Skip to content

Commit 1cc6eb2

Browse files
authored
Merge pull request #470 from PyAutoLabs/feature/dark-matter-potentials
Lensing potential for elliptical/spherical dark-matter profiles (NFW/gNFW) + NFWSph fix
2 parents 3f640f3 + a4b8ce2 commit 1cc6eb2

5 files changed

Lines changed: 61 additions & 44 deletions

File tree

autogalaxy/profiles/mass/abstract/abstract.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ def convergence_func(self, grid_radius: float, xp=np) -> float:
173173
"""
174174
raise NotImplementedError
175175

176-
def potential_2d_from(self, grid):
176+
def potential_2d_from(self, grid, xp=np, **kwargs):
177177
"""
178178
Returns the 2D lensing potential of the mass profile from a 2D grid of Cartesian (y,x) coordinates.
179179

autogalaxy/profiles/mass/dark/gnfw.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,33 @@ def deflections_2d_via_mge_from(self, grid: aa.type.Grid2DLike, xp=np, **kwargs)
6060
)
6161
return deflections_via_mge
6262

63+
@aa.over_sample
64+
@aa.decorators.to_array
65+
@aa.decorators.transform
66+
def potential_2d_from(self, grid: aa.type.Grid2DLike, xp=np, **kwargs):
67+
"""
68+
Returns the 2D lensing potential via the same MGE decomposition used for the
69+
deflection angles, so that ``grad(psi)`` is self-consistent with ``deflections_yx_2d_from``.
70+
71+
This implementation is inherited by every elliptical and spherical gNFW/NFW variant
72+
(``NFW``, ``gNFWSph``, and the MCR / Virial-mass subclasses), none of which have a
73+
closed-form elliptical potential. The MGE decomposer expands the 3D density into a
74+
Gaussian sum and integrates each Gaussian's analytic potential.
75+
"""
76+
radii_min = self.scale_radius / 20000.0
77+
radii_max = self.scale_radius * 200.0
78+
sigmas = xp.exp(xp.linspace(xp.log(radii_min), xp.log(radii_max), 30))
79+
80+
mge_decomp = MGEDecomposer(mass_profile=self)
81+
82+
return mge_decomp.potential_2d_via_mge_from(
83+
grid=grid,
84+
xp=xp,
85+
sigma_log_list=sigmas,
86+
ellipticity_convention="major",
87+
three_D=True,
88+
)
89+
6390
def convergence_func(self, grid_radius: float, xp=np) -> float:
6491

6592
from scipy.integrate import quad

autogalaxy/profiles/mass/dark/nfw.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -413,11 +413,19 @@ def potential_2d_from(self, grid: aa.type.Grid2DLike, xp=np, **kwargs):
413413
grid=grid, **kwargs
414414
) + 0j
415415
return np.real(
416-
2.0 * self.scale_radius * self.kappa_s * self.potential_func_sph(eta, xp=xp)
416+
2.0
417+
* self.scale_radius**2
418+
* self.kappa_s
419+
* self.potential_func_sph(eta, xp=xp)
417420
)
418421

419422
@staticmethod
420423
def potential_func_sph(eta, xp=np):
424+
# Spherical NFW lensing potential psi(x) = 2 kappa_s r_s^2 g(x), with
425+
# g(x) = ln^2(x/2) - arctanh^2(sqrt(1 - x^2)), obtained by integrating
426+
# the deflection field radially. The previous prefactor used a single
427+
# factor of r_s instead of r_s^2, so grad(psi) came out a factor 1/r_s
428+
# too small relative to deflections_yx_2d_from.
421429
return ((xp.log(eta.array / 2.0)) ** 2) - (
422430
xp.arctanh(xp.sqrt(1 - eta.array**2))
423431
) ** 2

test_autogalaxy/profiles/mass/dark/test_gnfw.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,8 +67,8 @@ def test__potential_2d_from__elliptical_vs_spherical():
6767
centre=(0.1, 0.2), kappa_s=2.0, inner_slope=1.5, scale_radius=3.0
6868
)
6969

70-
assert elliptical.convergence_2d_from(grid) == pytest.approx(
71-
spherical.convergence_2d_from(grid).array, 1e-4
70+
assert elliptical.potential_2d_from(grid) == pytest.approx(
71+
spherical.potential_2d_from(grid).array, 1e-4
7272
)
7373

7474

test_autogalaxy/profiles/mass/dark/test_nfw.py

Lines changed: 22 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -287,46 +287,28 @@ def test__convergence_2d_from__nfw_ell():
287287
assert convergence == pytest.approx(1.388511, 1e-3)
288288

289289

290-
# def test__potential_2d_from():
291-
# nfw = ag.mp.NFWSph(centre=(0.3, 0.2), kappa_s=2.5, scale_radius=4.0)
292-
#
293-
# potential = nfw.potential_2d_from(grid=ag.Grid2DIrregular([[0.1875, 0.1625]]))
294-
#
295-
# assert potential == pytest.approx(0.03702, 1e-3)
296-
#
297-
# nfw = ag.mp.NFWSph(centre=(0.3, 0.2), kappa_s=2.5, scale_radius=4.0)
298-
#
299-
# potential = nfw.potential_2d_from(grid=ag.Grid2DIrregular([[0.1875, 0.1625]]))
300-
#
301-
# assert potential == pytest.approx(0.03702, 1e-3)
302-
#
303-
# nfw = ag.mp.NFW(
304-
# centre=(0.3, 0.2),
305-
# ell_comps=(0.03669, 0.172614),
306-
# kappa_s=2.5,
307-
# scale_radius=4.0,
308-
# )
309-
#
310-
# potential = nfw.potential_2d_from(grid=ag.Grid2DIrregular([[0.1625, 0.1625]]))
311-
#
312-
# assert potential == pytest.approx(0.05380, 1e-3)
313-
#
314-
# nfw_spherical = ag.mp.NFWSph(centre=(0.3, 0.2), kappa_s=2.5, scale_radius=4.0)
315-
# nfw_elliptical = ag.mp.NFW(
316-
# centre=(0.3, 0.2),
317-
# ell_comps=(0.0, 0.0),
318-
# kappa_s=2.5,
319-
# scale_radius=4.0,
320-
# )
321-
#
322-
# potential_spherical = nfw_spherical.potential_2d_from(
323-
# grid=ag.Grid2DIrregular([[0.1875, 0.1625]])
324-
# )
325-
# potential_elliptical = nfw_elliptical.potential_2d_from(
326-
# grid=ag.Grid2DIrregular([[0.1875, 0.1625]])
327-
# )
328-
#
329-
# assert potential_spherical == pytest.approx(potential_elliptical, 1e-3)
290+
def test__potential_2d_from__nfw_sph():
291+
nfw = ag.mp.NFWSph(centre=(0.3, 0.2), kappa_s=2.5, scale_radius=4.0)
292+
293+
potential = nfw.potential_2d_from(grid=ag.Grid2DIrregular([[0.1875, 0.1625]]))
294+
295+
assert potential == pytest.approx(0.148108, 1e-3)
296+
297+
298+
def test__potential_2d_from__elliptical_reduces_to_spherical():
299+
# At zero ellipticity the elliptical NFW (MGE potential) must match the
300+
# spherical NFW (analytic potential) — a cross-check of two independent
301+
# implementations.
302+
spherical = ag.mp.NFWSph(centre=(0.3, 0.2), kappa_s=2.5, scale_radius=4.0)
303+
elliptical = ag.mp.NFW(
304+
centre=(0.3, 0.2), ell_comps=(0.0, 0.0), kappa_s=2.5, scale_radius=4.0
305+
)
306+
307+
grid = ag.Grid2DIrregular([[0.1875, 0.1625]])
308+
309+
assert elliptical.potential_2d_from(grid=grid) == pytest.approx(
310+
spherical.potential_2d_from(grid=grid).array, 1e-3
311+
)
330312

331313

332314
def test__shear_yx_2d_from__nfw_sph_config_1():

0 commit comments

Comments
 (0)