bayesmith 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (117) hide show
  1. bayesmith-0.1.0/.gitignore +14 -0
  2. bayesmith-0.1.0/CHANGELOG.md +32 -0
  3. bayesmith-0.1.0/LICENSE +21 -0
  4. bayesmith-0.1.0/PKG-INFO +87 -0
  5. bayesmith-0.1.0/README.md +66 -0
  6. bayesmith-0.1.0/docs/migration/README.md +100 -0
  7. bayesmith-0.1.0/docs/migration/conditioning.md +74 -0
  8. bayesmith-0.1.0/docs/migration/gls.md +81 -0
  9. bayesmith-0.1.0/docs/migration/identifiability.md +86 -0
  10. bayesmith-0.1.0/docs/migration/linear.md +236 -0
  11. bayesmith-0.1.0/docs/migration/noise.md +191 -0
  12. bayesmith-0.1.0/docs/migration/numpyro_bridge.md +138 -0
  13. bayesmith-0.1.0/docs/migration/parameters.md +140 -0
  14. bayesmith-0.1.0/docs/migration/plan.md +164 -0
  15. bayesmith-0.1.0/docs/migration/priors.md +89 -0
  16. bayesmith-0.1.0/docs/migration/sensitivity.md +100 -0
  17. bayesmith-0.1.0/docs/migration/sqrtinfo.md +67 -0
  18. bayesmith-0.1.0/docs/migration/uncertainty.md +85 -0
  19. bayesmith-0.1.0/pyproject.toml +136 -0
  20. bayesmith-0.1.0/src/bayesmith/__init__.py +173 -0
  21. bayesmith-0.1.0/src/bayesmith/bridge/__init__.py +1 -0
  22. bayesmith-0.1.0/src/bayesmith/bridge/numpyro_bridge.py +205 -0
  23. bayesmith-0.1.0/src/bayesmith/diagnose/__init__.py +50 -0
  24. bayesmith-0.1.0/src/bayesmith/diagnose/identifiability.py +398 -0
  25. bayesmith-0.1.0/src/bayesmith/diagnose/local.py +332 -0
  26. bayesmith-0.1.0/src/bayesmith/diagnose/priors.py +422 -0
  27. bayesmith-0.1.0/src/bayesmith/diagnose/sensitivity.py +816 -0
  28. bayesmith-0.1.0/src/bayesmith/dispatch/__init__.py +24 -0
  29. bayesmith-0.1.0/src/bayesmith/dispatch/classify.py +623 -0
  30. bayesmith-0.1.0/src/bayesmith/dispatch/execute.py +834 -0
  31. bayesmith-0.1.0/src/bayesmith/dispatch/plan.py +843 -0
  32. bayesmith-0.1.0/src/bayesmith/dispatch/streaming.py +237 -0
  33. bayesmith-0.1.0/src/bayesmith/errors.py +85 -0
  34. bayesmith-0.1.0/src/bayesmith/evidence/__init__.py +56 -0
  35. bayesmith-0.1.0/src/bayesmith/evidence/campaign.py +408 -0
  36. bayesmith-0.1.0/src/bayesmith/evidence/compress.py +384 -0
  37. bayesmith-0.1.0/src/bayesmith/evidence/diagnostics.py +177 -0
  38. bayesmith-0.1.0/src/bayesmith/evidence/factorize.py +281 -0
  39. bayesmith-0.1.0/src/bayesmith/evidence/sqrtinfo.py +403 -0
  40. bayesmith-0.1.0/src/bayesmith/exact/__init__.py +53 -0
  41. bayesmith-0.1.0/src/bayesmith/exact/block.py +456 -0
  42. bayesmith-0.1.0/src/bayesmith/exact/conditioning.py +118 -0
  43. bayesmith-0.1.0/src/bayesmith/exact/correct.py +344 -0
  44. bayesmith-0.1.0/src/bayesmith/exact/discrete.py +246 -0
  45. bayesmith-0.1.0/src/bayesmith/exact/fisher.py +500 -0
  46. bayesmith-0.1.0/src/bayesmith/exact/gaussian.py +548 -0
  47. bayesmith-0.1.0/src/bayesmith/exact/gibbs.py +416 -0
  48. bayesmith-0.1.0/src/bayesmith/exact/gls.py +646 -0
  49. bayesmith-0.1.0/src/bayesmith/exact/linearity.py +809 -0
  50. bayesmith-0.1.0/src/bayesmith/exact/precision.py +451 -0
  51. bayesmith-0.1.0/src/bayesmith/exact/solve.py +537 -0
  52. bayesmith-0.1.0/src/bayesmith/graph/__init__.py +1 -0
  53. bayesmith-0.1.0/src/bayesmith/graph/evaluate.py +177 -0
  54. bayesmith-0.1.0/src/bayesmith/graph/graph.py +151 -0
  55. bayesmith-0.1.0/src/bayesmith/graph/nodes.py +180 -0
  56. bayesmith-0.1.0/src/bayesmith/graph/trace.py +271 -0
  57. bayesmith-0.1.0/tests/__init__.py +0 -0
  58. bayesmith-0.1.0/tests/crosscheck/__init__.py +0 -0
  59. bayesmith-0.1.0/tests/crosscheck/conftest.py +53 -0
  60. bayesmith-0.1.0/tests/crosscheck/test_bridge.py +345 -0
  61. bayesmith-0.1.0/tests/crosscheck/test_conditioning.py +145 -0
  62. bayesmith-0.1.0/tests/crosscheck/test_diagnose_identifiability.py +217 -0
  63. bayesmith-0.1.0/tests/crosscheck/test_diagnose_jeffreys.py +214 -0
  64. bayesmith-0.1.0/tests/crosscheck/test_diagnose_sensitivity.py +270 -0
  65. bayesmith-0.1.0/tests/crosscheck/test_dispatch.py +546 -0
  66. bayesmith-0.1.0/tests/crosscheck/test_gaussian.py +584 -0
  67. bayesmith-0.1.0/tests/crosscheck/test_linear.py +816 -0
  68. bayesmith-0.1.0/tests/crosscheck/test_noise_logdet.py +333 -0
  69. bayesmith-0.1.0/tests/crosscheck/test_parameters.py +475 -0
  70. bayesmith-0.1.0/tests/crosscheck/test_sqrtinfo_agrees.py +177 -0
  71. bayesmith-0.1.0/tests/diagnose/__init__.py +0 -0
  72. bayesmith-0.1.0/tests/diagnose/models.py +370 -0
  73. bayesmith-0.1.0/tests/diagnose/test_identifiability.py +865 -0
  74. bayesmith-0.1.0/tests/diagnose/test_jeffreys.py +496 -0
  75. bayesmith-0.1.0/tests/diagnose/test_prior_sensitivity.py +605 -0
  76. bayesmith-0.1.0/tests/dispatch/__init__.py +0 -0
  77. bayesmith-0.1.0/tests/dispatch/test_acceptance.py +1240 -0
  78. bayesmith-0.1.0/tests/dispatch/test_chain_diagnostics.py +254 -0
  79. bayesmith-0.1.0/tests/dispatch/test_classify.py +656 -0
  80. bayesmith-0.1.0/tests/dispatch/test_dispatch_entry.py +1026 -0
  81. bayesmith-0.1.0/tests/dispatch/test_plan.py +734 -0
  82. bayesmith-0.1.0/tests/dispatch/test_streaming.py +390 -0
  83. bayesmith-0.1.0/tests/evidence/__init__.py +0 -0
  84. bayesmith-0.1.0/tests/evidence/test_campaign.py +416 -0
  85. bayesmith-0.1.0/tests/evidence/test_compress.py +239 -0
  86. bayesmith-0.1.0/tests/evidence/test_diagnostics.py +266 -0
  87. bayesmith-0.1.0/tests/evidence/test_epoch_fold.py +311 -0
  88. bayesmith-0.1.0/tests/evidence/test_factorize.py +469 -0
  89. bayesmith-0.1.0/tests/evidence/test_sqrtinfo.py +448 -0
  90. bayesmith-0.1.0/tests/evidence/test_streaming_equals_batch.py +198 -0
  91. bayesmith-0.1.0/tests/exact/__init__.py +0 -0
  92. bayesmith-0.1.0/tests/exact/models.py +1773 -0
  93. bayesmith-0.1.0/tests/exact/oracle.py +145 -0
  94. bayesmith-0.1.0/tests/exact/test_block.py +357 -0
  95. bayesmith-0.1.0/tests/exact/test_boundaries.py +394 -0
  96. bayesmith-0.1.0/tests/exact/test_conditioning.py +123 -0
  97. bayesmith-0.1.0/tests/exact/test_correct.py +790 -0
  98. bayesmith-0.1.0/tests/exact/test_discrete.py +267 -0
  99. bayesmith-0.1.0/tests/exact/test_extremes.py +309 -0
  100. bayesmith-0.1.0/tests/exact/test_fisher.py +1024 -0
  101. bayesmith-0.1.0/tests/exact/test_gaussian.py +564 -0
  102. bayesmith-0.1.0/tests/exact/test_gibbs.py +476 -0
  103. bayesmith-0.1.0/tests/exact/test_gls.py +1119 -0
  104. bayesmith-0.1.0/tests/exact/test_linearity.py +818 -0
  105. bayesmith-0.1.0/tests/exact/test_precision.py +1057 -0
  106. bayesmith-0.1.0/tests/exact/test_solve.py +1115 -0
  107. bayesmith-0.1.0/tests/test_bridge.py +141 -0
  108. bayesmith-0.1.0/tests/test_conjugate_oracle.py +82 -0
  109. bayesmith-0.1.0/tests/test_degenerate_graphs.py +121 -0
  110. bayesmith-0.1.0/tests/test_errors.py +80 -0
  111. bayesmith-0.1.0/tests/test_evaluate.py +95 -0
  112. bayesmith-0.1.0/tests/test_graph.py +138 -0
  113. bayesmith-0.1.0/tests/test_log_joint.py +82 -0
  114. bayesmith-0.1.0/tests/test_nodes.py +275 -0
  115. bayesmith-0.1.0/tests/test_plates.py +145 -0
  116. bayesmith-0.1.0/tests/test_public_api.py +347 -0
  117. bayesmith-0.1.0/tests/test_trace.py +264 -0
@@ -0,0 +1,14 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ *.egg-info/
4
+ build/
5
+ dist/
6
+ .venv/
7
+ venv/
8
+ .pytest_cache/
9
+ .hypothesis/
10
+ .coverage
11
+ htmlcov/
12
+ .mypy_cache/
13
+ .ruff_cache/
14
+ .DS_Store
@@ -0,0 +1,32 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0 — 2026-08-26
4
+
5
+ First release. Published so that downstream packages can declare a dependency
6
+ on it by name rather than by path; until now the version was `0.0.0` and the
7
+ package was not on any index.
8
+
9
+ ### What it does
10
+
11
+ - **Graph core.** Deterministic and probabilistic nodes, plates, and the joint
12
+ log-density assembled from them.
13
+ - **NumPyro bridge.** Any graph is runnable through NUTS, which is also the
14
+ oracle every exact path is verified against.
15
+ - **Structural dispatch** with the linear-Gaussian exact solves: conjugate,
16
+ Wiener, GCR and GLS, selected per subgraph, with declarations such as
17
+ `linear_in` checked at three scales rather than trusted.
18
+ - **Exact enumeration of discrete latents** (`bayesmith.exact.discrete`),
19
+ reading the `Discrete(n)` support declaration. Not yet dispatcher-selected —
20
+ see the README's Status section.
21
+ - **Streaming evidence** as square-root information factors, combined exactly
22
+ across epochs.
23
+ - **Graph diagnostics**: identifiability, prior sensitivity, linearity.
24
+ - **Per-parameter convergence diagnostics** on chain paths: split r-hat and ESS
25
+ per coordinate, gated ESS-first because a fixed r-hat threshold is not a
26
+ well-posed test — see `r_hat_ceiling`'s docstring for the measurements.
27
+
28
+ ### Known limits
29
+
30
+ - Forward-backward for chain-structured discrete latents is not implemented.
31
+ - Discrete enumeration is not yet chosen by the dispatcher.
32
+ - The API may move; this is an alpha release.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Zheng Zhang
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,87 @@
1
+ Metadata-Version: 2.5
2
+ Name: bayesmith
3
+ Version: 0.1.0
4
+ Summary: A graph of operators is a Bayesian model; its structure chooses the inference.
5
+ Project-URL: Repository, https://github.com/zzhang0123/bayesmith
6
+ Author: Zheng Zhang
7
+ License: MIT
8
+ License-File: LICENSE
9
+ Classifier: Development Status :: 3 - Alpha
10
+ Classifier: Intended Audience :: Science/Research
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Programming Language :: Python :: 3.11
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: Topic :: Scientific/Engineering :: Mathematics
15
+ Requires-Python: >=3.11
16
+ Requires-Dist: equinox>=0.13
17
+ Requires-Dist: jax>=0.5
18
+ Requires-Dist: numpy
19
+ Requires-Dist: numpyro>=0.15
20
+ Description-Content-Type: text/markdown
21
+
22
+ # bayesmith
23
+
24
+ A Bayesian model is a graph of operators. Deterministic operators propagate
25
+ dependence; probabilistic operators contribute a conditional density. Together
26
+ they *are* the joint distribution.
27
+
28
+ bayesmith makes that graph **explicit and inspectable**, and then uses its
29
+ structure to choose how the model is fitted — an exact solve where the structure
30
+ permits one, NUTS where it does not.
31
+
32
+ ```
33
+ block 0 {x} Wiener exact (linear_in checked, 3 scales)
34
+ block 1 {z} enumerate 4 states
35
+ block 2 {sigma, nu} NUTS (numpyro) no exact structure found
36
+ ```
37
+
38
+ The model tells you how it will be fitted, before it is fitted.
39
+
40
+ ## What bayesmith is not
41
+
42
+ It is **not another probabilistic programming language**. Distributions, MCMC
43
+ kernels, variational inference and transforms all come from
44
+ [NumPyro](https://github.com/pyro-ppl/numpyro). bayesmith is the dispatch layer
45
+ above them, and every line in it must answer *"why can NumPyro not do this?"*
46
+
47
+ What it owns, because a trace-based PPL structurally cannot:
48
+
49
+ - **Structural exact inference** — conjugate / Wiener / GCR / GLS solves, and
50
+ exact enumeration of discrete latents, selected per subgraph.
51
+ - **Streaming evidence** — square-root information factors combined exactly
52
+ across datasets and observing epochs.
53
+ - **Diagnostics on the graph** — identifiability, prior sensitivity, and
54
+ linearity checking of the declarations the dispatcher relies on.
55
+
56
+ Declarations such as `linear_in` are *claims about the model*, not hints, so
57
+ they are **checked rather than trusted**: a node declared linear is probed at
58
+ three scales before any exact solve is allowed to use it.
59
+
60
+ ## Status
61
+
62
+ **0.1.0, the first release.** Published so other packages can depend on it by
63
+ name. Alpha in the classifier's sense: the API may still move.
64
+
65
+ Implemented and tested, 1163 tests: the graph core with plates and joint
66
+ log-density; the NumPyro bridge, so any graph is runnable through NUTS;
67
+ structural dispatch with the linear-Gaussian exact solves; exact enumeration of
68
+ discrete latents; streaming evidence as square-root information factors; and
69
+ graph diagnostics for identifiability, prior sensitivity and linearity.
70
+
71
+ **Two things the page above describes that 0.1.0 does not do yet.** Stated here
72
+ because a front page is a claim, and finding out afterwards is worse than
73
+ reading it now:
74
+
75
+ - **Enumeration is not dispatcher-selected.** `bayesmith.exact.discrete`
76
+ computes the exact marginal and the posterior marginals over declared
77
+ discrete latents, and reads the `Discrete(n)` support declaration to do it —
78
+ but `classify` does not yet route a discrete subgraph to it. The
79
+ `block 1 {z} enumerate 4 states` line above is therefore a design sketch
80
+ rather than a transcript; call the module directly.
81
+ - **Forward-backward is not implemented**, so a chain of `T` discrete latents
82
+ costs `n ** T` by enumeration rather than `T * n**2`. Enumeration refuses
83
+ past a budget rather than hanging, and names the count it would have visited.
84
+
85
+ ## License
86
+
87
+ MIT
@@ -0,0 +1,66 @@
1
+ # bayesmith
2
+
3
+ A Bayesian model is a graph of operators. Deterministic operators propagate
4
+ dependence; probabilistic operators contribute a conditional density. Together
5
+ they *are* the joint distribution.
6
+
7
+ bayesmith makes that graph **explicit and inspectable**, and then uses its
8
+ structure to choose how the model is fitted — an exact solve where the structure
9
+ permits one, NUTS where it does not.
10
+
11
+ ```
12
+ block 0 {x} Wiener exact (linear_in checked, 3 scales)
13
+ block 1 {z} enumerate 4 states
14
+ block 2 {sigma, nu} NUTS (numpyro) no exact structure found
15
+ ```
16
+
17
+ The model tells you how it will be fitted, before it is fitted.
18
+
19
+ ## What bayesmith is not
20
+
21
+ It is **not another probabilistic programming language**. Distributions, MCMC
22
+ kernels, variational inference and transforms all come from
23
+ [NumPyro](https://github.com/pyro-ppl/numpyro). bayesmith is the dispatch layer
24
+ above them, and every line in it must answer *"why can NumPyro not do this?"*
25
+
26
+ What it owns, because a trace-based PPL structurally cannot:
27
+
28
+ - **Structural exact inference** — conjugate / Wiener / GCR / GLS solves, and
29
+ exact enumeration of discrete latents, selected per subgraph.
30
+ - **Streaming evidence** — square-root information factors combined exactly
31
+ across datasets and observing epochs.
32
+ - **Diagnostics on the graph** — identifiability, prior sensitivity, and
33
+ linearity checking of the declarations the dispatcher relies on.
34
+
35
+ Declarations such as `linear_in` are *claims about the model*, not hints, so
36
+ they are **checked rather than trusted**: a node declared linear is probed at
37
+ three scales before any exact solve is allowed to use it.
38
+
39
+ ## Status
40
+
41
+ **0.1.0, the first release.** Published so other packages can depend on it by
42
+ name. Alpha in the classifier's sense: the API may still move.
43
+
44
+ Implemented and tested, 1163 tests: the graph core with plates and joint
45
+ log-density; the NumPyro bridge, so any graph is runnable through NUTS;
46
+ structural dispatch with the linear-Gaussian exact solves; exact enumeration of
47
+ discrete latents; streaming evidence as square-root information factors; and
48
+ graph diagnostics for identifiability, prior sensitivity and linearity.
49
+
50
+ **Two things the page above describes that 0.1.0 does not do yet.** Stated here
51
+ because a front page is a claim, and finding out afterwards is worse than
52
+ reading it now:
53
+
54
+ - **Enumeration is not dispatcher-selected.** `bayesmith.exact.discrete`
55
+ computes the exact marginal and the posterior marginals over declared
56
+ discrete latents, and reads the `Discrete(n)` support declaration to do it —
57
+ but `classify` does not yet route a discrete subgraph to it. The
58
+ `block 1 {z} enumerate 4 states` line above is therefore a design sketch
59
+ rather than a transcript; call the module directly.
60
+ - **Forward-backward is not implemented**, so a chain of `T` discrete latents
61
+ costs `n ** T` by enumeration rather than `T * n**2`. Enumeration refuses
62
+ past a budget rather than hanging, and names the count it would have visited.
63
+
64
+ ## License
65
+
66
+ MIT
@@ -0,0 +1,100 @@
1
+ # Cross-check records — and what §六 is still waiting for
2
+
3
+ Migration spec §二 requires one page per module and gates **all** of §六
4
+ (rheplicant's wind-down) on step 6: *"全部通过后,才动 rheplicant 侧对应模
5
+ 块"*. This directory is where those pages live. It did not exist until
6
+ 2026-08-25, which is why §六 had never started.
7
+
8
+ **Do not read the table below as authority.** It is prose, and prose has no
9
+ test. `tests/test_migration_records.py` is the authority: it
10
+ derives the module list from the spec's own §四 tables, the page list from
11
+ this directory, and the test list from `tests/crosscheck/`, and fails when
12
+ they disagree. If the table and that test ever disagree, the test is right.
13
+
14
+ | §四 row | module | cross-check test | page |
15
+ |---|---|---|---|
16
+ | 4.1 | `linear.py` → `exact/block,linearity,solve` | `test_linear.py` | ✅ |
17
+ | 4.1* | `conditioning.py` → `exact/conditioning` | `test_conditioning.py` | ✅ |
18
+ | 4.1 | `gls.py` → `exact/gls` | `test_noise_logdet.py` (B1 half) | ✅ |
19
+ | 4.1 | `uncertainty.py` (Fisher) → `exact/fisher` | `test_noise_logdet.py` | ✅ |
20
+ | 4.1 | `likelihood.py`/`noise.py` → `exact/gaussian` | `test_gaussian.py` | ✅ `noise.md` |
21
+ | 4.2 | `parameters.py` → node declarations | `test_parameters.py` | ✅ |
22
+ | 4.2 | `noise.py` → probabilistic nodes | `test_gaussian.py` | ✅ `noise.md` |
23
+ | 4.2 | `plan.py`+`engines.py` → dispatch | `test_dispatch.py` | ✅ `plan.md` |
24
+ | 4.2 | `identifiability.py` → `diagnose/` | `test_diagnose_identifiability.py` | ✅ |
25
+ | 4.2 | `sensitivity.py` → `diagnose/` | `test_diagnose_sensitivity.py` | ✅ |
26
+ | 4.2 | `priors.py` → `diagnose/` | `test_diagnose_jeffreys.py` | ✅ |
27
+ | 4.2 | `numpyro_bridge.py` → `bridge/` | — | — |
28
+ | 4.3* | `sqrtinfo` (rewritten; kernel preserved per B11) | `test_sqrtinfo_agrees.py` | ✅ |
29
+
30
+ `*` — has a page but **no source row of its own** in §四, and the test
31
+ records why. `conditioning.py` appears only in the `linear.py` row's
32
+ DESTINATION cell (upstream moved it to `rheplicant.core` so `radio` could
33
+ use it without importing `inference`); `sqrtinfo` belongs to the evidence
34
+ layer, which §四 4.3 marks 不迁移 — rewritten under iron law 2, with its
35
+ numerical kernel required to be preserved exactly and a bitwise
36
+ cross-check enforcing it.
37
+
38
+ ## Where the gate actually stands
39
+
40
+ **Open, since 2026-08-25.** Twelve pages exist and **every §四 module has
41
+ one**. `tests/test_migration_records.py::test_the_gate_on_section_six_is_open_and_every_module_has_a_page`
42
+ now asserts that in the other direction: while the list was non-empty it
43
+ blocked §六, and now that §六's steps are being taken against these pages,
44
+ a page may not go missing.
45
+
46
+ | §四 row | closed |
47
+ |---|---|
48
+ | `linear.py` → `exact/{block,linearity,solve}` | `linear.md` |
49
+ | `likelihood.py`/`noise.py` → `exact/gaussian` | `noise.md` |
50
+ | `gls.py`, `uncertainty.py` (Fisher) | `gls.md`, `uncertainty.md` |
51
+ | `parameters.py` → node declarations | `parameters.md` |
52
+ | `noise.py` → probabilistic nodes | `noise.md` |
53
+ | `plan.py`+`engines.py` → dispatch | `plan.md` |
54
+ | `identifiability.py`, `sensitivity.py`, `priors.py` → `diagnose/` | three pages |
55
+ | `numpyro_bridge.py` → `bridge/` | `numpyro_bridge.md` |
56
+ | *(out of ledger)* `conditioning.py`, `sqrtinfo` | two pages, with their reasons |
57
+
58
+ **What that does and does not authorise.** §六 step 1 still governs:
59
+ nothing in `src/rheplicant/inference/` moves except the two exceptions
60
+ already in e-RHINO's Track A Batch 1 (B1's `plan.py` docstring and B4's
61
+ one-line fix), plus docstring pointers to bayesmith. Read §六's five steps
62
+ before starting, and note step 3's possible dividend — the two pytest
63
+ sessions may be able to merge once the evidence layer is out.
64
+
65
+ ## What a page must contain
66
+
67
+ The five headings §二 names, in its order. The P5 pages are the worked
68
+ examples.
69
+
70
+ **One thing NOT to write: a case count, unless the module is finished.**
71
+ Some pages here carry one and some do not, and the asymmetry is a
72
+ measurement rather than an oversight. The P5 pages' counts (17, 17, 10)
73
+ were written in session 3 and are still exact, because nothing touched
74
+ those modules afterwards. `plan.md`'s said 6 and was 7 within hours,
75
+ because its author kept working on the module after writing the page. So
76
+ the rule is about timing, not taste: a count is a safe thing to record only
77
+ once the thing counted has stopped moving, and the four pages written
78
+ during active work say `pytest --collect-only -q` instead. Nothing in this
79
+ repository reads a count out of a page, so none of them can go red.
80
+
81
+ The five headings:
82
+
83
+ 1. **Fixtures** — reused from rheplicant where possible (they are pinned
84
+ measurements, not fresh guesses), at least one healthy and one that must
85
+ be refused.
86
+ 2. **Numerical agreement** — deterministic exits to float64 roundoff,
87
+ sampled exits within MC error. Say which tolerance and why; where
88
+ exactness is not mathematically available (a null direction is a ray, a
89
+ null space is basis-dependent), say what is compared instead.
90
+ 3. **Refusal agreement** — every loud rheplicant refusal has a same-shape
91
+ refusal here, with the exception-class mapping recorded.
92
+ 4. **Independent oracle** (iron law 4) — analytic truth, a hand-written
93
+ NumPyro model, scipy, or mutation. *Two implementations agreeing is not
94
+ evidence.*
95
+ 5. **Intended differences** — with the reason and the equivalence argument.
96
+ A difference that is deliberate and unrecorded is indistinguishable from
97
+ a bug six months later.
98
+
99
+ Iron law 5 adds one hard item: any §三 defect belonging to the module is
100
+ fixed **before** step 2, or written as a signed, sized expected difference.
@@ -0,0 +1,74 @@
1
+ # Cross-check: `conditioning`
2
+
3
+ `rheplicant.core.conditioning` (moved there from `inference/` upstream when
4
+ `radio` needed it and could not import `inference`) →
5
+ `bayesmith.exact.conditioning`. Test: `tests/crosscheck/test_conditioning.py`
6
+ (9 cases). Page written 2026-08-25 from that test's assertions, re-run on
7
+ that date; the cross-check itself predates this page.
8
+
9
+ ## 1. Fixtures
10
+
11
+ A symmetric operator with an EXACTLY known spectrum, built as
12
+ `Q diag(λ) Qᵀ` from a QR of a fixed-seed normal matrix and handed to both
13
+ packages as the same callable — so a difference is the algorithm and never
14
+ the setup. Spectra: `[1,2,3]`, `[1e-6,1,5,5.5]`, six-fold degenerate
15
+ `[1]*6`, and a 20-point geometric spectrum with κ = 1e4.
16
+
17
+ For `tree_norm`, three pytrees including the **overflow** case both module
18
+ docstrings argue about: `{a: [1e20, 1e20], b: [0]}`. Squaring first turns
19
+ 1e20 into `inf`, and the scaling that avoids it is the entire content of
20
+ the function — a port that dropped it agrees on the ordinary row and
21
+ disagrees here.
22
+
23
+ ## 2. Numerical agreement
24
+
25
+ | quantity | agreement |
26
+ |---|---|
27
+ | `tree_norm`, all three trees | **exact float equality** |
28
+ | `largest_eigenvalue` (same operator, template, key, 12 iterations) | **exact float equality** |
29
+
30
+ Equality rather than a tolerance, because it is the same arithmetic in the
31
+ same order on the same library; a tolerance would let a genuinely different
32
+ iteration pass, which is what this file exists to notice.
33
+
34
+ ## 3. Refusal agreement
35
+
36
+ Neither module refuses anything — these are numerical estimators, not
37
+ gates. The refusal that *consumes* them (`condition_bound`'s κ ceiling)
38
+ belongs to `solve.py` and is checked in `tests/exact/`.
39
+
40
+ ## 4. Independent oracle
41
+
42
+ `jnp.linalg.eigvalsh` of the materialised matrix. Both packages'
43
+ `largest_eigenvalue` must approach the true λ_max **from below** (within
44
+ 1e-5 above, and within 1e-3 relative) — "agreeing on a wrong number is
45
+ still agreement", so the shared claim is checked against truth as well as
46
+ against each other.
47
+
48
+ ## 5. Intended differences — one, and it is the reason the harness exists
49
+
50
+ **`extreme_eigenvalues` was deliberately NOT ported.** rheplicant estimates
51
+ λ_min by running a second power iteration on `λ_max·I − M`, which on a
52
+ graded spectrum cannot separate the eigenvalues crowded against λ_max — and
53
+ the error is **one-sided and toward danger**: λ_min comes back too LARGE,
54
+ so κ too SMALL, so a convergence guard built on it is silent exactly when
55
+ it should fire. Measured upstream at κ = 1e4: λ_min over-large by 33.9×,
56
+ reported κ = 2.947e+02 against a true 1.000e+04.
57
+
58
+ bayesmith bounds λ_min below by the prior's own curvature instead
59
+ (`exact/solve.py::condition_bound`), giving an UPPER bound on κ — the
60
+ direction a safety guard needs.
61
+
62
+ Both halves are live tests, not prose:
63
+
64
+ - `test_bayesmith_does_not_carry_extreme_eigenvalues` goes red if someone
65
+ ports it after all, and points at the docstring that rejected it.
66
+ - `test_rheplicant_still_carries_it_and_still_leans_the_unsafe_way`
67
+ asserts the bias as an **inequality against the truth** (λ_min > 10×
68
+ true), so it survives an iteration-count retune. When rheplicant fixes
69
+ this, that test goes red and should be deleted together with the
70
+ paragraph it guards.
71
+
72
+ This is a **Track A item for e-RHINO, not this migration** — recorded here
73
+ because it is the first time the harness's reason for existing paid out: a
74
+ manual comparison holds only on the day it is written.
@@ -0,0 +1,81 @@
1
+ # Cross-check: `gls` — and the B1 log-determinant ledger
2
+
3
+ `rheplicant.inference.gls` (`iterative_gls`) → `bayesmith.exact.gls`
4
+ (§四 4.1). Test: `tests/crosscheck/test_noise_logdet.py` (17 cases, shared
5
+ with the Fisher row — see [uncertainty.md](uncertainty.md)) plus
6
+ `tests/exact/test_gls.py::test_the_fixed_point_is_the_unbiased_estimator_
7
+ not_the_gls_biased_one` on this side. Page written 2026-08-25 from those
8
+ tests' assertions, re-run on that date; the cross-checks predate this page.
9
+
10
+ **This page is unusual: its subject is a difference, not an agreement.**
11
+ §三 B1 is a defect in rheplicant, and iron law 5 forbids aligning a new
12
+ implementation to a defective one. So the acceptance is a *signed, sized*
13
+ expected difference on one side and a proof of correctness on the other.
14
+
15
+ ## 1. Fixtures
16
+
17
+ A constant-mean model under `RadiometerNoise`, so both estimators have a
18
+ closed form and nothing samples: `f ∈ {0.05, 0.2, 0.5}`, `n = 2000` for the
19
+ closed-form rows and `n = 200 000` for the asymptotic one (measured: at
20
+ f=0.5 the ratio lands 0.024 % from `1+f²` at n=200 000, against 2.5 % at
21
+ n=2000). A `HomoscedasticNoise` row is the anti-vacuity control.
22
+
23
+ ## 2. Numerical agreement — of each estimator with its own closed form
24
+
25
+ | claim | agreement |
26
+ |---|---|
27
+ | `include_logdet=False` maximiser = `Σd²/Σd` | rel **1e-8** |
28
+ | `include_logdet=True` maximiser = positive root of `n f² μ² + μ Σd − Σd² = 0` | rel **1e-8** |
29
+ | ratio of the two = `1 + f²` | rel **2e-3**, at three f |
30
+ | the full density's estimate = the truth | rel **5e-3** |
31
+
32
+ The quadratic's root is derived in the test rather than taken from
33
+ rheplicant's docstring, because the asymptotic claim rests on it: it is
34
+ what makes the full density **exactly unbiased** rather than differently
35
+ biased, and a test asserting only "closer to the truth" would pass on an
36
+ estimator that was merely less wrong.
37
+
38
+ The maximiser is found by **scipy on a Python closure** over rheplicant's
39
+ likelihood — no JAX gradient, no algebra of ours. A closed form checked
40
+ against its own rearrangement would be checking arithmetic, not the
41
+ estimator.
42
+
43
+ ## 3. Refusal agreement
44
+
45
+ None to compare: neither package refuses the mixture. That is the defect —
46
+ B1's point is that rheplicant's `nuts` route (whose `dist.Normal` carries
47
+ its own `−log σ`) and its `plan.sample` gradient block (which does not)
48
+ target different estimators on one model, **with no guard between them**,
49
+ while the same package's evidence layer raises full-versus-GLS to a
50
+ refusal.
51
+
52
+ ## 4. Independent oracles
53
+
54
+ - The two closed forms above (algebra, not either implementation).
55
+ - scipy's optimiser, sharing nothing with either package's gradients.
56
+ - **Anti-vacuity**: under `HomoscedasticNoise` the log-determinant is an
57
+ additive constant and the two estimators must **coincide** — so the gap
58
+ is attributed to the prediction-dependence and not to some other
59
+ difference between the two likelihood spellings.
60
+ - The ratio is checked across an f that varies the answer by a factor of
61
+ 25 between its ends, so a constant fudge cannot satisfy it; and the SIGN
62
+ is asserted separately, because it is the half that says which engine is
63
+ the optimistic one (`gls > full > 0`).
64
+
65
+ ## 5. Intended differences — the whole point of the row
66
+
67
+ **bayesmith's `iterative_gls` does NOT carry this bias, and must not be
68
+ made to.** It is frozen-sigma IRLS: each inner solve holds σ fixed and
69
+ recomputes it afterwards, so its fixed point satisfies `w = mean(u)`,
70
+ `u = d/x` — the same side as the full density. Measured: the fixed point is
71
+ 44–128× closer to `mean(u)` than to `Σu²/Σu`, at `κ ∈ {0.05, 0.2, 0.5, 1.0}`.
72
+
73
+ The spec's first draft asked for the opposite — that bayesmith's frozen-σ
74
+ path differ from a live-σ path by `(1+f²)` — and building that test would
75
+ have pulled a correct estimator toward a bias it does not have. **The
76
+ initial statement of the acceptance was wrong, and measurement is what
77
+ found it**; the correction is recorded in §三 B1 itself, marked
78
+ `[实测确认]`.
79
+
80
+ Consequence for the pending `plan`/`engines` row (§四 4.2): B1 must land
81
+ first, or that comparison will fix the GLS-type target as the reference.
@@ -0,0 +1,86 @@
1
+ # Cross-check: `identifiability`
2
+
3
+ `rheplicant.inference.identifiability` → `bayesmith.diagnose.identifiability`
4
+ (migration spec §八 step 5, acceptance §四 4.2). Executed 2026-08-25; every
5
+ number below was measured in this repo's venv on that date.
6
+
7
+ ## 1. Fixtures
8
+
9
+ Per §二 step 1, rheplicant's own pinned fixtures, re-expressed on the graph
10
+ (`tests/diagnose/models.py`):
11
+
12
+ - **Healthy + degenerate pair**: `data[t,f] = gain[t] * (T_ant[t,f] +
13
+ tone[f])`, 8×8, antenna temperature free-per-cell (72 parameters, the
14
+ under-determined case) or through a (3,3) polynomial basis (17). Same
15
+ coefficient matrix, deliberately asymmetric.
16
+ - **Must-refuse fixtures**: ambient float32; a graph pinning its own
17
+ arithmetic to float32; complex/integer selected latents; empty/unknown/
18
+ repeated names; a graph with no observed node; a Poisson observation
19
+ (no `loc`).
20
+ - Auxiliary: mixed-scale (1e10 unit gap), zero-column (a dead latent),
21
+ sort-trap (declaration order ≠ sorted order).
22
+
23
+ ## 2. Numerical agreement
24
+
25
+ `tests/crosscheck/test_diagnose_identifiability.py`, same arrays handed to
26
+ both packages (never the same construction recipe — §0.1's PRNG trap):
27
+
28
+ | quantity | agreement |
29
+ |---|---|
30
+ | (n_par, rank, nullity), all four rows | equal, cell for cell: (72,64,8)×2, (17,17,0), (17,16,1) |
31
+ | singular values (basis/off) | rel 1e-10 |
32
+ | `weakest_identified` | rel 1e-9, both 4.822138e-05 |
33
+ | null direction, nullity 1 | elementwise abs 1e-9, sign-fixed on the largest entry |
34
+ | null space, nullity 8 | as projectors `VᵀV`, abs 1e-9 — rows are basis-dependent, the subspace is not |
35
+
36
+ ## 3. Refusal agreement
37
+
38
+ Every loud rheplicant refusal has a same-shape refusal here (empty/unknown/
39
+ repeated names, complex → the R-linear explanation, integer, out-of-range
40
+ direction index, inconsistent report). Exception classes map
41
+ `ParameterSpaceError → GraphError`, `StateValidationError → StructureError`
42
+ — bayesmith's own taxonomy; the shared-identity concern of the design doc's
43
+ appendix applies to rheplicant's importers, not to new code here.
44
+
45
+ **One refusal changed mechanism, deliberately.** rheplicant forces
46
+ process-global x64 *inside* the diagnostic and refuses only a model that
47
+ pins its output to float32. This package's rule is that `src/` never touches
48
+ `jax.config`, so there are TWO refusals: ambient float32 (before any
49
+ tracing — a graph built in x64 and called outside it otherwise dies inside
50
+ `jax.linearize` with a bare dtype inconsistency) and result float32 (the
51
+ graph itself casts). Both are pinned, and the float32 counterfactual is
52
+ computed by hand in the suite to show what they protect.
53
+
54
+ ## 4. Independent oracles (iron law 4)
55
+
56
+ - "Moving along the reported direction does not move the model": the
57
+ end-to-end statement, evaluated through the graph itself, against a
58
+ random direction of the same size (< 1e-3 of the random movement), on
59
+ both the over- and under-determined fixtures.
60
+ - The free model's `weakest_identified` = 1/√2 exactly — fixed by the
61
+ fixture's geometry, not by either implementation.
62
+ - The no-normalisation counterfactual: the mixed-scale fixture's raw
63
+ spectrum ratio is measured (1.000000e-10) and shown to sit below the
64
+ default tolerance.
65
+ - 5 of the 17 hand-applied mutations in the port's mutation pass targeted
66
+ this module; all killed.
67
+
68
+ ## 5. Intended differences
69
+
70
+ 1. **The x64 mechanism** (above). Consequence: `DEFAULT_RANK_RTOL = 1e-8`
71
+ was **re-measured, not ported**, per the spec's recorded trap. Under
72
+ this regime: null direction 7.479266e-17 (upstream arithmetic 6.6e-17 —
73
+ the spectrum moved with evaluation order, the verdict did not), weakest
74
+ identified 4.822138e-05 (identical to every printed digit), float32
75
+ null direction 3.116759e-08 (upstream pinned 3.1168e-8). The window and
76
+ the constant survive; the justification now cites this side's numbers.
77
+ 2. **Signature**: `(space, pipeline, state_template)` → `graph`. `at`
78
+ defaults to prior centres (`prior_environment`, the dispatch layer's own
79
+ anchoring rule) rather than declared inits — the same point, one
80
+ spelling.
81
+ 3. **The Jacobian's row layout** is `dense_operator`'s (observed nodes in
82
+ sorted name order, flattened); rheplicant's is a single prediction
83
+ array. Irrelevant to the rank and the null space; recorded because the
84
+ `jacobian` field is public.
85
+ 4. rheplicant's raw-`Bind`-space and x64-subprocess tests do not port
86
+ (no such concepts here); the refusal-mechanism family replaces them.