@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.
- package/README.md +490 -336
- 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 +40 -21
- 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 +1 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +4 -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.map +1 -1
- package/dist/src/research/grsbm.js +6 -14
- package/dist/src/research/grsbm.js.map +1 -1
- package/dist/src/research/sync.d.ts.map +1 -1
- package/dist/src/research/sync.js +3 -29
- package/dist/src/research/sync.js.map +1 -1
- package/dist/src/research/terahac.d.ts +2 -0
- package/dist/src/research/terahac.d.ts.map +1 -1
- package/dist/src/research/terahac.js +7 -3
- package/dist/src/research/terahac.js.map +1 -1
- 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 +18 -3
- 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 +44 -20
- 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 +6 -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 +7 -16
- package/src/research/sync.ts +3 -32
- package/src/research/terahac.ts +10 -2
- 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,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";
|
package/src/research/grsbm.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
}
|
package/src/research/sync.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
}
|
package/src/research/terahac.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
|
package/src/types/index.ts
CHANGED
|
@@ -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
|
}
|