mixle.stats.latent.probabilistic_circuit module

Probabilistic circuits (sum-product networks) – a tractable deep model that scores in integer log-space.

A probabilistic circuit is a DAG of sum nodes (mixtures, with weights), product nodes (independent factorizations over disjoint variable scopes), and leaf distributions over a scope. When it is decomposable (a product’s children have pairwise-disjoint scopes) and smooth (a sum’s children share one scope) the density is exact and inference is linear in the circuit size – the appeal over an intractable deep net. Every node is a sum or a product of probabilities, so the whole forward pass runs in mixle’s logarithmic number system: products become integer ADDs, sums become integer logsumexp (the compiled max+LUT kernel), and leaf log-densities are quantized – a transcendental-free deep forward pass.

This is the model class where the LNS is a complete fit (not just the normalizer). v1 takes a user-supplied structure (build it with leaf() / prod() / summ()) and learns the per-leaf parameters and per-sum log-weights by EM; structure learning is a later phase that emits the same DAG.

leaf(scope, dist)[source]

A leaf node: an existing mixle dist over the variable indices scope (an int or a tuple).

Parameters:
Return type:

_Node

prod(children)[source]

A product node over children with PAIRWISE-DISJOINT scopes (the decomposability requirement).

Parameters:

children (list[_Node])

Return type:

_Node

summ(children, w=None)[source]

A sum (mixture) node over children that share ONE scope (smoothness); w are mixing weights.

Parameters:
Return type:

_Node

class ProbabilisticCircuitDistribution(root, num_vars, lns_step=None)[source]

Bases: SequenceEncodableProbabilityDistribution

A sum-product network density; build with leaf()/prod()/summ() then pass the root.

Parameters:
  • root (_Node)

  • num_vars (int)

  • lns_step (float | None)

log_density(x)[source]

Return the log-density or log-mass at a single observation.

Parameters:

x (Any)

Return type:

float

seq_log_density(x)[source]

Return vectorized log-density values for sequence-encoded observations.

Parameters:

x (Any)

Return type:

ndarray

dist_to_encoder()[source]

Return the data encoder used by this distribution for vectorized methods.

Return type:

ProbabilisticCircuitEncoder

sampler(seed=None)[source]

Return a sampler for drawing observations from this distribution.

Parameters:

seed (int | None)

Return type:

ProbabilisticCircuitSampler

estimator(pseudo_count=None)[source]

Return an estimator for fitting this distribution from data.

Parameters:

pseudo_count (float | None)

Return type:

Any

with_params(new_nodes, new_leaf_dists)[source]

A new circuit with the same structure but re-estimated sum-weights / leaf parameters (M-step output).

Parameters:
Return type:

ProbabilisticCircuitDistribution

class ProbabilisticCircuitEncoder(leaf_dists, leaf_scope)[source]

Bases: DataSequenceEncoder

Encode each leaf’s projected columns once with the leaf’s own encoder (shared across EM iterations).

Parameters:
seq_encode(x)[source]

Encode the iid observation sequence x for vectorized evaluation.

Parameters:

x (Any)

Return type:

dict[int, Any]

class ProbabilisticCircuitSampler(dist, seed=None)[source]

Bases: DistributionSampler

Ancestral top-down sampling: a sum draws one child by its weights, a product recurses into all.

Parameters:
  • dist (ProbabilisticCircuitDistribution)

  • seed (int | None)

sample(size=None)[source]

Draw observations.

Combinator samplers (mixture/sequence/…) accept batched. With batched=True (the default) each child stream is drawn in one vectorized call instead of a per-draw Python loop – far faster. Because every child sampler owns an independent RandomState, batching consumes each stream in the same order as the loop, so the draws are identical to the legacy path. batched=False forces that legacy per-draw loop as a guaranteed- stable reference. Leaf samplers are already vectorized and ignore the flag.

Parameters:

size (int | None)

Return type:

Any

class ProbabilisticCircuitAccumulator(nodes, leaf_scope, leaf_estimators)[source]

Bases: SequenceEncodableStatisticAccumulator

E-step sufficient statistics: per-sum-node expected child counts + per-leaf weighted statistics.

The E-step is the circuit FLOW (Peharz et al. EM-for-SPNs): an upward forward gives each node’s log-value, a downward pass gives each node’s log-context lc = derivative of the root log-density w.r.t. that node (the posterior the node is active). A sum node’s per-child responsibility is then exp(lc[sum] + value[child] + log_w - value[sum]) (its expected count), and a leaf’s responsibility is exp(lc[leaf]) (the weight for its sufficient statistic).

Parameters:
seq_update(enc, weights, estimate)[source]
Parameters:
  • enc (dict[int, Any])

  • weights (Any)

  • estimate (ProbabilisticCircuitDistribution)

Return type:

None

update(x, weight, estimate)[source]
Parameters:
  • x (Any)

  • weight (float)

  • estimate (ProbabilisticCircuitDistribution)

Return type:

None

initialize(x, weight, rng)[source]
Parameters:
Return type:

None

seq_initialize(enc, weights, rng)[source]
Parameters:
Return type:

None

combine(suff_stat)[source]
Parameters:

suff_stat (Any)

Return type:

ProbabilisticCircuitAccumulator

value()[source]
Return type:

Any

from_value(x)[source]
Parameters:

x (Any)

Return type:

ProbabilisticCircuitAccumulator

scale(c)[source]

Scale linear sufficient statistics in-place by c.

The structural default is correct for ordinary weighted sums, nested tuples/lists/dicts, and numeric arrays. Families whose value() payload includes non-linear metadata such as support bounds must override this method and leave that metadata unscaled.

Parameters:

c (float)

Return type:

ProbabilisticCircuitAccumulator

key_merge(stats_dict)[source]

Pool this accumulator’s statistics into stats_dict under its merge key.

The structural default implements the common single-key pattern: store the accumulator under self.keys the first time the key is seen, else combine into the one already there. Accumulators with several named keys (e.g. an HMM’s init/trans/state keys) or a non-accumulator stats payload override this. A keys of None (the default) is a no-op.

Parameters:

stats_dict (dict[str, Any])

Return type:

None

key_replace(stats_dict)[source]

Replace this accumulator’s statistics from the pooled stats_dict entry (see key_merge).

Parameters:

stats_dict (dict[str, Any])

Return type:

None

acc_to_encoder()[source]
Return type:

ProbabilisticCircuitEncoder

class ProbabilisticCircuitAccumulatorFactory(nodes, leaf_scope, leaf_estimators)[source]

Bases: StatisticAccumulatorFactory

Parameters:
make()[source]
Return type:

ProbabilisticCircuitAccumulator

class ProbabilisticCircuitEstimator(dist, pseudo_count=None)[source]

Bases: ParameterEstimator

Fits a fixed-structure circuit by EM: renormalize each sum node’s weights to its expected child counts, and re-estimate each leaf from its responsibility-weighted sufficient statistic.

Parameters:
  • dist (ProbabilisticCircuitDistribution)

  • pseudo_count (float | None)

accumulator_factory()[source]
Return type:

ProbabilisticCircuitAccumulatorFactory

estimate(nobs, suff_stat)[source]
Parameters:
Return type:

ProbabilisticCircuitDistribution