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
wand 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:
objectA motif rule keyed by how many common neighbours a candidate edge has (triangles it would close).
binsis an increasing list of thresholds; binbcovers 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.- 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 byself.binsgives its motif. Withon_edges=Falsethe candidates are the non-edges (addition); withon_edges=Truethey are the existing edges (removal). Non-candidates and the diagonal -> -1.
- 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 remainderpairs - 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.)
- 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:
SequenceEncodableProbabilityDistributionDistribution over dynamic graphs (sequences of adjacency snapshots) under a motif-edit grammar.
- Parameters:
- 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 viascore_components().validis False for an impossible node removal.
- score_components(components)[source]
Score a precomputed
transition_components()decomposition under THIS grammar’s parameters.
- log_density(x)[source]
Log-density of one dynamic graph: the sum of transition log-densities over the snapshot chain.
xis a sequence of binary adjacency matrices – densendarrayorscipy.sparse(large graphs). The initial graph is taken as given (its marginal is not modelled, matching how the static grammars treat their start symbol).
- seq_encode(x)[source]
- seq_log_density(x)[source]
Return vectorized log-density values for sequence-encoded observations.
- 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_stepsof grammar-sampled edits.
- 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 emitsscipy.sparsesnapshots, costing O(edges + wedges) per step:triangle-closing motifs (cn>=1) are exactly the wedge non-edges – the nonzeros of
A @ Athat 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_rejectattempts 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 ofcsr_arraysnapshots. The realized motif distribution matches the weights, so a model fit on these snapshots recovers the grammar.
- sample(size=None, *, num_steps=10, n_init=5)[source]
Draw observations.
Combinator samplers (mixture/sequence/…) accept
batched. Withbatched=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 independentRandomState, batching consumes each stream in the same order as the loop, so the draws are identical to the legacy path.batched=Falseforces that legacy per-draw loop as a guaranteed- stable reference. Leaf samplers are already vectorized and ignore the flag.
- class TemporalGraphGrammarEstimator(motif=None, pseudo_count=None, name=None)[source]
Bases:
ParameterEstimatorLearn the motif distribution (rule weights) + edge/node rates from observed dynamic graphs.
- accumulator_factory()[source]
- Return type:
TemporalGraphGrammarAccumulatorFactory
- class TemporalGraphGrammarAccumulator(motif)[source]
Bases:
SequenceEncodableStatisticAccumulatorAccumulate per-motif edge counts + step/edge/node totals – the exact sufficient statistics.
- Parameters:
motif (CommonNeighbourMotif)
- update(x, weight, estimate)[source]
- seq_update(x, weights, estimate)[source]
- seq_initialize(x, weights, rng)[source]
- Parameters:
x (Any)
weights (ndarray)
rng (RandomState | None)
- Return type:
None
- initialize(x, weight, rng)[source]
- Parameters:
x (Any)
weight (float)
rng (RandomState | None)
- Return type:
None
- combine(suff_stat)[source]
- Parameters:
suff_stat (tuple)
- Return type:
TemporalGraphGrammarAccumulator
- key_merge(stats_dict)[source]
Pool this accumulator’s statistics into
stats_dictunder its merge key.The structural default implements the common single-key pattern: store the accumulator under
self.keysthe first time the key is seen, elsecombineinto 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. AkeysofNone(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_dictentry (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
- class LabeledTemporalGraphGrammarDistribution(structure, node_dist=None, edge_dist=None, name=None)[source]
Bases:
SequenceEncodableProbabilityDistributionA dynamic graph whose nodes and edges carry attributes.
Composes a structural
TemporalGraphGrammarDistribution(the topology over time) with two ordinary mixle distributions:node_distover per-node attribute records (location, name, age, … – typically aCompositeDistributionof leaves or a mixture) andedge_distover 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.
- seq_log_density(x)[source]
Return vectorized log-density values for sequence-encoded observations.
- 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(size=None, **kw)[source]
Draw observations.
Combinator samplers (mixture/sequence/…) accept
batched. Withbatched=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 independentRandomState, batching consumes each stream in the same order as the loop, so the draws are identical to the legacy path.batched=Falseforces that legacy per-draw loop as a guaranteed- stable reference. Leaf samplers are already vectorized and ignore the flag.
- 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
- 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]
- seq_update(x, weights, estimate)[source]
- initialize(x, weight, rng)[source]
- Parameters:
x (tuple)
weight (float)
rng (RandomState | None)
- Return type:
None
- seq_initialize(x, weights, rng)[source]
- Parameters:
x (Any)
weights (ndarray)
rng (RandomState | None)
- Return type:
None
- combine(suff_stat)[source]
- Parameters:
suff_stat (tuple)
- Return type:
LabeledTemporalGraphGrammarAccumulator
- key_merge(stats_dict)[source]
Pool this accumulator’s statistics into
stats_dictunder its merge key.The structural default implements the common single-key pattern: store the accumulator under
self.keysthe first time the key is seen, elsecombineinto 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. AkeysofNone(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_dictentry (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:
SequenceEncodableProbabilityDistributionA 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 motifmbetween an (unordered) type pair (a, b) isPoisson(rate[m, a, b]), placed uniformly among the candidate non-edges of that motif and type pair. Makingrate[m, a, a]larger thanrate[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 fromtype_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:
- log_density(x)[source]
Return the log-density or log-mass at a single observation.
- seq_log_density(x)[source]
Return vectorized log-density values for sequence-encoded observations.
- 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]
- sample(size=None, **kw)[source]
Draw observations.
Combinator samplers (mixture/sequence/…) accept
batched. Withbatched=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 independentRandomState, batching consumes each stream in the same order as the loop, so the draws are identical to the legacy path.batched=Falseforces that legacy per-draw loop as a guaranteed- stable reference. Leaf samplers are already vectorized and ignore the flag.
- class HomophilyTemporalGraphGrammarEstimator(M, K, motif=None, pseudo_count=None, name=None)[source]
Bases:
ParameterEstimator- Parameters:
- accumulator_factory()[source]
- Return type:
HomophilyTemporalGraphGrammarAccumulatorFactory
- class HomophilyTemporalGraphGrammarAccumulator(M, K, motif)[source]
Bases:
SequenceEncodableStatisticAccumulator- update(x, weight, estimate)[source]
- seq_update(x, weights, estimate)[source]
- initialize(x, weight, rng)[source]
- Parameters:
x (tuple)
weight (float)
rng (RandomState | None)
- Return type:
None
- seq_initialize(x, weights, rng)[source]
- Parameters:
x (Any)
weights (ndarray)
rng (RandomState | None)
- Return type:
None
- combine(suff_stat)[source]
- Parameters:
suff_stat (tuple)
- Return type:
HomophilyTemporalGraphGrammarAccumulator
- key_merge(stats_dict)[source]
Pool this accumulator’s statistics into
stats_dictunder its merge key.The structural default implements the common single-key pattern: store the accumulator under
self.keysthe first time the key is seen, elsecombineinto 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. AkeysofNone(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_dictentry (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- make()[source]
- Return type:
HomophilyTemporalGraphGrammarAccumulator
- class ChurningTemporalGraphGrammarDistribution(edit_grammar, node_remove_rate=0.0, name=None)[source]
Bases:
SequenceEncodableProbabilityDistributionDynamic 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 orscipy.sparseadjacencies (the id alignment slices either); the sampler is dense.- Parameters:
- log_density(x)[source]
Return the log-density or log-mass at a single observation.
- seq_log_density(x)[source]
Return vectorized log-density values for sequence-encoded observations.
- 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]
- sample(size=None, **kw)[source]
Draw observations.
Combinator samplers (mixture/sequence/…) accept
batched. Withbatched=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 independentRandomState, batching consumes each stream in the same order as the loop, so the draws are identical to the legacy path.batched=Falseforces that legacy per-draw loop as a guaranteed- stable reference. Leaf samplers are already vectorized and ignore the flag.
- class ChurningTemporalGraphGrammarEstimator(edit_estimator, name=None)[source]
Bases:
ParameterEstimator- Parameters:
edit_estimator (Any)
name (str | None)
- accumulator_factory()[source]
- Return type:
ChurningTemporalGraphGrammarAccumulatorFactory
- class ChurningTemporalGraphGrammarAccumulator(edit_acc)[source]
Bases:
SequenceEncodableStatisticAccumulator- Parameters:
edit_acc (Any)
- update(x, weight, estimate)[source]
- seq_update(x, weights, estimate)[source]
- initialize(x, weight, rng)[source]
- Parameters:
x (Any)
weight (float)
rng (RandomState | None)
- Return type:
None
- seq_initialize(x, weights, rng)[source]
- Parameters:
x (Any)
weights (ndarray)
rng (RandomState | None)
- Return type:
None
- combine(suff_stat)[source]
- Parameters:
suff_stat (tuple)
- Return type:
ChurningTemporalGraphGrammarAccumulator
- key_merge(stats_dict)[source]
Pool this accumulator’s statistics into
stats_dictunder its merge key.The structural default implements the common single-key pattern: store the accumulator under
self.keysthe first time the key is seen, elsecombineinto 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. AkeysofNone(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_dictentry (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:
SequenceEncodableProbabilityDistributionA dynamic graph whose edit grammar is governed by a hidden, time-evolving REGIME.
A latent state z_t (a Markov chain:
initial_probspi,transition_matrixA) 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.
decodereturns the most likely regime active at each transition (Viterbi).- Parameters:
- log_density(x)[source]
Return the log-density or log-mass at a single observation.
- decode(x)[source]
Viterbi: the most likely regime governing each transition.
- seq_log_density(x)[source]
Return vectorized log-density values for sequence-encoded observations.
- 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]
- sample(size=None, **kw)[source]
Draw observations.
Combinator samplers (mixture/sequence/…) accept
batched. Withbatched=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 independentRandomState, batching consumes each stream in the same order as the loop, so the draws are identical to the legacy path.batched=Falseforces that legacy per-draw loop as a guaranteed- stable reference. Leaf samplers are already vectorized and ignore the flag.
- class LatentTemporalGraphGrammarEstimator(state_estimators, pseudo_count=None, name=None)[source]
Bases:
ParameterEstimatorEM (Baum-Welch) for the regime-switching grammar: forward-backward E-step, per-regime weighted M-step.
- accumulator_factory()[source]
- Return type:
LatentTemporalGraphGrammarAccumulatorFactory
- class LatentTemporalGraphGrammarAccumulator(k, state_accs)[source]
Bases:
SequenceEncodableStatisticAccumulator- Parameters:
k (int)
state_accs (Sequence[Any])
- update(x, weight, estimate)[source]
- initialize(x, weight, rng)[source]
- Parameters:
weight (float)
rng (RandomState | None)
- Return type:
None
- seq_update(x, weights, estimate)[source]
- seq_initialize(x, weights, rng)[source]
- Parameters:
weights (ndarray)
rng (RandomState | None)
- Return type:
None
- combine(suff_stat)[source]
- Parameters:
suff_stat (tuple)
- Return type:
LatentTemporalGraphGrammarAccumulator
- key_merge(stats_dict)[source]
Pool this accumulator’s statistics into
stats_dictunder its merge key.The structural default implements the common single-key pattern: store the accumulator under
self.keysthe first time the key is seen, elsecombineinto 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. AkeysofNone(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_dictentry (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:
SequenceEncodableProbabilityDistributionA 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)wherenode_features[t]/edge_features[t]are the attribute records of the nodes / edges that appear at transition t (lists, length = #transitions).- Parameters:
- log_density(x)[source]
Return the log-density or log-mass at a single observation.
- decode(x)[source]
Viterbi: the most likely regime governing each transition (jointly explaining structure+attrs).
- seq_log_density(x)[source]
Return vectorized log-density values for sequence-encoded observations.
- 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]
- sample(size=None, **kw)[source]
Draw observations.
Combinator samplers (mixture/sequence/…) accept
batched. Withbatched=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 independentRandomState, batching consumes each stream in the same order as the loop, so the draws are identical to the legacy path.batched=Falseforces that legacy per-draw loop as a guaranteed- stable reference. Leaf samplers are already vectorized and ignore the flag.
- class LatentAttributedTemporalGraphGrammarEstimator(structure_estimators, node_estimators=None, edge_estimators=None, pseudo_count=None, name=None)[source]
Bases:
ParameterEstimatorEM for the regime-switching attributed grammar: forward-backward E-step, per-regime weighted M-step over structure + node attrs + edge attrs together.
- Parameters:
- accumulator_factory()[source]
- Return type:
LatentAttributedTemporalGraphGrammarAccumulatorFactory
- 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]
- initialize(x, weight, rng)[source]
- Parameters:
x (tuple)
weight (float)
rng (RandomState | None)
- Return type:
None
- seq_update(x, weights, estimate)[source]
- seq_initialize(x, weights, rng)[source]
- Parameters:
weights (ndarray)
rng (RandomState | None)
- Return type:
None
- combine(suff_stat)[source]
- Parameters:
suff_stat (tuple)
- Return type:
LatentAttributedTemporalGraphGrammarAccumulator
- from_value(x)[source]
- Parameters:
x (tuple)
- Return type:
LatentAttributedTemporalGraphGrammarAccumulator
- key_merge(stats_dict)[source]
Pool this accumulator’s statistics into
stats_dictunder its merge key.The structural default implements the common single-key pattern: store the accumulator under
self.keysthe first time the key is seen, elsecombineinto 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. AkeysofNone(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_dictentry (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:
SequenceEncodableProbabilityDistributionA 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 isnode_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.decoderecovers the active regime.Observation =
(snapshots, node_ids)wherenode_idsis a list of per-snapshot id arrays. Dense.- Parameters:
- log_density(x)[source]
Return the log-density or log-mass at a single observation.
- seq_log_density(x)[source]
Return vectorized log-density values for sequence-encoded observations.
- 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]
- sample(size=None, **kw)[source]
Draw observations.
Combinator samplers (mixture/sequence/…) accept
batched. Withbatched=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 independentRandomState, batching consumes each stream in the same order as the loop, so the draws are identical to the legacy path.batched=Falseforces that legacy per-draw loop as a guaranteed- stable reference. Leaf samplers are already vectorized and ignore the flag.
- class LatentChurningTemporalGraphGrammarEstimator(state_estimators, pseudo_count=None, name=None)[source]
Bases:
ParameterEstimator- accumulator_factory()[source]
- Return type:
LatentChurningTemporalGraphGrammarAccumulatorFactory
- class LatentChurningTemporalGraphGrammarAccumulator(k, state_accs)[source]
Bases:
SequenceEncodableStatisticAccumulator- Parameters:
k (int)
state_accs (Sequence[Any])
- update(x, weight, estimate)[source]
- initialize(x, weight, rng)[source]
- Parameters:
x (tuple)
weight (float)
rng (RandomState | None)
- Return type:
None
- seq_update(x, weights, estimate)[source]
- seq_initialize(x, weights, rng)[source]
- Parameters:
weights (ndarray)
rng (RandomState | None)
- Return type:
None
- combine(suff_stat)[source]
- Parameters:
suff_stat (tuple)
- Return type:
LatentChurningTemporalGraphGrammarAccumulator
- from_value(x)[source]
- Parameters:
x (tuple)
- Return type:
LatentChurningTemporalGraphGrammarAccumulator
- key_merge(stats_dict)[source]
Pool this accumulator’s statistics into
stats_dictunder its merge key.The structural default implements the common single-key pattern: store the accumulator under
self.keysthe first time the key is seen, elsecombineinto 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. AkeysofNone(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_dictentry (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.
protois any distribution of the target class (used only for its motif/attribute structure);estimatorproduces the fitted result.