mixle.stats.graphs.temporal_graph_grammar module

Temporal (dynamic) graph grammar – a distribution over graph SEQUENCES you can score, fit, and sample.

A dynamic graph is observed as a sequence of adjacency snapshots [A_0, A_1, ..., A_T] (binary, undirected; nodes may be appended and edges added over time – a growth process). The model is a Markov chain over graphs whose transition kernel is a stochastic motif-edit grammar:

given G_{t-1}, draw a number of new edges, and produce each by firing a grammar rule – “add an edge that creates motif m” – where the rule is chosen from the motif distribution w and an anchor is chosen uniformly among the non-edges of G_{t-1} that instantiate that motif.

So the grammar EDITS the graph over time, and its rule weights ARE the motif distribution it imposes. The default motif family bins a candidate edge by how many triangles it would close (its number of common neighbours: 0 = a bridge, 1, 2, 3+), i.e. a learnable triadic-closure profile; a custom mutually-exclusive motif partition can be supplied instead. Because the bins are mutually exclusive each added edge has a single motif, so scoring and fitting are exact (no per-edge latent – the VRG/HRG grammars marginalise over derivations; here the derivation is read off the snapshots).

Edges both FORM and DISSOLVE: an ADD grammar (motif_weights / edge_rate) draws new edges by the motif each would create, and a separate REMOVE grammar (remove_weights / edge_remove_rate) deletes existing edges by the motif each is part of (so e.g. growth can favour triadic closure while decay favours bridges – ties in dense neighbourhoods persist). Removal defaults off, so the constructor is backward compatible and a pure-growth grammar still scores a deletion as -inf.

Adjacencies may be dense ndarray or scipy.sparse – scoring and fitting never form the n*n bin matrix (they touch only the changed edges and the wedge structure of A @ A), so a 200k-node graph scores in a fraction of a second where a dense adjacency would need hundreds of GB. (Sampling stays dense/moderate-scale.) LabeledTemporalGraphGrammarDistribution attaches node attributes (location, name, age, …) and edge attributes (communication counts, channel, …) as ordinary mixle distributions scored as emissions on top of the topology – the whole thing fits jointly with the full distribution machinery (mixtures, every leaf family, the numba fusion).

Graphs may be directed (directed=True): the adjacency is asymmetric (i->j and j->i are distinct edges), the candidate space is the full off-diagonal, and A @ A counts transitive i->k->j paths – a directed triadic-closure profile. Weighted edges are just an edge attribute: put a weight distribution (Poisson volume, Gaussian strength, …) in the labeled model’s edge_dist, so a directed + weighted + attributed dynamic graph is a directed structure composed with node/edge emission models.

Nodes also LEAVE: ChurningTemporalGraphGrammarDistribution tracks stable node identities (each snapshot is (adjacency, node_ids)) so a transition can remove nodes – those whose id disappears, their edges vanishing with them – before running the edit grammar on the surviving subgraph. Node churn is a thin wrapper: identity alignment + a node-removal Poisson term on top of all the motif/edge machinery.

The dynamics carry a HIDDEN REGIME: LatentTemporalGraphGrammarDistribution is an HMM whose emission models ARE the edit grammars – a latent Markov state z_t selects which of K grammars governs transition t, so the graph switches phases over time (bursty growth/densification, then fragmentation/decay – dynamics a single grammar cannot produce). The sequence likelihood marginalises the regime path by the forward algorithm; EM (Baum-Welch) runs forward-backward then a per-regime weighted M-step reusing each grammar’s accumulator; decode (Viterbi) recovers the active regime at each step.

This is the temporal counterpart of the static vertex-/hyperedge-replacement grammars in this package. Scope: undirected or directed, binary topology (attribute models carry weights/labels); edges add+remove, nodes join+leave; dense or sparse scoring, dense or scalable (rejection) sampling; optional hidden regime. Sparse-path churn and directed scalable sampling are the remaining extensions.

class CommonNeighbourMotif(bins=(0, 1, 2, 3), directed=False)[source]

Bases: object

A motif rule keyed by how many common neighbours a candidate edge has (triangles it would close).

bins is an increasing list of thresholds; bin b covers common-neighbour counts in [bins[b], bins[b+1]) with the last bin open-ended. The default [0, 1, 2, 3] gives the interpretable {bridge, closes-1, closes-2, closes-3+} partition. A non-edge falls in exactly one bin, so the motifs partition every candidate edge.

Parameters:
  • bins (Sequence[int])

  • directed (bool)

property num_motifs: int
assign(adj, on_edges=False)[source]

Motif bin of every candidate pair (and -1 on non-candidates / diagonal).

The common-neighbour count of a pair (i, j) is (A @ A)[i, j] – for a non-edge, how many triangles adding it would CLOSE; for an existing edge, how many triangles it is PART of. Binning by self.bins gives its motif. With on_edges=False the candidates are the non-edges (addition); with on_edges=True they are the existing edges (removal). Non-candidates and the diagonal -> -1.

Parameters:
Return type:

ndarray

counts_and_binner(adj, on_edges)[source]

Return (candidate_counts[M], lookup(i, j) -> motif index) WITHOUT forming the n*n bin matrix.

Sparse-scalable: only the existing edges (O(m)) and the non-edges that close a triangle (O(wedges) = A @ A’s nonzeros) are ever enumerated; the bridge count (cn=0 non-edges) is the analytic remainder pairs - edges - wedge_non_edges. The lookup reads (A @ A)[i, j] for the handful of observed edges. (For graphs with mega-hubs the wedge set itself is large – the documented limit.)

Parameters:
Return type:

tuple

class TemporalGraphGrammarDistribution(motif_weights, edge_rate=1.0, node_rate=0.0, remove_weights=None, edge_remove_rate=0.0, motif=None, directed=False, name=None)[source]

Bases: SequenceEncodableProbabilityDistribution

Distribution over dynamic graphs (sequences of adjacency snapshots) under a motif-edit grammar.

Parameters:
  • motif_weights (Sequence[float])

  • edge_rate (float)

  • node_rate (float)

  • remove_weights (Sequence[float] | None)

  • edge_remove_rate (float)

  • motif (CommonNeighbourMotif | None)

  • directed (bool)

  • name (str | None)

transition_components(prev, cur)[source]

The PARAMETER-INDEPENDENT decomposition of a transition: (new_nodes, add_bins, add_cand, rem_bins, rem_cand, valid). Depends only on the graph pair and the motif, NOT on the grammar’s weights/rates – so K regimes sharing a motif can compute the (expensive A@A) decomposition ONCE and score it K times via score_components(). valid is False for an impossible node removal.

Parameters:
Return type:

tuple

score_components(components)[source]

Score a precomputed transition_components() decomposition under THIS grammar’s parameters.

Parameters:

components (tuple)

Return type:

float

log_density(x)[source]

Log-density of one dynamic graph: the sum of transition log-densities over the snapshot chain.

x is a sequence of binary adjacency matrices – dense ndarray or scipy.sparse (large graphs). The initial graph is taken as given (its marginal is not modelled, matching how the static grammars treat their start symbol).

Parameters:

x (Sequence[ndarray])

Return type:

float

seq_encode(x)[source]
Parameters:

x (Sequence[Sequence[ndarray]])

Return type:

Sequence[Sequence[ndarray]]

seq_log_density(x)[source]

Return vectorized log-density values for sequence-encoded observations.

Parameters:

x (Sequence[Sequence[ndarray]])

Return type:

ndarray

sampler(seed=None)[source]

Return a sampler for drawing observations from this distribution.

Parameters:

seed (int | None)

Return type:

TemporalGraphGrammarSampler

estimator(pseudo_count=None)[source]

Return an estimator for fitting this distribution from data.

Parameters:

pseudo_count (float | None)

Return type:

TemporalGraphGrammarEstimator

dist_to_encoder()[source]

Return the data encoder used by this distribution for vectorized methods.

Return type:

TemporalGraphGrammarDataEncoder

class TemporalGraphGrammarSampler(dist, seed=None)[source]

Bases: DistributionSampler

Parameters:
  • dist (TemporalGraphGrammarDistribution)

  • seed (int | None)

sample_one(num_steps=10, seed_graph=None, n_init=5)[source]

Run a derivation: from a seed graph, apply num_steps of grammar-sampled edits.

Parameters:
Return type:

list[ndarray]

sample_one_scalable(num_steps=10, seed_edges=None, n_init=5, max_reject=64)[source]

Sample a dynamic graph for a LARGE sparse graph – never materialises the n*n adjacency.

The dense sample_one() is exact but O(n^2) in space (the full bin matrix). This path keeps the graph as an edge set and emits scipy.sparse snapshots, costing O(edges + wedges) per step:

  • triangle-closing motifs (cn>=1) are exactly the wedge non-edges – the nonzeros of A @ A that aren’t edges – so they are enumerated directly from the wedge structure, never the full pair grid;

  • bridges (cn=0) dominate a sparse graph and can’t be enumerated, so they are rejection-sampled: draw random pairs and accept those that are neither an edge nor a wedge (acceptance ~ 1 when the graph is sparse). max_reject attempts per bridge bound the loop; a shortfall is honest capping (same realized-rate semantics as the dense sampler when a motif’s anchors run out).

Growth+removal; undirected or directed (directed: ordered i->j edges, full off-diagonal candidates, A@A = transitive i->k->j wedges). Returns a list of csr_array snapshots. The realized motif distribution matches the weights, so a model fit on these snapshots recovers the grammar.

Parameters:
Return type:

list

sample(size=None, *, num_steps=10, n_init=5)[source]

Draw observations.

Combinator samplers (mixture/sequence/…) accept batched. With batched=True (the default) each child stream is drawn in one vectorized call instead of a per-draw Python loop – far faster. Because every child sampler owns an independent RandomState, batching consumes each stream in the same order as the loop, so the draws are identical to the legacy path. batched=False forces that legacy per-draw loop as a guaranteed- stable reference. Leaf samplers are already vectorized and ignore the flag.

Parameters:
  • size (int | None)

  • num_steps (int)

  • n_init (int)

Return type:

Any

class TemporalGraphGrammarEstimator(motif=None, pseudo_count=None, name=None)[source]

Bases: ParameterEstimator

Learn the motif distribution (rule weights) + edge/node rates from observed dynamic graphs.

Parameters:
  • motif (CommonNeighbourMotif | None)

  • pseudo_count (float | None)

  • name (str | None)

accumulator_factory()[source]
Return type:

TemporalGraphGrammarAccumulatorFactory

estimate(nobs, suff_stat)[source]
Parameters:
Return type:

TemporalGraphGrammarDistribution

class TemporalGraphGrammarAccumulator(motif)[source]

Bases: SequenceEncodableStatisticAccumulator

Accumulate per-motif edge counts + step/edge/node totals – the exact sufficient statistics.

Parameters:

motif (CommonNeighbourMotif)

update(x, weight, estimate)[source]
Parameters:
Return type:

None

seq_update(x, weights, estimate)[source]
Parameters:
Return type:

None

seq_initialize(x, weights, rng)[source]
Parameters:
Return type:

None

initialize(x, weight, rng)[source]
Parameters:
Return type:

None

combine(suff_stat)[source]
Parameters:

suff_stat (tuple)

Return type:

TemporalGraphGrammarAccumulator

value()[source]
Return type:

tuple

from_value(x)[source]
Parameters:

x (tuple)

Return type:

TemporalGraphGrammarAccumulator

key_merge(stats_dict)[source]

Pool this accumulator’s statistics into stats_dict under its merge key.

The structural default implements the common single-key pattern: store the accumulator under self.keys the first time the key is seen, else combine into the one already there. Accumulators with several named keys (e.g. an HMM’s init/trans/state keys) or a non-accumulator stats payload override this. A keys of None (the default) is a no-op.

Parameters:

stats_dict (dict)

Return type:

None

key_replace(stats_dict)[source]

Replace this accumulator’s statistics from the pooled stats_dict entry (see key_merge).

Parameters:

stats_dict (dict)

Return type:

None

acc_to_encoder()[source]
Return type:

TemporalGraphGrammarDataEncoder

class TemporalGraphGrammarAccumulatorFactory(motif)[source]

Bases: StatisticAccumulatorFactory

Parameters:

motif (CommonNeighbourMotif)

make()[source]
Return type:

TemporalGraphGrammarAccumulator

class TemporalGraphGrammarDataEncoder[source]

Bases: DataSequenceEncoder

seq_encode(x)[source]

Encode the iid observation sequence x for vectorized evaluation.

Parameters:

x (Sequence[Sequence[ndarray]])

Return type:

Sequence[Sequence[ndarray]]

class LabeledTemporalGraphGrammarDistribution(structure, node_dist=None, edge_dist=None, name=None)[source]

Bases: SequenceEncodableProbabilityDistribution

A dynamic graph whose nodes and edges carry attributes.

Composes a structural TemporalGraphGrammarDistribution (the topology over time) with two ordinary mixle distributions: node_dist over per-node attribute records (location, name, age, … – typically a CompositeDistribution of leaves or a mixture) and edge_dist over per-edge attribute records (communication counts, channel, weight, …). An observation is (snapshots, node_features, edge_features): the adjacency chain, one attribute record per node, and one per added edge. The likelihood factorises – structure x node attributes x edge attributes – so the attribute models are fit (and scored) with the full mixle distribution machinery (mixtures, fusion, all leaf families).

Parameters:
  • structure (TemporalGraphGrammarDistribution)

  • node_dist (SequenceEncodableProbabilityDistribution | None)

  • edge_dist (SequenceEncodableProbabilityDistribution | None)

  • name (str | None)

log_density(x)[source]

Return the log-density or log-mass at a single observation.

Parameters:

x (tuple)

Return type:

float

seq_encode(x)[source]
Parameters:

x (Sequence[tuple])

Return type:

Sequence[tuple]

seq_log_density(x)[source]

Return vectorized log-density values for sequence-encoded observations.

Parameters:

x (Sequence[tuple])

Return type:

ndarray

sampler(seed=None)[source]

Return a sampler for drawing observations from this distribution.

Parameters:

seed (int | None)

Return type:

LabeledTemporalGraphGrammarSampler

estimator(**kw)[source]

Return an estimator for fitting this distribution from data.

Parameters:

kw (Any)

Return type:

LabeledTemporalGraphGrammarEstimator

dist_to_encoder()[source]

Return the data encoder used by this distribution for vectorized methods.

Return type:

TemporalGraphGrammarDataEncoder

class LabeledTemporalGraphGrammarSampler(dist, seed=None)[source]

Bases: DistributionSampler

Parameters:
  • dist (LabeledTemporalGraphGrammarDistribution)

  • seed (int | None)

sample_one(**kw)[source]
Parameters:

kw (Any)

Return type:

tuple

sample(size=None, **kw)[source]

Draw observations.

Combinator samplers (mixture/sequence/…) accept batched. With batched=True (the default) each child stream is drawn in one vectorized call instead of a per-draw Python loop – far faster. Because every child sampler owns an independent RandomState, batching consumes each stream in the same order as the loop, so the draws are identical to the legacy path. batched=False forces that legacy per-draw loop as a guaranteed- stable reference. Leaf samplers are already vectorized and ignore the flag.

Parameters:
Return type:

Any

class LabeledTemporalGraphGrammarEstimator(structure_estimator, node_estimator=None, edge_estimator=None, name=None)[source]

Bases: ParameterEstimator

Parameters:
  • structure_estimator (Any)

  • node_estimator (Any)

  • edge_estimator (Any)

  • name (str | None)

accumulator_factory()[source]
Return type:

LabeledTemporalGraphGrammarAccumulatorFactory

estimate(nobs, suff_stat)[source]
Parameters:
Return type:

LabeledTemporalGraphGrammarDistribution

class LabeledTemporalGraphGrammarAccumulator(structure_acc, node_acc, edge_acc)[source]

Bases: SequenceEncodableStatisticAccumulator

Parameters:
  • structure_acc (Any)

  • node_acc (Any)

  • edge_acc (Any)

update(x, weight, estimate)[source]
Parameters:
Return type:

None

seq_update(x, weights, estimate)[source]
Parameters:
Return type:

None

initialize(x, weight, rng)[source]
Parameters:
Return type:

None

seq_initialize(x, weights, rng)[source]
Parameters:
Return type:

None

combine(suff_stat)[source]
Parameters:

suff_stat (tuple)

Return type:

LabeledTemporalGraphGrammarAccumulator

value()[source]
Return type:

tuple

from_value(x)[source]
Parameters:

x (tuple)

Return type:

LabeledTemporalGraphGrammarAccumulator

key_merge(stats_dict)[source]

Pool this accumulator’s statistics into stats_dict under its merge key.

The structural default implements the common single-key pattern: store the accumulator under self.keys the first time the key is seen, else combine into the one already there. Accumulators with several named keys (e.g. an HMM’s init/trans/state keys) or a non-accumulator stats payload override this. A keys of None (the default) is a no-op.

Parameters:

stats_dict (dict)

Return type:

None

key_replace(stats_dict)[source]

Replace this accumulator’s statistics from the pooled stats_dict entry (see key_merge).

Parameters:

stats_dict (dict)

Return type:

None

acc_to_encoder()[source]
Return type:

TemporalGraphGrammarDataEncoder

class LabeledTemporalGraphGrammarAccumulatorFactory(structure_factory, node_factory, edge_factory)[source]

Bases: StatisticAccumulatorFactory

Parameters:
  • structure_factory (Any)

  • node_factory (Any)

  • edge_factory (Any)

make()[source]
Return type:

LabeledTemporalGraphGrammarAccumulator

class HomophilyTemporalGraphGrammarDistribution(rate, type_weights, node_rate=0.0, motif=None, name=None)[source]

Bases: SequenceEncodableProbabilityDistribution

A growth grammar whose edge formation depends on node ATTRIBUTES, not just structure (homophily).

Each node carries a categorical type (community / location-bucket / …). The per-step number of new edges of motif m between an (unordered) type pair (a, b) is Poisson(rate[m, a, b]), placed uniformly among the candidate non-edges of that motif and type pair. Making rate[m, a, a] larger than rate[m, a, b] is homophily (“similar nodes connect more”); the rate tensor is the learnable coupling between attributes and topology. New nodes draw their type from type_weights.

Observation: (snapshots, node_types) – the adjacency chain plus an int type per node. Exact and closed-form: the rate tensor is just edge counts per (motif, type-pair) over steps, and the type distribution is node-type counts. (Phase: growth-only, dense; add+remove and sparse compose with the machinery above and are the natural extensions.)

Parameters:
  • rate (np.ndarray)

  • type_weights (Sequence[float])

  • node_rate (float)

  • motif (CommonNeighbourMotif | None)

  • name (str | None)

log_density(x)[source]

Return the log-density or log-mass at a single observation.

Parameters:

x (tuple)

Return type:

float

seq_encode(x)[source]
Parameters:

x (Sequence[tuple])

Return type:

Sequence[tuple]

seq_log_density(x)[source]

Return vectorized log-density values for sequence-encoded observations.

Parameters:

x (Sequence[tuple])

Return type:

ndarray

sampler(seed=None)[source]

Return a sampler for drawing observations from this distribution.

Parameters:

seed (int | None)

Return type:

HomophilyTemporalGraphGrammarSampler

estimator(pseudo_count=None)[source]

Return an estimator for fitting this distribution from data.

Parameters:

pseudo_count (float | None)

Return type:

HomophilyTemporalGraphGrammarEstimator

dist_to_encoder()[source]

Return the data encoder used by this distribution for vectorized methods.

Return type:

TemporalGraphGrammarDataEncoder

class HomophilyTemporalGraphGrammarSampler(dist, seed=None)[source]

Bases: DistributionSampler

Parameters:
  • dist (HomophilyTemporalGraphGrammarDistribution)

  • seed (int | None)

sample_one(num_steps=8, seed_graph=None, n_init=8)[source]
Parameters:
Return type:

tuple

sample(size=None, **kw)[source]

Draw observations.

Combinator samplers (mixture/sequence/…) accept batched. With batched=True (the default) each child stream is drawn in one vectorized call instead of a per-draw Python loop – far faster. Because every child sampler owns an independent RandomState, batching consumes each stream in the same order as the loop, so the draws are identical to the legacy path. batched=False forces that legacy per-draw loop as a guaranteed- stable reference. Leaf samplers are already vectorized and ignore the flag.

Parameters:
Return type:

Any

class HomophilyTemporalGraphGrammarEstimator(M, K, motif=None, pseudo_count=None, name=None)[source]

Bases: ParameterEstimator

Parameters:
  • M (int)

  • K (int)

  • motif (CommonNeighbourMotif | None)

  • pseudo_count (float | None)

  • name (str | None)

accumulator_factory()[source]
Return type:

HomophilyTemporalGraphGrammarAccumulatorFactory

estimate(nobs, suff_stat)[source]
Parameters:
Return type:

HomophilyTemporalGraphGrammarDistribution

class HomophilyTemporalGraphGrammarAccumulator(M, K, motif)[source]

Bases: SequenceEncodableStatisticAccumulator

Parameters:
  • M (int)

  • K (int)

  • motif (CommonNeighbourMotif)

update(x, weight, estimate)[source]
Parameters:
Return type:

None

seq_update(x, weights, estimate)[source]
Parameters:
Return type:

None

initialize(x, weight, rng)[source]
Parameters:
Return type:

None

seq_initialize(x, weights, rng)[source]
Parameters:
Return type:

None

combine(suff_stat)[source]
Parameters:

suff_stat (tuple)

Return type:

HomophilyTemporalGraphGrammarAccumulator

value()[source]
Return type:

tuple

from_value(x)[source]
Parameters:

x (tuple)

Return type:

HomophilyTemporalGraphGrammarAccumulator

key_merge(stats_dict)[source]

Pool this accumulator’s statistics into stats_dict under its merge key.

The structural default implements the common single-key pattern: store the accumulator under self.keys the first time the key is seen, else combine into the one already there. Accumulators with several named keys (e.g. an HMM’s init/trans/state keys) or a non-accumulator stats payload override this. A keys of None (the default) is a no-op.

Parameters:

stats_dict (dict)

Return type:

None

key_replace(stats_dict)[source]

Replace this accumulator’s statistics from the pooled stats_dict entry (see key_merge).

Parameters:

stats_dict (dict)

Return type:

None

acc_to_encoder()[source]
Return type:

TemporalGraphGrammarDataEncoder

class HomophilyTemporalGraphGrammarAccumulatorFactory(M, K, motif)[source]

Bases: StatisticAccumulatorFactory

Parameters:
  • M (int)

  • K (int)

  • motif (CommonNeighbourMotif)

make()[source]
Return type:

HomophilyTemporalGraphGrammarAccumulator

class ChurningTemporalGraphGrammarDistribution(edit_grammar, node_remove_rate=0.0, name=None)[source]

Bases: SequenceEncodableProbabilityDistribution

Dynamic graph where nodes both JOIN and LEAVE, tracked by stable identity.

Each snapshot is (adjacency, node_ids)node_ids[i] is the persistent identity of row i. A transition first removes nodes (those whose id disappears; count ~ Poisson(node_remove_rate), chosen uniformly, their edges vanishing with them), then runs the wrapped edit grammar on the surviving subgraph (which also appends new nodes + adds/removes edges). So churn is a thin wrapper: identity alignment + a node-removal Poisson term on top of all the existing motif/edge machinery. Scoring and fitting accept dense or scipy.sparse adjacencies (the id alignment slices either); the sampler is dense.

Parameters:
  • edit_grammar (TemporalGraphGrammarDistribution)

  • node_remove_rate (float)

  • name (str | None)

log_density(x)[source]

Return the log-density or log-mass at a single observation.

Parameters:

x (Sequence[tuple])

Return type:

float

seq_encode(x)[source]
Parameters:

x (Sequence[Any])

Return type:

Sequence[Any]

seq_log_density(x)[source]

Return vectorized log-density values for sequence-encoded observations.

Parameters:

x (Sequence[Any])

Return type:

ndarray

sampler(seed=None)[source]

Return a sampler for drawing observations from this distribution.

Parameters:

seed (int | None)

Return type:

ChurningTemporalGraphGrammarSampler

estimator(pseudo_count=None)[source]

Return an estimator for fitting this distribution from data.

Parameters:

pseudo_count (float | None)

Return type:

ChurningTemporalGraphGrammarEstimator

dist_to_encoder()[source]

Return the data encoder used by this distribution for vectorized methods.

Return type:

TemporalGraphGrammarDataEncoder

class ChurningTemporalGraphGrammarSampler(dist, seed=None)[source]

Bases: DistributionSampler

Parameters:
  • dist (ChurningTemporalGraphGrammarDistribution)

  • seed (int | None)

sample_one(num_steps=10, seed_graph=None, n_init=8)[source]
Parameters:
Return type:

list

sample(size=None, **kw)[source]

Draw observations.

Combinator samplers (mixture/sequence/…) accept batched. With batched=True (the default) each child stream is drawn in one vectorized call instead of a per-draw Python loop – far faster. Because every child sampler owns an independent RandomState, batching consumes each stream in the same order as the loop, so the draws are identical to the legacy path. batched=False forces that legacy per-draw loop as a guaranteed- stable reference. Leaf samplers are already vectorized and ignore the flag.

Parameters:
Return type:

Any

class ChurningTemporalGraphGrammarEstimator(edit_estimator, name=None)[source]

Bases: ParameterEstimator

Parameters:
  • edit_estimator (Any)

  • name (str | None)

accumulator_factory()[source]
Return type:

ChurningTemporalGraphGrammarAccumulatorFactory

estimate(nobs, suff_stat)[source]
Parameters:
Return type:

ChurningTemporalGraphGrammarDistribution

class ChurningTemporalGraphGrammarAccumulator(edit_acc)[source]

Bases: SequenceEncodableStatisticAccumulator

Parameters:

edit_acc (Any)

update(x, weight, estimate)[source]
Parameters:
Return type:

None

seq_update(x, weights, estimate)[source]
Parameters:
Return type:

None

initialize(x, weight, rng)[source]
Parameters:
Return type:

None

seq_initialize(x, weights, rng)[source]
Parameters:
Return type:

None

combine(suff_stat)[source]
Parameters:

suff_stat (tuple)

Return type:

ChurningTemporalGraphGrammarAccumulator

value()[source]
Return type:

tuple

from_value(x)[source]
Parameters:

x (tuple)

Return type:

ChurningTemporalGraphGrammarAccumulator

key_merge(stats_dict)[source]

Pool this accumulator’s statistics into stats_dict under its merge key.

The structural default implements the common single-key pattern: store the accumulator under self.keys the first time the key is seen, else combine into the one already there. Accumulators with several named keys (e.g. an HMM’s init/trans/state keys) or a non-accumulator stats payload override this. A keys of None (the default) is a no-op.

Parameters:

stats_dict (dict)

Return type:

None

key_replace(stats_dict)[source]

Replace this accumulator’s statistics from the pooled stats_dict entry (see key_merge).

Parameters:

stats_dict (dict)

Return type:

None

acc_to_encoder()[source]
Return type:

TemporalGraphGrammarDataEncoder

class ChurningTemporalGraphGrammarAccumulatorFactory(edit_factory)[source]

Bases: StatisticAccumulatorFactory

Parameters:

edit_factory (Any)

make()[source]
Return type:

ChurningTemporalGraphGrammarAccumulator

class LatentTemporalGraphGrammarDistribution(states, initial_probs=None, transition_matrix=None, name=None)[source]

Bases: SequenceEncodableProbabilityDistribution

A dynamic graph whose edit grammar is governed by a hidden, time-evolving REGIME.

A latent state z_t (a Markov chain: initial_probs pi, transition_matrix A) selects which of K edit grammars governs transition t. So the graph can switch regimes over time – e.g. a bursty growth / densification phase, then a fragmentation / decay phase – dynamics a single grammar cannot express. The sequence likelihood marginalises the regime path by the forward algorithm; emissions are the per- transition edit log-densities of each regime’s grammar, so this is an HMM whose emission models are the graph-edit grammars and EM reuses each grammar’s weighted accumulator for the M-step.

Observation = a plain list of adjacency snapshots (same as the base grammar) – the regime is latent. decode returns the most likely regime active at each transition (Viterbi).

Parameters:
  • states (Sequence[TemporalGraphGrammarDistribution])

  • initial_probs (Sequence[float] | None)

  • transition_matrix (Sequence[Sequence[float]] | None)

  • name (str | None)

log_density(x)[source]

Return the log-density or log-mass at a single observation.

Parameters:

x (Sequence[Any])

Return type:

float

decode(x)[source]

Viterbi: the most likely regime governing each transition.

Parameters:

x (Sequence[Any])

Return type:

list

seq_encode(x)[source]
Parameters:

x (Sequence[Any])

Return type:

Sequence[Any]

seq_log_density(x)[source]

Return vectorized log-density values for sequence-encoded observations.

Parameters:

x (Sequence[Any])

Return type:

ndarray

sampler(seed=None)[source]

Return a sampler for drawing observations from this distribution.

Parameters:

seed (int | None)

Return type:

LatentTemporalGraphGrammarSampler

estimator(pseudo_count=None)[source]

Return an estimator for fitting this distribution from data.

Parameters:

pseudo_count (float | None)

Return type:

LatentTemporalGraphGrammarEstimator

dist_to_encoder()[source]

Return the data encoder used by this distribution for vectorized methods.

Return type:

TemporalGraphGrammarDataEncoder

class LatentTemporalGraphGrammarSampler(dist, seed=None)[source]

Bases: DistributionSampler

Parameters:
  • dist (LatentTemporalGraphGrammarDistribution)

  • seed (int | None)

sample_one(num_steps=10, seed_graph=None, n_init=5)[source]
Parameters:
Return type:

list

sample(size=None, **kw)[source]

Draw observations.

Combinator samplers (mixture/sequence/…) accept batched. With batched=True (the default) each child stream is drawn in one vectorized call instead of a per-draw Python loop – far faster. Because every child sampler owns an independent RandomState, batching consumes each stream in the same order as the loop, so the draws are identical to the legacy path. batched=False forces that legacy per-draw loop as a guaranteed- stable reference. Leaf samplers are already vectorized and ignore the flag.

Parameters:
Return type:

Any

class LatentTemporalGraphGrammarEstimator(state_estimators, pseudo_count=None, name=None)[source]

Bases: ParameterEstimator

EM (Baum-Welch) for the regime-switching grammar: forward-backward E-step, per-regime weighted M-step.

Parameters:
  • state_estimators (Sequence[Any])

  • pseudo_count (float | None)

  • name (str | None)

accumulator_factory()[source]
Return type:

LatentTemporalGraphGrammarAccumulatorFactory

estimate(nobs, suff_stat)[source]
Parameters:
Return type:

LatentTemporalGraphGrammarDistribution

class LatentTemporalGraphGrammarAccumulator(k, state_accs)[source]

Bases: SequenceEncodableStatisticAccumulator

Parameters:
  • k (int)

  • state_accs (Sequence[Any])

update(x, weight, estimate)[source]
Parameters:
Return type:

None

initialize(x, weight, rng)[source]
Parameters:
Return type:

None

seq_update(x, weights, estimate)[source]
Parameters:
Return type:

None

seq_initialize(x, weights, rng)[source]
Parameters:
Return type:

None

combine(suff_stat)[source]
Parameters:

suff_stat (tuple)

Return type:

LatentTemporalGraphGrammarAccumulator

value()[source]
Return type:

tuple

from_value(x)[source]
Parameters:

x (tuple)

Return type:

LatentTemporalGraphGrammarAccumulator

key_merge(stats_dict)[source]

Pool this accumulator’s statistics into stats_dict under its merge key.

The structural default implements the common single-key pattern: store the accumulator under self.keys the first time the key is seen, else combine into the one already there. Accumulators with several named keys (e.g. an HMM’s init/trans/state keys) or a non-accumulator stats payload override this. A keys of None (the default) is a no-op.

Parameters:

stats_dict (dict)

Return type:

None

key_replace(stats_dict)[source]

Replace this accumulator’s statistics from the pooled stats_dict entry (see key_merge).

Parameters:

stats_dict (dict)

Return type:

None

acc_to_encoder()[source]
Return type:

TemporalGraphGrammarDataEncoder

class LatentTemporalGraphGrammarAccumulatorFactory(k, state_factories)[source]

Bases: StatisticAccumulatorFactory

Parameters:
  • k (int)

  • state_factories (Sequence[Any])

make()[source]
Return type:

LatentTemporalGraphGrammarAccumulator

class LatentAttributedTemporalGraphGrammarDistribution(structures, node_dists=None, edge_dists=None, initial_probs=None, transition_matrix=None, name=None)[source]

Bases: SequenceEncodableProbabilityDistribution

A regime-switching dynamic graph where the hidden regime drives the STRUCTURE and the ATTRIBUTES.

Each of K regimes carries a full edit grammar plus (optionally) a node-attribute distribution and an edge-attribute distribution, all switched by one latent Markov state z_t. So a single regime change can densify the topology AND spike communication volume / shift node properties together – e.g. an “active” phase with bursty triadic closure and high message counts, vs a “quiet” phase. The per-transition emission under regime k is structure_k(transition) + node_attrs_k(nodes added this step) + edge_attrs_k(edges added this step); the sequence likelihood marginalises the regime path by the forward algorithm and EM does forward-backward + a per-regime weighted M-step over each piece.

Observation = (snapshots, node_features, edge_features) where node_features[t] / edge_features[t] are the attribute records of the nodes / edges that appear at transition t (lists, length = #transitions).

Parameters:
  • structures (Sequence[TemporalGraphGrammarDistribution])

  • node_dists (Sequence[Any] | None)

  • edge_dists (Sequence[Any] | None)

  • initial_probs (Sequence[float] | None)

  • transition_matrix (Sequence[Sequence[float]] | None)

  • name (str | None)

log_density(x)[source]

Return the log-density or log-mass at a single observation.

Parameters:

x (tuple)

Return type:

float

decode(x)[source]

Viterbi: the most likely regime governing each transition (jointly explaining structure+attrs).

Parameters:

x (tuple)

Return type:

list

seq_encode(x)[source]
Parameters:

x (Sequence[Any])

Return type:

Sequence[Any]

seq_log_density(x)[source]

Return vectorized log-density values for sequence-encoded observations.

Parameters:

x (Sequence[Any])

Return type:

ndarray

sampler(seed=None)[source]

Return a sampler for drawing observations from this distribution.

Parameters:

seed (int | None)

Return type:

LatentAttributedTemporalGraphGrammarSampler

estimator(pseudo_count=None)[source]

Return an estimator for fitting this distribution from data.

Parameters:

pseudo_count (float | None)

Return type:

LatentAttributedTemporalGraphGrammarEstimator

dist_to_encoder()[source]

Return the data encoder used by this distribution for vectorized methods.

Return type:

TemporalGraphGrammarDataEncoder

class LatentAttributedTemporalGraphGrammarSampler(dist, seed=None)[source]

Bases: DistributionSampler

Parameters:
  • dist (LatentAttributedTemporalGraphGrammarDistribution)

  • seed (int | None)

sample_one(num_steps=10, seed_graph=None, n_init=5)[source]
Parameters:
Return type:

tuple

sample(size=None, **kw)[source]

Draw observations.

Combinator samplers (mixture/sequence/…) accept batched. With batched=True (the default) each child stream is drawn in one vectorized call instead of a per-draw Python loop – far faster. Because every child sampler owns an independent RandomState, batching consumes each stream in the same order as the loop, so the draws are identical to the legacy path. batched=False forces that legacy per-draw loop as a guaranteed- stable reference. Leaf samplers are already vectorized and ignore the flag.

Parameters:
Return type:

Any

class LatentAttributedTemporalGraphGrammarEstimator(structure_estimators, node_estimators=None, edge_estimators=None, pseudo_count=None, name=None)[source]

Bases: ParameterEstimator

EM for the regime-switching attributed grammar: forward-backward E-step, per-regime weighted M-step over structure + node attrs + edge attrs together.

Parameters:
  • structure_estimators (Sequence[Any])

  • node_estimators (Any)

  • edge_estimators (Any)

  • pseudo_count (float | None)

  • name (str | None)

accumulator_factory()[source]
Return type:

LatentAttributedTemporalGraphGrammarAccumulatorFactory

estimate(nobs, suff_stat)[source]
Parameters:
Return type:

LatentAttributedTemporalGraphGrammarDistribution

class LatentAttributedTemporalGraphGrammarAccumulator(k, struct_accs, node_accs, edge_accs)[source]

Bases: SequenceEncodableStatisticAccumulator

Parameters:
  • k (int)

  • struct_accs (Sequence[Any])

  • node_accs (Any)

  • edge_accs (Any)

update(x, weight, estimate)[source]
Parameters:
Return type:

None

initialize(x, weight, rng)[source]
Parameters:
Return type:

None

seq_update(x, weights, estimate)[source]
Parameters:
Return type:

None

seq_initialize(x, weights, rng)[source]
Parameters:
Return type:

None

combine(suff_stat)[source]
Parameters:

suff_stat (tuple)

Return type:

LatentAttributedTemporalGraphGrammarAccumulator

value()[source]
Return type:

tuple

from_value(x)[source]
Parameters:

x (tuple)

Return type:

LatentAttributedTemporalGraphGrammarAccumulator

key_merge(stats_dict)[source]

Pool this accumulator’s statistics into stats_dict under its merge key.

The structural default implements the common single-key pattern: store the accumulator under self.keys the first time the key is seen, else combine into the one already there. Accumulators with several named keys (e.g. an HMM’s init/trans/state keys) or a non-accumulator stats payload override this. A keys of None (the default) is a no-op.

Parameters:

stats_dict (dict)

Return type:

None

key_replace(stats_dict)[source]

Replace this accumulator’s statistics from the pooled stats_dict entry (see key_merge).

Parameters:

stats_dict (dict)

Return type:

None

acc_to_encoder()[source]
Return type:

TemporalGraphGrammarDataEncoder

class LatentAttributedTemporalGraphGrammarAccumulatorFactory(k, struct_factories, node_factories, edge_factories)[source]

Bases: StatisticAccumulatorFactory

Parameters:
  • k (int)

  • struct_factories (Sequence[Any])

  • node_factories (Any)

  • edge_factories (Any)

make()[source]
Return type:

LatentAttributedTemporalGraphGrammarAccumulator

class LatentChurningTemporalGraphGrammarDistribution(states, node_remove_rates=None, initial_probs=None, transition_matrix=None, name=None)[source]

Bases: SequenceEncodableProbabilityDistribution

A regime-switching dynamic graph where the hidden regime also governs NODE TURNOVER.

Combines the latent regime HMM with identity-tracked node churn: each of K regimes carries a full edit grammar AND its own node-removal rate, so the graph can switch between e.g. a stable phase (slow turnover, triadic growth) and a churn phase (fast member departure, fragmentation). Each snapshot is (adjacency, node_ids); per transition the active regime first removes nodes (those whose id disappears) then edits the surviving subgraph, and the per-transition emission under regime k is node_removal_k(#removed) + grammar_k(edit on the aligned surviving subgraph). The sequence likelihood marginalises the regime path by the forward algorithm; EM does forward-backward then a per-regime weighted M-step over both the grammar and the turnover rate. decode recovers the active regime.

Observation = (snapshots, node_ids) where node_ids is a list of per-snapshot id arrays. Dense.

Parameters:
  • states (Sequence[TemporalGraphGrammarDistribution])

  • node_remove_rates (Sequence[float] | None)

  • initial_probs (Sequence[float] | None)

  • transition_matrix (Sequence[Sequence[float]] | None)

  • name (str | None)

log_density(x)[source]

Return the log-density or log-mass at a single observation.

Parameters:

x (tuple)

Return type:

float

decode(x)[source]
Parameters:

x (tuple)

Return type:

list

seq_encode(x)[source]
Parameters:

x (Sequence[Any])

Return type:

Sequence[Any]

seq_log_density(x)[source]

Return vectorized log-density values for sequence-encoded observations.

Parameters:

x (Sequence[Any])

Return type:

ndarray

sampler(seed=None)[source]

Return a sampler for drawing observations from this distribution.

Parameters:

seed (int | None)

Return type:

LatentChurningTemporalGraphGrammarSampler

estimator(pseudo_count=None)[source]

Return an estimator for fitting this distribution from data.

Parameters:

pseudo_count (float | None)

Return type:

LatentChurningTemporalGraphGrammarEstimator

dist_to_encoder()[source]

Return the data encoder used by this distribution for vectorized methods.

Return type:

TemporalGraphGrammarDataEncoder

class LatentChurningTemporalGraphGrammarSampler(dist, seed=None)[source]

Bases: DistributionSampler

Parameters:
  • dist (LatentChurningTemporalGraphGrammarDistribution)

  • seed (int | None)

sample_one(num_steps=10, seed_graph=None, n_init=8)[source]
Parameters:
Return type:

tuple

sample(size=None, **kw)[source]

Draw observations.

Combinator samplers (mixture/sequence/…) accept batched. With batched=True (the default) each child stream is drawn in one vectorized call instead of a per-draw Python loop – far faster. Because every child sampler owns an independent RandomState, batching consumes each stream in the same order as the loop, so the draws are identical to the legacy path. batched=False forces that legacy per-draw loop as a guaranteed- stable reference. Leaf samplers are already vectorized and ignore the flag.

Parameters:
Return type:

Any

class LatentChurningTemporalGraphGrammarEstimator(state_estimators, pseudo_count=None, name=None)[source]

Bases: ParameterEstimator

Parameters:
  • state_estimators (Sequence[Any])

  • pseudo_count (float | None)

  • name (str | None)

accumulator_factory()[source]
Return type:

LatentChurningTemporalGraphGrammarAccumulatorFactory

estimate(nobs, suff_stat)[source]
Parameters:
Return type:

LatentChurningTemporalGraphGrammarDistribution

class LatentChurningTemporalGraphGrammarAccumulator(k, state_accs)[source]

Bases: SequenceEncodableStatisticAccumulator

Parameters:
  • k (int)

  • state_accs (Sequence[Any])

update(x, weight, estimate)[source]
Parameters:
Return type:

None

initialize(x, weight, rng)[source]
Parameters:
Return type:

None

seq_update(x, weights, estimate)[source]
Parameters:
Return type:

None

seq_initialize(x, weights, rng)[source]
Parameters:
Return type:

None

combine(suff_stat)[source]
Parameters:

suff_stat (tuple)

Return type:

LatentChurningTemporalGraphGrammarAccumulator

value()[source]
Return type:

tuple

from_value(x)[source]
Parameters:

x (tuple)

Return type:

LatentChurningTemporalGraphGrammarAccumulator

key_merge(stats_dict)[source]

Pool this accumulator’s statistics into stats_dict under its merge key.

The structural default implements the common single-key pattern: store the accumulator under self.keys the first time the key is seen, else combine into the one already there. Accumulators with several named keys (e.g. an HMM’s init/trans/state keys) or a non-accumulator stats payload override this. A keys of None (the default) is a no-op.

Parameters:

stats_dict (dict)

Return type:

None

key_replace(stats_dict)[source]

Replace this accumulator’s statistics from the pooled stats_dict entry (see key_merge).

Parameters:

stats_dict (dict)

Return type:

None

acc_to_encoder()[source]
Return type:

TemporalGraphGrammarDataEncoder

class LatentChurningTemporalGraphGrammarAccumulatorFactory(k, state_factories)[source]

Bases: StatisticAccumulatorFactory

Parameters:
  • k (int)

  • state_factories (Sequence[Any])

make()[source]
Return type:

LatentChurningTemporalGraphGrammarAccumulator

regime_moment_init(estimator, proto, data, k, seed=None)[source]

Seed a regime-switching grammar EM by clustering observed per-transition edit signatures.

Returns an initial distribution whose regimes are the k-means clusters of the (identifiable) transition signatures. Because the derivation is observed, these signatures are sufficient statistics that separate the regimes, so this avoids the local optima of random-restart EM. proto is any distribution of the target class (used only for its motif/attribute structure); estimator produces the fitted result.

Parameters:
Return type:

Any