@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.
- package/README.md +820 -235
- package/dist/algorithms.d.ts +42 -2
- package/dist/algorithms.js +11099 -2
- package/dist/algorithms.js.map +1 -1
- package/dist/src/algorithms/centrality/betweenness.d.ts +4 -0
- package/dist/src/algorithms/centrality/betweenness.d.ts.map +1 -1
- package/dist/src/algorithms/centrality/betweenness.js +124 -170
- package/dist/src/algorithms/centrality/betweenness.js.map +1 -1
- package/dist/src/algorithms/centrality/closeness.d.ts +26 -1
- package/dist/src/algorithms/centrality/closeness.d.ts.map +1 -1
- package/dist/src/algorithms/centrality/closeness.js +56 -149
- package/dist/src/algorithms/centrality/closeness.js.map +1 -1
- package/dist/src/algorithms/centrality/degree.js +3 -3
- package/dist/src/algorithms/centrality/degree.js.map +1 -1
- package/dist/src/algorithms/centrality/delta-pagerank-simple.d.ts +39 -0
- package/dist/src/algorithms/centrality/delta-pagerank-simple.d.ts.map +1 -0
- package/dist/src/algorithms/centrality/delta-pagerank-simple.js +286 -0
- package/dist/src/algorithms/centrality/delta-pagerank-simple.js.map +1 -0
- package/dist/src/algorithms/centrality/delta-pagerank.d.ts +80 -0
- package/dist/src/algorithms/centrality/delta-pagerank.d.ts.map +1 -0
- package/dist/src/algorithms/centrality/delta-pagerank.js +294 -0
- package/dist/src/algorithms/centrality/delta-pagerank.js.map +1 -0
- package/dist/src/algorithms/centrality/index.d.ts +2 -0
- package/dist/src/algorithms/centrality/index.d.ts.map +1 -1
- package/dist/src/algorithms/centrality/index.js +1 -0
- package/dist/src/algorithms/centrality/index.js.map +1 -1
- package/dist/src/algorithms/centrality/pagerank.d.ts +17 -0
- package/dist/src/algorithms/centrality/pagerank.d.ts.map +1 -1
- package/dist/src/algorithms/centrality/pagerank.js +45 -0
- package/dist/src/algorithms/centrality/pagerank.js.map +1 -1
- package/dist/src/algorithms/community/girvan-newman.d.ts.map +1 -1
- package/dist/src/algorithms/community/girvan-newman.js +47 -54
- package/dist/src/algorithms/community/girvan-newman.js.map +1 -1
- package/dist/src/algorithms/community/index.d.ts +2 -0
- package/dist/src/algorithms/community/index.d.ts.map +1 -1
- package/dist/src/algorithms/community/index.js.map +1 -1
- package/dist/src/algorithms/community/label-propagation.d.ts +13 -3
- package/dist/src/algorithms/community/label-propagation.d.ts.map +1 -1
- package/dist/src/algorithms/community/label-propagation.js +50 -35
- package/dist/src/algorithms/community/label-propagation.js.map +1 -1
- package/dist/src/algorithms/community/leiden.d.ts +3 -2
- package/dist/src/algorithms/community/leiden.d.ts.map +1 -1
- package/dist/src/algorithms/community/leiden.js +34 -39
- package/dist/src/algorithms/community/leiden.js.map +1 -1
- package/dist/src/algorithms/community/louvain-optimized.d.ts +112 -0
- package/dist/src/algorithms/community/louvain-optimized.d.ts.map +1 -0
- package/dist/src/algorithms/community/louvain-optimized.js +303 -0
- package/dist/src/algorithms/community/louvain-optimized.js.map +1 -0
- package/dist/src/algorithms/community/louvain.d.ts.map +1 -1
- package/dist/src/algorithms/community/louvain.js +17 -76
- package/dist/src/algorithms/community/louvain.js.map +1 -1
- package/dist/src/algorithms/community/modularity-utils.d.ts +59 -0
- package/dist/src/algorithms/community/modularity-utils.d.ts.map +1 -0
- package/dist/src/algorithms/community/modularity-utils.js +112 -0
- package/dist/src/algorithms/community/modularity-utils.js.map +1 -0
- package/dist/src/algorithms/matching/bipartite.d.ts.map +1 -1
- package/dist/src/algorithms/matching/bipartite.js +8 -40
- package/dist/src/algorithms/matching/bipartite.js.map +1 -1
- package/dist/src/algorithms/shortest-path/bellman-ford.d.ts.map +1 -1
- package/dist/src/algorithms/shortest-path/bellman-ford.js +1 -12
- package/dist/src/algorithms/shortest-path/bellman-ford.js.map +1 -1
- package/dist/src/algorithms/shortest-path/bidirectional-dijkstra.d.ts +37 -0
- package/dist/src/algorithms/shortest-path/bidirectional-dijkstra.d.ts.map +1 -0
- package/dist/src/algorithms/shortest-path/bidirectional-dijkstra.js +193 -0
- package/dist/src/algorithms/shortest-path/bidirectional-dijkstra.js.map +1 -0
- package/dist/src/algorithms/shortest-path/dijkstra.d.ts +5 -2
- package/dist/src/algorithms/shortest-path/dijkstra.d.ts.map +1 -1
- package/dist/src/algorithms/shortest-path/dijkstra.js +15 -14
- package/dist/src/algorithms/shortest-path/dijkstra.js.map +1 -1
- package/dist/src/algorithms/traversal/bfs-unified.d.ts +29 -0
- package/dist/src/algorithms/traversal/bfs-unified.d.ts.map +1 -0
- package/dist/src/algorithms/traversal/bfs-unified.js +333 -0
- package/dist/src/algorithms/traversal/bfs-unified.js.map +1 -0
- package/dist/src/algorithms/traversal/bfs-variants.d.ts +54 -0
- package/dist/src/algorithms/traversal/bfs-variants.d.ts.map +1 -0
- package/dist/src/algorithms/traversal/bfs-variants.js +368 -0
- package/dist/src/algorithms/traversal/bfs-variants.js.map +1 -0
- package/dist/src/algorithms/traversal/bfs.d.ts +4 -18
- package/dist/src/algorithms/traversal/bfs.d.ts.map +1 -1
- package/dist/src/algorithms/traversal/bfs.js +5 -182
- package/dist/src/algorithms/traversal/bfs.js.map +1 -1
- package/dist/src/benchmark-all-algorithms.d.ts +23 -0
- package/dist/src/benchmark-all-algorithms.d.ts.map +1 -0
- package/dist/src/benchmark-all-algorithms.js +462 -0
- package/dist/src/benchmark-all-algorithms.js.map +1 -0
- package/dist/src/clustering/hierarchical.d.ts +16 -15
- package/dist/src/clustering/hierarchical.d.ts.map +1 -1
- package/dist/src/clustering/hierarchical.js +209 -223
- package/dist/src/clustering/hierarchical.js.map +1 -1
- package/dist/src/clustering/index.d.ts +4 -2
- package/dist/src/clustering/index.d.ts.map +1 -1
- package/dist/src/clustering/index.js +2 -2
- package/dist/src/clustering/index.js.map +1 -1
- package/dist/src/clustering/k-core.d.ts +16 -15
- package/dist/src/clustering/k-core.d.ts.map +1 -1
- package/dist/src/clustering/k-core.js +57 -21
- package/dist/src/clustering/k-core.js.map +1 -1
- package/dist/src/clustering/spectral.d.ts +8 -0
- package/dist/src/clustering/spectral.d.ts.map +1 -1
- package/dist/src/clustering/spectral.js +158 -35
- package/dist/src/clustering/spectral.js.map +1 -1
- package/dist/src/core/graph.js +4 -4
- package/dist/src/core/graph.js.map +1 -1
- package/dist/src/flow/ford-fulkerson.d.ts +17 -11
- package/dist/src/flow/ford-fulkerson.d.ts.map +1 -1
- package/dist/src/flow/ford-fulkerson.js +141 -217
- package/dist/src/flow/ford-fulkerson.js.map +1 -1
- package/dist/src/flow/min-cut.d.ts +6 -5
- package/dist/src/flow/min-cut.d.ts.map +1 -1
- package/dist/src/flow/min-cut.js +17 -8
- package/dist/src/flow/min-cut.js.map +1 -1
- package/dist/src/index.d.ts +2 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +6 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/link-prediction/adamic-adar.d.ts.map +1 -1
- package/dist/src/link-prediction/adamic-adar.js +17 -26
- package/dist/src/link-prediction/adamic-adar.js.map +1 -1
- package/dist/src/link-prediction/common-neighbors.d.ts.map +1 -1
- package/dist/src/link-prediction/common-neighbors.js +7 -11
- package/dist/src/link-prediction/common-neighbors.js.map +1 -1
- package/dist/src/optimized/bit-packed.d.ts +143 -0
- package/dist/src/optimized/bit-packed.d.ts.map +1 -0
- package/dist/src/optimized/bit-packed.js +292 -0
- package/dist/src/optimized/bit-packed.js.map +1 -0
- package/dist/src/optimized/csr-graph.d.ts +86 -0
- package/dist/src/optimized/csr-graph.d.ts.map +1 -0
- package/dist/src/optimized/csr-graph.js +341 -0
- package/dist/src/optimized/csr-graph.js.map +1 -0
- package/dist/src/optimized/direction-optimized-bfs.d.ts +74 -0
- package/dist/src/optimized/direction-optimized-bfs.d.ts.map +1 -0
- package/dist/src/optimized/direction-optimized-bfs.js +223 -0
- package/dist/src/optimized/direction-optimized-bfs.js.map +1 -0
- package/dist/src/optimized/graph-adapter.d.ts +62 -0
- package/dist/src/optimized/graph-adapter.d.ts.map +1 -0
- package/dist/src/optimized/graph-adapter.js +146 -0
- package/dist/src/optimized/graph-adapter.js.map +1 -0
- package/dist/src/optimized/index.d.ts +14 -0
- package/dist/src/optimized/index.d.ts.map +1 -0
- package/dist/src/optimized/index.js +28 -0
- package/dist/src/optimized/index.js.map +1 -0
- package/dist/src/research/grsbm.d.ts +83 -0
- package/dist/src/research/grsbm.d.ts.map +1 -0
- package/dist/src/research/grsbm.js +403 -0
- package/dist/src/research/grsbm.js.map +1 -0
- package/dist/src/research/index.d.ts +12 -0
- package/dist/src/research/index.d.ts.map +1 -0
- package/dist/src/research/index.js +14 -0
- package/dist/src/research/index.js.map +1 -0
- package/dist/src/research/sync.d.ts +49 -0
- package/dist/src/research/sync.d.ts.map +1 -0
- package/dist/src/research/sync.js +316 -0
- package/dist/src/research/sync.js.map +1 -0
- package/dist/src/research/terahac.d.ts +65 -0
- package/dist/src/research/terahac.d.ts.map +1 -0
- package/dist/src/research/terahac.js +379 -0
- package/dist/src/research/terahac.js.map +1 -0
- package/dist/src/types/index.d.ts +8 -0
- package/dist/src/types/index.d.ts.map +1 -1
- package/dist/src/utils/algorithm-utilities.d.ts +2 -0
- package/dist/src/utils/algorithm-utilities.d.ts.map +1 -0
- package/dist/src/utils/algorithm-utilities.js +2 -0
- package/dist/src/utils/algorithm-utilities.js.map +1 -0
- package/dist/src/utils/graph-converters.d.ts +79 -0
- package/dist/src/utils/graph-converters.d.ts.map +1 -0
- package/dist/src/utils/graph-converters.js +196 -0
- package/dist/src/utils/graph-converters.js.map +1 -0
- package/dist/src/utils/graph-utilities.d.ts +62 -0
- package/dist/src/utils/graph-utilities.d.ts.map +1 -0
- package/dist/src/utils/graph-utilities.js +148 -0
- package/dist/src/utils/graph-utilities.js.map +1 -0
- package/dist/src/utils/index.d.ts +5 -0
- package/dist/src/utils/index.d.ts.map +1 -0
- package/dist/src/utils/index.js +5 -0
- package/dist/src/utils/index.js.map +1 -0
- package/dist/src/utils/math-utilities.d.ts +52 -0
- package/dist/src/utils/math-utilities.d.ts.map +1 -0
- package/dist/src/utils/math-utilities.js +123 -0
- package/dist/src/utils/math-utilities.js.map +1 -0
- package/dist/src/utils/matrix-utilities.d.ts +2 -0
- package/dist/src/utils/matrix-utilities.d.ts.map +1 -0
- package/dist/src/utils/matrix-utilities.js +2 -0
- package/dist/src/utils/matrix-utilities.js.map +1 -0
- package/dist/src/utils/optimization-helpers.d.ts +33 -0
- package/dist/src/utils/optimization-helpers.d.ts.map +1 -0
- package/dist/src/utils/optimization-helpers.js +47 -0
- package/dist/src/utils/optimization-helpers.js.map +1 -0
- package/package.json +28 -4
- package/src/algorithms/centrality/betweenness.ts +167 -199
- package/src/algorithms/centrality/closeness.ts +73 -191
- package/src/algorithms/centrality/degree.ts +3 -3
- package/src/algorithms/centrality/delta-pagerank-simple.ts +366 -0
- package/src/algorithms/centrality/delta-pagerank.ts +435 -0
- package/src/algorithms/centrality/index.ts +2 -0
- package/src/algorithms/centrality/pagerank.ts +66 -0
- package/src/algorithms/community/girvan-newman.ts +52 -59
- package/src/algorithms/community/index.ts +2 -0
- package/src/algorithms/community/label-propagation.ts +64 -35
- package/src/algorithms/community/leiden.ts +40 -41
- package/src/algorithms/community/louvain-optimized.ts +445 -0
- package/src/algorithms/community/louvain.ts +24 -104
- package/src/algorithms/community/modularity-utils.ts +146 -0
- package/src/algorithms/matching/bipartite.ts +8 -44
- package/src/algorithms/shortest-path/bellman-ford.ts +1 -14
- package/src/algorithms/shortest-path/bidirectional-dijkstra.ts +248 -0
- package/src/algorithms/shortest-path/dijkstra.ts +17 -15
- package/src/algorithms/traversal/bfs-unified.ts +433 -0
- package/src/algorithms/traversal/bfs-variants.ts +496 -0
- package/src/algorithms/traversal/bfs.ts +10 -228
- package/src/benchmark-all-algorithms.ts +548 -0
- package/src/clustering/hierarchical.ts +241 -251
- package/src/clustering/index.ts +4 -2
- package/src/clustering/k-core.ts +87 -39
- package/src/clustering/spectral.ts +185 -34
- package/src/core/graph.ts +4 -4
- package/src/flow/ford-fulkerson.ts +186 -254
- package/src/flow/min-cut.ts +21 -11
- package/src/index.ts +9 -0
- package/src/link-prediction/adamic-adar.ts +17 -30
- package/src/link-prediction/common-neighbors.ts +7 -16
- package/src/optimized/bit-packed.ts +340 -0
- package/src/optimized/csr-graph.ts +455 -0
- package/src/optimized/direction-optimized-bfs.ts +291 -0
- package/src/optimized/graph-adapter.ts +200 -0
- package/src/optimized/index.ts +36 -0
- package/src/research/grsbm.ts +580 -0
- package/src/research/index.ts +17 -0
- package/src/research/sync.ts +440 -0
- package/src/research/terahac.ts +514 -0
- package/src/types/index.ts +8 -0
- package/src/utils/algorithm-utilities.ts +2 -0
- package/src/utils/graph-converters.ts +241 -0
- package/src/utils/graph-utilities.ts +199 -0
- package/src/utils/index.ts +4 -0
- package/src/utils/math-utilities.ts +140 -0
- package/src/utils/matrix-utilities.ts +2 -0
- 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
|
+
}
|