use crate::algorithms::paths::dijkstra::dijkstra_distances;
use crate::core::{Graph, IgraphResult};
pub fn closeness_weighted(graph: &Graph, weights: &[f64]) -> 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 = dijkstra_distances(graph, v, weights)?;
let mut sum_dist: f64 = 0.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 += *dist;
reach += 1;
}
}
if reach == 0 || sum_dist == 0.0 {
out.push(None);
} else {
#[allow(clippy::cast_precision_loss)]
let val = (reach as f64) / sum_dist;
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_weighted(&g, &[]).unwrap().is_empty());
}
#[test]
fn isolated_vertices_all_none() {
let g = Graph::with_vertices(3);
let c = closeness_weighted(&g, &[]).unwrap();
assert_eq!(c, vec![None, None, None]);
}
#[test]
fn singleton_is_none() {
let g = Graph::with_vertices(1);
assert_eq!(closeness_weighted(&g, &[]).unwrap(), vec![None]);
}
#[test]
fn star_with_uniform_weights_matches_unweighted() {
let mut g = Graph::with_vertices(4);
for v in 1..4 {
g.add_edge(0, v).unwrap();
}
let c = closeness_weighted(&g, &[1.0, 1.0, 1.0]).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 star_with_non_uniform_weights() {
let mut g = Graph::with_vertices(4);
g.add_edge(0, 1).unwrap();
g.add_edge(0, 2).unwrap();
g.add_edge(0, 3).unwrap();
let c = closeness_weighted(&g, &[1.0, 2.0, 3.0]).unwrap();
approx_eq(c[0], Some(0.5), 1e-12);
approx_eq(c[1], Some(3.0 / 8.0), 1e-12);
approx_eq(c[2], Some(0.3), 1e-12);
approx_eq(c[3], Some(0.25), 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 c = closeness_weighted(&g, &[1.0, 1.0]).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);
}
#[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_weighted(&g, &[1.0, 1.0]).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 unit_weights_match_unweighted() {
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 cw = closeness_weighted(&g, &[1.0; 3]).unwrap();
let cu = crate::closeness(&g).unwrap();
for (a, b) in cw.iter().zip(cu.iter()) {
approx_eq(*a, *b, 1e-12);
}
}
#[test]
fn weights_size_mismatch_errors() {
let mut g = Graph::with_vertices(2);
g.add_edge(0, 1).unwrap();
assert!(closeness_weighted(&g, &[]).is_err());
assert!(closeness_weighted(&g, &[1.0, 2.0]).is_err());
}
#[test]
fn negative_weight_errors() {
let mut g = Graph::with_vertices(2);
g.add_edge(0, 1).unwrap();
assert!(closeness_weighted(&g, &[-1.0]).is_err());
}
}