use crate::algorithms::paths::dijkstra::dijkstra_distances;
use crate::core::{Graph, IgraphResult};
pub fn harmonic_centrality_weighted(graph: &Graph, weights: &[f64]) -> 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 = dijkstra_distances(graph, v, weights)?;
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.0 {
sum_inv += 1.0 / *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_weighted(&g, &[]).unwrap().is_empty());
}
#[test]
fn singleton_yields_zero() {
let g = Graph::with_vertices(1);
assert_eq!(harmonic_centrality_weighted(&g, &[]).unwrap(), vec![0.0]);
}
#[test]
fn isolated_vertices_all_zero() {
let g = Graph::with_vertices(3);
assert_eq!(
harmonic_centrality_weighted(&g, &[]).unwrap(),
vec![0.0, 0.0, 0.0]
);
}
#[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();
let hw = harmonic_centrality_weighted(&g, &[1.0, 1.0]).unwrap();
let hu = crate::harmonic_centrality(&g).unwrap();
for (a, b) in hw.iter().zip(hu.iter()) {
close(*a, *b, 1e-12);
}
}
#[test]
fn weighted_path_3_with_doubled_middle_edge() {
let mut g = Graph::with_vertices(3);
g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
let h = harmonic_centrality_weighted(&g, &[1.0, 2.0]).unwrap();
close(h[0], 2.0 / 3.0, 1e-12);
close(h[1], 0.75, 1e-12);
close(h[2], 5.0 / 12.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_weighted(&g, &[1.0, 1.0]).unwrap();
close(h[0], 0.75, 1e-12);
close(h[1], 0.5, 1e-12);
close(h[2], 0.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_weighted(&g, &[1.0, 1.0]).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 weights_size_mismatch_errors() {
let mut g = Graph::with_vertices(2);
g.add_edge(0, 1).unwrap();
assert!(harmonic_centrality_weighted(&g, &[]).is_err());
}
#[test]
fn negative_weight_errors() {
let mut g = Graph::with_vertices(2);
g.add_edge(0, 1).unwrap();
assert!(harmonic_centrality_weighted(&g, &[-1.0]).is_err());
}
}