communities
The communities of a weighted graph (Louvain): n nodes and links as [a, b, weight], either way round; each
community a list of its nodes, the nodes that link to each other more than to the rest.
export function communities(n: number, links: [number, number, number][]): number[][] { let adj = Array.from({ length: n }, () => new Map<number, number>()); for (const [a, b, w] of links) { if (a === b) continue; adj[a]!.set(b, (adj[a]!.get(b) ?? 0) + w); adj[b]!.set(a, (adj[b]!.get(a) ?? 0) + w); } let members = Array.from({ length: n }, (_, j) => [j]); for (let level = 0; level < 8; level++) { // move each node to the neighbouring community that gains the most modularity, until none moves const size = adj.length; const deg = adj.map((m) => [...m.values()].reduce((s, w) => s + w, 0)); const total = deg.reduce((s, d) => s + d, 0) || 1; const of = [...Array(size).keys()]; const tot = deg.slice(); let moved = true; let any = false; while (moved) { moved = false; for (let v = 0; v < size; v++) { const from = of[v]!; const into = new Map<number, number>(); for (const [u, w] of adj[v]!) if (u !== v) into.set(of[u]!, (into.get(of[u]!) ?? 0) + w); tot[from]! -= deg[v]!; let best = from; let gain = (into.get(from) ?? 0) - (tot[from]! * deg[v]!) / total; for (const [c, w] of into) { const g = w - (tot[c]! * deg[v]!) / total; if (g > gain + 1e-9) { gain = g; best = c; } } tot[best]! += deg[v]!; if (best !== from) { of[v] = best; moved = any = true; } } } if (!any) break; // then each community becomes a node, and again const ids = [...new Set(of)]; const index = new Map(ids.map((c, j) => [c, j])); const next = ids.map(() => [] as number[]); const nextAdj = ids.map(() => new Map<number, number>()); members.forEach((ms, v) => next[index.get(of[v]!)!]!.push(...ms)); adj.forEach((m, v) => { for (const [u, w] of m) { const a = index.get(of[v]!)!; const b = index.get(of[u]!)!; nextAdj[a]!.set(b, (nextAdj[a]!.get(b) ?? 0) + w); } }); members = next; adj = nextAdj; } return members; }