rust_igraph/algorithms/properties/
harmonic_cutoff.rs1use crate::core::{Graph, IgraphResult, VertexId};
11use std::collections::VecDeque;
12
13pub fn harmonic_centrality_cutoff(
46 graph: &Graph,
47 cutoff: u32,
48 normalized: bool,
49) -> IgraphResult<Vec<f64>> {
50 let n = graph.vcount();
51 let n_us = n as usize;
52
53 if n < 2 {
54 return Ok(vec![0.0; n_us]);
55 }
56
57 let denom = f64::from(n - 1);
58 let mut out = Vec::with_capacity(n_us);
59
60 for v in 0..n {
61 let sum_inv = bfs_harmonic_cutoff(graph, v, cutoff)?;
62 if normalized {
63 out.push(sum_inv / denom);
64 } else {
65 out.push(sum_inv);
66 }
67 }
68
69 Ok(out)
70}
71
72fn bfs_harmonic_cutoff(graph: &Graph, source: VertexId, cutoff: u32) -> IgraphResult<f64> {
75 let n = graph.vcount() as usize;
76 let mut visited = vec![false; n];
77 visited[source as usize] = true;
78
79 let mut queue: VecDeque<(VertexId, u32)> = VecDeque::new();
80 queue.push_back((source, 0));
81
82 let mut sum_inv: f64 = 0.0;
83
84 while let Some((node, dist)) = queue.pop_front() {
85 if dist > cutoff {
86 continue;
87 }
88
89 if node != source && dist > 0 {
90 sum_inv += 1.0 / f64::from(dist);
91 }
92
93 if dist == cutoff {
94 continue;
95 }
96
97 for &neighbor in &graph.neighbors(node)? {
98 if !visited[neighbor as usize] {
99 visited[neighbor as usize] = true;
100 queue.push_back((neighbor, dist.saturating_add(1)));
101 }
102 }
103 }
104
105 Ok(sum_inv)
106}
107
108#[cfg(test)]
109mod tests {
110 use super::*;
111 use crate::core::Graph;
112
113 #[test]
114 fn empty_graph() {
115 let g = Graph::new(0, false).unwrap();
116 let h = harmonic_centrality_cutoff(&g, 1, true).unwrap();
117 assert!(h.is_empty());
118 }
119
120 #[test]
121 fn single_vertex() {
122 let g = Graph::new(1, false).unwrap();
123 let h = harmonic_centrality_cutoff(&g, 1, true).unwrap();
124 assert_eq!(h, vec![0.0]);
125 }
126
127 #[test]
128 fn disconnected() {
129 let g = Graph::new(3, false).unwrap();
130 let h = harmonic_centrality_cutoff(&g, 5, true).unwrap();
131 assert_eq!(h, vec![0.0, 0.0, 0.0]);
132 }
133
134 #[test]
135 fn complete_graph_cutoff_1() {
136 let mut g = Graph::new(4, false).unwrap();
137 g.add_edges(vec![(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)])
138 .unwrap();
139 let h = harmonic_centrality_cutoff(&g, 1, true).unwrap();
140 for &val in &h {
142 assert!((val - 1.0).abs() < 1e-12);
143 }
144 }
145
146 #[test]
147 fn path_cutoff_1() {
148 let mut g = Graph::new(4, false).unwrap();
150 g.add_edges(vec![(0, 1), (1, 2), (2, 3)]).unwrap();
151 let h = harmonic_centrality_cutoff(&g, 1, true).unwrap();
152 let expected = 1.0 / 3.0;
154 assert!((h[0] - expected).abs() < 1e-12);
155 let expected1 = 2.0 / 3.0;
157 assert!((h[1] - expected1).abs() < 1e-12);
158 }
159
160 #[test]
161 fn path_cutoff_2() {
162 let mut g = Graph::new(5, false).unwrap();
164 g.add_edges(vec![(0, 1), (1, 2), (2, 3), (3, 4)]).unwrap();
165 let h = harmonic_centrality_cutoff(&g, 2, true).unwrap();
166 let expected = 1.5 / 4.0;
168 assert!((h[0] - expected).abs() < 1e-12);
169 }
170
171 #[test]
172 fn large_cutoff_matches_harmonic() {
173 let mut g = Graph::new(3, false).unwrap();
176 g.add_edges(vec![(0, 1), (1, 2)]).unwrap();
177 let h = harmonic_centrality_cutoff(&g, 100, true).unwrap();
178 assert!((h[0] - 0.75).abs() < 1e-12);
180 assert!((h[1] - 1.0).abs() < 1e-12);
182 }
183
184 #[test]
185 fn normalized_false() {
186 let mut g = Graph::new(3, false).unwrap();
188 g.add_edges(vec![(0, 1), (1, 2)]).unwrap();
189 let h = harmonic_centrality_cutoff(&g, 10, false).unwrap();
190 assert!((h[0] - 1.5).abs() < 1e-12);
192 assert!((h[1] - 2.0).abs() < 1e-12);
194 }
195
196 #[test]
197 fn cutoff_zero() {
198 let mut g = Graph::new(3, false).unwrap();
199 g.add_edges(vec![(0, 1), (1, 2)]).unwrap();
200 let h = harmonic_centrality_cutoff(&g, 0, true).unwrap();
201 assert_eq!(h, vec![0.0, 0.0, 0.0]);
203 }
204
205 #[test]
206 fn directed_graph() {
207 let mut g = Graph::new(3, true).unwrap();
209 g.add_edges(vec![(0, 1), (1, 2)]).unwrap();
210 let h = harmonic_centrality_cutoff(&g, 10, true).unwrap();
211 assert!((h[0] - 0.75).abs() < 1e-12);
213 assert!((h[2] - 0.0).abs() < 1e-12);
215 }
216
217 #[test]
218 fn star_cutoff_1() {
219 let mut g = Graph::new(5, false).unwrap();
221 g.add_edges(vec![(0, 1), (0, 2), (0, 3), (0, 4)]).unwrap();
222 let h = harmonic_centrality_cutoff(&g, 1, true).unwrap();
223 assert!((h[0] - 1.0).abs() < 1e-12);
225 assert!((h[1] - 0.25).abs() < 1e-12);
227 }
228}