Browse documentation
Docs/Language

Finite discrete inference

DISCRETEPOSTERIOR(model, evidence, queries) updates a small finite model from observations. It returns posterior marginal tables, evidence probability and the complete request needed to…

Finite discrete inference

DISCRETE_POSTERIOR(model, evidence, queries) updates a small finite model from observations. It returns posterior marginal tables, evidence probability and the complete request needed to reproduce the answer. Computation is a pure reactive operation in the existing Rust LIR runtime; defining or reading a model value creates no worker, job or external effect.

Use a named ordinary record for the reusable model and a separate call for each query. The complete example is fault diagnosis.

fault_model = {
  schemaVersion: 1,
  id: "machine",
  variables: [
    {id: "fault", states: ["healthy", "faulty"]},
    {id: "alarm", states: ["quiet", "sounding"]}
  ],
  factors: [
    {id: "prior", scope: ["fault"], weights: [0.9, 0.1]},
    {id: "sensor", scope: ["fault", "alarm"], weights: [0.8, 0.2, 0.2, 0.8]}
  ]
}

posterior = DISCRETE_POSTERIOR(fault_model, {alarm: "sounding"}, ["fault"])
marginals = posterior.marginals

The evidence probability is 0.26, and the posterior probability of faulty is 4/13 (approximately 0.307692). Observing quiet instead gives 1/37. Changing the prior weights to [0.8, 0.2] with a sounding alarm gives 0.5. Ordinary input dependencies recompute these results when assumptions change.

Model and query contract

  • schemaVersion must be 1; id identifies the authored model. Every variable, factor and state label contains 1–256 UTF-8 bytes. Identities are case-sensitive.
  • variables gives ordered variable IDs and nonempty ordered state labels. Variable IDs and each variable's states must be unique.
  • The nonempty factors list gives unique IDs, an ordered scope of variable IDs and flat nonnegative finite weights. Each scope variable occurs once. Its last variable varies fastest. The number of weights equals the product of its domain sizes. An empty scope is a scalar factor with one weight.
  • Weights are general potentials. The joint distribution is their product, normalized over all assignments. Unmentioned variables have uniform weight. Conditional tables in the example follow the same rule; there is no implicit conditional-row normalization or separate CPT syntax.
  • evidence is a record mapping observed variable IDs to declared state labels. An empty record means no observations. No soft evidence or automatic state coercion is performed.
  • queries is a nonempty list of distinct variable IDs. Each requested marginal retains its variable's state order. This profile returns single-variable marginals, not an arbitrary joint output table.

Unknown fields, variables/states, duplicate identities, wrong table shapes, negative/nonfinite weights and all-zero models are invalid. Shared factors can represent dependent variables; independence is not inferred from different names.

Reports, errors and persistence

A report contains schemaVersion, status, request, assignmentsEvaluated, evidenceProbability, logEvidenceProbability and marginals. Each marginal contains variable, states and probabilities. request retains the complete model, evidence and queries: the caller's model ID alone is never treated as proof that weights or observations are unchanged.

complete means the admitted state space was exhaustively evaluated using binary64 arithmetic. It does not mean exact rational arithmetic. Log-domain accumulation avoids multiplying tiny weights into a false zero. A positive evidence probability can underflow in evidenceProbability; its finite logEvidenceProbability and complete status distinguish this from impossibility.

impossibleEvidence means a valid model assigns exactly zero weight to the observations. Its probability is zero, log probability is null and marginals are empty. An invalid model produces a VALUE error containing DISCRETE_INVALID; an exceeded admission limit produces DISCRETE_LIMIT. The controlled Rust owner supports cancellation during admission and enumeration and returns the caller's error, never a partial posterior. This synchronous source function introduces no separate cancellation job or progress service.

Models, query expressions and source input edits use the existing source and LIR persistence. Saved LIR works without debug data. Runtime input overrides follow the normal model-input persistence contract; serializing the original LIR alone does not write transient overrides into its source. Retained reports include the actual request and can be reproduced through the same native inference owner. Structure or weights are admitted anew at query ingress after edits; this first profile does not retain a hidden incremental inference cache.

Bounds and execution ownership

The profile admits at most 32 variables, 64 states per variable, 256 factors, 65,536 joint assignments, 262,144 total factor entries, and 32 queried marginals. Checked admission also bounds work to 16,777,216 units: joint assignments times (variable count + query count + sum of factor scope lengths plus one per factor). Output is bounded by 2,048 marginal probabilities plus the bounded request. There is no automatic approximate fallback when a limit is exceeded.

The portable advanced-inference envelope uses kind: "discrete" with the same {model, evidence, queries} request. Its existing import/run/emit operations carry distinct problem kind 5 and the version-1 model schema; existing normal-linear, HMC, variational, particle-filter and Bayesian-optimization kinds retain their wire identities. Unknown kinds/schemas fail closed. Both source calls and this envelope execute the same solver-owned implementation.

This profile supplies observational conditioning, not causal identification, interventions or counterfactuals. Continuous models, plates, approximate discrete inference, Stan/BUGS import and automatic fault-tree conversion remain outside its scope. Existing CAUSAL, uncertainty propagation and reliability analysis continue to serve their respective contracts.

Frozen extension catalogs remain unchanged. Native source and the advanced LIR envelope support this operation; it is not automatically exposed to extension guests through an existing frozen function API.