From 04b596d7084790abfe5c9652e14e3f01fa90e293 Mon Sep 17 00:00:00 2001 From: Jammy2211 Date: Tue, 26 May 2026 11:29:52 +0100 Subject: [PATCH] docs: LaTeX docstrings for all mass profile classes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add Sphinx-compatible LaTeX docstrings with mathematical definitions, paper references, and parameter descriptions to every mass profile class: total (Isothermal, PowerLaw, dPIE families), dark (NFW, gNFW, cNFW), stellar (Sersic, Gaussian, Chameleon), sheets (ExternalShear, MassSheet, ExternalPotential), point (PointMass, SMBH, SMBHBinary), and abstract decomposers (MGEDecomposer, MassProfileCSE). 26 files, 1283 insertions. Documentation only — no code logic changed. Phase 5 (final) of mass profiles refactoring epic (#445). Co-Authored-By: Claude Opus 4.7 --- autogalaxy/profiles/mass/abstract/cse.py | 30 ++++ autogalaxy/profiles/mass/abstract/mge.py | 28 +++- autogalaxy/profiles/mass/dark/abstract.py | 30 ++++ autogalaxy/profiles/mass/dark/cnfw.py | 59 ++++++-- autogalaxy/profiles/mass/dark/gnfw.py | 61 +++++++- autogalaxy/profiles/mass/dark/nfw.py | 79 ++++++++-- .../profiles/mass/dark/nfw_truncated.py | 45 ++++++ autogalaxy/profiles/mass/point/point.py | 33 ++++- autogalaxy/profiles/mass/point/smbh.py | 36 +++-- autogalaxy/profiles/mass/point/smbh_binary.py | 51 +++++-- .../mass/sheets/external_potential.py | 28 ++++ .../profiles/mass/sheets/external_shear.py | 40 +++++ autogalaxy/profiles/mass/sheets/mass_sheet.py | 34 ++++- autogalaxy/profiles/mass/stellar/chameleon.py | 92 ++++++++---- .../profiles/mass/stellar/dev_vaucouleurs.py | 61 ++++++-- .../profiles/mass/stellar/exponential.py | 49 ++++-- autogalaxy/profiles/mass/stellar/gaussian.py | 35 ++++- autogalaxy/profiles/mass/stellar/sersic.py | 99 ++++++++++--- .../mass/total/dual_pseudo_isothermal_mass.py | 84 +++++++++++ .../total/dual_pseudo_isothermal_potential.py | 68 +++++++++ autogalaxy/profiles/mass/total/isothermal.py | 52 +++++++ .../profiles/mass/total/isothermal_core.py | 56 +++++++ autogalaxy/profiles/mass/total/power_law.py | 58 ++++++++ .../profiles/mass/total/power_law_broken.py | 79 ++++++++++ .../profiles/mass/total/power_law_core.py | 66 +++++++++ .../mass/total/power_law_multipole.py | 139 ++++++++++-------- 26 files changed, 1283 insertions(+), 209 deletions(-) diff --git a/autogalaxy/profiles/mass/abstract/cse.py b/autogalaxy/profiles/mass/abstract/cse.py index b3497126f..8d880dc42 100644 --- a/autogalaxy/profiles/mass/abstract/cse.py +++ b/autogalaxy/profiles/mass/abstract/cse.py @@ -4,6 +4,36 @@ class MassProfileCSE(ABC): + r""" + Cored Steep Ellipsoid (CSE) decomposition mixin for mass profiles. + + Provides the machinery to decompose an arbitrary projected convergence profile into + a sum of cored steep ellipsoid (CSE) basis functions, enabling analytic deflection + angle evaluation via Oguri (2021). + + The 1-D CSE convergence basis function (Oguri 2021 Eq. 14) is: + + .. math:: + + \kappa_{\rm CSE}(r; s) = \frac{1}{2(s^2 + r^2)^{3/2}} + + where :math:`s` is the CSE core radius. An arbitrary convergence profile + :math:`\kappa(r)` is approximated as: + + .. math:: + + \kappa(r) \approx \sum_{j} A_j \, \kappa_{\rm CSE}(r; s_j) + + The amplitudes :math:`A_j` and core radii :math:`s_j` are found by linear least + squares on a log-spaced radial grid. The deflection angles of each CSE component + are computed analytically from Oguri (2021) Eq. 19–20, and the total deflection is + their sum. + + References + ---------- + - Oguri 2021, PASP, 133, 074504 (arXiv:2106.11464) + """ + @staticmethod def convergence_cse_1d_from( grid_radii: np.ndarray, core_radius: float, xp=np diff --git a/autogalaxy/profiles/mass/abstract/mge.py b/autogalaxy/profiles/mass/abstract/mge.py index 7f7a9e21c..f5261790b 100644 --- a/autogalaxy/profiles/mass/abstract/mge.py +++ b/autogalaxy/profiles/mass/abstract/mge.py @@ -5,11 +5,31 @@ class MGEDecomposer: - """ - This class speeds up deflection angle calculations of certain mass profiles by decompositing them into many - Gaussians. + r""" + Multi-Gaussian Expansion (MGE) decomposer for arbitrary mass profiles. + + Accelerates deflection-angle and convergence calculations by decomposing a mass + profile's projected convergence (or 3-D density) into a sum of Gaussian components, + each of which has an analytic deflection-angle formula via the Faddeeva (scaled + complementary error) function. + + The decomposition of an arbitrary radial function :math:`f(\sigma)` into Gaussians + with amplitudes :math:`A_j` and widths :math:`\sigma_j` follows the numerical + contour-integration quadrature of Shajib (2019) Eq. 6: + + .. math:: + + f(\sigma) \approx \sum_{j} A_j \exp\!\left(-\frac{r^2}{2\sigma_j^2}\right) + + The deflection angles of each Gaussian component are then evaluated analytically via + the complex Faddeeva function :math:`w(z) = e^{-z^2}\,\mathrm{erfc}(-iz)`. + + Supported ellipticity conventions for the scale-parameter :math:`\sigma` are + ``'major'``, ``'circularised'``, and ``'minor'`` (see :meth:`sigmas_factor_from`). - This follows the method of Shajib 2019 - https://academic.oup.com/mnras/article/488/1/1387/5526256 + References + ---------- + - Shajib 2019, MNRAS, 488, 1387 (arXiv:1906.08263) """ def __init__( diff --git a/autogalaxy/profiles/mass/dark/abstract.py b/autogalaxy/profiles/mass/dark/abstract.py index ee65c4738..07e5c8580 100644 --- a/autogalaxy/profiles/mass/dark/abstract.py +++ b/autogalaxy/profiles/mass/dark/abstract.py @@ -15,6 +15,36 @@ class DarkProfile: class AbstractgNFW(MassProfile, DarkProfile): + r""" + Abstract base class for generalised NFW (gNFW) dark matter halo profiles. + + The three-dimensional density profile of the gNFW family is: + + .. math:: + + \rho(r) = \frac{\rho_s}{(r/r_s)^{\gamma}(1 + r/r_s)^{3-\gamma}} + + where :math:`\gamma` is the inner logarithmic slope (``inner_slope``), :math:`r_s` is + the scale radius, and :math:`\rho_s` is the characteristic density related to the + dimensionless convergence normalisation :math:`\kappa_s` via: + + .. math:: + + \kappa_s = \frac{\rho_s \, r_s}{\Sigma_{\rm crit}} + + The projected convergence is computed from this 3-D density via line-of-sight integration + using the auxiliary coordinate functions :math:`f(x)`, :math:`g(x)`, and :math:`h(x)`, + where :math:`x = \xi / r_s` and :math:`\xi` is the elliptical radius. + + The standard NFW profile (:class:`NFW`) is recovered for :math:`\gamma = 1`. + + References + ---------- + - Navarro, Frenk & White 1996, ApJ, 462, 563 + - Navarro, Frenk & White 1997, ApJ, 490, 493 + - Wyithe, Turner & Spergel 2001, ApJ, 555, 504 + """ + epsrel = 1.49e-5 def __init__( diff --git a/autogalaxy/profiles/mass/dark/cnfw.py b/autogalaxy/profiles/mass/dark/cnfw.py index dcb7c6b35..9ade2cf5b 100644 --- a/autogalaxy/profiles/mass/dark/cnfw.py +++ b/autogalaxy/profiles/mass/dark/cnfw.py @@ -9,6 +9,29 @@ class cNFW(AbstractgNFW): + r""" + Elliptical cored NFW (cNFW) dark matter halo profile. + + The three-dimensional density profile introduces a constant-density core of + radius :math:`r_c` that suppresses the central cusp of the standard NFW profile: + + .. math:: + + \rho(r) = \frac{\rho_0 \, r_s^3}{(r + r_c)(r + r_s)^2} + + where :math:`r_c` is the core radius (``core_radius``) and :math:`r_s` is the + scale radius (``scale_radius``). In the limit :math:`r_c \to 0` the profile + approaches the standard NFW density. + + The convergence and deflection angles are computed via a Multi-Gaussian + Expansion (MGE) decomposition following Shajib (2019). + + References + ---------- + - Read, Agertz & Collins 2016, MNRAS, 459, 2573 + - Shajib 2019, MNRAS, 488, 1387 (arXiv:1906.08263) + """ + def __init__( self, centre: Tuple[float, float] = (0.0, 0.0), @@ -17,9 +40,7 @@ def __init__( scale_radius: float = 1.0, core_radius: float = 0.01, ): - """ - Represents a cored NFW density distribution - + r""" Parameters ---------- centre @@ -28,11 +49,11 @@ def __init__( The first and second ellipticity components of the elliptical coordinate system. kappa_s The overall normalization of the dark matter halo - (kappa_s = (rho_0 * D_d * scale_radius)/lensing_critical_density) + (:math:`\kappa_s = \rho_0 D_d r_s / \Sigma_{\rm crit}`). scale_radius - The cored NFW scale radius `theta_s`, as an angle on the sky in arcseconds. + The cored NFW scale radius :math:`r_s`, as an angle on the sky in arcseconds. core_radius - The cored NFW core radius `theta_c`, as an angle on the sky in arcseconds. + The cored NFW core radius :math:`r_c`, as an angle on the sky in arcseconds. """ super().__init__(centre=centre, ell_comps=ell_comps) @@ -103,6 +124,22 @@ def potential_2d_from(self, grid: aa.type.Grid2DLike, xp=np, **kwargs): class cNFWSph(cNFW): + r""" + Spherical cored NFW (cNFW) dark matter halo profile. + + A special case of :class:`cNFW` with no ellipticity (:math:`q = 1`). The 3-D + density and projected convergence follow the same cored-NFW expressions with + an analytic deflection-angle formula available for the spherical case: + + .. math:: + + \rho(r) = \frac{\rho_0 \, r_s^3}{(r + r_c)(r + r_s)^2} + + References + ---------- + - Read, Agertz & Collins 2016, MNRAS, 459, 2573 + """ + def __init__( self, centre: Tuple[float, float] = (0.0, 0.0), @@ -110,20 +147,18 @@ def __init__( scale_radius: float = 1.0, core_radius: float = 0.01, ): - """ - Represents a spherical cored NFW density distribution - + r""" Parameters ---------- centre The (y,x) arc-second coordinates of the profile centre. kappa_s The overall normalization of the dark matter halo - (kappa_s = (rho_0 * D_d * scale_radius)/lensing_critical_density) + (:math:`\kappa_s = \rho_0 D_d r_s / \Sigma_{\rm crit}`). scale_radius - The cored NFW scale radius `theta_s`, as an angle on the sky in arcseconds. + The cored NFW scale radius :math:`r_s`, as an angle on the sky in arcseconds. core_radius - The cored NFW core radius `theta_c`, as an angle on the sky in arcseconds. + The cored NFW core radius :math:`r_c`, as an angle on the sky in arcseconds. """ super().__init__(centre=centre, ell_comps=(0.0, 0.0)) diff --git a/autogalaxy/profiles/mass/dark/gnfw.py b/autogalaxy/profiles/mass/dark/gnfw.py index 8a182c173..9afd78055 100644 --- a/autogalaxy/profiles/mass/dark/gnfw.py +++ b/autogalaxy/profiles/mass/dark/gnfw.py @@ -8,6 +8,38 @@ class gNFW(AbstractgNFW): + r""" + Elliptical generalised NFW (gNFW) dark matter halo profile with a free inner slope. + + The three-dimensional density profile is: + + .. math:: + + \rho(r) = \frac{\rho_s}{(r/r_s)^{\gamma}(1 + r/r_s)^{3-\gamma}} + + where :math:`\gamma` is the inner logarithmic slope (``inner_slope``). For + :math:`\gamma = 1` this reduces to the standard :class:`NFW` profile. + + The projected convergence is computed by numerical line-of-sight integration: + + .. math:: + + \kappa(\xi) = 2 \kappa_s \, (\xi/r_s)^{1-\gamma} + \left[ + (1 + \xi/r_s)^{\gamma-3} + + (3 - \gamma) \int_0^1 \frac{(y + \xi/r_s)^{\gamma-4} + \left(1 - \sqrt{1 - y^2}\right)}{1} \, \mathrm{d}y + \right] + + Deflection angles are computed via a Multi-Gaussian Expansion (MGE) decomposition + following Shajib (2019). + + References + ---------- + - Wyithe, Turner & Spergel 2001, ApJ, 555, 504 + - Shajib 2019, MNRAS, 488, 1387 (arXiv:1906.08263) + """ + def deflections_yx_2d_from(self, grid: aa.type.Grid2DLike, xp=np, **kwargs): return self.deflections_2d_via_mge_from(grid=grid, xp=xp, **kwargs) @@ -60,6 +92,22 @@ def integral_y(y, eta): class gNFWSph(gNFW): + r""" + Spherical generalised NFW (gNFW) dark matter halo profile with a free inner slope. + + A special case of :class:`gNFW` with no ellipticity (:math:`q = 1`). The 3-D + density and projected convergence follow the same gNFW expressions as the + elliptical variant but evaluated on a circular radial grid. + + .. math:: + + \rho(r) = \frac{\rho_s}{(r/r_s)^{\gamma}(1 + r/r_s)^{3-\gamma}} + + References + ---------- + - Wyithe, Turner & Spergel 2001, ApJ, 555, 504 + """ + def __init__( self, centre: Tuple[float, float] = (0.0, 0.0), @@ -67,21 +115,18 @@ def __init__( inner_slope: float = 1.0, scale_radius: float = 1.0, ): - """ - The spherical NFW profiles, used to fit the dark matter halo of the lens. - + r""" Parameters ---------- centre The (y,x) arc-second coordinates of the profile centre. kappa_s - The overall normalization of the dark matter halo \ - (kappa_s = (rho_s * scale_radius)/lensing_critical_density) + The overall normalization of the dark matter halo + (:math:`\kappa_s = \rho_s r_s / \Sigma_{\rm crit}`). inner_slope - The inner slope of the dark matter halo. + The inner logarithmic slope :math:`\gamma` of the dark matter density profile. scale_radius - The arc-second radius where the average density within this radius is 200 times the critical density of \ - the Universe.. + The NFW scale radius :math:`r_s`, as an angle on the sky in arcseconds. """ super().__init__( diff --git a/autogalaxy/profiles/mass/dark/nfw.py b/autogalaxy/profiles/mass/dark/nfw.py index 3b7551701..9963211d0 100644 --- a/autogalaxy/profiles/mass/dark/nfw.py +++ b/autogalaxy/profiles/mass/dark/nfw.py @@ -10,6 +10,42 @@ class NFW(gNFW, MassProfileCSE): + r""" + Elliptical Navarro-Frenk-White (NFW) dark matter halo profile. + + The NFW profile is the special case of the generalised NFW with inner slope + :math:`\gamma = 1`. The projected surface mass density (convergence) is: + + .. math:: + + \kappa_{\rm NFW}(x) = 2 \kappa_s \, g(x), \qquad x = \frac{\xi}{r_s} + + where :math:`\xi` is the elliptical radius, :math:`r_s` is the scale radius, + :math:`\kappa_s = \rho_s r_s / \Sigma_{\rm crit}` is the dimensionless + characteristic convergence, and + + .. math:: + + g(x) = \begin{cases} + \dfrac{1 - \tfrac{1}{\sqrt{1-x^2}}\,\mathrm{arccosh}(1/x)}{x^2 - 1} + & x < 1 \\[6pt] + \dfrac{1}{3} & x = 1 \\[6pt] + \dfrac{1 - \tfrac{1}{\sqrt{x^2-1}}\,\arccos(1/x)}{x^2 - 1} + & x > 1 + \end{cases} + + Deflection angles are computed analytically via the Heyrovský & Karamazov (2024) + formalism, or alternatively via a cored-steep-ellipsoid (CSE) decomposition + following Oguri (2021). + + References + ---------- + - Navarro, Frenk & White 1996, ApJ, 462, 563 + - Navarro, Frenk & White 1997, ApJ, 490, 493 + - Oguri 2021, PASP, 133, 074504 (arXiv:2106.11464) + - Heyrovský & Karamazov 2024 (arXiv:2407.xxxxx) + """ + def __init__( self, centre: Tuple[float, float] = (0.0, 0.0), @@ -18,8 +54,6 @@ def __init__( scale_radius: float = 1.0, ): r""" - The elliptical NFW profiles, used to fit the dark matter halo of the lens. - Parameters ---------- centre @@ -27,10 +61,10 @@ def __init__( ell_comps The first and second ellipticity components of the elliptical coordinate system. kappa_s - The overall normalization of the dark matter halo \| - (kappa_s = (rho_s * scale_radius)/lensing_critical_density) + The overall normalization of the dark matter halo + (:math:`\kappa_s = \rho_s r_s / \Sigma_{\rm crit}`). scale_radius - The NFW scale radius `r_s`, as an angle on the sky in arcseconds. + The NFW scale radius :math:`r_s`, as an angle on the sky in arcseconds. """ super().__init__( @@ -267,6 +301,32 @@ def convergence_2d_from(self, grid: aa.type.Grid2DLike, xp=np, **kwargs): class NFWSph(NFW): + r""" + Spherical Navarro-Frenk-White (NFW) dark matter halo profile. + + A special case of :class:`NFW` with no ellipticity (:math:`q = 1`). The convergence + is the standard NFW convergence evaluated on a circular radial grid: + + .. math:: + + \kappa_{\rm NFW}(r) = 2 \kappa_s \, g(r/r_s) + + and the analytic deflection angle at projected radius :math:`r` is: + + .. math:: + + \alpha(r) = \frac{4 \kappa_s r_s}{r/r_s}\,h(r/r_s) + + where :math:`h(x) = \ln(x/2) + f(x)` and :math:`f(x)` is the standard NFW + auxiliary function. The lensing potential is also available analytically. + + References + ---------- + - Navarro, Frenk & White 1996, ApJ, 462, 563 + - Navarro, Frenk & White 1997, ApJ, 490, 493 + - Wright & Brainerd 2000, ApJ, 534, 34 + """ + def __init__( self, centre: Tuple[float, float] = (0.0, 0.0), @@ -274,18 +334,15 @@ def __init__( scale_radius: float = 1.0, ): r""" - The spherical NFW profiles, used to fit the dark matter halo of the lens. - Parameters ---------- centre The (y,x) arc-second coordinates of the profile centre. kappa_s - The overall normalization of the dark matter halo \ - (kappa_s = (rho_s * scale_radius)/lensing_critical_density) + The overall normalization of the dark matter halo + (:math:`\kappa_s = \rho_s r_s / \Sigma_{\rm crit}`). scale_radius - The arc-second radius where the average density within this radius is 200 times the critical density of \ - the Universe.. + The NFW scale radius :math:`r_s`, as an angle on the sky in arcseconds. """ super().__init__( diff --git a/autogalaxy/profiles/mass/dark/nfw_truncated.py b/autogalaxy/profiles/mass/dark/nfw_truncated.py index aeb15f433..5a9e8df3a 100644 --- a/autogalaxy/profiles/mass/dark/nfw_truncated.py +++ b/autogalaxy/profiles/mass/dark/nfw_truncated.py @@ -8,6 +8,37 @@ class NFWTruncatedSph(AbstractgNFW): + r""" + Spherical truncated NFW (tNFW) dark matter halo profile (Baltz, Marshall & Oguri 2009). + + The tNFW profile introduces a smooth truncation of the NFW density at a truncation + radius :math:`r_t`, characterised by the dimensionless truncation ratio + :math:`\tau = r_t / r_s`: + + .. math:: + + \rho(r) = \frac{\rho_s}{(r/r_s)(1 + r/r_s)^2} + \left(\frac{\tau^2}{\tau^2 + (r/r_s)^2}\right) + + where :math:`r_s` is the scale radius and :math:`\rho_s` is related to the + dimensionless convergence normalisation via + :math:`\kappa_s = \rho_s r_s / \Sigma_{\rm crit}`. + + The projected convergence is given by: + + .. math:: + + \kappa(x) = 2 \kappa_s \, L(x, \tau) + + where :math:`L(x, \tau)` is the auxiliary function defined in Baltz et al. (2009) + (implemented here as ``coord_func_l``). + + References + ---------- + - Baltz, Marshall & Oguri 2009, JCAP, 2009, 015 (arXiv:0705.0735) + - Navarro, Frenk & White 1997, ApJ, 490, 493 + """ + def __init__( self, centre: Tuple[float, float] = (0.0, 0.0), @@ -15,6 +46,20 @@ def __init__( scale_radius: float = 1.0, truncation_radius: float = 2.0, ): + r""" + Parameters + ---------- + centre + The (y,x) arc-second coordinates of the profile centre. + kappa_s + The dimensionless convergence normalisation + (:math:`\kappa_s = \rho_s r_s / \Sigma_{\rm crit}`). + scale_radius + The NFW scale radius :math:`r_s`, as an angle on the sky in arcseconds. + truncation_radius + The truncation radius :math:`r_t`, as an angle on the sky in arcseconds. + The dimensionless truncation ratio is :math:`\tau = r_t / r_s`. + """ super().__init__( centre=centre, ell_comps=(0.0, 0.0), diff --git a/autogalaxy/profiles/mass/point/point.py b/autogalaxy/profiles/mass/point/point.py index 163a7682d..495c00520 100644 --- a/autogalaxy/profiles/mass/point/point.py +++ b/autogalaxy/profiles/mass/point/point.py @@ -8,18 +8,43 @@ class PointMass(MassProfile): + r""" + Point mass lens profile. + + A point mass produces a convergence that is a Dirac delta function at the lens + centre, a logarithmic potential, and a deflection angle that falls off as + :math:`1/r`: + + .. math:: + + \kappa(\boldsymbol{\theta}) = \pi \theta_E^2 \, \delta^{(2)}(\boldsymbol{\theta}) + + .. math:: + + \psi(\boldsymbol{\theta}) = \theta_E^2 \ln r + + .. math:: + + \boldsymbol{\alpha}(\boldsymbol{\theta}) = \frac{\theta_E^2}{r}\,\hat{r} + + where :math:`\theta_E` is the Einstein radius (``einstein_radius``) and :math:`r` + is the angular distance from the lens centre. + + This profile is used to represent compact objects such as black holes or stars. + In practice the convergence grid value at the centre pixel is set to zero; the + point-mass nature is captured entirely through the deflection and potential. + """ + def __init__( self, centre: Tuple[float, float] = (0.0, 0.0), einstein_radius: float = 1.0 ): - """ - Represents a point-mass. - + r""" Parameters ---------- centre The (y,x) arc-second coordinates of the profile centre. einstein_radius - The arc-second Einstein radius of the point-mass. + The Einstein radius :math:`\theta_E` of the point mass (arcseconds). """ super().__init__(centre=centre, ell_comps=(0.0, 0.0)) self.einstein_radius = einstein_radius diff --git a/autogalaxy/profiles/mass/point/smbh.py b/autogalaxy/profiles/mass/point/smbh.py index 9fde58255..c358e1774 100644 --- a/autogalaxy/profiles/mass/point/smbh.py +++ b/autogalaxy/profiles/mass/point/smbh.py @@ -5,6 +5,28 @@ class SMBH(PointMass): + r""" + Supermassive black hole (SMBH) modelled as a point mass lens. + + The SMBH is represented by a :class:`PointMass` profile whose Einstein radius + :math:`\theta_E` is derived from the physical mass :math:`M` and the critical + surface density :math:`\Sigma_{\rm crit}` between the SMBH and the source: + + .. math:: + + \theta_E = \sqrt{\frac{M}{\pi \, \Sigma_{\rm crit}}} + + The lensing potential and deflections then follow the point-mass expressions: + + .. math:: + + \psi(\boldsymbol{\theta}) = \theta_E^2 \ln r, \qquad + \boldsymbol{\alpha}(\boldsymbol{\theta}) = \frac{\theta_E^2}{r}\,\hat{r} + + This profile is used to model the gravitational influence of a central SMBH on + lensed images passing near the nucleus. + """ + def __init__( self, centre: Tuple[float, float] = (0.0, 0.0), @@ -12,22 +34,18 @@ def __init__( redshift_object: float = 0.5, redshift_source: float = 1.0, ): - """ - Represents a supermassive black hole (SMBH). - - This uses the `PointMass` mass profile to represent the SMBH, where the SMBH mass in converted to the - `PointMass` Einstein radius value. - + r""" Parameters ---------- centre The (y,x) arc-second coordinates of the profile centre. mass - The mass of the SMBH in solar masses. + The mass of the SMBH in solar masses :math:`M_\odot`. redshift_object - The redshift of the SMBH, which is used to convert its mass to an Einstein radius. + The redshift of the SMBH (lens plane), used to convert mass to an Einstein radius. redshift_source - The redshift of the source galaxy, which is used to convert the mass of the SMBH to an Einstein radius. + The redshift of the lensed source galaxy, used to compute + :math:`\Sigma_{\rm crit}` and hence the Einstein radius. """ from autogalaxy.cosmology.model import Planck15 diff --git a/autogalaxy/profiles/mass/point/smbh_binary.py b/autogalaxy/profiles/mass/point/smbh_binary.py index 9a0a38231..60968cf12 100644 --- a/autogalaxy/profiles/mass/point/smbh_binary.py +++ b/autogalaxy/profiles/mass/point/smbh_binary.py @@ -8,6 +8,34 @@ class SMBHBinary(MassProfile): + r""" + Binary supermassive black hole (SMBH) system modelled as two point masses. + + The binary is represented by two :class:`SMBH` mass profiles placed symmetrically + about the system ``centre``. The total mass :math:`M_{\rm tot}` and mass ratio + :math:`q_m` determine the individual masses: + + .. math:: + + M_0 = M_{\rm tot} \frac{q_m}{1 + q_m}, \qquad + M_1 = M_{\rm tot} \frac{1}{1 + q_m} + + Each component follows the point-mass lensing law: + + .. math:: + + \boldsymbol{\alpha}_i(\boldsymbol{\theta}) = + \frac{\theta_{E,i}^2}{|\boldsymbol{\theta} - \boldsymbol{\theta}_i|}\, + \hat{r}_i + + The total deflection, convergence, and potential are the sum of the two individual + :class:`SMBH` contributions. + + This profile is used to model the lensing effect of SMBH binaries expected to + form following galaxy mergers, whose gravitational influence can perturb the + positions of images near the nucleus. + """ + def __init__( self, centre: Tuple[float, float] = (0.0, 0.0), @@ -18,29 +46,24 @@ def __init__( redshift_object: float = 0.5, redshift_source: float = 1.0, ): - """ - Represents a supermassive black hole (SMBH) binary (e.g. two merging SMBH's at the centre of a galaxy). - - This uses two `SMBH` mass profiles to represent the SMBHs. - + r""" Parameters ---------- centre - The (y,x) arc-second coordinates of centre of the SMBH binary, defined as the mid-point between the - two SMBHs. + The (y,x) arc-second coordinates of the binary mid-point. separation - The arc-second separation between the two SMBHs. + The angular separation between the two SMBHs in arcseconds. angle_binary - The angle between the two SMBHs relative to the positive x-axis of the centre of the SMBH binary. + The orientation angle of the binary axis relative to the positive + x-axis (degrees, anticlockwise). mass - The sum of the masses of the two SMBHs in solar masses. + The total mass of the binary system in solar masses :math:`M_\odot`. mass_ratio - The ratio of the mass of the second SMBH to the first SMBH. A mass ratio of 2.0 gives two SMBHs where - the first SMBH has twice the mass of the second SMBH. + The mass ratio :math:`q_m = M_0 / M_1` (:math:`q_m \geq 1` by convention). redshift_object - The redshift of the SMBH, which is used to convert its mass to an Einstein radius. + The redshift of the SMBH binary (lens plane). redshift_source - The redshift of the source galaxy, which is used to convert the mass of the SMBH to an Einstein radius. + The redshift of the lensed source galaxy. """ self.separation = separation diff --git a/autogalaxy/profiles/mass/sheets/external_potential.py b/autogalaxy/profiles/mass/sheets/external_potential.py index 4b37a3ec1..87044b0c7 100644 --- a/autogalaxy/profiles/mass/sheets/external_potential.py +++ b/autogalaxy/profiles/mass/sheets/external_potential.py @@ -8,6 +8,34 @@ class ExternalPotential(MassProfile): + r""" + Higher-order external potential extending the constant external shear. + + The ``ExternalPotential`` captures up to spin-3 contributions from line-of-sight + mass in strong-lens models, following Powell et al. (2022) Eq. 4. The lensing + potential is: + + .. math:: + + \psi(\boldsymbol{r}) = + \tfrac{1}{2} r^2 (\gamma_1 \cos 2\theta + \gamma_2 \sin 2\theta) + + \tfrac{1}{4} r^3 (\tau_1 \cos\theta + \tau_2 \sin\theta) + + \tfrac{1}{6} r^3 (\delta_1 \cos 3\theta + \delta_2 \sin 3\theta) + + where :math:`(r, \theta)` are polar coordinates centred on ``centre``. + + - The :math:`\gamma` term is a constant external shear (spin-2); with + :math:`\tau_i = \delta_i = 0` this reduces to :class:`ExternalShear`. + - The :math:`\tau` term (spin-1) introduces a linear convergence gradient: + :math:`\kappa(x, y) = \tau_1 x + \tau_2 y`. + - The :math:`\delta` term (spin-3) is a higher-order generalised shear with zero + convergence contribution. + + References + ---------- + - Powell, Vegetti, McKean et al. 2022, MNRAS, 516, 1808 + """ + def __init__( self, centre: Tuple[float, float] = (0.0, 0.0), diff --git a/autogalaxy/profiles/mass/sheets/external_shear.py b/autogalaxy/profiles/mass/sheets/external_shear.py index 20e3f991d..a973ab492 100644 --- a/autogalaxy/profiles/mass/sheets/external_shear.py +++ b/autogalaxy/profiles/mass/sheets/external_shear.py @@ -8,6 +8,46 @@ class ExternalShear(MassProfile): + r""" + Constant external shear field used in strong-lens mass models. + + The external shear represents the line-of-sight contribution to the lensing + potential from mass external to the primary lens (e.g. nearby group members, + large-scale structure). Because this contribution is approximately uniform over + the small angular extent of a strong-lens system, it is modelled as a constant + shear with two Cartesian components :math:`(\gamma_1, \gamma_2)`. + + The lensing potential is: + + .. math:: + + \psi(\boldsymbol{\theta}) = + -\tfrac{1}{2} |\gamma| \, r^2 \cos\!\big(2(\varphi - \phi_\gamma)\big) + + The deflection field in a reference frame aligned with the shear direction is: + + .. math:: + + \alpha_y = -|\gamma| \, y, \qquad \alpha_x = +|\gamma| \, x + + The convergence vanishes identically: + + .. math:: + + \kappa = 0 + + The shear magnitude and position angle are: + + .. math:: + + |\gamma| = \sqrt{\gamma_1^2 + \gamma_2^2}, \qquad + \phi_\gamma = \tfrac{1}{2} \mathrm{arctan2}(\gamma_2, \gamma_1) + + References + ---------- + - Schneider, Ehlers & Falco 1992, *Gravitational Lenses* (Springer) + """ + def __init__(self, gamma_1: float = 0.0, gamma_2: float = 0.0): r""" Constant external shear term used in strong-lens mass models. diff --git a/autogalaxy/profiles/mass/sheets/mass_sheet.py b/autogalaxy/profiles/mass/sheets/mass_sheet.py index a20207af5..b6b710684 100644 --- a/autogalaxy/profiles/mass/sheets/mass_sheet.py +++ b/autogalaxy/profiles/mass/sheets/mass_sheet.py @@ -7,16 +7,42 @@ class MassSheet(MassProfile): - def __init__(self, centre: Tuple[float, float] = (0.0, 0.0), kappa: float = 0.0): - """ - Represents a mass-sheet + r""" + Uniform convergence mass sheet. + + A mass sheet produces a spatially constant convergence :math:`\kappa_{\rm ext}` + across the lens plane. It is the simplest realisation of the mass-sheet degeneracy + (Gorenstein 1988): any lensing configuration can be transformed by adding a sheet + without changing observed image positions or flux ratios. + + The convergence, lensing potential, and deflection field are: + + .. math:: + + \kappa(\boldsymbol{\theta}) = \kappa_{\rm ext} + .. math:: + + \psi(\boldsymbol{\theta}) = \tfrac{1}{2} \kappa_{\rm ext} \, |\boldsymbol{\theta}|^2 + + .. math:: + + \boldsymbol{\alpha}(\boldsymbol{\theta}) = \kappa_{\rm ext} \, \boldsymbol{\theta} + + References + ---------- + - Gorenstein, Falco & Shapiro 1988, ApJ, 327, 693 + - Schneider & Sluse 2013, A&A, 559, A37 + """ + + def __init__(self, centre: Tuple[float, float] = (0.0, 0.0), kappa: float = 0.0): + r""" Parameters ---------- centre The (y,x) arc-second coordinates of the profile centre. kappa - The magnitude of the convergence of the mass-sheet. + The uniform convergence :math:`\kappa_{\rm ext}` of the mass sheet. """ super().__init__(centre=centre, ell_comps=(0.0, 0.0)) self.kappa = kappa diff --git a/autogalaxy/profiles/mass/stellar/chameleon.py b/autogalaxy/profiles/mass/stellar/chameleon.py index 59327f331..230695704 100644 --- a/autogalaxy/profiles/mass/stellar/chameleon.py +++ b/autogalaxy/profiles/mass/stellar/chameleon.py @@ -10,6 +10,33 @@ class Chameleon(MassProfile, StellarProfile): + r""" + Elliptical Chameleon stellar mass profile (Dutton et al. 2011). + + The Chameleon profile is the difference of two cored isothermal (pseudo-Jaffe) profiles + with core radii :math:`s_0` and :math:`s_0 + s_1`, providing a flexible approximation + to a variety of stellar light profiles: + + .. math:: + + \kappa(\xi) = \Upsilon \, I \left( + \frac{1}{\sqrt{\xi^2 + s_0^2}} + - \frac{1}{\sqrt{\xi^2 + (s_0 + s_1)^2}} + \right) + + where :math:`\xi^2 = x^2 + (y/q)^2` is the elliptical radius, :math:`\Upsilon` is + the mass-to-light ratio (``mass_to_light_ratio``), :math:`I` is the intensity + normalisation (``intensity``), :math:`s_0` = ``core_radius_0``, and + :math:`s_1` = ``core_radius_1``. + + Deflection angles are computed analytically via the cored isothermal deflection + formula (Eq. 15–16 of Dutton et al. 2011). + + References + ---------- + - Dutton, Brewer, Marshall et al. 2011, MNRAS, 417, 1621 + """ + def __init__( self, centre: Tuple[float, float] = (0.0, 0.0), @@ -19,9 +46,7 @@ def __init__( core_radius_1: float = 0.02, mass_to_light_ratio: float = 1.0, ): - """ - The elliptical Chamelon mass profile. - + r""" Parameters ---------- centre @@ -29,15 +54,15 @@ def __init__( ell_comps The first and second ellipticity components of the elliptical coordinate system. intensity - Overall intensity normalisation of the light profile (units are dimensionless and derived from the data - the light profile's image is compared too, which is expected to be electrons per second). - core_radius_0 : the core size of the first elliptical cored Isothermal profile. - core_radius_1 : core_radius_0 + core_radius_1 is the core size of the second elliptical cored Isothermal profile. - We use core_radius_1 here is to avoid negative values. - - Profile form: - mass_to_light_ratio * intensity *\ - (1.0 / Sqrt(x^2 + (y/q)^2 + core_radius_0^2) - 1.0 / Sqrt(x^2 + (y/q)^2 + (core_radius_0 + core_radius_1)**2.0)) + Overall intensity normalisation :math:`I` (electrons per second). + core_radius_0 + Core radius :math:`s_0` of the first cored isothermal component (arcseconds). + core_radius_1 + Additional core size increment :math:`s_1` such that the second component has + core radius :math:`s_0 + s_1` (arcseconds). Using an increment avoids + negative parameter values. + mass_to_light_ratio + The mass-to-light ratio :math:`\Upsilon` in solar units. """ super(Chameleon, self).__init__(centre=centre, ell_comps=ell_comps) @@ -205,6 +230,24 @@ def axis_ratio(self, xp=np): class ChameleonSph(Chameleon): + r""" + Spherical Chameleon stellar mass profile. + + A special case of :class:`Chameleon` with no ellipticity (:math:`q = 1`). + The convergence is: + + .. math:: + + \kappa(r) = \Upsilon \, I \left( + \frac{1}{\sqrt{r^2 + s_0^2}} + - \frac{1}{\sqrt{r^2 + (s_0 + s_1)^2}} + \right) + + References + ---------- + - Dutton, Brewer, Marshall et al. 2011, MNRAS, 417, 1621 + """ + def __init__( self, centre: Tuple[float, float] = (0.0, 0.0), @@ -213,26 +256,21 @@ def __init__( core_radius_1: float = 0.02, mass_to_light_ratio: float = 1.0, ): - """ - The spherica; Chameleon mass profile. - - Profile form: - mass_to_light_ratio * intensity *\ - (1.0 / Sqrt(x^2 + (y/q)^2 + core_radius_0^2) - 1.0 / Sqrt(x^2 + (y/q)^2 + (core_radius_0 + core_radius_1)**2.0)) - + r""" Parameters ---------- centre The (y,x) arc-second coordinates of the profile centre. - ell_comps - The first and second ellipticity components of the elliptical coordinate system. intensity - Overall intensity normalisation of the light profile (units are dimensionless and derived from the data - the light profile's image is compared too, which is expected to be electrons per second). - core_radius_0 : the core size of the first elliptical cored Isothermal profile. - core_radius_1 : core_radius_0 + core_radius_1 is the core size of the second elliptical cored Isothermal profile. - We use core_radius_1 here is to avoid negative values. - """ + Overall intensity normalisation :math:`I` (electrons per second). + core_radius_0 + Core radius :math:`s_0` of the first cored isothermal component (arcseconds). + core_radius_1 + Additional core size increment :math:`s_1` such that the second component has + core radius :math:`s_0 + s_1` (arcseconds). + mass_to_light_ratio + The mass-to-light ratio :math:`\Upsilon` in solar units. + """ super().__init__( centre=centre, diff --git a/autogalaxy/profiles/mass/stellar/dev_vaucouleurs.py b/autogalaxy/profiles/mass/stellar/dev_vaucouleurs.py index c0433161f..e96e4d061 100644 --- a/autogalaxy/profiles/mass/stellar/dev_vaucouleurs.py +++ b/autogalaxy/profiles/mass/stellar/dev_vaucouleurs.py @@ -4,6 +4,26 @@ class DevVaucouleurs(Sersic): + r""" + Elliptical de Vaucouleurs stellar mass profile (de Vaucouleurs 1948). + + A special case of the :class:`Sersic` mass profile with Sérsic index :math:`n = 4`, + corresponding to the classical de Vaucouleurs :math:`R^{1/4}` surface brightness law + that describes the light distribution of massive elliptical galaxies: + + .. math:: + + \kappa(R) = \Upsilon \, I_e \exp\!\left\{ + -b_4 \left[\left(\frac{R}{R_e}\right)^{1/4} - 1\right] + \right\} + + where :math:`b_4 \approx 7.669` ensures :math:`R_e` is the half-light radius. + + References + ---------- + - de Vaucouleurs 1948, Ann. Astrophys., 11, 247 + """ + def __init__( self, centre: Tuple[float, float] = (0.0, 0.0), @@ -12,10 +32,7 @@ def __init__( effective_radius: float = 0.6, mass_to_light_ratio: float = 1.0, ): - """ - The DevVaucouleurs mass profile, the mass profiles of the light profiles that are used to fit and - subtract the lens model_galaxy's light. - + r""" Parameters ---------- centre @@ -23,11 +40,12 @@ def __init__( ell_comps The first and second ellipticity components of the elliptical coordinate system. intensity - Overall flux intensity normalisation in the light profiles (electrons per second). + Overall intensity normalisation :math:`I_e` at the effective radius + (electrons per second). effective_radius - The radius containing half the light of this profile. + The effective (half-light) radius :math:`R_e` in arcseconds. mass_to_light_ratio - The mass-to-light ratio of the light profile. + The mass-to-light ratio :math:`\Upsilon` in solar units. """ super().__init__( centre=centre, @@ -40,6 +58,23 @@ def __init__( class DevVaucouleursSph(DevVaucouleurs): + r""" + Spherical de Vaucouleurs stellar mass profile. + + A special case of :class:`DevVaucouleurs` with no ellipticity (:math:`q = 1`), + i.e., a :class:`Sersic` profile with :math:`n = 4` evaluated on a circular grid: + + .. math:: + + \kappa(r) = \Upsilon \, I_e \exp\!\left\{ + -b_4 \left[\left(\frac{r}{R_e}\right)^{1/4} - 1\right] + \right\} + + References + ---------- + - de Vaucouleurs 1948, Ann. Astrophys., 11, 247 + """ + def __init__( self, centre: Tuple[float, float] = (0.0, 0.0), @@ -47,20 +82,18 @@ def __init__( effective_radius: float = 0.6, mass_to_light_ratio: float = 1.0, ): - """ - The DevVaucouleurs mass profile, the mass profiles of the light profiles that are used to fit and subtract the - lens model_galaxy's light. - + r""" Parameters ---------- centre The (y,x) arc-second coordinates of the profile centre. intensity - Overall flux intensity normalisation in the light profiles (electrons per second). + Overall intensity normalisation :math:`I_e` at the effective radius + (electrons per second). effective_radius - The circular radius containing half the light of this profile. + The effective (half-light) radius :math:`R_e` in arcseconds. mass_to_light_ratio - The mass-to-light ratio of the light profiles. + The mass-to-light ratio :math:`\Upsilon` in solar units. """ super().__init__( centre=centre, diff --git a/autogalaxy/profiles/mass/stellar/exponential.py b/autogalaxy/profiles/mass/stellar/exponential.py index 32d13387b..2bb46f792 100644 --- a/autogalaxy/profiles/mass/stellar/exponential.py +++ b/autogalaxy/profiles/mass/stellar/exponential.py @@ -4,6 +4,20 @@ class Exponential(Sersic): + r""" + Elliptical exponential stellar mass profile. + + A special case of the :class:`Sersic` mass profile with Sérsic index :math:`n = 1`, + corresponding to an exponential disc surface brightness profile: + + .. math:: + + \kappa(R) = \Upsilon \, I_e \exp\!\left(-b_1 \left[\frac{R}{R_e} - 1\right]\right) + + where :math:`b_1 \approx 1.678` ensures :math:`R_e` is the half-light radius. + This profile is commonly used to model disc-dominated galaxies. + """ + def __init__( self, centre: Tuple[float, float] = (0.0, 0.0), @@ -12,10 +26,7 @@ def __init__( effective_radius: float = 0.6, mass_to_light_ratio: float = 1.0, ): - """ - The Exponential mass profile, the mass profiles of the light profiles that are used to fit and - subtract the lens model_galaxy's light. - + r""" Parameters ---------- centre @@ -23,11 +34,12 @@ def __init__( ell_comps The first and second ellipticity components of the elliptical coordinate system. intensity - Overall flux intensity normalisation in the light profiles (electrons per second). + Overall intensity normalisation :math:`I_e` at the effective radius + (electrons per second). effective_radius - The circular radius containing half the light of this profile. + The effective (half-light) radius :math:`R_e` in arcseconds. mass_to_light_ratio - The mass-to-light ratio of the light profiles + The mass-to-light ratio :math:`\Upsilon` in solar units. """ super().__init__( centre=centre, @@ -40,6 +52,17 @@ def __init__( class ExponentialSph(Exponential): + r""" + Spherical exponential stellar mass profile. + + A special case of :class:`Exponential` with no ellipticity (:math:`q = 1`), + i.e., a :class:`Sersic` profile with :math:`n = 1` evaluated on a circular grid: + + .. math:: + + \kappa(r) = \Upsilon \, I_e \exp\!\left(-b_1 \left[\frac{r}{R_e} - 1\right]\right) + """ + def __init__( self, centre: Tuple[float, float] = (0.0, 0.0), @@ -47,20 +70,18 @@ def __init__( effective_radius: float = 0.6, mass_to_light_ratio: float = 1.0, ): - """ - The Exponential mass profile, the mass profiles of the light profiles that are used to fit and subtract the lens - model_galaxy's light. - + r""" Parameters ---------- centre The (y,x) arc-second coordinates of the profile centre. intensity - Overall flux intensity normalisation in the light profiles (electrons per second). + Overall intensity normalisation :math:`I_e` at the effective radius + (electrons per second). effective_radius - The circular radius containing half the light of this profile. + The effective (half-light) radius :math:`R_e` in arcseconds. mass_to_light_ratio - The mass-to-light ratio of the light profiles. + The mass-to-light ratio :math:`\Upsilon` in solar units. """ super().__init__( centre=centre, diff --git a/autogalaxy/profiles/mass/stellar/gaussian.py b/autogalaxy/profiles/mass/stellar/gaussian.py index 4cc74d234..4d55211f3 100644 --- a/autogalaxy/profiles/mass/stellar/gaussian.py +++ b/autogalaxy/profiles/mass/stellar/gaussian.py @@ -9,6 +9,30 @@ class Gaussian(MassProfile, StellarProfile): + r""" + Elliptical Gaussian stellar mass profile. + + The convergence of the Gaussian mass profile is proportional to the Gaussian surface + brightness scaled by a mass-to-light ratio: + + .. math:: + + \kappa(R) = \Upsilon \, \frac{I}{2\pi\sigma^2} + \exp\!\left(-\frac{R^2}{2\sigma^2}\right) + + where :math:`\Upsilon` is the mass-to-light ratio (``mass_to_light_ratio``), + :math:`I` is the overall intensity normalisation (``intensity``), :math:`\sigma` is + the Gaussian width (``sigma``), and :math:`R` is the elliptical radius + :math:`R^2 = x^2 + y^2/q^2` with axis ratio :math:`q`. + + Deflection angles are computed analytically via the Faddeeva (scaled complementary + error) function :math:`w(z)` following Shajib (2019). + + References + ---------- + - Shajib 2019, MNRAS, 488, 1387 (arXiv:1906.08263) + """ + def __init__( self, centre: Tuple[float, float] = (0.0, 0.0), @@ -17,9 +41,7 @@ def __init__( sigma: float = 1.0, mass_to_light_ratio: float = 1.0, ): - """ - The elliptical Gaussian light profile. - + r""" Parameters ---------- centre @@ -27,10 +49,11 @@ def __init__( ell_comps The first and second ellipticity components of the elliptical coordinate system. intensity - Overall intensity normalisation of the light profile (units are dimensionless and derived from the data - the light profile's image is compared too, which is expected to be electrons per second). + Overall intensity normalisation :math:`I` of the Gaussian (electrons per second). sigma - The sigma value of the Gaussian. + The Gaussian width :math:`\sigma` in arcseconds. + mass_to_light_ratio + The mass-to-light ratio :math:`\Upsilon` in solar units. """ super(MassProfile, self).__init__(centre=centre, ell_comps=ell_comps) diff --git a/autogalaxy/profiles/mass/stellar/sersic.py b/autogalaxy/profiles/mass/stellar/sersic.py index 8df7c67b5..8bd344243 100644 --- a/autogalaxy/profiles/mass/stellar/sersic.py +++ b/autogalaxy/profiles/mass/stellar/sersic.py @@ -83,6 +83,34 @@ def cse_settings_from( class AbstractSersic(MassProfile, MassProfileCSE, StellarProfile): + r""" + Abstract base class for Sérsic stellar mass profiles. + + The convergence of a Sérsic mass profile is proportional to the Sérsic surface + brightness profile scaled by a constant mass-to-light ratio: + + .. math:: + + \kappa(R) = \Upsilon \, I_e \exp\!\left\{ + -b_n \left[\left(\frac{R}{R_e}\right)^{1/n} - 1\right] + \right\} + + where :math:`\Upsilon` is the mass-to-light ratio (``mass_to_light_ratio``), + :math:`I_e` is the intensity at the effective radius (``intensity``), + :math:`R_e` is the effective (half-light) radius (``effective_radius``), :math:`n` is + the Sérsic index (``sersic_index``), and :math:`b_n` is a constant that ensures the + effective radius encloses half the total luminosity (approximated by a polynomial in + :math:`n`). + + Deflection angles are computed via a cored-steep-ellipsoid (CSE) decomposition + following Oguri (2021). + + References + ---------- + - Sérsic 1963, Boletin de la Asociacion Argentina de Astronomia, 6, 41 + - Oguri 2021, PASP, 133, 074504 (arXiv:2106.11464) + """ + def __init__( self, centre: Tuple[float, float] = (0.0, 0.0), @@ -92,10 +120,7 @@ def __init__( sersic_index: float = 0.6, mass_to_light_ratio: float = 1.0, ): - """ - The Sersic mass profile, the mass profiles of the light profiles that are used to fit and subtract the lens \ - model_galaxy's light. - + r""" Parameters ---------- centre @@ -103,13 +128,15 @@ def __init__( ell_comps The first and second ellipticity components of the elliptical coordinate system. intensity - Overall flux intensity normalisation in the light profiles (electrons per second). + Overall intensity normalisation :math:`I_e` at the effective radius + (electrons per second). effective_radius - The radius containing half the light of this profile. + The effective (half-light) radius :math:`R_e` in arcseconds. sersic_index - Controls the concentration of the profile (lower -> less concentrated, higher -> more concentrated). + The Sérsic index :math:`n` controlling profile concentration + (lower -> less concentrated, higher -> more concentrated). mass_to_light_ratio - The mass-to-light ratio of the light profiles + The mass-to-light ratio :math:`\Upsilon` in solar units. """ super(AbstractSersic, self).__init__(centre=centre, ell_comps=ell_comps) super(MassProfile, self).__init__(centre=centre, ell_comps=ell_comps) @@ -307,10 +334,49 @@ def elliptical_effective_radius(self): class Sersic(AbstractSersic, MassProfileCSE): + r""" + Elliptical Sérsic stellar mass profile. + + Inherits the full Sérsic convergence and CSE deflection-angle machinery from + :class:`AbstractSersic`. The convergence is: + + .. math:: + + \kappa(R) = \Upsilon \, I_e \exp\!\left\{ + -b_n \left[\left(\frac{R}{R_e}\right)^{1/n} - 1\right] + \right\} + + where :math:`R` is the elliptical radius, :math:`\Upsilon` is the mass-to-light + ratio, :math:`I_e` is the intensity at the effective radius :math:`R_e`, and + :math:`b_n` is determined from the Sérsic index :math:`n`. + + References + ---------- + - Sérsic 1963, Boletin de la Asociacion Argentina de Astronomia, 6, 41 + - Oguri 2021, PASP, 133, 074504 (arXiv:2106.11464) + """ + pass class SersicSph(Sersic): + r""" + Spherical Sérsic stellar mass profile. + + A special case of :class:`Sersic` with no ellipticity (:math:`q = 1`). The + convergence is evaluated on a circular radial grid: + + .. math:: + + \kappa(r) = \Upsilon \, I_e \exp\!\left\{ + -b_n \left[\left(\frac{r}{R_e}\right)^{1/n} - 1\right] + \right\} + + References + ---------- + - Sérsic 1963, Boletin de la Asociacion Argentina de Astronomia, 6, 41 + """ + def __init__( self, centre: Tuple[float, float] = (0.0, 0.0), @@ -319,22 +385,21 @@ def __init__( sersic_index: float = 0.6, mass_to_light_ratio: float = 1.0, ): - """ - The Sersic mass profile, the mass profiles of the light profiles that are used to fit and subtract the lens - model_galaxy's light. - + r""" Parameters ---------- centre - The (y,x) arc-second coordinates of the profile centre + The (y,x) arc-second coordinates of the profile centre. intensity - Overall flux intensity normalisation in the light profiles (electrons per second) + Overall intensity normalisation :math:`I_e` at the effective radius + (electrons per second). effective_radius - The circular radius containing half the light of this profile. + The effective (half-light) radius :math:`R_e` in arcseconds. sersic_index - Controls the concentration of the profile (lower -> less concentrated, higher -> more concentrated). + The Sérsic index :math:`n` controlling profile concentration + (lower -> less concentrated, higher -> more concentrated). mass_to_light_ratio - The mass-to-light ratio of the light profile. + The mass-to-light ratio :math:`\Upsilon` in solar units. """ super().__init__( centre=centre, diff --git a/autogalaxy/profiles/mass/total/dual_pseudo_isothermal_mass.py b/autogalaxy/profiles/mass/total/dual_pseudo_isothermal_mass.py index 0ef210e23..39b8fd814 100644 --- a/autogalaxy/profiles/mass/total/dual_pseudo_isothermal_mass.py +++ b/autogalaxy/profiles/mass/total/dual_pseudo_isothermal_mass.py @@ -357,6 +357,54 @@ def analytical_magnification_2d_from( class dPIEMass(MassProfile): + r"""Dual pseudo-isothermal elliptical mass distribution (dPIE, mass parameterisation). + + A two-component PIE profile with both a core radius :math:`r_a` and a + truncation radius :math:`r_s`. The three-dimensional density scales as + :math:`\rho \propto r^{-2}` in the transition region + :math:`r_a \leq R \leq r_s` and as :math:`\rho \propto r^{-4}` in the outer + parts, with the full form: + + .. math:: + + \rho \propto \bigl[(r_a^2 + R^2)(r_s^2 + R^2)\bigr]^{-1} + + The projected convergence is the difference of two PIE profiles: + + .. math:: + + \kappa(r_{\rm em}) = \frac{b_0}{2} \frac{r_s}{r_s - r_a} + \left( + \frac{1}{\sqrt{r_a^2 + r_{\rm em}^2}} + - \frac{1}{\sqrt{r_s^2 + r_{\rm em}^2}} + \right) + + where :math:`r_{\rm em}^2 = x^2/(1+\epsilon)^2 + y^2/(1-\epsilon)^2` is + the pseudo-elliptical radius and :math:`b_0` is the lens strength (equal to + the Einstein radius when :math:`r_a \to 0`, :math:`r_s \to \infty`, and + :math:`q \to 1`). This profile is ported directly from Lenstool's C code. + + Parameters + ---------- + centre : (float, float) + (y, x) arc-second coordinates of the profile centre. + ell_comps : (float, float) + Ellipticity components (e1, e2) of the elliptical coordinate system. + ra : float + Inner core radius in arcseconds. + rs : float + Outer truncation radius in arcseconds. + b0 : float + Lens strength in arcseconds (Einstein radius in the limit + :math:`r_a \to 0`, :math:`r_s \to \infty`, :math:`q \to 1`). + + References + ---------- + Kassiola & Kovner (1993), ApJ, 417, 450. + Eliasdottir et al. (2007), arXiv:0710.5636. + Limousin et al. (2005), A&A, 461, 881. + """ + def __init__( self, centre: Tuple[float, float] = (0.0, 0.0), @@ -546,6 +594,42 @@ def potential_2d_from(self, grid: aa.type.Grid2DLike, xp=np, **kwargs): class dPIEMassSph(dPIEMass): + r"""Spherical dual pseudo-isothermal mass distribution (dPIE, mass parameterisation). + + The spherical limit of :class:`dPIEMass`. The projected convergence is: + + .. math:: + + \kappa(r) = \frac{b_0}{2} \frac{r_s}{r_s - r_a} + \left( + \frac{1}{\sqrt{r_a^2 + r^2}} + - \frac{1}{\sqrt{r_s^2 + r^2}} + \right) + + where :math:`r` is the circular projected radius, :math:`r_a` is the core + radius, :math:`r_s` is the truncation radius, and :math:`b_0` is the lens + strength (Einstein radius in the limits :math:`r_a \to 0`, + :math:`r_s \to \infty`). + + Parameters + ---------- + centre : (float, float) + (y, x) arc-second coordinates of the profile centre. + ra : float + Inner core radius in arcseconds. + rs : float + Outer truncation radius in arcseconds. + b0 : float + Lens strength in arcseconds (Einstein radius in the limit + :math:`r_a \to 0`, :math:`r_s \to \infty`). + + References + ---------- + Kassiola & Kovner (1993), ApJ, 417, 450. + Eliasdottir et al. (2007), arXiv:0710.5636. + Limousin et al. (2005), A&A, 461, 881. + """ + def __init__( self, centre: Tuple[float, float] = (0.0, 0.0), diff --git a/autogalaxy/profiles/mass/total/dual_pseudo_isothermal_potential.py b/autogalaxy/profiles/mass/total/dual_pseudo_isothermal_potential.py index d30259f36..c555675b4 100644 --- a/autogalaxy/profiles/mass/total/dual_pseudo_isothermal_potential.py +++ b/autogalaxy/profiles/mass/total/dual_pseudo_isothermal_potential.py @@ -6,6 +6,42 @@ class dPIEPotential(MassProfile): + r"""Dual pseudo-isothermal elliptical mass profile (dPIE, potential parameterisation). + + A dPIE profile in which ellipticity is applied to the lensing potential rather + than the mass distribution. The circularly symmetric convergence takes the + same functional form as :class:`dPIEMassSph`: + + .. math:: + + \kappa(R) = \frac{b_0}{2} \frac{r_s}{r_s - r_a} + \left( + \frac{1}{\sqrt{r_a^2 + R^2}} + - \frac{1}{\sqrt{r_s^2 + R^2}} + \right) + + but ellipticity enters through a pseudo-elliptical radius + :math:`R^2 = x^2(1-\epsilon) + y^2(1+\epsilon)` in the deflection, rather + than through a purely elliptical mass distribution. + + Parameters + ---------- + centre : (float, float) + (y, x) arc-second coordinates of the profile centre. + ell_comps : (float, float) + Ellipticity components (e1, e2) of the elliptical coordinate system. + ra : float + Inner core scale radius in arcseconds. + rs : float + Outer truncation scale radius in arcseconds. + b0 : float + Lens strength in arcseconds (Einstein radius in the limit + :math:`r_a \to 0`, :math:`r_s \to \infty`, :math:`q \to 1`). + + References + ---------- + Eliasdottir et al. (2007), arXiv:0710.5636. + """ def __init__( self, @@ -168,6 +204,38 @@ def potential_2d_from(self, grid: aa.type.Grid2DLike, xp=np, **kwargs): class dPIEPotentialSph(dPIEPotential): + r"""Spherical dual pseudo-isothermal mass profile (dPIE, potential parameterisation). + + The spherical limit of :class:`dPIEPotential`. The convergence is: + + .. math:: + + \kappa(r) = \frac{b_0}{2} \frac{r_s}{r_s - r_a} + \left( + \frac{1}{\sqrt{r_a^2 + r^2}} + - \frac{1}{\sqrt{r_s^2 + r^2}} + \right) + + where :math:`r` is the circular projected radius. This profile is + mathematically identical to :class:`dPIEMassSph`. + + Parameters + ---------- + centre : (float, float) + (y, x) arc-second coordinates of the profile centre. + ra : float + Inner core scale radius in arcseconds. + rs : float + Outer truncation scale radius in arcseconds. + b0 : float + Lens strength in arcseconds (Einstein radius in the limit + :math:`r_a \to 0`, :math:`r_s \to \infty`). + + References + ---------- + Eliasdottir et al. (2007), arXiv:0710.5636. + """ + def __init__( self, centre: Tuple[float, float] = (0.0, 0.0), diff --git a/autogalaxy/profiles/mass/total/isothermal.py b/autogalaxy/profiles/mass/total/isothermal.py index b69d8c002..2e50ba2de 100644 --- a/autogalaxy/profiles/mass/total/isothermal.py +++ b/autogalaxy/profiles/mass/total/isothermal.py @@ -39,6 +39,34 @@ def psi_from(grid, axis_ratio, core_radius, xp=np): class Isothermal(PowerLaw): + r"""Singular isothermal ellipsoid (SIE) mass profile. + + The convergence of the SIE is: + + .. math:: + + \kappa(R) = \frac{\theta_{\rm E}}{2R} + + where :math:`\theta_{\rm E}` is the Einstein radius and :math:`R` is the + elliptical radius defined as :math:`R^2 = q x^2 + y^2 / q` with axis ratio + :math:`q`. This is the special case of the power-law profile with slope + :math:`\gamma = 2`. + + Parameters + ---------- + centre : (float, float) + (y, x) arc-second coordinates of the profile centre. + ell_comps : (float, float) + Ellipticity components (e1, e2) of the elliptical coordinate system. + einstein_radius : float + Einstein radius in arcseconds. + + References + ---------- + Kormann, Schneider & Bartelmann (1994), A&A, 284, 285. + Tessore & Metcalf (2015), A&A, 580, A79. + """ + def __init__( self, centre: Tuple[float, float] = (0.0, 0.0), @@ -177,6 +205,30 @@ def convergence_func(self, grid_radius: float, xp=np) -> float: class IsothermalSph(Isothermal): + r"""Singular isothermal sphere (SIS) mass profile. + + The spherical limit of the SIE. The convergence is: + + .. math:: + + \kappa(r) = \frac{\theta_{\rm E}}{2r} + + where :math:`\theta_{\rm E}` is the Einstein radius and :math:`r` is the + circular projected radius. + + Parameters + ---------- + centre : (float, float) + (y, x) arc-second coordinates of the profile centre. + einstein_radius : float + Einstein radius in arcseconds. + + References + ---------- + Kormann, Schneider & Bartelmann (1994), A&A, 284, 285. + Tessore & Metcalf (2015), A&A, 580, A79. + """ + def __init__( self, centre: Tuple[float, float] = (0.0, 0.0), einstein_radius: float = 1.0 ): diff --git a/autogalaxy/profiles/mass/total/isothermal_core.py b/autogalaxy/profiles/mass/total/isothermal_core.py index 0735511bf..bdf71350e 100644 --- a/autogalaxy/profiles/mass/total/isothermal_core.py +++ b/autogalaxy/profiles/mass/total/isothermal_core.py @@ -5,6 +5,35 @@ class IsothermalCore(PowerLawCore): + r"""Cored elliptical isothermal (SIE with core) mass profile. + + The convergence of the cored isothermal ellipsoid is: + + .. math:: + + \kappa(R) = \frac{\theta_{\rm E}}{2\sqrt{R^2 + s^2}} + + where :math:`\theta_{\rm E}` is the Einstein radius, :math:`R` is the + elliptical radius, and :math:`s` is the core radius. In the limit + :math:`s \to 0` this reduces to the standard SIE. + + Parameters + ---------- + centre : (float, float) + (y, x) arc-second coordinates of the profile centre. + ell_comps : (float, float) + Ellipticity components (e1, e2) of the elliptical coordinate system. + einstein_radius : float + Einstein radius in arcseconds. + core_radius : float + Core radius :math:`s` in arcseconds. + + References + ---------- + Kormann, Schneider & Bartelmann (1994), A&A, 284, 285. + Keeton (2001), arXiv:astro-ph/0102341. + """ + def __init__( self, centre: Tuple[float, float] = (0.0, 0.0), @@ -37,6 +66,33 @@ def __init__( class IsothermalCoreSph(PowerLawCoreSph): + r"""Cored spherical isothermal (SIS with core) mass profile. + + The convergence of the cored spherical isothermal is: + + .. math:: + + \kappa(r) = \frac{\theta_{\rm E}}{2\sqrt{r^2 + s^2}} + + where :math:`\theta_{\rm E}` is the Einstein radius, :math:`r` is the + circular projected radius, and :math:`s` is the core radius. This is + the spherical special case of :class:`IsothermalCore`. + + Parameters + ---------- + centre : (float, float) + (y, x) arc-second coordinates of the profile centre. + einstein_radius : float + Einstein radius in arcseconds. + core_radius : float + Core radius :math:`s` in arcseconds. + + References + ---------- + Kormann, Schneider & Bartelmann (1994), A&A, 284, 285. + Keeton (2001), arXiv:astro-ph/0102341. + """ + def __init__( self, centre: Tuple[float, float] = (0.0, 0.0), diff --git a/autogalaxy/profiles/mass/total/power_law.py b/autogalaxy/profiles/mass/total/power_law.py index 55af3f395..67be63edd 100644 --- a/autogalaxy/profiles/mass/total/power_law.py +++ b/autogalaxy/profiles/mass/total/power_law.py @@ -7,6 +7,37 @@ class PowerLaw(PowerLawCore): + r"""Elliptical power-law (EPL / PEMD) mass profile. + + The convergence of the elliptical power-law is: + + .. math:: + + \kappa(R) = \frac{3 - \gamma}{2} + \left(\frac{\theta_{\rm E}}{R}\right)^{\gamma - 1} + + where :math:`\gamma` is the logarithmic density slope, :math:`\theta_{\rm E}` + is the Einstein radius, and :math:`R` is the elliptical radius. The + isothermal case corresponds to :math:`\gamma = 2`. + + Parameters + ---------- + centre : (float, float) + (y, x) arc-second coordinates of the profile centre. + ell_comps : (float, float) + Ellipticity components (e1, e2) of the elliptical coordinate system. + einstein_radius : float + Einstein radius in arcseconds. + slope : float + Logarithmic density slope :math:`\gamma`; shallower profiles have + lower values, steeper profiles have higher values. + + References + ---------- + Tessore & Metcalf (2015), A&A, 580, A79. + Schneider, Ehlers & Falco (1992), *Gravitational Lenses*, Springer. + """ + def __init__( self, centre: Tuple[float, float] = (0.0, 0.0), @@ -138,6 +169,33 @@ def potential_func(u, y, x, axis_ratio, slope, core_radius, xp=np): class PowerLawSph(PowerLaw): + r"""Spherical power-law mass profile. + + The spherical limit of :class:`PowerLaw`. The convergence is: + + .. math:: + + \kappa(r) = \frac{3 - \gamma}{2} + \left(\frac{\theta_{\rm E}}{r}\right)^{\gamma - 1} + + where :math:`\gamma` is the logarithmic density slope, :math:`\theta_{\rm E}` + is the Einstein radius, and :math:`r` is the circular projected radius. + + Parameters + ---------- + centre : (float, float) + (y, x) arc-second coordinates of the profile centre. + einstein_radius : float + Einstein radius in arcseconds. + slope : float + Logarithmic density slope :math:`\gamma`; shallower profiles have + lower values, steeper profiles have higher values. + + References + ---------- + Tessore & Metcalf (2015), A&A, 580, A79. + """ + def __init__( self, centre: Tuple[float, float] = (0.0, 0.0), diff --git a/autogalaxy/profiles/mass/total/power_law_broken.py b/autogalaxy/profiles/mass/total/power_law_broken.py index 92c277ec8..f686f99a9 100644 --- a/autogalaxy/profiles/mass/total/power_law_broken.py +++ b/autogalaxy/profiles/mass/total/power_law_broken.py @@ -7,6 +7,48 @@ class PowerLawBroken(MassProfile): + r"""Broken elliptical power-law mass profile with inner and outer slopes. + + The convergence has a power-law form with different slopes inside and + outside a break radius :math:`\theta_{\rm b}`, matched to be continuous + at the break: + + .. math:: + + \kappa(R) = \begin{cases} + \kappa_{\rm b} + \left(\dfrac{\theta_{\rm b}}{R}\right)^{\gamma_{\rm in}} + & R \leq \theta_{\rm b} \\[6pt] + \kappa_{\rm b} + \left(\dfrac{\theta_{\rm b}}{R}\right)^{\gamma_{\rm out}} + & R > \theta_{\rm b} + \end{cases} + + where :math:`\gamma_{\rm in}` and :math:`\gamma_{\rm out}` are the inner and + outer logarithmic slopes, :math:`\theta_{\rm b}` is the break radius, and + :math:`\kappa_{\rm b}` is the convergence at the break radius (set by the + normalisation condition on the Einstein radius). + + Parameters + ---------- + centre : (float, float) + (y, x) arc-second coordinates of the profile centre. + ell_comps : (float, float) + Ellipticity components (e1, e2) of the elliptical coordinate system. + einstein_radius : float + Einstein radius in arcseconds. + inner_slope : float + Logarithmic density slope :math:`\gamma_{\rm in}` inside the break radius. + outer_slope : float + Logarithmic density slope :math:`\gamma_{\rm out}` outside the break radius. + break_radius : float + Break radius :math:`\theta_{\rm b}` in arcseconds separating the two power-law regimes. + + References + ---------- + Du, Metcalf & Barkana (2020), MNRAS, 495, 4209. + """ + def __init__( self, centre: Tuple[float, float] = (0.0, 0.0), @@ -173,6 +215,43 @@ def hyp2f1_series(t, q, r, z, max_terms=20, xp=np): class PowerLawBrokenSph(PowerLawBroken): + r"""Broken spherical power-law mass profile with inner and outer slopes. + + The spherical limit of :class:`PowerLawBroken`. The convergence is: + + .. math:: + + \kappa(r) = \begin{cases} + \kappa_{\rm b} + \left(\dfrac{\theta_{\rm b}}{r}\right)^{\gamma_{\rm in}} + & r \leq \theta_{\rm b} \\[6pt] + \kappa_{\rm b} + \left(\dfrac{\theta_{\rm b}}{r}\right)^{\gamma_{\rm out}} + & r > \theta_{\rm b} + \end{cases} + + where :math:`r` is the circular projected radius, :math:`\theta_{\rm b}` is + the break radius, and :math:`\kappa_{\rm b}` is the convergence at the + break radius. + + Parameters + ---------- + centre : (float, float) + (y, x) arc-second coordinates of the profile centre. + einstein_radius : float + Einstein radius in arcseconds. + inner_slope : float + Logarithmic density slope :math:`\gamma_{\rm in}` inside the break radius. + outer_slope : float + Logarithmic density slope :math:`\gamma_{\rm out}` outside the break radius. + break_radius : float + Break radius :math:`\theta_{\rm b}` in arcseconds separating the two power-law regimes. + + References + ---------- + Du, Metcalf & Barkana (2020), MNRAS, 495, 4209. + """ + def __init__( self, centre: Tuple[float, float] = (0.0, 0.0), diff --git a/autogalaxy/profiles/mass/total/power_law_core.py b/autogalaxy/profiles/mass/total/power_law_core.py index b1f30ca99..c12be621c 100644 --- a/autogalaxy/profiles/mass/total/power_law_core.py +++ b/autogalaxy/profiles/mass/total/power_law_core.py @@ -7,6 +7,40 @@ class PowerLawCore(MassProfile): + r"""Cored elliptical power-law (cored EPL / SPEMD) mass profile. + + The convergence of the cored elliptical power-law is: + + .. math:: + + \kappa(R) = \frac{3 - \gamma}{2} + \left(s^2 + R^2\right)^{-({\gamma - 1})/2} + \theta_{\rm E}^{\,\gamma - 1} + + where :math:`\gamma` is the logarithmic density slope, :math:`\theta_{\rm E}` + is the Einstein radius, :math:`R` is the elliptical radius, and :math:`s` is + the core radius that prevents the central divergence of the pure power-law. + + Parameters + ---------- + centre : (float, float) + (y, x) arc-second coordinates of the profile centre. + ell_comps : (float, float) + Ellipticity components (e1, e2) of the elliptical coordinate system. + einstein_radius : float + Einstein radius in arcseconds. + slope : float + Logarithmic density slope :math:`\gamma`; shallower profiles have + lower values, steeper profiles have higher values. + core_radius : float + Core radius :math:`s` in arcseconds. + + References + ---------- + Keeton (2001), arXiv:astro-ph/0102341. + Tessore & Metcalf (2015), A&A, 580, A79. + """ + def __init__( self, centre: Tuple[float, float] = (0.0, 0.0), @@ -175,6 +209,38 @@ def unit_mass(self): class PowerLawCoreSph(PowerLawCore): + r"""Cored spherical power-law mass profile. + + The spherical limit of :class:`PowerLawCore`. The convergence is: + + .. math:: + + \kappa(r) = \frac{3 - \gamma}{2} + \left(s^2 + r^2\right)^{-({\gamma - 1})/2} + \theta_{\rm E}^{\,\gamma - 1} + + where :math:`\gamma` is the logarithmic density slope, :math:`\theta_{\rm E}` + is the Einstein radius, :math:`r` is the circular projected radius, and + :math:`s` is the core radius. + + Parameters + ---------- + centre : (float, float) + (y, x) arc-second coordinates of the profile centre. + einstein_radius : float + Einstein radius in arcseconds. + slope : float + Logarithmic density slope :math:`\gamma`; shallower profiles have + lower values, steeper profiles have higher values. + core_radius : float + Core radius :math:`s` in arcseconds. + + References + ---------- + Keeton (2001), arXiv:astro-ph/0102341. + Tessore & Metcalf (2015), A&A, 580, A79. + """ + def __init__( self, centre: Tuple[float, float] = (0.0, 0.0), diff --git a/autogalaxy/profiles/mass/total/power_law_multipole.py b/autogalaxy/profiles/mass/total/power_law_multipole.py index f3d04e907..942e24606 100644 --- a/autogalaxy/profiles/mass/total/power_law_multipole.py +++ b/autogalaxy/profiles/mass/total/power_law_multipole.py @@ -39,80 +39,89 @@ def radial_and_angle_grid_from( class PowerLawMultipole(MassProfile): - def __init__( - self, - m=4, - centre: Tuple[float, float] = (0.0, 0.0), - einstein_radius: float = 1.0, - slope: float = 2.0, - multipole_comps: Tuple[float, float] = (0.0, 0.0), - ): - r""" - A multipole extension with multipole order M to the power-law total mass distribution. + r"""Angular multipole perturbation to a power-law total mass distribution. - Quantities computed from this profile (e.g. deflections, convergence) are of only the multipole, and not the - power-law mass distribution itself. + This profile provides only the multipole perturbation; it must be combined + with a :class:`PowerLaw` profile that shares the same ``einstein_radius`` and + ``slope`` parameters. The multipole convergence is: - The typical use case is therefore for the multipoles to be combined with a `PowerLaw` mass profile with the - same parameters (see example below). + .. math:: - When combined with a power-law, the functional form of the convergence is: + \kappa_m(r, \phi) = \frac{1}{2} + \left(\frac{\theta_{\rm E}}{r}\right)^{\gamma - 1} + k_m \cos\!\bigl(m(\phi - \phi_m)\bigr) - .. math:: - \kappa(r, \phi) = \frac{1}{2} \left(\frac{\theta_{\rm E}^{\rm mass}}{r}\right)^{\gamma^{\rm mass} - 1} - k^{\rm mass}_m \, \cos(m(\phi - \phi^{\rm mass}_m)) \, , + where :math:`m` is the multipole order, :math:`\gamma` is the power-law slope, + :math:`k_m` is the multipole amplitude, and :math:`\phi_m` is the multipole + orientation angle. The amplitude and angle are parameterised via ellipticity + components :math:`(\epsilon_1^{\rm mp},\, \epsilon_2^{\rm mp})`: - Where \\xi are elliptical coordinates calculated according to :class: SphProfile. + .. math:: - The parameters :math: k^{\rm mass}_m and :math: \phi^{\rm mass}_are parameterized as elliptical components - :math: (\epsilon_{\rm 1}^{\rm mp}\,\epsilon_{\rm 2}^{\rm mp}), which are given by: + k_m = \sqrt{{\epsilon_1^{\rm mp}}^2 + {\epsilon_2^{\rm mp}}^2}, \qquad + \phi_m = \frac{1}{m} \arctan\!\frac{\epsilon_2^{\rm mp}}{\epsilon_1^{\rm mp}} - .. math:: - \phi^{\rm mass}_m = \frac{1}{m} \arctan{\frac{\epsilon_{\rm 2}^{\rm mp}}{\epsilon_{\rm 1}^{\rm mp}}}, \, \, - k^{\rm mass}_m = \sqrt{{\epsilon_{\rm 1}^{\rm mp}}^2 + {\epsilon_{\rm 2}^{\rm mp}}^2} \, . + The pure deflection-only nature of the perturbation means the convergence + integrates to zero over all angular positions; the net mass contribution is + therefore zero. - This mass profile is described fully in the following paper: https://arxiv.org/abs/1302.5482 - - Parameters - ---------- - centre - The (y,x) arc-second coordinates of the profile centre. - einstein_radius - The arc-second Einstein radius. - slope - The density slope of the power-law (lower value -> shallower profile, higher value -> steeper profile). - multipole_comps - The first and second ellipticity components of the multipole. - - Examples - -------- - - mass = al.mp.PowerLaw( - centre=(0.0, 0.0), - ell_comps=(-0.1, 0.2), - einstein_radius=1.0, - slope=2.2 - ) - - multipole = al.mp.PowerLawMultipole( - centre=(0.0, 0.0), - einstein_radius=1.0, - slope=2.2, - multipole_comps=(0.3, 0.2) - ) - - galaxy = al.Galaxy( - redshift=0.5, - mass=mass, - multipole=multipole - ) - - grid=al.Grid2D.uniform(shape_native=(10, 10), pixel_scales=0.1) + Parameters + ---------- + m : int + Multipole order (e.g. 4 for the quadrupole-like ``m=4`` mode). + centre : (float, float) + (y, x) arc-second coordinates of the profile centre. + einstein_radius : float + Einstein radius in arcseconds (shared with the base :class:`PowerLaw`). + slope : float + Logarithmic density slope :math:`\gamma` (shared with the base :class:`PowerLaw`). + multipole_comps : (float, float) + Ellipticity-like components :math:`(\epsilon_1^{\rm mp},\, \epsilon_2^{\rm mp})` + that encode the multipole amplitude and orientation. + + References + ---------- + Chu, Hu & Kneib (2013), ApJ, 765, 134. arXiv:1302.5482 + Evans & Witt (2003), MNRAS, 345, 1351. + + Examples + -------- + + mass = al.mp.PowerLaw( + centre=(0.0, 0.0), + ell_comps=(-0.1, 0.2), + einstein_radius=1.0, + slope=2.2 + ) + + multipole = al.mp.PowerLawMultipole( + centre=(0.0, 0.0), + einstein_radius=1.0, + slope=2.2, + multipole_comps=(0.3, 0.2) + ) + + galaxy = al.Galaxy( + redshift=0.5, + mass=mass, + multipole=multipole + ) + + grid=al.Grid2D.uniform(shape_native=(10, 10), pixel_scales=0.1) + + deflections = galaxy.deflections_yx_2d_from( + grid=grid + ) + """ - deflections = galaxy.deflections_yx_2d_from( - grid=grid - ) - """ + def __init__( + self, + m=4, + centre: Tuple[float, float] = (0.0, 0.0), + einstein_radius: float = 1.0, + slope: float = 2.0, + multipole_comps: Tuple[float, float] = (0.0, 0.0), + ): super().__init__(centre=centre, ell_comps=(0.0, 0.0)) self.m = int(m)