Skip to content

fix(graphical): EP builds strict NormalMessage from negative-variance cavity → sigma<0 crash #1363

Description

@Jammy2211

Overview

The nightly PyAutoHeart workspace-validation.yml run is RED on a single cell, run_scripts (3.12, autolens, guides), because autolens_workspace/scripts/guides/modeling/advanced/expectation_propagation.py crashes during expectation propagation with autofit.exc.MessageException: NormalMessage sigma cannot be negative. It is stochastic/unseeded (sigma=-0.016 on 2026-07-13, sigma=-0.17 on 2026-07-11), so the negative width is produced inside the EP message-passing algebra, not by a fixed model mistake. The fix belongs in the PyAutoFit library — the user-facing guide must keep exercising the real EP path (no seeding / no autoimmunity).

Plan

  • Reproduce the crash locally with a fixed seed that forces the negative-variance EP branch; capture the exact frame constructing a strict NormalMessage from a negative-precision/variance message.
  • Determine whether (a) the EP path should route that construction through NaturalNormal / update_invalid so a transient negative-variance cavity is tolerated or damped, or (b) the projection/inversion feeding sigma has a sign/validity bug.
  • Implement the minimal library fix in autofit/graphical/expectation_propagation and/or autofit/messages/normal.py; keep the strict assert_sigma_non_negative guard intact for the genuine prior-passing misuse it was added for.
  • Correct the now-misleading RelativeWidthModifier hint in the exception text (that path already abs()-guards and cannot emit a negative sigma).
  • Add a regression test under test_autofit/graphical/ driving an EP update through a negative-variance cavity without raising.
  • Validate: full test_autofit/ green (3.12/3.13), smoke the EP guide script, Heart readiness. The guide script is not modified.
Detailed implementation plan

Affected Repositories

  • PyAutoFit (primary)

Branch Survey

Repository Current Branch Dirty?
./PyAutoFit main clean

Suggested branch: feature/ep-negative-sigma-crash

Root cause (grounded)

  • The guard assert_sigma_non_negative (autofit/messages/normal.py:45) rejects sigma < 0 at strict NormalMessage.__init__ (normal.py:110). It was added deliberately for the prior-passing misuse case and is load-bearing there — do not delete it.
  • Its error hint blames RelativeWidthModifier, but that is stale for this path: RelativeWidthModifier.__call__ already returns value * abs(mean) (autofit/mapper/prior/width_modifier.py:110, Priors & messages: 9 confirmed bugs — guidance wanted on 5 decisions #1331 D5), so it cannot emit a negative sigma.
  • EP legitimately produces intermediate messages with negative/infinite variance (cavity division of Gaussians). The correct vehicle already exists: NaturalNormal (normal.py:508), which permits eta2 in (-inf, 0) and bypasses the guard via AbstractMessage.__init__. The bug is that somewhere on the EP/declarative path a strict NormalMessage is built from a negative-variance state where a NaturalNormal should be used (or the projection/inversion that yields the passed sigma has a sign/validity bug).

Implementation Steps

  1. Reproduce: run the EP guide (or a minimal EP factor-graph) under a seed forcing a negative-variance cavity; capture the traceback frame that calls strict NormalMessage(mean, sigma).
  2. Inspect autofit/graphical/expectation_propagation/ep_mean_field.py (cavity prod/division ~L196-222, rescale ~L447) and factor_optimiser.py (calc_exact_update/calc_exact_projection).
  3. Route the offending construction through NaturalNormal / AbstractMessage.update_invalid (abstract.py:346), or correct the inversion sign — narrowest change that unblocks EP without weakening the prior-passing guard.
  4. Fix the misleading RelativeWidthModifier hint in the exception at normal.py:48.
  5. Add a regression test in test_autofit/graphical/ that drives an EP update through a negative-variance cavity without raising; keep an assertion that strict NormalMessage(sigma<0) still raises for the prior-passing case.
  6. Run python -m pytest test_autofit/ (3.12 + 3.13); smoke the EP guide script; confirm Heart readiness.

Key Files

  • autofit/messages/normal.py — strict NormalMessage guard (L45/L110) vs NaturalNormal (L508); exception hint (L48).
  • autofit/graphical/expectation_propagation/ep_mean_field.py — cavity build / division / rescale.
  • autofit/graphical/expectation_propagation/factor_optimiser.py — exact update/projection.
  • autofit/messages/abstract.pyupdate_invalid (L346), from_natural_parameters (L173), project (L266).
  • test_autofit/graphical/ — new regression test.

Trade-off

Preserve the strict sigma<0 guard for prior passing; only tolerate/damp negative-variance messages on the EP natural-parameter seam. Keep the change minimal.

Evidence

PyAutoHeart workspace-validation runs 29227574734 (2026-07-13) and 29153442364 (2026-07-11). Related history: #1331 / #1348 (sigma<=0 semantics, point-mass idiom).

Original Prompt

Click to expand starting prompt

Type: bug · Target: autofit · Repo: PyAutoFit · Difficulty: medium · Autonomy: safe

The nightly PyAutoHeart workspace-validation.yml run is RED on run_scripts (3.12, autolens, guides) because the EP guide script crashes with NormalMessage sigma cannot be negative (sigma=-0.016 on 2026-07-13, sigma=-0.17 on 2026-07-11 — stochastic/unseeded). Root cause: the sigma<0 guard (normal.py:45) is load-bearing for prior passing, but RelativeWidthModifier already abs()-guards, so the EP crash comes from the message-passing algebra producing a negative-variance message that is wrapped as a strict NormalMessage instead of NaturalNormal. Fix in the PyAutoFit library (graphical + messages/normal.py), not the workspace guide (no autoimmunity). Fix the stale RelativeWidthModifier hint; add an EP regression test.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions