From 805af3b4801d7a42b6da79831c437e5af3ac032c Mon Sep 17 00:00:00 2001 From: Jammy2211 Date: Thu, 6 Aug 2026 15:21:54 -0400 Subject: [PATCH] feat: support Yang+2024 SIDM profiles in substructure halo batching Extends the substructure halo-parameter extractor to YangSIDMSph and YangSIDMMCRLudlowSph, packing the evolved (rho_s, r_s, r_c) so subhalo and line-of-sight halo populations can use the Yang+2024 gravothermal profile on the vmapped multi-plane tracing path. Per-plane halo ages enter through each profile's own t_age at construction. Requested in #631; companion to PyAutoGalaxy#556. Co-Authored-By: Claude Fable 5 --- autolens/lens/substructure_util.py | 21 +++ .../config/priors/dark_mass_profiles.yaml | 142 ++++++++++++++++++ test_autolens/lens/test_substructure_util.py | 51 +++++++ 3 files changed, 214 insertions(+) diff --git a/autolens/lens/substructure_util.py b/autolens/lens/substructure_util.py index 4ca4ba0c1..f51221a96 100644 --- a/autolens/lens/substructure_util.py +++ b/autolens/lens/substructure_util.py @@ -12,6 +12,14 @@ def _halo_parameter_extractor_from(profile_cls): ) if cls is not None ) + yang_profile_classes = tuple( + cls + for cls in ( + getattr(ag.mp, "YangSIDMSph", None), + getattr(ag.mp, "YangSIDMMCRLudlowSph", None), + ) + if cls is not None + ) if profile_cls is ag.mp.cNFWSph: @@ -41,6 +49,19 @@ def extract(prof): return 7, extract + if profile_cls in yang_profile_classes: + + def extract(prof): + return [ + prof.centre[0], + prof.centre[1], + prof.rho_s_evolved, + prof.scale_radius_evolved, + prof.core_radius_evolved, + ] + + return 5, extract + if profile_cls is ag.mp.NFWTruncatedSph: def extract(prof): diff --git a/test_autolens/config/priors/dark_mass_profiles.yaml b/test_autolens/config/priors/dark_mass_profiles.yaml index 2135715ad..9b241f6d0 100644 --- a/test_autolens/config/priors/dark_mass_profiles.yaml +++ b/test_autolens/config/priors/dark_mass_profiles.yaml @@ -454,3 +454,145 @@ KaplinghatCoredNFWMCRLudlowSph: width_modifier: type: Relative value: 0.5 +YangSIDMSph: + centre_0: + limits: + lower: -inf + upper: inf + mean: 0.0 + sigma: 0.1 + type: Gaussian + width_modifier: + type: Absolute + value: 0.05 + centre_1: + limits: + lower: -inf + upper: inf + mean: 0.0 + sigma: 0.1 + type: Gaussian + width_modifier: + type: Absolute + value: 0.05 + kappa_s: + limits: + lower: 0.0 + upper: inf + lower_limit: 0.0 + type: Uniform + upper_limit: 1.0 + width_modifier: + type: Relative + value: 0.2 + scale_radius: + limits: + lower: 0.0 + upper: inf + lower_limit: 0.0 + type: Uniform + upper_limit: 30.0 + width_modifier: + type: Relative + value: 0.2 + tau: + limits: + lower: 0.0 + upper: 1.0 + lower_limit: 0.0 + type: Uniform + upper_limit: 1.0 + width_modifier: + type: Relative + value: 0.2 +YangSIDMMCRLudlowSph: + centre_0: + limits: + lower: -inf + upper: inf + mean: 0.0 + sigma: 0.1 + type: Gaussian + width_modifier: + type: Absolute + value: 0.05 + centre_1: + limits: + lower: -inf + upper: inf + mean: 0.0 + sigma: 0.1 + type: Gaussian + width_modifier: + type: Absolute + value: 0.05 + mass_at_200: + limits: + lower: 0.0 + upper: inf + lower_limit: 100000000.0 + type: LogUniform + upper_limit: 1000000000000000.0 + width_modifier: + type: Relative + value: 0.5 + sigma_over_m: + limits: + lower: 0.0 + upper: inf + lower_limit: 0.0 + type: Uniform + upper_limit: 10.0 + width_modifier: + type: Relative + value: 0.5 + velocity_exponent: + limits: + lower: 0.0 + upper: 8.0 + lower_limit: 0.0 + type: Uniform + upper_limit: 4.0 + width_modifier: + type: Relative + value: 0.5 + velocity_ref: + limits: + lower: 0.0 + upper: inf + lower_limit: 1.0 + type: Uniform + upper_limit: 100.0 + width_modifier: + type: Relative + value: 0.5 + t_age: + limits: + lower: 0.0 + upper: inf + lower_limit: 0.0 + type: Uniform + upper_limit: 13.8 + width_modifier: + type: Relative + value: 0.2 + redshift_object: + limits: + lower: 0.0 + upper: inf + lower_limit: 0.0 + type: Uniform + upper_limit: 1.0 + width_modifier: + type: Relative + value: 0.5 + redshift_source: + limits: + lower: 0.0 + upper: inf + lower_limit: 0.0 + type: Uniform + upper_limit: 1.0 + width_modifier: + type: Relative + value: 0.5 diff --git a/test_autolens/lens/test_substructure_util.py b/test_autolens/lens/test_substructure_util.py index 95d969cb5..d17549baf 100644 --- a/test_autolens/lens/test_substructure_util.py +++ b/test_autolens/lens/test_substructure_util.py @@ -79,3 +79,54 @@ def test__galaxies_to_halo_arrays__raises_for_unsupported_profile_class(): max_n=1, profile_cls=al.mp.IsothermalSph, ) + + +requires_yang = pytest.mark.skipif( + not hasattr(al.mp, "YangSIDMSph"), + reason="Yang SIDM profiles are provided by the pending PyAutoGalaxy release.", +) + + +@requires_yang +def test__autolens_exposes_yang_profiles_from_autogalaxy(): + assert hasattr(al.mp, "YangSIDMSph") + assert hasattr(al.mp, "YangSIDMMCRLudlowSph") + + +@requires_jax +@requires_yang +def test__galaxies_to_halo_arrays__packs_yang_profile_parameters(): + profile = al.mp.YangSIDMSph( + centre=(0.1, -0.2), + kappa_s=0.03, + scale_radius=1.7, + tau=0.5, + ) + galaxies = [ + al.Galaxy(redshift=0.5, mass=profile), + al.Galaxy(redshift=1.0, mass_sheet=al.mp.MassSheet(kappa=-0.01)), + ] + + params, mask, sheet_kappas = substructure_util.galaxies_to_halo_arrays( + galaxies=galaxies, + plane_redshifts=[0.5, 1.0], + max_n=2, + profile_cls=al.mp.YangSIDMSph, + ) + + assert params.shape == (2, 2, 5) + assert mask.tolist() == [[True, False], [False, False]] + assert sheet_kappas.tolist() == [0.0, -0.01] + + np.testing.assert_allclose( + np.asarray(params[0, 0]), + np.array( + [ + 0.1, + -0.2, + profile.rho_s_evolved, + profile.scale_radius_evolved, + profile.core_radius_evolved, + ] + ), + )