galpy-1.3.0/0000755000076500000240000000000013236420072012730 5ustar bovystaff00000000000000galpy-1.3.0/AUTHORS.txt0000644000076500000240000000240613236350147014625 0ustar bovystaff00000000000000Author: ======= Jo Bovy Major contributions: ===================== Aladdin Seaifan (KuzminDiskPotential,SCFPotential) Direct contributions to the code base: ======================================= Denis Erkal (_mass for TwoPowerSphericalPotential; also simulations to test streamgapdf) Mark Fardal (streamdf improvements) Rok Roskar (first implementation of SnapshotRZPotential and InterpSnapshotRZPotential) Jason Sanders (velocity kicks induced by interactions with Plummer, Hernquist, and general potentials, currently in streamgapdf) Wilma Trick (KuzminKutuzovPotential and various other contributions) Anna Juranova (FerrersPotential) Semyeong Oh (FerrersPotential) Jack Hong (SpiralArmsPotential) Ted Mackereth (addition to estimateDeltaStaeckel function, and fast orbit characterisation docs) General help: ============== Greg Stinson (advice on SnapshotRZPotential tests) Matt Craig (added conda build) Brigitta Sipocz (help with conda build) Bug reports: ============ (incomplete list, apologies if I forgot anybody!) Alexander de la Vega Wai-Hin Ngan Wilma Trick T. E. Pickering Željko Ivezić Resmi Lekshmi Rok Roskar Dylan Gregersen Eduardo Balbinot Semyeong Oh Jorrit Hagen Ruth Angus Anthony Brown Larry Widrow Jean-François Robitaille galpy-1.3.0/galpy/0000755000076500000240000000000013236420072014044 5ustar bovystaff00000000000000galpy-1.3.0/galpy/__init__.py0000644000076500000240000000002613236414631016157 0ustar bovystaff00000000000000__version__ = "1.3.0" galpy-1.3.0/galpy/actionAngle.py0000644000076500000240000000274113236350147016653 0ustar bovystaff00000000000000from galpy.actionAngle_src import actionAngle from galpy.actionAngle_src import actionAngleAxi from galpy.actionAngle_src import actionAngleAdiabatic from galpy.actionAngle_src import actionAngleAdiabaticGrid from galpy.actionAngle_src import actionAngleStaeckel from galpy.actionAngle_src import actionAngleStaeckelGrid from galpy.actionAngle_src import actionAngleIsochrone from galpy.actionAngle_src import actionAngleIsochroneApprox from galpy.actionAngle_src import actionAngleSpherical from galpy.actionAngle_src import actionAngleTorus # # Exceptions # UnboundError= actionAngle.UnboundError # # Functions # estimateDeltaStaeckel= actionAngleStaeckel.estimateDeltaStaeckel estimateBIsochrone= actionAngleIsochroneApprox.estimateBIsochrone dePeriod= actionAngleIsochroneApprox.dePeriod # # Classes # actionAngle= actionAngle.actionAngle actionAngleAxi= actionAngleAxi.actionAngleAxi actionAngleAdiabatic= actionAngleAdiabatic.actionAngleAdiabatic actionAngleAdiabaticGrid= actionAngleAdiabaticGrid.actionAngleAdiabaticGrid actionAngleStaeckelSingle= actionAngleStaeckel.actionAngleStaeckelSingle actionAngleStaeckel= actionAngleStaeckel.actionAngleStaeckel actionAngleStaeckelGrid= actionAngleStaeckelGrid.actionAngleStaeckelGrid actionAngleIsochrone= actionAngleIsochrone.actionAngleIsochrone actionAngleIsochroneApprox=\ actionAngleIsochroneApprox.actionAngleIsochroneApprox actionAngleSpherical= actionAngleSpherical.actionAngleSpherical actionAngleTorus= actionAngleTorus.actionAngleTorus galpy-1.3.0/galpy/actionAngle_src/0000755000076500000240000000000013236420072017137 5ustar bovystaff00000000000000galpy-1.3.0/galpy/actionAngle_src/__init__.py0000644000076500000240000000000012671130470021240 0ustar bovystaff00000000000000galpy-1.3.0/galpy/actionAngle_src/actionAngle.py0000644000076500000240000003001013231751437021736 0ustar bovystaff00000000000000from six import with_metaclass import types import copy import math as m from galpy.util import config from galpy.util.bovy_conversion import physical_conversion_actionAngle, \ actionAngle_physical_input _APY_LOADED= True try: from astropy import units except ImportError: _APY_LOADED= False # Metaclass for copying docstrings from subclass methods, first func # to copy func def copyfunc(func): return types.FunctionType(func.__code__,func.__globals__, name=func.__name__, argdefs=func.__defaults__, closure=func.__closure__) class MetaActionAngle(type): """Metaclass to assign subclass' docstrings for methods _evaluate, _actionsFreqs, _actionsFreqsAngles, and _EccZmaxRperiRap to their public cousins __call__, actionsFreqs, etc.""" def __new__(meta,name,bases,attrs): for key in copy.copy(attrs): # copy bc size changes if key[0] == '_': skey= copy.copy(key[1:]) if skey == 'evaluate': skey= '__call__' for base in bases: original= getattr(base,skey,None) if original is not None: funccopy= copyfunc(original) funccopy.__doc__= attrs[key].__doc__ attrs[skey]= funccopy break return type.__new__(meta,name,bases,attrs) # Python 2 & 3 compatible way to have a metaclass class actionAngle(with_metaclass(MetaActionAngle,object)): """Top-level class for actionAngle classes""" def __init__(self,ro=None,vo=None): """ NAME: __init__ PURPOSE: initialize an actionAngle object INPUT: ro= (None) distance scale vo= (None) velocity scale OUTPUT: HISTORY: 2016-02-18 - Written - Bovy (UofT) """ # Parse ro and vo if ro is None: self._ro= config.__config__.getfloat('normalization','ro') self._roSet= False else: if _APY_LOADED and isinstance(ro,units.Quantity): ro= ro.to(units.kpc).value self._ro= ro self._roSet= True if vo is None: self._vo= config.__config__.getfloat('normalization','vo') self._voSet= False else: if _APY_LOADED and isinstance(vo,units.Quantity): vo= vo.to(units.km/units.s).value self._vo= vo self._voSet= True return None def _check_consistent_units(self): """Internal function to check that the set of units for this object is consistent with that for the potential""" if isinstance(self._pot,list): if self._roSet and self._pot[0]._roSet: assert m.fabs(self._ro-self._pot[0]._ro) < 10.**-10., 'Physical conversion for the actionAngle object is not consistent with that of the Potential given to it' if self._voSet and self._pot[0]._voSet: assert m.fabs(self._vo-self._pot[0]._vo) < 10.**-10., 'Physical conversion for the actionAngle object is not consistent with that of the Potential given to it' else: if self._roSet and self._pot._roSet: assert m.fabs(self._ro-self._pot._ro) < 10.**-10., 'Physical conversion for the actionAngle object is not consistent with that of the Potential given to it' if self._voSet and self._pot._voSet: assert m.fabs(self._vo-self._pot._vo) < 10.**-10., 'Physical conversion for the actionAngle object is not consistent with that of the Potential given to it' return None def _check_consistent_units_orbitInput(self,orb): """Internal function to check that the set of units for this object is consistent with that for an input orbit""" if self._roSet and orb._roSet: assert m.fabs(self._ro-orb._ro) < 10.**-10., 'Physical conversion for the actionAngle object is not consistent with that of the Orbit given to it' if self._voSet and orb._voSet: assert m.fabs(self._vo-orb._vo) < 10.**-10., 'Physical conversion for the actionAngle object is not consistent with that of the Orbit given to it' return None def turn_physical_off(self): """ NAME: turn_physical_off PURPOSE: turn off automatic returning of outputs in physical units INPUT: (none) OUTPUT: (none) HISTORY: 2017-06-05 - Written - Bovy (UofT) """ self._roSet= False self._voSet= False return None def turn_physical_on(self,ro=None,vo=None): """ NAME: turn_physical_on PURPOSE: turn on automatic returning of outputs in physical units INPUT: ro= reference distance (kpc; can be Quantity) vo= reference velocity (km/s; can be Quantity) OUTPUT: (none) HISTORY: 2016-06-05 - Written - Bovy (UofT) """ self._roSet= True self._voSet= True if not ro is None: if _APY_LOADED and isinstance(ro,units.Quantity): ro= ro.to(units.kpc).value self._ro= ro if not vo is None: if _APY_LOADED and isinstance(vo,units.Quantity): vo= vo.to(units.km/units.s).value self._vo= vo return None def _parse_eval_args(self,*args,**kwargs): """ NAME: _parse_eval_args PURPOSE: Internal function to parse the arguments given for an action/frequency/angle evaluation INPUT: OUTPUT: HISTORY: 2010-07-11 - Written - Bovy (NYU) """ if len(args) == 3: #R,vR.vT R,vR,vT= args self._eval_R= R self._eval_vR= vR self._eval_vT= vT self._eval_z= 0. self._eval_vz= 0. elif len(args) == 5: #R,vR.vT, z, vz R,vR,vT, z, vz= args self._eval_R= R self._eval_vR= vR self._eval_vT= vT self._eval_z= z self._eval_vz= vz elif len(args) == 6: #R,vR.vT, z, vz, phi R,vR,vT, z, vz, phi= args self._eval_R= R self._eval_vR= vR self._eval_vT= vT self._eval_z= z self._eval_vz= vz self._eval_phi= phi else: if not kwargs.get('_noOrbUnitsCheck',False): self._check_consistent_units_orbitInput(args[0]) if len(args) == 2: vxvv= args[0](args[1])._orb.vxvv else: try: vxvv= args[0]._orb.vxvv except AttributeError: #if we're given an OrbitTop instance vxvv= args[0].vxvv self._eval_R= vxvv[0] self._eval_vR= vxvv[1] self._eval_vT= vxvv[2] if len(vxvv) > 4: self._eval_z= vxvv[3] self._eval_vz= vxvv[4] if len(vxvv) > 5: self._eval_phi= vxvv[5] elif len(vxvv) > 3: self._eval_phi= vxvv[3] self._eval_z= 0. self._eval_vz= 0. else: self._eval_z= 0. self._eval_vz= 0. if hasattr(self,'_eval_z'): #calculate the polar angle if self._eval_z == 0.: self._eval_theta= m.pi/2. else: self._eval_theta= m.atan(self._eval_R/self._eval_z) return None @actionAngle_physical_input @physical_conversion_actionAngle('__call__',pop=True) def __call__(self,*args,**kwargs): """ NAME: __call__ PURPOSE: evaluate the actions (jr,lz,jz) INPUT: Either: a) R,vR,vT,z,vz[,phi]: 1) floats: phase-space value for single object (phi is optional) (each can be a Quantity) 2) numpy.ndarray: [N] phase-space values for N objects (each can be a Quantity) b) Orbit instance: initial condition used if that's it, orbit(t) if there is a time given as well as the second argument OUTPUT: (jr,lz,jz) HISTORY: 2014-01-03 - Written for top level - Bovy (IAS) """ try: return self._evaluate(*args,**kwargs) except AttributeError: #pragma: no cover raise NotImplementedError("'__call__' method not implemented for this actionAngle module") @actionAngle_physical_input @physical_conversion_actionAngle('actionsFreqs',pop=True) def actionsFreqs(self,*args,**kwargs): """ NAME: actionsFreqs PURPOSE: evaluate the actions and frequencies (jr,lz,jz,Omegar,Omegaphi,Omegaz) INPUT: Either: a) R,vR,vT,z,vz[,phi]: 1) floats: phase-space value for single object (phi is optional) (each can be a Quantity) 2) numpy.ndarray: [N] phase-space values for N objects (each can be a Quantity) b) Orbit instance: initial condition used if that's it, orbit(t) if there is a time given as well as the second argument OUTPUT: (jr,lz,jz,Omegar,Omegaphi,Omegaz) HISTORY: 2014-01-03 - Written for top level - Bovy (IAS) """ try: return self._actionsFreqs(*args,**kwargs) except AttributeError: #pragma: no cover raise NotImplementedError("'actionsFreqs' method not implemented for this actionAngle module") @actionAngle_physical_input @physical_conversion_actionAngle('actionsFreqsAngles',pop=True) def actionsFreqsAngles(self,*args,**kwargs): """ NAME: actionsFreqsAngles PURPOSE: evaluate the actions, frequencies, and angles (jr,lz,jz,Omegar,Omegaphi,Omegaz,angler,anglephi,anglez) INPUT: Either: a) R,vR,vT,z,vz,phi: 1) floats: phase-space value for single object (phi is optional) (each can be a Quantity) 2) numpy.ndarray: [N] phase-space values for N objects (each can be a Quantity) b) Orbit instance: initial condition used if that's it, orbit(t) if there is a time given as well as the second argument OUTPUT: (jr,lz,jz,Omegar,Omegaphi,Omegaz,angler,anglephi,anglez) HISTORY: 2014-01-03 - Written for top level - Bovy (IAS) """ try: return self._actionsFreqsAngles(*args,**kwargs) except AttributeError: #pragma: no cover raise NotImplementedError("'actionsFreqsAngles' method not implemented for this actionAngle module") @actionAngle_physical_input @physical_conversion_actionAngle('EccZmaxRperiRap',pop=True) def EccZmaxRperiRap(self,*args,**kwargs): """ NAME: EccZmaxRperiRap PURPOSE: evaluate the eccentricity, maximum height above the plane, peri- and apocenter INPUT: Either: a) R,vR,vT,z,vz[,phi]: 1) floats: phase-space value for single object (phi is optional) (each can be a Quantity) 2) numpy.ndarray: [N] phase-space values for N objects (each can be a Quantity) b) Orbit instance: initial condition used if that's it, orbit(t) if there is a time given as well as the second argument OUTPUT: (e,zmax,rperi,rap) HISTORY: 2017-12-12 - Written - Bovy (UofT) """ try: return self._EccZmaxRperiRap(*args,**kwargs) except AttributeError: #pragma: no cover raise NotImplementedError("'EccZmaxRperiRap' method not implemented for this actionAngle module") class UnboundError(Exception): #pragma: no cover def __init__(self, value): self.value = value def __str__(self): return repr(self.value) galpy-1.3.0/galpy/actionAngle_src/actionAngle_c_ext/0000755000076500000240000000000013236420072022545 5ustar bovystaff00000000000000galpy-1.3.0/galpy/actionAngle_src/actionAngle_c_ext/actionAngle.c0000644000076500000240000002310113236350147025137 0ustar bovystaff00000000000000#include #include #include #include #include void parse_actionAngleArgs(int npot, struct potentialArg * potentialArgs, int ** pot_type, double ** pot_args, bool forTorus){ int ii,jj,kk; int nR, nz; double * Rgrid, * zgrid, * potGrid_splinecoeffs; init_potentialArgs(npot,potentialArgs); for (ii=0; ii < npot; ii++){ switch ( *(*pot_type)++ ) { case 0: //LogarithmicHaloPotential, 4 arguments potentialArgs->potentialEval= &LogarithmicHaloPotentialEval; potentialArgs->Rforce= &LogarithmicHaloPotentialRforce; potentialArgs->zforce= &LogarithmicHaloPotentialzforce; potentialArgs->nargs= 4; break; case 5: //MiyamotoNagaiPotential, 3 arguments potentialArgs->potentialEval= &MiyamotoNagaiPotentialEval; potentialArgs->Rforce= &MiyamotoNagaiPotentialRforce; potentialArgs->zforce= &MiyamotoNagaiPotentialzforce; potentialArgs->nargs= 3; break; case 7: //PowerSphericalPotential, 2 arguments potentialArgs->potentialEval= &PowerSphericalPotentialEval; potentialArgs->Rforce= &PowerSphericalPotentialRforce; potentialArgs->zforce= &PowerSphericalPotentialzforce; potentialArgs->nargs= 2; break; case 8: //HernquistPotential, 2 arguments potentialArgs->potentialEval= &HernquistPotentialEval; potentialArgs->Rforce= &HernquistPotentialRforce; potentialArgs->zforce= &HernquistPotentialzforce; potentialArgs->nargs= 2; break; case 9: //NFWPotential, 2 arguments potentialArgs->potentialEval= &NFWPotentialEval; potentialArgs->Rforce= &NFWPotentialRforce; potentialArgs->zforce= &NFWPotentialzforce; potentialArgs->nargs= 2; break; case 10: //JaffePotential, 2 arguments potentialArgs->potentialEval= &JaffePotentialEval; potentialArgs->Rforce= &JaffePotentialRforce; potentialArgs->zforce= &JaffePotentialzforce; potentialArgs->nargs= 2; break; case 11: //DoubleExponentialDiskPotential, XX arguments potentialArgs->potentialEval= &DoubleExponentialDiskPotentialEval; potentialArgs->Rforce= &DoubleExponentialDiskPotentialRforce; potentialArgs->zforce= &DoubleExponentialDiskPotentialzforce; //Look at pot_args to figure out the number of arguments potentialArgs->nargs= (int) (8 + 2 * *(*pot_args+5) + 4 * ( *(*pot_args+4) + 1)); break; case 12: //FlattenedPowerPotential, 4 arguments potentialArgs->potentialEval= &FlattenedPowerPotentialEval; potentialArgs->Rforce= &FlattenedPowerPotentialRforce; potentialArgs->zforce= &FlattenedPowerPotentialzforce; potentialArgs->nargs= 4; break; case 13: //interpRZPotential, XX arguments //Grab the grids and the coefficients nR= (int) *(*pot_args)++; nz= (int) *(*pot_args)++; Rgrid= (double *) malloc ( nR * sizeof ( double ) ); zgrid= (double *) malloc ( nz * sizeof ( double ) ); potGrid_splinecoeffs= (double *) malloc ( nR * nz * sizeof ( double ) ); for (kk=0; kk < nR; kk++) *(Rgrid+kk)= *(*pot_args)++; for (kk=0; kk < nz; kk++) *(zgrid+kk)= *(*pot_args)++; for (kk=0; kk < nR; kk++) put_row(potGrid_splinecoeffs,kk,*pot_args+kk*nz,nz); *pot_args+= nR*nz; potentialArgs->i2d= interp_2d_alloc(nR,nz); interp_2d_init(potentialArgs->i2d,Rgrid,zgrid,potGrid_splinecoeffs, INTERP_2D_LINEAR); //latter bc we already calculated the coeffs potentialArgs->accx= gsl_interp_accel_alloc (); potentialArgs->accy= gsl_interp_accel_alloc (); potentialArgs->potentialEval= &interpRZPotentialEval; if ( forTorus == true ) { for (kk=0; kk < nR; kk++) put_row(potGrid_splinecoeffs,kk,*pot_args+kk*nz,nz); *pot_args+= nR*nz; potentialArgs->i2drforce= interp_2d_alloc(nR,nz); interp_2d_init(potentialArgs->i2drforce,Rgrid,zgrid,potGrid_splinecoeffs, INTERP_2D_LINEAR); //latter bc we already calculated the coeffs potentialArgs->accxrforce= gsl_interp_accel_alloc (); potentialArgs->accyrforce= gsl_interp_accel_alloc (); for (kk=0; kk < nR; kk++) put_row(potGrid_splinecoeffs,kk,*pot_args+kk*nz,nz); *pot_args+= nR*nz; potentialArgs->i2dzforce= interp_2d_alloc(nR,nz); interp_2d_init(potentialArgs->i2dzforce,Rgrid,zgrid,potGrid_splinecoeffs, INTERP_2D_LINEAR); //latter bc we already calculated the coeffs potentialArgs->accxzforce= gsl_interp_accel_alloc (); potentialArgs->accyzforce= gsl_interp_accel_alloc (); potentialArgs->Rforce= &interpRZPotentialRforce; potentialArgs->zforce= &interpRZPotentialzforce; } potentialArgs->nargs= 2; //clean up free(Rgrid); free(zgrid); free(potGrid_splinecoeffs); break; case 14: //IsochronePotential, 2 arguments potentialArgs->potentialEval= &IsochronePotentialEval; potentialArgs->Rforce= &IsochronePotentialRforce; potentialArgs->zforce= &IsochronePotentialzforce; potentialArgs->nargs= 2; break; case 15: //PowerSphericalPotentialwCutoff, 3 arguments potentialArgs->potentialEval= &PowerSphericalPotentialwCutoffEval; potentialArgs->Rforce= &PowerSphericalPotentialwCutoffRforce; potentialArgs->zforce= &PowerSphericalPotentialwCutoffzforce; potentialArgs->nargs= 3; break; case 16: //KuzminKutuzovStaeckelPotential, 3 arguments potentialArgs->potentialEval= &KuzminKutuzovStaeckelPotentialEval; potentialArgs->Rforce= &KuzminKutuzovStaeckelPotentialRforce; potentialArgs->zforce= &KuzminKutuzovStaeckelPotentialzforce; potentialArgs->nargs= 3; break; case 17: //PlummerPotential, 2 arguments potentialArgs->potentialEval= &PlummerPotentialEval; potentialArgs->Rforce= &PlummerPotentialRforce; potentialArgs->zforce= &PlummerPotentialzforce; potentialArgs->nargs= 2; break; case 18: //PseudoIsothermalPotential, 2 arguments potentialArgs->potentialEval= &PseudoIsothermalPotentialEval; potentialArgs->Rforce= &PseudoIsothermalPotentialRforce; potentialArgs->zforce= &PseudoIsothermalPotentialzforce; potentialArgs->nargs= 2; break; case 19: //KuzminDiskPotential, 2 arguments potentialArgs->potentialEval= &KuzminDiskPotentialEval; potentialArgs->Rforce= &KuzminDiskPotentialRforce; potentialArgs->zforce= &KuzminDiskPotentialzforce; potentialArgs->nargs= 2; break; case 20: //BurkertPotential, 2 arguments potentialArgs->potentialEval= &BurkertPotentialEval; potentialArgs->Rforce= &BurkertPotentialRforce; potentialArgs->zforce= &BurkertPotentialzforce; potentialArgs->nargs= 2; break; case 21: //TriaxialHernquistPotential, lots of arguments potentialArgs->potentialEval= &TriaxialHernquistPotentialEval; potentialArgs->Rforce= &TriaxialHernquistPotentialRforce; potentialArgs->zforce= &TriaxialHernquistPotentialzforce; potentialArgs->nargs= (int) (21 + 2 * *(*pot_args+14)); break; case 22: //TriaxialNFWPotential, lots of arguments potentialArgs->potentialEval= &TriaxialNFWPotentialEval; potentialArgs->Rforce= &TriaxialNFWPotentialRforce; potentialArgs->zforce= &TriaxialNFWPotentialzforce; potentialArgs->nargs= (int) (21 + 2 * *(*pot_args+14)); break; case 23: //TriaxialJaffePotential, lots of arguments potentialArgs->potentialEval= &TriaxialJaffePotentialEval; potentialArgs->Rforce= &TriaxialJaffePotentialRforce; potentialArgs->zforce= &TriaxialJaffePotentialzforce; potentialArgs->nargs= (int) (21 + 2 * *(*pot_args+14)); break; case 24: //SCFPotential, many arguments potentialArgs->potentialEval= &SCFPotentialEval; potentialArgs->Rforce= &SCFPotentialRforce; potentialArgs->zforce= &SCFPotentialzforce; potentialArgs->nargs= (int) (5 + (1 + *(*pot_args + 1)) * *(*pot_args+2) * *(*pot_args+3)* *(*pot_args+4) + 7); break; case 25: //SoftenedNeedleBarPotential, 13 arguments potentialArgs->potentialEval= &SoftenedNeedleBarPotentialEval; potentialArgs->Rforce= &SoftenedNeedleBarPotentialRforce; potentialArgs->zforce= &SoftenedNeedleBarPotentialzforce; potentialArgs->nargs= (int) 13; break; case 26: //DiskSCFPotential, nsigma+3 arguments potentialArgs->potentialEval= &DiskSCFPotentialEval; potentialArgs->Rforce= &DiskSCFPotentialRforce; potentialArgs->zforce= &DiskSCFPotentialzforce; potentialArgs->nargs= (int) **pot_args + 3; break; //////////////////////////////// WRAPPERS ///////////////////////////////////// case -1: //DehnenSmoothWrapperPotential potentialArgs->potentialEval= &DehnenSmoothWrapperPotentialEval; potentialArgs->Rforce= &DehnenSmoothWrapperPotentialRforce; potentialArgs->zforce= &DehnenSmoothWrapperPotentialzforce; potentialArgs->nargs= (int) 3; break; } if ( *(*pot_type-1) < 0 ) { // Parse wrapped potential for wrappers potentialArgs->nwrapped= (int) *(*pot_args)++; potentialArgs->wrappedPotentialArg= \ (struct potentialArg *) malloc ( potentialArgs->nwrapped \ * sizeof (struct potentialArg) ); parse_actionAngleArgs(potentialArgs->nwrapped, potentialArgs->wrappedPotentialArg, pot_type,pot_args,forTorus); } potentialArgs->args= (double *) malloc( potentialArgs->nargs * sizeof(double)); for (jj=0; jj < potentialArgs->nargs; jj++){ *(potentialArgs->args)= *(*pot_args)++; potentialArgs->args++; } potentialArgs->args-= potentialArgs->nargs; potentialArgs++; } potentialArgs-= npot; } galpy-1.3.0/galpy/actionAngle_src/actionAngle_c_ext/actionAngle.h0000644000076500000240000000213413236350147025147 0ustar bovystaff00000000000000/* C code for actionAngle calculations */ #ifndef __GALPY_ACTIONANGLE_H__ #define __GALPY_ACTIONANGLE_H__ #ifdef __cplusplus extern "C" { #endif #include #include #include #include "interp_2d.h" /* Macro for dealing with potentially unused variables due to OpenMP */ /* If we're not using GNU C, elide __attribute__ if it doesn't exist*/ #ifndef __has_attribute // Compatibility with non-clang compilers. #define __has_attribute(x) 0 #endif #if defined(__GNUC__) || __has_attribute(unused) # define UNUSED __attribute__((unused)) #else # define UNUSED /*NOTHING*/ #endif /* Structure declarations */ struct actionAngleArg{ //I think this isn't used JB 06/24/14 double (*potentialEval)(double R, double Z, double phi, double t, int nargs, double * args); int nargs; double * args; interp_2d * i2d; gsl_interp_accel * acc; }; struct pragmasolver{ gsl_root_fsolver *s; }; /* Function declarations */ void parse_actionAngleArgs(int,struct potentialArg *,int **,double **,bool); #ifdef __cplusplus } #endif #endif /* actionAngle.h */ galpy-1.3.0/galpy/actionAngle_src/actionAngle_c_ext/actionAngleAdiabatic.c0000644000076500000240000004444213236350147026734 0ustar bovystaff00000000000000/* C code for the adiabatic approximation */ #include #include #include #include #include #include #include #include #include #ifdef _OPENMP #include #endif #define CHUNKSIZE 10 //Potentials #include #include #ifndef M_PI #define M_PI 3.14159265358979323846 #endif /* Structure Declarations */ struct JRAdiabaticArg{ double ER; double Lz22; int nargs; struct potentialArg * actionAngleArgs; }; struct JzAdiabaticArg{ double Ez; double R; int nargs; struct potentialArg * actionAngleArgs; }; /* Function Declarations */ void actionAngleAdiabatic_RperiRapZmax(int,double *,double *,double *,double *, double *,int,int *,double *,double, double *,double *,double *,int *); void actionAngleAdiabatic_actions(int,double *,double *,double *,double *, double *,int,int *,double *,double, double *,double *,int *); void calcJRAdiabatic(int,double *,double *,double *,double *,double *, int,struct potentialArg *,int); void calcJzAdiabatic(int,double *,double *,double *,double *,int, struct potentialArg *,int); void calcRapRperi(int,double *,double *,double *,double *,double *, int,struct potentialArg *); void calcZmax(int,double *,double *,double *,double *,int, struct potentialArg *); double JRAdiabaticIntegrandSquared(double,void *); double JRAdiabaticIntegrand(double,void *); double JzAdiabaticIntegrandSquared(double,void *); double JzAdiabaticIntegrand(double,void *); double evaluateVerticalPotentials(double, double,int, struct potentialArg *); /* Actual functions, inlines first */ inline void calcEREzL(int ndata, double *R, double *vR, double *vT, double *z, double *vz, double *ER, double *Ez, double *Lz, int nargs, struct potentialArg * actionAngleArgs){ int ii; UNUSED int chunk= CHUNKSIZE; #pragma omp parallel for schedule(static,chunk) private(ii) for (ii=0; ii < ndata; ii++){ *(ER+ii)= evaluatePotentials(*(R+ii),0., nargs,actionAngleArgs) + 0.5 * *(vR+ii) * *(vR+ii) + 0.5 * *(vT+ii) * *(vT+ii); *(Ez+ii)= evaluateVerticalPotentials(*(R+ii),*(z+ii), nargs,actionAngleArgs) + 0.5 * *(vz+ii) * *(vz+ii); *(Lz+ii)= *(R+ii) * *(vT+ii); } } /* MAIN FUNCTIONS */ void actionAngleAdiabatic_RperiRapZmax(int ndata, double *R, double *vR, double *vT, double *z, double *vz, int npot, int * pot_type, double * pot_args, double gamma, double *rperi, double *rap, double *zmax, int * err){ int ii; //Set up the potentials struct potentialArg * actionAngleArgs= (struct potentialArg *) malloc ( npot * sizeof (struct potentialArg) ); parse_actionAngleArgs(npot,actionAngleArgs,&pot_type,&pot_args,false); //ER, Ez, Lz double *ER= (double *) malloc ( ndata * sizeof(double) ); double *Ez= (double *) malloc ( ndata * sizeof(double) ); double *Lz= (double *) malloc ( ndata * sizeof(double) ); calcEREzL(ndata,R,vR,vT,z,vz,ER,Ez,Lz,npot,actionAngleArgs); //Calculate peri and apocenters double *jz= (double *) malloc ( ndata * sizeof(double) ); calcZmax(ndata,zmax,z,R,Ez,npot,actionAngleArgs); calcJzAdiabatic(ndata,jz,zmax,R,Ez,npot,actionAngleArgs,10); //Adjust planar effective potential for gamma UNUSED int chunk= CHUNKSIZE; #pragma omp parallel for schedule(static,chunk) private(ii) for (ii=0; ii < ndata; ii++){ *(Lz+ii)= fabs( *(Lz+ii) ) + gamma * *(jz+ii); *(ER+ii)+= 0.5 * *(Lz+ii) * *(Lz+ii) / *(R+ii) / *(R+ii) - 0.5 * *(vT+ii) * *(vT+ii); } calcRapRperi(ndata,rperi,rap,R,ER,Lz,npot,actionAngleArgs); free_potentialArgs(npot,actionAngleArgs); free(actionAngleArgs); free(ER); free(Ez); free(Lz); free(jz); } void actionAngleAdiabatic_actions(int ndata, double *R, double *vR, double *vT, double *z, double *vz, int npot, int * pot_type, double * pot_args, double gamma, double *jr, double *jz, int * err){ int ii; //Set up the potentials struct potentialArg * actionAngleArgs= (struct potentialArg *) malloc ( npot * sizeof (struct potentialArg) ); parse_actionAngleArgs(npot,actionAngleArgs,&pot_type,&pot_args,false); //ER, Ez, Lz double *ER= (double *) malloc ( ndata * sizeof(double) ); double *Ez= (double *) malloc ( ndata * sizeof(double) ); double *Lz= (double *) malloc ( ndata * sizeof(double) ); calcEREzL(ndata,R,vR,vT,z,vz,ER,Ez,Lz,npot,actionAngleArgs); //Calculate peri and apocenters double *rperi= (double *) malloc ( ndata * sizeof(double) ); double *rap= (double *) malloc ( ndata * sizeof(double) ); double *zmax= (double *) malloc ( ndata * sizeof(double) ); calcZmax(ndata,zmax,z,R,Ez,npot,actionAngleArgs); calcJzAdiabatic(ndata,jz,zmax,R,Ez,npot,actionAngleArgs,10); //Adjust planar effective potential for gamma UNUSED int chunk= CHUNKSIZE; #pragma omp parallel for schedule(static,chunk) private(ii) for (ii=0; ii < ndata; ii++){ *(Lz+ii)= fabs( *(Lz+ii) ) + gamma * *(jz+ii); *(ER+ii)+= 0.5 * *(Lz+ii) * *(Lz+ii) / *(R+ii) / *(R+ii) - 0.5 * *(vT+ii) * *(vT+ii); } calcRapRperi(ndata,rperi,rap,R,ER,Lz,npot,actionAngleArgs); calcJRAdiabatic(ndata,jr,rperi,rap,ER,Lz,npot,actionAngleArgs,10); free_potentialArgs(npot,actionAngleArgs); free(actionAngleArgs); free(ER); free(Ez); free(Lz); free(rperi); free(rap); free(zmax); } void calcJRAdiabatic(int ndata, double * jr, double * rperi, double * rap, double * ER, double * Lz, int nargs, struct potentialArg * actionAngleArgs, int order){ int ii, tid, nthreads; #ifdef _OPENMP nthreads = omp_get_max_threads(); #else nthreads = 1; #endif gsl_function * JRInt= (gsl_function *) malloc ( nthreads * sizeof(gsl_function) ); struct JRAdiabaticArg * params= (struct JRAdiabaticArg *) malloc ( nthreads * sizeof (struct JRAdiabaticArg) ); for (tid=0; tid < nthreads; tid++){ (params+tid)->nargs= nargs; (params+tid)->actionAngleArgs= actionAngleArgs; } //Setup integrator gsl_integration_glfixed_table * T= gsl_integration_glfixed_table_alloc (order); UNUSED int chunk= CHUNKSIZE; #pragma omp parallel for schedule(static,chunk) \ private(tid,ii) \ shared(jr,rperi,rap,JRInt,params,T,ER,Lz) for (ii=0; ii < ndata; ii++){ #ifdef _OPENMP tid= omp_get_thread_num(); #else tid = 0; #endif if ( *(rperi+ii) == -9999.99 || *(rap+ii) == -9999.99 ){ *(jr+ii)= 9999.99; continue; } if ( (*(rap+ii) - *(rperi+ii)) / *(rap+ii) < 0.000001 ){//circular *(jr+ii) = 0.; continue; } //Setup function (params+tid)->ER= *(ER+ii); (params+tid)->Lz22= 0.5 * *(Lz+ii) * *(Lz+ii); (JRInt+tid)->function = &JRAdiabaticIntegrand; (JRInt+tid)->params = params+tid; //Integrate *(jr+ii)= gsl_integration_glfixed (JRInt+tid,*(rperi+ii),*(rap+ii),T) * sqrt(2.) / M_PI; } free(JRInt); free(params); gsl_integration_glfixed_table_free ( T ); } void calcJzAdiabatic(int ndata, double * jz, double * zmax, double * R, double * Ez, int nargs, struct potentialArg * actionAngleArgs, int order){ int ii, tid, nthreads; #ifdef _OPENMP nthreads = omp_get_max_threads(); #else nthreads = 1; #endif gsl_function * JzInt= (gsl_function *) malloc ( nthreads * sizeof(gsl_function) ); struct JzAdiabaticArg * params= (struct JzAdiabaticArg *) malloc ( nthreads * sizeof (struct JzAdiabaticArg) ); for (tid=0; tid < nthreads; tid++){ (params+tid)->nargs= nargs; (params+tid)->actionAngleArgs= actionAngleArgs; } //Setup integrator gsl_integration_glfixed_table * T= gsl_integration_glfixed_table_alloc (order); UNUSED int chunk= CHUNKSIZE; #pragma omp parallel for schedule(static,chunk) \ private(tid,ii) \ shared(jz,zmax,JzInt,params,T,Ez,R) for (ii=0; ii < ndata; ii++){ #ifdef _OPENMP tid= omp_get_thread_num(); #else tid = 0; #endif if ( *(zmax+ii) == -9999.99 ){ *(jz+ii)= 9999.99; continue; } if ( *(zmax+ii) < 0.000001 ){//circular *(jz+ii) = 0.; continue; } //Setup function (params+tid)->Ez= *(Ez+ii); (params+tid)->R= *(R+ii); (JzInt+tid)->function = &JzAdiabaticIntegrand; (JzInt+tid)->params = params+tid; //Integrate *(jz+ii)= gsl_integration_glfixed (JzInt+tid,0.,*(zmax+ii),T) * 2 * sqrt(2.) / M_PI; } free(JzInt); free(params); gsl_integration_glfixed_table_free ( T ); } void calcRapRperi(int ndata, double * rperi, double * rap, double * R, double * ER, double * Lz, int nargs, struct potentialArg * actionAngleArgs){ int ii, tid, nthreads; #ifdef _OPENMP nthreads = omp_get_max_threads(); #else nthreads = 1; #endif double peps, meps; gsl_function * JRRoot= (gsl_function *) malloc ( nthreads * sizeof(gsl_function) ); struct JRAdiabaticArg * params= (struct JRAdiabaticArg *) malloc ( nthreads * sizeof (struct JRAdiabaticArg) ); //Setup solver int status; int iter, max_iter = 100; const gsl_root_fsolver_type *T; double R_lo, R_hi; struct pragmasolver *s= (struct pragmasolver *) malloc ( nthreads * sizeof (struct pragmasolver) ); T = gsl_root_fsolver_brent; for (tid=0; tid < nthreads; tid++){ (params+tid)->nargs= nargs; (params+tid)->actionAngleArgs= actionAngleArgs; (s+tid)->s= gsl_root_fsolver_alloc (T); } UNUSED int chunk= CHUNKSIZE; gsl_set_error_handler_off(); #pragma omp parallel for schedule(static,chunk) \ private(tid,ii,iter,status,R_lo,R_hi,meps,peps) \ shared(rperi,rap,JRRoot,params,s,R,ER,Lz,max_iter) for (ii=0; ii < ndata; ii++){ #ifdef _OPENMP tid= omp_get_thread_num(); #else tid = 0; #endif //Setup function (params+tid)->ER= *(ER+ii); (params+tid)->Lz22= 0.5 * *(Lz+ii) * *(Lz+ii); (JRRoot+tid)->params = params+tid; (JRRoot+tid)->function = &JRAdiabaticIntegrandSquared; //Find starting points for minimum if ( fabs(GSL_FN_EVAL(JRRoot+tid,*(R+ii))) < 0.0000001){ //we are at rap or rperi peps= GSL_FN_EVAL(JRRoot+tid,*(R+ii)+0.0000001); meps= GSL_FN_EVAL(JRRoot+tid,*(R+ii)-0.0000001); if ( fabs(peps) < 0.00000001 && fabs(meps) < 0.00000001 && peps*meps >= 0.) {//circular *(rperi+ii) = *(R+ii); *(rap+ii) = *(R+ii); } else if ( peps < 0. && meps > 0. ) {//umax *(rap+ii)= *(R+ii); R_lo= 0.9 * (*(R+ii) - 0.0000001); R_hi= *(R+ii) - 0.00000001; while ( GSL_FN_EVAL(JRRoot+tid,R_lo) >= 0. && R_lo > 0.000000001){ R_hi= R_lo; //this makes sure that brent evaluates using previous R_lo*= 0.9; } //Find root status = gsl_root_fsolver_set ((s+tid)->s, JRRoot+tid, R_lo, R_hi); if (status == GSL_EINVAL) { *(rperi+ii) = 0.;//Assume zero if below 0.000000001 continue; } iter= 0; do { iter++; status = gsl_root_fsolver_iterate ((s+tid)->s); R_lo = gsl_root_fsolver_x_lower ((s+tid)->s); R_hi = gsl_root_fsolver_x_upper ((s+tid)->s); status = gsl_root_test_interval (R_lo, R_hi, 9.9999999999999998e-13, 4.4408920985006262e-16); } while (status == GSL_CONTINUE && iter < max_iter); // LCOV_EXCL_START if (status == GSL_EINVAL) {//Shouldn't ever get here *(rperi+ii) = -9999.99; *(rap+ii) = -9999.99; continue; } // LCOV_EXCL_STOP *(rperi+ii) = gsl_root_fsolver_root ((s+tid)->s); } else if ( peps > 0. && meps < 0. ){//umin *(rperi+ii)= *(R+ii); R_lo= *(R+ii) + 0.0000001; R_hi= 1.1 * (*(R+ii) + 0.0000001); while ( GSL_FN_EVAL(JRRoot+tid,R_hi) >= 0. && R_hi < 37.5) { R_lo= R_hi; //this makes sure that brent evaluates using previous R_hi*= 1.1; } //Find root status = gsl_root_fsolver_set ((s+tid)->s, JRRoot+tid, R_lo, R_hi); if (status == GSL_EINVAL) { *(rperi+ii) = -9999.99; *(rap+ii) = -9999.99; continue; } iter= 0; do { iter++; status = gsl_root_fsolver_iterate ((s+tid)->s); R_lo = gsl_root_fsolver_x_lower ((s+tid)->s); R_hi = gsl_root_fsolver_x_upper ((s+tid)->s); status = gsl_root_test_interval (R_lo, R_hi, 9.9999999999999998e-13, 4.4408920985006262e-16); } while (status == GSL_CONTINUE && iter < max_iter); // LCOV_EXCL_START if (status == GSL_EINVAL) {//Shouldn't ever get here *(rperi+ii) = -9999.99; *(rap+ii) = -9999.99; continue; } // LCOV_EXCL_STOP *(rap+ii) = gsl_root_fsolver_root ((s+tid)->s); } } else { R_lo= 0.9 * *(R+ii); R_hi= *(R+ii); while ( GSL_FN_EVAL(JRRoot+tid,R_lo) >= 0. && R_lo > 0.000000001){ R_hi= R_lo; //this makes sure that brent evaluates using previous R_lo*= 0.9; } R_hi= (R_lo < 0.9 * *(R+ii)) ? R_lo / 0.9 / 0.9: *(R+ii); //Find root status = gsl_root_fsolver_set ((s+tid)->s, JRRoot+tid, R_lo, R_hi); if (status == GSL_EINVAL) { *(rperi+ii) = 0.;//Assume zero if below 0.000000001 } else { iter= 0; do { iter++; status = gsl_root_fsolver_iterate ((s+tid)->s); R_lo = gsl_root_fsolver_x_lower ((s+tid)->s); R_hi = gsl_root_fsolver_x_upper ((s+tid)->s); status = gsl_root_test_interval (R_lo, R_hi, 9.9999999999999998e-13, 4.4408920985006262e-16); } while (status == GSL_CONTINUE && iter < max_iter); // LCOV_EXCL_START if (status == GSL_EINVAL) {//Shouldn't ever get here *(rperi+ii) = -9999.99; *(rap+ii) = -9999.99; continue; } // LCOV_EXCL_STOP *(rperi+ii) = gsl_root_fsolver_root ((s+tid)->s); } //Find starting points for maximum R_lo= *(R+ii); R_hi= 1.1 * *(R+ii); while ( GSL_FN_EVAL(JRRoot+tid,R_hi) > 0. && R_hi < 37.5) { R_lo= R_hi; //this makes sure that brent evaluates using previous R_hi*= 1.1; } R_lo= (R_hi > 1.1 * *(R+ii)) ? R_hi / 1.1 / 1.1: *(R+ii); //Find root status = gsl_root_fsolver_set ((s+tid)->s, JRRoot+tid, R_lo, R_hi); if (status == GSL_EINVAL) { *(rperi+ii) = -9999.99; *(rap+ii) = -9999.99; continue; } iter= 0; do { iter++; status = gsl_root_fsolver_iterate ((s+tid)->s); R_lo = gsl_root_fsolver_x_lower ((s+tid)->s); R_hi = gsl_root_fsolver_x_upper ((s+tid)->s); status = gsl_root_test_interval (R_lo, R_hi, 9.9999999999999998e-13, 4.4408920985006262e-16); } while (status == GSL_CONTINUE && iter < max_iter); // LCOV_EXCL_START if (status == GSL_EINVAL) {//Shouldn't ever get here *(rperi+ii) = -9999.99; *(rap+ii) = -9999.99; continue; } // LCOV_EXCL_STOP *(rap+ii) = gsl_root_fsolver_root ((s+tid)->s); } } gsl_set_error_handler (NULL); for (tid=0; tid < nthreads; tid++) gsl_root_fsolver_free( (s+tid)->s); free(s); free(JRRoot); free(params); } void calcZmax(int ndata, double * zmax, double * z, double * R, double * Ez, int nargs, struct potentialArg * actionAngleArgs){ int ii, tid, nthreads; #ifdef _OPENMP nthreads = omp_get_max_threads(); #else nthreads = 1; #endif gsl_function * JzRoot= (gsl_function *) malloc ( nthreads * sizeof(gsl_function) ); struct JzAdiabaticArg * params= (struct JzAdiabaticArg *) malloc ( nthreads * sizeof (struct JzAdiabaticArg) ); //Setup solver int status; int iter, max_iter = 100; const gsl_root_fsolver_type *T; double z_lo, z_hi; struct pragmasolver *s= (struct pragmasolver *) malloc ( nthreads * sizeof (struct pragmasolver) ); T = gsl_root_fsolver_brent; for (tid=0; tid < nthreads; tid++){ (params+tid)->nargs= nargs; (params+tid)->actionAngleArgs= actionAngleArgs; (s+tid)->s= gsl_root_fsolver_alloc (T); } UNUSED int chunk= CHUNKSIZE; gsl_set_error_handler_off(); #pragma omp parallel for schedule(static,chunk) \ private(tid,ii,iter,status,z_lo,z_hi) \ shared(zmax,JzRoot,params,s,z,Ez,R,max_iter) for (ii=0; ii < ndata; ii++){ #ifdef _OPENMP tid= omp_get_thread_num(); #else tid = 0; #endif //Setup function (params+tid)->Ez= *(Ez+ii); (params+tid)->R= *(R+ii); (JzRoot+tid)->function = &JzAdiabaticIntegrandSquared; (JzRoot+tid)->params = params+tid; //Find starting points for minimum if ( fabs(GSL_FN_EVAL(JzRoot+tid,*(z+ii))) < 0.0000001){ //we are at zmax *(zmax+ii)= fabs( *(z+ii) ); } else { z_lo= fabs ( *(z+ii) ); z_hi= ( *(z+ii) == 0. ) ? 0.1: 1.1 * fabs( *(z+ii) ); while ( GSL_FN_EVAL(JzRoot+tid,z_hi) >= 0. && z_hi < 37.5) { z_lo= z_hi; //this makes sure that brent evaluates using previous z_hi*= 1.1; } //Find root status = gsl_root_fsolver_set ((s+tid)->s, JzRoot+tid, z_lo, z_hi); if (status == GSL_EINVAL) { *(zmax+ii) = -9999.99; continue; } iter= 0; do { iter++; status = gsl_root_fsolver_iterate ((s+tid)->s); z_lo = gsl_root_fsolver_x_lower ((s+tid)->s); z_hi = gsl_root_fsolver_x_upper ((s+tid)->s); status = gsl_root_test_interval (z_lo, z_hi, 9.9999999999999998e-13, 4.4408920985006262e-16); } while (status == GSL_CONTINUE && iter < max_iter); // LCOV_EXCL_START if (status == GSL_EINVAL) {//Shouldn't ever get here *(zmax+ii) = -9999.99; continue; } // LCOV_EXCL_STOP *(zmax+ii) = gsl_root_fsolver_root ((s+tid)->s); } } gsl_set_error_handler (NULL); for (tid=0; tid < nthreads; tid++) gsl_root_fsolver_free( (s+tid)->s); free(s); free(JzRoot); free(params); } double JRAdiabaticIntegrand(double R, void * p){ return sqrt(JRAdiabaticIntegrandSquared(R,p)); } double JRAdiabaticIntegrandSquared(double R, void * p){ struct JRAdiabaticArg * params= (struct JRAdiabaticArg *) p; return params->ER - evaluatePotentials(R,0.,params->nargs,params->actionAngleArgs) - params->Lz22 / R / R; } double JzAdiabaticIntegrand(double z, void * p){ return sqrt(JzAdiabaticIntegrandSquared(z,p)); } double JzAdiabaticIntegrandSquared(double z, void * p){ struct JzAdiabaticArg * params= (struct JzAdiabaticArg *) p; return params->Ez - evaluateVerticalPotentials(params->R,z, params->nargs, params->actionAngleArgs); } double evaluateVerticalPotentials(double R, double z, int nargs, struct potentialArg * actionAngleArgs){ return evaluatePotentials(R,z,nargs,actionAngleArgs) -evaluatePotentials(R,0.,nargs,actionAngleArgs); } galpy-1.3.0/galpy/actionAngle_src/actionAngle_c_ext/actionAngleStaeckel.c0000644000076500000240000021031513236350147026620 0ustar bovystaff00000000000000/* C code for Binney (2012)'s Staeckel approximation code */ #include #include #include #include #include #include #include #include #include #ifdef _OPENMP #include #endif #define CHUNKSIZE 10 //Potentials #include #include #ifndef M_PI #define M_PI 3.14159265358979323846 #endif /* Structure Declarations */ struct JRStaeckelArg{ double E; double Lz22delta; double I3U; double delta; double u0; double sinh2u0; double v0; double sin2v0; double potu0v0; int nargs; struct potentialArg * actionAngleArgs; }; struct JzStaeckelArg{ double E; double Lz22delta; double I3V; double delta; double u0; double cosh2u0; double sinh2u0; double potupi2; int nargs; struct potentialArg * actionAngleArgs; }; struct dJRStaeckelArg{ double E; double Lz22delta; double I3U; double delta; double u0; double sinh2u0; double v0; double sin2v0; double potu0v0; double umin; double umax; int nargs; struct potentialArg * actionAngleArgs; }; struct dJzStaeckelArg{ double E; double Lz22delta; double I3V; double delta; double u0; double cosh2u0; double sinh2u0; double potupi2; double vmin; int nargs; struct potentialArg * actionAngleArgs; }; struct u0EqArg{ double E; double Lz22delta; double delta; int nargs; struct potentialArg * actionAngleArgs; }; /* Function Declarations */ void calcu0(int,double *,double *,int,int *,double *,int,double*, double *,int *); void actionAngleStaeckel_uminUmaxVmin(int,double *,double *,double *,double *, double *,double *,int,int *,double *, int,double *,double *, double *,double *,int *); void actionAngleStaeckel_actions(int,double *,double *,double *,double *, double *,double *,int,int *,double *,int, double *,int,double *,double *,int *); void actionAngleStaeckel_actionsFreqsAngles(int,double *,double *,double *, double *,double *,double *, int,int *,double *, int,double *,int,double *,double *, double *,double *,double *, double *,double *,double *,int *); void actionAngleStaeckel_actionsFreqs(int,double *,double *,double *,double *, double *,double *,int,int *,double *, int,double *,int,double *,double *, double *,double *,double *,int *); void calcAnglesStaeckel(int,double *,double *,double *,double *,double *, double *,double *,double *,double *,double *,double *, double *,double *,double *,double *,double *,double *, double *,double *,double *,double *,double *,double *, double *,int,double *,double *,double *,double *, double *,double *,double *,double *,double *,double *, int,struct potentialArg *,int); void calcFreqsFromDerivsStaeckel(int,double *,double *,double *, double *,double *,double *, double *,double *,double *,double *); void calcdI3dJFromDerivsStaeckel(int,double *,double *,double *,double *, double *,double *,double *,double *); void calcJRStaeckel(int,double *,double *,double *,double *,double *,double *, int,double *,double *,double *,double *,double *,double *, int,struct potentialArg *,int); void calcJzStaeckel(int,double *,double *,double *,double *,double *,int, double *,double *,double *,double *,double *,int, struct potentialArg *,int); void calcdJRStaeckel(int,double *,double *,double *,double *,double *, double *,double *,double *,int, double *,double *,double *,double *,double *,double *,int, struct potentialArg *,int); void calcdJzStaeckel(int,double *,double *,double *,double *,double *, double *,double *,int,double *,double *,double *,double *, double *,int, struct potentialArg *,int); void calcUminUmax(int,double *,double *,double *,double *,double *,double *, double *,int,double *,double *,double *,double *,double *, double *,int,struct potentialArg *); void calcVmin(int,double *,double *,double *,double *,double *,double *,int, double *,double *,double *,double *,double *,int, struct potentialArg *); double JRStaeckelIntegrandSquared(double,void *); double JRStaeckelIntegrand(double,void *); double JzStaeckelIntegrandSquared(double,void *); double JzStaeckelIntegrand(double,void *); double dJRdEStaeckelIntegrand(double,void *); double dJRdELowStaeckelIntegrand(double,void *); double dJRdEHighStaeckelIntegrand(double,void *); double dJRdLzStaeckelIntegrand(double,void *); double dJRdLzLowStaeckelIntegrand(double,void *); double dJRdLzHighStaeckelIntegrand(double,void *); double dJRdI3StaeckelIntegrand(double,void *); double dJRdI3LowStaeckelIntegrand(double,void *); double dJRdI3HighStaeckelIntegrand(double,void *); double dJzdEStaeckelIntegrand(double,void *); double dJzdELowStaeckelIntegrand(double,void *); double dJzdEHighStaeckelIntegrand(double,void *); double dJzdLzStaeckelIntegrand(double,void *); double dJzdLzLowStaeckelIntegrand(double,void *); double dJzdLzHighStaeckelIntegrand(double,void *); double dJzdI3StaeckelIntegrand(double,void *); double dJzdI3LowStaeckelIntegrand(double,void *); double dJzdI3HighStaeckelIntegrand(double,void *); double u0Equation(double,void *); double evaluatePotentials(double,double,int, struct potentialArg *); double evaluatePotentialsUV(double,double,double,int,struct potentialArg *); /* Actual functions, inlines first */ inline void uv_to_Rz(double u, double v, double * R, double *z,double delta){ *R= delta * sinh(u) * sin(v); *z= delta * cosh(u) * cos(v); } inline void Rz_to_uv_vec(int ndata, double *R, double *z, double *u, double *v, int ndelta, double * delta){ int ii; double d12, d22, coshu, cosv,tdelta; int delta_stride= ndelta == 1 ? 0 : 1; for (ii=0; ii < ndata; ii++) { tdelta= *(delta+ii*delta_stride); d12= (*(z+ii)+tdelta)*(*(z+ii)+tdelta)+(*(R+ii))*(*(R+ii)); d22= (*(z+ii)-tdelta)*(*(z+ii)-tdelta)+(*(R+ii))*(*(R+ii)); coshu= 0.5/tdelta*(sqrt(d12)+sqrt(d22)); cosv= 0.5/tdelta*(sqrt(d12)-sqrt(d22)); *u++= acosh(coshu); *v++= acos(cosv); } u-= ndata; v-= ndata; } inline void calcEL(int ndata, double *R, double *vR, double *vT, double *z, double *vz, double *E, double *Lz, int nargs, struct potentialArg * actionAngleArgs){ int ii; for (ii=0; ii < ndata; ii++){ *(E+ii)= evaluatePotentials(*(R+ii),*(z+ii), nargs,actionAngleArgs) + 0.5 * *(vR+ii) * *(vR+ii) + 0.5 * *(vT+ii) * *(vT+ii) + 0.5 * *(vz+ii) * *(vz+ii); *(Lz+ii)= *(R+ii) * *(vT+ii); } } /* MAIN FUNCTIONS */ void calcu0(int ndata, double *E, double *Lz, int npot, int * pot_type, double * pot_args, int ndelta, double * delta, double *u0, int * err){ int ii; //Set up the potentials struct potentialArg * actionAngleArgs= (struct potentialArg *) malloc ( npot * sizeof (struct potentialArg) ); parse_actionAngleArgs(npot,actionAngleArgs,&pot_type,&pot_args,false); //setup the function to be minimized gsl_function u0Eq; struct u0EqArg * params= (struct u0EqArg *) malloc ( sizeof (struct u0EqArg) ); params->nargs= npot; params->actionAngleArgs= actionAngleArgs; //Setup solver int status; int iter, max_iter = 100; const gsl_min_fminimizer_type *T; gsl_min_fminimizer *s; double u_guess, u_lo, u_hi; T = gsl_min_fminimizer_brent; s = gsl_min_fminimizer_alloc (T); u0Eq.function = &u0Equation; int delta_stride= ndelta == 1 ? 0 : 1; for (ii=0; ii < ndata; ii++){ //Setup function params->delta= *(delta+ii*delta_stride); params->E= *(E+ii); params->Lz22delta= 0.5 * *(Lz+ii) * *(Lz+ii) / *(delta+ii*delta_stride) / *(delta+ii*delta_stride); u0Eq.params = params; //Find starting points for minimum u_guess= 1.; u_lo= 0.001; u_hi= 100.; gsl_set_error_handler_off(); status = gsl_min_fminimizer_set (s, &u0Eq, u_guess, u_lo, u_hi); if (status == GSL_EINVAL) { *(u0+ii)= u_hi; gsl_set_error_handler (NULL); continue; } gsl_set_error_handler (NULL); iter= 0; do { iter++; status = gsl_min_fminimizer_iterate (s); u_guess = gsl_min_fminimizer_x_minimum (s); u_lo = gsl_min_fminimizer_x_lower (s); u_hi = gsl_min_fminimizer_x_upper (s); status = gsl_min_test_interval (u_lo, u_hi, 9.9999999999999998e-13, 4.4408920985006262e-16); } while (status == GSL_CONTINUE && iter < max_iter); *(u0+ii)= gsl_min_fminimizer_x_minimum (s); } gsl_min_fminimizer_free (s); free(params); free_potentialArgs(npot,actionAngleArgs); free(actionAngleArgs); *err= status; } void actionAngleStaeckel_uminUmaxVmin(int ndata, double *R, double *vR, double *vT, double *z, double *vz, double *u0, int npot, int * pot_type, double * pot_args, int ndelta, double * delta, double *umin, double *umax, double *vmin, int * err){ // Just copied this over from actionAngleStaeckel_actions below, not elegant // but does the job... int ii; double tdelta; //Set up the potentials struct potentialArg * actionAngleArgs= (struct potentialArg *) malloc ( npot * sizeof (struct potentialArg) ); parse_actionAngleArgs(npot,actionAngleArgs,&pot_type,&pot_args,false); //E,Lz double *E= (double *) malloc ( ndata * sizeof(double) ); double *Lz= (double *) malloc ( ndata * sizeof(double) ); calcEL(ndata,R,vR,vT,z,vz,E,Lz,npot,actionAngleArgs); //Calculate all necessary parameters double *ux= (double *) malloc ( ndata * sizeof(double) ); double *vx= (double *) malloc ( ndata * sizeof(double) ); Rz_to_uv_vec(ndata,R,z,ux,vx,ndelta,delta); double *coshux= (double *) malloc ( ndata * sizeof(double) ); double *sinhux= (double *) malloc ( ndata * sizeof(double) ); double *sinvx= (double *) malloc ( ndata * sizeof(double) ); double *cosvx= (double *) malloc ( ndata * sizeof(double) ); double *pux= (double *) malloc ( ndata * sizeof(double) ); double *pvx= (double *) malloc ( ndata * sizeof(double) ); double *sinh2u0= (double *) malloc ( ndata * sizeof(double) ); double *cosh2u0= (double *) malloc ( ndata * sizeof(double) ); double *v0= (double *) malloc ( ndata * sizeof(double) ); double *sin2v0= (double *) malloc ( ndata * sizeof(double) ); double *potu0v0= (double *) malloc ( ndata * sizeof(double) ); double *potupi2= (double *) malloc ( ndata * sizeof(double) ); double *I3U= (double *) malloc ( ndata * sizeof(double) ); double *I3V= (double *) malloc ( ndata * sizeof(double) ); int delta_stride= ndelta == 1 ? 0 : 1; UNUSED int chunk= CHUNKSIZE; #pragma omp parallel for schedule(static,chunk) private(ii,tdelta) for (ii=0; ii < ndata; ii++){ tdelta= *(delta+ii*delta_stride); *(coshux+ii)= cosh(*(ux+ii)); *(sinhux+ii)= sinh(*(ux+ii)); *(cosvx+ii)= cos(*(vx+ii)); *(sinvx+ii)= sin(*(vx+ii)); *(pux+ii)= tdelta * (*(vR+ii) * *(coshux+ii) * *(sinvx+ii) + *(vz+ii) * *(sinhux+ii) * *(cosvx+ii)); *(pvx+ii)= tdelta * (*(vR+ii) * *(sinhux+ii) * *(cosvx+ii) - *(vz+ii) * *(coshux+ii) * *(sinvx+ii)); *(sinh2u0+ii)= sinh(*(u0+ii)) * sinh(*(u0+ii)); *(cosh2u0+ii)= cosh(*(u0+ii)) * cosh(*(u0+ii)); *(v0+ii)= 0.5 * M_PI; //*(vx+ii); *(sin2v0+ii)= sin(*(v0+ii)) * sin(*(v0+ii)); *(potu0v0+ii)= evaluatePotentialsUV(*(u0+ii),*(v0+ii),tdelta, npot,actionAngleArgs); *(I3U+ii)= *(E+ii) * *(sinhux+ii) * *(sinhux+ii) - 0.5 * *(pux+ii) * *(pux+ii) / tdelta / tdelta - 0.5 * *(Lz+ii) * *(Lz+ii) / tdelta / tdelta / *(sinhux+ii) / *(sinhux+ii) - ( *(sinhux+ii) * *(sinhux+ii) + *(sin2v0+ii)) *evaluatePotentialsUV(*(ux+ii),*(v0+ii),tdelta, npot,actionAngleArgs) + ( *(sinh2u0+ii) + *(sin2v0+ii) )* *(potu0v0+ii); *(potupi2+ii)= evaluatePotentialsUV(*(u0+ii),0.5 * M_PI,tdelta, npot,actionAngleArgs); *(I3V+ii)= - *(E+ii) * *(sinvx+ii) * *(sinvx+ii) + 0.5 * *(pvx+ii) * *(pvx+ii) / tdelta / tdelta + 0.5 * *(Lz+ii) * *(Lz+ii) / tdelta / tdelta / *(sinvx+ii) / *(sinvx+ii) - *(cosh2u0+ii) * *(potupi2+ii) + ( *(sinh2u0+ii) + *(sinvx+ii) * *(sinvx+ii)) * evaluatePotentialsUV(*(u0+ii),*(vx+ii),tdelta, npot,actionAngleArgs); } //Calculate 'peri' and 'apo'centers calcUminUmax(ndata,umin,umax,ux,pux,E,Lz,I3U,ndelta,delta,u0,sinh2u0,v0, sin2v0,potu0v0,npot,actionAngleArgs); calcVmin(ndata,vmin,vx,pvx,E,Lz,I3V,ndelta,delta,u0,cosh2u0,sinh2u0,potupi2, npot,actionAngleArgs); //Free free_potentialArgs(npot,actionAngleArgs); free(actionAngleArgs); free(E); free(Lz); free(ux); free(vx); free(coshux); free(sinhux); free(sinvx); free(cosvx); free(pux); free(pvx); free(sinh2u0); free(cosh2u0); free(v0); free(sin2v0); free(potu0v0); free(potupi2); free(I3U); free(I3V); } void actionAngleStaeckel_actions(int ndata, double *R, double *vR, double *vT, double *z, double *vz, double *u0, int npot, int * pot_type, double * pot_args, int ndelta, double * delta, int order, double *jr, double *jz, int * err){ int ii; double tdelta; //Set up the potentials struct potentialArg * actionAngleArgs= (struct potentialArg *) malloc ( npot * sizeof (struct potentialArg) ); parse_actionAngleArgs(npot,actionAngleArgs,&pot_type,&pot_args,false); //E,Lz double *E= (double *) malloc ( ndata * sizeof(double) ); double *Lz= (double *) malloc ( ndata * sizeof(double) ); calcEL(ndata,R,vR,vT,z,vz,E,Lz,npot,actionAngleArgs); //Calculate all necessary parameters double *ux= (double *) malloc ( ndata * sizeof(double) ); double *vx= (double *) malloc ( ndata * sizeof(double) ); Rz_to_uv_vec(ndata,R,z,ux,vx,ndelta,delta); double *coshux= (double *) malloc ( ndata * sizeof(double) ); double *sinhux= (double *) malloc ( ndata * sizeof(double) ); double *sinvx= (double *) malloc ( ndata * sizeof(double) ); double *cosvx= (double *) malloc ( ndata * sizeof(double) ); double *pux= (double *) malloc ( ndata * sizeof(double) ); double *pvx= (double *) malloc ( ndata * sizeof(double) ); double *sinh2u0= (double *) malloc ( ndata * sizeof(double) ); double *cosh2u0= (double *) malloc ( ndata * sizeof(double) ); double *v0= (double *) malloc ( ndata * sizeof(double) ); double *sin2v0= (double *) malloc ( ndata * sizeof(double) ); double *potu0v0= (double *) malloc ( ndata * sizeof(double) ); double *potupi2= (double *) malloc ( ndata * sizeof(double) ); double *I3U= (double *) malloc ( ndata * sizeof(double) ); double *I3V= (double *) malloc ( ndata * sizeof(double) ); int delta_stride= ndelta == 1 ? 0 : 1; UNUSED int chunk= CHUNKSIZE; #pragma omp parallel for schedule(static,chunk) private(ii,tdelta) for (ii=0; ii < ndata; ii++){ tdelta= *(delta+ii*delta_stride); *(coshux+ii)= cosh(*(ux+ii)); *(sinhux+ii)= sinh(*(ux+ii)); *(cosvx+ii)= cos(*(vx+ii)); *(sinvx+ii)= sin(*(vx+ii)); *(pux+ii)= tdelta * (*(vR+ii) * *(coshux+ii) * *(sinvx+ii) + *(vz+ii) * *(sinhux+ii) * *(cosvx+ii)); *(pvx+ii)= tdelta * (*(vR+ii) * *(sinhux+ii) * *(cosvx+ii) - *(vz+ii) * *(coshux+ii) * *(sinvx+ii)); *(sinh2u0+ii)= sinh(*(u0+ii)) * sinh(*(u0+ii)); *(cosh2u0+ii)= cosh(*(u0+ii)) * cosh(*(u0+ii)); *(v0+ii)= 0.5 * M_PI; //*(vx+ii); *(sin2v0+ii)= sin(*(v0+ii)) * sin(*(v0+ii)); *(potu0v0+ii)= evaluatePotentialsUV(*(u0+ii),*(v0+ii),tdelta, npot,actionAngleArgs); *(I3U+ii)= *(E+ii) * *(sinhux+ii) * *(sinhux+ii) - 0.5 * *(pux+ii) * *(pux+ii) / tdelta / tdelta - 0.5 * *(Lz+ii) * *(Lz+ii) / tdelta / tdelta / *(sinhux+ii) / *(sinhux+ii) - ( *(sinhux+ii) * *(sinhux+ii) + *(sin2v0+ii)) *evaluatePotentialsUV(*(ux+ii),*(v0+ii),tdelta, npot,actionAngleArgs) + ( *(sinh2u0+ii) + *(sin2v0+ii) )* *(potu0v0+ii); *(potupi2+ii)= evaluatePotentialsUV(*(u0+ii),0.5 * M_PI,tdelta, npot,actionAngleArgs); *(I3V+ii)= - *(E+ii) * *(sinvx+ii) * *(sinvx+ii) + 0.5 * *(pvx+ii) * *(pvx+ii) / tdelta / tdelta + 0.5 * *(Lz+ii) * *(Lz+ii) / tdelta / tdelta / *(sinvx+ii) / *(sinvx+ii) - *(cosh2u0+ii) * *(potupi2+ii) + ( *(sinh2u0+ii) + *(sinvx+ii) * *(sinvx+ii)) * evaluatePotentialsUV(*(u0+ii),*(vx+ii),tdelta, npot,actionAngleArgs); } //Calculate 'peri' and 'apo'centers double *umin= (double *) malloc ( ndata * sizeof(double) ); double *umax= (double *) malloc ( ndata * sizeof(double) ); double *vmin= (double *) malloc ( ndata * sizeof(double) ); calcUminUmax(ndata,umin,umax,ux,pux,E,Lz,I3U,ndelta,delta,u0,sinh2u0,v0, sin2v0,potu0v0,npot,actionAngleArgs); calcVmin(ndata,vmin,vx,pvx,E,Lz,I3V,ndelta,delta,u0,cosh2u0,sinh2u0,potupi2, npot,actionAngleArgs); //Calculate the actions calcJRStaeckel(ndata,jr,umin,umax,E,Lz,I3U,ndelta,delta,u0,sinh2u0,v0,sin2v0, potu0v0,npot,actionAngleArgs,order); calcJzStaeckel(ndata,jz,vmin,E,Lz,I3V,ndelta,delta,u0,cosh2u0,sinh2u0, potupi2,npot,actionAngleArgs,order); //Free free_potentialArgs(npot,actionAngleArgs); free(actionAngleArgs); free(E); free(Lz); free(ux); free(vx); free(coshux); free(sinhux); free(sinvx); free(cosvx); free(pux); free(pvx); free(sinh2u0); free(cosh2u0); free(v0); free(sin2v0); free(potu0v0); free(potupi2); free(I3U); free(I3V); free(umin); free(umax); free(vmin); } void calcJRStaeckel(int ndata, double * jr, double * umin, double * umax, double * E, double * Lz, double * I3U, int ndelta, double * delta, double * u0, double * sinh2u0, double * v0, double * sin2v0, double * potu0v0, int nargs, struct potentialArg * actionAngleArgs, int order){ int ii, tid, nthreads; #ifdef _OPENMP nthreads = omp_get_max_threads(); #else nthreads = 1; #endif gsl_function * JRInt= (gsl_function *) malloc ( nthreads * sizeof(gsl_function) ); struct JRStaeckelArg * params= (struct JRStaeckelArg *) malloc ( nthreads * sizeof (struct JRStaeckelArg) ); for (tid=0; tid < nthreads; tid++){ (params+tid)->nargs= nargs; (params+tid)->actionAngleArgs= actionAngleArgs; } //Setup integrator gsl_integration_glfixed_table * T= gsl_integration_glfixed_table_alloc (order); int delta_stride= ndelta == 1 ? 0 : 1; UNUSED int chunk= CHUNKSIZE; #pragma omp parallel for schedule(static,chunk) \ private(tid,ii) \ shared(jr,umin,umax,JRInt,params,T,delta,E,Lz,I3U,u0,sinh2u0,v0,sin2v0,potu0v0) for (ii=0; ii < ndata; ii++){ #ifdef _OPENMP tid= omp_get_thread_num(); #else tid = 0; #endif if ( *(umin+ii) == -9999.99 || *(umax+ii) == -9999.99 ){ *(jr+ii)= 9999.99; continue; } if ( (*(umax+ii) - *(umin+ii)) / *(umax+ii) < 0.000001 ){//circular *(jr+ii) = 0.; continue; } //Setup function (params+tid)->delta= *(delta+ii*delta_stride); (params+tid)->E= *(E+ii); (params+tid)->Lz22delta= 0.5 * *(Lz+ii) * *(Lz+ii) / *(delta+ii*delta_stride) / *(delta+ii*delta_stride); (params+tid)->I3U= *(I3U+ii); (params+tid)->u0= *(u0+ii); (params+tid)->sinh2u0= *(sinh2u0+ii); (params+tid)->v0= *(v0+ii); (params+tid)->sin2v0= *(sin2v0+ii); (params+tid)->potu0v0= *(potu0v0+ii); (JRInt+tid)->function = &JRStaeckelIntegrand; (JRInt+tid)->params = params+tid; //Integrate *(jr+ii)= gsl_integration_glfixed (JRInt+tid,*(umin+ii),*(umax+ii),T) * sqrt(2.) * *(delta+ii*delta_stride) / M_PI; } free(JRInt); free(params); gsl_integration_glfixed_table_free ( T ); } void calcJzStaeckel(int ndata, double * jz, double * vmin, double * E, double * Lz, double * I3V, int ndelta, double * delta, double * u0, double * cosh2u0, double * sinh2u0, double * potupi2, int nargs, struct potentialArg * actionAngleArgs, int order){ int ii, tid, nthreads; #ifdef _OPENMP nthreads = omp_get_max_threads(); #else nthreads = 1; #endif gsl_function * JzInt= (gsl_function *) malloc ( nthreads * sizeof(gsl_function) ); struct JzStaeckelArg * params= (struct JzStaeckelArg *) malloc ( nthreads * sizeof (struct JzStaeckelArg) ); for (tid=0; tid < nthreads; tid++){ (params+tid)->nargs= nargs; (params+tid)->actionAngleArgs= actionAngleArgs; } //Setup integrator gsl_integration_glfixed_table * T= gsl_integration_glfixed_table_alloc (order); int delta_stride= ndelta == 1 ? 0 : 1; UNUSED int chunk= CHUNKSIZE; #pragma omp parallel for schedule(static,chunk) \ private(tid,ii) \ shared(jz,vmin,JzInt,params,T,delta,E,Lz,I3V,u0,cosh2u0,sinh2u0,potupi2) for (ii=0; ii < ndata; ii++){ #ifdef _OPENMP tid= omp_get_thread_num(); #else tid = 0; #endif if ( *(vmin+ii) == -9999.99 ){ *(jz+ii)= 9999.99; continue; } if ( (0.5 * M_PI - *(vmin+ii)) / M_PI * 2. < 0.000001 ){//circular *(jz+ii) = 0.; continue; } //Setup function (params+tid)->delta= *(delta+ii*delta_stride); (params+tid)->E= *(E+ii); (params+tid)->Lz22delta= 0.5 * *(Lz+ii) * *(Lz+ii) / *(delta+ii*delta_stride) / *(delta+ii*delta_stride); (params+tid)->I3V= *(I3V+ii); (params+tid)->u0= *(u0+ii); (params+tid)->cosh2u0= *(cosh2u0+ii); (params+tid)->sinh2u0= *(sinh2u0+ii); (params+tid)->potupi2= *(potupi2+ii); (JzInt+tid)->function = &JzStaeckelIntegrand; (JzInt+tid)->params = params+tid; //Integrate *(jz+ii)= gsl_integration_glfixed (JzInt+tid,*(vmin+ii),M_PI/2.,T) * 2 * sqrt(2.) * *(delta+ii*delta_stride) / M_PI; } free(JzInt); free(params); gsl_integration_glfixed_table_free ( T ); } void actionAngleStaeckel_actionsFreqs(int ndata, double *R, double *vR, double *vT, double *z, double *vz, double *u0, int npot, int * pot_type, double * pot_args, int ndelta, double * delta, int order, double *jr, double *jz, double *Omegar, double *Omegaphi, double *Omegaz, int * err){ int ii; double tdelta; //Set up the potentials struct potentialArg * actionAngleArgs= (struct potentialArg *) malloc ( npot * sizeof (struct potentialArg) ); parse_actionAngleArgs(npot,actionAngleArgs,&pot_type,&pot_args,false); //E,Lz double *E= (double *) malloc ( ndata * sizeof(double) ); double *Lz= (double *) malloc ( ndata * sizeof(double) ); calcEL(ndata,R,vR,vT,z,vz,E,Lz,npot,actionAngleArgs); //Calculate all necessary parameters double *ux= (double *) malloc ( ndata * sizeof(double) ); double *vx= (double *) malloc ( ndata * sizeof(double) ); Rz_to_uv_vec(ndata,R,z,ux,vx,ndelta,delta); double *coshux= (double *) malloc ( ndata * sizeof(double) ); double *sinhux= (double *) malloc ( ndata * sizeof(double) ); double *sinvx= (double *) malloc ( ndata * sizeof(double) ); double *cosvx= (double *) malloc ( ndata * sizeof(double) ); double *pux= (double *) malloc ( ndata * sizeof(double) ); double *pvx= (double *) malloc ( ndata * sizeof(double) ); double *sinh2u0= (double *) malloc ( ndata * sizeof(double) ); double *cosh2u0= (double *) malloc ( ndata * sizeof(double) ); double *v0= (double *) malloc ( ndata * sizeof(double) ); double *sin2v0= (double *) malloc ( ndata * sizeof(double) ); double *potu0v0= (double *) malloc ( ndata * sizeof(double) ); double *potupi2= (double *) malloc ( ndata * sizeof(double) ); double *I3U= (double *) malloc ( ndata * sizeof(double) ); double *I3V= (double *) malloc ( ndata * sizeof(double) ); int delta_stride= ndelta == 1 ? 0 : 1; UNUSED int chunk= CHUNKSIZE; #pragma omp parallel for schedule(static,chunk) private(ii,tdelta) for (ii=0; ii < ndata; ii++){ tdelta= *(delta+ii*delta_stride); *(coshux+ii)= cosh(*(ux+ii)); *(sinhux+ii)= sinh(*(ux+ii)); *(cosvx+ii)= cos(*(vx+ii)); *(sinvx+ii)= sin(*(vx+ii)); *(pux+ii)= tdelta * (*(vR+ii) * *(coshux+ii) * *(sinvx+ii) + *(vz+ii) * *(sinhux+ii) * *(cosvx+ii)); *(pvx+ii)= tdelta * (*(vR+ii) * *(sinhux+ii) * *(cosvx+ii) - *(vz+ii) * *(coshux+ii) * *(sinvx+ii)); *(sinh2u0+ii)= sinh(*(u0+ii)) * sinh(*(u0+ii)); *(cosh2u0+ii)= cosh(*(u0+ii)) * cosh(*(u0+ii)); *(v0+ii)= 0.5 * M_PI; //*(vx+ii); *(sin2v0+ii)= sin(*(v0+ii)) * sin(*(v0+ii)); *(potu0v0+ii)= evaluatePotentialsUV(*(u0+ii),*(v0+ii),tdelta, npot,actionAngleArgs); *(I3U+ii)= *(E+ii) * *(sinhux+ii) * *(sinhux+ii) - 0.5 * *(pux+ii) * *(pux+ii) / tdelta / tdelta - 0.5 * *(Lz+ii) * *(Lz+ii) / tdelta / tdelta / *(sinhux+ii) / *(sinhux+ii) - ( *(sinhux+ii) * *(sinhux+ii) + *(sin2v0+ii)) *evaluatePotentialsUV(*(ux+ii),*(v0+ii),tdelta, npot,actionAngleArgs) + ( *(sinh2u0+ii) + *(sin2v0+ii) )* *(potu0v0+ii); *(potupi2+ii)= evaluatePotentialsUV(*(u0+ii),0.5 * M_PI,tdelta, npot,actionAngleArgs); *(I3V+ii)= - *(E+ii) * *(sinvx+ii) * *(sinvx+ii) + 0.5 * *(pvx+ii) * *(pvx+ii) / tdelta / tdelta + 0.5 * *(Lz+ii) * *(Lz+ii) / tdelta / tdelta / *(sinvx+ii) / *(sinvx+ii) - *(cosh2u0+ii) * *(potupi2+ii) + ( *(sinh2u0+ii) + *(sinvx+ii) * *(sinvx+ii)) * evaluatePotentialsUV(*(u0+ii),*(vx+ii),tdelta, npot,actionAngleArgs); } //Calculate 'peri' and 'apo'centers double *umin= (double *) malloc ( ndata * sizeof(double) ); double *umax= (double *) malloc ( ndata * sizeof(double) ); double *vmin= (double *) malloc ( ndata * sizeof(double) ); calcUminUmax(ndata,umin,umax,ux,pux,E,Lz,I3U,ndelta,delta,u0,sinh2u0,v0, sin2v0,potu0v0,npot,actionAngleArgs); calcVmin(ndata,vmin,vx,pvx,E,Lz,I3V,ndelta,delta,u0,cosh2u0,sinh2u0,potupi2, npot,actionAngleArgs); //Calculate the actions calcJRStaeckel(ndata,jr,umin,umax,E,Lz,I3U,ndelta,delta,u0,sinh2u0,v0,sin2v0, potu0v0,npot,actionAngleArgs,order); calcJzStaeckel(ndata,jz,vmin,E,Lz,I3V,ndelta,delta,u0,cosh2u0,sinh2u0, potupi2,npot,actionAngleArgs,order); //Calculate the derivatives of the actions wrt the integrals of motion double *dJRdE= (double *) malloc ( ndata * sizeof(double) ); double *dJRdLz= (double *) malloc ( ndata * sizeof(double) ); double *dJRdI3= (double *) malloc ( ndata * sizeof(double) ); double *dJzdE= (double *) malloc ( ndata * sizeof(double) ); double *dJzdLz= (double *) malloc ( ndata * sizeof(double) ); double *dJzdI3= (double *) malloc ( ndata * sizeof(double) ); double *detA= (double *) malloc ( ndata * sizeof(double) ); calcdJRStaeckel(ndata,dJRdE,dJRdLz,dJRdI3, umin,umax,E,Lz,I3U,ndelta,delta,u0,sinh2u0,v0,sin2v0, potu0v0,npot,actionAngleArgs,order); calcdJzStaeckel(ndata,dJzdE,dJzdLz,dJzdI3, vmin,E,Lz,I3V,ndelta,delta,u0,cosh2u0,sinh2u0, potupi2,npot,actionAngleArgs,order); calcFreqsFromDerivsStaeckel(ndata,Omegar,Omegaphi,Omegaz,detA, dJRdE,dJRdLz,dJRdI3, dJzdE,dJzdLz,dJzdI3); //Free free_potentialArgs(npot,actionAngleArgs); free(actionAngleArgs); free(E); free(Lz); free(ux); free(vx); free(coshux); free(sinhux); free(sinvx); free(cosvx); free(pux); free(pvx); free(sinh2u0); free(cosh2u0); free(v0); free(sin2v0); free(potu0v0); free(potupi2); free(I3U); free(I3V); free(umin); free(umax); free(vmin); free(dJRdE); free(dJRdLz); free(dJRdI3); free(dJzdE); free(detA); free(dJzdLz); free(dJzdI3); } void actionAngleStaeckel_actionsFreqsAngles(int ndata, double *R, double *vR, double *vT, double *z, double *vz, double *u0, int npot, int * pot_type, double * pot_args, int ndelta, double * delta, int order, double *jr, double *jz, double *Omegar, double *Omegaphi, double *Omegaz, double *Angler, double *Anglephi, double *Anglez, int * err){ int ii; double tdelta; //Set up the potentials struct potentialArg * actionAngleArgs= (struct potentialArg *) malloc ( npot * sizeof (struct potentialArg) ); parse_actionAngleArgs(npot,actionAngleArgs,&pot_type,&pot_args,false); //E,Lz double *E= (double *) malloc ( ndata * sizeof(double) ); double *Lz= (double *) malloc ( ndata * sizeof(double) ); calcEL(ndata,R,vR,vT,z,vz,E,Lz,npot,actionAngleArgs); //Calculate all necessary parameters double *ux= (double *) malloc ( ndata * sizeof(double) ); double *vx= (double *) malloc ( ndata * sizeof(double) ); Rz_to_uv_vec(ndata,R,z,ux,vx,ndelta,delta); double *coshux= (double *) malloc ( ndata * sizeof(double) ); double *sinhux= (double *) malloc ( ndata * sizeof(double) ); double *sinvx= (double *) malloc ( ndata * sizeof(double) ); double *cosvx= (double *) malloc ( ndata * sizeof(double) ); double *pux= (double *) malloc ( ndata * sizeof(double) ); double *pvx= (double *) malloc ( ndata * sizeof(double) ); double *sinh2u0= (double *) malloc ( ndata * sizeof(double) ); double *cosh2u0= (double *) malloc ( ndata * sizeof(double) ); double *v0= (double *) malloc ( ndata * sizeof(double) ); double *sin2v0= (double *) malloc ( ndata * sizeof(double) ); double *potu0v0= (double *) malloc ( ndata * sizeof(double) ); double *potupi2= (double *) malloc ( ndata * sizeof(double) ); double *I3U= (double *) malloc ( ndata * sizeof(double) ); double *I3V= (double *) malloc ( ndata * sizeof(double) ); int delta_stride= ndelta == 1 ? 0 : 1; UNUSED int chunk= CHUNKSIZE; #pragma omp parallel for schedule(static,chunk) private(ii,tdelta) for (ii=0; ii < ndata; ii++){ tdelta= *(delta+ii*delta_stride); *(coshux+ii)= cosh(*(ux+ii)); *(sinhux+ii)= sinh(*(ux+ii)); *(cosvx+ii)= cos(*(vx+ii)); *(sinvx+ii)= sin(*(vx+ii)); *(pux+ii)= tdelta * (*(vR+ii) * *(coshux+ii) * *(sinvx+ii) + *(vz+ii) * *(sinhux+ii) * *(cosvx+ii)); *(pvx+ii)= tdelta * (*(vR+ii) * *(sinhux+ii) * *(cosvx+ii) - *(vz+ii) * *(coshux+ii) * *(sinvx+ii)); *(sinh2u0+ii)= sinh(*(u0+ii)) * sinh(*(u0+ii)); *(cosh2u0+ii)= cosh(*(u0+ii)) * cosh(*(u0+ii)); *(v0+ii)= 0.5 * M_PI; //*(vx+ii); *(sin2v0+ii)= sin(*(v0+ii)) * sin(*(v0+ii)); *(potu0v0+ii)= evaluatePotentialsUV(*(u0+ii),*(v0+ii),tdelta, npot,actionAngleArgs); *(I3U+ii)= *(E+ii) * *(sinhux+ii) * *(sinhux+ii) - 0.5 * *(pux+ii) * *(pux+ii) / tdelta / tdelta - 0.5 * *(Lz+ii) * *(Lz+ii) / tdelta / tdelta / *(sinhux+ii) / *(sinhux+ii) - ( *(sinhux+ii) * *(sinhux+ii) + *(sin2v0+ii)) *evaluatePotentialsUV(*(ux+ii),*(v0+ii),tdelta, npot,actionAngleArgs) + ( *(sinh2u0+ii) + *(sin2v0+ii) )* *(potu0v0+ii); *(potupi2+ii)= evaluatePotentialsUV(*(u0+ii),0.5 * M_PI,tdelta, npot,actionAngleArgs); *(I3V+ii)= - *(E+ii) * *(sinvx+ii) * *(sinvx+ii) + 0.5 * *(pvx+ii) * *(pvx+ii) / tdelta / tdelta + 0.5 * *(Lz+ii) * *(Lz+ii) / tdelta / tdelta / *(sinvx+ii) / *(sinvx+ii) - *(cosh2u0+ii) * *(potupi2+ii) + ( *(sinh2u0+ii) + *(sinvx+ii) * *(sinvx+ii)) * evaluatePotentialsUV(*(u0+ii),*(vx+ii),tdelta, npot,actionAngleArgs); } //Calculate 'peri' and 'apo'centers double *umin= (double *) malloc ( ndata * sizeof(double) ); double *umax= (double *) malloc ( ndata * sizeof(double) ); double *vmin= (double *) malloc ( ndata * sizeof(double) ); calcUminUmax(ndata,umin,umax,ux,pux,E,Lz,I3U,ndelta,delta,u0,sinh2u0,v0, sin2v0,potu0v0,npot,actionAngleArgs); calcVmin(ndata,vmin,vx,pvx,E,Lz,I3V,ndelta,delta,u0,cosh2u0,sinh2u0,potupi2, npot,actionAngleArgs); //Calculate the actions calcJRStaeckel(ndata,jr,umin,umax,E,Lz,I3U,ndelta,delta,u0,sinh2u0,v0,sin2v0, potu0v0,npot,actionAngleArgs,order); calcJzStaeckel(ndata,jz,vmin,E,Lz,I3V,ndelta,delta,u0,cosh2u0,sinh2u0, potupi2,npot,actionAngleArgs,order); //Calculate the derivatives of the actions wrt the integrals of motion double *dJRdE= (double *) malloc ( ndata * sizeof(double) ); double *dJRdLz= (double *) malloc ( ndata * sizeof(double) ); double *dJRdI3= (double *) malloc ( ndata * sizeof(double) ); double *dJzdE= (double *) malloc ( ndata * sizeof(double) ); double *dJzdLz= (double *) malloc ( ndata * sizeof(double) ); double *dJzdI3= (double *) malloc ( ndata * sizeof(double) ); double *detA= (double *) malloc ( ndata * sizeof(double) ); calcdJRStaeckel(ndata,dJRdE,dJRdLz,dJRdI3, umin,umax,E,Lz,I3U,ndelta,delta,u0,sinh2u0,v0,sin2v0, potu0v0,npot,actionAngleArgs,order); calcdJzStaeckel(ndata,dJzdE,dJzdLz,dJzdI3, vmin,E,Lz,I3V,ndelta,delta,u0,cosh2u0,sinh2u0, potupi2,npot,actionAngleArgs,order); calcFreqsFromDerivsStaeckel(ndata,Omegar,Omegaphi,Omegaz,detA, dJRdE,dJRdLz,dJRdI3, dJzdE,dJzdLz,dJzdI3); double *dI3dJR= (double *) malloc ( ndata * sizeof(double) ); double *dI3dJz= (double *) malloc ( ndata * sizeof(double) ); double *dI3dLz= (double *) malloc ( ndata * sizeof(double) ); calcdI3dJFromDerivsStaeckel(ndata,dI3dJR,dI3dJz,dI3dLz,detA, dJRdE,dJzdE,dJRdLz,dJzdLz); calcAnglesStaeckel(ndata,Angler,Anglephi,Anglez, Omegar,Omegaphi,Omegaz,dI3dJR,dI3dJz,dI3dLz, dJRdE,dJRdLz,dJRdI3, dJzdE,dJzdLz,dJzdI3, ux,vx,pux,pvx, umin,umax,E,Lz,I3U,ndelta,delta,u0,sinh2u0,v0,sin2v0, potu0v0, vmin,I3V,cosh2u0,potupi2, npot,actionAngleArgs,order); //Free free_potentialArgs(npot,actionAngleArgs); free(actionAngleArgs); free(E); free(Lz); free(ux); free(vx); free(coshux); free(sinhux); free(sinvx); free(cosvx); free(pux); free(pvx); free(sinh2u0); free(cosh2u0); free(v0); free(sin2v0); free(potu0v0); free(potupi2); free(I3U); free(I3V); free(umin); free(umax); free(vmin); free(dJRdE); free(dJRdLz); free(dJRdI3); free(dJzdE); free(dJzdLz); free(dJzdI3); free(detA); free(dI3dJR); free(dI3dJz); free(dI3dLz); } void calcFreqsFromDerivsStaeckel(int ndata, double * Omegar, double * Omegaphi, double * Omegaz, double * detA, double * djrdE, double * djrdLz, double * djrdI3, double * djzdE, double * djzdLz, double * djzdI3){ int ii; UNUSED int chunk= CHUNKSIZE; #pragma omp parallel for schedule(static,chunk) \ private(ii) \ shared(Omegar,Omegaphi,Omegaz,djrdE,djrdLz,djrdI3,djzdE,djzdLz,djzdI3,detA) for (ii=0; ii < ndata; ii++){ if ( *(djrdE+ii) == 9999.99 || *(djzdE+ii) == 9999.99 ) { *(Omegar+ii)= 9999.99; *(Omegaz+ii)= 9999.99; *(Omegaphi+ii)= 9999.99; } else { //First calculate the determinant of the relevant matrix *(detA+ii)= *(djrdE+ii) * *(djzdI3+ii) - *(djzdE+ii) * *(djrdI3+ii); //Then calculate the frequencies *(Omegar+ii)= *(djzdI3+ii) / *(detA+ii); *(Omegaz+ii)= - *(djrdI3+ii) / *(detA+ii); *(Omegaphi+ii)= ( *(djrdI3+ii) * *(djzdLz+ii) - *(djzdI3+ii) * *(djrdLz+ii)) / *(detA+ii); } } } void calcdI3dJFromDerivsStaeckel(int ndata, double * dI3dJR, double * dI3dJz, double * dI3dLz, double * detA, double * djrdE, double * djzdE, double * djrdLz, double * djzdLz){ int ii; UNUSED int chunk= CHUNKSIZE; #pragma omp parallel for schedule(static,chunk) \ private(ii) \ shared(djrdE,djzdE,djrdLz,djzdLz,dI3dJR,dI3dJz,dI3dLz,detA) for (ii=0; ii < ndata; ii++){ *(dI3dJR+ii)= - *(djzdE+ii) / *(detA+ii); *(dI3dJz+ii)= *(djrdE+ii) / *(detA+ii); *(dI3dLz+ii)= -( *(djrdE+ii) * *(djzdLz+ii) - *(djzdE+ii) * *(djrdLz+ii) ) / *(detA+ii); } } void calcdJRStaeckel(int ndata, double * djrdE, double * djrdLz, double * djrdI3, double * umin, double * umax, double * E, double * Lz, double * I3U, int ndelta, double * delta, double * u0, double * sinh2u0, double * v0, double * sin2v0, double * potu0v0, int nargs, struct potentialArg * actionAngleArgs, int order){ int ii, tid, nthreads; double mid; #ifdef _OPENMP nthreads = omp_get_max_threads(); #else nthreads = 1; #endif gsl_function * dJRInt= (gsl_function *) malloc ( nthreads * sizeof(gsl_function) ); struct dJRStaeckelArg * params= (struct dJRStaeckelArg *) malloc ( nthreads * sizeof (struct dJRStaeckelArg) ); for (tid=0; tid < nthreads; tid++){ (params+tid)->nargs= nargs; (params+tid)->actionAngleArgs= actionAngleArgs; } //Setup integrator gsl_integration_glfixed_table * T= gsl_integration_glfixed_table_alloc (order); int delta_stride= ndelta == 1 ? 0 : 1; UNUSED int chunk= CHUNKSIZE; #pragma omp parallel for schedule(static,chunk) \ private(tid,ii,mid) \ shared(djrdE,djrdLz,djrdI3,umin,umax,dJRInt,params,T,delta,E,Lz,I3U,u0,sinh2u0,v0,sin2v0,potu0v0) for (ii=0; ii < ndata; ii++){ #ifdef _OPENMP tid= omp_get_thread_num(); #else tid = 0; #endif if ( *(umin+ii) == -9999.99 || *(umax+ii) == -9999.99 ){ *(djrdE+ii)= 9999.99; *(djrdLz+ii)= 9999.99; *(djrdI3+ii)= 9999.99; continue; } if ( (*(umax+ii) - *(umin+ii)) / *(umax+ii) < 0.000001 ){//circular *(djrdE+ii) = 0.; *(djrdLz+ii) = 0.; *(djrdI3+ii) = 0.; continue; } //Setup function (params+tid)->delta= *(delta+ii*delta_stride); (params+tid)->E= *(E+ii); (params+tid)->Lz22delta= 0.5 * *(Lz+ii) * *(Lz+ii) / *(delta+ii*delta_stride) / *(delta+ii*delta_stride); (params+tid)->I3U= *(I3U+ii); (params+tid)->u0= *(u0+ii); (params+tid)->sinh2u0= *(sinh2u0+ii); (params+tid)->v0= *(v0+ii); (params+tid)->sin2v0= *(sin2v0+ii); (params+tid)->potu0v0= *(potu0v0+ii); (params+tid)->umin= *(umin+ii); (params+tid)->umax= *(umax+ii); (dJRInt+tid)->function = &dJRdELowStaeckelIntegrand; (dJRInt+tid)->params = params+tid; mid= sqrt( 0.5 * ( *(umax+ii) - *(umin+ii) ) ); //Integrate to get djrdE *(djrdE+ii)= gsl_integration_glfixed (dJRInt+tid,0.,mid,T); (dJRInt+tid)->function = &dJRdEHighStaeckelIntegrand; *(djrdE+ii)+= gsl_integration_glfixed (dJRInt+tid,0.,mid,T); *(djrdE+ii)*= *(delta+ii*delta_stride) / M_PI / sqrt(2.); //then calculate djrdLz (dJRInt+tid)->function = &dJRdLzLowStaeckelIntegrand; *(djrdLz+ii)= gsl_integration_glfixed (dJRInt+tid,0.,mid,T); (dJRInt+tid)->function = &dJRdLzHighStaeckelIntegrand; *(djrdLz+ii)+= gsl_integration_glfixed (dJRInt+tid,0.,mid,T); *(djrdLz+ii)*= - *(Lz+ii) / M_PI / sqrt(2.) / *(delta+ii*delta_stride); //then calculate djrdI3 (dJRInt+tid)->function = &dJRdI3LowStaeckelIntegrand; *(djrdI3+ii)= gsl_integration_glfixed (dJRInt+tid,0.,mid,T); (dJRInt+tid)->function = &dJRdI3HighStaeckelIntegrand; *(djrdI3+ii)+= gsl_integration_glfixed (dJRInt+tid,0.,mid,T); *(djrdI3+ii)*= - *(delta+ii*delta_stride) / M_PI / sqrt(2.); } free(dJRInt); free(params); gsl_integration_glfixed_table_free ( T ); } void calcdJzStaeckel(int ndata, double * djzdE, double * djzdLz, double * djzdI3, double * vmin, double * E, double * Lz, double * I3V, int ndelta, double * delta, double * u0, double * cosh2u0, double * sinh2u0, double * potupi2, int nargs, struct potentialArg * actionAngleArgs, int order){ int ii, tid, nthreads; double mid; #ifdef _OPENMP nthreads = omp_get_max_threads(); #else nthreads = 1; #endif gsl_function * dJzInt= (gsl_function *) malloc ( nthreads * sizeof(gsl_function) ); struct dJzStaeckelArg * params= (struct dJzStaeckelArg *) malloc ( nthreads * sizeof (struct dJzStaeckelArg) ); for (tid=0; tid < nthreads; tid++){ (params+tid)->nargs= nargs; (params+tid)->actionAngleArgs= actionAngleArgs; } //Setup integrator gsl_integration_glfixed_table * T= gsl_integration_glfixed_table_alloc (order); int delta_stride= ndelta == 1 ? 0 : 1; UNUSED int chunk= CHUNKSIZE; #pragma omp parallel for schedule(static,chunk) \ private(tid,ii,mid) \ shared(djzdE,djzdLz,djzdI3,vmin,dJzInt,params,T,delta,E,Lz,I3V,u0,cosh2u0,sinh2u0,potupi2) for (ii=0; ii < ndata; ii++){ #ifdef _OPENMP tid= omp_get_thread_num(); #else tid = 0; #endif if ( *(vmin+ii) == -9999.99 ){ *(djzdE+ii)= 9999.99; *(djzdLz+ii)= 9999.99; *(djzdI3+ii)= 9999.99; continue; } if ( (0.5 * M_PI - *(vmin+ii)) / M_PI * 2. < 0.000001 ){//circular *(djzdE+ii) = 0.; *(djzdLz+ii) = 0.; *(djzdI3+ii) = 0.; continue; } //Setup function (params+tid)->delta= *(delta+ii*delta_stride); (params+tid)->E= *(E+ii); (params+tid)->Lz22delta= 0.5 * *(Lz+ii) * *(Lz+ii) / *(delta+ii*delta_stride) / *(delta+ii*delta_stride); (params+tid)->I3V= *(I3V+ii); (params+tid)->u0= *(u0+ii); (params+tid)->cosh2u0= *(cosh2u0+ii); (params+tid)->sinh2u0= *(sinh2u0+ii); (params+tid)->potupi2= *(potupi2+ii); (params+tid)->vmin= *(vmin+ii); //First calculate dJzdE (dJzInt+tid)->function = &dJzdELowStaeckelIntegrand; (dJzInt+tid)->params = params+tid; mid= sqrt( 0.5 * (M_PI/2. - *(vmin+ii) ) ); //BOVY: pv does not vanish at pi/2, so no need to break up the integral //Integrate *(djzdE+ii)= gsl_integration_glfixed (dJzInt+tid,0.,mid,T); (dJzInt+tid)->function = &dJzdEHighStaeckelIntegrand; *(djzdE+ii)+= gsl_integration_glfixed (dJzInt+tid,0.,mid,T); *(djzdE+ii)*= sqrt(2.) * *(delta+ii*delta_stride) / M_PI; //Then calculate dJzdLz (dJzInt+tid)->function = &dJzdLzLowStaeckelIntegrand; //Integrate *(djzdLz+ii)= gsl_integration_glfixed (dJzInt+tid,0.,mid,T); (dJzInt+tid)->function = &dJzdLzHighStaeckelIntegrand; *(djzdLz+ii)+= gsl_integration_glfixed (dJzInt+tid,0.,mid,T); *(djzdLz+ii)*= - *(Lz+ii) * sqrt(2.) / M_PI / *(delta+ii*delta_stride); //Then calculate dJzdI3 (dJzInt+tid)->function = &dJzdI3LowStaeckelIntegrand; //Integrate *(djzdI3+ii)= gsl_integration_glfixed (dJzInt+tid,0.,mid,T); (dJzInt+tid)->function = &dJzdI3HighStaeckelIntegrand; *(djzdI3+ii)+= gsl_integration_glfixed (dJzInt+tid,0.,mid,T); *(djzdI3+ii)*= sqrt(2.) * *(delta+ii*delta_stride) / M_PI; } free(dJzInt); free(params); gsl_integration_glfixed_table_free ( T ); } void calcAnglesStaeckel(int ndata, double * Angler, double * Anglephi, double * Anglez, double * Omegar, double * Omegaphi, double * Omegaz, double * dI3dJR, double * dI3dJz, double * dI3dLz, double * dJRdE, double * dJRdLz, double * dJRdI3, double * dJzdE, double * dJzdLz, double * dJzdI3, double * ux, double * vx, double * pux, double * pvx, double * umin, double * umax, double * E, double * Lz, double * I3U, int ndelta, double * delta, double * u0, double * sinh2u0, double * v0, double * sin2v0, double * potu0v0, double * vmin, double * I3V, double * cosh2u0, double * potupi2, int nargs, struct potentialArg * actionAngleArgs, int order){ int ii, tid, nthreads; double Or1, Or2, I3r1, I3r2,phitmp; double mid, midpoint; #ifdef _OPENMP nthreads = omp_get_max_threads(); #else nthreads = 1; #endif gsl_function * AngleuInt= (gsl_function *) malloc ( nthreads * sizeof(gsl_function) ); gsl_function * AnglevInt= (gsl_function *) malloc ( nthreads * sizeof(gsl_function) ); struct dJRStaeckelArg * paramsu= (struct dJRStaeckelArg *) malloc ( nthreads * sizeof (struct dJRStaeckelArg) ); struct dJzStaeckelArg * paramsv= (struct dJzStaeckelArg *) malloc ( nthreads * sizeof (struct dJzStaeckelArg) ); for (tid=0; tid < nthreads; tid++){ (paramsu+tid)->nargs= nargs; (paramsu+tid)->actionAngleArgs= actionAngleArgs; (paramsv+tid)->nargs= nargs; (paramsv+tid)->actionAngleArgs= actionAngleArgs; } //Setup integrator gsl_integration_glfixed_table * T= gsl_integration_glfixed_table_alloc (order); int delta_stride= ndelta == 1 ? 0 : 1; UNUSED int chunk= CHUNKSIZE; #pragma omp parallel for schedule(static,chunk) \ private(tid,ii,mid,midpoint,Or1,Or2,I3r1,I3r2,phitmp) \ shared(Angler,Anglephi,Anglez,Omegar,Omegaz,dI3dJR,dI3dJz,umin,umax,AngleuInt,AnglevInt,paramsu,paramsv,T,delta,E,Lz,I3U,u0,sinh2u0,v0,sin2v0,potu0v0,vmin,I3V,cosh2u0,potupi2) for (ii=0; ii < ndata; ii++){ #ifdef _OPENMP tid= omp_get_thread_num(); #else tid = 0; #endif if ( *(umin+ii) == -9999.99 || *(umax+ii) == -9999.99 ){ *(Angler+ii)= 9999.99; *(Anglephi+ii)= 9999.99; *(Anglez+ii)= 9999.99; continue; } if ( (*(umax+ii) - *(umin+ii)) / *(umax+ii) < 0.000001 ){//circular *(Angler+ii) = 0.; *(Anglephi+ii) = 0.; *(Anglez+ii) = 0.; continue; } //Setup u function (paramsu+tid)->delta= *(delta+ii*delta_stride); (paramsu+tid)->E= *(E+ii); (paramsu+tid)->Lz22delta= 0.5 * *(Lz+ii) * *(Lz+ii) / *(delta+ii*delta_stride) / *(delta+ii*delta_stride); (paramsu+tid)->I3U= *(I3U+ii); (paramsu+tid)->u0= *(u0+ii); (paramsu+tid)->sinh2u0= *(sinh2u0+ii); (paramsu+tid)->v0= *(v0+ii); (paramsu+tid)->sin2v0= *(sin2v0+ii); (paramsu+tid)->potu0v0= *(potu0v0+ii); (paramsu+tid)->umin= *(umin+ii); (paramsu+tid)->umax= *(umax+ii); (AngleuInt+tid)->params = paramsu+tid; midpoint= *(umin+ii)+ 0.5 * ( *(umax+ii) - *(umin+ii) ); if ( *(pux+ii) > 0. ) { if ( *(ux+ii) > midpoint ) { mid= sqrt( ( *(umax+ii) - *(ux+ii) ) ); (AngleuInt+tid)->function = &dJRdEHighStaeckelIntegrand; Or1= gsl_integration_glfixed (AngleuInt+tid,0.,mid,T); (AngleuInt+tid)->function = &dJRdI3HighStaeckelIntegrand; I3r1= -gsl_integration_glfixed (AngleuInt+tid,0.,mid,T); (AngleuInt+tid)->function = &dJRdLzHighStaeckelIntegrand; *(Anglephi+ii)= M_PI * *(dJRdLz+ii) + *(Lz+ii) * gsl_integration_glfixed (AngleuInt+tid,0.,mid,T) / *(delta+ii*delta_stride) / sqrt(2.); Or1*= *(delta+ii*delta_stride) / sqrt(2.); I3r1*= *(delta+ii*delta_stride) / sqrt(2.); Or1= M_PI * *(dJRdE+ii) - Or1; I3r1= M_PI * *(dJRdI3+ii) - I3r1; } else { mid= sqrt( ( *(ux+ii) - *(umin+ii) ) ); (AngleuInt+tid)->function = &dJRdELowStaeckelIntegrand; Or1= gsl_integration_glfixed (AngleuInt+tid,0.,mid,T); (AngleuInt+tid)->function = &dJRdI3LowStaeckelIntegrand; I3r1= -gsl_integration_glfixed (AngleuInt+tid,0.,mid,T); (AngleuInt+tid)->function = &dJRdLzLowStaeckelIntegrand; *(Anglephi+ii)= - *(Lz+ii) * gsl_integration_glfixed (AngleuInt+tid,0.,mid,T) / *(delta+ii*delta_stride) / sqrt(2.); Or1*= *(delta+ii*delta_stride) / sqrt(2.); I3r1*= *(delta+ii*delta_stride) / sqrt(2.); } } else { if ( *(ux+ii) > midpoint ) { mid= sqrt( ( *(umax+ii) - *(ux+ii) ) ); (AngleuInt+tid)->function = &dJRdEHighStaeckelIntegrand; Or1= gsl_integration_glfixed (AngleuInt+tid,0.,mid,T); Or1*= *(delta+ii*delta_stride) / sqrt(2.); Or1= M_PI * *(dJRdE+ii) + Or1; (AngleuInt+tid)->function = &dJRdI3HighStaeckelIntegrand; I3r1= -gsl_integration_glfixed (AngleuInt+tid,0.,mid,T); I3r1*= *(delta+ii*delta_stride) / sqrt(2.); I3r1= M_PI * *(dJRdI3+ii) + I3r1; (AngleuInt+tid)->function = &dJRdLzHighStaeckelIntegrand; *(Anglephi+ii)= M_PI * *(dJRdLz+ii) - *(Lz+ii) * gsl_integration_glfixed (AngleuInt+tid,0.,mid,T) / *(delta+ii*delta_stride) / sqrt(2.); } else { mid= sqrt( ( *(ux+ii) - *(umin+ii) ) ); (AngleuInt+tid)->function = &dJRdELowStaeckelIntegrand; Or1= gsl_integration_glfixed (AngleuInt+tid,0.,mid,T); Or1*= *(delta+ii*delta_stride) / sqrt(2.); Or1= 2. * M_PI * *(dJRdE+ii) - Or1; (AngleuInt+tid)->function = &dJRdI3LowStaeckelIntegrand; I3r1= -gsl_integration_glfixed (AngleuInt+tid,0.,mid,T); I3r1*= *(delta+ii*delta_stride) / sqrt(2.); I3r1= 2. * M_PI * *(dJRdI3+ii) - I3r1; (AngleuInt+tid)->function = &dJRdLzLowStaeckelIntegrand; *(Anglephi+ii)= 2. * M_PI * *(dJRdLz+ii) + *(Lz+ii) * gsl_integration_glfixed (AngleuInt+tid,0.,mid,T) / *(delta+ii*delta_stride) / sqrt(2.); } } //Setup v function (paramsv+tid)->delta= *(delta+ii*delta_stride); (paramsv+tid)->E= *(E+ii); (paramsv+tid)->Lz22delta= 0.5 * *(Lz+ii) * *(Lz+ii) / *(delta+ii*delta_stride) / *(delta+ii*delta_stride); (paramsv+tid)->I3V= *(I3V+ii); (paramsv+tid)->u0= *(u0+ii); (paramsv+tid)->cosh2u0= *(cosh2u0+ii); (paramsv+tid)->sinh2u0= *(sinh2u0+ii); (paramsv+tid)->potupi2= *(potupi2+ii); (paramsv+tid)->vmin= *(vmin+ii); (AnglevInt+tid)->params = paramsv+tid; midpoint= *(vmin+ii)+ 0.5 * ( 0.5 * M_PI - *(vmin+ii) ); if ( *(pvx+ii) > 0. ) { if ( *(vx+ii) < midpoint || *(vx+ii) > (M_PI - midpoint) ) { mid = ( *(vx+ii) > 0.5 * M_PI ) ? sqrt( (M_PI - *(vx+ii) - *(vmin+ii))): sqrt( *(vx+ii) - *(vmin+ii)); (AnglevInt+tid)->function = &dJzdELowStaeckelIntegrand; Or2= gsl_integration_glfixed (AnglevInt+tid,0.,mid,T); Or2*= *(delta+ii*delta_stride) / sqrt(2.); (AnglevInt+tid)->function = &dJzdI3LowStaeckelIntegrand; I3r2= gsl_integration_glfixed (AnglevInt+tid,0.,mid,T); I3r2*= *(delta+ii*delta_stride) / sqrt(2.); (AnglevInt+tid)->function = &dJzdLzLowStaeckelIntegrand; phitmp= gsl_integration_glfixed (AnglevInt+tid,0.,mid,T); phitmp*= - *(Lz+ii) / *(delta+ii*delta_stride) / sqrt(2.); if ( *(vx+ii) > 0.5 * M_PI ) { Or2= M_PI * *(dJzdE+ii) - Or2; I3r2= M_PI * *(dJzdI3+ii) - I3r2; phitmp= M_PI * *(dJzdLz+ii) - phitmp; } } else { mid= sqrt( fabs ( 0.5 * M_PI - *(vx+ii) ) ); (AnglevInt+tid)->function = &dJzdEHighStaeckelIntegrand; Or2= gsl_integration_glfixed (AnglevInt+tid,0.,mid,T); Or2*= *(delta+ii*delta_stride) / sqrt(2.); (AnglevInt+tid)->function = &dJzdI3HighStaeckelIntegrand; I3r2= gsl_integration_glfixed (AnglevInt+tid,0.,mid,T); I3r2*= *(delta+ii*delta_stride) / sqrt(2.); (AnglevInt+tid)->function = &dJzdLzHighStaeckelIntegrand; phitmp= gsl_integration_glfixed (AnglevInt+tid,0.,mid,T); phitmp*= - *(Lz+ii) / *(delta+ii*delta_stride) / sqrt(2.); if ( *(vx+ii) > 0.5 * M_PI ) { Or2= 0.5 * M_PI * *(dJzdE+ii) + Or2; I3r2= 0.5 * M_PI * *(dJzdI3+ii) + I3r2; phitmp= 0.5 * M_PI * *(dJzdLz+ii) + phitmp; } else { Or2= 0.5 * M_PI * *(dJzdE+ii) - Or2; I3r2= 0.5 * M_PI * *(dJzdI3+ii) - I3r2; phitmp= 0.5 * M_PI * *(dJzdLz+ii) - phitmp; } } } else { if ( *(vx+ii) < midpoint || *(vx+ii) > (M_PI - midpoint)) { mid = ( *(vx+ii) > 0.5 * M_PI ) ? sqrt( (M_PI - *(vx+ii) - *(vmin+ii))): sqrt( *(vx+ii) - *(vmin+ii)); (AnglevInt+tid)->function = &dJzdELowStaeckelIntegrand; Or2= gsl_integration_glfixed (AnglevInt+tid,0.,mid,T); Or2*= *(delta+ii*delta_stride) / sqrt(2.); (AnglevInt+tid)->function = &dJzdI3LowStaeckelIntegrand; I3r2= gsl_integration_glfixed (AnglevInt+tid,0.,mid,T); I3r2*= *(delta+ii*delta_stride) / sqrt(2.); (AnglevInt+tid)->function = &dJzdLzLowStaeckelIntegrand; phitmp= gsl_integration_glfixed (AnglevInt+tid,0.,mid,T); phitmp*= - *(Lz+ii) / *(delta+ii*delta_stride) / sqrt(2.); if ( *(vx+ii) < 0.5 * M_PI ) { Or2= 2. * M_PI * *(dJzdE+ii) - Or2; I3r2= 2. * M_PI * *(dJzdI3+ii) - I3r2; phitmp= 2. * M_PI * *(dJzdLz+ii) - phitmp; } else { Or2= M_PI * *(dJzdE+ii) + Or2; I3r2= M_PI * *(dJzdI3+ii) + I3r2; phitmp= M_PI * *(dJzdLz+ii) + phitmp; } } else { mid= sqrt( fabs ( 0.5 * M_PI - *(vx+ii) ) ); (AnglevInt+tid)->function = &dJzdEHighStaeckelIntegrand; Or2= gsl_integration_glfixed (AnglevInt+tid,0.,mid,T); Or2*= *(delta+ii*delta_stride) / sqrt(2.); (AnglevInt+tid)->function = &dJzdI3HighStaeckelIntegrand; I3r2= gsl_integration_glfixed (AnglevInt+tid,0.,mid,T); I3r2*= *(delta+ii*delta_stride) / sqrt(2.); (AnglevInt+tid)->function = &dJzdLzHighStaeckelIntegrand; phitmp= gsl_integration_glfixed (AnglevInt+tid,0.,mid,T); phitmp*= - *(Lz+ii) / *(delta+ii*delta_stride) / sqrt(2.); if ( *(vx+ii) < 0.5 * M_PI ) { Or2= 1.5 * M_PI * *(dJzdE+ii) + Or2; I3r2= 1.5 * M_PI * *(dJzdI3+ii) + I3r2; phitmp= 1.5 * M_PI * *(dJzdLz+ii) + phitmp; } else { Or2= 1.5 * M_PI * *(dJzdE+ii) - Or2; I3r2= 1.5 * M_PI * *(dJzdI3+ii) - I3r2; phitmp= 1.5 * M_PI * *(dJzdLz+ii) - phitmp; } } } *(Angler+ii)= *(Omegar+ii) * ( Or1 + Or2 ) + *(dI3dJR+ii) * ( I3r1 + I3r2 ); // In Binney (2012) Anglez starts at zmax/vmin and v_z < 0 / v_v > 0; // Put this on the same system as Isochrone and Spherical angles +pi/2 *(Anglez+ii)= *(Omegaz+ii) * ( Or1 + Or2 ) + *(dI3dJz+ii) * ( I3r1 + I3r2 ) + 0.5 * M_PI; *(Anglephi+ii)+= phitmp; *(Anglephi+ii)+= *(Omegaphi+ii) * ( Or1 + Or2 ) + *(dI3dLz+ii) * ( I3r1 + I3r2 ); *(Angler+ii)= fmod(*(Angler+ii),2. * M_PI); *(Anglez+ii)= fmod(*(Anglez+ii),2. * M_PI); while ( *(Angler+ii) < 0. ) *(Angler+ii)+= 2. * M_PI; while ( *(Anglez+ii) < 0. ) *(Anglez+ii)+= 2. * M_PI; while ( *(Angler+ii) > 2. * M_PI ) *(Angler+ii)-= 2. * M_PI; while ( *(Anglez+ii) > 2. * M_PI ) *(Anglez+ii)-= 2. * M_PI; } free(AngleuInt); free(AnglevInt); free(paramsu); free(paramsv); gsl_integration_glfixed_table_free ( T ); } void calcUminUmax(int ndata, double * umin, double * umax, double * ux, double * pux, double * E, double * Lz, double * I3U, int ndelta, double * delta, double * u0, double * sinh2u0, double * v0, double * sin2v0, double * potu0v0, int nargs, struct potentialArg * actionAngleArgs){ int ii, tid, nthreads; #ifdef _OPENMP nthreads = omp_get_max_threads(); #else nthreads = 1; #endif double peps, meps; gsl_function * JRRoot= (gsl_function *) malloc ( nthreads * sizeof(gsl_function) ); struct JRStaeckelArg * params= (struct JRStaeckelArg *) malloc ( nthreads * sizeof (struct JRStaeckelArg) ); //Setup solver int status; int iter, max_iter = 100; const gsl_root_fsolver_type *T; struct pragmasolver *s= (struct pragmasolver *) malloc ( nthreads * sizeof (struct pragmasolver) );; double u_lo, u_hi; T = gsl_root_fsolver_brent; for (tid=0; tid < nthreads; tid++){ (params+tid)->nargs= nargs; (params+tid)->actionAngleArgs= actionAngleArgs; (s+tid)->s= gsl_root_fsolver_alloc (T); } int delta_stride= ndelta == 1 ? 0 : 1; UNUSED int chunk= CHUNKSIZE; gsl_set_error_handler_off(); #pragma omp parallel for schedule(static,chunk) \ private(tid,ii,iter,status,u_lo,u_hi,meps,peps) \ shared(umin,umax,JRRoot,params,s,ux,delta,E,Lz,I3U,u0,sinh2u0,v0,sin2v0,potu0v0,max_iter) for (ii=0; ii < ndata; ii++){ #ifdef _OPENMP tid= omp_get_thread_num(); #else tid = 0; #endif //Setup function (params+tid)->delta= *(delta+ii*delta_stride); (params+tid)->E= *(E+ii); (params+tid)->Lz22delta= 0.5 * *(Lz+ii) * *(Lz+ii) / *(delta+ii*delta_stride) / *(delta+ii*delta_stride); (params+tid)->I3U= *(I3U+ii); (params+tid)->u0= *(u0+ii); (params+tid)->sinh2u0= *(sinh2u0+ii); (params+tid)->v0= *(v0+ii); (params+tid)->sin2v0= *(sin2v0+ii); (params+tid)->potu0v0= *(potu0v0+ii); (JRRoot+tid)->function = &JRStaeckelIntegrandSquared; (JRRoot+tid)->params = params+tid; //Find starting points for minimum if ( fabs(GSL_FN_EVAL(JRRoot+tid,*(ux+ii))) < 0.0000001){ //we are at umin or umax peps= GSL_FN_EVAL(JRRoot+tid,*(ux+ii)+0.000001); meps= GSL_FN_EVAL(JRRoot+tid,*(ux+ii)-0.000001); if ( fabs(peps) < 0.00000001 && fabs(meps) < 0.00000001 ) {//circular *(umin+ii) = *(ux+ii); *(umax+ii) = *(ux+ii); } else if ( peps < 0. && meps > 0. ) {//umax *(umax+ii)= *(ux+ii); u_lo= 0.9 * (*(ux+ii) - 0.000001); u_hi= *(ux+ii) - 0.0000001; while ( GSL_FN_EVAL(JRRoot+tid,u_lo) >= 0. && u_lo > 0.000000001){ u_hi= u_lo; //this makes sure that brent evaluates using previous u_lo*= 0.9; } //Find root status = gsl_root_fsolver_set ((s+tid)->s, JRRoot+tid, u_lo, u_hi); if (status == GSL_EINVAL) { *(umin+ii) = 0.;//Assume zero if below 0.000000001 } else { iter= 0; do { iter++; status = gsl_root_fsolver_iterate ((s+tid)->s); u_lo = gsl_root_fsolver_x_lower ((s+tid)->s); u_hi = gsl_root_fsolver_x_upper ((s+tid)->s); status = gsl_root_test_interval (u_lo, u_hi, 9.9999999999999998e-13, 4.4408920985006262e-16); } while (status == GSL_CONTINUE && iter < max_iter); // LCOV_EXCL_START if (status == GSL_EINVAL) {//Shouldn't ever get here *(umin+ii) = -9999.99; *(umax+ii) = -9999.99; continue; } // LCOV_EXCL_STOP *(umin+ii) = gsl_root_fsolver_root ((s+tid)->s); } } else if ( peps > 0. && meps < 0. ){//umin *(umin+ii)= *(ux+ii); u_lo= *(ux+ii) + 0.000001; u_hi= 1.1 * (*(ux+ii) + 0.000001); while ( GSL_FN_EVAL(JRRoot+tid,u_hi) >= 0. && u_hi < asinh(37.5/ *(delta+ii*delta_stride))) { u_lo= u_hi; //this makes sure that brent evaluates using previous u_hi*= 1.1; } //Find root status = gsl_root_fsolver_set ((s+tid)->s, JRRoot+tid, u_lo, u_hi); if (status == GSL_EINVAL) { *(umin+ii) = -9999.99; *(umax+ii) = -9999.99; continue; } iter= 0; do { iter++; status = gsl_root_fsolver_iterate ((s+tid)->s); u_lo = gsl_root_fsolver_x_lower ((s+tid)->s); u_hi = gsl_root_fsolver_x_upper ((s+tid)->s); status = gsl_root_test_interval (u_lo, u_hi, 9.9999999999999998e-13, 4.4408920985006262e-16); } while (status == GSL_CONTINUE && iter < max_iter); // LCOV_EXCL_START if (status == GSL_EINVAL) {//Shouldn't ever get here *(umin+ii) = -9999.99; *(umax+ii) = -9999.99; continue; } // LCOV_EXCL_STOP *(umax+ii) = gsl_root_fsolver_root ((s+tid)->s); } } else { u_lo= 0.9 * *(ux+ii); u_hi= *(ux+ii); while ( GSL_FN_EVAL(JRRoot+tid,u_lo) >= 0. && u_lo > 0.000000001){ u_hi= u_lo; //this makes sure that brent evaluates using previous u_lo*= 0.9; } u_hi= (u_lo < 0.9 * *(ux+ii)) ? u_lo / 0.9 / 0.9: *(ux+ii); //Find root status = gsl_root_fsolver_set ((s+tid)->s, JRRoot+tid, u_lo, u_hi); if (status == GSL_EINVAL) { *(umin+ii) = 0.;//Assume zero if below 0.000000001 } else { iter= 0; do { iter++; status = gsl_root_fsolver_iterate ((s+tid)->s); u_lo = gsl_root_fsolver_x_lower ((s+tid)->s); u_hi = gsl_root_fsolver_x_upper ((s+tid)->s); status = gsl_root_test_interval (u_lo, u_hi, 9.9999999999999998e-13, 4.4408920985006262e-16); } while (status == GSL_CONTINUE && iter < max_iter); // LCOV_EXCL_START if (status == GSL_EINVAL) {//Shouldn't ever get here *(umin+ii) = -9999.99; *(umax+ii) = -9999.99; continue; } // LCOV_EXCL_STOP *(umin+ii) = gsl_root_fsolver_root ((s+tid)->s); } //Find starting points for maximum u_lo= *(ux+ii); u_hi= 1.1 * *(ux+ii); while ( GSL_FN_EVAL(JRRoot+tid,u_hi) > 0. && u_hi < asinh(37.5/ *(delta+ii*delta_stride))) { u_lo= u_hi; //this makes sure that brent evaluates using previous u_hi*= 1.1; } u_lo= (u_hi > 1.1 * *(ux+ii)) ? u_hi / 1.1 / 1.1: *(ux+ii); //Find root status = gsl_root_fsolver_set ((s+tid)->s, JRRoot+tid, u_lo, u_hi); if (status == GSL_EINVAL) { *(umin+ii) = -9999.99; *(umax+ii) = -9999.99; continue; } iter= 0; do { iter++; status = gsl_root_fsolver_iterate ((s+tid)->s); u_lo = gsl_root_fsolver_x_lower ((s+tid)->s); u_hi = gsl_root_fsolver_x_upper ((s+tid)->s); status = gsl_root_test_interval (u_lo, u_hi, 9.9999999999999998e-13, 4.4408920985006262e-16); } while (status == GSL_CONTINUE && iter < max_iter); // LCOV_EXCL_START if (status == GSL_EINVAL) {//Shouldn't ever get here *(umin+ii) = -9999.99; *(umax+ii) = -9999.99; continue; } // LCOV_EXCL_STOP *(umax+ii) = gsl_root_fsolver_root ((s+tid)->s); } } gsl_set_error_handler (NULL); for (tid=0; tid < nthreads; tid++) gsl_root_fsolver_free( (s+tid)->s); free(s); free(JRRoot); free(params); } void calcVmin(int ndata, double * vmin, double * vx, double * pvx, double * E, double * Lz, double * I3V, int ndelta, double * delta, double * u0, double * cosh2u0, double * sinh2u0, double * potupi2, int nargs, struct potentialArg * actionAngleArgs){ int ii, tid, nthreads; #ifdef _OPENMP nthreads = omp_get_max_threads(); #else nthreads = 1; #endif gsl_function * JzRoot= (gsl_function *) malloc ( nthreads * sizeof(gsl_function) ); struct JzStaeckelArg * params= (struct JzStaeckelArg *) malloc ( nthreads * sizeof (struct JzStaeckelArg) ); //Setup solver int status; int iter, max_iter = 100; const gsl_root_fsolver_type *T; struct pragmasolver *s= (struct pragmasolver *) malloc ( nthreads * sizeof (struct pragmasolver) );; double v_lo, v_hi; T = gsl_root_fsolver_brent; for (tid=0; tid < nthreads; tid++){ (params+tid)->nargs= nargs; (params+tid)->actionAngleArgs= actionAngleArgs; (s+tid)->s= gsl_root_fsolver_alloc (T); } int delta_stride= ndelta == 1 ? 0 : 1; UNUSED int chunk= CHUNKSIZE; gsl_set_error_handler_off(); #pragma omp parallel for schedule(static,chunk) \ private(tid,ii,iter,status,v_lo,v_hi) \ shared(vmin,JzRoot,params,s,vx,delta,E,Lz,I3V,u0,cosh2u0,sinh2u0,potupi2,max_iter) for (ii=0; ii < ndata; ii++){ #ifdef _OPENMP tid= omp_get_thread_num(); #else tid = 0; #endif //Setup function (params+tid)->delta= *(delta+ii*delta_stride); (params+tid)->E= *(E+ii); (params+tid)->Lz22delta= 0.5 * *(Lz+ii) * *(Lz+ii) / *(delta+ii*delta_stride) / *(delta+ii*delta_stride); (params+tid)->I3V= *(I3V+ii); (params+tid)->u0= *(u0+ii); (params+tid)->cosh2u0= *(cosh2u0+ii); (params+tid)->sinh2u0= *(sinh2u0+ii); (params+tid)->potupi2= *(potupi2+ii); (JzRoot+tid)->function = &JzStaeckelIntegrandSquared; (JzRoot+tid)->params = params+tid; //Find starting points for minimum if ( fabs(GSL_FN_EVAL(JzRoot+tid,*(vx+ii))) < 0.0000001) //we are at vmin *(vmin+ii)= ( *(vx+ii) > 0.5 * M_PI ) ? M_PI - *(vx+ii): *(vx+ii); else { if ( *(vx+ii) > 0.5 * M_PI ){ v_lo= 0.9 * ( M_PI - *(vx+ii) ); v_hi= M_PI - *(vx+ii); } else { v_lo= 0.9 * *(vx+ii); v_hi= *(vx+ii); } while ( GSL_FN_EVAL(JzRoot+tid,v_lo) >= 0. && v_lo > 0.000000001){ v_hi= v_lo; //this makes sure that brent evaluates using previous v_lo*= 0.9; } //Find root status = gsl_root_fsolver_set ((s+tid)->s, JzRoot+tid, v_lo, v_hi); if (status == GSL_EINVAL) { *(vmin+ii) = -9999.99; continue; } iter= 0; do { iter++; status = gsl_root_fsolver_iterate ((s+tid)->s); v_lo = gsl_root_fsolver_x_lower ((s+tid)->s); v_hi = gsl_root_fsolver_x_upper ((s+tid)->s); status = gsl_root_test_interval (v_lo, v_hi, 9.9999999999999998e-13, 4.4408920985006262e-16); } while (status == GSL_CONTINUE && iter < max_iter); // LCOV_EXCL_START if (status == GSL_EINVAL) {//Shouldn't ever get here *(vmin+ii) = -9999.99; continue; } // LCOV_EXCL_STOP *(vmin+ii) = gsl_root_fsolver_root ((s+tid)->s); fflush(stdout); } } gsl_set_error_handler (NULL); for (tid=0; tid < nthreads; tid++) gsl_root_fsolver_free( (s+tid)->s); free(s); free(JzRoot); free(params); } double JRStaeckelIntegrand(double u, void * p){ double out= JRStaeckelIntegrandSquared(u,p); if ( out <= 0.) return 0.; else return sqrt(out); } double JRStaeckelIntegrandSquared(double u, void * p){ struct JRStaeckelArg * params= (struct JRStaeckelArg *) p; double sinh2u= sinh(u) * sinh(u); double dU= (sinh2u+params->sin2v0) *evaluatePotentialsUV(u,params->v0,params->delta, params->nargs,params->actionAngleArgs) - (params->sinh2u0+params->sin2v0)*params->potu0v0; return params->E * sinh2u - params->I3U - dU - params->Lz22delta / sinh2u; } double JRStaeckelIntegrandSquared4dJR(double u, void * p){ struct dJRStaeckelArg * params= (struct dJRStaeckelArg *) p; double sinh2u= sinh(u) * sinh(u); double dU= (sinh2u+params->sin2v0) *evaluatePotentialsUV(u,params->v0,params->delta, params->nargs,params->actionAngleArgs) - (params->sinh2u0+params->sin2v0)*params->potu0v0; return params->E * sinh2u - params->I3U - dU - params->Lz22delta / sinh2u; } double JzStaeckelIntegrand(double v, void * p){ double out= JzStaeckelIntegrandSquared(v,p); if ( out <= 0. ) return 0.; else return sqrt(out); } double JzStaeckelIntegrandSquared(double v, void * p){ struct JzStaeckelArg * params= (struct JzStaeckelArg *) p; double sin2v= sin(v) * sin(v); double dV= params->cosh2u0 * params->potupi2 - (params->sinh2u0+sin2v) *evaluatePotentialsUV(params->u0,v,params->delta, params->nargs,params->actionAngleArgs); return params->E * sin2v + params->I3V + dV - params->Lz22delta / sin2v; } double JzStaeckelIntegrandSquared4dJz(double v, void * p){ struct dJzStaeckelArg * params= (struct dJzStaeckelArg *) p; double sin2v= sin(v) * sin(v); double dV= params->cosh2u0 * params->potupi2 - (params->sinh2u0+sin2v) *evaluatePotentialsUV(params->u0,v,params->delta, params->nargs,params->actionAngleArgs); return params->E * sin2v + params->I3V + dV - params->Lz22delta / sin2v; } double dJRdELowStaeckelIntegrand(double t, void * p){ struct dJRStaeckelArg * params= (struct dJRStaeckelArg *) p; double u= params->umin + t * t; return 2. * t * dJRdEStaeckelIntegrand(u,p); } double dJRdEHighStaeckelIntegrand(double t, void * p){ struct dJRStaeckelArg * params= (struct dJRStaeckelArg *) p; double u= params->umax - t * t; return 2. * t * dJRdEStaeckelIntegrand(u,p); } double dJRdEStaeckelIntegrand(double u, void * p){ double out= JRStaeckelIntegrandSquared4dJR(u,p); if ( out <= 0. ) return 0.; else return sinh(u)*sinh(u)/sqrt(out); } double dJRdLzLowStaeckelIntegrand(double t, void * p){ struct dJRStaeckelArg * params= (struct dJRStaeckelArg *) p; double u= params->umin + t * t; return 2. * t * dJRdLzStaeckelIntegrand(u,p); } double dJRdLzHighStaeckelIntegrand(double t, void * p){ struct dJRStaeckelArg * params= (struct dJRStaeckelArg *) p; double u= params->umax - t * t; return 2. * t * dJRdLzStaeckelIntegrand(u,p); } double dJRdLzStaeckelIntegrand(double u, void * p){ double out= JRStaeckelIntegrandSquared4dJR(u,p); if ( out <= 0. ) return 0.; else return 1./sinh(u)/sinh(u)/sqrt(out); } double dJRdI3LowStaeckelIntegrand(double t, void * p){ struct dJRStaeckelArg * params= (struct dJRStaeckelArg *) p; double u= params->umin + t * t; return 2. * t * dJRdI3StaeckelIntegrand(u,p); } double dJRdI3HighStaeckelIntegrand(double t, void * p){ struct dJRStaeckelArg * params= (struct dJRStaeckelArg *) p; double u= params->umax - t * t; return 2. * t * dJRdI3StaeckelIntegrand(u,p); } double dJRdI3StaeckelIntegrand(double u, void * p){ double out= JRStaeckelIntegrandSquared4dJR(u,p); if ( out <= 0. ) return 0.; else return 1./sqrt(out); } double dJzdELowStaeckelIntegrand(double t, void * p){ struct dJzStaeckelArg * params= (struct dJzStaeckelArg *) p; double v= params->vmin + t * t; return 2. * t * dJzdEStaeckelIntegrand(v,p); } double dJzdEHighStaeckelIntegrand(double t, void * p){ double v= M_PI/2. - t * t; return 2. * t * dJzdEStaeckelIntegrand(v,p); } double dJzdEStaeckelIntegrand(double v, void * p){ double out= JzStaeckelIntegrandSquared4dJz(v,p); if ( out <= 0. ) return 0.; else return sin(v)*sin(v)/sqrt(out); } double dJzdLzLowStaeckelIntegrand(double t, void * p){ struct dJzStaeckelArg * params= (struct dJzStaeckelArg *) p; double v= params->vmin + t * t; return 2. * t * dJzdLzStaeckelIntegrand(v,p); } double dJzdLzHighStaeckelIntegrand(double t, void * p){ double v= M_PI/2. - t * t; return 2. * t * dJzdLzStaeckelIntegrand(v,p); } double dJzdLzStaeckelIntegrand(double v, void * p){ double out= JzStaeckelIntegrandSquared4dJz(v,p); if ( out <= 0. ) return 0.; else return 1./sin(v)/sin(v)/sqrt(out); } double dJzdI3LowStaeckelIntegrand(double t, void * p){ struct dJzStaeckelArg * params= (struct dJzStaeckelArg *) p; double v= params->vmin + t * t; return 2. * t * dJzdI3StaeckelIntegrand(v,p); } double dJzdI3HighStaeckelIntegrand(double t, void * p){ double v= M_PI/2. - t * t; return 2. * t * dJzdI3StaeckelIntegrand(v,p); } double dJzdI3StaeckelIntegrand(double v, void * p){ double out= JzStaeckelIntegrandSquared4dJz(v,p); if ( out <= 0. ) return 0.; else return 1./sqrt(out); } double u0Equation(double u, void * p){ struct u0EqArg * params= (struct u0EqArg *) p; double sinh2u= sinh(u) * sinh(u); double cosh2u= cosh(u) * cosh(u); double dU= cosh2u * evaluatePotentialsUV(u,0.5*M_PI,params->delta, params->nargs,params->actionAngleArgs); return -(params->E*sinh2u-dU-params->Lz22delta/sinh2u); } double evaluatePotentialsUV(double u, double v, double delta, int nargs, struct potentialArg * actionAngleArgs){ double R,z; uv_to_Rz(u,v,&R,&z,delta); return evaluatePotentials(R,z,nargs,actionAngleArgs); } galpy-1.3.0/galpy/actionAngle_src/actionAngleAdiabatic.py0000644000076500000240000003244313231751437023534 0ustar bovystaff00000000000000############################################################################### # actionAngle: a Python module to calculate actions, angles, and frequencies # # class: actionAngleAdiabatic # # wrapper around actionAngleAxi (adiabatic approximation) to do # this for any (x,v) # # methods: # __call__: returns (jr,lz,jz) # ############################################################################### import copy import warnings import math as m import numpy as nu from galpy.util import galpyWarning from galpy.potential import planarPotential, MWPotential from galpy.actionAngle_src.actionAngleAxi import actionAngleAxi from galpy.actionAngle_src.actionAngle import actionAngle import galpy.actionAngle_src.actionAngleAdiabatic_c as actionAngleAdiabatic_c from galpy.actionAngle_src.actionAngleAdiabatic_c import _ext_loaded as ext_loaded from galpy.potential_src.Potential import _check_c class actionAngleAdiabatic(actionAngle): """Action-angle formalism for axisymmetric potentials using the adiabatic approximation""" def __init__(self,*args,**kwargs): """ NAME: __init__ PURPOSE: initialize an actionAngleAdiabatic object INPUT: pot= potential or list of potentials (planarPotentials) gamma= (default=1.) replace Lz by Lz+gamma Jz in effective potential ro= distance from vantage point to GC (kpc; can be Quantity) vo= circular velocity at ro (km/s; can be Quantity) OUTPUT: instance HISTORY: 2012-07-26 - Written - Bovy (IAS@MPIA) """ actionAngle.__init__(self, ro=kwargs.get('ro',None),vo=kwargs.get('vo',None)) if not 'pot' in kwargs: #pragma: no cover raise IOError("Must specify pot= for actionAngleAxi") self._pot= kwargs['pot'] if self._pot == MWPotential: warnings.warn("Use of MWPotential as a Milky-Way-like potential is deprecated; galpy.potential.MWPotential2014, a potential fit to a large variety of dynamical constraints (see Bovy 2015), is the preferred Milky-Way-like potential in galpy", galpyWarning) if ext_loaded and 'c' in kwargs and kwargs['c']: self._c= _check_c(self._pot) if 'c' in kwargs and kwargs['c'] and not self._c: warnings.warn("C module not used because potential does not have a C implementation",galpyWarning) #pragma: no cover else: self._c= False self._gamma= kwargs.get('gamma',1.) # Check the units self._check_consistent_units() return None def _evaluate(self,*args,**kwargs): """ NAME: __call__ (_evaluate) PURPOSE: evaluate the actions (jr,lz,jz) INPUT: Either: a) R,vR,vT,z,vz[,phi]: 1) floats: phase-space value for single object (phi is optional) (each can be a Quantity) 2) numpy.ndarray: [N] phase-space values for N objects (each can be a Quantity) b) Orbit instance: initial condition used if that's it, orbit(t) if there is a time given as well as the second argument c= (object-wide default, bool) True/False to override the object-wide setting for whether or not to use the C implementation scipy.integrate.quadrature keywords _justjr, _justjz= if True, only calculate the radial or vertical action (internal use) OUTPUT: (jr,lz,jz) HISTORY: 2012-07-26 - Written - Bovy (IAS@MPIA) """ if ((self._c and not ('c' in kwargs and not kwargs['c']))\ or (ext_loaded and (('c' in kwargs and kwargs['c'])))) \ and _check_c(self._pot): if len(args) == 5: #R,vR.vT, z, vz R,vR,vT, z, vz= args elif len(args) == 6: #R,vR.vT, z, vz, phi R,vR,vT, z, vz, phi= args else: self._parse_eval_args(*args) R= self._eval_R vR= self._eval_vR vT= self._eval_vT z= self._eval_z vz= self._eval_vz if isinstance(R,float): R= nu.array([R]) vR= nu.array([vR]) vT= nu.array([vT]) z= nu.array([z]) vz= nu.array([vz]) Lz= R*vT jr, jz, err= actionAngleAdiabatic_c.actionAngleAdiabatic_c(\ self._pot,self._gamma,R,vR,vT,z,vz) if err == 0: return (jr,Lz,jz) else: #pragma: no cover raise RuntimeError("C-code for calculation actions failed; try with c=False") else: if 'c' in kwargs and kwargs['c'] and not self._c: warnings.warn("C module not used because potential does not have a C implementation",galpyWarning) #pragma: no cover kwargs.pop('c',None) if (len(args) == 5 or len(args) == 6) \ and isinstance(args[0],nu.ndarray): ojr= nu.zeros((len(args[0]))) olz= nu.zeros((len(args[0]))) ojz= nu.zeros((len(args[0]))) for ii in range(len(args[0])): if len(args) == 5: targs= (args[0][ii],args[1][ii],args[2][ii], args[3][ii],args[4][ii]) elif len(args) == 6: targs= (args[0][ii],args[1][ii],args[2][ii], args[3][ii],args[4][ii],args[5][ii]) tjr,tlz,tjz= self(*targs,**copy.copy(kwargs)) ojr[ii]= tjr ojz[ii]= tjz olz[ii]= tlz return (ojr,olz,ojz) else: #Set up the actionAngleAxi object self._parse_eval_args(*args) if isinstance(self._pot,list): thispot= [p.toPlanar() for p in self._pot] else: thispot= self._pot.toPlanar() if isinstance(self._pot,list): thisverticalpot= [p.toVertical(self._eval_R) for p in self._pot] else: thisverticalpot= self._pot.toVertical(self._eval_R) aAAxi= actionAngleAxi(*args,pot=thispot, verticalPot=thisverticalpot, gamma=self._gamma) if kwargs.get('_justjr',False): kwargs.pop('_justjr') return (aAAxi.JR(**kwargs),nu.nan,nu.nan) elif kwargs.get('_justjz',False): kwargs.pop('_justjz') return (nu.nan,nu.nan,aAAxi.Jz(**kwargs)) else: return (aAAxi.JR(**kwargs),aAAxi._R*aAAxi._vT,aAAxi.Jz(**kwargs)) def _EccZmaxRperiRap(self,*args,**kwargs): """ NAME: EccZmaxRperiRap (_EccZmaxRperiRap) PURPOSE: evaluate the eccentricity, maximum height above the plane, peri- and apocenter in the adiabatic approximation INPUT: Either: a) R,vR,vT,z,vz[,phi]: 1) floats: phase-space value for single object (phi is optional) (each can be a Quantity) 2) numpy.ndarray: [N] phase-space values for N objects (each can be a Quantity) b) Orbit instance: initial condition used if that's it, orbit(t) if there is a time given as well as the second argument c= (object-wide default, bool) True/False to override the object-wide setting for whether or not to use the C implementation OUTPUT: (e,zmax,rperi,rap) HISTORY: 2017-12-21 - Written - Bovy (UofT) """ if ((self._c and not ('c' in kwargs and not kwargs['c']))\ or (ext_loaded and (('c' in kwargs and kwargs['c'])))) \ and _check_c(self._pot): if len(args) == 5: #R,vR.vT, z, vz R,vR,vT, z, vz= args elif len(args) == 6: #R,vR.vT, z, vz, phi R,vR,vT, z, vz, phi= args else: self._parse_eval_args(*args) R= self._eval_R vR= self._eval_vR vT= self._eval_vT z= self._eval_z vz= self._eval_vz if isinstance(R,float): R= nu.array([R]) vR= nu.array([vR]) vT= nu.array([vT]) z= nu.array([z]) vz= nu.array([vz]) rperi,Rap,zmax, err= actionAngleAdiabatic_c.actionAngleRperiRapZmaxAdiabatic_c(\ self._pot,self._gamma,R,vR,vT,z,vz) if err == 0: rap= nu.sqrt(Rap**2.+zmax**2.) ecc= (rap-rperi)/(rap+rperi) return (ecc,zmax,rperi,rap) else: #pragma: no cover raise RuntimeError("C-code for calculation actions failed; try with c=False") else: if 'c' in kwargs and kwargs['c'] and not self._c: warnings.warn("C module not used because potential does not have a C implementation",galpyWarning) #pragma: no cover kwargs.pop('c',None) if (len(args) == 5 or len(args) == 6) \ and isinstance(args[0],nu.ndarray): oecc= nu.zeros((len(args[0]))) orperi= nu.zeros((len(args[0]))) orap= nu.zeros((len(args[0]))) ozmax= nu.zeros((len(args[0]))) for ii in range(len(args[0])): if len(args) == 5: targs= (args[0][ii],args[1][ii],args[2][ii], args[3][ii],args[4][ii]) elif len(args) == 6: targs= (args[0][ii],args[1][ii],args[2][ii], args[3][ii],args[4][ii],args[5][ii]) tecc, tzmax, trperi,trap= self._EccZmaxRperiRap(\ *targs,**copy.copy(kwargs)) oecc[ii]= tecc ozmax[ii]= tzmax orperi[ii]= trperi orap[ii]= trap return (oecc,ozmax,orperi,orap) else: #Set up the actionAngleAxi object self._parse_eval_args(*args) if isinstance(self._pot,list): thispot= [p.toPlanar() for p in self._pot] else: thispot= self._pot.toPlanar() if isinstance(self._pot,list): thisverticalpot= [p.toVertical(self._eval_R) for p in self._pot] else: thisverticalpot= self._pot.toVertical(self._eval_R) aAAxi= actionAngleAxi(*args,pot=thispot, verticalPot=thisverticalpot, gamma=self._gamma) rperi,Rap= aAAxi.calcRapRperi(**kwargs) zmax= aAAxi.calczmax(**kwargs) rap= nu.sqrt(Rap**2.+zmax**2.) return ((rap-rperi)/(rap+rperi),zmax,rperi,rap) def calcRapRperi(self,*args,**kwargs): """ NAME: calcRapRperi PURPOSE: calculate the apocenter and pericenter radii INPUT: Either: a) R,vR,vT,z,vz b) Orbit instance: initial condition used if that's it, orbit(t) if there is a time given as well OUTPUT: (rperi,rap) HISTORY: 2013-11-27 - Written - Bovy (IAS) """ #Set up the actionAngleAxi object if isinstance(self._pot,list): thispot= [p.toPlanar() for p in self._pot if not isinstance(p,planarPotential)] thispot.extend([p for p in self._pot if isinstance(p,planarPotential)]) elif not isinstance(self._pot,planarPotential): thispot= self._pot.toPlanar() else: thispot= self._pot aAAxi= actionAngleAxi(*args,pot=thispot, gamma=self._gamma) return aAAxi.calcRapRperi(**kwargs) def calczmax(self,*args,**kwargs): #pragma: no cover """ NAME: calczmax PURPOSE: calculate the maximum height INPUT: Either: a) R,vR,vT,z,vz b) Orbit instance: initial condition used if that's it, orbit(t) if there is a time given as well OUTPUT: zmax HISTORY: 2012-06-01 - Written - Bovy (IAS) """ warnings.warn("actionAngleAdiabatic.calczmax function will soon be deprecated; please contact galpy's maintainer if you require this function") #Set up the actionAngleAxi object self._parse_eval_args(*args) if isinstance(self._pot,list): thispot= [p.toPlanar() for p in self._pot] else: thispot= self._pot.toPlanar() if isinstance(self._pot,list): thisverticalpot= [p.toVertical(self._eval_R) for p in self._pot] else: thisverticalpot= self._pot.toVertical(self._eval_R) aAAxi= actionAngleAxi(*args,pot=thispot, verticalPot=thisverticalpot, gamma=self._gamma) return aAAxi.calczmax(**kwargs) galpy-1.3.0/galpy/actionAngle_src/actionAngleAdiabatic_c.py0000644000076500000240000002114213231751437024030 0ustar bovystaff00000000000000import os import sys import sysconfig import warnings import ctypes import ctypes.util import numpy from numpy.ctypeslib import ndpointer from galpy.util import galpyWarning from galpy.orbit_src.integrateFullOrbit import _parse_pot #Find and load the library _lib= None outerr= None PY3= sys.version > '3' if PY3: #pragma: no cover _ext_suffix= sysconfig.get_config_var('EXT_SUFFIX') else: _ext_suffix= '.so' for path in sys.path: try: _lib = ctypes.CDLL(os.path.join(path,'galpy_actionAngle_c%s' % _ext_suffix)) except OSError as e: if os.path.exists(os.path.join(path,'galpy_actionAngle_c%s' % _ext_suffix)): #pragma: no cover outerr= e _lib = None else: break if _lib is None: #pragma: no cover if not outerr is None: warnings.warn("actionAngleAdiabatic_c extension module not loaded, because of error '%s' " % outerr, galpyWarning) else: warnings.warn("actionAngleAdiabatic_c extension module not loaded, because galpy_actionAngle_c%s image was not found" % _ext_suffix, galpyWarning) _ext_loaded= False else: _ext_loaded= True def actionAngleAdiabatic_c(pot,gamma,R,vR,vT,z,vz): """ NAME: actionAngleAdiabatic_c PURPOSE: Use C to calculate actions using the adiabatic approximation INPUT: pot - Potential or list of such instances gamma - as in Lz -> Lz+\gamma * J_z R, vR, vT, z, vz - coordinates (arrays) OUTPUT: (jr,jz,err) jr,jz : array, shape (len(R)) err - non-zero if error occured HISTORY: 2012-12-10 - Written - Bovy (IAS) """ #Parse the potential npot, pot_type, pot_args= _parse_pot(pot,potforactions=True) #Set up result arrays jr= numpy.empty(len(R)) jz= numpy.empty(len(R)) err= ctypes.c_int(0) #Set up the C code ndarrayFlags= ('C_CONTIGUOUS','WRITEABLE') actionAngleAdiabatic_actionsFunc= _lib.actionAngleAdiabatic_actions actionAngleAdiabatic_actionsFunc.argtypes= [ctypes.c_int, ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ctypes.c_int, ndpointer(dtype=numpy.int32,flags=ndarrayFlags), ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ctypes.c_double, ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ctypes.POINTER(ctypes.c_int)] #Array requirements, first store old order f_cont= [R.flags['F_CONTIGUOUS'], vR.flags['F_CONTIGUOUS'], vT.flags['F_CONTIGUOUS'], z.flags['F_CONTIGUOUS'], vz.flags['F_CONTIGUOUS']] R= numpy.require(R,dtype=numpy.float64,requirements=['C','W']) vR= numpy.require(vR,dtype=numpy.float64,requirements=['C','W']) vT= numpy.require(vT,dtype=numpy.float64,requirements=['C','W']) z= numpy.require(z,dtype=numpy.float64,requirements=['C','W']) vz= numpy.require(vz,dtype=numpy.float64,requirements=['C','W']) jr= numpy.require(jr,dtype=numpy.float64,requirements=['C','W']) jz= numpy.require(jz,dtype=numpy.float64,requirements=['C','W']) #Run the C code actionAngleAdiabatic_actionsFunc(len(R), R, vR, vT, z, vz, ctypes.c_int(npot), pot_type, pot_args, ctypes.c_double(gamma), jr, jz, ctypes.byref(err)) #Reset input arrays if f_cont[0]: R= numpy.asfortranarray(R) if f_cont[1]: vR= numpy.asfortranarray(vR) if f_cont[2]: vT= numpy.asfortranarray(vT) if f_cont[3]: z= numpy.asfortranarray(z) if f_cont[4]: vz= numpy.asfortranarray(vz) return (jr,jz,err.value) def actionAngleRperiRapZmaxAdiabatic_c(pot,gamma,R,vR,vT,z,vz): """ NAME: actionAngleRperiRapZmaxAdiabatic_c PURPOSE: Use C to calculate planar (Rperi,Rap) and the maximum height Zmax using the adiabatic approximation (rap = sqrt(Rap^2+Zmax^2)) INPUT: pot - Potential or list of such instances gamma - as in Lz -> Lz+\gamma * J_z R, vR, vT, z, vz - coordinates (arrays) OUTPUT: (Rperi,Rap,Zmax,err) Rperi,Rap,Zmax : array, shape (len(R)) err - non-zero if error occured HISTORY: 2017-12-21 - Written - Bovy (UofT) """ #Parse the potential npot, pot_type, pot_args= _parse_pot(pot,potforactions=True) #Set up result arrays rperi= numpy.empty(len(R)) rap= numpy.empty(len(R)) zmax= numpy.empty(len(R)) err= ctypes.c_int(0) #Set up the C code ndarrayFlags= ('C_CONTIGUOUS','WRITEABLE') actionAngleAdiabatic_actionsFunc= _lib.actionAngleAdiabatic_RperiRapZmax actionAngleAdiabatic_actionsFunc.argtypes= [ctypes.c_int, ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ctypes.c_int, ndpointer(dtype=numpy.int32,flags=ndarrayFlags), ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ctypes.c_double, ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ctypes.POINTER(ctypes.c_int)] #Array requirements, first store old order f_cont= [R.flags['F_CONTIGUOUS'], vR.flags['F_CONTIGUOUS'], vT.flags['F_CONTIGUOUS'], z.flags['F_CONTIGUOUS'], vz.flags['F_CONTIGUOUS']] R= numpy.require(R,dtype=numpy.float64,requirements=['C','W']) vR= numpy.require(vR,dtype=numpy.float64,requirements=['C','W']) vT= numpy.require(vT,dtype=numpy.float64,requirements=['C','W']) z= numpy.require(z,dtype=numpy.float64,requirements=['C','W']) vz= numpy.require(vz,dtype=numpy.float64,requirements=['C','W']) rperi= numpy.require(rperi,dtype=numpy.float64,requirements=['C','W']) rap= numpy.require(rap,dtype=numpy.float64,requirements=['C','W']) zmax= numpy.require(zmax,dtype=numpy.float64,requirements=['C','W']) #Run the C code actionAngleAdiabatic_actionsFunc(len(R), R, vR, vT, z, vz, ctypes.c_int(npot), pot_type, pot_args, ctypes.c_double(gamma), rperi, rap, zmax, ctypes.byref(err)) #Reset input arrays if f_cont[0]: R= numpy.asfortranarray(R) if f_cont[1]: vR= numpy.asfortranarray(vR) if f_cont[2]: vT= numpy.asfortranarray(vT) if f_cont[3]: z= numpy.asfortranarray(z) if f_cont[4]: vz= numpy.asfortranarray(vz) return (rperi,rap,zmax,err.value) galpy-1.3.0/galpy/actionAngle_src/actionAngleAdiabaticGrid.py0000644000076500000240000004221013231751437024333 0ustar bovystaff00000000000000############################################################################### # actionAngle: a Python module to calculate actions, angles, and frequencies # # class: actionAngleAdiabaticGrid # # build grid in integrals of motion to quickly evaluate # actionAngleAdiabatic # # methods: # __call__: returns (jr,lz,jz) # ############################################################################### from __future__ import print_function import math import numpy from scipy import interpolate from galpy.actionAngle_src.actionAngleAdiabatic import actionAngleAdiabatic from galpy.actionAngle_src.actionAngle import actionAngle, UnboundError import galpy.potential from galpy.potential_src.Potential import _evaluatePotentials from galpy.util import multi _PRINTOUTSIDEGRID= False class actionAngleAdiabaticGrid(actionAngle): """Action-angle formalism for axisymmetric potentials using the adiabatic approximation, grid-based interpolation""" def __init__(self,pot=None,zmax=1.,gamma=1.,Rmax=5., nR=16,nEz=16,nEr=31,nLz=31,numcores=1, **kwargs): """ NAME: __init__ PURPOSE: initialize an actionAngleAdiabaticGrid object INPUT: pot= potential or list of potentials zmax= zmax for building Ez grid Rmax = Rmax for building grids gamma= (default=1.) replace Lz by Lz+gamma Jz in effective potential nEz=, nEr=, nLz, nR= grid size numcores= number of cpus to use to parallellize c= if True, use C to calculate actions ro= distance from vantage point to GC (kpc; can be Quantity) vo= circular velocity at ro (km/s; can be Quantity) +scipy.integrate.quad keywords OUTPUT: instance HISTORY: 2012-07-27 - Written - Bovy (IAS@MPIA) """ actionAngle.__init__(self, ro=kwargs.get('ro',None),vo=kwargs.get('vo',None)) if pot is None: #pragma: no cover raise IOError("Must specify pot= for actionAngleAxi") self._c= kwargs.pop('c',False) self._gamma= gamma self._pot= pot self._zmax= zmax self._Rmax= Rmax self._Rmin= 0.01 #Set up the actionAngleAdiabatic object that we will use to interpolate self._aA= actionAngleAdiabatic(pot=self._pot,gamma=self._gamma, c=self._c) #Build grid for Ez, first calculate Ez(zmax;R) function self._Rs= numpy.linspace(self._Rmin,self._Rmax,nR) self._EzZmaxs= _evaluatePotentials(self._pot,self._Rs, self._zmax*numpy.ones(nR))\ -_evaluatePotentials(self._pot,self._Rs,numpy.zeros(nR)) self._EzZmaxsInterp= interpolate.InterpolatedUnivariateSpline(self._Rs,numpy.log(self._EzZmaxs),k=3) y= numpy.linspace(0.,1.,nEz) jz= numpy.zeros((nR,nEz)) jzEzzmax= numpy.zeros(nR) thisRs= (numpy.tile(self._Rs,(nEz,1)).T).flatten() thisEzZmaxs= (numpy.tile(self._EzZmaxs,(nEz,1)).T).flatten() thisy= (numpy.tile(y,(nR,1))).flatten() if self._c: jz= self._aA(thisRs, numpy.zeros(len(thisRs)), numpy.ones(len(thisRs)),#these two r dummies numpy.zeros(len(thisRs)), numpy.sqrt(2.*thisy*thisEzZmaxs), **kwargs)[2] jz= numpy.reshape(jz,(nR,nEz)) jzEzzmax[0:nR]= jz[:,nEz-1] else: if numcores > 1: jz= multi.parallel_map((lambda x: self._aA(thisRs[x],0.,1.,#these two r dummies 0.,math.sqrt(2.*thisy[x]*thisEzZmaxs[x]), _justjz=True, **kwargs)[2]), range(nR*nEz),numcores=numcores) jz= numpy.reshape(jz,(nR,nEz)) jzEzzmax[0:nR]= jz[:,nEz-1] else: for ii in range(nR): for jj in range(nEz): #Calculate Jz jz[ii,jj]= self._aA(self._Rs[ii],0.,1.,#these two r dummies 0.,numpy.sqrt(2.*y[jj]*self._EzZmaxs[ii]), _justjz=True,**kwargs)[2] if jj == nEz-1: jzEzzmax[ii]= jz[ii,jj] for ii in range(nR): jz[ii,:]/= jzEzzmax[ii] #First interpolate Ez=Ezmax self._jzEzmaxInterp= interpolate.InterpolatedUnivariateSpline(self._Rs,numpy.log(jzEzzmax+10.**-5.),k=3) self._jz= jz self._jzInterp= interpolate.RectBivariateSpline(self._Rs, y, jz, kx=3,ky=3,s=0.) #JR grid self._Lzmin= 0.01 self._Lzs= numpy.linspace(self._Lzmin, self._Rmax\ *galpy.potential.vcirc(self._pot, self._Rmax), nLz) self._Lzmax= self._Lzs[-1] #Calculate ER(vr=0,R=RL) self._RL= numpy.array([galpy.potential.rl(self._pot,l) for l in self._Lzs]) self._RLInterp= interpolate.InterpolatedUnivariateSpline(self._Lzs, self._RL,k=3) self._ERRL= _evaluatePotentials(self._pot,self._RL,numpy.zeros(nLz)) +self._Lzs**2./2./self._RL**2. self._ERRLmax= numpy.amax(self._ERRL)+1. self._ERRLInterp= interpolate.InterpolatedUnivariateSpline(self._Lzs, numpy.log(-(self._ERRL-self._ERRLmax)),k=3) self._Ramax= 99. self._ERRa= _evaluatePotentials(self._pot,self._Ramax,0.) +self._Lzs**2./2./self._Ramax**2. self._ERRamax= numpy.amax(self._ERRa)+1. self._ERRaInterp= interpolate.InterpolatedUnivariateSpline(self._Lzs, numpy.log(-(self._ERRa-self._ERRamax)),k=3) y= numpy.linspace(0.,1.,nEr) jr= numpy.zeros((nLz,nEr)) jrERRa= numpy.zeros(nLz) thisRL= (numpy.tile(self._RL,(nEr-1,1)).T).flatten() thisLzs= (numpy.tile(self._Lzs,(nEr-1,1)).T).flatten() thisERRL= (numpy.tile(self._ERRL,(nEr-1,1)).T).flatten() thisERRa= (numpy.tile(self._ERRa,(nEr-1,1)).T).flatten() thisy= (numpy.tile(y[0:-1],(nLz,1))).flatten() if self._c: mjr= self._aA(thisRL, numpy.sqrt(2.*(thisERRa+thisy*(thisERRL-thisERRa)-_evaluatePotentials(self._pot,thisRL,numpy.zeros((nEr-1)*nLz)))-thisLzs**2./thisRL**2.), thisLzs/thisRL, numpy.zeros(len(thisRL)), numpy.zeros(len(thisRL)), **kwargs)[0] jr[:,0:-1]= numpy.reshape(mjr,(nLz,nEr-1)) jrERRa[0:nLz]= jr[:,0] else: if numcores > 1: mjr= multi.parallel_map((lambda x: self._aA(thisRL[x], numpy.sqrt(2.*(thisERRa[x]+thisy[x]*(thisERRL[x]-thisERRa[x])-_evaluatePotentials(self._pot,thisRL[x],0.))-thisLzs[x]**2./thisRL[x]**2.), thisLzs[x]/thisRL[x], 0.,0., _justjr=True, **kwargs)[0]), range((nEr-1)*nLz), numcores=numcores) jr[:,0:-1]= numpy.reshape(mjr,(nLz,nEr-1)) jrERRa[0:nLz]= jr[:,0] else: for ii in range(nLz): for jj in range(nEr-1): #Last one is zero by construction try: jr[ii,jj]= self._aA(self._RL[ii], numpy.sqrt(2.*(self._ERRa[ii]+y[jj]*(self._ERRL[ii]-self._ERRa[ii])-_evaluatePotentials(self._pot,self._RL[ii],0.))-self._Lzs[ii]**2./self._RL[ii]**2.), self._Lzs[ii]/self._RL[ii], 0.,0., _justjr=True, **kwargs)[0] except UnboundError: #pragma: no cover raise if jj == 0: jrERRa[ii]= jr[ii,jj] for ii in range(nLz): jr[ii,:]/= jrERRa[ii] #First interpolate Ez=Ezmax self._jr= jr self._jrERRaInterp= interpolate.InterpolatedUnivariateSpline(self._Lzs, numpy.log(jrERRa+10.**-5.),k=3) self._jrInterp= interpolate.RectBivariateSpline(self._Lzs, y, jr, kx=3,ky=3,s=0.) # Check the units self._check_consistent_units() return None def _evaluate(self,*args,**kwargs): """ NAME: __call__ (_evaluate) PURPOSE: evaluate the actions (jr,lz,jz) INPUT: Either: a) R,vR,vT,z,vz[,phi]: 1) floats: phase-space value for single object (phi is optional) (each can be a Quantity) 2) numpy.ndarray: [N] phase-space values for N objects (each can be a Quantity) b) Orbit instance: initial condition used if that's it, orbit(t) if there is a time given as well as the second argument scipy.integrate.quadrature keywords (used when directly evaluating a point off the grid) OUTPUT: (jr,lz,jz) HISTORY: 2012-07-27 - Written - Bovy (IAS@MPIA) NOTE: For a Miyamoto-Nagai potential, this seems accurate to 0.1% and takes ~0.13 ms For a MWPotential, this takes ~ 0.17 ms """ if len(args) == 5: #R,vR.vT, z, vz R,vR,vT, z, vz= args elif len(args) == 6: #R,vR.vT, z, vz, phi R,vR,vT, z, vz, phi= args else: self._parse_eval_args(*args) R= self._eval_R vR= self._eval_vR vT= self._eval_vT z= self._eval_z vz= self._eval_vz #First work on the vertical action Phi= _evaluatePotentials(self._pot,R,z) try: Phio= _evaluatePotentials(self._pot,R,numpy.zeros(len(R))) except TypeError: Phio= _evaluatePotentials(self._pot,R,0.) Ez= Phi-Phio+vz**2./2. #Bigger than Ezzmax? thisEzZmax= numpy.exp(self._EzZmaxsInterp(R)) if isinstance(R,numpy.ndarray): indx= (R > self._Rmax) indx+= (R < self._Rmin) indx+= (Ez != 0.)*(numpy.log(Ez) > thisEzZmax) indxc= True^indx jz= numpy.empty(R.shape) if numpy.sum(indxc) > 0: jz[indxc]= (self._jzInterp.ev(R[indxc],Ez[indxc]/thisEzZmax[indxc])\ *(numpy.exp(self._jzEzmaxInterp(R[indxc]))-10.**-5.)) if numpy.sum(indx) > 0: jz[indx]= self._aA(R[indx], numpy.zeros(numpy.sum(indx)), numpy.ones(numpy.sum(indx)),#these two r dummies numpy.zeros(numpy.sum(indx)), numpy.sqrt(2.*Ez[indx]), _justjz=True, **kwargs)[2] else: if R > self._Rmax or R < self._Rmin or (Ez != 0 and numpy.log(Ez) > thisEzZmax): #Outside of the grid if _PRINTOUTSIDEGRID: #pragma: no cover print("Outside of grid in Ez", R > self._Rmax , R < self._Rmin , (Ez != 0 and numpy.log(Ez) > thisEzZmax)) jz= self._aA(R,0.,1.,#these two r dummies 0.,math.sqrt(2.*Ez), _justjz=True, **kwargs)[2] else: jz= (self._jzInterp(R,Ez/thisEzZmax)\ *(numpy.exp(self._jzEzmaxInterp(R))-10.**-5.))[0][0] #Radial action ERLz= numpy.fabs(R*vT)+self._gamma*jz ER= Phio+vR**2./2.+ERLz**2./2./R**2. thisRL= self._RLInterp(ERLz) thisERRL= -numpy.exp(self._ERRLInterp(ERLz))+self._ERRLmax thisERRa= -numpy.exp(self._ERRaInterp(ERLz))+self._ERRamax if isinstance(R,numpy.ndarray): indx= ((ER-thisERRa)/(thisERRL-thisERRa) > 1.)\ *(((ER-thisERRa)/(thisERRL-thisERRa)-1.) < 10.**-2.) ER[indx]= thisERRL[indx] indx= ((ER-thisERRa)/(thisERRL-thisERRa) < 0.)\ *((ER-thisERRa)/(thisERRL-thisERRa) > -10.**-2.) ER[indx]= thisERRa[indx] indx= (ERLz < self._Lzmin) indx+= (ERLz > self._Lzmax) indx+= ((ER-thisERRa)/(thisERRL-thisERRa) > 1.) indx+= ((ER-thisERRa)/(thisERRL-thisERRa) < 0.) indxc= True^indx jr= numpy.empty(R.shape) if numpy.sum(indxc) > 0: jr[indxc]= (self._jrInterp.ev(ERLz[indxc], (ER[indxc]-thisERRa[indxc])/(thisERRL[indxc]-thisERRa[indxc]))\ *(numpy.exp(self._jrERRaInterp(ERLz[indxc]))-10.**-5.)) if numpy.sum(indx) > 0: jr[indx]= self._aA(thisRL[indx], numpy.sqrt(2.*(ER[indx]-_evaluatePotentials(self._pot,thisRL[indx],0.))-ERLz[indx]**2./thisRL[indx]**2.), ERLz[indx]/thisRL[indx], numpy.zeros(len(thisRL)), numpy.zeros(len(thisRL)), _justjr=True, **kwargs)[0] else: if (ER-thisERRa)/(thisERRL-thisERRa) > 1. \ and ((ER-thisERRa)/(thisERRL-thisERRa)-1.) < 10.**-2.: ER= thisERRL elif (ER-thisERRa)/(thisERRL-thisERRa) < 0. \ and (ER-thisERRa)/(thisERRL-thisERRa) > -10.**-2.: ER= thisERRa #Outside of grid? if ERLz < self._Lzmin or ERLz > self._Lzmax \ or (ER-thisERRa)/(thisERRL-thisERRa) > 1. \ or (ER-thisERRa)/(thisERRL-thisERRa) < 0.: if _PRINTOUTSIDEGRID: #pragma: no cover print("Outside of grid in ER/Lz", ERLz < self._Lzmin , ERLz > self._Lzmax \ , (ER-thisERRa)/(thisERRL-thisERRa) > 1. \ , (ER-thisERRa)/(thisERRL-thisERRa) < 0., ER, thisERRL, thisERRa, (ER-thisERRa)/(thisERRL-thisERRa)) jr= self._aA(thisRL[0], numpy.sqrt(2.*(ER-_evaluatePotentials(self._pot,thisRL,0.))-ERLz**2./thisRL**2.)[0], (ERLz/thisRL)[0], 0.,0., _justjr=True, **kwargs)[0] else: jr= (self._jrInterp(ERLz, (ER-thisERRa)/(thisERRL-thisERRa))\ *(numpy.exp(self._jrERRaInterp(ERLz))-10.**-5.))[0][0] return (jr,R*vT,jz) def Jz(self,*args,**kwargs): """ NAME: Jz PURPOSE: evaluate the action jz INPUT: Either: a) R,vR,vT,z,vz b) Orbit instance: initial condition used if that's it, orbit(t) if there is a time given as well scipy.integrate.quadrature keywords OUTPUT: jz HISTORY: 2012-07-30 - Written - Bovy (IAS@MPIA) """ self._parse_eval_args(*args) Phi= _evaluatePotentials(self._pot,self._eval_R,self._eval_z) Phio= _evaluatePotentials(self._pot,self._eval_R,0.) Ez= Phi-Phio+self._eval_vz**2./2. #Bigger than Ezzmax? thisEzZmax= numpy.exp(self._EzZmaxsInterp(self._eval_R)) if self._eval_R > self._Rmax or self._eval_R < self._Rmin or (Ez != 0. and numpy.log(Ez) > thisEzZmax): #Outside of the grid if _PRINTOUTSIDEGRID: #pragma: no cover print("Outside of grid in Ez") jz= self._aA(self._eval_R,0.,1.,#these two r dummies 0.,math.sqrt(2.*Ez), _justjz=True, **kwargs)[2] else: jz= (self._jzInterp(self._eval_R,Ez/thisEzZmax)\ *(numpy.exp(self._jzEzmaxInterp(self._eval_R))-10.**-5.))[0][0] return jz galpy-1.3.0/galpy/actionAngle_src/actionAngleAxi.py0000644000076500000240000003457413003465421022413 0ustar bovystaff00000000000000############################################################################### # actionAngle: a Python module to calculate actions, angles, and frequencies # # class: actionAngleAxi # # methods: # JR # Jphi # angleR # TR # Tphi # I # calcRapRperi # calcEL ############################################################################### import math as m import numpy as nu from scipy import optimize, integrate from galpy.actionAngle_src.actionAngle import * from galpy.actionAngle_src.actionAngleVertical import actionAngleVertical from galpy.potential_src.planarPotential import _evaluateplanarPotentials from galpy.potential_src.Potential import epifreq from galpy.potential import vcirc _EPS= 10.**-15. class actionAngleAxi(actionAngleVertical): """Action-angle formalism for axisymmetric potentials""" def __init__(self,*args,**kwargs): """ NAME: __init__ PURPOSE: initialize an actionAngleAxi object INPUT: Either: a) R,vR,vT b) Orbit instance: initial condition used if that's it, orbit(t) if there is a time given as well pot= potential or list of potentials (planarPotentials) verticalPot= the vertical Potential gamma= (default=1.) replace Lz by Lz+gamma Jz in effective potential (if there is no vertical potential, this is set to zero) OUTPUT: HISTORY: 2010-12-01 - Written - Bovy (NYU) """ self._parse_eval_args(*args,_noOrbUnitsCheck=True,**kwargs) self._R= self._eval_R self._vR= self._eval_vR self._vT= self._eval_vT if not 'pot' in kwargs: #pragma: no cover raise IOError("Must specify pot= for actionAngleAxi") self._pot= kwargs['pot'] if 'verticalPot' in kwargs: kwargs.pop('pot') actionAngleVertical.__init__(self,*args,pot=kwargs['verticalPot'], **kwargs) self._gamma= kwargs.get('gamma',1.) else: self._gamma= 0. return None def angleR(self,**kwargs): #pragma: no cover """ NAME: AngleR PURPOSE: Calculate the radial angle INPUT: scipy.integrate.quadrature keywords OUTPUT: w_R(R,vT,vT) in radians + estimate of the error (does not include TR error) HISTORY: 2010-12-01 - Written - Bovy (NYU) """ if hasattr(self,'_angleR'): return self._angleR (rperi,rap)= self.calcRapRperi(**kwargs) if rap == rperi: return 0. TR= self.TR(**kwargs)[0] EL= self.calcEL(**kwargs) E, L= EL Rmean= m.exp((m.log(rperi)+m.log(rap))/2.) if self._R < Rmean: if self._R > rperi: wR= (2.*m.pi/TR* nu.array(integrate.quadrature(_TRAxiIntegrandSmall, 0.,m.sqrt(self._R-rperi), args=(E,L,self._pot,rperi), **kwargs)))\ +nu.array([m.pi,0.]) else: wR= nu.array([m.pi,0.]) else: if self._R < rap: wR= -(2.*m.pi/TR* nu.array(integrate.quadrature(_TRAxiIntegrandLarge, 0.,m.sqrt(rap-self._R), args=(E,L,self._pot,rap), **kwargs))) else: wR= nu.array([0.,0.]) if self._vR < 0.: wR[0]+= m.pi self._angleR= nu.array([wR[0] % (2.*m.pi),wR[1]]) return self._angleR def TR(self,**kwargs): #pragma: no cover """ NAME: TR PURPOSE: Calculate the radial period for a power-law rotation curve INPUT: scipy.integrate.quadrature keywords OUTPUT: T_R(R,vT,vT)*vc/ro + estimate of the error HISTORY: 2010-12-01 - Written - Bovy (NYU) """ if hasattr(self,'_TR'): return self._TR (rperi,rap)= self.calcRapRperi(**kwargs) if nu.fabs(rap-rperi)/rap < 10.**-4.: #Rough limit self._TR= 2.*m.pi/epifreq(self._pot,self._R,use_physical=False) return self._TR Rmean= m.exp((m.log(rperi)+m.log(rap))/2.) EL= self.calcEL(**kwargs) E, L= EL TR= 0. if Rmean > rperi: TR+= integrate.quadrature(_TRAxiIntegrandSmall, 0.,m.sqrt(Rmean-rperi), args=(E,L,self._pot,rperi), **kwargs)[0] if Rmean < rap: TR+= integrate.quadrature(_TRAxiIntegrandLarge, 0.,m.sqrt(rap-Rmean), args=(E,L,self._pot,rap), **kwargs)[0] self._TR= 2.*TR return self._TR def Tphi(self,**kwargs): #pragma: no cover """ NAME: Tphi PURPOSE: Calculate the azimuthal period INPUT: +scipy.integrate.quadrature keywords OUTPUT: T_phi(R,vT,vT)/ro/vc + estimate of the error HISTORY: 2010-12-01 - Written - Bovy (NYU) """ if hasattr(self,'_Tphi'): return self._Tphi (rperi,rap)= self.calcRapRperi(**kwargs) if rap == rperi:#Circular orbit return 2.*m.pi*self._R/self._vT TR= self.TR(**kwargs) I= self.I(**kwargs) Tphi= TR/I*m.pi self._Tphi= Tphi return self._Tphi def I(self,**kwargs): #pragma: no cover """ NAME: I PURPOSE: Calculate I, the 'ratio' between the radial and azimutha period INPUT: +scipy.integrate.quadrature keywords OUTPUT: I(R,vT,vT) + estimate of the error HISTORY: 2010-12-01 - Written - Bovy (NYU) """ if hasattr(self,'_I'): return self._I (rperi,rap)= self.calcRapRperi(**kwargs) Rmean= m.exp((m.log(rperi)+m.log(rap))/2.) if nu.fabs(rap-rperi)/rap < 10.**-4.: #Rough limit TR= self.TR()[0] Tphi= self.Tphi()[0] self._I= TR/Tphi*m.pi return self._I EL= self.calcEL(**kwargs) E, L= EL I= 0. if Rmean > rperi: I+= integrate.quadrature(_IAxiIntegrandSmall, 0.,m.sqrt(Rmean-rperi), args=(E,L,self._pot,rperi), **kwargs)[0] if Rmean < rap: I+= integrate.quadrature(_IAxiIntegrandLarge, 0.,m.sqrt(rap-Rmean), args=(E,L,self._pot,rap), **kwargs)[0] self._I= I*self._R*self._vT return self._I def Jphi(self): #pragma: no cover """ NAME: Jphi PURPOSE: Calculate the azimuthal action INPUT: OUTPUT: J_R(R,vT,vT)/ro/vc HISTORY: 2010-12-01 - Written - Bovy (NYU) """ return self._R*self._vT def JR(self,**kwargs): """ NAME: JR PURPOSE: Calculate the radial action INPUT: +scipy.integrate.quad keywords OUTPUT: J_R(R,vT,vT)/ro/vc + estimate of the error HISTORY: 2010-12-01 - Written - Bovy (NYU) """ if hasattr(self,'_JR'): #pragma: no cover return self._JR (rperi,rap)= self.calcRapRperi(**kwargs) EL= self.calcEL(**kwargs) E, L= EL self._JR= 1./nu.pi*integrate.quad(_JRAxiIntegrand,rperi,rap, args=(E,L,self._pot), **kwargs)[0] return self._JR def calcEL(self,**kwargs): """ NAME: calcEL PURPOSE: calculate the energy and angular momentum INPUT: scipy.integrate.quadrature keywords OUTPUT: (E,L) HISTORY: 2012-07-26 - Written - Bovy (IAS) """ E,L= calcELAxi(self._R,self._vR,self._vT,self._pot) if self._gamma != 0.: #Adjust E E-= self._vT**2./2. L= m.fabs(L)+self._gamma*self.Jz(**kwargs) E+= L**2./2./self._R**2. return (E,L) def calcRapRperi(self,**kwargs): """ NAME: calcRapRperi PURPOSE: calculate the apocenter and pericenter radii INPUT: OUTPUT: (rperi,rap) HISTORY: 2010-12-01 - Written - Bovy (NYU) """ if hasattr(self,'_rperirap'): #pragma: no cover return self._rperirap EL= self.calcEL(**kwargs) E, L= EL if self._vR == 0. and m.fabs(self._vT - vcirc(self._pot,self._R,use_physical=False)) < _EPS: #We are on a circular orbit rperi= self._R rap = self._R elif self._vR == 0. and self._vT > vcirc(self._pot,self._R,use_physical=False): #We are exactly at pericenter rperi= self._R if self._gamma != 0.: startsign= _rapRperiAxiEq(self._R+10.**-8.,E,L,self._pot) startsign/= m.fabs(startsign) else: startsign= 1. rend= _rapRperiAxiFindStart(self._R,E,L,self._pot,rap=True, startsign=startsign) rap= optimize.brentq(_rapRperiAxiEq,rperi+0.00001,rend, args=(E,L,self._pot)) # fprime=_rapRperiAxiDeriv) elif self._vR == 0. and self._vT < vcirc(self._pot,self._R,use_physical=False): #We are exactly at apocenter rap= self._R if self._gamma != 0.: startsign= _rapRperiAxiEq(self._R-10.**-8.,E,L,self._pot) startsign/= m.fabs(startsign) else: startsign= 1. rstart= _rapRperiAxiFindStart(self._R,E,L,self._pot, startsign=startsign) if rstart == 0.: rperi= 0. else: rperi= optimize.brentq(_rapRperiAxiEq,rstart,rap-0.000001, args=(E,L,self._pot)) # fprime=_rapRperiAxiDeriv) else: if self._gamma != 0.: startsign= _rapRperiAxiEq(self._R,E,L,self._pot) startsign/= m.fabs(startsign) else: startsign= 1. rstart= _rapRperiAxiFindStart(self._R,E,L,self._pot, startsign=startsign) if rstart == 0.: rperi= 0. else: try: rperi= optimize.brentq(_rapRperiAxiEq,rstart,self._R, (E,L,self._pot), maxiter=200) except RuntimeError: #pragma: no cover raise UnboundError("Orbit seems to be unbound") rend= _rapRperiAxiFindStart(self._R,E,L,self._pot,rap=True, startsign=startsign) rap= optimize.brentq(_rapRperiAxiEq,self._R,rend, (E,L,self._pot)) self._rperirap= (rperi,rap) return self._rperirap def calcELAxi(R,vR,vT,pot,vc=1.,ro=1.): """ NAME: calcELAxi PURPOSE: calculate the energy and angular momentum INPUT: R - Galactocentric radius (/ro) vR - radial part of the velocity (/vc) vT - azimuthal part of the velocity (/vc) vc - circular velocity ro - reference radius OUTPUT: (E,L) HISTORY: 2010-11-30 - Written - Bovy (NYU) """ return (potentialAxi(R,pot)+vR**2./2.+vT**2./2.,R*vT) def potentialAxi(R,pot,vc=1.,ro=1.): """ NAME: potentialAxi PURPOSE: return the potential INPUT: R - Galactocentric radius (/ro) pot - potential vc - circular velocity ro - reference radius OUTPUT: Phi(R) HISTORY: 2010-11-30 - Written - Bovy (NYU) """ return _evaluateplanarPotentials(pot,R) def _JRAxiIntegrand(r,E,L,pot): """The J_R integrand""" return nu.sqrt(2.*(E-potentialAxi(r,pot))-L**2./r**2.) def _TRAxiIntegrandSmall(t,E,L,pot,rperi): #pragma: no cover r= rperi+t**2.#part of the transformation return 2.*t/_JRAxiIntegrand(r,E,L,pot) def _TRAxiIntegrandLarge(t,E,L,pot,rap): #pragma: no cover r= rap-t**2.#part of the transformation return 2.*t/_JRAxiIntegrand(r,E,L,pot) def _IAxiIntegrandSmall(t,E,L,pot,rperi): #pragma: no cover r= rperi+t**2.#part of the transformation return 2.*t/_JRAxiIntegrand(r,E,L,pot)/r**2. def _IAxiIntegrandLarge(t,E,L,pot,rap): #pragma: no cover r= rap-t**2.#part of the transformation return 2.*t/_JRAxiIntegrand(r,E,L,pot)/r**2. def _rapRperiAxiEq(R,E,L,pot): """The vr=0 equation that needs to be solved to find apo- and pericenter""" return E-potentialAxi(R,pot)-L**2./2./R**2. def _rapRperiAxiFindStart(R,E,L,pot,rap=False,startsign=1.): """ NAME: _rapRperiAxiFindStart PURPOSE: Find adequate start or end points to solve for rap and rperi INPUT: R - Galactocentric radius E - energy L - angular momentum pot - potential rap - if True, find the rap end-point startsign= set to -1 if the function is not positive (due to gamma) OUTPUT: rstart or rend HISTORY: 2010-12-01 - Written - Bovy (NYU) """ if rap: rtry= 2.*R else: rtry= R/2. while startsign*_rapRperiAxiEq(rtry,E,L,pot) > 0. \ and rtry > 0.000000001: if rap: if rtry > 100.: #pragma: no cover raise UnboundError("Orbit seems to be unbound") rtry*= 2. else: rtry/= 2. if rtry < 0.000000001: return 0. return rtry galpy-1.3.0/galpy/actionAngle_src/actionAngleIsochrone.py0000644000076500000240000003262713236350147023626 0ustar bovystaff00000000000000############################################################################### # actionAngle: a Python module to calculate actions, angles, and frequencies # # class: actionAngleIsochrone # # Calculate actions-angle coordinates for the Isochrone potential # # methods: # __call__: returns (jr,lz,jz) # actionsFreqs: returns (jr,lz,jz,Or,Op,Oz) # actionsFreqsAngles: returns (jr,lz,jz,Or,Op,Oz,ar,ap,az) # ############################################################################### import copy import warnings import numpy as nu from galpy.actionAngle_src.actionAngle import actionAngle from galpy.potential import IsochronePotential from galpy.util import galpyWarning _APY_LOADED= True try: from astropy import units except ImportError: _APY_LOADED= False class actionAngleIsochrone(actionAngle): """Action-angle formalism for the isochrone potential, on the Jphi, Jtheta system of Binney & Tremaine (2008)""" def __init__(self,*args,**kwargs): """ NAME: __init__ PURPOSE: initialize an actionAngleIsochrone object INPUT: Either: b= scale parameter of the isochrone parameter (can be Quantity) ip= instance of a IsochronePotential ro= distance from vantage point to GC (kpc; can be Quantity) vo= circular velocity at ro (km/s; can be Quantity) OUTPUT: instance HISTORY: 2013-09-08 - Written - Bovy (IAS) """ actionAngle.__init__(self, ro=kwargs.get('ro',None),vo=kwargs.get('vo',None)) if not 'b' in kwargs and not 'ip' in kwargs: #pragma: no cover raise IOError("Must specify b= for actionAngleIsochrone") if 'ip' in kwargs: ip= kwargs['ip'] if not isinstance(ip,IsochronePotential): #pragma: no cover raise IOError("'Provided ip= does not appear to be an instance of an IsochronePotential") # Check the units self._pot= ip self._check_consistent_units() self.b= ip.b self.amp= ip._amp else: self.b= kwargs['b'] if _APY_LOADED and isinstance(self.b,units.Quantity): self.b= self.b.to(units.kpc).value/self._ro rb= nu.sqrt(self.b**2.+1.) self.amp= (self.b+rb)**2.*rb self._c= False ext_loaded= False if ext_loaded and (('c' in kwargs and kwargs['c']) or not 'c' in kwargs): #pragma: no cover self._c= True else: self._c= False if not self._c: self._ip= IsochronePotential(amp=self.amp,b=self.b) #Define _pot, because some functions that use actionAngle instances need this self._pot= IsochronePotential(amp=self.amp,b=self.b) # Check the units self._check_consistent_units() return None def _evaluate(self,*args,**kwargs): """ NAME: __call__ (_evaluate) PURPOSE: evaluate the actions (jr,lz,jz) INPUT: Either: a) R,vR,vT,z,vz[,phi]: 1) floats: phase-space value for single object (phi is optional) (each can be a Quantity) 2) numpy.ndarray: [N] phase-space values for N objects (each can be a Quantity) b) Orbit instance: initial condition used if that's it, orbit(t) if there is a time given as well as the second argument OUTPUT: (jr,lz,jz) HISTORY: 2013-09-08 - Written - Bovy (IAS) """ if len(args) == 5: #R,vR.vT, z, vz R,vR,vT, z, vz= args elif len(args) == 6: #R,vR.vT, z, vz, phi R,vR,vT, z, vz, phi= args else: self._parse_eval_args(*args) R= self._eval_R vR= self._eval_vR vT= self._eval_vT z= self._eval_z vz= self._eval_vz if isinstance(R,float): R= nu.array([R]) vR= nu.array([vR]) vT= nu.array([vT]) z= nu.array([z]) vz= nu.array([vz]) if self._c: #pragma: no cover pass else: Lz= R*vT Lx= -z*vT Ly= z*vR-R*vz L2= Lx*Lx+Ly*Ly+Lz*Lz E= self._ip(R,z)+vR**2./2.+vT**2./2.+vz**2./2. L= nu.sqrt(L2) #Actions Jphi= Lz Jz= L-nu.fabs(Lz) Jr= self.amp/nu.sqrt(-2.*E)\ -0.5*(L+nu.sqrt((L2+4.*self.amp*self.b))) return (Jr,Jphi,Jz) def _actionsFreqs(self,*args,**kwargs): """ NAME: actionsFreqs (_actionsFreqs) PURPOSE: evaluate the actions and frequencies (jr,lz,jz,Omegar,Omegaphi,Omegaz) INPUT: Either: a) R,vR,vT,z,vz[,phi]: 1) floats: phase-space value for single object (phi is optional) (each can be a Quantity) 2) numpy.ndarray: [N] phase-space values for N objects (each can be a Quantity) b) Orbit instance: initial condition used if that's it, orbit(t) if there is a time given as well as the second argument OUTPUT: (jr,lz,jz,Omegar,Omegaphi,Omegaz) HISTORY: 2013-09-08 - Written - Bovy (IAS) """ if len(args) == 5: #R,vR.vT, z, vz R,vR,vT, z, vz= args elif len(args) == 6: #R,vR.vT, z, vz, phi R,vR,vT, z, vz, phi= args else: self._parse_eval_args(*args) R= self._eval_R vR= self._eval_vR vT= self._eval_vT z= self._eval_z vz= self._eval_vz if isinstance(R,float): R= nu.array([R]) vR= nu.array([vR]) vT= nu.array([vT]) z= nu.array([z]) vz= nu.array([vz]) if self._c: #pragma: no cover pass else: Lz= R*vT Lx= -z*vT Ly= z*vR-R*vz L2= Lx*Lx+Ly*Ly+Lz*Lz E= self._ip(R,z)+vR**2./2.+vT**2./2.+vz**2./2. L= nu.sqrt(L2) #Actions Jphi= Lz Jz= L-nu.fabs(Lz) Jr= self.amp/nu.sqrt(-2.*E)\ -0.5*(L+nu.sqrt((L2+4.*self.amp*self.b))) #Frequencies Omegar= (-2.*E)**1.5/self.amp Omegaz= 0.5*(1.+L/nu.sqrt(L2+4.*self.amp*self.b))*Omegar Omegaphi= copy.copy(Omegaz) indx= Lz < 0. Omegaphi[indx]*= -1. return (Jr,Jphi,Jz,Omegar,Omegaphi,Omegaz) def _actionsFreqsAngles(self,*args,**kwargs): """ NAME: actionsFreqsAngles (_actionsFreqsAngles) PURPOSE: evaluate the actions, frequencies, and angles (jr,lz,jz,Omegar,Omegaphi,Omegaz,angler,anglephi,anglez) INPUT: Either: a) R,vR,vT,z,vz[,phi]: 1) floats: phase-space value for single object (phi is optional) (each can be a Quantity) 2) numpy.ndarray: [N] phase-space values for N objects (each can be a Quantity) b) Orbit instance: initial condition used if that's it, orbit(t) if there is a time given as well as the second argument OUTPUT: (jr,lz,jz,Omegar,Omegaphi,Omegaz,angler,anglephi,anglez) HISTORY: 2013-09-08 - Written - Bovy (IAS) """ if len(args) == 5: #R,vR.vT, z, vz pragma: no cover raise IOError("You need to provide phi when calculating angles") elif len(args) == 6: #R,vR.vT, z, vz, phi R,vR,vT, z, vz, phi= args else: self._parse_eval_args(*args) R= self._eval_R vR= self._eval_vR vT= self._eval_vT z= self._eval_z vz= self._eval_vz phi= self._eval_phi if isinstance(R,float): R= nu.array([R]) vR= nu.array([vR]) vT= nu.array([vT]) z= nu.array([z]) vz= nu.array([vz]) phi= nu.array([phi]) if self._c: #pragma: no cover pass else: Lz= R*vT Lx= -z*vT Ly= z*vR-R*vz L2= Lx*Lx+Ly*Ly+Lz*Lz E= self._ip(R,z)+vR**2./2.+vT**2./2.+vz**2./2. L= nu.sqrt(L2) #Actions Jphi= Lz Jz= L-nu.fabs(Lz) Jr= self.amp/nu.sqrt(-2.*E)\ -0.5*(L+nu.sqrt((L2+4.*self.amp*self.b))) #Frequencies Omegar= (-2.*E)**1.5/self.amp Omegaz= 0.5*(1.+L/nu.sqrt(L2+4.*self.amp*self.b))*Omegar Omegaphi= copy.copy(Omegaz) indx= Lz < 0. Omegaphi[indx]*= -1. #Angles c= -self.amp/2./E-self.b e2= 1.-L2/self.amp/c*(1.+self.b/c) e= nu.sqrt(e2) if self.b == 0.: coseta= 1/e*(1.-nu.sqrt(R**2.+z**2.)/c) else: s= 1.+nu.sqrt(1.+(R**2.+z**2.)/self.b**2.) coseta= 1/e*(1.-self.b/c*(s-2.)) pindx= (coseta > 1.)*(coseta < (1.+10.**-7.)) coseta[pindx]= 1. pindx= (coseta < -1.)*(coseta > (-1.-10.**-7.)) coseta[pindx]= -1. eta= nu.arccos(coseta) costheta= z/nu.sqrt(R**2.+z**2.) sintheta= R/nu.sqrt(R**2.+z**2.) vrindx= (vR*sintheta+vz*costheta) < 0. eta[vrindx]= 2.*nu.pi-eta[vrindx] angler= eta-e*c/(c+self.b)*nu.sin(eta) tan11= nu.arctan(nu.sqrt((1.+e)/(1.-e))*nu.tan(0.5*eta)) tan12= nu.arctan(nu.sqrt((1.+e+2.*self.b/c)/(1.-e+2.*self.b/c))*nu.tan(0.5*eta)) vzindx= (-vz*sintheta+vR*costheta) > 0. tan11[tan11 < 0.]+= nu.pi tan12[tan12 < 0.]+= nu.pi pindx= (Lz/L > 1.)*(Lz/L < (1.+10.**-7.)) Lz[pindx]= L[pindx] pindx= (Lz/L < -1.)*(Lz/L > (-1.-10.**-7.)) Lz[pindx]= -L[pindx] i= nu.arccos(Lz/L) sinpsi= costheta/nu.sin(i) pindx= (sinpsi > 1.)*(sinpsi < (1.+10.**-7.)) sinpsi[pindx]= 1. pindx= (sinpsi < -1.)*(sinpsi > (-1.-10.**-7.)) sinpsi[pindx]= -1. psi= nu.arcsin(sinpsi) psi[vzindx]= nu.pi-psi[vzindx] psi= psi % (2.*nu.pi) anglez= psi+Omegaz/Omegar*angler\ -tan11-1./nu.sqrt(1.+4*self.amp*self.b/L2)*tan12 sinu= z/R/nu.tan(i) pindx= (sinu > 1.)*(sinu < (1.+10.**-7.)) sinu[pindx]= 1. pindx= (sinu < -1.)*(sinu > (-1.-10.**-7.)) sinu[pindx]= -1. u= nu.arcsin(sinu) u[vzindx]= nu.pi-u[vzindx] Omega= phi-u anglephi= Omega anglephi[indx]-= anglez[indx] anglephi[True^indx]+= anglez[True^indx] angler= angler % (2.*nu.pi) anglephi= anglephi % (2.*nu.pi) anglez= anglez % (2.*nu.pi) return (Jr,Jphi,Jz,Omegar,Omegaphi,Omegaz,angler,anglephi,anglez) def _EccZmaxRperiRap(self,*args,**kwargs): """ NAME: _EccZmaxRperiRap PURPOSE: evaluate the eccentricity, maximum height above the plane, peri- and apocenter for an isochrone potential INPUT: Either: a) R,vR,vT,z,vz[,phi]: 1) floats: phase-space value for single object (phi is optional) (each can be a Quantity) 2) numpy.ndarray: [N] phase-space values for N objects (each can be a Quantity) b) Orbit instance: initial condition used if that's it, orbit(t) if there is a time given as well as the second argument OUTPUT: (e,zmax,rperi,rap) HISTORY: 2017-12-22 - Written - Bovy (UofT) """ if len(args) == 5: #R,vR.vT, z, vz pragma: no cover R,vR,vT, z, vz= args elif len(args) == 6: #R,vR.vT, z, vz, phi R,vR,vT, z, vz, phi= args else: self._parse_eval_args(*args) R= self._eval_R vR= self._eval_vR vT= self._eval_vT z= self._eval_z vz= self._eval_vz if isinstance(R,float): R= nu.array([R]) vR= nu.array([vR]) vT= nu.array([vT]) z= nu.array([z]) vz= nu.array([vz]) if self._c: #pragma: no cover pass else: Lz= R*vT Lx= -z*vT Ly= z*vR-R*vz L2= Lx*Lx+Ly*Ly+Lz*Lz E= self._ip(R,z)+vR**2./2.+vT**2./2.+vz**2./2. if self.b == 0: warnings.warn("zmax for point-mass (b=0) isochrone potential is only approximate, because it assumes that zmax is attained at rap, which is not necessarily the case",galpyWarning) a= -self.amp/2./E me2= L2/self.amp/a e= nu.sqrt(1.-me2) rperi= a*(1.-e) rap= a*(1.+e) else: smin= 0.5*((2.*E-self.amp/self.b)\ +nu.sqrt((2.*E-self.amp/self.b)**2. +2.*E*(4.*self.amp/self.b+L2/self.b**2.)))/E smax= 2.-self.amp/E/self.b-smin rperi= smin*nu.sqrt(1.-2./smin)*self.b rap= smax*nu.sqrt(1.-2./smax)*self.b return ((rap-rperi)/(rap+rperi),rap*nu.sqrt(1.-Lz**2./L2), rperi,rap) galpy-1.3.0/galpy/actionAngle_src/actionAngleIsochroneApprox.py0000644000076500000240000011062013231751437025010 0ustar bovystaff00000000000000############################################################################### # actionAngle: a Python module to calculate actions, angles, and frequencies # # class: actionAngleIsochroneApprox # # Calculate actions-angle coordinates for any potential by using # an isochrone potential as an approximate potential and using # a Fox & Binney (2013?) + torus machinery-like algorithm # (angle-fit) (Bovy 2014) # # methods: # __call__: returns (jr,lz,jz) # actionsFreqs: returns (jr,lz,jz,Or,Op,Oz) # actionsFreqsAngles: returns (jr,lz,jz,Or,Op,Oz,ar,ap,az) # ############################################################################### import math import warnings import numpy as nu import numpy.linalg as linalg from scipy import optimize from galpy.potential import dvcircdR, vcirc, _isNonAxi from galpy.actionAngle_src.actionAngleIsochrone import actionAngleIsochrone from galpy.actionAngle_src.actionAngle import actionAngle from galpy.potential import IsochronePotential, MWPotential from galpy.util import bovy_plot, galpyWarning from galpy.util.bovy_conversion import physical_conversion, \ potential_physical_input, time_in_Gyr _TWOPI= 2.*nu.pi _ANGLETOL= 0.02 #tolerance for deciding whether full angle range is covered _APY_LOADED= True try: from astropy import units except ImportError: _APY_LOADED= False class actionAngleIsochroneApprox(actionAngle): """Action-angle formalism using an isochrone potential as an approximate potential and using a Fox & Binney (2014?) like algorithm to calculate the actions using orbit integrations and a torus-machinery-like angle-fit to get the angles and frequencies (Bovy 2014)""" def __init__(self,*args,**kwargs): """ NAME: __init__ PURPOSE: initialize an actionAngleIsochroneApprox object INPUT: Either: b= scale parameter of the isochrone parameter (can be Quantity) ip= instance of a IsochronePotential aAI= instance of an actionAngleIsochrone pot= potential to calculate action-angle variables for tintJ= (default: 100) time to integrate orbits for to estimate actions (can be Quantity) ntintJ= (default: 10000) number of time-integration points integrate_method= (default: 'dopr54_c') integration method to use dt= (None) orbit.integrate dt keyword (for fixed stepsize integration) maxn= (default: 3) Default value for all methods when using a grid in vec(n) up to this n (zero-based) ro= distance from vantage point to GC (kpc; can be Quantity) vo= circular velocity at ro (km/s; can be Quantity) OUTPUT: instance HISTORY: 2013-09-10 - Written - Bovy (IAS) """ actionAngle.__init__(self, ro=kwargs.get('ro',None),vo=kwargs.get('vo',None)) if not 'pot' in kwargs: #pragma: no cover raise IOError("Must specify pot= for actionAngleIsochroneApprox") self._pot= kwargs['pot'] if self._pot == MWPotential: warnings.warn("Use of MWPotential as a Milky-Way-like potential is deprecated; galpy.potential.MWPotential2014, a potential fit to a large variety of dynamical constraints (see Bovy 2015), is the preferred Milky-Way-like potential in galpy", galpyWarning) if not 'b' in kwargs and not 'ip' in kwargs \ and not 'aAI' in kwargs: #pragma: no cover raise IOError("Must specify b=, ip=, or aAI= for actionAngleIsochroneApprox") if 'aAI' in kwargs: if not isinstance(kwargs['aAI'],actionAngleIsochrone): #pragma: no cover raise IOError("'Provided aAI= does not appear to be an instance of an actionAngleIsochrone") self._aAI= kwargs['aAI'] elif 'ip' in kwargs: ip= kwargs['ip'] if not isinstance(ip,IsochronePotential): #pragma: no cover raise IOError("'Provided ip= does not appear to be an instance of an IsochronePotential") self._aAI= actionAngleIsochrone(ip=ip) else: if _APY_LOADED and isinstance(kwargs['b'],units.Quantity): b= kwargs['b'].to(units.kpc).value/self._ro else: b= kwargs['b'] self._aAI= actionAngleIsochrone(ip=IsochronePotential(b=b, normalize=1.)) self._tintJ= kwargs.get('tintJ',100.) if _APY_LOADED and isinstance(self._tintJ,units.Quantity): self._tintJ= self._tintJ.to(units.Gyr).value\ /time_in_Gyr(self._vo,self._ro) self._ntintJ= kwargs.get('ntintJ',10000) self._integrate_dt= kwargs.get('dt',None) self._tsJ= nu.linspace(0.,self._tintJ,self._ntintJ) self._integrate_method= kwargs.get('integrate_method','dopr54_c') self._maxn= kwargs.get('maxn',3) self._c= False ext_loaded= False if ext_loaded and (('c' in kwargs and kwargs['c']) or not 'c' in kwargs): #pragma: no cover self._c= True else: self._c= False # Check the units self._check_consistent_units() return None def _evaluate(self,*args,**kwargs): """ NAME: __call__ (_evaluate) PURPOSE: evaluate the actions (jr,lz,jz) INPUT: Either: a) R,vR,vT,z,vz[,phi]: 1) floats: phase-space value for single object (phi is optional) (each can be a Quantity) 2) numpy.ndarray: [N] phase-space values for N objects (each can be a Quantity) b) Orbit instance: initial condition used if that's it, orbit(t) if there is a time given as well as the second argument cumul= if True, return the cumulative average actions (to look at convergence) OUTPUT: (jr,lz,jz) HISTORY: 2013-09-10 - Written - Bovy (IAS) """ R,vR,vT,z,vz,phi= self._parse_args(False,False,*args) if self._c: #pragma: no cover pass else: #Use self._aAI to calculate the actions and angles in the isochrone potential acfs= self._aAI._actionsFreqsAngles(R.flatten(), vR.flatten(), vT.flatten(), z.flatten(), vz.flatten(), phi.flatten()) jrI= nu.reshape(acfs[0],R.shape)[:,:-1] jzI= nu.reshape(acfs[2],R.shape)[:,:-1] anglerI= nu.reshape(acfs[6],R.shape) anglezI= nu.reshape(acfs[8],R.shape) if nu.any((nu.fabs(nu.amax(anglerI,axis=1)-_TWOPI) > _ANGLETOL)\ *(nu.fabs(nu.amin(anglerI,axis=1)) > _ANGLETOL)): #pragma: no cover warnings.warn("Full radial angle range not covered for at least one object; actions are likely not reliable",galpyWarning) if nu.any((nu.fabs(nu.amax(anglezI,axis=1)-_TWOPI) > _ANGLETOL)\ *(nu.fabs(nu.amin(anglezI,axis=1)) > _ANGLETOL)): #pragma: no cover warnings.warn("Full vertical angle range not covered for at least one object; actions are likely not reliable",galpyWarning) danglerI= ((nu.roll(anglerI,-1,axis=1)-anglerI) % _TWOPI)[:,:-1] danglezI= ((nu.roll(anglezI,-1,axis=1)-anglezI) % _TWOPI)[:,:-1] if kwargs.get('cumul',False): sumFunc= nu.cumsum else: sumFunc= nu.sum jr= sumFunc(jrI*danglerI,axis=1)/sumFunc(danglerI,axis=1) jz= sumFunc(jzI*danglezI,axis=1)/sumFunc(danglezI,axis=1) if _isNonAxi(self._pot): lzI= nu.reshape(acfs[1],R.shape)[:,:-1] anglephiI= nu.reshape(acfs[7],R.shape) danglephiI= ((nu.roll(anglephiI,-1,axis=1)-anglephiI) % _TWOPI)[:,:-1] if nu.any((nu.fabs(nu.amax(anglephiI,axis=1)-_TWOPI) > _ANGLETOL)\ *(nu.fabs(nu.amin(anglephiI,axis=1)) > _ANGLETOL)): #pragma: no cover warnings.warn("Full azimuthal angle range not covered for at least one object; actions are likely not reliable",galpyWarning) lz= sumFunc(lzI*danglephiI,axis=1)/sumFunc(danglephiI,axis=1) else: lz= R[:,0]*vT[:,0] return (jr,lz,jz) def _actionsFreqs(self,*args,**kwargs): """ NAME: actionsFreqs (_actionsFreqs) PURPOSE: evaluate the actions and frequencies (jr,lz,jz,Omegar,Omegaphi,Omegaz) INPUT: Either: a) R,vR,vT,z,vz[,phi]: 1) floats: phase-space value for single object (phi is optional) (each can be a Quantity) 2) numpy.ndarray: [N] phase-space values for N objects (each can be a Quantity) b) Orbit instance: initial condition used if that's it, orbit(t) if there is a time given as well as the second argument maxn= (default: object-wide default) Use a grid in vec(n) up to this n (zero-based) ts= if set, the phase-space points correspond to these times (IF NOT SET, WE ASSUME THAT ts IS THAT THAT IS ASSOCIATED WITH THIS OBJECT) _firstFlip= (False) if True and Orbits are given, the backward part of the orbit is integrated first and stored in the Orbit object OUTPUT: (jr,lz,jz,Omegar,Omegaphi,Omegaz) HISTORY: 2013-09-10 - Written - Bovy (IAS) """ acfs= self._actionsFreqsAngles(*args,**kwargs) return (acfs[0],acfs[1],acfs[2],acfs[3],acfs[4],acfs[5]) def _actionsFreqsAngles(self,*args,**kwargs): """ NAME: actionsFreqsAngles (_actionsFreqsAngles) PURPOSE: evaluate the actions, frequencies, and angles (jr,lz,jz,Omegar,Omegaphi,Omegaz,angler,anglephi,anglez) INPUT: Either: a) R,vR,vT,z,vz[,phi]: 1) floats: phase-space value for single object (phi is optional) (each can be a Quantity) 2) numpy.ndarray: [N] phase-space values for N objects (each can be a Quantity) b) Orbit instance: initial condition used if that's it, orbit(t) if there is a time given as well as the second argument maxn= (default: object-wide default) Use a grid in vec(n) up to this n (zero-based) ts= if set, the phase-space points correspond to these times (IF NOT SET, WE ASSUME THAT ts IS THAT THAT IS ASSOCIATED WITH THIS OBJECT) _firstFlip= (False) if True and Orbits are given, the backward part of the orbit is integrated first and stored in the Orbit object OUTPUT: (jr,lz,jz,Omegar,Omegaphi,Omegaz,angler,anglephi,anglez) HISTORY: 2013-09-10 - Written - Bovy (IAS) """ from galpy.orbit import Orbit _firstFlip= kwargs.get('_firstFlip',False) #If the orbit was already integrated, set ts to the integration times if isinstance(args[0],Orbit) and hasattr(args[0]._orb,'orbit') \ and not 'ts' in kwargs: kwargs['ts']= args[0]._orb.t elif (isinstance(args[0],list) and isinstance(args[0][0],Orbit)) \ and hasattr(args[0][0]._orb,'orbit') \ and not 'ts' in kwargs: kwargs['ts']= args[0][0]._orb.t R,vR,vT,z,vz,phi= self._parse_args(True,_firstFlip,*args) if 'ts' in kwargs and not kwargs['ts'] is None: ts= kwargs['ts'] if _APY_LOADED and isinstance(ts,units.Quantity): ts= ts.to(units.Gyr).value\ /time_in_Gyr(self._vo,self._ro) else: ts= nu.empty(R.shape[1]) ts[self._ntintJ-1:]= self._tsJ ts[:self._ntintJ-1]= -self._tsJ[1:][::-1] maxn= kwargs.get('maxn',self._maxn) if self._c: #pragma: no cover pass else: #Use self._aAI to calculate the actions and angles in the isochrone potential if '_acfs' in kwargs: acfs= kwargs['_acfs'] else: acfs= self._aAI._actionsFreqsAngles(R.flatten(), vR.flatten(), vT.flatten(), z.flatten(), vz.flatten(), phi.flatten()) jrI= nu.reshape(acfs[0],R.shape)[:,:-1] jzI= nu.reshape(acfs[2],R.shape)[:,:-1] anglerI= nu.reshape(acfs[6],R.shape) anglezI= nu.reshape(acfs[8],R.shape) if nu.any((nu.fabs(nu.amax(anglerI,axis=1)-_TWOPI) > _ANGLETOL)\ *(nu.fabs(nu.amin(anglerI,axis=1)) > _ANGLETOL)): #pragma: no cover warnings.warn("Full radial angle range not covered for at least one object; actions are likely not reliable",galpyWarning) if nu.any((nu.fabs(nu.amax(anglezI,axis=1)-_TWOPI) > _ANGLETOL)\ *(nu.fabs(nu.amin(anglezI,axis=1)) > _ANGLETOL)): #pragma: no cover warnings.warn("Full vertical angle range not covered for at least one object; actions are likely not reliable",galpyWarning) danglerI= ((nu.roll(anglerI,-1,axis=1)-anglerI) % _TWOPI)[:,:-1] danglezI= ((nu.roll(anglezI,-1,axis=1)-anglezI) % _TWOPI)[:,:-1] jr= nu.sum(jrI*danglerI,axis=1)/nu.sum(danglerI,axis=1) jz= nu.sum(jzI*danglezI,axis=1)/nu.sum(danglezI,axis=1) if _isNonAxi(self._pot): #pragma: no cover lzI= nu.reshape(acfs[1],R.shape)[:,:-1] anglephiI= nu.reshape(acfs[7],R.shape) if nu.any((nu.fabs(nu.amax(anglephiI,axis=1)-_TWOPI) > _ANGLETOL)\ *(nu.fabs(nu.amin(anglephiI,axis=1)) > _ANGLETOL)): #pragma: no cover warnings.warn("Full azimuthal angle range not covered for at least one object; actions are likely not reliable",galpyWarning) danglephiI= ((nu.roll(anglephiI,-1,axis=1)-anglephiI) % _TWOPI)[:,:-1] lz= nu.sum(lzI*danglephiI,axis=1)/nu.sum(danglephiI,axis=1) else: lz= R[:,len(ts)//2]*vT[:,len(ts)//2] #Now do an 'angle-fit' angleRT= dePeriod(nu.reshape(acfs[6],R.shape)) acfs7= nu.reshape(acfs[7],R.shape) negFreqIndx= nu.median(acfs7-nu.roll(acfs7,1,axis=1),axis=1) < 0. #anglephi is decreasing anglephiT= nu.empty(acfs7.shape) anglephiT[negFreqIndx,:]= dePeriod(_TWOPI-acfs7[negFreqIndx,:]) negFreqPhi= nu.zeros(R.shape[0],dtype='bool') negFreqPhi[negFreqIndx]= True anglephiT[True^negFreqIndx,:]= dePeriod(acfs7[True^negFreqIndx,:]) angleZT= dePeriod(nu.reshape(acfs[8],R.shape)) #Write the angle-fit as Y=AX, build A and Y nt= len(ts) no= R.shape[0] #remove 0,0,0 and half-plane if _isNonAxi(self._pot): nn= (2*maxn-1)**2*maxn-(maxn-1)*(2*maxn-1)-maxn else: nn= maxn*(2*maxn-1)-maxn A= nu.zeros((no,nt,2+nn)) A[:,:,0]= 1. A[:,:,1]= ts #sorting the phi and Z grids this way makes it easy to exclude the origin phig= list(nu.arange(-maxn+1,maxn,1)) phig.sort(key = lambda x: abs(x)) phig= nu.array(phig,dtype='int') if _isNonAxi(self._pot): grid= nu.meshgrid(nu.arange(maxn),phig,phig) else: grid= nu.meshgrid(nu.arange(maxn),phig) gridR= grid[0].T.flatten()[1:] #remove 0,0,0 gridZ= grid[1].T.flatten()[1:] mask = nu.ones(len(gridR),dtype=bool) # excludes axis that is not in half-space if _isNonAxi(self._pot): gridphi= grid[2].T.flatten()[1:] mask= True\ ^(gridR == 0)*((gridphi < 0)+((gridphi==0)*(gridZ < 0))) else: mask[:2*maxn-3:2]= False gridR= gridR[mask] gridZ= gridZ[mask] tangleR= nu.tile(angleRT.T,(nn,1,1)).T tgridR= nu.tile(gridR,(no,nt,1)) tangleZ= nu.tile(angleZT.T,(nn,1,1)).T tgridZ= nu.tile(gridZ,(no,nt,1)) if _isNonAxi(self._pot): gridphi= gridphi[mask] tgridphi= nu.tile(gridphi,(no,nt,1)) tanglephi= nu.tile(anglephiT.T,(nn,1,1)).T sinnR= nu.sin(tgridR*tangleR+tgridphi*tanglephi+tgridZ*tangleZ) else: sinnR= nu.sin(tgridR*tangleR+tgridZ*tangleZ) A[:,:,2:]= sinnR #Matrix magic atainv= nu.empty((no,2+nn,2+nn)) AT= nu.transpose(A,axes=(0,2,1)) for ii in range(no): atainv[ii,:,:,]= linalg.inv(nu.dot(AT[ii,:,:],A[ii,:,:])) ATAR= nu.sum(AT*nu.transpose(nu.tile(angleRT,(2+nn,1,1)),axes=(1,0,2)),axis=2) ATAT= nu.sum(AT*nu.transpose(nu.tile(anglephiT,(2+nn,1,1)),axes=(1,0,2)),axis=2) ATAZ= nu.sum(AT*nu.transpose(nu.tile(angleZT,(2+nn,1,1)),axes=(1,0,2)),axis=2) angleR= nu.sum(atainv[:,0,:]*ATAR,axis=1) OmegaR= nu.sum(atainv[:,1,:]*ATAR,axis=1) anglephi= nu.sum(atainv[:,0,:]*ATAT,axis=1) Omegaphi= nu.sum(atainv[:,1,:]*ATAT,axis=1) angleZ= nu.sum(atainv[:,0,:]*ATAZ,axis=1) OmegaZ= nu.sum(atainv[:,1,:]*ATAZ,axis=1) Omegaphi[negFreqIndx]= -Omegaphi[negFreqIndx] anglephi[negFreqIndx]= _TWOPI-anglephi[negFreqIndx] if kwargs.get('_retacfs',False): return (jr,lz,jz,OmegaR,Omegaphi,OmegaZ, #pragma: no cover angleR % _TWOPI, anglephi % _TWOPI, angleZ % _TWOPI,acfs) else: return (jr,lz,jz,OmegaR,Omegaphi,OmegaZ, angleR % _TWOPI, anglephi % _TWOPI, angleZ % _TWOPI) def plot(self,*args,**kwargs): """ NAME: plot PURPOSE: plot the angles vs. each other, to check whether the isochrone approximation is good INPUT: Either: a) R,vR,vT,z,vz: floats: phase-space value for single object b) Orbit instance type= ('araz') type of plot to make a) 'araz': az vs. ar, with color-coded aphi b) 'araphi': aphi vs. ar, with color-coded az c) 'azaphi': aphi vs. az, with color-coded ar d) 'jr': cumulative average of jr with time, to assess convergence e) 'lz': same as 'jr' but for lz f) 'jz': same as 'jr' but for jz deperiod= (False), if True, de-period the angles downsample= (False) if True, downsample what's plotted to 400 points +plot kwargs OUTPUT: plot to output HISTORY: 2013-09-10 - Written - Bovy (IAS) """ #Kwargs type= kwargs.pop('type','araz') deperiod= kwargs.pop('deperiod',False) downsample= kwargs.pop('downsample',False) #Parse input R,vR,vT,z,vz,phi= self._parse_args('a' in type,False,*args) #Use self._aAI to calculate the actions and angles in the isochrone potential acfs= self._aAI._actionsFreqsAngles(R.flatten(), vR.flatten(), vT.flatten(), z.flatten(), vz.flatten(), phi.flatten()) if type == 'jr' or type == 'lz' or type == 'jz': jrI= nu.reshape(acfs[0],R.shape)[:,:-1] jzI= nu.reshape(acfs[2],R.shape)[:,:-1] anglerI= nu.reshape(acfs[6],R.shape) anglezI= nu.reshape(acfs[8],R.shape) danglerI= ((nu.roll(anglerI,-1,axis=1)-anglerI) % _TWOPI)[:,:-1] danglezI= ((nu.roll(anglezI,-1,axis=1)-anglezI) % _TWOPI)[:,:-1] if True: sumFunc= nu.cumsum jr= sumFunc(jrI*danglerI,axis=1)/sumFunc(danglerI,axis=1) jz= sumFunc(jzI*danglezI,axis=1)/sumFunc(danglezI,axis=1) lzI= nu.reshape(acfs[1],R.shape)[:,:-1] anglephiI= nu.reshape(acfs[7],R.shape) danglephiI= ((nu.roll(anglephiI,-1,axis=1)-anglephiI) % _TWOPI)[:,:-1] lz= sumFunc(lzI*danglephiI,axis=1)/sumFunc(danglephiI,axis=1) from galpy.orbit import Orbit if isinstance(args[0],Orbit) and hasattr(args[0]._orb,'t'): ts= args[0]._orb.t[:-1] else: ts= self._tsJ[:-1] if type == 'jr': if downsample: plotx= ts[::int(round(self._ntintJ//400))] ploty= jr[0,::int(round(self._ntintJ//400))]/jr[0,-1] plotz= anglerI[0,:-1:int(round(self._ntintJ//400))] else: plotx= ts ploty= jr[0,:]/jr[0,-1] plotz= anglerI[0,:-1] bovy_plot.bovy_plot(plotx,ploty, c=plotz, s=20., scatter=True, edgecolor='none', xlabel=r'$t$', ylabel=r'$J^A_R / \langle J^A_R \rangle$', clabel=r'$\theta^A_R$', vmin=0.,vmax=2.*nu.pi, crange=[0.,2.*nu.pi], colorbar=True, **kwargs) elif type == 'lz': if downsample: plotx= ts[::int(round(self._ntintJ//400))] ploty= lz[0,::int(round(self._ntintJ//400))]/lz[0,-1] plotz= anglephiI[0,:-1:int(round(self._ntintJ//400))] else: plotx= ts ploty= lz[0,:]/lz[0,-1] plotz= anglephiI[0,:-1] bovy_plot.bovy_plot(plotx,ploty,c=plotz,s=20., scatter=True, edgecolor='none', xlabel=r'$t$', ylabel=r'$L^A_Z / \langle L^A_Z \rangle$', clabel=r'$\theta^A_\phi$', vmin=0.,vmax=2.*nu.pi, crange=[0.,2.*nu.pi], colorbar=True, **kwargs) elif type == 'jz': if downsample: plotx= ts[::int(round(self._ntintJ//400))] ploty= jz[0,::int(round(self._ntintJ//400))]/jz[0,-1] plotz= anglezI[0,:-1:int(round(self._ntintJ//400))] else: plotx= ts ploty= jz[0,:]/jz[0,-1] plotz= anglezI[0,:-1] bovy_plot.bovy_plot(plotx,ploty,c=plotz,s=20., scatter=True, edgecolor='none', xlabel=r'$t$', ylabel=r'$J^A_Z / \langle J^A_Z \rangle$', clabel=r'$\theta^A_Z$', vmin=0.,vmax=2.*nu.pi, crange=[0.,2.*nu.pi], colorbar=True, **kwargs) else: if deperiod: if 'ar' in type: angleRT= dePeriod(nu.reshape(acfs[6],R.shape)) else: angleRT= nu.reshape(acfs[6],R.shape) if 'aphi' in type: acfs7= nu.reshape(acfs[7],R.shape) negFreqIndx= nu.median(acfs7-nu.roll(acfs7,1,axis=1),axis=1) < 0. #anglephi is decreasing anglephiT= nu.empty(acfs7.shape) anglephiT[negFreqIndx,:]= dePeriod(_TWOPI-acfs7[negFreqIndx,:]) negFreqPhi= nu.zeros(R.shape[0],dtype='bool') negFreqPhi[negFreqIndx]= True anglephiT[True^negFreqIndx,:]= dePeriod(acfs7[True^negFreqIndx,:]) else: anglephiT= nu.reshape(acfs[7],R.shape) if 'az' in type: angleZT= dePeriod(nu.reshape(acfs[8],R.shape)) else: angleZT= nu.reshape(acfs[8],R.shape) xrange= None yrange= None else: angleRT= nu.reshape(acfs[6],R.shape) anglephiT= nu.reshape(acfs[7],R.shape) angleZT= nu.reshape(acfs[8],R.shape) xrange= [-0.5,2.*nu.pi+0.5] yrange= [-0.5,2.*nu.pi+0.5] vmin, vmax= 0.,2.*nu.pi crange= [vmin,vmax] if type == 'araz': if downsample: plotx= angleRT[0,::int(round(self._ntintJ//400))] ploty= angleZT[0,::int(round(self._ntintJ//400))] plotz= anglephiT[0,::int(round(self._ntintJ//400))] else: plotx= angleRT[0,:] ploty= angleZT[0,:] plotz= anglephiT[0,:] bovy_plot.bovy_plot(plotx,ploty,c=plotz,s=20., scatter=True, edgecolor='none', xlabel=r'$\theta^A_R$', ylabel=r'$\theta^A_Z$', clabel=r'$\theta^A_\phi$', xrange=xrange,yrange=yrange, vmin=vmin,vmax=vmax, crange=crange, colorbar=True, **kwargs) elif type == 'araphi': if downsample: plotx= angleRT[0,::int(round(self._ntintJ//400))] ploty= anglephiT[0,::int(round(self._ntintJ//400))] plotz= angleZT[0,::int(round(self._ntintJ//400))] else: plotx= angleRT[0,:] ploty= anglephiT[0,:] plotz= angleZT[0,:] bovy_plot.bovy_plot(plotx,ploty,c=plotz,s=20., scatter=True, edgecolor='none', xlabel=r'$\theta^A_R$', clabel=r'$\theta^A_Z$', ylabel=r'$\theta^A_\phi$', xrange=xrange,yrange=yrange, vmin=vmin,vmax=vmax, crange=crange, colorbar=True, **kwargs) elif type == 'azaphi': if downsample: plotx= angleZT[0,::int(round(self._ntintJ//400))] ploty= anglephiT[0,::int(round(self._ntintJ//400))] plotz= angleRT[0,::int(round(self._ntintJ//400))] else: plotx= angleZT[0,:] ploty= anglephiT[0,:] plotz= angleRT[0,:] bovy_plot.bovy_plot(plotx,ploty,c=plotz,s=20., scatter=True, edgecolor='none', clabel=r'$\theta^A_R$', xlabel=r'$\theta^A_Z$', ylabel=r'$\theta^A_\phi$', xrange=xrange,yrange=yrange, vmin=vmin,vmax=vmax, crange=crange, colorbar=True, **kwargs) return None def _parse_args(self,freqsAngles=True,_firstFlip=False,*args): """Helper function to parse the arguments to the __call__ and actionsFreqsAngles functions""" from galpy.orbit import Orbit RasOrbit= False integrated= True #whether the orbit was already integrated when given if len(args) == 5 or len(args) == 3: #pragma: no cover raise IOError("Must specify phi for actionAngleIsochroneApprox") if len(args) == 6 or len(args) == 4: if len(args) == 6: R,vR,vT, z, vz, phi= args else: R,vR,vT, phi= args z, vz= 0., 0. if isinstance(R,float): os= [Orbit([R,vR,vT,z,vz,phi])] RasOrbit= True integrated= False elif len(R.shape) == 1: #not integrated yet os= [Orbit([R[ii],vR[ii],vT[ii],z[ii],vz[ii],phi[ii]]) for ii in range(R.shape[0])] RasOrbit= True integrated= False if isinstance(args[0],Orbit) \ or (isinstance(args[0],list) and isinstance(args[0][0],Orbit)) \ or RasOrbit: if RasOrbit: pass elif not isinstance(args[0],list): os= [args[0]] if len(os[0]._orb.vxvv) == 3 or len(os[0]._orb.vxvv) == 5: #pragma: no cover raise IOError("Must specify phi for actionAngleIsochroneApprox") else: os= args[0] if len(os[0]._orb.vxvv) == 3 or len(os[0]._orb.vxvv) == 5: #pragma: no cover raise IOError("Must specify phi for actionAngleIsochroneApprox") self._check_consistent_units_orbitInput(os[0]) if not hasattr(os[0]._orb,'orbit'): #not integrated yet if _firstFlip: for o in os: o._orb.vxvv[1]= -o._orb.vxvv[1] o._orb.vxvv[2]= -o._orb.vxvv[2] o._orb.vxvv[4]= -o._orb.vxvv[4] [o.integrate(self._tsJ,pot=self._pot, method=self._integrate_method, dt=self._integrate_dt) for o in os] if _firstFlip: for o in os: o._orb.vxvv[1]= -o._orb.vxvv[1] o._orb.vxvv[2]= -o._orb.vxvv[2] o._orb.vxvv[4]= -o._orb.vxvv[4] o._orb.orbit[:,1]= -o._orb.orbit[:,1] o._orb.orbit[:,2]= -o._orb.orbit[:,2] o._orb.orbit[:,4]= -o._orb.orbit[:,4] integrated= False ntJ= os[0].getOrbit().shape[0] no= len(os) R= nu.empty((no,ntJ)) vR= nu.empty((no,ntJ)) vT= nu.empty((no,ntJ)) z= nu.zeros((no,ntJ))+10.**-7. #To avoid numpy warnings for vz= nu.zeros((no,ntJ))+10.**-7. #planarOrbits phi= nu.empty((no,ntJ)) for ii in range(len(os)): this_orbit= os[ii].getOrbit() R[ii,:]= this_orbit[:,0] vR[ii,:]= this_orbit[:,1] vT[ii,:]= this_orbit[:,2] if this_orbit.shape[1] == 6: z[ii,:]= this_orbit[:,3] vz[ii,:]= this_orbit[:,4] phi[ii,:]= this_orbit[:,5] else: phi[ii,:]= this_orbit[:,3] if freqsAngles and not integrated: #also integrate backwards in time, such that the requested point is not at the edge no= R.shape[0] nt= R.shape[1] oR= nu.empty((no,2*nt-1)) ovR= nu.empty((no,2*nt-1)) ovT= nu.empty((no,2*nt-1)) oz= nu.zeros((no,2*nt-1))+10.**-7. #To avoid numpy warnings for ovz= nu.zeros((no,2*nt-1))+10.**-7. #planarOrbits ophi= nu.empty((no,2*nt-1)) if _firstFlip: oR[:,:nt]= R[:,::-1] ovR[:,:nt]= vR[:,::-1] ovT[:,:nt]= vT[:,::-1] oz[:,:nt]= z[:,::-1] ovz[:,:nt]= vz[:,::-1] ophi[:,:nt]= phi[:,::-1] else: oR[:,nt-1:]= R ovR[:,nt-1:]= vR ovT[:,nt-1:]= vT oz[:,nt-1:]= z ovz[:,nt-1:]= vz ophi[:,nt-1:]= phi #load orbits if _firstFlip: os= [Orbit([R[ii,0],vR[ii,0],vT[ii,0],z[ii,0],vz[ii,0],phi[ii,0]]) for ii in range(R.shape[0])] else: os= [Orbit([R[ii,0],-vR[ii,0],-vT[ii,0],z[ii,0],-vz[ii,0],phi[ii,0]]) for ii in range(R.shape[0])] #integrate orbits [o.integrate(self._tsJ,pot=self._pot, method=self._integrate_method, dt=self._integrate_dt) for o in os] #extract phase-space points along the orbit ts= self._tsJ if _firstFlip: for ii in range(no): oR[ii,nt:]= os[ii].R(ts[1:]) #drop t=0, which we have ovR[ii,nt:]= os[ii].vR(ts[1:]) #already ovT[ii,nt:]= os[ii].vT(ts[1:]) # reverse, such that if os[ii].getOrbit().shape[1] == 6: oz[ii,nt:]= os[ii].z(ts[1:]) #everything is in the ovz[ii,nt:]= os[ii].vz(ts[1:]) #right order ophi[ii,nt:]= os[ii].phi(ts[1:]) #! else: for ii in range(no): oR[ii,:nt-1]= os[ii].R(ts[1:])[::-1] #drop t=0, which we have ovR[ii,:nt-1]= -os[ii].vR(ts[1:])[::-1] #already ovT[ii,:nt-1]= -os[ii].vT(ts[1:])[::-1] # reverse, such that if os[ii].getOrbit().shape[1] == 6: oz[ii,:nt-1]= os[ii].z(ts[1:])[::-1] #everything is in the ovz[ii,:nt-1]= -os[ii].vz(ts[1:])[::-1] #right order ophi[ii,:nt-1]= os[ii].phi(ts[1:])[::-1] #! return (oR,ovR,ovT,oz,ovz,ophi) else: return (R,vR,vT,z,vz,phi) @potential_physical_input @physical_conversion('position',pop=True) def estimateBIsochrone(pot,R,z,phi=None): """ NAME: estimateBIsochrone PURPOSE: Estimate a good value for the scale of the isochrone potential by matching the slope of the rotation curve INPUT: pot- Potential instance or list thereof R,z - coordinates (if these are arrays, the median estimated delta is returned, i.e., if this is an orbit) phi= (None) azimuth to use for non-axisymmetric potentials (array if R and z are arrays) OUTPUT: b if 1 R,Z given bmin,bmedian,bmax if multiple R given HISTORY: 2013-09-12 - Written - Bovy (IAS) 2016-02-20 - Changed input order to allow physical conversions - Bovy (UofT) 2016-06-28 - Added phi= keyword for non-axisymmetric potential - Bovy (UofT) """ if pot is None: #pragma: no cover raise IOError("pot= needs to be set to a Potential instance or list thereof") if isinstance(R,nu.ndarray): if phi is None: phi= [None for r in R] bs= nu.array([estimateBIsochrone(pot,R[ii],z[ii],phi=phi[ii], use_physical=False) for ii in range(len(R))]) return nu.array([nu.amin(bs[True^nu.isnan(bs)]), nu.median(bs[True^nu.isnan(bs)]), nu.amax(bs[True^nu.isnan(bs)])]) else: r2= R**2.+z**2 r= math.sqrt(r2) dlvcdlr= dvcircdR(pot,r,phi=phi,use_physical=False)/vcirc(pot,r,phi=phi,use_physical=False)*r try: b= optimize.brentq(lambda x: dlvcdlr-(x/math.sqrt(r2+x**2.)-0.5*r2/(r2+x**2.)), 0.01,100.) except: #pragma: no cover b= nu.nan return b def dePeriod(arr): """make an array of periodic angles increase linearly""" diff= arr-nu.roll(arr,1,axis=1) w= diff < -6. addto= nu.cumsum(w.astype(int),axis=1) return arr+_TWOPI*addto galpy-1.3.0/galpy/actionAngle_src/actionAngleSpherical.py0000644000076500000240000005445313236350147023610 0ustar bovystaff00000000000000############################################################################### # actionAngle: a Python module to calculate actions, angles, and frequencies # # class: actionAngleSpherical # # methods: # __call__: returns (jr,lz,jz) # actionsFreqs: returns (jr,lz,jz,Or,Op,Oz) # actionsFreqsAngles: returns (jr,lz,jz,Or,Op,Oz,ar,ap,az) # ############################################################################### import copy import math as m import numpy as nu from scipy import integrate from galpy.potential import epifreq, omegac from galpy.potential_src.Potential import _evaluatePotentials from galpy.actionAngle_src.actionAngle import * from galpy.actionAngle_src.actionAngleAxi import actionAngleAxi, potentialAxi class actionAngleSpherical(actionAngle): """Action-angle formalism for spherical potentials""" def __init__(self,*args,**kwargs): """ NAME: __init__ PURPOSE: initialize an actionAngleSpherical object INPUT: pot= a Spherical potential ro= distance from vantage point to GC (kpc; can be Quantity) vo= circular velocity at ro (km/s; can be Quantity) OUTPUT: instance HISTORY: 2013-12-28 - Written - Bovy (IAS) """ actionAngle.__init__(self, ro=kwargs.get('ro',None),vo=kwargs.get('vo',None)) if not 'pot' in kwargs: #pragma: no cover raise IOError("Must specify pot= for actionAngleSpherical") self._pot= kwargs['pot'] #Also store a 'planar' (2D) version of the potential if isinstance(self._pot,list): self._2dpot= [p.toPlanar() for p in self._pot] else: self._2dpot= self._pot.toPlanar() #The following for if we ever implement this code in C self._c= False ext_loaded= False if ext_loaded and (('c' in kwargs and kwargs['c']) or not 'c' in kwargs): self._c= True #pragma: no cover else: self._c= False # Check the units self._check_consistent_units() return None def _evaluate(self,*args,**kwargs): """ NAME: __call__ (_evaluate) PURPOSE: evaluate the actions (jr,lz,jz) INPUT: Either: a) R,vR,vT,z,vz[,phi]: 1) floats: phase-space value for single object (phi is optional) (each can be a Quantity) 2) numpy.ndarray: [N] phase-space values for N objects (each can be a Quantity) b) Orbit instance: initial condition used if that's it, orbit(t) if there is a time given as well as the second argument fixed_quad= (False) if True, use n=10 fixed_quad integration scipy.integrate.quadrature or .fixed_quad keywords OUTPUT: (jr,lz,jz) HISTORY: 2013-12-28 - Written - Bovy (IAS) """ fixed_quad= kwargs.pop('fixed_quad',False) if len(args) == 5: #R,vR.vT, z, vz R,vR,vT, z, vz= args elif len(args) == 6: #R,vR.vT, z, vz, phi R,vR,vT, z, vz, phi= args else: self._parse_eval_args(*args) R= self._eval_R vR= self._eval_vR vT= self._eval_vT z= self._eval_z vz= self._eval_vz if isinstance(R,float): R= nu.array([R]) vR= nu.array([vR]) vT= nu.array([vT]) z= nu.array([z]) vz= nu.array([vz]) if self._c: #pragma: no cover pass else: Lz= R*vT Lx= -z*vT Ly= z*vR-R*vz L2= Lx*Lx+Ly*Ly+Lz*Lz E= _evaluatePotentials(self._pot,R,z)\ +vR**2./2.+vT**2./2.+vz**2./2. L= nu.sqrt(L2) #Actions Jphi= Lz Jz= L-nu.fabs(Lz) #Jr requires some more work #Set up an actionAngleAxi object for EL and rap/rperi calculations axiR= nu.sqrt(R**2.+z**2.) axivT= L/axiR axivR= (R*vR+z*vz)/axiR Jr= [] for ii in range(len(axiR)): axiaA= actionAngleAxi(axiR[ii],axivR[ii],axivT[ii], pot=self._2dpot) (rperi,rap)= axiaA.calcRapRperi() EL= axiaA.calcEL() E, L= EL Jr.append(self._calc_jr(rperi,rap,E,L,fixed_quad,**kwargs)) return (nu.array(Jr),Jphi,Jz) def _actionsFreqs(self,*args,**kwargs): """ NAME: actionsFreqs (_actionsFreqs) PURPOSE: evaluate the actions and frequencies (jr,lz,jz,Omegar,Omegaphi,Omegaz) INPUT: Either: a) R,vR,vT,z,vz[,phi]: 1) floats: phase-space value for single object (phi is optional) (each can be a Quantity) 2) numpy.ndarray: [N] phase-space values for N objects (each can be a Quantity) b) Orbit instance: initial condition used if that's it, orbit(t) if there is a time given as well as the second argument fixed_quad= (False) if True, use n=10 fixed_quad integration scipy.integrate.quadrature or .fixed_quad keywords OUTPUT: (jr,lz,jz,Omegar,Omegaphi,Omegaz) HISTORY: 2013-12-28 - Written - Bovy (IAS) """ fixed_quad= kwargs.pop('fixed_quad',False) if len(args) == 5: #R,vR.vT, z, vz R,vR,vT, z, vz= args elif len(args) == 6: #R,vR.vT, z, vz, phi R,vR,vT, z, vz, phi= args else: self._parse_eval_args(*args) R= self._eval_R vR= self._eval_vR vT= self._eval_vT z= self._eval_z vz= self._eval_vz if isinstance(R,float): R= nu.array([R]) vR= nu.array([vR]) vT= nu.array([vT]) z= nu.array([z]) vz= nu.array([vz]) if self._c: #pragma: no cover pass else: Lz= R*vT Lx= -z*vT Ly= z*vR-R*vz L2= Lx*Lx+Ly*Ly+Lz*Lz E= _evaluatePotentials(self._pot,R,z)+vR**2./2.+vT**2./2.+vz**2./2. L= nu.sqrt(L2) #Actions Jphi= Lz Jz= L-nu.fabs(Lz) #Jr requires some more work #Set up an actionAngleAxi object for EL and rap/rperi calculations axiR= nu.sqrt(R**2.+z**2.) axivT= L/axiR axivR= (R*vR+z*vz)/axiR Jr= [] Or= [] Op= [] for ii in range(len(axiR)): axiaA= actionAngleAxi(axiR[ii],axivR[ii],axivT[ii], pot=self._2dpot) (rperi,rap)= axiaA.calcRapRperi() EL= axiaA.calcEL() E, L= EL Jr.append(self._calc_jr(rperi,rap,E,L,fixed_quad,**kwargs)) #Radial period if Jr[-1] < 10.**-9.: #Circular orbit Or.append(epifreq(self._pot,axiR[ii],use_physical=False)) Op.append(omegac(self._pot,axiR[ii],use_physical=False)) continue Rmean= m.exp((m.log(rperi)+m.log(rap))/2.) Or.append(self._calc_or(Rmean,rperi,rap,E,L,fixed_quad,**kwargs)) Op.append(self._calc_op(Or[-1],Rmean,rperi,rap,E,L,fixed_quad,**kwargs)) Op= nu.array(Op) Oz= copy.copy(Op) Op[vT < 0.]*= -1. return (nu.array(Jr),Jphi,Jz,nu.array(Or),Op,Oz) def _actionsFreqsAngles(self,*args,**kwargs): """ NAME: actionsFreqsAngles (_actionsFreqsAngles) PURPOSE: evaluate the actions, frequencies, and angles (jr,lz,jz,Omegar,Omegaphi,Omegaz,ar,ap,az) INPUT: Either: a) R,vR,vT,z,vz[,phi]: 1) floats: phase-space value for single object (phi is optional) (each can be a Quantity) 2) numpy.ndarray: [N] phase-space values for N objects (each can be a Quantity) b) Orbit instance: initial condition used if that's it, orbit(t) if there is a time given as well as the second argument fixed_quad= (False) if True, use n=10 fixed_quad integration scipy.integrate.quadrature or .fixed_quad keywords OUTPUT: (jr,lz,jz,Omegar,Omegaphi,Omegaz,ar,aphi,az) HISTORY: 2013-12-29 - Written - Bovy (IAS) """ fixed_quad= kwargs.pop('fixed_quad',False) if len(args) == 5: #R,vR.vT, z, vz pragma: no cover raise IOError("You need to provide phi when calculating angles") elif len(args) == 6: #R,vR.vT, z, vz, phi R,vR,vT, z, vz, phi= args else: self._parse_eval_args(*args) R= self._eval_R vR= self._eval_vR vT= self._eval_vT z= self._eval_z vz= self._eval_vz phi= self._eval_phi if isinstance(R,float): R= nu.array([R]) vR= nu.array([vR]) vT= nu.array([vT]) z= nu.array([z]) vz= nu.array([vz]) phi= nu.array([phi]) if self._c: #pragma: no cover pass else: Lz= R*vT Lx= -z*vT Ly= z*vR-R*vz L2= Lx*Lx+Ly*Ly+Lz*Lz E= _evaluatePotentials(self._pot,R,z)+vR**2./2.+vT**2./2.+vz**2./2. L= nu.sqrt(L2) #Actions Jphi= Lz Jz= L-nu.fabs(Lz) #Jr requires some more work #Set up an actionAngleAxi object for EL and rap/rperi calculations axiR= nu.sqrt(R**2.+z**2.) axivT= L/axiR #these are really spherical coords, called axi bc they go in actionAngleAxi axivR= (R*vR+z*vz)/axiR axivz= (z*vR-R*vz)/axiR Jr= [] Or= [] Op= [] ar= [] az= [] #Calculate the longitude of the ascending node asc= self._calc_long_asc(z,R,axivz,phi,Lz,L) for ii in range(len(axiR)): axiaA= actionAngleAxi(axiR[ii],axivR[ii],axivT[ii], pot=self._2dpot) (rperi,rap)= axiaA.calcRapRperi() EL= axiaA.calcEL() E, L= EL Jr.append(self._calc_jr(rperi,rap,E,L,fixed_quad,**kwargs)) #Radial period Rmean= m.exp((m.log(rperi)+m.log(rap))/2.) if Jr[-1] < 10.**-9.: #Circular orbit Or.append(epifreq(self._pot,axiR[ii],use_physical=False)) Op.append(omegac(self._pot,axiR[ii],use_physical=False)) else: Or.append(self._calc_or(Rmean,rperi,rap,E,L,fixed_quad,**kwargs)) Op.append(self._calc_op(Or[-1],Rmean,rperi,rap,E,L,fixed_quad,**kwargs)) #Angles ar.append(self._calc_angler(Or[-1],axiR[ii],Rmean,rperi,rap, E,L,axivR[ii],fixed_quad,**kwargs)) az.append(self._calc_anglez(Or[-1],Op[-1],ar[-1], z[ii],axiR[ii], Rmean,rperi,rap,E,L,Lz[ii], axivR[ii],axivz[ii], fixed_quad,**kwargs)) Op= nu.array(Op) Oz= copy.copy(Op) Op[vT < 0.]*= -1. ap= copy.copy(asc) ar= nu.array(ar) az= nu.array(az) ap[vT < 0.]-= az[vT < 0.] ap[vT >= 0.]+= az[vT >= 0.] ar= ar % (2.*nu.pi) ap= ap % (2.*nu.pi) az= az % (2.*nu.pi) return (nu.array(Jr),Jphi,Jz,nu.array(Or),Op,Oz, ar,ap,az) def _EccZmaxRperiRap(self,*args,**kwargs): """ NAME: EccZmaxRperiRap (_EccZmaxRperiRap) PURPOSE: evaluate the eccentricity, maximum height above the plane, peri- and apocenter for a spherical potential INPUT: Either: a) R,vR,vT,z,vz[,phi]: 1) floats: phase-space value for single object (phi is optional) (each can be a Quantity) 2) numpy.ndarray: [N] phase-space values for N objects (each can be a Quantity) b) Orbit instance: initial condition used if that's it, orbit(t) if there is a time given as well as the second argument OUTPUT: (e,zmax,rperi,rap) HISTORY: 2017-12-22 - Written - Bovy (UofT) """ if len(args) == 5: #R,vR.vT, z, vz R,vR,vT, z, vz= args elif len(args) == 6: #R,vR.vT, z, vz, phi R,vR,vT, z, vz, phi= args else: self._parse_eval_args(*args) R= self._eval_R vR= self._eval_vR vT= self._eval_vT z= self._eval_z vz= self._eval_vz if isinstance(R,float): R= nu.array([R]) vR= nu.array([vR]) vT= nu.array([vT]) z= nu.array([z]) vz= nu.array([vz]) if self._c: #pragma: no cover pass else: Lz= R*vT Lx= -z*vT Ly= z*vR-R*vz L2= Lx*Lx+Ly*Ly+Lz*Lz L= nu.sqrt(L2) #Set up an actionAngleAxi object for EL and rap/rperi calculations axiR= nu.sqrt(R**2.+z**2.) axivT= L/axiR axivR= (R*vR+z*vz)/axiR rperi, rap= [], [] for ii in range(len(axiR)): axiaA= actionAngleAxi(axiR[ii],axivR[ii],axivT[ii], pot=self._2dpot) trperi,trap= axiaA.calcRapRperi() rperi.append(trperi) rap.append(trap) rperi= nu.array(rperi) rap= nu.array(rap) return ((rap-rperi)/(rap+rperi),rap*nu.sqrt(1.-Lz**2./L2), rperi,rap) def _calc_jr(self,rperi,rap,E,L,fixed_quad,**kwargs): if fixed_quad: return integrate.fixed_quad(_JrSphericalIntegrand, rperi,rap, args=(E,L,self._2dpot), n=10, **kwargs)[0]/nu.pi else: return (nu.array(integrate.quad(_JrSphericalIntegrand, rperi,rap, args=(E,L,self._2dpot), **kwargs)))[0]/nu.pi def _calc_or(self,Rmean,rperi,rap,E,L,fixed_quad,**kwargs): Tr= 0. if Rmean > rperi and not fixed_quad: Tr+= nu.array(integrate.quadrature(_TrSphericalIntegrandSmall, 0.,m.sqrt(Rmean-rperi), args=(E,L,self._2dpot, rperi), **kwargs))[0] elif Rmean > rperi and fixed_quad: Tr+= integrate.fixed_quad(_TrSphericalIntegrandSmall, 0.,m.sqrt(Rmean-rperi), args=(E,L,self._2dpot, rperi), n=10,**kwargs)[0] if Rmean < rap and not fixed_quad: Tr+= nu.array(integrate.quadrature(_TrSphericalIntegrandLarge, 0.,m.sqrt(rap-Rmean), args=(E,L,self._2dpot, rap), **kwargs))[0] elif Rmean < rap and fixed_quad: Tr+= integrate.fixed_quad(_TrSphericalIntegrandLarge, 0.,m.sqrt(rap-Rmean), args=(E,L,self._2dpot, rap), n=10,**kwargs)[0] Tr= 2.*Tr return 2.*nu.pi/Tr def _calc_op(self,Or,Rmean,rperi,rap,E,L,fixed_quad,**kwargs): #Azimuthal period I= 0. if Rmean > rperi and not fixed_quad: I+= nu.array(integrate.quadrature(_ISphericalIntegrandSmall, 0.,m.sqrt(Rmean-rperi), args=(E,L,self._2dpot, rperi), **kwargs))[0] elif Rmean > rperi and fixed_quad: I+= integrate.fixed_quad(_ISphericalIntegrandSmall, 0.,m.sqrt(Rmean-rperi), args=(E,L,self._2dpot,rperi), n=10,**kwargs)[0] if Rmean < rap and not fixed_quad: I+= nu.array(integrate.quadrature(_ISphericalIntegrandLarge, 0.,m.sqrt(rap-Rmean), args=(E,L,self._2dpot, rap), **kwargs))[0] elif Rmean < rap and fixed_quad: I+= integrate.fixed_quad(_ISphericalIntegrandLarge, 0.,m.sqrt(rap-Rmean), args=(E,L,self._2dpot,rap), n=10,**kwargs)[0] I*= 2*L return I*Or/2./nu.pi def _calc_long_asc(self,z,R,axivz,phi,Lz,L): i= nu.arccos(Lz/L) sinu= z/R/nu.tan(i) pindx= (sinu > 1.)*(sinu < (1.+10.**-7.)) sinu[pindx]= 1. pindx= (sinu < -1.)*(sinu > (-1.-10.**-7.)) sinu[pindx]= -1. u= nu.arcsin(sinu) vzindx= axivz > 0. u[vzindx]= nu.pi-u[vzindx] return phi-u def _calc_angler(self,Or,r,Rmean,rperi,rap,E,L,vr,fixed_quad,**kwargs): if r < Rmean: if r > rperi and not fixed_quad: wr= Or*integrate.quadrature(_TrSphericalIntegrandSmall, 0.,m.sqrt(r-rperi), args=(E,L,self._2dpot,rperi), **kwargs)[0] elif r > rperi and fixed_quad: wr= Or*integrate.fixed_quad(_TrSphericalIntegrandSmall, 0.,m.sqrt(r-rperi), args=(E,L,self._2dpot,rperi), n=10,**kwargs)[0] else: wr= 0. if vr < 0.: wr= 2*m.pi-wr else: if r < rap and not fixed_quad: wr= Or*integrate.quadrature(_TrSphericalIntegrandLarge, 0.,m.sqrt(rap-r), args=(E,L,self._2dpot,rap), **kwargs)[0] elif r < rap and fixed_quad: wr= Or*integrate.fixed_quad(_TrSphericalIntegrandLarge, 0.,m.sqrt(rap-r), args=(E,L,self._2dpot,rap), n=10,**kwargs)[0] else: wr= m.pi if vr < 0.: wr= m.pi+wr else: wr= m.pi-wr return wr def _calc_anglez(self,Or,Op,ar,z,r,Rmean,rperi,rap,E,L,Lz,vr,axivz, fixed_quad,**kwargs): #First calculate psi i= nu.arccos(Lz/L) sinpsi= z/r/nu.sin(i) if sinpsi > 1. and sinpsi < (1.+10.**-7.): sinpsi= 1. if sinpsi < -1. and sinpsi > (-1.-10.**-7.): sinpsi= -1. psi= nu.arcsin(sinpsi) if axivz > 0.: psi= nu.pi-psi psi= psi % (2.*nu.pi) #Calculate dSr/dL dpsi= Op/Or*2.*nu.pi #this is the full I integral if r < Rmean: if not fixed_quad: wz= L*integrate.quadrature(_ISphericalIntegrandSmall, 0.,m.sqrt(r-rperi), args=(E,L,self._2dpot, rperi), **kwargs)[0] elif fixed_quad: wz= L*integrate.fixed_quad(_ISphericalIntegrandSmall, 0.,m.sqrt(r-rperi), args=(E,L,self._2dpot, rperi), n=10,**kwargs)[0] if vr < 0.: wz= dpsi-wz else: if not fixed_quad: wz= L*integrate.quadrature(_ISphericalIntegrandLarge, 0.,m.sqrt(rap-r), args=(E,L,self._2dpot, rap), **kwargs)[0] elif fixed_quad: wz= L*integrate.fixed_quad(_ISphericalIntegrandLarge, 0.,m.sqrt(rap-r), args=(E,L,self._2dpot, rap), n=10,**kwargs)[0] if vr < 0.: wz= dpsi/2.+wz else: wz= dpsi/2.-wz #Add everything wz= -wz+psi+Op/Or*ar return wz def _JrSphericalIntegrand(r,E,L,pot): """The J_r integrand""" return nu.sqrt(2.*(E-potentialAxi(r,pot))-L**2./r**2.) def _TrSphericalIntegrandSmall(t,E,L,pot,rperi): r= rperi+t**2.#part of the transformation return 2.*t/_JrSphericalIntegrand(r,E,L,pot) def _TrSphericalIntegrandLarge(t,E,L,pot,rap): r= rap-t**2.#part of the transformation return 2.*t/_JrSphericalIntegrand(r,E,L,pot) def _ISphericalIntegrandSmall(t,E,L,pot,rperi): r= rperi+t**2.#part of the transformation return 2.*t/_JrSphericalIntegrand(r,E,L,pot)/r**2. def _ISphericalIntegrandLarge(t,E,L,pot,rap): r= rap-t**2.#part of the transformation return 2.*t/_JrSphericalIntegrand(r,E,L,pot)/r**2. galpy-1.3.0/galpy/actionAngle_src/actionAngleStaeckel.py0000644000076500000240000013561113231751437023427 0ustar bovystaff00000000000000############################################################################### # actionAngle: a Python module to calculate actions, angles, and frequencies # # class: actionAngleStaeckel # # Use Binney (2012; MNRAS 426, 1324)'s Staeckel approximation for # calculating the actions # # methods: # __call__: returns (jr,lz,jz) # ############################################################################### import copy import warnings import numpy as nu from scipy import optimize, integrate from galpy.potential import evaluateR2derivs, evaluatez2derivs, \ evaluateRzderivs, epifreq, omegac, verticalfreq, MWPotential from galpy.potential_src.Potential import _evaluatePotentials, \ _evaluateRforces, _evaluatezforces from galpy.util import bovy_coords #for prolate confocal transforms from galpy.util import galpyWarning from galpy.util.bovy_conversion import physical_conversion, \ potential_physical_input, physical_conversion_actionAngle, \ actionAngle_physical_input from galpy.actionAngle_src.actionAngle import actionAngle, UnboundError import galpy.actionAngle_src.actionAngleStaeckel_c as actionAngleStaeckel_c from galpy.actionAngle_src.actionAngleStaeckel_c import _ext_loaded as ext_loaded from galpy.potential_src.Potential import _check_c _APY_LOADED= True try: from astropy import units except ImportError: _APY_LOADED= False class actionAngleStaeckel(actionAngle): """Action-angle formalism for axisymmetric potentials using Binney (2012)'s Staeckel approximation""" def __init__(self,*args,**kwargs): """ NAME: __init__ PURPOSE: initialize an actionAngleStaeckel object INPUT: pot= potential or list of potentials (3D) delta= focus (can be Quantity) useu0 - use u0 to calculate dV (NOT recommended) c= if True, always use C for calculations order= (10) number of points to use in the Gauss-Legendre numerical integration of the relevant action, frequency, and angle integrals ro= distance from vantage point to GC (kpc; can be Quantity) vo= circular velocity at ro (km/s; can be Quantity) OUTPUT: instance HISTORY: 2012-11-27 - Written - Bovy (IAS) """ actionAngle.__init__(self, ro=kwargs.get('ro',None),vo=kwargs.get('vo',None)) if not 'pot' in kwargs: #pragma: no cover raise IOError("Must specify pot= for actionAngleStaeckel") self._pot= kwargs['pot'] if self._pot == MWPotential: warnings.warn("Use of MWPotential as a Milky-Way-like potential is deprecated; galpy.potential.MWPotential2014, a potential fit to a large variety of dynamical constraints (see Bovy 2015), is the preferred Milky-Way-like potential in galpy", galpyWarning) if not 'delta' in kwargs: #pragma: no cover raise IOError("Must specify delta= for actionAngleStaeckel") if ext_loaded and (('c' in kwargs and kwargs['c']) or not 'c' in kwargs): self._c= _check_c(self._pot) if 'c' in kwargs and kwargs['c'] and not self._c: warnings.warn("C module not used because potential does not have a C implementation",galpyWarning) #pragma: no cover else: self._c= False self._useu0= kwargs.get('useu0',False) self._delta= kwargs['delta'] self._order= kwargs.get('order',10) if _APY_LOADED and isinstance(self._delta,units.Quantity): self._delta= self._delta.to(units.kpc).value/self._ro # Check the units self._check_consistent_units() return None def _evaluate(self,*args,**kwargs): """ NAME: __call__ (_evaluate) PURPOSE: evaluate the actions (jr,lz,jz) INPUT: Either: a) R,vR,vT,z,vz[,phi]: 1) floats: phase-space value for single object (phi is optional) (each can be a Quantity) 2) numpy.ndarray: [N] phase-space values for N objects (each can be a Quantity) b) Orbit instance: initial condition used if that's it, orbit(t) if there is a time given as well as the second argument delta= (object-wide default) can be used to override the object-wide focal length; can also be an array with length N to allow different delta for different phase-space points u0= (None) if object-wide option useu0 is set, u0 to use (if useu0 and useu0 is None, a good value will be computed) c= (object-wide default, bool) True/False to override the object-wide setting for whether or not to use the C implementation order= (object-wide default, int) number of points to use in the Gauss-Legendre numerical integration of the relevant action integrals When not using C: fixed_quad= (False) if True, use Gaussian quadrature (scipy.integrate.fixed_quad instead of scipy.integrate.quad) scipy.integrate.fixed_quad or .quad keywords OUTPUT: (jr,lz,jz) HISTORY: 2012-11-27 - Written - Bovy (IAS) 2017-12-27 - Allowed individual delta for each point - Bovy (UofT) """ delta= kwargs.pop('delta',self._delta) order= kwargs.get('order',self._order) if ((self._c and not ('c' in kwargs and not kwargs['c']))\ or (ext_loaded and (('c' in kwargs and kwargs['c'])))) \ and _check_c(self._pot): if len(args) == 5: #R,vR.vT, z, vz R,vR,vT, z, vz= args elif len(args) == 6: #R,vR.vT, z, vz, phi R,vR,vT, z, vz, phi= args else: self._parse_eval_args(*args) R= self._eval_R vR= self._eval_vR vT= self._eval_vT z= self._eval_z vz= self._eval_vz if isinstance(R,float): R= nu.array([R]) vR= nu.array([vR]) vT= nu.array([vT]) z= nu.array([z]) vz= nu.array([vz]) Lz= R*vT if self._useu0: #First calculate u0 if 'u0' in kwargs: u0= nu.asarray(kwargs['u0']) else: E= nu.array([_evaluatePotentials(self._pot,R[ii],z[ii]) +vR[ii]**2./2.+vz[ii]**2./2.+vT[ii]**2./2. for ii in range(len(R))]) u0= actionAngleStaeckel_c.actionAngleStaeckel_calcu0(\ E,Lz,self._pot,delta)[0] kwargs.pop('u0',None) else: u0= None jr, jz, err= actionAngleStaeckel_c.actionAngleStaeckel_c(\ self._pot,delta,R,vR,vT,z,vz,u0=u0,order=order) if err == 0: return (jr,Lz,jz) else: #pragma: no cover raise RuntimeError("C-code for calculation actions failed; try with c=False") else: if 'c' in kwargs and kwargs['c'] and not self._c: #pragma: no cover warnings.warn("C module not used because potential does not have a C implementation",galpyWarning) kwargs.pop('c',None) if (len(args) == 5 or len(args) == 6) \ and isinstance(args[0],nu.ndarray): ojr= nu.zeros((len(args[0]))) olz= nu.zeros((len(args[0]))) ojz= nu.zeros((len(args[0]))) for ii in range(len(args[0])): if len(args) == 5: targs= (args[0][ii],args[1][ii],args[2][ii], args[3][ii],args[4][ii]) elif len(args) == 6: targs= (args[0][ii],args[1][ii],args[2][ii], args[3][ii],args[4][ii],args[5][ii]) tkwargs= copy.copy(kwargs) try: tkwargs['delta']= delta[ii] except TypeError: tkwargs['delta']= delta tjr,tlz,tjz= self(*targs,**tkwargs) ojr[ii]= tjr ojz[ii]= tjz olz[ii]= tlz return (ojr,olz,ojz) else: #Set up the actionAngleStaeckelSingle object aASingle= actionAngleStaeckelSingle(*args,pot=self._pot, delta=delta) return (aASingle.JR(**copy.copy(kwargs)), aASingle._R*aASingle._vT, aASingle.Jz(**copy.copy(kwargs))) def _actionsFreqs(self,*args,**kwargs): """ NAME: actionsFreqs (_actionsFreqs) PURPOSE: evaluate the actions and frequencies (jr,lz,jz,Omegar,Omegaphi,Omegaz) INPUT: Either: a) R,vR,vT,z,vz[,phi]: 1) floats: phase-space value for single object (phi is optional) (each can be a Quantity) 2) numpy.ndarray: [N] phase-space values for N objects (each can be a Quantity) b) Orbit instance: initial condition used if that's it, orbit(t) if there is a time given as well as the second argument delta= (object-wide default) can be used to override the object-wide focal length; can also be an array with length N to allow different delta for different phase-space points u0= (None) if object-wide option useu0 is set, u0 to use (if useu0 and useu0 is None, a good value will be computed) c= (object-wide default, bool) True/False to override the object-wide setting for whether or not to use the C implementation order= (10) number of points to use in the Gauss-Legendre numerical integration of the relevant action and frequency integrals When not using C: fixed_quad= (False) if True, use Gaussian quadrature (scipy.integrate.fixed_quad instead of scipy.integrate.quad) scipy.integrate.fixed_quad or .quad keywords OUTPUT: (jr,lz,jz,Omegar,Omegaphi,Omegaz) HISTORY: 2013-08-28 - Written - Bovy (IAS) """ delta= kwargs.pop('delta',self._delta) order= kwargs.get('order',self._order) if ((self._c and not ('c' in kwargs and not kwargs['c']))\ or (ext_loaded and (('c' in kwargs and kwargs['c'])))) \ and _check_c(self._pot): if len(args) == 5: #R,vR.vT, z, vz R,vR,vT, z, vz= args elif len(args) == 6: #R,vR.vT, z, vz, phi R,vR,vT, z, vz, phi= args else: self._parse_eval_args(*args) R= self._eval_R vR= self._eval_vR vT= self._eval_vT z= self._eval_z vz= self._eval_vz if isinstance(R,float): R= nu.array([R]) vR= nu.array([vR]) vT= nu.array([vT]) z= nu.array([z]) vz= nu.array([vz]) Lz= R*vT if self._useu0: #First calculate u0 if 'u0' in kwargs: u0= nu.asarray(kwargs['u0']) else: E= nu.array([_evaluatePotentials(self._pot,R[ii],z[ii]) +vR[ii]**2./2.+vz[ii]**2./2.+vT[ii]**2./2. for ii in range(len(R))]) u0= actionAngleStaeckel_c.actionAngleStaeckel_calcu0(\ E,Lz,self._pot,delta)[0] kwargs.pop('u0',None) else: u0= None jr, jz, Omegar, Omegaphi, Omegaz, err= actionAngleStaeckel_c.actionAngleFreqStaeckel_c(\ self._pot,delta,R,vR,vT,z,vz,u0=u0,order=order) # Adjustements for close-to-circular orbits indx= nu.isnan(Omegar)*(jr < 10.**-3.)+nu.isnan(Omegaz)*(jz < 10.**-3.) #Close-to-circular and close-to-the-plane orbits if nu.sum(indx) > 0: Omegar[indx]= [epifreq(self._pot,r,use_physical=False) for r in R[indx]] Omegaphi[indx]= [omegac(self._pot,r,use_physical=False) for r in R[indx]] Omegaz[indx]= [verticalfreq(self._pot,r,use_physical=False) for r in R[indx]] if err == 0: return (jr,Lz,jz,Omegar,Omegaphi,Omegaz) else: #pragma: no cover raise RuntimeError("C-code for calculation actions failed; try with c=False") else: if 'c' in kwargs and kwargs['c'] and not self._c: #pragma: no cover warnings.warn("C module not used because potential does not have a C implementation",galpyWarning) raise NotImplementedError("actionsFreqs with c=False not implemented") def _actionsFreqsAngles(self,*args,**kwargs): """ NAME: actionsFreqsAngles (_actionsFreqsAngles) PURPOSE: evaluate the actions, frequencies, and angles (jr,lz,jz,Omegar,Omegaphi,Omegaz,angler,anglephi,anglez) INPUT: Either: a) R,vR,vT,z,vz[,phi]: 1) floats: phase-space value for single object (phi is optional) (each can be a Quantity) 2) numpy.ndarray: [N] phase-space values for N objects (each can be a Quantity) b) Orbit instance: initial condition used if that's it, orbit(t) if there is a time given as well as the second argument delta= (object-wide default) can be used to override the object-wide focal length; can also be an array with length N to allow different delta for different phase-space points u0= (None) if object-wide option useu0 is set, u0 to use (if useu0 and useu0 is None, a good value will be computed) c= (object-wide default, bool) True/False to override the object-wide setting for whether or not to use the C implementation order= (10) number of points to use in the Gauss-Legendre numerical integration of the relevant action, frequency, and angle integrals When not using C: fixed_quad= (False) if True, use Gaussian quadrature (scipy.integrate.fixed_quad instead of scipy.integrate.quad) scipy.integrate.fixed_quad or .quad keywords OUTPUT: (jr,lz,jz,Omegar,Omegaphi,Omegaz,angler,anglephi,anglez) HISTORY: 2013-08-28 - Written - Bovy (IAS) """ delta= kwargs.pop('delta',self._delta) order= kwargs.get('order',self._order) if ((self._c and not ('c' in kwargs and not kwargs['c']))\ or (ext_loaded and (('c' in kwargs and kwargs['c'])))) \ and _check_c(self._pot): if len(args) == 5: #R,vR.vT, z, vz pragma: no cover raise IOError("Must specify phi") elif len(args) == 6: #R,vR.vT, z, vz, phi R,vR,vT, z, vz, phi= args else: self._parse_eval_args(*args) R= self._eval_R vR= self._eval_vR vT= self._eval_vT z= self._eval_z vz= self._eval_vz phi= self._eval_phi if isinstance(R,float): R= nu.array([R]) vR= nu.array([vR]) vT= nu.array([vT]) z= nu.array([z]) vz= nu.array([vz]) phi= nu.array([phi]) Lz= R*vT if self._useu0: #First calculate u0 if 'u0' in kwargs: u0= nu.asarray(kwargs['u0']) else: E= nu.array([_evaluatePotentials(self._pot,R[ii],z[ii]) +vR[ii]**2./2.+vz[ii]**2./2.+vT[ii]**2./2. for ii in range(len(R))]) u0= actionAngleStaeckel_c.actionAngleStaeckel_calcu0(\ E,Lz,self._pot,delta)[0] kwargs.pop('u0',None) else: u0= None jr, jz, Omegar, Omegaphi, Omegaz, angler, anglephi,anglez, err= actionAngleStaeckel_c.actionAngleFreqAngleStaeckel_c(\ self._pot,delta,R,vR,vT,z,vz,phi,u0=u0,order=order) # Adjustements for close-to-circular orbits indx= nu.isnan(Omegar)*(jr < 10.**-3.)+nu.isnan(Omegaz)*(jz < 10.**-3.) #Close-to-circular and close-to-the-plane orbits if nu.sum(indx) > 0: Omegar[indx]= [epifreq(self._pot,r,use_physical=False) for r in R[indx]] Omegaphi[indx]= [omegac(self._pot,r,use_physical=False) for r in R[indx]] Omegaz[indx]= [verticalfreq(self._pot,r,use_physical=False) for r in R[indx]] if err == 0: return (jr,Lz,jz,Omegar,Omegaphi,Omegaz,angler,anglephi,anglez) else: raise RuntimeError("C-code for calculation actions failed; try with c=False") #pragma: no cover else: #pragma: no cover if 'c' in kwargs and kwargs['c'] and not self._c: #pragma: no cover warnings.warn("C module not used because potential does not have a C implementation",galpyWarning) raise NotImplementedError("actionsFreqs with c=False not implemented") def _EccZmaxRperiRap(self,*args,**kwargs): """ NAME: EccZmaxRperiRap (_EccZmaxRperiRap) PURPOSE: evaluate the eccentricity, maximum height above the plane, peri- and apocenter in the Staeckel approximation INPUT: Either: a) R,vR,vT,z,vz[,phi]: 1) floats: phase-space value for single object (phi is optional) (each can be a Quantity) 2) numpy.ndarray: [N] phase-space values for N objects (each can be a Quantity) b) Orbit instance: initial condition used if that's it, orbit(t) if there is a time given as well as the second argument delta= (object-wide default) can be used to override the object-wide focal length; can also be an array with length N to allow different delta for different phase-space points u0= (None) if object-wide option useu0 is set, u0 to use (if useu0 and useu0 is None, a good value will be computed) c= (object-wide default, bool) True/False to override the object-wide setting for whether or not to use the C implementation OUTPUT: (e,zmax,rperi,rap) HISTORY: 2017-12-12 - Written - Bovy (UofT) """ delta= kwargs.get('delta',self._delta) umin, umax, vmin= self._uminumaxvmin(*args,**kwargs) rperi= bovy_coords.uv_to_Rz(umin,nu.pi/2.,delta=delta)[0] rap_tmp, zmax= bovy_coords.uv_to_Rz(umax,vmin,delta=delta) rap= nu.sqrt(rap_tmp**2.+zmax**2.) e= (rap-rperi)/(rap+rperi) return (e,zmax,rperi,rap) def _uminumaxvmin(self,*args,**kwargs): """ NAME: _uminumaxvmin PURPOSE: evaluate u_min, u_max, and v_min INPUT: Either: a) R,vR,vT,z,vz b) Orbit instance: initial condition used if that's it, orbit(t) if there is a time given as well c= True/False; overrides the object's c= keyword to use C or not OUTPUT: (umin,umax,vmin) HISTORY: 2017-12-12 - Written - Bovy (UofT) """ delta= kwargs.pop('delta',self._delta) if ((self._c and not ('c' in kwargs and not kwargs['c']))\ or (ext_loaded and (('c' in kwargs and kwargs['c'])))) \ and _check_c(self._pot): if len(args) == 5: #R,vR.vT, z, vz R,vR,vT, z, vz= args elif len(args) == 6: #R,vR.vT, z, vz, phi R,vR,vT, z, vz, phi= args else: self._parse_eval_args(*args) R= self._eval_R vR= self._eval_vR vT= self._eval_vT z= self._eval_z vz= self._eval_vz if isinstance(R,float): R= nu.array([R]) vR= nu.array([vR]) vT= nu.array([vT]) z= nu.array([z]) vz= nu.array([vz]) Lz= R*vT if self._useu0: #First calculate u0 if 'u0' in kwargs: u0= nu.asarray(kwargs['u0']) else: E= nu.array([_evaluatePotentials(self._pot,R[ii],z[ii]) +vR[ii]**2./2.+vz[ii]**2./2.+vT[ii]**2./2. for ii in range(len(R))]) u0= actionAngleStaeckel_c.actionAngleStaeckel_calcu0(\ E,Lz,self._pot,delta)[0] kwargs.pop('u0',None) else: u0= None umin, umax, vmin, err= \ actionAngleStaeckel_c.actionAngleUminUmaxVminStaeckel_c(\ self._pot,delta,R,vR,vT,z,vz,u0=u0) if err == 0: return (umin,umax,vmin) else: #pragma: no cover raise RuntimeError("C-code for calculation actions failed; try with c=False") else: if 'c' in kwargs and kwargs['c'] and not self._c: #pragma: no cover warnings.warn("C module not used because potential does not have a C implementation",galpyWarning) kwargs.pop('c',None) if (len(args) == 5 or len(args) == 6) \ and isinstance(args[0],nu.ndarray): oumin= nu.zeros((len(args[0]))) oumax= nu.zeros((len(args[0]))) ovmin= nu.zeros((len(args[0]))) for ii in range(len(args[0])): if len(args) == 5: targs= (args[0][ii],args[1][ii],args[2][ii], args[3][ii],args[4][ii]) elif len(args) == 6: targs= (args[0][ii],args[1][ii],args[2][ii], args[3][ii],args[4][ii],args[5][ii]) tkwargs= copy.copy(kwargs) try: tkwargs['delta']= delta[ii] except TypeError: tkwargs['delta']= delta tumin,tumax,tvmin= self._uminumaxvmin(\ *targs,**tkwargs) oumin[ii]= tumin oumax[ii]= tumax ovmin[ii]= tvmin return (oumin,oumax,ovmin) else: #Set up the actionAngleStaeckelSingle object aASingle= actionAngleStaeckelSingle(*args,pot=self._pot, delta=delta) umin, umax= aASingle.calcUminUmax() vmin= aASingle.calcVmin() return (umin,umax,vmin) class actionAngleStaeckelSingle(actionAngle): """Action-angle formalism for axisymmetric potentials using Binney (2012)'s Staeckel approximation""" def __init__(self,*args,**kwargs): """ NAME: __init__ PURPOSE: initialize an actionAngleStaeckelSingle object INPUT: Either: a) R,vR,vT,z,vz b) Orbit instance: initial condition used if that's it, orbit(t) if there is a time given as well pot= potential or list of potentials OUTPUT: HISTORY: 2012-11-27 - Written - Bovy (IAS) """ self._parse_eval_args(*args,_noOrbUnitsCheck=True,**kwargs) self._R= self._eval_R self._vR= self._eval_vR self._vT= self._eval_vT self._z= self._eval_z self._vz= self._eval_vz if not 'pot' in kwargs: #pragma: no cover raise IOError("Must specify pot= for actionAngleStaeckelSingle") self._pot= kwargs['pot'] if not 'delta' in kwargs: #pragma: no cover raise IOError("Must specify delta= for actionAngleStaeckel") self._delta= kwargs['delta'] #Pre-calculate everything self._ux, self._vx= bovy_coords.Rz_to_uv(self._R,self._z, delta=self._delta) self._sinvx= nu.sin(self._vx) self._cosvx= nu.cos(self._vx) self._coshux= nu.cosh(self._ux) self._sinhux= nu.sinh(self._ux) self._pux= self._delta*(self._vR*self._coshux*self._sinvx +self._vz*self._sinhux*self._cosvx) self._pvx= self._delta*(self._vR*self._sinhux*self._cosvx -self._vz*self._coshux*self._sinvx) EL= self.calcEL() self._E= EL[0] self._Lz= EL[1] #Determine umin and umax self._u0= kwargs.pop('u0',self._ux) #u0 as defined by Binney does not matter for a #single action evaluation, so we don't determine it here self._sinhu0= nu.sinh(self._u0) self._potu0v0= potentialStaeckel(self._u0,self._vx, self._pot,self._delta) self._I3U= self._E*self._sinhux**2.-self._pux**2./2./self._delta**2.\ -self._Lz**2./2./self._delta**2./self._sinhux**2. self._potupi2= potentialStaeckel(self._ux,nu.pi/2., self._pot,self._delta) dV= (self._coshux**2.*self._potupi2 -(self._sinhux**2.+self._sinvx**2.) *potentialStaeckel(self._ux,self._vx, self._pot,self._delta)) self._I3V= -self._E*self._sinvx**2.+self._pvx**2./2./self._delta**2.\ +self._Lz**2./2./self._delta**2./self._sinvx**2.\ -dV self.calcUminUmax() self.calcVmin() return None def angleR(self,**kwargs): """ NAME: AngleR PURPOSE: Calculate the radial angle INPUT: scipy.integrate.quadrature keywords OUTPUT: w_R(R,vT,vT) in radians + estimate of the error (does not include TR error) HISTORY: 2012-11-27 - Written - Bovy (IAS) """ raise NotImplementedError("'angleR' not yet implemented for Staeckel approximation") def TR(self,**kwargs): """ NAME: TR PURPOSE: Calculate the radial period for a power-law rotation curve INPUT: scipy.integrate.quadrature keywords OUTPUT: T_R(R,vT,vT)*vc/ro + estimate of the error HISTORY: 2012-11-27 - Written - Bovy (IAS) """ raise NotImplementedError("'TR' not implemented yet for Staeckel approximation") def Tphi(self,**kwargs): """ NAME: Tphi PURPOSE: Calculate the azimuthal period INPUT: +scipy.integrate.quadrature keywords OUTPUT: T_phi(R,vT,vT)/ro/vc + estimate of the error HISTORY: 2012-11-27 - Written - Bovy (IAS) """ raise NotImplementedError("'Tphi' not implemented yet for Staeckel approxximation") def I(self,**kwargs): """ NAME: I PURPOSE: Calculate I, the 'ratio' between the radial and azimutha period INPUT: +scipy.integrate.quadrature keywords OUTPUT: I(R,vT,vT) + estimate of the error HISTORY: 2012-11-27 - Written - Bovy (IAS) """ raise NotImplementedError("'I' not implemented yet for Staeckel approxximation") def Jphi(self): #pragma: no cover """ NAME: Jphi PURPOSE: Calculate the azimuthal action INPUT: OUTPUT: J_R(R,vT,vT)/ro/vc HISTORY: 2012-11-27 - Written - Bovy (IAS) """ return self._R*self._vT def JR(self,**kwargs): """ NAME: JR PURPOSE: Calculate the radial action INPUT: fixed_quad= (False) if True, use n=10 fixed_quad +scipy.integrate.quad keywords OUTPUT: J_R(R,vT,vT)/ro/vc + estimate of the error (nan for fixed_quad) HISTORY: 2012-11-27 - Written - Bovy (IAS) """ if hasattr(self,'_JR'): #pragma: no cover return self._JR umin, umax= self.calcUminUmax() #print self._ux, self._pux, (umax-umin)/umax if (umax-umin)/umax < 10.**-6: return nu.array([0.]) order= kwargs.pop('order',10) if kwargs.pop('fixed_quad',False): # factor in next line bc integrand=/2delta^2 self._JR= 1./nu.pi*nu.sqrt(2.)*self._delta\ *integrate.fixed_quad(_JRStaeckelIntegrand, umin,umax, args=(self._E,self._Lz,self._I3U, self._delta, self._u0,self._sinhu0**2., self._vx,self._sinvx**2., self._potu0v0,self._pot), n=order, **kwargs)[0] else: self._JR= 1./nu.pi*nu.sqrt(2.)*self._delta\ *integrate.quad(_JRStaeckelIntegrand, umin,umax, args=(self._E,self._Lz,self._I3U, self._delta, self._u0,self._sinhu0**2., self._vx,self._sinvx**2., self._potu0v0,self._pot), **kwargs)[0] return self._JR def Jz(self,**kwargs): """ NAME: Jz PURPOSE: Calculate the vertical action INPUT: fixed_quad= (False) if True, use n=10 fixed_quad +scipy.integrate.quad keywords OUTPUT: J_z(R,vT,vT)/ro/vc + estimate of the error HISTORY: 2012-11-27 - Written - Bovy (IAS) """ if hasattr(self,'_JZ'): #pragma: no cover return self._JZ vmin= self.calcVmin() if (nu.pi/2.-vmin) < 10.**-7: return nu.array([0.]) order= kwargs.pop('order',10) if kwargs.pop('fixed_quad',False): # factor in next line bc integrand=/2delta^2 self._JZ= 2./nu.pi*nu.sqrt(2.)*self._delta \ *integrate.fixed_quad(_JzStaeckelIntegrand, vmin,nu.pi/2, args=(self._E,self._Lz,self._I3V, self._delta, self._ux,self._coshux**2., self._sinhux**2., self._potupi2,self._pot), n=order, **kwargs)[0] else: # factor in next line bc integrand=/2delta^2 self._JZ= 2./nu.pi*nu.sqrt(2.)*self._delta \ *integrate.quad(_JzStaeckelIntegrand, vmin,nu.pi/2, args=(self._E,self._Lz,self._I3V, self._delta, self._ux,self._coshux**2., self._sinhux**2., self._potupi2,self._pot), **kwargs)[0] return self._JZ def calcEL(self,**kwargs): """ NAME: calcEL PURPOSE: calculate the energy and angular momentum INPUT: scipy.integrate.quadrature keywords OUTPUT: (E,L) HISTORY: 2012-11-27 - Written - Bovy (IAS) """ E,L= calcELStaeckel(self._R,self._vR,self._vT,self._z,self._vz, self._pot) return (E,L) def calcUminUmax(self,**kwargs): """ NAME: calcUminUmax PURPOSE: calculate the u 'apocenter' and 'pericenter' INPUT: OUTPUT: (umin,umax) HISTORY: 2012-11-27 - Written - Bovy (IAS) """ if hasattr(self,'_uminumax'): #pragma: no cover return self._uminumax E, L= self._E, self._Lz if nu.fabs(self._pux) < 10.**-7.: #We are at umin or umax eps= 10.**-8. peps= _JRStaeckelIntegrandSquared(self._ux+eps, E,L,self._I3U,self._delta, self._u0,self._sinhu0**2., self._vx,self._sinvx**2., self._potu0v0,self._pot) meps= _JRStaeckelIntegrandSquared(self._ux-eps, E,L,self._I3U,self._delta, self._u0,self._sinhu0**2., self._vx,self._sinvx**2., self._potu0v0,self._pot) if peps < 0. and meps > 0.: #we are at umax umax= self._ux rstart,prevr= _uminUmaxFindStart(self._ux, E,L,self._I3U,self._delta, self._u0,self._sinhu0**2., self._vx,self._sinvx**2., self._potu0v0,self._pot) if rstart == 0.: umin= 0. else: try: umin= optimize.brentq(_JRStaeckelIntegrandSquared, rstart,self._ux-eps, (E,L,self._I3U,self._delta, self._u0,self._sinhu0**2., self._vx,self._sinvx**2., self._potu0v0,self._pot), maxiter=200) except RuntimeError: #pragma: no cover raise UnboundError("Orbit seems to be unbound") elif peps > 0. and meps < 0.: #we are at umin umin= self._ux rend,prevr= _uminUmaxFindStart(self._ux, E,L,self._I3U,self._delta, self._u0,self._sinhu0**2., self._vx,self._sinvx**2., self._potu0v0,self._pot, umax=True) umax= optimize.brentq(_JRStaeckelIntegrandSquared, self._ux+eps,rend, (E,L,self._I3U,self._delta, self._u0,self._sinhu0**2., self._vx,self._sinvx**2., self._potu0v0,self._pot), maxiter=200) else: #circular orbit umin= self._ux umax= self._ux else: rstart,prevr= _uminUmaxFindStart(self._ux, E,L,self._I3U,self._delta, self._u0,self._sinhu0**2., self._vx,self._sinvx**2., self._potu0v0,self._pot) if rstart == 0.: umin= 0. else: if nu.fabs(prevr-self._ux) < 10.**-2.: rup= self._ux else: rup= prevr try: umin= optimize.brentq(_JRStaeckelIntegrandSquared, rstart,rup, (E,L,self._I3U,self._delta, self._u0,self._sinhu0**2., self._vx,self._sinvx**2., self._potu0v0,self._pot), maxiter=200) except RuntimeError: #pragma: no cover raise UnboundError("Orbit seems to be unbound") rend,prevr= _uminUmaxFindStart(self._ux, E,L,self._I3U,self._delta, self._u0,self._sinhu0**2., self._vx,self._sinvx**2., self._potu0v0,self._pot, umax=True) umax= optimize.brentq(_JRStaeckelIntegrandSquared, prevr,rend, (E,L,self._I3U,self._delta, self._u0,self._sinhu0**2., self._vx,self._sinvx**2., self._potu0v0,self._pot), maxiter=200) self._uminumax= (umin,umax) return self._uminumax def calcVmin(self,**kwargs): """ NAME: calcVmin PURPOSE: calculate the v 'pericenter' INPUT: OUTPUT: vmin HISTORY: 2012-11-28 - Written - Bovy (IAS) """ if hasattr(self,'_vmin'): #pragma: no cover return self._vmin E, L= self._E, self._Lz if nu.fabs(self._pvx) < 10.**-7.: #We are at vmin or vmax eps= 10.**-8. peps= _JzStaeckelIntegrandSquared(self._vx+eps, E,L,self._I3V,self._delta, self._ux,self._coshux**2., self._sinhux**2., self._potupi2,self._pot) meps= _JzStaeckelIntegrandSquared(self._vx-eps, E,L,self._I3V,self._delta, self._ux,self._coshux**2., self._sinhux**2., self._potupi2,self._pot) if peps < 0. and meps > 0.: #we are at vmax, which cannot happen rstart= _vminFindStart(self._vx, E,L,self._I3V,self._delta, self._ux,self._coshux**2., self._sinhux**2., self._potupi2,self._pot) if rstart == 0.: vmin= 0. else: try: vmin= optimize.brentq(_JzStaeckelIntegrandSquared, rstart,self._vx-eps, (E,L,self._I3V,self._delta, self._ux,self._coshux**2., self._sinhux**2., self._potupi2,self._pot), maxiter=200) except RuntimeError: #pragma: no cover raise UnboundError("Orbit seems to be unbound") elif peps > 0. and meps < 0.: #we are at vmin vmin= self._vx else: #planar orbit vmin= self._vx else: rstart= _vminFindStart(self._vx, E,L,self._I3V,self._delta, self._ux,self._coshux**2., self._sinhux**2., self._potupi2,self._pot) if rstart == 0.: vmin= 0. else: try: vmin= optimize.brentq(_JzStaeckelIntegrandSquared, rstart,rstart/0.9, (E,L,self._I3V,self._delta, self._ux,self._coshux**2., self._sinhux**2., self._potupi2,self._pot), maxiter=200) except RuntimeError: #pragma: no cover raise UnboundError("Orbit seems to be unbound") self._vmin= vmin return self._vmin def calcELStaeckel(R,vR,vT,z,vz,pot,vc=1.,ro=1.): """ NAME: calcELStaeckel PURPOSE: calculate the energy and angular momentum INPUT: R - Galactocentric radius (/ro) vR - radial part of the velocity (/vc) vT - azimuthal part of the velocity (/vc) vc - circular velocity ro - reference radius OUTPUT: (E,L) HISTORY: 2012-11-30 - Written - Bovy (IAS) """ return (_evaluatePotentials(pot,R,z)+vR**2./2.+vT**2./2.+vz**2./2.,R*vT) def potentialStaeckel(u,v,pot,delta): """ NAME: potentialStaeckel PURPOSE: return the potential INPUT: u - confocal u v - confocal v pot - potential delta - focus OUTPUT: Phi(u,v) HISTORY: 2012-11-29 - Written - Bovy (IAS) """ R,z= bovy_coords.uv_to_Rz(u,v,delta=delta) return _evaluatePotentials(pot,R,z) def FRStaeckel(u,v,pot,delta): #pragma: no cover because unused """ NAME: FRStaeckel PURPOSE: return the radial force INPUT: u - confocal u v - confocal v pot - potential delta - focus OUTPUT: FR(u,v) HISTORY: 2012-11-30 - Written - Bovy (IAS) """ R,z= bovy_coords.uv_to_Rz(u,v,delta=delta) return _evaluateRforces(pot,R,z) def FZStaeckel(u,v,pot,delta): #pragma: no cover because unused """ NAME: FZStaeckel PURPOSE: return the vertical force INPUT: u - confocal u v - confocal v pot - potential delta - focus OUTPUT: FZ(u,v) HISTORY: 2012-11-30 - Written - Bovy (IAS) """ R,z= bovy_coords.uv_to_Rz(u,v,delta=delta) return _evaluatezforces(pot,R,z) def _JRStaeckelIntegrand(u,E,Lz,I3U,delta,u0,sinh2u0,v0,sin2v0, potu0v0,pot): return nu.sqrt(_JRStaeckelIntegrandSquared(u,E,Lz,I3U,delta,u0,sinh2u0, v0,sin2v0, potu0v0,pot)) def _JRStaeckelIntegrandSquared(u,E,Lz,I3U,delta,u0,sinh2u0,v0,sin2v0, potu0v0,pot): #potu0v0= potentialStaeckel(u0,v0,pot,delta) """The J_R integrand: p^2_u(u)/2/delta^2""" sinh2u= nu.sinh(u)**2. dU= (sinh2u+sin2v0)*potentialStaeckel(u,v0, pot,delta)\ -(sinh2u0+sin2v0)*potu0v0 return E*sinh2u-I3U-dU-Lz**2./2./delta**2./sinh2u def _JzStaeckelIntegrand(v,E,Lz,I3V,delta,u0,cosh2u0,sinh2u0, potu0pi2,pot): return nu.sqrt(_JzStaeckelIntegrandSquared(v,E,Lz,I3V,delta,u0,cosh2u0, sinh2u0, potu0pi2,pot)) def _JzStaeckelIntegrandSquared(v,E,Lz,I3V,delta,u0,cosh2u0,sinh2u0, potu0pi2,pot): #potu0pi2= potentialStaeckel(u0,nu.pi/2.,pot,delta) """The J_z integrand: p_v(v)/2/delta^2""" sin2v= nu.sin(v)**2. dV= cosh2u0*potu0pi2\ -(sinh2u0+sin2v)*potentialStaeckel(u0,v,pot,delta) return E*sin2v+I3V+dV-Lz**2./2./delta**2./sin2v def _uminUmaxFindStart(u, E,Lz,I3U,delta,u0,sinh2u0,v0,sin2v0, potu0v0,pot,umax=False): """ NAME: _uminUmaxFindStart PURPOSE: Find adequate start or end points to solve for umin and umax INPUT: same as JRStaeckelIntegrandSquared OUTPUT: rstart or rend HISTORY: 2012-11-30 - Written - Bovy (IAS) """ if umax: utry= u*1.1 else: utry= u*0.9 prevu= u while _JRStaeckelIntegrandSquared(utry, E,Lz,I3U,delta,u0,sinh2u0,v0,sin2v0, potu0v0,pot) >= 0. \ and utry > 0.000000001: prevu= utry if umax: if utry > 100.: raise UnboundError("Orbit seems to be unbound") utry*= 1.1 else: utry*= 0.9 if utry < 0.000000001: return (0.,prevu) return (utry,prevu) def _vminFindStart(v,E,Lz,I3V,delta,u0,cosh2u0,sinh2u0, potu0pi2,pot): """ NAME: _vminFindStart PURPOSE: Find adequate start point to solve for vmin INPUT: same as JzStaeckelIntegrandSquared OUTPUT: rstart HISTORY: 2012-11-28 - Written - Bovy (IAS) """ vtry= 0.9*v while _JzStaeckelIntegrandSquared(vtry, E,Lz,I3V,delta,u0,cosh2u0,sinh2u0, potu0pi2,pot) >= 0. \ and vtry > 0.000000001: vtry*= 0.9 if vtry < 0.000000001: return 0. return vtry @potential_physical_input @physical_conversion('position',pop=True) def estimateDeltaStaeckel(pot,R,z, no_median=False): """ NAME: estimateDeltaStaeckel PURPOSE: Estimate a good value for delta using eqn. (9) in Sanders (2012) INPUT: pot - Potential instance or list thereof R,z- coordinates (if these are arrays, the median estimated delta is returned, i.e., if this is an orbit) no_median - (False) if True, and input is array, return all calculated values of delta (useful for quickly estimating delta for many phase space points) OUTPUT: delta HISTORY: 2013-08-28 - Written - Bovy (IAS) 2016-02-20 - Changed input order to allow physical conversions - Bovy (UofT) """ if isinstance(R,nu.ndarray): delta2= nu.array([(z[ii]**2.-R[ii]**2. #eqn. (9) has a sign error +(3.*R[ii]*_evaluatezforces(pot,R[ii],z[ii]) -3.*z[ii]*_evaluateRforces(pot,R[ii],z[ii]) +R[ii]*z[ii]*(evaluateR2derivs(pot,R[ii],z[ii], use_physical=False) -evaluatez2derivs(pot,R[ii],z[ii], use_physical=False)))/evaluateRzderivs(pot,R[ii],z[ii],use_physical=False)) for ii in range(len(R))]) indx= (delta2 < 0.)*(delta2 > -10.**-10.) delta2[indx]= 0. if not no_median: delta2= nu.median(delta2[True^nu.isnan(delta2)]) else: delta2= (z**2.-R**2. #eqn. (9) has a sign error +(3.*R*_evaluatezforces(pot,R,z) -3.*z*_evaluateRforces(pot,R,z) +R*z*(evaluateR2derivs(pot,R,z,use_physical=False) -evaluatez2derivs(pot,R,z,use_physical=False)))/evaluateRzderivs(pot,R,z,use_physical=False)) if delta2 < 0. and delta2 > -10.**-10.: delta2= 0. return nu.sqrt(delta2) galpy-1.3.0/galpy/actionAngle_src/actionAngleStaeckel_c.py0000644000076500000240000005740413236350147023732 0ustar bovystaff00000000000000import os import sys import sysconfig import warnings import ctypes import ctypes.util import numpy from numpy.ctypeslib import ndpointer from galpy.util import galpyWarning from galpy.orbit_src.integrateFullOrbit import _parse_pot from galpy.util import bovy_coords #Find and load the library _lib= None outerr= None PY3= sys.version > '3' if PY3: #pragma: no cover _ext_suffix= sysconfig.get_config_var('EXT_SUFFIX') else: _ext_suffix= '.so' for path in sys.path: try: _lib = ctypes.CDLL(os.path.join(path,'galpy_actionAngle_c%s' % _ext_suffix)) except OSError as e: if os.path.exists(os.path.join(path,'galpy_actionAngle_c%s' % _ext_suffix)): #pragma: no cover outerr= e _lib = None else: break if _lib is None: #pragma: no cover if not outerr is None: warnings.warn("actionAngleStaeckel_c extension module not loaded, because of error '%s' " % outerr, galpyWarning) else: warnings.warn("actionAngleStaeckel_c extension module not loaded, because galpy_actionAngle_c%s image was not found" % _ext_suffix, galpyWarning) _ext_loaded= False else: _ext_loaded= True def actionAngleStaeckel_c(pot,delta,R,vR,vT,z,vz,u0=None,order=10): """ NAME: actionAngleStaeckel_c PURPOSE: Use C to calculate actions using the Staeckel approximation INPUT: pot - Potential or list of such instances delta - focal length of prolate spheroidal coordinates R, vR, vT, z, vz - coordinates (arrays) u0= (None) if set, u0 to use order= (10) order of Gauss-Legendre integration of the relevant integrals OUTPUT: (jr,jz,err) jr,jz : array, shape (len(R)) err - non-zero if error occured HISTORY: 2012-12-01 - Written - Bovy (IAS) """ if u0 is None: u0, dummy= bovy_coords.Rz_to_uv(R,z,delta=numpy.atleast_1d(delta)) #Parse the potential npot, pot_type, pot_args= _parse_pot(pot,potforactions=True) #Parse delta delta= numpy.atleast_1d(delta) ndelta= len(delta) #Set up result arrays jr= numpy.empty(len(R)) jz= numpy.empty(len(R)) err= ctypes.c_int(0) #Set up the C code ndarrayFlags= ('C_CONTIGUOUS','WRITEABLE') actionAngleStaeckel_actionsFunc= _lib.actionAngleStaeckel_actions actionAngleStaeckel_actionsFunc.argtypes= [ctypes.c_int, ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ctypes.c_int, ndpointer(dtype=numpy.int32,flags=ndarrayFlags), ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ctypes.c_int, ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ctypes.c_int, ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ctypes.POINTER(ctypes.c_int)] #Array requirements, first store old order f_cont= [R.flags['F_CONTIGUOUS'], vR.flags['F_CONTIGUOUS'], vT.flags['F_CONTIGUOUS'], z.flags['F_CONTIGUOUS'], vz.flags['F_CONTIGUOUS'], u0.flags['F_CONTIGUOUS'], delta.flags['F_CONTIGUOUS']] R= numpy.require(R,dtype=numpy.float64,requirements=['C','W']) vR= numpy.require(vR,dtype=numpy.float64,requirements=['C','W']) vT= numpy.require(vT,dtype=numpy.float64,requirements=['C','W']) z= numpy.require(z,dtype=numpy.float64,requirements=['C','W']) vz= numpy.require(vz,dtype=numpy.float64,requirements=['C','W']) u0= numpy.require(u0,dtype=numpy.float64,requirements=['C','W']) delta= numpy.require(delta,dtype=numpy.float64,requirements=['C','W']) jr= numpy.require(jr,dtype=numpy.float64,requirements=['C','W']) jz= numpy.require(jz,dtype=numpy.float64,requirements=['C','W']) #Run the C code actionAngleStaeckel_actionsFunc(len(R), R, vR, vT, z, vz, u0, ctypes.c_int(npot), pot_type, pot_args, ctypes.c_int(ndelta), delta, ctypes.c_int(order), jr, jz, ctypes.byref(err)) #Reset input arrays if f_cont[0]: R= numpy.asfortranarray(R) if f_cont[1]: vR= numpy.asfortranarray(vR) if f_cont[2]: vT= numpy.asfortranarray(vT) if f_cont[3]: z= numpy.asfortranarray(z) if f_cont[4]: vz= numpy.asfortranarray(vz) if f_cont[5]: u0= numpy.asfortranarray(u0) if f_cont[6]: delta= numpy.asfortranarray(delta) return (jr,jz,err.value) def actionAngleStaeckel_calcu0(E,Lz,pot,delta): """ NAME: actionAngleStaeckel_calcu0 PURPOSE: Use C to calculate u0 in the Staeckel approximation INPUT: E, Lz - energy and angular momentum pot - Potential or list of such instances delta - focal length of prolate spheroidal coordinates OUTPUT: (u0,err) u0 : array, shape (len(E)) err - non-zero if error occured HISTORY: 2012-12-03 - Written - Bovy (IAS) """ #Parse the potential npot, pot_type, pot_args= _parse_pot(pot,potforactions=True) #Set up result arrays u0= numpy.empty(len(E)) err= ctypes.c_int(0) #Parse delta delta= numpy.atleast_1d(delta) ndelta= len(delta) #Set up the C code ndarrayFlags= ('C_CONTIGUOUS','WRITEABLE') actionAngleStaeckel_actionsFunc= _lib.calcu0 actionAngleStaeckel_actionsFunc.argtypes= [ctypes.c_int, ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ctypes.c_int, ndpointer(dtype=numpy.int32,flags=ndarrayFlags), ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ctypes.c_int, ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ctypes.POINTER(ctypes.c_int)] #Array requirements, first store old order f_cont= [E.flags['F_CONTIGUOUS'], Lz.flags['F_CONTIGUOUS'], delta.flags['F_CONTIGUOUS']] E= numpy.require(E,dtype=numpy.float64,requirements=['C','W']) Lz= numpy.require(Lz,dtype=numpy.float64,requirements=['C','W']) delta= numpy.require(delta,dtype=numpy.float64,requirements=['C','W']) u0= numpy.require(u0,dtype=numpy.float64,requirements=['C','W']) #Run the C code actionAngleStaeckel_actionsFunc(len(E), E, Lz, ctypes.c_int(npot), pot_type, pot_args, ctypes.c_int(ndelta), delta, u0, ctypes.byref(err)) #Reset input arrays if f_cont[0]: E= numpy.asfortranarray(E) if f_cont[1]: Lz= numpy.asfortranarray(Lz) if f_cont[2]: delta= numpy.asfortranarray(delta) return (u0,err.value) def actionAngleFreqStaeckel_c(pot,delta,R,vR,vT,z,vz,u0=None,order=10): """ NAME: actionAngleFreqStaeckel_c PURPOSE: Use C to calculate actions and frequencies using the Staeckel approximation INPUT: pot - Potential or list of such instances delta - focal length of prolate spheroidal coordinates R, vR, vT, z, vz - coordinates (arrays) u0= (None) if set, u0 to use order= (10) order of Gauss-Legendre integration of the relevant integrals OUTPUT: (jr,jz,Omegar,Omegaphi,Omegaz,err) jr,jz,Omegar,Omegaphi,Omegaz : array, shape (len(R)) err - non-zero if error occured HISTORY: 2013-08-23 - Written - Bovy (IAS) """ if u0 is None: u0, dummy= bovy_coords.Rz_to_uv(R,z,delta=numpy.atleast_1d(delta)) #Parse the potential npot, pot_type, pot_args= _parse_pot(pot,potforactions=True) #Parse delta delta= numpy.atleast_1d(delta) ndelta= len(delta) #Set up result arrays jr= numpy.empty(len(R)) jz= numpy.empty(len(R)) Omegar= numpy.empty(len(R)) Omegaphi= numpy.empty(len(R)) Omegaz= numpy.empty(len(R)) err= ctypes.c_int(0) #Set up the C code ndarrayFlags= ('C_CONTIGUOUS','WRITEABLE') actionAngleStaeckel_actionsFunc= _lib.actionAngleStaeckel_actionsFreqs actionAngleStaeckel_actionsFunc.argtypes= [ctypes.c_int, ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ctypes.c_int, ndpointer(dtype=numpy.int32,flags=ndarrayFlags), ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ctypes.c_int, ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ctypes.c_int, ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ctypes.POINTER(ctypes.c_int)] #Array requirements, first store old order f_cont= [R.flags['F_CONTIGUOUS'], vR.flags['F_CONTIGUOUS'], vT.flags['F_CONTIGUOUS'], z.flags['F_CONTIGUOUS'], vz.flags['F_CONTIGUOUS'], u0.flags['F_CONTIGUOUS'], delta.flags['F_CONTIGUOUS']] R= numpy.require(R,dtype=numpy.float64,requirements=['C','W']) vR= numpy.require(vR,dtype=numpy.float64,requirements=['C','W']) vT= numpy.require(vT,dtype=numpy.float64,requirements=['C','W']) z= numpy.require(z,dtype=numpy.float64,requirements=['C','W']) vz= numpy.require(vz,dtype=numpy.float64,requirements=['C','W']) u0= numpy.require(u0,dtype=numpy.float64,requirements=['C','W']) delta= numpy.require(delta,dtype=numpy.float64,requirements=['C','W']) jr= numpy.require(jr,dtype=numpy.float64,requirements=['C','W']) jz= numpy.require(jz,dtype=numpy.float64,requirements=['C','W']) Omegar= numpy.require(Omegar,dtype=numpy.float64,requirements=['C','W']) Omegaphi= numpy.require(Omegaphi,dtype=numpy.float64, requirements=['C','W']) Omegaz= numpy.require(Omegaz,dtype=numpy.float64,requirements=['C','W']) #Run the C code actionAngleStaeckel_actionsFunc(len(R), R, vR, vT, z, vz, u0, ctypes.c_int(npot), pot_type, pot_args, ctypes.c_int(ndelta), delta, ctypes.c_int(order), jr, jz, Omegar, Omegaphi, Omegaz, ctypes.byref(err)) #Reset input arrays if f_cont[0]: R= numpy.asfortranarray(R) if f_cont[1]: vR= numpy.asfortranarray(vR) if f_cont[2]: vT= numpy.asfortranarray(vT) if f_cont[3]: z= numpy.asfortranarray(z) if f_cont[4]: vz= numpy.asfortranarray(vz) if f_cont[5]: u0= numpy.asfortranarray(u0) if f_cont[6]: delta= numpy.asfortranarray(delta) return (jr,jz,Omegar,Omegaphi,Omegaz,err.value) def actionAngleFreqAngleStaeckel_c(pot,delta,R,vR,vT,z,vz,phi, u0=None,order=10): """ NAME: actionAngleFreqAngleStaeckel_c PURPOSE: Use C to calculate actions, frequencies, and angles using the Staeckel approximation INPUT: pot - Potential or list of such instances delta - focal length of prolate spheroidal coordinates R, vR, vT, z, vz, phi - coordinates (arrays) u0= (None) if set, u0 to use order= (10) order of Gauss-Legendre integration of the relevant integrals OUTPUT: (jr,jz,Omegar,Omegaphi,Omegaz,Angler,Anglephi,Anglez,err) jr,jz,Omegar,Omegaphi,Omegaz,Angler,Anglephi,Anglez : array, shape (len(R)) err - non-zero if error occured HISTORY: 2013-08-27 - Written - Bovy (IAS) """ if u0 is None: u0, dummy= bovy_coords.Rz_to_uv(R,z,delta=numpy.atleast_1d(delta)) #Parse the potential npot, pot_type, pot_args= _parse_pot(pot,potforactions=True) #Parse delta delta= numpy.atleast_1d(delta) ndelta= len(delta) #Set up result arrays jr= numpy.empty(len(R)) jz= numpy.empty(len(R)) Omegar= numpy.empty(len(R)) Omegaphi= numpy.empty(len(R)) Omegaz= numpy.empty(len(R)) Angler= numpy.empty(len(R)) Anglephi= numpy.empty(len(R)) Anglez= numpy.empty(len(R)) err= ctypes.c_int(0) #Set up the C code ndarrayFlags= ('C_CONTIGUOUS','WRITEABLE') actionAngleStaeckel_actionsFunc= _lib.actionAngleStaeckel_actionsFreqsAngles actionAngleStaeckel_actionsFunc.argtypes= [ctypes.c_int, ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ctypes.c_int, ndpointer(dtype=numpy.int32,flags=ndarrayFlags), ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ctypes.c_int, ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ctypes.c_int, ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ctypes.POINTER(ctypes.c_int)] #Array requirements, first store old order f_cont= [R.flags['F_CONTIGUOUS'], vR.flags['F_CONTIGUOUS'], vT.flags['F_CONTIGUOUS'], z.flags['F_CONTIGUOUS'], vz.flags['F_CONTIGUOUS'], u0.flags['F_CONTIGUOUS'], delta.flags['F_CONTIGUOUS']] R= numpy.require(R,dtype=numpy.float64,requirements=['C','W']) vR= numpy.require(vR,dtype=numpy.float64,requirements=['C','W']) vT= numpy.require(vT,dtype=numpy.float64,requirements=['C','W']) z= numpy.require(z,dtype=numpy.float64,requirements=['C','W']) vz= numpy.require(vz,dtype=numpy.float64,requirements=['C','W']) u0= numpy.require(u0,dtype=numpy.float64,requirements=['C','W']) delta= numpy.require(delta,dtype=numpy.float64,requirements=['C','W']) jr= numpy.require(jr,dtype=numpy.float64,requirements=['C','W']) jz= numpy.require(jz,dtype=numpy.float64,requirements=['C','W']) Omegar= numpy.require(Omegar,dtype=numpy.float64,requirements=['C','W']) Omegaphi= numpy.require(Omegaphi,dtype=numpy.float64, requirements=['C','W']) Omegaz= numpy.require(Omegaz,dtype=numpy.float64,requirements=['C','W']) Angler= numpy.require(Angler,dtype=numpy.float64,requirements=['C','W']) Anglephi= numpy.require(Anglephi,dtype=numpy.float64, requirements=['C','W']) Anglez= numpy.require(Anglez,dtype=numpy.float64,requirements=['C','W']) #Run the C code actionAngleStaeckel_actionsFunc(len(R), R, vR, vT, z, vz, u0, ctypes.c_int(npot), pot_type, pot_args, ctypes.c_int(ndelta), delta, ctypes.c_int(order), jr, jz, Omegar, Omegaphi, Omegaz, Angler, Anglephi, Anglez, ctypes.byref(err)) #Reset input arrays if f_cont[0]: R= numpy.asfortranarray(R) if f_cont[1]: vR= numpy.asfortranarray(vR) if f_cont[2]: vT= numpy.asfortranarray(vT) if f_cont[3]: z= numpy.asfortranarray(z) if f_cont[4]: vz= numpy.asfortranarray(vz) if f_cont[5]: u0= numpy.asfortranarray(u0) if f_cont[6]: delta= numpy.asfortranarray(delta) badAngle = Anglephi != 9999.99 Anglephi[badAngle]= (Anglephi[badAngle] + phi[badAngle] % (2.*numpy.pi)) % (2.*numpy.pi) Anglephi[Anglephi < 0.]+= 2.*numpy.pi return (jr,jz,Omegar,Omegaphi,Omegaz,Angler, Anglephi,Anglez,err.value) def actionAngleUminUmaxVminStaeckel_c(pot,delta,R,vR,vT,z,vz,u0=None): """ NAME: actionAngleUminUmaxVminStaeckel_c PURPOSE: Use C to calculate umin, umax, and vmin using the Staeckel approximation INPUT: pot - Potential or list of such instances delta - focal length of prolate spheroidal coordinates R, vR, vT, z, vz - coordinates (arrays) OUTPUT: (umin,umax,vmin,err) umin,umax,vmin : array, shape (len(R)) err - non-zero if error occured HISTORY: 2017-12-12 - Written - Bovy (UofT) """ if u0 is None: u0, dummy= bovy_coords.Rz_to_uv(R,z,delta=numpy.atleast_1d(delta)) #Parse the potential npot, pot_type, pot_args= _parse_pot(pot,potforactions=True) #Parse delta delta= numpy.atleast_1d(delta) ndelta= len(delta) #Set up result arrays umin= numpy.empty(len(R)) umax= numpy.empty(len(R)) vmin= numpy.empty(len(R)) err= ctypes.c_int(0) #Set up the C code ndarrayFlags= ('C_CONTIGUOUS','WRITEABLE') actionAngleStaeckel_actionsFunc= _lib.actionAngleStaeckel_uminUmaxVmin actionAngleStaeckel_actionsFunc.argtypes= [ctypes.c_int, ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ctypes.c_int, ndpointer(dtype=numpy.int32,flags=ndarrayFlags), ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ctypes.c_int, ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ctypes.POINTER(ctypes.c_int)] #Array requirements, first store old order f_cont= [R.flags['F_CONTIGUOUS'], vR.flags['F_CONTIGUOUS'], vT.flags['F_CONTIGUOUS'], z.flags['F_CONTIGUOUS'], vz.flags['F_CONTIGUOUS'], u0.flags['F_CONTIGUOUS'], delta.flags['F_CONTIGUOUS']] R= numpy.require(R,dtype=numpy.float64,requirements=['C','W']) vR= numpy.require(vR,dtype=numpy.float64,requirements=['C','W']) vT= numpy.require(vT,dtype=numpy.float64,requirements=['C','W']) z= numpy.require(z,dtype=numpy.float64,requirements=['C','W']) vz= numpy.require(vz,dtype=numpy.float64,requirements=['C','W']) u0= numpy.require(u0,dtype=numpy.float64,requirements=['C','W']) delta= numpy.require(delta,dtype=numpy.float64,requirements=['C','W']) umin= numpy.require(umin,dtype=numpy.float64,requirements=['C','W']) umax= numpy.require(umax,dtype=numpy.float64,requirements=['C','W']) vmin= numpy.require(vmin,dtype=numpy.float64,requirements=['C','W']) #Run the C code actionAngleStaeckel_actionsFunc(len(R), R, vR, vT, z, vz, u0, ctypes.c_int(npot), pot_type, pot_args, ctypes.c_int(ndelta), delta, umin, umax, vmin, ctypes.byref(err)) #Reset input arrays if f_cont[0]: R= numpy.asfortranarray(R) if f_cont[1]: vR= numpy.asfortranarray(vR) if f_cont[2]: vT= numpy.asfortranarray(vT) if f_cont[3]: z= numpy.asfortranarray(z) if f_cont[4]: vz= numpy.asfortranarray(vz) if f_cont[5]: u0= numpy.asfortranarray(u0) if f_cont[6]: delta= numpy.asfortranarray(delta) return (umin,umax,vmin,err.value) galpy-1.3.0/galpy/actionAngle_src/actionAngleStaeckelGrid.py0000644000076500000240000010032013233763354024225 0ustar bovystaff00000000000000############################################################################### # actionAngle: a Python module to calculate actions, angles, and frequencies # # class: actionAngleStaeckelGrid # # build grid in integrals of motion to quickly evaluate # actionAngleStaeckel # # methods: # __call__: returns (jr,lz,jz) # ############################################################################### import numpy from scipy import interpolate, optimize, ndimage import galpy.actionAngle_src.actionAngleStaeckel as actionAngleStaeckel from galpy.actionAngle_src.actionAngle import actionAngle import galpy.actionAngle_src.actionAngleStaeckel_c as actionAngleStaeckel_c from galpy.actionAngle_src.actionAngleStaeckel_c import _ext_loaded as ext_loaded import galpy.potential from galpy.potential_src.Potential import _evaluatePotentials from galpy.util import multi, bovy_coords from galpy.util.bovy_conversion import physical_conversion_actionAngle, \ actionAngle_physical_input _PRINTOUTSIDEGRID= False _APY_LOADED= True try: from astropy import units except ImportError: _APY_LOADED= False class actionAngleStaeckelGrid(actionAngle): """Action-angle formalism for axisymmetric potentials using Binney (2012)'s Staeckel approximation, grid-based interpolation""" def __init__(self,pot=None,delta=None,Rmax=5., nE=25,npsi=25,nLz=30,numcores=1, interpecc=False, **kwargs): """ NAME: __init__ PURPOSE: initialize an actionAngleStaeckelGrid object INPUT: pot= potential or list of potentials delta= focus of prolate confocal coordinate system (can be Quantity) Rmax = Rmax for building grids (natural units) nE=, npsi=, nLz= grid size interpecc= (False) if True, also interpolate the approximate eccentricity, zmax, rperi, and rapo numcores= number of cpus to use to parallellize ro= distance from vantage point to GC (kpc; can be Quantity) vo= circular velocity at ro (km/s; can be Quantity) OUTPUT: instance HISTORY: 2012-11-29 - Written - Bovy (IAS) 2017-12-15 - Written - Bovy (UofT) """ actionAngle.__init__(self, ro=kwargs.get('ro',None),vo=kwargs.get('vo',None)) if pot is None: raise IOError("Must specify pot= for actionAngleStaeckelGrid") self._pot= pot if delta is None: raise IOError("Must specify delta= for actionAngleStaeckelGrid") if ext_loaded and 'c' in kwargs and kwargs['c']: self._c= True else: self._c= False self._delta= delta if _APY_LOADED and isinstance(self._delta,units.Quantity): self._delta= self._delta.to(units.kpc).value/self._ro self._Rmax= Rmax self._Rmin= 0.01 #Set up the actionAngleStaeckel object that we will use to interpolate self._aA= actionAngleStaeckel.actionAngleStaeckel(pot=self._pot,delta=self._delta,c=self._c) #Build grid self._Lzmin= 0.01 self._Lzs= numpy.linspace(self._Lzmin, self._Rmax\ *galpy.potential.vcirc(self._pot, self._Rmax), nLz) self._Lzmax= self._Lzs[-1] self._nLz= nLz #Calculate E_c(R=RL), energy of circular orbit self._RL= numpy.array([galpy.potential.rl(self._pot,l) for l in self._Lzs]) self._RLInterp= interpolate.InterpolatedUnivariateSpline(self._Lzs, self._RL,k=3) self._ERL= _evaluatePotentials(self._pot,self._RL, numpy.zeros(self._nLz))\ +self._Lzs**2./2./self._RL**2. self._ERLmax= numpy.amax(self._ERL)+1. self._ERLInterp= interpolate.InterpolatedUnivariateSpline(self._Lzs, numpy.log(-(self._ERL-self._ERLmax)),k=3) self._Ramax= 200./8. self._ERa= _evaluatePotentials(self._pot,self._Ramax,0.) +self._Lzs**2./2./self._Ramax**2. #self._EEsc= numpy.array([self._ERL[ii]+galpy.potential.vesc(self._pot,self._RL[ii])**2./4. for ii in range(nLz)]) self._ERamax= numpy.amax(self._ERa)+1. self._ERaInterp= interpolate.InterpolatedUnivariateSpline(self._Lzs, numpy.log(-(self._ERa-self._ERamax)),k=3) y= numpy.linspace(0.,1.,nE) self._nE= nE psis= numpy.linspace(0.,1.,npsi)*numpy.pi/2. self._npsi= npsi jr= numpy.zeros((nLz,nE,npsi)) jz= numpy.zeros((nLz,nE,npsi)) u0= numpy.zeros((nLz,nE)) jrLzE= numpy.zeros((nLz)) jzLzE= numpy.zeros((nLz)) #First calculate u0 thisLzs= (numpy.tile(self._Lzs,(nE,1)).T).flatten() thisERL= (numpy.tile(self._ERL,(nE,1)).T).flatten() thisERa= (numpy.tile(self._ERa,(nE,1)).T).flatten() thisy= (numpy.tile(y,(nLz,1))).flatten() thisE= _invEfunc(_Efunc(thisERa,thisERL)+thisy*(_Efunc(thisERL,thisERL)-_Efunc(thisERa,thisERL)),thisERL) if isinstance(self._pot,galpy.potential.interpRZPotential) and hasattr(self._pot,'_origPot'): u0pot= self._pot._origPot else: u0pot= self._pot if self._c: mu0= actionAngleStaeckel_c.actionAngleStaeckel_calcu0(thisE,thisLzs, u0pot, self._delta)[0] else: if numcores > 1: mu0= multi.parallel_map((lambda x: self.calcu0(thisE[x], thisLzs[x])), range(nE*nLz), numcores=numcores) else: mu0= list(map((lambda x: self.calcu0(thisE[x], thisLzs[x])), range(nE*nLz))) u0= numpy.reshape(mu0,(nLz,nE)) thisR= self._delta*numpy.sinh(u0) thisv= numpy.reshape(self.vatu0(thisE.flatten(),thisLzs.flatten(), u0.flatten(), thisR.flatten()),(nLz,nE)) self.thisv= thisv #reshape thisLzs= numpy.reshape(thisLzs,(nLz,nE)) thispsi= numpy.tile(psis,(nLz,nE,1)).flatten() thisLzs= numpy.tile(thisLzs.T,(npsi,1,1)).T.flatten() thisR= numpy.tile(thisR.T,(npsi,1,1)).T.flatten() thisv= numpy.tile(thisv.T,(npsi,1,1)).T.flatten() mjr, mlz, mjz= self._aA(thisR, #R thisv*numpy.cos(thispsi), #vR thisLzs/thisR, #vT numpy.zeros(len(thisR)), #z thisv*numpy.sin(thispsi), #vz fixed_quad=True) if interpecc: mecc, mzmax, mrperi, mrap=\ self._aA.EccZmaxRperiRap(thisR, #R thisv*numpy.cos(thispsi), #vR thisLzs/thisR, #vT numpy.zeros(len(thisR)), #z thisv*numpy.sin(thispsi)) #vz if isinstance(self._pot,galpy.potential.interpRZPotential) and hasattr(self._pot,'_origPot'): #Interpolated potentials have problems with extreme orbits indx= (mjr == 9999.99) indx+= (mjz == 9999.99) #Re-calculate these using the original potential, hopefully not too slow tmpaA= actionAngleStaeckel.actionAngleStaeckel(pot=self._pot._origPot,delta=self._delta,c=self._c) mjr[indx], dum, mjz[indx]= tmpaA(thisR[indx], #R thisv[indx]*numpy.cos(thispsi[indx]), #vR thisLzs[indx]/thisR[indx], #vT numpy.zeros(numpy.sum(indx)), #z thisv[indx]*numpy.sin(thispsi[indx]), #vz fixed_quad=True) if interpecc: mecc[indx], mzmax[indx], mrperi[indx], mrap[indx]=\ self._aA.EccZmaxRperiRap(thisR[indx], #R thisv[indx]*numpy.cos(thispsi[indx]), #vR thisLzs[indx]/thisR[indx], #vT numpy.zeros(numpy.sum(indx)), #z thisv[indx]*numpy.sin(thispsi[indx])) #vz jr= numpy.reshape(mjr,(nLz,nE,npsi)) jz= numpy.reshape(mjz,(nLz,nE,npsi)) if interpecc: ecc= numpy.reshape(mecc,(nLz,nE,npsi)) zmax= numpy.reshape(mzmax,(nLz,nE,npsi)) rperi= numpy.reshape(mrperi,(nLz,nE,npsi)) rap= numpy.reshape(mrap,(nLz,nE,npsi)) zmaxLzE= numpy.zeros((nLz)) rperiLzE= numpy.zeros((nLz)) rapLzE= numpy.zeros((nLz)) for ii in range(nLz): jrLzE[ii]= numpy.nanmax(jr[ii,(jr[ii,:,:] != 9999.99)])#:,:]) jzLzE[ii]= numpy.nanmax(jz[ii,(jz[ii,:,:] != 9999.99)])#:,:]) if interpecc: zmaxLzE[ii]= numpy.amax(zmax[ii,numpy.isfinite(zmax[ii])]) rperiLzE[ii]= numpy.amax(rperi[ii,numpy.isfinite(rperi[ii])]) rapLzE[ii]= numpy.amax(rap[ii,numpy.isfinite(rap[ii])]) jrLzE[(jrLzE == 0.)]= numpy.nanmin(jrLzE[(jrLzE > 0.)]) jzLzE[(jzLzE == 0.)]= numpy.nanmin(jzLzE[(jzLzE > 0.)]) if interpecc: zmaxLzE[(zmaxLzE == 0.)]= numpy.nanmin(zmaxLzE[(zmaxLzE > 0.)]) rperiLzE[(rperiLzE == 0.)]= numpy.nanmin(rperiLzE[(rperiLzE > 0.)]) rapLzE[(rapLzE == 0.)]= numpy.nanmin(rapLzE[(rapLzE > 0.)]) for ii in range(nLz): jr[ii,:,:]/= jrLzE[ii] jz[ii,:,:]/= jzLzE[ii] if interpecc: zmax[ii,:,:]/= zmaxLzE[ii] rperi[ii,:,:]/= rperiLzE[ii] rap[ii,:,:]/= rapLzE[ii] #Deal w/ 9999.99 jr[(jr > 1.)]= 1. jz[(jz > 1.)]= 1. #Deal w/ NaN jr[numpy.isnan(jr)]= 0. jz[numpy.isnan(jz)]= 0. if interpecc: ecc[(ecc < 0.)]= 0. ecc[(ecc > 1.)]= 1. ecc[numpy.isnan(ecc)]= 0. ecc[numpy.isinf(ecc)]= 1. zmax[(zmax > 1.)]= 1. zmax[numpy.isnan(zmax)]= 0. zmax[numpy.isinf(zmax)]= 1. rperi[(rperi > 1.)]= 1. rperi[numpy.isnan(rperi)]= 0. rperi[numpy.isinf(rperi)]= 0. # typically orbits that can reach 0 rap[(rap > 1.)]= 1. rap[numpy.isnan(rap)]= 0. rap[numpy.isinf(rap)]= 1. #First interpolate the maxima self._jr= jr self._jz= jz self._u0= u0 self._jrLzE= jrLzE self._jzLzE= jzLzE self._jrLzInterp= interpolate.InterpolatedUnivariateSpline(self._Lzs, numpy.log(jrLzE+10.**-5.),k=3) self._jzLzInterp= interpolate.InterpolatedUnivariateSpline(self._Lzs, numpy.log(jzLzE+10.**-5.),k=3) if interpecc: self._ecc= ecc self._zmax= zmax self._rperi= rperi self._rap= rap self._zmaxLzE= zmaxLzE self._rperiLzE= rperiLzE self._rapLzE= rapLzE self._zmaxLzInterp=\ interpolate.InterpolatedUnivariateSpline(self._Lzs, numpy.log(zmaxLzE+10.**-5.),k=3) self._rperiLzInterp=\ interpolate.InterpolatedUnivariateSpline(self._Lzs, numpy.log(rperiLzE+10.**-5.),k=3) self._rapLzInterp=\ interpolate.InterpolatedUnivariateSpline(self._Lzs, numpy.log(rapLzE+10.**-5.),k=3) #Interpolate u0 self._logu0Interp= interpolate.RectBivariateSpline(self._Lzs, y, numpy.log(u0), kx=3,ky=3,s=0.) #spline filter jr and jz, such that they can be used with ndimage.map_coordinates self._jrFiltered= ndimage.spline_filter(numpy.log(self._jr+10.**-10.),order=3) self._jzFiltered= ndimage.spline_filter(numpy.log(self._jz+10.**-10.),order=3) if interpecc: self._eccFiltered= ndimage.spline_filter(numpy.log(self._ecc+10.**-10.),order=3) self._zmaxFiltered= ndimage.spline_filter(numpy.log(self._zmax+10.**-10.),order=3) self._rperiFiltered= ndimage.spline_filter(numpy.log(self._rperi+10.**-10.),order=3) self._rapFiltered= ndimage.spline_filter(numpy.log(self._rap+10.**-10.),order=3) # Check the units self._check_consistent_units() return None def _evaluate(self,*args,**kwargs): """ NAME: __call__ (_evaluate) PURPOSE: evaluate the actions (jr,lz,jz) INPUT: Either: a) R,vR,vT,z,vz[,phi]: 1) floats: phase-space value for single object (phi is optional) (each can be a Quantity) 2) numpy.ndarray: [N] phase-space values for N objects (each can be a Quantity) b) Orbit instance: initial condition used if that's it, orbit(t) if there is a time given as well as the second argument Keywords for actionAngleStaeckel.__call__ for off-the-grid evaluations OUTPUT: (jr,lz,jz) HISTORY: 2012-11-29 - Written - Bovy (IAS) """ if len(args) == 5: #R,vR.vT, z, vz R,vR,vT, z, vz= args elif len(args) == 6: #R,vR.vT, z, vz, phi R,vR,vT, z, vz, phi= args else: self._parse_eval_args(*args) R= self._eval_R vR= self._eval_vR vT= self._eval_vT z= self._eval_z vz= self._eval_vz Lz= R*vT Phi= _evaluatePotentials(self._pot,R,z) E= Phi+vR**2./2.+vT**2./2.+vz**2./2. thisERL= -numpy.exp(self._ERLInterp(Lz))+self._ERLmax thisERa= -numpy.exp(self._ERaInterp(Lz))+self._ERamax if isinstance(R,numpy.ndarray): indx= ((E-thisERa)/(thisERL-thisERa) > 1.)\ *(((E-thisERa)/(thisERL-thisERa)-1.) < 10.**-2.) E[indx]= thisERL[indx] indx= ((E-thisERa)/(thisERL-thisERa) < 0.)\ *((E-thisERa)/(thisERL-thisERa) > -10.**-2.) E[indx]= thisERa[indx] indx= (Lz < self._Lzmin) indx+= (Lz > self._Lzmax) indx+= ((E-thisERa)/(thisERL-thisERa) > 1.) indx+= ((E-thisERa)/(thisERL-thisERa) < 0.) indxc= True^indx jr= numpy.empty(R.shape) jz= numpy.empty(R.shape) if numpy.sum(indxc) > 0: u0= numpy.exp(self._logu0Interp.ev(Lz[indxc], (_Efunc(E[indxc],thisERL[indxc])-_Efunc(thisERa[indxc],thisERL[indxc]))/(_Efunc(thisERL[indxc],thisERL[indxc])-_Efunc(thisERa[indxc],thisERL[indxc])))) sinh2u0= numpy.sinh(u0)**2. thisEr= self.Er(R[indxc],z[indxc],vR[indxc],vz[indxc], E[indxc],Lz[indxc],sinh2u0,u0) thisEz= self.Ez(R[indxc],z[indxc],vR[indxc],vz[indxc], E[indxc],Lz[indxc],sinh2u0,u0) thisv2= self.vatu0(E[indxc],Lz[indxc],u0,self._delta*numpy.sinh(u0),retv2=True) cos2psi= 2.*thisEr/thisv2/(1.+sinh2u0) #latter is cosh2u0 cos2psi[(cos2psi > 1.)*(cos2psi < 1.+10.**-5.)]= 1. indxCos2psi= (cos2psi > 1.) indxCos2psi+= (cos2psi < 0.) indxc[indxc]= True^indxCos2psi#Handle these two cases as off-grid indx= True^indxc psi= numpy.arccos(numpy.sqrt(cos2psi[True^indxCos2psi])) coords= numpy.empty((3,numpy.sum(indxc))) coords[0,:]= (Lz[indxc]-self._Lzmin)/(self._Lzmax-self._Lzmin)*(self._nLz-1.) y= (_Efunc(E[indxc],thisERL[indxc])-_Efunc(thisERa[indxc],thisERL[indxc]))/(_Efunc(thisERL[indxc],thisERL[indxc])-_Efunc(thisERa[indxc],thisERL[indxc])) coords[1,:]= y*(self._nE-1.) coords[2,:]= psi/numpy.pi*2.*(self._npsi-1.) jr[indxc]= (numpy.exp(ndimage.interpolation.map_coordinates(self._jrFiltered, coords, order=3, prefilter=False))-10.**-10.)*(numpy.exp(self._jrLzInterp(Lz[indxc]))-10.**-5.) #Switch to Ez-calculated psi sin2psi= 2.*thisEz[True^indxCos2psi]/thisv2[True^indxCos2psi]/(1.+sinh2u0[True^indxCos2psi]) #latter is cosh2u0 sin2psi[(sin2psi > 1.)*(sin2psi < 1.+10.**-5.)]= 1. indxSin2psi= (sin2psi > 1.) indxSin2psi+= (sin2psi < 0.) indxc[indxc]= True^indxSin2psi#Handle these two cases as off-grid indx= True^indxc psiz= numpy.arcsin(numpy.sqrt(sin2psi[True^indxSin2psi])) newcoords= numpy.empty((3,numpy.sum(indxc))) newcoords[0:2,:]= coords[0:2,True^indxSin2psi] newcoords[2,:]= psiz/numpy.pi*2.*(self._npsi-1.) jz[indxc]= (numpy.exp(ndimage.interpolation.map_coordinates(self._jzFiltered, newcoords, order=3, prefilter=False))-10.**-10.)*(numpy.exp(self._jzLzInterp(Lz[indxc]))-10.**-5.) if numpy.sum(indx) > 0: jrindiv, lzindiv, jzindiv= self._aA(R[indx], vR[indx], vT[indx], z[indx], vz[indx], **kwargs) jr[indx]= jrindiv jz[indx]= jzindiv """ jrindiv= numpy.empty(numpy.sum(indx)) jzindiv= numpy.empty(numpy.sum(indx)) for ii in range(numpy.sum(indx)): try: thisaA= actionAngleStaeckel.actionAngleStaeckelSingle(\ R[indx][ii], #R vR[indx][ii], #vR vT[indx][ii], #vT z[indx][ii], #z vz[indx][ii], #vz pot=self._pot,delta=self._delta) jrindiv[ii]= thisaA.JR(fixed_quad=True)[0] jzindiv[ii]= thisaA.Jz(fixed_quad=True)[0] except (UnboundError,OverflowError): jrindiv[ii]= numpy.nan jzindiv[ii]= numpy.nan jr[indx]= jrindiv jz[indx]= jzindiv """ else: jr,Lz, jz= self(numpy.array([R]), numpy.array([vR]), numpy.array([vT]), numpy.array([z]), numpy.array([vz]), **kwargs) return (jr[0],Lz[0],jz[0]) jr[jr < 0.]= 0. jz[jz < 0.]= 0. return (jr,R*vT,jz) def Jz(self,*args,**kwargs): """ NAME: Jz PURPOSE: evaluate the action jz INPUT: Either: a) R,vR,vT,z,vz b) Orbit instance: initial condition used if that's it, orbit(t) if there is a time given as well scipy.integrate.quadrature keywords OUTPUT: jz HISTORY: 2012-07-30 - Written - Bovy (IAS@MPIA) """ return self(*args,**kwargs)[2] def JR(self,*args,**kwargs): """ NAME: JR PURPOSE: evaluate the action jr INPUT: Either: a) R,vR,vT,z,vz b) Orbit instance: initial condition used if that's it, orbit(t) if there is a time given as well scipy.integrate.quadrature keywords OUTPUT: jr HISTORY: 2012-07-30 - Written - Bovy (IAS@MPIA) """ return self(*args,**kwargs)[0] def _EccZmaxRperiRap(self,*args,**kwargs): """ NAME: EccZmaxRperiRap (_EccZmaxRperiRap) PURPOSE: evaluate the eccentricity, maximum height above the plane, peri- and apocenter in the Staeckel approximation INPUT: Either: a) R,vR,vT,z,vz[,phi]: 1) floats: phase-space value for single object (phi is optional) (each can be a Quantity) 2) numpy.ndarray: [N] phase-space values for N objects (each can be a Quantity) b) Orbit instance: initial condition used if that's it, orbit(t) if there is a time given as well as the second argument OUTPUT: (e,zmax,rperi,rap) HISTORY: 2017-12-15 - Written - Bovy (UofT) """ if len(args) == 5: #R,vR.vT, z, vz R,vR,vT, z, vz= args elif len(args) == 6: #R,vR.vT, z, vz, phi R,vR,vT, z, vz, phi= args else: self._parse_eval_args(*args) R= self._eval_R vR= self._eval_vR vT= self._eval_vT z= self._eval_z vz= self._eval_vz Lz= R*vT Phi= _evaluatePotentials(self._pot,R,z) E= Phi+vR**2./2.+vT**2./2.+vz**2./2. thisERL= -numpy.exp(self._ERLInterp(Lz))+self._ERLmax thisERa= -numpy.exp(self._ERaInterp(Lz))+self._ERamax if isinstance(R,numpy.ndarray): indx= ((E-thisERa)/(thisERL-thisERa) > 1.)\ *(((E-thisERa)/(thisERL-thisERa)-1.) < 10.**-2.) E[indx]= thisERL[indx] indx= ((E-thisERa)/(thisERL-thisERa) < 0.)\ *((E-thisERa)/(thisERL-thisERa) > -10.**-2.) E[indx]= thisERa[indx] indx= (Lz < self._Lzmin) indx+= (Lz > self._Lzmax) indx+= ((E-thisERa)/(thisERL-thisERa) > 1.) indx+= ((E-thisERa)/(thisERL-thisERa) < 0.) indxc= True^indx ecc= numpy.empty(R.shape) zmax= numpy.empty(R.shape) rperi= numpy.empty(R.shape) rap= numpy.empty(R.shape) if numpy.sum(indxc) > 0: u0= numpy.exp(self._logu0Interp.ev(Lz[indxc], (_Efunc(E[indxc],thisERL[indxc])-_Efunc(thisERa[indxc],thisERL[indxc]))/(_Efunc(thisERL[indxc],thisERL[indxc])-_Efunc(thisERa[indxc],thisERL[indxc])))) sinh2u0= numpy.sinh(u0)**2. thisEr= self.Er(R[indxc],z[indxc],vR[indxc],vz[indxc], E[indxc],Lz[indxc],sinh2u0,u0) thisEz= self.Ez(R[indxc],z[indxc],vR[indxc],vz[indxc], E[indxc],Lz[indxc],sinh2u0,u0) thisv2= self.vatu0(E[indxc],Lz[indxc],u0,self._delta*numpy.sinh(u0),retv2=True) cos2psi= 2.*thisEr/thisv2/(1.+sinh2u0) #latter is cosh2u0 cos2psi[(cos2psi > 1.)*(cos2psi < 1.+10.**-5.)]= 1. indxCos2psi= (cos2psi > 1.) indxCos2psi+= (cos2psi < 0.) indxc[indxc]= True^indxCos2psi#Handle these two cases as off-grid indx= True^indxc psi= numpy.arccos(numpy.sqrt(cos2psi[True^indxCos2psi])) coords= numpy.empty((3,numpy.sum(indxc))) coords[0,:]= (Lz[indxc]-self._Lzmin)/(self._Lzmax-self._Lzmin)*(self._nLz-1.) y= (_Efunc(E[indxc],thisERL[indxc])-_Efunc(thisERa[indxc],thisERL[indxc]))/(_Efunc(thisERL[indxc],thisERL[indxc])-_Efunc(thisERa[indxc],thisERL[indxc])) coords[1,:]= y*(self._nE-1.) coords[2,:]= psi/numpy.pi*2.*(self._npsi-1.) ecc[indxc]= (numpy.exp(ndimage.interpolation.map_coordinates(self._eccFiltered, coords, order=3, prefilter=False))-10.**-10.) rperi[indxc]= (numpy.exp(ndimage.interpolation.map_coordinates(self._rperiFiltered, coords, order=3, prefilter=False))-10.**-10.)*(numpy.exp(self._rperiLzInterp(Lz[indxc]))-10.**-5.) # We do rap below with zmax #Switch to Ez-calculated psi sin2psi= 2.*thisEz[True^indxCos2psi]/thisv2[True^indxCos2psi]/(1.+sinh2u0[True^indxCos2psi]) #latter is cosh2u0 sin2psi[(sin2psi > 1.)*(sin2psi < 1.+10.**-5.)]= 1. indxSin2psi= (sin2psi > 1.) indxSin2psi+= (sin2psi < 0.) indxc[indxc]= True^indxSin2psi#Handle these two cases as off-grid indx= True^indxc psiz= numpy.arcsin(numpy.sqrt(sin2psi[True^indxSin2psi])) newcoords= numpy.empty((3,numpy.sum(indxc))) newcoords[0:2,:]= coords[0:2,True^indxSin2psi] newcoords[2,:]= psiz/numpy.pi*2.*(self._npsi-1.) zmax[indxc]= (numpy.exp(ndimage.interpolation.map_coordinates(self._zmaxFiltered, newcoords, order=3, prefilter=False))-10.**-10.)*(numpy.exp(self._zmaxLzInterp(Lz[indxc]))-10.**-5.) rap[indxc]= (numpy.exp(ndimage.interpolation.map_coordinates(self._rapFiltered, newcoords, order=3, prefilter=False))-10.**-10.)*(numpy.exp(self._rapLzInterp(Lz[indxc]))-10.**-5.) if numpy.sum(indx) > 0: eccindiv, zmaxindiv, rperiindiv, rapindiv=\ self._aA.EccZmaxRperiRap(R[indx], vR[indx], vT[indx], z[indx], vz[indx], **kwargs) ecc[indx]= eccindiv zmax[indx]= zmaxindiv rperi[indx]= rperiindiv rap[indx]= rapindiv else: ecc,zmax,rperi,rap= self.EccZmaxRperiRap(numpy.array([R]), numpy.array([vR]), numpy.array([vT]), numpy.array([z]), numpy.array([vz]), **kwargs) return (ecc[0],zmax[0],rperi[0],rap[0]) ecc[ecc < 0.]= 0. zmax[zmax < 0.]= 0. rperi[rperi < 0.]= 0. rap[rap < 0.]= 0. return (ecc,zmax,rperi,rap) def vatu0(self,E,Lz,u0,R,retv2=False): """ NAME: vatu0 PURPOSE: calculate the velocity at u0 INPUT: E - energy Lz - angular momentum u0 - u0 R - radius corresponding to u0,pi/2. retv2= (False), if True return v^2 OUTPUT: velocity HISTORY: 2012-11-29 - Written - Bovy (IAS) """ v2= (2.*(E-actionAngleStaeckel.potentialStaeckel(u0,numpy.pi/2., self._pot, self._delta)) -Lz**2./R**2.) if retv2: return v2 v2[(v2 < 0.)*(v2 > -10.**-7.)]= 0. return numpy.sqrt(v2) def calcu0(self,E,Lz): """ NAME: calcu0 PURPOSE: calculate the minimum of the u potential INPUT: E - energy Lz - angular momentum OUTPUT: u0 HISTORY: 2012-11-29 - Written - Bovy (IAS) """ logu0= optimize.brent(_u0Eq, args=(self._delta,self._pot, E,Lz**2./2.)) return numpy.exp(logu0) def Er(self,R,z,vR,vz,E,Lz,sinh2u0,u0): """ NAME: Er PURPOSE: calculate the 'radial energy' INPUT: R, z, vR, vz - coordinates E - energy Lz - angular momentum sinh2u0, u0 - sinh^2 and u0 OUTPUT: Er HISTORY: 2012-11-29 - Written - Bovy (IAS) """ u,v= bovy_coords.Rz_to_uv(R,z,self._delta) pu= (vR*numpy.cosh(u)*numpy.sin(v) +vz*numpy.sinh(u)*numpy.cos(v)) #no delta, bc we will divide it out out= (pu**2./2.+Lz**2./2./self._delta**2.*(1./numpy.sinh(u)**2.-1./sinh2u0) -E*(numpy.sinh(u)**2.-sinh2u0) +(numpy.sinh(u)**2.+1.)*actionAngleStaeckel.potentialStaeckel(u,numpy.pi/2.,self._pot,self._delta) -(sinh2u0+1.)*actionAngleStaeckel.potentialStaeckel(u0,numpy.pi/2.,self._pot,self._delta)) # +(numpy.sinh(u)**2.+numpy.sin(v)**2.)*actionAngleStaeckel.potentialStaeckel(u,v,self._pot,self._delta) # -(sinh2u0+numpy.sin(v)**2.)*actionAngleStaeckel.potentialStaeckel(u0,v,self._pot,self._delta)) return out def Ez(self,R,z,vR,vz,E,Lz,sinh2u0,u0): """ NAME: Ez PURPOSE: calculate the 'vertical energy' INPUT: R, z, vR, vz - coordinates E - energy Lz - angular momentum sinh2u0, u0 - sinh^2 and u0 OUTPUT: Ez HISTORY: 2012-12-23 - Written - Bovy (IAS) """ u,v= bovy_coords.Rz_to_uv(R,z,self._delta) pv= (vR*numpy.sinh(u)*numpy.cos(v) -vz*numpy.cosh(u)*numpy.sin(v)) #no delta, bc we will divide it out out= (pv**2./2.+Lz**2./2./self._delta**2.*(1./numpy.sin(v)**2.-1.) -E*(numpy.sin(v)**2.-1.) -(sinh2u0+1.)*actionAngleStaeckel.potentialStaeckel(u0,numpy.pi/2.,self._pot,self._delta) +(sinh2u0+numpy.sin(v)**2.)*actionAngleStaeckel.potentialStaeckel(u0,v,self._pot,self._delta)) return out def _u0Eq(logu,delta,pot,E,Lz22): """The equation that needs to be minimized to find u0""" u= numpy.exp(logu) sinh2u= numpy.sinh(u)**2. cosh2u= numpy.cosh(u)**2. dU= cosh2u*actionAngleStaeckel.potentialStaeckel(u,numpy.pi/2.,pot,delta) return -(E*sinh2u-dU-Lz22/delta**2./sinh2u) def _Efunc(E,*args): """Function to apply to the energy in building the grid (e.g., if this is a log, then the grid will be logarithmic""" # return ((E-args[0]))**0.5 return numpy.log((E-args[0]+10.**-10.)) def _invEfunc(Ef,*args): """Inverse of Efunc""" # return Ef**2.+args[0] return numpy.exp(Ef)+args[0]-10.**-10. galpy-1.3.0/galpy/actionAngle_src/actionAngleTorus.py0000644000076500000240000002246213203124675023004 0ustar bovystaff00000000000000############################################################################### # class: actionAngleTorus # # Use McMillan, Binney, and Dehnen's Torus code to calculate (x,v) # given actions and angles # # ############################################################################### import warnings import numpy from galpy.potential import MWPotential, _isNonAxi from galpy.util import galpyWarning import galpy.actionAngle_src.actionAngleTorus_c as actionAngleTorus_c from galpy.actionAngle_src.actionAngleTorus_c import _ext_loaded as ext_loaded from galpy.potential_src.Potential import _check_c _autofit_errvals= {} _autofit_errvals[-1]= 'something wrong with input, usually bad starting values for the parameters' _autofit_errvals[-2]= 'Fit failed the goal by a factor <= 2' _autofit_errvals[-3]= 'Fit failed the goal by more than 2' _autofit_errvals[-4]= 'Fit aborted: serious problems occured' class actionAngleTorus(object): """Action-angle formalism using the Torus machinery""" def __init__(self,*args,**kwargs): """ NAME: __init__ PURPOSE: initialize an actionAngleTorus object INPUT: pot= potential or list of potentials (3D) tol= default tolerance to use when fitting tori (|dJ|/J) dJ= default action difference when computing derivatives (Hessian or Jacobian) OUTPUT: instance HISTORY: 2015-08-07 - Written - Bovy (UofT) """ if not 'pot' in kwargs: #pragma: no cover raise IOError("Must specify pot= for actionAngleTorus") self._pot= kwargs['pot'] if _isNonAxi(self._pot): raise RuntimeError("actionAngleTorus for non-axisymmetric potentials is not supported") if self._pot == MWPotential: warnings.warn("Use of MWPotential as a Milky-Way-like potential is deprecated; galpy.potential.MWPotential2014, a potential fit to a large variety of dynamical constraints (see Bovy 2015), is the preferred Milky-Way-like potential in galpy", galpyWarning) if ext_loaded: self._c= _check_c(self._pot) if not self._c: raise RuntimeError('The given potential is not fully implemented in C; using the actionAngleTorus code is not supported in pure Python') else:# pragma: no cover raise RuntimeError('actionAngleTorus instances cannot be used, because the actionAngleTorus_c extension failed to load') self._tol= kwargs.get('tol',0.001) self._dJ= kwargs.get('dJ',0.001) return None def __call__(self,jr,jphi,jz,angler,anglephi,anglez,**kwargs): """ NAME: __call__ PURPOSE: evaluate the phase-space coordinates (x,v) for a number of angles on a single torus INPUT: jr - radial action (scalar) jphi - azimuthal action (scalar) jz - vertical action (scalar) angler - radial angle (array [N]) anglephi - azimuthal angle (array [N]) anglez - vertical angle (array [N]) tol= (object-wide value) goal for |dJ|/|J| along the torus OUTPUT: [R,vR,vT,z,vz,phi] HISTORY: 2015-08-07 - Written - Bovy (UofT) """ out= actionAngleTorus_c.actionAngleTorus_xvFreqs_c(\ self._pot, jr,jphi,jz, angler,anglephi,anglez, tol=kwargs.get('tol',self._tol)) if out[9] != 0: warnings.warn("actionAngleTorus' AutoFit exited with non-zero return status %i: %s" % (out[9],_autofit_errvals[out[9]]), galpyWarning) return numpy.array(out[:6]).T def xvFreqs(self,jr,jphi,jz,angler,anglephi,anglez,**kwargs): """ NAME: xvFreqs PURPOSE: evaluate the phase-space coordinates (x,v) for a number of angles on a single torus as well as the frequencies INPUT: jr - radial action (scalar) jphi - azimuthal action (scalar) jz - vertical action (scalar) angler - radial angle (array [N]) anglephi - azimuthal angle (array [N]) anglez - vertical angle (array [N]) tol= (object-wide value) goal for |dJ|/|J| along the torus OUTPUT: ([R,vR,vT,z,vz,phi],OmegaR,Omegaphi,Omegaz,AutoFit error message) HISTORY: 2015-08-07 - Written - Bovy (UofT) """ out= actionAngleTorus_c.actionAngleTorus_xvFreqs_c(\ self._pot, jr,jphi,jz, angler,anglephi,anglez, tol=kwargs.get('tol',self._tol)) if out[9] != 0: warnings.warn("actionAngleTorus' AutoFit exited with non-zero return status %i: %s" % (out[9],_autofit_errvals[out[9]]), galpyWarning) return (numpy.array(out[:6]).T,out[6],out[7],out[8],out[9]) def Freqs(self,jr,jphi,jz,**kwargs): """ NAME: Freqs PURPOSE: return the frequencies corresponding to a torus INPUT: jr - radial action (scalar) jphi - azimuthal action (scalar) jz - vertical action (scalar) tol= (object-wide value) goal for |dJ|/|J| along the torus OUTPUT: (OmegaR,Omegaphi,Omegaz) HISTORY: 2015-08-07 - Written - Bovy (UofT) """ out= actionAngleTorus_c.actionAngleTorus_Freqs_c(\ self._pot, jr,jphi,jz, tol=kwargs.get('tol',self._tol)) if out[3] != 0: warnings.warn("actionAngleTorus' AutoFit exited with non-zero return status %i: %s" % (out[3],_autofit_errvals[out[3]]), galpyWarning) return out def hessianFreqs(self,jr,jphi,jz,**kwargs): """ NAME: hessianFreqs PURPOSE: return the Hessian d Omega / d J and frequencies Omega corresponding to a torus INPUT: jr - radial action (scalar) jphi - azimuthal action (scalar) jz - vertical action (scalar) tol= (object-wide value) goal for |dJ|/|J| along the torus dJ= (object-wide value) action difference when computing derivatives (Hessian or Jacobian) nosym= (False) if True, don't explicitly symmetrize the Hessian (good to check errors) OUTPUT: (dO/dJ,Omegar,Omegaphi,Omegaz,Autofit error message) HISTORY: 2016-07-15 - Written - Bovy (UofT) """ out= actionAngleTorus_c.actionAngleTorus_hessian_c(\ self._pot, jr,jphi,jz, tol=kwargs.get('tol',self._tol), dJ=kwargs.get('dJ',self._dJ)) if out[4] != 0: warnings.warn("actionAngleTorus' AutoFit exited with non-zero return status %i: %s" % (out[4],_autofit_errvals[out[4]]), galpyWarning) # Re-arrange frequencies and actions to r,phi,z out[0][:,:]= out[0][:,[0,2,1]] out[0][:,:]= out[0][[0,2,1]] if kwargs.get('nosym',False): return out else :# explicitly symmetrize return (0.5*(out[0]+out[0].T),out[1],out[2],out[3],out[4]) def xvJacobianFreqs(self,jr,jphi,jz,angler,anglephi,anglez,**kwargs): """ NAME: xvJacobianFreqs PURPOSE: return [R,vR,vT,z,vz,phi], the Jacobian d [R,vR,vT,z,vz,phi] / d (J,angle), the Hessian dO/dJ, and frequencies Omega corresponding to a torus at multiple sets of angles INPUT: jr - radial action (scalar) jphi - azimuthal action (scalar) jz - vertical action (scalar) angler - radial angle (array [N]) anglephi - azimuthal angle (array [N]) anglez - vertical angle (array [N]) tol= (object-wide value) goal for |dJ|/|J| along the torus dJ= (object-wide value) action difference when computing derivatives (Hessian or Jacobian) nosym= (False) if True, don't explicitly symmetrize the Hessian (good to check errors) OUTPUT: ([R,vR,vT,z,vz,phi], [N,6] array d[R,vR,vT,z,vz,phi]/d[J,angle], --> (N,6,6) array dO/dJ, --> (3,3) array Omegar,Omegaphi,Omegaz, [N] arrays Autofit error message) HISTORY: 2016-07-19 - Written - Bovy (UofT) """ out= actionAngleTorus_c.actionAngleTorus_jacobian_c(\ self._pot, jr,jphi,jz, angler,anglephi,anglez, tol=kwargs.get('tol',self._tol), dJ=kwargs.get('dJ',self._dJ)) if out[11] != 0: warnings.warn("actionAngleTorus' AutoFit exited with non-zero return status %i: %s" % (out[11],_autofit_errvals[out[11]]), galpyWarning) # Re-arrange actions,angles to r,phi,z out[6][:,:,:]= out[6][:,:,[0,2,1,3,5,4]] out[7][:,:]= out[7][:,[0,2,1]] out[7][:,:]= out[7][[0,2,1]] # Re-arrange x,v to R,vR,vT,z,vz,phi out[6][:,:]= out[6][:,[0,3,5,1,4,2]] if not kwargs.get('nosym',False): # explicitly symmetrize out[7][:]= 0.5*(out[7]+out[7].T) return (numpy.array(out[:6]).T,out[6],out[7], out[8],out[9],out[10],out[11]) galpy-1.3.0/galpy/actionAngle_src/actionAngleTorus_c.py0000644000076500000240000003657213006073440023307 0ustar bovystaff00000000000000import os import sys import sysconfig import warnings import ctypes import ctypes.util import numpy from numpy.ctypeslib import ndpointer from galpy.util import galpyWarning from galpy.orbit_src.integrateFullOrbit import _parse_pot #Find and load the library _lib= None outerr= None PY3= sys.version > '3' if PY3: #pragma: no cover _ext_suffix= sysconfig.get_config_var('EXT_SUFFIX') else: _ext_suffix= '.so' for path in sys.path: try: _lib = ctypes.CDLL(os.path.join(path,'galpy_actionAngleTorus_c%s' % _ext_suffix)) except OSError as e: if os.path.exists(os.path.join(path,'galpy_actionAngleTorus_c%s' % _ext_suffix)): #pragma: no cover outerr= e _lib = None else: break if _lib is None: #pragma: no cover if not outerr is None: warnings.warn("actionAngleTorus_c extension module not loaded, because of error '%s' " % outerr, galpyWarning) else: warnings.warn("actionAngleTorus_c extension module not loaded, because galpy_actionAngleTorus_c%s image was not found" % _ext_suffix, galpyWarning) _ext_loaded= False else: _ext_loaded= True def actionAngleTorus_xvFreqs_c(pot,jr,jphi,jz, angler,anglephi,anglez, tol=0.003): """ NAME: actionAngleTorus_xvFreqs_c PURPOSE: compute configuration (x,v) and frequencies of a set of angles on a single torus INPUT: pot - Potential object or list thereof jr - radial action (scalar) jphi - azimuthal action (scalar) jz - vertical action (scalar) angler - radial angle (array [N]) anglephi - azimuthal angle (array [N]) anglez - vertical angle (array [N]) tol= (0.003) goal for |dJ|/|J| along the torus OUTPUT: (R,vR,vT,z,vz,phi,Omegar,Omegaphi,Omegaz,flag) HISTORY: 2015-08-05/07 - Written - Bovy (UofT) """ #Parse the potential npot, pot_type, pot_args= _parse_pot(pot,potfortorus=True) #Set up result arrays R= numpy.empty(len(angler)) vR= numpy.empty(len(angler)) vT= numpy.empty(len(angler)) z= numpy.empty(len(angler)) vz= numpy.empty(len(angler)) phi= numpy.empty(len(angler)) Omegar= numpy.empty(1) Omegaphi= numpy.empty(1) Omegaz= numpy.empty(1) flag= ctypes.c_int(0) #Set up the C code ndarrayFlags= ('C_CONTIGUOUS','WRITEABLE') actionAngleTorus_xvFreqsFunc= _lib.actionAngleTorus_xvFreqs actionAngleTorus_xvFreqsFunc.argtypes=\ [ctypes.c_double, ctypes.c_double, ctypes.c_double, ctypes.c_int, ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ctypes.c_int, ndpointer(dtype=numpy.int32,flags=ndarrayFlags), ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ctypes.c_double, ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ctypes.POINTER(ctypes.c_int)] #Array requirements, first store old order f_cont= [angler.flags['F_CONTIGUOUS'], anglephi.flags['F_CONTIGUOUS'], anglez.flags['F_CONTIGUOUS']] angler= numpy.require(angler,dtype=numpy.float64,requirements=['C','W']) anglephi= numpy.require(anglephi,dtype=numpy.float64,requirements=['C','W']) anglez= numpy.require(anglez,dtype=numpy.float64,requirements=['C','W']) R= numpy.require(R,dtype=numpy.float64,requirements=['C','W']) vR= numpy.require(vR,dtype=numpy.float64,requirements=['C','W']) vT= numpy.require(vT,dtype=numpy.float64,requirements=['C','W']) z= numpy.require(z,dtype=numpy.float64,requirements=['C','W']) vz= numpy.require(vz,dtype=numpy.float64,requirements=['C','W']) phi= numpy.require(phi,dtype=numpy.float64,requirements=['C','W']) Omegar= numpy.require(Omegar,dtype=numpy.float64,requirements=['C','W']) Omegaphi= numpy.require(Omegaphi,dtype=numpy.float64,requirements=['C','W']) Omegaz= numpy.require(Omegaz,dtype=numpy.float64,requirements=['C','W']) #Run the C code actionAngleTorus_xvFreqsFunc(ctypes.c_double(jr), ctypes.c_double(jphi), ctypes.c_double(jz), ctypes.c_int(len(angler)), angler, anglephi, anglez, ctypes.c_int(npot), pot_type, pot_args, ctypes.c_double(tol), R,vR,vT,z,vz,phi, Omegar,Omegaphi,Omegaz, ctypes.byref(flag)) #Reset input arrays if f_cont[0]: angler= numpy.asfortranarray(angler) if f_cont[1]: anglephi= numpy.asfortranarray(anglephi) if f_cont[2]: anglez= numpy.asfortranarray(anglez) return (R,vR,vT,z,vz,phi,Omegar[0],Omegaphi[0],Omegaz[0],flag.value) def actionAngleTorus_Freqs_c(pot,jr,jphi,jz, tol=0.003): """ NAME: actionAngleTorus_Freqs_c PURPOSE: compute frequencies on a single torus INPUT: pot - Potential object or list thereof jr - radial action (scalar) jphi - azimuthal action (scalar) jz - vertical action (scalar) tol= (0.003) goal for |dJ|/|J| along the torus OUTPUT: (Omegar,Omegaphi,Omegaz,flag) HISTORY: 2015-08-05/07 - Written - Bovy (UofT) """ #Parse the potential npot, pot_type, pot_args= _parse_pot(pot,potfortorus=True) #Set up result Omegar= numpy.empty(1) Omegaphi= numpy.empty(1) Omegaz= numpy.empty(1) flag= ctypes.c_int(0) #Set up the C code ndarrayFlags= ('C_CONTIGUOUS','WRITEABLE') actionAngleTorus_FreqsFunc= _lib.actionAngleTorus_Freqs actionAngleTorus_FreqsFunc.argtypes=\ [ctypes.c_double, ctypes.c_double, ctypes.c_double, ctypes.c_int, ndpointer(dtype=numpy.int32,flags=ndarrayFlags), ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ctypes.c_double, ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ctypes.POINTER(ctypes.c_int)] #Array requirements Omegar= numpy.require(Omegar,dtype=numpy.float64,requirements=['C','W']) Omegaphi= numpy.require(Omegaphi,dtype=numpy.float64,requirements=['C','W']) Omegaz= numpy.require(Omegaz,dtype=numpy.float64,requirements=['C','W']) #Run the C code actionAngleTorus_FreqsFunc(ctypes.c_double(jr), ctypes.c_double(jphi), ctypes.c_double(jz), ctypes.c_int(npot), pot_type, pot_args, ctypes.c_double(tol), Omegar,Omegaphi,Omegaz, ctypes.byref(flag)) return (Omegar[0],Omegaphi[0],Omegaz[0],flag.value) def actionAngleTorus_hessian_c(pot,jr,jphi,jz, tol=0.003,dJ=0.001): """ NAME: actionAngleTorus_hessian_c PURPOSE: compute dO/dJ on a single torus INPUT: pot - Potential object or list thereof jr - radial action (scalar) jphi - azimuthal action (scalar) jz - vertical action (scalar) tol= (0.003) goal for |dJ|/|J| along the torus dJ= (0.001) action difference when computing derivatives (Hessian or Jacobian) OUTPUT: (dO/dJ,Omegar,Omegaphi,Omegaz,Autofit error flag) Note: dO/dJ is *not* symmetrized here HISTORY: 2016-07-15 - Written - Bovy (UofT) """ #Parse the potential npot, pot_type, pot_args= _parse_pot(pot,potfortorus=True) #Set up result dOdJT= numpy.empty(9) Omegar= numpy.empty(1) Omegaphi= numpy.empty(1) Omegaz= numpy.empty(1) flag= ctypes.c_int(0) #Set up the C code ndarrayFlags= ('C_CONTIGUOUS','WRITEABLE') actionAngleTorus_HessFunc= _lib.actionAngleTorus_hessianFreqs actionAngleTorus_HessFunc.argtypes=\ [ctypes.c_double, ctypes.c_double, ctypes.c_double, ctypes.c_int, ndpointer(dtype=numpy.int32,flags=ndarrayFlags), ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ctypes.c_double, ctypes.c_double, ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ctypes.POINTER(ctypes.c_int)] #Array requirements dOdJT= numpy.require(dOdJT,dtype=numpy.float64,requirements=['C','W']) Omegar= numpy.require(Omegar,dtype=numpy.float64,requirements=['C','W']) Omegaphi= numpy.require(Omegaphi,dtype=numpy.float64,requirements=['C','W']) Omegaz= numpy.require(Omegaz,dtype=numpy.float64,requirements=['C','W']) #Run the C code actionAngleTorus_HessFunc(ctypes.c_double(jr), ctypes.c_double(jphi), ctypes.c_double(jz), ctypes.c_int(npot), pot_type, pot_args, ctypes.c_double(tol), ctypes.c_double(dJ), dOdJT, Omegar,Omegaphi,Omegaz, ctypes.byref(flag)) return (dOdJT.reshape((3,3)).T,Omegar[0],Omegaphi[0],Omegaz[0],flag.value) def actionAngleTorus_jacobian_c(pot,jr,jphi,jz,angler,anglephi,anglez, tol=0.003,dJ=0.001): """ NAME: actionAngleTorus_jacobian_c PURPOSE: compute d(x,v)/d(J,theta) on a single torus, also compute dO/dJ and the frequencies INPUT: pot - Potential object or list thereof jr - radial action (scalar) jphi - azimuthal action (scalar) jz - vertical action (scalar) angler - radial angle (array [N]) anglephi - azimuthal angle (array [N]) anglez - vertical angle (array [N]) tol= (0.003) goal for |dJ|/|J| along the torus dJ= (0.001) action difference when computing derivatives (Hessian or Jacobian) OUTPUT: (d[R,vR,vT,z,vz,phi]/d[J,theta], Omegar,Omegaphi,Omegaz, Autofit error message) Note: dO/dJ is *not* symmetrized here HISTORY: 2016-07-19 - Written - Bovy (UofT) """ #Parse the potential npot, pot_type, pot_args= _parse_pot(pot,potfortorus=True) #Set up result R= numpy.empty(len(angler)) vR= numpy.empty(len(angler)) vT= numpy.empty(len(angler)) z= numpy.empty(len(angler)) vz= numpy.empty(len(angler)) phi= numpy.empty(len(angler)) dxvOdJaT= numpy.empty(36*len(angler)) dOdJT= numpy.empty(9) Omegar= numpy.empty(1) Omegaphi= numpy.empty(1) Omegaz= numpy.empty(1) flag= ctypes.c_int(0) #Set up the C code ndarrayFlags= ('C_CONTIGUOUS','WRITEABLE') actionAngleTorus_JacFunc= _lib.actionAngleTorus_jacobianFreqs actionAngleTorus_JacFunc.argtypes=\ [ctypes.c_double, ctypes.c_double, ctypes.c_double, ctypes.c_int, ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ctypes.c_int, ndpointer(dtype=numpy.int32,flags=ndarrayFlags), ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ctypes.c_double, ctypes.c_double, ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ctypes.POINTER(ctypes.c_int)] #Array requirements, first store old order f_cont= [angler.flags['F_CONTIGUOUS'], anglephi.flags['F_CONTIGUOUS'], anglez.flags['F_CONTIGUOUS']] angler= numpy.require(angler,dtype=numpy.float64,requirements=['C','W']) anglephi= numpy.require(anglephi,dtype=numpy.float64,requirements=['C','W']) anglez= numpy.require(anglez,dtype=numpy.float64,requirements=['C','W']) R= numpy.require(R,dtype=numpy.float64,requirements=['C','W']) vR= numpy.require(vR,dtype=numpy.float64,requirements=['C','W']) vT= numpy.require(vT,dtype=numpy.float64,requirements=['C','W']) z= numpy.require(z,dtype=numpy.float64,requirements=['C','W']) vz= numpy.require(vz,dtype=numpy.float64,requirements=['C','W']) phi= numpy.require(phi,dtype=numpy.float64,requirements=['C','W']) dxvOdJaT= numpy.require(dxvOdJaT, dtype=numpy.float64,requirements=['C','W']) dOdJT= numpy.require(dOdJT,dtype=numpy.float64,requirements=['C','W']) Omegar= numpy.require(Omegar,dtype=numpy.float64,requirements=['C','W']) Omegaphi= numpy.require(Omegaphi,dtype=numpy.float64,requirements=['C','W']) Omegaz= numpy.require(Omegaz,dtype=numpy.float64,requirements=['C','W']) #Run the C code actionAngleTorus_JacFunc(ctypes.c_double(jr), ctypes.c_double(jphi), ctypes.c_double(jz), ctypes.c_int(len(angler)), angler, anglephi, anglez, ctypes.c_int(npot), pot_type, pot_args, ctypes.c_double(tol), ctypes.c_double(dJ), R,vR,vT,z,vz,phi, dxvOdJaT, dOdJT, Omegar,Omegaphi,Omegaz, ctypes.byref(flag)) #Reset input arrays if f_cont[0]: angler= numpy.asfortranarray(angler) if f_cont[1]: anglephi= numpy.asfortranarray(anglephi) if f_cont[2]: anglez= numpy.asfortranarray(anglez) dxvOdJaT= numpy.reshape(dxvOdJaT,(len(angler),6,6),order='C') dxvOdJa= numpy.swapaxes(dxvOdJaT,1,2) return (R,vR,vT,z,vz,phi, dxvOdJa, dOdJT.reshape((3,3)).T, Omegar[0],Omegaphi[0],Omegaz[0], flag.value) galpy-1.3.0/galpy/actionAngle_src/actionAngleTorus_c_ext/0000755000076500000240000000000013236420072023602 5ustar bovystaff00000000000000galpy-1.3.0/galpy/actionAngle_src/actionAngleTorus_c_ext/actionAngleTorus.cc0000644000076500000240000001650613236350147027407 0ustar bovystaff00000000000000/* C code wrapper around Dehnen/McMillan Torus code for actionAngle calculations */ #include #include #include #include #include #include #include #include #include "Torus.h" #include "interp_2d.h" #include "galpyPot.h" #include #include #include extern "C" { // Clean up function inline void cleanup(Torus * T,Potential * Phi, int npot,struct potentialArg * actionAngleArgs) { delete Phi; delete T; free_potentialArgs(npot,actionAngleArgs); free(actionAngleArgs); } // Calculate frequencies void actionAngleTorus_Freqs(double jr, double jphi, double jz, int npot, int * pot_type, double * pot_args, double tol, double * Omegar,double * Omegaphi,double * Omegaz, int * flag) { // set up Torus Torus *T; T= new(std::nothrow) Torus; // set up potential Potential *Phi; //Phi = new(std::nothrow) LogPotential(1.,0.8,0.,0.); struct potentialArg * actionAngleArgs= (struct potentialArg *) malloc ( npot * sizeof (struct potentialArg) ); parse_actionAngleArgs(npot,actionAngleArgs,&pot_type,&pot_args,true); Phi = new(std::nothrow) galpyPotential(npot,actionAngleArgs); // Load actions and fit Torus Actions J; J[0]= jr; J[1]= jz; J[2]= jphi; *flag = T->AutoFit(J,Phi,tol); Phi->set_Lz(J(2)); // Grab the frequencies Frequencies om=T->omega(); *Omegar= om(0); *Omegaz= om(1); *Omegaphi= om(2); // Clean up cleanup(T,Phi,npot,actionAngleArgs); } // Calculate (x,v) for angles on a single torus; also returns the frequencies void actionAngleTorus_xvFreqs(double jr, double jphi, double jz, int na, double * angler, double * anglephi, double * anglez, int npot, int * pot_type, double * pot_args, double tol, double * R, double * vR, double * vT, double * z, double * vz, double * phi, double * Omegar,double * Omegaphi,double * Omegaz, int * flag) { // set up Torus Torus *T; T= new(std::nothrow) Torus; // set up potential Potential *Phi; //Phi = new(std::nothrow) LogPotential(1.,0.8,0.,0.); struct potentialArg * actionAngleArgs= (struct potentialArg *) malloc ( npot * sizeof (struct potentialArg) ); parse_actionAngleArgs(npot,actionAngleArgs,&pot_type,&pot_args,true); Phi = new(std::nothrow) galpyPotential(npot,actionAngleArgs); // Load actions and fit Torus Actions J; J[0]= jr; J[1]= jz; J[2]= jphi; *flag = T->AutoFit(J,Phi,tol); Phi->set_Lz(J(2)); // Load angles and get (x,v) Angles A; PSPT Q; int ii; for (ii=0; ii < na; ii++) { // Load angles A[0]= *(angler+ii); A[1]= *(anglez+ii); A[2]= *(anglephi+ii); // get phase-space point Q= T->Map3D(A); *(R+ii)= Q(0); *(z+ii)= Q(1); *(phi+ii)= Q(2); *(vR+ii)= Q(3); *(vz+ii)= Q(4); *(vT+ii)= Q(5); } // Finally, grab the frequencies Frequencies om=T->omega(); *Omegar= om(0); *Omegaz= om(1); *Omegaphi= om(2); // Clean up cleanup(T,Phi,npot,actionAngleArgs); } // Calculate Hessian and frequencies void actionAngleTorus_hessianFreqs(double jr, double jphi, double jz, int npot, int * pot_type, double * pot_args, double tol, double indJ, double * dOdJT, double * Omegar, double * Omegaphi, double * Omegaz, int * flag) { int ii,jj; double dJ; // set up Torus Torus *T; T= new(std::nothrow) Torus; // set up potential Potential *Phi; //Phi = new(std::nothrow) LogPotential(1.,0.8,0.,0.); struct potentialArg * actionAngleArgs= (struct potentialArg *) malloc ( npot * sizeof (struct potentialArg) ); parse_actionAngleArgs(npot,actionAngleArgs,&pot_type,&pot_args,true); Phi = new(std::nothrow) galpyPotential(npot,actionAngleArgs); // Load actions and fit Torus Actions J,JdJ; Frequencies om, omdom; J[0]= jr; J[1]= jz; J[2]= jphi; *flag = T->AutoFit(J,Phi,tol); Phi->set_Lz(J(2)); // Grab the frequencies om=T->omega(); *Omegar= om(0); *Omegaz= om(1); *Omegaphi= om(2); // Now compute the Jacobian for (ii=0;ii < 3; ii++){ JdJ= J; dJ= J[ii]+indJ; dJ= dJ-J[ii]; JdJ[ii]= J[ii]+dJ; T->AutoFit(JdJ,Phi,tol); Phi->set_Lz(JdJ(2)); omdom=T->omega(); for (jj=0;jj<3;jj++) *(dOdJT+ii*3+jj)= (omdom(jj)-om(jj)) / dJ; } // Clean up cleanup(T,Phi,npot,actionAngleArgs); } // Calculate Jacobian and frequencies void actionAngleTorus_jacobianFreqs(double jr,double jphi, double jz,int na,double * angler, double * anglephi, double * anglez, int npot, int * pot_type, double * pot_args, double tol, double indJ, double * R, double * vR, double * vT, double * z, double * vz, double * phi, double * dxvOdJaT, double * dOdJT, double * Omegar, double * Omegaphi, double * Omegaz, int * flag) { int ii,jj,kk; double dJ, dA; // set up Torus Torus *T; T= new(std::nothrow) Torus; // set up potential Potential *Phi; struct potentialArg * actionAngleArgs= (struct potentialArg *) malloc ( npot * sizeof (struct potentialArg) ); parse_actionAngleArgs(npot,actionAngleArgs,&pot_type,&pot_args,true); Phi = new(std::nothrow) galpyPotential(npot,actionAngleArgs); // Load actions and fit Torus Actions J,JdJ; Frequencies om, omdom; Angles A, AdA; PSPT Q, QdQ; PSPT * Qs= (PSPT *) malloc ( na * sizeof ( PSPT ) ); J[0]= jr; J[1]= jz; J[2]= jphi; *flag = T->AutoFit(J,Phi,tol); Phi->set_Lz(J(2)); // Grab the frequencies om=T->omega(); *Omegar= om(0); *Omegaz= om(1); *Omegaphi= om(2); // Now compute the Jacobian: dangle changes for (ii=0;ii < na;ii++){ // Load angles and get phase-space point A[0]= *(angler+ii); A[1]= *(anglez+ii); A[2]= *(anglephi+ii); Q= T->Map3D(A); *(Qs+ii)= Q; // store for dJ calc. below *(R+ii)= Q(0); // output x,v *(z+ii)= Q(1); *(phi+ii)= Q(2); *(vR+ii)= Q(3); *(vz+ii)= Q(4); *(vT+ii)= Q(5); for (jj=0;jj < 3;jj++){ // Setup dangle AdA= A; dA= A[jj]+1.e-8; dA= dA-A[jj]; AdA[jj]= A[jj]+dA; // get phase-space point QdQ= T->Map3D(AdA); for (kk=0;kk < 6;kk++) *(dxvOdJaT+ii*36+(jj+3)*6+kk)= (QdQ(kk)-Q(kk)) / dA; } } // Now compute the Jacobian: dJ changes for (jj=0;jj < 3; jj++){ // Setup dJ torus JdJ= J; dJ= J[jj]+indJ; dJ= dJ-J[jj]; JdJ[jj]= J[jj]+dJ; T->AutoFit(JdJ,Phi,tol); Phi->set_Lz(JdJ(2)); for (ii=0;ii < na;ii++){ // Load angles and get phase-space point A[0]= *(angler+ii); A[1]= *(anglez+ii); A[2]= *(anglephi+ii); QdQ= T->Map3D(A); for (kk=0;kk < 6;kk++) *(dxvOdJaT+ii*36+jj*6+kk)= (QdQ(kk)-(*(Qs+ii))(kk)) / dJ; } // and frequencies omdom=T->omega(); for (kk=0;kk<3;kk++) *(dOdJT+jj*3+kk)= (omdom(kk)-om(kk)) / dJ; } // Clean up free(Qs); cleanup(T,Phi,npot,actionAngleArgs); } } galpy-1.3.0/galpy/actionAngle_src/actionAngleTorus_c_ext/galpyPot.cc0000644000076500000240000000420713133144160025710 0ustar bovystaff00000000000000/***************************************************************************//**\file galpyPot.cc \brief Contains class galpyPotential. General interface to galpy potentials * * * galpyPot.cc * * * * C++ code written by Jo Bovy, 2015 * *******************************************************************************/ //#include #include #include // Implementation for completeness, some parts never touched by galpy's use of TM // LCOV_EXCL_START void galpyPotential::error(const char* msgs) const { cerr << " Error in class galpyPotential: " << msgs << '\n'; exit(1); } double galpyPotential::operator() (double R, double z) const { return evaluatePotentials(R,z,nargs,potentialArgs); } // LCOV_EXCL_STOP double galpyPotential::operator() (double R, double z, double& dPdR,double& dPdz) const { dPdR= -calcRforce(R,z,0.,0.,nargs,potentialArgs); dPdz= -calczforce(R,z,0.,0.,nargs,potentialArgs); return evaluatePotentials(R,z,nargs,potentialArgs); } // LCOV_EXCL_START double galpyPotential::LfromRc(const double R, double* dR) const { double dPR,dPz,P; P = (*this)(R,0.,dPR,dPz); return sqrt(R*R*R*dPR); } // LCOV_EXCL_STOP double galpyPotential::RfromLc(const double L_in, double* dR) const { bool more=false; double R,lR=0.,dlR=0.001,dPR,dPz,P,LcR,oldL,L=fabs(L_in); R=exp(lR); P= (*this)(R,0.,dPR,dPz); LcR=sqrt(R*R*R*dPR); if(LcR == L) return R; if(L>LcR) more=true; oldL=LcR; for( ; ; ) { lR += (more)? dlR : -dlR; R=exp(lR); P= (*this)(R,0.,dPR,dPz); LcR=sqrt(R*R*R*dPR); if(LcR == L) return R; if((L< LcR && L>oldL) ||(L>LcR && L /** \brief A general interface to galpy potentials in C++ */ // Class declaration class galpyPotential : public Potential { int nargs; struct potentialArg * potentialArgs; void error(const char*) const; public: galpyPotential(int,struct potentialArg *); double operator() (const double, const double) const; double operator() (const double, double&, double&) const;//?? double operator() (const double, const double, double&, double&) const; double operator() (const double, const double, double&, double&, double&, double&, double&) const;//?? double RfromLc(const double, double* = 0) const; double LfromRc(const double, double* = 0) const; Frequencies KapNuOm(const double) const; }; inline galpyPotential::galpyPotential(int na, struct potentialArg * inPotentialArgs) : nargs(na), potentialArgs(inPotentialArgs) { } #endif /* galpyPot.h */ galpy-1.3.0/galpy/actionAngle_src/actionAngleVertical.py0000644000076500000240000001463713003465421023441 0ustar bovystaff00000000000000############################################################################### # actionAngle: a Python module to calculate actions, angles, and frequencies # # class: actionAngleVertical # # methods: # Jz # anglez # Tz # calczmax # calcEz ############################################################################### import math as m import numpy as nu from scipy import optimize, integrate from galpy.actionAngle_src.actionAngle import * from galpy.potential_src.linearPotential import linearPotential, \ evaluatelinearPotentials class actionAngleVertical(actionAngle): """Action-angle formalism for vertical integral using the adiabatic approximation""" def __init__(self,*args,**kwargs): """ NAME: __init__ PURPOSE: initialize an actionAngleVertical object INPUT: Either: a) z,vz b) Orbit instance: initial condition used if that's it, orbit(t) if there is a time given as well pot= potential or list of potentials (planarPotentials) OUTPUT: HISTORY: 2012-06-01 - Written - Bovy (IAS) """ self._parse_eval_args(*args,_noOrbUnitsCheck=True,**kwargs) self._z= self._eval_z self._vz= self._eval_vz if not 'pot' in kwargs: #pragma: no cover raise IOError("Must specify pot= for actionAngleVertical") self._verticalpot= kwargs['pot'] return None def Jz(self,**kwargs): """ NAME: Jz PURPOSE: Calculate the vertical action INPUT: +scipy.integrate.quad keywords OUTPUT: J_z(z,vz)/ro/vc + estimate of the error HISTORY: 2012-06-01 - Written - Bovy (IAS) """ if hasattr(self,'_Jz'): return self._Jz zmax= self.calczmax() if zmax == -9999.99: return nu.array([9999.99,nu.nan]) Ez= calcEz(self._z,self._vz,self._verticalpot) self._Jz= 2.*integrate.quad(_JzIntegrand,0.,zmax, args=(Ez,self._verticalpot), **kwargs)[0]/nu.pi return self._Jz def Tz(self,**kwargs): #pragma: no cover """ NAME: Tz PURPOSE: Calculate the vertical period INPUT: +scipy.integrate.quad keywords OUTPUT: T_z(z,vz)*vc/ro + estimate of the error HISTORY: 2012-06-01 - Written - Bovy (IAS) """ if hasattr(self,'_Tz'): return self._Tz zmax= self.calczmax() Ez= calcEz(self._z,self._vz,self._verticalpot) self._Tz= 4.*integrate.quad(_TzIntegrand,0.,zmax, args=(Ez,self._verticalpot), **kwargs)[0] return self._Tz def anglez(self,**kwargs): #pragma: no cover """ NAME: anglez PURPOSE: Calculate the vertical angle INPUT: +scipy.integrate.quad keywords OUTPUT: angle_z(z,vz)*vc/ro + estimate of the error HISTORY: 2012-06-01 - Written - Bovy (IAS) """ if hasattr(self,'_anglez'): return self._anglez zmax= self.calczmax() Ez= calcEz(self._z,self._vz,self._verticalpot) Tz= self.Tz(**kwargs) self._anglez= 2.*nu.pi*(nu.array(integrate.quad(_TzIntegrand,0.,nu.fabs(self._z), args=(Ez,self._verticalpot), **kwargs)))/Tz[0] if self._z >= 0. and self._vz >= 0.: pass elif self._z >= 0. and self._vz < 0.: self._anglez[0]= nu.pi-self._anglez[0] elif self._z < 0. and self._vz <= 0.: self._anglez[0]= nu.pi+self._anglez[0] else: self._anglez[0]= 2.*nu.pi-self._anglez[0] return self._anglez def calczmax(self): """ NAME: calczmax PURPOSE: calculate the maximum height INPUT: OUTPUT: zmax HISTORY: 2012-06-01 - Written - Bovy (IAS) """ if hasattr(self,'_zmax'): #pragma: no cover return self._zmax Ez= calcEz(self._z,self._vz,self._verticalpot) if self._vz == 0.: #We are exactly at the maximum height zmax= nu.fabs(self._z) else: zstart= self._z try: zend= _zmaxFindStart(self._z,Ez,self._verticalpot) except OverflowError: #pragma: no cover zmax= -9999.99 else: zmax= optimize.brentq(_zmaxEq,zstart,zend, (Ez,self._verticalpot)) self._zmax= zmax return self._zmax def _zmaxEq(z,Ez,pot): """The vz=0 equation that needs to be solved to find zmax""" return Ez-potentialVertical(z,pot) def calcEz(z,vz,pot): """ NAME: calcEz PURPOSE: calculate the vertical energy INPUT: z - height (/ro) vz - vertical part of the velocity (/vc) pot - potential OUTPUT: Ez HISTORY: 2012-06-01 - Written - Bovy (IAS) """ return potentialVertical(z,pot)+vz**2./2. def potentialVertical(z,pot): """ NAME: potentialVertical PURPOSE: return the potential INPUT: z - height (/ro) pot - potential OUTPUT: Phi_z(z) HISTORY: 2012-06-01 - Written - Bovy (IAS) """ return evaluatelinearPotentials(pot,z,use_physical=False) def _JzIntegrand(z,Ez,pot): """The J_z integrand""" return nu.sqrt(2.*(Ez-potentialVertical(z,pot))) def _TzIntegrand(z,Ez,pot): #pragma: no cover """The T_z integrand""" return 1./_JzIntegrand(z,Ez,pot) def _zmaxFindStart(z,Ez,pot): """ NAME: _zmaxFindStart PURPOSE: Find adequate end point to solve for zmax INPUT: z - height Ez - vertical energy pot - potential OUTPUT: zend HISTORY: 2012-06-01 - Written - Bovy (IAS) """ if z == 0.: ztry= 0.00001 else: ztry= 2.*nu.fabs(z) while (Ez-potentialVertical(ztry,pot)) > 0.: ztry*= 2. if ztry > 100.: #pragma: no cover raise OverflowError return ztry galpy-1.3.0/galpy/df.py0000644000076500000240000000273313157607401015021 0ustar bovystaff00000000000000from galpy.df_src import diskdf from galpy.df_src import surfaceSigmaProfile from galpy.df_src import evolveddiskdf from galpy.df_src import quasiisothermaldf from galpy.df_src import streamdf from galpy.df_src import streamgapdf # # Functions # impulse_deltav_plummer= streamgapdf.impulse_deltav_plummer impulse_deltav_plummer_curvedstream= streamgapdf.impulse_deltav_plummer_curvedstream impulse_deltav_hernquist= streamgapdf.impulse_deltav_hernquist impulse_deltav_hernquist_curvedstream= streamgapdf.impulse_deltav_hernquist_curvedstream impulse_deltav_general= streamgapdf.impulse_deltav_general impulse_deltav_general_curvedstream= streamgapdf.impulse_deltav_general_curvedstream impulse_deltav_general_orbitintegration= streamgapdf.impulse_deltav_general_orbitintegration impulse_deltav_general_fullplummerintegration= streamgapdf.impulse_deltav_general_fullplummerintegration impulse_deltav_plummerstream= streamgapdf.impulse_deltav_plummerstream impulse_deltav_plummerstream_curvedstream= streamgapdf.impulse_deltav_plummerstream_curvedstream # # Classes # shudf= diskdf.shudf dehnendf= diskdf.dehnendf schwarzschilddf= diskdf.schwarzschilddf DFcorrection= diskdf.DFcorrection diskdf= diskdf.diskdf evolveddiskdf= evolveddiskdf.evolveddiskdf expSurfaceSigmaProfile= surfaceSigmaProfile.expSurfaceSigmaProfile surfaceSigmaProfile= surfaceSigmaProfile.surfaceSigmaProfile quasiisothermaldf= quasiisothermaldf.quasiisothermaldf streamdf= streamdf.streamdf streamgapdf= streamgapdf.streamgapdf galpy-1.3.0/galpy/df_src/0000755000076500000240000000000013236420072015304 5ustar bovystaff00000000000000galpy-1.3.0/galpy/df_src/__init__.py0000644000076500000240000000000012671130470017405 0ustar bovystaff00000000000000galpy-1.3.0/galpy/df_src/data/0000755000076500000240000000000013236420072016215 5ustar bovystaff00000000000000././@LongLink0000000000000000000000000000017100000000000011214 Lustar 00000000000000galpy-1.3.0/galpy/df_src/data/dfcorrection_dehnendf_expSurfaceSigmaProfile_0.2500_0.7500_0.0125_0.0000_151_5.0000_20.savgalpy-1.3.0/galpy/df_src/data/dfcorrection_dehnendf_expSurfaceSigmaProfile_0.2500_0.7500_0.0125_0.000000644000076500000240000001647512671130470032041 0ustar bovystaff00000000000000(lp1 (lp2 F0.99807830382513363 aF0.99665116796745967 aa(lp3 F0.99879250968562705 aF0.99785671025953526 aa(lp4 F0.99935596126860826 aF0.99884759448410143 aa(lp5 F0.99979224526395516 aF0.99965445897334237 aa(lp6 F1.0001219677777138 aF1.0003042536926749 aa(lp7 F1.0003630399574011 aF1.000820584083457 aa(lp8 F1.0005309498169943 aF1.0012240539775163 aa(lp9 F1.0006390274365282 aF1.0015325842166944 aa(lp10 F1.0006986896904693 aF1.0017617091509132 aa(lp11 F1.0007196678858152 aF1.0019248493297441 aa(lp12 F1.0007102149397071 aF1.0020335599088974 aa(lp13 F1.0006772930875432 aF1.0020977555141428 aa(lp14 F1.0006267423274577 aF1.0021259124984956 aa(lp15 F1.0005634307332654 aF1.0021252499764948 aa(lp16 F1.0004913878079971 aF1.0021018911449109 aa(lp17 F1.000413922243268 aF1.0020610064935096 aa(lp18 F1.0003337254673854 aF1.0020069405131562 aa(lp19 F1.0002529623598733 aF1.0019433234672401 aa(lp20 F1.0001733504481998 aF1.0018731697214558 aa(lp21 F1.0000962288339512 aF1.0017989640331169 aa(lp22 F1.0000226180010365 aF1.0017227370984354 aa(lp23 F0.99995327156167269 aF1.0016461315507041 aa(lp24 F0.9998887209030195 aF1.001570459493264 aa(lp25 F0.9998293135969164 aF1.0014967525481018 aa(lp26 F0.99977524635017812 aF1.001425805305163 aa(lp27 F0.9997265931855065 aF1.0013582129598753 aa(lp28 F0.99968332946385574 aF1.0012944038484879 aa(lp29 F0.99964535229603657 aF1.0012346675068755 aa(lp30 F0.99961249781433481 aF1.0011791788108908 aa(lp31 F0.99958455573226523 aF1.0011280186929812 aa(lp32 F0.99956128155457957 aF1.0010811918693188 aa(lp33 F0.99954240676539474 aF1.001038641964219 aa(lp34 F0.99952764727628707 aF1.0010002643686866 aa(lp35 F0.99951671037927292 aF1.0009659171336085 aa(lp36 F0.99950930041812369 aF1.0009354301565765 aa(lp37 F0.99950512337338482 aF1.0009086128939155 aa(lp38 F0.99950389050990263 aF1.0008852607993697 aa(lp39 F0.9995053212408922 aF1.0008651606621732 aa(lp40 F0.99950914532560109 aF1.0008480950053136 aa(lp41 F0.99951510449773184 aF1.0008338456677126 aa(lp42 F0.99952295363917265 aF1.0008221967002484 aa(lp43 F0.99953246154124809 aF1.0008129366668279 aa(lp44 F0.99954341136482538 aF1.0008058604446539 aa(lp45 F0.99955560081517691 aF1.0008007705994402 aa(lp46 F0.99956884211483932 aF1.000797478403846 aa(lp47 F0.99958296179683603 aF1.0007958045524952 aa(lp48 F0.99959780035363055 aF1.0007955796313752 aa(lp49 F0.99961321180047891 aF1.0007966443733767 aa(lp50 F0.99962906313053312 aF1.0007988497508735 aa(lp51 F0.99964523372836533 aF1.000802056924307 aa(lp52 F0.99966161474516069 aF1.0008061370815877 aa(lp53 F0.99967810843442662 aF1.0008109711861035 aa(lp54 F0.99969462747699889 aF1.0008164496692291 aa(lp55 F0.99971109432914906 aF1.0008224720462733 aa(lp56 F0.9997274405298201 aF1.0008289465221269 aa(lp57 F0.99974360607012935 aF1.0008357895519202 aa(lp58 F0.99975953872801415 aF1.0008429253941979 aa(lp59 F0.99977519348487609 aF1.0008502856548018 aa(lp60 F0.99979053191717704 aF1.000857808823669 aa(lp61 F0.99980552162423353 aF1.0008654398327343 aa(lp62 F0.99982013570265049 aF1.0008731295905657 aa(lp63 F0.99983435227581119 aF1.0008808345785818 aa(lp64 F0.99984815393855664 aF1.0008885163971184 aa(lp65 F0.99986152738374756 aF1.0008961413951285 aa(lp66 F0.99987446293675897 aF1.0009036802601574 aa(lp67 F0.99988695422999263 aF1.0009111076722443 aa(lp68 F0.99989899773749646 aF1.000918401947573 aa(lp69 F0.99991059258461101 aF1.0009255447042877 aa(lp70 F0.99992174010073254 aF1.0009325205939403 aa(lp71 F0.99993244357863098 aF1.0009393169646901 aa(lp72 F0.99994270813935315 aF1.0009459236220171 aa(lp73 F0.99995254027699665 aF1.0009523325808696 aa(lp74 F0.99996194780687708 aF1.0009585378220593 aa(lp75 F0.99997093963100547 aF1.0009645351076866 aa(lp76 F0.99997952555579839 aF1.0009703217235453 aa(lp77 F0.99998771603391789 aF1.0009758963761004 aa(lp78 F0.99999552221455346 aF1.0009812589540603 aa(lp79 F1.0000029556761474 aF1.0009864104454482 aa(lp80 F1.0000100283835944 aF1.0009913527434635 aa(lp81 F1.0000167523912817 aF1.0009960885209104 aa(lp82 F1.0000231401161011 aF1.0010006211665912 aa(lp83 F1.0000292038782048 aF1.0010049546482784 aa(lp84 F1.0000349561331001 aF1.001009093394662 aa(lp85 F1.0000404091409685 aF1.0010130422222461 aa(lp86 F1.0000455752139064 aF1.0010168063386968 aa(lp87 F1.0000504663389451 aF1.001020391153377 aa(lp88 F1.0000550942850541 aF1.0010238022247986 aa(lp89 F1.00005947063654 aF1.0010270454197201 aa(lp90 F1.0000636072363149 aF1.0010301265163055 aa(lp91 F1.0000675143674558 aF1.0010330514673123 aa(lp92 F1.0000712034910961 aF1.0010358261526511 aa(lp93 F1.0000746844627499 aF1.0010384564322674 aa(lp94 F1.0000779677104075 aF1.0010409483669265 aa(lp95 F1.0000810630539572 aF1.0010433074810969 aa(lp96 F1.0000839797900745 aF1.0010455396003057 aa(lp97 F1.0000867269886544 aF1.0010476503669132 aa(lp98 F1.0000893136854505 aF1.0010496451245214 aa(lp99 F1.0000917480519855 aF1.0010515293061815 aa(lp100 F1.0000940381462913 aF1.0010533080932618 aa(lp101 F1.0000961909240824 aF1.0010549866361209 aa(lp102 F1.0000982153488209 aF1.001056569394998 aa(lp103 F1.000100117074592 aF1.0010580616513098 aa(lp104 F1.00010190406376 aF1.0010594675365947 aa(lp105 F1.0001035810967576 aF1.0010607915106786 aa(lp106 F1.0001051554249214 aF1.0010620380048647 aa(lp107 F1.0001066331299535 aF1.0010632112051281 aa(lp108 F1.000108019414705 aF1.0010643142883069 aa(lp109 F1.0001093180581713 aF1.0010653516906753 aa(lp110 F1.0001105365633158 aF1.0010663264856088 aa(lp111 F1.0001116793871991 aF1.0010672426455605 aa(lp112 F1.0001127486539829 aF1.0010681029007533 aa(lp113 F1.0001137504233313 aF1.0010689105189472 aa(lp114 F1.0001146889841834 aF1.0010696687475242 aa(lp115 F1.0001155680095066 aF1.0010703795537292 aa(lp116 F1.0001163894910108 aF1.0010710471742859 aa(lp117 F1.0001171591433686 aF1.0010716726825601 aa(lp118 F1.0001178774286081 aF1.0010722588952241 aa(lp119 F1.0001185506343324 aF1.0010728083681495 aa(lp120 F1.0001191803480745 aF1.0010733226837523 aa(lp121 F1.000119767138391 aF1.0010738049952996 aa(lp122 F1.0001203163284047 aF1.0010742557859649 aa(lp123 F1.0001208297633288 aF1.001074678756859 aa(lp124 F1.000121310219581 aF1.0010750741978447 aa(lp125 F1.000121761508926 aF1.0010754430893081 aa(lp126 F1.0001221730220644 aF1.0010757901922311 aa(lp127 F1.0001225624227918 aF1.001076113115994 aa(lp128 F1.0001229295696796 aF1.0010764160957455 aa(lp129 F1.0001232673001443 aF1.0010766975292151 aa(lp130 F1.0001235822778345 aF1.0010769618513145 aa(lp131 F1.0001238791988802 aF1.0010772091239908 aa(lp132 F1.0001241565231251 aF1.0010774383186325 aa(lp133 F1.0001244165804457 aF1.0010776532647419 aa(lp134 F1.0001246598626821 aF1.0010778544587162 aa(lp135 F1.0001248850414617 aF1.0010780416305025 aa(lp136 F1.0001250876186707 aF1.0010782171099926 aa(lp137 F1.0001252867297001 aF1.0010783795874003 aa(lp138 F1.0001254527992907 aF1.0010785324658358 aa(lp139 F1.0001256408804291 aF1.0010786755582719 aa(lp140 F1.0001257779484911 aF1.0010788060353104 aa(lp141 F1.0001259180912907 aF1.0010789285178829 aa(lp142 F1.0001260560511447 aF1.0010790455850036 aa(lp143 F1.0001262045540233 aF1.0010791560985852 aa(lp144 F1.0001263291205589 aF1.0010792510284021 aa(lp145 F1.0001264306321609 aF1.0010793484268623 aa(lp146 F1.0001265129477435 aF1.0010794336952156 aa(lp147 F1.0001266213985636 aF1.001079507815432 aa(lp148 F1.0001267152378297 aF1.0010795850540803 aa(lp149 F1.0001268173684306 aF1.0010796563934625 aa(lp150 F1.0001268524751608 aF1.0010797258444397 aa(lp151 F1.0001269178553707 aF1.0010797875439543 aa(lp152 F1.0001269834918931 aF1.0010798390051854 aa.././@LongLink0000000000000000000000000000017100000000000011214 Lustar 00000000000000galpy-1.3.0/galpy/df_src/data/dfcorrection_dehnendf_expSurfaceSigmaProfile_0.2500_0.7500_0.2000_0.0000_151_5.0000_20.savgalpy-1.3.0/galpy/df_src/data/dfcorrection_dehnendf_expSurfaceSigmaProfile_0.2500_0.7500_0.2000_0.000000644000076500000240000001673712671130470032034 0ustar bovystaff00000000000000(lp1 (lp2 F0.8698398204958413 aF0.61389563056799479 aa(lp3 F0.75359141114144645 aF0.34684196579658749 aa(lp4 F0.72849304523064373 aF0.39608170627468015 aa(lp5 F0.79344985710644023 aF0.48213217823515964 aa(lp6 F0.89494468126604632 aF0.61549681723892125 aa(lp7 F1.0138255581325004 aF0.76278112038330348 aa(lp8 F1.1056131527359223 aF0.92735440170208339 aa(lp9 F1.181793704481261 aF1.0790659564298939 aa(lp10 F1.2294526413402513 aF1.2104116896112114 aa(lp11 F1.2413457349825145 aF1.3246006805228809 aa(lp12 F1.2320840647833438 aF1.4114458603796829 aa(lp13 F1.2128355941695408 aF1.463312675030519 aa(lp14 F1.1859890611690467 aF1.4835565459953535 aa(lp15 F1.1521217001455462 aF1.4805486884322037 aa(lp16 F1.1132362365155073 aF1.4624250458811205 aa(lp17 F1.0729342037425353 aF1.4346415652150382 aa(lp18 F1.034751993331344 aF1.4005845794469496 aa(lp19 F1.0010581898565984 aF1.3626424422121777 aa(lp20 F0.97281719474540318 aF1.3228854212775567 aa(lp21 F0.949949712654929 aF1.283167615368173 aa(lp22 F0.93178773618980526 aF1.2450199920140583 aa(lp23 F0.9174302705698143 aF1.2095622558093293 aa(lp24 F0.90599920959592906 aF1.1774383305175045 aa(lp25 F0.89677158547017199 aF1.1488262892075605 aa(lp26 F0.88919282694464341 aF1.1235485765387974 aa(lp27 F0.88284523246554392 aF1.101210039316594 aa(lp28 F0.87740856819662083 aF1.0813372684990339 aa(lp29 F0.87263808301877477 aF1.0634881455530605 aa(lp30 F0.86836779658803653 aF1.0473193771350793 aa(lp31 F0.8645248818977217 aF1.0326118104445128 aa(lp32 F0.86113880607351456 aF1.0192641586832321 aa(lp33 F0.85833313548593171 aF1.007262891868576 aa(lp34 F0.8562953728504733 aF0.99663773230650532 aa(lp35 F0.85522583676825292 aF0.987414896268071 aa(lp36 F0.85527529403785352 aF0.97957783418299504 aa(lp37 F0.85648696430430449 aF0.97304259377801494 aa(lp38 F0.85875871720618036 aF0.96765119091617569 aa(lp39 F0.8618374878834032 aF0.96318438634494885 aa(lp40 F0.86535185454213581 aF0.95939167216232124 aa(lp41 F0.86887980643709495 aF0.95603301842865174 aa(lp42 F0.87203890625447966 aF0.95292301134437907 aa(lp43 F0.87457716358486504 aF0.94996638077392037 aa(lp44 F0.87643896156374324 aF0.94717392445094895 aa(lp45 F0.87778380499874087 aF0.9446521136294449 aa(lp46 F0.8789477095629793 aF0.94256671517484947 aa(lp47 F0.88035403202458806 aF0.9410883417897925 aa(lp48 F0.88239604559914375 aF0.94033381801997529 aa(lp49 F0.8853230348511465 aF0.94031931287016046 aa(lp50 F0.8891615524960762 aF0.94094045442272234 aa(lp51 F0.89369741269622005 aF0.94198775039989546 aa(lp52 F0.89852885760716195 aF0.9431971548721475 aa(lp53 F0.90318198528470561 aF0.94432293155360758 aa(lp54 F0.90725667032193957 aF0.9452095597337028 aa(lp55 F0.91055406833939467 aF0.94583613637530839 aa(lp56 F0.91313668271404769 aF0.94631439380948268 aa(lp57 F0.91529417678278679 aF0.94683900569082913 aa(lp58 F0.91742589459342316 aF0.94760859101382533 aa(lp59 F0.91988618732943028 aF0.94874803772593652 aa(lp60 F0.9228543394204205 aF0.95026260162482556 aa(lp61 F0.92628044982481583 aF0.95204156104989801 aa(lp62 F0.92992715744380061 aF0.95390886083928472 aa(lp63 F0.93348625591868173 aF0.95569770428484946 aa(lp64 F0.93671542387196194 aF0.95731459670663543 aa(lp65 F0.93953064747757864 aF0.95876368957629543 aa(lp66 F0.94201375051697778 aF0.96012315303791729 aa(lp67 F0.94434043178793936 aF0.96149103142291381 aa(lp68 F0.94667605862626658 aF0.96293211637358678 aa(lp69 F0.9490980282575795 aF0.96445275365187433 aa(lp70 F0.9515806075503771 aF0.96601128760714317 aa(lp71 F0.95403747924249327 aF0.96755105301460953 aa(lp72 F0.95638547911784588 aF0.96903247909172718 aa(lp73 F0.95858800507984754 aF0.97044640608088506 aa(lp74 F0.96065832728663758 aF0.97180621214996099 aa(lp75 F0.96263297125503233 aF0.97313003337230519 aa(lp76 F0.96454136672677593 aF0.97442722655713832 aa(lp77 F0.96639209781594526 aF0.97569590474768819 aa(lp78 F0.96817812249075574 aF0.97692869452156816 aa(lp79 F0.96988967419216243 aF0.97811948395757597 aa(lp80 F0.97152282328579076 aF0.97926623148380121 aa(lp81 F0.97307989696646502 aF0.98036981129331335 aa(lp82 F0.97456551531660174 aF0.98143168370167766 aa(lp83 F0.97598334508829321 aF0.98245263512824543 aa(lp84 F0.97733549563693212 aF0.98343286675631281 aa(lp85 F0.9786234467218361 aF0.98437255434027082 aa(lp86 F0.97984887283723299 aF0.98527216112688487 aa(lp87 F0.98101379919832354 aF0.9861324157323702 aa(lp88 F0.98212040458855154 aF0.98695418332645302 aa(lp89 F0.98317085796807435 aF0.98773839101158156 aa(lp90 F0.98416728433693335 aF0.98848601498679922 aa(lp91 F0.98511178401007948 aF0.98919808332998949 aa(lp92 F0.98600644284187045 aF0.98987567086374129 aa(lp93 F0.98685332622171085 aF0.99051988788508871 aa(lp94 F0.98765446971121595 aF0.9911318690591896 aa(lp95 F0.98841187192786539 aF0.99171276431720834 aa(lp96 F0.98912748973277154 aF0.99226373128344714 aa(lp97 F0.98980323425341954 aF0.99278592854460002 aa(lp98 F0.99044096747346966 aF0.9932805097105678 aa(lp99 F0.99104249934448252 aF0.99374861817201998 aa(lp100 F0.99160958553788547 aF0.99419138258147799 aa(lp101 F0.9921439256118062 aF0.9946099129630932 aa(lp102 F0.99264716182332313 aF0.99500529740397481 aa(lp103 F0.9931208781487233 aF0.9953785992800932 aa(lp104 F0.99356659994863861 aF0.99573085497829794 aa(lp105 F0.99398579370349294 aF0.99606307201051925 aa(lp106 F0.99437986736030581 aF0.99637622758746691 aa(lp107 F0.99475017062531357 aF0.99667126745765555 aa(lp108 F0.99509799579077907 aF0.99694910514655732 aa(lp109 F0.99542457854192634 aF0.99721062139654226 aa(lp110 F0.99573109909103663 aF0.99745666391356469 aa(lp111 F0.99601868335793542 aF0.99768804727123539 aa(lp112 F0.9962884043230622 aF0.997905553052993 aa(lp113 F0.99654128350786186 aF0.99810993012370464 aa(lp114 F0.99677829244532712 aF0.99830189505017042 aa(lp115 F0.99700035432707468 aF0.99848213264248209 aa(lp116 F0.99720834556729621 aF0.99865129659991203 aa(lp117 F0.99740309756644119 aF0.99881001021258964 aa(lp118 F0.99758539828871606 aF0.9989588671887617 aa(lp119 F0.9977559940444346 aF0.99909843244184737 aa(lp120 F0.99791559112857209 aF0.99922924304450367 aa(lp121 F0.99806485747104112 aF0.99935180909119814 aa(lp122 F0.99820442438110357 aF0.99946661467494791 aa(lp123 F0.9983348880866465 aF0.99957411882338343 aa(lp124 F0.99845681141113618 aF0.99967475645851533 aa(lp125 F0.99857072522999701 aF0.99976893937709788 aa(lp126 F0.99867713005777403 aF0.99985705719390572 aa(lp127 F0.99877649763518173 aF0.99993947827902929 aa(lp128 F0.9988692721334268 aF1.0000165507136205 aa(lp129 F0.99895587169682198 aF1.000088603181323 aa(lp130 F0.99903668974419968 aF1.0001559458915517 aa(lp131 F0.99911209627274644 aF1.0002188714458999 aa(lp132 F0.99918243907595317 aF1.0002776556302699 aa(lp133 F0.99924804493594643 aF1.0003325583682174 aa(lp134 F0.99930922074516171 aF1.0003838243787353 aa(lp135 F0.99936625467646234 aF1.0004316840606331 aa(lp136 F0.99941941708674409 aF1.0004763541274151 aa(lp137 F0.99946896172016719 aF1.0005180384232668 aa(lp138 F0.99951512634282036 aF1.0005569285484028 aa(lp139 F0.99955813416449535 aF1.0005932045302193 aa(lp140 F0.99959819388466253 aF1.0006270354605615 aa(lp141 F0.99963550149129654 aF1.0006585801114334 aa(lp142 F0.99967024027437879 aF1.0006879874839953 aa(lp143 F0.9997025819220694 aF1.0007153973432237 aa(lp144 F0.99973268703422347 aF1.0007409407936279 aa(lp145 F0.99976070580410215 aF1.0007647407383669 aa(lp146 F0.99978677893134282 aF1.0007869123817328 aa(lp147 F0.99981103806432214 aF1.0008075635861691 aa(lp148 F0.99983360594528625 aF1.0008267955418098 aa(lp149 F0.99985459743010285 aF1.0008447028006702 aa(lp150 F0.99987411987263297 aF1.0008613740028254 aa(lp151 F0.99989227362365074 aF1.0008768919782502 aa(lp152 F0.99990915299906113 aF1.0008913346130446 aa.././@LongLink0000000000000000000000000000017100000000000011214 Lustar 00000000000000galpy-1.3.0/galpy/df_src/data/dfcorrection_dehnendf_expSurfaceSigmaProfile_0.3333_0.6667_0.0125_0.0000_151_5.0000_20.savgalpy-1.3.0/galpy/df_src/data/dfcorrection_dehnendf_expSurfaceSigmaProfile_0.3333_0.6667_0.0125_0.000000644000076500000240000001644112671130470032054 0ustar bovystaff00000000000000(lp1 (lp2 F0.99727009731943717 aF0.99490273120018602 aa(lp3 F0.99821246266699548 aF0.9966355477260419 aa(lp4 F0.99895435261721321 aF0.99803602489691889 aa(lp5 F0.99952866418136332 aF0.99915585734449264 aa(lp6 F0.99996394372701003 aF1.0000399914567288 aa(lp7 F1.0002847892979174 aF1.0007272781469003 aa(lp8 F1.0005122498647241 aF1.001251141810453 aa(lp9 F1.0006642302854702 aF1.0016402053258358 aa(lp10 F1.0007558684181024 aF1.0019188716916008 aa(lp11 F1.0007998888053022 aF1.0021078566164181 aa(lp12 F1.0008069238828619 aF1.0022246696058021 aa(lp13 F1.0007858035951727 aF1.0022840448000085 aa(lp14 F1.0007438129833126 aF1.0022983235593221 aa(lp15 F1.0006869195332837 aF1.0022777920589527 aa(lp16 F1.000619972272959 aF1.0022309775088027 aa(lp17 F1.0005468750878264 aF1.0021649068531766 aa(lp18 F1.0004707367845829 aF1.002085331771243 aa(lp19 F1.0003940004266985 aF1.0019969236601578 aa(lp20 F1.0003185543395561 aF1.0019034420536388 aa(lp21 F1.0002458270251873 aF1.0018078796594863 aa(lp22 F1.000176868029613 aF1.0017125869113135 aa(lp23 F1.0001124166070721 aF1.0016193786402614 aa(lp24 F1.0000529598256409 aF1.0015296251930041 aa(lp25 F0.99999878156883004 aF1.0014443300581455 aa(lp26 F0.9999500037136585 aF1.0013641958200279 aa(lp27 F0.99990662060331348 aF1.0012896800385145 aa(lp28 F0.99986852778700941 aF1.0012210424511734 aa(lp29 F0.9998355458736613 aF1.0011583847178673 aa(lp30 F0.99980744022774659 aF1.001101683768391 aa(lp31 F0.99978393713792701 aF1.0010508196740355 aa(lp32 F0.99976473700174617 aF1.0010055988403064 aa(lp33 F0.99974952499002812 aF1.0009657732119961 aa(lp34 F0.99973797959113153 aF1.0009310560841451 aa(lp35 F0.99972977937662633 aF1.0009011350350416 aa(lp36 F0.99972460828193199 aF1.000875682421519 aa(lp37 F0.99972215964377686 aF1.000854363816744 aa(lp38 F0.99972213921822983 aF1.0008368447176919 aa(lp39 F0.99972426734702313 aF1.0008227958003966 aa(lp40 F0.99972828043386663 aF1.0008118969640682 aa(lp41 F0.99973393185259096 aF1.0008038403641599 aa(lp42 F0.99974099240590097 aF1.0007983326163368 aa(lp43 F0.99974925041562579 aF1.0007950963097136 aa(lp44 F0.99975851152527229 aF1.000793870963673 aa(lp45 F0.99976859828825437 aF1.0007944135269102 aa(lp46 F0.99977934957644765 aF1.000796498519275 aa(lp47 F0.99979061988056472 aF1.0007999178786455 aa(lp48 F0.99980227850079662 aF1.0008044805866356 aa(lp49 F0.99981420870424531 aF1.0008100121244445 aa(lp50 F0.99982630683520879 aF1.0008163537991273 aa(lp51 F0.99983848140590392 aF1.0008233619751667 aa(lp52 F0.99985065222765124 aF1.0008309072566206 aa(lp53 F0.99986274949017717 aF1.0008388736191809 aa(lp54 F0.99987471293073926 aF1.0008471575321021 aa(lp55 F0.9998864910099774 aF1.0008556670860393 aa(lp56 F0.99989804010534256 aF1.0008643211171744 aa(lp57 F0.99990932378595521 aF1.0008730483605279 aa(lp58 F0.99992031207425236 aF1.000881786644958 aa(lp59 F0.99993098082362808 aF1.0008904820983866 aa(lp60 F0.99994131108316808 aF1.0008990884070166 aa(lp61 F0.99995128850372339 aF1.0009075661125511 aa(lp62 F0.99996090284031136 aF1.0009158819439969 aa(lp63 F0.99997014748744695 aF1.0009240082165121 aa(lp64 F0.99997901893598451 aF1.0009319222287147 aa(lp65 F0.99998751647399153 aF1.0009396057518709 aa(lp66 F0.99999564174240907 aF1.0009470445079669 aa(lp67 F1.0000033984463026 aF1.00095422775703 aa(lp68 F1.0000107920023367 aF1.000961147842494 aa(lp69 F1.000017829314833 aF1.0009677998206965 aa(lp70 F1.0000245184786889 aF1.0009741811324635 aa(lp71 F1.0000308685688974 aF1.0009802912416723 aa(lp72 F1.0000368895057763 aF1.0009861314104163 aa(lp73 F1.0000425918035811 aF1.0009917044078693 aa(lp74 F1.0000479864059242 aF1.0009970142477165 aa(lp75 F1.0000530846572131 aF1.0010020660864252 aa(lp76 F1.0000578981541361 aF1.0010068658873363 aa(lp77 F1.0000624383945471 aF1.0010114203872955 aa(lp78 F1.000066717077281 aF1.0010157368748835 aa(lp79 F1.000070745929849 aF1.0010198230802632 aa(lp80 F1.0000745363388712 aF1.0010236870895106 aa(lp81 F1.00007809954751 aF1.0010273372237197 aa(lp82 F1.0000814467601093 aF1.0010307818973236 aa(lp83 F1.0000845887592698 aF1.0010340296016005 aa(lp84 F1.000087536081568 aF1.0010370889688567 aa(lp85 F1.0000902988394555 aF1.001039968367591 aa(lp86 F1.0000928871897969 aF1.0010426761386815 aa(lp87 F1.0000953105631716 aF1.0010452206152973 aa(lp88 F1.0000975775605312 aF1.0010476098589638 aa(lp89 F1.0000996977003669 aF1.0010498516001241 aa(lp90 F1.0001016797145992 aF1.0010519537012448 aa(lp91 F1.0001035304314652 aF1.0010539233445539 aa(lp92 F1.0001052588607764 aF1.001055767947493 aa(lp93 F1.0001068713622321 aF1.0010574940371477 aa(lp94 F1.000108375406465 aF1.0010591086443341 aa(lp95 F1.0001097776237513 aF1.0010606178319961 aa(lp96 F1.0001110840599943 aF1.0010620279583722 aa(lp97 F1.0001123012751019 aF1.0010633444045336 aa(lp98 F1.000113434316823 aF1.0010645735407331 aa(lp99 F1.0001144896759413 aF1.001065719603476 aa(lp100 F1.0001154706412698 aF1.0010667885505558 aa(lp101 F1.0001163821194927 aF1.001067784551551 aa(lp102 F1.0001172307557509 aF1.0010687122738353 aa(lp103 F1.0001180186286589 aF1.0010695763186199 aa(lp104 F1.0001187520866293 aF1.0010703802540546 aa(lp105 F1.000119429919833 aF1.0010711284719123 aa(lp106 F1.0001200609551533 aF1.0010718239347192 aa(lp107 F1.0001206470177095 aF1.0010724710279686 aa(lp108 F1.0001211909822321 aF1.0010730720584657 aa(lp109 F1.0001216927953782 aF1.0010736308153416 aa(lp110 F1.0001221598156809 aF1.001074148592588 aa(lp111 F1.0001225940088079 aF1.0010746302082201 aa(lp112 F1.0001229939704932 aF1.0010750769124199 aa(lp113 F1.0001233651496197 aF1.0010754907584405 aa(lp114 F1.0001237087743902 aF1.0010758760720684 aa(lp115 F1.0001240302809846 aF1.0010762311205792 aa(lp116 F1.0001243231821995 aF1.0010765613511798 aa(lp117 F1.0001245971666166 aF1.0010768684680567 aa(lp118 F1.0001248451498344 aF1.0010771518473816 aa(lp119 F1.0001250802062824 aF1.0010774133479514 aa(lp120 F1.0001252983295161 aF1.0010776573989257 aa(lp121 F1.0001254961802482 aF1.0010778807607996 aa(lp122 F1.0001256763515138 aF1.0010780908687069 aa(lp123 F1.0001258495372241 aF1.0010782814628418 aa(lp124 F1.000126011160845 aF1.0010784589105852 aa(lp125 F1.0001261546931328 aF1.0010786246723764 aa(lp126 F1.0001262805872699 aF1.0010787761972417 aa(lp127 F1.000126399371174 aF1.0010789178333936 aa(lp128 F1.0001265267203192 aF1.0010790469653272 aa(lp129 F1.0001266291140563 aF1.00107916602785 aa(lp130 F1.0001267232006565 aF1.0010792748440658 aa(lp131 F1.000126810318809 aF1.0010793836184351 aa(lp132 F1.00012688966802 aF1.0010794765824331 aa(lp133 F1.0001269793139638 aF1.0010795637128982 aa(lp134 F1.000127054732048 aF1.0010796492871277 aa(lp135 F1.0001271227598034 aF1.0010797162312828 aa(lp136 F1.0001271707957811 aF1.001079785214537 aa(lp137 F1.0001272479400889 aF1.0010798452187009 aa(lp138 F1.0001272437839068 aF1.0010799064573543 aa(lp139 F1.0001273306328906 aF1.0010799565514132 aa(lp140 F1.0001273288189449 aF1.001080015719263 aa(lp141 F1.0001273555223427 aF1.001080064412011 aa(lp142 F1.0001273815600842 aF1.0010800941716886 aa(lp143 F1.0001274781750937 aF1.0010801548169583 aa(lp144 F1.0001275208218485 aF1.0010801758813366 aa(lp145 F1.000127537212202 aF1.0010802190499495 aa(lp146 F1.0001275445640641 aF1.0010802413473638 aa(lp147 F1.000127604738791 aF1.0010802482828494 aa(lp148 F1.0001276440812976 aF1.0010802936632186 aa(lp149 F1.0001277045356598 aF1.0010803280825422 aa(lp150 F1.0001275857369347 aF1.0010803396195218 aa(lp151 F1.0001276118896525 aF1.0010803502848042 aa(lp152 F1.0001275707536303 aF1.0010803866714342 aa.././@LongLink0000000000000000000000000000017100000000000011214 Lustar 00000000000000galpy-1.3.0/galpy/df_src/data/dfcorrection_dehnendf_expSurfaceSigmaProfile_0.3333_0.6667_0.2000_0.0000_151_5.0000_20.savgalpy-1.3.0/galpy/df_src/data/dfcorrection_dehnendf_expSurfaceSigmaProfile_0.3333_0.6667_0.2000_0.000000644000076500000240000001672112671130470032047 0ustar bovystaff00000000000000(lp1 (lp2 F0.8688590418257689 aF0.55778549189531212 aa(lp3 F0.66943380441103129 aF0.12284826849669657 aa(lp4 F0.58290489091702324 aF0.18187241858293712 aa(lp5 F0.65050161179680077 aF0.29330535277798186 aa(lp6 F0.78866181043084838 aF0.45936432596346044 aa(lp7 F0.96342508885332345 aF0.64856878326608169 aa(lp8 F1.0907238958886873 aF0.88003455204867254 aa(lp9 F1.1988528349277414 aF1.1043207718857919 aa(lp10 F1.2755138951623797 aF1.2968253452738454 aa(lp11 F1.303964520738534 aF1.4594631346822839 aa(lp12 F1.3007494906009569 aF1.5777599762295036 aa(lp13 F1.281859761132244 aF1.6412564263038232 aa(lp14 F1.2531523643117308 aF1.6549256309738198 aa(lp15 F1.2169283914028151 aF1.6316567792359653 aa(lp16 F1.1751064382217726 aF1.5857846834683442 aa(lp17 F1.1306068093335104 aF1.528811336290933 aa(lp18 F1.0870491409924439 aF1.4681741365882113 aa(lp19 F1.0476107928236424 aF1.4082153417884984 aa(lp20 F1.0143518591369065 aF1.3513713438083266 aa(lp21 F0.98811743349448766 aF1.2989591062524188 aa(lp22 F0.96876012339423057 aF1.2515836624218746 aa(lp23 F0.95539604533643052 aF1.209423367501594 aa(lp24 F0.94669297823045018 aF1.1723708750098085 aa(lp25 F0.94115791299862506 aF1.140090619338012 aa(lp26 F0.93737205708079097 aF1.1120738247625306 aa(lp27 F0.93416541745161796 aF1.0877085346469368 aa(lp28 F0.93071996303736115 aF1.0663745114697929 aa(lp29 F0.92661223838772577 aF1.0475302720827488 aa(lp30 F0.92180217316818591 aF1.0307737403696271 aa(lp31 F0.9165729088078709 aF1.0158630373304429 aa(lp32 F0.91143029667447439 aF1.0027009744106965 aa(lp33 F0.90697821464184458 aF0.99128893786581207 aa(lp34 F0.90378924121222404 aF0.9816611525081006 aa(lp35 F0.90228656968384879 aF0.97381496861539985 aa(lp36 F0.90265169331509432 aF0.96765440504951694 aa(lp37 F0.90477152391776683 aF0.96296098318425349 aa(lp38 F0.90823799269073135 aF0.95940113477001487 aa(lp39 F0.91240992852271907 aF0.95657202143932929 aa(lp40 F0.91653706731769924 aF0.95407724622568635 aa(lp41 F0.91992840777323015 aF0.95161389365445781 aa(lp42 F0.9221261839864312 aF0.94904457992459323 aa(lp43 F0.92303295425900245 aF0.9464290110706951 aa(lp44 F0.92294427216128549 aF0.94399937742179896 aa(lp45 F0.92246591415970203 aF0.94208211936970931 aa(lp46 F0.92233400236628948 aF0.94098685440580399 aa(lp47 F0.9231904433074537 aF0.94089541830554502 aa(lp48 F0.92538207226842528 aF0.94178606370917994 aa(lp49 F0.9288468973910412 aF0.94342075798113445 aa(lp50 F0.93312962984938397 aF0.94540702250133346 aa(lp51 F0.93753327082011706 aF0.94732116281427625 aa(lp52 F0.94136601606123749 aF0.94885169310715289 aa(lp53 F0.94419500274736079 aF0.94990467645036569 aa(lp54 F0.94599903452125078 aF0.95062163385616005 aa(lp55 F0.94714680361920178 aF0.95129883053012576 aa(lp56 F0.94820820972602016 aF0.95224457174020161 aa(lp57 F0.94968936010648697 aF0.95364129621719007 aa(lp58 F0.9518180255791473 aF0.95547510666204605 aa(lp59 F0.95447768408907818 aF0.95756216115168213 aa(lp60 F0.95731262521117366 aF0.95965367554438097 aa(lp61 F0.95993897596721778 aF0.96156110783212723 aa(lp62 F0.96214083119151683 aF0.96323282536263677 aa(lp63 F0.96394393897270847 aF0.96474495204512456 aa(lp64 F0.96554126222491909 aF0.96622417461480103 aa(lp65 F0.9671398046783658 aF0.96776046974896202 aa(lp66 F0.96883788926605685 aF0.96936441731067913 aa(lp67 F0.97060239307582263 aF0.97098466570364306 aa(lp68 F0.97233483598959725 aF0.9725584942967439 aa(lp69 F0.97395763864046214 aF0.97405283535553444 aa(lp70 F0.97545566513201454 aF0.97547114668567314 aa(lp71 F0.97685928132874777 aF0.97683337978183615 aa(lp72 F0.97820300334196097 aF0.97815360124815964 aa(lp73 F0.97950008154920964 aF0.97943249163420287 aa(lp74 F0.98074511313621926 aF0.98066356608808503 aa(lp75 F0.9819294223769478 aF0.98184173213417947 aa(lp76 F0.98305066238449945 aF0.98296629487805076 aa(lp77 F0.98411201282185201 aF0.98403902058252612 aa(lp78 F0.98511752541456465 aF0.98506164765544058 aa(lp79 F0.98606978499202924 aF0.98603519975177845 aa(lp80 F0.98697038793133285 aF0.98696049721912216 aa(lp81 F0.98782095196158826 aF0.98783860572264159 aa(lp82 F0.98862339975946045 aF0.98867085638091956 aa(lp83 F0.98937977654918019 aF0.98945870891083909 aa(lp84 F0.99009210020782179 aF0.99020367667351694 aa(lp85 F0.99076234347165748 aF0.9909073144805477 aa(lp86 F0.99139245070600446 aF0.99157121417424454 aa(lp87 F0.99198433832301425 aF0.99219699164745634 aa(lp88 F0.99253988577819063 aF0.99278627208605708 aa(lp89 F0.99306092808194379 aF0.99334067769234691 aa(lp90 F0.9935492507683108 aF0.99386181771077386 aa(lp91 F0.99400658606183789 aF0.99435127994619954 aa(lp92 F0.9944346097870862 aF0.99481062355156213 aa(lp93 F0.99483493889272789 aF0.99524137300561266 aa(lp94 F0.99520912976738296 aF0.99564501317551768 aa(lp95 F0.99555867705853873 aF0.99602298534582956 aa(lp96 F0.99588501309015731 aF0.99637668411178071 aa(lp97 F0.99618950775230608 aF0.99670745503650982 aa(lp98 F0.99647346875015219 aF0.99701659297881695 aa(lp99 F0.99673814228639779 aF0.99730534101071333 aa(lp100 F0.99698471396589461 aF0.99757488983961862 aa(lp101 F0.99721431002783667 aF0.997826377686721 aa(lp102 F0.99742799876808796 aF0.99806089052295621 aa(lp103 F0.99762679208864513 aF0.99827946264527967 aa(lp104 F0.99781164729812488 aF0.99848307750863052 aa(lp105 F0.99798346884964362 aF0.99867266878306016 aa(lp106 F0.99814311039579284 aF0.9988491216079014 aa(lp107 F0.99829137664207235 aF0.99901327396653794 aa(lp108 F0.99842902543086176 aF0.99916591820451828 aa(lp109 F0.9985567697179637 aF0.99930780260665364 aa(lp110 F0.99867527967215397 aF0.99943963305729633 aa(lp111 F0.99878518461840815 aF0.99956207473392078 aa(lp112 F0.99888707499914065 aF0.99967575382954277 aa(lp113 F0.99898150444460343 aF0.99978125925455363 aa(lp114 F0.99906899154313333 aF0.99987914437325198 aa(lp115 F0.9991500217848357 aF0.99996992870540335 aa(lp116 F0.99922504928637379 aF1.0000540995771618 aa(lp117 F0.99929449861179542 aF1.0001321137866246 aa(lp118 F0.9993587663616601 aF1.0002043991595988 aa(lp119 F0.99941822286338211 aF1.000271356179421 aa(lp120 F0.99947321363854369 aF1.0003333593754202 aa(lp121 F0.99952406090824197 aF1.0003907588696357 aa(lp122 F0.99957106502099702 aF1.0004438817079968 aa(lp123 F0.99961450578803024 aF1.0004930332484296 aa(lp124 F0.99965464375898216 aF1.0005384983789449 aa(lp125 F0.99969172135066153 aF1.0005805428102867 aa(lp126 F0.99972596404565672 aF1.0006194141613216 aa(lp127 F0.99975758172149753 aF1.0006553431721337 aa(lp128 F0.99978676923648802 aF1.0006885446382059 aa(lp129 F0.99981370760634969 aF1.0007192185059557 aa(lp130 F0.99983856507918956 aF1.0007475507987731 aa(lp131 F0.99986149783349498 aF1.0007737144293289 aa(lp132 F0.99988265058141856 aF1.0007978702424511 aa(lp133 F0.99990215788662051 aF1.000820167555001 aa(lp134 F0.99992014427090481 aF1.0008407451440615 aa(lp135 F0.99993672521267063 aF1.0008597318206378 aa(lp136 F0.9999520078485441 aF1.0008772470727745 aa(lp137 F0.99996609133197922 aF1.0008934019221136 aa(lp138 F0.99997906730755703 aF1.0009082990864318 aa(lp139 F0.99999102142294116 aF1.0009220339767941 aa(lp140 F1.0000020315298759 aF1.0009346950387101 aa(lp141 F1.0000121711395518 aF1.0009463640423359 aa(lp142 F1.0000215073834577 aF1.0009571169687543 aa(lp143 F1.0000301027831031 aF1.0009670240101216 aa(lp144 F1.0000380146468038 aF1.0009761501336367 aa(lp145 F1.0000452960900137 aF1.0009845556689527 aa(lp146 F1.0000519964175874 aF1.0009922960466437 aa(lp147 F1.0000581616220625 aF1.0009994228738013 aa(lp148 F1.0000638333516962 aF1.0010059837301 aa(lp149 F1.0000690501250422 aF1.0010120228975106 aa(lp150 F1.000073847443248 aF1.0010175805889392 aa(lp151 F1.0000782593385193 aF1.0010226949478027 aa(lp152 F1.0000823158507319 aF1.0010274002084016 aa.././@LongLink0000000000000000000000000000017100000000000011214 Lustar 00000000000000galpy-1.3.0/galpy/df_src/data/dfcorrection_dehnendf_expSurfaceSigmaProfile_0.3333_1.0000_0.0125_0.0000_151_5.0000_20.savgalpy-1.3.0/galpy/df_src/data/dfcorrection_dehnendf_expSurfaceSigmaProfile_0.3333_1.0000_0.0125_0.000000644000076500000240000001646612671130470032033 0ustar bovystaff00000000000000(lp1 (lp2 F0.9990743950728167 aF0.99880468037221271 aa(lp3 F0.99935807285038303 aF0.99928119890865896 aa(lp4 F0.99959597270678402 aF0.99969281812756849 aa(lp5 F0.99979360960209707 aF1.0000466679138915 aa(lp6 F0.99995594760501638 aF1.0003492022297855 aa(lp7 F1.000087445583314 aF1.0006062530463655 aa(lp8 F1.0001920997722569 aF1.0008230824508781 aa(lp9 F1.0002734843788637 aF1.0010044310067099 aa(lp10 F1.0003347891952021 aF1.0011545629292211 aa(lp11 F1.0003788547816843 aF1.0012773080858932 aa(lp12 F1.0004082050166614 aF1.0013761009054434 aa(lp13 F1.000425077196915 aF1.0014540163422774 aa(lp14 F1.0004314497520188 aF1.0015138030306727 aa(lp15 F1.0004290676990244 aF1.0015579137837352 aa(lp16 F1.0004194659664856 aF1.0015885335948713 aa(lp17 F1.0004039907097484 aF1.0016076052992113 aa(lp18 F1.0003838187549321 aF1.0016168530516127 aa(lp19 F1.000359975302709 aF1.0016178037759287 aa(lp20 F1.0003333500135978 aF1.0016118067313999 aa(lp21 F1.0003047116050514 aF1.0016000513399856 aa(lp22 F1.0002747210709848 aF1.0015835834077584 aa(lp23 F1.000243943641844 aF1.0015633198678788 aa(lp24 F1.0002128595843238 aF1.0015400621661958 aa(lp25 F1.0001818739432402 aF1.0015145083999091 aa(lp26 F1.0001513253161711 aF1.0014872643141399 aa(lp27 F1.0001214937455138 aF1.0014588532552264 aa(lp28 F1.0000926078084147 aF1.0014297251680222 aa(lp29 F1.0000648509759531 aF1.0014002647236757 aa(lp30 F1.0000383673114877 aF1.0013707986516669 aa(lp31 F1.0000132665662624 aF1.0013416023503501 aa(lp32 F0.99998962873178265 aF1.0013129058375967 aa(lp33 F0.99996750810119628 aF1.0012848991044991 aa(lp34 F0.99994693688275771 aF1.001257736925081 aa(lp35 F0.99992792841584388 aF1.0012315431723935 aa(lp36 F0.9999104800207903 aF1.0012064146887321 aa(lp37 F0.99989457552592642 aF1.001182424749143 aa(lp38 F0.99988018749818575 aF1.0011596261591591 aa(lp39 F0.99986727921311669 aF1.0011380540201156 aa(lp40 F0.99985580638448146 aF1.001117728194465 aa(lp41 F0.99984571868268879 aF1.0010986555010466 aa(lp42 F0.99983696106597952 aF1.0010808316642932 aa(lp43 F0.99982947493292251 aF1.0010642430453074 aa(lp44 F0.99982319913604623 aF1.0010488681724112 aa(lp45 F0.99981807083994412 aF1.0010346790925966 aa(lp46 F0.99981402628164839 aF1.0010216425657561 aa(lp47 F0.99981100140320756 aF1.0010097211098195 aa(lp48 F0.99980893240147994 aF1.0009988739212676 aa(lp49 F0.99980775618294637 aF1.0009890576754183 aa(lp50 F0.99980741075520674 aF1.000980227224828 aa(lp51 F0.99980783553725094 aF1.0009723362060476 aa(lp52 F0.9998089716317542 aF1.0009653375602683 aa(lp53 F0.9998107620179526 aF1.0009591839825094 aa(lp54 F0.99981315172235774 aF1.0009538283069359 aa(lp55 F0.99981608795337629 aF1.0009492238259972 aa(lp56 F0.99981952017026543 aF1.0009453245671214 aa(lp57 F0.99982340016633597 aF1.0009420855202564 aa(lp58 F0.9998276820707418 aF1.000939462817628 aa(lp59 F0.99983232240432407 aF1.0009374138938154 aa(lp60 F0.99983728003657468 aF1.0009358975948943 aa(lp61 F0.9998425161773995 aF1.0009348742784245 aa(lp62 F0.99984799434337768 aF1.0009343058733522 aa(lp63 F0.99985368030713329 aF1.00093415593917 aa(lp64 F0.99985954203860705 aF1.000934389673324 aa(lp65 F0.99986554964791485 aF1.0009349739515416 aa(lp66 F0.9998716753007082 aF1.0009358773061328 aa(lp67 F0.99987789318083808 aF1.0009370699170554 aa(lp68 F0.99988417935402807 aF1.0009385236096018 aa(lp69 F0.99989051174727317 aF1.0009402117962272 aa(lp70 F0.9998968700347296 aF1.0009421094608091 aa(lp71 F0.9999032355359162 aF1.0009441931180425 aa(lp72 F0.99990959120632139 aF1.0009464407545225 aa(lp73 F0.99991592147297603 aF1.0009488317857498 aa(lp74 F0.99992221219336941 aF1.0009513470074136 aa(lp75 F0.99992845058586211 aF1.0009539685384006 aa(lp76 F0.99993462514483777 aF1.0009566797652818 aa(lp77 F0.99994072552541935 aF1.0009594652912763 aa(lp78 F0.99994674252440263 aF1.0009623108697032 aa(lp79 F0.99995266800490357 aF1.0009652033654866 aa(lp80 F0.99995849478486465 aF1.000968130678983 aa(lp81 F0.99996421659807933 aF1.0009710817115514 aa(lp82 F0.99996982806955348 aF1.0009740462916248 aa(lp83 F0.99997532456121885 aF1.00097701514485 aa(lp84 F0.99998070221889346 aF1.0009799798211849 aa(lp85 F0.99998595779052168 aF1.0009829326682107 aa(lp86 F0.99999108877011189 aF1.0009858667517146 aa(lp87 F0.99999609316496396 aF1.0009887758611984 aa(lp88 F1.0000009694481511 aF1.000991654388989 aa(lp89 F1.0000057166398006 aF1.0009944973854952 aa(lp90 F1.0000103343865976 aF1.0009973004092967 aa(lp91 F1.0000148223144245 aF1.0010000595856374 aa(lp92 F1.0000191808380259 aF1.001002771511839 aa(lp93 F1.0000234104371752 aF1.0010054332367719 aa(lp94 F1.0000275120218223 aF1.0010080422307235 aa(lp95 F1.0000314867199025 aF1.0010105963888944 aa(lp96 F1.0000353359375895 aF1.0010130938715427 aa(lp97 F1.0000390612934089 aF1.0010155332696713 aa(lp98 F1.0000426645683618 aF1.0010179133983232 aa(lp99 F1.0000461477380387 aF1.0010202334368887 aa(lp100 F1.0000495128527902 aF1.0010224927432529 aa(lp101 F1.0000527620095618 aF1.0010246908962246 aa(lp102 F1.0000558979015555 aF1.0010268277489389 aa(lp103 F1.0000589226230696 aF1.0010289033018396 aa(lp104 F1.0000618389501996 aF1.0010309177686121 aa(lp105 F1.0000646490830631 aF1.0010328715041328 aa(lp106 F1.0000673559665465 aF1.0010347649121394 aa(lp107 F1.0000699621401841 aF1.0010365986718481 aa(lp108 F1.0000724702690171 aF1.0010383734957813 aa(lp109 F1.0000748828523898 aF1.001040090141361 aa(lp110 F1.0000772028559133 aF1.0010417495921695 aa(lp111 F1.0000794329486227 aF1.001043352773298 aa(lp112 F1.0000815755804513 aF1.0010449005908573 aa(lp113 F1.0000836335949117 aF1.0010463944177561 aa(lp114 F1.0000856095703832 aF1.0010478352659276 aa(lp115 F1.0000875062662791 aF1.0010492242516769 aa(lp116 F1.0000893257168224 aF1.0010505627058335 aa(lp117 F1.0000910712933413 aF1.001051851737339 aa(lp118 F1.0000927446568273 aF1.0010530927480579 aa(lp119 F1.0000943488799938 aF1.001054286948825 aa(lp120 F1.0000958863249361 aF1.0010554355700636 aa(lp121 F1.000097358861294 aF1.0010565399869951 aa(lp122 F1.0000987690176568 aF1.001057601441915 aa(lp123 F1.0001001191965475 aF1.0010586212618107 aa(lp124 F1.0001014119513434 aF1.0010596004960806 aa(lp125 F1.0001026489172968 aF1.0010605405405797 aa(lp126 F1.0001038314313191 aF1.0010614426386641 aa(lp127 F1.000104962956609 aF1.0010623082759893 aa(lp128 F1.0001060455187143 aF1.0010631383365485 aa(lp129 F1.0001070791988897 aF1.0010639341803096 aa(lp130 F1.0001080676277188 aF1.0010646967683292 aa(lp131 F1.0001090122372336 aF1.0010654277440323 aa(lp132 F1.0001099143548824 aF1.0010661277844481 aa(lp133 F1.0001107767448656 aF1.0010667976880618 aa(lp134 F1.0001115993772434 aF1.0010674394401669 aa(lp135 F1.0001123850607756 aF1.0010680534083465 aa(lp136 F1.0001131344336112 aF1.0010686408270966 aa(lp137 F1.0001138505461997 aF1.0010692026352292 aa(lp138 F1.0001145315836062 aF1.0010697398932693 aa(lp139 F1.00011518452254 aF1.0010702537554002 aa(lp140 F1.0001158034820439 aF1.0010707444614175 aa(lp141 F1.0001163952274192 aF1.0010712143235503 aa(lp142 F1.0001169596816692 aF1.0010716622969136 aa(lp143 F1.0001174988236314 aF1.0010720901495773 aa(lp144 F1.0001180120359334 aF1.0010724990495545 aa(lp145 F1.0001185002539059 aF1.0010728889071521 aa(lp146 F1.0001189641031643 aF1.0010732615300322 aa(lp147 F1.0001194083842913 aF1.0010736169732553 aa(lp148 F1.0001198317701256 aF1.001073956651366 aa(lp149 F1.0001202337344206 aF1.0010742797304999 aa(lp150 F1.0001206134681926 aF1.0010745887891965 aa(lp151 F1.0001209780439861 aF1.0010748825207756 aa(lp152 F1.0001213246786778 aF1.0010751634361053 aa.././@LongLink0000000000000000000000000000017100000000000011214 Lustar 00000000000000galpy-1.3.0/galpy/df_src/data/dfcorrection_dehnendf_expSurfaceSigmaProfile_0.3333_1.0000_0.1000_0.0000_151_5.0000_20.savgalpy-1.3.0/galpy/df_src/data/dfcorrection_dehnendf_expSurfaceSigmaProfile_0.3333_1.0000_0.1000_0.000000644000076500000240000001671712671130470032023 0ustar bovystaff00000000000000(lp1 (lp2 F0.93774745070605769 aF0.86272101934702372 aa(lp3 F0.95220322549967706 aF0.88794351458892307 aa(lp4 F0.96538946394015535 aF0.91110192104591703 aa(lp5 F0.97714270537124237 aF0.93206141077449056 aa(lp6 F0.98737343712633807 aF0.9507787383069034 aa(lp7 F0.99606265633282809 aF0.96727634810348639 aa(lp8 F1.0032483024573122 aF0.98162393953146154 aa(lp9 F1.0090093514414202 aF0.99392712168078368 aa(lp10 F1.0134539980586257 aF1.0043179239293325 aa(lp11 F1.0167087091485743 aF1.0129460659854896 aa(lp12 F1.0189092716732062 aF1.0199713901460354 aa(lp13 F1.0201936023805074 aF1.0255573033039376 aa(lp14 F1.0206961824804803 aF1.029865517013169 aa(lp15 F1.0205441148353034 aF1.0330520278102862 aa(lp16 F1.0198545790777067 aF1.0352642310075006 aa(lp17 F1.0187334041670075 aF1.0366390348194765 aa(lp18 F1.0172745047369245 aF1.0373017908485733 aa(lp19 F1.0155599627953784 aF1.0373658623611315 aa(lp20 F1.0136605389880997 aF1.0369326878822978 aa(lp21 F1.0116364670673033 aF1.0360921840915469 aa(lp22 F1.0095384049467255 aF1.0349233834456282 aa(lp23 F1.0074084440776507 aF1.0334952223656195 aa(lp24 F1.005281125602191 aF1.0318673986526881 aa(lp25 F1.0031844150235898 aF1.0300912564463789 aa(lp26 F1.0011406066948494 aF1.0282106575019183 aa(lp27 F0.99916714855943567 aF1.0262628159520468 aa(lp28 F0.99727737933701499 aF1.0242790804732125 aa(lp29 F0.9954811787984007 aF1.0222856555478592 aa(lp30 F0.99378553530491032 aF1.0203042570515781 aa(lp31 F0.99219503675566978 aF1.0183527030210291 aa(lp32 F0.99071229222412516 aF1.0164454409726869 aa(lp33 F0.98933829171591126 aF1.0145940154231354 aa(lp34 F0.98807271157660892 aF1.0128074804364906 aa(lp35 F0.98691417264544801 aF1.0110927615877519 aa(lp36 F0.98586045746392192 aF1.009454972323953 aa(lp37 F0.98490869227426225 aF1.0078976895475871 aa(lp38 F0.9840554990041962 aF1.0064231930424377 aa(lp39 F0.98329712178286721 aF1.0050326729148518 aa(lp40 F0.98262953190677493 aF1.0037264089270543 aa(lp41 F0.98204851461525577 aF1.002503925224701 aa(lp42 F0.98154974054465527 aF1.0013641235999466 aa(lp43 F0.98112882431074677 aF1.000305398058398 aa(lp44 F0.98078137229239049 aF0.99932573313854023 aa(lp45 F0.9805030213644873 aF0.99842278814168151 aa(lp46 F0.98028947005080358 aF0.99759396914154186 aa(lp47 F0.98013650333621039 aF0.99683649042479328 aa(lp48 F0.98004001218699022 aF0.99614742679349566 aa(lp49 F0.97999600867100611 aF0.9955237579674443 aa(lp50 F0.98000063741671084 aF0.99496240616892806 aa(lp51 F0.98005018403352995 aF0.99446026782524688 aa(lp52 F0.98014108101568209 aF0.99401424018762541 aa(lp53 F0.98026991155139931 aF0.99362124357002701 aa(lp54 F0.98043341160527464 aF0.99327823980384122 aa(lp55 F0.98062847058154801 aF0.99298224743483654 aa(lp56 F0.98085213079666211 aF0.99273035410965504 aa(lp57 F0.9811015860147162 aF0.99251972654145759 aa(lp58 F0.98137417918427239 aF0.99234761839177177 aa(lp59 F0.9816673995588403 aF0.99221137636395407 aa(lp60 F0.98197887930981698 aF0.99210844475173798 aa(lp61 F0.98230638974083373 aF0.99203636867694922 aa(lp62 F0.98264783720130544 aF0.99199279618983383 aa(lp63 F0.98300125875375166 aF0.99197547940141351 aa(lp64 F0.98336481767103179 aF0.99198227479479528 aa(lp65 F0.98373679881753995 aF0.99201114283815794 aa(lp66 F0.9841156039436737 aF0.99206014701073075 aa(lp67 F0.98449974695749243 aF0.9921274523216167 aa(lp68 F0.98488784916041072 aF0.99221132341990681 aa(lp69 F0.98527863452686493 aF0.99231012236432681 aa(lp70 F0.98567092501261966 aF0.99242230610990778 aa(lp71 F0.98606363591874813 aF0.99254642376752178 aa(lp72 F0.98645577134504292 aF0.99268111368485634 aa(lp73 F0.9868464197032879 aF0.99282510039149896 aa(lp74 F0.98723474935404176 aF0.99297719144510954 aa(lp75 F0.98762000434362762 aF0.99313627420424122 aa(lp76 F0.98800150024370847 aF0.99330131256525478 aa(lp77 F0.98837862012171895 aF0.99347134367277645 aa(lp78 F0.98875081063494685 aF0.99364547464183461 aa(lp79 F0.9891175782575542 aF0.99382287929463942 aa(lp80 F0.98947848561230323 aF0.99400279493290844 aa(lp81 F0.98983314797398536 aF0.99418451916454764 aa(lp82 F0.99018122987932689 aF0.99436740678346724 aa(lp83 F0.99052244187608363 aF0.99455086672274018 aa(lp84 F0.99085653741045632 aF0.99473435908357655 aa(lp85 F0.9911833098442957 aF0.99491739224317022 aa(lp86 F0.99150258960598014 aF0.99509952006519597 aa(lp87 F0.99181424147798558 aF0.99528033917708703 aa(lp88 F0.99211816197779912 aF0.99545948637231096 aa(lp89 F0.99241427693535011 aF0.9956366360886898 aa(lp90 F0.99270253912890138 aF0.99581149798828539 aa(lp91 F0.99298292603168037 aF0.99598381465649788 aa(lp92 F0.9932554378012225 aF0.99615335936604554 aa(lp93 F0.9935200951679426 aF0.9963199339719333 aa(lp94 F0.99377693767386133 aF0.99648336689146633 aa(lp95 F0.99402602183114031 aF0.99664351117523209 aa(lp96 F0.9942674194731228 aF0.99680024268829126 aa(lp97 F0.99450121622139998 aF0.99695345837142813 aa(lp98 F0.9947275099675188 aF0.99710307459833536 aa(lp99 F0.99494640955529112 aF0.99724902563009743 aa(lp100 F0.99515803344381792 aF0.99739126213311824 aa(lp101 F0.9953625085465615 aF0.9975297498161988 aa(lp102 F0.99555996912120581 aF0.99766446810201748 aa(lp103 F0.99575055568085402 aF0.99779540892946439 aa(lp104 F0.99593441410986883 aF0.99792257558007791 aa(lp105 F0.99611169468100003 aF0.99804598161389124 aa(lp106 F0.99628255133636701 aF0.99816564985680822 aa(lp107 F0.99644714082840857 aF0.99828161145379501 aa(lp108 F0.99660562209484582 aF0.99839390499170366 aa(lp109 F0.99675815556523384 aF0.99850257568360523 aa(lp110 F0.99690490263195819 aF0.99860767459365274 aa(lp111 F0.99704602506163942 aF0.99870925793994414 aa(lp112 F0.99718168451617684 aF0.99880738643355871 aa(lp113 F0.99731204216866309 aF0.99890212466872108 aa(lp114 F0.99743725821254536 aF0.99899354056354395 aa(lp115 F0.99755749159059415 aF0.999081704834756 aa(lp116 F0.99767289959731309 aF0.99916669053702201 aa(lp117 F0.99778363766789602 aF0.99924857260402333 aa(lp118 F0.99788985904895511 aF0.99932742746046155 aa(lp119 F0.9979917146353231 aF0.99940333264974635 aa(lp120 F0.99808935274584631 aF0.99947636650133653 aa(lp121 F0.99818291893363498 aF0.99954660782484661 aa(lp122 F0.99827255590137431 aF0.99961413562458901 aa(lp123 F0.99835840330587 aF0.99967902886955162 aa(lp124 F0.99844059772309335 aF0.99974136624458776 aa(lp125 F0.99851927249854144 aF0.9998012259641732 aa(lp126 F0.99859455771776373 aF0.99985868558095703 aa(lp127 F0.99866658018498844 aF0.99991382182852762 aa(lp128 F0.99873546333272933 aF0.99996671048013219 aa(lp129 F0.99880132718621828 aF1.0000174262103039 aa(lp130 F0.99886428843844 aF1.0000660425096228 aa(lp131 F0.99892446037764671 aF1.000112631552754 aa(lp132 F0.9989819528988243 aF1.0001572641574561 aa(lp133 F0.99903687258008123 aF1.000200009672054 aa(lp134 F0.99908932263276307 aF1.000240935951894 aa(lp135 F0.99913940299341142 aF1.0002801092848832 aa(lp136 F0.99918721031854218 aF1.0003175943684997 aa(lp137 F0.99923283813969788 aF1.0003534542846457 aa(lp138 F0.9992763766611078 aF1.0003877504670373 aa(lp139 F0.99931791322522945 aF1.0004205426570361 aa(lp140 F0.99935753185438414 aF1.0004518889597631 aa(lp141 F0.99939531383950875 aF1.0004818457890832 aa(lp142 F0.99943133740435797 aF1.0005104679012871 aa(lp143 F0.9994656779626867 aF1.0005378083695791 aa(lp144 F0.9994984081206526 aF1.0005639186232265 aa(lp145 F0.99952959778617068 aF1.000588848465888 aa(lp146 F0.99955931422629496 aF1.0006126460719647 aa(lp147 F0.99958762217826569 aF1.000635358020638 aa(lp148 F0.99961458381722379 aF1.0006570293003725 aa(lp149 F0.99964025885479368 aF1.0006777033948748 aa(lp150 F0.99966470475854186 aF1.0006974222922391 aa(lp151 F0.99968797640940121 aF1.0007162262801157 aa(lp152 F0.99971012806814086 aF1.0007341549505357 aa.././@LongLink0000000000000000000000000000017200000000000011215 Lustar 00000000000000galpy-1.3.0/galpy/df_src/data/dfcorrection_dehnendf_expSurfaceSigmaProfile_0.3333_1.0000_0.2000_-0.1000_151_5.0000_20.savgalpy-1.3.0/galpy/df_src/data/dfcorrection_dehnendf_expSurfaceSigmaProfile_0.3333_1.0000_0.2000_-0.10000644000076500000240000001677012671130470032021 0ustar bovystaff00000000000000(lp1 (lp2 F0.99677544871204127 aF0.99395736532893164 aa(lp3 F0.88646036087442226 aF0.73683434612162624 aa(lp4 F0.89962617516171006 aF0.73936115910065436 aa(lp5 F0.91463908106261449 aF0.76738809887890147 aa(lp6 F0.93775329648196493 aF0.80153419104566948 aa(lp7 F0.96047707659067005 aF0.84051483641812585 aa(lp8 F0.9868109875883011 aF0.88195710407597039 aa(lp9 F1.0124953837776709 aF0.92347345883050735 aa(lp10 F1.0348754784012875 aF0.96449059093064227 aa(lp11 F1.0544236847100423 aF1.002631613527019 aa(lp12 F1.0702884395962236 aF1.0366798637039389 aa(lp13 F1.0814451188079834 aF1.0665864581144047 aa(lp14 F1.0880458432529772 aF1.0921720659422012 aa(lp15 F1.0907968655790998 aF1.1131109881612418 aa(lp16 F1.0902776636131428 aF1.1293607827927454 aa(lp17 F1.0869196271175912 aF1.141207633329028 aa(lp18 F1.0811599584933678 aF1.149108500565347 aa(lp19 F1.0735006529741784 aF1.1535572089104498 aa(lp20 F1.0644728913619521 aF1.1550228785269476 aa(lp21 F1.0545705567028281 aF1.1539444268440759 aa(lp22 F1.0442056653665064 aF1.1507382737897791 aa(lp23 F1.033696911729872 aF1.1458021271205199 aa(lp24 F1.0232810190759063 aF1.1395081269373721 aa(lp25 F1.0131309024535109 aF1.13219348725736 aa(lp26 F1.0033719947608175 aF1.1241534326768767 aa(lp27 F0.99409373009181923 aF1.115637301424574 aa(lp28 F0.98535676326980204 aF1.1068500147474702 aa(lp29 F0.9771973396380923 aF1.0979553320667361 aa(lp30 F0.96963082591069549 aF1.0890818058036438 aa(lp31 F0.96265528377438525 aF1.0803287634038599 aa(lp32 F0.95625551104356266 aF1.0717718351693166 aa(lp33 F0.95040733746624928 aF1.0634680815839315 aa(lp34 F0.94508172511845601 aF1.0554597409968658 aa(lp35 F0.94024829100123641 aF1.0477775917725993 aa(lp36 F0.93587786470299905 aF1.0404433660680987 aa(lp37 F0.93194401565733886 aF1.0334716283564029 aa(lp38 F0.92842359156803578 aF1.0268712023399229 aa(lp39 F0.92529643334548983 aF1.02064621306673 aa(lp40 F0.92254455038503724 aF1.0147968193507517 aa(lp41 F0.92015102587229125 aF1.0093197646898189 aa(lp42 F0.91809890394021099 aF1.0042087883662236 aa(lp43 F0.91637028761029837 aF0.99945497436743658 aa(lp44 F0.91494578241288482 aF0.99504710871059776 aa(lp45 F0.91380435936258853 aF0.9909720919005488 aa(lp46 F0.91292364104422341 aF0.98721541236981702 aa(lp47 F0.91228053899786887 aF0.98376169278767134 aa(lp48 F0.91185214570744522 aF0.98059527190471263 aa(lp49 F0.91161673385125941 aF0.97770076356087376 aa(lp50 F0.91155468847651111 aF0.97506355217469232 aa(lp51 F0.91164924972733064 aF0.97267013435373695 aa(lp52 F0.91188691477956152 aF0.97050829021310703 aa(lp53 F0.91225743904117984 aF0.96856704747913458 aa(lp54 F0.91275342009756866 aF0.96683645488072245 aa(lp55 F0.91336952020684814 aF0.96530720842160544 aa(lp56 F0.91410144672973048 aF0.96397020298870117 aa(lp57 F0.91494485778537171 aF0.96281609786690092 aa(lp58 F0.91589438118955591 aF0.96183498471130702 aa(lp59 F0.91694292179043024 aF0.96101623048918172 aa(lp60 F0.91808138728144073 aF0.96034853491852012 aa(lp61 F0.91929888742580601 aF0.95982019918818162 aa(lp62 F0.92058337132293699 aF0.95941955831685266 aa(lp63 F0.92192257801663313 aF0.95913548993475051 aa(lp64 F0.92330510579385094 aF0.95895789298412604 aa(lp65 F0.92472137477474714 aF0.95887803085120604 aa(lp66 F0.92616427276392888 aF0.9588886594759104 aa(lp67 F0.92762933928522151 aF0.95898390769798081 aa(lp68 F0.92911444432902446 aF0.95915893378177342 aa(lp69 F0.93061903489609532 aF0.95940943523037914 aa(lp70 F0.93214312512059472 aF0.95973112604285526 aa(lp71 F0.9336862688042491 aF0.9601193044850016 aa(lp72 F0.93524675765722209 aF0.96056861110056335 aa(lp73 F0.93682122977147475 aF0.96107303161508306 aa(lp74 F0.93840476612396362 aF0.96162612868369324 aa(lp75 F0.93999142346785491 aF0.9622214328332358 aa(lp76 F0.9415750378582044 aF0.96285287802557884 aa(lp77 F0.94315006567956838 aF0.96351516036628648 aa(lp78 F0.94471222939304023 aF0.96420392595790672 aa(lp79 F0.94625880399510198 aF0.96491574817326231 aa(lp80 F0.94778849630580575 aF0.96564791703449249 aa(lp81 F0.94930099119240463 aF0.96639812020193361 aa(lp82 F0.95079633473912351 aF0.96716411877400421 aa(lp83 F0.9522743571430895 aF0.96794351304027915 aa(lp84 F0.95373430344594556 aF0.96873365429204794 aa(lp85 F0.95517475279132125 aF0.96953170523119969 aa(lp86 F0.95659380225947588 aF0.97033480246522097 aa(lp87 F0.95798940686775147 aF0.97114024767539597 aa(lp88 F0.95935973122282681 aF0.97194565671711031 aa(lp89 F0.9607033903589578 aF0.97274902299051236 aa(lp90 F0.96201951900823568 aF0.97354869002976319 aa(lp91 F0.96330768334868433 aF0.97434326197468724 aa(lp92 F0.96456770327276642 aF0.97513149706477853 aa(lp93 F0.9657994718261983 aF0.97591222483707696 aa(lp94 F0.96700283715822788 aF0.97668430831460096 aa(lp95 F0.9681775712848194 aF0.97744664938557457 aa(lp96 F0.96932340845231824 aF0.97819821927915973 aa(lp97 F0.97044011326738777 aF0.97893809155317313 aa(lp98 F0.9715275390981799 aF0.97966546126532894 aa(lp99 F0.9725856549092724 aF0.98037964532320576 aa(lp100 F0.97361453898871497 aF0.98108006883053067 aa(lp101 F0.97461435372724203 aF0.98176624657182021 aa(lp102 F0.97558531837204243 aF0.98243776731676069 aa(lp103 F0.97652769044744314 aF0.98309428436014912 aa(lp104 F0.97744175869522398 aF0.98373551142810023 aa(lp105 F0.97832784315532551 aF0.98436122119869951 aa(lp106 F0.97918629770300769 aF0.98497124378601097 aa(lp107 F0.98001751059645614 aF0.98556546391852062 aa(lp108 F0.98082190233651223 aF0.98614381694997322 aa(lp109 F0.98159992185307365 aF0.98670628438327268 aa(lp110 F0.98235204253183639 aF0.98725288951243817 aa(lp111 F0.98307875794088195 aF0.98778369360739549 aa(lp112 F0.98378057943729669 aF0.98829879246171404 aa(lp113 F0.98445803330147741 aF0.98879831330973622 aa(lp114 F0.98511165835662173 aF0.98928241183472976 aa(lp115 F0.9857420042221775 aF0.98975126942437119 aa(lp116 F0.98634962871313014 aF0.99020509041063121 aa(lp117 F0.98693509611770835 aF0.99064409956144928 aa(lp118 F0.98749897524293428 aF0.99106853973493703 aa(lp119 F0.98804183754915109 aF0.99147866952683505 aa(lp120 F0.98856425568546458 aF0.99187476135078356 aa(lp121 F0.98906680198249586 aF0.99225709931744033 aa(lp122 F0.98955004671870928 aF0.99262597758274385 aa(lp123 F0.99001455767187541 aF0.99298169854430851 aa(lp124 F0.9904608978374716 aF0.99332457134409036 aa(lp125 F0.99088962558127058 aF0.99365491042108856 aa(lp126 F0.99130129266113098 aF0.99397303415681304 aa(lp127 F0.99169644396616596 aF0.99427926366638431 aa(lp128 F0.99207561678622858 aF0.99457392169337255 aa(lp129 F0.99243933948550711 aF0.99485733154331879 aa(lp130 F0.99278813182187986 aF0.9951298161732568 aa(lp131 F0.99312250365636312 aF0.99539169736544586 aa(lp132 F0.99344295495625923 aF0.99564329488392755 aa(lp133 F0.99374997499262463 aF0.9958849258672523 aa(lp134 F0.99404404270462443 aF0.9961169041594049 aa(lp135 F0.99432562539989067 aF0.99633953973515077 aa(lp136 F0.99459517969449274 aF0.99655313828995051 aa(lp137 F0.99485315038346711 aF0.99675800066727216 aa(lp138 F0.99509997076446588 aF0.99695442264651923 aa(lp139 F0.99533606275242403 aF0.99714269445800263 aa(lp140 F0.9955618362236015 aF0.99732310060805374 aa(lp141 F0.99577768971519132 aF0.99749591957779338 aa(lp142 F0.99598400980331014 aF0.99766142365089616 aa(lp143 F0.99618117163849285 aF0.99781987874439038 aa(lp144 F0.99636953902097147 aF0.99797154427655177 aa(lp145 F0.99654946385413068 aF0.99811667303924934 aa(lp146 F0.99672128752505029 aF0.99825551126432488 aa(lp147 F0.99688533891813114 aF0.99838829822665343 aa(lp148 F0.99704193889667792 aF0.99851526704535032 aa(lp149 F0.99719139130644596 aF0.99863664271593111 aa(lp150 F0.99733400237606196 aF0.99875264692364352 aa(lp151 F0.9974700404896486 aF0.99886348637090705 aa(lp152 F0.99759982842415684 aF0.99896938444039463 aa.././@LongLink0000000000000000000000000000017200000000000011215 Lustar 00000000000000galpy-1.3.0/galpy/df_src/data/dfcorrection_dehnendf_expSurfaceSigmaProfile_0.3333_1.0000_0.2000_-0.2000_151_5.0000_20.savgalpy-1.3.0/galpy/df_src/data/dfcorrection_dehnendf_expSurfaceSigmaProfile_0.3333_1.0000_0.2000_-0.20000644000076500000240000001676312671130470032024 0ustar bovystaff00000000000000(lp1 (lp2 F1.0000858990781711 aF1.0009931120387563 aa(lp3 F0.92592625981440391 aF0.82915993730691895 aa(lp4 F0.92484810309519694 aF0.81036237748218898 aa(lp5 F0.93215049094983893 aF0.81693795559167459 aa(lp6 F0.94717065042472082 aF0.8348506199458684 aa(lp7 F0.9628148770489442 aF0.85838549729896962 aa(lp8 F0.98133738455532582 aF0.88589232003534057 aa(lp9 F1.0012354555390812 aF0.91593212278239478 aa(lp10 F1.0210091016330058 aF0.94740567208563753 aa(lp11 F1.0396550165783913 aF0.97870747756534071 aa(lp12 F1.0559417090487662 aF1.0086787691738917 aa(lp13 F1.0690380098899153 aF1.036497187211266 aa(lp14 F1.0786543234850154 aF1.0614656697565708 aa(lp15 F1.084767591287557 aF1.0830959971653791 aa(lp16 F1.0874914474825195 aF1.1011599684854467 aa(lp17 F1.0870766343343869 aF1.1156350609179646 aa(lp18 F1.083897614403686 aF1.1266329116462028 aa(lp19 F1.0784030725150133 aF1.1343553545369878 aa(lp20 F1.0710624579572523 aF1.1390682382787898 aa(lp21 F1.0623259788109358 aF1.1410821423061843 aa(lp22 F1.0526023937130573 aF1.1407343648607509 aa(lp23 F1.0422498857807942 aF1.1383709966579953 aa(lp24 F1.0315742364958758 aF1.1343313049091084 aa(lp25 F1.0208301637995072 aF1.1289353888877294 aa(lp26 F1.0102239798892003 aF1.1224761394088709 aa(lp27 F0.99991693462714126 aF1.1152144836370075 aa(lp28 F0.99002929939506901 aF1.1073778710721967 aa(lp29 F0.98064504353764659 aF1.0991604555323682 aa(lp30 F0.97181697438948411 aF1.0907251254769623 aa(lp31 F0.96357219273101224 aF1.0822059118870762 aa(lp32 F0.95591753618287689 aF1.0737113664337667 aa(lp33 F0.94884475979894556 aF1.065327742892324 aa(lp34 F0.94233527961247232 aF1.0571224152773353 aa(lp35 F0.93636425165848813 aF1.0491469482259146 aa(lp36 F0.93090394870858684 aF1.0414401691135926 aa(lp37 F0.92592634604204771 aF1.034030790394318 aa(lp38 F0.92140493290182002 aF1.0269395481713119 aa(lp39 F0.9173157854533317 aF1.0201809907933368 aa(lp40 F0.91363797572020677 aF1.0137648568048998 aa(lp41 F0.91035343738885355 aF1.0076970627548987 aa(lp42 F0.90744639332120358 aF1.0019803332807939 aa(lp43 F0.90490249420717128 aF0.99661455003024246 aa(lp44 F0.90270782032931673 aF0.9915969302005383 aa(lp45 F0.90084790016318972 aF0.98692212389988732 aa(lp46 F0.8993068824897883 aF0.9825822949652897 aa(lp47 F0.89806697287952031 aF0.97856726919809778 aa(lp48 F0.89710822544828206 aF0.97486480214641635 aa(lp49 F0.89640872316402964 aF0.97146099485133386 aa(lp50 F0.89594514765215116 aF0.96834084273887644 aa(lp51 F0.89569367284358337 aF0.96548887828061647 aa(lp52 F0.89563106992865449 aF0.96288985531440474 aa(lp53 F0.89573588242129276 aF0.96052939872708132 aa(lp54 F0.89598950779516517 aF0.95839453477129166 aa(lp55 F0.89637701980193529 aF0.95647402805669046 aa(lp56 F0.89688759887513314 aF0.95475846254522223 aa(lp57 F0.89751444837332994 aF0.95324008083877521 aa(lp58 F0.89825422868851101 aF0.9519123440142776 aa(lp59 F0.89910600986956268 aF0.95076932991806118 aa(lp60 F0.90006990845501911 aF0.94980502764040908 aa(lp61 F0.90114559160854946 aF0.949012657417868 aa(lp62 F0.90233088146357288 aF0.94838413556918388 aa(lp63 F0.90362069193106276 aF0.94790978848194285 aa(lp64 F0.90500649032671732 aF0.94757838286831386 aa(lp65 F0.90647639888431786 aF0.94737748736325766 aa(lp66 F0.9080159467624358 aF0.94729411935194463 aa(lp67 F0.909609365275445 aF0.9473155730273467 aa(lp68 F0.91124121064080466 aF0.94743028123938988 aa(lp69 F0.91289802123242692 aF0.94762854729631296 aa(lp70 F0.91456969244455788 aF0.94790299648157261 aa(lp71 F0.91625028909334605 aF0.94824864514304696 aa(lp72 F0.9179381153578734 aF0.94866255963835922 aa(lp73 F0.9196350074659877 aF0.94914316215827332 aa(lp74 F0.92134497815981342 aF0.94968931946757162 aa(lp75 F0.92307248706285461 aF0.95029940346945141 aa(lp76 F0.92482070229987057 aF0.95097052501673496 aa(lp77 F0.92659013013873903 aF0.95169810857649428 aa(lp78 F0.92837791273428627 aF0.95247589983036196 aa(lp79 F0.93017794206420301 aF0.95329639862586779 aa(lp80 F0.93198174499783559 aF0.95415160575175528 aa(lp81 F0.93377990755927942 aF0.95503389839406172 aa(lp82 F0.93556367341747637 aF0.95593681157677146 aa(lp83 F0.93732631339280492 aF0.95685553859508177 aa(lp84 F0.93906393208492511 aF0.95778703672758725 aa(lp85 F0.94077553840022043 aF0.95872973526120253 aa(lp86 F0.94246241359038385 aF0.95968295101278478 aa(lp87 F0.94412700312812747 aF0.96064619116978578 aa(lp88 F0.94577167802831263 aF0.96161854260718316 aa(lp89 F0.94739772256771937 aF0.96259830594653095 aa(lp90 F0.94900480734914294 aF0.96358294691232127 aa(lp91 F0.95059103630097108 aF0.96456933522379773 aa(lp92 F0.95215347159856656 aF0.96555415648527732 aa(lp93 F0.95368890271435192 aF0.96653434449433828 aa(lp94 F0.95519458050024275 aF0.96750739628229343 aa(lp95 F0.95666869061624216 aF0.96847149090098228 aa(lp96 F0.95811046297309199 aF0.96942541140842509 aa(lp97 F0.95951995531215006 aF0.9703683357901367 aa(lp98 F0.96089765414427619 aF0.97129959407431909 aa(lp99 F0.96224407038568782 aF0.9722184801891125 aa(lp100 F0.96355946975364981 aF0.9731241674084441 aa(lp101 F0.96484379559715994 aF0.9740157266786309 aa(lp102 F0.96609675572662712 aF0.97489220958884881 aa(lp103 F0.96731799054064027 aF0.97575274376002141 aa(lp104 F0.96850723193086774 aF0.97659659935638143 aa(lp105 F0.96966439360758727 aF0.97742320936697313 aa(lp106 F0.97078958034579188 aF0.97823215052625834 aa(lp107 F0.97188304046578788 aF0.97902310514220148 aa(lp108 F0.97294510096044562 aF0.97979582480732408 aa(lp109 F0.97397611744130508 aF0.98055010818787047 aa(lp110 F0.97497645194226323 aF0.9812857943176625 aa(lp111 F0.9759464743587779 aF0.98200276552417431 aa(lp112 F0.97688657473981944 aF0.98270095260091173 aa(lp113 F0.97779717415658562 aF0.98338033715003592 aa(lp114 F0.97867872891173791 aF0.98404094980391632 aa(lp115 F0.97953172783710263 aF0.98468286572462593 aa(lp116 F0.98035668660494335 aF0.98530619926563479 aa(lp117 F0.98115414178941562 aF0.98591109927680154 aa(lp118 F0.98192464655103251 aF0.9864977454196765 aa(lp119 F0.98266876778626688 aF0.98706634528223858 aa(lp120 F0.98338708425429233 aF0.98761713182019284 aa(lp121 F0.98408018474882419 aF0.98815036091420316 aa(lp122 F0.98474866614353418 aF0.98866630894690177 aa(lp123 F0.98539313174677223 aF0.98916527041889646 aa(lp124 F0.98601418910744565 aF0.98964755566338225 aa(lp125 F0.98661244848675667 aF0.99011348875504357 aa(lp126 F0.98718852098216114 aF0.99056340548150923 aa(lp127 F0.98774301727371638 aF0.99099765154561759 aa(lp128 F0.98827654602166215 aF0.99141658081619322 aa(lp129 F0.98878971284005424 aF0.99182055372822564 aa(lp130 F0.9892831187889527 aF0.99220993583858685 aa(lp131 F0.98975735974369528 aF0.99258509640592607 aa(lp132 F0.99021302505285635 aF0.99294640711170412 aa(lp133 F0.99065069675400041 aF0.9932942409886899 aa(lp134 F0.99107094910045135 aF0.99362897119223825 aa(lp135 F0.99147434720927563 aF0.99395097017858558 aa(lp136 F0.99186144710697199 aF0.99426060867345556 aa(lp137 F0.99223279477219095 aF0.99455825499532446 aa(lp138 F0.99258892577109248 aF0.99484427415461518 aa(lp139 F0.992930364817454 aF0.99511902739981606 aa(lp140 F0.99325762548473473 aF0.99538287139791648 aa(lp141 F0.99357120981467673 aF0.99563615792095306 aa(lp142 F0.99387160816556774 aF0.99587923319599292 aa(lp143 F0.9941592990078989 aF0.99611243763140822 aa(lp144 F0.99443474866892967 aF0.99633610541239825 aa(lp145 F0.99469841119797753 aF0.99655056405971576 aa(lp146 F0.99495072949465879 aF0.99675613466969426 aa(lp147 F0.99519213094322101 aF0.99695313041127853 aa(lp148 F0.99542303867936555 aF0.9971418592991278 aa(lp149 F0.99564384626772662 aF0.99732261755362794 aa(lp150 F0.99585497067822581 aF0.99749570351415806 aa(lp151 F0.99605674666621391 aF0.9976613870776827 aa(lp152 F0.99624963843319314 aF0.99781998103821667 aa.././@LongLink0000000000000000000000000000017100000000000011214 Lustar 00000000000000galpy-1.3.0/galpy/df_src/data/dfcorrection_dehnendf_expSurfaceSigmaProfile_0.3333_1.0000_0.2000_0.0000_151_5.0000_20.savgalpy-1.3.0/galpy/df_src/data/dfcorrection_dehnendf_expSurfaceSigmaProfile_0.3333_1.0000_0.2000_0.000000644000076500000240000001676612671130470032030 0ustar bovystaff00000000000000(lp1 (lp2 F0.81121001069551002 aF0.53887054250829214 aa(lp3 F0.83123467594310818 aF0.58232434668183275 aa(lp4 F0.85741754566491302 aF0.63492574587228501 aa(lp5 F0.88852858677391455 aF0.6952153138029018 aa(lp6 F0.92448155468179383 aF0.75799806098044054 aa(lp7 F0.96164832172117032 aF0.82076087705692746 aa(lp8 F0.99662352877336979 aF0.88175223265275693 aa(lp9 F1.0279084852263898 aF0.93886395247454524 aa(lp10 F1.0540171669520766 aF0.99055964866171131 aa(lp11 F1.0739069407135364 aF1.0361634273401459 aa(lp12 F1.0876558205623683 aF1.0751592198534554 aa(lp13 F1.0958932587589427 aF1.1072320423048643 aa(lp14 F1.0992908932072052 aF1.1325027171090165 aa(lp15 F1.0985315448294117 aF1.1514554782948649 aa(lp16 F1.0943783761386361 aF1.1647296698493419 aa(lp17 F1.0876218913035913 aF1.1730120644469149 aa(lp18 F1.0789982459905336 aF1.177000083624947 aa(lp19 F1.0691392963956363 aF1.1773839132898589 aa(lp20 F1.0585608585453341 aF1.1748255820180813 aa(lp21 F1.0476723649729027 aF1.1699375967893009 aa(lp22 F1.0367921261386066 aF1.163266222842114 aa(lp23 F1.0261608170657035 aF1.1552830874245807 aa(lp24 F1.0159524304105039 aF1.1463852928204767 aa(lp25 F1.006284600391977 aF1.1368995740815366 aa(lp26 F0.99722871070790176 aF1.1270899524349549 aa(lp27 F0.98881953356348595 aF1.1171648575434061 aa(lp28 F0.98106450637754028 aF1.1072862639474139 aa(lp29 F0.97395177385349518 aF1.0975763710116082 aa(lp30 F0.96745697879129888 aF1.0881258658616211 aa(lp31 F0.96154881196479913 aF1.0789997851859932 aa(lp32 F0.95619317027836581 aF1.0702434037062689 aa(lp33 F0.951356069553566 aF1.0618867974299051 aa(lp34 F0.94700549475269191 aF1.0539487023991467 aa(lp35 F0.94311230375217392 aF1.0464393238208149 aa(lp36 F0.93965035115309947 aF1.0393624407070201 aa(lp37 F0.93659603646305845 aF1.032716859439087 aa(lp38 F0.93392747061447801 aF1.0264973825625918 aa(lp39 F0.93162350249514492 aF1.0206954792595908 aa(lp40 F0.92966284019897205 aF1.0152997679649782 aa(lp41 F0.92802339404954193 aF1.0102964628112021 aa(lp42 F0.92668202321527526 aF1.0056698195222078 aa(lp43 F0.9256146866574565 aF1.0014026431717442 aa(lp44 F0.92479696849155713 aF0.99747687305656763 aa(lp45 F0.92420489179699061 aF0.99387417603438988 aa(lp46 F0.92381584601551958 aF0.99057652413374786 aa(lp47 F0.9236094603274958 aF0.98756667595134207 aa(lp48 F0.92356824804562032 aF0.98482851014885209 aa(lp49 F0.92367789639458375 aF0.9823471688370049 aa(lp50 F0.92392713645851343 aF0.98010900328843353 aa(lp51 F0.92430720959488322 aF0.9781013477347128 aa(lp52 F0.92481102505478963 aF0.9763121786886878 aa(lp53 F0.92543216548963958 aF0.97472973911805305 aa(lp54 F0.92616392976370732 aF0.97334221385112818 aa(lp55 F0.92699859841493459 aF0.97213753174100437 aa(lp56 F0.92792706433470096 aF0.97110334091690764 aa(lp57 F0.92893889437307198 aF0.97022716403699305 aa(lp58 F0.93002279630181861 aF0.96949669804293592 aa(lp59 F0.93116737501264735 aF0.96890018689876978 aa(lp60 F0.93236199597643077 aF0.96842677626804929 aa(lp61 F0.93359754845242326 aF0.96806676179600615 aa(lp62 F0.9348669247239485 aF0.96781166834968868 aa(lp63 F0.93616510283972953 aF0.96765413892997654 aa(lp64 F0.93748881997624522 aF0.96758766133667451 aa(lp65 F0.93883592538124871 aF0.96760620501850891 aa(lp66 F0.94020459373632304 aF0.96770385735525288 aa(lp67 F0.94159259336004952 aF0.96787455941426803 aa(lp68 F0.94299680603603842 aF0.96811199521559677 aa(lp69 F0.94441310028520709 aF0.96840965709434013 aa(lp70 F0.94583656468296151 aF0.96876105090204423 aa(lp71 F0.94726200152149365 aF0.96915996843894581 aa(lp72 F0.94868451142984511 aF0.96960073891921461 aa(lp73 F0.95009998726708667 aF0.97007838113777922 aa(lp74 F0.95150537351140596 aF0.97058862088358322 aa(lp75 F0.95289863997170288 aF0.97112777789627758 aa(lp76 F0.95427851150780052 aF0.97169257199710957 aa(lp77 F0.95564407125951778 aF0.97227991793079194 aa(lp78 F0.95699438225956812 aF0.97288677332576834 aa(lp79 F0.95832824591711752 aF0.97351007739559514 aa(lp80 F0.95964415063999176 aF0.97414678101084728 aa(lp81 F0.96094038836785567 aF0.97479393668176006 aa(lp82 F0.96221526066600938 aF0.97544880096551634 aa(lp83 F0.96346727846207547 aF0.97610890661410366 aa(lp84 F0.9646952814004679 aF0.97677208224013201 aa(lp85 F0.96589845011555986 aF0.97743642229982708 aa(lp86 F0.96707623276078614 aF0.97810022866151858 aa(lp87 F0.96822823546817616 aF0.97876195094781016 aa(lp88 F0.96935412775945839 aF0.97942014610985983 aa(lp89 F0.9704535934413675 aF0.98007346402138262 aa(lp90 F0.97152632947460693 aF0.98072065312970813 aa(lp91 F0.97257207413193436 aF0.98136057371174468 aa(lp92 F0.97359063964428227 aF0.98199220730516967 aa(lp93 F0.97458193097462009 aF0.98261465687885807 aa(lp94 F0.97554594599817246 aF0.98322713853022503 aa(lp95 F0.97648276242149001 aF0.98382896912104389 aa(lp96 F0.97739252097086249 aF0.98441955439203432 aa(lp97 F0.97827541202315071 aF0.98499838006890761 aa(lp98 F0.97913166824744113 aF0.98556500604538155 aa(lp99 F0.97996156176121252 aF0.98611906227965473 aa(lp100 F0.98076540290994696 aF0.9866602450614117 aa(lp101 F0.98154353854643894 aF0.98718831283309283 aa(lp102 F0.98229634910907893 aF0.98770308159560627 aa(lp103 F0.98302424480213135 aF0.98820442016284415 aa(lp104 F0.98372766183569749 aF0.98869224567583935 aa(lp105 F0.98440705882728641 aF0.98916651939735922 aa(lp106 F0.98506291378895428 aF0.98962724289772952 aa(lp107 F0.98569572131562311 aF0.9900744544500012 aa(lp108 F0.98630599006600617 aF0.99050822567379826 aa(lp109 F0.98689424032821371 aF0.99092865832367205 aa(lp110 F0.98746100178256124 aF0.99133588128851902 aa(lp111 F0.98800681131630219 aF0.99173004773108875 aa(lp112 F0.98853221102613498 aF0.99211133244750083 aa(lp113 F0.98903774644605014 aF0.99247992938233176 aa(lp114 F0.98952396475524129 aF0.99283604929341396 aa(lp115 F0.98999141328003026 aF0.99317991761182411 aa(lp116 F0.99044063802110283 aF0.99351177242708422 aa(lp117 F0.99087218237659713 aF0.99383186261787659 aa(lp118 F0.99128658589001784 aF0.99414044613589314 aa(lp119 F0.99168438324424746 aF0.99443778841165986 aa(lp120 F0.99206610321284683 aF0.99472416087581106 aa(lp121 F0.99243226781460692 aF0.99499983962413119 aa(lp122 F0.99278339154131512 aF0.99526510416290381 aa(lp123 F0.99311998063669571 aF0.99552023629092723 aa(lp124 F0.99344253251108683 aF0.99576551904954358 aa(lp125 F0.99375153519400117 aF0.99600123580472433 aa(lp126 F0.99404746686936662 aF0.99622766937084561 aa(lp127 F0.9943307955336691 aF0.99644510126539076 aa(lp128 F0.99460197860350563 aF0.9966538109981159 aa(lp129 F0.99486146268996434 aF0.99685407546391425 aa(lp130 F0.9951096833852332 aF0.99704616838957583 aa(lp131 F0.99534706510556947 aF0.99723035984449138 aa(lp132 F0.99557402092697578 aF0.99740691579617458 aa(lp133 F0.99579095261900064 aF0.99757609777239675 aa(lp134 F0.99599825047326185 aF0.99773816248015235 aa(lp135 F0.99619629345915972 aF0.99789336158328934 aa(lp136 F0.99638544908975346 aF0.99804194141067348 aa(lp137 F0.9965660736951838 aF0.99818414281701573 aa(lp138 F0.99673851225516319 aF0.99832020095326668 aa(lp139 F0.99690309882080397 aF0.99845034521765008 aa(lp140 F0.997060156312779 aF0.99857479907620594 aa(lp141 F0.9972099970011622 aF0.99869378006502929 aa(lp142 F0.99735292241558871 aF0.99880749968208604 aa(lp143 F0.99748922371059168 aF0.99891616342525835 aa(lp144 F0.99761918175092312 aF0.99901997072964777 aa(lp145 F0.99774306734029305 aF0.99911911503761897 aa(lp146 F0.99786114159834249 aF0.99921378380935044 aa(lp147 F0.9979736556948795 aF0.9993041585144401 aa(lp148 F0.99808085230194754 aF0.9993904149363747 aa(lp149 F0.998182962858287 aF0.99947272255224506 aa(lp150 F0.99828021476543094 aF0.99955124645075155 aa(lp151 F0.99837281548571366 aF0.99962614255519 aa(lp152 F0.99846099449649439 aF0.99969757236002277 aa.././@LongLink0000000000000000000000000000017100000000000011214 Lustar 00000000000000galpy-1.3.0/galpy/df_src/data/dfcorrection_dehnendf_expSurfaceSigmaProfile_0.3333_1.0000_0.2000_0.1000_151_5.0000_20.savgalpy-1.3.0/galpy/df_src/data/dfcorrection_dehnendf_expSurfaceSigmaProfile_0.3333_1.0000_0.2000_0.100000644000076500000240000001677712671130470032033 0ustar bovystaff00000000000000(lp1 (lp2 F0.16937204089567359 aF0.13775002593966373 aa(lp3 F0.74299252316216979 aF0.35134364932722867 aa(lp4 F0.78211600801581249 aF0.51393590177564519 aa(lp5 F0.86226398082828493 aF0.6002743236325081 aa(lp6 F0.89838581750328272 aF0.71574587933567024 aa(lp7 F0.97396806328022389 aF0.79475001767707321 aa(lp8 F1.0071910055553313 aF0.89231290367060567 aa(lp9 F1.0487738293111499 aF0.9694742019620135 aa(lp10 F1.0845870964988373 aF1.0288166718012921 aa(lp11 F1.0996225912944313 aF1.0859979770429751 aa(lp12 F1.1069084198246404 aF1.1337037195071717 aa(lp13 F1.1125414804023823 aF1.1666816694043047 aa(lp14 F1.1140976770599968 aF1.1878390082883543 aa(lp15 F1.1096049690910978 aF1.2015910622504948 aa(lp16 F1.1002316296409307 aF1.2101415736413841 aa(lp17 F1.0884829628272807 aF1.2138871664609712 aa(lp18 F1.0762406881763062 aF1.2129669332433302 aa(lp19 F1.0643057956697493 aF1.2079441613060697 aa(lp20 F1.0527475937116537 aF1.1998059276368775 aa(lp21 F1.0414298226371159 aF1.1896336610101617 aa(lp22 F1.0303162315938919 aF1.1783368987910487 aa(lp23 F1.0195395717191436 aF1.1665417991655955 aa(lp24 F1.0093237466124669 aF1.1546191083491275 aa(lp25 F0.99987730008966536 aF1.1427677191170207 aa(lp26 F0.99132262034808327 aF1.1310981082502247 aa(lp27 F0.98367696538091676 aF1.1196896647740777 aa(lp28 F0.97687442490429677 aF1.1086172338821583 aa(lp29 F0.97080640141921204 aF1.0979549259282906 aa(lp30 F0.96536057286275068 aF1.0877695307870561 aa(lp31 F0.96044676305229415 aF1.0781124890880318 aa(lp32 F0.95600683118735008 aF1.0690152171623335 aa(lp33 F0.95201149661855911 aF1.0604891527447562 aa(lp34 F0.94844985249573455 aF1.0525293052614615 aa(lp35 F0.94531734145877333 aF1.0451193672173877 aa(lp36 F0.94260617920221046 aF1.0382366639009901 aa(lp37 F0.94030012070212743 aF1.0318559577743138 aa(lp38 F0.93837354997951383 aF1.0259517577698516 aa(lp39 F0.93679368278043618 aF1.020499294722423 aa(lp40 F0.93552422889925535 aF1.0154746734997975 aa(lp41 F0.9345291077805945 aF1.0108547002298323 aa(lp42 F0.93377533567653004 aF1.0066167010151252 aa(lp43 F0.93323469228058775 aF1.0027385212325977 aa(lp44 F0.93288424305855144 aF0.99919870565039914 aa(lp45 F0.93270601023819444 aF0.99597676335828378 aa(lp46 F0.93268613391663291 aF0.99305339173401375 aa(lp47 F0.93281380544533488 aF0.99041056149747575 aa(lp48 F0.93308015883254425 aF0.98803142681513445 aa(lp49 F0.93347723176275366 aF0.98590008533958495 aa(lp50 F0.9339970683288904 aF0.98400126135266142 aa(lp51 F0.93463103143392956 aF0.98232001101449573 aa(lp52 F0.93536940008867886 aF0.98084153848687039 aa(lp53 F0.93620131222723368 aF0.97955117600675312 aa(lp54 F0.93711506708876746 aF0.97843453317247231 aa(lp55 F0.93809873009907485 aF0.97747777028314053 aa(lp56 F0.93914090678053996 aF0.97666791355593785 aa(lp57 F0.94023149824116103 aF0.97599311755154816 aa(lp58 F0.94136224445325523 aF0.9754427952040472 aa(lp59 F0.94252691266509359 aF0.97500757174380437 aa(lp60 F0.94372107971001795 aF0.97467907847374413 aa(lp61 F0.94494157748095686 aF0.97444963469937584 aa(lp62 F0.94618575964632778 aF0.97431191477563339 aa(lp63 F0.94745080153425032 aF0.97425868648896674 aa(lp64 F0.94873322454537579 aF0.97428268723821154 aa(lp65 F0.9500287582481256 aF0.97437665830467846 aa(lp66 F0.95133254662328004 aF0.97453350257276194 aa(lp67 F0.95263958940649851 aF0.97474650158492526 aa(lp68 F0.95394525080745307 aF0.97500950692116295 aa(lp69 F0.95524565906980474 aF0.97531704180857237 aa(lp70 F0.95653787698859682 aF0.97566428569031083 aa(lp71 F0.95781981697838248 aF0.97604695803722263 aa(lp72 F0.95908996699826277 aF0.97646115113407883 aa(lp73 F0.96034705036849766 aF0.97690317339641264 aa(lp74 F0.96158974781560325 aF0.97736945118657526 aa(lp75 F0.96281656481610789 aF0.97785650736321883 aa(lp76 F0.96402585762579662 aF0.9783610026521713 aa(lp77 F0.96521596767539974 aF0.97887980491186799 aa(lp78 F0.96638538302223465 aF0.97941004826221112 aa(lp79 F0.96753285355942875 aF0.97994915683978767 aa(lp80 F0.96865742378549502 aF0.9804948287871621 aa(lp81 F0.96975839013213283 aF0.98104499398994105 aa(lp82 F0.97083522035256564 aF0.98159776652509279 aa(lp83 F0.97188747764115802 aF0.98215140891005837 aa(lp84 F0.97291477704236384 aF0.98270431504885836 aa(lp85 F0.97391677850021219 aF0.98325500827495071 aa(lp86 F0.97489320244183941 aF0.98380214557114887 aa(lp87 F0.97584384971921956 aF0.98434451977189485 aa(lp88 F0.97676861155302241 aF0.98488105570953022 aa(lp89 F0.97766746686915051 aF0.98541080094575773 aa(lp90 F0.97854047082581097 aF0.9859329142408173 aa(lp91 F0.97938774164167464 aF0.98644665488759375 aa(lp92 F0.98020945038412388 aF0.98695137416039347 aa(lp93 F0.98100581500869366 aF0.98744650882839058 aa(lp94 F0.98177709675374869 aF0.98793157570305634 aa(lp95 F0.98252359751186036 aF0.98840616635464773 aa(lp96 F0.98324565670302611 aF0.98886994173050768 aa(lp97 F0.98394364729907458 aF0.9893226267157621 aa(lp98 F0.98461797202716939 aF0.98976400487172034 aa(lp99 F0.98526905947882959 aF0.99019391346542607 aa(lp100 F0.98589736043400966 aF0.99061223888588312 aa(lp101 F0.98650334510101534 aF0.9910189123414086 aa(lp102 F0.98708749984667155 aF0.99141390583764677 aa(lp103 F0.98765032473468306 aF0.99179722840952089 aa(lp104 F0.98819233093387093 aF0.99216892253720113 aa(lp105 F0.98871403827832927 aF0.9925290608422479 aa(lp106 F0.98921597321559485 aF0.99287774294670506 aa(lp107 F0.98969866675561691 aF0.99321509258337715 aa(lp108 F0.99016265236365542 aF0.9935412548536936 aa(lp109 F0.99060846472104147 aF0.99385639377482882 aa(lp110 F0.99103663772556239 aF0.99416068984979267 aa(lp111 F0.99144770327544185 aF0.99445433797709193 aa(lp112 F0.99184218993207507 aF0.99473754541713777 aa(lp113 F0.99222062184549931 aF0.99501052993250072 aa(lp114 F0.99258351742121431 aF0.99527351810204978 aa(lp115 F0.99293138881064413 aF0.9955267437357247 aa(lp116 F0.99326474056893344 aF0.99577044646958845 aa(lp117 F0.99358406948608391 aF0.99600487041878039 aa(lp118 F0.99388986335168561 aF0.99623026297467754 aa(lp119 F0.99418260081069254 aF0.99644687377382268 aa(lp120 F0.9944627507944972 aF0.99665495361642087 aa(lp121 F0.99473077186769077 aF0.99685475365844867 aa(lp122 F0.99498711229101877 aF0.99704652453140585 aa(lp123 F0.99523220939683021 aF0.99723051567973275 aa(lp124 F0.99546648956572414 aF0.99740697465607564 aa(lp125 F0.99569036794522436 aF0.99757614659498017 aa(lp126 F0.9959042484680013 aF0.99773827366395018 aa(lp127 F0.99610852358943591 aF0.99789359462397276 aa(lp128 F0.99630357452618179 aF0.99804234445314299 aa(lp129 F0.99648977085206947 aF0.99818475396798956 aa(lp130 F0.99666747110098364 aF0.99832104958946111 aa(lp131 F0.99683702229568316 aF0.99845145301001037 aa(lp132 F0.99699876036885271 aF0.99857618107560875 aa(lp133 F0.99715301020288383 aF0.99869544553726364 aa(lp134 F0.99730008569064754 aF0.99880945296850099 aa(lp135 F0.99744029002478096 aF0.99891840462092762 aa(lp136 F0.99757391593314571 aF0.99902249636497531 aa(lp137 F0.99770124554779194 aF0.99912191863437005 aa(lp138 F0.99782255093943151 aF0.99921685641291325 aa(lp139 F0.99793809426500568 aF0.99930748914663703 aa(lp140 F0.99804812788483166 aF0.99939399086442282 aa(lp141 F0.99815289471402291 aF0.99947653013431192 aa(lp142 F0.99825262831364459 aF0.99955527010704759 aa(lp143 F0.99834755322210167 aF0.9996303686029403 aa(lp144 F0.99843788542737566 aF0.99970197812850581 aa(lp145 F0.99852383209173323 aF0.99977024600103415 aa(lp146 F0.99860559236909008 aF0.99983531440926221 aa(lp147 F0.99868335709890532 aF0.99989732047407553 aa(lp148 F0.99875730958747255 aF0.99995639645412626 aa(lp149 F0.99882762530043767 aF1.0000126695695752 aa(lp150 F0.99889447366986817 aF1.0000662629012498 aa(lp151 F0.99895801261105854 aF1.0001172932647273 aa(lp152 F0.99901840797093222 aF1.0001658784385667 aa.././@LongLink0000000000000000000000000000017100000000000011214 Lustar 00000000000000galpy-1.3.0/galpy/df_src/data/dfcorrection_dehnendf_expSurfaceSigmaProfile_0.3333_1.0000_0.2000_0.2000_151_5.0000_20.savgalpy-1.3.0/galpy/df_src/data/dfcorrection_dehnendf_expSurfaceSigmaProfile_0.3333_1.0000_0.2000_0.200000644000076500000240000001676412671130470032030 0ustar bovystaff00000000000000(lp1 (lp2 F0.26242761788829444 aF0.00088007207645892434 aa(lp3 F0.63700147756369196 aF0.17378090987737763 aa(lp4 F0.67378664145500156 aF0.34752932361499389 aa(lp5 F0.79958867839240455 aF0.4891112012266029 aa(lp6 F0.88447428705898956 aF0.64594632189379064 aa(lp7 F0.97163689065021963 aF0.78988975557470986 aa(lp8 F1.0426519440522342 aF0.91226835412180018 aa(lp9 F1.0821797817897225 aF1.026566479786676 aa(lp10 F1.1150321662686531 aF1.1128827500581593 aa(lp11 F1.1375267886141442 aF1.1733864110627992 aa(lp12 F1.1426567864111834 aF1.2204484385059677 aa(lp13 F1.1377685279163852 aF1.2544886378588644 aa(lp14 F1.1299880423511746 aF1.2733425765608386 aa(lp15 F1.1206016603607138 aF1.2792673547095117 aa(lp16 F1.1088889437880416 aF1.2765651576274493 aa(lp17 F1.0947716305743265 aF1.2689576754085563 aa(lp18 F1.0794351376740476 aF1.2583909367843373 aa(lp19 F1.0643594185720662 aF1.2456376326666228 aa(lp20 F1.0505155879464083 aF1.2311313082681241 aa(lp21 F1.0381830198813726 aF1.2154062741641571 aa(lp22 F1.0271996757476869 aF1.1991039379769868 aa(lp23 F1.0172601809811557 aF1.1828468274484401 aa(lp24 F1.0081250118239242 aF1.1671125767676069 aa(lp25 F0.99969569342365217 aF1.1521757359254683 aa(lp26 F0.99197806450364845 aF1.1381340249917624 aa(lp27 F0.98500967101480563 aF1.1249752406652469 aa(lp28 F0.97880315518833838 aF1.1126430213937841 aa(lp29 F0.97332540318786465 aF1.1010809257255845 aa(lp30 F0.96850666421823661 aF1.0902503434096884 aa(lp31 F0.96426334007791936 aF1.080130773720106 aa(lp32 F0.96051984901149823 aF1.0707112104462648 aa(lp33 F0.95722105652188982 aF1.0619801117502474 aa(lp34 F0.95433315590863121 aF1.0539186011018886 aa(lp35 F0.95183626549712508 aF1.0464983482193848 aa(lp36 F0.94971434954523093 aF1.0396831856080386 aa(lp37 F0.94794728345444035 aF1.0334325167969283 aa(lp38 F0.94650744321512792 aF1.0277048820615773 aa(lp39 F0.94536081171494624 aF1.0224606757157155 aa(lp40 F0.94447093231081192 aF1.0176637111858764 aa(lp41 F0.94380357743287957 aF1.0132817603705186 aa(lp42 F0.94333035937323118 aF1.0092863430367132 aa(lp43 F0.9430303057089664 aF1.0056520999084866 aa(lp44 F0.94288932496494449 aF1.0023560122811812 aa(lp45 F0.94289815768943641 aF0.99937665097759065 aa(lp46 F0.94304971896661816 aF0.99669356063596659 aa(lp47 F0.94333668943255222 aF0.99428683613383351 aa(lp48 F0.94374993527468332 aF0.99213692098282025 aa(lp49 F0.94427798472358571 aF0.99022462923690691 aa(lp50 F0.94490746747060417 aF0.98853135593651476 aa(lp51 F0.94562420258231528 aF0.98703940073546359 aa(lp52 F0.94641451922336861 aF0.98573230297079095 aa(lp53 F0.94726641086675445 aF0.98459508495868808 aa(lp54 F0.94817023198955042 aF0.98361432811966309 aa(lp55 F0.94911880866066223 aF0.98277805693263609 aa(lp56 F0.9501070081819275 aF0.98207546387305644 aa(lp57 F0.95113095475196119 aF0.98149655576272221 aa(lp58 F0.9521871552230633 aF0.98103182172316472 aa(lp59 F0.95327179398519979 aF0.9806720093671204 aa(lp60 F0.954380374378209 aF0.98040805510864693 aa(lp61 F0.95550775542320521 aF0.98023115801518512 aa(lp62 F0.95664850154640391 aF0.98013294305556631 aa(lp63 F0.95779737291789435 aF0.98010563361963643 aa(lp64 F0.95894976140583643 aF0.98014216283374889 aa(lp65 F0.96010192826547414 aF0.98023618398720735 aa(lp66 F0.9612509914483357 aF0.98038198625550432 aa(lp67 F0.96239471285907918 aF0.98057435397478421 aa(lp68 F0.96353119945279508 aF0.98080842409059354 aa(lp69 F0.96465864224821829 aF0.98107958699427777 aa(lp70 F0.96577517712114647 aF0.98138345009551109 aa(lp71 F0.96687888469958339 aF0.98171585473605261 aa(lp72 F0.96796788700093883 aF0.98207291824474807 aa(lp73 F0.96904046999976201 aF0.98245106980659302 aa(lp74 F0.97009516973226695 aF0.98284706038548109 aa(lp75 F0.9711307929144567 aF0.98325794390473498 aa(lp76 F0.97214638065758718 aF0.98368104042180415 aa(lp77 F0.97314114611227476 aF0.98411389698156981 aa(lp78 F0.97411441862311132 aF0.98455425804800367 aa(lp79 F0.97506561287922355 aF0.98500004903833061 aa(lp80 F0.97599422327924545 aF0.98544936962102303 aa(lp81 F0.97689983201726049 aF0.9859004903513372 aa(lp82 F0.97778211764597456 aF0.98635184742754134 aa(lp83 F0.97864085628970987 aF0.98680203380047082 aa(lp84 F0.97947591494710928 aF0.98724978769819782 aa(lp85 F0.98028724075930229 aF0.98769398068411163 aa(lp86 F0.98107485045897758 aF0.98813360681599316 aa(lp87 F0.98183882227767683 aF0.98856777342988811 aa(lp88 F0.98257929022188217 aF0.98899569313929436 aa(lp89 F0.98329643958880342 aF0.98941667647984055 aa(lp90 F0.9839905027189717 aF0.9898301248127187 aa(lp91 F0.98466175466977313 aF0.99023552342415089 aa(lp92 F0.98531050858278102 aF0.99063243487402564 aa(lp93 F0.98593711141717244 aF0.99102049270575676 aa(lp94 F0.98654193979768279 aF0.99139939554806955 aa(lp95 F0.98712539626911655 aF0.99176890161174425 aa(lp96 F0.98768790580340304 aF0.99212882352470722 aa(lp97 F0.98822991260965709 aF0.99247902349720596 aa(lp98 F0.98875187705429834 aF0.99281940879506403 aa(lp99 F0.98925427290061363 aF0.99314992747966979 aa(lp100 F0.98973758466870043 aF0.99347056447496684 aa(lp101 F0.99020230524140174 aF0.99378133785942868 aa(lp102 F0.99064893364549844 aF0.99408229542287474 aa(lp103 F0.99107797297858369 aF0.99437351151186537 aa(lp104 F0.99148992862605012 aF0.9946550840162639 aa(lp105 F0.9918853064192148 aF0.99492713166914726 aa(lp106 F0.99226461121054499 aF0.99518979149383757 aa(lp107 F0.99262834544623002 aF0.99544321646283995 aa(lp108 F0.99297700775827213 aF0.99568757333725189 aa(lp109 F0.99331109210720392 aF0.99592304070950688 aa(lp110 F0.99363108642099096 aF0.99614980714327883 aa(lp111 F0.99393747202536908 aF0.99636806954360657 aa(lp112 F0.99423072257286538 aF0.99657803161703873 aa(lp113 F0.99451130356877515 aF0.99677990248366821 aa(lp114 F0.99477967157935321 aF0.99697389542420933 aa(lp115 F0.99503627387890259 aF0.99716022671905802 aa(lp116 F0.99528154789745937 aF0.99733911463743385 aa(lp117 F0.99551592089339558 aF0.99751077847958702 aa(lp118 F0.99573980969772546 aF0.99767543774423406 aa(lp119 F0.99595362042209556 aF0.99783331139242104 aa(lp120 F0.99615774831134352 aF0.99798461715340514 aa(lp121 F0.99635257761490015 aF0.99812957095352506 aa(lp122 F0.99653848154428903 aF0.99826838636156467 aa(lp123 F0.99671582212958587 aF0.99840127416309155 aa(lp124 F0.99688495039734903 aF0.99852844192058932 aa(lp125 F0.99704620626826712 aF0.99865009365815038 aa(lp126 F0.99719991868867375 aF0.99876642953059791 aa(lp127 F0.99734640567903088 aF0.99887764559200443 aa(lp128 F0.99748597460075039 aF0.99898393358076676 aa(lp129 F0.99761892211743808 aF0.99908548071663472 aa(lp130 F0.99774553445260217 aF0.99918246961706914 aa(lp131 F0.99786608763402995 aF0.9992750781147417 aa(lp132 F0.99798084754507654 aF0.99936347924139757 aa(lp133 F0.99809007027143615 aF0.99944784112735541 aa(lp134 F0.99819400221013288 aF0.99952832698921867 aa(lp135 F0.99829288043120867 aF0.99960509511096907 aa(lp136 F0.99838693273971979 aF0.9996782988442473 aa(lp137 F0.99847637807499512 aF0.99974808665198522 aa(lp138 F0.99856142662765079 aF0.99981460212441342 aa(lp139 F0.99864228023713053 aF0.99987798400183581 aa(lp140 F0.99871913249407795 aF0.99993836629815758 aa(lp141 F0.9987921690052679 aF0.9999958783261057 aa(lp142 F0.99886156778381197 aF1.0000506447809765 aa(lp143 F0.9989274992845425 aF1.0001027858413811 aa(lp144 F0.99899012681020549 aF1.0001524172308571 aa(lp145 F0.9990496067763518 aF1.0001996503579194 aa(lp146 F0.99910608873324347 aF1.0002445923857888 aa(lp147 F0.99915971587363706 aF1.0002873463357118 aa(lp148 F0.9992106251027244 aF1.0003280111915995 aa(lp149 F0.99925894717776931 aF1.0003666820450217 aa(lp150 F0.99930480740765359 aF1.0004034502199428 aa(lp151 F0.99934832479266789 aF1.0004384031076614 aa(lp152 F0.99938961545606708 aF1.0004716254769435 aa.././@LongLink0000000000000000000000000000016600000000000011220 Lustar 00000000000000galpy-1.3.0/galpy/df_src/data/dfcorrection_shudf_expSurfaceSigmaProfile_0.3333_1.0000_0.2000_0.0000_151_5.0000_20.savgalpy-1.3.0/galpy/df_src/data/dfcorrection_shudf_expSurfaceSigmaProfile_0.3333_1.0000_0.2000_0.0000_0000644000076500000240000001702612671130470031653 0ustar bovystaff00000000000000(lp1 (lp2 F1.0247460826575929 aF1.0010805795621271 aa(lp3 F1.033451776586197 aF1.0043404205946462 aa(lp4 F1.0391893047321905 aF1.0046814957691361 aa(lp5 F1.0417622841310203 aF1.0020220865059544 aa(lp6 F1.0410397669035953 aF0.99638284349334005 aa(lp7 F1.0370540193109397 aF0.9878675792043311 aa(lp8 F1.0298069191583163 aF0.97667228429328501 aa(lp9 F1.0193982243013839 aF0.96311166129967651 aa(lp10 F1.0061739316744036 aF0.94760526063491857 aa(lp11 F0.9906800668874467 aF0.93066657437503675 aa(lp12 F0.97352517826898122 aF0.91287766435031159 aa(lp13 F0.9553313630672603 aF0.8948196283962998 aa(lp14 F0.93678171987678283 aF0.87699280831916959 aa(lp15 F0.91862271536381901 aF0.85979127019427293 aa(lp16 F0.90153434884209438 aF0.84353745456588658 aa(lp17 F0.88594709911346947 aF0.82851018605574567 aa(lp18 F0.87193451087708762 aF0.81490691754767641 aa(lp19 F0.85923967774812582 aF0.80274344086308769 aa(lp20 F0.84749315580945983 aF0.79188871470484345 aa(lp21 F0.83662478232414739 aF0.78244055798752232 aa(lp22 F0.82695885006890302 aF0.7747726096112566 aa(lp23 F0.81908729681663739 aF0.76936102659134409 aa(lp24 F0.81368084776922511 aF0.76661153122446646 aa(lp25 F0.81128880085304611 aF0.76673036532195415 aa(lp26 F0.8121646922785325 aF0.76964081898937409 aa(lp27 F0.81614639440679981 aF0.77494634665591078 aa(lp28 F0.82261032102014697 aF0.78194084065474356 aa(lp29 F0.83051480517916976 aF0.78967766951388696 aa(lp30 F0.83853729990566517 aF0.79709918363752885 aa(lp31 F0.84529389490477291 aF0.80321358623787065 aa(lp32 F0.84960346985787394 aF0.80728274088303287 aa(lp33 F0.85073378226730023 aF0.80897001067558472 aa(lp34 F0.84855933412428775 aF0.80840069739754361 aa(lp35 F0.84358306015104001 aF0.80612472990520323 aa(lp36 F0.83681835255613846 aF0.80299407074875162 aa(lp37 F0.82956633987204886 aF0.79998940262113205 aa(lp38 F0.8231517027500086 aF0.7980216417404471 aa(lp39 F0.81867297480099266 aF0.79773820351898672 aa(lp40 F0.81684955944621584 aF0.7994302356907983 aa(lp41 F0.81800571980871961 aF0.80310250518910098 aa(lp42 F0.82210431947762208 aF0.80855162316650375 aa(lp43 F0.82878528943936569 aF0.81540326589520973 aa(lp44 F0.8374147329445758 aF0.82314112966790853 aa(lp45 F0.84715084368431892 aF0.83115901770294176 aa(lp46 F0.85701964789860985 aF0.83882767057167862 aa(lp47 F0.86599500773291316 aF0.8455575227636003 aa(lp48 F0.87308152374219938 aF0.85082935548092942 aa(lp49 F0.87742904254475518 aF0.85423388210462403 aa(lp50 F0.87850509138745136 aF0.85556597060527406 aa(lp51 F0.87630273594246855 aF0.85497960978295207 aa(lp52 F0.87150150099661761 aF0.85311404573235705 aa(lp53 F0.86545009288665653 aF0.85106287607175057 aa(lp54 F0.85992979305314632 aF0.85012490393643336 aa(lp55 F0.85674521421235983 aF0.85143575290904006 aa(lp56 F0.85728373640844002 aF0.85562255793392061 aa(lp57 F0.86217400786028231 aF0.86262532049112672 aa(lp58 F0.87111587741832541 aF0.87171156121510773 aa(lp59 F0.88289280153938476 aF0.88164599271906297 aa(lp60 F0.8955844862104293 aF0.89097415390987622 aa(lp61 F0.90697921755911604 aF0.89837067039468299 aa(lp62 F0.91511205984888377 aF0.90296650268907797 aa(lp63 F0.91878556790367516 aF0.90455491768022223 aa(lp64 F0.91790348314806225 aF0.90362824686242293 aa(lp65 F0.91350899371368455 aF0.90125349910367636 aa(lp66 F0.90751372165950039 aF0.89883192094677189 aa(lp67 F0.90218798098954744 aF0.8977600012428778 aa(lp68 F0.89954460759381794 aF0.89905187254609686 aa(lp69 F0.90079760756865823 aF0.90303905410314589 aa(lp70 F0.90604941489177282 aF0.9092731804737938 aa(lp71 F0.91428517366257789 aF0.91667348904280699 aa(lp72 F0.92368000702391295 aF0.9238715224098426 aa(lp73 F0.93215944257794936 aF0.92964299804367578 aa(lp74 F0.93806377527601947 aF0.93328695326285416 aa(lp75 F0.9406817084696969 aF0.93481786130496036 aa(lp76 F0.94042890565042692 aF0.93489868398625475 aa(lp77 F0.9385938735015199 aF0.9345473953875898 aa(lp78 F0.93677925130833251 aF0.93474317060275147 aa(lp79 F0.93629364758714695 aF0.93609181033033573 aa(lp80 F0.93773634782694903 aF0.93867138067330314 aa(lp81 F0.94090239970134493 aF0.94209437355146675 aa(lp82 F0.94499685612166329 aF0.94573355230440959 aa(lp83 F0.9490391863635832 aF0.94900261907025341 aa(lp84 F0.95227772324192483 aF0.95157299808501039 aa(lp85 F0.95444059429166717 aF0.95344596834733042 aa(lp86 F0.95573470723292842 aF0.95487165552554498 aa(lp87 F0.95663568545223943 aF0.95617897513200778 aa(lp88 F0.95760989938852725 aF0.95761410956913806 aa(lp89 F0.95891568884039102 aF0.95926085849232079 aa(lp90 F0.96055660597727233 aF0.96105791604791813 aa(lp91 F0.96236618589044121 aF0.9628770196687344 aa(lp92 F0.96414486294909241 aF0.96460616332391547 aa(lp93 F0.96576600352625719 aF0.96619582950816785 aa(lp94 F0.96720790708044568 aF0.96765776065695108 aa(lp95 F0.96852109881322346 aF0.96903379744784379 aa(lp96 F0.96977202856365474 aF0.97036239445330053 aa(lp97 F0.97100265377817985 aF0.97166153280634948 aa(lp98 F0.97222111236627706 aF0.97293018879007298 aa(lp99 F0.97341494050328292 aF0.97415921679769335 aa(lp100 F0.9745690218080163 aF0.97534118141662884 aa(lp101 F0.97567578788250808 aF0.97647411348540503 aa(lp102 F0.97673571644477242 aF0.97756016958391834 aa(lp103 F0.97775295640229265 aF0.97860270245723024 aa(lp104 F0.97873135436887349 aF0.97960435229473986 aa(lp105 F0.97967304178952486 aF0.9805667058918911 aa(lp106 F0.98057892156016835 aF0.98149078634088627 aa(lp107 F0.98144958316243125 aF0.98237755513489833 aa(lp108 F0.98228580083683203 aF0.98322810630100266 aa(lp109 F0.98308857271782557 aF0.98404363408179485 aa(lp110 F0.98385898594450216 aF0.98482534857365467 aa(lp111 F0.98459811485952031 aF0.98557442910859239 aa(lp112 F0.98530699382451647 aF0.98629201758661877 aa(lp113 F0.98598662669943693 aF0.98697922585762121 aa(lp114 F0.986637998025786 aF0.98763714109958856 aa(lp115 F0.98726207657980891 aF0.9882668267786463 aa(lp116 F0.98785981417867175 aF0.98886932168304364 aa(lp117 F0.98843214385456402 aF0.98944563895456783 aa(lp118 F0.98897997863676479 aF0.98999676558233685 aa(lp119 F0.98950421091316576 aF0.99052366215756038 aa(lp120 F0.99000571190457376 aF0.99102726275208808 aa(lp121 F0.99048533131094751 aF0.99150847488887306 aa(lp122 F0.99094389701677121 aF0.99196817956110295 aa(lp123 F0.99138221483661249 aF0.99240723133140074 aa(lp124 F0.99180106840893134 aF0.99282645848812912 aa(lp125 F0.99220121909006653 aF0.99322666327444742 aa(lp126 F0.99258340597226302 aF0.99360862214558243 aa(lp127 F0.9929483459807924 aF0.99397308610398694 aa(lp128 F0.99329673392729134 aF0.99432078104343768 aa(lp129 F0.99362924273461939 aF0.99465240815930944 aa(lp130 F0.99394652363795299 aF0.99496864437820454 aa(lp131 F0.99424920646234094 aF0.99527014281080262 aa(lp132 F0.99453789986066521 aF0.99555753322976781 aa(lp133 F0.99481319175320082 aF0.9958314226012821 aa(lp134 F0.99507564954850025 aF0.99609239555975215 aa(lp135 F0.995325820678162 aF0.99634101499166583 aa(lp136 F0.99556423282559381 aF0.99657782253247851 aa(lp137 F0.99579139456981025 aF0.99680333917426944 aa(lp138 F0.99600779555703201 aF0.9970180657592822 aa(lp139 F0.9962139072624614 aF0.99722248362294019 aa(lp140 F0.996410183106193 aF0.99741705507338907 aa(lp141 F0.99659705924827502 aF0.99760222403571175 aa(lp142 F0.99677495482333578 aF0.99777841654319877 aa(lp143 F0.9969442725483636 aF0.99794604136360254 aa(lp144 F0.99710539915210117 aF0.99810549049116259 aa(lp145 F0.99725870577931097 aF0.99825713974038244 aa(lp146 F0.99740454881281282 aF0.99840134929275681 aa(lp147 F0.99754326946522975 aF0.99853846409596181 aa(lp148 F0.99767519632764934 aF0.99866881483652192 aa(lp149 F0.9978006409293686 aF0.99879271721702267 aa(lp150 F0.99791990943982589 aF0.9989104757497218 aa(lp151 F0.99803327748381143 aF0.99902237545422412 aa(lp152 F0.99814105578286894 aF0.99912870897829686 aa.././@LongLink0000000000000000000000000000016600000000000011220 Lustar 00000000000000galpy-1.3.0/galpy/df_src/data/dfcorrection_shudf_expSurfaceSigmaProfile_0.3333_1.0000_0.2000_0.0000_151_5.0000_40.savgalpy-1.3.0/galpy/df_src/data/dfcorrection_shudf_expSurfaceSigmaProfile_0.3333_1.0000_0.2000_0.0000_0000644000076500000240000001702412671130470031651 0ustar bovystaff00000000000000(lp1 (lp2 F1.0247460826575852 aF1.0010805795621225 aa(lp3 F1.0334528053257717 aF1.0043404666138369 aa(lp4 F1.0391834548319367 aF1.0046805085604837 aa(lp5 F1.0417616164986208 aF1.0020251573633892 aa(lp6 F1.0410892421084852 aF0.99638130189697938 aa(lp7 F1.0370021548411172 aF0.98787025323850464 aa(lp8 F1.0297502479560714 aF0.97664818488212346 aa(lp9 F1.0194180746404204 aF0.96309829243823375 aa(lp10 F1.0061900815781444 aF0.9476637745362595 aa(lp11 F0.99070445203630764 aF0.93075250723219516 aa(lp12 F0.97367754317389243 aF0.91288251579328206 aa(lp13 F0.95556867029976877 aF0.89470897992191301 aa(lp14 F0.93682138272720317 aF0.8768326416343819 aa(lp15 F0.91821942978331983 aF0.859660718360159 aa(lp16 F0.90078825870807977 aF0.84347930932862547 aa(lp17 F0.88532002205986493 aF0.82857355662954302 aa(lp18 F0.87196383018647194 aF0.81517642424853298 aa(lp19 F0.8601399381934236 aF0.80322370602652537 aa(lp20 F0.84893047827048651 aF0.79234828332741958 aa(lp21 F0.83792506157348645 aF0.7825856206657068 aa(lp22 F0.82746432502054579 aF0.77445869912569643 aa(lp23 F0.81844033469798283 aF0.76865891244628537 aa(lp24 F0.81196891790756376 aF0.7657615865946924 aa(lp25 F0.80902421667046975 aF0.76604696353194246 aa(lp26 F0.81013674515004552 aF0.76940751067460644 aa(lp27 F0.81518274268821966 aF0.77532392720361287 aa(lp28 F0.82330200461383085 aF0.78290595597081403 aa(lp29 F0.83297652421526513 aF0.79099936697528328 aa(lp30 F0.84227182921639587 aF0.79836559819877817 aa(lp31 F0.84923347876534272 aF0.80392173082555907 aa(lp32 F0.85236811036355298 aF0.80699365600529582 aa(lp33 F0.85106444104219503 aF0.80748655826891069 aa(lp34 F0.84578634989701429 aF0.80588423114209684 aa(lp35 F0.83795231827290073 aF0.80309889908781573 aa(lp36 F0.82955094140414187 aF0.8002534571238159 aa(lp37 F0.82262143206406335 aF0.79846490230156442 aa(lp38 F0.81873135961885146 aF0.79857452857058997 aa(lp39 F0.81854076707301493 aF0.80080522858045911 aa(lp40 F0.82167859283977029 aF0.804668454987295 aa(lp41 F0.82708261770448077 aF0.80941982745822494 aa(lp42 F0.83346768995003884 aF0.81445146552803094 aa(lp43 F0.8397198811938541 aF0.81939902467363379 aa(lp44 F0.84517779980123442 aF0.8241219270377852 aa(lp45 F0.84974392520792019 aF0.82866018850927003 aa(lp46 F0.85375818837658501 aF0.83319349583622893 aa(lp47 F0.85765140186004218 aF0.83790472129250781 aa(lp48 F0.86152271469313202 aF0.84274099178042317 aa(lp49 F0.8649171699563466 aF0.84725784648737557 aa(lp50 F0.86703142947247858 aF0.85079334187655742 aa(lp51 F0.86730615482272477 aF0.8529665645747444 aa(lp52 F0.86602231590146395 aF0.8541053610236452 aa(lp53 F0.86443249945941625 aF0.85520531707688074 aa(lp54 F0.86429377328368173 aF0.85742274731695678 aa(lp55 F0.86703249573166108 aF0.8614461817177812 aa(lp56 F0.87296940330714379 aF0.86708040317252633 aa(lp57 F0.88105619440621108 aF0.8733518824608848 aa(lp58 F0.88927286384224846 aF0.8790354879475446 aa(lp59 F0.89546878538998242 aF0.88322395559527112 aa(lp60 F0.89831286238922314 aF0.88569050141846206 aa(lp61 F0.89792318713679109 aF0.88696713407198158 aa(lp62 F0.89580605261446378 aF0.88809541950298054 aa(lp63 F0.89410497513050347 aF0.89010358642877163 aa(lp64 F0.89460109988965641 aF0.89349599371465971 aa(lp65 F0.89804815682789163 aF0.89809871593091306 aa(lp66 F0.90406549367274891 aF0.90329941372700917 aa(lp67 F0.91139883562174995 aF0.90836330277406696 aa(lp68 F0.91833871066574269 aF0.91262695180924425 aa(lp69 F0.92325653960378373 aF0.91564618994364544 aa(lp70 F0.92521350021947757 aF0.91736974106171365 aa(lp71 F0.92442116714422562 aF0.9182353836443452 aa(lp72 F0.92227007171091435 aF0.91906248351564113 aa(lp73 F0.92081933446867914 aF0.92074302641819927 aa(lp74 F0.92189591144054228 aF0.92382905639150359 aa(lp75 F0.92617470460230789 aF0.9281898708216102 aa(lp76 F0.93273284847069982 aF0.93298893158800411 aa(lp77 F0.9394319191078222 aF0.93709808403984129 aa(lp78 F0.94403107424555233 aF0.93973293539883507 aa(lp79 F0.94543261257059552 aF0.94089393648212638 aa(lp80 F0.94429436215756557 aF0.94133686500573166 aa(lp81 F0.94260301325580176 aF0.94211760746003215 aa(lp82 F0.94247597225827373 aF0.94398939141312732 aa(lp83 F0.94493507311673486 aF0.94699260269208452 aa(lp84 F0.9493783736364273 aF0.95047326816036271 aa(lp85 F0.95405082181615619 aF0.9535256535335499 aa(lp86 F0.95722609332439534 aF0.95557981589342522 aa(lp87 F0.95831942082636035 aF0.95673299921932986 aa(lp88 F0.95814668559172533 aF0.95760324600682767 aa(lp89 F0.95818213219212833 aF0.95884278000518386 aa(lp90 F0.95944286241961918 aF0.9606899605699043 aa(lp91 F0.96185857193845525 aF0.96287724617310622 aa(lp92 F0.96452748504767694 aF0.96492122317847118 aa(lp93 F0.9665469661597933 aF0.9665222605443442 aa(lp94 F0.9676919560182683 aF0.96774573722701396 aa(lp95 F0.96842332274156662 aF0.96888042491438453 aa(lp96 F0.96934525087400303 aF0.97015384765857315 aa(lp97 F0.97068199209792438 aF0.97157089823090415 aa(lp98 F0.97221220901382954 aF0.97298067157376034 aa(lp99 F0.97359910488105328 aF0.97425445408315448 aa(lp100 F0.97471741205065576 aF0.97538755572485347 aa(lp101 F0.97568517372763408 aF0.97645777232246078 aa(lp102 F0.9766657385604286 aF0.97752550338913813 aa(lp103 F0.97770310824915596 aF0.97858819853161993 aa(lp104 F0.97873414191881636 aF0.97961154767170933 aa(lp105 F0.97969698817953776 aF0.98057707888053536 aa(lp106 F0.98059018491674632 aF0.98149311381731141 aa(lp107 F0.98144514242151748 aF0.98237447932045907 aa(lp108 F0.9822793771174021 aF0.98322584702357074 aa(lp109 F0.98308761476657014 aF0.98404385595874921 aa(lp110 F0.98386094372742849 aF0.98482623448523088 aa(lp111 F0.98459912183670995 aF0.98557465979769243 aa(lp112 F0.98530667259346294 aF0.98629180581112708 aa(lp113 F0.9859862242601154 aF0.98697909521869243 aa(lp114 F0.98663798819450366 aF0.98763716794580114 aa(lp115 F0.98726218411359823 aF0.9882668679061376 aa(lp116 F0.9878598381042476 aF0.98886932320521137 aa(lp117 F0.98843212232208533 aF0.98944562960165439 aa(lp118 F0.98897997019478456 aF0.98999676387870861 aa(lp119 F0.98950421434695268 aF0.99052366384374657 aa(lp120 F0.9900057138521493 aF0.99102726323778101 aa(lp121 F0.99048533084920753 aF0.99150847463396263 aa(lp122 F0.99094389667393146 aF0.99196817946878635 aa(lp123 F0.99138221489341061 aF0.99240723136528564 aa(lp124 F0.99180106845662752 aF0.9928264585011457 aa(lp125 F0.99220121908361081 aF0.99322666327048814 aa(lp126 F0.99258340596635941 aF0.99360862214408374 aa(lp127 F0.99294834598224102 aF0.99397308610450508 aa(lp128 F0.99329673392712903 aF0.99432078104346822 aa(lp129 F0.99362924273513675 aF0.99465240815934064 aa(lp130 F0.99394652363732072 aF0.99496864437813726 aa(lp131 F0.99424920646297055 aF0.99527014281086346 aa(lp132 F0.99453789986002183 aF0.99555753322970586 aa(lp133 F0.99481319175385174 aF0.99583142260135049 aa(lp134 F0.99507564954786931 aF0.99609239555967866 aa(lp135 F0.99532582067874464 aF0.99634101499174088 aa(lp136 F0.99556423282508111 aF0.99657782253240612 aa(lp137 F0.99579139457024135 aF0.99680333917433583 aa(lp138 F0.99600779555668595 aF0.99701806575922347 aa(lp139 F0.99621390726272574 aF0.99722248362298993 aa(lp140 F0.99641018310600371 aF0.9974170550733481 aa(lp141 F0.99659705924839781 aF0.99760222403574428 aa(lp142 F0.99677495482327061 aF0.9977784165431739 aa(lp143 F0.99694427254838069 aF0.99794604136362142 aa(lp144 F0.99710539915212193 aF0.99810549049114983 aa(lp145 F0.99725870577926334 aF0.99825713974038921 aa(lp146 F0.99740454881287754 aF0.99840134929275481 aa(lp147 F0.9975432694651577 aF0.99853846409596025 aa(lp148 F0.99767519632771851 aF0.99866881483652681 aa(lp149 F0.9978006409293102 aF0.9987927172170169 aa(lp150 F0.99791990943986575 aF0.99891047574972702 aa(lp151 F0.99803327748379578 aF0.99902237545422079 aa(lp152 F0.99814105578285084 aF0.99912870897829587 aa.galpy-1.3.0/galpy/df_src/df.py0000644000076500000240000000452313120630022016241 0ustar bovystaff00000000000000from galpy.util import config _APY_LOADED= True try: from astropy import units except ImportError: _APY_LOADED= False class df(object): """Top-level class for DF classes""" def __init__(self,ro=None,vo=None): """ NAME: __init__ PURPOSE: initialize a DF object INPUT: ro= (None) distance scale vo= (None) velocity scale OUTPUT: HISTORY: 2016-02-28 - Written - Bovy (UofT) """ # Parse ro and vo if ro is None: self._ro= config.__config__.getfloat('normalization','ro') self._roSet= False else: if _APY_LOADED and isinstance(ro,units.Quantity): ro= ro.to(units.kpc).value self._ro= ro self._roSet= True if vo is None: self._vo= config.__config__.getfloat('normalization','vo') self._voSet= False else: if _APY_LOADED and isinstance(vo,units.Quantity): vo= vo.to(units.km/units.s).value self._vo= vo self._voSet= True return None def turn_physical_off(self): """ NAME: turn_physical_off PURPOSE: turn off automatic returning of outputs in physical units INPUT: (none) OUTPUT: (none) HISTORY: 2017-06-05 - Written - Bovy (UofT) """ self._roSet= False self._voSet= False return None def turn_physical_on(self,ro=None,vo=None): """ NAME: turn_physical_on PURPOSE: turn on automatic returning of outputs in physical units INPUT: ro= reference distance (kpc; can be Quantity) vo= reference velocity (km/s; can be Quantity) OUTPUT: (none) HISTORY: 2016-06-05 - Written - Bovy (UofT) """ self._roSet= True self._voSet= True if not ro is None: if _APY_LOADED and isinstance(ro,units.Quantity): ro= ro.to(units.kpc).value self._ro= ro if not vo is None: if _APY_LOADED and isinstance(vo,units.Quantity): vo= vo.to(units.km/units.s).value self._vo= vo return None galpy-1.3.0/galpy/df_src/diskdf.py0000644000076500000240000027512713175236210017141 0ustar bovystaff00000000000000############################################################################### # diskdf.py: module that interprets (E,Lz) pairs in terms of a # distribution function (following Dehnen 1999) # # This module contains the following classes: # # diskdf - top-level class that represents a distribution function # dehnendf - inherits from diskdf, implements Dehnen's 'new' DF # shudf - inherits from diskdf, implements Shu's DF # DFcorrection - class that represents corrections to the input Sigma(R) # and sigma_R(R) to get closer to the targets ############################################################################### from __future__ import print_function _EPSREL=10.**-14. _NSIGMA= 4. _INTERPDEGREE= 3 _RMIN=10.**-10. _MAXD_REJECTLOS= 4. _PROFILE= False import copy import re import os, os.path import pickle import math import numpy as nu import scipy as sc import scipy.integrate as integrate import scipy.interpolate as interpolate from scipy import linalg from scipy import stats from scipy import optimize from galpy.df_src.surfaceSigmaProfile import * from galpy.orbit import Orbit from galpy.util.bovy_ars import bovy_ars from galpy.util import save_pickles from galpy.util.bovy_conversion import physical_conversion, \ potential_physical_input, _APY_UNITS, surfdens_in_msolpc2 from galpy.potential import PowerSphericalPotential from galpy.actionAngle import actionAngleAdiabatic, actionAngleAxi from galpy.df_src.df import df, _APY_LOADED if _APY_LOADED: from astropy import units #scipy version try: sversion=re.split(r'\.',sc.__version__) _SCIPYVERSION=float(sversion[0])+float(sversion[1])/10. except: #pragma: no cover raise ImportError( "scipy.__version__ not understood, contact galpy developer, send scipy.__version__") _CORRECTIONSDIR=os.path.join(os.path.dirname(os.path.realpath(__file__)),'data') _DEGTORAD= math.pi/180. class diskdf(df): """Class that represents a disk DF""" def __init__(self,dftype='dehnen', surfaceSigma=expSurfaceSigmaProfile, profileParams=(1./3.,1.0,0.2), correct=False, beta=0.,ro=None,vo=None,**kwargs): """ NAME: __init__ PURPOSE: Initialize a DF INPUT: dftype= 'dehnen' or 'corrected-dehnen', 'shu' or 'corrected-shu' surfaceSigma - instance or class name of the target surface density and sigma_R profile (default: both exponential) profileParams - parameters of the surface and sigma_R profile: (xD,xS,Sro) where xD - disk surface mass scalelength / Ro xS - disk velocity dispersion scalelength / Ro Sro - disk velocity dispersion at Ro (/vo) Directly given to the 'surfaceSigmaProfile class, so could be anything that class takes beta - power-law index of the rotation curve correct - correct the DF (i.e., DFcorrection kwargs are also given) + DFcorrection kwargs (except for those already specified) OUTPUT: HISTORY: 2010-03-10 - Written - Bovy (NYU) """ df.__init__(self,ro=ro,vo=vo) self._dftype= dftype if isinstance(surfaceSigma,surfaceSigmaProfile): self._surfaceSigmaProfile= surfaceSigma else: if _APY_LOADED and isinstance(profileParams[0],units.Quantity): newprofileParams=\ (profileParams[0].to(units.kpc).value/self._ro, profileParams[1].to(units.kpc).value/self._ro, profileParams[2].to(units.km/units.s).value/self._vo) self._roSet= True self._voSet= True profileParams= newprofileParams self._surfaceSigmaProfile= surfaceSigma(profileParams) self._beta= beta self._gamma= sc.sqrt(2./(1.+self._beta)) if correct or 'corrections' in kwargs or 'rmax' in kwargs \ or 'niter' in kwargs or 'npoints' in kwargs: self._correct= True #Load corrections self._corr= DFcorrection(dftype=self.__class__, surfaceSigmaProfile=self._surfaceSigmaProfile, beta=beta,**kwargs) else: self._correct= False #Setup aA objects for frequency and rap,rperi calculation self._aA= actionAngleAdiabatic(pot=PowerSphericalPotential(normalize=1., alpha=2.-2.*self._beta),gamma=0.) return None @physical_conversion('phasespacedensity2d',pop=True) def __call__(self,*args,**kwargs): """ NAME: __call__ PURPOSE: evaluate the distribution function INPUT: either an orbit instance, a list of such instances, or E,Lz 1) Orbit instance or list: a) Orbit instance alone: use vxvv member b) Orbit instance + t: call the Orbit instance (for list, each instance is called at t) 2) E - energy (/vo^2; or can be Quantity) L - angular momentun (/ro/vo; or can be Quantity) 3) array vxvv [3/4,nt] [must be in natural units /vo,/ro; use Orbit interface for physical-unit input) KWARGS: marginalizeVperp - marginalize over perpendicular velocity (only supported with 1a) for single orbits above) marginalizeVlos - marginalize over line-of-sight velocity (only supported with 1a) for single orbits above) nsigma= number of sigma to integrate over when marginalizing +scipy.integrate.quad keywords OUTPUT: DF(orbit/E,L) HISTORY: 2010-07-10 - Written - Bovy (NYU) """ if isinstance(args[0],Orbit): if len(args) == 1: if kwargs.pop('marginalizeVperp',False): return self._call_marginalizevperp(args[0],**kwargs) elif kwargs.pop('marginalizeVlos',False): return self._call_marginalizevlos(args[0],**kwargs) else: return sc.real(self.eval(*vRvTRToEL(args[0]._orb.vxvv[1], args[0]._orb.vxvv[2], args[0]._orb.vxvv[0], self._beta, self._dftype))) else: no= args[0](args[1]) return sc.real(self.eval(*vRvTRToEL(no._orb.vxvv[1], no._orb.vxvv[2], no._orb.vxvv[0], self._beta, self._dftype))) elif isinstance(args[0],list) \ and isinstance(args[0][0],Orbit): #Grab all of the vR, vT, and R vR= nu.array([o._orb.vxvv[1] for o in args[0]]) vT= nu.array([o._orb.vxvv[2] for o in args[0]]) R= nu.array([o._orb.vxvv[0] for o in args[0]]) return sc.real(self.eval(*vRvTRToEL(vR,vT,R,self._beta, self._dftype))) elif isinstance(args[0],nu.ndarray) and \ not (hasattr(args[0],'isscalar') and args[0].isscalar): #Grab all of the vR, vT, and R vR= args[0][1] vT= args[0][2] R= args[0][0] return sc.real(self.eval(*vRvTRToEL(vR,vT,R,self._beta, self._dftype))) else: return sc.real(self.eval(*args)) def _call_marginalizevperp(self,o,**kwargs): """Call the DF, marginalizing over perpendicular velocity""" #Get l, vlos l= o.ll(obs=[1.,0.,0.],ro=1.)*_DEGTORAD vlos= o.vlos(ro=1.,vo=1.,obs=[1.,0.,0.,0.,0.,0.]) R= o.R(use_physical=False) phi= o.phi(use_physical=False) #Get local circular velocity, projected onto the los vcirc= R**self._beta vcirclos= vcirc*math.sin(phi+l) #Marginalize alphalos= phi+l if not 'nsigma' in kwargs or ('nsigma' in kwargs and \ kwargs['nsigma'] is None): nsigma= _NSIGMA else: nsigma= kwargs['nsigma'] kwargs.pop('nsigma',None) sigmaR2= self.targetSigma2(R,use_physical=False) sigmaR1= sc.sqrt(sigmaR2) #Use the asymmetric drift equation to estimate va va= sigmaR2/2./R**self._beta*(1./self._gamma**2.-1. -R*self._surfaceSigmaProfile.surfacemassDerivative(R,log=True) -R*self._surfaceSigmaProfile.sigma2Derivative(R,log=True)) if math.fabs(va) > sigmaR1: va = 0. #To avoid craziness near the center if math.fabs(math.sin(alphalos)) < math.sqrt(1./2.): cosalphalos= math.cos(alphalos) tanalphalos= math.tan(alphalos) return integrate.quad(_marginalizeVperpIntegrandSinAlphaSmall, -self._gamma*va/sigmaR1-nsigma, -self._gamma*va/sigmaR1+nsigma, args=(self,R,cosalphalos,tanalphalos, vlos-vcirclos,vcirc, sigmaR1/self._gamma), **kwargs)[0]/math.fabs(cosalphalos)\ *sigmaR1/self._gamma else: sinalphalos= math.sin(alphalos) cotalphalos= 1./math.tan(alphalos) return integrate.quad(_marginalizeVperpIntegrandSinAlphaLarge, -nsigma,nsigma, args=(self,R,sinalphalos,cotalphalos, vlos-vcirclos,vcirc,sigmaR1), **kwargs)[0]/math.fabs(sinalphalos)*sigmaR1 def _call_marginalizevlos(self,o,**kwargs): """Call the DF, marginalizing over line-of-sight velocity""" #Get d, l, vperp l= o.ll(obs=[1.,0.,0.],ro=1.)*_DEGTORAD vperp= o.vll(ro=1.,vo=1.,obs=[1.,0.,0.,0.,0.,0.]) R= o.R(use_physical=False) phi= o.phi(use_physical=False) #Get local circular velocity, projected onto the perpendicular #direction vcirc= R**self._beta vcircperp= vcirc*math.cos(phi+l) #Marginalize alphaperp= math.pi/2.+phi+l if not 'nsigma' in kwargs or ('nsigma' in kwargs and \ kwargs['nsigma'] is None): nsigma= _NSIGMA else: nsigma= kwargs['nsigma'] kwargs.pop('nsigma',None) sigmaR2= self.targetSigma2(R,use_physical=False) sigmaR1= sc.sqrt(sigmaR2) #Use the asymmetric drift equation to estimate va va= sigmaR2/2./R**self._beta*(1./self._gamma**2.-1. -R*self._surfaceSigmaProfile.surfacemassDerivative(R,log=True) -R*self._surfaceSigmaProfile.sigma2Derivative(R,log=True)) if math.fabs(va) > sigmaR1: va = 0. #To avoid craziness near the center if math.fabs(math.sin(alphaperp)) < math.sqrt(1./2.): cosalphaperp= math.cos(alphaperp) tanalphaperp= math.tan(alphaperp) #we can reuse the VperpIntegrand, since it is just another angle return integrate.quad(_marginalizeVperpIntegrandSinAlphaSmall, -self._gamma*va/sigmaR1-nsigma, -self._gamma*va/sigmaR1+nsigma, args=(self,R,cosalphaperp,tanalphaperp, vperp-vcircperp,vcirc, sigmaR1/self._gamma), **kwargs)[0]/math.fabs(cosalphaperp)\ *sigmaR1/self._gamma else: sinalphaperp= math.sin(alphaperp) cotalphaperp= 1./math.tan(alphaperp) #we can reuse the VperpIntegrand, since it is just another angle return integrate.quad(_marginalizeVperpIntegrandSinAlphaLarge, -nsigma,nsigma, args=(self,R,sinalphaperp,cotalphaperp, vperp-vcircperp,vcirc,sigmaR1), **kwargs)[0]/math.fabs(sinalphaperp)*sigmaR1 @potential_physical_input @physical_conversion('velocity2',pop=True) def targetSigma2(self,R,log=False): """ NAME: targetSigma2 PURPOSE: evaluate the target Sigma_R^2(R) INPUT: R - radius at which to evaluate (can be Quantity) OUTPUT: target Sigma_R^2(R) log - if True, return the log (default: False) HISTORY: 2010-03-28 - Written - Bovy (NYU) """ return self._surfaceSigmaProfile.sigma2(R,log=log) @potential_physical_input @physical_conversion('surfacedensity',pop=True) def targetSurfacemass(self,R,log=False): """ NAME: targetSurfacemass PURPOSE: evaluate the target surface mass at R INPUT: R - radius at which to evaluate (can be Quantity) log - if True, return the log (default: False) OUTPUT: Sigma(R) HISTORY: 2010-03-28 - Written - Bovy (NYU) """ return self._surfaceSigmaProfile.surfacemass(R,log=log) @physical_conversion('surfacedensitydistance',pop=True) def targetSurfacemassLOS(self,d,l,log=False,deg=True): """ NAME: targetSurfacemassLOS PURPOSE: evaluate the target surface mass along the LOS given l and d INPUT: d - distance along the line of sight (can be Quantity) l - Galactic longitude (in deg, unless deg=False; can be Quantity) deg= if False, l is in radians log - if True, return the log (default: False) OUTPUT: Sigma(d,l) x d HISTORY: 2011-03-23 - Written - Bovy (NYU) """ #Calculate R and phi if _APY_LOADED and isinstance(l,units.Quantity): lrad= l.to(units.rad).value elif deg: lrad= l*_DEGTORAD else: lrad= l if _APY_LOADED and isinstance(d,units.Quantity): d= d.to(units.kpc).value/self._ro R, phi= _dlToRphi(d,lrad) if log: return self._surfaceSigmaProfile.surfacemass(R,log=log)\ +math.log(d) else: return self._surfaceSigmaProfile.surfacemass(R,log=log)\ *d @physical_conversion('surfacedensitydistance',pop=True) def surfacemassLOS(self,d,l,deg=True,target=True, romberg=False,nsigma=None,relative=None): """ NAME: surfacemassLOS PURPOSE: evaluate the surface mass along the LOS given l and d INPUT: d - distance along the line of sight (can be Quantity) l - Galactic longitude (in deg, unless deg=False; can be Quantity) OPTIONAL INPUT: nsigma - number of sigma to integrate the velocities over KEYWORDS: target= if True, use target surfacemass (default) romberg - if True, use a romberg integrator (default: False) deg= if False, l is in radians OUTPUT: Sigma(d,l) x d HISTORY: 2011-03-24 - Written - Bovy (NYU) """ #Calculate R and phi if _APY_LOADED and isinstance(l,units.Quantity): lrad= l.to(units.rad).value elif deg: lrad= l*_DEGTORAD else: lrad= l if _APY_LOADED and isinstance(d,units.Quantity): d= d.to(units.kpc).value/self._ro R, phi= _dlToRphi(d,lrad) if target: if relative: return d else: return self.targetSurfacemass(R,use_physical=False)*d else: return self.surfacemass(R,romberg=romberg,nsigma=nsigma, relative=relative,use_physical=False)\ *d @physical_conversion('position',pop=True) def sampledSurfacemassLOS(self,l,n=1,maxd=None,target=True): """ NAME: sampledSurfacemassLOS PURPOSE: sample a distance along the line of sight INPUT: l - Galactic longitude (in rad; can be Quantity) n= number of distances to sample maxd= maximum distance to consider (for the rejection sampling) (can be Quantity) target= if True, sample from the 'target' surface mass density, rather than the actual surface mass density (default=True) OUTPUT: list of samples HISTORY: 2011-03-24 - Written - Bovy (NYU) """ #First calculate where the maximum is if target: minR= optimize.fmin_bfgs(lambda x: \ -self.targetSurfacemassLOS(x,l, use_physical=False, deg=False), 0.,disp=False)[0] maxSM= self.targetSurfacemassLOS(minR,l,deg=False, use_physical=False) else: minR= optimize.fmin_bfgs(lambda x: \ -self.surfacemassLOS(x,l, deg=False, use_physical=False), 0.,disp=False)[0] maxSM= self.surfacemassLOS(minR,l,deg=False,use_physical=False) #Now rejection-sample if _APY_LOADED and isinstance(l,units.Quantity): l= l.to(units.rad).value if _APY_LOADED and isinstance(maxd,units.Quantity): maxd= maxd.to(units.kpc).value/self._ro if maxd is None: maxd= _MAXD_REJECTLOS out= [] while len(out) < n: #sample prop= nu.random.random()*maxd if target: surfmassatprop= self.targetSurfacemassLOS(prop,l,deg=False, use_physical=False) else: surfmassatprop= self.surfacemassLOS(prop,l,deg=False, use_physical=False) if surfmassatprop/maxSM > nu.random.random(): #accept out.append(prop) return nu.array(out) @potential_physical_input @physical_conversion('velocity',pop=True) def sampleVRVT(self,R,n=1,nsigma=None,target=True): """ NAME: sampleVRVT PURPOSE: sample a radial and azimuthal velocity at R INPUT: R - Galactocentric distance (can be Quantity) n= number of distances to sample nsigma= number of sigma to rejection-sample on target= if True, sample using the 'target' sigma_R rather than the actual sigma_R (default=True) OUTPUT: list of samples BUGS: should use the fact that vR and vT separate HISTORY: 2011-03-24 - Written - Bovy (NYU) """ #Determine where the max of the v-distribution is using asymmetric drift maxVR= 0. maxVT= optimize.brentq(_vtmaxEq,0.,R**self._beta+0.2,(R,self)) maxVD= self(Orbit([R,maxVR,maxVT])) #Now rejection-sample if nsigma == None: nsigma= _NSIGMA out= [] if target: sigma= math.sqrt(self.targetSigma2(R,use_physical=False)) else: sigma= math.sqrt(self.sigma2(R,use_physical=False)) while len(out) < n: #sample vrg, vtg= nu.random.normal(), nu.random.normal() propvR= vrg*nsigma*sigma propvT= vtg*nsigma*sigma/self._gamma+maxVT VDatprop= self(Orbit([R,propvR,propvT])) if VDatprop/maxVD > nu.random.uniform()*nu.exp(-0.5*(vrg**2.+vtg**2.)): #accept out.append(sc.array([propvR,propvT])) return nu.array(out) def sampleLOS(self,los,n=1,deg=True,maxd=None,nsigma=None, targetSurfmass=True,targetSigma2=True): """ NAME: sampleLOS PURPOSE: sample along a given LOS INPUT: los - line of sight (in deg, unless deg=False; can be Quantity) n= number of desired samples deg= los in degrees? (default=True) targetSurfmass, targetSigma2= if True, use target surface mass and sigma2 profiles, respectively (there is not much point to doing the latter) (default=True) OUTPUT: returns list of Orbits BUGS: target=False uses target distribution for derivatives (this is a detail) HISTORY: 2011-03-24 - Started - Bovy (NYU) """ if _APY_LOADED and isinstance(los,units.Quantity): l= los.to(units.rad).value elif deg: l= los*_DEGTORAD else: l= los out= [] #sample distances ds= self.sampledSurfacemassLOS(l,n=n,maxd=maxd,target=targetSurfmass, use_physical=False) for ii in range(int(n)): #Calculate R and phi thisR,thisphi= _dlToRphi(ds[ii],l) #sample velocities vv= self.sampleVRVT(thisR,n=1,nsigma=nsigma,target=targetSigma2, use_physical=False)[0] if self._roSet and self._voSet: out.append(Orbit([thisR,vv[0],vv[1],thisphi],ro=self._ro, vo=self._vo)) else: out.append(Orbit([thisR,vv[0],vv[1],thisphi])) return out @potential_physical_input @physical_conversion('velocity',pop=True) def asymmetricdrift(self,R): """ NAME: asymmetricdrift PURPOSE: estimate the asymmetric drift (vc-mean-vphi) from an approximation to the Jeans equation INPUT: R - radius at which to calculate the asymmetric drift (can be Quantity) OUTPUT: asymmetric drift at R HISTORY: 2011-04-02 - Written - Bovy (NYU) """ sigmaR2= self.targetSigma2(R,use_physical=False) return sigmaR2/2./R**self._beta*(1./self._gamma**2.-1. -R*self._surfaceSigmaProfile.surfacemassDerivative(R,log=True) -R*self._surfaceSigmaProfile.sigma2Derivative(R,log=True)) @potential_physical_input @physical_conversion('surfacedensity',pop=True) def surfacemass(self,R,romberg=False,nsigma=None,relative=False): """ NAME: surfacemass PURPOSE: calculate the surface-mass at R by marginalizing over velocity INPUT: R - radius at which to calculate the surfacemass density (can be Quantity) OPTIONAL INPUT: nsigma - number of sigma to integrate the velocities over KEYWORDS: romberg - if True, use a romberg integrator (default: False) OUTPUT: surface mass at R HISTORY: 2010-03-XX - Written - Bovy (NYU) """ if nsigma == None: nsigma= _NSIGMA logSigmaR= self.targetSurfacemass(R,log=True,use_physical=False) sigmaR2= self.targetSigma2(R,use_physical=False) sigmaR1= sc.sqrt(sigmaR2) logsigmaR2= sc.log(sigmaR2) if relative: norm= 1. else: norm= sc.exp(logSigmaR) #Use the asymmetric drift equation to estimate va va= sigmaR2/2./R**self._beta*(1./self._gamma**2.-1. -R*self._surfaceSigmaProfile.surfacemassDerivative(R,log=True) -R*self._surfaceSigmaProfile.sigma2Derivative(R,log=True)) if math.fabs(va) > sigmaR1: va = 0.#To avoid craziness near the center if romberg: return sc.real(bovy_dblquad(_surfaceIntegrand, self._gamma*(R**self._beta-va)/sigmaR1-nsigma, self._gamma*(R**self._beta-va)/sigmaR1+nsigma, lambda x: 0., lambda x: nsigma, [R,self,logSigmaR,logsigmaR2,sigmaR1, self._gamma], tol=10.**-8)/sc.pi*norm) else: return integrate.dblquad(_surfaceIntegrand, self._gamma*(R**self._beta-va)/sigmaR1-nsigma, self._gamma*(R**self._beta-va)/sigmaR1+nsigma, lambda x: 0., lambda x: nsigma, (R,self,logSigmaR,logsigmaR2,sigmaR1, self._gamma), epsrel=_EPSREL)[0]/sc.pi*norm @potential_physical_input @physical_conversion('velocity2surfacedensity',pop=True) def sigma2surfacemass(self,R,romberg=False,nsigma=None, relative=False): """ NAME: sigma2surfacemass PURPOSE: calculate the product sigma_R^2 x surface-mass at R by marginalizing over velocity INPUT: R - radius at which to calculate the sigma_R^2 x surfacemass density (can be Quantity) OPTIONAL INPUT: nsigma - number of sigma to integrate the velocities over KEYWORDS: romberg - if True, use a romberg integrator (default: False) OUTPUT: sigma_R^2 x surface-mass at R HISTORY: 2010-03-XX - Written - Bovy (NYU) """ if nsigma == None: nsigma= _NSIGMA logSigmaR= self.targetSurfacemass(R,log=True,use_physical=False) sigmaR2= self.targetSigma2(R,use_physical=False) sigmaR1= sc.sqrt(sigmaR2) logsigmaR2= sc.log(sigmaR2) if relative: norm= 1. else: norm= sc.exp(logSigmaR+logsigmaR2) #Use the asymmetric drift equation to estimate va va= sigmaR2/2./R**self._beta*(1./self._gamma**2.-1. -R*self._surfaceSigmaProfile.surfacemassDerivative(R,log=True) -R*self._surfaceSigmaProfile.sigma2Derivative(R,log=True)) if math.fabs(va) > sigmaR1: va = 0. #To avoid craziness near the center if romberg: return sc.real(bovy_dblquad(_sigma2surfaceIntegrand, self._gamma*(R**self._beta-va)/sigmaR1-nsigma, self._gamma*(R**self._beta-va)/sigmaR1+nsigma, lambda x: 0., lambda x: nsigma, [R,self,logSigmaR,logsigmaR2,sigmaR1, self._gamma], tol=10.**-8)/sc.pi*norm) else: return integrate.dblquad(_sigma2surfaceIntegrand, self._gamma*(R**self._beta-va)/sigmaR1-nsigma, self._gamma*(R**self._beta-va)/sigmaR1+nsigma, lambda x: 0., lambda x: nsigma, (R,self,logSigmaR,logsigmaR2,sigmaR1, self._gamma), epsrel=_EPSREL)[0]/sc.pi*norm def vmomentsurfacemass(self,*args,**kwargs): """ NAME: vmomentsurfacemass PURPOSE: calculate the an arbitrary moment of the velocity distribution at R times the surfacmass INPUT: R - radius at which to calculate the moment (in natural units) n - vR^n m - vT^m OPTIONAL INPUT: nsigma - number of sigma to integrate the velocities over KEYWORDS: romberg - if True, use a romberg integrator (default: False) deriv= None, 'R', or 'phi': calculates derivative of the moment wrt R or phi OUTPUT: at R (no support for units) HISTORY: 2011-03-30 - Written - Bovy (NYU) """ use_physical= kwargs.pop('use_physical',True) ro= kwargs.pop('ro',None) if ro is None and hasattr(self,'_roSet') and self._roSet: ro= self._ro if _APY_LOADED and isinstance(ro,units.Quantity): ro= ro.to(units.kpc).value vo= kwargs.pop('vo',None) if vo is None and hasattr(self,'_voSet') and self._voSet: vo= self._vo if _APY_LOADED and isinstance(vo,units.Quantity): vo= vo.to(units.km/units.s).value if use_physical and not vo is None and not ro is None: fac= surfdens_in_msolpc2(vo,ro)*vo**(args[1]+args[2]) if _APY_UNITS: u= units.Msun/units.pc**2*(units.km/units.s)**(args[1]+args[2]) out= self._vmomentsurfacemass(*args,**kwargs) if _APY_UNITS: return units.Quantity(out*fac,unit=u) else: return out*fac else: return self._vmomentsurfacemass(*args,**kwargs) def _vmomentsurfacemass(self,R,n,m,romberg=False,nsigma=None, relative=False,phi=0.,deriv=None): """Non-physical version of vmomentsurfacemass, otherwise the same""" #odd moments of vR are zero if isinstance(n,int) and n%2 == 1: return 0. if nsigma == None: nsigma= _NSIGMA logSigmaR= self.targetSurfacemass(R,log=True,use_physical=False) sigmaR2= self.targetSigma2(R,use_physical=False) sigmaR1= sc.sqrt(sigmaR2) logsigmaR2= sc.log(sigmaR2) if relative: norm= 1. else: norm= sc.exp(logSigmaR+logsigmaR2*(n+m)/2.)/self._gamma**m #Use the asymmetric drift equation to estimate va va= sigmaR2/2./R**self._beta*(1./self._gamma**2.-1. -R*self._surfaceSigmaProfile.surfacemassDerivative(R,log=True) -R*self._surfaceSigmaProfile.sigma2Derivative(R,log=True)) if math.fabs(va) > sigmaR1: va = 0. #To avoid craziness near the center if deriv is None: if romberg: return sc.real(bovy_dblquad(_vmomentsurfaceIntegrand, self._gamma*(R**self._beta-va)/sigmaR1-nsigma, self._gamma*(R**self._beta-va)/sigmaR1+nsigma, lambda x: -nsigma, lambda x: nsigma, [R,self,logSigmaR,logsigmaR2,sigmaR1, self._gamma,n,m], tol=10.**-8)/sc.pi*norm/2.) else: return integrate.dblquad(_vmomentsurfaceIntegrand, self._gamma*(R**self._beta-va)/sigmaR1-nsigma, self._gamma*(R**self._beta-va)/sigmaR1+nsigma, lambda x: -nsigma, lambda x: nsigma, (R,self,logSigmaR,logsigmaR2,sigmaR1, self._gamma,n,m), epsrel=_EPSREL)[0]/sc.pi*norm/2. else: if romberg: return sc.real(bovy_dblquad(_vmomentderivsurfaceIntegrand, self._gamma*(R**self._beta-va)/sigmaR1-nsigma, self._gamma*(R**self._beta-va)/sigmaR1+nsigma, lambda x: -nsigma, lambda x: nsigma, [R,self,logSigmaR,logsigmaR2,sigmaR1, self._gamma,n,m,deriv], tol=10.**-8)/sc.pi*norm/2.) else: return integrate.dblquad(_vmomentderivsurfaceIntegrand, self._gamma*(R**self._beta-va)/sigmaR1-nsigma, self._gamma*(R**self._beta-va)/sigmaR1+nsigma, lambda x: -nsigma, lambda x: nsigma, (R,self,logSigmaR,logsigmaR2,sigmaR1, self._gamma,n,m,deriv), epsrel=_EPSREL)[0]/sc.pi*norm/2. @potential_physical_input @physical_conversion('frequency-kmskpc',pop=True) def oortA(self,R,romberg=False,nsigma=None,phi=0.): """ NAME: oortA PURPOSE: calculate the Oort function A INPUT: R - radius at which to calculate A (can be Quantity) OPTIONAL INPUT: nsigma - number of sigma to integrate the velocities over KEYWORDS: romberg - if True, use a romberg integrator (default: False) OUTPUT: Oort A at R HISTORY: 2011-04-19 - Written - Bovy (NYU) BUGS: could be made more efficient, e.g., surfacemass is calculated multiple times """ #2A= meanvphi/R-dmeanvR/R/dphi-dmeanvphi/dR meanvphi= self.meanvT(R,romberg=romberg,nsigma=nsigma,phi=phi, use_physical=False) dmeanvRRdphi= 0. #We know this, since the DF does not depend on phi surfmass= self._vmomentsurfacemass(R,0,0,phi=phi,romberg=romberg,nsigma=nsigma) dmeanvphidR= self._vmomentsurfacemass(R,0,1,deriv='R',phi=phi,romberg=romberg,nsigma=nsigma)/\ surfmass\ -self._vmomentsurfacemass(R,0,1,phi=phi,romberg=romberg,nsigma=nsigma)\ /surfmass**2.\ *self._vmomentsurfacemass(R,0,0,deriv='R',phi=phi,romberg=romberg,nsigma=nsigma) return 0.5*(meanvphi/R-dmeanvRRdphi/R-dmeanvphidR) @potential_physical_input @physical_conversion('frequency-kmskpc',pop=True) def oortB(self,R,romberg=False,nsigma=None,phi=0.): """ NAME: oortB PURPOSE: calculate the Oort function B INPUT: R - radius at which to calculate B (can be Quantity) OPTIONAL INPUT: nsigma - number of sigma to integrate the velocities over KEYWORDS: romberg - if True, use a romberg integrator (default: False) OUTPUT: Oort B at R HISTORY: 2011-04-19 - Written - Bovy (NYU) BUGS: could be made more efficient, e.g., surfacemass is calculated multiple times """ #2B= -meanvphi/R+dmeanvR/R/dphi-dmeanvphi/dR meanvphi= self.meanvT(R,romberg=romberg,nsigma=nsigma,phi=phi, use_physical=False) dmeanvRRdphi= 0. #We know this, since the DF does not depend on phi surfmass= self._vmomentsurfacemass(R,0,0,phi=phi,romberg=romberg,nsigma=nsigma) dmeanvphidR= self._vmomentsurfacemass(R,0,1,deriv='R',phi=phi,romberg=romberg,nsigma=nsigma)/\ surfmass\ -self._vmomentsurfacemass(R,0,1,phi=phi,romberg=romberg,nsigma=nsigma)\ /surfmass**2.\ *self._vmomentsurfacemass(R,0,0,deriv='R',phi=phi,romberg=romberg,nsigma=nsigma) return 0.5*(-meanvphi/R+dmeanvRRdphi/R-dmeanvphidR) @potential_physical_input @physical_conversion('frequency-kmskpc',pop=True) def oortC(self,R,romberg=False,nsigma=None,phi=0.): """ NAME: oortC PURPOSE: calculate the Oort function C INPUT: R - radius at which to calculate C (can be Quantity) OPTIONAL INPUT: nsigma - number of sigma to integrate the velocities over KEYWORDS: romberg - if True, use a romberg integrator (default: False) OUTPUT: Oort C at R HISTORY: 2011-04-19 - Written - Bovy (NYU) BUGS: could be made more efficient, e.g., surfacemass is calculated multiple times we know this is zero, but it is calculated anyway (bug or feature?) """ #2C= -meanvR/R-dmeanvphi/R/dphi+dmeanvR/dR meanvr= self.meanvR(R,romberg=romberg,nsigma=nsigma,phi=phi, use_physical=False) dmeanvphiRdphi= 0. #We know this, since the DF does not depend on phi surfmass= self._vmomentsurfacemass(R,0,0,phi=phi,romberg=romberg,nsigma=nsigma) dmeanvRdR= self._vmomentsurfacemass(R,1,0,deriv='R',phi=phi,romberg=romberg,nsigma=nsigma)/\ surfmass #other terms is zero because f is even in vR return 0.5*(-meanvr/R-dmeanvphiRdphi/R+dmeanvRdR) @potential_physical_input @physical_conversion('frequency-kmskpc',pop=True) def oortK(self,R,romberg=False,nsigma=None,phi=0.): """ NAME: oortK PURPOSE: calculate the Oort function K INPUT: R - radius at which to calculate K (can be Quantity) OPTIONAL INPUT: nsigma - number of sigma to integrate the velocities over KEYWORDS: romberg - if True, use a romberg integrator (default: False) OUTPUT: Oort K at R HISTORY: 2011-04-19 - Written - Bovy (NYU) BUGS: could be made more efficient, e.g., surfacemass is calculated multiple times we know this is zero, but it is calculated anyway (bug or feature?) """ #2K= meanvR/R+dmeanvphi/R/dphi+dmeanvR/dR meanvr= self.meanvR(R,romberg=romberg,nsigma=nsigma,phi=phi, use_physical=False) dmeanvphiRdphi= 0. #We know this, since the DF does not depend on phi surfmass= self._vmomentsurfacemass(R,0,0,phi=phi,romberg=romberg,nsigma=nsigma) dmeanvRdR= self._vmomentsurfacemass(R,1,0,deriv='R',phi=phi,romberg=romberg,nsigma=nsigma)/\ surfmass #other terms is zero because f is even in vR return 0.5*(+meanvr/R+dmeanvphiRdphi/R+dmeanvRdR) @potential_physical_input @physical_conversion('velocity2',pop=True) def sigma2(self,R,romberg=False,nsigma=None,phi=0.): """ NAME: sigma2 PURPOSE: calculate sigma_R^2 at R by marginalizing over velocity INPUT: R - radius at which to calculate sigma_R^2 density (can be Quantity) OPTIONAL INPUT: nsigma - number of sigma to integrate the velocities over KEYWORDS: romberg - if True, use a romberg integrator (default: False) OUTPUT: sigma_R^2 at R HISTORY: 2010-03-XX - Written - Bovy (NYU) """ return self.sigma2surfacemass(R,romberg,nsigma,use_physical=False)\ /self.surfacemass(R,romberg,nsigma,use_physical=False) @potential_physical_input @physical_conversion('velocity2',pop=True) def sigmaT2(self,R,romberg=False,nsigma=None,phi=0.): """ NAME: sigmaT2 PURPOSE: calculate sigma_T^2 at R by marginalizing over velocity INPUT: R - radius at which to calculate sigma_T^2 (can be Quantity) OPTIONAL INPUT: nsigma - number of sigma to integrate the velocities over KEYWORDS: romberg - if True, use a romberg integrator (default: False) OUTPUT: sigma_T^2 at R HISTORY: 2011-03-30 - Written - Bovy (NYU) """ surfmass= self.surfacemass(R,romberg=romberg,nsigma=nsigma, use_physical=False) return (self._vmomentsurfacemass(R,0,2,romberg=romberg,nsigma=nsigma) -self._vmomentsurfacemass(R,0,1,romberg=romberg,nsigma=nsigma)\ **2.\ /surfmass)/surfmass @potential_physical_input @physical_conversion('velocity2',pop=True) def sigmaR2(self,R,romberg=False,nsigma=None,phi=0.): """ NAME: sigmaR2 (duplicate of sigma2 for consistency) PURPOSE: calculate sigma_R^2 at R by marginalizing over velocity INPUT: R - radius at which to calculate sigma_R^2 (can be Quantity) OPTIONAL INPUT: nsigma - number of sigma to integrate the velocities over KEYWORDS: romberg - if True, use a romberg integrator (default: False) OUTPUT: sigma_R^2 at R HISTORY: 2011-03-30 - Written - Bovy (NYU) """ return self.sigma2(R,romberg=romberg,nsigma=nsigma,use_physical=False) @potential_physical_input @physical_conversion('velocity',pop=True) def meanvT(self,R,romberg=False,nsigma=None,phi=0.): """ NAME: meanvT PURPOSE: calculate at R by marginalizing over velocity INPUT: R - radius at which to calculate (can be Quantity) OPTIONAL INPUT: nsigma - number of sigma to integrate the velocities over KEYWORDS: romberg - if True, use a romberg integrator (default: False) OUTPUT: at R HISTORY: 2011-03-30 - Written - Bovy (NYU) """ return self._vmomentsurfacemass(R,0,1,romberg=romberg,nsigma=nsigma)\ /self.surfacemass(R,romberg=romberg,nsigma=nsigma, use_physical=False) @potential_physical_input @physical_conversion('velocity',pop=True) def meanvR(self,R,romberg=False,nsigma=None,phi=0.): """ NAME: meanvR PURPOSE: calculate at R by marginalizing over velocity INPUT: R - radius at which to calculate (can be Quantity) OPTIONAL INPUT: nsigma - number of sigma to integrate the velocities over KEYWORDS: romberg - if True, use a romberg integrator (default: False) OUTPUT: at R HISTORY: 2011-03-30 - Written - Bovy (NYU) """ return self._vmomentsurfacemass(R,1,0,romberg=romberg,nsigma=nsigma)\ /self.surfacemass(R,romberg=romberg,nsigma=nsigma, use_physical=False) @potential_physical_input def skewvT(self,R,romberg=False,nsigma=None,phi=0.): """ NAME: skewvT PURPOSE: calculate skew in vT at R by marginalizing over velocity INPUT: R - radius at which to calculate (can be Quantity) OPTIONAL INPUT: nsigma - number of sigma to integrate the velocities over KEYWORDS: romberg - if True, use a romberg integrator (default: False) OUTPUT: skewvT HISTORY: 2011-12-07 - Written - Bovy (NYU) """ surfmass= self.surfacemass(R,romberg=romberg,nsigma=nsigma, use_physical=False) vt= self._vmomentsurfacemass(R,0,1,romberg=romberg,nsigma=nsigma)\ /surfmass vt2= self._vmomentsurfacemass(R,0,2,romberg=romberg,nsigma=nsigma)\ /surfmass vt3= self._vmomentsurfacemass(R,0,3,romberg=romberg,nsigma=nsigma)\ /surfmass s2= vt2-vt**2. return (vt3-3.*vt*vt2+2.*vt**3.)*s2**(-1.5) @potential_physical_input def skewvR(self,R,romberg=False,nsigma=None,phi=0.): """ NAME: skewvR PURPOSE: calculate skew in vR at R by marginalizing over velocity INPUT: R - radius at which to calculate (can be Quantity) OPTIONAL INPUT: nsigma - number of sigma to integrate the velocities over KEYWORDS: romberg - if True, use a romberg integrator (default: False) OUTPUT: skewvR HISTORY: 2011-12-07 - Written - Bovy (NYU) """ surfmass= self.surfacemass(R,romberg=romberg,nsigma=nsigma, use_physical=False) vr= self._vmomentsurfacemass(R,1,0,romberg=romberg,nsigma=nsigma)\ /surfmass vr2= self._vmomentsurfacemass(R,2,0,romberg=romberg,nsigma=nsigma)\ /surfmass vr3= self._vmomentsurfacemass(R,3,0,romberg=romberg,nsigma=nsigma)\ /surfmass s2= vr2-vr**2. return (vr3-3.*vr*vr2+2.*vr**3.)*s2**(-1.5) @potential_physical_input def kurtosisvT(self,R,romberg=False,nsigma=None,phi=0.): """ NAME: kurtosisvT PURPOSE: calculate excess kurtosis in vT at R by marginalizing over velocity INPUT: R - radius at which to calculate (can be Quantity) OPTIONAL INPUT: nsigma - number of sigma to integrate the velocities over KEYWORDS: romberg - if True, use a romberg integrator (default: False) OUTPUT: kurtosisvT HISTORY: 2011-12-07 - Written - Bovy (NYU) """ surfmass= self.surfacemass(R,romberg=romberg,nsigma=nsigma, use_physical=False) vt= self._vmomentsurfacemass(R,0,1,romberg=romberg,nsigma=nsigma)\ /surfmass vt2= self._vmomentsurfacemass(R,0,2,romberg=romberg,nsigma=nsigma)\ /surfmass vt3= self._vmomentsurfacemass(R,0,3,romberg=romberg,nsigma=nsigma)\ /surfmass vt4= self._vmomentsurfacemass(R,0,4,romberg=romberg,nsigma=nsigma)\ /surfmass s2= vt2-vt**2. return (vt4-4.*vt*vt3+6.*vt**2.*vt2-3.*vt**4.)*s2**(-2.)-3. @potential_physical_input def kurtosisvR(self,R,romberg=False,nsigma=None,phi=0.): """ NAME: kurtosisvR PURPOSE: calculate excess kurtosis in vR at R by marginalizing over velocity INPUT: R - radius at which to calculate (can be Quantity) OPTIONAL INPUT: nsigma - number of sigma to integrate the velocities over KEYWORDS: romberg - if True, use a romberg integrator (default: False) OUTPUT: kurtosisvR HISTORY: 2011-12-07 - Written - Bovy (NYU) """ surfmass= self.surfacemass(R,romberg=romberg,nsigma=nsigma, use_physical=False) vr= self._vmomentsurfacemass(R,1,0,romberg=romberg,nsigma=nsigma)\ /surfmass vr2= self._vmomentsurfacemass(R,2,0,romberg=romberg,nsigma=nsigma)\ /surfmass vr3= self._vmomentsurfacemass(R,3,0,romberg=romberg,nsigma=nsigma)\ /surfmass vr4= self._vmomentsurfacemass(R,4,0,romberg=romberg,nsigma=nsigma)\ /surfmass s2= vr2-vr**2. return (vr4-4.*vr*vr3+6.*vr**2.*vr2-3.*vr**4.)*s2**(-2.)-3. def _ELtowRRapRperi(self,E,L): """ NAME: _ELtowRRapRperi PURPOSE: calculate the radial frequency based on E,L, also return rap and rperi INPUT: E - energy L - angular momentum OUTPUT: wR(E.L) HISTORY: 2010-07-11 - Written - Bovy (NYU) """ if self._beta == 0.: xE= sc.exp(E-.5) else: #non-flat rotation curve xE= (2.*E/(1.+1./self._beta))**(1./2./self._beta) rperi,rap= self._aA.calcRapRperi(xE,0.,L/xE,0.,0.) #Replace the above w/ aA= actionAngleAxi(xE,0.,L/xE, pot=PowerSphericalPotential(normalize=1., alpha=2.-2.*self._beta).toPlanar()) TR= aA.TR() return (2.*math.pi/TR,rap,rperi) def sample(self,n=1,rrange=None,returnROrbit=True,returnOrbit=False, nphi=1.,los=None,losdeg=True,nsigma=None,maxd=None,target=True): """ NAME: sample PURPOSE: sample n*nphi points from this DF INPUT: n - number of desired sample (specifying this rather than calling this routine n times is more efficient) rrange - if you only want samples in this rrange, set this keyword (only works when asking for an (RZ)Orbit) (can be Quantity) returnROrbit - if True, return a planarROrbit instance: [R,vR,vT] (default) returnOrbit - if True, return a planarOrbit instance (including phi) nphi - number of azimuths to sample for each E,L los= line of sight sampling along this line of sight (can be Quantity) losdeg= los in degrees? (default=True) target= if True, use target surface mass and sigma2 profiles (default=True) nsigma= number of sigma to rejection-sample on maxd= maximum distance to consider (for the rejection sampling) OUTPUT: n*nphi list of [[E,Lz],...] or list of planar(R)Orbits CAUTION: lists of EL need to be post-processed to account for the \kappa/\omega_R discrepancy HISTORY: 2010-07-10 - Started - Bovy (NYU) """ raise NotImplementedError("'sample' method for this disk df is not implemented") def _estimatemeanvR(self,R,phi=0.,log=False): """ NAME: _estimatemeanvR PURPOSE: quickly estimate meanvR (useful in evolveddiskdf where we need an estimate of this but we do not want to spend too much time on it) INPUT: R - radius at which to evaluate (/ro) phi= azimuth (not used) OUTPUT: target Sigma_R^2(R) log - if True, return the log (default: False) HISTORY: 2010-03-28 - Written - Bovy (NYU) """ return 0. def _estimatemeanvT(self,R,phi=0.,log=False): """ NAME: _estimatemeanvT PURPOSE: quickly estimate meanvR (useful in evolveddiskdf where we need an estimate of this but we do not want to spend too much time on it) INPUT: R - radius at which to evaluate (/ro) phi= azimuth (not used) OUTPUT: target Sigma_R^2(R) HISTORY: 2010-03-28 - Written - Bovy (NYU) """ return R**self._beta-self.asymmetricdrift(R,use_physical=False) def _estimateSigmaR2(self,R,phi=0.,log=False): """ NAME: _estimateSigmaR2 PURPOSE: quickly estimate SigmaR2 (useful in evolveddiskdf where we need an estimate of this but we do not want to spend too much time on it) INPUT: R - radius at which to evaluate (/ro) phi= azimuth (not used) OUTPUT: target Sigma_R^2(R) log - if True, return the log (default: False) HISTORY: 2010-03-28 - Written - Bovy (NYU) """ return self.targetSigma2(R,log=log,use_physical=False) def _estimateSigmaT2(self,R,phi=0.,log=False): """ NAME: _estimateSigmaT2 PURPOSE: quickly estimate SigmaT2 (useful in evolveddiskdf where we need an estimate of this but we do not want to spend too much time on it) INPUT: R - radius at which to evaluate (/ro) phi= azimuth (not used) OUTPUT: target Sigma_R^2(R) log - if True, return the log (default: False) HISTORY: 2010-03-28 - Written - Bovy (NYU) """ if log: return self.targetSigma2(R,log=log,use_physical=False)\ -2.*nu.log(self._gamma) else: return self.targetSigma2(R,log=log,use_physical=False)\ /self._gamma**2. class dehnendf(diskdf): """Dehnen's 'new' df""" def __init__(self,surfaceSigma=expSurfaceSigmaProfile, profileParams=(1./3.,1.0,0.2), correct=False, beta=0.,**kwargs): """ NAME: __init__ PURPOSE: Initialize a Dehnen 'new' DF INPUT: surfaceSigma - instance or class name of the target surface density and sigma_R profile (default: both exponential) profileParams - parameters of the surface and sigma_R profile: (xD,xS,Sro) where xD - disk surface mass scalelength (can be Quantity) xS - disk velocity dispersion scalelength (can be Quantity) Sro - disk velocity dispersion at Ro (can be Quantity) Directly given to the 'surfaceSigmaProfile class, so could be anything that class takes beta - power-law index of the rotation curve correct - if True, correct the DF ro= distance from vantage point to GC (kpc; can be Quantity) vo= circular velocity at ro (km/s; can be Quantity) +DFcorrection kwargs (except for those already specified) OUTPUT: instance HISTORY: 2010-03-10 - Written - Bovy (NYU) """ return diskdf.__init__(self,surfaceSigma=surfaceSigma, profileParams=profileParams, correct=correct,dftype='dehnen', beta=beta,**kwargs) def eval(self,E,L,logSigmaR=0.,logsigmaR2=0.): """ NAME: eval PURPOSE: evaluate the distribution function INPUT: E - energy (can be Quantity) L - angular momentum (can be Quantity) OUTPUT: DF(E,L) HISTORY: 2010-03-10 - Written - Bovy (NYU) 2010-03-28 - Moved to dehnenDF - Bovy (NYU) """ if _PROFILE: #pragma: no cover import time start= time.time() if _APY_LOADED and isinstance(E,units.Quantity): E= E.to(units.km**2/units.s**2).value/self._vo**2. if _APY_LOADED and isinstance(L,units.Quantity): L= L.to(units.kpc*units.km/units.s).value/self._ro/self._vo #Calculate Re,LE, OmegaE if self._beta == 0.: xE= sc.exp(E-.5) logOLLE= sc.log(L/xE-1.) else: #non-flat rotation curve xE= (2.*E/(1.+1./self._beta))**(1./2./self._beta) logOLLE= self._beta*sc.log(xE)+sc.log(L/xE-xE**self._beta) if _PROFILE: #pragma: no cover one_time= (time.time()-start) start= time.time() if self._correct: correction= self._corr.correct(xE,log=True) else: correction= sc.zeros(2) if _PROFILE: #pragma: no cover corr_time= (time.time()-start) start= time.time() SRE2= self.targetSigma2(xE,log=True,use_physical=False)+correction[1] if _PROFILE: #pragma: no cover targSigma_time= (time.time()-start) start= time.time() out= self._gamma*sc.exp(logsigmaR2-SRE2+self.targetSurfacemass(xE,log=True,use_physical=False)-logSigmaR+sc.exp(logOLLE-SRE2)+correction[0])/2./nu.pi out_time= (time.time()-start) tot_time= one_time+corr_time+targSigma_time+out_time print(one_time/tot_time, corr_time/tot_time, targSigma_time/tot_time, out_time/tot_time, tot_time) return out else: return self._gamma*sc.exp(logsigmaR2-SRE2+self.targetSurfacemass(xE,log=True,use_physical=False)-logSigmaR+sc.exp(logOLLE-SRE2)+correction[0])/2./nu.pi def sample(self,n=1,rrange=None,returnROrbit=True,returnOrbit=False, nphi=1.,los=None,losdeg=True,nsigma=None,targetSurfmass=True, targetSigma2=True, maxd=None,**kwargs): """ NAME: sample PURPOSE: sample n*nphi points from this DF INPUT: n - number of desired sample (specifying this rather than calling this routine n times is more efficient) rrange - if you only want samples in this rrange, set this keyword (only works when asking for an (RZ)Orbit returnROrbit - if True, return a planarROrbit instance: [R,vR,vT] (default) returnOrbit - if True, return a planarOrbit instance (including phi) nphi - number of azimuths to sample for each E,L los= if set, sample along this line of sight (deg) (assumes that the Sun is located at R=1,phi=0) losdeg= if False, los is in radians (default=True) targetSurfmass, targetSigma2= if True, use target surface mass and sigma2 profiles, respectively (there is not much point to doing the latter) (default=True) nsigma= number of sigma to rejection-sample on maxd= maximum distance to consider (for the rejection sampling) OUTPUT: n*nphi list of [[E,Lz],...] or list of planar(R)Orbits CAUTION: lists of EL need to be post-processed to account for the \kappa/\omega_R discrepancy; EL not returned in physical units HISTORY: 2010-07-10 - Started - Bovy (NYU) """ if not los is None: return self.sampleLOS(los,deg=losdeg,n=n,maxd=maxd, nsigma=nsigma,targetSurfmass=targetSurfmass, targetSigma2=targetSigma2) #First sample xE if self._correct: xE= sc.array(bovy_ars([0.,0.],[True,False],[0.05,2.],_ars_hx, _ars_hpx,nsamples=n, hxparams=(self._surfaceSigmaProfile, self._corr))) else: xE= sc.array(bovy_ars([0.,0.],[True,False],[0.05,2.],_ars_hx, _ars_hpx,nsamples=n, hxparams=(self._surfaceSigmaProfile, None))) #Calculate E if self._beta == 0.: E= sc.log(xE)+0.5 else: #non-flat rotation curve E= .5*xE**(2.*self._beta)*(1.+1./self._beta) #Then sample Lz LCE= xE**(self._beta+1.) OR= xE**(self._beta-1.) Lz= self._surfaceSigmaProfile.sigma2(xE)*sc.log(stats.uniform.rvs(size=n))/OR if self._correct: Lz*= self._corr.correct(xE,log=False)[1,:] Lz+= LCE if not returnROrbit and not returnOrbit: out= [[e,l] for e,l in zip(E,Lz)] else: if not rrange is None \ and _APY_LOADED and isinstance(rrange[0],units.Quantity): rrange[0]= rrange[0].to(units.kpc).value/self._ro rrange[1]= rrange[1].to(units.kpc).value/self._ro if not hasattr(self,'_psp'): self._psp= PowerSphericalPotential(alpha=2.-self._beta,normalize=True).toPlanar() out= [] for ii in range(int(n)): try: wR, rap, rperi= self._ELtowRRapRperi(E[ii],Lz[ii]) except ValueError: continue TR= 2.*math.pi/wR tr= stats.uniform.rvs()*TR if tr > TR/2.: tr-= TR/2. thisOrbit= Orbit([rperi,0.,Lz[ii]/rperi]) else: thisOrbit= Orbit([rap,0.,Lz[ii]/rap]) thisOrbit.integrate(sc.array([0.,tr]),self._psp) if returnOrbit: vxvv= thisOrbit(tr)._orb.vxvv thisOrbit= Orbit(vxvv=sc.array([vxvv[0],vxvv[1],vxvv[2], stats.uniform.rvs()\ *math.pi*2.])\ .reshape(4)) else: thisOrbit= thisOrbit(tr) kappa= _kappa(thisOrbit._orb.vxvv[0],self._beta) if not rrange == None: if thisOrbit._orb.vxvv[0] < rrange[0] \ or thisOrbit._orb.vxvv[0] > rrange[1]: continue mult= sc.ceil(kappa/wR*nphi)-1. kappawR= kappa/wR*nphi-mult while mult > 0: if returnOrbit: out.append(Orbit(vxvv=sc.array([vxvv[0],vxvv[1], vxvv[2], stats.uniform.rvs()*math.pi*2.]).reshape(4))) else: out.append(thisOrbit) mult-= 1 if stats.uniform.rvs() > kappawR: continue out.append(thisOrbit) #Recurse to get enough if len(out) < n*nphi: out.extend(self.sample(n=int(n-len(out)/nphi),rrange=rrange, returnROrbit=returnROrbit, returnOrbit=returnOrbit,nphi=int(nphi), los=los,losdeg=losdeg)) if len(out) > n*nphi: print(n, nphi, n*nphi) out= out[0:int(n*nphi)] if kwargs.get('use_physical',True) and \ self._roSet and self._voSet: if isinstance(out[0],Orbit): dum= [o.turn_physical_on(ro=self._ro,vo=self._vo) for o in out] return out def _dlnfdR(self,R,vR,vT): #Calculate a bunch of stuff that we need if self._beta == 0.: E= vR**2./2.+vT**2./2.+sc.log(R) xE= sc.exp(E-.5) OE= xE**-1. LCE= xE dRedR= xE/R else: #non-flat rotation curve E= vR**2./2.+vT**2./2.+1./2./self._beta*R**(2.*self._beta) xE= (2.*E/(1.+1./self._beta))**(1./2./self._beta) OE= xE**(self._beta-1.) LCE= xE**(self._beta+1.) dRedR= xE/2./self._beta/E*R**(2.*self._beta-1.) return self._dlnfdRe(R,vR,vT,E=E,xE=xE,OE=OE,LCE=LCE)*dRedR\ +self._dlnfdl(R,vR,vT,E=E,xE=xE,OE=OE)*vT def _dlnfdvR(self,R,vR,vT): #Calculate a bunch of stuff that we need if self._beta == 0.: E= vR**2./2.+vT**2./2.+sc.log(R) xE= sc.exp(E-.5) OE= xE**-1. LCE= xE dRedvR= xE*vR else: #non-flat rotation curve E= vR**2./2.+vT**2./2.+1./2./self._beta*R**(2.*self._beta) xE= (2.*E/(1.+1./self._beta))**(1./2./self._beta) OE= xE**(self._beta-1.) LCE= xE**(self._beta+1.) dRedvR= xE/2./self._beta/E*vR return self._dlnfdRe(R,vR,vT,E=E,xE=xE,OE=OE,LCE=LCE)*dRedvR def _dlnfdvT(self,R,vR,vT): #Calculate a bunch of stuff that we need if self._beta == 0.: E= vR**2./2.+vT**2./2.+sc.log(R) xE= sc.exp(E-.5) OE= xE**-1. LCE= xE dRedvT= xE*vT else: #non-flat rotation curve E= vR**2./2.+vT**2./2.+1./2./self._beta*R**(2.*self._beta) xE= (2.*E/(1.+1./self._beta))**(1./2./self._beta) OE= xE**(self._beta-1.) LCE= xE**(self._beta+1.) dRedvT= xE/2./self._beta/E*vT return self._dlnfdRe(R,vR,vT,E=E,xE=xE,OE=OE,LCE=LCE)*dRedvT\ +self._dlnfdl(R,vR,vT,E=E,xE=xE,OE=OE)*R def _dlnfdRe(self,R,vR,vT,E=None,xE=None,OE=None,LCE=None): """d ln f(x,v) / d R_e""" #Calculate a bunch of stuff that we need if E is None or xE is None or OE is None or LCE is None: if self._beta == 0.: E= vR**2./2.+vT**2./2.+sc.log(R) xE= sc.exp(E-.5) OE= xE**-1. LCE= xE else: #non-flat rotation curve E= vR**2./2.+vT**2./2.+1./2./self._beta*R**(2.*self._beta) xE= (2.*E/(1.+1./self._beta))**(1./2./self._beta) OE= xE**(self._beta-1.) LCE= xE**(self._beta+1.) L= R*vT sigma2xE= self._surfaceSigmaProfile.sigma2(xE,log=False) return (self._surfaceSigmaProfile.surfacemassDerivative(xE,log=True)\ -(1.+OE*(L-LCE)/sigma2xE)*self._surfaceSigmaProfile.sigma2Derivative(xE,log=True)\ +(L-LCE)/sigma2xE*(self._beta-1.)*xE**(self._beta-2.)\ -OE*(self._beta+1.)/sigma2xE*xE**self._beta) def _dlnfdl(self,R,vR,vT,E=None,xE=None,OE=None): #Calculate a bunch of stuff that we need if E is None or xE is None or OE is None: if self._beta == 0.: E= vR**2./2.+vT**2./2.+sc.log(R) xE= sc.exp(E-.5) OE= xE**-1. else: #non-flat rotation curve E= vR**2./2.+vT**2./2.+1./2./self._beta*R**(2.*self._beta) xE= (2.*E/(1.+1./self._beta))**(1./2./self._beta) OE= xE**(self._beta-1.) sigma2xE= self._surfaceSigmaProfile.sigma2(xE,log=False) return OE/sigma2xE class shudf(diskdf): """Shu's df (1969)""" def __init__(self,surfaceSigma=expSurfaceSigmaProfile, profileParams=(1./3.,1.0,0.2), correct=False, beta=0.,**kwargs): """ NAME: __init__ PURPOSE: Initialize a Shu DF INPUT: surfaceSigma - instance or class name of the target surface density and sigma_R profile (default: both exponential) profileParams - parameters of the surface and sigma_R profile: (xD,xS,Sro) where xD - disk surface mass scalelength (can be Quantity) xS - disk velocity dispersion scalelength (can be Quantity) Sro - disk velocity dispersion at Ro (can be Quantity) Directly given to the 'surfaceSigmaProfile class, so could be anything that class takes beta - power-law index of the rotation curve correct - if True, correct the DF ro= distance from vantage point to GC (kpc; can be Quantity) vo= circular velocity at ro (km/s; can be Quantity) +DFcorrection kwargs (except for those already specified) OUTPUT: instance HISTORY: 2010-05-09 - Written - Bovy (NYU) """ return diskdf.__init__(self,surfaceSigma=surfaceSigma, profileParams=profileParams, correct=correct,dftype='shu', beta=beta,**kwargs) def eval(self,E,L,logSigmaR=0.,logsigmaR2=0.): """ NAME: eval PURPOSE: evaluate the distribution function INPUT: E - energy (/vo^2) L - angular momentun (/ro/vo) OUTPUT: DF(E,L) HISTORY: 2010-05-09 - Written - Bovy (NYU) """ if _APY_LOADED and isinstance(E,units.Quantity): E= E.to(units.km**2/units.s**2).value/self._vo**2. if _APY_LOADED and isinstance(L,units.Quantity): L= L.to(units.kpc*units.km/units.s).value/self._ro/self._vo #Calculate RL,LL, OmegaL if self._beta == 0.: xL= L logECLE= sc.log(-sc.log(xL)-0.5+E) else: #non-flat rotation curve xL= L**(1./(self._beta+1.)) logECLE= sc.log(-0.5*(1./self._beta+1.)*xL**(2.*self._beta)+E) if xL < 0.: #We must remove counter-rotating mass return 0. if self._correct: correction= self._corr.correct(xL,log=True) else: correction= sc.zeros(2) SRE2= self.targetSigma2(xL,log=True,use_physical=False)+correction[1] return self._gamma*sc.exp(logsigmaR2-SRE2+self.targetSurfacemass(xL,log=True,use_physical=False)-logSigmaR-sc.exp(logECLE-SRE2)+correction[0])/2./nu.pi def sample(self,n=1,rrange=None,returnROrbit=True,returnOrbit=False, nphi=1.,los=None,losdeg=True,nsigma=None,maxd=None, targetSurfmass=True,targetSigma2=True,**kwargs): """ NAME: sample PURPOSE: sample n*nphi points from this DF INPUT: n - number of desired sample (specifying this rather than calling this routine n times is more efficient) rrange - if you only want samples in this rrange, set this keyword (only works when asking for an (RZ)Orbit returnROrbit - if True, return a planarROrbit instance: [R,vR,vT] (default) returnOrbit - if True, return a planarOrbit instance (including phi) nphi - number of azimuths to sample for each E,L los= if set, sample along this line of sight (deg) (assumes that the Sun is located at R=1,phi=0) losdeg= if False, los is in radians (default=True) targetSurfmass, targetSigma2= if True, use target surface mass and sigma2 profiles, respectively (there is not much point to doing the latter) (default=True) nsigma= number of sigma to rejection-sample on maxd= maximum distance to consider (for the rejection sampling) OUTPUT: n*nphi list of [[E,Lz],...] or list of planar(R)Orbits CAUTION: lists of EL need to be post-processed to account for the \kappa/\omega_R discrepancy HISTORY: 2010-07-10 - Started - Bovy (NYU) """ if not los is None: return self.sampleLOS(los,n=n,maxd=maxd, nsigma=nsigma,targetSurfmass=targetSurfmass, targetSigma2=targetSigma2) #First sample xL if self._correct: xL= sc.array(bovy_ars([0.,0.],[True,False],[0.05,2.],_ars_hx, _ars_hpx,nsamples=n, hxparams=(self._surfaceSigmaProfile, self._corr))) else: xL= sc.array(bovy_ars([0.,0.],[True,False],[0.05,2.],_ars_hx, _ars_hpx,nsamples=n, hxparams=(self._surfaceSigmaProfile, None))) #Calculate Lz Lz= xL**(self._beta+1.) #Then sample E if self._beta == 0.: ECL= sc.log(xL)+0.5 else: ECL= 0.5*(1./self._beta+1.)*xL**(2.*self._beta) E= -self._surfaceSigmaProfile.sigma2(xL)*sc.log(stats.uniform.rvs(size=n)) if self._correct: E*= self._corr.correct(xL,log=False)[1,:] E+= ECL if not returnROrbit and not returnOrbit: out= [[e,l] for e,l in zip(E,Lz)] else: if not rrange is None \ and _APY_LOADED and isinstance(rrange[0],units.Quantity): rrange[0]= rrange[0].to(units.kpc).value/self._ro rrange[1]= rrange[1].to(units.kpc).value/self._ro if not hasattr(self,'_psp'): self._psp= PowerSphericalPotential(alpha=2.-self._beta,normalize=True).toPlanar() out= [] for ii in range(n): try: wR, rap, rperi= self._ELtowRRapRperi(E[ii],Lz[ii]) except ValueError: #pragma: no cover continue TR= 2.*math.pi/wR tr= stats.uniform.rvs()*TR if tr > TR/2.: tr-= TR/2. thisOrbit= Orbit([rperi,0.,Lz[ii]/rperi]) else: thisOrbit= Orbit([rap,0.,Lz[ii]/rap]) thisOrbit.integrate(sc.array([0.,tr]),self._psp) if returnOrbit: vxvv= thisOrbit(tr)._orb.vxvv thisOrbit= Orbit(vxvv=sc.array([vxvv[0],vxvv[1],vxvv[2], stats.uniform.rvs()*math.pi*2.]).reshape(4)) else: thisOrbit= thisOrbit(tr) kappa= _kappa(thisOrbit._orb.vxvv[0],self._beta) if not rrange == None: if thisOrbit._orb.vxvv[0] < rrange[0] \ or thisOrbit._orb.vxvv[0] > rrange[1]: continue mult= sc.ceil(kappa/wR*nphi)-1. kappawR= kappa/wR*nphi-mult while mult > 0: if returnOrbit: out.append(Orbit(vxvv=sc.array([vxvv[0],vxvv[1], vxvv[2], stats.uniform.rvs()*math.pi*2.]).reshape(4))) else: out.append(thisOrbit) mult-= 1 if stats.uniform.rvs() > kappawR: continue out.append(thisOrbit) #Recurse to get enough if len(out) < n*nphi: out.extend(self.sample(n=int(n-len(out)/nphi),rrange=rrange, returnROrbit=returnROrbit, returnOrbit=returnOrbit,nphi=nphi)) if len(out) > n*nphi: out= out[0:int(n*nphi)] if kwargs.get('use_physical',True) and \ self._roSet and self._voSet: if isinstance(out[0],Orbit): dum= [o.turn_physical_on(ro=self._ro,vo=self._vo) for o in out] return out def _dlnfdR(self,R,vR,vT): #Calculate a bunch of stuff that we need E, L= vRvTRToEL(vR,vT,R,self._beta,self._dftype) if self._beta == 0.: xL= L dRldR= vT ECL= sc.log(xL)+0.5 dECLEdR= 0. else: #non-flat rotation curve xL= L**(1./(self._beta+1.)) dRldR= L**(1./(self._beta+1.))/R/(self._beta+1.) ECL= 0.5*(1./self._beta+1.)*xL**(2.*self._beta) dECLdRl= (1.+self._beta)*xL**(2.*self._beta-1) dEdR= R**(2.*self._beta-1.) dECLEdR= dECLdRl*dRldR-dEdR sigma2xL= self._surfaceSigmaProfile.sigma2(xL,log=False) return (self._surfaceSigmaProfile.surfacemassDerivative(xL,log=True)\ -(1.+(ECL-E)/sigma2xL)*self._surfaceSigmaProfile.sigma2Derivative(xL,log=True))*dRldR\ +dECLEdR/sigma2xL def _dlnfdvR(self,R,vR,vT): #Calculate a bunch of stuff that we need E, L= vRvTRToEL(vR,vT,R,self._beta,self._dftype) if self._beta == 0.: xL= L else: #non-flat rotation curve xL= L**(1./(self._beta+1.)) sigma2xL= self._surfaceSigmaProfile.sigma2(xL,log=False) return -vR/sigma2xL def _dlnfdvT(self,R,vR,vT): #Calculate a bunch of stuff that we need E, L= vRvTRToEL(vR,vT,R,self._beta,self._dftype) if self._beta == 0.: xL= L dRldvT= R ECL= sc.log(xL)+0.5 dECLEdvT= 1./vT-vT else: #non-flat rotation curve xL= L**(1./(self._beta+1.)) dRldvT= L**(1./(self._beta+1.))/vT/(self._beta+1.) ECL= 0.5*(1./self._beta+1.)*xL**(2.*self._beta) dECLdRl= (1.+self._beta)*xL**(2.*self._beta-1) dEdvT= vT dECLEdvT= dECLdRl*dRldvT-dEdvT sigma2xL= self._surfaceSigmaProfile.sigma2(xL,log=False) return (self._surfaceSigmaProfile.surfacemassDerivative(xL,log=True)\ -(1.+(ECL-E)/sigma2xL)*self._surfaceSigmaProfile.sigma2Derivative(xL,log=True))*dRldvT\ +dECLEdvT/sigma2xL class schwarzschilddf(shudf): """Schwarzschild's df""" def __init__(self,surfaceSigma=expSurfaceSigmaProfile, profileParams=(1./3.,1.0,0.2), correct=False, beta=0.,**kwargs): """ NAME: __init__ PURPOSE: Initialize a Schwarzschild DF INPUT: surfaceSigma - instance or class name of the target surface density and sigma_R profile (default: both exponential) profileParams - parameters of the surface and sigma_R profile: (xD,xS,Sro) where xD - disk surface mass scalelength (can be Quantity) xS - disk velocity dispersion scalelength (can be Quantity) Sro - disk velocity dispersion at Ro (can be Quantity) Directly given to the 'surfaceSigmaProfile class, so could be anything that class takes beta - power-law index of the rotation curve correct - if True, correct the DF ro= distance from vantage point to GC (kpc; can be Quantity) vo= circular velocity at ro (km/s; can be Quantity) +DFcorrection kwargs (except for those already specified) OUTPUT: instance HISTORY: 2017-09-17 - Written - Bovy (UofT) """ # Schwarzschild == Shu w/ energy computed in epicycle approx. # so all functions are the same as in Shu, only thing different is # how E is computed return diskdf.__init__(self,surfaceSigma=surfaceSigma, profileParams=profileParams, correct=correct,dftype='schwarzschild', beta=beta,**kwargs) def _surfaceIntegrand(vR,vT,R,df,logSigmaR,logsigmaR2,sigmaR1,gamma): """Internal function that is the integrand for the surface mass integration""" E,L= _vRpvTpRToEL(vR,vT,R,df._beta,sigmaR1,gamma,df._dftype) return df.eval(E,L,logSigmaR,logsigmaR2)*2.*nu.pi/df._gamma #correct def _sigma2surfaceIntegrand(vR,vT,R,df,logSigmaR,logsigmaR2,sigmaR1,gamma): """Internal function that is the integrand for the sigma-squared times surface mass integration""" E,L= _vRpvTpRToEL(vR,vT,R,df._beta,sigmaR1,gamma,df._dftype) return vR**2.*df.eval(E,L,logSigmaR,logsigmaR2)*2.*nu.pi/df._gamma #correct def _vmomentsurfaceIntegrand(vR,vT,R,df,logSigmaR,logsigmaR2,sigmaR1,gamma, n,m): """Internal function that is the integrand for the velocity moment times surface mass integration""" E,L= _vRpvTpRToEL(vR,vT,R,df._beta,sigmaR1,gamma,df._dftype) return vR**n*vT**m*df.eval(E,L,logSigmaR,logsigmaR2)*2.*nu.pi/df._gamma #correct def _vmomentderivsurfaceIntegrand(vR,vT,R,df,logSigmaR,logsigmaR2,sigmaR1, gamma,n,m,deriv): """Internal function that is the integrand for the derivative of velocity moment times surface mass integration""" E,L= _vRpvTpRToEL(vR,vT,R,df._beta,sigmaR1,gamma,df._dftype) if deriv.lower() == 'r': return vR**n*vT**m*df.eval(E,L,logSigmaR,logsigmaR2)*2.*nu.pi/df._gamma*df._dlnfdR(R,vR*sigmaR1,vT*sigmaR1/gamma) #correct else: return 0. def _vRpvTpRToEL(vR,vT,R,beta,sigmaR1,gamma,dftype='dehnen'): """Internal function that calculates E and L given velocities normalized by the velocity dispersion""" vR*= sigmaR1 vT*= sigmaR1/gamma return vRvTRToEL(vR,vT,R,beta,dftype) def _oned_intFunc(x,twodfunc,gfun,hfun,tol,args): """Internal function for bovy_dblquad""" thisargs= copy.deepcopy(args) thisargs.insert(0,x) return integrate.romberg(twodfunc,gfun(x),hfun(x),args=thisargs,tol=tol) def bovy_dblquad(func, a, b, gfun, hfun, args=(), tol=1.48e-08): """ NAME: bovy_dblquad PURPOSE: like scipy.integrate's dblquad, but using Romberg integration for the one-d integrals and using tol INPUT: same as scipy.integrate.dblquad except for tol and epsrel,epsabs OUTPUT: value HISTORY: 2010-03-11 - Written - Bpvy (NYU) """ return integrate.romberg(_oned_intFunc,a,b,args=(func,gfun,hfun,tol,args),tol=tol) class DFcorrection(object): """Class that contains the corrections necessary to reach exponential profiles""" def __init__(self,**kwargs): """ NAME: __init__ PURPOSE: initialize the corrections: set them, load them, or calculate and save them OPTIONAL INPUTS: corrections - if Set, these are the corrections and they should be used as such npoints - number of points from 0 to Rmax rmax - correct up to this radius (/ro) (default: 5) savedir - save the corrections in this directory surfaceSigmaProfile - target surfacemass and sigma_R^2 instance beta - power-law index of the rotation curve (when calculating) dftype - classname of the DF niter - number of iterations to perform to calculate the corrections interp_k - 'k' keyword to give to InterpolatedUnivariateSpline OUTPUT: HISTORY: 2010-03-10 - Written - Bovy (NYU) """ if not 'surfaceSigmaProfile' in kwargs: raise DFcorrectionError("surfaceSigmaProfile not given") else: self._surfaceSigmaProfile= kwargs['surfaceSigmaProfile'] self._rmax= kwargs.get('rmax',5.) self._niter= kwargs.get('niter',20) if not 'npoints' in kwargs: if 'corrections' in kwargs: self._npoints= kwargs['corrections'].shape[0] else: #pragma: no cover self._npoints= 151 #would take too long to cover else: self._npoints= kwargs['npoints'] self._dftype= kwargs.get('dftype',dehnendf) self._beta= kwargs.get('beta',0.) self._rs= sc.linspace(_RMIN,self._rmax,self._npoints) self._interp_k= kwargs.get('interp_k',_INTERPDEGREE) if 'corrections' in kwargs: self._corrections= kwargs['corrections'] if not len(self._corrections) == self._npoints: raise DFcorrectionError("Number of corrections has to be equal to the number of points npoints") else: self._savedir= kwargs.get('savedir',_CORRECTIONSDIR) self._savefilename= self._createSavefilename(self._niter) if os.path.exists(self._savefilename): savefile= open(self._savefilename,'rb') self._corrections= sc.array(pickle.load(savefile)) savefile.close() else: #Calculate the corrections self._corrections= self._calc_corrections() #Interpolation; smoothly go to zero interpRs= sc.append(self._rs,2.*self._rmax) self._surfaceInterpolate= interpolate.InterpolatedUnivariateSpline(interpRs, sc.log(sc.append(self._corrections[:,0],1.)), k=self._interp_k) self._sigma2Interpolate= interpolate.InterpolatedUnivariateSpline(interpRs, sc.log(sc.append(self._corrections[:,1],1.)), k=self._interp_k) #Interpolation for R < _RMIN surfaceInterpolateSmallR= interpolate.UnivariateSpline(interpRs[0:_INTERPDEGREE+2],sc.log(self._corrections[0:_INTERPDEGREE+2,0]),k=_INTERPDEGREE) self._surfaceDerivSmallR= surfaceInterpolateSmallR.derivatives(interpRs[0])[1] sigma2InterpolateSmallR= interpolate.UnivariateSpline(interpRs[0:_INTERPDEGREE+2],sc.log(self._corrections[0:_INTERPDEGREE+2,1]),k=_INTERPDEGREE) self._sigma2DerivSmallR= sigma2InterpolateSmallR.derivatives(interpRs[0])[1] return None def _createSavefilename(self,niter): #Form surfaceSigmaProfile string sspFormat= self._surfaceSigmaProfile.formatStringParams() sspString= '' for format in sspFormat: sspString+= format+'_' return os.path.join(self._savedir,'dfcorrection_'+ self._dftype.__name__+'_'+ self._surfaceSigmaProfile.__class__.__name__+'_'+ sspString % self._surfaceSigmaProfile.outputParams()+ '%6.4f_%i_%6.4f_%i.sav' % (self._beta,self._npoints,self._rmax,niter)) def correct(self,R,log=False): """ NAME: correct PURPOSE: calculate the correction in Sigma and sigma2 at R INPUT: R - Galactocentric radius(/ro) log - if True, return the log of the correction OUTPUT: [Sigma correction, sigma2 correction] HISTORY: 2010-03-10 - Written - Bovy (NYU) """ if isinstance(R,nu.ndarray): out= nu.empty((2,len(R))) #R < _RMIN rmin_indx= (R < _RMIN) if nu.sum(rmin_indx) > 0: out[0,rmin_indx]= math.log(self._corrections[0,0])\ +self._surfaceDerivSmallR*(R[rmin_indx]-_RMIN) out[1,rmin_indx]= math.log(self._corrections[0,1])\ +self._sigma2DerivSmallR*(R[rmin_indx]-_RMIN) #R > 2rmax rmax_indx= (R > (2.*self._rmax)) if nu.sum(rmax_indx) > 0: out[:,rmax_indx]= 0. #'normal' R r_indx= (R >= _RMIN)*(R <= (2.*self._rmax)) if nu.sum(r_indx) > 0: out[0,r_indx]= self._surfaceInterpolate(R[r_indx]) out[1,r_indx]= self._sigma2Interpolate(R[r_indx]) if log: return out else: return nu.exp(out) if R < _RMIN: out= sc.array([sc.log(self._corrections[0,0])+self._surfaceDerivSmallR*(R-_RMIN), sc.log(self._corrections[0,1])+self._sigma2DerivSmallR*(R-_RMIN)]) elif R > (2.*self._rmax): out= sc.array([0.,0.]) else: if _SCIPYVERSION >= 0.9: out= sc.array([self._surfaceInterpolate(R), self._sigma2Interpolate(R)]) else: #pragma: no cover out= sc.array([self._surfaceInterpolate(R)[0], self._sigma2Interpolate(R)[0]]) if log: return out else: return sc.exp(out) def derivLogcorrect(self,R): """ NAME: derivLogcorrect PURPOSE: calculate the derivative of the log of the correction in Sigma and sigma2 at R INPUT: R - Galactocentric radius(/ro) OUTPUT: [d log(Sigma correction)/dR, d log(sigma2 correction)/dR] HISTORY: 2010-03-10 - Written - Bovy (NYU) """ if R < _RMIN: out= sc.array([self._surfaceDerivSmallR, self._sigma2DerivSmallR]) elif R > (2.*self._rmax): out= sc.array([0.,0.]) else: if _SCIPYVERSION >= 0.9: out= sc.array([self._surfaceInterpolate(R,nu=1), self._sigma2Interpolate(R,nu=1)]) else: #pragma: no cover out= sc.array([self._surfaceInterpolate(R,nu=1)[0], self._sigma2Interpolate(R,nu=1)[0]]) return out def _calc_corrections(self): """Internal function that calculates the corrections""" searchIter= self._niter-1 while searchIter > 0: trySavefilename= self._createSavefilename(searchIter) if os.path.exists(trySavefilename): trySavefile= open(trySavefilename,'rb') corrections= sc.array(pickle.load(trySavefile)) trySavefile.close() break else: searchIter-= 1 if searchIter == 0: corrections= sc.ones((self._npoints,2)) for ii in range(searchIter,self._niter): if ii == 0: currentDF= self._dftype(surfaceSigma=self._surfaceSigmaProfile, beta=self._beta) else: currentDF= self._dftype(surfaceSigma=self._surfaceSigmaProfile, beta=self._beta, corrections=corrections, npoints=self._npoints, rmax=self._rmax, savedir=self._savedir, interp_k=self._interp_k) newcorrections= sc.zeros((self._npoints,2)) for jj in range(self._npoints): thisSurface= currentDF.surfacemass(self._rs[jj], use_physical=False) newcorrections[jj,0]= currentDF.targetSurfacemass(self._rs[jj],use_physical=False)/thisSurface newcorrections[jj,1]= currentDF.targetSigma2(self._rs[jj],use_physical=False)*thisSurface\ /currentDF.sigma2surfacemass(self._rs[jj], use_physical=False) #print(jj, newcorrections[jj,:]) corrections*= newcorrections #Save picklethis= [] for arr in list(corrections): picklethis.append([float(a) for a in arr]) save_pickles(self._savefilename,picklethis) #We pickle a list for platform-independence) return corrections class DFcorrectionError(Exception): def __init__(self, value): self.value = value def __str__(self): return repr(self.value) def vRvTRToEL(vR,vT,R,beta,dftype='dehnen'): """ NAME: vRvTRToEL PURPOSE: calculate the energy and angular momentum INPUT: vR - radial velocity vT - tangential velocity R - Galactocentric radius OUTPUT: HISTORY: 2010-03-10 - Written - Bovy (NYU) """ if dftype == 'schwarzschild': # Compute E in the epicycle approximation gamma= sc.sqrt(2./(1.+beta)) L= R*vT if beta == 0.: xL= L else: #non-flat rotation curve xL= L**(1./(beta+1.)) return (0.5*vR**2.+0.5*gamma**2.*(vT-R**beta)**2. +xL**(2.*beta)/2.+axipotential(xL,beta=beta), L) else: return (axipotential(R,beta)+0.5*vR**2.+0.5*vT**2.,vT*R) def axipotential(R,beta=0.): """ NAME: axipotential PURPOSE: return the axisymmetric potential at R/Ro INPUT: R - Galactocentric radius beta - rotation curve power-law OUTPUT: Pot(R)/vo**2. HISTORY: 2010-03-01 - Written - Bovy (NYU) """ if beta == 0.: if nu.any(R == 0.): out= nu.empty(R.shape) out[R == 0.]= math.log(_RMIN) out[R != 0.]= nu.log(R[R != 0.]) return out else: return nu.log(R) else: #non-flat rotation curve return R**(2.*beta)/2./beta def _ars_hx(x,args): """ NAME: _ars_hx PURPOSE: h(x) for ARS sampling of the input surfacemass profile INPUT: x - R(/ro) args= (surfaceSigma, dfcorr) surfaceSigma - surfaceSigmaProfile instance dfcorr - DFcorrection instance OUTPUT: log(x)+log surface(x) + log(correction) HISTORY: 2010-07-11 - Written - Bovy (NYU) """ surfaceSigma, dfcorr= args if dfcorr is None: return math.log(x)+surfaceSigma.surfacemass(x,log=True) else: return math.log(x)+surfaceSigma.surfacemass(x,log=True)+dfcorr.correct(x)[0] def _ars_hpx(x,args): """ NAME: _ars_hpx PURPOSE: h'(x) for ARS sampling of the input surfacemass profile INPUT: x - R(/ro) args= (surfaceSigma, dfcorr) surfaceSigma - surfaceSigmaProfile instance dfcorr - DFcorrection instance OUTPUT: derivative of log(x)+log surface(x) + log(correction) wrt x HISTORY: 2010-07-11 - Written - Bovy (NYU) """ surfaceSigma, dfcorr= args if dfcorr is None: return 1./x+surfaceSigma.surfacemassDerivative(x,log=True) else: return 1./x+surfaceSigma.surfacemassDerivative(x,log=True)+dfcorr.derivLogcorrect(x)[0] def _kappa(R,beta): """Internal function to give kappa(r)""" return math.sqrt(2.*(1.+beta))*R**(beta-1) def _dlToRphi(d,l): """Convert d and l to R and phi, l is in radians""" R= math.sqrt(1.+d**2.-2.*d*math.cos(l)) if R == 0.: R+= 0.0001 d+= 0.0001 if 1./math.cos(l) < d and math.cos(l) > 0.: theta= math.pi-math.asin(d/R*math.sin(l)) else: theta= math.asin(d/R*math.sin(l)) return (R,theta) def _vtmaxEq(vT,R,diskdf): """Equation to solve to find the max vT at R""" #Calculate a bunch of stuff that we need if diskdf._beta == 0.: E= vT**2./2.+sc.log(R) xE= sc.exp(E-.5) OE= xE**-1. LCE= xE dxEdvT= xE*vT else: #non-flat rotation curve E= vT**2./2.+1./2./diskdf._beta*R**(2.*diskdf._beta) xE= (2.*E/(1.+1./diskdf._beta))**(1./2./diskdf._beta) OE= xE**(diskdf._beta-1.) LCE= xE**(diskdf._beta+1.) dxEdvT= xE/2./diskdf._beta/E*vT L= R*vT sigma2xE= diskdf._surfaceSigmaProfile.sigma2(xE,log=False) return OE*R/sigma2xE+\ (diskdf._surfaceSigmaProfile.surfacemassDerivative(xE,log=True)\ -(1.+OE*(L-LCE)/sigma2xE)*diskdf._surfaceSigmaProfile.sigma2Derivative(xE,log=True)\ +(L-LCE)/sigma2xE*(diskdf._beta-1.)*xE**(diskdf._beta-2.)\ -OE*(diskdf._beta+1.)/sigma2xE*xE**diskdf._beta)\ *dxEdvT def _marginalizeVperpIntegrandSinAlphaLarge(vR,df,R,sinalpha,cotalpha, vlos,vcirc,sigma): return df(*vRvTRToEL(vR*sigma,cotalpha*vR*sigma+vlos/sinalpha+vcirc, R,df._beta,df._dftype)) def _marginalizeVperpIntegrandSinAlphaSmall(vT,df,R,cosalpha,tanalpha, vlos,vcirc,sigma): return df(*vRvTRToEL(tanalpha*vT*sigma-vlos/cosalpha,vT*sigma+vcirc, R,df._beta,df._dftype)) galpy-1.3.0/galpy/df_src/evolveddiskdf.py0000644000076500000240000034752213175236210020525 0ustar bovystaff00000000000000############################################################################### # evolveddiskdf.py: module that builds a distribution function as a # steady-state DF + subsequent evolution # # This module contains the following classes: # # evolveddiskdf - top-level class that represents a distribution function ############################################################################### from __future__ import print_function _NSIGMA= 4. _NTS= 1000 _PROFILE= False import sys import math import copy import time as time_module import warnings import numpy as nu from scipy import integrate from galpy.util import galpyWarning from galpy.orbit import Orbit from galpy.potential import calcRotcurve from galpy.df_src.df import df, _APY_LOADED from galpy.potential_src.Potential import _check_c from galpy.util.bovy_quadpack import dblquad from galpy.util import bovy_plot from galpy.util.bovy_conversion import physical_conversion, \ potential_physical_input, time_in_Gyr if _APY_LOADED: from astropy import units _DEGTORAD= math.pi/180. _RADTODEG= 180./math.pi _NAN= nu.nan class evolveddiskdf(df): """Class that represents a diskdf as initial DF + subsequent secular evolution""" def __init__(self,initdf,pot,to=0.): """ NAME: __init__ PURPOSE: initialize INPUT: initdf - the df at the start of the evolution (at to) (units are transferred) pot - potential to integrate orbits in to= initial time (time at which initdf is evaluated; orbits are integrated from current t back to to) (can be Quantity) OUTPUT: instance HISTORY: 2011-03-30 - Written - Bovy (NYU) """ if initdf._roSet: ro= initdf._ro else: ro= None if initdf._voSet: vo= initdf._vo else: vo= None df.__init__(self,ro=ro,vo=vo) self._initdf= initdf self._pot= pot if _APY_LOADED and isinstance(to,units.Quantity): to= to.to(units.Gyr).value/time_in_Gyr(self._vo,self._ro) self._to= to @physical_conversion('phasespacedensity2d',pop=True) def __call__(self,*args,**kwargs): """ NAME: __call__ PURPOSE: evaluate the distribution function INPUT: Orbit instance: a) Orbit instance alone: use initial state and t=0 b) Orbit instance + t: Orbit instance *NOT* called (i.e., Orbit's initial condition is used, call Orbit yourself), t can be Quantity If t is a list of t, DF is returned for each t, times must be in descending order and equally spaced (does not work with marginalize...) marginalizeVperp - marginalize over perpendicular velocity (only supported with 1a) above) + nsigma, +scipy.integrate.quad keywords marginalizeVlos - marginalize over line-of-sight velocity (only supported with 1a) above) + nsigma, +scipy.integrate.quad keywords log= if True, return the log (not for deriv, bc that can be negative) integrate_method= method argument of orbit.integrate deriv= None, 'R', or 'phi': calculates derivative of the moment wrt R or phi **not with the marginalize options** OUTPUT: DF(orbit,t) HISTORY: 2011-03-30 - Written - Bovy (NYU) 2011-04-15 - Added list of times option - Bovy (NYU) """ integrate_method= kwargs.pop('integrate_method','dopr54_c') # Must match Python fallback for non-C potentials here, bc odeint needs # custom t list to avoid numerically instabilities if '_c' in integrate_method and not _check_c(self._pot): if ('leapfrog' in integrate_method \ or 'symplec' in integrate_method): integrate_method= 'leapfrog' else: integrate_method= 'odeint' deriv= kwargs.get('deriv',None) if isinstance(args[0],Orbit): if len(args) == 1: t= 0. else: t= args[1] else: raise IOError("Input to __call__ not understood; this has to be an Orbit instance with optional time") if isinstance(t,list): t= nu.array(t) tlist= True elif isinstance(t,nu.ndarray) and \ not (hasattr(t,'isscalar') and t.isscalar): tlist= True else: tlist= False if _APY_LOADED and isinstance(t,units.Quantity): t= t.to(units.Gyr).value/time_in_Gyr(self._vo,self._ro) if kwargs.pop('marginalizeVperp',False): if tlist: raise IOError("Input times to __call__ is a list; this is not supported in conjunction with marginalizeVperp") if kwargs.pop('log',False): return nu.log(self._call_marginalizevperp(args[0],integrate_method=integrate_method,**kwargs)) else: return self._call_marginalizevperp(args[0],integrate_method=integrate_method,**kwargs) elif kwargs.pop('marginalizeVlos',False): if tlist: raise IOError("Input times to __call__ is a list; this is not supported in conjunction with marginalizeVlos") if kwargs.pop('log',False): return nu.log(self._call_marginalizevlos(args[0],integrate_method=integrate_method,**kwargs)) else: return self._call_marginalizevlos(args[0],integrate_method=integrate_method,**kwargs) #Integrate back if tlist: if self._to == t[0]: if kwargs.get('log',False): return nu.log([self._initdf(args[0],use_physical=False)]) else: return [self._initdf(args[0],use_physical=False)] ts= self._create_ts_tlist(t,integrate_method) o= args[0] #integrate orbit if _PROFILE: #pragma: no cover start= time_module.time() if not deriv is None: #Also calculate the derivative of the initial df with respect to R, phi, vR, and vT, and the derivative of Ro wrt R/phi etc., to calculate the derivative; in this case we also integrate a small area of phase space if deriv.lower() == 'r': dderiv= 10.**-10. tmp= o.R(use_physical=False)+dderiv dderiv= tmp-o.R(use_physical=False) msg= o._orb.integrate_dxdv([dderiv,0.,0.,0.],ts,self._pot,method=integrate_method) elif deriv.lower() == 'phi': dderiv= 10.**-10. tmp= o.phi(use_physical=False)+dderiv dderiv= tmp-o.phi(use_physical=False) msg= o._orb.integrate_dxdv([0.,0.,0.,dderiv],ts,self._pot,method=integrate_method) if msg > 0.: # pragma: no cover print("Warning: dxdv integration inaccurate, returning zero everywhere ... result might not be correct ...") if kwargs.get('log',False) and deriv is None: return nu.zeros(len(t))-nu.finfo(nu.dtype(nu.float64)).max else: return nu.zeros(len(t)) o._orb.orbit= o._orb.orbit_dxdv[:,0:4] else: o.integrate(ts,self._pot,method=integrate_method) if _PROFILE: #pragma: no cover int_time= (time_module.time()-start) #Now evaluate the DF if _PROFILE: #pragma: no cover start= time_module.time() if integrate_method == 'odeint': retval= [] os= [o(self._to+t[0]-ti,use_physical=False) for ti in t] retval= nu.array(self._initdf(os,use_physical=False)) else: if len(t) == 1: orb_array= o.getOrbit().T orb_array= orb_array[:,1] else: orb_array= o.getOrbit().T retval= self._initdf(orb_array,use_physical=False) if (isinstance(retval,float) or len(retval.shape) == 0) \ and nu.isnan(retval): retval= 0. elif not isinstance(retval,float) and len(retval.shape) > 0: retval[(nu.isnan(retval))]= 0. if len(t) > 1: retval= retval[::-1] if _PROFILE: #pragma: no cover df_time= (time_module.time()-start) tot_time= int_time+df_time print(int_time/tot_time, df_time/tot_time, tot_time) if not deriv is None: if integrate_method == 'odeint': dlnfdRo= nu.array([self._initdf._dlnfdR(o.R(self._to+t[0]-ti,use_physical=False), o.vR(self._to+t[0]-ti,use_physical=False), o.vT(self._to+t[0]-ti,use_physical=False)) for ti in t]) dlnfdvRo= nu.array([self._initdf._dlnfdvR(o.R(self._to+t[0]-ti,use_physical=False), o.vR(self._to+t[0]-ti,use_physical=False), o.vT(self._to+t[0]-ti,use_physical=False)) for ti in t]) dlnfdvTo= nu.array([self._initdf._dlnfdvT(o.R(self._to+t[0]-ti,use_physical=False), o.vR(self._to+t[0]-ti,use_physical=False), o.vT(self._to+t[0]-ti,use_physical=False)) for ti in t]) dRo= nu.array([o._orb.orbit_dxdv[list(ts).index(self._to+t[0]-ti),4] for ti in t])/dderiv dvRo= nu.array([o._orb.orbit_dxdv[list(ts).index(self._to+t[0]-ti),5] for ti in t])/dderiv dvTo= nu.array([o._orb.orbit_dxdv[list(ts).index(self._to+t[0]-ti),6] for ti in t])/dderiv #print(dRo, dvRo, dvTo) dlnfderiv= dlnfdRo*dRo+dlnfdvRo*dvRo+dlnfdvTo*dvTo retval*= dlnfderiv else: if len(t) == 1: dlnfdRo= self._initdf._dlnfdR(orb_array[0], orb_array[1], orb_array[2]) dlnfdvRo= self._initdf._dlnfdvR(orb_array[0], orb_array[1], orb_array[2]) dlnfdvTo= self._initdf._dlnfdvT(orb_array[0], orb_array[1], orb_array[2]) else: dlnfdRo= nu.array([self._initdf._dlnfdR(orb_array[0,ii], orb_array[1,ii], orb_array[2,ii]) for ii in range(len(t))]) dlnfdvRo= nu.array([self._initdf._dlnfdvR(orb_array[0,ii], orb_array[1,ii], orb_array[2,ii]) for ii in range(len(t))]) dlnfdvTo= nu.array([self._initdf._dlnfdvT(orb_array[0,ii], orb_array[1,ii], orb_array[2,ii]) for ii in range(len(t))]) dorb_array= o._orb.orbit_dxdv.T if len(t) == 1: dorb_array= dorb_array[:,1] dRo= dorb_array[4]/dderiv dvRo= dorb_array[5]/dderiv dvTo= dorb_array[6]/dderiv #print(dRo, dvRo, dvTo) dlnfderiv= dlnfdRo*dRo+dlnfdvRo*dvRo+dlnfdvTo*dvTo if len(t) > 1: dlnfderiv= dlnfderiv[::-1] retval*= dlnfderiv else: if self._to == t and deriv is None: if kwargs.get('log',False): return nu.log(self._initdf(args[0],use_physical=False)) else: return self._initdf(args[0],use_physical=False) elif self._to == t and not deriv is None: if deriv.lower() == 'r': return self._initdf(args[0])*self._initdf._dlnfdR(args[0]._orb.vxvv[0], args[0]._orb.vxvv[1], args[0]._orb.vxvv[2]) elif deriv.lower() == 'phi': return 0. if integrate_method == 'odeint': ts= nu.linspace(t,self._to,_NTS) else: ts= nu.linspace(t,self._to,2) o= args[0] #integrate orbit if not deriv is None: ts= nu.linspace(t,self._to,_NTS) #Also calculate the derivative of the initial df with respect to R, phi, vR, and vT, and the derivative of Ro wrt R/phi etc., to calculate the derivative; in this case we also integrate a small area of phase space if deriv.lower() == 'r': dderiv= 10.**-10. tmp= o.R(use_physical=False)+dderiv dderiv= tmp-o.R(use_physical=False) o._orb.integrate_dxdv([dderiv,0.,0.,0.],ts,self._pot,method=integrate_method) elif deriv.lower() == 'phi': dderiv= 10.**-10. tmp= o.phi(use_physical=False)+dderiv dderiv= tmp-o.phi(use_physical=False) o._orb.integrate_dxdv([0.,0.,0.,dderiv],ts,self._pot,method=integrate_method) o._orb.orbit= o._orb.orbit_dxdv[:,0:4] else: o.integrate(ts,self._pot,method=integrate_method) #int_time= (time.time()-start) #Now evaluate the DF if o.R(self._to-t,use_physical=False) <= 0.: if kwargs.get('log',False): return -nu.finfo(nu.dtype(nu.float64)).max else: return nu.finfo(nu.dtype(nu.float64)).eps #start= time.time() retval= self._initdf(o(self._to-t,use_physical=False), use_physical=False) #print( int_time/(time.time()-start)) if nu.isnan(retval): print(retval, o._orb.vxvv, o(self._to-t)._orb.vxvv) if not deriv is None: thisorbit= o(self._to-t)._orb.vxvv dlnfdRo= self._initdf._dlnfdR(thisorbit[0], thisorbit[1], thisorbit[2]) dlnfdvRo= self._initdf._dlnfdvR(thisorbit[0], thisorbit[1], thisorbit[2]) dlnfdvTo= self._initdf._dlnfdvT(thisorbit[0], thisorbit[1], thisorbit[2]) indx= list(ts).index(self._to-t) dRo= o._orb.orbit_dxdv[indx,4]/dderiv dvRo= o._orb.orbit_dxdv[indx,5]/dderiv dvTo= o._orb.orbit_dxdv[indx,6]/dderiv dlnfderiv= dlnfdRo*dRo+dlnfdvRo*dvRo+dlnfdvTo*dvTo retval*= dlnfderiv if kwargs.get('log',False) and deriv is None: if tlist: out= nu.log(retval) out[retval == 0.]= -nu.finfo(nu.dtype(nu.float64)).max else: if retval == 0.: out= -nu.finfo(nu.dtype(nu.float64)).max else: out= nu.log(retval) return out else: return retval def vmomentsurfacemass(self,R,n,m,t=0.,nsigma=None,deg=False, epsrel=1.e-02,epsabs=1.e-05,phi=0., grid=None,gridpoints=101,returnGrid=False, hierarchgrid=False,nlevels=2, print_progress=False, integrate_method='dopr54_c', deriv=None): """ NAME: vmomentsurfacemass PURPOSE: calculate the an arbitrary moment of the velocity distribution at (R,phi) times the surfacmass INPUT: R - radius at which to calculate the moment (in natural units) phi= azimuth (rad unless deg=True) n - vR^n m - vT^m t= time at which to evaluate the DF (can be a list or ndarray; if this is the case, list needs to be in descending order and equally spaced) nsigma - number of sigma to integrate the velocities over (based on an estimate, so be generous, but not too generous) deg= azimuth is in degree (default=False) epsrel, epsabs - scipy.integrate keywords (the integration calculates the ratio of this vmoment to that of the initial DF) grid= if set to True, build a grid and use that to evaluate integrals; if set to a grid-objects (such as returned by this procedure), use this grid; if this was created for a list of times, moments are calculated for each time gridpoints= number of points to use for the grid in 1D (default=101) returnGrid= if True, return the grid object (default=False) hierarchgrid= if True, use a hierarchical grid (default=False) nlevels= number of hierarchical levels for the hierarchical grid print_progress= if True, print progress updates integrate_method= orbit.integrate method argument deriv= None, 'R', or 'phi': calculates derivative of the moment wrt R or phi **onnly with grid options** OUTPUT: at R,phi (no support for units) COMMENT: grid-based calculation is the only one that is heavily tested (although the test suite also tests the direct calculation) HISTORY: 2011-03-30 - Written - Bovy (NYU) """ #if we have already precalculated a grid, use that if not grid is None and isinstance(grid,evolveddiskdfGrid): if returnGrid: return (self._vmomentsurfacemassGrid(n,m,grid),grid) else: return self._vmomentsurfacemassGrid(n,m,grid) elif not grid is None \ and isinstance(grid,evolveddiskdfHierarchicalGrid): if returnGrid: return (self._vmomentsurfacemassHierarchicalGrid(n,m,grid), grid) else: return self._vmomentsurfacemassHierarchicalGrid(n,m,grid) #Otherwise we need to do some more work if deg: az= phi*_DEGTORAD else: az= phi if nsigma is None: nsigma= _NSIGMA if _PROFILE: #pragma: no cover start= time_module.time() if hasattr(self._initdf,'_estimatemeanvR') \ and hasattr(self._initdf,'_estimatemeanvT') \ and hasattr(self._initdf,'_estimateSigmaR2') \ and hasattr(self._initdf,'_estimateSigmaT2'): sigmaR1= nu.sqrt(self._initdf._estimateSigmaR2(R,phi=az)) sigmaT1= nu.sqrt(self._initdf._estimateSigmaT2(R,phi=az)) meanvR= self._initdf._estimatemeanvR(R,phi=az) meanvT= self._initdf._estimatemeanvT(R,phi=az) else: warnings.warn("No '_estimateSigmaR2' etc. functions found for initdf in evolveddf; thus using potentially slow sigmaR2 etc functions", galpyWarning) sigmaR1= nu.sqrt(self._initdf.sigmaR2(R,phi=az,use_physical=False)) sigmaT1= nu.sqrt(self._initdf.sigmaT2(R,phi=az,use_physical=False)) meanvR= self._initdf.meanvR(R,phi=az,use_physical=False) meanvT= self._initdf.meanvT(R,phi=az,use_physical=False) if _PROFILE: #pragma: no cover setup_time= (time_module.time()-start) if not grid is None and isinstance(grid,bool) and grid: if not hierarchgrid: if _PROFILE: #pragma: no cover start= time_module.time() grido= self._buildvgrid(R,az,nsigma,t, sigmaR1,sigmaT1,meanvR,meanvT, gridpoints,print_progress, integrate_method,deriv) if _PROFILE: #pragma: no cover grid_time= (time_module.time()-start) print(setup_time/(setup_time+grid_time), \ grid_time/(setup_time+grid_time), \ setup_time+grid_time) if returnGrid: return (self._vmomentsurfacemassGrid(n,m,grido),grido) else: return self._vmomentsurfacemassGrid(n,m,grido) else: #hierarchical grid grido= evolveddiskdfHierarchicalGrid(self,R,az,nsigma,t, sigmaR1,sigmaT1,meanvR, meanvT, gridpoints,nlevels,deriv, print_progress=print_progress) if returnGrid: return (self._vmomentsurfacemassHierarchicalGrid(n,m, grido), grido) else: return self._vmomentsurfacemassHierarchicalGrid(n,m,grido) #Calculate the initdf moment and then calculate the ratio initvmoment= self._initdf.vmomentsurfacemass(R,n,m,nsigma=nsigma, phi=phi) if initvmoment == 0.: initvmoment= 1. norm= sigmaR1**(n+1)*sigmaT1**(m+1)*initvmoment if isinstance(t,(list,nu.ndarray)): raise IOError("list of times is only supported with grid-based calculation") return dblquad(_vmomentsurfaceIntegrand, meanvT/sigmaT1-nsigma, meanvT/sigmaT1+nsigma, lambda x: meanvR/sigmaR1 -nu.sqrt(nsigma**2.-(x-meanvT/sigmaT1)**2.), lambda x: meanvR/sigmaR1 +nu.sqrt(nsigma**2.-(x-meanvT/sigmaT1)**2.), (R,az,self,n,m,sigmaR1,sigmaT1,t,initvmoment), epsrel=epsrel,epsabs=epsabs)[0]*norm @potential_physical_input @physical_conversion('angle',pop=True) def vertexdev(self,R,t=0.,nsigma=None,deg=False, epsrel=1.e-02,epsabs=1.e-05,phi=0., grid=None,gridpoints=101,returnGrid=False, sigmaR2=None,sigmaT2=None,sigmaRT=None,surfacemass=None, hierarchgrid=False,nlevels=2, integrate_method='dopr54_c'): """ NAME: vertexdev PURPOSE: calculate the vertex deviation of the velocity distribution at (R,phi) INPUT: R - radius at which to calculate the moment (can be Quantity) phi= azimuth (rad unless deg=True; can be Quantity) t= time at which to evaluate the DF (can be a list or ndarray; if this is the case, list needs to be in descending order and equally spaced) (can be Quantity) sigmaR2, sigmaT2, sigmaRT= if set the vertex deviation is simply calculated using these nsigma - number of sigma to integrate the velocities over (based on an estimate, so be generous) deg= azimuth is in degree (default=False); do not set this when giving phi as a Quantity epsrel, epsabs - scipy.integrate keywords (the integration calculates the ratio of this vmoment to that of the initial DF) grid= if set to True, build a grid and use that to evaluate integrals; if set to a grid-objects (such as returned by this procedure), use this grid gridpoints= number of points to use for the grid in 1D (default=101) returnGrid= if True, return the grid object (default=False) hierarchgrid= if True, use a hierarchical grid (default=False) nlevels= number of hierarchical levels for the hierarchical grid integrate_method= orbit.integrate method argument OUTPUT: vertex deviation in rad HISTORY: 2011-03-31 - Written - Bovy (NYU) """ #The following aren't actually the moments, but they are the moments #times the surface-mass density; that drops out if isinstance(grid,evolveddiskdfGrid) or \ isinstance(grid,evolveddiskdfHierarchicalGrid): grido= grid elif (sigmaR2 is None or sigmaT2 is None or sigmaRT is None) \ and isinstance(grid,bool) and grid: #Precalculate the grid (sigmaR2_tmp,grido)= self.vmomentsurfacemass(R,2,0,deg=deg,t=t, nsigma=nsigma,phi=phi, epsrel=epsrel, epsabs=epsabs,grid=grid, gridpoints=gridpoints, returnGrid=True, hierarchgrid=hierarchgrid, nlevels=nlevels, integrate_method=integrate_method) else: grido= False if sigmaR2 is None: sigmaR2= self.sigmaR2(R,deg=deg,t=t,phi=phi, nsigma=nsigma,epsrel=epsrel, epsabs=epsabs,grid=grido, gridpoints=gridpoints, returnGrid=False, hierarchgrid=hierarchgrid, nlevels=nlevels, integrate_method=integrate_method, use_physical=False) if sigmaT2 is None: sigmaT2= self.sigmaT2(R,deg=deg,t=t,phi=phi, nsigma=nsigma,epsrel=epsrel, epsabs=epsabs,grid=grido, gridpoints=gridpoints, returnGrid=False, hierarchgrid=hierarchgrid, nlevels=nlevels, integrate_method=integrate_method, use_physical=False) if sigmaRT is None: sigmaRT= self.sigmaRT(R,deg=deg,t=t,phi=phi, nsigma=nsigma,epsrel=epsrel, epsabs=epsabs,grid=grido, gridpoints=gridpoints, returnGrid=False, hierarchgrid=hierarchgrid, nlevels=nlevels, integrate_method=integrate_method, use_physical=False) warnings.warn("In versions >1.3, the output unit of evolveddiskdf.vertexdev has been changed to radian (from degree before)",galpyWarning) if returnGrid and ((isinstance(grid,bool) and grid) or isinstance(grid,evolveddiskdfGrid) or isinstance(grid,evolveddiskdfHierarchicalGrid)): return (-nu.arctan(2.*sigmaRT/(sigmaR2-sigmaT2))/2.,grido) else: return -nu.arctan(2.*sigmaRT/(sigmaR2-sigmaT2))/2. @potential_physical_input @physical_conversion('velocity',pop=True) def meanvR(self,R,t=0.,nsigma=None,deg=False,phi=0., epsrel=1.e-02,epsabs=1.e-05, grid=None,gridpoints=101,returnGrid=False, surfacemass=None, hierarchgrid=False,nlevels=2,integrate_method='dopr54_c'): """ NAME: meanvR PURPOSE: calculate the mean vR of the velocity distribution at (R,phi) INPUT: R - radius at which to calculate the moment(/ro) (can be Quantity) phi= azimuth (rad unless deg=True; can be Quantity) t= time at which to evaluate the DF (can be a list or ndarray; if this is the case, list needs to be in descending order and equally spaced) (can be Quantity) surfacemass= if set use this pre-calculated surfacemass nsigma - number of sigma to integrate the velocities over (based on an estimate, so be generous) deg= azimuth is in degree (default=False); do not set this when giving phi as a Quantity epsrel, epsabs - scipy.integrate keywords (the integration calculates the ratio of this vmoment to that of the initial DF) grid= if set to True, build a grid and use that to evaluate integrals; if set to a grid-objects (such as returned by this procedure), use this grid gridpoints= number of points to use for the grid in 1D (default=101) returnGrid= if True, return the grid object (default=False) hierarchgrid= if True, use a hierarchical grid (default=False) nlevels= number of hierarchical levels for the hierarchical grid integrate_method= orbit.integrate method argument OUTPUT: mean vR HISTORY: 2011-03-31 - Written - Bovy (NYU) """ if isinstance(grid,evolveddiskdfGrid) or \ isinstance(grid,evolveddiskdfHierarchicalGrid): grido= grid vmomentR= self.vmomentsurfacemass(R,1,0,deg=deg,t=t,phi=phi, nsigma=nsigma, epsrel=epsrel, epsabs=epsabs,grid=grid, gridpoints=gridpoints, returnGrid=False, hierarchgrid=hierarchgrid, nlevels=nlevels, integrate_method=integrate_method) elif isinstance(grid,bool) and grid: #Precalculate the grid (vmomentR,grido)= self.vmomentsurfacemass(R,1,0,deg=deg,t=t, nsigma=nsigma,phi=phi, epsrel=epsrel, epsabs=epsabs,grid=grid, gridpoints=gridpoints, returnGrid=True, hierarchgrid=hierarchgrid, nlevels=nlevels, integrate_method=integrate_method) else: grido= False vmomentR= self.vmomentsurfacemass(R,1,0,deg=deg,t=t,phi=phi, nsigma=nsigma, epsrel=epsrel, epsabs=epsabs,grid=grid, gridpoints=gridpoints, returnGrid=False, hierarchgrid=hierarchgrid, nlevels=nlevels,integrate_method=integrate_method) if surfacemass is None: surfacemass= self.vmomentsurfacemass(R,0,0,deg=deg,t=t,phi=phi, nsigma=nsigma,epsrel=epsrel, epsabs=epsabs,grid=grido, gridpoints=gridpoints, returnGrid=False, hierarchgrid=hierarchgrid, nlevels=nlevels,integrate_method=integrate_method) out= vmomentR/surfacemass if returnGrid and ((isinstance(grid,bool) and grid) or isinstance(grid,evolveddiskdfGrid) or isinstance(grid,evolveddiskdfHierarchicalGrid)): return (out,grido) else: return out @potential_physical_input @physical_conversion('velocity',pop=True) def meanvT(self,R,t=0.,nsigma=None,deg=False,phi=0., epsrel=1.e-02,epsabs=1.e-05, grid=None,gridpoints=101,returnGrid=False, surfacemass=None, hierarchgrid=False,nlevels=2,integrate_method='dopr54_c'): """ NAME: meanvT PURPOSE: calculate the mean vT of the velocity distribution at (R,phi) INPUT: R - radius at which to calculate the moment (can be Quantity) phi= azimuth (rad unless deg=True; can be Quantity) t= time at which to evaluate the DF (can be a list or ndarray; if this is the case, list needs to be in descending order and equally spaced) (can be Quantity) surfacemass= if set use this pre-calculated surfacemass nsigma - number of sigma to integrate the velocities over (based on an estimate, so be generous) deg= azimuth is in degree (default=False); do not set this when giving phi as a Quantity epsrel, epsabs - scipy.integrate keywords (the integration calculates the ratio of this vmoment to that of the initial DF) grid= if set to True, build a grid and use that to evaluate integrals; if set to a grid-objects (such as returned by this procedure), use this grid gridpoints= number of points to use for the grid in 1D (default=101) returnGrid= if True, return the grid object (default=False) hierarchgrid= if True, use a hierarchical grid (default=False) nlevels= number of hierarchical levels for the hierarchical grid integrate_method= orbit.integrate method argument OUTPUT: mean vT HISTORY: 2011-03-31 - Written - Bovy (NYU) """ if isinstance(grid,evolveddiskdfGrid) or \ isinstance(grid,evolveddiskdfHierarchicalGrid): grido= grid vmomentT= self.vmomentsurfacemass(R,0,1,deg=deg,t=t, nsigma=nsigma,phi=phi, epsrel=epsrel, epsabs=epsabs,grid=grid, gridpoints=gridpoints, returnGrid=False, hierarchgrid=hierarchgrid, nlevels=nlevels, integrate_method=integrate_method) elif isinstance(grid,bool) and grid: #Precalculate the grid (vmomentT,grido)= self.vmomentsurfacemass(R,0,1,deg=deg,t=t, nsigma=nsigma,phi=phi, epsrel=epsrel, epsabs=epsabs,grid=grid, gridpoints=gridpoints, returnGrid=True, hierarchgrid=hierarchgrid, nlevels=nlevels, integrate_method=integrate_method) else: grido= False vmomentT= self.vmomentsurfacemass(R,0,1,deg=deg,t=t, nsigma=nsigma,phi=phi, epsrel=epsrel, epsabs=epsabs,grid=grid, gridpoints=gridpoints, returnGrid=False, hierarchgrid=hierarchgrid, nlevels=nlevels,integrate_method=integrate_method) if surfacemass is None: surfacemass= self.vmomentsurfacemass(R,0,0,deg=deg,t=t,phi=phi, nsigma=nsigma,epsrel=epsrel, epsabs=epsabs,grid=grido, gridpoints=gridpoints, returnGrid=False, hierarchgrid=hierarchgrid, nlevels=nlevels, integrate_method=integrate_method) out= vmomentT/surfacemass if returnGrid and ((isinstance(grid,bool) and grid) or isinstance(grid,evolveddiskdfGrid) or isinstance(grid,evolveddiskdfHierarchicalGrid)): return (out,grido) else: return out @potential_physical_input @physical_conversion('velocity2',pop=True) def sigmaR2(self,R,t=0.,nsigma=None,deg=False,phi=0., epsrel=1.e-02,epsabs=1.e-05, grid=None,gridpoints=101,returnGrid=False, surfacemass=None,meanvR=None, hierarchgrid=False,nlevels=2, integrate_method='dopr54_c'): """ NAME: sigmaR2 PURPOSE: calculate the radial variance of the velocity distribution at (R,phi) INPUT: R - radius at which to calculate the moment (can be Quantity) phi= azimuth (rad unless deg=True; can be Quantity) t= time at which to evaluate the DF (can be a list or ndarray; if this is the case, list needs to be in descending order and equally spaced) (can be Quantity) surfacemass, meanvR= if set use this pre-calculated surfacemass and mean vR nsigma - number of sigma to integrate the velocities over (based on an estimate, so be generous) deg= azimuth is in degree (default=False); do not set this when giving phi as a Quantity epsrel, epsabs - scipy.integrate keywords (the integration calculates the ratio of this vmoment to that of the initial DF) grid= if set to True, build a grid and use that to evaluate integrals; if set to a grid-objects (such as returned by this procedure), use this grid gridpoints= number of points to use for the grid in 1D (default=101) returnGrid= if True, return the grid object (default=False) hierarchgrid= if True, use a hierarchical grid (default=False) nlevels= number of hierarchical levels for the hierarchical grid integrate_method= orbit.integrate method argument OUTPUT: variance of vR HISTORY: 2011-03-31 - Written - Bovy (NYU) """ #The following aren't actually the moments, but they are the moments #times the surface-mass density if isinstance(grid,evolveddiskdfGrid) or \ isinstance(grid,evolveddiskdfHierarchicalGrid): grido= grid sigmaR2= self.vmomentsurfacemass(R,2,0,deg=deg,t=t,phi=phi, nsigma=nsigma, epsrel=epsrel, epsabs=epsabs,grid=grido, gridpoints=gridpoints, returnGrid=False, hierarchgrid=hierarchgrid, nlevels=nlevels, integrate_method=integrate_method) elif (meanvR is None or surfacemass is None ) \ and isinstance(grid,bool) and grid: #Precalculate the grid (sigmaR2,grido)= self.vmomentsurfacemass(R,2,0,deg=deg,t=t, nsigma=nsigma,phi=phi, epsrel=epsrel, epsabs=epsabs,grid=grid, gridpoints=gridpoints, returnGrid=True, hierarchgrid=hierarchgrid, nlevels=nlevels, integrate_method=integrate_method) else: grido= False sigmaR2= self.vmomentsurfacemass(R,2,0,deg=deg,t=t, nsigma=nsigma,phi=phi, epsrel=epsrel, epsabs=epsabs,grid=grido, gridpoints=gridpoints, returnGrid=False, hierarchgrid=hierarchgrid, nlevels=nlevels, integrate_method=integrate_method) if surfacemass is None: surfacemass= self.vmomentsurfacemass(R,0,0,deg=deg,t=t,phi=phi, nsigma=nsigma,epsrel=epsrel, epsabs=epsabs,grid=grido, gridpoints=gridpoints, returnGrid=False, hierarchgrid=hierarchgrid, nlevels=nlevels, integrate_method=integrate_method) if meanvR is None: meanvR= self.vmomentsurfacemass(R,1,0,deg=deg,t=t,phi=phi, nsigma=nsigma,epsrel=epsrel, epsabs=epsabs,grid=grido, gridpoints=gridpoints, returnGrid=False, hierarchgrid=hierarchgrid, nlevels=nlevels, integrate_method=integrate_method)/surfacemass out= sigmaR2/surfacemass-meanvR**2. if returnGrid and ((isinstance(grid,bool) and grid) or isinstance(grid,evolveddiskdfGrid) or isinstance(grid,evolveddiskdfHierarchicalGrid)): return (out,grido) else: return out @potential_physical_input @physical_conversion('velocity2',pop=True) def sigmaT2(self,R,t=0.,nsigma=None,deg=False,phi=0., epsrel=1.e-02,epsabs=1.e-05, grid=None,gridpoints=101,returnGrid=False, surfacemass=None,meanvT=None, hierarchgrid=False,nlevels=2, integrate_method='dopr54_c'): """ NAME: sigmaT2 PURPOSE: calculate the tangential variance of the velocity distribution at (R,phi) INPUT: R - radius at which to calculate the moment (can be Quantity) phi= azimuth (rad unless deg=True; can be Quantity) t= time at which to evaluate the DF (can be a list or ndarray; if this is the case, list needs to be in descending order and equally spaced) (can be Quantity) surfacemass, meanvT= if set use this pre-calculated surfacemass and mean tangential velocity nsigma - number of sigma to integrate the velocities over (based on an estimate, so be generous) deg= azimuth is in degree (default=False); do not set this when giving phi as a Quantity epsrel, epsabs - scipy.integrate keywords (the integration calculates the ratio of this vmoment to that of the initial DF) grid= if set to True, build a grid and use that to evaluate integrals; if set to a grid-objects (such as returned by this procedure), use this grid gridpoints= number of points to use for the grid in 1D (default=101) returnGrid= if True, return the grid object (default=False) hierarchgrid= if True, use a hierarchical grid (default=False) nlevels= number of hierarchical levels for the hierarchical grid integrate_method= orbit.integrate method argument OUTPUT: variance of vT HISTORY: 2011-03-31 - Written - Bovy (NYU) """ if isinstance(grid,evolveddiskdfGrid) or \ isinstance(grid,evolveddiskdfHierarchicalGrid): grido= grid sigmaT2= self.vmomentsurfacemass(R,0,2,deg=deg,t=t,phi=phi, nsigma=nsigma, epsrel=epsrel, epsabs=epsabs,grid=grido, gridpoints=gridpoints, returnGrid=False, hierarchgrid=hierarchgrid, nlevels=nlevels, integrate_method=integrate_method) elif (meanvT is None or surfacemass is None ) \ and isinstance(grid,bool) and grid: #Precalculate the grid (sigmaT2,grido)= self.vmomentsurfacemass(R,0,2,deg=deg,t=t, nsigma=nsigma,phi=phi, epsrel=epsrel, epsabs=epsabs,grid=grid, gridpoints=gridpoints, returnGrid=True, hierarchgrid=hierarchgrid, nlevels=nlevels, integrate_method=integrate_method) else: grido= False sigmaT2= self.vmomentsurfacemass(R,0,2,deg=deg,t=t, nsigma=nsigma,phi=phi, epsrel=epsrel, epsabs=epsabs,grid=grido, gridpoints=gridpoints, returnGrid=False, hierarchgrid=hierarchgrid, nlevels=nlevels, integrate_method=integrate_method) if surfacemass is None: surfacemass= self.vmomentsurfacemass(R,0,0,deg=deg,t=t,phi=phi, nsigma=nsigma,epsrel=epsrel, epsabs=epsabs,grid=grido, gridpoints=gridpoints, returnGrid=False, hierarchgrid=hierarchgrid, nlevels=nlevels, integrate_method=integrate_method) if meanvT is None: meanvT= self.vmomentsurfacemass(R,0,1,deg=deg,t=t,phi=phi, nsigma=nsigma,epsrel=epsrel, epsabs=epsabs,grid=grido, gridpoints=gridpoints, returnGrid=False, hierarchgrid=hierarchgrid, nlevels=nlevels, integrate_method=integrate_method)/surfacemass out= sigmaT2/surfacemass-meanvT**2. if returnGrid and ((isinstance(grid,bool) and grid) or isinstance(grid,evolveddiskdfGrid) or isinstance(grid,evolveddiskdfHierarchicalGrid)): return (out,grido) else: return out @potential_physical_input @physical_conversion('velocity2',pop=True) def sigmaRT(self,R,t=0.,nsigma=None,deg=False, epsrel=1.e-02,epsabs=1.e-05,phi=0., grid=None,gridpoints=101,returnGrid=False, surfacemass=None,meanvR=None,meanvT=None, hierarchgrid=False,nlevels=2, integrate_method='dopr54_c'): """ NAME: sigmaRT PURPOSE: calculate the radial-tangential co-variance of the velocity distribution at (R,phi) INPUT: R - radius at which to calculate the moment (can be Quantity) phi= azimuth (rad unless deg=True; can be Quantity) t= time at which to evaluate the DF (can be a list or ndarray; if this is the case, list needs to be in descending order and equally spaced) (can be Quantity) surfacemass, meanvR, meavT= if set use this pre-calculated surfacemass and mean vR and vT nsigma - number of sigma to integrate the velocities over (based on an estimate, so be generous) deg= azimuth is in degree (default=False); do not set this when giving phi as a Quantity epsrel, epsabs - scipy.integrate keywords (the integration calculates the ration of this vmoment to that of the initial DF) grid= if set to True, build a grid and use that to evaluate integrals; if set to a grid-objects (such as returned by this procedure), use this grid gridpoints= number of points to use for the grid in 1D (default=101) returnGrid= if True, return the grid object (default=False) hierarchgrid= if True, use a hierarchical grid (default=False) nlevels= number of hierarchical levels for the hierarchical grid integrate_method= orbit.integrate method argument OUTPUT: covariance of vR and vT HISTORY: 2011-03-31 - Written - Bovy (NYU) """ #The following aren't actually the moments, but they are the moments #times the surface-mass density if isinstance(grid,evolveddiskdfGrid) or \ isinstance(grid,evolveddiskdfHierarchicalGrid): grido= grid sigmaRT= self.vmomentsurfacemass(R,1,1,deg=deg,t=t,phi=phi, nsigma=nsigma, epsrel=epsrel, epsabs=epsabs,grid=grido, gridpoints=gridpoints, returnGrid=False, hierarchgrid=hierarchgrid, nlevels=nlevels, integrate_method=integrate_method) elif (meanvR is None or surfacemass is None ) \ and isinstance(grid,bool) and grid: #Precalculate the grid (sigmaRT,grido)= self.vmomentsurfacemass(R,1,1,deg=deg,t=t, nsigma=nsigma,phi=phi, epsrel=epsrel, epsabs=epsabs,grid=grid, gridpoints=gridpoints, returnGrid=True, hierarchgrid=hierarchgrid, nlevels=nlevels, integrate_method=integrate_method) else: grido= False sigmaRT= self.vmomentsurfacemass(R,1,1,deg=deg,t=t, nsigma=nsigma,phi=phi, epsrel=epsrel, epsabs=epsabs,grid=grido, gridpoints=gridpoints, returnGrid=False, hierarchgrid=hierarchgrid, nlevels=nlevels, integrate_method=integrate_method) if surfacemass is None: surfacemass= self.vmomentsurfacemass(R,0,0,deg=deg,t=t,phi=phi, nsigma=nsigma,epsrel=epsrel, epsabs=epsabs,grid=grido, gridpoints=gridpoints, returnGrid=False, hierarchgrid=hierarchgrid, nlevels=nlevels, integrate_method=integrate_method) if meanvR is None: meanvR= self.vmomentsurfacemass(R,1,0,deg=deg,t=t,phi=phi, nsigma=nsigma,epsrel=epsrel, epsabs=epsabs,grid=grido, gridpoints=gridpoints, returnGrid=False, hierarchgrid=hierarchgrid, nlevels=nlevels, integrate_method=integrate_method)/surfacemass if meanvT is None: meanvT= self.vmomentsurfacemass(R,0,1,deg=deg,t=t,phi=phi, nsigma=nsigma,epsrel=epsrel, epsabs=epsabs,grid=grido, gridpoints=gridpoints, returnGrid=False, hierarchgrid=hierarchgrid, nlevels=nlevels, integrate_method=integrate_method)/surfacemass out= sigmaRT/surfacemass-meanvR*meanvT if returnGrid and ((isinstance(grid,bool) and grid) or isinstance(grid,evolveddiskdfGrid) or isinstance(grid,evolveddiskdfHierarchicalGrid)): return (out,grido) else: return out @potential_physical_input @physical_conversion('frequency-kmskpc',pop=True) def oortA(self,R,t=0.,nsigma=None,deg=False,phi=0., epsrel=1.e-02,epsabs=1.e-05, grid=None,gridpoints=101,returnGrids=False, derivRGrid=None,derivphiGrid=None,derivGridpoints=101, derivHierarchgrid=False, hierarchgrid=False,nlevels=2,integrate_method='dopr54_c'): """ NAME: oortA PURPOSE: calculate the Oort function A at (R,phi,t) INPUT: R - radius at which to calculate A (can be Quantity) phi= azimuth (rad unless deg=True; can be Quantity t= time at which to evaluate the DF (can be a list or ndarray; if this is the case, list needs to be in descending order and equally spaced) (can be Quantity) nsigma - number of sigma to integrate the velocities over (based on an estimate, so be generous) deg= azimuth is in degree (default=False); do not set this when giving phi as a Quantity epsrel, epsabs - scipy.integrate keywords grid= if set to True, build a grid and use that to evaluate integrals; if set to a grid-objects (such as returned by this procedure), use this grid derivRGrid, derivphiGrid= if set to True, build a grid and use that to evaluate integrals of the derivatives of the DF;if set to a grid-objects (such as returned by this procedure), use this grid gridpoints= number of points to use for the grid in 1D (default=101) derivGridpoints= number of points to use for the grid in 1D (default=101) returnGrid= if True, return the grid objects (default=False) hierarchgrid= if True, use a hierarchical grid (default=False) derivHierarchgrid= if True, use a hierarchical grid (default=False) nlevels= number of hierarchical levels for the hierarchical grid integrate_method= orbit.integrate method argument OUTPUT: Oort A at R,phi,t HISTORY: 2011-10-16 - Written - Bovy (NYU) """ #First calculate the grids if they are not given if isinstance(grid,bool) and grid: (surfacemass,grid)= self.vmomentsurfacemass(R,0,0,deg=deg,t=t,phi=phi, nsigma=nsigma,epsrel=epsrel, epsabs=epsabs,grid=True, gridpoints=gridpoints, returnGrid=True, hierarchgrid=hierarchgrid, nlevels=nlevels, integrate_method=integrate_method) elif isinstance(grid,evolveddiskdfGrid) or \ isinstance(grid,evolveddiskdfHierarchicalGrid): surfacemass= self.vmomentsurfacemass(R,0,0,deg=deg,t=t,phi=phi, nsigma=nsigma,epsrel=epsrel, epsabs=epsabs,grid=grid, gridpoints=gridpoints, returnGrid=False, hierarchgrid=hierarchgrid, nlevels=nlevels, integrate_method=integrate_method) if isinstance(derivRGrid,bool) and derivRGrid: (dsurfacemassdR,derivRGrid)= self.vmomentsurfacemass(R,0,0,deg=deg,t=t,phi=phi, nsigma=nsigma,epsrel=epsrel, epsabs=epsabs,grid=True, gridpoints=derivGridpoints, returnGrid=True, hierarchgrid=derivHierarchgrid, nlevels=nlevels, integrate_method=integrate_method,deriv='R') elif isinstance(derivRGrid,evolveddiskdfGrid) or \ isinstance(derivRGrid,evolveddiskdfHierarchicalGrid): dsurfacemassdR= self.vmomentsurfacemass(R,0,0,deg=deg,t=t,phi=phi, nsigma=nsigma,epsrel=epsrel, epsabs=epsabs,grid=derivRGrid, gridpoints=derivGridpoints, returnGrid=False, hierarchgrid=derivHierarchgrid, nlevels=nlevels, integrate_method=integrate_method,deriv='R') if isinstance(derivphiGrid,bool) and derivphiGrid: (dsurfacemassdphi,derivphiGrid)= self.vmomentsurfacemass(R,0,0,deg=deg,t=t,phi=phi, nsigma=nsigma,epsrel=epsrel, epsabs=epsabs,grid=True, gridpoints=derivGridpoints, returnGrid=True, hierarchgrid=derivHierarchgrid, nlevels=nlevels, integrate_method=integrate_method,deriv='phi') elif isinstance(derivphiGrid,evolveddiskdfGrid) or \ isinstance(derivphiGrid,evolveddiskdfHierarchicalGrid): dsurfacemassdphi= self.vmomentsurfacemass(R,0,0,deg=deg,t=t,phi=phi, nsigma=nsigma,epsrel=epsrel, epsabs=epsabs,grid=derivphiGrid, gridpoints=derivGridpoints, returnGrid=False, hierarchgrid=derivHierarchgrid, nlevels=nlevels, integrate_method=integrate_method,deriv='phi') #2A= meanvT/R-dmeanvR/R/dphi-dmeanvphi/dR #meanvT meanvT= self.meanvT(R,t=t,nsigma=nsigma,deg=deg,phi=phi, epsrel=epsrel,epsabs=epsabs, grid=grid,gridpoints=gridpoints,returnGrid=False, surfacemass=surfacemass, hierarchgrid=hierarchgrid, nlevels=nlevels,integrate_method=integrate_method, use_physical=False) dmeanvRdphi= (self.vmomentsurfacemass(R,1,0,deg=deg,t=t,phi=phi, nsigma=nsigma,epsrel=epsrel, epsabs=epsabs,grid=derivphiGrid, gridpoints=derivGridpoints, returnGrid=False, hierarchgrid=derivHierarchgrid, nlevels=nlevels, integrate_method=integrate_method,deriv='phi') /surfacemass -self.vmomentsurfacemass(R,1,0,deg=deg,t=t,phi=phi, nsigma=nsigma,epsrel=epsrel, epsabs=epsabs,grid=grid, gridpoints=gridpoints, returnGrid=False, hierarchgrid=hierarchgrid, nlevels=nlevels, integrate_method=integrate_method) /surfacemass**2.*dsurfacemassdphi) dmeanvTdR= (self.vmomentsurfacemass(R,0,1,deg=deg,t=t,phi=phi, nsigma=nsigma,epsrel=epsrel, epsabs=epsabs,grid=derivRGrid, gridpoints=derivGridpoints, returnGrid=False, hierarchgrid=derivHierarchgrid, nlevels=nlevels, integrate_method=integrate_method,deriv='R') /surfacemass -self.vmomentsurfacemass(R,0,1,deg=deg,t=t,phi=phi, nsigma=nsigma,epsrel=epsrel, epsabs=epsabs,grid=grid, gridpoints=gridpoints, returnGrid=False, hierarchgrid=hierarchgrid, nlevels=nlevels, integrate_method=integrate_method) /surfacemass**2.*dsurfacemassdR) if returnGrids: return (0.5*(meanvT/R-dmeanvRdphi/R-dmeanvTdR),grid, derivRGrid,derivphiGrid) else: return 0.5*(meanvT/R-dmeanvRdphi/R-dmeanvTdR) @potential_physical_input @physical_conversion('frequency-kmskpc',pop=True) def oortB(self,R,t=0.,nsigma=None,deg=False,phi=0., epsrel=1.e-02,epsabs=1.e-05, grid=None,gridpoints=101,returnGrids=False, derivRGrid=None,derivphiGrid=None,derivGridpoints=101, derivHierarchgrid=False, hierarchgrid=False,nlevels=2,integrate_method='dopr54_c'): """ NAME: oortB PURPOSE: calculate the Oort function B at (R,phi,t) INPUT: R - radius at which to calculate B (can be Quantity) phi= azimuth (rad unless deg=True; can be Quantity) t= time at which to evaluate the DF (can be a list or ndarray; if this is the case, list needs to be in descending order and equally spaced) (can be Quantity) nsigma - number of sigma to integrate the velocities over (based on an estimate, so be generous) deg= azimuth is in degree (default=False); do not set this when giving phi as a Quantity epsrel, epsabs - scipy.integrate keywords grid= if set to True, build a grid and use that to evaluate integrals; if set to a grid-objects (such as returned by this procedure), use this grid derivRGrid, derivphiGrid= if set to True, build a grid and use that to evaluat integrals of the derivatives of the DF: if set to a grid-objects (such as returned by this procedure), use this grid gridpoints= number of points to use for the grid in 1D (default=101) derivGridpoints= number of points to use for the grid in 1D (default=101) returnGrid= if True, return the grid objects (default=False) hierarchgrid= if True, use a hierarchical grid (default=False) derivHierarchgrid= if True, use a hierarchical grid (default=False) nlevels= number of hierarchical levels for the hierarchical grid integrate_method= orbit.integrate method argument OUTPUT: Oort B at R,phi,t HISTORY: 2011-10-16 - Written - Bovy (NYU) """ #First calculate the grids if they are not given if isinstance(grid,bool) and grid: (surfacemass,grid)= self.vmomentsurfacemass(R,0,0,deg=deg,t=t,phi=phi, nsigma=nsigma,epsrel=epsrel, epsabs=epsabs,grid=True, gridpoints=gridpoints, returnGrid=True, hierarchgrid=hierarchgrid, nlevels=nlevels, integrate_method=integrate_method) elif isinstance(grid,evolveddiskdfGrid) or \ isinstance(grid,evolveddiskdfHierarchicalGrid): surfacemass= self.vmomentsurfacemass(R,0,0,deg=deg,t=t,phi=phi, nsigma=nsigma,epsrel=epsrel, epsabs=epsabs,grid=grid, gridpoints=gridpoints, returnGrid=False, hierarchgrid=hierarchgrid, nlevels=nlevels, integrate_method=integrate_method) if isinstance(derivRGrid,bool) and derivRGrid: (dsurfacemassdR,derivRGrid)= self.vmomentsurfacemass(R,0,0,deg=deg,t=t,phi=phi, nsigma=nsigma,epsrel=epsrel, epsabs=epsabs,grid=True, gridpoints=derivGridpoints, returnGrid=True, hierarchgrid=derivHierarchgrid, nlevels=nlevels, integrate_method=integrate_method,deriv='R') elif isinstance(derivRGrid,evolveddiskdfGrid) or \ isinstance(derivRGrid,evolveddiskdfHierarchicalGrid): dsurfacemassdR= self.vmomentsurfacemass(R,0,0,deg=deg,t=t,phi=phi, nsigma=nsigma,epsrel=epsrel, epsabs=epsabs,grid=derivRGrid, gridpoints=derivGridpoints, returnGrid=False, hierarchgrid=derivHierarchgrid, nlevels=nlevels, integrate_method=integrate_method,deriv='R') if isinstance(derivphiGrid,bool) and derivphiGrid: (dsurfacemassdphi,derivphiGrid)= self.vmomentsurfacemass(R,0,0,deg=deg,t=t,phi=phi, nsigma=nsigma,epsrel=epsrel, epsabs=epsabs,grid=True, gridpoints=derivGridpoints, returnGrid=True, hierarchgrid=derivHierarchgrid, nlevels=nlevels, integrate_method=integrate_method,deriv='phi') elif isinstance(derivphiGrid,evolveddiskdfGrid) or \ isinstance(derivphiGrid,evolveddiskdfHierarchicalGrid): dsurfacemassdphi= self.vmomentsurfacemass(R,0,0,deg=deg,t=t,phi=phi, nsigma=nsigma,epsrel=epsrel, epsabs=epsabs,grid=derivphiGrid, gridpoints=derivGridpoints, returnGrid=False, hierarchgrid=derivHierarchgrid, nlevels=nlevels, integrate_method=integrate_method,deriv='phi') #2B= -meanvT/R+dmeanvR/R/dphi-dmeanvphi/dR #meanvT meanvT= self.meanvT(R,t=t,nsigma=nsigma,deg=deg,phi=phi, epsrel=epsrel,epsabs=epsabs, grid=grid,gridpoints=gridpoints,returnGrid=False, surfacemass=surfacemass, hierarchgrid=hierarchgrid, nlevels=nlevels,integrate_method=integrate_method, use_physical=False) dmeanvRdphi= (self.vmomentsurfacemass(R,1,0,deg=deg,t=t,phi=phi, nsigma=nsigma,epsrel=epsrel, epsabs=epsabs,grid=derivphiGrid, gridpoints=derivGridpoints, returnGrid=False, hierarchgrid=derivHierarchgrid, nlevels=nlevels, integrate_method=integrate_method,deriv='phi') /surfacemass -self.vmomentsurfacemass(R,1,0,deg=deg,t=t,phi=phi, nsigma=nsigma,epsrel=epsrel, epsabs=epsabs,grid=grid, gridpoints=gridpoints, returnGrid=False, hierarchgrid=hierarchgrid, nlevels=nlevels, integrate_method=integrate_method) /surfacemass**2.*dsurfacemassdphi) dmeanvTdR= (self.vmomentsurfacemass(R,0,1,deg=deg,t=t,phi=phi, nsigma=nsigma,epsrel=epsrel, epsabs=epsabs,grid=derivRGrid, gridpoints=derivGridpoints, returnGrid=False, hierarchgrid=derivHierarchgrid, nlevels=nlevels, integrate_method=integrate_method,deriv='R') /surfacemass -self.vmomentsurfacemass(R,0,1,deg=deg,t=t,phi=phi, nsigma=nsigma,epsrel=epsrel, epsabs=epsabs,grid=grid, gridpoints=gridpoints, returnGrid=False, hierarchgrid=hierarchgrid, nlevels=nlevels, integrate_method=integrate_method) /surfacemass**2.*dsurfacemassdR) if returnGrids: return (0.5*(-meanvT/R+dmeanvRdphi/R-dmeanvTdR),grid, derivRGrid,derivphiGrid) else: return 0.5*(-meanvT/R+dmeanvRdphi/R-dmeanvTdR) @potential_physical_input @physical_conversion('frequency-kmskpc',pop=True) def oortC(self,R,t=0.,nsigma=None,deg=False,phi=0., epsrel=1.e-02,epsabs=1.e-05, grid=None,gridpoints=101,returnGrids=False, derivRGrid=None,derivphiGrid=None,derivGridpoints=101, derivHierarchgrid=False, hierarchgrid=False,nlevels=2,integrate_method='dopr54_c'): """ NAME: oortC PURPOSE: calculate the Oort function C at (R,phi,t) INPUT: R - radius at which to calculate C (can be Quantity) phi= azimuth (rad unless deg=True; can be Quantity) t= time at which to evaluate the DF (can be a list or ndarray; if this is the case, list needs to be in descending order and equally spaced) (can be Quantity) nsigma - number of sigma to integrate the velocities over (based on an estimate, so be generous) deg= azimuth is in degree (default=False); do not set this when giving phi as a Quantity epsrel, epsabs - scipy.integrate keywords grid= if set to True, build a grid and use that to evaluate integrals; if set to a grid-objects (such as returned by this procedure), use this grid derivRGrid, derivphiGrid= if set to True, build a grid and use that to evaluate integrals of the derivatives of the DF; if set to a grid-objects (such as returned by this procedure), use this grid gridpoints= number of points to use for the grid in 1D (default=101) derivGridpoints= number of points to use for the grid in 1D (default=101) returnGrid= if True, return the grid objects (default=False) hierarchgrid= if True, use a hierarchical grid (default=False) derivHierarchgrid= if True, use a hierarchical grid (default=False) nlevels= number of hierarchical levels for the hierarchical grid integrate_method= orbit.integrate method argument OUTPUT: Oort C at R,phi,t HISTORY: 2011-10-16 - Written - Bovy (NYU) """ #First calculate the grids if they are not given if isinstance(grid,bool) and grid: (surfacemass,grid)= self.vmomentsurfacemass(R,0,0,deg=deg,t=t,phi=phi, nsigma=nsigma,epsrel=epsrel, epsabs=epsabs,grid=True, gridpoints=gridpoints, returnGrid=True, hierarchgrid=hierarchgrid, nlevels=nlevels, integrate_method=integrate_method) elif isinstance(grid,evolveddiskdfGrid) or \ isinstance(grid,evolveddiskdfHierarchicalGrid): surfacemass= self.vmomentsurfacemass(R,0,0,deg=deg,t=t,phi=phi, nsigma=nsigma,epsrel=epsrel, epsabs=epsabs,grid=grid, gridpoints=gridpoints, returnGrid=False, hierarchgrid=hierarchgrid, nlevels=nlevels, integrate_method=integrate_method) if isinstance(derivRGrid,bool) and derivRGrid: (dsurfacemassdR,derivRGrid)= self.vmomentsurfacemass(R,0,0,deg=deg,t=t,phi=phi, nsigma=nsigma,epsrel=epsrel, epsabs=epsabs,grid=True, gridpoints=derivGridpoints, returnGrid=True, hierarchgrid=derivHierarchgrid, nlevels=nlevels, integrate_method=integrate_method,deriv='R') elif isinstance(derivRGrid,evolveddiskdfGrid) or \ isinstance(derivRGrid,evolveddiskdfHierarchicalGrid): dsurfacemassdR= self.vmomentsurfacemass(R,0,0,deg=deg,t=t,phi=phi, nsigma=nsigma,epsrel=epsrel, epsabs=epsabs,grid=derivRGrid, gridpoints=derivGridpoints, returnGrid=False, hierarchgrid=derivHierarchgrid, nlevels=nlevels, integrate_method=integrate_method,deriv='R') if isinstance(derivphiGrid,bool) and derivphiGrid: (dsurfacemassdphi,derivphiGrid)= self.vmomentsurfacemass(R,0,0,deg=deg,t=t,phi=phi, nsigma=nsigma,epsrel=epsrel, epsabs=epsabs,grid=True, gridpoints=derivGridpoints, returnGrid=True, hierarchgrid=derivHierarchgrid, nlevels=nlevels, integrate_method=integrate_method,deriv='phi') elif isinstance(derivphiGrid,evolveddiskdfGrid) or \ isinstance(derivphiGrid,evolveddiskdfHierarchicalGrid): dsurfacemassdphi= self.vmomentsurfacemass(R,0,0,deg=deg,t=t,phi=phi, nsigma=nsigma,epsrel=epsrel, epsabs=epsabs,grid=derivphiGrid, gridpoints=derivGridpoints, returnGrid=False, hierarchgrid=derivHierarchgrid, nlevels=nlevels, integrate_method=integrate_method,deriv='phi') #2C= -meanvR/R-dmeanvT/R/dphi+dmeanvR/dR #meanvR meanvR= self.meanvR(R,t=t,nsigma=nsigma,deg=deg,phi=phi, epsrel=epsrel,epsabs=epsabs, grid=grid,gridpoints=gridpoints,returnGrid=False, surfacemass=surfacemass, hierarchgrid=hierarchgrid, nlevels=nlevels,integrate_method=integrate_method, use_physical=False) dmeanvTdphi= (self.vmomentsurfacemass(R,0,1,deg=deg,t=t,phi=phi, nsigma=nsigma,epsrel=epsrel, epsabs=epsabs,grid=derivphiGrid, gridpoints=derivGridpoints, returnGrid=False, hierarchgrid=derivHierarchgrid, nlevels=nlevels, integrate_method=integrate_method,deriv='phi') /surfacemass -self.vmomentsurfacemass(R,0,1,deg=deg,t=t,phi=phi, nsigma=nsigma,epsrel=epsrel, epsabs=epsabs,grid=grid, gridpoints=gridpoints, returnGrid=False, hierarchgrid=hierarchgrid, nlevels=nlevels, integrate_method=integrate_method) /surfacemass**2.*dsurfacemassdphi) dmeanvRdR= (self.vmomentsurfacemass(R,1,0,deg=deg,t=t,phi=phi, nsigma=nsigma,epsrel=epsrel, epsabs=epsabs,grid=derivRGrid, gridpoints=derivGridpoints, returnGrid=False, hierarchgrid=derivHierarchgrid, nlevels=nlevels, integrate_method=integrate_method,deriv='R') /surfacemass -self.vmomentsurfacemass(R,1,0,deg=deg,t=t,phi=phi, nsigma=nsigma,epsrel=epsrel, epsabs=epsabs,grid=grid, gridpoints=gridpoints, returnGrid=False, hierarchgrid=hierarchgrid, nlevels=nlevels, integrate_method=integrate_method) /surfacemass**2.*dsurfacemassdR) if returnGrids: return (0.5*(-meanvR/R-dmeanvTdphi/R+dmeanvRdR),grid, derivRGrid,derivphiGrid) else: return 0.5*(-meanvR/R-dmeanvTdphi/R+dmeanvRdR) @potential_physical_input @physical_conversion('frequency-kmskpc',pop=True) def oortK(self,R,t=0.,nsigma=None,deg=False,phi=0., epsrel=1.e-02,epsabs=1.e-05, grid=None,gridpoints=101,returnGrids=False, derivRGrid=None,derivphiGrid=None,derivGridpoints=101, derivHierarchgrid=False, hierarchgrid=False,nlevels=2,integrate_method='dopr54_c'): """ NAME: oortK PURPOSE: calculate the Oort function K at (R,phi,t) INPUT: R - radius at which to calculate K (can be Quantity) phi= azimuth (rad unless deg=True; can be Quantity) t= time at which to evaluate the DF (can be a list or ndarray; if this is the case, list needs to be in descending order and equally spaced) (can be Quantity) nsigma - number of sigma to integrate the velocities over (based on an estimate, so be generous) deg= azimuth is in degree (default=False); do not set this when giving phi as a Quantity epsrel, epsabs - scipy.integrate keywords grid= if set to True, build a grid and use that to evaluate integrals; if set to a grid-objects (such as returned by this procedure), use this grid derivRGrid, derivphiGrid= if set to True, build a grid and use that to evaluate integrals of the derivatives of the DF; if set to a grid-objects (such as returned by this procedure), use this grid gridpoints= number of points to use for the grid in 1D (default=101) derivGridpoints= number of points to use for the grid in 1D (default=101) returnGrid= if True, return the grid objects (default=False) hierarchgrid= if True, use a hierarchical grid (default=False) derivHierarchgrid= if True, use a hierarchical grid (default=False) nlevels= number of hierarchical levels for the hierarchical grid integrate_method= orbit.integrate method argument OUTPUT: Oort K at R,phi,t HISTORY: 2011-10-16 - Written - Bovy (NYU) """ #First calculate the grids if they are not given if isinstance(grid,bool) and grid: (surfacemass,grid)= self.vmomentsurfacemass(R,0,0,deg=deg,t=t,phi=phi, nsigma=nsigma,epsrel=epsrel, epsabs=epsabs,grid=True, gridpoints=gridpoints, returnGrid=True, hierarchgrid=hierarchgrid, nlevels=nlevels, integrate_method=integrate_method) elif isinstance(grid,evolveddiskdfGrid) or \ isinstance(grid,evolveddiskdfHierarchicalGrid): surfacemass= self.vmomentsurfacemass(R,0,0,deg=deg,t=t,phi=phi, nsigma=nsigma,epsrel=epsrel, epsabs=epsabs,grid=grid, gridpoints=gridpoints, returnGrid=False, hierarchgrid=hierarchgrid, nlevels=nlevels, integrate_method=integrate_method) if isinstance(derivRGrid,bool) and derivRGrid: (dsurfacemassdR,derivRGrid)= self.vmomentsurfacemass(R,0,0,deg=deg,t=t,phi=phi, nsigma=nsigma,epsrel=epsrel, epsabs=epsabs,grid=True, gridpoints=derivGridpoints, returnGrid=True, hierarchgrid=derivHierarchgrid, nlevels=nlevels, integrate_method=integrate_method,deriv='R') elif isinstance(derivRGrid,evolveddiskdfGrid) or \ isinstance(derivRGrid,evolveddiskdfHierarchicalGrid): dsurfacemassdR= self.vmomentsurfacemass(R,0,0,deg=deg,t=t,phi=phi, nsigma=nsigma,epsrel=epsrel, epsabs=epsabs,grid=derivRGrid, gridpoints=derivGridpoints, returnGrid=False, hierarchgrid=derivHierarchgrid, nlevels=nlevels, integrate_method=integrate_method,deriv='R') if isinstance(derivphiGrid,bool) and derivphiGrid: (dsurfacemassdphi,derivphiGrid)= self.vmomentsurfacemass(R,0,0,deg=deg,t=t,phi=phi, nsigma=nsigma,epsrel=epsrel, epsabs=epsabs,grid=True, gridpoints=derivGridpoints, returnGrid=True, hierarchgrid=derivHierarchgrid, nlevels=nlevels, integrate_method=integrate_method,deriv='phi') elif isinstance(derivphiGrid,evolveddiskdfGrid) or \ isinstance(derivphiGrid,evolveddiskdfHierarchicalGrid): dsurfacemassdphi= self.vmomentsurfacemass(R,0,0,deg=deg,t=t,phi=phi, nsigma=nsigma,epsrel=epsrel, epsabs=epsabs,grid=derivphiGrid, gridpoints=derivGridpoints, returnGrid=False, hierarchgrid=derivHierarchgrid, nlevels=nlevels, integrate_method=integrate_method,deriv='phi') #2K= meanvR/R+dmeanvT/R/dphi+dmeanvR/dR #meanvR meanvR= self.meanvR(R,t=t,nsigma=nsigma,deg=deg,phi=phi, epsrel=epsrel,epsabs=epsabs, grid=grid,gridpoints=gridpoints,returnGrid=False, surfacemass=surfacemass, hierarchgrid=hierarchgrid, nlevels=nlevels,integrate_method=integrate_method, use_physical=False) dmeanvTdphi= (self.vmomentsurfacemass(R,0,1,deg=deg,t=t,phi=phi, nsigma=nsigma,epsrel=epsrel, epsabs=epsabs,grid=derivphiGrid, gridpoints=derivGridpoints, returnGrid=False, hierarchgrid=derivHierarchgrid, nlevels=nlevels, integrate_method=integrate_method,deriv='phi') /surfacemass -self.vmomentsurfacemass(R,0,1,deg=deg,t=t,phi=phi, nsigma=nsigma,epsrel=epsrel, epsabs=epsabs,grid=grid, gridpoints=gridpoints, returnGrid=False, hierarchgrid=hierarchgrid, nlevels=nlevels, integrate_method=integrate_method) /surfacemass**2.*dsurfacemassdphi) dmeanvRdR= (self.vmomentsurfacemass(R,1,0,deg=deg,t=t,phi=phi, nsigma=nsigma,epsrel=epsrel, epsabs=epsabs,grid=derivRGrid, gridpoints=derivGridpoints, returnGrid=False, hierarchgrid=derivHierarchgrid, nlevels=nlevels, integrate_method=integrate_method,deriv='R') /surfacemass -self.vmomentsurfacemass(R,1,0,deg=deg,t=t,phi=phi, nsigma=nsigma,epsrel=epsrel, epsabs=epsabs,grid=grid, gridpoints=gridpoints, returnGrid=False, hierarchgrid=hierarchgrid, nlevels=nlevels, integrate_method=integrate_method) /surfacemass**2.*dsurfacemassdR) if returnGrids: return (0.5*(meanvR/R+dmeanvTdphi/R+dmeanvRdR),grid, derivRGrid,derivphiGrid) else: return 0.5*(meanvR/R+dmeanvTdphi/R+dmeanvRdR) def _vmomentsurfacemassGrid(self,n,m,grid): """Internal function to evaluate vmomentsurfacemass using a grid rather than direct integration""" if len(grid.df.shape) == 3: tlist= True else: tlist= False if tlist: nt= grid.df.shape[2] out= [] for ii in range(nt): out.append(nu.dot(grid.vRgrid**n,nu.dot(grid.df[:,:,ii],grid.vTgrid**m))*\ (grid.vRgrid[1]-grid.vRgrid[0])*(grid.vTgrid[1]-grid.vTgrid[0])) return nu.array(out) else: return nu.dot(grid.vRgrid**n,nu.dot(grid.df,grid.vTgrid**m))*\ (grid.vRgrid[1]-grid.vRgrid[0])*(grid.vTgrid[1]-grid.vTgrid[0]) def _buildvgrid(self,R,phi,nsigma,t,sigmaR1,sigmaT1,meanvR,meanvT, gridpoints,print_progress,integrate_method,deriv): """Internal function to grid the vDF at a given location""" out= evolveddiskdfGrid() out.sigmaR1= sigmaR1 out.sigmaT1= sigmaT1 out.meanvR= meanvR out.meanvT= meanvT out.vRgrid= nu.linspace(meanvR-nsigma*sigmaR1,meanvR+nsigma*sigmaR1, gridpoints) out.vTgrid= nu.linspace(meanvT-nsigma*sigmaT1,meanvT+nsigma*sigmaT1, gridpoints) if isinstance(t,(list,nu.ndarray)): nt= len(t) out.df= nu.zeros((gridpoints,gridpoints,nt)) for ii in range(gridpoints): for jj in range(gridpoints-1,-1,-1):#Reverse, so we get the peak before we get to the extreme lags NOT NECESSARY if print_progress: #pragma: no cover sys.stdout.write('\r'+"Velocity gridpoint %i out of %i" % \ (jj+ii*gridpoints+1,gridpoints*gridpoints)) sys.stdout.flush() thiso= Orbit([R,out.vRgrid[ii],out.vTgrid[jj],phi]) out.df[ii,jj,:]= self(thiso,nu.array(t).flatten(), integrate_method=integrate_method, deriv=deriv,use_physical=False) out.df[ii,jj,nu.isnan(out.df[ii,jj,:])]= 0. #BOVY: for now if print_progress: sys.stdout.write('\n') #pragma: no cover else: out.df= nu.zeros((gridpoints,gridpoints)) for ii in range(gridpoints): for jj in range(gridpoints): if print_progress: #pragma: no cover sys.stdout.write('\r'+"Velocity gridpoint %i out of %i" % \ (jj+ii*gridpoints+1,gridpoints*gridpoints)) sys.stdout.flush() thiso= Orbit([R,out.vRgrid[ii],out.vTgrid[jj],phi]) out.df[ii,jj]= self(thiso,t, integrate_method=integrate_method, deriv=deriv,use_physical=False) if nu.isnan(out.df[ii,jj]): out.df[ii,jj]= 0. #BOVY: for now if print_progress: sys.stdout.write('\n') #pragma: no cover return out def _create_ts_tlist(self,t,integrate_method): #Check input if not all(t == sorted(t,reverse=True)): raise IOError("List of times has to be sorted in descending order") #Initialize if integrate_method == 'odeint': _NTS= 1000 tmax= nu.amax(t) ts= nu.linspace(tmax,self._to,_NTS) #Add other t ts= list(ts) ts.extend([self._to+tmax-ti for ti in t if ti != tmax]) else: if len(t) == 1: #Special case this because it is confusing ts= nu.array([t[0],self._to]) else: ts= -t+self._to+nu.amax(t) #sort ts= list(ts) ts.sort(reverse=True) return nu.array(ts) def _call_marginalizevperp(self,o,integrate_method='dopr54_c',**kwargs): """Call the DF, marginalizing over perpendicular velocity""" #Get d, l, vlos l= o.ll(obs=[1.,0.,0.],ro=1.)*_DEGTORAD vlos= o.vlos(ro=1.,vo=1.,obs=[1.,0.,0.,0.,0.,0.]) R= o.R(use_physical=False) phi= o.phi(use_physical=False) #Get local circular velocity, projected onto the los if isinstance(self._pot,list): vcirc= calcRotcurve([p for p in self._pot if not p.isNonAxi],R)[0] else: vcirc= calcRotcurve(self._pot,R)[0] vcirclos= vcirc*math.sin(phi+l) #Marginalize alphalos= phi+l if not 'nsigma' in kwargs or ('nsigma' in kwargs and \ kwargs['nsigma'] is None): nsigma= _NSIGMA else: nsigma= kwargs['nsigma'] kwargs.pop('nsigma',None) #BOVY: add asymmetric drift here? if math.fabs(math.sin(alphalos)) < math.sqrt(1./2.): sigmaR1= nu.sqrt(self._initdf.sigmaT2(R,phi=phi, use_physical=False)) #Slight abuse cosalphalos= math.cos(alphalos) tanalphalos= math.tan(alphalos) return integrate.quad(_marginalizeVperpIntegrandSinAlphaSmall, -nsigma,nsigma, args=(self,R,cosalphalos,tanalphalos, vlos-vcirclos,vcirc, sigmaR1,phi), **kwargs)[0]/math.fabs(cosalphalos)*sigmaR1 else: sigmaR1= nu.sqrt(self._initdf.sigmaR2(R,phi=phi, use_physical=False)) sinalphalos= math.sin(alphalos) cotalphalos= 1./math.tan(alphalos) return integrate.quad(_marginalizeVperpIntegrandSinAlphaLarge, -nsigma,nsigma, args=(self,R,sinalphalos,cotalphalos, vlos-vcirclos,vcirc,sigmaR1,phi), **kwargs)[0]/math.fabs(sinalphalos)*sigmaR1 def _call_marginalizevlos(self,o,integrate_method='dopr54_c',**kwargs): """Call the DF, marginalizing over line-of-sight velocity""" #Get d, l, vperp l= o.ll(obs=[1.,0.,0.],ro=1.)*_DEGTORAD vperp= o.vll(ro=1.,vo=1.,obs=[1.,0.,0.,0.,0.,0.]) R= o.R(use_physical=False) phi= o.phi(use_physical=False) #Get local circular velocity, projected onto the perpendicular #direction if isinstance(self._pot,list): vcirc= calcRotcurve([p for p in self._pot if not p.isNonAxi],R)[0] else: vcirc= calcRotcurve(self._pot,R)[0] vcircperp= vcirc*math.cos(phi+l) #Marginalize alphaperp= math.pi/2.+phi+l if not 'nsigma' in kwargs or ('nsigma' in kwargs and \ kwargs['nsigma'] is None): nsigma= _NSIGMA else: nsigma= kwargs['nsigma'] kwargs.pop('nsigma',None) if math.fabs(math.sin(alphaperp)) < math.sqrt(1./2.): sigmaR1= nu.sqrt(self._initdf.sigmaT2(R,phi=phi, use_physical=False)) #slight abuse va= vcirc-self._initdf.meanvT(R,phi=phi,use_physical=False) cosalphaperp= math.cos(alphaperp) tanalphaperp= math.tan(alphaperp) #we can reuse the VperpIntegrand, since it is just another angle return integrate.quad(_marginalizeVperpIntegrandSinAlphaSmall, -va/sigmaR1-nsigma, -va/sigmaR1+nsigma, args=(self,R,cosalphaperp,tanalphaperp, vperp-vcircperp,vcirc, sigmaR1,phi), **kwargs)[0]/math.fabs(cosalphaperp)*sigmaR1 else: sigmaR1= nu.sqrt(self._initdf.sigmaR2(R,phi=phi, use_physical=False)) sinalphaperp= math.sin(alphaperp) cotalphaperp= 1./math.tan(alphaperp) #we can reuse the VperpIntegrand, since it is just another angle return integrate.quad(_marginalizeVperpIntegrandSinAlphaLarge, -nsigma,nsigma, args=(self,R,sinalphaperp,cotalphaperp, vperp-vcircperp,vcirc,sigmaR1,phi), **kwargs)[0]/math.fabs(sinalphaperp)*sigmaR1 def _vmomentsurfacemassHierarchicalGrid(self,n,m,grid): """Internal function to evaluate vmomentsurfacemass using a hierarchical grid rather than direct integration, rather unnecessary""" return grid(n,m) class evolveddiskdfGrid(object): """(not quite) Empty class since it is only used to store some stuff""" def __init__(self): return None def plot(self,tt=0): """ NAME: plot PURPOSE: plot the velocity distribution INPUT: t= optional time index OUTPUT: plot of velocity distribution to output device HISTORY: 2011-06-27 - Written - Bovy (NYU) """ xrange= [self.vRgrid[0],self.vRgrid[len(self.vRgrid)-1]] yrange= [self.vTgrid[0],self.vTgrid[len(self.vTgrid)-1]] if len(self.df.shape) == 3: plotthis= self.df[:,:,tt] else: plotthis= self.df bovy_plot.bovy_dens2d(plotthis.T,cmap='gist_yarg',origin='lower', aspect=(xrange[1]-xrange[0])/\ (yrange[1]-yrange[0]), extent=[xrange[0],xrange[1], yrange[0],yrange[1]], xlabel=r'$v_R / v_0$', ylabel=r'$v_T / v_0$') class evolveddiskdfHierarchicalGrid(object): """Class that holds a hierarchical velocity grid""" def __init__(self,edf,R,phi,nsigma,t,sigmaR1,sigmaT1,meanvR,meanvT, gridpoints,nlevels,deriv,upperdxdy=None,print_progress=False, nlevelsTotal=None): """ NAME: __init__ PURPOSE: Initialize a hierarchical grid INPUT: edf - evolveddiskdf instance R - Radius phi- azimuth nsigma - number of sigma to integrate over t- time sigmaR1 - radial dispersion sigmaT1 - tangential dispersion meanvR - mean of radial velocity meanvT - mean of tangential velocity gridpoints- number of gridpoints nlevels- number of levels to build deriv- None, 'R', or 'phi': calculates derivative of the moment wrt R or phi upperdxdy= area element of previous hierarchical level print_progress= if True, print progress on building the grid OUTPUT: object HISTORY: 2011-04-21 - Written - Bovy (NYU) """ self.sigmaR1= sigmaR1 self.sigmaT1= sigmaT1 self.meanvR= meanvR self.meanvT= meanvT self.gridpoints= gridpoints self.vRgrid= nu.linspace(self.meanvR-nsigma*self.sigmaR1, self.meanvR+nsigma*self.sigmaR1, self.gridpoints) self.vTgrid= nu.linspace(self.meanvT-nsigma*self.sigmaT1, self.meanvT+nsigma*self.sigmaT1, self.gridpoints) self.t= t if nlevelsTotal is None: nlevelsTotal= nlevels self.nlevels= nlevels self.nlevelsTotal= nlevelsTotal if isinstance(t,(list,nu.ndarray)): nt= len(t) self.df= nu.zeros((gridpoints,gridpoints,nt)) dxdy= (self.vRgrid[1]-self.vRgrid[0])\ *(self.vTgrid[1]-self.vTgrid[0]) if nlevels > 0: xsubmin= int(gridpoints)//4 xsubmax= gridpoints-int(gridpoints)//4 else: xsubmin= gridpoints xsubmax= 0 ysubmin, ysubmax= xsubmin, xsubmax for ii in range(gridpoints): for jj in range(gridpoints): if print_progress: #pragma: no cover sys.stdout.write('\r'+"Velocity gridpoint %i out of %i" % \ (jj+ii*gridpoints+1,gridpoints*gridpoints)) sys.stdout.flush() #If this is part of a subgrid, ignore if nlevels > 1 and ii >= xsubmin and ii < xsubmax \ and jj >= ysubmin and jj < ysubmax: continue thiso= Orbit([R,self.vRgrid[ii],self.vTgrid[jj],phi]) self.df[ii,jj,:]= edf(thiso,nu.array(t).flatten(), deriv=deriv) self.df[ii,jj,nu.isnan(self.df[ii,jj,:])]= 0.#BOVY: for now #Multiply in area, somewhat tricky for edge objects if upperdxdy is None or (ii != 0 and ii != gridpoints-1\ and jj != 0 and jj != gridpoints-1): self.df[ii,jj,:]*= dxdy elif ((ii == 0 or ii == gridpoints-1) and \ (jj != 0 and jj != gridpoints-1))\ or \ ((jj == 0 or jj == gridpoints-1) and \ (ii != 0 and ii != gridpoints-1)): #edge self.df[ii,jj,:]*= 1.5*dxdy/1.5 #turn this off for now else: #corner self.df[ii,jj,:]*= 2.25*dxdy/2.25 #turn this off for now if print_progress: sys.stdout.write('\n') #pragma: no cover else: self.df= nu.zeros((gridpoints,gridpoints)) dxdy= (self.vRgrid[1]-self.vRgrid[0])\ *(self.vTgrid[1]-self.vTgrid[0]) if nlevels > 0: xsubmin= int(gridpoints)//4 xsubmax= gridpoints-int(gridpoints)//4 else: xsubmin= gridpoints xsubmax= 0 ysubmin, ysubmax= xsubmin, xsubmax for ii in range(gridpoints): for jj in range(gridpoints): if print_progress: #pragma: no cover sys.stdout.write('\r'+"Velocity gridpoint %i out of %i" % \ (jj+ii*gridpoints+1,gridpoints*gridpoints)) sys.stdout.flush() #If this is part of a subgrid, ignore if nlevels > 1 and ii >= xsubmin and ii < xsubmax \ and jj >= ysubmin and jj < ysubmax: continue thiso= Orbit([R,self.vRgrid[ii],self.vTgrid[jj],phi]) self.df[ii,jj]= edf(thiso,t,deriv=deriv) if nu.isnan(self.df[ii,jj]): self.df[ii,jj]= 0. #BOVY: for now #Multiply in area, somewhat tricky for edge objects if upperdxdy is None or (ii != 0 and ii != gridpoints-1\ and jj != 0 and jj != gridpoints-1): self.df[ii,jj]*= dxdy elif ((ii == 0 or ii == gridpoints-1) and \ (jj != 0 and jj != gridpoints-1))\ or \ ((jj == 0 or jj == gridpoints-1) and \ (ii != 0 and ii != gridpoints-1)): #edge self.df[ii,jj]*= 1.5*dxdy/1.5#turn this off for now else: #corner self.df[ii,jj]*= 2.25*dxdy/2.25#turn this off for now if print_progress: sys.stdout.write('\n') #pragma: no cover if nlevels > 1: #Set up subgrid subnsigma= (self.meanvR-self.vRgrid[xsubmin])/self.sigmaR1 self.subgrid= evolveddiskdfHierarchicalGrid(edf,R,phi, subnsigma,t, sigmaR1, sigmaT1, meanvR, meanvT, gridpoints, nlevels-1, deriv, upperdxdy=dxdy, print_progress=print_progress, nlevelsTotal=nlevelsTotal) else: self.subgrid= None return None def __call__(self,n,m): """Call""" if isinstance(self.t,(list,nu.ndarray)): tlist= True else: tlist= False if tlist: nt= self.df.shape[2] out= [] for ii in range(nt): #We already multiplied in the area out.append(nu.dot(self.vRgrid**n,nu.dot(self.df[:,:,ii], self.vTgrid**m))) if self.subgrid is None: return nu.array(out) else: return nu.array(out)+ self.subgrid(n,m) else: #We already multiplied in the area thislevel= nu.dot(self.vRgrid**n,nu.dot(self.df,self.vTgrid**m)) if self.subgrid is None: return thislevel else: return thislevel+self.subgrid(n,m) def plot(self,tt=0,vmax=None,aspect=None,extent=None): """ NAME: plot PURPOSE: plot the velocity distribution INPUT: t= optional time index OUTPUT: plot of velocity distribution to output device HISTORY: 2011-06-27 - Written - Bovy (NYU) """ if vmax is None: vmax= self.max(tt=tt)*2. #Figure out how big of a grid we need dvR= (self.vRgrid[1]-self.vRgrid[0]) dvT= (self.vTgrid[1]-self.vTgrid[0]) nvR= len(self.vRgrid) nvT= len(self.vTgrid) nUpperLevels= self.nlevelsTotal-self.nlevels nvRTot= nvR*2**nUpperLevels nvTTot= nvT*2**nUpperLevels plotthis= nu.zeros((nvRTot,nvTTot)) if len(self.df.shape) == 3: plotdf= copy.copy(self.df[:,:,tt]) else: plotdf= copy.copy(self.df) plotdf[(plotdf == 0.)]= _NAN #Fill up the grid if nUpperLevels > 0: xsubmin= nvRTot//2-nvR//2-1 xsubmax= nvRTot//2+nvR//2 ysubmin= nvTTot//2-nvT//2-1 ysubmax= nvTTot//2+nvT//2 #Set outside this subgrid to NaN plotthis[:,:]= _NAN #within the grid gets filled in below else: xsubmin= 0 xsubmax= nvR ysubmin= 0 ysubmax= nvT #Fill in this level plotthis[xsubmin:xsubmax,ysubmin:ysubmax]= plotdf/dvR/dvT/nvR/nvT #Plot if nUpperLevels == 0: xrange= [self.vRgrid[0]+dvR/2.,self.vRgrid[len(self.vRgrid)-1]-dvR/2.] yrange= [self.vTgrid[0]+dvT/2.,self.vTgrid[len(self.vTgrid)-1]-dvT/2.] aspect=(xrange[1]-xrange[0])/\ (yrange[1]-yrange[0]) extent=[xrange[0],xrange[1], yrange[0],yrange[1]] bovy_plot.bovy_dens2d(plotthis.T,cmap='gist_yarg',origin='lower', interpolation='nearest', aspect=aspect, extent=extent, xlabel=r'$v_R / v_0$', ylabel=r'$v_T / v_0$', vmin=0.,vmax=vmax) else: bovy_plot.bovy_dens2d(plotthis.T,cmap='gist_yarg',origin='lower', aspect=aspect,extent=extent, interpolation='nearest', overplot=True,vmin=0.,vmax=vmax) if not self.subgrid is None: self.subgrid.plot(tt=tt,vmax=vmax,aspect=aspect,extent=extent) def max(self,tt=0): if not self.subgrid is None: if len(self.df.shape) == 3: return nu.amax([nu.amax(self.df[:,:,tt]), self.subgrid.max(tt)]) else: return nu.amax([nu.amax(self.df[:,:]), self.subgrid.max()]) else: if len(self.df.shape) == 3: return nu.amax(self.df[:,:,tt]) else: return nu.amax(self.df[:,:]) def _vmomentsurfaceIntegrand(vR,vT,R,az,df,n,m,sigmaR1,sigmaT1,t,initvmoment): """Internal function that is the integrand for the velocity moment times surface mass integration""" o= Orbit([R,vR*sigmaR1,vT*sigmaT1,az]) return vR**n*vT**m*df(o,t)/initvmoment def _marginalizeVperpIntegrandSinAlphaLarge(vR,df,R,sinalpha,cotalpha, vlos,vcirc,sigma,phi): return df(Orbit([R,vR*sigma,cotalpha*vR*sigma+vlos/sinalpha+vcirc,phi])) def _marginalizeVperpIntegrandSinAlphaSmall(vT,df,R,cosalpha,tanalpha, vlos,vcirc,sigma,phi): return df(Orbit([R,tanalpha*vT*sigma-vlos/cosalpha,vT*sigma+vcirc,phi])) galpy-1.3.0/galpy/df_src/quasiisothermaldf.py0000644000076500000240000025561113231751432021416 0ustar bovystaff00000000000000#A 'Binney' quasi-isothermal DF import math import warnings import numpy from scipy import optimize, interpolate, integrate from galpy import potential from galpy import actionAngle from galpy.actionAngle import actionAngleIsochrone from galpy.potential import IsochronePotential from galpy.orbit import Orbit from galpy.df_src.df import df, _APY_LOADED from galpy.util import galpyWarning from galpy.util.bovy_conversion import physical_conversion, \ potential_physical_input, actionAngle_physical_input, _APY_UNITS if _APY_LOADED: from astropy import units _NSIGMA=4 _DEFAULTNGL=10 _DEFAULTNGL2=20 class quasiisothermaldf(df): """Class that represents a 'Binney' quasi-isothermal DF""" def __init__(self,hr,sr,sz,hsr,hsz,pot=None,aA=None, cutcounter=False, _precomputerg=True,_precomputergrmax=None, _precomputergnLz=51, refr=1.,lo=10./220./8., ro=None,vo=None): """ NAME: __init__ PURPOSE: Initialize a quasi-isothermal DF INPUT: hr - radial scale length (can be Quantity) sr - radial velocity dispersion at the solar radius (can be Quantity) sz - vertical velocity dispersion at the solar radius (can be Quantity) hsr - radial-velocity-dispersion scale length (can be Quantity) hsz - vertial-velocity-dispersion scale length (can be Quantity) pot= Potential instance or list thereof aA= actionAngle instance used to convert (x,v) to actions [must be an instance of an actionAngle class that computes (J,Omega,angle) for a given (x,v)] cutcounter= if True, set counter-rotating stars' DF to zero refr= reference radius for dispersions (can be different from ro) (can be Quantity) lo= reference angular momentum below where there are significant numbers of retrograde stars (can be Quantity) ro= distance from vantage point to GC (kpc; can be Quantity) vo= circular velocity at ro (km/s; can be Quantity) OTHER INPUTS: _precomputerg= if True (default), pre-compute the rL(L) _precomputergrmax= if set, this is the maximum R for which to pre-compute rg (default: 5*hr) _precomputergnLz if set, number of Lz to pre-compute rg for (default: 51) OUTPUT: object HISTORY: 2012-07-25 - Started - Bovy (IAS@MPIA) """ df.__init__(self,ro=ro,vo=vo) if _APY_LOADED and isinstance(hr,units.Quantity): hr= hr.to(units.kpc).value/self._ro if _APY_LOADED and isinstance(sr,units.Quantity): sr= sr.to(units.km/units.s).value/self._vo if _APY_LOADED and isinstance(sz,units.Quantity): sz= sz.to(units.km/units.s).value/self._vo if _APY_LOADED and isinstance(hsr,units.Quantity): hsr= hsr.to(units.kpc).value/self._ro if _APY_LOADED and isinstance(hsz,units.Quantity): hsz= hsz.to(units.kpc).value/self._ro if _APY_LOADED and isinstance(refr,units.Quantity): refr= refr.to(units.kpc).value/self._ro if _APY_LOADED and isinstance(lo,units.Quantity): lo= lo.to(units.kpc*units.km/units.s).value/self._ro/self._vo self._hr= hr self._sr= sr self._sz= sz self._hsr= hsr self._hsz= hsz self._refr= refr self._lo= lo self._lnsr= math.log(self._sr) self._lnsz= math.log(self._sz) if pot is None: raise IOError("pot= must be set") self._pot= pot if aA is None: raise IOError("aA= must be set") self._aA= aA if not self._aA._pot == self._pot: if not isinstance(self._aA,actionAngleIsochrone): raise IOError("Potential in aA does not appear to be the same as given potential pot") elif isinstance(self._pot,IsochronePotential) and \ not self._aA.b == self._pot.b and \ not self._aA.amp == self._pot._amp: raise IOError("Potential in aA does not appear to be the same as given potential pot") self._cutcounter= cutcounter if _precomputerg: if _precomputergrmax is None: _precomputergrmax= 5*self._hr self._precomputergrmax= _precomputergrmax self._precomputergnLz= _precomputergnLz self._precomputergLzmin= 0.01 self._precomputergLzmax= self._precomputergrmax\ *potential.vcirc(self._pot,self._precomputergrmax) self._precomputergLzgrid= numpy.linspace(self._precomputergLzmin,self._precomputergLzmax,self._precomputergnLz) self._rls= numpy.array([potential.rl(self._pot,l) for l in self._precomputergLzgrid]) #Spline interpolate self._rgInterp= interpolate.InterpolatedUnivariateSpline(self._precomputergLzgrid,self._rls,k=3) else: self._precomputergrmax= 0. self._rgInterp= None self._rls= None self._precomputergnr= None self._precomputergLzgrid= None self._precomputergLzmin= \ numpy.finfo(numpy.dtype(numpy.float64)).max self._precomputergLzmax= \ numpy.finfo(numpy.dtype(numpy.float64)).min self._precomputerg= _precomputerg self._glxdef, self._glwdef= \ numpy.polynomial.legendre.leggauss(_DEFAULTNGL) self._glxdef2, self._glwdef2= \ numpy.polynomial.legendre.leggauss(_DEFAULTNGL2) self._glxdef12, self._glwdef12= \ numpy.polynomial.legendre.leggauss(_DEFAULTNGL//2) return None @physical_conversion('phasespacedensity',pop=True) def __call__(self,*args,**kwargs): """ NAME: __call__ PURPOSE: return the DF INPUT: Either: a)(jr,lz,jz) tuple; each can be a Quantity where: jr - radial action lz - z-component of angular momentum jz - vertical action b) R,vR,vT,z,vz c) Orbit instance: initial condition used if that's it, orbit(t) if there is a time given as well log= if True, return the natural log +scipy.integrate.quadrature kwargs func= function of (jr,lz,jz) to multiply f with (useful for moments) OUTPUT: value of DF HISTORY: 2012-07-25 - Written - Bovy (IAS@MPIA) NOTE: For Miyamoto-Nagai/adiabatic approximation this seems to take about 30 ms / evaluation in the extended Solar neighborhood For a MWPotential/adiabatic approximation this takes about 50 ms / evaluation in the extended Solar neighborhood For adiabatic-approximation grid this seems to take about 0.67 to 0.75 ms / evaluation in the extended Solar neighborhood (includes some out of the grid) up to 200x faster when called with vector R,vR,vT,z,vz """ #First parse log log= kwargs.pop('log',False) _return_actions= kwargs.pop('_return_actions',False) _return_freqs= kwargs.pop('_return_freqs',False) _func= kwargs.pop('func',None) if 'rg' in kwargs: thisrg= kwargs.pop('rg') kappa= kwargs.pop('kappa') nu= kwargs.pop('nu') Omega= kwargs.pop('Omega') else: thisrg= None kappa= None nu= None Omega= None #First parse args if len(args) == 1 and not isinstance(args[0],Orbit): #(jr,lz,jz) jr,lz,jz= args[0] if _APY_LOADED and isinstance(jr,units.Quantity): jr= jr.to(units.kpc*units.km/units.s).value/self._ro/self._vo if _APY_LOADED and isinstance(lz,units.Quantity): lz= lz.to(units.kpc*units.km/units.s).value/self._ro/self._vo if _APY_LOADED and isinstance(jz,units.Quantity): jz= jz.to(units.kpc*units.km/units.s).value/self._ro/self._vo else: #Use self._aA to calculate the actions try: jr,lz,jz= self._aA(*args,use_physical=False,**kwargs) except actionAngle.UnboundError: if log: return -numpy.finfo(numpy.dtype(numpy.float64)).max else: return 0. #if isinstance(jr,(list,numpy.ndarray)) and len(jr) > 1: jr= jr[0] #if isinstance(jz,(list,numpy.ndarray)) and len(jz) > 1: jz= jz[0] if not isinstance(lz,numpy.ndarray) and self._cutcounter and lz < 0.: if log: return -numpy.finfo(numpy.dtype(numpy.float64)).max else: return 0. #First calculate rg if thisrg is None: thisrg= self._rg(lz) #Then calculate the epicycle and vertical frequencies kappa, nu= self._calc_epifreq(thisrg), self._calc_verticalfreq(thisrg) Omega= numpy.fabs(lz)/thisrg/thisrg #calculate surface-densities and sigmas lnsurfmass= (self._refr-thisrg)/self._hr lnsr= self._lnsr+(self._refr-thisrg)/self._hsr lnsz= self._lnsz+(self._refr-thisrg)/self._hsz #Calculate func if not _func is None: if log: funcTerm= numpy.log(_func(jr,lz,jz)) else: funcFactor= _func(jr,lz,jz) #Calculate fsr else: if log: funcTerm= 0. else: funcFactor= 1. if log: lnfsr= numpy.log(Omega)+lnsurfmass-2.*lnsr-math.log(math.pi)\ -numpy.log(kappa)\ +numpy.log(1.+numpy.tanh(lz/self._lo))\ -kappa*jr*numpy.exp(-2.*lnsr) lnfsz= numpy.log(nu)-math.log(2.*math.pi)\ -2.*lnsz-nu*jz*numpy.exp(-2.*lnsz) out= lnfsr+lnfsz+funcTerm if isinstance(lz,numpy.ndarray): out[numpy.isnan(out)]= -numpy.finfo(numpy.dtype(numpy.float64)).max if self._cutcounter: out[(lz < 0.)]= -numpy.finfo(numpy.dtype(numpy.float64)).max elif numpy.isnan(out): out= -numpy.finfo(numpy.dtype(numpy.float64)).max else: srm2= numpy.exp(-2.*lnsr) fsr= Omega*numpy.exp(lnsurfmass)*srm2/math.pi/kappa\ *(1.+numpy.tanh(lz/self._lo))\ *numpy.exp(-kappa*jr*srm2) szm2= numpy.exp(-2.*lnsz) fsz= nu/2./math.pi*szm2*numpy.exp(-nu*jz*szm2) out= fsr*fsz*funcFactor if isinstance(lz,numpy.ndarray): out[numpy.isnan(out)]= 0. if self._cutcounter: out[(lz < 0.)]= 0. elif numpy.isnan(out): out= 0. if _return_actions and _return_freqs: return (out,jr,lz,jz,thisrg,kappa,nu,Omega) elif _return_actions: return (out,jr,lz,jz) elif _return_freqs: return (out,thisrg,kappa,nu,Omega) else: return out @potential_physical_input @physical_conversion('position',pop=True) def estimate_hr(self,R,z=0.,dR=10.**-8.,**kwargs): """ NAME: estimate_hr PURPOSE: estimate the exponential scale length at R INPUT: R - Galactocentric radius (can be Quantity) z= height (default: 0 pc) (can be Quantity) dR- range in R to use (can be Quantity) density kwargs OUTPUT: estimated hR HISTORY: 2012-09-11 - Written - Bovy (IAS) 2013-01-28 - Re-written - Bovy """ Rs= [R-dR/2.,R+dR/2.] if z is None: sf= numpy.array([self.surfacemass_z(r,use_physical=False, **kwargs) for r in Rs]) else: sf= numpy.array([self.density(r,z,use_physical=False, **kwargs) for r in Rs]) lsf= numpy.log(sf) return -dR/(lsf[1]-lsf[0]) @potential_physical_input @physical_conversion('position',pop=True) def estimate_hz(self,R,z,dz=10.**-8.,**kwargs): """ NAME: estimate_hz PURPOSE: estimate the exponential scale height at R INPUT: R - Galactocentric radius (can be Quantity) dz - z range to use (can be Quantity) density kwargs OUTPUT: estimated hz HISTORY: 2012-08-30 - Written - Bovy (IAS) 2013-01-28 - Re-written - Bovy """ if z == 0.: zs= [z,z+dz] else: zs= [z-dz/2.,z+dz/2.] sf= numpy.array([self.density(R,zz,use_physical=False, **kwargs) for zz in zs]) lsf= numpy.log(sf) return -dz/(lsf[1]-lsf[0]) @potential_physical_input @physical_conversion('position',pop=True) def estimate_hsr(self,R,z=0.,dR=10.**-8.,**kwargs): """ NAME: estimate_hsr PURPOSE: estimate the exponential scale length of the radial dispersion at R INPUT: R - Galactocentric radius (can be Quantity) z= height (default: 0 pc) (can be Quantity) dR- range in R to use (can be Quantity) density kwargs OUTPUT: estimated hsR HISTORY: 2013-03-08 - Written - Bovy (IAS) """ Rs= [R-dR/2.,R+dR/2.] sf= numpy.array([self.sigmaR2(r,z,use_physical=False, **kwargs) for r in Rs]) lsf= numpy.log(sf)/2. return -dR/(lsf[1]-lsf[0]) @potential_physical_input @physical_conversion('position',pop=True) def estimate_hsz(self,R,z=0.,dR=10.**-8.,**kwargs): """ NAME: estimate_hsz PURPOSE: estimate the exponential scale length of the vertical dispersion at R INPUT: R - Galactocentric radius (can be Quantity) z= height (default: 0 pc) (can be Quantity) dR- range in R to use (can be Quantity) density kwargs OUTPUT: estimated hsz HISTORY: 2013-03-08 - Written - Bovy (IAS) """ Rs= [R-dR/2.,R+dR/2.] sf= numpy.array([self.sigmaz2(r,z,use_physical=False, **kwargs) for r in Rs]) lsf= numpy.log(sf)/2. return -dR/(lsf[1]-lsf[0]) @potential_physical_input @physical_conversion('numbersurfacedensity',pop=True) def surfacemass_z(self,R,nz=7,zmax=1.,fixed_quad=True,fixed_order=8, **kwargs): """ NAME: surfacemass_z PURPOSE: calculate the vertically-integrated surface density INPUT: R - Galactocentric radius (can be Quantity) fixed_quad= if True (default), use Gauss-Legendre integration fixed_order= (20), order of GL integration to use nz= number of zs to use to estimate zmax= maximum z to use (can be Quantity) density kwargs OUTPUT: \Sigma(R) HISTORY: 2012-08-30 - Written - Bovy (IAS) """ if fixed_quad: return 2.*integrate.fixed_quad(lambda x: self.density(R*numpy.ones(fixed_order),x,use_physical=False), 0.,.5,n=fixed_order)[0] zs= numpy.linspace(0.,zmax,nz) sf= numpy.array([self.density(R,z,use_physical=False, **kwargs) for z in zs]) lsf= numpy.log(sf) #Interpolate lsfInterp= interpolate.UnivariateSpline(zs, lsf, k=3) #Integrate return 2.*integrate.quad((lambda x: numpy.exp(lsfInterp(x))), 0.,1.)[0] def vmomentdensity(self,*args,**kwargs): """ NAME: vmomentdensity PURPOSE: calculate the an arbitrary moment of the velocity distribution at R times the density INPUT: R - radius at which to calculate the moment(/ro) n - vR^n m - vT^m o - vz^o OPTIONAL INPUT: nsigma - number of sigma to integrate the vR and vz velocities over (when doing explicit numerical integral; default: 4) vTmax - upper limit for integration over vT (default: 1.5) mc= if True, calculate using Monte Carlo integration nmc= if mc, use nmc samples gl= use Gauss-Legendre _returngl= if True, return the evaluated DF _return_actions= if True, return the evaluated actions (does not work with _returngl currently) _return_freqs= if True, return the evaluated frequencies and rg (does not work with _returngl currently) OUTPUT: at R,z (no support for units) HISTORY: 2012-08-06 - Written - Bovy (IAS@MPIA) """ use_physical= kwargs.pop('use_physical',True) ro= kwargs.pop('ro',None) if ro is None and hasattr(self,'_roSet') and self._roSet: ro= self._ro if _APY_LOADED and isinstance(ro,units.Quantity): ro= ro.to(units.kpc).value vo= kwargs.pop('vo',None) if vo is None and hasattr(self,'_voSet') and self._voSet: vo= self._vo if _APY_LOADED and isinstance(vo,units.Quantity): vo= vo.to(units.km/units.s).value if use_physical and not vo is None and not ro is None: fac= vo**(args[2]+args[3]+args[4])/ro**3 if _APY_UNITS: u= 1/units.kpc**3*(units.km/units.s)**(args[2]+args[3]+args[4]) out= self._vmomentdensity(*args,**kwargs) if _APY_UNITS: return units.Quantity(out*fac,unit=u) else: return out*fac else: return self._vmomentdensity(*args,**kwargs) def _vmomentdensity(self,R,z,n,m,o,nsigma=None,mc=False,nmc=10000, _returnmc=False,_vrs=None,_vts=None,_vzs=None, _rawgausssamples=False, gl=False,ngl=_DEFAULTNGL,_returngl=False,_glqeval=None, _return_actions=False,_jr=None,_lz=None,_jz=None, _return_freqs=False, _rg=None,_kappa=None,_nu=None,_Omega=None, _sigmaR1=None,_sigmaz1=None, **kwargs): """Non-physical version of vmomentdensity, otherwise the same""" if isinstance(R,numpy.ndarray): return numpy.array([self._vmomentdensity(r,zz,n,m,o,nsigma=nsigma, mc=mc,nmc=nmc, gl=gl,ngl=ngl,**kwargs) for r,zz in zip(R,z)]) if isinstance(self._aA,(actionAngle.actionAngleAdiabatic, actionAngle.actionAngleAdiabaticGrid)): if n % 2 == 1. or o % 2 == 1.: return 0. #we know this must be the case if nsigma == None: nsigma= _NSIGMA if _sigmaR1 is None: sigmaR1= self._sr*numpy.exp((self._refr-R)/self._hsr) else: sigmaR1= _sigmaR1 if _sigmaz1 is None: sigmaz1= self._sz*numpy.exp((self._refr-R)/self._hsz) else: sigmaz1= _sigmaz1 thisvc= potential.vcirc(self._pot,R,use_physical=False) #Use the asymmetric drift equation to estimate va gamma= numpy.sqrt(0.5) va= sigmaR1**2./2./thisvc\ *(gamma**2.-1. #Assume close to flat rotation curve, sigphi2/sigR2 =~ 0.5 +R*(1./self._hr+2./self._hsr)) if math.fabs(va) > sigmaR1: va = 0.#To avoid craziness near the center if gl: if ngl % 2 == 1: raise ValueError("ngl must be even") if not _glqeval is None and ngl != _glqeval.shape[0]: _glqeval= None #Use Gauss-Legendre integration for all if ngl == _DEFAULTNGL: glx, glw= self._glxdef, self._glwdef glx12, glw12= self._glxdef12, self._glwdef12 elif ngl == _DEFAULTNGL2: glx, glw= self._glxdef2, self._glwdef2 glx12, glw12= self._glxdef, self._glwdef else: glx, glw= numpy.polynomial.legendre.leggauss(ngl) glx12, glw12= numpy.polynomial.legendre.leggauss(ngl//2) #Evaluate everywhere if isinstance(self._aA,(actionAngle.actionAngleAdiabatic, actionAngle.actionAngleAdiabaticGrid)): vRgl= nsigma*sigmaR1/2.*(glx+1.) vzgl= nsigma*sigmaz1/2.*(glx+1.) vRglw= glw vzglw= glw else: vRgl= nsigma*sigmaR1/2.*(glx12+1.) #vRgl= 1.5/2.*(glx12+1.) vRgl= list(vRgl) vRgl.extend(-nsigma*sigmaR1/2.*(glx12+1.)) #vRgl.extend(-1.5/2.*(glx12+1.)) vRgl= numpy.array(vRgl) vzgl= nsigma*sigmaz1/2.*(glx12+1.) #vzgl= 1.5/2.*(glx12+1.) vzgl= list(vzgl) vzgl.extend(-nsigma*sigmaz1/2.*(glx12+1.)) #vzgl.extend(-1.5/2.*(glx12+1.)) vzgl= numpy.array(vzgl) vRglw= glw12 vRglw= list(vRglw) vRglw.extend(glw12) vRglw= numpy.array(vRglw) vzglw= glw12 vzglw= list(vzglw) vzglw.extend(glw12) vzglw= numpy.array(vzglw) if 'vTmax' in kwargs: vTmax = kwargs['vTmax'] else: vTmax = 1.5 vTgl= vTmax/2.*(glx+1.) #Tile everything vTgl= numpy.tile(vTgl,(ngl,ngl,1)).T vRgl= numpy.tile(numpy.reshape(vRgl,(1,ngl)).T,(ngl,1,ngl)) vzgl= numpy.tile(vzgl,(ngl,ngl,1)) vTglw= numpy.tile(glw,(ngl,ngl,1)).T #also tile weights vRglw= numpy.tile(numpy.reshape(vRglw,(1,ngl)).T,(ngl,1,ngl)) vzglw= numpy.tile(vzglw,(ngl,ngl,1)) #evaluate if _glqeval is None and _jr is None: logqeval, jr, lz, jz, rg, kappa, nu, Omega= self(R+numpy.zeros(ngl*ngl*ngl), vRgl.flatten(), vTgl.flatten(), z+numpy.zeros(ngl*ngl*ngl), vzgl.flatten(), log=True, _return_actions=True, _return_freqs=True, use_physical=False) logqeval= numpy.reshape(logqeval,(ngl,ngl,ngl)) elif not _jr is None and _rg is None: logqeval, jr, lz, jz, rg, kappa, nu, Omega= self((_jr,_lz,_jz), log=True, _return_actions=True, _return_freqs=True, use_physical=False) logqeval= numpy.reshape(logqeval,(ngl,ngl,ngl)) elif not _jr is None and not _rg is None: logqeval, jr, lz, jz, rg, kappa, nu, Omega= self((_jr,_lz,_jz), rg=_rg,kappa=_kappa,nu=_nu, Omega=_Omega, log=True, _return_actions=True, _return_freqs=True, use_physical=False) logqeval= numpy.reshape(logqeval,(ngl,ngl,ngl)) else: logqeval= _glqeval if _returngl: return (numpy.sum(numpy.exp(logqeval)*vRgl**n*vTgl**m*vzgl**o *vTglw*vRglw*vzglw)*sigmaR1*sigmaz1*0.125*vTmax*nsigma**2, logqeval) elif _return_actions and _return_freqs: return (numpy.sum(numpy.exp(logqeval)*vRgl**n*vTgl**m*vzgl**o *vTglw*vRglw*vzglw)*sigmaR1*sigmaz1*0.125*vTmax*nsigma**2, jr,lz,jz, rg,kappa,nu,Omega) elif _return_actions: return (numpy.sum(numpy.exp(logqeval)*vRgl**n*vTgl**m*vzgl**o *vTglw*vRglw*vzglw)*sigmaR1*sigmaz1*0.125*vTmax*nsigma**2, jr,lz,jz) else: return numpy.sum(numpy.exp(logqeval)*vRgl**n*vTgl**m*vzgl**o *vTglw*vRglw*vzglw*sigmaR1*sigmaz1*0.125*vTmax*nsigma**2) elif mc: mvT= (thisvc-va)/gamma/sigmaR1 if _vrs is None: vrs= numpy.random.normal(size=nmc) else: vrs= _vrs if _vts is None: vts= numpy.random.normal(size=nmc)+mvT else: if _rawgausssamples: vts= _vts+mvT else: vts= _vts if _vzs is None: vzs= numpy.random.normal(size=nmc) else: vzs= _vzs Is= _vmomentsurfaceMCIntegrand(vzs,vrs,vts,numpy.ones(nmc)*R, numpy.ones(nmc)*z, self,sigmaR1,gamma,sigmaz1,mvT, n,m,o) if _returnmc: if _rawgausssamples: return (numpy.mean(Is)*sigmaR1**(2.+n+m)*gamma**(1.+m)*sigmaz1**(1.+o), vrs,vts-mvT,vzs) else: return (numpy.mean(Is)*sigmaR1**(2.+n+m)*gamma**(1.+m)*sigmaz1**(1.+o), vrs,vts,vzs) else: return numpy.mean(Is)*sigmaR1**(2.+n+m)*gamma**(1.+m)*sigmaz1**(1.+o) else: #pragma: no cover because this is too slow; a warning is shown warnings.warn("Calculations using direct numerical integration using tplquad is not recommended and extremely slow; it has also not been carefully tested",galpyWarning) return integrate.tplquad(_vmomentsurfaceIntegrand, 1./gamma*(thisvc-va)/sigmaR1-nsigma, 1./gamma*(thisvc-va)/sigmaR1+nsigma, lambda x: 0., lambda x: nsigma, lambda x,y: 0., lambda x,y: nsigma, (R,z,self,sigmaR1,gamma,sigmaz1,n,m,o), **kwargs)[0]*sigmaR1**(2.+n+m)*gamma**(1.+m)*sigmaz1**(1.+o) def jmomentdensity(self,*args,**kwargs): """ NAME: jmomentdensity PURPOSE: calculate the an arbitrary moment of an action of the velocity distribution at R times the surfacmass INPUT: R - radius at which to calculate the moment(/ro) n - jr^n m - lz^m o - jz^o OPTIONAL INPUT: nsigma - number of sigma to integrate the velocities over (when doing explicit numerical integral) mc= if True, calculate using Monte Carlo integration nmc= if mc, use nmc samples OUTPUT: at R (no support for units) HISTORY: 2012-08-09 - Written - Bovy (IAS@MPIA) """ use_physical= kwargs.pop('use_physical',True) ro= kwargs.pop('ro',None) if ro is None and hasattr(self,'_roSet') and self._roSet: ro= self._ro if _APY_LOADED and isinstance(ro,units.Quantity): ro= ro.to(units.kpc).value vo= kwargs.pop('vo',None) if vo is None and hasattr(self,'_voSet') and self._voSet: vo= self._vo if _APY_LOADED and isinstance(vo,units.Quantity): vo= vo.to(units.km/units.s).value if use_physical and not vo is None and not ro is None: fac= (ro*vo)**(args[2]+args[3]+args[4])/ro**3 if _APY_UNITS: u= 1/units.kpc**3*(units.kpc*units.km/units.s)**(args[2]+args[3]+args[4]) out= self._jmomentdensity(*args,**kwargs) if _APY_UNITS: return units.Quantity(out*fac,unit=u) else: return out*fac else: return self._jmomentdensity(*args,**kwargs) def _jmomentdensity(self,R,z,n,m,o,nsigma=None,mc=True,nmc=10000, _returnmc=False,_vrs=None,_vts=None,_vzs=None, **kwargs): """Non-physical version of jmomentdensity, otherwise the same""" if nsigma == None: nsigma= _NSIGMA sigmaR1= self._sr*numpy.exp((self._refr-R)/self._hsr) sigmaz1= self._sz*numpy.exp((self._refr-R)/self._hsz) thisvc= potential.vcirc(self._pot,R,use_physical=False) #Use the asymmetric drift equation to estimate va gamma= numpy.sqrt(0.5) va= sigmaR1**2./2./thisvc\ *(gamma**2.-1. #Assume close to flat rotation curve, sigphi2/sigR2 =~ 0.5 +R*(1./self._hr+2./self._hsr)) if math.fabs(va) > sigmaR1: va = 0.#To avoid craziness near the center if mc: mvT= (thisvc-va)/gamma/sigmaR1 if _vrs is None: vrs= numpy.random.normal(size=nmc) else: vrs= _vrs if _vts is None: vts= numpy.random.normal(size=nmc)+mvT else: vts= _vts if _vzs is None: vzs= numpy.random.normal(size=nmc) else: vzs= _vzs Is= _jmomentsurfaceMCIntegrand(vzs,vrs,vts,numpy.ones(nmc)*R,numpy.ones(nmc)*z,self,sigmaR1,gamma,sigmaz1,mvT,n,m,o) if _returnmc: return (numpy.mean(Is)*sigmaR1**2.*gamma*sigmaz1, vrs,vts,vzs) else: return numpy.mean(Is)*sigmaR1**2.*gamma*sigmaz1 else: #pragma: no cover because this is too slow; a warning is shown warnings.warn("Calculations using direct numerical integration using tplquad is not recommended and extremely slow; it has also not been carefully tested",galpyWarning) return integrate.tplquad(_jmomentsurfaceIntegrand, 1./gamma*(thisvc-va)/sigmaR1-nsigma, 1./gamma*(thisvc-va)/sigmaR1+nsigma, lambda x: 0., lambda x: nsigma, lambda x,y: 0., lambda x,y: nsigma, (R,z,self,sigmaR1,gamma,sigmaz1,n,m,o), **kwargs)[0]*sigmaR1**2.*gamma*sigmaz1 @potential_physical_input @physical_conversion('numberdensity',pop=True) def density(self,R,z,nsigma=None,mc=False,nmc=10000, gl=True,ngl=_DEFAULTNGL,**kwargs): """ NAME: density PURPOSE: calculate the density at R,z by marginalizing over velocity INPUT: R - radius at which to calculate the density (can be Quantity) z - height at which to calculate the density (can be Quantity) OPTIONAL INPUT: nsigma - number of sigma to integrate the velocities over scipy.integrate.tplquad kwargs epsabs and epsrel mc= if True, calculate using Monte Carlo integration nmc= if mc, use nmc samples gl= if True, calculate using Gauss-Legendre integration ngl= if gl, use ngl-th order Gauss-Legendre integration for each dimension OUTPUT: density at (R,z) HISTORY: 2012-07-26 - Written - Bovy (IAS@MPIA) """ return self._vmomentdensity(R,z,0.,0.,0., nsigma=nsigma,mc=mc,nmc=nmc, gl=gl,ngl=ngl, **kwargs) @potential_physical_input @physical_conversion('velocity2',pop=True) def sigmaR2(self,R,z,nsigma=None,mc=False,nmc=10000, gl=True,ngl=_DEFAULTNGL,**kwargs): """ NAME: sigmaR2 PURPOSE: calculate sigma_R^2 by marginalizing over velocity INPUT: R - radius at which to calculate this (can be Quantity) z - height at which to calculate this (can be Quantity) OPTIONAL INPUT: nsigma - number of sigma to integrate the velocities over scipy.integrate.tplquad kwargs epsabs and epsrel mc= if True, calculate using Monte Carlo integration nmc= if mc, use nmc samples gl= if True, calculate using Gauss-Legendre integration ngl= if gl, use ngl-th order Gauss-Legendre integration for each dimension OUTPUT: sigma_R^2 HISTORY: 2012-07-30 - Written - Bovy (IAS@MPIA) """ if mc: surfmass, vrs, vts, vzs= self._vmomentdensity(R,z,0.,0.,0., nsigma=nsigma,mc=mc,nmc=nmc,_returnmc=True, **kwargs) return self._vmomentdensity(R,z,2.,0.,0., nsigma=nsigma,mc=mc,nmc=nmc,_returnmc=False, _vrs=vrs,_vts=vts,_vzs=vzs, **kwargs)/surfmass elif gl: surfmass, glqeval= self._vmomentdensity(R,z,0.,0.,0., gl=gl,ngl=ngl, _returngl=True, **kwargs) return self._vmomentdensity(R,z,2.,0.,0., ngl=ngl,gl=gl, _glqeval=glqeval, **kwargs)/surfmass else: #pragma: no cover because this is too slow; a warning is shown return (self._vmomentdensity(R,z,2.,0.,0., nsigma=nsigma,mc=mc,nmc=nmc, **kwargs)/ self._vmomentdensity(R,z,0.,0.,0., nsigma=nsigma,mc=mc,nmc=nmc, **kwargs)) @potential_physical_input @physical_conversion('velocity2',pop=True) def sigmaRz(self,R,z,nsigma=None,mc=False,nmc=10000, gl=True,ngl=_DEFAULTNGL,**kwargs): """ NAME: sigmaRz PURPOSE: calculate sigma_RZ^2 by marginalizing over velocity INPUT: R - radius at which to calculate this (can be Quantity) z - height at which to calculate this (can be Quantity) OPTIONAL INPUT: nsigma - number of sigma to integrate the velocities over scipy.integrate.tplquad kwargs epsabs and epsrel mc= if True, calculate using Monte Carlo integration nmc= if mc, use nmc samples gl= if True, calculate using Gauss-Legendre integration ngl= if gl, use ngl-th order Gauss-Legendre integration for each dimension OUTPUT: sigma_Rz^2 HISTORY: 2012-07-30 - Written - Bovy (IAS@MPIA) """ if mc: surfmass, vrs, vts, vzs= self._vmomentdensity(R,z,0.,0.,0., nsigma=nsigma,mc=mc,nmc=nmc,_returnmc=True, **kwargs) return self._vmomentdensity(R,z,1.,0.,1., nsigma=nsigma,mc=mc,nmc=nmc,_returnmc=False, _vrs=vrs,_vts=vts,_vzs=vzs, **kwargs)/surfmass elif gl: surfmass, glqeval= self._vmomentdensity(R,z,0.,0.,0., gl=gl,ngl=ngl, _returngl=True, **kwargs) return self._vmomentdensity(R,z,1.,0.,1., ngl=ngl,gl=gl, _glqeval=glqeval, **kwargs)/surfmass else: #pragma: no cover because this is too slow; a warning is shown return (self._vmomentdensity(R,z,1.,0.,1., nsigma=nsigma,mc=mc,nmc=nmc, **kwargs)/ self._vmomentdensity(R,z,0.,0.,0., nsigma=nsigma,mc=mc,nmc=nmc, **kwargs)) @potential_physical_input @physical_conversion('angle',pop=True) def tilt(self,R,z,nsigma=None,mc=False,nmc=10000, gl=True,ngl=_DEFAULTNGL,**kwargs): """ NAME: tilt PURPOSE: calculate the tilt of the velocity ellipsoid by marginalizing over velocity INPUT: R - radius at which to calculate this (can be Quantity) z - height at which to calculate this (can be Quantity) OPTIONAL INPUT: nsigma - number of sigma to integrate the velocities over scipy.integrate.tplquad kwargs epsabs and epsrel mc= if True, calculate using Monte Carlo integration nmc= if mc, use nmc samples gl= if True, calculate using Gauss-Legendre integration ngl= if gl, use ngl-th order Gauss-Legendre integration for each dimension OUTPUT: tilt in rad HISTORY: 2012-12-23 - Written - Bovy (IAS) 2017-10-28 - Changed return unit to rad - Bovy (UofT) """ warnings.warn("In versions >1.3, the output unit of quasiisothermaldf.tilt has been changed to radian (from degree before)",galpyWarning) if mc: surfmass, vrs, vts, vzs= self._vmomentdensity(R,z,0.,0.,0., nsigma=nsigma,mc=mc,nmc=nmc,_returnmc=True, **kwargs) tsigmar2= self._vmomentdensity(R,z,2.,0.,0., nsigma=nsigma,mc=mc,nmc=nmc,_returnmc=False, _vrs=vrs,_vts=vts,_vzs=vzs, **kwargs)/surfmass tsigmaz2= self._vmomentdensity(R,z,0.,0.,2., nsigma=nsigma,mc=mc,nmc=nmc,_returnmc=False, _vrs=vrs,_vts=vts,_vzs=vzs, **kwargs)/surfmass tsigmarz= self._vmomentdensity(R,z,1.,0.,1., nsigma=nsigma,mc=mc,nmc=nmc,_returnmc=False, _vrs=vrs,_vts=vts,_vzs=vzs, **kwargs)/surfmass return 0.5*numpy.arctan(2.*tsigmarz/(tsigmar2-tsigmaz2)) elif gl: surfmass, glqeval= self._vmomentdensity(R,z,0.,0.,0., gl=gl,ngl=ngl, _returngl=True, **kwargs) tsigmar2= self._vmomentdensity(R,z,2.,0.,0., ngl=ngl,gl=gl, _glqeval=glqeval, **kwargs)/surfmass tsigmaz2= self._vmomentdensity(R,z,0.,0.,2., ngl=ngl,gl=gl, _glqeval=glqeval, **kwargs)/surfmass tsigmarz= self._vmomentdensity(R,z,1.,0.,1., ngl=ngl,gl=gl, _glqeval=glqeval, **kwargs)/surfmass return 0.5*numpy.arctan(2.*tsigmarz/(tsigmar2-tsigmaz2)) else: raise NotImplementedError("Use either mc=True or gl=True") @potential_physical_input @physical_conversion('velocity2',pop=True) def sigmaz2(self,R,z,nsigma=None,mc=False,nmc=10000, gl=True,ngl=_DEFAULTNGL,**kwargs): """ NAME: sigmaz2 PURPOSE: calculate sigma_z^2 by marginalizing over velocity INPUT: R - radius at which to calculate this (can be Quantity) z - height at which to calculate this (can be Quantity) OPTIONAL INPUT: nsigma - number of sigma to integrate the velocities over scipy.integrate.tplquad kwargs epsabs and epsrel mc= if True, calculate using Monte Carlo integration nmc= if mc, use nmc samples gl= if True, calculate using Gauss-Legendre integration ngl= if gl, use ngl-th order Gauss-Legendre integration for each dimension OUTPUT: sigma_z^2 HISTORY: 2012-07-30 - Written - Bovy (IAS@MPIA) """ if mc: surfmass, vrs, vts, vzs= self._vmomentdensity(R,z,0.,0.,0., nsigma=nsigma,mc=mc,nmc=nmc,_returnmc=True, **kwargs) return self._vmomentdensity(R,z,0.,0.,2., nsigma=nsigma,mc=mc,nmc=nmc,_returnmc=False, _vrs=vrs,_vts=vts,_vzs=vzs, **kwargs)/surfmass elif gl: surfmass, glqeval= self._vmomentdensity(R,z,0.,0.,0., gl=gl,ngl=ngl, _returngl=True, **kwargs) return self._vmomentdensity(R,z,0.,0.,2., ngl=ngl,gl=gl, _glqeval=glqeval, **kwargs)/surfmass else: #pragma: no cover because this is too slow; a warning is shown return (self._vmomentdensity(R,z,0.,0.,2., nsigma=nsigma,mc=mc,nmc=nmc, **kwargs)/ self._vmomentdensity(R,z,0.,0.,0., nsigma=nsigma,mc=mc,nmc=nmc, **kwargs)) @potential_physical_input @physical_conversion('velocity',pop=True) def meanvT(self,R,z,nsigma=None,mc=False,nmc=10000, gl=True,ngl=_DEFAULTNGL,**kwargs): """ NAME: meanvT PURPOSE: calculate the mean rotational velocity by marginalizing over velocity INPUT: R - radius at which to calculate this (can be Quantity) z - height at which to calculate this (can be Quantity) OPTIONAL INPUT: nsigma - number of sigma to integrate the velocities over scipy.integrate.tplquad kwargs epsabs and epsrel mc= if True, calculate using Monte Carlo integration nmc= if mc, use nmc samples gl= if True, calculate using Gauss-Legendre integration ngl= if gl, use ngl-th order Gauss-Legendre integration for each dimension OUTPUT: meanvT HISTORY: 2012-07-30 - Written - Bovy (IAS@MPIA) """ if mc: surfmass, vrs, vts, vzs= self._vmomentdensity(R,z,0.,0.,0., nsigma=nsigma,mc=mc,nmc=nmc,_returnmc=True, **kwargs) return self._vmomentdensity(R,z,0.,1.,0., nsigma=nsigma,mc=mc,nmc=nmc,_returnmc=False, _vrs=vrs,_vts=vts,_vzs=vzs, **kwargs)/surfmass elif gl: surfmass, glqeval= self._vmomentdensity(R,z,0.,0.,0., gl=gl,ngl=ngl, _returngl=True, **kwargs) return self._vmomentdensity(R,z,0.,1.,0., ngl=ngl,gl=gl, _glqeval=glqeval, **kwargs)/surfmass else: #pragma: no cover because this is too slow; a warning is shown return (self._vmomentdensity(R,z,0.,1.,0., nsigma=nsigma,mc=mc,nmc=nmc, **kwargs)/ self._vmomentdensity(R,z,0.,0.,0., nsigma=nsigma,mc=mc,nmc=nmc, **kwargs)) @potential_physical_input @physical_conversion('velocity',pop=True) def meanvR(self,R,z,nsigma=None,mc=False,nmc=10000, gl=True,ngl=_DEFAULTNGL,**kwargs): """ NAME: meanvR PURPOSE: calculate the mean radial velocity by marginalizing over velocity INPUT: R - radius at which to calculate this (can be Quantity) z - height at which to calculate this (can be Quantity) OPTIONAL INPUT: nsigma - number of sigma to integrate the velocities over scipy.integrate.tplquad kwargs epsabs and epsrel mc= if True, calculate using Monte Carlo integration nmc= if mc, use nmc samples gl= if True, calculate using Gauss-Legendre integration ngl= if gl, use ngl-th order Gauss-Legendre integration for each dimension OUTPUT: meanvR HISTORY: 2012-12-23 - Written - Bovy (IAS) """ if mc: surfmass, vrs, vts, vzs= self._vmomentdensity(R,z,0.,0.,0., nsigma=nsigma,mc=mc,nmc=nmc,_returnmc=True, **kwargs) return self._vmomentdensity(R,z,1.,0.,0., nsigma=nsigma,mc=mc,nmc=nmc,_returnmc=False, _vrs=vrs,_vts=vts,_vzs=vzs, **kwargs)/surfmass elif gl: surfmass, glqeval= self._vmomentdensity(R,z,0.,0.,0., gl=gl,ngl=ngl, _returngl=True, **kwargs) return self._vmomentdensity(R,z,1.,0.,0., ngl=ngl,gl=gl, _glqeval=glqeval, **kwargs)/surfmass else: #pragma: no cover because this is too slow; a warning is shown return (self._vmomentdensity(R,z,1.,0.,0., nsigma=nsigma,mc=mc,nmc=nmc, **kwargs)/ self._vmomentdensity(R,z,0.,0.,0., nsigma=nsigma,mc=mc,nmc=nmc, **kwargs)) @potential_physical_input @physical_conversion('velocity',pop=True) def meanvz(self,R,z,nsigma=None,mc=False,nmc=10000, gl=True,ngl=_DEFAULTNGL,**kwargs): """ NAME: meanvz PURPOSE: calculate the mean vertical velocity by marginalizing over velocity INPUT: R - radius at which to calculate this (can be Quantity) z - height at which to calculate this (can be Quantity) OPTIONAL INPUT: nsigma - number of sigma to integrate the velocities over scipy.integrate.tplquad kwargs epsabs and epsrel mc= if True, calculate using Monte Carlo integration nmc= if mc, use nmc samples gl= if True, calculate using Gauss-Legendre integration ngl= if gl, use ngl-th order Gauss-Legendre integration for each dimension OUTPUT: meanvz HISTORY: 2012-12-23 - Written - Bovy (IAS) """ if mc: surfmass, vrs, vts, vzs= self._vmomentdensity(R,z,0.,0.,0., nsigma=nsigma,mc=mc,nmc=nmc,_returnmc=True, **kwargs) return self._vmomentdensity(R,z,0.,0.,1., nsigma=nsigma,mc=mc,nmc=nmc,_returnmc=False, _vrs=vrs,_vts=vts,_vzs=vzs, **kwargs)/surfmass elif gl: surfmass, glqeval= self._vmomentdensity(R,z,0.,0.,0., gl=gl,ngl=ngl, _returngl=True, **kwargs) return self._vmomentdensity(R,z,0.,0.,1., ngl=ngl,gl=gl, _glqeval=glqeval, **kwargs)/surfmass else: #pragma: no cover because this is too slow; a warning is shown return (self._vmomentdensity(R,z,0.,0.,1., nsigma=nsigma,mc=mc,nmc=nmc, **kwargs)/ self._vmomentdensity(R,z,0.,0.,0., nsigma=nsigma,mc=mc,nmc=nmc, **kwargs)) @potential_physical_input @physical_conversion('velocity2',pop=True) def sigmaT2(self,R,z,nsigma=None,mc=False,nmc=10000, gl=True,ngl=_DEFAULTNGL,**kwargs): """ NAME: sigmaT2 PURPOSE: calculate sigma_T^2 by marginalizing over velocity INPUT: R - radius at which to calculate this (can be Quantity) z - height at which to calculate this (can be Quantity) OPTIONAL INPUT: nsigma - number of sigma to integrate the velocities over scipy.integrate.tplquad kwargs epsabs and epsrel mc= if True, calculate using Monte Carlo integration nmc= if mc, use nmc samples gl= if True, calculate using Gauss-Legendre integration ngl= if gl, use ngl-th order Gauss-Legendre integration for each dimension OUTPUT: sigma_T^2 HISTORY: 2012-07-30 - Written - Bovy (IAS@MPIA) """ if mc: surfmass, vrs, vts, vzs= self._vmomentdensity(R,z,0.,0.,0., nsigma=nsigma,mc=mc,nmc=nmc,_returnmc=True, **kwargs) mvt= self._vmomentdensity(R,z,0.,1.,0., nsigma=nsigma,mc=mc,nmc=nmc,_returnmc=False, _vrs=vrs,_vts=vts,_vzs=vzs, **kwargs)/surfmass return self._vmomentdensity(R,z,0.,2.,0., nsigma=nsigma,mc=mc,nmc=nmc,_returnmc=False, _vrs=vrs,_vts=vts,_vzs=vzs, **kwargs)/surfmass\ -mvt**2. elif gl: surfmass, glqeval= self._vmomentdensity(R,z,0.,0.,0., gl=gl,ngl=ngl, _returngl=True, **kwargs) mvt= self._vmomentdensity(R,z,0.,1.,0., ngl=ngl,gl=gl, _glqeval=glqeval, **kwargs)/surfmass return self._vmomentdensity(R,z,0.,2.,0., ngl=ngl,gl=gl, _glqeval=glqeval, **kwargs)/surfmass-mvt**2. else: #pragma: no cover because this is too slow; a warning is shown surfmass= self._vmomentdensity(R,z,0.,0.,0., nsigma=nsigma,mc=mc,nmc=nmc, **kwargs) return (self._vmomentdensity(R,z,0.,2.,0., nsigma=nsigma,mc=mc,nmc=nmc, **kwargs)/surfmass\ -(self._vmomentdensity(R,z,0.,2.,0., nsigma=nsigma,mc=mc,nmc=nmc, **kwargs)/surfmass)**2.) @potential_physical_input @physical_conversion('action',pop=True) def meanjr(self,R,z,nsigma=None,mc=True,nmc=10000,**kwargs): """ NAME: meanjr PURPOSE: calculate the mean radial action by marginalizing over velocity INPUT: R - radius at which to calculate this (can be Quantity) z - height at which to calculate this (can be Quantity) OPTIONAL INPUT: nsigma - number of sigma to integrate the velocities over scipy.integrate.tplquad kwargs epsabs and epsrel mc= if True, calculate using Monte Carlo integration nmc= if mc, use nmc samples OUTPUT: meanjr HISTORY: 2012-08-09 - Written - Bovy (IAS@MPIA) """ if mc: surfmass, vrs, vts, vzs= self._vmomentdensity(R,z,0.,0.,0., nsigma=nsigma,mc=mc,nmc=nmc,_returnmc=True, **kwargs) return self._jmomentdensity(R,z,1.,0.,0., nsigma=nsigma,mc=mc,nmc=nmc,_returnmc=False, _vrs=vrs,_vts=vts,_vzs=vzs, **kwargs)/surfmass else: #pragma: no cover because this is too slow; a warning is shown return (self._jmomentdensity(R,z,1.,0.,0., nsigma=nsigma,mc=mc,nmc=nmc, **kwargs)/ self._vmomentdensity(R,z,0.,0.,0., nsigma=nsigma,mc=mc,nmc=nmc, **kwargs)) @potential_physical_input @physical_conversion('action',pop=True) def meanlz(self,R,z,nsigma=None,mc=True,nmc=10000,**kwargs): """ NAME: meanlz PURPOSE: calculate the mean angular momemtum by marginalizing over velocity INPUT: R - radius at which to calculate this (can be Quantity) z - height at which to calculate this (can be Quantity) OPTIONAL INPUT: nsigma - number of sigma to integrate the velocities over scipy.integrate.tplquad kwargs epsabs and epsrel mc= if True, calculate using Monte Carlo integration nmc= if mc, use nmc samples OUTPUT: meanlz HISTORY: 2012-08-09 - Written - Bovy (IAS@MPIA) """ if mc: surfmass, vrs, vts, vzs= self._vmomentdensity(R,z,0.,0.,0., nsigma=nsigma,mc=mc,nmc=nmc,_returnmc=True, **kwargs) return self._jmomentdensity(R,z,0.,1.,0., nsigma=nsigma,mc=mc,nmc=nmc,_returnmc=False, _vrs=vrs,_vts=vts,_vzs=vzs, **kwargs)/surfmass else: #pragma: no cover because this is too slow; a warning is shown return (self._jmomentdensity(R,z,0.,1.,0., nsigma=nsigma,mc=mc,nmc=nmc, **kwargs)/ self._vmomentdensity(R,z,0.,0.,0., nsigma=nsigma,mc=mc,nmc=nmc, **kwargs)) @potential_physical_input @physical_conversion('action',pop=True) def meanjz(self,R,z,nsigma=None,mc=True,nmc=10000,**kwargs): """ NAME: meanjz PURPOSE: calculate the mean vertical action by marginalizing over velocity INPUT: R - radius at which to calculate this (can be Quantity) z - height at which to calculate this (can be Quantity) OPTIONAL INPUT: nsigma - number of sigma to integrate the velocities over scipy.integrate.tplquad kwargs epsabs and epsrel mc= if True, calculate using Monte Carlo integration nmc= if mc, use nmc samples OUTPUT: meanjz HISTORY: 2012-08-09 - Written - Bovy (IAS@MPIA) """ if mc: surfmass, vrs, vts, vzs= self._vmomentdensity(R,z,0.,0.,0., nsigma=nsigma,mc=mc,nmc=nmc,_returnmc=True, **kwargs) return self._jmomentdensity(R,z,0.,0.,1., nsigma=nsigma,mc=mc,nmc=nmc,_returnmc=False, _vrs=vrs,_vts=vts,_vzs=vzs, **kwargs)/surfmass else: #pragma: no cover because this is too slow; a warning is shown return (self._jmomentdensity(R,z,0.,0.,1., nsigma=nsigma,mc=mc,nmc=nmc, **kwargs)/ self._vmomentdensity(R,z,0.,0.,0., nsigma=nsigma,mc=mc,nmc=nmc, **kwargs)) @potential_physical_input def sampleV(self,R,z,n=1): """ NAME: sampleV PURPOSE: sample a radial, azimuthal, and vertical velocity at R,z INPUT: R - Galactocentric distance (can be Quantity) z - height (can be Quantity) n= number of distances to sample OUTPUT: list of samples HISTORY: 2012-12-17 - Written - Bovy (IAS) """ #Determine the maximum of the velocity distribution maxVR= 0. maxVz= 0. maxVT= optimize.fmin_powell((lambda x: -self(R,0.,x,z,0.,log=True, use_physical=False)), 1.) logmaxVD= self(R,maxVR,maxVT,z,maxVz,log=True,use_physical=False) #Now rejection-sample vRs= [] vTs= [] vzs= [] while len(vRs) < n: nmore= n-len(vRs)+1 #sample propvR= numpy.random.normal(size=nmore)*2.*self._sr propvT= numpy.random.normal(size=nmore)*2.*self._sr+maxVT propvz= numpy.random.normal(size=nmore)*2.*self._sz VDatprop= self(R+numpy.zeros(nmore), propvR,propvT,z+numpy.zeros(nmore), propvz,log=True,use_physical=False)-logmaxVD VDatprop-= -0.5*(propvR**2./4./self._sr**2.+propvz**2./4./self._sz**2.\ +(propvT-maxVT)**2./4./self._sr**2.) VDatprop= numpy.reshape(VDatprop,(nmore)) indx= (VDatprop > numpy.log(numpy.random.random(size=nmore))) #accept vRs.extend(list(propvR[indx])) vTs.extend(list(propvT[indx])) vzs.extend(list(propvz[indx])) out= numpy.empty((n,3)) out[:,0]= vRs[0:n] out[:,1]= vTs[0:n] out[:,2]= vzs[0:n] if _APY_UNITS and self._voSet: return units.Quantity(out*self._vo,unit=units.km/units.s) else: return out @actionAngle_physical_input @physical_conversion('phasespacedensityvelocity2',pop=True) def pvR(self,vR,R,z,gl=True,ngl=_DEFAULTNGL2,nsigma=4.,vTmax=1.5): """ NAME: pvR PURPOSE: calculate the marginalized vR probability at this location (NOT normalized by the density) INPUT: vR - radial velocity (can be Quantity) R - radius (can be Quantity) z - height (can be Quantity) gl - use Gauss-Legendre integration (True, currently the only option) ngl - order of Gauss-Legendre integration nsigma - sets integration limits to [-1,+1]*nsigma*sigma_z(R) for integration over vz (default: 4) vTmax - sets integration limits to [0,vTmax] for integration over vT (default: 1.5) OUTPUT: p(vR,R,z) HISTORY: 2012-12-22 - Written - Bovy (IAS) """ sigmaz1= self._sz*numpy.exp((self._refr-R)/self._hsz) if gl: if ngl % 2 == 1: raise ValueError("ngl must be even") #Use Gauss-Legendre integration for all if ngl == _DEFAULTNGL: glx, glw= self._glxdef, self._glwdef glx12, glw12= self._glxdef12, self._glwdef12 elif ngl == _DEFAULTNGL2: glx, glw= self._glxdef2, self._glwdef2 glx12, glw12= self._glxdef, self._glwdef else: glx, glw= numpy.polynomial.legendre.leggauss(ngl) glx12, glw12= numpy.polynomial.legendre.leggauss(ngl//2) #Evaluate everywhere if isinstance(self._aA,(actionAngle.actionAngleAdiabatic, actionAngle.actionAngleAdiabaticGrid)): vzgl= nsigma*sigmaz1/2.*(glx+1.) vzglw= glw vzfac= nsigma*sigmaz1 #2 x integration over [0,nsigma*sigmaz1] else: vzgl= nsigma*sigmaz1/2.*(glx12+1.) vzgl= list(vzgl) vzgl.extend(-nsigma*sigmaz1/2.*(glx12+1.)) vzgl= numpy.array(vzgl) vzglw= glw12 vzglw= list(vzglw) vzglw.extend(glw12) vzglw= numpy.array(vzglw) vzfac = 0.5*nsigma*sigmaz1 #integration over [-nsigma*sigmaz1,0] and [0,nsigma*sigmaz1] vTgl= vTmax/2.*(glx+1.) vTfac= 0.5 * vTmax #integration over [0.,vTmax] #Tile everything vTgl= numpy.tile(vTgl,(ngl,1)).T vzgl= numpy.tile(vzgl,(ngl,1)) vTglw= numpy.tile(glw,(ngl,1)).T #also tile weights vzglw= numpy.tile(vzglw,(ngl,1)) #evaluate logqeval= numpy.reshape(self(R+numpy.zeros(ngl*ngl), vR+numpy.zeros(ngl*ngl), vTgl.flatten(), z+numpy.zeros(ngl*ngl), vzgl.flatten(), log=True, use_physical=False), (ngl,ngl)) return numpy.sum(numpy.exp(logqeval)*vTglw*vzglw*vzfac)*vTfac @actionAngle_physical_input @physical_conversion('phasespacedensityvelocity2',pop=True) def pvT(self,vT,R,z,gl=True,ngl=_DEFAULTNGL2,nsigma=4.): """ NAME: pvT PURPOSE: calculate the marginalized vT probability at this location (NOT normalized by the density) INPUT: vT - tangential velocity (can be Quantity) R - radius (can be Quantity) z - height (can be Quantity) gl - use Gauss-Legendre integration (True, currently the only option) ngl - order of Gauss-Legendre integration nsigma - sets integration limits to [-1,+1]*nsigma*sigma(R) for integration over vz and vR (default: 4) OUTPUT: p(vT,R,z) HISTORY: 2012-12-22 - Written - Bovy (IAS) 2018-01-12 - Added Gauss-Legendre integration prefactor nsigma^2/4 - Trick (MPA) """ sigmaR1= self._sr*numpy.exp((self._refr-R)/self._hsr) sigmaz1= self._sz*numpy.exp((self._refr-R)/self._hsz) if gl: if ngl % 2 == 1: raise ValueError("ngl must be even") #Use Gauss-Legendre integration for all if ngl == _DEFAULTNGL: glx, glw= self._glxdef, self._glwdef glx12, glw12= self._glxdef12, self._glwdef12 elif ngl == _DEFAULTNGL2: glx, glw= self._glxdef2, self._glwdef2 glx12, glw12= self._glxdef, self._glwdef else: glx, glw= numpy.polynomial.legendre.leggauss(ngl) glx12, glw12= numpy.polynomial.legendre.leggauss(ngl//2) #Evaluate everywhere if isinstance(self._aA,(actionAngle.actionAngleAdiabatic, actionAngle.actionAngleAdiabaticGrid)): vRgl= nsigma*sigmaR1/2.*(glx+1.) vzgl= nsigma*sigmaz1/2.*(glx+1.) vRglw= glw vzglw= glw vRfac= nsigma*sigmaR1 #2 x integration over [0,nsigma*sigmaR1] vzfac= nsigma*sigmaz1 #2 x integration over [0,nsigma*sigmaz1] else: vRgl= nsigma*sigmaR1/2.*(glx12+1.) vRgl= list(vRgl) vRgl.extend(-nsigma*sigmaR1/2.*(glx12+1.)) vRgl= numpy.array(vRgl) vzgl= nsigma*sigmaz1/2.*(glx12+1.) vzgl= list(vzgl) vzgl.extend(-nsigma*sigmaz1/2.*(glx12+1.)) vzgl= numpy.array(vzgl) vRglw= glw12 vRglw= list(vRglw) vRglw.extend(glw12) vRglw= numpy.array(vRglw) vzglw= glw12 vzglw= list(vzglw) vzglw.extend(glw12) vzglw= numpy.array(vzglw) vRfac = 0.5*nsigma*sigmaR1 #integration over [-nsigma*sigmaR1,0] and [0,nsigma*sigmaR1] vzfac = 0.5*nsigma*sigmaz1 #integration over [-nsigma*sigmaz1,0] and [0,nsigma*sigmaz1] #Tile everything vRgl= numpy.tile(vRgl,(ngl,1)).T vzgl= numpy.tile(vzgl,(ngl,1)) vRglw= numpy.tile(vRglw,(ngl,1)).T #also tile weights vzglw= numpy.tile(vzglw,(ngl,1)) #evaluate logqeval= numpy.reshape(self(R+numpy.zeros(ngl*ngl), vRgl.flatten(), vT+numpy.zeros(ngl*ngl), z+numpy.zeros(ngl*ngl), vzgl.flatten(), log=True, use_physical=False), (ngl,ngl)) return numpy.sum(numpy.exp(logqeval)*vRglw*vzglw*vRfac*vzfac) @actionAngle_physical_input @physical_conversion('phasespacedensityvelocity2',pop=True) def pvz(self,vz,R,z,gl=True,ngl=_DEFAULTNGL2, nsigma=4.,vTmax=1.5, _return_actions=False,_jr=None,_lz=None,_jz=None, _return_freqs=False, _rg=None,_kappa=None,_nu=None,_Omega=None, _sigmaR1=None): """ NAME: pvz PURPOSE: calculate the marginalized vz probability at this location (NOT normalized by the density) INPUT: vz - vertical velocity (can be Quantity) R - radius (can be Quantity) z - height (can be Quantity) gl - use Gauss-Legendre integration (True, currently the only option) ngl - order of Gauss-Legendre integration nsigma - sets integration limits to [-1,+1]*nsigma*sigma_R(R) for integration over vR (default: 4) vTmax - sets integration limits to [0,vTmax] for integration over vT (default: 1.5) OUTPUT: p(vz,R,z) HISTORY: 2012-12-22 - Written - Bovy (IAS) """ if _sigmaR1 is None: sigmaR1= self._sr*numpy.exp((self._refr-R)/self._hsr) else: sigmaR1= _sigmaR1 if gl: if ngl % 2 == 1: raise ValueError("ngl must be even") #Use Gauss-Legendre integration for all if ngl == _DEFAULTNGL: glx, glw= self._glxdef, self._glwdef glx12, glw12= self._glxdef12, self._glwdef12 elif ngl == _DEFAULTNGL2: glx, glw= self._glxdef2, self._glwdef2 glx12, glw12= self._glxdef, self._glwdef else: glx, glw= numpy.polynomial.legendre.leggauss(ngl) glx12, glw12= numpy.polynomial.legendre.leggauss(ngl//2) #Evaluate everywhere if isinstance(self._aA,(actionAngle.actionAngleAdiabatic, actionAngle.actionAngleAdiabaticGrid)): vRgl= (glx+1.) vRglw= glw vRfac= nsigma*sigmaR1 #2 x integration over [0,nsigma*sigmaR1] else: vRgl= (glx12+1.) vRgl= list(vRgl) vRgl.extend(-(glx12+1.)) vRgl= numpy.array(vRgl) vRglw= glw12 vRglw= list(vRglw) vRglw.extend(glw12) vRglw= numpy.array(vRglw) vRfac = 0.5*nsigma*sigmaR1 #integration over [-nsigma*sigmaR1,0] and [0,nsigma*sigmaR1] vTgl= vTmax/2.*(glx+1.) vTfac= 0.5 * vTmax #integration over [0.,vTmax] #Tile everything vTgl= numpy.tile(vTgl,(ngl,1)).T vRgl= numpy.tile(vRgl,(ngl,1)) vTglw= numpy.tile(glw,(ngl,1)).T #also tile weights vRglw= numpy.tile(vRglw,(ngl,1)) #If inputs are arrays, tile if isinstance(R,numpy.ndarray): nR= len(R) R= numpy.tile(R,(ngl,ngl,1)).T.flatten() z= numpy.tile(z,(ngl,ngl,1)).T.flatten() vz= numpy.tile(vz,(ngl,ngl,1)).T.flatten() vTgl= numpy.tile(vTgl,(nR,1,1)).flatten() vRgl= numpy.tile(vRgl,(nR,1,1)).flatten() vTglw= numpy.tile(vTglw,(nR,1,1)) vRglw= numpy.tile(vRglw,(nR,1,1)) scalarOut= False else: R= R+numpy.zeros(ngl*ngl) z= z+numpy.zeros(ngl*ngl) vz= vz+numpy.zeros(ngl*ngl) nR= 1 scalarOut= True vRgl= vRgl.flatten() vRgl*= numpy.tile(nsigma*sigmaR1/2.,(ngl,ngl,1)).T.flatten() #evaluate if _jr is None and _rg is None: logqeval, jr, lz, jz, rg, kappa, nu, Omega= self(R, vRgl.flatten(), vTgl.flatten(), z, vz, log=True, _return_actions=True, _return_freqs=True, use_physical=False) logqeval= numpy.reshape(logqeval,(nR,ngl*ngl)) elif not _jr is None and not _rg is None: logqeval, jr, lz, jz, rg, kappa, nu, Omega= self((_jr,_lz,_jz), rg=_rg,kappa=_kappa,nu=_nu, Omega=_Omega, log=True, _return_actions=True, _return_freqs=True, use_physical=False) logqeval= numpy.reshape(logqeval,(nR,ngl*ngl)) elif not _jr is None and _rg is None: logqeval, jr, lz, jz, rg, kappa, nu, Omega= self((_jr,_lz,_jz), log=True, _return_actions=True, _return_freqs=True, use_physical=False) logqeval= numpy.reshape(logqeval,(nR,ngl*ngl)) elif _jr is None and not _rg is None: logqeval, jr, lz, jz, rg, kappa, nu, Omega= self(R, vRgl.flatten(), vTgl.flatten(), z, vz, rg=_rg,kappa=_kappa,nu=_nu, Omega=_Omega, log=True, _return_actions=True, _return_freqs=True, use_physical=False) logqeval= numpy.reshape(logqeval,(nR,ngl*ngl)) vRglw= numpy.reshape(vRglw,(nR,ngl*ngl)) vTglw= numpy.reshape(vTglw,(nR,ngl*ngl)) if scalarOut: result= numpy.sum(numpy.exp(logqeval)*vTglw*vRglw,axis=1)[0]*vRfac*vTfac else: result= numpy.sum(numpy.exp(logqeval)*vTglw*vRglw,axis=1)*vRfac*vTfac if _return_actions and _return_freqs: return (result, jr,lz,jz, rg, kappa, nu, Omega) elif _return_freqs: return (result, rg, kappa, nu, Omega) elif _return_actions: return (result, jr,lz,jz) else: return result @actionAngle_physical_input @physical_conversion('phasespacedensityvelocity',pop=True) def pvRvT(self,vR,vT,R,z,gl=True,ngl=_DEFAULTNGL2,nsigma=4.): """ NAME: pvRvT PURPOSE: calculate the marginalized (vR,vT) probability at this location (NOT normalized by the density) INPUT: vR - radial velocity (can be Quantity) vT - tangential velocity (can be Quantity) R - radius (can be Quantity) z - height (can be Quantity) gl - use Gauss-Legendre integration (True, currently the only option) ngl - order of Gauss-Legendre integration nsigma - sets integration limits to [-1,+1]*nsigma*sigma_z(R) for integration over vz (default: 4) OUTPUT: p(vR,vT,R,z) HISTORY: 2013-01-02 - Written - Bovy (IAS) 2018-01-12 - Added Gauss-Legendre integration prefactor nsigma/2 - Trick (MPA) """ sigmaz1= self._sz*numpy.exp((self._refr-R)/self._hsz) if gl: if ngl % 2 == 1: raise ValueError("ngl must be even") #Use Gauss-Legendre integration for all if ngl == _DEFAULTNGL: glx, glw= self._glxdef, self._glwdef glx12, glw12= self._glxdef12, self._glwdef12 elif ngl == _DEFAULTNGL2: glx, glw= self._glxdef2, self._glwdef2 glx12, glw12= self._glxdef, self._glwdef else: glx, glw= numpy.polynomial.legendre.leggauss(ngl) glx12, glw12= numpy.polynomial.legendre.leggauss(ngl//2) #Evaluate everywhere if isinstance(self._aA,(actionAngle.actionAngleAdiabatic, actionAngle.actionAngleAdiabaticGrid)): vzgl= nsigma*sigmaz1/2.*(glx+1.) vzglw= glw vzfac= nsigma*sigmaz1 #2 x integration over [0,nsigma*sigmaz1] else: vzgl= nsigma*sigmaz1/2.*(glx12+1.) vzgl= list(vzgl) vzgl.extend(-nsigma*sigmaz1/2.*(glx12+1.)) vzgl= numpy.array(vzgl) vzglw= glw12 vzglw= list(vzglw) vzglw.extend(glw12) vzglw= numpy.array(vzglw) vzfac = 0.5*nsigma*sigmaz1 #integration over [-nsigma*sigmaz1,0] and [0,nsigma*sigmaz1] #evaluate logqeval= self(R+numpy.zeros(ngl), vR+numpy.zeros(ngl), vT+numpy.zeros(ngl), z+numpy.zeros(ngl), vzgl, log=True,use_physical=False) return numpy.sum(numpy.exp(logqeval)*vzglw*vzfac) @actionAngle_physical_input @physical_conversion('phasespacedensityvelocity',pop=True) def pvTvz(self,vT,vz,R,z,gl=True,ngl=_DEFAULTNGL2,nsigma=4.): """ NAME: pvTvz PURPOSE: calculate the marginalized (vT,vz) probability at this location (NOT normalized by the density) INPUT: vT - tangential velocity (can be Quantity) vz - vertical velocity (can be Quantity) R - radius (can be Quantity) z - height (can be Quantity) gl - use Gauss-Legendre integration (True, currently the only option) ngl - order of Gauss-Legendre integration nsigma - sets integration limits to [-1,+1]*nsigma*sigma_R(R) for integration over vR (default: 4) OUTPUT: p(vT,vz,R,z) HISTORY: 2012-12-22 - Written - Bovy (IAS) 2018-01-12 - Added Gauss-Legendre integration prefactor nsigma/2 - Trick (MPA) """ sigmaR1= self._sr*numpy.exp((self._refr-R)/self._hsr) if gl: if ngl % 2 == 1: raise ValueError("ngl must be even") #Use Gauss-Legendre integration for all if ngl == _DEFAULTNGL: glx, glw= self._glxdef, self._glwdef glx12, glw12= self._glxdef12, self._glwdef12 elif ngl == _DEFAULTNGL2: glx, glw= self._glxdef2, self._glwdef2 glx12, glw12= self._glxdef, self._glwdef else: glx, glw= numpy.polynomial.legendre.leggauss(ngl) glx12, glw12= numpy.polynomial.legendre.leggauss(ngl//2) #Evaluate everywhere if isinstance(self._aA,(actionAngle.actionAngleAdiabatic, actionAngle.actionAngleAdiabaticGrid)): vRgl= nsigma*sigmaR1/2.*(glx+1.) vRglw= glw vRfac= nsigma*sigmaR1 #2 x integration over [0,nsigma*sigmaR1] else: vRgl= nsigma*sigmaR1/2.*(glx12+1.) vRgl= list(vRgl) vRgl.extend(-nsigma*sigmaR1/2.*(glx12+1.)) vRgl= numpy.array(vRgl) vRglw= glw12 vRglw= list(vRglw) vRglw.extend(glw12) vRglw= numpy.array(vRglw) vRfac = 0.5*nsigma*sigmaR1 #integration over [-nsigma*sigmaR1,0] and [0,nsigma*sigmaR1] #evaluate logqeval= self(R+numpy.zeros(ngl), vRgl, vT+numpy.zeros(ngl), z+numpy.zeros(ngl), vz+numpy.zeros(ngl), log=True,use_physical=False) return numpy.sum(numpy.exp(logqeval)*vRglw*vRfac) @actionAngle_physical_input @physical_conversion('phasespacedensityvelocity',pop=True) def pvRvz(self,vR,vz,R,z,gl=True,ngl=_DEFAULTNGL2,vTmax=1.5): """ NAME: pvR PURPOSE: calculate the marginalized (vR,vz) probability at this location (NOT normalized by the density) INPUT: vR - radial velocity (can be Quantity) vz - vertical velocity (can be Quantity) R - radius (can be Quantity) z - height (can be Quantity) gl - use Gauss-Legendre integration (True, currently the only option) ngl - order of Gauss-Legendre integration vTmax - sets integration limits to [0,vTmax] for integration over vT (default: 1.5) OUTPUT: p(vR,vz,R,z) HISTORY: 2013-01-02 - Written - Bovy (IAS) 2018-01-12 - Added Gauss-Legendre integration prefactor vTmax/2 - Trick (MPA) """ if gl: if ngl % 2 == 1: raise ValueError("ngl must be even") #Use Gauss-Legendre integration for all if ngl == _DEFAULTNGL: glx, glw= self._glxdef, self._glwdef glx12, glw12= self._glxdef12, self._glwdef12 elif ngl == _DEFAULTNGL2: glx, glw= self._glxdef2, self._glwdef2 glx12, glw12= self._glxdef, self._glwdef else: glx, glw= numpy.polynomial.legendre.leggauss(ngl) glx12, glw12= numpy.polynomial.legendre.leggauss(ngl//2) #Evaluate everywhere vTgl= vTmax/2.*(glx+1.) vTglw= glw vTfac= 0.5 * vTmax #integration over [0.,vTmax] #If inputs are arrays, tile if isinstance(R,numpy.ndarray): nR= len(R) R= numpy.tile(R,(ngl,1)).T.flatten() z= numpy.tile(z,(ngl,1)).T.flatten() vR= numpy.tile(vR,(ngl,1)).T.flatten() vz= numpy.tile(vz,(ngl,1)).T.flatten() vTgl= numpy.tile(vTgl,(nR,1)).flatten() vTglw= numpy.tile(vTglw,(nR,1)) scalarOut= False else: R= R+numpy.zeros(ngl) vR= vR+numpy.zeros(ngl) z= z+numpy.zeros(ngl) vz= vz+numpy.zeros(ngl) nR= 1 scalarOut= True #evaluate logqeval= numpy.reshape(self(R, vR, vTgl, z, vz, log=True, use_physical=False), (nR,ngl)) out= numpy.sum(numpy.exp(logqeval)*vTglw*vTfac,axis=1) if scalarOut: return out[0] else: return out def _calc_epifreq(self,r): """ NAME: _calc_epifreq PURPOSE: calculate the epicycle frequency at r INPUT: r - radius OUTPUT: kappa HISTORY: 2012-07-25 - Written - Bovy (IAS@MPIA) NOTE: takes about 0.1 ms for a Miyamoto-Nagai potential """ return potential.epifreq(self._pot,r) def _calc_verticalfreq(self,r): """ NAME: _calc_verticalfreq PURPOSE: calculate the vertical frequency at r INPUT: r - radius OUTPUT: nu HISTORY: 2012-07-25 - Written - Bovy (IAS@MPIA) NOTE: takes about 0.05 ms for a Miyamoto-Nagai potential """ return potential.verticalfreq(self._pot,r) def _rg(self,lz): """ NAME: _rg PURPOSE: calculate the radius of a circular orbit of Lz INPUT: lz - Angular momentum OUTPUT: radius HISTORY: 2012-07-25 - Written - Bovy (IAS@MPIA) NOTE: seems to take about ~0.5 ms for a Miyamoto-Nagai potential; ~0.75 ms for a MWPotential about the same with or without interpolation of the rotation curve Not sure what to do about negative lz... """ if isinstance(lz,numpy.ndarray): indx= (lz > self._precomputergLzmax)*(lz < self._precomputergLzmin) indxc= True^indx out= numpy.empty(lz.shape) out[indxc]= self._rgInterp(lz[indxc]) out[indx]= numpy.array([potential.rl(self._pot,lz[indx][ii]) for ii in range(numpy.sum(indx))]) return out else: if lz > self._precomputergLzmax or lz < self._precomputergLzmin: return potential.rl(self._pot,lz) return numpy.atleast_1d(self._rgInterp(lz)) def _vmomentsurfaceIntegrand(vz,vR,vT,R,z,df,sigmaR1,gamma,sigmaz1,n,m,o): #pragma: no cover because this is too slow; a warning is shown """Internal function that is the integrand for the vmomentsurface mass integration""" return vR**n*vT**m*vz**o*df(R,vR*sigmaR1,vT*sigmaR1*gamma,z,vz*sigmaz1, use_physical=False) def _vmomentsurfaceMCIntegrand(vz,vR,vT,R,z,df,sigmaR1,gamma,sigmaz1,mvT,n,m,o): """Internal function that is the integrand for the vmomentsurface mass integration""" return vR**n*vT**m*vz**o*df(R,vR*sigmaR1,vT*sigmaR1*gamma,z,vz*sigmaz1,use_physical=False)*numpy.exp(vR**2./2.+(vT-mvT)**2./2.+vz**2./2.) def _jmomentsurfaceIntegrand(vz,vR,vT,R,z,df,sigmaR1,gamma,sigmaz1,n,m,o): #pragma: no cover because this is too slow; a warning is shown """Internal function that is the integrand for the vmomentsurface mass integration""" return df(R,vR*sigmaR1,vT*sigmaR1*gamma,z,vz*sigmaz1,use_physical=False, func= (lambda x,y,z: x**n*y**m*z**o)) def _jmomentsurfaceMCIntegrand(vz,vR,vT,R,z,df,sigmaR1,gamma,sigmaz1,mvT,n,m,o): """Internal function that is the integrand for the vmomentsurface mass integration""" return df(R,vR*sigmaR1,vT*sigmaR1*gamma,z,vz*sigmaz1,use_physical=False, func=(lambda x,y,z: x**n*y**m*z**o))\ *numpy.exp(vR**2./2.+(vT-mvT)**2./2.+vz**2./2.) galpy-1.3.0/galpy/df_src/streamdf.py0000644000076500000240000044632313200463647017505 0ustar bovystaff00000000000000#The DF of a tidal stream import copy import numpy import multiprocessing import scipy from scipy import special, interpolate, integrate, optimize _SCIPY_VERSION= [int(v.split('rc')[0]) for v in scipy.__version__.split('.')] if _SCIPY_VERSION[0] < 1 and _SCIPY_VERSION[1] < 10: #pragma: no cover from scipy.maxentropy import logsumexp else: from scipy.misc import logsumexp from galpy.orbit import Orbit from galpy.df_src.df import df, _APY_LOADED from galpy.util import bovy_coords, fast_cholesky_invert, \ bovy_conversion, multi, bovy_plot, stable_cho_factor, bovy_ars from galpy.util.bovy_conversion import physical_conversion, _APY_UNITS from galpy.actionAngle_src.actionAngleIsochroneApprox import dePeriod import warnings from galpy.util import galpyWarning if _APY_LOADED: from astropy import units _INTERPDURINGSETUP= True _USEINTERP= True _USESIMPLE= True # cast a wide net _TWOPIWRAPS= numpy.arange(-4,5)*2.*numpy.pi _labelDict= {'x': r'$X$', 'y': r'$Y$', 'z': r'$Z$', 'r': r'$R$', 'phi': r'$\phi$', 'vx':r'$V_X$', 'vy':r'$V_Y$', 'vz':r'$V_Z$', 'vr':r'$V_R$', 'vt':r'$V_T$', 'll':r'$\mathrm{Galactic\ longitude\, (deg)}$', 'bb':r'$\mathrm{Galactic\ latitude\, (deg)}$', 'dist':r'$\mathrm{distance\, (kpc)}$', 'pmll':r'$\mu_l\,(\mathrm{mas\,yr}^{-1})$', 'pmbb':r'$\mu_b\,(\mathrm{mas\,yr}^{-1})$', 'vlos':r'$V_{\mathrm{los}}\,(\mathrm{km\,s}^{-1})$'} class streamdf(df): """The DF of a tidal stream""" def __init__(self,sigv,progenitor=None,pot=None,aA=None,useTM=False, tdisrupt=None,sigMeanOffset=6.,leading=True, sigangle=None, deltaAngleTrack=None,nTrackChunks=None,nTrackIterations=None, progIsTrack=False, ro=None,vo=None, Vnorm=None,Rnorm=None, R0=8.,Zsun=0.025,vsun=[-11.1,8.*30.24,7.25], multi=None,interpTrack=_INTERPDURINGSETUP, useInterp=_USEINTERP,nosetup=False,nospreadsetup=False, approxConstTrackFreq=False,useTMHessian=False, custom_transform=None): """ NAME: __init__ PURPOSE: Initialize a quasi-isothermal DF INPUT: sigv - radial velocity dispersion of the progenitor (can be Quantity) tdisrupt= (5 Gyr) time since start of disruption (can be Quantity) leading= (True) if True, model the leading part of the stream if False, model the trailing part progenitor= progenitor orbit as Orbit instance (will be re-integrated, so don't bother integrating the orbit before) progIsTrack= (False) if True, then the progenitor (x,v) is actually the (x,v) of the stream track at zero angle separation; useful when initializing with an orbit fit; the progenitor's position will be calculated pot= Potential instance or list thereof aA= actionAngle instance used to convert (x,v) to actions useTM= (False) if set to an actionAngleTorus instance, use this to speed up calculations sigMeanOffset= (6.) offset between the mean of the frequencies and the progenitor, in units of the largest eigenvalue of the frequency covariance matrix (along the largest eigenvector), should be positive; to model the trailing part, set leading=False sigangle= (sigv/122/[1km/s]=1.8sigv in natural coordinates) estimate of the angle spread of the debris initially (can be Quantity) deltaAngleTrack= (None) angle to estimate the stream track over (rad; or can be Quantity) nTrackChunks= (floor(deltaAngleTrack/0.15)+1) number of chunks to divide the progenitor track in nTrackIterations= Number of iterations to perform when establishing the track; each iteration starts from a previous approximation to the track in (x,v) and calculates a new track based on the deviation between the previous track and the desired track in action-angle coordinates; if not set, an appropriate value is determined based on the magnitude of the misalignment between stream and orbit, with larger numbers of iterations for larger misalignments interpTrack= (might change), interpolate the stream track while setting up the instance (can be done by hand by calling self._interpolate_stream_track() and self._interpolate_stream_track_aA()) useInterp= (might change), use interpolation by default when calculating approximated frequencies and angles nosetup= (False) if True, don't setup the stream track and anything else that is expensive nospreadsetup= (False) if True, don't setup the spread around the stream track (only for nosetup is False) multi= (None) if set, use multi-processing Coordinate transformation inputs: vo= (220) circular velocity to normalize velocities with [used to be Vnorm; can be Quantity] ro= (8) Galactocentric radius to normalize positions with [used to be Rnorm; can be Quantity] R0= (8) Galactocentric radius of the Sun (kpc) [can be different from ro; can be Quantity] Zsun= (0.025) Sun's height above the plane (kpc; can be Quantity) vsun= ([-11.1,241.92,7.25]) Sun's motion in cylindrical coordinates (vR positive away from center) (can be Quantity) custom_transform= (None) matrix implementing the rotation from (ra,dec) to a custom set of sky coordinates approxConstTrackFreq= (False) if True, approximate the stream assuming that the frequency is constant along the stream (only works with useTM, for which this leads to a significant speed-up) useTMHessian= (False) if True, compute the basic Hessian dO/dJ_prog using TM; otherwise use aA OUTPUT: object HISTORY: 2013-09-16 - Started - Bovy (IAS) 2013-11-25 - Started over - Bovy (IAS) """ if ro is None and not Rnorm is None: warnings.warn("WARNING: Rnorm keyword input to streamdf is deprecated in favor of the standard ro keyword", galpyWarning) ro= Rnorm if vo is None and not Vnorm is None: warnings.warn("WARNING: Vnorm keyword input to streamdf is deprecated in favor of the standard vo keyword", galpyWarning) vo= Vnorm df.__init__(self,ro=ro,vo=vo) if _APY_LOADED and isinstance(sigv,units.Quantity): sigv= sigv.to(units.km/units.s).value/self._vo self._sigv= sigv if tdisrupt is None: self._tdisrupt= 5./bovy_conversion.time_in_Gyr(self._vo,self._ro) else: if _APY_LOADED and isinstance(tdisrupt,units.Quantity): tdisrupt= tdisrupt.to(units.Gyr).value\ /bovy_conversion.time_in_Gyr(self._vo,self._ro) self._tdisrupt= tdisrupt self._sigMeanOffset= sigMeanOffset if pot is None: #pragma: no cover raise IOError("pot= must be set") self._pot= pot self._aA= aA if not self._aA._pot == self._pot: raise IOError("Potential in aA does not appear to be the same as given potential pot") if useTM: self._useTM= True self._aAT= useTM # confusing, no? self._approxConstTrackFreq= approxConstTrackFreq if not self._aAT._pot == self._pot: raise IOError("Potential in useTM=actionAngleTorus instance does not appear to be the same as given potential pot") else: self._useTM= False if (multi is True): #if set to boolean, enable cpu_count processes self._multi= multiprocessing.cpu_count() else: self._multi= multi self._progenitor_setup(progenitor,leading,useTMHessian) if not sigangle is None and \ _APY_LOADED and isinstance(sigangle,units.Quantity): sigangle= sigangle.to(units.rad).value if not deltaAngleTrack is None and \ _APY_LOADED and isinstance(deltaAngleTrack,units.Quantity): deltaAngleTrack= deltaAngleTrack.to(units.rad).value self._offset_setup(sigangle,leading,deltaAngleTrack) # if progIsTrack, calculate the progenitor that gives a track that is approximately the given orbit if progIsTrack: self._setup_progIsTrack() if _APY_LOADED and isinstance(R0,units.Quantity): R0= R0.to(units.kpc).value if _APY_LOADED and isinstance(Zsun,units.Quantity): Zsun= Zsun.to(units.kpc).value if _APY_LOADED and isinstance(vsun,units.Quantity): vsun= vsun.to(units.km/units.s).value elif _APY_LOADED and isinstance(vsun[0],units.Quantity): vsun[0]= vsun[0].to(units.km/units.s).value vsun[1]= vsun[1].to(units.km/units.s).value vsun[2]= vsun[2].to(units.km/units.s).value self._setup_coord_transform(R0,Zsun,vsun,progenitor,custom_transform) #Determine the stream track if not nosetup: self._determine_nTrackIterations(nTrackIterations) self._determine_stream_track(nTrackChunks) self._useInterp= useInterp if interpTrack or self._useInterp: self._interpolate_stream_track() self._interpolate_stream_track_aA() self.calc_stream_lb() if not nospreadsetup: self._determine_stream_spread() return None def _progenitor_setup(self,progenitor,leading,useTMHessian): """The part of the setup relating to the progenitor's orbit""" #Progenitor orbit: Calculate actions, frequencies, and angles for the progenitor self._progenitor= progenitor() #call to get new Orbit # Make sure we do not use physical coordinates self._progenitor.turn_physical_off() acfs= self._aA.actionsFreqsAngles(self._progenitor, _firstFlip=(not leading), use_physical=False) self._progenitor_jr= acfs[0][0] self._progenitor_lz= acfs[1][0] self._progenitor_jz= acfs[2][0] self._progenitor_Omegar= acfs[3] self._progenitor_Omegaphi= acfs[4] self._progenitor_Omegaz= acfs[5] self._progenitor_Omega= numpy.array([acfs[3],acfs[4],acfs[5]]).reshape(3) self._progenitor_angler= acfs[6] self._progenitor_anglephi= acfs[7] self._progenitor_anglez= acfs[8] self._progenitor_angle= numpy.array([acfs[6],acfs[7],acfs[8]]).reshape(3) #Calculate dO/dJ Jacobian at the progenitor if useTMHessian: h, fr,fp,fz,e= self._aAT.hessianFreqs(self._progenitor_jr, self._progenitor_lz, self._progenitor_jz) self._dOdJp= h # Replace frequencies with TM frequencies self._progenitor_Omegar= fr self._progenitor_Omegaphi= fp self._progenitor_Omegaz= fz self._progenitor_Omega= numpy.array([self._progenitor_Omegar, self._progenitor_Omegaphi, self._progenitor_Omegaz]).reshape(3) else: self._dOdJp= calcaAJac(self._progenitor._orb.vxvv, self._aA,dxv=None,dOdJ=True, _initacfs=acfs) self._dOdJpInv= numpy.linalg.inv(self._dOdJp) self._dOdJpEig= numpy.linalg.eig(self._dOdJp) return None def _offset_setup(self,sigangle,leading,deltaAngleTrack): """The part of the setup related to calculating the stream/progenitor offset""" #From the progenitor orbit, determine the sigmas in J and angle self._sigjr= (self._progenitor.rap()-self._progenitor.rperi())/numpy.pi*self._sigv self._siglz= self._progenitor.rperi()*self._sigv self._sigjz= 2.*self._progenitor.zmax()/numpy.pi*self._sigv #Estimate the frequency covariance matrix from a diagonal J matrix x dOdJ self._sigjmatrix= numpy.diag([self._sigjr**2., self._siglz**2., self._sigjz**2.]) self._sigomatrix= numpy.dot(self._dOdJp, numpy.dot(self._sigjmatrix,self._dOdJp.T)) #Estimate angle spread as the ratio of the largest to the middle eigenvalue self._sigomatrixEig= numpy.linalg.eig(self._sigomatrix) self._sigomatrixEigsortIndx= numpy.argsort(self._sigomatrixEig[0]) self._sortedSigOEig= sorted(self._sigomatrixEig[0]) if sigangle is None: self._sigangle= self._sigv*1.8 else: self._sigangle= sigangle self._sigangle2= self._sigangle**2. self._lnsigangle= numpy.log(self._sigangle) #Estimate the frequency mean as lying along the direction of the largest eigenvalue self._dsigomeanProgDirection= self._sigomatrixEig[1][:,numpy.argmax(self._sigomatrixEig[0])] self._progenitor_Omega_along_dOmega= \ numpy.dot(self._progenitor_Omega,self._dsigomeanProgDirection) #Make sure we are modeling the correct part of the stream self._leading= leading self._sigMeanSign= 1. if self._leading and self._progenitor_Omega_along_dOmega < 0.: self._sigMeanSign= -1. elif not self._leading and self._progenitor_Omega_along_dOmega > 0.: self._sigMeanSign= -1. self._progenitor_Omega_along_dOmega*= self._sigMeanSign self._sigomean= self._progenitor_Omega\ +self._sigMeanOffset*self._sigMeanSign\ *numpy.sqrt(numpy.amax(self._sigomatrixEig[0]))\ *self._dsigomeanProgDirection #numpy.dot(self._dOdJp, # numpy.array([self._sigjr,self._siglz,self._sigjz])) self._dsigomeanProg= self._sigomean-self._progenitor_Omega self._meandO= self._sigMeanOffset\ *numpy.sqrt(numpy.amax(self._sigomatrixEig[0])) #Store cholesky of sigomatrix for fast evaluation self._sigomatrixNorm=\ numpy.sqrt(numpy.sum(self._sigomatrix**2.)) self._sigomatrixinv, self._sigomatrixLogdet= \ fast_cholesky_invert(self._sigomatrix/self._sigomatrixNorm, tiny=10.**-15.,logdet=True) self._sigomatrixinv/= self._sigomatrixNorm deltaAngleTrackLim = (self._sigMeanOffset+4.) * numpy.sqrt( self._sortedSigOEig[2]) * self._tdisrupt if (deltaAngleTrack is None): deltaAngleTrack = deltaAngleTrackLim else: if (deltaAngleTrack > deltaAngleTrackLim): warnings.warn("WARNING: angle range large compared to plausible value.", galpyWarning) self._deltaAngleTrack= deltaAngleTrack return None def _setup_coord_transform(self,R0,Zsun,vsun,progenitor,custom_transform): #Set the coordinate-transformation parameters; check that these do not conflict with those in the progenitor orbit object; need to use the original, since this objects _progenitor has physical turned off if progenitor._roSet \ and (numpy.fabs(self._ro-progenitor._orb._ro) > 10.**-.8 \ or numpy.fabs(R0-progenitor._orb._ro) > 10.**-8.): warnings.warn("Warning: progenitor's ro does not agree with streamdf's ro and R0; this may have unexpected consequences when projecting into observables", galpyWarning) if progenitor._voSet \ and numpy.fabs(self._vo-progenitor._orb._vo) > 10.**-8.: warnings.warn("Warning: progenitor's vo does not agree with streamdf's vo; this may have unexpected consequences when projecting into observables", galpyWarning) if (progenitor._roSet or progenitor._voSet) \ and numpy.fabs(Zsun-progenitor._orb._zo) > 10.**-8.: warnings.warn("Warning: progenitor's zo does not agree with streamdf's Zsun; this may have unexpected consequences when projecting into observables", galpyWarning) if (progenitor._roSet or progenitor._voSet) \ and numpy.any(numpy.fabs(vsun-numpy.array([0.,self._vo,0.])\ -progenitor._orb._solarmotion) > 10.**-8.): warnings.warn("Warning: progenitor's solarmotion does not agree with streamdf's vsun (after accounting for vo); this may have unexpected consequences when projecting into observables", galpyWarning) self._R0= R0 self._Zsun= Zsun self._vsun= vsun self._custom_transform= custom_transform return None def _setup_progIsTrack(self): """If progIsTrack, the progenitor orbit that was passed to the streamdf initialization is the track at zero angle separation; this routine computes an actual progenitor position that gives the desired track given the parameters of the streamdf""" # We need to flip the sign of the offset, to go to the progenitor self._sigMeanSign*= -1. # Use _determine_stream_track_single to calculate the track-progenitor # offset at zero angle separation prog_stream_offset=\ _determine_stream_track_single(self._aA, self._progenitor, 0., #time = 0 self._progenitor_angle, self._sigMeanSign, self._dsigomeanProgDirection, lambda x: self.meanOmega(x,use_physical=False), 0.) #angle = 0 # Setup the new progenitor orbit progenitor= Orbit(prog_stream_offset[3]) # Flip the offset sign again self._sigMeanSign*= -1. # Now re-do the previous setup self._progenitor_setup(progenitor,self._leading,False) self._offset_setup(self._sigangle,self._leading, self._deltaAngleTrack) return None @physical_conversion('angle',pop=True) def misalignment(self,isotropic=False,**kwargs): """ NAME: misalignment PURPOSE: calculate the misalignment between the progenitor's frequency and the direction along which the stream disrupts INPUT: isotropic= (False), if True, return the misalignment assuming an isotropic action distribution OUTPUT: misalignment in rad HISTORY: 2013-12-05 - Written - Bovy (IAS) 2017-10-28 - Changed output unit to rad - Bovy (UofT) """ warnings.warn("In versions >1.3, the output unit of streamdf.misalignment has been changed to radian (from degree before)",galpyWarning) if isotropic: dODir= self._dOdJpEig[1][:,numpy.argmax(numpy.fabs(self._dOdJpEig[0]))] else: dODir= self._dsigomeanProgDirection out= numpy.arccos(numpy.sum(self._progenitor_Omega*dODir)/numpy.sqrt(numpy.sum(self._progenitor_Omega**2.))) if out > numpy.pi/2.: return out-numpy.pi else: return out def freqEigvalRatio(self,isotropic=False): """ NAME: freqEigvalRatio PURPOSE: calculate the ratio between the largest and 2nd-to-largest (in abs) eigenvalue of sqrt(dO/dJ^T V_J dO/dJ) (if this is big, a 1D stream will form) INPUT: isotropic= (False), if True, return the ratio assuming an isotropic action distribution (i.e., just of dO/dJ) OUTPUT: ratio between eigenvalues of fabs(dO / dJ) HISTORY: 2013-12-05 - Written - Bovy (IAS) """ if isotropic: sortedEig= sorted(numpy.fabs(self._dOdJpEig[0])) return sortedEig[2]/sortedEig[1] else: return numpy.sqrt(self._sortedSigOEig)[2]\ /numpy.sqrt(self._sortedSigOEig)[1] @physical_conversion('time',pop=True) def estimateTdisrupt(self,deltaAngle): """ NAME: estimateTdisrupt PURPOSE: estimate the time of disruption INPUT: deltaAngle- spread in angle since disruption OUTPUT: time in natural units HISTORY: 2013-11-27 - Written - Bovy (IAS) """ return deltaAngle\ /numpy.sqrt(numpy.sum(self._dsigomeanProg**2.)) def subhalo_encounters(self,venc=numpy.inf,sigma=150./220., nsubhalo=0.3,bmax=0.025,yoon=False): """ NAME: subhalo_encounters PURPOSE: estimate the number of encounters with subhalos over the lifetime of this stream, using a formalism similar to that of Yoon et al. (2011) INPUT: venc= (numpy.inf) count encounters with (relative) speeds less than this (relative radial velocity in cylindrical stream frame, unless yoon is True) (can be Quantity) sigma= (150/220) velocity dispersion of the DM subhalo population (can be Quantity) nsubhalo= (0.3) spatial number density of subhalos (can be Quantity) bmax= (0.025) maximum impact parameter (if larger than width of stream) (can be Quantity) yoon= (False) if True, use erroneous Yoon et al. formula OUTPUT: number of encounters HISTORY: 2016-01-19 - Written - Bovy (UofT) """ if _APY_LOADED and isinstance(venc,units.Quantity): venc= venc.to(units.km/units.s).value/self._vo if _APY_LOADED and isinstance(sigma,units.Quantity): sigma= sigma.to(units.km/units.s).value/self._vo if _APY_LOADED and isinstance(nsubhalo,units.Quantity): nsubhalo= nsubhalo.to(1/units.kpc**3).value*self._ro**3. if _APY_LOADED and isinstance(bmax,units.Quantity): bmax= bmax.to(units.kpc).value/self._ro Ravg= numpy.mean(numpy.sqrt(self._progenitor._orb.orbit[:,0]**2. +self._progenitor._orb.orbit[:,3]**2.)) if numpy.isinf(venc): vencFac= 1. elif yoon: vencFac= (1.-(1.+venc**2./4./sigma**2.)\ *numpy.exp(-venc**2./4./sigma**2.)) else: vencFac= (1.-numpy.exp(-venc**2./2./sigma**2.)) if yoon: yoonFac= 2*numpy.sqrt(2.) else: yoonFac= 1. # Figure out width of stream w= self.sigangledAngle(self._meandO*self._tdisrupt,simple=True, use_physical=False) if bmax < w*Ravg/2.: bmax= w*Ravg/2. return yoonFac/numpy.sqrt(2.)*numpy.sqrt(numpy.pi)*Ravg*sigma\ *self._tdisrupt**2.*self._meandO\ *bmax*nsubhalo*vencFac ############################STREAM TRACK FUNCTIONS############################# def plotTrack(self,d1='x',d2='z',interp=True,spread=0,simple=_USESIMPLE, *args,**kwargs): """ NAME: plotTrack PURPOSE: plot the stream track INPUT: d1= plot this on the X axis ('x','y','z','R','phi','vx','vy','vz','vR','vt','ll','bb','dist','pmll','pmbb','vlos') d2= plot this on the Y axis (same list as for d1) interp= (True) if True, use the interpolated stream track spread= (0) if int > 0, also plot the spread around the track as spread x sigma scaleToPhysical= (False), if True, plot positions in kpc and velocities in km/s simple= (False), if True, use a simple estimate for the spread in perpendicular angle bovy_plot.bovy_plot args and kwargs OUTPUT: plot to output device HISTORY: 2013-12-09 - Written - Bovy (IAS) """ if not hasattr(self,'_ObsTrackLB') and \ (d1.lower() == 'll' or d1.lower() == 'bb' or d1.lower() == 'dist' or d1.lower() == 'pmll' or d1.lower() == 'pmbb' or d1.lower() == 'vlos' or d2.lower() == 'll' or d2.lower() == 'bb' or d2.lower() == 'dist' or d2.lower() == 'pmll' or d2.lower() == 'pmbb' or d2.lower() == 'vlos'): self.calc_stream_lb() phys= kwargs.pop('scaleToPhysical',False) tx= self._parse_track_dim(d1,interp=interp,phys=phys) ty= self._parse_track_dim(d2,interp=interp,phys=phys) bovy_plot.bovy_plot(tx,ty,*args, xlabel=_labelDict[d1.lower()], ylabel=_labelDict[d2.lower()], **kwargs) if spread: addx, addy= self._parse_track_spread(d1,d2,interp=interp,phys=phys, simple=simple) if ('ls' in kwargs and kwargs['ls'] == 'none') \ or ('linestyle' in kwargs \ and kwargs['linestyle'] == 'none'): kwargs.pop('ls',None) kwargs.pop('linestyle',None) spreadls= 'none' else: spreadls= '-.' spreadmarker= kwargs.pop('marker',None) spreadcolor= kwargs.pop('color',None) spreadlw= kwargs.pop('lw',1.) bovy_plot.bovy_plot(tx+spread*addx,ty+spread*addy,ls=spreadls, marker=spreadmarker,color=spreadcolor, lw=spreadlw, overplot=True) bovy_plot.bovy_plot(tx-spread*addx,ty-spread*addy,ls=spreadls, marker=spreadmarker,color=spreadcolor, lw=spreadlw, overplot=True) return None def plotProgenitor(self,d1='x',d2='z',*args,**kwargs): """ NAME: plotProgenitor PURPOSE: plot the progenitor orbit INPUT: d1= plot this on the X axis ('x','y','z','R','phi','vx','vy','vz','vR','vt','ll','bb','dist','pmll','pmbb','vlos') d2= plot this on the Y axis (same list as for d1) scaleToPhysical= (False), if True, plot positions in kpc and velocities in km/s bovy_plot.bovy_plot args and kwargs OUTPUT: plot to output device HISTORY: 2013-12-09 - Written - Bovy (IAS) """ tts= self._progenitor._orb.t[self._progenitor._orb.t \ < self._trackts[self._nTrackChunks-1]] obs= [self._R0,0.,self._Zsun] obs.extend(self._vsun) phys= kwargs.pop('scaleToPhysical',False) tx= self._parse_progenitor_dim(d1,tts,ro=self._ro,vo=self._vo, obs=obs,phys=phys) ty= self._parse_progenitor_dim(d2,tts,ro=self._ro,vo=self._vo, obs=obs,phys=phys) bovy_plot.bovy_plot(tx,ty,*args, xlabel=_labelDict[d1.lower()], ylabel=_labelDict[d2.lower()], **kwargs) return None def _parse_track_dim(self,d1,interp=True,phys=False): """Parse the dimension to plot the stream track for""" if interp: interpStr= 'interpolated' else: interpStr= '' if d1.lower() == 'x': tx= self.__dict__['_%sObsTrackXY' % interpStr][:,0] elif d1.lower() == 'y': tx= self.__dict__['_%sObsTrackXY' % interpStr][:,1] elif d1.lower() == 'z': tx= self.__dict__['_%sObsTrackXY' % interpStr][:,2] elif d1.lower() == 'r': tx= self.__dict__['_%sObsTrack' % interpStr][:,0] elif d1.lower() == 'phi': tx= self.__dict__['_%sObsTrack' % interpStr][:,5] elif d1.lower() == 'vx': tx= self.__dict__['_%sObsTrackXY' % interpStr][:,3] elif d1.lower() == 'vy': tx= self.__dict__['_%sObsTrackXY' % interpStr][:,4] elif d1.lower() == 'vz': tx= self.__dict__['_%sObsTrackXY' % interpStr][:,5] elif d1.lower() == 'vr': tx= self.__dict__['_%sObsTrack' % interpStr][:,1] elif d1.lower() == 'vt': tx= self.__dict__['_%sObsTrack' % interpStr][:,2] elif d1.lower() == 'll': tx= self.__dict__['_%sObsTrackLB' % interpStr][:,0] elif d1.lower() == 'bb': tx= self.__dict__['_%sObsTrackLB' % interpStr][:,1] elif d1.lower() == 'dist': tx= self.__dict__['_%sObsTrackLB' % interpStr][:,2] elif d1.lower() == 'pmll': tx= self.__dict__['_%sObsTrackLB' % interpStr][:,4] elif d1.lower() == 'pmbb': tx= self.__dict__['_%sObsTrackLB' % interpStr][:,5] elif d1.lower() == 'vlos': tx= self.__dict__['_%sObsTrackLB' % interpStr][:,3] if phys and (d1.lower() == 'x' or d1.lower() == 'y' \ or d1.lower() == 'z' or d1.lower() == 'r'): tx= copy.copy(tx) tx*= self._ro if phys and (d1.lower() == 'vx' or d1.lower() == 'vy' \ or d1.lower() == 'vz' or d1.lower() == 'vr' \ or d1.lower() == 'vt'): tx= copy.copy(tx) tx*= self._vo return tx def _parse_progenitor_dim(self,d1,ts,ro=None,vo=None,obs=None, phys=False): """Parse the dimension to plot the progenitor orbit for""" if d1.lower() == 'x': tx= self._progenitor.x(ts,ro=ro,vo=vo,obs=obs,use_physical=False) elif d1.lower() == 'y': tx= self._progenitor.y(ts,ro=ro,vo=vo,obs=obs,use_physical=False) elif d1.lower() == 'z': tx= self._progenitor.z(ts,ro=ro,vo=vo,obs=obs,use_physical=False) elif d1.lower() == 'r': tx= self._progenitor.R(ts,ro=ro,vo=vo,obs=obs,use_physical=False) elif d1.lower() == 'phi': tx= self._progenitor.phi(ts,ro=ro,vo=vo,obs=obs) elif d1.lower() == 'vx': tx= self._progenitor.vx(ts,ro=ro,vo=vo,obs=obs,use_physical=False) elif d1.lower() == 'vy': tx= self._progenitor.vy(ts,ro=ro,vo=vo,obs=obs,use_physical=False) elif d1.lower() == 'vz': tx= self._progenitor.vz(ts,ro=ro,vo=vo,obs=obs,use_physical=False) elif d1.lower() == 'vr': tx= self._progenitor.vR(ts,ro=ro,vo=vo,obs=obs,use_physical=False) elif d1.lower() == 'vt': tx= self._progenitor.vT(ts,ro=ro,vo=vo,obs=obs,use_physical=False) elif d1.lower() == 'll': tx= self._progenitor.ll(ts,ro=ro,vo=vo,obs=obs) elif d1.lower() == 'bb': tx= self._progenitor.bb(ts,ro=ro,vo=vo,obs=obs) elif d1.lower() == 'dist': tx= self._progenitor.dist(ts,ro=ro,vo=vo,obs=obs) elif d1.lower() == 'pmll': tx= self._progenitor.pmll(ts,ro=ro,vo=vo,obs=obs) elif d1.lower() == 'pmbb': tx= self._progenitor.pmbb(ts,ro=ro,vo=vo,obs=obs) elif d1.lower() == 'vlos': tx= self._progenitor.vlos(ts,ro=ro,vo=vo,obs=obs) if phys and (d1.lower() == 'x' or d1.lower() == 'y' \ or d1.lower() == 'z' or d1.lower() == 'r'): tx= copy.copy(tx) tx*= self._ro if phys and (d1.lower() == 'vx' or d1.lower() == 'vy' \ or d1.lower() == 'vz' or d1.lower() == 'vr' \ or d1.lower() == 'vt'): tx= copy.copy(tx) tx*= self._vo return tx def _parse_track_spread(self,d1,d2,interp=True,phys=False, simple=_USESIMPLE): """Determine the spread around the track""" if not hasattr(self,'_allErrCovs'): self._determine_stream_spread(simple=simple) okaySpreadR= ['r','vr','vt','z','vz','phi'] okaySpreadXY= ['x','y','z','vx','vy','vz'] okaySpreadLB= ['ll','bb','dist','vlos','pmll','pmbb'] #Determine which coordinate system we're in coord= [False,False,False] #R, XY, LB if d1.lower() in okaySpreadR and d2.lower() in okaySpreadR: coord[0]= True elif d1.lower() in okaySpreadXY and d2.lower() in okaySpreadXY: coord[1]= True elif d1.lower() in okaySpreadLB and d2.lower() in okaySpreadLB: coord[2]= True else: raise NotImplementedError("plotting the spread for coordinates from different systems not implemented yet ...") #Get the right 2D Jacobian indxDict= {} indxDict['r']= 0 indxDict['vr']= 1 indxDict['vt']= 2 indxDict['z']= 3 indxDict['vz']= 4 indxDict['phi']= 5 indxDictXY= {} indxDictXY['x']= 0 indxDictXY['y']= 1 indxDictXY['z']= 2 indxDictXY['vx']= 3 indxDictXY['vy']= 4 indxDictXY['vz']= 5 indxDictLB= {} indxDictLB['ll']= 0 indxDictLB['bb']= 1 indxDictLB['dist']= 2 indxDictLB['vlos']= 3 indxDictLB['pmll']= 4 indxDictLB['pmbb']= 5 if coord[0]: relevantCov= self._allErrCovs relevantDict= indxDict if phys:#apply scale factors tcov= copy.copy(relevantCov) scaleFac= numpy.array([self._ro,self._vo,self._vo, self._ro,self._vo,1.]) tcov*= numpy.tile(scaleFac,(6,1)) tcov*= numpy.tile(scaleFac,(6,1)).T relevantCov= tcov elif coord[1]: relevantCov= self._allErrCovsXY relevantDict= indxDictXY if phys:#apply scale factors tcov= copy.copy(relevantCov) scaleFac= numpy.array([self._ro,self._ro,self._ro, self._vo,self._vo,self._vo]) tcov*= numpy.tile(scaleFac,(6,1)) tcov*= numpy.tile(scaleFac,(6,1)).T relevantCov= tcov elif coord[2]: relevantCov= self._allErrCovsLBUnscaled relevantDict= indxDictLB indx0= numpy.array([[relevantDict[d1.lower()],relevantDict[d1.lower()]], [relevantDict[d2.lower()],relevantDict[d2.lower()]]]) indx1= numpy.array([[relevantDict[d1.lower()],relevantDict[d2.lower()]], [relevantDict[d1.lower()],relevantDict[d2.lower()]]]) cov= relevantCov[:,indx0,indx1] #cov contains all nTrackChunks covs if not interp: out= numpy.empty((self._nTrackChunks,2)) eigDir= numpy.array([1.,0.]) for ii in range(self._nTrackChunks): covEig= numpy.linalg.eig(cov[ii]) minIndx= numpy.argmin(covEig[0]) minEigvec= covEig[1][:,minIndx] #this is the direction of the transverse spread if numpy.sum(minEigvec*eigDir) < 0.: minEigvec*= -1. #Keep them pointing in the same direction out[ii]= minEigvec*numpy.sqrt(covEig[0][minIndx]) eigDir= minEigvec else: #We slerp the minor eigenvector and interpolate the eigenvalue #First store all of the eigenvectors on the track allEigval= numpy.empty(self._nTrackChunks) allEigvec= numpy.empty((self._nTrackChunks,2)) eigDir= numpy.array([1.,0.]) for ii in range(self._nTrackChunks): covEig= numpy.linalg.eig(cov[ii]) minIndx= numpy.argmin(covEig[0]) minEigvec= covEig[1][:,minIndx] #this is the direction of the transverse spread if numpy.sum(minEigvec*eigDir) < 0.: minEigvec*= -1. #Keep them pointing in the same direction allEigval[ii]= numpy.sqrt(covEig[0][minIndx]) allEigvec[ii]= minEigvec eigDir= minEigvec #Now interpolate where needed interpEigval=\ interpolate.InterpolatedUnivariateSpline(self._thetasTrack, allEigval,k=3) interpolatedEigval= interpEigval(self._interpolatedThetasTrack) #Interpolate in chunks interpolatedEigvec= numpy.empty((len(self._interpolatedThetasTrack), 2)) for ii in range(self._nTrackChunks-1): slerpOmega= numpy.arccos(numpy.sum(allEigvec[ii]*allEigvec[ii+1])) slerpts= (self._interpolatedThetasTrack-self._thetasTrack[ii])/\ (self._thetasTrack[ii+1]-self._thetasTrack[ii]) slerpIndx= (slerpts >= 0.)*(slerpts <= 1.) for jj in range(2): interpolatedEigvec[slerpIndx,jj]=\ (numpy.sin((1-slerpts[slerpIndx])*slerpOmega)*allEigvec[ii,jj] +numpy.sin(slerpts[slerpIndx]*slerpOmega)*allEigvec[ii+1,jj])/numpy.sin(slerpOmega) out= numpy.tile(interpolatedEigval.T,(2,1)).T*interpolatedEigvec if coord[2]: #if LB, undo rescalings that were applied before out[:,0]*= self._ErrCovsLBScale[relevantDict[d1.lower()]] out[:,1]*= self._ErrCovsLBScale[relevantDict[d2.lower()]] return (out[:,0],out[:,1]) def plotCompareTrackAAModel(self,**kwargs): """ NAME: plotCompareTrackAAModel PURPOSE: plot the comparison between the underlying model's dOmega_perp vs. dangle_r (line) and the track in (x,v)'s dOmega_perp vs. dangle_r (dots; explicitly calculating the track's action-angle coordinates) INPUT: bovy_plot.bovy_plot kwargs OUTPUT: plot HISTORY: 2014-08-27 - Written - Bovy (IAS) """ #First calculate the model model_adiff= (self._ObsTrackAA[:,3:]-self._progenitor_angle)[:,0]\ *self._sigMeanSign model_operp= numpy.dot(self._ObsTrackAA[:,:3]-self._progenitor_Omega, self._dsigomeanProgDirection)\ *self._sigMeanSign #Then calculate the track's frequency-angle coordinates if self._multi is None: aatrack= numpy.empty((self._nTrackChunks,6)) for ii in range(self._nTrackChunks): aatrack[ii]= self._aA.actionsFreqsAngles(Orbit(self._ObsTrack[ii,:]), use_physical=False)[3:] else: aatrack= numpy.reshape(\ multi.parallel_map( (lambda x: self._aA.actionsFreqsAngles(Orbit(self._ObsTrack[x,:]),use_physical=False)[3:]), range(self._nTrackChunks), numcores=numpy.amin([self._nTrackChunks, multiprocessing.cpu_count(), self._multi])),(self._nTrackChunks,6)) track_adiff= (aatrack[:,3:]-self._progenitor_angle)[:,0]\ *self._sigMeanSign track_operp= numpy.dot(aatrack[:,:3]-self._progenitor_Omega, self._dsigomeanProgDirection)\ *self._sigMeanSign overplot= kwargs.pop('overplot',False) yrange= kwargs.pop('yrange', [0.,numpy.amax(numpy.hstack((model_operp,track_operp)))*1.1]) xlabel= kwargs.pop('xlabel',r'$\Delta \theta_R$') ylabel= kwargs.pop('ylabel',r'$\Delta \Omega_\parallel$') bovy_plot.bovy_plot(model_adiff,model_operp,'k-',overplot=overplot, xlabel=xlabel,ylabel=ylabel,yrange=yrange,**kwargs) bovy_plot.bovy_plot(track_adiff,track_operp,'ko',overplot=True, **kwargs) return None def _determine_nTrackIterations(self,nTrackIterations): """Determine a good value for nTrackIterations based on the misalignment between stream and orbit; just based on some rough experience for now""" if not nTrackIterations is None: self.nTrackIterations= nTrackIterations return None if numpy.fabs(self.misalignment(quantity=False)) < 1./180.*numpy.pi: self.nTrackIterations= 0 elif numpy.fabs(self.misalignment(quantity=False)) >= 1./180.*numpy.pi \ and numpy.fabs(self.misalignment(quantity=False)) < 3./180.*numpy.pi: self.nTrackIterations= 1 elif numpy.fabs(self.misalignment(quantity=False)) >= 3./180.*numpy.pi: self.nTrackIterations= 2 return None def _determine_stream_track(self,nTrackChunks): """Determine the track of the stream in real space""" #Determine how much orbital time is necessary for the progenitor's orbit to cover the stream if nTrackChunks is None: #default is floor(self._deltaAngleTrack/0.15)+1 self._nTrackChunks= int(numpy.floor(self._deltaAngleTrack/0.15))+1 else: self._nTrackChunks= nTrackChunks if self._nTrackChunks < 4: self._nTrackChunks= 4 if not hasattr(self,'nInterpolatedTrackChunks'): self.nInterpolatedTrackChunks= 1001 dt= self._deltaAngleTrack\ /self._progenitor_Omega_along_dOmega self._trackts= numpy.linspace(0.,2*dt,2*self._nTrackChunks-1) #to be sure that we cover it if self._useTM: return self._determine_stream_track_TM() #Instantiate an auxiliaryTrack, which is an Orbit instance at the mean frequency of the stream, and zero angle separation wrt the progenitor; prog_stream_offset is the offset between this track and the progenitor at zero angle prog_stream_offset=\ _determine_stream_track_single(self._aA, self._progenitor, 0., #time = 0 self._progenitor_angle, self._sigMeanSign, self._dsigomeanProgDirection, lambda x: self.meanOmega(x,use_physical=False), 0.) #angle = 0 auxiliaryTrack= Orbit(prog_stream_offset[3]) if dt < 0.: self._trackts= numpy.linspace(0.,-2.*dt,2*self._nTrackChunks-1) #Flip velocities before integrating auxiliaryTrack= auxiliaryTrack.flip() auxiliaryTrack.integrate(self._trackts,self._pot) if dt < 0.: #Flip velocities again auxiliaryTrack._orb.orbit[:,1]= -auxiliaryTrack._orb.orbit[:,1] auxiliaryTrack._orb.orbit[:,2]= -auxiliaryTrack._orb.orbit[:,2] auxiliaryTrack._orb.orbit[:,4]= -auxiliaryTrack._orb.orbit[:,4] #Calculate the actions, frequencies, and angle for this auxiliary orbit acfs= self._aA.actionsFreqs(auxiliaryTrack(0.), use_physical=False) auxiliary_Omega= numpy.array([acfs[3],acfs[4],acfs[5]]).reshape(3\ ) auxiliary_Omega_along_dOmega= \ numpy.dot(auxiliary_Omega,self._dsigomeanProgDirection) #Now calculate the actions, frequencies, and angles + Jacobian for each chunk allAcfsTrack= numpy.empty((self._nTrackChunks,9)) alljacsTrack= numpy.empty((self._nTrackChunks,6,6)) allinvjacsTrack= numpy.empty((self._nTrackChunks,6,6)) thetasTrack= numpy.linspace(0.,self._deltaAngleTrack, self._nTrackChunks) ObsTrack= numpy.empty((self._nTrackChunks,6)) ObsTrackAA= numpy.empty((self._nTrackChunks,6)) detdOdJps= numpy.empty((self._nTrackChunks)) if self._multi is None: for ii in range(self._nTrackChunks): multiOut= _determine_stream_track_single(self._aA, auxiliaryTrack, self._trackts[ii]*numpy.fabs(self._progenitor_Omega_along_dOmega/auxiliary_Omega_along_dOmega), #this factor accounts for the difference in frequency between the progenitor and the auxiliary track self._progenitor_angle, self._sigMeanSign, self._dsigomeanProgDirection, lambda x: self.meanOmega(x,use_physical=False), thetasTrack[ii]) allAcfsTrack[ii,:]= multiOut[0] alljacsTrack[ii,:,:]= multiOut[1] allinvjacsTrack[ii,:,:]= multiOut[2] ObsTrack[ii,:]= multiOut[3] ObsTrackAA[ii,:]= multiOut[4] detdOdJps[ii]= multiOut[5] else: multiOut= multi.parallel_map(\ (lambda x: _determine_stream_track_single(self._aA,auxiliaryTrack, self._trackts[x]*numpy.fabs(self._progenitor_Omega_along_dOmega/auxiliary_Omega_along_dOmega), self._progenitor_angle, self._sigMeanSign, self._dsigomeanProgDirection, lambda x: self.meanOmega(x,use_physical=False), thetasTrack[x])), range(self._nTrackChunks), numcores=numpy.amin([self._nTrackChunks, multiprocessing.cpu_count(), self._multi])) for ii in range(self._nTrackChunks): allAcfsTrack[ii,:]= multiOut[ii][0] alljacsTrack[ii,:,:]= multiOut[ii][1] allinvjacsTrack[ii,:,:]= multiOut[ii][2] ObsTrack[ii,:]= multiOut[ii][3] ObsTrackAA[ii,:]= multiOut[ii][4] detdOdJps[ii]= multiOut[ii][5] #Repeat the track calculation using the previous track, to get closer to it for nn in range(self.nTrackIterations): if self._multi is None: for ii in range(self._nTrackChunks): multiOut= _determine_stream_track_single(self._aA, Orbit(ObsTrack[ii,:]), 0., self._progenitor_angle, self._sigMeanSign, self._dsigomeanProgDirection, lambda x:self.meanOmega(x,use_physical=False), thetasTrack[ii]) allAcfsTrack[ii,:]= multiOut[0] alljacsTrack[ii,:,:]= multiOut[1] allinvjacsTrack[ii,:,:]= multiOut[2] ObsTrack[ii,:]= multiOut[3] ObsTrackAA[ii,:]= multiOut[4] detdOdJps[ii]= multiOut[5] else: multiOut= multi.parallel_map(\ (lambda x: _determine_stream_track_single(self._aA,Orbit(ObsTrack[x,:]),0., self._progenitor_angle, self._sigMeanSign, self._dsigomeanProgDirection, lambda x: self.meanOmega(x,use_physical=False), thetasTrack[x])), range(self._nTrackChunks), numcores=numpy.amin([self._nTrackChunks, multiprocessing.cpu_count(), self._multi])) for ii in range(self._nTrackChunks): allAcfsTrack[ii,:]= multiOut[ii][0] alljacsTrack[ii,:,:]= multiOut[ii][1] allinvjacsTrack[ii,:,:]= multiOut[ii][2] ObsTrack[ii,:]= multiOut[ii][3] ObsTrackAA[ii,:]= multiOut[ii][4] detdOdJps[ii]= multiOut[ii][5] #Store the track self._thetasTrack= thetasTrack self._ObsTrack= ObsTrack self._ObsTrackAA= ObsTrackAA self._allAcfsTrack= allAcfsTrack self._alljacsTrack= alljacsTrack self._allinvjacsTrack= allinvjacsTrack self._detdOdJps= detdOdJps self._meandetdOdJp= numpy.mean(self._detdOdJps) self._logmeandetdOdJp= numpy.log(self._meandetdOdJp) self._calc_ObsTrackXY() return None def _calc_ObsTrackXY(self): #Also calculate _ObsTrackXY in XYZ,vXYZ coordinates self._ObsTrackXY= numpy.empty_like(self._ObsTrack) TrackX= self._ObsTrack[:,0]*numpy.cos(self._ObsTrack[:,5]) TrackY= self._ObsTrack[:,0]*numpy.sin(self._ObsTrack[:,5]) TrackZ= self._ObsTrack[:,3] TrackvX, TrackvY, TrackvZ=\ bovy_coords.cyl_to_rect_vec(self._ObsTrack[:,1], self._ObsTrack[:,2], self._ObsTrack[:,4], self._ObsTrack[:,5]) self._ObsTrackXY[:,0]= TrackX self._ObsTrackXY[:,1]= TrackY self._ObsTrackXY[:,2]= TrackZ self._ObsTrackXY[:,3]= TrackvX self._ObsTrackXY[:,4]= TrackvY self._ObsTrackXY[:,5]= TrackvZ return None def _determine_stream_track_TM(self): # With TM, can get the track in a single shot #Now calculate the actions, frequencies, and angles + Jacobian for each chunk thetasTrack= numpy.linspace(0.,self._deltaAngleTrack, self._nTrackChunks) if self._approxConstTrackFreq: alljacsTrack, allinvjacsTrack, ObsTrack, ObsTrackAA, detdOdJps= \ _determine_stream_track_TM_approxConstantTrackFreq(\ self._aAT, numpy.array([self._progenitor_jr,self._progenitor_lz, self._progenitor_jz]), self._progenitor_Omega, self._progenitor_angle, self._dOdJp, self._dOdJpInv, self._sigMeanSign, self._dsigomeanProgDirection, lambda x: self.meanOmega(x,use_physical=False), thetasTrack) #Store the track, didn't compute _allAcfsTrack self._thetasTrack= thetasTrack self._ObsTrack= ObsTrack self._ObsTrackAA= ObsTrackAA self._alljacsTrack= alljacsTrack self._allinvjacsTrack= allinvjacsTrack self._detdOdJps= detdOdJps self._meandetdOdJp= numpy.mean(self._detdOdJps) self._logmeandetdOdJp= numpy.log(self._meandetdOdJp) self._calc_ObsTrackXY() return None alljacsTrack= numpy.empty((self._nTrackChunks,6,6)) allinvjacsTrack= numpy.empty((self._nTrackChunks,6,6)) ObsTrack= numpy.empty((self._nTrackChunks,6)) ObsTrackAA= numpy.empty((self._nTrackChunks,6)) detdOdJps= numpy.empty((self._nTrackChunks)) if self._multi is None: for ii in range(self._nTrackChunks): multiOut= _determine_stream_track_TM_single(\ self._aAT, numpy.array([self._progenitor_jr,self._progenitor_lz, self._progenitor_jz]), self._progenitor_Omega, self._progenitor_angle, self._dOdJp, self._dOdJpInv, self._sigMeanSign, self._dsigomeanProgDirection, lambda x: self.meanOmega(x,use_physical=False), thetasTrack[ii]) alljacsTrack[ii,:,:]= multiOut[0] allinvjacsTrack[ii,:,:]= multiOut[1] ObsTrack[ii,:]= multiOut[2] ObsTrackAA[ii,:]= multiOut[3] detdOdJps[ii]= multiOut[4] else: multiOut= multi.parallel_map(\ (lambda x: _determine_stream_track_TM_single(\ self._aAT, numpy.array([self._progenitor_jr,self._progenitor_lz, self._progenitor_jz]), self._progenitor_Omega, self._progenitor_angle, self._dOdJp, self._dOdJpInv, self._sigMeanSign, self._dsigomeanProgDirection, lambda x: self.meanOmega(x,use_physical=False), thetasTrack[x])), range(self._nTrackChunks), numcores=numpy.amin([self._nTrackChunks, multiprocessing.cpu_count(), self._multi])) for ii in range(self._nTrackChunks): alljacsTrack[ii,:,:]= multiOut[ii][0] allinvjacsTrack[ii,:,:]= multiOut[ii][1] ObsTrack[ii,:]= multiOut[ii][2] ObsTrackAA[ii,:]= multiOut[ii][3] detdOdJps[ii]= multiOut[ii][4] #Store the track, didn't compute _allAcfsTrack self._thetasTrack= thetasTrack self._ObsTrack= ObsTrack self._ObsTrackAA= ObsTrackAA self._alljacsTrack= alljacsTrack self._allinvjacsTrack= allinvjacsTrack self._detdOdJps= detdOdJps self._meandetdOdJp= numpy.mean(self._detdOdJps) self._logmeandetdOdJp= numpy.log(self._meandetdOdJp) #Also calculate _ObsTrackXY in XYZ,vXYZ coordinates self._calc_ObsTrackXY() return None def _determine_stream_spread(self,simple=_USESIMPLE): """Determine the spread around the stream track, just sets matrices that describe the covariances""" allErrCovs= numpy.empty((self._nTrackChunks,6,6)) if self._multi is None: for ii in range(self._nTrackChunks): allErrCovs[ii]= _determine_stream_spread_single(self._sigomatrixEig, self._thetasTrack[ii], lambda x: self.sigOmega(x,use_physical=False), lambda y: self.sigangledAngle(y,simple=simple,use_physical=False), self._allinvjacsTrack[ii]) else: multiOut= multi.parallel_map(\ (lambda x: _determine_stream_spread_single(self._sigomatrixEig, self._thetasTrack[x], lambda x: self.sigOmega(x,use_physical=False), lambda y: self.sigangledAngle(y,simple=simple,use_physical=False), self._allinvjacsTrack[x])), range(self._nTrackChunks), numcores=numpy.amin([self._nTrackChunks, multiprocessing.cpu_count(), self._multi])) for ii in range(self._nTrackChunks): allErrCovs[ii]= multiOut[ii] self._allErrCovs= allErrCovs #Also propagate to XYZ coordinates allErrCovsXY= numpy.empty_like(self._allErrCovs) allErrCovsEigvalXY= numpy.empty((len(self._thetasTrack),6)) allErrCovsEigvecXY= numpy.empty_like(self._allErrCovs) eigDir= numpy.array([numpy.array([1.,0.,0.,0.,0.,0.]) for ii in range(6)]) for ii in range(self._nTrackChunks): tjac= bovy_coords.cyl_to_rect_jac(*self._ObsTrack[ii]) allErrCovsXY[ii]=\ numpy.dot(tjac,numpy.dot(self._allErrCovs[ii],tjac.T)) #Eigen decomposition for interpolation teig= numpy.linalg.eig(allErrCovsXY[ii]) #Sort them to match them up later sortIndx= numpy.argsort(teig[0]) allErrCovsEigvalXY[ii]= teig[0][sortIndx] #Make sure the eigenvectors point in the same direction for jj in range(6): if numpy.sum(eigDir[jj]*teig[1][:,sortIndx[jj]]) < 0.: teig[1][:,sortIndx[jj]]*= -1. eigDir[jj]= teig[1][:,sortIndx[jj]] allErrCovsEigvecXY[ii]= teig[1][:,sortIndx] self._allErrCovsXY= allErrCovsXY #Interpolate the allErrCovsXY covariance matrices along the interpolated track #Interpolate the eigenvalues interpAllErrCovsEigvalXY=\ [interpolate.InterpolatedUnivariateSpline(self._thetasTrack, allErrCovsEigvalXY[:,ii], k=3) for ii in range(6)] #Now build the interpolated allErrCovsXY using slerp interpolatedAllErrCovsXY= numpy.empty((len(self._interpolatedThetasTrack), 6,6)) interpolatedEigval=\ numpy.array([interpAllErrCovsEigvalXY[ii](self._interpolatedThetasTrack) for ii in range(6)]) #6,ninterp #Interpolate in chunks interpolatedEigvec= numpy.empty((len(self._interpolatedThetasTrack), 6,6)) for ii in range(self._nTrackChunks-1): slerpOmegas=\ [numpy.arccos(numpy.sum(allErrCovsEigvecXY[ii,:,jj]*allErrCovsEigvecXY[ii+1,:,jj])) for jj in range(6)] slerpts= (self._interpolatedThetasTrack-self._thetasTrack[ii])/\ (self._thetasTrack[ii+1]-self._thetasTrack[ii]) slerpIndx= (slerpts >= 0.)*(slerpts <= 1.) for jj in range(6): for kk in range(6): interpolatedEigvec[slerpIndx,kk,jj]=\ (numpy.sin((1-slerpts[slerpIndx])*slerpOmegas[jj])*allErrCovsEigvecXY[ii,kk,jj] +numpy.sin(slerpts[slerpIndx]*slerpOmegas[jj])*allErrCovsEigvecXY[ii+1,kk,jj])/numpy.sin(slerpOmegas[jj]) for ii in range(len(self._interpolatedThetasTrack)): interpolatedAllErrCovsXY[ii]=\ numpy.dot(interpolatedEigvec[ii], numpy.dot(numpy.diag(interpolatedEigval[:,ii]), interpolatedEigvec[ii].T)) self._interpolatedAllErrCovsXY= interpolatedAllErrCovsXY #Also interpolate in l and b coordinates self._determine_stream_spreadLB(simple=simple) return None def _determine_stream_spreadLB(self,simple=_USESIMPLE, ro=None,vo=None, R0=None,Zsun=None,vsun=None): """Determine the spread in the stream in observable coordinates""" if not hasattr(self,'_allErrCovs'): self._determine_stream_spread(simple=simple) if ro is None: ro= self._ro if vo is None: vo= self._vo if R0 is None: R0= self._R0 if Zsun is None: Zsun= self._Zsun if vsun is None: vsun= self._vsun allErrCovsLB= numpy.empty_like(self._allErrCovs) obs= [R0,0.,Zsun] obs.extend(vsun) obskwargs= {} obskwargs['ro']= ro obskwargs['vo']= vo obskwargs['obs']= obs self._ErrCovsLBScale= [180.,90., self._progenitor.dist(**obskwargs), numpy.fabs(self._progenitor.vlos(**obskwargs)), numpy.sqrt(self._progenitor.pmll(**obskwargs)**2. +self._progenitor.pmbb(**obskwargs)**2.), numpy.sqrt(self._progenitor.pmll(**obskwargs)**2. +self._progenitor.pmbb(**obskwargs)**2.)] allErrCovsEigvalLB= numpy.empty((len(self._thetasTrack),6)) allErrCovsEigvecLB= numpy.empty_like(self._allErrCovs) eigDir= numpy.array([numpy.array([1.,0.,0.,0.,0.,0.]) for ii in range(6)]) for ii in range(self._nTrackChunks): tjacXY= bovy_coords.galcenrect_to_XYZ_jac(*self._ObsTrackXY[ii]) tjacLB= bovy_coords.lbd_to_XYZ_jac(*self._ObsTrackLB[ii], degree=True) tjacLB[:3,:]/= ro tjacLB[3:,:]/= vo for jj in range(6): tjacLB[:,jj]*= self._ErrCovsLBScale[jj] tjac= numpy.dot(numpy.linalg.inv(tjacLB),tjacXY) allErrCovsLB[ii]=\ numpy.dot(tjac,numpy.dot(self._allErrCovsXY[ii],tjac.T)) #Eigen decomposition for interpolation teig= numpy.linalg.eig(allErrCovsLB[ii]) #Sort them to match them up later sortIndx= numpy.argsort(teig[0]) allErrCovsEigvalLB[ii]= teig[0][sortIndx] #Make sure the eigenvectors point in the same direction for jj in range(6): if numpy.sum(eigDir[jj]*teig[1][:,sortIndx[jj]]) < 0.: teig[1][:,sortIndx[jj]]*= -1. eigDir[jj]= teig[1][:,sortIndx[jj]] allErrCovsEigvecLB[ii]= teig[1][:,sortIndx] self._allErrCovsLBUnscaled= allErrCovsLB #Interpolate the allErrCovsLB covariance matrices along the interpolated track #Interpolate the eigenvalues interpAllErrCovsEigvalLB=\ [interpolate.InterpolatedUnivariateSpline(self._thetasTrack, allErrCovsEigvalLB[:,ii], k=3) for ii in range(6)] #Now build the interpolated allErrCovsXY using slerp interpolatedAllErrCovsLB= numpy.empty((len(self._interpolatedThetasTrack), 6,6)) interpolatedEigval=\ numpy.array([interpAllErrCovsEigvalLB[ii](self._interpolatedThetasTrack) for ii in range(6)]) #6,ninterp #Interpolate in chunks interpolatedEigvec= numpy.empty((len(self._interpolatedThetasTrack), 6,6)) for ii in range(self._nTrackChunks-1): slerpOmegas=\ [numpy.arccos(numpy.sum(allErrCovsEigvecLB[ii,:,jj]*allErrCovsEigvecLB[ii+1,:,jj])) for jj in range(6)] slerpts= (self._interpolatedThetasTrack-self._thetasTrack[ii])/\ (self._thetasTrack[ii+1]-self._thetasTrack[ii]) slerpIndx= (slerpts >= 0.)*(slerpts <= 1.) for jj in range(6): for kk in range(6): interpolatedEigvec[slerpIndx,kk,jj]=\ (numpy.sin((1-slerpts[slerpIndx])*slerpOmegas[jj])*allErrCovsEigvecLB[ii,kk,jj] +numpy.sin(slerpts[slerpIndx]*slerpOmegas[jj])*allErrCovsEigvecLB[ii+1,kk,jj])/numpy.sin(slerpOmegas[jj]) for ii in range(len(self._interpolatedThetasTrack)): interpolatedAllErrCovsLB[ii]=\ numpy.dot(interpolatedEigvec[ii], numpy.dot(numpy.diag(interpolatedEigval[:,ii]), interpolatedEigvec[ii].T)) self._interpolatedAllErrCovsLBUnscaled= interpolatedAllErrCovsLB #Also calculate the (l,b,..) -> (X,Y,..) Jacobian at all of the interpolated and not interpolated points trackLogDetJacLB= numpy.empty_like(self._thetasTrack) interpolatedTrackLogDetJacLB=\ numpy.empty_like(self._interpolatedThetasTrack) for ii in range(self._nTrackChunks): tjacLB= bovy_coords.lbd_to_XYZ_jac(*self._ObsTrackLB[ii], degree=True) trackLogDetJacLB[ii]= numpy.log(numpy.linalg.det(tjacLB)) self._trackLogDetJacLB= trackLogDetJacLB for ii in range(len(self._interpolatedThetasTrack)): tjacLB=\ bovy_coords.lbd_to_XYZ_jac(*self._interpolatedObsTrackLB[ii], degree=True) interpolatedTrackLogDetJacLB[ii]=\ numpy.log(numpy.linalg.det(tjacLB)) self._interpolatedTrackLogDetJacLB= interpolatedTrackLogDetJacLB return None def _interpolate_stream_track(self): """Build interpolations of the stream track""" if hasattr(self,'_interpolatedThetasTrack'): return None #Already did this TrackX= self._ObsTrack[:,0]*numpy.cos(self._ObsTrack[:,5]) TrackY= self._ObsTrack[:,0]*numpy.sin(self._ObsTrack[:,5]) TrackZ= self._ObsTrack[:,3] TrackvX, TrackvY, TrackvZ=\ bovy_coords.cyl_to_rect_vec(self._ObsTrack[:,1], self._ObsTrack[:,2], self._ObsTrack[:,4], self._ObsTrack[:,5]) #Interpolate self._interpTrackX=\ interpolate.InterpolatedUnivariateSpline(self._thetasTrack, TrackX,k=3) self._interpTrackY=\ interpolate.InterpolatedUnivariateSpline(self._thetasTrack, TrackY,k=3) self._interpTrackZ=\ interpolate.InterpolatedUnivariateSpline(self._thetasTrack, TrackZ,k=3) self._interpTrackvX=\ interpolate.InterpolatedUnivariateSpline(self._thetasTrack, TrackvX,k=3) self._interpTrackvY=\ interpolate.InterpolatedUnivariateSpline(self._thetasTrack, TrackvY,k=3) self._interpTrackvZ=\ interpolate.InterpolatedUnivariateSpline(self._thetasTrack, TrackvZ,k=3) #Now store an interpolated version of the stream track self._interpolatedThetasTrack=\ numpy.linspace(0.,self._deltaAngleTrack, self.nInterpolatedTrackChunks) self._interpolatedObsTrackXY= numpy.empty((len(self._interpolatedThetasTrack),6)) self._interpolatedObsTrackXY[:,0]=\ self._interpTrackX(self._interpolatedThetasTrack) self._interpolatedObsTrackXY[:,1]=\ self._interpTrackY(self._interpolatedThetasTrack) self._interpolatedObsTrackXY[:,2]=\ self._interpTrackZ(self._interpolatedThetasTrack) self._interpolatedObsTrackXY[:,3]=\ self._interpTrackvX(self._interpolatedThetasTrack) self._interpolatedObsTrackXY[:,4]=\ self._interpTrackvY(self._interpolatedThetasTrack) self._interpolatedObsTrackXY[:,5]=\ self._interpTrackvZ(self._interpolatedThetasTrack) #Also in cylindrical coordinates self._interpolatedObsTrack= \ numpy.empty((len(self._interpolatedThetasTrack),6)) tR,tphi,tZ= bovy_coords.rect_to_cyl(self._interpolatedObsTrackXY[:,0], self._interpolatedObsTrackXY[:,1], self._interpolatedObsTrackXY[:,2]) tvR,tvT,tvZ=\ bovy_coords.rect_to_cyl_vec(self._interpolatedObsTrackXY[:,3], self._interpolatedObsTrackXY[:,4], self._interpolatedObsTrackXY[:,5], tR,tphi,tZ,cyl=True) self._interpolatedObsTrack[:,0]= tR self._interpolatedObsTrack[:,1]= tvR self._interpolatedObsTrack[:,2]= tvT self._interpolatedObsTrack[:,3]= tZ self._interpolatedObsTrack[:,4]= tvZ self._interpolatedObsTrack[:,5]= tphi return None def _interpolate_stream_track_aA(self): """Build interpolations of the stream track in action-angle coordinates""" if hasattr(self,'_interpolatedObsTrackAA'): return None #Already did this #Calculate 1D meanOmega on a fine grid in angle and interpolate if not hasattr(self,'_interpolatedThetasTrack'): self._interpolate_stream_track() dmOs= numpy.array([self.meanOmega(da,oned=True,use_physical=False) for da in self._interpolatedThetasTrack]) self._interpTrackAAdmeanOmegaOneD=\ interpolate.InterpolatedUnivariateSpline(\ self._interpolatedThetasTrack,dmOs,k=3) #Build the interpolated AA self._interpolatedObsTrackAA=\ numpy.empty((len(self._interpolatedThetasTrack),6)) for ii in range(len(self._interpolatedThetasTrack)): self._interpolatedObsTrackAA[ii,:3]=\ self._progenitor_Omega+dmOs[ii]*self._dsigomeanProgDirection\ *self._sigMeanSign self._interpolatedObsTrackAA[ii,3:]=\ self._progenitor_angle+self._interpolatedThetasTrack[ii]\ *self._dsigomeanProgDirection*self._sigMeanSign self._interpolatedObsTrackAA[ii,3:]=\ numpy.mod(self._interpolatedObsTrackAA[ii,3:],2.*numpy.pi) return None def calc_stream_lb(self, vo=None,ro=None, R0=None,Zsun=None,vsun=None): """ NAME: calc_stream_lb PURPOSE: convert the stream track to observational coordinates and store INPUT: Coordinate transformation inputs (all default to the instance-wide values): vo= circular velocity to normalize velocities with ro= Galactocentric radius to normalize positions with R0= Galactocentric radius of the Sun (kpc) Zsun= Sun's height above the plane (kpc) vsun= Sun's motion in cylindrical coordinates (vR positive away from center) OUTPUT: (none) HISTORY: 2013-12-02 - Written - Bovy (IAS) """ if vo is None: vo= self._vo if ro is None: ro= self._ro if R0 is None: R0= self._R0 if Zsun is None: Zsun= self._Zsun if vsun is None: vsun= self._vsun self._ObsTrackLB= numpy.empty_like(self._ObsTrack) XYZ= bovy_coords.galcencyl_to_XYZ(self._ObsTrack[:,0]*ro, self._ObsTrack[:,5], self._ObsTrack[:,3]*ro, Xsun=R0,Zsun=Zsun).T vXYZ= bovy_coords.galcencyl_to_vxvyvz(self._ObsTrack[:,1]*vo, self._ObsTrack[:,2]*vo, self._ObsTrack[:,4]*vo, self._ObsTrack[:,5], vsun=vsun,Xsun=R0,Zsun=Zsun).T slbd=bovy_coords.XYZ_to_lbd(XYZ[0],XYZ[1],XYZ[2], degree=True) svlbd= bovy_coords.vxvyvz_to_vrpmllpmbb(vXYZ[0],vXYZ[1],vXYZ[2], slbd[:,0],slbd[:,1],slbd[:,2], degree=True) self._ObsTrackLB[:,0]= slbd[:,0] self._ObsTrackLB[:,1]= slbd[:,1] self._ObsTrackLB[:,2]= slbd[:,2] self._ObsTrackLB[:,3]= svlbd[:,0] self._ObsTrackLB[:,4]= svlbd[:,1] self._ObsTrackLB[:,5]= svlbd[:,2] if hasattr(self,'_interpolatedObsTrackXY'): #Do the same for the interpolated track self._interpolatedObsTrackLB=\ numpy.empty_like(self._interpolatedObsTrackXY) XYZ=\ bovy_coords.galcenrect_to_XYZ(\ self._interpolatedObsTrackXY[:,0]*ro, self._interpolatedObsTrackXY[:,1]*ro, self._interpolatedObsTrackXY[:,2]*ro, Xsun=R0,Zsun=Zsun).T vXYZ=\ bovy_coords.galcenrect_to_vxvyvz(\ self._interpolatedObsTrackXY[:,3]*vo, self._interpolatedObsTrackXY[:,4]*vo, self._interpolatedObsTrackXY[:,5]*vo, vsun=vsun,Xsun=R0,Zsun=Zsun).T slbd=bovy_coords.XYZ_to_lbd(XYZ[0],XYZ[1],XYZ[2], degree=True) svlbd= bovy_coords.vxvyvz_to_vrpmllpmbb(vXYZ[0],vXYZ[1],vXYZ[2], slbd[:,0],slbd[:,1], slbd[:,2], degree=True) self._interpolatedObsTrackLB[:,0]= slbd[:,0] self._interpolatedObsTrackLB[:,1]= slbd[:,1] self._interpolatedObsTrackLB[:,2]= slbd[:,2] self._interpolatedObsTrackLB[:,3]= svlbd[:,0] self._interpolatedObsTrackLB[:,4]= svlbd[:,1] self._interpolatedObsTrackLB[:,5]= svlbd[:,2] if hasattr(self,'_allErrCovsLBUnscaled'): #Re-calculate this self._determine_stream_spreadLB(simple=_USESIMPLE, vo=vo,ro=ro, R0=R0,Zsun=Zsun,vsun=vsun) return None def _find_closest_trackpoint(self,R,vR,vT,z,vz,phi,interp=True,xy=False, usev=False): """For backward compatibility""" return self.find_closest_trackpoint(R,vR,vT,z,vz,phi, interp=interp,xy=xy, usev=usev) def find_closest_trackpoint(self,R,vR,vT,z,vz,phi,interp=True,xy=False, usev=False): """ NAME: find_closest_trackpoint PURPOSE: find the closest point on the stream track to a given point INPUT: R,vR,vT,z,vz,phi - phase-space coordinates of the given point interp= (True), if True, return the index of the interpolated track xy= (False) if True, input is X,Y,Z,vX,vY,vZ in Galactocentric rectangular coordinates; if xy, some coordinates may be missing (given as None) and they will not be used usev= (False) if True, also use velocities to find the closest point OUTPUT: index into the track of the closest track point HISTORY: 2013-12-04 - Written - Bovy (IAS) """ if xy: X= R Y= vR Z= vT else: X= R*numpy.cos(phi) Y= R*numpy.sin(phi) Z= z if xy and usev: vX= z vY= vz vZ= phi elif usev: vX= vR*numpy.cos(phi)-vT*numpy.sin(phi) vY= vR*numpy.sin(phi)+vT*numpy.cos(phi) vZ= vz present= [not X is None,not Y is None,not Z is None] if usev: present.extend([not vX is None,not vY is None,not vZ is None]) present= numpy.array(present,dtype='float') if X is None: X= 0. if Y is None: Y= 0. if Z is None: Z= 0. if usev and vX is None: vX= 0. if usev and vY is None: vY= 0. if usev and vZ is None: vZ= 0. if interp: dist2= present[0]*(X-self._interpolatedObsTrackXY[:,0])**2.\ +present[1]*(Y-self._interpolatedObsTrackXY[:,1])**2.\ +present[2]*(Z-self._interpolatedObsTrackXY[:,2])**2. if usev: dist2+= present[3]*(vX-self._interpolatedObsTrackXY[:,3])**2.\ +present[4]*(vY-self._interpolatedObsTrackXY[:,4])**2.\ +present[5]*(vZ-self._interpolatedObsTrackXY[:,5])**2. else: dist2= present[0]*(X-self._ObsTrackXY[:,0])**2.\ +present[1]*(Y-self._ObsTrackXY[:,1])**2.\ +present[2]*(Z-self._ObsTrackXY[:,2])**2. if usev: dist2+= present[3]*(vX-self._ObsTrackXY[:,3])**2.\ +present[4]*(vY-self._ObsTrackXY[:,4])**2.\ +present[5]*(vZ-self._ObsTrackXY[:,5])**2. return numpy.argmin(dist2) def _find_closest_trackpointLB(self,l,b,D,vlos,pmll,pmbb,interp=True, usev=False): return self.find_closest_trackpointLB(l,b,D,vlos,pmll,pmbb, interp=interp, usev=usev) def find_closest_trackpointLB(self,l,b,D,vlos,pmll,pmbb,interp=True, usev=False): """ NAME: find_closest_trackpointLB PURPOSE: find the closest point on the stream track to a given point in (l,b,...) coordinates INPUT: l,b,D,vlos,pmll,pmbb- coordinates in (deg,deg,kpc,km/s,mas/yr,mas/yr) interp= (True) if True, return the closest index on the interpolated track usev= (False) if True, also use the velocity components (default is to only use the positions) OUTPUT: index of closest track point on the interpolated or not-interpolated track HISTORY: 2013-12-17- Written - Bovy (IAS) """ if interp: nTrackPoints= len(self._interpolatedThetasTrack) else: nTrackPoints= len(self._thetasTrack) if l is None: l= 0. trackL= numpy.zeros(nTrackPoints) elif interp: trackL= self._interpolatedObsTrackLB[:,0] else: trackL= self._ObsTrackLB[:,0] if b is None: b= 0. trackB= numpy.zeros(nTrackPoints) elif interp: trackB= self._interpolatedObsTrackLB[:,1] else: trackB= self._ObsTrackLB[:,1] if D is None: D= 1. trackD= numpy.ones(nTrackPoints) elif interp: trackD= self._interpolatedObsTrackLB[:,2] else: trackD= self._ObsTrackLB[:,2] if usev: if vlos is None: vlos= 0. trackVlos= numpy.zeros(nTrackPoints) elif interp: trackVlos= self._interpolatedObsTrackLB[:,3] else: trackVlos= self._ObsTrackLB[:,3] if pmll is None: pmll= 0. trackPmll= numpy.zeros(nTrackPoints) elif interp: trackPmll= self._interpolatedObsTrackLB[:,4] else: trackPmll= self._ObsTrackLB[:,4] if pmbb is None: pmbb= 0. trackPmbb= numpy.zeros(nTrackPoints) elif interp: trackPmbb= self._interpolatedObsTrackLB[:,5] else: trackPmbb= self._ObsTrackLB[:,5] #Calculate rectangular coordinates XYZ= bovy_coords.lbd_to_XYZ(l,b,D,degree=True) trackXYZ= bovy_coords.lbd_to_XYZ(trackL,trackB,trackD,degree=True) if usev: vxvyvz= bovy_coords.vrpmllpmbb_to_vxvyvz(vlos,pmll,pmbb, XYZ[0],XYZ[1],XYZ[2], XYZ=True) trackvxvyvz= bovy_coords.vrpmllpmbb_to_vxvyvz(trackVlos,trackPmll, trackPmbb, trackXYZ[:,0], trackXYZ[:,1], trackXYZ[:,2], XYZ=True) #Calculate distance dist2= (XYZ[0]-trackXYZ[:,0])**2.\ +(XYZ[1]-trackXYZ[:,1])**2.\ +(XYZ[2]-trackXYZ[:,2])**2. if usev: dist2+= (vxvyvz[0]-trackvxvyvz[:,0])**2.\ +(vxvyvz[1]-trackvxvyvz[:,1])**2.\ +(vxvyvz[2]-trackvxvyvz[:,2])**2. return numpy.argmin(dist2) def _find_closest_trackpointaA(self,Or,Op,Oz,ar,ap,az,interp=True): """ NAME: _find_closest_trackpointaA PURPOSE: find the closest point on the stream track to a given point in frequency-angle coordinates INPUT: Or,Op,Oz,ar,ap,az - phase-space coordinates of the given point interp= (True), if True, return the index of the interpolated track OUTPUT: index into the track of the closest track point HISTORY: 2013-12-22 - Written - Bovy (IAS) """ #Calculate angle offset along the stream parallel to the stream track, # finding first the angle among a few wraps where the point is # closest to the parallel track and then the closest trackpoint to that # point da= numpy.stack(\ numpy.meshgrid(_TWOPIWRAPS+ar-self._progenitor_angle[0], _TWOPIWRAPS+ap-self._progenitor_angle[1], _TWOPIWRAPS+az-self._progenitor_angle[2], indexing='xy')).T.reshape((len(_TWOPIWRAPS)**3,3)) dapar= self._sigMeanSign*numpy.dot(da[numpy.argmin(numpy.linalg.norm(\ numpy.cross(da,self._dsigomeanProgDirection),axis=1))], self._dsigomeanProgDirection) if interp: dist= numpy.fabs(dapar-self._interpolatedThetasTrack) else: dist= numpy.fabs(dapar-self._thetasTrack) return numpy.argmin(dist) #########DISTRIBUTION AS A FUNCTION OF ANGLE ALONG THE STREAM################## def pOparapar(self,Opar,apar,tdisrupt=None): """ NAME: pOparapar PURPOSE: return the probability of a given parallel (frequency,angle) offset pair INPUT: Opar - parallel frequency offset (array) (can be Quantity) apar - parallel angle offset along the stream (scalar) (can be Quantity) OUTPUT: p(Opar,apar) HISTORY: 2015-12-07 - Written - Bovy (UofT) """ if _APY_LOADED and isinstance(Opar,units.Quantity): Opar= Opar.to(1/units.Gyr).value\ /bovy_conversion.freq_in_Gyr(self._vo,self._ro) if _APY_LOADED and isinstance(apar,units.Quantity): apar= apar.to(units.rad).value if tdisrupt is None: tdisrupt= self._tdisrupt if isinstance(Opar,(int,float,numpy.float32,numpy.float64)): Opar= numpy.array([Opar]) out= numpy.zeros(len(Opar)) # Compute ts ts= apar/Opar # Evaluate out[(ts < tdisrupt)*(ts >= 0.)]=\ numpy.exp(-0.5*(Opar[(ts < tdisrupt)*(ts >= 0.)]-self._meandO)**2.\ /self._sortedSigOEig[2])/\ numpy.sqrt(self._sortedSigOEig[2]) return out def density_par(self,dangle,coord='apar',tdisrupt=None, **kwargs): """ NAME: density_par PURPOSE: calculate the density as a function of a parallel coordinate INPUT: dangle - parallel angle offset for this coordinate value coord - coordinate to return the density in ('apar' [default], 'll','ra','customra','phi') OUTPUT: density(angle) HISTORY: 2015-11-17 - Written - Bovy (UofT) """ if coord.lower() != 'apar': # Need to compute the Jacobian for this coordinate value ddangle= dangle+10.**-7. ddangle-= dangle if coord.lower() == 'phi': phi_h= bovy_coords.rect_to_cyl(\ self._interpTrackX(dangle+ddangle), self._interpTrackY(dangle+ddangle), self._interpTrackZ(dangle+ddangle)) phi= bovy_coords.rect_to_cyl(\ self._interpTrackX(dangle), self._interpTrackY(dangle), self._interpTrackZ(dangle)) jac= numpy.fabs(phi_h[1]-phi[1])/ddangle elif coord.lower() == 'll' or coord.lower() == 'ra' \ or coord.lower() == 'customra': XYZ_h= bovy_coords.galcenrect_to_XYZ(\ self._interpTrackX(dangle+ddangle)*self._ro, self._interpTrackY(dangle+ddangle)*self._ro, self._interpTrackZ(dangle+ddangle)*self._ro, Xsun=self._R0,Zsun=self._Zsun) lbd_h= bovy_coords.XYZ_to_lbd(XYZ_h[0],XYZ_h[1],XYZ_h[2], degree=True) XYZ= bovy_coords.galcenrect_to_XYZ(\ self._interpTrackX(dangle)*self._ro, self._interpTrackY(dangle)*self._ro, self._interpTrackZ(dangle)*self._ro, Xsun=self._R0,Zsun=self._Zsun) lbd= bovy_coords.XYZ_to_lbd(XYZ[0],XYZ[1],XYZ[2], degree=True) if coord.lower() == 'll': jac= numpy.fabs(lbd_h[0]-lbd[0])/ddangle else: radec_h= bovy_coords.lb_to_radec(lbd_h[0], lbd_h[1], degree=True) radec= bovy_coords.lb_to_radec(lbd[0], lbd[1], degree=True) if coord.lower() == 'ra': jac= numpy.fabs(radec_h[0]-radec[0])/ddangle else: xieta_h= bovy_coords.radec_to_custom(\ radec_h[0],radec_h[1],T=self._custom_transform, degree=True) xieta= bovy_coords.radec_to_custom(\ radec[0],radec[1],T=self._custom_transform, degree=True) jac= numpy.fabs(xieta_h[0]-xieta[0])/ddangle else: raise ValueError('Coordinate input %s not supported by density_par' % coord) else: jac= 1. return self._density_par(dangle,tdisrupt=tdisrupt,**kwargs)/jac def _density_par(self,dangle,tdisrupt=None): """The raw density as a function of parallel angle""" if tdisrupt is None: tdisrupt= self._tdisrupt dOmin= dangle/tdisrupt # Normalize to 1 close to progenitor return 0.5\ *(1.+special.erf((self._meandO-dOmin)\ /numpy.sqrt(2.*self._sortedSigOEig[2]))) def length(self,threshold=0.2,phys=False,ang=False,tdisrupt=None, **kwargs): """ NAME: length PURPOSE: calculate the length of the stream INPUT: threshold - threshold down from the density near the progenitor at which to define the 'end' of the stream phys= (False) if True, return the length in physical kpc ang= (False) if True, return the length in sky angular arc length in degree coord - coordinate to return the density in ('apar' [default], 'll','ra','customra','phi') OUTPUT: length (rad for parallel angle; kpc for physical length; deg for sky arc length) HISTORY: 2015-12-22 - Written - Bovy (UofT) """ peak_dens= self.density_par(0.1,tdisrupt=tdisrupt,**kwargs) # assume that this is the peak try: result=\ optimize.brentq(lambda x: self.density_par(x, tdisrupt=tdisrupt, **kwargs)\ -peak_dens*threshold, 0.1,self._deltaAngleTrack) except RuntimeError: #pragma: no cover raise RuntimeError('Length could not be returned, because length method failed to find the threshold value') except ValueError: raise ValueError('Length could not be returned, because length method failed to initialize') if phys: # Need to now integrate length dXda= self._interpTrackX.derivative() dYda= self._interpTrackY.derivative() dZda= self._interpTrackZ.derivative() result= integrate.quad(lambda da: numpy.sqrt(dXda(da)**2.\ +dYda(da)**2.\ +dZda(da)**2.), 0.,result)[0]*self._ro elif ang: # Need to now integrate length if numpy.median(numpy.roll(self._interpolatedObsTrackLB[:,0],-1) -self._interpolatedObsTrackLB[:,0]) > 0.: ll= dePeriod(self._interpolatedObsTrackLB[:,0][:,numpy.newaxis].T*numpy.pi/180.).T*180./numpy.pi else: ll= dePeriod(self._interpolatedObsTrackLB[::-1,0][:,numpy.newaxis].T*numpy.pi/180.).T[::-1]*180./numpy.pi if numpy.median(numpy.roll(self._interpolatedObsTrackLB[:,1],-1) -self._interpolatedObsTrackLB[:,1]) > 0.: bb= dePeriod(self._interpolatedObsTrackLB[:,1][:,numpy.newaxis].T*numpy.pi/180.).T*180./numpy.pi else: bb= dePeriod(self._interpolatedObsTrackLB[::-1,1][:,numpy.newaxis].T*numpy.pi/180.).T[::-1]*180./numpy.pi dlda= interpolate.InterpolatedUnivariateSpline(\ self._interpolatedThetasTrack,ll,k=3).derivative() dbda= interpolate.InterpolatedUnivariateSpline(\ self._interpolatedThetasTrack,bb,k=3).derivative() result= integrate.quad(lambda da: numpy.sqrt(dlda(da)**2.\ +dbda(da)**2.), 0.,result)[0] return result @physical_conversion('frequency',pop=True) def meanOmega(self,dangle,oned=False,offset_sign=None, tdisrupt=None): """ NAME: meanOmega PURPOSE: calculate the mean frequency as a function of angle, assuming a uniform time distribution up to a maximum time INPUT: dangle - angle offset oned= (False) if True, return the 1D offset from the progenitor (along the direction of disruption) offset_sign= sign of the frequency offset (shouldn't be set) OUTPUT: mean Omega HISTORY: 2013-12-01 - Written - Bovy (IAS) """ if offset_sign is None: offset_sign= self._sigMeanSign if tdisrupt is None: tdisrupt= self._tdisrupt dOmin= dangle/tdisrupt meandO= self._meandO dO1D= ((numpy.sqrt(2./numpy.pi)*numpy.sqrt(self._sortedSigOEig[2])\ *numpy.exp(-0.5*(meandO-dOmin)**2.\ /self._sortedSigOEig[2])/ (1.+special.erf((meandO-dOmin)\ /numpy.sqrt(2.*self._sortedSigOEig[2]))))\ +meandO) if oned: return dO1D else: return self._progenitor_Omega+dO1D*self._dsigomeanProgDirection\ *offset_sign @physical_conversion('frequency',pop=True) def sigOmega(self,dangle): """ NAME: sigmaOmega PURPOSE: calculate the 1D sigma in frequency as a function of angle, assuming a uniform time distribution up to a maximum time INPUT: dangle - angle offset OUTPUT: sigma Omega HISTORY: 2013-12-05 - Written - Bovy (IAS) """ dOmin= dangle/self._tdisrupt meandO= self._meandO sO1D2= ((numpy.sqrt(2./numpy.pi)*numpy.sqrt(self._sortedSigOEig[2])\ *(meandO+dOmin)\ *numpy.exp(-0.5*(meandO-dOmin)**2.\ /self._sortedSigOEig[2])/ (1.+special.erf((meandO-dOmin)\ /numpy.sqrt(2.*self._sortedSigOEig[2]))))\ +meandO**2.+self._sortedSigOEig[2]) mO= self.meanOmega(dangle,oned=True,use_physical=False) return numpy.sqrt(sO1D2-mO**2.) def ptdAngle(self,t,dangle): """ NAME: ptdangle PURPOSE: return the probability of a given stripping time at a given angle along the stream INPUT: t - stripping time dangle - angle offset along the stream OUTPUT: p(td|dangle) HISTORY: 2013-12-05 - Written - Bovy (IAS) """ if isinstance(t,(int,float,numpy.float32,numpy.float64)): t= numpy.array([t]) out= numpy.zeros(len(t)) if t > 0.: dO= dangle/t[t < self._tdisrupt] else: return 0. #p(t|a) = \int dO p(O,t|a) = \int dO p(t|O,a) p(O|a) = \int dO delta (t-a/O)p(O|a) = O*2/a p(O|a); p(O|a) = \int dt p(a|O,t) p(O)p(t) = 1/O p(O) out[t < self._tdisrupt]=\ dO**2./dangle*numpy.exp(-0.5*(dO-self._meandO)**2.\ /self._sortedSigOEig[2])/\ numpy.sqrt(self._sortedSigOEig[2]) return out @physical_conversion('time',pop=True) def meantdAngle(self,dangle): """ NAME: meantdAngle PURPOSE: calculate the mean stripping time at a given angle INPUT: dangle - angle offset along the stream OUTPUT: mean stripping time at this dangle HISTORY: 2013-12-05 - Written - Bovy (IAS) """ Tlow= dangle/(self._meandO+3.*numpy.sqrt(self._sortedSigOEig[2])) Thigh= dangle/(self._meandO-3.*numpy.sqrt(self._sortedSigOEig[2])) num= integrate.quad(lambda x: x*self.ptdAngle(x,dangle), Tlow,Thigh)[0] denom= integrate.quad(self.ptdAngle,Tlow,Thigh,(dangle,))[0] if denom == 0.: return self._tdisrupt elif numpy.isnan(denom): return 0. else: return num/denom @physical_conversion('time',pop=True) def sigtdAngle(self,dangle): """ NAME: sigtdAngle PURPOSE: calculate the dispersion in the stripping times at a given angle INPUT: dangle - angle offset along the stream OUTPUT: dispersion in the stripping times at this angle HISTORY: 2013-12-05 - Written - Bovy (IAS) """ Tlow= dangle/(self._meandO+3.*numpy.sqrt(self._sortedSigOEig[2])) Thigh= dangle/(self._meandO-3.*numpy.sqrt(self._sortedSigOEig[2])) numsig2= integrate.quad(lambda x: x**2.*self.ptdAngle(x,dangle), Tlow,Thigh)[0] nummean= integrate.quad(lambda x: x*self.ptdAngle(x,dangle), Tlow,Thigh)[0] denom= integrate.quad(self.ptdAngle,Tlow,Thigh,(dangle,))[0] if denom == 0.: return numpy.nan else: return numpy.sqrt(numsig2/denom-(nummean/denom)**2.) def pangledAngle(self,angleperp,dangle,smallest=False): """ NAME: pangledAngle PURPOSE: return the probability of a given perpendicular angle at a given angle along the stream INPUT: angleperp - perpendicular angle dangle - angle offset along the stream smallest= (False) calculate for smallest eigenvalue direction rather than for middle OUTPUT: p(angle_perp|dangle) HISTORY: 2013-12-06 - Written - Bovy (IAS) """ if isinstance(angleperp,(int,float,numpy.float32,numpy.float64)): angleperp= numpy.array([angleperp]) out= numpy.zeros(len(angleperp)) out= numpy.array([\ integrate.quad(self._pangledAnglet,0.,self._tdisrupt, (ap,dangle,smallest))[0] for ap in angleperp]) return out @physical_conversion('angle',pop=True) def meanangledAngle(self,dangle,smallest=False): """ NAME: meanangledAngle PURPOSE: calculate the mean perpendicular angle at a given angle INPUT: dangle - angle offset along the stream smallest= (False) calculate for smallest eigenvalue direction rather than for middle OUTPUT: mean perpendicular angle HISTORY: 2013-12-06 - Written - Bovy (IAS) """ if smallest: eigIndx= 0 else: eigIndx= 1 aplow= numpy.amax([numpy.sqrt(self._sortedSigOEig[eigIndx])\ *self._tdisrupt*5., self._sigangle]) num= integrate.quad(lambda x: x*self.pangledAngle(x,dangle,smallest), aplow,-aplow)[0] denom= integrate.quad(self.pangledAngle,aplow,-aplow, (dangle,smallest))[0] if denom == 0.: return numpy.nan else: return num/denom @physical_conversion('angle',pop=True) def sigangledAngle(self,dangle,assumeZeroMean=True,smallest=False, simple=False): """ NAME: sigangledAngle PURPOSE: calculate the dispersion in the perpendicular angle at a given angle INPUT: dangle - angle offset along the stream assumeZeroMean= (True) if True, assume that the mean is zero (should be) smallest= (False) calculate for smallest eigenvalue direction rather than for middle simple= (False), if True, return an even simpler estimate OUTPUT: dispersion in the perpendicular angle at this angle HISTORY: 2013-12-06 - Written - Bovy (IAS) """ if smallest: eigIndx= 0 else: eigIndx= 1 if simple: dt= self.meantdAngle(dangle,use_physical=False) return numpy.sqrt(self._sigangle2 +self._sortedSigOEig[eigIndx]*dt**2.) aplow= numpy.amax([numpy.sqrt(self._sortedSigOEig[eigIndx])*self._tdisrupt*5., self._sigangle]) numsig2= integrate.quad(lambda x: x**2.*self.pangledAngle(x,dangle), aplow,-aplow)[0] if not assumeZeroMean: nummean= integrate.quad(lambda x: x*self.pangledAngle(x,dangle), aplow,-aplow)[0] else: nummean= 0. denom= integrate.quad(self.pangledAngle,aplow,-aplow,(dangle,))[0] if denom == 0.: return numpy.nan else: return numpy.sqrt(numsig2/denom-(nummean/denom)**2.) def _pangledAnglet(self,t,angleperp,dangle,smallest): """p(angle_perp|angle_par,time)""" if smallest: eigIndx= 0 else: eigIndx= 1 if isinstance(angleperp,(int,float,numpy.float32,numpy.float64)): angleperp= numpy.array([angleperp]) t= numpy.array([t]) out= numpy.zeros_like(angleperp) tindx= t < self._tdisrupt out[tindx]=\ numpy.exp(-0.5*angleperp[tindx]**2.\ /(t[tindx]**2.*self._sortedSigOEig[eigIndx]+self._sigangle2))/\ numpy.sqrt(t[tindx]**2.*self._sortedSigOEig[eigIndx]+self._sigangle2)\ *self.ptdAngle(t[t < self._tdisrupt],dangle) return out ################APPROXIMATE FREQUENCY-ANGLE TRANSFORMATION##################### def _approxaA(self,R,vR,vT,z,vz,phi,interp=True,cindx=None): """ NAME: _approxaA PURPOSE: return action-angle coordinates for a point based on the linear approximation around the stream track INPUT: R,vR,vT,z,vz,phi - phase-space coordinates of the given point interp= (True), if True, use the interpolated track cindx= index of the closest point on the (interpolated) stream track if not given, determined from the dimensions given OUTPUT: (Or,Op,Oz,ar,ap,az) HISTORY: 2013-12-03 - Written - Bovy (IAS) 2015-11-12 - Added weighted sum of two nearest Jacobians to help with smoothness - Bovy (UofT) """ if isinstance(R,(int,float,numpy.float32,numpy.float64)): #Scalar input R= numpy.array([R]) vR= numpy.array([vR]) vT= numpy.array([vT]) z= numpy.array([z]) vz= numpy.array([vz]) phi= numpy.array([phi]) X= R*numpy.cos(phi) Y= R*numpy.sin(phi) Z= z if cindx is None: closestIndx= [self._find_closest_trackpoint(X[ii],Y[ii],Z[ii], z[ii],vz[ii],phi[ii], interp=interp, xy=True,usev=False) for ii in range(len(R))] else: closestIndx= cindx out= numpy.empty((6,len(R))) for ii in range(len(R)): dxv= numpy.empty(6) if interp: dxv[0]= R[ii]-self._interpolatedObsTrack[closestIndx[ii],0] dxv[1]= vR[ii]-self._interpolatedObsTrack[closestIndx[ii],1] dxv[2]= vT[ii]-self._interpolatedObsTrack[closestIndx[ii],2] dxv[3]= z[ii]-self._interpolatedObsTrack[closestIndx[ii],3] dxv[4]= vz[ii]-self._interpolatedObsTrack[closestIndx[ii],4] dxv[5]= phi[ii]-self._interpolatedObsTrack[closestIndx[ii],5] jacIndx= self._find_closest_trackpoint(R[ii],vR[ii],vT[ii], z[ii],vz[ii],phi[ii], interp=False, xy=False) else: dxv[0]= R[ii]-self._ObsTrack[closestIndx[ii],0] dxv[1]= vR[ii]-self._ObsTrack[closestIndx[ii],1] dxv[2]= vT[ii]-self._ObsTrack[closestIndx[ii],2] dxv[3]= z[ii]-self._ObsTrack[closestIndx[ii],3] dxv[4]= vz[ii]-self._ObsTrack[closestIndx[ii],4] dxv[5]= phi[ii]-self._ObsTrack[closestIndx[ii],5] jacIndx= closestIndx[ii] # Find 2nd closest Jacobian point for smoothing dmJacIndx= (X[ii]-self._ObsTrackXY[jacIndx,0])**2.\ +(Y[ii]-self._ObsTrackXY[jacIndx,1])**2.\ +(Z[ii]-self._ObsTrackXY[jacIndx,2])**2. if jacIndx == 0: jacIndx2= jacIndx+1 dmJacIndx2= (X[ii]-self._ObsTrackXY[jacIndx+1,0])**2.\ +(Y[ii]-self._ObsTrackXY[jacIndx+1,1])**2.\ +(Z[ii]-self._ObsTrackXY[jacIndx+1,2])**2. elif jacIndx == self._nTrackChunks-1: jacIndx2= jacIndx-1 dmJacIndx2= (X[ii]-self._ObsTrackXY[jacIndx-1,0])**2.\ +(Y[ii]-self._ObsTrackXY[jacIndx-1,1])**2.\ +(Z[ii]-self._ObsTrackXY[jacIndx-1,2])**2. else: dm1= (X[ii]-self._ObsTrackXY[jacIndx-1,0])**2.\ +(Y[ii]-self._ObsTrackXY[jacIndx-1,1])**2.\ +(Z[ii]-self._ObsTrackXY[jacIndx-1,2])**2. dm2= (X[ii]-self._ObsTrackXY[jacIndx+1,0])**2.\ +(Y[ii]-self._ObsTrackXY[jacIndx+1,1])**2.\ +(Z[ii]-self._ObsTrackXY[jacIndx+1,2])**2. if dm1 < dm2: jacIndx2= jacIndx-1 dmJacIndx2= dm1 else: jacIndx2= jacIndx+1 dmJacIndx2= dm2 ampJacIndx= numpy.sqrt(dmJacIndx)/(numpy.sqrt(dmJacIndx)\ +numpy.sqrt(dmJacIndx2)) #Make sure phi hasn't wrapped around if dxv[5] > numpy.pi: dxv[5]-= 2.*numpy.pi elif dxv[5] < -numpy.pi: dxv[5]+= 2.*numpy.pi #Apply closest jacobians out[:,ii]= numpy.dot((1.-ampJacIndx)*self._alljacsTrack[jacIndx,:,:] +ampJacIndx*self._alljacsTrack[jacIndx2,:,:], dxv) if interp: out[:,ii]+= self._interpolatedObsTrackAA[closestIndx[ii]] else: out[:,ii]+= self._ObsTrackAA[closestIndx[ii]] return out def _approxaAInv(self,Or,Op,Oz,ar,ap,az,interp=True): """ NAME: _approxaAInv PURPOSE: return R,vR,... coordinates for a point based on the linear approximation around the stream track INPUT: Or,Op,Oz,ar,ap,az - phase space coordinates in frequency-angle space interp= (True), if True, use the interpolated track OUTPUT: (R,vR,vT,z,vz,phi) HISTORY: 2013-12-22 - Written - Bovy (IAS) """ if isinstance(Or,(int,float,numpy.float32,numpy.float64)): #Scalar input Or= numpy.array([Or]) Op= numpy.array([Op]) Oz= numpy.array([Oz]) ar= numpy.array([ar]) ap= numpy.array([ap]) az= numpy.array([az]) #Calculate apar, angle offset along the stream closestIndx= [self._find_closest_trackpointaA(Or[ii],Op[ii],Oz[ii], ar[ii],ap[ii],az[ii], interp=interp)\ for ii in range(len(Or))] out= numpy.empty((6,len(Or))) for ii in range(len(Or)): dOa= numpy.empty(6) if interp: dOa[0]= Or[ii]-self._interpolatedObsTrackAA[closestIndx[ii],0] dOa[1]= Op[ii]-self._interpolatedObsTrackAA[closestIndx[ii],1] dOa[2]= Oz[ii]-self._interpolatedObsTrackAA[closestIndx[ii],2] dOa[3]= ar[ii]-self._interpolatedObsTrackAA[closestIndx[ii],3] dOa[4]= ap[ii]-self._interpolatedObsTrackAA[closestIndx[ii],4] dOa[5]= az[ii]-self._interpolatedObsTrackAA[closestIndx[ii],5] jacIndx= self._find_closest_trackpointaA(Or[ii],Op[ii],Oz[ii], ar[ii],ap[ii],az[ii], interp=False) else: dOa[0]= Or[ii]-self._ObsTrackAA[closestIndx[ii],0] dOa[1]= Op[ii]-self._ObsTrackAA[closestIndx[ii],1] dOa[2]= Oz[ii]-self._ObsTrackAA[closestIndx[ii],2] dOa[3]= ar[ii]-self._ObsTrackAA[closestIndx[ii],3] dOa[4]= ap[ii]-self._ObsTrackAA[closestIndx[ii],4] dOa[5]= az[ii]-self._ObsTrackAA[closestIndx[ii],5] jacIndx= closestIndx[ii] # Find 2nd closest Jacobian point for smoothing da= numpy.stack(\ numpy.meshgrid(_TWOPIWRAPS+ar[ii]-self._progenitor_angle[0], _TWOPIWRAPS+ap[ii]-self._progenitor_angle[1], _TWOPIWRAPS+az[ii]-self._progenitor_angle[2], indexing='xy')).T\ .reshape((len(_TWOPIWRAPS)**3,3)) dapar= self._sigMeanSign\ *numpy.dot(da[numpy.argmin(numpy.linalg.norm(\ numpy.cross(da,self._dsigomeanProgDirection), axis=1))], self._dsigomeanProgDirection) dmJacIndx= numpy.fabs(dapar-self._thetasTrack[jacIndx]) if jacIndx == 0: jacIndx2= jacIndx+1 dmJacIndx2= numpy.fabs(dapar-self._thetasTrack[jacIndx+1]) elif jacIndx == self._nTrackChunks-1: jacIndx2= jacIndx-1 dmJacIndx2= numpy.fabs(dapar-self._thetasTrack[jacIndx-1]) else: dm1= numpy.fabs(dapar-self._thetasTrack[jacIndx-1]) dm2= numpy.fabs(dapar-self._thetasTrack[jacIndx+1]) if dm1 < dm2: jacIndx2= jacIndx-1 dmJacIndx2= dm1 else: jacIndx2= jacIndx+1 dmJacIndx2= dm2 ampJacIndx= dmJacIndx/(dmJacIndx+dmJacIndx2) #Make sure the angles haven't wrapped around if dOa[3] > numpy.pi: dOa[3]-= 2.*numpy.pi elif dOa[3] < -numpy.pi: dOa[3]+= 2.*numpy.pi if dOa[4] > numpy.pi: dOa[4]-= 2.*numpy.pi elif dOa[4] < -numpy.pi: dOa[4]+= 2.*numpy.pi if dOa[5] > numpy.pi: dOa[5]-= 2.*numpy.pi elif dOa[5] < -numpy.pi: dOa[5]+= 2.*numpy.pi #Apply closest jacobian out[:,ii]= numpy.dot((1.-ampJacIndx)*self._allinvjacsTrack[jacIndx,:,:] +ampJacIndx*self._allinvjacsTrack[jacIndx2,:,:], dOa) if interp: out[:,ii]+= self._interpolatedObsTrack[closestIndx[ii]] else: out[:,ii]+= self._ObsTrack[closestIndx[ii]] return out ################################EVALUATE THE DF################################ def __call__(self,*args,**kwargs): """ NAME: __call__ PURPOSE: evaluate the DF INPUT: Either: a) R,vR,vT,z,vz,phi ndarray [nobjects] b) (Omegar,Omegaphi,Omegaz,angler,anglephi,anglez) tuple if aAInput where: Omegar - radial frequency Omegaphi - azimuthal frequency Omegaz - vertical frequency angler - radial angle anglephi - azimuthal angle anglez - vertical angle c) Orbit instance or list thereof log= if True, return the natural log aaInput= (False) if True, option b above OUTPUT: value of DF HISTORY: 2013-12-03 - Written - Bovy (IAS) """ #First parse log log= kwargs.pop('log',True) dOmega, dangle= self.prepData4Call(*args,**kwargs) #Omega part dOmega4dfOmega= dOmega\ -numpy.tile(self._dsigomeanProg.T,(dOmega.shape[1],1)).T logdfOmega= -0.5*numpy.sum(dOmega4dfOmega* numpy.dot(self._sigomatrixinv, dOmega4dfOmega), axis=0)-0.5*self._sigomatrixLogdet\ +numpy.log(numpy.fabs(numpy.dot(self._dsigomeanProgDirection,dOmega))) #Angle part dangle2= numpy.sum(dangle**2.,axis=0) dOmega2= numpy.sum(dOmega**2.,axis=0) dOmegaAngle= numpy.sum(dOmega*dangle,axis=0) logdfA= -0.5/self._sigangle2*(dangle2-dOmegaAngle**2./dOmega2)\ -2.*self._lnsigangle-0.5*numpy.log(dOmega2) #Finite stripping part a0= dOmegaAngle/numpy.sqrt(2.)/self._sigangle/numpy.sqrt(dOmega2) ad= numpy.sqrt(dOmega2)/numpy.sqrt(2.)/self._sigangle\ *(self._tdisrupt-dOmegaAngle/dOmega2) loga= numpy.log((special.erf(a0)+special.erf(ad))/2.) #divided by 2 st 0 for well-within the stream out= logdfA+logdfOmega+loga+self._logmeandetdOdJp if log: return out else: return numpy.exp(out) def prepData4Call(self,*args,**kwargs): """ NAME: prepData4Call PURPOSE: prepare stream data for the __call__ method INPUT: __call__ inputs OUTPUT: (dOmega,dangle); wrt the progenitor; each [3,nobj] HISTORY: 2013-12-04 - Written - Bovy (IAS) """ #First calculate the actionAngle coordinates if they're not given #as such freqsAngles= self._parse_call_args(*args,**kwargs) dOmega= freqsAngles[:3,:]\ -numpy.tile(self._progenitor_Omega.T,(freqsAngles.shape[1],1)).T dangle= freqsAngles[3:,:]\ -numpy.tile(self._progenitor_angle.T,(freqsAngles.shape[1],1)).T #Assuming single wrap, resolve large angle differences (wraps should be marginalized over) dangle[(dangle < -4.)]+= 2.*numpy.pi dangle[(dangle > 4.)]-= 2.*numpy.pi return (dOmega,dangle) def _parse_call_args(self,*args,**kwargs): """Helper function to parse the arguments to the __call__ and related functions, return [6,nobj] array of frequencies (:3) and angles (3:)""" interp= kwargs.get('interp',self._useInterp) if len(args) == 5: raise IOError("Must specify phi for streamdf") elif len(args) == 6: if kwargs.get('aAInput',False): if isinstance(args[0],(int,float,numpy.float32,numpy.float64)): out= numpy.empty((6,1)) else: out= numpy.empty((6,len(args[0]))) for ii in range(6): out[ii,:]= args[ii] return out else: return self._approxaA(*args,interp=interp) elif isinstance(args[0],Orbit): o= args[0] return self._approxaA(o.R(),o.vR(),o.vT(),o.z(),o.vz(),o.phi(), interp=interp) elif isinstance(args[0],list) and isinstance(args[0][0],Orbit): R, vR, vT, z, vz, phi= [], [], [], [], [], [] for o in args[0]: R.append(o.R()) vR.append(o.vR()) vT.append(o.vT()) z.append(o.z()) vz.append(o.vz()) phi.append(o.phi()) return self._approxaA(numpy.array(R),numpy.array(vR), numpy.array(vT),numpy.array(z), numpy.array(vz),numpy.array(phi), interp=interp) def callMarg(self,xy,**kwargs): """ NAME: callMarg PURPOSE: evaluate the DF, marginalizing over some directions, in Galactocentric rectangular coordinates (or in observed l,b,D,vlos,pmll,pmbb) coordinates) INPUT: xy - phase-space point [X,Y,Z,vX,vY,vZ]; the distribution of the dimensions set to None is returned interp= (object-wide interp default) if True, use the interpolated stream track cindx= index of the closest point on the (interpolated) stream track if not given, determined from the dimensions given nsigma= (3) number of sigma to marginalize the DF over (approximate sigma) ngl= (5) order of Gauss-Legendre integration lb= (False) if True, xy contains [l,b,D,vlos,pmll,pmbb] in [deg,deg,kpc,km/s,mas/yr,mas/yr] and the marginalized PDF in these coordinates is returned vo= (220) circular velocity to normalize with when lb=True ro= (8) Galactocentric radius to normalize with when lb=True R0= (8) Galactocentric radius of the Sun (kpc) Zsun= (0.025) Sun's height above the plane (kpc) vsun= ([-11.1,241.92,7.25]) Sun's motion in cylindrical coordinates (vR positive away from center) OUTPUT: p(xy) marginalized over missing directions in xy HISTORY: 2013-12-16 - Written - Bovy (IAS) """ coordGiven= numpy.array([not x is None for x in xy],dtype='bool') if numpy.sum(coordGiven) == 6: raise NotImplementedError("When specifying all coordinates, please use __call__ instead of callMarg") #First construct the Gaussian approximation at this xy gaussmean, gaussvar= self.gaussApprox(xy,**kwargs) cholvar, chollower= stable_cho_factor(gaussvar) #Now Gauss-legendre integrate over missing directions ngl= kwargs.get('ngl',5) nsigma= kwargs.get('nsigma',3) glx, glw= numpy.polynomial.legendre.leggauss(ngl) coordEval= [] weightEval= [] jj= 0 baseX= (glx+1)/2. baseX= list(baseX) baseX.extend(-(glx+1)/2.) baseX= numpy.array(baseX) baseW= glw baseW= list(baseW) baseW.extend(glw) baseW= numpy.array(baseW) for ii in range(6): if not coordGiven[ii]: coordEval.append(nsigma*baseX) weightEval.append(baseW) jj+= 1 else: coordEval.append(xy[ii]*numpy.ones(1)) weightEval.append(numpy.ones(1)) mgrid= numpy.meshgrid(*coordEval,indexing='ij') mgridNotGiven= numpy.array([mgrid[ii].flatten() for ii in range(6) if not coordGiven[ii]]) mgridNotGiven= numpy.dot(cholvar,mgridNotGiven) jj= 0 if coordGiven[0]: iX= mgrid[0] else: iX= mgridNotGiven[jj]+gaussmean[jj] jj+= 1 if coordGiven[1]: iY= mgrid[1] else: iY= mgridNotGiven[jj]+gaussmean[jj] jj+= 1 if coordGiven[2]: iZ= mgrid[2] else: iZ= mgridNotGiven[jj]+gaussmean[jj] jj+= 1 if coordGiven[3]: ivX= mgrid[3] else: ivX= mgridNotGiven[jj]+gaussmean[jj] jj+= 1 if coordGiven[4]: ivY= mgrid[4] else: ivY= mgridNotGiven[jj]+gaussmean[jj] jj+= 1 if coordGiven[5]: ivZ= mgrid[5] else: ivZ= mgridNotGiven[jj]+gaussmean[jj] jj+= 1 iXw, iYw, iZw, ivXw, ivYw, ivZw=\ numpy.meshgrid(*weightEval,indexing='ij') if kwargs.get('lb',False): #Convert to Galactocentric cylindrical coordinates #Setup coordinate transformation kwargs vo= kwargs.get('vo',self._vo) ro= kwargs.get('ro',self._ro) R0= kwargs.get('R0',self._R0) Zsun= kwargs.get('Zsun',self._Zsun) vsun= kwargs.get('vsun',self._vsun) tXYZ= bovy_coords.lbd_to_XYZ(iX.flatten(),iY.flatten(), iZ.flatten(), degree=True) iR,iphi,iZ= bovy_coords.XYZ_to_galcencyl(tXYZ[:,0],tXYZ[:,1], tXYZ[:,2], Xsun=R0,Zsun=Zsun).T tvxvyvz= bovy_coords.vrpmllpmbb_to_vxvyvz(ivX.flatten(), ivY.flatten(), ivZ.flatten(), tXYZ[:,0],tXYZ[:,1], tXYZ[:,2],XYZ=True) ivR,ivT,ivZ= bovy_coords.vxvyvz_to_galcencyl(tvxvyvz[:,0], tvxvyvz[:,1], tvxvyvz[:,2], iR,iphi,iZ, galcen=True, vsun=vsun, Xsun=R0,Zsun=Zsun).T iR/= ro iZ/= ro ivR/= vo ivT/= vo ivZ/= vo else: #Convert to cylindrical coordinates iR,iphi,iZ=\ bovy_coords.rect_to_cyl(iX.flatten(),iY.flatten(),iZ.flatten()) ivR,ivT,ivZ=\ bovy_coords.rect_to_cyl_vec(ivX.flatten(),ivY.flatten(), ivZ.flatten(), iR,iphi,iZ,cyl=True) #Add the additional Jacobian dXdY/dldb... if necessary if kwargs.get('lb',False): #Find the nearest track point interp= kwargs.get('interp',self._useInterp) if not 'cindx' in kwargs: cindx= self._find_closest_trackpointLB(*xy,interp=interp, usev=True) else: cindx= kwargs['cindx'] #Only l,b,d,... to Galactic X,Y,Z,... is necessary because going #from Galactic to Galactocentric has Jacobian determinant 1 if interp: addLogDet= self._interpolatedTrackLogDetJacLB[cindx] else: addLogDet= self._trackLogDetJacLB[cindx] else: addLogDet= 0. logdf= self(iR,ivR,ivT,iZ,ivZ,iphi,log=True) return logsumexp(logdf +numpy.log(iXw.flatten()) +numpy.log(iYw.flatten()) +numpy.log(iZw.flatten()) +numpy.log(ivXw.flatten()) +numpy.log(ivYw.flatten()) +numpy.log(ivZw.flatten()))\ +0.5*numpy.log(numpy.linalg.det(gaussvar))\ +addLogDet def gaussApprox(self,xy,**kwargs): """ NAME: gaussApprox PURPOSE: return the mean and variance of a Gaussian approximation to the stream DF at a given phase-space point in Galactocentric rectangular coordinates (distribution is over missing directions) INPUT: xy - phase-space point [X,Y,Z,vX,vY,vZ]; the distribution of the dimensions set to None is returned interp= (object-wide interp default) if True, use the interpolated stream track cindx= index of the closest point on the (interpolated) stream track if not given, determined from the dimensions given lb= (False) if True, xy contains [l,b,D,vlos,pmll,pmbb] in [deg,deg,kpc,km/s,mas/yr,mas/yr] and the Gaussian approximation in these coordinates is returned OUTPUT: (mean,variance) of the approximate Gaussian DF for the missing directions in xy HISTORY: 2013-12-12 - Written - Bovy (IAS) """ interp= kwargs.get('interp',self._useInterp) lb= kwargs.get('lb',False) #What are we looking for coordGiven= numpy.array([not x is None for x in xy],dtype='bool') nGiven= numpy.sum(coordGiven) #First find the nearest track point if not 'cindx' in kwargs and lb: cindx= self._find_closest_trackpointLB(*xy,interp=interp, usev=True) elif not 'cindx' in kwargs and not lb: cindx= self._find_closest_trackpoint(*xy,xy=True,interp=interp, usev=True) else: cindx= kwargs['cindx'] #Get the covariance matrix if interp and lb: tcov= self._interpolatedAllErrCovsLBUnscaled[cindx] tmean= self._interpolatedObsTrackLB[cindx] elif interp and not lb: tcov= self._interpolatedAllErrCovsXY[cindx] tmean= self._interpolatedObsTrackXY[cindx] elif not interp and lb: tcov= self._allErrCovsLBUnscaled[cindx] tmean= self._ObsTrackLB[cindx] elif not interp and not lb: tcov= self._allErrCovsXY[cindx] tmean= self._ObsTrackXY[cindx] if lb:#Apply scale factors tcov= copy.copy(tcov) tcov*= numpy.tile(self._ErrCovsLBScale,(6,1)) tcov*= numpy.tile(self._ErrCovsLBScale,(6,1)).T #Fancy indexing to recover V22, V11, and V12; V22, V11, V12 as in Appendix B of 0905.2979v1 V11indx0= numpy.array([[ii for jj in range(6-nGiven)] for ii in range(6) if not coordGiven[ii]]) V11indx1= numpy.array([[ii for ii in range(6) if not coordGiven[ii]] for jj in range(6-nGiven)]) V11= tcov[V11indx0,V11indx1] V22indx0= numpy.array([[ii for jj in range(nGiven)] for ii in range(6) if coordGiven[ii]]) V22indx1= numpy.array([[ii for ii in range(6) if coordGiven[ii]] for jj in range(nGiven)]) V22= tcov[V22indx0,V22indx1] V12indx0= numpy.array([[ii for jj in range(nGiven)] for ii in range(6) if not coordGiven[ii]]) V12indx1= numpy.array([[ii for ii in range(6) if coordGiven[ii]] for jj in range(6-nGiven)]) V12= tcov[V12indx0,V12indx1] #Also get m1 and m2, again following Appendix B of 0905.2979v1 m1= tmean[True^coordGiven] m2= tmean[coordGiven] #conditional mean and variance V22inv= numpy.linalg.inv(V22) v2= numpy.array([xy[ii] for ii in range(6) if coordGiven[ii]]) condMean= m1+numpy.dot(V12,numpy.dot(V22inv,v2-m2)) condVar= V11-numpy.dot(V12,numpy.dot(V22inv,V12.T)) return (condMean,condVar) ################################SAMPLE THE DF################################## def sample(self,n,returnaAdt=False,returndt=False,interp=None, xy=False,lb=False): """ NAME: sample PURPOSE: sample from the DF INPUT: n - number of points to return returnaAdt= (False) if True, return (Omega,angle,dt) returndT= (False) if True, also return the time since the star was stripped interp= (object-wide default) use interpolation of the stream track xy= (False) if True, return Galactocentric rectangular coordinates lb= (False) if True, return Galactic l,b,d,vlos,pmll,pmbb coordinates OUTPUT: (R,vR,vT,z,vz,phi) of points on the stream in 6,N array HISTORY: 2013-12-22 - Written - Bovy (IAS) """ #First sample frequencies Om,angle,dt= self._sample_aAt(n) if returnaAdt: if _APY_UNITS and self._voSet and self._roSet: Om=\ units.Quantity(\ Om*bovy_conversion.freq_in_Gyr(self._vo,self._ro), unit=1/units.Gyr) angle= units.Quantity(angle,unit=units.rad) dt= units.Quantity(\ dt*bovy_conversion.time_in_Gyr(self._vo,self._ro), unit=units.Gyr) return (Om,angle,dt) if interp is None: interp= self._useInterp #Propagate to R,vR,etc. RvR= self._approxaAInv(Om[0,:],Om[1,:],Om[2,:], angle[0,:],angle[1,:],angle[2,:], interp=interp) if returndt and not xy and not lb: if _APY_UNITS and self._voSet and self._roSet: return (units.Quantity(RvR[0]*self._ro,unit=units.kpc), units.Quantity(RvR[1]*self._vo,unit=units.km/units.s), units.Quantity(RvR[2]*self._vo,unit=units.km/units.s), units.Quantity(RvR[3]*self._ro,unit=units.kpc), units.Quantity(RvR[4]*self._vo,unit=units.km/units.s), units.Quantity(RvR[5],unit=units.rad), units.Quantity(\ dt*bovy_conversion.time_in_Gyr(self._vo,self._ro), unit=units.Gyr)) return (RvR[0],RvR[1],RvR[2],RvR[3],RvR[4],RvR[5],dt) elif not xy and not lb: if _APY_UNITS and self._voSet and self._roSet: return (units.Quantity(RvR[0]*self._ro,unit=units.kpc), units.Quantity(RvR[1]*self._vo,unit=units.km/units.s), units.Quantity(RvR[2]*self._vo,unit=units.km/units.s), units.Quantity(RvR[3]*self._ro,unit=units.kpc), units.Quantity(RvR[4]*self._vo,unit=units.km/units.s), units.Quantity(RvR[5],unit=units.rad)) return RvR if xy: sX= RvR[0]*numpy.cos(RvR[5]) sY= RvR[0]*numpy.sin(RvR[5]) sZ= RvR[3] svX, svY, svZ=\ bovy_coords.cyl_to_rect_vec(RvR[1], RvR[2], RvR[4], RvR[5]) out= numpy.empty((6,n)) out[0]= sX out[1]= sY out[2]= sZ out[3]= svX out[4]= svY out[5]= svZ if returndt: if _APY_UNITS and self._voSet and self._roSet: return (units.Quantity(out[0]*self._ro,unit=units.kpc), units.Quantity(out[1]*self._ro,unit=units.kpc), units.Quantity(out[2]*self._ro,unit=units.kpc), units.Quantity(out[3]*self._vo,unit=units.km/units.s), units.Quantity(out[4]*self._vo,unit=units.km/units.s), units.Quantity(out[5]*self._vo,unit=units.km/units.s), units.Quantity(\ dt*bovy_conversion.time_in_Gyr(self._vo,self._ro), unit=units.Gyr)) return (out[0],out[1],out[2],out[3],out[4],out[5],dt) else: if _APY_UNITS and self._voSet and self._roSet: return (units.Quantity(out[0]*self._ro,unit=units.kpc), units.Quantity(out[1]*self._ro,unit=units.kpc), units.Quantity(out[2]*self._ro,unit=units.kpc), units.Quantity(out[3]*self._vo,unit=units.km/units.s), units.Quantity(out[4]*self._vo,unit=units.km/units.s), units.Quantity(out[5]*self._vo,unit=units.km/units.s)) return out if lb: vo= self._vo ro= self._ro R0= self._R0 Zsun= self._Zsun vsun= self._vsun XYZ= bovy_coords.galcencyl_to_XYZ(RvR[0]*ro, RvR[5], RvR[3]*ro, Xsun=R0,Zsun=Zsun).T vXYZ= bovy_coords.galcencyl_to_vxvyvz(RvR[1]*vo, RvR[2]*vo, RvR[4]*vo, RvR[5], vsun=vsun,Xsun=R0,Zsun=Zsun).T slbd=bovy_coords.XYZ_to_lbd(XYZ[0],XYZ[1],XYZ[2], degree=True) svlbd= bovy_coords.vxvyvz_to_vrpmllpmbb(vXYZ[0],vXYZ[1],vXYZ[2], slbd[:,0],slbd[:,1], slbd[:,2], degree=True) out= numpy.empty((6,n)) out[0]= slbd[:,0] out[1]= slbd[:,1] out[2]= slbd[:,2] out[3]= svlbd[:,0] out[4]= svlbd[:,1] out[5]= svlbd[:,2] if returndt: if _APY_UNITS and self._voSet and self._roSet: return (units.Quantity(out[0],unit=units.deg), units.Quantity(out[1],unit=units.deg), units.Quantity(out[2],unit=units.kpc), units.Quantity(out[3],unit=units.km/units.s), units.Quantity(out[4],unit=units.mas/units.yr), units.Quantity(out[5],unit=units.mas/units.yr), units.Quantity(\ dt*bovy_conversion.time_in_Gyr(self._vo,self._ro), unit=units.Gyr)) return (out[0],out[1],out[2],out[3],out[4],out[5],dt) else: if _APY_UNITS and self._voSet and self._roSet: return (units.Quantity(out[0],unit=units.deg), units.Quantity(out[1],unit=units.deg), units.Quantity(out[2],unit=units.kpc), units.Quantity(out[3],unit=units.km/units.s), units.Quantity(out[4],unit=units.mas/units.yr), units.Quantity(out[5],unit=units.mas/units.yr)) return out def _sample_aAt(self,n): """Sampling frequencies, angles, and times part of sampling""" #Sample frequency along largest eigenvalue using ARS dO1s=\ bovy_ars.bovy_ars([0.,0.],[True,False], [self._meandO-numpy.sqrt(self._sortedSigOEig[2]), self._meandO+numpy.sqrt(self._sortedSigOEig[2])], _h_ars,_hp_ars,nsamples=n, hxparams=(self._meandO,self._sortedSigOEig[2]), maxn=100) dO1s= numpy.array(dO1s)*self._sigMeanSign dO2s= numpy.random.normal(size=n)*numpy.sqrt(self._sortedSigOEig[1]) dO3s= numpy.random.normal(size=n)*numpy.sqrt(self._sortedSigOEig[0]) #Rotate into dOs in R,phi,z coordinates dO= numpy.vstack((dO3s,dO2s,dO1s)) dO= numpy.dot(self._sigomatrixEig[1][:,self._sigomatrixEigsortIndx], dO) Om= dO+numpy.tile(self._progenitor_Omega.T,(n,1)).T #Also generate angles da= numpy.random.normal(size=(3,n))*self._sigangle #And a random time dt= self.sample_t(n) #Integrate the orbits relative to the progenitor da+= dO*numpy.tile(dt,(3,1)) angle= da+numpy.tile(self._progenitor_angle.T,(n,1)).T return (Om,angle,dt) def sample_t(self,n): """ NAME: sample_t PURPOSE: generate a stripping time (time since stripping); simple implementation could be replaced by more complicated distributions in sub-classes of streamdf INPUT: n - number of points to return OUTPUT: array of n stripping times HISTORY: 2015-09-16 - Written - Bovy (UofT) """ return numpy.random.uniform(size=n)*self._tdisrupt def _h_ars(x,params): """ln p(Omega) for ARS""" mO, sO2= params return -0.5*(x-mO)**2./sO2+numpy.log(x) def _hp_ars(x,params): """d ln p(Omega) / d Omega for ARS""" mO, sO2= params return -(x-mO)/sO2+1./x def _determine_stream_track_single(aA,progenitorTrack,trackt, progenitor_angle,sigMeanSign, dsigomeanProgDirection,meanOmega, thetasTrack): #Setup output allAcfsTrack= numpy.empty((9)) alljacsTrack= numpy.empty((6,6)) allinvjacsTrack= numpy.empty((6,6)) ObsTrack= numpy.empty((6)) ObsTrackAA= numpy.empty((6)) detdOdJ= numpy.empty(6) #Calculate tacfs= aA.actionsFreqsAngles(progenitorTrack(trackt)) allAcfsTrack[0]= tacfs[0][0] allAcfsTrack[1]= tacfs[1][0] allAcfsTrack[2]= tacfs[2][0] for jj in range(3,9): allAcfsTrack[jj]= tacfs[jj] tjac= calcaAJac(progenitorTrack(trackt)._orb.vxvv, aA, dxv=None,actionsFreqsAngles=True, lb=False, _initacfs=tacfs) alljacsTrack[:,:]= tjac[3:,:] tinvjac= numpy.linalg.inv(tjac[3:,:]) allinvjacsTrack[:,:]= tinvjac #Also store detdOdJ jindx= numpy.array([True,True,True,False,False,False,True,True,True], dtype='bool') dOdJ= numpy.dot(tjac[3:,:],numpy.linalg.inv(tjac[jindx,:]))[0:3,0:3] detdOdJ= numpy.linalg.det(dOdJ) theseAngles= numpy.mod(progenitor_angle\ +thetasTrack\ *sigMeanSign\ *dsigomeanProgDirection, 2.*numpy.pi) ObsTrackAA[3:]= theseAngles diffAngles= theseAngles-allAcfsTrack[6:] diffAngles[(diffAngles > numpy.pi)]= diffAngles[(diffAngles > numpy.pi)]-2.*numpy.pi diffAngles[(diffAngles < -numpy.pi)]= diffAngles[(diffAngles < -numpy.pi)]+2.*numpy.pi thisFreq= meanOmega(thetasTrack) ObsTrackAA[:3]= thisFreq diffFreqs= thisFreq-allAcfsTrack[3:6] ObsTrack[:]= numpy.dot(tinvjac, numpy.hstack((diffFreqs,diffAngles))) ObsTrack[0]+= \ progenitorTrack(trackt).R() ObsTrack[1]+= \ progenitorTrack(trackt).vR() ObsTrack[2]+= \ progenitorTrack(trackt).vT() ObsTrack[3]+= \ progenitorTrack(trackt).z() ObsTrack[4]+= \ progenitorTrack(trackt).vz() ObsTrack[5]+= \ progenitorTrack(trackt).phi() return [allAcfsTrack,alljacsTrack,allinvjacsTrack,ObsTrack,ObsTrackAA, detdOdJ] def _determine_stream_track_TM_single(aAT, progenitor_j, progenitor_Omega, progenitor_angle, dOdJ,dJdO, sigMeanSign, dsigomeanProgDirection, meanOmega, thetasTrack): #Setup output detdOdJ= numpy.empty(6) # Calculate track thisFreq= meanOmega(thetasTrack) theseAngles= numpy.mod(progenitor_angle\ +thetasTrack\ *sigMeanSign\ *dsigomeanProgDirection, 2.*numpy.pi) # Compute thisActions from thisFreq and dJ/dO near the progenitor thisActions= numpy.dot(dJdO,thisFreq-progenitor_Omega)+progenitor_j # Compute (x,v) using TM, also compute the Jacobian xvJacHess= aAT.xvJacobianFreqs(\ thisActions[0],thisActions[1],thisActions[2], numpy.array([theseAngles[0]]),numpy.array([theseAngles[1]]), numpy.array([theseAngles[2]])) # Output ObsTrack= xvJacHess[0] alljacsTrackTemp= numpy.linalg.inv(xvJacHess[1][0]) alljacsTrack= copy.copy(alljacsTrackTemp) # dOdJ here because it might be more precise alljacsTrack[:3,:3]= numpy.dot(dOdJ,alljacsTrackTemp[:3,:3]) alljacsTrack[3:,:3]= numpy.dot(dOdJ,alljacsTrackTemp[3:,:3]) allinvjacsTrack= numpy.linalg.inv(alljacsTrack) ObsTrackAA= numpy.empty(6) ObsTrackAA[:3]= thisFreq ObsTrackAA[3:]= theseAngles detdOdJ= numpy.linalg.det(xvJacHess[2]) return [alljacsTrack,allinvjacsTrack,ObsTrack,ObsTrackAA,detdOdJ] def _determine_stream_track_TM_approxConstantTrackFreq(aAT, progenitor_j, progenitor_Omega, progenitor_angle, dOdJ,dJdO, sigMeanSign, dsigomeanProgDirection, meanOmega, thetasTrack): #Setup output detdOdJ= numpy.empty(6) # Calculate track thisFreq= meanOmega(thetasTrack[0]) theseAngles= numpy.mod(numpy.tile(progenitor_angle,(len(thetasTrack),1))\ +numpy.tile(thetasTrack,(3,1)).T\ *sigMeanSign\ *dsigomeanProgDirection, 2.*numpy.pi) # Compute thisActions from thisFreq and dJ/dO near the progenitor thisActions= numpy.dot(dJdO,thisFreq-progenitor_Omega)+progenitor_j # Compute (x,v) using TM, also compute the Jacobian xvJacHess= aAT.xvJacobianFreqs(\ thisActions[0],thisActions[1],thisActions[2], theseAngles[:,0],theseAngles[:,1],theseAngles[:,2]) # Output ObsTrack= xvJacHess[0] alljacsTrack= numpy.empty((len(thetasTrack),6,6)) allinvjacsTrack= numpy.empty((len(thetasTrack),6,6)) detdOdJ= numpy.empty((len(thetasTrack))) for ii in range(len(thetasTrack)): alljacsTrackTemp= numpy.linalg.inv(xvJacHess[1][ii]) alljacsTrack[ii]= copy.copy(alljacsTrackTemp) # dOdJ here because it might be more precise alljacsTrack[ii,:3,:3]= numpy.dot(dOdJ,alljacsTrackTemp[:3,:3]) alljacsTrack[ii,3:,:3]= numpy.dot(dOdJ,alljacsTrackTemp[3:,:3]) allinvjacsTrack[ii]= numpy.linalg.inv(alljacsTrack[ii]) detdOdJ= numpy.linalg.det(xvJacHess[2]) ObsTrackAA= numpy.empty((len(thetasTrack),6)) ObsTrackAA[:,:3]= thisFreq ObsTrackAA[:,3:]= theseAngles return [alljacsTrack,allinvjacsTrack,ObsTrack,ObsTrackAA,detdOdJ] def _determine_stream_spread_single(sigomatrixEig, thetasTrack, sigOmega, sigAngle, allinvjacsTrack): """sigAngle input may either be a function that returns the dispersion in perpendicular angle as a function of parallel angle, or a value""" #Estimate the spread in all frequencies and angles sigObig2= sigOmega(thetasTrack)**2. tsigOdiag= copy.copy(sigomatrixEig[0]) tsigOdiag[numpy.argmax(tsigOdiag)]= sigObig2 tsigO= numpy.dot(sigomatrixEig[1], numpy.dot(numpy.diag(tsigOdiag), numpy.linalg.inv(sigomatrixEig[1]))) #angles if hasattr(sigAngle,'__call__'): sigangle2= sigAngle(thetasTrack)**2. else: sigangle2= sigAngle**2. tsigadiag= numpy.ones(3)*sigangle2 tsigadiag[numpy.argmax(tsigOdiag)]= 1. tsiga= numpy.dot(sigomatrixEig[1], numpy.dot(numpy.diag(tsigadiag), numpy.linalg.inv(sigomatrixEig[1]))) #correlations, assume half correlated for now (can be calculated) correlations= numpy.diag(0.5*numpy.ones(3))*numpy.sqrt(tsigOdiag*tsigadiag) correlations[numpy.argmax(tsigOdiag),numpy.argmax(tsigOdiag)]= 0. correlations= numpy.dot(sigomatrixEig[1], numpy.dot(correlations, numpy.linalg.inv(sigomatrixEig[1]))) #Now convert fullMatrix= numpy.empty((6,6)) fullMatrix[:3,:3]= tsigO fullMatrix[3:,3:]= tsiga fullMatrix[3:,:3]= correlations fullMatrix[:3,3:]= correlations.T return numpy.dot(allinvjacsTrack,numpy.dot(fullMatrix,allinvjacsTrack.T)) def calcaAJac(xv,aA,dxv=None,freqs=False,dOdJ=False,actionsFreqsAngles=False, lb=False,coordFunc=None, vo=220.,ro=8.,R0=8.,Zsun=0.025,vsun=[-11.1,8.*30.24,7.25], _initacfs=None): """ NAME: calcaAJac PURPOSE: calculate the Jacobian d(J,theta)/d(x,v) INPUT: xv - phase-space point: Either 1) [R,vR,vT,z,vz,phi] 2) [l,b,D,vlos,pmll,pmbb] (if lb=True, see below) 3) list/array of 6 numbers that can be transformed into (normalized) R,vR,vT,z,vz,phi using coordFunc aA - actionAngle instance dxv - infinitesimal to use (rescaled for lb, so think fractionally)) freqs= (False) if True, go to frequencies rather than actions dOdJ= (False), actually calculate d Frequency / d action actionsFreqsAngles= (False) if True, calculate d(action,freq.,angle)/d (xv) lb= (False) if True, start with (l,b,D,vlos,pmll,pmbb) in (deg,deg,kpc,km/s,mas/yr,mas/yr) vo= (220) circular velocity to normalize with when lb=True ro= (8) Galactocentric radius to normalize with when lb=True R0= (8) Galactocentric radius of the Sun (kpc) Zsun= (0.025) Sun's height above the plane (kpc) vsun= ([-11.1,241.92,7.25]) Sun's motion in cylindrical coordinates (vR positive away from center) coordFunc= (None) if set, this is a function that takes xv and returns R,vR,vT,z,vz,phi in normalized units (units where vc=1 at r=1 if the potential is normalized that way, for example) OUTPUT: Jacobian matrix HISTORY: 2013-11-25 - Written - Bovy (IAS) """ if lb: coordFunc= lambda x: lbCoordFunc(xv,vo,ro,R0,Zsun,vsun) if not coordFunc is None: R, vR, vT, z, vz, phi= coordFunc(xv) else: R, vR, vT, z, vz, phi= xv[0],xv[1],xv[2],xv[3],xv[4],xv[5] if dxv is None: dxv= 10.**-8.*numpy.ones(6) if lb: #Re-scale some of the differences, to be more natural dxv[0]*= 180./numpy.pi dxv[1]*= 180./numpy.pi dxv[2]*= ro dxv[3]*= vo dxv[4]*= vo/4.74047/xv[2] dxv[5]*= vo/4.74047/xv[2] if actionsFreqsAngles: jac= numpy.zeros((9,6)) else: jac= numpy.zeros((6,6)) if dOdJ: jac2= numpy.zeros((6,6)) if _initacfs is None: jr,lz,jz,Or,Ophi,Oz,ar,aphi,az\ = aA.actionsFreqsAngles(R,vR,vT,z,vz,phi) else: jr,lz,jz,Or,Ophi,Oz,ar,aphi,az\ = _initacfs for ii in range(6): temp= xv[ii]+dxv[ii] #Trick to make sure dxv is representable dxv[ii]= temp-xv[ii] xv[ii]+= dxv[ii] if not coordFunc is None: tR, tvR, tvT, tz, tvz, tphi= coordFunc(xv) else: tR, tvR, tvT, tz, tvz, tphi= xv[0],xv[1],xv[2],xv[3],xv[4],xv[5] tjr,tlz,tjz,tOr,tOphi,tOz,tar,taphi,taz\ = aA.actionsFreqsAngles(tR,tvR,tvT,tz,tvz,tphi) xv[ii]-= dxv[ii] angleIndx= 3 if actionsFreqsAngles: jac[0,ii]= (tjr-jr)/dxv[ii] jac[1,ii]= (tlz-lz)/dxv[ii] jac[2,ii]= (tjz-jz)/dxv[ii] jac[3,ii]= (tOr-Or)/dxv[ii] jac[4,ii]= (tOphi-Ophi)/dxv[ii] jac[5,ii]= (tOz-Oz)/dxv[ii] angleIndx= 6 elif freqs: jac[0,ii]= (tOr-Or)/dxv[ii] jac[1,ii]= (tOphi-Ophi)/dxv[ii] jac[2,ii]= (tOz-Oz)/dxv[ii] else: jac[0,ii]= (tjr-jr)/dxv[ii] jac[1,ii]= (tlz-lz)/dxv[ii] jac[2,ii]= (tjz-jz)/dxv[ii] if dOdJ: jac2[0,ii]= (tOr-Or)/dxv[ii] jac2[1,ii]= (tOphi-Ophi)/dxv[ii] jac2[2,ii]= (tOz-Oz)/dxv[ii] #For the angles, make sure we do not hit a turning point if tar-ar > numpy.pi: jac[angleIndx,ii]= (tar-ar-2.*numpy.pi)/dxv[ii] elif tar-ar < -numpy.pi: jac[angleIndx,ii]= (tar-ar+2.*numpy.pi)/dxv[ii] else: jac[angleIndx,ii]= (tar-ar)/dxv[ii] if taphi-aphi > numpy.pi: jac[angleIndx+1,ii]= (taphi-aphi-2.*numpy.pi)/dxv[ii] elif taphi-aphi < -numpy.pi: jac[angleIndx+1,ii]= (taphi-aphi+2.*numpy.pi)/dxv[ii] else: jac[angleIndx+1,ii]= (taphi-aphi)/dxv[ii] if taz-az > numpy.pi: jac[angleIndx+2,ii]= (taz-az-2.*numpy.pi)/dxv[ii] if taz-az < -numpy.pi: jac[angleIndx+2,ii]= (taz-az+2.*numpy.pi)/dxv[ii] else: jac[angleIndx+2,ii]= (taz-az)/dxv[ii] if dOdJ: jac2[3,:]= jac[3,:] jac2[4,:]= jac[4,:] jac2[5,:]= jac[5,:] jac= numpy.dot(jac2,numpy.linalg.inv(jac))[0:3,0:3] return jac def lbCoordFunc(xv,vo,ro,R0,Zsun,vsun): #Input is (l,b,D,vlos,pmll,pmbb) in (deg,deg,kpc,km/s,mas/yr,mas/yr) X,Y,Z= bovy_coords.lbd_to_XYZ(xv[0],xv[1],xv[2],degree=True) R,phi,Z= bovy_coords.XYZ_to_galcencyl(X,Y,Z, Xsun=R0,Zsun=Zsun) vx,vy,vz= bovy_coords.vrpmllpmbb_to_vxvyvz(xv[3],xv[4],xv[5], X,Y,Z,XYZ=True) vR,vT,vZ= bovy_coords.vxvyvz_to_galcencyl(vx,vy,vz,R,phi,Z,galcen=True, vsun=vsun,Xsun=R0,Zsun=Zsun) R/= ro Z/= ro vR/= vo vT/= vo vZ/= vo return (R,vR,vT,Z,vZ,phi) galpy-1.3.0/galpy/df_src/streamgapdf.py0000644000076500000240000022023213125613630020155 0ustar bovystaff00000000000000# The DF of a gap in a tidal stream from functools import wraps import copy import numpy import warnings import multiprocessing from scipy import integrate, interpolate, special from galpy.util import galpyWarning, bovy_coords, multi, bovy_conversion from galpy.util import _rotate_to_arbitrary_vector from galpy.orbit import Orbit from galpy.potential import evaluateRforces, MovingObjectPotential from galpy.df_src.df import df, _APY_LOADED from galpy.util.bovy_conversion import physical_conversion import galpy.df_src.streamdf from galpy.df_src.streamdf import _determine_stream_track_single if _APY_LOADED: from astropy import units def impact_check_range(func): """Decorator to check the range of interpolated kicks""" @wraps(func) def impact_wrapper(*args,**kwargs): if isinstance(args[1],numpy.ndarray): out= numpy.zeros(len(args[1])) goodIndx= (args[1] < args[0]._deltaAngleTrackImpact)*(args[1] > 0.) out[goodIndx]= func(args[0],args[1][goodIndx]) return out elif args[1] >= args[0]._deltaAngleTrackImpact or args[1] <= 0.: return 0. else: return func(*args,**kwargs) return impact_wrapper class streamgapdf(galpy.df_src.streamdf.streamdf): """The DF of a gap in a tidal stream""" def __init__(self,*args,**kwargs): """ NAME: __init__ PURPOSE: Initialize the DF of a gap in a stellar stream INPUT: streamdf args and kwargs Subhalo and impact parameters: impactb= impact parameter (can be Quantity) subhalovel= velocity of the subhalo shape=(3) (can be Quantity) timpact time since impact (can be Quantity) impact_angle= angle offset from progenitor at which the impact occurred (rad) (can be Quantity) Subhalo: specify either 1( mass and size of Plummer sphere or 2( general spherical-potential object (kick is numerically computed) 1( GM= mass of the subhalo (can be Quantity) rs= size parameter of the subhalo (can be Quantity) 2( subhalopot= galpy potential object or list thereof (should be spherical) 3( hernquist= (False) if True, use Hernquist kicks for GM/rs deltaAngleTrackImpact= (None) angle to estimate the stream track over to determine the effect of the impact [similar to deltaAngleTrack] (rad) nTrackChunksImpact= (floor(deltaAngleTrack/0.15)+1) number of chunks to divide the progenitor track in near the impact [similar to nTrackChunks] nKickPoints= (30xnTrackChunksImpact) number of points along the stream to compute the kicks at (kicks are then interpolated); '30' chosen such that higherorderTrack can be set to False and get calculations accurate to > 99% nokicksetup= (False) if True, only run as far as setting up the coordinate transformation at the time of impact (useful when using this in streampepperdf) spline_order= (3) order of the spline to interpolate the kicks with higherorderTrack= (False) if True, calculate the track using higher-order terms OUTPUT: object HISTORY: 2015-06-02 - Started - Bovy (IAS) """ df.__init__(self,ro=kwargs.get('ro',None),vo=kwargs.get('vo',None)) # Parse kwargs impactb= kwargs.pop('impactb',1.) if _APY_LOADED and isinstance(impactb,units.Quantity): impactb= impactb.to(units.kpc).value/self._ro subhalovel= kwargs.pop('subhalovel',numpy.array([0.,1.,0.])) if _APY_LOADED and isinstance(subhalovel,units.Quantity): subhalovel= subhalovel.to(units.km/units.s).value/self._vo hernquist= kwargs.pop('hernquist',False) GM= kwargs.pop('GM',None) if not GM is None \ and _APY_LOADED and isinstance(GM,units.Quantity): # GM can be GM or M try: GM= GM.to(units.pc*units.km**2/units.s**2)\ .value\ /bovy_conversion.mass_in_msol(self._vo,self._ro)\ /bovy_conversion._G except units.UnitConversionError: pass GM= GM.to(units.Msun).value\ /bovy_conversion.mass_in_msol(self._vo,self._ro) rs= kwargs.pop('rs',None) if not rs is None \ and _APY_LOADED and isinstance(rs,units.Quantity): rs= rs.to(units.kpc).value/self._ro subhalopot= kwargs.pop('subhalopot',None) timpact= kwargs.pop('timpact',1.) if _APY_LOADED and isinstance(timpact,units.Quantity): timpact= timpact.to(units.Gyr).value\ /bovy_conversion.time_in_Gyr(self._vo,self._ro) impact_angle= kwargs.pop('impact_angle',1.) if _APY_LOADED and isinstance(impact_angle,units.Quantity): impact_angle= impact_angle.to(units.rad).value nokicksetup= kwargs.pop('nokicksetup',False) deltaAngleTrackImpact= kwargs.pop('deltaAngleTrackImpact',None) nTrackChunksImpact= kwargs.pop('nTrackChunksImpact',None) nKickPoints= kwargs.pop('nKickPoints',None) spline_order= kwargs.pop('spline_order',3) higherorderTrack= kwargs.pop('higherorderTrack',False) # For setup later nTrackChunks= kwargs.pop('nTrackChunks',None) interpTrack= kwargs.pop('interpTrack', galpy.df_src.streamdf._INTERPDURINGSETUP) useInterp= kwargs.pop('useInterp', galpy.df_src.streamdf._USEINTERP) # Analytical Plummer or general potential? self._general_kick= GM is None or rs is None if self._general_kick and subhalopot is None: raise IOError("One of (GM=, rs=) or subhalopot= needs to be set to specify the subhalo's structure") # Now run the regular streamdf setup, but without calculating the # stream track (nosetup=True) kwargs['nosetup']= True super(streamgapdf,self).__init__(*args,**kwargs) # Setup the machinery to go between (x,v) and (Omega,theta) # near the impact self._determine_nTrackIterations(kwargs.get('nTrackIterations',None)) self._determine_deltaAngleTrackImpact(deltaAngleTrackImpact,timpact) self._determine_impact_coordtransform(self._deltaAngleTrackImpact, nTrackChunksImpact, timpact,impact_angle) # Set nKickPoints if nKickPoints is None: self._nKickPoints= 30*self._nTrackChunksImpact else: self._nKickPoints= nKickPoints if nokicksetup: return None # Compute \Delta Omega ( \Delta \theta_perp) and \Delta theta, # setup interpolating function self._determine_deltav_kick(impact_angle,impactb,subhalovel, GM,rs,subhalopot, spline_order,hernquist) self._determine_deltaOmegaTheta_kick(spline_order) # Then pass everything to the normal streamdf setup self.nInterpolatedTrackChunks= 201 #more expensive now self._higherorderTrack= higherorderTrack super(streamgapdf,self)._determine_stream_track(nTrackChunks) self._useInterp= useInterp if interpTrack or self._useInterp: super(streamgapdf,self)._interpolate_stream_track() super(streamgapdf,self)._interpolate_stream_track_aA() super(streamgapdf,self).calc_stream_lb() return None def pOparapar(self,Opar,apar): """ NAME: pOparapar PURPOSE: return the probability of a given parallel (frequency,angle) offset pair INPUT: Opar - parallel frequency offset (array) (can be Quantity) apar - parallel angle offset along the stream (scalar) (can be Quantity) OUTPUT: p(Opar,apar) HISTORY: 2015-11-17 - Written - Bovy (UofT) """ if _APY_LOADED and isinstance(Opar,units.Quantity): Opar= Opar.to(1/units.Gyr).value\ /bovy_conversion.freq_in_Gyr(self._vo,self._ro) if _APY_LOADED and isinstance(apar,units.Quantity): apar= apar.to(units.rad).value if isinstance(Opar,(int,float,numpy.float32,numpy.float64)): Opar= numpy.array([Opar]) out= numpy.zeros(len(Opar)) # Compute ts and where they were at impact for all ts= apar/Opar apar_impact= apar-Opar*self._timpact dOpar_impact= self._kick_interpdOpar(apar_impact) Opar_b4impact= Opar-dOpar_impact # Evaluate the smooth model in the two regimes: # stripped before or after impact afterIndx= (ts < self._timpact)*(ts >= 0.) out[afterIndx]=\ super(streamgapdf,self).pOparapar(Opar[afterIndx], apar) out[True^afterIndx]=\ super(streamgapdf,self).pOparapar(Opar_b4impact[True^afterIndx], apar_impact[True^afterIndx], tdisrupt= self._tdisrupt-self._timpact) return out def _density_par(self,dangle,tdisrupt=None,approx=True, higherorder=None): """The raw density as a function of parallel angle, approx= use faster method that directly integrates the spline representation""" if higherorder is None: higherorder= self._higherorderTrack if tdisrupt is None: tdisrupt= self._tdisrupt if approx: return self._density_par_approx(dangle,tdisrupt, higherorder=higherorder) else: return integrate.quad(lambda T: numpy.sqrt(self._sortedSigOEig[2])\ *(1+T*T)/(1-T*T)**2.\ *self.pOparapar(T/(1-T*T)\ *numpy.sqrt(self._sortedSigOEig[2])\ +self._meandO,dangle), -1.,1.)[0] def _density_par_approx(self,dangle,tdisrupt,_return_array=False, higherorder=False): """Compute the density as a function of parallel angle using the spline representation + approximations""" # First construct the breakpoints for this dangle Oparb= (dangle-self._kick_interpdOpar_poly.x)/self._timpact # Find the lower limit of the integration in the pw-linear-kick approx. lowbindx,lowx= self.minOpar(dangle,tdisrupt,_return_raw=True) lowbindx= numpy.arange(len(Oparb)-1)[lowbindx] Oparb[lowbindx+1]= Oparb[lowbindx]-lowx # Now integrate between breakpoints out= (0.5/(1.+self._kick_interpdOpar_poly.c[-2]*self._timpact)\ *(special.erf(1./numpy.sqrt(2.*self._sortedSigOEig[2])\ *(Oparb[:-1]-self._kick_interpdOpar_poly.c[-1]-self._meandO))\ -special.erf(1./numpy.sqrt(2.*self._sortedSigOEig[2])*(numpy.roll(Oparb,-1)[:-1]-self._kick_interpdOpar_poly.c[-1]-self._meandO\ -self._kick_interpdOpar_poly.c[-2]*self._timpact*(Oparb-numpy.roll(Oparb,-1))[:-1])))) if _return_array: return out out= numpy.sum(out[:lowbindx+1]) if higherorder: # Add higher-order contribution out+= self._density_par_approx_higherorder(Oparb,lowbindx) # Add integration to infinity out+= 0.5*(1.+special.erf((self._meandO-Oparb[0])\ /numpy.sqrt(2.*self._sortedSigOEig[2]))) return out def _density_par_approx_higherorder(self,Oparb,lowbindx, _return_array=False, gaussxpolyInt=None): """Contribution from non-linear spline terms""" spline_order= self._kick_interpdOpar_raw._eval_args[2] if spline_order == 1: return 0. # Form all Gaussian-like integrals necessary ll= (numpy.roll(Oparb,-1)[:-1]-self._kick_interpdOpar_poly.c[-1]\ -self._meandO\ -self._kick_interpdOpar_poly.c[-2]*self._timpact\ *(Oparb-numpy.roll(Oparb,-1))[:-1])\ /numpy.sqrt(2.*self._sortedSigOEig[2]) ul= (Oparb[:-1]-self._kick_interpdOpar_poly.c[-1]-self._meandO)\ /numpy.sqrt(2.*self._sortedSigOEig[2]) if gaussxpolyInt is None: gaussxpolyInt=\ self._densMoments_approx_higherorder_gaussxpolyInts(\ ll,ul,spline_order+1) # Now multiply in the coefficients for each order powers= numpy.tile(numpy.arange(spline_order+1)[::-1], (len(ul),1)).T gaussxpolyInt*= -0.5*(-numpy.sqrt(2.))**(powers+1)\ *self._sortedSigOEig[2]**(0.5*(powers-1)) powers= numpy.tile(numpy.arange(spline_order+1)[::-1][:-2], (len(ul),1)).T for jj in range(spline_order+1): gaussxpolyInt[-jj-1]*= numpy.sum(\ self._kick_interpdOpar_poly.c[:-2] *self._timpact**powers /(1.+self._kick_interpdOpar_poly.c[-2]*self._timpact) **(powers+1) *special.binom(powers,jj) *(Oparb[:-1]-self._kick_interpdOpar_poly.c[-1]-self._meandO) **(powers-jj),axis=0) if _return_array: return numpy.sum(gaussxpolyInt,axis=0) else: return numpy.sum(gaussxpolyInt[:,:lowbindx+1]) def _densMoments_approx_higherorder_gaussxpolyInts(self,ll,ul,maxj): """Calculate all of the polynomial x Gaussian integrals occuring in the higher-order terms, recursively""" gaussxpolyInt= numpy.zeros((maxj,len(ul))) gaussxpolyInt[-1]= 1./numpy.sqrt(numpy.pi)\ *(numpy.exp(-ll**2.)-numpy.exp(-ul**2.)) gaussxpolyInt[-2]= 1./numpy.sqrt(numpy.pi)\ *(numpy.exp(-ll**2.)*ll-numpy.exp(-ul**2.)*ul)\ +0.5*(special.erf(ul)-special.erf(ll)) for jj in range(maxj-2): gaussxpolyInt[-jj-3]= 1./numpy.sqrt(numpy.pi)\ *(numpy.exp(-ll**2.)*ll**(jj+2)-numpy.exp(-ul**2.)*ul**(jj+2))\ +0.5*(jj+2)*gaussxpolyInt[-jj-1] return gaussxpolyInt def minOpar(self,dangle,tdisrupt=None,_return_raw=False): """ NAME: minOpar PURPOSE: return the approximate minimum parallel frequency at a given angle INPUT: dangle - parallel angle OUTPUT: minimum frequency that gets to this parallel angle HISTORY: 2015-12-28 - Written - Bovy (UofT) """ if tdisrupt is None: tdisrupt= self._tdisrupt # First construct the breakpoints for this dangle Oparb= (dangle-self._kick_interpdOpar_poly.x[:-1])/self._timpact # Find the lower limit of the integration in the pw-linear-kick approx. lowx= ((Oparb-self._kick_interpdOpar_poly.c[-1])\ *(tdisrupt-self._timpact)+Oparb*self._timpact-dangle)\ /((tdisrupt-self._timpact)\ *(1.+self._kick_interpdOpar_poly.c[-2]*self._timpact)\ +self._timpact) lowx[lowx < 0.]= numpy.inf lowbindx= numpy.argmin(lowx) if _return_raw: return (lowbindx,lowx[lowbindx]) else: return Oparb[lowbindx]-lowx[lowbindx] @physical_conversion('frequency',pop=True) def meanOmega(self,dangle,oned=False,tdisrupt=None,approx=True, higherorder=None): """ NAME: meanOmega PURPOSE: calculate the mean frequency as a function of angle, assuming a uniform time distribution up to a maximum time INPUT: dangle - angle offset oned= (False) if True, return the 1D offset from the progenitor (along the direction of disruption) approx= (True) if True, compute the mean Omega by direct integration of the spline representation higherorder= (object-wide default higherorderTrack) if True, include higher-order spline terms in the approximate computation OUTPUT: mean Omega HISTORY: 2015-11-17 - Written - Bovy (UofT) """ if higherorder is None: higherorder= self._higherorderTrack if tdisrupt is None: tdisrupt= self._tdisrupt if approx: num= self._meanOmega_num_approx(dangle,tdisrupt, higherorder=higherorder) else: num=\ integrate.quad(lambda T: (T/(1-T*T)\ *numpy.sqrt(self._sortedSigOEig[2])\ +self._meandO)\ *numpy.sqrt(self._sortedSigOEig[2])\ *(1+T*T)/(1-T*T)**2.\ *self.pOparapar(T/(1-T*T)\ *numpy.sqrt(self._sortedSigOEig[2])\ +self._meandO,dangle), -1.,1.)[0] denom= self._density_par(dangle,tdisrupt=tdisrupt,approx=approx, higherorder=higherorder) dO1D= num/denom if oned: return dO1D else: return self._progenitor_Omega+dO1D*self._dsigomeanProgDirection\ *self._sigMeanSign def _meanOmega_num_approx(self,dangle,tdisrupt,higherorder=False): """Compute the numerator going into meanOmega using the direct integration of the spline representation""" # First construct the breakpoints for this dangle Oparb= (dangle-self._kick_interpdOpar_poly.x)/self._timpact # Find the lower limit of the integration in the pw-linear-kick approx. lowbindx,lowx= self.minOpar(dangle,tdisrupt,_return_raw=True) lowbindx= numpy.arange(len(Oparb)-1)[lowbindx] Oparb[lowbindx+1]= Oparb[lowbindx]-lowx # Now integrate between breakpoints out= numpy.sum(((Oparb[:-1] +(self._meandO+self._kick_interpdOpar_poly.c[-1] -Oparb[:-1])/ (1.+self._kick_interpdOpar_poly.c[-2]*self._timpact)) *self._density_par_approx(dangle,tdisrupt, _return_array=True) +numpy.sqrt(self._sortedSigOEig[2]/2./numpy.pi)/ (1.+self._kick_interpdOpar_poly.c[-2]*self._timpact)**2. *(numpy.exp(-0.5*(Oparb[:-1] -self._kick_interpdOpar_poly.c[-1] -(1.+self._kick_interpdOpar_poly.c[-2]*self._timpact) *(Oparb-numpy.roll(Oparb,-1))[:-1] -self._meandO)**2. /self._sortedSigOEig[2]) -numpy.exp(-0.5*(Oparb[:-1]-self._kick_interpdOpar_poly.c[-1] -self._meandO)**2. /self._sortedSigOEig[2])))[:lowbindx+1]) if higherorder: # Add higher-order contribution out+= self._meanOmega_num_approx_higherorder(Oparb,lowbindx) # Add integration to infinity out+= 0.5*(numpy.sqrt(2./numpy.pi)*numpy.sqrt(self._sortedSigOEig[2])\ *numpy.exp(-0.5*(self._meandO-Oparb[0])**2.\ /self._sortedSigOEig[2]) +self._meandO *(1.+special.erf((self._meandO-Oparb[0]) /numpy.sqrt(2.*self._sortedSigOEig[2])))) return out def _meanOmega_num_approx_higherorder(self,Oparb,lowbindx): """Contribution from non-linear spline terms""" spline_order= self._kick_interpdOpar_raw._eval_args[2] if spline_order == 1: return 0. # Form all Gaussian-like integrals necessary ll= (numpy.roll(Oparb,-1)[:-1]-self._kick_interpdOpar_poly.c[-1]\ -self._meandO\ -self._kick_interpdOpar_poly.c[-2]*self._timpact\ *(Oparb-numpy.roll(Oparb,-1))[:-1])\ /numpy.sqrt(2.*self._sortedSigOEig[2]) ul= (Oparb[:-1]-self._kick_interpdOpar_poly.c[-1]-self._meandO)\ /numpy.sqrt(2.*self._sortedSigOEig[2]) gaussxpolyInt=\ self._densMoments_approx_higherorder_gaussxpolyInts(ll,ul, spline_order+2) firstTerm= Oparb[:-1]\ *self._density_par_approx_higherorder(\ Oparb,lowbindx,_return_array=True, gaussxpolyInt=copy.copy(gaussxpolyInt[1:])) # Now multiply in the coefficients for each order powers= numpy.tile(numpy.arange(spline_order+2)[::-1], (len(ul),1)).T gaussxpolyInt*= -0.5*(-numpy.sqrt(2.))**(powers+1)\ *self._sortedSigOEig[2]**(0.5*(powers-1)) powers= numpy.tile(numpy.arange(spline_order+1)[::-1][:-2], (len(ul),1)).T for jj in range(spline_order+2): gaussxpolyInt[-jj-1]*= numpy.sum(\ self._kick_interpdOpar_poly.c[:-2] *self._timpact**powers /(1.+self._kick_interpdOpar_poly.c[-2]*self._timpact) **(powers+2) *special.binom(powers+1,jj) *(Oparb[:-1]-self._kick_interpdOpar_poly.c[-1]-self._meandO) **(powers-jj+1),axis=0) out= numpy.sum(gaussxpolyInt,axis=0) out+= firstTerm return numpy.sum(out[:lowbindx+1]) def _determine_deltav_kick(self,impact_angle,impactb,subhalovel, GM,rs,subhalopot, spline_order,hernquist): # Store some impact parameters self._impactb= impactb self._subhalovel= subhalovel # Sign of delta angle tells us whether the impact happens to the # leading or trailing arm, self._sigMeanSign contains this info; # Checked before, but check it again in case impact_angle has changed if impact_angle > 0.: self._gap_leading= True else: self._gap_leading= False if (self._gap_leading and not self._leading) \ or (not self._gap_leading and self._leading): raise ValueError('Modeling leading (trailing) impact for trailing (leading) arm; this is not allowed because it is nonsensical in this framework') self._impact_angle= numpy.fabs(impact_angle) # Interpolate the track near the gap in (x,v) at the kick_thetas self._interpolate_stream_track_kick() self._interpolate_stream_track_kick_aA() # Then compute delta v along the track if self._general_kick: self._kick_deltav=\ impulse_deltav_general_curvedstream(self._kick_interpolatedObsTrackXY[:,3:], self._kick_interpolatedObsTrackXY[:,:3], self._impactb, self._subhalovel, self._kick_ObsTrackXY_closest[:3], self._kick_ObsTrackXY_closest[3:], subhalopot) else: if hernquist: deltav_func= impulse_deltav_hernquist_curvedstream else: deltav_func= impulse_deltav_plummer_curvedstream self._kick_deltav= \ deltav_func(self._kick_interpolatedObsTrackXY[:,3:], self._kick_interpolatedObsTrackXY[:,:3], self._impactb, self._subhalovel, self._kick_ObsTrackXY_closest[:3], self._kick_ObsTrackXY_closest[3:], GM,rs) return None def _determine_deltaOmegaTheta_kick(self,spline_order): # Propagate deltav(angle) -> delta (Omega,theta) [angle] # Cylindrical coordinates of the perturbed points vXp= self._kick_interpolatedObsTrackXY[:,3]+self._kick_deltav[:,0] vYp= self._kick_interpolatedObsTrackXY[:,4]+self._kick_deltav[:,1] vZp= self._kick_interpolatedObsTrackXY[:,5]+self._kick_deltav[:,2] vRp,vTp,vZp=\ bovy_coords.rect_to_cyl_vec(vXp,vYp,vZp, self._kick_interpolatedObsTrack[:,0], self._kick_interpolatedObsTrack[:,5], self._kick_interpolatedObsTrack[:,3], cyl=True) # We will abuse streamdf functions for doing the (O,a) -> (R,vR) # coordinate transformation, to do this, we assign some of the # attributes related to the track near the impact to the equivalent # attributes related to the track at the present time, carefully # removing this again to avoid confusion (as much as possible) self._interpolatedObsTrack= self._kick_interpolatedObsTrack self._ObsTrack= self._gap_ObsTrack self._interpolatedObsTrackXY= self._kick_interpolatedObsTrackXY self._ObsTrackXY= self._gap_ObsTrackXY self._alljacsTrack= self._gap_alljacsTrack self._interpolatedObsTrackAA= self._kick_interpolatedObsTrackAA self._ObsTrackAA= self._gap_ObsTrackAA self._nTrackChunks= self._nTrackChunksImpact Oap= self._approxaA(self._kick_interpolatedObsTrack[:,0], vRp,vTp, self._kick_interpolatedObsTrack[:,3], vZp, self._kick_interpolatedObsTrack[:,5],interp=True, cindx=range(len(self._kick_interpolatedObsTrackAA))) # Remove attributes again to avoid confusion later delattr(self,'_interpolatedObsTrack') delattr(self,'_ObsTrack') delattr(self,'_interpolatedObsTrackXY') delattr(self,'_ObsTrackXY') delattr(self,'_alljacsTrack') delattr(self,'_interpolatedObsTrackAA') delattr(self,'_ObsTrackAA') delattr(self,'_nTrackChunks') # Generate (dO,da)[angle_offset] and interpolate (raw here, see below # for form that checks range) self._kick_dOap= Oap.T-self._kick_interpolatedObsTrackAA self._kick_interpdOr_raw=\ interpolate.InterpolatedUnivariateSpline(\ self._kick_interpolatedThetasTrack,self._kick_dOap[:,0], k=spline_order) self._kick_interpdOp_raw=\ interpolate.InterpolatedUnivariateSpline(\ self._kick_interpolatedThetasTrack,self._kick_dOap[:,1], k=spline_order) self._kick_interpdOz_raw=\ interpolate.InterpolatedUnivariateSpline(\ self._kick_interpolatedThetasTrack,self._kick_dOap[:,2], k=spline_order) self._kick_interpdar_raw=\ interpolate.InterpolatedUnivariateSpline(\ self._kick_interpolatedThetasTrack,self._kick_dOap[:,3], k=spline_order) self._kick_interpdap_raw=\ interpolate.InterpolatedUnivariateSpline(\ self._kick_interpolatedThetasTrack,self._kick_dOap[:,4], k=spline_order) self._kick_interpdaz_raw=\ interpolate.InterpolatedUnivariateSpline(\ self._kick_interpolatedThetasTrack,self._kick_dOap[:,5], k=spline_order) # Also interpolate parallel and perpendicular frequencies self._kick_dOaparperp=\ numpy.dot(self._kick_dOap[:,:3], self._sigomatrixEig[1][:,self._sigomatrixEigsortIndx]) self._kick_dOaparperp[:,2]*= self._sigMeanSign self._kick_interpdOpar_raw=\ interpolate.InterpolatedUnivariateSpline(\ self._kick_interpolatedThetasTrack, numpy.dot(self._kick_dOap[:,:3],self._dsigomeanProgDirection)\ *self._sigMeanSign, k=spline_order) # to get zeros with sproot self._kick_interpdOperp0_raw=\ interpolate.InterpolatedUnivariateSpline(\ self._kick_interpolatedThetasTrack,self._kick_dOaparperp[:,0], k=spline_order) self._kick_interpdOperp1_raw=\ interpolate.InterpolatedUnivariateSpline(\ self._kick_interpolatedThetasTrack,self._kick_dOaparperp[:,1], k=spline_order) # Also construct derivative of dOpar self._kick_interpdOpar_dapar= self._kick_interpdOpar_raw.derivative(1) # Also construct piecewise-polynomial representation of dOpar, # removing intervals at the start and end with zero range ppoly= interpolate.PPoly.from_spline(\ self._kick_interpdOpar_raw._eval_args) nzIndx=\ numpy.nonzero((numpy.roll(ppoly.x,-1)-ppoly.x > 0)\ *(numpy.arange(len(ppoly.x)) < len(ppoly.x)//2)\ +(ppoly.x-numpy.roll(ppoly.x,1) > 0)\ *(numpy.arange(len(ppoly.x)) >= len(ppoly.x)//2)) self._kick_interpdOpar_poly= interpolate.PPoly(\ ppoly.c[:,nzIndx[0][:-1]],ppoly.x[nzIndx[0]]) return None # Functions that evaluate the interpolated kicks, but also check the range @impact_check_range def _kick_interpdOpar(self,da): return self._kick_interpdOpar_raw(da) @impact_check_range def _kick_interpdOperp0(self,da): return self._kick_interpdOperp0_raw(da) @impact_check_range def _kick_interpdOperp1(self,da): return self._kick_interpdOperp1_raw(da) @impact_check_range def _kick_interpdOr(self,da): return self._kick_interpdOr_raw(da) @impact_check_range def _kick_interpdOp(self,da): return self._kick_interpdOp_raw(da) @impact_check_range def _kick_interpdOz(self,da): return self._kick_interpdOz_raw(da) @impact_check_range def _kick_interpdar(self,da): return self._kick_interpdar_raw(da) @impact_check_range def _kick_interpdap(self,da): return self._kick_interpdap_raw(da) @impact_check_range def _kick_interpdaz(self,da): return self._kick_interpdaz_raw(da) def _interpolate_stream_track_kick(self): """Build interpolations of the stream track near the kick""" if hasattr(self,'_kick_interpolatedThetasTrack'): #pragma: no cover self._store_closest() return None #Already did this # Setup the trackpoints where the kick will be computed, covering the # full length of the stream self._kick_interpolatedThetasTrack= \ numpy.linspace(self._gap_thetasTrack[0], self._gap_thetasTrack[-1], self._nKickPoints) TrackX= self._gap_ObsTrack[:,0]*numpy.cos(self._gap_ObsTrack[:,5]) TrackY= self._gap_ObsTrack[:,0]*numpy.sin(self._gap_ObsTrack[:,5]) TrackZ= self._gap_ObsTrack[:,3] TrackvX, TrackvY, TrackvZ=\ bovy_coords.cyl_to_rect_vec(self._gap_ObsTrack[:,1], self._gap_ObsTrack[:,2], self._gap_ObsTrack[:,4], self._gap_ObsTrack[:,5]) #Interpolate self._kick_interpTrackX=\ interpolate.InterpolatedUnivariateSpline(self._gap_thetasTrack, TrackX,k=3) self._kick_interpTrackY=\ interpolate.InterpolatedUnivariateSpline(self._gap_thetasTrack, TrackY,k=3) self._kick_interpTrackZ=\ interpolate.InterpolatedUnivariateSpline(self._gap_thetasTrack, TrackZ,k=3) self._kick_interpTrackvX=\ interpolate.InterpolatedUnivariateSpline(self._gap_thetasTrack, TrackvX,k=3) self._kick_interpTrackvY=\ interpolate.InterpolatedUnivariateSpline(self._gap_thetasTrack, TrackvY,k=3) self._kick_interpTrackvZ=\ interpolate.InterpolatedUnivariateSpline(self._gap_thetasTrack, TrackvZ,k=3) #Now store an interpolated version of the stream track self._kick_interpolatedObsTrackXY= numpy.empty((len(self._kick_interpolatedThetasTrack),6)) self._kick_interpolatedObsTrackXY[:,0]=\ self._kick_interpTrackX(self._kick_interpolatedThetasTrack) self._kick_interpolatedObsTrackXY[:,1]=\ self._kick_interpTrackY(self._kick_interpolatedThetasTrack) self._kick_interpolatedObsTrackXY[:,2]=\ self._kick_interpTrackZ(self._kick_interpolatedThetasTrack) self._kick_interpolatedObsTrackXY[:,3]=\ self._kick_interpTrackvX(self._kick_interpolatedThetasTrack) self._kick_interpolatedObsTrackXY[:,4]=\ self._kick_interpTrackvY(self._kick_interpolatedThetasTrack) self._kick_interpolatedObsTrackXY[:,5]=\ self._kick_interpTrackvZ(self._kick_interpolatedThetasTrack) #Also in cylindrical coordinates self._kick_interpolatedObsTrack= \ numpy.empty((len(self._kick_interpolatedThetasTrack),6)) tR,tphi,tZ= bovy_coords.rect_to_cyl(self._kick_interpolatedObsTrackXY[:,0], self._kick_interpolatedObsTrackXY[:,1], self._kick_interpolatedObsTrackXY[:,2]) tvR,tvT,tvZ=\ bovy_coords.rect_to_cyl_vec(self._kick_interpolatedObsTrackXY[:,3], self._kick_interpolatedObsTrackXY[:,4], self._kick_interpolatedObsTrackXY[:,5], tR,tphi,tZ,cyl=True) self._kick_interpolatedObsTrack[:,0]= tR self._kick_interpolatedObsTrack[:,1]= tvR self._kick_interpolatedObsTrack[:,2]= tvT self._kick_interpolatedObsTrack[:,3]= tZ self._kick_interpolatedObsTrack[:,4]= tvZ self._kick_interpolatedObsTrack[:,5]= tphi self._store_closest() return None def _store_closest(self): # Also store (x,v) for the point of closest approach self._kick_ObsTrackXY_closest= numpy.array([\ self._kick_interpTrackX(self._impact_angle), self._kick_interpTrackY(self._impact_angle), self._kick_interpTrackZ(self._impact_angle), self._kick_interpTrackvX(self._impact_angle), self._kick_interpTrackvY(self._impact_angle), self._kick_interpTrackvZ(self._impact_angle)]) return None def _interpolate_stream_track_kick_aA(self): """Build interpolations of the stream track near the impact in action-angle coordinates""" if hasattr(self,'_kick_interpolatedObsTrackAA'): #pragma: no cover return None #Already did this #Calculate 1D meanOmega on a fine grid in angle and interpolate dmOs= numpy.array([\ super(streamgapdf,self).meanOmega(da,oned=True, tdisrupt=self._tdisrupt -self._timpact, use_physical=False) for da in self._kick_interpolatedThetasTrack]) self._kick_interpTrackAAdmeanOmegaOneD=\ interpolate.InterpolatedUnivariateSpline(\ self._kick_interpolatedThetasTrack,dmOs,k=3) #Build the interpolated AA self._kick_interpolatedObsTrackAA=\ numpy.empty((len(self._kick_interpolatedThetasTrack),6)) for ii in range(len(self._kick_interpolatedThetasTrack)): self._kick_interpolatedObsTrackAA[ii,:3]=\ self._progenitor_Omega+dmOs[ii]*self._dsigomeanProgDirection\ *self._gap_sigMeanSign self._kick_interpolatedObsTrackAA[ii,3:]=\ self._progenitor_angle+self._kick_interpolatedThetasTrack[ii]\ *self._dsigomeanProgDirection*self._gap_sigMeanSign\ -self._timpact*self._progenitor_Omega self._kick_interpolatedObsTrackAA[ii,3:]=\ numpy.mod(self._kick_interpolatedObsTrackAA[ii,3:],2.*numpy.pi) return None def _determine_deltaAngleTrackImpact(self,deltaAngleTrackImpact,timpact): self._timpact= timpact deltaAngleTrackLim = (self._sigMeanOffset+4.) * numpy.sqrt( self._sortedSigOEig[2]) * (self._tdisrupt-self._timpact) if deltaAngleTrackImpact is None: deltaAngleTrackImpact= deltaAngleTrackLim else: if deltaAngleTrackImpact > deltaAngleTrackLim: warnings.warn("WARNING: deltaAngleTrackImpact angle range large compared to plausible value", galpyWarning) self._deltaAngleTrackImpact= deltaAngleTrackImpact return None def _determine_impact_coordtransform(self,deltaAngleTrackImpact, nTrackChunksImpact, timpact,impact_angle): """Function that sets up the transformation between (x,v) and (O,theta)""" # Integrate the progenitor backward to the time of impact self._gap_progenitor_setup() # Sign of delta angle tells us whether the impact happens to the # leading or trailing arm, self._sigMeanSign contains this info if impact_angle > 0.: self._gap_leading= True else: self._gap_leading= False if (self._gap_leading and not self._leading) \ or (not self._gap_leading and self._leading): raise ValueError('Modeling leading (trailing) impact for trailing (leading) arm; this is not allowed because it is nonsensical in this framework') self._gap_sigMeanSign= 1. if (self._gap_leading and self._progenitor_Omega_along_dOmega/self._sigMeanSign < 0.) \ or (not self._gap_leading and self._progenitor_Omega_along_dOmega/self._sigMeanSign > 0.): self._gap_sigMeanSign= -1. # Determine how much orbital time is necessary for the progenitor's orbit at the time of impact to cover the part of the stream near the impact; we cover the whole leading (or trailing) part of the stream if nTrackChunksImpact is None: #default is floor(self._deltaAngleTrackImpact/0.15)+1 self._nTrackChunksImpact= int(numpy.floor(self._deltaAngleTrackImpact/0.15))+1 else: self._nTrackChunksImpact= nTrackChunksImpact if self._nTrackChunksImpact < 4: self._nTrackChunksImpact= 4 dt= self._deltaAngleTrackImpact\ /self._progenitor_Omega_along_dOmega\ /self._sigMeanSign*self._gap_sigMeanSign self._gap_trackts= numpy.linspace(0.,2*dt,2*self._nTrackChunksImpact-1) #to be sure that we cover it #Instantiate an auxiliaryTrack, which is an Orbit instance at the mean frequency of the stream, and zero angle separation wrt the progenitor; prog_stream_offset is the offset between this track and the progenitor at zero angle (same as in streamdf, but just done at the time of impact rather than the current time) prog_stream_offset=\ _determine_stream_track_single(self._aA, self._gap_progenitor, self._timpact, # around the t of imp self._progenitor_angle-self._timpact*self._progenitor_Omega, self._gap_sigMeanSign, self._dsigomeanProgDirection, lambda da: super(streamgapdf,self).meanOmega(da,offset_sign=self._gap_sigMeanSign,tdisrupt=self._tdisrupt-self._timpact,use_physical=False), 0.) #angle = 0 auxiliaryTrack= Orbit(prog_stream_offset[3]) if dt < 0.: self._gap_trackts= numpy.linspace(0.,-2.*dt,2.*self._nTrackChunksImpact-1) #Flip velocities before integrating auxiliaryTrack= auxiliaryTrack.flip() auxiliaryTrack.integrate(self._gap_trackts,self._pot) if dt < 0.: #Flip velocities again auxiliaryTrack._orb.orbit[:,1]= -auxiliaryTrack._orb.orbit[:,1] auxiliaryTrack._orb.orbit[:,2]= -auxiliaryTrack._orb.orbit[:,2] auxiliaryTrack._orb.orbit[:,4]= -auxiliaryTrack._orb.orbit[:,4] #Calculate the actions, frequencies, and angle for this auxiliary orbit acfs= self._aA.actionsFreqs(auxiliaryTrack(0.),maxn=3, use_physical=False) auxiliary_Omega= numpy.array([acfs[3],acfs[4],acfs[5]]).reshape(3\ ) auxiliary_Omega_along_dOmega= \ numpy.dot(auxiliary_Omega,self._dsigomeanProgDirection) # compute the transformation using _determine_stream_track_single allAcfsTrack= numpy.empty((self._nTrackChunksImpact,9)) alljacsTrack= numpy.empty((self._nTrackChunksImpact,6,6)) allinvjacsTrack= numpy.empty((self._nTrackChunksImpact,6,6)) thetasTrack= numpy.linspace(0.,self._deltaAngleTrackImpact, self._nTrackChunksImpact) ObsTrack= numpy.empty((self._nTrackChunksImpact,6)) ObsTrackAA= numpy.empty((self._nTrackChunksImpact,6)) detdOdJps= numpy.empty((self._nTrackChunksImpact)) if self._multi is None: for ii in range(self._nTrackChunksImpact): multiOut= _determine_stream_track_single(self._aA, auxiliaryTrack, self._gap_trackts[ii]*numpy.fabs(self._progenitor_Omega_along_dOmega/auxiliary_Omega_along_dOmega), #this factor accounts for the difference in frequency between the progenitor and the auxiliary track, no timpact bc gap_tracks is relative to timpact self._progenitor_angle-self._timpact*self._progenitor_Omega, self._gap_sigMeanSign, self._dsigomeanProgDirection, lambda da: super(streamgapdf,self).meanOmega(da,offset_sign=self._gap_sigMeanSign,tdisrupt=self._tdisrupt-self._timpact,use_physical=False), thetasTrack[ii]) allAcfsTrack[ii,:]= multiOut[0] alljacsTrack[ii,:,:]= multiOut[1] allinvjacsTrack[ii,:,:]= multiOut[2] ObsTrack[ii,:]= multiOut[3] ObsTrackAA[ii,:]= multiOut[4] detdOdJps[ii]= multiOut[5] else: multiOut= multi.parallel_map(\ (lambda x: _determine_stream_track_single(self._aA, auxiliaryTrack, self._gap_trackts[x]*numpy.fabs(self._progenitor_Omega_along_dOmega/auxiliary_Omega_along_dOmega), #this factor accounts for the difference in frequency between the progenitor and the auxiliary track, no timpact bc gap_tracks is relative to timpact self._progenitor_angle-self._timpact*self._progenitor_Omega, self._gap_sigMeanSign, self._dsigomeanProgDirection, lambda da: super(streamgapdf,self).meanOmega(da,offset_sign=self._gap_sigMeanSign,tdisrupt=self._tdisrupt-self._timpact,use_physical=False), thetasTrack[x])), range(self._nTrackChunksImpact), numcores=numpy.amin([self._nTrackChunksImpact, multiprocessing.cpu_count(), self._multi])) for ii in range(self._nTrackChunksImpact): allAcfsTrack[ii,:]= multiOut[ii][0] alljacsTrack[ii,:,:]= multiOut[ii][1] allinvjacsTrack[ii,:,:]= multiOut[ii][2] ObsTrack[ii,:]= multiOut[ii][3] ObsTrackAA[ii,:]= multiOut[ii][4] detdOdJps[ii]= multiOut[ii][5] #Repeat the track calculation using the previous track, to get closer to it for nn in range(self.nTrackIterations): if self._multi is None: for ii in range(self._nTrackChunksImpact): multiOut= _determine_stream_track_single(self._aA, Orbit(ObsTrack[ii,:]), 0., self._progenitor_angle-self._timpact*self._progenitor_Omega, self._gap_sigMeanSign, self._dsigomeanProgDirection, lambda da: super(streamgapdf,self).meanOmega(da,offset_sign=self._gap_sigMeanSign,tdisrupt=self._tdisrupt-self._timpact,use_physical=False), thetasTrack[ii]) allAcfsTrack[ii,:]= multiOut[0] alljacsTrack[ii,:,:]= multiOut[1] allinvjacsTrack[ii,:,:]= multiOut[2] ObsTrack[ii,:]= multiOut[3] ObsTrackAA[ii,:]= multiOut[4] detdOdJps[ii]= multiOut[5] else: multiOut= multi.parallel_map(\ (lambda x: _determine_stream_track_single(self._aA,Orbit(ObsTrack[x,:]),0., self._progenitor_angle-self._timpact*self._progenitor_Omega, self._gap_sigMeanSign, self._dsigomeanProgDirection, lambda da: super(streamgapdf,self).meanOmega(da,offset_sign=self._gap_sigMeanSign,tdisrupt=self._tdisrupt-self._timpact,use_physical=False), thetasTrack[x])), range(self._nTrackChunksImpact), numcores=numpy.amin([self._nTrackChunksImpact, multiprocessing.cpu_count(), self._multi])) for ii in range(self._nTrackChunksImpact): allAcfsTrack[ii,:]= multiOut[ii][0] alljacsTrack[ii,:,:]= multiOut[ii][1] allinvjacsTrack[ii,:,:]= multiOut[ii][2] ObsTrack[ii,:]= multiOut[ii][3] ObsTrackAA[ii,:]= multiOut[ii][4] detdOdJps[ii]= multiOut[ii][5] #Store the track self._gap_thetasTrack= thetasTrack self._gap_ObsTrack= ObsTrack self._gap_ObsTrackAA= ObsTrackAA self._gap_allAcfsTrack= allAcfsTrack self._gap_alljacsTrack= alljacsTrack self._gap_allinvjacsTrack= allinvjacsTrack self._gap_detdOdJps= detdOdJps self._gap_meandetdOdJp= numpy.mean(self._gap_detdOdJps) self._gap_logmeandetdOdJp= numpy.log(self._gap_meandetdOdJp) #Also calculate _ObsTrackXY in XYZ,vXYZ coordinates self._gap_ObsTrackXY= numpy.empty_like(self._gap_ObsTrack) TrackX= self._gap_ObsTrack[:,0]*numpy.cos(self._gap_ObsTrack[:,5]) TrackY= self._gap_ObsTrack[:,0]*numpy.sin(self._gap_ObsTrack[:,5]) TrackZ= self._gap_ObsTrack[:,3] TrackvX, TrackvY, TrackvZ=\ bovy_coords.cyl_to_rect_vec(self._gap_ObsTrack[:,1], self._gap_ObsTrack[:,2], self._gap_ObsTrack[:,4], self._gap_ObsTrack[:,5]) self._gap_ObsTrackXY[:,0]= TrackX self._gap_ObsTrackXY[:,1]= TrackY self._gap_ObsTrackXY[:,2]= TrackZ self._gap_ObsTrackXY[:,3]= TrackvX self._gap_ObsTrackXY[:,4]= TrackvY self._gap_ObsTrackXY[:,5]= TrackvZ return None def _gap_progenitor_setup(self): """Setup an Orbit instance that's the progenitor integrated backwards""" self._gap_progenitor= self._progenitor().flip() # new orbit, flip velocities # Make sure we do not use physical coordinates self._gap_progenitor.turn_physical_off() # Now integrate backward in time until tdisrupt ts= numpy.linspace(0.,self._tdisrupt,1001) self._gap_progenitor.integrate(ts,self._pot) # Flip its velocities, should really write a function for this self._gap_progenitor._orb.orbit[:,1]= -self._gap_progenitor._orb.orbit[:,1] self._gap_progenitor._orb.orbit[:,2]= -self._gap_progenitor._orb.orbit[:,2] self._gap_progenitor._orb.orbit[:,4]= -self._gap_progenitor._orb.orbit[:,4] return None ################################SAMPLE THE DF################################## def _sample_aAt(self,n): """Sampling frequencies, angles, and times part of sampling, for stream with gap""" # Use streamdf's _sample_aAt to generate unperturbed frequencies, # angles Om,angle,dt= super(streamgapdf,self)._sample_aAt(n) # Now rewind angles by timpact, apply the kicks, and run forward again dangle_at_impact= angle-numpy.tile(self._progenitor_angle.T,(n,1)).T\ -(Om-numpy.tile(self._progenitor_Omega.T,(n,1)).T)*self._timpact dangle_par_at_impact= numpy.dot(dangle_at_impact.T, self._dsigomeanProgDirection)\ *self._gap_sigMeanSign # Calculate and apply kicks (points not yet released have zero kick) dOr= self._kick_interpdOr(dangle_par_at_impact) dOp= self._kick_interpdOp(dangle_par_at_impact) dOz= self._kick_interpdOz(dangle_par_at_impact) Om[0,:]+= dOr Om[1,:]+= dOp Om[2,:]+= dOz angle[0,:]+=\ self._kick_interpdar(dangle_par_at_impact)+dOr*self._timpact angle[1,:]+=\ self._kick_interpdap(dangle_par_at_impact)+dOp*self._timpact angle[2,:]+=\ self._kick_interpdaz(dangle_par_at_impact)+dOz*self._timpact return (Om,angle,dt) def impulse_deltav_plummer(v,y,b,w,GM,rs): """ NAME: impulse_deltav_plummer PURPOSE: calculate the delta velocity to due an encounter with a Plummer sphere in the impulse approximation; allows for arbitrary velocity vectors, but y is input as the position along the stream INPUT: v - velocity of the stream (nstar,3) y - position along the stream (nstar) b - impact parameter w - velocity of the Plummer sphere (3) GM - mass of the Plummer sphere (in natural units) rs - size of the Plummer sphere OUTPUT: deltav (nstar,3) HISTORY: 2015-04-30 - Written based on Erkal's expressions - Bovy (IAS) """ if len(v.shape) == 1: v= numpy.reshape(v,(1,3)) y= numpy.reshape(y,(1,1)) nv= v.shape[0] # Build the rotation matrices and their inverse rot= _rotation_vy(v) rotinv= _rotation_vy(v,inv=True) # Rotate the Plummer sphere's velocity to the stream frames tilew= numpy.sum(rot*numpy.tile(w,(nv,3,1)),axis=-1) # Use Denis' expressions wperp= numpy.sqrt(tilew[:,0]**2.+tilew[:,2]**2.) wpar= numpy.sqrt(numpy.sum(v**2.,axis=1))-tilew[:,1] wmag2= wpar**2.+wperp**2. wmag= numpy.sqrt(wmag2) out= numpy.empty_like(v) denom= wmag*((b**2.+rs**2.)*wmag2+wperp**2.*y**2.) out[:,0]= (b*wmag2*tilew[:,2]/wperp-y*wpar*tilew[:,0])/denom out[:,1]= -wperp**2.*y/denom out[:,2]= -(b*wmag2*tilew[:,0]/wperp+y*wpar*tilew[:,2])/denom # deal w/ perpendicular impacts wperp0Indx= numpy.fabs(wperp) < 10.**-10. out[wperp0Indx,0]= (b*wmag2[wperp0Indx]-y[wperp0Indx]*wpar[wperp0Indx]*tilew[wperp0Indx,0])/denom[wperp0Indx] out[wperp0Indx,2]=-(b*wmag2[wperp0Indx]+y[wperp0Indx]*wpar[wperp0Indx]*tilew[wperp0Indx,2])/denom[wperp0Indx] # Rotate back to the original frame return 2.0*GM*numpy.sum(\ rotinv*numpy.swapaxes(numpy.tile(out.T,(3,1,1)).T,1,2),axis=-1) def impulse_deltav_plummer_curvedstream(v,x,b,w,x0,v0,GM,rs): """ NAME: impulse_deltav_plummer_curvedstream PURPOSE: calculate the delta velocity to due an encounter with a Plummer sphere in the impulse approximation; allows for arbitrary velocity vectors, and arbitrary position along the stream INPUT: v - velocity of the stream (nstar,3) x - position along the stream (nstar,3) b - impact parameter w - velocity of the Plummer sphere (3) x0 - point of closest approach v0 - velocity of point of closest approach GM - mass of the Plummer sphere (in natural units) rs - size of the Plummer sphere OUTPUT: deltav (nstar,3) HISTORY: 2015-05-04 - Written based on above - SANDERS """ if len(v.shape) == 1: v= numpy.reshape(v,(1,3)) if len(x.shape) == 1: x= numpy.reshape(x,(1,3)) b0 = numpy.cross(w,v0) b0 *= b/numpy.sqrt(numpy.sum(b0**2)) b_ = b0+x-x0 w = w-v wmag = numpy.sqrt(numpy.sum(w**2,axis=1)) bdotw=numpy.sum(b_*w,axis=1)/wmag denom= wmag*(numpy.sum(b_**2,axis=1)+rs**2-bdotw**2) denom = 1./denom return -2.0*GM*((b_.T-bdotw*w.T/wmag)*denom).T def HernquistX(s): """ Computes X function from equations (33) & (34) of Hernquist (1990) """ if(s<0.): raise ValueError("s must be positive in Hernquist X function") elif(s<1.): return numpy.log((1+numpy.sqrt(1-s*s))/s)/numpy.sqrt(1-s*s) elif(s==1.): return 1. else: return numpy.arccos(1./s)/numpy.sqrt(s*s-1) def impulse_deltav_hernquist(v,y,b,w,GM,rs): """ NAME: impulse_deltav_hernquist PURPOSE: calculate the delta velocity to due an encounter with a Hernquist sphere in the impulse approximation; allows for arbitrary velocity vectors, but y is input as the position along the stream INPUT: v - velocity of the stream (nstar,3) y - position along the stream (nstar) b - impact parameter w - velocity of the Hernquist sphere (3) GM - mass of the Hernquist sphere (in natural units) rs - size of the Hernquist sphere OUTPUT: deltav (nstar,3) HISTORY: 2015-08-13 SANDERS, using Wyn Evans calculation """ if len(v.shape) == 1: v= numpy.reshape(v,(1,3)) nv= v.shape[0] # Build the rotation matrices and their inverse rot= _rotation_vy(v) rotinv= _rotation_vy(v,inv=True) # Rotate the Plummer sphere's velocity to the stream frames tilew= numpy.sum(rot*numpy.tile(w,(nv,3,1)),axis=-1) wperp= numpy.sqrt(tilew[:,0]**2.+tilew[:,2]**2.) wpar= numpy.sqrt(numpy.sum(v**2.,axis=1))-tilew[:,1] wmag2= wpar**2.+wperp**2. wmag= numpy.sqrt(wmag2) B = numpy.sqrt(b**2.+wperp**2.*y**2./wmag2) denom= wmag*(B**2-rs**2) denom=1./denom s = numpy.sqrt(2.*B/(rs+B)) HernquistXv=numpy.vectorize(HernquistX) Xfac = 1.-2.*rs/(rs+B)*HernquistXv(s) out= numpy.empty_like(v) out[:,0]= (b*tilew[:,2]/wperp-y*wpar*tilew[:,0]/wmag2)*denom*Xfac out[:,1]= -wperp**2.*y*denom*Xfac/wmag2 out[:,2]= -(b*tilew[:,0]/wperp+y*wpar*tilew[:,2]/wmag2)*denom*Xfac # deal w/ perpendicular impacts wperp0Indx= numpy.fabs(wperp) < 10.**-10. out[wperp0Indx,0]= (b-y[wperp0Indx]*wpar[wperp0Indx]*tilew[wperp0Indx,0]/wmag2[wperp0Indx])*denom[wperp0Indx]*Xfac[wperp0Indx] out[wperp0Indx,2]=-(b+y[wperp0Indx]*wpar[wperp0Indx]*tilew[wperp0Indx,2]/wmag2[wperp0Indx])*denom[wperp0Indx]*Xfac[wperp0Indx] # Rotate back to the original frame return 2.0*GM*numpy.sum(\ rotinv*numpy.swapaxes(numpy.tile(out.T,(3,1,1)).T,1,2),axis=-1) def impulse_deltav_hernquist_curvedstream(v,x,b,w,x0,v0,GM,rs): """ NAME: impulse_deltav_plummer_hernquist PURPOSE: calculate the delta velocity to due an encounter with a Hernquist sphere in the impulse approximation; allows for arbitrary velocity vectors, and arbitrary position along the stream INPUT: v - velocity of the stream (nstar,3) x - position along the stream (nstar,3) b - impact parameter w - velocity of the Hernquist sphere (3) x0 - point of closest approach v0 - velocity of point of closest approach GM - mass of the Hernquist sphere (in natural units) rs - size of the Hernquist sphere OUTPUT: deltav (nstar,3) HISTORY: 2015-08-13 - SANDERS, using Wyn Evans calculation """ if len(v.shape) == 1: v= numpy.reshape(v,(1,3)) if len(x.shape) == 1: x= numpy.reshape(x,(1,3)) b0 = numpy.cross(w,v0) b0 *= b/numpy.sqrt(numpy.sum(b0**2)) b_ = b0+x-x0 w = w-v wmag = numpy.sqrt(numpy.sum(w**2,axis=1)) bdotw=numpy.sum(b_*w,axis=1)/wmag B = numpy.sqrt(numpy.sum(b_**2,axis=1)-bdotw**2) denom= wmag*(B**2-rs**2) denom=1./denom s = numpy.sqrt(2.*B/(rs+B)) HernquistXv=numpy.vectorize(HernquistX) Xfac = 1.-2.*rs/(rs+B)*HernquistXv(s) return -2.0*GM*((b_.T-bdotw*w.T/wmag)*Xfac*denom).T def _a_integrand(T,y,b,w,pot,compt): t = T/(1-T*T) X = b+w*t+y*numpy.array([0,1,0]) r = numpy.sqrt(numpy.sum(X**2)) return (1+T*T)/(1-T*T)**2*evaluateRforces(pot,r,0.)*X[compt]/r def _deltav_integrate(y,b,w,pot): return numpy.array([integrate.quad(_a_integrand,-1.,1.,args=(y,b,w,pot,i))[0] for i in range(3)]) def impulse_deltav_general(v,y,b,w,pot): """ NAME: impulse_deltav_general PURPOSE: calculate the delta velocity to due an encounter with a general spherical potential in the impulse approximation; allows for arbitrary velocity vectors, but y is input as the position along the stream INPUT: v - velocity of the stream (nstar,3) y - position along the stream (nstar) b - impact parameter w - velocity of the subhalo (3) pot - Potential object or list thereof (should be spherical) OUTPUT: deltav (nstar,3) HISTORY: 2015-05-04 - SANDERS 2015-06-15 - Tweak to use galpy' potential objects - Bovy (IAS) """ if len(v.shape) == 1: v= numpy.reshape(v,(1,3)) nv= v.shape[0] # Build the rotation matrices and their inverse rot= _rotation_vy(v) rotinv= _rotation_vy(v,inv=True) # Rotate the subhalo's velocity to the stream frames tilew= numpy.sum(rot*numpy.tile(w,(nv,3,1)),axis=-1) tilew[:,1]-=numpy.sqrt(numpy.sum(v**2.,axis=1)) wmag = numpy.sqrt(tilew[:,0]**2+tilew[:,2]**2) b0 = b*numpy.array([-tilew[:,2]/wmag,numpy.zeros(nv),tilew[:,0]/wmag]).T return numpy.array(list(map(lambda i:numpy.sum(i[3] *_deltav_integrate(i[0],i[1],i[2],pot).T,axis=-1) ,zip(y,b0,tilew,rotinv)))) def impulse_deltav_general_curvedstream(v,x,b,w,x0,v0,pot): """ NAME: impulse_deltav_general_curvedstream PURPOSE: calculate the delta velocity to due an encounter with a general spherical potential in the impulse approximation; allows for arbitrary velocity vectors and arbitrary shaped streams INPUT: v - velocity of the stream (nstar,3) x - position along the stream (nstar,3) b - impact parameter w - velocity of the subhalo (3) x0 - position of closest approach (3) v0 - velocity of stream at closest approach (3) pot - Potential object or list thereof (should be spherical) OUTPUT: deltav (nstar,3) HISTORY: 2015-05-04 - SANDERS 2015-06-15 - Tweak to use galpy' potential objects - Bovy (IAS) """ if len(v.shape) == 1: v= numpy.reshape(v,(1,3)) if len(x.shape) == 1: x= numpy.reshape(x,(1,3)) b0 = numpy.cross(w,v0) b0 *= b/numpy.sqrt(numpy.sum(b0**2)) b_ = b0+x-x0 return numpy.array(list(map(lambda i:_deltav_integrate(0.,i[1],i[0],pot) ,zip(w-v,b_)))) def impulse_deltav_general_orbitintegration(v,x,b,w,x0,v0,pot,tmax,galpot, tmaxfac=10.,nsamp=1000, integrate_method='symplec4_c'): """ NAME: impulse_deltav_general_orbitintegration PURPOSE: calculate the delta velocity to due an encounter with a general spherical potential NOT in the impulse approximation by integrating each particle in the underlying galactic potential; allows for arbitrary velocity vectors and arbitrary shaped streams. INPUT: v - velocity of the stream (nstar,3) x - position along the stream (nstar,3) b - impact parameter w - velocity of the subhalo (3) x0 - position of closest approach (3) v0 - velocity of stream at closest approach (3) pot - Potential object or list thereof (should be spherical) tmax - maximum integration time galpot - galpy Potential object or list thereof nsamp(1000) - number of forward integration points integrate_method= ('symplec4_c') orbit integrator to use (see Orbit.integrate) OUTPUT: deltav (nstar,3) HISTORY: 2015-08-17 - SANDERS """ if len(v.shape) == 1: v= numpy.reshape(v,(1,3)) if len(x.shape) == 1: x= numpy.reshape(x,(1,3)) nstar,ndim=numpy.shape(v) b0 = numpy.cross(w,v0) b0 *= b/numpy.sqrt(numpy.sum(b0**2)) times = numpy.linspace(0.,tmax,nsamp) xres = numpy.zeros(shape=(len(x),nsamp*2-1,3)) R, phi, z= bovy_coords.rect_to_cyl(x[:,0],x[:,1],x[:,2]) vR, vp, vz= bovy_coords.rect_to_cyl_vec(v[:,0],v[:,1],v[:,2], R,phi,z,cyl=True) for i in range(nstar): o = Orbit([R[i],vR[i],vp[i],z[i],vz[i],phi[i]]) o.integrate(times,galpot,method=integrate_method) xres[i,nsamp:,0]=o.x(times)[1:] xres[i,nsamp:,1]=o.y(times)[1:] xres[i,nsamp:,2]=o.z(times)[1:] oreverse = o.flip() oreverse.integrate(times,galpot,method=integrate_method) xres[i,:nsamp,0]=oreverse.x(times)[::-1] xres[i,:nsamp,1]=oreverse.y(times)[::-1] xres[i,:nsamp,2]=oreverse.z(times)[::-1] times = numpy.concatenate((-times[::-1],times[1:])) nsamp = len(times) X = b0+xres-x0-numpy.outer(times,w) r = numpy.sqrt(numpy.sum(X**2,axis=-1)) acc = (numpy.reshape(evaluateRforces(pot,r.flatten(),0.),(nstar,nsamp))/r)[:,:,numpy.newaxis]*X return integrate.simps(acc,x=times,axis=1) def impulse_deltav_general_fullplummerintegration(v,x,b,w,x0,v0,galpot,GM,rs, tmaxfac=10.,N=1000, integrate_method='symplec4_c'): """ NAME: impulse_deltav_general_fullplummerintegration PURPOSE: calculate the delta velocity to due an encounter with a moving Plummer sphere and galactic potential relative to just in galactic potential INPUT: v - velocity of the stream (nstar,3) x - position along the stream (nstar,3) b - impact parameter w - velocity of the subhalo (3) x0 - position of closest approach (3) v0 - velocity of stream at closest approach (3) galpot - Galaxy Potential object GM - mass of Plummer rs - scale of Plummer tmaxfac(10) - multiple of rs/fabs(w - v0) to use for time integration interval N(1000) - number of forward integration points integrate_method('symplec4_c') - orbit integrator to use (see Orbit.integrate) OUTPUT: deltav (nstar,3) HISTORY: 2015-08-18 - SANDERS """ if len(v.shape) == 1: v= numpy.reshape(v,(1,3)) if len(x.shape) == 1: x= numpy.reshape(x,(1,3)) nstar,ndim=numpy.shape(v) b0 = numpy.cross(w,v0) b0 *= b/numpy.sqrt(numpy.sum(b0**2)) X = x0-b0 # Setup Plummer orbit R, phi, z= bovy_coords.rect_to_cyl(X[0],X[1],X[2]) vR, vp, vz= bovy_coords.rect_to_cyl_vec(w[0],w[1],w[2],R,phi,z,cyl=True) tmax = tmaxfac*rs/numpy.sqrt(numpy.sum((w-v0)**2)) times = numpy.linspace(0.,tmax,N) dtimes = numpy.linspace(-tmax,tmax,2*N) o = Orbit(vxvv=[R,-vR,-vp,z,-vz,phi]) o.integrate(times,galpot,method=integrate_method) oplum = o(times[-1]).flip() oplum.integrate(dtimes,galpot,method=integrate_method) plumpot = MovingObjectPotential(orbit=oplum, GM=GM, softening_model='plummer', softening_length=rs) # Now integrate each particle backwards in galaxy potential, forwards in combined potential and backwards again in galaxy and take diff deltav = numpy.zeros((nstar,3)) R, phi, z= bovy_coords.rect_to_cyl(x[:,0],x[:,1],x[:,2]) vR, vp, vz= bovy_coords.rect_to_cyl_vec(v[:,0],v[:,1],v[:,2], R,phi,z,cyl=True) for i in range(nstar): ostar= Orbit(vxvv=[R[i],-vR[i],-vp[i],z[i],-vz[i],phi[i]]) ostar.integrate(times,galpot,method=integrate_method) oboth = ostar(times[-1]).flip() oboth.integrate(dtimes,[galpot,plumpot],method=integrate_method) ogalpot = oboth(times[-1]).flip() ogalpot.integrate(times,galpot,method=integrate_method) deltav[i][0] = -ogalpot.vx(times[-1]) - v[i][0] deltav[i][1] = -ogalpot.vy(times[-1]) - v[i][1] deltav[i][2] = -ogalpot.vz(times[-1]) - v[i][2] return deltav def _astream_integrand_x(t,y,v,b,w,b2,w2,wperp,wperp2,wpar,GSigma,rs2): return GSigma(t)*(b*w2*w[2]/wperp-(y-v*t)*wpar*w[0])\ /((b2+rs2)*w2+wperp2*(y-v*t)**2.) def _astream_integrand_y(t,y,v,b2,w2,wperp2,GSigma,rs2): return GSigma(t)*(y-v*t)/((b2+rs2)*w2+wperp2*(y-v*t)**2.) def _astream_integrand_z(t,y,v,b,w,b2,w2,wperp,wperp2,wpar,GSigma,rs2): return -GSigma(t)*(b*w2*w[0]/wperp+(y-v*t)*wpar*w[2])\ /((b2+rs2)*w2+wperp2*(y-v*t)**2.) def impulse_deltav_plummerstream(v,y,b,w,GSigma,rs,tmin=None,tmax=None): """ NAME: impulse_deltav_plummerstream PURPOSE: calculate the delta velocity to due an encounter with a Plummer-softened stream in the impulse approximation; allows for arbitrary velocity vectors, but y is input as the position along the stream INPUT: v - velocity of the stream (nstar,3) y - position along the stream (nstar) b - impact parameter w - velocity of the Plummer sphere (3) GSigma - surface density of the Plummer-softened stream (in natural units); should be a function of time rs - size of the Plummer sphere tmin, tmax= (None) minimum and maximum time to consider for GSigma (need to be set) OUTPUT: deltav (nstar,3) HISTORY: 2015-11-14 - Written - Bovy (UofT) """ if len(v.shape) == 1: v= numpy.reshape(v,(1,3)) y= numpy.reshape(y,(1,1)) if tmax is None or tmax is None: raise ValueError("tmin= and tmax= need to be set") nv= v.shape[0] vmag= numpy.sqrt(numpy.sum(v**2.,axis=1)) # Build the rotation matrices and their inverse rot= _rotation_vy(v) rotinv= _rotation_vy(v,inv=True) # Rotate the perturbing stream's velocity to the stream frames tilew= numpy.sum(rot*numpy.tile(w,(nv,3,1)),axis=-1) # Use similar expressions to Denis' wperp= numpy.sqrt(tilew[:,0]**2.+tilew[:,2]**2.) wpar= numpy.sqrt(numpy.sum(v**2.,axis=1))-tilew[:,1] wmag2= wpar**2.+wperp**2. wmag= numpy.sqrt(wmag2) b2= b**2. rs2= rs**2. wperp2= wperp**2. out= numpy.empty_like(v) out[:,0]= [1./wmag[ii]\ *integrate.quad(_astream_integrand_x, tmin,tmax,args=(y[ii], vmag[ii],b,tilew[ii], b2,wmag2[ii], wperp[ii],wperp2[ii], wpar[ii], GSigma,rs2))[0] for ii in range(len(y))] out[:,1]= [-wperp2[ii]/wmag[ii]\ *integrate.quad(_astream_integrand_y, tmin,tmax,args=(y[ii], vmag[ii], b2,wmag2[ii], wperp2[ii], GSigma,rs2))[0] for ii in range(len(y))] out[:,2]= [1./wmag[ii]\ *integrate.quad(_astream_integrand_z , tmin,tmax,args=(y[ii], vmag[ii],b,tilew[ii], b2,wmag2[ii], wperp[ii],wperp2[ii], wpar[ii], GSigma,rs2))[0] for ii in range(len(y))] # Rotate back to the original frame return 2.0*numpy.sum(\ rotinv*numpy.swapaxes(numpy.tile(out.T,(3,1,1)).T,1,2),axis=-1) def _astream_integrand(t,b_,orb,tx,w,GSigma,rs2,tmin,compt): teval= tx-tmin-t b__= b_+numpy.array([orb.x(teval),orb.y(teval),orb.z(teval)]) w = w-numpy.array([orb.vx(teval),orb.vy(teval),orb.vz(teval)]) wmag = numpy.sqrt(numpy.sum(w**2)) bdotw=numpy.sum(b__*w)/wmag denom= wmag*(numpy.sum(b__**2)+rs2-bdotw**2) denom = 1./denom return -2.0*GSigma(t)*(((b__.T-bdotw*w.T/wmag)*denom).T)[compt] def _astream_integrate(b_,orb,tx,w,GSigma,rs2,otmin,tmin,tmax): return numpy.array([integrate.quad(_astream_integrand,tmin,tmax, args=(b_,orb,tx,w,GSigma,rs2, otmin,i))[0] \ for i in range(3)]) def impulse_deltav_plummerstream_curvedstream(v,x,t,b,w,x0,v0,GSigma,rs, galpot,tmin=None,tmax=None): """ NAME: impulse_deltav_plummerstream_curvedstream PURPOSE: calculate the delta velocity to due an encounter with a Plummer sphere in the impulse approximation; allows for arbitrary velocity vectors, and arbitrary position along the stream; velocities and positions are assumed to lie along an orbit INPUT: v - velocity of the stream (nstar,3) x - position along the stream (nstar,3) t - times at which (v,x) are reached, wrt the closest impact t=0 (nstar) b - impact parameter w - velocity of the Plummer sphere (3) x0 - point of closest approach v0 - velocity of point of closest approach GSigma - surface density of the Plummer-softened stream (in natural units); should be a function of time rs - size of the Plummer sphere galpot - galpy Potential object or list thereof tmin, tmax= (None) minimum and maximum time to consider for GSigma OUTPUT: deltav (nstar,3) HISTORY: 2015-11-20 - Written based on Plummer sphere above - Bovy (UofT) """ if len(v.shape) == 1: v= numpy.reshape(v,(1,3)) if len(x.shape) == 1: x= numpy.reshape(x,(1,3)) # Integrate an orbit to use to figure out where each (v,x) is at each time R, phi, z= bovy_coords.rect_to_cyl(x0[0],x0[1],x0[2]) vR, vT, vz= bovy_coords.rect_to_cyl_vec(v0[0],v0[1],v0[2],R,phi,z,cyl=True) # First back, then forward to cover the entire range with 1 orbit o= Orbit([R,vR,vT,z,vz,phi]).flip() ts= numpy.linspace(0.,numpy.fabs(numpy.amin(t)+tmin),101) o.integrate(ts,galpot) o= o(ts[-1]).flip() ts= numpy.linspace(0.,numpy.amax(t)+tmax-numpy.amin(t)-tmin,201) o.integrate(ts,galpot) # Calculate kicks b0 = numpy.cross(w,v0) b0 *= b/numpy.sqrt(numpy.sum(b0**2)) return numpy.array(list(map(lambda i:_astream_integrate(\ b0-x0,o,i,w,GSigma,rs**2.,numpy.amin(t)+tmin, tmin,tmax),t))) def _rotation_vy(v,inv=False): return _rotate_to_arbitrary_vector(v,[0,1,0],inv) galpy-1.3.0/galpy/df_src/surfaceSigmaProfile.py0000644000076500000240000001306612671130470021620 0ustar bovystaff00000000000000############################################################################### # surfaceSigmaProfile: classes that implement different surface-mass and # radial velocity dispersion profiles # # Includes the following: # surfaceSigmaProfile - top-level class that represents a surface # density profile and a sigma_R profile # expSurfaceSigmaProfile - class that represents an exponential surface # density profile and an exponential sigma_R # profile ############################################################################### import scipy as sc class surfaceSigmaProfile(object): """Class that contains the surface density and sigma_R^2 profile""" def __init__(self): """Place holder for implementations of this class""" return None def formatStringParams(self): """ NAME: formatStringParams PURPOSE: when writing the parameters of this profile, what format-strings to use? This function defaults to '%6.4f' for each parameter in self._params INPUT: OUTPUT: tuple of format-strings HISTORY: 2010-03-28 - Written - Bovy (NYU) """ out= [] for param in self._params: out.append('%6.4f') return out def outputParams(self): """ NAME: outputParams PURPOSE:' return a list of parameters of this profile, to create filenames INPUT: OUTPUT: tuple of parameters (given to % ...) HISTORY: 2010-03-28 - Written - Bovy """ return tuple(self._params) def surfacemass(self,R,log=False): """ NAME: surfacemass PURPOSE: return the surface density profile at this R INPUT: R - Galactocentric radius (/ro) log - if True, return the log (default: False) OUTPUT: Sigma(R) HISTORY: 2010-03-26 - Written - Bovy (NYU) """ raise NotImplementedError("'surfacemass' function not implemented for this surfaceSigmaProfile class") def sigma2(self,R,log=False): """ NAME: sigma2 PURPOSE: return the radial velocity variance at this R INPUT: R - Galactocentric radius (/ro) log - if True, return the log (default: False) OUTPUT: sigma^2(R) HISTORY: 2010-03-26 - Written - Bovy (NYU) """ raise NotImplementedError("'sigma2' function not implemented for this surfaceSigmaProfile class") class expSurfaceSigmaProfile(surfaceSigmaProfile): """Exponential surface density and sigma_R^2 class""" def __init__(self,params=(1./3.,1.0,0.2)): """ NAME: __init__ PURPOSE: initialize an exponential surface density and sigma_R^2 profile INPUT: params - tuple/list of (surface scale-length,sigma scale-length, sigma(ro)) (NOTE: *not* sigma2 scale-length) OUTPUT: HISTORY: 2010-03-26 - Written - Bovy (NYU) """ surfaceSigmaProfile.__init__(self) self._params= params def surfacemass(self,R,log=False): """ NAME: surfacemass PURPOSE: return the surface density profile at this R INPUT: R - Galactocentric radius (/ro) log - if True, return the log (default: False) OUTPUT: Sigma(R) HISTORY: 2010-03-26 - Written - Bovy (NYU) """ if log: return -R/self._params[0] else: return sc.exp(-R/self._params[0]) def surfacemassDerivative(self,R,log=False): """ NAME: surfacemassDerivative PURPOSE: return the derivative wrt R of the surface density profile at this R INPUT: R - Galactocentric radius (/ro) log - if True, return the derivative of the log (default: False) OUTPUT: Sigma'(R) or (log Sigma(r) )' HISTORY: 2010-03-26 - Written - Bovy (NYU) """ if log: return -1./self._params[0] else: return -sc.exp(-R/self._params[0])/self._params[0] def sigma2(self,R,log=False): """ NAME: sigma2 PURPOSE: return the radial velocity variance at this R INPUT: R - Galactocentric radius (/ro) log - if True, return the log (default: False) OUTPUT: sigma^2(R) HISTORY: 2010-03-26 - Written - Bovy (NYU) """ if log: return 2.*sc.log(self._params[2])-2.*(R-1.)/self._params[1] else: return self._params[2]**2.*sc.exp(-2.*(R-1.)/self._params[1]) def sigma2Derivative(self,R,log=False): """ NAME: sigmaDerivative PURPOSE: return the derivative wrt R of the sigma_R^2 profile at this R INPUT: R - Galactocentric radius (/ro) log - if True, return the derivative of the log (default: False) OUTPUT: Sigma_R^2'(R) or (log Sigma_R^2(r) )' HISTORY: 2011-03-24 - Written - Bovy (NYU) """ if log: return -2./self._params[1] else: return self._params[2]**2.*sc.exp(-2.*(R-1.)/self._params[1])\ *(-2./self._params[1]) galpy-1.3.0/galpy/orbit.py0000644000076500000240000000013512671130470015536 0ustar bovystaff00000000000000from galpy.orbit_src import Orbit # # Functions # #none # # Classes # Orbit= Orbit.Orbit galpy-1.3.0/galpy/orbit_src/0000755000076500000240000000000013236420072016032 5ustar bovystaff00000000000000galpy-1.3.0/galpy/orbit_src/__init__.py0000644000076500000240000000000012671130470020133 0ustar bovystaff00000000000000galpy-1.3.0/galpy/orbit_src/FullOrbit.py0000644000076500000240000010322713236414472020322 0ustar bovystaff00000000000000import warnings import math as m import numpy as nu from scipy import integrate, optimize import scipy _SCIPY_VERSION= [int(v.split('rc')[0]) for v in scipy.__version__.split('.')] if _SCIPY_VERSION[0] < 1 and _SCIPY_VERSION[1] < 10: #pragma: no cover from scipy.maxentropy import logsumexp else: from scipy.misc import logsumexp from galpy.potential_src.Potential import _evaluateRforces, _evaluatezforces,\ evaluatePotentials, _evaluatephiforces, evaluateDensities, _check_c from galpy.util import galpyWarning import galpy.util.bovy_plot as plot import galpy.util.bovy_symplecticode as symplecticode import galpy.util.bovy_coords as coords #try: from galpy.orbit_src.integrateFullOrbit import integrateFullOrbit_c, _ext_loaded ext_loaded= _ext_loaded from galpy.util.bovy_conversion import physical_conversion from galpy.orbit_src.OrbitTop import OrbitTop _ORBFITNORMRADEC= 360. _ORBFITNORMDIST= 10. _ORBFITNORMPMRADEC= 4. _ORBFITNORMVLOS= 200. class FullOrbit(OrbitTop): """Class that holds and integrates orbits in full 3D potentials""" def __init__(self,vxvv=[1.,0.,0.9,0.,0.1],vo=220.,ro=8.0,zo=0.025, solarmotion=nu.array([-10.1,4.0,6.7])): """ NAME: __init__ PURPOSE: intialize a full orbit INPUT: vxvv - initial condition [R,vR,vT,z,vz,phi] vo - circular velocity at ro (km/s) ro - distance from vantage point to GC (kpc) zo - offset toward the NGP of the Sun wrt the plane (kpc) solarmotion - value in [-U,V,W] (km/s) OUTPUT: (none) HISTORY: 2010-08-01 - Written - Bovy (NYU) 2014-06-11 - Added conversion kwargs to physical coordinates - Bovy (IAS) """ OrbitTop.__init__(self,vxvv=vxvv, ro=ro,zo=zo,vo=vo,solarmotion=solarmotion) return None def integrate(self,t,pot,method='symplec4_c',dt=None): """ NAME: integrate PURPOSE: integrate the orbit INPUT: t - list of times at which to output (0 has to be in this!) pot - potential instance or list of instances method= 'odeint' for scipy's odeint 'leapfrog' for a simple leapfrog implementation 'leapfrog_c' for a simple leapfrog implementation in C 'rk4_c' for a 4th-order Runge-Kutta integrator in C 'rk6_c' for a 6-th order Runge-Kutta integrator in C 'dopr54_c' for a Dormand-Prince integrator in C (generally the fastest) dt= (None) if set, force the integrator to use this basic stepsize; must be an integer divisor of output stepsize OUTPUT: (none) (get the actual orbit using getOrbit() HISTORY: 2010-08-01 - Written - Bovy (NYU) """ #Reset things that may have been defined by a previous integration if hasattr(self,'_orbInterp'): delattr(self,'_orbInterp') if hasattr(self,'rs'): delattr(self,'rs') self.t= nu.array(t) self._pot= pot self.orbit= _integrateFullOrbit(self.vxvv,pot,t,method,dt) @physical_conversion('energy') def Jacobi(self,*args,**kwargs): """ NAME: Jacobi PURPOSE: calculate the Jacobi integral of the motion INPUT: Omega - pattern speed of rotating frame t= time pot= potential instance or list of such instances OUTPUT: Jacobi integral HISTORY: 2011-04-18 - Written - Bovy (NYU) """ if not 'OmegaP' in kwargs or kwargs['OmegaP'] is None: OmegaP= 1. if not 'pot' in kwargs or kwargs['pot'] is None: try: pot= self._pot except AttributeError: raise AttributeError("Integrate orbit or specify pot=") else: pot= kwargs['pot'] if isinstance(pot,list): for p in pot: if hasattr(p,'OmegaP'): OmegaP= p.OmegaP() break else: if hasattr(pot,'OmegaP'): OmegaP= pot.OmegaP() kwargs.pop('OmegaP',None) else: OmegaP= kwargs.pop('OmegaP') #Make sure you are not using physical coordinates old_physical= kwargs.get('use_physical',None) kwargs['use_physical']= False if not isinstance(OmegaP,(int,float)) and len(OmegaP) == 3: if isinstance(OmegaP,list): thisOmegaP= nu.array(OmegaP) else: thisOmegaP= OmegaP out= self.E(*args,**kwargs)-nu.dot(thisOmegaP, self.L(*args,**kwargs).T).T else: out= self.E(*args,**kwargs)-OmegaP*self.L(*args,**kwargs)[:,2] if not old_physical is None: kwargs['use_physical']= old_physical else: kwargs.pop('use_physical') return out @physical_conversion('energy') def E(self,*args,**kwargs): """ NAME: E PURPOSE: calculate the energy INPUT: t - (optional) time at which to get the energy pot= potential instance or list of such instances OUTPUT: energy HISTORY: 2010-09-15 - Written - Bovy (NYU) """ if not 'pot' in kwargs or kwargs['pot'] is None: try: pot= self._pot except AttributeError: raise AttributeError("Integrate orbit or specify pot=") if 'pot' in kwargs and kwargs['pot'] is None: kwargs.pop('pot') else: pot= kwargs.pop('pot') if len(args) > 0: t= args[0] else: t= 0. #Get orbit thiso= self(*args,**kwargs) onet= (len(thiso.shape) == 1) if onet: return evaluatePotentials(pot,thiso[0],thiso[3], phi=thiso[5],t=t,use_physical=False)\ +thiso[1]**2./2.\ +thiso[2]**2./2.\ +thiso[4]**2./2. else: return nu.array([evaluatePotentials(pot,thiso[0,ii],thiso[3,ii], phi=thiso[5,ii], t=t[ii],use_physical=False)\ +thiso[1,ii]**2./2.\ +thiso[2,ii]**2./2.\ +thiso[4,ii]**2./2. for ii in range(len(t))]) @physical_conversion('energy') def ER(self,*args,**kwargs): """ NAME: ER PURPOSE: calculate the radial energy INPUT: t - (optional) time at which to get the energy pot= potential instance or list of such instances OUTPUT: radial energy HISTORY: 2013-11-30 - Written - Bovy (IAS) """ if not 'pot' in kwargs or kwargs['pot'] is None: try: pot= self._pot except AttributeError: raise AttributeError("Integrate orbit or specify pot=") if 'pot' in kwargs and kwargs['pot'] is None: kwargs.pop('pot') else: pot= kwargs.pop('pot') if len(args) > 0: t= args[0] else: t= 0. #Get orbit thiso= self(*args,**kwargs) onet= (len(thiso.shape) == 1) if onet: return evaluatePotentials(pot,thiso[0],0., phi=thiso[5],t=t,use_physical=False)\ +thiso[1]**2./2.\ +thiso[2]**2./2. else: return nu.array([evaluatePotentials(pot,thiso[0,ii],0., phi=thiso[5,ii], t=t[ii],use_physical=False)\ +thiso[1,ii]**2./2.\ +thiso[2,ii]**2./2. for ii in range(len(t))]) @physical_conversion('energy') def Ez(self,*args,**kwargs): """ NAME: Ez PURPOSE: calculate the vertical energy INPUT: t - (optional) time at which to get the energy pot= potential instance or list of such instances OUTPUT: vertical energy HISTORY: 2013-11-30 - Written - Bovy (IAS) """ if not 'pot' in kwargs or kwargs['pot'] is None: try: pot= self._pot except AttributeError: raise AttributeError("Integrate orbit or specify pot=") if 'pot' in kwargs and kwargs['pot'] is None: kwargs.pop('pot') else: pot= kwargs.pop('pot') if len(args) > 0: t= args[0] else: t= 0. #Get orbit thiso= self(*args,**kwargs) onet= (len(thiso.shape) == 1) if onet: return evaluatePotentials(pot,thiso[0],thiso[3], phi=thiso[5],t=t,use_physical=False)\ -evaluatePotentials(pot,thiso[0],0., phi=thiso[5],t=t, use_physical=False)\ +thiso[4]**2./2. else: return nu.array([evaluatePotentials(pot,thiso[0,ii],thiso[3,ii], phi=thiso[5,ii], t=t[ii],use_physical=False)\ -evaluatePotentials(pot,thiso[0,ii],0., phi=thiso[5,ii], t=t[ii],use_physical=False)\ +thiso[4,ii]**2./2. for ii in range(len(t))]) def e(self,analytic=False,pot=None,**kwargs): """ NAME: e PURPOSE: calculate the eccentricity INPUT: analytic - compute this analytically pot - potential to use for analytical calculation OUTPUT: eccentricity HISTORY: 2010-09-15 - Written - Bovy (NYU) """ if analytic: self._setupaA(pot=pot,**kwargs) return float(self._aA.EccZmaxRperiRap(self)[0]) if not hasattr(self,'orbit'): raise AttributeError("Integrate the orbit first or use analytic=True for approximate eccentricity") if not hasattr(self,'rs'): self.rs= nu.sqrt(self.orbit[:,0]**2.+self.orbit[:,3]**2.) return (nu.amax(self.rs)-nu.amin(self.rs))/(nu.amax(self.rs)+nu.amin(self.rs)) @physical_conversion('position') def rap(self,analytic=False,pot=None,**kwargs): """ NAME: rap PURPOSE: return the apocenter radius INPUT: analytic - compute this analytically pot - potential to use for analytical calculation OUTPUT: R_ap HISTORY: 2010-09-20 - Written - Bovy (NYU) """ if analytic: self._setupaA(pot=pot,**kwargs) return float(self._aA.EccZmaxRperiRap(self)[3]) if not hasattr(self,'orbit'): raise AttributeError("Integrate the orbit first or use analytic=True for approximate rap") if not hasattr(self,'rs'): self.rs= nu.sqrt(self.orbit[:,0]**2.+self.orbit[:,3]**2.) return nu.amax(self.rs) @physical_conversion('position') def rperi(self,analytic=False,pot=None,**kwargs): """ NAME: rperi PURPOSE: return the pericenter radius INPUT: analytic - compute this analytically pot - potential to use for analytical calculation OUTPUT: R_peri HISTORY: 2010-09-20 - Written - Bovy (NYU) """ if analytic: self._setupaA(pot=pot,**kwargs) return float(self._aA.EccZmaxRperiRap(self)[2]) if not hasattr(self,'orbit'): raise AttributeError("Integrate the orbit first or use analytic=True for approximate rperi") if not hasattr(self,'rs'): self.rs= nu.sqrt(self.orbit[:,0]**2.+self.orbit[:,3]**2.) return nu.amin(self.rs) @physical_conversion('position') def zmax(self,analytic=False,pot=None,**kwargs): """ NAME: zmax PURPOSE: return the maximum vertical height INPUT: analytic - compute this analytically pot - potential to use for analytical calculation OUTPUT: Z_max HISTORY: 2010-09-20 - Written - Bovy (NYU) 2012-06-01 - Added analytic calculation - Bovy (IAS) """ if analytic: self._setupaA(pot=pot,**kwargs) return float(self._aA.EccZmaxRperiRap(self)[1]) if not hasattr(self,'orbit'): raise AttributeError("Integrate the orbit first or use analytic=True for approximate zmax") return nu.amax(nu.fabs(self.orbit[:,3])) def fit(self,vxvv,vxvv_err=None,pot=None,radec=False,lb=False, customsky=False,lb_to_customsky=None,pmllpmbb_to_customsky=None, tintJ=10,ntintJ=1000,integrate_method='dopr54_c', disp=False, **kwargs): """ NAME: fit PURPOSE: fit an Orbit to data using the current orbit as the initial condition INPUT: vxvv - [:,6] array of positions and velocities along the orbit [cannot be Quantities] vxvv_err= [:,6] array of errors on positions and velocities along the orbit (if None, these are set to 0.01) [cannot be Quantities] pot= Potential to fit the orbit in Keywords related to the input data: radec= if True, input vxvv and vxvv_err are [ra,dec,d,mu_ra, mu_dec,vlos] in [deg,deg,kpc,mas/yr,mas/yr,km/s] (all J2000.0; mu_ra = mu_ra * cos dec); the attributes of the current Orbit are used to convert between these coordinates and Galactocentric coordinates; Note that for speed reasons, galpy's internal transformation between (l,b) and (ra,dec) is used, rather than astropy's lb= if True, input vxvv and vxvv_err are [long,lat,d,mu_ll, mu_bb,vlos] in [deg,deg,kpc,mas/yr,mas/yr,km/s] (mu_ll = mu_ll * cos lat); the attributes of the current Orbit are used to convert between these coordinates and Galactocentric coordinates customsky= if True, input vxvv and vxvv_err are [custom long,custom lat,d,mu_customll, mu_custombb,vlos] in [deg,deg,kpc,mas/yr,mas/yr,km/s] (mu_ll = mu_ll * cos lat) where custom longitude and custom latitude are a custom set of sky coordinates (e.g., ecliptic) and the proper motions are also expressed in these coordinats; you need to provide the functions lb_to_customsky and pmllpmbb_to_customsky to convert to the custom sky coordinates (these should have the same inputs and outputs as lb_to_radec and pmllpmbb_to_pmrapmdec); the attributes of the current Orbit are used to convert between these coordinates and Galactocentric coordinates obs=[X,Y,Z,vx,vy,vz] - (optional) position and velocity of observer (in kpc and km/s) (default=Object-wide default) Cannot be an Orbit instance with the orbit of the reference point, as w/ the ra etc. functions ro= distance in kpc corresponding to R=1. (default: taken from object) vo= velocity in km/s corresponding to v=1. (default: taken from object) lb_to_customsky= function that converts l,b,degree=False to the custom sky coordinates (like lb_to_radec); needs to be given when customsky=True pmllpmbb_to_customsky= function that converts pmll,pmbb,l,b,degree=False to proper motions in the custom sky coordinates (like pmllpmbb_to_pmrapmdec); needs to be given when customsky=True Keywords related to the orbit integrations: tintJ= (default: 10) time to integrate orbits for fitting the orbit ntintJ= (default: 1000) number of time-integration points integrate_method= (default: 'dopr54_c') integration method to use disp= (False) display the optimizer's convergence message OUTPUT: max of log likelihood HISTORY: 2014-06-17 - Written - Bovy (IAS) TEST: from galpy.potential import LogarithmicHaloPotential; lp= LogarithmicHaloPotential(normalize=1.); from galpy.orbit import Orbit; o= Orbit(vxvv=[1.,0.1,1.1,0.1,0.02,0.]); ts= numpy.linspace(0,10,1000); o.integrate(ts,lp); outts= [0.,0.1,0.2,0.3,0.4]; vxvv= numpy.array([o.R(outts),o.vR(outts),o.vT(outts),o.z(outts),o.vz(outts),o.phi(outts)]).T; of= Orbit(vxvv=[1.02,0.101,1.101,0.101,0.0201,0.001]); of._orb.fit(vxvv,pot=lp,radec=False,tintJ=10,ntintJ=1000) """ if pot is None: try: pot= self._pot except AttributeError: raise AttributeError("Integrate orbit first or specify pot=") if radec or lb or customsky: obs, ro, vo= self._parse_radec_kwargs(kwargs,vel=True,dontpop=True) else: obs, ro, vo= None, None, None if customsky \ and (lb_to_customsky is None or pmllpmbb_to_customsky is None): raise IOError('if customsky=True, the functions lb_to_customsky and pmllpmbb_to_customsky need to be given') new_vxvv, maxLogL= _fit_orbit(self,vxvv,vxvv_err,pot,radec=radec,lb=lb, customsky=customsky, lb_to_customsky=lb_to_customsky, pmllpmbb_to_customsky=pmllpmbb_to_customsky, tintJ=tintJ,ntintJ=ntintJ, integrate_method=integrate_method, ro=ro,vo=vo,obs=obs,disp=disp) #Setup with these new initial conditions self.vxvv= new_vxvv return maxLogL def plotEz(self,*args,**kwargs): """ NAME: plotEz PURPOSE: plot Ez(.) along the orbit INPUT: bovy_plot.bovy_plot inputs OUTPUT: figure to output device HISTORY: 2014-06-16 - Written - Bovy (IAS) """ if kwargs.pop('normed',False): kwargs['d2']= 'Eznorm' else: kwargs['d2']= 'Ez' return self.plot(*args,**kwargs) def plotER(self,*args,**kwargs): """ NAME: plotER PURPOSE: plot ER(.) along the orbit INPUT: bovy_plot.bovy_plot inputs OUTPUT: figure to output device HISTORY: 2014-06-16 - Written - Bovy (IAS) """ if kwargs.pop('normed',False): kwargs['d2']= 'ERnorm' else: kwargs['d2']= 'ER' return self.plot(*args,**kwargs) def plotEzJz(self,*args,**kwargs): """ NAME: plotEzJz PURPOSE: plot E_z(.)/sqrt(dens(R)) along the orbit INPUT: pot= Potential instance or list of instances in which the orbit was integrated d1= - plot Ez vs d1: e.g., 't', 'z', 'R', 'vR', 'vT', 'vz' +bovy_plot.bovy_plot inputs OUTPUT: figure to output device HISTORY: 2010-08-08 - Written - Bovy (NYU) """ labeldict= {'t':r'$t$','R':r'$R$','vR':r'$v_R$','vT':r'$v_T$', 'z':r'$z$','vz':r'$v_z$','phi':r'$\phi$', 'x':r'$x$','y':r'$y$','vx':r'$v_x$','vy':r'$v_y$'} if not 'pot' in kwargs: try: pot= self._pot except AttributeError: raise AttributeError("Integrate orbit first or specify pot=") else: pot= kwargs.pop('pot') d1= kwargs.pop('d1','t') self.EzJz= [(evaluatePotentials(pot,self.orbit[ii,0],self.orbit[ii,3], t=self.t[ii],use_physical=False)- evaluatePotentials(pot,self.orbit[ii,0],0., phi= self.orbit[ii,5],t=self.t[ii], use_physical=False)+ self.orbit[ii,4]**2./2.)/\ nu.sqrt(evaluateDensities(pot,self.orbit[ii,0],0., phi=self.orbit[ii,5], t=self.t[ii], use_physical=False))\ for ii in range(len(self.t))] if not 'xlabel' in kwargs: kwargs['xlabel']= labeldict[d1] if not 'ylabel' in kwargs: kwargs['ylabel']= r'$E_z/\sqrt{\rho}$' if d1 == 't': return plot.bovy_plot(nu.array(self.t), nu.array(self.EzJz)/self.EzJz[0], *args,**kwargs) elif d1 == 'z': return plot.bovy_plot(self.orbit[:,3], nu.array(self.EzJz)/self.EzJz[0], *args,**kwargs) elif d1 == 'R': return plot.bovy_plot(self.orbit[:,0], nu.array(self.EzJz)/self.EzJz[0], *args,**kwargs) elif d1 == 'vR': return plot.bovy_plot(self.orbit[:,1], nu.array(self.EzJz)/self.EzJz[0], *args,**kwargs) elif d1 == 'vT': return plot.bovy_plot(self.orbit[:,2], nu.array(self.EzJz)/self.EzJz[0], *args,**kwargs) elif d1 == 'vz': return plot.bovy_plot(self.orbit[:,4], nu.array(self.EzJz)/self.EzJz[0], *args,**kwargs) def _integrateFullOrbit(vxvv,pot,t,method,dt): """ NAME: _integrateFullOrbit PURPOSE: integrate an orbit in a Phi(R,z,phi) potential INPUT: vxvv - array with the initial conditions stacked like [R,vR,vT,z,vz,phi]; vR outward! pot - Potential instance t - list of times at which to output (0 has to be in this!) method - 'odeint' or 'leapfrog' dt - if set, force the integrator to use this basic stepsize; must be an integer divisor of output stepsize OUTPUT: [:,5] array of [R,vR,vT,z,vz,phi] at each t HISTORY: 2010-08-01 - Written - Bovy (NYU) """ #First check that the potential has C if '_c' in method: if not _check_c(pot): if ('leapfrog' in method or 'symplec' in method): method= 'leapfrog' else: method= 'odeint' warnings.warn("Cannot use C integration because some of the potentials are not implemented in C (using %s instead)" % (method), galpyWarning) if method.lower() == 'leapfrog': #go to the rectangular frame this_vxvv= nu.array([vxvv[0]*nu.cos(vxvv[5]), vxvv[0]*nu.sin(vxvv[5]), vxvv[3], vxvv[1]*nu.cos(vxvv[5])-vxvv[2]*nu.sin(vxvv[5]), vxvv[2]*nu.cos(vxvv[5])+vxvv[1]*nu.sin(vxvv[5]), vxvv[4]]) #integrate out= symplecticode.leapfrog(_rectForce,this_vxvv, t,args=(pot,),rtol=10.**-8) #go back to the cylindrical frame R= nu.sqrt(out[:,0]**2.+out[:,1]**2.) phi= nu.arccos(out[:,0]/R) phi[(out[:,1] < 0.)]= 2.*nu.pi-phi[(out[:,1] < 0.)] vR= out[:,3]*nu.cos(phi)+out[:,4]*nu.sin(phi) vT= out[:,4]*nu.cos(phi)-out[:,3]*nu.sin(phi) out[:,3]= out[:,2] out[:,4]= out[:,5] out[:,0]= R out[:,1]= vR out[:,2]= vT out[:,5]= phi elif ext_loaded and \ (method.lower() == 'leapfrog_c' or method.lower() == 'rk4_c' \ or method.lower() == 'rk6_c' or method.lower() == 'symplec4_c' \ or method.lower() == 'symplec6_c' or method.lower() == 'dopr54_c'): warnings.warn("Using C implementation to integrate orbits", galpyWarning) #go to the rectangular frame this_vxvv= nu.array([vxvv[0]*nu.cos(vxvv[5]), vxvv[0]*nu.sin(vxvv[5]), vxvv[3], vxvv[1]*nu.cos(vxvv[5])-vxvv[2]*nu.sin(vxvv[5]), vxvv[2]*nu.cos(vxvv[5])+vxvv[1]*nu.sin(vxvv[5]), vxvv[4]]) #integrate tmp_out, msg= integrateFullOrbit_c(pot,this_vxvv, t,method,dt=dt) #go back to the cylindrical frame R= nu.sqrt(tmp_out[:,0]**2.+tmp_out[:,1]**2.) phi= nu.arccos(tmp_out[:,0]/R) phi[(tmp_out[:,1] < 0.)]= 2.*nu.pi-phi[(tmp_out[:,1] < 0.)] vR= tmp_out[:,3]*nu.cos(phi)+tmp_out[:,4]*nu.sin(phi) vT= tmp_out[:,4]*nu.cos(phi)-tmp_out[:,3]*nu.sin(phi) out= nu.zeros((len(t),6)) out[:,0]= R out[:,1]= vR out[:,2]= vT out[:,5]= phi out[:,3]= tmp_out[:,2] out[:,4]= tmp_out[:,5] elif method.lower() == 'odeint' or not ext_loaded: vphi= vxvv[2]/vxvv[0] init= [vxvv[0],vxvv[1],vxvv[5],vphi,vxvv[3],vxvv[4]] intOut= integrate.odeint(_FullEOM,init,t,args=(pot,), rtol=10.**-8.)#,mxstep=100000000) out= nu.zeros((len(t),6)) out[:,0]= intOut[:,0] out[:,1]= intOut[:,1] out[:,2]= out[:,0]*intOut[:,3] out[:,3]= intOut[:,4] out[:,4]= intOut[:,5] out[:,5]= intOut[:,2] #post-process to remove negative radii neg_radii= (out[:,0] < 0.) out[neg_radii,0]= -out[neg_radii,0] out[neg_radii,5]+= m.pi return out def _FullEOM(y,t,pot): """ NAME: _FullEOM PURPOSE: implements the EOM, i.e., the right-hand side of the differential equation INPUT: y - current phase-space position t - current time pot - (list of) Potential instance(s) OUTPUT: dy/dt HISTORY: 2010-04-16 - Written - Bovy (NYU) """ l2= (y[0]**2.*y[3])**2. return [y[1], l2/y[0]**3.+_evaluateRforces(pot,y[0],y[4],phi=y[2],t=t), y[3], 1./y[0]**2.*(_evaluatephiforces(pot,y[0],y[4],phi=y[2],t=t) -2.*y[0]*y[1]*y[3]), y[5], _evaluatezforces(pot,y[0],y[4],phi=y[2],t=t)] def _rectForce(x,pot,t=0.): """ NAME: _rectForce PURPOSE: returns the force in the rectangular frame INPUT: x - current position t - current time pot - (list of) Potential instance(s) OUTPUT: force HISTORY: 2011-02-02 - Written - Bovy (NYU) """ #x is rectangular so calculate R and phi R= nu.sqrt(x[0]**2.+x[1]**2.) phi= nu.arccos(x[0]/R) sinphi= x[1]/R cosphi= x[0]/R if x[1] < 0.: phi= 2.*nu.pi-phi #calculate forces Rforce= _evaluateRforces(pot,R,x[2],phi=phi,t=t) phiforce= _evaluatephiforces(pot,R,x[2],phi=phi,t=t) return nu.array([cosphi*Rforce-1./R*sinphi*phiforce, sinphi*Rforce+1./R*cosphi*phiforce, _evaluatezforces(pot,R,x[2],phi=phi,t=t)]) def _fit_orbit(orb,vxvv,vxvv_err,pot,radec=False,lb=False, customsky=False,lb_to_customsky=None, pmllpmbb_to_customsky=None, tintJ=100,ntintJ=1000,integrate_method='dopr54_c', ro=None,vo=None,obs=None,disp=False): """Fit an orbit to data in a given potential""" # Need to turn this off for speed coords._APY_COORDS_ORIG= coords._APY_COORDS coords._APY_COORDS= False #Import here, because otherwise there is an infinite loop of imports from galpy.actionAngle import actionAngleIsochroneApprox, actionAngle #Mock this up, bc we want to use its orbit-integration routines class mockActionAngleIsochroneApprox(actionAngleIsochroneApprox): def __init__(self,tintJ,ntintJ,pot,integrate_method='dopr54_c'): actionAngle.__init__(self) self._tintJ= tintJ self._ntintJ=ntintJ self._tsJ= nu.linspace(0.,self._tintJ,self._ntintJ) self._integrate_dt= None self._pot= pot self._integrate_method= integrate_method return None tmockAA= mockActionAngleIsochroneApprox(tintJ,ntintJ,pot, integrate_method=integrate_method) opt_vxvv= optimize.fmin_powell(_fit_orbit_mlogl,orb.vxvv, args=(vxvv,vxvv_err,pot,radec,lb, customsky,lb_to_customsky, pmllpmbb_to_customsky, tmockAA, ro,vo,obs), disp=disp) maxLogL= -_fit_orbit_mlogl(opt_vxvv,vxvv,vxvv_err,pot,radec,lb, customsky,lb_to_customsky,pmllpmbb_to_customsky, tmockAA, ro,vo,obs) coords._APY_COORDS= coords._APY_COORDS_ORIG return (opt_vxvv,maxLogL) def _fit_orbit_mlogl(new_vxvv,vxvv,vxvv_err,pot,radec,lb, customsky,lb_to_customsky,pmllpmbb_to_customsky, tmockAA, ro,vo,obs): """The log likelihood for fitting an orbit""" #Use this _parse_args routine, which does forward and backward integration iR,ivR,ivT,iz,ivz,iphi= tmockAA._parse_args(True,False, new_vxvv[0], new_vxvv[1], new_vxvv[2], new_vxvv[3], new_vxvv[4], new_vxvv[5]) if radec or lb or customsky: #Need to transform to (l,b), (ra,dec), or a custom set #First transform to X,Y,Z,vX,vY,vZ (Galactic) X,Y,Z = coords.galcencyl_to_XYZ(iR.flatten(),iphi.flatten(), iz.flatten(), Xsun=obs[0]/ro, Zsun=obs[2]/ro).T vX,vY,vZ = coords.galcencyl_to_vxvyvz(ivR.flatten(),ivT.flatten(), ivz.flatten(),iphi.flatten(), vsun=nu.array(\ obs[3:6])/vo,Xsun=obs[0]/ro,Zsun=obs[2]/ro).T bad_indx= (X == 0.)*(Y == 0.)*(Z == 0.) if True in bad_indx: X[bad_indx]+= ro/10000. lbdvrpmllpmbb= coords.rectgal_to_sphergal(X*ro,Y*ro,Z*ro, vX*vo,vY*vo,vZ*vo, degree=True) if lb: orb_vxvv= nu.array([lbdvrpmllpmbb[:,0], lbdvrpmllpmbb[:,1], lbdvrpmllpmbb[:,2], lbdvrpmllpmbb[:,4], lbdvrpmllpmbb[:,5], lbdvrpmllpmbb[:,3]]).T elif radec: #Further transform to ra,dec,pmra,pmdec radec= coords.lb_to_radec(lbdvrpmllpmbb[:,0], lbdvrpmllpmbb[:,1],degree=True) pmrapmdec= coords.pmllpmbb_to_pmrapmdec(lbdvrpmllpmbb[:,4], lbdvrpmllpmbb[:,5], lbdvrpmllpmbb[:,0], lbdvrpmllpmbb[:,1], degree=True) orb_vxvv= nu.array([radec[:,0],radec[:,1], lbdvrpmllpmbb[:,2], pmrapmdec[:,0],pmrapmdec[:,1], lbdvrpmllpmbb[:,3]]).T elif customsky: #Further transform to ra,dec,pmra,pmdec customradec= lb_to_customsky(lbdvrpmllpmbb[:,0], lbdvrpmllpmbb[:,1],degree=True) custompmrapmdec= pmllpmbb_to_customsky(lbdvrpmllpmbb[:,4], lbdvrpmllpmbb[:,5], lbdvrpmllpmbb[:,0], lbdvrpmllpmbb[:,1], degree=True) orb_vxvv= nu.array([customradec[:,0],customradec[:,1], lbdvrpmllpmbb[:,2], custompmrapmdec[:,0],custompmrapmdec[:,1], lbdvrpmllpmbb[:,3]]).T else: #shape=(2tintJ-1,6) orb_vxvv= nu.array([iR.flatten(),ivR.flatten(),ivT.flatten(), iz.flatten(),ivz.flatten(),iphi.flatten()]).T out= 0. for ii in range(vxvv.shape[0]): sub_vxvv= (orb_vxvv-vxvv[ii,:].flatten())**2. #print(sub_vxvv[nu.argmin(nu.sum(sub_vxvv,axis=1))]) if not vxvv_err is None: sub_vxvv/= vxvv_err[ii,:]**2. else: sub_vxvv/= 0.01**2. out+= logsumexp(-0.5*nu.sum(sub_vxvv,axis=1)) return -out galpy-1.3.0/galpy/orbit_src/integrateFullOrbit.py0000644000076500000240000004045113236350147022222 0ustar bovystaff00000000000000import sys import sysconfig import warnings import numpy as nu import ctypes import ctypes.util from numpy.ctypeslib import ndpointer import os from galpy import potential from galpy.util import galpyWarning from galpy.orbit_src.integratePlanarOrbit import _parse_integrator, _parse_tol #Find and load the library _lib= None outerr= None PY3= sys.version > '3' if PY3: #pragma: no cover _ext_suffix= sysconfig.get_config_var('EXT_SUFFIX') else: _ext_suffix= '.so' for path in sys.path: try: _lib = ctypes.CDLL(os.path.join(path,'galpy_integrate_c%s' % _ext_suffix)) except OSError as e: if os.path.exists(os.path.join(path,'galpy_integrate_c%s' % _ext_suffix)): #pragma: no cover outerr= e _lib = None else: break if _lib is None: #pragma: no cover if not outerr is None: warnings.warn("integrateFullOrbit_c extension module not loaded, because of error '%s' " % outerr, galpyWarning) else: warnings.warn("integrateFullOrbit_c extension module not loaded, because galpy_integrate_c%s image was not found" % _ext_suffix, galpyWarning) _ext_loaded= False else: _ext_loaded= True def _parse_pot(pot,potforactions=False,potfortorus=False): """Parse the potential so it can be fed to C""" #Figure out what's in pot if not isinstance(pot,list): pot= [pot] #Initialize everything pot_type= [] pot_args= [] npot= len(pot) for p in pot: if isinstance(p,potential.LogarithmicHaloPotential): pot_type.append(0) if p.isNonAxi: pot_args.extend([p._amp,p._q,p._core2,p._1m1overb2]) else: pot_args.extend([p._amp,p._q,p._core2,2.]) # 1m1overb2 > 1: axi elif isinstance(p,potential.DehnenBarPotential): pot_type.append(1) pot_args.extend([p._amp*p._af,p._tform,p._tsteady,p._rb,p._omegab, p._barphi]) elif isinstance(p,potential.MiyamotoNagaiPotential): pot_type.append(5) pot_args.extend([p._amp,p._a,p._b]) elif isinstance(p,potential.PowerSphericalPotential): pot_type.append(7) pot_args.extend([p._amp,p.alpha]) elif isinstance(p,potential.HernquistPotential): pot_type.append(8) pot_args.extend([p._amp,p.a]) elif isinstance(p,potential.NFWPotential): pot_type.append(9) pot_args.extend([p._amp,p.a]) elif isinstance(p,potential.JaffePotential): pot_type.append(10) pot_args.extend([p._amp,p.a]) elif isinstance(p,potential.DoubleExponentialDiskPotential): pot_type.append(11) pot_args.extend([p._amp,p._alpha,p._beta,p._kmaxFac, p._nzeros,p._glorder]) pot_args.extend([p._glx[ii] for ii in range(p._glorder)]) pot_args.extend([p._glw[ii] for ii in range(p._glorder)]) pot_args.extend([p._j0zeros[ii] for ii in range(p._nzeros+1)]) pot_args.extend([p._dj0zeros[ii] for ii in range(p._nzeros+1)]) pot_args.extend([p._j1zeros[ii] for ii in range(p._nzeros+1)]) pot_args.extend([p._dj1zeros[ii] for ii in range(p._nzeros+1)]) pot_args.extend([p._kp._amp,p._kp.alpha]) elif isinstance(p,potential.FlattenedPowerPotential): pot_type.append(12) pot_args.extend([p._amp,p.alpha,p.q2,p.core2]) elif isinstance(p,potential.interpRZPotential): pot_type.append(13) pot_args.extend([len(p._rgrid),len(p._zgrid)]) if p._logR: pot_args.extend([p._logrgrid[ii] for ii in range(len(p._rgrid))]) else: pot_args.extend([p._rgrid[ii] for ii in range(len(p._rgrid))]) pot_args.extend([p._zgrid[ii] for ii in range(len(p._zgrid))]) if potforactions or potfortorus: pot_args.extend([x for x in p._potGrid_splinecoeffs.flatten(order='C')]) if not potforactions: pot_args.extend([x for x in p._rforceGrid_splinecoeffs.flatten(order='C')]) pot_args.extend([x for x in p._zforceGrid_splinecoeffs.flatten(order='C')]) pot_args.extend([p._amp,int(p._logR)]) elif isinstance(p,potential.IsochronePotential): pot_type.append(14) pot_args.extend([p._amp,p.b]) elif isinstance(p,potential.PowerSphericalPotentialwCutoff): pot_type.append(15) pot_args.extend([p._amp,p.alpha,p.rc]) elif isinstance(p,potential.MN3ExponentialDiskPotential): # Three Miyamoto-Nagai disks npot+= 2 pot_type.extend([5,5,5]) pot_args.extend([p._amp*p._mn3[0]._amp, p._mn3[0]._a,p._mn3[0]._b, p._amp*p._mn3[1]._amp, p._mn3[1]._a,p._mn3[1]._b, p._amp*p._mn3[2]._amp, p._mn3[2]._a,p._mn3[2]._b]) elif isinstance(p,potential.KuzminKutuzovStaeckelPotential): pot_type.append(16) pot_args.extend([p._amp,p._ac,p._Delta]) elif isinstance(p,potential.PlummerPotential): pot_type.append(17) pot_args.extend([p._amp,p._b]) elif isinstance(p,potential.PseudoIsothermalPotential): pot_type.append(18) pot_args.extend([p._amp,p._a]) elif isinstance(p,potential.KuzminDiskPotential): pot_type.append(19) pot_args.extend([p._amp,p._a]) elif isinstance(p,potential.BurkertPotential): pot_type.append(20) pot_args.extend([p._amp,p.a]) elif isinstance(p,potential.TwoPowerTriaxialPotential): if isinstance(p,potential.TriaxialHernquistPotential): pot_type.append(21) elif isinstance(p,potential.TriaxialNFWPotential): pot_type.append(22) elif isinstance(p,potential.TriaxialJaffePotential): pot_type.append(23) pot_args.extend([p._amp,p.a,p._b2,p._c2,int(p._aligned)]) if not p._aligned: pot_args.extend(list(p._rot.flatten())) else: pot_args.extend(list(nu.eye(3).flatten())) # not actually used pot_args.append(p._glorder) pot_args.extend([p._glx[ii] for ii in range(p._glorder)]) # this adds some common factors to the integration weights pot_args.extend([-p._glw[ii]*p._b*p._c/p.a**3.\ /nu.sqrt(( 1.+(p._b2-1.)*p._glx[ii]**2.) *(1.+(p._c2-1.)*p._glx[ii]**2.)) for ii in range(p._glorder)]) pot_args.extend([0.,0.,0.,0.,0.,0.]) # for caching elif isinstance(p,potential.SCFPotential): # Type 24, see stand-alone parser below pt,pa= _parse_scf_pot(p) pot_type.append(pt) pot_args.extend(pa) elif isinstance(p,potential.SoftenedNeedleBarPotential): pot_type.append(25) pot_args.extend([p._amp,p._a,p._b,p._c2,p._pa,p._omegab]) pot_args.extend([0.,0.,0.,0.,0.,0.,0.]) # for caching elif isinstance(p,potential.DiskSCFPotential): # Need to pull this apart into: (a) SCF part, (b) constituent # [Sigma_i,h_i] parts # (a) SCF, multiply in any add'l amp pt,pa= _parse_scf_pot(p._scf,extra_amp=p._amp) pot_type.append(pt) pot_args.extend(pa) # (b) constituent [Sigma_i,h_i] parts for Sigma,hz in zip(p._Sigma_dict,p._hz_dict): npot+= 1 pot_type.append(26) stype= Sigma.get('type','exp') if stype == 'exp' \ or (stype == 'exp' and 'Rhole' in Sigma): pot_args.extend([3,0, 4.*nu.pi*Sigma.get('amp',1.)*p._amp, Sigma.get('h',1./3.)]) elif stype == 'expwhole' \ or (stype == 'exp' and 'Rhole' in Sigma): pot_args.extend([4,1, 4.*nu.pi*Sigma.get('amp',1.)*p._amp, Sigma.get('h',1./3.), Sigma.get('Rhole',0.5)]) hztype= hz.get('type','exp') if hztype == 'exp': pot_args.extend([0,hz.get('h',0.0375)]) elif hztype == 'sech2': pot_args.extend([1,hz.get('h',0.0375)]) elif isinstance(p, potential.SpiralArmsPotential): pot_type.append(27) pot_args.extend([len(p._Cs), p._amp, p._N, p._sin_alpha, p._tan_alpha, p._r_ref, p._phi_ref, p._Rs, p._H, p._omega]) pot_args.extend(p._Cs) ############################## WRAPPERS ############################### elif isinstance(p,potential.DehnenSmoothWrapperPotential): pot_type.append(-1) wrap_npot, wrap_pot_type, wrap_pot_args= \ _parse_pot(p._pot, potforactions=potforactions,potfortorus=potfortorus) pot_args.append(wrap_npot) pot_type.extend(wrap_pot_type) pot_args.extend(wrap_pot_args) pot_args.extend([p._amp,p._tform,p._tsteady]) elif isinstance(p,potential.SolidBodyRotationWrapperPotential): pot_type.append(-2) # Not sure how to easily avoid this duplication wrap_npot, wrap_pot_type, wrap_pot_args= \ _parse_pot(p._pot, potforactions=potforactions,potfortorus=potfortorus) pot_args.append(wrap_npot) pot_type.extend(wrap_pot_type) pot_args.extend(wrap_pot_args) pot_args.extend([p._amp,p._omega,p._pa]) pot_type= nu.array(pot_type,dtype=nu.int32,order='C') pot_args= nu.array(pot_args,dtype=nu.float64,order='C') return (npot,pot_type,pot_args) def _parse_scf_pot(p,extra_amp=1.): # Stand-alone parser for SCF, bc re-used isNonAxi= p.isNonAxi pot_args= [p._a, isNonAxi] pot_args.extend(p._Acos.shape) pot_args.extend(extra_amp*p._amp*p._Acos.flatten(order='C')) if isNonAxi: pot_args.extend(extra_amp*p._amp*p._Asin.flatten(order='C')) pot_args.extend([-1.,0,0,0,0,0,0]) return (24,pot_args) def integrateFullOrbit_c(pot,yo,t,int_method,rtol=None,atol=None,dt=None): """ NAME: integrateFullOrbit_c PURPOSE: C integrate an ode for a FullOrbit INPUT: pot - Potential or list of such instances yo - initial condition [q,p] t - set of times at which one wants the result int_method= 'leapfrog_c', 'rk4_c', 'rk6_c', 'symplec4_c' rtol, atol dt= (None) force integrator to use this stepsize (default is to automatically determine one)) OUTPUT: (y,err) y : array, shape (len(y0), len(t)) Array containing the value of y for each desired time in t, \ with the initial value y0 in the first row. err: error message, if not zero: 1 means maximum step reduction happened for adaptive integrators HISTORY: 2011-11-13 - Written - Bovy (IAS) """ rtol, atol= _parse_tol(rtol,atol) npot, pot_type, pot_args= _parse_pot(pot) int_method_c= _parse_integrator(int_method) if dt is None: dt= -9999.99 #Set up result array result= nu.empty((len(t),6)) err= ctypes.c_int(0) #Set up the C code ndarrayFlags= ('C_CONTIGUOUS','WRITEABLE') integrationFunc= _lib.integrateFullOrbit integrationFunc.argtypes= [ndpointer(dtype=nu.float64,flags=ndarrayFlags), ctypes.c_int, ndpointer(dtype=nu.float64,flags=ndarrayFlags), ctypes.c_int, ndpointer(dtype=nu.int32,flags=ndarrayFlags), ndpointer(dtype=nu.float64,flags=ndarrayFlags), ctypes.c_double, ctypes.c_double, ctypes.c_double, ndpointer(dtype=nu.float64,flags=ndarrayFlags), ctypes.POINTER(ctypes.c_int), ctypes.c_int] #Array requirements, first store old order f_cont= [yo.flags['F_CONTIGUOUS'], t.flags['F_CONTIGUOUS']] yo= nu.require(yo,dtype=nu.float64,requirements=['C','W']) t= nu.require(t,dtype=nu.float64,requirements=['C','W']) result= nu.require(result,dtype=nu.float64,requirements=['C','W']) #Run the C code integrationFunc(yo, ctypes.c_int(len(t)), t, ctypes.c_int(npot), pot_type, pot_args, ctypes.c_double(dt), ctypes.c_double(rtol),ctypes.c_double(atol), result, ctypes.byref(err), ctypes.c_int(int_method_c)) if int(err.value) == -10: #pragma: no cover raise KeyboardInterrupt("Orbit integration interrupted by CTRL-C (SIGINT)") #Reset input arrays if f_cont[0]: yo= nu.asfortranarray(yo) if f_cont[1]: t= nu.asfortranarray(t) return (result,err.value) def integrateFullOrbit_dxdv_c(pot,yo,dyo,t,int_method,rtol=None,atol=None): #pragma: no cover because not included in v1, uncover when included """ NAME: integrateFullOrbit_dxdv_c PURPOSE: C integrate an ode for a planarOrbit+phase space volume dxdv INPUT: pot - Potential or list of such instances yo - initial condition [q,p] dyo - initial condition [dq,dp] t - set of times at which one wants the result int_method= 'leapfrog_c', 'rk4_c', 'rk6_c', 'symplec4_c' rtol, atol OUTPUT: (y,err) y : array, shape (len(y0), len(t)) Array containing the value of y for each desired time in t, \ with the initial value y0 in the first row. err: error message if not zero, 1: maximum step reduction happened for adaptive integrators HISTORY: 2011-11-13 - Written - Bovy (IAS) """ rtol, atol= _parse_tol(rtol,atol) npot, pot_type, pot_args= _parse_pot(pot) int_method_c= _parse_integrator(int_method) yo= nu.concatenate((yo,dyo)) #Set up result array result= nu.empty((len(t),12)) err= ctypes.c_int(0) #Set up the C code ndarrayFlags= ('C_CONTIGUOUS','WRITEABLE') integrationFunc= _lib.integrateFullOrbit_dxdv integrationFunc.argtypes= [ndpointer(dtype=nu.float64,flags=ndarrayFlags), ctypes.c_int, ndpointer(dtype=nu.float64,flags=ndarrayFlags), ctypes.c_int, ndpointer(dtype=nu.int32,flags=ndarrayFlags), ndpointer(dtype=nu.float64,flags=ndarrayFlags), ctypes.c_double, ctypes.c_double, ndpointer(dtype=nu.float64,flags=ndarrayFlags), ctypes.POINTER(ctypes.c_int), ctypes.c_int] #Array requirements, first store old order f_cont= [yo.flags['F_CONTIGUOUS'], t.flags['F_CONTIGUOUS']] yo= nu.require(yo,dtype=nu.float64,requirements=['C','W']) t= nu.require(t,dtype=nu.float64,requirements=['C','W']) result= nu.require(result,dtype=nu.float64,requirements=['C','W']) #Run the C code integrationFunc(yo, ctypes.c_int(len(t)), t, ctypes.c_int(npot), pot_type, pot_args, ctypes.c_double(rtol),ctypes.c_double(atol), result, ctypes.byref(err), ctypes.c_int(int_method_c)) if int(err.value) == -10: #pragma: no cover raise KeyboardInterrupt("Orbit integration interrupted by CTRL-C (SIGINT)") #Reset input arrays if f_cont[0]: yo= nu.asfortranarray(yo) if f_cont[1]: t= nu.asfortranarray(t) return (result,err.value) galpy-1.3.0/galpy/orbit_src/integratePlanarOrbit.py0000644000076500000240000005432113236350147022536 0ustar bovystaff00000000000000import sys import sysconfig import warnings import numpy as nu import ctypes import ctypes.util from numpy.ctypeslib import ndpointer import os from galpy import potential, potential_src from galpy.util import galpyWarning #Find and load the library _lib= None outerr= None PY3= sys.version > '3' if PY3: #pragma: no cover _ext_suffix= sysconfig.get_config_var('EXT_SUFFIX') else: _ext_suffix= '.so' for path in sys.path: try: _lib = ctypes.CDLL(os.path.join(path,'galpy_integrate_c%s' % _ext_suffix)) except OSError as e: if os.path.exists(os.path.join(path,'galpy_integrate_c%s' % _ext_suffix)): #pragma: no cover outerr= e _lib = None else: break if _lib is None: #pragma: no cover if not outerr is None: warnings.warn("integratePlanarOrbit_c extension module not loaded, because of error '%s' " % outerr, galpyWarning) else: warnings.warn("integratePlanarOrbit_c extension module not loaded, because galpy_integrate_c%s image was not found" % _ext_suffix, galpyWarning) _ext_loaded= False else: _ext_loaded= True def _parse_pot(pot): """Parse the potential so it can be fed to C""" from galpy.orbit_src.integrateFullOrbit import _parse_scf_pot #Figure out what's in pot if not isinstance(pot,list): pot= [pot] #Initialize everything pot_type= [] pot_args= [] npot= len(pot) for p in pot: # Prepare for wrappers if ((isinstance(p,\ potential_src.planarPotential.planarPotentialFromFullPotential) \ or isinstance(p,\ potential_src.planarPotential.planarPotentialFromRZPotential)) \ and isinstance(p._Pot,\ potential_src.WrapperPotential.parentWrapperPotential)) \ or isinstance(p,potential_src.WrapperPotential.parentWrapperPotential): if not isinstance(p,\ potential_src.WrapperPotential.parentWrapperPotential): wrap_npot, wrap_pot_type, wrap_pot_args= \ _parse_pot(potential.toPlanarPotential(p._Pot._pot)) else: wrap_npot, wrap_pot_type, wrap_pot_args= _parse_pot(p._pot) if (isinstance(p,potential_src.planarPotential.planarPotentialFromRZPotential) or isinstance(p,potential_src.planarPotential.planarPotentialFromFullPotential) ) \ and isinstance(p._Pot,potential.LogarithmicHaloPotential): pot_type.append(0) if p._Pot.isNonAxi: pot_args.extend([p._Pot._amp,p._Pot._core2,p._Pot._1m1overb2]) else: pot_args.extend([p._Pot._amp,p._Pot._core2,2.]) # 1m1overb2 > 1: axi elif isinstance(p,potential_src.planarPotential.planarPotentialFromFullPotential) \ and isinstance(p._Pot,potential.DehnenBarPotential): pot_type.append(1) pot_args.extend([p._Pot._amp*p._Pot._af,p._Pot._tform, p._Pot._tsteady,p._Pot._rb,p._Pot._omegab, p._Pot._barphi]) elif isinstance(p,potential.TransientLogSpiralPotential): pot_type.append(2) pot_args.extend([p._amp,p._A,p._to,p._sigma2,p._alpha,p._m, p._omegas,p._gamma]) elif isinstance(p,potential.SteadyLogSpiralPotential): pot_type.append(3) if p._tform is None: pot_args.extend([p._amp,float('nan'), float('nan'), p._A,p._alpha,p._m, p._omegas,p._gamma]) else: pot_args.extend([p._amp,p._tform,p._tsteady,p._A,p._alpha,p._m, p._omegas,p._gamma]) elif isinstance(p,potential.EllipticalDiskPotential): pot_type.append(4) if p._tform is None: pot_args.extend([p._amp,float('nan'), float('nan'), p._twophio,p._p,p._phib]) else: pot_args.extend([p._amp,p._tform,p._tsteady, p._twophio,p._p,p._phib]) elif isinstance(p,potential_src.planarPotential.planarPotentialFromRZPotential) \ and isinstance(p._Pot,potential.MiyamotoNagaiPotential): pot_type.append(5) pot_args.extend([p._Pot._amp,p._Pot._a,p._Pot._b]) elif isinstance(p,potential.LopsidedDiskPotential): pot_type.append(6) pot_args.extend([p._amp,p._mphio,p._p,p._phib]) elif isinstance(p,potential_src.planarPotential.planarPotentialFromRZPotential) \ and isinstance(p._Pot,potential.PowerSphericalPotential): pot_type.append(7) pot_args.extend([p._Pot._amp,p._Pot.alpha]) elif isinstance(p,potential_src.planarPotential.planarPotentialFromRZPotential) \ and isinstance(p._Pot,potential.HernquistPotential): pot_type.append(8) pot_args.extend([p._Pot._amp,p._Pot.a]) elif isinstance(p,potential_src.planarPotential.planarPotentialFromRZPotential) \ and isinstance(p._Pot,potential.NFWPotential): pot_type.append(9) pot_args.extend([p._Pot._amp,p._Pot.a]) elif isinstance(p,potential_src.planarPotential.planarPotentialFromRZPotential) \ and isinstance(p._Pot,potential.JaffePotential): pot_type.append(10) pot_args.extend([p._Pot._amp,p._Pot.a]) elif isinstance(p,potential_src.planarPotential.planarPotentialFromRZPotential) \ and isinstance(p._Pot,potential.DoubleExponentialDiskPotential): pot_type.append(11) pot_args.extend([p._Pot._amp,p._Pot._alpha, p._Pot._beta,p._Pot._kmaxFac, p._Pot._nzeros,p._Pot._glorder]) pot_args.extend([p._Pot._glx[ii] for ii in range(p._Pot._glorder)]) pot_args.extend([p._Pot._glw[ii] for ii in range(p._Pot._glorder)]) pot_args.extend([p._Pot._j0zeros[ii] for ii in range(p._Pot._nzeros+1)]) pot_args.extend([p._Pot._dj0zeros[ii] for ii in range(p._Pot._nzeros+1)]) pot_args.extend([p._Pot._j1zeros[ii] for ii in range(p._Pot._nzeros+1)]) pot_args.extend([p._Pot._dj1zeros[ii] for ii in range(p._Pot._nzeros+1)]) pot_args.extend([p._Pot._kp._amp,p._Pot._kp.alpha]) elif isinstance(p,potential_src.planarPotential.planarPotentialFromRZPotential) \ and isinstance(p._Pot,potential.FlattenedPowerPotential): pot_type.append(12) pot_args.extend([p._Pot._amp,p._Pot.alpha,p._Pot.core2]) elif isinstance(p,potential_src.planarPotential.planarPotentialFromRZPotential) \ and isinstance(p._Pot,potential.IsochronePotential): pot_type.append(14) pot_args.extend([p._Pot._amp,p._Pot.b]) elif isinstance(p,potential_src.planarPotential.planarPotentialFromRZPotential) \ and isinstance(p._Pot,potential.PowerSphericalPotentialwCutoff): pot_type.append(15) pot_args.extend([p._Pot._amp,p._Pot.alpha,p._Pot.rc]) elif isinstance(p,potential_src.planarPotential.planarPotentialFromRZPotential) \ and isinstance(p._Pot,potential.MN3ExponentialDiskPotential): # Three Miyamoto-Nagai disks npot+= 2 pot_type.extend([5,5,5]) pot_args.extend([p._Pot._amp*p._Pot._mn3[0]._amp, p._Pot._mn3[0]._a,p._Pot._mn3[0]._b, p._Pot._amp*p._Pot._mn3[1]._amp, p._Pot._mn3[1]._a,p._Pot._mn3[1]._b, p._Pot._amp*p._Pot._mn3[2]._amp, p._Pot._mn3[2]._a,p._Pot._mn3[2]._b]) elif isinstance(p,potential_src.planarPotential.planarPotentialFromRZPotential) \ and isinstance(p._Pot,potential.KuzminKutuzovStaeckelPotential): pot_type.append(16) pot_args.extend([p._Pot._amp,p._Pot._ac,p._Pot._Delta]) elif isinstance(p,potential_src.planarPotential.planarPotentialFromRZPotential) \ and isinstance(p._Pot,potential.PlummerPotential): pot_type.append(17) pot_args.extend([p._Pot._amp,p._Pot._b]) elif isinstance(p,potential_src.planarPotential.planarPotentialFromRZPotential) \ and isinstance(p._Pot,potential.PseudoIsothermalPotential): pot_type.append(18) pot_args.extend([p._Pot._amp,p._Pot._a]) elif isinstance(p,potential_src.planarPotential.planarPotentialFromRZPotential) \ and isinstance(p._Pot,potential.KuzminDiskPotential): pot_type.append(19) pot_args.extend([p._Pot._amp,p._Pot._a]) elif isinstance(p,potential_src.planarPotential.planarPotentialFromRZPotential) \ and isinstance(p._Pot,potential.BurkertPotential): pot_type.append(20) pot_args.extend([p._Pot._amp,p._Pot.a]) elif (isinstance(p,potential_src.planarPotential.planarPotentialFromFullPotential) or isinstance(p,potential_src.planarPotential.planarPotentialFromRZPotential)) and isinstance(p._Pot,potential.TwoPowerTriaxialPotential): if isinstance(p._Pot,potential.TriaxialHernquistPotential): pot_type.append(21) elif isinstance(p._Pot,potential.TriaxialNFWPotential): pot_type.append(22) elif isinstance(p._Pot,potential.TriaxialJaffePotential): pot_type.append(23) pot_args.extend([p._Pot._amp,p._Pot.a,p._Pot._b2, p._Pot._c2,int(p._Pot._aligned)]) if not p._Pot._aligned: pot_args.extend(list(p._Pot._rot.flatten())) else: pot_args.extend(list(nu.eye(3).flatten())) # not actually used pot_args.append(p._Pot._glorder) pot_args.extend([p._Pot._glx[ii] for ii in range(p._Pot._glorder)]) # this adds some common factors to the integration weights pot_args.extend([-p._Pot._glw[ii]*p._Pot._b*p._Pot._c/p._Pot.a**3.\ /nu.sqrt(( 1.+(p._Pot._b2-1.) *p._Pot._glx[ii]**2.) *(1.+(p._Pot._c2-1.) *p._Pot._glx[ii]**2.)) for ii in range(p._Pot._glorder)]) pot_args.extend([p._Pot._glw[ii] for ii in range(p._Pot._glorder)]) pot_args.extend([0.,0.,0.,0.,0.,0.]) elif (isinstance(p,potential_src.planarPotential.planarPotentialFromFullPotential) or isinstance(p,potential_src.planarPotential.planarPotentialFromRZPotential)) \ and isinstance(p._Pot,potential.SCFPotential): pt,pa= _parse_scf_pot(p._Pot) pot_type.append(pt) pot_args.extend(pa) elif isinstance(p,potential_src.planarPotential.planarPotentialFromFullPotential) \ and isinstance(p._Pot,potential.SoftenedNeedleBarPotential): pot_type.append(25) pot_args.extend([p._Pot._amp,p._Pot._a,p._Pot._b,p._Pot._c2, p._Pot._pa,p._Pot._omegab]) pot_args.extend([0.,0.,0.,0.,0.,0.,0.]) # for caching elif (isinstance(p,potential_src.planarPotential.planarPotentialFromFullPotential) or isinstance(p,potential_src.planarPotential.planarPotentialFromRZPotential)) \ and isinstance(p._Pot,potential.DiskSCFPotential): # Need to pull this apart into: (a) SCF part, (b) constituent # [Sigma_i,h_i] parts # (a) SCF, multiply in any add'l amp pt,pa= _parse_scf_pot(p._Pot._scf,extra_amp=p._Pot._amp) pot_type.append(pt) pot_args.extend(pa) # (b) constituent [Sigma_i,h_i] parts for Sigma,hz in zip(p._Pot._Sigma_dict,p._Pot._hz_dict): npot+= 1 pot_type.append(26) stype= Sigma.get('type','exp') if stype == 'exp' \ or (stype == 'exp' and 'Rhole' in Sigma): pot_args.extend([3,0, 4.*nu.pi*Sigma.get('amp',1.)*p._Pot._amp, Sigma.get('h',1./3.)]) elif stype == 'expwhole' \ or (stype == 'exp' and 'Rhole' in Sigma): pot_args.extend([4,1, 4.*nu.pi*Sigma.get('amp',1.)*p._Pot._amp, Sigma.get('h',1./3.), Sigma.get('Rhole',0.5)]) hztype= hz.get('type','exp') if hztype == 'exp': pot_args.extend([0,hz.get('h',0.0375)]) elif hztype == 'sech2': pot_args.extend([1,hz.get('h',0.0375)]) elif isinstance(p, potential_src.planarPotential.planarPotentialFromFullPotential) \ and isinstance(p._Pot, potential.SpiralArmsPotential): pot_type.append(27) pot_args.extend([len(p._Pot._Cs), p._Pot._amp, p._Pot._N, p._Pot._sin_alpha, p._Pot._tan_alpha, p._Pot._r_ref, p._Pot._phi_ref, p._Pot._Rs, p._Pot._H, p._Pot._omega]) pot_args.extend(p._Pot._Cs) elif isinstance(p,potential.CosmphiDiskPotential): pot_type.append(28) pot_args.extend([p._amp,p._mphio,p._p,p._mphib,p._m, p._rb,p._rbp,p._rb2p,p._r1p]) elif isinstance(p,potential.HenonHeilesPotential): pot_type.append(29) pot_args.extend([p._amp]) ############################## WRAPPERS ############################### elif ((isinstance(p,potential_src.planarPotential.planarPotentialFromFullPotential) or isinstance(p,potential_src.planarPotential.planarPotentialFromRZPotential)) \ and isinstance(p._Pot,potential.DehnenSmoothWrapperPotential)) \ or isinstance(p,potential.DehnenSmoothWrapperPotential): if not isinstance(p,potential.DehnenSmoothWrapperPotential): p= p._Pot pot_type.append(-1) # wrap_pot_type, args, and npot obtained before this horrible if pot_args.append(wrap_npot) pot_type.extend(wrap_pot_type) pot_args.extend(wrap_pot_args) pot_args.extend([p._amp,p._tform,p._tsteady]) elif ((isinstance(p,potential_src.planarPotential.planarPotentialFromFullPotential) or isinstance(p,potential_src.planarPotential.planarPotentialFromRZPotential)) \ and isinstance(p._Pot,potential.SolidBodyRotationWrapperPotential)) \ or isinstance(p,potential.SolidBodyRotationWrapperPotential): if not isinstance(p,potential.SolidBodyRotationWrapperPotential): p= p._Pot pot_type.append(-2) # wrap_pot_type, args, and npot obtained before this horrible if pot_args.append(wrap_npot) pot_type.extend(wrap_pot_type) pot_args.extend(wrap_pot_args) pot_args.extend([p._amp,p._omega,p._pa]) pot_type= nu.array(pot_type,dtype=nu.int32,order='C') pot_args= nu.array(pot_args,dtype=nu.float64,order='C') return (npot,pot_type,pot_args) def _parse_integrator(int_method): """parse the integrator method to pass to C""" #Pick integrator if int_method.lower() == 'rk4_c': int_method_c= 1 elif int_method.lower() == 'rk6_c': int_method_c= 2 elif int_method.lower() == 'symplec4_c': int_method_c= 3 elif int_method.lower() == 'symplec6_c': int_method_c= 4 elif int_method.lower() == 'dopr54_c': int_method_c= 5 else: int_method_c= 0 return int_method_c def _parse_tol(rtol,atol): """Parse the tolerance keywords""" #Process atol and rtol if rtol is None: rtol= -12.*nu.log(10.) else: #pragma: no cover rtol= nu.log(rtol) if atol is None: atol= -12.*nu.log(10.) else: #pragma: no cover atol= nu.log(atol) return (rtol,atol) def integratePlanarOrbit_c(pot,yo,t,int_method,rtol=None,atol=None, dt=None): """ NAME: integratePlanarOrbit_c PURPOSE: C integrate an ode for a planarOrbit INPUT: pot - Potential or list of such instances yo - initial condition [q,p] t - set of times at which one wants the result int_method= 'leapfrog_c', 'rk4_c', 'rk6_c', 'symplec4_c' rtol, atol dt= (None) force integrator to use this stepsize (default is to automatically determine one)) OUTPUT: (y,err) y : array, shape (len(y0), len(t)) Array containing the value of y for each desired time in t, \ with the initial value y0 in the first row. err: error message, if not zero: 1 means maximum step reduction happened for adaptive integrators HISTORY: 2011-10-03 - Written - Bovy (IAS) """ rtol, atol= _parse_tol(rtol,atol) npot, pot_type, pot_args= _parse_pot(pot) int_method_c= _parse_integrator(int_method) if dt is None: dt= -9999.99 #Set up result array result= nu.empty((len(t),4)) err= ctypes.c_int(0) #Set up the C code ndarrayFlags= ('C_CONTIGUOUS','WRITEABLE') integrationFunc= _lib.integratePlanarOrbit integrationFunc.argtypes= [ndpointer(dtype=nu.float64,flags=ndarrayFlags), ctypes.c_int, ndpointer(dtype=nu.float64,flags=ndarrayFlags), ctypes.c_int, ndpointer(dtype=nu.int32,flags=ndarrayFlags), ndpointer(dtype=nu.float64,flags=ndarrayFlags), ctypes.c_double, ctypes.c_double, ctypes.c_double, ndpointer(dtype=nu.float64,flags=ndarrayFlags), ctypes.POINTER(ctypes.c_int), ctypes.c_int] #Array requirements, first store old order f_cont= [yo.flags['F_CONTIGUOUS'], t.flags['F_CONTIGUOUS']] yo= nu.require(yo,dtype=nu.float64,requirements=['C','W']) t= nu.require(t,dtype=nu.float64,requirements=['C','W']) result= nu.require(result,dtype=nu.float64,requirements=['C','W']) #Run the C code integrationFunc(yo, ctypes.c_int(len(t)), t, ctypes.c_int(npot), pot_type, pot_args, ctypes.c_double(dt), ctypes.c_double(rtol),ctypes.c_double(atol), result, ctypes.byref(err), ctypes.c_int(int_method_c)) if err.value == -10: #pragma: no cover raise KeyboardInterrupt("Orbit integration interrupted by CTRL-C (SIGINT)") #Reset input arrays if f_cont[0]: yo= nu.asfortranarray(yo) if f_cont[1]: t= nu.asfortranarray(t) return (result,err.value) def integratePlanarOrbit_dxdv_c(pot,yo,dyo,t,int_method,rtol=None,atol=None, dt=None): """ NAME: integratePlanarOrbit_dxdv_c PURPOSE: C integrate an ode for a planarOrbit+phase space volume dxdv INPUT: pot - Potential or list of such instances yo - initial condition [q,p] dyo - initial condition [dq,dp] t - set of times at which one wants the result int_method= 'leapfrog_c', 'rk4_c', 'rk6_c', 'symplec4_c' rtol, atol dt= (None) force integrator to use this stepsize (default is to automatically determine one)) OUTPUT: (y,err) y : array, shape (len(y0), len(t)) Array containing the value of y for each desired time in t, \ with the initial value y0 in the first row. err: error message if not zero, 1: maximum step reduction happened for adaptive integrators HISTORY: 2011-10-19 - Written - Bovy (IAS) """ rtol, atol= _parse_tol(rtol,atol) npot, pot_type, pot_args= _parse_pot(pot) int_method_c= _parse_integrator(int_method) if dt is None: dt= -9999.99 yo= nu.concatenate((yo,dyo)) #Set up result array result= nu.empty((len(t),8)) err= ctypes.c_int(0) #Set up the C code ndarrayFlags= ('C_CONTIGUOUS','WRITEABLE') integrationFunc= _lib.integratePlanarOrbit_dxdv integrationFunc.argtypes= [ndpointer(dtype=nu.float64,flags=ndarrayFlags), ctypes.c_int, ndpointer(dtype=nu.float64,flags=ndarrayFlags), ctypes.c_int, ndpointer(dtype=nu.int32,flags=ndarrayFlags), ndpointer(dtype=nu.float64,flags=ndarrayFlags), ctypes.c_double, ctypes.c_double, ctypes.c_double, ndpointer(dtype=nu.float64,flags=ndarrayFlags), ctypes.POINTER(ctypes.c_int), ctypes.c_int] #Array requirements, first store old order f_cont= [yo.flags['F_CONTIGUOUS'], t.flags['F_CONTIGUOUS']] yo= nu.require(yo,dtype=nu.float64,requirements=['C','W']) t= nu.require(t,dtype=nu.float64,requirements=['C','W']) result= nu.require(result,dtype=nu.float64,requirements=['C','W']) #Run the C code integrationFunc(yo, ctypes.c_int(len(t)), t, ctypes.c_int(npot), pot_type, pot_args, ctypes.c_double(dt), ctypes.c_double(rtol),ctypes.c_double(atol), result, ctypes.byref(err), ctypes.c_int(int_method_c)) if err.value == -10: #pragma: no cover raise KeyboardInterrupt("Orbit integration interrupted by CTRL-C (SIGINT)") #Reset input arrays if f_cont[0]: yo= nu.asfortranarray(yo) if f_cont[1]: t= nu.asfortranarray(t) return (result,err.value) galpy-1.3.0/galpy/orbit_src/linearOrbit.py0000644000076500000240000001253013154252746020671 0ustar bovystaff00000000000000import numpy as nu from scipy import integrate from galpy.orbit_src.OrbitTop import OrbitTop from galpy.potential_src.linearPotential import _evaluatelinearForces,\ evaluatelinearPotentials import galpy.util.bovy_plot as plot import galpy.util.bovy_symplecticode as symplecticode from galpy.util.bovy_conversion import physical_conversion class linearOrbit(OrbitTop): """Class that represents an orbit in a (effectively) one-dimensional potential""" def __init__(self,vxvv=[1.,0.],vo=220.,ro=8.0): """ NAME: __init__ PURPOSE: Initialize a linear orbit INPUT: vxvv - [x,vx] vo - circular velocity at ro (km/s) ro - distance from vantage point to GC (kpc) OUTPUT: (none) HISTORY: 2010-07-13 - Written - Bovy (NYU) """ OrbitTop.__init__(self,vxvv=vxvv, ro=ro,zo=None,vo=vo,solarmotion=None) return None def integrate(self,t,pot,method='odeint',dt=None): """ NAME: integrate PURPOSE: integrate the orbit INPUT: t - list of times at which to output (0 has to be in this!) pot - potential instance or list of instances method= 'odeint'= scipy's odeint, or 'leapfrog' dt= (None) if set, force the integrator to use this basic stepsize; must be an integer divisor of output stepsize (NOT USED FOR LINEAR ORBIT SO FAR) OUTPUT: (none) (get the actual orbit using getOrbit() HISTORY: 2010-07-13 - Written - Bovy (NYU) """ if hasattr(self,'_orbInterp'): delattr(self,'_orbInterp') self.t= nu.array(t) self._pot= pot self.orbit= _integrateLinearOrbit(self.vxvv,pot,t,method) @physical_conversion('energy') def E(self,*args,**kwargs): """ NAME: E PURPOSE: calculate the energy INPUT: t - (optional) time at which to get the radius pot= linearPotential instance or list thereof OUTPUT: energy HISTORY: 2010-09-15 - Written - Bovy (NYU) """ if not 'pot' in kwargs or kwargs['pot'] is None: try: pot= self._pot except AttributeError: raise AttributeError("Integrate orbit or specify pot=") if 'pot' in kwargs and kwargs['pot'] is None: kwargs.pop('pot') else: pot= kwargs.pop('pot') if len(args) > 0: t= args[0] else: t= 0. #Get orbit thiso= self(*args,**kwargs) onet= (len(thiso.shape) == 1) if onet: return evaluatelinearPotentials(pot,thiso[0], t=t,use_physical=False)\ +thiso[1]**2./2. else: return nu.array([evaluatelinearPotentials(pot,thiso[0,ii], t=t[ii], use_physical=False)\ +thiso[1,ii]**2./2.\ for ii in range(len(t))]) def e(self,analytic=False,pot=None): #pragma: no cover """ NAME: e PURPOSE: calculate the eccentricity INPUT: OUTPUT: eccentricity HISTORY: 2010-09-15 - Written - Bovy (NYU) """ raise AttributeError("linearOrbit does not have an eccentricity") def rap(self,analytic=False,pot=None): #pragma: no cover raise AttributeError("linearOrbit does not have an apocenter") def rperi(self,analytic=False,pot=None): #pragma: no cover raise AttributeError("linearOrbit does not have a pericenter") def zmax(self): #pragma: no cover raise AttributeError("linearOrbit does not have a zmax") def _integrateLinearOrbit(vxvv,pot,t,method): """ NAME: integrateLinearOrbit PURPOSE: integrate a one-dimensional orbit INPUT: vxvv - initial condition [x,vx] pot - linearPotential or list of linearPotentials t - list of times at which to output (0 has to be in this!) method - 'odeint' or 'leapfrog' OUTPUT: [:,2] array of [x,vx] at each t HISTORY: 2010-07-13- Written - Bovy (NYU) """ if '_c' in method: if 'leapfrog' in method or 'symplec' in method: method= 'leapfrog' else: method= 'odeint' if method.lower() == 'leapfrog': return symplecticode.leapfrog(lambda x,t=t: _evaluatelinearForces(pot,x, t=t), nu.array(vxvv), t,rtol=10.**-8) elif method.lower() == 'odeint': return integrate.odeint(_linearEOM,vxvv,t,args=(pot,),rtol=10.**-8.) def _linearEOM(y,t,pot): """ NAME: linearEOM PURPOSE: the one-dimensional equation-of-motion INPUT: y - current phase-space position t - current time pot - (list of) linearPotential instance(s) OUTPUT: dy/dt HISTORY: 2010-07-13 - Bovy (NYU) """ return [y[1],_evaluatelinearForces(pot,y[0],t=t)] galpy-1.3.0/galpy/orbit_src/Orbit.py0000644000076500000240000031233013231751437017474 0ustar bovystaff00000000000000import warnings import numpy as nu _APY_LOADED= True try: from astropy import units except ImportError: _APY_LOADED= False import galpy.util.bovy_coords as coords from galpy.util.bovy_conversion import physical_conversion from galpy.util import galpyWarning from galpy.util import bovy_conversion from galpy.util import config _APY_UNITS= config.__config__.getboolean('astropy','astropy-units') from galpy.orbit_src.FullOrbit import FullOrbit from galpy.orbit_src.RZOrbit import RZOrbit from galpy.orbit_src.planarOrbit import planarOrbit, planarROrbit, \ planarOrbitTop from galpy.orbit_src.linearOrbit import linearOrbit _K=4.74047 if _APY_LOADED: vxvv_units= [units.kpc,units.km/units.s,units.km/units.s, units.kpc,units.km/units.s,units.rad] else: _APY_UNITS= False class Orbit(object): """General orbit class representing an orbit""" def __init__(self,vxvv=None,uvw=False,lb=False, radec=False,vo=None,ro=None,zo=0.025, solarmotion='hogg'): """ NAME: __init__ PURPOSE: Initialize an Orbit instance INPUT: vxvv - initial conditions 3D can be either 1) in Galactocentric cylindrical coordinates [R,vR,vT(,z,vz,phi)]; can be Quantities 2) [ra,dec,d,mu_ra, mu_dec,vlos] in [deg,deg,kpc,mas/yr,mas/yr,km/s] (all J2000.0; mu_ra = mu_ra * cos dec); can be Quantities 3) [ra,dec,d,U,V,W] in [deg,deg,kpc,km/s,km/s,kms]; can be Quantities 4) (l,b,d,mu_l, mu_b, vlos) in [deg,deg,kpc,mas/yr,mas/yr,km/s) (all J2000.0; mu_l = mu_l * cos b); can be Quantities 5) [l,b,d,U,V,W] in [deg,deg,kpc,km/s,km/s,kms]; can be Quantities 4) and 5) also work when leaving out b and mu_b/W OPTIONAL INPUTS: radec= if True, input is 2) (or 3) above uvw= if True, velocities are UVW lb= if True, input is 4) or 5) above ro= distance from vantage point to GC (kpc; can be Quantity) vo= circular velocity at ro (km/s; can be Quantity) zo= offset toward the NGP of the Sun wrt the plane (kpc; can be Quantity) solarmotion= 'hogg' or 'dehnen', or 'schoenrich', or value in [-U,V,W]; can be Quantity If ro and/or vo are specified, outputs involving distances or velocities (whether as instance methods or in plots) will by default be displayed in the physical coordinates implied by these scales. This can be overwritten for each individual method by using use_physical=False as a keyword for the method. OUTPUT: instance HISTORY: 2010-07-20 - Written - Bovy (NYU) """ # If you change the way an Orbit object is setup, also change each of # the methods that return Orbits if _APY_LOADED and isinstance(ro,units.Quantity): ro= ro.to(units.kpc).value if _APY_LOADED and isinstance(zo,units.Quantity): zo= zo.to(units.kpc).value if _APY_LOADED and isinstance(vo,units.Quantity): vo= vo.to(units.km/units.s).value if radec or lb: if ro is None: ro= config.__config__.getfloat('normalization','ro') if vo is None: vo= config.__config__.getfloat('normalization','vo') if isinstance(solarmotion,str) and solarmotion.lower() == 'hogg': vsolar= nu.array([-10.1,4.0,6.7]) elif isinstance(solarmotion,str) and solarmotion.lower() == 'dehnen': vsolar= nu.array([-10.,5.25,7.17]) elif isinstance(solarmotion,str) \ and solarmotion.lower() == 'schoenrich': vsolar= nu.array([-11.1,12.24,7.25]) elif _APY_LOADED and isinstance(solarmotion,units.Quantity): vsolar= solarmotion.to(units.km/units.s).value else: vsolar= nu.array(solarmotion) if radec or lb: if radec: if _APY_LOADED and isinstance(vxvv[0],units.Quantity): ra, dec= vxvv[0].to(units.deg).value, \ vxvv[1].to(units.deg).value else: ra, dec= vxvv[0], vxvv[1] l,b= coords.radec_to_lb(ra,dec,degree=True) elif len(vxvv) == 4: l, b= vxvv[0], 0. else: l,b= vxvv[0],vxvv[1] if _APY_LOADED and isinstance(l,units.Quantity): l= l.to(units.deg).value if _APY_LOADED and isinstance(b,units.Quantity): b= b.to(units.deg).value if uvw: if _APY_LOADED and isinstance(vxvv[2],units.Quantity): X,Y,Z= coords.lbd_to_XYZ(l,b,vxvv[2].to(units.kpc).value, degree=True) else: X,Y,Z= coords.lbd_to_XYZ(l,b,vxvv[2],degree=True) vx= vxvv[3] vy= vxvv[4] vz= vxvv[5] if _APY_LOADED and isinstance(vx,units.Quantity): vx= vx.to(units.km/units.s).value if _APY_LOADED and isinstance(vy,units.Quantity): vy= vy.to(units.km/units.s).value if _APY_LOADED and isinstance(vz,units.Quantity): vz= vz.to(units.km/units.s).value else: if radec: if _APY_LOADED and isinstance(vxvv[3],units.Quantity): pmra, pmdec= vxvv[3].to(units.mas/units.yr).value, \ vxvv[4].to(units.mas/units.yr).value else: pmra, pmdec= vxvv[3], vxvv[4] pmll, pmbb= coords.pmrapmdec_to_pmllpmbb(pmra,pmdec,ra,dec, degree=True) d, vlos= vxvv[2], vxvv[5] elif len(vxvv) == 4: pmll, pmbb= vxvv[2], 0. d, vlos= vxvv[1], vxvv[3] else: pmll, pmbb= vxvv[3], vxvv[4] d, vlos= vxvv[2], vxvv[5] if _APY_LOADED and isinstance(d,units.Quantity): d= d.to(units.kpc).value if _APY_LOADED and isinstance(vlos,units.Quantity): vlos= vlos.to(units.km/units.s).value if _APY_LOADED and isinstance(pmll,units.Quantity): pmll= pmll.to(units.mas/units.yr).value if _APY_LOADED and isinstance(pmbb,units.Quantity): pmbb= pmbb.to(units.mas/units.yr).value X,Y,Z,vx,vy,vz= coords.sphergal_to_rectgal(l,b,d, vlos,pmll, pmbb, degree=True) X/= ro Y/= ro Z/= ro vx/= vo vy/= vo vz/= vo vsun= nu.array([0.,1.,0.,])+vsolar/vo R, phi, z= coords.XYZ_to_galcencyl(X,Y,Z,Zsun=zo/ro) vR, vT,vz= coords.vxvyvz_to_galcencyl(vx,vy,vz, R,phi,z, vsun=vsun, Xsun=1.,Zsun=zo/ro, galcen=True) if lb and len(vxvv) == 4: vxvv= [R,vR,vT,phi] else: vxvv= [R,vR,vT,z,vz,phi] # Parse vxvv if it consists of Quantities if _APY_LOADED and isinstance(vxvv[0],units.Quantity): # Need to set ro and vo, default if not specified if ro is None: ro= config.__config__.getfloat('normalization','ro') if vo is None: vo= config.__config__.getfloat('normalization','vo') new_vxvv= [vxvv[0].to(vxvv_units[0]).value/ro, vxvv[1].to(vxvv_units[1]).value/vo] if len(vxvv) > 2: new_vxvv.append(vxvv[2].to(vxvv_units[2]).value/vo) if len(vxvv) == 4: new_vxvv.append(vxvv[3].to(vxvv_units[5]).value) elif len(vxvv) > 4: new_vxvv.append(vxvv[3].to(vxvv_units[3]).value/ro) new_vxvv.append(vxvv[4].to(vxvv_units[4]).value/vo) if len(vxvv) == 6: new_vxvv.append(vxvv[5].to(vxvv_units[5]).value) vxvv= new_vxvv if len(vxvv) == 2: self._orb= linearOrbit(vxvv=vxvv, ro=ro,vo=vo) elif len(vxvv) == 3: self._orb= planarROrbit(vxvv=vxvv, ro=ro,vo=vo,zo=zo,solarmotion=vsolar) elif len(vxvv) == 4: self._orb= planarOrbit(vxvv=vxvv, ro=ro,vo=vo,zo=zo,solarmotion=vsolar) elif len(vxvv) == 5: self._orb= RZOrbit(vxvv=vxvv, ro=ro,vo=vo,zo=zo,solarmotion=vsolar) elif len(vxvv) == 6: self._orb= FullOrbit(vxvv=vxvv, ro=ro,vo=vo,zo=zo,solarmotion=vsolar) #Store for actionAngle conversions if vo is None: self._vo= config.__config__.getfloat('normalization','vo') self._voSet= False else: self._vo= vo self._voSet= True if ro is None: self._ro= config.__config__.getfloat('normalization','ro') self._roSet= False else: self._ro= ro self._roSet= True return None def setphi(self,phi): """ NAME: setphi PURPOSE: set initial azimuth INPUT: phi - desired azimuth OUTPUT: (none) HISTORY: 2010-08-01 - Written - Bovy (NYU) BUGS: Should perform check that this orbit has phi """ if len(self._orb.vxvv) == 2: raise AttributeError("One-dimensional orbit has no azimuth") elif len(self._orb.vxvv) == 3: #Upgrade vxvv= [self._orb.vxvv[0],self._orb.vxvv[1],self._orb.vxvv[2],phi] self._orb= planarOrbit(vxvv=vxvv) elif len(self._orb.vxvv) == 4: self._orb.vxvv[-1]= phi elif len(self._orb.vxvv) == 5: #Upgrade vxvv= [self._orb.vxvv[0],self._orb.vxvv[1],self._orb.vxvv[2], self._orb.vxvv[3],self._orb.vxvv[4],phi] self._orb= FullOrbit(vxvv=vxvv) elif len(self._orb.vxvv) == 6: self._orb.vxvv[-1]= phi def dim(self): """ NAME: dim PURPOSE: return the dimension of the problem INPUT: (none) OUTPUT: dimension HISTORY: 2011-02-03 - Written - Bovy (NYU) """ if len(self._orb.vxvv) == 2: return 1 elif len(self._orb.vxvv) == 3 or len(self._orb.vxvv) == 4: return 2 elif len(self._orb.vxvv) == 5 or len(self._orb.vxvv) == 6: return 3 def turn_physical_off(self): """ NAME: turn_physical_off PURPOSE: turn off automatic returning of outputs in physical units INPUT: (none) OUTPUT: (none) HISTORY: 2014-06-17 - Written - Bovy (IAS) """ self._roSet= False self._voSet= False self._orb.turn_physical_off() def turn_physical_on(self,ro=None,vo=None): """ NAME: turn_physical_on PURPOSE: turn on automatic returning of outputs in physical units INPUT: ro= reference distance (kpc; can be Quantity) vo= reference velocity (km/s; can be Quantity) OUTPUT: (none) HISTORY: 2016-01-19 - Written - Bovy (UofT) """ self._roSet= True self._voSet= True if not ro is None: if _APY_LOADED and isinstance(ro,units.Quantity): ro= ro.to(units.kpc).value self._ro= ro if not vo is None: if _APY_LOADED and isinstance(vo,units.Quantity): vo= vo.to(units.km/units.s).value self._vo= vo self._orb.turn_physical_on(ro=ro,vo=vo) def integrate(self,t,pot,method='symplec4_c',dt=None): """ NAME: integrate PURPOSE: integrate the orbit INPUT: t - list of times at which to output (0 has to be in this!) (can be Quantity) pot - potential instance or list of instances method= 'odeint' for scipy's odeint 'leapfrog' for a simple leapfrog implementation 'leapfrog_c' for a simple leapfrog implementation in C 'symplec4_c' for a 4th order symplectic integrator in C 'symplec6_c' for a 6th order symplectic integrator in C 'rk4_c' for a 4th-order Runge-Kutta integrator in C 'rk6_c' for a 6-th order Runge-Kutta integrator in C 'dopr54_c' for a Dormand-Prince integrator in C (generally the fastest) dt= (None) if set, force the integrator to use this basic stepsize; must be an integer divisor of output stepsize (only works for the C integrators that use a fixed stepsize) (can be Quantity) OUTPUT: (none) (get the actual orbit using getOrbit() HISTORY: 2010-07-10 - Written - Bovy (NYU) 2015-06-28 - Added dt keyword - Bovy (IAS) """ _check_potential_dim(self,pot) _check_consistent_units(self,pot) # Parse t if _APY_LOADED and isinstance(t,units.Quantity): self._orb._integrate_t_asQuantity= True t= t.to(units.Gyr).value\ /bovy_conversion.time_in_Gyr(self._vo,self._ro) if _APY_LOADED and not dt is None and isinstance(dt,units.Quantity): dt= dt.to(units.Gyr).value\ /bovy_conversion.time_in_Gyr(self._vo,self._ro) from galpy.potential import MWPotential if pot == MWPotential: warnings.warn("Use of MWPotential as a Milky-Way-like potential is deprecated; galpy.potential.MWPotential2014, a potential fit to a large variety of dynamical constraints (see Bovy 2015), is the preferred Milky-Way-like potential in galpy", galpyWarning) if not _check_integrate_dt(t,dt): raise ValueError('dt input (integrator stepsize) for Orbit.integrate must be an integer divisor of the output stepsize') self._orb.integrate(t,pot,method=method,dt=dt) def integrate_dxdv(self,dxdv,t,pot,method='dopr54_c', rectIn=False,rectOut=False): """ NAME: integrate_dxdv PURPOSE: integrate the orbit and a small area of phase space INPUT: dxdv - [dR,dvR,dvT,dphi] t - list of times at which to output (0 has to be in this!) (can be Quantity) pot - potential instance or list of instances method= 'odeint' for scipy's odeint 'rk4_c' for a 4th-order Runge-Kutta integrator in C 'rk6_c' for a 6-th order Runge-Kutta integrator in C 'dopr54_c' for a Dormand-Prince integrator in C (generally the fastest) 'dopr54_c' is recommended, odeint is *not* recommended rectIn= (False) if True, input dxdv is in rectangular coordinates rectOut= (False) if True, output dxdv (that in orbit_dxdv) is in rectangular coordinates OUTPUT: (none) (get the actual orbit using getOrbit_dxdv(), the orbit that is integrated alongside with dxdv is stored as usual, any previous regular orbit integration will be erased!) HISTORY: 2010-10-17 - Written - Bovy (IAS) 2014-06-29 - Added rectIn and rectOut - Bovy (IAS) """ _check_potential_dim(self,pot) _check_consistent_units(self,pot) # Parse t if _APY_LOADED and isinstance(t,units.Quantity): self._orb._integrate_t_asQuantity= True t= t.to(units.Gyr).value\ /bovy_conversion.time_in_Gyr(self._vo,self._ro) self._orb.integrate_dxdv(dxdv,t,pot,method=method, rectIn=rectIn,rectOut=rectOut) def reverse(self): """ NAME: reverse PURPOSE: reverse an already integrated orbit (that is, make it go from end to beginning in t=0 to tend) INPUT: (none) OUTPUT: (none) HISTORY: 2011-04-13 - Written - Bovy (NYU) """ if hasattr(self,'_orbInterp'): delattr(self,'_orbInterp') if hasattr(self,'rs'): delattr(self,'rs') sortindx = list(range(len(self._orb.t))) sortindx.sort(key=lambda x: self._orb.t[x],reverse=True) for ii in range(self._orb.orbit.shape[1]): self._orb.orbit[:,ii]= self._orb.orbit[sortindx,ii] return None def flip(self,inplace=False): """ NAME: flip PURPOSE: 'flip' an orbit's initial conditions such that the velocities are minus the original velocities; useful for quick backward integration; returns a new Orbit instance INPUT: inplace= (False) if True, flip the orbit in-place, that is, without returning a new instance and also flip the velocities of the integrated orbit (if it exists) OUTPUT: Orbit instance that has the velocities of the current orbit flipped (inplace=False) or just flips all velocities of current instance (inplace=True) HISTORY: 2014-06-17 - Written - Bovy (IAS) 2016-07-21 - Added inplace keyword - Bovy (UofT) """ if inplace: self._orb.vxvv[1]= -self._orb.vxvv[1] if len(self._orb.vxvv) > 2: self._orb.vxvv[2]= -self._orb.vxvv[2] if len(self._orb.vxvv) > 4: self._orb.vxvv[4]= -self._orb.vxvv[4] if hasattr(self._orb,'orbit'): self._orb.orbit[:,1]= -self._orb.orbit[:,1] if len(self._orb.vxvv) > 2: self._orb.orbit[:,2]= -self._orb.orbit[:,2] if len(self._orb.vxvv) > 4: self._orb.orbit[:,4]= -self._orb.orbit[:,4] return None orbSetupKwargs= {'ro':None, 'vo':None, 'zo':self._orb._zo, 'solarmotion':self._orb._solarmotion} if self._orb._roSet: orbSetupKwargs['ro']= self._orb._ro if self._orb._voSet: orbSetupKwargs['vo']= self._orb._vo if len(self._orb.vxvv) == 2: return Orbit(vxvv= [self._orb.vxvv[0],-self._orb.vxvv[1]], **orbSetupKwargs) elif len(self._orb.vxvv) == 3: return Orbit(vxvv=[self._orb.vxvv[0],-self._orb.vxvv[1], -self._orb.vxvv[2]],**orbSetupKwargs) elif len(self._orb.vxvv) == 4: return Orbit(vxvv=[self._orb.vxvv[0],-self._orb.vxvv[1], -self._orb.vxvv[2],self._orb.vxvv[3]], **orbSetupKwargs) elif len(self._orb.vxvv) == 5: return Orbit(vxvv=[self._orb.vxvv[0],-self._orb.vxvv[1], -self._orb.vxvv[2],self._orb.vxvv[3], -self._orb.vxvv[4]],**orbSetupKwargs) elif len(self._orb.vxvv) == 6: return Orbit(vxvv= [self._orb.vxvv[0],-self._orb.vxvv[1], -self._orb.vxvv[2],self._orb.vxvv[3], -self._orb.vxvv[4],self._orb.vxvv[5]], **orbSetupKwargs) def getOrbit(self): """ NAME: getOrbit PURPOSE: return a previously calculated orbit INPUT: (none) OUTPUT: array orbit[nt,nd] HISTORY: 2010-07-10 - Written - Bovy (NYU) """ return self._orb.getOrbit() def getOrbit_dxdv(self): """ NAME: getOrbit_dxdv PURPOSE: return a previously calculated integration of a small phase-space volume (with integrate_dxdv) INPUT: (none) OUTPUT: array orbit[nt,nd*2] with for each t the dxdv vector HISTORY: 2010-07-10 - Written - Bovy (NYU) """ return self._orb.getOrbit_dxdv() def fit(self,vxvv,vxvv_err=None,pot=None,radec=False,lb=False, customsky=False,lb_to_customsky=None,pmllpmbb_to_customsky=None, tintJ=10,ntintJ=1000,integrate_method='dopr54_c', **kwargs): """ NAME: fit PURPOSE: fit an Orbit to data using the current orbit as the initial condition INPUT: vxvv - [:,6] array of positions and velocities along the orbit (if not lb=True or radec=True, these need to be in natural units [/ro,/vo], cannot be Quantities) vxvv_err= [:,6] array of errors on positions and velocities along the orbit (if None, these are set to 0.01) (if not lb=True or radec=True, these need to be in natural units [/ro,/vo], cannot be Quantities) pot= Potential to fit the orbit in Keywords related to the input data: radec= if True, input vxvv and vxvv are [ra,dec,d,mu_ra, mu_dec,vlos] in [deg,deg,kpc,mas/yr,mas/yr,km/s] (all J2000.0; mu_ra = mu_ra * cos dec); the attributes of the current Orbit are used to convert between these coordinates and Galactocentric coordinates lb= if True, input vxvv and vxvv are [long,lat,d,mu_ll, mu_bb,vlos] in [deg,deg,kpc,mas/yr,mas/yr,km/s] (mu_ll = mu_ll * cos lat); the attributes of the current Orbit are used to convert between these coordinates and Galactocentric coordinates customsky= if True, input vxvv and vxvv_err are [custom long,custom lat,d,mu_customll, mu_custombb,vlos] in [deg,deg,kpc,mas/yr,mas/yr,km/s] (mu_ll = mu_ll * cos lat) where custom longitude and custom latitude are a custom set of sky coordinates (e.g., ecliptic) and the proper motions are also expressed in these coordinats; you need to provide the functions lb_to_customsky and pmllpmbb_to_customsky to convert to the custom sky coordinates (these should have the same inputs and outputs as lb_to_radec and pmllpmbb_to_pmrapmdec); the attributes of the current Orbit are used to convert between these coordinates and Galactocentric coordinates obs=[X,Y,Z,vx,vy,vz] - (optional) position and velocity of observer (in kpc and km/s; entries can be Quantity) (default=Object-wide default) Cannot be an Orbit instance with the orbit of the reference point, as w/ the ra etc. functions Y is ignored and always assumed to be zero ro= distance in kpc corresponding to R=1. (default: taken from object; can be Quantity) vo= velocity in km/s corresponding to v=1. (default: taken from object; can be Quantity) lb_to_customsky= function that converts l,b,degree=False to the custom sky coordinates (like lb_to_radec); needs to be given when customsky=True pmllpmbb_to_customsky= function that converts pmll,pmbb,l,b,degree=False to proper motions in the custom sky coordinates (like pmllpmbb_to_pmrapmdec); needs to be given when customsky=True Keywords related to the orbit integrations: tintJ= (default: 10) time to integrate orbits for fitting the orbit (can be Quantity) ntintJ= (default: 1000) number of time-integration points integrate_method= (default: 'dopr54_c') integration method to use disp= (False) display the optimizer's convergence message OUTPUT: max of log likelihood HISTORY: 2014-06-17 - Written - Bovy (IAS) """ _check_potential_dim(self,pot) _check_consistent_units(self,pot) return self._orb.fit(vxvv,vxvv_err=vxvv_err,pot=pot, radec=radec,lb=lb, customsky=customsky, lb_to_customsky=lb_to_customsky, pmllpmbb_to_customsky=pmllpmbb_to_customsky, tintJ=tintJ,ntintJ=ntintJ, integrate_method=integrate_method, **kwargs) def E(self,*args,**kwargs): """ NAME: E PURPOSE: calculate the energy INPUT: t - (optional) time at which to get the energy (can be Quantity) pot= Potential instance or list of such instances vo= (Object-wide default) physical scale for velocities to use to convert (can be Quantity) use_physical= use to override Object-wide default for using a physical scale for output OUTPUT: energy HISTORY: 2010-09-15 - Written - Bovy (NYU) """ _check_consistent_units(self,kwargs.get('pot',None)) return self._orb.E(*args,**kwargs) def L(self,*args,**kwargs): """ NAME: L PURPOSE: calculate the angular momentum at time t INPUT: t - (optional) time at which to get the angular momentum (can be Quantity) ro= (Object-wide default) physical scale for distances to use to convert (can be Quantity) vo= (Object-wide default) physical scale for velocities to use to convert (can be Quantity) use_physical= use to override Object-wide default for using a physical scale for output OUTPUT: angular momentum HISTORY: 2010-09-15 - Written - Bovy (NYU) """ return self._orb.L(*args,**kwargs) def ER(self,*args,**kwargs): """ NAME: ER PURPOSE: calculate the radial energy INPUT: t - (optional) time at which to get the radial energy (can be Quantity) pot= Potential instance or list of such instances vo= (Object-wide default) physical scale for velocities to use to convert (can be Quantity) use_physical= use to override Object-wide default for using a physical scale for output (can be Quantity) OUTPUT: radial energy HISTORY: 2013-11-30 - Written - Bovy (IAS) """ _check_consistent_units(self,kwargs.get('pot',None)) return self._orb.ER(*args,**kwargs) def Ez(self,*args,**kwargs): """ NAME: Ez PURPOSE: calculate the vertical energy INPUT: t - (optional) time at which to get the vertical energy (can be Quantity) pot= Potential instance or list of such instances vo= (Object-wide default) physical scale for velocities to use to convert (can be Quantity) use_physical= use to override Object-wide default for using a physical scale for output (can be Quantity) OUTPUT: vertical energy HISTORY: 2013-11-30 - Written - Bovy (IAS) """ _check_consistent_units(self,kwargs.get('pot',None)) return self._orb.Ez(*args,**kwargs) def Jacobi(self,*args,**kwargs): """ NAME: Jacobi PURPOSE: calculate the Jacobi integral E - Omega L INPUT: t - (optional) time at which to get the Jacobi integral (can be Quantity) OmegaP= pattern speed (can be Quantity) pot= potential instance or list of such instances vo= (Object-wide default) physical scale for velocities to use to convert (can be Quantity) use_physical= use to override Object-wide default for using a physical scale for output OUTPUT: Jacobi integral HISTORY: 2011-04-18 - Written - Bovy (NYU) """ _check_consistent_units(self,kwargs.get('pot',None)) out= self._orb.Jacobi(*args,**kwargs) if not isinstance(out,float) and len(out) == 1: return out[0] else: return out def e(self,analytic=False,pot=None,**kwargs): """ NAME: e PURPOSE: calculate the eccentricity, either numerically from the numerical orbit integration or using analytical means INPUT: analytic(= False) compute this analytically pot - potential to use for analytical calculation For 3D orbits different approximations for analytic=True are available (see the EccZmaxRperiRap method of actionAngle modules): type= ('staeckel') type of actionAngle module to use 1) 'adiabatic': assuming motion splits into R and z 2) 'staeckel': assuming motion splits into u and v of prolate spheroidal coordinate system, exact for Staeckel potentials (incl. all spherical potentials) 3) 'spherical': for spherical potentials, exact +actionAngle module setup kwargs for the corresponding actionAngle modules (actionAngleAdiabatic, actionAngleStaeckel, and actionAngleSpherical) OUTPUT: eccentricity HISTORY: 2010-09-15 - Written - Bovy (NYU) 2017-12-25 - Added Staeckel approximation and made that the default - Bovy (UofT) """ _check_consistent_units(self,pot) return self._orb.e(analytic=analytic,pot=pot,**kwargs) def rap(self,analytic=False,pot=None,**kwargs): """ NAME: rap PURPOSE: calculate the apocenter radius, either numerically from the numerical orbit integration or using analytical means INPUT: analytic(= False) compute this analytically pot - potential to use for analytical calculation For 3D orbits different approximations for analytic=True are available (see the EccZmaxRperiRap method of actionAngle modules): type= ('staeckel') type of actionAngle module to use 1) 'adiabatic': assuming motion splits into R and z 2) 'staeckel': assuming motion splits into u and v of prolate spheroidal coordinate system, exact for Staeckel potentials (incl. all spherical potentials) 3) 'spherical': for spherical potentials, exact +actionAngle module setup kwargs for the corresponding actionAngle modules (actionAngleAdiabatic, actionAngleStaeckel, and actionAngleSpherical) ro= (Object-wide default) physical scale for distances to use to convert (can be Quantity) use_physical= use to override Object-wide default for using a physical scale for output OUTPUT: R_ap HISTORY: 2010-09-20 - Written - Bovy (NYU) 2017-12-25 - Added Staeckel approximation and made that the default - Bovy (UofT) """ _check_consistent_units(self,pot) return self._orb.rap(analytic=analytic,pot=pot,**kwargs) def rperi(self,analytic=False,pot=None,**kwargs): """ NAME: rperi PURPOSE: calculate the pericenter radius, either numerically from the numerical orbit integration or using analytical means INPUT: analytic(= False) compute this analytically pot - potential to use for analytical calculation For 3D orbits different approximations for analytic=True are available (see the EccZmaxRperiRap method of actionAngle modules): type= ('staeckel') type of actionAngle module to use 1) 'adiabatic': assuming motion splits into R and z 2) 'staeckel': assuming motion splits into u and v of prolate spheroidal coordinate system, exact for Staeckel potentials (incl. all spherical potentials) 3) 'spherical': for spherical potentials, exact +actionAngle module setup kwargs for the corresponding actionAngle modules (actionAngleAdiabatic, actionAngleStaeckel, and actionAngleSpherical) ro= (Object-wide default) physical scale for distances to use to convert (can be Quantity) use_physical= use to override Object-wide default for using a physical scale for output OUTPUT: R_peri HISTORY: 2010-09-20 - Written - Bovy (NYU) 2017-12-25 - Added Staeckel approximation and made that the default - Bovy (UofT) """ _check_consistent_units(self,pot) return self._orb.rperi(analytic=analytic,pot=pot,**kwargs) def zmax(self,analytic=False,pot=None,**kwargs): """ NAME: zmax PURPOSE: calculate the maximum vertical height, either numerically from the numerical orbit integration or using analytical means INPUT: analytic(= False) compute this analytically pot - potential to use for analytical calculation For 3D orbits different approximations for analytic=True are available (see the EccZmaxRperiRap method of actionAngle modules): type= ('staeckel') type of actionAngle module to use 1) 'adiabatic': assuming motion splits into R and z 2) 'staeckel': assuming motion splits into u and v of prolate spheroidal coordinate system, exact for Staeckel potentials (incl. all spherical potentials) 3) 'spherical': for spherical potentials, exact +actionAngle module setup kwargs for the corresponding actionAngle modules (actionAngleAdiabatic, actionAngleStaeckel, and actionAngleSpherical) ro= (Object-wide default) physical scale for distances to use to convert (can be Quantity) use_physical= use to override Object-wide default for using a physical scale for output OUTPUT: Z_max HISTORY: 2010-09-20 - Written - Bovy (NYU) 2017-12-25 - Added Staeckel approximation and made that the default - Bovy (UofT) """ _check_consistent_units(self,pot) return self._orb.zmax(analytic=analytic,pot=pot,**kwargs) def resetaA(self,pot=None,type=None): """ NAME: resetaA PURPOSE: re-set up an actionAngle module for this Orbit INPUT: (none) OUTPUT: True if reset happened, False otherwise HISTORY: 2014-01-06 - Written - Bovy (IAS) """ try: delattr(self._orb,'_aA') except AttributeError: return False else: return True @physical_conversion('action') def jr(self,pot=None,**kwargs): """ NAME: jr PURPOSE: calculate the radial action INPUT: pot - potential type= ('staeckel') type of actionAngle module to use 1) 'adiabatic' 2) 'staeckel' 3) 'isochroneApprox' 4) 'spherical' +actionAngle module setup kwargs ro= (Object-wide default) physical scale for distances to use to convert (can be Quantity) vo= (Object-wide default) physical scale for velocities to use to convert (can be Quantity) use_physical= use to override Object-wide default for using a physical scale for output OUTPUT: jr HISTORY: 2010-11-30 - Written - Bovy (NYU) 2013-11-27 - Re-written using new actionAngle modules - Bovy (IAS) """ _check_consistent_units(self,pot) self._orb._setupaA(pot=pot,**kwargs) if self._orb._aAType.lower() == 'isochroneapprox': return float(self._orb._aA(self(),use_physical=False)[0]) else: return float(self._orb._aA(self,use_physical=False)[0]) @physical_conversion('action') def jp(self,pot=None,**kwargs): """ NAME: jp PURPOSE: calculate the azimuthal action INPUT: pot - potential type= ('staeckel') type of actionAngle module to use 1) 'adiabatic' 2) 'staeckel' 3) 'isochroneApprox' 4) 'spherical' +actionAngle module setup kwargs ro= (Object-wide default) physical scale for distances to use to convert (can be Quantity) vo= (Object-wide default) physical scale for velocities to use to convert (can be Quantity) use_physical= use to override Object-wide default for using a physical scale for output OUTPUT: jp HISTORY: 2010-11-30 - Written - Bovy (NYU) 2013-11-27 - Re-written using new actionAngle modules - Bovy (IAS) """ _check_consistent_units(self,pot) self._orb._setupaA(pot=pot,**kwargs) if self._orb._aAType.lower() == 'isochroneapprox': return float(self._orb._aA(self(),use_physical=False)[1]) else: return float(self._orb._aA(self,use_physical=False)[1]) @physical_conversion('action') def jz(self,pot=None,**kwargs): """ NAME: jz PURPOSE: calculate the vertical action INPUT: pot - potential type= ('staeckel') type of actionAngle module to use 1) 'adiabatic' 2) 'staeckel' 3) 'isochroneApprox' 4) 'spherical' +actionAngle module setup kwargs ro= (Object-wide default) physical scale for distances to use to convert (can be Quantity) vo= (Object-wide default) physical scale for velocities to use to convert (can be Quantity) use_physical= use to override Object-wide default for using a physical scale for output OUTPUT: jz HISTORY: 2012-06-01 - Written - Bovy (IAS) 2013-11-27 - Re-written using new actionAngle modules - Bovy (IAS) """ _check_consistent_units(self,pot) self._orb._setupaA(pot=pot,**kwargs) if self._orb._aAType.lower() == 'isochroneapprox': return float(self._orb._aA(self(),use_physical=False)[2]) else: return float(self._orb._aA(self,use_physical=False)[2]) @physical_conversion('angle') def wr(self,pot=None,**kwargs): """ NAME: wr PURPOSE: calculate the radial angle INPUT: pot - potential type= ('staeckel') type of actionAngle module to use 1) 'adiabatic' 2) 'staeckel' 3) 'isochroneApprox' 4) 'spherical' +actionAngle module setup kwargs OUTPUT: wr HISTORY: 2010-11-30 - Written - Bovy (NYU) 2013-11-27 - Re-written using new actionAngle modules - Bovy (IAS) """ _check_consistent_units(self,pot) self._orb._setupaA(pot=pot,**kwargs) if self._orb._aAType.lower() == 'isochroneapprox': return float(self._orb._aA.actionsFreqsAngles(self(), use_physical=False)[6][0]) else: return float(self._orb._aA.actionsFreqsAngles(self, use_physical=False)[6][0]) @physical_conversion('angle') def wp(self,pot=None,**kwargs): """ NAME: wp PURPOSE: calculate the azimuthal angle INPUT: pot - potential type= ('staeckel') type of actionAngle module to use 1) 'adiabatic' 2) 'staeckel' 3) 'isochroneApprox' 4) 'spherical' +actionAngle module setup kwargs OUTPUT: wp HISTORY: 2010-11-30 - Written - Bovy (NYU) 2013-11-27 - Re-written using new actionAngle modules - Bovy (IAS) """ _check_consistent_units(self,pot) self._orb._setupaA(pot=pot,**kwargs) if self._orb._aAType.lower() == 'isochroneapprox': return float(self._orb._aA.actionsFreqsAngles(self(), use_physical=False)[7][0]) else: return float(self._orb._aA.actionsFreqsAngles(self, use_physical=False)[7][0]) @physical_conversion('angle') def wz(self,pot=None,**kwargs): """ NAME: wz PURPOSE: calculate the vertical angle INPUT: pot - potential type= ('staeckel') type of actionAngle module to use 1) 'adiabatic' 2) 'staeckel' 3) 'isochroneApprox' 4) 'spherical' +actionAngle module setup kwargs OUTPUT: wz HISTORY: 2012-06-01 - Written - Bovy (IAS) 2013-11-27 - Re-written using new actionAngle modules - Bovy (IAS) """ _check_consistent_units(self,pot) self._orb._setupaA(pot=pot,**kwargs) if self._orb._aAType.lower() == 'isochroneapprox': return float(self._orb._aA.actionsFreqsAngles(self(), use_physical=False)[8][0]) else: return float(self._orb._aA.actionsFreqsAngles(self, use_physical=False)[8][0]) @physical_conversion('time') def Tr(self,pot=None,**kwargs): """ NAME: Tr PURPOSE: calculate the radial period INPUT: pot - potential type= ('staeckel') type of actionAngle module to use 1) 'adiabatic' 2) 'staeckel' 3) 'isochroneApprox' 4) 'spherical' +actionAngle module setup kwargs ro= (Object-wide default) physical scale for distances to use to convert (can be Quantity) vo= (Object-wide default) physical scale for velocities to use to convert (can be Quantity) use_physical= use to override Object-wide default for using a physical scale for output OUTPUT: Tr HISTORY: 2010-11-30 - Written - Bovy (NYU) 2013-11-27 - Re-written using new actionAngle modules - Bovy (IAS) """ _check_consistent_units(self,pot) self._orb._setupaA(pot=pot,**kwargs) if self._orb._aAType.lower() == 'isochroneapprox': return float(2.*nu.pi/self._orb._aA.actionsFreqs(self(), use_physical=False)[3][0]) else: return float(2.*nu.pi/self._orb._aA.actionsFreqs(self, use_physical=False)[3][0]) @physical_conversion('time') def Tp(self,pot=None,**kwargs): """ NAME: Tp PURPOSE: calculate the azimuthal period INPUT: pot - potential type= ('staeckel') type of actionAngle module to use 1) 'adiabatic' 2) 'staeckel' 3) 'isochroneApprox' 4) 'spherical' +actionAngle module setup kwargs ro= (Object-wide default) physical scale for distances to use to convert (can be Quantity) vo= (Object-wide default) physical scale for velocities to use to convert (can be Quantity) use_physical= use to override Object-wide default for using a physical scale for output OUTPUT: Tp HISTORY: 2010-11-30 - Written - Bovy (NYU) 2013-11-27 - Re-written using new actionAngle modules - Bovy (IAS) """ _check_consistent_units(self,pot) self._orb._setupaA(pot=pot,**kwargs) if self._orb._aAType.lower() == 'isochroneapprox': return float(2.*nu.pi/self._orb._aA.actionsFreqs(self(), use_physical=False)[4][0]) else: return float(2.*nu.pi/self._orb._aA.actionsFreqs(self, use_physical=False)[4][0]) def TrTp(self,pot=None,**kwargs): """ NAME: TrTp PURPOSE: the 'ratio' between the radial and azimuthal period Tr/Tphi*pi INPUT: pot - potential type= ('staeckel') type of actionAngle module to use 1) 'adiabatic' 2) 'staeckel' 3) 'isochroneApprox' 4) 'spherical' +actionAngle module setup kwargs OUTPUT: Tr/Tp*pi HISTORY: 2010-11-30 - Written - Bovy (NYU) 2013-11-27 - Re-written using new actionAngle modules - Bovy (IAS) """ _check_consistent_units(self,pot) self._orb._setupaA(pot=pot,**kwargs) if self._orb._aAType.lower() == 'isochroneapprox': return float(self._orb._aA.actionsFreqs(self())[4][0]/self._orb._aA.actionsFreqs(self())[3][0]*nu.pi) else: return float(self._orb._aA.actionsFreqs(self)[4][0]/self._orb._aA.actionsFreqs(self)[3][0]*nu.pi) @physical_conversion('time') def Tz(self,pot=None,**kwargs): """ NAME: Tz PURPOSE: calculate the vertical period INPUT: pot - potential type= ('staeckel') type of actionAngle module to use 1) 'adiabatic' 2) 'staeckel' 3) 'isochroneApprox' 4) 'spherical' +actionAngle module setup kwargs ro= (Object-wide default) physical scale for distances to use to convert (can be Quantity) vo= (Object-wide default) physical scale for velocities to use to convert (can be Quantity) use_physical= use to override Object-wide default for using a physical scale for output OUTPUT: Tz HISTORY: 2012-06-01 - Written - Bovy (IAS) 2013-11-27 - Re-written using new actionAngle modules - Bovy (IAS) """ _check_consistent_units(self,pot) self._orb._setupaA(pot=pot,**kwargs) if self._orb._aAType.lower() == 'isochroneapprox': return float(2.*nu.pi/self._orb._aA.actionsFreqs(self(), use_physical=False)[5][0]) else: return float(2.*nu.pi/self._orb._aA.actionsFreqs(self, use_physical=False)[5][0]) @physical_conversion('frequency') def Or(self,pot=None,**kwargs): """ NAME: Or PURPOSE: calculate the radial frequency INPUT: pot - potential type= ('staeckel') type of actionAngle module to use 1) 'adiabatic' 2) 'staeckel' 3) 'isochroneApprox' 4) 'spherical' +actionAngle module setup kwargs ro= (Object-wide default) physical scale for distances to use to convert (can be Quantity) vo= (Object-wide default) physical scale for velocities to use to convert (can be Quantity) use_physical= use to override Object-wide default for using a physical scale for output OUTPUT: Or HISTORY: 2013-11-27 - Written - Bovy (IAS) """ _check_consistent_units(self,pot) self._orb._setupaA(pot=pot,**kwargs) if self._orb._aAType.lower() == 'isochroneapprox': return float(self._orb._aA.actionsFreqs(self(),use_physical=False)[3][0]) else: return float(self._orb._aA.actionsFreqs(self,use_physical=False)[3][0]) @physical_conversion('frequency') def Op(self,pot=None,**kwargs): """ NAME: Op PURPOSE: calculate the azimuthal frequency INPUT: pot - potential type= ('staeckel') type of actionAngle module to use 1) 'adiabatic' 2) 'staeckel' 3) 'isochroneApprox' 4) 'spherical' +actionAngle module setup kwargs ro= (Object-wide default) physical scale for distances to use to convert (can be Quantity) vo= (Object-wide default) physical scale for velocities to use to convert (can be Quantity) use_physical= use to override Object-wide default for using a physical scale for output OUTPUT: Op HISTORY: 2013-11-27 - Written - Bovy (IAS) """ _check_consistent_units(self,pot) self._orb._setupaA(pot=pot,**kwargs) if self._orb._aAType.lower() == 'isochroneapprox': return float(self._orb._aA.actionsFreqs(self(),use_physical=False)[4][0]) else: return float(self._orb._aA.actionsFreqs(self,use_physical=False)[4][0]) @physical_conversion('frequency') def Oz(self,pot=None,**kwargs): """ NAME: Oz PURPOSE: calculate the vertical frequency INPUT: pot - potential type= ('staeckel') type of actionAngle module to use 1) 'adiabatic' 2) 'staeckel' 3) 'isochroneApprox' 4) 'spherical' +actionAngle module setup kwargs ro= (Object-wide default) physical scale for distances to use to convert (can be Quantity) vo= (Object-wide default) physical scale for velocities to use to convert (can be Quantity) use_physical= use to override Object-wide default for using a physical scale for output OUTPUT: Oz HISTORY: 2013-11-27 - Written - Bovy (IAS) """ _check_consistent_units(self,pot) self._orb._setupaA(pot=pot,**kwargs) if self._orb._aAType.lower() == 'isochroneapprox': return float(self._orb._aA.actionsFreqs(self(),use_physical=False)[5][0]) else: return float(self._orb._aA.actionsFreqs(self,use_physical=False)[5][0]) def time(self,*args,**kwargs): """ NAME: t PURPOSE: return the times at which the orbit is sampled INPUT: t - (default: integration times) time at which to get the time (for consistency reasons); default is to return the list of times at which the orbit is sampled (can be Quantity) ro= (Object-wide default) physical scale for distances to use to convert (can be Quantity) vo= (Object-wide default) physical scale for velocities to use to convert (can be Quantity) use_physical= use to override Object-wide default for using a physical scale for output OUTPUT: t(t) HISTORY: 2014-06-11 - Written - Bovy (IAS) """ return self._orb.time(*args,**kwargs) def R(self,*args,**kwargs): """ NAME: R PURPOSE: return cylindrical radius at time t INPUT: t - (optional) time at which to get the radius (can be Quantity) ro= (Object-wide default) physical scale for distances to use to convert (can be Quantity) use_physical= use to override Object-wide default for using a physical scale for output OUTPUT: R(t) HISTORY: 2010-09-21 - Written - Bovy (NYU) """ return self._orb.R(*args,**kwargs) def r(self,*args,**kwargs): """ NAME: r PURPOSE: return spherical radius at time t INPUT: t - (optional) time at which to get the radius (can be Quantity) ro= (Object-wide default) physical scale for distances to use to convert (can be Quantity) use_physical= use to override Object-wide default for using a physical scale for output OUTPUT: r(t) HISTORY: 2016-04-19 - Written - Bovy (UofT) """ return self._orb.r(*args,**kwargs) def vR(self,*args,**kwargs): """ NAME: vR PURPOSE: return radial velocity at time t INPUT: t - (optional) time at which to get the radial velocity vo= (Object-wide default) physical scale for velocities to use to convert (can be Quantity) use_physical= use to override Object-wide default for using a physical scale for output OUTPUT: vR(t) HISTORY: 2010-09-21 - Written - Bovy (NYU) """ return self._orb.vR(*args,**kwargs) def vT(self,*args,**kwargs): """ NAME: vT PURPOSE: return tangential velocity at time t INPUT: t - (optional) time at which to get the tangential velocity (can be Quantity) vo= (Object-wide default) physical scale for velocities to use to convert (can be Quantity) use_physical= use to override Object-wide default for using a physical scale for output OUTPUT: vT(t) HISTORY: 2010-09-21 - Written - Bovy (NYU) """ return self._orb.vT(*args,**kwargs) def z(self,*args,**kwargs): """ NAME: z PURPOSE: return vertical height INPUT: t - (optional) time at which to get the vertical height (can be Quantity) ro= (Object-wide default) physical scale for distances to use to convert (can be Quantity) use_physical= use to override Object-wide default for using a physical scale for output OUTPUT: z(t) HISTORY: 2010-09-21 - Written - Bovy (NYU) """ return self._orb.z(*args,**kwargs) def vz(self,*args,**kwargs): """ NAME: vz PURPOSE: return vertical velocity INPUT: t - (optional) time at which to get the vertical velocity (can be Quantity) vo= (Object-wide default) physical scale for velocities to use to convert (can be Quantity) use_physical= use to override Object-wide default for using a physical scale for output OUTPUT: vz(t) HISTORY: 2010-09-21 - Written - Bovy (NYU) """ return self._orb.vz(*args,**kwargs) def phi(self,*args,**kwargs): """ NAME: phi PURPOSE: return azimuth INPUT: t - (optional) time at which to get the azimuth (can be Quantity) OUTPUT: phi(t) HISTORY: 2010-09-21 - Written - Bovy (NYU) """ return self._orb.phi(*args,**kwargs) def vphi(self,*args,**kwargs): """ NAME: vphi PURPOSE: return angular velocity INPUT: t - (optional) time at which to get the angular velocity (can be Quantity) vo= (Object-wide default) physical scale for velocities to use to convert (can be Quantity) use_physical= use to override Object-wide default for using a physical scale for output OUTPUT: vphi(t) HISTORY: 2010-09-21 - Written - Bovy (NYU) """ return self._orb.vphi(*args,**kwargs) def x(self,*args,**kwargs): """ NAME: x PURPOSE: return x INPUT: t - (optional) time at which to get x (can be Quantity) ro= (Object-wide default) physical scale for distances to use to convert (can be Quantity) use_physical= use to override Object-wide default for using a physical scale for output OUTPUT: x(t) HISTORY: 2010-09-21 - Written - Bovy (NYU) """ out= self._orb.x(*args,**kwargs) if len(out) == 1: return out[0] else: return out def y(self,*args,**kwargs): """ NAME: y PURPOSE: return y INPUT: t - (optional) time at which to get y (can be Quantity) ro= (Object-wide default) physical scale for distances to use to convert (can be Quantity) use_physical= use to override Object-wide default for using a physical scale for output OUTPUT: y(t) HISTORY: 2010-09-21 - Written - Bovy (NYU) """ out= self._orb.y(*args,**kwargs) if len(out) == 1: return out[0] else: return out def vx(self,*args,**kwargs): """ NAME: vx PURPOSE: return x velocity at time t INPUT: t - (optional) time at which to get the velocity (can be Quantity) vo= (Object-wide default) physical scale for velocities to use to convert (can be Quantity) use_physical= use to override Object-wide default for using a physical scale for output OUTPUT: vx(t) HISTORY: 2010-11-30 - Written - Bovy (NYU) """ out= self._orb.vx(*args,**kwargs) if len(out) == 1: return out[0] else: return out def vy(self,*args,**kwargs): """ NAME: vy PURPOSE: return y velocity at time t INPUT: t - (optional) time at which to get the velocity (can be Quantity) vo= (Object-wide default) physical scale for velocities to use to convert (can be Quantity) use_physical= use to override Object-wide default for using a physical scale for output OUTPUT: vy(t) HISTORY: 2010-11-30 - Written - Bovy (NYU) """ out= self._orb.vy(*args,**kwargs) if len(out) == 1: return out[0] else: return out def ra(self,*args,**kwargs): """ NAME: ra PURPOSE: return the right ascension INPUT: t - (optional) time at which to get ra (can be Quantity) obs=[X,Y,Z] - (optional) position of observer (in kpc; entries can be Quantity) (default=[8.0,0.,0.]) OR Orbit object that corresponds to the orbit of the observer (default=Object-wide default; can be Quantity) Y is ignored and always assumed to be zero ro= (Object-wide default) physical scale for distances to use to convert (can be Quantity) OUTPUT: ra(t) in deg HISTORY: 2011-02-23 - Written - Bovy (NYU) """ out= self._orb.ra(*args,**kwargs) if len(out) == 1: return out[0] else: return out def dec(self,*args,**kwargs): """ NAME: dec PURPOSE: return the declination INPUT: t - (optional) time at which to get dec (can be Quantity) obs=[X,Y,Z] - (optional) position of observer (in kpc; entries can be Quantity) (default=[8.0,0.,0.]) OR Orbit object that corresponds to the orbit of the observer Y is ignored and always assumed to be zero ro= (Object-wide default) physical scale for distances to use to convert (can be Quantity) OUTPUT: dec(t) in deg HISTORY: 2011-02-23 - Written - Bovy (NYU) """ out= self._orb.dec(*args,**kwargs) if len(out) == 1: return out[0] else: return out def ll(self,*args,**kwargs): """ NAME: ll PURPOSE: return Galactic longitude INPUT: t - (optional) time at which to get ll (can be Quantity) obs=[X,Y,Z] - (optional) position of observer (in kpc; entries can be Quantity) (default=[8.0,0.,0.]) OR Orbit object that corresponds to the orbit of the observer Y is ignored and always assumed to be zero ro= (Object-wide default) physical scale for distances to use to convert (can be Quantity) OUTPUT: l(t) in deg HISTORY: 2011-02-23 - Written - Bovy (NYU) """ out= self._orb.ll(*args,**kwargs) if len(out) == 1: return out[0] else: return out def bb(self,*args,**kwargs): """ NAME: bb PURPOSE: return Galactic latitude INPUT: t - (optional) time at which to get bb (can be Quantity) obs=[X,Y,Z] - (optional) position of observer (in kpc; entries can be Quantity) (default=[8.0,0.,0.]) OR Orbit object that corresponds to the orbit of the observer Y is ignored and always assumed to be zero ro= (Object-wide default) physical scale for distances to use to convert (can be Quantity) OUTPUT: b(t) in deg HISTORY: 2011-02-23 - Written - Bovy (NYU) """ out= self._orb.bb(*args,**kwargs) if len(out) == 1: return out[0] else: return out def dist(self,*args,**kwargs): """ NAME: dist PURPOSE: return distance from the observer INPUT: t - (optional) time at which to get dist (can be Quantity) obs=[X,Y,Z] - (optional) position of observer (in kpc; entries can be Quantity) (default=[8.0,0.,0.]) OR Orbit object that corresponds to the orbit of the observer Y is ignored and always assumed to be zero ro= (Object-wide default) physical scale for distances to use to convert (can be Quantity) OUTPUT: dist(t) in kpc HISTORY: 2011-02-23 - Written - Bovy (NYU) """ out= self._orb.dist(*args,**kwargs) if len(out) == 1: return out[0] else: return out def pmra(self,*args,**kwargs): """ NAME: pmra PURPOSE: return proper motion in right ascension (in mas/yr) INPUT: t - (optional) time at which to get pmra (can be Quantity) obs=[X,Y,Z,vx,vy,vz] - (optional) position and velocity of observer in the Galactocentric frame (in kpc and km/s) (default=[8.0,0.,0.,0.,220.,0.]; entries can be Quantities) OR Orbit object that corresponds to the orbit of the observer Y is ignored and always assumed to be zero ro= (Object-wide default) physical scale for distances to use to convert (can be Quantity) vo= (Object-wide default) physical scale for velocities to use to convert (can be Quantity) OUTPUT: pm_ra(t) in mas/yr HISTORY: 2011-02-24 - Written - Bovy (NYU) """ out= self._orb.pmra(*args,**kwargs) if len(out) == 1: return out[0] else: return out def pmdec(self,*args,**kwargs): """ NAME: pmdec PURPOSE: return proper motion in declination (in mas/yr) INPUT: t - (optional) time at which to get pmdec (can be Quantity) obs=[X,Y,Z,vx,vy,vz] - (optional) position and velocity of observer in the Galactocentric frame (in kpc and km/s) (default=[8.0,0.,0.,0.,220.,0.]; entries can be Quantities) OR Orbit object that corresponds to the orbit of the observer Y is ignored and always assumed to be zero ro= (Object-wide default) physical scale for distances to use to convert (can be Quantity) vo= (Object-wide default) physical scale for velocities to use to convert (can be Quantity) OUTPUT: pm_dec(t) in mas/yr HISTORY: 2011-02-24 - Written - Bovy (NYU) """ out= self._orb.pmdec(*args,**kwargs) if len(out) == 1: return out[0] else: return out def pmll(self,*args,**kwargs): """ NAME: pmll PURPOSE: return proper motion in Galactic longitude (in mas/yr) INPUT: t - (optional) time at which to get pmll (can be Quantity) v obs=[X,Y,Z,vx,vy,vz] - (optional) position and velocity of observer in the Galactocentric frame (in kpc and km/s) (default=[8.0,0.,0.,0.,220.,0.]; entries can be Quantities) OR Orbit object that corresponds to the orbit of the observer Y is ignored and always assumed to be zero ro= (Object-wide default) physical scale for distances to use to convert (can be Quantity) vo= (Object-wide default) physical scale for velocities to use to convert (can be Quantity) OUTPUT: pm_l(t) in mas/yr HISTORY: 2011-02-24 - Written - Bovy (NYU) """ out= self._orb.pmll(*args,**kwargs) if len(out) == 1: return out[0] else: return out def pmbb(self,*args,**kwargs): """ NAME: pmbb PURPOSE: return proper motion in Galactic latitude (in mas/yr) INPUT: t - (optional) time at which to get pmbb (can be Quantity) obs=[X,Y,Z,vx,vy,vz] - (optional) position and velocity of observer in the Galactocentric frame (in kpc and km/s) (default=[8.0,0.,0.,0.,220.,0.]; entries can be Quantity) OR Orbit object that corresponds to the orbit of the observer Y is ignored and always assumed to be zero ro= (Object-wide default) physical scale for distances to use to convert (can be Quantity) vo= (Object-wide default) physical scale for velocities to use to convert (can be Quantity) OUTPUT: pm_b(t) in mas/yr HISTORY: 2011-02-24 - Written - Bovy (NYU) """ out= self._orb.pmbb(*args,**kwargs) if len(out) == 1: return out[0] else: return out def vlos(self,*args,**kwargs): """ NAME: vlos PURPOSE: return the line-of-sight velocity (in km/s) INPUT: t - (optional) time at which to get vlos (can be Quantity) obs=[X,Y,Z,vx,vy,vz] - (optional) position and velocity of observer in the Galactocentric frame (in kpc and km/s) (default=[8.0,0.,0.,0.,220.,0.]; entries can be Quantity) OR Orbit object that corresponds to the orbit of the observer Y is ignored and always assumed to be zero ro= (Object-wide default) physical scale for distances to use to convert (can be Quantity) vo= (Object-wide default) physical scale for velocities to use to convert (can be Quantity) OUTPUT: vlos(t) in km/s HISTORY: 2011-02-24 - Written - Bovy (NYU) """ out= self._orb.vlos(*args,**kwargs) if len(out) == 1: return out[0] else: return out def vra(self,*args,**kwargs): """ NAME: vra PURPOSE: return velocity in right ascension (km/s) INPUT: t - (optional) time at which to get vra (can be Quantity) obs=[X,Y,Z,vx,vy,vz] - (optional) position and velocity of observer in the Galactocentric frame (in kpc and km/s) (default=[8.0,0.,0.,0.,220.,0.]; entries can be Quantity) OR Orbit object that corresponds to the orbit of the observer Y is ignored and always assumed to be zero ro= (Object-wide default) physical scale for distances to use to convert (can be Quantity) vo= (Object-wide default) physical scale for velocities to use to convert (can be Quantity) OUTPUT: v_ra(t) in km/s HISTORY: 2011-03-27 - Written - Bovy (NYU) """ from galpy.orbit_src.OrbitTop import _check_roSet, _check_voSet _check_roSet(self,kwargs,'vra') _check_voSet(self,kwargs,'vra') dist= self._orb.dist(*args,**kwargs) if _APY_UNITS and isinstance(dist,units.Quantity): out= units.Quantity(dist.to(units.kpc).value*_K* self._orb.pmra(*args,**kwargs)\ .to(units.mas/units.yr).value, unit=units.km/units.s) else: out= dist*_K*self._orb.pmra(*args,**kwargs) if len(out) == 1: return out[0] else: return out def vdec(self,*args,**kwargs): """ NAME: vdec PURPOSE: return velocity in declination (km/s) INPUT: t - (optional) time at which to get vdec (can be Quantity) obs=[X,Y,Z,vx,vy,vz] - (optional) position and velocity of observer in the Galactocentric frame (in kpc and km/s) (default=[8.0,0.,0.,0.,220.,0.]; entries can be Quantity) OR Orbit object that corresponds to the orbit of the observer Y is ignored and always assumed to be zero ro= (Object-wide default) physical scale for distances to use to convert (can be Quantity) vo= (Object-wide default) physical scale for velocities to use to convert (can be Quantity) OUTPUT: v_dec(t) in km/s HISTORY: 2011-03-27 - Written - Bovy (NYU) """ from galpy.orbit_src.OrbitTop import _check_roSet, _check_voSet _check_roSet(self,kwargs,'vdec') _check_voSet(self,kwargs,'vdec') dist= self._orb.dist(*args,**kwargs) if _APY_UNITS and isinstance(dist,units.Quantity): out= units.Quantity(dist.to(units.kpc).value*_K* self._orb.pmdec(*args,**kwargs)\ .to(units.mas/units.yr).value, unit=units.km/units.s) else: out= dist*_K*self._orb.pmdec(*args,**kwargs) if len(out) == 1: return out[0] else: return out def vll(self,*args,**kwargs): """ NAME: vll PURPOSE: return the velocity in Galactic longitude (km/s) INPUT: t - (optional) time at which to get vll (can be Quantity) obs=[X,Y,Z,vx,vy,vz] - (optional) position and velocity of observer in the Galactocentric frame (in kpc and km/s) (default=[8.0,0.,0.,0.,220.,0.]; entries can be Quantity) OR Orbit object that corresponds to the orbit of the observer Y is ignored and always assumed to be zero ro= (Object-wide default) physical scale for distances to use to convert (can be Quantity) vo= (Object-wide default) physical scale for velocities to use to convert (can be Quantity) OUTPUT: v_l(t) in km/s HISTORY: 2011-03-27 - Written - Bovy (NYU) """ from galpy.orbit_src.OrbitTop import _check_roSet, _check_voSet _check_roSet(self,kwargs,'vll') _check_voSet(self,kwargs,'vll') dist= self._orb.dist(*args,**kwargs) if _APY_UNITS and isinstance(dist,units.Quantity): out= units.Quantity(dist.to(units.kpc).value*_K* self._orb.pmll(*args,**kwargs)\ .to(units.mas/units.yr).value, unit=units.km/units.s) else: out= dist*_K*self._orb.pmll(*args,**kwargs) if len(out) == 1: return out[0] else: return out def vbb(self,*args,**kwargs): """ NAME: vbb PURPOSE: return velocity in Galactic latitude (km/s) INPUT: t - (optional) time at which to get vbb (can be Quantity) obs=[X,Y,Z,vx,vy,vz] - (optional) position and velocity of observer in the Galactocentric frame (in kpc and km/s) (default=[8.0,0.,0.,0.,220.,0.]; entries can be Quantity) OR Orbit object that corresponds to the orbit of the observer Y is ignored and always assumed to be zero ro= (Object-wide default) physical scale for distances to use to convert (can be Quantity) vo= (Object-wide default) physical scale for velocities to use to convert (can be Quantity) OUTPUT: v_b(t) in km/s HISTORY: 2011-02-24 - Written - Bovy (NYU) """ from galpy.orbit_src.OrbitTop import _check_roSet, _check_voSet _check_roSet(self,kwargs,'vbb') _check_voSet(self,kwargs,'vbb') dist= self._orb.dist(*args,**kwargs) if _APY_UNITS and isinstance(dist,units.Quantity): out= units.Quantity(dist.to(units.kpc).value*_K* self._orb.pmbb(*args,**kwargs)\ .to(units.mas/units.yr).value, unit=units.km/units.s) else: out= dist*_K*self._orb.pmbb(*args,**kwargs) if len(out) == 1: return out[0] else: return out def helioX(self,*args,**kwargs): """ NAME: helioX PURPOSE: return Heliocentric Galactic rectangular x-coordinate (aka "X") INPUT: t - (optional) time at which to get X (can be Quantity) obs=[X,Y,Z] - (optional) position of observer in the Galactocentric frame (in kpc and km/s) (default=[8.0,0.,0.]; entries can be Quantity) OR Orbit object that corresponds to the orbit of the observer Y is ignored and always assumed to be zero ro= (Object-wide default) physical scale for distances to use to convert (can be Quantity) OUTPUT: helioX(t) in kpc HISTORY: 2011-02-24 - Written - Bovy (NYU) """ out= self._orb.helioX(*args,**kwargs) if len(out) == 1: return out[0] else: return out def helioY(self,*args,**kwargs): """ NAME: helioY PURPOSE: return Heliocentric Galactic rectangular y-coordinate (aka "Y") INPUT: t - (optional) time at which to get Y (can be Quantity) obs=[X,Y,Z] - (optional) position and of observer in the Galactocentric frame (in kpc and km/s) (default=[8.0,0.,0.]; entries can be Quantity)) OR Orbit object that corresponds to the orbit of the observer Y is ignored and always assumed to be zero ro= (Object-wide default) physical scale for distances to use to convert (can be Quantity) OUTPUT: helioY(t) in kpc HISTORY: 2011-02-24 - Written - Bovy (NYU) """ out= self._orb.helioY(*args,**kwargs) if len(out) == 1: return out[0] else: return out def helioZ(self,*args,**kwargs): """ NAME: helioZ PURPOSE: return Heliocentric Galactic rectangular z-coordinate (aka "Z") INPUT: t - (optional) time at which to get Z (can be Quantity) obs=[X,Y,Z] - (optional) position of observer in the Galactocentric frame (in kpc and km/s) (default=[8.0,0.,0.]; entries can be Quantity) OR Orbit object that corresponds to the orbit of the observer Y is ignored and always assumed to be zero ro= (Object-wide default) physical scale for distances to use to convert (can be Quantity) OUTPUT: helioZ(t) in kpc HISTORY: 2011-02-24 - Written - Bovy (NYU) """ out= self._orb.helioZ(*args,**kwargs) if len(out) == 1: return out[0] else: return out def U(self,*args,**kwargs): """ NAME: U PURPOSE: return Heliocentric Galactic rectangular x-velocity (aka "U") INPUT: t - (optional) time at which to get U (can be Quantity) obs=[X,Y,Z,vx,vy,vz] - (optional) position and velocity of observer in the Galactocentric frame (in kpc and km/s) (default=[8.0,0.,0.,0.,220.,0.]; entries can be Quantity) OR Orbit object that corresponds to the orbit of the observer Y is ignored and always assumed to be zero ro= (Object-wide default) physical scale for distances to use to convert (can be Quantity) vo= (Object-wide default) physical scale for velocities to use to convert (can be Quantity) OUTPUT: U(t) in km/s HISTORY: 2011-02-24 - Written - Bovy (NYU) """ out= self._orb.U(*args,**kwargs) if len(out) == 1: return out[0] else: return out def V(self,*args,**kwargs): """ NAME: V PURPOSE: return Heliocentric Galactic rectangular y-velocity (aka "V") INPUT: t - (optional) time at which to get V (can be Quantity) obs=[X,Y,Z,vx,vy,vz] - (optional) position and velocity of observer in the Galactocentric frame (in kpc and km/s) (default=[8.0,0.,0.,0.,220.,0.]; entries can be Quantity) OR Orbit object that corresponds to the orbit of the observer Y is ignored and always assumed to be zero ro= (Object-wide default) physical scale for distances to use to convert (can be Quantity) vo= (Object-wide default) physical scale for velocities to use to convert (can be Quantity) OUTPUT: V(t) in km/s HISTORY: 2011-02-24 - Written - Bovy (NYU) """ out= self._orb.V(*args,**kwargs) if len(out) == 1: return out[0] else: return out def W(self,*args,**kwargs): """ NAME: W PURPOSE: return Heliocentric Galactic rectangular z-velocity (aka "W") INPUT: t - (optional) time at which to get W (can be Quantity) obs=[X,Y,Z,vx,vy,vz] - (optional) position and velocity of observer in the Galactocentric frame (in kpc and km/s) (default=[8.0,0.,0.,0.,220.,0.]; entries can be Quantity) OR Orbit object that corresponds to the orbit of the observer Y is ignored and always assumed to be zero ro= (Object-wide default) physical scale for distances to use to convert (can be Quantity) vo= (Object-wide default) physical scale for velocities to use to convert (can be Quantity) OUTPUT: W(t) in km/s HISTORY: 2011-02-24 - Written - Bovy (NYU) """ out= self._orb.W(*args,**kwargs) if len(out) == 1: return out[0] else: return out def SkyCoord(self,*args,**kwargs): """ NAME: SkyCoord PURPOSE: return the position as an astropy SkyCoord INPUT: t - (optional) time at which to get the position (can be Quantity) obs=[X,Y,Z] - (optional) position of observer (in kpc; entries can be Quantity) (default=Object-wide default) OR Orbit object that corresponds to the orbit of the observer Y is ignored and always assumed to be zero ro= (Object-wide default) physical scale for distances to use to convert (can be Quantity) OUTPUT: SkyCoord(t) HISTORY: 2015-06-02 - Written - Bovy (IAS) """ return self._orb.SkyCoord(*args,**kwargs) def __call__(self,*args,**kwargs): """ NAME: __call__ PURPOSE: return the orbit at time t INPUT: t - desired time (can be Quantity) rect - if true, return rectangular coordinates OUTPUT: an Orbit instance with initial condition set to the phase-space at time t or list of Orbit instances if multiple times are given HISTORY: 2010-07-10 - Written - Bovy (NYU) """ orbSetupKwargs= {'ro':None, 'vo':None, 'zo':self._orb._zo, 'solarmotion':self._orb._solarmotion} if self._orb._roSet: orbSetupKwargs['ro']= self._orb._ro if self._orb._voSet: orbSetupKwargs['vo']= self._orb._vo thiso= self._orb(*args,**kwargs) if len(thiso.shape) == 1: return Orbit(vxvv=thiso,**orbSetupKwargs) else: return [Orbit(vxvv=thiso[:,ii], **orbSetupKwargs) for ii in range(thiso.shape[1])] def plot(self,*args,**kwargs): """ NAME: plot PURPOSE: plot a previously calculated orbit (with reasonable defaults) INPUT: d1= first dimension to plot ('x', 'y', 'R', 'vR', 'vT', 'z', 'vz', ...); can also be a user-defined function of time (e.g., lambda t: o.R(t) for R) d2= second dimension to plot; can also be a user-defined function of time (e.g., lambda t: o.R(t) for R) ro= (Object-wide default) physical scale for distances to use to convert (can be Quantity) vo= (Object-wide default) physical scale for velocities to use to convert (can be Quantity) use_physical= use to override Object-wide default for using a physical scale for output matplotlib.plot inputs+bovy_plot.plot inputs OUTPUT: sends plot to output device HISTORY: 2010-07-10 - Written - Bovy (NYU) """ return self._orb.plot(*args,**kwargs) def plot3d(self,*args,**kwargs): """ NAME: plot3d PURPOSE: plot 3D aspects of an Orbit INPUT: d1= first dimension to plot ('x', 'y', 'R', 'vR', 'vT', 'z', 'vz', ...); can also be a user-defined function of time (e.g., lambda t: o.R(t) for R) d2= second dimension to plot d3= third dimension to plot ro= (Object-wide default) physical scale for distances to use to convert (can be Quantity) vo= (Object-wide default) physical scale for velocities to use to convert (can be Quantity) use_physical= use to override Object-wide default for using a physical scale for output bovy_plot3d args and kwargs OUTPUT: plot HISTORY: 2010-07-26 - Written - Bovy (NYU) 2010-09-22 - Adapted to more general framework - Bovy (NYU) 2010-01-08 - Adapted to 3D - Bovy (NYU) """ return self._orb.plot3d(*args,**kwargs) def plotE(self,*args,**kwargs): """ NAME: plotE PURPOSE: plot E(.) along the orbit INPUT: pot= Potential instance or list of instances in which the orbit was integrated d1= plot Ez vs d1: e.g., 't', 'z', 'R', 'vR', 'vT', 'vz' normed= if set, plot E(t)/E(0) rather than E(t) ro= (Object-wide default) physical scale for distances to use to convert (can be Quantity) vo= (Object-wide default) physical scale for velocities to use to convert (can be Quantity) use_physical= use to override Object-wide default for using a physical scale for output +bovy_plot.bovy_plot inputs OUTPUT: figure to output device HISTORY: 2010-07-10 - Written - Bovy (NYU) 2014-06-16 - Changed to actually plot E rather than E/E0 - Bovy (IAS) """ return self._orb.plotE(*args,**kwargs) def plotEz(self,*args,**kwargs): """ NAME: plotEz PURPOSE: plot E_z(.) along the orbit INPUT: pot= Potential instance or list of instances in which the orbit was integrated d1= plot Ez vs d1: e.g., 't', 'z', 'R' normed= if set, plot E(t)/E(0) rather than E(t) ro= (Object-wide default) physical scale for distances to use to convert (can be Quantity) vo= (Object-wide default) physical scale for velocities to use to convert (can be Quantity) use_physical= use to override Object-wide default for using a physical scale for output +bovy_plot.bovy_plot inputs OUTPUT: figure to output device HISTORY: 2010-07-10 - Written - Bovy (NYU) """ return self._orb.plotEz(*args,**kwargs) def plotER(self,*args,**kwargs): """ NAME: plotER PURPOSE: plot E_R(.) along the orbit INPUT: pot= Potential instance or list of instances in which the orbit was integrated d1= plot ER vs d1: e.g., 't', 'z', 'R' normed= if set, plot E(t)/E(0) rather than E(t) ro= (Object-wide default) physical scale for distances to use to convert (can be Quantity) vo= (Object-wide default) physical scale for velocities to use to convert (can be Quantity) use_physical= use to override Object-wide default for using a physical scale for output +bovy_plot.bovy_plot inputs OUTPUT: figure to output device HISTORY: 2010-07-10 - Written - Bovy (NYU) """ return self._orb.plotER(*args,**kwargs) def plotEzJz(self,*args,**kwargs): """ NAME: plotEzJzt PURPOSE: plot E_z(t)/sqrt(dens(R)) / (E_z(0)/sqrt(dens(R(0)))) along the orbit (an approximation to the vertical action) INPUT: pot - Potential instance or list of instances in which the orbit was integrated d1= plot Ez vs d1: e.g., 't', 'z', 'R' +bovy_plot.bovy_plot inputs OUTPUT: figure to output device HISTORY: 2010-08-08 - Written - Bovy (NYU) """ return self._orb.plotEzJz(*args,**kwargs) def plotJacobi(self,*args,**kwargs): """ NAME: plotJacobi PURPOSE: plot the Jacobi integral along the orbit INPUT: OmegaP= pattern speed pot= - Potential instance or list of instances in which the orbit was integrated d1= - plot Ez vs d1: e.g., 't', 'z', 'R', 'vR', 'vT', 'vz' normed= if set, plot E(t)/E(0) rather than E(t) ro= (Object-wide default) physical scale for distances to use to convert (can be Quantity) vo= (Object-wide default) physical scale for velocities to use to convert (can be Quantity) use_physical= use to override Object-wide default for using a physical scale for output +bovy_plot.bovy_plot inputs OUTPUT: figure to output device HISTORY: 2011-10-10 - Written - Bovy (IAS) """ return self._orb.plotJacobi(*args,**kwargs) def plotR(self,*args,**kwargs): """ NAME: plotR PURPOSE: plot R(.) along the orbit INPUT: d1= plot vs d1: e.g., 't', 'z', 'R' ro= (Object-wide default) physical scale for distances to use to convert (can be Quantity) use_physical= use to override Object-wide default for using a physical scale for output bovy_plot.bovy_plot inputs OUTPUT: figure to output device HISTORY: 2010-07-10 - Written - Bovy (NYU) """ return self._orb.plotR(*args,**kwargs) def plotz(self,*args,**kwargs): """ NAME: plotz PURPOSE: plot z(.) along the orbit INPUT: d1= plot vs d1: e.g., 't', 'z', 'R' ro= (Object-wide default) physical scale for distances to use to convert (can be Quantity) use_physical= use to override Object-wide default for using a physical scale for output bovy_plot.bovy_plot inputs OUTPUT: figure to output device HISTORY: 2010-07-10 - Written - Bovy (NYU) """ return self._orb.plotz(*args,**kwargs) def plotvR(self,*args,**kwargs): """ NAME: plotvR PURPOSE: plot vR(.) along the orbit INPUT: d1= plot vs d1: e.g., 't', 'z', 'R' vo= (Object-wide default) physical scale for velocities to use to convert (can be Quantity) bovy_plot.bovy_plot inputs OUTPUT: figure to output device HISTORY: 2010-07-10 - Written - Bovy (NYU) """ return self._orb.plotvR(*args,**kwargs) def plotvT(self,*args,**kwargs): """ NAME: plotvT PURPOSE: plot vT(.) along the orbit INPUT: d1= plot vs d1: e.g., 't', 'z', 'R' vo= (Object-wide default) physical scale for velocities to use to convert (can be Quantity) bovy_plot.bovy_plot inputs OUTPUT: figure to output device HISTORY: 2010-07-10 - Written - Bovy (NYU) """ return self._orb.plotvT(*args,**kwargs) def plotphi(self,*args,**kwargs): """ NAME: plotphi PURPOSE: plot \phi(.) along the orbit INPUT: d1= plot vs d1: e.g., 't', 'z', 'R' bovy_plot.bovy_plot inputs OUTPUT: figure to output device HISTORY: 2010-07-10 - Written - Bovy (NYU) """ return self._orb.plotphi(*args,**kwargs) def plotvz(self,*args,**kwargs): """ NAME: plotvz PURPOSE: plot vz(.) along the orbit INPUT: d1= plot vs d1: e.g., 't', 'z', 'R' vo= (Object-wide default) physical scale for velocities to use to convert (can be Quantity) bovy_plot.bovy_plot inputs OUTPUT: figure to output device HISTORY: 2010-07-10 - Written - Bovy (NYU) """ return self._orb.plotvz(*args,**kwargs) def plotx(self,*args,**kwargs): """ NAME: plotx PURPOSE: plot x(.) along the orbit INPUT: d1= plot vs d1: e.g., 't', 'z', 'R' ro= (Object-wide default) physical scale for distances to use to convert (can be Quantity) use_physical= use to override Object-wide default for using a physical scale for output bovy_plot.bovy_plot inputs OUTPUT: figure to output device HISTORY: 2010-07-21 - Written - Bovy (NYU) """ return self._orb.plotx(*args,**kwargs) def plotvx(self,*args,**kwargs): """ NAME: plotvx PURPOSE: plot vx(.) along the orbit INPUT: d1= plot vs d1: e.g., 't', 'z', 'R' vo= (Object-wide default) physical scale for velocities to use to convert (can be Quantity) bovy_plot.bovy_plot inputs OUTPUT: figure to output device HISTORY: 2010-07-21 - Written - Bovy (NYU) """ return self._orb.plotvx(*args,**kwargs) def ploty(self,*args,**kwargs): """ NAME: ploty PURPOSE: plot y(.) along the orbit INPUT: d1= plot vs d1: e.g., 't', 'z', 'R' ro= (Object-wide default) physical scale for distances to use to convert (can be Quantity) use_physical= use to override Object-wide default for using a physical scale for output bovy_plot.bovy_plot inputs OUTPUT: figure to output device HISTORY: 2010-07-21 - Written - Bovy (NYU) """ return self._orb.ploty(*args,**kwargs) def plotvy(self,*args,**kwargs): """ NAME: plotvy PURPOSE: plot vy(.) along the orbit INPUT: d1= plot vs d1: e.g., 't', 'z', 'R' vo= (Object-wide default) physical scale for velocities to use to convert (can be Quantity) bovy_plot.bovy_plot inputs OUTPUT: figure to output device HISTORY: 2010-07-21 - Written - Bovy (NYU) """ return self._orb.plotvy(*args,**kwargs) def toPlanar(self): """ NAME: toPlanar PURPOSE: convert a 3D orbit into a 2D orbit INPUT: (none) OUTPUT: planar Orbit HISTORY: 2010-11-30 - Written - Bovy (NYU) """ orbSetupKwargs= {'ro':None, 'vo':None, 'zo':self._orb._zo, 'solarmotion':self._orb._solarmotion} if self._orb._roSet: orbSetupKwargs['ro']= self._orb._ro if self._orb._voSet: orbSetupKwargs['vo']= self._orb._vo if len(self._orb.vxvv) == 6: vxvv= [self._orb.vxvv[0],self._orb.vxvv[1], self._orb.vxvv[2],self._orb.vxvv[5]] elif len(self._orb.vxvv) == 5: vxvv= [self._orb.vxvv[0],self._orb.vxvv[1],self._orb.vxvv[2]] else: raise AttributeError("planar or linear Orbits do not have the toPlanar attribute") return Orbit(vxvv=vxvv,**orbSetupKwargs) def toLinear(self): """ NAME: toLinear PURPOSE: convert a 3D orbit into a 1D orbit (z) INPUT: (none) OUTPUT: linear Orbit HISTORY: 2010-11-30 - Written - Bovy (NYU) """ orbSetupKwargs= {'ro':None, 'vo':None, 'zo':self._orb._zo, 'solarmotion':self._orb._solarmotion} if self._orb._roSet: orbSetupKwargs['ro']= self._orb._ro if self._orb._voSet: orbSetupKwargs['vo']= self._orb._vo if len(self._orb.vxvv) == 6 or len(self._orb.vxvv) == 5: vxvv= [self._orb.vxvv[3],self._orb.vxvv[4]] else: raise AttributeError("planar or linear Orbits do not have the toPlanar attribute") return Orbit(vxvv=vxvv,**orbSetupKwargs) def __add__(self,linOrb): """ NAME: __add__ PURPOSE: add a linear orbit and a planar orbit to make a 3D orbit INPUT: linear or plane orbit instance OUTPUT: 3D orbit HISTORY: 2010-07-21 - Written - Bovy (NYU) """ orbSetupKwargs= {'ro':None, 'vo':None, 'zo':self._orb._zo, 'solarmotion':self._orb._solarmotion} if self._orb._roSet: orbSetupKwargs['ro']= self._orb._ro if self._orb._voSet: orbSetupKwargs['vo']= self._orb._vo if (not (isinstance(self._orb,planarOrbitTop) and isinstance(linOrb._orb,linearOrbit)) and not (isinstance(self._orb,linearOrbit) and isinstance(linOrb._orb,planarOrbitTop))): raise AttributeError("Only planarOrbit+linearOrbit is supported") if isinstance(self._orb,planarROrbit): return Orbit(vxvv=[self._orb.vxvv[0],self._orb.vxvv[1], self._orb.vxvv[2], linOrb._orb.vxvv[0],linOrb._orb.vxvv[1]], **orbSetupKwargs) elif isinstance(self._orb,planarOrbit): return Orbit(vxvv=[self._orb.vxvv[0],self._orb.vxvv[1], self._orb.vxvv[2], linOrb._orb.vxvv[0],linOrb._orb.vxvv[1], self._orb.vxvv[3]], **orbSetupKwargs) elif isinstance(linOrb._orb,planarROrbit): return Orbit(vxvv=[linOrb._orb.vxvv[0],linOrb._orb.vxvv[1], linOrb._orb.vxvv[2], self._orb.vxvv[0],self._orb.vxvv[1]], **orbSetupKwargs) elif isinstance(linOrb._orb,planarOrbit): return Orbit(vxvv=[linOrb._orb.vxvv[0],linOrb._orb.vxvv[1], linOrb._orb.vxvv[2], self._orb.vxvv[0],self._orb.vxvv[1], linOrb._orb.vxvv[3]], **orbSetupKwargs) def animate(self,*args,**kwargs): #pragma: no cover """ NAME: animate PURPOSE: animate a previously calculated orbit (with reasonable defaults) INPUT: d1= first dimension to plot ('x', 'y', 'R', 'vR', 'vT', 'z', 'vz', ...); can be list with up to three entries for three subplots; each entry can also be a user-defined function of time (e.g., lambda t: o.R(t) for R) d2= second dimension to plot; can be list with up to three entries for three subplots; each entry can also be a user-defined function of time (e.g., lambda t: o.R(t) for R) width= (600) width of output div in px height= (400) height of output div in px xlabel= (pre-defined labels) label for the first dimension (or list of labels if d1 is a list); should only have to be specified when using a function as d1 and can then specify as, e.g., [None,'YOUR LABEL',None] if d1 is a list of three xs and the first and last are standard entries) ylabel= (pre-defined labels) label for the second dimension (or list of labels if d2 is a list); should only have to be specified when using a function as d2 and can then specify as, e.g., [None,'YOUR LABEL',None] if d1 is a list of three xs and the first and last are standard entries) json_filename= (None) if set, save the data necessary for the figure in this filename (e.g., json_filename= 'orbit_data/orbit.json'); this path is also used in the output HTML, so needs to be accessible ro= (Object-wide default) physical scale for distances to use to convert (can be Quantity) vo= (Object-wide default) physical scale for velocities to use to convert (can be Quantity) use_physical= use to override Object-wide default for using a physical scale for output OUTPUT: IPython.display.HTML object with code to animate the orbit; can be directly shown in jupyter notebook or embedded in HTML pages; get a text version of the HTML using the _repr_html_() function HISTORY: 2017-09-17-24 - Written - Bovy (UofT) """ return self._orb.animate(*args,**kwargs) def _check_integrate_dt(t,dt): """Check that the stepszie in t is an integer x dt""" if dt is None: return True mult= round((t[1]-t[0])/dt) if nu.fabs(mult*dt-t[1]+t[0]) < 10.**-10.: return True else: return False def _check_potential_dim(orb,pot): from galpy.potential import _dim # Don't deal with pot=None here, just dimensionality assert pot is None or orb.dim() <= _dim(pot), 'Orbit dimensionality is %i, but potential dimensionality is %i < %i; orbit needs to be of equal or lower dimensionality as the potential; you can reduce the dimensionality---if appropriate---of your orbit with orbit.toPlanar or orbit.toLinear' % (orb.dim(),_dim(pot),orb.dim()) def _check_consistent_units(orb,pot): if pot is None: return None if isinstance(pot,list): if orb._roSet and pot[0]._roSet: assert nu.fabs(orb._ro-pot[0]._ro) < 10.**-10., 'Physical conversion for the Orbit object is not consistent with that of the Potential given to it' if orb._voSet and pot[0]._voSet: assert nu.fabs(orb._vo-pot[0]._vo) < 10.**-10., 'Physical conversion for the Orbit object is not consistent with that of the Potential given to it' else: if orb._roSet and pot._roSet: assert nu.fabs(orb._ro-pot._ro) < 10.**-10., 'Physical conversion for the Orbit object is not consistent with that of the Potential given to it' if orb._voSet and pot._voSet: assert nu.fabs(orb._vo-pot._vo) < 10.**-10., 'Physical conversion for the Orbit object is not consistent with that of the Potential given to it' return None galpy-1.3.0/galpy/orbit_src/orbit_c_ext/0000755000076500000240000000000013236420072020333 5ustar bovystaff00000000000000galpy-1.3.0/galpy/orbit_src/orbit_c_ext/integrateFullOrbit.c0000644000076500000240000004477413236350147024331 0ustar bovystaff00000000000000/* Wrappers around the C integration code for Full Orbits */ #include #include #include #include #include #include //Potentials #include #ifndef M_PI #define M_PI 3.14159265358979323846 #endif /* Function Declarations */ void evalRectForce(double, double *, double *, int, struct potentialArg *); void evalRectDeriv(double, double *, double *, int, struct potentialArg *); void evalRectDeriv_dxdv(double,double *, double *, int, struct potentialArg *); /* Actual functions */ void parse_leapFuncArgs_Full(int npot, struct potentialArg * potentialArgs, int ** pot_type, double ** pot_args){ int ii,jj,kk; int nR, nz; double * Rgrid, * zgrid, * potGrid_splinecoeffs; init_potentialArgs(npot,potentialArgs); for (ii=0; ii < npot; ii++){ switch ( *(*pot_type)++ ) { case 0: //LogarithmicHaloPotential, 4 arguments potentialArgs->Rforce= &LogarithmicHaloPotentialRforce; potentialArgs->zforce= &LogarithmicHaloPotentialzforce; potentialArgs->phiforce= &LogarithmicHaloPotentialphiforce; //potentialArgs->R2deriv= &LogarithmicHaloPotentialR2deriv; //potentialArgs->planarphi2deriv= &ZeroForce; //potentialArgs->planarRphideriv= &ZeroForce; potentialArgs->nargs= 4; break; case 1: //DehnenBarPotential, 6 arguments potentialArgs->Rforce= &DehnenBarPotentialRforce; potentialArgs->phiforce= &DehnenBarPotentialphiforce; potentialArgs->zforce= &DehnenBarPotentialzforce; potentialArgs->nargs= 6; break; case 5: //MiyamotoNagaiPotential, 3 arguments potentialArgs->Rforce= &MiyamotoNagaiPotentialRforce; potentialArgs->zforce= &MiyamotoNagaiPotentialzforce; potentialArgs->phiforce= &ZeroForce; //potentialArgs->R2deriv= &MiyamotoNagaiPotentialR2deriv; //potentialArgs->planarphi2deriv= &ZeroForce; //potentialArgs->planarRphideriv= &ZeroForce; potentialArgs->nargs= 3; break; case 7: //PowerSphericalPotential, 2 arguments potentialArgs->Rforce= &PowerSphericalPotentialRforce; potentialArgs->zforce= &PowerSphericalPotentialzforce; potentialArgs->phiforce= &ZeroForce; //potentialArgs->R2deriv= &PowerSphericalPotentialR2deriv; //potentialArgs->planarphi2deriv= &ZeroForce; //potentialArgs->planarRphideriv= &ZeroForce; potentialArgs->nargs= 2; break; case 8: //HernquistPotential, 2 arguments potentialArgs->Rforce= &HernquistPotentialRforce; potentialArgs->zforce= &HernquistPotentialzforce; potentialArgs->phiforce= &ZeroForce; //potentialArgs->R2deriv= &HernquistPotentialR2deriv; //potentialArgs->planarphi2deriv= &ZeroForce; //potentialArgs->planarRphideriv= &ZeroForce; potentialArgs->nargs= 2; break; case 9: //NFWPotential, 2 arguments potentialArgs->Rforce= &NFWPotentialRforce; potentialArgs->zforce= &NFWPotentialzforce; potentialArgs->phiforce= &ZeroForce; //potentialArgs->R2deriv= &NFWPotentialR2deriv; //potentialArgs->planarphi2deriv= &ZeroForce; //potentialArgs->planarRphideriv= &ZeroForce; potentialArgs->nargs= 2; break; case 10: //JaffePotential, 2 arguments potentialArgs->Rforce= &JaffePotentialRforce; potentialArgs->zforce= &JaffePotentialzforce; potentialArgs->phiforce= &ZeroForce; //potentialArgs->R2deriv= &JaffePotentialR2deriv; //potentialArgs->planarphi2deriv= &ZeroForce; //potentialArgs->planarRphideriv= &ZeroForce; potentialArgs->nargs= 2; break; case 11: //DoubleExponentialDiskPotential, XX arguments potentialArgs->Rforce= &DoubleExponentialDiskPotentialRforce; potentialArgs->zforce= &DoubleExponentialDiskPotentialzforce; potentialArgs->phiforce= &ZeroForce; //Look at pot_args to figure out the number of arguments potentialArgs->nargs= (int) (8 + 2 * *(*pot_args+5) + 4 * ( *(*pot_args+4) + 1 )); break; case 12: //FlattenedPowerPotential, 4 arguments potentialArgs->Rforce= &FlattenedPowerPotentialRforce; potentialArgs->zforce= &FlattenedPowerPotentialzforce; potentialArgs->phiforce= &ZeroForce; potentialArgs->nargs= 4; break; case 13: //interpRZPotential, XX arguments //Grab the grids and the coefficients nR= (int) *(*pot_args)++; nz= (int) *(*pot_args)++; Rgrid= (double *) malloc ( nR * sizeof ( double ) ); zgrid= (double *) malloc ( nz * sizeof ( double ) ); potGrid_splinecoeffs= (double *) malloc ( nR * nz * sizeof ( double ) ); for (kk=0; kk < nR; kk++) *(Rgrid+kk)= *(*pot_args)++; for (kk=0; kk < nz; kk++) *(zgrid+kk)= *(*pot_args)++; for (kk=0; kk < nR; kk++) put_row(potGrid_splinecoeffs,kk,*pot_args+kk*nz,nz); *pot_args+= nR*nz; potentialArgs->i2drforce= interp_2d_alloc(nR,nz); interp_2d_init(potentialArgs->i2drforce,Rgrid,zgrid,potGrid_splinecoeffs, INTERP_2D_LINEAR); //latter bc we already calculated the coeffs potentialArgs->accxrforce= gsl_interp_accel_alloc (); potentialArgs->accyrforce= gsl_interp_accel_alloc (); for (kk=0; kk < nR; kk++) put_row(potGrid_splinecoeffs,kk,*pot_args+kk*nz,nz); *pot_args+= nR*nz; potentialArgs->i2dzforce= interp_2d_alloc(nR,nz); interp_2d_init(potentialArgs->i2dzforce,Rgrid,zgrid,potGrid_splinecoeffs, INTERP_2D_LINEAR); //latter bc we already calculated the coeffs potentialArgs->accxzforce= gsl_interp_accel_alloc (); potentialArgs->accyzforce= gsl_interp_accel_alloc (); potentialArgs->Rforce= &interpRZPotentialRforce; potentialArgs->zforce= &interpRZPotentialzforce; potentialArgs->phiforce= &ZeroForce; potentialArgs->nargs= 2; //clean up free(Rgrid); free(zgrid); free(potGrid_splinecoeffs); break; case 14: //IsochronePotential, 2 arguments potentialArgs->Rforce= &IsochronePotentialRforce; potentialArgs->zforce= &IsochronePotentialzforce; potentialArgs->phiforce= &ZeroForce; potentialArgs->nargs= 2; break; case 15: //PowerSphericalwCutoffPotential, 3 arguments potentialArgs->Rforce= &PowerSphericalPotentialwCutoffRforce; potentialArgs->zforce= &PowerSphericalPotentialwCutoffzforce; potentialArgs->phiforce= &ZeroForce; //potentialArgs->R2deriv= &PowerSphericalPotentialR2deriv; //potentialArgs->planarphi2deriv= &ZeroForce; //potentialArgs->planarRphideriv= &ZeroForce; potentialArgs->nargs= 3; break; case 16: //KuzminKutuzovStaeckelPotential, 3 arguments potentialArgs->Rforce= &KuzminKutuzovStaeckelPotentialRforce; potentialArgs->zforce= &KuzminKutuzovStaeckelPotentialzforce; potentialArgs->phiforce= &ZeroForce; //potentialArgs->R2deriv= &KuzminKutuzovStaeckelPotentialR2deriv; potentialArgs->nargs= 3; break; case 17: //PlummerPotential, 2 arguments potentialArgs->Rforce= &PlummerPotentialRforce; potentialArgs->zforce= &PlummerPotentialzforce; potentialArgs->phiforce= &ZeroForce; //potentialArgs->R2deriv= &PlummerPotentialR2deriv; potentialArgs->nargs= 2; break; case 18: //PseudoIsothermalPotential, 2 arguments potentialArgs->Rforce= &PseudoIsothermalPotentialRforce; potentialArgs->zforce= &PseudoIsothermalPotentialzforce; potentialArgs->phiforce= &ZeroForce; //potentialArgs->R2deriv= &PseudoIsothermalPotentialR2deriv; potentialArgs->nargs= 2; break; case 19: //KuzminDiskPotential, 2 arguments potentialArgs->Rforce= &KuzminDiskPotentialRforce; potentialArgs->zforce= &KuzminDiskPotentialzforce; potentialArgs->phiforce= &ZeroForce; potentialArgs->nargs= 2; break; case 20: //BurkertPotential, 2 arguments potentialArgs->Rforce= &BurkertPotentialRforce; potentialArgs->zforce= &BurkertPotentialzforce; potentialArgs->phiforce= &ZeroForce; potentialArgs->nargs= 2; break; case 21: //TriaxialHernquistPotential, lots of arguments potentialArgs->Rforce= &TriaxialHernquistPotentialRforce; potentialArgs->zforce= &TriaxialHernquistPotentialzforce; potentialArgs->phiforce= &TriaxialHernquistPotentialphiforce; potentialArgs->nargs= (int) (21 + 2 * *(*pot_args+14)); break; case 22: //TriaxialNFWPotential, lots of arguments potentialArgs->Rforce= &TriaxialNFWPotentialRforce; potentialArgs->zforce= &TriaxialNFWPotentialzforce; potentialArgs->phiforce= &TriaxialNFWPotentialphiforce; potentialArgs->nargs= (int) (21 + 2 * *(*pot_args+14)); break; case 23: //TriaxialJaffePotential, lots of arguments potentialArgs->Rforce= &TriaxialJaffePotentialRforce; potentialArgs->zforce= &TriaxialJaffePotentialzforce; potentialArgs->phiforce= &TriaxialJaffePotentialphiforce; potentialArgs->nargs= (int) (21 + 2 * *(*pot_args+14)); break; case 24: //SCFPotential, many arguments potentialArgs->Rforce= &SCFPotentialRforce; potentialArgs->zforce= &SCFPotentialzforce; potentialArgs->phiforce= &SCFPotentialphiforce; potentialArgs->nargs= (int) (5 + (1 + *(*pot_args + 1)) * *(*pot_args+2) * *(*pot_args+3)* *(*pot_args+4) + 7); break; case 25: //SoftenedNeedleBarPotential, 13 arguments potentialArgs->Rforce= &SoftenedNeedleBarPotentialRforce; potentialArgs->zforce= &SoftenedNeedleBarPotentialzforce; potentialArgs->phiforce= &SoftenedNeedleBarPotentialphiforce; potentialArgs->nargs= (int) 13; break; case 26: //DiskSCFPotential, nsigma+3 arguments potentialArgs->Rforce= &DiskSCFPotentialRforce; potentialArgs->zforce= &DiskSCFPotentialzforce; potentialArgs->phiforce= &ZeroForce; potentialArgs->nargs= (int) **pot_args + 3; break; case 27: // SpiralArmsPotential, 10 arguments + array of Cs potentialArgs->Rforce = &SpiralArmsPotentialRforce; potentialArgs->zforce = &SpiralArmsPotentialzforce; potentialArgs->phiforce = &SpiralArmsPotentialphiforce; //potentialArgs->R2deriv = &SpiralArmsPotentialR2deriv; //potentialArgs->z2deriv = &SpiralArmsPotentialz2deriv; potentialArgs->phi2deriv = &SpiralArmsPotentialphi2deriv; //potentialArgs->Rzderiv = &SpiralArmsPotentialRzderiv; potentialArgs->Rphideriv = &SpiralArmsPotentialRphideriv; potentialArgs->nargs = (int) 10 + **pot_args; break; //////////////////////////////// WRAPPERS ///////////////////////////////////// case -1: //DehnenSmoothWrapperPotential potentialArgs->Rforce= &DehnenSmoothWrapperPotentialRforce; potentialArgs->zforce= &DehnenSmoothWrapperPotentialzforce; potentialArgs->phiforce= &DehnenSmoothWrapperPotentialphiforce; potentialArgs->nargs= (int) 3; break; case -2: //SolidBodyRotationWrapperPotential potentialArgs->Rforce= &SolidBodyRotationWrapperPotentialRforce; potentialArgs->zforce= &SolidBodyRotationWrapperPotentialzforce; potentialArgs->phiforce= &SolidBodyRotationWrapperPotentialphiforce; potentialArgs->nargs= (int) 3; break; } if ( *(*pot_type-1) < 0 ) { // Parse wrapped potential for wrappers potentialArgs->nwrapped= (int) *(*pot_args)++; potentialArgs->wrappedPotentialArg= \ (struct potentialArg *) malloc ( potentialArgs->nwrapped \ * sizeof (struct potentialArg) ); parse_leapFuncArgs_Full(potentialArgs->nwrapped, potentialArgs->wrappedPotentialArg, pot_type,pot_args); } potentialArgs->args= (double *) malloc( potentialArgs->nargs * sizeof(double)); for (jj=0; jj < potentialArgs->nargs; jj++){ *(potentialArgs->args)= *(*pot_args)++; potentialArgs->args++; } potentialArgs->args-= potentialArgs->nargs; potentialArgs++; } potentialArgs-= npot; } void integrateFullOrbit(double *yo, int nt, double *t, int npot, int * pot_type, double * pot_args, double dt, double rtol, double atol, double *result, int * err, int odeint_type){ //Set up the forces, first count int dim; struct potentialArg * potentialArgs= (struct potentialArg *) malloc ( npot * sizeof (struct potentialArg) ); parse_leapFuncArgs_Full(npot,potentialArgs,&pot_type,&pot_args); //Integrate void (*odeint_func)(void (*func)(double, double *, double *, int, struct potentialArg *), int, double *, int, double, double *, int, struct potentialArg *, double, double, double *,int *); void (*odeint_deriv_func)(double, double *, double *, int,struct potentialArg *); switch ( odeint_type ) { case 0: //leapfrog odeint_func= &leapfrog; odeint_deriv_func= &evalRectForce; dim= 3; break; case 1: //RK4 odeint_func= &bovy_rk4; odeint_deriv_func= &evalRectDeriv; dim= 6; break; case 2: //RK6 odeint_func= &bovy_rk6; odeint_deriv_func= &evalRectDeriv; dim= 6; break; case 3: //symplec4 odeint_func= &symplec4; odeint_deriv_func= &evalRectForce; dim= 3; break; case 4: //symplec6 odeint_func= &symplec6; odeint_deriv_func= &evalRectForce; dim= 3; break; case 5: //DOPR54 odeint_func= &bovy_dopr54; odeint_deriv_func= &evalRectDeriv; dim= 6; break; } odeint_func(odeint_deriv_func,dim,yo,nt,dt,t,npot,potentialArgs,rtol,atol, result,err); //Free allocated memory free_potentialArgs(npot,potentialArgs); free(potentialArgs); //Done! } // LCOV_EXCL_START void integrateOrbit_dxdv(double *yo, int nt, double *t, int npot, int * pot_type, double * pot_args, double rtol, double atol, double *result, int * err, int odeint_type){ //Set up the forces, first count int dim; struct potentialArg * potentialArgs= (struct potentialArg *) malloc ( npot * sizeof (struct potentialArg) ); parse_leapFuncArgs_Full(npot,potentialArgs,&pot_type,&pot_args); //Integrate void (*odeint_func)(void (*func)(double, double *, double *, int, struct potentialArg *), int, double *, int, double, double *, int, struct potentialArg *, double, double, double *,int *); void (*odeint_deriv_func)(double, double *, double *, int,struct potentialArg *); switch ( odeint_type ) { case 0: //leapfrog odeint_func= &leapfrog; odeint_deriv_func= &evalRectForce; dim= 6; break; case 1: //RK4 odeint_func= &bovy_rk4; odeint_deriv_func= &evalRectDeriv_dxdv; dim= 12; break; case 2: //RK6 odeint_func= &bovy_rk6; odeint_deriv_func= &evalRectDeriv_dxdv; dim= 12; break; case 3: //symplec4 odeint_func= &symplec4; odeint_deriv_func= &evalRectForce; dim= 6; break; case 4: //symplec6 odeint_func= &symplec6; odeint_deriv_func= &evalRectForce; dim= 6; break; case 5: //DOPR54 odeint_func= &bovy_dopr54; odeint_deriv_func= &evalRectDeriv_dxdv; dim= 12; break; } odeint_func(odeint_deriv_func,dim,yo,nt,-9999.99,t,npot,potentialArgs, rtol,atol,result,err); //Free allocated memory free_potentialArgs(npot,potentialArgs); free(potentialArgs); //Done! } // LCOV_EXCL_STOP void evalRectForce(double t, double *q, double *a, int nargs, struct potentialArg * potentialArgs){ double sinphi, cosphi, x, y, phi,R,Rforce,phiforce, z, zforce; //q is rectangular so calculate R and phi x= *q; y= *(q+1); z= *(q+2); R= sqrt(x*x+y*y); phi= acos(x/R); sinphi= y/R; cosphi= x/R; if ( y < 0. ) phi= 2.*M_PI-phi; //Calculate the forces Rforce= calcRforce(R,z,phi,t,nargs,potentialArgs); zforce= calczforce(R,z,phi,t,nargs,potentialArgs); phiforce= calcPhiforce(R,z,phi,t,nargs,potentialArgs); *a++= cosphi*Rforce-1./R*sinphi*phiforce; *a++= sinphi*Rforce+1./R*cosphi*phiforce; *a= zforce; } void evalRectDeriv(double t, double *q, double *a, int nargs, struct potentialArg * potentialArgs){ double sinphi, cosphi, x, y, phi,R,Rforce,phiforce,z,zforce; //first three derivatives are just the velocities *a++= *(q+3); *a++= *(q+4); *a++= *(q+5); //Rest is force //q is rectangular so calculate R and phi x= *q; y= *(q+1); z= *(q+2); R= sqrt(x*x+y*y); phi= acos(x/R); sinphi= y/R; cosphi= x/R; if ( y < 0. ) phi= 2.*M_PI-phi; //Calculate the forces Rforce= calcRforce(R,z,phi,t,nargs,potentialArgs); zforce= calczforce(R,z,phi,t,nargs,potentialArgs); phiforce= calcPhiforce(R,z,phi,t,nargs,potentialArgs); *a++= cosphi*Rforce-1./R*sinphi*phiforce; *a++= sinphi*Rforce+1./R*cosphi*phiforce; *a= zforce; } // LCOV_EXCL_START void evalRectDeriv_dxdv(double t, double *q, double *a, int nargs, struct potentialArg * potentialArgs){ double sinphi, cosphi, x, y, phi,R,Rforce,phiforce,z,zforce; double R2deriv, phi2deriv, Rphideriv, dFxdx, dFxdy, dFydx, dFydy; //first three derivatives are just the velocities *a++= *(q+3); *a++= *(q+4); *a++= *(q+5); //Rest is force //q is rectangular so calculate R and phi x= *q; y= *(q+1); z= *(q+2); R= sqrt(x*x+y*y); phi= acos(x/R); sinphi= y/R; cosphi= x/R; if ( y < 0. ) phi= 2.*M_PI-phi; //Calculate the forces Rforce= calcRforce(R,z,phi,t,nargs,potentialArgs); zforce= calczforce(R,z,phi,t,nargs,potentialArgs); phiforce= calcPhiforce(R,z,phi,t,nargs,potentialArgs); *a++= cosphi*Rforce-1./R*sinphi*phiforce; *a++= sinphi*Rforce+1./R*cosphi*phiforce; *a++= zforce; //dx derivatives are just dv *a++= *(q+9); *a++= *(q+10); *a++= *(q+11); //for the dv derivatives we need also R2deriv, phi2deriv, and Rphideriv R2deriv= calcR2deriv(R,z,phi,t,nargs,potentialArgs); phi2deriv= calcphi2deriv(R,z,phi,t,nargs,potentialArgs); Rphideriv= calcRphideriv(R,z,phi,t,nargs,potentialArgs); //..and dFxdx, dFxdy, dFydx, dFydy dFxdx= -cosphi*cosphi*R2deriv +2.*cosphi*sinphi/R/R*phiforce +sinphi*sinphi/R*Rforce +2.*sinphi*cosphi/R*Rphideriv -sinphi*sinphi/R/R*phi2deriv; dFxdy= -sinphi*cosphi*R2deriv +(sinphi*sinphi-cosphi*cosphi)/R/R*phiforce -cosphi*sinphi/R*Rforce -(cosphi*cosphi-sinphi*sinphi)/R*Rphideriv +cosphi*sinphi/R/R*phi2deriv; dFydx= -cosphi*sinphi*R2deriv +(sinphi*sinphi-cosphi*cosphi)/R/R*phiforce +(sinphi*sinphi-cosphi*cosphi)/R*Rphideriv -sinphi*cosphi/R*Rforce +sinphi*cosphi/R/R*phi2deriv; dFydy= -sinphi*sinphi*R2deriv -2.*sinphi*cosphi/R/R*phiforce -2.*sinphi*cosphi/R*Rphideriv +cosphi*cosphi/R*Rforce -cosphi*cosphi/R/R*phi2deriv; *a++= dFxdx * *(q+4) + dFxdy * *(q+5); *a++= dFydx * *(q+4) + dFydy * *(q+5); *a= 0; //BOVY: PUT IN Z2DERIVS } // LCOV_EXCL_STOP galpy-1.3.0/galpy/orbit_src/orbit_c_ext/integrateFullOrbit.h0000644000076500000240000000042013200435507024305 0ustar bovystaff00000000000000#ifndef __INTEGRATEFULLORBIT_H__ #define __INTEGRATEFULLORBIT_H__ #ifdef __cplusplus extern "C" { #endif #include void parse_leapFuncArgs_Full(int, struct potentialArg *,int **,double **); #ifdef __cplusplus } #endif #endif /* integrateFullOrbit.h */ galpy-1.3.0/galpy/orbit_src/orbit_c_ext/integratePlanarOrbit.c0000644000076500000240000004747513236350147024645 0ustar bovystaff00000000000000/* Wrappers around the C integration code for planar Orbits */ #include #include #include #include #include #include //Potentials #include #ifndef M_PI #define M_PI 3.14159265358979323846 #endif /* Function Declarations */ void evalPlanarRectForce(double, double *, double *, int, struct potentialArg *); void evalPlanarRectDeriv(double, double *, double *, int, struct potentialArg *); void evalPlanarRectDeriv_dxdv(double, double *, double *, int, struct potentialArg *); /* Actual functions */ void parse_leapFuncArgs(int npot,struct potentialArg * potentialArgs, int ** pot_type, double ** pot_args){ int ii,jj; init_potentialArgs(npot,potentialArgs); for (ii=0; ii < npot; ii++){ switch ( *(*pot_type)++ ) { case 0: //LogarithmicHaloPotential, 3 arguments potentialArgs->planarRforce= &LogarithmicHaloPotentialPlanarRforce; potentialArgs->planarphiforce= &LogarithmicHaloPotentialPlanarphiforce; potentialArgs->planarR2deriv= &LogarithmicHaloPotentialPlanarR2deriv; potentialArgs->planarphi2deriv= &LogarithmicHaloPotentialPlanarphi2deriv; potentialArgs->planarRphideriv= &LogarithmicHaloPotentialPlanarRphideriv; potentialArgs->nargs= 3; break; case 1: //DehnenBarPotential, 6 arguments potentialArgs->planarRforce= &DehnenBarPotentialPlanarRforce; potentialArgs->planarphiforce= &DehnenBarPotentialPlanarphiforce; potentialArgs->planarR2deriv= &DehnenBarPotentialPlanarR2deriv; potentialArgs->planarphi2deriv= &DehnenBarPotentialPlanarphi2deriv; potentialArgs->planarRphideriv= &DehnenBarPotentialPlanarRphideriv; potentialArgs->nargs= 6; break; case 2: //TransientLogSpiralPotential, 8 arguments potentialArgs->planarRforce= &TransientLogSpiralPotentialRforce; potentialArgs->planarphiforce= &TransientLogSpiralPotentialphiforce; potentialArgs->nargs= 8; break; case 3: //SteadyLogSpiralPotential, 8 arguments potentialArgs->planarRforce= &SteadyLogSpiralPotentialRforce; potentialArgs->planarphiforce= &SteadyLogSpiralPotentialphiforce; potentialArgs->nargs= 8; break; case 4: //EllipticalDiskPotential, 6 arguments potentialArgs->planarRforce= &EllipticalDiskPotentialRforce; potentialArgs->planarphiforce= &EllipticalDiskPotentialphiforce; potentialArgs->planarR2deriv= &EllipticalDiskPotentialR2deriv; potentialArgs->planarphi2deriv= &EllipticalDiskPotentialphi2deriv; potentialArgs->planarRphideriv= &EllipticalDiskPotentialRphideriv; potentialArgs->nargs= 6; break; case 5: //MiyamotoNagaiPotential, 3 arguments potentialArgs->planarRforce= &MiyamotoNagaiPotentialPlanarRforce; potentialArgs->planarphiforce= &ZeroPlanarForce; potentialArgs->planarR2deriv= &MiyamotoNagaiPotentialPlanarR2deriv; potentialArgs->planarphi2deriv= &ZeroPlanarForce; potentialArgs->planarRphideriv= &ZeroPlanarForce; potentialArgs->nargs= 3; break; case 6: //LopsidedDiskPotential, 4 arguments potentialArgs->planarRforce= &LopsidedDiskPotentialRforce; potentialArgs->planarphiforce= &LopsidedDiskPotentialphiforce; potentialArgs->planarR2deriv= &LopsidedDiskPotentialR2deriv; potentialArgs->planarphi2deriv= &LopsidedDiskPotentialphi2deriv; potentialArgs->planarRphideriv= &LopsidedDiskPotentialRphideriv; potentialArgs->nargs= 4; break; case 7: //PowerSphericalPotential, 2 arguments potentialArgs->planarRforce= &PowerSphericalPotentialPlanarRforce; potentialArgs->planarphiforce= &ZeroPlanarForce; potentialArgs->planarR2deriv= &PowerSphericalPotentialPlanarR2deriv; potentialArgs->planarphi2deriv= &ZeroPlanarForce; potentialArgs->planarRphideriv= &ZeroPlanarForce; potentialArgs->nargs= 2; break; case 8: //HernquistPotential, 2 arguments potentialArgs->planarRforce= &HernquistPotentialPlanarRforce; potentialArgs->planarphiforce= &ZeroPlanarForce; potentialArgs->planarR2deriv= &HernquistPotentialPlanarR2deriv; potentialArgs->planarphi2deriv= &ZeroPlanarForce; potentialArgs->planarRphideriv= &ZeroPlanarForce; potentialArgs->nargs= 2; break; case 9: //NFWPotential, 2 arguments potentialArgs->planarRforce= &NFWPotentialPlanarRforce; potentialArgs->planarphiforce= &ZeroPlanarForce; potentialArgs->planarR2deriv= &NFWPotentialPlanarR2deriv; potentialArgs->planarphi2deriv= &ZeroPlanarForce; potentialArgs->planarRphideriv= &ZeroPlanarForce; potentialArgs->nargs= 2; break; case 10: //JaffePotential, 2 arguments potentialArgs->planarRforce= &JaffePotentialPlanarRforce; potentialArgs->planarphiforce= &ZeroPlanarForce; potentialArgs->planarR2deriv= &JaffePotentialPlanarR2deriv; potentialArgs->planarphi2deriv= &ZeroPlanarForce; potentialArgs->planarRphideriv= &ZeroPlanarForce; potentialArgs->nargs= 2; break; case 11: //DoubleExponentialDiskPotential, XX arguments potentialArgs->planarRforce= &DoubleExponentialDiskPotentialPlanarRforce; potentialArgs->planarphiforce= &ZeroPlanarForce; //potentialArgs->planarR2deriv= &DoubleExponentialDiskPotentialPlanarR2deriv; potentialArgs->planarphi2deriv= &ZeroPlanarForce; potentialArgs->planarRphideriv= &ZeroPlanarForce; //Look at pot_args to figure out the number of arguments potentialArgs->nargs= (int) (8 + 2 * *(*pot_args+5) + 4 * ( *(*pot_args+4) + 1 )); break; case 12: //FlattenedPowerPotential, 4 arguments potentialArgs->planarRforce= &FlattenedPowerPotentialPlanarRforce; potentialArgs->planarphiforce= &ZeroPlanarForce; potentialArgs->planarR2deriv= &FlattenedPowerPotentialPlanarR2deriv; potentialArgs->planarphi2deriv= &ZeroPlanarForce; potentialArgs->planarRphideriv= &ZeroPlanarForce; potentialArgs->nargs= 3; break; case 14: //IsochronePotential, 2 arguments potentialArgs->planarRforce= &IsochronePotentialPlanarRforce; potentialArgs->planarphiforce= &ZeroPlanarForce; potentialArgs->planarR2deriv= &IsochronePotentialPlanarR2deriv; potentialArgs->planarphi2deriv= &ZeroPlanarForce; potentialArgs->planarRphideriv= &ZeroPlanarForce; potentialArgs->nargs= 2; break; case 15: //PowerSphericalPotentialwCutoff, 3 arguments potentialArgs->planarRforce= &PowerSphericalPotentialwCutoffPlanarRforce; potentialArgs->planarphiforce= &ZeroPlanarForce; potentialArgs->planarR2deriv= &PowerSphericalPotentialwCutoffPlanarR2deriv; potentialArgs->planarphi2deriv= &ZeroPlanarForce; potentialArgs->planarRphideriv= &ZeroPlanarForce; potentialArgs->nargs= 3; break; case 16: //KuzminKutuzovStaeckelPotential, 3 arguments potentialArgs->planarRforce= &KuzminKutuzovStaeckelPotentialPlanarRforce; potentialArgs->planarphiforce= &ZeroPlanarForce; potentialArgs->planarR2deriv= &KuzminKutuzovStaeckelPotentialPlanarR2deriv; potentialArgs->planarphi2deriv= &ZeroPlanarForce; potentialArgs->planarRphideriv= &ZeroPlanarForce; potentialArgs->nargs= 3; break; case 17: //PlummerPotential, 2 arguments potentialArgs->planarRforce= &PlummerPotentialPlanarRforce; potentialArgs->planarphiforce= &ZeroPlanarForce; potentialArgs->planarR2deriv= &PlummerPotentialPlanarR2deriv; potentialArgs->planarphi2deriv= &ZeroPlanarForce; potentialArgs->planarRphideriv= &ZeroPlanarForce; potentialArgs->nargs= 2; break; case 18: //PseudoIsothermalPotential, 2 arguments potentialArgs->planarRforce= &PseudoIsothermalPotentialPlanarRforce; potentialArgs->planarphiforce= &ZeroPlanarForce; potentialArgs->planarR2deriv= &PseudoIsothermalPotentialPlanarR2deriv; potentialArgs->planarphi2deriv= &ZeroPlanarForce; potentialArgs->planarRphideriv= &ZeroPlanarForce; potentialArgs->nargs= 2; break; case 19: //KuzminDiskPotential, 2 arguments potentialArgs->planarRforce= &KuzminDiskPotentialPlanarRforce; potentialArgs->planarphiforce= &ZeroPlanarForce; potentialArgs->planarR2deriv= &KuzminDiskPotentialPlanarR2deriv; potentialArgs->planarphi2deriv= &ZeroPlanarForce; potentialArgs->planarRphideriv= &ZeroPlanarForce; potentialArgs->nargs= 2; break; case 20: //BurkertPotential, 2 arguments potentialArgs->planarRforce= &BurkertPotentialPlanarRforce; potentialArgs->planarphiforce= &ZeroPlanarForce; potentialArgs->planarR2deriv= &BurkertPotentialPlanarR2deriv; potentialArgs->planarphi2deriv= &ZeroPlanarForce; potentialArgs->planarRphideriv= &ZeroPlanarForce; potentialArgs->nargs= 2; break; case 21: //TriaxialHernquistPotential, lots of arguments potentialArgs->planarRforce= &TriaxialHernquistPotentialPlanarRforce; potentialArgs->planarphiforce= &TriaxialHernquistPotentialPlanarphiforce; potentialArgs->nargs= (int) (21 + 2 * *(*pot_args+14)); break; case 22: //TriaxialNFWPotential, lots of arguments potentialArgs->planarRforce= &TriaxialNFWPotentialPlanarRforce; potentialArgs->planarphiforce= &TriaxialNFWPotentialPlanarphiforce; potentialArgs->nargs= (int) (21 + 2 * *(*pot_args+14)); break; case 23: //TriaxialJaffePotential, lots of arguments potentialArgs->planarRforce= &TriaxialJaffePotentialPlanarRforce; potentialArgs->planarphiforce= &TriaxialJaffePotentialPlanarphiforce; potentialArgs->nargs= (int) (21 + 2 * *(*pot_args+14)); break; case 24: //SCFPotential, many arguments potentialArgs->planarRforce= &SCFPotentialPlanarRforce; potentialArgs->planarphiforce= &SCFPotentialPlanarphiforce; potentialArgs->planarR2deriv= &SCFPotentialPlanarR2deriv; potentialArgs->planarphi2deriv= &SCFPotentialPlanarphi2deriv; potentialArgs->planarRphideriv= &SCFPotentialPlanarRphideriv; potentialArgs->nargs= (int) (5 + (1 + *(*pot_args + 1)) * *(*pot_args+2) * *(*pot_args+3)* *(*pot_args+4) + 7); break; case 25: //SoftenedNeedleBarPotential, 13 arguments potentialArgs->planarRforce= &SoftenedNeedleBarPotentialPlanarRforce; potentialArgs->planarphiforce= &SoftenedNeedleBarPotentialPlanarphiforce; potentialArgs->nargs= (int) 13; break; case 26: //DiskSCFPotential, nsigma+3 arguments potentialArgs->planarRforce= &DiskSCFPotentialPlanarRforce; potentialArgs->planarphiforce= &ZeroPlanarForce; potentialArgs->nargs= (int) **pot_args + 3; break; case 27: // SpiralArmsPotential, 10 arguments + array of Cs potentialArgs->planarRforce = &SpiralArmsPotentialPlanarRforce; potentialArgs->planarphiforce = &SpiralArmsPotentialPlanarphiforce; potentialArgs->planarR2deriv = &SpiralArmsPotentialPlanarR2deriv; potentialArgs->planarphi2deriv = &SpiralArmsPotentialPlanarphi2deriv; potentialArgs->planarRphideriv = &SpiralArmsPotentialPlanarRphideriv; potentialArgs->nargs = (int) 10 + **pot_args; break; case 28: //CosmphiDiskPotential, 9 arguments potentialArgs->planarRforce= &CosmphiDiskPotentialRforce; potentialArgs->planarphiforce= &CosmphiDiskPotentialphiforce; potentialArgs->planarR2deriv= &CosmphiDiskPotentialR2deriv; potentialArgs->planarphi2deriv= &CosmphiDiskPotentialphi2deriv; potentialArgs->planarRphideriv= &CosmphiDiskPotentialRphideriv; potentialArgs->nargs= 9; break; case 29: //HenonHeilesPotential, 1 argument potentialArgs->planarRforce= &HenonHeilesPotentialRforce; potentialArgs->planarphiforce= &HenonHeilesPotentialphiforce; potentialArgs->planarR2deriv= &HenonHeilesPotentialR2deriv; potentialArgs->planarphi2deriv= &HenonHeilesPotentialphi2deriv; potentialArgs->planarRphideriv= &HenonHeilesPotentialRphideriv; potentialArgs->nargs= 1; break; //////////////////////////////// WRAPPERS ///////////////////////////////////// case -1: //DehnenSmoothWrapperPotential potentialArgs->planarRforce= &DehnenSmoothWrapperPotentialPlanarRforce; potentialArgs->planarphiforce= &DehnenSmoothWrapperPotentialPlanarphiforce; potentialArgs->planarR2deriv= &DehnenSmoothWrapperPotentialPlanarR2deriv; potentialArgs->planarphi2deriv= &DehnenSmoothWrapperPotentialPlanarphi2deriv; potentialArgs->planarRphideriv= &DehnenSmoothWrapperPotentialPlanarRphideriv; potentialArgs->nargs= (int) 3; break; case -2: //SolidBodyRotationWrapperPotential potentialArgs->planarRforce= &SolidBodyRotationWrapperPotentialPlanarRforce; potentialArgs->planarphiforce= &SolidBodyRotationWrapperPotentialPlanarphiforce; potentialArgs->planarR2deriv= &SolidBodyRotationWrapperPotentialPlanarR2deriv; potentialArgs->planarphi2deriv= &SolidBodyRotationWrapperPotentialPlanarphi2deriv; potentialArgs->planarRphideriv= &SolidBodyRotationWrapperPotentialPlanarRphideriv; potentialArgs->nargs= (int) 3; break; } if ( *(*pot_type-1) < 0) { // Parse wrapped potential for wrappers potentialArgs->nwrapped= (int) *(*pot_args)++; potentialArgs->wrappedPotentialArg= \ (struct potentialArg *) malloc ( potentialArgs->nwrapped \ * sizeof (struct potentialArg) ); parse_leapFuncArgs(potentialArgs->nwrapped, potentialArgs->wrappedPotentialArg, pot_type,pot_args); } potentialArgs->args= (double *) malloc( potentialArgs->nargs * sizeof(double)); for (jj=0; jj < potentialArgs->nargs; jj++){ *(potentialArgs->args)= *(*pot_args)++; potentialArgs->args++; } potentialArgs->args-= potentialArgs->nargs; potentialArgs++; } potentialArgs-= npot; } void integratePlanarOrbit(double *yo, int nt, double *t, int npot, int * pot_type, double * pot_args, double dt, double rtol, double atol, double *result, int * err, int odeint_type){ //Set up the forces, first count int dim; struct potentialArg * potentialArgs= (struct potentialArg *) malloc ( npot * sizeof (struct potentialArg) ); parse_leapFuncArgs(npot,potentialArgs,&pot_type,&pot_args); //Integrate void (*odeint_func)(void (*func)(double, double *, double *, int, struct potentialArg *), int, double *, int, double, double *, int, struct potentialArg *, double, double, double *,int *); void (*odeint_deriv_func)(double, double *, double *, int,struct potentialArg *); switch ( odeint_type ) { case 0: //leapfrog odeint_func= &leapfrog; odeint_deriv_func= &evalPlanarRectForce; dim= 2; break; case 1: //RK4 odeint_func= &bovy_rk4; odeint_deriv_func= &evalPlanarRectDeriv; dim= 4; break; case 2: //RK6 odeint_func= &bovy_rk6; odeint_deriv_func= &evalPlanarRectDeriv; dim= 4; break; case 3: //symplec4 odeint_func= &symplec4; odeint_deriv_func= &evalPlanarRectForce; dim= 2; break; case 4: //symplec6 odeint_func= &symplec6; odeint_deriv_func= &evalPlanarRectForce; dim= 2; break; case 5: //DOPR54 odeint_func= &bovy_dopr54; odeint_deriv_func= &evalPlanarRectDeriv; dim= 4; break; } odeint_func(odeint_deriv_func,dim,yo,nt,dt,t,npot,potentialArgs,rtol,atol, result,err); //Free allocated memory free_potentialArgs(npot,potentialArgs); free(potentialArgs); //Done! } void integratePlanarOrbit_dxdv(double *yo, int nt, double *t, int npot, int * pot_type, double * pot_args, double dt, double rtol, double atol, double *result, int * err, int odeint_type){ //Set up the forces, first count int dim; struct potentialArg * potentialArgs= (struct potentialArg *) malloc ( npot * sizeof (struct potentialArg) ); parse_leapFuncArgs(npot,potentialArgs,&pot_type,&pot_args); //Integrate void (*odeint_func)(void (*func)(double, double *, double *, int, struct potentialArg *), int, double *, int, double, double *, int, struct potentialArg *, double, double, double *,int *); void (*odeint_deriv_func)(double, double *, double *, int,struct potentialArg *); switch ( odeint_type ) { case 1: //RK4 odeint_func= &bovy_rk4; odeint_deriv_func= &evalPlanarRectDeriv_dxdv; dim= 8; break; case 2: //RK6 odeint_func= &bovy_rk6; odeint_deriv_func= &evalPlanarRectDeriv_dxdv; dim= 8; break; case 5: //DOPR54 odeint_func= &bovy_dopr54; odeint_deriv_func= &evalPlanarRectDeriv_dxdv; dim= 8; break; } odeint_func(odeint_deriv_func,dim,yo,nt,dt,t,npot,potentialArgs,rtol,atol, result,err); //Free allocated memory free_potentialArgs(npot,potentialArgs); free(potentialArgs); //Done! } void evalPlanarRectForce(double t, double *q, double *a, int nargs, struct potentialArg * potentialArgs){ double sinphi, cosphi, x, y, phi,R,Rforce,phiforce; //q is rectangular so calculate R and phi x= *q; y= *(q+1); R= sqrt(x*x+y*y); phi= acos(x/R); sinphi= y/R; cosphi= x/R; if ( y < 0. ) phi= 2.*M_PI-phi; //Calculate the forces Rforce= calcPlanarRforce(R,phi,t,nargs,potentialArgs); phiforce= calcPlanarphiforce(R,phi,t,nargs,potentialArgs); *a++= cosphi*Rforce-1./R*sinphi*phiforce; *a--= sinphi*Rforce+1./R*cosphi*phiforce; } void evalPlanarRectDeriv(double t, double *q, double *a, int nargs, struct potentialArg * potentialArgs){ double sinphi, cosphi, x, y, phi,R,Rforce,phiforce; //first two derivatives are just the velocities *a++= *(q+2); *a++= *(q+3); //Rest is force //q is rectangular so calculate R and phi x= *q; y= *(q+1); R= sqrt(x*x+y*y); phi= acos(x/R); sinphi= y/R; cosphi= x/R; if ( y < 0. ) phi= 2.*M_PI-phi; //Calculate the forces Rforce= calcPlanarRforce(R,phi,t,nargs,potentialArgs); phiforce= calcPlanarphiforce(R,phi,t,nargs,potentialArgs); *a++= cosphi*Rforce-1./R*sinphi*phiforce; *a= sinphi*Rforce+1./R*cosphi*phiforce; } void evalPlanarRectDeriv_dxdv(double t, double *q, double *a, int nargs, struct potentialArg * potentialArgs){ double sinphi, cosphi, x, y, phi,R,Rforce,phiforce; double R2deriv, phi2deriv, Rphideriv, dFxdx, dFxdy, dFydx, dFydy; //first two derivatives are just the velocities *a++= *(q+2); *a++= *(q+3); //Rest is force //q is rectangular so calculate R and phi x= *q; y= *(q+1); R= sqrt(x*x+y*y); phi= acos(x/R); sinphi= y/R; cosphi= x/R; if ( y < 0. ) phi= 2.*M_PI-phi; //Calculate the forces Rforce= calcPlanarRforce(R,phi,t,nargs,potentialArgs); phiforce= calcPlanarphiforce(R,phi,t,nargs,potentialArgs); *a++= cosphi*Rforce-1./R*sinphi*phiforce; *a++= sinphi*Rforce+1./R*cosphi*phiforce; //dx derivatives are just dv *a++= *(q+6); *a++= *(q+7); //for the dv derivatives we need also R2deriv, phi2deriv, and Rphideriv R2deriv= calcPlanarR2deriv(R,phi,t,nargs,potentialArgs); phi2deriv= calcPlanarphi2deriv(R,phi,t,nargs,potentialArgs); Rphideriv= calcPlanarRphideriv(R,phi,t,nargs,potentialArgs); //..and dFxdx, dFxdy, dFydx, dFydy dFxdx= -cosphi*cosphi*R2deriv +2.*cosphi*sinphi/R/R*phiforce +sinphi*sinphi/R*Rforce +2.*sinphi*cosphi/R*Rphideriv -sinphi*sinphi/R/R*phi2deriv; dFxdy= -sinphi*cosphi*R2deriv +(sinphi*sinphi-cosphi*cosphi)/R/R*phiforce -cosphi*sinphi/R*Rforce -(cosphi*cosphi-sinphi*sinphi)/R*Rphideriv +cosphi*sinphi/R/R*phi2deriv; dFydx= -cosphi*sinphi*R2deriv +(sinphi*sinphi-cosphi*cosphi)/R/R*phiforce +(sinphi*sinphi-cosphi*cosphi)/R*Rphideriv -sinphi*cosphi/R*Rforce +sinphi*cosphi/R/R*phi2deriv; dFydy= -sinphi*sinphi*R2deriv -2.*sinphi*cosphi/R/R*phiforce -2.*sinphi*cosphi/R*Rphideriv +cosphi*cosphi/R*Rforce -cosphi*cosphi/R/R*phi2deriv; *a++= dFxdx * *(q+4) + dFxdy * *(q+5); *a= dFydx * *(q+4) + dFydy * *(q+5); } galpy-1.3.0/galpy/orbit_src/OrbitTop.py0000644000076500000240000031140613231751437020162 0ustar bovystaff00000000000000import warnings import json from random import choice from string import ascii_lowercase import math as m import numpy as nu import scipy from scipy import interpolate _APY_LOADED= True try: from astropy import units, coordinates except ImportError: _APY_LOADED= False from galpy import actionAngle from galpy.potential import PotentialError import galpy.util.bovy_plot as plot import galpy.util.bovy_coords as coords from galpy.util.bovy_conversion import physical_conversion from galpy.util import bovy_conversion, galpyWarning from galpy.util import config if int(scipy.__version__.split('.')[0]) < 1 and \ int(scipy.__version__.split('.')[1]) < 15: #pragma: no cover _OLD_SCIPY= True _KWINTERP= {} #for scipy version <1.15 else: _OLD_SCIPY= False _KWINTERP= {'ext':2} #for scipy version >=1.15 class OrbitTop(object): """General class that holds orbits and integrates them""" def __init__(self,vxvv=None,vo=None,ro=None,zo=0.025, solarmotion=nu.array([-10.1,4.0,6.7])): """ NAME: __init__ PURPOSE: Initialize an orbit instance INPUT: vxvv - initial condition vo - circular velocity at ro (km/s) ro - distance from vantage point to GC (kpc) zo - offset toward the NGP of the Sun wrt the plane (kpc) solarmotion - value in [-U,V,W] (km/s) OUTPUT: (none) HISTORY: 2010-07-10 - Written - Bovy (NYU) """ # If you change the way an Orbit object is setup, also change each of # the methods that return Orbits self.vxvv= vxvv if vo is None: self._vo= config.__config__.getfloat('normalization','vo') self._voSet= False else: self._vo= vo self._voSet= True if ro is None: self._ro= config.__config__.getfloat('normalization','ro') self._roSet= False else: self._ro= ro self._roSet= True self._zo= zo self._solarmotion= solarmotion return None def turn_physical_off(self): """ NAME: turn_physical_off PURPOSE: turn off automatic returning of outputs in physical units INPUT: (none) OUTPUT: (none) HISTORY: 2014-06-17 - Written - Bovy (IAS) """ self._roSet= False self._voSet= False return None def turn_physical_on(self,ro=None,vo=None): """ NAME: turn_physical_on PURPOSE: turn on automatic returning of outputs in physical units INPUT: ro= reference distance (kpc) vo= reference velocity (km/s) OUTPUT: (none) HISTORY: 2016-01-19 - Written - Bovy (UofT) """ self._roSet= True self._voSet= True if not ro is None: self._ro= ro if not vo is None: self._vo= vo return None def integrate(self,t,pot,method='symplec4_c',dt=None): """ NAME: integrate PURPOSE: integrate the orbit INPUT: t - list of times at which to output (0 has to be in this!) pot - Potential instance or list of instances OUTPUT: (none) (get the actual orbit using self.getOrbit() HISTORY: 2010-07-10 """ raise NotImplementedError def getOrbit(self): """ NAME: getOrbit PURPOSE: return a previously calculated orbit INPUT: (none) OUTPUT: (none) HISTORY: 2010-07-10 - Written - Bovy (NYU) """ return self.orbit def getOrbit_dxdv(self): """ NAME: getOrbit_dxdv PURPOSE: return a previously calculated orbit_dxdv INPUT: (none) OUTPUT: (none) HISTORY: 2010-07-10 - Written - Bovy (NYU) """ return self.orbit_dxdv[:,4:] @physical_conversion('time') def time(self,*args,**kwargs): """ NAME: time PURPOSE: return the times at which the orbit is sampled INPUT: t - (default: integration times) time at which to get the time (for consistency reasons); default is to return the list of times at which the orbit is sampled ro= (Object-wide default) physical scale for distances to use to convert vo= (Object-wide default) physical scale for velocities to use to convert use_physical= use to override Object-wide default for using a physical scale for output OUTPUT: t(t) HISTORY: 2014-06-11 - Written - Bovy (IAS) """ if len(args) == 0: try: return self.t except AttributeError: return 0. else: return args[0] @physical_conversion('position') def R(self,*args,**kwargs): """ NAME: R PURPOSE: return cylindrical radius at time t INPUT: t - (optional) time at which to get the radius ro= (Object-wide default) physical scale for distances to use to convert use_physical= use to override Object-wide default for using a physical scale for output OUTPUT: R(t) HISTORY: 2010-09-21 - Written - Bovy (NYU) """ thiso= self(*args,**kwargs) onet= (len(thiso.shape) == 1) if onet: return thiso[0] else: return thiso[0,:] @physical_conversion('position') def r(self,*args,**kwargs): """ NAME: r PURPOSE: return spherical radius at time t INPUT: t - (optional) time at which to get the radius ro= (Object-wide default) physical scale for distances to use to convert use_physical= use to override Object-wide default for using a physical scale for output OUTPUT: r(t) HISTORY: 2016-04-19 - Written - Bovy (UofT) """ thiso= self(*args,**kwargs) onet= (len(thiso.shape) == 1) if onet: return nu.sqrt(thiso[0]**2.+thiso[3]**2.) else: return nu.sqrt(thiso[0,:]**2.+thiso[3,:]**2.) @physical_conversion('velocity') def vR(self,*args,**kwargs): """ NAME: vR PURPOSE: return radial velocity at time t INPUT: t - (optional) time at which to get the radial velocity vo= (Object-wide default) physical scale for velocities to use to convert use_physical= use to override Object-wide default for using a physical scale for output OUTPUT: vR(t) HISTORY: 2010-09-21 - Written - Bovy (NYU) """ thiso= self(*args,**kwargs) onet= (len(thiso.shape) == 1) if onet: return thiso[1] else: return thiso[1,:] @physical_conversion('velocity') def vT(self,*args,**kwargs): """ NAME: vT PURPOSE: return tangential velocity at time t INPUT: t - (optional) time at which to get the tangential velocity vo= (Object-wide default) physical scale for velocities to use to convert use_physical= use to override Object-wide default for using a physical scale for output OUTPUT: vT(t) HISTORY: 2010-09-21 - Written - Bovy (NYU) """ thiso= self(*args,**kwargs) onet= (len(thiso.shape) == 1) if onet: return thiso[2] else: return thiso[2,:] @physical_conversion('position') def z(self,*args,**kwargs): """ NAME: z PURPOSE: return vertical height INPUT: t - (optional) time at which to get the vertical height ro= (Object-wide default) physical scale for distances to use to convert use_physical= use to override Object-wide default for using a physical scale for output OUTPUT: z(t) HISTORY: 2010-09-21 - Written - Bovy (NYU) """ if len(self.vxvv) < 5: raise AttributeError("linear and planar orbits do not have z()") thiso= self(*args,**kwargs) onet= (len(thiso.shape) == 1) if onet: return thiso[3] else: return thiso[3,:] @physical_conversion('velocity') def vz(self,*args,**kwargs): """ NAME: vz PURPOSE: return vertical velocity INPUT: t - (optional) time at which to get the vertical velocity vo= (Object-wide default) physical scale for velocities to use to convert use_physical= use to override Object-wide default for using a physical scale for output OUTPUT: vz(t) HISTORY: 2010-09-21 - Written - Bovy (NYU) """ if len(self.vxvv) < 5: raise AttributeError("linear and planar orbits do not have vz()") thiso= self(*args,**kwargs) onet= (len(thiso.shape) == 1) if onet: return thiso[4] else: return thiso[4,:] @physical_conversion('angle') def phi(self,*args,**kwargs): """ NAME: phi PURPOSE: return azimuth INPUT: t - (optional) time at which to get the azimuth OUTPUT: phi(t) HISTORY: 2010-09-21 - Written - Bovy (NYU) """ if len(self.vxvv) != 4 and len(self.vxvv) != 6: raise AttributeError("orbit must track azimuth to use phi()") thiso= self(*args,**kwargs) onet= (len(thiso.shape) == 1) if onet: return thiso[-1] else: return thiso[-1,:] @physical_conversion('position') def x(self,*args,**kwargs): """ NAME: x PURPOSE: return x INPUT: t - (optional) time at which to get x ro= (Object-wide default) physical scale for distances to use to convert use_physical= use to override Object-wide default for using a physical scale for output OUTPUT: x(t) HISTORY: 2010-09-21 - Written - Bovy (NYU) """ thiso= self(*args,**kwargs) if not len(thiso.shape) == 2: thiso= thiso.reshape((thiso.shape[0],1)) if len(thiso[:,0]) == 2: return thiso[0,:] if len(thiso[:,0]) != 4 and len(thiso[:,0]) != 6: raise AttributeError("orbit must track azimuth to use x()") elif len(thiso[:,0]) == 4: return thiso[0,:]*nu.cos(thiso[3,:]) else: return thiso[0,:]*nu.cos(thiso[5,:]) @physical_conversion('position') def y(self,*args,**kwargs): """ NAME: y PURPOSE: return y INPUT: t - (optional) time at which to get y ro= (Object-wide default) physical scale for distances to use to convert use_physical= use to override Object-wide default for using a physical scale for output OUTPUT: y(t) HISTORY: 2010-09-21 - Written - Bovy (NYU) """ thiso= self(*args,**kwargs) if not len(thiso.shape) == 2: thiso= thiso.reshape((thiso.shape[0],1)) if len(thiso[:,0]) != 4 and len(thiso[:,0]) != 6: raise AttributeError("orbit must track azimuth to use x()") elif len(thiso[:,0]) == 4: return thiso[0,:]*nu.sin(thiso[3,:]) else: return thiso[0,:]*nu.sin(thiso[5,:]) @physical_conversion('velocity') def vx(self,*args,**kwargs): """ NAME: vx PURPOSE: return x velocity at time t INPUT: t - (optional) time at which to get the velocity vo= (Object-wide default) physical scale for velocities to use to convert use_physical= use to override Object-wide default for using a physical scale for output OUTPUT: vx(t) HISTORY: 2010-11-30 - Written - Bovy (NYU) """ thiso= self(*args,**kwargs) if not len(thiso.shape) == 2: thiso= thiso.reshape((thiso.shape[0],1)) if len(thiso[:,0]) == 2: return thiso[1,:] if len(thiso[:,0]) != 4 and len(thiso[:,0]) != 6: raise AttributeError("orbit must track azimuth to use vx()") elif len(thiso[:,0]) == 4: theta= thiso[3,:] else: theta= thiso[5,:] return thiso[1,:]*nu.cos(theta)-thiso[2,:]*nu.sin(theta) @physical_conversion('velocity') def vy(self,*args,**kwargs): """ NAME: vy PURPOSE: return y velocity at time t INPUT: t - (optional) time at which to get the velocity vo= (Object-wide default) physical scale for velocities to use to convert use_physical= use to override Object-wide default for using a physical scale for output OUTPUT: vy(t) HISTORY: 2010-11-30 - Written - Bovy (NYU) """ thiso= self(*args,**kwargs) if not len(thiso.shape) == 2: thiso= thiso.reshape((thiso.shape[0],1)) if len(thiso[:,0]) != 4 and len(thiso[:,0]) != 6: raise AttributeError("orbit must track azimuth to use vx()") elif len(thiso[:,0]) == 4: theta= thiso[3,:] else: theta= thiso[5,:] return thiso[2,:]*nu.cos(theta)+thiso[1,:]*nu.sin(theta) @physical_conversion('velocity') def vphi(self,*args,**kwargs): """ NAME: vphi PURPOSE: return angular velocity INPUT: t - (optional) time at which to get the angular velocity vo= (Object-wide default) physical scale for velocities to use to convert use_physical= use to override Object-wide default for using a physical scale for output OUTPUT: vphi(t) HISTORY: 2010-09-21 - Written - Bovy (NYU) """ thiso= self(*args,**kwargs) if not len(thiso.shape) == 2: thiso= thiso.reshape((thiso.shape[0],1)) return thiso[2,:]/thiso[0,:] @physical_conversion('angle_deg') def ra(self,*args,**kwargs): """ NAME: ra PURPOSE: return the right ascension INPUT: t - (optional) time at which to get ra obs=[X,Y,Z] - (optional) position of observer (in kpc) (default=Object-wide default) OR Orbit object that corresponds to the orbit of the observer Y is ignored and always assumed to be zero ro= distance in kpc corresponding to R=1. (default=Object-wide default) OUTPUT: ra(t) HISTORY: 2011-02-23 - Written - Bovy (NYU) """ _check_roSet(self,kwargs,'ra') radec= self._radec(*args,**kwargs) return radec[:,0] @physical_conversion('angle_deg') def dec(self,*args,**kwargs): """ NAME: dec PURPOSE: return the declination INPUT: t - (optional) time at which to get dec obs=[X,Y,Z] - (optional) position of observer (in kpc) (default=Object-wide default) OR Orbit object that corresponds to the orbit of the observer Y is ignored and always assumed to be zero ro= distance in kpc corresponding to R=1. (default=Object-wide default) OUTPUT: dec(t) HISTORY: 2011-02-23 - Written - Bovy (NYU) """ _check_roSet(self,kwargs,'dec') radec= self._radec(*args,**kwargs) return radec[:,1] @physical_conversion('angle_deg') def ll(self,*args,**kwargs): """ NAME: ll PURPOSE: return Galactic longitude INPUT: t - (optional) time at which to get ll obs=[X,Y,Z] - (optional) position of observer (in kpc) (default=Object-wide default) OR Orbit object that corresponds to the orbit of the observer Y is ignored and always assumed to be zero ro= distance in kpc corresponding to R=1. (default=Object-wide default) OUTPUT: l(t) HISTORY: 2011-02-23 - Written - Bovy (NYU) """ _check_roSet(self,kwargs,'ll') lbd= self._lbd(*args,**kwargs) return lbd[:,0] @physical_conversion('angle_deg') def bb(self,*args,**kwargs): """ NAME: bb PURPOSE: return Galactic latitude INPUT: t - (optional) time at which to get bb obs=[X,Y,Z] - (optional) position of observer (in kpc) (default=Object-wide default) OR Orbit object that corresponds to the orbit of the observer Y is ignored and always assumed to be zero ro= distance in kpc corresponding to R=1. (default=Object-wide default) OUTPUT: b(t) HISTORY: 2011-02-23 - Written - Bovy (NYU) """ _check_roSet(self,kwargs,'bb') lbd= self._lbd(*args,**kwargs) return lbd[:,1] @physical_conversion('position_kpc') def dist(self,*args,**kwargs): """ NAME: dist PURPOSE: return distance from the observer in kpc INPUT: t - (optional) time at which to get dist obs=[X,Y,Z] - (optional) position of observer (in kpc) (default=Object-wide default) OR Orbit object that corresponds to the orbit of the observer Y is ignored and always assumed to be zero ro= distance in kpc corresponding to R=1. (default=Object-wide default) OUTPUT: dist(t) in kpc HISTORY: 2011-02-23 - Written - Bovy (NYU) """ _check_roSet(self,kwargs,'dist') lbd= self._lbd(*args,**kwargs) return lbd[:,2].astype('float64') @physical_conversion('proper-motion_masyr') def pmra(self,*args,**kwargs): """ NAME: pmra PURPOSE: return proper motion in right ascension (in mas/yr) INPUT: t - (optional) time at which to get pmra obs=[X,Y,Z,vx,vy,vz] - (optional) position and velocity of observer (in kpc and km/s) (default=Object-wide default) OR Orbit object that corresponds to the orbit of the observer Y is ignored and always assumed to be zero ro= distance in kpc corresponding to R=1. (default=Object-wide default) vo= velocity in km/s corresponding to v=1. (default=Object-wide default) OUTPUT: pm_ra(t) in mas / yr HISTORY: 2011-02-24 - Written - Bovy (NYU) """ _check_roSet(self,kwargs,'pmra') _check_voSet(self,kwargs,'pmra') pmrapmdec= self._pmrapmdec(*args,**kwargs) return pmrapmdec[:,0] @physical_conversion('proper-motion_masyr') def pmdec(self,*args,**kwargs): """ NAME: pmdec PURPOSE: return proper motion in declination (in mas/yr) INPUT: t - (optional) time at which to get pmdec obs=[X,Y,Z,vx,vy,vz] - (optional) position and velocity of observer (in kpc and km/s) (default=Object-wide default) OR Orbit object that corresponds to the orbit of the observer Y is ignored and always assumed to be zero ro= distance in kpc corresponding to R=1. (default=Object-wide default) vo= velocity in km/s corresponding to v=1. (default=Object-wide default) OUTPUT: pm_dec(t) in mas/yr HISTORY: 2011-02-24 - Written - Bovy (NYU) """ _check_roSet(self,kwargs,'pmdec') _check_voSet(self,kwargs,'pmdec') pmrapmdec= self._pmrapmdec(*args,**kwargs) return pmrapmdec[:,1] @physical_conversion('proper-motion_masyr') def pmll(self,*args,**kwargs): """ NAME: pmll PURPOSE: return proper motion in Galactic longitude (in mas/yr) INPUT: t - (optional) time at which to get pmll obs=[X,Y,Z,vx,vy,vz] - (optional) position and velocity of observer (in kpc and km/s) (default=Object-wide default) OR Orbit object that corresponds to the orbit of the observer Y is ignored and always assumed to be zero ro= distance in kpc corresponding to R=1. (default=Object-wide default) vo= velocity in km/s corresponding to v=1. (default=Object-wide default) OUTPUT: pm_l(t) in mas/yr HISTORY: 2011-02-24 - Written - Bovy (NYU) """ _check_roSet(self,kwargs,'pmll') _check_voSet(self,kwargs,'pmll') lbdvrpmllpmbb= self._lbdvrpmllpmbb(*args,**kwargs) return lbdvrpmllpmbb[:,4] @physical_conversion('proper-motion_masyr') def pmbb(self,*args,**kwargs): """ NAME: pmbb PURPOSE: return proper motion in Galactic latitude (in mas/yr) INPUT: t - (optional) time at which to get pmbb obs=[X,Y,Z,vx,vy,vz] - (optional) position and velocity of observer (in kpc and km/s) (default=Object-wide default) OR Orbit object that corresponds to the orbit of the observer Y is ignored and always assumed to be zero ro= distance in kpc corresponding to R=1. (default=Object-wide default) vo= velocity in km/s corresponding to v=1. (default=Object-wide default) OUTPUT: pm_b(t) in mas/yr HISTORY: 2011-02-24 - Written - Bovy (NYU) """ _check_roSet(self,kwargs,'pmbb') _check_voSet(self,kwargs,'pmbb') lbdvrpmllpmbb= self._lbdvrpmllpmbb(*args,**kwargs) return lbdvrpmllpmbb[:,5] @physical_conversion('velocity_kms') def vlos(self,*args,**kwargs): """ NAME: vlos PURPOSE: return the line-of-sight velocity (in km/s) INPUT: t - (optional) time at which to get vlos obs=[X,Y,Z,vx,vy,vz] - (optional) position and velocity of observer (in kpc and km/s) (default=Object-wide default) OR Orbit object that corresponds to the orbit of the observer Y is ignored and always assumed to be zero ro= distance in kpc corresponding to R=1. (default=Object-wide default) vo= velocity in km/s corresponding to v=1. (default=Object-wide default) OUTPUT: vlos(t) in km/s HISTORY: 2011-02-24 - Written - Bovy (NYU) """ _check_roSet(self,kwargs,'vlos') _check_voSet(self,kwargs,'vlos') lbdvrpmllpmbb= self._lbdvrpmllpmbb(*args,**kwargs) return lbdvrpmllpmbb[:,3] @physical_conversion('position_kpc') def helioX(self,*args,**kwargs): """ NAME: helioX PURPOSE: return Heliocentric Galactic rectangular x-coordinate (aka "X") INPUT: t - (optional) time at which to get X obs=[X,Y,Z] - (optional) position and velocity of observer (in kpc and km/s) (default=Object-wide default) OR Orbit object that corresponds to the orbit of the observer Y is ignored and always assumed to be zero ro= distance in kpc corresponding to R=1. (default=Object-wide default) OUTPUT: helioX(t) in kpc HISTORY: 2011-02-24 - Written - Bovy (NYU) """ _check_roSet(self,kwargs,'helioX') X, Y, Z= self._helioXYZ(*args,**kwargs) return X @physical_conversion('position_kpc') def helioY(self,*args,**kwargs): """ NAME: helioY PURPOSE: return Heliocentric Galactic rectangular y-coordinate (aka "Y") INPUT: t - (optional) time at which to get Y obs=[X,Y,Z] - (optional) position and velocity of observer (in kpc and km/s) (default=Object-wide default) OR Orbit object that corresponds to the orbit of the observer Y is ignored and always assumed to be zero ro= distance in kpc corresponding to R=1. (default=Object-wide default) OUTPUT: helioY(t) in kpc HISTORY: 2011-02-24 - Written - Bovy (NYU) """ _check_roSet(self,kwargs,'helioY') X, Y, Z= self._helioXYZ(*args,**kwargs) return Y @physical_conversion('position_kpc') def helioZ(self,*args,**kwargs): """ NAME: helioZ PURPOSE: return Heliocentric Galactic rectangular z-coordinate (aka "Z") INPUT: t - (optional) time at which to get Z obs=[X,Y,Z] - (optional) position and velocity of observer (in kpc and km/s) (default=Object-wide default) OR Orbit object that corresponds to the orbit of the observer Y is ignored and always assumed to be zero ro= distance in kpc corresponding to R=1. (default=Object-wide default) OUTPUT: helioZ(t) in kpc HISTORY: 2011-02-24 - Written - Bovy (NYU) """ _check_roSet(self,kwargs,'helioZ') X, Y, Z= self._helioXYZ(*args,**kwargs) return Z @physical_conversion('velocity_kms') def U(self,*args,**kwargs): """ NAME: U PURPOSE: return Heliocentric Galactic rectangular x-velocity (aka "U") INPUT: t - (optional) time at which to get U obs=[X,Y,Z,vx,vy,vz] - (optional) position and velocity of observer (in kpc and km/s) (default=Object-wide default) OR Orbit object that corresponds to the orbit of the observer Y is ignored and always assumed to be zero ro= distance in kpc corresponding to R=1. (default=Object-wide default) vo= velocity in km/s corresponding to v=1. (default=Object-wide default) OUTPUT: U(t) in km/s HISTORY: 2011-02-24 - Written - Bovy (NYU) """ _check_roSet(self,kwargs,'U') _check_voSet(self,kwargs,'U') X, Y, Z, U, V, W= self._XYZvxvyvz(*args,**kwargs) return U @physical_conversion('velocity_kms') def V(self,*args,**kwargs): """ NAME: V PURPOSE: return Heliocentric Galactic rectangular y-velocity (aka "V") INPUT: t - (optional) time at which to get U obs=[X,Y,Z,vx,vy,vz] - (optional) position and velocity of observer (in kpc and km/s) (default=Object-wide default) OR Orbit object that corresponds to the orbit of the observer Y is ignored and always assumed to be zero ro= distance in kpc corresponding to R=1. (default=Object-wide default) vo= velocity in km/s corresponding to v=1. (default=Object-wide default) OUTPUT: V(t) in km/s HISTORY: 2011-02-24 - Written - Bovy (NYU) """ _check_roSet(self,kwargs,'V') _check_voSet(self,kwargs,'V') X, Y, Z, U, V, W= self._XYZvxvyvz(*args,**kwargs) return V @physical_conversion('velocity_kms') def W(self,*args,**kwargs): """ NAME: W PURPOSE: return Heliocentric Galactic rectangular z-velocity (aka "W") INPUT: t - (optional) time at which to get W obs=[X,Y,Z,vx,vy,vz] - (optional) position and velocity of observer (in kpc and km/s) (default=Object-wide default) OR Orbit object that corresponds to the orbit of the observer Y is ignored and always assumed to be zero ro= distance in kpc corresponding to R=1. (default=Object-wide default) vo= velocity in km/s corresponding to v=1. (default=Object-wide default) OUTPUT: W(t) in km/s HISTORY: 2011-02-24 - Written - Bovy (NYU) """ _check_roSet(self,kwargs,'W') _check_voSet(self,kwargs,'W') X, Y, Z, U, V, W= self._XYZvxvyvz(*args,**kwargs) return W def SkyCoord(self,*args,**kwargs): """ NAME: SkyCoord PURPOSE: return the position as an astropy SkyCoord INPUT: t - (optional) time at which to get the position obs=[X,Y,Z] - (optional) position of observer (in kpc) (default=Object-wide default) OR Orbit object that corresponds to the orbit of the observer Y is ignored and always assumed to be zero ro= distance in kpc corresponding to R=1. (default=Object-wide default) OUTPUT: SkyCoord(t) HISTORY: 2015-06-02 - Written - Bovy (IAS) """ kwargs.pop('quantity',None) # rm useless keyword to no conflict later _check_roSet(self,kwargs,'SkyCoord') radec= self._radec(*args,**kwargs) tdist= self.dist(quantity=False,*args,**kwargs) return coordinates.SkyCoord(radec[:,0]*units.degree, radec[:,1]*units.degree, distance=tdist*units.kpc, frame='fk5',equinox='J2000') def _radec(self,*args,**kwargs): """Calculate ra and dec""" lbd= self._lbd(*args,**kwargs) return coords.lb_to_radec(lbd[:,0],lbd[:,1],degree=True) def _pmrapmdec(self,*args,**kwargs): """Calculate pmra and pmdec""" lbdvrpmllpmbb= self._lbdvrpmllpmbb(*args,**kwargs) return coords.pmllpmbb_to_pmrapmdec(lbdvrpmllpmbb[:,4], lbdvrpmllpmbb[:,5], lbdvrpmllpmbb[:,0], lbdvrpmllpmbb[:,1],degree=True) def _lbd(self,*args,**kwargs): """Calculate l,b, and d""" obs, ro, vo= self._parse_radec_kwargs(kwargs,dontpop=True) X,Y,Z= self._helioXYZ(*args,**kwargs) bad_indx= (X == 0.)*(Y == 0.)*(Z == 0.) if True in bad_indx: X[bad_indx]+= ro/10000. return coords.XYZ_to_lbd(X,Y,Z,degree=True) def _helioXYZ(self,*args,**kwargs): """Calculate heliocentric rectangular coordinates""" obs, ro, vo= self._parse_radec_kwargs(kwargs) thiso= self(*args,**kwargs) if not len(thiso.shape) == 2: thiso= thiso.reshape((thiso.shape[0],1)) if len(thiso[:,0]) != 4 and len(thiso[:,0]) != 6: #pragma: no cover raise AttributeError("orbit must track azimuth to use radeclbd functions") elif len(thiso[:,0]) == 4: #planarOrbit if isinstance(obs,(nu.ndarray,list)): X,Y,Z = coords.galcencyl_to_XYZ(\ thiso[0,:],thiso[3,:]-nu.arctan2(obs[1],obs[0]),0., Xsun=nu.sqrt(obs[0]**2.+obs[1]**2.)/ro, Zsun=obs[2]/ro).T else: #Orbit instance obs.turn_physical_off() if obs.dim() == 2: X,Y,Z = coords.galcencyl_to_XYZ(\ thiso[0,:],thiso[3,:]-obs.phi(*args,**kwargs), nu.zeros_like(thiso[0]), Xsun=obs.R(*args,**kwargs),Zsun=0.).T else: X,Y,Z = coords.galcencyl_to_XYZ(\ thiso[0,:],thiso[3,:]-obs.phi(*args,**kwargs), nu.zeros_like(thiso[0]), Xsun=obs.R(*args,**kwargs), Zsun=obs.z(*args,**kwargs)).T obs.turn_physical_on() else: #FullOrbit if isinstance(obs,(nu.ndarray,list)): X,Y,Z = coords.galcencyl_to_XYZ(\ thiso[0,:],thiso[5,:]-nu.arctan2(obs[1],obs[0]), thiso[3,:], Xsun=nu.sqrt(obs[0]**2.+obs[1]**2.)/ro, Zsun=obs[2]/ro).T else: #Orbit instance obs.turn_physical_off() if obs.dim() == 2: X,Y,Z = coords.galcencyl_to_XYZ(\ thiso[0,:],thiso[5,:]-obs.phi(*args,**kwargs), thiso[3,:], Xsun=obs.R(*args,**kwargs),Zsun=0.).T else: X,Y,Z = coords.galcencyl_to_XYZ(\ thiso[0,:],thiso[5,:]-obs.phi(*args,**kwargs), thiso[3,:], Xsun=obs.R(*args,**kwargs), Zsun=obs.z(*args,**kwargs)).T obs.turn_physical_on() return (X*ro,Y*ro,Z*ro) def _lbdvrpmllpmbb(self,*args,**kwargs): """Calculate l,b,d,vr,pmll,pmbb""" obs, ro, vo= self._parse_radec_kwargs(kwargs,dontpop=True) X,Y,Z,vX,vY,vZ= self._XYZvxvyvz(*args,**kwargs) bad_indx= (X == 0.)*(Y == 0.)*(Z == 0.) if True in bad_indx: X[bad_indx]+= ro/10000. return coords.rectgal_to_sphergal(X,Y,Z,vX,vY,vZ,degree=True) def _XYZvxvyvz(self,*args,**kwargs): """Calculate X,Y,Z,U,V,W""" obs, ro, vo= self._parse_radec_kwargs(kwargs,vel=True) thiso= self(*args,**kwargs) if not len(thiso.shape) == 2: thiso= thiso.reshape((thiso.shape[0],1)) if len(thiso[:,0]) != 4 and len(thiso[:,0]) != 6: #pragma: no cover raise AttributeError("orbit must track azimuth to use radeclbduvw functions") elif len(thiso[:,0]) == 4: #planarOrbit if isinstance(obs,(nu.ndarray,list)): Xsun= nu.sqrt(obs[0]**2.+obs[1]**2.) X,Y,Z = coords.galcencyl_to_XYZ(\ thiso[0,:],thiso[3,:]-nu.arctan2(obs[1],obs[0]), nu.zeros_like(thiso[0]), Xsun=Xsun/ro,Zsun=obs[2]/ro).T vX,vY,vZ = coords.galcencyl_to_vxvyvz(\ thiso[1,:],thiso[2,:],nu.zeros_like(thiso[0]), thiso[3,:]-nu.arctan2(obs[1],obs[0]), vsun=nu.array(# have to rotate [obs[3]*obs[0]/Xsun+obs[4]*obs[1]/Xsun, -obs[3]*obs[1]/Xsun+obs[4]*obs[0]/Xsun, obs[5]])/vo, Xsun=Xsun/ro,Zsun=obs[2]/ro).T else: #Orbit instance obs.turn_physical_off() if obs.dim() == 2: X,Y,Z = coords.galcencyl_to_XYZ(\ thiso[0,:],thiso[3,:]-obs.phi(*args,**kwargs), nu.zeros_like(thiso[0]), Xsun=obs.R(*args,**kwargs),Zsun=0.).T vX,vY,vZ = coords.galcencyl_to_vxvyvz(\ thiso[1,:],thiso[2,:],nu.zeros_like(thiso[0]), thiso[3,:]-obs.phi(*args,**kwargs), vsun=nu.array([\ obs.vR(*args,**kwargs),obs.vT(*args,**kwargs), 0.]), Xsun=obs.R(*args,**kwargs),Zsun=0.).T else: X,Y,Z = coords.galcencyl_to_XYZ(\ thiso[0,:],thiso[3,:]-obs.phi(*args,**kwargs), nu.zeros_like(thiso[0]), Xsun=obs.R(*args,**kwargs), Zsun=obs.z(*args,**kwargs)).T vX,vY,vZ = coords.galcencyl_to_vxvyvz(\ thiso[1,:],thiso[2,:],nu.zeros_like(thiso[0]), thiso[3,:]-obs.phi(*args,**kwargs), vsun=nu.array([\ obs.vR(*args,**kwargs), obs.vT(*args,**kwargs), obs.vz(*args,**kwargs)]), Xsun=obs.R(*args,**kwargs), Zsun=obs.z(*args,**kwargs)).T obs.turn_physical_on() else: #FullOrbit if isinstance(obs,(nu.ndarray,list)): Xsun= nu.sqrt(obs[0]**2.+obs[1]**2.) X,Y,Z = coords.galcencyl_to_XYZ(\ thiso[0,:],thiso[5,:]-nu.arctan2(obs[1],obs[0]),thiso[3,:], Xsun=Xsun/ro,Zsun=obs[2]/ro).T vX,vY,vZ = coords.galcencyl_to_vxvyvz(\ thiso[1,:],thiso[2,:],thiso[4,:], thiso[5,:]-nu.arctan2(obs[1],obs[0]), vsun=nu.array(# have to rotate [obs[3]*obs[0]/Xsun+obs[4]*obs[1]/Xsun, -obs[3]*obs[1]/Xsun+obs[4]*obs[0]/Xsun, obs[5]])/vo, Xsun=Xsun/ro,Zsun=obs[2]/ro).T else: #Orbit instance obs.turn_physical_off() if obs.dim() == 2: X,Y,Z = coords.galcencyl_to_XYZ(\ thiso[0,:],thiso[5,:]-obs.phi(*args,**kwargs), thiso[3,:], Xsun=obs.R(*args,**kwargs),Zsun=0.).T vX,vY,vZ = coords.galcencyl_to_vxvyvz(\ thiso[1,:],thiso[2,:],thiso[4,:], thiso[5,:]-obs.phi(*args,**kwargs), vsun=nu.array([\ obs.vR(*args,**kwargs),obs.vT(*args,**kwargs), 0.]), Xsun=obs.R(*args,**kwargs),Zsun=0.).T else: X,Y,Z = coords.galcencyl_to_XYZ(\ thiso[0,:],thiso[5,:]-obs.phi(*args,**kwargs), thiso[3,:], Xsun=obs.R(*args,**kwargs), Zsun=obs.z(*args,**kwargs)).T vX,vY,vZ = coords.galcencyl_to_vxvyvz(\ thiso[1,:],thiso[2,:],thiso[4,:], thiso[5,:]-obs.phi(*args,**kwargs), vsun=nu.array([\ obs.vR(*args,**kwargs), obs.vT(*args,**kwargs), obs.vz(*args,**kwargs)]), Xsun=obs.R(*args,**kwargs), Zsun=obs.z(*args,**kwargs)).T obs.turn_physical_on() return (X*ro,Y*ro,Z*ro,vX*vo,vY*vo,vZ*vo) def _parse_radec_kwargs(self,kwargs,vel=False,dontpop=False): if 'obs' in kwargs: obs= kwargs['obs'] if not dontpop: kwargs.pop('obs') if isinstance(obs,(list,nu.ndarray)): if len(obs) == 2: obs= [obs[0],obs[1],0.] elif len(obs) == 4: obs= [obs[0],obs[1],0.,obs[2],obs[3],0.] for ii in range(len(obs)): if _APY_LOADED and isinstance(obs[ii],units.Quantity): if ii < 3: obs[ii]= obs[ii].to(units.kpc).value else: obs[ii]= obs[ii].to(units.km/units.s).value else: if vel: obs= [self._ro,0.,self._zo, self._solarmotion[0],self._solarmotion[1]+self._vo, self._solarmotion[2]] else: obs= [self._ro,0.,self._zo] if 'ro' in kwargs: ro= kwargs['ro'] if _APY_LOADED and isinstance(ro,units.Quantity): ro= ro.to(units.kpc).value if not dontpop: kwargs.pop('ro') else: ro= self._ro if 'vo' in kwargs: vo= kwargs['vo'] if _APY_LOADED and isinstance(vo,units.Quantity): vo= vo.to(units.km/units.s).value if not dontpop: kwargs.pop('vo') else: vo= self._vo return (obs,ro,vo) def Jacobi(self,Omega,t=0.,pot=None): """ NAME: Jacobi PURPOSE: calculate the Jacobi integral E - Omega L INPUT: Omega - pattern speed t= time at which to evaluate the Jacobi integral Pot= potential instance or list of such instances OUTPUT: Jacobi integral HISTORY: 2011-04-18 - Written - Bovy (NYU) """ raise NotImplementedError("'Jacobi' for this Orbit type is not implemented yet") @physical_conversion('action') def L(self,*args,**kwargs): """ NAME: L PURPOSE: calculate the angular momentum INPUT: (none) OUTPUT: angular momentum HISTORY: 2010-09-15 - Written - Bovy (NYU) """ #Make sure you are not using physical coordinates old_physical= kwargs.get('use_physical',None) kwargs['use_physical']= False Omega= kwargs.pop('Omega',None) t= kwargs.pop('t',None) thiso= self(*args,**kwargs) if not len(thiso.shape) == 2: thiso= thiso.reshape((thiso.shape[0],1)) if len(thiso[:,0]) < 3: raise AttributeError("'linearOrbit has no angular momentum") elif len(thiso[:,0]) == 3 or len(thiso[:,0]) == 4: if Omega is None: out= thiso[0,:]*thiso[2,:] else: out= thiso[0,:]*(thiso[2,:]-Omega*t*thiso[0,:]) elif len(thiso[:,0]) == 5: raise AttributeError("You must track the azimuth to get the angular momentum of a 3D Orbit") else: #len(thiso[:,0]) == 6 vx= self.vx(*args,**kwargs) vy= self.vy(*args,**kwargs) vz= self.vz(*args,**kwargs) x= self.x(*args,**kwargs) y= self.y(*args,**kwargs) z= self.z(*args,**kwargs) out= nu.zeros((len(x),3)) out[:,0]= y*vz-z*vy out[:,1]= z*vx-x*vz out[:,2]= x*vy-y*vx if not old_physical is None: kwargs['use_physical']= old_physical else: kwargs.pop('use_physical') return out def _resetaA(self,pot=None,type=None): """ NAME: _resetaA PURPOSE: re-set up an actionAngle module for this Orbit ONLY TO BE CALLED FROM WITHIN SETUPAA INPUT: pot - potential OUTPUT: True if reset happened, False otherwise HISTORY: 2012-06-01 - Written - Bovy (IAS) """ if (not pot is None and pot != self._aAPot) \ or (not type is None and type != self._aAType): delattr(self,'_aA') return True else: pass #Already set up def _setupaA(self,pot=None,type='staeckel',**kwargs): """ NAME: _setupaA PURPOSE: set up an actionAngle module for this Orbit INPUT: pot - potential type= ('staeckel') type of actionAngle module to use 1) 'adiabatic' 2) 'staeckel' 3) 'isochroneApprox' 4) 'spherical' OUTPUT: HISTORY: 2010-11-30 - Written - Bovy (NYU) 2013-11-27 - Re-written in terms of new actionAngle modules - Bovy (IAS) 2017-12-25 - Changed default method to 'staeckel' and automatic delta estimation - Bovy (UofT) """ if hasattr(self,'_aA'): if not self._resetaA(pot=pot,type=type): return None if pot is None: try: pot= self._pot except AttributeError: raise AttributeError("Integrate orbit or specify pot=") self._aAPot= pot self._aAType= type #Setup if self._aAType.lower() == 'adiabatic': self._aA= actionAngle.actionAngleAdiabatic(pot=self._aAPot, **kwargs) elif self._aAType.lower() == 'staeckel': try: delta= \ kwargs.pop('delta', actionAngle.estimateDeltaStaeckel(\ self._aAPot,self.R(use_physical=False), self.z(use_physical=False)+(nu.fabs(self.z(use_physical=False)) < 1e-8) * (2.*(self.z(use_physical=False) >= 0)-1.)*1e-10)) # try to make sure this is not 0 except PotentialError as e: if 'deriv' in str(e): raise PotentialError('Automagic calculation of delta parameter for Staeckel approximation failed because the necessary second derivatives of the given potential are not implemented; set delta= explicitly') elif 'non-axi' in str(e): raise PotentialError('Automagic calculation of delta parameter for Staeckel approximation failed because the given potential is not axisymmetric; pass an axisymmetric potential instead') else: #pragma: no cover raise if delta < 1e-6: self._setupaA(pot=pot,type='spherical') else: self._aA= actionAngle.actionAngleStaeckel(pot=self._aAPot, delta=delta, **kwargs) elif self._aAType.lower() == 'isochroneapprox': from galpy.actionAngle_src.actionAngleIsochroneApprox import actionAngleIsochroneApprox self._aA= actionAngleIsochroneApprox(pot=self._aAPot, **kwargs) elif self._aAType.lower() == 'spherical': self._aA= actionAngle.actionAngleSpherical(pot=self._aAPot, **kwargs) return None def _xw(self,*args,**kwargs): #pragma: no cover """ NAME: xw PURPOSE: return the Fourier transform of xx INPUT: t - (optional) time at which to get xw OUTPUT: xw(t) HISTORY: 2011-01-04 - Written - Bovy (NYU) """ #BOVY: REPLACE WITH CALCULATION FUNCTION x= self.x(self.t) xw= nu.fft.fft(x)#-nu.mean(x)) xw= nu.abs(xw[0:len(xw)/2])*(self.t[1]-self.t[0])/(self.t[-1]-self.t[0]) return xw def _plotxw(self,*args,**kwargs): #pragma: no cover """ NAME: plotxw PURPOSE: plot the spectrum of x INPUT: bovy_plot.bovy_plot args and kwargs OUTPUT: x(t) HISTORY: 2010-09-21 - Written - Bovy (NYU) """ xw= self.xw() #BOVY: CHECK THAT THIS IS CORRECT plot.bovy_plot(2.*m.pi*nu.fft.fftfreq(len(self.t), d=(self.t[1]-self.t[0]))\ [0:len(xw)], xw,*args,**kwargs) def __call__(self,*args,**kwargs): """ NAME: __call__ PURPOSE: return the orbit vector at time t INPUT: t - desired time OUTPUT: [R,vR,vT,z,vz(,phi)] or [R,vR,vT(,phi)] depending on the orbit HISTORY: 2010-07-10 - Written - Bovy (NYU) """ if len(args) == 0: return nu.array(self.vxvv) else: t= args[0] # Parse t if _APY_LOADED and isinstance(t,units.Quantity): t= t.to(units.Gyr).value\ /bovy_conversion.time_in_Gyr(self._vo,self._ro) elif hasattr(self,'_integrate_t_asQuantity') \ and self._integrate_t_asQuantity \ and not nu.all(t == self.t): warnings.warn("You specified integration times as a Quantity, but are evaluating at times not specified as a Quantity; assuming that time given is in natural (internal) units (multiply time by unit to get output at physical time)",galpyWarning) if isinstance(t,(int,float)) and hasattr(self,'t') \ and t in list(self.t): return self.orbit[list(self.t).index(t),:] else: if isinstance(t,(int,float)): nt= 1 t= [t] else: nt= len(t) dim= len(self.vxvv) try: self._setupOrbitInterp() except: out= nu.zeros((dim,nt)) for jj in range(nt): try: indx= list(self.t).index(t[jj]) except ValueError: raise LookupError("Orbit interpolaton failed; integrate on finer grid") for ii in range(dim): out[ii,jj]= self.orbit[indx,ii] return out #should always have nt > 1, bc otherwise covered by above out= [] if _OLD_SCIPY and not isinstance(self._orbInterp[0],_fakeInterp) \ and nu.any((nu.array(t) < self._orbInterp[0]._data[3])\ +(nu.array(t) > self._orbInterp[0]._data[4])): #pragma: no cover raise ValueError("One or more requested time is not within the integrated range") if dim == 4 or dim == 6: #Unpack interpolated x and y to R and phi x= self._orbInterp[0](t) y= self._orbInterp[-1](t) R= nu.sqrt(x*x+y*y) phi= nu.arctan2(y,x) % (2.*nu.pi) for ii in range(dim): if ii == 0: out.append(R) elif ii == dim-1: out.append(phi) else: out.append(self._orbInterp[ii](t)) else: for ii in range(dim): out.append(self._orbInterp[ii](t)) if nt == 1: return nu.array(out).reshape(dim) else: return nu.array(out).reshape((dim,nt)) def plot(self,*args,**kwargs): """ NAME: plot PURPOSE: plot aspects of an Orbit INPUT: bovy_plot args and kwargs ro= (Object-wide default) physical scale for distances to use to convert vo= (Object-wide default) physical scale for velocities to use to convert use_physical= use to override Object-wide default for using a physical scale for output +kwargs for ra,dec,ll,bb, etc. functions OUTPUT: plot HISTORY: 2010-07-26 - Written - Bovy (NYU) 2010-09-22 - Adapted to more general framework - Bovy (NYU) 2013-11-29 - added ra,dec kwargs and other derived quantities - Bovy (IAS) 2014-06-11 - Support for plotting in physical coordinates - Bovy (IAS) 2017-11-28 - Allow arbitrary functions of time to be plotted - Bovy (UofT) """ if (kwargs.get('use_physical',False) \ and kwargs.get('ro',self._roSet)) or \ (not 'use_physical' in kwargs \ and kwargs.get('ro',self._roSet)): labeldict= {'t':r'$t\ (\mathrm{Gyr})$','R':r'$R\ (\mathrm{kpc})$', 'vR':r'$v_R\ (\mathrm{km\,s}^{-1})$', 'vT':r'$v_T\ (\mathrm{km\,s}^{-1})$', 'z':r'$z\ (\mathrm{kpc})$', 'vz':r'$v_z\ (\mathrm{km\,s}^{-1})$','phi':r'$\phi$', 'r':r'$r\ (\mathrm{kpc})$', 'x':r'$x\ (\mathrm{kpc})$','y':r'$y\ (\mathrm{kpc})$', 'vx':r'$v_x\ (\mathrm{km\,s}^{-1})$', 'vy':r'$v_y\ (\mathrm{km\,s}^{-1})$', 'E':r'$E\,(\mathrm{km}^2\,\mathrm{s}^{-2})$', 'Ez':r'$E_z\,(\mathrm{km}^2\,\mathrm{s}^{-2})$', 'ER':r'$E_R\,(\mathrm{km}^2\,\mathrm{s}^{-2})$', 'Enorm':r'$E(t)/E(0.)$', 'Eznorm':r'$E_z(t)/E_z(0.)$', 'ERnorm':r'$E_R(t)/E_R(0.)$', 'Jacobi':r'$E-\Omega_p\,L\,(\mathrm{km}^2\,\mathrm{s}^{-2})$', 'Jacobinorm':r'$(E-\Omega_p\,L)(t)/(E-\Omega_p\,L)(0)$'} else: labeldict= {'t':r'$t$','R':r'$R$','vR':r'$v_R$','vT':r'$v_T$', 'z':r'$z$','vz':r'$v_z$','phi':r'$\phi$', 'r':r'$r$', 'x':r'$x$','y':r'$y$','vx':r'$v_x$','vy':r'$v_y$', 'E':r'$E$','Enorm':r'$E(t)/E(0.)$', 'Ez':r'$E_z$','Eznorm':r'$E_z(t)/E_z(0.)$', 'ER':r'$E_R$','ERnorm':r'$E_R(t)/E_R(0.)$', 'Jacobi':r'$E-\Omega_p\,L$', 'Jacobinorm':r'$(E-\Omega_p\,L)(t)/(E-\Omega_p\,L)(0)$'} labeldict.update({'ra':r'$\alpha\ (\mathrm{deg})$', 'dec':r'$\delta\ (\mathrm{deg})$', 'll':r'$l\ (\mathrm{deg})$', 'bb':r'$b\ (\mathrm{deg})$', 'dist':r'$d\ (\mathrm{kpc})$', 'pmra':r'$\mu_\alpha\ (\mathrm{mas\,yr}^{-1})$', 'pmdec':r'$\mu_\delta\ (\mathrm{mas\,yr}^{-1})$', 'pmll':r'$\mu_l\ (\mathrm{mas\,yr}^{-1})$', 'pmbb':r'$\mu_b\ (\mathrm{mas\,yr}^{-1})$', 'vlos':r'$v_\mathrm{los}\ (\mathrm{km\,s}^{-1})$', 'helioX':r'$X\ (\mathrm{kpc})$', 'helioY':r'$Y\ (\mathrm{kpc})$', 'helioZ':r'$Z\ (\mathrm{kpc})$', 'U':r'$U\ (\mathrm{km\,s}^{-1})$', 'V':r'$V\ (\mathrm{km\,s}^{-1})$', 'W':r'$W\ (\mathrm{km\,s}^{-1})$'}) # Cannot be using Quantity output kwargs['quantity']= False #Defaults if not 'd1' in kwargs and not 'd2' in kwargs: if len(self.vxvv) == 3: d1= 'R' d2= 'vR' elif len(self.vxvv) == 4: d1= 'x' d2= 'y' elif len(self.vxvv) == 2: d1= 'x' d2= 'vx' elif len(self.vxvv) == 5 or len(self.vxvv) == 6: d1= 'R' d2= 'z' elif not 'd1' in kwargs: d2= kwargs.pop('d2') d1= 't' elif not 'd2' in kwargs: d1= kwargs.pop('d1') d2= 't' else: d1= kwargs.pop('d1') d2= kwargs.pop('d2') #Get x and y if callable(d1): x= d1(self.t) elif d1 == 't': x= self.time(self.t,**kwargs) elif d1 == 'R': x= self.R(self.t,**kwargs) elif d1 == 'r': x= nu.sqrt(self.R(self.t,**kwargs)**2. +self.z(self.t,**kwargs)**2.) elif d1 == 'z': x= self.z(self.t,**kwargs) elif d1 == 'vz': x= self.vz(self.t,**kwargs) elif d1 == 'vR': x= self.vR(self.t,**kwargs) elif d1 == 'vT': x= self.vT(self.t,**kwargs) elif d1 == 'x': x= self.x(self.t,**kwargs) elif d1 == 'y': x= self.y(self.t,**kwargs) elif d1 == 'vx': x= self.vx(self.t,**kwargs) elif d1 == 'vy': x= self.vy(self.t,**kwargs) elif d1 == 'phi': x= self.phi(self.t,**kwargs) elif d1.lower() == 'ra': x= self.ra(self.t,**kwargs) elif d1.lower() == 'dec': x= self.dec(self.t,**kwargs) elif d1 == 'll': x= self.ll(self.t,**kwargs) elif d1 == 'bb': x= self.bb(self.t,**kwargs) elif d1 == 'dist': x= self.dist(self.t,**kwargs) elif d1 == 'pmra': x= self.pmra(self.t,**kwargs) elif d1 == 'pmdec': x= self.pmdec(self.t,**kwargs) elif d1 == 'pmll': x= self.pmll(self.t,**kwargs) elif d1 == 'pmbb': x= self.pmbb(self.t,**kwargs) elif d1 == 'vlos': x= self.vlos(self.t,**kwargs) elif d1 == 'helioX': x= self.helioX(self.t,**kwargs) elif d1 == 'helioY': x= self.helioY(self.t,**kwargs) elif d1 == 'helioZ': x= self.helioZ(self.t,**kwargs) elif d1 == 'U': x= self.U(self.t,**kwargs) elif d1 == 'V': x= self.V(self.t,**kwargs) elif d1 == 'W': x= self.W(self.t,**kwargs) elif d1 == 'E': x= self.E(self.t,**kwargs) elif d1 == 'Enorm': x= self.E(self.t,**kwargs)/self.E(0.,**kwargs) elif d1 == 'Ez': x= self.Ez(self.t,**kwargs) elif d1 == 'Eznorm': x= self.Ez(self.t,**kwargs)/self.Ez(0.,**kwargs) elif d1 == 'ER': x= self.ER(self.t,**kwargs) elif d1 == 'ERnorm': x= self.ER(self.t,**kwargs)/self.ER(0.,**kwargs) elif d1 == 'Jacobi': x= self.Jacobi(self.t,**kwargs) elif d1 == 'Jacobinorm': x= self.Jacobi(self.t,**kwargs)/self.Jacobi(0.,**kwargs) if callable(d2): y= d2(self.t) elif d2 == 't': y= self.time(self.t,**kwargs) elif d2 == 'R': y= self.R(self.t,**kwargs) elif d2 == 'r': y= nu.sqrt(self.R(self.t,**kwargs)**2. +self.z(self.t,**kwargs)**2.) elif d2 == 'z': y= self.z(self.t,**kwargs) elif d2 == 'vz': y= self.vz(self.t,**kwargs) elif d2 == 'vR': y= self.vR(self.t,**kwargs) elif d2 == 'vT': y= self.vT(self.t,**kwargs) elif d2 == 'x': y= self.x(self.t,**kwargs) elif d2 == 'y': y= self.y(self.t,**kwargs) elif d2 == 'vx': y= self.vx(self.t,**kwargs) elif d2 == 'vy': y= self.vy(self.t,**kwargs) elif d2 == 'phi': y= self.phi(self.t,**kwargs) elif d2.lower() == 'ra': y= self.ra(self.t,**kwargs) elif d2.lower() == 'dec': y= self.dec(self.t,**kwargs) elif d2 == 'll': y= self.ll(self.t,**kwargs) elif d2 == 'bb': y= self.bb(self.t,**kwargs) elif d2 == 'dist': y= self.dist(self.t,**kwargs) elif d2 == 'pmra': y= self.pmra(self.t,**kwargs) elif d2 == 'pmdec': y= self.pmdec(self.t,**kwargs) elif d2 == 'pmll': y= self.pmll(self.t,**kwargs) elif d2 == 'pmbb': y= self.pmbb(self.t,**kwargs) elif d2 == 'vlos': y= self.vlos(self.t,**kwargs) elif d2 == 'helioX': y= self.helioX(self.t,**kwargs) elif d2 == 'helioY': y= self.helioY(self.t,**kwargs) elif d2 == 'helioZ': y= self.helioZ(self.t,**kwargs) elif d2 == 'U': y= self.U(self.t,**kwargs) elif d2 == 'V': y= self.V(self.t,**kwargs) elif d2 == 'W': y= self.W(self.t,**kwargs) elif d2 == 'E': y= self.E(self.t,**kwargs) elif d2 == 'Enorm': y= self.E(self.t,**kwargs)/self.E(0.,**kwargs) elif d2 == 'Ez': y= self.Ez(self.t,**kwargs) elif d2 == 'Eznorm': y= self.Ez(self.t,**kwargs)/self.Ez(0.,**kwargs) elif d2 == 'ER': y= self.ER(self.t,**kwargs) elif d2 == 'ERnorm': y= self.ER(self.t,**kwargs)/self.ER(0.,**kwargs) elif d2 == 'Jacobi': y= self.Jacobi(self.t,**kwargs) elif d2 == 'Jacobinorm': y= self.Jacobi(self.t,**kwargs)/self.Jacobi(0.,**kwargs) kwargs.pop('ro',None) kwargs.pop('vo',None) kwargs.pop('obs',None) kwargs.pop('use_physical',None) kwargs.pop('pot',None) kwargs.pop('OmegaP',None) kwargs.pop('quantity',None) #Plot if not 'xlabel' in kwargs: kwargs['xlabel']= labeldict.get(d1,'\mathrm{No\ xlabel\ specified}') if not 'ylabel' in kwargs: kwargs['ylabel']= labeldict.get(d2,'\mathrm{No\ ylabel\ specified}') return plot.bovy_plot(x,y,*args,**kwargs) def plot3d(self,*args,**kwargs): """ NAME: plot3d PURPOSE: plot 3D aspects of an Orbit INPUT: ro= (Object-wide default) physical scale for distances to use to convert vo= (Object-wide default) physical scale for velocities to use to convert use_physical= use to override Object-wide default for using a physical scale for output bovy_plot args and kwargs OUTPUT: plot HISTORY: 2010-07-26 - Written - Bovy (NYU) 2010-09-22 - Adapted to more general framework - Bovy (NYU) 2010-01-08 - Adapted to 3D - Bovy (NYU) 2013-11-29 - added ra,dec kwargs and other derived quantities - Bovy (IAS) 2014-06-11 - Support for plotting in physical coordinates - Bovy (IAS) 2017-11-28 - Allow arbitrary functions of time to be plotted - Bovy (UofT) """ if (kwargs.get('use_physical',False) \ and kwargs.get('ro',self._roSet)) or \ (not 'use_physical' in kwargs \ and kwargs.get('ro',self._roSet)): labeldict= {'t':r'$t\ (\mathrm{Gyr})$','R':r'$R\ (\mathrm{kpc})$', 'vR':r'$v_R\ (\mathrm{km\,s}^{-1})$', 'vT':r'$v_T\ (\mathrm{km\,s}^{-1})$', 'z':r'$z\ (\mathrm{kpc})$', 'vz':r'$v_z\ (\mathrm{km\,s}^{-1})$','phi':r'$\phi$', 'r':r'$r\ (\mathrm{kpc})$', 'x':r'$x\ (\mathrm{kpc})$','y':r'$y\ (\mathrm{kpc})$', 'vx':r'$v_x\ (\mathrm{km\,s}^{-1})$', 'vy':r'$v_y\ (\mathrm{km\,s}^{-1})$'} else: labeldict= {'t':r'$t$','R':r'$R$','vR':r'$v_R$','vT':r'$v_T$', 'z':r'$z$','vz':r'$v_z$','phi':r'$\phi$', 'r':r'$r$','x':r'$x$','y':r'$y$', 'vx':r'$v_x$','vy':r'$v_y$'} labeldict.update({'ra':r'$\alpha\ (\mathrm{deg})$', 'dec':r'$\delta\ (\mathrm{deg})$', 'll':r'$l\ (\mathrm{deg})$', 'bb':r'$b\ (\mathrm{deg})$', 'dist':r'$d\ (\mathrm{kpc})$', 'pmra':r'$\mu_\alpha\ (\mathrm{mas\,yr}^{-1})$', 'pmdec':r'$\mu_\delta\ (\mathrm{mas\,yr}^{-1})$', 'pmll':r'$\mu_l\ (\mathrm{mas\,yr}^{-1})$', 'pmbb':r'$\mu_b\ (\mathrm{mas\,yr}^{-1})$', 'vlos':r'$v_\mathrm{los}\ (\mathrm{km\,s}^{-1})$', 'helioX':r'$X\ (\mathrm{kpc})$', 'helioY':r'$Y\ (\mathrm{kpc})$', 'helioZ':r'$Z\ (\mathrm{kpc})$', 'U':r'$U\ (\mathrm{km\,s}^{-1})$', 'V':r'$V\ (\mathrm{km\,s}^{-1})$', 'W':r'$W\ (\mathrm{km\,s}^{-1})$'}) # Cannot be using Quantity output kwargs['quantity']= False #Defaults if not 'd1' in kwargs and not 'd2' in kwargs and not 'd3' in kwargs: if len(self.vxvv) == 3: d1= 'R' d2= 'vR' d3= 'vT' elif len(self.vxvv) == 4: d1= 'x' d2= 'y' d3= 'vR' elif len(self.vxvv) == 2: raise AttributeError("Cannot plot 3D aspects of 1D orbits") elif len(self.vxvv) == 5: d1= 'R' d2= 'vR' d3= 'z' elif len(self.vxvv) == 6: d1= 'x' d2= 'y' d3= 'z' elif not ('d1' in kwargs and 'd2' in kwargs and 'd3' in kwargs): raise AttributeError("Please provide 'd1', 'd2', and 'd3'") else: d1= kwargs.pop('d1') d2= kwargs.pop('d2') d3= kwargs.pop('d3') #Get x, y, and z if callable(d1): x= d1(self.t) elif d1 == 't': x= self.time(self.t,**kwargs) elif d1 == 'R': x= self.R(self.t,**kwargs) elif d1 == 'r': x= nu.sqrt(self.R(self.t,**kwargs)**2. +self.z(self.t,**kwargs)**2.) elif d1 == 'z': x= self.z(self.t,**kwargs) elif d1 == 'vz': x= self.vz(self.t,**kwargs) elif d1 == 'vR': x= self.vR(self.t,**kwargs) elif d1 == 'vT': x= self.vT(self.t,**kwargs) elif d1 == 'x': x= self.x(self.t,**kwargs) elif d1 == 'y': x= self.y(self.t,**kwargs) elif d1 == 'vx': x= self.vx(self.t,**kwargs) elif d1 == 'vy': x= self.vy(self.t,**kwargs) elif d1 == 'phi': x= self.phi(self.t,**kwargs) elif d1.lower() == 'ra': x= self.ra(self.t,**kwargs) elif d1.lower() == 'dec': x= self.dec(self.t,**kwargs) elif d1 == 'll': x= self.ll(self.t,**kwargs) elif d1 == 'bb': x= self.bb(self.t,**kwargs) elif d1 == 'dist': x= self.dist(self.t,**kwargs) elif d1 == 'pmra': x= self.pmra(self.t,**kwargs) elif d1 == 'pmdec': x= self.pmdec(self.t,**kwargs) elif d1 == 'pmll': x= self.pmll(self.t,**kwargs) elif d1 == 'pmbb': x= self.pmbb(self.t,**kwargs) elif d1 == 'vlos': x= self.vlos(self.t,**kwargs) elif d1 == 'helioX': x= self.helioX(self.t,**kwargs) elif d1 == 'helioY': x= self.helioY(self.t,**kwargs) elif d1 == 'helioZ': x= self.helioZ(self.t,**kwargs) elif d1 == 'U': x= self.U(self.t,**kwargs) elif d1 == 'V': x= self.V(self.t,**kwargs) elif d1 == 'W': x= self.W(self.t,**kwargs) if callable(d2): y= d2(self.t) elif d2 == 't': y= self.time(self.t,**kwargs) elif d2 == 'R': y= self.R(self.t,**kwargs) elif d2 == 'r': y= nu.sqrt(self.R(self.t,**kwargs)**2. +self.z(self.t,**kwargs)**2.) elif d2 == 'z': y= self.z(self.t,**kwargs) elif d2 == 'vz': y= self.vz(self.t,**kwargs) elif d2 == 'vR': y= self.vR(self.t,**kwargs) elif d2 == 'vT': y= self.vT(self.t,**kwargs) elif d2 == 'x': y= self.x(self.t,**kwargs) elif d2 == 'y': y= self.y(self.t,**kwargs) elif d2 == 'vx': y= self.vx(self.t,**kwargs) elif d2 == 'vy': y= self.vy(self.t,**kwargs) elif d2 == 'phi': y= self.phi(self.t,**kwargs) elif d2.lower() == 'ra': y= self.ra(self.t,**kwargs) elif d2.lower() == 'dec': y= self.dec(self.t,**kwargs) elif d2 == 'll': y= self.ll(self.t,**kwargs) elif d2 == 'bb': y= self.bb(self.t,**kwargs) elif d2 == 'dist': y= self.dist(self.t,**kwargs) elif d2 == 'pmra': y= self.pmra(self.t,**kwargs) elif d2 == 'pmdec': y= self.pmdec(self.t,**kwargs) elif d2 == 'pmll': y= self.pmll(self.t,**kwargs) elif d2 == 'pmbb': y= self.pmbb(self.t,**kwargs) elif d2 == 'vlos': y= self.vlos(self.t,**kwargs) elif d2 == 'helioX': y= self.helioX(self.t,**kwargs) elif d2 == 'helioY': y= self.helioY(self.t,**kwargs) elif d2 == 'helioZ': y= self.helioZ(self.t,**kwargs) elif d2 == 'U': y= self.U(self.t,**kwargs) elif d2 == 'V': y= self.V(self.t,**kwargs) elif d2 == 'W': y= self.W(self.t,**kwargs) if callable(d3): z= d3(self.t) elif d3 == 't': z= self.time(self.t,**kwargs) elif d3 == 'R': z= self.R(self.t,**kwargs) elif d3 == 'r': z= nu.sqrt(self.R(self.t,**kwargs)**2. +self.z(self.t,**kwargs)**2.) elif d3 == 'z': z= self.z(self.t,**kwargs) elif d3 == 'vz': z= self.vz(self.t,**kwargs) elif d3 == 'vR': z= self.vR(self.t,**kwargs) elif d3 == 'vT': z= self.vT(self.t,**kwargs) elif d3 == 'x': z= self.x(self.t,**kwargs) elif d3 == 'y': z= self.y(self.t,**kwargs) elif d3 == 'vx': z= self.vx(self.t,**kwargs) elif d3 == 'vy': z= self.vy(self.t,**kwargs) elif d3 == 'phi': z= self.phi(self.t,**kwargs) elif d3.lower() == 'ra': z= self.ra(self.t,**kwargs) elif d3.lower() == 'dec': z= self.dec(self.t,**kwargs) elif d3 == 'll': z= self.ll(self.t,**kwargs) elif d3 == 'bb': z= self.bb(self.t,**kwargs) elif d3 == 'dist': z= self.dist(self.t,**kwargs) elif d3 == 'pmra': z= self.pmra(self.t,**kwargs) elif d3 == 'pmdec': z= self.pmdec(self.t,**kwargs) elif d3 == 'pmll': z= self.pmll(self.t,**kwargs) elif d3 == 'pmbb': z= self.pmbb(self.t,**kwargs) elif d3 == 'vlos': z= self.vlos(self.t,**kwargs) elif d3 == 'helioX': z= self.helioX(self.t,**kwargs) elif d3 == 'helioY': z= self.helioY(self.t,**kwargs) elif d3 == 'helioZ': z= self.helioZ(self.t,**kwargs) elif d3 == 'U': z= self.U(self.t,**kwargs) elif d3 == 'V': z= self.V(self.t,**kwargs) elif d3 == 'W': z= self.W(self.t,**kwargs) kwargs.pop('ro',None) kwargs.pop('vo',None) kwargs.pop('obs',None) kwargs.pop('use_physical',None) kwargs.pop('quantity',None) #Plot if not 'xlabel' in kwargs: kwargs['xlabel']= labeldict.get(d1,'\mathrm{No\ xlabel\ specified}') if not 'ylabel' in kwargs: kwargs['ylabel']= labeldict.get(d2,'\mathrm{No\ ylabel\ specified}') if not 'zlabel' in kwargs: kwargs['zlabel']= labeldict.get(d3,'\mathrm{No\ zlabel\ specified}') return plot.bovy_plot3d(x,y,z,*args,**kwargs) def plotR(self,*args,**kwargs): """ NAME: plotR PURPOSE: plot R(.) along the orbit INPUT: bovy_plot.bovy_plot inputs OUTPUT: figure to output device HISTORY: 2010-07-10 - Written - Bovy (NYU) """ kwargs['d2']= 'R' return self.plot(*args,**kwargs) def plotz(self,*args,**kwargs): """ NAME: plotz PURPOSE: plot z(.) along the orbit INPUT: bovy_plot.bovy_plot inputs OUTPUT: figure to output device HISTORY: 2010-07-10 - Written - Bovy (NYU) """ kwargs['d2']= 'z' return self.plot(*args,**kwargs) def plotx(self,*args,**kwargs): """ NAME: plotx PURPOSE: plot x(.) along the orbit INPUT: bovy_plot.bovy_plot inputs OUTPUT: figure to output device HISTORY: 2010-07-10 - Written - Bovy (NYU) """ kwargs['d2']= 'x' return self.plot(*args,**kwargs) def plotvx(self,*args,**kwargs): """ NAME: plotvx PURPOSE: plot vx(.) along the orbit INPUT: bovy_plot.bovy_plot inputs OUTPUT: figure to output device HISTORY: 2010-07-10 - Written - Bovy (NYU) """ kwargs['d2']= 'vx' return self.plot(*args,**kwargs) def ploty(self,*args,**kwargs): """ NAME: ploty PURPOSE: plot y(.) along the orbit INPUT: bovy_plot.bovy_plot inputs OUTPUT: figure to output device HISTORY: 2010-07-10 - Written - Bovy (NYU) """ kwargs['d2']= 'y' return self.plot(*args,**kwargs) def plotvy(self,*args,**kwargs): """ NAME: plotvy PURPOSE: plot vy(.) along the orbit INPUT: bovy_plot.bovy_plot inputs OUTPUT: figure to output device HISTORY: 2010-07-10 - Written - Bovy (NYU) """ kwargs['d2']= 'vy' return self.plot(*args,**kwargs) def plotvR(self,*args,**kwargs): """ NAME: plotvR PURPOSE: plot vR(.) along the orbit INPUT: bovy_plot.bovy_plot inputs OUTPUT: figure to output device HISTORY: 2010-07-10 - Written - Bovy (NYU) """ kwargs['d2']= 'vR' return self.plot(*args,**kwargs) def plotvT(self,*args,**kwargs): """ NAME: plotvT PURPOSE: plot vT(.) along the orbit INPUT: bovy_plot.bovy_plot inputs OUTPUT: figure to output device HISTORY: 2010-07-10 - Written - Bovy (NYU) """ kwargs['d2']= 'vT' return self.plot(*args,**kwargs) def plotphi(self,*args,**kwargs): """ NAME: plotphi PURPOSE: plot \phi(.) along the orbit INPUT: bovy_plot.bovy_plot inputs OUTPUT: figure to output device HISTORY: 2010-07-10 - Written - Bovy (NYU) """ kwargs['d2']= 'phi' return self.plot(*args,**kwargs) def plotvz(self,*args,**kwargs): """ NAME: plotvz PURPOSE: plot vz(.) along the orbit INPUT: bovy_plot.bovy_plot inputs OUTPUT: figure to output device HISTORY: 2010-07-10 - Written - Bovy (NYU) """ kwargs['d2']= 'vz' return self.plot(*args,**kwargs) def plotE(self,*args,**kwargs): """ NAME: plotE PURPOSE: plot E(.) along the orbit INPUT: bovy_plot.bovy_plot inputs OUTPUT: figure to output device HISTORY: 2014-06-16 - Written - Bovy (IAS) """ if kwargs.pop('normed',False): kwargs['d2']= 'Enorm' else: kwargs['d2']= 'E' return self.plot(*args,**kwargs) def plotJacobi(self,*args,**kwargs): """ NAME: plotE PURPOSE: plot Jacobi(.) along the orbit INPUT: bovy_plot.bovy_plot inputs OUTPUT: figure to output device HISTORY: 2014-06-16 - Written - Bovy (IAS) """ if kwargs.pop('normed',False): kwargs['d2']= 'Jacobinorm' else: kwargs['d2']= 'Jacobi' return self.plot(*args,**kwargs) def _setupOrbitInterp(self): if not hasattr(self,"_orbInterp"): # First check that times increase if hasattr(self,"t"): #Orbit has been integrated if self.t[1] < self.t[0]: #must be backward sindx= nu.argsort(self.t) # sort self.t= self.t[sindx] self.orbit= self.orbit[sindx] usindx= nu.argsort(sindx) # to later unsort orbInterp= [] for ii in range(len(self.vxvv)): if (len(self.vxvv) == 4 or len(self.vxvv) == 6) and ii == 0: #Interpolate x and y rather than R and phi to avoid issues w/ phase wrapping if not hasattr(self,"t"): #Orbit has not been integrated orbInterp.append(_fakeInterp(self.vxvv[0]*nu.cos(self.vxvv[-1]))) else: orbInterp.append(interpolate.InterpolatedUnivariateSpline(\ self.t,self.orbit[:,0]*nu.cos(self.orbit[:,-1]), **_KWINTERP)) elif (len(self.vxvv) == 4 or len(self.vxvv) == 6) and \ ii == len(self.vxvv)-1: if not hasattr(self,"t"): #Orbit has not been integrated orbInterp.append(_fakeInterp(self.vxvv[0]*nu.sin(self.vxvv[-1]))) else: orbInterp.append(interpolate.InterpolatedUnivariateSpline(\ self.t,self.orbit[:,0]*nu.sin(self.orbit[:,-1]),**_KWINTERP)) else: if not hasattr(self,"t"): #Orbit has not been integrated orbInterp.append(_fakeInterp(self.vxvv[ii])) else: orbInterp.append(interpolate.InterpolatedUnivariateSpline(\ self.t,self.orbit[:,ii],**_KWINTERP)) self._orbInterp= orbInterp try: #unsort self.t= self.t[usindx] self.orbit= self.orbit[usindx] except: pass return None def animate(self,*args,**kwargs): #pragma: no cover """ NAME: animate PURPOSE: animate an Orbit INPUT: d1= first dimension to plot ('x', 'y', 'R', 'vR', 'vT', 'z', 'vz', ...); can be list with up to three entries for three subplots d2= second dimension to plot; can be list with up to three entries for three subplots width= (600) width of output div in px height= (400) height of output div in px json_filename= (None) if set, save the data necessary for the figure in this filename (e.g., json_filename= 'orbit_data/orbit.json'); this path is also used in the output HTML, so needs to be accessible ro= (Object-wide default) physical scale for distances to use to convert vo= (Object-wide default) physical scale for velocities to use to convert use_physical= use to override Object-wide default for using a physical scale for output +kwargs for ra,dec,ll,bb, etc. functions OUTPUT: IPython.display.HTML object with code to animate the orbit; can be directly shown in jupyter notebook or embedded in HTML pages; get a text version of the HTML using the _repr_html_() function HISTORY: 2017-09-17-24 - Written - Bovy (UofT) 2017-11-28 - Allow arbitrary functions of time to be plotted - Bovy (UofT) """ try: from IPython.display import HTML except ImportError: raise ImportError("Orbit.animate requires ipython/jupyter to be installed") if (kwargs.get('use_physical',False) \ and kwargs.get('ro',self._roSet)) or \ (not 'use_physical' in kwargs \ and kwargs.get('ro',self._roSet)): labeldict= {'t':'t (Gyr)', 'R':'R (kpc)', 'vR':'v_R (km/s)', 'vT':'v_T (km/s)', 'z':'z (kpc)', 'vz':'v_z (km/s)', 'phi':'azimuthal angle', 'r':'r (kpc)', 'x':'x (kpc)', 'y':'y (kpc)', 'vx':'v_x (km/s)', 'vy':'v_y (km/s)', 'E':'E (km^2/s^2)', 'Ez':'E_z (km^2/s^2)', 'ER':'E_R (km^2/s^2)', 'Enorm':'E(t)/E(0.)', 'Eznorm':'E_z(t)/E_z(0.)', 'ERnorm':'E_R(t)/E_R(0.)', 'Jacobi':'E-Omega_p L (km^2/s^2)', 'Jacobinorm':'(E-Omega_p L)(t)/(E-Omega_p L)(0)'} else: labeldict= {'t':'t','R':'R','vR':'v_R','vT':'v_T', 'z':'z','vz':'v_z','phi':r'azimuthal angle', 'r':'r', 'x':'x','y':'y','vx':'v_x','vy':'v_y', 'E':'E','Enorm':'E(t)/E(0.)', 'Ez':'E_z','Eznorm':'E_z(t)/E_z(0.)', 'ER':r'E_R','ERnorm':r'E_R(t)/E_R(0.)', 'Jacobi':r'E-Omega_p L', 'Jacobinorm':r'(E-Omega_p L)(t)/(E-Omega_p L)(0)'} labeldict.update({'ra':'RA (deg)', 'dec':'Dec (deg)', 'll':'Galactic lon (deg)', 'bb':'Galactic lat (deg)', 'dist':'distance (kpc)', 'pmra':'pmRA (mas/yr)', 'pmdec':'pmDec (mas/yr)', 'pmll':'pmGlon (mas/yr)', 'pmbb':'pmGlat (mas/yr)', 'vlos':'line-of-sight vel (km/s)', 'helioX':'X (kpc)', 'helioY':'Y (kpc)', 'helioZ':'Z (kpc)', 'U':'U (km/s)', 'V':'V (km/s)', 'W':'W (km/s)'}) # Cannot be using Quantity output kwargs['quantity']= False #Defaults if not 'd1' in kwargs and not 'd2' in kwargs: if len(self.vxvv) == 3: d1= 'R' d2= 'vR' elif len(self.vxvv) == 4: d1= 'x' d2= 'y' elif len(self.vxvv) == 2: d1= 'x' d2= 'vx' elif len(self.vxvv) == 5 or len(self.vxvv) == 6: d1= 'R' d2= 'z' elif not 'd1' in kwargs: d2= kwargs.pop('d2') d1= 't' elif not 'd2' in kwargs: d1= kwargs.pop('d1') d2= 't' else: d1= kwargs.pop('d1') d2= kwargs.pop('d2') xs= [] ys= [] xlabels= [] ylabels= [] if isinstance(d1,str) or callable(d1): d1s= [d1] d2s= [d2] else: d1s= d1 d2s= d2 if len(d1s) > 3: raise ValueError('Orbit.animate only works for up to three subplots') all_xlabel= kwargs.get('xlabel',[None for d in d1]) all_ylabel= kwargs.get('ylabel',[None for d in d2]) for d1,d2, xlabel, ylabel in zip(d1s,d2s,all_xlabel,all_ylabel): #Get x and y for each subplot if callable(d1): x= d1(self.t) elif d1 == 't': x= self.time(self.t,**kwargs) elif d1 == 'R': x= self.R(self.t,**kwargs) elif d1 == 'r': x= nu.sqrt(self.R(self.t,**kwargs)**2. +self.z(self.t,**kwargs)**2.) elif d1 == 'z': x= self.z(self.t,**kwargs) elif d1 == 'vz': x= self.vz(self.t,**kwargs) elif d1 == 'vR': x= self.vR(self.t,**kwargs) elif d1 == 'vT': x= self.vT(self.t,**kwargs) elif d1 == 'x': x= self.x(self.t,**kwargs) elif d1 == 'y': x= self.y(self.t,**kwargs) elif d1 == 'vx': x= self.vx(self.t,**kwargs) elif d1 == 'vy': x= self.vy(self.t,**kwargs) elif d1 == 'phi': x= self.phi(self.t,**kwargs) elif d1.lower() == 'ra': x= self.ra(self.t,**kwargs) elif d1.lower() == 'dec': x= self.dec(self.t,**kwargs) elif d1 == 'll': x= self.ll(self.t,**kwargs) elif d1 == 'bb': x= self.bb(self.t,**kwargs) elif d1 == 'dist': x= self.dist(self.t,**kwargs) elif d1 == 'pmra': x= self.pmra(self.t,**kwargs) elif d1 == 'pmdec': x= self.pmdec(self.t,**kwargs) elif d1 == 'pmll': x= self.pmll(self.t,**kwargs) elif d1 == 'pmbb': x= self.pmbb(self.t,**kwargs) elif d1 == 'vlos': x= self.vlos(self.t,**kwargs) elif d1 == 'helioX': x= self.helioX(self.t,**kwargs) elif d1 == 'helioY': x= self.helioY(self.t,**kwargs) elif d1 == 'helioZ': x= self.helioZ(self.t,**kwargs) elif d1 == 'U': x= self.U(self.t,**kwargs) elif d1 == 'V': x= self.V(self.t,**kwargs) elif d1 == 'W': x= self.W(self.t,**kwargs) elif d1 == 'E': x= self.E(self.t,**kwargs) elif d1 == 'Enorm': x= self.E(self.t,**kwargs)/self.E(0.,**kwargs) elif d1 == 'Ez': x= self.Ez(self.t,**kwargs) elif d1 == 'Eznorm': x= self.Ez(self.t,**kwargs)/self.Ez(0.,**kwargs) elif d1 == 'ER': x= self.ER(self.t,**kwargs) elif d1 == 'ERnorm': x= self.ER(self.t,**kwargs)/self.ER(0.,**kwargs) elif d1 == 'Jacobi': x= self.Jacobi(self.t,**kwargs) elif d1 == 'Jacobinorm': x= self.Jacobi(self.t,**kwargs)/self.Jacobi(0.,**kwargs) if callable(d2): y= d2(self.t) elif d2 == 't': y= self.time(self.t,**kwargs) elif d2 == 'R': y= self.R(self.t,**kwargs) elif d2 == 'r': y= nu.sqrt(self.R(self.t,**kwargs)**2. +self.z(self.t,**kwargs)**2.) elif d2 == 'z': y= self.z(self.t,**kwargs) elif d2 == 'vz': y= self.vz(self.t,**kwargs) elif d2 == 'vR': y= self.vR(self.t,**kwargs) elif d2 == 'vT': y= self.vT(self.t,**kwargs) elif d2 == 'x': y= self.x(self.t,**kwargs) elif d2 == 'y': y= self.y(self.t,**kwargs) elif d2 == 'vx': y= self.vx(self.t,**kwargs) elif d2 == 'vy': y= self.vy(self.t,**kwargs) elif d2 == 'phi': y= self.phi(self.t,**kwargs) elif d2.lower() == 'ra': y= self.ra(self.t,**kwargs) elif d2.lower() == 'dec': y= self.dec(self.t,**kwargs) elif d2 == 'll': y= self.ll(self.t,**kwargs) elif d2 == 'bb': y= self.bb(self.t,**kwargs) elif d2 == 'dist': y= self.dist(self.t,**kwargs) elif d2 == 'pmra': y= self.pmra(self.t,**kwargs) elif d2 == 'pmdec': y= self.pmdec(self.t,**kwargs) elif d2 == 'pmll': y= self.pmll(self.t,**kwargs) elif d2 == 'pmbb': y= self.pmbb(self.t,**kwargs) elif d2 == 'vlos': y= self.vlos(self.t,**kwargs) elif d2 == 'helioX': y= self.helioX(self.t,**kwargs) elif d2 == 'helioY': y= self.helioY(self.t,**kwargs) elif d2 == 'helioZ': y= self.helioZ(self.t,**kwargs) elif d2 == 'U': y= self.U(self.t,**kwargs) elif d2 == 'V': y= self.V(self.t,**kwargs) elif d2 == 'W': y= self.W(self.t,**kwargs) elif d2 == 'E': y= self.E(self.t,**kwargs) elif d2 == 'Enorm': y= self.E(self.t,**kwargs)/self.E(0.,**kwargs) elif d2 == 'Ez': y= self.Ez(self.t,**kwargs) elif d2 == 'Eznorm': y= self.Ez(self.t,**kwargs)/self.Ez(0.,**kwargs) elif d2 == 'ER': y= self.ER(self.t,**kwargs) elif d2 == 'ERnorm': y= self.ER(self.t,**kwargs)/self.ER(0.,**kwargs) elif d2 == 'Jacobi': y= self.Jacobi(self.t,**kwargs) elif d2 == 'Jacobinorm': y= self.Jacobi(self.t,**kwargs)/self.Jacobi(0.,**kwargs) xs.append(x) ys.append(y) if xlabel is None: xlabels.append(labeldict.get(d1,'\mathrm{No\ xlabel\ specified}')) else: xlabels.append(xlabel) if ylabel is None: ylabels.append(labeldict.get(d2,'\mathrm{No\ ylabel\ specified}')) else: ylabels.append(ylabel) kwargs.pop('ro',None) kwargs.pop('vo',None) kwargs.pop('obs',None) kwargs.pop('use_physical',None) kwargs.pop('pot',None) kwargs.pop('OmegaP',None) kwargs.pop('quantity',None) width= kwargs.pop('width',600) height= kwargs.pop('height',400) # Dump data to HTML nplots= len(xs) jsonDict= {} jsonDict['x']= xs[0].tolist() jsonDict['y']= ys[0].tolist() for ii in range(1,nplots): jsonDict['x%i' % (ii+1)]= xs[ii].tolist() jsonDict['y%i' % (ii+1)]= ys[ii].tolist() json_filename= kwargs.pop('json_filename',None) if json_filename is None: jd= json.dumps(jsonDict) json_code= """ let data= JSON.parse('{jd}');""".format(jd=jd) close_json_code= "" else: with open(json_filename,'w') as jfile: json.dump(jsonDict,jfile) json_code= """Plotly.d3.json('{jfilename}',function(data){{""".format(jfilename=json_filename) close_json_code= "});" self.divid= 'galpy-'\ +''.join(choice(ascii_lowercase) for i in range(24)) button_width= 419.51+4.*10. button_margin_left= int(nu.round((width-button_width)/2.)) if button_margin_left < 0: button_margin_left= 0 # Layout for multiple plots if len(d1s) == 1: xmin= [0,0,0] xmax= [1,1,1] elif len(d1s) == 2: xmin= [0,0.55,0] xmax= [0.45,1,1] elif len(d1s) == 3: xmin= [0,0.365,0.73] xmax= [0.27,0.635,1] layout= """{{ xaxis: {{ title: '{xlabel}', domain: [{xmin},{xmax}], }}, yaxis: {{title: '{ylabel}'}}, margin: {{t: 20}}, hovermode: 'closest', showlegend: false, """.format(xlabel=xlabels[0],ylabel=ylabels[0],xmin=xmin[0],xmax=xmax[0]) for ii in range(1,nplots): layout+= """ xaxis{idx}: {{ title: '{xlabel}', anchor: 'y{idx}', domain: [{xmin},{xmax}], }}, yaxis{idx}: {{ title: '{ylabel}', anchor: 'x{idx}', }}, """.format(idx=ii+1,xlabel=xlabels[ii],ylabel=ylabels[ii], xmin=xmin[ii],xmax=xmax[ii]) layout+="""}""" # Additional traces for additional plots if len(d1s) > 1: setup_trace2= """ let trace3= {{ x: data.x2.slice(0,numPerFrame), y: data.y2.slice(0,numPerFrame), xaxis: 'x2', yaxis: 'y2', mode: 'lines', line: {{ shape: 'spline', width: 0.8, color: '#1f77b4', }}, }}; let trace4= {{ x: data.x2.slice(0,numPerFrame), y: data.y2.slice(0,numPerFrame), xaxis: 'x2', yaxis: 'y2', mode: 'lines', line: {{ shape: 'spline', width: 3., color: '#d62728', }}, }}; """.format(divid=self.divid) # not used! delete_trace4= """Plotly.deleteTraces('{divid}',3);""".format(divid=self.divid) delete_trace3= """Plotly.deleteTraces('{divid}',0);""".format(divid=self.divid) update_trace34= """ trace_slice_begin+= trace_slice_len; Plotly.extendTraces('{divid}', {{ x: [data.x2.slice(trace_slice_begin,trace_slice_end)], y: [data.y2.slice(trace_slice_begin,trace_slice_end)], }}, [2]); trace_slice_begin-= trace_slice_len; trace4= {{ x: [data.x2.slice(trace_slice_begin,trace_slice_end)], y: [data.y2.slice(trace_slice_begin,trace_slice_end)], }}, Plotly.restyle('{divid}',trace4,[3]); """.format(divid=self.divid) else: setup_trace2= """ let traces= [trace1,trace2]; """ delete_trace4= "" delete_trace3= "" update_trace34= "" if len(d1s) > 2: setup_trace3= """ let trace5= {{ x: data.x3.slice(0,numPerFrame), y: data.y3.slice(0,numPerFrame), xaxis: 'x3', yaxis: 'y3', mode: 'lines', line: {{ shape: 'spline', width: 0.8, color: '#1f77b4', }}, }}; let trace6= {{ x: data.x3.slice(0,numPerFrame), y: data.y3.slice(0,numPerFrame), xaxis: 'x3', yaxis: 'y3', mode: 'lines', line: {{ shape: 'spline', width: 3., color: '#d62728', }}, }}; let traces= [trace1,trace2,trace3,trace4,trace5,trace6]; """.format(divid=self.divid) delete_trace6= """Plotly.deleteTraces('{divid}',5);""".format(divid=self.divid) delete_trace5= """Plotly.deleteTraces('{divid}',0);""".format(divid=self.divid) update_trace56= """ trace_slice_begin+= trace_slice_len; Plotly.extendTraces('{divid}', {{ x: [data.x3.slice(trace_slice_begin,trace_slice_end)], y: [data.y3.slice(trace_slice_begin,trace_slice_end)], }}, [4]); trace_slice_begin-= trace_slice_len; trace6= {{ x: [data.x3.slice(trace_slice_begin,trace_slice_end)], y: [data.y3.slice(trace_slice_begin,trace_slice_end)], }}, Plotly.restyle('{divid}',trace6,[5]); """.format(divid=self.divid) elif len(d1s) > 1: setup_trace3= """ let traces= [trace1,trace2,trace3,trace4]; """ delete_trace5= "" delete_trace6= "" update_trace56= "" else: setup_trace3= "" delete_trace5= "" delete_trace6= "" update_trace56= "" return HTML("""
""".format(json_code=json_code,close_json_code=close_json_code, divid=self.divid,width=width,height=height, button_margin_left=button_margin_left, layout=layout, setup_trace2=setup_trace2,setup_trace3=setup_trace3, delete_trace4=delete_trace4,delete_trace6=delete_trace6, delete_trace3=delete_trace3,delete_trace5=delete_trace5, update_trace34=update_trace34, update_trace56=update_trace56)) class _fakeInterp(object): """Fake class to simulate interpolation when orbit was not integrated""" def __init__(self,x): self.x= x def __call__(self,t): if nu.any(nu.array(t) != 0.): raise ValueError("Integrate instance before evaluating it at non-zero time") else: return nu.array([self.x for i in t]) def _check_roSet(orb,kwargs,funcName): """Function to check whether ro is set, because it's required for funcName""" if not orb._roSet and kwargs.get('ro',None) is None: warnings.warn("Method %s(.) requires ro to be given at Orbit initialization or at method evaluation; using default ro which is %f kpc" % (funcName,orb._ro), galpyWarning) def _check_voSet(orb,kwargs,funcName): """Function to check whether vo is set, because it's required for funcName""" if not orb._voSet and kwargs.get('vo',None) is None: warnings.warn("Method %s(.) requires vo to be given at Orbit initialization or at method evaluation; using default vo which is %f km/s" % (funcName,orb._vo), galpyWarning) galpy-1.3.0/galpy/orbit_src/planarOrbit.py0000644000076500000240000007426613231751437020707 0ustar bovystaff00000000000000import math as m import warnings import numpy as nu from scipy import integrate import galpy.util.bovy_symplecticode as symplecticode from galpy.util.bovy_conversion import physical_conversion from galpy.orbit_src.OrbitTop import OrbitTop from galpy.potential_src.planarPotential import _evaluateplanarRforces,\ RZToplanarPotential, toPlanarPotential, _evaluateplanarphiforces,\ _evaluateplanarPotentials from galpy.potential_src.Potential import Potential, _check_c from galpy.util import galpyWarning #try: from galpy.orbit_src.integratePlanarOrbit import integratePlanarOrbit_c,\ integratePlanarOrbit_dxdv_c, _ext_loaded ext_loaded= _ext_loaded class planarOrbitTop(OrbitTop): """Top-level class representing a planar orbit (i.e., one in the plane of a galaxy)""" def __init__(self,vxvv=None,vo=220.,ro=8.0,zo=0.025, solarmotion=nu.array([-10.1,4.0,6.7])): #pragma: no cover (never used) """ NAME: __init__ PURPOSE: Initialize a planar orbit INPUT: vxvv - [R,vR,vT(,phi)] vo - circular velocity at ro (km/s) ro - distance from vantage point to GC (kpc) zo - offset toward the NGP of the Sun wrt the plane (kpc) solarmotion - value in [-U,V,W] (km/s) OUTPUT: HISTORY: 2010-07-12 - Written - Bovy (NYU) 2014-06-11 - Added conversion kwargs to physical coordinates - Bovy (IAS) """ OrbitTop.__init__(self,vxvv=vxvv, ro=ro,zo=zo,vo=vo,solarmotion=solarmotion) return None def e(self,analytic=False,pot=None): """ NAME: e PURPOSE: calculate the eccentricity INPUT: analytic - compute this analytically pot - potential to use for analytical calculation OUTPUT: eccentricity HISTORY: 2010-09-15 - Written - Bovy (NYU) """ if analytic: self._setupaA(pot=pot,type='adiabatic') (rperi,rap)= self._aA.calcRapRperi(self) return (rap-rperi)/(rap+rperi) if not hasattr(self,'orbit'): raise AttributeError("Integrate the orbit first or use analytic=True for approximate eccentricity") if not hasattr(self,'rs'): self.rs= self.orbit[:,0] return (nu.amax(self.rs)-nu.amin(self.rs))/(nu.amax(self.rs)+nu.amin(self.rs)) @physical_conversion('energy') def Jacobi(self,*args,**kwargs): """ NAME: Jacobi PURPOSE: calculate the Jacobi integral of the motion INPUT: t - (optional) time at which to get the radius OmegaP= pattern speed of rotating frame pot= potential instance or list of such instances OUTPUT: Jacobi integral HISTORY: 2011-04-18 - Written - Bovy (NYU) """ if not 'OmegaP' in kwargs or kwargs['OmegaP'] is None: OmegaP= 1. if not 'pot' in kwargs or kwargs['pot'] is None: try: pot= self._pot except AttributeError: raise AttributeError("Integrate orbit or specify pot=") else: pot= kwargs['pot'] if isinstance(pot,list): for p in pot: if hasattr(p,'OmegaP'): OmegaP= p.OmegaP() break else: if hasattr(pot,'OmegaP'): OmegaP= pot.OmegaP() kwargs.pop('OmegaP',None) else: OmegaP= kwargs.pop('OmegaP') #Make sure you are not using physical coordinates old_physical= kwargs.get('use_physical',None) kwargs['use_physical']= False out= self.E(*args,**kwargs)-OmegaP*self.L(*args,**kwargs) if not old_physical is None: kwargs['use_physical']= old_physical else: kwargs.pop('use_physical') return out @physical_conversion('position') def rap(self,analytic=False,pot=None,**kwargs): """ NAME: rap PURPOSE: return the apocenter radius INPUT: analytic - compute this analytically pot - potential to use for analytical calculation OUTPUT: R_ap HISTORY: 2010-09-20 - Written - Bovy (NYU) """ if analytic: self._setupaA(pot=pot,type='adiabatic') (rperi,rap)= self._aA.calcRapRperi(self) return rap if not hasattr(self,'orbit'): raise AttributeError("Integrate the orbit first") if not hasattr(self,'rs'): self.rs= self.orbit[:,0] return nu.amax(self.rs) @physical_conversion('position') def rperi(self,analytic=False,pot=None,**kwargs): """ NAME: rperi PURPOSE: return the pericenter radius INPUT: analytic - compute this analytically pot - potential to use for analytical calculation OUTPUT: R_peri HISTORY: 2010-09-20 - Written - Bovy (NYU) """ if analytic: self._setupaA(pot=pot,type='adiabatic') (rperi,rap)= self._aA.calcRapRperi(self) return rperi if not hasattr(self,'orbit'): raise AttributeError("Integrate the orbit first") if not hasattr(self,'rs'): self.rs= self.orbit[:,0] return nu.amin(self.rs) @physical_conversion('position') def zmax(self,pot=None,analytic=False,**kwargs): raise AttributeError("planarOrbit does not have a zmax") class planarROrbit(planarOrbitTop): """Class representing a planar orbit, without \phi. Useful for orbit-integration in axisymmetric potentials when you don't care about the azimuth""" def __init__(self,vxvv=[1.,0.,1.],vo=220.,ro=8.0,zo=0.025, solarmotion=nu.array([-10.1,4.0,6.7])): """ NAME: __init__ PURPOSE: Initialize a planarROrbit INPUT: vxvv - [R,vR,vT] vo - circular velocity at ro (km/s) ro - distance from vantage point to GC (kpc) zo - offset toward the NGP of the Sun wrt the plane (kpc) solarmotion - value in [-U,V,W] (km/s) OUTPUT: HISTORY: 2010-07-12 - Written - Bovy (NYU) 2014-06-11 - Added conversion kwargs to physical coordinates - Bovy (IAS) """ OrbitTop.__init__(self,vxvv=vxvv, ro=ro,zo=zo,vo=vo,solarmotion=solarmotion) return None def integrate(self,t,pot,method='symplec4_c',dt=None): """ NAME: integrate PURPOSE: integrate the orbit INPUT: t - list of times at which to output (0 has to be in this!) pot - potential instance or list of instances method= 'odeint' for scipy's odeint 'leapfrog' for a simple leapfrog implementation 'leapfrog_c' for a simple leapfrog implementation in C 'rk4_c' for a 4th-order Runge-Kutta integrator in C 'rk6_c' for a 6-th order Runge-Kutta integrator in C 'dopr54_c' for a Dormand-Prince integrator in C (generally the fastest) dt= (None) if set, force the integrator to use this basic stepsize; must be an integer divisor of output stepsize OUTPUT: error message number (get the actual orbit using getOrbit() HISTORY: 2010-07-20 """ if hasattr(self,'_orbInterp'): delattr(self,'_orbInterp') if hasattr(self,'rs'): delattr(self,'rs') thispot= RZToplanarPotential(pot) self.t= nu.array(t) self._pot= thispot self.orbit, msg= _integrateROrbit(self.vxvv,thispot,t,method,dt) return msg @physical_conversion('energy') def E(self,*args,**kwargs): """ NAME: E PURPOSE: calculate the energy INPUT: t - (optional) time at which to get the radius pot= potential instance or list of such instances OUTPUT: energy HISTORY: 2010-09-15 - Written - Bovy (NYU) 2011-04-18 - Added t - Bovy (NYU) """ if not 'pot' in kwargs or kwargs['pot'] is None: try: pot= self._pot except AttributeError: raise AttributeError("Integrate orbit or specify pot=") if 'pot' in kwargs and kwargs['pot'] is None: kwargs.pop('pot') else: pot= kwargs.pop('pot') if isinstance(pot,Potential): thispot= RZToplanarPotential(pot) elif isinstance(pot,list): thispot= [] for p in pot: if isinstance(p,Potential): thispot.append(RZToplanarPotential(p)) else: thispot.append(p) else: thispot= pot if len(args) > 0: t= args[0] else: t= 0. #Get orbit thiso= self(*args,**kwargs) onet= (len(thiso.shape) == 1) if onet: return _evaluateplanarPotentials(thispot,thiso[0], t=t)\ +thiso[1]**2./2.\ +thiso[2]**2./2. else: return nu.array([_evaluateplanarPotentials(thispot,thiso[0,ii], t=t[ii])\ +thiso[1,ii]**2./2.\ +thiso[2,ii]**2./2. for ii in range(len(t))]) class planarOrbit(planarOrbitTop): """Class representing a full planar orbit (R,vR,vT,phi)""" def __init__(self,vxvv=[1.,0.,1.,0.],vo=220.,ro=8.0,zo=0.025, solarmotion=nu.array([-10.1,4.0,6.7])): """ NAME: __init__ PURPOSE: Initialize a planarOrbit INPUT: vxvv - [R,vR,vT,phi] vo - circular velocity at ro (km/s) ro - distance from vantage point to GC (kpc) zo - offset toward the NGP of the Sun wrt the plane (kpc) solarmotion - value in [-U,V,W] (km/s) OUTPUT: HISTORY: 2010-07-12 - Written - Bovy (NYU) 2014-06-11 - Added conversion kwargs to physical coordinates - Bovy (IAS) """ if len(vxvv) == 3: #pragma: no cover raise ValueError("You only provided R,vR, & vT, but not phi; you probably want planarROrbit") OrbitTop.__init__(self,vxvv=vxvv, ro=ro,zo=zo,vo=vo,solarmotion=solarmotion) return None def integrate(self,t,pot,method='symplec4_c',dt=None): """ NAME: integrate PURPOSE: integrate the orbit INPUT: t - list of times at which to output (0 has to be in this!) pot - potential instance or list of instances method= 'odeint' for scipy's odeint 'leapfrog' for a simple leapfrog implementation 'leapfrog_c' for a simple leapfrog implementation in C 'rk4_c' for a 4th-order Runge-Kutta integrator in C 'rk6_c' for a 6-th order Runge-Kutta integrator in C 'dopr54_c' for a Dormand-Prince integrator in C (generally the fastest) dt= (None) if set, force the integrator to use this basic stepsize; must be an integer divisor of output stepsize OUTPUT: (none) (get the actual orbit using getOrbit() HISTORY: 2010-07-20 """ if hasattr(self,'_orbInterp'): delattr(self,'_orbInterp') if hasattr(self,'rs'): delattr(self,'rs') thispot= toPlanarPotential(pot) self.t= nu.array(t) self._pot= thispot self.orbit, msg= _integrateOrbit(self.vxvv,thispot,t,method,dt) return msg def integrate_dxdv(self,dxdv,t,pot,method='dopr54_c', rectIn=False,rectOut=False): """ NAME: integrate_dxdv PURPOSE: integrate the orbit and a small area of phase space INPUT: dxdv - [dR,dvR,dvT,dphi] t - list of times at which to output (0 has to be in this!) pot - potential instance or list of instances method= 'odeint' for scipy's odeint 'rk4_c' for a 4th-order Runge-Kutta integrator in C 'rk6_c' for a 6-th order Runge-Kutta integrator in C 'dopr54_c' for a Dormand-Prince integrator in C (generally the fastest) rectIn= (False) if True, input dxdv is in rectangular coordinates rectOut= (False) if True, output dxdv (that in orbit_dxdv) is in rectangular coordinates OUTPUT: (none) (get the actual orbit using getOrbit_dxdv() HISTORY: 2010-10-17 - Written - Bovy (IAS) 2014-06-29 - Added rectIn and rectOut - Bovy (IAS) """ if hasattr(self,'_orbInterp'): delattr(self,'_orbInterp') if hasattr(self,'rs'): delattr(self,'rs') thispot= toPlanarPotential(pot) self.t= nu.array(t) self._pot_dxdv= thispot self._pot= thispot self.orbit_dxdv, msg= _integrateOrbit_dxdv(self.vxvv,dxdv,thispot,t, method,rectIn,rectOut) self.orbit= self.orbit_dxdv[:,:4] return msg @physical_conversion('energy') def E(self,*args,**kwargs): """ NAME: E PURPOSE: calculate the energy INPUT: pot= t= time at which to evaluate E OUTPUT: energy HISTORY: 2010-09-15 - Written - Bovy (NYU) """ if not 'pot' in kwargs or kwargs['pot'] is None: try: pot= self._pot except AttributeError: raise AttributeError("Integrate orbit or specify pot=") if 'pot' in kwargs and kwargs['pot'] is None: kwargs.pop('pot') else: pot= kwargs.pop('pot') if isinstance(pot,Potential): thispot= toPlanarPotential(pot) elif isinstance(pot,list): thispot= [] for p in pot: if isinstance(p,Potential): thispot.append(toPlanarPotential(p)) else: thispot.append(p) else: thispot= pot if len(args) > 0: t= args[0] else: t= 0. #Get orbit thiso= self(*args,**kwargs) onet= (len(thiso.shape) == 1) if onet: return _evaluateplanarPotentials(thispot,thiso[0], phi=thiso[3],t=t)\ +thiso[1]**2./2.\ +thiso[2]**2./2. else: return nu.array([_evaluateplanarPotentials(thispot,thiso[0,ii], phi=thiso[3,ii], t=t[ii])\ +thiso[1,ii]**2./2.\ +thiso[2,ii]**2./2. for ii in range(len(t))]) def e(self,analytic=False,pot=None): """ NAME: e PURPOSE: calculate the eccentricity INPUT: analytic - calculate e analytically pot - potential used to analytically calculate e OUTPUT: eccentricity HISTORY: 2010-09-15 - Written - Bovy (NYU) """ if analytic: self._setupaA(pot=pot,type='adiabatic') (rperi,rap)= self._aA.calcRapRperi(self) return (rap-rperi)/(rap+rperi) if not hasattr(self,'orbit'): raise AttributeError("Integrate the orbit first or use analytic=True for approximate eccentricity") if not hasattr(self,'rs'): self.rs= self.orbit[:,0] return (nu.amax(self.rs)-nu.amin(self.rs))/(nu.amax(self.rs)+nu.amin(self.rs)) def _integrateROrbit(vxvv,pot,t,method,dt): """ NAME: _integrateROrbit PURPOSE: integrate an orbit in a Phi(R) potential in the R-plane INPUT: vxvv - array with the initial conditions stacked like [R,vR,vT]; vR outward! pot - Potential instance t - list of times at which to output (0 has to be in this!) method - 'odeint' or 'leapfrog' dt - if set, force the integrator to use this basic stepsize; must be an integer divisor of output stepsize OUTPUT: [:,3] array of [R,vR,vT] at each t HISTORY: 2010-07-20 - Written - Bovy (NYU) """ #First check that the potential has C if '_c' in method: if not _check_c(pot): if ('leapfrog' in method or 'symplec' in method): method= 'leapfrog' else: method= 'odeint' warnings.warn("Cannot use C integration because some of the potentials are not implemented in C (using %s instead)" % (method), galpyWarning) if method.lower() == 'leapfrog': #We hack this by putting in a dummy phi this_vxvv= nu.zeros(len(vxvv)+1) this_vxvv[0:len(vxvv)]= vxvv tmp_out, msg= _integrateOrbit(this_vxvv,pot,t,method,dt) #tmp_out is (nt,4) out= tmp_out[:,0:3] elif method.lower() == 'leapfrog_c' or method.lower() == 'rk4_c' \ or method.lower() == 'rk6_c' or method.lower() == 'symplec4_c' \ or method.lower() == 'symplec6_c' or method.lower() == 'dopr54_c': #We hack this by putting in a dummy phi this_vxvv= nu.zeros(len(vxvv)+1) this_vxvv[0:len(vxvv)]= vxvv tmp_out, msg= _integrateOrbit(this_vxvv,pot,t,method,dt) #tmp_out is (nt,4) out= tmp_out[:,0:3] elif method.lower() == 'odeint': l= vxvv[0]*vxvv[2] l2= l**2. init= [vxvv[0],vxvv[1]] intOut= integrate.odeint(_REOM,init,t,args=(pot,l2), rtol=10.**-8.)#,mxstep=100000000) out= nu.zeros((len(t),3)) out[:,0]= intOut[:,0] out[:,1]= intOut[:,1] out[:,2]= l/out[:,0] msg= 0 #post-process to remove negative radii neg_radii= (out[:,0] < 0.) out[neg_radii,0]= -out[neg_radii,0] _parse_warnmessage(msg) return (out,msg) def _REOM(y,t,pot,l2): """ NAME: _REOM PURPOSE: implements the EOM, i.e., the right-hand side of the differential equation INPUT: y - current phase-space position t - current time pot - (list of) Potential instance(s) l2 - angular momentum squared OUTPUT: dy/dt HISTORY: 2010-07-20 - Written - Bovy (NYU) """ return [y[1], l2/y[0]**3.+_evaluateplanarRforces(pot,y[0],t=t)] def _integrateOrbit(vxvv,pot,t,method,dt): """ NAME: _integrateOrbit PURPOSE: integrate an orbit in a Phi(R) potential in the (R,phi)-plane INPUT: vxvv - array with the initial conditions stacked like [R,vR,vT,phi]; vR outward! pot - Potential instance t - list of times at which to output (0 has to be in this!) method - 'odeint' or 'leapfrog' dt- if set, force the integrator to use this basic stepsize; must be an integer divisor of output stepsize OUTPUT: [:,4] array of [R,vR,vT,phi] at each t HISTORY: 2010-07-20 - Written - Bovy (NYU) """ #First check that the potential has C if '_c' in method: if not _check_c(pot): if ('leapfrog' in method or 'symplec' in method): method= 'leapfrog' else: method= 'odeint' warnings.warn("Cannot use C integration because some of the potentials are not implemented in C (using %s instead)" % (method), galpyWarning) if method.lower() == 'leapfrog': #go to the rectangular frame this_vxvv= nu.array([vxvv[0]*nu.cos(vxvv[3]), vxvv[0]*nu.sin(vxvv[3]), vxvv[1]*nu.cos(vxvv[3])-vxvv[2]*nu.sin(vxvv[3]), vxvv[2]*nu.cos(vxvv[3])+vxvv[1]*nu.sin(vxvv[3])]) #integrate tmp_out= symplecticode.leapfrog(_rectForce,this_vxvv, t,args=(pot,),rtol=10.**-8) #go back to the cylindrical frame R= nu.sqrt(tmp_out[:,0]**2.+tmp_out[:,1]**2.) phi= nu.arccos(tmp_out[:,0]/R) phi[(tmp_out[:,1] < 0.)]= 2.*nu.pi-phi[(tmp_out[:,1] < 0.)] vR= tmp_out[:,2]*nu.cos(phi)+tmp_out[:,3]*nu.sin(phi) vT= tmp_out[:,3]*nu.cos(phi)-tmp_out[:,2]*nu.sin(phi) out= nu.zeros((len(t),4)) out[:,0]= R out[:,1]= vR out[:,2]= vT out[:,3]= phi msg= 0 elif method.lower() == 'leapfrog_c' or method.lower() == 'rk4_c' \ or method.lower() == 'rk6_c' or method.lower() == 'symplec4_c' \ or method.lower() == 'symplec6_c' or method.lower() == 'dopr54_c': warnings.warn("Using C implementation to integrate orbits",galpyWarning) #go to the rectangular frame this_vxvv= nu.array([vxvv[0]*nu.cos(vxvv[3]), vxvv[0]*nu.sin(vxvv[3]), vxvv[1]*nu.cos(vxvv[3])-vxvv[2]*nu.sin(vxvv[3]), vxvv[2]*nu.cos(vxvv[3])+vxvv[1]*nu.sin(vxvv[3])]) #integrate tmp_out, msg= integratePlanarOrbit_c(pot,this_vxvv, t,method,dt=dt) #go back to the cylindrical frame R= nu.sqrt(tmp_out[:,0]**2.+tmp_out[:,1]**2.) phi= nu.arccos(tmp_out[:,0]/R) phi[(tmp_out[:,1] < 0.)]= 2.*nu.pi-phi[(tmp_out[:,1] < 0.)] vR= tmp_out[:,2]*nu.cos(phi)+tmp_out[:,3]*nu.sin(phi) vT= tmp_out[:,3]*nu.cos(phi)-tmp_out[:,2]*nu.sin(phi) out= nu.zeros((len(t),4)) out[:,0]= R out[:,1]= vR out[:,2]= vT out[:,3]= phi elif method.lower() == 'odeint': vphi= vxvv[2]/vxvv[0] init= [vxvv[0],vxvv[1],vxvv[3],vphi] intOut= integrate.odeint(_EOM,init,t,args=(pot,), rtol=10.**-8.)#,mxstep=100000000) out= nu.zeros((len(t),4)) out[:,0]= intOut[:,0] out[:,1]= intOut[:,1] out[:,3]= intOut[:,2] out[:,2]= out[:,0]*intOut[:,3] msg= 0 else: raise NotImplementedError("requested integration method does not exist") #post-process to remove negative radii neg_radii= (out[:,0] < 0.) out[neg_radii,0]= -out[neg_radii,0] out[neg_radii,3]+= m.pi _parse_warnmessage(msg) return (out,msg) def _integrateOrbit_dxdv(vxvv,dxdv,pot,t,method,rectIn,rectOut): """ NAME: _integrateOrbit_dxdv PURPOSE: integrate an orbit and area of phase space in a Phi(R) potential in the (R,phi)-plane INPUT: vxvv - array with the initial conditions stacked like [R,vR,vT,phi]; vR outward! dxdv - difference to integrate [dR,dvR,dvT,dphi] pot - Potential instance t - list of times at which to output (0 has to be in this!) method - 'odeint' or 'leapfrog' rectIn= (False) if True, input dxdv is in rectangular coordinates rectOut= (False) if True, output dxdv (that in orbit_dxdv) is in rectangular coordinates OUTPUT: [:,8] array of [R,vR,vT,phi,dR,dvR,dvT,dphi] at each t error message from integrator HISTORY: 2010-10-17 - Written - Bovy (IAS) """ #First check that the potential has C if '_c' in method: allHasC= _check_c(pot) and _check_c(pot,dxdv=True) if not allHasC and not 'leapfrog' in method and not 'symplec' in method: method= 'odeint' warnings.warn("Using odeint because not all used potential have adequate C implementations to integrate phase-space volumes",galpyWarning) #go to the rectangular frame this_vxvv= nu.array([vxvv[0]*nu.cos(vxvv[3]), vxvv[0]*nu.sin(vxvv[3]), vxvv[1]*nu.cos(vxvv[3])-vxvv[2]*nu.sin(vxvv[3]), vxvv[2]*nu.cos(vxvv[3])+vxvv[1]*nu.sin(vxvv[3])]) if not rectIn: this_dxdv= nu.array([nu.cos(vxvv[3])*dxdv[0] -vxvv[0]*nu.sin(vxvv[3])*dxdv[3], nu.sin(vxvv[3])*dxdv[0] +vxvv[0]*nu.cos(vxvv[3])*dxdv[3], -(vxvv[1]*nu.sin(vxvv[3]) +vxvv[2]*nu.cos(vxvv[3]))*dxdv[3] +nu.cos(vxvv[3])*dxdv[1]-nu.sin(vxvv[3])*dxdv[2], (vxvv[1]*nu.cos(vxvv[3]) -vxvv[2]*nu.sin(vxvv[3]))*dxdv[3] +nu.sin(vxvv[3])*dxdv[1]+nu.cos(vxvv[3])*dxdv[2]]) else: this_dxdv= dxdv if 'leapfrog' in method.lower() or 'symplec' in method.lower(): raise TypeError('Symplectic integration for phase-space volume is not possible') elif method.lower() == 'rk4_c' or method.lower() == 'rk6_c' \ or method.lower() == 'dopr54_c': warnings.warn("Using C implementation to integrate orbits",galpyWarning) #integrate tmp_out, msg= integratePlanarOrbit_dxdv_c(pot,this_vxvv,this_dxdv, t,method) elif method.lower() == 'odeint': init= [this_vxvv[0],this_vxvv[1],this_vxvv[2],this_vxvv[3], this_dxdv[0],this_dxdv[1],this_dxdv[2],this_dxdv[3]] #integrate tmp_out= integrate.odeint(_EOM_dxdv,init,t,args=(pot,), rtol=10.**-8.)#,mxstep=100000000) msg= 0 else: raise NotImplementedError("requested integration method does not exist") #go back to the cylindrical frame R= nu.sqrt(tmp_out[:,0]**2.+tmp_out[:,1]**2.) phi= nu.arccos(tmp_out[:,0]/R) phi[(tmp_out[:,1] < 0.)]= 2.*nu.pi-phi[(tmp_out[:,1] < 0.)] vR= tmp_out[:,2]*nu.cos(phi)+tmp_out[:,3]*nu.sin(phi) vT= tmp_out[:,3]*nu.cos(phi)-tmp_out[:,2]*nu.sin(phi) cp= nu.cos(phi) sp= nu.sin(phi) dR= cp*tmp_out[:,4]+sp*tmp_out[:,5] dphi= (cp*tmp_out[:,5]-sp*tmp_out[:,4])/R dvR= cp*tmp_out[:,6]+sp*tmp_out[:,7]+vT*dphi dvT= cp*tmp_out[:,7]-sp*tmp_out[:,6]-vR*dphi out= nu.zeros((len(t),8)) out[:,0]= R out[:,1]= vR out[:,2]= vT out[:,3]= phi if rectOut: out[:,4:]= tmp_out[:,4:] else: out[:,4]= dR out[:,7]= dphi out[:,5]= dvR out[:,6]= dvT _parse_warnmessage(msg) return (out,msg) def _EOM_dxdv(x,t,pot): """ NAME: _EOM_dxdv PURPOSE: implements the EOM, i.e., the right-hand side of the differential equation, for integrating phase space differences, rectangular INPUT: x - current phase-space position t - current time pot - (list of) Potential instance(s) OUTPUT: dy/dt HISTORY: 2011-10-18 - Written - Bovy (NYU) """ #x is rectangular so calculate R and phi R= nu.sqrt(x[0]**2.+x[1]**2.) phi= nu.arccos(x[0]/R) sinphi= x[1]/R cosphi= x[0]/R if x[1] < 0.: phi= 2.*nu.pi-phi #calculate forces Rforce= _evaluateplanarRforces(pot,R,phi=phi,t=t) phiforce= _evaluateplanarphiforces(pot,R,phi=phi,t=t) R2deriv= _evaluateplanarPotentials(pot,R,phi=phi,t=t,dR=2) phi2deriv= _evaluateplanarPotentials(pot,R,phi=phi,t=t,dphi=2) Rphideriv= _evaluateplanarPotentials(pot,R,phi=phi,t=t,dR=1,dphi=1) #Calculate derivatives and derivatives+time derivatives dFxdx= -cosphi**2.*R2deriv\ +2.*cosphi*sinphi/R**2.*phiforce\ +sinphi**2./R*Rforce\ +2.*sinphi*cosphi/R*Rphideriv\ -sinphi**2./R**2.*phi2deriv dFxdy= -sinphi*cosphi*R2deriv\ +(sinphi**2.-cosphi**2.)/R**2.*phiforce\ -cosphi*sinphi/R*Rforce\ -(cosphi**2.-sinphi**2.)/R*Rphideriv\ +cosphi*sinphi/R**2.*phi2deriv dFydx= -cosphi*sinphi*R2deriv\ +(sinphi**2.-cosphi**2.)/R**2.*phiforce\ +(sinphi**2.-cosphi**2.)/R*Rphideriv\ -sinphi*cosphi/R*Rforce\ +sinphi*cosphi/R**2.*phi2deriv dFydy= -sinphi**2.*R2deriv\ -2.*sinphi*cosphi/R**2.*phiforce\ -2.*sinphi*cosphi/R*Rphideriv\ +cosphi**2./R*Rforce\ -cosphi**2./R**2.*phi2deriv return nu.array([x[2],x[3], cosphi*Rforce-1./R*sinphi*phiforce, sinphi*Rforce+1./R*cosphi*phiforce, x[6],x[7], dFxdx*x[4]+dFxdy*x[5], dFydx*x[4]+dFydy*x[5]]) def _EOM(y,t,pot): """ NAME: _EOM PURPOSE: implements the EOM, i.e., the right-hand side of the differential equation INPUT: y - current phase-space position t - current time pot - (list of) Potential instance(s) l2 - angular momentum squared OUTPUT: dy/dt HISTORY: 2010-07-20 - Written - Bovy (NYU) """ l2= (y[0]**2.*y[3])**2. return [y[1], l2/y[0]**3.+_evaluateplanarRforces(pot,y[0],phi=y[2],t=t), y[3], 1./y[0]**2.*(_evaluateplanarphiforces(pot,y[0],phi=y[2],t=t)- 2.*y[0]*y[1]*y[3])] def _rectForce(x,pot,t=0.): """ NAME: _rectForce PURPOSE: returns the force in the rectangular frame INPUT: x - current position t - current time pot - (list of) Potential instance(s) OUTPUT: force HISTORY: 2011-02-02 - Written - Bovy (NYU) """ #x is rectangular so calculate R and phi R= nu.sqrt(x[0]**2.+x[1]**2.) phi= nu.arccos(x[0]/R) sinphi= x[1]/R cosphi= x[0]/R if x[1] < 0.: phi= 2.*nu.pi-phi #calculate forces Rforce= _evaluateplanarRforces(pot,R,phi=phi,t=t) phiforce= _evaluateplanarphiforces(pot,R,phi=phi,t=t) return nu.array([cosphi*Rforce-1./R*sinphi*phiforce, sinphi*Rforce+1./R*cosphi*phiforce]) def _parse_warnmessage(msg): if msg == 1: #pragma: no cover warnings.warn("During numerical integration, steps smaller than the smallest step were requested; integration might not be accurate",galpyWarning) galpy-1.3.0/galpy/orbit_src/RZOrbit.py0000644000076500000240000004474413231751437017763 0ustar bovystaff00000000000000import warnings import math as m import numpy as nu from scipy import integrate from galpy.potential_src.Potential import _evaluateRforces, _evaluatezforces,\ evaluatePotentials, evaluateDensities, _check_c from galpy.util import galpyWarning import galpy.util.bovy_plot as plot import galpy.util.bovy_symplecticode as symplecticode from galpy.orbit_src.FullOrbit import _integrateFullOrbit from galpy.util.bovy_conversion import physical_conversion from galpy.orbit_src.OrbitTop import OrbitTop class RZOrbit(OrbitTop): """Class that holds and integrates orbits in axisymetric potentials in the (R,z) plane""" def __init__(self,vxvv=[1.,0.,0.9,0.,0.1],vo=220.,ro=8.0,zo=0.025, solarmotion=nu.array([-10.1,4.0,6.7])): """ NAME: __init__ PURPOSE: intialize an RZ-orbit INPUT: vxvv - initial condition [R,vR,vT,z,vz] vo - circular velocity at ro (km/s) ro - distance from vantage point to GC (kpc) zo - offset toward the NGP of the Sun wrt the plane (kpc) solarmotion - value in [-U,V,W] (km/s) OUTPUT: (none) HISTORY: 2010-07-10 - Written - Bovy (NYU) 2014-06-11 - Added conversion kwargs to physical coordinates - Bovy (IAS) """ OrbitTop.__init__(self,vxvv=vxvv, ro=ro,zo=zo,vo=vo,solarmotion=solarmotion) return None def integrate(self,t,pot,method='symplec4_c',dt=None): """ NAME: integrate PURPOSE: integrate the orbit INPUT: t - list of times at which to output (0 has to be in this!) pot - potential instance or list of instances method= 'odeint' for scipy's odeint 'leapfrog' for a simple leapfrog implementation 'leapfrog_c' for a simple leapfrog implementation in C 'rk4_c' for a 4th-order Runge-Kutta integrator in C 'rk6_c' for a 6-th order Runge-Kutta integrator in C 'dopr54_c' for a Dormand-Prince integrator in C (generally the fastest) dt= (None) if set, force the integrator to use this basic stepsize; must be an integer divisor of output stepsize OUTPUT: (none) (get the actual orbit using getOrbit() HISTORY: 2010-07-10 """ if hasattr(self,'_orbInterp'): delattr(self,'_orbInterp') if hasattr(self,'rs'): delattr(self,'rs') self.t= nu.array(t) self._pot= pot self.orbit= _integrateRZOrbit(self.vxvv,pot,t,method,dt) @physical_conversion('energy') def E(self,*args,**kwargs): """ NAME: E PURPOSE: calculate the energy INPUT: t - (optional) time at which to get the radius pot= RZPotential instance or list thereof OUTPUT: energy HISTORY: 2010-09-15 - Written - Bovy (NYU) """ if not 'pot' in kwargs or kwargs['pot'] is None: try: pot= self._pot except AttributeError: raise AttributeError("Integrate orbit or specify pot=") if 'pot' in kwargs and kwargs['pot'] is None: kwargs.pop('pot') else: pot= kwargs.pop('pot') if len(args) > 0: t= args[0] else: t= 0. #Get orbit thiso= self(*args,**kwargs) onet= (len(thiso.shape) == 1) if onet: return evaluatePotentials(pot,thiso[0],thiso[3], t=t,use_physical=False)\ +thiso[1]**2./2.\ +thiso[2]**2./2.\ +thiso[4]**2./2. else: return nu.array([evaluatePotentials(pot,thiso[0,ii],thiso[3,ii], t=t[ii],use_physical=False)\ +thiso[1,ii]**2./2.\ +thiso[2,ii]**2./2.\ +thiso[4,ii]**2./2. for ii in range(len(t))]) @physical_conversion('energy') def ER(self,*args,**kwargs): """ NAME: ER PURPOSE: calculate the radial energy INPUT: t - (optional) time at which to get the energy pot= potential instance or list of such instances OUTPUT: radial energy HISTORY: 2013-11-30 - Written - Bovy (IAS) """ if not 'pot' in kwargs or kwargs['pot'] is None: try: pot= self._pot except AttributeError: raise AttributeError("Integrate orbit or specify pot=") if 'pot' in kwargs and kwargs['pot'] is None: kwargs.pop('pot') else: pot= kwargs.pop('pot') if len(args) > 0: t= args[0] else: t= 0. #Get orbit thiso= self(*args,**kwargs) onet= (len(thiso.shape) == 1) if onet: return evaluatePotentials(pot,thiso[0],0., t=t,use_physical=False)\ +thiso[1]**2./2.\ +thiso[2]**2./2. else: return nu.array([evaluatePotentials(pot,thiso[0,ii],0., t=t[ii],use_physical=False)\ +thiso[1,ii]**2./2.\ +thiso[2,ii]**2./2. for ii in range(len(t))]) @physical_conversion('energy') def Ez(self,*args,**kwargs): """ NAME: Ez PURPOSE: calculate the vertical energy INPUT: t - (optional) time at which to get the energy pot= potential instance or list of such instances OUTPUT: vertical energy HISTORY: 2013-11-30 - Written - Bovy (IAS) """ if not 'pot' in kwargs or kwargs['pot'] is None: try: pot= self._pot except AttributeError: raise AttributeError("Integrate orbit or specify pot=") if 'pot' in kwargs and kwargs['pot'] is None: kwargs.pop('pot') else: pot= kwargs.pop('pot') if len(args) > 0: t= args[0] else: t= 0. #Get orbit thiso= self(*args,**kwargs) onet= (len(thiso.shape) == 1) if onet: return evaluatePotentials(pot,thiso[0],thiso[3], t=t,use_physical=False)\ -evaluatePotentials(pot,thiso[0],0., t=t, use_physical=False)\ +thiso[4]**2./2. else: return nu.array([evaluatePotentials(pot,thiso[0,ii],thiso[3,ii], t=t[ii],use_physical=False)\ -evaluatePotentials(pot,thiso[0,ii],0., t=t[ii],use_physical=False)\ +thiso[4,ii]**2./2. for ii in range(len(t))]) @physical_conversion('energy') def Jacobi(self,*args,**kwargs): """ NAME: Jacobi PURPOSE: calculate the Jacobi integral of the motion INPUT: t - (optional) time at which to get the radius OmegaP= pattern speed of rotating frame (scalar) pot= potential instance or list of such instances OUTPUT: Jacobi integral HISTORY: 2011-04-18 - Written - Bovy (NYU) """ if not 'OmegaP' in kwargs or kwargs['OmegaP'] is None: OmegaP= 1. if not 'pot' in kwargs or kwargs['pot'] is None: try: pot= self._pot except AttributeError: raise AttributeError("Integrate orbit or specify pot=") else: pot= kwargs['pot'] if isinstance(pot,list): for p in pot: if hasattr(p,'OmegaP'): OmegaP= p.OmegaP() break else: if hasattr(pot,'OmegaP'): OmegaP= pot.OmegaP() kwargs.pop('OmegaP',None) else: OmegaP= kwargs.pop('OmegaP') #Make sure you are not using physical coordinates old_physical= kwargs.get('use_physical',None) kwargs['use_physical']= False thiso= self(*args,**kwargs) out= self.E(*args,**kwargs)-OmegaP*thiso[0]*thiso[2] if not old_physical is None: kwargs['use_physical']= old_physical else: kwargs.pop('use_physical') return out def e(self,analytic=False,pot=None,**kwargs): """ NAME: e PURPOSE: calculate the eccentricity INPUT: analytic - compute this analytically pot - potential to use for analytical calculation OUTPUT: eccentricity HISTORY: 2010-09-15 - Written - Bovy (NYU) """ if analytic: self._setupaA(pot=pot,**kwargs) return float(self._aA.EccZmaxRperiRap(self)[0]) if not hasattr(self,'orbit'): raise AttributeError("Integrate the orbit first or use analytic=True for approximate eccentricity") if not hasattr(self,'rs'): self.rs= nu.sqrt(self.orbit[:,0]**2.+self.orbit[:,3]**2.) return (nu.amax(self.rs)-nu.amin(self.rs))/(nu.amax(self.rs)+nu.amin(self.rs)) @physical_conversion('position') def rap(self,analytic=False,pot=None,**kwargs): """ NAME: rap PURPOSE: return the apocenter radius INPUT: analytic - compute this analytically pot - potential to use for analytical calculation OUTPUT: R_ap HISTORY: 2010-09-20 - Written - Bovy (NYU) """ if analytic: self._setupaA(pot=pot,**kwargs) return float(self._aA.EccZmaxRperiRap(self)[3]) if not hasattr(self,'orbit'): raise AttributeError("Integrate the orbit first or use analytic=True for approximate rap") if not hasattr(self,'rs'): self.rs= nu.sqrt(self.orbit[:,0]**2.+self.orbit[:,3]**2.) return nu.amax(self.rs) @physical_conversion('position') def rperi(self,analytic=False,pot=None,**kwargs): """ NAME: rperi PURPOSE: return the pericenter radius INPUT: analytic - compute this analytically pot - potential to use for analytical calculation OUTPUT: R_peri HISTORY: 2010-09-20 - Written - Bovy (NYU) """ if analytic: self._setupaA(pot=pot,**kwargs) return float(self._aA.EccZmaxRperiRap(self)[2]) if not hasattr(self,'orbit'): raise AttributeError("Integrate the orbit first or use analytic=True for approximate rperi") if not hasattr(self,'rs'): self.rs= nu.sqrt(self.orbit[:,0]**2.+self.orbit[:,3]**2.) return nu.amin(self.rs) @physical_conversion('position') def zmax(self,analytic=False,pot=None,**kwargs): """ NAME: zmax PURPOSE: return the maximum vertical height INPUT: OUTPUT: Z_max HISTORY: 2010-09-20 - Written - Bovy (NYU) """ if analytic: self._setupaA(pot=pot,**kwargs) return float(self._aA.EccZmaxRperiRap(self)[1]) if not hasattr(self,'orbit'): raise AttributeError("Integrate the orbit first or use analytic=True for approximate zmax") return nu.amax(nu.fabs(self.orbit[:,3])) def plotEz(self,*args,**kwargs): """ NAME: plotEz PURPOSE: plot Ez(.) along the orbit INPUT: bovy_plot.bovy_plot inputs OUTPUT: figure to output device HISTORY: 2014-06-16 - Written - Bovy (IAS) """ if kwargs.pop('normed',False): kwargs['d2']= 'Eznorm' else: kwargs['d2']= 'Ez' return self.plot(*args,**kwargs) def plotER(self,*args,**kwargs): """ NAME: plotER PURPOSE: plot ER(.) along the orbit INPUT: bovy_plot.bovy_plot inputs OUTPUT: figure to output device HISTORY: 2014-06-16 - Written - Bovy (IAS) """ if kwargs.pop('normed',False): kwargs['d2']= 'ERnorm' else: kwargs['d2']= 'ER' return self.plot(*args,**kwargs) def plotEzJz(self,*args,**kwargs): """ NAME: plotEzJz PURPOSE: plot E_z(.)/sqrt(dens(R)) along the orbit INPUT: pot= Potential instance or list of instances in which the orbit was integrated d1= - plot Ez vs d1: e.g., 't', 'z', 'R', 'vR', 'vT', 'vz' +bovy_plot.bovy_plot inputs OUTPUT: figure to output device HISTORY: 2010-08-08 - Written - Bovy (NYU) """ labeldict= {'t':r'$t$','R':r'$R$','vR':r'$v_R$','vT':r'$v_T$', 'z':r'$z$','vz':r'$v_z$','phi':r'$\phi$', 'x':r'$x$','y':r'$y$','vx':r'$v_x$','vy':r'$v_y$'} if not 'pot' in kwargs: try: pot= self._pot except AttributeError: raise AttributeError("Integrate orbit first or specify pot=") else: pot= kwargs.pop('pot') d1= kwargs.pop('d1','t') self.EzJz= [(evaluatePotentials(pot,self.orbit[ii,0],self.orbit[ii,3], t=self.t[ii],use_physical=False)- evaluatePotentials(pot,self.orbit[ii,0],0.,t=self.t[ii], use_physical=False)+ self.orbit[ii,4]**2./2.)/\ nu.sqrt(evaluateDensities(pot,self.orbit[ii,0],0., t=self.t[ii], use_physical=False))\ for ii in range(len(self.t))] if not 'xlabel' in kwargs: kwargs['xlabel']= labeldict[d1] if not 'ylabel' in kwargs: kwargs['ylabel']= r'$E_z/\sqrt{\rho}$' if d1 == 't': return plot.bovy_plot(nu.array(self.t), nu.array(self.EzJz)/self.EzJz[0], *args,**kwargs) elif d1 == 'z': return plot.bovy_plot(self.orbit[:,3], nu.array(self.EzJz)/self.EzJz[0], *args,**kwargs) elif d1 == 'R': return plot.bovy_plot(self.orbit[:,0], nu.array(self.EzJz)/self.EzJz[0], *args,**kwargs) elif d1 == 'vR': return plot.bovy_plot(self.orbit[:,1], nu.array(self.EzJz)/self.EzJz[0], *args,**kwargs) elif d1 == 'vT': return plot.bovy_plot(self.orbit[:,2], nu.array(self.EzJz)/self.EzJz[0], *args,**kwargs) elif d1 == 'vz': return plot.bovy_plot(self.orbit[:,4], nu.array(self.EzJz)/self.EzJz[0], *args,**kwargs) def _integrateRZOrbit(vxvv,pot,t,method,dt): """ NAME: _integrateRZOrbit PURPOSE: integrate an orbit in a Phi(R,z) potential in the (R,z) plane INPUT: vxvv - array with the initial conditions stacked like [R,vR,vT,z,vz]; vR outward! pot - Potential instance t - list of times at which to output (0 has to be in this!) method - 'odeint' or 'leapfrog' dt - if set, force the integrator to use this basic stepsize; must be an integer divisor of output stepsize OUTPUT: [:,5] array of [R,vR,vT,z,vz] at each t HISTORY: 2010-04-16 - Written - Bovy (NYU) """ #First check that the potential has C if '_c' in method: if not _check_c(pot): if ('leapfrog' in method or 'symplec' in method): method= 'leapfrog' else: method= 'odeint' warnings.warn("Cannot use C integration because some of the potentials are not implemented in C (using %s instead)" % (method), galpyWarning) if method.lower() == 'leapfrog' \ or method.lower() == 'leapfrog_c' or method.lower() == 'rk4_c' \ or method.lower() == 'rk6_c' or method.lower() == 'symplec4_c' \ or method.lower() == 'symplec6_c' or method.lower() == 'dopr54_c': #We hack this by upgrading to a FullOrbit this_vxvv= nu.zeros(len(vxvv)+1) this_vxvv[0:len(vxvv)]= vxvv tmp_out= _integrateFullOrbit(this_vxvv,pot,t,method,dt) #tmp_out is (nt,6) out= tmp_out[:,0:5] elif method.lower() == 'odeint': l= vxvv[0]*vxvv[2] l2= l**2. init= [vxvv[0],vxvv[1],vxvv[3],vxvv[4]] intOut= integrate.odeint(_RZEOM,init,t,args=(pot,l2), rtol=10.**-8.)#,mxstep=100000000) out= nu.zeros((len(t),5)) out[:,0]= intOut[:,0] out[:,1]= intOut[:,1] out[:,3]= intOut[:,2] out[:,4]= intOut[:,3] out[:,2]= l/out[:,0] #post-process to remove negative radii neg_radii= (out[:,0] < 0.) out[neg_radii,0]= -out[neg_radii,0] return out def _RZEOM(y,t,pot,l2): """ NAME: _RZEOM PURPOSE: implements the EOM, i.e., the right-hand side of the differential equation INPUT: y - current phase-space position t - current time pot - (list of) Potential instance(s) l2 - angular momentum squared OUTPUT: dy/dt HISTORY: 2010-04-16 - Written - Bovy (NYU) """ return [y[1], l2/y[0]**3.+_evaluateRforces(pot,y[0],y[2],t=t), y[3], _evaluatezforces(pot,y[0],y[2],t=t)] galpy-1.3.0/galpy/potential.py0000644000076500000240000001744713236420020016423 0ustar bovystaff00000000000000import warnings from galpy.potential_src import Potential from galpy.potential_src import planarPotential from galpy.potential_src import linearPotential from galpy.potential_src import verticalPotential from galpy.potential_src import MiyamotoNagaiPotential from galpy.potential_src import IsochronePotential from galpy.potential_src import LogarithmicHaloPotential from galpy.potential_src import DoubleExponentialDiskPotential from galpy.potential_src import PowerSphericalPotential from galpy.potential_src import PowerSphericalPotentialwCutoff from galpy.potential_src import TwoPowerSphericalPotential from galpy.potential_src import plotRotcurve from galpy.potential_src import plotEscapecurve from galpy.potential_src import KGPotential from galpy.potential_src import interpRZPotential from galpy.potential_src import DehnenBarPotential from galpy.potential_src import SteadyLogSpiralPotential from galpy.potential_src import TransientLogSpiralPotential from galpy.potential_src import MovingObjectPotential from galpy.potential_src import ForceSoftening from galpy.potential_src import EllipticalDiskPotential from galpy.potential_src import CosmphiDiskPotential from galpy.potential_src import RazorThinExponentialDiskPotential from galpy.potential_src import FlattenedPowerPotential from galpy.potential_src import SnapshotRZPotential from galpy.potential_src import BurkertPotential from galpy.potential_src import MN3ExponentialDiskPotential from galpy.potential_src import KuzminKutuzovStaeckelPotential from galpy.potential_src import PlummerPotential from galpy.potential_src import PseudoIsothermalPotential from galpy.potential_src import KuzminDiskPotential from galpy.potential_src import TwoPowerTriaxialPotential from galpy.potential_src import FerrersPotential from galpy.potential_src import SCFPotential from galpy.potential_src import SoftenedNeedleBarPotential from galpy.potential_src import DiskSCFPotential from galpy.potential_src import SpiralArmsPotential from galpy.potential_src import HenonHeilesPotential from galpy.potential_src import DehnenSmoothWrapperPotential from galpy.potential_src import SolidBodyRotationWrapperPotential # # Functions # evaluatePotentials= Potential.evaluatePotentials evaluateDensities= Potential.evaluateDensities evaluateRforces= Potential.evaluateRforces evaluatephiforces= Potential.evaluatephiforces evaluatezforces= Potential.evaluatezforces evaluaterforces= Potential.evaluaterforces evaluateR2derivs= Potential.evaluateR2derivs evaluatez2derivs= Potential.evaluatez2derivs evaluateRzderivs= Potential.evaluateRzderivs RZToplanarPotential= planarPotential.RZToplanarPotential toPlanarPotential= planarPotential.toPlanarPotential RZToverticalPotential= verticalPotential.RZToverticalPotential plotPotentials= Potential.plotPotentials plotDensities= Potential.plotDensities plotplanarPotentials= planarPotential.plotplanarPotentials plotlinearPotentials= linearPotential.plotlinearPotentials calcRotcurve= plotRotcurve.calcRotcurve vcirc= plotRotcurve.vcirc dvcircdR= plotRotcurve.dvcircdR epifreq= Potential.epifreq verticalfreq= Potential.verticalfreq flattening= Potential.flattening rl= Potential.rl omegac= Potential.omegac vterm= Potential.vterm lindbladR= Potential.lindbladR plotRotcurve= plotRotcurve.plotRotcurve calcEscapecurve= plotEscapecurve.calcEscapecurve vesc= plotEscapecurve.vesc plotEscapecurve= plotEscapecurve.plotEscapecurve evaluateplanarPotentials= planarPotential.evaluateplanarPotentials evaluateplanarRforces= planarPotential.evaluateplanarRforces evaluateplanarR2derivs= planarPotential.evaluateplanarR2derivs evaluateplanarphiforces= planarPotential.evaluateplanarphiforces evaluatelinearPotentials= linearPotential.evaluatelinearPotentials evaluatelinearForces= linearPotential.evaluatelinearForces PotentialError= Potential.PotentialError LinShuReductionFactor= planarPotential.LinShuReductionFactor nemo_accname= Potential.nemo_accname nemo_accpars= Potential.nemo_accpars turn_physical_off= Potential.turn_physical_off turn_physical_on= Potential.turn_physical_on _dim= Potential._dim _isNonAxi= Potential._isNonAxi scf_compute_coeffs_spherical = SCFPotential.scf_compute_coeffs_spherical scf_compute_coeffs_axi = SCFPotential.scf_compute_coeffs_axi scf_compute_coeffs = SCFPotential.scf_compute_coeffs # # Classes # Potential= Potential.Potential planarAxiPotential= planarPotential.planarAxiPotential planarPotential= planarPotential.planarPotential linearPotential= linearPotential.linearPotential MiyamotoNagaiPotential= MiyamotoNagaiPotential.MiyamotoNagaiPotential IsochronePotential= IsochronePotential.IsochronePotential DoubleExponentialDiskPotential= DoubleExponentialDiskPotential.DoubleExponentialDiskPotential LogarithmicHaloPotential= LogarithmicHaloPotential.LogarithmicHaloPotential KeplerPotential= PowerSphericalPotential.KeplerPotential PowerSphericalPotential= PowerSphericalPotential.PowerSphericalPotential PowerSphericalPotentialwCutoff= PowerSphericalPotentialwCutoff.PowerSphericalPotentialwCutoff NFWPotential= TwoPowerSphericalPotential.NFWPotential JaffePotential= TwoPowerSphericalPotential.JaffePotential HernquistPotential= TwoPowerSphericalPotential.HernquistPotential TwoPowerSphericalPotential= TwoPowerSphericalPotential.TwoPowerSphericalPotential KGPotential= KGPotential.KGPotential interpRZPotential= interpRZPotential.interpRZPotential DehnenBarPotential= DehnenBarPotential.DehnenBarPotential SteadyLogSpiralPotential= SteadyLogSpiralPotential.SteadyLogSpiralPotential TransientLogSpiralPotential= TransientLogSpiralPotential.TransientLogSpiralPotential MovingObjectPotential= MovingObjectPotential.MovingObjectPotential EllipticalDiskPotential= EllipticalDiskPotential.EllipticalDiskPotential LopsidedDiskPotential= CosmphiDiskPotential.LopsidedDiskPotential CosmphiDiskPotential= CosmphiDiskPotential.CosmphiDiskPotential RazorThinExponentialDiskPotential= RazorThinExponentialDiskPotential.RazorThinExponentialDiskPotential FlattenedPowerPotential= FlattenedPowerPotential.FlattenedPowerPotential InterpSnapshotRZPotential = SnapshotRZPotential.InterpSnapshotRZPotential SnapshotRZPotential = SnapshotRZPotential.SnapshotRZPotential BurkertPotential= BurkertPotential.BurkertPotential MN3ExponentialDiskPotential= MN3ExponentialDiskPotential.MN3ExponentialDiskPotential KuzminKutuzovStaeckelPotential = KuzminKutuzovStaeckelPotential.KuzminKutuzovStaeckelPotential PlummerPotential = PlummerPotential.PlummerPotential PseudoIsothermalPotential = PseudoIsothermalPotential.PseudoIsothermalPotential KuzminDiskPotential = KuzminDiskPotential.KuzminDiskPotential TriaxialHernquistPotential= TwoPowerTriaxialPotential.TriaxialHernquistPotential TriaxialNFWPotential= TwoPowerTriaxialPotential.TriaxialNFWPotential TriaxialJaffePotential= TwoPowerTriaxialPotential.TriaxialJaffePotential TwoPowerTriaxialPotential= TwoPowerTriaxialPotential.TwoPowerTriaxialPotential FerrersPotential= FerrersPotential.FerrersPotential SCFPotential = SCFPotential.SCFPotential SoftenedNeedleBarPotential= SoftenedNeedleBarPotential.SoftenedNeedleBarPotential DiskSCFPotential = DiskSCFPotential.DiskSCFPotential SpiralArmsPotential = SpiralArmsPotential.SpiralArmsPotential HenonHeilesPotential= HenonHeilesPotential.HenonHeilesPotential #Wrappers DehnenSmoothWrapperPotential= DehnenSmoothWrapperPotential.DehnenSmoothWrapperPotential SolidBodyRotationWrapperPotential= SolidBodyRotationWrapperPotential.SolidBodyRotationWrapperPotential #Softenings PlummerSoftening= ForceSoftening.PlummerSoftening # # Constants # MWPotential= [MiyamotoNagaiPotential(a=0.5,b=0.0375,normalize=.6), NFWPotential(a=4.5,normalize=.35), HernquistPotential(a=0.6/8,normalize=0.05)] # See Table 1 in galpy paper: Bovy (2014) MWPotential2014= [PowerSphericalPotentialwCutoff(normalize=0.05,alpha=1.8,rc=1.9/8.), MiyamotoNagaiPotential(a=3./8.,b=0.28/8.,normalize=0.6), NFWPotential(a=2.,normalize=0.35)] galpy-1.3.0/galpy/potential_src/0000755000076500000240000000000013236420072016712 5ustar bovystaff00000000000000galpy-1.3.0/galpy/potential_src/__init__.py0000644000076500000240000000000012671130470021013 0ustar bovystaff00000000000000galpy-1.3.0/galpy/potential_src/BurkertPotential.py0000644000076500000240000001225013003761364022566 0ustar bovystaff00000000000000############################################################################### # BurkertPotential.py: Potential with a Burkert density ############################################################################### import numpy from galpy.potential_src.Potential import Potential, _APY_LOADED if _APY_LOADED: from astropy import units class BurkertPotential(Potential): """BurkertPotential.py: Potential with a Burkert density .. math:: \\rho(r) = \\frac{\\mathrm{amp}}{(1+r/a)\\,(1+[r/a]^2)} """ def __init__(self,amp=1.,a=2.,normalize=False, ro=None,vo=None): """ NAME: __init__ PURPOSE: initialize a Burkert-density potential INPUT: amp - amplitude to be applied to the potential (default: 1); can be a Quantity with units of mass density or Gxmass density a = scale radius (can be Quantity) normalize - if True, normalize such that vc(1.,0.)=1., or, if given as a number, such that the force is this fraction of the force necessary to make vc(1.,0.)=1. ro=, vo= distance and velocity scales for translation into internal units (default from configuration file) OUTPUT: (none) HISTORY: 2013-04-10 - Written - Bovy (IAS) """ Potential.__init__(self,amp=amp,ro=ro,vo=vo,amp_units='density') if _APY_LOADED and isinstance(a,units.Quantity): a= a.to(units.kpc).value/self._ro self.a=a self._scale= self.a if normalize or \ (isinstance(normalize,(int,float)) \ and not isinstance(normalize,bool)): #pragma: no cover self.normalize(normalize) self.hasC= True self.hasC_dxdv= True return None def _evaluate(self,R,z,phi=0.,t=0.): """ NAME: _evaluate PURPOSE: evaluate the potential at R,z INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t - time OUTPUT: Phi(R,z) HISTORY: 2013-04-10 - Started - Bovy (IAS) """ x= numpy.sqrt(R**2.+z**2.)/self.a return -self.a**2.*numpy.pi/x*(-numpy.pi+2.*(1.+x)*numpy.arctan(1/x)+2.*(1.+x)*numpy.log(1.+x)+(1.-x)*numpy.log(1.+x**2.)) def _Rforce(self,R,z,phi=0.,t=0.): """ NAME: _Rforce PURPOSE: evaluate the radial force for this potential INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t - time OUTPUT: the radial force HISTORY: 2013-04-10 - Written - Bovy (IAS) """ r= numpy.sqrt(R**2.+z**2.) x= r/self.a return self.a*numpy.pi/x**2.*(numpy.pi-2.*numpy.arctan(1./x)-2.*numpy.log(1.+x)-numpy.log(1.+x**2.))*R/r def _zforce(self,R,z,phi=0.,t=0.): """ NAME: _zforce PURPOSE: evaluate the vertical force for this potential INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t - time OUTPUT: the vertical force HISTORY: 2013-04-10 - Written - Bovy (IAS) """ r= numpy.sqrt(R**2.+z**2.) x= r/self.a return self.a*numpy.pi/x**2.*(numpy.pi-2.*numpy.arctan(1./x)-2.*numpy.log(1.+x)-numpy.log(1.+x**2.))*z/r def _R2deriv(self,R,z,phi=0.,t=0.): """ NAME: _Rderiv PURPOSE: evaluate the second radial derivative for this potential INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t - time OUTPUT: the second radial derivative HISTORY: 2013-04-10 - Written - Bovy (IAS) """ r= numpy.sqrt(R**2.+z**2.) x= r/self.a return -numpy.pi/x**3./r**2.*(-4.*R**2.*r**3./(self.a**2.+r**2.)/(self.a+r)+(z**2.-2.*R**2.)*(numpy.pi-2.*numpy.arctan(1./x)-2.*numpy.log(1.+x)-numpy.log(1.+x**2.))) def _z2deriv(self,R,z,phi=0.,t=0.): """ NAME: _z2deriv PURPOSE: evaluate the second vertical derivative for this potential INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t- time OUTPUT: the second vertical derivative HISTORY: 2012-07-26 - Written - Bovy (IAS@MPIA) """ return self._R2deriv(z,R) #Spherical potential def _dens(self,R,z,phi=0.,t=0.): """ NAME: _dens PURPOSE: evaluate the density force for this potential INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t - time OUTPUT: the density HISTORY: 2013-01-09 - Written - Bovy (IAS) """ r= numpy.sqrt(R**2.+z**2.) x= r/self.a return 1./(1.+x)/(1.+x**2.) galpy-1.3.0/galpy/potential_src/CosmphiDiskPotential.py0000644000076500000240000001717713157625526023413 0ustar bovystaff00000000000000############################################################################### # CosmphiDiskPotential: cos(mphi) potential ############################################################################### import math from galpy.potential_src.planarPotential import planarPotential, _APY_LOADED if _APY_LOADED: from astropy import units _degtorad= math.pi/180. class CosmphiDiskPotential(planarPotential): """Class that implements the disk potential .. math:: \\Phi(R,\\phi) = \\mathrm{amp}\\,\\phi_0\\,\\,\\cos\\left[m\\,(\\phi-\\phi_b)\\right]\\times \\begin{cases} \\left(\\frac{R}{R_1}\\right)^p\\,, & \\text{for}\\ R \\geq R_b\\\\ \\left[2-\\left(\\frac{R_b}{R}\\right)^p\\right]\\times\\left(\\frac{R_b}{R_1}\\right)^p\\,, & \\text{for}\\ R\\leq R_b. \\end{cases} This potential can be grown between :math:`t_{\mathrm{form}}` and :math:`t_{\mathrm{form}}+T_{\mathrm{steady}}` in a similar way as DehnenBarPotential by wrapping it with a DehnenSmoothWrapperPotential """ def __init__(self,amp=1.,phib=25.*_degtorad, p=1.,phio=0.01,m=4,r1=1.,rb=None, cp=None,sp=None,ro=None,vo=None): """ NAME: __init__ PURPOSE: initialize an cosmphi disk potential INPUT: amp= amplitude to be applied to the potential (default: 1.), degenerate with phio below, but kept for overall consistency with potentials m= cos( m * (phi - phib) ), integer p= power-law index of the phi(R) = (R/Ro)^p part r1= (1.) normalization radius for the amplitude (can be Quantity); amp x phio is only the potential at (R,phi) = (r1,pib) when r1 > rb; otherwise more complicated rb= (None) if set, break radius for power-law: potential R^p at R > Rb, R^-p at R < Rb, potential and force continuous at Rb Either: a) phib= angle (in rad; default=25 degree; or can be Quantity) phio= potential perturbation (in terms of phio/vo^2 if vo=1 at Ro=1; or can be Quantity with units of velocity-squared) b) cp, sp= m * phio * cos(m * phib), m * phio * sin(m * phib); can be Quantity with units of velocity-squared) OUTPUT: (none) HISTORY: 2011-10-27 - Started - Bovy (IAS) 2017-09-16 - Added break radius rb - Bovy (UofT) """ planarPotential.__init__(self,amp=amp,ro=ro,vo=vo) if _APY_LOADED and isinstance(phib,units.Quantity): phib= phib.to(units.rad).value if _APY_LOADED and isinstance(r1,units.Quantity): r1= r1.to(units.kpc).value/self._ro if _APY_LOADED and isinstance(rb,units.Quantity): rb= rb.to(units.kpc).value/self._ro if _APY_LOADED and isinstance(phio,units.Quantity): phio= phio.to(units.km**2/units.s**2).value/self._vo**2. if _APY_LOADED and isinstance(cp,units.Quantity): cp= cp.to(units.km**2/units.s**2).value/self._vo**2. if _APY_LOADED and isinstance(sp,units.Quantity): sp= sp.to(units.km**2/units.s**2).value/self._vo**2. # Back to old definition self._r1p= r1**p self._amp/= self._r1p self.hasC= False self._m= int(m) # make sure this is an int if cp is None or sp is None: self._phib= phib self._mphio= phio*self._m else: self._mphio= math.sqrt(cp*cp+sp*sp) self._phib= math.atan(sp/cp)/self._m if m < 2. and cp < 0.: self._phib= math.pi+self._phib self._p= p if rb is None: self._rb= 0. self._rbp= 1. # never used, but for p < 0 general expr fails self._rb2p= 1. else: self._rb= rb self._rbp= self._rb**self._p self._rb2p= self._rbp**2. self._mphib= self._m*self._phib self.hasC= True self.hasC_dxdv= True def _evaluate(self,R,phi=0.,t=0.): """ NAME: _evaluate PURPOSE: evaluate the potential at R,phi,t INPUT: R - Galactocentric cylindrical radius phi - azimuth t - time OUTPUT: Phi(R,phi,t) HISTORY: 2011-10-19 - Started - Bovy (IAS) """ if R < self._rb: return self._mphio/self._m*math.cos(self._m*phi-self._mphib)\ *self._rbp*(2.*self._r1p-self._rbp/R**self._p) else: return self._mphio/self._m*R**self._p\ *math.cos(self._m*phi-self._mphib) def _Rforce(self,R,phi=0.,t=0.): """ NAME: _Rforce PURPOSE: evaluate the radial force for this potential INPUT: R - Galactocentric cylindrical radius phi - azimuth t - time OUTPUT: the radial force HISTORY: 2011-10-19 - Written - Bovy (IAS) """ if R < self._rb: return -self._p*self._mphio/self._m*self._rb2p/R**(self._p+1.)\ *math.cos(self._m*phi-self._mphib) else: return -self._p*self._mphio/self._m*R**(self._p-1.)\ *math.cos(self._m*phi-self._mphib) def _phiforce(self,R,phi=0.,t=0.): """ NAME: _phiforce PURPOSE: evaluate the azimuthal force for this potential INPUT: R - Galactocentric cylindrical radius phi - azimuth t - time OUTPUT: the azimuthal force HISTORY: 2011-10-19 - Written - Bovy (IAS) """ if R < self._rb: return self._mphio*math.sin(self._m*phi-self._mphib)\ *self._rbp*(2.*self._r1p-self._rbp/R**self._p) else: return self._mphio*R**self._p*math.sin(self._m*phi-self._mphib) def _R2deriv(self,R,phi=0.,t=0.): if R < self._rb: return -self._p*(self._p+1.)*self._mphio/self._m\ *self._rb2p/R**(self._p+2.)*math.cos(self._m*phi-self._mphib) else: return self._p*(self._p-1.)/self._m*self._mphio*R**(self._p-2.)\ *math.cos(self._m*phi-self._mphib) def _phi2deriv(self,R,phi=0.,t=0.): if R < self._rb: return -self._m*self._mphio*math.cos(self._m*phi-self._mphib)\ *self._rbp*(2.*self._r1p-self._rbp/R**self._p) else: return -self._m*self._mphio*R**self._p\ *math.cos(self._m*phi-self._mphib) def _Rphideriv(self,R,phi=0.,t=0.): if R < self._rb: return -self._p*self._mphio/self._m*self._rb2p/R**(self._p+1.)\ *math.sin(self._m*phi-self._mphib) else: return -self._p*self._mphio*R**(self._p-1.)*\ math.sin(self._m*phi-self._mphib) class LopsidedDiskPotential(CosmphiDiskPotential): """Class that implements the disk potential .. math:: \\Phi(R,\\phi) = \\mathrm{amp}\\,\\phi_0\\,\\left(\\frac{R}{R_1}\\right)^p\\,\\cos\\left(\\phi-\\phi_b\\right) Special case of CosmphiDiskPotential with m=1; see documentation for CosmphiDiskPotential """ def __init__(self,amp=1.,phib=25.*_degtorad, p=1.,phio=0.01,r1=1., cp=None,sp=None,ro=None,vo=None): CosmphiDiskPotential.__init__(self, amp=amp, phib=phib, p=p,phio=phio,m=1., cp=cp,sp=sp,ro=ro,vo=vo) self.hasC= True self.hasC_dxdv= True galpy-1.3.0/galpy/potential_src/DehnenBarPotential.py0000644000076500000240000003545513157250707023016 0ustar bovystaff00000000000000############################################################################### # DehnenBarPotential: Dehnen (2000)'s bar potential ############################################################################### import numpy from galpy.util import bovy_conversion from galpy.potential_src.Potential import Potential, _APY_LOADED if _APY_LOADED: from astropy import units _degtorad= numpy.pi/180. class DehnenBarPotential(Potential): """Class that implements the Dehnen bar potential (`Dehnen 2000 `__), generalized to 3D following `Monari et al. (2016) `__ .. math:: \\Phi(R,z,\\phi) = A_b(t)\\,\\cos\\left(2\\,(\\phi-\\Omega_b\\,t)\\right))\\,\\left(\\frac{R}{r}\\right)^2\\,\\times \\begin{cases} -(R_b/r)^3\\,, & \\text{for}\\ r \\geq R_b\\\\ (r/R_b)^3-2\\,, & \\text{for}\\ r\\leq R_b. \\end{cases} where :math:`r^2 = R^2+z^2` is the spherical radius and .. math:: A_b(t) = A_f\\,\\left(\\frac{3}{16}\\xi^5-\\frac{5}{8}\\xi^3+\\frac{15}{16}\\xi+\\frac{1}{2}\\right)\\,, \\xi = 2\\frac{t/T_b-t_\\mathrm{form}}{T_\mathrm{steady}}-1\\,,\ \mathrm{if}\ t_\\mathrm{form} \\leq \\frac{t}{T_b} \\leq t_\\mathrm{form}+T_\\mathrm{steady} and .. math:: A_b(t) = \\begin{cases} 0\\,, & \\frac{t}{T_b} < t_\mathrm{form}\\\\ A_f\\,, & \\frac{t}{T_b} > t_\mathrm{form}+T_\mathrm{steady} \\end{cases} where .. math:: T_b = \\frac{2\pi}{\\Omega_b} is the bar period and the strength can also be specified using :math:`\\alpha` .. math:: \\alpha = 3\\,\\frac{A_f}{v_0^2}\\,\\left(\\frac{R_b}{r_0}\\right)^3\,. """ normalize= property() # turn off normalize def __init__(self,amp=1.,omegab=None,rb=None,chi=0.8, rolr=0.9,barphi=25.*_degtorad, tform=-4.,tsteady=None,beta=0., alpha=0.01,Af=None,ro=None,vo=None): """ NAME: __init__ PURPOSE: initialize a Dehnen bar potential INPUT: amp - amplitude to be applied to the potential (default: 1., see alpha or Ab below) barphi - angle between sun-GC line and the bar's major axis (in rad; default=25 degree; or can be Quantity)) tform - start of bar growth / bar period (default: -4) tsteady - time from tform at which the bar is fully grown / bar period (default: -tform/2, st the perturbation is fully grown at tform/2) Either provide: a) rolr - radius of the Outer Lindblad Resonance for a circular orbit (can be Quantity) chi - fraction R_bar / R_CR (corotation radius of bar) alpha - relative bar strength (default: 0.01) beta - power law index of rotation curve (to calculate OLR, etc.) b) omegab - rotation speed of the bar (can be Quantity) rb - bar radius (can be Quantity) Af - bar strength (can be Quantity) OUTPUT: (none) HISTORY: 2010-11-24 - Started - Bovy (NYU) 2017-06-23 - Converted to 3D following Monari et al. (2016) - Bovy (UofT/CCA) """ Potential.__init__(self,amp=amp,ro=ro,vo=vo) if _APY_LOADED and isinstance(barphi,units.Quantity): barphi= barphi.to(units.rad).value if _APY_LOADED and isinstance(rolr,units.Quantity): rolr= rolr.to(units.kpc).value/self._ro if _APY_LOADED and isinstance(rb,units.Quantity): rb= rb.to(units.kpc).value/self._ro if _APY_LOADED and isinstance(omegab,units.Quantity): omegab= omegab.to(units.km/units.s/units.kpc).value\ /bovy_conversion.freq_in_kmskpc(self._vo,self._ro) if _APY_LOADED and isinstance(Af,units.Quantity): Af= Af.to(units.km**2/units.s**2).value/self._vo**2. self.hasC= True self.hasC_dxdv= True self.isNonAxi= True self._barphi= barphi if omegab is None: self._rolr= rolr self._chi= chi self._beta= beta #Calculate omegab and rb self._omegab= 1./((self._rolr**(1.-self._beta))/(1.+numpy.sqrt((1.+self._beta)/2.))) self._rb= self._chi*self._omegab**(1./(self._beta-1.)) self._alpha= alpha self._af= self._alpha/3./self._rb**3. else: self._omegab= omegab self._rb= rb self._af= Af self._tb= 2.*numpy.pi/self._omegab self._tform= tform*self._tb if tsteady is None: self._tsteady= self._tform/2. else: self._tsteady= self._tform+tsteady*self._tb def _evaluate(self,R,z,phi=0.,t=0.): """ NAME: _evaluate PURPOSE: evaluate the potential at R,phi,t INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t - time OUTPUT: Phi(R,z,phi,t) HISTORY: 2010-11-24 - Started - Bovy (NYU) """ #Calculate relevant time if t < self._tform: smooth= 0. elif t < self._tsteady: deltat= t-self._tform xi= 2.*deltat/(self._tsteady-self._tform)-1. smooth= (3./16.*xi**5.-5./8*xi**3.+15./16.*xi+.5) else: #bar is fully on smooth= 1. r2= R**2.+z**2. r= numpy.sqrt(r2) if r <= self._rb: return self._af*smooth*numpy.cos(2.*(phi-self._omegab*t-self._barphi))\ *((r/self._rb)**3.-2.)*R**2./r2 else: return -self._af*smooth*numpy.cos(2.*(phi-self._omegab*t- self._barphi))\ *(self._rb/r)**3.\ *R**2./r2 def _Rforce(self,R,z,phi=0.,t=0.): """ NAME: _Rforce PURPOSE: evaluate the radial force for this potential INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t - time OUTPUT: the radial force HISTORY: 2010-11-24 - Written - Bovy (NYU) """ #Calculate relevant time if t < self._tform: smooth= 0. elif t < self._tsteady: deltat= t-self._tform xi= 2.*deltat/(self._tsteady-self._tform)-1. smooth= (3./16.*xi**5.-5./8*xi**3.+15./16.*xi+.5) else: #bar is fully on smooth= 1. r= numpy.sqrt(R**2.+z**2.) if r <= self._rb: return -self._af*smooth*numpy.cos(2.*(phi-self._omegab*t -self._barphi))\ *((r/self._rb)**3.*R*(3.*R**2.+2.*z**2.)-4.*R*z**2.)/r**4. else: return -self._af*smooth*numpy.cos(2.*(phi-self._omegab*t- self._barphi))\ *(self._rb/r)**3.*R/r**4.*(3.*R**2.-2.*z**2.) def _phiforce(self,R,z,phi=0.,t=0.): """ NAME: _phiforce PURPOSE: evaluate the azimuthal force for this potential INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t - time OUTPUT: the azimuthal force HISTORY: 2010-11-24 - Written - Bovy (NYU) """ #Calculate relevant time if t < self._tform: smooth= 0. elif t < self._tsteady: deltat= t-self._tform xi= 2.*deltat/(self._tsteady-self._tform)-1. smooth= (3./16.*xi**5.-5./8*xi**3.+15./16.*xi+.5) else: #bar is fully on smooth= 1. r2= R**2.+z**2. r= numpy.sqrt(r2) if r <= self._rb: return 2.*self._af*smooth*numpy.sin(2.*(phi-self._omegab*t- self._barphi))\ *((r/self._rb)**3.-2.)*R**2./r2 else: return -2.*self._af*smooth*numpy.sin(2.*(phi-self._omegab*t- self._barphi))\ *(self._rb/r)**3.*R**2./r2 def _zforce(self,R,z,phi=0.,t=0.): """ NAME: _zforce PURPOSE: evaluate the vertical force for this potential INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t - time OUTPUT: the vertical force HISTORY: 2017-06-23 - Written - Bovy (NYU) """ #Calculate relevant time if t < self._tform: smooth= 0. elif t < self._tsteady: deltat= t-self._tform xi= 2.*deltat/(self._tsteady-self._tform)-1. smooth= (3./16.*xi**5.-5./8*xi**3.+15./16.*xi+.5) else: #bar is fully on smooth= 1. r= numpy.sqrt(R**2.+z**2.) if r <= self._rb: return -self._af*smooth*numpy.cos(2.*(phi-self._omegab*t -self._barphi))\ *((r/self._rb)**3.+4.)*R**2.*z/r**4. else: return -5.*self._af*smooth*numpy.cos(2.*(phi-self._omegab*t- self._barphi))\ *(self._rb/r)**3.*R**2.*z/r**4. def _R2deriv(self,R,z,phi=0.,t=0.): #Calculate relevant time if t < self._tform: smooth= 0. elif t < self._tsteady: deltat= t-self._tform xi= 2.*deltat/(self._tsteady-self._tform)-1. smooth= (3./16.*xi**5.-5./8*xi**3.+15./16.*xi+.5) else: #bar is fully on smooth= 1. r= numpy.sqrt(R**2.+z**2.) if r <= self._rb: return self._af*smooth*numpy.cos(2.*(phi-self._omegab*t -self._barphi))\ *((r/self._rb)**3.*((9.*R**2.+2.*z**2.)/r**4. -R**2./r**6.*(3.*R**2.+2.*z**2.))\ +4.*z**2./r**6.*(4.*R**2.-r**2.)) else: return self._af*smooth*numpy.cos(2.*(phi-self._omegab*t- self._barphi))\ *(self._rb/r)**3./r**6.*((r**2.-7.*R**2.)*(3.*R**2.-2.*z**2.)\ +6.*R**2.*r**2.) def _phi2deriv(self,R,z,phi=0.,t=0.): #Calculate relevant time if t < self._tform: smooth= 0. elif t < self._tsteady: deltat= t-self._tform xi= 2.*deltat/(self._tsteady-self._tform)-1. smooth= (3./16.*xi**5.-5./8*xi**3.+15./16.*xi+.5) else: #bar is fully on smooth= 1. r= numpy.sqrt(R**2.+z**2.) if r <= self._rb: return -4.*self._af*smooth*numpy.cos(2.*(phi-self._omegab*t- self._barphi))\ *((r/self._rb)**3.-2.)*R**2./r**2. else: return 4.*self._af*smooth*numpy.cos(2.*(phi-self._omegab*t- self._barphi))\ *(self._rb/r)**3.*R**2./r**2. def _Rphideriv(self,R,z,phi=0.,t=0.): #Calculate relevant time if t < self._tform: smooth= 0. elif t < self._tsteady: deltat= t-self._tform xi= 2.*deltat/(self._tsteady-self._tform)-1. smooth= (3./16.*xi**5.-5./8*xi**3.+15./16.*xi+.5) else: #bar is fully on smooth= 1. r= numpy.sqrt(R**2.+z**2.) if r <= self._rb: return -2.*self._af*smooth*numpy.sin(2.*(phi-self._omegab*t -self._barphi))\ *((r/self._rb)**3.*R*(3.*R**2.+2.*z**2.)-4.*R*z**2.)/r**4. else: return -2.*self._af*smooth*numpy.sin(2.*(phi-self._omegab*t- self._barphi))\ *(self._rb/r)**3.*R/r**4.*(3.*R**2.-2.*z**2.) def _z2deriv(self,R,z,phi=0.,t=0.): #Calculate relevant time if t < self._tform: smooth= 0. elif t < self._tsteady: deltat= t-self._tform xi= 2.*deltat/(self._tsteady-self._tform)-1. smooth= (3./16.*xi**5.-5./8*xi**3.+15./16.*xi+.5) else: #bar is fully on smooth= 1. r= numpy.sqrt(R**2.+z**2.) if r <= self._rb: return self._af*smooth*numpy.cos(2.*(phi-self._omegab*t -self._barphi))\ *R**2./r**6.*((r/self._rb)**3.*(r**2.-z**2.) +4.*(r**2.-4.*z**2.)) else: return 5.*self._af*smooth*numpy.cos(2.*(phi-self._omegab*t- self._barphi))\ *(self._rb/r)**3.*R**2./r**6.*(r**2.-7.*z**2.) def _Rzderiv(self,R,z,phi=0.,t=0.): #Calculate relevant time if t < self._tform: smooth= 0. elif t < self._tsteady: deltat= t-self._tform xi= 2.*deltat/(self._tsteady-self._tform)-1. smooth= (3./16.*xi**5.-5./8*xi**3.+15./16.*xi+.5) else: #bar is fully on smooth= 1. r= numpy.sqrt(R**2.+z**2.) if r <= self._rb: return self._af*smooth*numpy.cos(2.*(phi-self._omegab*t -self._barphi))\ *R*z/r**6.*((r/self._rb)**3.*(2.*r**2.-R**2.) +8.*(r**2.-2.*R**2.)) else: return 5.*self._af*smooth*numpy.cos(2.*(phi-self._omegab*t- self._barphi))\ *(self._rb/r)**3.*R*z/r**6.*(2.*r**2.-7.*R**2.) def tform(self): #pragma: no cover """ NAME: tform PURPOSE: return formation time of the bar INPUT: (none) OUTPUT: tform in normalized units HISTORY: 2011-03-08 - Written - Bovy (NYU) """ return self._tform def OmegaP(self): """ NAME: OmegaP PURPOSE: return the pattern speed INPUT: (none) OUTPUT: pattern speed HISTORY: 2011-10-10 - Written - Bovy (IAS) """ return self._omegab galpy-1.3.0/galpy/potential_src/DehnenSmoothWrapperPotential.py0000644000076500000240000000573513153272706025121 0ustar bovystaff00000000000000############################################################################### # DehnenSmoothWrapperPotential.py: Wrapper to smoothly grow a potential ############################################################################### from galpy.potential_src.WrapperPotential import parentWrapperPotential from galpy.potential_src.Potential import _APY_LOADED from galpy.util import bovy_conversion if _APY_LOADED: from astropy import units class DehnenSmoothWrapperPotential(parentWrapperPotential): """Potential wrapper class that implements the growth of a gravitational potential following `Dehnen (2000) `__. The amplitude A applied to a potential wrapped by an instance of this class is changed as .. math:: A(t) = amp\\,\\left(\\frac{3}{16}\\xi^5-\\frac{5}{8}\\xi^3+\\frac{15}{16}\\xi+\\frac{1}{2}\\right) where .. math:: \\xi = \\begin{cases} 0 & t < t_\\mathrm{form}\\\\ 2\\left(\\frac{t-t_\\mathrm{form}}{t_\mathrm{steady}}\\right)-1\\,, & t_\\mathrm{form} \\leq t \\leq t_\\mathrm{form}+t_\\mathrm{steady}\\\\ 1 & t > t_\\mathrm{form}+t_\\mathrm{steady} \\end{cases} """ def __init__(self,amp=1.,pot=None,tform=-4.,tsteady=None,ro=None,vo=None): """ NAME: __init__ PURPOSE: initialize a DehnenSmoothWrapper Potential INPUT: amp - amplitude to be applied to the potential (default: 1.) pot - Potential instance or list thereof; the amplitude of this will be grown by this wrapper tform - start of growth (can be a Quantity) tsteady - time from tform at which the potential is fully grown (default: -tform/2, st the perturbation is fully grown at tform/2; can be a Quantity) OUTPUT: (none) HISTORY: 2017-06-26 - Started - Bovy (UofT) """ if _APY_LOADED and isinstance(tform,units.Quantity): tform= tform.to(units.Gyr).value\ /bovy_conversion.time_in_Gyr(self._vo,self._ro) if _APY_LOADED and isinstance(tsteady,units.Quantity): tsteady= tsteady.to(units.Gyr).value\ /bovy_conversion.time_in_Gyr(self._vo,self._ro) self._tform= tform if tsteady is None: self._tsteady= self._tform/2. else: self._tsteady= self._tform+tsteady self.hasC= True self.hasC_dxdv= True def _smooth(self,t): #Calculate relevant time if t < self._tform: smooth= 0. elif t < self._tsteady: deltat= t-self._tform xi= 2.*deltat/(self._tsteady-self._tform)-1. smooth= (3./16.*xi**5.-5./8*xi**3.+15./16.*xi+.5) else: #bar is fully on smooth= 1. return smooth def _wrap(self,attribute,*args,**kwargs): return self._smooth(kwargs.get('t',0.))\ *self._wrap_pot_func(attribute)(self._pot,*args,**kwargs) galpy-1.3.0/galpy/potential_src/DiskSCFPotential.py0000644000076500000240000004626013031266224022402 0ustar bovystaff00000000000000############################################################################### # DiskSCFPotential.py: Potential expansion for disk+halo potentials ############################################################################### import copy import numpy from scipy.misc import logsumexp from galpy.potential_src.Potential import Potential, _APY_LOADED from galpy.potential_src.SCFPotential import SCFPotential, \ scf_compute_coeffs_axi, scf_compute_coeffs if _APY_LOADED: from astropy import units class DiskSCFPotential(Potential): """Class that implements a basis-function-expansion technique for solving the Poisson equation for disk (+halo) systems. We solve the Poisson equation for a given density :math:`\\rho(R,\phi,z)` by introducing *K* helper function pairs :math:`[\\Sigma_i(R),h_i(z)]`, with :math:`h_i(z) = \mathrm{d}^2 H(z) / \mathrm{d} z^2` and search for solutions of the form .. math:: \Phi(R,\phi,z = \Phi_{\mathrm{ME}}(R,\phi,z) + 4\pi G\sum_i \\Sigma_i(r)\,H_i(z)\,, where :math:`r` is the spherical radius :math:`r^2 = R^2+z^2`. We can solve for :math:`\Phi_{\mathrm{ME}}(R,\phi,z)` by solving .. math:: \\frac{\\Delta \Phi_{\mathrm{ME}}(R,\phi,z)}{4\pi G} = \\rho(R,\phi,z) - \sum_i\left\{ \Sigma_i(r)\,h_i(z) + \\frac{\mathrm{d}^2 \Sigma_i(r)}{\mathrm{d} r^2}\,H_i(z)+\\frac{2}{r}\,\\frac{\mathrm{d} \Sigma_i(r)}{\mathrm{d} r}\left[H_i(z)+z\,\\frac{\mathrm{d}H_i(z)}{\mathrm{d} z}\\right]\\right\}\,. We solve this equation by using the :ref:`SCFPotential ` class and methods (:ref:`scf_compute_coeffs_axi ` or :ref:`scf_compute_coeffs ` depending on whether :math:`\\rho(R,\phi,z)` is axisymmetric or not). This technique works very well if the disk portion of the potential can be exactly written as :math:`\\rho_{\mathrm{disk}} = \sum_i \Sigma_i(R)\,h_i(z)`, because the effective density on the right-hand side of this new Poisson equation is then not 'disky' and can be well represented using spherical harmonics. But the technique is general and can be used to compute the potential of any disk+halo potential; the closer the disk is to :math:`\\rho_{\mathrm{disk}} \\approx \sum_i \Sigma_i(R)\,h_i(z)`, the better the technique works. This technique was introduced by `Kuijken & Dubinski (1995) `__ and was popularized by `Dehnen & Binney (1998) `__. The current implementation is a slight generalization of the technique in those papers and uses the SCF approach of `Hernquist & Ostriker (1992) `__ to solve the Poisson equation for :math:`\Phi_{\mathrm{ME}}(R,\phi,z)` rather than solving it on a grid using spherical harmonics and interpolating the solution (as done in `Dehnen & Binney 1998 `__). """ def __init__(self,amp=1.,normalize=False, dens= lambda R,z: 13.5*numpy.exp(-3.*R)\ *numpy.exp(-27.*numpy.fabs(z)), Sigma={'type':'exp','h':1./3.,'amp':1.}, hz={'type':'exp','h':1./27.}, Sigma_amp=None,dSigmadR=None,d2SigmadR2=None, Hz=None,dHzdz=None, N=10,L=10,a=1.,radial_order=None,costheta_order=None, phi_order=None, ro=None,vo=None): """ NAME: __init__ PURPOSE: initialize a DiskSCF Potential INPUT: amp - amplitude to be applied to the potential (default: 1); cannot have units currently normalize - if True, normalize such that vc(1.,0.)=1., or, if given as a number, such that the force is this fraction of the force necessary to make vc(1.,0.)=1. ro=, vo= distance and velocity scales for translation into internal units (default from configuration file) dens= function of R,z[,phi optional] that gives the density [in natural units, cannot return a Quantity currently] N=, L=, a=, radial_order=, costheta_order=, phi_order= keywords setting parameters for SCF solution for Phi_ME (see :ref:`scf_compute_coeffs_axi ` or :ref:`scf_compute_coeffs ` depending on whether :math:`\\rho(R,\phi,z)` is axisymmetric or not) Either: (a) Sigma= Dictionary of surface density (example: {'type':'exp','h':1./3.,'amp':1.,'Rhole':0.} for amp x exp(-Rhole/R-R/h) ) hz= Dictionary of vertical profile, either 'exp' or 'sech2' (example {'type':'exp','h':1./27.} for exp(-|z|/h)/[2h], sech2 is sech^2(z/[2h])/[4h]) (b) Sigma= function of R that gives the surface density dSigmadR= function that gives d Sigma / d R d2SigmadR2= function that gives d^2 Sigma / d R^2 Sigma_amp= amplitude to apply to all Sigma functions hz= function of z that gives the vertical profile Hz= function of z such that d^2 Hz(z) / d z^2 = hz dHzdz= function of z that gives d Hz(z) / d z In both of these cases lists of arguments can be given for multiple disk components; can't mix (a) and (b) in these lists; if hz is a single item the same vertical profile is assumed for all Sigma OUTPUT: DiskSCFPotential object HISTORY: 2016-12-26 - Written - Bovy (UofT) """ Potential.__init__(self,amp=amp,ro=ro,vo=vo,amp_units=None) if _APY_LOADED and isinstance(a,units.Quantity): a= a.to(units.kpc).value/self._ro # Parse and store given functions self.isNonAxi= dens.__code__.co_argcount == 3 self._parse_Sigma(Sigma_amp,Sigma,dSigmadR,d2SigmadR2) self._parse_hz(hz,Hz,dHzdz) if self.isNonAxi: self._inputdens= dens else: self._inputdens= lambda R,z,phi: dens(R,z) # Solve Poisson equation for Phi_ME if not self.isNonAxi: dens_func= lambda R,z: phiME_dens(R,z,0.,self._inputdens, self._Sigma,self._dSigmadR, self._d2SigmadR2, self._hz,self._Hz, self._dHzdz,self._Sigma_amp) Acos, Asin= scf_compute_coeffs_axi(dens_func,N,L,a=a, radial_order=radial_order, costheta_order=costheta_order) else: dens_func= lambda R,z,phi: phiME_dens(R,z,phi,self._inputdens, self._Sigma,self._dSigmadR, self._d2SigmadR2, self._hz,self._Hz, self._dHzdz,self._Sigma_amp) Acos, Asin= scf_compute_coeffs(dens_func,N,L,a=a, radial_order=radial_order, costheta_order=costheta_order, phi_order=phi_order) self._phiME_dens_func= dens_func self._scf= SCFPotential(amp=1.,Acos=Acos,Asin=Asin,a=a,ro=None,vo=None) if not self._Sigma_dict is None and not self._hz_dict is None: self.hasC= True if normalize or \ (isinstance(normalize,(int,float)) \ and not isinstance(normalize,bool)): self.normalize(normalize) return None def _parse_Sigma(self,Sigma_amp,Sigma,dSigmadR,d2SigmadR2): """ NAME: _parse_Sigma PURPOSE: Parse the various input options for Sigma* functions HISTORY: 2016-12-27 - Written - Bovy (UofT/CCA) """ if isinstance(Sigma,dict): Sigma= [Sigma] try: nsigma= len(Sigma) except TypeError: Sigma_amp= [Sigma_amp] Sigma= [Sigma] dSigmadR= [dSigmadR] d2SigmadR2= [d2SigmadR2] nsigma= 1 self._nsigma= nsigma self._Sigma_amp= Sigma_amp self._Sigma= Sigma self._dSigmadR= dSigmadR self._d2SigmadR2= d2SigmadR2 if isinstance(Sigma[0],dict): self._Sigma_dict= copy.copy(Sigma) self._parse_Sigma_dict() else: self._Sigma_dict= None return None def _parse_Sigma_dict(self): Sigma_amp, Sigma, dSigmadR, d2SigmadR2= [], [], [], [] for ii in range(self._nsigma): ta, ts, tds, td2s= self._parse_Sigma_dict_indiv(self._Sigma[ii]) Sigma_amp.append(ta) Sigma.append(ts) dSigmadR.append(tds) d2SigmadR2.append(td2s) self._Sigma_amp= Sigma_amp self._Sigma= Sigma self._dSigmadR= dSigmadR self._d2SigmadR2= d2SigmadR2 return None def _parse_Sigma_dict_indiv(self,Sigma): stype= Sigma.get('type','exp') if stype == 'exp' and not 'Rhole' in Sigma: rd= Sigma.get('h',1./3.) ta= Sigma.get('amp',1.) ts= lambda R, trd=rd: numpy.exp(-R/trd) tds= lambda R, trd=rd: -numpy.exp(-R/trd)/trd td2s= lambda R, trd=rd: numpy.exp(-R/trd)/trd**2. elif stype == 'expwhole' or (stype == 'exp' and 'Rhole' in Sigma): rd= Sigma.get('h',1./3.) rm= Sigma.get('Rhole',0.5) ta= Sigma.get('amp',1.) ts= lambda R, trd=rd, trm=rm: numpy.exp(-trm/R-R/trd) tds= lambda R, trd=rd, trm=rm: \ (trm/R**2.-1./trd)*numpy.exp(-trm/R-R/trd) td2s= lambda R, trd=rd,trm=rm: \ ((trm/R**2.-1./trd)**2.-2.*trm/R**3.)*numpy.exp(-trm/R-R/trd) return (ta,ts,tds,td2s) def _parse_hz(self,hz,Hz,dHzdz): """ NAME: _parse_hz PURPOSE: Parse the various input options for Sigma* functions HISTORY: 2016-12-27 - Written - Bovy (UofT/CCA) """ if isinstance(hz,dict): hz= [hz] try: nhz= len(hz) except TypeError: hz= [hz] Hz= [Hz] dHzdz= [dHzdz] nhz= 1 if nhz != self._nsigma and nhz != 1: raise ValueError('Number of hz functions needs to be equal to the number of Sigma functions or to 1') if nhz == 1 and self._nsigma > 1: hz= [hz[0] for ii in range(self._nsigma)] if not isinstance(hz[0],dict): Hz= [Hz[0] for ii in range(self._nsigma)] dHzdz= [dHzdz[0] for ii in range(self._nsigma)] self._Hz= Hz self._hz= hz self._dHzdz= dHzdz self._nhz= len(self._hz) if isinstance(hz[0],dict): self._hz_dict= copy.copy(hz) self._parse_hz_dict() else: self._hz_dict= None return None def _parse_hz_dict(self): hz, Hz, dHzdz= [], [], [] for ii in range(self._nhz): th, tH, tdH= self._parse_hz_dict_indiv(self._hz[ii]) hz.append(th) Hz.append(tH) dHzdz.append(tdH) self._hz= hz self._Hz= Hz self._dHzdz= dHzdz return None def _parse_hz_dict_indiv(self,hz): htype= hz.get('type','exp') if htype == 'exp': zd= hz.get('h',0.0375) th= lambda z, tzd=zd: 1./2./tzd*numpy.exp(-numpy.fabs(z)/tzd) tH= lambda z, tzd= zd: (numpy.exp(-numpy.fabs(z)/tzd)-1. +numpy.fabs(z)/tzd)*tzd/2. tdH= lambda z, tzd= zd: 0.5*numpy.sign(z)\ *(1.-numpy.exp(-numpy.fabs(z)/tzd)) elif htype == 'sech2': zd= hz.get('h',0.0375) th= lambda z, tzd=zd: 1./numpy.cosh(z/2./tzd)**2./4./tzd # Avoid overflow in cosh tH= lambda z, tzd= zd: \ tzd*(logsumexp(numpy.array([z/2./tzd,-z/2./tzd]),axis=0)\ -numpy.log(2.)) tdH= lambda z, tzd= zd: numpy.tanh(z/2./tzd)/2. return (th,tH,tdH) def _evaluate(self,R,z,phi=0.,t=0.): """ NAME: _evaluate PURPOSE: evaluate the potential at (R,z, phi) INPUT: R - Cylindrical Galactocentric radius z - vertical height phi - azimuth t - time OUTPUT: potential at (R,z, phi) HISTORY: 2016-12-26 - Written - Bovy (UofT/CCA) """ r= numpy.sqrt(R**2.+z**2.) out= self._scf(R,z,phi=phi,use_physical=False) for a,s,H in zip(self._Sigma_amp,self._Sigma,self._Hz): out+= 4.*numpy.pi*a*s(r)*H(z) return out def _Rforce(self,R,z,phi=0, t=0): """ NAME: _Rforce PURPOSE: evaluate the radial force at (R,z, phi) INPUT: R - Cylindrical Galactocentric radius z - vertical height phi - azimuth t - time OUTPUT: radial force at (R,z, phi) HISTORY: 2016-12-26 - Written - Bovy (UofT/CCA) """ r= numpy.sqrt(R**2.+z**2.) out= self._scf.Rforce(R,z,phi=phi,use_physical=False) for a,ds,H in zip(self._Sigma_amp,self._dSigmadR,self._Hz): out-= 4.*numpy.pi*a*ds(r)*H(z)*R/r return out def _zforce(self,R,z,phi=0,t=0): """ NAME: _zforce PURPOSE: evaluate the vertical force at (R,z, phi) INPUT: R - Cylindrical Galactocentric radius z - vertical height phi - azimuth t - time OUTPUT: vertical force at (R,z, phi) HISTORY: 2016-12-26 - Written - Bovy (UofT/CCA) """ r= numpy.sqrt(R**2.+z**2.) out= self._scf.zforce(R,z,phi=phi,use_physical=False) for a,s,ds,H,dH in zip(self._Sigma_amp,self._Sigma,self._dSigmadR, self._Hz,self._dHzdz): out-= 4.*numpy.pi*a*(ds(r)*H(z)*z/r+s(r)*dH(z)) return out def _phiforce(self,R,z,phi=0.,t=0.): """ NAME: _phiforce PURPOSE: evaluate the azimuthal force for this potential INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t - time OUTPUT: the azimuthal force HISTORY: 2016-12-26 - Written - Bovy (UofT) """ return self._scf.phiforce(R,z,phi=phi,use_physical=False) def _R2deriv(self,R,z,phi=0.,t=0.): #pragma: no cover """ NAME: _R2deriv PURPOSE: evaluate the second radial derivative for this potential INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t - time OUTPUT: the second radial derivative HISTORY: 2016-12-26 - Written - Bovy (UofT/CCA) """ raise AttributeError # Implementation above does not work bc SCF.R2deriv is not implemented r= numpy.sqrt(R**2.+z**2.) out= self._scf.R2deriv(R,z,phi=phi,use_physical=False) for a,ds,d2s,H in zip(self._Sigma_amp,self._dSigmadR,self._d2SigmadR2, self._Hz): out+= 4.*numpy.pi*a*H(z)/r**2.*(d2s(r)*R**2.+z**2./r*ds(r)) return out def _z2deriv(self,R,z,phi=0.,t=0.): #pragma: no cover """ NAME: _z2deriv PURPOSE: evaluate the second vertical derivative for this potential INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t - time OUTPUT: the second vertical derivative HISTORY: 2016-12-26 - Written - Bovy (UofT/CCA) """ raise AttributeError # Implementation above does not work bc SCF.z2deriv is not implemented r= numpy.sqrt(R**2.+z**2.) out= self._scf.z2deriv(R,z,phi=phi,use_physical=False) for a,s,ds,d2s,h,H,dH in zip(self._Sigma_amp, self._Sigma,self._dSigmadR,self._d2SigmadR2, self._hz,self._Hz,self._dHzdz): out+= 4.*numpy.pi*a*(H(z)/r**2.*(d2s(r)*z**2.+ds(r)*R**2./r) +2.*ds(r)*dH(z)*z/r+s(r)*h(z)) return out def _Rzderiv(self,R,z,phi=0.,t=0.): #pragma: no cover """ NAME: _Rzderiv PURPOSE: evaluate the mixed R,z derivative for this potential INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t - time OUTPUT: d2phi/dR/dz HISTORY: 2016-12-26 - Written - Bovy (UofT/CCA) """ raise AttributeError # Implementation above does not work bc SCF.Rzderiv is not implemented r= numpy.sqrt(R**2.+z**2.) out= self._scf.Rzderiv(R,z,phi=phi,use_physical=False) for a,ds,d2s,H,dH in zip(self._Sigma_amp,self._dsigmadR, self._d2SigmadR2,self._Hz,self._dHzdz): out+= 4.*numpy.pi*a*(H(z)*R*z/r**2.*(d2s(r)-ds(r)/r) +ds(r)*dH(z)*R/r) return out def _phi2deriv(self,R,z,phi=0.,t=0.): #pragma: no cover """ NAME: _phi2deriv PURPOSE: evaluate the second azimuthal derivative for this potential INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t - time OUTPUT: the second azimuthal derivative HISTORY: 2016-12-26 - Written - Bovy (UofT/CCA) """ raise AttributeError # Implementation above does not work bc SCF.phi2deriv is not implemented return self._scf.phi2deriv(R,z,phi=phi,use_physical=False) def _dens(self,R,z,phi=0.,t=0.): """ NAME: _dens PURPOSE: evaluate the density at (R,z, phi) INPUT: R - Cylindrical Galactocentric radius z - vertical height phi - azimuth t - time OUTPUT: density at (R,z, phi) HISTORY: 2016-12-26 - Written - Bovy (UofT/CCA) """ r= numpy.sqrt(R**2.+z**2.) out= self._scf.dens(R,z,phi=phi,use_physical=False) for a,s,ds,d2s,h,H,dH in zip(self._Sigma_amp,self._Sigma, self._dSigmadR,self._d2SigmadR2, self._hz,self._Hz,self._dHzdz): out+= a*(s(r)*h(z)+d2s(r)*H(z)+2./r*ds(r)*(H(z)+z*dH(z))) return out def phiME_dens(R,z,phi,dens,Sigma,dSigmadR,d2SigmadR2,hz,Hz,dHzdz,Sigma_amp): """The density corresponding to phi_ME""" r= numpy.sqrt(R**2.+z**2.) out= dens(R,z,phi) for a,s,ds,d2s,h,H,dH \ in zip(Sigma_amp,Sigma,dSigmadR,d2SigmadR2,hz,Hz,dHzdz): out-= a*(s(r)*h(z)+d2s(r)*H(z)+2./r*ds(r)*(H(z)+z*dH(z))) return out galpy-1.3.0/galpy/potential_src/DoubleExponentialDiskPotential.py0000644000076500000240000003331713125613630025410 0ustar bovystaff00000000000000############################################################################### # DoubleExponentialDiskPotential.py: class that implements the double # exponential disk potential # # rho(R,z) = rho_0 e^-R/h_R e^-|z|/h_z ############################################################################### import numpy as nu import warnings from scipy import special, integrate from galpy.util import galpyWarning from galpy.potential_src.PowerSphericalPotential import KeplerPotential from galpy.potential_src.Potential import Potential, _APY_LOADED if _APY_LOADED: from astropy import units _TOL= 1.4899999999999999e-15 _MAXITER= 20 class DoubleExponentialDiskPotential(Potential): """Class that implements the double exponential disk potential .. math:: \\rho(R,z) = \\mathrm{amp}\\,\\exp\\left(-R/h_R-|z|/h_z\\right) """ def __init__(self,amp=1.,hr=1./3.,hz=1./16., maxiter=_MAXITER,tol=0.001,normalize=False, ro=None,vo=None, new=True,kmaxFac=2.,glorder=10): """ NAME: __init__ PURPOSE: initialize a double-exponential disk potential INPUT: amp - amplitude to be applied to the potential (default: 1); can be a Quantity with units of mass density or Gxmass density hr - disk scale-length (can be Quantity) hz - scale-height (can be Quantity) tol - relative accuracy of potential-evaluations maxiter - scipy.integrate keyword normalize - if True, normalize such that vc(1.,0.)=1., or, if given as a number, such that the force is this fraction of the force necessary to make vc(1.,0.)=1. ro=, vo= distance and velocity scales for translation into internal units (default from configuration file) OUTPUT: DoubleExponentialDiskPotential object HISTORY: 2010-04-16 - Written - Bovy (NYU) 2013-01-01 - Re-implemented using faster integration techniques - Bovy (IAS) """ Potential.__init__(self,amp=amp,ro=ro,vo=vo,amp_units='density') if _APY_LOADED and isinstance(hr,units.Quantity): hr= hr.to(units.kpc).value/self._ro if _APY_LOADED and isinstance(hz,units.Quantity): hz= hz.to(units.kpc).value/self._ro self.hasC= True self._kmaxFac= kmaxFac self._glorder= glorder self._hr= hr self._scale= self._hr self._hz= hz self._alpha= 1./self._hr self._beta= 1./self._hz self._gamma= self._alpha/self._beta self._maxiter= maxiter self._tol= tol self._zforceNotSetUp= True #We have not calculated a typical Kz yet #Setup j0 zeros etc. self._glx, self._glw= nu.polynomial.legendre.leggauss(self._glorder) self._nzeros=100 #j0 for potential and z self._j0zeros= nu.zeros(self._nzeros+1) self._j0zeros[1:self._nzeros+1]= special.jn_zeros(0,self._nzeros) self._dj0zeros= self._j0zeros-nu.roll(self._j0zeros,1) self._dj0zeros[0]= self._j0zeros[0] #j1 for R self._j1zeros= nu.zeros(self._nzeros+1) self._j1zeros[1:self._nzeros+1]= special.jn_zeros(1,self._nzeros) self._dj1zeros= self._j1zeros-nu.roll(self._j1zeros,1) self._dj1zeros[0]= self._j1zeros[0] #j2 for R2deriv self._j2zeros= nu.zeros(self._nzeros+1) self._j2zeros[1:self._nzeros+1]= special.jn_zeros(2,self._nzeros) self._dj2zeros= self._j2zeros-nu.roll(self._j2zeros,1) self._dj2zeros[0]= self._j2zeros[0] if normalize or \ (isinstance(normalize,(int,float)) \ and not isinstance(normalize,bool)): #pragma: no cover self.normalize(normalize) #Load Kepler potential for large R self._kp= KeplerPotential(normalize=4.*nu.pi/self._alpha**2./self._beta) def _evaluate(self,R,z,phi=0.,t=0.,dR=0,dphi=0): """ NAME: _evaluate PURPOSE: evaluate the potential at (R,z) INPUT: R - Cylindrical Galactocentric radius z - vertical height phi - azimuth t - time OUTPUT: potential at (R,z) HISTORY: 2010-04-16 - Written - Bovy (NYU) 2012-12-26 - New method using Gaussian quadrature between zeros - Bovy (IAS) DOCTEST: >>> doubleExpPot= DoubleExponentialDiskPotential() >>> r= doubleExpPot(1.,0) #doctest: +ELLIPSIS ... >>> assert( r+1.89595350484)**2.< 10.**-6. """ if True: if isinstance(R,float): floatIn= True R= nu.array([R]) z= nu.array([z]) else: floatIn= False out= nu.empty(len(R)) indx= (R <= 6.) out[True^indx]= self._kp(R[True^indx],z[True^indx]) R4max= nu.copy(R) R4max[(R < 1.)]= 1. kmax= self._kmaxFac*self._beta for jj in range(len(R)): if not indx[jj]: continue maxj0zeroIndx= nu.argmin((self._j0zeros-kmax*R4max[jj])**2.) #close enough ks= nu.array([0.5*(self._glx+1.)*self._dj0zeros[ii+1] + self._j0zeros[ii] for ii in range(maxj0zeroIndx)]).flatten() weights= nu.array([self._glw*self._dj0zeros[ii+1] for ii in range(maxj0zeroIndx)]).flatten() evalInt= special.jn(0,ks*R[jj])*(self._alpha**2.+ks**2.)**-1.5*(self._beta*nu.exp(-ks*nu.fabs(z[jj]))-ks*nu.exp(-self._beta*nu.fabs(z[jj])))/(self._beta**2.-ks**2.) out[jj]= -2.*nu.pi*self._alpha*nu.sum(weights*evalInt) if floatIn: return out[0] else: return out def _Rforce(self,R,z,phi=0.,t=0.): """ NAME: Rforce PURPOSE: evaluate radial force K_R (R,z) INPUT: R - Cylindrical Galactocentric radius z - vertical height phi - azimuth t - time OUTPUT: K_R (R,z) HISTORY: 2010-04-16 - Written - Bovy (NYU) DOCTEST: """ if True: if isinstance(R,nu.ndarray): if not isinstance(z,nu.ndarray): z= nu.ones_like(R)*z out= nu.array([self._Rforce(rr,zz) for rr,zz in zip(R,z)]) return out if (R > 16.*self._hr or R > 6.) and hasattr(self,'_kp'): return self._kp.Rforce(R,z) if R < 1.: R4max= 1. else: R4max= R kmax= self._kmaxFac*self._beta kmax= 2.*self._kmaxFac*self._beta maxj1zeroIndx= nu.argmin((self._j1zeros-kmax*R4max)**2.) #close enough ks= nu.array([0.5*(self._glx+1.)*self._dj1zeros[ii+1] + self._j1zeros[ii] for ii in range(maxj1zeroIndx)]).flatten() weights= nu.array([self._glw*self._dj1zeros[ii+1] for ii in range(maxj1zeroIndx)]).flatten() evalInt= ks*special.jn(1,ks*R)*(self._alpha**2.+ks**2.)**-1.5*(self._beta*nu.exp(-ks*nu.fabs(z))-ks*nu.exp(-self._beta*nu.fabs(z)))/(self._beta**2.-ks**2.) return -2.*nu.pi*self._alpha*nu.sum(weights*evalInt) def _zforce(self,R,z,phi=0.,t=0.): """ NAME: zforce PURPOSE: evaluate vertical force K_z (R,z) INPUT: R - Cylindrical Galactocentric radius z - vertical height phi - azimuth t - time OUTPUT: K_z (R,z) HISTORY: 2010-04-16 - Written - Bovy (NYU) DOCTEST: """ if True: if isinstance(R,nu.ndarray): if not isinstance(z,nu.ndarray): z= nu.ones_like(R)*z out= nu.array([self._zforce(rr,zz) for rr,zz in zip(R,z)]) return out if R > 16.*self._hr or R > 6.: return self._kp.zforce(R,z) if R < 1.: R4max= 1. else: R4max= R kmax= self._kmaxFac*self._beta maxj0zeroIndx= nu.argmin((self._j0zeros-kmax*R4max)**2.) #close enough ks= nu.array([0.5*(self._glx+1.)*self._dj0zeros[ii+1] + self._j0zeros[ii] for ii in range(maxj0zeroIndx)]).flatten() weights= nu.array([self._glw*self._dj0zeros[ii+1] for ii in range(maxj0zeroIndx)]).flatten() evalInt= ks*special.jn(0,ks*R)*(self._alpha**2.+ks**2.)**-1.5*(nu.exp(-ks*nu.fabs(z))-nu.exp(-self._beta*nu.fabs(z)))/(self._beta**2.-ks**2.) if z > 0.: return -2.*nu.pi*self._alpha*self._beta*nu.sum(weights*evalInt) else: return 2.*nu.pi*self._alpha*self._beta*nu.sum(weights*evalInt) def _R2deriv(self,R,z,phi=0.,t=0.): """ NAME: R2deriv PURPOSE: evaluate R2 derivative INPUT: R - Cylindrical Galactocentric radius z - vertical height phi - azimuth t - time OUTPUT: -d K_R (R,z) d R HISTORY: 2012-12-27 - Written - Bovy (IAS) """ if True: if isinstance(R,nu.ndarray): if not isinstance(z,nu.ndarray): z= nu.ones_like(R)*z out= nu.array([self._R2deriv(rr,zz) for rr,zz in zip(R,z)]) return out if R > 16.*self._hr or R > 6.: return self._kp.R2deriv(R,z) if R < 1.: R4max= 1. else: R4max= R kmax= 2.*self._kmaxFac*self._beta maxj0zeroIndx= nu.argmin((self._j0zeros-kmax*R4max)**2.) #close enough maxj2zeroIndx= nu.argmin((self._j2zeros-kmax*R4max)**2.) #close enough ks0= nu.array([0.5*(self._glx+1.)*self._dj0zeros[ii+1] + self._j0zeros[ii] for ii in range(maxj0zeroIndx)]).flatten() weights0= nu.array([self._glw*self._dj0zeros[ii+1] for ii in range(maxj0zeroIndx)]).flatten() ks2= nu.array([0.5*(self._glx+1.)*self._dj2zeros[ii+1] + self._j2zeros[ii] for ii in range(maxj2zeroIndx)]).flatten() weights2= nu.array([self._glw*self._dj2zeros[ii+1] for ii in range(maxj2zeroIndx)]).flatten() evalInt0= ks0**2.*special.jn(0,ks0*R)*(self._alpha**2.+ks0**2.)**-1.5*(self._beta*nu.exp(-ks0*nu.fabs(z))-ks0*nu.exp(-self._beta*nu.fabs(z)))/(self._beta**2.-ks0**2.) evalInt2= ks2**2.*special.jn(2,ks2*R)*(self._alpha**2.+ks2**2.)**-1.5*(self._beta*nu.exp(-ks2*nu.fabs(z))-ks2*nu.exp(-self._beta*nu.fabs(z)))/(self._beta**2.-ks2**2.) return nu.pi*self._alpha*(nu.sum(weights0*evalInt0) -nu.sum(weights2*evalInt2)) def _z2deriv(self,R,z,phi=0.,t=0.): """ NAME: z2deriv PURPOSE: evaluate z2 derivative INPUT: R - Cylindrical Galactocentric radius z - vertical height phi - azimuth t - time OUTPUT: -d K_Z (R,z) d Z HISTORY: 2012-12-26 - Written - Bovy (IAS) """ if True: if isinstance(R,nu.ndarray): if not isinstance(z,nu.ndarray): z= nu.ones_like(R)*z out= nu.array([self._z2deriv(rr,zz) for rr,zz in zip(R,z)]) return out if R > 16.*self._hr or R > 6.: return self._kp.z2deriv(R,z) if R < 1.: R4max= 1. else: R4max= R kmax= self._kmaxFac*self._beta maxj0zeroIndx= nu.argmin((self._j0zeros-kmax*R4max)**2.) #close enough ks= nu.array([0.5*(self._glx+1.)*self._dj0zeros[ii+1] + self._j0zeros[ii] for ii in range(maxj0zeroIndx)]).flatten() weights= nu.array([self._glw*self._dj0zeros[ii+1] for ii in range(maxj0zeroIndx)]).flatten() evalInt= ks*special.jn(0,ks*R)*(self._alpha**2.+ks**2.)**-1.5*(ks*nu.exp(-ks*nu.fabs(z))-self._beta*nu.exp(-self._beta*nu.fabs(z)))/(self._beta**2.-ks**2.) return -2.*nu.pi*self._alpha*self._beta*nu.sum(weights*evalInt) def _Rzderiv(self,R,z,phi=0.,t=0.): """ NAME: Rzderiv PURPOSE: evaluate the mixed R,z derivative INPUT: R - Cylindrical Galactocentric radius z - vertical height phi - azimuth t - time OUTPUT: d2phi/dR/dz HISTORY: 2013-08-28 - Written - Bovy (IAS) """ if True: if isinstance(R,nu.ndarray): if not isinstance(z,nu.ndarray): z= nu.ones_like(R)*z out= nu.array([self._Rzderiv(rr,zz) for rr,zz in zip(R,z)]) return out if R > 6.: return self._kp.Rzderiv(R,z) if R < 1.: R4max= 1. else: R4max= R kmax= 2.*self._kmaxFac*self._beta maxj1zeroIndx= nu.argmin((self._j1zeros-kmax*R4max)**2.) #close enough ks= nu.array([0.5*(self._glx+1.)*self._dj1zeros[ii+1] + self._j1zeros[ii] for ii in range(maxj1zeroIndx)]).flatten() weights= nu.array([self._glw*self._dj1zeros[ii+1] for ii in range(maxj1zeroIndx)]).flatten() evalInt= ks**2.*special.jn(1,ks*R)*(self._alpha**2.+ks**2.)**-1.5*(nu.exp(-ks*nu.fabs(z))-nu.exp(-self._beta*nu.fabs(z)))/(self._beta**2.-ks**2.) if z >= 0.: return -2.*nu.pi*self._alpha*self._beta*nu.sum(weights*evalInt) else: return 2.*nu.pi*self._alpha*self._beta*nu.sum(weights*evalInt) def _dens(self,R,z,phi=0.,t=0.): """ NAME: dens PURPOSE: evaluate the density INPUT: R - Cylindrical Galactocentric radius z - vertical height phi - azimuth t - time OUTPUT: rho (R,z) HISTORY: 2010-08-08 - Written - Bovy (NYU) """ return nu.exp(-self._alpha*R-self._beta*nu.fabs(z)) galpy-1.3.0/galpy/potential_src/EllipticalDiskPotential.py0000644000076500000240000002067213126041126024045 0ustar bovystaff00000000000000############################################################################### # EllipticalDiskPotential: Kuijken & Tremaine (1994)'s elliptical disk # potential ############################################################################### import math as m from galpy.util import bovy_conversion from galpy.potential_src.planarPotential import planarPotential, _APY_LOADED if _APY_LOADED: from astropy import units _degtorad= m.pi/180. class EllipticalDiskPotential(planarPotential): """Class that implements the Elliptical disk potential of Kuijken & Tremaine (1994) .. math:: \\Phi(R,\\phi) = \\mathrm{amp}\\,\\phi_0\\,\\left(\\frac{R}{R_1}\\right)^p\\,\\cos\\left(2\\,(\\phi-\\phi_b)\\right) This potential can be grown between :math:`t_{\mathrm{form}}` and :math:`t_{\mathrm{form}}+T_{\mathrm{steady}}` in a similar way as DehnenBarPotential, but times are given directly in galpy time units """ def __init__(self,amp=1.,phib=25.*_degtorad, p=1.,twophio=0.01,r1=1., tform=None,tsteady=None, cp=None,sp=None,ro=None,vo=None): """ NAME: __init__ PURPOSE: initialize an Elliptical disk potential phi(R,phi) = phio (R/Ro)^p cos[2(phi-phib)] INPUT: amp= amplitude to be applied to the potential (default: 1.), see twophio below tform= start of growth (to smoothly grow this potential (can be Quantity) tsteady= time delay at which the perturbation is fully grown (default: 2.; can be Quantity) p= power-law index of the phi(R) = (R/Ro)^p part r1= (1.) normalization radius for the amplitude (can be Quantity) Either: a) phib= angle (in rad; default=25 degree; or can be Quantity) twophio= potential perturbation (in terms of 2phio/vo^2 if vo=1 at Ro=1; can be Quantity with units of velocity-squared) b) cp, sp= twophio * cos(2phib), twophio * sin(2phib) (can be Quantity with units of velocity-squared) OUTPUT: (none) HISTORY: 2011-10-19 - Started - Bovy (IAS) """ planarPotential.__init__(self,amp=amp,ro=ro,vo=vo) if _APY_LOADED and isinstance(phib,units.Quantity): phib= phib.to(units.rad).value if _APY_LOADED and isinstance(r1,units.Quantity): r1= r1.to(units.kpc).value/self._ro if _APY_LOADED and isinstance(tform,units.Quantity): tform= tform.to(units.Gyr).value\ /bovy_conversion.time_in_Gyr(self._vo,self._ro) if _APY_LOADED and isinstance(tsteady,units.Quantity): tsteady= tsteady.to(units.Gyr).value\ /bovy_conversion.time_in_Gyr(self._vo,self._ro) if _APY_LOADED and isinstance(twophio,units.Quantity): twophio= twophio.to(units.km**2/units.s**2).value/self._vo**2. if _APY_LOADED and isinstance(cp,units.Quantity): cp= cp.to(units.km**2/units.s**2).value/self._vo**2. if _APY_LOADED and isinstance(sp,units.Quantity): sp= sp.to(units.km**2/units.s**2).value/self._vo**2. # Back to old definition self._amp/= r1**p self.hasC= True self.hasC_dxdv= True if cp is None or sp is None: self._phib= phib self._twophio= twophio else: self._twophio= m.sqrt(cp*cp+sp*sp) self._phib= m.atan2(sp,cp)/2. self._p= p if not tform is None: self._tform= tform else: self._tform= None if not tsteady is None: self._tsteady= self._tform+tsteady else: if self._tform is None: self._tsteady= None else: self._tsteady= self._tform+2. def _evaluate(self,R,phi=0.,t=0.): """ NAME: _evaluate PURPOSE: evaluate the potential at R,phi,t INPUT: R - Galactocentric cylindrical radius phi - azimuth t - time OUTPUT: Phi(R,phi,t) HISTORY: 2011-10-19 - Started - Bovy (IAS) """ #Calculate relevant time if not self._tform is None: if t < self._tform: smooth= 0. elif t < self._tsteady: deltat= t-self._tform xi= 2.*deltat/(self._tsteady-self._tform)-1. smooth= (3./16.*xi**5.-5./8*xi**3.+15./16.*xi+.5) else: #fully on smooth= 1. else: smooth= 1. return smooth*self._twophio/2.*R**self._p\ *m.cos(2.*(phi-self._phib)) def _Rforce(self,R,phi=0.,t=0.): """ NAME: _Rforce PURPOSE: evaluate the radial force for this potential INPUT: R - Galactocentric cylindrical radius phi - azimuth t - time OUTPUT: the radial force HISTORY: 2011-10-19 - Written - Bovy (IAS) """ #Calculate relevant time if not self._tform is None: if t < self._tform: smooth= 0. elif t < self._tsteady: deltat= t-self._tform xi= 2.*deltat/(self._tsteady-self._tform)-1. smooth= (3./16.*xi**5.-5./8*xi**3.+15./16.*xi+.5) else: #fully on smooth= 1. else: smooth= 1. return -smooth*self._p*self._twophio/2.*R**(self._p-1.)\ *m.cos(2.*(phi-self._phib)) def _phiforce(self,R,phi=0.,t=0.): """ NAME: _phiforce PURPOSE: evaluate the azimuthal force for this potential INPUT: R - Galactocentric cylindrical radius phi - azimuth t - time OUTPUT: the azimuthal force HISTORY: 2011-10-19 - Written - Bovy (IAS) """ #Calculate relevant time if not self._tform is None: if t < self._tform: smooth= 0. elif t < self._tsteady: deltat= t-self._tform xi= 2.*deltat/(self._tsteady-self._tform)-1. smooth= (3./16.*xi**5.-5./8*xi**3.+15./16.*xi+.5) else: #fully on smooth= 1. else: smooth= 1. return smooth*self._twophio*R**self._p*m.sin(2.*(phi-self._phib)) def _R2deriv(self,R,phi=0.,t=0.): #Calculate relevant time if not self._tform is None: if t < self._tform: smooth= 0. elif t < self._tsteady: deltat= t-self._tform xi= 2.*deltat/(self._tsteady-self._tform)-1. smooth= (3./16.*xi**5.-5./8*xi**3.+15./16.*xi+.5) else: #fully on smooth= 1. else: smooth= 1. return smooth*self._p*(self._p-1.)/2.*self._twophio*R**(self._p-2.)\ *m.cos(2.*(phi-self._phib)) def _phi2deriv(self,R,phi=0.,t=0.): #Calculate relevant time if not self._tform is None: if t < self._tform: smooth= 0. elif t < self._tsteady: deltat= t-self._tform xi= 2.*deltat/(self._tsteady-self._tform)-1. smooth= (3./16.*xi**5.-5./8*xi**3.+15./16.*xi+.5) else: #perturbation is fully on smooth= 1. else: smooth= 1. return -2.*smooth*self._twophio*R**self._p*m.cos(2.*(phi-self._phib)) def _Rphideriv(self,R,phi=0.,t=0.): #Calculate relevant time if not self._tform is None: if t < self._tform: smooth= 0. elif t < self._tsteady: deltat= t-self._tform xi= 2.*deltat/(self._tsteady-self._tform)-1. smooth= (3./16.*xi**5.-5./8*xi**3.+15./16.*xi+.5) else: #perturbation is fully on smooth= 1. else: smooth= 1. return -smooth*self._p*self._twophio*R**(self._p-1.)*m.sin(2.*(phi-self._phib)) def tform(self): #pragma: no cover """ NAME: tform PURPOSE: return formation time of the perturbation INPUT: (none) OUTPUT: tform in normalized units HISTORY: 2011-10-19 - Written - Bovy (IAS) """ return self._tform galpy-1.3.0/galpy/potential_src/FerrersPotential.py0000644000076500000240000004115313172265624022571 0ustar bovystaff00000000000000############################################################################### # FerrersPotential.py: General class for triaxial Ferrers Potential # # rho(r) = amp/[a^3 b c pi^1.5] Gamma(n+5/2)/Gamma(n+1) (1 - (m/a)^2)^n # # with # # m^2 = x^2 + y^2/b^2 + z^2/c^2 ######################################################################## import numpy as np import hashlib from scipy import integrate from scipy.special import gamma from galpy.util import bovy_conversion, bovy_coords from galpy.potential_src.Potential import Potential, _APY_LOADED if _APY_LOADED: from astropy import units class FerrersPotential(Potential): """Class that implements triaxial Ferrers potential for the ellipsoidal density profile with the short axis along the z-direction .. math:: \\rho(x,y,z) = \\frac{\\mathrm{amp}}{\\pi^{1.5} a^3 b c} \\frac{\\Gamma(n+\\frac{5}{2})}{\\Gamma(n+1)}\\,(1-(m/a)^2)^n with .. math:: m^2 = x'^2 + \\frac{y'^2}{b^2}+\\frac{z'^2}{c^2} and :math:`(x',y',z')` is a rotated frame wrt :math:`(x,y,z)` so that the major axis is aligned with :math:`x'`. Note that this potential has not yet been optimized for speed and has no C implementation, so orbit integration is currently slow. """ def __init__(self,amp=1.,a=1.,n=2,b=0.35,c=0.2375,omegab=0., pa=0.,normalize=False,ro=None,vo=None): """ NAME: __init__ PURPOSE: initialize a Ferrers potential INPUT: amp - total mass of the ellipsoid determines the amplitude of the potential; can be a Quantity with units of mass or Gxmass a - scale radius (can be Quantity) n - power of Ferrers density (n > 0) b - y-to-x axis ratio of the density c - z-to-x axis ratio of the density omegab - rotation speed of the ellipsoid (can be Quantity) pa= (None) If set, the position angle of the x axis (rad or Quantity) normalize - if True, normalize such that vc(1.,0.)=1., or, if given as a number, such that the force is this fraction of the force necessary to make vc(1.,0.)=1. ro=, vo= distance and velocity scales for translation into internal units (default from configuration file) OUTPUT: (none) """ Potential.__init__(self,amp=amp,ro=ro,vo=vo,amp_units='mass') if _APY_LOADED and isinstance(a,units.Quantity): a= a.to(units.kpc).value/self._ro if _APY_LOADED and isinstance(omegab,units.Quantity): omegab= omegab.to(units.km/units.s/units.kpc).value\ /bovy_conversion.freq_in_kmskpc(self._vo,self._ro) if _APY_LOADED and isinstance(pa,units.Quantity): pa= pa.to(units.rad).value self.a= a self._scale= self.a if n <= 0: raise ValueError('FerrersPotential requires n > 0') self.n= n self._b= b self._c= c self._omegab= omegab self._a2= self.a**2 self._b2= self._b**2. self._c2= self._c**2. self._force_hash= None self._pa = pa self._rhoc_M = gamma(n+2.5)/gamma(n+1) / np.pi**1.5/a**3/b/c if normalize or \ (isinstance(normalize,(int,float)) \ and not isinstance(normalize,bool)): #pragma: no cover self.normalize(normalize) if np.fabs(self._b-1.) > 10.**-10.: self.isNonAxi= True return None def _evaluate(self,R,z,phi=0.,t=0.): """ NAME: _evaluate PURPOSE: evaluate the potential at R,z INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t - time OUTPUT: Phi(R,z) """ if not self.isNonAxi: phi= 0. x,y,z= bovy_coords.cyl_to_rect(R,phi,z) xy= np.dot(self.rot(t),np.array([x,y])) x,y= xy[0],xy[1] return self._evaluate_xyz(x,y,z) def _evaluate_xyz(self,x,y,z=0.): """Evaluation of the potential as a function of (x,y,z) in the aligned coordinate frame""" return -np.pi * self._rhoc_M /(self.n+1.)*self.a**3*self._b*self._c * \ _potInt(x, y, z, self._a2, self._b2*self._a2, self._c2*self._a2, self.n) def _Rforce(self,R,z,phi=0.,t=0.): """ NAME: _Rforce PURPOSE:T evaluate the radial force for this potential INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t - time OUTPUT: the radial force """ if not self.isNonAxi: phi= 0. self._compute_xyzforces(R,z,phi,t) return np.cos(phi)*self._cached_Fx+np.sin(phi)*self._cached_Fy def _phiforce(self,R,z,phi=0.,t=0.): """ NAME: _phiforce PURPOSE: evaluate the azimuthal force for this potential INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t - time OUTPUT: the azimuthal force """ if not self.isNonAxi: phi= 0. self._compute_xyzforces(R,z,phi,t) return R*(-np.sin(phi)*self._cached_Fx\ +np.cos(phi)*self._cached_Fy) def _zforce(self,R,z,phi=0.,t=0.): """ NAME: _zforce PURPOSE: evaluate the vertical force for this potential INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t - time OUTPUT: the vertical force """ if not self.isNonAxi: phi= 0. self._compute_xyzforces(R,z,phi,t) return self._cached_Fz def _compute_xyz(self,R,phi,z,t): return bovy_coords.cyl_to_rect(R,phi-self._pa-self._omegab*t,z) def _compute_xyzforces(self,R,z,phi,t): # Compute all rectangular forces new_hash= hashlib.md5(np.array([R,phi,z,t])).hexdigest() if new_hash == self._force_hash: Fx= self._cached_Fx Fy= self._cached_Fy Fz= self._cached_Fz else: x,y,z= self._compute_xyz(R,phi,z,t) Fx= self._xforce_xyz(x,y,z) Fy= self._yforce_xyz(x,y,z) Fz= self._zforce_xyz(x,y,z) self._force_hash= new_hash tp= self._pa+self._omegab*t cp, sp= np.cos(tp), np.sin(tp) self._cached_Fx= cp*Fx-sp*Fy self._cached_Fy= sp*Fx+cp*Fy self._cached_Fz= Fz def _xforce_xyz(self,x,y,z): """Evaluation of the x force as a function of (x,y,z) in the aligned coordinate frame""" return -2.*np.pi*self._rhoc_M * self.a**3*self._b*self._c * \ _forceInt(x, y, z, self._a2, self._b2*self._a2, self._c2*self._a2, self.n, 0) def _yforce_xyz(self,x,y,z): """Evaluation of the y force as a function of (x,y,z) in the aligned coordinate frame""" return -2.*np.pi*self._rhoc_M * self.a**3*self._b*self._c * \ _forceInt(x, y, z, self._a2, self._b2*self._a2, self._c2*self._a2, self.n, 1) def _zforce_xyz(self,x,y,z): """Evaluation of the z force as a function of (x,y,z) in the aligned coordinate frame""" return -2.*np.pi*self._rhoc_M * self.a**3*self._b*self._c * \ _forceInt(x, y, z, self._a2, self._b2*self._a2, self._c2*self._a2, self.n, 2) def _R2deriv(self,R,z,phi=0.,t=0.): """ NAME: _R2deriv PURPOSE: evaluate the second radial derivative for this potential INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t - time OUTPUT: the second radial derivative """ if not self.isNonAxi: phi= 0. x,y,z= self._compute_xyz(R,phi,z,t) phixxa= self._2ndderiv_xyz(x,y,z,0,0) phixya= self._2ndderiv_xyz(x,y,z,0,1) phiyya= self._2ndderiv_xyz(x,y,z,1,1) ang = self._omegab*t + self._pa c, s = np.cos(ang), np.sin(ang) phixx = c**2*phixxa + 2.*c*s*phixya + s**2*phiyya phixy = (c**2-s**2)*phixya + c*s*(phiyya - phixxa) phiyy = s**2*phixxa - 2.*c*s*phixya + c**2*phiyya return np.cos(phi)**2.*phixx + np.sin(phi)**2.*phiyy + \ 2.*np.cos(phi)*np.sin(phi)*phixy def _Rzderiv(self,R,z,phi=0.,t=0.): """ NAME: _Rzderiv PURPOSE: evaluate the mixed radial, vertical derivative for this potential INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t - time OUTPUT: the mixed radial, vertical derivative """ if not self.isNonAxi: phi= 0. x,y,z= self._compute_xyz(R,phi,z,t) phixza= self._2ndderiv_xyz(x,y,z,0,2) phiyza= self._2ndderiv_xyz(x,y,z,1,2) ang = self._omegab*t + self._pa c, s = np.cos(ang), np.sin(ang) phixz = c*phixza + s*phiyza phiyz = -s*phixza + c*phiyza return np.cos(phi)*phixz + np.sin(phi)*phiyz def _z2deriv(self,R,z,phi=0.,t=0.): """ NAME: _z2deriv PURPOSE: evaluate the second vertical derivative for this potential INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t - time OUTPUT: the second vertical derivative """ if not self.isNonAxi: phi= 0. x,y,z= self._compute_xyz(R,phi,z,t) return self._2ndderiv_xyz(x,y,z,2,2) def _phi2deriv(self,R,z,phi=0.,t=0.): """ NAME: _phi2deriv PURPOSE: evaluate the second azimuthal derivative for this potential INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t - time OUTPUT: the second azimuthal derivative """ if not self.isNonAxi: phi= 0. x,y,z= self._compute_xyz(R,phi,z,t) Fx= self._xforce_xyz(x,y,z) Fy= self._yforce_xyz(x,y,z) Fxy= np.dot(self.rot(t, transposed = True),np.array([Fx,Fy])) Fx, Fy= Fxy[0], Fxy[1] phixxa= self._2ndderiv_xyz(x,y,z,0,0) phixya= self._2ndderiv_xyz(x,y,z,0,1) phiyya= self._2ndderiv_xyz(x,y,z,1,1) ang = self._omegab*t + self._pa c, s = np.cos(ang), np.sin(ang) phixx = c**2*phixxa + 2.*c*s*phixya + s**2*phiyya phixy = (c**2-s**2)*phixya + c*s*(phiyya - phixxa) phiyy = s**2*phixxa - 2.*c*s*phixya + c**2*phiyya return R**2.*(np.sin(phi)**2.*phixx+np.cos(phi)**2.*phiyy\ -2.*np.cos(phi)*np.sin(phi)*phixy)\ +R*(np.cos(phi)*Fx+np.sin(phi)*Fy) def _Rphideriv(self,R,z,phi=0.,t=0.): """ NAME: _Rphideriv PURPOSE: evaluate the mixed radial, azimuthal derivative for this potential INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t - time OUTPUT: the mixed radial, azimuthal derivative """ if not self.isNonAxi: phi= 0. x,y,z= self._compute_xyz(R,phi,z,t) Fx= self._xforce_xyz(x,y,z) Fy= self._yforce_xyz(x,y,z) Fxy= np.dot(self.rot(t, transposed = True),np.array([Fx,Fy])) Fx, Fy= Fxy[0], Fxy[1] phixxa= self._2ndderiv_xyz(x,y,z,0,0) phixya= self._2ndderiv_xyz(x,y,z,0,1) phiyya= self._2ndderiv_xyz(x,y,z,1,1) ang = self._omegab*t + self._pa c, s = np.cos(ang), np.sin(ang) phixx = c**2*phixxa + 2.*c*s*phixya + s**2*phiyya phixy = (c**2-s**2)*phixya + c*s*(phiyya - phixxa) phiyy = s**2*phixxa - 2.*c*s*phixya + c**2*phiyya return R*np.cos(phi)*np.sin(phi)*\ (phiyy-phixx)+R*np.cos(2.*(phi))*phixy\ +np.sin(phi)*Fx-np.cos(phi)*Fy def _2ndderiv_xyz(self,x,y,z,i,j): """General 2nd derivative of the potential as a function of (x,y,z) in the aligned coordinate frame, d^2\Phi/dx_i/dx_j""" return -np.pi*self._rhoc_M*self.a**3*self._b*self._c *\ _2ndDerivInt(x,y,z,self._a2,self._b2*self._a2,self._c2*self._a2,self.n,i,j) def _dens(self,R,z,phi=0.,t=0.): """ NAME: _dens PURPOSE: evaluate the density for this potential INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t - time OUTPUT: the density """ x,y,z= self._compute_xyz(R,phi,z,t) m2 = x**2/self._a2+y**2/self._b2+z**2/self._c2 if m2 < 1: return self._rhoc_M*(1.-m2/self.a**2)**self.n else: return 0. def OmegaP(self): """ NAME: OmegaP PURPOSE: return the pattern speed INPUT: (none) OUTPUT: pattern speed """ return self._omegab def rot(self, t=0., transposed=False): """2D Rotation matrix for non-zero pa or pattern speed to goto the aligned coordinates """ rotmat = np.array( [[np.cos(self._pa+self._omegab*t),np.sin(self._pa+self._omegab*t)], [-np.sin(self._pa+self._omegab*t),np.cos(self._pa+self._omegab*t)]]) if transposed: return rotmat.T else: return rotmat def _potInt(x,y,z,a2,b2,c2,n): """Integral involed in the potential at (x,y,z) integrates 1/A B^(n+1) where A = sqrt((tau+a)(tau+b)(tau+c)) and B = (1-x^2/(tau+a)-y^2/(tau+b)-z^2/(tau+c)) from lambda to infty with respect to tau. The lower limit lambda is given by lowerlim function. """ def integrand(tau): return _FracInt(x, y, z, a2, b2, c2, tau, n+1) return integrate.quad(integrand, lowerlim(x**2,y**2,z**2,a2,b2,c2), np.inf)[0] def _forceInt(x,y,z,a2,b2,c2,n,i): """Integral involved in the force at (x,y,z) integrates 1/A B^n (x_i/(tau+a_i)) where A = sqrt((tau+a)(tau+b)(tau+c)) and B = (1-x^2/(tau+a)-y^2/(tau+b)-z^2/(tau+c)) from lambda to infty with respect to tau. The lower limit lambda is given by lowerlim function. """ def integrand(tau): return (x*(i==0) + y*(i==1) + z*(i==2))/(a2*(i==0) + b2*(i==1) + c2*(i==2) + tau) * \ _FracInt(x, y, z, a2, b2, c2, tau, n) return integrate.quad(integrand, lowerlim(x**2, y**2, z**2, a2, b2, c2), np.inf, epsabs=1e-12)[0] def _2ndDerivInt(x,y,z,a2,b2,c2,n,i,j): """Integral involved in second derivatives d^\Phi/(dx_i dx_j) integrate 1/A B^(n-1) (-2 x_i/(tau+a_i)) (-2 x_j/(tau+a_j)) when i /= j or 1/A [ B^(n-1) 4n x_i^2 / (a_i+t)^2 + B^n -(-2/(a_i+t)) ] when i == j where A = sqrt((tau+a)(tau+b)(tau+c)) and B = (1-x^2/(tau+a)-y^2/(tau+b)-z^2/(tau+c)) from lambda to infty with respect to tau The lower limit lambda is given by lowerlim function. This is a second derivative of _potInt. """ def integrand(tau): if i!=j: return _FracInt(x,y,z,a2,b2,c2,tau,n-1)*n*\ (1.+(-1.-2.*x/(tau+a2))*(i==0 or j==0))*\ (1.+(-1.-2.*y/(tau+b2))*(i==1 or j==1))*\ (1.+(-1.-2.*z/(tau+c2))*(i==2 or j==2)) else: var2 = x**2*(i==0) + y**2*(i==1) + z**2*(i==2) coef2 = a2*(i==0) + b2*(i==1) + c2*(i==2) return _FracInt(x,y,z,a2,b2,c2,tau,n-1)*n*(4.*var2)/(tau+coef2)**2 +\ _FracInt(x,y,z,a2,b2,c2,tau,n)*(-2./(tau+coef2)) return integrate.quad(integrand, lowerlim(x**2,y**2,z**2,a2,b2,c2), np.inf)[0] def _FracInt(x,y,z,a,b,c,tau,n): """Returns 1 x^2 y^2 z^2 -------------------------- (1 - ------- - ------- - -------)^n sqrt(tau+a)(tau+b)(tau+c)) tau+a tau+b tau+c """ denom = np.sqrt((a + tau)*(b + tau)*(c + tau)) return (1. - x**2/(a + tau) - y**2/(b + tau) - z**2/(c + tau))**n / denom def lowerlim(x, y, z, a, b, c): """Returns the real positive root of x/(a+t) + y/(b+t) + z/(c+t) = 1 when x/a + y/b + z/c > 1 else zero """ if x/a + y/b + z/c > 1: B = a + b + c - x - y - z C = a*b + a*c + b*c - a*y - a*z - b*x - b*z - c*x - c*y D = a*b*c - a*b*z - a*c*y - b*c*x r = np.roots([1,B,C,D]) ll = r[~np.iscomplex(r) & (r>0.)] return ll[0].real else: return 0. galpy-1.3.0/galpy/potential_src/FlattenedPowerPotential.py0000644000076500000240000001545313003761364024103 0ustar bovystaff00000000000000############################################################################### # FlattenedPowerPotential.py: Power-law potential that is flattened in the # potential (NOT the density) # # amp # phi(R,z)= --------- ; m^2 = R^2 + z^2/q^2 # m^\alpha ############################################################################### import numpy as nu from scipy import special, integrate from galpy.potential_src.Potential import Potential, _APY_LOADED if _APY_LOADED: from astropy import units _CORE=10**-8 class FlattenedPowerPotential(Potential): """Class that implements a power-law potential that is flattened in the potential (NOT the density) .. math:: \\Phi(R,z) = -\\frac{\\mathrm{amp}\,r_1^\\alpha}{\\alpha\\,\\left(R^2+(z/q)^2+\\mathrm{core}^2\\right)^{\\alpha/2}} and the same as LogarithmicHaloPotential for :math:`\\alpha=0` See Figure 1 in `Evans (1994) `_ for combinations of alpha and q that correspond to positive densities """ def __init__(self,amp=1.,alpha=0.5,q=0.9,core=_CORE,normalize=False,r1=1., ro=None,vo=None): """ NAME: __init__ PURPOSE: initialize a flattened power-law potential INPUT: amp - amplitude to be applied to the potential (default: 1); can be a Quantity with units of velocity-squared alpha - power q - flattening core - core radius (can be Quantity) r1= (1.) reference radius for amplitude (can be Quantity) normalize - if True, normalize such that vc(1.,0.)=1., or, if given as a number, such that the force is this fraction of the force necessary to make vc(1.,0.)=1. ro=, vo= distance and velocity scales for translation into internal units (default from configuration file) OUTPUT: (none) HISTORY: 2013-01-09 - Written - Bovy (IAS) """ Potential.__init__(self,amp=amp,ro=ro,vo=vo,amp_units='velocity2') if _APY_LOADED and isinstance(core,units.Quantity): core= core.to(units.kpc).value/self._ro if _APY_LOADED and isinstance(r1,units.Quantity): r1= r1.to(units.kpc).value/self._ro self.alpha= alpha self.q2= q**2. self.core2= core**2. # Back to old definition self._amp*= r1**self.alpha if normalize or \ (isinstance(normalize,(int,float)) \ and not isinstance(normalize,bool)): #pragma: no cover self.normalize(normalize) self.hasC= True self.hasC_dxdv= True def _evaluate(self,R,z,phi=0.,t=0.): """ NAME: _evaluate PURPOSE: evaluate the potential at R,z INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t - time OUTPUT: Phi(R,z) HISTORY: 2013-01-09 - Started - Bovy (IAS) """ if self.alpha == 0.: return 1./2.*nu.log(R**2.+z**2./self.q2+self.core2) else: m2= self.core2+R**2.+z**2./self.q2 return -m2**(-self.alpha/2.)/self.alpha def _Rforce(self,R,z,phi=0.,t=0.): """ NAME: _Rforce PURPOSE: evaluate the radial force for this potential INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t - time OUTPUT: the radial force HISTORY: 2010-07-10 - Written - Bovy (NYU) """ if self.alpha == 0.: return -R/(R**2.+z**2./self.q2+self.core2) else: m2= self.core2+R**2.+z**2./self.q2 return -m2**(-self.alpha/2.-1.)*R def _zforce(self,R,z,phi=0.,t=0.): """ NAME: _zforce PURPOSE: evaluate the vertical force for this potential INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t - time OUTPUT: the vertical force HISTORY: 2010-07-10 - Written - Bovy (NYU) """ if self.alpha == 0.: return -z/self.q2/(R**2.+z**2./self.q2+self.core2) else: m2= self.core2+R**2.+z**2./self.q2 return -m2**(-self.alpha/2.-1.)*z/self.q2 def _R2deriv(self,R,z,phi=0.,t=0.): """ NAME: _Rderiv PURPOSE: evaluate the second radial derivative for this potential INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t - time OUTPUT: the second radial derivative HISTORY: 2011-10-09 - Written - Bovy (NYU) """ if self.alpha == 0.: denom= 1./(R**2.+z**2./self.q2+self.core2) return denom-2.*R**2.*denom**2. else: m2= self.core2+R**2.+z**2./self.q2 return -m2**(-self.alpha/2.-1.)*((self.alpha+2)*R**2./m2-1.) def _z2deriv(self,R,z,phi=0.,t=0.): """ NAME: _z2deriv PURPOSE: evaluate the second vertical derivative for this potential INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t- time OUTPUT: the second vertical derivative HISTORY: 2012-07-26 - Written - Bovy (IAS@MPIA) """ if self.alpha == 0.: denom= 1./(R**2.+z**2./self.q2+self.core2) return denom/self.q2-2.*z**2.*denom**2./self.q2**2. else: m2= self.core2+R**2.+z**2./self.q2 return -1./self.q2*m2**(-self.alpha/2.-1.)*((self.alpha+2)*z**2./m2/self.q2-1.) def _dens(self,R,z,phi=0.,t=0.): """ NAME: _dens PURPOSE: evaluate the density force for this potential INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t - time OUTPUT: the density HISTORY: 2013-01-09 - Written - Bovy (IAS) """ if self.alpha == 0.: return 1./4./nu.pi/self.q2*((2.*self.q2+1.)*self.core2+R**2.\ +(2.-1./self.q2)*z**2.)/\ (R**2.+z**2./self.q2+self.core2)**2. else: m2= self.core2+R**2.+z**2./self.q2 return 1./self.q2*(self.core2*(1.+2.*self.q2)+R**2.*(1.-self.alpha*self.q2)+z**2.*(2.-(1.+self.alpha)/self.q2))*m2**(-self.alpha/2.-2.)/4./nu.pi galpy-1.3.0/galpy/potential_src/ForceSoftening.py0000644000076500000240000000631613003465421022204 0ustar bovystaff00000000000000############################################################################### # ForceSoftening: class representing a force softening kernel ############################################################################### import numpy as nu class ForceSoftening: """class representing a force softening kernel""" def __init__(self): #pragma: no cover pass def __call__(self,d): #pragma: no cover """ NAME: __call__ PURPOSE: evaluate the force of the softening kernel INPUT: d - distance OUTPUT: softened force (amplitude; without GM) HISTORY: 2011-04-13 - Written - Bovy (NYU) """ raise AttributeError("'__call__' not implemented for this softening kernel") def potential(self,d): #pragma: no cover """ NAME: potential PURPOSE: return the potential corresponding to this softening kernel INPUT: d - distance OUTPUT: potential (without GM) HISTORY: 2011-04-13 - Written - Bovy (NYU) """ raise AttributeError("'potential' not implemented for this softening kernel") def density(self,d): #pragma: no cover """ NAME: density PURPOSE: return the density corresponding to this softening kernel INPUT: d - distance OUTPUT: density (without GM) HISTORY: 2011-04-13 - Written - Bovy (NYU) """ raise AttributeError("'density' not implemented for this softening kernel") class PlummerSoftening (ForceSoftening): """class representing a Plummer softening kernel""" def __init__(self,softening_length=0.01): """ NAME: __init__ PURPOSE: Initialize a Plummer softening kernel INPUT: softening_length= OUTPUT: HISTORY: 2011-04-13 - Written - Bovy (NYU) """ self._softening_length= softening_length def __call__(self,d): """ NAME: __call__ PURPOSE: evaluate the force of the softening kernel INPUT: d - distance OUTPUT: softened force (amplitude; without GM) HISTORY: 2011-04-13 - Written - Bovy (NYU) """ return d/(d**2.+self._softening_length**2.)**1.5 def potential(self,d): """ NAME: potential PURPOSE: return the potential corresponding to this softening kernel INPUT: d - distance OUTPUT: potential (without GM) HISTORY: 2011-04-13 - Written - Bovy (NYU) """ return (d**2.+self._softening_length**2.)**-0.5 def density(self,d): """ NAME: density PURPOSE: return the density corresponding to this softening kernel INPUT: d - distance OUTPUT: density (without GM) HISTORY: 2011-04-13 - Written - Bovy (NYU) """ return 3./4./nu.pi*self._softening_length**2.\ *(d**2.+self._softening_length**2.)**-2.5 galpy-1.3.0/galpy/potential_src/HenonHeilesPotential.py0000644000076500000240000000731013171712137023352 0ustar bovystaff00000000000000############################################################################### # HenonHeilesPotential: the Henon-Heiles (1964) potential ############################################################################### import numpy from galpy.potential_src.planarPotential import planarPotential class HenonHeilesPotential(planarPotential): """Class that implements a the `Henon & Heiles (1964) `__ potential .. math:: \\Phi(R,\\phi) = \\frac{\\mathrm{amp}}{2}\\,\\left[R^2 + \\frac{2\\,R^3}{3}\\,\\sin\\left(3\,\phi\\right)\\right] """ def __init__(self,amp=1.,ro=None,vo=None): """ NAME: __init__ PURPOSE: initialize a Henon-Heiles potential INPUT: amp - amplitude to be applied to the potential (default: 1.) OUTPUT: (none) HISTORY: 2017-10-16 - Written - Bovy (UofT) """ planarPotential.__init__(self,amp=amp,ro=ro,vo=vo) self.hasC= True self.hasC_dxdv= True def _evaluate(self,R,phi=0.,t=0.): """ NAME: _evaluate PURPOSE: evaluate the potential at R,phi,t INPUT: R - Galactocentric cylindrical radius phi - azimuth t - time OUTPUT: Phi(R,phi,t) HISTORY: 2017-10-16 - Written - Bovy (UofT) """ return 0.5*R*R*(1.+2./3.*R*numpy.sin(3.*phi)) def _Rforce(self,R,phi=0.,t=0.): """ NAME: _Rforce PURPOSE: evaluate the radial force for this potential INPUT: R - Galactocentric cylindrical radius phi - azimuth t - time OUTPUT: the radial force HISTORY: 2017-10-16 - Written - Bovy (UofT) """ return -R*(1.+R*numpy.sin(3.*phi)) def _phiforce(self,R,phi=0.,t=0.): """ NAME: _phiforce PURPOSE: evaluate the azimuthal force for this potential INPUT: R - Galactocentric cylindrical radius phi - azimuth t - time OUTPUT: the azimuthal force HISTORY: 2017-10-16 - Written - Bovy (UofT) """ return -R**3.*numpy.cos(3.*phi) def _R2deriv(self,R,phi=0.,t=0.): """ NAME: _R2deriv PURPOSE: evaluate the second radial derivative for this potential INPUT: R - Galactocentric cylindrical radius phi - azimuth t - time OUTPUT: the second radial derivative HISTORY: 2017-10-16 - Written - Bovy (UofT) """ return 1.+2.*R*numpy.sin(3.*phi) def _phi2deriv(self,R,phi=0.,t=0.): """ NAME: _phi2deriv PURPOSE: evaluate the second azimuthal derivative for this potential INPUT: R - Galactocentric cylindrical radius phi - azimuth t - time OUTPUT: the second azimuthal derivative HISTORY: 2017-10-16 - Written - Bovy (UofT) """ return -3.*R**3.*numpy.sin(3.*phi) def _Rphideriv(self,R,phi=0.,t=0.): """ NAME: _Rphideriv PURPOSE: evaluate the mixed radial, azimuthal derivative for this potential INPUT: R - Galactocentric cylindrical radius phi - azimuth t - time OUTPUT: the mixed radial, azimuthal derivative HISTORY: 2017-10-16 - Written - Bovy (UofT) """ return 3.*R**2.*numpy.cos(3.*phi) galpy-1.3.0/galpy/potential_src/interppotential_c_ext/0000755000076500000240000000000013236420072023315 5ustar bovystaff00000000000000galpy-1.3.0/galpy/potential_src/interppotential_c_ext/interppotential_calc_potential.c0000644000076500000240000001223013236350147031746 0ustar bovystaff00000000000000/* C code for calculating a potential and its forces on a grid */ #include #include #include #include #ifdef _OPENMP #include #endif #define CHUNKSIZE 1 //Potentials #include #include #include #include #include /* MAIN FUNCTIONS */ void calc_potential(int nR, double *R, int nz, double *z, int npot, int * pot_type, double * pot_args, double *out, int * err){ int ii, jj, tid, nthreads; #ifdef _OPENMP nthreads = omp_get_max_threads(); #else nthreads = 1; #endif double * row= (double *) malloc ( nthreads * nz * ( sizeof ( double ) ) ); //Set up the potentials struct potentialArg * potentialArgs= (struct potentialArg *) malloc ( npot * sizeof (struct potentialArg) ); parse_actionAngleArgs(npot,potentialArgs,&pot_type,&pot_args,false); //Run through the grid and calculate UNUSED int chunk= CHUNKSIZE; #pragma omp parallel for schedule(static,chunk) private(ii,tid,jj) \ shared(row,npot,potentialArgs,R,z,nR,nz) for (ii=0; ii < nR; ii++){ #ifdef _OPENMP tid= omp_get_thread_num(); #else tid = 0; #endif for (jj=0; jj < nz; jj++){ *(row+jj+tid*nz)= evaluatePotentials(*(R+ii),*(z+jj),npot,potentialArgs); } put_row(out,ii,row+tid*nz,nz); } free_potentialArgs(npot,potentialArgs); free(potentialArgs); free(row); } void calc_rforce(int nR, double *R, int nz, double *z, int npot, int * pot_type, double * pot_args, double *out, int * err){ int ii, jj, tid, nthreads; #ifdef _OPENMP nthreads = omp_get_max_threads(); #else nthreads = 1; #endif double * row= (double *) malloc ( nthreads * nz * ( sizeof ( double ) ) ); //Set up the potentials struct potentialArg * potentialArgs= (struct potentialArg *) malloc ( npot * sizeof (struct potentialArg) ); parse_leapFuncArgs_Full(npot,potentialArgs,&pot_type,&pot_args); //Run through the grid and calculate UNUSED int chunk= CHUNKSIZE; #pragma omp parallel for schedule(static,chunk) private(ii,tid,jj) \ shared(row,npot,potentialArgs,R,z,nR,nz) for (ii=0; ii < nR; ii++){ #ifdef _OPENMP tid= omp_get_thread_num(); #else tid = 0; #endif for (jj=0; jj < nz; jj++){ *(row+jj+tid*nz)= calcRforce(*(R+ii),*(z+jj),0.,0.,npot,potentialArgs); } put_row(out,ii,row+tid*nz,nz); } free_potentialArgs(npot,potentialArgs); free(potentialArgs); free(row); } void calc_zforce(int nR, double *R, int nz, double *z, int npot, int * pot_type, double * pot_args, double *out, int * err){ int ii, jj, tid, nthreads; #ifdef _OPENMP nthreads = omp_get_max_threads(); #else nthreads = 1; #endif double * row= (double *) malloc ( nthreads * nz * ( sizeof ( double ) ) ); //Set up the potentials struct potentialArg * potentialArgs= (struct potentialArg *) malloc ( npot * sizeof (struct potentialArg) ); parse_leapFuncArgs_Full(npot,potentialArgs,&pot_type,&pot_args); //Run through the grid and calculate UNUSED int chunk= CHUNKSIZE; #pragma omp parallel for schedule(static,chunk) private(ii,tid,jj) \ shared(row,npot,potentialArgs,R,z,nR,nz) for (ii=0; ii < nR; ii++){ #ifdef _OPENMP tid= omp_get_thread_num(); #else tid = 0; #endif for (jj=0; jj < nz; jj++){ *(row+jj+tid*nz)= calczforce(*(R+ii),*(z+jj),0.,0.,npot,potentialArgs); } put_row(out,ii,row+tid*nz,nz); } free_potentialArgs(npot,potentialArgs); free(potentialArgs); free(row); } void eval_potential(int nR, double *R, double *z, int npot, int * pot_type, double * pot_args, double *out, int * err){ int ii; //Set up the potentials struct potentialArg * potentialArgs= (struct potentialArg *) malloc ( npot * sizeof (struct potentialArg) ); parse_actionAngleArgs(npot,potentialArgs,&pot_type,&pot_args,false); //Run through and evaluate for (ii=0; ii < nR; ii++){ *(out+ii)= evaluatePotentials(*(R+ii),*(z+ii),npot,potentialArgs); } free_potentialArgs(npot,potentialArgs); free(potentialArgs); } void eval_rforce(int nR, double *R, double *z, int npot, int * pot_type, double * pot_args, double *out, int * err){ int ii; //Set up the potentials struct potentialArg * potentialArgs= (struct potentialArg *) malloc ( npot * sizeof (struct potentialArg) ); parse_leapFuncArgs_Full(npot,potentialArgs,&pot_type,&pot_args); //Run through and evaluate for (ii=0; ii < nR; ii++){ *(out+ii)= calcRforce(*(R+ii),*(z+ii),0.,0.,npot,potentialArgs); } free_potentialArgs(npot,potentialArgs); free(potentialArgs); } void eval_zforce(int nR, double *R, double *z, int npot, int * pot_type, double * pot_args, double *out, int * err){ int ii; //Set up the potentials struct potentialArg * potentialArgs= (struct potentialArg *) malloc ( npot * sizeof (struct potentialArg) ); parse_leapFuncArgs_Full(npot,potentialArgs,&pot_type,&pot_args); //Run through and evaluate for (ii=0; ii < nR; ii++){ *(out+ii)= calczforce(*(R+ii),*(z+ii),0.,0.,npot,potentialArgs); } free_potentialArgs(npot,potentialArgs); free(potentialArgs); } galpy-1.3.0/galpy/potential_src/interpRZPotential.py0000644000076500000240000007516413125613630022737 0ustar bovystaff00000000000000import os import sys import sysconfig import copy import ctypes import ctypes.util import warnings from functools import wraps import numpy from numpy.ctypeslib import ndpointer from scipy import interpolate from galpy.util import multi, galpyWarning from galpy.potential_src.Potential import Potential from galpy.util.bovy_conversion import physical_conversion _DEBUG= False #Find and load the library _lib= None outerr= None PY3= sys.version > '3' if PY3: #pragma: no cover _ext_suffix= sysconfig.get_config_var('EXT_SUFFIX') else: _ext_suffix= '.so' for path in sys.path: try: _lib = ctypes.CDLL(os.path.join(path,'galpy_interppotential_c%s' % _ext_suffix)) except OSError as e: if os.path.exists(os.path.join(path,'galpy_interppotential_c%s' % _ext_suffix)): #pragma: no cover outerr= e _lib = None else: break if _lib is None: #pragma: no cover if not outerr is None: warnings.warn("interppotential_c extension module not loaded, because of error '%s' " % outerr, galpyWarning) else: warnings.warn("interppotential_c extension module not loaded, because galpy_interppotential_c%s image was not found" % _ext_suffix, galpyWarning) ext_loaded= False else: ext_loaded= True def scalarVectorDecorator(func): """Decorator to return scalar outputs as a set""" @wraps(func) def scalar_wrapper(*args,**kwargs): if numpy.array(args[1]).shape == () \ and numpy.array(args[2]).shape == (): #only if both R and z are scalars scalarOut= True args= (args[0],numpy.array([args[1]]),numpy.array([args[2]])) elif numpy.array(args[1]).shape == () \ and not numpy.array(args[2]).shape == (): #R scalar, z vector scalarOut= False args= (args[0],args[1]*numpy.ones_like(args[2]),args[2]) elif not numpy.array(args[1]).shape == () \ and numpy.array(args[2]).shape == (): #R vector, z scalar scalarOut= False args= (args[0],args[1],args[2]*numpy.ones_like(args[1])) else: scalarOut= False result= func(*args,**kwargs) if scalarOut: return result[0] else: return result return scalar_wrapper def zsymDecorator(odd): """Decorator to deal with zsym=True input; set odd=True if the function is an odd function of z (like zforce)""" def wrapper(func): @wraps(func) def zsym_wrapper(*args,**kwargs): if args[0]._zsym: out= func(args[0],args[1],numpy.fabs(args[2]),**kwargs) else: out= func(*args,**kwargs) if odd and args[0]._zsym: return sign(args[2])*out else: return out return zsym_wrapper return wrapper def scalarDecorator(func): """Decorator to return scalar output for 1D functions (vcirc,etc.)""" @wraps(func) def scalar_wrapper(*args,**kwargs): if numpy.array(args[1]).shape == (): scalarOut= True args= (args[0],numpy.array([args[1]])) else: scalarOut= False result= func(*args,**kwargs) if scalarOut: return result[0] else: return result return scalar_wrapper class interpRZPotential(Potential): """Class that interpolates a given potential on a grid for fast orbit integration""" def __init__(self, RZPot=None,rgrid=(numpy.log(0.01),numpy.log(20.),101), zgrid=(0.,1.,101),logR=True, interpPot=False,interpRforce=False,interpzforce=False, interpDens=False, interpvcirc=False, interpdvcircdr=False, interpepifreq=False,interpverticalfreq=False, ro=None,vo=None, use_c=False,enable_c=False,zsym=True, numcores=None): """ NAME: __init__ PURPOSE: Initialize an interpRZPotential instance INPUT: RZPot - RZPotential to be interpolated rgrid - R grid to be given to linspace as in rs= linspace(*rgrid) zgrid - z grid to be given to linspace as in zs= linspace(*zgrid) logR - if True, rgrid is in the log of R so logrs= linspace(*rgrid) interpPot, interpRforce, interpzforce, interpDens,interpvcirc, interpepifreq, interpverticalfreq, interpdvcircdr= if True, interpolate these functions use_c= use C to speed up the calculation of the grid enable_c= enable use of C for interpolations zsym= if True (default), the potential is assumed to be symmetric around z=0 (so you can use, e.g., zgrid=(0.,1.,101)). numcores= if set to an integer, use this many cores (only used for vcirc, dvcircdR, epifreq, and verticalfreq; NOT NECESSARILY FASTER, TIME TO MAKE SURE) ro=, vo= distance and velocity scales for translation into internal units (default from configuration file) OUTPUT: instance HISTORY: 2010-07-21 - Written - Bovy (NYU) 2013-01-24 - Started with new implementation - Bovy (IAS) """ if isinstance(RZPot,interpRZPotential): from galpy.potential import PotentialError raise PotentialError('Cannot setup interpRZPotential with another interpRZPotential') # Propagate ro and vo roSet= True voSet= True if ro is None: if isinstance(RZPot,list): ro= RZPot[0]._ro roSet= RZPot[0]._roSet else: ro= RZPot._ro roSet= RZPot._roSet if vo is None: if isinstance(RZPot,list): vo= RZPot[0]._vo voSet= RZPot[0]._voSet else: vo= RZPot._vo voSet= RZPot._voSet Potential.__init__(self,amp=1.,ro=ro,vo=vo) # Turn off physical if it hadn't been on if not roSet: self._roSet= False if not voSet: self._voSet= False self._origPot= RZPot self._rgrid= numpy.linspace(*rgrid) self._logR= logR if self._logR: self._rgrid= numpy.exp(self._rgrid) self._logrgrid= numpy.log(self._rgrid) self._zgrid= numpy.linspace(*zgrid) self._interpPot= interpPot self._interpRforce= interpRforce self._interpzforce= interpzforce self._interpDens= interpDens self._interpvcirc= interpvcirc self._interpdvcircdr= interpdvcircdr self._interpepifreq= interpepifreq self._interpverticalfreq= interpverticalfreq self._enable_c= enable_c*ext_loaded self.hasC= self._enable_c self._zsym= zsym if interpPot: if use_c*ext_loaded: self._potGrid, err= calc_potential_c(self._origPot,self._rgrid,self._zgrid) else: from galpy.potential import evaluatePotentials potGrid= numpy.zeros((len(self._rgrid),len(self._zgrid))) for ii in range(len(self._rgrid)): for jj in range(len(self._zgrid)): potGrid[ii,jj]= evaluatePotentials(self._origPot,self._rgrid[ii],self._zgrid[jj]) self._potGrid= potGrid if self._logR: self._potInterp= interpolate.RectBivariateSpline(self._logrgrid, self._zgrid, self._potGrid, kx=3,ky=3,s=0.) else: self._potInterp= interpolate.RectBivariateSpline(self._rgrid, self._zgrid, self._potGrid, kx=3,ky=3,s=0.) if enable_c*ext_loaded: self._potGrid_splinecoeffs= calc_2dsplinecoeffs_c(self._potGrid) if interpRforce: if use_c*ext_loaded: self._rforceGrid, err= calc_potential_c(self._origPot,self._rgrid,self._zgrid,rforce=True) else: from galpy.potential import evaluateRforces rforceGrid= numpy.zeros((len(self._rgrid),len(self._zgrid))) for ii in range(len(self._rgrid)): for jj in range(len(self._zgrid)): rforceGrid[ii,jj]= evaluateRforces(self._origPot,self._rgrid[ii],self._zgrid[jj]) self._rforceGrid= rforceGrid if self._logR: self._rforceInterp= interpolate.RectBivariateSpline(self._logrgrid, self._zgrid, self._rforceGrid, kx=3,ky=3,s=0.) else: self._rforceInterp= interpolate.RectBivariateSpline(self._rgrid, self._zgrid, self._rforceGrid, kx=3,ky=3,s=0.) if enable_c*ext_loaded: self._rforceGrid_splinecoeffs= calc_2dsplinecoeffs_c(self._rforceGrid) if interpzforce: if use_c*ext_loaded: self._zforceGrid, err= calc_potential_c(self._origPot,self._rgrid,self._zgrid,zforce=True) else: from galpy.potential import evaluatezforces zforceGrid= numpy.zeros((len(self._rgrid),len(self._zgrid))) for ii in range(len(self._rgrid)): for jj in range(len(self._zgrid)): zforceGrid[ii,jj]= evaluatezforces(self._origPot,self._rgrid[ii],self._zgrid[jj]) self._zforceGrid= zforceGrid if self._logR: self._zforceInterp= interpolate.RectBivariateSpline(self._logrgrid, self._zgrid, self._zforceGrid, kx=3,ky=3,s=0.) else: self._zforceInterp= interpolate.RectBivariateSpline(self._rgrid, self._zgrid, self._zforceGrid, kx=3,ky=3,s=0.) if enable_c*ext_loaded: self._zforceGrid_splinecoeffs= calc_2dsplinecoeffs_c(self._zforceGrid) if interpDens: from galpy.potential import evaluateDensities densGrid= numpy.zeros((len(self._rgrid),len(self._zgrid))) for ii in range(len(self._rgrid)): for jj in range(len(self._zgrid)): densGrid[ii,jj]= evaluateDensities(self._origPot,self._rgrid[ii],self._zgrid[jj]) self._densGrid= densGrid if self._logR: self._densInterp= interpolate.RectBivariateSpline(self._logrgrid, self._zgrid, numpy.log(self._densGrid+10.**-10.), kx=3,ky=3,s=0.) else: self._densInterp= interpolate.RectBivariateSpline(self._rgrid, self._zgrid, numpy.log(self._densGrid+10.**-10.), kx=3,ky=3,s=0.) if interpvcirc: from galpy.potential import vcirc if not numcores is None: self._vcircGrid= multi.parallel_map((lambda x: vcirc(self._origPot,self._rgrid[x])), list(range(len(self._rgrid))),numcores=numcores) else: self._vcircGrid= numpy.array([vcirc(self._origPot,r) for r in self._rgrid]) if self._logR: self._vcircInterp= interpolate.InterpolatedUnivariateSpline(self._logrgrid,self._vcircGrid,k=3) else: self._vcircInterp= interpolate.InterpolatedUnivariateSpline(self._rgrid,self._vcircGrid,k=3) if interpdvcircdr: from galpy.potential import dvcircdR if not numcores is None: self._dvcircdrGrid= multi.parallel_map((lambda x: dvcircdR(self._origPot,self._rgrid[x])), list(range(len(self._rgrid))),numcores=numcores) else: self._dvcircdrGrid= numpy.array([dvcircdR(self._origPot,r) for r in self._rgrid]) if self._logR: self._dvcircdrInterp= interpolate.InterpolatedUnivariateSpline(self._logrgrid,self._dvcircdrGrid,k=3) else: self._dvcircdrInterp= interpolate.InterpolatedUnivariateSpline(self._rgrid,self._dvcircdrGrid,k=3) if interpepifreq: from galpy.potential import epifreq if not numcores is None: self._epifreqGrid= numpy.array(multi.parallel_map((lambda x: epifreq(self._origPot,self._rgrid[x])), list(range(len(self._rgrid))),numcores=numcores)) else: self._epifreqGrid= numpy.array([epifreq(self._origPot,r) for r in self._rgrid]) indx= True^numpy.isnan(self._epifreqGrid) if numpy.sum(indx) < 4: if self._logR: self._epifreqInterp= interpolate.InterpolatedUnivariateSpline(self._logrgrid[indx],self._epifreqGrid[indx],k=1) else: self._epifreqInterp= interpolate.InterpolatedUnivariateSpline(self._rgrid[indx],self._epifreqGrid[indx],k=1) else: if self._logR: self._epifreqInterp= interpolate.InterpolatedUnivariateSpline(self._logrgrid[indx],self._epifreqGrid[indx],k=3) else: self._epifreqInterp= interpolate.InterpolatedUnivariateSpline(self._rgrid[indx],self._epifreqGrid[indx],k=3) if interpverticalfreq: from galpy.potential import verticalfreq if not numcores is None: self._verticalfreqGrid= multi.parallel_map((lambda x: verticalfreq(self._origPot,self._rgrid[x])), list(range(len(self._rgrid))),numcores=numcores) else: self._verticalfreqGrid= numpy.array([verticalfreq(self._origPot,r) for r in self._rgrid]) if self._logR: self._verticalfreqInterp= interpolate.InterpolatedUnivariateSpline(self._logrgrid,self._verticalfreqGrid,k=3) else: self._verticalfreqInterp= interpolate.InterpolatedUnivariateSpline(self._rgrid,self._verticalfreqGrid,k=3) return None @scalarVectorDecorator @zsymDecorator(False) def _evaluate(self,R,z,phi=0.,t=0.): from galpy.potential import evaluatePotentials if self._interpPot: out= numpy.empty_like(R) indx= (R >= self._rgrid[0])*(R <= self._rgrid[-1])\ *(z <= self._zgrid[-1])*(z >= self._zgrid[0]) if numpy.sum(indx) > 0: if self._enable_c: out[indx]= eval_potential_c(self,R[indx],z[indx])[0]/self._amp else: if self._logR: out[indx]= self._potInterp.ev(numpy.log(R[indx]),z[indx]) else: out[indx]= self._potInterp.ev(R[indx],z[indx]) if numpy.sum(True^indx) > 0: out[True^indx]= evaluatePotentials(self._origPot, R[True^indx], z[True^indx]) return out else: return evaluatePotentials(self._origPot,R,z) @scalarVectorDecorator @zsymDecorator(False) def _Rforce(self,R,z,phi=0.,t=0.): from galpy.potential import evaluateRforces if self._interpRforce: out= numpy.empty_like(R) indx= (R >= self._rgrid[0])*(R <= self._rgrid[-1])\ *(z <= self._zgrid[-1])*(z >= self._zgrid[0]) if numpy.sum(indx) > 0: if self._enable_c: out[indx]= eval_force_c(self,R[indx],z[indx])[0]/self._amp else: if self._logR: out[indx]= self._rforceInterp.ev(numpy.log(R[indx]),z[indx]) else: out[indx]= self._rforceInterp.ev(R[indx],z[indx]) if numpy.sum(True^indx) > 0: out[True^indx]= evaluateRforces(self._origPot, R[True^indx], z[True^indx]) return out else: return evaluateRforces(self._origPot,R,z) @scalarVectorDecorator @zsymDecorator(True) def _zforce(self,R,z,phi=0.,t=0.): from galpy.potential import evaluatezforces if self._interpzforce: out= numpy.empty_like(R) indx= (R >= self._rgrid[0])*(R <= self._rgrid[-1])\ *(z <= self._zgrid[-1])*(z >= self._zgrid[0]) if numpy.sum(indx) > 0: if self._enable_c: out[indx]= eval_force_c(self,R[indx],z[indx], zforce=True)[0]/self._amp else: if self._logR: out[indx]= self._zforceInterp.ev(numpy.log(R[indx]), z[indx]) else: out[indx]= self._zforceInterp.ev(R[indx],z[indx]) if numpy.sum(True^indx) > 0: out[True^indx]= evaluatezforces(self._origPot, R[True^indx], z[True^indx]) return out else: return evaluatezforces(self._origPot,R,z) def _Rzderiv(self,R,z,phi=0.,t=0.): from galpy.potential import evaluateRzderivs return evaluateRzderivs(self._origPot,R,z) @scalarVectorDecorator @zsymDecorator(False) def _dens(self,R,z,phi=0.,t=0.): from galpy.potential import evaluateDensities if self._interpDens: out= numpy.empty_like(R) indx= (R >= self._rgrid[0])*(R <= self._rgrid[-1])\ *(z <= self._zgrid[-1])*(z >= self._zgrid[0]) if numpy.sum(indx) > 0: if self._logR: out[indx]= numpy.exp(self._densInterp.ev(numpy.log(R[indx]),z[indx]))-10.**-10. else: out[indx]= numpy.exp(self._densInterp.ev(R[indx],z[indx]))-10.**-10. if numpy.sum(True^indx) > 0: out[True^indx]= evaluateDensities(self._origPot, R[True^indx], z[True^indx]) return out else: return evaluateDensities(self._origPot,R,z) @physical_conversion('velocity',pop=True) @scalarDecorator def vcirc(self,R): from galpy.potential import vcirc if self._interpvcirc: indx= (R >= self._rgrid[0])*(R <= self._rgrid[-1]) out= numpy.empty_like(R) if numpy.sum(indx) > 0: if self._logR: out[indx]= self._vcircInterp(numpy.log(R[indx])) else: out[indx]= self._vcircInterp(R[indx]) if numpy.sum(True^indx) > 0: out[True^indx]= vcirc(self._origPot,R[True^indx]) return out else: return vcirc(self._origPot,R) @physical_conversion('frequency',pop=True) @scalarDecorator def dvcircdR(self,R): from galpy.potential import dvcircdR if self._interpdvcircdr: indx= (R >= self._rgrid[0])*(R <= self._rgrid[-1]) out= numpy.empty_like(R) if numpy.sum(indx) > 0: if self._logR: out[indx]= self._dvcircdrInterp(numpy.log(R[indx])) else: out[indx]= self._dvcircdrInterp(R[indx]) if numpy.sum(True^indx) > 0: out[True^indx]= dvcircdR(self._origPot,R[True^indx]) return out else: return dvcircdR(self._origPot,R) @physical_conversion('frequency',pop=True) @scalarDecorator def epifreq(self,R): from galpy.potential import epifreq if self._interpepifreq: indx= (R >= self._rgrid[0])*(R <= self._rgrid[-1]) out= numpy.empty_like(R) if numpy.sum(indx) > 0: if self._logR: out[indx]= self._epifreqInterp(numpy.log(R[indx])) else: out[indx]= self._epifreqInterp(R[indx]) if numpy.sum(True^indx) > 0: out[True^indx]= epifreq(self._origPot,R[True^indx]) return out else: return epifreq(self._origPot,R) @physical_conversion('frequency',pop=True) @scalarDecorator def verticalfreq(self,R): from galpy.potential import verticalfreq if self._interpverticalfreq: indx= (R >= self._rgrid[0])*(R <= self._rgrid[-1]) out= numpy.empty_like(R) if numpy.sum(indx) > 0: if self._logR: out[indx]= self._verticalfreqInterp(numpy.log(R[indx])) else: out[indx]= self._verticalfreqInterp(R[indx]) if numpy.sum(True^indx) > 0: out[True^indx]= verticalfreq(self._origPot,R[True^indx]) return out else: return verticalfreq(self._origPot,R) def calc_potential_c(pot,R,z,rforce=False,zforce=False): """ NAME: calc_potential_c PURPOSE: Use C to calculate the potential on a grid INPUT: pot - Potential or list of such instances R - grid in R z - grid in z rforce=, zforce= if either of these is True, calculate the radial or vertical force instead OUTPUT: potential on the grid (2D array) HISTORY: 2013-01-24 - Written - Bovy (IAS) 2013-01-29 - Added forces - Bovy (IAS) """ from galpy.orbit_src.integrateFullOrbit import _parse_pot #here bc otherwise there is an infinite loop #Parse the potential npot, pot_type, pot_args= _parse_pot(pot) #Set up result arrays out= numpy.empty((len(R),len(z))) err= ctypes.c_int(0) #Set up the C code ndarrayFlags= ('C_CONTIGUOUS','WRITEABLE') if rforce: interppotential_calc_potentialFunc= _lib.calc_rforce elif zforce: interppotential_calc_potentialFunc= _lib.calc_zforce else: interppotential_calc_potentialFunc= _lib.calc_potential interppotential_calc_potentialFunc.argtypes= [ctypes.c_int, ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ctypes.c_int, ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ctypes.c_int, ndpointer(dtype=numpy.int32,flags=ndarrayFlags), ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ctypes.POINTER(ctypes.c_int)] #Array requirements, first store old order f_cont= [R.flags['F_CONTIGUOUS'], z.flags['F_CONTIGUOUS']] R= numpy.require(R,dtype=numpy.float64,requirements=['C','W']) z= numpy.require(z,dtype=numpy.float64,requirements=['C','W']) out= numpy.require(out,dtype=numpy.float64,requirements=['C','W']) #Run the C code interppotential_calc_potentialFunc(len(R), R, len(z), z, ctypes.c_int(npot), pot_type, pot_args, out, ctypes.byref(err)) #Reset input arrays if f_cont[0]: R= numpy.asfortranarray(R) if f_cont[1]: z= numpy.asfortranarray(z) return (out,err.value) def calc_2dsplinecoeffs_c(array2d): """ NAME: calc_2dsplinecoeffs_c PURPOSE: Use C to calculate spline coefficients for a 2D array INPUT: array2d OUTPUT: new array with spline coeffs HISTORY: 2013-01-24 - Written - Bovy (IAS) """ #Set up result arrays out= copy.copy(array2d) out= numpy.require(out,dtype=numpy.float64,requirements=['C','W']) #Set up the C code ndarrayFlags= ('C_CONTIGUOUS','WRITEABLE') interppotential_calc_2dsplinecoeffs= _lib.samples_to_coefficients interppotential_calc_2dsplinecoeffs.argtypes= [ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ctypes.c_int, ctypes.c_int] #Run the C code interppotential_calc_2dsplinecoeffs(out,out.shape[1],out.shape[0]) return out def eval_potential_c(pot,R,z): """ NAME: eval_potential_c PURPOSE: Use C to evaluate the interpolated potential INPUT: pot - Potential or list of such instances R - array z - array OUTPUT: potential evaluated R and z HISTORY: 2013-01-24 - Written - Bovy (IAS) """ from galpy.orbit_src.integrateFullOrbit import _parse_pot #here bc otherwise there is an infinite loop #Parse the potential npot, pot_type, pot_args= _parse_pot(pot,potforactions=True) #Set up result arrays out= numpy.empty((len(R))) err= ctypes.c_int(0) #Set up the C code ndarrayFlags= ('C_CONTIGUOUS','WRITEABLE') interppotential_calc_potentialFunc= _lib.eval_potential interppotential_calc_potentialFunc.argtypes= [ctypes.c_int, ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ctypes.c_int, ndpointer(dtype=numpy.int32,flags=ndarrayFlags), ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ctypes.POINTER(ctypes.c_int)] #Array requirements, first store old order f_cont= [R.flags['F_CONTIGUOUS'], z.flags['F_CONTIGUOUS']] R= numpy.require(R,dtype=numpy.float64,requirements=['C','W']) z= numpy.require(z,dtype=numpy.float64,requirements=['C','W']) out= numpy.require(out,dtype=numpy.float64,requirements=['C','W']) #Run the C code interppotential_calc_potentialFunc(len(R), R, z, ctypes.c_int(npot), pot_type, pot_args, out, ctypes.byref(err)) #Reset input arrays if f_cont[0]: R= numpy.asfortranarray(R) if f_cont[1]: z= numpy.asfortranarray(z) return (out,err.value) def eval_force_c(pot,R,z,zforce=False): """ NAME: eval_force_c PURPOSE: Use C to evaluate the interpolated potential's forces INPUT: pot - Potential or list of such instances R - array z - array zforce= if True, return the vertical force, otherwise return the radial force OUTPUT: force evaluated R and z HISTORY: 2013-01-29 - Written - Bovy (IAS) """ from galpy.orbit_src.integrateFullOrbit import _parse_pot #here bc otherwise there is an infinite loop #Parse the potential npot, pot_type, pot_args= _parse_pot(pot) #Set up result arrays out= numpy.empty((len(R))) err= ctypes.c_int(0) #Set up the C code ndarrayFlags= ('C_CONTIGUOUS','WRITEABLE') if zforce: interppotential_calc_forceFunc= _lib.eval_zforce else: interppotential_calc_forceFunc= _lib.eval_rforce interppotential_calc_forceFunc.argtypes= [ctypes.c_int, ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ctypes.c_int, ndpointer(dtype=numpy.int32,flags=ndarrayFlags), ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ndpointer(dtype=numpy.float64,flags=ndarrayFlags), ctypes.POINTER(ctypes.c_int)] #Array requirements, first store old order f_cont= [R.flags['F_CONTIGUOUS'], z.flags['F_CONTIGUOUS']] R= numpy.require(R,dtype=numpy.float64,requirements=['C','W']) z= numpy.require(z,dtype=numpy.float64,requirements=['C','W']) out= numpy.require(out,dtype=numpy.float64,requirements=['C','W']) #Run the C code interppotential_calc_forceFunc(len(R), R, z, ctypes.c_int(npot), pot_type, pot_args, out, ctypes.byref(err)) #Reset input arrays if f_cont[0]: R= numpy.asfortranarray(R) if f_cont[1]: z= numpy.asfortranarray(z) return (out,err.value) def sign(x): out= numpy.ones_like(x) out[(x < 0.)]= -1. return out galpy-1.3.0/galpy/potential_src/IsochronePotential.py0000644000076500000240000001340413003761364023103 0ustar bovystaff00000000000000############################################################################### # IsochronePotential.py: The isochrone potential # # - amp # Phi(r)= --------------------- # b + sqrt{b^2+r^2} ############################################################################### import numpy as nu from galpy.potential_src.Potential import Potential, _APY_LOADED if _APY_LOADED: from astropy import units class IsochronePotential(Potential): """Class that implements the Isochrone potential .. math:: \\Phi(r) = -\\frac{\\mathrm{amp}}{b+\\sqrt{b^2+r^2}} """ def __init__(self,amp=1.,b=1.,normalize=False, ro=None,vo=None): """ NAME: __init__ PURPOSE: initialize an isochrone potential INPUT: amp= amplitude to be applied to the potential (default: 1); can be a Quantity with units of mass density or Gxmass density b= scale radius of the isochrone potential (can be Quantity) normalize= if True, normalize such that vc(1.,0.)=1., or, if given as a number, such that the force is this fraction of the force necessary to make vc(1.,0.)=1. ro=, vo= distance and velocity scales for translation into internal units (default from configuration file) OUTPUT: (none) HISTORY: 2013-09-08 - Written - Bovy (IAS) """ Potential.__init__(self,amp=amp,ro=ro,vo=vo,amp_units='mass') if _APY_LOADED and isinstance(b,units.Quantity): b= b.to(units.kpc).value/self._ro self.b= b self._scale= self.b self.b2= self.b**2. if normalize or \ (isinstance(normalize,(int,float)) \ and not isinstance(normalize,bool)): #pragma: no cover self.normalize(normalize) self.hasC= True self.hasC_dxdv= True def _evaluate(self,R,z,phi=0.,t=0.): """ NAME: _evaluate PURPOSE: evaluate the potential at R,z INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t - time OUTPUT: Phi(R,z) HISTORY: 2013-09-08 - Written - Bovy (IAS) """ r2= R**2.+z**2. rb= nu.sqrt(r2+self.b2) return -1./(self.b+rb) def _Rforce(self,R,z,phi=0.,t=0.): """ NAME: _Rforce PURPOSE: evaluate the radial force for this potential INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t - time OUTPUT: the radial force HISTORY: 2013-09-08 - Written - Bovy (IAS) """ r2= R**2.+z**2. rb= nu.sqrt(r2+self.b2) dPhidrr= -1./rb/(self.b+rb)**2. return dPhidrr*R def _zforce(self,R,z,phi=0.,t=0.): """ NAME: _zforce PURPOSE: evaluate the vertical force for this potential INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t - time OUTPUT: the vertical force HISTORY: 2013-09-08 - Written - Bovy (IAS) """ r2= R**2.+z**2. rb= nu.sqrt(r2+self.b2) dPhidrr= -1./rb/(self.b+rb)**2. return dPhidrr*z def _R2deriv(self,R,z,phi=0.,t=0.): """ NAME: _Rderiv PURPOSE: evaluate the second radial derivative for this potential INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t - time OUTPUT: the second radial derivative HISTORY: 2013-09-08 - Written - Bovy (IAS) """ r2= R**2.+z**2. rb= nu.sqrt(r2+self.b2) return -(-self.b**3.-self.b*z**2.+(2.*R**2.-z**2.-self.b**2.)*rb)/\ rb**3./(self.b+rb)**3. def _z2deriv(self,R,z,phi=0.,t=0.): """ NAME: _z2deriv PURPOSE: evaluate the second vertical derivative for this potential INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t- time OUTPUT: the second vertical derivative HISTORY: 2013-09-08 - Written - Bovy (IAS) """ r2= R**2.+z**2. rb= nu.sqrt(r2+self.b2) return -(-self.b**3.-self.b*R**2.-(R**2.-2.*z**2.+self.b**2.)*rb)/\ rb**3./(self.b+rb)**3. def _Rzderiv(self,R,z,phi=0.,t=0.): """ NAME: _Rzderiv PURPOSE: evaluate the mixed R,z derivative for this potential INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t- time OUTPUT: d2phi/dR/dz HISTORY: 2013-09-08 - Written - Bovy (IAS) """ r2= R**2.+z**2. rb= nu.sqrt(r2+self.b2) return -R*z*(self.b+3.*rb)/\ rb**3./(self.b+rb)**3. def _dens(self,R,z,phi=0.,t=0.): """ NAME: _dens PURPOSE: evaluate the density force for this potential INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t - time OUTPUT: the density HISTORY: 2013-09-08 - Written - Bovy (IAS) """ r2= R**2.+z**2. rb= nu.sqrt(r2+self.b2) return (3.*(self.b+rb)*rb**2.-r2*(self.b+3.*rb))/\ rb**3./(self.b+rb)**3./4./nu.pi galpy-1.3.0/galpy/potential_src/KGPotential.py0000644000076500000240000000540613003465421021451 0ustar bovystaff00000000000000import scipy as sc from galpy.util import bovy_conversion from galpy.potential_src.linearPotential import linearPotential, _APY_LOADED if _APY_LOADED: from astropy import units class KGPotential(linearPotential): """Class representing the Kuijken & Gilmore (1989) potential .. math:: \Phi(x) = \\mathrm{amp}\\,\\left(K\\,\\left(\\sqrt{x^2+D^2}-D\\right)+F\\,x^2\\right) """ def __init__(self,K=1.15,F=0.03,D=1.8,amp=1.,ro=None,vo=None): """ NAME: __init__ PURPOSE: Initialize a KGPotential INPUT: K= K parameter (= :math:`2\\pi \\Sigma_{\\mathrm{disk}}`; specify either as surface density or directly as force [i.e., including :math:`2\\pi G`]; can be Quantity) F= F parameter (= :math:`4\\pi\\rho_{\\mathrm{halo}}`; specify either as density or directly as second potential derivative [i.e., including :math:`4\\pi G`]; can be Quantity) D= D parameter (natural units or Quantity length units) amp - an overall amplitude OUTPUT: instance HISTORY: 2010-07-12 - Written - Bovy (NYU) """ linearPotential.__init__(self,amp=amp,ro=ro,vo=vo) if _APY_LOADED and isinstance(D,units.Quantity): D= D.to(units.kpc).value/self._ro if _APY_LOADED and isinstance(K,units.Quantity): try: K= K.to(units.pc/units.Myr**2).value\ /bovy_conversion.force_in_pcMyr2(self._vo,self._ro) except units.UnitConversionError: pass if _APY_LOADED and isinstance(K,units.Quantity): try: K= K.to(units.Msun/units.pc**2).value\ /bovy_conversion.force_in_2piGmsolpc2(self._vo,self._ro) except units.UnitConversionError: raise units.UnitConversionError("Units for K not understood; should be force or surface density") if _APY_LOADED and isinstance(F,units.Quantity): try: F= F.to(units.Msun/units.pc**3).value\ /bovy_conversion.dens_in_msolpc3(self._vo,self._ro)*4.*sc.pi except units.UnitConversionError: pass if _APY_LOADED and isinstance(F,units.Quantity): try: F= F.to(units.km**2/units.s**2/units.kpc**2).value\ *ro**2/vo**2 except units.UnitConversionError: raise units.UnitConversionError("Units for F not understood; should be density") self._K= K self._F= F self._D= D self._D2= self._D**2. def _evaluate(self,x,t=0.): return self._K*(sc.sqrt(x**2.+self._D2)-self._D)+self._F*x**2. def _force(self,x,t=0.): return -x*(self._K/sc.sqrt(x**2+self._D2)+2.*self._F) galpy-1.3.0/galpy/potential_src/KuzminDiskPotential.py0000644000076500000240000001262713003761364023250 0ustar bovystaff00000000000000############################################################################### # KuzminDiskPotential.py: class that implements Kuzmin disk potential # # - amp # Phi(R, z)= --------------------------- # \sqrt{R^2 + (a + |z|)^2} ############################################################################### import numpy as nu from galpy.potential_src.Potential import Potential, _APY_LOADED if _APY_LOADED: from astropy import units class KuzminDiskPotential(Potential): """Class that implements the Kuzmin Disk potential .. math:: \\Phi(R,z) = -\\frac{\\mathrm{amp}}{\\sqrt{R^2 + (a + |z|)^2}} """ def __init__(self, amp=1., a=1. ,normalize=False, ro=None,vo=None): """ NAME: __init__ PURPOSE: initialize a Kuzmin disk Potential INPUT: amp - amplitude to be applied to the potential (default: 1); can be a Quantity with units of mass density or Gxmass density a - scale length (can be Quantity) normalize - if True, normalize such that vc(1.,0.)=1., or, if given as a number, such that the force is this fraction of the force necessary to make vc(1.,0.)=1. ro=, vo= distance and velocity scales for translation into internal units (default from configuration file) OUTPUT: KuzminDiskPotential object HISTORY: 2016-05-09 - Written - Aladdin """ Potential.__init__(self,amp=amp,ro=ro,vo=vo,amp_units='mass') if _APY_LOADED and isinstance(a,units.Quantity): a= a.to(units.kpc).value/self._ro self._a = a ## a must be greater or equal to 0. if normalize or \ (isinstance(normalize,(int,float)) \ and not isinstance(normalize,bool)): self.normalize(normalize) self.hasC = True self.hasC_dxdv= True return None def _evaluate(self,R,z,phi=0.,t=0.): """ NAME: _evaluate PURPOSE: evaluate the potential at (R,z) INPUT: R - Cylindrical Galactocentric radius z - vertical height phi - azimuth t - time OUTPUT: potential at (R,z) HISTORY: 2016-05-09 - Written - Aladdin """ return -self._denom(R, z)**-0.5 def _Rforce(self,R,z,phi=0.,t=0.): """ NAME: _Rforce PURPOSE: evaluate the radial force for this potential INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t - time OUTPUT: the radial force = -dphi/dR HISTORY: 2016-05-09 - Written - Aladdin """ return -self._denom(R, z)**-1.5 * R def _zforce(self, R, z, phi=0., t=0.): """ NAME: _zforce PURPOSE: evaluate the vertical force for this potential INPUT: R - Cylindrical Galactocentric radius z - vertical height phi - azimuth t - time OUTPUT: the vertical force = -dphi/dz HISTORY: 2016-05-09 - Written - Aladdin """ return -nu.sign(z) * self._denom(R,z)**-1.5 * (self._a + nu.fabs(z)) def _R2deriv(self,R,z,phi=0.,t=0.): """ NAME: _Rforce PURPOSE: evaluate the second radial derivative for this potential INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t - time OUTPUT: the second radial derivative HISTORY: 2016-05-13 - Written - Aladdin """ return self._denom(R, z)**-1.5 - 3.*R**2 * self._denom(R, z)**-2.5 def _z2deriv(self,R,z,phi=0.,t=0.): """ NAME: _z2deriv PURPOSE: evaluate the second vertical derivative for this potential INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t - time OUTPUT: the second vertical derivative HISTORY: 2016-05-13 - Written - Aladdin """ a = self._a return self._denom(R, z)**-1.5 - 3. * (a + nu.fabs(z))**2. * self._denom(R, z)**-2.5 def _Rzderiv(self,R,z,phi=0.,t=0.): """ NAME: _Rzderiv PURPOSE: evaluate the mixed R,z derivative for this potential INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t - time OUTPUT: d2phi/dR/dz HISTORY: 2016-05-13 - Written - Aladdin """ return -3 * nu.sign(z) * R * (self._a + nu.fabs(z)) *self._denom(R, z)**-2.5 def _denom(self, R, z): """ NAME: _denom PURPOSE: evaluate R^2 + (a + |z|)^2 which is used in the denominator of most equations INPUT: R - Cylindrical Galactocentric radius z - vertical height OUTPUT: R^2 + (a + |z|)^2 HISTORY: 2016-05-09 - Written - Aladdin """ return (R**2. + (self._a + nu.fabs(z))**2.) galpy-1.3.0/galpy/potential_src/KuzminKutuzovStaeckelPotential.py0000644000076500000240000002427113003761364025517 0ustar bovystaff00000000000000############################################################################### # KuzminKutuzovStaeckelPotential.py: # class that implements a simple Staeckel potential # generated by a Kuzmin-Kutuzov potential # - amp # Phi(r)= --------------------------- # \sqrt{lambda} + \sqrt{nu} ############################################################################### import numpy as nu from galpy.potential_src.Potential import Potential, _APY_LOADED if _APY_LOADED: from astropy import units from galpy.util import bovy_coords #for prolate spherical coordinate transforms class KuzminKutuzovStaeckelPotential(Potential): """Class that implements the Kuzmin-Kutuzov Staeckel potential .. math:: \\Phi(R,z) = -\\frac{\\mathrm{amp}}{\\sqrt{\\lambda} + \\sqrt{\\nu}} (see, e.g., `Batsleer & Dejonghe 1994 `__) """ def __init__(self,amp=1.,ac=5.,Delta=1.,normalize=False, ro=None,vo=None): """ NAME: __init__ PURPOSE: initialize a Kuzmin-Kutuzov Staeckel potential INPUT: amp - amplitude to be applied to the potential (default: 1); can be a Quantity with units of mass density or Gxmass density ac - axis ratio of the coordinate surfaces; (a/c) = sqrt(-alpha) / sqrt(-gamma) (default: 5.) Delta - focal distance that defines the spheroidal coordinate system (default: 1.); Delta=sqrt(gamma-alpha) (can be Quantity) normalize - if True, normalize such that vc(1.,0.)=1., or, if given as a number, such that the force is this fraction of the force necessary to make vc(1.,0.)=1. ro=, vo= distance and velocity scales for translation into internal units (default from configuration file) OUTPUT: (none) HISTORY: 2015-02-15 - Written - Trick (MPIA) """ Potential.__init__(self,amp=amp,ro=ro,vo=vo,amp_units='mass') if _APY_LOADED and isinstance(Delta,units.Quantity): Delta= Delta.to(units.kpc).value/self._ro self._ac = ac self._Delta = Delta self._gamma = self._Delta**2 / (1.-self._ac**2) self._alpha = self._gamma - self._Delta**2 if normalize or \ (isinstance(normalize,(int,float)) \ and not isinstance(normalize,bool)): self.normalize(normalize) self.hasC = True self.hasC_dxdv = True def _evaluate(self,R,z,phi=0.,t=0.): """ NAME: _evaluate PURPOSE: evaluate the potential at R,z INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t - time OUTPUT: Phi(R,z) HISTORY: 2015-02-15 - Written - Trick (MPIA) """ l,n = bovy_coords.Rz_to_lambdanu(R,z,ac=self._ac,Delta=self._Delta) return -1./(nu.sqrt(l) + nu.sqrt(n)) def _Rforce(self,R,z,phi=0.,t=0.): """ NAME: _Rforce PURPOSE: evaluate the radial force for this potential INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t - time OUTPUT: the radial force = -dphi/dR HISTORY: 2015-02-13 - Written - Trick (MPIA) """ l,n = bovy_coords.Rz_to_lambdanu (R,z,ac=self._ac,Delta=self._Delta) jac = bovy_coords.Rz_to_lambdanu_jac(R,z, Delta=self._Delta) dldR = jac[0,0] dndR = jac[1,0] return - (dldR * self._lderiv(l,n) + \ dndR * self._nderiv(l,n)) def _zforce(self,R,z,phi=0.,t=0.): """ NAME: _zforce PURPOSE: evaluate the vertical force for this potential INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t - time OUTPUT: the vertical force HISTORY: 2015-02-13 - Written - Trick (MPIA) """ l,n = bovy_coords.Rz_to_lambdanu (R,z,ac=self._ac,Delta=self._Delta) jac = bovy_coords.Rz_to_lambdanu_jac(R,z, Delta=self._Delta) dldz = jac[0,1] dndz = jac[1,1] return - (dldz * self._lderiv(l,n) + \ dndz * self._nderiv(l,n)) def _R2deriv(self,R,z,phi=0.,t=0.): """ NAME: _R2deriv PURPOSE: evaluate the second radial derivative for this potential INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t - time OUTPUT: the second radial derivative HISTORY: 2015-02-13 - Written - Trick (MPIA) """ l,n = bovy_coords.Rz_to_lambdanu (R,z,ac=self._ac,Delta=self._Delta) jac = bovy_coords.Rz_to_lambdanu_jac (R,z, Delta=self._Delta) hess = bovy_coords.Rz_to_lambdanu_hess(R,z, Delta=self._Delta) dldR = jac[0,0] dndR = jac[1,0] d2ldR2 = hess[0,0,0] d2ndR2 = hess[1,0,0] return d2ldR2 * self._lderiv(l,n) + \ d2ndR2 * self._nderiv(l,n) + \ (dldR)**2 * self._l2deriv(l,n) + \ (dndR)**2 * self._n2deriv(l,n) + \ 2.*dldR*dndR * self._lnderiv(l,n) def _z2deriv(self,R,z,phi=0.,t=0.): """ NAME: _z2deriv PURPOSE: evaluate the second vertical derivative for this potential INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t- time OUTPUT: the second vertical derivative HISTORY: 2015-02-13 - Written - Trick (MPIA) """ l,n = bovy_coords.Rz_to_lambdanu (R,z,ac=self._ac,Delta=self._Delta) jac = bovy_coords.Rz_to_lambdanu_jac(R,z, Delta=self._Delta) hess = bovy_coords.Rz_to_lambdanu_hess(R,z, Delta=self._Delta) dldz = jac[0,1] dndz = jac[1,1] d2ldz2 = hess[0,1,1] d2ndz2 = hess[1,1,1] return d2ldz2 * self._lderiv(l,n) + \ d2ndz2 * self._nderiv(l,n) + \ (dldz)**2 * self._l2deriv(l,n) + \ (dndz)**2 * self._n2deriv(l,n) + \ 2.*dldz*dndz * self._lnderiv(l,n) def _Rzderiv(self,R,z,phi=0.,t=0.): """ NAME: _Rzderiv PURPOSE: evaluate the mixed R,z derivative for this potential INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t- time OUTPUT: d2phi/dR/dz HISTORY: 2015-02-13 - Written - Trick (MPIA) """ l,n = bovy_coords.Rz_to_lambdanu (R,z,ac=self._ac,Delta=self._Delta) jac = bovy_coords.Rz_to_lambdanu_jac(R,z, Delta=self._Delta) hess = bovy_coords.Rz_to_lambdanu_hess(R,z, Delta=self._Delta) dldR = jac[0,0] dndR = jac[1,0] dldz = jac[0,1] dndz = jac[1,1] d2ldRdz = hess[0,0,1] d2ndRdz = hess[1,0,1] return d2ldRdz * self._lderiv(l,n) + \ d2ndRdz * self._nderiv(l,n) + \ dldR*dldz * self._l2deriv(l,n) + \ dndR*dndz * self._n2deriv(l,n) + \ (dldR*dndz+dldz*dndR)* self._lnderiv(l,n) def _lderiv(self,l,n): """ NAME: _lderiv PURPOSE: evaluate the derivative w.r.t. lambda for this potential INPUT: l - prolate spheroidal coordinate lambda n - prolate spheroidal coordinate nu OUTPUT: derivative w.r.t. lambda HISTORY: 2015-02-15 - Written - Trick (MPIA) """ return 0.5/nu.sqrt(l)/(nu.sqrt(l)+nu.sqrt(n))**2 def _nderiv(self,l,n): """ NAME: _nderiv PURPOSE: evaluate the derivative w.r.t. nu for this potential INPUT: l - prolate spheroidal coordinate lambda n - prolate spheroidal coordinate nu OUTPUT: derivative w.r.t. nu HISTORY: 2015-02-15 - Written - Trick (MPIA) """ return 0.5/nu.sqrt(n)/(nu.sqrt(l)+nu.sqrt(n))**2 def _l2deriv(self,l,n): """ NAME: _l2deriv PURPOSE: evaluate the second derivative w.r.t. lambda for this potential INPUT: l - prolate spheroidal coordinate lambda n - prolate spheroidal coordinate nu OUTPUT: second derivative w.r.t. lambda HISTORY: 2015-02-15 - Written - Trick (MPIA) """ numer = -3.*nu.sqrt(l) - nu.sqrt(n) denom = 4. * l**1.5 * (nu.sqrt(l)+nu.sqrt(n))**3 return numer / denom def _n2deriv(self,l,n): """ NAME: _n2deriv PURPOSE: evaluate the second derivative w.r.t. nu for this potential INPUT: l - prolate spheroidal coordinate lambda n - prolate spheroidal coordinate nu OUTPUT: second derivative w.r.t. nu HISTORY: 2015-02-15 - Written - Trick (MPIA) """ numer = -nu.sqrt(l) - 3.*nu.sqrt(n) denom = 4. * n**1.5 * (nu.sqrt(l)+nu.sqrt(n))**3 return numer / denom def _lnderiv(self,l,n): """ NAME: _lnderiv PURPOSE: evaluate the mixed derivative w.r.t. lambda and nu for this potential INPUT: l - prolate spheroidal coordinate lambda n - prolate spheroidal coordinate nu OUTPUT: d2phi/dl/dn HISTORY: 2015-02-13 - Written - Trick (MPIA) """ return -0.5/(nu.sqrt(l) * nu.sqrt(n) * (nu.sqrt(l)+nu.sqrt(n))**3) galpy-1.3.0/galpy/potential_src/linearPotential.py0000644000076500000240000002111113003465421022411 0ustar bovystaff00000000000000from __future__ import division, print_function import os, os.path import pickle import numpy as nu import galpy.util.bovy_plot as plot from galpy.util import config from galpy.potential_src.Potential import PotentialError from galpy.util.bovy_conversion import physical_conversion,\ potential_physical_input _APY_LOADED= True try: from astropy import units except ImportError: _APY_LOADED= False class linearPotential(object): """Class representing 1D potentials""" def __init__(self,amp=1.,ro=None,vo=None): self._amp= amp self.dim= 1 self.isRZ= False self.hasC= False # Parse ro and vo if ro is None: self._ro= config.__config__.getfloat('normalization','ro') self._roSet= False else: if _APY_LOADED and isinstance(ro,units.Quantity): ro= ro.to(units.kpc).value self._ro= ro self._roSet= True if vo is None: self._vo= config.__config__.getfloat('normalization','vo') self._voSet= False else: if _APY_LOADED and isinstance(vo,units.Quantity): vo= vo.to(units.km/units.s).value self._vo= vo self._voSet= True return None def turn_physical_off(self): """ NAME: turn_physical_off PURPOSE: turn off automatic returning of outputs in physical units INPUT: (none) OUTPUT: (none) HISTORY: 2016-01-30 - Written - Bovy (UofT) """ self._roSet= False self._voSet= False return None def turn_physical_on(self,ro=None,vo=None): """ NAME: turn_physical_on PURPOSE: turn on automatic returning of outputs in physical units INPUT: ro= reference distance (kpc; can be Quantity) vo= reference velocity (km/s; can be Quantity) OUTPUT: (none) HISTORY: 2016-01-30 - Written - Bovy (UofT) """ self._roSet= True self._voSet= True if not ro is None: if _APY_LOADED and isinstance(ro,units.Quantity): ro= ro.to(units.kpc).value self._ro= ro if not vo is None: if _APY_LOADED and isinstance(vo,units.Quantity): vo= vo.to(units.km/units.s).value self._vo= vo return None @potential_physical_input @physical_conversion('energy',pop=True) def __call__(self,x,t=0.): """ NAME: __call__ PURPOSE: evaluate the potential INPUT: x - position (can be Quantity) t= time (optional; can be Quantity) OUTPUT: Phi(x,t) HISTORY: 2010-07-12 - Written - Bovy (NYU) """ return self._call_nodecorator(x,t=t) def _call_nodecorator(self,x,t=0.): # Separate, so it can be used during orbit integration try: return self._amp*self._evaluate(x,t=t) except AttributeError: #pragma: no cover raise PotentialError("'_evaluate' function not implemented for this potential") @potential_physical_input @physical_conversion('force',pop=True) def force(self,x,t=0.): """ NAME: force PURPOSE: evaluate the force INPUT: x - position (can be Quantity) t= time (optional; can be Quantity) OUTPUT: F(x,t) HISTORY: 2010-07-12 - Written - Bovy (NYU) """ return self._force_nodecorator(x,t=t) def _force_nodecorator(self,x,t=0.): # Separate, so it can be used during orbit integration try: return self._amp*self._force(x,t=t) except AttributeError: #pragma: no cover raise PotentialError("'_force' function not implemented for this potential") def plot(self,t=0.,min=-15.,max=15,ns=21,savefilename=None): """ NAME: plot PURPOSE: plot the potential INPUT: t - time to evaluate the potential at min - minimum x max - maximum x ns - grid in x savefilename - save to or restore from this savefile (pickle) OUTPUT: plot to output device HISTORY: 2010-07-13 - Written - Bovy (NYU) """ if not savefilename == None and os.path.exists(savefilename): print("Restoring savefile "+savefilename+" ...") savefile= open(savefilename,'rb') potx= pickle.load(savefile) xs= pickle.load(savefile) savefile.close() else: xs= nu.linspace(min,max,ns) potx= nu.zeros(ns) for ii in range(ns): potx[ii]= self._evaluate(xs[ii],t=t) if not savefilename == None: print("Writing savefile "+savefilename+" ...") savefile= open(savefilename,'wb') pickle.dump(potx,savefile) pickle.dump(xs,savefile) savefile.close() return plot.bovy_plot(xs,potx, xlabel=r"$x/x_0$",ylabel=r"$\Phi(x)$", xrange=[min,max]) @potential_physical_input @physical_conversion('energy',pop=True) def evaluatelinearPotentials(Pot,x,t=0.): """ NAME: evaluatelinearPotentials PURPOSE: evaluate the sum of a list of potentials INPUT: Pot - (list of) linearPotential instance(s) x - evaluate potentials at this position (can be Quantity) t - time to evaluate at (can be Quantity) OUTPUT: pot(x,t) HISTORY: 2010-07-13 - Written - Bovy (NYU) """ return _evaluatelinearPotentials(Pot,x,t=t) def _evaluatelinearPotentials(Pot,x,t=0.): """Raw, undecorated function for internal use""" if isinstance(Pot,list): sum= 0. for pot in Pot: sum+= pot._call_nodecorator(x,t=t) return sum elif isinstance(Pot,linearPotential): return Pot._call_nodecorator(x,t=t) else: #pragma: no cover raise PotentialError("Input to 'evaluatelinearPotentials' is neither a linearPotential-instance or a list of such instances") @potential_physical_input @physical_conversion('force',pop=True) def evaluatelinearForces(Pot,x,t=0.): """ NAME: evaluatelinearForces PURPOSE: evaluate the forces due to a list of potentials INPUT: Pot - (list of) linearPotential instance(s) x - evaluate forces at this position (can be Quantity) t - time to evaluate at (can be Quantity) OUTPUT: force(x,t) HISTORY: 2010-07-13 - Written - Bovy (NYU) """ return _evaluatelinearForces(Pot,x,t=t) def _evaluatelinearForces(Pot,x,t=0.): """Raw, undecorated function for internal use""" if isinstance(Pot,list): sum= 0. for pot in Pot: sum+= pot._force_nodecorator(x,t=t) return sum elif isinstance(Pot,linearPotential): return Pot._force_nodecorator(x,t=t) else: #pragma: no cover raise PotentialError("Input to 'evaluateForces' is neither a linearPotential-instance or a list of such instances") def plotlinearPotentials(Pot,t=0.,min=-15.,max=15,ns=21,savefilename=None): """ NAME: plotlinearPotentials PURPOSE: plot a combination of potentials INPUT: t - time to evaluate potential at min - minimum x max - maximum x ns - grid in x savefilename - save to or restore from this savefile (pickle) OUTPUT: plot to output device HISTORY: 2010-07-13 - Written - Bovy (NYU) """ if not savefilename == None and os.path.exists(savefilename): print("Restoring savefile "+savefilename+" ...") savefile= open(savefilename,'rb') potx= pickle.load(savefile) xs= pickle.load(savefile) savefile.close() else: xs= nu.linspace(min,max,ns) potx= nu.zeros(ns) for ii in range(ns): potx[ii]= evaluatelinearPotentials(Pot,xs[ii],t=t) if not savefilename == None: print("Writing savefile "+savefilename+" ...") savefile= open(savefilename,'wb') pickle.dump(potx,savefile) pickle.dump(xs,savefile) savefile.close() return plot.bovy_plot(xs,potx, xlabel=r"$x/x_0$",ylabel=r"$\Phi(x)$", xrange=[min,max]) galpy-1.3.0/galpy/potential_src/LogarithmicHaloPotential.py0000644000076500000240000002543013171712137024222 0ustar bovystaff00000000000000############################################################################### # LogarithmicHaloPotential.py: class that implements the logarithmic # potential Phi(r) = vc**2 ln(r) ############################################################################### import warnings import numpy as nu from galpy.potential_src.Potential import Potential, kms_to_kpcGyrDecorator, \ _APY_LOADED if _APY_LOADED: from astropy import units from galpy.util import galpyWarning _CORE=10**-8 class LogarithmicHaloPotential(Potential): """Class that implements the logarithmic potential .. math:: \\Phi(R,z) = \\frac{\\mathrm{amp}}{2}\\,\\ln\\left[R^2+\\left(\\frac{z}{q}\\right)^2+\\mathrm{core}^2\\right] Alternatively, the potential can be made triaxial by adding a parameter :math:`b` .. math:: \\Phi(x,y,z) = \\frac{\\mathrm{amp}}{2}\\,\\ln\\left[x^2+\\left(\\frac{y}{b}\\right)^2+\\left(\\frac{z}{q}\\right)^2+\\mathrm{core}^2\\right] With these definitions, :math:`\\sqrt{\mathrm{amp}}` is the circular velocity at :math:`r \gg \mathrm{core}` at :math:`(y,z) = (0,0)`. """ def __init__(self,amp=1.,core=_CORE,q=1.,b=None,normalize=False, ro=None,vo=None): """ NAME: __init__ PURPOSE: initialize a logarithmic potential INPUT: amp - amplitude to be applied to the potential (default: 1); can be a Quantity with units of velocity-squared core - core radius at which the logarithm is cut (can be Quantity) q - potential flattening (z/q)**2. b= (None) if set, shape parameter in y-direction (y --> y/b; see definition) normalize - if True, normalize such that vc(1.,0.)=1., or, if given as a number, such that the force is this fraction of the force necessary to make vc(1.,0.)=1. ro=, vo= distance and velocity scales for translation into internal units (default from configuration file) OUTPUT: (none) HISTORY: 2010-04-02 - Started - Bovy (NYU) """ Potential.__init__(self,amp=amp,ro=ro,vo=vo,amp_units='velocity2') if _APY_LOADED and isinstance(core,units.Quantity): core= core.to(units.kpc).value/self._ro self.hasC= True self.hasC_dxdv= True self._core2= core**2. self._q= q self._b= b if not self._b is None: self.isNonAxi= True self._1m1overb2= 1.-1./self._b**2. if normalize or \ (isinstance(normalize,(int,float)) \ and not isinstance(normalize,bool)): #pragma: no cover self.normalize(normalize) self._nemo_accname= 'LogPot' return None def _evaluate(self,R,z,phi=0.,t=0.): """ NAME: _evaluate PURPOSE: evaluate the potential at R,z INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t - time OUTPUT: Phi(R,z) HISTORY: 2010-04-02 - Started - Bovy (NYU) 2010-04-30 - Adapted for R,z - Bovy (NYU) """ if self.isNonAxi: return 1./2.*nu.log(R**2.*(1.-self._1m1overb2*nu.sin(phi)**2.) +(z/self._q)**2.+self._core2) else: return 1./2.*nu.log(R**2.+(z/self._q)**2.+self._core2) def _Rforce(self,R,z,phi=0.,t=0.): """ NAME: _Rforce PURPOSE: evaluate the radial force for this potential INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t - time OUTPUT: the radial force HISTORY: """ if self.isNonAxi: Rt2= R**2.*(1.-self._1m1overb2*nu.sin(phi)**2.) return -Rt2/R/(Rt2+(z/self._q)**2.+self._core2) else: return -R/(R**2.+(z/self._q)**2.+self._core2) def _zforce(self,R,z,phi=0.,t=0.): """ NAME: _zforce PURPOSE: evaluate the vertical force for this potential INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t - time OUTPUT: the vertical force HISTORY: """ if self.isNonAxi: Rt2= R**2.*(1.-self._1m1overb2*nu.sin(phi)**2.) return -z/self._q**2./(Rt2+(z/self._q)**2.+self._core2) else: return -z/self._q**2./(R**2.+(z/self._q)**2.+self._core2) def _phiforce(self,R,z,phi=0.,t=0.): """ NAME: _phiforce PURPOSE: evaluate the azimuthal force for this potential INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t - time OUTPUT: the azimuthal force HISTORY: """ if self.isNonAxi: Rt2= R**2.*(1.-self._1m1overb2*nu.sin(phi)**2.) return R**2./(Rt2+(z/self._q)**2.+self._core2)\ *nu.sin(2.*phi)*self._1m1overb2/2. else: return 0 def _dens(self,R,z,phi=0.,t=0.): """ NAME: _dens PURPOSE: evaluate the density for this potential INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t - time OUTPUT: the density HISTORY: """ if self.isNonAxi: R2= R**2. Rt2= R2*(1.-self._1m1overb2*nu.sin(phi)**2.) denom= 1./(Rt2+(z/self._q)**2.+self._core2) denom2= denom**2. return 1./4./nu.pi\ *(2.*Rt2/R2*(denom-Rt2*denom2)\ +denom/self._q**2.-2.*z**2.*denom**2./self._q**4.\ -self._1m1overb2\ *(2.*R2*nu.sin(2.*phi)**2./4.*self._1m1overb2\ *denom**2.+denom*nu.cos(2.*phi))) else: return 1./4./nu.pi/self._q**2.*((2.*self._q**2.+1.)*self._core2+R**2.\ +(2.-self._q**-2.)*z**2.)/\ (R**2.+(z/self._q)**2.+self._core2)**2. def _R2deriv(self,R,z,phi=0.,t=0.): """ NAME: _R2deriv PURPOSE: evaluate the second radial derivative for this potential INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t - time OUTPUT: the second radial derivative HISTORY: 2011-10-09 - Written - Bovy (IAS) """ if self.isNonAxi: Rt2= R**2.*(1.-self._1m1overb2*nu.sin(phi)**2.) denom= 1./(Rt2+(z/self._q)**2.+self._core2) return (denom-2.*Rt2*denom**2.)*Rt2/R**2. else: denom= 1./(R**2.+(z/self._q)**2.+self._core2) return denom-2.*R**2.*denom**2. def _z2deriv(self,R,z,phi=0.,t=0.): """ NAME: _z2deriv PURPOSE: evaluate the second vertical derivative for this potential INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t - time OUTPUT: the second vertical derivative HISTORY: 2012-07-25 - Written - Bovy (IAS@MPIA) """ if self.isNonAxi: Rt2= R**2.*(1.-self._1m1overb2*nu.sin(phi)**2.) denom= 1./(Rt2+(z/self._q)**2.+self._core2) return denom/self._q**2.-2.*z**2.*denom**2./self._q**4. else: denom= 1./(R**2.+(z/self._q)**2.+self._core2) return denom/self._q**2.-2.*z**2.*denom**2./self._q**4. def _Rzderiv(self,R,z,phi=0.,t=0.): """ NAME: _Rzderiv PURPOSE: evaluate the mixed R,z derivative for this potential INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t - time OUTPUT: d2Phi/dR/dz HISTORY: 2013-08-28 - Written - Bovy (IAS) """ if self.isNonAxi: Rt2= R**2.*(1.-self._1m1overb2*nu.sin(phi)**2.) return -2.*Rt2/R*z/self._q**2./(Rt2+(z/self._q)**2.+self._core2)**2. else: return -2.*R*z/self._q**2./(R**2.+(z/self._q)**2.+self._core2)**2. def _phi2deriv(self,R,z,phi=0.,t=0.): """ NAME: _phi2deriv PURPOSE: evaluate the second azimuthal derivative for this potential INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t - time OUTPUT: the second azimuthal derivative HISTORY: 2017-10-15 - Written - Bovy (UofT) """ if self.isNonAxi: Rt2= R**2.*(1.-self._1m1overb2*nu.sin(phi)**2.) denom= 1./(Rt2+(z/self._q)**2.+self._core2) return -self._1m1overb2\ *(R**4.*nu.sin(2.*phi)**2./2.*self._1m1overb2\ *denom**2. +R**2.*denom*nu.cos(2.*phi)) else: return 0. def _Rphideriv(self,R,z,phi=0.,t=0.): """ NAME: _Rphideriv PURPOSE: evaluate the mixed R,phi derivative for this potential INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t - time OUTPUT: d2Phi/dR/dphi HISTORY: 2017-10-15 - Written - Bovy (UofT) """ if self.isNonAxi: Rt2= R**2.*(1.-self._1m1overb2*nu.sin(phi)**2.) denom= 1./(Rt2+(z/self._q)**2.+self._core2) return -(denom-Rt2*denom**2.)*R*nu.sin(2.*phi)*self._1m1overb2 else: return 0. @kms_to_kpcGyrDecorator def _nemo_accpars(self,vo,ro): """ NAME: _nemo_accpars PURPOSE: return the accpars potential parameters for use of this potential with NEMO INPUT: vo - velocity unit in km/s ro - length unit in kpc OUTPUT: accpars string HISTORY: 2014-12-18 - Written - Bovy (IAS) """ warnings.warn("NEMO's LogPot does not allow flattening in z (for some reason); therefore, flip y and z in NEMO wrt galpy; also does not allow the triaxial b parameter",galpyWarning) ampl= self._amp*vo**2. return "0,%s,%s,1.0,%s" % (ampl, self._core2*ro**2.*self._q**(2./3.), #somewhat weird gyrfalcon implementation self._q) galpy-1.3.0/galpy/potential_src/MiyamotoNagaiPotential.py0000644000076500000240000001705113215750630023711 0ustar bovystaff00000000000000############################################################################### # MiyamotoNagaiPotential.py: class that implements the Miyamoto-Nagai # potential # GM # phi(R,z) = - --------------------------------- # \sqrt(R^2+(a+\sqrt(z^2+b^2))^2) ############################################################################### import numpy as nu from galpy.potential_src.Potential import Potential, kms_to_kpcGyrDecorator, \ _APY_LOADED if _APY_LOADED: from astropy import units class MiyamotoNagaiPotential(Potential): """Class that implements the Miyamoto-Nagai potential .. math:: \\Phi(R,z) = -\\frac{\\mathrm{amp}}{\\sqrt{R^2+(a+\\sqrt{z^2+b^2})^2}} """ def __init__(self,amp=1.,a=1.,b=0.1,normalize=False, ro=None,vo=None): """ NAME: __init__ PURPOSE: initialize a Miyamoto-Nagai potential INPUT: amp - amplitude to be applied to the potential (default: 1); can be a Quantity with units of mass or Gxmass a - scale length (can be Quantity) b - scale height (can be Quantity) normalize - if True, normalize such that vc(1.,0.)=1., or, if given as a number, such that the force is this fraction of the force necessary to make vc(1.,0.)=1. ro=, vo= distance and velocity scales for translation into internal units (default from configuration file) OUTPUT: (none) HISTORY: 2010-07-09 - Started - Bovy (NYU) """ Potential.__init__(self,amp=amp,ro=ro,vo=vo,amp_units='mass') if _APY_LOADED and isinstance(a,units.Quantity): a= a.to(units.kpc).value/self._ro if _APY_LOADED and isinstance(b,units.Quantity): b= b.to(units.kpc).value/self._ro self._a= a self._scale= self._a self._b= b self._b2= self._b**2. if normalize or \ (isinstance(normalize,(int,float)) \ and not isinstance(normalize,bool)): self.normalize(normalize) self.hasC= True self.hasC_dxdv= True self._nemo_accname= 'MiyamotoNagai' def _evaluate(self,R,z,phi=0.,t=0.): """ NAME: _evaluate PURPOSE: evaluate the potential at R,z INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t - time OUTPUT: Phi(R,z) HISTORY: 2010-07-09 - Started - Bovy (NYU) """ return -1./nu.sqrt(R**2.+(self._a+nu.sqrt(z**2.+self._b2))**2.) def _Rforce(self,R,z,phi=0.,t=0.): """ NAME: _Rforce PURPOSE: evaluate the radial force for this potential INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t - time OUTPUT: the radial force HISTORY: 2010-07-09 - Written - Bovy (NYU) """ return -R/(R**2.+(self._a+nu.sqrt(z**2.+self._b2))**2.)**(3./2.) def _zforce(self,R,z,phi=0.,t=0.): """ NAME: _zforce PURPOSE: evaluate the vertical force for this potential INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t - time OUTPUT: the vertical force HISTORY: 2010-07-09 - Written - Bovy (NYU) """ sqrtbz= nu.sqrt(self._b2+z**2.) asqrtbz= self._a+sqrtbz if isinstance(R,float) and sqrtbz == asqrtbz: return (-z/ (R**2.+(self._a+nu.sqrt(z**2.+self._b2))**2.)**(3./2.)) else: return (-z*asqrtbz/sqrtbz/ (R**2.+(self._a+nu.sqrt(z**2.+self._b2))**2.)**(3./2.)) def _dens(self,R,z,phi=0.,t=0.): """ NAME: _dens PURPOSE: evaluate the density for this potential INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t - time OUTPUT: the density HISTORY: 2010-08-08 - Written - Bovy (NYU) """ sqrtbz= nu.sqrt(self._b2+z**2.) asqrtbz= self._a+sqrtbz if isinstance(R,float) and sqrtbz == asqrtbz: return 3./\ (R**2.+sqrtbz**2.)**2.5/4./nu.pi*self._b2 else: return (self._a*R**2.+(self._a+3.*sqrtbz)*asqrtbz**2.)/\ (R**2.+asqrtbz**2.)**2.5/sqrtbz**3./4./nu.pi*self._b2 def _R2deriv(self,R,z,phi=0.,t=0.): """ NAME: _R2deriv PURPOSE: evaluate the second radial derivative for this potential INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t - time OUTPUT: the second radial derivative HISTORY: 2011-10-09 - Written - Bovy (IAS) """ return 1./(R**2.+(self._a+nu.sqrt(z**2.+self._b2))**2.)**1.5 \ -3.*R**2./(R**2.+(self._a+nu.sqrt(z**2.+self._b2))**2.)**2.5 def _z2deriv(self,R,z,phi=0.,t=0.): """ NAME: _z2deriv PURPOSE: evaluate the second vertical derivative for this potential INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t - time OUTPUT: the second vertical derivative HISTORY: 2012-07-25 - Written - Bovy (IAS@MPIA) """ sqrtbz= nu.sqrt(self._b2+z**2.) asqrtbz= self._a+sqrtbz if isinstance(R,float) and sqrtbz == asqrtbz: return (self._b2+R**2.-2.*z**2.)*(self._b2+R**2.+z**2.)**-2.5 else: return ((self._a**3.*self._b2 + self._a**2.*(3.*self._b2 - 2.* z**2.) *nu.sqrt(self._b2 + z**2.) + (self._b2 + R**2. - 2.*z**2.)*(self._b2 + z**2.)**1.5 +self._a* (3.*self._b2**2. - 4.*z**4. + self._b2*(R**2. - z**2.)))/ ((self._b2 + z**2.)**1.5* (R**2. + asqrtbz**2.)**2.5)) def _Rzderiv(self,R,z,phi=0.,t=0.): """ NAME: _Rzderiv PURPOSE: evaluate the mixed R,z derivative for this potential INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t - time OUTPUT: d2phi/dR/dz HISTORY: 2013-08-28 - Written - Bovy (IAS) """ sqrtbz= nu.sqrt(self._b2+z**2.) asqrtbz= self._a+sqrtbz if isinstance(R,float) and sqrtbz == asqrtbz: return -(3.*R*z/(R**2.+asqrtbz**2.)**2.5) else: return -(3.*R*z*asqrtbz /sqrtbz/(R**2.+asqrtbz**2.)**2.5) @kms_to_kpcGyrDecorator def _nemo_accpars(self,vo,ro): """ NAME: _nemo_accpars PURPOSE: return the accpars potential parameters for use of this potential with NEMO INPUT: vo - velocity unit in km/s ro - length unit in kpc OUTPUT: accpars string HISTORY: 2014-12-18 - Written - Bovy (IAS) """ ampl= self._amp*vo**2.*ro return "0,%s,%s,%s" % (ampl,self._a*ro,self._b*ro) galpy-1.3.0/galpy/potential_src/MN3ExponentialDiskPotential.py0000644000076500000240000002610713003761364024575 0ustar bovystaff00000000000000############################################################################### # MN3ExponentialDiskPotential.py: class that implements the three Miyamoto- # Nagai approximation to a radially # exponential disk potential of Smith et al. # 2015 ############################################################################### import numpy import warnings from galpy.util import galpyWarning from galpy.potential_src.Potential import Potential, kms_to_kpcGyrDecorator, \ _APY_LOADED if _APY_LOADED: from astropy import units from galpy.potential_src.MiyamotoNagaiPotential import MiyamotoNagaiPotential class MN3ExponentialDiskPotential(Potential): """class that implements the three Miyamoto-Nagai approximation to a radially-exponential disk potential of `Smith et al. 2015 `_ .. math:: \\rho(R,z) = \\mathrm{amp}\\,\\exp\\left(-R/h_R-|z|/h_z\\right) or .. math:: \\rho(R,z) = \\mathrm{amp}\\,\\exp\\left(-R/h_R\\right)\\mathrm{sech}^2\\left(-|z|/h_z\\right) depending on whether sech=True or not. This density is approximated using three Miyamoto-Nagai disks """ def __init__(self,amp=1.,hr=1./3.,hz=1./16., sech=False,posdens=False, normalize=False, ro=None,vo=None): """ NAME: __init__ PURPOSE: initialize a 3MN approximation to an exponential disk potential INPUT: amp - amplitude to be applied to the potential (default: 1); can be a Quantity with units of mass density or Gxmass density hr - disk scale-length (can be Quantity) hz - scale-height (can be Quantity) sech= (False) if True, hz is the scale height of a sech vertical profile (default is exponential vertical profile) posdens= (False) if True, allow only positive density solutions (Table 2 in Smith et al. rather than Table 1) normalize - if True, normalize such that vc(1.,0.)=1., or, if given as a number, such that the force is this fraction of the force necessary to make vc(1.,0.)=1. ro=, vo= distance and velocity scales for translation into internal units (default from configuration file) OUTPUT: MN3ExponentialDiskPotential object HISTORY: 2015-02-07 - Written - Bovy (IAS) """ Potential.__init__(self,amp=amp,ro=ro,vo=vo,amp_units='density') if _APY_LOADED and isinstance(hr,units.Quantity): hr= hr.to(units.kpc).value/self._ro if _APY_LOADED and isinstance(hz,units.Quantity): hz= hz.to(units.kpc).value/self._ro self._hr= hr self._hz= hz self._scale= self._hr # Adjust amp for definition self._amp*= 4.*numpy.pi*self._hr**2.*self._hz # First determine b/rd if sech: self._brd= _b_sechhz(self._hz/self._hr) else: self._brd= _b_exphz(self._hz/self._hr) if self._brd < 0.: raise IOError("MN3ExponentialDiskPotential's b/Rd is negative for the given hz") # Check range if (not posdens and self._brd > 3.) \ or (posdens and self._brd > 1.35): warnings.warn("MN3ExponentialDiskPotential's b/Rd = %g is outside of the interpolation range of Smith et al. (2015)" % self._brd, galpyWarning) self._b= self._brd*self._hr # Now setup the various MN disks if posdens: self._mn3= [MiyamotoNagaiPotential(amp=_mass1_tab2(self._brd), a=_a1_tab2(self._brd)*self._hr, b=self._b), MiyamotoNagaiPotential(amp=_mass2_tab2(self._brd), a=_a2_tab2(self._brd)*self._hr, b=self._b), MiyamotoNagaiPotential(amp=_mass3_tab2(self._brd), a=_a3_tab2(self._brd)*self._hr, b=self._b)] else: self._mn3= [MiyamotoNagaiPotential(amp=_mass1_tab1(self._brd), a=_a1_tab1(self._brd)*self._hr, b=self._b), MiyamotoNagaiPotential(amp=_mass2_tab1(self._brd), a=_a2_tab1(self._brd)*self._hr, b=self._b), MiyamotoNagaiPotential(amp=_mass3_tab1(self._brd), a=_a3_tab1(self._brd)*self._hr, b=self._b)] if normalize or \ (isinstance(normalize,(int,float)) \ and not isinstance(normalize,bool)): self.normalize(normalize) self.hasC= True self.hasC_dxdv= True self._nemo_accname= 'MiyamotoNagai+MiyamotoNagai+MiyamotoNagai' return None def _evaluate(self,R,z,phi=0.,t=0.): """ NAME: _evaluate PURPOSE: evaluate the potential at R,z INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t - time OUTPUT: Phi(R,z) HISTORY: 2015-02-07 - Written - Bovy (IAS) """ return self._mn3[0](R,z,phi=phi,t=t)\ +self._mn3[1](R,z,phi=phi,t=t)\ +self._mn3[2](R,z,phi=phi,t=t) def _Rforce(self,R,z,phi=0.,t=0.): """ NAME: _Rforce PURPOSE: evaluate the radial force for this potential INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t - time OUTPUT: the radial force HISTORY: 2015-02-07 - Written - Bovy (IAS) """ return self._mn3[0].Rforce(R,z,phi=phi,t=t)\ +self._mn3[1].Rforce(R,z,phi=phi,t=t)\ +self._mn3[2].Rforce(R,z,phi=phi,t=t) def _zforce(self,R,z,phi=0.,t=0.): """ NAME: _zforce PURPOSE: evaluate the vertical force for this potential INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t - time OUTPUT: the vertical force HISTORY: 2015-02-07 - Written - Bovy (IAS) """ return self._mn3[0].zforce(R,z,phi=phi,t=t)\ +self._mn3[1].zforce(R,z,phi=phi,t=t)\ +self._mn3[2].zforce(R,z,phi=phi,t=t) def _dens(self,R,z,phi=0.,t=0.): """ NAME: _dens PURPOSE: evaluate the density for this potential INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t - time OUTPUT: the density HISTORY: 2015-02-07 - Written - Bovy (IAS) """ return self._mn3[0].dens(R,z,phi=phi,t=t)\ +self._mn3[1].dens(R,z,phi=phi,t=t)\ +self._mn3[2].dens(R,z,phi=phi,t=t) def _R2deriv(self,R,z,phi=0.,t=0.): """ NAME: _R2deriv PURPOSE: evaluate the second radial derivative for this potential INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t - time OUTPUT: the second radial derivative HISTORY: 2015-02-07 - Written - Bovy (IAS) """ return self._mn3[0].R2deriv(R,z,phi=phi,t=t)\ +self._mn3[1].R2deriv(R,z,phi=phi,t=t)\ +self._mn3[2].R2deriv(R,z,phi=phi,t=t) def _z2deriv(self,R,z,phi=0.,t=0.): """ NAME: _z2deriv PURPOSE: evaluate the second vertical derivative for this potential INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t - time OUTPUT: the second vertical derivative HISTORY: 2015-02-07 - Written - Bovy (IAS) """ return self._mn3[0].z2deriv(R,z,phi=phi,t=t)\ +self._mn3[1].z2deriv(R,z,phi=phi,t=t)\ +self._mn3[2].z2deriv(R,z,phi=phi,t=t) def _Rzderiv(self,R,z,phi=0.,t=0.): """ NAME: _Rzderiv PURPOSE: evaluate the mixed R,z derivative for this potential INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t - time OUTPUT: d2phi/dR/dz HISTORY: 2015-02-07 - Written - Bovy (IAS) """ return self._mn3[0].Rzderiv(R,z,phi=phi,t=t)\ +self._mn3[1].Rzderiv(R,z,phi=phi,t=t)\ +self._mn3[2].Rzderiv(R,z,phi=phi,t=t) @kms_to_kpcGyrDecorator def _nemo_accpars(self,vo,ro): """ NAME: _nemo_accpars PURPOSE: return the accpars potential parameters for use of this potential with NEMO INPUT: vo - velocity unit in km/s ro - length unit in kpc OUTPUT: accpars string HISTORY: 2015-02-09 - Written - Bovy (IAS) """ out= "" # Loop through the self._mn3 MN potentials for ii in range(3): if ii > 0: out+= '#' ampl= self._amp*self._mn3[ii]._amp*vo**2.*ro out+= "0,%s,%s,%s" % (ampl,self._mn3[ii]._a*ro,self._mn3[ii]._b*ro) return out # Equations from Table 1 def _mass1_tab1(brd): return -0.0090*brd**4.+0.0640*brd**3.-0.1653*brd**2.+0.1164*brd+1.9487 def _mass2_tab1(brd): return 0.0173*brd**4.-0.0903*brd**3.+0.0877*brd**2.+0.2029*brd-1.3077 def _mass3_tab1(brd): return -0.0051*brd**4.+0.0287*brd**3.-0.0361*brd**2.-0.0544*brd+0.2242 def _a1_tab1(brd): return -0.0358*brd**4.+0.2610*brd**3.-0.6987*brd**2.-0.1193*brd+2.0074 def _a2_tab1(brd): return -0.0830*brd**4.+0.4992*brd**3.-0.7967*brd**2.-1.2966*brd+4.4441 def _a3_tab1(brd): return -0.0247*brd**4.+0.1718*brd**3.-0.4124*brd**2.-0.5944*brd+0.7333 # Equations from Table 2 def _mass1_tab2(brd): return 0.0036*brd**4.-0.0330*brd**3.+0.1117*brd**2.-0.1335*brd+0.1749 def _mass2_tab2(brd): return -0.0131*brd**4.+0.1090*brd**3.-0.3035*brd**2.+0.2921*brd-5.7976 def _mass3_tab2(brd): return -0.0048*brd**4.+0.0454*brd**3.-0.1425*brd**2.+0.1012*brd+6.7120 def _a1_tab2(brd): return -0.0158*brd**4.+0.0993*brd**3.-0.2070*brd**2.-0.7089*brd+0.6445 def _a2_tab2(brd): return -0.0319*brd**4.+0.1514*brd**3.-0.1279*brd**2.-0.9325*brd+2.6836 def _a3_tab2(brd): return -0.0326*brd**4.+0.1816*brd**3.-0.2943*brd**2.-0.6329*brd+2.3193 # Equations to go from hz to b def _b_exphz(hz): return -0.269*hz**3.+1.080*hz**2.+1.092*hz def _b_sechhz(hz): return -0.033*hz**3.+0.262*hz**2.+0.659*hz galpy-1.3.0/galpy/potential_src/MovingObjectPotential.py0000644000076500000240000001520013003761364023534 0ustar bovystaff00000000000000############################################################################### # MovingObjectPotential.py: class that implements the potential coming from # a moving object # GM # phi(R,z) = - --------------------------------- # distance ############################################################################### import copy import numpy as nu from galpy.potential_src.Potential import Potential, _APY_LOADED if _APY_LOADED: from astropy import units from galpy.potential_src.ForceSoftening import PlummerSoftening class MovingObjectPotential(Potential): """Class that implements the potential coming from a moving object .. math:: \\Phi(R,z,\\phi,t) = -\\mathrm{amp}\\,GM\\,S(d) where :math:`d` is the distance between :math:`(R,z,\\phi)` and the moving object at time :math:`t` and :math:`S(\\cdot)` is a softening kernel. In the case of Plummer softening, this kernel is .. math:: S(d) = \\frac{1}{\\sqrt{d^2+\\mathrm{softening\_length}^2}} Plummer is currently the only implemented softening. """ def __init__(self,orbit,amp=1.,GM=.06, ro=None,vo=None, softening=None, softening_model='plummer',softening_length=0.01): """ NAME: __init__ PURPOSE: initialize a MovingObjectPotential INPUT: orbit - the Orbit of the object (Orbit object) amp= - amplitude to be applied to the potential (default: 1); can be a Quantity with units of mass or Gxmass GM - 'mass' of the object (degenerate with amp, don't use both); can be a Quantity with units of mass or Gxmass Softening: either provide a) softening= with a ForceSoftening-type object b) softening_model= type of softening to use ('plummer') softening_length= (optional; can be Quantity) ro=, vo= distance and velocity scales for translation into internal units (default from configuration file) OUTPUT: (none) HISTORY: 2011-04-10 - Started - Bovy (NYU) """ Potential.__init__(self,amp=amp*GM,ro=ro,vo=vo,amp_units='mass') if _APY_LOADED and isinstance(softening_length,units.Quantity): softening_length= softening_length.to(units.kpc).value/self._ro # Make sure we aren't getting physical outputs self._orb= copy.deepcopy(orbit) self._orb.turn_physical_off() if softening is None: if softening_model.lower() == 'plummer': self._softening= PlummerSoftening(softening_length=softening_length) else: self._softening= softening self.isNonAxi= True return None def _evaluate(self,R,z,phi=0.,t=0.): """ NAME: _evaluate PURPOSE: evaluate the potential at R,z, phi INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t - time OUTPUT: Phi(R,z,phi) HISTORY: 2010104-10 - Started - Bovy (NYU) """ #Calculate distance dist= _cyldist(R,phi,z, self._orb.R(t),self._orb.phi(t),self._orb.z(t)) #Evaluate potential return -self._softening.potential(dist) def _Rforce(self,R,z,phi=0.,t=0.): """ NAME: _Rforce PURPOSE: evaluate the radial force for this potential INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t - time OUTPUT: the radial force HISTORY: 2011-04-10 - Written - Bovy (NYU) """ #Calculate distance and difference vector (xd,yd,zd,dist)= _cyldiffdist(self._orb.R(t),self._orb.phi(t), self._orb.z(t), R,phi,z) #Evaluate force return (nu.cos(phi)*xd+nu.sin(phi)*yd)/dist\ *self._softening(dist) def _zforce(self,R,z,phi=0.,t=0.): """ NAME: _zforce PURPOSE: evaluate the vertical force for this potential INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t - time OUTPUT: the vertical force HISTORY: 2011-04-10 - Written - Bovy (NYU) """ #Calculate distance and difference vector (xd,yd,zd,dist)= _cyldiffdist(self._orb.R(t),self._orb.phi(t), self._orb.z(t), R,phi,z) #Evaluate force return zd/dist*self._softening(dist) def _phiforce(self,R,z,phi=0.,t=0.): """ NAME: _phiforce PURPOSE: evaluate the azimuthal force for this potential INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t - time OUTPUT: the azimuthal force HISTORY: 2011-04-10 - Written - Bovy (NYU) """ #Calculate distance and difference vector (xd,yd,zd,dist)= _cyldiffdist(self._orb.R(t),self._orb.phi(t), self._orb.z(t), R,phi,z) #Evaluate force return R*(nu.cos(phi)*yd-nu.sin(phi)*xd)/dist\ *self._softening(dist) def _dens(self,R,z,phi=0.,t=0.): """ NAME: _dens PURPOSE: evaluate the density for this potential INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t - time OUTPUT: the density HISTORY: 2010-08-08 - Written - Bovy (NYU) """ dist= _cyldist(R,phi,z, self._orb.R(t),self._orb.phi(t),self._orb.z(t)) return self._softening.density(dist) def _cyldist(R1,phi1,z1,R2,phi2,z2): return nu.sqrt( (R1*nu.cos(phi1)-R2*nu.cos(phi2))**2. +(R1*nu.sin(phi1)-R2*nu.sin(phi2))**2. +(z1-z2)**2.) def _cyldiffdist(R1,phi1,z1,R2,phi2,z2): x= R1*nu.cos(phi1)-R2*nu.cos(phi2) y= R1*nu.sin(phi1)-R2*nu.sin(phi2) z= z1-z2 return (x,y,z,nu.sqrt(x**2.+y**2.+z**2.)) galpy-1.3.0/galpy/potential_src/planarPotential.py0000644000076500000240000010431213152311474022424 0ustar bovystaff00000000000000from __future__ import division, print_function import os import pickle import numpy as nu from scipy import integrate import galpy.util.bovy_plot as plot from galpy.util import config from galpy.util.bovy_conversion import physical_conversion,\ potential_physical_input, freq_in_Gyr from galpy.potential_src.Potential import Potential, PotentialError, lindbladR from galpy.potential_src.plotRotcurve import plotRotcurve from galpy.potential_src.plotEscapecurve import _INF, plotEscapecurve _APY_LOADED= True try: from astropy import units except ImportError: _APY_LOADED= False class planarPotential(object): """Class representing 2D (R,\phi) potentials""" def __init__(self,amp=1.,ro=None,vo=None): self._amp= amp self.dim= 2 self.isNonAxi= True #Gets reset by planarAxiPotential self.isRZ= False self.hasC= False self.hasC_dxdv= False # Parse ro and vo if ro is None: self._ro= config.__config__.getfloat('normalization','ro') self._roSet= False else: if _APY_LOADED and isinstance(ro,units.Quantity): ro= ro.to(units.kpc).value self._ro= ro self._roSet= True if vo is None: self._vo= config.__config__.getfloat('normalization','vo') self._voSet= False else: if _APY_LOADED and isinstance(vo,units.Quantity): vo= vo.to(units.km/units.s).value self._vo= vo self._voSet= True return None def turn_physical_off(self): """ NAME: turn_physical_off PURPOSE: turn off automatic returning of outputs in physical units INPUT: (none) OUTPUT: (none) HISTORY: 2016-01-30 - Written - Bovy (UofT) """ self._roSet= False self._voSet= False return None def turn_physical_on(self,ro=None,vo=None): """ NAME: turn_physical_on PURPOSE: turn on automatic returning of outputs in physical units INPUT: ro= reference distance (kpc; can be Quantity) vo= reference velocity (km/s; can be Quantity) OUTPUT: (none) HISTORY: 2016-01-30 - Written - Bovy (UofT) """ self._roSet= True self._voSet= True if not ro is None: if _APY_LOADED and isinstance(ro,units.Quantity): ro= ro.to(units.kpc).value self._ro= ro if not vo is None: if _APY_LOADED and isinstance(vo,units.Quantity): vo= vo.to(units.km/units.s).value self._vo= vo return None @potential_physical_input @physical_conversion('energy',pop=True) def __call__(self,R,phi=0.,t=0.,dR=0,dphi=0): """ NAME: __call__ PURPOSE: evaluate the potential INPUT: R - Cylindrica radius (can be Quantity) phi= azimuth (optional; can be Quantity) t= time (optional; can be Quantity) OUTPUT: Phi(R(,phi,t))) HISTORY: 2010-07-13 - Written - Bovy (NYU) """ return self._call_nodecorator(R,phi=phi,t=t,dR=dR,dphi=dphi) def _call_nodecorator(self,R,phi=0.,t=0.,dR=0,dphi=0): # Separate, so it can be used during orbit integration if dR == 0 and dphi == 0: try: return self._amp*self._evaluate(R,phi=phi,t=t) except AttributeError: #pragma: no cover raise PotentialError("'_evaluate' function not implemented for this potential") elif dR == 1 and dphi == 0: return -self.Rforce(R,phi=phi,t=t,use_physical=False) elif dR == 0 and dphi == 1: return -self.phiforce(R,phi=phi,t=t,use_physical=False) elif dR == 2 and dphi == 0: return self.R2deriv(R,phi=phi,t=t,use_physical=False) elif dR == 0 and dphi == 2: return self.phi2deriv(R,phi=phi,t=t,use_physical=False) elif dR == 1 and dphi == 1: return self.Rphideriv(R,phi=phi,t=t,use_physical=False) @potential_physical_input @physical_conversion('force',pop=True) def Rforce(self,R,phi=0.,t=0.): """ NAME: Rforce PURPOSE: evaluate the radial force INPUT: R - Cylindrical radius (can be Quantity) phi= azimuth (optional; can be Quantity) t= time (optional; can be Quantity) OUTPUT: F_R(R,(\phi,t))) HISTORY: 2010-07-13 - Written - Bovy (NYU) """ return self._Rforce_nodecorator(R,phi=phi,t=t) def _Rforce_nodecorator(self,R,phi=0.,t=0.): # Separate, so it can be used during orbit integration try: return self._amp*self._Rforce(R,phi=phi,t=t) except AttributeError: #pragma: no cover raise PotentialError("'_Rforce' function not implemented for this potential") @potential_physical_input @physical_conversion('force',pop=True) def phiforce(self,R,phi=0.,t=0.): """ NAME: phiforce PURPOSE: evaluate the phi force INPUT: R - Cylindrical radius (can be Quantity) phi= azimuth (optional; can be Quantity) t= time (optional; can be Quantity) OUTPUT: F_phi(R,(phi,t))) HISTORY: 2010-07-13 - Written - Bovy (NYU) """ return self._phiforce_nodecorator(R,phi=phi,t=t) def _phiforce_nodecorator(self,R,phi=0.,t=0.): # Separate, so it can be used during orbit integration try: return self._amp*self._phiforce(R,phi=phi,t=t) except AttributeError: #pragma: no cover raise PotentialError("'_phiforce' function not implemented for this potential") @potential_physical_input @physical_conversion('forcederivative',pop=True) def R2deriv(self,R,phi=0.,t=0.): """ NAME: R2deriv PURPOSE: evaluate the second radial derivative INPUT: R - Cylindrical radius (can be Quantity) phi= azimuth (optional; can be Quantity) t= time (optional; can be Quantity) OUTPUT: d2phi/dR2 HISTORY: 2011-10-09 - Written - Bovy (IAS) """ try: return self._amp*self._R2deriv(R,phi=phi,t=t) except AttributeError: #pragma: no cover raise PotentialError("'_R2deriv' function not implemented for this potential") @potential_physical_input @physical_conversion('forcederivative',pop=True) def phi2deriv(self,R,phi=0.,t=0.): """ NAME: phi2deriv PURPOSE: evaluate the second azimuthal derivative INPUT: R - Cylindrical radius (can be Quantity) phi= azimuth (optional; can be Quantity) t= time (optional; can be Quantity) OUTPUT: d2phi/daz2 HISTORY: 2014-04-06 - Written - Bovy (IAS) """ try: return self._amp*self._phi2deriv(R,phi=phi,t=t) except AttributeError: #pragma: no cover raise PotentialError("'_phi2deriv' function not implemented for this potential") @potential_physical_input @physical_conversion('forcederivative',pop=True) def Rphideriv(self,R,phi=0.,t=0.): """ NAME: Rphideriv PURPOSE: evaluate the mixed radial and azimuthal derivative INPUT: R - Cylindrical radius (can be Quantity) phi= azimuth (optional can be Quantity) t= time (optional; can be Quantity) OUTPUT: d2phi/dR d az HISTORY: 2014-05-21 - Written - Bovy (IAS) """ try: return self._amp*self._Rphideriv(R,phi=phi,t=t) except AttributeError: #pragma: no cover raise PotentialError("'_Rphideriv' function not implemented for this potential") def plot(self,*args,**kwargs): """ NAME: plot PURPOSE: plot the potential INPUT: Rrange - range (can be Quantity) grid - number of points to plot savefilename - save to or restore from this savefile (pickle) +bovy_plot(*args,**kwargs) OUTPUT: plot to output device HISTORY: 2010-07-13 - Written - Bovy (NYU) """ return plotplanarPotentials(self,*args,**kwargs) class planarAxiPotential(planarPotential): """Class representing axisymmetric planar potentials""" def __init__(self,amp=1.,ro=None,vo=None): planarPotential.__init__(self,amp=amp,ro=ro,vo=vo) self.isNonAxi= False return None def _phiforce(self,R,phi=0.,t=0.): return 0. def _phi2deriv(self,R,phi=0.,t=0.): #pragma: no cover """ NAME: _phi2deriv PURPOSE: evaluate the second azimuthal derivative for this potential INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t - time OUTPUT: the second azimuthal derivative HISTORY: 2011-10-17 - Written - Bovy (IAS) """ return 0. def _Rphideriv(self,R,phi=0.,t=0.): #pragma: no cover """ NAME: _Rphideriv PURPOSE: evaluate the radial+azimuthal derivative for this potential INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t - time OUTPUT: the radial+azimuthal derivative HISTORY: 2011-10-17 - Written - Bovy (IAS) """ return 0. @potential_physical_input @physical_conversion('velocity',pop=True) def vcirc(self,R,phi=None): """ NAME: vcirc PURPOSE: calculate the circular velocity at R in potential Pot INPUT: Pot - Potential instance or list of such instances R - Galactocentric radius (can be Quantity) phi= (None) azimuth to use for non-axisymmetric potentials OUTPUT: circular rotation velocity HISTORY: 2011-10-09 - Written - Bovy (IAS) 2016-06-15 - Added phi= keyword for non-axisymmetric potential - Bovy (UofT) """ return nu.sqrt(R*-self.Rforce(R,phi=phi,use_physical=False)) @potential_physical_input @physical_conversion('frequency',pop=True) def omegac(self,R): """ NAME: omegac PURPOSE: calculate the circular angular speed at R in potential Pot INPUT: Pot - Potential instance or list of such instances R - Galactocentric radius (can be Quantity) OUTPUT: circular angular speed HISTORY: 2011-10-09 - Written - Bovy (IAS) """ return nu.sqrt(-self.Rforce(R,use_physical=False)/R) @potential_physical_input @physical_conversion('frequency',pop=True) def epifreq(self,R): """ NAME: epifreq PURPOSE: calculate the epicycle frequency at R in this potential INPUT: R - Galactocentric radius (can be Quantity) OUTPUT: epicycle frequency HISTORY: 2011-10-09 - Written - Bovy (IAS) """ return nu.sqrt(self.R2deriv(R,use_physical=False) -3./R*self.Rforce(R,use_physical=False)) @physical_conversion('position',pop=True) def lindbladR(self,OmegaP,m=2,**kwargs): """ NAME: lindbladR PURPOSE: calculate the radius of a Lindblad resonance INPUT: OmegaP - pattern speed (can be Quantity) m= order of the resonance (as in m(O-Op)=kappa (negative m for outer) use m='corotation' for corotation +scipy.optimize.brentq xtol,rtol,maxiter kwargs OUTPUT: radius of Linblad resonance, None if there is no resonance HISTORY: 2011-10-09 - Written - Bovy (IAS) """ if _APY_LOADED and isinstance(OmegaP,units.Quantity): OmegaP= OmegaP.to(1/units.Gyr).value/freq_in_Gyr(self._vo,self._ro) return lindbladR(self,OmegaP,m=m,use_physical=False,**kwargs) @potential_physical_input @physical_conversion('velocity',pop=True) def vesc(self,R): """ NAME: vesc PURPOSE: calculate the escape velocity at R for potential Pot INPUT: Pot - Potential instances or list thereof R - Galactocentric radius (can be Quantity) OUTPUT: escape velocity HISTORY: 2011-10-09 - Written - Bovy (IAS) """ return nu.sqrt(2.*(self(_INF,use_physical=False) -self(R,use_physical=False))) def plotRotcurve(self,*args,**kwargs): """ NAME: plotRotcurve PURPOSE: plot the rotation curve for this potential INPUT: Rrange - range (can be Quantity) grid - number of points to plot savefilename - save to or restore from this savefile (pickle) +bovy_plot(*args,**kwargs) OUTPUT: plot to output device HISTORY: 2010-07-13 - Written - Bovy (NYU) """ return plotRotcurve(self,*args,**kwargs) def plotEscapecurve(self,*args,**kwargs): """ NAME: plotEscapecurve PURPOSE: plot the escape velocity curve for this potential INPUT: Rrange - range (can be Quantity) grid - number of points to plot savefilename - save to or restore from this savefile (pickle) +bovy_plot(*args,**kwargs) OUTPUT: plot to output device HISTORY: 2010-07-13 - Written - Bovy (NYU) """ return plotEscapecurve(self,*args,**kwargs) class planarPotentialFromRZPotential(planarAxiPotential): """Class that represents an axisymmetic planar potential derived from a RZPotential""" def __init__(self,RZPot): """ NAME: __init__ PURPOSE: Initialize INPUT: RZPot - RZPotential instance OUTPUT: planarAxiPotential instance HISTORY: 2010-07-13 - Written - Bovy (NYU) """ planarAxiPotential.__init__(self,amp=1.,ro=RZPot._ro,vo=RZPot._vo) # Also transfer roSet and voSet self._roSet= RZPot._roSet self._voSet= RZPot._voSet self._Pot= RZPot self.hasC= RZPot.hasC self.hasC_dxdv= RZPot.hasC_dxdv return None def _evaluate(self,R,phi=0.,t=0.): """ NAME: _evaluate PURPOSE: evaluate the potential INPUT: R phi t OUTPUT: Pot(R(,\phi,t)) HISTORY: 2010-07-13 - Written - Bovy (NYU) """ return self._Pot(R,0.,t=t,use_physical=False) def _Rforce(self,R,phi=0.,t=0.): """ NAME: _Rforce PURPOSE: evaluate the radial force INPUT: R phi t OUTPUT: F_R(R(,\phi,t)) HISTORY: 2010-07-13 - Written - Bovy (NYU) """ return self._Pot.Rforce(R,0.,t=t,use_physical=False) def _R2deriv(self,R,phi=0.,t=0.): """ NAME: _R2deriv PURPOSE: evaluate the second radial derivative INPUT: R phi t OUTPUT: d2phi/dR2 HISTORY: 2011-10-09 - Written - Bovy (IAS) """ return self._Pot.R2deriv(R,0.,t=t,use_physical=False) def RZToplanarPotential(RZPot): """ NAME: RZToplanarPotential PURPOSE: convert an RZPotential to a planarPotential in the mid-plane (z=0) INPUT: RZPot - RZPotential instance or list of such instances (existing planarPotential instances are just copied to the output) OUTPUT: planarPotential instance(s) HISTORY: 2010-07-13 - Written - Bovy (NYU) """ if isinstance(RZPot,list): out= [] for pot in RZPot: if isinstance(pot,planarPotential): out.append(pot) else: out.append(planarPotentialFromRZPotential(pot)) return out elif isinstance(RZPot,Potential): return planarPotentialFromRZPotential(RZPot) elif isinstance(RZPot,planarPotential): return RZPot else: raise PotentialError("Input to 'RZToplanarPotential' is neither an RZPotential-instance or a list of such instances") class planarPotentialFromFullPotential(planarPotential): """Class that represents a planar potential derived from a non-axisymmetric 3D potential""" def __init__(self,Pot): """ NAME: __init__ PURPOSE: Initialize INPUT: Pot - Potential instance OUTPUT: planarPotential instance HISTORY: 2016-06-02 - Written - Bovy (UofT) """ planarPotential.__init__(self,amp=1.,ro=Pot._ro,vo=Pot._vo) # Also transfer roSet and voSet self._roSet= Pot._roSet self._voSet= Pot._voSet self._Pot= Pot self.hasC= Pot.hasC self.hasC_dxdv= Pot.hasC_dxdv return None def _evaluate(self,R,phi=0.,t=0.): """ NAME: _evaluate PURPOSE: evaluate the potential INPUT: R phi t OUTPUT: Pot(R(,\phi,t)) HISTORY: 2016-06-02 - Written - Bovy (UofT) """ return self._Pot(R,0.,phi=phi,t=t,use_physical=False) def _Rforce(self,R,phi=0.,t=0.): """ NAME: _Rforce PURPOSE: evaluate the radial force INPUT: R phi t OUTPUT: F_R(R(,\phi,t)) HISTORY: 2016-06-02 - Written - Bovy (UofT) """ return self._Pot.Rforce(R,0.,phi=phi,t=t,use_physical=False) def _phiforce(self,R,phi=0.,t=0.): """ NAME: _phiforce PURPOSE: evaluate the azimuthal force INPUT: R phi t OUTPUT: F_phi(R(,\phi,t)) HISTORY: 2016-06-02 - Written - Bovy (UofT) """ return self._Pot.phiforce(R,0.,phi=phi,t=t,use_physical=False) def _R2deriv(self,R,phi=0.,t=0.): """ NAME: _R2deriv PURPOSE: evaluate the second radial derivative INPUT: R phi t OUTPUT: d2phi/dR2 HISTORY: 2016-06-02 - Written - Bovy (UofT) """ return self._Pot.R2deriv(R,0.,phi=phi,t=t,use_physical=False) def _phi2deriv(self,R,phi=0.,t=0.): """ NAME: _phi2deriv PURPOSE: evaluate the second azimuthal derivative INPUT: R phi t OUTPUT: d2phi/dphi2 HISTORY: 2016-06-02 - Written - Bovy (UofT) """ return self._Pot.phi2deriv(R,0.,phi=phi,t=t,use_physical=False) def _Rphideriv(self,R,phi=0.,t=0.): """ NAME: _Rphideriv PURPOSE: evaluate the mixed radial-azimuthal derivative INPUT: R phi t OUTPUT: d2phi/dRdphi HISTORY: 2016-06-02 - Written - Bovy (UofT) """ return self._Pot.Rphideriv(R,0.,phi=phi,t=t,use_physical=False) def OmegaP(self): """ NAME: OmegaP PURPOSE: return the pattern speed INPUT: (none) OUTPUT: pattern speed HISTORY: 2016-05-31 - Written - Bovy (UofT) """ return self._Pot.OmegaP() def toPlanarPotential(Pot): """ NAME: toPlanarPotential PURPOSE: convert an Potential to a planarPotential in the mid-plane (z=0) INPUT: Pot - Potential instance or list of such instances (existing planarPotential instances are just copied to the output) OUTPUT: planarPotential instance(s) HISTORY: 2016-06-11 - Written - Bovy (UofT) """ if isinstance(Pot,list): out= [] for pot in Pot: if isinstance(pot,planarPotential): out.append(pot) elif pot.isNonAxi: out.append(planarPotentialFromFullPotential(pot)) else: out.append(planarPotentialFromRZPotential(pot)) return out elif isinstance(Pot,Potential) and Pot.isNonAxi: return planarPotentialFromFullPotential(Pot) elif isinstance(Pot,Potential): return planarPotentialFromRZPotential(Pot) elif isinstance(Pot,planarPotential): return Pot else: raise PotentialError("Input to 'toPlanarPotential' is neither an Potential-instance or a list of such instances") @potential_physical_input @physical_conversion('energy',pop=True) def evaluateplanarPotentials(Pot,R,phi=None,t=0.,dR=0,dphi=0): """ NAME: evaluateplanarPotentials PURPOSE: evaluate a (list of) planarPotential instance(s) INPUT: Pot - (list of) planarPotential instance(s) R - Cylindrical radius (can be Quantity) phi= azimuth (optional; can be Quantity) t= time (optional; can be Quantity) dR=, dphi= if set to non-zero integers, return the dR,dphi't derivative instead OUTPUT: Phi(R(,phi,t)) HISTORY: 2010-07-13 - Written - Bovy (NYU) """ return _evaluateplanarPotentials(Pot,R,phi=phi,t=t,dR=dR,dphi=dphi) def _evaluateplanarPotentials(Pot,R,phi=None,t=0.,dR=0,dphi=0): from galpy.potential_src.Potential import _isNonAxi isList= isinstance(Pot,list) nonAxi= _isNonAxi(Pot) if nonAxi and phi is None: raise PotentialError("The (list of) planarPotential instances is non-axisymmetric, but you did not provide phi") if isList and nu.all([isinstance(p,planarPotential) for p in Pot]): sum= 0. for pot in Pot: if nonAxi: sum+= pot._call_nodecorator(R,phi=phi,t=t,dR=dR,dphi=dphi) else: sum+= pot._call_nodecorator(R,t=t,dR=dR,dphi=dphi) return sum elif isinstance(Pot,planarPotential): if nonAxi: return Pot._call_nodecorator(R,phi=phi,t=t,dR=dR,dphi=dphi) else: return Pot._call_nodecorator(R,t=t,dR=dR,dphi=dphi) else: #pragma: no cover raise PotentialError("Input to 'evaluatePotentials' is neither a Potential-instance or a list of such instances") @potential_physical_input @physical_conversion('force',pop=True) def evaluateplanarRforces(Pot,R,phi=None,t=0.): """ NAME: evaluateplanarRforces PURPOSE: evaluate the Rforce of a (list of) planarPotential instance(s) INPUT: Pot - (list of) planarPotential instance(s) R - Cylindrical radius (can be Quantity) phi= azimuth (optional can be Quantity) t= time (optional; can be Quantity) OUTPUT: F_R(R(,phi,t)) HISTORY: 2010-07-13 - Written - Bovy (NYU) """ return _evaluateplanarRforces(Pot,R,phi=phi,t=t) def _evaluateplanarRforces(Pot,R,phi=None,t=0.): """Raw, undecorated function for internal use""" from galpy.potential_src.Potential import _isNonAxi isList= isinstance(Pot,list) nonAxi= _isNonAxi(Pot) if nonAxi and phi is None: raise PotentialError("The (list of) planarPotential instances is non-axisymmetric, but you did not provide phi") if isinstance(Pot,list) \ and nu.all([isinstance(p,planarPotential) for p in Pot]): sum= 0. for pot in Pot: if nonAxi: sum+= pot._Rforce_nodecorator(R,phi=phi,t=t) else: sum+= pot._Rforce_nodecorator(R,t=t) return sum elif isinstance(Pot,planarPotential): if nonAxi: return Pot._Rforce_nodecorator(R,phi=phi,t=t) else: return Pot._Rforce_nodecorator(R,t=t) else: #pragma: no cover raise PotentialError("Input to 'evaluatePotentials' is neither a Potential-instance or a list of such instances") @potential_physical_input @physical_conversion('force',pop=True) def evaluateplanarphiforces(Pot,R,phi=None,t=0.): """ NAME: evaluateplanarphiforces PURPOSE: evaluate the phiforce of a (list of) planarPotential instance(s) INPUT: Pot - (list of) planarPotential instance(s) R - Cylindrical radius (can be Quantity) phi= azimuth (optional; can be Quantity) t= time (optional; can be Quantity) OUTPUT: F_phi(R(,phi,t)) HISTORY: 2010-07-13 - Written - Bovy (NYU) """ return _evaluateplanarphiforces(Pot,R,phi=phi,t=t) def _evaluateplanarphiforces(Pot,R,phi=None,t=0.): from galpy.potential_src.Potential import _isNonAxi isList= isinstance(Pot,list) nonAxi= _isNonAxi(Pot) if nonAxi and phi is None: raise PotentialError("The (list of) planarPotential instances is non-axisymmetric, but you did not provide phi") if isinstance(Pot,list) \ and nu.all([isinstance(p,planarPotential) for p in Pot]): sum= 0. for pot in Pot: if nonAxi: sum+= pot._phiforce_nodecorator(R,phi=phi,t=t) else: sum+= pot._phiforce_nodecorator(R,t=t) return sum elif isinstance(Pot,planarPotential): if nonAxi: return Pot._phiforce_nodecorator(R,phi=phi,t=t) else: return Pot._phiforce_nodecorator(R,t=t) else: #pragma: no cover raise PotentialError("Input to 'evaluatePotentials' is neither a Potential-instance or a list of such instances") @potential_physical_input @physical_conversion('forcederivative',pop=True) def evaluateplanarR2derivs(Pot,R,phi=None,t=0.): """ NAME: evaluateplanarR2derivs PURPOSE: evaluate the second radial derivative of a (list of) planarPotential instance(s) INPUT: Pot - (list of) planarPotential instance(s) R - Cylindrical radius (can be Quantity) phi= azimuth (optional; can be Quantity) t= time (optional; can be Quantity) OUTPUT: F_R(R(,phi,t)) HISTORY: 2010-10-09 - Written - Bovy (IAS) """ from galpy.potential_src.Potential import _isNonAxi isList= isinstance(Pot,list) nonAxi= _isNonAxi(Pot) if nonAxi and phi is None: raise PotentialError("The (list of) planarPotential instances is non-axisymmetric, but you did not provide phi") if isinstance(Pot,list) \ and nu.all([isinstance(p,planarPotential) for p in Pot]): sum= 0. for pot in Pot: if nonAxi: sum+= pot.R2deriv(R,phi=phi,t=t,use_physical=False) else: sum+= pot.R2deriv(R,t=t,use_physical=False) return sum elif isinstance(Pot,planarPotential): if nonAxi: return Pot.R2deriv(R,phi=phi,t=t,use_physical=False) else: return Pot.R2deriv(R,t=t,use_physical=False) else: #pragma: no cover raise PotentialError("Input to 'evaluatePotentials' is neither a Potential-instance or a list of such instances") def LinShuReductionFactor(axiPot,R,sigmar,nonaxiPot=None, k=None,m=None,OmegaP=None): """ NAME: LinShuReductionFactor PURPOSE: Calculate the Lin & Shu (1966) reduction factor: the reduced linear response of a kinematically-warm stellar disk to a perturbation INPUT: axiPot - The background, axisymmetric potential R - Cylindrical radius (can be Quantity) sigmar - radial velocity dispersion of the population (can be Quantity) Then either provide: 1) m= m in the perturbation's m x phi (number of arms for a spiral) k= wavenumber (see Binney & Tremaine 2008) OmegaP= pattern speed (can be Quantity) 2) nonaxiPot= a non-axisymmetric Potential instance (such as SteadyLogSpiralPotential) that has functions that return OmegaP, m, and wavenumber OUTPUT: reduction factor HISTORY: 2014-08-23 - Written - Bovy (IAS) """ from galpy.potential import omegac, epifreq if nonaxiPot is None and (OmegaP is None or k is None or m is None): raise IOError("Need to specify either nonaxiPot= or m=, k=, OmegaP= for LinShuReductionFactor") elif not nonaxiPot is None: OmegaP= nonaxiPot.OmegaP() k= nonaxiPot.wavenumber(R) m= nonaxiPot.m() tepif= epifreq(axiPot,R) s= m*(OmegaP-omegac(axiPot,R))/tepif chi= sigmar**2.*k**2./tepif**2. return (1.-s**2.)/nu.sin(nu.pi*s)\ *integrate.quad(lambda t: nu.exp(-chi*(1.+nu.cos(t)))\ *nu.sin(s*t)*nu.sin(t), 0.,nu.pi)[0] def plotplanarPotentials(Pot,*args,**kwargs): """ NAME: plotplanarPotentials PURPOSE: plot a planar potential INPUT: Rrange - range (can be Quantity) xrange, yrange - if relevant (can be Quantity) grid, gridx, gridy - number of points to plot savefilename - save to or restore from this savefile (pickle) ncontours - number of contours to plot (if applicable) +bovy_plot(*args,**kwargs) or bovy_dens2d(**kwargs) OUTPUT: plot to output device HISTORY: 2010-07-13 - Written - Bovy (NYU) """ Rrange= kwargs.pop('Rrange',[0.01,5.]) xrange= kwargs.pop('xrange',[-5.,5.]) yrange= kwargs.pop('yrange',[-5.,5.]) if _APY_LOADED: if hasattr(Pot,'_ro'): tro= Pot._ro else: tro= Pot[0]._ro if isinstance(Rrange[0],units.Quantity): Rrange[0]= Rrange[0].to(units.kpc).value/tro if isinstance(Rrange[1],units.Quantity): Rrange[1]= Rrange[1].to(units.kpc).value/tro if isinstance(xrange[0],units.Quantity): xrange[0]= xrange[0].to(units.kpc).value/tro if isinstance(xrange[1],units.Quantity): xrange[1]= xrange[1].to(units.kpc).value/tro if isinstance(yrange[0],units.Quantity): yrange[0]= yrange[0].to(units.kpc).value/tro if isinstance(yrange[1],units.Quantity): yrange[1]= yrange[1].to(units.kpc).value/tro grid= kwargs.pop('grid',100) gridx= kwargs.pop('gridx',100) gridy= kwargs.pop('gridy',gridx) savefilename= kwargs.pop('savefilename',None) isList= isinstance(Pot,list) nonAxi= ((isList and Pot[0].isNonAxi) or (not isList and Pot.isNonAxi)) if not savefilename is None and os.path.exists(savefilename): print("Restoring savefile "+savefilename+" ...") savefile= open(savefilename,'rb') potR= pickle.load(savefile) if nonAxi: xs= pickle.load(savefile) ys= pickle.load(savefile) else: Rs= pickle.load(savefile) savefile.close() else: if nonAxi: xs= nu.linspace(xrange[0],xrange[1],gridx) ys= nu.linspace(yrange[0],yrange[1],gridy) potR= nu.zeros((gridx,gridy)) for ii in range(gridx): for jj in range(gridy): thisR= nu.sqrt(xs[ii]**2.+ys[jj]**2.) if xs[ii] >= 0.: thisphi= nu.arcsin(ys[jj]/thisR) else: thisphi= -nu.arcsin(ys[jj]/thisR)+nu.pi potR[ii,jj]= evaluateplanarPotentials(Pot,thisR, phi=thisphi, use_physical=False) else: Rs= nu.linspace(Rrange[0],Rrange[1],grid) potR= nu.zeros(grid) for ii in range(grid): potR[ii]= evaluateplanarPotentials(Pot,Rs[ii], use_physical=False) if not savefilename is None: print("Writing planar savefile "+savefilename+" ...") savefile= open(savefilename,'wb') pickle.dump(potR,savefile) if nonAxi: pickle.dump(xs,savefile) pickle.dump(ys,savefile) else: pickle.dump(Rs,savefile) savefile.close() if nonAxi: if not 'orogin' in kwargs: kwargs['origin']= 'lower' if not 'cmap' in kwargs: kwargs['cmap']= 'gist_yarg' if not 'contours' in kwargs: kwargs['contours']= True if not 'xlabel' in kwargs: kwargs['xlabel']= r"$x / R_0$" if not 'ylabel' in kwargs: kwargs['ylabel']= "$y / R_0$" if not 'aspect' in kwargs: kwargs['aspect']= 1. if not 'cntrls' in kwargs: kwargs['cntrls']= '-' ncontours= kwargs.pop('ncontours',10) if not 'levels' in kwargs: kwargs['levels']= nu.linspace(nu.nanmin(potR),nu.nanmax(potR),ncontours) return plot.bovy_dens2d(potR.T, xrange=xrange, yrange=yrange,**kwargs) else: kwargs['xlabel']=r"$R/R_0$" kwargs['ylabel']=r"$\Phi(R)$" kwargs['xrange']=Rrange return plot.bovy_plot(Rs,potR,*args,**kwargs) galpy-1.3.0/galpy/potential_src/plotEscapecurve.py0000644000076500000240000001245413125613630022437 0ustar bovystaff00000000000000from __future__ import division, print_function import os import pickle import numpy as nu import galpy.util.bovy_plot as plot from galpy.util.bovy_conversion import physical_conversion,\ potential_physical_input _APY_LOADED= True try: from astropy import units except ImportError: _APY_LOADED= False _INF= 10**12. def plotEscapecurve(Pot,*args,**kwargs): """ NAME: plotEscapecurve PURPOSE: plot the escape velocity curve for this potential (in the z=0 plane for non-spherical potentials) INPUT: Pot - Potential or list of Potential instances Rrange= Range in R to consider (can be Quantity) grid= grid in R savefilename= save to or restore from this savefile (pickle) +bovy_plot.bovy_plot args and kwargs OUTPUT: plot to output device HISTORY: 2010-08-08 - Written - Bovy (NYU) """ # Using physical units or not? if isinstance(Pot,list): potro= Pot[0]._ro roSet= Pot[0]._roSet potvo= Pot[0]._vo voSet= Pot[0]._voSet else: potro= Pot._ro roSet= Pot._roSet potvo= Pot._vo voSet= Pot._voSet if (kwargs.get('use_physical',False) \ and kwargs.get('ro',roSet) and kwargs.get('vo',voSet)) or \ (not 'use_physical' in kwargs \ and kwargs.get('ro',roSet) and kwargs.get('vo',voSet)): use_physical= True potro= kwargs.get('ro',potro) potvo= kwargs.get('vo',potvo) xlabel= r'$R\,(\mathrm{kpc})$' ylabel= r"$v_e(R)\,(\mathrm{km\,s}^{-1})$" Rrange= kwargs.pop('Rrange',[0.01*potro,5.*potro]) else: use_physical= False xlabel= r"$R/R_0$" ylabel= r"$v_e(R)/v_c(R_0)$" Rrange= kwargs.pop('Rrange',[0.01,5.]) # Parse ro if _APY_LOADED: if isinstance(potro,units.Quantity): potro= potro.to(units.kpc).value if isinstance(potvo,units.Quantity): potvo= potvo.to(units.km/units.s).value if isinstance(Rrange[0],units.Quantity): Rrange[0]= Rrange[0].to(units.kpc).value if isinstance(Rrange[1],units.Quantity): Rrange[1]= Rrange[1].to(units.kpc).value if use_physical: Rrange[0]/= potro Rrange[1]/= potro grid= kwargs.pop('grid',1001) savefilename= kwargs.pop('savefilename',None) if not savefilename == None and os.path.exists(savefilename): print("Restoring savefile "+savefilename+" ...") savefile= open(savefilename,'rb') esccurve= pickle.load(savefile) Rs= pickle.load(savefile) savefile.close() else: Rs= nu.linspace(Rrange[0],Rrange[1],grid) esccurve= calcEscapecurve(Pot,Rs) if not savefilename == None: print("Writing savefile "+savefilename+" ...") savefile= open(savefilename,'wb') pickle.dump(esccurve,savefile) pickle.dump(Rs,savefile) savefile.close() if use_physical: Rs*= potro esccurve*= potvo Rrange[0]*= potro Rrange[1]*= potro if not 'xlabel' in kwargs: kwargs['xlabel']= xlabel if not 'ylabel' in kwargs: kwargs['ylabel']= ylabel if not 'xrange' in kwargs: kwargs['xrange']= Rrange if not 'yrange' in kwargs: kwargs['yrange']=\ [0.,1.2*nu.amax(esccurve[True^nu.isnan(esccurve)])] kwargs.pop('ro',None) kwargs.pop('vo',None) kwargs.pop('use_physical',None) return plot.bovy_plot(Rs,esccurve,*args, **kwargs) def calcEscapecurve(Pot,Rs): """ NAME: calcEscapecurve PURPOSE: calculate the escape velocity curve for this potential (in the z=0 plane for non-spherical potentials) INPUT: Pot - Potential or list of Potential instances Rs - (array of) radius(i) OUTPUT: array of v_esc HISTORY: 2011-04-16 - Written - Bovy (NYU) """ isList= isinstance(Pot,list) isNonAxi= ((isList and Pot[0].isNonAxi) or (not isList and Pot.isNonAxi)) if isNonAxi: raise AttributeError("Escape velocity curve plotting for non-axisymmetric potentials is not currently supported") try: grid= len(Rs) except TypeError: grid=1 Rs= nu.array([Rs]) esccurve= nu.zeros(grid) for ii in range(grid): esccurve[ii]= vesc(Pot,Rs[ii],use_physical=False) return esccurve @potential_physical_input @physical_conversion('velocity',pop=True) def vesc(Pot,R): """ NAME: vesc PURPOSE: calculate the escape velocity at R for potential Pot INPUT: Pot - Potential instances or list thereof R - Galactocentric radius (can be Quantity) OUTPUT: escape velocity HISTORY: 2011-10-09 - Written - Bovy (IAS) """ from galpy.potential import evaluateplanarPotentials from galpy.potential import PotentialError try: return nu.sqrt(2.*(evaluateplanarPotentials(Pot,_INF,use_physical=False)-evaluateplanarPotentials(Pot,R,use_physical=False))) except PotentialError: from galpy.potential import RZToplanarPotential Pot= RZToplanarPotential(Pot) return nu.sqrt(2.*(evaluateplanarPotentials(Pot,_INF,use_physical=False)-evaluateplanarPotentials(Pot,R,use_physical=False))) galpy-1.3.0/galpy/potential_src/plotRotcurve.py0000644000076500000240000001537013003465421022001 0ustar bovystaff00000000000000from __future__ import division, print_function import os import pickle import numpy as nu import galpy.util.bovy_plot as plot from galpy.util.bovy_conversion import physical_conversion,\ potential_physical_input _APY_LOADED= True try: from astropy import units except ImportError: _APY_LOADED= False def plotRotcurve(Pot,*args,**kwargs): """ NAME: plotRotcurve PURPOSE: plot the rotation curve for this potential (in the z=0 plane for non-spherical potentials) INPUT: Pot - Potential or list of Potential instances Rrange - Range in R to consider (needs to be in the units that you are plotting; can be Quantity) grid= grid in R phi= (None) azimuth to use for non-axisymmetric potentials savefilename= save to or restore from this savefile (pickle) +bovy_plot.bovy_plot args and kwargs OUTPUT: plot to output device HISTORY: 2010-07-10 - Written - Bovy (NYU) 2016-06-15 - Added phi= keyword for non-axisymmetric potential - Bovy (UofT) """ # Using physical units or not? if isinstance(Pot,list): potro= Pot[0]._ro roSet= Pot[0]._roSet potvo= Pot[0]._vo voSet= Pot[0]._voSet else: potro= Pot._ro roSet= Pot._roSet potvo= Pot._vo voSet= Pot._voSet if (kwargs.get('use_physical',False) \ and kwargs.get('ro',roSet) and kwargs.get('vo',voSet)) or \ (not 'use_physical' in kwargs \ and kwargs.get('ro',roSet) and kwargs.get('vo',voSet)): use_physical= True potro= kwargs.get('ro',potro) potvo= kwargs.get('vo',potvo) xlabel= r'$R\,(\mathrm{kpc})$' ylabel= r"$v_c(R)\,(\mathrm{km\,s}^{-1})$" Rrange= kwargs.pop('Rrange',[0.01*potro,5.*potro]) else: use_physical= False xlabel= r"$R/R_0$" ylabel= r"$v_c(R)/v_c(R_0)$" Rrange= kwargs.pop('Rrange',[0.01,5.]) # Parse ro if _APY_LOADED: if isinstance(potro,units.Quantity): potro= potro.to(units.kpc).value if isinstance(potvo,units.Quantity): potvo= potvo.to(units.km/units.s).value if isinstance(Rrange[0],units.Quantity): Rrange[0]= Rrange[0].to(units.kpc).value if isinstance(Rrange[1],units.Quantity): Rrange[1]= Rrange[1].to(units.kpc).value if use_physical: Rrange[0]/= potro Rrange[1]/= potro grid= kwargs.pop('grid',1001) savefilename= kwargs.pop('savefilename',None) phi= kwargs.pop('phi',None) if not savefilename is None and os.path.exists(savefilename): print("Restoring savefile "+savefilename+" ...") savefile= open(savefilename,'rb') rotcurve= pickle.load(savefile) Rs= pickle.load(savefile) savefile.close() else: Rs= nu.linspace(Rrange[0],Rrange[1],grid) rotcurve= calcRotcurve(Pot,Rs,phi=phi) if not savefilename == None: print("Writing savefile "+savefilename+" ...") savefile= open(savefilename,'wb') pickle.dump(rotcurve,savefile) pickle.dump(Rs,savefile) savefile.close() if use_physical: Rs*= potro rotcurve*= potvo Rrange[0]*= potro Rrange[1]*= potro if not 'xlabel' in kwargs: kwargs['xlabel']= xlabel if not 'ylabel' in kwargs: kwargs['ylabel']= ylabel if not 'xrange' in kwargs: kwargs['xrange']= Rrange if not 'yrange' in kwargs: kwargs['yrange']= [0.,1.2*nu.amax(rotcurve)] kwargs.pop('ro',None) kwargs.pop('vo',None) kwargs.pop('use_physical',None) return plot.bovy_plot(Rs,rotcurve,*args, **kwargs) def calcRotcurve(Pot,Rs,phi=None): """ NAME: calcRotcurve PURPOSE: calculate the rotation curve for this potential (in the z=0 plane for non-spherical potentials) INPUT: Pot - Potential or list of Potential instances Rs - (array of) radius(i) phi= (None) azimuth to use for non-axisymmetric potentials OUTPUT: array of vc HISTORY: 2011-04-13 - Written - Bovy (NYU) 2016-06-15 - Added phi= keyword for non-axisymmetric potential - Bovy (UofT) """ try: grid= len(Rs) except TypeError: grid=1 Rs= nu.array([Rs]) rotcurve= nu.zeros(grid) for ii in range(grid): rotcurve[ii]= vcirc(Pot,Rs[ii],phi=phi,use_physical=False) return rotcurve @potential_physical_input @physical_conversion('velocity',pop=True) def vcirc(Pot,R,phi=None): """ NAME: vcirc PURPOSE: calculate the circular velocity at R in potential Pot INPUT: Pot - Potential instance or list of such instances R - Galactocentric radius (can be Quantity) phi= (None) azimuth to use for non-axisymmetric potentials OUTPUT: circular rotation velocity HISTORY: 2011-10-09 - Written - Bovy (IAS) 2016-06-15 - Added phi= keyword for non-axisymmetric potential - Bovy (UofT) """ from galpy.potential import evaluateplanarRforces from galpy.potential import PotentialError try: return nu.sqrt(-R*evaluateplanarRforces(Pot,R,phi=phi, use_physical=False)) except PotentialError: from galpy.potential import toPlanarPotential Pot= toPlanarPotential(Pot) return nu.sqrt(-R*evaluateplanarRforces(Pot,R,phi=phi, use_physical=False)) @potential_physical_input @physical_conversion('frequency',pop=True) def dvcircdR(Pot,R,phi=None): """ NAME: dvcircdR PURPOSE: calculate the derivative of the circular velocity wrt R at R in potential Pot INPUT: Pot - Potential instance or list of such instances R - Galactocentric radius (can be Quantity) phi= (None) azimuth to use for non-axisymmetric potentials OUTPUT: derivative of the circular rotation velocity wrt R HISTORY: 2013-01-08 - Written - Bovy (IAS) 2016-06-28 - Added phi= keyword for non-axisymmetric potential - Bovy (UofT) """ from galpy.potential import evaluateplanarRforces, evaluateplanarR2derivs from galpy.potential import PotentialError tvc= vcirc(Pot,R,phi=phi,use_physical=False) try: return 0.5*(-evaluateplanarRforces(Pot,R,phi=phi,use_physical=False)+R*evaluateplanarR2derivs(Pot,R,phi=phi,use_physical=False))/tvc except PotentialError: from galpy.potential import RZToplanarPotential Pot= RZToplanarPotential(Pot) return 0.5*(-evaluateplanarRforces(Pot,R,phi=phi,use_physical=False)+R*evaluateplanarR2derivs(Pot,R,phi=phi,use_physical=False))/tvc galpy-1.3.0/galpy/potential_src/PlummerPotential.py0000644000076500000240000001354713003761364022603 0ustar bovystaff00000000000000############################################################################### # PlummerPotential.py: class that implements the Plummer potential # GM # phi(R,z) = - --------------------------------- # \sqrt(R^2+z^2+b^2) ############################################################################### import numpy as nu from galpy.potential_src.Potential import Potential, kms_to_kpcGyrDecorator, \ _APY_LOADED if _APY_LOADED: from astropy import units class PlummerPotential(Potential): """Class that implements the Plummer potential .. math:: \\Phi(R,z) = -\\frac{\\mathrm{amp}}{\\sqrt{R^2+z^2+b^2}} """ def __init__(self,amp=1.,b=0.8,normalize=False, ro=None,vo=None): """ NAME: __init__ PURPOSE: initialize a Plummer potential INPUT: amp - amplitude to be applied to the potential (default: 1); can be a Quantity with units of mass or Gxmass b - scale parameter (can be Quantity) normalize - if True, normalize such that vc(1.,0.)=1., or, if given as a number, such that the force is this fraction of the force necessary to make vc(1.,0.)=1. ro=, vo= distance and velocity scales for translation into internal units (default from configuration file) OUTPUT: (none) HISTORY: 2015-06-15 - Written - Bovy (IAS) """ Potential.__init__(self,amp=amp,ro=ro,vo=vo,amp_units='mass') if _APY_LOADED and isinstance(b,units.Quantity): b= b.to(units.kpc).value/self._ro self._b= b self._scale= self._b self._b2= self._b**2. if normalize or \ (isinstance(normalize,(int,float)) \ and not isinstance(normalize,bool)): self.normalize(normalize) self.hasC= True self.hasC_dxdv= True self._nemo_accname= 'Plummer' def _evaluate(self,R,z,phi=0.,t=0.): """ NAME: _evaluate PURPOSE: evaluate the potential at R,z INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t - time OUTPUT: Phi(R,z) HISTORY: 2015-06-15 - Started - Bovy (IAS) """ return -1./nu.sqrt(R**2.+z**2.+self._b2) def _Rforce(self,R,z,phi=0.,t=0.): """ NAME: _Rforce PURPOSE: evaluate the radial force for this potential INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t - time OUTPUT: the radial force HISTORY: 2015-06-15 - Written - Bovy (IAS) """ dPhidrr= -(R**2.+z**2.+self._b2)**-1.5 return dPhidrr*R def _zforce(self,R,z,phi=0.,t=0.): """ NAME: _zforce PURPOSE: evaluate the vertical force for this potential INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t - time OUTPUT: the vertical force HISTORY: 2015-06-15 - Written - Bovy (IAS) """ dPhidrr= -(R**2.+z**2.+self._b2)**-1.5 return dPhidrr*z def _dens(self,R,z,phi=0.,t=0.): """ NAME: _dens PURPOSE: evaluate the density for this potential INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t - time OUTPUT: the density HISTORY: 2015-06-15 - Written - Bovy (IAS) """ return 3./4./nu.pi*self._b2*(R**2.+z**2.+self._b2)**-2.5 def _R2deriv(self,R,z,phi=0.,t=0.): """ NAME: _R2deriv PURPOSE: evaluate the second radial derivative for this potential INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t - time OUTPUT: the second radial derivative HISTORY: 2015-06-15 - Written - Bovy (IAS) """ return (self._b2-2.*R**2.+z**2.)*(R**2.+z**2.+self._b2)**-2.5 def _z2deriv(self,R,z,phi=0.,t=0.): """ NAME: _z2deriv PURPOSE: evaluate the second vertical derivative for this potential INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t - time OUTPUT: the second vertical derivative HISTORY: 2015-06-15 - Written - Bovy (IAS) """ return (self._b2+R**2.-2.*z**2.)*(R**2.+z**2.+self._b2)**-2.5 def _Rzderiv(self,R,z,phi=0.,t=0.): """ NAME: _Rzderiv PURPOSE: evaluate the mixed R,z derivative for this potential INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t - time OUTPUT: d2phi/dR/dz HISTORY: 2015-06-15 - Written - Bovy (IAS) """ return -3.*R*z*(R**2.+z**2.+self._b2)**-2.5 @kms_to_kpcGyrDecorator def _nemo_accpars(self,vo,ro): """ NAME: _nemo_accpars PURPOSE: return the accpars potential parameters for use of this potential with NEMO INPUT: vo - velocity unit in km/s ro - length unit in kpc OUTPUT: accpars string HISTORY: 2014-12-18 - Written - Bovy (IAS) """ ampl= self._amp*vo**2.*ro return "0,%s,%s" % (ampl,self._b*ro) galpy-1.3.0/galpy/potential_src/Potential.py0000644000076500000240000022405313231463221021227 0ustar bovystaff00000000000000############################################################################### # Potential.py: top-level class for a full potential # # Evaluate by calling the instance: Pot(R,z,phi) # # API for Potentials: # function _evaluate(self,R,z,phi) returns Phi(R,z,phi) # for orbit integration you need # function _Rforce(self,R,z,phi) return -d Phi d R # function _zforce(self,R,z,phi) return - d Phi d Z # density # function _dens(self,R,z,phi) return BOVY?? # for epicycle frequency # function _R2deriv(self,R,z,phi) return d2 Phi dR2 ############################################################################### from __future__ import division, print_function import os, os.path import pickle from functools import wraps import math import numpy as nu from scipy import optimize, integrate import galpy.util.bovy_plot as plot from galpy.util import bovy_coords from galpy.util import config from galpy.util.bovy_conversion import velocity_in_kpcGyr, \ physical_conversion, potential_physical_input, freq_in_Gyr from galpy.util import bovy_conversion from galpy.potential_src.plotRotcurve import plotRotcurve, vcirc from galpy.potential_src.plotEscapecurve import _INF, plotEscapecurve _APY_LOADED= True try: from astropy import units except ImportError: _APY_LOADED= False class Potential(object): """Top-level class for a potential""" def __init__(self,amp=1.,ro=None,vo=None,amp_units=None): """ NAME: __init__ PURPOSE: INPUT: amp - amplitude to be applied when evaluating the potential and its forces amp_units - ('mass', 'velocity2', 'density') type of units that amp should have if it has units OUTPUT: HISTORY: """ self._amp= amp self.dim= 3 self.isRZ= True self.isNonAxi= False self.hasC= False self.hasC_dxdv= False # Parse ro and vo if ro is None: self._ro= config.__config__.getfloat('normalization','ro') self._roSet= False else: if _APY_LOADED and isinstance(ro,units.Quantity): ro= ro.to(units.kpc).value self._ro= ro self._roSet= True if vo is None: self._vo= config.__config__.getfloat('normalization','vo') self._voSet= False else: if _APY_LOADED and isinstance(vo,units.Quantity): vo= vo.to(units.km/units.s).value self._vo= vo self._voSet= True # Parse amp if it has units if _APY_LOADED and isinstance(self._amp,units.Quantity): # Try a bunch of possible units unitFound= False # velocity^2 try: self._amp= self._amp.to(units.km**2/units.s**2).value\ /self._vo**2. except units.UnitConversionError: pass else: unitFound= True if not amp_units == 'velocity2': raise units.UnitConversionError('amp= parameter of %s should have units of %s, but has units of velocity2 instead' % (type(self).__name__,amp_units)) if not unitFound: # mass try: self._amp= self._amp.to(units.Msun).value\ /bovy_conversion.mass_in_msol(self._vo,self._ro) except units.UnitConversionError: pass else: unitFound= True if not amp_units == 'mass': raise units.UnitConversionError('amp= parameter of %s should have units of %s, but has units of mass instead' % (type(self).__name__,amp_units)) if not unitFound: # G x mass try: self._amp= self._amp.to(units.pc*units.km**2/units.s**2)\ .value\ /bovy_conversion.mass_in_msol(self._vo,self._ro)\ /bovy_conversion._G except units.UnitConversionError: pass else: unitFound= True if not amp_units == 'mass': raise units.UnitConversionError('amp= parameter of %s should have units of %s, but has units of G x mass instead' % (type(self).__name__,amp_units)) if not unitFound: # density try: self._amp= self._amp.to(units.Msun/units.pc**3).value\ /bovy_conversion.dens_in_msolpc3(self._vo,self._ro) except units.UnitConversionError: pass else: unitFound= True if not amp_units == 'density': raise units.UnitConversionError('amp= parameter of %s should have units of %s, but has units of density instead' % (type(self).__name__,amp_units)) if not unitFound: # G x density try: self._amp= self._amp.to(units.km**2/units.s**2\ /units.pc**2).value\ /bovy_conversion.dens_in_msolpc3(self._vo,self._ro)\ /bovy_conversion._G except units.UnitConversionError: pass else: unitFound= True if not amp_units == 'density': raise units.UnitConversionError('amp= parameter of %s should have units of %s, but has units of G x density instead' % (type(self).__name__,amp_units)) if not unitFound: # surface density try: self._amp= self._amp.to(units.Msun/units.pc**2).value\ /bovy_conversion.surfdens_in_msolpc2(self._vo,self._ro) except units.UnitConversionError: pass else: unitFound= True if not amp_units == 'surfacedensity': raise units.UnitConversionError('amp= parameter of %s should have units of %s, but has units of surface density instead' % (type(self).__name__,amp_units)) if not unitFound: # G x surface density try: self._amp= self._amp.to(units.km**2/units.s**2\ /units.pc).value\ /bovy_conversion.surfdens_in_msolpc2(self._vo,self._ro)\ /bovy_conversion._G except units.UnitConversionError: pass else: unitFound= True if not amp_units == 'surfacedensity': raise units.UnitConversionError('amp= parameter of %s should have units of %s, but has units of G x surface density instead' % (type(self).__name__,amp_units)) if not unitFound: raise units.UnitConversionError('amp= parameter of %s should have units of %s; given units are not understood' % (type(self).__name__,amp_units)) else: # When amplitude is given with units, turn on physical output self._roSet= True self._voSet= True return None def turn_physical_off(self): """ NAME: turn_physical_off PURPOSE: turn off automatic returning of outputs in physical units INPUT: (none) OUTPUT: (none) HISTORY: 2016-01-30 - Written - Bovy (UofT) """ self._roSet= False self._voSet= False return None def turn_physical_on(self,ro=None,vo=None): """ NAME: turn_physical_on PURPOSE: turn on automatic returning of outputs in physical units INPUT: ro= reference distance (kpc; can be Quantity) vo= reference velocity (km/s; can be Quantity) OUTPUT: (none) HISTORY: 2016-01-30 - Written - Bovy (UofT) """ self._roSet= True self._voSet= True if not ro is None: if _APY_LOADED and isinstance(ro,units.Quantity): ro= ro.to(units.kpc).value self._ro= ro if not vo is None: if _APY_LOADED and isinstance(vo,units.Quantity): vo= vo.to(units.km/units.s).value self._vo= vo return None @potential_physical_input @physical_conversion('energy',pop=True) def __call__(self,R,z,phi=0.,t=0.,dR=0,dphi=0): """ NAME: __call__ PURPOSE: evaluate the potential at (R,z,phi,t) INPUT: R - Cylindrical Galactocentric radius (can be Quantity) z - vertical height (can be Quantity) phi - azimuth (optional; can be Quantity) t - time (optional; can be Quantity) OUTPUT: Phi(R,z,t) HISTORY: 2010-04-16 - Written - Bovy (NYU) """ return self._call_nodecorator(R,z,phi=phi,t=t,dR=dR,dphi=dphi) def _call_nodecorator(self,R,z,phi=0.,t=0.,dR=0.,dphi=0): if dR == 0 and dphi == 0: try: rawOut= self._evaluate(R,z,phi=phi,t=t) except AttributeError: #pragma: no cover raise PotentialError("'_evaluate' function not implemented for this potential") if rawOut is None: return rawOut else: return self._amp*rawOut elif dR == 1 and dphi == 0: return -self.Rforce(R,z,phi=phi,t=t,use_physical=False) elif dR == 0 and dphi == 1: return -self.phiforce(R,z,phi=phi,t=t,use_physical=False) elif dR == 2 and dphi == 0: return self.R2deriv(R,z,phi=phi,t=t,use_physical=False) elif dR == 0 and dphi == 2: return self.phi2deriv(R,z,phi=phi,t=t,use_physical=False) elif dR == 1 and dphi == 1: return self.Rphideriv(R,z,phi=phi,t=t,use_physical=False) elif dR != 0 or dphi != 0: raise NotImplementedError('Higher-order derivatives not implemented for this potential') @potential_physical_input @physical_conversion('force',pop=True) def Rforce(self,R,z,phi=0.,t=0.): """ NAME: Rforce PURPOSE: evaluate cylindrical radial force F_R (R,z) INPUT: R - Cylindrical Galactocentric radius (can be Quantity) z - vertical height (can be Quantity) phi - azimuth (optional; can be Quantity) t - time (optional; can be Quantity) OUTPUT: F_R (R,z,phi,t) HISTORY: 2010-04-16 - Written - Bovy (NYU) """ return self._Rforce_nodecorator(R,z,phi=phi,t=t) def _Rforce_nodecorator(self,R,z,phi=0.,t=0.): # Separate, so it can be used during orbit integration try: return self._amp*self._Rforce(R,z,phi=phi,t=t) except AttributeError: #pragma: no cover raise PotentialError("'_Rforce' function not implemented for this potential") @potential_physical_input @physical_conversion('force',pop=True) def zforce(self,R,z,phi=0.,t=0.): """ NAME: zforce PURPOSE: evaluate the vertical force F_z (R,z,t) INPUT: R - Cylindrical Galactocentric radius (can be Quantity) z - vertical height (can be Quantity) phi - azimuth (optional; can be Quantity) t - time (optional; can be Quantity) OUTPUT: F_z (R,z,phi,t) HISTORY: 2010-04-16 - Written - Bovy (NYU) """ return self._zforce_nodecorator(R,z,phi=phi,t=t) def _zforce_nodecorator(self,R,z,phi=0.,t=0.): # Separate, so it can be used during orbit integration try: return self._amp*self._zforce(R,z,phi=phi,t=t) except AttributeError: #pragma: no cover raise PotentialError("'_zforce' function not implemented for this potential") @potential_physical_input @physical_conversion('force',pop=True) def rforce(self,R,z,phi=0.,t=0.): """ NAME: rforce PURPOSE: evaluate spherical radial force F_r (R,z) INPUT: R - Cylindrical Galactocentric radius (can be Quantity) z - vertical height (can be Quantity) phi - azimuth (optional; can be Quantity) t - time (optional; can be Quantity) OUTPUT: F_r (R,z,phi,t) HISTORY: 2016-06-20 - Written - Bovy (UofT) """ r= nu.sqrt(R**2.+z**2.) return self.Rforce(R,z,phi=phi,t=t,use_physical=False)*R/r\ +self.zforce(R,z,phi=phi,t=t,use_physical=False)*z/r @potential_physical_input @physical_conversion('density',pop=True) def dens(self,R,z,phi=0.,t=0.,forcepoisson=False): """ NAME: dens PURPOSE: evaluate the density rho(R,z,t) INPUT: R - Cylindrical Galactocentric radius (can be Quantity) z - vertical height (can be Quantity) phi - azimuth (optional; can be Quantity) t - time (optional; can be Quantity) KEYWORDS: forcepoisson= if True, calculate the density through the Poisson equation, even if an explicit expression for the density exists OUTPUT: rho (R,z,phi,t) HISTORY: 2010-08-08 - Written - Bovy (NYU) """ try: if forcepoisson: raise AttributeError #Hack! return self._amp*self._dens(R,z,phi=phi,t=t) except AttributeError: #Use the Poisson equation to get the density return (-self.Rforce(R,z,phi=phi,t=t,use_physical=False)/R +self.R2deriv(R,z,phi=phi,t=t,use_physical=False) +self.phi2deriv(R,z,phi=phi,t=t,use_physical=False)/R**2. +self.z2deriv(R,z,phi=phi,t=t,use_physical=False))/4./nu.pi @potential_physical_input @physical_conversion('mass',pop=True) def mass(self,R,z=None,t=0.,forceint=False): """ NAME: mass PURPOSE: evaluate the mass enclosed INPUT: R - Cylindrical Galactocentric radius (can be Quantity) z= (None) vertical height (can be Quantity) t - time (optional; can be Quantity) KEYWORDS: forceint= if True, calculate the mass through integration of the density, even if an explicit expression for the mass exists OUTPUT: 1) for spherical potentials: M( 0.: if lower: rtry/= 2. else: rtry*= 2. return rtry @physical_conversion('position',pop=True) def lindbladR(Pot,OmegaP,m=2,**kwargs): """ NAME: lindbladR PURPOSE: calculate the radius of a Lindblad resonance INPUT: Pot - Potential instance or list of such instances OmegaP - pattern speed (can be Quantity) m= order of the resonance (as in m(O-Op)=kappa (negative m for outer) use m='corotation' for corotation +scipy.optimize.brentq xtol,rtol,maxiter kwargs OUTPUT: radius of Linblad resonance, None if there is no resonance HISTORY: 2011-10-09 - Written - Bovy (IAS) """ if _APY_LOADED and isinstance(OmegaP,units.Quantity): if hasattr(Pot,'_ro'): OmegaP= OmegaP.to(1/units.Gyr).value/freq_in_Gyr(Pot._vo,Pot._ro) elif hasattr(Pot[0],'_ro'): OmegaP= OmegaP.to(1/units.Gyr).value\ /freq_in_Gyr(Pot[0]._vo,Pot[0]._ro) if isinstance(m,str): if 'corot' in m.lower(): corotation= True else: raise IOError("'m' input not recognized, should be an integer or 'corotation'") else: corotation= False if corotation: try: out= optimize.brentq(_corotationR_eq,0.0000001,1000., args=(Pot,OmegaP),**kwargs) except ValueError: return None except RuntimeError: #pragma: no cover raise return out else: try: out= optimize.brentq(_lindbladR_eq,0.0000001,1000., args=(Pot,OmegaP,m),**kwargs) except ValueError: return None except RuntimeError: #pragma: no cover raise return out def _corotationR_eq(R,Pot,OmegaP): return omegac(Pot,R,use_physical=False)-OmegaP def _lindbladR_eq(R,Pot,OmegaP,m): return m*(omegac(Pot,R,use_physical=False)-OmegaP)\ -epifreq(Pot,R,use_physical=False) @potential_physical_input @physical_conversion('frequency',pop=True) def omegac(Pot,R): """ NAME: omegac PURPOSE: calculate the circular angular speed velocity at R in potential Pot INPUT: Pot - Potential instance or list of such instances R - Galactocentric radius (can be Quantity) OUTPUT: circular angular speed HISTORY: 2011-10-09 - Written - Bovy (IAS) """ from galpy.potential import evaluateplanarRforces try: return nu.sqrt(-evaluateplanarRforces(Pot,R,use_physical=False)/R) except PotentialError: from galpy.potential import RZToplanarPotential Pot= RZToplanarPotential(Pot) return nu.sqrt(-evaluateplanarRforces(Pot,R,use_physical=False)/R) def nemo_accname(Pot): """ NAME: nemo_accname PURPOSE: return the accname potential name for use of this potential or list of potentials with NEMO INPUT: Pot - Potential instance or list of such instances OUTPUT: Acceleration name in the correct format to give to accname= HISTORY: 2014-12-18 - Written - Bovy (IAS) """ if isinstance(Pot,list): out= '' for ii,pot in enumerate(Pot): if ii > 0: out+= '+' out+= pot.nemo_accname() return out elif isinstance(Pot,Potential): return Pot.nemo_accname() else: #pragma: no cover raise PotentialError("Input to 'nemo_accname' is neither a Potential-instance or a list of such instances") def nemo_accpars(Pot,vo,ro): """ NAME: nemo_accpars PURPOSE: return the accpars potential parameters for use of this potential or list of potentials with NEMO INPUT: Pot - Potential instance or list of such instances vo - velocity unit in km/s ro - length unit in kpc OUTPUT: accpars string in the corrct format to give to accpars HISTORY: 2014-12-18 - Written - Bovy (IAS) """ if isinstance(Pot,list): out= '' for ii,pot in enumerate(Pot): if ii > 0: out+= '#' out+= pot.nemo_accpars(vo,ro) return out elif isinstance(Pot,Potential): return Pot.nemo_accpars(vo,ro) else: #pragma: no cover raise PotentialError("Input to 'nemo_accpars' is neither a Potential-instance or a list of such instances") def turn_physical_off(Pot): """ NAME: turn_physical_off PURPOSE: turn off automatic returning of outputs in physical units INPUT: (none) OUTPUT: (none) HISTORY: 2016-01-30 - Written - Bovy (UofT) """ if isinstance(Pot,list): for pot in Pot: pot.turn_physical_off() else: Pot.turn_physical_off() return None def turn_physical_on(Pot,ro=None,vo=None): """ NAME: turn_physical_on PURPOSE: turn on automatic returning of outputs in physical units INPUT: ro= reference distance (kpc; can be Quantity) vo= reference velocity (km/s; can be Quantity) OUTPUT: (none) HISTORY: 2016-01-30 - Written - Bovy (UofT) """ if isinstance(Pot,list): for pot in Pot: pot.turn_physical_on(ro=ro,vo=vo) else: Pot.turn_physical_on(ro=ro,vo=vo) return None def _check_c(Pot,dxdv=False): """ NAME: _check_c PURPOSE: check whether a potential or list thereof has a C implementation INPUT: Pot - Potential instance or list of such instances dxdv= (False) check whether the potential has dxdv implementation OUTPUT: True if a C implementation exists, False otherwise HISTORY: 2014-02-17 - Written - Bovy (IAS) 2017-07-01 - Generalized to dxdv, added general support for WrapperPotentials, and added support for planarPotentials """ from galpy.potential import planarPotential if dxdv: hasC_attr= 'hasC_dxdv' else: hasC_attr= 'hasC' from galpy.potential_src.WrapperPotential import parentWrapperPotential if isinstance(Pot,list): return nu.all(nu.array([_check_c(p,dxdv=dxdv) for p in Pot], dtype='bool')) elif isinstance(Pot,parentWrapperPotential): return bool(Pot.__dict__[hasC_attr]*_check_c(Pot._pot)) elif isinstance(Pot,Potential) or isinstance(Pot,planarPotential): return Pot.__dict__[hasC_attr] def _dim(Pot): """ NAME: _dim PURPOSE: Determine the dimensionality of this potential INPUT: Pot - Potential instance or list of such instances OUTPUT: Minimum of the dimensionality of all potentials if list; otherwise Pot.dim HISTORY: 2016-04-19 - Written - Bovy (UofT) """ from galpy.potential import planarPotential, linearPotential if isinstance(Pot,list): return nu.amin(nu.array([p.dim for p in Pot],dtype='int')) elif isinstance(Pot,(Potential,planarPotential,linearPotential)): return Pot.dim def _isNonAxi(Pot): """ NAME: _isNonAxi PURPOSE: Determine whether this potential is non-axisymmetric INPUT: Pot - Potential instance or list of such instances OUTPUT: True or False depending on whether the potential is non-axisymmetric (note that some potentials might return True, even though for some parameter values they are axisymmetric) HISTORY: 2016-06-16 - Written - Bovy (UofT) """ isList= isinstance(Pot,list) if isList: isAxis= [not p.isNonAxi for p in Pot] nonAxi= not nu.prod(nu.array(isAxis)) else: nonAxi= Pot.isNonAxi return nonAxi def kms_to_kpcGyrDecorator(func): """Decorator to convert velocities from km/s to kpc/Gyr""" @wraps(func) def kms_to_kpcGyr_wrapper(*args,**kwargs): return func(args[0],velocity_in_kpcGyr(args[1],1.),args[2],**kwargs) return kms_to_kpcGyr_wrapper galpy-1.3.0/galpy/potential_src/potential_c_ext/0000755000076500000240000000000013236420072022073 5ustar bovystaff00000000000000galpy-1.3.0/galpy/potential_src/potential_c_ext/BurkertPotential.c0000644000076500000240000000425213003465421025537 0ustar bovystaff00000000000000#include #include #ifndef M_PI #define M_PI 3.14159265358979323846 #endif //BurkertPotential // 2 arguments: amp, a double BurkertPotentialEval(double R,double Z, double phi, double t, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; //Get args double amp= *args; double a= *(args+1); //Calculate potential double x= sqrt( R*R + Z*Z) / a; return -amp * a * a * M_PI / x * ( -M_PI + 2. * ( 1. + x ) * atan( 1 / x ) + 2. * ( 1. + x ) * log ( 1. + x ) + ( 1. - x ) * log ( 1. + x * x )); } double BurkertPotentialRforce(double R,double Z, double phi, double t, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; //Get args double amp= *args; double a= *(args+1); //Calculate Rforce double r= sqrt( R*R + Z*Z); double x= r / a; return amp * a * M_PI / x / x * ( M_PI - 2. * atan ( 1. / x ) - 2. * log ( 1. + x ) - log ( 1. + x * x)) * R / r; } double BurkertPotentialPlanarRforce(double R,double phi, double t, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; //Get args double amp= *args; double a= *(args+1); //Calculate Rforce double x= R / a; return amp * a * M_PI / x / x * ( M_PI - 2. * atan ( 1. / x ) - 2. * log ( 1. + x ) - log ( 1. + x * x)); } double BurkertPotentialzforce(double R,double z,double phi, double t, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; //Get args double amp= *args; double a= *(args+1); //Calculate zforce double r= sqrt( R*R + z*z); double x= r / a; return amp * a * M_PI / x / x * ( M_PI - 2. * atan ( 1. / x ) - 2. * log ( 1. + x ) - log ( 1. + x * x)) * z / r; } double BurkertPotentialPlanarR2deriv(double R,double phi, double t, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; //Get args double amp= *args; double a= *(args+1); //Calculate R2deriv double x= R / a; double R5= pow(R,5); return -amp * M_PI * pow(a,3) / R5 * (-4. * R5 / ( a * a + R * R ) / ( a + R ) - 2. * R * R * ( M_PI - 2. * atan ( 1. / x ) - 2. * log( 1. + x ) - log( 1. + x * x ))); } galpy-1.3.0/galpy/potential_src/potential_c_ext/CosmphiDiskPotential.c0000644000076500000240000000516713157625526026362 0ustar bovystaff00000000000000#include #include //CosmphiDiskPotential double CosmphiDiskPotentialRforce(double R,double phi,double t, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; //Get args double amp= *args++; double mphio= *args++; double p= *args++; double mphib= *args++; int m= (int) *args++; double rb= *args++; double rb2p= *(args+1); if ( R <= rb ) return -amp * p * mphio / m * rb2p / pow(R,p+1.) * cos( m * phi - mphib); else return -amp * p * mphio / m * pow(R,p-1.) * cos( m * phi - mphib); } double CosmphiDiskPotentialphiforce(double R,double phi,double t, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; //Get args double amp= *args++; double mphio= *args++; double p= *args++; double mphib= *args++; int m= (int) *args++; double rb= *args++; double rbp= *args++; double r1p= *(args+1); if ( R <= rb ) return amp * mphio * rbp * ( 2. * r1p - rbp / pow(R,p) )\ * sin( m * phi-mphib); else return amp * mphio * pow(R,p) * sin( m * phi-mphib); } double CosmphiDiskPotentialR2deriv(double R,double phi,double t, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; //Get args double amp= *args++; double mphio= *args++; double p= *args++; double mphib= *args++; int m= (int) *args++; double rb= *args++; double rb2p= *(args+1); if ( R <= rb ) return -amp * p * ( p + 1 ) * mphio / m * rb2p / pow(R,p+2.) \ * cos( m * phi - mphib); else return amp * p * ( p - 1) * mphio / m * pow(R,p-2.) * cos(m * phi - mphib); } double CosmphiDiskPotentialphi2deriv(double R,double phi,double t, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; //Get args double amp= *args++; double mphio= *args++; double p= *args++; double mphib= *args++; int m= (int) *args++; double rb= *args++; double rbp= *args++; double r1p= *(args+1); if ( R <= rb ) return - amp * m * mphio * rbp * ( 2. * r1p - rbp / pow(R,p) )\ * cos( m * phi-mphib); else return - amp * m * mphio * pow(R,p) * cos( m * phi - mphib ); } double CosmphiDiskPotentialRphideriv(double R,double phi,double t, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; //Get args double amp= *args++; double mphio= *args++; double p= *args++; double mphib= *args++; int m= (int) *args++; double rb= *args++; double rb2p= *(args+1); if ( R <= rb ) return - amp * p * mphio / m * rb2p / pow(R,p+1) * sin( m * phi-mphib); else return - amp * p * mphio * pow(R,p-1.) * sin( m * phi - mphib ); } galpy-1.3.0/galpy/potential_src/potential_c_ext/DehnenBarPotential.c0000644000076500000240000001217513133144160025750 0ustar bovystaff00000000000000#include #include //DehnenBarPotential // double dehnenBarSmooth(double t,double tform, double tsteady){ double smooth, xi,deltat; if ( t < tform ) smooth= 0.; else if ( t < tsteady ) { deltat= t-tform; xi= 2.*deltat/(tsteady-tform)-1.; smooth= (3./16.*pow(xi,5.)-5./8.*pow(xi,3.)+15./16.*xi+.5); } else smooth= 1.; return smooth; } double DehnenBarPotentialRforce(double R,double z,double phi,double t, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; //declare double smooth; double r; //Get args double amp= *args++; double tform= *args++; double tsteady= *args++; double rb= *args++; double omegab= *args++; double barphi= *args++; //Calculate Rforce smooth= dehnenBarSmooth(t,tform,tsteady); r= sqrt( R * R + z * z ); if (r <= rb ) return -amp*smooth*cos(2.*(phi-omegab*t-barphi))*\ (pow(r/rb,3.)*R*(3.*R*R+2.*z*z)-4.*R*z*z)/pow(r,4.); else return -amp*smooth*cos(2.*(phi-omegab*t-barphi))\ *pow(rb/r,3.)*R/pow(r,4)*(3.*R*R-2.*z*z); } double DehnenBarPotentialPlanarRforce(double R,double phi,double t, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; //declare double smooth; //Get args double amp= *args++; double tform= *args++; double tsteady= *args++; double rb= *args++; double omegab= *args++; double barphi= *args++; //Calculate Rforce smooth= dehnenBarSmooth(t,tform,tsteady); if (R <= rb ) return -3.*amp*smooth*cos(2.*(phi-omegab*t-barphi))*pow(R/rb,3.)/R; else return -3.*amp*smooth*cos(2.*(phi-omegab*t-barphi))*pow(rb/R,3.)/R; } double DehnenBarPotentialphiforce(double R,double z,double phi,double t, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; //declare double smooth; double r, r2; //Get args double amp= *args++; double tform= *args++; double tsteady= *args++; double rb= *args++; double omegab= *args++; double barphi= *args++; //Calculate phiforce smooth= dehnenBarSmooth(t,tform,tsteady); r2= R * R + z * z; r= sqrt( r2 ); if ( R <= rb ) return 2.*amp*smooth*sin(2.*(phi-omegab*t-barphi))*(pow(r/rb,3.)-2.)\ *R*R/r2; else return -2.*amp*smooth*sin(2.*(phi-omegab*t-barphi))*pow(rb/r,3.)*R*R/r2; } double DehnenBarPotentialPlanarphiforce(double R,double phi,double t, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; //declare double smooth; //Get args double amp= *args++; double tform= *args++; double tsteady= *args++; double rb= *args++; double omegab= *args++; double barphi= *args++; //Calculate phiforce smooth= dehnenBarSmooth(t,tform,tsteady); if ( R <= rb ) return 2.*amp*smooth*sin(2.*(phi-omegab*t-barphi))*(pow(R/rb,3.)-2.); else return -2.*amp*smooth*sin(2.*(phi-omegab*t-barphi))*pow(rb/R,3.); } double DehnenBarPotentialzforce(double R,double z,double phi,double t, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; //declare double smooth; double r; //Get args double amp= *args++; double tform= *args++; double tsteady= *args++; double rb= *args++; double omegab= *args++; double barphi= *args++; //Calculate Rforce smooth= dehnenBarSmooth(t,tform,tsteady); r= sqrt( R * R + z * z ); if (r <= rb ) return -amp*smooth*cos(2.*(phi-omegab*t-barphi))*\ (pow(r/rb,3.)+4.)*R*R*z/pow(r,4.); else return -5.*amp*smooth*cos(2.*(phi-omegab*t-barphi))\ *pow(rb/r,3.)*R*R*z/pow(r,4); } double DehnenBarPotentialPlanarR2deriv(double R,double phi,double t, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; //declare double smooth; //Get args double amp= *args++; double tform= *args++; double tsteady= *args++; double rb= *args++; double omegab= *args++; double barphi= *args++; smooth= dehnenBarSmooth(t,tform,tsteady); if (R <= rb ) return 6.*amp*smooth*cos(2.*(phi-omegab*t-barphi))*pow(R/rb,3.)/R/R; else return -12.*amp*smooth*cos(2.*(phi-omegab*t-barphi))*pow(rb/R,3.)/R/R; } double DehnenBarPotentialPlanarphi2deriv(double R,double phi,double t, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; //declare double smooth; //Get args double amp= *args++; double tform= *args++; double tsteady= *args++; double rb= *args++; double omegab= *args++; double barphi= *args++; smooth= dehnenBarSmooth(t,tform,tsteady); if (R <= rb ) return -4.*amp*smooth*cos(2.*(phi-omegab*t-barphi))*(pow(R/rb,3.)-2.); else return 4.*amp*smooth*cos(2.*(phi-omegab*t-barphi))*pow(rb/R,3.); } double DehnenBarPotentialPlanarRphideriv(double R,double phi,double t, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; //declare double smooth; //Get args double amp= *args++; double tform= *args++; double tsteady= *args++; double rb= *args++; double omegab= *args++; double barphi= *args++; smooth= dehnenBarSmooth(t,tform,tsteady); if (R <= rb ) return -6.*amp*smooth*sin(2.*(phi-omegab*t-barphi))*pow(R/rb,3.)/R; else return -6.*amp*smooth*sin(2.*(phi-omegab*t-barphi))*pow(rb/R,3.)/R; } galpy-1.3.0/galpy/potential_src/potential_c_ext/DehnenSmoothWrapperPotential.c0000644000076500000240000000703413133144160030054 0ustar bovystaff00000000000000#include #include #include //DehnenSmoothWrapperPotential double dehnenSmooth(double t,double tform, double tsteady){ double smooth, xi,deltat; if ( t < tform ) smooth= 0.; else if ( t < tsteady ) { deltat= t-tform; xi= 2.*deltat/(tsteady-tform)-1.; smooth= (3./16.*pow(xi,5.)-5./8.*pow(xi,3.)+15./16.*xi+.5); } else smooth= 1.; return smooth; } double DehnenSmoothWrapperPotentialEval(double R,double z,double phi, double t, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; //Calculate potential, only used in actionAngle, so phi=0, t=0 return *args * dehnenSmooth(t,*(args+1),*(args+2)) \ * evaluatePotentials(R,z, potentialArgs->nwrapped, potentialArgs->wrappedPotentialArg); } double DehnenSmoothWrapperPotentialRforce(double R,double z,double phi, double t, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; //Calculate Rforce return *args * dehnenSmooth(t,*(args+1),*(args+2)) \ * calcRforce(R,z,phi,t, potentialArgs->nwrapped,potentialArgs->wrappedPotentialArg); } double DehnenSmoothWrapperPotentialphiforce(double R,double z,double phi, double t, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; //Calculate phiforce return *args * dehnenSmooth(t,*(args+1),*(args+2)) \ * calcPhiforce(R,z,phi,t, potentialArgs->nwrapped,potentialArgs->wrappedPotentialArg); } double DehnenSmoothWrapperPotentialzforce(double R,double z,double phi, double t, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; //Calculate zforce return *args * dehnenSmooth(t,*(args+1),*(args+2)) \ * calczforce(R,z,phi,t, potentialArgs->nwrapped,potentialArgs->wrappedPotentialArg); } double DehnenSmoothWrapperPotentialPlanarRforce(double R,double phi,double t, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; //Calculate Rforce return *args * dehnenSmooth(t,*(args+1),*(args+2)) \ * calcPlanarRforce(R,phi,t, potentialArgs->nwrapped, potentialArgs->wrappedPotentialArg); } double DehnenSmoothWrapperPotentialPlanarphiforce(double R,double phi,double t, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; //Calculate phiforce return *args * dehnenSmooth(t,*(args+1),*(args+2)) \ * calcPlanarphiforce(R,phi,t, potentialArgs->nwrapped, potentialArgs->wrappedPotentialArg); } double DehnenSmoothWrapperPotentialPlanarR2deriv(double R,double phi,double t, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; //Calculate R2deriv return *args * dehnenSmooth(t,*(args+1),*(args+2)) \ * calcPlanarR2deriv(R,phi,t, potentialArgs->nwrapped, potentialArgs->wrappedPotentialArg); } double DehnenSmoothWrapperPotentialPlanarphi2deriv(double R,double phi, double t, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; //Calculate phi2deriv return *args * dehnenSmooth(t,*(args+1),*(args+2)) \ * calcPlanarphi2deriv(R,phi,t, potentialArgs->nwrapped, potentialArgs->wrappedPotentialArg); } double DehnenSmoothWrapperPotentialPlanarRphideriv(double R,double phi, double t, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; //Calculate Rphideriv return *args * dehnenSmooth(t,*(args+1),*(args+2)) \ * calcPlanarRphideriv(R,phi,t, potentialArgs->nwrapped, potentialArgs->wrappedPotentialArg); } galpy-1.3.0/galpy/potential_src/potential_c_ext/DiskSCFPotential.c0000644000076500000240000001264613031266224025356 0ustar bovystaff00000000000000/////////////////////////////////////////////////////////////////////////////// // // DiskSCFPotential.c: // // The C implementation of DiskSCFPotential uses the fact that // (a) the SCF part of the potential can be gotten directly from the SCF // implementation // (b) the approximation part can be written as a sum over pairs // [Sigma_i(R),h_i(z)] // Thus, this file only implements the potential and forces coming from // a single of the approximation pairs; the entire potential is obtained // by summing all of these (by treating them as separate instances of the // potential) and adding the SCF; this is all handled through the parsing // of the potential in the Python code. // // Surface-density profile is passed by type: // // 0= exponential: amp x exp(-R/h) // 1= exponential w/ hole: amp x exp(-Rhole/R-R/h) // // Vertical profile is passed by type: // // 0= exponential: exp(-|z|/h)/[2h] // 1= sech2: sech^2(z/[2h])/[4h] // /////////////////////////////////////////////////////////////////////////////// #include #include #ifndef M_LN2 #define M_LN2 0.693147180559945309417232121 #endif //DiskSCFPotential //Only the part coming from a single approximation pair // Arguments: nsigma_args,sigma_type,sigma_amp,sigma_h[,sigma_rhole], // hz_type,hz_h double Sigma(double R,double * Sigma_args){ int Sigma_type= (int) *Sigma_args; switch ( Sigma_type ) { case 0: // Pure exponential return *(Sigma_args+1) * exp(-R / *(Sigma_args+2) ); case 1: // Exponential with central hole return *(Sigma_args+1) * exp(- *(Sigma_args+3) / R - R / *(Sigma_args+2) ); } return -1; // LCOV_EXCL_LINE } double dSigmadR(double R,double * Sigma_args){ int Sigma_type= (int) *Sigma_args; switch ( Sigma_type ) { case 0: // Pure exponential return - *(Sigma_args+1) * exp(-R / *(Sigma_args+2) ) / *(Sigma_args+2); case 1: // Exponential with central hole return *(Sigma_args+1) * ( *(Sigma_args+3) / R / R - 1. / *(Sigma_args+2))\ * exp(- *(Sigma_args+3) / R - R / *(Sigma_args+2) ); } return -1; // LCOV_EXCL_LINE } //LCOV_EXCL_START // Not currently used, bc only in 2nd derivatives double d2SigmadR2(double R,double * Sigma_args){ int Sigma_type= (int) *Sigma_args; switch ( Sigma_type ) { case 0: // Pure exponential return *(Sigma_args+1) * exp(-R / *(Sigma_args+2) ) \ / *(Sigma_args+2) / *(Sigma_args+2); case 1: // Exponential with central hole return *(Sigma_args+1) * ( pow( *(Sigma_args+3) / R / R \ - 1. / *(Sigma_args+2) , 2 ) \ -2. * *(Sigma_args+3) / R / R / R )\ * exp(- *(Sigma_args+3) / R - R / *(Sigma_args+2) ); } return -1; // LCOV_EXCL_LINE } double hz(double z,double * hz_args){ int hz_type= (int) *hz_args; double fz; switch ( hz_type ) { case 0: // exponential fz= fabs(z); return 0.5 * exp ( - fz / *(hz_args+1) ) / *(hz_args+1); case 1: // sech2 return 0.25 * pow ( cosh ( 0.5 * z / *(hz_args+1) ) , -2 ) \ / *(hz_args+1); } return -1; // LCOV_EXCL_LINE } //LCOV_EXCL_STOP double Hz(double z,double * hz_args){ int hz_type= (int) *hz_args; double fz= fabs(z); switch ( hz_type ) { case 0: // exponential return 0.5 * ( exp ( - fz / *(hz_args+1) ) - 1. + fz / *(hz_args+1) ) \ * *(hz_args+1); case 1: // sech2 return *(hz_args+1) * ( log ( 1. + exp ( - fz / *(hz_args+1) ) ) \ + 0.5 * fz / *(hz_args+1) - M_LN2 ); } return -1; // LCOV_EXCL_LINE } double dHzdz(double z,double * hz_args){ int hz_type= (int) *hz_args; double fz; switch ( hz_type ) { case 0: // exponential fz= fabs(z); return 0.5 * copysign ( 1. - exp ( - fz / *(hz_args+1) ) , z); case 1: // sech2 return 0.5 * tanh ( 0.5 * z / *(hz_args+1) ); } return -1; // LCOV_EXCL_LINE } double DiskSCFPotentialEval(double R,double Z, double phi, double t, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; //Get args int nsigma_args= (int) *args; double * Sigma_args= args+1; double * hz_args= args+1+nsigma_args; //Calculate Rforce double r= sqrt( R * R + Z * Z ); return Sigma(r,Sigma_args) * Hz(Z,hz_args); } double DiskSCFPotentialRforce(double R,double Z, double phi, double t, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; //Get args int nsigma_args= (int) *args; double * Sigma_args= args+1; double * hz_args= args+1+nsigma_args; //Calculate Rforce double r= sqrt( R * R + Z * Z ); return -dSigmadR(r,Sigma_args) * Hz(Z,hz_args) * R / r; } double DiskSCFPotentialPlanarRforce(double R,double phi, double t, struct potentialArg * potentialArgs){ //Supposed to be zero (bc H(0) supposed to be zero), but just to make sure double * args= potentialArgs->args; //Get args int nsigma_args= (int) *args; double * Sigma_args= args+1; double * hz_args= args+1+nsigma_args; //Calculate Rforce return -dSigmadR(R,Sigma_args) * Hz(0.,hz_args); } double DiskSCFPotentialzforce(double R,double Z, double phi, double t, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; //Get args int nsigma_args= (int) *args; double * Sigma_args= args+1; double * hz_args= args+1+nsigma_args; //Calculate Rforce double r= sqrt( R * R + Z * Z ); return -dSigmadR(r,Sigma_args) * Hz(Z,hz_args) * Z / r \ -Sigma(r,Sigma_args) * dHzdz(Z,hz_args); } galpy-1.3.0/galpy/potential_src/potential_c_ext/DoubleExponentialDiskPotential.c0000644000076500000240000001273413030761151030360 0ustar bovystaff00000000000000#include #include #include #ifndef M_PI #define M_PI 3.14159265358979323846 #endif //Double exponential disk potential double DoubleExponentialDiskPotentialEval(double R,double z, double phi, double t, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; double amp, alpha; int nzeros, glorder; if ( R > 6. ) { //Approximate as Keplerian nzeros= (int) *(args+4); glorder= (int) *(args+5); amp= *(args + 6 + 2 * glorder + 4 * (nzeros + 1)); alpha= *(args + 7 + 2 * glorder + 4 * (nzeros + 1)); return - *args * amp * pow(R*R+z*z,1.-0.5*alpha) / (alpha - 2.); } //Get args amp= *args++; alpha= *args++; double beta= *args++; double kmaxFac= *args++; double kmax= kmaxFac * beta; nzeros= (int) *args++; glorder= (int) *args++; double * glx= args; double * glw= args + glorder; double * j0zeros= args + 2 * glorder; double * dj0zeros= args + 2 * glorder + nzeros + 1; //Calculate potential double out= 0.; double k; int ii, jj; if ( R < 1. ) kmax= kmax/R; for (ii=0; ii < ( nzeros + 1 ); ii++) { for (jj=0; jj < glorder; jj++) { k= 0.5 * ( *(glx+jj) + 1. ) * *(dj0zeros+ii+1) + *(j0zeros+ii); out+= *(glw+jj) * *(dj0zeros+ii+1) * gsl_sf_bessel_J0(k*R) * pow(alpha * alpha + k * k,-1.5) * (beta * exp(-k * fabs(z) ) - k * exp(-beta * fabs(z) )) / (beta * beta - k * k); } if ( k > kmax ) break; } return - amp * 2 * M_PI * alpha * out; } double DoubleExponentialDiskPotentialRforce(double R,double z, double phi, double t, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; double amp, alpha; int nzeros,glorder; if ( R > 6. ) { //Approximate as Keplerian nzeros= (int) *(args+4); glorder= (int) *(args+5); amp= *(args + 6 + 2 * glorder + 4 * (nzeros + 1)); alpha= *(args + 7 + 2 * glorder + 4 * (nzeros + 1)); return - *args * amp * R * pow(R*R+z*z,-0.5*alpha); } //Get args amp= *args++; alpha= *args++; double beta= *args++; double kmaxFac= *args++; double kmax= 2. * kmaxFac * beta; nzeros= (int) *args++; glorder= (int) *args++; double * glx= args; double * glw= args + glorder; double * j1zeros= args + 2 * glorder + 2 * (nzeros + 1); double * dj1zeros= args + 2 * glorder + 3 * (nzeros + 1); //Calculate potential double out= 0.; double k; int ii, jj; if ( R < 1. ) kmax= kmax/R; for (ii=0; ii < ( nzeros + 1 ); ii++) { for (jj=0; jj < glorder; jj++) { k= 0.5 * ( *(glx+jj) + 1. ) * *(dj1zeros+ii+1) + *(j1zeros+ii); out+= *(glw+jj) * *(dj1zeros+ii+1) * k * gsl_sf_bessel_J1(k*R) * pow(alpha * alpha + k * k,-1.5) * (beta * exp(-k * fabs(z) ) - k * exp(-beta * fabs(z) )) / (beta * beta - k * k); } if ( k > kmax ) break; } return - amp * 2 * M_PI * alpha * out; } double DoubleExponentialDiskPotentialPlanarRforce(double R,double phi, double t, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; double amp, alpha; int nzeros, glorder; if ( R > 6. ) { //Approximate as Keplerian nzeros= (int) *(args+4); glorder= (int) *(args+5); amp= *(args + 6 + 2 * glorder + 4 * (nzeros + 1)); alpha= *(args + 7 + 2 * glorder + 4 * (nzeros + 1)); return - *args * amp * pow(R,-alpha + 1.); } //Get args amp= *args++; alpha= *args++; double beta= *args++; double kmaxFac= *args++; double kmax= 2. * kmaxFac * beta; nzeros= (int) *args++; glorder= (int) *args++; double * glx= args; double * glw= args + glorder; double * j1zeros= args + 2 * glorder + 2 * (nzeros + 1); double * dj1zeros= args + 2 * glorder + 3 * (nzeros + 1); //Calculate potential double out= 0.; double k; int ii, jj; if ( R < 1. ) kmax= kmax/R; for (ii=0; ii < ( nzeros + 1 ); ii++) { for (jj=0; jj < glorder; jj++) { k= 0.5 * ( *(glx+jj) + 1. ) * *(dj1zeros+ii+1) + *(j1zeros+ii); out+= *(glw+jj) * *(dj1zeros+ii+1) * k * gsl_sf_bessel_J1(k*R) * pow(alpha * alpha + k * k,-1.5) / (beta + k); } if ( k > kmax ) break; } return - amp * 2 * M_PI * alpha * out; } double DoubleExponentialDiskPotentialzforce(double R,double z,double phi, double t, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; double amp, alpha; int nzeros, glorder; if ( R > 6. ) { //Approximate as Keplerian nzeros= (int) *(args+4); glorder= (int) *(args+5); amp= *(args + 6 + 2 * glorder + 4 * (nzeros + 1)); alpha= *(args + 7 + 2 * glorder + 4 * (nzeros + 1)); return - *args * amp * z * pow(R*R+z*z,-0.5*alpha); } //Get args amp= *args++; alpha= *args++; double beta= *args++; double kmaxFac= *args++; double kmax= kmaxFac * beta; nzeros= (int) *args++; glorder= (int) *args++; double * glx= args; double * glw= args + glorder; double * j0zeros= args + 2 * glorder; double * dj0zeros= args + 2 * glorder + nzeros + 1; //Calculate potential double out= 0.; double k; int ii, jj; if ( R < 1. ) kmax= kmax/R; for (ii=0; ii < ( nzeros + 1 ); ii++) { for (jj=0; jj < glorder; jj++) { k= 0.5 * ( *(glx+jj) + 1. ) * *(dj0zeros+ii+1) + *(j0zeros+ii); out+= *(glw+jj) * *(dj0zeros+ii+1) * k * gsl_sf_bessel_J0(k*R) * pow(alpha * alpha + k * k,-1.5) * (exp(-k * fabs(z) ) - exp(-beta * fabs(z) )) / (beta * beta - k * k); } if ( k > kmax ) break; } if ( z > 0. ) return - amp * 2 * M_PI * alpha * beta * out; else return amp * 2 * M_PI * alpha * beta * out; } galpy-1.3.0/galpy/potential_src/potential_c_ext/EllipticalDiskPotential.c0000644000076500000240000000566513047413076027036 0ustar bovystaff00000000000000#include #include //EllipticalDiskPotential // double EllipticalDiskSmooth(double t,double tform, double tsteady){ double smooth, xi,deltat; //Slightly different smoothing if ( ! isnan(tform) ) if ( t < tform ) smooth= 0.; else if ( t < tsteady ) { deltat= t-tform; xi= 2.*deltat/(tsteady-tform)-1.; smooth= (3./16.*pow(xi,5.)-5./8.*pow(xi,3.)+15./16.*xi+.5); } else smooth= 1.; else smooth=1.; return smooth; } double EllipticalDiskPotentialRforce(double R,double phi,double t, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; //declare double smooth; //Get args double amp= *args++; double tform= *args++; double tsteady= *args++; double twophio= *args++; double p= *args++; double phib= *args; //Calculate Rforce smooth= EllipticalDiskSmooth(t,tform,tsteady); return -amp * smooth * p * twophio / 2. * pow(R,p-1.) * cos( 2. * (phi - phib)); } double EllipticalDiskPotentialphiforce(double R,double phi,double t, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; //declare double smooth; //Get args double amp= *args++; double tform= *args++; double tsteady= *args++; double twophio= *args++; double p= *args++; double phib= *args; //Calculate phiforce smooth= EllipticalDiskSmooth(t,tform,tsteady); return amp * smooth * twophio * pow(R,p) * sin( 2. * (phi-phib)); } double EllipticalDiskPotentialR2deriv(double R,double phi,double t, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; //declare double smooth; //Get args double amp= *args++; double tform= *args++; double tsteady= *args++; double twophio= *args++; double p= *args++; double phib= *args; //Calculate Rforce smooth= EllipticalDiskSmooth(t,tform,tsteady); return amp * smooth * p * ( p - 1) * twophio / 2. * pow(R,p-2.) * cos( 2. * ( phi - phib ) ); } double EllipticalDiskPotentialphi2deriv(double R,double phi,double t, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; //declare double smooth; //Get args double amp= *args++; double tform= *args++; double tsteady= *args++; double twophio= *args++; double p= *args++; double phib= *args; //Calculate Rforce smooth= EllipticalDiskSmooth(t,tform,tsteady); return - 2. * amp * smooth * twophio * pow(R,p) * cos( 2. * ( phi - phib ) ); } double EllipticalDiskPotentialRphideriv(double R,double phi,double t, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; //declare double smooth; //Get args double amp= *args++; double tform= *args++; double tsteady= *args++; double twophio= *args++; double p= *args++; double phib= *args; //Calculate Rforce smooth= EllipticalDiskSmooth(t,tform,tsteady); return - amp * smooth * p * twophio * pow(R,p-1.) * sin( 2. * ( phi - phib ) ); } galpy-1.3.0/galpy/potential_src/potential_c_ext/FlattenedPowerPotential.c0000644000076500000240000000463212671130470027051 0ustar bovystaff00000000000000#include #include //FlattenedPowerPotential //4 arguments: amp, alpha, q^2, and core^2 double FlattenedPowerPotentialEval(double R,double Z, double phi, double t, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; //Get args double amp= *args; double alpha= *(args+1); double q2= *(args+2); double core2= *(args+3); double m2; //Calculate potential if ( alpha == 0. ) return 0.5 * amp * log(R*R+Z*Z/q2+core2); else { m2= core2+R*R+Z*Z/q2; return - amp * pow(m2,-0.5 * alpha) / alpha; } } double FlattenedPowerPotentialRforce(double R,double Z, double phi, double t, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; //Get args double amp= *args; double alpha= *(args+1); double q2= *(args+2); double core2= *(args+3); double m2; //Calculate potential if ( alpha == 0. ) return - amp * R/(R*R+Z*Z/q2+core2); else { m2= core2+R*R+Z*Z/q2; return - amp * pow(m2,-0.5 * alpha-1.) * R; } } double FlattenedPowerPotentialPlanarRforce(double R,double phi, double t, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; //Get args double amp= *args; double alpha= *(args+1); double core2= *(args+2); double m2; //Calculate potential if ( alpha == 0. ) return - amp * R/(R*R+core2); else { m2= core2+R*R; return - amp * pow(m2,-0.5 * alpha - 1.) * R; } } double FlattenedPowerPotentialzforce(double R,double Z, double phi, double t, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; //Get args double amp= *args; double alpha= *(args+1); double q2= *(args+2); double core2= *(args+3); double m2; //Calculate potential if ( alpha == 0. ) return -amp * Z/q2/(R*R+Z*Z/q2+core2); else { m2= core2+R*R+Z*Z/q2; return - amp * pow(m2,-0.5 * alpha - 1.) * Z / q2; } } double FlattenedPowerPotentialPlanarR2deriv(double R,double phi, double t, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; //Get args double amp= *args; double alpha= *(args+1); double core2= *(args+2); double m2; //Calculate potential if ( alpha == 0. ) return amp * (1.- 2.*R*R/(R*R+core2))/(R*R+core2); else { m2= core2+R*R; return - amp * pow(m2,-0.5 * alpha - 1.) * ( (alpha + 1.) * R*R/m2 -1.); } } galpy-1.3.0/galpy/potential_src/potential_c_ext/galpy_potentials.c0000644000076500000240000001330513200440334025611 0ustar bovystaff00000000000000#include void cyl_to_rect(double R, double phi,double *x, double *y){ *x= R * cos ( phi ); *y= R * sin ( phi ); } void init_potentialArgs(int npot, struct potentialArg * potentialArgs){ int ii; for (ii=0; ii < npot; ii++) { (potentialArgs+ii)->i2d= NULL; (potentialArgs+ii)->accx= NULL; (potentialArgs+ii)->accy= NULL; (potentialArgs+ii)->i2drforce= NULL; (potentialArgs+ii)->accxrforce= NULL; (potentialArgs+ii)->accyrforce= NULL; (potentialArgs+ii)->i2dzforce= NULL; (potentialArgs+ii)->accxzforce= NULL; (potentialArgs+ii)->accyzforce= NULL; (potentialArgs+ii)->wrappedPotentialArg= NULL; } } void free_potentialArgs(int npot, struct potentialArg * potentialArgs){ int ii; for (ii=0; ii < npot; ii++) { if ( (potentialArgs+ii)->i2d ) interp_2d_free((potentialArgs+ii)->i2d) ; if ( (potentialArgs+ii)->accx ) gsl_interp_accel_free ((potentialArgs+ii)->accx); if ( (potentialArgs+ii)->accy ) gsl_interp_accel_free ((potentialArgs+ii)->accy); if ( (potentialArgs+ii)->i2drforce ) interp_2d_free((potentialArgs+ii)->i2drforce) ; if ( (potentialArgs+ii)->accxrforce ) gsl_interp_accel_free ((potentialArgs+ii)->accxrforce); if ( (potentialArgs+ii)->accyrforce ) gsl_interp_accel_free ((potentialArgs+ii)->accyrforce); if ( (potentialArgs+ii)->i2dzforce ) interp_2d_free((potentialArgs+ii)->i2dzforce) ; if ( (potentialArgs+ii)->accxzforce ) gsl_interp_accel_free ((potentialArgs+ii)->accxzforce); if ( (potentialArgs+ii)->accyzforce ) gsl_interp_accel_free ((potentialArgs+ii)->accyzforce); if ( (potentialArgs+ii)->wrappedPotentialArg ) { free_potentialArgs((potentialArgs+ii)->nwrapped, (potentialArgs+ii)->wrappedPotentialArg); free((potentialArgs+ii)->wrappedPotentialArg); } free((potentialArgs+ii)->args); } } double evaluatePotentials(double R, double Z, int nargs, struct potentialArg * potentialArgs){ int ii; double pot= 0.; for (ii=0; ii < nargs; ii++){ pot+= potentialArgs->potentialEval(R,Z,0.,0., potentialArgs); potentialArgs++; } potentialArgs-= nargs; return pot; } double calcRforce(double R, double Z, double phi, double t, int nargs, struct potentialArg * potentialArgs){ int ii; double Rforce= 0.; for (ii=0; ii < nargs; ii++){ Rforce+= potentialArgs->Rforce(R,Z,phi,t, potentialArgs); potentialArgs++; } potentialArgs-= nargs; return Rforce; } double calczforce(double R, double Z, double phi, double t, int nargs, struct potentialArg * potentialArgs){ int ii; double zforce= 0.; for (ii=0; ii < nargs; ii++){ zforce+= potentialArgs->zforce(R,Z,phi,t, potentialArgs); potentialArgs++; } potentialArgs-= nargs; return zforce; } double calcPhiforce(double R, double Z, double phi, double t, int nargs, struct potentialArg * potentialArgs){ int ii; double phiforce= 0.; for (ii=0; ii < nargs; ii++){ phiforce+= potentialArgs->phiforce(R,Z,phi,t, potentialArgs); potentialArgs++; } potentialArgs-= nargs; return phiforce; } double calcPlanarRforce(double R, double phi, double t, int nargs, struct potentialArg * potentialArgs){ int ii; double Rforce= 0.; for (ii=0; ii < nargs; ii++){ Rforce+= potentialArgs->planarRforce(R,phi,t, potentialArgs); potentialArgs++; } potentialArgs-= nargs; return Rforce; } double calcPlanarphiforce(double R, double phi, double t, int nargs, struct potentialArg * potentialArgs){ int ii; double phiforce= 0.; for (ii=0; ii < nargs; ii++){ phiforce+= potentialArgs->planarphiforce(R,phi,t, potentialArgs); potentialArgs++; } potentialArgs-= nargs; return phiforce; } // LCOV_EXCL_START double calcR2deriv(double R, double Z, double phi, double t, int nargs, struct potentialArg * potentialArgs){ int ii; double R2deriv= 0.; for (ii=0; ii < nargs; ii++){ R2deriv+= potentialArgs->R2deriv(R,Z,phi,t, potentialArgs); potentialArgs++; } potentialArgs-= nargs; return R2deriv; } double calcphi2deriv(double R, double Z, double phi, double t, int nargs, struct potentialArg * potentialArgs){ int ii; double phi2deriv= 0.; for (ii=0; ii < nargs; ii++){ phi2deriv+= potentialArgs->phi2deriv(R,Z,phi,t, potentialArgs); potentialArgs++; } potentialArgs-= nargs; return phi2deriv; } double calcRphideriv(double R, double Z, double phi, double t, int nargs, struct potentialArg * potentialArgs){ int ii; double Rphideriv= 0.; for (ii=0; ii < nargs; ii++){ Rphideriv+= potentialArgs->Rphideriv(R,Z,phi,t, potentialArgs); potentialArgs++; } potentialArgs-= nargs; return Rphideriv; } // LCOV_EXCL_STOP double calcPlanarR2deriv(double R, double phi, double t, int nargs, struct potentialArg * potentialArgs){ int ii; double R2deriv= 0.; for (ii=0; ii < nargs; ii++){ R2deriv+= potentialArgs->planarR2deriv(R,phi,t, potentialArgs); potentialArgs++; } potentialArgs-= nargs; return R2deriv; } double calcPlanarphi2deriv(double R, double phi, double t, int nargs, struct potentialArg * potentialArgs){ int ii; double phi2deriv= 0.; for (ii=0; ii < nargs; ii++){ phi2deriv+= potentialArgs->planarphi2deriv(R,phi,t, potentialArgs); potentialArgs++; } potentialArgs-= nargs; return phi2deriv; } double calcPlanarRphideriv(double R, double phi, double t, int nargs, struct potentialArg * potentialArgs){ int ii; double Rphideriv= 0.; for (ii=0; ii < nargs; ii++){ Rphideriv+= potentialArgs->planarRphideriv(R,phi,t, potentialArgs); potentialArgs++; } potentialArgs-= nargs; return Rphideriv; } galpy-1.3.0/galpy/potential_src/potential_c_ext/galpy_potentials.h0000644000076500000240000005306613236350147025641 0ustar bovystaff00000000000000/* C implementations of galpy potentials */ /* Structure declarations */ #ifndef __GALPY_POTENTIALS_H__ #define __GALPY_POTENTIALS_H__ #ifdef __cplusplus extern "C" { #endif #include struct potentialArg{ double (*potentialEval)(double R, double Z, double phi, double t, struct potentialArg *); double (*Rforce)(double R, double Z, double phi, double t, struct potentialArg *); double (*zforce)(double R, double Z, double phi, double t, struct potentialArg *); double (*phiforce)(double R, double Z, double phi, double t, struct potentialArg *); double (*planarRforce)(double R,double phi, double t, struct potentialArg *); double (*planarphiforce)(double R,double phi, double t, struct potentialArg *); double (*R2deriv)(double R,double Z,double phi, double t, struct potentialArg *); double (*phi2deriv)(double R,double Z,double phi, double t, struct potentialArg *); double (*Rphideriv)(double R,double Z,double phi, double t, struct potentialArg *); double (*planarR2deriv)(double R,double phi, double t, struct potentialArg *); double (*planarphi2deriv)(double R,double phi, double t, struct potentialArg *); double (*planarRphideriv)(double R,double phi, double t, struct potentialArg *); int nargs; double * args; interp_2d * i2d; gsl_interp_accel * accx; gsl_interp_accel * accy; interp_2d * i2drforce; gsl_interp_accel * accxrforce; gsl_interp_accel * accyrforce; interp_2d * i2dzforce; gsl_interp_accel * accxzforce; gsl_interp_accel * accyzforce; int nwrapped; // For wrappers struct potentialArg * wrappedPotentialArg; }; /* Function declarations */ //Utility void cyl_to_rect(double,double,double *,double *); //Dealing with potentialArg void init_potentialArgs(int,struct potentialArg *); void free_potentialArgs(int,struct potentialArg *); //Potential and force evaluation double evaluatePotentials(double,double,int, struct potentialArg *); double calcRforce(double,double,double,double,int,struct potentialArg *); double calczforce(double,double,double,double,int,struct potentialArg *); double calcPhiforce(double, double,double, double, int, struct potentialArg *); double calcR2deriv(double, double, double,double, int, struct potentialArg *); double calcphi2deriv(double, double, double,double, int, struct potentialArg *); double calcRphideriv(double, double, double,double, int, struct potentialArg *); double calcPlanarRforce(double, double, double, int, struct potentialArg *); double calcPlanarphiforce(double, double, double, int, struct potentialArg *); double calcPlanarR2deriv(double, double, double, int, struct potentialArg *); double calcPlanarphi2deriv(double, double, double, int, struct potentialArg *); double calcPlanarRphideriv(double, double, double, int, struct potentialArg *); //ZeroForce double ZeroPlanarForce(double,double,double, struct potentialArg *); double ZeroForce(double,double,double,double, struct potentialArg *); //LogarithmicHaloPotential double LogarithmicHaloPotentialEval(double ,double , double, double, struct potentialArg *); double LogarithmicHaloPotentialRforce(double ,double , double, double, struct potentialArg *); double LogarithmicHaloPotentialPlanarRforce(double ,double, double, struct potentialArg *); double LogarithmicHaloPotentialzforce(double,double,double,double, struct potentialArg *); double LogarithmicHaloPotentialphiforce(double,double,double,double, struct potentialArg *); double LogarithmicHaloPotentialPlanarphiforce(double,double,double, struct potentialArg *); double LogarithmicHaloPotentialPlanarR2deriv(double ,double, double, struct potentialArg *); double LogarithmicHaloPotentialPlanarphi2deriv(double ,double, double, struct potentialArg *); double LogarithmicHaloPotentialPlanarRphideriv(double ,double, double, struct potentialArg *); //DehnenBarPotential double DehnenBarPotentialRforce(double,double,double,double, struct potentialArg *); double DehnenBarPotentialphiforce(double,double,double,double, struct potentialArg *); double DehnenBarPotentialzforce(double,double,double,double, struct potentialArg *); double DehnenBarPotentialPlanarRforce(double,double,double, struct potentialArg *); double DehnenBarPotentialPlanarphiforce(double,double,double, struct potentialArg *); double DehnenBarPotentialPlanarR2deriv(double,double,double, struct potentialArg *); double DehnenBarPotentialPlanarphi2deriv(double,double,double, struct potentialArg *); double DehnenBarPotentialPlanarRphideriv(double,double,double, struct potentialArg *); //TransientLogSpiralPotential double TransientLogSpiralPotentialRforce(double,double,double, struct potentialArg *); double TransientLogSpiralPotentialphiforce(double,double,double, struct potentialArg *); //SteadyLogSpiralPotential double SteadyLogSpiralPotentialRforce(double,double,double, struct potentialArg *); double SteadyLogSpiralPotentialphiforce(double,double,double, struct potentialArg *); //EllipticalDiskPotential double EllipticalDiskPotentialRforce(double,double,double, struct potentialArg *); double EllipticalDiskPotentialphiforce(double,double,double, struct potentialArg *); double EllipticalDiskPotentialR2deriv(double,double,double, struct potentialArg *); double EllipticalDiskPotentialphi2deriv(double,double,double, struct potentialArg *); double EllipticalDiskPotentialRphideriv(double,double,double, struct potentialArg *); //Miyamoto-Nagai Potential double MiyamotoNagaiPotentialEval(double ,double , double, double, struct potentialArg *); double MiyamotoNagaiPotentialRforce(double ,double , double, double, struct potentialArg *); double MiyamotoNagaiPotentialPlanarRforce(double ,double, double, struct potentialArg *); double MiyamotoNagaiPotentialzforce(double,double,double,double, struct potentialArg *); double MiyamotoNagaiPotentialPlanarR2deriv(double ,double, double, struct potentialArg *); //LopsidedDiskPotential double LopsidedDiskPotentialRforce(double,double,double, struct potentialArg *); double LopsidedDiskPotentialphiforce(double,double,double, struct potentialArg *); double LopsidedDiskPotentialR2deriv(double,double,double, struct potentialArg *); double LopsidedDiskPotentialphi2deriv(double,double,double, struct potentialArg *); double LopsidedDiskPotentialRphideriv(double,double,double, struct potentialArg *); //PowerSphericalPotential double PowerSphericalPotentialEval(double ,double , double, double, struct potentialArg *); double PowerSphericalPotentialRforce(double ,double , double, double, struct potentialArg *); double PowerSphericalPotentialPlanarRforce(double ,double, double, struct potentialArg *); double PowerSphericalPotentialzforce(double,double,double,double, struct potentialArg *); double PowerSphericalPotentialPlanarR2deriv(double ,double, double, struct potentialArg *); //HernquistPotential double HernquistPotentialEval(double ,double , double, double, struct potentialArg *); double HernquistPotentialRforce(double ,double , double, double, struct potentialArg *); double HernquistPotentialPlanarRforce(double ,double, double, struct potentialArg *); double HernquistPotentialzforce(double,double,double,double, struct potentialArg *); double HernquistPotentialPlanarR2deriv(double ,double, double, struct potentialArg *); //NFWPotential double NFWPotentialEval(double ,double , double, double, struct potentialArg *); double NFWPotentialRforce(double ,double , double, double, struct potentialArg *); double NFWPotentialPlanarRforce(double ,double, double, struct potentialArg *); double NFWPotentialzforce(double,double,double,double, struct potentialArg *); double NFWPotentialPlanarR2deriv(double ,double, double, struct potentialArg *); //JaffePotential double JaffePotentialEval(double ,double , double, double, struct potentialArg *); double JaffePotentialRforce(double ,double , double, double, struct potentialArg *); double JaffePotentialPlanarRforce(double ,double, double, struct potentialArg *); double JaffePotentialzforce(double,double,double,double, struct potentialArg *); double JaffePotentialPlanarR2deriv(double ,double, double, struct potentialArg *); //DoubleExponentialDiskPotential double DoubleExponentialDiskPotentialEval(double ,double , double, double, struct potentialArg *); double DoubleExponentialDiskPotentialRforce(double,double, double,double, struct potentialArg *); double DoubleExponentialDiskPotentialPlanarRforce(double,double,double, struct potentialArg *); double DoubleExponentialDiskPotentialzforce(double,double, double,double, struct potentialArg *); //FlattenedPowerPotential double FlattenedPowerPotentialEval(double,double,double,double, struct potentialArg *); double FlattenedPowerPotentialRforce(double,double,double,double, struct potentialArg *); double FlattenedPowerPotentialPlanarRforce(double,double,double, struct potentialArg *); double FlattenedPowerPotentialzforce(double,double,double,double, struct potentialArg *); double FlattenedPowerPotentialPlanarR2deriv(double,double,double, struct potentialArg *); //interpRZPotential double interpRZPotentialEval(double ,double , double, double, struct potentialArg *); double interpRZPotentialRforce(double ,double , double, double, struct potentialArg *); double interpRZPotentialzforce(double ,double , double, double, struct potentialArg *); //IsochronePotential double IsochronePotentialEval(double ,double , double, double, struct potentialArg *); double IsochronePotentialRforce(double ,double , double, double, struct potentialArg *); double IsochronePotentialPlanarRforce(double ,double, double, struct potentialArg *); double IsochronePotentialzforce(double,double,double,double, struct potentialArg *); double IsochronePotentialPlanarR2deriv(double ,double, double, struct potentialArg *); //PowerSphericalPotentialwCutoff double PowerSphericalPotentialwCutoffEval(double ,double , double, double, struct potentialArg *); double PowerSphericalPotentialwCutoffRforce(double ,double , double, double, struct potentialArg *); double PowerSphericalPotentialwCutoffPlanarRforce(double ,double, double, struct potentialArg *); double PowerSphericalPotentialwCutoffzforce(double,double,double,double, struct potentialArg *); double PowerSphericalPotentialwCutoffPlanarR2deriv(double ,double, double, struct potentialArg *); //KuzminKutuzovStaeckelPotential double KuzminKutuzovStaeckelPotentialEval(double,double,double,double, struct potentialArg *); double KuzminKutuzovStaeckelPotentialRforce(double,double,double,double, struct potentialArg *); double KuzminKutuzovStaeckelPotentialPlanarRforce(double,double,double, struct potentialArg *); double KuzminKutuzovStaeckelPotentialzforce(double,double,double,double, struct potentialArg *); double KuzminKutuzovStaeckelPotentialPlanarR2deriv(double,double,double, struct potentialArg *); //KuzminDiskPotential double KuzminDiskPotentialEval(double,double,double,double, struct potentialArg *); double KuzminDiskPotentialRforce(double,double,double,double, struct potentialArg *); double KuzminDiskPotentialPlanarRforce(double,double,double, struct potentialArg *); double KuzminDiskPotentialzforce(double,double,double,double, struct potentialArg *); double KuzminDiskPotentialPlanarR2deriv(double,double,double, struct potentialArg *); //PlummerPotential double PlummerPotentialEval(double,double,double,double, struct potentialArg *); double PlummerPotentialRforce(double,double,double,double, struct potentialArg *); double PlummerPotentialPlanarRforce(double,double,double, struct potentialArg *); double PlummerPotentialzforce(double,double,double,double, struct potentialArg *); double PlummerPotentialPlanarR2deriv(double,double,double, struct potentialArg *); //PseudoIsothermalPotential double PseudoIsothermalPotentialEval(double,double,double,double, struct potentialArg *); double PseudoIsothermalPotentialRforce(double,double,double,double, struct potentialArg *); double PseudoIsothermalPotentialPlanarRforce(double,double,double, struct potentialArg *); double PseudoIsothermalPotentialzforce(double,double,double,double, struct potentialArg *); double PseudoIsothermalPotentialPlanarR2deriv(double,double,double, struct potentialArg *); //BurkertPotential double BurkertPotentialEval(double,double,double,double, struct potentialArg *); double BurkertPotentialRforce(double,double,double,double, struct potentialArg *); double BurkertPotentialPlanarRforce(double,double,double, struct potentialArg *); double BurkertPotentialzforce(double,double,double,double, struct potentialArg *); double BurkertPotentialPlanarR2deriv(double,double,double, struct potentialArg *); //TriaxialHernquistPotential double TriaxialHernquistPotentialEval(double,double,double,double, struct potentialArg *); double TriaxialHernquistPotentialRforce(double,double,double,double, struct potentialArg *); double TriaxialHernquistPotentialPlanarRforce(double,double,double, struct potentialArg *); double TriaxialHernquistPotentialphiforce(double,double,double,double, struct potentialArg *); double TriaxialHernquistPotentialPlanarphiforce(double,double,double, struct potentialArg *); double TriaxialHernquistPotentialzforce(double,double,double,double, struct potentialArg *); //TriaxialNFWPotential double TriaxialNFWPotentialEval(double,double,double,double, struct potentialArg *); double TriaxialNFWPotentialRforce(double,double,double,double, struct potentialArg *); double TriaxialNFWPotentialPlanarRforce(double,double,double, struct potentialArg *); double TriaxialNFWPotentialphiforce(double,double,double,double, struct potentialArg *); double TriaxialNFWPotentialPlanarphiforce(double,double,double, struct potentialArg *); double TriaxialNFWPotentialzforce(double,double,double,double, struct potentialArg *); //TriaxialJaffePotential double TriaxialJaffePotentialEval(double,double,double,double, struct potentialArg *); double TriaxialJaffePotentialRforce(double,double,double,double, struct potentialArg *); double TriaxialJaffePotentialPlanarRforce(double,double,double, struct potentialArg *); double TriaxialJaffePotentialphiforce(double,double,double,double, struct potentialArg *); double TriaxialJaffePotentialPlanarphiforce(double,double,double, struct potentialArg *); double TriaxialJaffePotentialzforce(double,double,double,double, struct potentialArg *); //SCFPotential double SCFPotentialEval(double,double,double,double, struct potentialArg *); double SCFPotentialRforce(double,double,double,double, struct potentialArg *); double SCFPotentialzforce(double,double,double,double, struct potentialArg *); double SCFPotentialphiforce(double,double,double,double, struct potentialArg *); double SCFPotentialPlanarRforce(double,double,double, struct potentialArg *); double SCFPotentialPlanarphiforce(double,double,double, struct potentialArg *); double SCFPotentialPlanarR2deriv(double,double,double, struct potentialArg *); double SCFPotentialPlanarphi2deriv(double,double,double, struct potentialArg *); double SCFPotentialPlanarRphideriv(double,double,double, struct potentialArg *); //SoftenedNeedleBarPotential double SoftenedNeedleBarPotentialEval(double,double,double,double, struct potentialArg *); double SoftenedNeedleBarPotentialRforce(double,double,double,double, struct potentialArg *); double SoftenedNeedleBarPotentialzforce(double,double,double,double, struct potentialArg *); double SoftenedNeedleBarPotentialphiforce(double,double,double,double, struct potentialArg *); double SoftenedNeedleBarPotentialPlanarRforce(double,double,double, struct potentialArg *); double SoftenedNeedleBarPotentialPlanarphiforce(double,double,double, struct potentialArg *); //DiskSCFPotential double DiskSCFPotentialEval(double,double,double,double, struct potentialArg *); double DiskSCFPotentialRforce(double,double,double,double, struct potentialArg *); double DiskSCFPotentialzforce(double,double,double,double, struct potentialArg *); double DiskSCFPotentialPlanarRforce(double,double,double, struct potentialArg *); // SpiralArmsPotential double SpiralArmsPotentialEval(double, double, double, double, struct potentialArg*); double SpiralArmsPotentialRforce(double, double, double, double, struct potentialArg*); double SpiralArmsPotentialzforce(double, double, double, double, struct potentialArg*); double SpiralArmsPotentialphiforce(double, double, double, double, struct potentialArg*); double SpiralArmsPotentialR2deriv(double R, double z, double phi, double t, struct potentialArg* potentialArgs); double SpiralArmsPotentialz2deriv(double R, double z, double phi, double t, struct potentialArg* potentialArgs); double SpiralArmsPotentialphi2deriv(double R, double z, double phi, double t, struct potentialArg* potentialArgs); double SpiralArmsPotentialRzderiv(double R, double z, double phi, double t, struct potentialArg* potentialArgs); double SpiralArmsPotentialRphideriv(double R, double z, double phi, double t, struct potentialArg* potentialArgs); double SpiralArmsPotentialPlanarRforce(double, double, double, struct potentialArg*); double SpiralArmsPotentialPlanarphiforce(double, double, double, struct potentialArg*); double SpiralArmsPotentialPlanarR2deriv(double, double, double, struct potentialArg*); double SpiralArmsPotentialPlanarphi2deriv(double, double, double, struct potentialArg*); double SpiralArmsPotentialPlanarRphideriv(double, double, double, struct potentialArg*); //////////////////////////////// WRAPPERS ///////////////////////////////////// //DehnenSmoothWrapperPotential double DehnenSmoothWrapperPotentialEval(double,double,double,double, struct potentialArg *); double DehnenSmoothWrapperPotentialRforce(double,double,double,double, struct potentialArg *); double DehnenSmoothWrapperPotentialphiforce(double,double,double,double, struct potentialArg *); double DehnenSmoothWrapperPotentialzforce(double,double,double,double, struct potentialArg *); double DehnenSmoothWrapperPotentialPlanarRforce(double,double,double, struct potentialArg *); double DehnenSmoothWrapperPotentialPlanarphiforce(double,double,double, struct potentialArg *); double DehnenSmoothWrapperPotentialPlanarR2deriv(double,double,double, struct potentialArg *); double DehnenSmoothWrapperPotentialPlanarphi2deriv(double,double,double, struct potentialArg *); double DehnenSmoothWrapperPotentialPlanarRphideriv(double,double,double, struct potentialArg *); //SolidBodyRotationWrapperPotential double SolidBodyRotationWrapperPotentialRforce(double,double,double,double, struct potentialArg *); double SolidBodyRotationWrapperPotentialphiforce(double,double,double,double, struct potentialArg *); double SolidBodyRotationWrapperPotentialzforce(double,double,double,double, struct potentialArg *); double SolidBodyRotationWrapperPotentialPlanarRforce(double,double,double, struct potentialArg *); double SolidBodyRotationWrapperPotentialPlanarphiforce(double,double,double, struct potentialArg *); double SolidBodyRotationWrapperPotentialPlanarR2deriv(double,double,double, struct potentialArg *); double SolidBodyRotationWrapperPotentialPlanarphi2deriv(double,double,double, struct potentialArg *); double SolidBodyRotationWrapperPotentialPlanarRphideriv(double,double,double, struct potentialArg *); //CosmphiDiskPotential double CosmphiDiskPotentialRforce(double,double,double, struct potentialArg *); double CosmphiDiskPotentialphiforce(double,double,double, struct potentialArg *); double CosmphiDiskPotentialR2deriv(double,double,double, struct potentialArg *); double CosmphiDiskPotentialphi2deriv(double,double,double, struct potentialArg *); double CosmphiDiskPotentialRphideriv(double,double,double, struct potentialArg *); //HenonHeilesPotential double HenonHeilesPotentialRforce(double,double,double, struct potentialArg *); double HenonHeilesPotentialphiforce(double,double,double, struct potentialArg *); double HenonHeilesPotentialR2deriv(double,double,double, struct potentialArg *); double HenonHeilesPotentialphi2deriv(double,double,double, struct potentialArg *); double HenonHeilesPotentialRphideriv(double,double,double, struct potentialArg *); #ifdef __cplusplus } #endif #endif /* galpy_potentials.h */ galpy-1.3.0/galpy/potential_src/potential_c_ext/HenonHeilesPotential.c0000644000076500000240000000212113171712137026320 0ustar bovystaff00000000000000#include #include //HenonHeilesPotential double HenonHeilesPotentialRforce(double R,double phi,double t, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; return - *args * R * (1. + R * sin( 3. * phi ) ); } double HenonHeilesPotentialphiforce(double R,double phi,double t, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; //Calculate phiforce return - *args * pow(R,3.) * cos( 3. * phi ); } double HenonHeilesPotentialR2deriv(double R,double phi,double t, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; return *args * ( 1. + 2. * R * sin( 3. * phi ) ); } double HenonHeilesPotentialphi2deriv(double R,double phi,double t, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; return - 3. * *args * pow(R,3.) * sin( 3. * phi ); } double HenonHeilesPotentialRphideriv(double R,double phi,double t, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; return 3. * *args * R * R * cos( 3. * phi ); } galpy-1.3.0/galpy/potential_src/potential_c_ext/HernquistPotential.c0000644000076500000240000000321612671130470026105 0ustar bovystaff00000000000000#include #include //HernquistPotential //2 arguments: amp, a double HernquistPotentialEval(double R,double Z, double phi, double t, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; double amp= *args++; double a= *args; //Calculate Rforce double sqrtRz= pow(R*R+Z*Z,0.5); return - amp / (1. + sqrtRz / a ) / 2. / a; } double HernquistPotentialRforce(double R,double Z, double phi, double t, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; //Get args double amp= *args++; double a= *args; //Calculate Rforce double sqrtRz= pow(R*R+Z*Z,0.5); return - amp * R / a / sqrtRz * pow(1. + sqrtRz / a , -2. ) / 2. / a; } double HernquistPotentialPlanarRforce(double R,double phi, double t, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; //Get args double amp= *args++; double a= *args; //Calculate Rforce return - amp / a * pow(1. + R / a , -2. ) / 2. / a; } double HernquistPotentialzforce(double R,double Z,double phi, double t, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; //Get args double amp= *args++; double a= *args; //Calculate Rforce double sqrtRz= pow(R*R+Z*Z,0.5); return - amp * Z / a / sqrtRz * pow(1. + sqrtRz / a , -2. ) / 2. / a; } double HernquistPotentialPlanarR2deriv(double R,double phi, double t, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; //Get args double amp= *args++; double a= *args; //Calculate R2deriv return -amp / a / a / a * pow(1. + R / a, -3. ); } galpy-1.3.0/galpy/potential_src/potential_c_ext/interpRZPotential.c0000644000076500000240000000351112671130470025676 0ustar bovystaff00000000000000#include #include #include //interpRZpotential //2 remaining arguments: amp, logR double interpRZPotentialEval(double R,double z, double phi, double t, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; //Get args double y; double amp= *args++; int logR= (int) *args; if ( logR == 1) y= ( R > 0. ) ? log(R): -20.72326583694641; else y= R; //Calculate potential through interpolation return amp * interp_2d_eval_cubic_bspline(potentialArgs->i2d,y,fabs(z), potentialArgs->accx, potentialArgs->accy); } double interpRZPotentialRforce(double R,double z, double phi, double t, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; //Get args double y; double amp= *args++; int logR= (int) *args; if ( logR == 1) y= ( R > 0. ) ? log(R): -20.72326583694641; else y= R; //Calculate potential through interpolation return amp * interp_2d_eval_cubic_bspline(potentialArgs->i2drforce,y,fabs(z), potentialArgs->accxrforce, potentialArgs->accyrforce); } double interpRZPotentialzforce(double R,double z, double phi, double t, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; //Get args double y; double amp= *args++; int logR= (int) *args; if ( logR == 1) y= ( R > 0. ) ? log(R): -20.72326583694641; else y= R; //Calculate potential through interpolation if ( z < 0. ) return - amp * interp_2d_eval_cubic_bspline(potentialArgs->i2dzforce,y, -z, potentialArgs->accxzforce, potentialArgs->accyzforce); else return amp * interp_2d_eval_cubic_bspline(potentialArgs->i2dzforce,y, z, potentialArgs->accxzforce, potentialArgs->accyzforce); } galpy-1.3.0/galpy/potential_src/potential_c_ext/IsochronePotential.c0000644000076500000240000000340712671130470026056 0ustar bovystaff00000000000000#include #include //IsochronePotential //2 arguments: amp, b double IsochronePotentialEval(double R,double Z, double phi, double t, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; //Get args double amp= *args; double b= *(args+1); //Calculate potential double r2= R*R+Z*Z; return -amp / ( b + sqrt(r2 + b * b) ); } double IsochronePotentialRforce(double R,double Z, double phi, double t, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; //Get args double amp= *args; double b= *(args+1); //Calculate Rforce double r2= R*R+Z*Z; double rb= sqrt(r2 + b * b); return - amp * R / rb * pow(b + rb,-2.); } double IsochronePotentialPlanarRforce(double R,double phi, double t, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; //Get args double amp= *args; double b= *(args+1); //Calculate Rforce double r2= R*R; double rb= sqrt(r2 + b * b); return - amp * R / rb * pow(b + rb,-2.); } double IsochronePotentialzforce(double R,double Z,double phi, double t, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; //Get args double amp= *args; double b= *(args+1); //Calculate zforce double r2= R*R+Z*Z; double rb= sqrt(r2 + b * b); return - amp * Z / rb * pow(b + rb,-2.); } double IsochronePotentialPlanarR2deriv(double R,double phi, double t, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; //Get args double amp= *args; double b= *(args+1); //Calculate Rforce double r2= R*R; double rb= sqrt(r2 + b * b); return - amp * ( -pow(b,3.) - b * b * rb + 2. * r2 * rb ) * pow(rb * ( b + rb ),-3.); } galpy-1.3.0/galpy/potential_src/potential_c_ext/JaffePotential.c0000644000076500000240000000315412671130470025137 0ustar bovystaff00000000000000#include #include //JaffePotential //2 arguments: amp, a double JaffePotentialEval(double R,double Z, double phi, double t, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; //Get args double amp= *args++; double a= *args; //Calculate Rforce double sqrtRz= pow(R*R+Z*Z,0.5); return - amp * log ( 1. + a / sqrtRz ) / a; } double JaffePotentialRforce(double R,double Z, double phi, double t, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; //Get args double amp= *args++; double a= *args; //Calculate Rforce double sqrtRz= pow(R*R+Z*Z,0.5); return - amp * R * pow( sqrtRz , -3. ) / ( 1. + a / sqrtRz ); } double JaffePotentialPlanarRforce(double R,double phi, double t, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; //Get args double amp= *args++; double a= *args; //Calculate Rforce return - amp * pow(R,-2.) / ( 1. + a / R ); } double JaffePotentialzforce(double R,double Z,double phi, double t, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; //Get args double amp= *args++; double a= *args; //Calculate Rforce double sqrtRz= pow(R*R+Z*Z,0.5); return - amp * Z * pow( sqrtRz , -3. ) / ( 1. + a / sqrtRz ); } double JaffePotentialPlanarR2deriv(double R,double phi, double t, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; //Get args double amp= *args++; double a= *args; //Calculate R2deriv return - amp * (a + 2. * R) * pow(R,-4.) * pow(1.+a/R,-2.); } galpy-1.3.0/galpy/potential_src/potential_c_ext/KuzminDiskPotential.c0000644000076500000240000000320513003465421026206 0ustar bovystaff00000000000000#include #include //Kuzmin Disk potential //2 arguments: amp, a double KuzminDiskPotentialEval(double R,double z, double phi, double t, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; //Get args double amp= *args++; double a= *args; //Calculate Potential return - amp * pow(R*R+pow(a+fabs(z),2.),-0.5); } double KuzminDiskPotentialRforce(double R,double z, double phi, double t, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; //Get args double amp= *args++; double a= *args; //Calculate Rforce return - amp * R * pow(R*R+pow(a+fabs(z),2.),-1.5); } double KuzminDiskPotentialPlanarRforce(double R,double phi, double t, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; //Get args double amp= *args++; double a= *args; //Calculate planarRforce return - amp * R * pow(R*R+a*a,-1.5); } double KuzminDiskPotentialzforce(double R,double z,double phi, double t, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; //Get args double amp= *args++; double a= *args; //Calculate zforce double zsign= (z > 0 ) - (z < 0); //Gets the sign of z return -zsign* amp * pow(R*R+pow(a+fabs(z),2.),-1.5) * (a + fabs(z)); } double KuzminDiskPotentialPlanarR2deriv(double R,double phi, double t, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; //Get args double amp= *args++; double a= *args; //calculate R2deriv double denom=R*R+a*a; return amp * (-pow(denom,-1.5) + 3*R*R*pow(denom, -2.5)); } galpy-1.3.0/galpy/potential_src/potential_c_ext/KuzminKutuzovStaeckelPotential.c0000644000076500000240000000750512671130470030471 0ustar bovystaff00000000000000#include #include //KuzminKutuzovStaeckelPotential //3 arguments: amp, ac, Delta double KuzminKutuzovStaeckelPotentialEval(double R,double z, double phi, double t, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; //Get args double amp = *args; double ac = *(args+1); double Delta = *(args+2); //Coordinate transformation double gamma = Delta*Delta / (1.-ac*ac); double alpha = gamma - Delta*Delta; double term = R*R + z*z - alpha - gamma; double discr = pow(R*R + z*z - Delta*Delta, 2.) + (4. * Delta*Delta * R*R); double l = 0.5 * (term + sqrt(discr)); double n = 0.5 * (term - sqrt(discr)); n= ((n > 0.) ? n: 0.); //Calculate potential return -amp /(sqrt(l) + sqrt(n)); } double KuzminKutuzovStaeckelPotentialRforce(double R,double z, double phi, double t, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; //Get args double amp = *args; double ac = *(args+1); double Delta = *(args+2); //Coordinate transformation double gamma = Delta*Delta / (1.-ac*ac); double alpha = gamma - Delta*Delta; double term = R*R + z*z - alpha - gamma; double discr = pow(R*R + z*z - Delta*Delta, 2.) + (4. * Delta*Delta * R*R); double l = 0.5 * (term + sqrt(discr)); double n = 0.5 * (term - sqrt(discr)); double dldR = R * (1. + (R*R + z*z + Delta*Delta) / sqrt(discr)); double dndR = R * (1. - (R*R + z*z + Delta*Delta) / sqrt(discr)); //Calculate Rforce double dVdl = 0.5/sqrt(l)/pow(sqrt(l)+sqrt(n),2.); double dVdn = 0.5/sqrt(n)/pow(sqrt(l)+sqrt(n),2.); return -amp * (dldR * dVdl + dndR * dVdn); } double KuzminKutuzovStaeckelPotentialPlanarRforce(double R,double phi, double t, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; //Get args double amp = *args; double ac = *(args+1); double Delta = *(args+2); //Coordinate transformation (for z=0) double gamma = Delta*Delta / (1.-ac*ac); double alpha = gamma - Delta*Delta; double l = R*R - alpha; double n = -gamma; double dldR = 2.*R; //Calculate Rforce double dVdl = 0.5/sqrt(l)/pow(sqrt(l)+sqrt(n),2.); return -amp * dldR * dVdl; } double KuzminKutuzovStaeckelPotentialzforce(double R,double z,double phi, double t, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; //Get args double amp = *args; double ac = *(args+1); double Delta = *(args+2); //Coordinate transformation double gamma = Delta*Delta / (1.-ac*ac); double alpha = gamma - Delta*Delta; double term = R*R + z*z - alpha - gamma; double discr = pow(R*R + z*z - Delta*Delta, 2.) + (4. * Delta*Delta * R*R); double l = 0.5 * (term + sqrt(discr)); double n = 0.5 * (term - sqrt(discr)); double dldz = z * (1. + (R*R + z*z - Delta*Delta) / sqrt(discr)); double dndz = z * (1. - (R*R + z*z - Delta*Delta) / sqrt(discr)); //Calculate zforce double dVdl = 0.5/sqrt(l)/pow(sqrt(l)+sqrt(n),2.); double dVdn = 0.5/sqrt(n)/pow(sqrt(l)+sqrt(n),2.); return -amp * (dldz * dVdl + dndz * dVdn); } double KuzminKutuzovStaeckelPotentialPlanarR2deriv(double R,double phi, double t, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; //Get args double amp = *args; double ac = *(args+1); double Delta = *(args+2); //Coordinate transformation (for z=0) double gamma = Delta*Delta / (1.-ac*ac); double alpha = gamma - Delta*Delta; double l = R*R - alpha; double n = -gamma; double dldR = 2.*R; double d2ldR2 = 2.; //Calculate R2deriv double dVdl = 0.5/sqrt(l)/pow(sqrt(l)+sqrt(n),2.); double d2Vdl2 = (-3.*sqrt(l)-sqrt(n)) / (4. * pow(l,1.5) * pow(sqrt(l)+sqrt(n),3.)); return amp * (d2ldR2 * dVdl + dldR*dldR*d2Vdl2); } galpy-1.3.0/galpy/potential_src/potential_c_ext/LogarithmicHaloPotential.c0000644000076500000240000001074613236350147027202 0ustar bovystaff00000000000000#include #include //LogarithmicHaloPotential //3 (2) arguments: amp, c2, (and q), now also 1-1/b^2 for triaxial! double LogarithmicHaloPotentialEval(double R,double Z, double phi, double t, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; //Get args double amp= *args; double q= *(args+1); double c= *(args+2); double onem1overb2= *(args+3); //Calculate potential double zq= Z/q; if ( onem1overb2 < 1 ) return 0.5 * amp * log(R*R * (1. - onem1overb2 * pow(sin(phi),2))+zq*zq+c); else return 0.5 * amp * log(R*R+zq*zq+c); } double LogarithmicHaloPotentialRforce(double R,double Z, double phi, double t, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; //Get args double amp= *args; double q= *(args+1); double c= *(args+2); double onem1overb2= *(args+3); //Calculate Rforce double zq= Z/q; double Rt2; if ( onem1overb2 < 1 ) { Rt2= R*R * (1. - onem1overb2 * pow(sin(phi),2)); return - amp * Rt2/R/(Rt2+zq*zq+c); } else return - amp * R/(R*R+zq*zq+c); } double LogarithmicHaloPotentialPlanarRforce(double R,double phi, double t, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; //Get args double amp= *args; double c= *(args+1); double onem1overb2= *(args+2); //Calculate Rforce double Rt2; if ( onem1overb2 < 1 ) { Rt2= R*R * (1. - onem1overb2 * pow(sin(phi),2)); return -amp * Rt2/R/(Rt2+c); } else return -amp * R/(R*R+c); } double LogarithmicHaloPotentialzforce(double R,double z,double phi, double t, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; //Get args double amp= *args; double q= *(args+1); double c= *(args+2); double onem1overb2= *(args+3); //Calculate zforce double zq= z/q; double Rt2; if ( onem1overb2 < 1 ) { Rt2= R*R * (1. - onem1overb2 * pow(sin(phi),2)); return -amp * zq/q/(Rt2+zq*zq+c); } else return -amp * zq/q/(R*R+zq*zq+c); } double LogarithmicHaloPotentialphiforce(double R,double z,double phi, double t, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; //Get args double amp= *args; double q= *(args+1); double c= *(args+2); double onem1overb2= *(args+3); //Calculate phiforce double zq; double Rt2; if ( onem1overb2 < 1 ) { zq= z/q; Rt2= R*R * (1. - onem1overb2 * pow(sin(phi),2)); return amp * R*R / (Rt2+zq*zq+c) * sin(2*phi) * onem1overb2 / 2.; } else return 0.; } double LogarithmicHaloPotentialPlanarphiforce(double R,double phi, double t, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; //Get args double amp= *args; double c= *(args+1); double onem1overb2= *(args+2); //Calculate phiforce double Rt2; if ( onem1overb2 < 1 ) { Rt2= R*R * (1. - onem1overb2 * pow(sin(phi),2)); return amp * R*R / (Rt2+c) * sin(2*phi) * onem1overb2 / 2.; } else return 0.; } double LogarithmicHaloPotentialPlanarR2deriv(double R,double phi, double t, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; //Get args double amp= *args; double c= *(args+1); double onem1overb2= *(args+2); //Calculate Rforce double Rt2; if ( onem1overb2 < 1 ) { Rt2= R*R * (1. - onem1overb2 * pow(sin(phi),2)); return amp * (1.- 2.*Rt2/(Rt2+c))/(Rt2+c)*Rt2/R/R; } else return amp * (1.- 2.*R*R/(R*R+c))/(R*R+c); } double LogarithmicHaloPotentialPlanarphi2deriv(double R,double phi, double t, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; //Get args double amp= *args; double c= *(args+1); double onem1overb2= *(args+2); //Calculate Rforce double Rt2; if ( onem1overb2 < 1 ) { Rt2= R*R * (1. - onem1overb2 * pow(sin(phi),2)); return - amp * onem1overb2 * (0.5 * pow(R*R*sin(2.*phi),2.) * onem1overb2 \ /(Rt2+c)/(Rt2+c)+R*R/(Rt2+c)*cos(2.*phi)); } else return 0.; } double LogarithmicHaloPotentialPlanarRphideriv(double R,double phi, double t, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; //Get args double amp= *args; double c= *(args+1); double onem1overb2= *(args+2); //Calculate Rforce double Rt2; if ( onem1overb2 < 1 ) { Rt2= R*R * (1. - onem1overb2 * pow(sin(phi),2)); return - amp * c / (Rt2+c) / (Rt2+c) * R * sin(2.*phi) * onem1overb2; } else return 0.; } galpy-1.3.0/galpy/potential_src/potential_c_ext/LopsidedDiskPotential.c0000644000076500000240000000333413157625526026515 0ustar bovystaff00000000000000#include #include //LopsidedDiskPotential double LopsidedDiskPotentialRforce(double R,double phi,double t, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; //Get args double amp= *args++; double phio= *args++; double p= *args++; double phib= *args; //Calculate Rforce return -amp * p * phio * pow(R,p-1.) * cos( phi - phib); } double LopsidedDiskPotentialphiforce(double R,double phi,double t, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; //Get args double amp= *args++; double phio= *args++; double p= *args++; double phib= *args; //Calculate phiforce return amp * phio * pow(R,p) * sin( phi-phib); } double LopsidedDiskPotentialR2deriv(double R,double phi,double t, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; //Get args double amp= *args++; double phio= *args++; double p= *args++; double phib= *args; //Calculate Rforce return amp * p * ( p - 1) * phio * pow(R,p-2.) * cos(phi - phib); } double LopsidedDiskPotentialphi2deriv(double R,double phi,double t, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; //Get args double amp= *args++; double phio= *args++; double p= *args++; double phib= *args; //Calculate Rforce return - amp * phio * pow(R,p) * cos( phi - phib ); } double LopsidedDiskPotentialRphideriv(double R,double phi,double t, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; //Get args double amp= *args++; double phio= *args++; double p= *args++; double phib= *args; //Calculate Rforce return - amp * p * phio * pow(R,p-1.) * sin( phi - phib ); } galpy-1.3.0/galpy/potential_src/potential_c_ext/MiyamotoNagaiPotential.c0000644000076500000240000000357112671130470026665 0ustar bovystaff00000000000000#include #include //Miyamoto-Nagai potential //3 arguments: amp, a, b double MiyamotoNagaiPotentialEval(double R,double z, double phi, double t, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; //Get args double amp= *args++; double a= *args++; double b= *args; //Calculate Rforce return - amp * pow(R*R+pow(a+pow(z*z+b*b,0.5),2.),-0.5); } double MiyamotoNagaiPotentialRforce(double R,double z, double phi, double t, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; //Get args double amp= *args++; double a= *args++; double b= *args; //Calculate Rforce return - amp * R * pow(R*R+pow(a+pow(z*z+b*b,0.5),2.),-1.5); } double MiyamotoNagaiPotentialPlanarRforce(double R,double phi, double t, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; //Get args double amp= *args++; double a= *args++; double b= *args; //Calculate Rforce return - amp * R * pow(R*R+pow(a+b,2.),-1.5); } double MiyamotoNagaiPotentialzforce(double R,double z,double phi, double t, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; //Get args double amp= *args++; double a= *args++; double b= *args; //Calculate zforce double sqrtbz= pow(b*b+z*z,.5); double asqrtbz= a+sqrtbz; if ( a == 0. ) return amp * ( -z * pow(R*R+asqrtbz*asqrtbz,-1.5) ); else return amp * ( -z * asqrtbz / sqrtbz * pow(R*R+asqrtbz*asqrtbz,-1.5) ); } double MiyamotoNagaiPotentialPlanarR2deriv(double R,double phi, double t, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; //Get args double amp= *args++; double a= *args++; double b= *args; //calculate R2deriv double denom= R*R+pow(a+b,2.); return amp * (pow(denom,-1.5) - 3. * R * R * pow(denom,-2.5)); } galpy-1.3.0/galpy/potential_src/potential_c_ext/NFWPotential.c0000644000076500000240000000327712726354025024571 0ustar bovystaff00000000000000#include #include //NFWPotential //2 arguments: amp, a double NFWPotentialEval(double R,double Z, double phi, double t, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; //Get args double amp= *args++; double a= *args; //Calculate Rforce double sqrtRz= pow(R*R+Z*Z,0.5); return - amp * log ( 1. + sqrtRz / a ) / sqrtRz; } double NFWPotentialRforce(double R,double Z, double phi, double t, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; //Get args double amp= *args++; double a= *args; //Calculate Rforce double Rz= R*R+Z*Z; double sqrtRz= pow(Rz,0.5); return amp * R * (1. / Rz / (a + sqrtRz)-log(1.+sqrtRz / a)/sqrtRz/Rz); } double NFWPotentialPlanarRforce(double R,double phi, double t, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; //Get args double amp= *args++; double a= *args; //Calculate Rforce return amp / R * (1. / (a + R)-log(1.+ R / a)/ R); } double NFWPotentialzforce(double R,double Z,double phi, double t, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; //Get args double amp= *args++; double a= *args; //Calculate Rforce double Rz= R*R+Z*Z; double sqrtRz= pow(Rz,0.5); return amp * Z * (1. / Rz / (a + sqrtRz)-log(1.+sqrtRz / a)/sqrtRz/Rz); } double NFWPotentialPlanarR2deriv(double R,double phi, double t, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; //Get args double amp= *args++; double a= *args; //Calculate R2deriv double aR= a+R; double aR2= aR*aR; return amp * (((R*(2.*a+3.*R))-2.*aR2*log(1.+R/a))/R/R/R/aR2); } galpy-1.3.0/galpy/potential_src/potential_c_ext/PlummerPotential.c0000644000076500000240000000307212671130470025544 0ustar bovystaff00000000000000#include #include //PlummerPotential //2 arguments: amp, b double PlummerPotentialEval(double R,double Z, double phi, double t, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; //Get args double amp= *args; double b2= *(args+1) * *(args+1); //Calculate potential return - amp / sqrt( R * R + Z * Z + b2 ); } double PlummerPotentialRforce(double R,double Z, double phi, double t, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; //Get args double amp= *args; double b2= *(args+1) * *(args+1); //Calculate Rforce return - amp * R * pow(R*R+Z*Z+b2,-1.5); } double PlummerPotentialPlanarRforce(double R,double phi, double t, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; //Get args double amp= *args; double b2= *(args+1) * *(args+1); //Calculate Rforce return - amp * R * pow(R*R+b2,-1.5); } double PlummerPotentialzforce(double R,double Z,double phi, double t, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; //Get args double amp= *args; double b2= *(args+1) * *(args+1); //Calculate zforce return - amp * Z * pow(R*R+Z*Z+b2,-1.5); } double PlummerPotentialPlanarR2deriv(double R,double phi, double t, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; //Get args double amp= *args; double b2= *(args+1) * *(args+1); //Calculate Rforce return amp * (b2 - 2.*R*R)*pow(R*R+b2,-2.5); } galpy-1.3.0/galpy/potential_src/potential_c_ext/PowerSphericalPotential.c0000644000076500000240000000317512671130470027056 0ustar bovystaff00000000000000#include #include //PowerSphericalPotential //2 arguments: amp, alpha double PowerSphericalPotentialEval(double R,double Z, double phi, double t, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; //Get args double amp= *args++; double alpha= *args; //Calculate Rforce if ( alpha == 2. ) return 0.5 * amp * log ( R*R+Z*Z); else return - amp * pow(R*R+Z*Z,1.-0.5*alpha) / (alpha - 2.); } double PowerSphericalPotentialRforce(double R,double Z, double phi, double t, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; //Get args double amp= *args++; double alpha= *args; //Calculate Rforce return - amp * R * pow(R*R+Z*Z,-0.5*alpha); } double PowerSphericalPotentialPlanarRforce(double R,double phi, double t, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; //Get args double amp= *args++; double alpha= *args; //Calculate Rforce return - amp * pow(R,-alpha + 1.); } double PowerSphericalPotentialzforce(double R,double Z,double phi, double t, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; //Get args double amp= *args++; double alpha= *args; //Calculate zforce return - amp * Z * pow(R*R+Z*Z,-0.5*alpha); } double PowerSphericalPotentialPlanarR2deriv(double R,double phi, double t, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; //Get args double amp= *args++; double alpha= *args; //Calculate R2deriv return amp * (1. - alpha ) * pow(R,-alpha); } galpy-1.3.0/galpy/potential_src/potential_c_ext/PowerSphericalPotentialwCutoff.c0000644000076500000240000000463612721574473030432 0ustar bovystaff00000000000000#include #include #include #ifndef M_PI #define M_PI 3.14159265358979323846 #endif //PowerSphericalPotentialwCutoff //3 arguments: amp, alpha, rc double mass(double r2,double alpha, double rc){ return 2. * M_PI * pow ( rc , 3. - alpha ) * ( gsl_sf_gamma ( 1.5 - 0.5 * alpha ) - gsl_sf_gamma_inc ( 1.5 - 0.5 * alpha , r2 / rc / rc ) ); } double PowerSphericalPotentialwCutoffEval(double R,double Z, double phi, double t, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; //Get args double amp= *args++; double alpha= *args++; double rc= *args; //Radius double r2= R*R+Z*Z; double r= sqrt(r2); return amp * 2. * M_PI * pow(rc,3.-alpha) / r * ( r / rc * ( gsl_sf_gamma ( 1. - 0.5 * alpha ) - gsl_sf_gamma_inc ( 1. - 0.5 * alpha , r2 / rc / rc ) ) - ( gsl_sf_gamma ( 1.5 - 0.5 * alpha ) - gsl_sf_gamma_inc ( 1.5 - 0.5 * alpha , r2 / rc / rc) ) ); } double PowerSphericalPotentialwCutoffRforce(double R,double Z, double phi, double t, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; //Get args double amp= *args++; double alpha= *args++; double rc= *args; //Radius double r2= R*R+Z*Z; //Calculate Rforce return - amp * mass (r2,alpha,rc) * R / pow(r2,1.5); } double PowerSphericalPotentialwCutoffPlanarRforce(double R,double phi, double t, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; //Get args double amp= *args++; double alpha= *args++; double rc= *args; //Radius double r2= R*R; //Calculate Rforce return - amp * mass (r2,alpha,rc) / r2; } double PowerSphericalPotentialwCutoffzforce(double R,double Z,double phi, double t, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; //Get args double amp= *args++; double alpha= *args++; double rc= *args; //Radius double r2= R*R+Z*Z; //Calculate Rforce return - amp * mass (r2,alpha,rc) * Z / pow(r2,1.5); } double PowerSphericalPotentialwCutoffPlanarR2deriv(double R,double phi, double t, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; //Get args double amp= *args++; double alpha= *args++; double rc= *args; //Radius double r2= R*R; //Calculate R2deriv return amp * ( 4. * M_PI * pow(r2,- 0.5 * alpha) * exp(-r2/rc/rc) - 2. * mass(r2,alpha,rc)/pow(r2,1.5) ); } galpy-1.3.0/galpy/potential_src/potential_c_ext/PseudoIsothermalPotential.c0000644000076500000240000000355112741726747027434 0ustar bovystaff00000000000000#include #include //PseudoIsothermalPotential //2 arguments: amp, a double PseudoIsothermalPotentialEval(double R,double Z, double phi, double t, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; //Get args double amp= *args; double a= *(args+1); double a2= a*a; //Calculate potential double r2= R*R+Z*Z; double r= sqrt(r2); return amp * (0.5 * log(1 + r2 / a2) + a / r * atan(r / a)) / a; } double PseudoIsothermalPotentialRforce(double R,double Z, double phi, double t, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; //Get args double amp= *args; double a= *(args+1); //Calculate potential double r2= R*R+Z*Z; double r= sqrt(r2); return - amp * (1. / r - a / r2 * atan(r / a)) / a * R / r; } double PseudoIsothermalPotentialPlanarRforce(double R,double phi, double t, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; //Get args double amp= *args; double a= *(args+1); //Calculate potential return - amp * (1. / R - a / R / R * atan(R / a)) / a; } double PseudoIsothermalPotentialzforce(double R,double z,double phi, double t, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; //Get args double amp= *args; double a= *(args+1); //Calculate potential double r2= R*R+z*z; double r= sqrt(r2); return - amp * (1. / r - a / r2 * atan(r / a)) / a * z / r; } double PseudoIsothermalPotentialPlanarR2deriv(double R,double phi, double t, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; //Get args double amp= *args; double a= *(args+1); double a2= a*a; //Calculate potential double R2= R*R; return amp / R2 * (2. * a / R * atan(R / a) - ( 2. * a2 + R2)/(a2 + R2) ) / a; } galpy-1.3.0/galpy/potential_src/potential_c_ext/SCFPotential.c0000644000076500000240000005017713133144160024541 0ustar bovystaff00000000000000#include #include #include #include #include #ifndef GSL_MAJOR_VERSION #define GSL_MAJOR_VERSION 1 #endif //SCF Disk potential //4 arguments: amp, Acos, Asin, a const int FORCE =1; const int DERIV =2; //Useful Functions //Converts from cylindrical coordinates to spherical static inline void cyl_to_spher(double R, double Z,double *r, double *theta) { *r = sqrt(R*R + Z*Z); *theta = atan2(R, Z); } //Integer power double power(double x, int i) { if (i==0) return 1; return x*power(x,i - 1); } //Calculates xi static inline void calculateXi(double r, double a, double *xi) { *xi = (r - a)/(r + a); } //Potentials, forces, and derivative functions //LCOV_EXCL_START double computePhi(double Acos_val, double Asin_val, double mCos, double mSin, double P, double phiTilde, int m) { return (Acos_val*mCos + Asin_val*mSin)*P*phiTilde; } //LCOV_EXCL_STOP double computeAxiPhi(double Acos_val, double P, double phiTilde) { return Acos_val*P*phiTilde; } double computeF_r(double Acos_val, double Asin_val, double mCos, double mSin, double P, double dphiTilde, int m) { return -(Acos_val*mCos + Asin_val*mSin)*P*dphiTilde; } double computeAxiF_r(double Acos_val, double P, double dphiTilde) { return -Acos_val*P*dphiTilde; } double computeF_theta(double Acos_val, double Asin_val, double mCos, double mSin, double dP, double phiTilde, int m) { return -(Acos_val*mCos + Asin_val*mSin)*dP*phiTilde; } double computeAxiF_theta(double Acos_val, double dP, double phiTilde) { return -Acos_val*dP*phiTilde; } double computeF_phi(double Acos_val, double Asin_val, double mCos, double mSin, double P, double phiTilde, int m) { return m*(Acos_val*mSin - Asin_val*mCos)*P*phiTilde; } double computeAxiF_phi(double Acos_val, double P, double phiTilde) { return 0.; } double computeF_rr(double Acos_val, double Asin_val, double mCos, double mSin, double P, double d2phiTilde, int m) { return -(Acos_val*mCos + Asin_val*mSin)*P*d2phiTilde; } double computeAxiF_rr(double Acos_val, double P, double d2phiTilde) { return -Acos_val*P*d2phiTilde; } double computeF_rphi(double Acos_val, double Asin_val, double mCos, double mSin, double P, double dphiTilde, int m) { return m*(Acos_val*mSin - Asin_val*mCos)*P*dphiTilde; } double computeAxiF_rphi(double Acos_val, double P, double d2phiTilde) { return 0.; } double computeF_phiphi(double Acos_val, double Asin_val, double mCos, double mSin, double P, double phiTilde, int m) { return m*m*(Acos_val*mCos + Asin_val*mSin)*P*phiTilde; } double computeAxiF_phiphi(double Acos_val, double P, double d2phiTilde) { return 0.; } //Calculates the Gegenbauer polynomials void compute_C(double xi, int N, int L, double * C_array) { int l; for (l = 0; l < L; l++) { gsl_sf_gegenpoly_array(N - 1, 3./2 + 2*l, xi, C_array + l*N); } } //Calculates the derivative of the Gegenbauer polynomials void compute_dC(double xi, int N, int L, double * dC_array) { int n,l; for (l = 0; l < L; l++) { *(dC_array +l*N) = 0; if (N != 1) gsl_sf_gegenpoly_array(N - 2, 5./2 + 2*l, xi, dC_array + l*N + 1); for (n = 0; n1) *(d2C_array +l*N + 1) = 0; if (N > 2) gsl_sf_gegenpoly_array(N - 3, 7./2 + 2*l, xi, d2C_array + l*N + 2); for (n = 0; nargs; //Get args double a = *args++; int isNonAxi = (int)*args++; int N = (int)*args++; int L = (int)*args++; int M = (int)*args++; double* Acos = args; double* caching_i = (args + (isNonAxi + 1)*N*L*M); double *Asin; if (isNonAxi == 1) { Asin = args + N*L*M; } double *cached_type = caching_i; double * cached_coords = (caching_i+ 1); double * cached_values = (caching_i + 4); if ((int)*cached_type==FORCE) { if (*cached_coords == R && *(cached_coords + 1) == Z && *(cached_coords + 2) == phi) { *F = *cached_values; *(F + 1) = *(cached_values + 1); *(F + 2) = *(cached_values + 2); return; } } double r; double theta; cyl_to_spher(R, Z, &r, &theta); double xi; calculateXi(r, a, &xi); //Compute the gegenbauer polynomials and its derivative. double C[N*L]; double dC[N*L]; compute_C(xi, N, L, &C[0]); compute_dC(xi, N, L, &dC[0]); //Compute phiTilde and its derivative double phiTilde[L*N]; compute_phiTilde(r, a, N, L, &C[0], &phiTilde[0]); double dphiTilde[L*N]; compute_dphiTilde(r, a, N, L, &C[0], &dC[0], &dphiTilde[0]); //Compute Associated Legendre Polynomials int M_eff = M; int size = 0; if (isNonAxi==0) { M_eff = 1; size = L; } else{ size = L*L - L*(L-1)/2; } double P[size]; double dP[size]; compute_P_dP(cos(theta), L, M_eff, &P[0], &dP[0]); double (*PhiTilde_Pointer[3]) = {&dphiTilde[0],&phiTilde[0],&phiTilde[0]}; double (*P_Pointer[3]) = {&P[0], &dP[0], &P[0]}; double Constant[3] = {1., -sin(theta), 1.}; if (isNonAxi == 1) { double (*Eq[3])(double, double, double, double, double, double, int) = {&computeF_r, &computeF_theta, &computeF_phi}; equations e = {Eq,&PhiTilde_Pointer[0], &P_Pointer[0], &Constant[0]}; computeNonAxi(a, N, L, M,r, theta, phi, Acos, Asin, 3, e, F); } else { double (*Eq[3])(double, double, double) = {&computeAxiF_r, &computeAxiF_theta, &computeAxiF_phi}; axi_equations e = {Eq,&PhiTilde_Pointer[0], &P_Pointer[0], &Constant[0]}; compute(a, N, L, M,r, theta, phi, Acos, 3, e, F); } //Caching *cached_type = (double)FORCE; * cached_coords = R; * (cached_coords + 1) = Z; * (cached_coords + 2) = phi; * (cached_values) = *F; * (cached_values + 1) = *(F + 1); * (cached_values + 2) = *(F + 2); } //Compute the Derivatives void computeDeriv(double R,double Z, double phi, double t, struct potentialArg * potentialArgs, double * F) { double * args= potentialArgs->args; //Get args double a = *args++; int isNonAxi = (int)*args++; int N = *args++; int L = *args++; int M = *args++; double* Acos = args; double * caching_i = (args + (isNonAxi + 1)*N*L*M); double *Asin; if (isNonAxi == 1) { Asin = args + N*L*M; } double *cached_type = caching_i; double * cached_coords = (caching_i+ 1); double * cached_values = (caching_i + 4); if ((int)*cached_type==DERIV) { if (*cached_coords == R && *(cached_coords + 1) == Z && *(cached_coords + 2) == phi) { *F = *cached_values; *(F + 1) = *(cached_values + 1); *(F + 2) = *(cached_values + 2); return; } } double r; double theta; cyl_to_spher(R, Z, &r, &theta); double xi; calculateXi(r, a, &xi); //Compute the gegenbauer polynomials and its derivative. double C[N*L]; double dC[N*L]; double d2C[N*L]; compute_C(xi, N, L, &C[0]); compute_dC(xi, N, L, &dC[0]); compute_d2C(xi, N, L, &d2C[0]); //Compute phiTilde and its derivative double phiTilde[L*N]; compute_phiTilde(r, a, N, L, &C[0], &phiTilde[0]); double dphiTilde[L*N]; compute_dphiTilde(r, a, N, L, &C[0], &dC[0], &dphiTilde[0]); double d2phiTilde[L*N]; compute_d2phiTilde(r, a, N, L, &C[0], &dC[0], &d2C[0], &d2phiTilde[0]); //Compute Associated Legendre Polynomials int M_eff = M; int size = 0; if (isNonAxi==0) { M_eff = 1; size = L; } else{ size = L*L - L*(L-1)/2; } double P[size]; compute_P(cos(theta), L,M_eff, &P[0]); double (*PhiTilde_Pointer[3])= {&d2phiTilde[0],&phiTilde[0],&dphiTilde[0]}; double (*P_Pointer[3]) = {&P[0], &P[0], &P[0]}; double Constant[3] = {1., 1., 1.}; if (isNonAxi==1) { double (*Eq[3])(double, double, double, double, double, double, int) = {&computeF_rr, &computeF_phiphi, &computeF_rphi}; equations e = {Eq,&PhiTilde_Pointer[0],&P_Pointer[0],&Constant[0]}; computeNonAxi(a, N, L, M,r, theta, phi, Acos, Asin, 3, e, F); } else { double (*Eq[3])(double, double, double) = {&computeAxiF_rr, &computeAxiF_phiphi, &computeAxiF_rphi}; axi_equations e = {Eq,&PhiTilde_Pointer[0],&P_Pointer[0],&Constant[0]}; compute(a, N, L, M,r, theta, phi, Acos, 3, e, F); } //Caching *cached_type = (double)DERIV; * cached_coords = R; * (cached_coords + 1) = Z; * (cached_coords + 2) = phi; * (cached_values) = *F; * (cached_values + 1) = *(F + 1); * (cached_values + 2) = *(F + 2); } //Compute the Potential double SCFPotentialEval(double R,double Z, double phi, double t, struct potentialArg * potentialArgs) { double * args= potentialArgs->args; //Get args double a = *args++; int isNonAxi = (int)*args++; int N = *args++; int L = *args++; int M = *args++; double* Acos = args; double* Asin; if (isNonAxi==1) //LCOV_EXCL_START { Asin = args + N*L*M; } //LCOV_EXCL_STOP //convert R,Z to r, theta double r; double theta; cyl_to_spher(R, Z,&r, &theta); double xi; calculateXi(r, a, &xi); //Compute the gegenbauer polynomials and its derivative. double C[N*L]; compute_C(xi, N, L, &C[0]); //Compute phiTilde and its derivative double phiTilde[L*N]; compute_phiTilde(r, a, N, L, &C[0], &phiTilde[0]); //Compute Associated Legendre Polynomials int M_eff = M; int size = 0; if (isNonAxi==0) { M_eff = 1; size = L; } else{ //LCOV_EXCL_START size = L*L - L*(L-1)/2; } //LCOV_EXCL_STOP double P[size]; compute_P(cos(theta), L,M_eff, &P[0]); double potential; double (*PhiTilde_Pointer[1]) = {&phiTilde[0]}; double (*P_Pointer[1]) = {&P[0]}; double Constant[1] = {1.}; if (isNonAxi==1) //LCOV_EXCL_START { double (*Eq[1])(double, double, double, double, double, double, int) = {&computePhi}; equations e = {Eq,&PhiTilde_Pointer[0],&P_Pointer[0],&Constant[0]}; computeNonAxi(a, N, L, M,r, theta, phi, Acos, Asin, 1, e, &potential); } //LCOV_EXCL_STOP else { double (*Eq[1])(double, double, double) = {&computeAxiPhi}; axi_equations e = {Eq,&PhiTilde_Pointer[0],&P_Pointer[0],&Constant[0]}; compute(a, N, L, M,r, theta, phi, Acos, 1, e, &potential); } return potential; } //Compute the force in the R direction double SCFPotentialRforce(double R,double Z, double phi, double t, struct potentialArg * potentialArgs) { double r; double theta; cyl_to_spher(R, Z, &r, &theta); // The derivatives double dr_dR = R/r; double dtheta_dR = Z/(r*r); double dphi_dR = 0; double F[3]; computeForce(R, Z, phi, t,potentialArgs, &F[0]) ; return *(F + 0)*dr_dR + *(F + 1)*dtheta_dR + *(F + 2)*dphi_dR; } //Compute the force in the z direction double SCFPotentialzforce(double R,double Z, double phi, double t, struct potentialArg * potentialArgs) { double r; double theta; cyl_to_spher(R, Z,&r, &theta); double dr_dz = Z/r; double dtheta_dz = -R/(r*r); double dphi_dz = 0; double F[3]; computeForce(R, Z, phi, t,potentialArgs, &F[0]) ; return *(F + 0)*dr_dz + *(F + 1)*dtheta_dz + *(F + 2)*dphi_dz; } //Compute the force in the phi direction double SCFPotentialphiforce(double R,double Z, double phi, double t, struct potentialArg * potentialArgs) { double r; double theta; cyl_to_spher(R, Z, &r, &theta); double dr_dphi = 0; double dtheta_dphi = 0; double dphi_dphi = 1; double F[3]; computeForce(R, Z, phi, t,potentialArgs, &F[0]) ; return *(F + 0)*dr_dphi + *(F + 1)*dtheta_dphi + *(F + 2)*dphi_dphi; } //Compute the planar force in the R direction double SCFPotentialPlanarRforce(double R,double phi, double t, struct potentialArg * potentialArgs) { return SCFPotentialRforce(R,0., phi,t,potentialArgs); } //Compute the planar force in the phi direction double SCFPotentialPlanarphiforce(double R,double phi, double t, struct potentialArg * potentialArgs) { return SCFPotentialphiforce(R,0., phi,t,potentialArgs); } //Compute the planar double derivative of the potential with respect to R double SCFPotentialPlanarR2deriv(double R, double phi, double t, struct potentialArg * potentialArgs) { double Farray[3]; computeDeriv(R, 0, phi, t,potentialArgs, &Farray[0]) ; return *Farray; } //Compute the planar double derivative of the potential with respect to phi double SCFPotentialPlanarphi2deriv(double R, double phi, double t, struct potentialArg * potentialArgs) { double Farray[3]; computeDeriv(R, 0, phi, t,potentialArgs, &Farray[0]) ; return *(Farray + 1); } //Compute the planar double derivative of the potential with respect to R, Phi double SCFPotentialPlanarRphideriv(double R, double phi, double t, struct potentialArg * potentialArgs) { double Farray[3]; computeDeriv(R, 0, phi, t,potentialArgs, &Farray[0]) ; return *(Farray + 2); } galpy-1.3.0/galpy/potential_src/potential_c_ext/SoftenedNeedleBarPotential.c0000644000076500000240000001056713047413174027446 0ustar bovystaff00000000000000#include #include //SoftenedNeedleBarPotentials static inline void compute_TpTm(double x, double y, double z, double *Tp, double *Tm, double a, double b, double c2){ double secondpart= y * y + pow( b + sqrt ( z * z + c2 ) , 2); *Tp= sqrt ( pow ( a + x , 2) + secondpart ); *Tm= sqrt ( pow ( a - x , 2) + secondpart ); } double SoftenedNeedleBarPotentialEval(double R,double z, double phi, double t, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; //Get args: amp, a, b, c2, pa, omegab double amp= *args++; double a= *args++; double b= *args++; double c2= *args++; double pa= *args++; double omegab= *args++; double x,y; double Tp,Tm; //Calculate potential cyl_to_rect(R,phi-pa-omegab*t,&x,&y); compute_TpTm(x,y,z,&Tp,&Tm,a,b,c2); return 0.5 * amp * log( ( x - a + Tm ) / ( x + a + Tp ) ) / a; } void SoftenedNeedleBarPotentialxyzforces_xyz(double R,double z, double phi, double t,double * args, double a,double b, double c2, double pa, double omegab, double cached_R, double cached_z, double cached_phi, double cached_t){ double x,y; double Tp,Tm; double Fx, Fy, Fz; double zc; double cp, sp; if ( R != cached_R || phi != cached_phi || z != cached_z || t != cached_t){ // Set up cache *args= R; *(args + 1)= z; *(args + 2)= phi; *(args + 3)= t; // Compute forces in rectangular, aligned frame cyl_to_rect(R,phi-pa-omegab*t,&x,&y); compute_TpTm(x,y,z,&Tp,&Tm,a,b,c2); Fx= -2. * x / Tp / Tm / (Tp+Tm); Fy= -y / 2. / Tp /Tm * ( Tp + Tm -4. * x * x / (Tp+Tm) ) \ / ( y * y + pow( b + sqrt ( z * z + c2), 2)); zc= sqrt ( z * z + c2 ); Fz= Fy * z / y * ( b + zc ) / zc; cp= cos ( pa + omegab * t ); sp= sin ( pa + omegab * t ); // Rotate to rectangular, correct frame *(args + 4)= cp * Fx - sp * Fy; *(args + 5)= sp * Fx + cp * Fy; *(args + 6)= Fz; } } double SoftenedNeedleBarPotentialRforce(double R,double z, double phi, double t, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; //Get args: amp, a, b, c2, pa, omegab double amp= *args++; double a= *args++; double b= *args++; double c2= *args++; double pa= *args++; double omegab= *args++; double cached_R= *args; double cached_z= *(args + 1); double cached_phi= *(args + 2); double cached_t= *(args + 3); //Calculate potential SoftenedNeedleBarPotentialxyzforces_xyz(R,z,phi,t,args,a,b,c2,pa,omegab, cached_R,cached_z, cached_phi,cached_t); return amp * ( cos ( phi ) * *(args + 4) + sin( phi ) * *(args + 5) ); } double SoftenedNeedleBarPotentialPlanarRforce(double R,double phi,double t, struct potentialArg * potentialArgs){ return SoftenedNeedleBarPotentialRforce(R,0.,phi,t,potentialArgs); } double SoftenedNeedleBarPotentialphiforce(double R,double z, double phi, double t, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; //Get args: amp, a, b, c2, pa, omegab double amp= *args++; double a= *args++; double b= *args++; double c2= *args++; double pa= *args++; double omegab= *args++; double cached_R= *args; double cached_z= *(args + 1); double cached_phi= *(args + 2); double cached_t= *(args + 3); //Calculate potential SoftenedNeedleBarPotentialxyzforces_xyz(R,z,phi,t,args,a,b,c2,pa,omegab, cached_R,cached_z, cached_phi,cached_t); return amp * R * ( -sin ( phi ) * *(args + 4) + cos( phi ) * *(args + 5) ); } double SoftenedNeedleBarPotentialPlanarphiforce(double R, double phi,double t, struct potentialArg * potentialArgs){ return SoftenedNeedleBarPotentialphiforce(R,0.,phi,t,potentialArgs); } double SoftenedNeedleBarPotentialzforce(double R,double z, double phi, double t, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; //Get args: amp, a, b, c2, pa, omegab double amp= *args++; double a= *args++; double b= *args++; double c2= *args++; double pa= *args++; double omegab= *args++; double cached_R= *args; double cached_z= *(args + 1); double cached_phi= *(args + 2); double cached_t= *(args + 3); //Calculate potential SoftenedNeedleBarPotentialxyzforces_xyz(R,z,phi,t,args,a,b,c2,pa,omegab, cached_R,cached_z, cached_phi,cached_t); return amp * *(args + 6); } galpy-1.3.0/galpy/potential_src/potential_c_ext/SolidBodyRotationWrapperPotential.c0000644000076500000240000000540513147154373031105 0ustar bovystaff00000000000000#include #include #include //SolidBodyRotationWrapperPotential double SolidBodyRotationWrapperPotentialRforce(double R,double z,double phi, double t, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; //Calculate Rforce return *args * calcRforce(R,z,phi - *(args+1) * t - *(args+2),t, potentialArgs->nwrapped,potentialArgs->wrappedPotentialArg); } double SolidBodyRotationWrapperPotentialphiforce(double R,double z,double phi, double t, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; //Calculate phiforce return *args * calcPhiforce(R,z,phi - *(args+1) * t - *(args+2),t, potentialArgs->nwrapped,potentialArgs->wrappedPotentialArg); } double SolidBodyRotationWrapperPotentialzforce(double R,double z,double phi, double t, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; //Calculate zforce return *args * calczforce(R,z,phi - *(args+1) * t - *(args+2),t, potentialArgs->nwrapped,potentialArgs->wrappedPotentialArg); } double SolidBodyRotationWrapperPotentialPlanarRforce(double R,double phi,double t, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; //Calculate Rforce return *args * calcPlanarRforce(R,phi - *(args+1) * t - *(args+2),t, potentialArgs->nwrapped, potentialArgs->wrappedPotentialArg); } double SolidBodyRotationWrapperPotentialPlanarphiforce(double R,double phi,double t, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; //Calculate phiforce return *args * calcPlanarphiforce(R,phi - *(args+1) * t - *(args+2),t, potentialArgs->nwrapped, potentialArgs->wrappedPotentialArg); } double SolidBodyRotationWrapperPotentialPlanarR2deriv(double R,double phi,double t, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; //Calculate R2deriv return *args * calcPlanarR2deriv(R,phi - *(args+1) * t - *(args+2),t, potentialArgs->nwrapped, potentialArgs->wrappedPotentialArg); } double SolidBodyRotationWrapperPotentialPlanarphi2deriv(double R,double phi, double t, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; //Calculate phi2deriv return *args * calcPlanarphi2deriv(R,phi - *(args+1) * t - *(args+2),t, potentialArgs->nwrapped, potentialArgs->wrappedPotentialArg); } double SolidBodyRotationWrapperPotentialPlanarRphideriv(double R,double phi, double t, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; //Calculate Rphideriv return *args * calcPlanarRphideriv(R,phi - *(args+1) * t - *(args+2),t, potentialArgs->nwrapped, potentialArgs->wrappedPotentialArg); } galpy-1.3.0/galpy/potential_src/potential_c_ext/SpiralArmsPotential.c0000644000076500000240000006651613200223017026201 0ustar bovystaff00000000000000#include #include double gam(double R, double phi, double N, double phi_ref, double r_ref, double tan_alpha); double dgam_dR(double R, double N, double tan_alpha); double K(double R, double n, double N, double sin_alpha); double B(double R, double H, double n, double N, double sin_alpha); double D(double R, double H, double n, double N, double sin_alpha); double dK_dR(double R, double n, double N, double sin_alpha); double dB_dR(double R, double H, double n, double N, double sin_alpha); double dD_dR(double R, double H, double n, double N, double sin_alpha); //LCOV_EXCL_START double SpiralArmsPotentialEval(double R, double z, double phi, double t, struct potentialArg *potentialArgs) { // Get args double *args = potentialArgs->args; int nCs = (int) *args++; double amp = *args++; double N = *args++; double sin_alpha = *args++; double tan_alpha = *args++; double r_ref = *args++; double phi_ref = *args++; double Rs = *args++; double H = *args++; double omega = *args++; double g = gam(R, phi-omega*t, N, phi_ref, r_ref, tan_alpha); // Return value of the potential. double sum = 0; int n; double Cn; double Kn; double Bn; double Dn; for (n = 1; n <= nCs; n++) { Cn = *args++; Kn = K(R, n, N, sin_alpha); Bn = B(R, H, n, N, sin_alpha); Dn = D(R, H, n, N, sin_alpha); sum += Cn / Kn / Dn * cos(n * g) / pow(cosh(Kn * z / Bn), Bn); } return -amp * H * exp(-(R - r_ref) / Rs) * sum; } //LCOV_EXCL_STOP double SpiralArmsPotentialRforce(double R, double z, double phi, double t, struct potentialArg *potentialArgs) { // Get args double *args = potentialArgs->args; int nCs = (int) *args++; double amp = *args++; double N = *args++; double sin_alpha = *args++; double tan_alpha = *args++; double r_ref = *args++; double phi_ref = *args++; double Rs = *args++; double H = *args++; double omega = *args++; double g = gam(R, phi-omega*t, N, phi_ref, r_ref, tan_alpha); double dg_dR = dgam_dR(R, N, tan_alpha); // Return the Rforce (-dPhi / dR) double sum = 0; int n; double Cn; double Kn; double Bn; double Dn; double dKn_dR; double dBn_dR; double dDn_dR; double cos_ng; double sin_ng; double zKB; double sechzKB; for (n = 1; n <= nCs; n++) { Cn = *args++; Kn = K(R, n, N, sin_alpha); Bn = B(R, H, n, N, sin_alpha); Dn = D(R, H, n, N, sin_alpha); dKn_dR = dK_dR(R, n, N, sin_alpha); dBn_dR = dB_dR(R, H, n, N, sin_alpha); dDn_dR = dD_dR(R, H, n, N, sin_alpha); cos_ng = cos(n * g); sin_ng = sin(n * g); zKB = z * Kn / Bn; sechzKB = 1 / cosh(zKB); sum += Cn * pow(sechzKB, Bn) / Dn * ((n * dg_dR / Kn * sin_ng + cos_ng * (z * tanh(zKB) * (dKn_dR / Kn - dBn_dR / Bn) - dBn_dR / Kn * log(sechzKB) + dKn_dR / Kn / Kn + dDn_dR / Dn / Kn)) + cos_ng / Kn / Rs); } return -amp * H * exp(-(R - r_ref) / Rs) * sum; } double SpiralArmsPotentialzforce(double R, double z, double phi, double t, struct potentialArg *potentialArgs) { // Get args double *args = potentialArgs->args; int nCs = (int) *args++; double amp = *args++; double N = *args++; double sin_alpha = *args++; double tan_alpha = *args++; double r_ref = *args++; double phi_ref = *args++; double Rs = *args++; double H = *args++; double omega = *args++; double g = gam(R, phi-omega*t, N, phi_ref, r_ref, tan_alpha); // Return the zforce (-dPhi / dz) double sum = 0; int n; double Cn; double Kn; double Bn; double Dn; double zKn_Bn; for (n = 1; n <= nCs; n++) { Cn = *args++; Kn = K(R, n, N, sin_alpha); Bn = B(R, H, n, N, sin_alpha); Dn = D(R, H, n, N, sin_alpha); zKn_Bn = z * Kn / Bn; sum += Cn / Dn * cos(n * g) * tanh(zKn_Bn) / pow(cosh(zKn_Bn), Bn); } return -amp * H * exp(-(R - r_ref) / Rs) * sum; } double SpiralArmsPotentialphiforce(double R, double z, double phi, double t, struct potentialArg *potentialArgs) { // Get args double *args = potentialArgs->args; int nCs = (int) *args++; double amp = *args++; double N = *args++; double sin_alpha = *args++; double tan_alpha = *args++; double r_ref = *args++; double phi_ref = *args++; double Rs = *args++; double H = *args++; double omega = *args++; double g = gam(R, phi-omega*t, N, phi_ref, r_ref, tan_alpha); // Return the phiforce (-dPhi / dphi) double sum = 0; int n; double Cn; double Kn; double Bn; double Dn; for (n = 1; n <= nCs; n++) { Cn = *args++; Kn = K(R, n, N, sin_alpha); Bn = B(R, H, n, N, sin_alpha); Dn = D(R, H, n, N, sin_alpha); sum += N * n * Cn / Dn / Kn / pow(cosh(z * Kn / Bn), Bn) * sin(n * g); } return -amp * H * exp(-(R - r_ref) / Rs) * sum; } //LCOV_EXCL_START double SpiralArmsPotentialR2deriv(double R, double z, double phi, double t, struct potentialArg *potentialArgs) { // Get args double *args = potentialArgs->args; int nCs = (int) *args++; double amp = *args++; double N = *args++; double sin_alpha = *args++; double tan_alpha = *args++; double r_ref = *args++; double phi_ref = *args++; double Rs = *args++; double H = *args++; double omega = *args++; double g = gam(R, phi-omega*t, N, phi_ref, r_ref, tan_alpha); double dg_dR = dgam_dR(R, N, tan_alpha); double d2g_dR2 = N / R / R / tan_alpha; // Return the second derivative of the potential wrt R (d2Phi / dR2) double sum = 0; int n; double Cn; double Kn; double Bn; double Dn; double dKn_dR; double dBn_dR; double dDn_dR; double HNn; double HNn_R; double R_sina; double HNn_R_sina; double HNn_R_sina_2; double x; double d2Kn_dR2; double d2Bn_dR2; double d2Dn_dR2; double cos_ng; double sin_ng; double zKB; double sechzKB; double sechzKB_B; double log_sechzKB; double tanhzKB; double ztanhzKB; for (n = 1; n <= nCs; n++) { Cn = *args++; Kn = K(R, n, N, sin_alpha); Bn = B(R, H, n, N, sin_alpha); Dn = D(R, H, n, N, sin_alpha); dKn_dR = dK_dR(R, n, N, sin_alpha); dBn_dR = dB_dR(R, H, n, N, sin_alpha); dDn_dR = dD_dR(R, H, n, N, sin_alpha); HNn = H * N * n; HNn_R = HNn / R; R_sina = R * sin_alpha; HNn_R_sina = HNn / R_sina; HNn_R_sina_2 = HNn_R_sina * HNn_R_sina; x = R * (0.3 * HNn_R_sina + 1) * sin_alpha; d2Kn_dR2 = 2 * N * n / R / R / R / sin_alpha; d2Bn_dR2 = HNn_R / R / R / sin_alpha * (2.4 * HNn_R / sin_alpha + 2); d2Dn_dR2 = sin_alpha / R / x * (HNn * (0.18 * HNn * (HNn_R_sina + 0.3 * HNn_R_sina_2 + 1) / x / x + 2 / R_sina - 0.6 * HNn_R_sina * (1 + 0.6 * HNn_R_sina) / x - 0.6 * (HNn_R_sina + 0.3 * HNn_R_sina_2 + 1) / x + 1.8 * HNn / R_sina / R_sina)); cos_ng = cos(n * g); sin_ng = sin(n * g); zKB = z * Kn / Bn; sechzKB = 1 / cosh(zKB); sechzKB_B = pow(sechzKB, Bn); log_sechzKB = log(sechzKB); tanhzKB = tanh(zKB); ztanhzKB = z * tanhzKB; sum += (Cn * sechzKB_B / Dn * ((n * dg_dR / Kn * sin_ng + cos_ng * (ztanhzKB * (dKn_dR / Kn - dBn_dR / Bn) - dBn_dR / Kn * log_sechzKB + dKn_dR / Kn / Kn + dDn_dR / Dn / Kn)) - (Rs * (1 / Kn * ((ztanhzKB * (dBn_dR / Bn * Kn - dKn_dR) + log_sechzKB * dBn_dR) - dDn_dR / Dn) * (n * dg_dR * sin_ng + cos_ng * (ztanhzKB * Kn * (dKn_dR / Kn - dBn_dR / Bn) - dBn_dR * log_sechzKB + dKn_dR / Kn / Kn + dDn_dR / Dn)) + (n * (sin_ng * (d2g_dR2 / Kn - dg_dR / Kn / Kn * dKn_dR) + dg_dR * dg_dR / Kn * cos_ng * n) + z * (-sin_ng * n * dg_dR * tanhzKB * (dKn_dR / Kn - dBn_dR / Bn) + cos_ng * (z * (dKn_dR / Bn - dBn_dR / Bn / Bn * Kn) * (1 - tanhzKB * tanhzKB) * (dKn_dR / Kn - dBn_dR / Bn) + tanhzKB * (d2Kn_dR2 / Kn - (dKn_dR / Kn) * (dKn_dR / Kn) - d2Bn_dR2 / Bn + (dBn_dR / Bn) * (dBn_dR / Bn)))) + (cos_ng * (dBn_dR / Kn * ztanhzKB * (dKn_dR / Bn - dBn_dR / Bn / Bn * Kn) - (d2Bn_dR2 / Kn - dBn_dR * dKn_dR / Kn / Kn) * log_sechzKB) + dBn_dR / Kn * log_sechzKB * sin_ng * n * dg_dR) + ((cos_ng * (d2Kn_dR2 / Kn / Kn - 2 * dKn_dR * dKn_dR / Kn / Kn / Kn) - dKn_dR / Kn / Kn * sin_ng * n * dg_dR) + (cos_ng * (d2Dn_dR2 / Dn / Kn - (dDn_dR / Dn) * (dDn_dR / Dn) / Kn - dDn_dR / Dn / Kn / Kn * dKn_dR) - sin_ng * n * dg_dR * dDn_dR / Dn / Kn)))) - 1 / Kn * (cos_ng / Rs + (cos_ng * ((dDn_dR * Kn + Dn * dKn_dR) / (Dn * Kn) - (ztanhzKB * (dBn_dR / Bn * Kn - dKn_dR) + log_sechzKB * dBn_dR)) + sin_ng * n * dg_dR))))); } return -amp * H * exp(-(R - r_ref) / Rs) / Rs * sum; } //LCOV_EXCL_STOP //LCOV_EXCL_START double SpiralArmsPotentialz2deriv(double R, double z, double phi, double t, struct potentialArg *potentialArgs) { // Get args double *args = potentialArgs->args; int nCs = (int) *args++; double amp = *args++; double N = *args++; double sin_alpha = *args++; double tan_alpha = *args++; double r_ref = *args++; double phi_ref = *args++; double Rs = *args++; double H = *args++; double omega = *args++; double g = gam(R, phi-omega*t, N, phi_ref, r_ref, tan_alpha); // Return the second derivative of the potential wrt z (d2Phi / dz2) double sum = 0; int n; double Cn; double Kn; double Bn; double Dn; double zKB; double tanh2_zKB; for (n = 1; n <= nCs; n++) { Cn = *args++; Kn = K(R, n, N, sin_alpha); Bn = B(R, H, n, N, sin_alpha); Dn = D(R, H, n, N, sin_alpha); zKB = z * Kn / Bn; tanh2_zKB = tanh(zKB) * tanh(zKB); sum += Cn * Kn / Dn * ((tanh2_zKB - 1) / Bn + tanh2_zKB) * cos(n * g) / pow(cosh(zKB), Bn); } return -amp * H * exp(-(R - r_ref) / Rs) * sum; } //LCOV_EXCL_STOP //LCOV_EXCL_START double SpiralArmsPotentialphi2deriv(double R, double z, double phi, double t, struct potentialArg *potentialArgs) { // Get args double *args = potentialArgs->args; int nCs = (int) *args++; double amp = *args++; double N = *args++; double sin_alpha = *args++; double tan_alpha = *args++; double r_ref = *args++; double phi_ref = *args++; double Rs = *args++; double H = *args++; double omega = *args++; double g = gam(R, phi-omega*t, N, phi_ref, r_ref, tan_alpha); // Return the second derivative of the potential wrt phi (d2Phi / dphi2) double sum = 0; int n; double Cn; double Kn; double Bn; double Dn; for (n = 1; n <= nCs; n++) { Cn = *args++; Kn = K(R, n, N, sin_alpha); Bn = B(R, H, n, N, sin_alpha); Dn = D(R, H, n, N, sin_alpha); sum += Cn * N * N * n * n / Dn / Kn / pow(cosh(z * Kn / Bn), Bn) * cos(n * g); } return amp * H * exp(-(R - r_ref) / Rs) * sum; } //LCOV_EXCL_STOP //LCOV_EXCL_START double SpiralArmsPotentialRzderiv(double R, double z, double phi, double t, struct potentialArg *potentialArgs) { // Get args double *args = potentialArgs->args; int nCs = (int) *args++; double amp = *args++; double N = *args++; double sin_alpha = *args++; double tan_alpha = *args++; double r_ref = *args++; double phi_ref = *args++; double Rs = *args++; double H = *args++; double omega = *args++; double g = gam(R, phi-omega*t, N, phi_ref, r_ref, tan_alpha); double dg_dR = dgam_dR(R, N, tan_alpha); // Return the mixed (cylindrical) radial and vertical derivative of the potential (d^2 potential / dR dz). double sum = 0; int n; double Cn; double Kn; double Bn; double Dn; double dKn_dR; double dBn_dR; double dDn_dR; double cos_ng; double sin_ng; double zKB; double sechzKB; double sechzKB_B; double log_sechzKB; double tanhzKB; for (n = 1; n <= nCs; n++) { Cn = *args++; Kn = K(R, n, N, sin_alpha); Bn = B(R, H, n, N, sin_alpha); Dn = D(R, H, n, N, sin_alpha); dKn_dR = dK_dR(R, n, N, sin_alpha); dBn_dR = dB_dR(R, H, n, N, sin_alpha); dDn_dR = dD_dR(R, H, n, N, sin_alpha); cos_ng = cos(n * g); sin_ng = sin(n * g); zKB = z * Kn / Bn; sechzKB = 1 / cosh(zKB); sechzKB_B = pow(sechzKB, Bn); log_sechzKB = log(sechzKB); tanhzKB = tanh(zKB); sum += sechzKB_B * Cn / Dn * (Kn * tanhzKB * (n * dg_dR / Kn * sin_ng + cos_ng * (z * tanhzKB * (dKn_dR / Kn - dBn_dR / Bn) - dBn_dR / Kn * log_sechzKB + dKn_dR / Kn / Kn + dDn_dR / Dn / Kn)) - cos_ng * ((zKB * (dKn_dR / Kn - dBn_dR / Bn) * (1 - tanhzKB * tanhzKB) + tanhzKB * (dKn_dR / Kn - dBn_dR / Bn) + dBn_dR / Bn * tanhzKB) - tanhzKB / Rs)); } return -amp * H * exp(-(R - r_ref) / Rs) * sum; } //LCOV_EXCL_STOP //LCOV_EXCL_START double SpiralArmsPotentialRphideriv(double R, double z, double phi, double t, struct potentialArg *potentialArgs) { // Get args double *args = potentialArgs->args; int nCs = (int) *args++; double amp = *args++; double N = *args++; double sin_alpha = *args++; double tan_alpha = *args++; double r_ref = *args++; double phi_ref = *args++; double Rs = *args++; double H = *args++; double omega = *args++; double g = gam(R, phi-omega*t, N, phi_ref, r_ref, tan_alpha); double dg_dR = dgam_dR(R, N, tan_alpha); // Return the mixed (cylindrical) radial and azimuthal derivative of the potential (d^2 potential / dR dphi). double sum = 0; int n; double Cn; double Kn; double Bn; double Dn; double dKn_dR; double dBn_dR; double dDn_dR; double cos_ng; double sin_ng; double zKB; double sechzKB; double sechzKB_B; for (n = 1; n <= nCs; n++) { Cn = *args++; Kn = K(R, n, N, sin_alpha); Bn = B(R, H, n, N, sin_alpha); Dn = D(R, H, n, N, sin_alpha); dKn_dR = dK_dR(R, n, N, sin_alpha); dBn_dR = dB_dR(R, H, n, N, sin_alpha); dDn_dR = dD_dR(R, H, n, N, sin_alpha); cos_ng = cos(n * g); sin_ng = sin(n * g); zKB = z * Kn / Bn; sechzKB = 1 / cosh(zKB); sechzKB_B = pow(sechzKB, Bn); sum += Cn * sechzKB_B / Dn * n * N * (-n * dg_dR / Kn * cos_ng + sin_ng * (z * tanh(zKB) * (dKn_dR / Kn - dBn_dR / Bn) + 1 / Kn * (-dBn_dR * log(sechzKB) + dKn_dR / Kn + dDn_dR / Dn + 1 / Rs))); } return -amp * H * exp(-(R - r_ref) / Rs) * sum; } //LCOV_EXCL_STOP double SpiralArmsPotentialPlanarRforce(double R, double phi, double t, struct potentialArg *potentialArgs) { // Get args double *args = potentialArgs->args; int nCs = (int) *args++; double amp = *args++; double N = *args++; double sin_alpha = *args++; double tan_alpha = *args++; double r_ref = *args++; double phi_ref = *args++; double Rs = *args++; double H = *args++; double omega = *args++; double g = gam(R, phi-omega*t, N, phi_ref, r_ref, tan_alpha); double dg_dR = dgam_dR(R, N, tan_alpha); // Return the planar Rforce (-dPhi / dR) double sum = 0; int n; double Cn; double Kn; double Dn; double dKn_dR; double dDn_dR; double cos_ng; double sin_ng; for (n = 1; n <= nCs; n++) { Cn = *args++; Kn = K(R, n, N, sin_alpha); Dn = D(R, H, n, N, sin_alpha); dKn_dR = dK_dR(R, n, N, sin_alpha); dDn_dR = dD_dR(R, H, n, N, sin_alpha); cos_ng = cos(n * g); sin_ng = sin(n * g); sum += Cn / Dn * ((n * dg_dR / Kn * sin_ng + cos_ng * (dKn_dR / Kn / Kn + dDn_dR / Dn / Kn)) + cos_ng / Kn / Rs); } return -amp * H * exp(-(R - r_ref) / Rs) * sum; } double SpiralArmsPotentialPlanarphiforce(double R, double phi, double t, struct potentialArg *potentialArgs) { // Get args double *args = potentialArgs->args; int nCs = (int) *args++; double amp = *args++; double N = *args++; double sin_alpha = *args++; double tan_alpha = *args++; double r_ref = *args++; double phi_ref = *args++; double Rs = *args++; double H = *args++; double omega = *args++; double g = gam(R, phi-omega*t, N, phi_ref, r_ref, tan_alpha); // Return the planar phiforce (-dPhi / dphi) double sum = 0; int n; double Cn; double Kn; double Dn; for (n = 1; n <= nCs; n++) { Cn = *args++; Kn = K(R, n, N, sin_alpha); Dn = D(R, H, n, N, sin_alpha); sum += N * n * Cn / Dn / Kn * sin(n * g); } return -amp * H * exp(-(R - r_ref) / Rs) * sum; } double SpiralArmsPotentialPlanarR2deriv(double R, double phi, double t, struct potentialArg *potentialArgs) { // Get args double *args = potentialArgs->args; int nCs = (int) *args++; double amp = *args++; double N = *args++; double sin_alpha = *args++; double tan_alpha = *args++; double r_ref = *args++; double phi_ref = *args++; double Rs = *args++; double H = *args++; double omega = *args++; double g = gam(R, phi-omega*t, N, phi_ref, r_ref, tan_alpha); double dg_dR = dgam_dR(R, N, tan_alpha); double d2g_dR2 = N / R / R / tan_alpha; // Return the planar second derivative of the potential wrt R (d2Phi / dR2) double sum = 0; int n; double Cn; double Kn; double Dn; double dKn_dR; double dDn_dR; double HNn; double R_sina; double HNn_R_sina; double HNn_R_sina_2; double x; double d2Kn_dR2; double d2Dn_dR2; double cos_ng; double sin_ng; for (n = 1; n <= nCs; n++) { Cn = *args++; Kn = K(R, n, N, sin_alpha); Dn = D(R, H, n, N, sin_alpha); dKn_dR = dK_dR(R, n, N, sin_alpha); dDn_dR = dD_dR(R, H, n, N, sin_alpha); HNn = H * N * n; R_sina = R * sin_alpha; HNn_R_sina = HNn / R_sina; HNn_R_sina_2 = HNn_R_sina * HNn_R_sina; x = R * (0.3 * HNn_R_sina + 1) * sin_alpha; d2Kn_dR2 = 2 * N * n / R / R / R / sin_alpha; d2Dn_dR2 = sin_alpha / R / x * (HNn * (0.18 * HNn * (HNn_R_sina + 0.3 * HNn_R_sina_2 + 1) / x / x + 2 / R_sina - 0.6 * HNn_R_sina * (1 + 0.6 * HNn_R_sina) / x - 0.6 * (HNn_R_sina + 0.3 * HNn_R_sina_2 + 1) / x + 1.8 * HNn / R_sina / R_sina)); cos_ng = cos(n * g); sin_ng = sin(n * g); sum += (Cn / Dn * ((n * dg_dR / Kn * sin_ng + cos_ng * (dKn_dR / Kn / Kn + dDn_dR / Dn / Kn)) - (Rs * (1 / Kn * (-dDn_dR / Dn) * (n * dg_dR * sin_ng + cos_ng * (dKn_dR / Kn / Kn + dDn_dR / Dn)) + (n * (sin_ng * (d2g_dR2 / Kn - dg_dR / Kn / Kn * dKn_dR) + dg_dR * dg_dR / Kn * cos_ng * n) + ((cos_ng * (d2Kn_dR2 / Kn / Kn - 2 * dKn_dR * dKn_dR / Kn / Kn / Kn) - dKn_dR / Kn / Kn * sin_ng * n * dg_dR) + (cos_ng * (d2Dn_dR2 / Dn / Kn - (dDn_dR / Dn) * (dDn_dR / Dn) / Kn - dDn_dR / Dn / Kn / Kn * dKn_dR) - sin_ng * n * dg_dR * dDn_dR / Dn / Kn)))) - 1 / Kn * (cos_ng / Rs + (cos_ng * ((dDn_dR * Kn + Dn * dKn_dR) / (Dn * Kn)) + sin_ng * n * dg_dR))))); } return -amp * H * exp(-(R - r_ref) / Rs) / Rs * sum; } double SpiralArmsPotentialPlanarphi2deriv(double R, double phi, double t, struct potentialArg *potentialArgs) { // Get args double *args = potentialArgs->args; int nCs = (int) *args++; double amp = *args++; double N = *args++; double sin_alpha = *args++; double tan_alpha = *args++; double r_ref = *args++; double phi_ref = *args++; double Rs = *args++; double H = *args++; double omega = *args++; double g = gam(R, phi-omega*t, N, phi_ref, r_ref, tan_alpha); // Return the second derivative of the potential wrt phi (d2Phi / dphi2) double sum = 0; int n; double Cn; double Kn; double Dn; for (n = 1; n <= nCs; n++) { Cn = *args++; Kn = K(R, n, N, sin_alpha); Dn = D(R, H, n, N, sin_alpha); sum += Cn * N * N * n * n / Dn / Kn * cos(n * g); } return amp * H * exp(-(R - r_ref) / Rs) * sum; } double SpiralArmsPotentialPlanarRphideriv(double R, double phi, double t, struct potentialArg *potentialArgs) { // Get args double *args = potentialArgs->args; int nCs = (int) *args++; double amp = *args++; double N = *args++; double sin_alpha = *args++; double tan_alpha = *args++; double r_ref = *args++; double phi_ref = *args++; double Rs = *args++; double H = *args++; double omega = *args++; double g = gam(R, phi-omega*t, N, phi_ref, r_ref, tan_alpha); double dg_dR = dgam_dR(R, N, tan_alpha); // Return the mixed (cylindrical) radial and azimuthal derivative of the potential (d^2 potential / dR dphi). double sum = 0; int n; double Cn; double Kn; double Dn; double dKn_dR; double dDn_dR; double cos_ng; double sin_ng; for (n = 1; n <= nCs; n++) { Cn = *args++; Kn = K(R, n, N, sin_alpha); Dn = D(R, H, n, N, sin_alpha); dKn_dR = dK_dR(R, n, N, sin_alpha); dDn_dR = dD_dR(R, H, n, N, sin_alpha); cos_ng = cos(n * g); sin_ng = sin(n * g); sum += Cn / Dn * n * N * (-n * dg_dR / Kn * cos_ng + sin_ng * (1 / Kn * (dKn_dR / Kn + dDn_dR / Dn + 1 / Rs))); } return -amp * H * exp(-(R - r_ref) / Rs) * sum; } double gam(double R, double phi, double N, double phi_ref, double r_ref, double tan_alpha) { return N * (phi - phi_ref - log(R / r_ref) / tan_alpha); } double dgam_dR(double R, double N, double tan_alpha) { return -N / R / tan_alpha; } double K(double R, double n, double N, double sin_alpha) { return n * N / R / sin_alpha; } double B(double R, double H, double n, double N, double sin_alpha) { double HNn_R = H * N * n / R; return HNn_R / sin_alpha * (0.4 * HNn_R / sin_alpha + 1); } double D(double R, double H, double n, double N, double sin_alpha) { double HNn = H * N * n; return (0.3 * HNn * HNn / sin_alpha / R + HNn + R * sin_alpha) / (0.3 * HNn + R * sin_alpha); } double dK_dR(double R, double n, double N, double sin_alpha) { return -n * N / (R * R) / sin_alpha; } double dB_dR(double R, double H, double n, double N, double sin_alpha) { double HNn = H * N * n; return -HNn / R / R / R / sin_alpha / sin_alpha * (0.8 * HNn + R * sin_alpha); } double dD_dR(double R, double H, double n, double N, double sin_alpha) { double HNn_R_sina = H * N * n / R / sin_alpha; return HNn_R_sina * (0.3 * (HNn_R_sina + 0.3 * HNn_R_sina * HNn_R_sina + 1) / R / pow((0.3 * HNn_R_sina + 1), 2) - (1 / R * (1 + 0.6 * HNn_R_sina) / (0.3 * HNn_R_sina + 1))); } galpy-1.3.0/galpy/potential_src/potential_c_ext/SteadyLogSpiralPotential.c0000644000076500000240000000306613047413273027177 0ustar bovystaff00000000000000#include #include //SteadyLogSpiralPotential // double dehnenSpiralSmooth(double t,double tform, double tsteady){ double smooth, xi,deltat; //Slightly different smoothing if ( ! isnan(tform) ) if ( t < tform ) smooth= 0.; else if ( t < tsteady ) { deltat= t-tform; xi= 2.*deltat/(tsteady-tform)-1.; smooth= (3./16.*pow(xi,5.)-5./8.*pow(xi,3.)+15./16.*xi+.5); } else smooth= 1.; else smooth=1.; return smooth; } double SteadyLogSpiralPotentialRforce(double R,double phi,double t, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; //declare double smooth; //Get args double amp= *args++; double tform= *args++; double tsteady= *args++; double A= *args++; double alpha= *args++; double m= *args++; double omegas= *args++; double gamma= *args++; //Calculate Rforce smooth= dehnenSpiralSmooth(t,tform,tsteady); return amp * smooth * A / R * sin(alpha * log(R) - m * (phi-omegas*t-gamma)); } double SteadyLogSpiralPotentialphiforce(double R,double phi,double t, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; //declare double smooth; //Get args double amp= *args++; double tform= *args++; double tsteady= *args++; double A= *args++; double alpha= *args++; double m= *args++; double omegas= *args++; double gamma= *args++; //Calculate Rforce smooth= dehnenSpiralSmooth(t,tform,tsteady); return -amp * smooth * A / alpha * m * sin(alpha * log(R) - m * (phi-omegas*t-gamma)); } galpy-1.3.0/galpy/potential_src/potential_c_ext/TransientLogSpiralPotential.c0000644000076500000240000000203612671130470027706 0ustar bovystaff00000000000000#include #include //TransientLogSpiralPotential // double TransientLogSpiralPotentialRforce(double R,double phi,double t, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; //Get args double amp= *args++; double A= *args++; double to= *args++; double sigma2= *args++; double alpha= *args++; double m= *args++; double omegas= *args++; double gamma= *args++; //Calculate Rforce return amp * A * exp(-pow(t-to,2.)/2./sigma2) / R * sin(alpha*log(R)-m*(phi-omegas*t-gamma)); } double TransientLogSpiralPotentialphiforce(double R,double phi,double t, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; //Get args double amp= *args++; double A= *args++; double to= *args++; double sigma2= *args++; double alpha= *args++; double m= *args++; double omegas= *args++; double gamma= *args++; //Calculate phiforce return -amp * A * exp(-pow(t-to,2.)/2./sigma2) / alpha * m * sin(alpha*log(R)-m*(phi-omegas*t-gamma)); } galpy-1.3.0/galpy/potential_src/potential_c_ext/TwoPowerTriaxialPotential.c0000644000076500000240000002711513047413244027414 0ustar bovystaff00000000000000#include #include #include //General routines for TwoPowerTriaxialPotentials, specific potentials below //TriaxialNFWPotential static inline void rotate(double *x, double *y, double *z, double *rot){ double xp,yp,zp; xp= *(rot) * *x + *(rot+1) * *y + *(rot+2) * *z; yp= *(rot+3) * *x + *(rot+4) * *y + *(rot+5) * *z; zp= *(rot+6) * *x + *(rot+7) * *y + *(rot+8) * *z; *x= xp; *y= yp; *z= zp; } static inline void rotate_force(double *Fx, double *Fy, double *Fz, double *rot){ double Fxp,Fyp,Fzp; Fxp= *(rot) * *Fx + *(rot+3) * *Fy + *(rot+6) * *Fz; Fyp= *(rot+1) * *Fx + *(rot+4) * *Fy + *(rot+7) * *Fz; Fzp= *(rot+2) * *Fx + *(rot+5) * *Fy + *(rot+8) * *Fz; *Fx= Fxp; *Fy= Fyp; *Fz= Fzp; } static inline double dens(double m, double alpha, double beta){ if ( alpha == 1 && beta == 3) // NFW case return 1. / m / ( 1. + m ) / ( 1. + m ); else if ( alpha == 1 && beta == 4) // Hernquist case return 1. / m / ( 1. + m ) / ( 1. + m ) / ( 1. + m ); else if ( alpha == 2 && beta == 4) // Jaffe case return 1. / m / m / ( 1. + m ) / ( 1. + m ); else // not currently used //LCOV_EXCL_START return pow ( m, -alpha) * pow ( 1. + m , alpha - beta); //LCOV_EXCL_STOP } void TwoPowerTriaxialPotentialxyzforces_xyz(double x,double y, double z, double * Fx, double * Fy, double * Fz,double *args, double a, double alpha, double beta, double b2, double c2, bool aligned, double * rot, int glorder, double * glx, double * glw){ int ii; double t; double td; *args= x; *(args + 1)= y; *(args + 2)= z; if ( !aligned ) rotate(&x,&y,&z,rot); *Fx= 0.; *Fy= 0.; *Fz= 0.; for (ii=0; ii < glorder; ii++) { t= 1. / *(glx+ii) / *(glx+ii) - 1.; td= *(glw+ii) * dens( sqrt ( x * x / ( 1. + t ) + y * y / ( b2 + t ) \ + z * z / ( c2 + t ) ) / a,alpha,beta); *Fx+= td * x / ( 1. + t ); *Fy+= td * y / ( b2 + t ); *Fz+= td * z / ( c2 + t ); } *(args + 3)= *Fx; *(args + 4)= *Fy; *(args + 5)= *Fz; } double TwoPowerTriaxialPotentialRforce(double R,double z, double phi, double t, double alpha, double beta, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; //Get args double amp= *args++; double a= *args++; double b2= *args++; double c2= *args++; bool aligned= (bool) *args++; double * rot= args; args+= 9; int glorder= (int) *args++; double * glx= args; double * glw= args + glorder; args+= 2 * glorder; double cached_x= *args; double cached_y= *(args + 1); double cached_z= *(args + 2); //Calculate potential double x, y; double Fx, Fy, Fz; cyl_to_rect(R,phi,&x,&y); if ( x == cached_x && y == cached_y && z == cached_z ){ //LCOV_EXCL_START Fx= *(args + 3); Fy= *(args + 4); Fz= *(args + 5); //LCOV_EXCL_STOP } else TwoPowerTriaxialPotentialxyzforces_xyz(x,y,z,&Fx,&Fy,&Fz,args, a,alpha,beta,b2,c2, aligned,rot,glorder,glx,glw); if ( !aligned ) rotate_force(&Fx,&Fy,&Fz,rot); return amp * ( cos ( phi ) * Fx + sin( phi ) * Fy ); } double TwoPowerTriaxialPotentialphiforce(double R,double z, double phi, double t, double alpha, double beta, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; //Get args double amp= *args++; double a= *args++; double b2= *args++; double c2= *args++; bool aligned= (bool) *args++; double * rot= args; args+= 9; int glorder= (int) *args++; double * glx= args; double * glw= args + glorder; args+= 2 * glorder; double cached_x= *args; double cached_y= *(args + 1); double cached_z= *(args + 2); //Calculate potential double x, y; double Fx, Fy, Fz; cyl_to_rect(R,phi,&x,&y); if ( x == cached_x && y == cached_y && z == cached_z ){ Fx= *(args + 3); Fy= *(args + 4); Fz= *(args + 5); } else //LCOV_EXCL_START TwoPowerTriaxialPotentialxyzforces_xyz(x,y,z,&Fx,&Fy,&Fz,args, a,alpha,beta,b2,c2, aligned,rot,glorder,glx,glw); //LCOV_EXCL_STOP if ( !aligned ) rotate_force(&Fx,&Fy,&Fz,rot); return amp * R * ( -sin ( phi ) * Fx + cos( phi ) * Fy ); } double TwoPowerTriaxialPotentialzforce(double R,double z, double phi, double t, double alpha, double beta, struct potentialArg * potentialArgs){ double * args= potentialArgs->args; //Get args double amp= *args++; double a= *args++; double b2= *args++; double c2= *args++; bool aligned= (bool) *args++; double * rot= args; args+= 9; int glorder= (int) *args++; double * glx= args; double * glw= args + glorder; args+= 2 * glorder; double cached_x= *args; double cached_y= *(args + 1); double cached_z= *(args + 2); //Calculate potential double x, y; double Fx, Fy, Fz; cyl_to_rect(R,phi,&x,&y); if ( x == cached_x && y == cached_y && z == cached_z ){ Fx= *(args + + 3); Fy= *(args + + 4); Fz= *(args + + 5); } else //LCOV_EXCL_START TwoPowerTriaxialPotentialxyzforces_xyz(x,y,z,&Fx,&Fy,&Fz,args, a,alpha,beta,b2,c2, aligned,rot,glorder,glx,glw); //LCOV_EXCL_STOP if ( !aligned ) rotate_force(&Fx,&Fy,&Fz,rot); return amp * Fz; } double TriaxialNFWPotentialRforce(double R,double z, double phi, double t, struct potentialArg * potentialArgs){ return TwoPowerTriaxialPotentialRforce(R,z,phi,t,1,3,potentialArgs); } double TriaxialNFWPotentialPlanarRforce(double R,double phi,double t, struct potentialArg * potentialArgs){ return TwoPowerTriaxialPotentialRforce(R,0.,phi,t,1,3,potentialArgs); } double TriaxialNFWPotentialphiforce(double R,double z, double phi, double t, struct potentialArg * potentialArgs){ return TwoPowerTriaxialPotentialphiforce(R,z,phi,t,1,3,potentialArgs); } double TriaxialNFWPotentialPlanarphiforce(double R,double phi,double t, struct potentialArg * potentialArgs){ return TwoPowerTriaxialPotentialphiforce(R,0.,phi,t,1,3,potentialArgs); } double TriaxialNFWPotentialzforce(double R,double z, double phi, double t, struct potentialArg * potentialArgs){ return TwoPowerTriaxialPotentialzforce(R,z,phi,t,1,3,potentialArgs); } //Hernquist double TriaxialHernquistPotentialRforce(double R,double z, double phi, double t, struct potentialArg * potentialArgs){ return TwoPowerTriaxialPotentialRforce(R,z,phi,t,1,4,potentialArgs); } double TriaxialHernquistPotentialPlanarRforce(double R,double phi,double t, struct potentialArg * potentialArgs){ return TwoPowerTriaxialPotentialRforce(R,0.,phi,t,1,4,potentialArgs); } double TriaxialHernquistPotentialphiforce(double R,double z, double phi, double t, struct potentialArg * potentialArgs){ return TwoPowerTriaxialPotentialphiforce(R,z,phi,t,1,4,potentialArgs); } double TriaxialHernquistPotentialPlanarphiforce(double R,double phi,double t, struct potentialArg * potentialArgs){ return TwoPowerTriaxialPotentialphiforce(R,0.,phi,t,1,4,potentialArgs); } double TriaxialHernquistPotentialzforce(double R,double z, double phi, double t, struct potentialArg * potentialArgs){ return TwoPowerTriaxialPotentialzforce(R,z,phi,t,1,4,potentialArgs); } //Jaffe double TriaxialJaffePotentialRforce(double R,double z, double phi, double t, struct potentialArg * potentialArgs){ return TwoPowerTriaxialPotentialRforce(R,z,phi,t,2,4,potentialArgs); } double TriaxialJaffePotentialPlanarRforce(double R,double phi,double t, struct potentialArg * potentialArgs){ return TwoPowerTriaxialPotentialRforce(R,0.,phi,t,2,4,potentialArgs); } double TriaxialJaffePotentialphiforce(double R,double z, double phi, double t, struct potentialArg * potentialArgs){ return TwoPowerTriaxialPotentialphiforce(R,z,phi,t,2,4,potentialArgs); } double TriaxialJaffePotentialPlanarphiforce(double R,double phi,double t, struct potentialArg * potentialArgs){ return TwoPowerTriaxialPotentialphiforce(R,0.,phi,t,2,4,potentialArgs); } double TriaxialJaffePotentialzforce(double R,double z, double phi, double t, struct potentialArg * potentialArgs){ return TwoPowerTriaxialPotentialzforce(R,z,phi,t,2,4,potentialArgs); } // Implement the potentials separately //NFW static inline double TriaxialNFWPotential_psi(double x){ return 1. / ( 1. + x ); } double TriaxialNFWPotentialpotential_xyz_integrand(double s, double x,double y, double z,double a, double b2,double c2){ double t= 1. / s / s - 1.; return TriaxialNFWPotential_psi( sqrt ( x * x / ( 1. + t ) \ + y * y / ( b2 + t ) \ + z * z / ( c2 + t ) ) / a ); } double TriaxialNFWPotentialEval(double R,double z, double phi, double t, struct potentialArg * potentialArgs){ int ii; double * args= potentialArgs->args; //Get args double amp= *args++; double a= *args++; double b2= *args++; double c2= *args++; bool aligned= (bool) *args++; double * rot= args; args+= 9; int glorder= (int) *args++; double * glx= args; double * glw= args + glorder; //Calculate potential double x, y; double out= 0.; cyl_to_rect(R,phi,&x,&y); if ( !aligned ) rotate(&x,&y,&z,rot); for (ii=0; ii < glorder; ii++) out+= *(glw+ii) * a * a \ * TriaxialNFWPotentialpotential_xyz_integrand(*(glx+ii),x,y,z, a,b2,c2); return amp * out; } //Hernquist static inline double TriaxialHernquistPotential_psi(double x){ return 0.5 / ( 1. + x ) / ( 1. + x ); } double TriaxialHernquistPotentialpotential_xyz_integrand(double s, double x,double y, double z,double a, double b2,double c2){ double t= 1. / s / s - 1.; return TriaxialHernquistPotential_psi( sqrt ( x * x / ( 1. + t ) \ + y * y / ( b2 + t ) \ + z * z / ( c2 + t ) ) / a ); } double TriaxialHernquistPotentialEval(double R,double z, double phi, double t, struct potentialArg * potentialArgs){ int ii; double * args= potentialArgs->args; //Get args double amp= *args++; double a= *args++; double b2= *args++; double c2= *args++; bool aligned= (bool) *args++; double * rot= args; args+= 9; int glorder= (int) *args++; double * glx= args; double * glw= args + glorder; //Calculate potential double x, y; double out= 0.; cyl_to_rect(R,phi,&x,&y); if ( !aligned ) rotate(&x,&y,&z,rot); for (ii=0; ii < glorder; ii++) out+= *(glw+ii) * a * a \ * TriaxialHernquistPotentialpotential_xyz_integrand(*(glx+ii),x,y,z, a,b2,c2); return amp * out; } //Jaffe static inline double TriaxialJaffePotential_psi(double x){ return - 1. / ( 1. + x ) - log ( x / ( 1. + x ) ) ; } double TriaxialJaffePotentialpotential_xyz_integrand(double s, double x,double y, double z,double a, double b2,double c2){ double t= 1. / s / s - 1.; return TriaxialJaffePotential_psi( sqrt ( x * x / ( 1. + t ) \ + y * y / ( b2 + t ) \ + z * z / ( c2 + t ) ) / a ); } double TriaxialJaffePotentialEval(double R,double z, double phi, double t, struct potentialArg * potentialArgs){ int ii; double * args= potentialArgs->args; //Get args double amp= *args++; double a= *args++; double b2= *args++; double c2= *args++; bool aligned= (bool) *args++; double * rot= args; args+= 9; int glorder= (int) *args++; double * glx= args; double * glw= args + glorder; //Calculate potential double x, y; double out= 0.; cyl_to_rect(R,phi,&x,&y); if ( !aligned ) rotate(&x,&y,&z,rot); for (ii=0; ii < glorder; ii++) out+= *(glw+ii) * a * a \ * TriaxialJaffePotentialpotential_xyz_integrand(*(glx+ii),x,y,z, a,b2,c2); return amp * out; } galpy-1.3.0/galpy/potential_src/potential_c_ext/ZeroPotential.c0000644000076500000240000000043012671130470025035 0ustar bovystaff00000000000000#include //Dummy zero forces double ZeroPlanarForce(double R, double phi,double t, struct potentialArg * potentialArgs){ return 0.; } double ZeroForce(double R, double Z, double phi,double t, struct potentialArg * potentialArgs){ return 0.; } galpy-1.3.0/galpy/potential_src/PowerSphericalPotential.py0000644000076500000240000001621113003761364024100 0ustar bovystaff00000000000000############################################################################### # PowerSphericalPotential.py: General class for potentials derived from # densities with two power-laws # # amp # rho(r)= --------- # r^\alpha ############################################################################### import numpy as nu from scipy import special, integrate from galpy.potential_src.Potential import Potential, _APY_LOADED if _APY_LOADED: from astropy import units class PowerSphericalPotential(Potential): """Class that implements spherical potentials that are derived from power-law density models .. math:: \\rho(r) = \\frac{\\mathrm{amp}}{r_1^3}\\,\\left(\\frac{r_1}{r}\\right)^{\\alpha} """ def __init__(self,amp=1.,alpha=1.,normalize=False,r1=1., ro=None,vo=None): """ NAME: __init__ PURPOSE: initialize a power-law-density potential INPUT: amp - amplitude to be applied to the potential (default: 1); can be a Quantity with units of mass density or Gxmass density alpha - inner power r1= (1.) reference radius for amplitude (can be Quantity) normalize - if True, normalize such that vc(1.,0.)=1., or, if given as a number, such that the force is this fraction of the force necessary to make vc(1.,0.)=1. ro=, vo= distance and velocity scales for translation into internal units (default from configuration file) OUTPUT: (none) HISTORY: 2010-07-10 - Written - Bovy (NYU) """ Potential.__init__(self,amp=amp,ro=ro,vo=vo,amp_units='mass') if _APY_LOADED and isinstance(r1,units.Quantity): r1= r1.to(units.kpc).value/self._ro self.alpha= alpha # Back to old definition if self.alpha != 3.: self._amp*= r1**(self.alpha-3.)*4.*nu.pi/(3.-self.alpha) if normalize or \ (isinstance(normalize,(int,float)) \ and not isinstance(normalize,bool)): self.normalize(normalize) self.hasC= True self.hasC_dxdv= True def _evaluate(self,R,z,phi=0.,t=0.): """ NAME: _evaluate PURPOSE: evaluate the potential at R,z INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t - time OUTPUT: Phi(R,z) HISTORY: 2010-07-10 - Started - Bovy (NYU) """ if self.alpha == 2.: return nu.log(R**2.+z**2.)/2. else: return -(R**2.+z**2.)**(1.-self.alpha/2.)/(self.alpha-2.) def _Rforce(self,R,z,phi=0.,t=0.): """ NAME: _Rforce PURPOSE: evaluate the radial force for this potential INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t - time OUTPUT: the radial force HISTORY: 2010-07-10 - Written - Bovy (NYU) """ return -R/(R**2.+z**2.)**(self.alpha/2.) def _zforce(self,R,z,phi=0.,t=0.): """ NAME: _zforce PURPOSE: evaluate the vertical force for this potential INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t - time OUTPUT: the vertical force HISTORY: 2010-07-10 - Written - Bovy (NYU) """ return -z/(R**2.+z**2.)**(self.alpha/2.) def _R2deriv(self,R,z,phi=0.,t=0.): """ NAME: _Rderiv PURPOSE: evaluate the second radial derivative for this potential INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t - time OUTPUT: the second radial derivative HISTORY: 2011-10-09 - Written - Bovy (NYU) """ return 1./(R**2.+z**2.)**(self.alpha/2.)\ -self.alpha*R**2./(R**2.+z**2.)**(self.alpha/2.+1.) def _z2deriv(self,R,z,phi=0.,t=0.): """ NAME: _z2deriv PURPOSE: evaluate the second vertical derivative for this potential INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t- time OUTPUT: the second vertical derivative HISTORY: 2012-07-26 - Written - Bovy (IAS@MPIA) """ return self._R2deriv(z,R) #Spherical potential def _Rzderiv(self,R,z,phi=0.,t=0.): """ NAME: _Rzderiv PURPOSE: evaluate the mixed R,z derivative for this potential INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t - time OUTPUT: d2phi/dR/dz HISTORY: 2013-08-28 - Written - Bovy (IAs) """ return -self.alpha*R*z*(R**2.+z**2.)**(-1.-self.alpha/2.) def _dens(self,R,z,phi=0.,t=0.): """ NAME: _dens PURPOSE: evaluate the density force for this potential INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t - time OUTPUT: the density HISTORY: 2013-01-09 - Written - Bovy (IAS) """ r= nu.sqrt(R**2.+z**2.) return (3.-self.alpha)/4./nu.pi/r**self.alpha class KeplerPotential(PowerSphericalPotential): """Class that implements the Kepler potential .. math:: \\Phi(r) = -\\frac{\\mathrm{amp}}{r} """ def __init__(self,amp=1.,normalize=False, ro=None,vo=None): """ NAME: __init__ PURPOSE: initialize a Kepler potential INPUT: amp - amplitude to be applied to the potential (default: 1); can be a Quantity with units of mass density or Gxmass density alpha - inner power normalize - if True, normalize such that vc(1.,0.)=1., or, if given as a number, such that the force is this fraction of the force necessary to make vc(1.,0.)=1. ro=, vo= distance and velocity scales for translation into internal units (default from configuration file) OUTPUT: (none) HISTORY: 2010-07-10 - Written - Bovy (NYU) """ PowerSphericalPotential.__init__(self,amp=amp,normalize=normalize, alpha=3.,ro=ro,vo=vo) def _mass(self,R,z=0.,t=0.): """ NAME: _mass PURPOSE: evaluate the mass within R for this potential INPUT: R - Galactocentric cylindrical radius z - vertical height t - time OUTPUT: the mass enclosed HISTORY: 2014-07-02 - Written - Bovy (IAS) """ return 1. galpy-1.3.0/galpy/potential_src/PowerSphericalPotentialwCutoff.py0000644000076500000240000001654213003761364025445 0ustar bovystaff00000000000000############################################################################### # PowerSphericalPotentialwCutoff.py: spherical power-law potential w/ cutoff # # amp # rho(r)= --------- e^{-(r/rc)^2} # r^\alpha ############################################################################### import numpy as nu from scipy import special, integrate from galpy.potential_src.Potential import Potential, kms_to_kpcGyrDecorator, \ _APY_LOADED if _APY_LOADED: from astropy import units class PowerSphericalPotentialwCutoff(Potential): """Class that implements spherical potentials that are derived from power-law density models .. math:: \\rho(r) = \\mathrm{amp}\,\\left(\\frac{r_1}{r}\\right)^\\alpha\\,\\exp\\left(-(r/rc)^2\\right) """ def __init__(self,amp=1.,alpha=1.,rc=1.,normalize=False,r1=1., ro=None,vo=None): """ NAME: __init__ PURPOSE: initialize a power-law-density potential INPUT: amp= amplitude to be applied to the potential (default: 1); can be a Quantity with units of mass density or Gxmass density alpha= inner power rc= cut-off radius (can be Quantity) r1= (1.) reference radius for amplitude (can be Quantity) normalize= if True, normalize such that vc(1.,0.)=1., or, if given as a number, such that the force is this fraction of the force necessary to make vc(1.,0.)=1. ro=, vo= distance and velocity scales for translation into internal units (default from configuration file) OUTPUT: (none) HISTORY: 2013-06-28 - Written - Bovy (IAS) """ Potential.__init__(self,amp=amp,ro=ro,vo=vo,amp_units='density') if _APY_LOADED and isinstance(r1,units.Quantity): r1= r1.to(units.kpc).value/self._ro if _APY_LOADED and isinstance(rc,units.Quantity): rc= rc.to(units.kpc).value/self._ro self.alpha= alpha # Back to old definition self._amp*= r1**self.alpha self.rc= rc self._scale= self.rc if normalize or \ (isinstance(normalize,(int,float)) \ and not isinstance(normalize,bool)): #pragma: no cover self.normalize(normalize) self.hasC= True self.hasC_dxdv= True self._nemo_accname= 'PowSphwCut' def _evaluate(self,R,z,phi=0.,t=0.): """ NAME: _evaluate PURPOSE: evaluate the potential at R,z INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t - time OUTPUT: Phi(R,z) HISTORY: 2013-06-28 - Started - Bovy (IAS) """ r= nu.sqrt(R**2.+z**2.) return 2.*nu.pi*self.rc**(3.-self.alpha)/r*(r/self.rc*special.gamma(1.-self.alpha/2.)*special.gammainc(1.-self.alpha/2.,(r/self.rc)**2.)-special.gamma(1.5-self.alpha/2.)*special.gammainc(1.5-self.alpha/2.,(r/self.rc)**2.)) def _Rforce(self,R,z,phi=0.,t=0.): """ NAME: _Rforce PURPOSE: evaluate the radial force for this potential INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t - time OUTPUT: the radial force HISTORY: 2013-06-26 - Written - Bovy (IAS) """ r= nu.sqrt(R*R+z*z) return -self._mass(r)*R/r**3. def _zforce(self,R,z,phi=0.,t=0.): """ NAME: _zforce PURPOSE: evaluate the vertical force for this potential INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t - time OUTPUT: the vertical force HISTORY: 2013-06-26 - Written - Bovy (IAS) """ r= nu.sqrt(R*R+z*z) return -self._mass(r)*z/r**3. def _R2deriv(self,R,z,phi=0.,t=0.): """ NAME: _Rderiv PURPOSE: evaluate the second radial derivative for this potential INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t - time OUTPUT: the second radial derivative HISTORY: 2013-06-28 - Written - Bovy (IAS) """ r= nu.sqrt(R*R+z*z) return 4.*nu.pi*r**(-2.-self.alpha)*nu.exp(-(r/self.rc)**2.)*R**2.\ +self._mass(r)/r**5.*(z**2.-2.*R**2.) def _z2deriv(self,R,z,phi=0.,t=0.): """ NAME: _z2deriv PURPOSE: evaluate the second vertical derivative for this potential INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t- time OUTPUT: the second vertical derivative HISTORY: 2013-06-28 - Written - Bovy (IAS) """ r= nu.sqrt(R*R+z*z) return 4.*nu.pi*r**(-2.-self.alpha)*nu.exp(-(r/self.rc)**2.)*z**2.\ +self._mass(r)/r**5.*(R**2.-2.*z**2.) def _Rzderiv(self,R,z,phi=0.,t=0.): """ NAME: _Rzderiv PURPOSE: evaluate the mixed R,z derivative for this potential INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t- time OUTPUT: d2phi/dR/dz HISTORY: 2013-08-28 - Written - Bovy (IAS) """ r= nu.sqrt(R*R+z*z) return R*z*(4.*nu.pi*r**(-2.-self.alpha)*nu.exp(-(r/self.rc)**2.) -3.*self._mass(r)/r**5.) def _dens(self,R,z,phi=0.,t=0.): """ NAME: _dens PURPOSE: evaluate the density force for this potential INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t - time OUTPUT: the density HISTORY: 2013-06-28 - Written - Bovy (IAS) """ r= nu.sqrt(R**2.+z**2.) return 1./r**self.alpha*nu.exp(-(r/self.rc)**2.) def _mass(self,R,z=0.,t=0.): """ NAME: _mass PURPOSE: evaluate the mass within R for this potential INPUT: R - Galactocentric cylindrical radius z - vertical height t - time OUTPUT: the mass enclosed HISTORY: 2013-XX-XX - Written - Bovy (IAS) """ if z is None: r= R else: r= nu.sqrt(R**2.+z**2.) return 2.*nu.pi*self.rc**(3.-self.alpha)*special.gammainc(1.5-self.alpha/2.,(r/self.rc)**2.)*special.gamma(1.5-self.alpha/2.) @kms_to_kpcGyrDecorator def _nemo_accpars(self,vo,ro): """ NAME: _nemo_accpars PURPOSE: return the accpars potential parameters for use of this potential with NEMO INPUT: vo - velocity unit in km/s ro - length unit in kpc OUTPUT: accpars string HISTORY: 2014-12-18 - Written - Bovy (IAS) """ ampl= self._amp*vo**2.*ro**(self.alpha-2.) return "0,%s,%s,%s" % (ampl,self.alpha,self.rc*ro) galpy-1.3.0/galpy/potential_src/PseudoIsothermalPotential.py0000644000076500000240000001352313003761364024443 0ustar bovystaff00000000000000############################################################################### # PseudoIsothermalPotential.py: class that implements the pseudo-isothermal # halo potential ############################################################################### import numpy as nu from galpy.potential_src.Potential import Potential, _APY_LOADED if _APY_LOADED: from astropy import units class PseudoIsothermalPotential(Potential): """Class that implements the pseudo-isothermal potential .. math:: \\rho(r) = \\frac{\\mathrm{amp}}{4\\,\pi\\, a^3}\\,\\frac{1}{1+(r/a)^2} """ def __init__(self,amp=1.,a=1.,normalize=False, ro=None,vo=None): """ NAME: __init__ PURPOSE: initialize a pseudo-isothermal potential INPUT: amp - amplitude to be applied to the potential (default: 1); can be a Quantity with units of mass or Gxmass a - core radius (can be Quantity) normalize - if True, normalize such that vc(1.,0.)=1., or, if given as a number, such that the force is this fraction of the force necessary to make vc(1.,0.)=1. ro=, vo= distance and velocity scales for translation into internal units (default from configuration file) OUTPUT: (none) HISTORY: 2015-12-04 - Started - Bovy (UofT) """ Potential.__init__(self,amp=amp,ro=ro,vo=vo,amp_units='mass') if _APY_LOADED and isinstance(a,units.Quantity): a= a.to(units.kpc).value/self._ro self.hasC= True self.hasC_dxdv= True self._a= a self._a2= a**2. self._a3= a**3. if normalize or \ (isinstance(normalize,(int,float)) \ and not isinstance(normalize,bool)): #pragma: no cover self.normalize(normalize) return None def _evaluate(self,R,z,phi=0.,t=0.): """ NAME: _evaluate PURPOSE: evaluate the potential at R,z INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t - time OUTPUT: Phi(R,z) HISTORY: 2015-12-04 - Started - Bovy (UofT) """ r2= R**2.+z**2. r= nu.sqrt(r2) return (0.5*nu.log(1+r2/self._a2)\ +self._a/r*nu.arctan(r/self._a))/self._a def _Rforce(self,R,z,phi=0.,t=0.): """ NAME: _Rforce PURPOSE: evaluate the radial force for this potential INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t - time OUTPUT: the radial force HISTORY: 2015-12-04 - Started - Bovy (UofT) """ r2= R**2.+z**2. r= nu.sqrt(r2) return -(1./r-self._a/r2*nu.arctan(r/self._a))/self._a*R/r def _zforce(self,R,z,phi=0.,t=0.): """ NAME: _zforce PURPOSE: evaluate the vertical force for this potential INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t - time OUTPUT: the vertical force HISTORY: 2015-12-04 - Started - Bovy (UofT) """ r2= R**2.+z**2. r= nu.sqrt(r2) return -(1./r-self._a/r2*nu.arctan(r/self._a))/self._a*z/r def _dens(self,R,z,phi=0.,t=0.): """ NAME: _dens PURPOSE: evaluate the density for this potential INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t - time OUTPUT: the density HISTORY: 2015-12-04 - Started - Bovy (UofT) """ return 1./(1.+(R**2.+z**2.)/self._a2)/4./nu.pi/self._a3 def _R2deriv(self,R,z,phi=0.,t=0.): """ NAME: _R2deriv PURPOSE: evaluate the second radial derivative for this potential INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t - time OUTPUT: the second radial derivative HISTORY: 2011-10-09 - Written - Bovy (IAS) """ r2= R**2.+z**2. r= nu.sqrt(r2) return (1./r2*(1.-R**2./r2*(3.*self._a2+2.*r2)/(self._a2+r2))\ +self._a/r2/r*(3.*R**2./r2-1.)*nu.arctan(r/self._a))\ /self._a def _z2deriv(self,R,z,phi=0.,t=0.): """ NAME: _z2deriv PURPOSE: evaluate the second vertical derivative for this potential INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t - time OUTPUT: the second vertical derivative HISTORY: 2012-07-25 - Written - Bovy (IAS@MPIA) """ r2= R**2.+z**2. r= nu.sqrt(r2) return (1./r2*(1.-z**2./r2*(3.*self._a2+2.*r2)/(self._a2+r2))\ +self._a/r2/r*(3.*z**2./r2-1.)*nu.arctan(r/self._a))\ /self._a def _Rzderiv(self,R,z,phi=0.,t=0.): """ NAME: _Rzderiv PURPOSE: evaluate the mixed R,z derivative for this potential INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t - time OUTPUT: d2Phi/dR/dz HISTORY: 2013-08-28 - Written - Bovy (IAS) """ r2= R**2.+z**2. r= nu.sqrt(r2) return (3.*self._a/r2/r2*nu.arctan(r/self._a)\ -1./r2/r*((3.*self._a2+2.*r2)/(r2+self._a2)))*R*z/r\ /self._a galpy-1.3.0/galpy/potential_src/RazorThinExponentialDiskPotential.py0000644000076500000240000002046313003761364026117 0ustar bovystaff00000000000000############################################################################### # RazorThinExponentialDiskPotential.py: class that implements the razor thin # exponential disk potential # # rho(R,z) = rho_0 e^-R/h_R delta(z) ############################################################################### import numpy as nu import warnings from scipy import special, integrate from galpy.util import galpyWarning from galpy.potential_src.Potential import Potential, _APY_LOADED if _APY_LOADED: from astropy import units _TOL= 1.4899999999999999e-15 _MAXITER= 20 class RazorThinExponentialDiskPotential(Potential): """Class that implements the razor-thin exponential disk potential .. math:: \\rho(R,z) = \\mathrm{amp}\\,\\exp\\left(-R/h_R\\right)\\,\\delta(z) """ def __init__(self,amp=1.,hr=1./3., maxiter=_MAXITER,tol=0.001,normalize=False, ro=None,vo=None, new=True,glorder=100): """ NAME: __init__ PURPOSE: initialize a razor-thin-exponential disk potential INPUT: amp - amplitude to be applied to the potential (default: 1); can be a Quantity with units of surface-mass or Gxsurface-mass hr - disk scale-length (can be Quantity) tol - relative accuracy of potential-evaluations maxiter - scipy.integrate keyword normalize - if True, normalize such that vc(1.,0.)=1., or, if given as a number, such that the force is this fraction of the force necessary to make vc(1.,0.)=1. ro=, vo= distance and velocity scales for translation into internal units (default from configuration file) OUTPUT: RazorThinExponentialDiskPotential object HISTORY: 2012-12-27 - Written - Bovy (IAS) """ Potential.__init__(self,amp=amp,ro=ro,vo=vo,amp_units='surfacedensity') if _APY_LOADED and isinstance(hr,units.Quantity): hr= hr.to(units.kpc).value/self._ro self._new= new self._glorder= glorder self._hr= hr self._scale= self._hr self._alpha= 1./self._hr self._maxiter= maxiter self._tol= tol self._glx, self._glw= nu.polynomial.legendre.leggauss(self._glorder) if normalize or \ (isinstance(normalize,(int,float)) \ and not isinstance(normalize,bool)): #pragma: no cover self.normalize(normalize) #Load Kepler potential for large R #self._kp= KeplerPotential(normalize=4.*nu.pi/self._alpha**2./self._beta) def _evaluate(self,R,z,phi=0.,t=0.): """ NAME: _evaluate PURPOSE: evaluate the potential at (R,z) INPUT: R - Cylindrical Galactocentric radius z - vertical height phi - azimuth t - time OUTPUT: potential at (R,z) HISTORY: 2012-12-26 - Written - Bovy (IAS) """ if self._new: #if R > 6.: return self._kp(R,z) if nu.fabs(z) < 10.**-6.: y= 0.5*self._alpha*R return -nu.pi*R*(special.i0(y)*special.k1(y)-special.i1(y)*special.k0(y)) kalphamax= 10. ks= kalphamax*0.5*(self._glx+1.) weights= kalphamax*self._glw sqrtp= nu.sqrt(z**2.+(ks+R)**2.) sqrtm= nu.sqrt(z**2.+(ks-R)**2.) evalInt= nu.arcsin(2.*ks/(sqrtp+sqrtm))*ks*special.k0(self._alpha*ks) return -2.*self._alpha*nu.sum(weights*evalInt) raise NotImplementedError("Not new=True not implemented for RazorThinExponentialDiskPotential") def _Rforce(self,R,z,phi=0.,t=0.): """ NAME: Rforce PURPOSE: evaluate radial force K_R (R,z) INPUT: R - Cylindrical Galactocentric radius z - vertical height phi - azimuth t - time OUTPUT: K_R (R,z) HISTORY: 2012-12-27 - Written - Bovy (IAS) """ if self._new: #if R > 6.: return self._kp(R,z) if nu.fabs(z) < 10.**-6.: y= 0.5*self._alpha*R return -2.*nu.pi*y*(special.i0(y)*special.k0(y)-special.i1(y)*special.k1(y)) kalphamax1= R ks1= kalphamax1*0.5*(self._glx+1.) weights1= kalphamax1*self._glw sqrtp= nu.sqrt(z**2.+(ks1+R)**2.) sqrtm= nu.sqrt(z**2.+(ks1-R)**2.) evalInt1= ks1**2.*special.k0(ks1*self._alpha)*((ks1+R)/sqrtp-(ks1-R)/sqrtm)/nu.sqrt(R**2.+z**2.-ks1**2.+sqrtp*sqrtm)/(sqrtp+sqrtm) if R < 10.: kalphamax2= 10. ks2= (kalphamax2-kalphamax1)*0.5*(self._glx+1.)+kalphamax1 weights2= (kalphamax2-kalphamax1)*self._glw sqrtp= nu.sqrt(z**2.+(ks2+R)**2.) sqrtm= nu.sqrt(z**2.+(ks2-R)**2.) evalInt2= ks2**2.*special.k0(ks2*self._alpha)*((ks2+R)/sqrtp-(ks2-R)/sqrtm)/nu.sqrt(R**2.+z**2.-ks2**2.+sqrtp*sqrtm)/(sqrtp+sqrtm) return -2.*nu.sqrt(2.)*self._alpha*nu.sum(weights1*evalInt1 +weights2*evalInt2) else: return -2.*nu.sqrt(2.)*self._alpha*nu.sum(weights1*evalInt1) raise NotImplementedError("Not new=True not implemented for RazorThinExponentialDiskPotential") def _zforce(self,R,z,phi=0.,t=0.): """ NAME: zforce PURPOSE: evaluate vertical force K_z (R,z) INPUT: R - Cylindrical Galactocentric radius z - vertical height phi - azimuth t - time OUTPUT: K_z (R,z) HISTORY: 2012-12-27 - Written - Bovy (IAS) """ if self._new: #if R > 6.: return self._kp(R,z) if nu.fabs(z) < 10.**-6.: return 0. kalphamax1= R ks1= kalphamax1*0.5*(self._glx+1.) weights1= kalphamax1*self._glw sqrtp= nu.sqrt(z**2.+(ks1+R)**2.) sqrtm= nu.sqrt(z**2.+(ks1-R)**2.) evalInt1= ks1**2.*special.k0(ks1*self._alpha)*(1./sqrtp+1./sqrtm)/nu.sqrt(R**2.+z**2.-ks1**2.+sqrtp*sqrtm)/(sqrtp+sqrtm) if R < 10.: kalphamax2= 10. ks2= (kalphamax2-kalphamax1)*0.5*(self._glx+1.)+kalphamax1 weights2= (kalphamax2-kalphamax1)*self._glw sqrtp= nu.sqrt(z**2.+(ks2+R)**2.) sqrtm= nu.sqrt(z**2.+(ks2-R)**2.) evalInt2= ks2**2.*special.k0(ks2*self._alpha)*(1./sqrtp+1./sqrtm)/nu.sqrt(R**2.+z**2.-ks2**2.+sqrtp*sqrtm)/(sqrtp+sqrtm) return -z*2.*nu.sqrt(2.)*self._alpha*nu.sum(weights1*evalInt1 +weights2*evalInt2) else: return -z*2.*nu.sqrt(2.)*self._alpha*nu.sum(weights1*evalInt1) raise NotImplementedError("Not new=True not implemented for RazorThinExponentialDiskPotential") def _R2deriv(self,R,z,phi=0.,t=0.): """ NAME: R2deriv PURPOSE: evaluate R2 derivative INPUT: R - Cylindrical Galactocentric radius z - vertical height phi - azimuth t - time OUTPUT: -d K_R (R,z) d R HISTORY: 2012-12-27 - Written - Bovy (IAS) """ if self._new: if nu.fabs(z) < 10.**-6.: y= 0.5*self._alpha*R return nu.pi*self._alpha*(special.i0(y)*special.k0(y)-special.i1(y)*special.k1(y)) \ +nu.pi/4.*self._alpha**2.*R*(special.i1(y)*(3.*special.k0(y)+special.kn(2,y))-special.k1(y)*(3.*special.i0(y)+special.iv(2,y))) raise AttributeError("'R2deriv' for RazorThinExponentialDisk not implemented for z =/= 0") def _z2deriv(self,R,z,phi=0.,t=0.): #pragma: no cover """ NAME: z2deriv PURPOSE: evaluate z2 derivative INPUT: R - Cylindrical Galactocentric radius z - vertical height phi - azimuth t - time OUTPUT: -d K_z (R,z) d z HISTORY: 2012-12-27 - Written - Bovy (IAS) """ return nu.infty galpy-1.3.0/galpy/potential_src/SCFPotential.py0000644000076500000240000007217513113032763021573 0ustar bovystaff00000000000000 import numpy as nu from galpy.potential_src.Potential import Potential, _APY_LOADED if _APY_LOADED: from astropy import units from galpy.util import bovy_coords from scipy.special import eval_gegenbauer, lpmn, gamma from numpy.polynomial.legendre import leggauss from scipy.special import gammaln import hashlib class SCFPotential(Potential): """Class that implements the `Hernquist & Ostriker (1992) `_ Self-Consistent-Field-type potential. Note that we divide the amplitude by 2 such that :math:`Acos = \\delta_{0n}\\delta_{0l}\\delta_{0m}` and :math:`Asin = 0` corresponds to :ref:`Galpy's Hernquist Potential `. .. math:: \\rho(r, \\theta, \\phi) = \\frac{amp}{2}\\sum_{n=0}^{\\infty} \\sum_{l=0}^{\\infty} \\sum_{m=0}^l N_{lm} P_{lm}(\\cos(\\theta)) \\tilde{\\rho}_{nl}(r) \\left(A_{cos, nlm} \\cos(m\\phi) + A_{sin, nlm} \\sin(m\\phi)\\right) where .. math:: \\tilde{\\rho}_{nl}(r) = \\frac{K_{nl}}{\\sqrt{\\pi}} \\frac{(a r)^l}{(r/a) (a + r)^{2l + 3}} C_{n}^{2l + 3/2}(\\xi) .. math:: \\Phi(r, \\theta, \\phi) = \\sum_{n=0}^{\\infty} \\sum_{l=0}^{\\infty} \\sum_{m=0}^l N_{lm} P_{lm}(\\cos(\\theta)) \\tilde{\\Phi}_{nl}(r) \\left(A_{cos, nlm} \\cos(m\\phi) + A_{sin, nlm} \\sin(m\\phi)\\right) where .. math:: \\tilde{\\Phi}_{nl}(r) = -\\sqrt{4 \\pi}K_{nl} \\frac{(ar)^l}{(a + r)^{2l + 1}} C_{n}^{2l + 3/2}(\\xi) where .. math:: \\xi = \\frac{r - a}{r + a} \\qquad N_{lm} = \\sqrt{\\frac{2l + 1}{4\\pi} \\frac{(l - m)!}{(l + m)!}}(2 - \\delta_{m0}) \\qquad K_{nl} = \\frac{1}{2} n (n + 4l + 3) + (l + 1)(2l + 1) and :math:`P_{lm}` is the Associated Legendre Polynomials whereas :math:`C_n^{\\alpha}` is the Gegenbauer polynomial. """ def __init__(self, amp=1., Acos=nu.array([[[1]]]),Asin=None, a = 1., normalize=False, ro=None,vo=None): """ NAME: __init__ PURPOSE: initialize a SCF Potential INPUT: amp - amplitude to be applied to the potential (default: 1); can be a Quantity with units of mass or Gxmass Acos - The real part of the expansion coefficent (NxLxL matrix, or optionally NxLx1 if Asin=None) Asin - The imaginary part of the expansion coefficient (NxLxL matrix or None) a - scale length (can be Quantity) normalize - if True, normalize such that vc(1.,0.)=1., or, if given as a number, such that the force is this fraction of the force necessary to make vc(1.,0.)=1. ro=, vo= distance and velocity scales for translation into internal units (default from configuration file) OUTPUT: SCFPotential object HISTORY: 2016-05-13 - Written - Aladdin """ Potential.__init__(self,amp=amp/2.,ro=ro,vo=vo,amp_units='mass') if _APY_LOADED and isinstance(a,units.Quantity): a= a.to(units.kpc).value/self._ro ##Errors shape = Acos.shape errorMessage = None if len(shape) != 3: errorMessage="Acos must be a 3 dimensional numpy array" elif Asin is not None and shape[1] != shape[2]: errorMessage="The second and third dimension of the expansion coefficients must have the same length" elif Asin is None and not (shape[2] == 1 or shape[1] == shape[2]): errorMessage="The third dimension must have length=1 or equal to the length of the second dimension" elif Asin is None and shape[1] > 1 and nu.any(Acos[:,:,1:] !=0): errorMessage="Acos has non-zero elements at indices m>0, which implies a non-axi symmetric potential.\n" +\ "Asin=None which implies an axi symmetric potential.\n" + \ "Contradiction." elif Asin is not None and Asin.shape != shape: errorMessage = "The shape of Asin does not match the shape of Acos." if errorMessage is not None: raise RuntimeError(errorMessage) ##Warnings warningMessage=None if nu.any(nu.triu(Acos,1) != 0) or (Asin is not None and nu.any(nu.triu(Asin,1) != 0)): warningMessage="Found non-zero values at expansion coefficients where m > l\n" + \ "The Mth and Lth dimension is expected to make a lower triangular matrix.\n" + \ "All values found above the diagonal will be ignored." if warningMessage is not None: raise RuntimeWarning(warningMessage) ##Is non axi? self.isNonAxi= True if Asin is None or shape[1] == 1 or (nu.all(Acos[:,:,1:] == 0) and nu.all(Asin[:,:,:]==0)): self.isNonAxi = False self._a = a NN = self._Nroot(Acos.shape[1], Acos.shape[2]) self._Acos= Acos*NN[nu.newaxis,:,:] if Asin is not None: self._Asin = Asin*NN[nu.newaxis,:,:] else: self._Asin = nu.zeros_like(Acos) self._force_hash= None self.hasC= True self.hasC_dxdv=True if normalize or \ (isinstance(normalize,(int,float)) \ and not isinstance(normalize,bool)): self.normalize(normalize) return None def _Nroot(self, L, M=None): """ NAME: _Nroot PURPOSE: Evaluate the square root of equation (3.15) with the (2 - del_m,0) term outside the square root INPUT: L - evaluate Nroot for 0 <= l <= L M - evaluate Nroot for 0 <= m <= M OUTPUT: The square root of equation (3.15) with the (2 - del_m,0) outside HISTORY: 2016-05-16 - Written - Aladdin """ if M is None: M =L NN = nu.zeros((L,M),float) l = nu.arange(0,L)[:,nu.newaxis] m = nu.arange(0,M)[nu.newaxis, :] nLn = gammaln(l-m+1) - gammaln(l+m+1) NN[:,:] = ((2*l+1.)/(4.*nu.pi) * nu.e**nLn)**.5 * 2 NN[:,0] /= 2. NN = nu.tril(NN) return NN def _calculateXi(self, r): """ NAME: _calculateXi PURPOSE: Calculate xi given r INPUT: r - Evaluate at radius r OUTPUT: xi HISTORY: 2016-05-18 - Written - Aladdin """ a = self._a return (r - a)/(r + a) def _rhoTilde(self, r, N,L): """ NAME: _rhoTilde PURPOSE: Evaluate rho_tilde as defined in equation 3.9 and 2.24 for 0 <= n < N and 0 <= l < L INPUT: r - Evaluate at radius r N - size of the N dimension L - size of the L dimension OUTPUT: rho tilde HISTORY: 2016-05-17 - Written - Aladdin """ xi = self._calculateXi(r) CC = _C(xi,N,L) a = self._a rho = nu.zeros((N,L), float) n = nu.arange(0,N, dtype=float)[:, nu.newaxis] l = nu.arange(0, L, dtype=float)[nu.newaxis,:] K = 0.5 * n * (n + 4*l + 3) + (l + 1.)*(2*l + 1) rho[:,:] = K * ((a*r)**l) / ((r/a)*(a + r)**(2*l + 3.)) * CC[:,:]* (nu.pi)**-0.5 return rho def _phiTilde(self, r, N,L): """ NAME: _phiTilde PURPOSE: Evaluate phi_tilde as defined in equation 3.10 and 2.25 for 0 <= n < N and 0 <= l < L INPUT: r - Evaluate at radius r N - size of the N dimension L - size of the L dimension OUTPUT: phi tilde HISTORY: 2016-05-17 - Written - Aladdin """ xi = self._calculateXi(r) CC = _C(xi,N,L) a = self._a phi = nu.zeros((N,L), float) n = nu.arange(0,N)[:, nu.newaxis] l = nu.arange(0, L)[nu.newaxis,:] phi[:,:] = - (r*a)**l/ ((a + r)**(2*l + 1.)) * CC[:,:]* (4*nu.pi)**0.5 return phi def _compute(self, funcTilde, R, z, phi): """ NAME: _compute PURPOSE: evaluate the NxLxM density or potential INPUT: funcTidle - must be _rhoTilde or _phiTilde R - Cylindrical Galactocentric radius z - vertical height phi - azimuth OUTPUT: An NxLxM density or potential at (R,z, phi) HISTORY: 2016-05-18 - Written - Aladdin """ Acos, Asin = self._Acos, self._Asin N, L, M = Acos.shape r, theta, phi = bovy_coords.cyl_to_spher(R,z,phi) PP = lpmn(M-1,L-1,nu.cos(theta))[0].T ##Get the Legendre polynomials func_tilde = funcTilde(r, N, L) ## Tilde of the function of interest func = nu.zeros((N,L,M), float) ## The function of interest (density or potential) m = nu.arange(0, M)[nu.newaxis, nu.newaxis, :] mcos = nu.cos(m*phi) msin = nu.sin(m*phi) func = func_tilde[:,:,None]*(Acos[:,:,:]*mcos + Asin[:,:,:]*msin)*PP[None,:,:] return func def _computeArray(self, funcTilde, R, z, phi): """ NAME: _computeArray PURPOSE: evaluate the density or potential for a given array of coordinates INPUT: funcTidle - must be _rhoTilde or _phiTilde R - Cylindrical Galactocentric radius z - vertical height phi - azimuth OUTPUT: density or potential evaluated at (R,z, phi) HISTORY: 2016-06-02 - Written - Aladdin """ R = nu.array(R,dtype=float); z = nu.array(z,dtype=float); phi = nu.array(phi,dtype=float); shape = (R*z*phi).shape if shape == (): return nu.sum(self._compute(funcTilde, R,z,phi)) R = R*nu.ones(shape); z = z*nu.ones(shape); phi = phi*nu.ones(shape); func = nu.zeros(shape, float) li = _cartesian(shape) for i in range(li.shape[0]): j = nu.split(li[i], li.shape[1]) func[j] = nu.sum(self._compute(funcTilde, R[j][0],z[j][0],phi[j][0])) return func def _dens(self, R, z, phi=0., t=0.): """ NAME: _dens PURPOSE: evaluate the density at (R,z, phi) INPUT: R - Cylindrical Galactocentric radius z - vertical height phi - azimuth t - time OUTPUT: density at (R,z, phi) HISTORY: 2016-05-17 - Written - Aladdin """ if not self.isNonAxi and phi is None: phi= 0. return self._computeArray(self._rhoTilde, R,z,phi) def _evaluate(self,R,z,phi=0.,t=0.): """ NAME: _evaluate PURPOSE: evaluate the potential at (R,z, phi) INPUT: R - Cylindrical Galactocentric radius z - vertical height phi - azimuth t - time OUTPUT: potential at (R,z, phi) HISTORY: 2016-05-17 - Written - Aladdin """ if not self.isNonAxi and phi is None: phi= 0. return self._computeArray(self._phiTilde, R,z,phi) def _dphiTilde(self, r, N, L): """ NAME: _dphiTilde PURPOSE: Evaluate the derivative of phiTilde with respect to r INPUT: r - spherical radius N - size of the N dimension L - size of the L dimension OUTPUT: the derivative of phiTilde with respect to r HISTORY: 2016-06-06 - Written - Aladdin """ a = self._a l = nu.arange(0, L, dtype=float)[nu.newaxis, :] n = nu.arange(0, N, dtype=float)[:, nu.newaxis] xi = self._calculateXi(r) dC = _dC(xi,N,L) return -(4*nu.pi)**.5 * (nu.power(a*r, l)*(l*(a + r)*nu.power(r,-1) -(2*l + 1))/((a + r)**(2*l + 2))*_C(xi,N,L) + a**-1*(1 - xi)**2 * (a*r)**l / (a + r)**(2*l + 1) *dC/2.) def _computeforce(self,R,z,phi=0,t=0): """ NAME: _computeforce PURPOSE: Evaluate the first derivative of Phi with respect to R, z and phi INPUT: R - Cylindrical Galactocentric radius z - vertical height phi - azimuth t - time OUTPUT: dPhi/dr, dPhi/dtheta, dPhi/dphi HISTORY: 2016-06-07 - Written - Aladdin """ Acos, Asin = self._Acos, self._Asin N, L, M = Acos.shape r, theta, phi = bovy_coords.cyl_to_spher(R,z,phi) new_hash= hashlib.md5(nu.array([R, z,phi])).hexdigest() if new_hash == self._force_hash: dPhi_dr = self._cached_dPhi_dr dPhi_dtheta = self._cached_dPhi_dtheta dPhi_dphi = self._cached_dPhi_dphi else: PP, dPP = lpmn(M-1,L-1,nu.cos(theta)) ##Get the Legendre polynomials PP = PP.T[None,:,:] dPP = dPP.T[None,:,:] phi_tilde = self._phiTilde(r, N, L)[:,:,nu.newaxis] dphi_tilde = self._dphiTilde(r,N,L)[:,:,nu.newaxis] m = nu.arange(0, M)[nu.newaxis, nu.newaxis, :] mcos = nu.cos(m*phi) msin = nu.sin(m*phi) dPhi_dr = -nu.sum((Acos*mcos + Asin*msin)*PP*dphi_tilde) dPhi_dtheta = -nu.sum((Acos*mcos + Asin*msin)*phi_tilde*dPP*(-nu.sin(theta))) dPhi_dphi =-nu.sum(m*(Asin*mcos - Acos*msin)*phi_tilde*PP) self._force_hash = new_hash self._cached_dPhi_dr = dPhi_dr self._cached_dPhi_dtheta = dPhi_dtheta self._cached_dPhi_dphi = dPhi_dphi return dPhi_dr,dPhi_dtheta,dPhi_dphi def _computeforceArray(self,dr_dx, dtheta_dx, dphi_dx, R, z, phi): """ NAME: _computeforceArray PURPOSE: evaluate the forces in the x direction for a given array of coordinates INPUT: dr_dx - the derivative of r with respect to the chosen variable x dtheta_dx - the derivative of theta with respect to the chosen variable x dphi_dx - the derivative of phi with respect to the chosen variable x R - Cylindrical Galactocentric radius z - vertical height phi - azimuth t - time OUTPUT: The forces in the x direction HISTORY: 2016-06-02 - Written - Aladdin """ R = nu.array(R,dtype=float); z = nu.array(z,dtype=float); phi = nu.array(phi,dtype=float); shape = (R*z*phi).shape if shape == (): dPhi_dr,dPhi_dtheta,dPhi_dphi = \ self._computeforce(R,z,phi) return dr_dx*dPhi_dr + dtheta_dx*dPhi_dtheta +dPhi_dphi*dphi_dx R = R*nu.ones(shape); z = z* nu.ones(shape); phi = phi* nu.ones(shape); force = nu.zeros(shape, float) dr_dx = dr_dx*nu.ones(shape); dtheta_dx = dtheta_dx*nu.ones(shape);dphi_dx = dphi_dx*nu.ones(shape); li = _cartesian(shape) for i in range(li.shape[0]): j = nu.split(li[i], li.shape[1]) dPhi_dr,dPhi_dtheta,dPhi_dphi = \ self._computeforce(R[j][0],z[j][0],phi[j][0]) force[j] = dr_dx[j][0]*dPhi_dr + dtheta_dx[j][0]*dPhi_dtheta +dPhi_dphi*dphi_dx[j][0] return force def _Rforce(self, R, z, phi=0, t=0): """ NAME: _Rforce PURPOSE: evaluate the radial force at (R,z, phi) INPUT: R - Cylindrical Galactocentric radius z - vertical height phi - azimuth t - time OUTPUT: radial force at (R,z, phi) HISTORY: 2016-06-06 - Written - Aladdin """ if not self.isNonAxi and phi is None: phi= 0. r, theta, phi = bovy_coords.cyl_to_spher(R,z,phi) #x = R dr_dR = nu.divide(R,r); dtheta_dR = nu.divide(z,r**2); dphi_dR = 0 return self._computeforceArray(dr_dR, dtheta_dR, dphi_dR, R,z,phi) def _zforce(self, R, z, phi=0., t=0.): """ NAME: _zforce PURPOSE: evaluate the vertical force at (R,z, phi) INPUT: R - Cylindrical Galactocentric radius z - vertical height phi - azimuth t - time OUTPUT: vertical force at (R,z, phi) HISTORY: 2016-06-06 - Written - Aladdin """ if not self.isNonAxi and phi is None: phi= 0. r, theta, phi = bovy_coords.cyl_to_spher(R,z,phi) #x = z dr_dz = nu.divide(z,r); dtheta_dz = nu.divide(-R,r**2); dphi_dz = 0 return self._computeforceArray(dr_dz, dtheta_dz, dphi_dz, R,z,phi) def _phiforce(self, R,z,phi=0,t=0): """ NAME: _phiforce PURPOSE: evaluate the azimuth force at (R,z, phi) INPUT: R - Cylindrical Galactocentric radius z - vertical height phi - azimuth t - time OUTPUT: azimuth force at (R,z, phi) HISTORY: 2016-06-06 - Written - Aladdin """ if not self.isNonAxi and phi is None: phi= 0. r, theta, phi = bovy_coords.cyl_to_spher(R,z,phi) #x = phi dr_dphi = 0; dtheta_dphi = 0; dphi_dphi = 1 return self._computeforceArray(dr_dphi, dtheta_dphi, dphi_dphi, R,z,phi) def OmegaP(self): return 0 def _xiToR(xi, a =1): return a*nu.divide((1. + xi),(1. - xi)) def _C(xi, N,L, alpha = lambda x: 2*x + 3./2): """ NAME: _C PURPOSE: Evaluate C_n,l (the Gegenbauer polynomial) for 0 <= l < L and 0<= n < N INPUT: xi - radial transformed variable N - Size of the N dimension L - Size of the L dimension alpha = A lambda function of l. Default alpha = 2l + 3/2 OUTPUT: An LxN Gegenbauer Polynomial HISTORY: 2016-05-16 - Written - Aladdin """ CC = nu.zeros((N,L), float) for l in range(L): for n in range(N): a = alpha(l) if n==0: CC[n][l] = 1. continue elif n==1: CC[n][l] = 2.*a*xi if n + 1 != N: CC[n+1][l] = (n + 1.)**-1. * (2*(n + a)*xi*CC[n][l] - (n + 2*a - 1)*CC[n-1][l]) return CC def _dC(xi, N, L): l = nu.arange(0,L)[nu.newaxis, :] CC = _C(xi,N + 1,L, alpha = lambda x: 2*x + 5./2) CC = nu.roll(CC, 1, axis=0)[:-1,:] CC[0, :] = 0 CC *= 2*(2*l + 3./2) return CC def scf_compute_coeffs_spherical(dens, N, a=1., radial_order=None): """ NAME: scf_compute_coeffs_spherical PURPOSE: Numerically compute the expansion coefficients for a given spherical density INPUT: dens - A density function that takes a parameter R N - size of expansion coefficients a - parameter used to shift the basis functions radial_order - Number of sample points of the radial integral. If None, radial_order=max(20, N + 1) OUTPUT: (Acos,Asin) - Expansion coefficients for density dens that can be given to SCFPotential.__init__ HISTORY: 2016-05-18 - Written - Aladdin """ numOfParam = 0 try: dens(0) numOfParam=1 except: try: dens(0,0) numOfParam=2 except: numOfParam=3 param = [0]*numOfParam; def integrand(xi): r = _xiToR(xi, a) R = r param[0] = R return a**3. * dens(*param)*(1 + xi)**2. * (1 - xi)**-3. * _C(xi, N, 1)[:,0] Acos = nu.zeros((N,1,1), float) Asin = None Ksample = [max(N + 1, 20)] if radial_order != None: Ksample[0] = radial_order integrated = _gaussianQuadrature(integrand, [[-1., 1.]], Ksample=Ksample) n = nu.arange(0,N) K = 16*nu.pi*(n + 3./2)/((n + 2)*(n + 1)*(1 + n*(n + 3.)/2.)) Acos[n,0,0] = 2*K*integrated return Acos, Asin def scf_compute_coeffs_axi(dens, N, L, a=1.,radial_order=None, costheta_order=None): """ NAME: scf_compute_coeffs_axi PURPOSE: Numerically compute the expansion coefficients for a given axi-symmetric density INPUT: dens - A density function that takes a parameter R and z N - size of the Nth dimension of the expansion coefficients L - size of the Lth dimension of the expansion coefficients a - parameter used to shift the basis functions radial_order - Number of sample points of the radial integral. If None, radial_order=max(20, N + 3/2L + 1) costheta_order - Number of sample points of the costheta integral. If None, If costheta_order=max(20, L + 1) OUTPUT: (Acos,Asin) - Expansion coefficients for density dens that can be given to SCFPotential.__init__ HISTORY: 2016-05-20 - Written - Aladdin """ numOfParam = 0 try: dens(0,0) numOfParam=2 except: numOfParam=3 param = [0]*numOfParam; def integrand(xi, costheta): l = nu.arange(0, L)[nu.newaxis, :] r = _xiToR(xi,a) R = r*nu.sqrt(1 - costheta**2.) z = r*costheta Legandre = lpmn(0,L-1,costheta)[0].T[nu.newaxis,:,0] dV = (1. + xi)**2. * nu.power(1. - xi, -4.) phi_nl = a**3*(1. + xi)**l * (1. - xi)**(l + 1.)*_C(xi, N, L)[:,:] * Legandre param[0] = R param[1] = z return phi_nl*dV * dens(*param) Acos = nu.zeros((N,L,1), float) Asin = None ##This should save us some computation time since we're only taking the double integral once, rather then L times Ksample = [max(N + 3*L//2 + 1, 20) , max(L + 1,20) ] if radial_order != None: Ksample[0] = radial_order if costheta_order != None: Ksample[1] = costheta_order integrated = _gaussianQuadrature(integrand, [[-1, 1], [-1, 1]], Ksample = Ksample)*(2*nu.pi) n = nu.arange(0,N)[:,nu.newaxis] l = nu.arange(0,L)[nu.newaxis,:] K = .5*n*(n + 4*l + 3) + (l + 1)*(2*l + 1) #I = -K*(4*nu.pi)/(2.**(8*l + 6)) * gamma(n + 4*l + 3)/(gamma(n + 1)*(n + 2*l + 3./2)*gamma(2*l + 3./2)**2) ##Taking the ln of I will allow bigger size coefficients lnI = -(8*l + 6)*nu.log(2) + gammaln(n + 4*l + 3) - gammaln(n + 1) - nu.log(n + 2*l + 3./2) - 2*gammaln(2*l + 3./2) I = -K*(4*nu.pi) * nu.e**(lnI) constants = -2.**(-2*l)*(2*l + 1.)**.5 Acos[:,:,0] = 2*I**-1 * integrated*constants return Acos, Asin def scf_compute_coeffs(dens, N, L, a=1., radial_order=None, costheta_order=None, phi_order=None): """ NAME: scf_compute_coeffs PURPOSE: Numerically compute the expansion coefficients for a given triaxial density INPUT: dens - A density function that takes a parameter R, z and phi N - size of the Nth dimension of the expansion coefficients L - size of the Lth and Mth dimension of the expansion coefficients a - parameter used to shift the basis functions radial_order - Number of sample points of the radial integral. If None, radial_order=max(20, N + 3/2L + 1) costheta_order - Number of sample points of the costheta integral. If None, If costheta_order=max(20, L + 1) phi_order - Number of sample points of the phi integral. If None, If costheta_order=max(20, L + 1) OUTPUT: (Acos,Asin) - Expansion coefficients for density dens that can be given to SCFPotential.__init__ HISTORY: 2016-05-27 - Written - Aladdin """ def integrand(xi, costheta, phi): l = nu.arange(0, L)[nu.newaxis, :, nu.newaxis] m = nu.arange(0, L)[nu.newaxis,nu.newaxis,:] r = _xiToR(xi, a) R = r*nu.sqrt(1 - costheta**2.) z = r*costheta Legandre = lpmn(L - 1,L-1,costheta)[0].T[nu.newaxis,:,:] dV = (1. + xi)**2. * nu.power(1. - xi, -4.) phi_nl = - a**3*(1. + xi)**l * (1. - xi)**(l + 1.)*_C(xi, N, L)[:,:,nu.newaxis] * Legandre return dens(R,z, phi) * phi_nl[nu.newaxis, :,:,:]*nu.array([nu.cos(m*phi), nu.sin(m*phi)])*dV Acos = nu.zeros((N,L,L), float) Asin = nu.zeros((N,L,L), float) Ksample = [max(N + 3*L//2 + 1,20), max(L + 1,20 ), max(L + 1,20)] if radial_order != None: Ksample[0] = radial_order if costheta_order != None: Ksample[1] = costheta_order if phi_order != None: Ksample[2] = phi_order integrated = _gaussianQuadrature(integrand, [[-1., 1.], [-1., 1.], [0, 2*nu.pi]], Ksample = Ksample) n = nu.arange(0,N)[:,nu.newaxis, nu.newaxis] l = nu.arange(0,L)[nu.newaxis,:, nu.newaxis] m = nu.arange(0,L)[nu.newaxis,nu.newaxis,:] K = .5*n*(n + 4*l + 3) + (l + 1)*(2*l + 1) Nln = .5*gammaln(l - m + 1) - .5*gammaln(l + m + 1) - (2*l)*nu.log(2) NN = nu.e**(Nln) NN[nu.where(NN == nu.inf)] = 0 ## To account for the fact that m cant be bigger than l constants = NN*(2*l + 1.)**.5 lnI = -(8*l + 6)*nu.log(2) + gammaln(n + 4*l + 3) - gammaln(n + 1) - nu.log(n + 2*l + 3./2) - 2*gammaln(2*l + 3./2) I = -K*(4*nu.pi) * nu.e**(lnI) Acos[:,:,:],Asin[:,:,:] = 2*(I**-1.)[nu.newaxis,:,:,:] * integrated * constants[nu.newaxis,:,:,:] return Acos, Asin def _cartesian(arraySizes, out=None): """ NAME: cartesian PURPOSE: Generate a cartesian product of input arrays. INPUT: arraySizes - list of size of arrays out - Array to place the cartesian product in. OUTPUT: 2-D array of shape (product(arraySizes), len(arraySizes)) containing cartesian products formed of input arrays. HISTORY: 2016-06-02 - Obtained from http://stackoverflow.com/questions/1208118/using-numpy-to-build-an-array-of-all-combinations-of-two-arrays """ arrays = [] for i in range(len(arraySizes)): arrays.append(nu.arange(0, arraySizes[i])) arrays = [nu.asarray(x) for x in arrays] dtype = arrays[0].dtype n = nu.prod([x.size for x in arrays]) if out is None: out = nu.zeros([n, len(arrays)], dtype=dtype) m = n // arrays[0].size out[:,0] = nu.repeat(arrays[0], m) if arrays[1:]: _cartesian(arraySizes[1:], out=out[0:m,1:]) for j in range(1, arrays[0].size): out[j*m:(j+1)*m,1:] = out[0:m,1:] return out def _gaussianQuadrature(integrand, bounds, Ksample=[20], roundoff=0): """ NAME: _gaussianQuadrature PURPOSE: Numerically take n integrals over a function that returns a float or an array INPUT: integrand - The function you're integrating over. bounds - The bounds of the integral in the form of [[a_0, b_0], [a_1, b_1], ... , [a_n, b_n]] where a_i is the lower bound and b_i is the upper bound Ksample - Number of sample points in the form of [K_0, K_1, ..., K_n] where K_i is the sample point of the ith integral. roundoff - if the integral is less than this value, round it to 0. OUTPUT: The integral of the function integrand HISTORY: 2016-05-24 - Written - Aladdin """ ##Maps the sample point and weights xp = nu.zeros((len(bounds), nu.max(Ksample)), float) wp = nu.zeros((len(bounds), nu.max(Ksample)), float) for i in range(len(bounds)): x,w = leggauss(Ksample[i]) ##Calculates the sample points and weights a,b = bounds[i] xp[i, :Ksample[i]] = .5*(b-a)*x + .5*(b+a) wp[i, :Ksample[i]] = .5*(b - a)*w ##Determines the shape of the integrand s = 0. shape=None s_temp = integrand(*nu.zeros(len(bounds))) if type(s_temp).__name__ == nu.ndarray.__name__ : shape = s_temp.shape s = nu.zeros(shape, float) #gets all combinations of indices from each integrand li = _cartesian(Ksample) ##Performs the actual integration for i in range(li.shape[0]): index = [nu.arange(len(bounds)),li[i]] s+= nu.prod(wp[index])*integrand(*xp[index]) ##Rounds values that are less than roundoff to zero if shape!= None: s[nu.where(nu.fabs(s) < roundoff)] = 0 else: s *= nu.fabs(s) >roundoff return s galpy-1.3.0/galpy/potential_src/SnapshotRZPotential.py0000644000076500000240000005753113125613630023233 0ustar bovystaff00000000000000from os import system import hashlib import numpy as np from scipy import interpolate from galpy.potential_src.Potential import Potential from galpy.potential_src import interpRZPotential from galpy.potential_src.interpRZPotential import scalarVectorDecorator, \ zsymDecorator try: import pynbody from pynbody import gravity from pynbody.units import NoUnit except ImportError: #pragma: no cover _PYNBODY_LOADED= False else: _PYNBODY_LOADED= True class SnapshotRZPotential(Potential): """Class that implements an axisymmetrized version of the potential of an N-body snapshot (requires `pynbody `__) `_evaluate`, `_Rforce`, and `_zforce` calculate a hash for the array of points that is passed in by the user. The hash and corresponding potential/force arrays are stored -- if a subsequent request matches a previously computed hash, the previous results are returned and not recalculated. """ def __init__(self, s, num_threads=None,nazimuths=4, ro=None,vo=None): """ NAME: __init__ PURPOSE: Initialize a SnapshotRZ potential object INPUT: s - a simulation snapshot loaded with pynbody num_threads= (4) number of threads to use for calculation nazimuths= (4) number of azimuths to average over ro=, vo= distance and velocity scales for translation into internal units (default from configuration file) OUTPUT: instance HISTORY: 2013 - Written - Rok Roskar (ETH) 2014-11-24 - Edited for merging into main galpy - Bovy (IAS) """ if not _PYNBODY_LOADED: raise ImportError("The SnapShotRZPotential class is designed to work with pynbody snapshots, which cannot be loaded (probably because it is not installed) -- obtain from pynbody.github.io") Potential.__init__(self,amp=1.0,ro=ro,vo=vo) self._s = s self._point_hash = {} if num_threads is None: self._num_threads= pynbody.config['number_of_threads'] else: self._num_threads = num_threads # Set up azimuthal averaging self._naz= nazimuths self._cosaz= np.cos(np.arange(self._naz,dtype='float')\ /self._naz*2.*np.pi) self._sinaz= np.sin(np.arange(self._naz,dtype='float')\ /self._naz*2.*np.pi) self._zones= np.ones(self._naz) self._zzeros= np.zeros(self._naz) return None @scalarVectorDecorator def _evaluate(self, R,z,phi=None,t=None,dR=None,dphi=None) : pot, acc = self._setup_potential(R,z) return pot @scalarVectorDecorator def _Rforce(self, R,z,phi=None,t=None,dR=None,dphi=None) : pot, acc = self._setup_potential(R,z) return acc[:,0] @scalarVectorDecorator def _zforce(self, R,z,phi=None,t=None,dR=None,dphi=None) : pot, acc = self._setup_potential(R,z) return acc[:,1] def _setup_potential(self, R, z, use_pkdgrav = False) : # compute the hash for the requested grid new_hash = hashlib.md5(np.array([R,z])).hexdigest() # if we computed for these points before, return; otherwise compute if new_hash in self._point_hash : pot, rz_acc = self._point_hash[new_hash] # if use_pkdgrav : else : # set up the four points per R,z pair to mimic axisymmetry points = np.zeros((len(R),self._naz,3)) for i in range(len(R)) : points[i] = np.array([R[i]*self._cosaz,R[i]*self._sinaz, z[i]*self._zones]).T points_new = points.reshape(points.size//3,3) pot, acc = gravity.calc.direct(self._s,points_new,num_threads=self._num_threads) pot = pot.reshape(len(R),self._naz) acc = acc.reshape(len(R),self._naz,3) # need to average the potentials pot = pot.mean(axis=1) # get the radial accelerations rz_acc = np.zeros((len(R),2)) rvecs= np.array([self._cosaz,self._sinaz,self._zzeros]).T for i in range(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 /= self._naz # store the computed values for reuse self._point_hash[new_hash] = [pot,rz_acc] return pot, rz_acc class InterpSnapshotRZPotential(interpRZPotential.interpRZPotential) : """ Interpolated axisymmetrized potential extracted from a simulation output (see ``interpRZPotential`` and ``SnapshotRZPotential``) """ def __init__(self, s, ro=None,vo=None, rgrid=(np.log(0.01),np.log(20.),101), zgrid=(0.,1.,101), interpepifreq = False, interpverticalfreq = False, interpPot = True, enable_c = True, logR = True, zsym = True, numcores=None,nazimuths=4,use_pkdgrav = False) : """ NAME: __init__ PURPOSE: Initialize an InterpSnapshotRZPotential instance INPUT: s - a simulation snapshot loaded with pynbody rgrid - R grid to be given to linspace as in rs= linspace(*rgrid) zgrid - z grid to be given to linspace as in zs= linspace(*zgrid) logR - if True, rgrid is in the log of R so logrs= linspace(*rgrid) interpPot, interpepifreq, interpverticalfreq= if True, interpolate these functions (interpPot=True also interpolates the R and zforce) enable_c= enable use of C for interpolations zsym= if True (default), the potential is assumed to be symmetric around z=0 (so you can use, e.g., zgrid=(0.,1.,101)). numcores= if set to an integer, use this many cores nazimuths= (4) number of azimuths to average over use_pkdgrav= (False) use PKDGRAV to calculate the snapshot's potential and forces (CURRENTLY NOT IMPLEMENTED) ro=, vo= distance and velocity scales for translation into internal units (default from configuration file) OUTPUT: instance HISTORY: 2013 - Written - Rok Roskar (ETH) 2014-11-24 - Edited for merging into main galpy - Bovy (IAS) """ if not _PYNBODY_LOADED: raise ImportError("The InterpSnapRZShotPotential class is designed to work with pynbody snapshots, which cannot be loaded (probably because it is not installed) -- obtain from pynbody.github.io") # inititalize using the base class Potential.__init__(self,amp=1.0,ro=ro,vo=vo) # other properties if numcores is None: self._numcores= pynbody.config['number_of_threads'] else: self._numcores = numcores self._s = s # Set up azimuthal averaging self._naz= nazimuths self._cosaz= np.cos(np.arange(self._naz,dtype='float')\ /self._naz*2.*np.pi) self._sinaz= np.sin(np.arange(self._naz,dtype='float')\ /self._naz*2.*np.pi) self._zones= np.ones(self._naz) self._zzeros= np.zeros(self._naz) # the interpRZPotential class sets these flags self._enable_c = enable_c self.hasC = True # set up the flags for interpolated quantities # since the potential and force are always calculated together, # set the force interpolations to true if potential is true and # vice versa self._interpPot = interpPot self._interpRforce = self._interpPot self._interpzforce = self._interpPot self._interpvcirc = self._interpPot # these require additional calculations so set them seperately self._interpepifreq = interpepifreq self._interpverticalfreq = interpverticalfreq # make the potential accessible at points beyond the grid self._origPot = SnapshotRZPotential(s, numcores) # setup the grid self._zsym = zsym self._logR = logR self._rgrid = np.linspace(*rgrid) if logR : self._rgrid = np.exp(self._rgrid) self._logrgrid = np.log(self._rgrid) rs = self._logrgrid else : rs = self._rgrid self._zgrid = np.linspace(*zgrid) # calculate the grids self._setup_potential(self._rgrid,self._zgrid,use_pkdgrav=use_pkdgrav) if enable_c and interpPot: self._potGrid_splinecoeffs = interpRZPotential.calc_2dsplinecoeffs_c(self._potGrid) self._rforceGrid_splinecoeffs = interpRZPotential.calc_2dsplinecoeffs_c(self._rforceGrid) self._zforceGrid_splinecoeffs = interpRZPotential.calc_2dsplinecoeffs_c(self._zforceGrid) else : self._potInterp= interpolate.RectBivariateSpline(rs, self._zgrid, self._potGrid, kx=3,ky=3,s=0.) self._rforceInterp= interpolate.RectBivariateSpline(rs, self._zgrid, self._rforceGrid, kx=3,ky=3,s=0.) self._zforceInterp= interpolate.RectBivariateSpline(rs, self._zgrid, self._zforceGrid, kx=3,ky=3,s=0.) if interpepifreq: self._R2interp = interpolate.RectBivariateSpline(rs, self._zgrid, self._R2derivGrid, kx=3,ky=3,s=0.) if interpverticalfreq: self._z2interp = interpolate.RectBivariateSpline(rs, self._zgrid, self._z2derivGrid, kx=3,ky=3,s=0.) if interpepifreq and interpverticalfreq: self._Rzinterp = interpolate.RectBivariateSpline(rs, self._zgrid, self._RzderivGrid, kx=3,ky=3,s=0.) # setup the derived quantities if interpPot: self._vcircGrid = np.sqrt(self._rgrid*(-self._rforceGrid[:,0])) self._vcircInterp = interpolate.InterpolatedUnivariateSpline(rs, self._vcircGrid, k=3) if interpepifreq: self._epifreqGrid = np.sqrt(self._R2derivGrid[:,0] - 3./self._rgrid*self._rforceGrid[:,0]) goodindx= True^np.isnan(self._epifreqGrid) self._epifreqInterp=\ interpolate.InterpolatedUnivariateSpline(rs[goodindx], self._epifreqGrid[goodindx], k=3) self._epigoodindx= goodindx if interpverticalfreq: self._verticalfreqGrid = np.sqrt(np.abs(self._z2derivGrid[:,0])) goodindx= True^np.isnan(self._verticalfreqGrid) self._verticalfreqInterp=\ interpolate.InterpolatedUnivariateSpline(rs[goodindx], self._verticalfreqGrid[goodindx],k=3) self._verticalgoodindx= goodindx def _setup_potential(self, R, z, use_pkdgrav = False, dr = 0.0001) : """ Calculates the potential and force grids for the snapshot for use with other galpy functions. **Input**: *R*: R grid coordinates (numpy array) *z*: z grid coordinates (numpy array) **Optional Keywords**: *use_pkdgrav*: (False) whether to use pkdgrav for the gravity calculation *dr*: (0.01) offset to use for the gradient calculation - the points are positioned at +/- dr from the central point """ # set up the four points per R,z pair to mimic axisymmetry points = np.zeros((len(R),len(z),self._naz,3)) for i in range(len(R)) : for j in range(len(z)) : points[i,j] = np.array([R[i]*self._cosaz,R[i]*self._sinaz, z[j]*self._zones]).T points_new = points.reshape(points.size//3,3) self.points = points_new # set up the points to calculate the second derivatives zgrad_points = np.zeros((len(points_new)*2,3)) rgrad_points = np.zeros((len(points_new)*2,3)) for i,p in enumerate(points_new) : zgrad_points[i*2] = p zgrad_points[i*2][2] -= dr zgrad_points[i*2+1] = p zgrad_points[i*2+1][2] += dr rgrad_points[i*2] = p rgrad_points[i*2][:2] -= p[:2]/np.sqrt(np.dot(p[:2],p[:2]))*dr rgrad_points[i*2+1] = p rgrad_points[i*2+1][:2] += p[:2]/np.sqrt(np.dot(p[:2],p[:2]))*dr if use_pkdgrav : #pragma: no cover raise RuntimeError("using pkdgrav not currently implemented") sn = pynbody.snapshot._new(len(self._s.d)+len(self._s.g)+len(self._s.s)+len(points_new)) print("setting up %d grid points"%(len(points_new))) #sn['pos'][0:len(self.s)] = self.s['pos'] #sn['mass'][0:len(self.s)] = self.s['mass'] #sn['phi'] = 0.0 #sn['eps'] = 1e3 #sn['eps'][0:len(self.s)] = self.s['eps'] #sn['vel'][0:len(self.s)] = self.s['vel'] #sn['mass'][len(self.s):] = 1e-10 sn['pos'][len(self._s):] = points_new sn['mass'][len(self._s):] = 0.0 sn.write(fmt=pynbody.tipsy.TipsySnap, filename='potgridsnap') command = '~/bin/pkdgrav2_pthread -sz %d -n 0 +std -o potgridsnap -I potgridsnap +potout +overwrite %s'%(self._numcores, self._s._paramfile['filename']) print(command) system(command) sn = pynbody.load('potgridsnap') acc = sn['accg'][len(self._s):].reshape(len(R)*len(z),self._naz,3) pot = sn['pot'][len(self._s):].reshape(len(R)*len(z),self._naz) else : if self._interpPot: pot, acc = gravity.calc.direct(self._s,points_new,num_threads=self._numcores) pot = pot.reshape(len(R)*len(z),self._naz) acc = acc.reshape(len(R)*len(z),self._naz,3) # need to average the potentials pot = pot.mean(axis=1) # get the radial accelerations rz_acc = np.zeros((len(R)*len(z),2)) rvecs= np.array([self._cosaz,self._sinaz,self._zzeros]).T # 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),self._naz,3)) for i in range(len(R)*len(z)) : 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 /= self._naz self._potGrid = pot.reshape((len(R),len(z))) self._rforceGrid = rz_acc[:,0].reshape((len(R),len(z))) self._zforceGrid = rz_acc[:,1].reshape((len(R),len(z))) # compute the force gradients # first get the accelerations if self._interpverticalfreq : zgrad_pot, zgrad_acc = gravity.calc.direct(self._s,zgrad_points,num_threads=self._numcores) # each point from the points used above for pot and acc is straddled by # two points to get the gradient across it. Compute the gradient by # using a finite difference zgrad = np.zeros(len(points_new)) # do a loop through the pairs of points -- reshape the array # so that each item is the pair of acceleration vectors # then calculate the gradient from the two points for i,zacc in enumerate(zgrad_acc.reshape((len(zgrad_acc)//2,2,3))) : zgrad[i] = ((zacc[1]-zacc[0])/(dr*2.0))[2] # reshape the arrays self._z2derivGrid = -zgrad.reshape((len(zgrad)//self._naz,self._naz)).mean(axis=1).reshape((len(R),len(z))) # do the same for the radial component if self._interpepifreq: rgrad_pot, rgrad_acc = gravity.calc.direct(self._s,rgrad_points,num_threads=self._numcores) rgrad = np.zeros(len(points_new)) for i,racc in enumerate(rgrad_acc.reshape((len(rgrad_acc)//2,2,3))) : point = points_new[i] point[2] = 0.0 rvec = point/np.sqrt(np.dot(point,point)) rgrad_vec = (np.dot(racc[1],rvec)- np.dot(racc[0],rvec)) / (dr*2.0) rgrad[i] = rgrad_vec self._R2derivGrid = -rgrad.reshape((len(rgrad)//self._naz,self._naz)).mean(axis=1).reshape((len(R),len(z))) # do the same for the mixed radial-vertical component if self._interpepifreq and self._interpverticalfreq: # re-use this Rzgrad = np.zeros(len(points_new)) for i,racc in enumerate(rgrad_acc.reshape((len(rgrad_acc)//2,2,3))) : Rzgrad[i] = ((racc[1]-racc[0])/(dr*2.0))[2] # reshape the arrays self._RzderivGrid = -Rzgrad.reshape((len(Rzgrad)//self._naz,self._naz)).mean(axis=1).reshape((len(R),len(z))) @scalarVectorDecorator @zsymDecorator(False) def _R2deriv(self,R,Z,phi=0.,t=0.): return self._R2interp(R,Z) @scalarVectorDecorator @zsymDecorator(False) def _z2deriv(self,R,Z,phi=None,t=None): return self._z2interp(R,Z) @scalarVectorDecorator @zsymDecorator(True) def _Rzderiv(self,R,Z,phi=None,t=None): return self._Rzinterp(R,Z) def normalize(self, R0=8.0) : """ Normalize all positions by R0 and velocities by Vc(R0). If :class:`~scipy.interpolate.RectBivariateSpline` or :class:`~scipy.interpolate.InterpolatedUnivariateSpline` are used, redefine them for use with the rescaled coordinates. To undo the normalization, call :func:`~galpy.potential.SnapshotPotential.InterpSnapshotPotential.denormalize`. """ Vc0 = self.vcirc(R0) Phi0 = np.abs(self.Rforce(R0,0.0)) self._normR0 = R0 self._normVc0 = Vc0 self._normPhi0 = Phi0 # rescale the simulation if not isinstance(self._s['pos'].units,NoUnit): self._posunit = self._s['pos'].units self._s['pos'].convert_units('%s kpc'%R0) else: self._posunit = None if not isinstance(self._s['vel'].units,NoUnit): self._velunit = self._s['vel'].units self._s['vel'].convert_units('%s km s**-1'%Vc0) else: self._velunit = None # rescale the grid self._rgrid /= R0 if self._logR: self._logrgrid -= np.log(R0) rs = self._logrgrid else : rs = self._rgrid self._zgrid /= R0 # rescale the potential self._amp /= Phi0 self._savedsplines = {} # rescale anything using splines if not self._enable_c and self._interpPot : for spline,name in zip([self._potInterp, self._rforceInterp, self._zforceInterp], ["pot", "rforce", "zforce"]): self._savedsplines[name] = spline self._potInterp= interpolate.RectBivariateSpline(rs, self._zgrid, self._potGrid/R0, kx=3,ky=3,s=0.) self._rforceInterp= interpolate.RectBivariateSpline(rs, self._zgrid, self._rforceGrid, kx=3,ky=3,s=0.) self._zforceInterp= interpolate.RectBivariateSpline(rs, self._zgrid, self._zforceGrid, kx=3,ky=3,s=0.) elif self._enable_c and self._interpPot: self._potGrid_splinecoeffs= interpRZPotential.calc_2dsplinecoeffs_c(self._potGrid/R0) if self._interpPot : self._savedsplines['vcirc'] = self._vcircInterp self._vcircInterp = interpolate.InterpolatedUnivariateSpline(rs, self._vcircGrid/Vc0, k=3) if self._interpepifreq: self._savedsplines['R2deriv'] = self._R2interp self._savedsplines['epifreq'] = self._epifreqInterp self._R2interp = interpolate.RectBivariateSpline(rs, self._zgrid, self._R2derivGrid, kx=3,ky=3,s=0.) self._epifreqInterp = interpolate.InterpolatedUnivariateSpline(rs[self._epigoodindx], self._epifreqGrid[self._epigoodindx]/np.sqrt(Phi0/R0), k=3) if self._interpverticalfreq: self._savedsplines['z2deriv'] = self._z2interp self._savedsplines['verticalfreq'] = self._verticalfreqInterp self._z2interp = interpolate.RectBivariateSpline(rs, self._zgrid, self._z2derivGrid, kx=3,ky=3,s=0.) self._verticalfreqInterp = interpolate.InterpolatedUnivariateSpline(rs[self._verticalgoodindx], self._verticalfreqGrid[self._verticalgoodindx]/np.sqrt(Phi0/R0), k=3) def denormalize(self) : """ Undo the normalization. """ R0 = self._normR0 Vc0 = self._normVc0 Phi0 = self._normPhi0 # rescale the simulation if not self._posunit is None: self._s['pos'].convert_units(self._posunit) if not self._velunit is None: self._s['vel'].convert_units(self._velunit) # rescale the grid self._rgrid *= R0 if self._logR: self._logrgrid += np.log(R0) rs = self._logrgrid else : rs = self._rgrid self._zgrid *= R0 # rescale the potential self._amp *= Phi0 # restore the splines if not self._enable_c and self._interpPot : self._potInterp= self._savedsplines['pot'] self._rforceInterp= self._savedsplines['rforce'] self._zforceInterp= self._savedsplines['zforce'] elif self._enable_c and self._interpPot: self._potGrid_splinecoeffs= interpRZPotential.calc_2dsplinecoeffs_c(self._potGrid) if self._interpPot : self._vcircInterp = self._savedsplines['vcirc'] if self._interpepifreq : self._R2interp = self._savedsplines['R2deriv'] self._epifreqInterp = self._savedsplines['epifreq'] if self._interpverticalfreq: self._z2interp = self._savedsplines['z2deriv'] self._verticalfreqInterp = self._savedsplines['verticalfreq'] galpy-1.3.0/galpy/potential_src/SoftenedNeedleBarPotential.py0000644000076500000240000001731713147073041024467 0ustar bovystaff00000000000000############################################################################### # SoftenedNeedleBarPotential.py: class that implements the softened needle # bar potential from Long & Murali (1992) ############################################################################### import hashlib import numpy from galpy.potential_src.Potential import Potential, \ _APY_LOADED if _APY_LOADED: from astropy import units from galpy.util import bovy_coords, bovy_conversion class SoftenedNeedleBarPotential(Potential): """Class that implements the softened needle bar potential from `Long & Murali (1992) `__ .. math:: \\Phi(x,y,z) = \\frac{\\mathrm{amp}}{2a}\\,\\ln\\left(\\frac{x-a+T_-}{x+a+T_+}\\right) where .. math:: T_{\\pm} = \\sqrt{(a\\pm x)^2 + y^2+(b+\\sqrt{z^2+c^2})^2} For a prolate bar, set :math:`b` to zero. """ def __init__(self,amp=1.,a=4.,b=0.,c=1.,normalize=False, pa=0.4,omegab=1.8,ro=None,vo=None): """ NAME: __init__ PURPOSE: initialize a softened-needle bar potential INPUT: amp - amplitude to be applied to the potential (default: 1); can be a Quantity with units of mass a= (4.) Bar half-length (can be Quantity) b= (1.) Triaxial softening length (can be Quantity) c= (1.) Prolate softening length (can be Quantity) pa= (0.4) The position angle of the x axis (rad or Quantity) omegab= (1.8) Pattern speed (can be Quantity) normalize - if True, normalize such that vc(1.,0.)=1., or, if given as a number, such that the force is this fraction of the force necessary to make vc(1.,0.)=1. ro=, vo= distance and velocity scales for translation into internal units (default from configuration file) OUTPUT: (none) HISTORY: 2016-11-02 - Started - Bovy (UofT) """ Potential.__init__(self,amp=amp,ro=ro,vo=vo,amp_units='mass') if _APY_LOADED and isinstance(a,units.Quantity): a= a.to(units.kpc).value/self._ro if _APY_LOADED and isinstance(b,units.Quantity): b= b.to(units.kpc).value/self._ro if _APY_LOADED and isinstance(c,units.Quantity): c= c.to(units.kpc).value/self._ro if _APY_LOADED and isinstance(pa,units.Quantity): pa= pa.to(units.rad).value if _APY_LOADED and isinstance(omegab,units.Quantity): omegab= omegab.to(units.km/units.s/units.kpc).value\ /bovy_conversion.freq_in_kmskpc(self._vo,self._ro) self._a= a self._b= b self._c2= c**2. self._pa= pa self._omegab= omegab self._force_hash= None self.hasC= True self.hasC_dxdv= False if normalize or \ (isinstance(normalize,(int,float)) \ and not isinstance(normalize,bool)): #pragma: no cover self.normalize(normalize) self.isNonAxi= True return None def _evaluate(self,R,z,phi=0.,t=0.): """ NAME: _evaluate PURPOSE: evaluate the potential at R,z INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t - time OUTPUT: Phi(R,z) HISTORY: 2016-11-02 - Started - Bovy (UofT) """ x,y,z= self._compute_xyz(R,phi,z,t) Tp, Tm= self._compute_TpTm(x,y,z) return numpy.log((x-self._a+Tm)/(x+self._a+Tp))/2./self._a def _Rforce(self,R,z,phi=0.,t=0.): """ NAME: _Rforce PURPOSE: evaluate the radial force for this potential INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t - time OUTPUT: the radial force HISTORY: 2016-11-02 - Written - Bovy (UofT) """ self._compute_xyzforces(R,z,phi,t) return numpy.cos(phi)*self._cached_Fx+numpy.sin(phi)*self._cached_Fy def _phiforce(self,R,z,phi=0.,t=0.): """ NAME: _phiforce PURPOSE: evaluate the azimuthal force for this potential INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t - time OUTPUT: the azimuthal force HISTORY: 2016-11-02 - Written - Bovy (UofT) """ self._compute_xyzforces(R,z,phi,t) return R*(-numpy.sin(phi)*self._cached_Fx\ +numpy.cos(phi)*self._cached_Fy) def _zforce(self,R,z,phi=0.,t=0.): """ NAME: _zforce PURPOSE: evaluate the vertical force for this potential INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t - time OUTPUT: the vertical force HISTORY: 2016-11-02 - Written - Bovy (UofT) """ self._compute_xyzforces(R,z,phi,t) return self._cached_Fz def OmegaP(self): """ NAME: OmegaP PURPOSE: return the pattern speed INPUT: (none) OUTPUT: pattern speed HISTORY: 2016-11-02 - Written - Bovy (UofT) """ return self._omegab def _compute_xyz(self,R,phi,z,t): return bovy_coords.cyl_to_rect(R,phi-self._pa-self._omegab*t,z) def _compute_TpTm(self,x,y,z): secondpart= y**2.+(self._b+numpy.sqrt(self._c2+z**2.))**2. return (numpy.sqrt((self._a+x)**2.+secondpart), numpy.sqrt((self._a-x)**2.+secondpart)) def _compute_xyzforces(self,R,z,phi,t): # Compute all rectangular forces new_hash= hashlib.md5(numpy.array([R,phi,z,t])).hexdigest() if new_hash != self._force_hash: x,y,z= self._compute_xyz(R,phi,z,t) Tp, Tm= self._compute_TpTm(x,y,z) Fx= self._xforce_xyz(x,y,z,Tp,Tm) Fy= self._yforce_xyz(x,y,z,Tp,Tm) Fz= self._zforce_xyz(x,y,z,Tp,Tm) self._force_hash= new_hash tp= self._pa+self._omegab*t cp, sp= numpy.cos(tp), numpy.sin(tp) self._cached_Fx= cp*Fx-sp*Fy self._cached_Fy= sp*Fx+cp*Fy self._cached_Fz= Fz def _xforce_xyz(self,x,y,z,Tp,Tm): return -2.*x/Tp/Tm/(Tp+Tm) def _yforce_xyz(self,x,y,z,Tp,Tm): return -y/2./Tp/Tm*(Tp+Tm-4.*x**2./(Tp+Tm))\ /(y**2.+(self._b+numpy.sqrt(z**2.+self._c2))**2.) def _zforce_xyz(self,x,y,z,Tp,Tm): zc= numpy.sqrt(z**2.+self._c2) return self._yforce_xyz(x,y,z,Tp,Tm)*z/y*(self._b+zc)/zc def _dens(self,R,z,phi=0.,t=0.): """ NAME: _dens PURPOSE: evaluate the density for this potential INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t - time OUTPUT: the density HISTORY: 2016-11-04 - Written - Bovy (UofT/CCA) """ x,y,z= self._compute_xyz(R,phi,z,t) zc= numpy.sqrt(z**2.+self._c2) bzc2= (self._b+zc)**2. bigA= self._b*y**2.+(self._b+3.*zc)*bzc2 bigC= y**2.+bzc2 return self._c2/24./numpy.pi/self._a/bigC**2./zc**3.\ *((x+self._a)*(3.*bigA*bigC+(2.*bigA+self._b*bigC)*(x+self._a)**2.)\ /(bigC+(x+self._a)**2.)**1.5\ -(x-self._a)*(3.*bigA*bigC+(2.*bigA+self._b*bigC)*(x-self._a)**2.)\ /(bigC+(x-self._a)**2.)**1.5) galpy-1.3.0/galpy/potential_src/SolidBodyRotationWrapperPotential.py0000644000076500000240000000501113153272706026121 0ustar bovystaff00000000000000############################################################################### # SolidBodyRotationWrapperPotential.py: Wrapper to make a potential rotate # with a fixed pattern speed, around # the z axis ############################################################################### from galpy.potential_src.WrapperPotential import parentWrapperPotential from galpy.potential_src.Potential import _APY_LOADED from galpy.util import bovy_conversion if _APY_LOADED: from astropy import units class SolidBodyRotationWrapperPotential(parentWrapperPotential): """Potential wrapper class that implements solid-body rotation around the z-axis. Can be used to make a bar or other perturbation rotate. The potential is rotated by replacing .. math:: \\phi \\rightarrow \\phi + \\Omega \\times t + \\mathrm{pa} with :math:`\\Omega` the fixed pattern speed and :math:`\\mathrm{pa}` the position angle at :math:`t=0`. """ def __init__(self,amp=1.,pot=None,omega=1.,pa=0.,ro=None,vo=None): """ NAME: __init__ PURPOSE: initialize a SolidBodyRotationWrapper Potential INPUT: amp - amplitude to be applied to the potential (default: 1.) pot - Potential instance or list thereof; this potential is made to rotate around the z axis by the wrapper omega= (1.) the pattern speed (can be a Quantity) pa= (0.) the position angle (can be a Quantity) OUTPUT: (none) HISTORY: 2017-08-22 - Started - Bovy (UofT) """ if _APY_LOADED and isinstance(omega,units.Quantity): omega= omega.to(units.km/units.s/units.kpc).value\ /bovy_conversion.freq_in_kmskpc(self._vo,self._ro) if _APY_LOADED and isinstance(pa,units.Quantity): pa= pa.to(units.rad).value self._omega= omega self._pa= pa self.hasC= True self.hasC_dxdv= True def OmegaP(self): """ NAME: OmegaP PURPOSE: return the pattern speed INPUT: (none) OUTPUT: pattern speed HISTORY: 2016-11-02 - Written - Bovy (UofT) """ return self._omega def _wrap(self,attribute,*args,**kwargs): kwargs['phi']= \ kwargs.get('phi',0.)-self._omega*kwargs.get('t',0.)-self._pa return self._wrap_pot_func(attribute)(self._pot,*args,**kwargs) galpy-1.3.0/galpy/potential_src/SpiralArmsPotential.py0000644000076500000240000006173613147554716023254 0ustar bovystaff00000000000000############################################################################### # SpiralArmsPotential.py: class that implements the spiral arms potential # from Cox and Gomez (2002) # # https://arxiv.org/abs/astro-ph/0207635 # # Phi(r, phi, z) = -4*pi*G*H*rho0*exp(-(r-r0)/Rs)*sum(Cn/(Kn*Dn)*cos(n*gamma)*sech(Kn*z/Bn)^Bn) # NOTE: Methods do not take array inputs. ############################################################################### from __future__ import division from functools import wraps from galpy.potential_src.Potential import Potential, _APY_LOADED from galpy.util import bovy_conversion import numpy as np if _APY_LOADED: from astropy import units def check_inputs_not_arrays(func): """ Decorator to check inputs and throw TypeError if any of the inputs are arrays. Methods potentially return with silent errors if inputs are not checked. """ @wraps(func) def func_wrapper(self, R, z, phi, t): if hasattr(R, '__len__') or hasattr(z, '__len__') or hasattr(phi, '__len__') or hasattr(t, '__len__'): raise TypeError('Methods in SpiralArmsPotential do not accept array inputs. Please input scalars.') return func(self, R, z, phi, t) return func_wrapper class SpiralArmsPotential(Potential): """Class that implements the spiral arms potential from (`Cox and Gomez 2002 `__). Should be used to modulate an existing potential (density is positive in the arms, negative outside). .. math:: \\Phi(R, \\phi, z) = -4 \\pi GH \\,\\rho_0 exp \\left( -\\frac{R-r_{ref}}{R_s} \\right) \\sum{\\frac{C_n}{K_n D_n} \\,\cos(n \\gamma) \\,\\mathrm{sech}^{B_n} \\left( \\frac{K_n z}{B_n} \\right)} where .. math:: K_n &= \\frac{n N}{R \sin(\\alpha)} \\\\ B_n &= K_n H (1 + 0.4 K_n H) \\\\ D_n &= \\frac{1 + K_n H + 0.3 (K_n H)^2}{1 + 0.3 K_n H} \\\\ and .. math:: \\gamma = N \\left[\\phi - \\phi_{ref} - \\frac{\\ln(R/r_{ref})}{\\tan(\\alpha)} \\right] The default of :math:`C_n=[1]` gives a sinusoidal profile for the potential. An alternative from `Cox and Gomez (2002) `__ creates a density that behaves approximately as a cosine squared in the arms but is separated by a flat interarm region by setting .. math:: C_n = \\left[\\frac{8}{3 \\pi}\,,\\frac{1}{2} \\,, \\frac{8}{15 \\pi}\\right] """ normalize= property() # turn off normalize def __init__(self, amp=1, ro=None, vo=None, amp_units='density', N=2, alpha=0.2, r_ref=1, phi_ref=0, Rs=0.3, H=0.125, omega=0, Cs=[1]): """ NAME: __init__ PURPOSE: initialize a spiral arms potential INPUT: :amp: amplitude to be applied to the potential (default: 1); can be a Quantity with units of density. (:math:`amp = 4 \\pi G \\rho_0`) :ro: distance scales for translation into internal units (default from configuration file) :vo: velocity scales for translation into internal units (default from configuration file) :N: number of spiral arms :alpha: pitch angle of the logarithmic spiral arms in radians (can be Quantity) :r_ref: fiducial radius where :math:`\\rho = \\rho_0` (:math:`r_0` in the paper by Cox and Gomez) (can be Quantity) :phi_ref: reference angle (:math:`\\phi_p(r_0)` in the paper by Cox and Gomez) (can be Quantity) :Rs: radial scale length of the drop-off in density amplitude of the arms (can be Quantity) :H: scale height of the stellar arm perturbation (can be Quantity) :Cs: list of constants multiplying the :math:`\cos(n \\gamma)` terms :omega: rotational pattern speed of the spiral arms (can be Quantity) OUTPUT: (none) HISTORY: Started - 2017-05-12 Jack Hong (UBC) Completed - 2017-07-04 Jack Hong (UBC) """ Potential.__init__(self, amp=amp, ro=ro, vo=vo, amp_units=amp_units) if _APY_LOADED: if isinstance(alpha, units.Quantity): alpha = alpha.to(units.rad).value if isinstance(r_ref, units.Quantity): r_ref = r_ref.to(units.kpc).value / self._ro if isinstance(phi_ref, units.Quantity): phi_ref = phi_ref.to(units.rad).value if isinstance(Rs, units.Quantity): Rs = Rs.to(units.kpc).value / self._ro if isinstance(H, units.Quantity): H = H.to(units.kpc).value / self._ro if isinstance(omega, units.Quantity): omega = omega.to(units.km / units.s / units.kpc).value \ / bovy_conversion.freq_in_kmskpc(self._vo, self._ro) self._N = -N # trick to flip to left handed coordinate system; flips sign for phi and phi_ref, but also alpha. self._alpha = -alpha # we don't want sign for alpha to change, so flip alpha. (see eqn. 3 in the paper) self._sin_alpha = np.sin(-alpha) self._tan_alpha = np.tan(-alpha) self._r_ref = r_ref self._phi_ref = phi_ref self._Rs = Rs self._H = H self._Cs = np.array(Cs) self._ns = np.arange(1, len(Cs) + 1) self._omega = omega self._rho0 = 1 / (4 * np.pi) self._HNn = self._H * self._N * self._ns self.isNonAxi = True # Potential is not axisymmetric self.hasC = True # Potential has C implementation to speed up orbit integrations self.hasC_dxdv = True # Potential has C implementation of second derivatives @check_inputs_not_arrays def _evaluate(self, R, z, phi=0, t=0): """ NAME: _evaluate PURPOSE: Evaluate the potential at the given coordinates. (without the amp factor; handled by super class) INPUT: :param R: galactocentric cylindrical radius (must be scalar, not array) :param z: vertical height (must be scalar, not array) :param phi: azimuth (must be scalar, not array) :param t: time (must be scalar, not array) OUTPUT: :return: Phi(R, z, phi, t) HISTORY: 2017-05-12 Jack Hong (UBC) """ Ks = self._K(R) Bs = self._B(R) Ds = self._D(R) return -self._H * np.exp(-(R-self._r_ref) / self._Rs) \ * np.sum(self._Cs / Ks / Ds * np.cos(self._ns * self._gamma(R, phi - self._omega * t)) / np.cosh(Ks * z / Bs) ** Bs) @check_inputs_not_arrays def _Rforce(self, R, z, phi=0, t=0): """ NAME: _Rforce PURPOSE: Evaluate the radial force for this potential at the given coordinates. (-dPhi/dR) INPUT: :param R: galactocentric cylindrical radius (must be scalar, not array) :param z: vertical height (must be scalar, not array) :param phi: azimuth (must be scalar, not array) :param t: time (must be scalar, not array) OUTPUT: :return: the radial force HISTORY: 2017-05-12 Jack Hong (UBC) """ He = self._H * np.exp(-(R-self._r_ref)/self._Rs) Ks = self._K(R) Bs = self._B(R) Ds = self._D(R) dKs_dR = self._dK_dR(R) dBs_dR = self._dB_dR(R) dDs_dR = self._dD_dR(R) g = self._gamma(R, phi - self._omega * t) dg_dR = self._dgamma_dR(R) cos_ng = np.cos(self._ns * g) sin_ng = np.sin(self._ns * g) zKB = z * Ks / Bs sechzKB = 1 / np.cosh(zKB) return -He * np.sum(self._Cs * sechzKB**Bs / Ds * ((self._ns * dg_dR / Ks * sin_ng + cos_ng * (z * np.tanh(zKB) * (dKs_dR/Ks - dBs_dR/Bs) - dBs_dR / Ks * np.log(sechzKB) + dKs_dR / Ks**2 + dDs_dR / Ds / Ks)) + cos_ng / Ks / self._Rs)) @check_inputs_not_arrays def _zforce(self, R, z, phi=0, t=0): """ NAME: _zforce PURPOSE: Evaluate the vertical force for this potential at the given coordinates. (-dPhi/dz) INPUT: :param R: galactocentric cylindrical radius (must be scalar, not array) :param z: vertical height (must be scalar, not array) :param phi: azimuth (must be scalar, not array) :param t: time (must be scalar, not array) OUTPUT: :return: the vertical force HISTORY: 2017-05-25 Jack Hong (UBC) """ Ks = self._K(R) Bs = self._B(R) Ds = self._D(R) zK_B = z * Ks / Bs return -self._H * np.exp(-(R-self._r_ref) / self._Rs) \ * np.sum(self._Cs / Ds * np.cos(self._ns * self._gamma(R, phi - self._omega * t)) * np.tanh(zK_B) / np.cosh(zK_B)**Bs) @check_inputs_not_arrays def _phiforce(self, R, z, phi=0, t=0): """ NAME: _phiforce PURPOSE: Evaluate the azimuthal force in cylindrical coordinates. (-dPhi/dphi) INPUT: :param R: galactocentric cylindrical radius (must be scalar, not array) :param z: vertical height (must be scalar, not array) :param phi: azimuth (must be scalar, not array) :param t: time (must be scalar, not array) OUTPUT: :return: the azimuthal force HISTORY: 2017-05-25 Jack Hong (UBC) """ g = self._gamma(R, phi - self._omega * t) Ks = self._K(R) Bs = self._B(R) Ds = self._D(R) return -self._H * np.exp(-(R-self._r_ref) / self._Rs) \ * np.sum(self._N * self._ns * self._Cs / Ds / Ks / np.cosh(z * Ks / Bs)**Bs * np.sin(self._ns * g)) @check_inputs_not_arrays def _R2deriv(self, R, z, phi=0, t=0): """ NAME: _R2deriv PURPOSE: Evaluate the second (cylindrical) radial derivative of the potential. (d^2 potential / d R^2) INPUT: :param R: galactocentric cylindrical radius (must be scalar, not array) :param z: vertical height (must be scalar, not array) :param phi: azimuth (must be scalar, not array) :param t: time (must be scalar, not array) OUTPUT: :return: the second radial derivative HISTORY: 2017-05-31 Jack Hong (UBC) """ Rs = self._Rs He = self._H * np.exp(-(R-self._r_ref)/self._Rs) Ks = self._K(R) Bs = self._B(R) Ds = self._D(R) dKs_dR = self._dK_dR(R) dBs_dR = self._dB_dR(R) dDs_dR = self._dD_dR(R) R_sina = R * self._sin_alpha HNn_R_sina = self._HNn / R_sina HNn_R_sina_2 = HNn_R_sina**2 x = R * (0.3 * HNn_R_sina + 1) * self._sin_alpha d2Ks_dR2 = 2 * self._N * self._ns / R**3 / self._sin_alpha d2Bs_dR2 = HNn_R_sina / R**2 * (2.4 * HNn_R_sina + 2) d2Ds_dR2 = self._sin_alpha / R / x * (self._HNn* (0.18 * self._HNn * (HNn_R_sina + 0.3 * HNn_R_sina_2 + 1) / x**2 + 2 / R_sina - 0.6 * HNn_R_sina * (1 + 0.6 * HNn_R_sina) / x - 0.6 * (HNn_R_sina + 0.3 * HNn_R_sina_2 + 1) / x + 1.8 * self._HNn / R_sina**2)) g = self._gamma(R, phi - self._omega * t) dg_dR = self._dgamma_dR(R) d2g_dR2 = self._N / R**2 / self._tan_alpha sin_ng = np.sin(self._ns * g) cos_ng = np.cos(self._ns * g) zKB = z * Ks / Bs sechzKB = 1 / np.cosh(zKB) sechzKB_Bs = sechzKB**Bs log_sechzKB = np.log(sechzKB) tanhzKB = np.tanh(zKB) ztanhzKB = z * tanhzKB return -He / Rs * (np.sum(self._Cs * sechzKB_Bs / Ds * ((self._ns * dg_dR / Ks * sin_ng + cos_ng * (ztanhzKB * (dKs_dR/Ks - dBs_dR/Bs) - dBs_dR / Ks * log_sechzKB + dKs_dR / Ks**2 + dDs_dR / Ds / Ks)) - (Rs * (1 / Ks * ((ztanhzKB * (dBs_dR / Bs * Ks - dKs_dR) + log_sechzKB * dBs_dR) - dDs_dR / Ds) * (self._ns * dg_dR * sin_ng + cos_ng * (ztanhzKB * Ks * (dKs_dR/Ks - dBs_dR/Bs) - dBs_dR * log_sechzKB + dKs_dR / Ks + dDs_dR / Ds)) + (self._ns * (sin_ng * (d2g_dR2 / Ks - dg_dR / Ks**2 * dKs_dR) + dg_dR**2 / Ks * cos_ng * self._ns) + z * (-sin_ng * self._ns * dg_dR * tanhzKB * (dKs_dR/Ks - dBs_dR/Bs) + cos_ng * (z * (dKs_dR/Bs - dBs_dR/Bs**2 * Ks) * (1-tanhzKB**2) * (dKs_dR/Ks - dBs_dR/Bs) + tanhzKB * (d2Ks_dR2/Ks-(dKs_dR/Ks)**2 - d2Bs_dR2/Bs + (dBs_dR/Bs)**2))) + (cos_ng * (dBs_dR/Ks * ztanhzKB * (dKs_dR/Bs - dBs_dR/Bs**2*Ks) -(d2Bs_dR2/Ks-dBs_dR*dKs_dR/Ks**2) * log_sechzKB) + dBs_dR/Ks * log_sechzKB * sin_ng * self._ns * dg_dR) + ((cos_ng * (d2Ks_dR2 / Ks**2 - 2 * dKs_dR**2 / Ks**3) - dKs_dR / Ks**2 * sin_ng * self._ns * dg_dR) + (cos_ng * (d2Ds_dR2 / Ds / Ks - (dDs_dR/Ds)**2 / Ks - dDs_dR / Ds / Ks**2 * dKs_dR) - sin_ng * self._ns * dg_dR * dDs_dR / Ds / Ks)))) - 1 / Ks * (cos_ng / Rs + (cos_ng * ((dDs_dR * Ks + Ds * dKs_dR) / (Ds * Ks) - (ztanhzKB * (dBs_dR / Bs * Ks - dKs_dR) + log_sechzKB * dBs_dR)) + sin_ng * self._ns * dg_dR)))))) @check_inputs_not_arrays def _z2deriv(self, R, z, phi=0, t=0): """ NAME: _z2deriv PURPOSE: Evaluate the second (cylindrical) vertical derivative of the potential. (d^2 potential / d z^2) INPUT: :param R: galactocentric cylindrical radius (must be scalar, not array) :param z: vertical height (must be scalar, not array) :param phi: azimuth (must be scalar, not array) :param t: time (must be scalar, not array) OUTPUT: :return: the second vertical derivative HISTORY: 2017-05-26 Jack Hong (UBC) """ g = self._gamma(R, phi - self._omega * t) Ks = self._K(R) Bs = self._B(R) Ds = self._D(R) zKB = z * Ks / Bs tanh2_zKB = np.tanh(zKB)**2 return -self._H * np.exp(-(R-self._r_ref)/self._Rs) \ * np.sum(self._Cs * Ks / Ds * ((tanh2_zKB - 1) / Bs + tanh2_zKB) * np.cos(self._ns * g) / np.cosh(zKB)**Bs) @check_inputs_not_arrays def _phi2deriv(self, R, z, phi=0, t=0): """ NAME: _phi2deriv PURPOSE: Evaluate the second azimuthal derivative of the potential in cylindrical coordinates. (d^2 potential / d phi^2) INPUT: :param R: galactocentric cylindrical radius (must be scalar, not array) :param z: vertical height (must be scalar, not array) :param phi: azimuth (must be scalar, not array) :param t: time (must be scalar, not array) OUTPUT: :return: d^2 potential / d phi^2 HISTORY: 2017-05-29 Jack Hong (UBC) """ g = self._gamma(R, phi - self._omega * t) Ks = self._K(R) Bs = self._B(R) Ds = self._D(R) return self._H * np.exp(-(R-self._r_ref) / self._Rs) \ * np.sum(self._Cs * self._N**2. * self._ns**2. / Ds / Ks / np.cosh(z*Ks/Bs)**Bs * np.cos(self._ns*g)) @check_inputs_not_arrays def _Rzderiv(self, R, z, phi=0., t=0.): """ NAME: _Rzderiv PURPOSE: Evaluate the mixed (cylindrical) radial and vertical derivative of the potential (d^2 potential / dR dz). INPUT: :param R: galactocentric cylindrical radius (must be scalar, not array) :param z: vertical height (must be scalar, not array) :param phi: azimuth (must be scalar, not array) :param t: time (must be scalar, not array) OUTPUT: :return: d^2 potential / dR dz HISTORY: 2017-05-12 Jack Hong (UBC) """ Rs = self._Rs He = self._H * np.exp(-(R-self._r_ref)/self._Rs) Ks = self._K(R) Bs = self._B(R) Ds = self._D(R) dKs_dR = self._dK_dR(R) dBs_dR = self._dB_dR(R) dDs_dR = self._dD_dR(R) g = self._gamma(R, phi - self._omega * t) dg_dR = self._dgamma_dR(R) cos_ng = np.cos(self._ns * g) sin_ng = np.sin(self._ns * g) zKB = z * Ks / Bs sechzKB = 1 / np.cosh(zKB) sechzKB_Bs = sechzKB**Bs log_sechzKB = np.log(sechzKB) tanhzKB = np.tanh(zKB) return - He * np.sum(sechzKB_Bs * self._Cs / Ds * (Ks * tanhzKB * (self._ns * dg_dR / Ks * sin_ng + cos_ng * (z * tanhzKB * (dKs_dR/Ks - dBs_dR/Bs) - dBs_dR / Ks * log_sechzKB + dKs_dR / Ks**2 + dDs_dR / Ds / Ks)) - cos_ng * ((zKB * (dKs_dR/Ks - dBs_dR/Bs) * (1 - tanhzKB**2) + tanhzKB * (dKs_dR/Ks - dBs_dR/Bs) + dBs_dR / Bs * tanhzKB) - tanhzKB / Rs))) @check_inputs_not_arrays def _Rphideriv(self, R, z, phi=0,t=0): """ NAME: _Rphideriv PURPOSE: Return the mixed radial and azimuthal derivative of the potential in cylindrical coordinates (d^2 potential / dR dphi) INPUT: :param R: galactocentric cylindrical radius (must be scalar, not array) :param z: vertical height (must be scalar, not array) :param phi: azimuth (must be scalar, not array) :param t: time (must be scalar, not array) OUTPUT: :return: the mixed radial and azimuthal derivative HISTORY: 2017-06-09 Jack Hong (UBC) """ He = self._H * np.exp(-(R - self._r_ref) / self._Rs) Ks = self._K(R) Bs = self._B(R) Ds = self._D(R) dKs_dR = self._dK_dR(R) dBs_dR = self._dB_dR(R) dDs_dR = self._dD_dR(R) g = self._gamma(R, phi - self._omega * t) dg_dR = self._dgamma_dR(R) cos_ng = np.cos(self._ns * g) sin_ng = np.sin(self._ns * g) zKB = z * Ks / Bs sechzKB = 1 / np.cosh(zKB) sechzKB_Bs = sechzKB ** Bs return - He * np.sum(self._Cs * sechzKB_Bs / Ds * self._ns * self._N * (- self._ns * dg_dR / Ks * cos_ng + sin_ng * (z * np.tanh(zKB) * (dKs_dR / Ks - dBs_dR / Bs) + 1/Ks * (-dBs_dR * np.log(sechzKB) + dKs_dR / Ks + dDs_dR / Ds + 1 / self._Rs)))) @check_inputs_not_arrays def _dens(self, R, z, phi=0, t=0): """ NAME: _dens PURPOSE: Evaluate the density. If not given, the density is computed using the Poisson equation from the first and second derivatives of the potential (if all are implemented). INPUT: :param R: galactocentric cylindrical radius (must be scalar, not array) :param z: vertical height (must be scalar, not array) :param phi: azimuth (must be scalar, not array) :param t: time (must be scalar, not array) OUTPUT: :return: the density HISTORY: 2017-05-12 Jack Hong (UBC) """ g = self._gamma(R, phi - self._omega * t) Ks = self._K(R) Bs = self._B(R) Ds = self._D(R) ng = self._ns * g zKB = z * Ks / Bs sech_zKB = 1 / np.cosh(zKB) tanh_zKB = np.tanh(zKB) log_sech_zKB = np.log(sech_zKB) # numpy of E as defined in the appendix of the paper. E = 1 + Ks * self._H / Ds * (1 - 0.3 / (1 + 0.3 * Ks * self._H) ** 2) - R / self._Rs \ - (Ks * self._H) * (1 + 0.8 * Ks * self._H) * log_sech_zKB \ - 0.4 * (Ks * self._H) ** 2 * zKB * tanh_zKB # numpy array of rE' as define in the appendix of the paper. rE = -Ks * self._H / Ds * (1 - 0.3 * (1 - 0.3 * Ks * self._H) / (1 + 0.3 * Ks * self._H) ** 3) \ + (Ks * self._H / Ds * (1 - 0.3 / (1 + 0.3 * Ks * self._H) ** 2)) - R / self._Rs \ + Ks * self._H * (1 + 1.6 * Ks * self._H) * log_sech_zKB \ - (0.4 * (Ks * self._H) ** 2 * zKB * sech_zKB) ** 2 / Bs \ + 1.2 * (Ks * self._H) ** 2 * zKB * tanh_zKB return np.sum(self._Cs * self._rho0 * (self._H / (Ds * R)) * np.exp(-(R - self._r_ref) / self._Rs) * sech_zKB**Bs * (np.cos(ng) * (Ks * R * (Bs + 1) / Bs * sech_zKB**2 - 1 / Ks / R * (E**2 + rE)) - 2 * np.sin(ng)* E * np.cos(self._alpha))) def OmegaP(self): """ NAME: OmegaP PURPOSE: Return the pattern speed. (used to compute the Jacobi integral for orbits). INPUT: :param self OUTPUT: :return: the pattern speed HISTORY: 2017-06-09 Jack Hong (UBC) """ return self._omega def _gamma(self, R, phi): """Return gamma. (eqn 3 in the paper)""" return self._N * (phi - self._phi_ref - np.log(R / self._r_ref) / self._tan_alpha) def _dgamma_dR(self, R): """Return the first derivative of gamma wrt R.""" return -self._N / R / self._tan_alpha def _K(self, R): """Return numpy array from K1 up to and including Kn. (eqn. 5)""" return self._ns * self._N / R / self._sin_alpha def _dK_dR(self, R): """Return numpy array of dK/dR from K1 up to and including Kn.""" return -self._ns * self._N / R**2 / self._sin_alpha def _B(self, R): """Return numpy array from B1 up to and including Bn. (eqn. 6)""" HNn_R = self._HNn / R return HNn_R / self._sin_alpha * (0.4 * HNn_R / self._sin_alpha + 1) def _dB_dR(self, R): """Return numpy array of dB/dR from B1 up to and including Bn.""" return -self._HNn / R**3 / self._sin_alpha**2 * (0.8 * self._HNn + R * self._sin_alpha) def _D(self, R): """Return numpy array from D1 up to and including Dn. (eqn. 7)""" return (0.3 * self._HNn**2 / self._sin_alpha / R + self._HNn + R * self._sin_alpha) / (0.3 * self._HNn + R * self._sin_alpha) def _dD_dR(self, R): """Return numpy array of dD/dR from D1 up to and including Dn.""" HNn_R_sina = self._HNn / R / self._sin_alpha return HNn_R_sina * (0.3 * (HNn_R_sina + 0.3 * HNn_R_sina**2. + 1) / R / (0.3 * HNn_R_sina + 1)**2 - (1/R * (1 + 0.6 * HNn_R_sina) / (0.3 * HNn_R_sina + 1))) galpy-1.3.0/galpy/potential_src/SteadyLogSpiralPotential.py0000644000076500000240000001705513003761364024226 0ustar bovystaff00000000000000############################################################################### # SteadyLogSpiralPotential: a steady-state spiral potential ############################################################################### import math from galpy.util import bovy_conversion from galpy.potential_src.planarPotential import planarPotential, _APY_LOADED if _APY_LOADED: from astropy import units _degtorad= math.pi/180. class SteadyLogSpiralPotential(planarPotential): """Class that implements a steady-state spiral potential .. math:: \\Phi(R,\\phi) = \\frac{\\mathrm{amp}\\times A}{\\alpha}\\,\\cos\\left(\\alpha\,\ln R - m\\,(\\phi-\\Omega_s\\,t-\\gamma)\\right) Can be grown in a similar way as the DehnenBarPotential, but using :math:`T_s = 2\pi/\Omega_s` to normalize :math:`t_{\mathrm{form}}` and :math:`T_{\mathrm{steady}}`. """ def __init__(self,amp=1.,omegas=0.65,A=-0.035, alpha=-7.,m=2,gamma=math.pi/4.,p=None, tform=None,tsteady=None,ro=None,vo=None): """ NAME: __init__ PURPOSE: initialize a logarithmic spiral potential INPUT: amp - amplitude to be applied to the potential (default: 1., A below) gamma - angle between sun-GC line and the line connecting the peak of the spiral pattern at the Solar radius (in rad; default=45 degree; or can be Quantity) A - amplitude (alpha*potential-amplitude; default=0.035; can be Quantity omegas= - pattern speed (default=0.65; can be Quantity) m= number of arms Either provide: a) alpha= b) p= pitch angle (rad; can be Quantity) tform - start of spiral growth / spiral period (default: -Infinity) tsteady - time from tform at which the spiral is fully grown / spiral period (default: 2 periods) OUTPUT: (none) HISTORY: 2011-03-27 - Started - Bovy (NYU) """ planarPotential.__init__(self,amp=amp,ro=ro,vo=vo) if _APY_LOADED and isinstance(gamma,units.Quantity): gamma= gamma.to(units.rad).value if _APY_LOADED and isinstance(p,units.Quantity): p= p.to(units.rad).value if _APY_LOADED and isinstance(A,units.Quantity): A= A.to(units.km**2/units.s**2).value/self._vo**2. if _APY_LOADED and isinstance(omegas,units.Quantity): omegas= omegas.to(units.km/units.s/units.kpc).value\ /bovy_conversion.freq_in_kmskpc(self._vo,self._ro) self._omegas= omegas self._A= A self._m= m self._gamma= gamma if not p is None: self._alpha= self._m/math.tan(p) else: self._alpha= alpha self._ts= 2.*math.pi/self._omegas if not tform is None: self._tform= tform*self._ts else: self._tform= None if not tsteady is None: self._tsteady= self._tform+tsteady*self._ts else: if self._tform is None: self._tsteady= None else: self._tsteady= self._tform+2.*self._ts self.hasC= True def _evaluate(self,R,phi=0.,t=0.): """ NAME: _evaluate PURPOSE: evaluate the potential at R,phi,t INPUT: R - Galactocentric cylindrical radius phi - azimuth t - time OUTPUT: Phi(R,phi,t) HISTORY: 2011-03-27 - Started - Bovy (NYU) """ if not self._tform is None: if t < self._tform: smooth= 0. elif t < self._tsteady: deltat= t-self._tform xi= 2.*deltat/(self._tsteady-self._tform)-1. smooth= (3./16.*xi**5.-5./8*xi**3.+15./16.*xi+.5) else: #spiral is fully on smooth= 1. else: smooth= 1. return smooth*self._A/self._alpha*math.cos(self._alpha*math.log(R) -self._m*(phi-self._omegas*t -self._gamma)) def _Rforce(self,R,phi=0.,t=0.): """ NAME: _Rforce PURPOSE: evaluate the radial force for this potential INPUT: R - Galactocentric cylindrical radius phi - azimuth t - time OUTPUT: the radial force HISTORY: 2010-11-24 - Written - Bovy (NYU) """ if not self._tform is None: if t < self._tform: smooth= 0. elif t < self._tsteady: deltat= t-self._tform xi= 2.*deltat/(self._tsteady-self._tform)-1. smooth= (3./16.*xi**5.-5./8*xi**3.+15./16.*xi+.5) else: #spiral is fully on smooth= 1. else: smooth= 1. return smooth*self._A/R*math.sin(self._alpha*math.log(R) -self._m*(phi-self._omegas*t -self._gamma)) def _phiforce(self,R,phi=0.,t=0.): """ NAME: _phiforce PURPOSE: evaluate the azimuthal force for this potential INPUT: R - Galactocentric cylindrical radius phi - azimuth t - time OUTPUT: the azimuthal force HISTORY: 2010-11-24 - Written - Bovy (NYU) """ if not self._tform is None: if t < self._tform: smooth= 0. elif t < self._tsteady: deltat= t-self._tform xi= 2.*deltat/(self._tsteady-self._tform)-1. smooth= (3./16.*xi**5.-5./8*xi**3.+15./16.*xi+.5) else: #spiral is fully on smooth= 1. else: smooth= 1. return -smooth*self._A/self._alpha*self._m*math.sin(self._alpha*math.log(R) -self._m*(phi -self._omegas*t -self._gamma)) def wavenumber(self,R): """ NAME: wavenumber PURPOSE: return the wavenumber at radius R (d f(R)/ d R in Phi_a(R) = F(R) e^[i f(R)]; see Binney & Tremaine 2008) INPUT: R - Cylindrical radius OUTPUT: wavenumber at R HISTORY: 2014-08-23 - Written - Bovy (IAS) """ return self._alpha/R def OmegaP(self): """ NAME: OmegaP PURPOSE: return the pattern speed INPUT: (none) OUTPUT: pattern speed HISTORY: 2011-10-10 - Written - Bovy (IAS) """ return self._omegas def m(self): """ NAME: m PURPOSE: return the number of arms INPUT: (none) OUTPUT: number of arms HISTORY: 2014-08-23 - Written - Bovy (IAS) """ return self._m def tform(self): #pragma: no cover """ NAME: tform PURPOSE: return formation time of the bar INPUT: (none) OUTPUT: tform in normalized units HISTORY: 2011-03-08 - Written - Bovy (NYU) """ return self._tform galpy-1.3.0/galpy/potential_src/TransientLogSpiralPotential.py0000644000076500000240000001234413147073107024740 0ustar bovystaff00000000000000############################################################################### # TransientLogSpiralPotential: a transient spiral potential ############################################################################### import math from galpy.util import bovy_conversion from galpy.potential_src.planarPotential import planarPotential, _APY_LOADED if _APY_LOADED: from astropy import units _degtorad= math.pi/180. class TransientLogSpiralPotential(planarPotential): """Class that implements a steady-state spiral potential .. math:: \\Phi(R,\\phi) = \\frac{\\mathrm{amp}(t)}{\\alpha}\\,\\cos\\left(\\alpha\,\ln R - m\\,(\\phi-\\Omega_s\\,t-\\gamma)\\right) where .. math:: \\mathrm{amp}(t) = \\mathrm{amp}\\,\\times A\\,\\exp\\left(-\\frac{[t-t_0]^2}{2\\,\\sigma^2}\\right) """ def __init__(self,amp=1.,omegas=0.65,A=-0.035, alpha=-7.,m=2,gamma=math.pi/4.,p=None, sigma=1.,to=0.,ro=None,vo=None): """ NAME: __init__ PURPOSE: initialize a transient logarithmic spiral potential localized around to INPUT: amp - amplitude to be applied to the potential (default: 1., A below) gamma - angle between sun-GC line and the line connecting the peak of the spiral pattern at the Solar radius (in rad; default=45 degree; can be Quantity) A - amplitude (alpha*potential-amplitude; default=0.035; can be Quantity) omegas= - pattern speed (default=0.65; can be Quantity) m= number of arms to= time at which the spiral peaks (can be Quantity) sigma= "spiral duration" (sigma in Gaussian amplitude; can be Quantity) Either provide: a) alpha= b) p= pitch angle (rad; can be Quantity) OUTPUT: (none) HISTORY: 2011-03-27 - Started - Bovy (NYU) """ planarPotential.__init__(self,amp=amp,ro=ro,vo=vo) if _APY_LOADED and isinstance(gamma,units.Quantity): gamma= gamma.to(units.rad).value if _APY_LOADED and isinstance(p,units.Quantity): p= p.to(units.rad).value if _APY_LOADED and isinstance(A,units.Quantity): A= A.to(units.km**2/units.s**2).value/self._vo**2. if _APY_LOADED and isinstance(omegas,units.Quantity): omegas= omegas.to(units.km/units.s/units.kpc).value\ /bovy_conversion.freq_in_kmskpc(self._vo,self._ro) if _APY_LOADED and isinstance(to,units.Quantity): to= to.to(units.Gyr).value\ /bovy_conversion.time_in_Gyr(self._vo,self._ro) if _APY_LOADED and isinstance(sigma,units.Quantity): sigma= sigma.to(units.Gyr).value\ /bovy_conversion.time_in_Gyr(self._vo,self._ro) self._omegas= omegas self._A= A self._m= m self._gamma= gamma self._to= to self._sigma2= sigma**2. if not p is None: self._alpha= self._m/math.tan(p) else: self._alpha= alpha self.hasC= True def _evaluate(self,R,phi=0.,t=0.): """ NAME: _evaluate PURPOSE: evaluate the potential at R,phi,t INPUT: R - Galactocentric cylindrical radius phi - azimuth t - time OUTPUT: Phi(R,phi,t) HISTORY: 2011-03-27 - Started - Bovy (NYU) """ return self._A*math.exp(-(t-self._to)**2./2./self._sigma2)\ /self._alpha*math.cos(self._alpha*math.log(R) -self._m*(phi-self._omegas*t-self._gamma)) def _Rforce(self,R,phi=0.,t=0.): """ NAME: _Rforce PURPOSE: evaluate the radial force for this potential INPUT: R - Galactocentric cylindrical radius phi - azimuth t - time OUTPUT: the radial force HISTORY: 2010-11-24 - Written - Bovy (NYU) """ return self._A*math.exp(-(t-self._to)**2./2./self._sigma2)\ /R*math.sin(self._alpha*math.log(R) -self._m*(phi-self._omegas*t-self._gamma)) def _phiforce(self,R,phi=0.,t=0.): """ NAME: _phiforce PURPOSE: evaluate the azimuthal force for this potential INPUT: R - Galactocentric cylindrical radius phi - azimuth t - time OUTPUT: the azimuthal force HISTORY: 2010-11-24 - Written - Bovy (NYU) """ return -self._A*math.exp(-(t-self._to)**2./2./self._sigma2)\ /self._alpha*self._m*math.sin(self._alpha*math.log(R) -self._m*(phi-self._omegas*t -self._gamma)) def OmegaP(self): """ NAME: OmegaP PURPOSE: return the pattern speed INPUT: (none) OUTPUT: pattern speed HISTORY: 2011-10-10 - Written - Bovy (IAS) """ return self._omegas galpy-1.3.0/galpy/potential_src/TwoPowerSphericalPotential.py0000644000076500000240000007500313006073440024570 0ustar bovystaff00000000000000############################################################################### # TwoPowerSphericalPotential.py: General class for potentials derived from # densities with two power-laws # # amp # rho(r)= ------------------------------------ # (r/a)^\alpha (1+r/a)^(\beta-\alpha) ############################################################################### import math as m import numpy from scipy import special, optimize from galpy.util import bovy_conversion from galpy.potential_src.Potential import Potential, kms_to_kpcGyrDecorator, \ _APY_LOADED if _APY_LOADED: from astropy import units class TwoPowerSphericalPotential(Potential): """Class that implements spherical potentials that are derived from two-power density models .. math:: \\rho(r) = \\frac{\\mathrm{amp}}{4\\,\\pi\\,a^3}\\,\\frac{1}{(r/a)^\\alpha\\,(1+r/a)^{\\beta-\\alpha}} """ def __init__(self,amp=1.,a=5.,alpha=1.5,beta=3.5,normalize=False, ro=None,vo=None): """ NAME: __init__ PURPOSE: initialize a two-power-density potential INPUT: amp - amplitude to be applied to the potential (default: 1); can be a Quantity with units of mass or Gxmass a - scale radius (can be Quantity) alpha - inner power beta - outer power normalize - if True, normalize such that vc(1.,0.)=1., or, if given as a number, such that the force is this fraction of the force necessary to make vc(1.,0.)=1. ro=, vo= distance and velocity scales for translation into internal units (default from configuration file) OUTPUT: (none) HISTORY: 2010-07-09 - Started - Bovy (NYU) """ if alpha == round(alpha) and beta == round(beta): Potential.__init__(self,amp=amp,ro=ro,vo=vo,amp_units='mass') integerSelf= TwoPowerIntegerSphericalPotential(amp=1.,a=a, alpha=int(alpha), beta=int(beta), normalize=False) self.integerSelf= integerSelf else: Potential.__init__(self,amp=amp,ro=ro,vo=vo,amp_units='mass') self.integerSelf= None if _APY_LOADED and isinstance(a,units.Quantity): a= a.to(units.kpc).value/self._ro self.a= a self._scale= self.a self.alpha= alpha self.beta= beta if normalize or \ (isinstance(normalize,(int,float)) \ and not isinstance(normalize,bool)): #pragma: no cover self.normalize(normalize) return None def _evaluate(self,R,z,phi=0.,t=0.,_forceFloatEval=False): """ NAME: _evaluate PURPOSE: evaluate the potential at R,z INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t - time OUTPUT: Phi(R,z) HISTORY: 2010-07-09 - Started - Bovy (NYU) """ if not _forceFloatEval and not self.integerSelf == None: return self.integerSelf._evaluate(R,z,phi=phi,t=t) elif self.beta == 3.: r= numpy.sqrt(R**2.+z**2.) return (1./self.a)\ *(r-self.a*(r/self.a)**(3.-self.alpha)/(3.-self.alpha)\ *special.hyp2f1(3.-self.alpha, 2.-self.alpha, 4.-self.alpha, -r/self.a))/(self.alpha-2.)/r else: r= numpy.sqrt(R**2.+z**2.) return special.gamma(self.beta-3.)\ *((r/self.a)**(3.-self.beta)/special.gamma(self.beta-1.)\ *special.hyp2f1(self.beta-3., self.beta-self.alpha, self.beta-1., -self.a/r) -special.gamma(3.-self.alpha)/special.gamma(self.beta-self.alpha))/r def _Rforce(self,R,z,phi=0.,t=0.,_forceFloatEval=False): """ NAME: _Rforce PURPOSE: evaluate the radial force for this potential INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t - time OUTPUT: the radial force HISTORY: 2010-07-09 - Written - Bovy (NYU) """ if not _forceFloatEval and not self.integerSelf == None: return self.integerSelf._Rforce(R,z,phi=phi,t=t) else: r= numpy.sqrt(R**2.+z**2.) return -R/r**self.alpha*self.a**(self.alpha-3.)/(3.-self.alpha)\ *special.hyp2f1(3.-self.alpha, self.beta-self.alpha, 4.-self.alpha, -r/self.a) def _zforce(self,R,z,phi=0.,t=0.,_forceFloatEval=False): """ NAME: _zforce PURPOSE: evaluate the vertical force for this potential INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t - time OUTPUT: the vertical force HISTORY: 2010-07-09 - Written - Bovy (NYU) """ if not _forceFloatEval and not self.integerSelf == None: return self.integerSelf._zforce(R,z,phi=phi,t=t) else: r= numpy.sqrt(R**2.+z**2.) return -z/r**self.alpha*self.a**(self.alpha-3.)/(3.-self.alpha)\ *special.hyp2f1(3.-self.alpha, self.beta-self.alpha, 4.-self.alpha, -r/self.a) def _dens(self,R,z,phi=0.,t=0.): """ NAME: _dens PURPOSE: evaluate the density force for this potential INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t - time OUTPUT: the density HISTORY: 2010-08-08 - Written - Bovy (NYU) """ r= numpy.sqrt(R**2.+z**2.) return (self.a/r)**self.alpha/(1.+r/self.a)**(self.beta-self.alpha)/4./m.pi/self.a**3. def _z2deriv(self,R,z,phi=0.,t=0.): """ NAME: _z2deriv PURPOSE: evaluate the second vertical derivative for this potential INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t- time OUTPUT: the second vertical derivative HISTORY: 2012-07-26 - Written - Bovy (IAS@MPIA) """ return self._R2deriv(numpy.fabs(z),R) #Spherical potential def _mass(self,R,z=0.,t=0.): """ NAME: _mass PURPOSE: evaluate the mass within R for this potential INPUT: R - Galactocentric cylindrical radius z - vertical height t - time OUTPUT: the mass enclosed HISTORY: 2014-04-01 - Written - Erkal (IoA) """ if z is None: r= R else: r= numpy.sqrt(R**2.+z**2.) return (r/self.a)**(3.-self.alpha)/(3.-self.alpha)*special.hyp2f1(3.-self.alpha,-self.alpha+self.beta,4.-self.alpha,-r/self.a) class TwoPowerIntegerSphericalPotential(TwoPowerSphericalPotential): """Class that implements the two-power-density spherical potentials in the case of integer powers""" def __init__(self,amp=1.,a=1.,alpha=1,beta=3,normalize=False, ro=None,vo=None): """ NAME: __init__ PURPOSE: initialize a two-power-density potential for integer powers INPUT: amp - amplitude to be applied to the potential (default: 1); can be a Quantity with units of mass or Gxmass a - scale radius (can be Quantity) alpha - inner power (default: NFW) beta - outer power (default: NFW) normalize - if True, normalize such that vc(1.,0.)=1., or, if given as a number, such that the force is this fraction of the force necessary to make vc(1.,0.)=1. ro=, vo= distance and velocity scales for translation into internal units (default from configuration file) OUTPUT: (none) HISTORY: 2010-07-09 - Started - Bovy (NYU) """ if alpha == 1 and beta == 4: Potential.__init__(self,amp=amp,ro=ro,vo=vo,amp_units='mass') HernquistSelf= HernquistPotential(amp=1.,a=a,normalize=False) self.HernquistSelf= HernquistSelf self.JaffeSelf= None self.NFWSelf= None elif alpha == 2 and beta == 4: Potential.__init__(self,amp=amp,ro=ro,vo=vo,amp_units='mass') JaffeSelf= JaffePotential(amp=1.,a=a,normalize=False) self.HernquistSelf= None self.JaffeSelf= JaffeSelf self.NFWSelf= None elif alpha == 1 and beta == 3: Potential.__init__(self,amp=amp,ro=ro,vo=vo,amp_units='mass') NFWSelf= NFWPotential(amp=1.,a=a,normalize=False) self.HernquistSelf= None self.JaffeSelf= None self.NFWSelf= NFWSelf else: Potential.__init__(self,amp=amp,ro=ro,vo=vo,amp_units='mass') self.HernquistSelf= None self.JaffeSelf= None self.NFWSelf= None if _APY_LOADED and isinstance(a,units.Quantity): a= a.to(units.kpc).value/self._ro self.alpha= alpha self.beta= beta self.a= a self._scale= self.a if normalize or \ (isinstance(normalize,(int,float)) \ and not isinstance(normalize,bool)): #pragma: no cover self.normalize(normalize) return None def _evaluate(self,R,z,phi=0.,t=0.): """ NAME: _evaluate PURPOSE: evaluate the potential at R,z INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t - time OUTPUT: Phi(R,z) HISTORY: 2010-07-09 - Started - Bovy (NYU) """ if not self.HernquistSelf == None: return self.HernquistSelf._evaluate(R,z,phi=phi,t=t) elif not self.JaffeSelf == None: return self.JaffeSelf._evaluate(R,z,phi=phi,t=t) elif not self.NFWSelf == None: return self.NFWSelf._evaluate(R,z,phi=phi,t=t) else: return TwoPowerSphericalPotential._evaluate(self,R,z, phi=phi,t=t, _forceFloatEval=True) def _Rforce(self,R,z,phi=0.,t=0.): """ NAME: _Rforce PURPOSE: evaluate the radial force for this potential INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t - time OUTPUT: the radial force HISTORY: 2010-07-09 - Written - Bovy (NYU) """ if not self.HernquistSelf == None: return self.HernquistSelf._Rforce(R,z,phi=phi,t=t) elif not self.JaffeSelf == None: return self.JaffeSelf._Rforce(R,z,phi=phi,t=t) elif not self.NFWSelf == None: return self.NFWSelf._Rforce(R,z,phi=phi,t=t) else: return TwoPowerSphericalPotential._Rforce(self,R,z, phi=phi,t=t, _forceFloatEval=True) def _zforce(self,R,z,phi=0.,t=0.): """ NAME: _zforce PURPOSE: evaluate the vertical force for this potential INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t - time OUTPUT: the vertical force HISTORY: 2010-07-09 - Written - Bovy (NYU) """ if not self.HernquistSelf == None: return self.HernquistSelf._zforce(R,z,phi=phi,t=t) elif not self.JaffeSelf == None: return self.JaffeSelf._zforce(R,z,phi=phi,t=t) elif not self.NFWSelf == None: return self.NFWSelf._zforce(R,z,phi=phi,t=t) else: return TwoPowerSphericalPotential._zforce(self,R,z, phi=phi,t=t, _forceFloatEval=True) class HernquistPotential(TwoPowerIntegerSphericalPotential): """Class that implements the Hernquist potential .. math:: \\rho(r) = \\frac{\\mathrm{amp}}{4\\,\\pi\\,a^3}\\,\\frac{1}{(r/a)\\,(1+r/a)^{3}} """ def __init__(self,amp=1.,a=1.,normalize=False, ro=None,vo=None): """ NAME: __init__ PURPOSE: Initialize a Hernquist potential INPUT: amp - amplitude to be applied to the potential (default: 1); can be a Quantity with units of mass or Gxmass a - scale radius (can be Quantity) normalize - if True, normalize such that vc(1.,0.)=1., or, if given as a number, such that the force is this fraction of the force necessary to make vc(1.,0.)=1. ro=, vo= distance and velocity scales for translation into internal units (default from configuration file) OUTPUT: (none) HISTORY: 2010-07-09 - Written - Bovy (NYU) """ Potential.__init__(self,amp=amp,ro=ro,vo=vo,amp_units='mass') if _APY_LOADED and isinstance(a,units.Quantity): a= a.to(units.kpc).value/self._ro self.a= a self._scale= self.a self.alpha= 1 self.beta= 4 if normalize or \ (isinstance(normalize,(int,float)) \ and not isinstance(normalize,bool)): self.normalize(normalize) self.hasC= True self.hasC_dxdv= True return None def _evaluate(self,R,z,phi=0.,t=0.): """ NAME: _evaluate PURPOSE: evaluate the potential at R,z INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t - time OUTPUT: Phi(R,z) HISTORY: 2010-07-09 - Started - Bovy (NYU) """ return -1./(1.+numpy.sqrt(R**2.+z**2.)/self.a)/2./self.a def _Rforce(self,R,z,phi=0.,t=0.): """ NAME: _Rforce PURPOSE: evaluate the radial force for this potential INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t- time OUTPUT: the radial force HISTORY: 2010-07-09 - Written - Bovy (NYU) """ sqrtRz= numpy.sqrt(R**2.+z**2.) return -R/self.a/sqrtRz/(1.+sqrtRz/self.a)**2./2./self.a def _zforce(self,R,z,phi=0.,t=0.): """ NAME: _zforce PURPOSE: evaluate the vertical force for this potential INPUT: R - Galactocentric cylindrical radius z - vertical height t - time OUTPUT: the vertical force HISTORY: 2010-07-09 - Written - Bovy (NYU) """ sqrtRz= numpy.sqrt(R**2.+z**2.) return -z/self.a/sqrtRz/(1.+sqrtRz/self.a)**2./2./self.a def _R2deriv(self,R,z,phi=0.,t=0.): """ NAME: _R2deriv PURPOSE: evaluate the second radial derivative for this potential INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t- time OUTPUT: the second radial derivative HISTORY: 2011-10-09 - Written - Bovy (IAS) """ sqrtRz= numpy.sqrt(R**2.+z**2.) return (self.a*z**2.+(z**2.-2.*R**2.)*sqrtRz)/sqrtRz**3.\ /(self.a+sqrtRz)**3./2. def _Rzderiv(self,R,z,phi=0.,t=0.): """ NAME: _Rzderiv PURPOSE: evaluate the mixed R,z derivative for this potential INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t- time OUTPUT: d2phi/dR/dz HISTORY: 2013-08-28 - Written - Bovy (IAS) """ sqrtRz= numpy.sqrt(R**2.+z**2.) return -R*z*(self.a+3.*sqrtRz)*(sqrtRz*(self.a+sqrtRz))**-3./2. def _mass(self,R,z=0.,t=0.): """ NAME: _mass PURPOSE: calculate the mass out to a given radius INPUT: R - radius at which to return the enclosed mass z - (don't specify this) vertical height OUTPUT: mass in natural units HISTORY: 2014-01-29 - Written - Bovy (IAS) """ if z is None: r= R else: r= numpy.sqrt(R**2.+z**2.) return (r/self.a)**2./2./(1.+r/self.a)**2. class JaffePotential(TwoPowerIntegerSphericalPotential): """Class that implements the Jaffe potential .. math:: \\rho(r) = \\frac{\\mathrm{amp}}{4\\,\\pi\\,a^3}\\,\\frac{1}{(r/a)^2\\,(1+r/a)^{2}} """ def __init__(self,amp=1.,a=1.,normalize=False, ro=None,vo=None): """ NAME: __init__ PURPOSE: Initialize a Jaffe potential INPUT: amp - amplitude to be applied to the potential (default: 1); can be a Quantity with units of mass or Gxmass a - scale radius (can be Quantity) normalize - if True, normalize such that vc(1.,0.)=1., or, if given as a number, such that the force is this fraction of the force necessary to make vc(1.,0.)=1. ro=, vo= distance and velocity scales for translation into internal units (default from configuration file) OUTPUT: (none) HISTORY: 2010-07-09 - Written - Bovy (NYU) """ Potential.__init__(self,amp=amp,ro=ro,vo=vo,amp_units='mass') if _APY_LOADED and isinstance(a,units.Quantity): a= a.to(units.kpc).value/self._ro self.a= a self._scale= self.a self.alpha= 2 self.beta= 4 if normalize or \ (isinstance(normalize,(int,float)) \ and not isinstance(normalize,bool)): #pragma: no cover self.normalize(normalize) self.hasC= True self.hasC_dxdv= True return None def _evaluate(self,R,z,phi=0.,t=0.): """ NAME: _evaluate PURPOSE: evaluate the potential at R,z INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t - time OUTPUT: Phi(R,z) HISTORY: 2010-07-09 - Started - Bovy (NYU) """ return -numpy.log(1.+self.a/numpy.sqrt(R**2.+z**2.))/self.a def _Rforce(self,R,z,phi=0.,t=0.): """ NAME: _Rforce PURPOSE: evaluate the radial force for this potential INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t - time OUTPUT: the radial force HISTORY: 2010-07-09 - Written - Bovy (NYU) """ sqrtRz= numpy.sqrt(R**2.+z**2.) return -R/sqrtRz**3./(1.+self.a/sqrtRz) def _zforce(self,R,z,phi=0.,t=0.): """ NAME: _zforce PURPOSE: evaluate the vertical force for this potential INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t - time OUTPUT: the vertical force HISTORY: 2010-07-09 - Written - Bovy (NYU) """ sqrtRz= numpy.sqrt(R**2.+z**2.) return -z/sqrtRz**3./(1.+self.a/sqrtRz) def _R2deriv(self,R,z,phi=0.,t=0.): """ NAME: _R2deriv PURPOSE: evaluate the second radial derivative for this potential INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t - time OUTPUT: the second radial derivative HISTORY: 2011-10-09 - Written - Bovy (IAS) """ sqrtRz= numpy.sqrt(R**2.+z**2.) return (self.a*(z**2.-R**2.)+(z**2.-2.*R**2.)*sqrtRz)\ /sqrtRz**4./(self.a+sqrtRz)**2. def _Rzderiv(self,R,z,phi=0.,t=0.): """ NAME: _Rzderiv PURPOSE: evaluate the mixed R,z derivative for this potential INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t - time OUTPUT: d2phi/dR/dz HISTORY: 2013-08-28 - Written - Bovy (IAS) """ sqrtRz= numpy.sqrt(R**2.+z**2.) return -R*z*(2.*self.a+3.*sqrtRz)*sqrtRz**-4.\ *(self.a+sqrtRz)**-2. def _mass(self,R,z=0.,t=0.): """ NAME: _mass PURPOSE: calculate the mass out to a given radius INPUT: R - radius at which to return the enclosed mass z - (don't specify this) vertical height OUTPUT: mass in natural units HISTORY: 2014-01-29 - Written - Bovy (IAS) """ if z is None: r= R else: r= numpy.sqrt(R**2.+z**2.) return r/self.a/(1.+r/self.a) class NFWPotential(TwoPowerIntegerSphericalPotential): """Class that implements the NFW potential .. math:: \\rho(r) = \\frac{\\mathrm{amp}}{4\\,\\pi\\,a^3}\\,\\frac{1}{(r/a)\\,(1+r/a)^{2}} """ def __init__(self,amp=1.,a=1.,normalize=False, conc=None,mvir=None, vo=None,ro=None, H=70.,Om=0.3,overdens=200.,wrtcrit=False): """ NAME: __init__ PURPOSE: Initialize a NFW potential INPUT: amp - amplitude to be applied to the potential (default: 1); can be a Quantity with units of mass or Gxmass a - scale radius (can be Quantity) normalize - if True, normalize such that vc(1.,0.)=1., or, if given as a number, such that the force is this fraction of the force necessary to make vc(1.,0.)=1. Alternatively, NFW potentials can be initialized using conc= concentration mvir= virial mass in 10^12 Msolar in which case you also need to supply the following keywords H= (default: 70) Hubble constant in km/s/Mpc Om= (default: 0.3) Omega matter overdens= (200) overdensity which defines the virial radius wrtcrit= (False) if True, the overdensity is wrt the critical density rather than the mean matter density ro=, vo= distance and velocity scales for translation into internal units (default from configuration file) OUTPUT: (none) HISTORY: 2010-07-09 - Written - Bovy (NYU) 2014-04-03 - Initialization w/ concentration and mass - Bovy (IAS) """ Potential.__init__(self,amp=amp,ro=ro,vo=vo,amp_units='mass') if _APY_LOADED and isinstance(a,units.Quantity): a= a.to(units.kpc).value/self._ro if conc is None: self.a= a self.alpha= 1 self.beta= 3 if normalize or \ (isinstance(normalize,(int,float)) \ and not isinstance(normalize,bool)): self.normalize(normalize) else: if wrtcrit: od= overdens/bovy_conversion.dens_in_criticaldens(self._vo, self._ro,H=H) else: od= overdens/bovy_conversion.dens_in_meanmatterdens(self._vo, self._ro, H=H,Om=Om) mvirNatural= mvir*100./bovy_conversion.mass_in_1010msol(self._vo, self._ro) rvir= (3.*mvirNatural/od/4./numpy.pi)**(1./3.) self.a= rvir/conc self._amp= mvirNatural/(numpy.log(1.+conc)-conc/(1.+conc)) self._scale= self.a self.hasC= True self.hasC_dxdv= True self._nemo_accname= 'NFW' return None def _evaluate(self,R,z,phi=0.,t=0.): """ NAME: _evaluate PURPOSE: evaluate the potential at R,z INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t - time OUTPUT: Phi(R,z) HISTORY: 2010-07-09 - Started - Bovy (NYU) """ r= numpy.sqrt(R**2.+z**2.) return -numpy.log(1.+r/self.a)/r def _Rforce(self,R,z,phi=0.,t=0.): """ NAME: _Rforce PURPOSE: evaluate the radial force for this potential INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t - time OUTPUT: the radial force HISTORY: 2010-07-09 - Written - Bovy (NYU) """ Rz= R**2.+z**2. sqrtRz= numpy.sqrt(Rz) return R*(1./Rz/(self.a+sqrtRz)-numpy.log(1.+sqrtRz/self.a)/sqrtRz/Rz) def _zforce(self,R,z,phi=0.,t=0.): """ NAME: _zforce PURPOSE: evaluate the vertical force for this potential INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t - time OUTPUT: the vertical force HISTORY: 2010-07-09 - Written - Bovy (NYU) """ Rz= R**2.+z**2. sqrtRz= numpy.sqrt(Rz) return z*(1./Rz/(self.a+sqrtRz)-numpy.log(1.+sqrtRz/self.a)/sqrtRz/Rz) def _R2deriv(self,R,z,phi=0.,t=0.): """ NAME: _R2deriv PURPOSE: evaluate the second radial derivative for this potential INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t - time OUTPUT: the second radial derivative HISTORY: 2011-10-09 - Written - Bovy (IAS) """ Rz= R**2.+z**2. sqrtRz= numpy.sqrt(Rz) return (3.*R**4.+2.*R**2.*(z**2.+self.a*sqrtRz)\ -z**2.*(z**2.+self.a*sqrtRz)\ -(2.*R**2.-z**2.)*(self.a**2.+R**2.+z**2.+2.*self.a*sqrtRz)\ *numpy.log(1.+sqrtRz/self.a))\ /Rz**2.5/(self.a+sqrtRz)**2. def _Rzderiv(self,R,z,phi=0.,t=0.): """ NAME: _Rzderiv PURPOSE: evaluate the mixed R,z derivative for this potential INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t - time OUTPUT: d2phi/dR/dz HISTORY: 2013-08-28 - Written - Bovy (IAS) """ Rz= R**2.+z**2. sqrtRz= numpy.sqrt(Rz) return -R*z*(-4.*Rz-3.*self.a*sqrtRz+3.*(self.a**2.+Rz+2.*self.a*sqrtRz)*numpy.log(1.+sqrtRz/self.a))*Rz**-2.5*(self.a+sqrtRz)**-2. def _mass(self,R,z=0.,t=0.): """ NAME: _mass PURPOSE: calculate the mass out to a given radius INPUT: R - radius at which to return the enclosed mass z - (don't specify this) vertical height OUTPUT: mass in natural units HISTORY: 2014-01-29 - Written - Bovy (IAS) """ if z is None: r= R else: r= numpy.sqrt(R**2.+z**2.) return numpy.log(1+r/self.a)-r/self.a/(1.+r/self.a) @bovy_conversion.physical_conversion('position',pop=False) def rvir(self,H=70.,Om=0.3,overdens=200.,wrtcrit=False,ro=None,vo=None, use_physical=False): # use_physical necessary bc of pop=False, does nothing inside """ NAME: rvir PURPOSE: calculate the virial radius for this density distribution INPUT: H= (default: 70) Hubble constant in km/s/Mpc Om= (default: 0.3) Omega matter overdens= (200) overdensity which defines the virial radius wrtcrit= (False) if True, the overdensity is wrt the critical density rather than the mean matter density ro= distance scale in kpc or as Quantity (default: object-wide, which if not set is 8 kpc)) vo= velocity scale in km/s or as Quantity (default: object-wide, which if not set is 220 km/s)) OUTPUT: virial radius HISTORY: 2014-01-29 - Written - Bovy (IAS) """ if ro is None: ro= self._ro if vo is None: vo= self._vo if wrtcrit: od= overdens/bovy_conversion.dens_in_criticaldens(vo,ro,H=H) else: od= overdens/bovy_conversion.dens_in_meanmatterdens(vo,ro, H=H,Om=Om) dc= 12.*self.dens(self.a,0.,use_physical=False)/od x= optimize.brentq(lambda y: (numpy.log(1.+y)-y/(1.+y))/y**3.-1./dc, 0.01,100.) return x*self.a @kms_to_kpcGyrDecorator def _nemo_accpars(self,vo,ro): """ NAME: _nemo_accpars PURPOSE: return the accpars potential parameters for use of this potential with NEMO INPUT: vo - velocity unit in km/s ro - length unit in kpc OUTPUT: accpars string HISTORY: 2014-12-18 - Written - Bovy (IAS) """ ampl= self._amp*vo**2.*ro vmax= numpy.sqrt(ampl/self.a/ro*0.2162165954) #Take that factor directly from gyrfalcon return "0,%s,%s" % (self.a*ro,vmax) galpy-1.3.0/galpy/potential_src/TwoPowerTriaxialPotential.py0000644000076500000240000007742013231525271024444 0ustar bovystaff00000000000000############################################################################### # TwoPowerTriaxialPotential.py: General class for triaxial potentials # derived from densities with two power-laws # # amp/[4pia^3] # rho(r)= ------------------------------------ # (m/a)^\alpha (1+m/a)^(\beta-\alpha) # # with # # m^2 = x^2 + y^2/b^2 + z^2/c^2 ############################################################################### import hashlib import numpy from scipy import integrate, special from galpy.util import bovy_conversion, bovy_coords from galpy.util import _rotate_to_arbitrary_vector from galpy.potential_src.Potential import Potential, _APY_LOADED if _APY_LOADED: from astropy import units class TwoPowerTriaxialPotential(Potential): """Class that implements triaxial potentials that are derived from two-power density models .. math:: \\rho(x,y,z) = \\frac{\\mathrm{amp}}{4\\,\\pi\\,a^3}\\,\\frac{1}{(m/a)^\\alpha\\,(1+m/a)^{\\beta-\\alpha}} with .. math:: m^2 = x'^2 + \\frac{y'^2}{b^2}+\\frac{z'^2}{c^2} and :math:`(x',y',z')` is a rotated frame wrt :math:`(x,y,z)` specified by parameters ``zvec`` and ``pa`` which specify (a) ``zvec``: the location of the :math:`z'` axis in the :math:`(x,y,z)` frame and (b) ``pa``: the position angle of the :math:`x'` axis wrt the :math:`\\tilde{x}` axis, that is, the :math:`x` axis after rotating to ``zvec``. """ def __init__(self,amp=1.,a=5.,alpha=1.5,beta=3.5,b=1.,c=1., zvec=None,pa=None,glorder=50, normalize=False,ro=None,vo=None): """ NAME: __init__ PURPOSE: initialize a triaxial two-power-density potential INPUT: amp - amplitude to be applied to the potential (default: 1); can be a Quantity with units of mass or Gxmass a - scale radius (can be Quantity) alpha - inner power (0 <= alpha < 3) beta - outer power ( beta > 2) b - y-to-x axis ratio of the density c - z-to-x axis ratio of the density zvec= (None) If set, a unit vector that corresponds to the z axis pa= (None) If set, the position angle of the x axis (rad or Quantity) glorder= (50) if set, compute the relevant force and potential integrals with Gaussian quadrature of this order normalize - if True, normalize such that vc(1.,0.)=1., or, if given as a number, such that the force is this fraction of the force necessary to make vc(1.,0.)=1. ro=, vo= distance and velocity scales for translation into internal units (default from configuration file) OUTPUT: (none) HISTORY: 2016-05-30 - Started - Bovy (UofT) """ if alpha == 1 and beta == 4: Potential.__init__(self,amp=amp,ro=ro,vo=vo,amp_units='mass') HernquistSelf= TriaxialHernquistPotential(amp=1.,a=a,b=b,c=c, zvec=zvec,pa=pa, glorder=glorder, normalize=False) self.HernquistSelf= HernquistSelf self.JaffeSelf= None self.NFWSelf= None elif alpha == 2 and beta == 4: Potential.__init__(self,amp=amp,ro=ro,vo=vo,amp_units='mass') JaffeSelf= TriaxialJaffePotential(amp=1.,a=a,b=b,c=c, zvec=zvec,pa=pa,glorder=glorder, normalize=False) self.HernquistSelf= None self.JaffeSelf= JaffeSelf self.NFWSelf= None elif alpha == 1 and beta == 3: Potential.__init__(self,amp=amp,ro=ro,vo=vo,amp_units='mass') NFWSelf= TriaxialNFWPotential(amp=1.,a=a,b=b,c=c,pa=pa, glorder=glorder, zvec=zvec,normalize=False) self.HernquistSelf= None self.JaffeSelf= None self.NFWSelf= NFWSelf else: Potential.__init__(self,amp=amp,ro=ro,vo=vo,amp_units='mass') self.HernquistSelf= None self.JaffeSelf= None self.NFWSelf= None if _APY_LOADED and isinstance(a,units.Quantity): a= a.to(units.kpc).value/self._ro self.a= a self._scale= self.a if beta <= 2. or alpha < 0. or alpha >= 3.: raise IOError('TwoPowerTriaxialPotential requires 0 <= alpha < 3 and beta > 2') self.alpha= alpha self.beta= beta self._b= b self._c= c self._b2= self._b**2. self._c2= self._c**2. self._force_hash= None self._setup_zvec_pa(zvec,pa) self._setup_gl(glorder) if normalize or \ (isinstance(normalize,(int,float)) \ and not isinstance(normalize,bool)): #pragma: no cover self.normalize(normalize) if not self._aligned or numpy.fabs(self._b-1.) > 10.**-10.: self.isNonAxi= True return None def _setup_zvec_pa(self,zvec,pa): if not pa is None: if _APY_LOADED and isinstance(pa,units.Quantity): pa= pa.to(units.rad).value if zvec is None and (pa is None or numpy.fabs(pa) < 10.**-10.): self._aligned= True else: self._aligned= False if not pa is None: pa_rot= numpy.array([[numpy.cos(pa),numpy.sin(pa),0.], [-numpy.sin(pa),numpy.cos(pa),0.], [0.,0.,1.]]) else: pa_rot= numpy.eye(3) if not zvec is None: if not isinstance(zvec,numpy.ndarray): zvec= numpy.array(zvec) zvec/= numpy.sqrt(numpy.sum(zvec**2.)) zvec_rot= _rotate_to_arbitrary_vector(\ numpy.array([[0.,0.,1.]]),zvec,inv=True)[0] else: zvec_rot= numpy.eye(3) self._rot= numpy.dot(pa_rot,zvec_rot) return None def _setup_gl(self,glorder): self._glorder= glorder if self._glorder is None: self._glx, self._glw= None, None else: self._glx, self._glw=\ numpy.polynomial.legendre.leggauss(self._glorder) # Interval change self._glx= 0.5*self._glx+0.5 self._glw*= 0.5 return None def _evaluate(self,R,z,phi=0.,t=0.): """ NAME: _evaluate PURPOSE: evaluate the potential at R,z INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t - time OUTPUT: Phi(R,z) HISTORY: 2016-05-30 - Started - Bovy (UofT) """ if not self.isNonAxi: phi= 0. x,y,z= bovy_coords.cyl_to_rect(R,phi,z) if self._aligned: return self._evaluate_xyz(x,y,z) else: xyzp= numpy.dot(self._rot,numpy.array([x,y,z])) return self._evaluate_xyz(xyzp[0],xyzp[1],xyzp[2]) def _evaluate_xyz(self,x,y,z): """Evaluation of the potential as a function of (x,y,z) in the aligned coordinate frame""" if not self.HernquistSelf == None: return self.HernquistSelf._evaluate_xyz(x,y,z) elif not self.JaffeSelf == None: return self.JaffeSelf._evaluate_xyz(x,y,z) elif not self.NFWSelf == None: return self.NFWSelf._evaluate_xyz(x,y,z) else: if self.alpha == 2.: raise NotImplementedError('alpha=2 potential evaluation case not implemented') else: psi_inf=\ special.gamma(self.beta-2.)*special.gamma(3.-self.alpha)\ /special.gamma(self.beta-self.alpha) psi= lambda m:\ psi_inf-(m/self.a)**(2.-self.alpha)\ /(2.-self.alpha)\ *special.hyp2f1(2.-self.alpha, self.beta-self.alpha, 3.-self.alpha,-m/self.a) return -self._b*self._c/self.a\ *_potInt(x,y,z,psi,self._b2,self._c2,glx=self._glx,glw=self._glw) def _Rforce(self,R,z,phi=0.,t=0.): """ NAME: _Rforce PURPOSE: evaluate the radial force for this potential INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t - time OUTPUT: the radial force HISTORY: 2016-06-09 - Written - Bovy (UofT) """ if not self.isNonAxi: phi= 0. x,y,z= bovy_coords.cyl_to_rect(R,phi,z) # Compute all rectangular forces new_hash= hashlib.md5(numpy.array([x,y,z])).hexdigest() if new_hash == self._force_hash: Fx= self._cached_Fx Fy= self._cached_Fy Fz= self._cached_Fz else: if self._aligned: xp, yp, zp= x, y, z else: xyzp= numpy.dot(self._rot,numpy.array([x,y,z])) xp, yp, zp= xyzp[0], xyzp[1], xyzp[2] Fx= self._xforce_xyz(xp,yp,zp) Fy= self._yforce_xyz(xp,yp,zp) Fz= self._zforce_xyz(xp,yp,zp) self._force_hash= new_hash self._cached_Fx= Fx self._cached_Fy= Fy self._cached_Fz= Fz if not self._aligned: Fxyz= numpy.dot(self._rot.T,numpy.array([Fx,Fy,Fz])) Fx, Fy= Fxyz[0], Fxyz[1] return numpy.cos(phi)*Fx+numpy.sin(phi)*Fy def _phiforce(self,R,z,phi=0.,t=0.): """ NAME: _phiforce PURPOSE: evaluate the azimuthal force for this potential INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t - time OUTPUT: the azimuthal force HISTORY: 2016-06-09 - Written - Bovy (UofT) """ if not self.isNonAxi: phi= 0. x,y,z= bovy_coords.cyl_to_rect(R,phi,z) # Compute all rectangular forces new_hash= hashlib.md5(numpy.array([x,y,z])).hexdigest() if new_hash == self._force_hash: Fx= self._cached_Fx Fy= self._cached_Fy Fz= self._cached_Fz else: if self._aligned: xp, yp, zp= x, y, z else: xyzp= numpy.dot(self._rot,numpy.array([x,y,z])) xp, yp, zp= xyzp[0], xyzp[1], xyzp[2] Fx= self._xforce_xyz(xp,yp,zp) Fy= self._yforce_xyz(xp,yp,zp) Fz= self._zforce_xyz(xp,yp,zp) self._force_hash= new_hash self._cached_Fx= Fx self._cached_Fy= Fy self._cached_Fz= Fz if not self._aligned: Fxyz= numpy.dot(self._rot.T,numpy.array([Fx,Fy,Fz])) Fx, Fy= Fxyz[0], Fxyz[1] return R*(-numpy.sin(phi)*Fx+numpy.cos(phi)*Fy) def _zforce(self,R,z,phi=0.,t=0.): """ NAME: _zforce PURPOSE: evaluate the vertical force for this potential INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t - time OUTPUT: the vertical force HISTORY: 2016-06-09 - Written - Bovy (UofT) """ if not self.isNonAxi: phi= 0. x,y,z= bovy_coords.cyl_to_rect(R,phi,z) # Compute all rectangular forces new_hash= hashlib.md5(numpy.array([x,y,z])).hexdigest() if new_hash == self._force_hash: Fx= self._cached_Fx Fy= self._cached_Fy Fz= self._cached_Fz else: if self._aligned: xp, yp, zp= x, y, z else: xyzp= numpy.dot(self._rot,numpy.array([x,y,z])) xp, yp, zp= xyzp[0], xyzp[1], xyzp[2] Fx= self._xforce_xyz(xp,yp,zp) Fy= self._yforce_xyz(xp,yp,zp) Fz= self._zforce_xyz(xp,yp,zp) self._force_hash= new_hash self._cached_Fx= Fx self._cached_Fy= Fy self._cached_Fz= Fz if not self._aligned: Fxyz= numpy.dot(self._rot.T,numpy.array([Fx,Fy,Fz])) Fz= Fxyz[2] return Fz def _xforce_xyz(self,x,y,z): """Evaluation of the x force as a function of (x,y,z) in the aligned coordinate frame""" return -self._b*self._c/self.a**3.\ *_forceInt(x,y,z, lambda m: (self.a/m)**self.alpha/(1.+m/self.a)**(self.beta-self.alpha), self._b2,self._c2,0,glx=self._glx,glw=self._glw) def _yforce_xyz(self,x,y,z): """Evaluation of the y force as a function of (x,y,z) in the aligned coordinate frame""" return -self._b*self._c/self.a**3.\ *_forceInt(x,y,z, lambda m: (self.a/m)**self.alpha/(1.+m/self.a)**(self.beta-self.alpha), self._b2,self._c2,1,glx=self._glx,glw=self._glw) def _zforce_xyz(self,x,y,z): """Evaluation of the z force as a function of (x,y,z) in the aligned coordinate frame""" return -self._b*self._c/self.a**3.\ *_forceInt(x,y,z, lambda m: (self.a/m)**self.alpha/(1.+m/self.a)**(self.beta-self.alpha), self._b2,self._c2,2,glx=self._glx,glw=self._glw) def _R2deriv(self,R,z,phi=0.,t=0.): """ NAME: _R2deriv PURPOSE: evaluate the second radial derivative for this potential INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t - time OUTPUT: the second radial derivative HISTORY: 2016-06-15 - Written - Bovy (UofT) """ if not self.isNonAxi: phi= 0. x,y,z= bovy_coords.cyl_to_rect(R,phi,z) if not self._aligned: raise NotImplementedError("2nd potential derivatives of TwoPowerTriaxialPotential not implemented for rotated coordinated frames (non-trivial zvec and pa)") phixx= self._2ndderiv_xyz(x,y,z,0,0) phixy= self._2ndderiv_xyz(x,y,z,0,1) phiyy= self._2ndderiv_xyz(x,y,z,1,1) return numpy.cos(phi)**2.*phixx+numpy.sin(phi)**2.*phiyy\ +2.*numpy.cos(phi)*numpy.sin(phi)*phixy def _Rzderiv(self,R,z,phi=0.,t=0.): """ NAME: _Rzderiv PURPOSE: evaluate the mixed radial, vertical derivative for this potential INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t - time OUTPUT: the mixed radial, vertical derivative HISTORY: 2016-06-15 - Written - Bovy (UofT) """ if not self.isNonAxi: phi= 0. x,y,z= bovy_coords.cyl_to_rect(R,phi,z) if not self._aligned: raise NotImplementedError("2nd potential derivatives of TwoPowerTriaxialPotential not implemented for rotated coordinated frames (non-trivial zvec and pa)") phixz= self._2ndderiv_xyz(x,y,z,0,2) phiyz= self._2ndderiv_xyz(x,y,z,1,2) return numpy.cos(phi)*phixz+numpy.sin(phi)*phiyz def _z2deriv(self,R,z,phi=0.,t=0.): """ NAME: _z2deriv PURPOSE: evaluate the second vertical derivative for this potential INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t - time OUTPUT: the second vertical derivative HISTORY: 2016-06-15 - Written - Bovy (UofT) """ if not self.isNonAxi: phi= 0. x,y,z= bovy_coords.cyl_to_rect(R,phi,z) if not self._aligned: raise NotImplementedError("2nd potential derivatives of TwoPowerTriaxialPotential not implemented for rotated coordinated frames (non-trivial zvec and pa)") return self._2ndderiv_xyz(x,y,z,2,2) def _phi2deriv(self,R,z,phi=0.,t=0.): """ NAME: _phi2deriv PURPOSE: evaluate the second azimuthal derivative for this potential INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t - time OUTPUT: the second azimuthal derivative HISTORY: 2016-06-15 - Written - Bovy (UofT) """ if not self.isNonAxi: phi= 0. x,y,z= bovy_coords.cyl_to_rect(R,phi,z) if not self._aligned: raise NotImplementedError("2nd potential derivatives of TwoPowerTriaxialPotential not implemented for rotated coordinated frames (non-trivial zvec and pa)") Fx= self._xforce_xyz(x,y,z) Fy= self._yforce_xyz(x,y,z) phixx= self._2ndderiv_xyz(x,y,z,0,0) phixy= self._2ndderiv_xyz(x,y,z,0,1) phiyy= self._2ndderiv_xyz(x,y,z,1,1) return R**2.*(numpy.sin(phi)**2.*phixx+numpy.cos(phi)**2.*phiyy\ -2.*numpy.cos(phi)*numpy.sin(phi)*phixy)\ +R*(numpy.cos(phi)*Fx+numpy.sin(phi)*Fy) def _Rphideriv(self,R,z,phi=0.,t=0.): """ NAME: _Rphideriv PURPOSE: evaluate the mixed radial, azimuthal derivative for this potential INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t - time OUTPUT: the mixed radial, azimuthal derivative HISTORY: 2016-06-15 - Written - Bovy (UofT) """ if not self.isNonAxi: phi= 0. x,y,z= bovy_coords.cyl_to_rect(R,phi,z) if not self._aligned: raise NotImplementedError("2nd potential derivatives of TwoPowerTriaxialPotential not implemented for rotated coordinated frames (non-trivial zvec and pa)") Fx= self._xforce_xyz(x,y,z) Fy= self._yforce_xyz(x,y,z) phixx= self._2ndderiv_xyz(x,y,z,0,0) phixy= self._2ndderiv_xyz(x,y,z,0,1) phiyy= self._2ndderiv_xyz(x,y,z,1,1) return R*numpy.cos(phi)*numpy.sin(phi)*\ (phiyy-phixx)+R*numpy.cos(2.*phi)*phixy\ +numpy.sin(phi)*Fx-numpy.cos(phi)*Fy def _2ndderiv_xyz(self,x,y,z,i,j): """General 2nd derivative of the potential as a function of (x,y,z) in the aligned coordinate frame""" return self._b*self._c/self.a**3.\ *_2ndDerivInt(x,y,z, lambda m: (self.a/m)**self.alpha/(1.+m/self.a)**(self.beta-self.alpha), lambda m: -(self.a/m)**self.alpha/(1.+m/self.a)**(self.beta-self.alpha)/self.a*(self.alpha*(self.a/m)+(self.beta-self.alpha)/(1.+m/self.a)), self._b2,self._c2,i,j,glx=self._glx,glw=self._glw) def _dens(self,R,z,phi=0.,t=0.): """ NAME: _dens PURPOSE: evaluate the density for this potential INPUT: R - Galactocentric cylindrical radius z - vertical height phi - azimuth t - time OUTPUT: the density HISTORY: 2016-05-31 - Written - Bovy (UofT) """ x,y,z= bovy_coords.cyl_to_rect(R,phi,z) if self._aligned: xp, yp, zp= x, y, z else: xyzp= numpy.dot(self._rot,numpy.array([x,y,z])) xp, yp, zp= xyzp[0], xyzp[1], xyzp[2] m= numpy.sqrt(xp**2.+yp**2./self._b2+zp**2./self._c2) return (self.a/m)**self.alpha/(1.+m/self.a)**(self.beta-self.alpha)/4./numpy.pi/self.a**3. def OmegaP(self): """ NAME: OmegaP PURPOSE: return the pattern speed INPUT: (none) OUTPUT: pattern speed HISTORY: 2016-05-31 - Written - Bovy (UofT) """ return 0. class TriaxialHernquistPotential(TwoPowerTriaxialPotential): """Class that implements the triaxial Hernquist potential .. math:: \\rho(x,y,z) = \\frac{\\mathrm{amp}}{4\\,\\pi\\,a^3}\\,\\frac{1}{(m/a)\\,(1+m/a)^{3}} with .. math:: m^2 = x'^2 + \\frac{y'^2}{b^2}+\\frac{z'^2}{c^2} and :math:`(x',y',z')` is a rotated frame wrt :math:`(x,y,z)` specified by parameters ``zvec`` and ``pa`` which specify (a) ``zvec``: the location of the :math:`z'` axis in the :math:`(x,y,z)` frame and (b) ``pa``: the position angle of the :math:`x'` axis wrt the :math:`\\tilde{x}` axis, that is, the :math:`x` axis after rotating to ``zvec``. """ def __init__(self,amp=1.,a=2.,normalize=False,b=1.,c=1.,zvec=None,pa=None, glorder=50,ro=None,vo=None): """ NAME: __init__ PURPOSE: Initialize a triaxial Hernquist potential INPUT: amp - amplitude to be applied to the potential (default: 1); can be a Quantity with units of mass or Gxmass a - scale radius (can be Quantity) b - y-to-x axis ratio of the density c - z-to-x axis ratio of the density zvec= (None) If set, a unit vector that corresponds to the z axis pa= (None) If set, the position angle of the x axis glorder= (50) if set, compute the relevant force and potential integrals with Gaussian quadrature of this order normalize - if True, normalize such that vc(1.,0.)=1., or, if given as a number, such that the force is this fraction of the force necessary to make vc(1.,0.)=1. ro=, vo= distance and velocity scales for translation into internal units (default from configuration file) OUTPUT: (none) HISTORY: 2010-07-09 - Written - Bovy (UofT) """ Potential.__init__(self,amp=amp,ro=ro,vo=vo,amp_units='mass') if _APY_LOADED and isinstance(a,units.Quantity): a= a.to(units.kpc).value/self._ro self.a= a self._scale= self.a self.alpha= 1 self.beta= 4 self._b= b self._b2= self._b**2 self._c= c self._c2= self._c**2 self._setup_gl(glorder) self._setup_zvec_pa(zvec,pa) self._force_hash= None if normalize or \ (isinstance(normalize,(int,float)) \ and not isinstance(normalize,bool)): self.normalize(normalize) self.hasC= not self._glorder is None self.hasC_dxdv= False if not self._aligned or numpy.fabs(self._b-1.) > 10.**-10.: self.isNonAxi= True return None def _evaluate_xyz(self,x,y,z): """Evaluation of the potential as a function of (x,y,z) in the aligned coordinate frame""" psi= lambda m: 1./(1.+m/self.a)**2./2. return -self._b*self._c/self.a\ *_potInt(x,y,z,psi,self._b2,self._c2,glx=self._glx,glw=self._glw) class TriaxialJaffePotential(TwoPowerTriaxialPotential): """Class that implements the Jaffe potential .. math:: \\rho(x,y,z) = \\frac{\\mathrm{amp}}{4\\,\\pi\\,a^3}\\,\\frac{1}{(m/a)^2\\,(1+m/a)^{2}} with .. math:: m^2 = x'^2 + \\frac{y'^2}{b^2}+\\frac{z'^2}{c^2} and :math:`(x',y',z')` is a rotated frame wrt :math:`(x,y,z)` specified by parameters ``zvec`` and ``pa`` which specify (a) ``zvec``: the location of the :math:`z'` axis in the :math:`(x,y,z)` frame and (b) ``pa``: the position angle of the :math:`x'` axis wrt the :math:`\\tilde{x}` axis, that is, the :math:`x` axis after rotating to ``zvec``. """ def __init__(self,amp=1.,a=2.,b=1.,c=1.,zvec=None,pa=None,normalize=False, glorder=50,ro=None,vo=None): """ NAME: __init__ PURPOSE: Initialize a Jaffe potential INPUT: amp - amplitude to be applied to the potential (default: 1); can be a Quantity with units of mass or Gxmass a - scale radius (can be Quantity) b - y-to-x axis ratio of the density c - z-to-x axis ratio of the density zvec= (None) If set, a unit vector that corresponds to the z axis pa= (None) If set, the position angle of the x axis glorder= (50) if set, compute the relevant force and potential integrals with Gaussian quadrature of this order normalize - if True, normalize such that vc(1.,0.)=1., or, if given as a number, such that the force is this fraction of the force necessary to make vc(1.,0.)=1. ro=, vo= distance and velocity scales for translation into internal units (default from configuration file) OUTPUT: (none) HISTORY: 2010-07-09 - Written - Bovy (UofT) """ Potential.__init__(self,amp=amp,ro=ro,vo=vo,amp_units='mass') if _APY_LOADED and isinstance(a,units.Quantity): a= a.to(units.kpc).value/self._ro self.a= a self._scale= self.a self.alpha= 2 self.beta= 4 self._b= b self._b2= self._b**2. self._c= c self._c2= self._c**2. self._setup_gl(glorder) self._setup_zvec_pa(zvec,pa) self._force_hash= None if normalize or \ (isinstance(normalize,(int,float)) \ and not isinstance(normalize,bool)): #pragma: no cover self.normalize(normalize) self.hasC= not self._glorder is None self.hasC_dxdv= False if not self._aligned or numpy.fabs(self._b-1.) > 10.**-10.: self.isNonAxi= True return None def _evaluate_xyz(self,x,y,z): """Evaluation of the potential as a function of (x,y,z) in the aligned coordinate frame""" psi= lambda m: -1./(1.+m/self.a)-numpy.log(m/self.a/(1.+m/self.a)) return -self._b*self._c/self.a\ *_potInt(x,y,z,psi,self._b2,self._c2,glx=self._glx,glw=self._glw) class TriaxialNFWPotential(TwoPowerTriaxialPotential): """Class that implements the triaxial NFW potential .. math:: \\rho(x,y,z) = \\frac{\\mathrm{amp}}{4\\,\\pi\\,a^3}\\,\\frac{1}{(m/a)\\,(1+m/a)^{2}} with .. math:: m^2 = x'^2 + \\frac{y'^2}{b^2}+\\frac{z'^2}{c^2} and :math:`(x',y',z')` is a rotated frame wrt :math:`(x,y,z)` specified by parameters ``zvec`` and ``pa`` which specify (a) ``zvec``: the location of the :math:`z'` axis in the :math:`(x,y,z)` frame and (b) ``pa``: the position angle of the :math:`x'` axis wrt the :math:`\\tilde{x}` axis, that is, the :math:`x` axis after rotating to ``zvec``. """ def __init__(self,amp=1.,a=2.,b=1.,c=1.,zvec=None,pa=None, normalize=False, conc=None,mvir=None, glorder=50,vo=None,ro=None, H=70.,Om=0.3,overdens=200.,wrtcrit=False): """ NAME: __init__ PURPOSE: Initialize a triaxial NFW potential INPUT: amp - amplitude to be applied to the potential (default: 1); can be a Quantity with units of mass or Gxmass a - scale radius (can be Quantity) b - y-to-x axis ratio of the density c - z-to-x axis ratio of the density zvec= (None) If set, a unit vector that corresponds to the z axis pa= (None) If set, the position angle of the x axis glorder= (50) if set, compute the relevant force and potential integrals with Gaussian quadrature of this order normalize - if True, normalize such that vc(1.,0.)=1., or, if given as a number, such that the force is this fraction of the force necessary to make vc(1.,0.)=1. Alternatively, NFW potentials can be initialized using conc= concentration mvir= virial mass in 10^12 Msolar in which case you also need to supply the following keywords H= (default: 70) Hubble constant in km/s/Mpc Om= (default: 0.3) Omega matter overdens= (200) overdensity which defines the virial radius wrtcrit= (False) if True, the overdensity is wrt the critical density rather than the mean matter density ro=, vo= distance and velocity scales for translation into internal units (default from configuration file) OUTPUT: (none) HISTORY: 2016-05-30 - Written - Bovy (UofT) """ Potential.__init__(self,amp=amp,ro=ro,vo=vo,amp_units='mass') if _APY_LOADED and isinstance(a,units.Quantity): a= a.to(units.kpc).value/self._ro self.alpha= 1 self.beta= 3 self._b= b self._b2= self._b**2. self._c= c self._c2= self._c**2. self._setup_gl(glorder) self._setup_zvec_pa(zvec,pa) self._force_hash= None if conc is None: self.a= a if normalize or \ (isinstance(normalize,(int,float)) \ and not isinstance(normalize,bool)): self.normalize(normalize) else: from galpy.potential import NFWPotential dum= NFWPotential(mvir=mvir,conc=conc,ro=self._ro,vo=self._vo, H=H,Om=Om,wrtcrit=wrtcrit,overdens=overdens) self.a= dum.a self._amp= dum._amp self._scale= self.a self.hasC= not self._glorder is None self.hasC_dxdv= False if not self._aligned or numpy.fabs(self._b-1.) > 10.**-10.: self.isNonAxi= True return None def _evaluate_xyz(self,x,y,z): """Evaluation of the potential as a function of (x,y,z) in the aligned coordinate frame""" psi= lambda m: 1./(1.+m/self.a) return -self._b*self._c/self.a\ *_potInt(x,y,z,psi,self._b2,self._c2,glx=self._glx,glw=self._glw) def _potInt(x,y,z,psi,b2,c2,glx=None,glw=None): """int_0^\infty psi~(m))/sqrt([1+tau]x[b^2+tau]x[c^2+tau])dtau, where psi~(m) = [psi(\infty)-psi(m)]/[2Aa^2], with A=amp/[4pia^3]""" def integrand(s): t= 1/s**2.-1. return psi(numpy.sqrt(x**2./(1.+t)+y**2./(b2+t)+z**2./(c2+t)))\ /numpy.sqrt((1.+(b2-1.)*s**2.)*(1.+(c2-1.)*s**2.)) if glx is None: return integrate.quad(integrand,0.,1.)[0] else: return numpy.sum(glw*integrand(glx)) def _forceInt(x,y,z,dens,b2,c2,i,glx=None,glw=None): """Integral that gives the force in x,y,z""" def integrand(s): t= 1/s**2.-1. return dens(numpy.sqrt(x**2./(1.+t)+y**2./(b2+t)+z**2./(c2+t)))\ *(x/(1.+t)*(i==0)+y/(b2+t)*(i==1)+z/(c2+t)*(i==2))\ /numpy.sqrt((1.+(b2-1.)*s**2.)*(1.+(c2-1.)*s**2.)) if glx is None: return integrate.quad(integrand,0.,1.)[0] else: return numpy.sum(glw*integrand(glx)) def _2ndDerivInt(x,y,z,dens,densDeriv,b2,c2,i,j,glx=None,glw=None): """Integral that gives the 2nd derivative of the potential in x,y,z""" def integrand(s): t= 1/s**2.-1. m= numpy.sqrt(x**2./(1.+t)+y**2./(b2+t)+z**2./(c2+t)) return (densDeriv(m) *(x/(1.+t)*(i==0)+y/(b2+t)*(i==1)+z/(c2+t)*(i==2)) *(x/(1.+t)*(j==0)+y/(b2+t)*(j==1)+z/(c2+t)*(j==2))/m\ +dens(m)*(i==j)*((1./(1.+t)*(i==0)+1./(b2+t)*(i==1)+1./(c2+t)*(i==2))))\ /numpy.sqrt((1.+(b2-1.)*s**2.)*(1.+(c2-1.)*s**2.)) if glx is None: return integrate.quad(integrand,0.,1.)[0] else: return numpy.sum(glw*integrand(glx)) galpy-1.3.0/galpy/potential_src/verticalPotential.py0000644000076500000240000000572713154132212022763 0ustar bovystaff00000000000000from galpy.potential_src.linearPotential import linearPotential from galpy.potential_src.Potential import PotentialError, Potential _APY_LOADED= True try: from astropy import units except ImportError: _APY_LOADED= False class verticalPotential(linearPotential): """Class that represents a vertical potential derived from a RZPotential: phi(z;R)= phi(R,z)-phi(R,0.)""" def __init__(self,RZPot,R=1.): """ NAME: __init__ PURPOSE: Initialize INPUT: RZPot - RZPotential instance R - Galactocentric radius at which to create the vertical potential OUTPUT: verticalPotential instance HISTORY: 2010-07-13 - Written - Bovy (NYU) """ linearPotential.__init__(self,amp=1.,ro=RZPot._ro,vo=RZPot._vo) self._Pot= RZPot self._R= R # Also transfer roSet and voSet self._roSet= RZPot._roSet self._voSet= RZPot._voSet return None def _evaluate(self,z,t=0.): """ NAME: _evaluate PURPOSE: evaluate the potential INPUT: z t OUTPUT: Pot(z,t;R) HISTORY: 2010-07-13 - Written - Bovy (NYU) """ return self._Pot(self._R,z,t=t,use_physical=False)\ -self._Pot(self._R,0.,t=t,use_physical=False) def _force(self,z,t=0.): """ NAME: _force PURPOSE: evaluate the force INPUT: z t OUTPUT: F_z(z,t;R) HISTORY: 2010-07-13 - Written - Bovy (NYU) """ return self._Pot.zforce(self._R,z,t=t,use_physical=False)\ -self._Pot.zforce(self._R,0.,t=t,use_physical=False) def RZToverticalPotential(RZPot,R): """ NAME: RZToverticalPotential PURPOSE: convert a RZPotential to a vertical potential at a given R INPUT: RZPot - RZPotential instance or list of such instances R - Galactocentric radius at which to evaluate the vertical potential (can be Quantity) OUTPUT: (list of) linearPotential instance(s) HISTORY: 2010-07-21 - Written - Bovy (NYU) """ if _APY_LOADED and isinstance(R,units.Quantity): if hasattr(RZPot,'_ro'): R= R.to(units.kpc).value/RZPot._ro else: R= R.to(units.kpc).value/RZPot[0]._ro if isinstance(RZPot,list): out= [] for pot in RZPot: if isinstance(pot,linearPotential): out.append(pot) else: out.append(verticalPotential(pot,R)) return out elif isinstance(RZPot,Potential): return verticalPotential(RZPot,R) elif isinstance(RZPot,linearPotential): return RZPot else: #pragma: no cover raise PotentialError("Input to 'RZToverticalPotential' is neither an RZPotential-instance or a list of such instances") galpy-1.3.0/galpy/potential_src/WrapperPotential.py0000644000076500000240000001457113236350147022601 0ustar bovystaff00000000000000############################################################################### # WrapperPotential.py: Super-class for wrapper potentials ############################################################################### from galpy.potential_src.Potential import Potential, _isNonAxi, _dim from galpy.potential_src.planarPotential import planarPotential from galpy.potential_src.Potential import evaluatePotentials, \ evaluateRforces, evaluatephiforces, evaluatezforces, \ evaluateR2derivs, evaluatez2derivs, \ evaluateRzderivs, evaluateDensities from galpy.potential_src.planarPotential import evaluateplanarPotentials, \ evaluateplanarRforces, evaluateplanarphiforces, \ evaluateplanarR2derivs class parentWrapperPotential(object): """'Dummy' class only used to delegate wrappers to either 2D planarWrapperPotential or 3D WrapperPotential based on pot's dimensionality, using a little python object creation magic...""" def __new__(cls,*args,**kwargs): if kwargs.pop('_init',False): # When we get here recursively, just create new object return object.__new__(cls) # Decide whether superclass is Wrapper or planarWrapper based on dim pot= kwargs.get('pot',None) if _dim(pot) == 2: parentWrapperPotential= planarWrapperPotential elif _dim(pot) == 3: parentWrapperPotential= WrapperPotential else: raise ValueError("WrapperPotentials are only supported in 3D and 2D") # Create object from custom class that derives from correct wrapper, # make sure to turn off normalization for all wrappers kwargs['_init']= True # to break recursion above out= type.__new__(type,'_%s' % cls.__name__, (parentWrapperPotential,cls), {'normalize':property()})(*args,**kwargs) kwargs.pop('_init',False) # This runs init for the subclass (the specific wrapper) cls.__init__(out,*args,**kwargs) return out class WrapperPotential(Potential): def __init__(self,amp=1.,pot=None,ro=None,vo=None,_init=None,**kwargs): """ NAME: __init__ PURPOSE: initialize a WrapperPotential, a super-class for wrapper potentials INPUT: amp - amplitude to be applied to the potential (default: 1.) pot - Potential instance or list thereof; the amplitude of this will be grown by this wrapper OUTPUT: (none) HISTORY: 2017-06-26 - Started - Bovy (UofT) """ if not _init: return None # Don't run __init__ at the end of setup Potential.__init__(self,amp=amp,ro=ro,vo=vo) self._pot= pot self.isNonAxi= _isNonAxi(self._pot) def __getattr__(self,attribute): if attribute == '_evaluate' \ or attribute == '_Rforce' or attribute == '_zforce' \ or attribute == '_phiforce' \ or attribute == '_R2deriv' or attribute == '_z2deriv' \ or attribute == '_Rzderiv' or attribute == '_phi2deriv' \ or attribute == '_Rphideriv' or attribute == '_dens': return lambda R,Z,phi=0.,t=0.: \ self._wrap(attribute,R,Z,phi=phi,t=t) else: return super(WrapperPotential,self).__getattr__(attribute) def _wrap_pot_func(self,attribute): if attribute == '_evaluate': return evaluatePotentials elif attribute == '_dens': return evaluateDensities elif attribute == '_Rforce': return evaluateRforces elif attribute == '_zforce': return evaluatezforces elif attribute == '_phiforce': return evaluatephiforces elif attribute == '_R2deriv': return evaluateR2derivs elif attribute == '_z2deriv': return evaluatez2derivs elif attribute == '_Rzderiv': return evaluateRzderivs elif attribute == '_phi2deriv': return lambda p,R,Z,phi=0.,t=0.: \ evaluatePotentials(p,R,Z,phi=phi,t=t,dphi=2) elif attribute == '_Rphideriv': return lambda p,R,Z,phi=0.,t=0.: \ evaluatePotentials(p,R,Z,phi=phi,t=t,dR=1,dphi=1) else: #pragma: no cover raise AttributeError("Attribute %s not found in for this WrapperPotential" % attribute) class planarWrapperPotential(planarPotential): def __init__(self,amp=1.,pot=None,ro=None,vo=None,_init=None,**kwargs): """ NAME: __init__ PURPOSE: initialize a WrapperPotential, a super-class for wrapper potentials INPUT: amp - amplitude to be applied to the potential (default: 1.) pot - Potential instance or list thereof; the amplitude of this will be grown by this wrapper OUTPUT: (none) HISTORY: 2017-06-26 - Started - Bovy (UofT) """ if not _init: return None # Don't run __init__ at the end of setup planarPotential.__init__(self,amp=amp,ro=ro,vo=vo) self._pot= pot self.isNonAxi= _isNonAxi(self._pot) def __getattr__(self,attribute): if attribute == '_evaluate' \ or attribute == '_Rforce' \ or attribute == '_phiforce' \ or attribute == '_R2deriv' \ or attribute == '_phi2deriv' \ or attribute == '_Rphideriv': return lambda R,phi=0.,t=0.: \ self._wrap(attribute,R,phi=phi,t=t) else: return super(planarWrapperPotential,self).__getattr__(attribute) def _wrap_pot_func(self,attribute): if attribute == '_evaluate': return evaluateplanarPotentials elif attribute == '_Rforce': return evaluateplanarRforces elif attribute == '_phiforce': return evaluateplanarphiforces elif attribute == '_R2deriv': return evaluateplanarR2derivs elif attribute == '_phi2deriv': return lambda p,R,phi=0.,t=0.: \ evaluateplanarPotentials(p,R,phi=phi,t=t,dphi=2) elif attribute == '_Rphideriv': return lambda p,R,phi=0.,t=0.: \ evaluateplanarPotentials(p,R,phi=phi,t=t,dR=1,dphi=1) else: #pragma: no cover raise AttributeError("Attribute %s not found in for this WrapperPotential" % attribute) galpy-1.3.0/galpy/snapshot.py0000644000076500000240000000041612741726747016300 0ustar bovystaff00000000000000from galpy.snapshot_src import Snapshot from galpy.snapshot_src import snapshotMovies import galpy.snapshot_src.nemo_util # # Functions # snapshotToMovie= snapshotMovies.snapshotToMovie nemo_util= galpy.snapshot_src.nemo_util # # Classes # Snapshot= Snapshot.Snapshot galpy-1.3.0/galpy/snapshot_src/0000755000076500000240000000000013236420072016552 5ustar bovystaff00000000000000galpy-1.3.0/galpy/snapshot_src/__init__.py0000644000076500000240000000000012671130470020653 0ustar bovystaff00000000000000galpy-1.3.0/galpy/snapshot_src/directnbody.py0000644000076500000240000001077712671130470021450 0ustar bovystaff00000000000000#Direct force summation N-body code from __future__ import print_function import numpy as nu from numpy import linalg import galpy.util.bovy_symplecticode as symplecticode from galpy.potential_src.Potential import evaluateRforces, evaluatezforces,\ evaluatephiforces from galpy.potential_src.planarPotential import evaluateplanarRforces,\ evaluateplanarphiforces from galpy.potential_src.linearPotential import evaluatelinearForces def direct_nbody(q,p,m,t,pot=None,softening_model='plummer', softening_length=None, atol=None,rtol=None): """ NAME: direct_nbody PURPOSE: N-body code using direct summation for force evaluation INPUT: q - list of initial positions (numpy.ndarrays) p - list of initial momenta (numpy.ndarrays) m - list of masses t - times at which output is desired pot= external potential (galpy.potential or list of galpy.potentials) softening_model= type of softening to use ('plummer') softening_length= (optional) OUTPUT: list of [q,p] at times t HISTORY: 2011-02-03 - Written - Bovy (NYU) """ #Set up everything if softening_model.lower() == 'plummer': softening= _plummer_soft out= [] out.append([q,p]) qo= q po= p #Determine appropriate stepsize dt= t[1]-t[0] ndt= 1 to= t[0] #determine appropriate softening length if not given if softening_length is None: softening_length= 0.01 #Run simulation for ii in range(1,len(t)): print(ii) for jj in range(ndt): #loop over number of sub-intervals (qo,po)= _direct_nbody_step(qo,po,m,to,dt,pot, softening,(softening_length,)) #print(qo) to+= dt out.append([qo,po]) #Return output return out def _direct_nbody_step(q,p,m,t,dt,pot,softening,softening_args): """One N-body step: drift-kick-drift""" #drift q12= [symplecticode.leapfrog_leapq(q[ii],p[ii],dt/2.) \ for ii in range(len(q))] #kick force= _direct_nbody_force(q12,m,t+dt/2.,pot,softening,softening_args) #print(force) p= [symplecticode.leapfrog_leapp(p[ii],dt,force[ii]) \ for ii in range(len(p))] #drift q= [symplecticode.leapfrog_leapq(q12[ii],p[ii],dt/2.) \ for ii in range(len(q12))] return (q,p) def _direct_nbody_force(q,m,t,pot,softening,softening_args): """Calculate the force""" #First do the particles #Calculate all the distances nq= len(q) dim= len(q[0]) dist_vec= nu.zeros((nq,nq,dim)) dist= nu.zeros((nq,nq)) for ii in range(nq): for jj in range(ii+1,nq): dist_vec[ii,jj,:]= q[jj]-q[ii] dist_vec[jj,ii,:]= -dist_vec[ii,jj,:] dist[ii,jj]= linalg.norm(dist_vec[ii,jj,:]) dist[jj,ii]= dist[ii,jj] #Calculate all the forces force= [] for ii in range(nq): thisforce= nu.zeros(dim) for jj in range(nq): if ii == jj: continue thisforce+= m[jj]*softening(dist[ii,jj],*softening_args)\ /dist[ii,jj]*dist_vec[ii,jj,:] force.append(thisforce) #Then add the external force if pot is None: return force for ii in range(nq): force[ii]+= _external_force(q[ii],t,pot) return force def _external_force(x,t,pot): dim= len(x) if dim == 3: #x is rectangular so calculate R and phi R= nu.sqrt(x[0]**2.+x[1]**2.) phi= nu.arccos(x[0]/R) sinphi= x[1]/R cosphi= x[0]/R if x[1] < 0.: phi= 2.*nu.pi-phi #calculate forces Rforce= evaluateRforces(R,x[2],pot,phi=phi,t=t) phiforce= evaluatephiforces(R,x[2],pot,phi=phi,t=t) return nu.array([cosphi*Rforce-1./R*sinphi*phiforce, sinphi*Rforce+1./R*cosphi*phiforce, evaluatezforces(R,x[2],pot,phi=phi,t=t)]) elif dim == 2: #x is rectangular so calculate R and phi R= nu.sqrt(x[0]**2.+x[1]**2.) phi= nu.arccos(x[0]/R) sinphi= x[1]/R cosphi= x[0]/R if x[1] < 0.: phi= 2.*nu.pi-phi #calculate forces Rforce= evaluateplanarRforces(R,pot,phi=phi,t=t) phiforce= evaluateplanarphiforces(R,pot,phi=phi,t=t) return nu.array([cosphi*Rforce-1./R*sinphi*phiforce, sinphi*Rforce+1./R*cosphi*phiforce]) elif dim == 1: return evaluatelinearForces(x,pot,t=t) def _plummer_soft(d,eps): return d/(d**2.+eps**2.)**1.5 galpy-1.3.0/galpy/snapshot_src/GadgetSnapshot.py0000644000076500000240000000425712671130470022051 0ustar bovystaff00000000000000try: import pynbody _PYNBODYENABLED= True except ImportError: _PYNBODYENABLED= False class GadgetSnapshot(object): """Snapshot coming out of gadget""" def __init__(self,*args,**kwargs): """ NAME: __init__ PURPOSE: initialize a Gadget snapshot object INPUT: Initialize using: 1) filename OUTPUT: HISTORY: 2011-08-15 - Started - Bovy """ if not _PYNBODYENABLED: raise ImportError("pynbody could not be loaded to read the gadget snapshot") filename= args[0] self._sim = pynbody.load(filename) self._sim.physical_units() self._star= self._sim.star return None def __getattr__(self,i): if i == 'x': return self.pos[0] elif i =='pos': return self._star['pos'] def plot(self,*args,**kwargs): """ NAME: plot PURPOSE: plot the snapshot INPUT: OUTPUT: HISTORY: 2011-08-15 - Started - Bovy (NYU) """ labeldict= {'t':r'$t$','R':r'$R$','vR':r'$v_R$','vT':r'$v_T$', 'z':r'$z$','vz':r'$v_z$','phi':r'$\phi$', 'x':r'$x$','y':r'$y$','vx':r'$v_x$','vy':r'$v_y$'} #Defaults if not kwargs.has_key('d1') and not kwargs.has_key('d2'): if len(self.orbits[0].vxvv) == 3: d1= 'R' d2= 'vR' elif len(self.orbits[0].vxvv) == 4: d1= 'x' d2= 'y' elif len(self.orbits[0].vxvv) == 2: d1= 'x' d2= 'vx' elif len(self.orbits[0].vxvv) == 5 \ or len(self.orbits[0].vxvv) == 6: d1= 'R' d2= 'z' elif not kwargs.has_key('d1'): d2= kwargs['d2'] kwargs.pop('d2') d1= 't' elif not kwargs.has_key('d2'): d1= kwargs['d1'] kwargs.pop('d1') d2= 't' else: d1= kwargs['d1'] kwargs.pop('d1') d2= kwargs['d2'] kwargs.pop('d2') galpy-1.3.0/galpy/snapshot_src/nemo_util.py0000644000076500000240000000366112741726747021147 0ustar bovystaff00000000000000############################################################################### # nemo_util.py: some utilities for handling NEMO snapshots ############################################################################### import os import numpy import tempfile import subprocess def read(filename,ext=None,swapyz=False): """ NAME: read PURPOSE: read a NEMO snapshot file consisting of mass,position,velocity INPUT: filename - name of the file ext= if set, 'nemo' for NEMO binary format, otherwise assumed ASCII; if not set, gleaned from extension swapyz= (False) if True, swap the y and z axes in the output (only for position and velocity) OUTPUT: snapshots [nbody,ndim,nt] HISTORY: 2015-11-18 - Written - Bovy (UofT) """ if ext is None and filename.split('.')[-1] == 'nemo': ext= 'nemo' elif ext is None: ext= 'dat' # Convert to ASCII if necessary if ext.lower() == 'nemo': file_handle, asciifilename= tempfile.mkstemp() os.close(file_handle) stderr= open('/dev/null','w') try: subprocess.check_call(['s2a',filename,asciifilename])#,stderr=stderr) except subprocess.CalledProcessError: os.remove(asciifilename) finally: stderr.close() else: asciifilename= filename # Now read out= numpy.loadtxt(asciifilename,comments='#') if ext.lower() == 'nemo': os.remove(asciifilename) if swapyz: out[:,[2,3]]= out[:,[3,2]] out[:,[5,6]]= out[:,[6,5]] # Get the number of snapshots nt= (_wc(asciifilename)-out.shape[0])//13 # 13 comments/snapshot out= numpy.reshape(out,(nt,out.shape[0]//nt,out.shape[1])) return numpy.swapaxes(numpy.swapaxes(out,0,1),1,2) def _wc(filename): try: return int(subprocess.check_output(['wc','-l',filename]).split()[0]) except subprocess.CalledProcessError: return numpy.nan galpy-1.3.0/galpy/snapshot_src/Snapshot.py0000644000076500000240000003233212671130470020730 0ustar bovystaff00000000000000import numpy as nu from galpy.orbit import Orbit from galpy.potential_src.planarPotential import RZToplanarPotential import galpy.util.bovy_plot as plot from directnbody import direct_nbody class Snapshot(object): """General snapshot = collection of particles class""" def __init__(self,*args,**kwargs): """ NAME: __init__ PURPOSE: initialize a snapshot object INPUT: Initialize using: 1) list of orbits, list of masses (masses=) Coming soon: 2) observations 3) DFs to draw from OUTPUT: HISTORY: 2011-02-02 - Started - Bovy """ if isinstance(args[0],list) and isinstance(args[0][0],Orbit): self.orbits= args[0] if kwargs.has_key('masses'): self.masses= kwargs['masses'] else: self.masses= nu.ones(len(self.orbits)) return None def integrate(self,t,pot=None,method='test-particle', **kwargs): """ NAME: integrate PURPOSE: integrate the snapshot in time INPUT: t - numpy.array of times to save the snapshots at (must start at 0) pot= potential object or list of such objects (default=None) method= method to use ('test-particle' or 'direct-python' for now) OUTPUT: list of snapshots at times t HISTORY: 2011-02-02 - Written - Bovy (NYU) """ if method.lower() == 'test-particle': return self._integrate_test_particle(t,pot) elif method.lower() == 'direct-python': return self._integrate_direct_python(t,pot,**kwargs) def _integrate_test_particle(self,t,pot): """Integrate the snapshot as a set of test particles in an external \ potential""" #Integrate all the orbits for o in self.orbits: o.integrate(t,pot) #Return them as a set of snapshots out= [] for ii in range(len(t)): outOrbits= [] for o in self.orbits: outOrbits.append(o(t[ii])) out.append(Snapshot(outOrbits,self.masses)) return out def _integrate_direct_python(self,t,pot,**kwargs): """Integrate the snapshot using a direct force summation method \ written entirely in python""" #Prepare input for direct_nbody q= [] p= [] nq= len(self.orbits) dim= self.orbits[0].dim() if pot is None: thispot= None elif dim == 2: thispot= RZToplanarPotential(pot) else: thispot= pot for ii in range(nq): #Transform to rectangular frame if dim == 1: thisq= nu.array([self.orbits[ii].x()]).flatten() thisp= nu.array([self.orbits[ii].vx()]).flatten() elif dim == 2: thisq= nu.array([self.orbits[ii].x(), self.orbits[ii].y()]).flatten() thisp= nu.array([self.orbits[ii].vx(), self.orbits[ii].vy()]).flatten() elif dim == 3: thisq= nu.array([self.orbits[ii].x(), self.orbits[ii].y(), self.orbits[ii].z()]).flatten() thisp= nu.array([self.orbits[ii].vx(), self.orbits[ii].vy(), self.orbits[ii].vz()]).flatten() q.append(thisq) p.append(thisp) #Run simulation nbody_out= direct_nbody(q,p,self.masses,t,pot=thispot,**kwargs) #Post-process output nt= len(nbody_out) out= [] for ii in range(nt): snap_orbits= [] for jj in range(nq): if dim == 3: #go back to the cylindrical frame R= nu.sqrt(nbody_out[ii][0][jj][0]**2. +nbody_out[ii][0][jj][1]**2.) phi= nu.arccos(nbody_out[ii][0][jj][0]/R) if nbody_out[ii][0][jj][1] < 0.: phi= 2.*nu.pi-phi vR= nbody_out[ii][1][jj][0]*nu.cos(phi)\ +nbody_out[ii][1][jj][1]*nu.sin(phi) vT= nbody_out[ii][1][jj][1]*nu.cos(phi)\ -nbody_out[ii][1][jj][0]*nu.sin(phi) vxvv= nu.zeros(dim*2) vxvv[3]= nbody_out[ii][0][jj][2] vxvv[4]= nbody_out[ii][1][jj][2] vxvv[0]= R vxvv[1]= vR vxvv[2]= vT vxvv[5]= phi if dim == 2: #go back to the cylindrical frame R= nu.sqrt(nbody_out[ii][0][jj][0]**2. +nbody_out[ii][0][jj][1]**2.) phi= nu.arccos(nbody_out[ii][0][jj][0]/R) if nbody_out[ii][0][jj][1] < 0.: phi= 2.*nu.pi-phi vR= nbody_out[ii][1][jj][0]*nu.cos(phi)\ +nbody_out[ii][1][jj][1]*nu.sin(phi) vT= nbody_out[ii][1][jj][1]*nu.cos(phi)\ -nbody_out[ii][1][jj][0]*nu.sin(phi) vxvv= nu.zeros(dim*2) vxvv[0]= R vxvv[1]= vR vxvv[2]= vT vxvv[3]= phi if dim == 1: vxvv= [nbody_out[ii][0][jj],nbody_out[ii][1][jj]] snap_orbits.append(Orbit(vxvv)) out.append(Snapshot(snap_orbits,self.masses)) return out #Plotting def plot(self,*args,**kwargs): """ NAME: plot PURPOSE: plot the snapshot (with reasonable defaults) INPUT: d1= first dimension to plot ('x', 'y', 'R', 'vR', 'vT', 'z', 'vz', ...) d2= second dimension to plot matplotlib.plot inputs+bovy_plot.plot inputs OUTPUT: sends plot to output device HISTORY: 2011-02-06 - Written based on Orbit's plot """ labeldict= {'t':r'$t$','R':r'$R$','vR':r'$v_R$','vT':r'$v_T$', 'z':r'$z$','vz':r'$v_z$','phi':r'$\phi$', 'x':r'$x$','y':r'$y$','vx':r'$v_x$','vy':r'$v_y$'} #Defaults if not kwargs.has_key('d1') and not kwargs.has_key('d2'): if len(self.orbits[0].vxvv) == 3: d1= 'R' d2= 'vR' elif len(self.orbits[0].vxvv) == 4: d1= 'x' d2= 'y' elif len(self.orbits[0].vxvv) == 2: d1= 'x' d2= 'vx' elif len(self.orbits[0].vxvv) == 5 \ or len(self.orbits[0].vxvv) == 6: d1= 'R' d2= 'z' elif not kwargs.has_key('d1'): d2= kwargs['d2'] kwargs.pop('d2') d1= 't' elif not kwargs.has_key('d2'): d1= kwargs['d1'] kwargs.pop('d1') d2= 't' else: d1= kwargs['d1'] kwargs.pop('d1') d2= kwargs['d2'] kwargs.pop('d2') #Get x and y if d1 == 'R': x= [o.R() for o in self.orbits] elif d1 == 'z': x= [o.z() for o in self.orbits] elif d1 == 'vz': x= [o.vz() for o in self.orbits] elif d1 == 'vR': x= [o.vR() for o in self.orbits] elif d1 == 'vT': x= [o.vT() for o in self.orbits] elif d1 == 'x': x= [o.x() for o in self.orbits] elif d1 == 'y': x= [o.y() for o in self.orbits] elif d1 == 'vx': x= [o.vx() for o in self.orbits] elif d1 == 'vy': x= [o.vy() for o in self.orbits] elif d1 == 'phi': x= [o.phi() for o in self.orbits] if d2 == 'R': y= [o.R() for o in self.orbits] elif d2 == 'z': y= [o.z() for o in self.orbits] elif d2 == 'vz': y= [o.vz() for o in self.orbits] elif d2 == 'vR': y= [o.vR() for o in self.orbits] elif d2 == 'vT': y= [o.vT() for o in self.orbits] elif d2 == 'x': y= [o.x() for o in self.orbits] elif d2 == 'y': y= [o.y() for o in self.orbits] elif d2 == 'vx': y= [o.vx() for o in self.orbits] elif d2 == 'vy': y= [o.vy() for o in self.orbits] elif d2 == 'phi': y= [o.phi() for o in self.orbits] #Plot if not kwargs.has_key('xlabel'): kwargs['xlabel']= labeldict[d1] if not kwargs.has_key('ylabel'): kwargs['ylabel']= labeldict[d2] if len(args) == 0: args= (',',) plot.bovy_plot(x,y,*args,**kwargs) def plot3d(self,*args,**kwargs): """ NAME: plot3d PURPOSE: plot the snapshot in 3D (with reasonable defaults) INPUT: d1= first dimension to plot ('x', 'y', 'R', 'vR', 'vT', 'z', 'vz', ...) d2= second dimension to plot d3= third dimension to plot matplotlib.plot inputs+bovy_plot.plot3d inputs OUTPUT: sends plot to output device HISTORY: 2011-02-06 - Written based on Orbit's plot3d """ labeldict= {'t':r'$t$','R':r'$R$','vR':r'$v_R$','vT':r'$v_T$', 'z':r'$z$','vz':r'$v_z$','phi':r'$\phi$', 'x':r'$x$','y':r'$y$','vx':r'$v_x$','vy':r'$v_y$'} #Defaults if not kwargs.has_key('d1') and not kwargs.has_key('d2') \ and not kwargs.has_key('d3'): if len(self.orbits[0].vxvv) == 3: d1= 'R' d2= 'vR' d3= 'vT' elif len(self.orbits[0].vxvv) == 4: d1= 'x' d2= 'y' d3= 'vR' elif len(self.orbits[0].vxvv) == 2: raise AttributeError("Cannot plot 3D aspects of 1D orbits") elif len(self.orbits[0].vxvv) == 5: d1= 'R' d2= 'vR' d3= 'z' elif len(self.orbits[0].vxvv) == 6: d1= 'x' d2= 'y' d3= 'z' elif not (kwargs.has_key('d1') and kwargs.has_key('d2') \ and kwargs.has_key('d3')): raise AttributeError("Please provide 'd1', 'd2', and 'd3'") else: d1= kwargs['d1'] kwargs.pop('d1') d2= kwargs['d2'] kwargs.pop('d2') d3= kwargs['d3'] kwargs.pop('d3') #Get x, y, and z if d1 == 'R': x= [o.R() for o in self.orbits] elif d1 == 'z': x= [o.z() for o in self.orbits] elif d1 == 'vz': x= [o.vz() for o in self.orbits] elif d1 == 'vR': x= [o.vR() for o in self.orbits] elif d1 == 'vT': x= [o.vT() for o in self.orbits] elif d1 == 'x': x= [o.x() for o in self.orbits] elif d1 == 'y': x= [o.y() for o in self.orbits] elif d1 == 'vx': x= [o.vx() for o in self.orbits] elif d1 == 'vy': x= [o.vy() for o in self.orbits] elif d1 == 'phi': x= [o.phi() for o in self.orbits] if d2 == 'R': y= [o.R() for o in self.orbits] elif d2 == 'z': y= [o.z() for o in self.orbits] elif d2 == 'vz': y= [o.vz() for o in self.orbits] elif d2 == 'vR': y= [o.vR() for o in self.orbits] elif d2 == 'vT': y= [o.vT() for o in self.orbits] elif d2 == 'x': y= [o.x() for o in self.orbits] elif d2 == 'y': y= [o.y() for o in self.orbits] elif d2 == 'vx': y= [o.vx() for o in self.orbits] elif d2 == 'vy': y= [o.vy() for o in self.orbits] elif d2 == 'phi': y= [o.phi() for o in self.orbits] if d3 == 'R': z= [o.R() for o in self.orbits] elif d3 == 'z': z= [o.z() for o in self.orbits] elif d3 == 'vz': z= [o.vz() for o in self.orbits] elif d3 == 'vR': z= [o.vR() for o in self.orbits] elif d3 == 'vT': z= [o.vT() for o in self.orbits] elif d3 == 'x': z= [o.x() for o in self.orbits] elif d3 == 'y': z= [o.y() for o in self.orbits] elif d3 == 'vx': z= [o.vx() for o in self.orbits] elif d3 == 'vy': z= [o.vy() for o in self.orbits] elif d3 == 'phi': z= [o.phi() for o in self.orbits] #Plot if not kwargs.has_key('xlabel'): kwargs['xlabel']= labeldict[d1] if not kwargs.has_key('ylabel'): kwargs['ylabel']= labeldict[d2] if not kwargs.has_key('zlabel'): kwargs['zlabel']= labeldict[d3] if len(args) == 0: args= (',',) plot.bovy_plot3d(x,y,z,*args,**kwargs) #Pickling def __getstate__(self): return (self.orbits, self.masses) def __setstate__(self,state): self.orbits= state[0] self.masses= state[1] galpy-1.3.0/galpy/snapshot_src/snapshotMovies.py0000644000076500000240000001043212671130470022150 0ustar bovystaff00000000000000#Functions to turn snapshots into movies from __future__ import print_function import re import os, os.path import tempfile import shutil import subprocess import math as m import galpy.util.bovy_plot as bovy_plot from Snapshot import * def snapshotToMovie(snap,filename,*args,**kwargs): """ NAME: snapshotToMovie PURPOSE: turn a list of snapshots into a movie INPUT: snap - the snapshots (list) filename - name of the file to save the movie to framerate= in fps bitrate= ? thumbnail=False : create thumbnail image (filename-extension+.jpg) thumbsize= size of thumbnail +Snapshot.plot args and kwargs OUTPUT: movie is saved to file DEPENDENCIES: this procedure uses ffmpeg and convert BUGS: matplotlib's 'Agg' backend has a memory leak that prevents it from creating hundred's of figures. It is recommended to call import matplotlib matplotlib.use('PDF') at the beginning of the movie creating script as the PDF backend does not have the same memory leak. HISTORY: 2011-02-06 - Written - Bovy (NYU) """ if kwargs.has_key('tmpdir'): tmpdir= kwargs['tmpdir'] kwargs.pop('tmpdir') else: tmpdir= '/tmp' if kwargs.has_key('framerate'): framerate= kwargs['framerate'] kwargs.pop('framerate') else: framerate= 25 if kwargs.has_key('bitrate'): bitrate= kwargs['bitrate'] kwargs.pop('bitrate') else: bitrate= 1000 if kwargs.has_key('thumbnail') and kwargs['thumbnail']: thumbnail= True kwargs.pop('thumbnail') elif kwargs.has_key('thumbnail'): kwargs.pop('thumbnail') thumbnail= False else: thumbnail= False if kwargs.has_key('thumbsize'): thumbsize= kwargs['thumbsize'] else: thumbsize= 300 #Create all of the files tempdir= tempfile.mkdtemp(dir=tmpdir) #Temporary directory tmpfiles= [] nsnap= len(snap) file_length= int(m.ceil(m.log10(nsnap))) #Determine good xrange BOVY TO DO if not kwargs.has_key('xrange'): pass if not kwargs.has_key('yrange'): pass for ii in range(nsnap): tmpfiles.append(os.path.join(tempdir, str(ii).zfill(file_length))) bovy_plot.bovy_print() snap[ii].plot(*args,**kwargs) bovy_plot.bovy_end_print(tmpfiles[ii]+'.pdf') #Convert to jpeg try: subprocess.check_call(['convert', tmpfiles[ii]+'.pdf', tmpfiles[ii]+'.jpg']) except subprocess.CalledProcessError: print("'convert' failed") raise subprocess.CalledProcessError #turn them into a movie try: subprocess.check_call(['ffmpeg', '-r',str(framerate), '-b', str(bitrate), '-i', os.path.join(tempdir, '%'+'0%id.jpg' % file_length), '-y', filename]) if thumbnail: thumbnameTemp= re.split(r'\.',filename) thumbnameTemp= thumbnameTemp[0:len(thumbnameTemp)-1] thumbname= '' for t in thumbnameTemp: thumbname+= t thumbname+= '.jpg' subprocess.check_call(['ffmpeg', '-itsoffset','-4','-y', '-i',filename, '-vcodec', 'mjpeg', '-vframes','1', '-an', '-f', 'rawvideo', '-s', '%ix%i' % (thumbsize,thumbsize), thumbname]) except subprocess.CalledProcessError: print("'ffmpeg' failed") _cleanupMovieTempdir(tempdir) raise subprocess.CalledProcessError finally: _cleanupMovieTempdir(tempdir) def _cleanupMovieTempdir(tempdir): shutil.rmtree(tempdir) galpy-1.3.0/galpy/util/0000755000076500000240000000000013236420072015021 5ustar bovystaff00000000000000galpy-1.3.0/galpy/util/__init__.py0000644000076500000240000001156513133144160017137 0ustar bovystaff00000000000000from __future__ import print_function import os import shutil import warnings import tempfile import pickle import numpy import scipy.linalg as linalg class galpyWarning(Warning): pass def _warning( message, category=galpyWarning, filename='', lineno=-1, file=None, line=None): if issubclass(category,galpyWarning): print("galpyWarning: "+str(message)) else: print(warnings.formatwarning(message,category,filename,lineno)) warnings.showwarning = _warning def save_pickles(savefilename,*args,**kwargs): """ NAME: save_pickles PURPOSE: relatively safely save things to a pickle INPUT: savefilename - name of the file to save to; actual save operation will be performed on a temporary file and then that file will be shell mv'ed to savefilename +things to pickle (as many as you want!) OUTPUT: none HISTORY: 2010-? - Written - Bovy (NYU) 2011-08-23 - generalized and added to galpy.util - Bovy (NYU) """ saving= True interrupted= False file, tmp_savefilename= tempfile.mkstemp() #savefilename+'.tmp' os.close(file) #Easier this way while saving: try: savefile= open(tmp_savefilename,'wb') file_open= True if kwargs.get('testKeyboardInterrupt',False) and not interrupted: raise KeyboardInterrupt for f in args: pickle.dump(f,savefile,pickle.HIGHEST_PROTOCOL) savefile.close() file_open= False shutil.move(tmp_savefilename,savefilename) saving= False if interrupted: raise KeyboardInterrupt except KeyboardInterrupt: if not saving: raise print("KeyboardInterrupt ignored while saving pickle ...") interrupted= True finally: if file_open: savefile.close() def logsumexp(arr,axis=0): """Faster logsumexp?""" minarr= numpy.amax(arr,axis=axis) if axis == 1: minarr= numpy.reshape(minarr,(arr.shape[0],1)) if axis == 0: minminarr= numpy.tile(minarr,(arr.shape[0],1)) elif axis == 1: minminarr= numpy.tile(minarr,(1,arr.shape[1])) elif axis == None: minminarr= numpy.tile(minarr,arr.shape) else: raise NotImplementedError("'galpy.util.logsumexp' not implemented for axis > 2") if axis == 1: minarr= numpy.reshape(minarr,(arr.shape[0])) return minarr+numpy.log(numpy.sum(numpy.exp(arr-minminarr),axis=axis)) _TINY= 0.000000001 def stable_cho_factor(x,tiny=_TINY): """ NAME: stable_cho_factor PURPOSE: Stable version of the cholesky decomposition INPUT: x - (sc.array) positive definite matrix tiny - (double) tiny number to add to the covariance matrix to make the decomposition stable (has a default) OUTPUT: (L,lowerFlag) - output from scipy.linalg.cho_factor for lower=True REVISION HISTORY: 2009-09-25 - Written - Bovy (NYU) """ return linalg.cho_factor(x+numpy.sum(numpy.diag(x))*tiny*numpy.eye(x.shape[0]),lower=True) def fast_cholesky_invert(A,logdet=False,tiny=_TINY): """ NAME: fast_cholesky_invert PURPOSE: invert a positive definite matrix by using its Cholesky decomposition INPUT: A - matrix to be inverted logdet - (Bool) if True, return the logarithm of the determinant as well tiny - (double) tiny number to add to the covariance matrix to make the decomposition stable (has a default) OUTPUT: A^{-1} REVISION HISTORY: 2009-10-07 - Written - Bovy (NYU) """ L= stable_cho_factor(A,tiny=tiny) if logdet: return (linalg.cho_solve(L,numpy.eye(A.shape[0])), 2.*numpy.sum(numpy.log(numpy.diag(L[0])))) else: return linalg.cho_solve(L,numpy.eye(A.shape[0])) def _rotate_to_arbitrary_vector(v,a,inv=False): """ Return a rotation matrix that rotates v to align with unit vector a i.e. R . v = |v|\hat{a} """ normv= v/numpy.tile(numpy.sqrt(numpy.sum(v**2.,axis=1)),(3,1)).T rotaxis= numpy.cross(normv,a) rotaxis/= numpy.tile(numpy.sqrt(numpy.sum(rotaxis**2.,axis=1)),(3,1)).T crossmatrix= numpy.empty((len(v),3,3)) crossmatrix[:,0,:]= numpy.cross(rotaxis,[1,0,0]) crossmatrix[:,1,:]= numpy.cross(rotaxis,[0,1,0]) crossmatrix[:,2,:]= numpy.cross(rotaxis,[0,0,1]) costheta= numpy.dot(normv,a) sintheta= numpy.sqrt(1.-costheta**2.) if inv: sgn= 1. else: sgn= -1. out= numpy.tile(costheta,(3,3,1)).T*numpy.tile(numpy.eye(3),(len(v),1,1))\ +sgn*numpy.tile(sintheta,(3,3,1)).T*crossmatrix\ +numpy.tile(1.-costheta,(3,3,1)).T\ *(rotaxis[:,:,numpy.newaxis]*rotaxis[:,numpy.newaxis,:]) out[numpy.fabs(costheta-1.) < 10.**-10.]= numpy.eye(3) out[numpy.fabs(costheta+1.) < 10.**-10.]= -numpy.eye(3) return out galpy-1.3.0/galpy/util/bovy_ars.py0000644000076500000240000003321712671130470017227 0ustar bovystaff00000000000000############################################################################# #Copyright (c) 2011, Jo Bovy #All rights reserved. # #Redistribution and use in source and binary forms, with or without #modification, are permitted provided that the following conditions are met: # # Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # Redistributions in binary form must reproduce the above copyright notice, # this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # The name of the author may not be used to endorse or promote products # derived from this software without specific prior written permission. # #THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS #"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT #LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR #A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT #HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, #INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, #BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS #OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED #AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT #LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY #WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE #POSSIBILITY OF SUCH DAMAGE. ############################################################################# import scipy as sc import scipy.stats as stats import math as m #TO DO: #Throw errors in the sample_hull routine def bovy_ars(domain,isDomainFinite,abcissae,hx,hpx,nsamples=1, hxparams=(),maxn=100): """bovy_ars: Implementation of the Adaptive-Rejection Sampling algorithm by Gilks & Wild (1992): Adaptive Rejection Sampling for Gibbs Sampling, Applied Statistics, 41, 337 Based on Wild & Gilks (1993), Algorithm AS 287: Adaptive Rejection Sampling from Log-concave Density Functions, Applied Statistics, 42, 701 Input: domain - [.,.] upper and lower limit to the domain isDomainFinite - [.,.] is there a lower/upper limit to the domain? abcissae - initial list of abcissae (must lie on either side of the peak in hx if the domain is unbounded hx - function that evaluates h(x) = ln g(x) hpx - function that evaluates hp(x) = d h(x) / d x nsamples - (optional) number of desired samples (default=1) hxparams - (optional) a tuple of parameters for h(x) and h'(x) maxn - (optional) maximum number of updates to the hull (default=100) Output: list with nsamples of samples from exp(h(x)) External dependencies: math scipy scipy.stats History: 2009-05-21 - Written - Bovy (NYU) """ #First set-up the upper and lower hulls hull=setup_hull(domain,isDomainFinite,abcissae,hx,hpx,hxparams) #Then start sampling: call sampleone repeatedly out= [] nupdates= 0 for ii in range(int(nsamples)): thissample, hull, nupdates= sampleone(hull,hx,hpx,domain,isDomainFinite,maxn,nupdates,hxparams) out.append(thissample) return out def setup_hull(domain,isDomainFinite,abcissae,hx,hpx,hxparams): """setup_hull: set up the upper and lower hull and everything that comes with that Input: domain - [.,.] upper and lower limit to the domain isDomainFinite - [.,.] is there a lower/upper limit to the domain? abcissae - initial list of abcissae (must lie on either side of the peak in hx if the domain is unbounded hx - function that evaluates h(x) hpx - function that evaluates hp(x) hxparams - tuple of parameters for h(x) and h'(x) Output: list with: [0]= c_u [1]= xs [2]= h(xs) [3]= hp(xs) [4]= zs [5]= s_cum [6]= hu(zi) History: 2009-05-21 - Written - Bovy (NYU) """ nx= len(abcissae) #Create the output arrays xs= sc.zeros(nx) hxs= sc.zeros(nx) hpxs= sc.zeros(nx) zs= sc.zeros(nx-1) scum= sc.zeros(nx-1) hus= sc.zeros(nx-1) #Function evaluations xs= sc.sort(abcissae) for ii in range(nx): hxs[ii]= hx(xs[ii],hxparams) hpxs[ii]= hpx(xs[ii],hxparams) #THERE IS NO CHECKING HERE TO SEE WHETHER IN THE INFINITE DOMAIN CASE #WE HAVE ABCISSAE ON BOTH SIDES OF THE PEAK #zi for jj in range(nx-1): zs[jj]= (hxs[jj+1]-hxs[jj]-xs[jj+1]*hpxs[jj+1]+xs[jj]*hpxs[jj])/( hpxs[jj]-hpxs[jj+1]) #hu for jj in range(nx-1): hus[jj]= hpxs[jj]*(zs[jj]-xs[jj])+hxs[jj] #Calculate cu and scum if isDomainFinite[0]: scum[0]= 1./hpxs[0]*(m.exp(hus[0])-m.exp( hpxs[0]*(domain[0]-xs[0])+hxs[0])) else: scum[0]= 1./hpxs[0]*m.exp(hus[0]) if nx > 2: for jj in range(nx-2): if hpxs[jj+1] == 0.: scum[jj+1]= (zs[jj+1]-zs[jj])*m.exp(hxs[jj+1]) else: scum[jj+1]=1./hpxs[jj+1]*(m.exp(hus[jj+1])-m.exp(hus[jj])) if isDomainFinite[1]: cu=1./hpxs[nx-1]*(m.exp(hpxs[nx-1]*( domain[1]-xs[nx-1])+hxs[nx-1]) - m.exp(hus[nx-2])) else: cu=- 1./hpxs[nx-1]*m.exp(hus[nx-2]) cu= cu+sc.sum(scum) scum= sc.cumsum(scum)/cu out=[] out.append(cu) out.append(xs) out.append(hxs) out.append(hpxs) out.append(zs) out.append(scum) out.append(hus) return out def sampleone(hull,hx,hpx,domain,isDomainFinite,maxn,nupdates,hxparams): """sampleone: sample one point by ars Input: hull - the hull (see doc of setup_hull for definition) hx - function that evaluates h(x) hpx - function that evaluates hp(x) domain - [.,.] upper and lower limit to the domain isDomainFinite - [.,.] is there a lower/upper limit to the domain? maxn - maximum number of updates to the hull nupdates - number of updates to the hull that have occured hxparams - tuple of parameters for h(x) and h'(x) Output: a sample a new hull nupdates History: 2009-05-21 - Written - Bovy (NYU) """ thishull= hull noSampleYet= True while noSampleYet: #Sample a candidate from the upper hull candidate= sample_hull(thishull,domain,isDomainFinite) thishux, thishlx= evaluate_hull(candidate,thishull) u= stats.uniform.rvs() if u < m.exp(thishlx-thishux): thissample= candidate noSampleYet= False else: thishx= hx(candidate,hxparams) if u < m.exp(thishx-thishux): thissample= candidate noSampleYet= False if nupdates < maxn: thishpx= hpx(candidate,hxparams) thishull= update_hull(thishull,candidate,thishx,thishpx, domain,isDomainFinite) nupdates= nupdates+1 return thissample, thishull, nupdates def sample_hull(hull,domain,isDomainFinite): """sample_hull: Sample the upper hull Input: hull - hull structure (see setup_hull for a definition of this) domain - [.,.] upper and lower limit to the domain isDomainFinite - [.,.] is there a lower/upper limit to the domain? Output: a sample from the hull History: 2009-05-21 - Written - Bovy """ u= stats.uniform.rvs() #Find largest zs[jj] such that scum[jj] < u #The first bin is a special case if hull[5][0] >= u: if hull[3][0] == 0: if isDomainFinite[0]: thissample= domain[0]+u/hull[5][0]*(hull[4][0]-domain[0]) else: thissample= 100000000 #Throw some kind of error else: thissample= hull[4][0]+1./hull[3][0]*m.log(1.-hull[3][0]*hull[0]*(hull[5][0]-u)/m.exp(hull[6][0])) else: if len(hull[5]) == 1: indx= 0 else: indx= 1 while indx < len(hull[5]) and hull[5][indx] < u: indx= indx+1 indx= indx-1 if m.fabs(hull[3][indx+1]) == 0: if indx != (len(hull[5])-1): thissample= hull[4][indx]+(u-hull[5][indx])/(hull[5][indx+1]-hull[5][indx])*(hull[4][indx+1]-hull[4][indx]) else: if isDomainFinite[1]: thissample= hull[4][indx]+(u-hull[5][indx])/(1.-hull[5][indx])*(domain[1]-hull[4][indx]) else: thissample= 100000 #Throw some kind of error else: thissample= hull[4][indx]+1./hull[3][indx+1]*m.log(1.+hull[3][indx+1]*hull[0]*(u-hull[5][indx])/m.exp(hull[6][indx])) return thissample def evaluate_hull(x,hull): """evaluate_hull: evaluate h_u(x) and (optional) h_l(x) Input: x - abcissa hull - the hull (see setup_hull for a definition) Output: hu(x) (optional), hl(x) History: 2009-05-21 - Written - Bovy (NYU) """ #Find in which [z_{i-1},z_i] interval x lies if x < hull[4][0]: #x lies in the first interval hux= hull[3][0]*(x-hull[1][0])+hull[2][0] indx= 0 else: if len(hull[5]) == 1: #There are only two intervals indx= 1 else: indx= 1 while indx < len(hull[4]) and hull[4][indx] < x: indx= indx+1 indx= indx-1 hux= hull[3][indx]*(x-hull[1][indx])+hull[2][indx] #Now evaluate hlx neginf= sc.finfo(sc.dtype(sc.float64)).min if x < hull[1][0] or x > hull[1][-1]: hlx= neginf else: if indx == 0: hlx= ((hull[1][1]-x)*hull[2][0]+(x-hull[1][0])*hull[2][1])/(hull[1][1]-hull[1][0]) elif indx == len(hull[4]): hlx= ((hull[1][-1]-x)*hull[2][-2]+(x-hull[1][-2])*hull[2][-1])/(hull[1][-1]-hull[1][-2]) elif x < hull[1][indx+1]: hlx= ((hull[1][indx+1]-x)*hull[2][indx]+(x-hull[1][indx])*hull[2][indx+1])/(hull[1][indx+1]-hull[1][indx]) else: hlx= ((hull[1][indx+2]-x)*hull[2][indx+1]+(x-hull[1][indx+1])*hull[2][indx+2])/(hull[1][indx+2]-hull[1][indx+1]) return hux, hlx def update_hull(hull,newx,newhx,newhpx,domain,isDomainFinite): """update_hull: update the hull with a new function evaluation Input: hull - the current hull (see setup_hull for a definition) newx - a new abcissa newhx - h(newx) newhpx - hp(newx) domain - [.,.] upper and lower limit to the domain isDomainFinite - [.,.] is there a lower/upper limit to the domain? Output: newhull History: 2009-05-21 - Written - Bovy (NYU) """ #BOVY: Perhaps add a check that newx is sufficiently far from any existing point #Find where newx fits in with the other xs if newx > hull[1][-1]: newxs= sc.append(hull[1],newx) newhxs= sc.append(hull[2],newhx) newhpxs= sc.append(hull[3],newhpx) #new z newz= ( newhx - hull[2][-1] - newx*newhpx + hull[1][-1]*hull[3][-1])/( hull[3][-1] - newhpx) newzs= sc.append(hull[4],newz) #New hu newhu= hull[3][-1]*(newz-hull[1][-1]) + hull[2][-1] newhus= sc.append(hull[6],newhu) else: indx= 0 while newx > hull[1][indx]: indx=indx+1 newxs= sc.insert(hull[1],indx,newx) newhxs= sc.insert(hull[2],indx,newhx) newhpxs= sc.insert(hull[3],indx,newhpx) #Replace old z with new zs if newx < hull[1][0]: newz= (hull[2][0]-newhx-hull[1][0]*hull[3][0]+newx*newhpx)/(newhpx-hull[3][0]) newzs= sc.insert(hull[4],0,newz) #Also add the new hu newhu= newhpx*(newz-newx)+newhx newhus= sc.insert(hull[6],0,newhu) else: newz1= (newhx-hull[2][indx-1] - newx*newhpx+hull[1][indx-1]*hull[3][indx-1])/(hull[3][indx-1]-newhpx) newz2= (hull[2][indx]-newhx - hull[1][indx]*hull[3][indx]+newx*newhpx)/(newhpx-hull[3][indx]) #Insert newz1 and replace z_old newzs= sc.insert(hull[4],indx-1,newz1) newzs[indx]= newz2 #Update the hus newhu1= hull[3][indx-1]*(newz1-hull[1][indx-1])+hull[2][indx-1] newhu2= newhpx*(newz2-newx)+newhx newhus= sc.insert(hull[6],indx-1,newhu1) newhus[indx]= newhu2 #Recalculate the cumulative sum nx= len(newxs) newscum= sc.zeros(nx-1) if isDomainFinite[0]: newscum[0]= 1./newhpxs[0]*(m.exp(newhus[0])-m.exp( newhpxs[0]*(domain[0]-newxs[0])+newhxs[0])) else: newscum[0]= 1./newhpxs[0]*m.exp(newhus[0]) if nx > 2: for jj in range(nx-2): if newhpxs[jj+1] == 0.: newscum[jj+1]= (newzs[jj+1]-newzs[jj])*m.exp(newhxs[jj+1]) else: newscum[jj+1]=1./newhpxs[jj+1]*(m.exp(newhus[jj+1])-m.exp(newhus[jj])) if isDomainFinite[1]: newcu=1./newhpxs[nx-1]*(m.exp(newhpxs[nx-1]*( domain[1]-newxs[nx-1])+newhxs[nx-1]) - m.exp(newhus[nx-2])) else: newcu=- 1./newhpxs[nx-1]*m.exp(newhus[nx-2]) newcu= newcu+sc.sum(newscum) newscum= sc.cumsum(newscum)/newcu newhull=[] newhull.append(newcu) newhull.append(newxs) newhull.append(newhxs) newhull.append(newhpxs) newhull.append(newzs) newhull.append(newscum) newhull.append(newhus) return newhull galpy-1.3.0/galpy/util/bovy_conversion.py0000644000076500000240000005736713236350147020646 0ustar bovystaff00000000000000############################################################################### # # bovy_conversion: utilities to convert from galpy 'natural units' to physical # units # ############################################################################### from functools import wraps import warnings import copy import math as m from galpy.util import galpyWarning from galpy.util.config import __config__ _APY_UNITS= __config__.getboolean('astropy','astropy-units') _APY_LOADED= True try: from astropy import units, constants except ImportError: _APY_UNITS= False _APY_LOADED= False _G= 4.302*10.**-3. #pc / Msolar (km/s)^2 _kmsInPcMyr= 1.0227121655399913 _PCIN10p18CM= 3.08567758 #10^18 cm _CIN10p5KMS= 2.99792458 #10^5 km/s _MSOLAR10p30KG= 1.9891 #10^30 kg _EVIN10m19J= 1.60217657 #10^-19 J else: _G= constants.G.to(units.pc/units.Msun*units.km**2/units.s**2).value _kmsInPcMyr= (units.km/units.s).to((units.pc/units.Myr)) _PCIN10p18CM= units.pc.to(units.cm)/10.**18. #10^18 cm _CIN10p5KMS= constants.c.to((units.km/units.s)).value/10.**5. #10^5 km/s _MSOLAR10p30KG= units.Msun.to(units.kg)/10.**30. #10^30 kg _EVIN10m19J= units.eV.to(units.J)*10.**19. #10^-19 J _MyrIn1013Sec= 3.65242198*0.24*3.6 #use tropical year, like for pms _TWOPI= 2.*m.pi def dens_in_criticaldens(vo,ro,H=70.): """ NAME: dens_in_criticaldens PURPOSE: convert density to units of the critical density INPUT: vo - velocity unit in km/s ro - length unit in kpc H= (default: 70) Hubble constant in km/s/Mpc OUTPUT: conversion from units where vo=1. at ro=1. to units of the critical density HISTORY: 2014-01-28 - Written - Bovy (IAS) """ return vo**2./ro**2.*10.**6./H**2.*8.*m.pi/3. def dens_in_meanmatterdens(vo,ro,H=70.,Om=0.3): """ NAME: dens_in_meanmatterdens PURPOSE: convert density to units of the mean matter density INPUT: vo - velocity unit in km/s ro - length unit in kpc H= (default: 70) Hubble constant in km/s/Mpc Om= (default: 0.3) Omega matter OUTPUT: conversion from units where vo=1. at ro=1. to units of the mean matter density HISTORY: 2014-01-28 - Written - Bovy (IAS) """ return dens_in_criticaldens(vo,ro,H=H)/Om def dens_in_gevcc(vo,ro): """ NAME: dens_in_gevcc PURPOSE: convert density to GeV / cm^3 INPUT: vo - velocity unit in km/s ro - length unit in kpc OUTPUT: conversion from units where vo=1. at ro=1. to GeV/cm^3 HISTORY: 2014-06-16 - Written - Bovy (IAS) """ return vo**2./ro**2./_G*_MSOLAR10p30KG*_CIN10p5KMS**2./_EVIN10m19J/ _PCIN10p18CM**3.*10.**-4. def dens_in_msolpc3(vo,ro): """ NAME: dens_in_msolpc3 PURPOSE: convert density to Msolar / pc^3 INPUT: vo - velocity unit in km/s ro - length unit in kpc OUTPUT: conversion from units where vo=1. at ro=1. to Msolar/pc^3 HISTORY: 2013-09-01 - Written - Bovy (IAS) """ return vo**2./ro**2./_G*10.**-6. def force_in_2piGmsolpc2(vo,ro): """ NAME: force_in_2piGmsolpc2 PURPOSE: convert a force or acceleration to 2piG x Msolar / pc^2 INPUT: vo - velocity unit in km/s ro - length unit in kpc OUTPUT: conversion from units where vo=1. at ro=1. HISTORY: 2013-09-01 - Written - Bovy (IAS) """ return vo**2./ro/_G*10.**-3./_TWOPI def force_in_pcMyr2(vo,ro): """ NAME: force_in_pcMyr2 PURPOSE: convert a force or acceleration to pc/Myr^2 INPUT: vo - velocity unit in km/s ro - length unit in kpc OUTPUT: conversion from units where vo=1. at ro=1. HISTORY: 2013-09-01 - Written - Bovy (IAS) """ return vo**2./ro*_kmsInPcMyr**2.*10.**-3. def force_in_kmsMyr(vo,ro): """ NAME: force_in_kmsMyr PURPOSE: convert a force or acceleration to km/s/Myr INPUT: vo - velocity unit in km/s ro - length unit in kpc OUTPUT: conversion from units where vo=1. at ro=1. HISTORY: 2013-09-01 - Written - Bovy (IAS) """ return vo**2./ro*_kmsInPcMyr*10.**-3. def force_in_10m13kms2(vo,ro): """ NAME: force_in_10m13kms2 PURPOSE: convert a force or acceleration to 10^(-13) km/s^2 INPUT: vo - velocity unit in km/s ro - length unit in kpc OUTPUT: conversion from units where vo=1. at ro=1. HISTORY: 2014-01-22 - Written - Bovy (IAS) """ return vo**2./ro*_kmsInPcMyr*10.**-3./_MyrIn1013Sec def freq_in_Gyr(vo,ro): """ NAME: freq_in_Gyr PURPOSE: convert a frequency to 1/Gyr INPUT: vo - velocity unit in km/s ro - length unit in kpc OUTPUT: conversion from units where vo=1. at ro=1. HISTORY: 2013-09-01 - Written - Bovy (IAS) """ return vo/ro*_kmsInPcMyr def freq_in_kmskpc(vo,ro): """ NAME: freq_in_kmskpc PURPOSE: convert a frequency to km/s/kpc INPUT: vo - velocity unit in km/s ro - length unit in kpc OUTPUT: conversion from units where vo=1. at ro=1. HISTORY: 2013-09-01 - Written - Bovy (IAS) """ return vo/ro def surfdens_in_msolpc2(vo,ro): """ NAME: surfdens_in_msolpc2 PURPOSE: convert a surface density to Msolar / pc^2 INPUT: vo - velocity unit in km/s ro - length unit in kpc OUTPUT: conversion from units where vo=1. at ro=1. HISTORY: 2013-09-01 - Written - Bovy (IAS) """ return vo**2./ro/_G*10.**-3. def mass_in_msol(vo,ro): """ NAME: mass_in_msol PURPOSE: convert a mass to Msolar INPUT: vo - velocity unit in km/s ro - length unit in kpc OUTPUT: conversion from units where vo=1. at ro=1. HISTORY: 2013-09-01 - Written - Bovy (IAS) """ return vo**2.*ro/_G*10.**3. def mass_in_1010msol(vo,ro): """ NAME: mass_in_1010msol PURPOSE: convert a mass to 10^10 x Msolar INPUT: vo - velocity unit in km/s ro - length unit in kpc OUTPUT: conversion from units where vo=1. at ro=1. HISTORY: 2013-09-01 - Written - Bovy (IAS) """ return vo**2.*ro/_G*10.**-7. def time_in_Gyr(vo,ro): """ NAME: time_in_Gyr PURPOSE: convert a time to Gyr INPUT: vo - velocity unit in km/s ro - length unit in kpc OUTPUT: conversion from units where vo=1. at ro=1. HISTORY: 2013-09-01 - Written - Bovy (IAS) """ return ro/vo/_kmsInPcMyr def velocity_in_kpcGyr(vo,ro): """ NAME: velocity_in_kpcGyr PURPOSE: convert a velocity to kpc/Gyr INPUT: vo - velocity unit in km/s ro - length unit in kpc OUTPUT: conversion from units where vo=1. at ro=1. HISTORY: 2014-12-19 - Written - Bovy (IAS) """ return vo*_kmsInPcMyr #Decorator to apply these transformations def print_physical_warning(): warnings.warn("The behavior of Orbit member functions has changed in versions > 0.1 to return positions in kpc, velocities in km/s, energies and the Jacobi integral in (km/s)^2, the angular momentum o.L() and actions in km/s kpc, frequencies in 1/Gyr, and times and periods in Gyr if a distance and velocity scale was specified upon Orbit initialization with ro=...,vo=...; you can turn this off by specifying use_physical=False when calling the function (e.g., o=Orbit(...); o.R(use_physical=False)", galpyWarning) # NOTE: names with underscores in them signify return values that *always* have # units, which is depended on in the Orbit returns (see issue #326) _roNecessary= {'time': True, 'position': True, 'position_kpc': True, 'velocity': False, 'velocity2': False, 'velocity2surfacendensity': False, 'velocity_kms': False, 'energy': False, 'density': True, 'numberdensity': True, 'force': True, 'velocity2surfacedensity': True, 'surfacedensity': True, 'numbersurfacedensity': True, 'surfacedensitydistance': True, 'mass': True, 'action': True, 'frequency':True, 'frequency-kmskpc':True, 'forcederivative':True, 'angle':True, 'angle_deg':True, 'proper-motion_masyr':True, 'phasespacedensity':True, 'phasespacedensity2d':True, 'phasespacedensityvelocity':True, 'phasespacedensityvelocity2':True, 'dimensionless':False} _voNecessary= copy.copy(_roNecessary) _voNecessary['position']= False _voNecessary['position_kpc']= False _voNecessary['angle']= False _voNecessary['angle_deg']= False _voNecessary['velocity']= True _voNecessary['velocity2']= True _voNecessary['velocity_kms']= True _voNecessary['energy']= True def physical_conversion(quantity,pop=False): """Decorator to convert to physical coordinates: quantity = [position,velocity,time]""" def wrapper(method): @wraps(method) def wrapped(*args,**kwargs): use_physical= kwargs.get('use_physical',True) and \ not kwargs.get('log',False) # Parse whether ro or vo should be considered to be set, because # the return value will have units anyway # (like in Orbit methods that return numbers with units, like ra) roSet= '_' in quantity # _ in quantity name means always units voSet= '_' in quantity # _ in quantity name means always units use_physical= use_physical or '_' in quantity # _ in quantity name means always units ro= kwargs.get('ro',None) if ro is None and \ (roSet or (hasattr(args[0],'_roSet') and args[0]._roSet)): ro= args[0]._ro if ro is None and isinstance(args[0],list) \ and hasattr(args[0][0],'_roSet') and args[0][0]._roSet: # For lists of Potentials ro= args[0][0]._ro if _APY_LOADED and isinstance(ro,units.Quantity): ro= ro.to(units.kpc).value vo= kwargs.get('vo',None) if vo is None and \ (voSet or (hasattr(args[0],'_voSet') and args[0]._voSet)): vo= args[0]._vo if vo is None and isinstance(args[0],list) \ and hasattr(args[0][0],'_voSet') and args[0][0]._voSet: # For lists of Potentials vo= args[0][0]._vo if _APY_LOADED and isinstance(vo,units.Quantity): vo= vo.to(units.km/units.s).value # Override Quantity output? _apy_units= kwargs.get('quantity',_APY_UNITS) #Remove ro and vo kwargs if necessary if pop and 'use_physical' in kwargs: kwargs.pop('use_physical') if pop and 'ro' in kwargs: kwargs.pop('ro') if pop and 'vo' in kwargs: kwargs.pop('vo') if use_physical and \ not (_voNecessary[quantity.lower()] and vo is None) and \ not (_roNecessary[quantity.lower()] and ro is None): from galpy.orbit import Orbit if isinstance(args[0],Orbit): print_physical_warning() if quantity.lower() == 'time': fac= time_in_Gyr(vo,ro) if _apy_units: u= units.Gyr elif quantity.lower() == 'position': fac= ro if _apy_units: u= units.kpc elif quantity.lower() == 'position_kpc': # already in kpc fac= 1. if _apy_units: u= units.kpc elif quantity.lower() == 'velocity': fac= vo if _apy_units: u= units.km/units.s elif quantity.lower() == 'velocity2': fac= vo**2. if _apy_units: u= (units.km/units.s)**2 elif quantity.lower() == 'velocity_kms': # already in km/s fac= 1. if _apy_units: u= units.km/units.s elif quantity.lower() == 'frequency': if kwargs.get('kmskpc',False) and not _apy_units: fac= freq_in_kmskpc(vo,ro) else: fac= freq_in_Gyr(vo,ro) if _apy_units: u= units.Gyr**-1. elif quantity.lower() == 'frequency-kmskpc': fac= freq_in_kmskpc(vo,ro) if _apy_units: u= units.km/units.s/units.kpc elif quantity.lower() == 'action': fac= ro*vo if _apy_units: u= units.kpc*units.km/units.s elif quantity.lower() == 'energy': fac= vo**2. if _apy_units: u= units.km**2./units.s**2. elif quantity.lower() == 'angle': # in rad fac= 1. if _apy_units: u= units.rad elif quantity.lower() == 'angle_deg': # already in deg fac= 1. if _apy_units: u= units.deg elif quantity.lower() == 'proper-motion_masyr': # already in mas/yr fac= 1. if _apy_units: u= units.mas/units.yr elif quantity.lower() == 'force': fac= force_in_kmsMyr(vo,ro) if _apy_units: u= units.km/units.s/units.Myr elif quantity.lower() == 'density': fac= dens_in_msolpc3(vo,ro) if _apy_units: u= units.Msun/units.pc**3 elif quantity.lower() == 'numberdensity': fac= 1/ro**3. if _apy_units: u= 1/units.kpc**3 elif quantity.lower() == 'velocity2surfacedensity': fac= surfdens_in_msolpc2(vo,ro)*vo**2 if _apy_units: u= units.Msun/units.pc**2*(units.km/units.s)**2 elif quantity.lower() == 'surfacedensity': fac= surfdens_in_msolpc2(vo,ro) if _apy_units: u= units.Msun/units.pc**2 elif quantity.lower() == 'numbersurfacedensity': fac= 1./ro**2. if _apy_units: u= 1/units.kpc**2 elif quantity.lower() == 'surfacedensitydistance': fac= surfdens_in_msolpc2(vo,ro)*ro*1000. if _apy_units: u= units.Msun/units.pc elif quantity.lower() == 'mass': fac= mass_in_msol(vo,ro) if _apy_units: u= units.Msun elif quantity.lower() == 'forcederivative': fac= freq_in_Gyr(vo,ro)**2. if _apy_units: u= units.Gyr**-2. elif quantity.lower() == 'phasespacedensity': fac= 1./vo**3./ro**3. if _apy_units: u= 1/(units.km/units.s)**3/units.kpc**3 elif quantity.lower() == 'phasespacedensity2d': fac= 1./vo**2./ro**2. if _apy_units: u= 1/(units.km/units.s)**2/units.kpc**2 elif quantity.lower() == 'phasespacedensityvelocity': fac= 1./vo**2./ro**3. if _apy_units: u= 1/(units.km/units.s)**2/units.kpc**3 elif quantity.lower() == 'phasespacedensityvelocity2': fac= 1./vo/ro**3. if _apy_units: u= 1/(units.km/units.s)/units.kpc**3 elif quantity.lower() == 'dimensionless': fac= 1. if _apy_units: u= units.dimensionless_unscaled out= method(*args,**kwargs) if out is None: return out if _apy_units: return units.Quantity(out*fac,unit=u) else: return out*fac else: return method(*args,**kwargs) return wrapped return wrapper def potential_physical_input(method): """Decorator to convert inputs to Potential functions from physical to internal coordinates""" @wraps(method) def wrapper(*args,**kwargs): ro= kwargs.get('ro',None) if ro is None and hasattr(args[0],'_ro'): ro= args[0]._ro if ro is None and isinstance(args[0],list) \ and hasattr(args[0][0],'_ro'): # For lists of Potentials ro= args[0][0]._ro if _APY_LOADED and isinstance(ro,units.Quantity): ro= ro.to(units.kpc).value if 't' in kwargs: vo= kwargs.get('vo',None) if vo is None and hasattr(args[0],'_vo'): vo= args[0]._vo if vo is None and isinstance(args[0],list) \ and hasattr(args[0][0],'_vo'): # For lists of Potentials vo= args[0][0]._vo if _APY_LOADED and isinstance(vo,units.Quantity): vo= vo.to(units.km/units.s).value # Loop through args newargs= () for ii in range(len(args)): if _APY_LOADED and isinstance(args[ii],units.Quantity): newargs= newargs+(args[ii].to(units.kpc).value/ro,) else: newargs= newargs+(args[ii],) args= newargs # phi and t kwargs if 'phi' in kwargs and _APY_LOADED \ and isinstance(kwargs['phi'],units.Quantity): kwargs['phi']= kwargs['phi'].to(units.rad).value if 't' in kwargs and _APY_LOADED \ and isinstance(kwargs['t'],units.Quantity): kwargs['t']= kwargs['t'].to(units.Gyr).value\ /time_in_Gyr(vo,ro) # kwargs that come up in quasiisothermaldf if 'z' in kwargs and _APY_LOADED \ and isinstance(kwargs['z'],units.Quantity): kwargs['z']= kwargs['z'].to(units.kpc).value/ro if 'dz' in kwargs and _APY_LOADED \ and isinstance(kwargs['dz'],units.Quantity): kwargs['dz']= kwargs['dz'].to(units.kpc).value/ro if 'dR' in kwargs and _APY_LOADED \ and isinstance(kwargs['dR'],units.Quantity): kwargs['dR']= kwargs['dR'].to(units.kpc).value/ro if 'zmax' in kwargs and _APY_LOADED \ and isinstance(kwargs['zmax'],units.Quantity): kwargs['zmax']= kwargs['zmax'].to(units.kpc).value/ro return method(*args,**kwargs) return wrapper def physical_conversion_actionAngle(quantity,pop=False): """Decorator to convert to physical coordinates for the actionAngle methods: quantity= call, actionsFreqs, or actionsFreqsAngles (or EccZmaxRperiRap for actionAngleStaeckel)""" def wrapper(method): @wraps(method) def wrapped(*args,**kwargs): use_physical= kwargs.get('use_physical',True) ro= kwargs.get('ro',None) if ro is None and hasattr(args[0],'_roSet') and args[0]._roSet: ro= args[0]._ro if _APY_LOADED and isinstance(ro,units.Quantity): ro= ro.to(units.kpc).value vo= kwargs.get('vo',None) if vo is None and hasattr(args[0],'_voSet') and args[0]._voSet: vo= args[0]._vo if _APY_LOADED and isinstance(vo,units.Quantity): vo= vo.to(units.km/units.s).value #Remove ro and vo kwargs if necessary if pop and 'use_physical' in kwargs: kwargs.pop('use_physical') if pop and 'ro' in kwargs: kwargs.pop('ro') if pop and 'vo' in kwargs: kwargs.pop('vo') if use_physical and not vo is None and not ro is None: if 'call' in quantity or 'actions' in quantity: fac= [ro*vo,ro*vo,ro*vo] if _APY_UNITS: u= [units.kpc*units.km/units.s, units.kpc*units.km/units.s, units.kpc*units.km/units.s] if 'Freqs' in quantity: FreqsFac= freq_in_Gyr(vo,ro) fac.extend([FreqsFac,FreqsFac,FreqsFac]) if _APY_UNITS: Freqsu= units.Gyr**-1. u.extend([Freqsu,Freqsu,Freqsu]) if 'Angles' in quantity: fac.extend([1.,1.,1.]) if _APY_UNITS: Freqsu= units.Gyr**-1. u.extend([units.rad,units.rad,units.rad]) if 'EccZmaxRperiRap' in quantity: fac= [1.,ro,ro,ro] if _APY_UNITS: u= [1., units.kpc, units.kpc, units.kpc] out= method(*args,**kwargs) if _APY_UNITS: newOut= () for ii in range(len(out)): newOut= newOut+(units.Quantity(out[ii]*fac[ii], unit=u[ii]),) else: newOut= () for ii in range(len(out)): newOut= newOut+(out[ii]*fac[ii],) return newOut else: return method(*args,**kwargs) return wrapped return wrapper def actionAngle_physical_input(method): """Decorator to convert inputs to actionAngle functions from physical to internal coordinates""" @wraps(method) def wrapper(*args,**kwargs): if len(args) < 3: # orbit input return method(*args,**kwargs) ro= kwargs.get('ro',None) if ro is None and hasattr(args[0],'_ro'): ro= args[0]._ro if _APY_LOADED and isinstance(ro,units.Quantity): ro= ro.to(units.kpc).value vo= kwargs.get('vo',None) if vo is None and hasattr(args[0],'_vo'): vo= args[0]._vo if _APY_LOADED and isinstance(vo,units.Quantity): vo= vo.to(units.km/units.s).value # Loop through args newargs= () for ii in range(len(args)): if _APY_LOADED and isinstance(args[ii],units.Quantity): try: targ= args[ii].to(units.kpc).value/ro except units.UnitConversionError: try: targ= args[ii].to(units.km/units.s).value/vo except units.UnitConversionError: try: targ= args[ii].to(units.rad).value except units.UnitConversionError: raise units.UnitConversionError("Input units not understood") newargs= newargs+(targ,) else: newargs= newargs+(args[ii],) args= newargs return method(*args,**kwargs) return wrapper galpy-1.3.0/galpy/util/bovy_coords.py0000644000076500000240000016637613236414472017755 0ustar bovystaff00000000000000############################################################################### # # bovy_coords: module for coordinate transformations between the equatorial # and Galactic coordinate frame # # # Main included functions: # radec_to_lb # lb_to_radec # lbd_to_XYZ # XYZ_to_lbd # rectgal_to_sphergal # sphergal_to_rectgal # vrpmllpmbb_to_vxvyvz # vxvyvz_to_vrpmllpmbb # pmrapmdec_to_pmllpmbb # pmllpmbb_to_pmrapmdec # cov_pmrapmdec_to_pmllpmbb # cov_dvrpmllbb_to_vxyz # XYZ_to_galcenrect # XYZ_to_galcencyl # galcenrect_to_XYZ # galcencyl_to_XYZ # rect_to_cyl # cyl_to_rect # rect_to_cyl_vec # cyl_to_rect_vec # vxvyvz_to_galcenrect # vxvyvz_to_galcencyl # galcenrect_to_vxvyvz # galcencyl_to_vxvyvz # dl_to_rphi_2d # rphi_to_dl_2d # Rz_to_coshucosv # Rz_to_uv # uv_to_Rz # Rz_to_lambdanu # Rz_to_lambdanu_jac # Rz_to_lambdanu_hess # lambdanu_to_Rz # ############################################################################## ############################################################################# #Copyright (c) 2010 - 2016, Jo Bovy #All rights reserved. # #Redistribution and use in source and binary forms, with or without #modification, are permitted provided that the following conditions are met: # # Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # Redistributions in binary form must reproduce the above copyright notice, # this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # The name of the author may not be used to endorse or promote products # derived from this software without specific prior written permission. # #THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS #"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT #LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR #A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT #HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, #INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, #BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS #OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED #AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT #LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY #WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE #POSSIBILITY OF SUCH DAMAGE. ############################################################################# from functools import wraps import math as m import numpy as nu import scipy as sc from galpy.util.config import __config__ _APY_COORDS= __config__.getboolean('astropy','astropy-coords') _APY_LOADED= True try: import astropy.coordinates as apycoords from astropy import units except ImportError: _APY_LOADED= False _APY_COORDS*= _APY_LOADED _DEGTORAD= m.pi/180. _K=4.74047 # numpy 1.14 einsum bug causes astropy conversions to fail in py2.7 -> turn off if _APY_COORDS: ra, dec= nu.array([192.25*_DEGTORAD]), nu.array([27.4*_DEGTORAD]) c= apycoords.SkyCoord(ra*units.rad,dec*units.rad, equinox='B1950',frame='fk4') # This conversion fails bc of einsum bug try: c= c.transform_to(apycoords.Galactic) except TypeError: _APY_COORDS= False def scalarDecorator(func): """Decorator to return scalar outputs as a set""" @wraps(func) def scalar_wrapper(*args,**kwargs): if nu.array(args[0]).shape == (): scalarOut= True newargs= () for ii in range(len(args)): newargs= newargs+(nu.array([args[ii]]),) args= newargs else: scalarOut= False result= func(*args,**kwargs) if scalarOut: out= () for ii in range(result.shape[1]): out= out+(result[0,ii],) return out else: return result return scalar_wrapper def degreeDecorator(inDegrees,outDegrees): """Decorator to transform angles from and to degrees if necessary""" def wrapper(func): @wraps(func) def wrapped(*args,**kwargs): if kwargs.get('degree',False): newargs= () for ii in range(len(args)): if ii in inDegrees: newargs= newargs+(args[ii]*nu.pi/180.,) else: newargs= newargs+(args[ii],) args= newargs out= func(*args,**kwargs) if kwargs.get('degree',False): for indx in outDegrees: out[:,indx]/= nu.pi/180. return out return wrapped return wrapper @scalarDecorator @degreeDecorator([0,1],[0,1]) def radec_to_lb(ra,dec,degree=False,epoch=2000.0): """ NAME: radec_to_lb PURPOSE: transform from equatorial coordinates to Galactic coordinates INPUT: ra - right ascension dec - declination degree - (Bool) if True, ra and dec are given in degree and l and b will be as well epoch - epoch of ra,dec (right now only 2000.0 and 1950.0 are supported when not using astropy's transformations internally; when internally using astropy's coordinate transformations, epoch can be None for ICRS, 'JXXXX' for FK5, and 'BXXXX' for FK4) OUTPUT: l,b For vector inputs [:,2] HISTORY: 2009-11-12 - Written - Bovy (NYU) 2014-06-14 - Re-written w/ numpy functions for speed and w/ decorators for beauty - Bovy (IAS) 2016-05-13 - Added support for using astropy's coordinate transformations and for non-standard epochs - Bovy (UofT) """ if _APY_COORDS: epoch, frame= _parse_epoch_frame_apy(epoch) c= apycoords.SkyCoord(ra*units.rad,dec*units.rad, equinox=epoch,frame=frame) c= c.transform_to(apycoords.Galactic) return nu.array([c.l.to(units.rad).value,c.b.to(units.rad).value]).T #First calculate the transformation matrix T theta,dec_ngp,ra_ngp= get_epoch_angles(epoch) T= sc.dot(sc.array([[sc.cos(theta),sc.sin(theta),0.],[sc.sin(theta),-sc.cos(theta),0.],[0.,0.,1.]]),sc.dot(sc.array([[-sc.sin(dec_ngp),0.,sc.cos(dec_ngp)],[0.,1.,0.],[sc.cos(dec_ngp),0.,sc.sin(dec_ngp)]]),sc.array([[sc.cos(ra_ngp),sc.sin(ra_ngp),0.],[-sc.sin(ra_ngp),sc.cos(ra_ngp),0.],[0.,0.,1.]]))) #Whether to use degrees and scalar input is handled by decorators XYZ= nu.array([nu.cos(dec)*nu.cos(ra), nu.cos(dec)*nu.sin(ra), nu.sin(dec)]) galXYZ= nu.dot(T,XYZ) b= nu.arcsin(galXYZ[2]) l= nu.arctan2(galXYZ[1]/sc.cos(b),galXYZ[0]/sc.cos(b)) l[l<0.]+= 2.*nu.pi out= nu.array([l,b]) return out.T @scalarDecorator @degreeDecorator([0,1],[0,1]) def lb_to_radec(l,b,degree=False,epoch=2000.0): """ NAME: lb_to_radec PURPOSE: transform from Galactic coordinates to equatorial coordinates INPUT: l - Galactic longitude b - Galactic lattitude degree - (Bool) if True, l and b are given in degree and ra and dec will be as well epoch - epoch of ra,dec (right now only 2000.0 and 1950.0 are supported when not using astropy's transformations internally; when internally using astropy's coordinate transformations, epoch can be None for ICRS, 'JXXXX' for FK5, and 'BXXXX' for FK4) OUTPUT: ra,dec For vector inputs [:,2] HISTORY: 2010-04-07 - Written - Bovy (NYU) 2014-06-14 - Re-written w/ numpy functions for speed and w/ decorators for beauty - Bovy (IAS) 2016-05-13 - Added support for using astropy's coordinate transformations and for non-standard epochs - Bovy (UofT) """ if _APY_COORDS: epoch, frame= _parse_epoch_frame_apy(epoch) c= apycoords.SkyCoord(l*units.rad,b*units.rad,frame='galactic') if not epoch is None and 'J' in epoch: c= c.transform_to(apycoords.FK5(equinox=epoch)) elif not epoch is None and 'B' in epoch: c= c.transform_to(apycoords.FK4(equinox=epoch)) else: c= c.transform_to(apycoords.ICRS) return nu.array([c.ra.to(units.rad).value,c.dec.to(units.rad).value]).T #First calculate the transformation matrix T' theta,dec_ngp,ra_ngp= get_epoch_angles(epoch) T= sc.dot(sc.array([[sc.cos(ra_ngp),-sc.sin(ra_ngp),0.],[sc.sin(ra_ngp),sc.cos(ra_ngp),0.],[0.,0.,1.]]),sc.dot(sc.array([[-sc.sin(dec_ngp),0.,sc.cos(dec_ngp)],[0.,1.,0.],[sc.cos(dec_ngp),0.,sc.sin(dec_ngp)]]),sc.array([[sc.cos(theta),sc.sin(theta),0.],[sc.sin(theta),-sc.cos(theta),0.],[0.,0.,1.]]))) #Whether to use degrees and scalar input is handled by decorators XYZ= nu.array([nu.cos(b)*nu.cos(l), nu.cos(b)*nu.sin(l), nu.sin(b)]) eqXYZ= nu.dot(T,XYZ) dec= nu.arcsin(eqXYZ[2]) ra= nu.arctan2(eqXYZ[1],eqXYZ[0]) ra[ra<0.]+= 2.*nu.pi return nu.array([ra,dec]).T @scalarDecorator @degreeDecorator([0,1],[]) def lbd_to_XYZ(l,b,d,degree=False): """ NAME: lbd_to_XYZ PURPOSE: transform from spherical Galactic coordinates to rectangular Galactic coordinates (works with vector inputs) INPUT: l - Galactic longitude (rad) b - Galactic lattitude (rad) d - distance (arbitrary units) degree - (bool) if True, l and b are in degrees OUTPUT: [X,Y,Z] in whatever units d was in For vector inputs [:,3] HISTORY: 2009-10-24- Written - Bovy (NYU) 2014-06-14 - Re-written w/ numpy functions for speed and w/ decorators for beauty - Bovy (IAS) """ #Whether to use degrees and scalar input is handled by decorators return nu.array([d*nu.cos(b)*nu.cos(l), d*nu.cos(b)*nu.sin(l), d*nu.sin(b)]).T def rectgal_to_sphergal(X,Y,Z,vx,vy,vz,degree=False): """ NAME: rectgal_to_sphergal PURPOSE: transform phase-space coordinates in rectangular Galactic coordinates to spherical Galactic coordinates (can take vector inputs) INPUT: X - component towards the Galactic Center (kpc) Y - component in the direction of Galactic rotation (kpc) Z - component towards the North Galactic Pole (kpc) vx - velocity towards the Galactic Center (km/s) vy - velocity in the direction of Galactic rotation (km/s) vz - velocity towards the North Galactic Pole (km/s) degree - (Bool) if True, return l and b in degrees OUTPUT: (l,b,d,vr,pmll x cos(b),pmbb) in (rad,rad,kpc,km/s,mas/yr,mas/yr) HISTORY: 2009-10-25 - Written - Bovy (NYU) """ lbd= XYZ_to_lbd(X,Y,Z,degree=degree) vrpmllpmbb= vxvyvz_to_vrpmllpmbb(vx,vy,vz,X,Y,Z,XYZ=True) if sc.array(X).shape == (): return sc.array([lbd[0],lbd[1],lbd[2],vrpmllpmbb[0],vrpmllpmbb[1],vrpmllpmbb[2]]) else: out=sc.zeros((len(X),6)) out[:,0:3]= lbd out[:,3:6]= vrpmllpmbb return out def sphergal_to_rectgal(l,b,d,vr,pmll,pmbb,degree=False): """ NAME: sphergal_to_rectgal PURPOSE: transform phase-space coordinates in spherical Galactic coordinates to rectangular Galactic coordinates (can take vector inputs) INPUT: l - Galactic longitude (rad) b - Galactic lattitude (rad) d - distance (kpc) vr - line-of-sight velocity (km/s) pmll - proper motion in the Galactic longitude direction (mu_l*cos(b) ) (mas/yr) pmbb - proper motion in the Galactic lattitude (mas/yr) degree - (bool) if True, l and b are in degrees OUTPUT: (X,Y,Z,vx,vy,vz) in (kpc,kpc,kpc,km/s,km/s,km/s) HISTORY: 2009-10-25 - Written - Bovy (NYU) """ XYZ= lbd_to_XYZ(l,b,d,degree=degree) vxvyvz= vrpmllpmbb_to_vxvyvz(vr,pmll,pmbb,l,b,d,XYZ=False,degree=degree) if sc.array(l).shape == (): return sc.array([XYZ[0],XYZ[1],XYZ[2],vxvyvz[0],vxvyvz[1],vxvyvz[2]]) else: out=sc.zeros((len(l),6)) out[:,0:3]= XYZ out[:,3:6]= vxvyvz return out @scalarDecorator @degreeDecorator([3,4],[]) def vrpmllpmbb_to_vxvyvz(vr,pmll,pmbb,l,b,d,XYZ=False,degree=False): """ NAME: vrpmllpmbb_to_vxvyvz PURPOSE: Transform velocities in the spherical Galactic coordinate frame to the rectangular Galactic coordinate frame (can take vector inputs) INPUT: vr - line-of-sight velocity (km/s) pmll - proper motion in the Galactic longitude (mu_l * cos(b))(mas/yr) pmbb - proper motion in the Galactic lattitude (mas/yr) l - Galactic longitude b - Galactic lattitude d - distance (kpc) XYZ - (bool) If True, then l,b,d is actually X,Y,Z (rectangular Galactic coordinates) degree - (bool) if True, l and b are in degrees OUTPUT: (vx,vy,vz) in (km/s,km/s,km/s) For vector inputs [:,3] HISTORY: 2009-10-24 - Written - Bovy (NYU) 2014-06-14 - Re-written w/ numpy functions for speed and w/ decorators for beauty - Bovy (IAS) """ #Whether to use degrees and scalar input is handled by decorators if XYZ: #undo the incorrect conversion that the decorator did if degree: l*= 180./nu.pi b*= 180./nu.pi lbd= XYZ_to_lbd(l,b,d,degree=False) l= lbd[:,0] b= lbd[:,1] d= lbd[:,2] R=nu.zeros((3,3,len(l))) R[0,0]= nu.cos(l)*nu.cos(b) R[1,0]= -nu.sin(l) R[2,0]= -nu.cos(l)*nu.sin(b) R[0,1]= nu.sin(l)*nu.cos(b) R[1,1]= nu.cos(l) R[2,1]= -nu.sin(l)*nu.sin(b) R[0,2]= nu.sin(b) R[2,2]= nu.cos(b) invr= nu.array([[vr,vr,vr], [d*pmll*_K,d*pmll*_K,d*pmll*_K], [d*pmbb*_K,d*pmbb*_K,d*pmbb*_K]]) return (R.T*invr.T).sum(-1) @scalarDecorator @degreeDecorator([3,4],[]) def vxvyvz_to_vrpmllpmbb(vx,vy,vz,l,b,d,XYZ=False,degree=False): """ NAME: vxvyvz_to_vrpmllpmbb PURPOSE: Transform velocities in the rectangular Galactic coordinate frame to the spherical Galactic coordinate frame (can take vector inputs) INPUT: vx - velocity towards the Galactic Center (km/s) vy - velocity in the direction of Galactic rotation (km/s) vz - velocity towards the North Galactic Pole (km/s) l - Galactic longitude b - Galactic lattitude d - distance (kpc) XYZ - (bool) If True, then l,b,d is actually X,Y,Z (rectangular Galactic coordinates) degree - (bool) if True, l and b are in degrees OUTPUT: (vr,pmll x cos(b),pmbb) in (km/s,mas/yr,mas/yr); pmll = mu_l * cos(b) For vector inputs [:,3] HISTORY: 2009-10-24 - Written - Bovy (NYU) 2014-06-14 - Re-written w/ numpy functions for speed and w/ decorators for beauty - Bovy (IAS) """ #Whether to use degrees and scalar input is handled by decorators if XYZ: #undo the incorrect conversion that the decorator did if degree: l*= 180./nu.pi b*= 180./nu.pi lbd= XYZ_to_lbd(l,b,d,degree=False) l= lbd[:,0] b= lbd[:,1] d= lbd[:,2] R=nu.zeros((3,3,len(l))) R[0,0]= nu.cos(l)*nu.cos(b) R[0,1]= -nu.sin(l) R[0,2]= -nu.cos(l)*nu.sin(b) R[1,0]= nu.sin(l)*nu.cos(b) R[1,1]= nu.cos(l) R[1,2]= -nu.sin(l)*nu.sin(b) R[2,0]= nu.sin(b) R[2,2]= nu.cos(b) invxyz= nu.array([[vx,vx,vx], [vy,vy,vy], [vz,vz,vz]]) vrvlvb= (R.T*invxyz.T).sum(-1) vrvlvb[:,1]/= d*_K vrvlvb[:,2]/= d*_K return vrvlvb @scalarDecorator @degreeDecorator([],[0,1]) def XYZ_to_lbd(X,Y,Z,degree=False): """ NAME: XYZ_to_lbd PURPOSE: transform from rectangular Galactic coordinates to spherical Galactic coordinates (works with vector inputs) INPUT: X - component towards the Galactic Center (in kpc; though this obviously does not matter)) Y - component in the direction of Galactic rotation (in kpc) Z - component towards the North Galactic Pole (kpc) degree - (Bool) if True, return l and b in degrees OUTPUT: [l,b,d] in (rad or degree,rad or degree,kpc) For vector inputs [:,3] HISTORY: 2009-10-24 - Written - Bovy (NYU) 2014-06-14 - Re-written w/ numpy functions for speed and w/ decorators for beauty - Bovy (IAS) """ #Whether to use degrees and scalar input is handled by decorators d= nu.sqrt(X**2.+Y**2.+Z**2.) b=nu.arcsin(Z/d) cosl= X/d/nu.cos(b) sinl= Y/d/nu.cos(b) l= nu.arcsin(sinl) l[cosl < 0.]= nu.pi-l[cosl < 0.] l[(cosl >= 0.)*(sinl < 0.)]+= 2.*nu.pi out= nu.empty((len(l),3)) out[:,0]= l out[:,1]= b out[:,2]= d return out @scalarDecorator @degreeDecorator([2,3],[]) def pmrapmdec_to_pmllpmbb(pmra,pmdec,ra,dec,degree=False,epoch=2000.0): """ NAME: pmrapmdec_to_pmllpmbb PURPOSE: rotate proper motions in (ra,dec) into proper motions in (l,b) INPUT: pmra - proper motion in ra (multplied with cos(dec)) [mas/yr] pmdec - proper motion in dec [mas/yr] ra - right ascension dec - declination degree - if True, ra and dec are given in degrees (default=False) epoch - epoch of ra,dec (right now only 2000.0 and 1950.0 are supported when not using astropy's transformations internally; when internally using astropy's coordinate transformations, epoch can be None for ICRS, 'JXXXX' for FK5, and 'BXXXX' for FK4) OUTPUT: (pmll x cos(b),pmbb) for vector inputs [:,2] HISTORY: 2010-04-07 - Written - Bovy (NYU) 2014-06-14 - Re-written w/ numpy functions for speed and w/ decorators for beauty - Bovy (IAS) """ theta,dec_ngp,ra_ngp= get_epoch_angles(epoch) #Whether to use degrees and scalar input is handled by decorators dec[dec == dec_ngp]+= 10.**-16 #deal w/ pole. sindec_ngp= nu.sin(dec_ngp) cosdec_ngp= nu.cos(dec_ngp) sindec= nu.sin(dec) cosdec= nu.cos(dec) sinrarangp= nu.sin(ra-ra_ngp) cosrarangp= nu.cos(ra-ra_ngp) #These were replaced by Poleski (2013)'s equivalent form that is better at the poles #cosphi= (sindec_ngp-sindec*sinb)/cosdec/cosb #sinphi= sinrarangp*cosdec_ngp/cosb cosphi= sindec_ngp*cosdec-cosdec_ngp*sindec*cosrarangp sinphi= sinrarangp*cosdec_ngp norm= nu.sqrt(cosphi**2.+sinphi**2.) cosphi/= norm sinphi/= norm return (nu.array([[cosphi,-sinphi],[sinphi,cosphi]]).T\ *nu.array([[pmra,pmra],[pmdec,pmdec]]).T).sum(-1) @scalarDecorator @degreeDecorator([2,3],[]) def pmllpmbb_to_pmrapmdec(pmll,pmbb,l,b,degree=False,epoch=2000.0): """ NAME: pmllpmbb_to_pmrapmdec PURPOSE: rotate proper motions in (l,b) into proper motions in (ra,dec) INPUT: pmll - proper motion in l (multplied with cos(b)) [mas/yr] pmbb - proper motion in b [mas/yr] l - Galactic longitude b - Galactic lattitude degree - if True, l and b are given in degrees (default=False) epoch - epoch of ra,dec (right now only 2000.0 and 1950.0 are supported when not using astropy's transformations internally; when internally using astropy's coordinate transformations, epoch can be None for ICRS, 'JXXXX' for FK5, and 'BXXXX' for FK4) OUTPUT: (pmra x cos(dec),pmdec), for vector inputs [:,2] HISTORY: 2010-04-07 - Written - Bovy (NYU) 2014-06-14 - Re-written w/ numpy functions for speed and w/ decorators for beauty - Bovy (IAS) """ theta,dec_ngp,ra_ngp= get_epoch_angles(epoch) #Whether to use degrees and scalar input is handled by decorators radec = lb_to_radec(l,b,degree=False,epoch=epoch) ra= radec[:,0] dec= radec[:,1] dec[dec == dec_ngp]+= 10.**-16 #deal w/ pole. sindec_ngp= nu.sin(dec_ngp) cosdec_ngp= nu.cos(dec_ngp) sindec= nu.sin(dec) cosdec= nu.cos(dec) sinrarangp= nu.sin(ra-ra_ngp) cosrarangp= nu.cos(ra-ra_ngp) #These were replaced by Poleski (2013)'s equivalent form that is better at the poles #cosphi= (sindec_ngp-sindec*sinb)/cosdec/cosb #sinphi= sinrarangp*cosdec_ngp/cosb cosphi= sindec_ngp*cosdec-cosdec_ngp*sindec*cosrarangp sinphi= sinrarangp*cosdec_ngp norm= nu.sqrt(cosphi**2.+sinphi**2.) cosphi/= norm sinphi/= norm return (nu.array([[cosphi,sinphi],[-sinphi,cosphi]]).T\ *nu.array([[pmll,pmll],[pmbb,pmbb]]).T).sum(-1) def cov_pmrapmdec_to_pmllpmbb(cov_pmradec,ra,dec,degree=False,epoch=2000.0): """ NAME: cov_pmrapmdec_to_pmllpmbb PURPOSE: propagate the proper motions errors through the rotation from (ra,dec) to (l,b) INPUT: covar_pmradec - uncertainty covariance matrix of the proper motion in ra (multplied with cos(dec)) and dec [2,2] or [:,2,2] ra - right ascension dec - declination degree - if True, ra and dec are given in degrees (default=False) epoch - epoch of ra,dec (right now only 2000.0 and 1950.0 are supported when not using astropy's transformations internally; when internally using astropy's coordinate transformations, epoch can be None for ICRS, 'JXXXX' for FK5, and 'BXXXX' for FK4) OUTPUT: covar_pmllbb [2,2] or [:,2,2] [pmll here is pmll x cos(b)] HISTORY: 2010-04-12 - Written - Bovy (NYU) """ if len(cov_pmradec.shape) == 3: out= sc.zeros(cov_pmradec.shape) ndata= out.shape[0] lb = radec_to_lb(ra,dec,degree=degree,epoch=epoch) for ii in range(ndata): out[ii,:,:]= cov_pmradec_to_pmllbb_single(cov_pmradec[ii,:,:], ra[ii],dec[ii],lb[ii,1], degree,epoch) return out else: l,b = radec_to_lb(ra,dec,degree=degree,epoch=epoch) return cov_pmradec_to_pmllbb_single(cov_pmradec,ra,dec,b,degree,epoch) def cov_pmradec_to_pmllbb_single(cov_pmradec,ra,dec,b,degree=False,epoch=2000.0): """ NAME: cov_pmradec_to_pmllbb_single PURPOSE: propagate the proper motions errors through the rotation from (ra,dec) to (l,b) for scalar inputs INPUT: covar_pmradec - uncertainty covariance matrix of the proper motion in ra (multplied with cos(dec)) and dec [2,2] or [:,2,2] ra - right ascension dec - declination degree - if True, ra and dec are given in degrees (default=False) epoch - epoch of ra,dec (right now only 2000.0 and 1950.0 are supported when not using astropy's transformations internally; when internally using astropy's coordinate transformations, epoch can be None for ICRS, 'JXXXX' for FK5, and 'BXXXX' for FK4) OUTPUT: cov_pmllbb HISTORY: 2010-04-12 - Written - Bovy (NYU) """ theta,dec_ngp,ra_ngp= get_epoch_angles(epoch) if degree: sindec_ngp= m.sin(dec_ngp) cosdec_ngp= m.cos(dec_ngp) sindec= m.sin(dec*_DEGTORAD) cosdec= m.cos(dec*_DEGTORAD) sinrarangp= m.sin(ra*_DEGTORAD-ra_ngp) cosrarangp= m.cos(ra*_DEGTORAD-ra_ngp) else: sindec_ngp= m.sin(dec_ngp) cosdec_ngp= m.cos(dec_ngp) sindec= m.sin(dec) cosdec= m.cos(dec) sinrarangp= m.sin(ra-ra_ngp) cosrarangp= m.cos(ra-ra_ngp) #These were replaced by Poleski (2013)'s equivalent form that is better at the poles #cosphi= (sindec_ngp-sindec*sinb)/cosdec/cosb #sinphi= sinrarangp*cosdec_ngp/cosb cosphi= sindec_ngp*cosdec-cosdec_ngp*sindec*cosrarangp sinphi= sinrarangp*cosdec_ngp norm= m.sqrt(cosphi**2.+sinphi**2.) cosphi/= norm sinphi/= norm P= sc.array([[cosphi,sinphi],[-sinphi,cosphi]]) return sc.dot(P,sc.dot(cov_pmradec,P.T)) def cov_dvrpmllbb_to_vxyz(d,e_d,e_vr,pmll,pmbb,cov_pmllbb,l,b, plx=False,degree=False): """ NAME: cov_dvrpmllbb_to_vxyz PURPOSE: propagate distance, radial velocity, and proper motion uncertainties to Galactic coordinates INPUT: d - distance [kpc, as/mas for plx] e_d - distance uncertainty [kpc, [as/mas] for plx] e_vr - low velocity uncertainty [km/s] pmll - proper motion in l (*cos(b)) [ [as/mas]/yr ] pmbb - proper motion in b [ [as/mas]/yr ] cov_pmllbb - uncertainty covariance for proper motion [pmll is pmll x cos(b)] l - Galactic longitude b - Galactic lattitude KEYWORDS: plx - if True, d is a parallax, and e_d is a parallax uncertainty degree - if True, l and b are given in degree OUTPUT: cov(vx,vy,vz) [3,3] or [:,3,3] HISTORY: 2010-04-12 - Written - Bovy (NYU) """ if plx: d= 1./d e_d*= d**2. if degree: l*= _DEGTORAD b*= _DEGTORAD if sc.array(d).shape == (): return cov_dvrpmllbb_to_vxyz_single(d,e_d,e_vr,pmll,pmbb,cov_pmllbb, l,b) else: ndata= len(d) out= sc.zeros((ndata,3,3)) for ii in range(ndata): out[ii,:,:]= cov_dvrpmllbb_to_vxyz_single(d[ii],e_d[ii],e_vr[ii], pmll[ii],pmbb[ii], cov_pmllbb[ii,:,:], l[ii],b[ii]) return out def cov_dvrpmllbb_to_vxyz_single(d,e_d,e_vr,pmll,pmbb,cov_pmllbb,l,b): """ NAME: cov_dvrpmllbb_to_vxyz PURPOSE: propagate distance, radial velocity, and proper motion uncertainties to Galactic coordinates for scalar inputs INPUT: d - distance [kpc, as/mas for plx] e_d - distance uncertainty [kpc, [as/mas] for plx] e_vr - low velocity uncertainty [km/s] pmll - proper motion in l (*cos(b)) [ [as/mas]/yr ] pmbb - proper motion in b [ [as/mas]/yr ] cov_pmllbb - uncertainty covariance for proper motion l - Galactic longitude [rad] b - Galactic lattitude [rad] OUTPUT: cov(vx,vy,vz) [3,3] HISTORY: 2010-04-12 - Written - Bovy (NYU) """ M= _K*sc.array([[pmll,d,0.],[pmbb,0.,d]]) cov_dpmllbb= sc.zeros((3,3)) cov_dpmllbb[0,0]= e_d**2. cov_dpmllbb[1:3,1:3]= cov_pmllbb cov_vlvb= sc.dot(M,sc.dot(cov_dpmllbb,M.T)) cov_vrvlvb= sc.zeros((3,3)) cov_vrvlvb[0,0]= e_vr**2. cov_vrvlvb[1:3,1:3]= cov_vlvb R= sc.array([[m.cos(l)*m.cos(b), m.sin(l)*m.cos(b), m.sin(b)], [-m.sin(l),m.cos(l),0.], [-m.cos(l)*m.sin(b),-m.sin(l)*m.sin(b), m.cos(b)]]) return sc.dot(R.T,sc.dot(cov_vrvlvb,R)) @scalarDecorator def XYZ_to_galcenrect(X,Y,Z,Xsun=1.,Zsun=0.): """ NAME: XYZ_to_galcenrect PURPOSE: transform XYZ coordinates (wrt Sun) to rectangular Galactocentric coordinates INPUT: X - X Y - Y Z - Z Xsun - cylindrical distance to the GC Zsun - Sun's height above the midplane OUTPUT: (Xg, Yg, Zg) HISTORY: 2010-09-24 - Written - Bovy (NYU) 2016-05-12 - Edited to properly take into account the Sun's vertical position; dropped Ysun keyword - Bovy (UofT) """ dgc= nu.sqrt(Xsun**2.+Zsun**2.) costheta, sintheta= Xsun/dgc, Zsun/dgc return nu.dot(nu.array([[costheta,0.,-sintheta], [0.,1.,0.], [sintheta,0.,costheta]]), nu.array([-X+dgc,Y,nu.sign(Xsun)*Z])).T @scalarDecorator def galcenrect_to_XYZ(X,Y,Z,Xsun=1.,Zsun=0.): """ NAME: galcenrect_to_XYZ PURPOSE: transform rectangular Galactocentric to XYZ coordinates (wrt Sun) coordinates INPUT: X, Y, Z - Galactocentric rectangular coordinates Xsun - cylindrical distance to the GC (can be array of same length as X) Zsun - Sun's height above the midplane (can be array of same length as X) OUTPUT: (X, Y, Z) HISTORY: 2011-02-23 - Written - Bovy (NYU) 2016-05-12 - Edited to properly take into account the Sun's vertical position; dropped Ysun keyword - Bovy (UofT) 2017-10-24 - Allowed Xsun/Zsun to be arrays - Bovy (UofT) """ dgc= nu.sqrt(Xsun**2.+Zsun**2.) costheta, sintheta= Xsun/dgc, Zsun/dgc if isinstance(Xsun,nu.ndarray): zero= nu.zeros(len(Xsun)) one= nu.ones(len(Xsun)) Carr= nu.rollaxis(nu.array([[-costheta,zero,-sintheta], [zero,one,zero], [-nu.sign(Xsun)*sintheta,zero, nu.sign(Xsun)*costheta]]),2) return ((Carr*nu.array([[X,X,X],[Y,Y,Y],[Z,Z,Z]]).T).sum(-1) +nu.array([dgc,zero,zero]).T) else: return nu.dot(nu.array([[-costheta,0.,-sintheta], [0.,1.,0.], [-nu.sign(Xsun)*sintheta,0., nu.sign(Xsun)*costheta]]), nu.array([X,Y,Z])).T+nu.array([dgc,0.,0.]) def rect_to_cyl(X,Y,Z): """ NAME: rect_to_cyl PURPOSE: convert from rectangular to cylindrical coordinates INPUT: X, Y, Z - rectangular coordinates OUTPUT: R,phi,z HISTORY: 2010-09-24 - Written - Bovy (NYU) """ R= sc.sqrt(X**2.+Y**2.) phi= sc.arctan2(Y,X) if isinstance(phi,nu.ndarray): phi[phi<0.]+= 2.*nu.pi elif phi < 0.: phi+= 2.*nu.pi return (R,phi,Z) def cyl_to_rect(R,phi,Z): """ NAME: cyl_to_rect PURPOSE: convert from cylindrical to rectangular coordinates INPUT: R, phi, Z - cylindrical coordinates OUTPUT: X,Y,Z HISTORY: 2011-02-23 - Written - Bovy (NYU) """ return (R*sc.cos(phi),R*sc.sin(phi),Z) def cyl_to_spher(R,Z, phi): """ NAME: cyl_to_spher PURPOSE: convert from cylindrical to spherical coordinates INPUT: R, Z, phi- cylindrical coordinates OUTPUT: R, theta, phi - spherical coordinates HISTORY: 2016-05-16 - Written - Aladdin """ theta = nu.arctan2(R, Z) r = (R**2 + Z**2)**.5 return (r,theta, phi) def spher_to_cyl(r, theta, phi): """ NAME: spher_to_cyl PURPOSE: convert from spherical to cylindrical coordinates INPUT: r, theta, phi - spherical coordinates OUTPUT: R, z, phi - spherical coordinates HISTORY: 2016-05-20 - Written - Aladdin """ R = r*nu.sin(theta) z = r*nu.cos(theta) return (R,z, phi) @scalarDecorator def XYZ_to_galcencyl(X,Y,Z,Xsun=1.,Zsun=0.): """ NAME: XYZ_to_galcencyl PURPOSE: transform XYZ coordinates (wrt Sun) to cylindrical Galactocentric coordinates INPUT: X - X Y - Y Z - Z Xsun - cylindrical distance to the GC Zsun - Sun's height above the midplane OUTPUT: R,phi,z HISTORY: 2010-09-24 - Written - Bovy (NYU) """ XYZ= nu.atleast_2d(XYZ_to_galcenrect(X,Y,Z,Xsun=Xsun,Zsun=Zsun)) return nu.array(rect_to_cyl(XYZ[:,0],XYZ[:,1],XYZ[:,2])).T @scalarDecorator def galcencyl_to_XYZ(R,phi,Z,Xsun=1.,Zsun=0.): """ NAME: galcencyl_to_XYZ PURPOSE: transform cylindrical Galactocentric coordinates to XYZ coordinates (wrt Sun) INPUT: R, phi, Z - Galactocentric cylindrical coordinates Xsun - cylindrical distance to the GC (can be array of same length as R) Zsun - Sun's height above the midplane (can be array of same length as R) OUTPUT: X,Y,Z HISTORY: 2011-02-23 - Written - Bovy (NYU) 2017-10-24 - Allowed Xsun/Zsun to be arrays - Bovy (UofT) """ Xr,Yr,Zr= cyl_to_rect(R,phi,Z) return galcenrect_to_XYZ(Xr,Yr,Zr,Xsun=Xsun,Zsun=Zsun) @scalarDecorator def vxvyvz_to_galcenrect(vx,vy,vz,vsun=[0.,1.,0.],Xsun=1.,Zsun=0.): """ NAME: vxvyvz_to_galcenrect PURPOSE: transform velocities in XYZ coordinates (wrt Sun) to rectangular Galactocentric coordinates for velocities INPUT: vx - U vy - V vz - W vsun - velocity of the sun in the GC frame ndarray[3] Xsun - cylindrical distance to the GC Zsun - Sun's height above the midplane OUTPUT: [:,3]= vXg, vYg, vZg HISTORY: 2010-09-24 - Written - Bovy (NYU) 2016-05-12 - Edited to properly take into account the Sun's vertical position; dropped Ysun keyword - Bovy (UofT) """ dgc= nu.sqrt(Xsun**2.+Zsun**2.) costheta, sintheta= Xsun/dgc, Zsun/dgc return nu.dot(nu.array([[costheta,0.,-sintheta], [0.,1.,0.], [sintheta,0.,costheta]]), nu.array([-vx,vy,nu.sign(Xsun)*vz])).T+nu.array(vsun) @scalarDecorator def vxvyvz_to_galcencyl(vx,vy,vz,X,Y,Z,vsun=[0.,1.,0.],Xsun=1.,Zsun=0., galcen=False): """ NAME: vxvyvz_to_galcencyl PURPOSE: transform velocities in XYZ coordinates (wrt Sun) to cylindrical Galactocentric coordinates for velocities INPUT: vx - U vy - V vz - W X - X in Galactocentric rectangular coordinates Y - Y in Galactocentric rectangular coordinates Z - Z in Galactocentric rectangular coordinates vsun - velocity of the sun in the GC frame ndarray[3] Xsun - cylindrical distance to the GC Zsun - Sun's height above the midplane galcen - if True, then X,Y,Z are in cylindrical Galactocentric coordinates rather than rectangular coordinates OUTPUT: vRg, vTg, vZg HISTORY: 2010-09-24 - Written - Bovy (NYU) """ vxyz= vxvyvz_to_galcenrect(vx,vy,vz,vsun=vsun,Xsun=Xsun,Zsun=Zsun) return nu.array(\ rect_to_cyl_vec(vxyz[:,0],vxyz[:,1],vxyz[:,2],X,Y,Z,cyl=galcen)).T @scalarDecorator def galcenrect_to_vxvyvz(vXg,vYg,vZg,vsun=[0.,1.,0.],Xsun=1.,Zsun=0.): """ NAME: galcenrect_to_vxvyvz PURPOSE: transform rectangular Galactocentric coordinates to XYZ coordinates (wrt Sun) for velocities INPUT: vXg - Galactocentric x-velocity vYg - Galactocentric y-velocity vZg - Galactocentric z-velocity vsun - velocity of the sun in the GC frame ndarray[3] (can be array of same length as vXg; shape [3,N]) Xsun - cylindrical distance to the GC (can be array of same length as vXg) Zsun - Sun's height above the midplane (can be array of same length as vXg) OUTPUT: [:,3]= vx, vy, vz HISTORY: 2011-02-24 - Written - Bovy (NYU) 2016-05-12 - Edited to properly take into account the Sun's vertical position; dropped Ysun keyword - Bovy (UofT) 2017-10-24 - Allowed vsun/Xsun/Zsun to be arrays - Bovy (UofT) """ dgc= nu.sqrt(Xsun**2.+Zsun**2.) costheta, sintheta= Xsun/dgc, Zsun/dgc if isinstance(Xsun,nu.ndarray): zero= nu.zeros(len(Xsun)) one= nu.ones(len(Xsun)) Carr= nu.rollaxis(nu.array([[-costheta,zero,-sintheta], [zero,one,zero], [-nu.sign(Xsun)*sintheta,zero, nu.sign(Xsun)*costheta]]),2) return ((Carr *nu.array([[vXg-vsun[0],vXg-vsun[0],vXg-vsun[0]], [vYg-vsun[1],vYg-vsun[1],vYg-vsun[1]], [vZg-vsun[2],vZg-vsun[2],vZg-vsun[2]]]).T).sum(-1)) else: return nu.dot(nu.array([[-costheta,0.,-sintheta], [0.,1.,0.], [-nu.sign(Xsun)*sintheta,0., nu.sign(Xsun)*costheta]]), nu.array([vXg-vsun[0],vYg-vsun[1],vZg-vsun[2]])).T @scalarDecorator def galcencyl_to_vxvyvz(vR,vT,vZ,phi,vsun=[0.,1.,0.],Xsun=1.,Zsun=0.): """ NAME: galcencyl_to_vxvyvz PURPOSE: transform cylindrical Galactocentric coordinates to XYZ (wrt Sun) coordinates for velocities INPUT: vR - Galactocentric radial velocity vT - Galactocentric tangential velocity vZ - Galactocentric vertical velocity phi - Galactocentric azimuth vsun - velocity of the sun in the GC frame ndarray[3] (can be array of same length as vRg; shape [3,N]) Xsun - cylindrical distance to the GC (can be array of same length as vRg) Zsun - Sun's height above the midplane (can be array of same length as vRg) OUTPUT: vx,vy,vz HISTORY: 2011-02-24 - Written - Bovy (NYU) 2017-10-24 - Allowed vsun/Xsun/Zsun to be arrays - Bovy (NYU) """ vXg, vYg, vZg= cyl_to_rect_vec(vR,vT,vZ,phi) return galcenrect_to_vxvyvz(vXg,vYg,vZg,vsun=vsun,Xsun=Xsun,Zsun=Zsun) def rect_to_cyl_vec(vx,vy,vz,X,Y,Z,cyl=False): """ NAME: rect_to_cyl_vec PURPOSE: transform vectors from rectangular to cylindrical coordinates vectors INPUT: vx - vy - vz - X - X Y - Y Z - Z cyl - if True, X,Y,Z are already cylindrical OUTPUT: vR,vT,vz HISTORY: 2010-09-24 - Written - Bovy (NYU) """ if not cyl: R,phi,Z= rect_to_cyl(X,Y,Z) else: phi= Y vr=+vx*sc.cos(phi)+vy*sc.sin(phi) vt= -vx*sc.sin(phi)+vy*sc.cos(phi) return (vr,vt,vz) def cyl_to_rect_vec(vr,vt,vz,phi): """ NAME: cyl_to_rect_vec PURPOSE: transform vectors from cylindrical to rectangular coordinate vectors INPUT: vr - radial velocity vt - tangential velocity vz - vertical velocity phi - azimuth OUTPUT: vx,vy,vz HISTORY: 2011-02-24 - Written - Bovy (NYU) """ vx= vr*sc.cos(phi)-vt*sc.sin(phi) vy= vr*sc.sin(phi)+vt*sc.cos(phi) return (vx,vy,vz) def cyl_to_rect_jac(*args): """ NAME: cyl_to_rect_jac PURPOSE: calculate the Jacobian of the cylindrical to rectangular conversion INPUT: R, phi, Z- cylindrical coordinates vR, vT, vZ- cylindrical velocities if 6 inputs: R,vR,vT,z,vz,phi if 3: R, phi, Z OUTPUT: jacobian d(rect)/d(cyl) HISTORY: 2013-12-09 - Written - Bovy (IAS) """ out= sc.zeros((6,6)) if len(args) == 3: R, phi, Z= args vR, vT, vZ= 0., 0., 0. outIndx= sc.array([True,False,False,True,False,True],dtype='bool') elif len(args) == 6: R, vR, vT, Z, vZ, phi= args outIndx= sc.ones(6,dtype='bool') cp= sc.cos(phi) sp= sc.sin(phi) out[0,0]= cp out[0,5]= -R*sp out[1,0]= sp out[1,5]= R*cp out[2,3]= 1. out[3,1]= cp out[3,2]= -sp out[3,5]= -vT*cp-vR*sp out[4,1]= sp out[4,2]= cp out[4,5]= -vT*sp+vR*cp out[5,4]= 1. if len(args) == 3: out= out[:3,outIndx] out[:,[1,2]]= out[:,[2,1]] return out def galcenrect_to_XYZ_jac(*args,**kwargs): """ NAME: galcenrect_to_XYZ_jac PURPOSE: calculate the Jacobian of the Galactocentric rectangular to Galactic coordinates INPUT: X,Y,Z- Galactocentric rectangular coordinates vX, vY, vZ- Galactocentric rectangular velocities if 6 inputs: X,Y,Z,vX,vY,vZ if 3: X,Y,Z Xsun - cylindrical distance to the GC Zsun - Sun's height above the midplane OUTPUT: jacobian d(galcen.)/d(Galactic) HISTORY: 2013-12-09 - Written - Bovy (IAS) """ Xsun= kwargs.get('Xsun',1.) dgc= nu.sqrt(Xsun**2.+kwargs.get('Zsun',0.)**2.) costheta, sintheta= Xsun/dgc, kwargs.get('Zsun',0.)/dgc out= sc.zeros((6,6)) out[0,0]= -costheta out[0,2]= -sintheta out[1,1]= 1. out[2,0]= -nu.sign(Xsun)*sintheta out[2,2]= nu.sign(Xsun)*costheta if len(args) == 3: return out[:3,:3] out[3,3]= -costheta out[3,5]= -sintheta out[4,4]= 1. out[5,3]= -nu.sign(Xsun)*sintheta out[5,5]= nu.sign(Xsun)*costheta return out def lbd_to_XYZ_jac(*args,**kwargs): """ NAME: lbd_to_XYZ_jac PURPOSE: calculate the Jacobian of the Galactic spherical coordinates to Galactic rectangular coordinates transformation INPUT: l,b,D- Galactic spherical coordinates vlos,pmll,pmbb- Galactic spherical velocities (some as proper motions) if 6 inputs: l,b,D,vlos,pmll x cos(b),pmbb if 3: l,b,D degree= (False) if True, l and b are in degrees OUTPUT: jacobian HISTORY: 2013-12-09 - Written - Bovy (IAS) """ out= sc.zeros((6,6)) if len(args) == 3: l,b,D= args vlos, pmll, pmbb= 0., 0., 0. elif len(args) == 6: l,b,D,vlos,pmll,pmbb= args if kwargs.get('degree',False): l*= _DEGTORAD b*= _DEGTORAD cl= sc.cos(l) sl= sc.sin(l) cb= sc.cos(b) sb= sc.sin(b) out[0,0]= -D*cb*sl out[0,1]= -D*sb*cl out[0,2]= cb*cl out[1,0]= D*cb*cl out[1,1]= -D*sb*sl out[1,2]= cb*sl out[2,1]= D*cb out[2,2]= sb if len(args) == 3: if kwargs.get('degree',False): out[:,0]*= _DEGTORAD out[:,1]*= _DEGTORAD return out[:3,:3] out[3,0]= -sl*cb*vlos-cl*_K*D*pmll+sb*sl*_K*D*pmbb out[3,1]= -cl*sb*vlos-cb*cl*_K*D*pmbb out[3,2]= -sl*_K*pmll-sb*cl*_K*pmbb out[3,3]= cl*cb out[3,4]= -sl*_K*D out[3,5]= -cl*sb*_K*D out[4,0]= cl*cb*vlos-sl*_K*D*pmll-cl*sb*_K*D*pmbb out[4,1]= -sl*sb*vlos-sl*cb*_K*D*pmbb out[4,2]= cl*_K*pmll-sl*sb*_K*pmbb out[4,3]= sl*cb out[4,4]= cl*_K*D out[4,5]= -sl*sb*_K*D out[5,1]= cb*vlos-sb*_K*D*pmbb out[5,2]= cb*_K*pmbb out[5,3]= sb out[5,5]= cb*_K*D if kwargs.get('degree',False): out[:,0]*= _DEGTORAD out[:,1]*= _DEGTORAD return out def dl_to_rphi_2d(d,l,degree=False,ro=1.,phio=0.): """ NAME: dl_to_rphi_2d PURPOSE: convert Galactic longitude and distance to Galactocentric radius and azimuth INPUT: d - distance l - Galactic longitude [rad/deg if degree] KEYWORDS: degree= (False): l is in degrees rather than rad ro= (1) Galactocentric radius of the observer phio= (0) Galactocentric azimuth of the observer [rad/deg if degree] OUTPUT: (R,phi); phi in degree if degree HISTORY: 2012-01-04 - Written - Bovy (IAS) """ scalarOut, listOut= False, False if isinstance(d,(int,float)): d= sc.array([d]) scalarOut= True elif isinstance(d,list): d= sc.array(d) listOut= True if isinstance(l,(int,float)): l= sc.array([l]) elif isinstance(l,list): l= sc.array(l) if degree: l*= _DEGTORAD R= sc.sqrt(ro**2.+d**2.-2.*d*ro*sc.cos(l)) phi= sc.arcsin(d/R*sc.sin(l)) indx= (ro/sc.cos(l) < d)*(sc.cos(l) > 0.) phi[indx]= sc.pi-sc.arcsin(d[indx]/R[indx]*sc.sin(l[indx])) if degree: phi/= _DEGTORAD phi+= phio if scalarOut: return (R[0],phi[0]) elif listOut: return (list(R),list(phi)) else: return (R,phi) def rphi_to_dl_2d(R,phi,degree=False,ro=1.,phio=0.): """ NAME: rphi_to_dl_2d PURPOSE: convert Galactocentric radius and azimuth to distance and Galactic longitude INPUT: R - Galactocentric radius phi - Galactocentric azimuth [rad/deg if degree] KEYWORDS: degree= (False): phi is in degrees rather than rad ro= (1) Galactocentric radius of the observer phio= (0) Galactocentric azimuth of the observer [rad/deg if degree] OUTPUT: (d,l); phi in degree if degree HISTORY: 2012-01-04 - Written - Bovy (IAS) """ scalarOut, listOut= False, False if isinstance(R,(int,float)): R= sc.array([R]) scalarOut= True elif isinstance(R,list): R= sc.array(R) listOut= True if isinstance(phi,(int,float)): phi= sc.array([phi]) elif isinstance(phi,list): phi= sc.array(phi) phi-= phio if degree: phi*= _DEGTORAD d= sc.sqrt(R**2.+ro**2.-2.*R*ro*sc.cos(phi)) l= sc.arcsin(R/d*sc.sin(phi)) indx= (ro/sc.cos(phi) < R)*(sc.cos(phi) > 0.) l[indx]= sc.pi-sc.arcsin(R[indx]/d[indx]*sc.sin(phi[indx])) if degree: l/= _DEGTORAD if scalarOut: return (d[0],l[0]) elif listOut: return (list(d),list(l)) else: return (d,l) def Rz_to_coshucosv(R,z,delta=1.,oblate=False): """ NAME: Rz_to_coshucosv PURPOSE: calculate prolate confocal cosh(u) and cos(v) coordinates from R,z, and delta INPUT: R - radius z - height delta= focus oblate= (False) if True, compute oblate confocal coordinates instead of prolate OUTPUT: (cosh(u),cos(v)) HISTORY: 2012-11-27 - Written - Bovy (IAS) 2017-10-11 - Added oblate coordinates - Bovy (UofT) """ if oblate: d12= (R+delta)**2.+z**2. d22= (R-delta)**2.+z**2. else: d12= (z+delta)**2.+R**2. d22= (z-delta)**2.+R**2. coshu= 0.5/delta*(sc.sqrt(d12)+sc.sqrt(d22)) cosv= 0.5/delta*(sc.sqrt(d12)-sc.sqrt(d22)) if oblate: # cosv is currently really sinv cosv= sc.sqrt(1.-cosv**2.) return (coshu,cosv) def Rz_to_uv(R,z,delta=1.,oblate=False): """ NAME: Rz_to_uv PURPOSE: calculate prolate or oblate confocal u and v coordinates from R,z, and delta INPUT: R - radius z - height delta= focus oblate= (False) if True, compute oblate confocal coordinates instead of prolate OUTPUT: (u,v) HISTORY: 2012-11-27 - Written - Bovy (IAS) 2017-10-11 - Added oblate coordinates - Bovy (UofT) """ coshu, cosv= Rz_to_coshucosv(R,z,delta,oblate=oblate) u= sc.arccosh(coshu) v= sc.arccos(cosv) return (u,v) def uv_to_Rz(u,v,delta=1.,oblate=False): """ NAME: uv_to_Rz PURPOSE: calculate R and z from prolate confocal u and v coordinates INPUT: u - confocal u v - confocal v delta= focus oblate= (False) if True, compute oblate confocal coordinates instead of prolate OUTPUT: (R,z) HISTORY: 2012-11-27 - Written - Bovy (IAS) 2017-10-11 - Added oblate coordinates - Bovy (UofT) """ if oblate: R= delta*sc.cosh(u)*sc.sin(v) z= delta*sc.sinh(u)*sc.cos(v) else: R= delta*sc.sinh(u)*sc.sin(v) z= delta*sc.cosh(u)*sc.cos(v) return (R,z) def vRvz_to_pupv(vR,vz,R,z,delta=1.,oblate=False,uv=False): """ NAME: vRvz_to_pupv PURPOSE: calculate momenta in prolate or oblate confocal u and v coordinates from cylindrical velocities vR,vz for a given focal length delta INPUT: vR - radial velocity in cylindrical coordinates vz - vertical velocity in cylindrical coordinates R - radius z - height delta= focus oblate= (False) if True, compute oblate confocal coordinates instead of prolate uv= (False) if True, the given R,z are actually u,v OUTPUT: (pu,pv) HISTORY: 2017-11-28 - Written - Bovy (UofT) """ if not uv: u,v= Rz_to_uv(R,z,delta,oblate=oblate) else: u,v= R,z if oblate: pu= delta*(vR*sc.sinh(u)*sc.sin(v)+vz*sc.cosh(u)*sc.cos(v)) pv= delta*(vR*sc.cosh(u)*sc.cos(v)-vz*sc.sinh(u)*sc.sin(v)) else: pu= delta*(vR*sc.cosh(u)*sc.sin(v)+vz*sc.sinh(u)*sc.cos(v)) pv= delta*(vR*sc.sinh(u)*sc.cos(v)-vz*sc.cosh(u)*sc.sin(v)) return (pu,pv) def pupv_to_vRvz(pu,pv,u,v,delta=1.,oblate=False): """ NAME: pupv_to_vRvz PURPOSE: calculate cylindrical vR and vz from momenta in prolate or oblate confocal u and v coordinates for a given focal length delta INPUT: pu - u momentum pv - v momentum u - u coordinate v - v coordinate delta= focus oblate= (False) if True, compute oblate confocal coordinates instead of prolate OUTPUT: (vR,vz) HISTORY: 2017-12-04 - Written - Bovy (UofT) """ if oblate: denom= delta*(sc.sinh(u)**2.+sc.cos(v)**2.) vR= (pu*sc.sinh(u)*sc.sin(v)+pv*sc.cosh(u)*sc.cos(v))/denom vz= (pu*sc.cosh(u)*sc.cos(v)-pv*sc.sinh(u)*sc.sin(v))/denom else: denom= delta*(sc.sinh(u)**2.+sc.sin(v)**2.) vR= (pu*sc.cosh(u)*sc.sin(v)+pv*sc.sinh(u)*sc.cos(v))/denom vz= (pu*sc.sinh(u)*sc.cos(v)-pv*sc.cosh(u)*sc.sin(v))/denom return (vR,vz) def Rz_to_lambdanu(R,z,ac=5.,Delta=1.): """ NAME: Rz_to_lambdanu PURPOSE: calculate the prolate spheroidal coordinates (lambda,nu) from galactocentric cylindrical coordinates (R,z) by solving eq. (2.2) in Dejonghe & de Zeeuw (1988a) for (lambda,nu): R^2 = (l+a) * (n+a) / (a-g) z^2 = (l+g) * (n+g) / (g-a) Delta^2 = g-a INPUT: R - Galactocentric cylindrical radius z - vertical height ac - axis ratio of the coordinate surfaces (a/c) = sqrt(-a) / sqrt(-g) (default: 5.) Delta - focal distance that defines the spheroidal coordinate system (default: 1.) Delta=sqrt(g-a) OUTPUT: (lambda,nu) HISTORY: 2015-02-13 - Written - Trick (MPIA) """ g = Delta**2 / (1.-ac**2) a = g - Delta**2 term = R**2 + z**2 - a - g discr = (R**2 + z**2 - Delta**2)**2 + (4. * Delta**2 * R**2) l = 0.5 * (term + nu.sqrt(discr)) n = 0.5 * (term - nu.sqrt(discr)) if isinstance(z,float) and z == 0.: l = R**2 - a n = -g elif isinstance(z,nu.ndarray) and nu.sum(z == 0.) > 0: if isinstance(R,float): l[z==0.] = R**2 - a if isinstance(R,sc.ndarray): l[z==0.] = R[z==0.]**2 - a n[z==0.] = -g return (l,n) def Rz_to_lambdanu_jac(R,z,Delta=1.): """ NAME: Rz_to_lambdanu_jac PURPOSE: calculate the Jacobian of the cylindrical (R,z) to prolate spheroidal (lambda,nu) conversion INPUT: R - Galactocentric cylindrical radius z - vertical height Delta - focal distance that defines the spheroidal coordinate system (default: 1.) Delta=sqrt(g-a) OUTPUT: jacobian d((lambda,nu))/d((R,z)) HISTORY: 2015-02-13 - Written - Trick (MPIA) """ discr = (R**2 + z**2 - Delta**2)**2 + (4. * Delta**2 * R**2) dldR = R * (1. + (R**2 + z**2 + Delta**2) / nu.sqrt(discr)) dndR = R * (1. - (R**2 + z**2 + Delta**2) / nu.sqrt(discr)) dldz = z * (1. + (R**2 + z**2 - Delta**2) / nu.sqrt(discr)) dndz = z * (1. - (R**2 + z**2 - Delta**2) / nu.sqrt(discr)) dim = 1 if isinstance(R,nu.ndarray): dim = len(R) elif isinstance(z,nu.ndarray): dim = len(z) jac = nu.zeros((2,2,dim)) jac[0,0,:] = dldR jac[0,1,:] = dldz jac[1,0,:] = dndR jac[1,1,:] = dndz if dim == 1: return jac[:,:,0] else: return jac def Rz_to_lambdanu_hess(R,z,Delta=1.): """ NAME: Rz_to_lambdanu_hess PURPOSE: calculate the Hessian of the cylindrical (R,z) to prolate spheroidal (lambda,nu) conversion INPUT: R - Galactocentric cylindrical radius z - vertical height Delta - focal distance that defines the spheroidal coordinate system (default: 1.) Delta=sqrt(g-a) OUTPUT: hessian [d^2(lamda)/d(R,z)^2 , d^2(nu)/d(R,z)^2] HISTORY: 2015-02-13 - Written - Trick (MPIA) """ D = Delta R2 = R**2 z2 = z**2 D2 = D**2 discr = (R2 + z2 - D2)**2 + (4. * D2 * R2) d2ldR2 = 1. + (3.*R2+ z2+D2)/discr**0.5 - (2.*R2*(R2+z2+D2)**2)/discr**1.5 d2ndR2 = 1. - (3.*R2+ z2+D2)/discr**0.5 + (2.*R2*(R2+z2+D2)**2)/discr**1.5 d2ldz2 = 1. + ( R2+3.*z2-D2)/discr**0.5 - (2.*z2*(R2+z2-D2)**2)/discr**1.5 d2ndz2 = 1. - ( R2+3.*z2-D2)/discr**0.5 + (2.*z2*(R2+z2-D2)**2)/discr**1.5 d2ldRdz = 2.*R*z/discr**0.5 * ( 1. - ((R2+z2)**2-D**4)/discr) d2ndRdz = 2.*R*z/discr**0.5 * (-1. + ((R2+z2)**2-D**4)/discr) dim = 1 if isinstance(R,nu.ndarray): dim = len(R) elif isinstance(z,nu.ndarray): dim = len(z) hess = nu.zeros((2,2,2,dim)) #Hessian for lambda: hess[0,0,0,:] = d2ldR2 hess[0,0,1,:] = d2ldRdz hess[0,1,0,:] = d2ldRdz hess[0,1,1,:] = d2ldz2 #Hessian for nu: hess[1,0,0,:] = d2ndR2 hess[1,0,1,:] = d2ndRdz hess[1,1,0,:] = d2ndRdz hess[1,1,1,:] = d2ndz2 if dim == 1: return hess[:,:,:,0] else: return hess def lambdanu_to_Rz(l,n,ac=5.,Delta=1.): """ NAME: lambdanu_to_Rz PURPOSE: calculate galactocentric cylindrical coordinates (R,z) from prolate spheroidal coordinates (lambda,nu), cf. eq. (2.2) in Dejonghe & de Zeeuw (1988a) INPUT: l - prolate spheroidal coordinate lambda n - prolate spheroidal coordinate nu ac - axis ratio of the coordinate surfaces (a/c) = sqrt(-a) / sqrt(-g) (default: 5.) Delta - focal distance that defines the spheroidal coordinate system (default: 1.) Delta=sqrt(g-a) OUTPUT: (R,z) HISTORY: 2015-02-13 - Written - Trick (MPIA) """ g = Delta**2 / (1.-ac**2) a = g - Delta**2 r2 = (l + a) * (n + a) / (a - g) z2 = (l + g) * (n + g) / (g - a) index = (r2 < 0.) * ((n+a) > 0.) * ((n+a) < 1e-10) if nu.any(index): if isinstance(r2,nu.ndarray): r2[index] = 0. else: r2 = 0. index = (z2 < 0.) * ((n+g) < 0.) * ((n+g) > -1e-10) if nu.any(index): if isinstance(z2,nu.ndarray): z2[index] = 0. else: z2 = 0. return (nu.sqrt(r2),nu.sqrt(z2)) @scalarDecorator @degreeDecorator([0,1],[0,1]) def radec_to_custom(ra,dec,T=None,degree=False,epoch=2000.0): """ NAME: radec_to_custom PURPOSE: transform from equatorial coordinates to a custom set of sky coordinates INPUT: ra - right ascension dec - declination T= matrix defining the transformation: new_rect= T dot old_rect, where old_rect = [cos(dec)cos(ra),cos(dec)sin(ra),sin(dec)] and similar for new_rect degree - (Bool) if True, ra and dec are given in degree and l and b will be as well OUTPUT: custom longitude, custom latitude (with longitude -180 to 180) For vector inputs [:,2] HISTORY: 2009-11-12 - Written - Bovy (NYU) 2014-06-14 - Re-written w/ numpy functions for speed and w/ decorators for beauty - Bovy (IAS) """ if T is None: raise ValueError("Must set T= for radec_to_custom") #Whether to use degrees and scalar input is handled by decorators XYZ= nu.array([nu.cos(dec)*nu.cos(ra), nu.cos(dec)*nu.sin(ra), nu.sin(dec)]) galXYZ= nu.dot(T,XYZ) b= nu.arcsin(galXYZ[2]) l= nu.arctan2(galXYZ[1]/sc.cos(b),galXYZ[0]/sc.cos(b)) out= nu.array([l,b]) return out.T @scalarDecorator @degreeDecorator([2,3],[]) def pmrapmdec_to_custom(pmra,pmdec,ra,dec,T=None,degree=False,epoch=2000.0): """ NAME: pmrapmdec_to_custom PURPOSE: rotate proper motions in (ra,dec) to proper motions in a custom set of sky coordinates (phi1,phi2) INPUT: pmra - proper motion in ra (multplied with cos(dec)) [mas/yr] pmdec - proper motion in dec [mas/yr] ra - right ascension dec - declination T= matrix defining the transformation: new_rect= T dot old_rect, where old_rect = [cos(dec)cos(ra),cos(dec)sin(ra),sin(dec)] and similar for new_rect degree= (False) if True, ra and dec are given in degrees (default=False) epoch= (2000.) epoch of ra,dec (right now only 2000.0 and 1950.0 are supported when not using astropy's transformations internally; when internally using astropy's coordinate transformations, epoch can be None for ICRS, 'JXXXX' for FK5, and 'BXXXX' for FK4) OUTPUT: (pmphi1 x cos(phi2),pmph2) for vector inputs [:,2] HISTORY: 2016-10-24 - Written - Bovy (UofT/CCA) """ if T is None: raise ValueError("Must set T= for radec_to_custom") # Need to figure out ra_ngp and dec_ngp for this custom set of sky coords # Should replace this eventually with custom_to_radec customXYZ= nu.dot(T.T,nu.array([0.,0.,1.])) dec_ngp= nu.arcsin(customXYZ[2]) ra_ngp= nu.arctan2(customXYZ[1]/sc.cos(dec_ngp), customXYZ[0]/sc.cos(dec_ngp)) #Whether to use degrees and scalar input is handled by decorators dec[dec == dec_ngp]+= 10.**-16 #deal w/ pole. sindec_ngp= nu.sin(dec_ngp) cosdec_ngp= nu.cos(dec_ngp) sindec= nu.sin(dec) cosdec= nu.cos(dec) sinrarangp= nu.sin(ra-ra_ngp) cosrarangp= nu.cos(ra-ra_ngp) cosphi= sindec_ngp*cosdec-cosdec_ngp*sindec*cosrarangp sinphi= sinrarangp*cosdec_ngp norm= nu.sqrt(cosphi**2.+sinphi**2.) cosphi/= norm sinphi/= norm return (nu.array([[cosphi,-sinphi],[sinphi,cosphi]]).T\ *nu.array([[pmra,pmra],[pmdec,pmdec]]).T).sum(-1) def get_epoch_angles(epoch=2000.0): """ NAME: get_epoch_angles PURPOSE: get the angles relevant for the transformation from ra, dec to l,b for the given epoch INPUT: epoch - epoch of ra,dec (right now only 2000.0 and 1950.0 are supported when not using astropy's transformations internally; when internally using astropy's coordinate transformations, epoch can be None for ICRS, 'JXXXX' for FK5, and 'BXXXX' for FK4) OUTPUT: set of angles HISTORY: 2010-04-07 - Written - Bovy (NYU) 2016-05-13 - Added support for using astropy's coordinate transformations and for non-standard epochs - Bovy (UofT) """ if epoch == 2000.0: theta= 122.932/180.*sc.pi dec_ngp= 27.12825/180.*sc.pi ra_ngp= 192.85948/180.*sc.pi elif epoch == 1950.0: theta= 123./180.*sc.pi dec_ngp= 27.4/180.*sc.pi ra_ngp= 192.25/180.*sc.pi elif _APY_LOADED: # Use astropy to get the angles epoch, frame= _parse_epoch_frame_apy(epoch) c= apycoords.SkyCoord(180.*units.deg,90.*units.deg, frame=frame,equinox=epoch) c= c.transform_to(apycoords.Galactic) theta= c.l.to(units.rad).value c= apycoords.SkyCoord(180.*units.deg,90.*units.deg, frame='galactic') if not epoch is None and 'J' in epoch: c= c.transform_to(apycoords.FK5(equinox=epoch)) elif not epoch is None and 'B' in epoch: c= c.transform_to(apycoords.FK4(equinox=epoch)) else: c= c.transform_to(apycoords.ICRS) dec_ngp= c.dec.to(units.rad).value ra_ngp= c.ra.to(units.rad).value else: raise IOError("Only epochs 1950 and 2000 are supported if you don't have astropy") return (theta,dec_ngp,ra_ngp) def _parse_epoch_frame_apy(epoch): if epoch == 2000.0 or epoch == '2000': epoch= 'J2000' elif epoch == 1950.0 or epoch == '1950': epoch= 'B1950' if not epoch is None and 'J' in epoch: frame= 'fk5' elif not epoch is None and 'B' in epoch: frame= 'fk4' else: frame= 'icrs' return (epoch,frame) galpy-1.3.0/galpy/util/bovy_plot.py0000644000076500000240000011440113165465406017424 0ustar bovystaff00000000000000############################################################################## # # bovy_plot.py: general wrappers for matplotlib plotting # # 'public' methods: # bovy_end_print # bovy_dens2d # bovy_hist # bovy_plot # bovy_print # scatterplot (like hogg_scatterplot) # bovy_text # # this module also defines a custom matplotlib # projection in which the polar azimuth increases # clockwise (as in, the Galaxy viewed from the NGP) # ############################################################################# ############################################################################# #Copyright (c) 2010 - 2013, Jo Bovy #All rights reserved. # #Redistribution and use in source and binary forms, with or without #modification, are permitted provided that the following conditions are met: # # Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # Redistributions in binary form must reproduce the above copyright notice, # this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # The name of the author may not be used to endorse or promote products # derived from this software without specific prior written permission. # #THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS #"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT #LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR #A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT #HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, #INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, #BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS #OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED #AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT #LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY #WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE #POSSIBILITY OF SUCH DAMAGE. ############################################################################# import re import math as m import scipy as sc from scipy import special from scipy import interpolate from scipy import ndimage import matplotlib import matplotlib.pyplot as pyplot import matplotlib.ticker as ticker import matplotlib.cm as cm from matplotlib import rc from matplotlib.ticker import NullFormatter from matplotlib.projections import PolarAxes, register_projection from matplotlib.transforms import Affine2D, Bbox, IdentityTransform from mpl_toolkits.mplot3d import Axes3D from galpy.util.config import __config__ if __config__.getboolean('plot','seaborn-bovy-defaults'): try: import seaborn as sns except: pass else: sns.set_style('ticks', {'xtick.direction': u'in', 'ytick.direction': u'in', 'axes.labelsize': 18.0, 'axes.titlesize': 18.0, 'figure.figsize': sc.array([ 6.64, 4. ]), 'grid.linewidth': 2.0, 'legend.fontsize': 18.0, 'lines.linewidth': 2.0, 'lines.markeredgewidth': 0.0, 'lines.markersize': 14.0, 'patch.linewidth': 0.6, 'xtick.labelsize': 16.0, 'xtick.major.pad': 14.0, 'xtick.major.width': 2.0, 'xtick.minor.width': 1.0, 'ytick.labelsize': 16.0, 'ytick.major.pad': 14.0, 'ytick.major.width': 2.0,}) _DEFAULTNCNTR= 10 def bovy_end_print(filename,**kwargs): """ NAME: bovy_end_print PURPOSE: saves the current figure(s) to filename INPUT: filename - filename for plot (with extension) OPTIONAL INPUTS: format - file-format OUTPUT: (none) HISTORY: 2009-12-23 - Written - Bovy (NYU) """ if 'format' in kwargs: pyplot.savefig(filename,**kwargs) else: pyplot.savefig(filename,format=re.split(r'\.',filename)[-1],**kwargs) pyplot.close() def bovy_hist(x,xlabel=None,ylabel=None,overplot=False,**kwargs): """ NAME: bovy_hist PURPOSE: wrapper around matplotlib's hist function INPUT: x - array to histogram xlabel - (raw string!) x-axis label, LaTeX math mode, no $s needed ylabel - (raw string!) y-axis label, LaTeX math mode, no $s needed yrange - set the y-axis range +all pyplot.hist keywords OUTPUT: (from the matplotlib docs: http://matplotlib.sourceforge.net/api/pyplot_api.html#matplotlib.pyplot.hist) The return value is a tuple (n, bins, patches) or ([n0, n1, ...], bins, [patches0, patches1,...]) if the input contains multiple data HISTORY: 2009-12-23 - Written - Bovy (NYU) """ if not overplot: pyplot.figure() if 'xrange' in kwargs: xlimits= kwargs.pop('xrange') if not 'range' in kwargs: kwargs['range']= xlimits xrangeSet= True else: xrangeSet= False if 'yrange' in kwargs: ylimits= kwargs.pop('yrange') yrangeSet= True else: yrangeSet= False out= pyplot.hist(x,**kwargs) if overplot: return out _add_axislabels(xlabel,ylabel) if not 'range' in kwargs and not xrangeSet: if isinstance(x,list): xlimits=(sc.array(x).min(),sc.array(x).max()) else: pyplot.xlim(x.min(),x.max()) elif xrangeSet: pyplot.xlim(xlimits) else: pyplot.xlim(kwargs['range']) if yrangeSet: pyplot.ylim(ylimits) _add_ticks() return out def bovy_plot(*args,**kwargs): """ NAME: bovy_plot PURPOSE: wrapper around matplotlib's plot function INPUT: see http://matplotlib.sourceforge.net/api/pyplot_api.html#matplotlib.pyplot.plot xlabel - (raw string!) x-axis label, LaTeX math mode, no $s needed ylabel - (raw string!) y-axis label, LaTeX math mode, no $s needed xrange yrange scatter= if True, use pyplot.scatter and its options etc. colorbar= if True, and scatter==True, add colorbar crange - range for colorbar of scatter==True clabel= label for colorbar overplot=True does not start a new figure and does not change the ranges and labels gcf=True does not start a new figure (does change the ranges and labels) onedhists - if True, make one-d histograms on the sides onedhistcolor, onedhistfc, onedhistec onedhistxnormed, onedhistynormed - normed keyword for one-d histograms onedhistxweights, onedhistyweights - weights keyword for one-d histograms bins= number of bins for onedhists semilogx=, semilogy=, loglog= if True, plot logs OUTPUT: plot to output device, returns what pyplot.plot returns, or 3 Axes instances if onedhists=True HISTORY: 2009-12-28 - Written - Bovy (NYU) """ overplot= kwargs.pop('overplot',False) gcf= kwargs.pop('gcf',False) onedhists= kwargs.pop('onedhists',False) scatter= kwargs.pop('scatter',False) loglog= kwargs.pop('loglog',False) semilogx= kwargs.pop('semilogx',False) semilogy= kwargs.pop('semilogy',False) colorbar= kwargs.pop('colorbar',False) onedhisttype= kwargs.pop('onedhisttype','step') onedhistcolor= kwargs.pop('onedhistcolor','k') onedhistfc= kwargs.pop('onedhistfc','w') onedhistec= kwargs.pop('onedhistec','k') onedhistxnormed= kwargs.pop('onedhistxnormed',True) onedhistynormed= kwargs.pop('onedhistynormed',True) onedhistxweights= kwargs.pop('onedhistxweights',None) onedhistyweights= kwargs.pop('onedhistyweights',None) if 'bins' in kwargs: bins= kwargs['bins'] kwargs.pop('bins') elif onedhists: if isinstance(args[0],sc.ndarray): bins= round(0.3*sc.sqrt(args[0].shape[0])) elif isinstance(args[0],list): bins= round(0.3*sc.sqrt(len(args[0]))) else: bins= 30 if onedhists: if overplot or gcf: fig= pyplot.gcf() else: fig= pyplot.figure() nullfmt = NullFormatter() # no labels # definitions for the axes left, width = 0.1, 0.65 bottom, height = 0.1, 0.65 bottom_h = left_h = left+width rect_scatter = [left, bottom, width, height] rect_histx = [left, bottom_h, width, 0.2] rect_histy = [left_h, bottom, 0.2, height] axScatter = pyplot.axes(rect_scatter) axHistx = pyplot.axes(rect_histx) axHisty = pyplot.axes(rect_histy) # no labels axHistx.xaxis.set_major_formatter(nullfmt) axHistx.yaxis.set_major_formatter(nullfmt) axHisty.xaxis.set_major_formatter(nullfmt) axHisty.yaxis.set_major_formatter(nullfmt) fig.sca(axScatter) elif not overplot and not gcf: pyplot.figure() ax=pyplot.gca() ax.set_autoscale_on(False) xlabel= kwargs.pop('xlabel',None) ylabel= kwargs.pop('ylabel',None) clabel= kwargs.pop('clabel',None) xlimits= kwargs.pop('xrange',None) if xlimits is None: if isinstance(args[0],list): xlimits=(sc.array(args[0]).min(),sc.array(args[0]).max()) else: xlimits=(args[0].min(),args[0].max()) ylimits= kwargs.pop('yrange',None) if ylimits is None: if isinstance(args[1],list): ylimits=(sc.array(args[1]).min(),sc.array(args[1]).max()) else: ylimits=(args[1].min(),args[1].max()) climits= kwargs.pop('crange',None) if climits is None and scatter: if 'c' in kwargs and isinstance(kwargs['c'],list): climits=(sc.array(kwargs['c']).min(),sc.array(kwargs['c']).max()) elif 'c' in kwargs: climits=(kwargs['c'].min(),kwargs['c'].max()) else: climits= None if scatter: out= pyplot.scatter(*args,**kwargs) elif loglog: out= pyplot.loglog(*args,**kwargs) elif semilogx: out= pyplot.semilogx(*args,**kwargs) elif semilogy: out= pyplot.semilogy(*args,**kwargs) else: out= pyplot.plot(*args,**kwargs) if overplot: pass else: if semilogy: ax= pyplot.gca() ax.set_yscale('log') elif semilogx: ax= pyplot.gca() ax.set_xscale('log') elif loglog: ax= pyplot.gca() ax.set_xscale('log') ax.set_yscale('log') pyplot.xlim(*xlimits) pyplot.ylim(*ylimits) _add_axislabels(xlabel,ylabel) if not semilogy and not semilogx and not loglog: _add_ticks() elif semilogy: _add_ticks(xticks=True,yticks=False) elif semilogx: _add_ticks(yticks=True,xticks=False) #Add colorbar if colorbar: cbar= pyplot.colorbar(out,fraction=0.15) cbar.set_clim(*climits) if not clabel is None: cbar.set_label(clabel) #Add onedhists if not onedhists: return out histx, edges, patches= axHistx.hist(args[0], bins=bins, normed=onedhistxnormed, weights=onedhistxweights, histtype=onedhisttype, range=sorted(xlimits), color=onedhistcolor,fc=onedhistfc, ec=onedhistec) histy, edges, patches= axHisty.hist(args[1], bins=bins, orientation='horizontal', weights=onedhistyweights, normed=onedhistynormed, histtype=onedhisttype, range=sorted(ylimits), color=onedhistcolor,fc=onedhistfc, ec=onedhistec) axHistx.set_xlim( axScatter.get_xlim() ) axHisty.set_ylim( axScatter.get_ylim() ) axHistx.set_ylim( 0, 1.2*sc.amax(histx)) axHisty.set_xlim( 0, 1.2*sc.amax(histy)) return (axScatter,axHistx,axHisty) def bovy_plot3d(*args,**kwargs): """ NAME: bovy_plot3d PURPOSE: plot in 3d much as in 2d INPUT: see http://matplotlib.sourceforge.net/api/pyplot_api.html#matplotlib.pyplot.plot xlabel - (raw string!) x-axis label, LaTeX math mode, no $s needed ylabel - (raw string!) y-axis label, LaTeX math mode, no $s needed xrange yrange overplot=True does not start a new figure OUTPUT: HISTORY: 2011-01-08 - Written - Bovy (NYU) """ overplot= kwargs.pop('overplot',False) if not overplot: pyplot.figure() ax=pyplot.gca(projection='3d') ax.set_autoscale_on(False) xlabel= kwargs.pop('xlabel',None) ylabel= kwargs.pop('ylabel',None) zlabel= kwargs.pop('zlabel',None) if 'xrange' in kwargs: xlimits= kwargs.pop('xrange') else: if isinstance(args[0],list): xlimits=(sc.array(args[0]).min(),sc.array(args[0]).max()) else: xlimits=(args[0].min(),args[0].max()) if 'yrange' in kwargs: ylimits= kwargs.pop('yrange') else: if isinstance(args[1],list): ylimits=(sc.array(args[1]).min(),sc.array(args[1]).max()) else: ylimits=(args[1].min(),args[1].max()) if 'zrange' in kwargs: zlimits= kwargs.pop('zrange') else: if isinstance(args[2],list): zlimits=(sc.array(args[2]).min(),sc.array(args[2]).max()) else: zlimits=(args[1].min(),args[2].max()) out= pyplot.plot(*args,**kwargs) if overplot: pass else: if xlabel != None: if xlabel[0] != '$': thisxlabel=r'$'+xlabel+'$' else: thisxlabel=xlabel ax.set_xlabel(thisxlabel) if ylabel != None: if ylabel[0] != '$': thisylabel=r'$'+ylabel+'$' else: thisylabel=ylabel ax.set_ylabel(thisylabel) if zlabel != None: if zlabel[0] != '$': thiszlabel=r'$'+zlabel+'$' else: thiszlabel=zlabel ax.set_zlabel(thiszlabel) ax.set_xlim3d(*xlimits) ax.set_ylim3d(*ylimits) ax.set_zlim3d(*zlimits) return out def bovy_dens2d(X,**kwargs): """ NAME: bovy_dens2d PURPOSE: plot a 2d density with optional contours INPUT: first argument is the density matplotlib.pyplot.imshow keywords (see http://matplotlib.sourceforge.net/api/axes_api.html#matplotlib.axes.Axes.imshow) xlabel - (raw string!) x-axis label, LaTeX math mode, no $s needed ylabel - (raw string!) y-axis label, LaTeX math mode, no $s needed xrange yrange noaxes - don't plot any axes overplot - if True, overplot colorbar - if True, add colorbar shrink= colorbar argument: shrink the colorbar by the factor (optional) conditional - normalize each column separately (for probability densities, i.e., cntrmass=True) gcf=True does not start a new figure (does change the ranges and labels) Contours: justcontours - if True, only draw contours contours - if True, draw contours (10 by default) levels - contour-levels cntrmass - if True, the density is a probability and the levels are probability masses contained within the contour cntrcolors - colors for contours (single color or array) cntrlabel - label the contours cntrlw, cntrls - linewidths and linestyles for contour cntrlabelsize, cntrlabelcolors,cntrinline - contour arguments cntrSmooth - use ndimage.gaussian_filter to smooth before contouring onedhists - if True, make one-d histograms on the sides onedhistcolor - histogram color retAxes= return all Axes instances retCont= return the contour instance OUTPUT: plot to output device, Axes instances depending on input HISTORY: 2010-03-09 - Written - Bovy (NYU) """ overplot= kwargs.pop('overplot',False) gcf= kwargs.pop('gcf',False) if not overplot and not gcf: pyplot.figure() xlabel= kwargs.pop('xlabel',None) ylabel= kwargs.pop('ylabel',None) zlabel= kwargs.pop('zlabel',None) if 'extent' in kwargs: extent= kwargs.pop('extent') else: xlimits= kwargs.pop('xrange',[0,X.shape[1]]) ylimits= kwargs.pop('yrange',[0,X.shape[0]]) extent= xlimits+ylimits if not 'aspect' in kwargs: kwargs['aspect']= (xlimits[1]-xlimits[0])/float(ylimits[1]-ylimits[0]) noaxes= kwargs.pop('noaxes',False) justcontours= kwargs.pop('justcontours',False) if ('contours' in kwargs and kwargs['contours']) or \ 'levels' in kwargs or justcontours or \ ('cntrmass' in kwargs and kwargs['cntrmass']): contours= True else: contours= False kwargs.pop('contours',None) if 'levels' in kwargs: levels= kwargs['levels'] kwargs.pop('levels') elif contours: if 'cntrmass' in kwargs and kwargs['cntrmass']: levels= sc.linspace(0.,1.,_DEFAULTNCNTR) elif True in sc.isnan(sc.array(X)): levels= sc.linspace(sc.nanmin(X),sc.nanmax(X),_DEFAULTNCNTR) else: levels= sc.linspace(sc.amin(X),sc.amax(X),_DEFAULTNCNTR) cntrmass= kwargs.pop('cntrmass',False) conditional= kwargs.pop('conditional',False) cntrcolors= kwargs.pop('cntrcolors','k') cntrlabel= kwargs.pop('cntrlabel',False) cntrlw= kwargs.pop('cntrlw',None) cntrls= kwargs.pop('cntrls',None) cntrSmooth= kwargs.pop('cntrSmooth',None) cntrlabelsize= kwargs.pop('cntrlabelsize',None) cntrlabelcolors= kwargs.pop('cntrlabelcolors',None) cntrinline= kwargs.pop('cntrinline',None) retCumImage= kwargs.pop('retCumImage',False) cb= kwargs.pop('colorbar',False) shrink= kwargs.pop('shrink',None) onedhists= kwargs.pop('onedhists',False) onedhistcolor= kwargs.pop('onedhistcolor','k') retAxes= kwargs.pop('retAxes',False) retCont= kwargs.pop('retCont',False) if onedhists: if overplot or gcf: fig= pyplot.gcf() else: fig= pyplot.figure() nullfmt = NullFormatter() # no labels # definitions for the axes left, width = 0.1, 0.65 bottom, height = 0.1, 0.65 bottom_h = left_h = left+width rect_scatter = [left, bottom, width, height] rect_histx = [left, bottom_h, width, 0.2] rect_histy = [left_h, bottom, 0.2, height] axScatter = pyplot.axes(rect_scatter) axHistx = pyplot.axes(rect_histx) axHisty = pyplot.axes(rect_histy) # no labels axHistx.xaxis.set_major_formatter(nullfmt) axHistx.yaxis.set_major_formatter(nullfmt) axHisty.xaxis.set_major_formatter(nullfmt) axHisty.yaxis.set_major_formatter(nullfmt) fig.sca(axScatter) ax=pyplot.gca() ax.set_autoscale_on(False) if conditional: plotthis= X/sc.tile(sc.sum(X,axis=0),(X.shape[1],1)) else: plotthis= X if not justcontours: out= pyplot.imshow(plotthis,extent=extent,**kwargs) if not overplot: pyplot.axis(extent) _add_axislabels(xlabel,ylabel) _add_ticks() #Add colorbar if cb and not justcontours: if shrink is None: shrink= sc.amin([float(kwargs.pop('aspect',1.))*0.87,1.]) CB1= pyplot.colorbar(out,shrink=shrink) if not zlabel is None: if zlabel[0] != '$': thiszlabel=r'$'+zlabel+'$' else: thiszlabel=zlabel CB1.set_label(thiszlabel) if contours or retCumImage: aspect= kwargs.get('aspect',None) origin= kwargs.get('origin',None) if cntrmass: #Sum from the top down! plotthis[sc.isnan(plotthis)]= 0. sortindx= sc.argsort(plotthis.flatten())[::-1] cumul= sc.cumsum(sc.sort(plotthis.flatten())[::-1])/sc.sum(plotthis.flatten()) cntrThis= sc.zeros(sc.prod(plotthis.shape)) cntrThis[sortindx]= cumul cntrThis= sc.reshape(cntrThis,plotthis.shape) else: cntrThis= plotthis if contours: if not cntrSmooth is None: cntrThis= ndimage.gaussian_filter(cntrThis,cntrSmooth, mode='nearest') cont= pyplot.contour(cntrThis,levels,colors=cntrcolors, linewidths=cntrlw,extent=extent,aspect=aspect, linestyles=cntrls,origin=origin) if cntrlabel: pyplot.clabel(cont,fontsize=cntrlabelsize, colors=cntrlabelcolors, inline=cntrinline) if noaxes: ax.set_axis_off() #Add onedhists if not onedhists: if retCumImage: return cntrThis elif retAxes: return pyplot.gca() elif retCont: return cont elif justcontours: return cntrThis else: return out histx= sc.nansum(X.T,axis=1)*m.fabs(ylimits[1]-ylimits[0])/X.shape[1] #nansum bc nan is *no dens value* histy= sc.nansum(X.T,axis=0)*m.fabs(xlimits[1]-xlimits[0])/X.shape[0] histx[sc.isnan(histx)]= 0. histy[sc.isnan(histy)]= 0. dx= (extent[1]-extent[0])/float(len(histx)) axHistx.plot(sc.linspace(extent[0]+dx,extent[1]-dx,len(histx)),histx, drawstyle='steps-mid',color=onedhistcolor) dy= (extent[3]-extent[2])/float(len(histy)) axHisty.plot(histy,sc.linspace(extent[2]+dy,extent[3]-dy,len(histy)), drawstyle='steps-mid',color=onedhistcolor) axHistx.set_xlim( axScatter.get_xlim() ) axHisty.set_ylim( axScatter.get_ylim() ) axHistx.set_ylim( 0, 1.2*sc.amax(histx)) axHisty.set_xlim( 0, 1.2*sc.amax(histy)) if retCumImage: return cntrThis elif retAxes: return (axScatter,axHistx,axHisty) elif justcontours: return cntrThis else: return out def bovy_print(fig_width=5,fig_height=5,axes_labelsize=16, text_fontsize=11,legend_fontsize=12, xtick_labelsize=10,ytick_labelsize=10, xtick_minor_size=2,ytick_minor_size=2, xtick_major_size=4,ytick_major_size=4): """ NAME: bovy_print PURPOSE: setup a figure for plotting INPUT: fig_width - width in inches fig_height - height in inches axes_labelsize - size of the axis-labels text_fontsize - font-size of the text (if any) legend_fontsize - font-size of the legend (if any) xtick_labelsize - size of the x-axis labels ytick_labelsize - size of the y-axis labels xtick_minor_size - size of the minor x-ticks ytick_minor_size - size of the minor y-ticks OUTPUT: (none) HISTORY: 2009-12-23 - Written - Bovy (NYU) """ fig_size = [fig_width,fig_height] params = {'axes.labelsize': axes_labelsize, 'font.size': text_fontsize, 'legend.fontsize': legend_fontsize, 'xtick.labelsize':xtick_labelsize, 'ytick.labelsize':ytick_labelsize, 'text.usetex': True, 'figure.figsize': fig_size, 'xtick.major.size' : xtick_major_size, 'ytick.major.size' : ytick_major_size, 'xtick.minor.size' : xtick_minor_size, 'ytick.minor.size' : ytick_minor_size, 'legend.numpoints':1, 'xtick.top': True, 'xtick.direction': 'in', 'ytick.right': True, 'ytick.direction': 'in'} pyplot.rcParams.update(params) rc('text.latex', preamble=r'\usepackage{amsmath}'+'\n' +r'\usepackage{amssymb}') def bovy_text(*args,**kwargs): """ NAME: bovy_text PURPOSE: thin wrapper around matplotlib's text and annotate use keywords: 'bottom_left=True' 'bottom_right=True' 'top_left=True' 'top_right=True' 'title=True' to place the text in one of the corners or use it as the title INPUT: see matplotlib's text (http://matplotlib.sourceforge.net/api/pyplot_api.html#matplotlib.pyplot.text) OUTPUT: prints text on the current figure HISTORY: 2010-01-26 - Written - Bovy (NYU) """ if kwargs.pop('title',False): pyplot.annotate(args[0],(0.5,1.05),xycoords='axes fraction', horizontalalignment='center', verticalalignment='top',**kwargs) elif kwargs.pop('bottom_left',False): pyplot.annotate(args[0],(0.05,0.05),xycoords='axes fraction',**kwargs) elif kwargs.pop('bottom_right',False): pyplot.annotate(args[0],(0.95,0.05),xycoords='axes fraction', horizontalalignment='right',**kwargs) elif kwargs.pop('top_right',False): pyplot.annotate(args[0],(0.95,0.95),xycoords='axes fraction', horizontalalignment='right', verticalalignment='top',**kwargs) elif kwargs.pop('top_left',False): pyplot.annotate(args[0],(0.05,0.95),xycoords='axes fraction', verticalalignment='top',**kwargs) else: pyplot.text(*args,**kwargs) def scatterplot(x,y,*args,**kwargs): """ NAME: scatterplot PURPOSE: make a 'smart' scatterplot that is a density plot in high-density regions and a regular scatterplot for outliers INPUT: x, y xlabel - (raw string!) x-axis label, LaTeX math mode, no $s needed ylabel - (raw string!) y-axis label, LaTeX math mode, no $s needed xrange yrange bins - number of bins to use in each dimension weights - data-weights aspect - aspect ratio conditional - normalize each column separately (for probability densities, i.e., cntrmass=True) gcf=True does not start a new figure (does change the ranges and labels) contours - if False, don't plot contours justcontours - if True, only draw contours, no density cntrcolors - color of contours (can be array as for bovy_dens2d) cntrlw, cntrls - linewidths and linestyles for contour cntrSmooth - use ndimage.gaussian_filter to smooth before contouring levels - contour-levels; data points outside of the last level will be individually shown (so, e.g., if this list is descending, contours and data points will be overplotted) onedhists - if True, make one-d histograms on the sides onedhistx - if True, make one-d histograms on the side of the x distribution onedhisty - if True, make one-d histograms on the side of the y distribution onedhistcolor, onedhistfc, onedhistec onedhistxnormed, onedhistynormed - normed keyword for one-d histograms onedhistxweights, onedhistyweights - weights keyword for one-d histograms cmap= cmap for density plot hist= and edges= - you can supply the histogram of the data yourself, this can be useful if you want to censor the data, both need to be set and calculated using scipy.histogramdd with the given range retAxes= return all Axes instances OUTPUT: plot to output device, Axes instance(s) or not, depending on input HISTORY: 2010-04-15 - Written - Bovy (NYU) """ xlabel= kwargs.pop('xlabel',None) ylabel= kwargs.pop('ylabel',None) if 'xrange' in kwargs: xrange= kwargs.pop('xrange') else: if isinstance(x,list): xrange=[sc.amin(x),sc.amax(x)] else: xrange=[x.min(),x.max()] if 'yrange' in kwargs: yrange= kwargs.pop('yrange') else: if isinstance(y,list): yrange=[sc.amin(y),sc.amax(y)] else: yrange=[y.min(),y.max()] ndata= len(x) bins= kwargs.pop('bins',round(0.3*sc.sqrt(ndata))) weights= kwargs.pop('weights',None) levels= kwargs.pop('levels',special.erf(sc.arange(1,4)/sc.sqrt(2.))) aspect= kwargs.pop('aspect',(xrange[1]-xrange[0])/(yrange[1]-yrange[0])) conditional= kwargs.pop('conditional',False) contours= kwargs.pop('contours',True) justcontours= kwargs.pop('justcontours',False) cntrcolors= kwargs.pop('cntrcolors','k') cntrlw= kwargs.pop('cntrlw',None) cntrls= kwargs.pop('cntrls',None) cntrSmooth= kwargs.pop('cntrSmooth',None) onedhists= kwargs.pop('onedhists',False) onedhistx= kwargs.pop('onedhistx',onedhists) onedhisty= kwargs.pop('onedhisty',onedhists) onedhisttype= kwargs.pop('onedhisttype','step') onedhistcolor= kwargs.pop('onedhistcolor','k') onedhistfc= kwargs.pop('onedhistfc','w') onedhistec= kwargs.pop('onedhistec','k') onedhistls= kwargs.pop('onedhistls','solid') onedhistlw= kwargs.pop('onedhistlw',None) onedhistsbins= kwargs.pop('onedhistsbins',round(0.3*sc.sqrt(ndata))) overplot= kwargs.pop('overplot',False) gcf= kwargs.pop('gcf',False) cmap= kwargs.pop('cmap',cm.gist_yarg) onedhistxnormed= kwargs.pop('onedhistxnormed',True) onedhistynormed= kwargs.pop('onedhistynormed',True) onedhistxweights= kwargs.pop('onedhistxweights',weights) onedhistyweights= kwargs.pop('onedhistyweights',weights) retAxes= kwargs.pop('retAxes',False) if onedhists or onedhistx or onedhisty: if overplot or gcf: fig= pyplot.gcf() else: fig= pyplot.figure() nullfmt = NullFormatter() # no labels # definitions for the axes left, width = 0.1, 0.65 bottom, height = 0.1, 0.65 bottom_h = left_h = left+width rect_scatter = [left, bottom, width, height] rect_histx = [left, bottom_h, width, 0.2] rect_histy = [left_h, bottom, 0.2, height] axScatter = pyplot.axes(rect_scatter) if onedhistx: axHistx = pyplot.axes(rect_histx) # no labels axHistx.xaxis.set_major_formatter(nullfmt) axHistx.yaxis.set_major_formatter(nullfmt) if onedhisty: axHisty = pyplot.axes(rect_histy) # no labels axHisty.xaxis.set_major_formatter(nullfmt) axHisty.yaxis.set_major_formatter(nullfmt) fig.sca(axScatter) data= sc.array([x,y]).T if 'hist' in kwargs and 'edges' in kwargs: hist=kwargs['hist'] kwargs.pop('hist') edges=kwargs['edges'] kwargs.pop('edges') else: hist, edges= sc.histogramdd(data,bins=bins,range=[xrange,yrange], weights=weights) if contours: cumimage= bovy_dens2d(hist.T,contours=contours,levels=levels, cntrmass=contours,cntrSmooth=cntrSmooth, cntrcolors=cntrcolors,cmap=cmap,origin='lower', xrange=xrange,yrange=yrange,xlabel=xlabel, ylabel=ylabel,interpolation='nearest', retCumImage=True,aspect=aspect, conditional=conditional, cntrlw=cntrlw,cntrls=cntrls, justcontours=justcontours,zorder=5*justcontours, overplot=(gcf or onedhists or overplot or onedhistx or onedhisty)) else: cumimage= bovy_dens2d(hist.T,contours=contours, cntrcolors=cntrcolors, cmap=cmap,origin='lower', xrange=xrange,yrange=yrange,xlabel=xlabel, ylabel=ylabel,interpolation='nearest', conditional=conditional, retCumImage=True,aspect=aspect, cntrlw=cntrlw,cntrls=cntrls, overplot=(gcf or onedhists or overplot or onedhistx or onedhisty)) #Set axes and labels pyplot.axis(list(xrange)+list(yrange)) if not overplot: _add_axislabels(xlabel,ylabel) _add_ticks() binxs= [] xedge= edges[0] for ii in range(len(xedge)-1): binxs.append((xedge[ii]+xedge[ii+1])/2.) binxs= sc.array(binxs) binys= [] yedge= edges[1] for ii in range(len(yedge)-1): binys.append((yedge[ii]+yedge[ii+1])/2.) binys= sc.array(binys) cumInterp= interpolate.RectBivariateSpline(binxs,binys,cumimage.T, kx=1,ky=1) cums= [] for ii in range(len(x)): cums.append(cumInterp(x[ii],y[ii])[0,0]) cums= sc.array(cums) plotx= x[cums > levels[-1]] ploty= y[cums > levels[-1]] if not len(plotx) == 0: if not weights == None: w8= weights[cums > levels[-1]] for ii in range(len(plotx)): bovy_plot(plotx[ii],ploty[ii],overplot=True, color='%.2f'%(1.-w8[ii]),*args,**kwargs) else: bovy_plot(plotx,ploty,overplot=True,zorder=1,*args,**kwargs) #Add onedhists if not (onedhists or onedhistx or onedhisty): if retAxes: return pyplot.gca() else: return None if onedhistx: histx, edges, patches= axHistx.hist(x,bins=onedhistsbins, normed=onedhistxnormed, weights=onedhistxweights, histtype=onedhisttype, range=sorted(xrange), color=onedhistcolor,fc=onedhistfc, ec=onedhistec,ls=onedhistls, lw=onedhistlw) if onedhisty: histy, edges, patches= axHisty.hist(y,bins=onedhistsbins, orientation='horizontal', weights=onedhistyweights, normed=onedhistynormed, histtype=onedhisttype, range=sorted(yrange), color=onedhistcolor,fc=onedhistfc, ec=onedhistec,ls=onedhistls, lw=onedhistlw) if onedhistx and not overplot: axHistx.set_xlim( axScatter.get_xlim() ) axHistx.set_ylim( 0, 1.2*sc.amax(histx)) if onedhisty and not overplot: axHisty.set_ylim( axScatter.get_ylim() ) axHisty.set_xlim( 0, 1.2*sc.amax(histy)) if not onedhistx: axHistx= None if not onedhisty: axHisty= None if retAxes: return (axScatter,axHistx,axHisty) else: return None def _add_axislabels(xlabel,ylabel): """ NAME: _add_axislabels PURPOSE: add axis labels to the current figure INPUT: xlabel - (raw string!) x-axis label, LaTeX math mode, no $s needed ylabel - (raw string!) y-axis label, LaTeX math mode, no $s needed OUTPUT: (none; works on the current axes) HISTORY: 2009-12-23 - Written - Bovy (NYU) """ if xlabel != None: if xlabel[0] != '$': thisxlabel=r'$'+xlabel+'$' else: thisxlabel=xlabel pyplot.xlabel(thisxlabel) if ylabel != None: if ylabel[0] != '$': thisylabel=r'$'+ylabel+'$' else: thisylabel=ylabel pyplot.ylabel(thisylabel) def _add_ticks(xticks=True,yticks=True): """ NAME: _add_ticks PURPOSE: add minor axis ticks to a plot INPUT: (none; works on the current axes) OUTPUT: (none; works on the current axes) HISTORY: 2009-12-23 - Written - Bovy (NYU) """ ax=pyplot.gca() if xticks: xstep= ax.xaxis.get_majorticklocs() xstep= xstep[1]-xstep[0] ax.xaxis.set_minor_locator(ticker.MultipleLocator(xstep/5.)) if yticks: ystep= ax.yaxis.get_majorticklocs() ystep= ystep[1]-ystep[0] ax.yaxis.set_minor_locator(ticker.MultipleLocator(ystep/5.)) class GalPolarAxes(PolarAxes): ''' A variant of PolarAxes where theta increases clockwise ''' name = 'galpolar' class GalPolarTransform(PolarAxes.PolarTransform): def transform(self, tr): xy = sc.zeros(tr.shape, sc.float_) t = tr[:, 0:1] r = tr[:, 1:2] x = xy[:, 0:1] y = xy[:, 1:2] x[:] = r * sc.cos(t) y[:] = -r * sc.sin(t) return xy transform_non_affine = transform def inverted(self): return GalPolarAxes.InvertedGalPolarTransform() class InvertedGalPolarTransform(PolarAxes.InvertedPolarTransform): def transform(self, xy): x = xy[:, 0:1] y = xy[:, 1:] r = sc.sqrt(x*x + y*y) theta = sc.arctan2(y, x) return sc.concatenate((theta, r), 1) def inverted(self): return GalPolarAxes.GalPolarTransform() def _set_lim_and_transforms(self): PolarAxes._set_lim_and_transforms(self) self.transProjection = self.GalPolarTransform() self.transData = ( self.transScale + self.transProjection + (self.transProjectionAffine + self.transAxes)) self._xaxis_transform = ( self.transProjection + self.PolarAffine(IdentityTransform(), Bbox.unit()) + self.transAxes) self._xaxis_text1_transform = ( self._theta_label1_position + self._xaxis_transform) self._yaxis_transform = ( Affine2D().scale(sc.pi * 2.0, 1.0) + self.transData) self._yaxis_text1_transform = ( self._r_label1_position + Affine2D().scale(1.0 / 360.0, 1.0) + self._yaxis_transform) register_projection(GalPolarAxes) galpy-1.3.0/galpy/util/bovy_quadpack.py0000644000076500000240000000123512671130470020226 0ustar bovystaff00000000000000############################################################################### # bovy_quadpack: some variations on scipy's quadpack ############################################################################### from scipy.integrate import quad def _infunc(x,func,gfun,hfun,more_args,epsrel,epsabs): a = gfun(x) b = hfun(x) myargs = (x,) + more_args retval= quad(func,a,b,args=myargs,epsrel=epsrel,epsabs=epsabs) #print x, a, b, retval return retval[0] def dblquad(func, a, b, gfun, hfun, args=(), epsabs=1.49e-8, epsrel=1.49e-8): return quad(_infunc,a,b,(func,gfun,hfun,args,epsrel,epsabs), epsabs=epsabs,epsrel=epsrel) galpy-1.3.0/galpy/util/bovy_rk.c0000644000076500000240000006067613003465421016656 0ustar bovystaff00000000000000/* C implementations of Runge-Kutta integrators */ /* Copyright (c) 2011, Jo Bovy All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. The name of the author may not be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include "signal.h" #include #include #define _MAX_STEPCHANGE_POWERTWO 3. #define _MIN_STEPCHANGE_POWERTWO -3. #define _MAX_STEPREDUCE 10000. #define _MAX_DT_REDUCE 10000. /* Runge-Kutta 4 integrator Usage: Provide the acceleration function func with calling sequence func (t,q,a,nargs,args) where double t: time double * q: current value (dimension: dim) double * a: will be set to the derivative by func int nargs: number of arguments the function takes double *args: arguments Other arguments are: int dim: dimension double *yo: initial value, dimension: dim int nt: number of times at which the output is wanted double dt: (optional) stepsize to use, must be an integer divisor of time difference between output steps (NOT CHECKED EXPLICITLY) double *t: times at which the output is wanted (EQUALLY SPACED) int nargs: see above double *args: see above double rtol, double atol: relative and absolute tolerance levels desired Output: double *result: result (nt blocks of size 2dim) int *err: error: -10 if interrupted by CTRL-C (SIGINT) */ void bovy_rk4(void (*func)(double t, double *q, double *a, int nargs, struct potentialArg * potentialArgs), int dim, double * yo, int nt, double dt, double *t, int nargs, struct potentialArg * potentialArgs, double rtol, double atol, double *result, int * err){ //Declare and initialize double *yn= (double *) malloc ( dim * sizeof(double) ); double *yn1= (double *) malloc ( dim * sizeof(double) ); double *ynk= (double *) malloc ( dim * sizeof(double) ); double *a= (double *) malloc ( dim * sizeof(double) ); int ii, jj, kk; save_rk(dim,yo,result); result+= dim; *err= 0; for (ii=0; ii < dim; ii++) *(yn+ii)= *(yo+ii); for (ii=0; ii < dim; ii++) *(yn1+ii)= *(yo+ii); //Estimate necessary stepsize double init_dt= (*(t+1))-(*t); if ( dt == -9999.99 ) { dt= rk4_estimate_step(*func,dim,yo,init_dt,t,nargs,potentialArgs, rtol,atol); } long ndt= (long) (init_dt/dt); //Integrate the system double to= *t; // Handle KeyboardInterrupt gracefully struct sigaction action; memset(&action, 0, sizeof(struct sigaction)); action.sa_handler= handle_sigint; sigaction(SIGINT,&action,NULL); for (ii=0; ii < (nt-1); ii++){ if ( interrupted ) { *err= -10; interrupted= 0; // need to reset, bc library and vars stay in memory break; } for (jj=0; jj < (ndt-1); jj++) { bovy_rk4_onestep(func,dim,yn,yn1,to,dt,nargs,potentialArgs,ynk,a); to+= dt; //reset yn for (kk=0; kk < dim; kk++) *(yn+kk)= *(yn1+kk); } bovy_rk4_onestep(func,dim,yn,yn1,to,dt,nargs,potentialArgs,ynk,a); to+= dt; //save save_rk(dim,yn1,result); result+= dim; //reset yn for (kk=0; kk < dim; kk++) *(yn+kk)= *(yn1+kk); } // Back to default handler action.sa_handler= SIG_DFL; sigaction(SIGINT,&action,NULL); //Free allocated memory free(yn); free(yn1); free(ynk); free(a); //We're done } void bovy_rk4_onestep(void (*func)(double t, double *q, double *a, int nargs, struct potentialArg * potentialArgs), int dim, double * yn,double * yn1, double tn, double dt, int nargs, struct potentialArg * potentialArgs, double * ynk, double * a){ int ii; //calculate k1 func(tn,yn,a,nargs,potentialArgs); for (ii=0; ii < dim; ii++) *(yn1+ii) += dt * *(a+ii) / 6.; for (ii=0; ii < dim; ii++) *(ynk+ii)= *(yn+ii) + dt * *(a+ii) / 2.; //calculate k2 func(tn+dt/2.,ynk,a,nargs,potentialArgs); for (ii=0; ii < dim; ii++) *(yn1+ii) += dt * *(a+ii) / 3.; for (ii=0; ii < dim; ii++) *(ynk+ii)= *(yn+ii) + dt * *(a+ii) / 2.; //calculate k3 func(tn+dt/2.,ynk,a,nargs,potentialArgs); for (ii=0; ii < dim; ii++) *(yn1+ii) += dt * *(a+ii) / 3.; for (ii=0; ii < dim; ii++) *(ynk+ii)= *(yn+ii) + dt * *(a+ii); //calculate k4 func(tn+dt,ynk,a,nargs,potentialArgs); for (ii=0; ii < dim; ii++) *(yn1+ii) += dt * *(a+ii) / 6.; //yn1 is new value } /* RK6 integrator, same calling sequence as RK4 */ void bovy_rk6(void (*func)(double t, double *q, double *a, int nargs, struct potentialArg * potentialArgs), int dim, double * yo, int nt, double dt, double *t, int nargs, struct potentialArg * potentialArgs, double rtol, double atol, double *result, int * err){ //Declare and initialize double *yn= (double *) malloc ( dim * sizeof(double) ); double *yn1= (double *) malloc ( dim * sizeof(double) ); double *ynk= (double *) malloc ( dim * sizeof(double) ); double *a= (double *) malloc ( dim * sizeof(double) ); double *k1= (double *) malloc ( dim * sizeof(double) ); double *k2= (double *) malloc ( dim * sizeof(double) ); double *k3= (double *) malloc ( dim * sizeof(double) ); double *k4= (double *) malloc ( dim * sizeof(double) ); double *k5= (double *) malloc ( dim * sizeof(double) ); int ii, jj, kk; save_rk(dim,yo,result); result+= dim; *err= 0; for (ii=0; ii < dim; ii++) *(yn+ii)= *(yo+ii); for (ii=0; ii < dim; ii++) *(yn1+ii)= *(yo+ii); //Estimate necessary stepsize double init_dt= (*(t+1))-(*t); if ( dt == -9999.99 ) { dt= rk6_estimate_step(*func,dim,yo,init_dt,t,nargs,potentialArgs, rtol,atol); } long ndt= (long) (init_dt/dt); //Integrate the system double to= *t; // Handle KeyboardInterrupt gracefully struct sigaction action; memset(&action, 0, sizeof(struct sigaction)); action.sa_handler= handle_sigint; sigaction(SIGINT,&action,NULL); for (ii=0; ii < (nt-1); ii++){ if ( interrupted ) { *err= -10; interrupted= 0; // need to reset, bc library and vars stay in memory break; } for (jj=0; jj < (ndt-1); jj++) { bovy_rk6_onestep(func,dim,yn,yn1,to,dt,nargs,potentialArgs,ynk,a, k1,k2,k3,k4,k5); to+= dt; //reset yn for (kk=0; kk < dim; kk++) *(yn+kk)= *(yn1+kk); } bovy_rk6_onestep(func,dim,yn,yn1,to,dt,nargs,potentialArgs,ynk,a, k1,k2,k3,k4,k5); to+= dt; //save save_rk(dim,yn1,result); result+= dim; //reset yn for (kk=0; kk < dim; kk++) *(yn+kk)= *(yn1+kk); } // Back to default handler action.sa_handler= SIG_DFL; sigaction(SIGINT,&action,NULL); //Free allocated memory free(yn); free(yn1); free(ynk); free(a); free(k1); free(k2); free(k3); free(k4); free(k5); //We're done } /* RK6 SOLVER: needs 7 function evaluations per step x[i+1] = x[i] + (11*k_1 + 81*k_3 + 81*k_4 - 32*k_5 - 32*k_6 + 11*k_7)/120 k_1 = step * dxdt(start, x[i]) k_2 = step * dxdt(start + step/3, x[i] + k_1/3) k_3 = step * dxdt(start + 2*step/3, x[i] + 2 * k_2/3) k_4 = step * dxdt(start + step/3, x[i] + (k_1 + 4*k_2 - k_3)/12) k_5 = step * dxdt(start + step/2, x[i] + (-k_1 + 18*k_2 - 3*k_3 - 6*k_4)/16) k_6 = step * dxdt(start + step/2, x[i] + (9 * k_2 - 3*k_3 - 6*k_4 + 4*k_5)/8) k_7 = step * dxdt(start + step, x[i] + (9*k_1 - 36*k_2 + 63*k_3 + 72*k_4 -64*k_6)/44) */ void bovy_rk6_onestep(void (*func)(double t, double *q, double *a, int nargs, struct potentialArg * potentialArgs), int dim, double * yn,double * yn1, double tn, double dt, int nargs, struct potentialArg * potentialArgs, double * ynk, double * a, double * k1, double * k2, double * k3, double * k4, double * k5){ int ii; //calculate k1 func(tn,yn,a,nargs,potentialArgs); for (ii=0; ii < dim; ii++) *(yn1+ii) += 11.* dt * *(a+ii) / 120.; for (ii=0; ii < dim; ii++) *(k1+ii)= dt * *(a+ii); for (ii=0; ii < dim; ii++) *(ynk+ii)= *(yn+ii) + *(k1+ii)/3.; //calculate k2 func(tn+dt/3.,ynk,a,nargs,potentialArgs); for (ii=0; ii < dim; ii++) *(k2+ii)= dt * *(a+ii); for (ii=0; ii < dim; ii++) *(ynk+ii)= *(yn+ii) + 2. * *(k2+ii)/3.; //calculate k3 func(tn+2.*dt/3.,ynk,a,nargs,potentialArgs); for (ii=0; ii < dim; ii++) *(yn1+ii) += 81. * dt * *(a+ii) / 120.; for (ii=0; ii < dim; ii++) *(k3+ii)= dt * *(a+ii); for (ii=0; ii < dim; ii++) *(ynk+ii)= *(yn+ii) + ( *(k1+ii) + 4. * *(k2+ii) - *(k3+ii))/12.; //calculate k4 func(tn+dt/3.,ynk,a,nargs,potentialArgs); for (ii=0; ii < dim; ii++) *(yn1+ii) += 81.* dt * *(a+ii) / 120.; for (ii=0; ii < dim; ii++) *(k4+ii)= dt * *(a+ii); for (ii=0; ii < dim; ii++) *(ynk+ii)= *(yn+ii) + ( -*(k1+ii) + 18. * *(k2+ii) - 3. * *(k3+ii) -6.* *(k4+ii))/16.; //calculate k5 func(tn+dt/2.,ynk,a,nargs,potentialArgs); for (ii=0; ii < dim; ii++) *(yn1+ii) -= 32.* dt * *(a+ii) / 120.; for (ii=0; ii < dim; ii++) *(k5+ii)= dt * *(a+ii); for (ii=0; ii < dim; ii++) *(ynk+ii)= *(yn+ii) + ( 9. * *(k2+ii) - 3. * *(k3+ii) -6.* *(k4+ii) + 4. * *(k5+ii))/8.; //calculate k6 func(tn+dt/2.,ynk,a,nargs,potentialArgs); for (ii=0; ii < dim; ii++) *(yn1+ii) -= 32.* dt * *(a+ii) / 120.; for (ii=0; ii < dim; ii++) *(k5+ii)= dt * *(a+ii); //re-use k5 for k6 for (ii=0; ii < dim; ii++) *(ynk+ii)= *(yn+ii) + ( 9. * *(k1+ii) - 36. * *(k2+ii) +63.* *(k3+ii) + 72. * *(k4+ii) -64. * *(k5+ii))/44.; //calculate k7 func(tn+dt,ynk,a,nargs,potentialArgs); for (ii=0; ii < dim; ii++) *(yn1+ii) += 11.* dt * *(a+ii) / 120.; //yn1 is new value } double rk4_estimate_step(void (*func)(double t, double *y, double *a,int nargs, struct potentialArg *), int dim, double *yo, double dt, double *t, int nargs,struct potentialArg * potentialArgs, double rtol,double atol){ //return dt; //scalars double err= 2.; double max_val; double to= *t; double init_dt= dt; double *yn= (double *) malloc ( dim * sizeof(double) ); double *y1= (double *) malloc ( dim * sizeof(double) ); double *y21= (double *) malloc ( dim * sizeof(double) ); double *y2= (double *) malloc ( dim * sizeof(double) ); double *ynk= (double *) malloc ( dim * sizeof(double) ); double *a= (double *) malloc ( dim * sizeof(double) ); double *scale= (double *) malloc ( dim * sizeof(double) ); int ii; //find maximum values max_val= fabs(*yo); for (ii=1; ii < dim; ii++) if ( fabs(*(yo+ii)) > max_val ) max_val= fabs(*(yo+ii)); //set up scale double c= fmax(atol, rtol * max_val); double s= log(exp(atol-c)+exp(rtol*max_val-c))+c; for (ii=0; ii < dim; ii++) *(scale+ii)= s; //find good dt //dt*= 2.; while ( err > 1. ){ //dt/= 2.; //copy initial codition for (ii=0; ii < dim; ii++) *(yn+ii)= *(yo+ii); for (ii=0; ii < dim; ii++) *(y1+ii)= *(yo+ii); for (ii=0; ii < dim; ii++) *(y21+ii)= *(yo+ii); //do one step with step dt, and one with step dt/2. //dt bovy_rk4_onestep(func,dim,yn,y1,to,dt,nargs,potentialArgs,ynk,a); //dt/2 bovy_rk4_onestep(func,dim,yn,y21,to,dt/2.,nargs,potentialArgs,ynk,a); for (ii=0; ii < dim; ii++) *(y2+ii)= *(y21+ii); bovy_rk4_onestep(func,dim,y21,y2,to+dt/2.,dt/2.,nargs,potentialArgs,ynk,a); //Norm err= 0.; for (ii=0; ii < dim; ii++) { err+= exp(2.*log(fabs(*(y1+ii)-*(y2+ii)))-2.* *(scale+ii)); } err= sqrt(err/dim); if ( ceil(pow(err,1./5.)) > 1. && init_dt / dt * ceil(pow(err,1./5.)) < _MAX_DT_REDUCE) dt/= ceil(pow(err,1./5.)); else break; } //free what we allocated free(yn); free(y1); free(y2); free(y21); free(ynk); free(a); free(scale); //return //printf("%f\n",dt); //fflush(stdout); return dt; } double rk6_estimate_step(void (*func)(double t, double *y, double *a,int nargs, struct potentialArg *), int dim, double *yo, double dt, double *t, int nargs,struct potentialArg * potentialArgs, double rtol,double atol){ //return dt; //scalars double err= 2.; double max_val; double to= *t; double init_dt= dt; double *yn= (double *) malloc ( dim * sizeof(double) ); double *y1= (double *) malloc ( dim * sizeof(double) ); double *y21= (double *) malloc ( dim * sizeof(double) ); double *y2= (double *) malloc ( dim * sizeof(double) ); double *ynk= (double *) malloc ( dim * sizeof(double) ); double *a= (double *) malloc ( dim * sizeof(double) ); double *k1= (double *) malloc ( dim * sizeof(double) ); double *k2= (double *) malloc ( dim * sizeof(double) ); double *k3= (double *) malloc ( dim * sizeof(double) ); double *k4= (double *) malloc ( dim * sizeof(double) ); double *k5= (double *) malloc ( dim * sizeof(double) ); double *scale= (double *) malloc ( dim * sizeof(double) ); int ii; //find maximum values max_val= fabs(*yo); for (ii=1; ii < dim; ii++) if ( fabs(*(yo+ii)) > max_val ) max_val= fabs(*(yo+ii)); //set up scale double c= fmax(atol, rtol * max_val); double s= log(exp(atol-c)+exp(rtol*max_val-c))+c; for (ii=0; ii < dim; ii++) *(scale+ii)= s; //find good dt //dt*= 2.; while ( err > 1. ){ //dt/= 2.; //copy initial codition for (ii=0; ii < dim; ii++) *(yn+ii)= *(yo+ii); for (ii=0; ii < dim; ii++) *(y1+ii)= *(yo+ii); for (ii=0; ii < dim; ii++) *(y21+ii)= *(yo+ii); //do one step with step dt, and one with step dt/2. //dt bovy_rk6_onestep(func,dim,yn,y1,to,dt,nargs,potentialArgs,ynk,a, k1,k2,k3,k4,k5); //dt/2 bovy_rk6_onestep(func,dim,yn,y21,to,dt/2.,nargs,potentialArgs,ynk,a, k1,k2,k3,k4,k5); for (ii=0; ii < dim; ii++) *(y2+ii)= *(y21+ii); bovy_rk6_onestep(func,dim,y21,y2,to+dt/2.,dt/2.,nargs,potentialArgs,ynk,a, k1,k2,k3,k4,k5); //Norm err= 0.; for (ii=0; ii < dim; ii++) { err+= exp(2.*log(fabs(*(y1+ii)-*(y2+ii)))-2.* *(scale+ii)); } err= sqrt(err/dim); if ( ceil(pow(err,1./7.)) > 1. && init_dt / dt * ceil(pow(err,1./7.)) < _MAX_DT_REDUCE) dt/= ceil(pow(err,1./7.)); else break; } //free what we allocated free(yn); free(y1); free(y2); free(y21); free(ynk); free(a); free(scale); free(k1); free(k2); free(k3); free(k4); free(k5); //return //printf("%f\n",dt); //fflush(stdout); return dt; } /* Runge-Kutta Dormand-Prince 5/4 integrator Usage: Provide the acceleration function func with calling sequence func (t,q,a,nargs,args) where double t: time double * q: current value (dimension: dim) double * a: will be set to the derivative by func int nargs: number of arguments the function takes double *args: arguments Other arguments are: int dim: dimension double *yo: initial value, dimension: dim int nt: number of times at which the output is wanted double dt_one: (optional) stepsize to use, must be an integer divisor of time difference between output steps (NOT CHECKED EXPLICITLY) double *t: times at which the output is wanted (EQUALLY SPACED) int nargs: see above double *args: see above double rtol, double atol: relative and absolute tolerance levels desired Output: double *result: result (nt blocks of size 2dim) int * err: if non-zero, something bad happened (1: maximum step reduction happened; -10: interrupted by CTRL-C (SIGINT) */ void bovy_dopr54(void (*func)(double t, double *q, double *a, int nargs, struct potentialArg * potentialArgs), int dim, double * yo, int nt, double dt_one, double *t, int nargs, struct potentialArg * potentialArgs, double rtol, double atol, double *result, int * err){ //Declare and initialize double *a= (double *) malloc ( dim * sizeof(double) ); double *a1= (double *) malloc ( dim * sizeof(double) ); double *k1= (double *) malloc ( dim * sizeof(double) ); double *k2= (double *) malloc ( dim * sizeof(double) ); double *k3= (double *) malloc ( dim * sizeof(double) ); double *k4= (double *) malloc ( dim * sizeof(double) ); double *k5= (double *) malloc ( dim * sizeof(double) ); double *k6= (double *) malloc ( dim * sizeof(double) ); double *yn= (double *) malloc ( dim * sizeof(double) ); double *yn1= (double *) malloc ( dim * sizeof(double) ); double *yerr= (double *) malloc ( dim * sizeof(double) ); double *ynk= (double *) malloc ( dim * sizeof(double) ); int ii; save_rk(dim,yo,result); result+= dim; *err= 0; for (ii=0; ii < dim; ii++) *(yn+ii)= *(yo+ii); double dt= (*(t+1))-(*t); if ( dt_one == -9999.99 ) { dt_one= rk4_estimate_step(*func,dim,yo,dt,t,nargs,potentialArgs, rtol,atol); } //Integrate the system double to= *t; //set up a1 func(to,yn,a1,nargs,potentialArgs); // Handle KeyboardInterrupt gracefully struct sigaction action; memset(&action, 0, sizeof(struct sigaction)); action.sa_handler= handle_sigint; sigaction(SIGINT,&action,NULL); for (ii=0; ii < (nt-1); ii++){ if ( interrupted ) { *err= -10; interrupted= 0; // need to reset, bc library and vars stay in memory break; } bovy_dopr54_onestep(func,dim,yn,dt,&to,&dt_one, nargs,potentialArgs,rtol,atol, a1,a,k1,k2,k3,k4,k5,k6,yn1,yerr,ynk,err); //save save_rk(dim,yn,result); result+= dim; } // Back to default handler action.sa_handler= SIG_DFL; sigaction(SIGINT,&action,NULL); // Free allocated memory free(a); free(a1); free(k1); free(k2); free(k3); free(k4); free(k5); free(k6); free(yn); free(yn1); free(yerr); free(ynk); } //one output step, consists of multiple steps potentially void bovy_dopr54_onestep(void (*func)(double t, double *y, double *a,int nargs, struct potentialArg *), int dim, double *yo, double dt, double *to,double * dt_one, int nargs,struct potentialArg * potentialArgs, double rtol,double atol, double * a1, double * a, double * k1, double * k2, double * k3, double * k4, double * k5, double * k6, double * yn1, double * yerr,double * ynk, int * err){ double init_dt_one= *dt_one; double init_to= *to; unsigned char accept; //printf("%f,%f\n",*to,init_to+dt); while ( ( dt >= 0. && *to < (init_to+dt)) || ( dt < 0. && *to > (init_to+dt)) ) { accept= 0; if ( init_dt_one/ *dt_one > _MAX_STEPREDUCE || *dt_one != *dt_one) { // check for NaN *dt_one= init_dt_one/_MAX_STEPREDUCE; accept= 1; if ( *err % 2 == 0) *err+= 1; } if ( dt >= 0. && *dt_one > (init_to+dt - *to) ) *dt_one= (init_to + dt - *to); if ( dt < 0. && *dt_one < (init_to+dt - *to) ) *dt_one = (init_to + dt - *to); *dt_one= bovy_dopr54_actualstep(func,dim,yo,*dt_one,to,nargs,potentialArgs, rtol,atol, a1,a,k1,k2,k3,k4,k5,k6,yn1,yerr,ynk, accept); } } double bovy_dopr54_actualstep(void (*func)(double t, double *y, double *a,int nargs, struct potentialArg *), int dim, double *yo, double dt, double *to, int nargs,struct potentialArg * potentialArgs, double rtol,double atol, double * a1, double * a, double * k1, double * k2, double * k3, double * k4, double * k5, double * k6, double * yn1, double * yerr,double * ynk, unsigned char accept){ //constant static const double c2= 0.2; static const double c3= 0.3; static const double c4= 0.8; static const double c5= 8./9.; static const double a21= 0.2; static const double a31= 3./40.; static const double a41= 44./45.; static const double a51= 19372./6561; static const double a61= 9017./3168.; static const double a71= 35./384.; static const double a32= 9./40.; static const double a42= -56./15.; static const double a52= -25360./2187.; static const double a62= -355./33.; static const double a43= 32./9.; static const double a53= 64448./6561.; static const double a63= 46732./5247.; static const double a73= 500./1113.; static const double a54= -212./729.; static const double a64= 49./176.; static const double a74= 125./192.; static const double a65= -5103./18656.; static const double a75= -2187./6784.; static const double a76= 11./84.; static const double b1= 35./384.; static const double b3= 500./1113.; static const double b4= 125./192.; static const double b5= -2187./6784.; static const double b6= 11./84.; //coefficients of the error estimate const double be1= b1-5179./57600.; const double be3= b3-7571./16695.; const double be4= b4-393./640.; const double be5= b5+92097./339200.; const double be6= b6-187./2100.; static const double be7= -1./40.; int ii; //setup yn1 for (ii=0; ii < dim; ii++) *(yn1+ii) = *(yo+ii); //calculate k1 for (ii=0; ii < dim; ii++) *(a+ii)= *(a1+ii); for (ii=0; ii < dim; ii++){ *(k1+ii)= dt * *(a+ii); *(yn1+ii) += b1* *(k1+ii); *(yerr+ii) = be1* *(k1+ii); *(ynk+ii)= *(yo+ii) + a21 * *(k1+ii); } //calculate k2 func(*to+c2*dt,ynk,a,nargs,potentialArgs); for (ii=0; ii < dim; ii++){ *(k2+ii)= dt * *(a+ii); *(ynk+ii)= *(yo+ii) + a31 * *(k1+ii) + a32 * *(k2+ii); } //calculate k3 func(*to+c3*dt,ynk,a,nargs,potentialArgs); for (ii=0; ii < dim; ii++){ *(k3+ii)= dt * *(a+ii); *(yn1+ii) += b3* *(k3+ii); *(yerr+ii) += be3* *(k3+ii); *(ynk+ii)= *(yo+ii) + a41 * *(k1+ii) + a42 * *(k2+ii) + a43 * *(k3+ii); } //calculate k4 func(*to+c4*dt,ynk,a,nargs,potentialArgs); for (ii=0; ii < dim; ii++){ *(k4+ii)= dt * *(a+ii); *(yn1+ii) += b4* *(k4+ii); *(yerr+ii) += be4* *(k4+ii); *(ynk+ii)= *(yo+ii) + a51 * *(k1+ii) + a52 * *(k2+ii) + a53 * *(k3+ii) + a54 * *(k4+ii); } //calculate k5 func(*to+c5*dt,ynk,a,nargs,potentialArgs); for (ii=0; ii < dim; ii++){ *(k5+ii)= dt * *(a+ii); *(yn1+ii) += b5* *(k5+ii); *(yerr+ii) += be5* *(k5+ii); *(ynk+ii)= *(yo+ii) + a61 * *(k1+ii) + a62 * *(k2+ii) + a63 * *(k3+ii) + a64 * *(k4+ii) + a65 * *(k5+ii); } //calculate k6 func(*to+dt,ynk,a,nargs,potentialArgs); for (ii=0; ii < dim; ii++){ *(k6+ii)= dt * *(a+ii); *(yn1+ii) += b6* *(k6+ii); *(yerr+ii) += be6* *(k6+ii); *(ynk+ii)= *(yo+ii) + a71 * *(k1+ii) + a73 * *(k3+ii) //a72=0 + a74 * *(k4+ii) + a75 * *(k5+ii) + a76 * *(k6+ii); } //calculate k7 func(*to+dt,ynk,a,nargs,potentialArgs); for (ii=0; ii < dim; ii++) *(yerr+ii) += be7 * dt * *(a+ii); //yn1 is proposed new value //find maximum values double max_val= fabs(*yo); for (ii=1; ii < dim; ii++) if ( fabs(*(yo+ii)) > max_val ) max_val= fabs(*(yo+ii)); //set up scale double c= fmax(atol, rtol * max_val); double s= log(exp(atol-c)+exp(rtol*max_val-c))+c; //Norm double err= 0.; for (ii=0; ii < dim; ii++) err+= exp(2.*log(fabs(*(yerr+ii)))-2.* s); err= sqrt(err/dim); double corr= 0.85*pow(err,-.2); //Round to the nearest power of two double powertwo= round(log(corr)/log(2.)); if ( powertwo > _MAX_STEPCHANGE_POWERTWO ) powertwo= _MAX_STEPCHANGE_POWERTWO; else if ( powertwo < _MIN_STEPCHANGE_POWERTWO ) powertwo= _MIN_STEPCHANGE_POWERTWO; //printf("%f,%f\n",powertwo,err); //fflush(stdout); //accept or reject double dt_one; if ( ( powertwo >= 0. ) || accept ) {//accept, if the step is the smallest possible, always accept for (ii= 0; ii < dim; ii++) { *(a1+ii)= *(a+ii); *(yo+ii)= *(yn1+ii); } *to+= dt; //printf("%f,%f\n",*to,dt); } dt_one= dt*pow(2.,powertwo); return dt_one; } galpy-1.3.0/galpy/util/bovy_rk.h0000644000076500000240000001006313003465421016644 0ustar bovystaff00000000000000/* C implementations of Runge-Kutta integrators */ /* Copyright (c) 2011, Jo Bovy All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. The name of the author may not be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef __BOVY_RK_H__ #define __BOVY_RK_H__ #ifdef __cplusplus extern "C" { #endif /* include */ #include /* Function declarations */ void bovy_rk4(void (*func)(double, double *, double *, int, struct potentialArg *), int, double *, int, double, double *, int, struct potentialArg *, double, double, double *,int *); void bovy_rk4_onestep(void (*func)(double, double *, double *, int, struct potentialArg *), int, double *,double *, double, double, int, struct potentialArg *, double *,double *); void bovy_rk6(void (*func)(double, double *, double *, int, struct potentialArg *), int, double *, int, double, double *, int, struct potentialArg *, double, double, double *,int *); void bovy_rk6_onestep(void (*func)(double, double *, double *, int, struct potentialArg *), int, double *,double *, double, double, int, struct potentialArg *, double *,double *, double *, double *, double * , double *, double *); static inline void save_rk(int dim, double *yo, double *result){ int ii; for (ii=0; ii < dim; ii++) *result++= *yo++; } double rk4_estimate_step(void (*func)(double , double *, double *,int, struct potentialArg *), int, double *, double, double *, int,struct potentialArg *, double,double); double rk6_estimate_step(void (*func)(double , double *, double *,int, struct potentialArg *), int, double *, double, double *, int,struct potentialArg *, double,double); void bovy_dopr54(void (*func)(double, double *, double *, int, struct potentialArg *), int, double *, int, double, double *, int, struct potentialArg *, double, double, double *,int *); void bovy_dopr54_onestep(void (*func)(double, double *, double *,int, struct potentialArg *), int, double *, double, double *,double *, int,struct potentialArg *, double,double, double *, double *, double *, double *, double *, double *, double *, double *, double *, double *, double *,int *); double bovy_dopr54_actualstep(void (*func)(double, double *, double *,int, struct potentialArg *), int, double *, double, double *, int,struct potentialArg *, double,double, double *, double *, double *, double *, double *, double *, double *, double *, double *, double *, double *,unsigned char); #ifdef __cplusplus } #endif #endif /* bovy_rk.h */ galpy-1.3.0/galpy/util/bovy_symplecticode.c0000644000076500000240000007261413003465421021101 0ustar bovystaff00000000000000/* C implementations of symplectic integrators */ /* Copyright (c) 2011, Jo Bovy All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. The name of the author may not be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include #include #include #include "signal.h" #include #define _MAX_DT_REDUCE 10000. volatile sig_atomic_t interrupted= 0; void handle_sigint(int signum) { interrupted= 1; } inline void leapfrog_leapq(int dim, double *q,double *p,double dt,double *qn){ int ii; for (ii=0; ii < dim; ii++) (*qn++)= (*q++) +dt * (*p++); } inline void leapfrog_leapp(int dim, double *p,double dt,double *a,double *pn){ int ii; for (ii=0; ii< dim; ii++) (*pn++)= (*p++) + dt * (*a++); } inline void save_qp(int dim, double *qo, double *po, double *result){ int ii; for (ii=0; ii < dim; ii++) *result++= *qo++; for (ii=0; ii < dim; ii++) *result++= *po++; } /* Leapfrog integrator Usage: Provide the acceleration function func with calling sequence func (t,q,a,nargs,args) where double t: time double * q: current position (dimension: dim) double * a: will be set to the derivative int nargs: number of arguments the function takes struct potentialArg * potentialArg structure pointer, see header file Other arguments are: int dim: dimension double *yo: initial value [qo,po], dimension: 2*dim int nt: number of times at which the output is wanted double dt: (optional) stepsize to use, must be an integer divisor of time difference between output steps (NOT CHECKED EXPLICITLY) double *t: times at which the output is wanted (EQUALLY SPACED) int nargs: see above double *args: see above double rtol, double atol: relative and absolute tolerance levels desired Output: double *result: result (nt blocks of size 2dim) int *err: error: -10 if interrupted by CTRL-C (SIGINT) */ void leapfrog(void (*func)(double t, double *q, double *a, int nargs, struct potentialArg * potentialArgs), int dim, double * yo, int nt, double dt, double *t, int nargs, struct potentialArg * potentialArgs, double rtol, double atol, double *result,int * err){ //Initialize double *qo= (double *) malloc ( dim * sizeof(double) ); double *po= (double *) malloc ( dim * sizeof(double) ); double *q12= (double *) malloc ( dim * sizeof(double) ); double *p12= (double *) malloc ( dim * sizeof(double) ); double *a= (double *) malloc ( dim * sizeof(double) ); int ii, jj, kk; for (ii=0; ii < dim; ii++) { *qo++= *(yo+ii); *po++= *(yo+dim+ii); } qo-= dim; po-= dim; save_qp(dim,qo,po,result); result+= 2 * dim; *err= 0; //Estimate necessary stepsize double init_dt= (*(t+1))-(*t); if ( dt == -9999.99 ) { dt= leapfrog_estimate_step(*func,dim,qo,po,init_dt,t,nargs,potentialArgs, rtol,atol); } long ndt= (long) (init_dt/dt); //Integrate the system double to= *t; // Handle KeyboardInterrupt gracefully struct sigaction action; memset(&action, 0, sizeof(struct sigaction)); action.sa_handler= handle_sigint; sigaction(SIGINT,&action,NULL); for (ii=0; ii < (nt-1); ii++){ if ( interrupted ) { *err= -10; interrupted= 0; // need to reset, bc library and vars stay in memory break; } //drift half leapfrog_leapq(dim,qo,po,dt/2.,q12); //now drift full for a while for (jj=0; jj < (ndt-1); jj++){ //kick func(to+dt/2.,q12,a,nargs,potentialArgs); leapfrog_leapp(dim,po,dt,a,p12); //drift leapfrog_leapq(dim,q12,p12,dt,qo); //reset to= to+dt; for (kk=0; kk < dim; kk++) { *(q12+kk)= *(qo+kk); *(po+kk)= *(p12+kk); } } //end with one last kick and drift //kick func(to+dt/2.,q12,a,nargs,potentialArgs); leapfrog_leapp(dim,po,dt,a,po); //drift leapfrog_leapq(dim,q12,po,dt/2.,qo); to= to+dt; //save save_qp(dim,qo,po,result); result+= 2 * dim; } // Back to default handler action.sa_handler= SIG_DFL; sigaction(SIGINT,&action,NULL); //Free allocated memory free(qo); free(po); free(q12); free(a); //We're done } /* Fourth order symplectic integrator from Kinoshika et al. Usage: Provide the acceleration function func with calling sequence func (t,q,a,nargs,args) where double t: time double * q: current position (dimension: dim) double * a: will be set to the derivative int nargs: number of arguments the function takes struct potentialArg * potentialArg structure pointer, see header file Other arguments are: int dim: dimension double *yo: initial value [qo,po], dimension: 2*dim int nt: number of times at which the output is wanted double dt: (optional) stepsize to use, must be an integer divisor of time difference between output steps (NOT CHECKED EXPLICITLY) double *t: times at which the output is wanted (EQUALLY SPACED) int nargs: see above double *args: see above double rtol, double atol: relative and absolute tolerance levels desired Output: double *result: result (nt blocks of size 2dim) int *err: error: -10 if interrupted by CTRL-C (SIGINT) */ void symplec4(void (*func)(double t, double *q, double *a, int nargs, struct potentialArg * potentialArgs), int dim, double * yo, int nt, double dt, double *t, int nargs, struct potentialArg * potentialArgs, double rtol, double atol, double *result,int * err){ //coefficients double c1= 0.6756035959798289; double c4= c1; double c2= -0.1756035959798288; double c3= c2; double d1= 1.3512071919596578; double d3= d1; double d2= -1.7024143839193153; //d4=0 //Initialize double *qo= (double *) malloc ( dim * sizeof(double) ); double *po= (double *) malloc ( dim * sizeof(double) ); double *q12= (double *) malloc ( dim * sizeof(double) ); double *p12= (double *) malloc ( dim * sizeof(double) ); double *a= (double *) malloc ( dim * sizeof(double) ); int ii, jj, kk; for (ii=0; ii < dim; ii++) { *qo++= *(yo+ii); *po++= *(yo+dim+ii); } qo-= dim; po-= dim; save_qp(dim,qo,po,result); result+= 2 * dim; *err= 0; //Estimate necessary stepsize double init_dt= (*(t+1))-(*t); if ( dt == -9999.99 ) { dt= symplec4_estimate_step(*func,dim,qo,po,init_dt,t,nargs,potentialArgs, rtol,atol); } long ndt= (long) (init_dt/dt); //Integrate the system double to= *t; // Handle KeyboardInterrupt gracefully struct sigaction action; memset(&action, 0, sizeof(struct sigaction)); action.sa_handler= handle_sigint; sigaction(SIGINT,&action,NULL); for (ii=0; ii < (nt-1); ii++){ if ( interrupted ) { *err= -10; interrupted= 0; // need to reset, bc library and vars stay in memory break; } //drift for c1*dt leapfrog_leapq(dim,qo,po,c1*dt,q12); to+= c1*dt; //steps ignoring q4/p4 when output is not wanted for (jj=0; jj < (ndt-1); jj++){ //kick for d1*dt func(to,q12,a,nargs,potentialArgs); leapfrog_leapp(dim,po,d1*dt,a,p12); //drift for c2*dt leapfrog_leapq(dim,q12,p12,c2*dt,qo); //kick for d2*dt to+= c2*dt; func(to,qo,a,nargs,potentialArgs); leapfrog_leapp(dim,p12,d2*dt,a,po); //drift for c3*dt leapfrog_leapq(dim,qo,po,c3*dt,q12); to+= c3*dt; //kick for d3*dt func(to,q12,a,nargs,potentialArgs); leapfrog_leapp(dim,po,d3*dt,a,p12); //drift for (c4+c1)*dt leapfrog_leapq(dim,q12,p12,(c4+c1)*dt,qo); to+= (c4+c1)*dt; //reset for (kk=0; kk < dim; kk++) { *(q12+kk)= *(qo+kk); *(po+kk)= *(p12+kk); } } //steps not ignoring q4/p4 when output is wanted //kick for d1*dt func(to,q12,a,nargs,potentialArgs); leapfrog_leapp(dim,po,d1*dt,a,p12); //drift for c2*dt leapfrog_leapq(dim,q12,p12,c2*dt,qo); //kick for d2*dt to+= c2*dt; func(to,qo,a,nargs,potentialArgs); leapfrog_leapp(dim,p12,d2*dt,a,po); //drift for c3*dt leapfrog_leapq(dim,qo,po,c3*dt,q12); to+= c3*dt; //kick for d3*dt func(to,q12,a,nargs,potentialArgs); leapfrog_leapp(dim,po,d3*dt,a,p12); //drift for c4*dt leapfrog_leapq(dim,q12,p12,c4*dt,qo); to+= c4*dt; //p4=p3 for (kk=0; kk < dim; kk++) *(po+kk)= *(p12+kk); //save save_qp(dim,qo,po,result); result+= 2 * dim; } // Back to default handler action.sa_handler= SIG_DFL; sigaction(SIGINT,&action,NULL); //Free allocated memory free(qo); free(po); free(q12); free(a); //We're done } /* Sixth order symplectic integrator from Kinoshika et al., Yoshida (1990) Usage: Provide the acceleration function func with calling sequence func (t,q,a,nargs,args) where double t: time double * q: current position (dimension: dim) double * a: will be set to the derivative int nargs: number of arguments the function takes struct potentialArg * potentialArg structure pointer, see header file Other arguments are: int dim: dimension double *yo: initial value [qo,po], dimension: 2*dim int nt: number of times at which the output is wanted double dt: (optional) stepsize to use, must be an integer divisor of time difference between output steps (NOT CHECKED EXPLICITLY) double *t: times at which the output is wanted (EQUALLY SPACED) int nargs: see above double *args: see above double rtol, double atol: relative and absolute tolerance levels desired Output: double *result: result (nt blocks of size 2dim) int *err: error: -10 if interrupted by CTRL-C (SIGINT) */ void symplec6(void (*func)(double t, double *q, double *a, int nargs, struct potentialArg * potentialArgs), int dim, double * yo, int nt, double dt, double *t, int nargs, struct potentialArg * potentialArgs, double rtol, double atol, double *result,int * err){ //coefficients double c1= 0.392256805238780; double c8= c1; double c2= 0.510043411918458; double c7= c2; double c3= -0.471053385409758; double c6= c3; double c4= 0.687531682525198e-1; double c5= c4; double d1= 0.784513610477560; double d7= d1; double d2= 0.235573213359357; double d6= d2; double d3= -0.117767998417887e1; double d5= d3; double d4= 0.131518632068391e1; //d8=0 //Initialize double *qo= (double *) malloc ( dim * sizeof(double) ); double *po= (double *) malloc ( dim * sizeof(double) ); double *q12= (double *) malloc ( dim * sizeof(double) ); double *p12= (double *) malloc ( dim * sizeof(double) ); double *a= (double *) malloc ( dim * sizeof(double) ); int ii, jj, kk; for (ii=0; ii < dim; ii++) { *qo++= *(yo+ii); *po++= *(yo+dim+ii); } qo-= dim; po-= dim; save_qp(dim,qo,po,result); result+= 2 * dim; *err= 0; //Estimate necessary stepsize double init_dt= (*(t+1))-(*t); if ( dt == -9999.99 ) { dt= symplec6_estimate_step(*func,dim,qo,po,init_dt,t,nargs,potentialArgs, rtol,atol); } long ndt= (long) (init_dt/dt); //Integrate the system double to= *t; // Handle KeyboardInterrupt gracefully struct sigaction action; memset(&action, 0, sizeof(struct sigaction)); action.sa_handler= handle_sigint; sigaction(SIGINT,&action,NULL); for (ii=0; ii < (nt-1); ii++){ if ( interrupted ) { *err= -10; interrupted= 0; // need to reset, bc library and vars stay in memory break; } //drift for c1*dt leapfrog_leapq(dim,qo,po,c1*dt,q12); to+= c1*dt; //steps ignoring q8/p8 when output is not wanted for (jj=0; jj < (ndt-1); jj++){ //kick for d1*dt func(to,q12,a,nargs,potentialArgs); leapfrog_leapp(dim,po,d1*dt,a,p12); //drift for c2*dt leapfrog_leapq(dim,q12,p12,c2*dt,qo); to+= c2*dt; //kick for d2*dt func(to,qo,a,nargs,potentialArgs); leapfrog_leapp(dim,p12,d2*dt,a,po); //drift for c3*dt leapfrog_leapq(dim,qo,po,c3*dt,q12); to+= c3*dt; //kick for d3*dt func(to,q12,a,nargs,potentialArgs); leapfrog_leapp(dim,po,d3*dt,a,p12); //drift for c4*dt leapfrog_leapq(dim,q12,p12,c4*dt,qo); //kick for d4*dt to+= c4*dt; func(to,qo,a,nargs,potentialArgs); leapfrog_leapp(dim,p12,d4*dt,a,po); //drift for c5*dt leapfrog_leapq(dim,qo,po,c5*dt,q12); to+= c5*dt; //kick for d5*dt func(to,q12,a,nargs,potentialArgs); leapfrog_leapp(dim,po,d5*dt,a,p12); //drift for c6*dt leapfrog_leapq(dim,q12,p12,c6*dt,qo); //kick for d6*dt to+= c6*dt; func(to,qo,a,nargs,potentialArgs); leapfrog_leapp(dim,p12,d6*dt,a,po); //drift for c7*dt leapfrog_leapq(dim,qo,po,c7*dt,q12); to+= c7*dt; //kick for d7*dt func(to,q12,a,nargs,potentialArgs); leapfrog_leapp(dim,po,d7*dt,a,p12); //drift for (c8+c1)*dt leapfrog_leapq(dim,q12,p12,(c8+c1)*dt,qo); to+= (c8+c1)*dt; //reset for (kk=0; kk < dim; kk++) { *(q12+kk)= *(qo+kk); *(po+kk)= *(p12+kk); } } //steps not ignoring q8/p8 when output is wanted //kick for d1*dt func(to,q12,a,nargs,potentialArgs); leapfrog_leapp(dim,po,d1*dt,a,p12); //drift for c2*dt leapfrog_leapq(dim,q12,p12,c2*dt,qo); to+= c2*dt; //kick for d2*dt func(to,qo,a,nargs,potentialArgs); leapfrog_leapp(dim,p12,d2*dt,a,po); //drift for c3*dt leapfrog_leapq(dim,qo,po,c3*dt,q12); to+= c3*dt; //kick for d3*dt func(to,q12,a,nargs,potentialArgs); leapfrog_leapp(dim,po,d3*dt,a,p12); //drift for c4*dt leapfrog_leapq(dim,q12,p12,c4*dt,qo); to+= c4*dt; //kick for d4*dt func(to,qo,a,nargs,potentialArgs); leapfrog_leapp(dim,p12,d4*dt,a,po); //drift for c5*dt leapfrog_leapq(dim,qo,po,c5*dt,q12); to+= c5*dt; //kick for d5*dt func(to,q12,a,nargs,potentialArgs); leapfrog_leapp(dim,po,d5*dt,a,p12); //drift for c6*dt leapfrog_leapq(dim,q12,p12,c6*dt,qo); //kick for d6*dt to+= c6*dt; func(to,qo,a,nargs,potentialArgs); leapfrog_leapp(dim,p12,d6*dt,a,po); //drift for c7*dt leapfrog_leapq(dim,qo,po,c7*dt,q12); to+= c7*dt; //kick for d7*dt func(to,q12,a,nargs,potentialArgs); leapfrog_leapp(dim,po,d7*dt,a,p12); //drift for c8*dt leapfrog_leapq(dim,q12,p12,c8*dt,qo); to+= c8*dt; //p8=p7 for (kk=0; kk < dim; kk++) *(po+kk)= *(p12+kk); //save save_qp(dim,qo,po,result); result+= 2 * dim; } // Back to default handler action.sa_handler= SIG_DFL; sigaction(SIGINT,&action,NULL); //Free allocated memory free(qo); free(po); free(q12); free(a); //We're done } double leapfrog_estimate_step(void (*func)(double t, double *q, double *a,int nargs, struct potentialArg *), int dim, double *qo,double *po, double dt, double *t, int nargs,struct potentialArg * potentialArgs, double rtol,double atol){ //return dt; //scalars double err= 2.; double max_val_q, max_val_p; double to= *t; double init_dt= dt; //allocate and initialize double *q11= (double *) malloc ( dim * sizeof(double) ); double *q12= (double *) malloc ( dim * sizeof(double) ); double *p11= (double *) malloc ( dim * sizeof(double) ); double *p12= (double *) malloc ( dim * sizeof(double) ); double *qtmp= (double *) malloc ( dim * sizeof(double) ); double *ptmp= (double *) malloc ( dim * sizeof(double) ); double *a= (double *) malloc ( dim * sizeof(double) ); double *scale= (double *) malloc ( 2 * dim * sizeof(double) ); int ii; //find maximum values max_val_q= fabs(*qo); for (ii=1; ii < dim; ii++) if ( fabs(*(qo+ii)) > max_val_q ) max_val_q= fabs(*(qo+ii)); max_val_p= fabs(*po); for (ii=1; ii < dim; ii++) if ( fabs(*(po+ii)) > max_val_p ) max_val_p= fabs(*(po+ii)); //set up scale double c= fmax(atol, rtol * max_val_q); double s= log(exp(atol-c)+exp(rtol*max_val_q-c))+c; for (ii=0; ii < dim; ii++) *(scale+ii)= s; c= fmax(atol, rtol * max_val_p); s= log(exp(atol-c)+exp(rtol*max_val_p-c))+c; for (ii=0; ii < dim; ii++) *(scale+ii+dim)= s; //find good dt dt*= 2.; while ( err > 1. && init_dt / dt < _MAX_DT_REDUCE){ dt/= 2.; //do one leapfrog step with step dt, and one with step dt/2. //dt leapfrog_leapq(dim,qo,po,dt/2.,q12); func(to+dt/2.,q12,a,nargs,potentialArgs); leapfrog_leapp(dim,po,dt,a,p11); leapfrog_leapq(dim,q12,p11,dt/2.,q11); //dt/2. leapfrog_leapq(dim,qo,po,dt/4.,q12); func(to+dt/4.,q12,a,nargs,potentialArgs); leapfrog_leapp(dim,po,dt/2.,a,ptmp); leapfrog_leapq(dim,q12,ptmp,dt/2.,qtmp);//Take full step combining two half func(to+3.*dt/4.,qtmp,a,nargs,potentialArgs); leapfrog_leapp(dim,ptmp,dt/2.,a,p12); leapfrog_leapq(dim,qtmp,p12,dt/4.,q12);//Take full step combining two half //Norm err= 0.; for (ii=0; ii < dim; ii++) { err+= exp(2.*log(fabs(*(q11+ii)-*(q12+ii)))-2.* *(scale+ii)); err+= exp(2.*log(fabs(*(p11+ii)-*(p12+ii)))-2.* *(scale+ii+dim)); } err= sqrt(err/2./dim); } //free what we allocated free(q11); free(q12); free(p11); free(qtmp); free(ptmp); free(a); free(scale); //return //printf("%f\n",dt); //fflush(stdout); return dt; } double symplec4_estimate_step(void (*func)(double t, double *q, double *a,int nargs, struct potentialArg *), int dim, double *qo,double *po, double dt, double *t, int nargs,struct potentialArg * potentialArgs, double rtol,double atol){ //return dt; //coefficients double c1= 0.6756035959798289; double c4= c1; double c2= -0.1756035959798288; double c3= c2; double d1= 1.3512071919596578; double d3= d1; double d2= -1.7024143839193153; //d4=0 //scalars double err= 2.; double max_val_q, max_val_p; double to= *t; double init_dt= dt; //allocate and initialize double *q11= (double *) malloc ( dim * sizeof(double) ); double *q12= (double *) malloc ( dim * sizeof(double) ); double *p11= (double *) malloc ( dim * sizeof(double) ); double *p12= (double *) malloc ( dim * sizeof(double) ); double *qtmp= (double *) malloc ( dim * sizeof(double) ); double *ptmp= (double *) malloc ( dim * sizeof(double) ); double *a= (double *) malloc ( dim * sizeof(double) ); double *scale= (double *) malloc ( 2 * dim * sizeof(double) ); int ii; //find maximum values max_val_q= fabs(*qo); for (ii=1; ii < dim; ii++) if ( fabs(*(qo+ii)) > max_val_q ) max_val_q= fabs(*(qo+ii)); max_val_p= fabs(*po); for (ii=1; ii < dim; ii++) if ( fabs(*(po+ii)) > max_val_p ) max_val_p= fabs(*(po+ii)); //set up scale double c= fmax(atol, rtol * max_val_q); double s= log(exp(atol-c)+exp(rtol*max_val_q-c))+c; for (ii=0; ii < dim; ii++) *(scale+ii)= s; c= fmax(atol, rtol * max_val_p); s= log(exp(atol-c)+exp(rtol*max_val_p-c))+c; for (ii=0; ii < dim; ii++) *(scale+ii+dim)= s; //find good dt dt*= 2.; while ( err > 1. && init_dt / dt < _MAX_DT_REDUCE ){ dt/= 2.; //do one step with step dt, and one with step dt/2. /* dt */ //drift for c1*dt leapfrog_leapq(dim,qo,po,c1*dt,q12); to+= c1*dt; //kick for d1*dt func(to,q12,a,nargs,potentialArgs); leapfrog_leapp(dim,po,d1*dt,a,p12); //drift for c2*dt leapfrog_leapq(dim,q12,p12,c2*dt,qtmp); //kick for d2*dt to+= c2*dt; func(to,qtmp,a,nargs,potentialArgs); leapfrog_leapp(dim,p12,d2*dt,a,ptmp); //drift for c3*dt leapfrog_leapq(dim,qtmp,ptmp,c3*dt,q12); to+= c3*dt; //kick for d3*dt func(to,q12,a,nargs,potentialArgs); leapfrog_leapp(dim,ptmp,d3*dt,a,p11); //drift for c4*dt leapfrog_leapq(dim,q12,p11,c4*dt,q11); to+= c4*dt; //p4=p3 //reset to-= dt; /* dt/2 */ //drift for c1*dt/2 leapfrog_leapq(dim,qo,po,c1*dt/2.,q12); to+= c1*dt/2.; //kick for d1*dt/2 func(to,q12,a,nargs,potentialArgs); leapfrog_leapp(dim,po,d1*dt/2.,a,p12); //drift for c2*dt/2 leapfrog_leapq(dim,q12,p12,c2*dt/2.,qtmp); //kick for d2*dt/2 to+= c2*dt/2.; func(to,qtmp,a,nargs,potentialArgs); leapfrog_leapp(dim,p12,d2*dt/2.,a,ptmp); //drift for c3*dt/2 leapfrog_leapq(dim,qtmp,ptmp,c3*dt/2.,q12); to+= c3*dt/2.; //kick for d3*dt/2 func(to,q12,a,nargs,potentialArgs); leapfrog_leapp(dim,ptmp,d3*dt/2.,a,p12); //drift for (c4+c1)*dt/2, skipping q4/p4 leapfrog_leapq(dim,q12,p12,(c1+c4)*dt/2.,qtmp); to+= (c1+c4)*dt/2.; //kick for d1*dt/2 func(to,qtmp,a,nargs,potentialArgs); leapfrog_leapp(dim,p12,d1*dt/2.,a,ptmp); //drift for c2*dt/2 leapfrog_leapq(dim,qtmp,ptmp,c2*dt/2.,q12); //kick for d2*dt/2 to+= c2*dt/2.; func(to,q12,a,nargs,potentialArgs); leapfrog_leapp(dim,ptmp,d2*dt/2.,a,p12); //drift for c3*dt/2 leapfrog_leapq(dim,q12,p12,c3*dt/2.,qtmp); to+= c3*dt/2.; //kick for d3*dt func(to,qtmp,a,nargs,potentialArgs); leapfrog_leapp(dim,p12,d3*dt/2.,a,ptmp); //drift for c4*dt/2. leapfrog_leapq(dim,qtmp,ptmp,c4*dt/2.,q12); to+= c4*dt/2.; //p4=p3 for (ii=0; ii < dim; ii++) *(p12+ii)= *(ptmp+ii); //Norm err= 0.; for (ii=0; ii < dim; ii++) { err+= exp(2.*log(fabs(*(q11+ii)-*(q12+ii)))-2.* *(scale+ii)); err+= exp(2.*log(fabs(*(p11+ii)-*(p12+ii)))-2.* *(scale+ii+dim)); } err= sqrt(err/2./dim); //reset to-= dt; } //free what we allocated free(q11); free(q12); free(p11); free(qtmp); free(ptmp); free(a); free(scale); //return //printf("%f\n",dt); //fflush(stdout); return dt; } double symplec6_estimate_step(void (*func)(double t, double *q, double *a,int nargs, struct potentialArg *), int dim, double *qo,double *po, double dt, double *t, int nargs,struct potentialArg * potentialArgs, double rtol,double atol){ //return dt; //coefficients double c1= 0.392256805238780; double c8= c1; double c2= 0.510043411918458; double c7= c2; double c3= -0.471053385409758; double c6= c3; double c4= 0.687531682525198e-1; double c5= c4; double d1= 0.784513610477560; double d7= d1; double d2= 0.235573213359357; double d6= d2; double d3= -0.117767998417887e1; double d5= d3; double d4= 0.131518632068391e1; //d8=0 //scalars double err= 2.; double max_val_q, max_val_p; double to= *t; double init_dt= dt; //allocate and initialize double *q11= (double *) malloc ( dim * sizeof(double) ); double *q12= (double *) malloc ( dim * sizeof(double) ); double *p11= (double *) malloc ( dim * sizeof(double) ); double *p12= (double *) malloc ( dim * sizeof(double) ); double *qtmp= (double *) malloc ( dim * sizeof(double) ); double *ptmp= (double *) malloc ( dim * sizeof(double) ); double *a= (double *) malloc ( dim * sizeof(double) ); double *scale= (double *) malloc ( 2 * dim * sizeof(double) ); int ii; //find maximum values max_val_q= fabs(*qo); for (ii=1; ii < dim; ii++) if ( fabs(*(qo+ii)) > max_val_q ) max_val_q= fabs(*(qo+ii)); max_val_p= fabs(*po); for (ii=1; ii < dim; ii++) if ( fabs(*(po+ii)) > max_val_p ) max_val_p= fabs(*(po+ii)); //set up scale double c= fmax(atol, rtol * max_val_q); double s= log(exp(atol-c)+exp(rtol*max_val_q-c))+c; for (ii=0; ii < dim; ii++) *(scale+ii)= s; c= fmax(atol, rtol * max_val_p); s= log(exp(atol-c)+exp(rtol*max_val_p-c))+c; for (ii=0; ii < dim; ii++) *(scale+ii+dim)= s; //find good dt dt*= 2.; while ( err > 1. && init_dt / dt < _MAX_DT_REDUCE ){ dt/= 2.; //do one step with step dt, and one with step dt/2. /* dt */ //drift for c1*dt leapfrog_leapq(dim,qo,po,c1*dt,q12); to+= c1*dt; //kick for d1*dt func(to,q12,a,nargs,potentialArgs); leapfrog_leapp(dim,po,d1*dt,a,p12); //drift for c2*dt leapfrog_leapq(dim,q12,p12,c2*dt,qtmp); to+= c2*dt; //kick for d2*dt func(to,qtmp,a,nargs,potentialArgs); leapfrog_leapp(dim,p12,d2*dt,a,ptmp); //drift for c3*dt leapfrog_leapq(dim,qtmp,ptmp,c3*dt,q12); to+= c3*dt; //kick for d3*dt func(to,q12,a,nargs,potentialArgs); leapfrog_leapp(dim,ptmp,d3*dt,a,p12); //drift for c4*dt leapfrog_leapq(dim,q12,p12,c4*dt,qtmp); to+= c4*dt; //kick for d4*dt func(to,qtmp,a,nargs,potentialArgs); leapfrog_leapp(dim,p12,d4*dt,a,ptmp); //drift for c5*dt leapfrog_leapq(dim,qtmp,ptmp,c5*dt,q12); to+= c5*dt; //kick for d5*dt func(to,q12,a,nargs,potentialArgs); leapfrog_leapp(dim,ptmp,d5*dt,a,p12); //drift for c6*dt leapfrog_leapq(dim,q12,p12,c6*dt,qtmp); to+= c6*dt; //kick for d6*dt func(to,qtmp,a,nargs,potentialArgs); leapfrog_leapp(dim,p12,d6*dt,a,ptmp); //drift for c7*dt leapfrog_leapq(dim,qtmp,ptmp,c7*dt,q12); to+= c7*dt; //kick for d7*dt func(to,q12,a,nargs,potentialArgs); leapfrog_leapp(dim,ptmp,d7*dt,a,p11); //drift for c8*dt leapfrog_leapq(dim,q12,p11,c8*dt,q11); to+= c8*dt; //p8=p7 //reset to-= dt; /* dt/2 */ //drift for c1*dt/2 leapfrog_leapq(dim,qo,po,c1*dt/2.,q12); to+= c1*dt/2.; //kick for d1*dt/2 func(to,q12,a,nargs,potentialArgs); leapfrog_leapp(dim,po,d1*dt/2.,a,p12); //drift for c2*dt/2 leapfrog_leapq(dim,q12,p12,c2*dt/2.,qtmp); to+= c2*dt/2.; //kick for d2*dt/2 func(to,qtmp,a,nargs,potentialArgs); leapfrog_leapp(dim,p12,d2*dt/2.,a,ptmp); //drift for c3*dt/2 leapfrog_leapq(dim,qtmp,ptmp,c3*dt/2.,q12); to+= c3*dt/2.; //kick for d3*dt/2 func(to,q12,a,nargs,potentialArgs); leapfrog_leapp(dim,ptmp,d3*dt/2.,a,p12); //drift for c4*dt/2 leapfrog_leapq(dim,q12,p12,c4*dt/2.,qtmp); to+= c4*dt/2.; //kick for d4*dt/2 func(to,qtmp,a,nargs,potentialArgs); leapfrog_leapp(dim,p12,d4*dt/2.,a,ptmp); //drift for c5*dt/2 leapfrog_leapq(dim,qtmp,ptmp,c5*dt/2.,q12); to+= c5*dt/2.; //kick for d5*dt/2 func(to,q12,a,nargs,potentialArgs); leapfrog_leapp(dim,ptmp,d5*dt/2.,a,p12); //drift for c6*dt/2 leapfrog_leapq(dim,q12,p12,c6*dt/2.,qtmp); to+= c6*dt/2.; //kick for d6*dt func(to,qtmp,a,nargs,potentialArgs); leapfrog_leapp(dim,p12,d6*dt/2.,a,ptmp); //drift for c7*dt/2. leapfrog_leapq(dim,qtmp,ptmp,c7*dt/2.,q12); to+= c7*dt/2.; //kick for d7*dt/2 func(to,q12,a,nargs,potentialArgs); leapfrog_leapp(dim,ptmp,d7*dt/2.,a,p12); //drift for (c1+c8)*dt/2 leapfrog_leapq(dim,q12,p12,(c1+c8)*dt/2.,qtmp); to+= (c1+c8)*dt/2.; //kick for d1*dt/2 func(to,qtmp,a,nargs,potentialArgs); leapfrog_leapp(dim,p12,d1*dt/2.,a,ptmp); //drift for c2*dt/2 leapfrog_leapq(dim,qtmp,ptmp,c2*dt/2.,q12); to+= c2*dt/2.; //kick for d2*dt/2 func(to,q12,a,nargs,potentialArgs); leapfrog_leapp(dim,ptmp,d2*dt/2.,a,p12); //drift for c3*dt/2 leapfrog_leapq(dim,q12,p12,c3*dt/2.,qtmp); to+= c3*dt/2.; //kick for d3*dt/2 func(to,qtmp,a,nargs,potentialArgs); leapfrog_leapp(dim,p12,d3*dt/2.,a,ptmp); //drift for c4*dt/2 leapfrog_leapq(dim,qtmp,ptmp,c4*dt/2.,q12); to+= c4*dt/2.; //kick for d4*dt/2 func(to,q12,a,nargs,potentialArgs); leapfrog_leapp(dim,ptmp,d4*dt/2.,a,p12); //drift for c5*dt/2 leapfrog_leapq(dim,q12,p12,c5*dt/2.,qtmp); to+= c5*dt/2.; //kick for d5*dt/2 func(to,qtmp,a,nargs,potentialArgs); leapfrog_leapp(dim,p12,d5*dt/2.,a,ptmp); //drift for c6*dt/2 leapfrog_leapq(dim,qtmp,ptmp,c6*dt/2.,q12); to+= c6*dt/2.; //kick for d6*dt/2 func(to,q12,a,nargs,potentialArgs); leapfrog_leapp(dim,ptmp,d6*dt/2.,a,p12); //drift for c7*dt/2 leapfrog_leapq(dim,q12,p12,c7*dt/2.,qtmp); to+= c7*dt/2.; //kick for d7*dt func(to,qtmp,a,nargs,potentialArgs); leapfrog_leapp(dim,p12,d7*dt/2.,a,ptmp); //drift for c8*dt/2. leapfrog_leapq(dim,qtmp,ptmp,c8*dt/2.,q12); to+= c8*dt/2.; //p8=p7 for (ii=0; ii < dim; ii++) *(p12+ii)= *(ptmp+ii); //Norm err= 0.; for (ii=0; ii < dim; ii++) { err+= exp(2.*log(fabs(*(q11+ii)-*(q12+ii)))-2.* *(scale+ii)); err+= exp(2.*log(fabs(*(p11+ii)-*(p12+ii)))-2.* *(scale+ii+dim)); } err= sqrt(err/2./dim); //reset to-= dt; } //free what we allocated free(q11); free(q12); free(p11); free(qtmp); free(ptmp); free(a); free(scale); //return //printf("%f\n",dt); //fflush(stdout); return dt; } galpy-1.3.0/galpy/util/bovy_symplecticode.h0000644000076500000240000000621113135133615021077 0ustar bovystaff00000000000000/* C implementations of symplectic integrators */ /* Copyright (c) 2011, Jo Bovy All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. The name of the author may not be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #ifndef __BOVY_SYMPLECTICODE_H__ #define __BOVY_SYMPLECTICODE_H__ #ifdef __cplusplus extern "C" { #endif #include "signal.h" #include /* Global variables */ extern volatile sig_atomic_t interrupted; /* Function declarations */ void handle_sigint(int); void leapfrog(void (*func)(double, double *, double *, int, struct potentialArg *), int, double *, int, double, double *, int, struct potentialArg *, double, double, double *,int *); double leapfrog_estimate_step(void (*func)(double , double *, double *,int, struct potentialArg *), int, double *,double *, double, double *, int,struct potentialArg *, double,double); void symplec4(void (*func)(double, double *, double *, int, struct potentialArg *), int, double *, int, double, double *, int, struct potentialArg *, double, double, double *,int *); double symplec4_estimate_step(void (*func)(double , double *, double *,int, struct potentialArg *), int, double *,double *, double, double *, int,struct potentialArg *, double,double); void symplec6(void (*func)(double, double *, double *, int, struct potentialArg *), int, double *, int, double, double *, int, struct potentialArg *, double, double, double *,int *); double symplec6_estimate_step(void (*func)(double , double *, double *,int, struct potentialArg *), int, double *,double *, double, double *, int,struct potentialArg *, double,double); #ifdef __cplusplus } #endif #endif /* bovy_symplecticode.h */ galpy-1.3.0/galpy/util/bovy_symplecticode.py0000644000076500000240000001053513154134615021306 0ustar bovystaff00000000000000############################################################################# #Symplectic ODE integrators #Follows scipy.integrate.odeint inputs as much as possible ############################################################################# ############################################################################# #Copyright (c) 2011, Jo Bovy #All rights reserved. # #Redistribution and use in source and binary forms, with or without #modification, are permitted provided that the following conditions are met: # # Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # Redistributions in binary form must reproduce the above copyright notice, # this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # The name of the author may not be used to endorse or promote products # derived from this software without specific prior written permission. # #THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS #"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT #LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR #A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT #HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, #INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, #BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS #OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED #AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT #LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY #WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE #POSSIBILITY OF SUCH DAMAGE. ############################################################################# import numpy as nu def leapfrog(func,yo,t,args=(),rtol=1.49012e-8,atol=0.): """ NAME: leapfrog PURPOSE: leapfrog integrate an ode INPUT: func - force function of (y,*args) yo - initial condition [q,p] t - set of times at which one wants the result rtol, atol OUTPUT: y : array, shape (len(y0), len(t)) Array containing the value of y for each desired time in t, \ with the initial value y0 in the first row. HISTORY: 2011-02-02 - Written - Bovy (NYU) """ #Initialize qo= yo[0:len(yo)//2] po= yo[len(yo)//2:len(yo)] out= nu.zeros((len(t),len(yo))) out[0,:]= yo #Estimate necessary step size dt= t[1]-t[0] #assumes that the steps are equally spaced init_dt= dt dt= _leapfrog_estimate_step(func,qo,po,dt,t[0],args,rtol,atol) ndt= int(init_dt/dt) #Integrate to= t[0] for ii in range(1,len(t)): for jj in range(ndt): #loop over number of sub-intervals #This could be made faster by combining the drifts #drift q12= leapfrog_leapq(qo,po,dt/2.) #kick force= func(q12,*args,t=to+dt/2) po= leapfrog_leapp(po,dt,force) #drift qo= leapfrog_leapq(q12,po,dt/2.) #Get ready for next to+= dt out[ii,0:len(yo)//2]= qo out[ii,len(yo)//2:len(yo)]= po return out def leapfrog_leapq(q,p,dt): return q+dt*p def leapfrog_leapp(p,dt,force): return p+dt*force def _leapfrog_estimate_step(func,qo,po,dt,to,args,rtol,atol): qmax= nu.amax(nu.fabs(qo))+nu.zeros(len(qo)) pmax= nu.amax(nu.fabs(po))+nu.zeros(len(po)) scale= atol+rtol*nu.array([qmax,pmax]).flatten() err= 2. dt*= 2. while err > 1.: #Do one leapfrog step with step dt and one with dt/2. #dt q12= leapfrog_leapq(qo,po,dt/2.) force= func(q12,*args,t=to+dt/2) p11= leapfrog_leapp(po,dt,force) q11= leapfrog_leapq(q12,p11,dt/2.) #dt/2. q12= leapfrog_leapq(qo,po,dt/4.) force= func(q12,*args,t=to+dt/4) ptmp= leapfrog_leapp(po,dt/2.,force) qtmp= leapfrog_leapq(q12,ptmp,dt/2.)#Take full step combining two half force= func(qtmp,*args,t=to+3.*dt/4) p12= leapfrog_leapp(ptmp,dt/2.,force) q12= leapfrog_leapq(qtmp,p12,dt/4.) #Norm delta= nu.array([nu.fabs(q11-q12),nu.fabs(p11-p12)]).flatten() err= nu.sqrt(nu.mean((delta/scale)**2.)) dt/= 2. return dt galpy-1.3.0/galpy/util/config.py0000644000076500000240000000567613133761631016663 0ustar bovystaff00000000000000import os, os.path try: import configparser except: from six.moves import configparser _APY_LOADED= True try: from astropy import units except ImportError: _APY_LOADED= False # The default configuration default_configuration= {'normalization': {'ro':'8.', 'vo':'220.'}, 'astropy': {'astropy-units':'False', 'astropy-coords':'True'}, 'plot': {'seaborn-bovy-defaults':'False'}} default_filename= os.path.join(os.path.expanduser('~'),'.galpyrc') def check_config(configuration): # Check that the configuration is a valid galpy configuration for sec_key in default_configuration.keys(): if not configuration.has_section(sec_key): return False for key in default_configuration[sec_key]: if not configuration.has_option(sec_key,key): return False return True def write_config(filename,configuration=None): # Writes default if configuration is None writeconfig= configparser.ConfigParser() # Write different sections for sec_key in default_configuration.keys(): writeconfig.add_section(sec_key) for key in default_configuration[sec_key]: if configuration is None \ or not configuration.has_section(sec_key) \ or not configuration.has_option(sec_key,key): writeconfig.set(sec_key,key, default_configuration[sec_key][key]) else: writeconfig.set(sec_key,key,configuration.get(sec_key,key)) with open(filename,'w') as configfile: writeconfig.write(configfile) return None # Read the configuration file __config__= configparser.ConfigParser() cfilename= __config__.read('.galpyrc') if not cfilename: cfilename= __config__.read(default_filename) if not cfilename: write_config(default_filename) __config__.read(default_filename) if not check_config(__config__): write_config(cfilename[-1],__config__) __config__.read(cfilename[-1]) # Set configuration variables on the fly def set_ro(ro): """ NAME: set_ro PURPOSE: set the global configuration value of ro (distance scale) INPUT: ro - scale in kpc or astropy Quantity OUTPUT: (none) HISTORY: 2016-01-05 - Written - Bovy (UofT) """ if _APY_LOADED and isinstance(ro,units.Quantity): ro= ro.to(units.kpc).value __config__.set('normalization','ro',str(ro)) def set_vo(vo): """ NAME: set_vo PURPOSE: set the global configuration value of vo (velocity scale) INPUT: vo - scale in km/s or astropy Quantity OUTPUT: (none) HISTORY: 2016-01-05 - Written - Bovy (UofT) """ if _APY_LOADED and isinstance(vo,units.Quantity): vo= vo.to(units.km/units.s).value __config__.set('normalization','vo',str(vo)) galpy-1.3.0/galpy/util/interp_2d/0000755000076500000240000000000013236420072016707 5ustar bovystaff00000000000000galpy-1.3.0/galpy/util/interp_2d/cubic_bspline_2d_coeffs.c0000644000076500000240000002127712671130470023601 0ustar bovystaff00000000000000/***************************************************************************** * This code is based on work by Phillipe Thevenaz, which can be found * at http://bigwww.epfl.ch/thevenaz/interpolation/ ****************************************************************************/ #include "cubic_bspline_2d_coeffs.h" /*--------------------------------------------------------------------------*/ static void convert_to_interpolation_coefficients ( double c[], /* input samples --> output coefficients */ long data_length, /* number of samples or coefficients */ double z[], /* poles */ long nb_poles, /* number of poles */ double tolerance /* admissible relative error */ ); /*--------------------------------------------------------------------------*/ static void get_column ( double *data, /* input image array */ long width, /* width of the image */ long x, /* x coordinate of the selected line */ double line[], /* output linear array */ long height /* length of the line and height of the image */ ); /*--------------------------------------------------------------------------*/ static void get_row ( double *data, /* input image array */ long y, /* y coordinate of the selected line */ double line[], /* output linear array */ long width /* length of the line and width of the image */ ); /*--------------------------------------------------------------------------*/ static double initial_causal_coefficient ( double c[], /* coefficients */ long data_length, /* number of coefficients */ double z, /* actual pole */ double tolerance /* admissible relative error */ ); /*--------------------------------------------------------------------------*/ static double initial_anticausal_coefficient ( double c[], /* coefficients */ long data_length, /* number of samples or coefficients */ double z /* actual pole */ ); /*--------------------------------------------------------------------------*/ static void put_column ( double *data, /* output image array */ long width, /* width of the image */ long x, /* x coordinate of the selected line */ double line[], /* input linear array */ long height /* length of the line and height of the image */ ); /*--------------------------------------------------------------------------*/ //void put_row now declared in header file /***************************************************************************** * Definition of static procedures ****************************************************************************/ /*--------------------------------------------------------------------------*/ static void convert_to_interpolation_coefficients ( double c[], /* input samples --> output coefficients */ long data_length, /* number of samples or coefficients */ double z[], /* poles */ long nb_poles, /* number of poles */ double tolerance /* admissible relative error */ ) { /* begin convert_to_interpolation_coefficients */ double lambda = 1.0; long n, k; /* special case required by mirror boundaries */ // LCOV_EXCL_START if (data_length == 1L) { return; } // LCOV_EXCL_STOP /* compute the overall gain */ for (k = 0L; k < nb_poles; k++) { lambda = lambda * (1.0 - z[k]) * (1.0 - 1.0 / z[k]); } /* apply the gain */ for (n = 0L; n < data_length; n++) { c[n] *= lambda; } /* loop over all poles */ for (k = 0L; k < nb_poles; k++) { /* causal initialization */ c[0] = initial_causal_coefficient(c, data_length, z[k], tolerance); /* causal recursion */ for (n = 1L; n < data_length; n++) { c[n] += z[k] * c[n - 1L]; } /* anticausal initialization */ c[data_length - 1L] = initial_anticausal_coefficient(c, data_length, z[k]); /* anticausal recursion */ for (n = data_length - 2L; 0 <= n; n--) { c[n] = z[k] * (c[n + 1L] - c[n]); } } } /* end convert_to_interpolation_coefficients */ /*--------------------------------------------------------------------------*/ static double initial_causal_coefficient ( double c[], /* coefficients */ long data_length, /* number of coefficients */ double z, /* actual pole */ double tolerance /* admissible relative error */ ) { /* begin initial_causal_coefficient */ double sum, zn, z2n, iz; long n, horizon; /* this initialization corresponds to mirror boundaries */ horizon = data_length; if (tolerance > 0.0) { horizon = (long)ceil(log(tolerance) / log(fabs(z))); } if (horizon < data_length) { /* accelerated loop */ zn = z; sum = c[0]; for (n = 1L; n < horizon; n++) { sum += zn * c[n]; zn *= z; } return(sum); } else { /* full loop */ // LCOV_EXCL_START zn = z; iz = 1.0 / z; z2n = pow(z, (double)(data_length - 1L)); sum = c[0] + z2n * c[data_length - 1L]; z2n *= z2n * iz; for (n = 1L; n <= data_length - 2L; n++) { sum += (zn + z2n) * c[n]; zn *= z; z2n *= iz; } return(sum / (1.0 - zn * zn)); // LCOV_EXCL_STOP } } /* end initial_causal_coefficient */ /*--------------------------------------------------------------------------*/ static void get_column ( double *data, /* input image array */ long width, /* width of the image */ long x, /* x coordinate of the selected line */ double line[], /* output linear array */ long height /* length of the line */ ) { /* begin get_column */ long y; data = data + (ptrdiff_t)x; for (y = 0L; y < height; y++) { line[y] = (double)*data; data += (ptrdiff_t)width; } } /* end get_column */ /*--------------------------------------------------------------------------*/ static void get_row ( double *data, /* input image array */ long y, /* y coordinate of the selected line */ double line[], /* output linear array */ long width /* length of the line */ ) { /* begin get_row */ long x; data = data + (ptrdiff_t)(y * width); for (x = 0L; x < width; x++) { line[x] = (double)*data++; } } /* end get_row */ /*--------------------------------------------------------------------------*/ static double initial_anticausal_coefficient ( double c[], /* coefficients */ long data_length, /* number of samples or coefficients */ double z /* actual pole */ ) { /* begin initial_anticausal_coefficient */ /* this initialization corresponds to mirror boundaries */ return((z / (z * z - 1.0)) * (z * c[data_length - 2L] + c[data_length - 1L])); } /* end initial_anticausal_coefficient */ /*--------------------------------------------------------------------------*/ static void put_column ( double *data, /* output image array */ long width, /* width of the image */ long x, /* x coordinate of the selected line */ double line[], /* input linear array */ long height /* length of the line and height of the image */ ) { /* begin put_column */ long y; data = data + (ptrdiff_t)x; for (y = 0L; y < height; y++) { *data = (double)line[y]; data += (ptrdiff_t)width; } } /* end put_column */ /*--------------------------------------------------------------------------*/ void put_row ( double *data, /* output image array */ long y, /* y coordinate of the selected line */ double line[], /* input linear array */ long width /* length of the line and width of the image */ ) { /* begin put_row */ long x; data = data + (ptrdiff_t)(y * width); for (x = 0L; x < width; x++) { *data++ = (double)line[x]; } } /* end put_row */ /***************************************************************************** * Definition of extern procedures ****************************************************************************/ /*--------------------------------------------------------------------------*/ extern int samples_to_coefficients ( double *data, /* in-place processing */ long width, /* width of the image */ long height /* height of the image */ ) { /* begin SamplesToCoefficients */ double *line; double pole[4]; long nb_poles; long x, y; nb_poles = 1L; pole[0] = sqrt(3.0) - 2.0; /* convert the image samples into interpolation coefficients */ /* in-place separable process, along x */ line = (double *)malloc((size_t)(width * (long)sizeof(double))); // LCOV_EXCL_START if (line == (double *)NULL) { printf("Row allocation failed\n"); return(1); } // LCOV_EXCL_STOP for (y = 0L; y < height; y++) { get_row(data, y, line, width); convert_to_interpolation_coefficients(line, width, pole, nb_poles, DBL_EPSILON); put_row(data, y, line, width); } free(line); /* in-place separable process, along y */ line = (double *)malloc((size_t)(height * (long)sizeof(double))); // LCOV_EXCL_START if (line == (double *)NULL) { printf("Column allocation failed\n"); return(1); } // LCOV_EXCL_STOP for (x = 0L; x < width; x++) { get_column(data, width, x, line, height); convert_to_interpolation_coefficients(line, height, pole, nb_poles, DBL_EPSILON); put_column(data, width, x, line, height); } free(line); return(0); } /* end SamplesToCoefficients */ galpy-1.3.0/galpy/util/interp_2d/cubic_bspline_2d_coeffs.h0000644000076500000240000000115313003465421023572 0ustar bovystaff00000000000000/***************************************************************************** * This code is based on work by Phillipe Thevenaz, which can be found * at http://bigwww.epfl.ch/thevenaz/interpolation/ ****************************************************************************/ #ifdef __cplusplus extern "C" { #endif #include #include #include #include #include /*--------------------------------------------------------------------------*/ void put_row(double *,long,double *,long); int samples_to_coefficients(double *,long,long); #ifdef __cplusplus } #endif galpy-1.3.0/galpy/util/interp_2d/cubic_bspline_2d_interpol.c0000644000076500000240000002055412671130470024165 0ustar bovystaff00000000000000/***************************************************************************** * This code is based on work by Phillipe Thevenaz, which can be found * at http://bigwww.epfl.ch/thevenaz/interpolation/ ****************************************************************************/ #include "cubic_bspline_2d_interpol.h" /*--------------------------------------------------------------------------*/ extern double cubic_bspline_2d_interpol ( double *coeffs, /* input B-spline array of coefficients */ long width, /* width of the image */ long height, /* height of the image */ double x, /* x coordinate where to interpolate */ double y /* y coordinate where to interpolate */ ) { /* begin InterpolatedValue */ int spline_degree = 3; //double *p; long x_index[4], y_index[4]; double x_weight[4], y_weight[4]; double interpolated; double w/*, w2, w4, t, t0, t1*/; long width2 = 2L * width - 2L, height2 = 2L * height - 2L; long i, j, k; /* compute the interpolation indexes: floor(x) + {-1,0,1,2}, floor(y) + {-1,0,1,2} */ i = (long)floor(x) - spline_degree / 2L; j = (long)floor(y) - spline_degree / 2L; for (k = 0L; k <= spline_degree; k++) { x_index[k] = i++; y_index[k] = j++; } /* compute the interpolation weights */ /* x */ w = x - (double)x_index[1]; x_weight[3] = (1.0 / 6.0) * w * w * w; x_weight[0] = (1.0 / 6.0) + (1.0 / 2.0) * w * (w - 1.0) - x_weight[3]; x_weight[2] = w + x_weight[0] - 2.0 * x_weight[3]; x_weight[1] = 1.0 - x_weight[0] - x_weight[2] - x_weight[3]; /* y */ w = y - (double)y_index[1]; y_weight[3] = (1.0 / 6.0) * w * w * w; y_weight[0] = (1.0 / 6.0) + (1.0 / 2.0) * w * (w - 1.0) - y_weight[3]; y_weight[2] = w + y_weight[0] - 2.0 * y_weight[3]; y_weight[1] = 1.0 - y_weight[0] - y_weight[2] - y_weight[3]; /* apply the mirror boundary conditions */ for (k = 0L; k <= spline_degree; k++) { x_index[k] = (width == 1L) ? (0L) : ((x_index[k] < 0L) ? (-x_index[k] - width2 * ((-x_index[k]) / width2)) : (x_index[k] - width2 * (x_index[k] / width2))); if (width <= x_index[k]) { x_index[k] = width2 - x_index[k]; } y_index[k] = (height == 1L) ? (0L) : ((y_index[k] < 0L) ?(-y_index[k] - height2 * ((-y_index[k]) / height2)) : (y_index[k] - height2 * (y_index[k] / height2))); if (height <= y_index[k]) { y_index[k] = height2 - y_index[k]; } } /* perform interpolation */ interpolated = 0.0; /*for (j = 0L; j <= spline_degree; j++) { p = coeffs + (ptrdiff_t)(y_index[j] * width); w = 0.0; for (i = 0L; i <= spline_degree; i++) { w += x_weight[i] * p[x_index[i]]; } interpolated += y_weight[j] * w; }*/ for(i=0L; i<=spline_degree; i++) { for(j=0L; j<=spline_degree; j++) { interpolated += coeffs[x_index[i]*height+y_index[j]] * x_weight[i] * y_weight[j]; } } return(interpolated); } // LCOV_EXCL_START /*--------------------------------------------------------------------------*/ extern double cubic_bspline_2d_interpol_dx ( double *coeffs, /* input B-spline array of coefficients */ long width, /* width of the image */ long height, /* height of the image */ double x, /* x coordinate where to interpolate */ double y /* y coordinate where to interpolate */ ) { /* begin InterpolatedValue */ int spline_degree = 3; //double *p; long x_index[3], y_index[4]; double x_weight[3], y_weight[4]; double interpolated; double w/*, w2, w4, t, t0, t1*/; long width2 = 2L * width - 2L, height2 = 2L * height - 2L; long i, j, k; /* compute the interpolation indexes */ /* here we have to calculate B^2(x-j+0.5) */ i = (long)floor(x+1) - (spline_degree-1) / 2L; j = (long)floor(y) - spline_degree / 2L; for (k = 0L; k <= spline_degree; k++) { if(k #include #include #include /*--------------------------------------------------------------------------*/ extern double cubic_bspline_2d_interpol ( double *coeffs, /* input B-spline array of coefficients */ long width, /* width of the image */ long height, /* height of the image */ double x, /* x coordinate where to interpolate */ double y /* y coordinate where to interpolate */ ); extern double cubic_bspline_2d_interpol_dx ( double *coeffs, /* input B-spline array of coefficients */ long width, /* width of the image */ long height, /* height of the image */ double x, /* x coordinate where to interpolate */ double y /* y coordinate where to interpolate */ ); extern double cubic_bspline_2d_interpol_dy ( double *coeffs, /* input B-spline array of coefficients */ long width, /* width of the image */ long height, /* height of the image */ double x, /* x coordinate where to interpolate */ double y /* y coordinate where to interpolate */ ); #ifdef __cplusplus } #endif galpy-1.3.0/galpy/util/interp_2d/interp_2d.c0000644000076500000240000001156012671130470020746 0ustar bovystaff00000000000000#include "interp_2d.h" interp_2d * interp_2d_alloc(int size1, int size2) { interp_2d * i2d = (interp_2d *)malloc(sizeof(interp_2d)); i2d->size1 = size1; i2d->size2 = size2; i2d->xa = (double *)malloc(size1*sizeof(double)); i2d->ya = (double *)malloc(size2*sizeof(double)); i2d->za = (double *)malloc(size1*size2*sizeof(double)); return i2d; } void interp_2d_free(interp_2d * i2d) { free(i2d->xa); free(i2d->ya); free(i2d->za); free(i2d); } void interp_2d_init(interp_2d * i2d, const double * xa, const double * ya, const double * za, int type) { i2d->type = type; memcpy(i2d->xa,xa,(i2d->size1)*sizeof(double)); memcpy(i2d->ya,ya,(i2d->size2)*sizeof(double)); memcpy(i2d->za,za,(i2d->size1)*(i2d->size2)*sizeof(double)); // LCOV_EXCL_START if(type==INTERP_2D_CUBIC_BSPLINE) { samples_to_coefficients(i2d->za,i2d->size1,i2d->size2); } // LCOV_EXCL_STOP } // LCOV_EXCL_START double interp_2d_eval_linear(interp_2d * i2d, double x, double y, gsl_interp_accel * accx, gsl_interp_accel * accy) { int size1 = i2d->size1; int size2 = i2d->size2; double * xa = i2d->xa; double * ya = i2d->ya; double * za = i2d->za; //x = (x > xa[size1-1]) ? xa[size1-1] : x; //y = (y > ya[size2-1]) ? ya[size2-1] : y; int ix = gsl_interp_accel_find(accx, xa, size1, x); int iy = gsl_interp_accel_find(accy, ya, size2, y); double z00 = za[ix*size2+iy]; double z01 = za[ix*size2+iy+1]; double z10 = za[(ix+1)*size2+iy]; double z11 = za[(ix+1)*size2+iy+1]; double denom = (xa[ix+1]-xa[ix])*(ya[iy+1]-ya[iy]); return z00*(xa[ix+1]-x)*(ya[iy+1]-y)/denom + z10*(x-xa[ix])*(ya[iy+1]-y)/denom + z01*(xa[ix+1]-x)*(y-ya[iy])/denom + z11*(x-xa[ix])*(y-ya[iy])/denom; } void interp_2d_eval_grad_linear(interp_2d * i2d, double x, double y, double * grad, gsl_interp_accel * accx, gsl_interp_accel * accy) { int size1 = i2d->size1; int size2 = i2d->size2; double * xa = i2d->xa; double * ya = i2d->ya; double * za = i2d->za; //x = (x > xa[size1-1]) ? xa[size1-1] : x; //y = (y > ya[size2-1]) ? ya[size2-1] : y; int ix = gsl_interp_accel_find(accx, xa, size1, x); int iy = gsl_interp_accel_find(accy, ya, size2, y); double z00 = za[ix*size2+iy]; double z01 = za[ix*size2+iy+1]; double z10 = za[(ix+1)*size2+iy]; double z11 = za[(ix+1)*size2+iy+1]; double denom = (xa[ix+1]-xa[ix])*(ya[iy+1]-ya[iy]); grad[0] = -1*z00*(ya[iy+1]-y)/denom + z10*(ya[iy+1]-y)/denom + -1*z01*(y-ya[iy])/denom + z11*(y-ya[iy])/denom; grad[1] = -1*z00*(xa[ix+1]-x)/denom - z10*(x-xa[ix])/denom + z01*(xa[ix+1]-x)/denom + z11*(x-xa[ix])/denom; return; } // LCOV_EXCL_STOP double interp_2d_eval_cubic_bspline(interp_2d * i2d, double x, double y, gsl_interp_accel * accx, gsl_interp_accel * accy) { int size1 = i2d->size1; int size2 = i2d->size2; double * xa = i2d->xa; double * ya = i2d->ya; double * za = i2d->za; x = (x > xa[size1-1]) ? xa[size1-1] : x; x = (x < xa[0]) ? xa[0] : x; y = (y > ya[size2-1]) ? ya[size2-1] : y; y = (y < ya[0]) ? ya[0] : y; int ix = gsl_interp_accel_find(accx, xa, size1, x); int iy = gsl_interp_accel_find(accy, ya, size2, y); double x_norm = ix + (x-xa[ix])/(xa[ix+1]-xa[ix]); double y_norm = iy + (y-ya[iy])/(ya[iy+1]-ya[iy]); return cubic_bspline_2d_interpol(za,size1,size2,x_norm,y_norm); } // LCOV_EXCL_START void interp_2d_eval_grad_cubic_bspline(interp_2d * i2d, double x, double y, double * grad, gsl_interp_accel * accx, gsl_interp_accel * accy) { int size1 = i2d->size1; int size2 = i2d->size2; double * xa = i2d->xa; double * ya = i2d->ya; double * za = i2d->za; int ix = gsl_interp_accel_find(accx, xa, size1, x); int iy = gsl_interp_accel_find(accy, ya, size2, y); double x_norm = ix + (x-xa[ix])/(xa[ix+1]-xa[ix]); double y_norm = iy + (y-ya[iy])/(ya[iy+1]-ya[iy]); grad[0] = cubic_bspline_2d_interpol_dx(za,size1,size2,x_norm,y_norm)/(xa[ix+1]-xa[ix]); grad[1] = cubic_bspline_2d_interpol_dy(za,size1,size2,x_norm,y_norm)/(ya[iy+1]-ya[iy]); return; } double interp_2d_eval(interp_2d * i2d, double x, double y, gsl_interp_accel * accx, gsl_interp_accel * accy) { return (i2d->type == INTERP_2D_CUBIC_BSPLINE ? interp_2d_eval_cubic_bspline(i2d,x,y,accx,accy) : interp_2d_eval_linear(i2d,x,y,accx,accy) ); } void interp_2d_eval_grad(interp_2d * i2d, double x, double y, double * grad, gsl_interp_accel * accx, gsl_interp_accel * accy) { if(i2d->type == INTERP_2D_CUBIC_BSPLINE) { interp_2d_eval_grad_cubic_bspline(i2d,x,y,grad,accx,accy); } else { interp_2d_eval_grad_linear(i2d,x,y,grad,accx,accy); } return; } // LCOV_EXCL_STOP galpy-1.3.0/galpy/util/interp_2d/interp_2d.h0000644000076500000240000000210713003465421020745 0ustar bovystaff00000000000000#ifndef __INTERP_2D_H__ #define __INTERP_2D_H__ #ifdef __cplusplus extern "C" { #endif #include //#include //#include //#include #include #include "cubic_bspline_2d_coeffs.h" #include "cubic_bspline_2d_interpol.h" enum { INTERP_2D_LINEAR=0, INTERP_2D_CUBIC_BSPLINE=1 }; typedef struct { int size1; int size2; double * xa; double * ya; double * za; int type; }interp_2d; interp_2d * interp_2d_alloc(int size1, int size2); void interp_2d_free(interp_2d * i2d); void interp_2d_init(interp_2d * i2d, const double * xa, const double * ya, const double * za, int type); double interp_2d_eval(interp_2d * i2d, double x, double y, gsl_interp_accel * accx, gsl_interp_accel * accy); void interp_2d_eval_grad(interp_2d * i2d, double x, double y, double * grad, gsl_interp_accel * accx, gsl_interp_accel * accy); double interp_2d_eval_cubic_bspline(interp_2d * i2d, double x, double y, gsl_interp_accel * accx,gsl_interp_accel * accy); #ifdef __cplusplus } #endif #endif galpy-1.3.0/galpy/util/multi.py0000644000076500000240000001435612671130470016540 0ustar bovystaff00000000000000#Brian Refsdal's parallel_map, from astropython.org #Not sure what license this is released under, but until I know better: # #Copyright (c) 2010, Brian Refsdal #All rights reserved. # #Redistribution and use in source and binary forms, with or without #modification, are permitted provided that the following conditions are #met: # #1. Redistributions of source code must retain the above copyright #notice, this list of conditions and the following disclaimer. # #2. Redistributions in binary form must reproduce the above copyright #notice, this list of conditions and the following disclaimer in the #documentation and/or other materials provided with the distribution. # #3. The name of the author may not be used to endorse or promote #products derived from this software without specific prior written #permission. # #THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS #"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT #LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR #A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT #HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, #SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT #LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, #DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY #THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT #(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE #OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. from __future__ import print_function import numpy _multi=False _ncpus=1 try: # May raise ImportError import multiprocessing _multi=True # May raise NotImplementedError _ncpus = multiprocessing.cpu_count() except: pass __all__ = ('parallel_map',) def worker(f, ii, chunk, out_q, err_q, lock): """ A worker function that maps an input function over a slice of the input iterable. :param f : callable function that accepts argument from iterable :param ii : process ID :param chunk: slice of input iterable :param out_q: thread-safe output queue :param err_q: thread-safe queue to populate on exception :param lock : thread-safe lock to protect a resource ( useful in extending parallel_map() ) """ vals = [] # iterate over slice for val in chunk: try: result = f(val) except Exception as e: err_q.put(e) return vals.append(result) # output the result and task ID to output queue out_q.put( (ii, vals) ) def run_tasks(procs, err_q, out_q, num): """ A function that executes populated processes and processes the resultant array. Checks error queue for any exceptions. :param procs: list of Process objects :param out_q: thread-safe output queue :param err_q: thread-safe queue to populate on exception :param num : length of resultant array """ # function to terminate processes that are still running. die = (lambda vals : [val.terminate() for val in vals if val.exitcode is None]) try: for proc in procs: proc.start() for proc in procs: proc.join() except Exception as e: # kill all slave processes on ctrl-C try: die(procs) finally: raise e if not err_q.empty(): # kill all on any exception from any one slave try: die(procs) finally: raise err_q.get() # Processes finish in arbitrary order. Process IDs double # as index in the resultant array. results=[None]*num; while not out_q.empty(): idx, result = out_q.get() results[idx] = result # Remove extra dimension added by array_split return list(numpy.concatenate(results)) def parallel_map(function, sequence, numcores=None): """ A parallelized version of the native Python map function that utilizes the Python multiprocessing module to divide and conquer sequence. parallel_map does not yet support multiple argument sequences. :param function: callable function that accepts argument from iterable :param sequence: iterable sequence :param numcores: number of cores to use """ if not callable(function): raise TypeError("input function '%s' is not callable" % repr(function)) if not numpy.iterable(sequence): raise TypeError("input '%s' is not iterable" % repr(sequence)) size = len(sequence) if not _multi or size == 1: return map(function, sequence) if numcores is None: numcores = _ncpus # Returns a started SyncManager object which can be used for sharing # objects between processes. The returned manager object corresponds # to a spawned child process and has methods which will create shared # objects and return corresponding proxies. manager = multiprocessing.Manager() # Create FIFO queue and lock shared objects and return proxies to them. # The managers handles a server process that manages shared objects that # each slave process has access to. Bottom line -- thread-safe. out_q = manager.Queue() err_q = manager.Queue() lock = manager.Lock() # if sequence is less than numcores, only use len sequence number of # processes if size < numcores: numcores = size # group sequence into numcores-worth of chunks sequence = numpy.array_split(sequence, numcores) procs = [multiprocessing.Process(target=worker, args=(function, ii, chunk, out_q, err_q, lock)) for ii, chunk in enumerate(sequence)] return run_tasks(procs, err_q, out_q, numcores) if __name__ == "__main__": """ Unit test of parallel_map() Create an arbitrary length list of references to a single matrix containing random floats and compute the eigenvals in serial and parallel. Compare the results and timings. """ import time numtasks = 5 #size = (1024,1024) size = (512,512) vals = numpy.random.rand(*size) f = numpy.linalg.eigvals iterable = [vals]*numtasks print ('Running numpy.linalg.eigvals %iX on matrix size [%i,%i]' % (numtasks,size[0],size[1])) tt = time.time() presult = parallel_map(f, iterable) print('parallel map in %g secs' % (time.time()-tt)) tt = time.time() result = map(f, iterable) print('serial map in %g secs' % (time.time()-tt)) assert (numpy.asarray(result) == numpy.asarray(presult)).all() galpy-1.3.0/galpy.egg-info/0000755000076500000240000000000013236420072015536 5ustar bovystaff00000000000000galpy-1.3.0/galpy.egg-info/dependency_links.txt0000644000076500000240000000000113236420072021604 0ustar bovystaff00000000000000 galpy-1.3.0/galpy.egg-info/PKG-INFO0000644000076500000240000001272313236420072016640 0ustar bovystaff00000000000000Metadata-Version: 1.1 Name: galpy Version: 1.3.0 Summary: Galactic Dynamics in python Home-page: http://github.com/jobovy/galpy Author: Jo Bovy Author-email: bovy@astro.utoronto.ca License: New BSD Description-Content-Type: UNKNOWN Description: galpy ====== **Galactic Dynamics in python** AUTHOR ------- Jo Bovy - bovy at astro dot utoronto dot ca See `AUTHORS.txt `__ for a full list of contributors. If you find this code useful in your research, please let me know. **If you use galpy in a publication, please cite** `Bovy (2015) `__ **and link to http://github.com/jobovy/galpy**. See `the acknowledgement documentation section `__ for a more detailed guide to citing parts of the code. Please also send me a reference to the paper or send a pull request including your paper in the list of galpy papers on `this page `__ (this page is at doc/source/index.rst). Thanks! DOCUMENTATION -------------- The latest documentation can be found `here `__. An alternative that is kept up-to-date less frequently is `here `__. PYTHON VERSIONS AND DEPENDENCIES --------------------------------- ``galpy`` supports both Python 2 and 3. Specifically, galpy supports Python 2.7 and Python 3.4, 3.5, and 3.6. It may also work on earlier Python 3.* versions, but this has not been tested. Travis CI builds regularly check support for Python 2.7 and 3.6 (and of 3.4/3.5 using a limited set of tests). This package requires `Numpy `__, `Scipy `__, and `Matplotlib `__. Certain advanced features require the GNU Scientific Library (`GSL `__), with action calculations requiring version 1.14 or higher. Use of ``SnapshotRZPotential`` and ``InterpSnapshotRZPotential`` requires `pynbody `__. Support for providing inputs and getting outputs as Quantities with units is provided through `astropy `__. ISSUES ------- If you find *any* bug in the code, please report these using the `Issue Tracker `__ or by emailing the maintainer of the code. If you are having issues with the installation of ``galpy``, please first consult the `Installation FAQ `__. CONTRIBUTING TO GALPY ---------------------- If you are interested in contributing to galpy's development, take a look at `this brief guide `__ on the wiki. This will hopefully help you get started! Some further development notes can be found on the `wiki `__. This includes a list of small and larger extensions of galpy that would be useful `here `__ as well as a longer-term roadmap `here `__. Please let the main developer know if you need any help contributing! DISK DF CORRECTIONS -------------------- The dehnendf and shudf disk distribution functions can be corrected to follow the desired surface-mass density and radial-velocity-dispersion profiles more closely (see `1999AJ....118.1201D `__). Calculating these corrections is expensive, and a large set of precalculated corrections can be found `here `__ \[tar.gz archive\]. Install these by downloading them and unpacking them into the galpy/df_src/data directory before running the setup.py installation. E.g.:: curl -O https://github.s3.amazonaws.com/downloads/jobovy/galpy/galpy-dfcorrections.tar.gz tar xvzf galpy-dfcorrections.tar.gz -C ./galpy/df_src/data/ Platform: UNKNOWN Classifier: Development Status :: 6 - Mature Classifier: Intended Audience :: Science/Research Classifier: License :: OSI Approved :: BSD License Classifier: Operating System :: OS Independent Classifier: Programming Language :: C Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3.3 Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: 3.6 Classifier: Topic :: Scientific/Engineering :: Astronomy Classifier: Topic :: Scientific/Engineering :: Physics galpy-1.3.0/galpy.egg-info/requires.txt0000644000076500000240000000004713236420072020137 0ustar bovystaff00000000000000numpy>=1.7 scipy matplotlib pytest six galpy-1.3.0/galpy.egg-info/SOURCES.txt0000644000076500000240000001777613236420072017444 0ustar bovystaff00000000000000AUTHORS.txt HISTORY.txt LICENSE MANIFEST.in README.dev README.nemo README.rst setup.py galpy/__init__.py galpy/actionAngle.py galpy/df.py galpy/orbit.py galpy/potential.py galpy/snapshot.py galpy.egg-info/PKG-INFO galpy.egg-info/SOURCES.txt galpy.egg-info/dependency_links.txt galpy.egg-info/requires.txt galpy.egg-info/top_level.txt galpy/actionAngle_src/__init__.py galpy/actionAngle_src/actionAngle.py galpy/actionAngle_src/actionAngleAdiabatic.py galpy/actionAngle_src/actionAngleAdiabaticGrid.py galpy/actionAngle_src/actionAngleAdiabatic_c.py galpy/actionAngle_src/actionAngleAxi.py galpy/actionAngle_src/actionAngleIsochrone.py galpy/actionAngle_src/actionAngleIsochroneApprox.py galpy/actionAngle_src/actionAngleSpherical.py galpy/actionAngle_src/actionAngleStaeckel.py galpy/actionAngle_src/actionAngleStaeckelGrid.py galpy/actionAngle_src/actionAngleStaeckel_c.py galpy/actionAngle_src/actionAngleTorus.py galpy/actionAngle_src/actionAngleTorus_c.py galpy/actionAngle_src/actionAngleVertical.py galpy/actionAngle_src/actionAngleTorus_c_ext/actionAngleTorus.cc galpy/actionAngle_src/actionAngleTorus_c_ext/galpyPot.cc galpy/actionAngle_src/actionAngleTorus_c_ext/galpyPot.h galpy/actionAngle_src/actionAngle_c_ext/actionAngle.c galpy/actionAngle_src/actionAngle_c_ext/actionAngle.h galpy/actionAngle_src/actionAngle_c_ext/actionAngleAdiabatic.c galpy/actionAngle_src/actionAngle_c_ext/actionAngleStaeckel.c galpy/df_src/__init__.py galpy/df_src/df.py galpy/df_src/diskdf.py galpy/df_src/evolveddiskdf.py galpy/df_src/quasiisothermaldf.py galpy/df_src/streamdf.py galpy/df_src/streamgapdf.py galpy/df_src/surfaceSigmaProfile.py galpy/df_src/data/dfcorrection_dehnendf_expSurfaceSigmaProfile_0.2500_0.7500_0.0125_0.0000_151_5.0000_20.sav galpy/df_src/data/dfcorrection_dehnendf_expSurfaceSigmaProfile_0.2500_0.7500_0.2000_0.0000_151_5.0000_20.sav galpy/df_src/data/dfcorrection_dehnendf_expSurfaceSigmaProfile_0.3333_0.6667_0.0125_0.0000_151_5.0000_20.sav galpy/df_src/data/dfcorrection_dehnendf_expSurfaceSigmaProfile_0.3333_0.6667_0.2000_0.0000_151_5.0000_20.sav galpy/df_src/data/dfcorrection_dehnendf_expSurfaceSigmaProfile_0.3333_1.0000_0.0125_0.0000_151_5.0000_20.sav galpy/df_src/data/dfcorrection_dehnendf_expSurfaceSigmaProfile_0.3333_1.0000_0.1000_0.0000_151_5.0000_20.sav galpy/df_src/data/dfcorrection_dehnendf_expSurfaceSigmaProfile_0.3333_1.0000_0.2000_-0.1000_151_5.0000_20.sav galpy/df_src/data/dfcorrection_dehnendf_expSurfaceSigmaProfile_0.3333_1.0000_0.2000_-0.2000_151_5.0000_20.sav galpy/df_src/data/dfcorrection_dehnendf_expSurfaceSigmaProfile_0.3333_1.0000_0.2000_0.0000_151_5.0000_20.sav galpy/df_src/data/dfcorrection_dehnendf_expSurfaceSigmaProfile_0.3333_1.0000_0.2000_0.1000_151_5.0000_20.sav galpy/df_src/data/dfcorrection_dehnendf_expSurfaceSigmaProfile_0.3333_1.0000_0.2000_0.2000_151_5.0000_20.sav galpy/df_src/data/dfcorrection_shudf_expSurfaceSigmaProfile_0.3333_1.0000_0.2000_0.0000_151_5.0000_20.sav galpy/df_src/data/dfcorrection_shudf_expSurfaceSigmaProfile_0.3333_1.0000_0.2000_0.0000_151_5.0000_40.sav galpy/orbit_src/FullOrbit.py galpy/orbit_src/Orbit.py galpy/orbit_src/OrbitTop.py galpy/orbit_src/RZOrbit.py galpy/orbit_src/__init__.py galpy/orbit_src/integrateFullOrbit.py galpy/orbit_src/integratePlanarOrbit.py galpy/orbit_src/linearOrbit.py galpy/orbit_src/planarOrbit.py galpy/orbit_src/orbit_c_ext/integrateFullOrbit.c galpy/orbit_src/orbit_c_ext/integrateFullOrbit.h galpy/orbit_src/orbit_c_ext/integratePlanarOrbit.c galpy/potential_src/BurkertPotential.py galpy/potential_src/CosmphiDiskPotential.py galpy/potential_src/DehnenBarPotential.py galpy/potential_src/DehnenSmoothWrapperPotential.py galpy/potential_src/DiskSCFPotential.py galpy/potential_src/DoubleExponentialDiskPotential.py galpy/potential_src/EllipticalDiskPotential.py galpy/potential_src/FerrersPotential.py galpy/potential_src/FlattenedPowerPotential.py galpy/potential_src/ForceSoftening.py galpy/potential_src/HenonHeilesPotential.py galpy/potential_src/IsochronePotential.py galpy/potential_src/KGPotential.py galpy/potential_src/KuzminDiskPotential.py galpy/potential_src/KuzminKutuzovStaeckelPotential.py galpy/potential_src/LogarithmicHaloPotential.py galpy/potential_src/MN3ExponentialDiskPotential.py galpy/potential_src/MiyamotoNagaiPotential.py galpy/potential_src/MovingObjectPotential.py galpy/potential_src/PlummerPotential.py galpy/potential_src/Potential.py galpy/potential_src/PowerSphericalPotential.py galpy/potential_src/PowerSphericalPotentialwCutoff.py galpy/potential_src/PseudoIsothermalPotential.py galpy/potential_src/RazorThinExponentialDiskPotential.py galpy/potential_src/SCFPotential.py galpy/potential_src/SnapshotRZPotential.py galpy/potential_src/SoftenedNeedleBarPotential.py galpy/potential_src/SolidBodyRotationWrapperPotential.py galpy/potential_src/SpiralArmsPotential.py galpy/potential_src/SteadyLogSpiralPotential.py galpy/potential_src/TransientLogSpiralPotential.py galpy/potential_src/TwoPowerSphericalPotential.py galpy/potential_src/TwoPowerTriaxialPotential.py galpy/potential_src/WrapperPotential.py galpy/potential_src/__init__.py galpy/potential_src/interpRZPotential.py galpy/potential_src/linearPotential.py galpy/potential_src/planarPotential.py galpy/potential_src/plotEscapecurve.py galpy/potential_src/plotRotcurve.py galpy/potential_src/verticalPotential.py galpy/potential_src/interppotential_c_ext/interppotential_calc_potential.c galpy/potential_src/potential_c_ext/BurkertPotential.c galpy/potential_src/potential_c_ext/CosmphiDiskPotential.c galpy/potential_src/potential_c_ext/DehnenBarPotential.c galpy/potential_src/potential_c_ext/DehnenSmoothWrapperPotential.c galpy/potential_src/potential_c_ext/DiskSCFPotential.c galpy/potential_src/potential_c_ext/DoubleExponentialDiskPotential.c galpy/potential_src/potential_c_ext/EllipticalDiskPotential.c galpy/potential_src/potential_c_ext/FlattenedPowerPotential.c galpy/potential_src/potential_c_ext/HenonHeilesPotential.c galpy/potential_src/potential_c_ext/HernquistPotential.c galpy/potential_src/potential_c_ext/IsochronePotential.c galpy/potential_src/potential_c_ext/JaffePotential.c galpy/potential_src/potential_c_ext/KuzminDiskPotential.c galpy/potential_src/potential_c_ext/KuzminKutuzovStaeckelPotential.c galpy/potential_src/potential_c_ext/LogarithmicHaloPotential.c galpy/potential_src/potential_c_ext/LopsidedDiskPotential.c galpy/potential_src/potential_c_ext/MiyamotoNagaiPotential.c galpy/potential_src/potential_c_ext/NFWPotential.c galpy/potential_src/potential_c_ext/PlummerPotential.c galpy/potential_src/potential_c_ext/PowerSphericalPotential.c galpy/potential_src/potential_c_ext/PowerSphericalPotentialwCutoff.c galpy/potential_src/potential_c_ext/PseudoIsothermalPotential.c galpy/potential_src/potential_c_ext/SCFPotential.c galpy/potential_src/potential_c_ext/SoftenedNeedleBarPotential.c galpy/potential_src/potential_c_ext/SolidBodyRotationWrapperPotential.c galpy/potential_src/potential_c_ext/SpiralArmsPotential.c galpy/potential_src/potential_c_ext/SteadyLogSpiralPotential.c galpy/potential_src/potential_c_ext/TransientLogSpiralPotential.c galpy/potential_src/potential_c_ext/TwoPowerTriaxialPotential.c galpy/potential_src/potential_c_ext/ZeroPotential.c galpy/potential_src/potential_c_ext/galpy_potentials.c galpy/potential_src/potential_c_ext/galpy_potentials.h galpy/potential_src/potential_c_ext/interpRZPotential.c galpy/snapshot_src/GadgetSnapshot.py galpy/snapshot_src/Snapshot.py galpy/snapshot_src/__init__.py galpy/snapshot_src/directnbody.py galpy/snapshot_src/nemo_util.py galpy/snapshot_src/snapshotMovies.py galpy/util/__init__.py galpy/util/bovy_ars.py galpy/util/bovy_conversion.py galpy/util/bovy_coords.py galpy/util/bovy_plot.py galpy/util/bovy_quadpack.py galpy/util/bovy_rk.c galpy/util/bovy_rk.h galpy/util/bovy_symplecticode.c galpy/util/bovy_symplecticode.h galpy/util/bovy_symplecticode.py galpy/util/config.py galpy/util/multi.py galpy/util/interp_2d/cubic_bspline_2d_coeffs.c galpy/util/interp_2d/cubic_bspline_2d_coeffs.h galpy/util/interp_2d/cubic_bspline_2d_interpol.c galpy/util/interp_2d/cubic_bspline_2d_interpol.h galpy/util/interp_2d/interp_2d.c galpy/util/interp_2d/interp_2d.hgalpy-1.3.0/galpy.egg-info/top_level.txt0000644000076500000240000000025113236420072020266 0ustar bovystaff00000000000000galpy galpy/actionAngle_src galpy/df_src galpy/orbit_src galpy/potential_src galpy/snapshot_src galpy/util galpy_actionAngle_c galpy_integrate_c galpy_interppotential_c galpy-1.3.0/HISTORY.txt0000644000076500000240000003343413236350147014646 0ustar bovystaff00000000000000v1.3 (2017-XX-XX) ================== - Added a fast and precise method for approximating an orbit's eccentricity, peri- and apocenter radii, and maximum height above the midplane using the Staeckel approximation (see Mackereth & Bovy 2018); available as an actionAngle method EccZmaxRperiRap and for Orbit instances through the e, rperi, rap, and zmax methods. - Added support for potential wrappers---classes that wrap existing potentials to modify their behavior (#307). See the documentation on potentials and the potential API for more information on these. - Added DehnenSmoothWrapperPotential, a potential wrapper to smoothly grow a gravitational potential (e.g., a bar) following Dehnen (2000). - Added SolidBodyRotationWrapperPotential, a potential wrapper to make a gravitational potential rotate around the z axis with a fixed, uniform pattern speed. - Added DiskSCFPotential, a class that implements general density/potential pairs for disk potentials by combining Kuijken & Dubinski (1995)'s trick for turning a separable disk density [rho(R,z) = \Sigma(R) x h(z)] into a ellipsoidal-ish density, with the SCF basis-function-expansion approach of Hernquist & Ostriker (1992) for solving for the ellipsoidal-ish density's potential. This is a fully general Poisson solver (i.e., any density can be represented in this way). - Added SoftenedNeedleBarPotential, a simple potential for a bar that consists of a needle softened by a Miyamoto-Nagai kernel (Long & Murali 1992). - Added FerrersPotential, a class that implements the potential for triaxial ellipsoids (Binney & Tremaine 2008, Ch 2.5) with an option to give a pattern speed to the ellipsoid. - Added SpiralArmsPotential, a class that implements the spiral arms potential from Cox and Gomez (2002). https://arxiv.org/abs/astro-ph/0207635v1 (#305) - Added the Henon & Heiles (1964) potential - Added an Orbit method to display an animation of an integrated orbit in jupyter notebooks (Orbit.animate). - Added galpy.df.schwarzschilddf, the simple Schwarzschild distribution function for a razor-thin disk. - Upgraded LogarithmicHaloPotential to allow it to be a triaxial potential. - Upgraded DehnenBarPotential to a 3D potential following Monari et al. (2016) (#304). - Generalized CosmphiDiskPotential to allow for a break radius within which the radial dependence of the potential changes from R^p to R^-p; also added C implementation of CosmphiDiskPotential. - Changed default method for computing actions, frequencies, and angles for Orbit instances to be the Staeckel approximation with an automatically-estimated delta parameter. - Added an option to the estimateDeltaStaeckel function to facilitate the return of an estimated delta parameter at every phase space point passed, rather than returning a median of the estimate at each point. - Generalized actionAngleStaeckel to allow for different focal lengths delta for different phase-space points. Also allowed the order of the Gauss-Legendre integration to be specified (default: 10, which is good enough when using actionAngleStaeckel to compute approximate actions etc. for an axisymmetric potential). - Allow transformations of (ra,dec) and (pmra,pmdec) to custom coordinate systems. - Allow plotting of the spherical radius in Orbit.plot - Allow plotting of arbitrary user-supplied functions of time in Orbit.plot, Orbit.plot3d, and Orbit.animate. - Added conversion to and from oblate spheroidal coordinates. Also added conversion of velocities to the momenta in oblate or prolate spheroidal coordinates (but not the other way around). - Made SCFPotential compatible with GSL v2. - Orbit methods now all return a scalar when called with a single time (see #247 and #294). - Added a warning whenever the orbit-integration method that is used is different from the requested one (for when C integration falls back onto Python; see #286). - Turn off changing the seaborn default plot configuration to that preferred by this code's maintainer; can be turned back on by specifying ``seaborn-bovy-defaults = True`` in the configuration file (see documentation). Old configuration files will be automatically updated to add this option (but remain otherwise the same). - Added button to code examples in the documentation that copies the examples to the user's clipboard for easy pasting into a Python interpreter. - Switched test suite from nose to pytest (#303). - quasiisothermaldf.tilt return unit switched from deg to rad. - streamdf.misalignment return unit switched from deg to rad. - evolveddiskdf.vertexdev return unit switched from deg to rad. - In quasiisothermaldf.py, the following prefactors were added to the following functions: [nsigma^2/2] --> pvT(), [nsigma/2] --> pvRvT(), [nsigma/2] --> pvTvz(), [vTmax/2] --> pvRvz(). This change was required to correctly account for the Gauss-Legendre integration limits. v1.2 (2016-09-06) ================== - Added support for providing inputs to all initializations, methods, and functions as Quantities with units and for providing outputs as astropy Quantities. See pull request #258 and the documentation for full details. - Added TwoPowerTriaxialPotential, a set of triaxial potentials with iso-density contours that are arbitrary, similar, coaxial ellipsoids whose 'radial' density is a (different) power-law at small and large radii: 1/m^alpha/(1+m)^beta-alpha (the triaxial generalization of TwoPowerSphericalPotential, with flattening in the density rather than in the potential, see Merritt & Fridman 1996, Binney & Tremaine 2008). These include triaxial Hernquist and NFW potentials. Includes fast C implementations that allow efficient orbit integration in these potentials (<~5 ms / orbit). - Added SCFPotential, a class that implements general density/potential pairs through the basis expansion approach to solving the Poisson equation of Hernquist & Ostriker (1992). Also implemented functions to compute the coefficients for a given density function. - Implemented galpy.actionAngle.actionAngleIsochroneApprox (Bovy 2014) for the general case of a time-independent potential, allowing action-angle coordinates to be computed for triaxial potentials. Previously, this module only supported axisymmetric potentials. Also allow the maximum number of terms in the expansion to be set object-wide and allow a fixed time-step to be used in the necessary orbit integration. - Added an (experimental) interface to Binney & McMillan's TorusMapper code for computing positions and velocities for given actions and angles. See the installation instructions for how to properly install this. - Re-defined the amplitude parameters of a few potentials to allow for easier setups with amplitudes provided as Quantities with units. This is the case for FlattenedPowerPotential, PowerSphericalPotential, and PowerSphericalPotentialwCutoff, CosmphiDiskPotential, LopsidedDiskPotential, and EllipticalDiskPotential. - Added a module for modeling the effect of a subhalo impact on a tidal stream (galpy.df.streamgapdf); see Sanders, Bovy, & Erkal (2016). Also includes the fast methods for computing the density along the stream and the stream track for a perturbed stream from Bovy, Erkal, & Sanders (2016). - Added functions to galpy.df.streamdf to compute the length of the stream, the density along the stream quickly (in various coordinates, including custom sky coordinates), to compute the probability p(freq_parallel,angle_parallel), and to estimate the number of encounters with DM subhalos. Also allow the stream track to be computing using the new actionAngleTorus (this last addition is still very experimental). - Added PseudoIsothermalPotential, a standard pseudo-isothermal-sphere potential. - Added KuzminDiskPotential, a potential represented by a razor thin disk - Allow transformations to custom sets of sky coordinates in galpy.util.bovy_coords. - Added the spherical radius as an Orbit method. - Added the spherical-radial force as a Potential method (rforce) and function (evaluaterforces). - Support for flipping the velocities of an orbit in-place (useful for backwards integration). - quasiisothermaldf input ro replaced by refr to avoid clash with ro that specifies units (see above). - Properly intercept CTRL-C (SIGINT) signals during orbit integration in C, allowing long orbit integrations to be interrupted without killing an entire Python session. - Internally use astropy.coordinates transformations to transform between (ra,dec) and (l,b). Can be tuned using the astropy-coords configuration parameter. Also allow general epochs for the (ra,dec) specification. v1.1 (2015-06-30) ================== - Added Python 3 support: in particular, Python 3.3 and 3.4 are now supported; earlier 3.* may also work, but haven't been tested. Python 2 and 3 are supported using a single common codebase. - Added SnapshotRZPotential and InterpSnapshotRZPotential potential classes: these can be used to get a frozen snapshot of the potential of an N-body simulation into galpy. SnapshotRZPotential directly calculates the potential and forces using direct summation; InterpSnapshotRZPotential builds an interpolation table (it's a subclass of interpRZPotential and can be used in the same way). This code was mainly written by Rok Roskar. - Added KuzminKutuzovStaeckelPotential, a Staeckel potential that can be used to approximate the potential of a disk galaxy (see Batsleer & Dejonghe 1994). - Added MN3ExponentialDiskPotential that gives the approximation to a radially-exponential disk potential as three Miyamoto-Nagai disks from Smith et al. (2015; arXiv:1502.00627v1) - Added PlummerPotential, a standard Plummer potential. - Add support for converting potential parameters to NEMO format and units: nemo_accname and nemo_accpars (both instance and general Potential method). - Added nemo/ directory for C++ implementations of additional NEMO potentials (not included in the NEMO release); add PowSphwCut.cc for PowerSphericalPotential2Cutoff and Makefile to compile and install it. This allows gyrfalcON simulations with MWPotential2014. - Allow user to directly specify the integration step size to use in orbit integration. - Better implementations of vcirc, eipfreq, and verticalfreq for lists of potentials that allows for negative amplitude components. - Improvements to streamdf: add progIsTrack keyword to specify that the progenitor= keyword actually specifies the desired starting phase-space point of the track; the phase-space point of the progenitor is calculated. Refactored streamdf setup to make this easy to implement. - Orbit fitting can now be performed in custom sky coordinates (requires one to specify customsky=True and to implement and pass the functions (a) lb_to_customsky= and (b) pmllpmbb_to_customsky=, similar to lb_to_radec and pmllpmbb_to_pmradec). For example, this allows one to do an orbit fit in the GD-1 frame of Koposov et al. (2010). - Orbit.time now returns the times at which an Orbit is integrated if called without arguments. - Slightly improved implementation of actionAngleStaeckel.py (more robust umin/umax finding). v1.0 (2014-12-10) ================== - Added MWPotential2014: a Milky-Way-like potential fit to a bunch of relevant data on the Milky Way; supersedes MWPotential, which is still included; changed many examples to use this new potential; - Changed default integrator to symplec4_c; - Changed default vo and ro to 220 km/s and 8 kpc, respectively; - Allow Orbit initialization to specify a distance and velocity scale that is then used to output distances, velocities, energies, actions, etc. in physical coordinates; - Added an orbit-fitting routine (Orbit.fit). - Added virial radius of NFW potential, allow initialization of NFW potential with concentration and mass; - Added potential.mass to return the mass enclosed for individual potential instances; both spherical and axisymmetric potentials are covered. - Sped up many of the functions in galpy.util.bovy_coords by removing scipy.frompyfunc dependency; - Added galpy.util.bovy_conversion.dens_in_gevcc, galpy.util.bovy_conversion.force_in_10m13kms2, galpy.util.bovy_conversion.dens_in_criticaldens, galpy.util.bovy_conversion.dens_in_meanmatterdens. - Much improved test coverage. - Added planarPotential.LinShuReductionFactor to calculate the reduction factor for the response of a kinematically warm population to a spiral perturbation. - Added non-axisymmetric DFs to the top-level and the documentation. - New streamdf track calculation (see arXiv_v2/published version of Bovy 2014); better handdling of coordinate-transformation parameters in streamdf. - Installation now prints informative message about whether or not the C extensions were installed. - More informative warning messages when C functions are not used. - Fix issues with non-square grids in the C implementation of interpRZPotential. - Added Orbit.flip to generate new Orbit instances with flipped velocities. - Improved actionAngle frequencies for circular orbits. - Removed actionAngleFlat and actionAnglePower, because they are superseded. - Added hasC_dxdv member to Potential instances to indicate whether or not they can be used with integrate_dxdv - Add C implementation of PowerSphericalPotentialwCutoff; - Moved MovingObjectPotential, BurkertPotential, and interpRZPotential to top level; better implementation of interpRZPotential; - Better handling of vo,ro, and solarmotion in Orbit instances (Orbit initialization value is now used to generate RA, Dec, etc. outputs); - Added --no-openmp installation option to allow installation without OpenMP support (workaround for clang not supporting OpenMP; not necessary anymore); v0.1 (2014-01-09) ================== - Initial release galpy-1.3.0/LICENSE0000644000076500000240000000262612671130470013745 0ustar bovystaff00000000000000Copyright (c) 2010, Jo Bovy All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. The name of the author may not be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.galpy-1.3.0/MANIFEST.in0000644000076500000240000000054713236417402014477 0ustar bovystaff00000000000000include README.rst README.dev README.nemo LICENSE HISTORY.txt AUTHORS.txt include galpy/df_src/data/*.sav include galpy/actionAngle_src/actionAngle_c_ext/*.h include galpy/actionAngle_src/actionAngleTorus_c_ext/*.h include galpy/orbit_src/orbit_c_ext/*.h include galpy/potential_src/potential_c_ext/*.h include galpy/util/*.h include galpy/util/interp_2d/*.h galpy-1.3.0/PKG-INFO0000644000076500000240000001272313236420072014032 0ustar bovystaff00000000000000Metadata-Version: 1.1 Name: galpy Version: 1.3.0 Summary: Galactic Dynamics in python Home-page: http://github.com/jobovy/galpy Author: Jo Bovy Author-email: bovy@astro.utoronto.ca License: New BSD Description-Content-Type: UNKNOWN Description: galpy ====== **Galactic Dynamics in python** AUTHOR ------- Jo Bovy - bovy at astro dot utoronto dot ca See `AUTHORS.txt `__ for a full list of contributors. If you find this code useful in your research, please let me know. **If you use galpy in a publication, please cite** `Bovy (2015) `__ **and link to http://github.com/jobovy/galpy**. See `the acknowledgement documentation section `__ for a more detailed guide to citing parts of the code. Please also send me a reference to the paper or send a pull request including your paper in the list of galpy papers on `this page `__ (this page is at doc/source/index.rst). Thanks! DOCUMENTATION -------------- The latest documentation can be found `here `__. An alternative that is kept up-to-date less frequently is `here `__. PYTHON VERSIONS AND DEPENDENCIES --------------------------------- ``galpy`` supports both Python 2 and 3. Specifically, galpy supports Python 2.7 and Python 3.4, 3.5, and 3.6. It may also work on earlier Python 3.* versions, but this has not been tested. Travis CI builds regularly check support for Python 2.7 and 3.6 (and of 3.4/3.5 using a limited set of tests). This package requires `Numpy `__, `Scipy `__, and `Matplotlib `__. Certain advanced features require the GNU Scientific Library (`GSL `__), with action calculations requiring version 1.14 or higher. Use of ``SnapshotRZPotential`` and ``InterpSnapshotRZPotential`` requires `pynbody `__. Support for providing inputs and getting outputs as Quantities with units is provided through `astropy `__. ISSUES ------- If you find *any* bug in the code, please report these using the `Issue Tracker `__ or by emailing the maintainer of the code. If you are having issues with the installation of ``galpy``, please first consult the `Installation FAQ `__. CONTRIBUTING TO GALPY ---------------------- If you are interested in contributing to galpy's development, take a look at `this brief guide `__ on the wiki. This will hopefully help you get started! Some further development notes can be found on the `wiki `__. This includes a list of small and larger extensions of galpy that would be useful `here `__ as well as a longer-term roadmap `here `__. Please let the main developer know if you need any help contributing! DISK DF CORRECTIONS -------------------- The dehnendf and shudf disk distribution functions can be corrected to follow the desired surface-mass density and radial-velocity-dispersion profiles more closely (see `1999AJ....118.1201D `__). Calculating these corrections is expensive, and a large set of precalculated corrections can be found `here `__ \[tar.gz archive\]. Install these by downloading them and unpacking them into the galpy/df_src/data directory before running the setup.py installation. E.g.:: curl -O https://github.s3.amazonaws.com/downloads/jobovy/galpy/galpy-dfcorrections.tar.gz tar xvzf galpy-dfcorrections.tar.gz -C ./galpy/df_src/data/ Platform: UNKNOWN Classifier: Development Status :: 6 - Mature Classifier: Intended Audience :: Science/Research Classifier: License :: OSI Approved :: BSD License Classifier: Operating System :: OS Independent Classifier: Programming Language :: C Classifier: Programming Language :: Python :: 2.7 Classifier: Programming Language :: Python :: 3.3 Classifier: Programming Language :: Python :: 3.4 Classifier: Programming Language :: Python :: 3.5 Classifier: Programming Language :: Python :: 3.6 Classifier: Topic :: Scientific/Engineering :: Astronomy Classifier: Topic :: Scientific/Engineering :: Physics galpy-1.3.0/README.dev0000644000076500000240000000232112671130470014365 0ustar bovystaff00000000000000Develop README ============== Adding a potential to the C integrator -------------------------------------- 1) Implement the potential in a .c file under potential_src/potential_c_ext. Look at potential_src/potential_c_ext/LogarithmicHaloPotential.c for the right format 2) Add your new potential to potential_src/potential_c_ext/galpy_potentials.h 3) Edit the code under orbit_src/orbit_c_ext/integratePlanarOrbit.c to set up your new potential (in the 'parse_leapFuncArgs' function) 4) Edit the code in orbit_src/integratePlanarOrbit.py to set up your new potential 5) Edit the code under orbit_src/orbit_c_ext/integrateFullOrbit.c to set up your new potential (in the 'parse_leapFuncArgs_Full' function) 6) Edit the code in orbit_src/integrateFullOrbit.py to set up your new potential 7) Edit the code in actionAngle_src/actionAngle_c_ext/actionAngle.c to parse the new potential 8) Finally, add 'self.hasC= True' to the initialization of the potential in question (after the initialization of the super class) 9) It should work now! 10) If you implement the second derivatives of the potential necessary to integrate phase-space volumes, also set self.hasC_dxdv=True to the initialization of the potential in question.galpy-1.3.0/README.nemo0000644000076500000240000000222412671130470014547 0ustar bovystaff00000000000000Adding potentials in NEMO format ================================= NEMO (http://bima.astro.umd.edu/nemo/) uses potentials by compiling each potential into a shared-object library (*.so) that can then be dynamically loaded at runtime. Therefore, adding a potential just requires one to compile a new library for the potential in a way that NEMO can pass parameters to it. The directory nemo/ contains C++ implementations of additional potentials that are not included in the NEMO release. To add a new potential to this, do: 1) Use nemo/PowSphwCut.cc as a template and implement your new potential based on this and store it in the nemo/ directory; 2) Add the new potential to nemo/Makefile in a similar manner as the PowSphwCut.so target (if you need additional libraries, make sure to link to these). Also add this target to the 'all' target near the top. 3) With NEMO running (having sourced nemo_start), such that the NEMO environment variables are defined, you can then install the new potential by running make in the nemo directory. 4) If it works, consider contributing this potential to the galpy codebase by adding it to a fork and opening a pull request!galpy-1.3.0/README.rst0000644000076500000240000001362313236410606014426 0ustar bovystaff00000000000000galpy ====== **Galactic Dynamics in python** .. image:: https://travis-ci.org/jobovy/galpy.svg?branch=master :target: http://travis-ci.org/jobovy/galpy .. image:: https://img.shields.io/coveralls/jobovy/galpy.svg :target: https://coveralls.io/r/jobovy/galpy?branch=master .. image:: http://codecov.io/github/jobovy/galpy/coverage.svg?branch=master :target: http://codecov.io/github/jobovy/galpy?branch=master .. image:: https://readthedocs.org/projects/galpy/badge/?version=latest :target: http://galpy.readthedocs.io/en/latest/ .. image:: http://img.shields.io/pypi/v/galpy.svg :target: https://pypi.python.org/pypi/galpy/ .. image:: http://img.shields.io/badge/license-New%20BSD-brightgreen.svg :target: https://github.com/jobovy/galpy/blob/master/LICENSE .. image:: http://img.shields.io/badge/DOI-10.1088/0067%2D%2D0049/216/2/29-blue.svg :target: http://dx.doi.org/10.1088/0067-0049/216/2/29 .. image:: http://img.shields.io/badge/powered%20by-AstroPy-orange.svg?style=flat :target: http://www.astropy.org/ AUTHOR ------- Jo Bovy - bovy at astro dot utoronto dot ca See `AUTHORS.txt `__ for a full list of contributors. If you find this code useful in your research, please let me know. **If you use galpy in a publication, please cite** `Bovy (2015) `__ **and link to http://github.com/jobovy/galpy**. See `the acknowledgement documentation section `__ for a more detailed guide to citing parts of the code. Please also send me a reference to the paper or send a pull request including your paper in the list of galpy papers on `this page `__ (this page is at doc/source/index.rst). Thanks! DOCUMENTATION -------------- The latest documentation can be found `here `__. An alternative that is kept up-to-date less frequently is `here `__. PYTHON VERSIONS AND DEPENDENCIES --------------------------------- ``galpy`` supports both Python 2 and 3. Specifically, galpy supports Python 2.7 and Python 3.4, 3.5, and 3.6. It may also work on earlier Python 3.* versions, but this has not been tested. Travis CI builds regularly check support for Python 2.7 and 3.6 (and of 3.4/3.5 using a limited set of tests). This package requires `Numpy `__, `Scipy `__, and `Matplotlib `__. Certain advanced features require the GNU Scientific Library (`GSL `__), with action calculations requiring version 1.14 or higher. Use of ``SnapshotRZPotential`` and ``InterpSnapshotRZPotential`` requires `pynbody `__. Support for providing inputs and getting outputs as Quantities with units is provided through `astropy `__. ISSUES ------- If you find *any* bug in the code, please report these using the `Issue Tracker `__ or by emailing the maintainer of the code. If you are having issues with the installation of ``galpy``, please first consult the `Installation FAQ `__. CONTRIBUTING TO GALPY ---------------------- If you are interested in contributing to galpy's development, take a look at `this brief guide `__ on the wiki. This will hopefully help you get started! Some further development notes can be found on the `wiki `__. This includes a list of small and larger extensions of galpy that would be useful `here `__ as well as a longer-term roadmap `here `__. Please let the main developer know if you need any help contributing! DETAILED BUILD, COVERAGE, AND DOCUMENTATION STATUS --------------------------------------------------- **master**: .. image:: https://travis-ci.org/jobovy/galpy.svg?branch=master :target: http://travis-ci.org/jobovy/galpy .. image:: https://img.shields.io/coveralls/jobovy/galpy.svg :target: https://coveralls.io/r/jobovy/galpy?branch=master .. image:: http://codecov.io/github/jobovy/galpy/coverage.svg?branch=master :target: http://codecov.io/github/jobovy/galpy?branch=master .. image:: https://readthedocs.org/projects/galpy/badge/?branch=master?version=latest :target: http://galpy.readthedocs.io/en/master/ **development branch** (if it exists): .. image:: https://travis-ci.org/jobovy/galpy.svg?branch=dev :target: http://travis-ci.org/jobovy/galpy/branches .. image:: https://img.shields.io/coveralls/jobovy/galpy.svg?branch=dev :target: https://coveralls.io/r/jobovy/galpy?branch=dev .. image:: http://codecov.io/github/jobovy/galpy/coverage.svg?branch=dev :target: http://codecov.io/github/jobovy/galpy?branch=dev .. image:: https://readthedocs.org/projects/galpy/badge/?branch=master?version=latest :target: http://galpy.readthedocs.io/en/dev/ DISK DF CORRECTIONS -------------------- The dehnendf and shudf disk distribution functions can be corrected to follow the desired surface-mass density and radial-velocity-dispersion profiles more closely (see `1999AJ....118.1201D `__). Calculating these corrections is expensive, and a large set of precalculated corrections can be found `here `__ \[tar.gz archive\]. Install these by downloading them and unpacking them into the galpy/df_src/data directory before running the setup.py installation. E.g.:: curl -O https://github.s3.amazonaws.com/downloads/jobovy/galpy/galpy-dfcorrections.tar.gz tar xvzf galpy-dfcorrections.tar.gz -C ./galpy/df_src/data/ galpy-1.3.0/setup.cfg0000644000076500000240000000004613236420072014551 0ustar bovystaff00000000000000[egg_info] tag_build = tag_date = 0 galpy-1.3.0/setup.py0000644000076500000240000003560213236414622014454 0ustar bovystaff00000000000000from setuptools import setup from distutils.core import Extension import sys import sysconfig import os, os.path import subprocess import glob PY3= sys.version > '3' long_description= '' previous_line= '' with open('README.rst') as dfile: for line in dfile: if not 'image' in line and not 'target' in line \ and not 'DETAILED' in line and not '**master**' in line \ and not '**development' in line \ and not 'DETAILED' in previous_line: long_description+= line previous_line= line # Parse options; current options # --no-openmp: compile without OpenMP support # --coverage: compile with gcov support # --single_ext: compile all of the C code into a single extension (just for testing, do not use this) # --orbit_ext: just compile the orbit extension (for use w/ --coverage) # --actionAngle_ext: just compile the actionAngle extension (for use w/ --coverage) # --interppotential_ext: just compile the interppotential extension (for use w/ --coverage) pot_libraries= ['m','gsl','gslcblas','gomp'] #Option to forego OpenMP try: openmp_pos = sys.argv.index('--no-openmp') except ValueError: extra_compile_args=["-fopenmp"] else: del sys.argv[openmp_pos] extra_compile_args= ["-DNO_OMP"] pot_libraries.remove('gomp') #Option to track coverage try: coverage_pos = sys.argv.index('--coverage') except ValueError: extra_link_args= [] else: del sys.argv[coverage_pos] extra_compile_args.extend(["-O0","--coverage"]) extra_link_args= ["--coverage"] #Option to compile everything into a single extension try: single_ext_pos = sys.argv.index('--single_ext') except ValueError: single_ext= False else: del sys.argv[single_ext_pos] single_ext= True #Option to just compile the orbit extension try: orbit_ext_pos = sys.argv.index('--orbit_ext') except ValueError: orbit_ext= False else: del sys.argv[orbit_ext_pos] orbit_ext= True #Option to just compile the actionAngle extension try: actionAngle_ext_pos = sys.argv.index('--actionAngle_ext') except ValueError: actionAngle_ext= False else: del sys.argv[actionAngle_ext_pos] actionAngle_ext= True #Option to just compile the interppotential extension try: interppotential_ext_pos = sys.argv.index('--interppotential_ext') except ValueError: interppotential_ext= False else: del sys.argv[interppotential_ext_pos] interppotential_ext= True #code to check the GSL version cmd= ['gsl-config', '--version'] try: if sys.version_info < (2,7): #subprocess.check_output does not exist gsl_version= subprocess.Popen(cmd, stdout=subprocess.PIPE).communicate()[0] else: gsl_version= subprocess.check_output(cmd) except (OSError,subprocess.CalledProcessError): gsl_version= ['0','0'] else: if PY3: gsl_version= gsl_version.decode('utf-8') gsl_version= gsl_version.split('.') extra_compile_args.append("-D GSL_MAJOR_VERSION=%s" % (gsl_version[0])) #HACK for testing #gsl_version= ['0','0'] #Orbit integration C extension orbit_int_c_src= ['galpy/util/bovy_symplecticode.c','galpy/util/bovy_rk.c'] orbit_int_c_src.extend(glob.glob('galpy/potential_src/potential_c_ext/*.c')) orbit_int_c_src.extend(glob.glob('galpy/orbit_src/orbit_c_ext/*.c')) orbit_int_c_src.extend(glob.glob('galpy/util/interp_2d/*.c')) orbit_libraries=['m'] if float(gsl_version[0]) >= 1.: orbit_libraries.extend(['gsl','gslcblas']) orbit_include_dirs= ['galpy/util', 'galpy/util/interp_2d', 'galpy/potential_src/potential_c_ext'] #actionAngleTorus C extension (files here, so we can compile a single extension if so desidered) actionAngleTorus_c_src= \ glob.glob('galpy/actionAngle_src/actionAngleTorus_c_ext/*.cc') actionAngleTorus_c_src.extend(\ glob.glob('galpy/actionAngle_src/actionAngleTorus_c_ext/torus/src/*.cc')) actionAngleTorus_c_src.extend(\ ['galpy/actionAngle_src/actionAngleTorus_c_ext/torus/src/utils/CHB.cc', 'galpy/actionAngle_src/actionAngleTorus_c_ext/torus/src/utils/Err.cc', 'galpy/actionAngle_src/actionAngleTorus_c_ext/torus/src/utils/Compress.cc', 'galpy/actionAngle_src/actionAngleTorus_c_ext/torus/src/utils/Numerics.cc', 'galpy/actionAngle_src/actionAngleTorus_c_ext/torus/src/utils/PJMNum.cc']) actionAngleTorus_c_src.append(\ 'galpy/actionAngle_src/actionAngle_c_ext/actionAngle.c') actionAngleTorus_c_src.extend(\ glob.glob('galpy/potential_src/potential_c_ext/*.c')) actionAngleTorus_c_src.extend(\ glob.glob('galpy/orbit_src/orbit_c_ext/integrateFullOrbit.c')) actionAngleTorus_c_src.extend(glob.glob('galpy/util/interp_2d/*.c')) actionAngleTorus_c_src.extend(glob.glob('galpy/util/*.c')) actionAngleTorus_include_dirs= \ ['galpy/actionAngle_src/actionAngleTorus_c_ext', 'galpy/actionAngle_src/actionAngleTorus_c_ext/torus/src', 'galpy/actionAngle_src/actionAngleTorus_c_ext/torus/src/utils', 'galpy/actionAngle_src/actionAngle_c_ext', 'galpy/util/interp_2d', 'galpy/util', 'galpy/orbit_src/orbit_c_ext', 'galpy/potential_src/potential_c_ext'] if single_ext: #add the code and libraries for the other extensions #src orbit_int_c_src.extend(glob.glob('galpy/actionAngle_src/actionAngle_c_ext/*.c')) orbit_int_c_src.extend(glob.glob('galpy/potential_src/interppotential_c_ext/*.c')) if os.path.exists('galpy/actionAngle_src/actionAngleTorus_c_ext/torus/src'): # Add Torus code orbit_int_c_src.extend(actionAngleTorus_c_src) orbit_int_c_src= list(set(orbit_int_c_src)) #libraries for lib in pot_libraries: if not lib in orbit_libraries: orbit_libraries.append(lib) #includes orbit_include_dirs.extend(['galpy/actionAngle_src/actionAngle_c_ext', 'galpy/util/interp_2d', 'galpy/potential_src/potential_c_ext']) orbit_include_dirs.extend(['galpy/potential_src/potential_c_ext', 'galpy/util/interp_2d', 'galpy/util/', 'galpy/actionAngle_src/actionAngle_c_ext', 'galpy/orbit_src/orbit_c_ext', 'galpy/potential_src/interppotential_c_ext']) # Add Torus code orbit_include_dirs.extend(actionAngleTorus_include_dirs) orbit_include_dirs= list(set(orbit_include_dirs)) orbit_int_c= Extension('galpy_integrate_c', sources=orbit_int_c_src, libraries=orbit_libraries, include_dirs=orbit_include_dirs, extra_compile_args=extra_compile_args, extra_link_args=extra_link_args) ext_modules=[] if float(gsl_version[0]) >= 1. and \ not actionAngle_ext and not interppotential_ext: orbit_int_c_incl= True ext_modules.append(orbit_int_c) else: orbit_int_c_incl= False #actionAngle C extension actionAngle_c_src= glob.glob('galpy/actionAngle_src/actionAngle_c_ext/*.c') actionAngle_c_src.extend(glob.glob('galpy/potential_src/potential_c_ext/*.c')) actionAngle_c_src.extend(glob.glob('galpy/util/interp_2d/*.c')) actionAngle_include_dirs= ['galpy/actionAngle_src/actionAngle_c_ext', 'galpy/util/interp_2d', 'galpy/potential_src/potential_c_ext'] #Installation of this extension using the GSL may (silently) fail, if the GSL #is built for the wrong architecture, on Mac you can install the GSL correctly #using #brew install gsl --universal actionAngle_c= Extension('galpy_actionAngle_c', sources=actionAngle_c_src, libraries=pot_libraries, include_dirs=actionAngle_include_dirs, extra_compile_args=extra_compile_args, extra_link_args=extra_link_args) if float(gsl_version[0]) >= 1. \ and (float(gsl_version[0]) >= 2. or float(gsl_version[1]) >= 14.) and \ not orbit_ext and not interppotential_ext and not single_ext: actionAngle_c_incl= True ext_modules.append(actionAngle_c) else: actionAngle_c_incl= False #interppotential C extension interppotential_c_src= glob.glob('galpy/potential_src/potential_c_ext/*.c') interppotential_c_src.extend(glob.glob('galpy/potential_src/interppotential_c_ext/*.c')) interppotential_c_src.extend(['galpy/util/bovy_symplecticode.c','galpy/util/bovy_rk.c']) interppotential_c_src.append('galpy/actionAngle_src/actionAngle_c_ext/actionAngle.c') interppotential_c_src.append('galpy/orbit_src/orbit_c_ext/integrateFullOrbit.c') interppotential_c_src.extend(glob.glob('galpy/util/interp_2d/*.c')) interppotential_include_dirs= ['galpy/potential_src/potential_c_ext', 'galpy/util/interp_2d', 'galpy/util/', 'galpy/actionAngle_src/actionAngle_c_ext', 'galpy/orbit_src/orbit_c_ext', 'galpy/potential_src/interppotential_c_ext'] interppotential_c= Extension('galpy_interppotential_c', sources=interppotential_c_src, libraries=pot_libraries, include_dirs=interppotential_include_dirs, extra_compile_args=extra_compile_args, extra_link_args=extra_link_args) if float(gsl_version[0]) >= 1. \ and (float(gsl_version[0]) >= 2. or float(gsl_version[1]) >= 14.) \ and not orbit_ext and not actionAngle_ext and not single_ext: interppotential_c_incl= True ext_modules.append(interppotential_c) else: interppotential_c_incl= False # Add the actionAngleTorus extension (src and include specified above) #Installation of this extension using the GSL may (silently) fail, if the GSL #is built for the wrong architecture, on Mac you can install the GSL correctly #using #brew install gsl --universal actionAngleTorus_c= Extension('galpy_actionAngleTorus_c', sources=actionAngleTorus_c_src, libraries=pot_libraries, include_dirs=actionAngleTorus_include_dirs, extra_compile_args=extra_compile_args, extra_link_args=extra_link_args) if float(gsl_version[0]) >= 1. \ and (float(gsl_version[0]) >= 2. or float(gsl_version[1]) >= 14.) and \ os.path.exists('galpy/actionAngle_src/actionAngleTorus_c_ext/torus/src') and \ not orbit_ext and not interppotential_ext and not single_ext: actionAngleTorus_c_incl= True ext_modules.append(actionAngleTorus_c) else: actionAngleTorus_c_incl= False setup(name='galpy', version='1.3.0', description='Galactic Dynamics in python', author='Jo Bovy', author_email='bovy@astro.utoronto.ca', license='New BSD', long_description=long_description, url='http://github.com/jobovy/galpy', package_dir = {'galpy/': ''}, packages=['galpy','galpy/orbit_src','galpy/potential_src', 'galpy/df_src','galpy/util','galpy/snapshot_src', 'galpy/actionAngle_src'], package_data={'galpy/df_src':['data/*.sav'], "": ["README.rst","README.dev","LICENSE","AUTHORS.rst"]}, include_package_data=True, install_requires=['numpy>=1.7','scipy','matplotlib','pytest','six'], ext_modules=ext_modules, classifiers=[ "Development Status :: 6 - Mature", "Intended Audience :: Science/Research", "License :: OSI Approved :: BSD License", "Operating System :: OS Independent", "Programming Language :: C", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3.3", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Topic :: Scientific/Engineering :: Astronomy", "Topic :: Scientific/Engineering :: Physics"] ) def print_gsl_message(num_messages=1): if num_messages > 1: this_str= 'these installations' else: this_str= 'this installation' print('If you believe that %s should have worked, make sure\n(1) that the GSL include/ directory can be found by the compiler (you might have to edit CFLAGS for this: export CFLAGS="$CFLAGS -I/path/to/gsl/include/", or equivalent for C-type shells; replace /path/to/gsl/include/ with the actual path to the include directory),\n(2) that the GSL library can be found by the linker (you might have to edit LDFLAGS for this: export LDFLAGS="$LDFLAGS -L/path/to/gsl/lib/", or equivalent for C-type shells; replace /path/to/gsl/lib/ with the actual path to the lib directory),\n(3) and that `gsl-config --version` returns the correct version' % this_str) num_gsl_warn= 0 if not orbit_int_c_incl: num_gsl_warn+= 1 print('\033[91;1m'+'WARNING: orbit-integration C library not installed because your GSL version < 1'+'\033[0m') if not actionAngle_c_incl and not single_ext: num_gsl_warn+= 1 print('\033[91;1m'+'WARNING: action-angle C library not installed because your GSL version < 1.14'+'\033[0m') if not interppotential_c_incl and not single_ext: num_gsl_warn+= 1 print('\033[91;1m'+'WARNING: Potential-interpolation C library not installed because your GSL version < 1.14'+'\033[0m') if not actionAngleTorus_c_incl and not single_ext: num_gsl_warn+= 1 print('\033[91;1m'+'WARNING: action-angle-torus C library not installed because your GSL version < 1.14 or because you did not first download the torus code as explained in the installation guide in the html documentation'+'\033[0m') if num_gsl_warn > 0: print_gsl_message(num_messages=num_gsl_warn) print('\033[1m'+'These warning messages about the C code do not mean that the python package was not installed successfully'+'\033[0m') print('\033[1m'+'Finished installing galpy'+'\033[0m') print('You can run the test suite using `pytest -v tests/` to check the installation (but note that the test suite currently takes about 33 minutes to run)') #if single_ext, symlink the other (non-compiled) extensions to galpy_integrate_c.so (use EXT_SUFFIX for python3 compatibility) if PY3: _ext_suffix= sysconfig.get_config_var('EXT_SUFFIX') else: _ext_suffix= '.so' if single_ext: if not os.path.exists('galpy_actionAngle_c%s' % _ext_suffix): os.symlink('galpy_integrate_c%s' % _ext_suffix, 'galpy_actionAngle_c%s' % _ext_suffix) if not os.path.exists('galpy_interppotential_c%s' % _ext_suffix): os.symlink('galpy_integrate_c%s' % _ext_suffix, 'galpy_interppotential_c%s' % _ext_suffix) if not os.path.exists('galpy_actionAngleTorus_c%s' % _ext_suffix) \ and os.path.exists('galpy/actionAngle_src/actionAngleTorus_c_ext/torus/src'): os.symlink('galpy_integrate_c%s' % _ext_suffix, 'galpy_actionAngleTorus_c%s' % _ext_suffix)