@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,469 @@
1
+ import type {Graph} from "../core/graph.js";
2
+ import type {NodeId} from "../types/index.js";
3
+
4
+ /**
5
+ * Configuration options for the SynC (Synergistic Deep Graph Clustering) algorithm
6
+ */
7
+ export interface SynCConfig {
8
+ /** Number of clusters to find */
9
+ numClusters: number;
10
+ /** Maximum number of iterations for convergence */
11
+ maxIterations?: number;
12
+ /** Convergence tolerance */
13
+ tolerance?: number;
14
+ /** Random seed for reproducibility */
15
+ seed?: number;
16
+ /** Learning rate for optimization */
17
+ learningRate?: number;
18
+ /** Regularization parameter */
19
+ lambda?: number;
20
+ }
21
+
22
+ /**
23
+ * Result of the SynC clustering algorithm
24
+ */
25
+ export interface SynCResult {
26
+ /** Cluster assignments for each node */
27
+ clusters: Map<NodeId, number>;
28
+ /** Final loss value */
29
+ loss: number;
30
+ /** Number of iterations performed */
31
+ iterations: number;
32
+ /** Node embeddings learned by the algorithm */
33
+ embeddings: Map<NodeId, number[]>;
34
+ /** Whether the algorithm converged */
35
+ converged: boolean;
36
+ }
37
+
38
+ /**
39
+ * Synergistic Deep Graph Clustering (SynC) Algorithm
40
+ *
41
+ * This algorithm combines representation learning with structure augmentation
42
+ * for improved clustering performance on graphs. It jointly optimizes node
43
+ * embeddings and cluster assignments while preserving graph structure.
44
+ *
45
+ * Based on: "Synergistic Deep Graph Clustering" (arXiv:2406.15797, June 2024)
46
+ *
47
+ * @param graph - Input graph to cluster
48
+ * @param config - Configuration options
49
+ * @returns Clustering result with assignments and embeddings
50
+ */
51
+ export function syncClustering(graph: Graph, config: SynCConfig): SynCResult {
52
+ const {
53
+ numClusters,
54
+ maxIterations = 100,
55
+ tolerance = 1e-6,
56
+ seed = 42,
57
+ learningRate = 0.01,
58
+ lambda = 0.1,
59
+ } = config;
60
+
61
+ // Set random seed for reproducibility (save original)
62
+ const originalRandom = Math.random;
63
+ Math.random = seedRandom(seed);
64
+
65
+ const nodes = Array.from(graph.nodes());
66
+ const nodeCount = nodes.length;
67
+
68
+ if (nodeCount === 0) {
69
+ return {
70
+ clusters: new Map(),
71
+ loss: 0,
72
+ iterations: 0,
73
+ embeddings: new Map(),
74
+ converged: true,
75
+ };
76
+ }
77
+
78
+ if (numClusters <= 0 || numClusters > nodeCount) {
79
+ throw new Error(`Invalid number of clusters: ${String(numClusters)}. Must be between 1 and ${String(nodeCount)}`);
80
+ }
81
+
82
+ // Initialize node embeddings (simplified version using graph features)
83
+ const embeddingDim = Math.min(64, nodeCount);
84
+ const embeddings = new Map<NodeId, number[]>();
85
+
86
+ // Initialize embeddings based on node features
87
+ for (const node of nodes) {
88
+ const embedding = initializeNodeEmbedding(graph, node.id, embeddingDim);
89
+ embeddings.set(node.id, embedding);
90
+ }
91
+
92
+ // Initialize cluster centers
93
+ const clusterCenters = initializeClusterCenters(embeddings, numClusters);
94
+
95
+ let previousLoss = Infinity;
96
+ let iterations = 0;
97
+ let converged = false;
98
+
99
+ for (iterations = 0; iterations < maxIterations; iterations++) {
100
+ // E-step: Assign nodes to clusters
101
+ const clusters = new Map<NodeId, number>();
102
+
103
+ for (const node of nodes) {
104
+ const nodeEmbedding = embeddings.get(node.id);
105
+ if (!nodeEmbedding) {
106
+ continue;
107
+ }
108
+
109
+ let bestCluster = 0;
110
+ let minDistance = Infinity;
111
+
112
+ for (let k = 0; k < numClusters; k++) {
113
+ const center = clusterCenters[k];
114
+ if (!center) {
115
+ continue;
116
+ }
117
+
118
+ const distance = euclideanDistance(nodeEmbedding, center);
119
+ if (distance < minDistance) {
120
+ minDistance = distance;
121
+ bestCluster = k;
122
+ }
123
+ }
124
+
125
+ clusters.set(node.id, bestCluster);
126
+ }
127
+
128
+ // M-step: Update embeddings and cluster centers
129
+ updateEmbeddings(graph, embeddings, clusters, learningRate, lambda);
130
+ updateClusterCenters(embeddings, clusters, clusterCenters, numClusters);
131
+
132
+ // Calculate current loss
133
+ const currentLoss = calculateLoss(graph, embeddings, clusters, clusterCenters, lambda);
134
+
135
+ // Check for convergence
136
+ if (Math.abs(previousLoss - currentLoss) < tolerance) {
137
+ converged = true;
138
+ break;
139
+ }
140
+
141
+ previousLoss = currentLoss;
142
+ }
143
+
144
+ // Final cluster assignment
145
+ const finalClusters = new Map<NodeId, number>();
146
+ for (const node of nodes) {
147
+ const nodeEmbedding = embeddings.get(node.id);
148
+ if (!nodeEmbedding) {
149
+ continue;
150
+ }
151
+
152
+ let bestCluster = 0;
153
+ let minDistance = Infinity;
154
+
155
+ for (let k = 0; k < numClusters; k++) {
156
+ const center = clusterCenters[k];
157
+ if (!center) {
158
+ continue;
159
+ }
160
+
161
+ const distance = euclideanDistance(nodeEmbedding, center);
162
+ if (distance < minDistance) {
163
+ minDistance = distance;
164
+ bestCluster = k;
165
+ }
166
+ }
167
+
168
+ finalClusters.set(node.id, bestCluster);
169
+ }
170
+
171
+ // Restore original random function
172
+ Math.random = originalRandom;
173
+
174
+ return {
175
+ clusters: finalClusters,
176
+ loss: previousLoss,
177
+ iterations: iterations + 1,
178
+ embeddings,
179
+ converged,
180
+ };
181
+ }
182
+
183
+ /**
184
+ * Initialize node embedding based on graph structure and features
185
+ */
186
+ function initializeNodeEmbedding(graph: Graph, nodeId: NodeId, dim: number): number[] {
187
+ const embedding = new Array<number>(dim).fill(0);
188
+
189
+ // Use node degree as a base feature
190
+ const degree = graph.degree(nodeId);
191
+ const normalizedDegree = degree / Math.max(1, graph.nodeCount - 1);
192
+
193
+ // Initialize with small random values influenced by graph structure
194
+ for (let i = 0; i < dim; i++) {
195
+ embedding[i] = ((Math.random() - 0.5) * 0.1) + (normalizedDegree * 0.1);
196
+ }
197
+
198
+ return embedding;
199
+ }
200
+
201
+ /**
202
+ * Initialize cluster centers using k-means++ style initialization
203
+ */
204
+ function initializeClusterCenters(embeddings: Map<NodeId, number[]>, numClusters: number): number[][] {
205
+ const embeddingArray = Array.from(embeddings.values());
206
+ const centers: number[][] = [];
207
+
208
+ // Choose first center randomly
209
+ const firstCenter = embeddingArray[Math.floor(Math.random() * embeddingArray.length)];
210
+ if (firstCenter) {
211
+ centers.push([... firstCenter] as number[]);
212
+ }
213
+
214
+ // Choose remaining centers using k-means++ initialization
215
+ for (let k = 1; k < numClusters; k++) {
216
+ const distances: number[] = [];
217
+ let totalDistance = 0;
218
+
219
+ for (const embedding of embeddingArray) {
220
+ let minDistance = Infinity;
221
+ for (const center of centers) {
222
+ const distance = euclideanDistance(embedding, center);
223
+ minDistance = Math.min(minDistance, distance);
224
+ }
225
+ distances.push(minDistance * minDistance);
226
+ totalDistance += minDistance * minDistance;
227
+ }
228
+
229
+ // Choose next center with probability proportional to squared distance
230
+ let randomValue = Math.random() * totalDistance;
231
+ for (let i = 0; i < embeddingArray.length; i++) {
232
+ const distanceValue = distances[i];
233
+ if (distanceValue !== undefined) {
234
+ randomValue -= distanceValue;
235
+ }
236
+
237
+ if (randomValue <= 0) {
238
+ const newCenter = embeddingArray[i];
239
+ if (newCenter) {
240
+ centers.push([... newCenter] as number[]);
241
+ }
242
+
243
+ break;
244
+ }
245
+ }
246
+ }
247
+
248
+ return centers;
249
+ }
250
+
251
+ /**
252
+ * Update node embeddings using gradient descent
253
+ */
254
+ function updateEmbeddings(
255
+ graph: Graph,
256
+ embeddings: Map<NodeId, number[]>,
257
+ clusters: Map<NodeId, number>,
258
+ learningRate: number,
259
+ lambda: number,
260
+ ): void {
261
+ const gradients = new Map<NodeId, number[]>();
262
+
263
+ // Initialize gradients
264
+ for (const [nodeId, embedding] of embeddings) {
265
+ gradients.set(nodeId, new Array<number>(embedding.length).fill(0));
266
+ }
267
+
268
+ // Calculate gradients based on graph structure (simplified)
269
+ for (const node of graph.nodes()) {
270
+ const nodeId = node.id;
271
+ const nodeEmbedding = embeddings.get(nodeId);
272
+ if (!nodeEmbedding) {
273
+ continue;
274
+ }
275
+
276
+ const gradient = gradients.get(nodeId);
277
+ if (!gradient) {
278
+ continue;
279
+ }
280
+
281
+ // Neighbor reconstruction loss gradient
282
+ for (const neighborId of graph.neighbors(nodeId)) {
283
+ const neighborEmbedding = embeddings.get(neighborId);
284
+ if (!neighborEmbedding) {
285
+ continue;
286
+ }
287
+
288
+ const diff = nodeEmbedding.map((val: number, i: number) => val - (neighborEmbedding[i] ?? 0));
289
+
290
+ for (let i = 0; i < gradient.length; i++) {
291
+ const gradVal = gradient[i];
292
+ const diffVal = diff[i];
293
+ if (gradVal !== undefined && diffVal !== undefined) {
294
+ gradient[i] = gradVal + (lambda * diffVal);
295
+ }
296
+ }
297
+ }
298
+
299
+ // Regularization gradient
300
+ for (let i = 0; i < gradient.length; i++) {
301
+ const gradVal = gradient[i];
302
+ const nodeVal = nodeEmbedding[i];
303
+ if (gradVal !== undefined && nodeVal !== undefined) {
304
+ gradient[i] = gradVal + (lambda * nodeVal);
305
+ }
306
+ }
307
+ }
308
+
309
+ // Update embeddings
310
+ for (const [nodeId, embedding] of embeddings) {
311
+ const gradient = gradients.get(nodeId);
312
+ if (!gradient) {
313
+ continue;
314
+ }
315
+
316
+ for (let i = 0; i < embedding.length; i++) {
317
+ const embVal = embedding[i];
318
+ const gradVal = gradient[i];
319
+ if (embVal !== undefined && gradVal !== undefined) {
320
+ embedding[i] = embVal - (learningRate * gradVal);
321
+ }
322
+ }
323
+ }
324
+ }
325
+
326
+ /**
327
+ * Update cluster centers based on current assignments
328
+ */
329
+ function updateClusterCenters(
330
+ embeddings: Map<NodeId, number[]>,
331
+ clusters: Map<NodeId, number>,
332
+ clusterCenters: number[][],
333
+ numClusters: number,
334
+ ): void {
335
+ const dimensions = clusterCenters[0]?.length ?? 0;
336
+ const clusterSums: number[][] = Array.from({length: numClusters}, () =>
337
+ new Array(dimensions).fill(0) as number[],
338
+ );
339
+ const clusterCounts = new Array<number>(numClusters).fill(0);
340
+
341
+ // Sum embeddings for each cluster
342
+ for (const [nodeId, clusterIdx] of clusters) {
343
+ const embedding = embeddings.get(nodeId);
344
+ if (!embedding) {
345
+ continue;
346
+ }
347
+
348
+ for (let i = 0; i < embedding.length; i++) {
349
+ const clusterSum = clusterSums[clusterIdx];
350
+ if (!clusterSum) {
351
+ continue;
352
+ }
353
+
354
+ const sumVal = clusterSum[i];
355
+ const embVal = embedding[i];
356
+ if (sumVal !== undefined && embVal !== undefined) {
357
+ clusterSum[i] = sumVal + embVal;
358
+ }
359
+ }
360
+ if (clusterCounts[clusterIdx] !== undefined) {
361
+ clusterCounts[clusterIdx]++;
362
+ }
363
+ }
364
+
365
+ // Update cluster centers (average of assigned embeddings)
366
+ for (let k = 0; k < numClusters; k++) {
367
+ const count = clusterCounts[k];
368
+ const center = clusterCenters[k];
369
+ const sum = clusterSums[k];
370
+ if (count !== undefined && count > 0 && center && sum) {
371
+ for (let i = 0; i < center.length; i++) {
372
+ const sumVal = sum[i];
373
+ if (sumVal !== undefined) {
374
+ center[i] = sumVal / count;
375
+ }
376
+ }
377
+ }
378
+ }
379
+ }
380
+
381
+ /**
382
+ * Calculate the total loss function
383
+ */
384
+ function calculateLoss(
385
+ graph: Graph,
386
+ embeddings: Map<NodeId, number[]>,
387
+ clusters: Map<NodeId, number>,
388
+ clusterCenters: number[][],
389
+ lambda: number,
390
+ ): number {
391
+ let clusteringLoss = 0;
392
+ let reconstructionLoss = 0;
393
+ let regularizationLoss = 0;
394
+
395
+ // Clustering loss (distance to cluster centers)
396
+ for (const [nodeId, clusterIdx] of clusters) {
397
+ const embedding = embeddings.get(nodeId);
398
+ if (!embedding) {
399
+ continue;
400
+ }
401
+
402
+ const center = clusterCenters[clusterIdx];
403
+ if (!center) {
404
+ continue;
405
+ }
406
+
407
+ clusteringLoss += euclideanDistance(embedding, center) ** 2;
408
+ }
409
+
410
+ // Graph reconstruction loss
411
+ for (const node of graph.nodes()) {
412
+ const nodeId = node.id;
413
+ const nodeEmbedding = embeddings.get(nodeId);
414
+ if (!nodeEmbedding) {
415
+ continue;
416
+ }
417
+
418
+ for (const neighborId of graph.neighbors(nodeId)) {
419
+ const neighborEmbedding = embeddings.get(neighborId);
420
+ if (!neighborEmbedding) {
421
+ continue;
422
+ }
423
+
424
+ const distance = euclideanDistance(nodeEmbedding, neighborEmbedding);
425
+ reconstructionLoss += distance ** 2;
426
+ }
427
+ }
428
+
429
+ // Regularization loss
430
+ for (const embedding of embeddings.values()) {
431
+ for (const value of embedding) {
432
+ regularizationLoss += value ** 2;
433
+ }
434
+ }
435
+
436
+ return clusteringLoss + (lambda * reconstructionLoss) + (lambda * regularizationLoss);
437
+ }
438
+
439
+ /**
440
+ * Calculate Euclidean distance between two vectors
441
+ */
442
+ function euclideanDistance(a: number[], b: number[]): number {
443
+ let sum = 0;
444
+ for (let i = 0; i < a.length; i++) {
445
+ const aVal = a[i];
446
+ const bVal = b[i];
447
+ if (aVal !== undefined && bVal !== undefined) {
448
+ const diff = aVal - bVal;
449
+ sum += diff * diff;
450
+ }
451
+ }
452
+ return Math.sqrt(sum);
453
+ }
454
+
455
+ /**
456
+ * Simple seeded random number generator for reproducibility
457
+ */
458
+ function seedRandom(seed: number): () => number {
459
+ const m = 0x80000000; // 2**31
460
+ const a = 1103515245;
461
+ const c = 12345;
462
+
463
+ seed = seed % m;
464
+
465
+ return function() {
466
+ seed = ((a * seed) + c) % m;
467
+ return seed / (m - 1);
468
+ };
469
+ }