@graphty/algorithms 1.2.0 → 1.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (230) hide show
  1. package/README.md +490 -336
  2. package/dist/algorithms.d.ts +42 -2
  3. package/dist/algorithms.js +11099 -2
  4. package/dist/algorithms.js.map +1 -1
  5. package/dist/src/algorithms/centrality/betweenness.d.ts +4 -0
  6. package/dist/src/algorithms/centrality/betweenness.d.ts.map +1 -1
  7. package/dist/src/algorithms/centrality/betweenness.js +124 -170
  8. package/dist/src/algorithms/centrality/betweenness.js.map +1 -1
  9. package/dist/src/algorithms/centrality/closeness.d.ts +26 -1
  10. package/dist/src/algorithms/centrality/closeness.d.ts.map +1 -1
  11. package/dist/src/algorithms/centrality/closeness.js +56 -149
  12. package/dist/src/algorithms/centrality/closeness.js.map +1 -1
  13. package/dist/src/algorithms/centrality/degree.js +3 -3
  14. package/dist/src/algorithms/centrality/degree.js.map +1 -1
  15. package/dist/src/algorithms/centrality/delta-pagerank-simple.d.ts +39 -0
  16. package/dist/src/algorithms/centrality/delta-pagerank-simple.d.ts.map +1 -0
  17. package/dist/src/algorithms/centrality/delta-pagerank-simple.js +286 -0
  18. package/dist/src/algorithms/centrality/delta-pagerank-simple.js.map +1 -0
  19. package/dist/src/algorithms/centrality/delta-pagerank.d.ts +80 -0
  20. package/dist/src/algorithms/centrality/delta-pagerank.d.ts.map +1 -0
  21. package/dist/src/algorithms/centrality/delta-pagerank.js +294 -0
  22. package/dist/src/algorithms/centrality/delta-pagerank.js.map +1 -0
  23. package/dist/src/algorithms/centrality/index.d.ts +2 -0
  24. package/dist/src/algorithms/centrality/index.d.ts.map +1 -1
  25. package/dist/src/algorithms/centrality/index.js +1 -0
  26. package/dist/src/algorithms/centrality/index.js.map +1 -1
  27. package/dist/src/algorithms/centrality/pagerank.d.ts +17 -0
  28. package/dist/src/algorithms/centrality/pagerank.d.ts.map +1 -1
  29. package/dist/src/algorithms/centrality/pagerank.js +45 -0
  30. package/dist/src/algorithms/centrality/pagerank.js.map +1 -1
  31. package/dist/src/algorithms/community/girvan-newman.d.ts.map +1 -1
  32. package/dist/src/algorithms/community/girvan-newman.js +47 -54
  33. package/dist/src/algorithms/community/girvan-newman.js.map +1 -1
  34. package/dist/src/algorithms/community/index.d.ts +2 -0
  35. package/dist/src/algorithms/community/index.d.ts.map +1 -1
  36. package/dist/src/algorithms/community/index.js.map +1 -1
  37. package/dist/src/algorithms/community/label-propagation.d.ts +13 -3
  38. package/dist/src/algorithms/community/label-propagation.d.ts.map +1 -1
  39. package/dist/src/algorithms/community/label-propagation.js +50 -35
  40. package/dist/src/algorithms/community/label-propagation.js.map +1 -1
  41. package/dist/src/algorithms/community/leiden.d.ts +3 -2
  42. package/dist/src/algorithms/community/leiden.d.ts.map +1 -1
  43. package/dist/src/algorithms/community/leiden.js +34 -39
  44. package/dist/src/algorithms/community/leiden.js.map +1 -1
  45. package/dist/src/algorithms/community/louvain-optimized.d.ts +112 -0
  46. package/dist/src/algorithms/community/louvain-optimized.d.ts.map +1 -0
  47. package/dist/src/algorithms/community/louvain-optimized.js +303 -0
  48. package/dist/src/algorithms/community/louvain-optimized.js.map +1 -0
  49. package/dist/src/algorithms/community/louvain.d.ts.map +1 -1
  50. package/dist/src/algorithms/community/louvain.js +17 -76
  51. package/dist/src/algorithms/community/louvain.js.map +1 -1
  52. package/dist/src/algorithms/community/modularity-utils.d.ts +59 -0
  53. package/dist/src/algorithms/community/modularity-utils.d.ts.map +1 -0
  54. package/dist/src/algorithms/community/modularity-utils.js +112 -0
  55. package/dist/src/algorithms/community/modularity-utils.js.map +1 -0
  56. package/dist/src/algorithms/matching/bipartite.d.ts.map +1 -1
  57. package/dist/src/algorithms/matching/bipartite.js +8 -40
  58. package/dist/src/algorithms/matching/bipartite.js.map +1 -1
  59. package/dist/src/algorithms/shortest-path/bellman-ford.d.ts.map +1 -1
  60. package/dist/src/algorithms/shortest-path/bellman-ford.js +1 -12
  61. package/dist/src/algorithms/shortest-path/bellman-ford.js.map +1 -1
  62. package/dist/src/algorithms/shortest-path/bidirectional-dijkstra.d.ts +37 -0
  63. package/dist/src/algorithms/shortest-path/bidirectional-dijkstra.d.ts.map +1 -0
  64. package/dist/src/algorithms/shortest-path/bidirectional-dijkstra.js +193 -0
  65. package/dist/src/algorithms/shortest-path/bidirectional-dijkstra.js.map +1 -0
  66. package/dist/src/algorithms/shortest-path/dijkstra.d.ts +5 -2
  67. package/dist/src/algorithms/shortest-path/dijkstra.d.ts.map +1 -1
  68. package/dist/src/algorithms/shortest-path/dijkstra.js +15 -14
  69. package/dist/src/algorithms/shortest-path/dijkstra.js.map +1 -1
  70. package/dist/src/algorithms/traversal/bfs-unified.d.ts +29 -0
  71. package/dist/src/algorithms/traversal/bfs-unified.d.ts.map +1 -0
  72. package/dist/src/algorithms/traversal/bfs-unified.js +333 -0
  73. package/dist/src/algorithms/traversal/bfs-unified.js.map +1 -0
  74. package/dist/src/algorithms/traversal/bfs-variants.d.ts +54 -0
  75. package/dist/src/algorithms/traversal/bfs-variants.d.ts.map +1 -0
  76. package/dist/src/algorithms/traversal/bfs-variants.js +368 -0
  77. package/dist/src/algorithms/traversal/bfs-variants.js.map +1 -0
  78. package/dist/src/algorithms/traversal/bfs.d.ts +4 -18
  79. package/dist/src/algorithms/traversal/bfs.d.ts.map +1 -1
  80. package/dist/src/algorithms/traversal/bfs.js +5 -182
  81. package/dist/src/algorithms/traversal/bfs.js.map +1 -1
  82. package/dist/src/benchmark-all-algorithms.d.ts +23 -0
  83. package/dist/src/benchmark-all-algorithms.d.ts.map +1 -0
  84. package/dist/src/benchmark-all-algorithms.js +462 -0
  85. package/dist/src/benchmark-all-algorithms.js.map +1 -0
  86. package/dist/src/clustering/hierarchical.d.ts +16 -15
  87. package/dist/src/clustering/hierarchical.d.ts.map +1 -1
  88. package/dist/src/clustering/hierarchical.js +209 -223
  89. package/dist/src/clustering/hierarchical.js.map +1 -1
  90. package/dist/src/clustering/index.d.ts +4 -2
  91. package/dist/src/clustering/index.d.ts.map +1 -1
  92. package/dist/src/clustering/index.js +2 -2
  93. package/dist/src/clustering/index.js.map +1 -1
  94. package/dist/src/clustering/k-core.d.ts +16 -15
  95. package/dist/src/clustering/k-core.d.ts.map +1 -1
  96. package/dist/src/clustering/k-core.js +57 -21
  97. package/dist/src/clustering/k-core.js.map +1 -1
  98. package/dist/src/clustering/spectral.d.ts +8 -0
  99. package/dist/src/clustering/spectral.d.ts.map +1 -1
  100. package/dist/src/clustering/spectral.js +40 -21
  101. package/dist/src/clustering/spectral.js.map +1 -1
  102. package/dist/src/core/graph.js +4 -4
  103. package/dist/src/core/graph.js.map +1 -1
  104. package/dist/src/flow/ford-fulkerson.d.ts +17 -11
  105. package/dist/src/flow/ford-fulkerson.d.ts.map +1 -1
  106. package/dist/src/flow/ford-fulkerson.js +141 -217
  107. package/dist/src/flow/ford-fulkerson.js.map +1 -1
  108. package/dist/src/flow/min-cut.d.ts +6 -5
  109. package/dist/src/flow/min-cut.d.ts.map +1 -1
  110. package/dist/src/flow/min-cut.js +17 -8
  111. package/dist/src/flow/min-cut.js.map +1 -1
  112. package/dist/src/index.d.ts +1 -0
  113. package/dist/src/index.d.ts.map +1 -1
  114. package/dist/src/index.js +4 -0
  115. package/dist/src/index.js.map +1 -1
  116. package/dist/src/link-prediction/adamic-adar.d.ts.map +1 -1
  117. package/dist/src/link-prediction/adamic-adar.js +17 -26
  118. package/dist/src/link-prediction/adamic-adar.js.map +1 -1
  119. package/dist/src/link-prediction/common-neighbors.d.ts.map +1 -1
  120. package/dist/src/link-prediction/common-neighbors.js +7 -11
  121. package/dist/src/link-prediction/common-neighbors.js.map +1 -1
  122. package/dist/src/optimized/bit-packed.d.ts +143 -0
  123. package/dist/src/optimized/bit-packed.d.ts.map +1 -0
  124. package/dist/src/optimized/bit-packed.js +292 -0
  125. package/dist/src/optimized/bit-packed.js.map +1 -0
  126. package/dist/src/optimized/csr-graph.d.ts +86 -0
  127. package/dist/src/optimized/csr-graph.d.ts.map +1 -0
  128. package/dist/src/optimized/csr-graph.js +341 -0
  129. package/dist/src/optimized/csr-graph.js.map +1 -0
  130. package/dist/src/optimized/direction-optimized-bfs.d.ts +74 -0
  131. package/dist/src/optimized/direction-optimized-bfs.d.ts.map +1 -0
  132. package/dist/src/optimized/direction-optimized-bfs.js +223 -0
  133. package/dist/src/optimized/direction-optimized-bfs.js.map +1 -0
  134. package/dist/src/optimized/graph-adapter.d.ts +62 -0
  135. package/dist/src/optimized/graph-adapter.d.ts.map +1 -0
  136. package/dist/src/optimized/graph-adapter.js +146 -0
  137. package/dist/src/optimized/graph-adapter.js.map +1 -0
  138. package/dist/src/optimized/index.d.ts +14 -0
  139. package/dist/src/optimized/index.d.ts.map +1 -0
  140. package/dist/src/optimized/index.js +28 -0
  141. package/dist/src/optimized/index.js.map +1 -0
  142. package/dist/src/research/grsbm.d.ts.map +1 -1
  143. package/dist/src/research/grsbm.js +6 -14
  144. package/dist/src/research/grsbm.js.map +1 -1
  145. package/dist/src/research/sync.d.ts.map +1 -1
  146. package/dist/src/research/sync.js +3 -29
  147. package/dist/src/research/sync.js.map +1 -1
  148. package/dist/src/research/terahac.d.ts +2 -0
  149. package/dist/src/research/terahac.d.ts.map +1 -1
  150. package/dist/src/research/terahac.js +7 -3
  151. package/dist/src/research/terahac.js.map +1 -1
  152. package/dist/src/types/index.d.ts +8 -0
  153. package/dist/src/types/index.d.ts.map +1 -1
  154. package/dist/src/utils/algorithm-utilities.d.ts +2 -0
  155. package/dist/src/utils/algorithm-utilities.d.ts.map +1 -0
  156. package/dist/src/utils/algorithm-utilities.js +2 -0
  157. package/dist/src/utils/algorithm-utilities.js.map +1 -0
  158. package/dist/src/utils/graph-converters.d.ts +79 -0
  159. package/dist/src/utils/graph-converters.d.ts.map +1 -0
  160. package/dist/src/utils/graph-converters.js +196 -0
  161. package/dist/src/utils/graph-converters.js.map +1 -0
  162. package/dist/src/utils/graph-utilities.d.ts +62 -0
  163. package/dist/src/utils/graph-utilities.d.ts.map +1 -0
  164. package/dist/src/utils/graph-utilities.js +148 -0
  165. package/dist/src/utils/graph-utilities.js.map +1 -0
  166. package/dist/src/utils/index.d.ts +5 -0
  167. package/dist/src/utils/index.d.ts.map +1 -0
  168. package/dist/src/utils/index.js +5 -0
  169. package/dist/src/utils/index.js.map +1 -0
  170. package/dist/src/utils/math-utilities.d.ts +52 -0
  171. package/dist/src/utils/math-utilities.d.ts.map +1 -0
  172. package/dist/src/utils/math-utilities.js +123 -0
  173. package/dist/src/utils/math-utilities.js.map +1 -0
  174. package/dist/src/utils/matrix-utilities.d.ts +2 -0
  175. package/dist/src/utils/matrix-utilities.d.ts.map +1 -0
  176. package/dist/src/utils/matrix-utilities.js +2 -0
  177. package/dist/src/utils/matrix-utilities.js.map +1 -0
  178. package/dist/src/utils/optimization-helpers.d.ts +33 -0
  179. package/dist/src/utils/optimization-helpers.d.ts.map +1 -0
  180. package/dist/src/utils/optimization-helpers.js +47 -0
  181. package/dist/src/utils/optimization-helpers.js.map +1 -0
  182. package/package.json +18 -3
  183. package/src/algorithms/centrality/betweenness.ts +167 -199
  184. package/src/algorithms/centrality/closeness.ts +73 -191
  185. package/src/algorithms/centrality/degree.ts +3 -3
  186. package/src/algorithms/centrality/delta-pagerank-simple.ts +366 -0
  187. package/src/algorithms/centrality/delta-pagerank.ts +435 -0
  188. package/src/algorithms/centrality/index.ts +2 -0
  189. package/src/algorithms/centrality/pagerank.ts +66 -0
  190. package/src/algorithms/community/girvan-newman.ts +52 -59
  191. package/src/algorithms/community/index.ts +2 -0
  192. package/src/algorithms/community/label-propagation.ts +64 -35
  193. package/src/algorithms/community/leiden.ts +40 -41
  194. package/src/algorithms/community/louvain-optimized.ts +445 -0
  195. package/src/algorithms/community/louvain.ts +24 -104
  196. package/src/algorithms/community/modularity-utils.ts +146 -0
  197. package/src/algorithms/matching/bipartite.ts +8 -44
  198. package/src/algorithms/shortest-path/bellman-ford.ts +1 -14
  199. package/src/algorithms/shortest-path/bidirectional-dijkstra.ts +248 -0
  200. package/src/algorithms/shortest-path/dijkstra.ts +17 -15
  201. package/src/algorithms/traversal/bfs-unified.ts +433 -0
  202. package/src/algorithms/traversal/bfs-variants.ts +496 -0
  203. package/src/algorithms/traversal/bfs.ts +10 -228
  204. package/src/benchmark-all-algorithms.ts +548 -0
  205. package/src/clustering/hierarchical.ts +241 -251
  206. package/src/clustering/index.ts +4 -2
  207. package/src/clustering/k-core.ts +87 -39
  208. package/src/clustering/spectral.ts +44 -20
  209. package/src/core/graph.ts +4 -4
  210. package/src/flow/ford-fulkerson.ts +186 -254
  211. package/src/flow/min-cut.ts +21 -11
  212. package/src/index.ts +6 -0
  213. package/src/link-prediction/adamic-adar.ts +17 -30
  214. package/src/link-prediction/common-neighbors.ts +7 -16
  215. package/src/optimized/bit-packed.ts +340 -0
  216. package/src/optimized/csr-graph.ts +455 -0
  217. package/src/optimized/direction-optimized-bfs.ts +291 -0
  218. package/src/optimized/graph-adapter.ts +200 -0
  219. package/src/optimized/index.ts +36 -0
  220. package/src/research/grsbm.ts +7 -16
  221. package/src/research/sync.ts +3 -32
  222. package/src/research/terahac.ts +10 -2
  223. package/src/types/index.ts +8 -0
  224. package/src/utils/algorithm-utilities.ts +2 -0
  225. package/src/utils/graph-converters.ts +241 -0
  226. package/src/utils/graph-utilities.ts +199 -0
  227. package/src/utils/index.ts +4 -0
  228. package/src/utils/math-utilities.ts +140 -0
  229. package/src/utils/matrix-utilities.ts +2 -0
  230. package/src/utils/optimization-helpers.ts +81 -0
@@ -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
- * Calculate betweenness centrality for all nodes using Brandes' algorithm
31
+ * Result of Brandes algorithm computation for a single source
27
32
  */
28
- export function betweennessCentrality(
29
- graph: Graph,
30
- options: BetweennessCentralityOptions = {},
31
- ): Record<string, number> {
32
- const nodes = Array.from(graph.nodes()).map((node) => node.id);
33
- const centrality: Record<string, number> = {};
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
- // Initialize centrality scores
36
- for (const nodeId of nodes) {
37
- centrality[String(nodeId)] = 0;
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
- // Brandes' algorithm
41
- for (const source of nodes) {
42
- const stack: NodeId[] = [];
43
- const predecessors = new Map<NodeId, NodeId[]>();
44
- const sigma = new Map<NodeId, number>(); // Number of shortest paths
45
- const distance = new Map<NodeId, number>();
46
- const delta = new Map<NodeId, number>();
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
- // Initialize
49
- for (const nodeId of nodes) {
50
- predecessors.set(nodeId, []);
51
- sigma.set(nodeId, 0);
52
- distance.set(nodeId, -1);
53
- delta.set(nodeId, 0);
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
- sigma.set(source, 1);
57
- distance.set(source, 0);
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
- stack.push(current);
83
+ for (const v of wPreds) {
84
+ const vSigma = sigma.get(v) ?? 0;
85
+ const vDelta = delta.get(v) ?? 0;
69
86
 
70
- for (const neighbor of Array.from(graph.neighbors(current))) {
71
- const currentDistance = distance.get(current);
72
- let neighborDistance = distance.get(neighbor);
87
+ if (vSigma > 0 && wSigma > 0) {
88
+ let contribution = (vSigma / wSigma) * (1 + wDelta);
73
89
 
74
- if (currentDistance === undefined || neighborDistance === undefined) {
75
- continue;
76
- }
77
-
78
- // First time we reach this neighbor
79
- if (neighborDistance < 0) {
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
- // Accumulation - back-propagation of dependencies
100
- while (stack.length > 0) {
101
- const w = stack.pop();
102
- if (!w) {
103
- break;
104
- }
103
+ if (w !== source) {
104
+ const currentCentrality = centrality[String(w)] ?? 0;
105
+ centrality[String(w)] = currentCentrality + wDelta;
106
+ }
107
+ }
108
+ }
105
109
 
106
- const wPreds = predecessors.get(w) ?? [];
107
- const wSigma = sigma.get(w) ?? 0;
108
- const wDelta = delta.get(w) ?? 0;
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
- for (const v of wPreds) {
111
- const vSigma = sigma.get(v) ?? 0;
112
- const vDelta = delta.get(v) ?? 0;
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
- if (vSigma > 0 && wSigma > 0) {
115
- let contribution = (vSigma / wSigma) * (1 + wDelta);
135
+ const wPreds = predecessors.get(w) ?? [];
136
+ const wSigma = sigma.get(w) ?? 0;
137
+ const wDelta = delta.get(w) ?? 0;
116
138
 
117
- // Apply endpoints option: when false, exclude endpoint contributions
118
- if (!options.endpoints) {
119
- // For standard betweenness, don't count paths that only involve endpoints
120
- const isTargetEndpoint = predecessors.get(w)?.length === 0 && w !== source;
121
- if (isTargetEndpoint) {
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
- delta.set(v, vDelta + contribution);
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
- if (w !== source) {
131
- const currentCentrality = centrality[String(w)] ?? 0;
132
- centrality[String(w)] = currentCentrality + wDelta;
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 currentValue = centrality[key];
142
- if (currentValue !== undefined) {
143
- centrality[key] = currentValue / 2;
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
- const n = nodes.length;
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 stack: NodeId[] = [];
208
- const predecessors = new Map<NodeId, NodeId[]>();
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 ? (n - 1) * (n - 2) : ((n - 1) * (n - 2)) / 2;
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
-