@graphty/algorithms 1.2.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 (230) hide show
  1. package/README.md +490 -336
  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 +40 -21
  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 +1 -0
  113. package/dist/src/index.d.ts.map +1 -1
  114. package/dist/src/index.js +4 -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.map +1 -1
  143. package/dist/src/research/grsbm.js +6 -14
  144. package/dist/src/research/grsbm.js.map +1 -1
  145. package/dist/src/research/sync.d.ts.map +1 -1
  146. package/dist/src/research/sync.js +3 -29
  147. package/dist/src/research/sync.js.map +1 -1
  148. package/dist/src/research/terahac.d.ts +2 -0
  149. package/dist/src/research/terahac.d.ts.map +1 -1
  150. package/dist/src/research/terahac.js +7 -3
  151. package/dist/src/research/terahac.js.map +1 -1
  152. package/dist/src/types/index.d.ts +8 -0
  153. package/dist/src/types/index.d.ts.map +1 -1
  154. package/dist/src/utils/algorithm-utilities.d.ts +2 -0
  155. package/dist/src/utils/algorithm-utilities.d.ts.map +1 -0
  156. package/dist/src/utils/algorithm-utilities.js +2 -0
  157. package/dist/src/utils/algorithm-utilities.js.map +1 -0
  158. package/dist/src/utils/graph-converters.d.ts +79 -0
  159. package/dist/src/utils/graph-converters.d.ts.map +1 -0
  160. package/dist/src/utils/graph-converters.js +196 -0
  161. package/dist/src/utils/graph-converters.js.map +1 -0
  162. package/dist/src/utils/graph-utilities.d.ts +62 -0
  163. package/dist/src/utils/graph-utilities.d.ts.map +1 -0
  164. package/dist/src/utils/graph-utilities.js +148 -0
  165. package/dist/src/utils/graph-utilities.js.map +1 -0
  166. package/dist/src/utils/index.d.ts +5 -0
  167. package/dist/src/utils/index.d.ts.map +1 -0
  168. package/dist/src/utils/index.js +5 -0
  169. package/dist/src/utils/index.js.map +1 -0
  170. package/dist/src/utils/math-utilities.d.ts +52 -0
  171. package/dist/src/utils/math-utilities.d.ts.map +1 -0
  172. package/dist/src/utils/math-utilities.js +123 -0
  173. package/dist/src/utils/math-utilities.js.map +1 -0
  174. package/dist/src/utils/matrix-utilities.d.ts +2 -0
  175. package/dist/src/utils/matrix-utilities.d.ts.map +1 -0
  176. package/dist/src/utils/matrix-utilities.js +2 -0
  177. package/dist/src/utils/matrix-utilities.js.map +1 -0
  178. package/dist/src/utils/optimization-helpers.d.ts +33 -0
  179. package/dist/src/utils/optimization-helpers.d.ts.map +1 -0
  180. package/dist/src/utils/optimization-helpers.js +47 -0
  181. package/dist/src/utils/optimization-helpers.js.map +1 -0
  182. package/package.json +18 -3
  183. package/src/algorithms/centrality/betweenness.ts +167 -199
  184. package/src/algorithms/centrality/closeness.ts +73 -191
  185. package/src/algorithms/centrality/degree.ts +3 -3
  186. package/src/algorithms/centrality/delta-pagerank-simple.ts +366 -0
  187. package/src/algorithms/centrality/delta-pagerank.ts +435 -0
  188. package/src/algorithms/centrality/index.ts +2 -0
  189. package/src/algorithms/centrality/pagerank.ts +66 -0
  190. package/src/algorithms/community/girvan-newman.ts +52 -59
  191. package/src/algorithms/community/index.ts +2 -0
  192. package/src/algorithms/community/label-propagation.ts +64 -35
  193. package/src/algorithms/community/leiden.ts +40 -41
  194. package/src/algorithms/community/louvain-optimized.ts +445 -0
  195. package/src/algorithms/community/louvain.ts +24 -104
  196. package/src/algorithms/community/modularity-utils.ts +146 -0
  197. package/src/algorithms/matching/bipartite.ts +8 -44
  198. package/src/algorithms/shortest-path/bellman-ford.ts +1 -14
  199. package/src/algorithms/shortest-path/bidirectional-dijkstra.ts +248 -0
  200. package/src/algorithms/shortest-path/dijkstra.ts +17 -15
  201. package/src/algorithms/traversal/bfs-unified.ts +433 -0
  202. package/src/algorithms/traversal/bfs-variants.ts +496 -0
  203. package/src/algorithms/traversal/bfs.ts +10 -228
  204. package/src/benchmark-all-algorithms.ts +548 -0
  205. package/src/clustering/hierarchical.ts +241 -251
  206. package/src/clustering/index.ts +4 -2
  207. package/src/clustering/k-core.ts +87 -39
  208. package/src/clustering/spectral.ts +44 -20
  209. package/src/core/graph.ts +4 -4
  210. package/src/flow/ford-fulkerson.ts +186 -254
  211. package/src/flow/min-cut.ts +21 -11
  212. package/src/index.ts +6 -0
  213. package/src/link-prediction/adamic-adar.ts +17 -30
  214. package/src/link-prediction/common-neighbors.ts +7 -16
  215. package/src/optimized/bit-packed.ts +340 -0
  216. package/src/optimized/csr-graph.ts +455 -0
  217. package/src/optimized/direction-optimized-bfs.ts +291 -0
  218. package/src/optimized/graph-adapter.ts +200 -0
  219. package/src/optimized/index.ts +36 -0
  220. package/src/research/grsbm.ts +7 -16
  221. package/src/research/sync.ts +3 -32
  222. package/src/research/terahac.ts +10 -2
  223. package/src/types/index.ts +8 -0
  224. package/src/utils/algorithm-utilities.ts +2 -0
  225. package/src/utils/graph-converters.ts +241 -0
  226. package/src/utils/graph-utilities.ts +199 -0
  227. package/src/utils/index.ts +4 -0
  228. package/src/utils/math-utilities.ts +140 -0
  229. package/src/utils/matrix-utilities.ts +2 -0
  230. package/src/utils/optimization-helpers.ts +81 -0
@@ -0,0 +1,291 @@
1
+ import type {NodeId} from "../types/index.js";
2
+ import {CompactDistanceArray, GraphBitSet} from "./bit-packed.js";
3
+ import {CSRGraph} from "./csr-graph.js";
4
+
5
+ /**
6
+ * Options for Direction-Optimized BFS
7
+ */
8
+ export interface DirectionOptimizedBFSOptions {
9
+ alpha?: number; // Default: 15 - Controls top-down to bottom-up switch
10
+ beta?: number; // Default: 18 - Controls bottom-up to top-down switch
11
+ }
12
+
13
+ /**
14
+ * Result of BFS traversal
15
+ */
16
+ export interface BFSResult<TNodeId> {
17
+ distances: Map<TNodeId, number>;
18
+ parents: Map<TNodeId, TNodeId | null>;
19
+ visitedCount: number;
20
+ }
21
+
22
+ /**
23
+ * Direction-Optimized Breadth-First Search
24
+ *
25
+ * Dynamically switches between top-down and bottom-up search strategies
26
+ * based on the size of the frontier. Particularly effective for low-diameter
27
+ * graphs like social networks.
28
+ *
29
+ * Based on: Beamer, S., Asanović, K., & Patterson, D. (2012).
30
+ * "Direction-optimizing breadth-first search." SC'12.
31
+ */
32
+ export class DirectionOptimizedBFS<TNodeId = NodeId> {
33
+ private graph: CSRGraph<TNodeId>;
34
+ private alpha: number;
35
+ private beta: number;
36
+
37
+ // Data structures
38
+ private parent: Int32Array; // -1 = unvisited, -2 = source, else parent ID
39
+ private frontier: GraphBitSet; // Current frontier bitmap
40
+ private nextFrontier: GraphBitSet; // Next level frontier
41
+ private distances: CompactDistanceArray;
42
+
43
+ constructor(graph: CSRGraph<TNodeId>, options?: DirectionOptimizedBFSOptions) {
44
+ this.graph = graph;
45
+ this.alpha = options?.alpha ?? 15;
46
+ this.beta = options?.beta ?? 18;
47
+
48
+ const nodeCount = graph.nodeCount();
49
+ this.parent = new Int32Array(nodeCount).fill(-1);
50
+ this.frontier = new GraphBitSet(nodeCount);
51
+ this.nextFrontier = new GraphBitSet(nodeCount);
52
+ this.distances = new CompactDistanceArray(nodeCount);
53
+ }
54
+
55
+ /**
56
+ * Perform BFS from a single source
57
+ */
58
+ search(source: TNodeId): BFSResult<TNodeId> {
59
+ const sourceIndex = this.graph.nodeToIndex(source);
60
+ this.parent[sourceIndex] = -2; // Mark as source
61
+ this.frontier.add(sourceIndex);
62
+ this.distances.set(sourceIndex, 0);
63
+
64
+ // let currentDistance = 0; // Variable not used
65
+ // Initialize edgesToCheck to total edges to prevent immediate switch to bottom-up
66
+ let edgesToCheck = this.graph.edgeCount();
67
+ let scoutCount = 0; // Will be set after first top-down step
68
+ let awakeCount = 1; // Number of nodes in current frontier
69
+ let oldAwakeCount = 0;
70
+
71
+ // Direction tracking
72
+ let useBottomUp = false;
73
+
74
+ while (!this.frontier.isEmpty()) {
75
+ oldAwakeCount = awakeCount;
76
+
77
+ if (useBottomUp) {
78
+ // Check if we should switch back to top-down
79
+ if (awakeCount >= oldAwakeCount ||
80
+ awakeCount > this.graph.nodeCount() / this.beta) {
81
+ useBottomUp = false;
82
+ }
83
+ } else {
84
+ // Check if we should switch to bottom-up
85
+ if (scoutCount > edgesToCheck / this.alpha) {
86
+ useBottomUp = true;
87
+ }
88
+ }
89
+
90
+ if (useBottomUp) {
91
+ awakeCount = this.bottomUpStep();
92
+ } else {
93
+ scoutCount = this.topDownStep();
94
+ awakeCount = this.nextFrontier.size();
95
+ }
96
+
97
+ // Swap frontiers
98
+ this.frontier.swap(this.nextFrontier);
99
+ this.nextFrontier.clear();
100
+
101
+ // currentDistance++; // Variable not used
102
+ edgesToCheck = this.calculateEdgesToCheck();
103
+ }
104
+
105
+ return this.buildResult();
106
+ }
107
+
108
+ /**
109
+ * Top-down BFS step - explore from frontier
110
+ */
111
+ private topDownStep(): number {
112
+ let scoutCount = 0;
113
+
114
+ // Iterate through frontier nodes
115
+ for (const node of this.frontier) {
116
+ const currentDistance = this.distances.get(node);
117
+
118
+ for (const neighbor of this.graph.iterateNeighborIndices(node)) {
119
+ if (this.parent[neighbor] === -1) {
120
+ this.parent[neighbor] = node;
121
+ this.distances.set(neighbor, currentDistance + 1);
122
+ this.nextFrontier.add(neighbor);
123
+ scoutCount += this.graph.outDegreeByIndex(neighbor);
124
+ }
125
+ }
126
+ }
127
+
128
+ return scoutCount;
129
+ }
130
+
131
+ /**
132
+ * Bottom-up BFS step - check unvisited nodes
133
+ */
134
+ private bottomUpStep(): number {
135
+ const nodeCount = this.graph.nodeCount();
136
+ let awakeCount = 0;
137
+
138
+ // Check all unvisited nodes
139
+ for (let node = 0; node < nodeCount; node++) {
140
+ if (this.parent[node] === -1) {
141
+ // Use incoming edges for bottom-up traversal
142
+ for (const neighbor of this.graph.iterateIncomingNeighborIndices(node)) {
143
+ if (this.frontier.has(neighbor)) {
144
+ this.parent[node] = neighbor;
145
+ const neighborDist = this.distances.get(neighbor);
146
+ this.distances.set(node, neighborDist + 1);
147
+ this.nextFrontier.add(node);
148
+ awakeCount++;
149
+ break; // Found a parent, no need to check more
150
+ }
151
+ }
152
+ }
153
+ }
154
+
155
+ return awakeCount;
156
+ }
157
+
158
+ /**
159
+ * Calculate edges to check for switching heuristic
160
+ */
161
+ private calculateEdgesToCheck(): number {
162
+ let count = 0;
163
+ for (const node of this.frontier) {
164
+ count += this.graph.outDegreeByIndex(node);
165
+ }
166
+ return count;
167
+ }
168
+
169
+ /**
170
+ * Build result map from internal data structures
171
+ */
172
+ private buildResult(): BFSResult<TNodeId> {
173
+ const distances = new Map<TNodeId, number>();
174
+ const parents = new Map<TNodeId, TNodeId | null>();
175
+ let visitedCount = 0;
176
+
177
+ for (let i = 0; i < this.graph.nodeCount(); i++) {
178
+ if (this.parent[i] !== -1) {
179
+ const nodeId = this.graph.indexToNodeId(i);
180
+ distances.set(nodeId, this.distances.get(i));
181
+
182
+ if (this.parent[i] === -2) {
183
+ // Source node
184
+ parents.set(nodeId, null);
185
+ } else {
186
+ // Regular node with parent
187
+ const parentIndex = this.parent[i];
188
+ if (parentIndex !== undefined && parentIndex >= 0) {
189
+ parents.set(nodeId, this.graph.indexToNodeId(parentIndex));
190
+ }
191
+ }
192
+
193
+ visitedCount++;
194
+ }
195
+ }
196
+
197
+ return {distances, parents, visitedCount};
198
+ }
199
+
200
+ /**
201
+ * Perform multi-source BFS
202
+ */
203
+ searchMultiple(sources: TNodeId[]): BFSResult<TNodeId> {
204
+ // Initialize multiple sources
205
+ for (const source of sources) {
206
+ const sourceIndex = this.graph.nodeToIndex(source);
207
+ this.parent[sourceIndex] = -2; // Mark as source
208
+ this.frontier.add(sourceIndex);
209
+ this.distances.set(sourceIndex, 0);
210
+ }
211
+
212
+ // let currentDistance = 0; // Variable not used
213
+ let edgesToCheck = 0;
214
+ let scoutCount = 0;
215
+
216
+ // Calculate initial edges to check
217
+ for (const node of this.frontier) {
218
+ const degree = this.graph.outDegreeByIndex(node);
219
+ edgesToCheck += degree;
220
+ scoutCount += degree;
221
+ }
222
+
223
+ let awakeCount = sources.length;
224
+ let oldAwakeCount = 0;
225
+ let useBottomUp = false;
226
+
227
+ while (!this.frontier.isEmpty()) {
228
+ oldAwakeCount = awakeCount;
229
+
230
+ if (useBottomUp) {
231
+ if (awakeCount >= oldAwakeCount ||
232
+ awakeCount > this.graph.nodeCount() / this.beta) {
233
+ useBottomUp = false;
234
+ }
235
+ } else {
236
+ if (scoutCount > edgesToCheck / this.alpha) {
237
+ useBottomUp = true;
238
+ }
239
+ }
240
+
241
+ if (useBottomUp) {
242
+ awakeCount = this.bottomUpStep();
243
+ } else {
244
+ scoutCount = this.topDownStep();
245
+ awakeCount = this.nextFrontier.size();
246
+ }
247
+
248
+ this.frontier.swap(this.nextFrontier);
249
+ this.nextFrontier.clear();
250
+
251
+ // currentDistance++; // Variable not used
252
+ edgesToCheck = this.calculateEdgesToCheck();
253
+ }
254
+
255
+ return this.buildResult();
256
+ }
257
+
258
+ /**
259
+ * Reset internal state for reuse
260
+ */
261
+ reset(): void {
262
+ this.parent.fill(-1);
263
+ this.frontier.clear();
264
+ this.nextFrontier.clear();
265
+ this.distances.clear();
266
+ }
267
+ }
268
+
269
+ /**
270
+ * Convenience function for single-source BFS
271
+ */
272
+ export function directionOptimizedBFS<TNodeId = NodeId>(
273
+ graph: CSRGraph<TNodeId>,
274
+ source: TNodeId,
275
+ options?: DirectionOptimizedBFSOptions,
276
+ ): BFSResult<TNodeId> {
277
+ const bfs = new DirectionOptimizedBFS(graph, options);
278
+ return bfs.search(source);
279
+ }
280
+
281
+ /**
282
+ * Convenience function for multi-source BFS
283
+ */
284
+ export function directionOptimizedBFSMultiple<TNodeId = NodeId>(
285
+ graph: CSRGraph<TNodeId>,
286
+ sources: TNodeId[],
287
+ options?: DirectionOptimizedBFSOptions,
288
+ ): BFSResult<TNodeId> {
289
+ const bfs = new DirectionOptimizedBFS(graph, options);
290
+ return bfs.searchMultiple(sources);
291
+ }
@@ -0,0 +1,200 @@
1
+ import type {Graph} from "../core/graph.js";
2
+ import type {NodeId} from "../types/index.js";
3
+ import {CSRGraph, type ReadonlyGraph} from "./csr-graph.js";
4
+
5
+ /**
6
+ * Adapter to convert standard Graph to CSR format
7
+ *
8
+ * Provides transparent conversion while maintaining API compatibility
9
+ */
10
+ export class GraphAdapter<TNodeId = NodeId> implements ReadonlyGraph<TNodeId> {
11
+ private csrGraph: CSRGraph<TNodeId>;
12
+
13
+ constructor(graph: Graph | ReadonlyGraph<TNodeId>) {
14
+ // Check if already a CSRGraph
15
+ if (graph instanceof CSRGraph) {
16
+ this.csrGraph = graph as CSRGraph<TNodeId>;
17
+ } else {
18
+ // Convert to CSR format
19
+ this.csrGraph = this.convertToCSR(graph);
20
+ }
21
+ }
22
+
23
+ /**
24
+ * Convert standard graph to CSR format
25
+ */
26
+ private convertToCSR(graph: Graph | ReadonlyGraph<TNodeId>): CSRGraph<TNodeId> {
27
+ const adjacencyList = new Map<TNodeId, TNodeId[]>();
28
+ const weights = new Map<string, number>();
29
+
30
+ // Handle both Graph and ReadonlyGraph interfaces
31
+ if ("nodes" in graph && typeof graph.nodes === "function") {
32
+ // Build adjacency list from Graph
33
+ for (const node of graph.nodes()) {
34
+ const nodeId = (node as {id: TNodeId}).id;
35
+ const neighbors: TNodeId[] = [];
36
+
37
+ const graphTyped = graph as Graph;
38
+ for (const neighborId of graphTyped.neighbors(nodeId as NodeId)) {
39
+ neighbors.push(neighborId as TNodeId);
40
+
41
+ // Get edge weight if available
42
+ if ("getEdge" in graph && typeof graph.getEdge === "function") {
43
+ const edge = graphTyped.getEdge(nodeId as NodeId, neighborId);
44
+ if (edge?.weight !== undefined) {
45
+ weights.set(`${String(nodeId)}-${String(neighborId)}`, edge.weight);
46
+ }
47
+ }
48
+ }
49
+
50
+ adjacencyList.set(nodeId, neighbors);
51
+ }
52
+ } else if ("nodeIds" in graph && typeof graph.nodeIds === "function") {
53
+ // Handle ReadonlyGraph with nodeIds method
54
+ interface ReadonlyGraphWithNodeIds {
55
+ nodeIds(): Iterable<TNodeId>;
56
+ edges?(): Iterable<{source: TNodeId, target: TNodeId, weight?: number}>;
57
+ }
58
+ const readonlyGraph = graph as ReadonlyGraphWithNodeIds;
59
+ for (const nodeId of readonlyGraph.nodeIds()) {
60
+ adjacencyList.set(nodeId, []);
61
+ }
62
+
63
+ // Build adjacency from edges
64
+ if ("edges" in readonlyGraph && typeof readonlyGraph.edges === "function") {
65
+ for (const edge of readonlyGraph.edges()) {
66
+ const neighbors = adjacencyList.get(edge.source);
67
+ if (neighbors) {
68
+ neighbors.push(edge.target);
69
+ if (edge.weight !== undefined) {
70
+ weights.set(`${String(edge.source)}-${String(edge.target)}`, edge.weight);
71
+ }
72
+ }
73
+ }
74
+ }
75
+ }
76
+
77
+ return new CSRGraph(adjacencyList, weights.size > 0 ? weights : undefined);
78
+ }
79
+
80
+ // Delegate all methods to CSR implementation
81
+ nodeCount(): number {
82
+ return this.csrGraph.nodeCount();
83
+ }
84
+
85
+ edgeCount(): number {
86
+ return this.csrGraph.edgeCount();
87
+ }
88
+
89
+ hasNode(nodeId: TNodeId): boolean {
90
+ return this.csrGraph.hasNode(nodeId);
91
+ }
92
+
93
+ hasEdge(source: TNodeId, target: TNodeId): boolean {
94
+ return this.csrGraph.hasEdge(source, target);
95
+ }
96
+
97
+ neighbors(nodeId: TNodeId): IterableIterator<TNodeId> {
98
+ return this.csrGraph.neighbors(nodeId);
99
+ }
100
+
101
+ outDegree(nodeId: TNodeId): number {
102
+ return this.csrGraph.outDegree(nodeId);
103
+ }
104
+
105
+ nodes(): IterableIterator<TNodeId> {
106
+ return this.csrGraph.nodes();
107
+ }
108
+
109
+ /**
110
+ * Get the underlying CSR graph
111
+ */
112
+ getCSRGraph(): CSRGraph<TNodeId> {
113
+ return this.csrGraph;
114
+ }
115
+ }
116
+
117
+ /**
118
+ * Configuration for graph algorithm optimizations
119
+ */
120
+ export interface GraphAlgorithmConfig {
121
+ // Enable optimizations
122
+ useDirectionOptimizedBFS?: boolean;
123
+ useCSRFormat?: boolean;
124
+ useBitPackedStructures?: boolean;
125
+
126
+ // Algorithm-specific parameters
127
+ bfsAlpha?: number;
128
+ bfsBeta?: number;
129
+
130
+ // Memory vs speed tradeoffs
131
+ preallocateSize?: number;
132
+ enableCaching?: boolean;
133
+ }
134
+
135
+ /**
136
+ * Configure optimizations globally
137
+ * @deprecated This function is a no-op and will be removed in a future version
138
+ */
139
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
140
+ export function configureOptimizations(_config: GraphAlgorithmConfig): void {
141
+ // No-op for backward compatibility
142
+ }
143
+
144
+ /**
145
+ * Get current configuration
146
+ * @deprecated This function returns empty config and will be removed in a future version
147
+ */
148
+ export function getOptimizationConfig(): GraphAlgorithmConfig {
149
+ return {};
150
+ }
151
+
152
+ /**
153
+ * Check if a graph is already in CSR format
154
+ */
155
+ export function isCSRGraph<TNodeId = NodeId>(graph: unknown): graph is CSRGraph<TNodeId> {
156
+ return graph instanceof CSRGraph;
157
+ }
158
+
159
+ /**
160
+ * Convert any graph to CSR format
161
+ */
162
+ export function toCSRGraph<TNodeId = NodeId>(
163
+ graph: Graph | ReadonlyGraph<TNodeId> | CSRGraph<TNodeId>,
164
+ ): CSRGraph<TNodeId> {
165
+ if (isCSRGraph<TNodeId>(graph)) {
166
+ return graph;
167
+ }
168
+
169
+ const adapter = new GraphAdapter<TNodeId>(graph);
170
+ return adapter.getCSRGraph();
171
+ }
172
+
173
+ /**
174
+ * Create an optimized graph from nodes and edges
175
+ */
176
+ export function createOptimizedGraph<TNodeId = NodeId>(
177
+ nodes: TNodeId[],
178
+ edges: [TNodeId, TNodeId, number?][],
179
+ ): CSRGraph<TNodeId> {
180
+ const adjacencyList = new Map<TNodeId, TNodeId[]>();
181
+ const weights = new Map<string, number>();
182
+
183
+ // Initialize nodes
184
+ for (const node of nodes) {
185
+ adjacencyList.set(node, []);
186
+ }
187
+
188
+ // Add edges
189
+ for (const [source, target, weight] of edges) {
190
+ const neighbors = adjacencyList.get(source);
191
+ if (neighbors) {
192
+ neighbors.push(target);
193
+ if (weight !== undefined) {
194
+ weights.set(`${String(source)}-${String(target)}`, weight);
195
+ }
196
+ }
197
+ }
198
+
199
+ return new CSRGraph(adjacencyList, weights.size > 0 ? weights : undefined);
200
+ }
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Optimized graph algorithm implementations
3
+ *
4
+ * High-performance implementations using:
5
+ * - Direction-Optimized BFS
6
+ * - Compressed Sparse Row (CSR) format
7
+ * - Bit-packed data structures
8
+ */
9
+
10
+ // Configuration and optimization control
11
+ export {
12
+ configureOptimizations,
13
+ getOptimizationConfig,
14
+ type GraphAlgorithmConfig,
15
+ } from "./graph-adapter.js";
16
+
17
+ // Note: The main BFS functions in algorithms/traversal/bfs.ts automatically
18
+ // use these optimizations for large graphs (>10k nodes) when optimizations are enabled.
19
+ // No manual configuration needed - just use breadthFirstSearch, shortestPathBFS, etc.
20
+ //
21
+ // The previously exported bfsOptimized, shortestPathBFSOptimized, and
22
+ // singleSourceShortestPathBFSOptimized functions have been removed since they
23
+ // are no longer needed. All optimization happens automatically.
24
+
25
+ // BFS optimization functions have been removed. The main BFS functions
26
+ // (breadthFirstSearch, shortestPathBFS, singleSourceShortestPathBFS)
27
+ // in algorithms/traversal/bfs.ts now automatically use optimizations
28
+ // for large graphs without requiring manual configuration.
29
+
30
+ // Export data structures for advanced users
31
+ export {CompactDistanceArray, GraphBitSet, VisitedBitArray} from "./bit-packed.js";
32
+ export {CSRGraph} from "./csr-graph.js";
33
+ export {DirectionOptimizedBFS} from "./direction-optimized-bfs.js";
34
+
35
+ // Export utilities
36
+ export {createOptimizedGraph, isCSRGraph, toCSRGraph} from "./graph-adapter.js";
@@ -1,5 +1,6 @@
1
1
  import type {Graph} from "../core/graph.js";
2
2
  import type {NodeId} from "../types/index.js";
3
+ import {SeededRandom} from "../utils/math-utilities.js";
3
4
 
4
5
  /**
5
6
  * Configuration options for the GRSBM (Greedy Recursive Spectral Bisection) algorithm
@@ -95,7 +96,9 @@ export function grsbm(graph: Graph, config: GRSBMConfig = {}): GRSBMResult {
95
96
  } = config;
96
97
 
97
98
  // Set random seed for reproducibility
98
- Math.random = seedRandom(seed);
99
+ const rng = SeededRandom.createGenerator(seed);
100
+ const originalRandom = Math.random;
101
+ Math.random = rng;
99
102
 
100
103
  const nodes = Array.from(graph.nodes());
101
104
  const nodeCount = nodes.length;
@@ -228,6 +231,9 @@ export function grsbm(graph: Graph, config: GRSBMConfig = {}): GRSBMResult {
228
231
 
229
232
  assignClusterIds(root);
230
233
 
234
+ // Restore original random function
235
+ Math.random = originalRandom;
236
+
231
237
  return {
232
238
  root,
233
239
  clusters,
@@ -572,18 +578,3 @@ function calculateModularity(graph: Graph, assignment: Map<NodeId, number>): num
572
578
  return modularity;
573
579
  }
574
580
 
575
- /**
576
- * Simple seeded random number generator for reproducibility
577
- */
578
- function seedRandom(seed: number): () => number {
579
- const m = 0x80000000; // 2**31
580
- const a = 1103515245;
581
- const c = 12345;
582
-
583
- seed = seed % m;
584
-
585
- return function() {
586
- seed = ((a * seed) + c) % m;
587
- return seed / (m - 1);
588
- };
589
- }
@@ -1,5 +1,6 @@
1
1
  import type {Graph} from "../core/graph.js";
2
2
  import type {NodeId} from "../types/index.js";
3
+ import {euclideanDistance, SeededRandom} from "../utils/math-utilities.js";
3
4
 
4
5
  /**
5
6
  * Configuration options for the SynC (Synergistic Deep Graph Clustering) algorithm
@@ -60,7 +61,8 @@ export function syncClustering(graph: Graph, config: SynCConfig): SynCResult {
60
61
 
61
62
  // Set random seed for reproducibility (save original)
62
63
  const originalRandom = Math.random;
63
- Math.random = seedRandom(seed);
64
+ const rng = SeededRandom.createGenerator(seed);
65
+ Math.random = rng;
64
66
 
65
67
  const nodes = Array.from(graph.nodes());
66
68
  const nodeCount = nodes.length;
@@ -436,34 +438,3 @@ function calculateLoss(
436
438
  return clusteringLoss + (lambda * reconstructionLoss) + (lambda * regularizationLoss);
437
439
  }
438
440
 
439
- /**
440
- * Calculate Euclidean distance between two vectors
441
- */
442
- function euclideanDistance(a: number[], b: number[]): number {
443
- let sum = 0;
444
- for (let i = 0; i < a.length; i++) {
445
- const aVal = a[i];
446
- const bVal = b[i];
447
- if (aVal !== undefined && bVal !== undefined) {
448
- const diff = aVal - bVal;
449
- sum += diff * diff;
450
- }
451
- }
452
- return Math.sqrt(sum);
453
- }
454
-
455
- /**
456
- * Simple seeded random number generator for reproducibility
457
- */
458
- function seedRandom(seed: number): () => number {
459
- const m = 0x80000000; // 2**31
460
- const a = 1103515245;
461
- const c = 12345;
462
-
463
- seed = seed % m;
464
-
465
- return function() {
466
- seed = ((a * seed) + c) % m;
467
- return seed / (m - 1);
468
- };
469
- }
@@ -15,6 +15,8 @@ export interface TeraHACConfig {
15
15
  maxNodes?: number;
16
16
  /** Use graph structure for distance calculation */
17
17
  useGraphDistance?: boolean;
18
+ /** Custom warning handler (default: console.warn) */
19
+ onWarning?: (message: string) => void;
18
20
  }
19
21
 
20
22
  /**
@@ -70,8 +72,14 @@ export function teraHAC(graph: Graph, config: TeraHACConfig = {}): TeraHACResult
70
72
  distanceThreshold,
71
73
  maxNodes = 10000,
72
74
  useGraphDistance = true,
75
+ onWarning = console.warn,
73
76
  } = config;
74
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
+
75
83
  const nodes = Array.from(graph.nodes());
76
84
  const nodeCount = nodes.length;
77
85
 
@@ -80,7 +88,7 @@ export function teraHAC(graph: Graph, config: TeraHACConfig = {}): TeraHACResult
80
88
  }
81
89
 
82
90
  if (nodeCount > maxNodes) {
83
- console.warn(`Graph has ${String(nodeCount)} nodes, which exceeds maxNodes (${String(maxNodes)}). Performance may be degraded.`);
91
+ onWarning(`Graph has ${String(nodeCount)} nodes, which exceeds maxNodes (${String(maxNodes)}). Performance may be degraded.`);
84
92
  }
85
93
 
86
94
  // Initialize each node as its own cluster
@@ -112,7 +120,7 @@ export function teraHAC(graph: Graph, config: TeraHACConfig = {}): TeraHACResult
112
120
  let dendrogram: ClusterNode | undefined;
113
121
 
114
122
  // Perform agglomerative clustering
115
- while (clusters.size > 1) {
123
+ while (clusters.size > 1 && mergeCandidates.length > 0) {
116
124
  // Find closest pair of clusters
117
125
  const {cluster1Id, cluster2Id, distance} = findClosestPair(mergeCandidates);
118
126
 
@@ -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 {};