use crate::graph::graph::Graph;
use std::collections::{HashMap, VecDeque};
use std::fmt::Debug;
use std::hash::Hash;
#[derive(Debug)]
pub enum CentralityError {
InvalidNode(usize),
DistanceOverflow,
}
impl std::fmt::Display for CentralityError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
CentralityError::InvalidNode(id) => {
write!(f, "Invalid node reference: node ID {} not found", id)
}
CentralityError::DistanceOverflow => {
write!(f, "Distance calculation overflowed")
}
}
}
}
impl std::error::Error for CentralityError {}
pub trait Centrality<W, N, E> {
fn degree_centrality(&self) -> Result<HashMap<usize, usize>, CentralityError>;
fn betweenness_centrality(&self) -> Result<HashMap<usize, f64>, CentralityError>;
fn closeness_centrality(&self) -> Result<HashMap<usize, f64>, CentralityError>;
}
impl<W, N, E> Centrality<W, N, E> for Graph<W, N, E>
where
W: Copy + Default + PartialEq,
N: Clone + Eq + Hash + Debug,
E: Clone + Default + Debug,
{
fn degree_centrality(&self) -> Result<HashMap<usize, usize>, CentralityError> {
let mut centrality = HashMap::new();
for (id, node) in self.nodes.iter() {
centrality.insert(id, node.neighbors.len());
}
Ok(centrality)
}
fn betweenness_centrality(&self) -> Result<HashMap<usize, f64>, CentralityError> {
let nodes: Vec<usize> = self.all_nodes().map(|(id, _)| id).collect();
let num_nodes = nodes.len();
let mut betweenness = HashMap::new();
if num_nodes <= 1 {
return Ok(nodes.into_iter().map(|n| (n, 0.0)).collect());
}
for &s in &nodes {
let mut stack = Vec::new();
let mut pred: Vec<Vec<usize>> = vec![Vec::new(); self.nodes.len()];
let mut sigma = vec![0.0; self.nodes.len()];
let mut dist: Vec<Option<i32>> = vec![None; self.nodes.len()];
sigma[s] = 1.0;
dist[s] = Some(0);
let mut queue = VecDeque::new();
queue.push_back(s);
while let Some(v) = queue.pop_front() {
stack.push(v);
let neighbors = self.nodes.get(v).ok_or(CentralityError::InvalidNode(v))?;
for &(w, _) in &neighbors.neighbors {
if dist[w].is_none() {
dist[w] = Some(
dist[v]
.unwrap()
.checked_add(1_i32)
.ok_or(CentralityError::DistanceOverflow)?,
);
queue.push_back(w);
}
if dist[w] == Some(dist[v].unwrap() + 1) {
sigma[w] += sigma[v];
pred[w].push(v);
}
}
}
let mut delta = vec![0.0; self.nodes.len()];
while let Some(w) = stack.pop() {
for &v in &pred[w] {
delta[v] += (sigma[v] / sigma[w]) * (1.0 + delta[w]);
}
if w != s {
*betweenness.entry(w).or_insert(0.0) += delta[w];
}
}
}
if !self.directed {
betweenness.values_mut().for_each(|v| *v /= 2.0);
}
let scale = if self.directed {
1.0 / ((num_nodes - 1) * (num_nodes - 2)) as f64
} else {
1.0 / ((num_nodes - 1) * (num_nodes - 2) / 2) as f64
};
betweenness.iter_mut().for_each(|(_, v)| *v *= scale);
Ok(betweenness)
}
fn closeness_centrality(&self) -> Result<HashMap<usize, f64>, CentralityError> {
let nodes: Vec<usize> = self.all_nodes().map(|(id, _)| id).collect();
let mut closeness = HashMap::new();
for &node in &nodes {
let mut dist = HashMap::new();
let mut queue = VecDeque::new();
dist.insert(node, 0_i32); queue.push_back(node);
while let Some(v) = queue.pop_front() {
let neighbors = self.nodes.get(v).ok_or(CentralityError::InvalidNode(v))?;
for &(w, _) in &neighbors.neighbors {
if !dist.contains_key(&w) {
let new_dist = dist[&v]
.checked_add(1_i32)
.ok_or(CentralityError::DistanceOverflow)?;
dist.insert(w, new_dist);
queue.push_back(w);
}
}
}
let total_dist: i32 = dist.values().sum();
let reachable = dist.len().saturating_sub(1);
let centrality = if total_dist > 0 && reachable > 0 {
reachable as f64 / total_dist as f64
} else {
0.0
};
closeness.insert(node, centrality);
}
Ok(closeness)
}
}
#[cfg(test)]
mod tests {
use super::*;
use float_cmp::approx_eq;
const TOLERANCE: f64 = 1e-5;
fn assert_approx_eq(a: f64, b: f64) {
assert!(approx_eq!(f64, a, b, epsilon = TOLERANCE), "{} ≈ {}", a, b);
}
#[test]
fn test_degree_centrality() {
let matrix = vec![vec![0, 1, 0], vec![1, 0, 1], vec![0, 1, 0]];
let graph = Graph::from_adjacency_matrix(&matrix, false, (), ()).unwrap();
let centrality = graph.degree_centrality().unwrap();
assert_eq!(centrality[&0], 1);
assert_eq!(centrality[&1], 2);
assert_eq!(centrality[&2], 1);
}
#[test]
fn test_betweenness_centrality() {
let matrix = vec![
vec![0, 1, 0, 0],
vec![1, 0, 1, 1],
vec![0, 1, 0, 0],
vec![0, 1, 0, 0],
];
let graph = Graph::from_adjacency_matrix(&matrix, false, (), ()).unwrap();
let betweenness = graph.betweenness_centrality().unwrap();
assert_approx_eq(betweenness[&1], 1.0);
}
#[test]
fn test_closeness_centrality() {
let matrix = vec![vec![0, 1, 1], vec![1, 0, 1], vec![1, 1, 0]];
let graph = Graph::from_adjacency_matrix(&matrix, false, (), ()).unwrap();
let closeness = graph.closeness_centrality().unwrap();
assert_approx_eq(closeness[&0], 1.0);
}
#[test]
fn test_empty_graph() {
let graph = Graph::<u32, (), ()>::new(false);
assert!(graph.degree_centrality().unwrap().is_empty());
assert!(graph.betweenness_centrality().unwrap().is_empty());
assert!(graph.closeness_centrality().unwrap().is_empty());
}
}