use crate::algorithms::paths::distances::distances;
use crate::core::{Graph, IgraphResult};
pub fn closeness(graph: &Graph) -> IgraphResult<Vec<Option<f64>>> {
let n = graph.vcount();
let n_us = n as usize;
let mut out = Vec::with_capacity(n_us);
for v in 0..n {
let d = distances(graph, v)?;
let mut sum_dist: u64 = 0;
let mut reach: u64 = 0;
let v_us = v as usize;
for (target, &val) in d.iter().enumerate() {
if target == v_us {
continue;
}
if let Some(dist) = val {
sum_dist += u64::from(dist);
reach += 1;
}
}
if reach == 0 || sum_dist == 0 {
out.push(None);
} else {
#[allow(clippy::cast_precision_loss)]
let val = (reach as f64) / (sum_dist as f64);
out.push(Some(val));
}
}
Ok(out)
}
#[cfg(test)]
mod tests {
use super::*;
fn approx_eq(a: Option<f64>, b: Option<f64>, tol: f64) {
match (a, b) {
(Some(x), Some(y)) => {
assert!((x - y).abs() < tol, "{x} vs {y}");
}
(None, None) => {}
_ => panic!("{a:?} vs {b:?}"),
}
}
#[test]
fn empty_graph_yields_empty() {
let g = Graph::with_vertices(0);
assert!(closeness(&g).unwrap().is_empty());
}
#[test]
fn isolated_vertices_all_none() {
let g = Graph::with_vertices(3);
let c = closeness(&g).unwrap();
assert_eq!(c, vec![None, None, None]);
}
#[test]
fn singleton_is_none() {
let g = Graph::with_vertices(1);
assert_eq!(closeness(&g).unwrap(), vec![None]);
}
#[test]
fn k3_triangle_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 c = closeness(&g).unwrap();
for &val in &c {
approx_eq(val, Some(1.0), 1e-12);
}
}
#[test]
fn star_centre_has_highest() {
let mut g = Graph::with_vertices(4);
for v in 1..4 {
g.add_edge(0, v).unwrap();
}
let c = closeness(&g).unwrap();
approx_eq(c[0], Some(1.0), 1e-12);
for &leaf_val in &c[1..4] {
approx_eq(leaf_val, Some(0.6), 1e-12);
}
}
#[test]
fn path_5_endpoints_lower_than_centre() {
let mut g = Graph::with_vertices(5);
for i in 0..4 {
g.add_edge(i, i + 1).unwrap();
}
let c = closeness(&g).unwrap();
approx_eq(c[0], Some(0.4), 1e-12);
approx_eq(c[1], Some(4.0 / 7.0), 1e-12);
approx_eq(c[2], Some(4.0 / 6.0), 1e-12);
approx_eq(c[3], Some(4.0 / 7.0), 1e-12);
approx_eq(c[4], Some(0.4), 1e-12);
}
#[test]
fn disconnected_components_within_component_only() {
let mut g = Graph::with_vertices(4);
g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
let c = closeness(&g).unwrap();
approx_eq(c[0], Some(2.0 / 3.0), 1e-12);
approx_eq(c[1], Some(1.0), 1e-12);
approx_eq(c[2], Some(2.0 / 3.0), 1e-12);
assert_eq!(c[3], None);
}
#[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 c = closeness(&g).unwrap();
approx_eq(c[0], Some(2.0 / 3.0), 1e-12);
approx_eq(c[1], Some(1.0), 1e-12);
assert_eq!(c[2], None);
}
}