@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,555 @@
1
+ /**
2
+ * Hierarchical Clustering Algorithm
3
+ *
4
+ * Builds a hierarchy of clusters through agglomeration (bottom-up)
5
+ * or division (top-down). Useful for understanding multi-scale
6
+ * structure in graphs.
7
+ */
8
+
9
+ export interface ClusterNode<T> {
10
+ id: string;
11
+ members: Set<T>;
12
+ left?: ClusterNode<T> | undefined;
13
+ right?: ClusterNode<T> | undefined;
14
+ distance: number;
15
+ height: number;
16
+ // For forest roots, store the individual trees
17
+ trees?: ClusterNode<T>[] | undefined;
18
+ }
19
+
20
+ export interface HierarchicalClusteringResult<T> {
21
+ root: ClusterNode<T>;
22
+ dendrogram: ClusterNode<T>[];
23
+ clusters: Map<number, Set<T>[]>;
24
+ }
25
+
26
+ export type LinkageMethod = "single" | "complete" | "average" | "ward";
27
+
28
+ /**
29
+ * Compute distance between two nodes based on graph structure
30
+ */
31
+ function computeGraphDistance<T>(
32
+ graph: Map<T, Set<T>>,
33
+ distances: Map<T, Map<T, number>>,
34
+ ): void {
35
+ // Use BFS to compute shortest path distances
36
+ for (const start of graph.keys()) {
37
+ const dist = new Map<T, number>();
38
+ const queue: T[] = [start];
39
+ dist.set(start, 0);
40
+
41
+ while (queue.length > 0) {
42
+ const node = queue.shift();
43
+ if (!node) {
44
+ continue;
45
+ }
46
+
47
+ const neighbors = graph.get(node);
48
+
49
+ if (neighbors) {
50
+ for (const neighbor of neighbors) {
51
+ if (!dist.has(neighbor)) {
52
+ const nodeDistance = dist.get(node);
53
+ if (nodeDistance !== undefined) {
54
+ dist.set(neighbor, nodeDistance + 1);
55
+ }
56
+
57
+ queue.push(neighbor);
58
+ }
59
+ }
60
+ }
61
+ }
62
+
63
+ distances.set(start, dist);
64
+ }
65
+ }
66
+
67
+ /**
68
+ * Compute distance between clusters based on linkage method
69
+ */
70
+ function clusterDistance<T>(
71
+ cluster1: Set<T>,
72
+ cluster2: Set<T>,
73
+ distances: Map<T, Map<T, number>>,
74
+ method: LinkageMethod,
75
+ ): number {
76
+ if (cluster1.size === 0 || cluster2.size === 0) {
77
+ return Infinity;
78
+ }
79
+
80
+ const allDistances: number[] = [];
81
+
82
+ for (const node1 of cluster1) {
83
+ for (const node2 of cluster2) {
84
+ const dist = distances.get(node1)?.get(node2) ?? Infinity;
85
+ allDistances.push(dist);
86
+ }
87
+ }
88
+
89
+ switch (method) {
90
+ case "single":
91
+ return Math.min(... allDistances);
92
+ case "complete":
93
+ return Math.max(... allDistances);
94
+ case "average":
95
+ return allDistances.reduce((a, b) => a + b, 0) / allDistances.length;
96
+ case "ward":
97
+ // Ward's method minimizes within-cluster variance
98
+ // For simplicity, we use average here
99
+ return allDistances.reduce((a, b) => a + b, 0) / allDistances.length;
100
+ default:
101
+ return Math.min(... allDistances);
102
+ }
103
+ }
104
+
105
+ /**
106
+ * Agglomerative hierarchical clustering
107
+ * Builds clusters bottom-up by merging closest pairs
108
+ *
109
+ * @param graph - Undirected graph
110
+ * @param linkage - Linkage method for cluster distance
111
+ * @returns Hierarchical clustering result
112
+ *
113
+ * Time Complexity: O(n³) naive, O(n² log n) with heap
114
+ * Space Complexity: O(n²)
115
+ */
116
+ export function hierarchicalClustering<T>(
117
+ graph: Map<T, Set<T>>,
118
+ linkage: LinkageMethod = "single",
119
+ ): HierarchicalClusteringResult<T> {
120
+ const nodes = Array.from(graph.keys());
121
+ const n = nodes.length;
122
+
123
+ if (n === 0) {
124
+ return {
125
+ root: {id: "empty", members: new Set(), distance: 0, height: 0},
126
+ dendrogram: [],
127
+ clusters: new Map(),
128
+ };
129
+ }
130
+
131
+ // Compute pairwise distances
132
+ const distances = new Map<T, Map<T, number>>();
133
+ computeGraphDistance(graph, distances);
134
+
135
+ // Initialize clusters - each node is its own cluster
136
+ const clusters: ClusterNode<T>[] = nodes.map((node, i) => ({
137
+ id: `leaf-${String(i)}`,
138
+ members: new Set([node]),
139
+ distance: 0,
140
+ height: 0,
141
+ }));
142
+
143
+ const dendrogram: ClusterNode<T>[] = [... clusters];
144
+ let clusterCount = n;
145
+
146
+ // Distance matrix between clusters
147
+ const clusterDistances = new Map<string, Map<string, number>>();
148
+
149
+ // Initialize cluster distances
150
+ for (let i = 0; i < clusters.length; i++) {
151
+ const clusterI = clusters[i];
152
+ if (!clusterI) {
153
+ continue;
154
+ }
155
+
156
+ clusterDistances.set(clusterI.id, new Map());
157
+ for (let j = i + 1; j < clusters.length; j++) {
158
+ const clusterJ = clusters[j];
159
+ if (!clusterJ) {
160
+ continue;
161
+ }
162
+
163
+ const dist = clusterDistance(
164
+ clusterI.members,
165
+ clusterJ.members,
166
+ distances,
167
+ linkage,
168
+ );
169
+ const clusterIDistances = clusterDistances.get(clusterI.id);
170
+ if (clusterIDistances) {
171
+ clusterIDistances.set(clusterJ.id, dist);
172
+ }
173
+ }
174
+ }
175
+
176
+ // Merge clusters until only one remains
177
+ while (clusters.length > 1) {
178
+ // Find closest pair of clusters
179
+ let minDist = Infinity;
180
+ let merge1 = 0;
181
+ let merge2 = 0;
182
+
183
+ for (let i = 0; i < clusters.length; i++) {
184
+ for (let j = i + 1; j < clusters.length; j++) {
185
+ const clusterI = clusters[i];
186
+ const clusterJ = clusters[j];
187
+ if (!clusterI || !clusterJ) {
188
+ continue;
189
+ }
190
+
191
+ const dist = clusterDistances.get(clusterI.id)?.get(clusterJ.id) ??
192
+ clusterDistances.get(clusterJ.id)?.get(clusterI.id) ??
193
+ Infinity;
194
+
195
+ if (dist < minDist) {
196
+ minDist = dist;
197
+ merge1 = i;
198
+ merge2 = j;
199
+ }
200
+ }
201
+ }
202
+
203
+ // Check if we found a valid merge
204
+ if (minDist === Infinity || merge1 === merge2) {
205
+ // No more clusters can be merged (disconnected components)
206
+ break;
207
+ }
208
+
209
+ // Create new cluster
210
+ const cluster1 = clusters[merge1];
211
+ const cluster2 = clusters[merge2];
212
+ if (!cluster1 || !cluster2) {
213
+ continue;
214
+ }
215
+
216
+ const newCluster: ClusterNode<T> = {
217
+ id: `cluster-${String(clusterCount++)}`,
218
+ members: new Set([... cluster1.members, ... cluster2.members]),
219
+ left: cluster1,
220
+ right: cluster2,
221
+ distance: minDist,
222
+ height: Math.max(cluster1.height, cluster2.height) + 1,
223
+ };
224
+
225
+ dendrogram.push(newCluster);
226
+
227
+ // Update cluster distances for new cluster
228
+ clusterDistances.set(newCluster.id, new Map());
229
+
230
+ for (let i = 0; i < clusters.length; i++) {
231
+ if (i !== merge1 && i !== merge2) {
232
+ const clusterI = clusters[i];
233
+ if (!clusterI) {
234
+ continue;
235
+ }
236
+
237
+ const newDist = clusterDistance(
238
+ newCluster.members,
239
+ clusterI.members,
240
+ distances,
241
+ linkage,
242
+ );
243
+ const newClusterDistances = clusterDistances.get(newCluster.id);
244
+ if (newClusterDistances) {
245
+ newClusterDistances.set(clusterI.id, newDist);
246
+ }
247
+ }
248
+ }
249
+
250
+ // Remove old clusters and their distances
251
+ clusterDistances.delete(cluster1.id);
252
+ clusterDistances.delete(cluster2.id);
253
+ for (const [, dists] of clusterDistances) {
254
+ dists.delete(cluster1.id);
255
+ dists.delete(cluster2.id);
256
+ }
257
+
258
+ // Remove merged clusters and add new one
259
+ clusters.splice(Math.max(merge1, merge2), 1);
260
+ clusters.splice(Math.min(merge1, merge2), 1);
261
+ clusters.push(newCluster);
262
+ }
263
+
264
+ // Generate clusters at different levels
265
+ const clustersByLevel = new Map<number, Set<T>[]>();
266
+
267
+ function extractClustersAtHeight(node: ClusterNode<T>, height: number, clusters: Set<T>[]): void {
268
+ if (node.height <= height || !node.left || !node.right) {
269
+ clusters.push(node.members);
270
+ } else {
271
+ extractClustersAtHeight(node.left, height, clusters);
272
+ extractClustersAtHeight(node.right, height, clusters);
273
+ }
274
+ }
275
+
276
+ // Handle the case where we have multiple disconnected components
277
+ let root: ClusterNode<T>;
278
+ if (clusters.length === 0) {
279
+ return {
280
+ root: {id: "empty", members: new Set(), distance: 0, height: 0},
281
+ dendrogram: [],
282
+ clusters: new Map(),
283
+ };
284
+ } else if (clusters.length === 1 && clusters[0]) {
285
+ root = clusters[0];
286
+ } else {
287
+ // Create a root that combines all remaining clusters (forest)
288
+ const allMembers = new Set<T>();
289
+ let maxHeight = 0;
290
+ for (const cluster of clusters) {
291
+ for (const member of cluster.members) {
292
+ allMembers.add(member);
293
+ }
294
+ maxHeight = Math.max(maxHeight, cluster.height);
295
+ }
296
+
297
+ root = {
298
+ id: "root-forest",
299
+ members: allMembers,
300
+ distance: Infinity,
301
+ height: maxHeight + 1,
302
+ // Store references to the individual trees
303
+ trees: [... clusters],
304
+ };
305
+
306
+ dendrogram.push(root);
307
+ }
308
+
309
+ for (let h = 0; h <= root.height; h++) {
310
+ const clustersAtHeight: Set<T>[] = [];
311
+
312
+ if (clusters.length > 1 && h === root.height) {
313
+ // At forest root height, return single cluster with all members
314
+ clustersAtHeight.push(root.members);
315
+ } else if (clusters.length > 1 && h < root.height) {
316
+ // For forest, extract from each tree separately
317
+ for (const tree of clusters) {
318
+ extractClustersAtHeight(tree, h, clustersAtHeight);
319
+ }
320
+ } else {
321
+ extractClustersAtHeight(root, h, clustersAtHeight);
322
+ }
323
+
324
+ clustersByLevel.set(h, clustersAtHeight);
325
+ }
326
+
327
+ return {
328
+ root,
329
+ dendrogram,
330
+ clusters: clustersByLevel,
331
+ };
332
+ }
333
+
334
+ /**
335
+ * Cut dendrogram at specified height to get clusters
336
+ */
337
+ export function cutDendrogram<T>(
338
+ root: ClusterNode<T>,
339
+ height: number,
340
+ ): Set<T>[] {
341
+ const clusters: Set<T>[] = [];
342
+
343
+ function traverse(node: ClusterNode<T>): void {
344
+ // Handle forest root specially
345
+ if (node.trees) {
346
+ if (node.height <= height) {
347
+ // If we're at or below the requested height, return the whole forest as one cluster
348
+ clusters.push(node.members);
349
+ } else {
350
+ // Otherwise, traverse each tree in the forest
351
+ for (const tree of node.trees) {
352
+ traverse(tree);
353
+ }
354
+ }
355
+
356
+ return;
357
+ }
358
+
359
+ // Normal binary tree traversal
360
+ if (node.height <= height || !node.left || !node.right) {
361
+ clusters.push(node.members);
362
+ } else {
363
+ traverse(node.left);
364
+ traverse(node.right);
365
+ }
366
+ }
367
+
368
+ traverse(root);
369
+ return clusters;
370
+ }
371
+
372
+ /**
373
+ * Cut dendrogram to get k clusters
374
+ */
375
+ export function cutDendrogramKClusters<T>(
376
+ root: ClusterNode<T>,
377
+ k: number,
378
+ ): Set<T>[] {
379
+ if (k <= 0) {
380
+ return [];
381
+ }
382
+
383
+ if (k === 1) {
384
+ return [root.members];
385
+ }
386
+
387
+ // Find the height that gives us k clusters
388
+ let low = 0;
389
+ let high = root.height;
390
+
391
+ while (low < high) {
392
+ const mid = Math.floor((low + high) / 2);
393
+ const clusters = cutDendrogram(root, mid);
394
+
395
+ if (clusters.length === k) {
396
+ return clusters;
397
+ } else if (clusters.length < k) {
398
+ high = mid;
399
+ } else {
400
+ low = mid + 1;
401
+ }
402
+ }
403
+
404
+ return cutDendrogram(root, low);
405
+ }
406
+
407
+ /**
408
+ * Compute modularity-based hierarchical clustering
409
+ * Uses modularity gain to decide merges
410
+ */
411
+ export function modularityHierarchicalClustering<T>(
412
+ graph: Map<T, Set<T>>,
413
+ ): HierarchicalClusteringResult<T> {
414
+ const nodes = Array.from(graph.keys());
415
+ const n = nodes.length;
416
+
417
+ if (n === 0) {
418
+ return {
419
+ root: {id: "empty", members: new Set(), distance: 0, height: 0},
420
+ dendrogram: [],
421
+ clusters: new Map(),
422
+ };
423
+ }
424
+
425
+ // Calculate total edges
426
+ let m = 0;
427
+ for (const neighbors of graph.values()) {
428
+ m += neighbors.size;
429
+ }
430
+ m = m / 2; // Each edge counted twice
431
+
432
+ // Initialize clusters
433
+ const clusters: ClusterNode<T>[] = nodes.map((node, i) => ({
434
+ id: `leaf-${String(i)}`,
435
+ members: new Set([node]),
436
+ distance: 0,
437
+ height: 0,
438
+ }));
439
+
440
+ const dendrogram: ClusterNode<T>[] = [... clusters];
441
+ let clusterCount = n;
442
+
443
+ // Track community assignments
444
+ const communityMap = new Map<T, number>();
445
+ nodes.forEach((node, i) => communityMap.set(node, i));
446
+
447
+ // Compute modularity gain for merging communities
448
+ function modularityGain(comm1: Set<T>, comm2: Set<T>): number {
449
+ let edgesBetween = 0;
450
+ let degree1 = 0;
451
+ let degree2 = 0;
452
+
453
+ for (const node1 of comm1) {
454
+ const neighbors = graph.get(node1);
455
+ if (!neighbors) {
456
+ continue;
457
+ }
458
+
459
+ degree1 += neighbors.size;
460
+
461
+ for (const neighbor of neighbors) {
462
+ if (comm2.has(neighbor)) {
463
+ edgesBetween++;
464
+ }
465
+ }
466
+ }
467
+
468
+ for (const node2 of comm2) {
469
+ const node2Neighbors = graph.get(node2);
470
+ if (node2Neighbors) {
471
+ degree2 += node2Neighbors.size;
472
+ }
473
+ }
474
+
475
+ // Modularity gain formula
476
+ const gain = (edgesBetween / m) - ((degree1 * degree2) / (4 * m * m));
477
+ return gain;
478
+ }
479
+
480
+ // Merge clusters based on modularity
481
+ while (clusters.length > 1) {
482
+ let maxGain = -Infinity;
483
+ let merge1 = 0;
484
+ let merge2 = 0;
485
+
486
+ for (let i = 0; i < clusters.length; i++) {
487
+ for (let j = i + 1; j < clusters.length; j++) {
488
+ const clusterI = clusters[i];
489
+ const clusterJ = clusters[j];
490
+ if (!clusterI || !clusterJ) {
491
+ continue;
492
+ }
493
+
494
+ const gain = modularityGain(clusterI.members, clusterJ.members);
495
+
496
+ if (gain > maxGain) {
497
+ maxGain = gain;
498
+ merge1 = i;
499
+ merge2 = j;
500
+ }
501
+ }
502
+ }
503
+
504
+ // Create new cluster
505
+ const cluster1 = clusters[merge1];
506
+ const cluster2 = clusters[merge2];
507
+ if (!cluster1 || !cluster2) {
508
+ continue;
509
+ }
510
+
511
+ const newCluster: ClusterNode<T> = {
512
+ id: `cluster-${String(clusterCount++)}`,
513
+ members: new Set([... cluster1.members, ... cluster2.members]),
514
+ left: cluster1,
515
+ right: cluster2,
516
+ distance: -maxGain, // Use negative gain as distance
517
+ height: Math.max(cluster1.height, cluster2.height) + 1,
518
+ };
519
+
520
+ dendrogram.push(newCluster);
521
+
522
+ // Update community assignments
523
+ for (const node of newCluster.members) {
524
+ communityMap.set(node, clusterCount - 1);
525
+ }
526
+
527
+ // Remove merged clusters
528
+ clusters.splice(Math.max(merge1, merge2), 1);
529
+ clusters.splice(Math.min(merge1, merge2), 1);
530
+ clusters.push(newCluster);
531
+ }
532
+
533
+ // Generate clusters at different levels
534
+ const clustersByLevel = new Map<number, Set<T>[]>();
535
+ const root = clusters[0];
536
+
537
+ if (!root) {
538
+ return {
539
+ root: {id: "empty", members: new Set(), distance: 0, height: 0},
540
+ dendrogram: [],
541
+ clusters: new Map(),
542
+ };
543
+ }
544
+
545
+ for (let h = 0; h <= root.height; h++) {
546
+ clustersByLevel.set(h, cutDendrogram(root, h));
547
+ }
548
+
549
+ return {
550
+ root,
551
+ dendrogram,
552
+ clusters: clustersByLevel,
553
+ };
554
+ }
555
+
@@ -0,0 +1,6 @@
1
+ export * from "./hierarchical.js";
2
+ export * from "./k-core.js";
3
+ export type {MCLOptions, MCLResult} from "./mcl.js";
4
+ export {calculateMCLModularity, markovClustering} from "./mcl.js";
5
+ export type {SpectralClusteringOptions, SpectralClusteringResult} from "./spectral.js";
6
+ export {spectralClustering} from "./spectral.js";