Skip to content

Commit 63f9ef9

Browse files
Jammy2211Jammy2211claude
authored
test: skip jax-backed tests when jax is absent (Python matrix) (#604)
These tests exercise jax-only code paths (vmapped profiles / blackjax NUTS / nufft sparse operator / use_jax=True), so they need jax installed to run — it ships via the [optional] extras and is present in the per-repo CI (3.12/3.13). The PyAutoBuild Python Version Matrix installs the NumPy-only stack (and jax can't install on 3.9 anyway), so guard the 5 affected test(s) with a skipif on jax availability rather than letting them ModuleNotFoundError. Numpy-only tests in the same files are unaffected. Co-authored-by: Jammy2211 <JNightingale2211@gmail.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 5ec2da6 commit 63f9ef9

2 files changed

Lines changed: 201 additions & 176 deletions

File tree

Lines changed: 189 additions & 176 deletions
Original file line numberDiff line numberDiff line change
@@ -1,176 +1,189 @@
1-
from pathlib import Path
2-
import pytest
3-
4-
import autofit as af
5-
import autoarray as aa
6-
import autolens as al
7-
from autolens import exc
8-
9-
from autolens.interferometer.model.result import ResultInterferometer
10-
11-
directory = Path(__file__).resolve().parent
12-
13-
14-
def test__make_result__result_interferometer_is_returned(interferometer_7):
15-
model = af.Collection(galaxies=af.Collection(galaxy_0=al.Galaxy(redshift=0.5)))
16-
17-
analysis = al.AnalysisInterferometer(dataset=interferometer_7, use_jax=False)
18-
19-
search = al.m.MockSearch(name="test_search")
20-
21-
result = search.fit(model=model, analysis=analysis)
22-
23-
assert isinstance(result, ResultInterferometer)
24-
25-
26-
def test__figure_of_merit__matches_correct_fit_given_galaxy_profiles(interferometer_7):
27-
lens_galaxy = al.Galaxy(redshift=0.5, light=al.lp.Sersic(intensity=0.1))
28-
29-
model = af.Collection(galaxies=af.Collection(lens=lens_galaxy))
30-
31-
analysis = al.AnalysisInterferometer(dataset=interferometer_7, use_jax=False)
32-
33-
instance = model.instance_from_unit_vector([])
34-
analysis_log_likelihood = analysis.log_likelihood_function(instance=instance)
35-
36-
tracer = analysis.tracer_via_instance_from(instance=instance)
37-
38-
fit = al.FitInterferometer(dataset=interferometer_7, tracer=tracer)
39-
40-
assert fit.log_likelihood == analysis_log_likelihood
41-
42-
43-
def test__positions__likelihood_overwrite__changes_likelihood(
44-
interferometer_7, mask_2d_7x7
45-
):
46-
lens = al.Galaxy(redshift=0.5, mass=al.mp.IsothermalSph(centre=(0.05, 0.05)))
47-
source = al.Galaxy(redshift=1.0, light=al.lp.SersicSph(centre=(0.05, 0.05)))
48-
49-
model = af.Collection(galaxies=af.Collection(lens=lens, source=source))
50-
51-
analysis = al.AnalysisInterferometer(dataset=interferometer_7, use_jax=False)
52-
53-
instance = model.instance_from_unit_vector([])
54-
analysis_log_likelihood = analysis.log_likelihood_function(instance=instance)
55-
56-
tracer = analysis.tracer_via_instance_from(instance=instance)
57-
58-
fit = al.FitInterferometer(dataset=interferometer_7, tracer=tracer)
59-
60-
assert fit.log_likelihood == analysis_log_likelihood
61-
assert analysis_log_likelihood == pytest.approx(-62.463179940, 1.0e-4)
62-
63-
positions_likelihood = al.PositionsLH(
64-
positions=al.Grid2DIrregular([(1.0, 100.0), (200.0, 2.0)]), threshold=0.01
65-
)
66-
67-
analysis = al.AnalysisInterferometer(
68-
dataset=interferometer_7,
69-
positions_likelihood_list=[positions_likelihood],
70-
use_jax=False,
71-
)
72-
analysis_log_likelihood = analysis.log_likelihood_function(instance=instance)
73-
74-
assert analysis_log_likelihood == pytest.approx(-44097289569.2342, 1.0e-4)
75-
76-
77-
def _pixelization_model():
78-
pixelization = al.Pixelization(
79-
mesh=al.mesh.RectangularUniform(shape=(3, 3)),
80-
regularization=al.reg.Constant(coefficient=0.01),
81-
)
82-
return af.Collection(
83-
galaxies=af.Collection(
84-
galaxy_0=al.Galaxy(redshift=0.5),
85-
galaxy_1=al.Galaxy(redshift=0.5, pixelization=pixelization),
86-
)
87-
)
88-
89-
90-
def test__shared_state_from__preloads_curvature_reused__figure_of_merit_unchanged(
91-
interferometer_7,
92-
):
93-
dataset = interferometer_7.apply_sparse_operator(use_jax=False)
94-
95-
model = _pixelization_model()
96-
instance = model.instance_from_unit_vector([])
97-
98-
analysis = al.AnalysisInterferometer(
99-
dataset=dataset, use_jax=False, shared_preloads=True
100-
)
101-
102-
# `shared_state_from` builds a `PreloadsInterferometer` carrying the curvature matrix `F` and
103-
# the mapper (the channel-invariant inversion-setup quantities).
104-
shared = analysis.shared_state_from(instance=instance)
105-
assert isinstance(shared, aa.PreloadsInterferometer)
106-
assert shared.curvature_matrix is not None
107-
assert shared.mapper_galaxy_dict is not None
108-
109-
# The preloaded `F` and mapper are reused by the fit (identity) and leave the figure of merit
110-
# unchanged. Reusing the mapper means the Delaunay triangulation is not rebuilt per channel.
111-
fit_unshared = analysis.fit_from(instance=instance)
112-
fit_shared = analysis.fit_from(instance=instance, preloads=shared)
113-
114-
assert fit_shared.inversion.curvature_matrix is shared.curvature_matrix
115-
assert fit_shared.tracer_to_inversion.mapper_galaxy_dict is shared.mapper_galaxy_dict
116-
assert fit_shared.figure_of_merit == pytest.approx(fit_unshared.figure_of_merit)
117-
118-
# The full `log_likelihood_function` with the shared object matches the unshared call.
119-
assert analysis.log_likelihood_function(
120-
instance=instance, shared=shared
121-
) == pytest.approx(analysis.log_likelihood_function(instance=instance))
122-
123-
124-
def test__shared_state_from__returns_none_when_not_opted_in(interferometer_7):
125-
dataset = interferometer_7.apply_sparse_operator(use_jax=False)
126-
127-
model = _pixelization_model()
128-
instance = model.instance_from_unit_vector([])
129-
130-
analysis = al.AnalysisInterferometer(dataset=dataset, use_jax=False)
131-
132-
assert analysis.shared_state_from(instance=instance) is None
133-
134-
135-
def test__preloads_scoped__cross_type_preloads_reduced_to_mesh_view(interferometer_7):
136-
lens = al.Galaxy(redshift=0.5, light=al.lp.Sersic(intensity=0.1))
137-
tracer = al.Tracer(galaxies=[lens])
138-
139-
# Cross-dataset-type preloads (e.g. from an imaging lead factor in a joint graph): only
140-
# the mesh-geometry view is valid for an interferometer fit.
141-
cross_type = aa.PreloadsImaging(
142-
source_plane_mesh_grid=[["mesh"]], image_plane_mesh_grid=[["image-mesh"]]
143-
)
144-
145-
fit = al.FitInterferometer(
146-
dataset=interferometer_7, tracer=tracer, preloads=cross_type
147-
)
148-
149-
scoped = fit._preloads_scoped
150-
assert isinstance(scoped, aa.PreloadsInterferometer)
151-
assert scoped.source_plane_mesh_grid == [["mesh"]]
152-
assert scoped.curvature_matrix is None
153-
assert scoped.mapper_galaxy_dict is None
154-
155-
same_type = aa.PreloadsInterferometer(curvature_matrix="F")
156-
fit = al.FitInterferometer(
157-
dataset=interferometer_7, tracer=tracer, preloads=same_type
158-
)
159-
assert fit._preloads_scoped is same_type
160-
161-
162-
def test__shared_state_from__populates_mesh_geometry_fields(interferometer_7):
163-
dataset = interferometer_7.apply_sparse_operator(use_jax=False)
164-
165-
model = _pixelization_model()
166-
instance = model.instance_from_unit_vector([])
167-
168-
analysis = al.AnalysisInterferometer(
169-
dataset=dataset, use_jax=False, shared_preloads=True
170-
)
171-
172-
# The mesh-geometry fields ride alongside the curvature matrix + mapper so that
173-
# cross-dataset-type factors of a joint graph can consume the shared mesh.
174-
shared = analysis.shared_state_from(instance=instance)
175-
assert shared.source_plane_mesh_grid is not None
176-
assert shared.image_plane_mesh_grid is not None
1+
import importlib.util
2+
from pathlib import Path
3+
import pytest
4+
5+
import autofit as af
6+
import autoarray as aa
7+
import autolens as al
8+
from autolens import exc
9+
10+
# The interferometer shared-state preload builds the jax-backed NUFFT sparse
11+
# operator, so these tests need jax installed to run (it ships via the
12+
# `[optional]` extras). The NumPy-only Python-version matrix has no jax, so skip
13+
# there rather than fail.
14+
requires_jax = pytest.mark.skipif(
15+
importlib.util.find_spec("jax") is None,
16+
reason="requires jax (installed via the [optional] extras; absent on the NumPy-only matrix env)",
17+
)
18+
19+
from autolens.interferometer.model.result import ResultInterferometer
20+
21+
directory = Path(__file__).resolve().parent
22+
23+
24+
def test__make_result__result_interferometer_is_returned(interferometer_7):
25+
model = af.Collection(galaxies=af.Collection(galaxy_0=al.Galaxy(redshift=0.5)))
26+
27+
analysis = al.AnalysisInterferometer(dataset=interferometer_7, use_jax=False)
28+
29+
search = al.m.MockSearch(name="test_search")
30+
31+
result = search.fit(model=model, analysis=analysis)
32+
33+
assert isinstance(result, ResultInterferometer)
34+
35+
36+
def test__figure_of_merit__matches_correct_fit_given_galaxy_profiles(interferometer_7):
37+
lens_galaxy = al.Galaxy(redshift=0.5, light=al.lp.Sersic(intensity=0.1))
38+
39+
model = af.Collection(galaxies=af.Collection(lens=lens_galaxy))
40+
41+
analysis = al.AnalysisInterferometer(dataset=interferometer_7, use_jax=False)
42+
43+
instance = model.instance_from_unit_vector([])
44+
analysis_log_likelihood = analysis.log_likelihood_function(instance=instance)
45+
46+
tracer = analysis.tracer_via_instance_from(instance=instance)
47+
48+
fit = al.FitInterferometer(dataset=interferometer_7, tracer=tracer)
49+
50+
assert fit.log_likelihood == analysis_log_likelihood
51+
52+
53+
def test__positions__likelihood_overwrite__changes_likelihood(
54+
interferometer_7, mask_2d_7x7
55+
):
56+
lens = al.Galaxy(redshift=0.5, mass=al.mp.IsothermalSph(centre=(0.05, 0.05)))
57+
source = al.Galaxy(redshift=1.0, light=al.lp.SersicSph(centre=(0.05, 0.05)))
58+
59+
model = af.Collection(galaxies=af.Collection(lens=lens, source=source))
60+
61+
analysis = al.AnalysisInterferometer(dataset=interferometer_7, use_jax=False)
62+
63+
instance = model.instance_from_unit_vector([])
64+
analysis_log_likelihood = analysis.log_likelihood_function(instance=instance)
65+
66+
tracer = analysis.tracer_via_instance_from(instance=instance)
67+
68+
fit = al.FitInterferometer(dataset=interferometer_7, tracer=tracer)
69+
70+
assert fit.log_likelihood == analysis_log_likelihood
71+
assert analysis_log_likelihood == pytest.approx(-62.463179940, 1.0e-4)
72+
73+
positions_likelihood = al.PositionsLH(
74+
positions=al.Grid2DIrregular([(1.0, 100.0), (200.0, 2.0)]), threshold=0.01
75+
)
76+
77+
analysis = al.AnalysisInterferometer(
78+
dataset=interferometer_7,
79+
positions_likelihood_list=[positions_likelihood],
80+
use_jax=False,
81+
)
82+
analysis_log_likelihood = analysis.log_likelihood_function(instance=instance)
83+
84+
assert analysis_log_likelihood == pytest.approx(-44097289569.2342, 1.0e-4)
85+
86+
87+
def _pixelization_model():
88+
pixelization = al.Pixelization(
89+
mesh=al.mesh.RectangularUniform(shape=(3, 3)),
90+
regularization=al.reg.Constant(coefficient=0.01),
91+
)
92+
return af.Collection(
93+
galaxies=af.Collection(
94+
galaxy_0=al.Galaxy(redshift=0.5),
95+
galaxy_1=al.Galaxy(redshift=0.5, pixelization=pixelization),
96+
)
97+
)
98+
99+
100+
@requires_jax
101+
def test__shared_state_from__preloads_curvature_reused__figure_of_merit_unchanged(
102+
interferometer_7,
103+
):
104+
dataset = interferometer_7.apply_sparse_operator(use_jax=False)
105+
106+
model = _pixelization_model()
107+
instance = model.instance_from_unit_vector([])
108+
109+
analysis = al.AnalysisInterferometer(
110+
dataset=dataset, use_jax=False, shared_preloads=True
111+
)
112+
113+
# `shared_state_from` builds a `PreloadsInterferometer` carrying the curvature matrix `F` and
114+
# the mapper (the channel-invariant inversion-setup quantities).
115+
shared = analysis.shared_state_from(instance=instance)
116+
assert isinstance(shared, aa.PreloadsInterferometer)
117+
assert shared.curvature_matrix is not None
118+
assert shared.mapper_galaxy_dict is not None
119+
120+
# The preloaded `F` and mapper are reused by the fit (identity) and leave the figure of merit
121+
# unchanged. Reusing the mapper means the Delaunay triangulation is not rebuilt per channel.
122+
fit_unshared = analysis.fit_from(instance=instance)
123+
fit_shared = analysis.fit_from(instance=instance, preloads=shared)
124+
125+
assert fit_shared.inversion.curvature_matrix is shared.curvature_matrix
126+
assert fit_shared.tracer_to_inversion.mapper_galaxy_dict is shared.mapper_galaxy_dict
127+
assert fit_shared.figure_of_merit == pytest.approx(fit_unshared.figure_of_merit)
128+
129+
# The full `log_likelihood_function` with the shared object matches the unshared call.
130+
assert analysis.log_likelihood_function(
131+
instance=instance, shared=shared
132+
) == pytest.approx(analysis.log_likelihood_function(instance=instance))
133+
134+
135+
@requires_jax
136+
def test__shared_state_from__returns_none_when_not_opted_in(interferometer_7):
137+
dataset = interferometer_7.apply_sparse_operator(use_jax=False)
138+
139+
model = _pixelization_model()
140+
instance = model.instance_from_unit_vector([])
141+
142+
analysis = al.AnalysisInterferometer(dataset=dataset, use_jax=False)
143+
144+
assert analysis.shared_state_from(instance=instance) is None
145+
146+
147+
def test__preloads_scoped__cross_type_preloads_reduced_to_mesh_view(interferometer_7):
148+
lens = al.Galaxy(redshift=0.5, light=al.lp.Sersic(intensity=0.1))
149+
tracer = al.Tracer(galaxies=[lens])
150+
151+
# Cross-dataset-type preloads (e.g. from an imaging lead factor in a joint graph): only
152+
# the mesh-geometry view is valid for an interferometer fit.
153+
cross_type = aa.PreloadsImaging(
154+
source_plane_mesh_grid=[["mesh"]], image_plane_mesh_grid=[["image-mesh"]]
155+
)
156+
157+
fit = al.FitInterferometer(
158+
dataset=interferometer_7, tracer=tracer, preloads=cross_type
159+
)
160+
161+
scoped = fit._preloads_scoped
162+
assert isinstance(scoped, aa.PreloadsInterferometer)
163+
assert scoped.source_plane_mesh_grid == [["mesh"]]
164+
assert scoped.curvature_matrix is None
165+
assert scoped.mapper_galaxy_dict is None
166+
167+
same_type = aa.PreloadsInterferometer(curvature_matrix="F")
168+
fit = al.FitInterferometer(
169+
dataset=interferometer_7, tracer=tracer, preloads=same_type
170+
)
171+
assert fit._preloads_scoped is same_type
172+
173+
174+
@requires_jax
175+
def test__shared_state_from__populates_mesh_geometry_fields(interferometer_7):
176+
dataset = interferometer_7.apply_sparse_operator(use_jax=False)
177+
178+
model = _pixelization_model()
179+
instance = model.instance_from_unit_vector([])
180+
181+
analysis = al.AnalysisInterferometer(
182+
dataset=dataset, use_jax=False, shared_preloads=True
183+
)
184+
185+
# The mesh-geometry fields ride alongside the curvature matrix + mapper so that
186+
# cross-dataset-type factors of a joint graph can consume the shared mesh.
187+
shared = analysis.shared_state_from(instance=instance)
188+
assert shared.source_plane_mesh_grid is not None
189+
assert shared.image_plane_mesh_grid is not None

0 commit comments

Comments
 (0)