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