Source code for galpy.actionAngle.actionAngleSpherical

###############################################################################
#   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 numpy
from scipy import integrate
from ..potential import epifreq, omegac, _dim
from ..potential.Potential import _evaluatePotentials
from ..potential.Potential import flatten as flatten_potential
from .actionAngle import actionAngle
from .actionAngleAxi import actionAngleAxi, potentialAxi
[docs]class actionAngleSpherical(actionAngle): """Action-angle formalism for spherical potentials"""
[docs] 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= flatten_potential(kwargs['pot']) #Also store a 'planar' (2D) version of the potential if _dim(self._pot) == 2: self._2dpot= self._pot elif 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= numpy.array([R]) vR= numpy.array([vR]) vT= numpy.array([vT]) z= numpy.array([z]) vz= numpy.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= numpy.sqrt(L2) #Actions Jphi= Lz Jz= L-numpy.fabs(Lz) #Jr requires some more work #Set up an actionAngleAxi object for EL and rap/rperi calculations axiR= numpy.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 (numpy.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= numpy.array([R]) vR= numpy.array([vR]) vT= numpy.array([vT]) z= numpy.array([z]) vz= numpy.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= numpy.sqrt(L2) #Actions Jphi= Lz Jz= L-numpy.fabs(Lz) #Jr requires some more work #Set up an actionAngleAxi object for EL and rap/rperi calculations axiR= numpy.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= numpy.exp((numpy.log(rperi)+numpy.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= numpy.array(Op) Oz= copy.copy(Op) Op[vT < 0.]*= -1. return (numpy.array(Jr),Jphi,Jz,numpy.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= numpy.array([R]) vR= numpy.array([vR]) vT= numpy.array([vT]) z= numpy.array([z]) vz= numpy.array([vz]) phi= numpy.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= numpy.sqrt(L2) #Actions Jphi= Lz Jz= L-numpy.fabs(Lz) #Jr requires some more work #Set up an actionAngleAxi object for EL and rap/rperi calculations axiR= numpy.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= numpy.exp((numpy.log(rperi)+numpy.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= numpy.array(Op) Oz= copy.copy(Op) Op[vT < 0.]*= -1. ap= copy.copy(asc) ar= numpy.array(ar) az= numpy.array(az) ap[vT < 0.]-= az[vT < 0.] ap[vT >= 0.]+= az[vT >= 0.] ar= ar % (2.*numpy.pi) ap= ap % (2.*numpy.pi) az= az % (2.*numpy.pi) return (numpy.array(Jr),Jphi,Jz,numpy.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= numpy.array([R]) vR= numpy.array([vR]) vT= numpy.array([vT]) z= numpy.array([z]) vz= numpy.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= numpy.sqrt(L2) #Set up an actionAngleAxi object for EL and rap/rperi calculations axiR= numpy.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= numpy.array(rperi) rap= numpy.array(rap) return ((rap-rperi)/(rap+rperi),rap*numpy.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]/numpy.pi else: return (numpy.array(integrate.quad(_JrSphericalIntegrand, rperi,rap, args=(E,L,self._2dpot), **kwargs)))[0]/numpy.pi def _calc_or(self,Rmean,rperi,rap,E,L,fixed_quad,**kwargs): Tr= 0. if Rmean > rperi and not fixed_quad: Tr+= numpy.array(integrate.quadrature(_TrSphericalIntegrandSmall, 0.,numpy.sqrt(Rmean-rperi), args=(E,L,self._2dpot, rperi), **kwargs))[0] elif Rmean > rperi and fixed_quad: Tr+= integrate.fixed_quad(_TrSphericalIntegrandSmall, 0.,numpy.sqrt(Rmean-rperi), args=(E,L,self._2dpot, rperi), n=10,**kwargs)[0] if Rmean < rap and not fixed_quad: Tr+= numpy.array(integrate.quadrature(_TrSphericalIntegrandLarge, 0.,numpy.sqrt(rap-Rmean), args=(E,L,self._2dpot, rap), **kwargs))[0] elif Rmean < rap and fixed_quad: Tr+= integrate.fixed_quad(_TrSphericalIntegrandLarge, 0.,numpy.sqrt(rap-Rmean), args=(E,L,self._2dpot, rap), n=10,**kwargs)[0] Tr= 2.*Tr return 2.*numpy.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+= numpy.array(integrate.quadrature(_ISphericalIntegrandSmall, 0.,numpy.sqrt(Rmean-rperi), args=(E,L,self._2dpot, rperi), **kwargs))[0] elif Rmean > rperi and fixed_quad: I+= integrate.fixed_quad(_ISphericalIntegrandSmall, 0.,numpy.sqrt(Rmean-rperi), args=(E,L,self._2dpot,rperi), n=10,**kwargs)[0] if Rmean < rap and not fixed_quad: I+= numpy.array(integrate.quadrature(_ISphericalIntegrandLarge, 0.,numpy.sqrt(rap-Rmean), args=(E,L,self._2dpot, rap), **kwargs))[0] elif Rmean < rap and fixed_quad: I+= integrate.fixed_quad(_ISphericalIntegrandLarge, 0.,numpy.sqrt(rap-Rmean), args=(E,L,self._2dpot,rap), n=10,**kwargs)[0] I*= 2*L return I*Or/2./numpy.pi def _calc_long_asc(self,z,R,axivz,phi,Lz,L): i= numpy.arccos(Lz/L) sinu= z/R/numpy.tan(i) pindx= (sinu > 1.)*(sinu < (1.+10.**-7.)) sinu[pindx]= 1. pindx= (sinu < -1.)*(sinu > (-1.-10.**-7.)) sinu[pindx]= -1. u= numpy.arcsin(sinu) vzindx= axivz > 0. u[vzindx]= numpy.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.,numpy.sqrt(r-rperi), args=(E,L,self._2dpot,rperi), **kwargs)[0] elif r > rperi and fixed_quad: wr= Or*integrate.fixed_quad(_TrSphericalIntegrandSmall, 0.,numpy.sqrt(r-rperi), args=(E,L,self._2dpot,rperi), n=10,**kwargs)[0] else: wr= 0. if vr < 0.: wr= 2*numpy.pi-wr else: if r < rap and not fixed_quad: wr= Or*integrate.quadrature(_TrSphericalIntegrandLarge, 0.,numpy.sqrt(rap-r), args=(E,L,self._2dpot,rap), **kwargs)[0] elif r < rap and fixed_quad: wr= Or*integrate.fixed_quad(_TrSphericalIntegrandLarge, 0.,numpy.sqrt(rap-r), args=(E,L,self._2dpot,rap), n=10,**kwargs)[0] else: wr= numpy.pi if vr < 0.: wr= numpy.pi+wr else: wr= numpy.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= numpy.arccos(Lz/L) sinpsi= z/r/numpy.sin(i) if sinpsi > 1. and sinpsi < (1.+10.**-7.): sinpsi= 1. if sinpsi < -1. and sinpsi > (-1.-10.**-7.): sinpsi= -1. psi= numpy.arcsin(sinpsi) if axivz > 0.: psi= numpy.pi-psi psi= psi % (2.*numpy.pi) #Calculate dSr/dL dpsi= Op/Or*2.*numpy.pi #this is the full I integral if r < Rmean: if not fixed_quad: wz= L*integrate.quadrature(_ISphericalIntegrandSmall, 0.,numpy.sqrt(r-rperi), args=(E,L,self._2dpot, rperi), **kwargs)[0] elif fixed_quad: wz= L*integrate.fixed_quad(_ISphericalIntegrandSmall, 0.,numpy.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.,numpy.sqrt(rap-r), args=(E,L,self._2dpot, rap), **kwargs)[0] elif fixed_quad: wz= L*integrate.fixed_quad(_ISphericalIntegrandLarge, 0.,numpy.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 numpy.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.