bayesmithDocumentation0.10.0 source

Example 05 · Five questions, one model

After Example 01, keep the same simple model and ask five different statistical questions. Use an explicit task when the question, budget and provenance must travel with the answer.

Pick the statistical quantity first

QuestionTask → Result
What remains uncertain about the parameters?PosteriorTask → PosteriorResult
Which point summary do I want?PointEstimateTask → PointEstimateResult
What observations could this posterior predict?PredictiveTask → PredictiveResult
What does the forward model generate?SimulationTask → SimulationResult
What is the normalized probability of these data?EvidenceTask → EvidenceResult

Compilation and execution can return Refusal. Check both boundaries before consuming the result. A numerical execution failure can raise an exception; it is not a statistical conclusion about the model.

Run the complete workflow

Save the script as a file so the model source can be fingerprinted. Run JAX_ENABLE_X64=1 python five_tasks.py. Each successful stage prints its Result type; the final round trip checks artifact identity.

Complete runnable script
five_tasks.py
"""Run with JAX_ENABLE_X64=1 python five_tasks.py."""

from pathlib import Path
from tempfile import TemporaryDirectory

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

import bayesmith as bs
from bayesmith.artifacts import (
    ArtifactKind,
    ArtifactRef,
    ComputeBudget,
    Estimand,
    EvidenceResult,
    EvidenceTask,
    ParameterSource,
    ParameterSourceKind,
    PointEstimateResult,
    PointEstimateTask,
    PosteriorResult,
    PosteriorTask,
    PredictiveResult,
    PredictiveTask,
    Refusal,
    SimulationResult,
    SimulationTask,
    dump_artifact,
    load_artifact,
    model_ref_from_callable,
    new_task_meta,
)


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 run_workflow():
    """Return each result so the tutorial can plot what was actually run."""
    graph = bs.trace(model, jnp.array([0.8, 1.0, 1.2]))
    model_ref = model_ref_from_callable(model, identifier="level-example")
    keys = iter(jax.random.split(jax.random.key(7), 10))

    def run(task, expected, source=None):
        planned = bs.compile_task(graph, task, model_ref=model_ref, key=next(keys))
        if isinstance(planned, Refusal):
            raise SystemExit(planned.failed_premise)
        result = bs.execute_task(planned, key=next(keys), source_posterior=source)
        if isinstance(result, Refusal):
            raise SystemExit(result.failed_premise)
        assert isinstance(result, expected)
        print(type(result).__name__)
        return result

    # 1. Posterior: retain uncertainty about the latent level.
    posterior = run(
        PosteriorTask(
            meta=new_task_meta(label="level posterior"),
            budget=ComputeBudget(draws=256),
        ),
        PosteriorResult,
    )

    # 2. Point estimate: ask explicitly for the posterior mean.
    point = run(
        PointEstimateTask(
            meta=new_task_meta(label="level mean"),
            estimand=Estimand.POSTERIOR_MEAN,
        ),
        PointEstimateResult,
    )
    print({array.name: array.value for array in point.values})

    # 3. Predictive: replicate the measurements using this posterior.
    source_ref = ArtifactRef(
        artifact_id=posterior.meta.artifact_id,
        revision=posterior.meta.revision,
        artifact_type=ArtifactKind.RESULT,
    )
    predictive = run(
        PredictiveTask(
            meta=new_task_meta(label="replicate signal"),
            source_posterior_ref=source_ref,
            conditioned_sites=("signal",),
            replicated_sites=("signal",),
            latent_sites=("level",),
        ),
        PredictiveResult,
        source=posterior,
    )
    print(predictive.replicated_draws[0].value.shape)

    # 4. Simulation: sample from the declared prior, then generate data.
    simulation = run(
        SimulationTask(
            meta=new_task_meta(label="prior simulation"),
            parameter_source=ParameterSource(kind=ParameterSourceKind.PRIOR),
            latent_sites=("level",),
            observed_sites=("signal",),
            budget=ComputeBudget(draws=32),
        ),
        SimulationResult,
    )

    # 5. Evidence: x64 and a fully normalized linear-Gaussian model.
    evidence = run(
        EvidenceTask(meta=new_task_meta(label="analytic evidence")), EvidenceResult
    )
    print("log p(data):", evidence.log_evidence)

    # Store data artifacts, never a live Graph or compiled executable.
    with TemporaryDirectory() as directory:
        path = Path(directory) / "posterior.json"
        dump_artifact(posterior, path)
        restored = load_artifact(path)
        assert isinstance(restored, PosteriorResult)
        assert restored.meta.artifact_id == posterior.meta.artifact_id
    print("Artifact round trip: identity preserved")
    return {
        "graph": graph,
        "model_ref": model_ref,
        "posterior": posterior,
        "point": point,
        "predictive": predictive,
        "simulation": simulation,
        "evidence": evidence,
    }


def main():
    results = run_workflow()
    return tuple(
        results[name]
        for name in ("graph", "model_ref", "posterior", "predictive", "evidence")
    )


if __name__ == "__main__":
    main()

Download this example

What the run returns

ResultRecorded output
PosteriorResult256 level draws; mean 0.9633
PointEstimateResultPosterior mean 0.9796
PredictiveResultReplicated signal shape (256, 3)
SimulationResultPrior simulation shape (32,)
EvidenceResultlog p(data) = -2.905733
Artifact round tripIdentity preserved (asserted during execution)
Recorded 2026-09-19 with bayesmith 0.9.0 on arm64 (accelerate BLAS, x64 on); seed 7, posterior_draws 256, prior_draws 32. Sampled columns are one run on that platform; closed-form columns are recomputed by the test suite. Full record.
Posterior level draws are narrower than the generated noisy observations

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

Both histograms use results from the script. A new measurement includes observation noise, so its predictive distribution is wider than uncertainty in the latent level. Replicated observations within each dataset share a latent draw; flattening them here shows marginal distributions, not independent samples.

The run record contains the numerical draws, captured console output, software versions, source hashes and figure checksums. This one-data-set example demonstrates the workflow, not calibration across datasets.

Understand the references

A predictive task stores a reference to its source posterior; execution receives the actual posterior separately. Results and provenance explains identity checks and reuse.

The point task requests POSTERIOR_MEAN. Changing it to MAP changes the estimand even when both coincide in this symmetric Gaussian example. The simulation task draws from the prior; it does not reuse the posterior unless you declare that parameter source.

Supported models

These five tasks work on this deliberately simple graph. General posterior sampling has wider support than replicated prediction, and whole-graph analytic evidence has stronger normalization and structure requirements than posterior sampling. A successful Gaussian tutorial does not certify arbitrary graphs.

Next: prediction and simulation, or results and provenance.

Type a concept or API name.