Skip to content

Priors & messages: 9 confirmed bugs — guidance wanted on 5 decisions #1331

Description

@Jammy2211

An AI-assisted audit of autofit/mapper/prior/ and autofit/messages/ (follow-up to the LogUniform fix, #1329) found 9 real bugs plus refactor ideas. Every finding has now been independently re-verified on main @ 0f26ff2d8 — reproductions and full detail in #1330. This issue is the decision hub: maintainer guidance + contributor input wanted, especially from anyone with exponential-family / EP background (@rhayes777).

Will fix as one batch — no decision needed

  • LogGaussianPrior.with_limits crashes on first call (TypeError: passes lower_limit/upper_limit kwargs the ctor doesn't accept); _new_for_base_message also broken. Fix: drop the kwargs — a log-Gaussian has support (0, ∞).
  • UniformPrior.logpdf fails on array input (scalar-only boundary snap, acknowledged # TODO). Fix: vectorise with np.where.
  • TruncatedNormalMessage.log_partition is missing the Gaussian term — via the generic exponential-family interface the pdf integrates to 2.27, not 1.0 (error factor exactly σ·exp(μ²/2σ²)). Sampling and log_prior_from_value are unaffected; the wrong path is the one the EP machinery consumes. Fix: add the Gaussian log-partition (as NormalMessage already does).
  • FixedMessage.logpdf_cache is an unbounded class-level dict that also returns an aliased mutable array (mutating a result corrupts later calls). Fix: np.zeros_like(x), no cache.

Decisions wanted (my recommendation in bold)

  1. Beta projection clamp is a no-op (inv_beta_suffstats clamps into a local that's immediately overwritten, so negative α, β escape with only a warning). When Newton–Raphson goes negative: raise (a failed projection shouldn't be silently patched to 0.5), or make the clamp work as originally intended?
  2. NormalMessage accepts negative sigma (the assert is commented out; TruncatedNormalMessage rejects it, so the two disagree). Enforce reject σ ≤ 0 strictly, or reject only σ < 0 and permit a σ = 0 point-mass?
  3. GammaMessage.from_mode(mode, V) builds the wrong variance — inversely wrong: request var 0.25, get 2.0; request 4.0, get 0.235 (α = 1 + m²·V has V upside-down). Which invariant should from_mode preserve: match mean + variance (α = m²/V, β = m/V — consistent with the Normal family), the α ≥ 1-guaranteed variant (α = 1 + m²/V), or solve the quadratic to match mode + variance exactly?
  4. log_prior_from_value normalisation is inconsistent: Uniform/LogUniform/Gaussian/LogGaussian drop their additive constants; TruncatedNormal returns the fully normalised density. Posterior shape is unaffected either way, but evidence/Bayes-factor arithmetic and logpdf users see silently different conventions. Option A: drop constants everywhere, document it as the contract, add an optional per-prior log-normaliser hook — or Option B: fully normalised density everywhere (scipy convention, touches every prior + JAX paths)?
  5. RelativeWidthModifier (the default prior-passing width, 0.5·mean) yields σ = 0 for means at zero and σ < 0 for negative means — silently, until (2) lands. Scope: minimal — use abs(mean) with an optional configurable absolute floor, mandatory floor with config migration, or a full width-modifier redesign?

Parked (design work, sequenced behind the EP framework review)

Property-based prior tests (after the fixes land), TransformedMessage semantics documentation, the single-source-of-truth density refactor, the Prior/Message hierarchy collapse, and bijector-library migration. These will be shaped by the EP statistics review now underway and get their own design issues later.

🤖 Generated with Claude Code

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