Skip to content

Commit

Permalink
Add the potential of a thin, spherical shell
Browse files Browse the repository at this point in the history
  • Loading branch information
jobovy committed Aug 5, 2018
1 parent 7d7c49f commit b713308
Show file tree
Hide file tree
Showing 7 changed files with 260 additions and 8 deletions.
1 change: 1 addition & 0 deletions doc/source/reference/potential.rst
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,7 @@ Spherical potentials
potentialpowerspher.rst
potentialpowerspherwcut.rst
potentialpseudoiso.rst
potentialsphericalshell.rst

Axisymmetric potentials
***********************
Expand Down
5 changes: 5 additions & 0 deletions doc/source/reference/potentialsphericalshell.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
Spherical Shell Potential
=========================

.. autoclass:: galpy.potential.SphericalShellPotential
:members: __init__
2 changes: 1 addition & 1 deletion galpy/potential/PowerSphericalPotential.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ def __init__(self,amp=1.,alpha=1.,normalize=False,r1=1.,
INPUT:
amp - amplitude to be applied to the potential (default: 1); can be a Quantity with units of mass density or Gxmass density
amp - amplitude to be applied to the potential (default: 1); can be a Quantity with units of mass or Gxmass
alpha - inner power
Expand Down
208 changes: 208 additions & 0 deletions galpy/potential/SphericalShellPotential.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
###############################################################################
# SphericalShellPotential.py: The gravitational potential of a thin,
# spherical shell
###############################################################################
import numpy as nu
from .Potential import Potential, _APY_LOADED
if _APY_LOADED:
from astropy import units
class SphericalShellPotential(Potential):
"""Class that implements the potential of an infinitesimally-thin, spherical shell
.. math::
\\rho(r) = \\frac{\\mathrm{amp}}{4\pi\,r_0^2}\\,\\delta(r-r_0)
"""
def __init__(self,amp=1.,r0=0.75,normalize=False,ro=None,vo=None):
"""
NAME:
__init__
PURPOSE:
initialize a spherical shell potential
INPUT:
amp - mass of the shell (default: 1); can be a Quantity with units of mass or Gxmass
r0= (2.) radius of the shell (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.; note that because the force is always zero at r < r0, this does not work if r_0 > 1
ro=, vo= distance and velocity scales for translation into internal units (default from configuration file)
OUTPUT:
(none)
HISTORY:
2018-08-04 - Written - Bovy (UofT)
"""
Potential.__init__(self,amp=amp,ro=ro,vo=vo,amp_units='mass')
if _APY_LOADED and isinstance(r0,units.Quantity):
r0= r0.to(units.kpc).value/self._ro
self.r0= r0
self.r02= r0**2
if normalize or \
(isinstance(normalize,(int,float)) \
and not isinstance(normalize,bool)):
if self.r0 > 1.:
raise ValueError('SphericalShellPotential with normalize= for r0 > 1 is not supported (because the force is always 0 at r=1)')
self.normalize(normalize)
self.hasC= False
self.hasC_dxdv= False

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:
2018-08-04 - Written - Bovy (UofT)
"""
r2= R**2+z**2
if r2 <= self.r02:
return -1./self.r0
else:
return -1./nu.sqrt(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:
2018-08-04 - Written - Bovy (UofT)
"""
r= nu.sqrt(R**2+z**2)
if r <= self.r0:
return 0.
else:
return -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:
2018-08-04 - Written - Bovy (UofT)
"""
r= nu.sqrt(R**2+z**2)
if r <= self.r0:
return 0.
else:
return -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:
2018-08-04 - Written - Bovy (UofT)
"""
r= nu.sqrt(R**2+z**2)
if r <= self.r0:
return 0.
else:
return (z**2-2*R**2)/r**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:
2018-08-04 - Written - Bovy (UofT)
"""
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:
2018-08-04 - Written - Bovy (UofT)
"""
r= nu.sqrt(R**2+z**2)
if r <= self.r0:
return 0.
else:
return -3*R*z/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:
2018-08-04 - Written - Bovy (UofT)
"""
r2= R**2+z**2
if r2 != self.r02:
return 0.
else: # pragma: no cover
return nu.infty
4 changes: 3 additions & 1 deletion galpy/potential/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
from . import CorotatingRotationWrapperPotential
from . import GaussianAmplitudeWrapperPotential
from . import ChandrasekharDynamicalFrictionForce
from . import SphericalShellPotential
#
# Functions
#
Expand Down Expand Up @@ -145,12 +146,13 @@
DiskSCFPotential = DiskSCFPotential.DiskSCFPotential
SpiralArmsPotential = SpiralArmsPotential.SpiralArmsPotential
HenonHeilesPotential= HenonHeilesPotential.HenonHeilesPotential
ChandrasekharDynamicalFrictionForce= ChandrasekharDynamicalFrictionForce.ChandrasekharDynamicalFrictionForce
SphericalShellPotential= SphericalShellPotential.SphericalShellPotential
#Wrappers
DehnenSmoothWrapperPotential= DehnenSmoothWrapperPotential.DehnenSmoothWrapperPotential
SolidBodyRotationWrapperPotential= SolidBodyRotationWrapperPotential.SolidBodyRotationWrapperPotential
CorotatingRotationWrapperPotential= CorotatingRotationWrapperPotential.CorotatingRotationWrapperPotential
GaussianAmplitudeWrapperPotential= GaussianAmplitudeWrapperPotential.GaussianAmplitudeWrapperPotential
ChandrasekharDynamicalFrictionForce= ChandrasekharDynamicalFrictionForce.ChandrasekharDynamicalFrictionForce
#Softenings
PlummerSoftening= ForceSoftening.PlummerSoftening

Expand Down
18 changes: 12 additions & 6 deletions tests/test_potential.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ def test_normalize_potential():
'MovingObjectPotential',
'interpRZPotential', 'linearPotential', 'planarAxiPotential',
'planarPotential', 'verticalPotential','PotentialError',
'SnapshotRZPotential','InterpSnapshotRZPotential']
'SnapshotRZPotential','InterpSnapshotRZPotential',
'SphericalShellPotential']
if False: #_TRAVIS: #travis CI
rmpots.append('DoubleExponentialDiskPotential')
rmpots.append('RazorThinExponentialDiskPotential')
Expand Down Expand Up @@ -147,7 +148,8 @@ def test_forceAsDeriv_potential():
'MovingObjectPotential',
'interpRZPotential', 'linearPotential', 'planarAxiPotential',
'planarPotential', 'verticalPotential','PotentialError',
'SnapshotRZPotential','InterpSnapshotRZPotential']
'SnapshotRZPotential','InterpSnapshotRZPotential',
'SphericalShellPotential']
if False: #_TRAVIS: #travis CI
rmpots.append('DoubleExponentialDiskPotential')
rmpots.append('RazorThinExponentialDiskPotential')
Expand Down Expand Up @@ -310,7 +312,8 @@ def test_2ndDeriv_potential():
'MovingObjectPotential',
'interpRZPotential', 'linearPotential', 'planarAxiPotential',
'planarPotential', 'verticalPotential','PotentialError',
'SnapshotRZPotential','InterpSnapshotRZPotential']
'SnapshotRZPotential','InterpSnapshotRZPotential',
'SphericalShellPotential']
if False: #_TRAVIS: #travis CI
rmpots.append('DoubleExponentialDiskPotential')
rmpots.append('RazorThinExponentialDiskPotential')
Expand Down Expand Up @@ -529,7 +532,8 @@ def test_poisson_potential():
'MovingObjectPotential',
'interpRZPotential', 'linearPotential', 'planarAxiPotential',
'planarPotential', 'verticalPotential','PotentialError',
'SnapshotRZPotential','InterpSnapshotRZPotential']
'SnapshotRZPotential','InterpSnapshotRZPotential',
'SphericalShellPotential']
if False: #_TRAVIS: #travis CI
rmpots.append('DoubleExponentialDiskPotential')
rmpots.append('RazorThinExponentialDiskPotential')
Expand Down Expand Up @@ -647,7 +651,8 @@ def test_evaluateAndDerivs_potential():
'MovingObjectPotential',
'interpRZPotential', 'linearPotential', 'planarAxiPotential',
'planarPotential', 'verticalPotential','PotentialError',
'SnapshotRZPotential','InterpSnapshotRZPotential']
'SnapshotRZPotential','InterpSnapshotRZPotential',
'SphericalShellPotential']
if False: #_TRAVIS: #travis CI
rmpots.append('DoubleExponentialDiskPotential')
rmpots.append('RazorThinExponentialDiskPotential')
Expand Down Expand Up @@ -923,7 +928,8 @@ def test_toVertical_toPlanar():
'MovingObjectPotential',
'interpRZPotential', 'linearPotential', 'planarAxiPotential',
'planarPotential', 'verticalPotential','PotentialError',
'SnapshotRZPotential','InterpSnapshotRZPotential']
'SnapshotRZPotential','InterpSnapshotRZPotential',
'SphericalShellPotential']
if False: #_TRAVIS: #travis CI
rmpots.append('DoubleExponentialDiskPotential')
rmpots.append('RazorThinExponentialDiskPotential')
Expand Down
30 changes: 30 additions & 0 deletions tests/test_quantity.py
Original file line number Diff line number Diff line change
Expand Up @@ -1772,6 +1772,14 @@ def test_potential_ampunits():
# # SpiralArmsPotential
# pot= potential.SpiralArmsPotential(amp=0.3*units.Msun / units.pc**3)
# assert numpy.fabs(pot(1.,0.,phi=1.,use_physical=False)*) < 10.**-8., "SpiralArmsPotential w/ amp w/ units does not behave as expected"
# SphericalShellPotential
pot= potential.SphericalShellPotential(amp=4.*10.**10.*units.Msun,
ro=ro,vo=vo)
pot_nounits= potential.SphericalShellPotential(\
amp=4./bovy_conversion.mass_in_1010msol(vo,ro),
ro=ro,vo=vo)
# Check potential
assert numpy.fabs(pot(4.,0.,phi=1.,use_physical=False)-pot_nounits(4.,0.,phi=1.,use_physical=False)) < 10.**-8., "SphericalShellPotential w/ amp w/ units does not behave as expected"
return None

def test_potential_ampunits_altunits():
Expand Down Expand Up @@ -1919,6 +1927,14 @@ def test_potential_ampunits_altunits():
a=1.,b=2.,c=3.,pa=0.,omegab=0.,ro=ro,vo=vo)
# Check potential
assert numpy.fabs(pot(4.,0.,phi=1.,use_physical=False)-pot_nounits(4.,0.,phi=1.,use_physical=False)) < 10.**-8., "FerrersPotential w/ amp w/ units does not behave as expected"
# SphericalShellPotential
pot= potential.SphericalShellPotential(amp=4.*10.**10.*units.Msun*constants.G,
ro=ro,vo=vo)
pot_nounits= potential.SphericalShellPotential(\
amp=4./bovy_conversion.mass_in_1010msol(vo,ro),
ro=ro,vo=vo)
# Check potential
assert numpy.fabs(pot(4.,0.,phi=1.,use_physical=False)-pot_nounits(4.,0.,phi=1.,use_physical=False)) < 10.**-8., "SphericalShellPotential w/ amp w/ units does not behave as expected"
return None

def test_potential_ampunits_wrongunits():
Expand Down Expand Up @@ -2033,6 +2049,10 @@ def test_potential_ampunits_wrongunits():
# SpiralArmsPotential
with pytest.raises(units.UnitConversionError) as excinfo:
potential.SpiralArmsPotential(amp=10**10 * units.Msun)
# SphericalShellPotential
with pytest.raises(units.UnitConversionError) as excinfo:
potential.SphericalShellPotential(amp=40.*units.Msun/units.pc**2,
r0=2.,ro=ro,vo=vo)
return None

def test_potential_paramunits():
Expand Down Expand Up @@ -2389,6 +2409,16 @@ def test_potential_paramunits():
GMs=10.**8./bovy_conversion.mass_in_msol(vo,ro),rhm=12/ro,
minr=1./ro/1000.,maxr=100./ro)
assert numpy.fabs(pot_nounits.Rforce(1.5,0.3,phi=0.1,v=[1.,0.,0.],use_physical=False)-pot_nounits_direct.Rforce(1.5,0.3,phi=0.1,v=[1.,0.,0.],use_physical=False)) < 10.**-8., "ChandrasekharDynamicalFrictionForce w/ parameters w/ units does not behave as expected"
# SphericalShellPotential
pot= potential.SphericalShellPotential(amp=4.*10.**10.*units.Msun,
r0=5.*units.kpc,
ro=ro,vo=vo)
pot_nounits= potential.SphericalShellPotential(\
amp=4.*10.**10.*units.Msun,
r0=5./ro,
ro=ro,vo=vo)
# Check potential
assert numpy.fabs(pot(4.,0.,phi=1.,use_physical=False)-pot_nounits(4.,0.,phi=1.,use_physical=False)) < 10.**-8., "SphericalShellPotential w/ amp w/ units does not behave as expected"
# If you add one here, don't base it on ChandrasekharDynamicalFrictionForce!!
return None

Expand Down

0 comments on commit b713308

Please sign in to comment.