Skip to content

Commit a573118

Browse files
committed
Implemented Brandes' algorithm
Signed-off-by: Marvin Hansen <[email protected]>
1 parent 017bd5e commit a573118

File tree

1 file changed

+171
-3
lines changed

1 file changed

+171
-3
lines changed

ultragraph/src/types/storage/graph_csm/graph_csm_algo_centrality.rs

Lines changed: 171 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22
* SPDX-License-Identifier: MIT
33
* Copyright (c) "2025" . The DeepCausality Authors and Contributors. All Rights Reserved.
44
*/
5-
use crate::{CentralityGraphAlgorithms, CsmGraph, GraphError};
5+
use crate::{CentralityGraphAlgorithms, CsmGraph, GraphError, GraphView};
6+
use std::collections::VecDeque;
67

78
impl<N, W> CentralityGraphAlgorithms<N, W> for CsmGraph<N, W>
89
where
@@ -14,7 +15,53 @@ where
1415
directed: bool,
1516
normalized: bool,
1617
) -> Result<Vec<(usize, f64)>, GraphError> {
17-
todo!()
18+
let num_nodes = self.number_nodes();
19+
if num_nodes == 0 {
20+
return Ok(Vec::new());
21+
}
22+
23+
let mut centrality = vec![0.0; num_nodes];
24+
25+
for s in 0..num_nodes {
26+
let (_, sigma, pred, mut stack) = self._brandes_bfs_and_path_counting(s, directed)?;
27+
28+
// Accumulation
29+
let mut delta = vec![0.0; num_nodes];
30+
while let Some(w) = stack.pop() {
31+
for &v in &pred[w] {
32+
let sigma_v = sigma[v];
33+
let sigma_w = sigma[w];
34+
if sigma_w == 0.0 {
35+
// This should ideally not happen if paths are correctly counted,
36+
// but as a safeguard against division by zero.
37+
return Err(GraphError::AlgorithmError(
38+
"Division by zero in sigma calculation",
39+
));
40+
}
41+
delta[v] += (sigma_v / sigma_w) * (1.0 + delta[w]);
42+
}
43+
if w != s {
44+
centrality[w] += delta[w];
45+
}
46+
}
47+
}
48+
49+
// Normalization
50+
if normalized {
51+
let scale = if directed {
52+
((num_nodes - 1) * (num_nodes - 2)) as f64
53+
} else {
54+
((num_nodes - 1) * (num_nodes - 2)) as f64 / 2.0
55+
};
56+
57+
if scale > 0.0 {
58+
for score in centrality.iter_mut() {
59+
*score /= scale;
60+
}
61+
}
62+
}
63+
64+
Ok(centrality.into_iter().enumerate().collect())
1865
}
1966

2067
fn pathway_betweenness_centrality(
@@ -23,6 +70,127 @@ where
2370
directed: bool,
2471
normalized: bool,
2572
) -> Result<Vec<(usize, f64)>, GraphError> {
26-
todo!()
73+
let num_nodes = self.number_nodes();
74+
if num_nodes == 0 {
75+
return Ok(Vec::new());
76+
}
77+
78+
let mut centrality = vec![0.0; num_nodes];
79+
80+
for &(s, t) in pathways {
81+
if !self.contains_node(s) || !self.contains_node(t) {
82+
// Skip invalid pathways
83+
continue;
84+
}
85+
86+
let (dist, sigma, pred, mut stack) =
87+
self._brandes_bfs_and_path_counting(s, directed)?;
88+
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];
108+
}
109+
}
110+
}
111+
}
112+
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;
119+
}
120+
}
121+
}
122+
123+
Ok(centrality.into_iter().enumerate().collect())
124+
}
125+
}
126+
127+
impl<N, W> CsmGraph<N, W>
128+
where
129+
N: Clone,
130+
W: Clone + Default,
131+
{
132+
/// Private helper for Brandes' algorithm: BFS and path counting phase.
133+
/// Returns (dist, sigma, pred, stack)
134+
#[allow(clippy::type_complexity)]
135+
#[inline]
136+
fn _brandes_bfs_and_path_counting(
137+
&self,
138+
s: usize,
139+
directed: bool,
140+
) -> Result<(Vec<Option<usize>>, Vec<f64>, Vec<Vec<usize>>, Vec<usize>), GraphError> {
141+
let num_nodes = self.number_nodes();
142+
let mut stack = Vec::new(); // S in Brandes' algorithm
143+
let mut queue = VecDeque::new();
144+
let mut dist = vec![None; num_nodes];
145+
let mut sigma = vec![0.0; num_nodes]; // Number of shortest paths
146+
let mut pred: Vec<Vec<usize>> = vec![Vec::new(); num_nodes]; // Predecessors on shortest paths
147+
148+
dist[s] = Some(0);
149+
sigma[s] = 1.0;
150+
queue.push_back(s);
151+
152+
while let Some(v) = queue.pop_front() {
153+
stack.push(v);
154+
155+
let mut neighbors_to_process = Vec::new();
156+
if directed {
157+
let start = self.forward_edges.offsets[v];
158+
let end = self.forward_edges.offsets[v + 1];
159+
for &neighbor in &self.forward_edges.targets[start..end] {
160+
neighbors_to_process.push(neighbor);
161+
}
162+
} else {
163+
// For undirected, consider both forward and backward neighbors
164+
let start_fwd = self.forward_edges.offsets[v];
165+
let end_fwd = self.forward_edges.offsets[v + 1];
166+
for &neighbor in &self.forward_edges.targets[start_fwd..end_fwd] {
167+
neighbors_to_process.push(neighbor);
168+
}
169+
let start_bwd = self.backward_edges.offsets[v];
170+
let end_bwd = self.backward_edges.offsets[v + 1];
171+
for &neighbor in &self.backward_edges.targets[start_bwd..end_bwd] {
172+
neighbors_to_process.push(neighbor);
173+
}
174+
neighbors_to_process.sort_unstable();
175+
neighbors_to_process.dedup(); // Remove duplicates if any
176+
}
177+
178+
for &w in &neighbors_to_process {
179+
let v_dist = dist[v].ok_or(GraphError::AlgorithmError("Distance for v not set"))?;
180+
// Path discovery
181+
if dist[w].is_none() {
182+
dist[w] = Some(v_dist + 1);
183+
queue.push_back(w);
184+
}
185+
// Path counting
186+
if dist[w].ok_or(GraphError::AlgorithmError("Distance for w not set"))?
187+
== v_dist + 1
188+
{
189+
sigma[w] += sigma[v];
190+
pred[w].push(v);
191+
}
192+
}
193+
}
194+
Ok((dist, sigma, pred, stack))
27195
}
28196
}

0 commit comments

Comments
 (0)