###############################################################################
# PowerSphericalPotential.py: General class for potentials derived from
# densities with two power-laws
#
# amp
# rho(r)= ---------
# r^\alpha
###############################################################################
import numpy
from scipy import special
from ..util import conversion
from .Potential import Potential
[docs]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}
"""
[docs] 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 or Gxmass
alpha - power-law exponent
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')
r1= conversion.parse_length(r1,ro=self._ro)
self.alpha= alpha
# Back to old definition
if self.alpha != 3.:
self._amp*= r1**(self.alpha-3.)*4.*numpy.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
self.hasC_dens= 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)
"""
r2= R**2.+z**2.
if self.alpha == 2.:
return numpy.log(r2)/2.
elif isinstance(r2,(float,int)) and r2 == 0 and self.alpha > 2:
return -numpy.inf
else:
out= -r2**(1.-self.alpha/2.)/(self.alpha-2.)
if isinstance(r2,numpy.ndarray) and self.alpha > 2:
out[r2 == 0]= -numpy.inf
return out
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 _rforce_jax(self,r):
"""
NAME:
_rforce_jax
PURPOSE:
evaluate the spherical radial force for this potential using JAX
INPUT:
r - Galactocentric spherical radius
OUTPUT:
the radial force
HISTORY:
2021-02-14 - Written - Bovy (UofT)
"""
# No need for actual JAX!
return -self._amp/r**(self.alpha-1.)
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 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.)
return (3.-self.alpha)/4./numpy.pi/r**self.alpha
def _ddensdr(self,r,t=0.):
"""
NAME:
_ddensdr
PURPOSE:
evaluate the radial density derivative for this potential
INPUT:
r - spherical radius
t= time
OUTPUT:
the density derivative
HISTORY:
2021-02-25 - Written - Bovy (UofT)
"""
return -self._amp\
*self.alpha*(3.-self.alpha)/4./numpy.pi/r**(self.alpha+1.)
def _d2densdr2(self,r,t=0.):
"""
NAME:
_d2densdr2
PURPOSE:
evaluate the second radial density derivative for this potential
INPUT:
r - spherical radius
t= time
OUTPUT:
the 2nd density derivative
HISTORY:
2021-02-25 - Written - Bovy (UofT)
"""
return self._amp*(self.alpha+1.)*self.alpha\
*(3.-self.alpha)/4./numpy.pi/r**(self.alpha+2.)
def _ddenstwobetadr(self,r,beta=0):
"""
NAME:
_ddenstwobetadr
PURPOSE:
evaluate the radial density derivative x r^(2beta) for this potential
INPUT:
r - spherical radius
beta= (0)
OUTPUT:
d (rho x r^{2beta} ) / d r
HISTORY:
2021-02-14 - Written - Bovy (UofT)
"""
return -self._amp*(self.alpha-2.*beta)\
*(3.-self.alpha)/4./numpy.pi/r**(self.alpha+1.-2.*beta)
def _surfdens(self,R,z,phi=0.,t=0.):
"""
NAME:
_surfdens
PURPOSE:
evaluate the surface density for this potential
INPUT:
R - Galactocentric cylindrical radius
z - vertical height
phi - azimuth
t - time
OUTPUT:
the surface density
HISTORY:
2018-08-19 - Written - Bovy (UofT)
"""
return (3.-self.alpha)/2./numpy.pi*z*R**-self.alpha\
*special.hyp2f1(0.5,self.alpha/2.,1.5,-(z/R)**2)
[docs]class KeplerPotential(PowerSphericalPotential):
"""Class that implements the Kepler (point mass) potential
.. math::
\\Phi(r) = -\\frac{\\mathrm{amp}}{r}
with :math:`\\mathrm{amp} = GM` the mass.
"""
[docs] def __init__(self,amp=1.,normalize=False,
ro=None,vo=None):
"""
NAME:
__init__
PURPOSE:
initialize a Kepler, point-mass potential
INPUT:
amp - amplitude to be applied to the potential, the mass of the point mass (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=None,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.