From e738caed4022cde0b3e07331d4c29a20eb7b5519 Mon Sep 17 00:00:00 2001 From: Jammy2211 Date: Fri, 29 May 2026 09:12:07 +0100 Subject: [PATCH] test: regression guard for HowToLens tutorial_3 NaN axis-limits crash Adds test_autolens/lens/plot/test_tracer_plots_nan_axis.py reproducing the HowToLens chapter-1 tutorial_3_more_ray_tracing four-galaxy two-plane tracer on a grid whose pixels coincide with the singular isothermal mass centres. The original crash (ValueError: Axis limits cannot be NaN or Inf during plotting) was already fixed producer-side: mass-profile deflections now handle r=0, so the traced grids and images stay finite and the tracer plotters receive finite axis-limit extents. These tests lock that in: - traced grids (both planes) and the image are finite at the singular centres - subplot_tracer and subplot_galaxies_images render without raising Test-only; no library code changes. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../lens/plot/test_tracer_plots_nan_axis.py | 128 ++++++++++++++++++ 1 file changed, 128 insertions(+) create mode 100644 test_autolens/lens/plot/test_tracer_plots_nan_axis.py diff --git a/test_autolens/lens/plot/test_tracer_plots_nan_axis.py b/test_autolens/lens/plot/test_tracer_plots_nan_axis.py new file mode 100644 index 000000000..a34611106 --- /dev/null +++ b/test_autolens/lens/plot/test_tracer_plots_nan_axis.py @@ -0,0 +1,128 @@ +""" +Regression test for the HowToLens chapter-1 ``tutorial_3_more_ray_tracing`` +crash: ``ValueError: Axis limits cannot be NaN or Inf during plotting``. + +The tutorial builds a multi-plane tracer whose lens mass profiles are singular +isothermal spheres centred on grid pixels. Historically the deflection angles +evaluated at those singular centres produced NaN/Inf, so the traced +source-plane grid contained non-finite coordinates; the tracer plotters then +derived their axis limits from those coordinates and matplotlib raised +``ValueError: Axis limits cannot be NaN or Inf``. + +The fix was made at the *producer*: the mass-profile deflection code now handles +``r = 0`` (see autogalaxy "handle r=0 in ... deflections" and the NaN-safe +masking changes), so the traced grids and images stay finite and the plotters +receive finite extents. + +These tests lock that in by reproducing the tutorial-3 four-galaxy, two-plane +tracer on a grid whose pixels coincide with the singular mass centres, and +asserting (a) the traced grids and image remain finite and (b) the tracer +subplots render without raising. +""" +import numpy as np +import pytest + +import autolens as al +from autolens.lens.plot.tracer_plots import ( + subplot_tracer, + subplot_galaxies_images, +) + + +@pytest.fixture(name="grid_singular_centres") +def make_grid_singular_centres(): + # An odd ``shape_native`` with 0.5" pixels places pixels exactly on the + # singular mass centres at (0.0, 0.0) and (1.0, 0.0) -- the historical + # NaN/Inf trigger for the deflection angles. + return al.Grid2D.uniform(shape_native=(7, 7), pixel_scales=0.5) + + +@pytest.fixture(name="tracer_tutorial_3") +def make_tracer_tutorial_3(): + # The "Multi Galaxy Ray Tracing" tracer from HowToLens tutorial 3: two lens + # galaxies at z=0.5 (a main lens with a singular isothermal + external shear + # and a satellite) and two source galaxies at z=1.0. + lens = al.Galaxy( + redshift=0.5, + bulge=al.lp.SersicSph( + centre=(0.0, 0.0), intensity=2.0, effective_radius=0.5, sersic_index=2.5 + ), + mass=al.mp.Isothermal( + centre=(0.0, 0.0), ell_comps=(0.0, -0.111111), einstein_radius=1.6 + ), + shear=al.mp.ExternalShear(gamma_1=0.05, gamma_2=0.0), + ) + lens_satellite = al.Galaxy( + redshift=0.5, + bulge=al.lp.DevVaucouleursSph( + centre=(1.0, 0.0), intensity=2.0, effective_radius=0.2 + ), + mass=al.mp.IsothermalSph(centre=(1.0, 0.0), einstein_radius=0.4), + ) + source_0 = al.Galaxy( + redshift=1.0, + bulge=al.lp.DevVaucouleursSph( + centre=(0.1, 0.2), intensity=0.3, effective_radius=0.3 + ), + disk=al.lp.ExponentialCore( + centre=(0.1, 0.2), + ell_comps=(0.111111, 0.0), + intensity=3.0, + effective_radius=2.0, + ), + ) + source_1 = al.Galaxy( + redshift=1.0, + disk=al.lp.ExponentialCore( + centre=(-0.3, -0.5), + ell_comps=(0.1, 0.0), + intensity=8.0, + effective_radius=1.0, + ), + ) + return al.Tracer( + galaxies=[lens, lens_satellite, source_0, source_1], + cosmology=al.cosmo.Planck15(), + ) + + +def test__traced_grids_and_image_are_finite_at_singular_mass_centres( + tracer_tutorial_3, grid_singular_centres +): + # Producer-side guard: deflections at the singular mass centres must not + # introduce NaN/Inf, otherwise the plotters below would derive non-finite + # axis limits and crash (the original tutorial-3 failure). + traced = tracer_tutorial_3.traced_grid_2d_list_from(grid=grid_singular_centres) + + for plane_grid in traced: + assert np.isfinite(np.asarray(plane_grid.array)).all() + + image = tracer_tutorial_3.image_2d_from(grid=grid_singular_centres) + + assert np.isfinite(np.asarray(image.array)).all() + + +def test__subplot_tracer__singular_mass_centres__does_not_raise( + tracer_tutorial_3, grid_singular_centres, tmp_path, plot_patch +): + subplot_tracer( + tracer=tracer_tutorial_3, + grid=grid_singular_centres, + output_path=tmp_path, + output_format="png", + ) + + assert str(tmp_path / "tracer.png") in plot_patch.paths + + +def test__subplot_galaxies_images__singular_mass_centres__does_not_raise( + tracer_tutorial_3, grid_singular_centres, tmp_path, plot_patch +): + subplot_galaxies_images( + tracer=tracer_tutorial_3, + grid=grid_singular_centres, + output_path=tmp_path, + output_format="png", + ) + + assert str(tmp_path / "galaxies_images.png") in plot_patch.paths