1+ import numpy as np
2+
3+ from typing import Tuple
4+
5+ from autogalaxy .profiles .mass .dark .abstract import AbstractgNFW
6+
7+ import autoarray as aa
8+
9+ class cNFWSph (AbstractgNFW ):
10+ def __init__ (
11+ self ,
12+ centre : Tuple [float , float ] = (0.0 , 0.0 ),
13+ kappa_s : float = 0.05 ,
14+ scale_radius : float = 1.0 ,
15+ core_radius : float = 0.01 ,
16+ ):
17+ """
18+ Represents a spherical cored NFW density distribution
19+
20+ Parameters
21+ ----------
22+ centre
23+ The (y,x) arc-second coordinates of the profile centre.
24+ kappa_s
25+ The overall normalization of the dark matter halo \|
26+ (kappa_s = (rho_0 * D_d * scale_radius)/lensing_critical_density)
27+ scale_radius
28+ The cored NFW scale radius `theta_s`, as an angle on the sky in arcseconds.
29+ core_radius
30+ The cored NFW core radius `theta_c`, as an angle on the sky in arcseconds.
31+ """
32+
33+ super ().__init__ (
34+ centre = centre ,
35+ ell_comps = (0.0 , 0.0 ))
36+
37+ self .kappa_s = kappa_s
38+ self .scale_radius = scale_radius
39+ self .core_radius = core_radius
40+
41+
42+ @aa .grid_dec .to_vector_yx
43+ @aa .grid_dec .transform
44+ def deflections_yx_2d_from (self , grid : aa .type .Grid2DLike , xp = np , ** kwargs ):
45+ """
46+ Calculate the deflection angles on a grid of (y,x) arc-second coordinates.
47+
48+ The input grid of (y,x) coordinates are transformed to a coordinate system centred on the profile centre with
49+ and rotated based on the position angle defined from its `ell_comps` (this is described fully below).
50+
51+ The numerical backend can be selected via the ``xp`` argument, allowing this
52+ method to be used with both NumPy and JAX (e.g. inside ``jax.jit``-compiled
53+ code). This is described fully later in this example.
54+
55+ Parameters
56+ ----------
57+ grid
58+ The grid of (y,x) arc-second coordinates the deflection angles are computed on.
59+ xp
60+ The numerical backend to use, either `numpy` or `jax.numpy`.
61+ """
62+ theta = self .radial_grid_from (grid = grid , xp = xp , ** kwargs ).array
63+ theta = xp .maximum (theta , 1e-8 )
64+
65+ factor = (
66+ 4.0
67+ * self .kappa_s
68+ * self .scale_radius ** 2
69+ )
70+
71+ deflection_r = (
72+ factor
73+ * (self .F_func (theta , self .scale_radius , xp = xp ) - self .F_func (theta , self .core_radius , xp = xp )
74+ - (self .scale_radius - self .core_radius ) * self .dev_F_func (theta , self .scale_radius , xp = xp )
75+ )
76+ / (theta * (self .scale_radius - self .core_radius )** 2 )
77+ )
78+
79+
80+ return self ._cartesian_grid_via_radial_from (
81+ grid = grid ,
82+ radius = deflection_r ,
83+ xp = xp ,
84+ ** kwargs ,
85+ )
86+
87+ def F_func (self , theta , radius , xp = np ):
88+
89+ F = theta * 0.0
90+
91+ # theta < radius
92+ mask1 = (theta > 0 ) & (theta < radius )
93+
94+ # theta > radius
95+ mask2 = theta > radius
96+
97+ F = xp .where (
98+ mask1 ,
99+ (
100+ radius / 2 * xp .log (2 * radius / theta )
101+ - xp .sqrt (radius ** 2 - theta ** 2 )
102+ * xp .arctanh (xp .sqrt ((radius - theta ) / (radius + theta )))
103+ ),
104+ F ,
105+ )
106+
107+ F = xp .where (
108+ mask2 ,
109+ (
110+ radius / 2 * xp .log (2 * radius / theta )
111+ + xp .sqrt (theta ** 2 - radius ** 2 )
112+ * xp .arctan (xp .sqrt ((theta - radius ) / (theta + radius )))
113+ ),
114+ F ,
115+ )
116+
117+ return 2 * radius * F
118+
119+ def dev_F_func (self , theta , radius , xp = np ):
120+
121+ dev_F = theta * 0.0
122+
123+ mask1 = (theta > 0 ) & (theta < radius )
124+ mask2 = theta == radius
125+ mask3 = theta > radius
126+
127+ dev_F = xp .where (
128+ mask1 ,
129+ (
130+ radius * xp .log (2 * radius / theta )
131+ - (2 * radius ** 2 - theta ** 2 ) / xp .sqrt (radius ** 2 - theta ** 2 )
132+ * xp .arctanh (xp .sqrt ((radius - theta ) / (radius + theta )))
133+ ),
134+ dev_F ,
135+ )
136+
137+ dev_F = xp .where (
138+ mask2 ,
139+ radius * (xp .log (2 ) - 1 / 2 ),
140+ dev_F ,
141+ )
142+
143+ dev_F = xp .where (
144+ mask3 ,
145+ (
146+ radius * xp .log (2 * radius / theta )
147+ + (theta ** 2 - 2 * radius ** 2 ) / xp .sqrt (theta ** 2 - radius ** 2 )
148+ * xp .arctan (xp .sqrt ((theta - radius ) / (theta + radius )))
149+ ),
150+ dev_F ,
151+ )
152+
153+ return 2 * dev_F
154+
155+ @aa .grid_dec .to_array
156+ def convergence_2d_from (self , grid : aa .type .Grid2DLike , xp = np , ** kwargs ):
157+ """
158+ Convergence (dimensionless surface mass density) for the cored NFW profile.
159+ This is not yet implemented for `cNFWSph`.
160+ """
161+ raise NotImplementedError (
162+ "convergence_2d_from is not implemented for cNFWSph; a physical cNFW "
163+ "convergence expression must be added before use."
164+ )
165+
166+ @aa .grid_dec .to_array
167+ def potential_2d_from (self , grid : aa .type .Grid2DLike , xp = np , ** kwargs ):
168+ """
169+ Lensing potential for the cored NFW profile.
170+ This is not yet implemented for `cNFWSph`.
171+ """
172+ raise NotImplementedError (
173+ "potential_2d_from is not implemented for cNFWSph; a physical cNFW "
174+ "potential expression must be added before use."
175+ )
0 commit comments