Skip to content

fix(priors): width-modifier safety + strict sigma>0 (Phase 2 of #1331) #1346

Description

@Jammy2211

Overview

Phase 2 of the #1331 priors/messages batch — the behaviour-changing pair, split from Phase 1 (#1344, merged via #1345) because it alters prior-passing widths in search chaining (SLaM-style pipelines). Verdicts: bug/priors/06 + 08 on #1330; decisions resolved on #1331 (Decision 5: minimal — abs(mean) + optional configurable absolute floor; Decision 2: reject σ ≤ 0 strictly).

Sequencing constraint (from the Phase-1 impact analysis): the width fix (D5) lands with the strict σ check (D2) in one PR so today's silent σ=0 degeneracy (a mean=0 chained parameter freezes into a delta function) never becomes an unexplained hard crash: where a computed width is still ≤ 0, prior passing raises a clear, parameter-named PriorException pointing at the width-modifier config — not a deep MessageException.

Plan

  • RelativeWidthModifier: sigma = value * abs(mean), plus opt-in absolute_floor (YAML-configurable; engages when value*abs(mean) falls below it). Negative means no longer produce negative sigma (today 0.5 * -1.0 = -0.5 flows silently into GaussianPrior and flips value_for).
  • Prior passing (gaussian_prior_model_for_arguments width application): if the final width ≤ 0 (e.g. mean=0, no floor), raise PriorException naming the parameter path with remediation guidance (set an absolute width modifier or an absolute_floor).
  • NormalMessage.__init__: enforce σ > 0 (numpy path; JAX defers to NaN propagation — the existing assert_sigma_non_negative helper's lax.cond branch is broken by design and is retired). GaussianPrior(mean, sigma<=0) and GaussianPrior.with_limits(5, 5) become loud.
  • TruncatedNormalMessage: extend its existing < 0 rejection to <= 0 so the two classes agree (strictly).
  • Regression tests: mean=0 chained parameter (clear error without floor; correct width with floor), abs(mean) on negative means, floor engagement + config round-trip, strict ctor rejection on both message classes.
  • Full test_autofit/ suite; pre-flight grep for tests that deliberately construct σ ≤ 0 messages (they may encode point-mass behaviour — FixedMessage is the sanctioned path for that).
Detailed implementation plan

Affected Repositories

  • PyAutoLabs/PyAutoFit (primary)

Branch Survey

Repository Current Branch Dirty?
./PyAutoFit main (post-#1345, c0b6c94) clean

Suggested branch: feature/prior-width-safety

Parallel disjoint PyAutoFit claim alongside ep-graphical-docs (#1334) + ep-diagnostics (#1335), both autofit/graphical — this work is autofit/mapper/prior/width_modifier.py, autofit/mapper/prior_model/abstract.py (width application site), autofit/messages/{normal,truncated_normal}.py. Zero overlap.

Implementation Steps

  1. autofit/mapper/prior/width_modifier.pyRelativeWidthModifier.__init__(value, absolute_floor=None); __call__ returns max(value * abs(mean), floor) when floor set, else value * abs(mean); from_dict/dict round-trip the optional key.
  2. autofit/mapper/prior_model/abstract.py — after width = width_modifier(mean) (and the explicit r/a paths), raise exc.PriorException naming the prior path if width <= 0.
  3. autofit/messages/normal.py — numpy-path strict sigma > 0 enforcement in NormalMessage.__init__; retire the broken assert_sigma_non_negative JAX branch.
  4. autofit/messages/truncated_normal.py< 0<= 0.
  5. Tests per the plan bullet above (numpy-only).

Behaviour changes (user-visible, disclosed)

  • Chained fits with parameters whose posterior median is negative get a positive passed width (previously negative sigma, silently sign-flipping value_for). Sampling of affected chained fits changes — for the better.
  • Chained fits with a mean=0 unconfigured parameter now fail loudly with guidance (previously silently froze the parameter via a delta-function prior).
  • GaussianPrior/NormalMessage with σ ≤ 0 raise at construction.

Autonomy: supervised (--auto, human instructed live 2026-07-10 after Phase-1 merge; heart-ack 6-reason set recorded in active.md this chain).

🤖 Generated with Claude Code

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