Metadata-Version: 2.5
Name: bayesmith
Version: 0.8.0
Summary: A graph of operators is a Bayesian model; its structure chooses the inference.
Project-URL: Repository, https://github.com/zzhang0123/bayesmith
Author: Zheng Zhang
License: MIT
License-File: LICENSE
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Science/Research
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Scientific/Engineering :: Mathematics
Requires-Python: >=3.11
Requires-Dist: equinox>=0.13
Requires-Dist: jax>=0.5
Requires-Dist: numpy
Requires-Dist: numpyro>=0.15
Description-Content-Type: text/markdown

# bayesmith

A Bayesian model is a graph of operators. Deterministic operators propagate
dependence; probabilistic operators contribute a conditional density. Together
they *are* the joint distribution.

bayesmith makes that graph **explicit and inspectable**, analyzes the structure,
and compiles an inference route — an exact solve where the structure permits
one, NumPyro NUTS as the current general fallback where it does not.

```
block 0  {x}          Wiener exact        (linear_in checked, 3 scales)
block 1  {z}          enumerate 4 states
block 2  {sigma, nu}  NUTS (numpyro)      no exact structure found
```

The model tells you how it will be fitted, before it is fitted. The longer-term
direction is a task-aware Bayesian workflow layer whose posterior, predictive,
model-checking and evidence results share explicit provenance and quality gates;
the approved boundary and roadmap live in the
[top-level design](docs/superpowers/specs/2026-08-30-bayesmith-top-level-design.md).
Those future protocols are not claimed as current API here.

An illustrated overview -- what the package is, where it sits next to NumPyro,
BlackJAX and ArviZ, what each subpackage owns, and where the roadmap stands --
is published as a page:
[bayesmith Overview](https://claude.ai/code/artifact/e3514ec4-000f-4a59-9e6c-79b12281b83c)
(English, with a Chinese toggle; generated from the 0.7.2 checkout on
2026-09-03, so it describes that release rather than tracking this file).

## What bayesmith is not

It is **not another probabilistic programming language or a sampler zoo**.
[NumPyro](https://github.com/pyro-ppl/numpyro) is the current general posterior
backend; mature upstream libraries should continue to own generic MCMC,
optimization and neural-estimation kernels. bayesmith owns the graph-aware
statistical semantics around those kernels, plus exact routes whose structure
the graph can certify. Its current ownership inventory is
[recorded explicitly](docs/ownership.md).

What it owns today:

- **Graph analysis and structural dispatch** — checked linearity, Gaussianity,
  support, coupling and conditioning claims, followed by an inspectable plan.
- **Structural exact inference** — first-party conjugate / Wiener / GCR / GLS
  solves and exact posterior sampling, plus exact enumeration of discrete
  latents, selected per subgraph.
- **Streamed marginal likelihoods** (`bayesmith.marginal`) — each epoch or
  dataset compressed to a square-root information term, combined exactly. Not
  by itself the graph-level Bayesian evidence `p(d)`: a term is a function of
  the surviving parameters, with that dataset's own nuisances integrated away.
  The subpackage was called `evidence` through 0.4.0 and that path still works,
  with a `DeprecationWarning`, until 1.0.
- **Diagnostics on the graph** (`bayesmith.diagnose`) — identifiability and
  prior sensitivity. Linearity checking lives with the solvers that exploit
  it, in `bayesmith.exact.linearity`, because the declaration it checks is
  what those solvers rely on.
- **Current non-exact exits** — a thin NumPyro bridge, `bayesmith.optimize` for
  gradient MAP on a graph or scalar objective, and `bayesmith.amortize` for a
  posterior fitted to simulations. Their graph-facing contracts belong here;
  their generic optimizer and neural-estimator algorithms are reference or
  upstream-candidate implementations rather than a commitment to grow local
  algorithm families.

Declarations such as `linear_in` are *claims about the model*, not hints, so
they are **checked rather than trusted**: a node declared linear is probed at
three scales before any exact solve is allowed to use it.

## Worked examples

[`docs/factor-partition-examples.md`](docs/factor-partition-examples.md) walks
two models from declaration to auto-partitioned sampling -- three factors
three routes, then a hierarchy where the ancestry rule earns its keep. Every
printout there was produced by running the code shown, and the partitions are
pinned by ``tests/dispatch/test_factor.py``.
### The typed task workflow (R1)

R1 publishes a serialisable, invalidatable, evaluable protocol in
`bayesmith.artifacts`, reached from the root through two lazy entry points.
The five Tasks map one-to-one onto five Results; a task that cannot be
compiled or executed returns a typed `Refusal` rather than an exception in
disguise. The legacy entry points (`compile()`, `InferencePlan.sample()` /
`.estimate()`, `Posterior`, `Estimate`, `fit`) are unchanged and remain
supported -- the typed entry points wrap them, they do not replace them.

```python
import jax
import jax.numpy as jnp
import numpyro.distributions as dist

import bayesmith
from bayesmith.artifacts import PosteriorTask, model_ref_from_callable, new_task_meta


def model(data):
    x = bayesmith.sample("x", lambda: dist.Normal(0.0, 2.0))
    bayesmith.observe("d", lambda v: dist.Normal(v, 0.5), x, obs=data)


graph = bayesmith.trace(model, jnp.array([1.0, 2.0]))
task = PosteriorTask(meta=new_task_meta(label="radiometer posterior"))
ref = model_ref_from_callable(model, identifier="radiometer")

planned = bayesmith.compile_task(graph, task, model_ref=ref, key=jax.random.key(0))
result = bayesmith.execute_task(planned, key=jax.random.key(0))
# result is a PosteriorResult: a DrawsPosterior or WeightedDrawsPosterior,
# with run provenance, fingerprints and a frozen Refusal.grounds on refusal.
```

`model_ref_from_callable` derives the source digest automatically when the
model is defined in an importable module. A model typed into a REPL or built
by `exec` has no recoverable source, so pass `source_digest=...` explicitly --
`repr()` is never a fallback, because it carries a memory address.

The full protocol -- the five-in/five-out table, fingerprint boundaries, the
invalidation matrix, `Refusal.grounds` and the gate truth table -- is
documented in [`docs/artifacts.md`](docs/artifacts.md).

Since 0.7.0 the `PredictiveTask` executes as well (R2): `execute_task(planned,
key=..., source_posterior=...)` pushes a `PosteriorResult`'s draws onto the
observed nodes and returns a `PredictiveResult` whose replicated draws and
pointwise log-likelihood come from one loc/scale read, so an observed-data
replay is never mistaken for a posterior predictive. A source posterior drawn
under different data, graph or model is refused as `posterior_data_mismatch`;
a correlated or non-Gaussian observed node is refused as
`predictive_noise_unsupported`. An optional, export-only ArviZ seam lives in
`bayesmith.bridge.arviz`.

`SimulationTask` executes too, since R3 opened the evaluation layer: prior,
fixed and posterior-sourced parameter sources all run through the same forward
primitives the predictive seam uses, so there is one forward model rather than
a simulator beside it. `EvidenceTask` is the one of the five still refused, and
it returns the typed `capability_unavailable_r1`.

R3's own surface is model checking. `bayesmith.evaluation.check_posterior(graph,
posterior, key=..., budget=..., model_ref=...)` runs the checks that apply to
one fitted posterior — posterior and prior predictive checks, held-out
prediction on the points the graph's mask withheld, PSIS-LOO through the
optional ArviZ seam, identifiability and prior sensitivity — files each as a
typed `EvaluationReport`, and aggregates them under the versioned
`model_checking@1` gate. Every report answers on **two** axes rather than one:
an applicability (`APPLICABLE`, `INAPPLICABLE`, `UNVERIFIABLE`) beside a
conclusion (`PASS`, `FAIL`, `ABSTAIN`), so that "this check does not apply
here", "it applies but its inputs were missing" and "it ran and the model
failed" are three answers instead of one word. Only an `APPLICABLE` check may
pass or fail, which is what keeps a run failure from being dressed as a verdict
about the model. One false-positive rate is declared in advance for the whole
layer — `ALPHA = 0.05` — and the draw and replicate floors are derived from it
rather than chosen beside it. The page is
[`docs/evaluation.md`](docs/evaluation.md).

**What a model-checking PASS does not promise** — stated here for the same
reason the Status section below names what this release does not do yet: a
front page is a claim, and finding out afterwards is worse than reading it now.
A predictive check bounds the statistics it computed and nothing wider:
`curved_line(0.15)` — a straight line fitted to data with a real quadratic
term, a quarter the size of one the same check catches at p = 0.0000 — passes
all five default discrepancies, and a green test in the suite exists to pin
that it does. An SBC pass says a route's stated
uncertainty is consistent with its stated prior, which a "posterior" that
ignores the data and returns prior draws satisfies by construction. Neither is
a defect being disclosed; both are what these checks measure. Every predictive
check writes the first caveat into its own report's `meta.summary` — "a pass
bounds these statistics and nothing wider" — so that one travels with the
artifact rather than living only on a page.

## Status

**0.8.0.** The minor slot rather than the patch one because R3 opens a new
public subpackage, `bayesmith.evaluation`; nothing existing changed signature,
default or numerical result. What other packages can depend on by name is whatever
`pypi.org/simple/bayesmith/` lists, and that index is the place to ask rather
than this line: 0.6.0, 0.6.1, 0.6.2 and 0.7.0 were each tagged and never
reached it, every one failing its own built-wheel test (see `CHANGELOG.md`;
0.7.0's failure was a Linux-only cluster in the numerical-gate suite, which is
also why `suite.yml` now runs the suite on Linux before any tag exists), so
from 2026-08-28 the index's newest release was 0.5.0. rheplicant
uses bayesmith across its production inference layer: its auto-partition and
log-space seams import `dispatch.factor.first_fit` and `exact.loglinear`; its
adapter presents a pipeline as a `Graph`, reads `AffinityRefused`'s payload and
declares complex latents with `ComplexNormal`; and its diagnostics delegate to
`diagnose.identifiability`, `diagnose.sensitivity` and `diagnose.local`. It pins
`bayesmith>=0.5`. The consumer contract is guarded by running rheplicant's own
inference and seam suites against the candidate bayesmith checkout, not by a
hard-coded count of importing modules.

Alpha in the classifier's sense: the API may
still move -- 0.3.0 made `reason` required on `NotGaussian` and
`NotLogLinear`, and 0.4.0 tightens two precision refusals, each breaking for
a caller who was relying on the wrong answer.

Implemented and tested, 5729 tests: the graph core with plates and joint
log-density, with flagged samples declared per node and honoured by every
route; the NumPyro bridge, so any graph is runnable through NUTS;
structural dispatch with the linear-Gaussian exact solves; the FACTOR
partition -- as many exact blocks as the model has factors, grouped by
pairwise probe, with log-space blocks discovered rather than declared
(`factor_partition`, `sample_factors`, `log_space`); exact enumeration of
discrete latents; streamed marginal-likelihood terms as square-root information
factors; and graph diagnostics for identifiability, prior sensitivity and
linearity. A graph-level `EvidenceTask`, Bayes factors and general model
comparison are roadmap work, not present capabilities.

**Two things the page above describes that this release does not do yet.** Stated here
because a front page is a claim, and finding out afterwards is worse than
reading it now:

- **Enumeration is not dispatcher-selected.** `bayesmith.exact.discrete`
  computes the exact marginal and the posterior marginals over declared
  discrete latents, and reads the `Discrete(n)` support declaration to do it —
  but `classify` does not yet route a discrete subgraph to it. The
  `block 1  {z}  enumerate 4 states` line above is therefore a design sketch
  rather than a transcript; call the module directly.
- **Forward-backward is not implemented**, so a chain of `T` discrete latents
  costs `n ** T` by enumeration rather than `T * n**2`. Enumeration refuses
  past a budget rather than hanging, and names the count it would have visited.

## License

MIT
