Skip to content

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.

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.

Conventional slice value (-1) denoting an atemporal (static) variable, to be used wherever a slice index is expected.

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

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 (ATEMPORAL for a static endpoint)
    • headSlice (int) – slices of the tail and head (ATEMPORAL for a static endpoint)
    • tail (str) – alternatively, engine names for both endpoints (bracket notation)
    • head (str) – alternatively, engine names for both endpoints (bracket notation)
  • Raises:
  • Return type: None

Shortcut for add(var, False): adds an atemporal (static) variable.

Shortcut for add(var, True): adds a temporal process.

  • Returns: all arcs, each as a (tail, head) pair of (base, slice) endpoints
  • Return type: tuple[tuple[tuple[str, int], tuple[str, int]], ...]
  • Returns: the base names of the atemporal variables
  • Return type: set[str]
  • 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
  • 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

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
  • Raises:
  • Return type: None

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 (ATEMPORAL for 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 (ATEMPORAL if atemporal)
  • Return type: tuple[tuple[str, int], ...]

Remove all variables and arcs, keeping the order k.

  • Return type: None

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 (ATEMPORAL for an atemporal node)
    • node_name (str) – alternatively, the node’s engine name
  • Returns: the (mutable) conditional probability table
  • Return type: Tensor
  • Returns: True if the template contains no variable
  • Return type: bool

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

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 (ATEMPORAL for a static endpoint)
    • headSlice (int) – slices of the tail and head (ATEMPORAL for 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
  • Parameters: base (str) – a base name
  • Returns: True if a variable with this base name exists
  • Return type: bool
  • 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 (ATEMPORAL for a static endpoint)
    • headSlice (int) – slices of the tail and head (ATEMPORAL for 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

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 (ATEMPORAL for 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:
  • Return type: None

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 by pyagrum.KTBN.toBN()). In that case the whole network is read that way: every temporal node name must be base[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()
2

Bracket 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

Randomly generate the CPT of a single node.

  • Parameters:
    • base (str) – the base name of the node
    • slice (int) – the slice of the node (ATEMPORAL for an atemporal node)
    • node_name (str) – alternatively, the node’s engine name
  • Return type: None

Randomly generate every CPT of the template.

  • Return type: None
  • Returns: the order k of the k-TBN
  • Return type: int

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
  • Returns: the number of atemporal variables
  • Return type: int
  • Returns: the number of temporal processes
  • Return type: int
  • Returns: all nodes as (base, slice) pairs; an atemporal node uses ATEMPORAL as its slice
  • Return type: tuple[tuple[str, int], ...]

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 (ATEMPORAL for 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 (ATEMPORAL if atemporal)
  • Return type: tuple[tuple[str, int], ...]

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
  • Returns: the number of nodes in the template (all slices)
  • Return type: int
  • Returns: the number of arcs in the template
  • Return type: int

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
  • Returns: the base names of the temporal processes
  • Return type: set[str]
  • Parameters: var (DiscreteVariable) – a variable of this k-TBN
  • Returns: the time slice of var, or ATEMPORAL if it is atemporal
  • Return type: int
  • Raises: pyagrum.NotFound – if var is not a node of this k-TBN
  • Returns: a deep copy of the underlying template, as a plain Bayesian network (nodes named with the bracket-encoded engine names)
  • Return type: BayesNet
  • Returns: a Graphviz dot string with one cluster per time slice of the template
  • Return type: str
  • 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)
  • Returns: a Graphviz dot string
  • Return type: str
  • Raises: pyagrum.OperationNotAllowed – if T < k

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

Returns the variable of a (process, slice) couple.

  • Parameters:
    • base (str) – the base name of the node
    • slice (int) – the slice of the node (ATEMPORAL for an atemporal node)
    • node_name (str) – alternatively, the node’s engine name
  • Returns: the variable
  • Return type: DiscreteVariable
  • Raises:

pyagrum.ktbn.KTBNGenerator draws a random k-TBN template, mainly to build ground-truth models for learning experiments.

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]

  • Returns: a freshly drawn model
  • Return type: KTBN

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
  • 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

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]

Set the range domain sizes are drawn uniformly from.

  • Parameters:
    • minModality (int) – the minimum domain size (>= 2)
    • maxModality (int) – the maximum domain size (>= minModality)
  • Returns: self, for chaining
  • Return type: KTBNGenerator
  • Raises: pyagrum.InvalidArgument – if minModality < 2 or maxModality < minModality

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

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

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

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
  • Returns: self, for chaining
  • Return type: KTBNGenerator
  • Raises: pyagrum.InvalidArgument – if a prefix is empty or the two are equal