@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
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type {Graph} from "../../core/graph.js";
|
|
2
2
|
import type {NodeId} from "../../types/index.js";
|
|
3
|
+
import {bfsWithPathCounting} from "../traversal/bfs-variants.js";
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Betweenness centrality implementation using Brandes' algorithm
|
|
@@ -20,151 +21,206 @@ export interface BetweennessCentralityOptions {
|
|
|
20
21
|
* Whether to use endpoints in path counting (default: false)
|
|
21
22
|
*/
|
|
22
23
|
endpoints?: boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Whether to use optimized BFS implementation for large graphs
|
|
26
|
+
*/
|
|
27
|
+
optimized?: boolean;
|
|
23
28
|
}
|
|
24
29
|
|
|
25
30
|
/**
|
|
26
|
-
*
|
|
31
|
+
* Result of Brandes algorithm computation for a single source
|
|
27
32
|
*/
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
33
|
+
interface BrandesResult {
|
|
34
|
+
stack: NodeId[];
|
|
35
|
+
predecessors: Map<NodeId, NodeId[]>;
|
|
36
|
+
sigma: Map<NodeId, number>;
|
|
37
|
+
distance: Map<NodeId, number>;
|
|
38
|
+
}
|
|
34
39
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
40
|
+
/**
|
|
41
|
+
* Run single-source shortest path computation using BFS
|
|
42
|
+
*/
|
|
43
|
+
function brandesSingleSource(graph: Graph, source: NodeId, optimized?: boolean): BrandesResult {
|
|
44
|
+
const result = bfsWithPathCounting(graph, source, optimized !== undefined ? {optimized} : {});
|
|
45
|
+
|
|
46
|
+
return {
|
|
47
|
+
stack: result.stack,
|
|
48
|
+
predecessors: result.predecessors,
|
|
49
|
+
sigma: result.sigma,
|
|
50
|
+
distance: result.distances,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
39
53
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
54
|
+
/**
|
|
55
|
+
* Accumulate betweenness contributions from a single source
|
|
56
|
+
*/
|
|
57
|
+
function accumulateBetweenness(
|
|
58
|
+
result: BrandesResult,
|
|
59
|
+
source: NodeId,
|
|
60
|
+
centrality: Record<string, number>,
|
|
61
|
+
options: BetweennessCentralityOptions,
|
|
62
|
+
): void {
|
|
63
|
+
const {stack, predecessors, sigma} = result;
|
|
64
|
+
const delta = new Map<NodeId, number>();
|
|
65
|
+
|
|
66
|
+
// Initialize delta
|
|
67
|
+
for (const node of stack) {
|
|
68
|
+
delta.set(node, 0);
|
|
69
|
+
}
|
|
47
70
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
71
|
+
// Accumulation - back-propagation of dependencies
|
|
72
|
+
// Process nodes in reverse BFS order
|
|
73
|
+
for (let i = stack.length - 1; i >= 0; i--) {
|
|
74
|
+
const w = stack[i];
|
|
75
|
+
if (!w) {
|
|
76
|
+
continue;
|
|
54
77
|
}
|
|
55
78
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
const queue: NodeId[] = [source];
|
|
60
|
-
|
|
61
|
-
// BFS to find shortest paths
|
|
62
|
-
while (queue.length > 0) {
|
|
63
|
-
const current = queue.shift();
|
|
64
|
-
if (!current) {
|
|
65
|
-
break;
|
|
66
|
-
}
|
|
79
|
+
const wPreds = predecessors.get(w) ?? [];
|
|
80
|
+
const wSigma = sigma.get(w) ?? 0;
|
|
81
|
+
const wDelta = delta.get(w) ?? 0;
|
|
67
82
|
|
|
68
|
-
|
|
83
|
+
for (const v of wPreds) {
|
|
84
|
+
const vSigma = sigma.get(v) ?? 0;
|
|
85
|
+
const vDelta = delta.get(v) ?? 0;
|
|
69
86
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
let neighborDistance = distance.get(neighbor);
|
|
87
|
+
if (vSigma > 0 && wSigma > 0) {
|
|
88
|
+
let contribution = (vSigma / wSigma) * (1 + wDelta);
|
|
73
89
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
queue.push(neighbor);
|
|
81
|
-
distance.set(neighbor, currentDistance + 1);
|
|
82
|
-
neighborDistance = currentDistance + 1; // Update local variable
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// Shortest path to neighbor via current
|
|
86
|
-
if (neighborDistance === currentDistance + 1) {
|
|
87
|
-
const neighborSigma = sigma.get(neighbor) ?? 0;
|
|
88
|
-
const currentSigma = sigma.get(current) ?? 0;
|
|
89
|
-
sigma.set(neighbor, neighborSigma + currentSigma);
|
|
90
|
-
|
|
91
|
-
const preds = predecessors.get(neighbor);
|
|
92
|
-
if (preds) {
|
|
93
|
-
preds.push(current);
|
|
90
|
+
// Apply endpoints option: when false, exclude endpoint contributions
|
|
91
|
+
if (!options.endpoints) {
|
|
92
|
+
// For standard betweenness, don't count paths that only involve endpoints
|
|
93
|
+
const isTargetEndpoint = wPreds.length === 0 && w !== source;
|
|
94
|
+
if (isTargetEndpoint) {
|
|
95
|
+
contribution = 0; // Exclude endpoint contributions
|
|
94
96
|
}
|
|
95
97
|
}
|
|
98
|
+
|
|
99
|
+
delta.set(v, vDelta + contribution);
|
|
96
100
|
}
|
|
97
101
|
}
|
|
98
102
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
103
|
+
if (w !== source) {
|
|
104
|
+
const currentCentrality = centrality[String(w)] ?? 0;
|
|
105
|
+
centrality[String(w)] = currentCentrality + wDelta;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
105
109
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
110
|
+
/**
|
|
111
|
+
* Accumulate edge betweenness contributions from a single source
|
|
112
|
+
*/
|
|
113
|
+
function accumulateEdgeBetweenness(
|
|
114
|
+
result: BrandesResult,
|
|
115
|
+
source: NodeId,
|
|
116
|
+
edgeCentrality: Map<string, number>,
|
|
117
|
+
options: BetweennessCentralityOptions,
|
|
118
|
+
): void {
|
|
119
|
+
const {stack, predecessors, sigma} = result;
|
|
120
|
+
const delta = new Map<NodeId, number>();
|
|
121
|
+
|
|
122
|
+
// Initialize delta
|
|
123
|
+
for (const node of stack) {
|
|
124
|
+
delta.set(node, 0);
|
|
125
|
+
}
|
|
109
126
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
127
|
+
// Accumulation for edges
|
|
128
|
+
// Process nodes in reverse BFS order
|
|
129
|
+
for (let i = stack.length - 1; i >= 0; i--) {
|
|
130
|
+
const w = stack[i];
|
|
131
|
+
if (!w) {
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
113
134
|
|
|
114
|
-
|
|
115
|
-
|
|
135
|
+
const wPreds = predecessors.get(w) ?? [];
|
|
136
|
+
const wSigma = sigma.get(w) ?? 0;
|
|
137
|
+
const wDelta = delta.get(w) ?? 0;
|
|
116
138
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
contribution = 0; // Exclude endpoint contributions
|
|
123
|
-
}
|
|
124
|
-
}
|
|
139
|
+
for (const v of wPreds) {
|
|
140
|
+
const vSigma = sigma.get(v) ?? 0;
|
|
141
|
+
|
|
142
|
+
if (vSigma > 0 && wSigma > 0) {
|
|
143
|
+
let edgeContribution = (vSigma / wSigma) * (1 + wDelta);
|
|
125
144
|
|
|
126
|
-
|
|
145
|
+
// Apply endpoints option for edge betweenness
|
|
146
|
+
if (!options.endpoints) {
|
|
147
|
+
const isTargetEndpoint = wPreds.length === 0 && w !== source;
|
|
148
|
+
if (isTargetEndpoint) {
|
|
149
|
+
edgeContribution = 0; // Exclude endpoint contributions
|
|
150
|
+
}
|
|
127
151
|
}
|
|
152
|
+
|
|
153
|
+
// Update edge centrality
|
|
154
|
+
const edgeKey = `${String(v)}-${String(w)}`;
|
|
155
|
+
const currentEdgeCentrality = edgeCentrality.get(edgeKey) ?? 0;
|
|
156
|
+
edgeCentrality.set(edgeKey, currentEdgeCentrality + edgeContribution);
|
|
157
|
+
|
|
158
|
+
// Update node delta
|
|
159
|
+
const vDelta = delta.get(v) ?? 0;
|
|
160
|
+
delta.set(v, vDelta + edgeContribution);
|
|
128
161
|
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
129
165
|
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
166
|
+
/**
|
|
167
|
+
* Apply normalization to centrality values
|
|
168
|
+
*/
|
|
169
|
+
function normalizeCentrality(
|
|
170
|
+
centrality: Record<string, number>,
|
|
171
|
+
nodeCount: number,
|
|
172
|
+
isDirected: boolean,
|
|
173
|
+
): void {
|
|
174
|
+
const n = nodeCount;
|
|
175
|
+
const normalizationFactor = isDirected ?
|
|
176
|
+
(n - 1) * (n - 2) :
|
|
177
|
+
((n - 1) * (n - 2)) / 2;
|
|
178
|
+
|
|
179
|
+
if (normalizationFactor > 0) {
|
|
180
|
+
for (const key in centrality) {
|
|
181
|
+
const value = centrality[key];
|
|
182
|
+
if (value !== undefined) {
|
|
183
|
+
centrality[key] = value / normalizationFactor;
|
|
133
184
|
}
|
|
134
185
|
}
|
|
135
186
|
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Calculate betweenness centrality for all nodes using Brandes' algorithm
|
|
191
|
+
*/
|
|
192
|
+
export function betweennessCentrality(
|
|
193
|
+
graph: Graph,
|
|
194
|
+
options: BetweennessCentralityOptions = {},
|
|
195
|
+
): Record<string, number> {
|
|
196
|
+
const nodes = Array.from(graph.nodes()).map((node) => node.id);
|
|
197
|
+
const centrality: Record<string, number> = {};
|
|
198
|
+
|
|
199
|
+
// Initialize centrality scores
|
|
200
|
+
for (const nodeId of nodes) {
|
|
201
|
+
centrality[String(nodeId)] = 0;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Brandes' algorithm - run from each source
|
|
205
|
+
for (const source of nodes) {
|
|
206
|
+
const result = brandesSingleSource(graph, source, options.optimized);
|
|
207
|
+
accumulateBetweenness(result, source, centrality, options);
|
|
208
|
+
}
|
|
136
209
|
|
|
137
210
|
// For undirected graphs, divide by 2 (each shortest path is counted twice)
|
|
138
211
|
if (!graph.isDirected) {
|
|
139
212
|
for (const nodeId of nodes) {
|
|
140
213
|
const key = String(nodeId);
|
|
141
|
-
const
|
|
142
|
-
if (
|
|
143
|
-
centrality[key] =
|
|
214
|
+
const value = centrality[key];
|
|
215
|
+
if (value !== undefined) {
|
|
216
|
+
centrality[key] = value / 2;
|
|
144
217
|
}
|
|
145
218
|
}
|
|
146
219
|
}
|
|
147
220
|
|
|
148
221
|
// Normalization
|
|
149
222
|
if (options.normalized) {
|
|
150
|
-
|
|
151
|
-
let normalizationFactor: number;
|
|
152
|
-
|
|
153
|
-
if (graph.isDirected) {
|
|
154
|
-
normalizationFactor = (n - 1) * (n - 2);
|
|
155
|
-
} else {
|
|
156
|
-
normalizationFactor = ((n - 1) * (n - 2)) / 2;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
if (normalizationFactor > 0) {
|
|
160
|
-
for (const nodeId of nodes) {
|
|
161
|
-
const key = String(nodeId);
|
|
162
|
-
const currentValue = centrality[key];
|
|
163
|
-
if (currentValue !== undefined) {
|
|
164
|
-
centrality[key] = currentValue / normalizationFactor;
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
}
|
|
223
|
+
normalizeCentrality(centrality, nodes.length, graph.isDirected);
|
|
168
224
|
}
|
|
169
225
|
|
|
170
226
|
return centrality;
|
|
@@ -204,97 +260,8 @@ export function edgeBetweennessCentrality(
|
|
|
204
260
|
|
|
205
261
|
// Modified Brandes' algorithm for edge betweenness
|
|
206
262
|
for (const source of nodes) {
|
|
207
|
-
const
|
|
208
|
-
|
|
209
|
-
const sigma = new Map<NodeId, number>();
|
|
210
|
-
const distance = new Map<NodeId, number>();
|
|
211
|
-
const delta = new Map<NodeId, number>();
|
|
212
|
-
|
|
213
|
-
// Initialize
|
|
214
|
-
for (const nodeId of nodes) {
|
|
215
|
-
predecessors.set(nodeId, []);
|
|
216
|
-
sigma.set(nodeId, 0);
|
|
217
|
-
distance.set(nodeId, -1);
|
|
218
|
-
delta.set(nodeId, 0);
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
sigma.set(source, 1);
|
|
222
|
-
distance.set(source, 0);
|
|
223
|
-
|
|
224
|
-
const queue: NodeId[] = [source];
|
|
225
|
-
|
|
226
|
-
// BFS to find shortest paths
|
|
227
|
-
while (queue.length > 0) {
|
|
228
|
-
const current = queue.shift();
|
|
229
|
-
if (!current) {
|
|
230
|
-
break;
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
stack.push(current);
|
|
234
|
-
|
|
235
|
-
for (const neighbor of Array.from(graph.neighbors(current))) {
|
|
236
|
-
const currentDistance = distance.get(current);
|
|
237
|
-
let neighborDistance = distance.get(neighbor);
|
|
238
|
-
|
|
239
|
-
if (currentDistance === undefined || neighborDistance === undefined) {
|
|
240
|
-
continue;
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
if (neighborDistance < 0) {
|
|
244
|
-
queue.push(neighbor);
|
|
245
|
-
distance.set(neighbor, currentDistance + 1);
|
|
246
|
-
neighborDistance = currentDistance + 1; // Update local variable
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
if (neighborDistance === currentDistance + 1) {
|
|
250
|
-
const neighborSigma = sigma.get(neighbor) ?? 0;
|
|
251
|
-
const currentSigma = sigma.get(current) ?? 0;
|
|
252
|
-
sigma.set(neighbor, neighborSigma + currentSigma);
|
|
253
|
-
|
|
254
|
-
const preds = predecessors.get(neighbor);
|
|
255
|
-
if (preds) {
|
|
256
|
-
preds.push(current);
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
// Accumulation for edges
|
|
263
|
-
while (stack.length > 0) {
|
|
264
|
-
const w = stack.pop();
|
|
265
|
-
if (!w) {
|
|
266
|
-
break;
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
const wPreds = predecessors.get(w) ?? [];
|
|
270
|
-
const wSigma = sigma.get(w) ?? 0;
|
|
271
|
-
const wDelta = delta.get(w) ?? 0;
|
|
272
|
-
|
|
273
|
-
for (const v of wPreds) {
|
|
274
|
-
const vSigma = sigma.get(v) ?? 0;
|
|
275
|
-
|
|
276
|
-
if (vSigma > 0 && wSigma > 0) {
|
|
277
|
-
let edgeContribution = vSigma / wSigma * (1 + wDelta);
|
|
278
|
-
|
|
279
|
-
// Apply endpoints option for edge betweenness
|
|
280
|
-
if (!options.endpoints) {
|
|
281
|
-
const isTargetEndpoint = predecessors.get(w)?.length === 0 && w !== source;
|
|
282
|
-
if (isTargetEndpoint) {
|
|
283
|
-
edgeContribution = 0; // Exclude endpoint contributions
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
// Update edge centrality
|
|
288
|
-
const edgeKey = `${String(v)}-${String(w)}`;
|
|
289
|
-
const currentEdgeCentrality = edgeCentrality.get(edgeKey) ?? 0;
|
|
290
|
-
edgeCentrality.set(edgeKey, currentEdgeCentrality + edgeContribution);
|
|
291
|
-
|
|
292
|
-
// Update node delta
|
|
293
|
-
const vDelta = delta.get(v) ?? 0;
|
|
294
|
-
delta.set(v, vDelta + edgeContribution);
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
}
|
|
263
|
+
const result = brandesSingleSource(graph, source, options.optimized);
|
|
264
|
+
accumulateEdgeBetweenness(result, source, edgeCentrality, options);
|
|
298
265
|
}
|
|
299
266
|
|
|
300
267
|
// For undirected graphs, divide by 2 (each shortest path is counted twice)
|
|
@@ -307,7 +274,9 @@ export function edgeBetweennessCentrality(
|
|
|
307
274
|
// Normalization for edges
|
|
308
275
|
if (options.normalized) {
|
|
309
276
|
const n = nodes.length;
|
|
310
|
-
const normalizationFactor = graph.isDirected ?
|
|
277
|
+
const normalizationFactor = graph.isDirected ?
|
|
278
|
+
(n - 1) * (n - 2) :
|
|
279
|
+
((n - 1) * (n - 2)) / 2;
|
|
311
280
|
|
|
312
281
|
if (normalizationFactor > 0) {
|
|
313
282
|
for (const [edgeKey, centrality] of Array.from(edgeCentrality)) {
|
|
@@ -318,4 +287,3 @@ export function edgeBetweennessCentrality(
|
|
|
318
287
|
|
|
319
288
|
return edgeCentrality;
|
|
320
289
|
}
|
|
321
|
-
|