@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,672 @@
1
+ import type {Graph} from "../core/graph.js";
2
+ import type {NodeId} from "../types/index.js";
3
+
4
+ /**
5
+ * Spectral Clustering implementation
6
+ *
7
+ * Uses eigenvalues and eigenvectors of the graph Laplacian matrix to perform clustering.
8
+ * Particularly effective for finding non-convex clusters and communities in graphs.
9
+ *
10
+ * Time complexity: O(V³) for eigendecomposition
11
+ * Space complexity: O(V²)
12
+ */
13
+
14
+ export interface SpectralClusteringOptions {
15
+ k: number; // Number of clusters to find
16
+ laplacianType?: "unnormalized" | "normalized" | "randomWalk"; // Type of Laplacian
17
+ maxIterations?: number; // Max iterations for k-means (default: 100)
18
+ tolerance?: number; // Convergence tolerance (default: 1e-4)
19
+ }
20
+
21
+ export interface SpectralClusteringResult {
22
+ communities: NodeId[][];
23
+ clusterAssignments: Map<NodeId, number>;
24
+ eigenvalues?: number[];
25
+ eigenvectors?: number[][];
26
+ }
27
+
28
+ /**
29
+ * Perform spectral clustering on a graph
30
+ */
31
+ export function spectralClustering(
32
+ graph: Graph,
33
+ options: SpectralClusteringOptions,
34
+ ): SpectralClusteringResult {
35
+ const {
36
+ k,
37
+ laplacianType = "normalized",
38
+ maxIterations = 100,
39
+ } = options;
40
+
41
+ const nodes = Array.from(graph.nodes());
42
+ const nodeIds = nodes.map((node) => node.id);
43
+ const n = nodeIds.length;
44
+
45
+ if (k >= n) {
46
+ // Return each node as its own cluster
47
+ const communities: NodeId[][] = nodeIds.map((id) => [id]);
48
+ const clusterAssignments = new Map<NodeId, number>();
49
+ nodeIds.forEach((id, index) => clusterAssignments.set(id, index));
50
+ return {communities, clusterAssignments};
51
+ }
52
+
53
+ // Build adjacency matrix
54
+ const adjacencyMatrix = buildAdjacencyMatrix(graph, nodeIds);
55
+
56
+ // Build Laplacian matrix
57
+ const laplacianMatrix = buildLaplacianMatrix(adjacencyMatrix, laplacianType);
58
+
59
+ // Find k smallest eigenvectors
60
+ const eigenResult = findSmallestEigenvectors(laplacianMatrix, k);
61
+
62
+ // Perform k-means clustering on the eigenvectors
63
+ // For spectral clustering, we need to transpose the eigenvector matrix
64
+ // Each row should be a data point (node) with features from the eigenvectors
65
+ const dataPoints: number[][] = [];
66
+ for (let i = 0; i < nodeIds.length; i++) {
67
+ const point: number[] = [];
68
+ for (let j = 0; j < k; j++) {
69
+ const eigenvector = eigenResult.eigenvectors[j];
70
+ if (eigenvector) {
71
+ point.push(eigenvector[i] ?? 0);
72
+ }
73
+ }
74
+ dataPoints.push(point);
75
+ }
76
+
77
+ // Normalize the data points row-wise (for normalized spectral clustering)
78
+ if (laplacianType === "normalized") {
79
+ normalizeRows(dataPoints);
80
+ }
81
+
82
+ const kmeans = kMeansClustering(dataPoints, k, maxIterations);
83
+
84
+ // Build communities
85
+ const communities: NodeId[][] = Array.from({length: k}, () => []);
86
+ const clusterAssignments = new Map<NodeId, number>();
87
+
88
+ for (let i = 0; i < nodeIds.length; i++) {
89
+ const clusterId = kmeans.assignments[i] ?? 0;
90
+ const nodeId = nodeIds[i];
91
+ if (!nodeId) {
92
+ continue;
93
+ }
94
+
95
+ // Ensure clusterId is valid
96
+ if (clusterId >= 0 && clusterId < k) {
97
+ const community = communities[clusterId];
98
+ if (community) {
99
+ community.push(nodeId);
100
+ }
101
+
102
+ clusterAssignments.set(nodeId, clusterId);
103
+ } else {
104
+ // Assign to first cluster if invalid
105
+ const firstCommunity = communities[0];
106
+ if (firstCommunity) {
107
+ firstCommunity.push(nodeId);
108
+ }
109
+
110
+ clusterAssignments.set(nodeId, 0);
111
+ }
112
+ }
113
+
114
+ // Filter out empty communities
115
+ const nonEmptyCommunities = communities.filter((community) => community.length > 0);
116
+
117
+ return {
118
+ communities: nonEmptyCommunities,
119
+ clusterAssignments,
120
+ eigenvalues: eigenResult.eigenvalues,
121
+ eigenvectors: eigenResult.eigenvectors,
122
+ };
123
+ }
124
+
125
+ /**
126
+ * Build adjacency matrix from graph
127
+ */
128
+ function buildAdjacencyMatrix(graph: Graph, nodeIds: NodeId[]): number[][] {
129
+ const n = nodeIds.length;
130
+ const matrix: number[][] = Array.from({length: n}, () => Array(n).fill(0) as number[]);
131
+ const nodeToIndex = new Map<NodeId, number>();
132
+
133
+ nodeIds.forEach((id, index) => nodeToIndex.set(id, index));
134
+
135
+ for (let i = 0; i < n; i++) {
136
+ const nodeId = nodeIds[i];
137
+ if (!nodeId) {
138
+ continue;
139
+ }
140
+
141
+ const neighbors = graph.neighbors(nodeId);
142
+
143
+ for (const neighbor of neighbors) {
144
+ const j = nodeToIndex.get(neighbor);
145
+ if (j !== undefined) {
146
+ const edge = graph.getEdge(nodeId, neighbor);
147
+ const weight = edge?.weight ?? 1;
148
+ const matrixRow = matrix[i];
149
+ if (matrixRow) {
150
+ matrixRow[j] = weight;
151
+ }
152
+
153
+ if (!graph.isDirected) {
154
+ const matrixRowJ = matrix[j];
155
+ if (matrixRowJ) {
156
+ matrixRowJ[i] = weight;
157
+ }
158
+ }
159
+ }
160
+ }
161
+ }
162
+
163
+ return matrix;
164
+ }
165
+
166
+ /**
167
+ * Build Laplacian matrix from adjacency matrix
168
+ */
169
+ function buildLaplacianMatrix(adjacency: number[][], type: string): number[][] {
170
+ const n = adjacency.length;
171
+ const laplacian: number[][] = Array.from({length: n}, () => Array(n).fill(0) as number[]);
172
+
173
+ // Calculate degree matrix
174
+ const degrees = Array(n).fill(0) as number[];
175
+ for (let i = 0; i < n; i++) {
176
+ for (let j = 0; j < n; j++) {
177
+ const adjacencyRow = adjacency[i];
178
+ const adjacencyVal = adjacencyRow ? adjacencyRow[j] : 0;
179
+ if (adjacencyVal !== undefined) {
180
+ const degreeVal = degrees[i];
181
+ if (degreeVal !== undefined) {
182
+ degrees[i] = degreeVal + adjacencyVal;
183
+ }
184
+ }
185
+ }
186
+ }
187
+
188
+ if (type === "unnormalized") {
189
+ // L = D - A
190
+ for (let i = 0; i < n; i++) {
191
+ for (let j = 0; j < n; j++) {
192
+ if (i === j) {
193
+ const laplacianRow = laplacian[i];
194
+ const degreeVal = degrees[i];
195
+ if (laplacianRow && degreeVal !== undefined) {
196
+ laplacianRow[j] = degreeVal;
197
+ }
198
+ } else {
199
+ const adjacencyRow = adjacency[i];
200
+ const laplacianRow = laplacian[i];
201
+ if (adjacencyRow && laplacianRow) {
202
+ const adjacencyVal = adjacencyRow[j];
203
+ laplacianRow[j] = adjacencyVal !== undefined ? -adjacencyVal : 0;
204
+ }
205
+ }
206
+ }
207
+ }
208
+ } else if (type === "normalized") {
209
+ // L_sym = D^(-1/2) * L * D^(-1/2)
210
+ for (let i = 0; i < n; i++) {
211
+ for (let j = 0; j < n; j++) {
212
+ if (i === j) {
213
+ const di = degrees[i];
214
+ const laplacianRow = laplacian[i];
215
+ if (laplacianRow) {
216
+ laplacianRow[j] = di !== undefined && di > 0 ? 1 : 0;
217
+ }
218
+ } else {
219
+ const adjacencyRow = adjacency[i];
220
+ if (!adjacencyRow) {
221
+ continue;
222
+ }
223
+
224
+ const adjacencyVal = adjacencyRow[j];
225
+ const di = degrees[i];
226
+ const dj = degrees[j];
227
+ if (adjacencyVal !== undefined && adjacencyVal > 0 && di !== undefined && dj !== undefined && di > 0 && dj > 0) {
228
+ const laplacianRow = laplacian[i];
229
+ if (laplacianRow) {
230
+ laplacianRow[j] = -adjacencyVal / Math.sqrt(di * dj);
231
+ }
232
+ }
233
+ }
234
+ }
235
+ }
236
+ } else if (type === "randomWalk") {
237
+ // L_rw = D^(-1) * L
238
+ for (let i = 0; i < n; i++) {
239
+ for (let j = 0; j < n; j++) {
240
+ if (i === j) {
241
+ const di = degrees[i];
242
+ const laplacianRow = laplacian[i];
243
+ if (laplacianRow) {
244
+ laplacianRow[j] = di !== undefined && di > 0 ? 1 : 0;
245
+ }
246
+ } else {
247
+ const adjacencyRow = adjacency[i];
248
+ if (!adjacencyRow) {
249
+ continue;
250
+ }
251
+
252
+ const adjacencyVal = adjacencyRow[j];
253
+ const di = degrees[i];
254
+ if (adjacencyVal !== undefined && adjacencyVal > 0 && di !== undefined && di > 0) {
255
+ const laplacianRow = laplacian[i];
256
+ if (laplacianRow) {
257
+ laplacianRow[j] = -adjacencyVal / di;
258
+ }
259
+ }
260
+ }
261
+ }
262
+ }
263
+ }
264
+
265
+ return laplacian;
266
+ }
267
+
268
+ /**
269
+ * Find k smallest eigenvectors using simplified eigendecomposition
270
+ * This is a simplified implementation - in practice, you'd use LAPACK or similar
271
+ */
272
+ function findSmallestEigenvectors(matrix: number[][], k: number): {
273
+ eigenvalues: number[];
274
+ eigenvectors: number[][];
275
+ } {
276
+ const n = matrix.length;
277
+
278
+ if (n === 0 || k === 0) {
279
+ return {eigenvalues: [], eigenvectors: []};
280
+ }
281
+
282
+ // For very small matrices, use the full power iteration approach
283
+ // Remove the simplified approach that was causing issues
284
+ if (k >= n) {
285
+ // If k >= n, we still need proper eigenvectors, not identity
286
+ // Fall through to the power iteration below
287
+ }
288
+
289
+ // For spectral clustering, we need proper eigenvectors
290
+ // Special handling for small k values which are common in clustering
291
+ if (k <= 3 && n > k) {
292
+ return computeSmallestEigenvectorsSimple(matrix, k, n);
293
+ }
294
+
295
+ // For larger k, use power iteration
296
+ const eigenvectors: number[][] = [];
297
+ const eigenvalues: number[] = [];
298
+ const maxIterations = 100;
299
+
300
+ for (let eigIdx = 0; eigIdx < k; eigIdx++) {
301
+ // Initialize random vector
302
+ let vector = Array(n).fill(0).map(() => Math.random() - 0.5);
303
+
304
+ // Normalize initial vector
305
+ const initNorm = Math.sqrt(vector.reduce((sum, val) => sum + (val * val), 0));
306
+ if (initNorm > 0) {
307
+ vector = vector.map((val) => val / initNorm);
308
+ }
309
+
310
+ // Orthogonalize against previous eigenvectors
311
+ for (let j = 0; j < eigIdx; j++) {
312
+ const ejVector = eigenvectors[j];
313
+ if (!ejVector) {
314
+ continue;
315
+ }
316
+
317
+ const dot = vector.reduce((sum, val, idx) => sum + (val * (ejVector[idx] ?? 0)), 0);
318
+ vector = vector.map((val, idx) => val - (dot * (ejVector[idx] ?? 0)));
319
+ }
320
+
321
+ // Power iteration
322
+ for (let iter = 0; iter < maxIterations; iter++) {
323
+ // Multiply by matrix
324
+ const newVector = Array(n).fill(0) as number[];
325
+ for (let i = 0; i < n; i++) {
326
+ for (let j = 0; j < n; j++) {
327
+ const matrixRow = matrix[i];
328
+ const vecVal = vector[j];
329
+ const matrixVal = matrixRow?.[j];
330
+ if (matrixVal !== undefined && vecVal !== undefined) {
331
+ const nvVal = newVector[i];
332
+ if (nvVal !== undefined) {
333
+ newVector[i] = nvVal + (matrixVal * vecVal);
334
+ }
335
+ }
336
+ }
337
+ }
338
+
339
+ // Orthogonalize against previous eigenvectors
340
+ for (let j = 0; j < eigIdx; j++) {
341
+ const ejVector = eigenvectors[j];
342
+ if (!ejVector) {
343
+ continue;
344
+ }
345
+
346
+ const dot = newVector.reduce((sum, val, idx) => sum + (val * (ejVector[idx] ?? 0)), 0);
347
+ for (let i = 0; i < n; i++) {
348
+ const ejVal = ejVector[i];
349
+ if (ejVal !== undefined) {
350
+ const nvVal = newVector[i];
351
+ if (nvVal !== undefined) {
352
+ newVector[i] = nvVal - (dot * ejVal);
353
+ }
354
+ }
355
+ }
356
+ }
357
+
358
+ // Normalize
359
+ const norm = Math.sqrt(newVector.reduce((sum, val) => sum + ((val * val)), 0));
360
+ if (norm > 1e-10) {
361
+ vector = newVector.map((val) => val / norm);
362
+ } else {
363
+ break;
364
+ }
365
+ }
366
+
367
+ // Calculate eigenvalue (Rayleigh quotient)
368
+ let eigenvalue = 0;
369
+ const Av = Array(n).fill(0) as number[];
370
+ for (let i = 0; i < n; i++) {
371
+ for (let j = 0; j < n; j++) {
372
+ const matrixRow = matrix[i];
373
+ const vecVal = vector[j];
374
+ const matrixVal = matrixRow?.[j];
375
+ if (matrixVal !== undefined && vecVal !== undefined) {
376
+ const avVal = Av[i];
377
+ if (avVal !== undefined) {
378
+ Av[i] = avVal + (matrixVal * vecVal);
379
+ }
380
+ }
381
+ }
382
+ }
383
+ eigenvalue = vector.reduce((sum, val, idx) => {
384
+ const avVal = Av[idx];
385
+ return sum + (val * (avVal ?? 0));
386
+ }, 0);
387
+
388
+ eigenvectors.push(vector);
389
+ eigenvalues.push(eigenvalue);
390
+ }
391
+
392
+ return {eigenvalues, eigenvectors};
393
+ }
394
+
395
+ /**
396
+ * Normalize matrix rows to unit length
397
+ */
398
+ function normalizeRows(matrix: number[][]): void {
399
+ for (const row of matrix) {
400
+ const norm = Math.sqrt(row.reduce((sum, val) => sum + (val * val), 0));
401
+ if (norm > 0) {
402
+ for (let j = 0; j < row.length; j++) {
403
+ const val = row[j];
404
+ if (val !== undefined) {
405
+ row[j] = val / norm;
406
+ }
407
+ }
408
+ }
409
+ }
410
+ }
411
+
412
+ /**
413
+ * K-means clustering algorithm
414
+ */
415
+ function kMeansClustering(
416
+ data: number[][],
417
+ k: number,
418
+ maxIterations: number,
419
+ ): {assignments: number[], centroids: number[][]} {
420
+ const n = data.length;
421
+ const d = data[0]?.length ?? 0;
422
+
423
+ // Handle edge cases
424
+ if (n === 0 || k === 0) {
425
+ return {assignments: [], centroids: []};
426
+ }
427
+
428
+ if (k >= n) {
429
+ // Each point is its own cluster
430
+ return {
431
+ assignments: Array.from({length: n}, (_, i) => i),
432
+ centroids: data.slice(0, n),
433
+ };
434
+ }
435
+
436
+ // Initialize centroids by selecting random data points
437
+ const centroids: number[][] = [];
438
+ const selectedIndices = new Set<number>();
439
+
440
+ while (centroids.length < k && selectedIndices.size < n) {
441
+ const idx = Math.floor(Math.random() * n);
442
+ if (!selectedIndices.has(idx) && data[idx]) {
443
+ selectedIndices.add(idx);
444
+ centroids.push([... data[idx]]);
445
+ }
446
+ }
447
+
448
+ // Fill remaining centroids with random values if needed
449
+ while (centroids.length < k) {
450
+ const centroid = Array(d).fill(0) as number[];
451
+ for (let j = 0; j < d; j++) {
452
+ centroid[j] = Math.random() - 0.5;
453
+ }
454
+ centroids.push(centroid);
455
+ }
456
+
457
+ const assignments = Array(n).fill(0) as number[];
458
+ let oldAssignments = Array(n).fill(-1) as number[];
459
+
460
+ for (let iteration = 0; iteration < maxIterations; iteration++) {
461
+ // Assign points to closest centroids
462
+ for (let i = 0; i < n; i++) {
463
+ let minDistance = Number.POSITIVE_INFINITY;
464
+ let bestCluster = 0;
465
+
466
+ for (let j = 0; j < k; j++) {
467
+ const dataPoint = data[i];
468
+ const centroid = centroids[j];
469
+ if (!dataPoint || !centroid) {
470
+ continue;
471
+ }
472
+
473
+ const distance = euclideanDistance(dataPoint, centroid);
474
+ if (distance < minDistance) {
475
+ minDistance = distance;
476
+ bestCluster = j;
477
+ }
478
+ }
479
+
480
+ assignments[i] = bestCluster;
481
+ }
482
+
483
+ // Check for convergence
484
+ let changed = false;
485
+ for (let i = 0; i < n; i++) {
486
+ if (assignments[i] !== oldAssignments[i]) {
487
+ changed = true;
488
+ break;
489
+ }
490
+ }
491
+
492
+ if (!changed) {
493
+ break;
494
+ }
495
+
496
+ oldAssignments = [... assignments];
497
+
498
+ // Update centroids
499
+ const counts = Array(k).fill(0) as number[];
500
+ const sums: number[][] = Array.from({length: k}, () => Array(d).fill(0) as number[]);
501
+
502
+ for (let i = 0; i < n; i++) {
503
+ const cluster = assignments[i] ?? 0;
504
+ const dataPoint = data[i];
505
+ if (!dataPoint) {
506
+ continue;
507
+ }
508
+
509
+ const sumsCluster = sums[cluster];
510
+ if (sumsCluster !== undefined) {
511
+ const countVal = counts[cluster];
512
+ if (countVal !== undefined) {
513
+ counts[cluster] = countVal + 1;
514
+ }
515
+
516
+ for (let j = 0; j < d; j++) {
517
+ const dpVal = dataPoint[j];
518
+ if (dpVal !== undefined) {
519
+ const sumVal = sumsCluster[j];
520
+ if (sumVal !== undefined) {
521
+ sumsCluster[j] = sumVal + dpVal;
522
+ }
523
+ }
524
+ }
525
+ }
526
+ }
527
+
528
+ for (let i = 0; i < k; i++) {
529
+ const countVal = counts[i];
530
+ if (countVal !== undefined && countVal > 0) {
531
+ const sumsRow = sums[i];
532
+ const centroidsRow = centroids[i];
533
+ if (sumsRow !== undefined && centroidsRow !== undefined) {
534
+ for (let j = 0; j < d; j++) {
535
+ const sumVal = sumsRow[j];
536
+ if (sumVal !== undefined) {
537
+ const countVal = counts[i];
538
+ if (countVal !== undefined) {
539
+ centroidsRow[j] = sumVal / countVal;
540
+ }
541
+ }
542
+ }
543
+ }
544
+ }
545
+ }
546
+ }
547
+
548
+ return {assignments, centroids};
549
+ }
550
+
551
+ /**
552
+ * Calculate Euclidean distance between two vectors
553
+ */
554
+ function euclideanDistance(a: number[], b: number[]): number {
555
+ let sum = 0;
556
+ for (let i = 0; i < a.length; i++) {
557
+ const diff = (a[i] ?? 0) - (b[i] ?? 0);
558
+ sum += diff * diff;
559
+ }
560
+ return Math.sqrt(sum);
561
+ }
562
+
563
+ /**
564
+ * Compute smallest eigenvectors for small k (optimized for k=2, k=3)
565
+ */
566
+ function computeSmallestEigenvectorsSimple(matrix: number[][], k: number, n: number): {
567
+ eigenvalues: number[];
568
+ eigenvectors: number[][];
569
+ } {
570
+ const eigenvectors: number[][] = [];
571
+ const eigenvalues: number[] = [];
572
+
573
+ // First eigenvector is constant (corresponds to eigenvalue 0 for connected graph)
574
+ const firstVector = Array(n).fill(1 / Math.sqrt(n)) as number[];
575
+ eigenvectors.push(firstVector);
576
+ eigenvalues.push(0);
577
+
578
+ // For k >= 2, compute the Fiedler vector (second smallest eigenvector)
579
+ if (k >= 2) {
580
+ // Use power iteration on I - L/lambda_max to find second smallest
581
+ const maxEig = 2; // For normalized Laplacian, max eigenvalue <= 2
582
+ let vector = Array(n).fill(0).map(() => Math.random() - 0.5);
583
+
584
+ // Make orthogonal to first eigenvector
585
+ const dot1 = vector.reduce((sum, val) => sum + (val / Math.sqrt(n)), 0);
586
+ vector = vector.map((val) => val - (dot1 / Math.sqrt(n)));
587
+
588
+ // Power iteration on shifted matrix
589
+ for (let iter = 0; iter < 100; iter++) {
590
+ // Compute (I - L/maxEig) * v
591
+ const newVector = Array(n).fill(0) as number[];
592
+
593
+ // Identity part
594
+ for (let i = 0; i < n; i++) {
595
+ newVector[i] = vector[i] ?? 0;
596
+ }
597
+
598
+ // Subtract L * v / maxEig
599
+ for (let i = 0; i < n; i++) {
600
+ for (let j = 0; j < n; j++) {
601
+ const matrixVal = matrix[i]?.[j] ?? 0;
602
+ const vecVal = vector[j] ?? 0;
603
+ newVector[i] = (newVector[i] ?? 0) - ((matrixVal * vecVal) / maxEig);
604
+ }
605
+ }
606
+
607
+ // Orthogonalize against first eigenvector
608
+ const dot = newVector.reduce((sum, val) => sum + (val / Math.sqrt(n)), 0);
609
+ for (let i = 0; i < n; i++) {
610
+ newVector[i] = (newVector[i] ?? 0) - (dot / Math.sqrt(n));
611
+ }
612
+
613
+ // Normalize
614
+ const norm = Math.sqrt(newVector.reduce((sum, val) => sum + (val * val), 0));
615
+ if (norm > 1e-10) {
616
+ vector = newVector.map((val) => val / norm);
617
+ }
618
+ }
619
+
620
+ eigenvectors.push(vector);
621
+ eigenvalues.push(0.1); // Approximate
622
+ }
623
+
624
+ // For k = 3, add another eigenvector
625
+ if (k >= 3) {
626
+ let vector = Array(n).fill(0).map(() => Math.random() - 0.5);
627
+
628
+ // Orthogonalize against previous eigenvectors
629
+ for (const prev of eigenvectors) {
630
+ const dot = vector.reduce((sum, val, idx) => sum + (val * (prev[idx] ?? 0)), 0);
631
+ vector = vector.map((val, idx) => val - (dot * (prev[idx] ?? 0)));
632
+ }
633
+
634
+ // Similar power iteration
635
+ for (let iter = 0; iter < 50; iter++) {
636
+ const newVector = Array(n).fill(0) as number[];
637
+
638
+ // Identity part
639
+ for (let i = 0; i < n; i++) {
640
+ newVector[i] = vector[i] ?? 0;
641
+ }
642
+
643
+ // Subtract L * v / 2
644
+ for (let i = 0; i < n; i++) {
645
+ for (let j = 0; j < n; j++) {
646
+ const matrixVal = matrix[i]?.[j] ?? 0;
647
+ const vecVal = vector[j] ?? 0;
648
+ newVector[i] = (newVector[i] ?? 0) - ((matrixVal * vecVal) / 2);
649
+ }
650
+ }
651
+
652
+ // Orthogonalize
653
+ for (const prev of eigenvectors) {
654
+ const dot = newVector.reduce((sum, val, idx) => sum + (val * (prev[idx] ?? 0)), 0);
655
+ for (let i = 0; i < n; i++) {
656
+ newVector[i] = (newVector[i] ?? 0) - (dot * (prev[i] ?? 0));
657
+ }
658
+ }
659
+
660
+ // Normalize
661
+ const norm = Math.sqrt(newVector.reduce((sum, val) => sum + (val * val), 0));
662
+ if (norm > 1e-10) {
663
+ vector = newVector.map((val) => val / norm);
664
+ }
665
+ }
666
+
667
+ eigenvectors.push(vector);
668
+ eigenvalues.push(0.2); // Approximate
669
+ }
670
+
671
+ return {eigenvalues: eigenvalues.slice(0, k), eigenvectors: eigenvectors.slice(0, k)};
672
+ }