Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
39aab61
Added cNFW
Jan 13, 2026
3397bd4
added cnfw.yaml to config
Jan 14, 2026
c0e0e98
included cnfw in __init__.py
Jan 14, 2026
8748cc5
included cnfw in __init__.py of mass folder
Jan 14, 2026
19fb509
added cnfw.py
Jan 14, 2026
efbb0d9
cnfw.yaml added
Jan 14, 2026
b9d974e
fix of F functions, remove if loop
Jan 14, 2026
a1a3e15
removed xp.asarray(theta)
Jan 15, 2026
a779a76
change xp.zeros_like to theta * 0.0
Jan 15, 2026
0579846
pass on xp=xp to F functions
Jan 15, 2026
bb41d60
prevent theta=0
Jan 15, 2026
36750ab
remove unnecessary mask0
Jan 15, 2026
189d401
added convergence_2d_from and potential_2d_from with zeros
Jan 15, 2026
700f4fc
docstring: corrected kappa_s
Jan 15, 2026
728bcb8
added test_nfw.py
Jan 16, 2026
e2791a5
fix convergence and potential test
Jan 16, 2026
1505abf
fix deflection test
Jan 16, 2026
980e655
changed class cNFW to cNFWsph
Jan 21, 2026
16d2dc2
change cNFW to cNFWsph in tests
Jan 21, 2026
1dcf3d0
corrected typo
Jan 21, 2026
cfea2c0
added kappa_s_and_scale_radius_and_core_radius
Jan 21, 2026
7e8bdda
changed naming cNFWsph to cNFWSph
Jan 21, 2026
5daf88d
changed kappa_s_and_scale_radius_and_core_radius to kappa_s_scale_rad…
Jan 21, 2026
791d7f9
added cnfw_mcr.py and added it in init files
Jan 21, 2026
e719e44
added cnfw_mcr.yaml
Jan 21, 2026
ec3f05c
xp=np in kappa_s_scale_radius_and_core_radius_for_duffy
Jan 21, 2026
0a82598
converted cnfw with Duffy to Ludlow
Jan 21, 2026
fec2847
cnfw_mcr.py fix, added scatter_sigma=0.0
Jan 21, 2026
45162d1
changed r_c and f_c priors to half of r_s
Jan 22, 2026
1de95e6
added a test in test_nfw_mcr and changed the inheritance from MassPro…
Jan 22, 2026
c50d7f0
fix import of AbstractgNFW in cnfw.py
Jan 22, 2026
bfde04c
change in test assert
Jan 22, 2026
ee1bc3c
change in test assert
Jan 22, 2026
7dea916
pre change commit
Jan 26, 2026
3f1f8cc
remove test.py and clean up cnfw.py
Jan 28, 2026
ed50465
Merge branch 'main' into feature/cored_NFW
Jan 28, 2026
34efa98
changed f_c lower bound to 0.0001
Jan 28, 2026
aa9ac34
deleted commented out code
Jan 28, 2026
d867d6a
changed potential and convergence bein zeros to a warning that it isn…
Jan 28, 2026
dec498c
Merge branch 'feature/cored_NFW' of https://github.com/Jammy2211/PyAu…
Jan 28, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions autogalaxy/config/priors/mass/dark/cnfw.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
cNFWSph:
centre_0:
type: Gaussian
mean: 0.0
sigma: 0.1
width_modifier:
type: Absolute
value: 0.05
limits:
lower: -inf
upper: inf
centre_1:
type: Gaussian
mean: 0.0
sigma: 0.1
width_modifier:
type: Absolute
value: 0.05
limits:
lower: -inf
upper: inf
kappa_s:
type: Uniform
lower_limit: 0.0
upper_limit: 1.0
width_modifier:
type: Relative
value: 0.2
limits:
lower: 0.0
upper: inf
scale_radius:
type: Uniform
lower_limit: 0.0
upper_limit: 30.0
width_modifier:
type: Relative
value: 0.2
limits:
lower: 0.0
upper: inf
core_radius:
type: Uniform
lower_limit: 0.0
upper_limit: 15.0
width_modifier:
type: Relative
value: 0.2
limits:
lower: 0.0
Comment on lines +44 to +50
Copy link

Copilot AI Jan 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The prior for core_radius allows lower_limit: 0.0, but the cNFW deflection implementation calls F_func(theta, radius=self.core_radius) where F_func contains terms like radius * log(2 * radius / theta); setting core_radius=0 will drive log(0) and produce invalid values. To avoid this singular case, consider enforcing core_radius > 0 (or handling the core_radius -> 0 limit analytically) instead of allowing exactly zero.

Suggested change
lower_limit: 0.0
upper_limit: 15.0
width_modifier:
type: Relative
value: 0.2
limits:
lower: 0.0
lower_limit: 0.0001
upper_limit: 15.0
width_modifier:
type: Relative
value: 0.2
limits:
lower: 0.0001

Copilot uses AI. Check for mistakes.
upper: inf
61 changes: 61 additions & 0 deletions autogalaxy/config/priors/mass/dark/cnfw_mcr.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
cNFWMCRLudlowSph:
centre_0:
type: Gaussian
mean: 0.0
sigma: 0.1
width_modifier:
type: Absolute
value: 0.05
limits:
lower: -inf
upper: inf
centre_1:
type: Gaussian
mean: 0.0
sigma: 0.1
width_modifier:
type: Absolute
value: 0.05
limits:
lower: -inf
upper: inf
mass_at_200:
type: LogUniform
lower_limit: 100000000.0
upper_limit: 1000000000000000.0
width_modifier:
type: Relative
value: 0.5
limits:
lower: 0.0
upper: inf
f_c:
type: Uniform
lower_limit: 0.0001
upper_limit: 0.5
width_modifier:
type: Relative
value: 0.2
limits:
lower: 0.0001
upper: inf
redshift_object:
type: Uniform
lower_limit: 0.0
upper_limit: 1.0
width_modifier:
type: Relative
value: 0.5
limits:
lower: 0.0
upper: inf
redshift_source:
type: Uniform
lower_limit: 0.0
upper_limit: 1.0
width_modifier:
type: Relative
value: 0.5
limits:
lower: 0.0
upper: inf
2 changes: 2 additions & 0 deletions autogalaxy/profiles/mass/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@
NFWMCRLudlow,
gNFWMCRLudlow,
NFWVirialMassConcSph,
cNFWSph,
cNFWMCRLudlowSph,
)
from .stellar import (
Gaussian,
Expand Down
2 changes: 2 additions & 0 deletions autogalaxy/profiles/mass/dark/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,5 @@
from .nfw_truncated_mcr import NFWTruncatedMCRLudlowSph, NFWTruncatedMCRDuffySph
from .nfw_truncated_mcr_scatter import NFWTruncatedMCRScatterLudlowSph
from .nfw_virial_mass_conc import NFWVirialMassConcSph
from .cnfw import cNFWSph
from .cnfw_mcr import cNFWMCRLudlowSph
175 changes: 175 additions & 0 deletions autogalaxy/profiles/mass/dark/cnfw.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
import numpy as np

from typing import Tuple

from autogalaxy.profiles.mass.dark.abstract import AbstractgNFW

import autoarray as aa

class cNFWSph(AbstractgNFW):
def __init__(
self,
centre: Tuple[float, float] = (0.0, 0.0),
kappa_s: float = 0.05,
scale_radius: float = 1.0,
core_radius: float = 0.01,
):
"""
Represents a spherical cored NFW density distribution

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)
scale_radius
The cored NFW scale radius `theta_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.
"""

super().__init__(
centre=centre,
ell_comps=(0.0, 0.0))

self.kappa_s = kappa_s
self.scale_radius = scale_radius
self.core_radius = core_radius


@aa.grid_dec.to_vector_yx
@aa.grid_dec.transform
def deflections_yx_2d_from(self, grid: aa.type.Grid2DLike, xp=np, **kwargs):
"""
Calculate the deflection angles on a grid of (y,x) arc-second coordinates.

The input grid of (y,x) coordinates are transformed to a coordinate system centred on the profile centre with
and rotated based on the position angle defined from its `ell_comps` (this is described fully below).

The numerical backend can be selected via the ``xp`` argument, allowing this
method to be used with both NumPy and JAX (e.g. inside ``jax.jit``-compiled
code). This is described fully later in this example.

Parameters
----------
grid
The grid of (y,x) arc-second coordinates the deflection angles are computed on.
xp
The numerical backend to use, either `numpy` or `jax.numpy`.
"""
theta = self.radial_grid_from(grid=grid, xp=xp, **kwargs).array
theta = xp.maximum(theta, 1e-8)

factor = (
4.0
* self.kappa_s
* self.scale_radius**2
)

deflection_r = (
factor
* (self.F_func(theta, self.scale_radius, xp=xp) - self.F_func(theta, self.core_radius, xp=xp)
- (self.scale_radius - self.core_radius) * self.dev_F_func(theta, self.scale_radius, xp=xp)
)
/ (theta * (self.scale_radius - self.core_radius)**2)
)
Comment on lines +71 to +77
Copy link

Copilot AI Jan 28, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The deflection formula divides by (self.scale_radius - self.core_radius)**2, so if scale_radius equals core_radius this will raise a division-by-zero error; consider either constraining these parameters so they cannot coincide (e.g. via priors / validation) or handling the degenerate scale_radius == core_radius limit explicitly.

Copilot uses AI. Check for mistakes.


return self._cartesian_grid_via_radial_from(
grid=grid,
radius=deflection_r,
xp=xp,
**kwargs,
)

def F_func(self, theta, radius, xp=np):

F = theta * 0.0

# theta < radius
mask1 = (theta > 0) & (theta < radius)

# theta > radius
mask2 = theta > radius

F = xp.where(
mask1,
(
radius / 2 * xp.log(2 * radius / theta)
- xp.sqrt(radius ** 2 - theta ** 2)
* xp.arctanh(xp.sqrt((radius - theta) / (radius + theta)))
),
F,
)

F = xp.where(
mask2,
(
radius / 2 * xp.log(2 * radius / theta)
+ xp.sqrt(theta ** 2 - radius ** 2)
* xp.arctan(xp.sqrt((theta - radius) / (theta + radius)))
),
F,
)

return 2 * radius * F

def dev_F_func(self, theta, radius, xp=np):

dev_F = theta * 0.0

mask1 = (theta > 0) & (theta < radius)
mask2 = theta == radius
mask3 = theta > radius

dev_F = xp.where(
mask1,
(
radius * xp.log(2 * radius / theta)
- (2 * radius ** 2 - theta ** 2) / xp.sqrt(radius ** 2 - theta ** 2)
* xp.arctanh(xp.sqrt((radius - theta) / (radius + theta)))
),
dev_F,
)

dev_F = xp.where(
mask2,
radius * (xp.log(2) - 1 / 2),
dev_F,
)

dev_F = xp.where(
mask3,
(
radius * xp.log(2 * radius / theta)
+ (theta ** 2 - 2 * radius ** 2) / xp.sqrt(theta ** 2 - radius ** 2)
* xp.arctan(xp.sqrt((theta - radius) / (theta + radius)))
),
dev_F,
)

return 2 * dev_F

@aa.grid_dec.to_array
def convergence_2d_from(self, grid: aa.type.Grid2DLike, xp=np, **kwargs):
"""
Convergence (dimensionless surface mass density) for the cored NFW profile.
This is not yet implemented for `cNFWSph`.
"""
raise NotImplementedError(
"convergence_2d_from is not implemented for cNFWSph; a physical cNFW "
"convergence expression must be added before use."
)

@aa.grid_dec.to_array
def potential_2d_from(self, grid: aa.type.Grid2DLike, xp=np, **kwargs):
"""
Lensing potential for the cored NFW profile.
This is not yet implemented for `cNFWSph`.
"""
raise NotImplementedError(
"potential_2d_from is not implemented for cNFWSph; a physical cNFW "
"potential expression must be added before use."
)
34 changes: 34 additions & 0 deletions autogalaxy/profiles/mass/dark/cnfw_mcr.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
from typing import Tuple

from autogalaxy.profiles.mass.dark.cnfw import cNFWSph

from autogalaxy.profiles.mass.dark import mcr_util

class cNFWMCRLudlowSph(cNFWSph):
def __init__(
self,
centre: Tuple[float, float] = (0.0, 0.0),
mass_at_200: float = 1e9,
f_c = 0.01,
redshift_object: float = 0.5,
redshift_source: float = 1.0,
):
self.mass_at_200 = mass_at_200
self.f_c = f_c
self.redshift_object = redshift_object
self.redshift_source = redshift_source

(
kappa_s,
scale_radius,
core_radius,
radius_at_200,
) = mcr_util.kappa_s_scale_radius_and_core_radius_for_ludlow(
mass_at_200=mass_at_200,
scatter_sigma=0.0,
f_c=f_c,
redshift_object=redshift_object,
redshift_source=redshift_source,
)

super().__init__(centre=centre, kappa_s=kappa_s, scale_radius=scale_radius, core_radius=core_radius)
Loading
Loading