tbnpy.spec

A model can be written as data, a JSON file of variables and nodes, instead of as Python classes. This module defines that format and validates it; tbnpy.compile turns it into the varis and probs the rest of tbnpy uses.

A spec has five node types:

type

Meaning

distribution

A parametric family (normal, lognormal, categorical, …) whose parameters are expressions over the parents (tbnpy.expr).

equation

A deterministic function of the parents, with an optional tolerance.

discrete_cpt

An explicit table over discrete child and parents; compiles to Cpt.

discretise

Bins one continuous parent into the child’s discrete states.

custom

A hand-written probability class, imported by module and class name. Anything the other types cannot express is written in Python and referenced from here.

Every node owns one variable, named by child (by default, the node’s own name), except a custom node, which may own several. It lists them in childs, and its class receives them in that order, as a field drawn jointly over many sites needs:

{"name": "ground-motion", "type": "custom",
 "module": "my_hazard", "cls": "GroundMotionField",
 "childs": ["SA::site1", "SA::site2", "SA::site3"],
 "parents": [],
 "kwargs": {"mw": 7.0, "sites": [[36.6, -121.9], [36.7, -121.6], [36.9, -121.4]]}}

kwargs are the class’s constructor arguments, so a spec built from custom nodes records each probability object as its class, its variables and its arguments. Files using childs declare "spec_version": 2; earlier files are version 1 and load unchanged.

The ABCDE example’s model as a spec is examples/ABCDE/abcde.tbn.json; see ABCDE example.

{
  "name": "ABCDE",
  "description": "The ABCDE toy network of s1_define_model.py, written declaratively. Compiling it gives the same model as the hand-written classes c.py, oc.py and e.py.",
  "variables": [
    {"name": "A", "kind": "discrete", "states": [-0.3, 0.3], "meaning": "first additive shift"},
    {"name": "B", "kind": "discrete", "states": [-0.2, 0.0, 0.2], "meaning": "second additive shift"},
    {"name": "C", "kind": "continuous", "meaning": "the quantity of interest"},
    {"name": "OC", "kind": "continuous", "meaning": "a noisy observation of C"},
    {"name": "D", "kind": "discrete", "states": [0, 1], "meaning": "gate: 0 passes C through to E, 1 blocks it"},
    {"name": "E", "kind": "continuous", "meaning": "C when the gate is open, otherwise 0"}
  ],
  "nodes": [
    {"name": "A", "type": "discrete_cpt", "parents": [], "probabilities": [0.5, 0.5]},
    {"name": "B", "type": "discrete_cpt", "parents": [], "probabilities": [0.3, 0.4, 0.3]},
    {"name": "C", "type": "distribution", "parents": ["A", "B"], "family": "normal",
     "parameters": {"loc": "A + B", "scale": "0.6"},
     "meaning": "C | A, B ~ Normal(A + B, 0.6^2)"},
    {"name": "OC", "type": "distribution", "parents": ["C"], "family": "normal",
     "parameters": {"loc": "C", "scale": "0.5"},
     "meaning": "OC | C ~ Normal(C, 0.5^2)"},
    {"name": "D", "type": "discrete_cpt", "parents": [], "probabilities": [0.4, 0.6]},
    {"name": "E", "type": "equation", "parents": ["C", "D"], "equation": "If(D == 0, C, 0)",
     "meaning": "E = C if D == 0, else 0"}
  ],
  "analysis": {
    "query_nodes": ["C", "E"],
    "observed_nodes": ["OC"],
    "notes": "Condition on OC with tbnpy.inference.sample_evidence; E is deterministic, so observe OC rather than E."
  }
}

API

Declarative model specification for tbnpy.

A spec is a plain data description of a Bayesian network – its variables, its graph, and how each node is quantified – that can be written by hand, generated from a natural-language description, exported from GeNIe, or edited in a GUI. tbnpy.compile turns a spec into the varis and probs objects the rest of the library works with.

The spec deliberately describes only the model. It carries nothing about how inference should be run, because tbnpy exists precisely for problems that need bespoke inference algorithms, and a model description that presumed one algorithm would be a description of the wrong thing. Hints about intended analysis live in the optional AnalysisSpec and are never required to build a model.

Node types

distribution

A parametric family whose parameters are expressions over the parents.

equation

A deterministic function of the parents.

discrete_cpt

An explicit conditional probability table; compiles to tbnpy.cpt.Cpt.

discretise

Bins a continuous parent into the child’s discrete states.

custom

A hand-written node class, imported by module and name. Bespoke nodes stay first-class citizens: anything the declarative types cannot express is written in Python and referenced from here. A custom node may own several child variables (childs), such as a field drawn jointly over many sites; every other type owns one (child).

Serialisation is JSON (stdlib only). YAML is accepted too when pyyaml happens to be installed, but it is never required.

exception tbnpy.spec.SpecError[source]

Raised when a specification is malformed or internally inconsistent.

class tbnpy.spec.VariableSpec(name: str = '', kind: str = 'continuous', states: list | None = None, support: list | None = None, bounds: list | None = None, unit: str = '', meaning: str = '')[source]

One random variable.

kind

"continuous" or "discrete".

Type:

str

states

Discrete only: the state labels, worst first by tbnpy convention. State indices are 0 .. len(states) - 1; the labels are what expressions see when the variable is used as a value.

Type:

list | None

support

Continuous only: (low, high) for the variable’s actual support. Defaults to the whole real line.

Type:

list | None

bounds

Optional plausibility range, used by diagnostics to flag samples that leave it. Not a truncation – GeNIe bounds are frequently only a display range, so the two concepts are kept apart deliberately.

Type:

list | None

property has_numeric_states: bool

Whether the state labels can be used as values in an expression.

class tbnpy.spec.NodeSpec(name: str = '', child: str = '', parents: list = <factory>, meaning: str = '', references: list = <factory>, metadata: dict = <factory>, type: str = '')[source]

Base for every node type. name is the key in the probs dict.

metadata: dict

Free-form, carried through to the compiled object as .spec_metadata. Bespoke inference algorithms can stash whatever they need here without the spec format having to know about it.

property child_names: list

[child], or a custom node’s childs.

Type:

The variables this node owns

validate_against(variables: dict) → None[source]

Type-specific checks that need the variable table. Overridden below.

class tbnpy.spec.DistributionNode(name: str = '', child: str = '', parents: list = <factory>, meaning: str = '', references: list = <factory>, metadata: dict = <factory>, type: str = 'distribution', family: str = '', parameters: dict = <factory>)[source]

A parametric family; each parameter is an expression over the parents.

class tbnpy.spec.EquationNode(name: str = '', child: str = '', parents: list = <factory>, meaning: str = '', references: list = <factory>, metadata: dict = <factory>, type: str = 'equation', equation: str = '', tolerance: float = 0.0)[source]

A deterministic function of the parents.

tolerance widens the equality that log_prob accepts. It defaults to zero, matching exact deterministic nodes. Note that a zero tolerance makes the node impossible to condition on directly when the child is continuous: every particle gets -inf. Conditioning on such a node needs a tolerance, an explicit observation node, or discretisation.

class tbnpy.spec.DiscreteCptNode(name: str = '', child: str = '', parents: list = <factory>, meaning: str = '', references: list = <factory>, metadata: dict = <factory>, type: str = 'discrete_cpt', probabilities: list = <factory>)[source]

An explicit CPT over discrete child and discrete parents.

probabilities has one row per parent-state combination and one column per child state. Rows are ordered with the last parent varying fastest (C order over the parents), which matches GeNIe’s flat CPT layout. A root node has a single row.

validate_against(variables: dict) → None[source]

Type-specific checks that need the variable table. Overridden below.

class tbnpy.spec.DiscretiseNode(name: str = '', child: str = '', parents: list = <factory>, meaning: str = '', references: list = <factory>, metadata: dict = <factory>, type: str = 'discretise', intervals: list = <factory>)[source]

Bins one continuous parent into the child’s discrete states.

intervals is a list of [low, high] pairs, one per child state, in state order. Bins are half-open [low, high) except the last, which includes its upper edge.

validate_against(variables: dict) → None[source]

Type-specific checks that need the variable table. Overridden below.

class tbnpy.spec.CustomNode(name: str = '', child: str = '', parents: list = <factory>, meaning: str = '', references: list = <factory>, metadata: dict = <factory>, type: str = 'custom', module: str = '', cls: str = '', kwargs: dict = <factory>, childs: list = <factory>)[source]

A hand-written node class, imported and constructed by the compiler.

The class must follow the tbnpy node protocol: childs, parents, sample, log_prob. It is given childs and parents (and device when its signature accepts one) plus everything in kwargs.

A node owning several variables, such as a field drawn jointly over many sites, lists them in childs instead of naming one child; they reach the class in that order.

property child_names: list

[child], or a custom node’s childs.

Type:

The variables this node owns

class tbnpy.spec.AnalysisSpec(query_nodes: list = <factory>, observed_nodes: list = <factory>, notes: str = '', options: dict = <factory>)[source]

Intended-use hints. Advisory only; no compiler behaviour depends on it.

options: dict

Free-form room for algorithm-specific settings, kept out of the model proper so that adding a bespoke inference method never changes the model format.

class tbnpy.spec.ModelSpec(name: 'str' = '', description: 'str' = '', variables: "'list'" = <factory>, nodes: "'list'" = <factory>, analysis: "'AnalysisSpec | None'" = None, open_questions: "'list'" = <factory>, source: 'str' = '', spec_version: 'int' = 2)[source]
validate() → ModelSpec[source]

Check the spec is internally consistent. Returns self.

topological_order() → list[source]

Node names ordered so parents precede children.

tbnpy.spec.load_spec(path, validate: bool = True) → ModelSpec[source]

Load a spec from .json (always) or .yaml (if pyyaml is present).

tbnpy.spec.save_spec(spec: ModelSpec, path) → Path[source]

Write a spec as formatted JSON.