δ-temporal motifs: counting ordered patterns inside a time window
Topic 24 counted static triangles with a masked matrix multiply; the previous guide showed that once edges carry timestamps, order is information. This paper fuses the two: a pattern is no longer a subgraph but an ordered sequence of edges that must all land inside a window δ — and counting those is a new algorithmic problem. This chapter builds the six concepts, ending at the exact window-scan operator M33’s WITHIN δ needs.
This is a guide to a paper, so its anchors are the paper’s own section,
figure, algorithm and theorem numbers: Paranjape, Benson, Leskovec, “Motifs
in Temporal Networks,” WSDM 2017
(arXiv:1612.09259). Every count and
complexity below was checked against that PDF; the one Rust block is an
illustration, marked as such. One notation warning up front: the paper writes
k for the number of nodes and l for the number of edges in a
motif — this guide uses those letters the paper’s way throughout.
The problem in one sentence
In a trace like the SNAP sx-stackoverflow dataset the paper released
(2,601,977 nodes, 63,497,050 timestamped edges), “A messaged B, then B
messaged C, then C messaged A — all within one hour” is a single pattern out
of the 36 possible 3-edge orderings on at most 3 nodes, and counting its
instances by enumerating triples of edges is hopeless.
The concepts, step by step
Step 1 — timestamped edges, and why order is information
In: nothing yet — this step fixes what a temporal edge is here (no duration λ, unlike the paths guide) and why a static count throws away the answer. Out: the multiset of
(u, v, t)events every later step consumes, and the observation that reordering them changes their meaning.
A temporal network here is a multiset of directed, timestamped edges
(u, v, t) — who contacted whom, when. The paper’s own definition: a temporal
graph T on node set V is a collection of tuples (ui, vi, ti),
i = 1..m, each ti a timestamp in ℝ (§2). Note there is no traversal time
λ — an edge is an instantaneous event, not a road you spend time on (this
is the sharpest difference from the temporal-paths guide’s (u, v, t, λ)). The
paper assumes the ti are unique, so the edges are strictly ordered
(§2 — an assumption for clean presentation, adaptable to ties).
A static motif (a small subgraph pattern, e.g. a triangle) treats these two histories as identical:
history 1: A→B at 9:00, B→C at 9:05 plausible information flow
history 2: B→C at 9:00, A→B at 9:05 B "forwarded" before receiving
Both condense to the static path A→B→C, but only one is a possible relay.
Why it matters: every behavioral question — forwarding, reciprocation, who-answers-whom — lives in the ordering, which the static count destroys. This is the previous guide’s “condensing lies” applied to patterns instead of paths.
Step 2 — the δ-temporal motif: sequence + order + window
In: the
(u, v, t)events of Step 1. Out: the formal object being counted — ak-node,l-edge motif and its instances — plus the number 36 that indexes the whole empirical paper.
The paper’s definition, quoted (§2): a k-node, l-edge δ-temporal motif
is a sequence of l edges M = (u1, v1, t1), …, (ul, vl, tl) that are
time-ordered within a δ duration, i.e. t1 < t2 < … < tl and
tl − t1 ≤ δ. Here δ is the window: the span from first to last edge may
not exceed it. An instance of M is a set of l actual edges that
(a) map onto the placeholders consistently, (b) occur in exactly the specified
order, and (c) satisfy tl − t1 ≤ δ. Example with δ = 1 h:
edges between ann and bob: ann→bob 9:00 bob→ann 9:20 ann→bob 9:50
ann→bob 11:00
M = (A→B, B→A, A→B):
(9:00, 9:20, 9:50) ✓ instance of M — right order, spans 50 min ≤ δ
(9:00, 9:20, 11:00) ✗ spans 2 h > δ
(9:20, 9:50, 11:00) ✗ order is B→A, A→B, A→B — a different motif
The paper fixes attention on l = 3 edges and k ≤ 3 nodes and shows there
are exactly 36 such motifs (Fig 3). The count decomposes cleanly (Fig 3’s
own colours): 4 two-node motifs + 8 triangle motifs + 24 star
motifs = 36. Fig 3 lays them in a 6 × 6 grid: the first edge is fixed (green
node → orange node), the second edge indexes the row, the third edge the
column. (Why 6 per axis: with the first edge fixed as node 1 → node 2, a
following edge is either between the two existing nodes — 2 directions — or
between an existing node and a new third node — 2 existing nodes × 2
directions = 4, for 2 + 4 = 6 choices. Q1 asks you to finish this argument.)
Why it matters: δ is doing real semantic work — it encodes “these events belong to one interaction,” and every count is meaningless without stating it. This is precisely the WITHIN δ clause of capstone M33.
Step 3 — why counting is hard: one static shape, many temporal instances
In: the motif
Mand window δ from Step 2. Out: the cost model that rules out naive enumeration and demands the per-event state of Step 4.
Because the same pair can carry many timestamped edges, a single static
subgraph instance can host an enormous number of temporal instances — and they
overlap. If ann and bob exchanged just 20 messages, there are
C(20, 3) = 20·19·18 / 6 = 1,140 3-edge subsequences to test against M for
order and window; a static triangle of three chatty nodes multiplies three such
counts. Naive enumeration of edge triples over the whole trace is
O(m³)-shaped; even per-subgraph enumeration explodes with activity.
Two structural facts rescue us: instances of an l-edge motif are
subsequences (not arbitrary sets) of the time-sorted edge list, and the
window constraint means an edge only ever combines with edges at most δ away —
a sliding window.
Why it matters: this is a classic streaming-aggregation shape — the cost model becomes “per-edge work × m,” not “candidate tuples” — if you can find the right per-edge state. Step 4 is that state.
Step 4 — the general algorithm: gather, then one window scan
In: the time-sorted edges among one static subgraph’s nodes (Step 3). Out: a single motif-instance count, produced by one sliding-window pass that maintains counts of partial matches.
The paper’s general algorithm (§4.1, Algorithm 1) has two phases: (1) enumerate
instances of the motif’s underlying static subgraph H (subgraph matching,
topic 24 machinery); (2) for each instance, gather the timestamped edges among
its nodes, sort by time, and count matching subsequences with one pass of a
sliding window, maintaining counts of partial matches. Algorithm 1 counts
all motifs at once by keying counters on label strings; the paper notes
there are O(l²) contiguous subsequences of an l-edge motif (§4.1).
Specialized to one motif, the state is exactly those O(l²) counters of the
motif’s contiguous fragments:
#![allow(unused)]
fn main() {
// ILLUSTRATION — not quoted from Paranjape et al. This specializes the paper's
// Algorithm 1 (§4.1) to a single motif; the paper's own version keys counters
// on label strings to count all 36 at once (its Fig 2 traces the counters).
// The nearest single-pass-over-time-sorted-events code in this repo is
// experiments/src/temporal_reach.rs:20 (same streaming shape, different state).
//
// event = (t, lab); lab says which ordered node-pair the edge uses
// (for M = (A→B, B→A, A→B): A→B ⇒ 0, B→A ⇒ 1, so motif = [0, 1, 0]).
fn count_delta_motif(events: &[(u64, u8)], motif: &[u8], delta: u64) -> u64 {
let l = motif.len();
let mut cnt = vec![vec![0u64; l]; l]; // cnt[i][j]: matches of motif[i..=j]
let (mut total, mut head) = (0u64, 0usize);
for &(t, lab) in events {
// 1. expire events older than t − δ. The expiring event is the
// OLDEST in the window, so any partial match containing it must
// START with it — subtract those, SHORTEST fragments first, so
// the inner count cnt[i+1][j] is already old-free when used.
while events[head].0 + delta < t {
let old = events[head].1;
for len in 1..l {
for i in 0..=l - len {
let j = i + len - 1;
if motif[i] == old {
cnt[i][j] -= if len == 1 { 1 } else { cnt[i + 1][j] };
}
}
}
head += 1;
}
// 2. bank completions BEFORE inserting: the new event can only
// ever be the LAST edge of a full match. `total` never expires.
if lab == motif[l - 1] {
total += if l == 1 { 1 } else { cnt[0][l - 2] };
}
// 3. insert: extend fragments, LONGEST first, so the new event is
// counted at most once per match.
for len in (1..l).rev() {
for j in len - 1..l {
let i = j + 1 - len;
if motif[j] == lab {
cnt[i][j] += if len == 1 { 1 } else { cnt[i][j - 1] };
}
}
}
}
total
}
}
Per event the work is the O(l²) counter updates — for l = 3 that is a
fixed 3×3 grid, a constant — so the scan is linear in the instance’s edge count
and never materializes a candidate triple. The paper states the matching
2-node bound as O(2lm), linear in m and optimal up to constants (§4.1).
Why it matters: correctness lives entirely in the two update orders (expire shortest-first, insert longest-first) — get either wrong and you double-count. The cost that remains is phase (1): static subgraph enumeration dominates, which motivates Step 5.
Step 5 — fast paths: 2-node and star motifs are easy, triangles are the fight
In: phase (1)’s subgraph-enumeration cost from Step 4. Out: three specialized bounds — the reason the 63M-edge traces are feasible at all.
For motifs whose static shape is trivial, phase (1) collapses:
- 2-node motifs: group edges by unordered pair, run Step 4’s scan per pair.
Linear overall,
O(m)— the paper calls this optimal up to constants (§4.1). - Star motifs (all three edges touch one center node): a dynamic program
over each center’s incident edges, keyed by neighbor and direction, with a
correction that subtracts the 2-node counts for the degenerate case where
the two “spoke” neighbors coincide (that instance is really a 2-node motif).
Also
O(m), linear in the input (§4.2). - Triangle motifs are the hard case: an edge between
uandvparticipates in every triangle through that pair, so per-triangle scanning re-reads hot edges. The paper’s fast algorithm (§4.2, Alg 5) assigns each edge to the triangles it can complete and runs inO(TriEnum + m√τ), whereTriEnumis the time to list all static triangles,mis the number of temporal edges, andτis the number of static triangles in the induced graph. That is a genuine reduction from the naive per-triangleO(mτ)down toO(m√τ)(§4.2, Theorem).
Worked, to feel the reduction: on a graph with m = 10⁶ temporal edges and
τ = 10⁴ static triangles, the naive O(mτ) = 10¹⁰; the fast O(m√τ)
replaces τ = 10⁴ with √τ = 10², giving 10⁸ — a 100× cut, which is why
the paper reports its fast temporal-triangle counter is up to 56.5× faster
than a competitive baseline in practice (abstract / §5).
Why it matters: this mirrors topic 24 exactly — stars are the cheap degree-local counts, triangles are where algorithmic care pays. The paper’s scalability experiments (§5; pull the exact per-dataset speedups into notes.md) show the specialized algorithms are what make the 63M-edge and larger traces feasible.
Step 6 — what the counts reveal: motif fingerprints of communication
In: the per-motif counts produced by Steps 4–5. Out: the 36-vector “fingerprint” and the query shapes M33 must serve.
A network’s vector of 36 motif counts (usually normalized to fractions) is a
behavioral fingerprint. The paper’s flagship contrast is blocking vs
non-blocking communication: on a phone call you cannot talk to two people
at once, so motifs where a node fires a second outgoing edge before receiving a
reply are rare in call networks — while email, which queues, shows them freely
(§5, the switching analysis of Fig 7 finds switching least common on Stack
Overflow, most common in email). Reciprocation chains like (A→B, B→A, A→B)
dominate messaging data; on-off Q&A rhythms show up in the Stack Exchange
traces. And sweeping δ turns one count into a curve whose knees expose the
natural timescales of an interaction (the paper finds certain Stack Overflow
Q&A patterns need ≥ 30 minutes to develop, §5).
Why it matters: these analyses are exactly the query shapes a temporal graph database gets asked — MATCH an ordered pattern WITHIN δ, GROUP BY motif, sweep δ — so the counting operators of Steps 4–5 are not paper curiosities; they are M33’s aggregate path.
How to read the paper (with the concepts in hand)
~10 pages, budget ~2.5 h.
- §1 (intro) — read carefully. The motivating example and the blocking/non-blocking teaser; this is Steps 1 and 6 in miniature.
- §2 (definitions) — read carefully. The formal
k-node,l-edge δ-temporal motif and instance definitions (Step 2) and the 36-motif grid (Fig 3). Spend real time on Fig 3 — the empirical sections index everything by its rows and columns, and you want to point at any cell and name the behavior it encodes. - §4 (algorithms) — the core. Read the general algorithm §4.1 (Step 4)
first and check its counter-update orders against the Rust above; then the
star section (cheap, §4.2), then the triangle section slowly (Step 5) — the
edge-to-triangle assignment argument and its
O(m√τ)bound are the paper’s main algorithmic contribution. Skim complexity proofs on first pass. - §5 (experiments/analysis) — read the heatmaps carefully, skim the rest. The per-dataset motif-fraction heatmaps (Fig 5) carry the findings of Step 6; extract the blocking-vs-non-blocking evidence and two concrete speedup numbers (general vs fast algorithms) into notes.md.
- Related work — skim, noting how δ-motifs differ from earlier “time-respecting subgraph” definitions that require paths rather than ordered windows.
Questions to answer in notes.md
- Derive the 36: why exactly that many motifs with
l = 3edges onk ≤ 3nodes and a total order? Show the counting argument (the 4 + 8 + 24 decomposition, or the 6 × 6 grid). - In Step 4’s code, why must expiry update shortest fragments first and insertion longest first? Construct a 3-event sequence that gets miscounted if either order is flipped.
- From the heatmaps (Fig 5): which motif cells separate the phone/SMS (blocking) datasets from email (non-blocking)? Record the actual fractions the paper reports.
- Capstone M33: write motif
M = (A→B, B→A, A→B)as a time-respecting MATCH with WITHIN δ. Which parts does the planner get free from the δ constraint, and where must Step 4’s window-scan operator replace enumerate-then-filter to avoid Step 3’s C(n, 3) blowup? - Topic 24 tie: static triangle counting is a masked matrix multiply in GraphBLAS. Exactly where does the temporal triangle count stop being expressible as a matrix product, and what per-triangle state survives?
- δ-sweep as a workload: if a user recomputes counts at 20 values of δ, what does topic 30’s time-bucketed storage (M30) let you reuse across sweeps, and what must be recomputed?
Done when
Answer each before unfolding it.
-
You can derive the 36-motif count.
Answer
Count
l = 3-edge motifs onk ≤ 3nodes, up to relabeling nodes by order of first appearance, and decompose by node count (Fig 3’s colours):- 2-node (nodes A, B only): the first edge is fixed A→B by the
first-appearance convention, leaving 2 directions each for edges 2 and 3 →
2 × 2 = 4motifs. - Triangle (3 distinct nodes, each of the 3 edges on a different pair):
8motifs. - Star (a center plus two spokes, all 3 edges incident to the center):
the paper groups these into 3 classes (pre / post / mid) of
2³ = 8each →24motifs.
4 + 8 + 24 = 36, exactly the 6 × 6 grid the paper indexes by second edge (row) and third edge (column) with the first edge fixed (§2, Fig 3). - 2-node (nodes A, B only): the first edge is fixed A→B by the
first-appearance convention, leaving 2 directions each for edges 2 and 3 →
-
You can hand-trace Step 4’s window scan over a five-event sequence without miscounting.
Answer
Take
M = [0, 1, 0](i.e. A→B, B→A, A→B), δ large enough that nothing expires, and events(t, lab)=(1,0), (2,1), (3,0), (4,1), (5,0). Process left to right, banking completions before inserting (Step 4, phase 2):(1,0):lab 0 == motif[2] 0, butcnt[0][1] = 0, so bank 0. Insert extends fragment[0]:cnt[0][0] = 1.(2,1):lab 1 ≠ motif[2] 0, bank nothing. Insert:motif[1] = 1, socnt[0][1] += cnt[0][0] = 1.(3,0):lab 0 == motif[2], bankcnt[0][1] = 1→total = 1. Insert:cnt[0][0] += 1 = 2.(4,1): bank nothing. Insert:cnt[0][1] += cnt[0][0] = 2→cnt[0][1] = 3.(5,0): bankcnt[0][1] = 3→total = 4.
Four instances: the A→B at position 5 completes with each earlier (A→B, B→A) prefix, and the A→B at position 3 completed one earlier. Banking before inserting is what stops event
(5,0)from pairing with itself; the shortest-first expiry (unused here) is what keeps the subtraction consistent when δ is finite. -
You can explain in one sentence each why stars are cheap and triangles are hard.
Answer
Stars are cheap because every edge of a 3-node star touches the center, so a single dynamic-programming pass over each center’s incident edges — keyed by neighbor and direction, minus the 2-node-motif correction for coincident spokes — counts them in
O(m), linear in the input (§4.2).Triangles are hard because an edge between
uandvlies on every triangle through that pair, so scanning per triangle re-reads hot edges; the paper’s fix assigns each edge to the triangles it can complete, cutting the naiveO(mτ)toO(TriEnum + m√τ)whereτis the static-triangle count (§4.2) — the√τis the whole reason a 63M-edge trace finishes. -
You can state which M33 query shape each of the paper’s two algorithm families maps onto.
Answer
The general algorithm (§4.1, Step 4) counts any single
Mby gathering the edges among a matched static subgraph and running theO(l²)-state sliding-window scan — it maps onto M33’s “MATCH this specific ordered pattern WITHIN δ”, where the planner has already pinned the shape and only the window scan remains.The specialized family (§4.2, Step 5 — 2-node, star, triangle) counts all motifs of a class at once with per-class bounds (
O(m)for 2-node and stars,O(m√τ)for triangles) — it maps onto M33’s “GROUP BY motif over the whole trace” aggregate, where enumerate-then-filter would pay Step 3’sC(n,3)blow-up and the specialized counters avoid it.
References
Papers
- Paranjape, Benson, Leskovec — “Motifs in Temporal Networks” (WSDM 2017) —
arXiv /
PDF — ~10 pages, ~2.5 h: read the §2
definitions and Fig 3 carefully, the §4.1 general algorithm against Step 4’s
code, the §4.2 triangle section slowly, and the §5 heatmaps for the findings;
skim proofs. Anchors used above: §2 + Fig 3 (definition, the 36 motifs),
§4.1 (Algorithm 1,
O(l²)counters,O(2lm)2-node bound), §4.2 (starO(m), triangleO(TriEnum + m√τ), up to 56.5× speedup), §5 (blocking vs non-blocking, δ-sweep). - Dataset: the SNAP
sx-stackoverflowtemporal network the paper released — 2,601,977 nodes, 63,497,050 temporal edges, 2,774-day span (snap.stanford.edu/data/sx-stackoverflow).
Related guides
- reading-temporal-paths.md — where reachability (not ordering) is the information; the two guides are the two ways timestamps change a graph question.
- README.md — the topic’s measured false-positive headline; the motif count is the aggregate companion to that path query.