Multipole Expansion Potential¶
- class galpy.potential.MultipoleExpansionPotential(amp=1.0, rho_cos_splines=None, rho_sin_splines=None, rgrid=array([1.00000000e-03, 1.01036227e-03, 1.02083192e-03, ..., 2.93877957e+01, 2.96923201e+01, 3.00000000e+01], shape=(1001,)), tgrid=None, normalize=False, ro=None, vo=None)[source]¶
Class that implements a gravitational potential computed via multipole expansion of an arbitrary density distribution.
This class decomposes a user-supplied density function into real spherical harmonics on a radial grid, then evaluates the gravitational potential using classical multipole integrals (the real-valued form of Bovy 2026, Chapter 12.3.1, equations 12.78–12.79):
\[\rho(r,\theta,\phi) = \sum_{l=0}^{L-1}\sum_{m=0}^{l}\,\left[\rho^{\cos}_{lm}(r)\,\cos(m\phi) + \rho^{\sin}_{lm}(r)\,\sin(m\phi)\right]\,P_l^m(\cos\theta)\,,\]where the radial density coefficients are obtained by projection:
\[ \begin{align}\begin{aligned}\rho^{\cos}_{lm}(r) = \alpha_{lm} \int_0^{\pi}\!\int_0^{2\pi} \rho(r,\theta,\phi)\,P_l^m(\cos\theta)\,\cos(m\phi)\,\sin\theta\,\mathrm{d}\phi\,\mathrm{d}\theta\,,\\\rho^{\sin}_{lm}(r) = \alpha_{lm} \int_0^{\pi}\!\int_0^{2\pi} \rho(r,\theta,\phi)\,P_l^m(\cos\theta)\,\sin(m\phi)\,\sin\theta\,\mathrm{d}\phi\,\mathrm{d}\theta\,,\end{aligned}\end{align} \]with \(\alpha_{lm} = \sqrt{\frac{2l+1}{4\pi}\,\frac{(l-m)!}{(l+m)!}}\). The gravitational potential has an analogous expansion:
\[\Phi(r, \theta, \phi) = \sum_{l=0}^{L-1}\sum_{m=0}^{l}\,\left[\Phi^{\cos}_{lm}(r)\,\cos(m\phi) + \Phi^{\sin}_{lm}(r)\,\sin(m\phi)\right]\,P_l^m(\cos\theta)\,,\]where the radial potential functions are given by:
\[\Phi^{\cos,\sin}_{lm}(r) = -\frac{4\pi}{2l+1} \left[r^{-(l+1)} \int_0^r a^{l+2} \rho^{\cos,\sin}_{lm}(a) \, da + r^l \int_r^{\infty} a^{1-l} \rho^{\cos,\sin}_{lm}(a) \, da\right]\]The spherical harmonic decomposition is performed via Gauss-Legendre quadrature (theta) and trapezoidal rule (phi). Radial integrals are evaluated to high precision using spline integration and precomputed cumulative integrals (I_inner, I_outer), which are stored as Bernstein polynomials to ensure smooth derivatives that satisfy the radial Poisson equation exactly. Outside of the radial grid, below the minimum radius, a constant-density extrapolation is used (with density equal to the value at the minimum grid radius), while above the maximum radius, the density is assumed to be zero.
Time-dependent potentials are supported in two ways:
Via direct initialization: pass
rho_cos_splines(and optionallyrho_sin_splines) as lists of callablesf(r, t)instead ofInterpolatedUnivariateSplineinstances, together with atgridarray. Each callable should return the normalized radial density coefficient at the given radius and time (see therho_cos_splinesparameter documentation for details on the normalization convention).Via
from_density: pass a density function that accepts atkeyword argument, i.e.,dens(R, z, phi, t=0.0), together with atgridarray of time grid points. The density function must be in galpy’s internal units (astropyQuantityoutput is not supported for time-dependent densities).
In both cases, the radial multipole integrals are precomputed at each time in
tgrid, and the resulting radial functions \(\Phi^{\cos,\sin}_{lm}(r, t)\) are interpolated in time using piecewise polynomials. This allows efficient evaluation of the potential, forces, and second derivatives at arbitrary times within thetgridrange during orbit integration.- __init__(amp=1.0, rho_cos_splines=None, rho_sin_splines=None, rgrid=array([1.00000000e-03, 1.01036227e-03, 1.02083192e-03, ..., 2.93877957e+01, 2.96923201e+01, 3.00000000e+01], shape=(1001,)), tgrid=None, normalize=False, ro=None, vo=None)[source]¶
Initialize a MultipoleExpansionPotential from precomputed density splines (use
MultipoleExpansionPotential.from_densityto initialize from a density function).- Parameters:
amp (float or Quantity, optional) – Amplitude to be applied to the potential (default: 1).
rho_cos_splines (list of list of InterpolatedUnivariateSpline, optional) – Cosine density coefficient splines with the spherical harmonic normalization absorbed, shape
[L][M].rho_cos_splines[l][m]is a spline representing \(\hat{\rho}^{\cos}_{lm}(r) = \beta_{lm}\,\rho^{\cos}_{lm}(r)\) as a function ofr, where \(\rho^{\cos}_{lm}(r)\) are the coefficients in the real spherical-harmonic expansion of the density \(\rho(r,\theta,\phi) = \sum_{l,m} [\rho^{\cos}_{lm}(r)\,\cos(m\phi) + \rho^{\sin}_{lm}(r)\,\sin(m\phi)]\,P_l^m(\cos\theta)\) (the real-valued form of Eq. 12.78 in Bovy 2026) and \(\beta_{lm} = (2-\delta_{m0})\,\sqrt{\frac{2l+1}{4\pi}\,\frac{(l-m)!}{(l+m)!}}\) is the real spherical harmonic normalization factor. IfNone, computes a default Hernquist monopole. For time-dependent potentials, these can be callablesf(r, t)instead ofInterpolatedUnivariateSplineinstances; in that casetgridmust also be provided.rho_sin_splines (list of list of InterpolatedUnivariateSpline, optional) – Like
rho_cos_splinesbut for the sine coefficients:rho_sin_splines[l][m]represents \(\hat{\rho}^{\sin}_{lm}(r) = \beta_{lm}\,\rho^{\sin}_{lm}(r)\). IfNone, set to zero splines matchingrho_cos_splines. For time-dependent potentials, these can be callablesf(r, t)likerho_cos_splines.rgrid (numpy.ndarray, optional) – Radial grid points (1D array). Default:
numpy.geomspace(1e-3, 30, 1_001).tgrid (numpy.ndarray or None, optional) – Time grid for time-dependent potentials. If provided,
rho_cos_splinesandrho_sin_splinesare interpreted as callablesf(r, t)and the potential is time-dependent: BPoly radial integrals are precomputed at each time intgridand interpolated in time at evaluation. Default:None(static potential).normalize (bool or float, optional) – 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 (float or Quantity, optional) – Distance scale for translation into internal units (default from configuration file).
vo (float or Quantity, optional) – Velocity scale for translation into internal units (default from configuration file).
Notes
2026-02-13 - Written - Bovy (UofT)
2026-03-06 - Refactored to accept splines; density computation moved to from_density - Bovy (UofT)
2026-03-23 - Added time-dependent support via tgrid - Bovy (UofT)
- classmethod from_density(dens, L=6, rgrid=array([1.00000000e-03, 1.01036227e-03, 1.02083192e-03, ..., 2.93877957e+01, 2.96923201e+01, 3.00000000e+01], shape=(1001,)), tgrid=None, symmetry=None, costheta_order=None, phi_order=None, amp=1.0, normalize=False, ro=None, vo=None)[source]¶
Initialize a MultipoleExpansionPotential from a density function.
- Parameters:
dens (callable or Potential) – Density function. Can take 1 arg (r), 2 args (R, z), or 3 args (R, z, phi). Can also be a galpy Potential instance. For time-dependent densities, add a
tkeyword argument (e.g.,dens(R, z, phi, t=0.)).L (int, optional) – Maximum spherical harmonic degree + 1 (l goes from 0 to L-1). Default: 6.
rgrid (numpy.ndarray, optional) – Radial grid points (1D array). Default:
numpy.geomspace(1e-3, 30, 1_001).tgrid (numpy.ndarray or None, optional) – Time grid for time-dependent potentials. Required when the density function accepts a
tparameter. Default:None.symmetry (str or None, optional) –
'spherical','axisymmetric', orNone(general). Determines M.costheta_order (int, optional) – Gauss-Legendre quadrature order for theta. Default:
max(20, L+1).phi_order (int, optional) – Number of uniform phi points for trapezoidal rule. Default:
max(20, 2*L+1).amp (float or Quantity, optional) – Amplitude to be applied to the potential (default: 1).
normalize (bool or float, optional) – 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 (float or Quantity, optional) – Distance scale for translation into internal units (default from configuration file).
vo (float or Quantity, optional) – Velocity scale for translation into internal units (default from configuration file).
- Returns:
A new MultipoleExpansionPotential instance.
- Return type:
Notes
2026-03-06 - Written - Bovy (UofT)
2026-03-23 - Added time-dependent support - Bovy (UofT)