@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,455 @@
1
+ import type {NodeId} from "../types/index.js";
2
+
3
+ /**
4
+ * Interface for read-only graph operations
5
+ */
6
+ export interface ReadonlyGraph<TNodeId = NodeId> {
7
+ nodeCount(): number;
8
+ edgeCount(): number;
9
+ hasNode(nodeId: TNodeId): boolean;
10
+ hasEdge(source: TNodeId, target: TNodeId): boolean;
11
+ neighbors(nodeId: TNodeId): IterableIterator<TNodeId>;
12
+ outDegree(nodeId: TNodeId): number;
13
+ nodes(): IterableIterator<TNodeId>;
14
+ }
15
+
16
+ /**
17
+ * Internal data structure for CSR representation
18
+ */
19
+ interface CSRGraphData<TNodeId> {
20
+ // Row pointers: indices where each node's edges start
21
+ rowPointers: Uint32Array;
22
+
23
+ // Column indices: destination nodes for each edge
24
+ columnIndices: Uint32Array;
25
+
26
+ // Edge weights (optional)
27
+ edgeWeights?: Float32Array;
28
+
29
+ // Reverse edges for bottom-up BFS (optional)
30
+ reverseRowPointers?: Uint32Array;
31
+ reverseColumnIndices?: Uint32Array;
32
+
33
+ // Node ID mapping
34
+ nodeIdToIndex: Map<TNodeId, number>;
35
+ indexToNodeId: TNodeId[];
36
+ }
37
+
38
+ /**
39
+ * Compressed Sparse Row (CSR) graph representation
40
+ *
41
+ * Provides cache-efficient graph storage with sequential memory access patterns.
42
+ * Optimized for traversal operations and sparse graphs.
43
+ */
44
+ export class CSRGraph<TNodeId = NodeId> implements ReadonlyGraph<TNodeId> {
45
+ private data: CSRGraphData<TNodeId>;
46
+
47
+ constructor(adjacencyList: Map<TNodeId, TNodeId[]>, weights?: Map<string, number>, buildReverse = true) {
48
+ this.data = this.buildCSR(adjacencyList, weights, buildReverse);
49
+ }
50
+
51
+ /**
52
+ * Build CSR structure from adjacency list
53
+ */
54
+ private buildCSR(
55
+ adjacencyList: Map<TNodeId, TNodeId[]>,
56
+ weights?: Map<string, number>,
57
+ buildReverse = true,
58
+ ): CSRGraphData<TNodeId> {
59
+ // Collect all unique nodes (both sources and targets)
60
+ const allNodes = new Set<TNodeId>();
61
+ for (const [source, neighbors] of adjacencyList) {
62
+ allNodes.add(source);
63
+ for (const neighbor of neighbors) {
64
+ allNodes.add(neighbor);
65
+ }
66
+ }
67
+
68
+ // Sort nodes for consistent ordering. Numeric IDs sort numerically,
69
+ // string IDs sort lexicographically for predictable iteration order.
70
+ const nodes = Array.from(allNodes).sort((a, b) => {
71
+ if (typeof a === "number" && typeof b === "number") {
72
+ return a - b;
73
+ }
74
+
75
+ return String(a).localeCompare(String(b));
76
+ });
77
+ const nodeCount = nodes.length;
78
+
79
+ // Build node mappings
80
+ const nodeIdToIndex = new Map<TNodeId, number>();
81
+ const indexToNodeId: TNodeId[] = [];
82
+
83
+ nodes.forEach((nodeId, index) => {
84
+ nodeIdToIndex.set(nodeId, index);
85
+ indexToNodeId[index] = nodeId;
86
+ });
87
+
88
+ // Count total edges
89
+ let edgeCount = 0;
90
+ for (const neighbors of adjacencyList.values()) {
91
+ edgeCount += neighbors.length;
92
+ }
93
+
94
+ // Allocate arrays
95
+ const rowPointers = new Uint32Array(nodeCount + 1);
96
+ const columnIndices = new Uint32Array(edgeCount);
97
+ const edgeWeights = weights ? new Float32Array(edgeCount) : undefined;
98
+
99
+ // Build CSR structure
100
+ let currentEdge = 0;
101
+ for (let i = 0; i < nodeCount; i++) {
102
+ rowPointers[i] = currentEdge;
103
+ const nodeId = indexToNodeId[i];
104
+ if (nodeId === undefined) {
105
+ throw new Error(`Invalid node index ${String(i)}`);
106
+ }
107
+
108
+ const neighbors = adjacencyList.get(nodeId) ?? [];
109
+
110
+ // Sort neighbors for better cache locality and binary search
111
+ const sortedNeighbors = neighbors
112
+ .map((n) => {
113
+ const index = nodeIdToIndex.get(n);
114
+ if (index === undefined) {
115
+ throw new Error(`Node ${String(n)} not found in nodeIdToIndex map`);
116
+ }
117
+
118
+ return {id: n, index};
119
+ })
120
+ .sort((a, b) => a.index - b.index);
121
+
122
+ for (const neighbor of sortedNeighbors) {
123
+ columnIndices[currentEdge] = neighbor.index;
124
+
125
+ if (edgeWeights && weights) {
126
+ const edgeKey = `${String(nodeId)}-${String(neighbor.id)}`;
127
+ const weight = weights.get(edgeKey);
128
+ if (weight !== undefined) {
129
+ edgeWeights[currentEdge] = weight;
130
+ } else {
131
+ edgeWeights[currentEdge] = 1;
132
+ }
133
+ }
134
+
135
+ currentEdge++;
136
+ }
137
+ }
138
+ rowPointers[nodeCount] = currentEdge;
139
+
140
+ const result: CSRGraphData<TNodeId> = {
141
+ rowPointers,
142
+ columnIndices,
143
+ nodeIdToIndex,
144
+ indexToNodeId,
145
+ };
146
+
147
+ if (edgeWeights) {
148
+ result.edgeWeights = edgeWeights;
149
+ }
150
+
151
+ // Build reverse edges for bottom-up BFS
152
+ if (buildReverse) {
153
+ const reverseAdjacency = new Map<number, number[]>();
154
+ for (let i = 0; i < nodeCount; i++) {
155
+ reverseAdjacency.set(i, []);
156
+ }
157
+
158
+ // Build reverse adjacency from forward edges
159
+ for (let i = 0; i < nodeCount; i++) {
160
+ const start = rowPointers[i];
161
+ const end = rowPointers[i + 1];
162
+ if (start !== undefined && end !== undefined) {
163
+ for (let j = start; j < end; j++) {
164
+ const target = columnIndices[j];
165
+ if (target !== undefined) {
166
+ reverseAdjacency.get(target)?.push(i);
167
+ }
168
+ }
169
+ }
170
+ }
171
+
172
+ // Build reverse CSR
173
+ const reverseRowPointers = new Uint32Array(nodeCount + 1);
174
+ const reverseColumnIndices = new Uint32Array(edgeCount);
175
+
176
+ let reverseEdge = 0;
177
+ for (let i = 0; i < nodeCount; i++) {
178
+ reverseRowPointers[i] = reverseEdge;
179
+ const incoming = reverseAdjacency.get(i) ?? [];
180
+ incoming.sort((a, b) => a - b);
181
+
182
+ for (const source of incoming) {
183
+ reverseColumnIndices[reverseEdge++] = source;
184
+ }
185
+ }
186
+ reverseRowPointers[nodeCount] = reverseEdge;
187
+
188
+ result.reverseRowPointers = reverseRowPointers;
189
+ result.reverseColumnIndices = reverseColumnIndices;
190
+ }
191
+
192
+ return result;
193
+ }
194
+
195
+ // Core API methods
196
+ nodeCount(): number {
197
+ return this.data.indexToNodeId.length;
198
+ }
199
+
200
+ edgeCount(): number {
201
+ return this.data.columnIndices.length;
202
+ }
203
+
204
+ hasNode(nodeId: TNodeId): boolean {
205
+ return this.data.nodeIdToIndex.has(nodeId);
206
+ }
207
+
208
+ hasEdge(source: TNodeId, target: TNodeId): boolean {
209
+ const sourceIndex = this.data.nodeIdToIndex.get(source);
210
+ const targetIndex = this.data.nodeIdToIndex.get(target);
211
+
212
+ if (sourceIndex === undefined || targetIndex === undefined) {
213
+ return false;
214
+ }
215
+
216
+ const start = this.data.rowPointers[sourceIndex];
217
+ const end = this.data.rowPointers[sourceIndex + 1];
218
+
219
+ // Binary search for target in sorted neighbors
220
+ if (start === undefined || end === undefined) {
221
+ return false;
222
+ }
223
+
224
+ return this.binarySearch(this.data.columnIndices, targetIndex, start, end) !== -1;
225
+ }
226
+
227
+ /**
228
+ * Get neighbors as node IDs
229
+ */
230
+ neighbors(nodeId: TNodeId): IterableIterator<TNodeId> {
231
+ const nodeIndex = this.data.nodeIdToIndex.get(nodeId);
232
+ if (nodeIndex === undefined) {
233
+ return new Set<TNodeId>().values();
234
+ }
235
+
236
+ const start = this.data.rowPointers[nodeIndex];
237
+ const end = this.data.rowPointers[nodeIndex + 1];
238
+
239
+ const {columnIndices, indexToNodeId} = this.data;
240
+ function* generateNeighbors(): Generator<TNodeId> {
241
+ if (start !== undefined && end !== undefined) {
242
+ for (let i = start; i < end; i++) {
243
+ const idx = columnIndices[i];
244
+ if (idx !== undefined) {
245
+ const nodeId = indexToNodeId[idx];
246
+ if (nodeId !== undefined) {
247
+ yield nodeId;
248
+ }
249
+ }
250
+ }
251
+ }
252
+ }
253
+
254
+ return generateNeighbors();
255
+ }
256
+
257
+ /**
258
+ * Get all nodes
259
+ */
260
+ nodes(): IterableIterator<TNodeId> {
261
+ return this.data.indexToNodeId.values();
262
+ }
263
+
264
+ /**
265
+ * Get neighbors as indices (internal use)
266
+ */
267
+ getNeighborIndices(nodeIndex: number): number[] {
268
+ if (nodeIndex < 0 || nodeIndex >= this.data.indexToNodeId.length) {
269
+ return [];
270
+ }
271
+
272
+ const start = this.data.rowPointers[nodeIndex];
273
+ const end = this.data.rowPointers[nodeIndex + 1];
274
+ if (start === undefined || end === undefined) {
275
+ return [];
276
+ }
277
+
278
+ return Array.from(this.data.columnIndices.subarray(start, end));
279
+ }
280
+
281
+ outDegree(nodeId: TNodeId): number {
282
+ const nodeIndex = this.data.nodeIdToIndex.get(nodeId);
283
+ if (nodeIndex === undefined) {
284
+ return 0;
285
+ }
286
+
287
+ return this.outDegreeByIndex(nodeIndex);
288
+ }
289
+
290
+ /**
291
+ * Get out-degree by index (internal use)
292
+ */
293
+ outDegreeByIndex(nodeIndex: number): number {
294
+ const start = this.data.rowPointers[nodeIndex];
295
+ const end = this.data.rowPointers[nodeIndex + 1];
296
+ if (start !== undefined && end !== undefined) {
297
+ return end - start;
298
+ }
299
+
300
+ return 0;
301
+ }
302
+
303
+ /**
304
+ * Iterator support for neighbor indices
305
+ */
306
+ *iterateNeighborIndices(nodeIndex: number): Generator<number> {
307
+ const start = this.data.rowPointers[nodeIndex];
308
+ const end = this.data.rowPointers[nodeIndex + 1];
309
+
310
+ if (start !== undefined && end !== undefined) {
311
+ for (let i = start; i < end; i++) {
312
+ const value = this.data.columnIndices[i];
313
+ if (value !== undefined) {
314
+ yield value;
315
+ }
316
+ }
317
+ }
318
+ }
319
+
320
+ /**
321
+ * Iterator support for incoming neighbor indices (for bottom-up BFS)
322
+ */
323
+ *iterateIncomingNeighborIndices(nodeIndex: number): Generator<number> {
324
+ if (!this.data.reverseRowPointers || !this.data.reverseColumnIndices) {
325
+ return;
326
+ }
327
+
328
+ const start = this.data.reverseRowPointers[nodeIndex];
329
+ const end = this.data.reverseRowPointers[nodeIndex + 1];
330
+
331
+ if (start !== undefined && end !== undefined) {
332
+ for (let i = start; i < end; i++) {
333
+ const value = this.data.reverseColumnIndices[i];
334
+ if (value !== undefined) {
335
+ yield value;
336
+ }
337
+ }
338
+ }
339
+ }
340
+
341
+ /**
342
+ * Convert node ID to index
343
+ */
344
+ nodeToIndex(nodeId: TNodeId): number {
345
+ const index = this.data.nodeIdToIndex.get(nodeId);
346
+ if (index === undefined) {
347
+ throw new Error(`Node ${String(nodeId)} not found in graph`);
348
+ }
349
+
350
+ return index;
351
+ }
352
+
353
+ /**
354
+ * Convert index to node ID
355
+ */
356
+ indexToNodeId(index: number): TNodeId {
357
+ const nodeId = this.data.indexToNodeId[index];
358
+ if (nodeId === undefined) {
359
+ throw new Error(`Index ${String(index)} out of bounds`);
360
+ }
361
+
362
+ return nodeId;
363
+ }
364
+
365
+ /**
366
+ * Get edge weight
367
+ */
368
+ getEdgeWeight(source: TNodeId, target: TNodeId): number | undefined {
369
+ if (!this.data.edgeWeights) {
370
+ return undefined;
371
+ }
372
+
373
+ const sourceIndex = this.data.nodeIdToIndex.get(source);
374
+ const targetIndex = this.data.nodeIdToIndex.get(target);
375
+
376
+ if (sourceIndex === undefined || targetIndex === undefined) {
377
+ return undefined;
378
+ }
379
+
380
+ const start = this.data.rowPointers[sourceIndex];
381
+ const end = this.data.rowPointers[sourceIndex + 1];
382
+
383
+ if (start === undefined || end === undefined) {
384
+ return undefined;
385
+ }
386
+
387
+ const edgeIndex = this.binarySearch(this.data.columnIndices, targetIndex, start, end);
388
+ if (edgeIndex === -1) {
389
+ return undefined;
390
+ }
391
+
392
+ return this.data.edgeWeights[edgeIndex];
393
+ }
394
+
395
+ /**
396
+ * Binary search for target in sorted array
397
+ */
398
+ private binarySearch(arr: Uint32Array, target: number, start: number, end: number): number {
399
+ let left = start;
400
+ let right = end - 1;
401
+
402
+ while (left <= right) {
403
+ const mid = (left + right) >>> 1;
404
+ const value = arr[mid];
405
+
406
+ if (value === undefined) {
407
+ return -1;
408
+ }
409
+
410
+ if (value === target) {
411
+ return mid;
412
+ }
413
+
414
+ if (value < target) {
415
+ left = mid + 1;
416
+ } else {
417
+ right = mid - 1;
418
+ }
419
+ }
420
+
421
+ return -1;
422
+ }
423
+
424
+ /**
425
+ * Create CSR graph from standard Graph
426
+ */
427
+ static fromGraph<TNodeId = NodeId>(graph: {
428
+ nodes(): IterableIterator<{id: TNodeId}>;
429
+ neighbors(nodeId: TNodeId): IterableIterator<TNodeId>;
430
+ hasNode(nodeId: TNodeId): boolean;
431
+ getEdge?(source: TNodeId, target: TNodeId): {weight?: number} | undefined;
432
+ }): CSRGraph<TNodeId> {
433
+ const adjacencyList = new Map<TNodeId, TNodeId[]>();
434
+ const weights = new Map<string, number>();
435
+
436
+ // Build adjacency list
437
+ for (const node of graph.nodes()) {
438
+ const neighbors: TNodeId[] = [];
439
+ for (const neighbor of graph.neighbors(node.id)) {
440
+ neighbors.push(neighbor);
441
+
442
+ // Get edge weight if available
443
+ if (graph.getEdge) {
444
+ const edge = graph.getEdge(node.id, neighbor);
445
+ if (edge?.weight !== undefined) {
446
+ weights.set(`${String(node.id)}-${String(neighbor)}`, edge.weight);
447
+ }
448
+ }
449
+ }
450
+ adjacencyList.set(node.id, neighbors);
451
+ }
452
+
453
+ return new CSRGraph(adjacencyList, weights.size > 0 ? weights : undefined);
454
+ }
455
+ }