use crate::algorithms::paths::distances::distances;
use crate::core::{Graph, IgraphResult};
pub fn harmonic_centrality(graph: &Graph) -> IgraphResult<Vec<f64>> {
let n = graph.vcount();
let n_us = n as usize;
if n < 2 {
return Ok(vec![0.0; n_us]);
}
let mut out = Vec::with_capacity(n_us);
let denom = f64::from(n - 1);
for v in 0..n {
let d = distances(graph, v)?;
let mut sum_inv: f64 = 0.0;
let v_us = v as usize;
for (target, &val) in d.iter().enumerate() {
if target == v_us {
continue;
}
if let Some(dist) = val {
if dist > 0 {
sum_inv += 1.0 / f64::from(dist);
}
}
}
out.push(sum_inv / denom);
}
Ok(out)
}
#[cfg(test)]
mod tests {
use super::*;
fn close(a: f64, b: f64, tol: f64) {
assert!((a - b).abs() < tol, "{a} vs {b}");
}
#[test]
fn empty_graph_yields_empty() {
let g = Graph::with_vertices(0);
assert!(harmonic_centrality(&g).unwrap().is_empty());
}
#[test]
fn singleton_yields_zero() {
let g = Graph::with_vertices(1);
assert_eq!(harmonic_centrality(&g).unwrap(), vec![0.0]);
}
#[test]
fn isolated_vertices_all_zero() {
let g = Graph::with_vertices(3);
assert_eq!(harmonic_centrality(&g).unwrap(), vec![0.0, 0.0, 0.0]);
}
#[test]
fn path_3_centre_one_ends_three_quarters() {
let mut g = Graph::with_vertices(3);
g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
let h = harmonic_centrality(&g).unwrap();
assert_eq!(h, vec![0.75, 1.0, 0.75]);
}
#[test]
fn k3_uniform_one() {
let mut g = Graph::with_vertices(3);
g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
g.add_edge(2, 0).unwrap();
let h = harmonic_centrality(&g).unwrap();
for &x in &h {
close(x, 1.0, 1e-12);
}
}
#[test]
fn star_centre_one_leaves_two_thirds() {
let mut g = Graph::with_vertices(4);
for v in 1..4 {
g.add_edge(0, v).unwrap();
}
let h = harmonic_centrality(&g).unwrap();
close(h[0], 1.0, 1e-12);
for &leaf in &h[1..4] {
close(leaf, 2.0 / 3.0, 1e-12);
}
}
#[test]
fn disconnected_components_well_defined() {
let mut g = Graph::with_vertices(4);
g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
let h = harmonic_centrality(&g).unwrap();
close(h[0], 0.5, 1e-12);
close(h[1], 2.0 / 3.0, 1e-12); close(h[2], 0.5, 1e-12);
close(h[3], 0.0, 1e-12);
}
#[test]
fn directed_path_uses_out_edges() {
let mut g = Graph::new(3, true).unwrap();
g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
let h = harmonic_centrality(&g).unwrap();
close(h[0], 0.75, 1e-12);
close(h[1], 0.5, 1e-12);
close(h[2], 0.0, 1e-12);
}
}