Skip to content

Commit 9cb3c53

Browse files
committed
Consolidate duplicated critical-curve plot helper into plot_utils
_compute_critical_curve_lines was copy-pasted verbatim into both autolens/imaging/plot/fit_imaging_plots.py and autolens/interferometer/plot/fit_interferometer_plots.py, and the two copies had drifted: the imaging copy narrowly caught (ModuleNotFoundError, ValueError) and loudly logged anything else (guarding the PyAutoGalaxy abd7b717 / PyAutoFit #1280 silent-fallback regression), while the interferometer copy still had a bare 'except Exception' that silently swallowed every failure — the exact mode that regression guard exists to prevent. Model code imported inconsistent behaviour: interferometer/model/{visualizer,plotter} got the silent copy, point/model/* got the hardened one. Move the single canonical (hardened) definition to autolens/plot/plot_utils.py — which already re-exports the underlying _critical_curves_from/_caustics_from primitives — and re-export it from both fit-plot modules so every existing 'from ...fit_{imaging,interferometer}_plots import _compute_critical_curve_lines' import keeps resolving. All call sites now share one hardened definition. Behaviour-preserving except on the interferometer error path, which is intentionally lifted from silent-swallow to the hardened logging behaviour. _plot_source_plane is deliberately left duplicated: the two copies genuinely differ (mask source, zoom_extent_scale), so consolidating it is not behaviour-neutral. Witness tests (imaging error-path silent/logged assertions) updated to patch and observe the symbol at its new home; interferometer plot tests do not pin the error path, so none break. Dead imports (_caustics_from, numpy_lines) trimmed from both modules. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RSRerFzzFttWNeZ5C7U4fx
1 parent ce0ade9 commit 9cb3c53

4 files changed

Lines changed: 78 additions & 89 deletions

File tree

autolens/imaging/plot/fit_imaging_plots.py

Lines changed: 6 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -8,64 +8,16 @@
88
from autogalaxy.util.plot_utils import plot_array
99
from autoarray.plot.array import _zoom_array_2d
1010
from autoarray.plot.utils import subplots, save_figure, hide_unused_axes, conf_subplot_figsize, tight_layout
11-
from autoarray.plot.utils import numpy_lines as _to_lines
1211
from autoarray.inversion.mappers.abstract import Mapper
1312
from autoarray.inversion.plot.mapper_plots import plot_mapper
14-
from autogalaxy.util.plot_utils import _critical_curves_from, _caustics_from
13+
from autogalaxy.util.plot_utils import _critical_curves_from
1514

16-
logger = logging.getLogger(__name__)
17-
18-
19-
def _compute_critical_curve_lines(tracer, grid):
20-
"""Compute critical-curve and caustic lines for a tracer on a given grid.
21-
22-
Returns a 4-tuple ``(image_plane_lines, image_plane_line_colors,
23-
source_plane_lines, source_plane_line_colors)`` suitable for passing
24-
directly to :func:`~autoarray.plot.array.plot_array`. On failure
25-
(e.g. the mass model has no critical curves) returns
26-
``(None, None, None, None)``.
15+
# Canonical, single-source definition. Re-exported here so existing
16+
# ``from autolens.imaging.plot.fit_imaging_plots import _compute_critical_curve_lines``
17+
# imports keep working.
18+
from autolens.plot.plot_utils import _compute_critical_curve_lines
2719

28-
Parameters
29-
----------
30-
tracer
31-
The tracer whose mass distribution is used to trace critical curves
32-
and caustics.
33-
grid
34-
Image-plane grid on which the curves are evaluated.
35-
"""
36-
try:
37-
tan_cc, rad_cc = _critical_curves_from(tracer, grid)
38-
tan_ca, rad_ca = _caustics_from(tracer, grid)
39-
_tan_cc_lines = _to_lines(list(tan_cc) if tan_cc is not None else []) or []
40-
_rad_cc_lines = _to_lines(list(rad_cc) if rad_cc is not None else []) or []
41-
_tan_ca_lines = _to_lines(list(tan_ca) if tan_ca is not None else []) or []
42-
_rad_ca_lines = _to_lines(list(rad_ca) if rad_ca is not None else []) or []
43-
image_plane_lines = (_tan_cc_lines + _rad_cc_lines) or None
44-
image_plane_line_colors = (
45-
["white"] * len(_tan_cc_lines) + ["yellow"] * len(_rad_cc_lines)
46-
)
47-
source_plane_lines = (_tan_ca_lines + _rad_ca_lines) or None
48-
source_plane_line_colors = (
49-
["white"] * len(_tan_ca_lines) + ["yellow"] * len(_rad_ca_lines)
50-
)
51-
return image_plane_lines, image_plane_line_colors, source_plane_lines, source_plane_line_colors
52-
except (ModuleNotFoundError, ValueError):
53-
# ModuleNotFoundError: jax_zero_contour missing — already warned upstream in
54-
# plot_utils._critical_curves_method().
55-
# ValueError: no zero crossings in the eigenvalue grid (e.g. slope >= 2
56-
# isothermal where lambda_r > 0 everywhere). Curves don't exist for this
57-
# model, so rendering without overlays is correct.
58-
return None, None, None, None
59-
except Exception:
60-
# Anything else — log loudly with traceback so the next regression of the
61-
# "ZeroSolver raised inside model-fit, viz fell back to all-zero" failure
62-
# mode (PyAutoGalaxy abd7b717, PyAutoFit #1280) does not stay silent.
63-
logger.warning(
64-
"Critical-curve computation failed unexpectedly; rendering without "
65-
"overlays. Investigate — this used to be a silent fallback.",
66-
exc_info=True,
67-
)
68-
return None, None, None, None
20+
logger = logging.getLogger(__name__)
6921

7022

7123
def _compute_critical_curves_from_fit(fit):

autolens/interferometer/plot/fit_interferometer_plots.py

Lines changed: 8 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -8,41 +8,19 @@
88
from autogalaxy.util.plot_utils import plot_array
99
from autoarray.plot.yx import plot_yx
1010
from autoarray.plot.utils import subplots, save_figure, conf_subplot_figsize, tight_layout
11-
from autoarray.plot.utils import numpy_lines as _to_lines
1211
from autoarray.inversion.mappers.abstract import Mapper
1312
from autoarray.inversion.plot.mapper_plots import plot_mapper
14-
from autogalaxy.util.plot_utils import _critical_curves_from, _caustics_from
1513
from autolens.lens.plot.tracer_plots import plane_image_from
1614

17-
logger = logging.getLogger(__name__)
18-
19-
20-
def _compute_critical_curve_lines(tracer, grid):
21-
"""Compute critical-curve and caustic lines for a tracer on a given grid.
15+
# Canonical, single-source definition (shared with the imaging fit plots).
16+
# Re-exported here so existing
17+
# ``from autolens.interferometer.plot.fit_interferometer_plots import _compute_critical_curve_lines``
18+
# imports keep working. This also adopts the hardened error handling that the
19+
# imaging copy carried and this copy previously lacked (a bare ``except`` that
20+
# silently swallowed every failure).
21+
from autolens.plot.plot_utils import _compute_critical_curve_lines
2222

23-
Returns a 4-tuple ``(image_plane_lines, image_plane_line_colors,
24-
source_plane_lines, source_plane_line_colors)`` suitable for passing
25-
directly to :func:`~autoarray.plot.array.plot_array`. On failure
26-
returns ``(None, None, None, None)``.
27-
"""
28-
try:
29-
tan_cc, rad_cc = _critical_curves_from(tracer, grid)
30-
tan_ca, rad_ca = _caustics_from(tracer, grid)
31-
_tan_cc_lines = _to_lines(list(tan_cc) if tan_cc is not None else []) or []
32-
_rad_cc_lines = _to_lines(list(rad_cc) if rad_cc is not None else []) or []
33-
_tan_ca_lines = _to_lines(list(tan_ca) if tan_ca is not None else []) or []
34-
_rad_ca_lines = _to_lines(list(rad_ca) if rad_ca is not None else []) or []
35-
image_plane_lines = (_tan_cc_lines + _rad_cc_lines) or None
36-
image_plane_line_colors = (
37-
["white"] * len(_tan_cc_lines) + ["yellow"] * len(_rad_cc_lines)
38-
)
39-
source_plane_lines = (_tan_ca_lines + _rad_ca_lines) or None
40-
source_plane_line_colors = (
41-
["white"] * len(_tan_ca_lines) + ["yellow"] * len(_rad_ca_lines)
42-
)
43-
return image_plane_lines, image_plane_line_colors, source_plane_lines, source_plane_line_colors
44-
except Exception:
45-
return None, None, None, None
23+
logger = logging.getLogger(__name__)
4624

4725

4826
def _plot_source_plane(fit, ax, plane_index, zoom_to_brightest=True,

autolens/plot/plot_utils.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,58 @@
1+
import logging
2+
13
from autogalaxy.util.plot_utils import _critical_curves_from, _caustics_from
4+
from autoarray.plot.utils import numpy_lines as _to_lines
5+
6+
logger = logging.getLogger(__name__)
7+
8+
9+
def _compute_critical_curve_lines(tracer, grid):
10+
"""Compute critical-curve and caustic lines for a tracer on a given grid.
11+
12+
Returns a 4-tuple ``(image_plane_lines, image_plane_line_colors,
13+
source_plane_lines, source_plane_line_colors)`` suitable for passing
14+
directly to :func:`~autoarray.plot.array.plot_array`. On failure
15+
(e.g. the mass model has no critical curves) returns
16+
``(None, None, None, None)``.
17+
18+
Parameters
19+
----------
20+
tracer
21+
The tracer whose mass distribution is used to trace critical curves
22+
and caustics.
23+
grid
24+
Image-plane grid on which the curves are evaluated.
25+
"""
26+
try:
27+
tan_cc, rad_cc = _critical_curves_from(tracer, grid)
28+
tan_ca, rad_ca = _caustics_from(tracer, grid)
29+
_tan_cc_lines = _to_lines(list(tan_cc) if tan_cc is not None else []) or []
30+
_rad_cc_lines = _to_lines(list(rad_cc) if rad_cc is not None else []) or []
31+
_tan_ca_lines = _to_lines(list(tan_ca) if tan_ca is not None else []) or []
32+
_rad_ca_lines = _to_lines(list(rad_ca) if rad_ca is not None else []) or []
33+
image_plane_lines = (_tan_cc_lines + _rad_cc_lines) or None
34+
image_plane_line_colors = (
35+
["white"] * len(_tan_cc_lines) + ["yellow"] * len(_rad_cc_lines)
36+
)
37+
source_plane_lines = (_tan_ca_lines + _rad_ca_lines) or None
38+
source_plane_line_colors = (
39+
["white"] * len(_tan_ca_lines) + ["yellow"] * len(_rad_ca_lines)
40+
)
41+
return image_plane_lines, image_plane_line_colors, source_plane_lines, source_plane_line_colors
42+
except (ModuleNotFoundError, ValueError):
43+
# ModuleNotFoundError: jax_zero_contour missing — already warned upstream in
44+
# plot_utils._critical_curves_method().
45+
# ValueError: no zero crossings in the eigenvalue grid (e.g. slope >= 2
46+
# isothermal where lambda_r > 0 everywhere). Curves don't exist for this
47+
# model, so rendering without overlays is correct.
48+
return None, None, None, None
49+
except Exception:
50+
# Anything else — log loudly with traceback so the next regression of the
51+
# "ZeroSolver raised inside model-fit, viz fell back to all-zero" failure
52+
# mode (PyAutoGalaxy abd7b717, PyAutoFit #1280) does not stay silent.
53+
logger.warning(
54+
"Critical-curve computation failed unexpectedly; rendering without "
55+
"overlays. Investigate — this used to be a silent fallback.",
56+
exc_info=True,
57+
)
58+
return None, None, None, None

test_autolens/imaging/plot/test_fit_imaging_plots.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33

44
import pytest
55

6-
from autolens.imaging.plot import fit_imaging_plots
6+
from autolens.plot import plot_utils
77
from autolens.imaging.plot.fit_imaging_plots import (
88
_compute_critical_curve_lines,
99
subplot_fit,
@@ -179,9 +179,11 @@ def test__compute_critical_curve_lines__known_recoverable_exceptions__silent(
179179
def boom(*args, **kwargs):
180180
raise exc_cls("synthetic failure for test")
181181

182-
monkeypatch.setattr(fit_imaging_plots, "_critical_curves_from", boom)
182+
# _compute_critical_curve_lines lives in autolens.plot.plot_utils and is
183+
# re-exported by fit_imaging_plots; patch/observe it at its canonical home.
184+
monkeypatch.setattr(plot_utils, "_critical_curves_from", boom)
183185

184-
with caplog.at_level(logging.WARNING, logger=fit_imaging_plots.__name__):
186+
with caplog.at_level(logging.WARNING, logger=plot_utils.__name__):
185187
result = _compute_critical_curve_lines(tracer=None, grid=None)
186188

187189
assert result == (None, None, None, None)
@@ -203,9 +205,9 @@ def test__compute_critical_curve_lines__unexpected_exception__logs_warning(
203205
def boom(*args, **kwargs):
204206
raise RuntimeError("synthetic unexpected failure for test")
205207

206-
monkeypatch.setattr(fit_imaging_plots, "_critical_curves_from", boom)
208+
monkeypatch.setattr(plot_utils, "_critical_curves_from", boom)
207209

208-
with caplog.at_level(logging.WARNING, logger=fit_imaging_plots.__name__):
210+
with caplog.at_level(logging.WARNING, logger=plot_utils.__name__):
209211
result = _compute_critical_curve_lines(tracer=None, grid=None)
210212

211213
assert result == (None, None, None, None)

0 commit comments

Comments
 (0)