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:
|
Meaning |
|---|---|
|
A parametric family ( |
|
A deterministic function of the parents, with an optional |
|
An explicit table over discrete child and parents; compiles to
|
|
Bins one continuous parent into the child’s discrete states. |
|
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¶
distributionA parametric family whose parameters are expressions over the parents.
equationA deterministic function of the parents.
discrete_cptAn explicit conditional probability table; compiles to
tbnpy.cpt.Cpt.discretiseBins a continuous parent into the child’s discrete states.
customA 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.
nameis the key in theprobsdict.- 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’schilds.- Type:
The variables this node owns
- 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.
tolerancewidens the equality thatlog_probaccepts. 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.
probabilitieshas 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.
- 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.
intervalsis 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.
- 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 givenchildsandparents(anddevicewhen its signature accepts one) plus everything inkwargs.A node owning several variables, such as a field drawn jointly over many sites, lists them in
childsinstead of naming onechild; they reach the class in that order.- property child_names: list¶
[child], or a custom node’schilds.- 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]¶