1+ import numpy as np
2+
3+ from typing import Tuple
4+
5+ import autoarray as aa
6+
7+ from autogalaxy .profiles .mass .abstract .abstract import MassProfile
8+
9+ class cNFW (MassProfile ):
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.5 ,
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 * 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+
64+ factor = (
65+ 4.0
66+ * self .kappa_s
67+ * self .scale_radius ** 2
68+ )
69+
70+ deflection_r = (
71+ factor
72+ * (self .F_func (theta , self .scale_radius ) - self .F_func (theta , self .core_radius )
73+ - (self .scale_radius - self .core_radius ) * self .dev_F_func (theta , self .scale_radius )
74+ )
75+ / (theta * (self .scale_radius - self .core_radius )** 2 )
76+ )
77+
78+
79+ return self ._cartesian_grid_via_radial_from (
80+ grid = grid ,
81+ radius = deflection_r ,
82+ xp = xp ,
83+ ** kwargs ,
84+ )
85+
86+ def F_func (self , theta , radius , xp = np ):
87+ if theta == 0 :
88+ F = 0
89+ elif theta < radius :
90+ F = (radius / 2 * xp .log (2 * radius / theta ) - xp .sqrt (radius ** 2 - theta ** 2 )
91+ * xp .arctanh (xp .sqrt ((radius - theta ) / (radius + theta )))
92+ )
93+ else :
94+ F = (radius / 2 * xp .log (2 * radius / theta ) + xp .sqrt (theta ** 2 - radius ** 2 )
95+ * xp .arctan (xp .sqrt ((theta - radius ) / (theta + radius )))
96+ )
97+ return 2 * radius * F
98+
99+ def dev_F_func (self , theta , radius , xp = np ):
100+ if theta == 0 :
101+ dev_F = 0
102+ elif theta < radius :
103+ dev_F = (radius * xp .log (2 * radius / theta )
104+ - (2 * radius ** 2 - theta ** 2 ) / xp .sqrt (radius ** 2 - theta ** 2 )
105+ * xp .arctanh (xp .sqrt ((radius - theta ) / (radius + theta )))
106+ )
107+ elif theta == radius :
108+ dev_F = (radius * (xp .log (2 ) - 1 / 2 ))
109+ else :
110+ dev_F = (radius * xp .log (2 * radius / theta )
111+ + (theta ** 2 - 2 * radius ** 2 ) / xp .sqrt (theta ** 2 - radius ** 2 )
112+ * xp .arctan (xp .sqrt ((theta - radius ) / (theta + radius )))
113+ )
114+ return 2 * dev_F
0 commit comments