Simple class for generating actions for particles in a simulation that can be read using pynbody. The action calculation is done with galpy.
See the notebook for an example.
| import pynbody | |
| from pynbody import grav_omp | |
| import numpy as np | |
| from galpy.potential import Potential | |
| import hashlib | |
| class SnapshotPotential(Potential): | |
| """ | |
| Create a snapshot potential object. The potential and forces are | |
| calculated as needed through the _evaluate and _Rforce methods. | |
| **Input**: | |
| *s* : a simulation snapshot loaded with pynbody | |
| **Optional Keywords**: | |
| *num_threads* (4): number of threads to use for calculation | |
| """ | |
| def __init__(self, s, num_threads=4) : | |
| self.s = s | |
| self.point_hash = None | |
| self.pots = None | |
| self.rz_acc = None | |
| self._amp = 1.0 | |
| def _evaluate(self, R,z,phi=None,t=None,dR=None,dphi=None) : | |
| if isinstance(R,float) : | |
| R = np.array([R]) | |
| if isinstance(z, float) : | |
| z = np.array([z]) | |
| new_point_hash = hashlib.md5(np.array([R,z])).hexdigest() | |
| if self.pots is None or new_point_hash != self.point_hash: | |
| self.setup_potential(R,z) | |
| self.point_hash = hashlib.md5(np.array([R,z])).hexdigest() | |
| return self.pots | |
| def _Rforce(self, R,z,phi=None,t=None,dR=None,dphi=None) : | |
| if isinstance(R,float) : | |
| R = np.array([R]) | |
| if isinstance(z, float) : | |
| z = np.array([z]) | |
| new_point_hash = hashlib.md5(np.array([R,z])).hexdigest() | |
| if self.rz_acc is None or new_point_hash != self.point_hash: | |
| self.setup_potential(R,z) | |
| self.point_hash = hashlib.md5(np.array([R,z])).hexdigest() | |
| return self.rz_acc[:,0] | |
| def setup_potential(self, R, z) : | |
| # | |
| # set up the four points per R,z pair to mimic axisymmetry | |
| # | |
| points = np.zeros((len(R),len(z),4,3)) | |
| for i in xrange(len(R)) : | |
| for j in xrange(len(z)) : | |
| points[i,j] = [(R[i],0,z[j]), | |
| (0,R[i],z[j]), | |
| (-R[i],0,z[j]), | |
| (0,-R[i],z[j])] | |
| points_new = points.reshape(points.size/3,3) | |
| pot, acc = grav_omp.direct(self.s,points_new,num_threads=4) | |
| pot = pot.reshape(len(R)*len(z),4) | |
| acc = acc.reshape(len(R)*len(z),4,3) | |
| # | |
| # need to average the potentials | |
| # | |
| if len(pot) > 1: | |
| pot = pot.mean(axis=1) | |
| else : | |
| pot = pot.mean() | |
| # | |
| # get the radial accelerations | |
| # | |
| rz_acc = np.zeros((len(R)*len(z),2)) | |
| rvecs = [(1.0,0.0,0.0), | |
| (0.0,1.0,0.0), | |
| (-1.0,0.0,0.0), | |
| (0.0,-1.0,0.0)] | |
| # reshape the acc to make sure we have a leading index even | |
| # if we are only evaluating a single point, i.e. we have | |
| # shape = (1,4,3) not (4,3) | |
| acc = acc.reshape((len(rz_acc),4,3)) | |
| for i in xrange(len(R)) : | |
| for j,rvec in enumerate(rvecs) : | |
| rz_acc[i,0] += acc[i,j].dot(rvec) | |
| rz_acc[i,1] += acc[i,j,2] | |
| rz_acc /= 4.0 | |
| self.pots = pot | |
| self.rz_acc = rz_acc |