GPU Graph connectivity and communities
Find disconnected groups with GPUGraphConnectedComponents
Question: Which vertices belong to the same connected island if edge direction is ignored?
GPUGraphConnectedComponents identifies vertices connected by any path when edge direction is
ignored. Use it to separate disconnected social networks, collect related transaction accounts,
find infrastructure islands, or detect independent dependency groups.
For example, two transfers Ana -> Bo and Bo -> Cy put all three accounts in the same group,
even though Cy has no outgoing transfer. An unrelated transfer Dee -> Eli forms a different
group. Choose weak components when group membership matters, not the distance from a selected
account or the direction in which influence flows.
import {GPUGraphConnectedComponents} from '@luma.gl/gpgpu/gpu-graph';
const components = new GPUGraphConnectedComponents({
topology,
output: componentIds,
iterations: 32,
converged: componentsConverged
});
Once propagation converges, every vertex in a weakly connected component receives that group's lowest stable vertex identifier; an isolated vertex labels itself. Directed edges connect both endpoints, so reverse adjacency is unnecessary.
The caller chooses a bounded iteration budget. The optional one-row uint32 converged result is
one only when the final iteration reaches a fixed point; zero means convergence was not established
or the required adjacency overflowed. A connected component answers whether entities connect at
all; it does not claim to discover densely connected communities within one connected network.
Discover densely connected communities with GPUGraphLabelPropagation
Question: Which vertices form closely connected communities inside a network that is otherwise connected?
GPUGraphLabelPropagation groups vertices by the labels most common in their immediate
neighborhood. Use it to reveal circles of friends within a social network, identify related
transaction accounts within a larger fraud investigation, separate service ownership groups inside
a connected dependency graph, or color locally cohesive regions of a citation network.
A connected component answers whether any path links two vertices. Community detection asks a different question: are these vertices more strongly connected to one another than to the rest of the same network? Imagine two teams whose members interact frequently within their own team but share only one relationship across teams. That single bridge makes the whole network one weakly connected component, while label propagation can still give each team a different community label. Use weak components to find disconnected islands; use community labels to inspect local structure within an island.
import {GPUGraphLabelPropagation} from '@luma.gl/gpgpu/gpu-graph';
const communities = new GPUGraphLabelPropagation({
topology,
output: communityIds,
iterations: 32,
converged: communitiesConverged
});
Every vertex begins with its stable vertex identifier as its label. Each synchronous round reads the preceding round's complete label snapshot and selects the most frequent label among one self vote and all incoming or outgoing neighbor occurrences. Equal vote counts choose the numerically lowest label, so the result does not depend on unspecified adjacency ordering. Self-loops add no extra self votes; duplicate edges and reciprocal directed edges vote independently. Existing edge weights are preserved by topology but ignored by this unweighted majority vote.
Directed graphs require both forward and reverse adjacency to include every weak neighbor.
Undirected graphs reuse symmetric forward adjacency without reverse CSR. output is a
caller-owned, packed GPUVector<'uint32'> containing exactly one community label per vertex;
the optional converged output is a separate, caller-owned one-row GPUVector<'uint32'>.
Neither allocation may physically alias graph inputs, adjacency storage, or another writable
output.
The default is 32 synchronous rounds; applications can explicitly choose an integer from 1
through 1024. Every declared round is encoded without CPU synchronization, automatic readback,
or early termination. converged becomes one only when the final round changes no labels; zero
means that the chosen budget did not establish a fixed point. Some graphs can oscillate between
label assignments, so a bounded round count never guarantees convergence. An empty graph reports
convergence, and an isolated vertex retains its own identifier.
If required forward or reverse adjacency overflows, all output labels become 0xffffffff and
converged becomes zero rather than publishing partial communities. The worst-case work is
O(sum(degree²)) per round because counting support for each candidate can rescan a vertex's
neighborhood; a high-degree hub can therefore be disproportionately expensive. This deterministic
label-propagation heuristic is not Louvain or Leiden, does not optimize modularity, and does not
guarantee objectively correct communities or a particular clustering quality.
Improve weighted community partitions with GPUGraphModularityOptimization
Question: Which actual community reassignment improves a network's measurable partition quality, rather than merely winning a neighborhood vote?
GPUGraphModularityOptimization improves an existing or automatically initialized community
partition by accepting the best strictly beneficial single-vertex move in each bounded round.
Use it when a social grouping should reflect actual interaction strength, a proposed fraud ring
should concentrate transaction weight, ownership boundaries should better match weighted service
dependencies, or a knowledge-graph partition needs an objective comparison before and after
refinement.
Three community questions are related but distinct. GPUGraphLabelPropagation cheaply proposes
groups from unweighted neighborhood votes without optimizing their quality;
GPUGraphModularity scores any caller-provided partition without changing it; and
GPUGraphModularityOptimization actually changes group assignments when the same weighted
modularity objective improves. Start from separate communities when discovering structure, or
provide initialCommunities to refine labels supplied by a heuristic, application, or prior
analysis. The caller receives both the improved labels and the score of that exact final
partition.
import {GPUGraphModularityOptimization} from '@luma.gl/gpgpu/gpu-graph';
const optimizedCommunities = new GPUGraphModularityOptimization({
topology,
output: improvedCommunityIds,
modularity: optimizedModularity,
initialCommunities: proposedCommunityIds,
resolution: 1,
iterations: 32,
minimumGain: 0,
converged: optimizationConverged,
valid: optimizationValid
});
optimizedCommunities.addToGraph(workflow);
output is a caller-owned, packed GPUVector<'uint32'> with exactly one community identifier per
vertex. The optional initialCommunities is a separate caller-owned, packed uint32 vector with
the same number of rows; without it, every vertex initially belongs to its own stable-identifier
community. modularity is a separate caller-owned one-row GPUVector<'float32'> containing the
real final weighted partition score. The optional converged and valid results are physically
distinct caller-owned one-row GPUVector<'uint32'> status vectors.
The objective is the same Newman modularity used by GPUGraphModularity. Directed graphs use
Q = Σc [Lc / W - γ × Kout,c × Kin,c / W²]; undirected graphs use
Q = Σc [Lc / W - γ × (Kc / (2W))²]. For every eligible vertex, the contributor considers
neighboring communities and the lowest genuinely unused stable community identifier. Occupancy
counts every vertex, including zero-degree isolates, so an occupied zero-volume community is never
mistaken for an empty one. This singleton candidate lets an over-merged warm start split even when
its only relationships are self-loops: two equally weighted self-loops with initial labels
[0, 0] can become [1, 0], improving modularity from zero to 0.5.
For each candidate, the contributor evaluates
ΔQ = Q(partition after moving the vertex) - Q(current partition). It accepts exactly one
globally best move per round, and only when ΔQ is strictly positive and strictly greater than
minimumGain. Tied gains choose the lowest stable vertex identifier, followed by the lowest
candidate community identifier. Evaluating an immutable prior partition and applying only one move
avoids simultaneous conflicting moves and never intentionally accepts a modularity regression.
This tie-breaking policy is deterministic for a fixed snapshot of computed float32 gains.
However, weighted degrees and community volumes use unordered atomic additions, and floating-point
addition is not associative. Low-order rounding can therefore differ across GPU execution orders or
adapters. Near-tied gains, strict minimumGain decisions, selected community labels, and final
modularity scores may consequently vary across runs or devices; weighted partitions are not
guaranteed to be identical.
iterations defaults to 32 and may be any integer from 0 through 1024.
minimumGain defaults to zero and must be a finite, nonnegative value representable as float32.
resolution defaults to one and follows the same finite, nonnegative float32 contract as the
standalone modularity scorer. Zero rounds preserve the caller's initial partition or the identity
assignment, publish its real modularity, and report convergence zero for a valid nonempty graph.
If a completed round finds no admissible positive-gain move, converged becomes one. If the
iteration budget ends immediately after an improving move, convergence remains zero: a bounded
improvement is not evidence that a local optimum has been reached.
A local fixed point is not necessarily the globally best partition.
Directed graphs require both forward and reverse CSR; undirected graphs reuse symmetric forward
CSR. Original nonnegative float32 edge weights are preserved, and missing weights mean one.
Parallel source edges, reciprocal directed edges, and self-loops retain exactly the same weighted
multiplicity and degree-volume conventions as GPUGraphModularity. Out-of-domain source endpoints
are ignored together with their weights; an invalid warm-start label, negative or nonfinite
accepted edge weight, zero valid total edge weight, floating-point accumulation overflow, or
overflow in required adjacency fails closed. Every output label then becomes 0xffffffff, the
modularity score becomes zero, and optional validity and convergence become zero. An empty graph
has no label rows, score zero, validity zero, and convergence one if adjacency did not overflow.
The contributor encodes all bounded candidate evaluation, stable tie-broken winner selection, label
updates, and final GPUGraphModularity scoring into the caller-owned GPU command graph. It does
not submit work, read results back, or synchronize with the CPU. Worst-case work for K rounds is
O(K × (V + E + sum(degree²))), with separate O(V + E) initialization and final scoring and
O(V + E) graph-owned packed scratch; linear per-round community occupancy and vacancy checks are
included in that bound. High-degree hubs and large round budgets require explicit measurement.
This is single-level Louvain-style local moving, not the complete multilevel
Louvain algorithm, Leiden refinement, community coarsening, hierarchical aggregation, a global
optimality guarantee, or a seventh Graphalytics workload.
Evaluate community quality with GPUGraphModularity
Question: Does an existing community grouping keep more relationship weight inside its groups than a degree-matched random network would predict?
GPUGraphModularity scores a partition that the application already owns; it does not create or
improve that partition. Use it to compare rival social-network groupings, check whether a detected
fraud ring concentrates transaction weight, evaluate whether service ownership labels follow real
dependency structure, or monitor whether an evolving document grouping is more meaningful than
chance. Feed it labels from GPUGraphLabelPropagation, GPUGraphModularityOptimization, an
external clustering method, or any other caller-owned assignment.
A high positive score means the specified partition keeps more relationship weight within its groups than the corresponding degree-preserving random baseline predicts; a score near zero suggests little advantage over that baseline. A negative score means the partition keeps less internal weight than expected. Scores depend on the graph and resolution parameter: they are not universal quality percentages or proof that one partition is objectively correct.
import {GPUGraphModularity} from '@luma.gl/gpgpu/gpu-graph';
const partitionQuality = new GPUGraphModularity({
graph,
communities: communityIds,
output: modularityScore,
resolution: 1,
communityContributions,
valid: modularityValid
});
partitionQuality.addToGraph(workflow);
communities is a caller-owned, packed GPUVector<'uint32'> containing exactly one stable
community identifier per vertex. Every label must be in the range from zero through
vertexCount - 1. output is a separate caller-owned, packed one-row GPUVector<'float32'>.
The optional communityContributions result has one float32 row per possible community label;
unused identifiers receive zero. The optional one-row GPUVector<'uint32'> valid distinguishes
a successfully evaluated partition from a zeroed failure result. Outputs may not physically alias
the graph's original buffers, community labels, or one another.
Let W be the total weight of all valid-endpoint original source edges and let Lc be the
internal original edge weight for community c. A missing weight column gives every edge weight
one. Directed graphs use the weighted directed modularity formula
Q = Σc [Lc / W - γ × Kout,c × Kin,c / W²], where Kout,c and Kin,c are that community's
outgoing and incoming weighted volumes. Undirected graphs use
Q = Σc [Lc / W - γ × (Kc / (2W))²], where Kc is the community's weighted degree volume. An
undirected self-loop contributes once to W and Lc but twice to Kc. Parallel source edges
and reciprocal directed source edges retain their original multiplicity; modularity deliberately
does not apply the simple-graph deduplication used by core numbers.
resolution, written γ above, defaults to one and must be a finite, nonnegative value that
remains finite as float32; it adjusts the degree-matched expectation without changing the input
partition. An invalid community identifier, a negative or nonfinite weight on an edge with valid
endpoints, an empty graph, total valid edge weight of zero, or floating-point accumulation
overflow publishes score zero, zero per-community contributions, and optional valid zero.
Edges with invalid endpoints are excluded entirely, including their weights. Existing source edge
batches remain separate, ordered, and borrowed; the operation reads them directly without
constructing or requiring forward or reverse CSR.
Weighted contributions, community volumes, and the final score use ordinary GPU float32
arithmetic; concurrent atomic accumulation can make the final low-order rounding vary across
execution orders or devices. Work is bounded by O(V + E), with O(V) graph-owned community
volume scratch and bounded reduction storage. This contributor measures a supplied partition; it
is not Louvain, Leiden, automatic community optimization, hierarchical coarsening, or a
guarantee that label propagation converged.