Skip to content

fix(priors): width-modifier safety + sigma validation agreement#1348

Merged
Jammy2211 merged 1 commit into
mainfrom
feature/prior-width-safety
Jul 10, 2026
Merged

fix(priors): width-modifier safety + sigma validation agreement#1348
Jammy2211 merged 1 commit into
mainfrom
feature/prior-width-safety

Conversation

@Jammy2211

Copy link
Copy Markdown
Collaborator

Summary

Phase 2 of the #1331 priors/messages batch (tracker #1346; Phase 1 merged via #1345) — the behaviour-changing width-modifier pair, shipped as one PR by design so the width fix and the sigma check land together. RelativeWidthModifier now returns value * abs(mean) with an opt-in, config-round-tripping absolute_floor; the default prior-passing path raises a parameter-named PriorException with remediation guidance when a computed width is still ≤ 0 (previously: a silent σ=0 delta prior froze the parameter); and NormalMessage finally rejects negative sigma, agreeing with TruncatedNormalMessage — the actual defect behind Decision 2.

One evidence-based deviation from the #1331 rec, disclosed in detail on #1346: strict σ ≤ 0 rejection broke 20 tests across three subsystems that deliberately use σ = 0 as a point-mass idiom (latent variables' simple_model_for_kwargs, from_mode(covariance=0), model_centred_relative pinning sigma == 0 at mean=0). This PR ships the permissive variant #1331 itself listed as the alternative: σ < 0 rejected, σ = 0 permitted and documented. If strict is still wanted, the prerequisite is a first-class point-mass representation for the latent machinery — separate design prompt on request.

API Changes

Behaviour changes on existing public symbols — nothing removed, one optional argument added.
RelativeWidthModifier gains absolute_floor=; its width is now value * abs(mean) (was value * mean, which went negative with the median). Default-path prior passing raises on non-positive computed widths instead of silently freezing the parameter. NormalMessage/GaussianPrior reject σ < 0 at construction (previously silent with sign-flipped value_for).
See full details below.

Test Plan

  • Full test_autofit/ suite: 1447 passed, 14 skipped
  • New test_autofit/mapper/prior/test_prior_width_safety.py — 11 regression tests: abs(mean), floor engagement + config round-trip, σ<0 rejection on both classes, σ=0 point-mass preserved, and the mean=0 chained-parameter gate (clear error without floor / correct width with floor / positive width for negative medians)
  • Previously-failing clusters under the strict variant re-verified green after the permissive adjustment (latent variables, from_mode, model_relative — 67/67)
  • Post-merge: downstream smoke — no workspace scripts touch the changed symbols directly (see impact note in fix(priors): width-modifier safety + strict sigma>0 (Phase 2 of #1331) #1346)

Validation checklist (--auto run — plan on the issue; D2 scope adjusted mid-run, disclosed)

  • Effective level: supervised (header: supervised, cap: bug → supervised); launched live in-session 2026-07-10 after Phase-1 merge
  • Plan: on the issue (fix(priors): width-modifier safety + strict sigma>0 (Phase 2 of #1331) #1346), written at start; one disclosed amendment (Decision 2 strict → permissive on test evidence, full rationale on fix(priors): width-modifier safety + strict sigma>0 (Phase 2 of #1331) #1346)
  • Gate: tests 1447 pass / 14 skip · smoke pending post-PR (no direct workspace consumers) · review CLEAN · Heart YELLOW within the live 6-reason ack recorded in active.md this session
  • Human: plan sound in hindsight — specifically the D2 permissive call?
  • Human: diff matches plan (no scope creep)?
  • Human: merge, amend, or reject — then log the outcome
Full API Changes (for automation & release notes)

Added

  • RelativeWidthModifier(value, absolute_floor=None) — optional minimum passed-prior width; engages when value * abs(mean) falls below it; round-trips through the width_modifier priors-config entry (absolute_floor key) and WidthModifier.from_dict/.dict

Changed Behaviour

  • RelativeWidthModifier.__call__(mean) — returns value * abs(mean) (was value * mean). Chained fits with negative posterior medians previously received a negative sigma that constructed silently and sign-flipped value_for/sample; they now receive the correct positive width. This changes sampling of affected chained fits — correctly.
  • AbstractPriorModel.mapper_from_prior_means (default width-modifier path only) — a computed width ≤ 0 (posterior median 0, relative modifier, no floor) raises exc.PriorException naming the parameter path and pointing at AbsoluteWidthModifier/absolute_floor remediation. Previously the parameter was silently frozen behind a σ=0 delta prior. Explicit a=/r= widths are exempt and keep their historical point-mass semantics (pinned by test_model_relative).
  • NormalMessage.__init__ (⇒ GaussianPrior) — rejects σ < 0 with exc.MessageException (previously constructed silently with deceptive variance = σ² > 0). σ = 0 remains permitted as the documented point-mass idiom. The dead assert_sigma_non_negative jax.lax.cond branch (traced both branches, never raised under jit) is retired; the JAX path defers to NaN propagation.
  • TruncatedNormalMessage.__init__ — same σ < 0 rejection as before with a clearer message; the two classes now agree.
  • WidthModifier.__eq__ — compares full .dict (so modifiers differing only in absolute_floor are unequal).

Migration

  • None required for typical use. Pipelines that (knowingly or not) relied on a negative sigma from a relative width modifier will now sample a correct positive width. Pipelines with an unconfigured mean=0 parameter on the default width path now fail loudly with guidance — set an AbsoluteWidthModifier or absolute_floor for that parameter.

🤖 Generated with Claude Code

Phase 2 of the #1331 priors/messages batch (Decisions 5 + 2, one PR by
design so the width fix and the sigma check land together):

- RelativeWidthModifier: sigma = value * abs(mean) (a negative posterior
  median previously produced a negative sigma that silently flipped the
  passed prior's scale) + opt-in absolute_floor (config round-trips via
  from_dict/dict; WidthModifier.__eq__ now compares full dicts)
- Prior passing (mapper_from_prior_means, default width-modifier path):
  a computed width <= 0 (mean=0, relative modifier, no floor) raises a
  parameter-named PriorException with remediation guidance instead of
  silently freezing the parameter behind a sigma=0 delta prior. Explicit
  a=/r= widths keep their historical, test-pinned point-mass semantics.
- NormalMessage: reject sigma < 0 (previously constructed silently with
  deceptive variance = sigma^2 > 0); the broken jax.lax.cond assert
  branch is retired (JAX defers to NaN propagation). sigma == 0 stays
  permitted: it is the established point-mass idiom (latent variables'
  simple_model_for_kwargs, from_mode(covariance=0), and
  model_centred_relative at mean=0 all depend on it) — evidence-based
  adjustment from the strict #1331 rec, disclosed on #1346.
- TruncatedNormalMessage: unchanged sigma < 0 rejection with a clearer
  message — the two classes now agree, which was the actual defect.

Adds test_prior_width_safety.py (11 regression tests incl. the mean=0
chained-parameter gate). Full suite: 1447 passed, 14 skipped.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

pending-release PR queued for the next release build

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant