rust_igraph/algorithms/properties/
harmonic_weighted.rs1use crate::algorithms::paths::dijkstra::dijkstra_distances;
12use crate::core::{Graph, IgraphResult};
13
14pub fn harmonic_centrality_weighted(graph: &Graph, weights: &[f64]) -> IgraphResult<Vec<f64>> {
42 let n = graph.vcount();
43 let n_us = n as usize;
44 if n < 2 {
45 return Ok(vec![0.0; n_us]);
46 }
47 let mut out = Vec::with_capacity(n_us);
48 let denom = f64::from(n - 1);
49 for v in 0..n {
50 let d = dijkstra_distances(graph, v, weights)?;
51 let mut sum_inv: f64 = 0.0;
52 let v_us = v as usize;
53 for (target, val) in d.iter().enumerate() {
54 if target == v_us {
55 continue;
56 }
57 if let Some(dist) = val {
58 if *dist > 0.0 {
59 sum_inv += 1.0 / *dist;
60 }
61 }
62 }
63 out.push(sum_inv / denom);
64 }
65 Ok(out)
66}
67
68#[cfg(test)]
69mod tests {
70 use super::*;
71
72 fn close(a: f64, b: f64, tol: f64) {
73 assert!((a - b).abs() < tol, "{a} vs {b}");
74 }
75
76 #[test]
77 fn empty_graph_yields_empty() {
78 let g = Graph::with_vertices(0);
79 assert!(harmonic_centrality_weighted(&g, &[]).unwrap().is_empty());
80 }
81
82 #[test]
83 fn singleton_yields_zero() {
84 let g = Graph::with_vertices(1);
85 assert_eq!(harmonic_centrality_weighted(&g, &[]).unwrap(), vec![0.0]);
86 }
87
88 #[test]
89 fn isolated_vertices_all_zero() {
90 let g = Graph::with_vertices(3);
91 assert_eq!(
92 harmonic_centrality_weighted(&g, &[]).unwrap(),
93 vec![0.0, 0.0, 0.0]
94 );
95 }
96
97 #[test]
98 fn unit_weights_match_unweighted() {
99 let mut g = Graph::with_vertices(3);
101 g.add_edge(0, 1).unwrap();
102 g.add_edge(1, 2).unwrap();
103 let hw = harmonic_centrality_weighted(&g, &[1.0, 1.0]).unwrap();
104 let hu = crate::harmonic_centrality(&g).unwrap();
105 for (a, b) in hw.iter().zip(hu.iter()) {
106 close(*a, *b, 1e-12);
107 }
108 }
109
110 #[test]
111 fn weighted_path_3_with_doubled_middle_edge() {
112 let mut g = Graph::with_vertices(3);
115 g.add_edge(0, 1).unwrap();
116 g.add_edge(1, 2).unwrap();
117 let h = harmonic_centrality_weighted(&g, &[1.0, 2.0]).unwrap();
118 close(h[0], 2.0 / 3.0, 1e-12);
120 close(h[1], 0.75, 1e-12);
122 close(h[2], 5.0 / 12.0, 1e-12);
124 }
125
126 #[test]
127 fn directed_path_uses_out_edges() {
128 let mut g = Graph::new(3, true).unwrap();
129 g.add_edge(0, 1).unwrap();
130 g.add_edge(1, 2).unwrap();
131 let h = harmonic_centrality_weighted(&g, &[1.0, 1.0]).unwrap();
132 close(h[0], 0.75, 1e-12);
133 close(h[1], 0.5, 1e-12);
134 close(h[2], 0.0, 1e-12);
135 }
136
137 #[test]
138 fn disconnected_components_well_defined() {
139 let mut g = Graph::with_vertices(4);
141 g.add_edge(0, 1).unwrap();
142 g.add_edge(1, 2).unwrap();
143 let h = harmonic_centrality_weighted(&g, &[1.0, 1.0]).unwrap();
144 close(h[0], 0.5, 1e-12);
146 close(h[1], 2.0 / 3.0, 1e-12);
148 close(h[2], 0.5, 1e-12);
149 close(h[3], 0.0, 1e-12);
150 }
151
152 #[test]
153 fn weights_size_mismatch_errors() {
154 let mut g = Graph::with_vertices(2);
155 g.add_edge(0, 1).unwrap();
156 assert!(harmonic_centrality_weighted(&g, &[]).is_err());
157 }
158
159 #[test]
160 fn negative_weight_errors() {
161 let mut g = Graph::with_vertices(2);
162 g.add_edge(0, 1).unwrap();
163 assert!(harmonic_centrality_weighted(&g, &[-1.0]).is_err());
164 }
165}