Skip to content

Commit dccff0a

Browse files
Jammy2211claude
authored andcommitted
fix: dataset-scoped preloads consumption — cross-type shared state reduces to mesh view
In a joint imaging+interferometer FactorGraphModel, the lead factor's shared preloads are forwarded to EVERY factor. A PreloadsInterferometer carries a mapper + curvature matrix that embed ITS dataset's grids — consuming them in an imaging fit (or vice versa) would silently corrupt the likelihood. Both fits now scope their preloads at consumption (_preloads_scoped): same-type passes through by identity; cross-type reduces to the mesh-geometry view, the only part valid across dataset types (PyAutoLens#599 D5). AnalysisInterferometer.shared_state_from also populates the mesh fields so an interferometer lead shares the mesh cross-type. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent 1513236 commit dccff0a

5 files changed

Lines changed: 121 additions & 2 deletions

File tree

autolens/imaging/fit_imaging.py

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,25 @@ def profile_subtracted_image(self) -> aa.Array2D:
130130
"""
131131
return self.data - self.blurred_image
132132

133+
@property
134+
def _preloads_scoped(self):
135+
"""
136+
The preloads as consumed by this fit's inversion, scoped to its dataset type.
137+
138+
Cross-dataset-type shared state (e.g. an interferometer lead factor in a joint
139+
imaging + interferometer graph) may carry a mapper / curvature matrix that embed the
140+
OTHER dataset's grids — consuming them here would silently corrupt the fit. Only the
141+
source-plane mesh geometry is valid across dataset types, so any non-imaging preloads
142+
are reduced to their mesh-geometry view.
143+
"""
144+
if self.preloads is None or isinstance(self.preloads, aa.PreloadsImaging):
145+
return self.preloads
146+
147+
return aa.PreloadsImaging(
148+
source_plane_mesh_grid=self.preloads.source_plane_mesh_grid,
149+
image_plane_mesh_grid=self.preloads.image_plane_mesh_grid,
150+
)
151+
133152
@property
134153
def tracer_to_inversion(self) -> TracerToInversion:
135154

@@ -147,7 +166,7 @@ def tracer_to_inversion(self) -> TracerToInversion:
147166
adapt_images=self.adapt_images,
148167
settings=self.settings,
149168
xp=self._xp,
150-
preloads=self.preloads,
169+
preloads=self._preloads_scoped,
151170
)
152171

153172
@cached_property

autolens/interferometer/fit_interferometer.py

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,26 @@ def tracer_to_inversion(self) -> TracerToInversion:
146146
adapt_images=self.adapt_images,
147147
settings=self.settings,
148148
xp=self._xp,
149-
preloads=self.preloads,
149+
preloads=self._preloads_scoped,
150+
)
151+
152+
@property
153+
def _preloads_scoped(self):
154+
"""
155+
The preloads as consumed by this fit's inversion, scoped to its dataset type.
156+
157+
Cross-dataset-type shared state (e.g. an imaging lead factor in a joint
158+
imaging + interferometer graph) is valid here only through its source-plane mesh
159+
geometry — a mapper / curvature matrix from another dataset type would embed that
160+
dataset's grids and silently corrupt the fit, so non-interferometer preloads are
161+
reduced to their mesh-geometry view.
162+
"""
163+
if self.preloads is None or isinstance(self.preloads, aa.PreloadsInterferometer):
164+
return self.preloads
165+
166+
return aa.PreloadsInterferometer(
167+
source_plane_mesh_grid=self.preloads.source_plane_mesh_grid,
168+
image_plane_mesh_grid=self.preloads.image_plane_mesh_grid,
150169
)
151170

152171
@cached_property

autolens/interferometer/model/analysis.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,9 +205,15 @@ def shared_state_from(self, instance: af.ModelInstance):
205205
tracer_to_inversion = fit.tracer_to_inversion
206206
inversion = tracer_to_inversion.inversion
207207

208+
# The mesh-geometry fields are also populated so that cross-dataset-type factors of a
209+
# joint graph (e.g. an imaging factor when this interferometer analysis leads) can share
210+
# the source-plane mesh; they consume ONLY these fields (see `_preloads_scoped` on the
211+
# fits) because the mapper and curvature matrix embed this dataset's grids.
208212
return aa.PreloadsInterferometer(
209213
curvature_matrix=inversion.curvature_matrix,
210214
mapper_galaxy_dict=tracer_to_inversion.mapper_galaxy_dict,
215+
source_plane_mesh_grid=tracer_to_inversion.traced_mesh_grid_pg_list,
216+
image_plane_mesh_grid=tracer_to_inversion.image_plane_mesh_grid_pg_list,
211217
)
212218

213219
def fit_from(

test_autolens/imaging/model/test_analysis_imaging.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,3 +266,34 @@ def counting(self, instance):
266266
_factor_graph_log_likelihood(masked_imaging_7x7, shared_preloads=True)
267267

268268
assert calls["n"] == 1
269+
270+
271+
def test__preloads_scoped__cross_type_preloads_reduced_to_mesh_view(masked_imaging_7x7):
272+
import autoarray as aa
273+
274+
lens = al.Galaxy(redshift=0.5, light=al.lp.Sersic(intensity=0.1))
275+
tracer = al.Tracer(galaxies=[lens])
276+
277+
# Cross-dataset-type preloads (e.g. from an interferometer lead factor in a joint graph):
278+
# the mapper / curvature matrix embed the other dataset's grids and must NOT be consumed
279+
# by an imaging fit — only the mesh-geometry view survives the scoping.
280+
cross_type = aa.PreloadsInterferometer(
281+
curvature_matrix="other-datasets-F",
282+
mapper_galaxy_dict="other-datasets-mapper",
283+
source_plane_mesh_grid=[["mesh"]],
284+
image_plane_mesh_grid=[["image-mesh"]],
285+
)
286+
287+
fit = al.FitImaging(dataset=masked_imaging_7x7, tracer=tracer, preloads=cross_type)
288+
289+
scoped = fit._preloads_scoped
290+
assert isinstance(scoped, aa.PreloadsImaging)
291+
assert scoped.source_plane_mesh_grid == [["mesh"]]
292+
assert scoped.image_plane_mesh_grid == [["image-mesh"]]
293+
assert scoped.curvature_matrix is None
294+
assert scoped.mapper_galaxy_dict is None
295+
296+
# Same-type preloads pass through untouched.
297+
same_type = aa.PreloadsImaging(source_plane_mesh_grid=[["mesh"]])
298+
fit = al.FitImaging(dataset=masked_imaging_7x7, tracer=tracer, preloads=same_type)
299+
assert fit._preloads_scoped is same_type

test_autolens/interferometer/model/test_analysis_interferometer.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,3 +130,47 @@ def test__shared_state_from__returns_none_when_not_opted_in(interferometer_7):
130130
analysis = al.AnalysisInterferometer(dataset=dataset, use_jax=False)
131131

132132
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

0 commit comments

Comments
 (0)