Skip to content

fix: analysis log_likelihood_function discards the cause behind FitException #638

Description

@Jammy2211

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 LinAlgErrorFitException → 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

  • PyAutoLens (only)

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

  1. At each of the three sites, change raise af.exc.FitExceptionraise 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
  2. 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).
  3. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions