rust_igraph/algorithms/properties/
bridge_ratios.rs1#![allow(
12 clippy::cast_possible_truncation,
13 clippy::cast_precision_loss,
14 clippy::many_single_char_names,
15 clippy::needless_range_loop,
16 clippy::similar_names,
17 clippy::too_many_lines
18)]
19
20use crate::core::{Graph, IgraphResult};
21
22pub fn bridge_edge_ratio(graph: &Graph) -> IgraphResult<f64> {
39 let m = graph.ecount();
40 if m == 0 {
41 return Ok(0.0);
42 }
43
44 let bridges = count_bridges(graph)?;
45 Ok(bridges as f64 / m as f64)
46}
47
48pub fn articulation_ratio(graph: &Graph) -> IgraphResult<f64> {
65 let n = graph.vcount() as usize;
66 if n < 3 || graph.ecount() == 0 {
67 return Ok(0.0);
68 }
69
70 let cut_vertices = count_articulation_points(graph)?;
71 Ok(cut_vertices as f64 / n as f64)
72}
73
74pub fn biconnected_ratio(graph: &Graph) -> IgraphResult<f64> {
93 let m = graph.ecount();
94 if m == 0 {
95 return Ok(0.0);
96 }
97
98 let max_block_edges = largest_block_edge_count(graph)?;
99 Ok(max_block_edges as f64 / m as f64)
100}
101
102pub fn leaf_ratio(graph: &Graph) -> IgraphResult<f64> {
120 let n = graph.vcount() as usize;
121 if n == 0 {
122 return Ok(0.0);
123 }
124
125 let mut leaves = 0_u64;
126 for v in 0..n {
127 if graph.degree(v as u32)? == 1 {
128 leaves += 1;
129 }
130 }
131
132 Ok(leaves as f64 / n as f64)
133}
134
135fn count_bridges(graph: &Graph) -> IgraphResult<u64> {
137 let n = graph.vcount() as usize;
138 if n == 0 {
139 return Ok(0);
140 }
141
142 let mut disc = vec![0_u32; n];
143 let mut low = vec![0_u32; n];
144 let mut visited = vec![false; n];
145 let mut timer = 1_u32;
146 let mut bridges = 0_u64;
147
148 for start in 0..n {
149 if visited[start] {
150 continue;
151 }
152
153 let mut stack: Vec<(usize, usize, usize)> = Vec::new();
155 visited[start] = true;
156 disc[start] = timer;
157 low[start] = timer;
158 timer += 1;
159 stack.push((start, usize::MAX, 0));
160
161 while let Some((v, parent, idx)) = stack.last_mut() {
162 let v = *v;
163 let parent = *parent;
164 let nbrs = graph.neighbors(v as u32)?;
165
166 if *idx < nbrs.len() {
167 let u = nbrs[*idx] as usize;
168 *idx += 1;
169
170 if !visited[u] {
171 visited[u] = true;
172 disc[u] = timer;
173 low[u] = timer;
174 timer += 1;
175 stack.push((u, v, 0));
176 } else if u != parent && disc[u] < low[v] {
177 let len = stack.len();
178 let cv = stack[len - 1].0;
179 if disc[u] < low[cv] {
180 low[cv] = disc[u];
181 }
182 }
183 } else {
184 let cv = v;
186 stack.pop();
187 if let Some(top) = stack.last_mut() {
188 let pv = top.0;
189 if low[cv] < low[pv] {
190 low[pv] = low[cv];
191 }
192 if low[cv] > disc[pv] {
193 bridges += 1;
194 }
195 }
196 }
197 }
198 }
199
200 Ok(bridges)
201}
202
203fn count_articulation_points(graph: &Graph) -> IgraphResult<u64> {
205 let n = graph.vcount() as usize;
206 if n == 0 {
207 return Ok(0);
208 }
209
210 let mut disc = vec![0_u32; n];
211 let mut low = vec![0_u32; n];
212 let mut visited = vec![false; n];
213 let mut is_cut = vec![false; n];
214 let mut timer = 1_u32;
215
216 for start in 0..n {
217 if visited[start] {
218 continue;
219 }
220
221 visited[start] = true;
222 disc[start] = timer;
223 low[start] = timer;
224 timer += 1;
225
226 let mut root_children = 0_u32;
228 let mut stack: Vec<(usize, usize, usize)> = Vec::new();
229 stack.push((start, usize::MAX, 0));
230
231 while let Some((v, parent, idx)) = stack.last_mut() {
232 let v = *v;
233 let parent = *parent;
234 let nbrs = graph.neighbors(v as u32)?;
235
236 if *idx < nbrs.len() {
237 let u = nbrs[*idx] as usize;
238 *idx += 1;
239
240 if !visited[u] {
241 visited[u] = true;
242 disc[u] = timer;
243 low[u] = timer;
244 timer += 1;
245
246 if v == start {
247 root_children += 1;
248 }
249
250 stack.push((u, v, 0));
251 } else if u != parent {
252 let len = stack.len();
253 let cv = stack[len - 1].0;
254 if disc[u] < low[cv] {
255 low[cv] = disc[u];
256 }
257 }
258 } else {
259 let cv = v;
260 stack.pop();
261 if let Some(top) = stack.last_mut() {
262 let pv = top.0;
263 if low[cv] < low[pv] {
264 low[pv] = low[cv];
265 }
266 if pv != start && low[cv] >= disc[pv] {
268 is_cut[pv] = true;
269 }
270 }
271 }
272 }
273
274 if root_children > 1 {
276 is_cut[start] = true;
277 }
278 }
279
280 Ok(is_cut.iter().filter(|&&c| c).count() as u64)
281}
282
283fn largest_block_edge_count(graph: &Graph) -> IgraphResult<u64> {
285 let n = graph.vcount() as usize;
286 if n == 0 {
287 return Ok(0);
288 }
289
290 let mut disc = vec![0_u32; n];
291 let mut low = vec![0_u32; n];
292 let mut visited = vec![false; n];
293 let mut timer = 1_u32;
294 let mut max_block = 0_u64;
295
296 let mut edge_stack: Vec<(usize, usize)> = Vec::new();
298
299 for start in 0..n {
300 if visited[start] {
301 continue;
302 }
303
304 visited[start] = true;
305 disc[start] = timer;
306 low[start] = timer;
307 timer += 1;
308
309 let mut stack: Vec<(usize, usize, usize)> = Vec::new();
310 stack.push((start, usize::MAX, 0));
311
312 while let Some((v, parent, idx)) = stack.last_mut() {
313 let v = *v;
314 let parent = *parent;
315 let nbrs = graph.neighbors(v as u32)?;
316
317 if *idx < nbrs.len() {
318 let u = nbrs[*idx] as usize;
319 *idx += 1;
320
321 if !visited[u] {
322 visited[u] = true;
323 disc[u] = timer;
324 low[u] = timer;
325 timer += 1;
326 edge_stack.push((v, u));
327 stack.push((u, v, 0));
328 } else if u != parent && disc[u] < disc[v] {
329 edge_stack.push((v, u));
330 let len = stack.len();
331 let cv = stack[len - 1].0;
332 if disc[u] < low[cv] {
333 low[cv] = disc[u];
334 }
335 }
336 } else {
337 let cv = v;
338 stack.pop();
339 if let Some(top) = stack.last_mut() {
340 let pv = top.0;
341 if low[cv] < low[pv] {
342 low[pv] = low[cv];
343 }
344 if low[cv] >= disc[pv] {
346 let mut block_edges = 0_u64;
347 while let Some(&(a, b)) = edge_stack.last() {
348 edge_stack.pop();
349 block_edges += 1;
350 if (a == pv && b == cv) || (a == cv && b == pv) {
351 break;
352 }
353 }
354 if block_edges > max_block {
355 max_block = block_edges;
356 }
357 }
358 }
359 }
360 }
361 }
362
363 Ok(max_block)
364}
365
366#[cfg(test)]
367mod tests {
368 use super::*;
369
370 fn empty() -> Graph {
371 Graph::with_vertices(0)
372 }
373
374 fn single() -> Graph {
375 Graph::with_vertices(1)
376 }
377
378 fn single_edge() -> Graph {
379 Graph::from_edges(&[(0, 1)], false, Some(2)).unwrap()
380 }
381
382 fn path3() -> Graph {
383 Graph::from_edges(&[(0, 1), (1, 2)], false, Some(3)).unwrap()
384 }
385
386 fn path4() -> Graph {
387 Graph::from_edges(&[(0, 1), (1, 2), (2, 3)], false, Some(4)).unwrap()
388 }
389
390 fn k3() -> Graph {
391 Graph::from_edges(&[(0, 1), (1, 2), (0, 2)], false, Some(3)).unwrap()
392 }
393
394 fn k4() -> Graph {
395 Graph::from_edges(
396 &[(0, 1), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)],
397 false,
398 Some(4),
399 )
400 .unwrap()
401 }
402
403 fn cycle4() -> Graph {
404 Graph::from_edges(&[(0, 1), (1, 2), (2, 3), (3, 0)], false, Some(4)).unwrap()
405 }
406
407 fn star5() -> Graph {
408 Graph::from_edges(&[(0, 1), (0, 2), (0, 3), (0, 4)], false, Some(5)).unwrap()
409 }
410
411 fn paw() -> Graph {
412 Graph::from_edges(&[(0, 1), (1, 2), (0, 2), (2, 3)], false, Some(4)).unwrap()
413 }
414
415 fn bowtie() -> Graph {
416 Graph::from_edges(
418 &[(0, 1), (1, 2), (0, 2), (2, 3), (3, 4), (2, 4)],
419 false,
420 Some(5),
421 )
422 .unwrap()
423 }
424
425 #[test]
428 fn br_empty() {
429 assert!(bridge_edge_ratio(&empty()).unwrap().abs() < 1e-10);
430 }
431
432 #[test]
433 fn br_single() {
434 assert!(bridge_edge_ratio(&single()).unwrap().abs() < 1e-10);
435 }
436
437 #[test]
438 fn br_single_edge() {
439 assert!((bridge_edge_ratio(&single_edge()).unwrap() - 1.0).abs() < 1e-10);
441 }
442
443 #[test]
444 fn br_path3() {
445 assert!((bridge_edge_ratio(&path3()).unwrap() - 1.0).abs() < 1e-10);
447 }
448
449 #[test]
450 fn br_path4() {
451 assert!((bridge_edge_ratio(&path4()).unwrap() - 1.0).abs() < 1e-10);
453 }
454
455 #[test]
456 fn br_k3() {
457 assert!(bridge_edge_ratio(&k3()).unwrap().abs() < 1e-10);
459 }
460
461 #[test]
462 fn br_k4() {
463 assert!(bridge_edge_ratio(&k4()).unwrap().abs() < 1e-10);
464 }
465
466 #[test]
467 fn br_cycle4() {
468 assert!(bridge_edge_ratio(&cycle4()).unwrap().abs() < 1e-10);
469 }
470
471 #[test]
472 fn br_star5() {
473 assert!((bridge_edge_ratio(&star5()).unwrap() - 1.0).abs() < 1e-10);
475 }
476
477 #[test]
478 fn br_paw() {
479 assert!((bridge_edge_ratio(&paw()).unwrap() - 1.0 / 4.0).abs() < 1e-10);
481 }
482
483 #[test]
484 fn br_bowtie() {
485 assert!(bridge_edge_ratio(&bowtie()).unwrap().abs() < 1e-10);
487 }
488
489 #[test]
490 fn br_in_01() {
491 for g in &[single_edge(), path3(), k3(), k4(), cycle4(), star5(), paw()] {
492 let r = bridge_edge_ratio(g).unwrap();
493 assert!(r >= -1e-10);
494 assert!(r <= 1.0 + 1e-10);
495 }
496 }
497
498 #[test]
501 fn ar_empty() {
502 assert!(articulation_ratio(&empty()).unwrap().abs() < 1e-10);
503 }
504
505 #[test]
506 fn ar_single() {
507 assert!(articulation_ratio(&single()).unwrap().abs() < 1e-10);
508 }
509
510 #[test]
511 fn ar_single_edge() {
512 assert!(articulation_ratio(&single_edge()).unwrap().abs() < 1e-10);
514 }
515
516 #[test]
517 fn ar_path3() {
518 assert!((articulation_ratio(&path3()).unwrap() - 1.0 / 3.0).abs() < 1e-10);
520 }
521
522 #[test]
523 fn ar_path4() {
524 assert!((articulation_ratio(&path4()).unwrap() - 0.5).abs() < 1e-10);
526 }
527
528 #[test]
529 fn ar_k3() {
530 assert!(articulation_ratio(&k3()).unwrap().abs() < 1e-10);
532 }
533
534 #[test]
535 fn ar_k4() {
536 assert!(articulation_ratio(&k4()).unwrap().abs() < 1e-10);
537 }
538
539 #[test]
540 fn ar_cycle4() {
541 assert!(articulation_ratio(&cycle4()).unwrap().abs() < 1e-10);
542 }
543
544 #[test]
545 fn ar_star5() {
546 assert!((articulation_ratio(&star5()).unwrap() - 1.0 / 5.0).abs() < 1e-10);
548 }
549
550 #[test]
551 fn ar_paw() {
552 assert!((articulation_ratio(&paw()).unwrap() - 1.0 / 4.0).abs() < 1e-10);
554 }
555
556 #[test]
557 fn ar_bowtie() {
558 assert!((articulation_ratio(&bowtie()).unwrap() - 1.0 / 5.0).abs() < 1e-10);
560 }
561
562 #[test]
563 fn ar_in_01() {
564 for g in &[path3(), k3(), k4(), cycle4(), star5(), paw(), bowtie()] {
565 let r = articulation_ratio(g).unwrap();
566 assert!(r >= -1e-10);
567 assert!(r <= 1.0 + 1e-10);
568 }
569 }
570
571 #[test]
574 fn bcr_empty() {
575 assert!(biconnected_ratio(&empty()).unwrap().abs() < 1e-10);
576 }
577
578 #[test]
579 fn bcr_single_edge() {
580 assert!((biconnected_ratio(&single_edge()).unwrap() - 1.0).abs() < 1e-10);
582 }
583
584 #[test]
585 fn bcr_path3() {
586 assert!((biconnected_ratio(&path3()).unwrap() - 0.5).abs() < 1e-10);
588 }
589
590 #[test]
591 fn bcr_k3() {
592 assert!((biconnected_ratio(&k3()).unwrap() - 1.0).abs() < 1e-10);
594 }
595
596 #[test]
597 fn bcr_k4() {
598 assert!((biconnected_ratio(&k4()).unwrap() - 1.0).abs() < 1e-10);
599 }
600
601 #[test]
602 fn bcr_cycle4() {
603 assert!((biconnected_ratio(&cycle4()).unwrap() - 1.0).abs() < 1e-10);
604 }
605
606 #[test]
607 fn bcr_paw() {
608 assert!((biconnected_ratio(&paw()).unwrap() - 3.0 / 4.0).abs() < 1e-10);
611 }
612
613 #[test]
614 fn bcr_bowtie() {
615 assert!((biconnected_ratio(&bowtie()).unwrap() - 0.5).abs() < 1e-10);
617 }
618
619 #[test]
620 fn bcr_in_01() {
621 for g in &[single_edge(), path3(), k3(), k4(), cycle4(), star5(), paw()] {
622 let r = biconnected_ratio(g).unwrap();
623 assert!(r >= -1e-10);
624 assert!(r <= 1.0 + 1e-10);
625 }
626 }
627
628 #[test]
631 fn lr_empty() {
632 assert!(leaf_ratio(&empty()).unwrap().abs() < 1e-10);
633 }
634
635 #[test]
636 fn lr_single() {
637 assert!(leaf_ratio(&single()).unwrap().abs() < 1e-10);
639 }
640
641 #[test]
642 fn lr_single_edge() {
643 assert!((leaf_ratio(&single_edge()).unwrap() - 1.0).abs() < 1e-10);
645 }
646
647 #[test]
648 fn lr_path3() {
649 assert!((leaf_ratio(&path3()).unwrap() - 2.0 / 3.0).abs() < 1e-10);
651 }
652
653 #[test]
654 fn lr_k3() {
655 assert!(leaf_ratio(&k3()).unwrap().abs() < 1e-10);
657 }
658
659 #[test]
660 fn lr_k4() {
661 assert!(leaf_ratio(&k4()).unwrap().abs() < 1e-10);
662 }
663
664 #[test]
665 fn lr_cycle4() {
666 assert!(leaf_ratio(&cycle4()).unwrap().abs() < 1e-10);
667 }
668
669 #[test]
670 fn lr_star5() {
671 assert!((leaf_ratio(&star5()).unwrap() - 4.0 / 5.0).abs() < 1e-10);
673 }
674
675 #[test]
676 fn lr_paw() {
677 assert!((leaf_ratio(&paw()).unwrap() - 1.0 / 4.0).abs() < 1e-10);
679 }
680
681 #[test]
682 fn lr_in_01() {
683 for g in &[single_edge(), path3(), k3(), k4(), cycle4(), star5(), paw()] {
684 let r = leaf_ratio(g).unwrap();
685 assert!(r >= -1e-10);
686 assert!(r <= 1.0 + 1e-10);
687 }
688 }
689
690 #[test]
693 fn tree_all_bridges() {
694 assert!((bridge_edge_ratio(&path3()).unwrap() - 1.0).abs() < 1e-10);
696 assert!((bridge_edge_ratio(&star5()).unwrap() - 1.0).abs() < 1e-10);
697 assert!((bridge_edge_ratio(&path4()).unwrap() - 1.0).abs() < 1e-10);
698 }
699
700 #[test]
701 fn biconnected_no_bridges() {
702 assert!(bridge_edge_ratio(&k3()).unwrap().abs() < 1e-10);
704 assert!(bridge_edge_ratio(&k4()).unwrap().abs() < 1e-10);
705 assert!(bridge_edge_ratio(&cycle4()).unwrap().abs() < 1e-10);
706 }
707
708 #[test]
709 fn biconnected_no_cut_vertices() {
710 assert!(articulation_ratio(&k3()).unwrap().abs() < 1e-10);
711 assert!(articulation_ratio(&k4()).unwrap().abs() < 1e-10);
712 assert!(articulation_ratio(&cycle4()).unwrap().abs() < 1e-10);
713 }
714
715 #[test]
716 fn biconnected_full_block() {
717 assert!((biconnected_ratio(&k3()).unwrap() - 1.0).abs() < 1e-10);
719 assert!((biconnected_ratio(&k4()).unwrap() - 1.0).abs() < 1e-10);
720 assert!((biconnected_ratio(&cycle4()).unwrap() - 1.0).abs() < 1e-10);
721 }
722}