bayesmith.evaluation
Source-derived signatures and contracts. See the reference index for coverage and conventions.
Module contract
Model checking: reports ABOUT results, never results. The layer the top-level design's §7.2 puts between the execution adapters and a workflow. Everything here reads a finished :class:`~bayesmith.artifacts.results.PosteriorResult`, :class:`~bayesmith.artifacts.results.PredictiveResult` or :class:`~bayesmith.artifacts.results.SimulationResult` and produces an :class:`~bayesmith.artifacts.reports.EvaluationReport` whose ``subject_ref`` points back at what it read. **Three things this layer does not do**, and they are the reason it is a layer rather than a handful of functions inside ``dispatch``: * It does not MODIFY a result. A check that could reweight a posterior would be an inference step wearing a verdict's schema. * It does not CHOOSE an algorithm. Deciding that a check failed and then re-running with a longer chain is a workflow's decision, made from a report, not a decision this layer may take on the execution layer's behalf (§2.4). * It does not RE-JUDGE a verdict that already has a home. ``identifiability`` and ``prior_sensitivity`` decide their own thresholds inside :mod:`bayesmith.diagnose`; the projections here read the report fields and file them, so a threshold has one owner rather than two that agree until they do not. **Dependencies run one way** -- ``evaluation`` imports ``dispatch``, ``graph``, ``artifacts`` and ``bridge.arviz``, and none of them imports it. ``tests/test_layering.py`` holds that direction, because the shortcut it forbids (``dispatch`` importing a check so a run can grade itself) is exactly the arrangement §2.4 describes as the thing to avoid. **ArviZ stays optional.** It is imported inside the function that needs it, never at module scope and never through this ``__init__``, so a clone without it installed gets an UNVERIFIABLE report rather than an ImportError (§7.3). ``tests/test_layering.py`` checks that in a subprocess.
ALPHA
Defined in bayesmith.evaluation, line 75.
ALPHA = 0.05DEFAULT_DISCREPANCIES
Defined in bayesmith.evaluation.checks, line 218.
DEFAULT_DISCREPANCIES: tuple[Callable[..., Any], ...] = (mean, sd, smallest, largest, residual_sd)DRAW_FLOOR
Defined in bayesmith.evaluation.checks, line 138.
DRAW_FLOOR: int = math.ceil(1.0 / (ALPHA / 2.0))REPLICATE_FLOOR
Defined in bayesmith.evaluation.sbc, line 149.
REPLICATE_FLOOR = 100SbcRanks
Defined in bayesmith.evaluation.sbc, line 247.
class SbcRanks
coordinates: tuple[str, ...]
ranks: tuple[tuple[float, ...], ...]
requested: int
usable: int
refused: int
unconverged: int
undrawn: int
route: str
subject_ref: ArtifactRef
fingerprints: FingerprintBundleWhat the replicate loop measured, with no verdict attached. Separated from :func:`sbc_report` so that the two arms of §0.11 -- a compiled route and an arbitrary sampler -- reach one judgement through one object, and so that a caller who wants the ranks themselves (a histogram, a plot) does not have to reach inside a report to get them. ``ranks`` is one tuple per coordinate, each holding one rank per USABLE replicate; the failure counts say how many replicates are missing from those tuples and why.
SbcRanks.unusable
unusable(self) -> intReplicates that produced no rank, whatever the reason.
check_posterior
Defined in bayesmith.evaluation.gate, line 581.
check_posterior(graph: Graph, posterior: PosteriorResult, *, key: jax.Array, budget: ComputeBudget, model_ref: ModelRef, carried: Sequence[EvaluationReport]=()) -> GateResultRun ``model_checking@1`` over one posterior: slots, then the aggregator.
:func:`model_checking_slots` then
:func:`~bayesmith.artifacts.gates.aggregate_gate`, and nothing between them
-- the same split as :func:`~bayesmith.evaluation.sbc.simulation_based_calibration`'s,
for the same reason: everything about what ran is in the first, everything
about what it means is in the second, and this function decides neither.
The aggregator is not wrapped, extended or second-guessed here (§8.2).
Args:
graph: as :func:`model_checking_slots` takes it.
posterior: the subject. Two of its fields reach the aggregator
directly: ``predictive_ready`` as ``prerequisites_ready``, and the
lifecycle status of its envelope as ``inputs_current``.
key: PRNG key for the two internal runs.
budget: what those runs may spend.
model_ref: what the model callable is.
carried: ``sbc`` and ``chain_diagnostics`` reports the caller holds.
Returns:
The :class:`~bayesmith.artifacts.gates.GateResult`. Its ``meta``
carries the posterior's own fingerprints and names it as a parent, so
§0.3 retires this verdict when the thing it judged changes.
The result's identity is minted, so two calls on identical inputs give
two DIFFERENT ``report_refs`` over the same decision: they are two
artifacts, and ``new_artifact_meta`` says so in as many words. What is
deterministic is everything the gate decided, which is what
``test_gate.py`` compares.discrepancy_identity
Defined in bayesmith.evaluation.checks, line 227.
discrepancy_identity(discrepancy: object) -> str``module.qualname``, and only when importing it back returns THIS object.
§0.3 says a user discrepancy is recorded by its importable identity, on
:func:`bayesmith.dispatch.amortized._embed_identity`'s rule -- and adds
that a lambda or a REPL function is refused. Those two clauses do not
agree as written, which was measured rather than assumed:
``_embed_identity`` reads ``__module__`` and ``__qualname__`` and refuses
only when one of them is empty, and a lambda has ``__qualname__ ==
'<lambda>'``, which is not empty. Handed one, it returns
``'__main__.<lambda>'`` -- an address that will never resolve again.
So the rule here is the same identity with the missing half supplied: the
identity must RESOLVE, by importing the module and walking the qualname,
and the object it resolves to must be the one that was passed. That
refuses a lambda (``getattr(module, '<lambda>')`` does not exist), a
closure (``'outer.<locals>.inner'``), a REPL function (``__main__`` has no
file to re-import from in another process), and the subtler case neither
of the first two covers: a module-level name that has since been rebound
to something else, where the address survives and its meaning does not.
Raises:
ValueError: when there is no identity, or it does not resolve back.held_out_report
Defined in bayesmith.evaluation.heldout, line 217.
held_out_report(graph: Graph, predictive: PredictiveResult, *, source_posterior: PosteriorResult) -> EvaluationReportScore the positions ``graph`` masked out, against ``predictive``'s draws.
Args:
graph: the model the predictive result was produced against. Its
observed nodes' ``observed_mask`` is the definition of held out.
predictive: the subject. Its carried ``latent_draws`` are replayed
through the forward model; nothing is re-sampled.
source_posterior: the posterior ``predictive.source_posterior_ref``
names. Required for its WEIGHTS, which a predictive result does
not carry -- see the module docstring.
Returns:
An :class:`~bayesmith.artifacts.reports.EvaluationReport` of kind
``held_out_prediction``, on §0.2's row:
* APPLICABLE / PASS -- every held-out point's PIT is inside
``[α/2m, 1 - α/2m]``.
* APPLICABLE / FAIL -- at least one is outside it.
* INAPPLICABLE / ABSTAIN -- the graph masks nothing out, so there is
no out-of-sample point to score. Not a PASS: a check with no
subject has verified nothing, and a gate counting it as a check that
was made would be counting an empty set.
* UNVERIFIABLE / ABSTAIN -- the predictive cannot be evaluated: a
correlated or non-Gaussian observed node (§0.4's coverage domain),
a source posterior that holds no draws, or a subject whose carried
latents do not cover the graph.
Raises:
TypeError: if the arguments are of the wrong type, or if
``source_posterior`` is not the artifact and revision
``predictive.source_posterior_ref`` names. That is a caller
error, not a verdict about a model: a report saying "unverifiable"
would file the mistake as a property of the subject.identifiability_report
Defined in bayesmith.evaluation.diagnostics, line 154.
identifiability_report(graph: Graph, subject: PosteriorResult, *, names: Sequence[str] | str | None=None, rtol: float=DEFAULT_RANK_RTOL) -> EvaluationReportFile :func:`~bayesmith.diagnose.identifiability.identifiability`'s rank
verdict as a report about ``subject``.
PASS at nullity 0, FAIL above it; §0.2's row, and the whole comparison.
The rank cut is ``rtol``'s, decided in ``diagnose`` and passed through so
that a caller who has read
:attr:`~bayesmith.diagnose.identifiability.IdentifiabilityReport.weakest_identified`
can tighten it without this layer acquiring an opinion about the value.
Args:
graph: the model ``subject``'s posterior was taken over. Build it
inside ``with jax.enable_x64(True):`` -- see the module docstring.
subject: the result this report judges. Only its identity and its
fingerprints are read; no draw is touched.
names: which latents to analyse, as
:func:`~bayesmith.diagnose.identifiability.identifiability` takes
them. ``None`` is the joint question over all of them, which is
the one a per-block guard cannot ask.
rtol: the relative rank tolerance.
Returns:
APPLICABLE × PASS when nothing is degenerate, APPLICABLE × FAIL with a
``null_participation`` finding naming the latents the first blind
direction mixes, or UNVERIFIABLE × ABSTAIN carrying the diagnostic's
refusal.loo_report
Defined in bayesmith.evaluation.loo, line 264.
loo_report(result: Any, *, graph: Graph, chain_shape: tuple[int, int] | None=None, source_posterior: Any=None) -> EvaluationReportJudge one result's PSIS-LOO, via ``arviz.loo`` over R2's export.
Args:
result: a :class:`~bayesmith.artifacts.results.PosteriorResult` or
:class:`~bayesmith.artifacts.results.PredictiveResult`.
graph: the model the result came from, handed on to the export for the
observed data. It is READ, never modified -- this layer evaluates
results and does not touch the model (§2.4).
chain_shape: ``(chains, draws)`` to override what the result recorded.
A predictive result carries no chain shape of its own, so a caller
holding the source posterior's may supply it here; left out, an
unchained result is exported as one chain.
source_posterior: the :class:`~bayesmith.artifacts.results.PosteriorResult`
a predictive result came from, read ONLY for its representation --
it is what says whether the sample is importance-weighted, and a
``source_posterior_ref`` is a pointer, not a representation. It
deliberately does not supply ``chain_shape``: that override already
has a home above, and one argument quietly feeding two decisions is
how the second one stops being visible.
Returns:
An :class:`~bayesmith.artifacts.reports.EvaluationReport` of kind
``loo_psis``, filed against ``result``:
* APPLICABLE / PASS -- arviz reported no reliability warning.
* APPLICABLE / ABSTAIN -- arviz warned. The ESTIMATE is unreliable,
which is not a claim about the model (§0.2).
* INAPPLICABLE / ABSTAIN -- the result carries no pointwise log
density, so there is no LOO to compute.
* UNVERIFIABLE / ABSTAIN -- the sample is importance-weighted, and
R2's export carries no weights for arviz to honour.
* UNVERIFIABLE / ABSTAIN -- arviz is not installed (§7.3).posterior_predictive_check
Defined in bayesmith.evaluation.checks, line 611.
posterior_predictive_check(graph: Graph, predictive: PredictiveResult, *, source_posterior: PosteriorResult, discrepancies: Sequence[Callable[..., Any]]=DEFAULT_DISCREPANCIES) -> EvaluationReportDo the observed data look like the model's replicated datasets? (§0.2)
Args:
graph: the model the predictive result was produced from. It carries
the observed data and the loc/scale walk; the result carries the
draws.
predictive: the subject. Its ``replicated_draws`` are the replicated
datasets and its ``meta`` is what the report's ``subject_ref``
points at.
source_posterior: the posterior ``predictive.source_posterior_ref``
names. Required, and checked by id and revision, for the same
reason :func:`bayesmith.dispatch.task.execute_task` requires it:
there is no artifact store to load one from, and §0.3 computes the
p-value with the SOURCE posterior's weights -- a weighted
posterior scored as though it were iid is a different number that
looks exactly like this one.
discrepancies: callables on §0.3's ``(y, loc) -> per-draw scalar``
contract, defaulting to :data:`DEFAULT_DISCREPANCIES`. Each is
recorded by :func:`discrepancy_identity` and never as an object.
Returns:
An :class:`~bayesmith.artifacts.reports.EvaluationReport` of kind
``posterior_predictive_check``: APPLICABLE and PASS when every cell's
p-value is inside the declared band, APPLICABLE and FAIL when one is
outside, APPLICABLE and ABSTAIN below :data:`DRAW_FLOOR` draws, and
UNVERIFIABLE where the discrepancy has no definition on this subject.
Raises:
TypeError: for the wrong kind of argument, for a source posterior that
is not the one the result names, or for an empty discrepancy set.
ValueError: for a discrepancy with no importable identity.prior_predictive_check
Defined in bayesmith.evaluation.checks, line 696.
prior_predictive_check(graph: Graph, simulation: SimulationResult, *, discrepancies: Sequence[Callable[..., Any]]=DEFAULT_DISCREPANCIES) -> EvaluationReportCould the PRIORS have generated data like this? (§0.2)
The same procedure as :func:`posterior_predictive_check` over a different
parameter source, and a different question: this one is answerable before
any fit, and what it catches is a prior so wide (or so narrow) that the
data it implies are nothing like the data in hand. Measured on this
checkout: ``straight_line``'s own ``w ~ N(0, 2)`` puts all five default
cells inside the band, and the same model under ``w ~ N(0, 1e6)`` saturates
``sd`` at 1.0000 and ``residual_sd`` at 0.0000 -- while ``mean``,
``smallest`` and ``largest`` stay comfortably inside it, which is the
limited-power sentence at the top of this module, measured again.
Args:
graph: the model, carrying the observed data the prior draws are
compared against.
simulation: the subject. Its ``parameter_source`` must be the PRIOR:
a simulation from a posterior or from fixed values answers a
different question, and is reported INAPPLICABLE rather than
silently scored as though it were this one.
discrepancies: as in :func:`posterior_predictive_check`.
Returns:
An :class:`~bayesmith.artifacts.reports.EvaluationReport` of kind
``prior_predictive_check``. Prior draws are iid, so every draw
carries weight ``1/N``: there is no source posterior to read weights
from and none is invented.
Raises:
TypeError: for the wrong kind of argument, or an empty discrepancy set.
ValueError: for a discrepancy with no importable identity.prior_sensitivity_report
Defined in bayesmith.evaluation.diagnostics, line 264.
prior_sensitivity_report(graph: Graph, subject: PosteriorResult, *, names: Sequence[str] | str | None=None, at: dict[str, jax.Array] | None=None) -> EvaluationReportFile :func:`~bayesmith.diagnose.sensitivity.prior_sensitivity`'s worst
mode displacement as a report about ``subject``.
§0.2's row: PASS while the worst ``|shift_sigma|`` is below
:data:`~bayesmith.diagnose.sensitivity.CRITERION_SHIFT`, FAIL at or above
it, ABSTAIN when the closed form and the refit did not verify each other,
UNVERIFIABLE when the diagnostic refused. The verification is read FIRST;
the module docstring says why.
Args:
graph: the model, built inside ``with jax.enable_x64(True):``.
subject: the result this report judges.
names: which latents' priors to analyse; ``None`` means all of them.
at: where to expand, as ``prior_sensitivity`` takes it.
Returns:
An :class:`~bayesmith.artifacts.reports.EvaluationReport` whose
``worst_shift`` finding carries ``(latent, index, shift)`` against
``CRITERION_SHIFT``, and whose ``prior_width_margin`` finding carries
the declared width against the width at which that latent's shift
would reach it.sbc_ranks
Defined in bayesmith.evaluation.sbc, line 411.
sbc_ranks(graph: Graph, *, key: jax.Array, replicates: int, model_ref: ModelRef, build: GraphBuilder | None=None, budget: ComputeBudget | None=None, nuts_on_collapse: bool=False, sampler: Sampler | None=None, sampler_draws: int=0, subject_ref: ArtifactRef | None=None) -> SbcRanks | RefusalRun the replicate loop and return its ranks, unjudged.
Args:
graph: the model, used for the prior simulation. Its own observed data
is not read -- ``SimulationTask(PRIOR)`` draws the observations
from their own distributions.
key: PRNG key. Split once into a simulation key and a replicate key,
then ``fold_in`` per replicate, so replicate i is reproducible on
its own and adding replicates does not disturb the earlier ones.
replicates: how many ``(theta_true, y)`` pairs to draw. Never
defaulted: a replicate count is a budget, and D106 is a floor on
it rather than a value for it.
model_ref: what the model callable is, for every compiled task.
build: ``build(data) -> Graph``, the route arm. Required unless
``sampler`` is given.
budget: the per-replicate posterior budget for the route arm.
nuts_on_collapse: passed to each replicate's ``PosteriorTask``. The
default is False so that a route that collapses is COUNTED as an
unusable replicate rather than silently answered by a different
route -- SBC is a statement about one route.
sampler: the §0.11 arm. ``sampler(datum, key, n) -> {name: draws}``.
sampler_draws: ``n`` for the sampler arm.
subject_ref: required with ``sampler``, which produces no Result to
point at; ignored by the route arm, which uses the first usable
replicate's posterior (§0.2's "routing representative").
Returns:
:class:`SbcRanks`, or the ``Refusal`` that stopped the prior
simulation. A REPLICATE's refusal is never returned: §0.6 says a
refused replicate is COUNTED and the report abstains, so propagating
one would replace a verdict about a calibration run with a verdict
about its first bad draw.sbc_report
Defined in bayesmith.evaluation.sbc, line 636.
sbc_report(ranks: SbcRanks, *, alpha: float=ALPHA, floor: int=REPLICATE_FLOOR) -> EvaluationReportJudge one :class:`SbcRanks`, on §0.2's two axes. * Any replicate that produced no rank -> APPLICABLE x ABSTAIN, finding ``replicates_not_completed``, with all three counts. Checked BEFORE the floor, because "45 of 100 replicates diverged" and "you only asked for 45" are different facts and the first is the one worth reporting. * Fewer usable replicates than D106 -> APPLICABLE x ABSTAIN, finding ``replicates_below_floor``. * Otherwise every coordinate's KS p against ``alpha / K`` decides: all at or above -> PASS, any below -> FAIL. ``alpha / K`` is the Bonferroni correction §0.4 derives for K simultaneous coordinates; it is a derivation from D104 and carries no threshold of its own. The per-coordinate findings record the level they were compared against, so the verdict can be recomputed from the report -- which is §8's gate G8, and the reason the level is in ``expected`` rather than only in the message.
simulation_based_calibration
Defined in bayesmith.evaluation.sbc, line 734.
simulation_based_calibration(graph: Graph, *, key: jax.Array, replicates: int, model_ref: ModelRef, build: GraphBuilder | None=None, budget: ComputeBudget | None=None, nuts_on_collapse: bool=False, sampler: Sampler | None=None, sampler_draws: int=0, subject_ref: ArtifactRef | None=None, alpha: float=ALPHA, floor: int=REPLICATE_FLOOR) -> EvaluationReport | RefusalRun the replicates and judge them: :func:`sbc_ranks` then :func:`sbc_report`. The convenience the gate runner calls. It decides nothing the two halves do not: everything about which arm runs is in the first, everything about the verdict is in the second, and this function only refuses to judge what could not be measured.