Connected Components (Label Propagation)
Connected components by max-label propagation, where every vertex repeatedly adopts the largest label among itself and its neighbours until labels stop changing, expressed as a single iterated extended Einsum over a symmetric, reflexive graph tensor.
Status Intro
This page gives connected components computed by max-label propagation. Each vertex starts with a unique label and, every generation, adopts the maximum label among itself and its neighbours. Labels only ever increase and are bounded, so the labels converge; vertices that end up sharing a label are in the same connected component. The whole computation is a single extended Einsum threaded through a generational rank $I$, iterated until the label vector stops changing.
Problem Statement
Given an undirected graph $G$ on $\lvert V\rvert$ vertices, partition the vertices into connected components: assign every pair of vertices the same component label exactly when a path connects them.
The state is carried by two tensors:
- $G^{S \equiv \lvert V\rvert,\, D \equiv \lvert V\rvert} \to \text{float}$,
the adjacency matrix, with
empty$= 0.0$. $G$ is static, so it carries no generational rank. - $L^{I,\, S \equiv \lvert V\rvert} \to \text{float}$, the per-vertex label at
generation $i$, with
empty$= 0.0$.
Correctness and termination require $G$ to be symmetric, reflexive (a
self-loop on every vertex), and $0/1$-weighted, and the initial labels
$L_0$ to be unique per vertex with every vertex seeded (there is no source
set). The standard seeding is $\text{label}(v) = v + 1$, which keeps every
label strictly above the empty value $0.0$.
Working Example Part 1
Take five vertices in two components: a path $0 - 1 - 2$ and a single edge $3 - 4$. Adding the required self-loops, the symmetric, reflexive, $0/1$ adjacency matrix is
\[G = \begin{bmatrix} 1 & 1 & 0 & 0 & 0\\ 1 & 1 & 1 & 0 & 0\\ 0 & 1 & 1 & 0 & 0\\ 0 & 0 & 0 & 1 & 1\\ 0 & 0 & 0 & 1 & 1 \end{bmatrix}.\]Every vertex is seeded with its own unique label $\text{label}(v) = v + 1$:
\[L_0 = \lbrace 0:1,\ 1:2,\ 2:3,\ 3:4,\ 4:5\rbrace.\]Working Example Part 2
Each generation replaces every vertex’s label with the maximum label among itself and its neighbours, $L_{i+1,d} = \max_{s}\,\lbrace G_{s,d} \times L_{i,s}\rbrace$. The reflexive self-loop puts each vertex’s own current label into that maximum, so labels never decrease.
| $i$ | $L_i$ | per-vertex update |
|---|---|---|
| 0 | $\lbrace 1,\ 2,\ 3,\ 4,\ 5\rbrace$ | initial labels |
| 1 | $\lbrace 2,\ 3,\ 3,\ 5,\ 5\rbrace$ | $0!:!\max(1,2),\ 1!:!\max(1,2,3),\ 2!:!\max(2,3),\ 3,4!:!\max(4,5)$ |
| 2 | $\lbrace 3,\ 3,\ 3,\ 5,\ 5\rbrace$ | $0!:!\max(2,3),\ 1!:!\max(2,3,3),\ 2!:!\max(3,3)$ |
| 3 | $\lbrace 3,\ 3,\ 3,\ 5,\ 5\rbrace$ | unchanged $\equiv L_2$ — stop |
At $i = 3$ the labels equal those at $i = 2$, so $L_3 \equiv L_2$ fires and the iteration halts. The final labels are $L = \lbrace 0:3,\ 1:3,\ 2:3,\ 3:5,\ 4:5\rbrace$: component $\lbrace 0,1,2\rbrace$ settles on the maximum of its labels, $3$, and component $\lbrace 3,4\rbrace$ settles on $5$. Vertices sharing a final label are exactly the vertices in the same connected component.
EDGE Expression Walkthrough
Propagate. $L_{i+1,d} = G_{s,d} \times L_{i,s} :: \bigwedge_s \times(\cap)\ \bigvee_s \max(\cup)$. The map $\times(\cap)$ touches a coordinate only where both the edge $G_{s,d}$ and the source label $L_{i,s}$ are present, multiplying them; with $0/1$ edge weights this passes the neighbour’s label through unchanged. Empty is not zero: the intersection simply skips a point where either operand is absent, it does not contribute a $0$. The reduction $\bigvee_s \max(\cup)$ then keeps the largest incoming label at each destination $d$.
Because the gather is over $G_{s,d}$ (edges into $d$), a label flows $s \to d$ only when $(s,d) \in G$. Symmetry makes labels propagate both ways across each undirected edge. The reflexive self-loop $(d,d)$ is what puts vertex $d$’s own current label $L_{i,d}$ into the set of values gathered at $d$ — the reduction state is seeded at the $\max$ identity, not at $L_{i,d}$, so it is the self-loop, not the $\max$, that guarantees $L_{i+1,d} \ge L_{i,d}$.
Stop. $\diamond : L_{i+1} \equiv L_i$. The stop is value convergence, not occupancy: $L$ never empties, it converges in value. The whole-tensor $\equiv$ holds when every coordinate of $L_{i+1}$ equals the corresponding coordinate of $L_i$. With reflexivity, labels are monotonically non-decreasing and bounded above by the largest seed, so this condition is always reached.
EDGE Expression
Other Notes
All three graph preconditions are load-bearing. Reflexivity is required
for termination, not just correctness: without a self-loop, a vertex never
gathers its own current label, and the iteration can oscillate forever — on
the path $0 - 1 - 2$ with $L_0 = \lbrace 1,2,3\rbrace$ it cycles
$\lbrace 2,3,2\rbrace \to \lbrace 3,2,3\rbrace \to \cdots$ and never settles.
Symmetry is required because the gather is over in-edges; on a directed
graph this maxes over in-edges only and does not give weakly connected
components. $0/1$ weights are required because the map is $\times$: a
present weight must be exactly $1$ so it passes the label through unchanged; a
weight $\ne 1$ scales the propagated label into a meaningless component id, and
a weight $> 1$ makes labels grow unboundedly and breaks termination. Labels
must also stay strictly above the empty value $0.0$, which the
$\text{label}(v) = v + 1$ convention guarantees; a label of $0$ would be
indistinguishable from absent and silently swallowed. An isolated vertex keeps
its own label through its self-loop and is correctly its own singleton
component.
Variants
Replacing the $\max$ reduction with $\min$ propagates the smallest label instead, giving the same components labelled by their minimum seed. Structurally this Einsum is the breadth-first-search advance, $T_{i,d} = G_{s,d} \cdot F_{i,s} :: \bigwedge_s +(\cap)\ \bigvee_s \text{ANY}(\cup)$, with the map $+$ replaced by $\times$ and the reduce $\text{ANY}$ replaced by $\max$. Swapping the map $\times(\cap)$ for take-right $\rightarrow(\cap)$ would pass the source label through regardless of edge weight, making “edge weights do not matter” literally true and removing the scaling and termination hazard tied to $\times$.
Implementation Notes
Each generation is a sparse matrix-by-label product followed by a max reduction over the source rank — the same kernel shape as a BFS advance, with multiply-and-max in place of add-and-any. Keeping $L$ sparse (present-only) means each generation touches work proportional to the active labels and the edges incident to them. Termination is detected by comparing $L_{i+1}$ to $L_i$ for whole-tensor equality each round.
Complexity Costs
Each generation costs $O(\lvert V\rvert + \lvert E\rvert)$ for the propagate step. The number of generations needed for a label to traverse a component is bounded by that component’s diameter, so the total work is $O((\lvert V\rvert + \lvert E\rvert)\cdot \text{diam})$ in the worst case.