rust_igraph/algorithms/properties/
path_ratios.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::similar_names,
16 clippy::too_many_lines
17)]
18
19use crate::core::{Graph, IgraphResult};
20
21pub fn diameter_radius_ratio(graph: &Graph) -> IgraphResult<f64> {
37 let n = graph.vcount() as usize;
38 if n < 2 {
39 return Ok(0.0);
40 }
41
42 let dist = all_pairs_bfs(graph)?;
43
44 let mut max_ecc = 0_u32;
45 let mut min_ecc = u32::MAX;
46
47 for v in 0..n {
48 let mut ecc = 0_u32;
49 for u in 0..n {
50 if u == v {
51 continue;
52 }
53 let d = dist[v * n + u];
54 if d == u32::MAX {
55 return Ok(0.0);
56 }
57 if d > ecc {
58 ecc = d;
59 }
60 }
61 if ecc > max_ecc {
62 max_ecc = ecc;
63 }
64 if ecc < min_ecc {
65 min_ecc = ecc;
66 }
67 }
68
69 if min_ecc == 0 {
70 return Ok(0.0);
71 }
72
73 Ok(f64::from(max_ecc) / f64::from(min_ecc))
74}
75
76pub fn avg_path_fraction(graph: &Graph) -> IgraphResult<f64> {
93 let n = graph.vcount() as usize;
94 if n < 2 {
95 return Ok(0.0);
96 }
97
98 let dist = all_pairs_bfs(graph)?;
99
100 let mut sum = 0_u64;
101 let mut diameter = 0_u32;
102 let mut pair_count = 0_u64;
103
104 for v in 0..n {
105 for u in (v + 1)..n {
106 let d = dist[v * n + u];
107 if d == u32::MAX {
108 return Ok(0.0);
109 }
110 sum += u64::from(d);
111 pair_count += 1;
112 if d > diameter {
113 diameter = d;
114 }
115 }
116 }
117
118 if diameter == 0 || pair_count == 0 {
119 return Ok(0.0);
120 }
121
122 let avg_dist = sum as f64 / pair_count as f64;
123 Ok(avg_dist / f64::from(diameter))
124}
125
126pub fn efficiency_ratio(graph: &Graph) -> IgraphResult<f64> {
144 let n = graph.vcount() as usize;
145 if n < 2 {
146 return Ok(0.0);
147 }
148
149 let dist = all_pairs_bfs(graph)?;
150
151 let mut sum_inv = 0.0_f64;
152 let mut pair_count = 0_u64;
153
154 for v in 0..n {
155 for u in (v + 1)..n {
156 let d = dist[v * n + u];
157 if d != u32::MAX && d > 0 {
158 sum_inv += 1.0 / f64::from(d);
159 }
160 pair_count += 1;
161 }
162 }
163
164 if pair_count == 0 {
165 return Ok(0.0);
166 }
167
168 Ok(sum_inv / pair_count as f64)
169}
170
171pub fn graph_compactness(graph: &Graph) -> IgraphResult<f64> {
188 let n = graph.vcount() as usize;
189 if n < 2 {
190 return Ok(0.0);
191 }
192
193 let dist = all_pairs_bfs(graph)?;
194
195 let mut sum = 0_u64;
196 let mut diameter = 0_u32;
197 let mut pair_count = 0_u64;
198
199 for v in 0..n {
200 for u in (v + 1)..n {
201 let d = dist[v * n + u];
202 if d == u32::MAX {
203 return Ok(0.0);
204 }
205 sum += u64::from(d);
206 pair_count += 1;
207 if d > diameter {
208 diameter = d;
209 }
210 }
211 }
212
213 if diameter == 0 || pair_count == 0 {
214 return Ok(0.0);
215 }
216
217 let avg_dist = sum as f64 / pair_count as f64;
218 Ok(1.0 - avg_dist / f64::from(diameter))
219}
220
221fn all_pairs_bfs(graph: &Graph) -> IgraphResult<Vec<u32>> {
222 let n = graph.vcount() as usize;
223 let mut dist = vec![u32::MAX; n * n];
224
225 for s in 0..n {
226 dist[s * n + s] = 0;
227 let mut queue = std::collections::VecDeque::new();
228 queue.push_back(s);
229 while let Some(v) = queue.pop_front() {
230 let d = dist[s * n + v];
231 let neighbors = graph.neighbors(v as u32)?;
232 for &u in &neighbors {
233 let ui = u as usize;
234 if dist[s * n + ui] == u32::MAX {
235 dist[s * n + ui] = d + 1;
236 queue.push_back(ui);
237 }
238 }
239 }
240 }
241
242 Ok(dist)
243}
244
245#[cfg(test)]
246mod tests {
247 use super::*;
248
249 fn single_edge() -> Graph {
250 Graph::from_edges(&[(0, 1)], false, Some(2)).unwrap()
251 }
252
253 fn path3() -> Graph {
254 Graph::from_edges(&[(0, 1), (1, 2)], false, Some(3)).unwrap()
255 }
256
257 fn k3() -> Graph {
258 Graph::from_edges(&[(0, 1), (1, 2), (0, 2)], false, Some(3)).unwrap()
259 }
260
261 fn k4() -> Graph {
262 Graph::from_edges(
263 &[(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)],
264 false,
265 Some(4),
266 )
267 .unwrap()
268 }
269
270 fn cycle4() -> Graph {
271 Graph::from_edges(&[(0, 1), (1, 2), (2, 3), (3, 0)], false, Some(4)).unwrap()
272 }
273
274 fn star5() -> Graph {
275 Graph::from_edges(&[(0, 1), (0, 2), (0, 3), (0, 4)], false, Some(5)).unwrap()
276 }
277
278 fn paw() -> Graph {
279 Graph::from_edges(&[(0, 1), (1, 2), (0, 2), (2, 3)], false, Some(4)).unwrap()
280 }
281
282 #[test]
285 fn drr_empty() {
286 let g = Graph::with_vertices(0);
287 assert!(diameter_radius_ratio(&g).unwrap().abs() < 1e-10);
288 }
289
290 #[test]
291 fn drr_single() {
292 let g = Graph::with_vertices(1);
293 assert!(diameter_radius_ratio(&g).unwrap().abs() < 1e-10);
294 }
295
296 #[test]
297 fn drr_single_edge() {
298 assert!((diameter_radius_ratio(&single_edge()).unwrap() - 1.0).abs() < 1e-10);
300 }
301
302 #[test]
303 fn drr_k3() {
304 assert!((diameter_radius_ratio(&k3()).unwrap() - 1.0).abs() < 1e-10);
306 }
307
308 #[test]
309 fn drr_k4() {
310 assert!((diameter_radius_ratio(&k4()).unwrap() - 1.0).abs() < 1e-10);
312 }
313
314 #[test]
315 fn drr_cycle4() {
316 assert!((diameter_radius_ratio(&cycle4()).unwrap() - 1.0).abs() < 1e-10);
318 }
319
320 #[test]
321 fn drr_path3() {
322 assert!((diameter_radius_ratio(&path3()).unwrap() - 2.0).abs() < 1e-10);
324 }
325
326 #[test]
327 fn drr_star5() {
328 assert!((diameter_radius_ratio(&star5()).unwrap() - 2.0).abs() < 1e-10);
330 }
331
332 #[test]
333 fn drr_paw() {
334 assert!((diameter_radius_ratio(&paw()).unwrap() - 2.0).abs() < 1e-10);
341 }
342
343 #[test]
344 fn drr_disconnected() {
345 let g = Graph::from_edges(&[(0, 1), (2, 3)], false, Some(4)).unwrap();
347 assert!(diameter_radius_ratio(&g).unwrap().abs() < 1e-10);
348 }
349
350 #[test]
353 fn apf_empty() {
354 let g = Graph::with_vertices(0);
355 assert!(avg_path_fraction(&g).unwrap().abs() < 1e-10);
356 }
357
358 #[test]
359 fn apf_single() {
360 let g = Graph::with_vertices(1);
361 assert!(avg_path_fraction(&g).unwrap().abs() < 1e-10);
362 }
363
364 #[test]
365 fn apf_k3() {
366 assert!((avg_path_fraction(&k3()).unwrap() - 1.0).abs() < 1e-10);
368 }
369
370 #[test]
371 fn apf_k4() {
372 assert!((avg_path_fraction(&k4()).unwrap() - 1.0).abs() < 1e-10);
374 }
375
376 #[test]
377 fn apf_path3() {
378 assert!((avg_path_fraction(&path3()).unwrap() - 2.0 / 3.0).abs() < 1e-10);
381 }
382
383 #[test]
384 fn apf_cycle4() {
385 assert!((avg_path_fraction(&cycle4()).unwrap() - 2.0 / 3.0).abs() < 1e-10);
388 }
389
390 #[test]
391 fn apf_disconnected() {
392 let g = Graph::from_edges(&[(0, 1), (2, 3)], false, Some(4)).unwrap();
393 assert!(avg_path_fraction(&g).unwrap().abs() < 1e-10);
394 }
395
396 #[test]
399 fn er_empty() {
400 let g = Graph::with_vertices(0);
401 assert!(efficiency_ratio(&g).unwrap().abs() < 1e-10);
402 }
403
404 #[test]
405 fn er_single() {
406 let g = Graph::with_vertices(1);
407 assert!(efficiency_ratio(&g).unwrap().abs() < 1e-10);
408 }
409
410 #[test]
411 fn er_k3() {
412 assert!((efficiency_ratio(&k3()).unwrap() - 1.0).abs() < 1e-10);
414 }
415
416 #[test]
417 fn er_k4() {
418 assert!((efficiency_ratio(&k4()).unwrap() - 1.0).abs() < 1e-10);
420 }
421
422 #[test]
423 fn er_path3() {
424 assert!((efficiency_ratio(&path3()).unwrap() - 2.5 / 3.0).abs() < 1e-10);
426 }
427
428 #[test]
429 fn er_cycle4() {
430 assert!((efficiency_ratio(&cycle4()).unwrap() - 5.0 / 6.0).abs() < 1e-10);
433 }
434
435 #[test]
436 fn er_isolated() {
437 let g = Graph::with_vertices(3);
438 assert!(efficiency_ratio(&g).unwrap().abs() < 1e-10);
440 }
441
442 #[test]
445 fn gc_empty() {
446 let g = Graph::with_vertices(0);
447 assert!(graph_compactness(&g).unwrap().abs() < 1e-10);
448 }
449
450 #[test]
451 fn gc_single() {
452 let g = Graph::with_vertices(1);
453 assert!(graph_compactness(&g).unwrap().abs() < 1e-10);
454 }
455
456 #[test]
457 fn gc_k3() {
458 assert!(graph_compactness(&k3()).unwrap().abs() < 1e-10);
460 }
461
462 #[test]
463 fn gc_k4() {
464 assert!(graph_compactness(&k4()).unwrap().abs() < 1e-10);
466 }
467
468 #[test]
469 fn gc_path3() {
470 assert!((graph_compactness(&path3()).unwrap() - 1.0 / 3.0).abs() < 1e-10);
472 }
473
474 #[test]
475 fn gc_cycle4() {
476 assert!((graph_compactness(&cycle4()).unwrap() - 1.0 / 3.0).abs() < 1e-10);
478 }
479
480 #[test]
481 fn gc_disconnected() {
482 let g = Graph::from_edges(&[(0, 1), (2, 3)], false, Some(4)).unwrap();
483 assert!(graph_compactness(&g).unwrap().abs() < 1e-10);
484 }
485
486 #[test]
489 fn drr_ge1() {
490 for g in &[single_edge(), path3(), k3(), k4(), cycle4(), star5(), paw()] {
491 let r = diameter_radius_ratio(g).unwrap();
492 assert!(r >= 1.0 - 1e-10 || r.abs() < 1e-10);
493 }
494 }
495
496 #[test]
497 fn apf_in_01() {
498 for g in &[single_edge(), path3(), k3(), k4(), cycle4(), star5(), paw()] {
499 let r = avg_path_fraction(g).unwrap();
500 assert!(r >= -1e-10);
501 assert!(r <= 1.0 + 1e-10);
502 }
503 }
504
505 #[test]
506 fn er_in_01() {
507 for g in &[single_edge(), path3(), k3(), k4(), cycle4(), star5(), paw()] {
508 let r = efficiency_ratio(g).unwrap();
509 assert!(r >= -1e-10);
510 assert!(r <= 1.0 + 1e-10);
511 }
512 }
513
514 #[test]
515 fn gc_in_01() {
516 for g in &[single_edge(), path3(), k3(), k4(), cycle4(), star5(), paw()] {
517 let r = graph_compactness(g).unwrap();
518 assert!(r >= -1e-10);
519 assert!(r <= 1.0 + 1e-10);
520 }
521 }
522
523 #[test]
524 fn complete_graphs_self_centered() {
525 assert!((diameter_radius_ratio(&k3()).unwrap() - 1.0).abs() < 1e-10);
527 assert!((diameter_radius_ratio(&k4()).unwrap() - 1.0).abs() < 1e-10);
528 }
529
530 #[test]
531 fn complete_graphs_full_efficiency() {
532 assert!((efficiency_ratio(&k3()).unwrap() - 1.0).abs() < 1e-10);
533 assert!((efficiency_ratio(&k4()).unwrap() - 1.0).abs() < 1e-10);
534 }
535}