@graphty/algorithms 1.1.0 → 1.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (237) hide show
  1. package/README.md +820 -235
  2. package/dist/algorithms.d.ts +42 -2
  3. package/dist/algorithms.js +11099 -2
  4. package/dist/algorithms.js.map +1 -1
  5. package/dist/src/algorithms/centrality/betweenness.d.ts +4 -0
  6. package/dist/src/algorithms/centrality/betweenness.d.ts.map +1 -1
  7. package/dist/src/algorithms/centrality/betweenness.js +124 -170
  8. package/dist/src/algorithms/centrality/betweenness.js.map +1 -1
  9. package/dist/src/algorithms/centrality/closeness.d.ts +26 -1
  10. package/dist/src/algorithms/centrality/closeness.d.ts.map +1 -1
  11. package/dist/src/algorithms/centrality/closeness.js +56 -149
  12. package/dist/src/algorithms/centrality/closeness.js.map +1 -1
  13. package/dist/src/algorithms/centrality/degree.js +3 -3
  14. package/dist/src/algorithms/centrality/degree.js.map +1 -1
  15. package/dist/src/algorithms/centrality/delta-pagerank-simple.d.ts +39 -0
  16. package/dist/src/algorithms/centrality/delta-pagerank-simple.d.ts.map +1 -0
  17. package/dist/src/algorithms/centrality/delta-pagerank-simple.js +286 -0
  18. package/dist/src/algorithms/centrality/delta-pagerank-simple.js.map +1 -0
  19. package/dist/src/algorithms/centrality/delta-pagerank.d.ts +80 -0
  20. package/dist/src/algorithms/centrality/delta-pagerank.d.ts.map +1 -0
  21. package/dist/src/algorithms/centrality/delta-pagerank.js +294 -0
  22. package/dist/src/algorithms/centrality/delta-pagerank.js.map +1 -0
  23. package/dist/src/algorithms/centrality/index.d.ts +2 -0
  24. package/dist/src/algorithms/centrality/index.d.ts.map +1 -1
  25. package/dist/src/algorithms/centrality/index.js +1 -0
  26. package/dist/src/algorithms/centrality/index.js.map +1 -1
  27. package/dist/src/algorithms/centrality/pagerank.d.ts +17 -0
  28. package/dist/src/algorithms/centrality/pagerank.d.ts.map +1 -1
  29. package/dist/src/algorithms/centrality/pagerank.js +45 -0
  30. package/dist/src/algorithms/centrality/pagerank.js.map +1 -1
  31. package/dist/src/algorithms/community/girvan-newman.d.ts.map +1 -1
  32. package/dist/src/algorithms/community/girvan-newman.js +47 -54
  33. package/dist/src/algorithms/community/girvan-newman.js.map +1 -1
  34. package/dist/src/algorithms/community/index.d.ts +2 -0
  35. package/dist/src/algorithms/community/index.d.ts.map +1 -1
  36. package/dist/src/algorithms/community/index.js.map +1 -1
  37. package/dist/src/algorithms/community/label-propagation.d.ts +13 -3
  38. package/dist/src/algorithms/community/label-propagation.d.ts.map +1 -1
  39. package/dist/src/algorithms/community/label-propagation.js +50 -35
  40. package/dist/src/algorithms/community/label-propagation.js.map +1 -1
  41. package/dist/src/algorithms/community/leiden.d.ts +3 -2
  42. package/dist/src/algorithms/community/leiden.d.ts.map +1 -1
  43. package/dist/src/algorithms/community/leiden.js +34 -39
  44. package/dist/src/algorithms/community/leiden.js.map +1 -1
  45. package/dist/src/algorithms/community/louvain-optimized.d.ts +112 -0
  46. package/dist/src/algorithms/community/louvain-optimized.d.ts.map +1 -0
  47. package/dist/src/algorithms/community/louvain-optimized.js +303 -0
  48. package/dist/src/algorithms/community/louvain-optimized.js.map +1 -0
  49. package/dist/src/algorithms/community/louvain.d.ts.map +1 -1
  50. package/dist/src/algorithms/community/louvain.js +17 -76
  51. package/dist/src/algorithms/community/louvain.js.map +1 -1
  52. package/dist/src/algorithms/community/modularity-utils.d.ts +59 -0
  53. package/dist/src/algorithms/community/modularity-utils.d.ts.map +1 -0
  54. package/dist/src/algorithms/community/modularity-utils.js +112 -0
  55. package/dist/src/algorithms/community/modularity-utils.js.map +1 -0
  56. package/dist/src/algorithms/matching/bipartite.d.ts.map +1 -1
  57. package/dist/src/algorithms/matching/bipartite.js +8 -40
  58. package/dist/src/algorithms/matching/bipartite.js.map +1 -1
  59. package/dist/src/algorithms/shortest-path/bellman-ford.d.ts.map +1 -1
  60. package/dist/src/algorithms/shortest-path/bellman-ford.js +1 -12
  61. package/dist/src/algorithms/shortest-path/bellman-ford.js.map +1 -1
  62. package/dist/src/algorithms/shortest-path/bidirectional-dijkstra.d.ts +37 -0
  63. package/dist/src/algorithms/shortest-path/bidirectional-dijkstra.d.ts.map +1 -0
  64. package/dist/src/algorithms/shortest-path/bidirectional-dijkstra.js +193 -0
  65. package/dist/src/algorithms/shortest-path/bidirectional-dijkstra.js.map +1 -0
  66. package/dist/src/algorithms/shortest-path/dijkstra.d.ts +5 -2
  67. package/dist/src/algorithms/shortest-path/dijkstra.d.ts.map +1 -1
  68. package/dist/src/algorithms/shortest-path/dijkstra.js +15 -14
  69. package/dist/src/algorithms/shortest-path/dijkstra.js.map +1 -1
  70. package/dist/src/algorithms/traversal/bfs-unified.d.ts +29 -0
  71. package/dist/src/algorithms/traversal/bfs-unified.d.ts.map +1 -0
  72. package/dist/src/algorithms/traversal/bfs-unified.js +333 -0
  73. package/dist/src/algorithms/traversal/bfs-unified.js.map +1 -0
  74. package/dist/src/algorithms/traversal/bfs-variants.d.ts +54 -0
  75. package/dist/src/algorithms/traversal/bfs-variants.d.ts.map +1 -0
  76. package/dist/src/algorithms/traversal/bfs-variants.js +368 -0
  77. package/dist/src/algorithms/traversal/bfs-variants.js.map +1 -0
  78. package/dist/src/algorithms/traversal/bfs.d.ts +4 -18
  79. package/dist/src/algorithms/traversal/bfs.d.ts.map +1 -1
  80. package/dist/src/algorithms/traversal/bfs.js +5 -182
  81. package/dist/src/algorithms/traversal/bfs.js.map +1 -1
  82. package/dist/src/benchmark-all-algorithms.d.ts +23 -0
  83. package/dist/src/benchmark-all-algorithms.d.ts.map +1 -0
  84. package/dist/src/benchmark-all-algorithms.js +462 -0
  85. package/dist/src/benchmark-all-algorithms.js.map +1 -0
  86. package/dist/src/clustering/hierarchical.d.ts +16 -15
  87. package/dist/src/clustering/hierarchical.d.ts.map +1 -1
  88. package/dist/src/clustering/hierarchical.js +209 -223
  89. package/dist/src/clustering/hierarchical.js.map +1 -1
  90. package/dist/src/clustering/index.d.ts +4 -2
  91. package/dist/src/clustering/index.d.ts.map +1 -1
  92. package/dist/src/clustering/index.js +2 -2
  93. package/dist/src/clustering/index.js.map +1 -1
  94. package/dist/src/clustering/k-core.d.ts +16 -15
  95. package/dist/src/clustering/k-core.d.ts.map +1 -1
  96. package/dist/src/clustering/k-core.js +57 -21
  97. package/dist/src/clustering/k-core.js.map +1 -1
  98. package/dist/src/clustering/spectral.d.ts +8 -0
  99. package/dist/src/clustering/spectral.d.ts.map +1 -1
  100. package/dist/src/clustering/spectral.js +158 -35
  101. package/dist/src/clustering/spectral.js.map +1 -1
  102. package/dist/src/core/graph.js +4 -4
  103. package/dist/src/core/graph.js.map +1 -1
  104. package/dist/src/flow/ford-fulkerson.d.ts +17 -11
  105. package/dist/src/flow/ford-fulkerson.d.ts.map +1 -1
  106. package/dist/src/flow/ford-fulkerson.js +141 -217
  107. package/dist/src/flow/ford-fulkerson.js.map +1 -1
  108. package/dist/src/flow/min-cut.d.ts +6 -5
  109. package/dist/src/flow/min-cut.d.ts.map +1 -1
  110. package/dist/src/flow/min-cut.js +17 -8
  111. package/dist/src/flow/min-cut.js.map +1 -1
  112. package/dist/src/index.d.ts +2 -0
  113. package/dist/src/index.d.ts.map +1 -1
  114. package/dist/src/index.js +6 -0
  115. package/dist/src/index.js.map +1 -1
  116. package/dist/src/link-prediction/adamic-adar.d.ts.map +1 -1
  117. package/dist/src/link-prediction/adamic-adar.js +17 -26
  118. package/dist/src/link-prediction/adamic-adar.js.map +1 -1
  119. package/dist/src/link-prediction/common-neighbors.d.ts.map +1 -1
  120. package/dist/src/link-prediction/common-neighbors.js +7 -11
  121. package/dist/src/link-prediction/common-neighbors.js.map +1 -1
  122. package/dist/src/optimized/bit-packed.d.ts +143 -0
  123. package/dist/src/optimized/bit-packed.d.ts.map +1 -0
  124. package/dist/src/optimized/bit-packed.js +292 -0
  125. package/dist/src/optimized/bit-packed.js.map +1 -0
  126. package/dist/src/optimized/csr-graph.d.ts +86 -0
  127. package/dist/src/optimized/csr-graph.d.ts.map +1 -0
  128. package/dist/src/optimized/csr-graph.js +341 -0
  129. package/dist/src/optimized/csr-graph.js.map +1 -0
  130. package/dist/src/optimized/direction-optimized-bfs.d.ts +74 -0
  131. package/dist/src/optimized/direction-optimized-bfs.d.ts.map +1 -0
  132. package/dist/src/optimized/direction-optimized-bfs.js +223 -0
  133. package/dist/src/optimized/direction-optimized-bfs.js.map +1 -0
  134. package/dist/src/optimized/graph-adapter.d.ts +62 -0
  135. package/dist/src/optimized/graph-adapter.d.ts.map +1 -0
  136. package/dist/src/optimized/graph-adapter.js +146 -0
  137. package/dist/src/optimized/graph-adapter.js.map +1 -0
  138. package/dist/src/optimized/index.d.ts +14 -0
  139. package/dist/src/optimized/index.d.ts.map +1 -0
  140. package/dist/src/optimized/index.js +28 -0
  141. package/dist/src/optimized/index.js.map +1 -0
  142. package/dist/src/research/grsbm.d.ts +83 -0
  143. package/dist/src/research/grsbm.d.ts.map +1 -0
  144. package/dist/src/research/grsbm.js +403 -0
  145. package/dist/src/research/grsbm.js.map +1 -0
  146. package/dist/src/research/index.d.ts +12 -0
  147. package/dist/src/research/index.d.ts.map +1 -0
  148. package/dist/src/research/index.js +14 -0
  149. package/dist/src/research/index.js.map +1 -0
  150. package/dist/src/research/sync.d.ts +49 -0
  151. package/dist/src/research/sync.d.ts.map +1 -0
  152. package/dist/src/research/sync.js +316 -0
  153. package/dist/src/research/sync.js.map +1 -0
  154. package/dist/src/research/terahac.d.ts +65 -0
  155. package/dist/src/research/terahac.d.ts.map +1 -0
  156. package/dist/src/research/terahac.js +379 -0
  157. package/dist/src/research/terahac.js.map +1 -0
  158. package/dist/src/types/index.d.ts +8 -0
  159. package/dist/src/types/index.d.ts.map +1 -1
  160. package/dist/src/utils/algorithm-utilities.d.ts +2 -0
  161. package/dist/src/utils/algorithm-utilities.d.ts.map +1 -0
  162. package/dist/src/utils/algorithm-utilities.js +2 -0
  163. package/dist/src/utils/algorithm-utilities.js.map +1 -0
  164. package/dist/src/utils/graph-converters.d.ts +79 -0
  165. package/dist/src/utils/graph-converters.d.ts.map +1 -0
  166. package/dist/src/utils/graph-converters.js +196 -0
  167. package/dist/src/utils/graph-converters.js.map +1 -0
  168. package/dist/src/utils/graph-utilities.d.ts +62 -0
  169. package/dist/src/utils/graph-utilities.d.ts.map +1 -0
  170. package/dist/src/utils/graph-utilities.js +148 -0
  171. package/dist/src/utils/graph-utilities.js.map +1 -0
  172. package/dist/src/utils/index.d.ts +5 -0
  173. package/dist/src/utils/index.d.ts.map +1 -0
  174. package/dist/src/utils/index.js +5 -0
  175. package/dist/src/utils/index.js.map +1 -0
  176. package/dist/src/utils/math-utilities.d.ts +52 -0
  177. package/dist/src/utils/math-utilities.d.ts.map +1 -0
  178. package/dist/src/utils/math-utilities.js +123 -0
  179. package/dist/src/utils/math-utilities.js.map +1 -0
  180. package/dist/src/utils/matrix-utilities.d.ts +2 -0
  181. package/dist/src/utils/matrix-utilities.d.ts.map +1 -0
  182. package/dist/src/utils/matrix-utilities.js +2 -0
  183. package/dist/src/utils/matrix-utilities.js.map +1 -0
  184. package/dist/src/utils/optimization-helpers.d.ts +33 -0
  185. package/dist/src/utils/optimization-helpers.d.ts.map +1 -0
  186. package/dist/src/utils/optimization-helpers.js +47 -0
  187. package/dist/src/utils/optimization-helpers.js.map +1 -0
  188. package/package.json +28 -4
  189. package/src/algorithms/centrality/betweenness.ts +167 -199
  190. package/src/algorithms/centrality/closeness.ts +73 -191
  191. package/src/algorithms/centrality/degree.ts +3 -3
  192. package/src/algorithms/centrality/delta-pagerank-simple.ts +366 -0
  193. package/src/algorithms/centrality/delta-pagerank.ts +435 -0
  194. package/src/algorithms/centrality/index.ts +2 -0
  195. package/src/algorithms/centrality/pagerank.ts +66 -0
  196. package/src/algorithms/community/girvan-newman.ts +52 -59
  197. package/src/algorithms/community/index.ts +2 -0
  198. package/src/algorithms/community/label-propagation.ts +64 -35
  199. package/src/algorithms/community/leiden.ts +40 -41
  200. package/src/algorithms/community/louvain-optimized.ts +445 -0
  201. package/src/algorithms/community/louvain.ts +24 -104
  202. package/src/algorithms/community/modularity-utils.ts +146 -0
  203. package/src/algorithms/matching/bipartite.ts +8 -44
  204. package/src/algorithms/shortest-path/bellman-ford.ts +1 -14
  205. package/src/algorithms/shortest-path/bidirectional-dijkstra.ts +248 -0
  206. package/src/algorithms/shortest-path/dijkstra.ts +17 -15
  207. package/src/algorithms/traversal/bfs-unified.ts +433 -0
  208. package/src/algorithms/traversal/bfs-variants.ts +496 -0
  209. package/src/algorithms/traversal/bfs.ts +10 -228
  210. package/src/benchmark-all-algorithms.ts +548 -0
  211. package/src/clustering/hierarchical.ts +241 -251
  212. package/src/clustering/index.ts +4 -2
  213. package/src/clustering/k-core.ts +87 -39
  214. package/src/clustering/spectral.ts +185 -34
  215. package/src/core/graph.ts +4 -4
  216. package/src/flow/ford-fulkerson.ts +186 -254
  217. package/src/flow/min-cut.ts +21 -11
  218. package/src/index.ts +9 -0
  219. package/src/link-prediction/adamic-adar.ts +17 -30
  220. package/src/link-prediction/common-neighbors.ts +7 -16
  221. package/src/optimized/bit-packed.ts +340 -0
  222. package/src/optimized/csr-graph.ts +455 -0
  223. package/src/optimized/direction-optimized-bfs.ts +291 -0
  224. package/src/optimized/graph-adapter.ts +200 -0
  225. package/src/optimized/index.ts +36 -0
  226. package/src/research/grsbm.ts +580 -0
  227. package/src/research/index.ts +17 -0
  228. package/src/research/sync.ts +440 -0
  229. package/src/research/terahac.ts +514 -0
  230. package/src/types/index.ts +8 -0
  231. package/src/utils/algorithm-utilities.ts +2 -0
  232. package/src/utils/graph-converters.ts +241 -0
  233. package/src/utils/graph-utilities.ts +199 -0
  234. package/src/utils/index.ts +4 -0
  235. package/src/utils/math-utilities.ts +140 -0
  236. package/src/utils/matrix-utilities.ts +2 -0
  237. package/src/utils/optimization-helpers.ts +81 -0
@@ -0,0 +1,514 @@
1
+ import type {Graph} from "../core/graph.js";
2
+ import type {NodeId} from "../types/index.js";
3
+
4
+ /**
5
+ * Configuration options for the TeraHAC (Hierarchical Agglomerative Clustering) algorithm
6
+ */
7
+ export interface TeraHACConfig {
8
+ /** Linkage criterion: 'single', 'complete', 'average', 'ward' */
9
+ linkage?: "single" | "complete" | "average" | "ward";
10
+ /** Number of clusters to stop at (optional) */
11
+ numClusters?: number;
12
+ /** Distance threshold to stop clustering */
13
+ distanceThreshold?: number;
14
+ /** Maximum number of nodes to process efficiently */
15
+ maxNodes?: number;
16
+ /** Use graph structure for distance calculation */
17
+ useGraphDistance?: boolean;
18
+ /** Custom warning handler (default: console.warn) */
19
+ onWarning?: (message: string) => void;
20
+ }
21
+
22
+ /**
23
+ * Represents a node in the dendrogram/cluster hierarchy
24
+ */
25
+ export interface ClusterNode {
26
+ /** Unique identifier for this cluster */
27
+ id: string;
28
+ /** Node IDs in this cluster */
29
+ members: Set<NodeId>;
30
+ /** Left child cluster (if internal node) */
31
+ left?: ClusterNode;
32
+ /** Right child cluster (if internal node) */
33
+ right?: ClusterNode;
34
+ /** Distance at which this cluster was formed */
35
+ distance: number;
36
+ /** Size of the cluster */
37
+ size: number;
38
+ }
39
+
40
+ /**
41
+ * Result of the TeraHAC clustering algorithm
42
+ */
43
+ export interface TeraHACResult {
44
+ /** Root of the dendrogram */
45
+ dendrogram: ClusterNode;
46
+ /** Flat clustering at specified level */
47
+ clusters: Map<NodeId, number>;
48
+ /** All merge distances in order */
49
+ distances: number[];
50
+ /** Number of clusters in final result */
51
+ numClusters: number;
52
+ }
53
+
54
+ /**
55
+ * TeraHAC - Hierarchical Agglomerative Clustering for Large Graphs
56
+ *
57
+ * This algorithm performs hierarchical clustering on graphs by iteratively
58
+ * merging the closest clusters. Optimized for scalability to handle large
59
+ * graphs efficiently.
60
+ *
61
+ * Based on: "Scaling Hierarchical Agglomerative Clustering to Trillion-Edge Graphs"
62
+ * Google Research 2024
63
+ *
64
+ * @param graph - Input graph to cluster
65
+ * @param config - Configuration options
66
+ * @returns Hierarchical clustering result
67
+ */
68
+ export function teraHAC(graph: Graph, config: TeraHACConfig = {}): TeraHACResult {
69
+ const {
70
+ linkage = "average",
71
+ numClusters,
72
+ distanceThreshold,
73
+ maxNodes = 10000,
74
+ useGraphDistance = true,
75
+ onWarning = console.warn,
76
+ } = config;
77
+
78
+ // Input validation
79
+ if (numClusters !== undefined && (numClusters < 1 || !Number.isInteger(numClusters))) {
80
+ throw new Error("numClusters must be a positive integer");
81
+ }
82
+
83
+ const nodes = Array.from(graph.nodes());
84
+ const nodeCount = nodes.length;
85
+
86
+ if (nodeCount === 0) {
87
+ throw new Error("Cannot cluster empty graph");
88
+ }
89
+
90
+ if (nodeCount > maxNodes) {
91
+ onWarning(`Graph has ${String(nodeCount)} nodes, which exceeds maxNodes (${String(maxNodes)}). Performance may be degraded.`);
92
+ }
93
+
94
+ // Initialize each node as its own cluster
95
+ const clusters = new Map<string, ClusterNode>();
96
+ let nextClusterId = nodeCount;
97
+
98
+ for (let i = 0; i < nodeCount; i++) {
99
+ const node = nodes[i];
100
+ if (!node) {
101
+ continue;
102
+ }
103
+
104
+ const clusterId = i.toString();
105
+ clusters.set(clusterId, {
106
+ id: clusterId,
107
+ members: new Set([node.id]),
108
+ distance: 0,
109
+ size: 1,
110
+ });
111
+ }
112
+
113
+ // Calculate initial distance matrix
114
+ const distanceMatrix = calculateDistanceMatrix(graph, nodes, useGraphDistance);
115
+ const mergeDistances: number[] = [];
116
+
117
+ // Priority queue for efficient nearest neighbor finding
118
+ const mergeCandidates = initializeMergeCandidates(clusters, distanceMatrix);
119
+
120
+ let dendrogram: ClusterNode | undefined;
121
+
122
+ // Perform agglomerative clustering
123
+ while (clusters.size > 1 && mergeCandidates.length > 0) {
124
+ // Find closest pair of clusters
125
+ const {cluster1Id, cluster2Id, distance} = findClosestPair(mergeCandidates);
126
+
127
+ // Check stopping criteria
128
+ if (numClusters && clusters.size <= numClusters) {
129
+ break;
130
+ }
131
+
132
+ if (distanceThreshold && distance > distanceThreshold) {
133
+ break;
134
+ }
135
+
136
+ // Get the two clusters to merge
137
+ const cluster1 = clusters.get(cluster1Id);
138
+ const cluster2 = clusters.get(cluster2Id);
139
+ if (!cluster1 || !cluster2) {
140
+ continue;
141
+ }
142
+
143
+ // Create new merged cluster
144
+ const newClusterId = (nextClusterId++).toString();
145
+ const mergedMembers = new Set([... cluster1.members, ... cluster2.members]);
146
+
147
+ const newCluster: ClusterNode = {
148
+ id: newClusterId,
149
+ members: mergedMembers,
150
+ left: cluster1,
151
+ right: cluster2,
152
+ distance,
153
+ size: cluster1.size + cluster2.size,
154
+ };
155
+
156
+ // Remove old clusters
157
+ clusters.delete(cluster1Id);
158
+ clusters.delete(cluster2Id);
159
+
160
+ // Add new cluster
161
+ clusters.set(newClusterId, newCluster);
162
+ mergeDistances.push(distance);
163
+
164
+ // Update merge candidates
165
+ updateMergeCandidates(mergeCandidates, cluster1Id, cluster2Id, newClusterId, clusters, distanceMatrix, linkage);
166
+
167
+ dendrogram = newCluster;
168
+ }
169
+
170
+ // If we have multiple remaining clusters, create a virtual root
171
+ if (clusters.size > 1) {
172
+ const remainingClusters = Array.from(clusters.values());
173
+ let root = remainingClusters[0];
174
+ if (!root) {
175
+ dendrogram = undefined;
176
+ } else {
177
+ for (let i = 1; i < remainingClusters.length; i++) {
178
+ const currentCluster = remainingClusters[i];
179
+ if (!currentCluster) {
180
+ continue;
181
+ }
182
+
183
+ const newRoot: ClusterNode = {
184
+ id: (nextClusterId++).toString(),
185
+ members: new Set([... root.members, ... currentCluster.members]),
186
+ left: root,
187
+ right: currentCluster,
188
+ distance: Infinity,
189
+ size: root.size + currentCluster.size,
190
+ };
191
+ root = newRoot;
192
+ }
193
+ dendrogram = root;
194
+ }
195
+
196
+ dendrogram = root;
197
+ }
198
+
199
+ dendrogram ??= Array.from(clusters.values())[0];
200
+
201
+ // Extract flat clustering
202
+ const finalNumClusters = numClusters ?? clusters.size;
203
+ const flatClusters = dendrogram ? extractFlatClustering(dendrogram, finalNumClusters) : new Map<NodeId, number>();
204
+
205
+ if (!dendrogram) {
206
+ throw new Error("Failed to create dendrogram");
207
+ }
208
+
209
+ return {
210
+ dendrogram,
211
+ clusters: flatClusters,
212
+ distances: mergeDistances,
213
+ numClusters: finalNumClusters,
214
+ };
215
+ }
216
+
217
+ /**
218
+ * Calculate distance matrix between all pairs of nodes
219
+ */
220
+ function calculateDistanceMatrix(graph: Graph, nodes: {id: NodeId}[], useGraphDistance: boolean): number[][] {
221
+ const n = nodes.length;
222
+ const matrix: number[][] = Array.from({length: n}, () => new Array(n).fill(Infinity) as number[]);
223
+
224
+ if (useGraphDistance) {
225
+ // Use graph-based distances (shortest path)
226
+ for (let i = 0; i < n; i++) {
227
+ const node = nodes[i];
228
+ if (!node) {
229
+ continue;
230
+ }
231
+
232
+ const distances = bfsShortestPaths(graph, node.id);
233
+ for (let j = 0; j < n; j++) {
234
+ if (i !== j) {
235
+ const targetNode = nodes[j];
236
+ if (targetNode && i < matrix.length && j < n) {
237
+ const row = matrix[i];
238
+ if (row && j < row.length) {
239
+ const distance = distances.get(targetNode.id);
240
+ row[j] = distance ?? Infinity;
241
+ }
242
+ }
243
+ } else if (i < matrix.length) {
244
+ const row = matrix[i];
245
+ if (row && j < row.length) {
246
+ row[j] = 0;
247
+ }
248
+ }
249
+ }
250
+ }
251
+ } else {
252
+ // Use simple edge-based distances
253
+ for (let i = 0; i < n; i++) {
254
+ for (let j = i + 1; j < n; j++) {
255
+ const node1 = nodes[i];
256
+ const node2 = nodes[j];
257
+ if (node1 && node2) {
258
+ const hasEdge = graph.hasEdge(node1.id, node2.id);
259
+ const distance = hasEdge ? 1 : 2; // Connected: 1, not connected: 2
260
+ if (i < matrix.length) {
261
+ const rowI = matrix[i];
262
+ if (rowI && j < rowI.length) {
263
+ rowI[j] = distance;
264
+ }
265
+ }
266
+
267
+ if (j < matrix.length) {
268
+ const rowJ = matrix[j];
269
+ if (rowJ && i < rowJ.length) {
270
+ rowJ[i] = distance;
271
+ }
272
+ }
273
+ }
274
+ }
275
+ if (i < matrix.length) {
276
+ const row = matrix[i];
277
+ if (row && i < row.length) {
278
+ row[i] = 0;
279
+ }
280
+ }
281
+ }
282
+ }
283
+
284
+ return matrix;
285
+ }
286
+
287
+ /**
288
+ * BFS-based shortest path calculation from a source node
289
+ */
290
+ function bfsShortestPaths(graph: Graph, source: NodeId): Map<NodeId, number> {
291
+ const distances = new Map<NodeId, number>();
292
+ const queue: [NodeId, number][] = [[source, 0]];
293
+ const visited = new Set<NodeId>();
294
+
295
+ visited.add(source);
296
+ distances.set(source, 0);
297
+
298
+ while (queue.length > 0) {
299
+ const item = queue.shift();
300
+ if (!item) {
301
+ break;
302
+ }
303
+
304
+ const [current, distance] = item;
305
+
306
+ for (const neighbor of graph.neighbors(current)) {
307
+ if (!visited.has(neighbor)) {
308
+ visited.add(neighbor);
309
+ distances.set(neighbor, distance + 1);
310
+ queue.push([neighbor, distance + 1]);
311
+ }
312
+ }
313
+ }
314
+
315
+ return distances;
316
+ }
317
+
318
+ /**
319
+ * Initialize merge candidates priority queue
320
+ */
321
+ function initializeMergeCandidates(
322
+ clusters: Map<string, ClusterNode>,
323
+ distanceMatrix: number[][],
324
+ ): {cluster1Id: string, cluster2Id: string, distance: number}[] {
325
+ const candidates: {cluster1Id: string, cluster2Id: string, distance: number}[] = [];
326
+ const clusterIds = Array.from(clusters.keys());
327
+
328
+ for (let i = 0; i < clusterIds.length; i++) {
329
+ for (let j = i + 1; j < clusterIds.length; j++) {
330
+ const id1 = clusterIds[i];
331
+ const id2 = clusterIds[j];
332
+ if (!id1 || !id2) {
333
+ continue;
334
+ }
335
+
336
+ const row = distanceMatrix[parseInt(id1)];
337
+ if (!row) {
338
+ continue;
339
+ }
340
+
341
+ const distance = row[parseInt(id2)] ?? Infinity;
342
+ // Include all candidates, even disconnected ones (with finite but large distance)
343
+ candidates.push({
344
+ cluster1Id: id1,
345
+ cluster2Id: id2,
346
+ distance: distance === Infinity ? 100 : distance,
347
+ });
348
+ }
349
+ }
350
+
351
+ // Sort by distance (ascending)
352
+ candidates.sort((a, b) => a.distance - b.distance);
353
+ return candidates;
354
+ }
355
+
356
+ /**
357
+ * Find the closest pair of clusters
358
+ */
359
+ function findClosestPair(
360
+ mergeCandidates: {cluster1Id: string, cluster2Id: string, distance: number}[],
361
+ ): {cluster1Id: string, cluster2Id: string, distance: number} {
362
+ // Return the first (closest) valid candidate
363
+ const candidate = mergeCandidates.shift();
364
+ if (!candidate) {
365
+ throw new Error("No merge candidates available");
366
+ }
367
+
368
+ return candidate;
369
+ }
370
+
371
+ /**
372
+ * Update merge candidates after a merge operation
373
+ */
374
+ function updateMergeCandidates(
375
+ mergeCandidates: {cluster1Id: string, cluster2Id: string, distance: number}[],
376
+ oldCluster1Id: string,
377
+ oldCluster2Id: string,
378
+ newClusterId: string,
379
+ clusters: Map<string, ClusterNode>,
380
+ distanceMatrix: number[][],
381
+ linkage: string,
382
+ ): void {
383
+ // Remove candidates involving the merged clusters
384
+ for (let i = mergeCandidates.length - 1; i >= 0; i--) {
385
+ const candidate = mergeCandidates[i];
386
+ if (!candidate) {
387
+ continue;
388
+ }
389
+
390
+ if (candidate.cluster1Id === oldCluster1Id || candidate.cluster1Id === oldCluster2Id ||
391
+ candidate.cluster2Id === oldCluster1Id || candidate.cluster2Id === oldCluster2Id) {
392
+ mergeCandidates.splice(i, 1);
393
+ }
394
+ }
395
+
396
+ // Add new candidates for the merged cluster
397
+ const newCluster = clusters.get(newClusterId);
398
+ if (!newCluster) {
399
+ return;
400
+ }
401
+
402
+ for (const [clusterId, cluster] of clusters) {
403
+ if (clusterId !== newClusterId) {
404
+ const distance = calculateClusterDistance(newCluster, cluster, distanceMatrix, linkage);
405
+ mergeCandidates.push({
406
+ cluster1Id: newClusterId,
407
+ cluster2Id: clusterId,
408
+ distance: distance === Infinity ? 100 : distance,
409
+ });
410
+ }
411
+ }
412
+
413
+ // Re-sort candidates
414
+ mergeCandidates.sort((a, b) => a.distance - b.distance);
415
+ }
416
+
417
+ /**
418
+ * Calculate distance between two clusters based on linkage criterion
419
+ */
420
+ function calculateClusterDistance(
421
+ cluster1: ClusterNode,
422
+ cluster2: ClusterNode,
423
+ distanceMatrix: number[][],
424
+ linkage: string,
425
+ ): number {
426
+ const members1 = Array.from(cluster1.members);
427
+ const members2 = Array.from(cluster2.members);
428
+ const distances: number[] = [];
429
+
430
+ // Calculate all pairwise distances between cluster members
431
+ for (const member1 of members1) {
432
+ for (const member2 of members2) {
433
+ const idx1 = parseInt(member1.toString());
434
+ const idx2 = parseInt(member2.toString());
435
+ if (idx1 < distanceMatrix.length && idx2 < distanceMatrix.length) {
436
+ const row = distanceMatrix[idx1];
437
+ if (row) {
438
+ const distance = row[idx2];
439
+ if (distance !== undefined) {
440
+ distances.push(distance);
441
+ }
442
+ }
443
+ }
444
+ }
445
+ }
446
+
447
+ if (distances.length === 0) {
448
+ return Infinity;
449
+ }
450
+
451
+ // Apply linkage criterion
452
+ switch (linkage) {
453
+ case "single":
454
+ return Math.min(... distances);
455
+ case "complete":
456
+ return Math.max(... distances);
457
+ case "average":
458
+ return distances.reduce((sum, d) => sum + d, 0) / distances.length;
459
+ case "ward":
460
+ // Simplified Ward linkage (would need cluster centroids for full implementation)
461
+ return distances.reduce((sum, d) => sum + (d * d), 0) / distances.length;
462
+ default:
463
+ return distances.reduce((sum, d) => sum + d, 0) / distances.length;
464
+ }
465
+ }
466
+
467
+ /**
468
+ * Extract flat clustering from dendrogram
469
+ */
470
+ function extractFlatClustering(dendrogram: ClusterNode, numClusters: number): Map<NodeId, number> {
471
+ const clusters = new Map<NodeId, number>();
472
+
473
+ if (numClusters === 1) {
474
+ // Single cluster
475
+ const clusterId = 0;
476
+ for (const member of dendrogram.members) {
477
+ clusters.set(member, clusterId);
478
+ }
479
+ return clusters;
480
+ }
481
+
482
+ // Find clusters at the specified level
483
+ const clusterNodes: ClusterNode[] = [];
484
+ const queue: ClusterNode[] = [dendrogram];
485
+
486
+ while (queue.length > 0 && clusterNodes.length < numClusters) {
487
+ const current = queue.shift();
488
+ if (!current) {
489
+ break;
490
+ }
491
+
492
+ if (!current.left || !current.right || clusterNodes.length + queue.length + 1 >= numClusters) {
493
+ // This is a leaf or we need to keep this level
494
+ clusterNodes.push(current);
495
+ } else {
496
+ // Continue decomposing
497
+ queue.push(current.left, current.right);
498
+ }
499
+ }
500
+
501
+ // Assign cluster IDs
502
+ for (let i = 0; i < clusterNodes.length; i++) {
503
+ const cluster = clusterNodes[i];
504
+ if (!cluster) {
505
+ continue;
506
+ }
507
+
508
+ for (const member of cluster.members) {
509
+ clusters.set(member, i);
510
+ }
511
+ }
512
+
513
+ return clusters;
514
+ }
@@ -72,6 +72,12 @@ export interface TraversalOptions {
72
72
 
73
73
  export interface DijkstraOptions {
74
74
  target?: NodeId;
75
+ /**
76
+ * Use bidirectional search optimization for point-to-point queries.
77
+ * Defaults to true for graphs with >10 nodes, false for smaller graphs.
78
+ * Set explicitly to override automatic heuristic.
79
+ */
80
+ bidirectional?: boolean;
75
81
  }
76
82
 
77
83
  export interface BellmanFordResult {
@@ -98,9 +104,11 @@ export interface LouvainOptions {
98
104
  resolution?: number; // Resolution parameter (default: 1.0)
99
105
  maxIterations?: number; // Maximum iterations (default: 100)
100
106
  tolerance?: number; // Improvement tolerance (default: 1e-6)
107
+ useOptimized?: boolean; // Use optimized implementation for large graphs (default: true for graphs > 50 nodes)
101
108
  }
102
109
 
103
110
  export interface GirvanNewmanOptions {
104
111
  maxCommunities?: number; // Stop when this many communities reached
105
112
  minCommunitySize?: number; // Minimum size for valid community
113
+ maxIterations?: number; // Maximum iterations to prevent infinite loops
106
114
  }
@@ -0,0 +1,2 @@
1
+ // Algorithm utility functions will be added here
2
+ export {};