1010 N : Clone ,
1111 W : Clone + Default ,
1212{
13+ /// Calculates the betweenness centrality of each node in the graph.
1314 fn betweenness_centrality (
1415 & self ,
1516 directed : bool ,
2223
2324 let mut centrality = vec ! [ 0.0 ; num_nodes] ;
2425
26+ // Betweenness centrality is 0 for graphs with less than 3 nodes.
27+ if num_nodes < 3 {
28+ return Ok ( centrality. into_iter ( ) . enumerate ( ) . collect ( ) ) ;
29+ }
30+
2531 for s in 0 ..num_nodes {
2632 let ( _, sigma, pred, mut stack) = self . _brandes_bfs_and_path_counting ( s, directed) ?;
2733
4652 }
4753 }
4854
55+ // It aligns the code with the standard formal definition.
56+ if !directed {
57+ for score in centrality. iter_mut ( ) {
58+ * score /= 2.0 ;
59+ }
60+ }
61+
4962 // Normalization
5063 if normalized {
5164 let scale = if directed {
6477 Ok ( centrality. into_iter ( ) . enumerate ( ) . collect ( ) )
6578 }
6679
80+ /// Calculates betweenness centrality across a specific set of critical pathways.
6781 fn pathway_betweenness_centrality (
6882 & self ,
6983 pathways : & [ ( usize , usize ) ] ,
@@ -72,54 +86,70 @@ where
7286 ) -> Result < Vec < ( usize , f64 ) > , GraphError > {
7387 let num_nodes = self . number_nodes ( ) ;
7488 if num_nodes == 0 {
75- return Ok ( Vec :: new ( ) ) ;
89+ return Ok ( vec ! [ ] ) ;
7690 }
7791
7892 let mut centrality = vec ! [ 0.0 ; num_nodes] ;
93+ let mut valid_pathways = 0 ;
7994
95+ // Unlike the global version, we cannot easily group by source,
96+ // as the dependency calculation is specific to each target `t`.
8097 for & ( s, t) in pathways {
8198 if !self . contains_node ( s) || !self . contains_node ( t) {
82- // Skip invalid pathways
8399 continue ;
84100 }
85101
102+ if s == t {
103+ valid_pathways += 1 ;
104+ continue ;
105+ }
106+
107+ // We need a fresh BFS for each s->t pair to get the correct stack order
86108 let ( dist, sigma, pred, mut stack) =
87109 self . _brandes_bfs_and_path_counting ( s, directed) ?;
88110
89- // Accumulation for specific pathway (s, t)
90- if dist[ t] . is_some ( ) {
91- // Only accumulate if t is reachable from s
92- let mut delta = vec ! [ 0.0 ; num_nodes] ;
93- delta[ t] = 1.0 ;
94-
95- while let Some ( w) = stack. pop ( ) {
96- for & v in & pred[ w] {
97- let sigma_v = sigma[ v] ;
98- let sigma_w = sigma[ w] ;
99- if sigma_w == 0.0 {
100- return Err ( GraphError :: AlgorithmError (
101- "Division by zero in sigma calculation for pathway" ,
102- ) ) ;
103- }
104- delta[ v] += ( sigma_v / sigma_w) * ( 1.0 + delta[ w] ) ;
105- }
106- if w != s {
107- centrality[ w] += delta[ w] ;
111+ // If t is not reachable from s, this pathway contributes nothing.
112+ if dist[ t] . is_none ( ) {
113+ continue ;
114+ }
115+ valid_pathways += 1 ;
116+
117+ let mut delta = vec ! [ 0.0 ; num_nodes] ;
118+
119+ // Process nodes in reverse order from the stack
120+ while let Some ( w) = stack. pop ( ) {
121+ // The contribution to dependency is only propagated from nodes on a shortest path to t.
122+ // This can be simplified: if a node `w` is part of an s-t path, its `delta` will be non-zero
123+ // if one of its successors on the shortest path has non-zero delta. We start this at `t`.
124+ if w == t {
125+ // This is the starting point of our back-propagation for this s-t path
126+ delta[ w] = 1.0 ;
127+ }
128+
129+ for & v in & pred[ w] {
130+ // The dependency of v is its share of the dependency of w.
131+ if sigma[ w] > 0.0 {
132+ delta[ v] += ( sigma[ v] / sigma[ w] ) * delta[ w] ;
108133 }
109134 }
110135 }
111- }
112136
113- // Normalization
114- if normalized {
115- let num_pathways = pathways. len ( ) as f64 ;
116- if num_pathways > 0.0 {
117- for score in centrality. iter_mut ( ) {
118- * score /= num_pathways;
137+ // Add the accumulated dependencies to the final centrality scores
138+ // The endpoints s and t themselves do not get centrality credit from this path.
139+ for i in 0 ..num_nodes {
140+ if i != s && i != t {
141+ centrality[ i] += delta[ i] ;
119142 }
120143 }
121144 }
122145
146+ if normalized && valid_pathways > 0 {
147+ let scale = valid_pathways as f64 ;
148+ for score in & mut centrality {
149+ * score /= scale;
150+ }
151+ }
152+
123153 Ok ( centrality. into_iter ( ) . enumerate ( ) . collect ( ) )
124154 }
125155}
0 commit comments