@@ -65,6 +65,62 @@ def _nfw_mass_3d_within_radius_from(r, kappa_s, scale_radius):
6565 return 4.0 * np .pi * (kappa_s / scale_radius ) * scale_radius ** 3 * mass_factor
6666
6767
68+ def _trapezoid_from (y , x , axis , xp ):
69+ if hasattr (xp , "trapezoid" ):
70+ return xp .trapezoid (y , x = x , axis = axis )
71+ return xp .trapz (y , x = x , axis = axis )
72+
73+
74+ def _interp_lane_emden_xp (x , xp ):
75+ x_table , h_table , _ = _isothermal_lane_emden_table ()
76+ return xp .interp (x , xp .asarray (x_table ), xp .asarray (h_table ))
77+
78+
79+ def _nfw_radial_deflection_from (r , kappa_s , scale_radius , xp ):
80+ x = xp .maximum (r / scale_radius , 1.0e-8 )
81+
82+ below = x < 1.0
83+ above = x > 1.0
84+ x_below = xp .minimum (x , 1.0 - 1.0e-8 )
85+ x_above = xp .maximum (x , 1.0 + 1.0e-8 )
86+
87+ f_below = xp .arccosh (1.0 / x_below ) / xp .sqrt (1.0 - x_below ** 2 )
88+ f_above = xp .arccos (1.0 / x_above ) / xp .sqrt (x_above ** 2 - 1.0 )
89+ f_at_one = 1.0
90+ f = xp .where (below , f_below , xp .where (above , f_above , f_at_one ))
91+
92+ g = xp .log (x / 2.0 ) + f
93+ return 4.0 * kappa_s * scale_radius * g / x
94+
95+
96+ def _kaplinghat_density_3d_from_radius (
97+ radii ,
98+ kappa_s ,
99+ scale_radius ,
100+ interaction_radius ,
101+ central_density ,
102+ isothermal_radius ,
103+ xp ,
104+ ):
105+ x_nfw = xp .maximum (radii / scale_radius , 1.0e-12 )
106+ nfw_density = (kappa_s / scale_radius ) / (x_nfw * (1.0 + x_nfw ) ** 2 )
107+
108+ safe_isothermal_radius = xp .maximum (isothermal_radius , 1.0e-12 )
109+ x_iso = xp .maximum (radii / safe_isothermal_radius , 1.0e-5 )
110+ h = _interp_lane_emden_xp (x_iso , xp = xp )
111+ safe_central_density = xp .where (xp .isfinite (central_density ), central_density , 0.0 )
112+ iso_density = safe_central_density * xp .exp (- h )
113+
114+ use_iso = (
115+ (interaction_radius > 0.0 )
116+ & (isothermal_radius > 0.0 )
117+ & xp .isfinite (central_density )
118+ & (radii < interaction_radius )
119+ )
120+
121+ return xp .where (use_iso , iso_density , nfw_density )
122+
123+
68124def _matched_isothermal_parameters_from (interaction_radius , kappa_s , scale_radius ):
69125 rho_1 = _nfw_density_from (
70126 r = interaction_radius ,
@@ -243,6 +299,57 @@ def radial_deflection_from_radius(self, radius):
243299 )[0 ]
244300 return 2.0 * mass_2d / radius
245301
302+ @staticmethod
303+ def radial_deflection_from (r , params , xp ):
304+ kappa_s = params [0 ]
305+ scale_radius = params [1 ]
306+ interaction_radius = params [2 ]
307+ central_density = params [3 ]
308+ isothermal_radius = params [4 ]
309+
310+ r = xp .asarray (r )
311+ r_safe = xp .maximum (r , 1.0e-8 )
312+
313+ z_max = xp .maximum (500.0 * scale_radius , 50.0 * interaction_radius )
314+ z_unit = xp .linspace (1.0e-5 , 1.0 , 160 )
315+ z = z_max * z_unit ** 3
316+ u = xp .linspace (0.0 , 1.0 , 64 )
317+
318+ projected_radii = xp .maximum (r_safe [:, None ] * u [None , :], 1.0e-6 )
319+ three_d_radii = xp .sqrt (
320+ projected_radii [:, :, None ] ** 2 + z [None , None , :] ** 2
321+ )
322+
323+ density = _kaplinghat_density_3d_from_radius (
324+ radii = three_d_radii ,
325+ kappa_s = kappa_s ,
326+ scale_radius = scale_radius ,
327+ interaction_radius = interaction_radius ,
328+ central_density = central_density ,
329+ isothermal_radius = isothermal_radius ,
330+ xp = xp ,
331+ )
332+ convergence = 2.0 * _trapezoid_from (density , x = z , axis = - 1 , xp = xp )
333+
334+ mass_integral = r_safe ** 2 * _trapezoid_from (
335+ convergence * u [None , :], x = u , axis = - 1 , xp = xp
336+ )
337+ numerical = 2.0 * mass_integral / r_safe
338+ analytic_nfw = _nfw_radial_deflection_from (
339+ r = r_safe ,
340+ kappa_s = kappa_s ,
341+ scale_radius = scale_radius ,
342+ xp = xp ,
343+ )
344+
345+ radial_deflection = xp .where (
346+ interaction_radius > 0.0 ,
347+ numerical ,
348+ analytic_nfw ,
349+ )
350+
351+ return xp .where (r > 1.0e-8 , radial_deflection , 0.0 )
352+
246353 @aa .decorators .to_vector_yx
247354 @aa .decorators .transform
248355 def deflections_yx_2d_from (self , grid : aa .type .Grid2DLike , xp = np , ** kwargs ):
0 commit comments