@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.
Files changed (237) hide show
  1. package/README.md +820 -235
  2. package/dist/algorithms.d.ts +42 -2
  3. package/dist/algorithms.js +11099 -2
  4. package/dist/algorithms.js.map +1 -1
  5. package/dist/src/algorithms/centrality/betweenness.d.ts +4 -0
  6. package/dist/src/algorithms/centrality/betweenness.d.ts.map +1 -1
  7. package/dist/src/algorithms/centrality/betweenness.js +124 -170
  8. package/dist/src/algorithms/centrality/betweenness.js.map +1 -1
  9. package/dist/src/algorithms/centrality/closeness.d.ts +26 -1
  10. package/dist/src/algorithms/centrality/closeness.d.ts.map +1 -1
  11. package/dist/src/algorithms/centrality/closeness.js +56 -149
  12. package/dist/src/algorithms/centrality/closeness.js.map +1 -1
  13. package/dist/src/algorithms/centrality/degree.js +3 -3
  14. package/dist/src/algorithms/centrality/degree.js.map +1 -1
  15. package/dist/src/algorithms/centrality/delta-pagerank-simple.d.ts +39 -0
  16. package/dist/src/algorithms/centrality/delta-pagerank-simple.d.ts.map +1 -0
  17. package/dist/src/algorithms/centrality/delta-pagerank-simple.js +286 -0
  18. package/dist/src/algorithms/centrality/delta-pagerank-simple.js.map +1 -0
  19. package/dist/src/algorithms/centrality/delta-pagerank.d.ts +80 -0
  20. package/dist/src/algorithms/centrality/delta-pagerank.d.ts.map +1 -0
  21. package/dist/src/algorithms/centrality/delta-pagerank.js +294 -0
  22. package/dist/src/algorithms/centrality/delta-pagerank.js.map +1 -0
  23. package/dist/src/algorithms/centrality/index.d.ts +2 -0
  24. package/dist/src/algorithms/centrality/index.d.ts.map +1 -1
  25. package/dist/src/algorithms/centrality/index.js +1 -0
  26. package/dist/src/algorithms/centrality/index.js.map +1 -1
  27. package/dist/src/algorithms/centrality/pagerank.d.ts +17 -0
  28. package/dist/src/algorithms/centrality/pagerank.d.ts.map +1 -1
  29. package/dist/src/algorithms/centrality/pagerank.js +45 -0
  30. package/dist/src/algorithms/centrality/pagerank.js.map +1 -1
  31. package/dist/src/algorithms/community/girvan-newman.d.ts.map +1 -1
  32. package/dist/src/algorithms/community/girvan-newman.js +47 -54
  33. package/dist/src/algorithms/community/girvan-newman.js.map +1 -1
  34. package/dist/src/algorithms/community/index.d.ts +2 -0
  35. package/dist/src/algorithms/community/index.d.ts.map +1 -1
  36. package/dist/src/algorithms/community/index.js.map +1 -1
  37. package/dist/src/algorithms/community/label-propagation.d.ts +13 -3
  38. package/dist/src/algorithms/community/label-propagation.d.ts.map +1 -1
  39. package/dist/src/algorithms/community/label-propagation.js +50 -35
  40. package/dist/src/algorithms/community/label-propagation.js.map +1 -1
  41. package/dist/src/algorithms/community/leiden.d.ts +3 -2
  42. package/dist/src/algorithms/community/leiden.d.ts.map +1 -1
  43. package/dist/src/algorithms/community/leiden.js +34 -39
  44. package/dist/src/algorithms/community/leiden.js.map +1 -1
  45. package/dist/src/algorithms/community/louvain-optimized.d.ts +112 -0
  46. package/dist/src/algorithms/community/louvain-optimized.d.ts.map +1 -0
  47. package/dist/src/algorithms/community/louvain-optimized.js +303 -0
  48. package/dist/src/algorithms/community/louvain-optimized.js.map +1 -0
  49. package/dist/src/algorithms/community/louvain.d.ts.map +1 -1
  50. package/dist/src/algorithms/community/louvain.js +17 -76
  51. package/dist/src/algorithms/community/louvain.js.map +1 -1
  52. package/dist/src/algorithms/community/modularity-utils.d.ts +59 -0
  53. package/dist/src/algorithms/community/modularity-utils.d.ts.map +1 -0
  54. package/dist/src/algorithms/community/modularity-utils.js +112 -0
  55. package/dist/src/algorithms/community/modularity-utils.js.map +1 -0
  56. package/dist/src/algorithms/matching/bipartite.d.ts.map +1 -1
  57. package/dist/src/algorithms/matching/bipartite.js +8 -40
  58. package/dist/src/algorithms/matching/bipartite.js.map +1 -1
  59. package/dist/src/algorithms/shortest-path/bellman-ford.d.ts.map +1 -1
  60. package/dist/src/algorithms/shortest-path/bellman-ford.js +1 -12
  61. package/dist/src/algorithms/shortest-path/bellman-ford.js.map +1 -1
  62. package/dist/src/algorithms/shortest-path/bidirectional-dijkstra.d.ts +37 -0
  63. package/dist/src/algorithms/shortest-path/bidirectional-dijkstra.d.ts.map +1 -0
  64. package/dist/src/algorithms/shortest-path/bidirectional-dijkstra.js +193 -0
  65. package/dist/src/algorithms/shortest-path/bidirectional-dijkstra.js.map +1 -0
  66. package/dist/src/algorithms/shortest-path/dijkstra.d.ts +5 -2
  67. package/dist/src/algorithms/shortest-path/dijkstra.d.ts.map +1 -1
  68. package/dist/src/algorithms/shortest-path/dijkstra.js +15 -14
  69. package/dist/src/algorithms/shortest-path/dijkstra.js.map +1 -1
  70. package/dist/src/algorithms/traversal/bfs-unified.d.ts +29 -0
  71. package/dist/src/algorithms/traversal/bfs-unified.d.ts.map +1 -0
  72. package/dist/src/algorithms/traversal/bfs-unified.js +333 -0
  73. package/dist/src/algorithms/traversal/bfs-unified.js.map +1 -0
  74. package/dist/src/algorithms/traversal/bfs-variants.d.ts +54 -0
  75. package/dist/src/algorithms/traversal/bfs-variants.d.ts.map +1 -0
  76. package/dist/src/algorithms/traversal/bfs-variants.js +368 -0
  77. package/dist/src/algorithms/traversal/bfs-variants.js.map +1 -0
  78. package/dist/src/algorithms/traversal/bfs.d.ts +4 -18
  79. package/dist/src/algorithms/traversal/bfs.d.ts.map +1 -1
  80. package/dist/src/algorithms/traversal/bfs.js +5 -182
  81. package/dist/src/algorithms/traversal/bfs.js.map +1 -1
  82. package/dist/src/benchmark-all-algorithms.d.ts +23 -0
  83. package/dist/src/benchmark-all-algorithms.d.ts.map +1 -0
  84. package/dist/src/benchmark-all-algorithms.js +462 -0
  85. package/dist/src/benchmark-all-algorithms.js.map +1 -0
  86. package/dist/src/clustering/hierarchical.d.ts +16 -15
  87. package/dist/src/clustering/hierarchical.d.ts.map +1 -1
  88. package/dist/src/clustering/hierarchical.js +209 -223
  89. package/dist/src/clustering/hierarchical.js.map +1 -1
  90. package/dist/src/clustering/index.d.ts +4 -2
  91. package/dist/src/clustering/index.d.ts.map +1 -1
  92. package/dist/src/clustering/index.js +2 -2
  93. package/dist/src/clustering/index.js.map +1 -1
  94. package/dist/src/clustering/k-core.d.ts +16 -15
  95. package/dist/src/clustering/k-core.d.ts.map +1 -1
  96. package/dist/src/clustering/k-core.js +57 -21
  97. package/dist/src/clustering/k-core.js.map +1 -1
  98. package/dist/src/clustering/spectral.d.ts +8 -0
  99. package/dist/src/clustering/spectral.d.ts.map +1 -1
  100. package/dist/src/clustering/spectral.js +158 -35
  101. package/dist/src/clustering/spectral.js.map +1 -1
  102. package/dist/src/core/graph.js +4 -4
  103. package/dist/src/core/graph.js.map +1 -1
  104. package/dist/src/flow/ford-fulkerson.d.ts +17 -11
  105. package/dist/src/flow/ford-fulkerson.d.ts.map +1 -1
  106. package/dist/src/flow/ford-fulkerson.js +141 -217
  107. package/dist/src/flow/ford-fulkerson.js.map +1 -1
  108. package/dist/src/flow/min-cut.d.ts +6 -5
  109. package/dist/src/flow/min-cut.d.ts.map +1 -1
  110. package/dist/src/flow/min-cut.js +17 -8
  111. package/dist/src/flow/min-cut.js.map +1 -1
  112. package/dist/src/index.d.ts +2 -0
  113. package/dist/src/index.d.ts.map +1 -1
  114. package/dist/src/index.js +6 -0
  115. package/dist/src/index.js.map +1 -1
  116. package/dist/src/link-prediction/adamic-adar.d.ts.map +1 -1
  117. package/dist/src/link-prediction/adamic-adar.js +17 -26
  118. package/dist/src/link-prediction/adamic-adar.js.map +1 -1
  119. package/dist/src/link-prediction/common-neighbors.d.ts.map +1 -1
  120. package/dist/src/link-prediction/common-neighbors.js +7 -11
  121. package/dist/src/link-prediction/common-neighbors.js.map +1 -1
  122. package/dist/src/optimized/bit-packed.d.ts +143 -0
  123. package/dist/src/optimized/bit-packed.d.ts.map +1 -0
  124. package/dist/src/optimized/bit-packed.js +292 -0
  125. package/dist/src/optimized/bit-packed.js.map +1 -0
  126. package/dist/src/optimized/csr-graph.d.ts +86 -0
  127. package/dist/src/optimized/csr-graph.d.ts.map +1 -0
  128. package/dist/src/optimized/csr-graph.js +341 -0
  129. package/dist/src/optimized/csr-graph.js.map +1 -0
  130. package/dist/src/optimized/direction-optimized-bfs.d.ts +74 -0
  131. package/dist/src/optimized/direction-optimized-bfs.d.ts.map +1 -0
  132. package/dist/src/optimized/direction-optimized-bfs.js +223 -0
  133. package/dist/src/optimized/direction-optimized-bfs.js.map +1 -0
  134. package/dist/src/optimized/graph-adapter.d.ts +62 -0
  135. package/dist/src/optimized/graph-adapter.d.ts.map +1 -0
  136. package/dist/src/optimized/graph-adapter.js +146 -0
  137. package/dist/src/optimized/graph-adapter.js.map +1 -0
  138. package/dist/src/optimized/index.d.ts +14 -0
  139. package/dist/src/optimized/index.d.ts.map +1 -0
  140. package/dist/src/optimized/index.js +28 -0
  141. package/dist/src/optimized/index.js.map +1 -0
  142. package/dist/src/research/grsbm.d.ts +83 -0
  143. package/dist/src/research/grsbm.d.ts.map +1 -0
  144. package/dist/src/research/grsbm.js +403 -0
  145. package/dist/src/research/grsbm.js.map +1 -0
  146. package/dist/src/research/index.d.ts +12 -0
  147. package/dist/src/research/index.d.ts.map +1 -0
  148. package/dist/src/research/index.js +14 -0
  149. package/dist/src/research/index.js.map +1 -0
  150. package/dist/src/research/sync.d.ts +49 -0
  151. package/dist/src/research/sync.d.ts.map +1 -0
  152. package/dist/src/research/sync.js +316 -0
  153. package/dist/src/research/sync.js.map +1 -0
  154. package/dist/src/research/terahac.d.ts +65 -0
  155. package/dist/src/research/terahac.d.ts.map +1 -0
  156. package/dist/src/research/terahac.js +379 -0
  157. package/dist/src/research/terahac.js.map +1 -0
  158. package/dist/src/types/index.d.ts +8 -0
  159. package/dist/src/types/index.d.ts.map +1 -1
  160. package/dist/src/utils/algorithm-utilities.d.ts +2 -0
  161. package/dist/src/utils/algorithm-utilities.d.ts.map +1 -0
  162. package/dist/src/utils/algorithm-utilities.js +2 -0
  163. package/dist/src/utils/algorithm-utilities.js.map +1 -0
  164. package/dist/src/utils/graph-converters.d.ts +79 -0
  165. package/dist/src/utils/graph-converters.d.ts.map +1 -0
  166. package/dist/src/utils/graph-converters.js +196 -0
  167. package/dist/src/utils/graph-converters.js.map +1 -0
  168. package/dist/src/utils/graph-utilities.d.ts +62 -0
  169. package/dist/src/utils/graph-utilities.d.ts.map +1 -0
  170. package/dist/src/utils/graph-utilities.js +148 -0
  171. package/dist/src/utils/graph-utilities.js.map +1 -0
  172. package/dist/src/utils/index.d.ts +5 -0
  173. package/dist/src/utils/index.d.ts.map +1 -0
  174. package/dist/src/utils/index.js +5 -0
  175. package/dist/src/utils/index.js.map +1 -0
  176. package/dist/src/utils/math-utilities.d.ts +52 -0
  177. package/dist/src/utils/math-utilities.d.ts.map +1 -0
  178. package/dist/src/utils/math-utilities.js +123 -0
  179. package/dist/src/utils/math-utilities.js.map +1 -0
  180. package/dist/src/utils/matrix-utilities.d.ts +2 -0
  181. package/dist/src/utils/matrix-utilities.d.ts.map +1 -0
  182. package/dist/src/utils/matrix-utilities.js +2 -0
  183. package/dist/src/utils/matrix-utilities.js.map +1 -0
  184. package/dist/src/utils/optimization-helpers.d.ts +33 -0
  185. package/dist/src/utils/optimization-helpers.d.ts.map +1 -0
  186. package/dist/src/utils/optimization-helpers.js +47 -0
  187. package/dist/src/utils/optimization-helpers.js.map +1 -0
  188. package/package.json +28 -4
  189. package/src/algorithms/centrality/betweenness.ts +167 -199
  190. package/src/algorithms/centrality/closeness.ts +73 -191
  191. package/src/algorithms/centrality/degree.ts +3 -3
  192. package/src/algorithms/centrality/delta-pagerank-simple.ts +366 -0
  193. package/src/algorithms/centrality/delta-pagerank.ts +435 -0
  194. package/src/algorithms/centrality/index.ts +2 -0
  195. package/src/algorithms/centrality/pagerank.ts +66 -0
  196. package/src/algorithms/community/girvan-newman.ts +52 -59
  197. package/src/algorithms/community/index.ts +2 -0
  198. package/src/algorithms/community/label-propagation.ts +64 -35
  199. package/src/algorithms/community/leiden.ts +40 -41
  200. package/src/algorithms/community/louvain-optimized.ts +445 -0
  201. package/src/algorithms/community/louvain.ts +24 -104
  202. package/src/algorithms/community/modularity-utils.ts +146 -0
  203. package/src/algorithms/matching/bipartite.ts +8 -44
  204. package/src/algorithms/shortest-path/bellman-ford.ts +1 -14
  205. package/src/algorithms/shortest-path/bidirectional-dijkstra.ts +248 -0
  206. package/src/algorithms/shortest-path/dijkstra.ts +17 -15
  207. package/src/algorithms/traversal/bfs-unified.ts +433 -0
  208. package/src/algorithms/traversal/bfs-variants.ts +496 -0
  209. package/src/algorithms/traversal/bfs.ts +10 -228
  210. package/src/benchmark-all-algorithms.ts +548 -0
  211. package/src/clustering/hierarchical.ts +241 -251
  212. package/src/clustering/index.ts +4 -2
  213. package/src/clustering/k-core.ts +87 -39
  214. package/src/clustering/spectral.ts +185 -34
  215. package/src/core/graph.ts +4 -4
  216. package/src/flow/ford-fulkerson.ts +186 -254
  217. package/src/flow/min-cut.ts +21 -11
  218. package/src/index.ts +9 -0
  219. package/src/link-prediction/adamic-adar.ts +17 -30
  220. package/src/link-prediction/common-neighbors.ts +7 -16
  221. package/src/optimized/bit-packed.ts +340 -0
  222. package/src/optimized/csr-graph.ts +455 -0
  223. package/src/optimized/direction-optimized-bfs.ts +291 -0
  224. package/src/optimized/graph-adapter.ts +200 -0
  225. package/src/optimized/index.ts +36 -0
  226. package/src/research/grsbm.ts +580 -0
  227. package/src/research/index.ts +17 -0
  228. package/src/research/sync.ts +440 -0
  229. package/src/research/terahac.ts +514 -0
  230. package/src/types/index.ts +8 -0
  231. package/src/utils/algorithm-utilities.ts +2 -0
  232. package/src/utils/graph-converters.ts +241 -0
  233. package/src/utils/graph-utilities.ts +199 -0
  234. package/src/utils/index.ts +4 -0
  235. package/src/utils/math-utilities.ts +140 -0
  236. package/src/utils/matrix-utilities.ts +2 -0
  237. 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 = cloneGraph(graph);
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
- // Initialize
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
- while (queue.length > 0) {
215
- const current = queue.shift();
216
- if (current === undefined) {
217
- break;
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
- const edge = graph.getEdge(current, neighbor);
227
- const edgeWeight = edge?.weight ?? 1;
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 (Math.abs(newDistance - neighborDistance) < 1e-10) {
251
+ } else if (newDistance === neighborDistance) {
245
252
  // Found alternative shortest path
246
253
  const neighborPredecessors = predecessors.get(neighbor);
247
- const neighborPathCount = pathCounts.get(neighbor);
248
- const currentPathCount = pathCounts.get(current);
249
-
250
- if (neighborPredecessors && neighborPathCount !== undefined && currentPathCount !== undefined) {
254
+ if (neighborPredecessors) {
251
255
  neighborPredecessors.push(current);
252
- pathCounts.set(neighbor, neighborPathCount + currentPathCount);
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
- // Calculate modularity: Q = (1/2m) * Σ[A_ij - (k_i * k_j)/(2m)] * δ(c_i, c_j)
291
- for (const nodeI of graph.nodes()) {
292
- for (const nodeJ of graph.nodes()) {
293
- if (communityMap.get(nodeI.id) === communityMap.get(nodeJ.id)) {
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
- modularity += edgeWeight - ((degreeI * degreeJ) / (2 * totalEdgeWeight));
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
- export function labelPropagation(
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
- let seed = randomSeed;
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
- export function labelPropagationAsync(
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
- export function labelPropagationSemiSupervised(
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
- let seed = randomSeed;
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
- * Fisher-Yates shuffle
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 shuffle(array: unknown[], random: () => number): void {
382
- for (let i = array.length - 1; i > 0; i--) {
383
- const j = Math.floor(random() * (i + 1));
384
- const temp = array[i];
385
- const swapItem = array[j];
386
- if (temp !== undefined && swapItem !== undefined) {
387
- array[i] = swapItem;
388
- array[j] = temp;
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
- // Louvain import removed - not used in this implementation
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
- export function leiden(
38
- graph: Map<string, Map<string, number>>,
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 (graph.size === 0) {
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
- let seed = randomSeed;
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 graph) {
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(graph.keys());
74
+ const nodes = Array.from(currentGraph.keys());
81
75
  nodes.forEach((node, i) => communities.set(node, i));
82
76
 
83
- let modularity = calculateModularity(graph, communities, degrees, totalWeight, resolution);
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, graph, communities);
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, graph, communities, degrees, totalWeight, resolution,
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(graph, communities);
128
+ createAggregateNetwork(currentGraph, communities);
135
129
 
136
130
  // Refine partition using aggregate network
137
131
  const subsetPartition = refinePartition(
138
- graph, communities,
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(graph, communities, degrees, totalWeight, resolution);
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(graph, communities);
162
- if (aggregated.graph.size === 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: newGraph} = aggregated;
168
- graph = newGraph;
161
+ const {graph: aggregatedGraph} = aggregated;
162
+ currentGraph = aggregatedGraph;
169
163
  communities.clear();
170
164
  let communityId = 0;
171
- for (const node of graph.keys()) {
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
- * Fisher-Yates shuffle
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 shuffle(array: unknown[], random: () => number): void {
562
- for (let i = array.length - 1; i > 0; i--) {
563
- const j = Math.floor(random() * (i + 1));
564
- const temp = array[i];
565
- const swapItem = array[j];
566
- if (temp !== undefined && swapItem !== undefined) {
567
- array[i] = swapItem;
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