oxicuda_graphalg/centrality/
harmonic.rs1use std::collections::VecDeque;
23
24use crate::error::GraphalgResult;
25use crate::repr::adjacency_list::AdjacencyList;
26
27pub fn harmonic_centrality(g: &AdjacencyList) -> GraphalgResult<Vec<f64>> {
36 let n = g.n;
37 let mut hc = vec![0.0f64; n];
38 for s in 0..n {
39 let mut dist = vec![-1i64; n];
40 dist[s] = 0;
41 let mut q: VecDeque<usize> = VecDeque::new();
42 q.push_back(s);
43 while let Some(u) = q.pop_front() {
44 for &v in g.neighbors(u)? {
45 if dist[v] < 0 {
46 dist[v] = dist[u] + 1;
47 q.push_back(v);
48 }
49 }
50 }
51 let mut acc = 0.0f64;
52 for &d in dist.iter() {
53 if d > 0 {
54 acc += 1.0 / d as f64;
55 }
56 }
57 hc[s] = acc;
58 }
59 Ok(hc)
60}
61
62pub fn harmonic_centrality_normalized(g: &AdjacencyList) -> GraphalgResult<Vec<f64>> {
70 let n = g.n;
71 let mut hc = harmonic_centrality(g)?;
72 if n > 1 {
73 let denom = (n - 1) as f64;
74 for v in hc.iter_mut() {
75 *v /= denom;
76 }
77 }
78 Ok(hc)
79}
80
81#[cfg(test)]
82mod tests {
83 use super::*;
84
85 #[test]
86 fn path_graph_centre_value() {
87 let mut g = AdjacencyList::new(3);
89 g.add_undirected_edge(0, 1).expect("ok");
90 g.add_undirected_edge(1, 2).expect("ok");
91 let h = harmonic_centrality(&g).expect("ok");
92 assert!((h[1] - 2.0).abs() < 1e-12, "h[1] = {}", h[1]);
93 assert!((h[0] - 1.5).abs() < 1e-12, "h[0] = {}", h[0]);
95 }
96
97 #[test]
98 fn complete_graph_is_n_minus_1() {
99 let n = 4;
101 let mut g = AdjacencyList::new(n);
102 for i in 0..n {
103 for j in i + 1..n {
104 g.add_undirected_edge(i, j).expect("ok");
105 }
106 }
107 let h = harmonic_centrality(&g).expect("ok");
108 for (v, &val) in h.iter().enumerate() {
109 assert!((val - 3.0).abs() < 1e-12, "vertex {v}: {val}");
110 }
111 }
112
113 #[test]
114 fn disconnected_graph_well_defined() {
115 let mut g = AdjacencyList::new(4);
118 g.add_undirected_edge(0, 1).expect("ok");
119 g.add_undirected_edge(2, 3).expect("ok");
120 let h = harmonic_centrality(&g).expect("ok");
121 for (v, &val) in h.iter().enumerate() {
122 assert!((val - 1.0).abs() < 1e-12, "vertex {v}: {val}");
123 assert!(val.is_finite());
124 }
125 }
126
127 #[test]
128 fn isolated_vertex_is_zero() {
129 let mut g = AdjacencyList::new(3);
131 g.add_undirected_edge(0, 1).expect("ok");
132 let h = harmonic_centrality(&g).expect("ok");
133 assert!((h[2] - 0.0).abs() < 1e-12, "isolated h[2] = {}", h[2]);
134 }
135
136 #[test]
137 fn empty_graph() {
138 let g = AdjacencyList::new(0);
139 let h = harmonic_centrality(&g).expect("ok");
140 assert!(h.is_empty());
141 }
142
143 #[test]
144 fn single_vertex() {
145 let g = AdjacencyList::new(1);
146 let h = harmonic_centrality(&g).expect("ok");
147 assert_eq!(h.len(), 1);
148 assert!((h[0] - 0.0).abs() < 1e-12);
149 }
150
151 #[test]
152 fn normalized_in_unit_range() {
153 let n = 5;
154 let mut g = AdjacencyList::new(n);
155 for i in 0..n {
156 for j in i + 1..n {
157 g.add_undirected_edge(i, j).expect("ok");
158 }
159 }
160 let h = harmonic_centrality_normalized(&g).expect("ok");
161 for &val in &h {
163 assert!((val - 1.0).abs() < 1e-12, "val = {val}");
164 assert!((0.0..=1.0).contains(&val));
165 }
166 }
167
168 #[test]
169 fn directed_asymmetry() {
170 let mut g = AdjacencyList::new(3);
173 g.add_edge(0, 1).expect("ok");
174 g.add_edge(0, 2).expect("ok");
175 let h = harmonic_centrality(&g).expect("ok");
176 assert!((h[0] - 2.0).abs() < 1e-12, "h[0] = {}", h[0]);
177 assert!((h[1] - 0.0).abs() < 1e-12, "h[1] = {}", h[1]);
178 assert!((h[2] - 0.0).abs() < 1e-12, "h[2] = {}", h[2]);
179 }
180
181 #[test]
182 fn deterministic() {
183 let mut g = AdjacencyList::new(6);
184 g.add_undirected_edge(0, 1).expect("ok");
185 g.add_undirected_edge(1, 2).expect("ok");
186 g.add_undirected_edge(2, 3).expect("ok");
187 g.add_undirected_edge(3, 4).expect("ok");
188 g.add_undirected_edge(4, 5).expect("ok");
189 let a = harmonic_centrality(&g).expect("ok");
190 let b = harmonic_centrality(&g).expect("ok");
191 assert_eq!(a, b);
192 }
193
194 #[test]
195 fn all_values_finite_and_nonneg() {
196 let mut g = AdjacencyList::new(5);
198 g.add_undirected_edge(0, 1).expect("ok");
199 g.add_undirected_edge(1, 2).expect("ok");
200 g.add_undirected_edge(3, 4).expect("ok");
201 let h = harmonic_centrality(&g).expect("ok");
202 assert!(h.iter().all(|&v| v.is_finite() && v >= 0.0));
203 }
204
205 #[test]
206 fn star_centre_higher_than_leaf() {
207 let mut g = AdjacencyList::new(4);
209 g.add_undirected_edge(0, 1).expect("ok");
210 g.add_undirected_edge(0, 2).expect("ok");
211 g.add_undirected_edge(0, 3).expect("ok");
212 let h = harmonic_centrality(&g).expect("ok");
213 assert!((h[0] - 3.0).abs() < 1e-12, "centre {}", h[0]);
215 assert!((h[1] - 2.0).abs() < 1e-12, "leaf {}", h[1]);
216 assert!(h[0] > h[1]);
217 }
218}