@graphty/algorithms 1.1.0 → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +820 -235
- package/dist/algorithms.d.ts +42 -2
- package/dist/algorithms.js +11099 -2
- package/dist/algorithms.js.map +1 -1
- package/dist/src/algorithms/centrality/betweenness.d.ts +4 -0
- package/dist/src/algorithms/centrality/betweenness.d.ts.map +1 -1
- package/dist/src/algorithms/centrality/betweenness.js +124 -170
- package/dist/src/algorithms/centrality/betweenness.js.map +1 -1
- package/dist/src/algorithms/centrality/closeness.d.ts +26 -1
- package/dist/src/algorithms/centrality/closeness.d.ts.map +1 -1
- package/dist/src/algorithms/centrality/closeness.js +56 -149
- package/dist/src/algorithms/centrality/closeness.js.map +1 -1
- package/dist/src/algorithms/centrality/degree.js +3 -3
- package/dist/src/algorithms/centrality/degree.js.map +1 -1
- package/dist/src/algorithms/centrality/delta-pagerank-simple.d.ts +39 -0
- package/dist/src/algorithms/centrality/delta-pagerank-simple.d.ts.map +1 -0
- package/dist/src/algorithms/centrality/delta-pagerank-simple.js +286 -0
- package/dist/src/algorithms/centrality/delta-pagerank-simple.js.map +1 -0
- package/dist/src/algorithms/centrality/delta-pagerank.d.ts +80 -0
- package/dist/src/algorithms/centrality/delta-pagerank.d.ts.map +1 -0
- package/dist/src/algorithms/centrality/delta-pagerank.js +294 -0
- package/dist/src/algorithms/centrality/delta-pagerank.js.map +1 -0
- package/dist/src/algorithms/centrality/index.d.ts +2 -0
- package/dist/src/algorithms/centrality/index.d.ts.map +1 -1
- package/dist/src/algorithms/centrality/index.js +1 -0
- package/dist/src/algorithms/centrality/index.js.map +1 -1
- package/dist/src/algorithms/centrality/pagerank.d.ts +17 -0
- package/dist/src/algorithms/centrality/pagerank.d.ts.map +1 -1
- package/dist/src/algorithms/centrality/pagerank.js +45 -0
- package/dist/src/algorithms/centrality/pagerank.js.map +1 -1
- package/dist/src/algorithms/community/girvan-newman.d.ts.map +1 -1
- package/dist/src/algorithms/community/girvan-newman.js +47 -54
- package/dist/src/algorithms/community/girvan-newman.js.map +1 -1
- package/dist/src/algorithms/community/index.d.ts +2 -0
- package/dist/src/algorithms/community/index.d.ts.map +1 -1
- package/dist/src/algorithms/community/index.js.map +1 -1
- package/dist/src/algorithms/community/label-propagation.d.ts +13 -3
- package/dist/src/algorithms/community/label-propagation.d.ts.map +1 -1
- package/dist/src/algorithms/community/label-propagation.js +50 -35
- package/dist/src/algorithms/community/label-propagation.js.map +1 -1
- package/dist/src/algorithms/community/leiden.d.ts +3 -2
- package/dist/src/algorithms/community/leiden.d.ts.map +1 -1
- package/dist/src/algorithms/community/leiden.js +34 -39
- package/dist/src/algorithms/community/leiden.js.map +1 -1
- package/dist/src/algorithms/community/louvain-optimized.d.ts +112 -0
- package/dist/src/algorithms/community/louvain-optimized.d.ts.map +1 -0
- package/dist/src/algorithms/community/louvain-optimized.js +303 -0
- package/dist/src/algorithms/community/louvain-optimized.js.map +1 -0
- package/dist/src/algorithms/community/louvain.d.ts.map +1 -1
- package/dist/src/algorithms/community/louvain.js +17 -76
- package/dist/src/algorithms/community/louvain.js.map +1 -1
- package/dist/src/algorithms/community/modularity-utils.d.ts +59 -0
- package/dist/src/algorithms/community/modularity-utils.d.ts.map +1 -0
- package/dist/src/algorithms/community/modularity-utils.js +112 -0
- package/dist/src/algorithms/community/modularity-utils.js.map +1 -0
- package/dist/src/algorithms/matching/bipartite.d.ts.map +1 -1
- package/dist/src/algorithms/matching/bipartite.js +8 -40
- package/dist/src/algorithms/matching/bipartite.js.map +1 -1
- package/dist/src/algorithms/shortest-path/bellman-ford.d.ts.map +1 -1
- package/dist/src/algorithms/shortest-path/bellman-ford.js +1 -12
- package/dist/src/algorithms/shortest-path/bellman-ford.js.map +1 -1
- package/dist/src/algorithms/shortest-path/bidirectional-dijkstra.d.ts +37 -0
- package/dist/src/algorithms/shortest-path/bidirectional-dijkstra.d.ts.map +1 -0
- package/dist/src/algorithms/shortest-path/bidirectional-dijkstra.js +193 -0
- package/dist/src/algorithms/shortest-path/bidirectional-dijkstra.js.map +1 -0
- package/dist/src/algorithms/shortest-path/dijkstra.d.ts +5 -2
- package/dist/src/algorithms/shortest-path/dijkstra.d.ts.map +1 -1
- package/dist/src/algorithms/shortest-path/dijkstra.js +15 -14
- package/dist/src/algorithms/shortest-path/dijkstra.js.map +1 -1
- package/dist/src/algorithms/traversal/bfs-unified.d.ts +29 -0
- package/dist/src/algorithms/traversal/bfs-unified.d.ts.map +1 -0
- package/dist/src/algorithms/traversal/bfs-unified.js +333 -0
- package/dist/src/algorithms/traversal/bfs-unified.js.map +1 -0
- package/dist/src/algorithms/traversal/bfs-variants.d.ts +54 -0
- package/dist/src/algorithms/traversal/bfs-variants.d.ts.map +1 -0
- package/dist/src/algorithms/traversal/bfs-variants.js +368 -0
- package/dist/src/algorithms/traversal/bfs-variants.js.map +1 -0
- package/dist/src/algorithms/traversal/bfs.d.ts +4 -18
- package/dist/src/algorithms/traversal/bfs.d.ts.map +1 -1
- package/dist/src/algorithms/traversal/bfs.js +5 -182
- package/dist/src/algorithms/traversal/bfs.js.map +1 -1
- package/dist/src/benchmark-all-algorithms.d.ts +23 -0
- package/dist/src/benchmark-all-algorithms.d.ts.map +1 -0
- package/dist/src/benchmark-all-algorithms.js +462 -0
- package/dist/src/benchmark-all-algorithms.js.map +1 -0
- package/dist/src/clustering/hierarchical.d.ts +16 -15
- package/dist/src/clustering/hierarchical.d.ts.map +1 -1
- package/dist/src/clustering/hierarchical.js +209 -223
- package/dist/src/clustering/hierarchical.js.map +1 -1
- package/dist/src/clustering/index.d.ts +4 -2
- package/dist/src/clustering/index.d.ts.map +1 -1
- package/dist/src/clustering/index.js +2 -2
- package/dist/src/clustering/index.js.map +1 -1
- package/dist/src/clustering/k-core.d.ts +16 -15
- package/dist/src/clustering/k-core.d.ts.map +1 -1
- package/dist/src/clustering/k-core.js +57 -21
- package/dist/src/clustering/k-core.js.map +1 -1
- package/dist/src/clustering/spectral.d.ts +8 -0
- package/dist/src/clustering/spectral.d.ts.map +1 -1
- package/dist/src/clustering/spectral.js +158 -35
- package/dist/src/clustering/spectral.js.map +1 -1
- package/dist/src/core/graph.js +4 -4
- package/dist/src/core/graph.js.map +1 -1
- package/dist/src/flow/ford-fulkerson.d.ts +17 -11
- package/dist/src/flow/ford-fulkerson.d.ts.map +1 -1
- package/dist/src/flow/ford-fulkerson.js +141 -217
- package/dist/src/flow/ford-fulkerson.js.map +1 -1
- package/dist/src/flow/min-cut.d.ts +6 -5
- package/dist/src/flow/min-cut.d.ts.map +1 -1
- package/dist/src/flow/min-cut.js +17 -8
- package/dist/src/flow/min-cut.js.map +1 -1
- package/dist/src/index.d.ts +2 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +6 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/link-prediction/adamic-adar.d.ts.map +1 -1
- package/dist/src/link-prediction/adamic-adar.js +17 -26
- package/dist/src/link-prediction/adamic-adar.js.map +1 -1
- package/dist/src/link-prediction/common-neighbors.d.ts.map +1 -1
- package/dist/src/link-prediction/common-neighbors.js +7 -11
- package/dist/src/link-prediction/common-neighbors.js.map +1 -1
- package/dist/src/optimized/bit-packed.d.ts +143 -0
- package/dist/src/optimized/bit-packed.d.ts.map +1 -0
- package/dist/src/optimized/bit-packed.js +292 -0
- package/dist/src/optimized/bit-packed.js.map +1 -0
- package/dist/src/optimized/csr-graph.d.ts +86 -0
- package/dist/src/optimized/csr-graph.d.ts.map +1 -0
- package/dist/src/optimized/csr-graph.js +341 -0
- package/dist/src/optimized/csr-graph.js.map +1 -0
- package/dist/src/optimized/direction-optimized-bfs.d.ts +74 -0
- package/dist/src/optimized/direction-optimized-bfs.d.ts.map +1 -0
- package/dist/src/optimized/direction-optimized-bfs.js +223 -0
- package/dist/src/optimized/direction-optimized-bfs.js.map +1 -0
- package/dist/src/optimized/graph-adapter.d.ts +62 -0
- package/dist/src/optimized/graph-adapter.d.ts.map +1 -0
- package/dist/src/optimized/graph-adapter.js +146 -0
- package/dist/src/optimized/graph-adapter.js.map +1 -0
- package/dist/src/optimized/index.d.ts +14 -0
- package/dist/src/optimized/index.d.ts.map +1 -0
- package/dist/src/optimized/index.js +28 -0
- package/dist/src/optimized/index.js.map +1 -0
- package/dist/src/research/grsbm.d.ts +83 -0
- package/dist/src/research/grsbm.d.ts.map +1 -0
- package/dist/src/research/grsbm.js +403 -0
- package/dist/src/research/grsbm.js.map +1 -0
- package/dist/src/research/index.d.ts +12 -0
- package/dist/src/research/index.d.ts.map +1 -0
- package/dist/src/research/index.js +14 -0
- package/dist/src/research/index.js.map +1 -0
- package/dist/src/research/sync.d.ts +49 -0
- package/dist/src/research/sync.d.ts.map +1 -0
- package/dist/src/research/sync.js +316 -0
- package/dist/src/research/sync.js.map +1 -0
- package/dist/src/research/terahac.d.ts +65 -0
- package/dist/src/research/terahac.d.ts.map +1 -0
- package/dist/src/research/terahac.js +379 -0
- package/dist/src/research/terahac.js.map +1 -0
- package/dist/src/types/index.d.ts +8 -0
- package/dist/src/types/index.d.ts.map +1 -1
- package/dist/src/utils/algorithm-utilities.d.ts +2 -0
- package/dist/src/utils/algorithm-utilities.d.ts.map +1 -0
- package/dist/src/utils/algorithm-utilities.js +2 -0
- package/dist/src/utils/algorithm-utilities.js.map +1 -0
- package/dist/src/utils/graph-converters.d.ts +79 -0
- package/dist/src/utils/graph-converters.d.ts.map +1 -0
- package/dist/src/utils/graph-converters.js +196 -0
- package/dist/src/utils/graph-converters.js.map +1 -0
- package/dist/src/utils/graph-utilities.d.ts +62 -0
- package/dist/src/utils/graph-utilities.d.ts.map +1 -0
- package/dist/src/utils/graph-utilities.js +148 -0
- package/dist/src/utils/graph-utilities.js.map +1 -0
- package/dist/src/utils/index.d.ts +5 -0
- package/dist/src/utils/index.d.ts.map +1 -0
- package/dist/src/utils/index.js +5 -0
- package/dist/src/utils/index.js.map +1 -0
- package/dist/src/utils/math-utilities.d.ts +52 -0
- package/dist/src/utils/math-utilities.d.ts.map +1 -0
- package/dist/src/utils/math-utilities.js +123 -0
- package/dist/src/utils/math-utilities.js.map +1 -0
- package/dist/src/utils/matrix-utilities.d.ts +2 -0
- package/dist/src/utils/matrix-utilities.d.ts.map +1 -0
- package/dist/src/utils/matrix-utilities.js +2 -0
- package/dist/src/utils/matrix-utilities.js.map +1 -0
- package/dist/src/utils/optimization-helpers.d.ts +33 -0
- package/dist/src/utils/optimization-helpers.d.ts.map +1 -0
- package/dist/src/utils/optimization-helpers.js +47 -0
- package/dist/src/utils/optimization-helpers.js.map +1 -0
- package/package.json +28 -4
- package/src/algorithms/centrality/betweenness.ts +167 -199
- package/src/algorithms/centrality/closeness.ts +73 -191
- package/src/algorithms/centrality/degree.ts +3 -3
- package/src/algorithms/centrality/delta-pagerank-simple.ts +366 -0
- package/src/algorithms/centrality/delta-pagerank.ts +435 -0
- package/src/algorithms/centrality/index.ts +2 -0
- package/src/algorithms/centrality/pagerank.ts +66 -0
- package/src/algorithms/community/girvan-newman.ts +52 -59
- package/src/algorithms/community/index.ts +2 -0
- package/src/algorithms/community/label-propagation.ts +64 -35
- package/src/algorithms/community/leiden.ts +40 -41
- package/src/algorithms/community/louvain-optimized.ts +445 -0
- package/src/algorithms/community/louvain.ts +24 -104
- package/src/algorithms/community/modularity-utils.ts +146 -0
- package/src/algorithms/matching/bipartite.ts +8 -44
- package/src/algorithms/shortest-path/bellman-ford.ts +1 -14
- package/src/algorithms/shortest-path/bidirectional-dijkstra.ts +248 -0
- package/src/algorithms/shortest-path/dijkstra.ts +17 -15
- package/src/algorithms/traversal/bfs-unified.ts +433 -0
- package/src/algorithms/traversal/bfs-variants.ts +496 -0
- package/src/algorithms/traversal/bfs.ts +10 -228
- package/src/benchmark-all-algorithms.ts +548 -0
- package/src/clustering/hierarchical.ts +241 -251
- package/src/clustering/index.ts +4 -2
- package/src/clustering/k-core.ts +87 -39
- package/src/clustering/spectral.ts +185 -34
- package/src/core/graph.ts +4 -4
- package/src/flow/ford-fulkerson.ts +186 -254
- package/src/flow/min-cut.ts +21 -11
- package/src/index.ts +9 -0
- package/src/link-prediction/adamic-adar.ts +17 -30
- package/src/link-prediction/common-neighbors.ts +7 -16
- package/src/optimized/bit-packed.ts +340 -0
- package/src/optimized/csr-graph.ts +455 -0
- package/src/optimized/direction-optimized-bfs.ts +291 -0
- package/src/optimized/graph-adapter.ts +200 -0
- package/src/optimized/index.ts +36 -0
- package/src/research/grsbm.ts +580 -0
- package/src/research/index.ts +17 -0
- package/src/research/sync.ts +440 -0
- package/src/research/terahac.ts +514 -0
- package/src/types/index.ts +8 -0
- package/src/utils/algorithm-utilities.ts +2 -0
- package/src/utils/graph-converters.ts +241 -0
- package/src/utils/graph-utilities.ts +199 -0
- package/src/utils/index.ts +4 -0
- package/src/utils/math-utilities.ts +140 -0
- package/src/utils/matrix-utilities.ts +2 -0
- package/src/utils/optimization-helpers.ts +81 -0
|
@@ -27,9 +27,11 @@ export function girvanNewman(
|
|
|
27
27
|
): CommunityResult[] {
|
|
28
28
|
const {maxCommunities} = options;
|
|
29
29
|
const minCommunitySize = options.minCommunitySize ?? 1;
|
|
30
|
+
const maxIterations = options.maxIterations ?? 100; // Prevent infinite loops
|
|
30
31
|
|
|
31
32
|
const dendrogram: CommunityResult[] = [];
|
|
32
|
-
const workingGraph =
|
|
33
|
+
const workingGraph = graph.clone();
|
|
34
|
+
let iterations = 0;
|
|
33
35
|
|
|
34
36
|
// Initial state: one large component
|
|
35
37
|
let components = getConnectedComponentsResult(workingGraph);
|
|
@@ -38,7 +40,8 @@ export function girvanNewman(
|
|
|
38
40
|
modularity: calculateModularity(graph, components.componentMap),
|
|
39
41
|
});
|
|
40
42
|
|
|
41
|
-
while (Array.from(workingGraph.edges()).length > 0) {
|
|
43
|
+
while (Array.from(workingGraph.edges()).length > 0 && iterations < maxIterations) {
|
|
44
|
+
iterations++;
|
|
42
45
|
// Calculate edge betweenness centrality
|
|
43
46
|
const edgeBetweenness = calculateEdgeBetweenness(workingGraph);
|
|
44
47
|
|
|
@@ -187,7 +190,7 @@ function calculateEdgeBetweenness(graph: Graph): Map<string, number> {
|
|
|
187
190
|
}
|
|
188
191
|
|
|
189
192
|
/**
|
|
190
|
-
* Find all shortest paths from a source node using BFS
|
|
193
|
+
* Find all shortest paths from a source node using optimized BFS
|
|
191
194
|
*/
|
|
192
195
|
function findAllShortestPaths(graph: Graph, source: NodeId): {
|
|
193
196
|
distances: Map<NodeId, number>;
|
|
@@ -198,39 +201,43 @@ function findAllShortestPaths(graph: Graph, source: NodeId): {
|
|
|
198
201
|
const predecessors = new Map<NodeId, NodeId[]>();
|
|
199
202
|
const pathCounts = new Map<NodeId, number>();
|
|
200
203
|
const queue: NodeId[] = [];
|
|
204
|
+
const visited = new Set<NodeId>();
|
|
201
205
|
|
|
202
|
-
//
|
|
203
|
-
for (const node of graph.nodes()) {
|
|
204
|
-
distances.set(node.id, Infinity);
|
|
205
|
-
predecessors.set(node.id, []);
|
|
206
|
-
pathCounts.set(node.id, 0);
|
|
207
|
-
}
|
|
208
|
-
|
|
206
|
+
// Only initialize for reachable nodes
|
|
209
207
|
distances.set(source, 0);
|
|
208
|
+
predecessors.set(source, []);
|
|
210
209
|
pathCounts.set(source, 1);
|
|
211
210
|
queue.push(source);
|
|
212
211
|
|
|
213
|
-
// BFS
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
212
|
+
// Optimized BFS with early termination
|
|
213
|
+
let queueIndex = 0;
|
|
214
|
+
while (queueIndex < queue.length) {
|
|
215
|
+
const current = queue[queueIndex++];
|
|
216
|
+
if (!current) {
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (visited.has(current)) {
|
|
221
|
+
continue;
|
|
218
222
|
}
|
|
219
223
|
|
|
224
|
+
visited.add(current);
|
|
225
|
+
|
|
220
226
|
const currentDistance = distances.get(current);
|
|
221
227
|
if (currentDistance === undefined) {
|
|
222
228
|
continue;
|
|
223
229
|
}
|
|
224
230
|
|
|
225
231
|
for (const neighbor of graph.neighbors(current)) {
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
const newDistance = currentDistance + edgeWeight;
|
|
229
|
-
const neighborDistance = distances.get(neighbor);
|
|
230
|
-
if (neighborDistance === undefined) {
|
|
232
|
+
// Skip if already processed
|
|
233
|
+
if (visited.has(neighbor)) {
|
|
231
234
|
continue;
|
|
232
235
|
}
|
|
233
236
|
|
|
237
|
+
const edgeWeight = 1; // Unweighted for community detection
|
|
238
|
+
const newDistance = currentDistance + edgeWeight;
|
|
239
|
+
const neighborDistance = distances.get(neighbor) ?? Infinity;
|
|
240
|
+
|
|
234
241
|
if (newDistance < neighborDistance) {
|
|
235
242
|
// Found shorter path
|
|
236
243
|
distances.set(neighbor, newDistance);
|
|
@@ -241,15 +248,15 @@ function findAllShortestPaths(graph: Graph, source: NodeId): {
|
|
|
241
248
|
}
|
|
242
249
|
|
|
243
250
|
queue.push(neighbor);
|
|
244
|
-
} else if (
|
|
251
|
+
} else if (newDistance === neighborDistance) {
|
|
245
252
|
// Found alternative shortest path
|
|
246
253
|
const neighborPredecessors = predecessors.get(neighbor);
|
|
247
|
-
|
|
248
|
-
const currentPathCount = pathCounts.get(current);
|
|
249
|
-
|
|
250
|
-
if (neighborPredecessors && neighborPathCount !== undefined && currentPathCount !== undefined) {
|
|
254
|
+
if (neighborPredecessors) {
|
|
251
255
|
neighborPredecessors.push(current);
|
|
252
|
-
pathCounts.
|
|
256
|
+
const currentPathCount = pathCounts.get(current);
|
|
257
|
+
if (currentPathCount !== undefined) {
|
|
258
|
+
pathCounts.set(neighbor, (pathCounts.get(neighbor) ?? 0) + currentPathCount);
|
|
259
|
+
}
|
|
253
260
|
}
|
|
254
261
|
}
|
|
255
262
|
}
|
|
@@ -274,7 +281,7 @@ function getEdgeKey(source: NodeId, target: NodeId): string {
|
|
|
274
281
|
}
|
|
275
282
|
|
|
276
283
|
/**
|
|
277
|
-
* Calculate modularity for a given community structure
|
|
284
|
+
* Calculate modularity for a given community structure - optimized version
|
|
278
285
|
*/
|
|
279
286
|
function calculateModularity(
|
|
280
287
|
graph: Graph,
|
|
@@ -286,19 +293,27 @@ function calculateModularity(
|
|
|
286
293
|
}
|
|
287
294
|
|
|
288
295
|
let modularity = 0;
|
|
296
|
+
const degrees = new Map<NodeId, number>();
|
|
289
297
|
|
|
290
|
-
//
|
|
291
|
-
for (const
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
const edge = graph.getEdge(nodeI.id, nodeJ.id);
|
|
295
|
-
const edgeWeight = edge?.weight ?? 0;
|
|
296
|
-
|
|
297
|
-
const degreeI = getNodeDegree(graph, nodeI.id);
|
|
298
|
-
const degreeJ = getNodeDegree(graph, nodeJ.id);
|
|
298
|
+
// Pre-calculate all degrees
|
|
299
|
+
for (const node of graph.nodes()) {
|
|
300
|
+
degrees.set(node.id, getNodeDegree(graph, node.id));
|
|
301
|
+
}
|
|
299
302
|
|
|
300
|
-
|
|
303
|
+
// Only iterate over existing edges
|
|
304
|
+
for (const edge of graph.edges()) {
|
|
305
|
+
const communityI = communityMap.get(edge.source);
|
|
306
|
+
const communityJ = communityMap.get(edge.target);
|
|
307
|
+
|
|
308
|
+
if (communityI === communityJ) {
|
|
309
|
+
const edgeWeight = edge.weight ?? 1;
|
|
310
|
+
const degreeI = degrees.get(edge.source);
|
|
311
|
+
const degreeJ = degrees.get(edge.target);
|
|
312
|
+
if (degreeI === undefined || degreeJ === undefined) {
|
|
313
|
+
continue;
|
|
301
314
|
}
|
|
315
|
+
|
|
316
|
+
modularity += edgeWeight - ((degreeI * degreeJ) / (2 * totalEdgeWeight));
|
|
302
317
|
}
|
|
303
318
|
}
|
|
304
319
|
|
|
@@ -348,25 +363,3 @@ function getConnectedComponentsResult(graph: Graph): ComponentResult {
|
|
|
348
363
|
return {components, componentMap};
|
|
349
364
|
}
|
|
350
365
|
|
|
351
|
-
/**
|
|
352
|
-
* Create a deep clone of the graph
|
|
353
|
-
*/
|
|
354
|
-
function cloneGraph(graph: Graph): Graph {
|
|
355
|
-
const clone = new Graph({
|
|
356
|
-
directed: graph.isDirected,
|
|
357
|
-
allowSelfLoops: true,
|
|
358
|
-
allowParallelEdges: false,
|
|
359
|
-
});
|
|
360
|
-
|
|
361
|
-
// Add all nodes
|
|
362
|
-
for (const node of graph.nodes()) {
|
|
363
|
-
clone.addNode(node.id, node.data);
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
// Add all edges
|
|
367
|
-
for (const edge of graph.edges()) {
|
|
368
|
-
clone.addEdge(edge.source, edge.target, edge.weight, edge.data);
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
return clone;
|
|
372
|
-
}
|
|
@@ -7,7 +7,9 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
export {girvanNewman} from "./girvan-newman.js";
|
|
10
|
+
export type {LabelPropagationOptions, LabelPropagationResult} from "./label-propagation.js";
|
|
10
11
|
export {labelPropagation, labelPropagationAsync, labelPropagationSemiSupervised} from "./label-propagation.js";
|
|
12
|
+
export type {LeidenOptions, LeidenResult} from "./leiden.js";
|
|
11
13
|
export {leiden} from "./leiden.js";
|
|
12
14
|
export {louvain} from "./louvain.js";
|
|
13
15
|
|
|
@@ -9,6 +9,10 @@
|
|
|
9
9
|
* detect community structures in large-scale networks"
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
+
import type {Graph} from "../../core/graph.js";
|
|
13
|
+
import {graphToMap} from "../../utils/graph-converters.js";
|
|
14
|
+
import {SeededRandom, shuffle} from "../../utils/math-utilities.js";
|
|
15
|
+
|
|
12
16
|
export interface LabelPropagationOptions {
|
|
13
17
|
maxIterations?: number;
|
|
14
18
|
randomSeed?: number;
|
|
@@ -21,17 +25,9 @@ export interface LabelPropagationResult {
|
|
|
21
25
|
}
|
|
22
26
|
|
|
23
27
|
/**
|
|
24
|
-
* Label Propagation Algorithm
|
|
25
|
-
* Each node adopts the most frequent label among its neighbors
|
|
26
|
-
*
|
|
27
|
-
* @param graph - Undirected graph (can be weighted)
|
|
28
|
-
* @param options - Algorithm options
|
|
29
|
-
* @returns Community assignments
|
|
30
|
-
*
|
|
31
|
-
* Time Complexity: O(m) per iteration, typically O(km) for k iterations
|
|
32
|
-
* Space Complexity: O(n)
|
|
28
|
+
* Internal implementation of Label Propagation Algorithm
|
|
33
29
|
*/
|
|
34
|
-
|
|
30
|
+
function labelPropagationImpl(
|
|
35
31
|
graph: Map<string, Map<string, number>>,
|
|
36
32
|
options: LabelPropagationOptions = {},
|
|
37
33
|
): LabelPropagationResult {
|
|
@@ -49,11 +45,7 @@ export function labelPropagation(
|
|
|
49
45
|
}
|
|
50
46
|
|
|
51
47
|
// Initialize random number generator
|
|
52
|
-
|
|
53
|
-
const random = (): number => {
|
|
54
|
-
seed = ((seed * 1664525) + 1013904223) % 2147483647;
|
|
55
|
-
return seed / 2147483647;
|
|
56
|
-
};
|
|
48
|
+
const random = SeededRandom.createGenerator(randomSeed);
|
|
57
49
|
|
|
58
50
|
// Initialize labels - each node gets its own label
|
|
59
51
|
const labels = new Map<string, number>();
|
|
@@ -157,10 +149,10 @@ export function labelPropagation(
|
|
|
157
149
|
}
|
|
158
150
|
|
|
159
151
|
/**
|
|
160
|
-
* Asynchronous Label Propagation
|
|
152
|
+
* Internal implementation of Asynchronous Label Propagation
|
|
161
153
|
* Updates all nodes simultaneously (can lead to oscillations)
|
|
162
154
|
*/
|
|
163
|
-
|
|
155
|
+
function labelPropagationAsyncImpl(
|
|
164
156
|
graph: Map<string, Map<string, number>>,
|
|
165
157
|
options: LabelPropagationOptions = {},
|
|
166
158
|
): LabelPropagationResult {
|
|
@@ -267,10 +259,10 @@ export function labelPropagationAsync(
|
|
|
267
259
|
}
|
|
268
260
|
|
|
269
261
|
/**
|
|
270
|
-
* Semi-supervised Label Propagation
|
|
262
|
+
* Internal implementation of Semi-supervised Label Propagation
|
|
271
263
|
* Some nodes have fixed labels that don't change
|
|
272
264
|
*/
|
|
273
|
-
|
|
265
|
+
function labelPropagationSemiSupervisedImpl(
|
|
274
266
|
graph: Map<string, Map<string, number>>,
|
|
275
267
|
seedLabels: Map<string, number>,
|
|
276
268
|
options: LabelPropagationOptions = {},
|
|
@@ -281,11 +273,7 @@ export function labelPropagationSemiSupervised(
|
|
|
281
273
|
} = options;
|
|
282
274
|
|
|
283
275
|
// Initialize random number generator
|
|
284
|
-
|
|
285
|
-
const random = (): number => {
|
|
286
|
-
seed = ((seed * 1664525) + 1013904223) % 2147483647;
|
|
287
|
-
return seed / 2147483647;
|
|
288
|
-
};
|
|
276
|
+
const random = SeededRandom.createGenerator(randomSeed);
|
|
289
277
|
|
|
290
278
|
// Initialize labels
|
|
291
279
|
const labels = new Map<string, number>();
|
|
@@ -376,17 +364,58 @@ export function labelPropagationSemiSupervised(
|
|
|
376
364
|
}
|
|
377
365
|
|
|
378
366
|
/**
|
|
379
|
-
*
|
|
367
|
+
* Label Propagation Algorithm
|
|
368
|
+
* Each node adopts the most frequent label among its neighbors
|
|
369
|
+
*
|
|
370
|
+
* @param graph - Undirected graph (can be weighted)
|
|
371
|
+
* @param options - Algorithm options
|
|
372
|
+
* @returns Community assignments
|
|
373
|
+
*
|
|
374
|
+
* Time Complexity: O(m) per iteration, typically O(km) for k iterations
|
|
375
|
+
* Space Complexity: O(n)
|
|
380
376
|
*/
|
|
381
|
-
function
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
377
|
+
export function labelPropagation(
|
|
378
|
+
graph: Graph,
|
|
379
|
+
options: LabelPropagationOptions = {},
|
|
380
|
+
): LabelPropagationResult {
|
|
381
|
+
// Convert Graph to Map representation
|
|
382
|
+
const graphMap = graphToMap(graph);
|
|
383
|
+
return labelPropagationImpl(graphMap, options);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* Asynchronous Label Propagation
|
|
388
|
+
* Updates all nodes simultaneously (can lead to oscillations)
|
|
389
|
+
*
|
|
390
|
+
* @param graph - Undirected graph (can be weighted)
|
|
391
|
+
* @param options - Algorithm options
|
|
392
|
+
* @returns Community assignments
|
|
393
|
+
*/
|
|
394
|
+
export function labelPropagationAsync(
|
|
395
|
+
graph: Graph,
|
|
396
|
+
options: LabelPropagationOptions = {},
|
|
397
|
+
): LabelPropagationResult {
|
|
398
|
+
// Convert Graph to Map representation
|
|
399
|
+
const graphMap = graphToMap(graph);
|
|
400
|
+
return labelPropagationAsyncImpl(graphMap, options);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* Semi-supervised Label Propagation
|
|
405
|
+
* Some nodes have fixed labels that don't change
|
|
406
|
+
*
|
|
407
|
+
* @param graph - Undirected graph (can be weighted)
|
|
408
|
+
* @param seedLabels - Initial fixed labels for some nodes
|
|
409
|
+
* @param options - Algorithm options
|
|
410
|
+
* @returns Community assignments
|
|
411
|
+
*/
|
|
412
|
+
export function labelPropagationSemiSupervised(
|
|
413
|
+
graph: Graph,
|
|
414
|
+
seedLabels: Map<string, number>,
|
|
415
|
+
options: LabelPropagationOptions = {},
|
|
416
|
+
): LabelPropagationResult {
|
|
417
|
+
// Convert Graph to Map representation
|
|
418
|
+
const graphMap = graphToMap(graph);
|
|
419
|
+
return labelPropagationSemiSupervisedImpl(graphMap, seedLabels, options);
|
|
391
420
|
}
|
|
392
421
|
|
|
@@ -8,7 +8,9 @@
|
|
|
8
8
|
* "From Louvain to Leiden: guaranteeing well-connected communities"
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
import type {Graph} from "../../core/graph.js";
|
|
12
|
+
import {graphToMap} from "../../utils/graph-converters.js";
|
|
13
|
+
import {SeededRandom, shuffle} from "../../utils/math-utilities.js";
|
|
12
14
|
|
|
13
15
|
export interface LeidenOptions {
|
|
14
16
|
resolution?: number;
|
|
@@ -24,18 +26,11 @@ export interface LeidenResult {
|
|
|
24
26
|
}
|
|
25
27
|
|
|
26
28
|
/**
|
|
27
|
-
* Leiden algorithm for community detection
|
|
29
|
+
* Internal implementation of Leiden algorithm for community detection
|
|
28
30
|
* Improves upon Louvain by ensuring well-connected communities
|
|
29
|
-
*
|
|
30
|
-
* @param graph - Undirected weighted graph
|
|
31
|
-
* @param options - Algorithm options
|
|
32
|
-
* @returns Community assignments and modularity
|
|
33
|
-
*
|
|
34
|
-
* Time Complexity: O(m) per iteration, typically O(m log m) total
|
|
35
|
-
* Space Complexity: O(n + m)
|
|
36
31
|
*/
|
|
37
|
-
|
|
38
|
-
|
|
32
|
+
function leidenImpl(
|
|
33
|
+
inputGraph: Map<string, Map<string, number>>,
|
|
39
34
|
options: LeidenOptions = {},
|
|
40
35
|
): LeidenResult {
|
|
41
36
|
const {
|
|
@@ -46,7 +41,7 @@ export function leiden(
|
|
|
46
41
|
} = options;
|
|
47
42
|
|
|
48
43
|
// Handle empty graph
|
|
49
|
-
if (
|
|
44
|
+
if (inputGraph.size === 0) {
|
|
50
45
|
return {
|
|
51
46
|
communities: new Map(),
|
|
52
47
|
modularity: 0,
|
|
@@ -54,18 +49,17 @@ export function leiden(
|
|
|
54
49
|
};
|
|
55
50
|
}
|
|
56
51
|
|
|
52
|
+
// Use a mutable variable for the current graph state
|
|
53
|
+
let currentGraph = inputGraph;
|
|
54
|
+
|
|
57
55
|
// Initialize random number generator
|
|
58
|
-
|
|
59
|
-
const random = (): number => {
|
|
60
|
-
seed = ((seed * 1664525) + 1013904223) % 2147483647;
|
|
61
|
-
return seed / 2147483647;
|
|
62
|
-
};
|
|
56
|
+
const random = SeededRandom.createGenerator(randomSeed);
|
|
63
57
|
|
|
64
58
|
// Calculate total weight
|
|
65
59
|
let totalWeight = 0;
|
|
66
60
|
const degrees = new Map<string, number>();
|
|
67
61
|
|
|
68
|
-
for (const [node, neighbors] of
|
|
62
|
+
for (const [node, neighbors] of currentGraph) {
|
|
69
63
|
let degree = 0;
|
|
70
64
|
for (const weight of neighbors.values()) {
|
|
71
65
|
degree += weight;
|
|
@@ -77,10 +71,10 @@ export function leiden(
|
|
|
77
71
|
|
|
78
72
|
// Initialize communities - each node in its own community
|
|
79
73
|
const communities = new Map<string, number>();
|
|
80
|
-
const nodes = Array.from(
|
|
74
|
+
const nodes = Array.from(currentGraph.keys());
|
|
81
75
|
nodes.forEach((node, i) => communities.set(node, i));
|
|
82
76
|
|
|
83
|
-
let modularity = calculateModularity(
|
|
77
|
+
let modularity = calculateModularity(currentGraph, communities, degrees, totalWeight, resolution);
|
|
84
78
|
let bestModularity = modularity;
|
|
85
79
|
let bestCommunities = new Map(communities);
|
|
86
80
|
let iterations = 0;
|
|
@@ -100,7 +94,7 @@ export function leiden(
|
|
|
100
94
|
continue;
|
|
101
95
|
}
|
|
102
96
|
|
|
103
|
-
const neighborCommunities = getNeighborCommunities(node,
|
|
97
|
+
const neighborCommunities = getNeighborCommunities(node, currentGraph, communities);
|
|
104
98
|
|
|
105
99
|
let bestCommunity = currentCommunity;
|
|
106
100
|
let bestGain = 0;
|
|
@@ -112,7 +106,7 @@ export function leiden(
|
|
|
112
106
|
}
|
|
113
107
|
|
|
114
108
|
const gain = calculateModularityGain(
|
|
115
|
-
node, community,
|
|
109
|
+
node, community, currentGraph, communities, degrees, totalWeight, resolution,
|
|
116
110
|
);
|
|
117
111
|
|
|
118
112
|
if (gain > bestGain) {
|
|
@@ -131,11 +125,11 @@ export function leiden(
|
|
|
131
125
|
|
|
132
126
|
// Phase 2: Refinement (Leiden improvement over Louvain)
|
|
133
127
|
// Create aggregate network based on current partition
|
|
134
|
-
createAggregateNetwork(
|
|
128
|
+
createAggregateNetwork(currentGraph, communities);
|
|
135
129
|
|
|
136
130
|
// Refine partition using aggregate network
|
|
137
131
|
const subsetPartition = refinePartition(
|
|
138
|
-
|
|
132
|
+
currentGraph, communities,
|
|
139
133
|
);
|
|
140
134
|
|
|
141
135
|
// Apply refined partition
|
|
@@ -144,7 +138,7 @@ export function leiden(
|
|
|
144
138
|
}
|
|
145
139
|
|
|
146
140
|
// Recalculate modularity
|
|
147
|
-
modularity = calculateModularity(
|
|
141
|
+
modularity = calculateModularity(currentGraph, communities, degrees, totalWeight, resolution);
|
|
148
142
|
|
|
149
143
|
// Check if we've improved
|
|
150
144
|
if (modularity > bestModularity + threshold) {
|
|
@@ -158,17 +152,17 @@ export function leiden(
|
|
|
158
152
|
}
|
|
159
153
|
|
|
160
154
|
// Phase 3: Aggregate network (create super-nodes)
|
|
161
|
-
const aggregated = aggregateCommunities(
|
|
162
|
-
if (aggregated.graph.size ===
|
|
155
|
+
const aggregated = aggregateCommunities(currentGraph, communities);
|
|
156
|
+
if (aggregated.graph.size === currentGraph.size) {
|
|
163
157
|
break;
|
|
164
158
|
} // No aggregation possible
|
|
165
159
|
|
|
166
160
|
// Continue with aggregated network
|
|
167
|
-
const {graph:
|
|
168
|
-
|
|
161
|
+
const {graph: aggregatedGraph} = aggregated;
|
|
162
|
+
currentGraph = aggregatedGraph;
|
|
169
163
|
communities.clear();
|
|
170
164
|
let communityId = 0;
|
|
171
|
-
for (const node of
|
|
165
|
+
for (const node of currentGraph.keys()) {
|
|
172
166
|
communities.set(node, communityId++);
|
|
173
167
|
}
|
|
174
168
|
}
|
|
@@ -556,17 +550,22 @@ function aggregateCommunities(
|
|
|
556
550
|
}
|
|
557
551
|
|
|
558
552
|
/**
|
|
559
|
-
*
|
|
553
|
+
* Leiden algorithm for community detection
|
|
554
|
+
* Improves upon Louvain by ensuring well-connected communities
|
|
555
|
+
*
|
|
556
|
+
* @param graph - Undirected weighted graph - accepts Graph class or Map representation
|
|
557
|
+
* @param options - Algorithm options
|
|
558
|
+
* @returns Community assignments and modularity
|
|
559
|
+
*
|
|
560
|
+
* Time Complexity: O(m) per iteration, typically O(m log m) total
|
|
561
|
+
* Space Complexity: O(n + m)
|
|
560
562
|
*/
|
|
561
|
-
function
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
array[j] = temp;
|
|
569
|
-
}
|
|
570
|
-
}
|
|
563
|
+
export function leiden(
|
|
564
|
+
graph: Graph,
|
|
565
|
+
options: LeidenOptions = {},
|
|
566
|
+
): LeidenResult {
|
|
567
|
+
// Convert Graph to Map representation
|
|
568
|
+
const graphMap = graphToMap(graph);
|
|
569
|
+
return leidenImpl(graphMap, options);
|
|
571
570
|
}
|
|
572
571
|
|