bayesmithDocumentation0.10.0 source

Example 01 · Your first posterior

Start here after installation: this first tutorial teaches the complete declare → compile → sample → check loop. Infer one latent level from three measurements. The whole model is one exact block, so the posterior draws are independent and exact.

Model

A sample node contributes a prior. An observe node contributes a likelihood and attaches data. Parent references express dependencies; ordinary JAX operations live inside the functions attached to nodes.

Complete runnable script
first_model.py
"""Run with: python first_model.py"""

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

import bayesmith as bs

DATA = jnp.array([0.8, 1.0, 1.2])


def model(data):
    level = bs.sample("level", lambda: dist.Normal(0.0, 2.0))
    bs.observe("signal", lambda x: dist.Normal(x, 0.5), level, obs=data)


def main():
    graph = bs.trace(model, DATA)
    plan = bs.compile(graph)
    print(plan)
    posterior = plan.sample(jax.random.key(7), num_samples=256)
    print("Posterior sample shape:", posterior.samples["level"].shape)
    print("Posterior mean estimate:", posterior.samples["level"].mean())
    return posterior


if __name__ == "__main__":
    main()

Download this example

Data

Three measurements of one level, 0.8, 1.0 and 1.2, each with known noise standard deviation 0.5. The prior on the level is Normal with mean 0 and standard deviation 2.

Infer

Read the plan before the samples:

BlockLatentsRouteWhy
0levelGCR exactstructurally certified; numerical check 3 scales x 3 at-points (max 0.00e+00)
Executioniid draws, no chain
Printed plan
block 0  {level}          GCR exact         structurally certified; numerical check 3 scales x 3 at-points (max 0.00e+00)
         kappa=49 -> tol=2.0408163e-05, guard reachable, off by default
         (require_convergence=)
         exact block ['level']: sigma does not move with the block (0.000e+00 relative,
         rtol=1.0e-08), so the covariance is fixed and one Wiener solve is exact
execution: iid draws, no chain
Recorded 2026-09-19 with bayesmith 0.9.0 on arm64 (accelerate BLAS, x64 on) from first_model.py. Condition numbers and tolerances in the printout depend on the BLAS; block membership and routes are re-checked by the test suite. Full record.

The prior is Normal, the prediction is the level itself and the noise is fixed, so the whole graph is one exact block. The execution line says each draw is independent and exact: there is no chain, no warmup and no convergence to diagnose.

See the posterior

Histogram of 256 exact draws overlaid with the analytic posterior and the wider prior

Scroll the figure horizontally, or open it for a full-size view.

The histogram comes from running the script with seed 7. The blue curve is the closed-form posterior; the gray curve is the prior. Data concentrate uncertainty near one.
QuantityClosed formRecorded draws
Posterior mean0.97960.9816
Posterior sd0.28570.2923
Recorded 2026-09-19 with bayesmith 0.9.0 on arm64 (accelerate BLAS, x64 on); seed 7, draws 256. Sampled columns are one run on that platform; closed-form columns are recomputed by the test suite. Full record.

Check

This model has a closed-form posterior. With prior standard deviation 2 and measurement standard deviation 0.5:

\sigma_{\mathrm{post}}^2=\left(\frac{1}{2^2}+\frac{3}{0.5^2}\right)^{-1}=\frac{1}{12.25}
\mu_{\mathrm{post}}=\sigma_{\mathrm{post}}^2\frac{0.8+1.0+1.2}{0.5^2}=\frac{12}{12.25}\approx0.9796

The mean of 256 independent draws differs from 0.9796 by Monte Carlo error with standard deviation \sigma_{\mathrm{post}}/\sqrt{256}\approx0.018.

Move to named questions and stored results

plan.sample returns a runtime posterior. The typed interface adds task identity, a compute budget, immutable metadata and run provenance. Continue to Partial structure for a model with an exact block inside NUTS, or to five statistical questions for a posterior → predictive → artifact workflow.

Reference: trace, sample, observe, compile.

Type a concept or API name.