You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Phase 3 of the priors/messages cleanup (census: PyAutoMind/draft/research/autofit/priors_and_messages_math_audit.md, finding C3; prompt bug/priors/09; hub #1331). All nine confirmed bugs from the audit are fixed and merged (#1345, #1348), each locked by a pointwise regression test. This task adds the general property-based sweep over every Prior subclass — the tests that would have caught the original LogUniform sign bug (#1266), the with_limits crash, the Gamma from_mode inversion and the TruncatedNormal normalisation gap in one stroke — so the next latent math bug of this shape cannot survive. It also folds in the remaining small half of bug/priors/11: the LinearShiftTransform class docstring (the reversal-convention half shipped via #1334).
Plan
Add a parametrised property-test module sweeping every concrete Prior subclass (Uniform, LogUniform, Gaussian, LogGaussian, TruncatedGaussian; ≥2 parameterisations each).
Five properties: inverse-CDF round-trip; normalised pdf integrates to 1 (locking the Priors & messages: 9 confirmed bugs — guidance wanted on 5 decisions #1331 Option A drop-constants + log_normalisation() contract, and extending check_dist_norm-style coverage to TruncatedNormalMessage and transformed priors — the exact gap that hid the log-partition bug); log-prior gradient matches the message-density gradient by finite differences (constants cancel — the sign-convention canary); with_limits round-trip for every family; from_mode(m, V) mean/variance invariants for NormalMessage and GammaMessage with V≠1 discriminating points.
NumPy-only at the library level (house rule; EP-level integration coverage was Phase 3 of the EP framework review and complements this).
Add the LinearShiftTransform docstring: shift/scale are physical-space parameters, the stored parent Jacobian is 1/scale because the transform runs physical → base, and the log_det sign follows.
Expect all tests to PASS on current main; any failure is a new finding to file separately, not to bundle here.
Detailed implementation plan
Affected Repositories
PyAutoFit (primary)
Branch Survey
Repository
Current Branch
Dirty?
/workspace/pyautofit
main @ 7d4d931 (shallow clone, cloud session)
clean
PyAutoFit is also claimed by two in-flight tasks (stored-sample-reconstruction-guard → autofit/non_linear/samples/; version-stamp-sync-guards → autofit/__init__.py) — file-disjoint from this task's surface (test_autofit/mapper/prior/, autofit/messages/transform.py), proceeding under the documented disjoint-override convention.
Suggested branch:feature/prior-property-tests
Implementation Steps
New test_autofit/mapper/prior/test_prior_properties.py:
P1 — inverse CDF:prior.cdf(prior.value_for(u)) ≈ u for u ∈ {0.01, 0.1, 0.3, 0.5, 0.7, 0.9, 0.99}, abs=1e-6.
P2 — normalisation:∫ exp(log_prior_from_value(x) + log_normalisation()) dx ≈ 1 over the support (infinite limits truncated via value_for(1e-9) / value_for(1-1e-9); scipy.integrate.quad, abs=1e-3). Also ∫ message.pdf ≈ 1 where the generic exponential-family path exists — extends check_dist_norm to TruncatedNormalMessage and TransformedMessage-wrapped priors (the Priors & messages: 9 confirmed bugs — guidance wanted on 5 decisions #1331-04 gap).
P3 — gradient consistency: central finite difference of log_prior_from_value matches central finite difference of message.logpdf at x = value_for(u), u ∈ {0.3, 0.5, 0.7} (rtol=1e-2, atol=1e-3); constants cancel, so this catches exactly the sign/convention drift class of fix: log_prior_from_value sign-convention bug across Prior subclasses #1266.
P4 — with_limits: constructs for every family (regression breadth for Priors & messages: 9 confirmed bugs — guidance wanted on 5 decisions #1331-01); for limit-respecting families value_for(1e-6)/value_for(1-1e-6) lie within [lower, upper]; LogGaussianPrior.with_limits asserts construction + positive support only (the shipped fix drops the kwargs by design).
Click to expand starting prompt (bug/priors/09_prior_property_tests.md)
@PyAutoFit Add property-based correctness tests for every Prior subclass
Type: bug
Target: priors
Difficulty: large
Autonomy: supervised
Priority: normal
Status: formalised
Found during the priors/messages audit (see PyAutoPrompt/autofit/priors_and_messages_math_audit.md, finding C3).
Prerequisite: Prompts 01-08 should be acked (and ideally merged)
first. The point of these tests is to lock in the fixes — adding them
before the fixes would just produce a long list of red tests with no
clear action.
Problem
The audit found three real bugs (LogGaussianPrior.with_limits crash, GammaMessage.from_mode wrong formula, TruncatedNormalMessage pdf
not normalised) that would each have been caught by a single
property-based test. None of them were caught because the existing
test suite is hand-rolled per-class and covers each method in
isolation.
The original LogUniformPrior sign-convention bug (e95295b83) is
in the same category — it would have been caught by a single test:
"for every prior, the analytic gradient of log_prior_from_value
matches a finite-difference gradient".
check_dist_norm(dist) — uses scipy.integrate.quad to verify pdf integrates to 1. Run on NormalMessage, BetaMessage, GammaMessage, LogNormalMessage only. Not run on TruncatedNormalMessage, LogGaussianPrior, any Prior subclass,
or any TransformedMessage-wrapped distribution. That's why
prompt 04's bug survived.
check_log_normalisation(ms) — verifies the product-of-messages
log normalisation matches numerical integration.
check_numerical_gradient_hessians(message, x=None) — verifies
analytic gradient and Hessian match finite differences. Same limited
coverage as above.
So the patterns are there; they just don't sweep every prior.
Proposed scope
Add test_autofit/mapper/prior/test_prior_properties.py (and/or test_autofit/messages/test_message_properties.py) with the five
properties above. Parametrise over every concrete subclass.
For each property, pick the tolerance carefully:
Integrals: abs=1e-3 is enough to catch all the audit findings;
tighter would slow CI.
Inverse-CDF round-trip: abs=1e-6 works for double precision.
Fable verdict (2026-07-08, PyAutoFit main @ 0f26ff2; PyAutoFit#1330)
Verdict: still wanted, unchanged — schedule after 01–08 land.
Coverage gap re-verified: check_dist_norm-style sweeps in test_autofit/graphical/functionality/test_messages.py still exclude TruncatedNormalMessage, LogGaussianPrior, prior subclasses and
transformed messages — which is why 04 survived. Keep library-level property
tests numpy-only (house rule); EP-level integration coverage is Phase 3 of research/graphical_ep/ep_framework_review.md and complements, not
replaces, this.
(Full prompt with the test-sketch code block: PyAutoMind/active/09_prior_property_tests.md. Folded-in second half: PyAutoMind/draft/bug/priors/11_transformed_message_semantics_doc.md §2 only — the LinearShiftTransform docstring.)
Overview
Phase 3 of the priors/messages cleanup (census:
PyAutoMind/draft/research/autofit/priors_and_messages_math_audit.md, finding C3; promptbug/priors/09; hub #1331). All nine confirmed bugs from the audit are fixed and merged (#1345, #1348), each locked by a pointwise regression test. This task adds the general property-based sweep over everyPriorsubclass — the tests that would have caught the original LogUniform sign bug (#1266), thewith_limitscrash, the Gammafrom_modeinversion and the TruncatedNormal normalisation gap in one stroke — so the next latent math bug of this shape cannot survive. It also folds in the remaining small half ofbug/priors/11: theLinearShiftTransformclass docstring (the reversal-convention half shipped via #1334).Plan
Priorsubclass (Uniform, LogUniform, Gaussian, LogGaussian, TruncatedGaussian; ≥2 parameterisations each).log_normalisation()contract, and extendingcheck_dist_norm-style coverage toTruncatedNormalMessageand transformed priors — the exact gap that hid the log-partition bug); log-prior gradient matches the message-density gradient by finite differences (constants cancel — the sign-convention canary);with_limitsround-trip for every family;from_mode(m, V)mean/variance invariants forNormalMessageandGammaMessagewith V≠1 discriminating points.LinearShiftTransformdocstring:shift/scaleare physical-space parameters, the stored parent Jacobian is1/scalebecause the transform runs physical → base, and thelog_detsign follows.Detailed implementation plan
Affected Repositories
Branch Survey
7d4d931(shallow clone, cloud session)PyAutoFit is also claimed by two in-flight tasks (
stored-sample-reconstruction-guard→autofit/non_linear/samples/;version-stamp-sync-guards→autofit/__init__.py) — file-disjoint from this task's surface (test_autofit/mapper/prior/,autofit/messages/transform.py), proceeding under the documented disjoint-override convention.Suggested branch:
feature/prior-property-testsImplementation Steps
test_autofit/mapper/prior/test_prior_properties.py:all_priors()fixture list:UniformPrior(0,1),UniformPrior(-3,7.5),GaussianPrior(0,1),GaussianPrior(2.5,0.3),LogUniformPrior(1e-2,1e2),LogGaussianPrior(0,1),LogGaussianPrior(1,0.5),TruncatedGaussianPrior(0,1,-2,2),TruncatedGaussianPrior(1,0.5,0,3). Skip containers/point-masses (TuplePrior,Constant,DeferredArgument).prior.cdf(prior.value_for(u)) ≈ uforu ∈ {0.01, 0.1, 0.3, 0.5, 0.7, 0.9, 0.99},abs=1e-6.∫ exp(log_prior_from_value(x) + log_normalisation()) dx ≈ 1over the support (infinite limits truncated viavalue_for(1e-9)/value_for(1-1e-9);scipy.integrate.quad,abs=1e-3). Also∫ message.pdf ≈ 1where the generic exponential-family path exists — extendscheck_dist_normtoTruncatedNormalMessageandTransformedMessage-wrapped priors (the Priors & messages: 9 confirmed bugs — guidance wanted on 5 decisions #1331-04 gap).log_prior_from_valuematches central finite difference ofmessage.logpdfatx = value_for(u),u ∈ {0.3, 0.5, 0.7}(rtol=1e-2, atol=1e-3); constants cancel, so this catches exactly the sign/convention drift class of fix: log_prior_from_value sign-convention bug across Prior subclasses #1266.with_limits: constructs for every family (regression breadth for Priors & messages: 9 confirmed bugs — guidance wanted on 5 decisions #1331-01); for limit-respecting familiesvalue_for(1e-6)/value_for(1-1e-6)lie within[lower, upper];LogGaussianPrior.with_limitsasserts construction + positive support only (the shipped fix drops the kwargs by design).from_mode:NormalMessage.from_mode(m, V)andGammaMessage.from_mode(m, V)reproduce.mean ≈ m,.variance ≈ Vat V≠1 discriminating points (e.g. (2, 0.25), (2, 4.0)) — locks the Priors & messages: 9 confirmed bugs — guidance wanted on 5 decisions #1331-D3 mean+variance invariant.autofit/messages/transform.py—LinearShiftTransformclass docstring (bug/priors/11 §2): kwargs describe physical space; stored parent Jacobian isDiagonalMatrix(1/scale)becausetransform()maps physical → base;log_det = -log(scale)accordingly.pytest test_autofit/mapper/prior/ test_autofit/messages/ test_autofit/graphical/functionality/test_messages.py.Key Files
test_autofit/mapper/prior/test_prior_properties.py— new property sweepautofit/messages/transform.py—LinearShiftTransformdocstringtest_autofit/graphical/functionality/test_messages.py— pattern source (check_dist_norm, tolerance precedents); unchangedautofit/mapper/prior/abstract.py—log_normalisation()contract (P2's anchor)Original Prompt
Click to expand starting prompt (bug/priors/09_prior_property_tests.md)
@PyAutoFitAdd property-based correctness tests for everyPriorsubclassType: bug
Target: priors
Difficulty: large
Autonomy: supervised
Priority: normal
Status: formalised
Found during the priors/messages audit (see
PyAutoPrompt/autofit/priors_and_messages_math_audit.md, finding C3).Problem
The audit found three real bugs (
LogGaussianPrior.with_limitscrash,GammaMessage.from_modewrong formula,TruncatedNormalMessagepdfnot normalised) that would each have been caught by a single
property-based test. None of them were caught because the existing
test suite is hand-rolled per-class and covers each method in
isolation.
The original
LogUniformPriorsign-convention bug (e95295b83) isin the same category — it would have been caught by a single test:
"for every prior, the analytic gradient of
log_prior_from_valuematches a finite-difference gradient".
Wider context — what exists already
@PyAutoFit/test_autofit/graphical/functionality/test_messages.py:check_dist_norm(dist)— usesscipy.integrate.quadto verifypdfintegrates to 1. Run onNormalMessage,BetaMessage,GammaMessage,LogNormalMessageonly. Not run onTruncatedNormalMessage,LogGaussianPrior, anyPriorsubclass,or any
TransformedMessage-wrapped distribution. That's whyprompt 04's bug survived.
check_log_normalisation(ms)— verifies the product-of-messageslog normalisation matches numerical integration.
check_numerical_gradient_hessians(message, x=None)— verifiesanalytic gradient and Hessian match finite differences. Same limited
coverage as above.
So the patterns are there; they just don't sweep every prior.
Proposed scope
Add
test_autofit/mapper/prior/test_prior_properties.py(and/ortest_autofit/messages/test_message_properties.py) with the fiveproperties above. Parametrise over every concrete subclass.
For each property, pick the tolerance carefully:
abs=1e-3is enough to catch all the audit findings;tighter would slow CI.
abs=1e-6works for double precision.rtol=1e-2, atol=1e-3(existingcheck_numerical_gradient_hessiansuses these).Fable verdict (2026-07-08, PyAutoFit main @ 0f26ff2; PyAutoFit#1330)
Verdict: still wanted, unchanged — schedule after 01–08 land.
Coverage gap re-verified:
check_dist_norm-style sweeps intest_autofit/graphical/functionality/test_messages.pystill excludeTruncatedNormalMessage,LogGaussianPrior, prior subclasses andtransformed messages — which is why 04 survived. Keep library-level property
tests numpy-only (house rule); EP-level integration coverage is Phase 3 of
research/graphical_ep/ep_framework_review.mdand complements, notreplaces, this.
(Full prompt with the test-sketch code block:
PyAutoMind/active/09_prior_property_tests.md. Folded-in second half:PyAutoMind/draft/bug/priors/11_transformed_message_semantics_doc.md§2 only — theLinearShiftTransformdocstring.)