@graphty/algorithms 1.0.1 → 1.2.0

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 (294) hide show
  1. package/README.md +1259 -61
  2. package/dist/algorithms.d.ts +2 -0
  3. package/dist/algorithms.d.ts.map +1 -0
  4. package/dist/algorithms.js +2 -0
  5. package/dist/algorithms.js.map +1 -0
  6. package/dist/src/algorithms/centrality/betweenness.d.ts.map +1 -1
  7. package/dist/src/algorithms/centrality/betweenness.js +22 -7
  8. package/dist/src/algorithms/centrality/betweenness.js.map +1 -1
  9. package/dist/src/algorithms/centrality/closeness.js +7 -7
  10. package/dist/src/algorithms/centrality/closeness.js.map +1 -1
  11. package/dist/src/algorithms/centrality/degree.js +1 -1
  12. package/dist/src/algorithms/centrality/degree.js.map +1 -1
  13. package/dist/src/algorithms/centrality/eigenvector.d.ts +27 -0
  14. package/dist/src/algorithms/centrality/eigenvector.d.ts.map +1 -0
  15. package/dist/src/algorithms/centrality/eigenvector.js +113 -0
  16. package/dist/src/algorithms/centrality/eigenvector.js.map +1 -0
  17. package/dist/src/algorithms/centrality/hits.d.ts +35 -0
  18. package/dist/src/algorithms/centrality/hits.d.ts.map +1 -0
  19. package/dist/src/algorithms/centrality/hits.js +146 -0
  20. package/dist/src/algorithms/centrality/hits.js.map +1 -0
  21. package/dist/src/algorithms/centrality/index.d.ts +6 -0
  22. package/dist/src/algorithms/centrality/index.d.ts.map +1 -1
  23. package/dist/src/algorithms/centrality/index.js +3 -0
  24. package/dist/src/algorithms/centrality/index.js.map +1 -1
  25. package/dist/src/algorithms/centrality/katz.d.ts +30 -0
  26. package/dist/src/algorithms/centrality/katz.d.ts.map +1 -0
  27. package/dist/src/algorithms/centrality/katz.js +85 -0
  28. package/dist/src/algorithms/centrality/katz.js.map +1 -0
  29. package/dist/src/algorithms/centrality/pagerank.js +3 -3
  30. package/dist/src/algorithms/centrality/pagerank.js.map +1 -1
  31. package/dist/src/algorithms/community/girvan-newman.d.ts +23 -0
  32. package/dist/src/algorithms/community/girvan-newman.d.ts.map +1 -0
  33. package/dist/src/algorithms/community/girvan-newman.js +297 -0
  34. package/dist/src/algorithms/community/girvan-newman.js.map +1 -0
  35. package/dist/src/algorithms/community/index.d.ts +12 -0
  36. package/dist/src/algorithms/community/index.d.ts.map +1 -0
  37. package/dist/src/algorithms/community/index.js +12 -0
  38. package/dist/src/algorithms/community/index.js.map +1 -0
  39. package/dist/src/algorithms/community/label-propagation.d.ts +42 -0
  40. package/dist/src/algorithms/community/label-propagation.d.ts.map +1 -0
  41. package/dist/src/algorithms/community/label-propagation.js +307 -0
  42. package/dist/src/algorithms/community/label-propagation.js.map +1 -0
  43. package/dist/src/algorithms/community/leiden.d.ts +33 -0
  44. package/dist/src/algorithms/community/leiden.d.ts.map +1 -0
  45. package/dist/src/algorithms/community/leiden.js +434 -0
  46. package/dist/src/algorithms/community/leiden.js.map +1 -0
  47. package/dist/src/algorithms/community/louvain.d.ts +19 -0
  48. package/dist/src/algorithms/community/louvain.d.ts.map +1 -0
  49. package/dist/src/algorithms/community/louvain.js +219 -0
  50. package/dist/src/algorithms/community/louvain.js.map +1 -0
  51. package/dist/src/algorithms/components/connected.js +7 -7
  52. package/dist/src/algorithms/components/connected.js.map +1 -1
  53. package/dist/src/algorithms/index.d.ts +7 -0
  54. package/dist/src/algorithms/index.d.ts.map +1 -1
  55. package/dist/src/algorithms/index.js +14 -0
  56. package/dist/src/algorithms/index.js.map +1 -1
  57. package/dist/src/algorithms/matching/bipartite.d.ts +37 -0
  58. package/dist/src/algorithms/matching/bipartite.d.ts.map +1 -0
  59. package/dist/src/algorithms/matching/bipartite.js +127 -0
  60. package/dist/src/algorithms/matching/bipartite.js.map +1 -0
  61. package/dist/src/algorithms/matching/index.d.ts +8 -0
  62. package/dist/src/algorithms/matching/index.d.ts.map +1 -0
  63. package/dist/src/algorithms/matching/index.js +6 -0
  64. package/dist/src/algorithms/matching/index.js.map +1 -0
  65. package/dist/src/algorithms/matching/isomorphism.d.ts +32 -0
  66. package/dist/src/algorithms/matching/isomorphism.d.ts.map +1 -0
  67. package/dist/src/algorithms/matching/isomorphism.js +291 -0
  68. package/dist/src/algorithms/matching/isomorphism.js.map +1 -0
  69. package/dist/src/algorithms/mst/index.d.ts +4 -0
  70. package/dist/src/algorithms/mst/index.d.ts.map +1 -0
  71. package/dist/src/algorithms/mst/index.js +3 -0
  72. package/dist/src/algorithms/mst/index.js.map +1 -0
  73. package/dist/src/algorithms/mst/kruskal.d.ts +9 -0
  74. package/dist/src/algorithms/mst/kruskal.d.ts.map +1 -0
  75. package/dist/src/algorithms/mst/kruskal.js +43 -0
  76. package/dist/src/algorithms/mst/kruskal.js.map +1 -0
  77. package/dist/src/algorithms/mst/prim.d.ts +5 -0
  78. package/dist/src/algorithms/mst/prim.d.ts.map +1 -0
  79. package/dist/src/algorithms/mst/prim.js +63 -0
  80. package/dist/src/algorithms/mst/prim.js.map +1 -0
  81. package/dist/src/algorithms/shortest-path/bellman-ford.js +2 -2
  82. package/dist/src/algorithms/shortest-path/bellman-ford.js.map +1 -1
  83. package/dist/src/algorithms/shortest-path/dijkstra.js +5 -5
  84. package/dist/src/algorithms/shortest-path/dijkstra.js.map +1 -1
  85. package/dist/src/algorithms/shortest-path/floyd-warshall.d.ts +14 -0
  86. package/dist/src/algorithms/shortest-path/floyd-warshall.d.ts.map +1 -0
  87. package/dist/src/algorithms/shortest-path/floyd-warshall.js +129 -0
  88. package/dist/src/algorithms/shortest-path/floyd-warshall.js.map +1 -0
  89. package/dist/src/algorithms/shortest-path/index.d.ts +2 -0
  90. package/dist/src/algorithms/shortest-path/index.d.ts.map +1 -1
  91. package/dist/src/algorithms/shortest-path/index.js +1 -0
  92. package/dist/src/algorithms/shortest-path/index.js.map +1 -1
  93. package/dist/src/algorithms/traversal/bfs.js +2 -2
  94. package/dist/src/algorithms/traversal/bfs.js.map +1 -1
  95. package/dist/src/algorithms/traversal/dfs.js +11 -11
  96. package/dist/src/algorithms/traversal/dfs.js.map +1 -1
  97. package/dist/src/clustering/hierarchical.d.ts +48 -0
  98. package/dist/src/clustering/hierarchical.d.ts.map +1 -0
  99. package/dist/src/clustering/hierarchical.js +439 -0
  100. package/dist/src/clustering/hierarchical.js.map +1 -0
  101. package/dist/src/clustering/index.d.ts +7 -0
  102. package/dist/src/clustering/index.d.ts.map +1 -0
  103. package/dist/src/clustering/index.js +5 -0
  104. package/dist/src/clustering/index.js.map +1 -0
  105. package/dist/src/clustering/k-core.d.ts +63 -0
  106. package/dist/src/clustering/k-core.d.ts.map +1 -0
  107. package/dist/src/clustering/k-core.js +377 -0
  108. package/dist/src/clustering/k-core.js.map +1 -0
  109. package/dist/src/clustering/mcl.d.ts +36 -0
  110. package/dist/src/clustering/mcl.d.ts.map +1 -0
  111. package/dist/src/clustering/mcl.js +343 -0
  112. package/dist/src/clustering/mcl.js.map +1 -0
  113. package/dist/src/clustering/spectral.d.ts +28 -0
  114. package/dist/src/clustering/spectral.d.ts.map +1 -0
  115. package/dist/src/clustering/spectral.js +551 -0
  116. package/dist/src/clustering/spectral.js.map +1 -0
  117. package/dist/src/core/graph.d.ts +9 -0
  118. package/dist/src/core/graph.d.ts.map +1 -1
  119. package/dist/src/core/graph.js +31 -11
  120. package/dist/src/core/graph.js.map +1 -1
  121. package/dist/src/data-structures/priority-queue.js +0 -2
  122. package/dist/src/data-structures/priority-queue.js.map +1 -1
  123. package/dist/src/data-structures/union-find.js +2 -5
  124. package/dist/src/data-structures/union-find.js.map +1 -1
  125. package/dist/src/flow/ford-fulkerson.d.ts +53 -0
  126. package/dist/src/flow/ford-fulkerson.d.ts.map +1 -0
  127. package/dist/src/flow/ford-fulkerson.js +355 -0
  128. package/dist/src/flow/ford-fulkerson.js.map +1 -0
  129. package/dist/src/flow/index.d.ts +3 -0
  130. package/dist/src/flow/index.d.ts.map +1 -0
  131. package/dist/src/flow/index.js +3 -0
  132. package/dist/src/flow/index.js.map +1 -0
  133. package/dist/src/flow/min-cut.d.ts +49 -0
  134. package/dist/src/flow/min-cut.d.ts.map +1 -0
  135. package/dist/src/flow/min-cut.js +383 -0
  136. package/dist/src/flow/min-cut.js.map +1 -0
  137. package/dist/src/index.d.ts +2 -1
  138. package/dist/src/index.d.ts.map +1 -1
  139. package/dist/src/index.js +2 -0
  140. package/dist/src/index.js.map +1 -1
  141. package/dist/src/link-prediction/adamic-adar.d.ts +56 -0
  142. package/dist/src/link-prediction/adamic-adar.d.ts.map +1 -0
  143. package/dist/src/link-prediction/adamic-adar.js +256 -0
  144. package/dist/src/link-prediction/adamic-adar.js.map +1 -0
  145. package/dist/src/link-prediction/common-neighbors.d.ts +50 -0
  146. package/dist/src/link-prediction/common-neighbors.d.ts.map +1 -0
  147. package/dist/src/link-prediction/common-neighbors.js +155 -0
  148. package/dist/src/link-prediction/common-neighbors.js.map +1 -0
  149. package/dist/src/link-prediction/index.d.ts +7 -0
  150. package/dist/src/link-prediction/index.d.ts.map +1 -0
  151. package/dist/src/link-prediction/index.js +6 -0
  152. package/dist/src/link-prediction/index.js.map +1 -0
  153. package/dist/src/pathfinding/astar.d.ts +50 -0
  154. package/dist/src/pathfinding/astar.d.ts.map +1 -0
  155. package/dist/src/pathfinding/astar.js +180 -0
  156. package/dist/src/pathfinding/astar.js.map +1 -0
  157. package/dist/src/pathfinding/index.d.ts +3 -0
  158. package/dist/src/pathfinding/index.d.ts.map +1 -0
  159. package/dist/src/pathfinding/index.js +3 -0
  160. package/dist/src/pathfinding/index.js.map +1 -0
  161. package/dist/src/pathfinding/utils.d.ts +18 -0
  162. package/dist/src/pathfinding/utils.d.ts.map +1 -0
  163. package/dist/src/pathfinding/utils.js +68 -0
  164. package/dist/src/pathfinding/utils.js.map +1 -0
  165. package/dist/src/research/grsbm.d.ts +83 -0
  166. package/dist/src/research/grsbm.d.ts.map +1 -0
  167. package/dist/src/research/grsbm.js +411 -0
  168. package/dist/src/research/grsbm.js.map +1 -0
  169. package/dist/src/research/index.d.ts +12 -0
  170. package/dist/src/research/index.d.ts.map +1 -0
  171. package/dist/src/research/index.js +14 -0
  172. package/dist/src/research/index.js.map +1 -0
  173. package/dist/src/research/sync.d.ts +49 -0
  174. package/dist/src/research/sync.d.ts.map +1 -0
  175. package/dist/src/research/sync.js +342 -0
  176. package/dist/src/research/sync.js.map +1 -0
  177. package/dist/src/research/terahac.d.ts +63 -0
  178. package/dist/src/research/terahac.d.ts.map +1 -0
  179. package/dist/src/research/terahac.js +375 -0
  180. package/dist/src/research/terahac.js.map +1 -0
  181. package/dist/src/types/index.d.ts +9 -0
  182. package/dist/src/types/index.d.ts.map +1 -1
  183. package/dist/src/utils/graphNode.d.ts +8 -0
  184. package/dist/src/utils/graphNode.d.ts.map +1 -0
  185. package/dist/src/utils/graphNode.js +2 -0
  186. package/dist/src/utils/graphNode.js.map +1 -0
  187. package/dist/src/utils/priorityQueue.d.ts +21 -0
  188. package/dist/src/utils/priorityQueue.d.ts.map +1 -0
  189. package/dist/src/utils/priorityQueue.js +101 -0
  190. package/dist/src/utils/priorityQueue.js.map +1 -0
  191. package/package.json +13 -2
  192. package/src/algorithms/centrality/betweenness.ts +25 -7
  193. package/src/algorithms/centrality/closeness.ts +7 -7
  194. package/src/algorithms/centrality/degree.ts +1 -1
  195. package/src/algorithms/centrality/eigenvector.ts +162 -0
  196. package/src/algorithms/centrality/hits.ts +210 -0
  197. package/src/algorithms/centrality/index.ts +6 -0
  198. package/src/algorithms/centrality/katz.ts +139 -0
  199. package/src/algorithms/centrality/pagerank.ts +3 -3
  200. package/src/algorithms/community/girvan-newman.ts +372 -0
  201. package/src/algorithms/community/index.ts +13 -0
  202. package/src/algorithms/community/label-propagation.ts +392 -0
  203. package/src/algorithms/community/leiden.ts +572 -0
  204. package/src/algorithms/community/louvain.ts +298 -0
  205. package/src/algorithms/components/connected.ts +7 -7
  206. package/src/algorithms/index.ts +21 -0
  207. package/src/algorithms/matching/bipartite.ts +179 -0
  208. package/src/algorithms/matching/index.ts +8 -0
  209. package/src/algorithms/matching/isomorphism.ts +392 -0
  210. package/src/algorithms/mst/index.ts +4 -0
  211. package/src/algorithms/mst/kruskal.ts +61 -0
  212. package/src/algorithms/mst/prim.ts +80 -0
  213. package/src/algorithms/shortest-path/bellman-ford.ts +2 -2
  214. package/src/algorithms/shortest-path/dijkstra.ts +5 -5
  215. package/src/algorithms/shortest-path/floyd-warshall.ts +170 -0
  216. package/src/algorithms/shortest-path/index.ts +2 -0
  217. package/src/algorithms/traversal/bfs.ts +2 -2
  218. package/src/algorithms/traversal/dfs.ts +11 -11
  219. package/src/clustering/hierarchical.ts +555 -0
  220. package/src/clustering/index.ts +6 -0
  221. package/src/clustering/k-core.ts +460 -0
  222. package/src/clustering/mcl.ts +454 -0
  223. package/src/clustering/spectral.ts +672 -0
  224. package/src/core/graph.ts +34 -6
  225. package/src/data-structures/union-find.ts +2 -2
  226. package/src/flow/ford-fulkerson.ts +459 -0
  227. package/src/flow/index.ts +2 -0
  228. package/src/flow/min-cut.ts +482 -0
  229. package/src/index.ts +5 -0
  230. package/src/link-prediction/adamic-adar.ts +353 -0
  231. package/src/link-prediction/common-neighbors.ts +252 -0
  232. package/src/link-prediction/index.ts +20 -0
  233. package/src/pathfinding/astar.ts +229 -0
  234. package/src/pathfinding/index.ts +2 -0
  235. package/src/pathfinding/utils.ts +87 -0
  236. package/src/research/grsbm.ts +589 -0
  237. package/src/research/index.ts +17 -0
  238. package/src/research/sync.ts +469 -0
  239. package/src/research/terahac.ts +506 -0
  240. package/src/types/index.ts +11 -0
  241. package/src/utils/graphNode.ts +7 -0
  242. package/src/utils/priorityQueue.ts +120 -0
  243. package/dist/test/browser/basic.test.d.ts +0 -2
  244. package/dist/test/browser/basic.test.d.ts.map +0 -1
  245. package/dist/test/browser/basic.test.js +0 -70
  246. package/dist/test/browser/basic.test.js.map +0 -1
  247. package/dist/test/unit/bellman-ford.test.d.ts +0 -2
  248. package/dist/test/unit/bellman-ford.test.d.ts.map +0 -1
  249. package/dist/test/unit/bellman-ford.test.js +0 -231
  250. package/dist/test/unit/bellman-ford.test.js.map +0 -1
  251. package/dist/test/unit/betweenness-centrality.test.d.ts +0 -2
  252. package/dist/test/unit/betweenness-centrality.test.d.ts.map +0 -1
  253. package/dist/test/unit/betweenness-centrality.test.js +0 -311
  254. package/dist/test/unit/betweenness-centrality.test.js.map +0 -1
  255. package/dist/test/unit/bfs.test.d.ts +0 -2
  256. package/dist/test/unit/bfs.test.d.ts.map +0 -1
  257. package/dist/test/unit/bfs.test.js +0 -277
  258. package/dist/test/unit/bfs.test.js.map +0 -1
  259. package/dist/test/unit/closeness.test.d.ts +0 -2
  260. package/dist/test/unit/closeness.test.d.ts.map +0 -1
  261. package/dist/test/unit/closeness.test.js +0 -266
  262. package/dist/test/unit/closeness.test.js.map +0 -1
  263. package/dist/test/unit/connected-components.test.d.ts +0 -2
  264. package/dist/test/unit/connected-components.test.d.ts.map +0 -1
  265. package/dist/test/unit/connected-components.test.js +0 -355
  266. package/dist/test/unit/connected-components.test.js.map +0 -1
  267. package/dist/test/unit/degree-centrality.test.d.ts +0 -2
  268. package/dist/test/unit/degree-centrality.test.d.ts.map +0 -1
  269. package/dist/test/unit/degree-centrality.test.js +0 -260
  270. package/dist/test/unit/degree-centrality.test.js.map +0 -1
  271. package/dist/test/unit/dfs.test.d.ts +0 -2
  272. package/dist/test/unit/dfs.test.d.ts.map +0 -1
  273. package/dist/test/unit/dfs.test.js +0 -350
  274. package/dist/test/unit/dfs.test.js.map +0 -1
  275. package/dist/test/unit/dijkstra.test.d.ts +0 -2
  276. package/dist/test/unit/dijkstra.test.d.ts.map +0 -1
  277. package/dist/test/unit/dijkstra.test.js +0 -311
  278. package/dist/test/unit/dijkstra.test.js.map +0 -1
  279. package/dist/test/unit/graph.test.d.ts +0 -2
  280. package/dist/test/unit/graph.test.d.ts.map +0 -1
  281. package/dist/test/unit/graph.test.js +0 -175
  282. package/dist/test/unit/graph.test.js.map +0 -1
  283. package/dist/test/unit/pagerank.test.d.ts +0 -2
  284. package/dist/test/unit/pagerank.test.d.ts.map +0 -1
  285. package/dist/test/unit/pagerank.test.js +0 -281
  286. package/dist/test/unit/pagerank.test.js.map +0 -1
  287. package/dist/test/unit/priority-queue.test.d.ts +0 -2
  288. package/dist/test/unit/priority-queue.test.d.ts.map +0 -1
  289. package/dist/test/unit/priority-queue.test.js +0 -189
  290. package/dist/test/unit/priority-queue.test.js.map +0 -1
  291. package/dist/test/unit/union-find.test.d.ts +0 -2
  292. package/dist/test/unit/union-find.test.d.ts.map +0 -1
  293. package/dist/test/unit/union-find.test.js +0 -245
  294. package/dist/test/unit/union-find.test.js.map +0 -1
@@ -0,0 +1,392 @@
1
+ /**
2
+ * Label Propagation Algorithm for Community Detection
3
+ *
4
+ * A fast, near-linear time algorithm that detects communities by
5
+ * propagating labels through the network. Each node adopts the label
6
+ * that most of its neighbors have.
7
+ *
8
+ * Reference: Raghavan et al. (2007) "Near linear time algorithm to
9
+ * detect community structures in large-scale networks"
10
+ */
11
+
12
+ export interface LabelPropagationOptions {
13
+ maxIterations?: number;
14
+ randomSeed?: number;
15
+ }
16
+
17
+ export interface LabelPropagationResult {
18
+ communities: Map<string, number>;
19
+ iterations: number;
20
+ converged: boolean;
21
+ }
22
+
23
+ /**
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)
33
+ */
34
+ export function labelPropagation(
35
+ graph: Map<string, Map<string, number>>,
36
+ options: LabelPropagationOptions = {},
37
+ ): LabelPropagationResult {
38
+ const {
39
+ maxIterations = 100,
40
+ randomSeed = 42,
41
+ } = options;
42
+
43
+ if (graph.size === 0) {
44
+ return {
45
+ communities: new Map(),
46
+ iterations: 0,
47
+ converged: true,
48
+ };
49
+ }
50
+
51
+ // Initialize random number generator
52
+ let seed = randomSeed;
53
+ const random = (): number => {
54
+ seed = ((seed * 1664525) + 1013904223) % 2147483647;
55
+ return seed / 2147483647;
56
+ };
57
+
58
+ // Initialize labels - each node gets its own label
59
+ const labels = new Map<string, number>();
60
+ const nodes = Array.from(graph.keys());
61
+ nodes.forEach((node, i) => labels.set(node, i));
62
+
63
+ let iterations = 0;
64
+ let converged = false;
65
+
66
+ // Main loop
67
+ while (iterations < maxIterations && !converged) {
68
+ iterations++;
69
+ converged = true;
70
+
71
+ // Create random order for node updates
72
+ const nodeOrder = [... nodes];
73
+ shuffle(nodeOrder, random);
74
+
75
+ // Update each node's label
76
+ for (const node of nodeOrder) {
77
+ const neighbors = graph.get(node);
78
+ if (!neighbors || neighbors.size === 0) {
79
+ continue;
80
+ }
81
+
82
+ // Count label frequencies among neighbors
83
+ const labelCounts = new Map<number, number>();
84
+ let maxCount = 0;
85
+ const candidateLabels: number[] = [];
86
+
87
+ for (const [neighbor, weight] of neighbors) {
88
+ const neighborLabel = labels.get(neighbor);
89
+ if (neighborLabel === undefined) {
90
+ continue;
91
+ }
92
+
93
+ const count = (labelCounts.get(neighborLabel) ?? 0) + weight;
94
+ labelCounts.set(neighborLabel, count);
95
+
96
+ if (count > maxCount) {
97
+ maxCount = count;
98
+ candidateLabels.length = 0;
99
+ candidateLabels.push(neighborLabel);
100
+ } else if (count === maxCount) {
101
+ candidateLabels.push(neighborLabel);
102
+ }
103
+ }
104
+
105
+ // Choose label (break ties randomly)
106
+ const currentLabel = labels.get(node);
107
+ if (currentLabel === undefined) {
108
+ continue;
109
+ }
110
+
111
+ let newLabel = currentLabel;
112
+
113
+ if (candidateLabels.length > 0) {
114
+ // Include current label if it has max count
115
+ if (labelCounts.get(currentLabel) === maxCount) {
116
+ candidateLabels.push(currentLabel);
117
+ }
118
+
119
+ // Random selection from candidates
120
+ const index = Math.floor(random() * candidateLabels.length);
121
+ const selectedLabel = candidateLabels[index];
122
+ if (selectedLabel !== undefined) {
123
+ newLabel = selectedLabel;
124
+ }
125
+ }
126
+
127
+ // Update label if changed
128
+ if (newLabel !== currentLabel) {
129
+ labels.set(node, newLabel);
130
+ converged = false;
131
+ }
132
+ }
133
+ }
134
+
135
+ // Renumber communities consecutively
136
+ const uniqueLabels = new Set(labels.values());
137
+ const labelMap = new Map<number, number>();
138
+ let communityId = 0;
139
+
140
+ for (const label of uniqueLabels) {
141
+ labelMap.set(label, communityId++);
142
+ }
143
+
144
+ const communities = new Map<string, number>();
145
+ for (const [node, label] of labels) {
146
+ const mappedLabel = labelMap.get(label);
147
+ if (mappedLabel !== undefined) {
148
+ communities.set(node, mappedLabel);
149
+ }
150
+ }
151
+
152
+ return {
153
+ communities,
154
+ iterations,
155
+ converged,
156
+ };
157
+ }
158
+
159
+ /**
160
+ * Asynchronous Label Propagation
161
+ * Updates all nodes simultaneously (can lead to oscillations)
162
+ */
163
+ export function labelPropagationAsync(
164
+ graph: Map<string, Map<string, number>>,
165
+ options: LabelPropagationOptions = {},
166
+ ): LabelPropagationResult {
167
+ const {
168
+ maxIterations = 100,
169
+ } = options;
170
+
171
+ if (graph.size === 0) {
172
+ return {
173
+ communities: new Map(),
174
+ iterations: 0,
175
+ converged: true,
176
+ };
177
+ }
178
+
179
+ // Initialize labels
180
+ const labels = new Map<string, number>();
181
+ const nodes = Array.from(graph.keys());
182
+ nodes.forEach((node, i) => labels.set(node, i));
183
+
184
+ let iterations = 0;
185
+ let converged = false;
186
+
187
+ // Main loop
188
+ while (iterations < maxIterations && !converged) {
189
+ iterations++;
190
+ converged = true;
191
+
192
+ // Store new labels
193
+ const newLabels = new Map<string, number>();
194
+
195
+ // Update all nodes simultaneously
196
+ for (const node of nodes) {
197
+ const neighbors = graph.get(node);
198
+ if (!neighbors || neighbors.size === 0) {
199
+ const nodeLabel = labels.get(node);
200
+ if (nodeLabel !== undefined) {
201
+ newLabels.set(node, nodeLabel);
202
+ }
203
+
204
+ continue;
205
+ }
206
+
207
+ // Count label frequencies
208
+ const labelCounts = new Map<number, number>();
209
+ let maxCount = 0;
210
+ const nodeLabel = labels.get(node);
211
+ if (nodeLabel === undefined) {
212
+ continue;
213
+ }
214
+
215
+ let maxLabel = nodeLabel;
216
+
217
+ for (const [neighbor, weight] of neighbors) {
218
+ const neighborLabel = labels.get(neighbor);
219
+ if (neighborLabel === undefined) {
220
+ continue;
221
+ }
222
+
223
+ const count = (labelCounts.get(neighborLabel) ?? 0) + weight;
224
+ labelCounts.set(neighborLabel, count);
225
+
226
+ if (count > maxCount || (count === maxCount && neighborLabel < maxLabel)) {
227
+ maxCount = count;
228
+ maxLabel = neighborLabel;
229
+ }
230
+ }
231
+
232
+ newLabels.set(node, maxLabel);
233
+
234
+ if (maxLabel !== labels.get(node)) {
235
+ converged = false;
236
+ }
237
+ }
238
+
239
+ // Apply new labels
240
+ for (const [node, label] of newLabels) {
241
+ labels.set(node, label);
242
+ }
243
+ }
244
+
245
+ // Renumber communities
246
+ const uniqueLabels = new Set(labels.values());
247
+ const labelMap = new Map<number, number>();
248
+ let communityId = 0;
249
+
250
+ for (const label of uniqueLabels) {
251
+ labelMap.set(label, communityId++);
252
+ }
253
+
254
+ const communities = new Map<string, number>();
255
+ for (const [node, label] of labels) {
256
+ const mappedLabel = labelMap.get(label);
257
+ if (mappedLabel !== undefined) {
258
+ communities.set(node, mappedLabel);
259
+ }
260
+ }
261
+
262
+ return {
263
+ communities,
264
+ iterations,
265
+ converged,
266
+ };
267
+ }
268
+
269
+ /**
270
+ * Semi-supervised Label Propagation
271
+ * Some nodes have fixed labels that don't change
272
+ */
273
+ export function labelPropagationSemiSupervised(
274
+ graph: Map<string, Map<string, number>>,
275
+ seedLabels: Map<string, number>,
276
+ options: LabelPropagationOptions = {},
277
+ ): LabelPropagationResult {
278
+ const {
279
+ maxIterations = 100,
280
+ randomSeed = 42,
281
+ } = options;
282
+
283
+ // Initialize random number generator
284
+ let seed = randomSeed;
285
+ const random = (): number => {
286
+ seed = ((seed * 1664525) + 1013904223) % 2147483647;
287
+ return seed / 2147483647;
288
+ };
289
+
290
+ // Initialize labels
291
+ const labels = new Map<string, number>();
292
+ const nodes = Array.from(graph.keys());
293
+ let labelCounter = Math.max(... Array.from(seedLabels.values())) + 1;
294
+
295
+ for (const node of nodes) {
296
+ if (seedLabels.has(node)) {
297
+ const seedLabel = seedLabels.get(node);
298
+ if (seedLabel !== undefined) {
299
+ labels.set(node, seedLabel);
300
+ }
301
+ } else {
302
+ labels.set(node, labelCounter++);
303
+ }
304
+ }
305
+
306
+ let iterations = 0;
307
+ let converged = false;
308
+
309
+ // Main loop
310
+ while (iterations < maxIterations && !converged) {
311
+ iterations++;
312
+ converged = true;
313
+
314
+ // Create random order
315
+ const nodeOrder = nodes.filter((n) => !seedLabels.has(n));
316
+ shuffle(nodeOrder, random);
317
+
318
+ // Update non-seed nodes
319
+ for (const node of nodeOrder) {
320
+ const neighbors = graph.get(node);
321
+ if (!neighbors || neighbors.size === 0) {
322
+ continue;
323
+ }
324
+
325
+ // Count label frequencies
326
+ const labelCounts = new Map<number, number>();
327
+ let maxCount = 0;
328
+ const candidateLabels: number[] = [];
329
+
330
+ for (const [neighbor, weight] of neighbors) {
331
+ const neighborLabel = labels.get(neighbor);
332
+ if (neighborLabel === undefined) {
333
+ continue;
334
+ }
335
+
336
+ const count = (labelCounts.get(neighborLabel) ?? 0) + weight;
337
+ labelCounts.set(neighborLabel, count);
338
+
339
+ if (count > maxCount) {
340
+ maxCount = count;
341
+ candidateLabels.length = 0;
342
+ candidateLabels.push(neighborLabel);
343
+ } else if (count === maxCount) {
344
+ candidateLabels.push(neighborLabel);
345
+ }
346
+ }
347
+
348
+ // Choose label
349
+ const currentLabel = labels.get(node);
350
+ if (currentLabel === undefined) {
351
+ continue;
352
+ }
353
+
354
+ let newLabel = currentLabel;
355
+
356
+ if (candidateLabels.length > 0) {
357
+ const index = Math.floor(random() * candidateLabels.length);
358
+ const selectedLabel = candidateLabels[index];
359
+ if (selectedLabel !== undefined) {
360
+ newLabel = selectedLabel;
361
+ }
362
+ }
363
+
364
+ if (newLabel !== currentLabel) {
365
+ labels.set(node, newLabel);
366
+ converged = false;
367
+ }
368
+ }
369
+ }
370
+
371
+ return {
372
+ communities: labels,
373
+ iterations,
374
+ converged,
375
+ };
376
+ }
377
+
378
+ /**
379
+ * Fisher-Yates shuffle
380
+ */
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
+ }
391
+ }
392
+