rust_igraph/algorithms/properties/
hyper_wiener.rs1#![allow(
11 clippy::cast_possible_truncation,
12 clippy::cast_precision_loss,
13 clippy::many_single_char_names,
14 clippy::needless_range_loop,
15 clippy::too_many_lines
16)]
17
18use crate::core::{Graph, IgraphResult};
19use std::collections::VecDeque;
20
21pub fn hyper_wiener_index(graph: &Graph) -> IgraphResult<f64> {
40 let n = graph.vcount() as usize;
41 if n < 2 {
42 return Ok(0.0);
43 }
44
45 let dist = all_pairs_bfs(graph, n);
46 let mut sum_d = 0.0_f64;
47 let mut sum_d2 = 0.0_f64;
48
49 for u in 0..n {
50 for v in (u + 1)..n {
51 let d = dist[u * n + v];
52 if d == u32::MAX {
53 continue;
54 }
55 let df = f64::from(d);
56 sum_d += df;
57 sum_d2 += df * df;
58 }
59 }
60
61 Ok(0.5 * sum_d + 0.5 * sum_d2)
62}
63
64pub fn harary_index(graph: &Graph) -> IgraphResult<f64> {
82 let n = graph.vcount() as usize;
83 if n < 2 {
84 return Ok(0.0);
85 }
86
87 let dist = all_pairs_bfs(graph, n);
88 let mut h = 0.0_f64;
89
90 for u in 0..n {
91 for v in (u + 1)..n {
92 let d = dist[u * n + v];
93 if d != u32::MAX && d > 0 {
94 h += 1.0 / f64::from(d);
95 }
96 }
97 }
98
99 Ok(h)
100}
101
102fn all_pairs_bfs(graph: &Graph, n: usize) -> Vec<u32> {
103 let adj = build_adj_list(graph, n);
104 let mut dist = vec![u32::MAX; n * n];
105 for src in 0..n {
106 bfs_distances(&adj, n, src, &mut dist);
107 }
108 dist
109}
110
111fn build_adj_list(graph: &Graph, n: usize) -> Vec<Vec<usize>> {
112 let mut adj = vec![Vec::new(); n];
113 for (u, v) in graph.edges() {
114 let ui = u as usize;
115 let vi = v as usize;
116 adj[ui].push(vi);
117 if !graph.is_directed() && ui != vi {
118 adj[vi].push(ui);
119 }
120 }
121 adj
122}
123
124fn bfs_distances(adj: &[Vec<usize>], n: usize, src: usize, dist: &mut [u32]) {
125 dist[src * n + src] = 0;
126 let mut queue = VecDeque::new();
127 queue.push_back(src);
128 while let Some(v) = queue.pop_front() {
129 let d = dist[src * n + v];
130 for &w in &adj[v] {
131 if dist[src * n + w] == u32::MAX {
132 dist[src * n + w] = d.saturating_add(1);
133 queue.push_back(w);
134 }
135 }
136 }
137}
138
139#[cfg(test)]
140mod tests {
141 use super::*;
142
143 fn single_edge() -> Graph {
144 Graph::from_edges(&[(0, 1)], false, Some(2)).unwrap()
145 }
146
147 fn path3() -> Graph {
148 Graph::from_edges(&[(0, 1), (1, 2)], false, Some(3)).unwrap()
149 }
150
151 fn path4() -> Graph {
152 Graph::from_edges(&[(0, 1), (1, 2), (2, 3)], false, Some(4)).unwrap()
153 }
154
155 fn path5() -> Graph {
156 Graph::from_edges(&[(0, 1), (1, 2), (2, 3), (3, 4)], false, Some(5)).unwrap()
157 }
158
159 fn k3() -> Graph {
160 Graph::from_edges(&[(0, 1), (1, 2), (0, 2)], false, Some(3)).unwrap()
161 }
162
163 fn k4() -> Graph {
164 Graph::from_edges(
165 &[(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)],
166 false,
167 Some(4),
168 )
169 .unwrap()
170 }
171
172 fn cycle4() -> Graph {
173 Graph::from_edges(&[(0, 1), (1, 2), (2, 3), (3, 0)], false, Some(4)).unwrap()
174 }
175
176 fn cycle5() -> Graph {
177 Graph::from_edges(&[(0, 1), (1, 2), (2, 3), (3, 4), (4, 0)], false, Some(5)).unwrap()
178 }
179
180 fn star5() -> Graph {
181 Graph::from_edges(&[(0, 1), (0, 2), (0, 3), (0, 4)], false, Some(5)).unwrap()
182 }
183
184 #[test]
187 fn hww_empty() {
188 let g = Graph::with_vertices(0);
189 assert!((hyper_wiener_index(&g).unwrap() - 0.0).abs() < 1e-10);
190 }
191
192 #[test]
193 fn hww_single_vertex() {
194 let g = Graph::with_vertices(1);
195 assert!((hyper_wiener_index(&g).unwrap() - 0.0).abs() < 1e-10);
196 }
197
198 #[test]
199 fn hww_no_edges() {
200 let g = Graph::with_vertices(3);
201 assert!((hyper_wiener_index(&g).unwrap() - 0.0).abs() < 1e-10);
202 }
203
204 #[test]
205 fn hww_single_edge() {
206 assert!((hyper_wiener_index(&single_edge()).unwrap() - 1.0).abs() < 1e-10);
208 }
209
210 #[test]
211 fn hww_path3() {
212 assert!((hyper_wiener_index(&path3()).unwrap() - 5.0).abs() < 1e-10);
215 }
216
217 #[test]
218 fn hww_path4() {
219 assert!((hyper_wiener_index(&path4()).unwrap() - 15.0).abs() < 1e-10);
224 }
225
226 #[test]
227 fn hww_path5() {
228 assert!((hyper_wiener_index(&path5()).unwrap() - 35.0).abs() < 1e-10);
233 }
234
235 #[test]
236 fn hww_k3() {
237 assert!((hyper_wiener_index(&k3()).unwrap() - 3.0).abs() < 1e-10);
240 }
241
242 #[test]
243 fn hww_k4() {
244 assert!((hyper_wiener_index(&k4()).unwrap() - 6.0).abs() < 1e-10);
247 }
248
249 #[test]
250 fn hww_cycle4() {
251 assert!((hyper_wiener_index(&cycle4()).unwrap() - 10.0).abs() < 1e-10);
257 }
258
259 #[test]
260 fn hww_cycle5() {
261 assert!((hyper_wiener_index(&cycle5()).unwrap() - 20.0).abs() < 1e-10);
266 }
267
268 #[test]
269 fn hww_star5() {
270 assert!((hyper_wiener_index(&star5()).unwrap() - 22.0).abs() < 1e-10);
277 }
278
279 #[test]
280 fn hww_with_isolated() {
281 let g = Graph::from_edges(&[(0, 1)], false, Some(3)).unwrap();
283 assert!((hyper_wiener_index(&g).unwrap() - 1.0).abs() < 1e-10);
284 }
285
286 #[test]
287 fn hww_complete_formula() {
288 for n in 2_u32..=6 {
291 let edges: Vec<(u32, u32)> = (0..n)
292 .flat_map(|i| ((i + 1)..n).map(move |j| (i, j)))
293 .collect();
294 let g = Graph::from_edges(&edges, false, Some(n)).unwrap();
295 let pairs = f64::from(n) * f64::from(n - 1) / 2.0;
296 assert!((hyper_wiener_index(&g).unwrap() - pairs).abs() < 1e-10);
297 }
298 }
299
300 #[test]
301 fn hww_geq_wiener() {
302 for g in &[
304 single_edge(),
305 path3(),
306 path4(),
307 k3(),
308 k4(),
309 cycle4(),
310 star5(),
311 ] {
312 let ww = hyper_wiener_index(g).unwrap();
313 let n = g.vcount() as usize;
314 let dist = all_pairs_bfs(g, n);
315 let mut w = 0.0_f64;
316 for u in 0..n {
317 for v in (u + 1)..n {
318 let d = dist[u * n + v];
319 if d != u32::MAX {
320 w += f64::from(d);
321 }
322 }
323 }
324 assert!(ww >= w, "WW={ww} < W={w}");
325 }
326 }
327
328 #[test]
329 fn hww_tree_formula() {
330 for g in &[single_edge(), path3(), path4(), path5(), star5()] {
332 let n = g.vcount() as usize;
333 let dist = all_pairs_bfs(g, n);
334 let mut w = 0.0_f64;
335 let mut sq = 0.0_f64;
336 for u in 0..n {
337 for v in (u + 1)..n {
338 let d = dist[u * n + v];
339 if d != u32::MAX {
340 let df = f64::from(d);
341 w += df;
342 sq += df * df;
343 }
344 }
345 }
346 let ww = hyper_wiener_index(g).unwrap();
347 assert!((ww - (0.5 * w + 0.5 * sq)).abs() < 1e-10);
348 }
349 }
350
351 #[test]
352 fn hww_two_components() {
353 let g = Graph::from_edges(&[(0, 1), (2, 3)], false, Some(4)).unwrap();
356 assert!((hyper_wiener_index(&g).unwrap() - 2.0).abs() < 1e-10);
357 }
358
359 #[test]
362 fn hi_empty() {
363 let g = Graph::with_vertices(0);
364 assert!((harary_index(&g).unwrap() - 0.0).abs() < 1e-10);
365 }
366
367 #[test]
368 fn hi_single_vertex() {
369 let g = Graph::with_vertices(1);
370 assert!((harary_index(&g).unwrap() - 0.0).abs() < 1e-10);
371 }
372
373 #[test]
374 fn hi_no_edges() {
375 let g = Graph::with_vertices(3);
376 assert!((harary_index(&g).unwrap() - 0.0).abs() < 1e-10);
377 }
378
379 #[test]
380 fn hi_single_edge() {
381 assert!((harary_index(&single_edge()).unwrap() - 1.0).abs() < 1e-10);
383 }
384
385 #[test]
386 fn hi_path3() {
387 assert!((harary_index(&path3()).unwrap() - 2.5).abs() < 1e-10);
389 }
390
391 #[test]
392 fn hi_path4() {
393 let h = harary_index(&path4()).unwrap();
395 assert!((h - 13.0 / 3.0).abs() < 1e-10);
396 }
397
398 #[test]
399 fn hi_k3() {
400 assert!((harary_index(&k3()).unwrap() - 3.0).abs() < 1e-10);
402 }
403
404 #[test]
405 fn hi_k4() {
406 assert!((harary_index(&k4()).unwrap() - 6.0).abs() < 1e-10);
408 }
409
410 #[test]
411 fn hi_cycle4() {
412 assert!((harary_index(&cycle4()).unwrap() - 5.0).abs() < 1e-10);
415 }
416
417 #[test]
418 fn hi_cycle5() {
419 assert!((harary_index(&cycle5()).unwrap() - 7.5).abs() < 1e-10);
422 }
423
424 #[test]
425 fn hi_star5() {
426 assert!((harary_index(&star5()).unwrap() - 7.0).abs() < 1e-10);
430 }
431
432 #[test]
433 fn hi_complete_formula() {
434 for n in 2_u32..=6 {
436 let edges: Vec<(u32, u32)> = (0..n)
437 .flat_map(|i| ((i + 1)..n).map(move |j| (i, j)))
438 .collect();
439 let g = Graph::from_edges(&edges, false, Some(n)).unwrap();
440 let pairs = f64::from(n) * f64::from(n - 1) / 2.0;
441 assert!((harary_index(&g).unwrap() - pairs).abs() < 1e-10);
442 }
443 }
444
445 #[test]
446 fn hi_with_isolated() {
447 let g = Graph::from_edges(&[(0, 1)], false, Some(3)).unwrap();
448 assert!((harary_index(&g).unwrap() - 1.0).abs() < 1e-10);
449 }
450
451 #[test]
452 fn hi_two_components() {
453 let g = Graph::from_edges(&[(0, 1), (2, 3)], false, Some(4)).unwrap();
454 assert!((harary_index(&g).unwrap() - 2.0).abs() < 1e-10);
455 }
456
457 #[test]
458 fn hi_positive_for_connected() {
459 for g in &[
460 single_edge(),
461 path3(),
462 path4(),
463 k3(),
464 k4(),
465 cycle4(),
466 star5(),
467 ] {
468 assert!(harary_index(g).unwrap() > 0.0);
469 }
470 }
471
472 #[test]
473 fn hi_leq_pairs() {
474 for g in &[path3(), path4(), k3(), k4(), cycle4(), star5()] {
476 let n = f64::from(g.vcount());
477 let max = n * (n - 1.0) / 2.0;
478 assert!(harary_index(g).unwrap() <= max + 1e-10);
479 }
480 }
481
482 #[test]
483 fn hi_path5() {
484 let h = harary_index(&path5()).unwrap();
489 let expected = 4.0 + 1.5 + 2.0 / 3.0 + 0.25;
490 assert!((h - expected).abs() < 1e-10);
491 }
492
493 #[test]
494 fn hi_diamond() {
495 let g =
500 Graph::from_edges(&[(0, 1), (0, 2), (0, 3), (1, 2), (1, 3)], false, Some(4)).unwrap();
501 assert!((harary_index(&g).unwrap() - 5.5).abs() < 1e-10);
502 }
503
504 #[test]
507 fn hww_equals_wiener_for_d1() {
508 for n in 2_u32..=5 {
510 let edges: Vec<(u32, u32)> = (0..n)
511 .flat_map(|i| ((i + 1)..n).map(move |j| (i, j)))
512 .collect();
513 let g = Graph::from_edges(&edges, false, Some(n)).unwrap();
514 let h = harary_index(&g).unwrap();
515 let ww = hyper_wiener_index(&g).unwrap();
516 assert!((h - ww).abs() < 1e-10);
517 }
518 }
519
520 #[test]
521 fn harary_geq_wiener_reciprocal() {
522 for g in &[path3(), path4(), k3(), k4(), cycle4(), star5()] {
524 let n = g.vcount() as usize;
525 let dist = all_pairs_bfs(g, n);
526 let mut w = 0.0_f64;
527 for u in 0..n {
528 for v in (u + 1)..n {
529 let d = dist[u * n + v];
530 if d != u32::MAX {
531 w += f64::from(d);
532 }
533 }
534 }
535 let pairs = n as f64 * (n as f64 - 1.0) / 2.0;
536 let h = harary_index(g).unwrap();
537 assert!(
539 h >= pairs * pairs / w - 1e-10,
540 "H={h} < pairs²/W={}, n={n}",
541 pairs * pairs / w
542 );
543 }
544 }
545}