Skip to content

Commit 95ef6a4

Browse files
Jammy2211claude
authored andcommitted
Honour PYAUTO_TEST_MODE in LOSSampler to fix los_halos simulator timeouts
LOSSampler.galaxies_from now caps the line-of-sight halo population to a few per plane and loosens the negative-kappa quad integration when PYAUTO_TEST_MODE is active. This collapses the los_halos simulator runtime under the workspace integration runner (simulator.py 320s timeout -> 28s; simulator_jax.py timeout -> 45s) while keeping the full sampling and ray-tracing code paths exercised. Non-test-mode behaviour is bit-identical (still samples ~1100 halos with full-accuracy kappa). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 709f7d1 commit 95ef6a4

2 files changed

Lines changed: 180 additions & 17 deletions

File tree

autolens/lens/los.py

Lines changed: 70 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -12,14 +12,24 @@
1212
multi-plane :class:`autolens.Tracer`.
1313
"""
1414

15+
import warnings
16+
1517
import numpy as np
16-
from scipy.integrate import quad
18+
from scipy.integrate import quad, IntegrationWarning
1719
from scipy.interpolate import interp1d
1820
from typing import List, Optional, Tuple
1921

2022
import autogalaxy as ag
2123
from autogalaxy.cosmology import Planck15
2224

25+
from autoconf.test_mode import is_test_mode
26+
27+
# Number of LOS halos retained per plane when ``PYAUTO_TEST_MODE`` is active.
28+
# Capping the population keeps the multi-plane ray-tracing and per-galaxy
29+
# plotting paths exercised (so regressions still surface) while collapsing the
30+
# downstream cost from ~1100 halos to a few dozen. See ``LOSSampler.galaxies_from``.
31+
_TEST_MODE_MAX_HALOS_PER_PLANE = 3
32+
2333

2434
def comoving_distance_mpc_from(z, cosmology):
2535
"""
@@ -241,6 +251,8 @@ def negative_kappa_from(
241251
truncation_factor,
242252
c_scatter,
243253
cosmology,
254+
quad_limit=50,
255+
quad_epsrel=1.49e-8,
244256
):
245257
"""
246258
Compute the negative convergence sheet for a single LOS plane.
@@ -278,6 +290,16 @@ def negative_kappa_from(
278290
Log-normal scatter in concentration (sigma in dex, e.g. 0.15).
279291
cosmology
280292
A ``LensingCosmology`` instance.
293+
quad_limit
294+
Maximum number of adaptive subintervals for both the inner
295+
(concentration) and outer (mass) ``scipy.integrate.quad`` calls.
296+
Defaults to scipy's own default of ``50``. ``LOSSampler.galaxies_from``
297+
lowers this under ``PYAUTO_TEST_MODE`` to make the double integral cheap
298+
while still exercising the full integrand (the inner ``fsolve`` is the
299+
dominant cost, so fewer subintervals is a ~50x speed-up).
300+
quad_epsrel
301+
Relative error tolerance passed to both ``quad`` calls. Defaults to
302+
scipy's own default of ``1.49e-8``; loosened under test mode.
281303
282304
Returns
283305
-------
@@ -316,13 +338,20 @@ def _integrand_mass(m):
316338
lgc_hi = lgc_centre + 4.0 * c_scatter
317339

318340
c_integral = quad(
319-
_integrand_concentration, lgc_lo, lgc_hi, args=(m, lgc_centre)
341+
_integrand_concentration,
342+
lgc_lo,
343+
lgc_hi,
344+
args=(m, lgc_centre),
345+
limit=quad_limit,
346+
epsrel=quad_epsrel,
320347
)[0]
321348

322349
dndm = 10 ** B_mf * m ** A_mf
323350
return dndm * m * c_integral
324351

325-
mass_integral = quad(_integrand_mass, m_min, m_max)[0]
352+
mass_integral = quad(
353+
_integrand_mass, m_min, m_max, limit=quad_limit, epsrel=quad_epsrel
354+
)[0]
326355

327356
kappa = mass_integral * comoving_volume_per_arcsec2 / sigma_cr_mpc2
328357

@@ -601,6 +630,17 @@ def galaxies_from(self) -> List[ag.Galaxy]:
601630
cosmology = self.cosmology
602631
rng = np.random.RandomState(self.seed)
603632

633+
# ``PYAUTO_TEST_MODE`` (integration tests / workspace smoke runs) makes
634+
# the full LOS population prohibitively slow: a science run samples
635+
# ~1100 halos (driving multi-plane ray tracing to ~90s) and the
636+
# per-plane negative-kappa double integral costs ~3.8s/plane. Under test
637+
# mode we cap the halos per plane and loosen the kappa integral, which
638+
# keeps both code paths exercised while collapsing the runtime so the
639+
# los_halos simulators finish well under the per-script timeout cap.
640+
test_mode = is_test_mode()
641+
quad_limit = 1 if test_mode else 50
642+
quad_epsrel = 0.1 if test_mode else 1.49e-8
643+
604644
boundaries, centres = los_planes_from(
605645
z_lens=self.z_lens,
606646
z_source=self.z_source,
@@ -682,6 +722,9 @@ def galaxies_from(self) -> List[ag.Galaxy]:
682722
)
683723
n_halos = rng.poisson(n_bar)
684724

725+
if test_mode:
726+
n_halos = min(n_halos, _TEST_MODE_MAX_HALOS_PER_PLANE)
727+
685728
if n_halos > 0:
686729
masses = sample_halo_masses(
687730
n=n_halos,
@@ -718,20 +761,30 @@ def galaxies_from(self) -> List[ag.Galaxy]:
718761
ag.Galaxy(redshift=z_cen, mass=halo)
719762
)
720763

721-
kappa_neg = negative_kappa_from(
722-
z_centre=z_cen,
723-
comoving_volume_per_arcsec2=vol_depth,
724-
A_mf=mf_coeffs[i, 0],
725-
B_mf=mf_coeffs[i, 1],
726-
A_mc=mc_coeffs[i, 0],
727-
B_mc=mc_coeffs[i, 1],
728-
m_min=self.m_min,
729-
m_max=self.m_max,
730-
z_source=self.z_source,
731-
truncation_factor=self.truncation_factor,
732-
c_scatter=self.c_scatter,
733-
cosmology=cosmology,
734-
)
764+
with warnings.catch_warnings():
765+
# Under test mode the deliberately low ``quad_limit`` makes
766+
# scipy emit a (harmless, expected) max-subdivisions warning per
767+
# integral; silence it so smoke-run output stays clean. Full
768+
# accuracy runs (quad_limit=50) never trip it.
769+
if test_mode:
770+
warnings.simplefilter("ignore", IntegrationWarning)
771+
772+
kappa_neg = negative_kappa_from(
773+
z_centre=z_cen,
774+
comoving_volume_per_arcsec2=vol_depth,
775+
A_mf=mf_coeffs[i, 0],
776+
B_mf=mf_coeffs[i, 1],
777+
A_mc=mc_coeffs[i, 0],
778+
B_mc=mc_coeffs[i, 1],
779+
m_min=self.m_min,
780+
m_max=self.m_max,
781+
z_source=self.z_source,
782+
truncation_factor=self.truncation_factor,
783+
c_scatter=self.c_scatter,
784+
cosmology=cosmology,
785+
quad_limit=quad_limit,
786+
quad_epsrel=quad_epsrel,
787+
)
735788
galaxies.append(
736789
ag.Galaxy(
737790
redshift=z_cen,

test_autolens/lens/test_los.py

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
import numpy as np
2+
import pytest
3+
4+
import autolens as al
5+
from autogalaxy.cosmology import Planck15
6+
from autolens.lens import los
7+
8+
9+
def _approx_coefficients(n_planes):
10+
"""
11+
Approximate per-plane mass-function and mass-concentration coefficients,
12+
matching the pre-computed fallback used by the los_halos workspace
13+
simulators (avoids the optional ``hmf`` / ``colossus`` dependencies).
14+
"""
15+
mf = np.tile([-1.9, 8.0], (n_planes, 1))
16+
mc = np.tile([-3.0, 40.0], (n_planes, 1))
17+
return mf, mc
18+
19+
20+
def test__negative_kappa_from__loose_quad_matches_reference_and_is_negative():
21+
"""
22+
The ``quad_limit`` / ``quad_epsrel`` knobs that test mode lowers must thread
23+
through to both ``quad`` calls and still produce a finite, negative kappa.
24+
A coarse integration (limit=1) should agree with a finer one to a few
25+
percent — the value is unused in test mode, so loose accuracy is fine.
26+
"""
27+
cosmology = Planck15()
28+
_, centres = los.los_planes_from(
29+
z_lens=0.5, z_source=1.0, planes_before_lens=4, planes_after_lens=4
30+
)
31+
mf, mc = _approx_coefficients(len(centres))
32+
33+
kwargs = dict(
34+
z_centre=centres[0],
35+
comoving_volume_per_arcsec2=1.0,
36+
A_mf=mf[0, 0],
37+
B_mf=mf[0, 1],
38+
A_mc=mc[0, 0],
39+
B_mc=mc[0, 1],
40+
m_min=1e7,
41+
m_max=1e10,
42+
z_source=1.0,
43+
truncation_factor=100.0,
44+
c_scatter=0.15,
45+
cosmology=cosmology,
46+
)
47+
48+
reference = los.negative_kappa_from(quad_limit=10, quad_epsrel=1e-3, **kwargs)
49+
coarse = los.negative_kappa_from(quad_limit=1, quad_epsrel=1e-1, **kwargs)
50+
51+
assert reference < 0.0
52+
assert coarse < 0.0
53+
assert coarse == pytest.approx(reference, rel=0.05)
54+
55+
56+
def test__galaxies_from__test_mode_caps_halos_per_plane(monkeypatch):
57+
"""
58+
Under ``PYAUTO_TEST_MODE`` ``galaxies_from`` must cap the halo population to
59+
a handful per plane (so the downstream multi-plane ray tracing stays cheap)
60+
while still emitting one negative-kappa ``MassSheet`` galaxy per plane.
61+
"""
62+
monkeypatch.setenv("PYAUTO_TEST_MODE", "2")
63+
64+
cosmology = Planck15()
65+
_, centres = los.los_planes_from(
66+
z_lens=0.5, z_source=1.0, planes_before_lens=4, planes_after_lens=4
67+
)
68+
n_planes = len(centres)
69+
mf, mc = _approx_coefficients(n_planes)
70+
71+
sampler = los.LOSSampler(
72+
z_lens=0.5,
73+
z_source=1.0,
74+
planes_before_lens=4,
75+
planes_after_lens=4,
76+
m_min=1e7,
77+
m_max=1e10,
78+
cone_radius_arcsec=5.0,
79+
c_scatter=0.15,
80+
truncation_factor=100.0,
81+
cosmology=cosmology,
82+
mass_function_coefficients=mf,
83+
mass_concentration_coefficients=mc,
84+
seed=42,
85+
)
86+
87+
galaxies = sampler.galaxies_from()
88+
89+
halos = [
90+
g
91+
for g in galaxies
92+
if hasattr(g, "mass") and isinstance(g.mass, al.mp.NFWTruncatedSph)
93+
]
94+
sheets = [
95+
g
96+
for g in galaxies
97+
if hasattr(g, "mass_sheet") and isinstance(g.mass_sheet, al.mp.MassSheet)
98+
]
99+
100+
# One negative-kappa sheet per plane, all with negative convergence.
101+
assert len(sheets) == n_planes
102+
assert all(g.mass_sheet.kappa < 0.0 for g in sheets)
103+
104+
# Halos are capped to at most three per plane (grouped by plane redshift).
105+
counts = {}
106+
for g in halos:
107+
counts[g.redshift] = counts.get(g.redshift, 0) + 1
108+
109+
assert len(counts) > 0
110+
assert all(count <= 3 for count in counts.values())

0 commit comments

Comments
 (0)