Skip to content
Merged
Original file line number Diff line number Diff line change
@@ -1,3 +1,53 @@
Property-based correctness sweep over every concrete `Prior` subclass — census
finding C3, the Phase 3 safety net of the priors/messages cleanup (hub
PyAutoFit#1331). Issue PyAutoFit#1497; **PR PyAutoFit#1499, merged 2026-08-18
as `21288bb` with all checks green** (unittest 3.12 + 3.13, docs). Closes the
loop the census opened: the nine bug fixes shipped in July (#1345/#1348) are
now locked as class-wide invariants, not just pointwise regressions.

- New `test_autofit/mapper/prior/test_prior_properties.py` — 134 parametrised
tests, five properties over Uniform/LogUniform/Gaussian/LogGaussian/
TruncatedGaussian (two parameterisations each): P1 `cdf(value_for(u)) ≈ u`
(1e-6); P2 normalisation three ways — `exp(log_prior_from_value +
log_normalisation())` integrates to 1 (locks the #1331 Option A contract),
the message physical-density path integrates to 1, and the generic
exponential-family pdf integrates to 1 for direct messages **including
`TruncatedNormalMessage`** (the exclusion that let #1331-04 survive); P3
finite-difference gradient parity between `log_prior_from_value` and the
physical log density (the #1266 sign-convention canary); P4 `with_limits`
constructs for every family with documented semantics (hard-limit families
hit the limits exactly; Gaussian families centre between limits, and the
LogGaussian keeps positive support per the #1331-01 fix); P5
`from_mode(m, V)` reproduces mean and variance at V≠1 discriminating points
for Normal and Gamma (locks #1331-D3 — V=1 cannot discriminate the
historical inverted formula).
- Also carried `bug/priors/11` §2 (see [[transformed-message-semantics-doc]]).
- Every property was numerically validated against clean main before being
committed as an assertion; NumPy-only per the house rule (EP-level coverage
belongs to the EP framework review, [[ep-framework-review]]).

**Key trap / new finding:** `TransformedMessage.logpdf`/`pdf` omit the
transform Jacobian — the generic path returns the *base-space* density at
physical coordinates (`UniformPrior(0,1).message.pdf` integrates to exactly
1/(2√π) ≈ 0.282, not 1; LogUniform(0.01,100) to 15.83) while `factor()` is
correct, and the #1334 module docstring claims the opposite. Same failure
shape as #1331-04. Filed as **PyAutoFit#1498** + intaken as
`draft/bug/priors/15_transformed_message_logpdf_jacobian.md`; the tests
assert the physical density via `factor()` (`physical_log_density` helper)
and cite #1498 at the site so they can be tightened once adjudicated.

**API notes for future sweeps:** `with_limits` is an instance method on
Uniform (abstract) but a classmethod on the Gaussian families;
`GammaMessage.value_for` deliberately raises (no inverse CDF) so infinite
supports are truncated at mean ± 40σ for integration; `prior.cdf` works on
every family via the message and round-trips exactly.

Sibling work filed the same day: the bundled 12+13 design issue
PyAutoFit#1500 (single-source density + Prior/Message collapse), which the
census retirement and this sweep were the groundwork for.

## Original prompt

# `@PyAutoFit` Add property-based correctness tests for every `Prior` subclass

Type: bug
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,38 @@
`TransformedMessage` semantics documentation — census finding C6,
`bug/priors/11`. Shipped in two halves, both now on PyAutoFit main:

- **§1 (asymmetric reversal convention)** — shipped 2026-07-10 via
PyAutoFit#1333 / PR#1334 as part of EP-review Phase 2: docstrings on
`_transform` / `_inverse_transform` in `messages/composed_transform.py`
plus the module docstring's worked `UniformPrior(0, 2).value_for(0.5)`
example and `autofit/graphical/README.md` §2 pointer.
- **§2 (`LinearShiftTransform` stores the reciprocal of the intuitive
scale)** — shipped 2026-08-18 riding PR PyAutoFit#1499 (merged `21288bb`,
the [[prior-property-tests]] PR): class docstring stating that
`shift`/`scale` describe physical space, the stored parent Jacobian is
`DiagonalMatrix(1/scale)` because `transform()` runs physical → base, and
`log_det = -log(scale)` follows; cites #1266 as the historical cost of
getting the reciprocal backwards.

The prompt's optional `physical_scale=` rename was **not** done — the audit's
own leaning ("docs alone are fine if the bijector migration stays on the
roadmap") applied, and the bijector question is now explicitly parked behind
the PyAutoFit#1500 design decision.

Note: the same PR's test work surfaced that `TransformedMessage.logpdf`
contradicts the #1334 module docstring (Jacobian never accumulated) — that is
PyAutoFit#1498 / `draft/bug/priors/15`, deliberately outside this doc task.

## Original prompt

# `@PyAutoFit` `TransformedMessage` reversal convention is undocumented foot-gun

Type: bug
Target: priors
Difficulty: large
Difficulty: small
Autonomy: supervised
Priority: normal
Status: HALF SHIPPED — edge 1 is documented, edge 2 is not (2026-08-09)
Status: HALF SHIPPED — edge 1 is documented, edge 2 is not (2026-08-09; Difficulty dropped large → small per the note below, 2026-08-18)

## 2026-08-09 — the reversal-convention half landed; the reciprocal half did not

Expand Down
4 changes: 3 additions & 1 deletion complete/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Token-light navigation over the finished-work records (schema:
only then grep a dated bucket. Curators: edit the band between the CURATED
markers; everything below GENERATED is rebuilt.

1023 records across 7 buckets.
1025 records across 7 buckets.

<!-- CURATED:START -->
## Highlights
Expand Down Expand Up @@ -111,6 +111,7 @@ _(curate hard-won records here — survives regeneration.)_
- [power-law-omega-convergence](2026/08/power-law-omega-convergence.md) — Bounded the fixed 20-term JAX PowerLaw omega recurrence across the packaged slope and ellipticity priors, meas…
- [pr-ci-for-own-test-suite](2026/08/pr-ci-for-own-test-suite.md)
- [pre-build-stages-untracked-wip](2026/08/pre-build-stages-untracked-wip.md) — CLOSED completed
- [prior-property-tests](2026/08/prior-property-tests.md)
- [prior-support-clipper](2026/08/prior-support-clipper.md) — Shipped `AbstractClipper` / `ClipperNone` / `ClipperPriorBox` in
- [profile-validation-resample-recovery](2026/08/profile-validation-resample-recovery.md) — Shipped the approved narrow compatibility fix: invalid profile construction is now both a direct `ValueError` …
- [purge-autocti-dataset-1d-overview](2026/08/purge-autocti-dataset-1d-overview.md) — no GitHub issue — the leftover from `autocti-util-dataset-export`, executed on direct human instruction "do th…
Expand Down Expand Up @@ -144,6 +145,7 @@ _(curate hard-won records here — survives regeneration.)_
- [tenant-firewall-hygiene-extras](2026/08/tenant-firewall-hygiene-extras.md) — auto-closed by the merge
- [test-mode-fit-exception-finalization](2026/08/test-mode-fit-exception-finalization.md) — `PYAUTO_TEST_MODE=1` no longer finalizes a model point whose reconstruction raises `FitException`; it substitu…
- [test-mode-samples-info-hook-contract](2026/08/test-mode-samples-info-hook-contract.md) — `NonLinearSearch._test_mode_samples_info()`'s docstring told subclasses
- [transformed-message-semantics-doc](2026/08/transformed-message-semantics-doc.md)
- [uniform-prior-bounds-numpy-path](2026/08/uniform-prior-bounds-numpy-path.md) — auto-closed by the merge's `Closes` line
- [version-skew-yank-awareness](2026/08/version-skew-yank-awareness.md)
- [wiki-provenance-restamp](2026/08/wiki-provenance-restamp.md)
Expand Down
21 changes: 11 additions & 10 deletions dashboard.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ Tasks only — the organism's health lives with the Heart (`/health`), not here.

| Where | Count |
|-------|------:|
| [In flight](#in-flight) (`active/`) | 3 |
| [Parked](#parked) (`parked.md`) | 1 |
| [In flight](#in-flight) (`active/`) | 5 |
| [Parked](#parked) (`parked.md`) | 3 |
| [Planned](#planned) (`planned.md`) | 7 |
| [Backlog](#backlog) (`draft/`) | 127 |
| [Backlog](#backlog) (`draft/`) | 124 |

Live on GitHub: [open issues](https://github.com/search?q=org%3APyAutoLabs+is%3Aissue+is%3Aopen&type=issues) · [open pull requests](https://github.com/search?q=org%3APyAutoLabs+is%3Apr+is%3Aopen&type=prs)

Expand Down Expand Up @@ -45,6 +45,8 @@ Live on GitHub: [open issues](https://github.com/search?q=org%3APyAutoLabs+is%3A

Issued — each has an open GitHub issue and usually a branch. The full record for each is in [`active.md`](active.md).

- [`@PyAutoFit` Refactor: each density should live in one place, not](active/12_single_source_density_refactor.md) — [issue #1500](https://github.com/PyAutoLabs/PyAutoFit/issues/1500)
- [`@PyAutoFit` Refactor: collapse the `Prior` / `Message` two-layer hierarchy](active/13_collapse_prior_and_message.md) — [issue #1500](https://github.com/PyAutoLabs/PyAutoFit/issues/1500)
- [PyAutoReduce validation: slacs1430+4105 ACS reduction vs trusted legacy dataset](active/pyautoreduce_slacs1430_acs_comparison.md)
- [Reconstructing a stored sample raises through `ignore_assertions=True`](active/to_instance_guard_gap.md) — [issue #1486](https://github.com/PyAutoLabs/PyAutoFit/issues/1486) — library-dev — WORKSPACE HALF SHIPPED; the PyAutoFit hardening (#1486) is what remains
- [Version-stamp sync to 2026.8.17.1 + release-sed guards](active/version_stamp_sync_and_release_sed_guards.md) — [issue #235](https://github.com/PyAutoLabs/PyAutoHands/issues/235) — pr-open (https://github.com/PyAutoLabs/PyAutoLens/pull/700)
Expand All @@ -54,8 +56,10 @@ Issued — each has an open GitHub issue and usually a branch. The full record f
Started or scoped, not currently in flight — resume by moving the row back to `active.md`. Full detail in [`parked.md`](parked.md).

<details>
<summary><b>1</b> task(s)</summary>
<summary><b>3</b> task(s)</summary>

- **single-source-density-design** — [issue #1500](https://github.com/PyAutoLabs/PyAutoFit/issues/1500)
- **prior-message-collapse-design** — [issue #1500](https://github.com/PyAutoLabs/PyAutoFit/issues/1500)
- **pyautoreduce-slacs1430-acs-comparison**

</details>
Expand All @@ -79,10 +83,10 @@ Scoped but not started; some are not yet prompt files. Full detail in [`planned.

## Backlog

**127** filed prompts, not started. Each section is sorted most-pickable first (priority, then size).
**124** filed prompts, not started. Each section is sorted most-pickable first (priority, then size).

<details>
<summary><b>bug</b> — 35</summary>
<summary><b>bug</b> — 32</summary>

- [EP hierarchical parent-scale collapse: cure the basin, or document the](draft/bug/autofit/ep_scale_collapse_basin_cure_or_caveat.md) — autofit · too-large · human-required · high
- [`NFWTruncatedSph.potential_2d_from`: MGE potential fails `grad(psi)=alpha` self-consistency](draft/bug/autogalaxy/nfw_truncated_potential_accuracy.md) — autogalaxy · too-large · supervised · high
Expand Down Expand Up @@ -110,11 +114,8 @@ Scoped but not started; some are not yet prompt files. Full detail in [`planned.
- [interferometer Delaunay pixelization — non-PD FitException in test-mode bypass](draft/bug/autolens/interferometer_delaunay_nonpd_fitexception.md) — autolens · medium · supervised · normal
- [JAX point-source smoke sentinel: point.py returns -1e99 instead of -83.38](draft/bug/autolens/jax_point_source_point_smoke_sentinel.md) — autolens · medium · supervised · normal
- [JIT cache not hit in modeling_visualization delaunay/rectangular scripts](draft/bug/autolens/jit_cache_not_hit_modeling_visualization.md) — autolens · medium · supervised · normal
- [`@PyAutoFit` Add property-based correctness tests for every `Prior` subclass](draft/bug/priors/09_prior_property_tests.md) — priors · large · supervised · normal
- [`@PyAutoFit` `TransformedMessage` reversal convention is undocumented foot-gun](draft/bug/priors/11_transformed_message_semantics_doc.md) — priors · large · supervised · normal
- [`@PyAutoFit` `TransformedMessage.logpdf`/`pdf` omit the transform Jacobian](draft/bug/priors/15_transformed_message_logpdf_jacobian.md) — priors · medium · supervised · normal
- [Resolve release-profile timeout scripts deliberately](draft/bug/health_fixes/release_timeout_policy.md) — health_fixes · too-large · supervised · normal
- [`@PyAutoFit` Refactor: each density should live in one place, not](draft/bug/priors/12_single_source_density_refactor.md) — priors · too-large · supervised · normal
- [`@PyAutoFit` Refactor: collapse the `Prior` / `Message` two-layer hierarchy](draft/bug/priors/13_collapse_prior_and_message.md) — priors · too-large · supervised · normal
- [`@PyAutoFit` Refactor: replace hand-rolled `AbstractDensityTransform` with `tfp.bijectors` / `numpyro.distributions.transforms`](draft/bug/priors/14_replace_transform_stack_with_bijectors.md) — priors · too-large · supervised · normal
- [Priors & Messages cleanup — tracker](draft/bug/priors/z_features.md) — priors · too-large · supervised · normal
- [`add_notebook_quotes` mistakes a code string literal's closing delimiter for a](draft/bug/hands/notebook_quotes_string_literal_closing_delimiter.md) — hands · small · safe · low
Expand Down
69 changes: 69 additions & 0 deletions draft/bug/priors/15_transformed_message_logpdf_jacobian.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# `@PyAutoFit` `TransformedMessage.logpdf`/`pdf` omit the transform Jacobian

Type: bug
Target: priors
Difficulty: medium
Autonomy: supervised
Priority: normal
Status: formalised — issue filed (PyAutoFit#1498), awaiting adjudication

Found 2026-08-18 while implementing the #1497 property sweep (prompt
`bug/priors/09`). Same failure *shape* as census finding A4 (#1331-04): a
generic-interface density path is silently wrong while the direct paths are
right.

## The finding

`TransformedMessage` inherits `MessageInterface.logpdf` (the natural-parameter
path). Its overrides forward `natural_parameters`, `calc_log_base_measure` and
`to_canonical_form` to the base message — `to_canonical_form` is
`@transform`-decorated, so the physical input is mapped to base coordinates —
but **no `log_det` change-of-variables term is ever added**. `logpdf(x)` at a
physical `x` therefore returns the *base-space* density at the mapped point,
not the physical density. `TransformedMessage.factor(x)` does it correctly
(`base.logpdf(transform(x)) + log_det`).

Reproduction on `main` @ `7d4d931` (exact numbers):

- `af.UniformPrior(0,1).message.logpdf(0.7)` = −1.05644 (physical density is
1.0 → expect 0.0); `factor(0.7)` = 0.0 ✓
- `∫ exp(logpdf)` over [0,1] = **0.282095 = 1/(2√π)**, not 1.0;
`∫ exp(factor)` = 1.000000 ✓
- `LogUniformPrior(0.01, 100)`: generic-path pdf integrates to **15.83**.

Every `TransformedMessage`-wrapped prior (Uniform, LogUniform, LogGaussian) is
affected. `NormalMessage` / `TruncatedNormalMessage` priors are not.
`value_for`, `cdf`, `log_prior_from_value` and `factor` are all verified
correct — sampling and MCMC/MLE log-priors are unaffected; the exposure is
anything treating `transformed_message.pdf()` as a physical density.

## Doc contradiction

The `composed_transform.py` module docstring (added by #1334) claims `logpdf`
accumulates the log-Jacobian. The code does not. Either the docstring states
the intended contract (then `logpdf` needs the `log_det` term) or `logpdf` is
deliberately base-space for EP message arithmetic (then the docstring and
`pdf()` are misleading and should say so).

## What adjudication needs

1. Inventory callers of `TransformedMessage.logpdf`/`pdf` vs `factor` —
especially whether any EP projection / `log_norm` path evaluates `logpdf`
on transformed messages at physical coordinates.
2. `logpdf_gradient` returns the base `log_likelihood` with a
Jacobian-corrected *gradient* (value base-space, gradient physical) — a
third convention to settle in the same pass.
3. Decide: add `log_det` to the generic path, or document base-space `logpdf`
as the contract and fix the module docstring + `pdf()`.

The #1497 property tests assert the physical density via `factor` and cite
#1498 at the site (`physical_log_density` helper in
`test_autofit/mapper/prior/test_prior_properties.py`); tighten them to
`logpdf` once resolved.

## Sequencing

Adjudicate alongside the parked single-source-density design (census C1/C4,
prompts `bug/priors/12`+`13`) — this is a fourth density-convention divergence
of exactly the kind that design exists to eliminate. The 12+13 design issue
should cite PyAutoFit#1498 as fresh evidence.
Loading
Loading