Overview
The numpy-path log_likelihood_function in all three lens analyses wraps the fit in except Exception as e: raise af.exc.FitException — capturing e but discarding it (no chaining, no log). The FitException itself is correct and must stay (PR#607 path-parity: a numpy-path non-PD LinAlgError → FitException → the sampler resamples, mirroring the JAX NaN-resample). The defect is purely observability: when a fit raises, the traceback is just FitException at analysis.py:182, so the true cause (which inversion/matrix, non-PD vs. a genuine code bug) is invisible — this directly cost debugging time on autolens_workspace#308/#309. A real bug (KeyError, shape error) is also silently reclassified as "resample this model" and can hide as a slightly-lower acceptance rate indefinitely.
Plan
- Chain the cause —
raise af.exc.FitException from e at all three sites (imaging, interferometer, point). No behaviour change to real fits; the original traceback survives for anyone who unwraps.
- Add an opt-in debug escape hatch (env
PYAUTO_RAISE_ANALYSIS_EXCEPTIONS=1) that re-raises the original e instead of wrapping, so a developer can see real crashes without editing library source. Default OFF (production keeps wrapping so searches resample).
- Do NOT narrow the
except to specific types — path-parity needs to catch the numpy non-PD raise; the goal is visibility, not a tighter catch.
- Unit test in
test_autolens (numpy-only, no JAX): a deliberately-crashing analysis shows the real traceback with the env set and resamples cleanly (FitException) without it.
Detailed implementation plan
Affected Repositories
Branch Survey
| Repository |
Current Branch |
Dirty? |
| ./PyAutoLens |
main |
dirty (pre-existing paper_jax/paper.md, unrelated) |
Suggested branch: analysis-fitexception-masks-cause
No worktree conflict.
Implementation Steps
- At each of the three sites, change
raise af.exc.FitException → raise af.exc.FitException from e:
autolens/imaging/model/analysis.py:143-144
autolens/interferometer/model/analysis.py:181-182
autolens/point/model/analysis.py:138-139
- Add the opt-in raise-through: before wrapping, if
os.environ.get("PYAUTO_RAISE_ANALYSIS_EXCEPTIONS") is truthy, raise (re-raise original e). Factor into a tiny shared helper if it reduces duplication cleanly; otherwise inline the 3 lines (match surrounding style).
- Unit test in
test_autolens (numpy-only): a stub analysis/model whose fit raises a sentinel error → assert FitException is raised and __cause__ is the sentinel; with the env set, assert the sentinel propagates unwrapped. Guard the env in the test so it doesn't leak.
Key Files
PyAutoLens/autolens/imaging/model/analysis.py:143
PyAutoLens/autolens/interferometer/model/analysis.py:181
PyAutoLens/autolens/point/model/analysis.py:138
- new/updated test under
PyAutoLens/test_autolens/
Related
Original Prompt
Click to expand starting prompt
# Analysis log_likelihood_function masks the real error behind a bare FitException
Type: bug / Target: autolens / Repos: PyAutoLens / Difficulty: small / Autonomy: supervised
The numpy-path log_likelihood_function in all three lens analyses wraps the fit in a bare
`except Exception as e: raise af.exc.FitException` and DISCARDS e (no from e, no log):
imaging/model/analysis.py:143-144, interferometer/model/analysis.py:181-182, point/model/analysis.py:138-139.
The FitException is CORRECT (PR#607 path-parity: numpy non-PD LinAlgError → FitException → resample). The
problem is observability: the cause is thrown away (traceback is just FitException at analysis.py:182), and
`except Exception` is maximally broad so a genuine code bug hides as a resample.
Fix: `raise af.exc.FitException from e` at all three sites; opt-in PYAUTO_RAISE_ANALYSIS_EXCEPTIONS=1 to
re-raise the original (default OFF); do NOT narrow the except. Unit test in test_autolens (numpy-only).
Out of scope: TEST_MODE=2 bypass tolerance.
Overview
The numpy-path
log_likelihood_functionin all three lens analyses wraps the fit inexcept Exception as e: raise af.exc.FitException— capturingebut discarding it (no chaining, no log). TheFitExceptionitself is correct and must stay (PR#607 path-parity: a numpy-path non-PDLinAlgError→FitException→ the sampler resamples, mirroring the JAX NaN-resample). The defect is purely observability: when a fit raises, the traceback is justFitExceptionatanalysis.py:182, so the true cause (which inversion/matrix, non-PD vs. a genuine code bug) is invisible — this directly cost debugging time on autolens_workspace#308/#309. A real bug (KeyError, shape error) is also silently reclassified as "resample this model" and can hide as a slightly-lower acceptance rate indefinitely.Plan
raise af.exc.FitException from eat all three sites (imaging, interferometer, point). No behaviour change to real fits; the original traceback survives for anyone who unwraps.PYAUTO_RAISE_ANALYSIS_EXCEPTIONS=1) that re-raises the originaleinstead of wrapping, so a developer can see real crashes without editing library source. Default OFF (production keeps wrapping so searches resample).exceptto specific types — path-parity needs to catch the numpy non-PD raise; the goal is visibility, not a tighter catch.test_autolens(numpy-only, no JAX): a deliberately-crashing analysis shows the real traceback with the env set and resamples cleanly (FitException) without it.Detailed implementation plan
Affected Repositories
Branch Survey
paper_jax/paper.md, unrelated)Suggested branch:
analysis-fitexception-masks-causeNo worktree conflict.
Implementation Steps
raise af.exc.FitException→raise af.exc.FitException from e:autolens/imaging/model/analysis.py:143-144autolens/interferometer/model/analysis.py:181-182autolens/point/model/analysis.py:138-139os.environ.get("PYAUTO_RAISE_ANALYSIS_EXCEPTIONS")is truthy,raise(re-raise originale). Factor into a tiny shared helper if it reduces duplication cleanly; otherwise inline the 3 lines (match surrounding style).test_autolens(numpy-only): a stub analysis/model whose fit raises a sentinel error → assertFitExceptionis raised and__cause__is the sentinel; with the env set, assert the sentinel propagates unwrapped. Guard the env in the test so it doesn't leak.Key Files
PyAutoLens/autolens/imaging/model/analysis.py:143PyAutoLens/autolens/interferometer/model/analysis.py:181PyAutoLens/autolens/point/model/analysis.py:138PyAutoLens/test_autolens/Related
TEST_MODE=2bypass tolerance for a single-eval FitException.Original Prompt
Click to expand starting prompt