The k-TBN model and its generator
pyagrum.ktbn.KTBN is the main class for representing and manipulating
k-order dynamic Bayesian networks in pyAgrum. It stores a compact template of
k time slices, distinguishing temporal processes from atemporal (static)
variables, and can be unrolled into a plain pyagrum.BayesNet for any
number of time slices.
class pyagrum.ktbn.KTBN(*args)
Section titled “class pyagrum.ktbn.KTBN(*args)”KTBN represents a k-order dynamic Bayesian network (k-TBN): a compact template of k consecutive time slices that fully characterizes a time-homogeneous dynamic process. It generalizes the classical 2-TBN by letting a variable at time t depend on up to the k most recent time slices instead of a single step backward.
A KTBN distinguishes two kinds of variables:
: - temporal variables (processes), which evolve through time and have one
instance per slice (added with addTemporal());
- atemporal variables, constant through time (e.g. a static context), which
have a single instance (added with
addAtemporal()).
Internally, nodes are addressed either by an engine name using bracket notation
("X[2]" for the 3rd slice of process X, or a bare name such as "C" for
an atemporal variable), or, in most of the public API, by an explicit
(base, slice) pair – slice being ATEMPORAL (-1) for an atemporal
variable. Both spellings are accepted almost everywhere.
The template can be unrolled into a plain pyagrum.BayesNet for any number
of time slices with unroll().
KTBN(k=2) -> KTBN : Parameters: : - k (int) – the order of the k-TBN (number of time slices in the template). Must be >= 1.
- Raises: pyagrum.InvalidArgument – If k is 0.
ATEMPORAL = -1
Section titled “ATEMPORAL = -1”Conventional slice value (-1) denoting an atemporal (static) variable, to be
used wherever a slice index is expected.
add(*args)
Section titled “add(*args)”Add a variable to the k-TBN.
A temporal variable (process) is cloned into k instances (one per slice); an atemporal variable is added once.
- Parameters:
- var (pyagrum.DiscreteVariable) – the variable to add (added by copy)
- temporal (bool) – whether the variable is temporal (default True)
- Raises:
- pyagrum.DuplicateLabel – if a variable with that base name already exists (of any kind – base names are globally unique)
- pyagrum.InvalidArgument – if adding the variable would create a name collision between a temporal and
an atemporal variable (e.g. an atemporal
"X[0]"when a temporal process"X"already exists)
- Return type:
None
addArc(*args)
Section titled “addArc(*args)”Add an arc between two (process, slice) endpoints.
- Parameters:
- tailBase (str) – base names of the tail and head variables
- headBase (str) – base names of the tail and head variables
- tailSlice (int) – slices of the tail and head (
ATEMPORALfor a static endpoint) - headSlice (int) – slices of the tail and head (
ATEMPORALfor a static endpoint) - tail (str) – alternatively, engine names for both endpoints (bracket notation)
- head (str) – alternatively, engine names for both endpoints (bracket notation)
- Raises:
- pyagrum.NotFound – if an endpoint does not exist
- pyagrum.OutOfBounds – if a slice is out of range for a temporal endpoint
- pyagrum.OperationNotAllowed – if the arc violates temporal causality (a temporal endpoint pointing to an atemporal one, or an arc from a future slice to a past slice)
- pyagrum.DuplicateElement – if the arc already exists
- pyagrum.InvalidDirectedCycle – if the arc would create a cycle
- Return type:
None
addAtemporal(*args)
Section titled “addAtemporal(*args)”Shortcut for add(var, False): adds an atemporal (static) variable.
- Parameters: var (pyagrum.DiscreteVariable) – the variable to add (added by copy)
- Return type:
None
addTemporal(*args)
Section titled “addTemporal(*args)”Shortcut for add(var, True): adds a temporal process.
- Parameters: var (pyagrum.DiscreteVariable) – the variable to add (added by copy)
- Return type:
None
arcs()
Section titled “arcs()”- Returns: all arcs, each as a (tail, head) pair of (base, slice) endpoints
- Return type:
tuple[tuple[tuple[str,int],tuple[str,int]],...]
atemporalVarNames()
Section titled “atemporalVarNames()”- Returns: the base names of the atemporal variables
- Return type:
set[str]
baseName(var)
Section titled “baseName(var)”- Parameters:
var (
DiscreteVariable) – a variable of this k-TBN - Returns: the base name of var (without the bracket-encoded slice)
- Return type:
str - Raises: pyagrum.NotFound – if var is not a node of this k-TBN
bnToDot()
Section titled “bnToDot()”- Returns: the Graphviz dot string of the underlying storage BayesNet, with nodes labelled by their internal engine names (no time-slice clustering)
- Return type:
str
changeVariableName(oldBase, newBase)
Section titled “changeVariableName(oldBase, newBase)”Rename a variable (temporal process or atemporal variable). For a temporal process, all k slice names are updated accordingly.
- Parameters:
- oldBase (
str) – the current base name - newBase (
str) – the new base name
- oldBase (
- Raises:
- pyagrum.NotFound – if no variable named oldBase exists
- pyagrum.DuplicateLabel – if a variable named newBase already exists
- pyagrum.InvalidArgument – if newBase is empty or would create a name collision
- Return type:
None
children(*args)
Section titled “children(*args)”Returns the children of a node, as (base, slice) pairs.
- Parameters:
- base (str) – the base name of the node (used together with slice)
- slice (int) – the slice of the node (
ATEMPORALfor an atemporal node) - node_name (str) – alternatively, the node’s engine name (e.g.
"X[1]", or"C")
- Returns:
the children, as (base, slice) pairs (
ATEMPORALif atemporal) - Return type:
tuple[tuple[str,int],...]
clear()
Section titled “clear()”Remove all variables and arcs, keeping the order k.
- Return type:
None
cpt(*args)
Section titled “cpt(*args)”Returns the conditional probability table of a (process, slice) couple. The
returned pyagrum.Tensor is a live reference: use any of its methods
(e.g. fillWith) to fill it, or use fillCPT() for an order-safe,
parent-name-based alternative.
- Parameters:
- base (str) – the base name of the node
- slice (int) – the slice of the node (
ATEMPORALfor an atemporal node) - node_name (str) – alternatively, the node’s engine name
- Returns: the (mutable) conditional probability table
- Return type:
Tensor
empty()
Section titled “empty()”- Returns: True if the template contains no variable
- Return type:
bool
erase(base)
Section titled “erase(base)”Remove a variable (and all its incident arcs). For a temporal process, all k slice nodes are removed. The kind (temporal or atemporal) is determined automatically.
- Parameters:
base (
str) – the base name of the variable to remove - Raises: pyagrum.NotFound – if no variable with that name exists
- Return type:
None
eraseArc(*args)
Section titled “eraseArc(*args)”Remove an arc between two (process, slice) endpoints.
- Parameters:
- tailBase (str) – base names of the tail and head variables
- headBase (str) – base names of the tail and head variables
- tailSlice (int) – slices of the tail and head (
ATEMPORALfor a static endpoint) - headSlice (int) – slices of the tail and head (
ATEMPORALfor a static endpoint) - tail (str) – alternatively, engine names for both endpoints
- head (str) – alternatively, engine names for both endpoints
- Raises: pyagrum.NotFound – if an endpoint, or the arc itself, does not exist
- Return type:
None
exists(base)
Section titled “exists(base)”- Parameters:
base (
str) – a base name - Returns: True if a variable with this base name exists
- Return type:
bool
existsArc(*args)
Section titled “existsArc(*args)”- Parameters:
- tailBase (str) – base names of the tail and head variables
- headBase (str) – base names of the tail and head variables
- tailSlice (int) – slices of the tail and head (
ATEMPORALfor a static endpoint) - headSlice (int) – slices of the tail and head (
ATEMPORALfor a static endpoint) - tail (str) – alternatively, engine names for both endpoints
- head (str) – alternatively, engine names for both endpoints
- Returns: True if the arc exists
- Return type:
bool
fillCPT(*args)
Section titled “fillCPT(*args)”Fill one conditional distribution P(node | parent configuration), addressing the node and its parents by their (base, slice) identity rather than by CPT positional order.
Every parent of the node must be listed, in any order, as a dict entry keyed by
either an engine name ("X[1]") or a (base, slice) tuple (use
ATEMPORAL as slice for a static parent). A parent’s value is either its
modality index (int) or its modality label (str).
Note
Key spellings may only be mixed in the same dict when the target node
itself is addressed by its engine name (the node_name form below). When
the target is addressed as (base, slice), every parent key must also be
a (base, slice) tuple – a plain engine-name key is then rejected.
Warning
An unquoted integer is always a modality index, a quoted string always a modality label. The two differ for a variable whose modalities are themselves numeric.
Examples
>>> m.fillCPT("C", ktbn.KTBN.ATEMPORAL, {}, [0.4, 0.6]) # P(C), no parents>>> m.fillCPT("X", 1, {("X", 0): 1, ("C", -1): 0}, [0.6, 0.4]) # P(X[1] | X[0]=1, C=0)>>> m.fillCPT("X[1]", {"X[0]": "1", "C": 0}, [0.6, 0.4]) # same, engine names- Parameters:
- base (str) – base name of the target node (used together with slice)
- slice (int) – slice of the target node (
ATEMPORALfor a static node) - node_name (str) – alternatively, the target node’s engine name
- parents (dict) – one (engine-name or (base,slice)) -> (index or label) entry per parent
- distribution (list *[*float ]) – probabilities over the node’s own modalities for that parent configuration; its length must equal the node’s domain size
- Raises:
- pyagrum.NotFound – if the node or a parent does not exist, or if a modality label is unknown
- pyagrum.OutOfBounds – if a modality index is out of range
- pyagrum.SizeError – if distribution’s length differs from the node’s domain size, or if a parent is missing
- pyagrum.InvalidArgument – if a dict entry’s node is not actually a parent of the target
- Return type:
None
static fromBN(*args)
Section titled “static fromBN(*args)”Build a k-TBN from an existing pyagrum.BayesNet, reading its node names
under one of two mutually exclusive conventions – picked automatically, never
mixed within one network:
- Bracket convention: used as soon as any node name already carries the
engine’s own
base[t]bracket notation (e.g. produced bypyagrum.KTBN.toBN()). In that case the whole network is read that way: every temporal node name must bebase[t]with a plain decimal slice (no leading zeros), any other name is atemporal. - Bracket-free convention: used when no node name carries a bracket at
all. A name ending in a run of digits denotes a temporal variable at the
timeslice given by that integer – the base is everything before the
digits, so
'X0'and'X12'both belong to process'X', at slices 0 and 12 respectively. Any other name (no trailing digit) is atemporal.
In both conventions, the order k is inferred as one plus the largest slice
index found. A group of same-base nodes that does not cover every slice
0..k-1 is not an error: each of its nodes is reclassified as an atemporal
variable (original name kept), with a warning – pass the name explicitly in
atemporalNodes to silence it. When no temporal process survives, k falls
back to 1.
Examples
Bracket-free convention (a plain, hand-built or foreign BN):
>>> bn = pyagrum.BayesNet()>>> bn.add(pyagrum.LabelizedVariable('X0', '', 2))>>> bn.add(pyagrum.LabelizedVariable('X1', '', 2))>>> bn.add(pyagrum.LabelizedVariable('C', '', 2)) # atemporal: no trailing digit>>> bn.addArc('X0', 'X1')>>> bn.addArc('C', 'X1')>>> ktbn = pyagrum.KTBN.fromBN(bn)>>> ktbn.k()2Bracket convention (round-tripping a KTBN’s own storage BN):
>>> ktbn2 = pyagrum.KTBN.fromBN(ktbn.toBN())- Parameters:
- bn (pyagrum.BayesNet) – the source Bayesian network (copied)
- atemporalNodes (set *[*str ]) – node names – exactly as they appear in bn – to classify atemporal outright. Only needed to lift an ambiguity: a name the active convention already reads as atemporal changes nothing by being listed, while listing a temporal-shaped name says ‘I meant this atemporally’ where the reclassification above would only guess (and warn) (default: empty)
- Returns: the reconstructed k-TBN
- Return type:
KTBN - Raises:
- pyagrum.NotFound – if a name in atemporalNodes is not a node of bn
- pyagrum.OperationNotAllowed – if the temporal structure of bn is inconsistent: two variables mapping to the same (process, slice), a base name used both as an atemporal variable and as a temporal process, an arc from a temporal node into an atemporal one, or an arc from the future to the past
generateCPT(*args)
Section titled “generateCPT(*args)”Randomly generate the CPT of a single node.
- Parameters:
- base (str) – the base name of the node
- slice (int) – the slice of the node (
ATEMPORALfor an atemporal node) - node_name (str) – alternatively, the node’s engine name
- Return type:
None
generateCPTs()
Section titled “generateCPTs()”Randomly generate every CPT of the template.
- Return type:
None
- Returns: the order k of the k-TBN
- Return type:
int
static load(filename)
Section titled “static load(filename)”Load a k-TBN from a file produced by save(), selected by the file
extension.
- Parameters:
filename (
str) – the GUM file to read - Returns: the loaded k-TBN
- Return type:
KTBN - Raises: pyagrum.IOError – if the file cannot be read or is not valid
nbAtemporalVars()
Section titled “nbAtemporalVars()”- Returns: the number of atemporal variables
- Return type:
int
nbTemporalVars()
Section titled “nbTemporalVars()”- Returns: the number of temporal processes
- Return type:
int
nodes()
Section titled “nodes()”- Returns:
all nodes as (base, slice) pairs; an atemporal node uses
ATEMPORALas its slice - Return type:
tuple[tuple[str,int],...]
parents(*args)
Section titled “parents(*args)”Returns the parents of a node, as (base, slice) pairs.
- Parameters:
- base (str) – the base name of the node (used together with slice)
- slice (int) – the slice of the node (
ATEMPORALfor an atemporal node) - node_name (str) – alternatively, the node’s engine name (e.g.
"X[1]", or"C")
- Returns:
the parents, as (base, slice) pairs (
ATEMPORALif atemporal) - Return type:
tuple[tuple[str,int],...]
save(filename)
Section titled “save(filename)”Save the k-TBN in a jgum (text/JSON) or bgum (binary/msgpack) file, selected by
the file extension (.jgum for text, anything else for binary).
- Parameters:
filename (
str) – the destination file - Return type:
None
size()
Section titled “size()”- Returns: the number of nodes in the template (all slices)
- Return type:
int
sizeArcs()
Section titled “sizeArcs()”- Returns: the number of arcs in the template
- Return type:
int
summaryGraph()
Section titled “summaryGraph()”Returns the Graphviz dot string of the summary graph: the projection of the transition kernel alone (the pattern that actually repeats through time when unrolling), not the whole template.
Every process (temporal or atemporal) becomes a single node. Only arcs whose head lies in the last time slice (k-1) are kept – an arc between two earlier slices belongs to the initial-condition structure (slices 0..k-2), not to the repeated pattern, and is dropped. Several arcs may connect the same pair of nodes when the kernel depends on more than one lag: each is kept and labelled with its lag (head slice minus tail slice). An arc from an atemporal variable has no lag and is left unlabelled.
- Returns: a Graphviz dot string
- Return type:
str
temporalVarNames()
Section titled “temporalVarNames()”- Returns: the base names of the temporal processes
- Return type:
set[str]
timeSlice(var)
Section titled “timeSlice(var)”- Parameters:
var (
DiscreteVariable) – a variable of this k-TBN - Returns:
the time slice of var, or
ATEMPORALif it is atemporal - Return type:
int - Raises: pyagrum.NotFound – if var is not a node of this k-TBN
toBN()
Section titled “toBN()”- Returns: a deep copy of the underlying template, as a plain Bayesian network (nodes named with the bracket-encoded engine names)
- Return type:
BayesNet
toDot()
Section titled “toDot()”- Returns: a Graphviz dot string with one cluster per time slice of the template
- Return type:
str
toString()
Section titled “toString()”- Returns: a human-readable description of the k-TBN
- Return type:
str
toUnrolledDot(T, highlightReplicated=False)
Section titled “toUnrolledDot(T, highlightReplicated=False)”Returns a Graphviz dot string of the k-TBN unrolled over T time slices, without
actually materializing the unrolled pyagrum.BayesNet.
- Parameters:
- T (
int) – total number of time slices to display; must be >= k - highlightReplicated (
bool) – if True, shade the replicated slices (>= k) differently from the template slices (default False)
- T (
- Returns: a Graphviz dot string
- Return type:
str - Raises: pyagrum.OperationNotAllowed – if T < k
unroll(nbTimeSlices)
Section titled “unroll(nbTimeSlices)”Unroll the k-TBN into a standard Bayesian network with exactly nbTimeSlices time slices. Slices 0..k-1 are copied verbatim from the template; every additional slice replicates the transition kernel (slice k-1), shifting each temporal parent’s slice accordingly so that lags are preserved.
- Parameters:
nbTimeSlices (
int) – the total number of time slices of the unrolled network; must be >= k - Returns:
the unrolled Bayesian network (nodes named
base[slice], atemporal variables keeping their bare name) - Return type:
BayesNet - Raises: pyagrum.OperationNotAllowed – if nbTimeSlices < k
variable(*args)
Section titled “variable(*args)”Returns the variable of a (process, slice) couple.
- Parameters:
- base (str) – the base name of the node
- slice (int) – the slice of the node (
ATEMPORALfor an atemporal node) - node_name (str) – alternatively, the node’s engine name
- Returns: the variable
- Return type:
DiscreteVariable - Raises:
- pyagrum.NotFound – if no such variable exists
- pyagrum.OutOfBounds – if slice is out of range for a temporal variable
- pyagrum.OperationNotAllowed – if the temporal/atemporal kind does not match slice
pyagrum.ktbn.KTBNGenerator draws a random k-TBN template, mainly to
build ground-truth models for learning experiments.
class pyagrum.ktbn.KTBNGenerator(*args)
Section titled “class pyagrum.ktbn.KTBNGenerator(*args)”KTBNGenerator draws a random pyagrum.ktbn.KTBN template: variables, a
legal arc set and (optionally) random CPTs. It is the k-TBN counterpart of
pyAgrum’s Bayesian network generators, mainly used to build ground-truth models
for learning experiments: sample trajectories from a generated model, learn it
back, and compare.
Acyclicity comes for free: a cross-slice arc can never close a cycle since the slice index strictly increases along it, so the generator only needs to aNone cycles among same-slice (temporal, lag 0) and atemporal-to-atemporal arcs, which it does by drawing one random ranking per family and only admitting arcs from lower to higher rank.
Examples
>>> import pyagrum as gum>>> import pyagrum.ktbn as ktbn>>> pyagrum.initRandom(42) # reproducible>>> gen = ktbn.KTBNGenerator(3, 4, 1) # k=3, 4 temporal, 1 atemporal>>> gen.setDensity(0.15).setMaxParents(4)>>> model = gen.generate()KTBNGenerator(k, nbTemporal, nbAtemporal=0, maxArcs=0, maxModality=2) -> KTBNGenerator
: Parameters:
: - k (int) – order of the generated k-TBN (number of template
slices); must be >= 1
- nbTemporal (int) – number of temporal processes
- nbAtemporal (int) – number of atemporal variables
- maxArcs (int) – hard cap on the number of arcs; 0 (default)
derives it from the density instead (see setDensity())
- maxModality (int) – largest domain size; domains are drawn
uniformly in [2, maxModality]
- Raises: pyagrum.InvalidArgument – if k is 0 or maxModality is below 2
generate()
Section titled “generate()”- Returns: a freshly drawn model
- Return type:
KTBN
generateKTBN(out)
Section titled “generateKTBN(out)”Fill out with a freshly drawn model (its previous content is discarded). Seed
with pyagrum.initRandom() for reproducibility.
- Parameters:
out (
KTBN) – the k-TBN to fill - Return type:
None
- Returns: the order of the generated models
- Return type:
int
nbLegalArcs()
Section titled “nbLegalArcs()”- Returns: how many arcs the k-TBN’s own rules allow, given the current shape; the density is a fraction of this, and it is also the ceiling any maxArcs is silently clamped to
- Return type:
int
setDensity(density)
Section titled “setDensity(density)”Set the fraction of the legal arc set to draw, in [0,1]. Ignored when a non-zero maxArcs was given to the constructor. Default 0.1.
- Parameters:
density (
float) – the density, in [0,1] - Returns: self, for chaining
- Return type:
KTBNGenerator - Raises: pyagrum.OutOfBounds – if density is outside [0,1]
setDomainRange(minModality, maxModality)
Section titled “setDomainRange(minModality, maxModality)”Set the range domain sizes are drawn uniformly from.
- Parameters:
- minModality (
int) – the minimum domain size (>= 2) - maxModality (
int) – the maximum domain size (>= minModality)
- minModality (
- Returns: self, for chaining
- Return type:
KTBNGenerator - Raises: pyagrum.InvalidArgument – if minModality < 2 or maxModality < minModality
setGenerateCPTs(on)
Section titled “setGenerateCPTs(on)”Whether to fill the CPTs with random values (default True). When False, only the structure is drawn and the CPTs stay at their default content.
- Parameters:
on (
bool) – whether to generate random CPTs - Returns: self, for chaining
- Return type:
KTBNGenerator
setGuaranteeOrder(on)
Section titled “setGuaranteeOrder(on)”Force one arc of lag k-1 into the kernel slice, so the generated model’s effective Markov order really equals k (default True). Without this, a random draw may end up with no arc of lag k-1, making the true order lower than k and undetectable from data sampled off the model. No-op when k = 1 or there is no temporal process.
- Parameters:
on (
bool) – whether to guarantee the effective order - Returns: self, for chaining
- Return type:
KTBNGenerator
setMaxParents(maxParents)
Section titled “setMaxParents(maxParents)”Cap the number of parents of any node, which bounds CPT size. 0 (the default) means unlimited – a dense draw can then produce very large CPTs, so set this when generating dense or high-k models.
- Parameters:
maxParents (
int) – the maximum number of parents per node - Returns: self, for chaining
- Return type:
KTBNGenerator
setNamePrefixes(temporal, atemporal)
Section titled “setNamePrefixes(temporal, atemporal)”Set the name prefixes used for generated variables: temporal processes are
named prefix0, prefix1, … Defaults are "X" (temporal) and
"A" (atemporal).
- Parameters:
- temporal (
str) – the prefix for temporal process names - atemporal (
str) – the prefix for atemporal variable names
- temporal (
- Returns: self, for chaining
- Return type:
KTBNGenerator - Raises: pyagrum.InvalidArgument – if a prefix is empty or the two are equal