###############################################################################
# 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 numpy
from ..util import galpyWarning
from ..potential import MWPotential
from ..potential.Potential import flatten as flatten_potential
from ..potential import toPlanarPotential, toVerticalPotential
from .actionAngleAxi import actionAngleAxi
from .actionAngle import actionAngle
from . import actionAngleAdiabatic_c
from .actionAngleAdiabatic_c import _ext_loaded as ext_loaded
from ..potential.Potential import _check_c
[docs]class actionAngleAdiabatic(actionAngle):
"""Action-angle formalism for axisymmetric potentials using the adiabatic approximation"""
[docs] 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= flatten_potential(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 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 and not ('c' in kwargs and not kwargs['c']))\
or (ext_loaded and (('c' in kwargs and kwargs['c'])))) \
and _check_c(self._pot):
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(R) > 1:
ojr= numpy.zeros((len(R)))
olz= numpy.zeros((len(R)))
ojz= numpy.zeros((len(R)))
for ii in range(len(R)):
targs= (R[ii],vR[ii],vT[ii],z[ii],vz[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
thispot= toPlanarPotential(self._pot)
thisverticalpot= toVerticalPotential(self._pot,R[0])
aAAxi= actionAngleAxi(R[0],vR[0],vT[0],z[0],vz[0],
pot=thispot,
verticalPot=thisverticalpot,
gamma=self._gamma)
if kwargs.get('_justjr',False):
kwargs.pop('_justjr')
return (aAAxi.JR(**kwargs),numpy.nan,numpy.nan)
elif kwargs.get('_justjz',False):
kwargs.pop('_justjz')
return (numpy.atleast_1d(numpy.nan),
numpy.atleast_1d(numpy.nan),
numpy.atleast_1d(aAAxi.Jz(**kwargs)))
else:
return (numpy.atleast_1d(aAAxi.JR(**kwargs)),
numpy.atleast_1d(aAAxi._R*aAAxi._vT),
numpy.atleast_1d(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 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 and not ('c' in kwargs and not kwargs['c']))\
or (ext_loaded and (('c' in kwargs and kwargs['c'])))) \
and _check_c(self._pot):
rperi,Rap,zmax, err= actionAngleAdiabatic_c.actionAngleRperiRapZmaxAdiabatic_c(\
self._pot,self._gamma,R,vR,vT,z,vz)
if err == 0:
rap= numpy.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(R) > 1:
oecc= numpy.zeros((len(R)))
orperi= numpy.zeros((len(R)))
orap= numpy.zeros((len(R)))
ozmax= numpy.zeros((len(R)))
for ii in range(len(R)):
targs= (R[ii],vR[ii],vT[ii],z[ii],vz[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
thispot= toPlanarPotential(self._pot)
thisverticalpot= toVerticalPotential(self._pot,R[0])
aAAxi= actionAngleAxi(R[0],vR[0],vT[0],z[0],vz[0],
pot=thispot,
verticalPot=thisverticalpot,
gamma=self._gamma)
rperi,Rap= aAAxi.calcRapRperi(**kwargs)
zmax= aAAxi.calczmax(**kwargs)
rap= numpy.sqrt(Rap**2.+zmax**2.)
return (numpy.atleast_1d((rap-rperi)/(rap+rperi)),
numpy.atleast_1d(zmax),numpy.atleast_1d(rperi),
numpy.atleast_1d(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
thispot= toPlanarPotential(self._pot)
aAAxi= actionAngleAxi(*args,pot=thispot,amma=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)
thispot= toPlanarPotential(self._pot)
thisverticalpot= toVerticalPotential(self._pot,self._eval_R)
aAAxi= actionAngleAxi(*args,pot=thispot,
verticalPot=thisverticalpot,
gamma=self._gamma)
return aAAxi.calczmax(**kwargs)