use std::collections::VecDeque;
use crate::error::{GraphError, GraphResult};
pub fn betweenness_centrality(adj: &[Vec<usize>], n_nodes: usize) -> GraphResult<Vec<f64>> {
if n_nodes == 0 {
return Err(GraphError::EmptyGraph);
}
for (u, heads) in adj.iter().enumerate() {
for &w in heads {
if w >= n_nodes {
return Err(GraphError::InvalidPlan(format!(
"adjacency edge {u} -> {w} references node >= n_nodes={n_nodes}"
)));
}
}
}
let mut centrality = vec![0.0f64; n_nodes];
let mut dist = vec![-1i64; n_nodes];
let mut sigma = vec![0.0f64; n_nodes];
let mut delta = vec![0.0f64; n_nodes];
let mut preds: Vec<Vec<usize>> = vec![Vec::new(); n_nodes];
let mut order: Vec<usize> = Vec::with_capacity(n_nodes);
let mut queue: VecDeque<usize> = VecDeque::new();
for s in 0..n_nodes {
for v in 0..n_nodes {
dist[v] = -1;
sigma[v] = 0.0;
delta[v] = 0.0;
preds[v].clear();
}
order.clear();
queue.clear();
dist[s] = 0;
sigma[s] = 1.0;
queue.push_back(s);
while let Some(v) = queue.pop_front() {
order.push(v);
if let Some(neighbours) = adj.get(v) {
for &w in neighbours {
if dist[w] < 0 {
dist[w] = dist[v] + 1;
queue.push_back(w);
}
if dist[w] == dist[v] + 1 {
sigma[w] += sigma[v];
preds[w].push(v);
}
}
}
}
while let Some(w) = order.pop() {
let coeff = (1.0 + delta[w]) / sigma[w];
for &v in &preds[w] {
delta[v] += sigma[v] * coeff;
}
if w != s {
centrality[w] += delta[w];
}
}
}
Ok(centrality)
}
#[cfg(test)]
mod tests {
use super::*;
fn undirected(n: usize, edges: &[(usize, usize)]) -> Vec<Vec<usize>> {
let mut adj = vec![Vec::new(); n];
for &(a, b) in edges {
adj[a].push(b);
adj[b].push(a);
}
adj
}
#[test]
fn nonneg() {
let adj = undirected(5, &[(0, 1), (1, 2), (2, 3), (3, 4), (4, 0)]);
let c = betweenness_centrality(&adj, 5).expect("betweenness_centrality should succeed");
for &v in &c {
assert!(v >= 0.0, "negative betweenness {v}");
}
}
#[test]
fn star_center_highest() {
let adj = undirected(5, &[(0, 1), (0, 2), (0, 3), (0, 4)]);
let c = betweenness_centrality(&adj, 5).expect("betweenness_centrality should succeed");
for leaf in 1..5 {
assert!(
c[0] > c[leaf],
"centre {} should exceed leaf {} = {}",
c[0],
leaf,
c[leaf]
);
assert!(
(c[leaf]).abs() < 1e-12,
"leaf {leaf} should be 0, got {}",
c[leaf]
);
}
}
#[test]
fn path_graph_middle_highest() {
let adj = undirected(5, &[(0, 1), (1, 2), (2, 3), (3, 4)]);
let c = betweenness_centrality(&adj, 5).expect("betweenness_centrality should succeed");
assert!(c[2] > c[1] && c[2] > c[3], "middle {} not the max", c[2]);
assert!(c[1] > c[0] && c[3] > c[4], "endpoints should be lowest");
}
#[test]
fn complete_graph_zero() {
let n = 4;
let mut edges = Vec::new();
for a in 0..n {
for b in (a + 1)..n {
edges.push((a, b));
}
}
let adj = undirected(n, &edges);
let c = betweenness_centrality(&adj, n).expect("betweenness_centrality should succeed");
for &v in &c {
assert!(
v.abs() < 1e-12,
"complete-graph betweenness {v} should be 0"
);
}
}
#[test]
fn n_nodes_0_error() {
let adj: Vec<Vec<usize>> = vec![];
let err = betweenness_centrality(&adj, 0);
assert!(matches!(err, Err(GraphError::EmptyGraph)), "got {err:?}");
}
#[test]
fn isolated_node_zero() {
let adj = undirected(4, &[(0, 1), (1, 2)]);
let c = betweenness_centrality(&adj, 4).expect("betweenness_centrality should succeed");
assert!(c[3].abs() < 1e-12, "isolated node betweenness {}", c[3]);
}
#[test]
fn output_shape() {
let adj = undirected(6, &[(0, 1), (2, 3), (4, 5)]);
let c = betweenness_centrality(&adj, 6).expect("betweenness_centrality should succeed");
assert_eq!(c.len(), 6);
}
#[test]
fn single_node_zero() {
let adj = vec![vec![]];
let c = betweenness_centrality(&adj, 1).expect("betweenness_centrality should succeed");
assert_eq!(c.len(), 1);
assert!(c[0].abs() < 1e-12, "single-node betweenness {}", c[0]);
}
#[test]
fn symmetric_values() {
let adj = undirected(5, &[(0, 1), (1, 2), (2, 3), (3, 4)]);
let c = betweenness_centrality(&adj, 5).expect("betweenness_centrality should succeed");
assert!((c[0] - c[4]).abs() < 1e-9, "endpoints {} vs {}", c[0], c[4]);
assert!((c[1] - c[3]).abs() < 1e-9, "inner {} vs {}", c[1], c[3]);
}
#[test]
fn out_of_range_edge_error() {
let adj = vec![vec![9]]; let err = betweenness_centrality(&adj, 3);
assert!(
matches!(err, Err(GraphError::InvalidPlan(_))),
"got {err:?}"
);
}
}