bayesmith.compiled
Source-derived signatures and contracts. See the reference index for coverage and conventions.
Module contract
The compiled residual problem, at the leaf so both layers may import it. **Why this is a module of its own and not in ``dispatch/evidence.py``, where it was written.** R5 Task 6's adapter lives in ``bridge/`` and must assert what it was handed BY TYPE. Measured, the declared direction is ``dispatch -> bridge``: ``bridge/`` imports only ``graph/`` and ``distributions`` at module scope and never ``dispatch/``, while ``dispatch/`` imports ``bridge/`` at ``evidence.py`` and at ``execute.py``. A module-scope import of this class from ``bridge/`` would therefore close a cycle that ``tests/test_layering.py::test_the_module_scope_import_graph_is_acyclic`` raises on. Plan 6.2 offers two ways out and says only one keeps "dependency direction is unchanged" true. The other -- importing it inside the adapter's function -- leaves the module-scope graph untouched, so the layering test passes while the runtime dependency exists. That test's SUBJECT is which layer depends on which; its IMPLEMENTATION is which import statements sit at module scope. Taking the function-scope route would be choosing to satisfy the instrument rather than the property, knowingly, and three separate guards in this repository have already been walked past exactly that way. So the class moves to a leaf: ``dispatch -> compiled`` and ``bridge -> compiled``, with no new edge between them. It was cheap to move -- a plain frozen dataclass, **not** a registered artifact type, and nothing under ``src/`` imported it. ``dispatch.evidence`` re-exports it, so existing imports keep working. Not ``bayesmith/evidence/``: that is a deprecated alias for :mod:`bayesmith.marginal`, retiring at 1.0, whose own docstring argues the word was wrong there. Not ``artifacts/``: this is not an artifact. 〔Design 0.17: the parent ``CompiledProblem`` this is nominally a variant of has never been written, and R5 does not write it. This module's name leaves room for it without claiming it exists.〕
CompiledEvidenceProblem
Defined in bayesmith.compiled, line 44.
class CompiledEvidenceProblem
log_prior: Any
log_likelihood: Any
prior_sample: Any
exact_elimination: tuple[str, ...]
residual_parameters: tuple[str, ...]
shapes: tuple[tuple[str, tuple[int, ...]], ...]
prior_terms: tuple[str, ...]
likelihood_terms: tuple[str, ...]A residual evidence problem, with its prior and likelihood separated.
Nested sampling needs ``log L(theta)`` apart from ``log pi(theta)``, and
nothing else in this package produces that separation:
:func:`~bayesmith.graph.evaluate.log_joint` sums every ``Probabilistic``
node into ONE running total and then adds ``joint_prior`` and every
``evidence_terms`` entry in the same loop. Building the split is a compiler
pass and bayesmith owns it -- design section 1.5's first clause is graph
semantics, structure discovery, premise validation and task-aware
compilation.
**No FIELD is a Graph, and the densities close over one.** Both halves are
asserted, because stating only the first is how a guard that reads a
spelling gets written: an earlier test here walked the field values for a
``.nodes`` attribute, and they are functions, so it passed while the graph
was reachable through every one of the three closures.
Design line 192 says a compiled problem may contain the residual log
density, transforms, constant terms and a reconstruction map, "but it may
not re-interpret the Graph". What that forbids is a BACKEND doing the
re-interpreting. bayesmith compiling the densities itself, by closing over
the graph, is the contract kept rather than broken -- the adapter receives
callables and cannot reach a graph without walking closure internals. The
half that binds an adapter is asserted where an adapter exists: its module
imports no ``Graph`` and its signature takes only a compiled problem.
The parent type design line 482 names, ``CompiledProblem``, does not exist
in this package -- the name appears six times in the design and nowhere in
the source -- so this is built standalone and to line 192's contract, in
order that the variant relation is a refactor rather than a redesign when
the parent is written.
Attributes:
log_prior: ``theta -> log pi(theta)``, the latent nodes' own densities
plus the graph-level ``joint_prior``.
log_likelihood: ``theta -> log L(theta)``, the observed nodes' densities
(honouring ``observed_mask``) plus every graph-level
``evidence_terms`` entry. Those hold graph-level LIKELIHOOD factors
despite the field's name, which the R4 PLAN records and declines to
rename (`2026-09-04-r4-evidence.md:650`; the close-out does not
mention the field at all, and an earlier version of this line cited
it). The assignment is asserted by consequence -- the prior side
integrates to one -- and never by the name.
prior_sample: ``key -> theta``, a draw from the prior. Strictly
stronger than a prior that integrates to one, and it is what a
nested sampler actually needs: ``improper_outside_prior`` raises
here rather than returning a number.
exact_elimination: latents already integrated in closed form.
residual_parameters: latents the backend must integrate. Disjoint from
``exact_elimination`` -- ``InferencePlanRecord`` already refuses a
name in both, and the two would otherwise disagree about which
parameters a backend was handed.
shapes: the parameter layout, ``(name, shape)`` per residual parameter.
prior_terms, likelihood_terms: which term went to which side. Carried
as data so that a test can assert every term is filed exactly once
without re-deriving the partition it is grading.