1#![allow(
18 clippy::cast_possible_truncation,
19 clippy::cast_precision_loss,
20 clippy::many_single_char_names,
21 clippy::needless_range_loop,
22 clippy::too_many_lines
23)]
24
25use crate::core::Graph;
26use std::collections::VecDeque;
27
28pub fn hyperbolicity_twice(graph: &Graph) -> Result<u32, crate::core::IgraphError> {
51 let n = graph.vcount() as usize;
52 if n < 4 {
53 return Ok(0);
54 }
55
56 let dist = all_pairs_bfs(graph, n);
57
58 let mut max_twice_delta: u32 = 0;
59
60 for u in 0..n {
61 for v in (u + 1)..n {
62 if dist[u * n + v] == u32::MAX {
63 continue;
64 }
65 for w in (v + 1)..n {
66 if dist[u * n + w] == u32::MAX || dist[v * n + w] == u32::MAX {
67 continue;
68 }
69 for x in (w + 1)..n {
70 if dist[u * n + x] == u32::MAX
71 || dist[v * n + x] == u32::MAX
72 || dist[w * n + x] == u32::MAX
73 {
74 continue;
75 }
76
77 let s1 = dist[u * n + v].saturating_add(dist[w * n + x]);
78 let s2 = dist[u * n + w].saturating_add(dist[v * n + x]);
79 let s3 = dist[u * n + x].saturating_add(dist[v * n + w]);
80
81 let mut sums = [s1, s2, s3];
82 sums.sort_unstable();
83
84 let twice_delta = sums[2].saturating_sub(sums[1]);
85 if twice_delta > max_twice_delta {
86 max_twice_delta = twice_delta;
87 }
88 }
89 }
90 }
91 }
92
93 Ok(max_twice_delta)
94}
95
96pub fn hyperbolicity(graph: &Graph) -> Result<f64, crate::core::IgraphError> {
110 let twice = hyperbolicity_twice(graph)?;
111 Ok(f64::from(twice) / 2.0)
112}
113
114fn all_pairs_bfs(graph: &Graph, n: usize) -> Vec<u32> {
115 let adj = build_adj_list(graph, n);
116 let mut dist = vec![u32::MAX; n * n];
117
118 for src in 0..n {
119 bfs_distances(&adj, n, src, &mut dist);
120 }
121
122 dist
123}
124
125fn build_adj_list(graph: &Graph, n: usize) -> Vec<Vec<usize>> {
126 let mut adj = vec![Vec::new(); n];
127 for (u, v) in graph.edges() {
128 let ui = u as usize;
129 let vi = v as usize;
130 adj[ui].push(vi);
131 if !graph.is_directed() {
132 adj[vi].push(ui);
133 }
134 }
135 adj
136}
137
138fn bfs_distances(adj: &[Vec<usize>], n: usize, src: usize, dist: &mut [u32]) {
139 dist[src * n + src] = 0;
140 let mut queue = VecDeque::new();
141 queue.push_back(src);
142
143 while let Some(v) = queue.pop_front() {
144 let d = dist[src * n + v];
145 for &w in &adj[v] {
146 if dist[src * n + w] == u32::MAX {
147 dist[src * n + w] = d.saturating_add(1);
148 queue.push_back(w);
149 }
150 }
151 }
152}
153
154#[cfg(test)]
155mod tests {
156 use super::*;
157
158 fn path4() -> Graph {
159 Graph::from_edges(&[(0, 1), (1, 2), (2, 3)], false, Some(4)).unwrap()
160 }
161
162 fn path5() -> Graph {
163 Graph::from_edges(&[(0, 1), (1, 2), (2, 3), (3, 4)], false, Some(5)).unwrap()
164 }
165
166 fn k4() -> Graph {
167 Graph::from_edges(
168 &[(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)],
169 false,
170 Some(4),
171 )
172 .unwrap()
173 }
174
175 fn cycle4() -> Graph {
176 Graph::from_edges(&[(0, 1), (1, 2), (2, 3), (3, 0)], false, Some(4)).unwrap()
177 }
178
179 fn cycle5() -> Graph {
180 Graph::from_edges(&[(0, 1), (1, 2), (2, 3), (3, 4), (4, 0)], false, Some(5)).unwrap()
181 }
182
183 fn cycle6() -> Graph {
184 Graph::from_edges(
185 &[(0, 1), (1, 2), (2, 3), (3, 4), (4, 5), (5, 0)],
186 false,
187 Some(6),
188 )
189 .unwrap()
190 }
191
192 fn cycle8() -> Graph {
193 Graph::from_edges(
194 &[
195 (0, 1),
196 (1, 2),
197 (2, 3),
198 (3, 4),
199 (4, 5),
200 (5, 6),
201 (6, 7),
202 (7, 0),
203 ],
204 false,
205 Some(8),
206 )
207 .unwrap()
208 }
209
210 fn star5() -> Graph {
211 Graph::from_edges(&[(0, 1), (0, 2), (0, 3), (0, 4)], false, Some(5)).unwrap()
212 }
213
214 fn petersen() -> Graph {
215 Graph::from_edges(
216 &[
217 (0, 1),
218 (1, 2),
219 (2, 3),
220 (3, 4),
221 (4, 0),
222 (0, 5),
223 (1, 6),
224 (2, 7),
225 (3, 8),
226 (4, 9),
227 (5, 7),
228 (7, 9),
229 (9, 6),
230 (6, 8),
231 (8, 5),
232 ],
233 false,
234 Some(10),
235 )
236 .unwrap()
237 }
238
239 #[test]
242 fn ht_empty() {
243 let g = Graph::with_vertices(0);
244 assert_eq!(hyperbolicity_twice(&g).unwrap(), 0);
245 }
246
247 #[test]
248 fn ht_small() {
249 let g = Graph::with_vertices(3);
250 assert_eq!(hyperbolicity_twice(&g).unwrap(), 0);
251 }
252
253 #[test]
254 fn ht_path4() {
255 assert_eq!(hyperbolicity_twice(&path4()).unwrap(), 0);
257 }
258
259 #[test]
260 fn ht_path5() {
261 assert_eq!(hyperbolicity_twice(&path5()).unwrap(), 0);
262 }
263
264 #[test]
265 fn ht_star5() {
266 assert_eq!(hyperbolicity_twice(&star5()).unwrap(), 0);
268 }
269
270 #[test]
271 fn ht_k4() {
272 assert_eq!(hyperbolicity_twice(&k4()).unwrap(), 0);
275 }
276
277 #[test]
278 fn ht_cycle4() {
279 assert_eq!(hyperbolicity_twice(&cycle4()).unwrap(), 2);
281 }
282
283 #[test]
284 fn ht_cycle5() {
285 let ht = hyperbolicity_twice(&cycle5()).unwrap();
287 assert_eq!(ht, 1);
288 }
289
290 #[test]
291 fn ht_cycle6() {
292 let ht = hyperbolicity_twice(&cycle6()).unwrap();
294 assert_eq!(ht, 2);
295 }
296
297 #[test]
298 fn ht_cycle8() {
299 assert_eq!(hyperbolicity_twice(&cycle8()).unwrap(), 4);
301 }
302
303 #[test]
304 fn ht_petersen() {
305 let ht = hyperbolicity_twice(&petersen()).unwrap();
307 assert_eq!(ht, 1);
308 }
309
310 #[test]
313 fn h_path() {
314 let h = hyperbolicity(&path4()).unwrap();
315 assert!((h - 0.0).abs() < 1e-10);
316 }
317
318 #[test]
319 fn h_cycle4() {
320 let h = hyperbolicity(&cycle4()).unwrap();
321 assert!((h - 1.0).abs() < 1e-10);
322 }
323
324 #[test]
325 fn h_cycle8() {
326 let h = hyperbolicity(&cycle8()).unwrap();
327 assert!((h - 2.0).abs() < 1e-10);
328 }
329
330 #[test]
333 fn tree_hyperbolicity_zero() {
334 let trees = vec![
335 path4(),
336 path5(),
337 star5(),
338 Graph::from_edges(&[(0, 1), (1, 2), (2, 3), (2, 4)], false, Some(5)).unwrap(),
339 ];
340 for g in &trees {
341 assert_eq!(hyperbolicity_twice(g).unwrap(), 0);
342 }
343 }
344
345 #[test]
346 fn hyperbolicity_non_negative() {
347 for g in &[path4(), k4(), cycle4(), cycle5(), star5(), petersen()] {
348 assert!(hyperbolicity(g).unwrap() >= 0.0);
349 }
350 }
351
352 #[test]
353 fn hyperbolicity_leq_half_diameter() {
354 for g in &[cycle4(), cycle5(), cycle6(), cycle8(), petersen()] {
356 let h = hyperbolicity(g).unwrap();
357 let diam = g.diameter().unwrap().unwrap_or(0);
358 assert!(
359 h <= f64::from(diam) / 2.0 + 1e-10,
360 "δ={h} > diameter/2={}",
361 f64::from(diam) / 2.0
362 );
363 }
364 }
365}