bayesmithDocumentation0.10.0 source

bayesmith.graph.evaluate

Source-derived signatures and contracts. See the reference index for coverage and conventions.

Module contract
Running a graph forward, and reading its log-density.

Evaluation is one linear scan in topological order: every node's parents are
already in the environment by the time it is reached, which is what the
ordering rule in :class:`~bayesmith.graph.graph.Graph` buys.

Env

Defined in bayesmith.graph.evaluate, line 21.

Env = dict[str, Any]

apply_deterministic

Defined in bayesmith.graph.evaluate, line 40.

apply_deterministic(graph: Graph, node: Deterministic, env: Env) -> Any
Call ``node.fn``, vmapped over the plate the node lives in.

Public because the NumPyro bridge re-runs the same scan and must apply
plates identically; a private name used across modules is not private.

apply_probabilistic

Defined in bayesmith.graph.evaluate, line 60.

apply_probabilistic(graph: Graph, node: Probabilistic, env: Env) -> Any
Build ``node``'s distribution, vmapping ``dist_fn`` over its plate.

Public for the same reason as :func:`apply_deterministic`: ``log_joint``
and the NumPyro bridge must build the *same* distribution for the *same*
node, so both call this one function instead of each independently
calling ``node.dist_fn`` unmapped. Before this function existed, both
call sites made that same independent (and wrong) choice, which is
exactly why the bridge/log_joint cross-check could not catch it -- two
scans with an identical blind spot agree with each other by
construction, whether or not they agree with the truth.

Deliberately does **not** raise when a plated node has no plated parent,
unlike :func:`apply_deterministic`. That refusal exists there because
``fn``'s return value is an arbitrary array that must already be the
right shape -- there is no fallback that is still correct, so refusing
is the only safe option. A distribution has a real fallback: calling
``dist_fn`` once, unmapped, yields one shared distribution whose
``log_prob``/``sample`` already know how to broadcast themselves against
a plate-shaped value -- plain array broadcasting in ``log_joint``, and
``numpyro.plate``'s own ``.expand()`` in ``to_numpyro``. That is not a
workaround, it is the ordinary "N iid draws from one shared prior"
pattern -- already relied on today by a plated node with *no* parents at
all (see ``test_a_plated_latent_site_carries_the_plate_axis``). Refusing
it here would break that already-correct, already-tested pattern for no
safety benefit: there is no shape for dist_fn's output to get wrong,
only a distribution object whose own broadcasting contract handles it.

evaluate

Defined in bayesmith.graph.evaluate, line 106.

evaluate(graph: Graph | ReducedGraph, values: Mapping[str, Any] | None=None) -> Env
Compute every node's value.

Args:
    graph: the graph to run.
    values: values for the latent nodes. Observed nodes take their own
        data and must not appear here; deterministic and constant nodes
        are computed.

Returns:
    A mapping from node name to value, covering every node in the graph.

Raises:
    GraphError: if a latent node has no value, or ``values`` names
        something that is not a latent node.

graph_density

Defined in bayesmith.graph.evaluate, line 176.

graph_density(graph: Graph, term: Any, env: Env, *, label: str, names: tuple[str, ...]) -> jax.Array
Evaluate one graph-level term and require one scalar contribution.

Shared by both density scans. NumPyro otherwise sums a vector-valued
``factor`` while :func:`log_joint` broadcasts it, so accepting a vector
would make the two scans disagree without either one raising.

log_joint

Defined in bayesmith.graph.evaluate, line 201.

log_joint(graph: Graph | ReducedGraph, values: Mapping[str, Any] | None=None) -> jax.Array
The joint log-density of the graph at ``values``.

Every probabilistic node contributes ``log_prob`` of its value under the
distribution its parents parameterise; deterministic nodes contribute
nothing but the dependence they carry.

A node's :attr:`~bayesmith.graph.nodes.Probabilistic.observed_mask` drops
the samples it says were never taken. That is the whole of masking on THIS
side -- there is no infinite scale to take a limit of, because a sample
that was not observed contributes no term rather than a term whose width
went to infinity, and the two differ by exactly the ``log sigma`` that
would have sent the joint to ``-inf``.

Type a concept or API name.