@graphty/algorithms 1.0.1 → 1.1.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 (273) hide show
  1. package/README.md +827 -60
  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 +447 -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 +1 -1
  138. package/dist/src/index.d.ts.map +1 -1
  139. package/dist/src/index.js.map +1 -1
  140. package/dist/src/link-prediction/adamic-adar.d.ts +56 -0
  141. package/dist/src/link-prediction/adamic-adar.d.ts.map +1 -0
  142. package/dist/src/link-prediction/adamic-adar.js +256 -0
  143. package/dist/src/link-prediction/adamic-adar.js.map +1 -0
  144. package/dist/src/link-prediction/common-neighbors.d.ts +50 -0
  145. package/dist/src/link-prediction/common-neighbors.d.ts.map +1 -0
  146. package/dist/src/link-prediction/common-neighbors.js +155 -0
  147. package/dist/src/link-prediction/common-neighbors.js.map +1 -0
  148. package/dist/src/link-prediction/index.d.ts +7 -0
  149. package/dist/src/link-prediction/index.d.ts.map +1 -0
  150. package/dist/src/link-prediction/index.js +6 -0
  151. package/dist/src/link-prediction/index.js.map +1 -0
  152. package/dist/src/pathfinding/astar.d.ts +50 -0
  153. package/dist/src/pathfinding/astar.d.ts.map +1 -0
  154. package/dist/src/pathfinding/astar.js +180 -0
  155. package/dist/src/pathfinding/astar.js.map +1 -0
  156. package/dist/src/pathfinding/index.d.ts +3 -0
  157. package/dist/src/pathfinding/index.d.ts.map +1 -0
  158. package/dist/src/pathfinding/index.js +3 -0
  159. package/dist/src/pathfinding/index.js.map +1 -0
  160. package/dist/src/pathfinding/utils.d.ts +18 -0
  161. package/dist/src/pathfinding/utils.d.ts.map +1 -0
  162. package/dist/src/pathfinding/utils.js +68 -0
  163. package/dist/src/pathfinding/utils.js.map +1 -0
  164. package/dist/src/types/index.d.ts +9 -0
  165. package/dist/src/types/index.d.ts.map +1 -1
  166. package/dist/src/utils/graphNode.d.ts +8 -0
  167. package/dist/src/utils/graphNode.d.ts.map +1 -0
  168. package/dist/src/utils/graphNode.js +2 -0
  169. package/dist/src/utils/graphNode.js.map +1 -0
  170. package/dist/src/utils/priorityQueue.d.ts +21 -0
  171. package/dist/src/utils/priorityQueue.d.ts.map +1 -0
  172. package/dist/src/utils/priorityQueue.js +101 -0
  173. package/dist/src/utils/priorityQueue.js.map +1 -0
  174. package/package.json +3 -1
  175. package/src/algorithms/centrality/betweenness.ts +25 -7
  176. package/src/algorithms/centrality/closeness.ts +7 -7
  177. package/src/algorithms/centrality/degree.ts +1 -1
  178. package/src/algorithms/centrality/eigenvector.ts +162 -0
  179. package/src/algorithms/centrality/hits.ts +210 -0
  180. package/src/algorithms/centrality/index.ts +6 -0
  181. package/src/algorithms/centrality/katz.ts +139 -0
  182. package/src/algorithms/centrality/pagerank.ts +3 -3
  183. package/src/algorithms/community/girvan-newman.ts +372 -0
  184. package/src/algorithms/community/index.ts +13 -0
  185. package/src/algorithms/community/label-propagation.ts +392 -0
  186. package/src/algorithms/community/leiden.ts +572 -0
  187. package/src/algorithms/community/louvain.ts +298 -0
  188. package/src/algorithms/components/connected.ts +7 -7
  189. package/src/algorithms/index.ts +21 -0
  190. package/src/algorithms/matching/bipartite.ts +179 -0
  191. package/src/algorithms/matching/index.ts +8 -0
  192. package/src/algorithms/matching/isomorphism.ts +392 -0
  193. package/src/algorithms/mst/index.ts +4 -0
  194. package/src/algorithms/mst/kruskal.ts +61 -0
  195. package/src/algorithms/mst/prim.ts +80 -0
  196. package/src/algorithms/shortest-path/bellman-ford.ts +2 -2
  197. package/src/algorithms/shortest-path/dijkstra.ts +5 -5
  198. package/src/algorithms/shortest-path/floyd-warshall.ts +170 -0
  199. package/src/algorithms/shortest-path/index.ts +2 -0
  200. package/src/algorithms/traversal/bfs.ts +2 -2
  201. package/src/algorithms/traversal/dfs.ts +11 -11
  202. package/src/clustering/hierarchical.ts +555 -0
  203. package/src/clustering/index.ts +6 -0
  204. package/src/clustering/k-core.ts +460 -0
  205. package/src/clustering/mcl.ts +454 -0
  206. package/src/clustering/spectral.ts +545 -0
  207. package/src/core/graph.ts +34 -6
  208. package/src/data-structures/union-find.ts +2 -2
  209. package/src/flow/ford-fulkerson.ts +459 -0
  210. package/src/flow/index.ts +2 -0
  211. package/src/flow/min-cut.ts +482 -0
  212. package/src/index.ts +2 -0
  213. package/src/link-prediction/adamic-adar.ts +353 -0
  214. package/src/link-prediction/common-neighbors.ts +252 -0
  215. package/src/link-prediction/index.ts +20 -0
  216. package/src/pathfinding/astar.ts +229 -0
  217. package/src/pathfinding/index.ts +2 -0
  218. package/src/pathfinding/utils.ts +87 -0
  219. package/src/types/index.ts +11 -0
  220. package/src/utils/graphNode.ts +7 -0
  221. package/src/utils/priorityQueue.ts +120 -0
  222. package/dist/test/browser/basic.test.d.ts +0 -2
  223. package/dist/test/browser/basic.test.d.ts.map +0 -1
  224. package/dist/test/browser/basic.test.js +0 -70
  225. package/dist/test/browser/basic.test.js.map +0 -1
  226. package/dist/test/unit/bellman-ford.test.d.ts +0 -2
  227. package/dist/test/unit/bellman-ford.test.d.ts.map +0 -1
  228. package/dist/test/unit/bellman-ford.test.js +0 -231
  229. package/dist/test/unit/bellman-ford.test.js.map +0 -1
  230. package/dist/test/unit/betweenness-centrality.test.d.ts +0 -2
  231. package/dist/test/unit/betweenness-centrality.test.d.ts.map +0 -1
  232. package/dist/test/unit/betweenness-centrality.test.js +0 -311
  233. package/dist/test/unit/betweenness-centrality.test.js.map +0 -1
  234. package/dist/test/unit/bfs.test.d.ts +0 -2
  235. package/dist/test/unit/bfs.test.d.ts.map +0 -1
  236. package/dist/test/unit/bfs.test.js +0 -277
  237. package/dist/test/unit/bfs.test.js.map +0 -1
  238. package/dist/test/unit/closeness.test.d.ts +0 -2
  239. package/dist/test/unit/closeness.test.d.ts.map +0 -1
  240. package/dist/test/unit/closeness.test.js +0 -266
  241. package/dist/test/unit/closeness.test.js.map +0 -1
  242. package/dist/test/unit/connected-components.test.d.ts +0 -2
  243. package/dist/test/unit/connected-components.test.d.ts.map +0 -1
  244. package/dist/test/unit/connected-components.test.js +0 -355
  245. package/dist/test/unit/connected-components.test.js.map +0 -1
  246. package/dist/test/unit/degree-centrality.test.d.ts +0 -2
  247. package/dist/test/unit/degree-centrality.test.d.ts.map +0 -1
  248. package/dist/test/unit/degree-centrality.test.js +0 -260
  249. package/dist/test/unit/degree-centrality.test.js.map +0 -1
  250. package/dist/test/unit/dfs.test.d.ts +0 -2
  251. package/dist/test/unit/dfs.test.d.ts.map +0 -1
  252. package/dist/test/unit/dfs.test.js +0 -350
  253. package/dist/test/unit/dfs.test.js.map +0 -1
  254. package/dist/test/unit/dijkstra.test.d.ts +0 -2
  255. package/dist/test/unit/dijkstra.test.d.ts.map +0 -1
  256. package/dist/test/unit/dijkstra.test.js +0 -311
  257. package/dist/test/unit/dijkstra.test.js.map +0 -1
  258. package/dist/test/unit/graph.test.d.ts +0 -2
  259. package/dist/test/unit/graph.test.d.ts.map +0 -1
  260. package/dist/test/unit/graph.test.js +0 -175
  261. package/dist/test/unit/graph.test.js.map +0 -1
  262. package/dist/test/unit/pagerank.test.d.ts +0 -2
  263. package/dist/test/unit/pagerank.test.d.ts.map +0 -1
  264. package/dist/test/unit/pagerank.test.js +0 -281
  265. package/dist/test/unit/pagerank.test.js.map +0 -1
  266. package/dist/test/unit/priority-queue.test.d.ts +0 -2
  267. package/dist/test/unit/priority-queue.test.d.ts.map +0 -1
  268. package/dist/test/unit/priority-queue.test.js +0 -189
  269. package/dist/test/unit/priority-queue.test.js.map +0 -1
  270. package/dist/test/unit/union-find.test.d.ts +0 -2
  271. package/dist/test/unit/union-find.test.d.ts.map +0 -1
  272. package/dist/test/unit/union-find.test.js +0 -245
  273. package/dist/test/unit/union-find.test.js.map +0 -1
@@ -0,0 +1,545 @@
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
+ // Normalize the eigenvector matrix row-wise (for normalized spectral clustering)
63
+ if (laplacianType === "normalized") {
64
+ normalizeRows(eigenResult.eigenvectors);
65
+ }
66
+
67
+ // Perform k-means clustering on the eigenvectors
68
+ const kmeans = kMeansClustering(eigenResult.eigenvectors, k, maxIterations);
69
+
70
+ // Build communities
71
+ const communities: NodeId[][] = Array.from({length: k}, () => []);
72
+ const clusterAssignments = new Map<NodeId, number>();
73
+
74
+ for (let i = 0; i < nodeIds.length; i++) {
75
+ const clusterId = kmeans.assignments[i] ?? 0;
76
+ const nodeId = nodeIds[i];
77
+ if (!nodeId) {
78
+ continue;
79
+ }
80
+
81
+ // Ensure clusterId is valid
82
+ if (clusterId >= 0 && clusterId < k) {
83
+ const community = communities[clusterId];
84
+ if (community) {
85
+ community.push(nodeId);
86
+ }
87
+
88
+ clusterAssignments.set(nodeId, clusterId);
89
+ } else {
90
+ // Assign to first cluster if invalid
91
+ const firstCommunity = communities[0];
92
+ if (firstCommunity) {
93
+ firstCommunity.push(nodeId);
94
+ }
95
+
96
+ clusterAssignments.set(nodeId, 0);
97
+ }
98
+ }
99
+
100
+ // Filter out empty communities
101
+ const nonEmptyCommunities = communities.filter((community) => community.length > 0);
102
+
103
+ return {
104
+ communities: nonEmptyCommunities,
105
+ clusterAssignments,
106
+ eigenvalues: eigenResult.eigenvalues,
107
+ eigenvectors: eigenResult.eigenvectors,
108
+ };
109
+ }
110
+
111
+ /**
112
+ * Build adjacency matrix from graph
113
+ */
114
+ function buildAdjacencyMatrix(graph: Graph, nodeIds: NodeId[]): number[][] {
115
+ const n = nodeIds.length;
116
+ const matrix: number[][] = Array.from({length: n}, () => Array(n).fill(0) as number[]);
117
+ const nodeToIndex = new Map<NodeId, number>();
118
+
119
+ nodeIds.forEach((id, index) => nodeToIndex.set(id, index));
120
+
121
+ for (let i = 0; i < n; i++) {
122
+ const nodeId = nodeIds[i];
123
+ if (!nodeId) {
124
+ continue;
125
+ }
126
+
127
+ const neighbors = graph.neighbors(nodeId);
128
+
129
+ for (const neighbor of neighbors) {
130
+ const j = nodeToIndex.get(neighbor);
131
+ if (j !== undefined) {
132
+ const edge = graph.getEdge(nodeId, neighbor);
133
+ const weight = edge?.weight ?? 1;
134
+ const matrixRow = matrix[i];
135
+ if (matrixRow) {
136
+ matrixRow[j] = weight;
137
+ }
138
+
139
+ if (!graph.isDirected) {
140
+ const matrixRowJ = matrix[j];
141
+ if (matrixRowJ) {
142
+ matrixRowJ[i] = weight;
143
+ }
144
+ }
145
+ }
146
+ }
147
+ }
148
+
149
+ return matrix;
150
+ }
151
+
152
+ /**
153
+ * Build Laplacian matrix from adjacency matrix
154
+ */
155
+ function buildLaplacianMatrix(adjacency: number[][], type: string): number[][] {
156
+ const n = adjacency.length;
157
+ const laplacian: number[][] = Array.from({length: n}, () => Array(n).fill(0) as number[]);
158
+
159
+ // Calculate degree matrix
160
+ const degrees = Array(n).fill(0) as number[];
161
+ for (let i = 0; i < n; i++) {
162
+ for (let j = 0; j < n; j++) {
163
+ const adjacencyRow = adjacency[i];
164
+ const adjacencyVal = adjacencyRow ? adjacencyRow[j] : 0;
165
+ if (adjacencyVal !== undefined) {
166
+ const degreeVal = degrees[i];
167
+ if (degreeVal !== undefined) {
168
+ degrees[i] = degreeVal + adjacencyVal;
169
+ }
170
+ }
171
+ }
172
+ }
173
+
174
+ if (type === "unnormalized") {
175
+ // L = D - A
176
+ for (let i = 0; i < n; i++) {
177
+ for (let j = 0; j < n; j++) {
178
+ if (i === j) {
179
+ const laplacianRow = laplacian[i];
180
+ const degreeVal = degrees[i];
181
+ if (laplacianRow && degreeVal !== undefined) {
182
+ laplacianRow[j] = degreeVal;
183
+ }
184
+ } else {
185
+ const adjacencyRow = adjacency[i];
186
+ const laplacianRow = laplacian[i];
187
+ if (adjacencyRow && laplacianRow) {
188
+ const adjacencyVal = adjacencyRow[j];
189
+ laplacianRow[j] = adjacencyVal !== undefined ? -adjacencyVal : 0;
190
+ }
191
+ }
192
+ }
193
+ }
194
+ } else if (type === "normalized") {
195
+ // L_sym = D^(-1/2) * L * D^(-1/2)
196
+ for (let i = 0; i < n; i++) {
197
+ for (let j = 0; j < n; j++) {
198
+ if (i === j) {
199
+ const di = degrees[i];
200
+ const laplacianRow = laplacian[i];
201
+ if (laplacianRow) {
202
+ laplacianRow[j] = di !== undefined && di > 0 ? 1 : 0;
203
+ }
204
+ } else {
205
+ const adjacencyRow = adjacency[i];
206
+ if (!adjacencyRow) {
207
+ continue;
208
+ }
209
+
210
+ const adjacencyVal = adjacencyRow[j];
211
+ const di = degrees[i];
212
+ const dj = degrees[j];
213
+ if (adjacencyVal !== undefined && adjacencyVal > 0 && di !== undefined && dj !== undefined && di > 0 && dj > 0) {
214
+ const laplacianRow = laplacian[i];
215
+ if (laplacianRow) {
216
+ laplacianRow[j] = -adjacencyVal / Math.sqrt(di * dj);
217
+ }
218
+ }
219
+ }
220
+ }
221
+ }
222
+ } else if (type === "randomWalk") {
223
+ // L_rw = D^(-1) * L
224
+ for (let i = 0; i < n; i++) {
225
+ for (let j = 0; j < n; j++) {
226
+ if (i === j) {
227
+ const di = degrees[i];
228
+ const laplacianRow = laplacian[i];
229
+ if (laplacianRow) {
230
+ laplacianRow[j] = di !== undefined && di > 0 ? 1 : 0;
231
+ }
232
+ } else {
233
+ const adjacencyRow = adjacency[i];
234
+ if (!adjacencyRow) {
235
+ continue;
236
+ }
237
+
238
+ const adjacencyVal = adjacencyRow[j];
239
+ const di = degrees[i];
240
+ if (adjacencyVal !== undefined && adjacencyVal > 0 && di !== undefined && di > 0) {
241
+ const laplacianRow = laplacian[i];
242
+ if (laplacianRow) {
243
+ laplacianRow[j] = -adjacencyVal / di;
244
+ }
245
+ }
246
+ }
247
+ }
248
+ }
249
+ }
250
+
251
+ return laplacian;
252
+ }
253
+
254
+ /**
255
+ * Find k smallest eigenvectors using simplified eigendecomposition
256
+ * This is a simplified implementation - in practice, you'd use LAPACK or similar
257
+ */
258
+ function findSmallestEigenvectors(matrix: number[][], k: number): {
259
+ eigenvalues: number[];
260
+ eigenvectors: number[][];
261
+ } {
262
+ const n = matrix.length;
263
+
264
+ if (n === 0 || k === 0) {
265
+ return {eigenvalues: [], eigenvectors: []};
266
+ }
267
+
268
+ // For small matrices or when k >= n, use a simplified approach
269
+ if (n <= 3 || k >= n) {
270
+ // Create identity-like eigenvectors for simplicity
271
+ const eigenvectors: number[][] = [];
272
+ const eigenvalues: number[] = [];
273
+
274
+ for (let i = 0; i < Math.min(k, n); i++) {
275
+ const vector = Array(n).fill(0) as number[];
276
+ vector[i] = 1;
277
+ eigenvectors.push(vector);
278
+ eigenvalues.push(i);
279
+ }
280
+
281
+ return {eigenvalues, eigenvectors};
282
+ }
283
+
284
+ // For larger matrices, use power iteration to find dominant eigenvectors
285
+ // This is still simplified but better than random
286
+ const eigenvectors: number[][] = [];
287
+ const eigenvalues: number[] = [];
288
+ const maxIterations = 50;
289
+
290
+ for (let eigIdx = 0; eigIdx < k; eigIdx++) {
291
+ // Initialize random vector
292
+ let vector = Array(n).fill(0).map(() => Math.random() - 0.5);
293
+
294
+ // Orthogonalize against previous eigenvectors
295
+ for (let j = 0; j < eigIdx; j++) {
296
+ const ejVector = eigenvectors[j];
297
+ if (!ejVector) {
298
+ continue;
299
+ }
300
+
301
+ const dot = vector.reduce((sum, val, idx) => sum + (val * (ejVector[idx] ?? 0)), 0);
302
+ vector = vector.map((val, idx) => val - (dot * (ejVector[idx] ?? 0)));
303
+ }
304
+
305
+ // Power iteration
306
+ for (let iter = 0; iter < maxIterations; iter++) {
307
+ // Multiply by matrix
308
+ const newVector = Array(n).fill(0) as number[];
309
+ for (let i = 0; i < n; i++) {
310
+ for (let j = 0; j < n; j++) {
311
+ const matrixRow = matrix[i];
312
+ const vecVal = vector[j];
313
+ const matrixVal = matrixRow?.[j];
314
+ if (matrixVal !== undefined && vecVal !== undefined) {
315
+ const nvVal = newVector[i];
316
+ if (nvVal !== undefined) {
317
+ newVector[i] = nvVal + (matrixVal * vecVal);
318
+ }
319
+ }
320
+ }
321
+ }
322
+
323
+ // Orthogonalize against previous eigenvectors
324
+ for (let j = 0; j < eigIdx; j++) {
325
+ const ejVector = eigenvectors[j];
326
+ if (!ejVector) {
327
+ continue;
328
+ }
329
+
330
+ const dot = newVector.reduce((sum, val, idx) => sum + (val * (ejVector[idx] ?? 0)), 0);
331
+ for (let i = 0; i < n; i++) {
332
+ const ejVal = ejVector[i];
333
+ if (ejVal !== undefined) {
334
+ const nvVal = newVector[i];
335
+ if (nvVal !== undefined) {
336
+ newVector[i] = nvVal - (dot * ejVal);
337
+ }
338
+ }
339
+ }
340
+ }
341
+
342
+ // Normalize
343
+ const norm = Math.sqrt(newVector.reduce((sum, val) => sum + ((val * val)), 0));
344
+ if (norm > 1e-10) {
345
+ vector = newVector.map((val) => val / norm);
346
+ } else {
347
+ break;
348
+ }
349
+ }
350
+
351
+ // Calculate eigenvalue (Rayleigh quotient)
352
+ let eigenvalue = 0;
353
+ const Av = Array(n).fill(0) as number[];
354
+ for (let i = 0; i < n; i++) {
355
+ for (let j = 0; j < n; j++) {
356
+ const matrixRow = matrix[i];
357
+ const vecVal = vector[j];
358
+ const matrixVal = matrixRow?.[j];
359
+ if (matrixVal !== undefined && vecVal !== undefined) {
360
+ const avVal = Av[i];
361
+ if (avVal !== undefined) {
362
+ Av[i] = avVal + (matrixVal * vecVal);
363
+ }
364
+ }
365
+ }
366
+ }
367
+ eigenvalue = vector.reduce((sum, val, idx) => {
368
+ const avVal = Av[idx];
369
+ return sum + (val * (avVal ?? 0));
370
+ }, 0);
371
+
372
+ eigenvectors.push(vector);
373
+ eigenvalues.push(eigenvalue);
374
+ }
375
+
376
+ return {eigenvalues, eigenvectors};
377
+ }
378
+
379
+ /**
380
+ * Normalize matrix rows to unit length
381
+ */
382
+ function normalizeRows(matrix: number[][]): void {
383
+ for (const row of matrix) {
384
+ const norm = Math.sqrt(row.reduce((sum, val) => sum + (val * val), 0));
385
+ if (norm > 0) {
386
+ for (let j = 0; j < row.length; j++) {
387
+ const val = row[j];
388
+ if (val !== undefined) {
389
+ row[j] = val / norm;
390
+ }
391
+ }
392
+ }
393
+ }
394
+ }
395
+
396
+ /**
397
+ * K-means clustering algorithm
398
+ */
399
+ function kMeansClustering(
400
+ data: number[][],
401
+ k: number,
402
+ maxIterations: number,
403
+ ): {assignments: number[], centroids: number[][]} {
404
+ const n = data.length;
405
+ const d = data[0]?.length ?? 0;
406
+
407
+ // Handle edge cases
408
+ if (n === 0 || k === 0) {
409
+ return {assignments: [], centroids: []};
410
+ }
411
+
412
+ if (k >= n) {
413
+ // Each point is its own cluster
414
+ return {
415
+ assignments: Array.from({length: n}, (_, i) => i),
416
+ centroids: data.slice(0, n),
417
+ };
418
+ }
419
+
420
+ // Initialize centroids by selecting random data points
421
+ const centroids: number[][] = [];
422
+ const selectedIndices = new Set<number>();
423
+
424
+ while (centroids.length < k && selectedIndices.size < n) {
425
+ const idx = Math.floor(Math.random() * n);
426
+ if (!selectedIndices.has(idx) && data[idx]) {
427
+ selectedIndices.add(idx);
428
+ centroids.push([... data[idx]]);
429
+ }
430
+ }
431
+
432
+ // Fill remaining centroids with random values if needed
433
+ while (centroids.length < k) {
434
+ const centroid = Array(d).fill(0) as number[];
435
+ for (let j = 0; j < d; j++) {
436
+ centroid[j] = Math.random() - 0.5;
437
+ }
438
+ centroids.push(centroid);
439
+ }
440
+
441
+ const assignments = Array(n).fill(0) as number[];
442
+ let oldAssignments = Array(n).fill(-1) as number[];
443
+
444
+ for (let iteration = 0; iteration < maxIterations; iteration++) {
445
+ // Assign points to closest centroids
446
+ for (let i = 0; i < n; i++) {
447
+ let minDistance = Number.POSITIVE_INFINITY;
448
+ let bestCluster = 0;
449
+
450
+ for (let j = 0; j < k; j++) {
451
+ const dataPoint = data[i];
452
+ const centroid = centroids[j];
453
+ if (!dataPoint || !centroid) {
454
+ continue;
455
+ }
456
+
457
+ const distance = euclideanDistance(dataPoint, centroid);
458
+ if (distance < minDistance) {
459
+ minDistance = distance;
460
+ bestCluster = j;
461
+ }
462
+ }
463
+
464
+ assignments[i] = bestCluster;
465
+ }
466
+
467
+ // Check for convergence
468
+ let changed = false;
469
+ for (let i = 0; i < n; i++) {
470
+ if (assignments[i] !== oldAssignments[i]) {
471
+ changed = true;
472
+ break;
473
+ }
474
+ }
475
+
476
+ if (!changed) {
477
+ break;
478
+ }
479
+
480
+ oldAssignments = [... assignments];
481
+
482
+ // Update centroids
483
+ const counts = Array(k).fill(0) as number[];
484
+ const sums: number[][] = Array.from({length: k}, () => Array(d).fill(0) as number[]);
485
+
486
+ for (let i = 0; i < n; i++) {
487
+ const cluster = assignments[i] ?? 0;
488
+ const dataPoint = data[i];
489
+ if (!dataPoint) {
490
+ continue;
491
+ }
492
+
493
+ const sumsCluster = sums[cluster];
494
+ if (sumsCluster !== undefined) {
495
+ const countVal = counts[cluster];
496
+ if (countVal !== undefined) {
497
+ counts[cluster] = countVal + 1;
498
+ }
499
+
500
+ for (let j = 0; j < d; j++) {
501
+ const dpVal = dataPoint[j];
502
+ if (dpVal !== undefined) {
503
+ const sumVal = sumsCluster[j];
504
+ if (sumVal !== undefined) {
505
+ sumsCluster[j] = sumVal + dpVal;
506
+ }
507
+ }
508
+ }
509
+ }
510
+ }
511
+
512
+ for (let i = 0; i < k; i++) {
513
+ const countVal = counts[i];
514
+ if (countVal !== undefined && countVal > 0) {
515
+ const sumsRow = sums[i];
516
+ const centroidsRow = centroids[i];
517
+ if (sumsRow !== undefined && centroidsRow !== undefined) {
518
+ for (let j = 0; j < d; j++) {
519
+ const sumVal = sumsRow[j];
520
+ if (sumVal !== undefined) {
521
+ const countVal = counts[i];
522
+ if (countVal !== undefined) {
523
+ centroidsRow[j] = sumVal / countVal;
524
+ }
525
+ }
526
+ }
527
+ }
528
+ }
529
+ }
530
+ }
531
+
532
+ return {assignments, centroids};
533
+ }
534
+
535
+ /**
536
+ * Calculate Euclidean distance between two vectors
537
+ */
538
+ function euclideanDistance(a: number[], b: number[]): number {
539
+ let sum = 0;
540
+ for (let i = 0; i < a.length; i++) {
541
+ const diff = (a[i] ?? 0) - (b[i] ?? 0);
542
+ sum += diff * diff;
543
+ }
544
+ return Math.sqrt(sum);
545
+ }
package/src/core/graph.ts CHANGED
@@ -51,7 +51,7 @@ export class Graph {
51
51
  // Remove all edges connected to this node
52
52
  const outgoingEdges = this.adjacencyList.get(id);
53
53
  if (outgoingEdges) {
54
- for (const targetId of outgoingEdges.keys()) {
54
+ for (const targetId of Array.from(outgoingEdges.keys())) {
55
55
  this.removeEdge(id, targetId);
56
56
  }
57
57
  }
@@ -59,13 +59,13 @@ export class Graph {
59
59
  if (this.config.directed) {
60
60
  const incomingEdges = this.incomingEdges.get(id);
61
61
  if (incomingEdges) {
62
- for (const sourceId of incomingEdges.keys()) {
62
+ for (const sourceId of Array.from(incomingEdges.keys())) {
63
63
  this.removeEdge(sourceId, id);
64
64
  }
65
65
  }
66
66
  } else {
67
67
  // For undirected graphs, also remove edges where this node is the target
68
- for (const [nodeId, edges] of this.adjacencyList) {
68
+ for (const [nodeId, edges] of Array.from(this.adjacencyList)) {
69
69
  if (edges.has(id)) {
70
70
  this.removeEdge(nodeId, id);
71
71
  }
@@ -222,7 +222,7 @@ export class Graph {
222
222
  * Get all edges in the graph
223
223
  */
224
224
  *edges(): IterableIterator<Edge> {
225
- for (const [source, edges] of this.adjacencyList) {
225
+ for (const [source, edges] of Array.from(this.adjacencyList)) {
226
226
  for (const edge of edges.values()) {
227
227
  // For undirected graphs, only yield each edge once
228
228
  if (!this.config.directed && source > edge.target) {
@@ -300,12 +300,12 @@ export class Graph {
300
300
  const cloned = new Graph(this.config);
301
301
 
302
302
  // Copy nodes
303
- for (const node of this.nodeMap.values()) {
303
+ for (const node of Array.from(this.nodeMap.values())) {
304
304
  cloned.addNode(node.id, node.data ? {... node.data} : undefined);
305
305
  }
306
306
 
307
307
  // Copy edges
308
- for (const edge of this.edges()) {
308
+ for (const edge of Array.from(this.edges())) {
309
309
  cloned.addEdge(
310
310
  edge.source,
311
311
  edge.target,
@@ -323,4 +323,32 @@ export class Graph {
323
323
  getConfig(): GraphConfig {
324
324
  return {... this.config};
325
325
  }
326
+
327
+ /**
328
+ * Clear all nodes and edges from the graph
329
+ */
330
+ clear(): void {
331
+ this.nodeMap.clear();
332
+ this.adjacencyList.clear();
333
+ this.incomingEdges.clear();
334
+ this.edgeCount = 0;
335
+ }
336
+
337
+ /**
338
+ * Get the number of unique edges in the graph
339
+ * For undirected graphs, each edge is counted once
340
+ */
341
+ get uniqueEdgeCount(): number {
342
+ if (this.config.directed) {
343
+ return this.edgeCount;
344
+ }
345
+
346
+ // For undirected graphs, we need to count each edge only once
347
+ let count = 0;
348
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
349
+ for (const _edge of Array.from(this.edges())) {
350
+ count++;
351
+ }
352
+ return count;
353
+ }
326
354
  }
@@ -103,7 +103,7 @@ export class UnionFind {
103
103
  const root = this.find(element);
104
104
  const component: NodeId[] = [];
105
105
 
106
- for (const [node] of this.parent) {
106
+ for (const [node] of Array.from(this.parent)) {
107
107
  if (this.find(node) === root) {
108
108
  component.push(node);
109
109
  }
@@ -118,7 +118,7 @@ export class UnionFind {
118
118
  getAllComponents(): NodeId[][] {
119
119
  const componentMap = new Map<NodeId, NodeId[]>();
120
120
 
121
- for (const [node] of this.parent) {
121
+ for (const [node] of Array.from(this.parent)) {
122
122
  const root = this.find(node);
123
123
 
124
124
  if (!componentMap.has(root)) {