@@ -42,8 +42,63 @@ def __init__(
4242 )
4343 super (MassProfileCSE , self ).__init__ ()
4444
45- def deflections_yx_2d_from (self , grid : aa .type .Grid2DLike ):
46- return self .deflections_2d_via_cse_from (grid = grid )
45+ def deflections_yx_2d_from (self , grid : aa .type .Grid2DLike , xp = np , ** kwargs ):
46+ return self .deflections_2d_via_analytic_from (grid = grid , xp = xp , ** kwargs )
47+
48+ @aa .grid_dec .to_vector_yx
49+ @aa .grid_dec .transform
50+ def deflections_2d_via_analytic_from (
51+ self , grid : aa .type .Grid2DLike , xp = np , ** kwargs
52+ ):
53+ """
54+ Analytic calculation deflection angles from Heyrovský & Karamazov 2024 via Eq. 30 & 31
55+
56+ Parameters
57+ ----------
58+ grid
59+ The grid of (y,x) arc-second coordinates the deflection angles are computed on.
60+ """
61+
62+ # Convert e definitions:
63+ # from q = (1-e)/(1+e) to q = sqrt(1-e**2)
64+
65+ e_autolens = xp .sqrt (self .ell_comps [1 ] ** 2 + self .ell_comps [0 ] ** 2 )
66+ e_hk24 = 2 * xp .sqrt (e_autolens ) / (1 + e_autolens )
67+
68+ # Define dimensionless length coords
69+
70+ x1 = grid .array [:, 1 ] / self .scale_radius
71+ x2 = grid .array [:, 0 ] / self .scale_radius
72+
73+ r2 = x1 ** 2 + x2 ** 2
74+
75+ # Avoid nans
76+
77+ mask = r2 > 1e-24
78+
79+ prefactor = xp .where (mask , 4 * self .kappa_s * xp .sqrt (1 - e_hk24 ** 2 ) / (
80+ ((x1 - e_hk24 ) ** 2 + x2 ** 2 ) * ((x1 + e_hk24 ) ** 2 + x2 ** 2 )
81+ ), 0.0 )
82+
83+ f1 = xp .where (mask , nfw_hk24_util .small_f_1 (x1 , x2 , e_hk24 , xp = xp ), 0.0 )
84+ f2 = xp .where (mask , nfw_hk24_util .small_f_2 (x1 , x2 , e_hk24 , xp = xp ), 0.0 )
85+ f3 = xp .where (mask , nfw_hk24_util .small_f_3 (x1 , x2 , e_hk24 , xp = xp ), 0.0 )
86+
87+ deflection_x = (x1 * ((x1 ** 2 - e_hk24 ** 2 ) * (1 - e_hk24 ** 2 ) + x2 ** 2 * (1 + e_hk24 ** 2 )) * f1
88+ + x1 * (x1 ** 2 + x2 ** 2 - e_hk24 ** 2 ) * f2
89+ - x2 * (x1 ** 2 + x2 ** 2 + e_hk24 ** 2 ) * f3 )
90+ deflection_x *= prefactor
91+
92+ deflection_y = (x2 * (x1 ** 2 * (1 - 2 * e_hk24 ** 2 ) + x2 ** 2 + e_hk24 ** 2 ) * f1
93+ + x2 * (x1 ** 2 + x2 ** 2 + e_hk24 ** 2 ) * f2
94+ + x1 * (x1 ** 2 + x2 ** 2 - e_hk24 ** 2 ) * f3 )
95+ deflection_y *= prefactor
96+
97+ return self .rotated_grid_from_reference_frame_from (
98+ xp .multiply (self .scale_radius , xp .vstack ((deflection_y , deflection_x )).T ),
99+ xp = xp ,
100+ ** kwargs ,
101+ )
47102
48103 @aa .grid_dec .to_vector_yx
49104 @aa .grid_dec .transform
@@ -146,7 +201,7 @@ def convergence_func(self, grid_radius: float) -> float:
146201 return np .real (
147202 2.0
148203 * self .kappa_s
149- * np .array (self .coord_func_g (grid_radius = grid_radius , xp = xp ))
204+ * np .array (self .coord_func_g (grid_radius = grid_radius ))
150205 )
151206
152207 @aa .over_sample
@@ -271,26 +326,26 @@ def shear_yx_2d_from(self, grid: aa.type.Grid2DLike, xp=np, **kwargs):
271326 # Convert e definitions:
272327 # from q = (1-e)/(1+e) to q = sqrt(1-e**2)
273328
274- e_autolens = np .sqrt (self .ell_comps [1 ] ** 2 + self .ell_comps [0 ] ** 2 )
275- e_hk24 = 2 * np .sqrt (e_autolens ) / np . sqrt (1 + 2 * e_autolens + e_autolens ** 2 )
329+ e_autolens = xp .sqrt (self .ell_comps [1 ] ** 2 + self .ell_comps [0 ] ** 2 )
330+ e_hk24 = 2 * xp .sqrt (e_autolens ) / (1 + e_autolens )
276331
277332 # Define dimensionless length coords
278333
279334 x1 = grid .array [:, 1 ] / self .scale_radius
280335 x2 = grid .array [:, 0 ] / self .scale_radius
281336
282337 # Avoid nans due to x=0
283- x1 = np .where (np .abs (x1 ) < 1e-6 , 1e-6 , x1 )
284- x2 = np .where (np .abs (x2 ) < 1e-6 , 1e-6 , x2 )
338+ x1 = xp .where (xp .abs (x1 ) < 1e-6 , 1e-6 , x1 )
339+ x2 = xp .where (xp .abs (x2 ) < 1e-6 , 1e-6 , x2 )
285340
286341 # Calculate shear from nfw_HK24.py
287342
288- g1 , g2 = nfw_hk24_util .g1_g2_from (x1 = x1 , x2 = x2 , e = e_hk24 , k_s = self .kappa_s )
343+ g1 , g2 = nfw_hk24_util .g1_g2_from (x1 = x1 , x2 = x2 , e = e_hk24 , k_s = self .kappa_s , xp = xp )
289344
290345 # Rotation for shear
291346
292347 shear_field = self .rotated_grid_from_reference_frame_from (
293- grid = np .vstack ((g2 , g1 )).T , angle = self .angle (xp ) * 2
348+ grid = xp .vstack ((g2 , g1 )).T , angle = self .angle (xp ) * 2 , xp = xp
294349 )
295350
296351 return aa .VectorYX2DIrregular (values = shear_field , grid = grid )
@@ -315,8 +370,8 @@ def convergence_2d_from(self, grid: aa.type.Grid2DLike, xp=np, **kwargs):
315370 # Convert e definitions:
316371 # from q = (1-e)/(1+e) to q = sqrt(1-e**2)
317372
318- e_autolens = np .sqrt (self .ell_comps [1 ] ** 2 + self .ell_comps [0 ] ** 2 )
319- e_hk24 = 2 * np .sqrt (e_autolens ) / np . sqrt (1 + 2 * e_autolens + e_autolens ** 2 )
373+ e_autolens = xp .sqrt (self .ell_comps [1 ] ** 2 + self .ell_comps [0 ] ** 2 )
374+ e_hk24 = 2 * xp .sqrt (e_autolens ) / (1 + e_autolens )
320375
321376 # Define dimensionless length coords
322377
@@ -325,13 +380,13 @@ def convergence_2d_from(self, grid: aa.type.Grid2DLike, xp=np, **kwargs):
325380
326381 # Avoid nans due to x=0
327382
328- x1 = np .where (np .abs (x1 ) < 1e-6 , 1e-6 , x1 )
329- x2 = np .where (np .abs (x2 ) < 1e-6 , 1e-6 , x2 )
383+ x1 = xp .where (xp .abs (x1 ) < 1e-6 , 1e-6 , x1 )
384+ x2 = xp .where (xp .abs (x2 ) < 1e-6 , 1e-6 , x2 )
330385
331386 # Calculate convergence from nfw_HK24.py
332- a = nfw_hk24_util .semi_major_axis_from (x1 , x2 , e_hk24 )
387+ a = nfw_hk24_util .semi_major_axis_from (x1 , x2 , e_hk24 , xp = xp )
333388
334- return nfw_hk24_util .kappa_from (k_s = self .kappa_s , a = a )
389+ return nfw_hk24_util .kappa_from (k_s = self .kappa_s , a = a , xp = xp )
335390
336391
337392class NFWSph (NFW ):
0 commit comments