1use crate::core::cache::CachedProperty;
21use crate::core::graph::EdgeId;
22use crate::core::{Graph, IgraphResult};
23
24pub fn has_loop(graph: &Graph) -> IgraphResult<bool> {
38 if let Some(v) = graph.cache_get(CachedProperty::HasLoop) {
39 return Ok(v);
40 }
41 let m = u32::try_from(graph.ecount())
42 .map_err(|_| crate::IgraphError::Internal("ecount exceeds u32::MAX"))?;
43 for e in 0..m {
44 let (u, v) = graph.edge(e as EdgeId)?;
45 if u == v {
46 graph.cache_set(CachedProperty::HasLoop, true);
47 return Ok(true);
48 }
49 }
50 graph.cache_set(CachedProperty::HasLoop, false);
51 Ok(false)
52}
53
54pub fn has_multiple(graph: &Graph) -> IgraphResult<bool> {
79 if let Some(v) = graph.cache_get(CachedProperty::HasMulti) {
80 return Ok(v);
81 }
82 let m = u32::try_from(graph.ecount())
83 .map_err(|_| crate::IgraphError::Internal("ecount exceeds u32::MAX"))?;
84 if m < 2 {
85 graph.cache_set(CachedProperty::HasMulti, false);
86 return Ok(false);
87 }
88 let mut pairs: Vec<(u32, u32)> = Vec::with_capacity(m as usize);
89 for e in 0..m {
90 pairs.push(graph.edge(e as EdgeId)?);
91 }
92 pairs.sort_unstable();
93 for i in 1..pairs.len() {
94 if pairs[i] == pairs[i - 1] {
95 graph.cache_set(CachedProperty::HasMulti, true);
96 return Ok(true);
97 }
98 }
99 graph.cache_set(CachedProperty::HasMulti, false);
100 Ok(false)
101}
102
103pub fn is_loop(graph: &Graph) -> IgraphResult<Vec<bool>> {
121 let m = u32::try_from(graph.ecount())
122 .map_err(|_| crate::IgraphError::Internal("ecount exceeds u32::MAX"))?;
123 let mut out = Vec::with_capacity(m as usize);
124 for e in 0..m {
125 let (u, v) = graph.edge(e as EdgeId)?;
126 out.push(u == v);
127 }
128 Ok(out)
129}
130
131pub fn is_multiple(graph: &Graph) -> IgraphResult<Vec<bool>> {
156 let m = u32::try_from(graph.ecount())
157 .map_err(|_| crate::IgraphError::Internal("ecount exceeds u32::MAX"))?;
158 let m_us = m as usize;
159 if m_us == 0 {
160 return Ok(Vec::new());
161 }
162 let mut pairs: Vec<((u32, u32), u32)> = Vec::with_capacity(m_us);
165 for e in 0..m {
166 pairs.push((graph.edge(e as EdgeId)?, e));
167 }
168 pairs.sort_unstable_by(|a, b| a.0.cmp(&b.0).then(a.1.cmp(&b.1)));
169 let mut out = vec![false; m_us];
170 let mut i = 0usize;
171 while i < m_us {
172 let mut j = i + 1;
173 while j < m_us && pairs[j].0 == pairs[i].0 {
174 j += 1;
175 }
176 for entry in &pairs[i + 1..j] {
178 out[entry.1 as usize] = true;
179 }
180 i = j;
181 }
182 Ok(out)
183}
184
185pub fn count_loops(graph: &Graph) -> IgraphResult<usize> {
208 let m = u32::try_from(graph.ecount())
209 .map_err(|_| crate::IgraphError::Internal("ecount exceeds u32::MAX"))?;
210 let mut count = 0usize;
211 for e in 0..m {
212 let (u, v) = graph.edge(e as EdgeId)?;
213 if u == v {
214 count += 1;
215 }
216 }
217 Ok(count)
218}
219
220pub fn count_multiple(graph: &Graph) -> IgraphResult<Vec<usize>> {
250 let m = u32::try_from(graph.ecount())
251 .map_err(|_| crate::IgraphError::Internal("ecount exceeds u32::MAX"))?;
252 let m_us = m as usize;
253 if m_us == 0 {
254 return Ok(Vec::new());
255 }
256 let mut pairs: Vec<((u32, u32), u32)> = Vec::with_capacity(m_us);
257 for e in 0..m {
258 pairs.push((graph.edge(e as EdgeId)?, e));
259 }
260 pairs.sort_unstable_by_key(|p| p.0);
261 let mut out = vec![0usize; m_us];
262 let mut i = 0usize;
263 while i < m_us {
264 let mut j = i + 1;
265 while j < m_us && pairs[j].0 == pairs[i].0 {
266 j += 1;
267 }
268 let group_size = j - i;
269 for entry in &pairs[i..j] {
270 out[entry.1 as usize] = group_size;
271 }
272 i = j;
273 }
274 Ok(out)
275}
276
277pub fn count_multiple_1(graph: &Graph, eid: EdgeId) -> IgraphResult<usize> {
298 let m = graph.ecount();
299 if (eid as usize) >= m {
300 return Err(crate::IgraphError::InvalidArgument(format!(
301 "count_multiple_1: edge id {eid} out of range (ecount={m})"
302 )));
303 }
304 let (from, to) = graph.edge(eid)?;
305 let neighbors = graph.neighbors(from)?;
306 let mut count = neighbors.iter().filter(|&&nb| nb == to).count();
307 if !graph.is_directed() && from == to {
310 count /= 2;
311 }
312 Ok(count)
313}
314
315#[cfg(test)]
316mod tests {
317 use super::*;
318
319 #[test]
320 fn empty_graph_has_no_loop() {
321 let g = Graph::with_vertices(0);
322 assert!(!has_loop(&g).unwrap());
323 }
324
325 #[test]
326 fn empty_graph_has_no_multi() {
327 let g = Graph::with_vertices(0);
328 assert!(!has_multiple(&g).unwrap());
329 }
330
331 #[test]
332 fn no_edge_graph_has_neither() {
333 let g = Graph::with_vertices(5);
334 assert!(!has_loop(&g).unwrap());
335 assert!(!has_multiple(&g).unwrap());
336 }
337
338 #[test]
339 fn path_has_neither() {
340 let mut g = Graph::with_vertices(4);
341 g.add_edge(0, 1).unwrap();
342 g.add_edge(1, 2).unwrap();
343 g.add_edge(2, 3).unwrap();
344 assert!(!has_loop(&g).unwrap());
345 assert!(!has_multiple(&g).unwrap());
346 }
347
348 #[test]
349 fn detects_self_loop() {
350 let mut g = Graph::with_vertices(2);
351 g.add_edge(0, 0).unwrap();
352 assert!(has_loop(&g).unwrap());
353 assert!(!has_multiple(&g).unwrap());
355 }
356
357 #[test]
358 fn detects_parallel_undirected() {
359 let mut g = Graph::with_vertices(2);
360 g.add_edge(0, 1).unwrap();
361 g.add_edge(1, 0).unwrap();
362 assert!(!has_loop(&g).unwrap());
363 assert!(has_multiple(&g).unwrap());
364 }
365
366 #[test]
367 fn detects_parallel_directed() {
368 let mut g = Graph::new(2, true).unwrap();
369 g.add_edge(0, 1).unwrap();
370 g.add_edge(0, 1).unwrap();
371 assert!(has_multiple(&g).unwrap());
372 }
373
374 #[test]
375 fn directed_mutual_pair_not_parallel() {
376 let mut g = Graph::new(2, true).unwrap();
378 g.add_edge(0, 1).unwrap();
379 g.add_edge(1, 0).unwrap();
380 assert!(!has_multiple(&g).unwrap());
381 }
382
383 #[test]
384 fn duplicate_self_loops_count_as_parallel() {
385 let mut g = Graph::with_vertices(1);
388 g.add_edge(0, 0).unwrap();
389 g.add_edge(0, 0).unwrap();
390 assert!(has_loop(&g).unwrap());
391 assert!(has_multiple(&g).unwrap());
392 }
393
394 #[test]
395 fn is_loop_per_edge_marks_self_loops_only() {
396 let mut g = Graph::with_vertices(3);
397 g.add_edge(0, 1).unwrap();
398 g.add_edge(2, 2).unwrap();
399 g.add_edge(1, 2).unwrap();
400 assert_eq!(is_loop(&g).unwrap(), vec![false, true, false]);
401 }
402
403 #[test]
404 fn is_loop_empty_graph() {
405 let g = Graph::with_vertices(0);
406 assert!(is_loop(&g).unwrap().is_empty());
407 }
408
409 #[test]
410 fn is_multiple_per_edge_marks_only_duplicates_after_first() {
411 let mut g = Graph::with_vertices(3);
415 g.add_edge(0, 1).unwrap();
416 g.add_edge(0, 1).unwrap();
417 g.add_edge(1, 2).unwrap();
418 assert_eq!(is_multiple(&g).unwrap(), vec![false, true, false]);
419 }
420
421 #[test]
422 fn is_multiple_directed_mutual_pair_not_multiple() {
423 let mut g = Graph::new(2, true).unwrap();
424 g.add_edge(0, 1).unwrap();
425 g.add_edge(1, 0).unwrap();
426 assert_eq!(is_multiple(&g).unwrap(), vec![false, false]);
427 }
428
429 #[test]
430 fn is_multiple_three_copies_first_canonical_only() {
431 let mut g = Graph::with_vertices(2);
434 for _ in 0..3 {
435 g.add_edge(0, 1).unwrap();
436 }
437 assert_eq!(is_multiple(&g).unwrap(), vec![false, true, true]);
438 }
439
440 #[test]
441 fn is_multiple_empty_graph() {
442 let g = Graph::with_vertices(0);
443 assert!(is_multiple(&g).unwrap().is_empty());
444 }
445
446 #[test]
447 fn matches_is_simple_negation_for_simple_graphs() {
448 let mut g = Graph::with_vertices(4);
450 for u in 0..4u32 {
451 for v in (u + 1)..4 {
452 g.add_edge(u, v).unwrap();
453 }
454 }
455 assert!(!has_loop(&g).unwrap());
456 assert!(!has_multiple(&g).unwrap());
457 assert!(crate::is_simple(&g).unwrap());
458 }
459
460 #[test]
461 fn count_loops_empty_graph() {
462 let g = Graph::with_vertices(0);
463 assert_eq!(count_loops(&g).unwrap(), 0);
464 }
465
466 #[test]
467 fn count_loops_no_loops() {
468 let mut g = Graph::with_vertices(4);
469 for (u, v) in [(0, 1), (1, 2), (2, 3)] {
470 g.add_edge(u, v).unwrap();
471 }
472 assert_eq!(count_loops(&g).unwrap(), 0);
473 }
474
475 #[test]
476 fn count_loops_counts_each_self_loop() {
477 let mut g = Graph::with_vertices(3);
478 g.add_edge(0, 1).unwrap();
479 g.add_edge(1, 1).unwrap();
480 g.add_edge(2, 2).unwrap();
481 g.add_edge(2, 2).unwrap();
482 assert_eq!(count_loops(&g).unwrap(), 3);
483 }
484
485 #[test]
486 fn count_loops_directed_self_loops() {
487 let mut g = Graph::new(3, true).unwrap();
488 g.add_edge(0, 0).unwrap();
489 g.add_edge(0, 1).unwrap();
490 g.add_edge(2, 2).unwrap();
491 assert_eq!(count_loops(&g).unwrap(), 2);
492 }
493
494 #[test]
495 fn count_multiple_empty_graph() {
496 let g = Graph::with_vertices(0);
497 assert!(count_multiple(&g).unwrap().is_empty());
498 }
499
500 #[test]
501 fn count_multiple_simple_graph_all_ones() {
502 let mut g = Graph::with_vertices(4);
503 for (u, v) in [(0, 1), (1, 2), (2, 3)] {
504 g.add_edge(u, v).unwrap();
505 }
506 assert_eq!(count_multiple(&g).unwrap(), vec![1, 1, 1]);
507 }
508
509 #[test]
510 fn count_multiple_groups_parallel_undirected() {
511 let mut g = Graph::with_vertices(3);
512 g.add_edge(0, 1).unwrap();
513 g.add_edge(1, 0).unwrap();
514 g.add_edge(1, 2).unwrap();
515 assert_eq!(count_multiple(&g).unwrap(), vec![2, 2, 1]);
517 }
518
519 #[test]
520 fn count_multiple_directed_mutual_pair_distinct() {
521 let mut g = Graph::new(2, true).unwrap();
523 g.add_edge(0, 1).unwrap();
524 g.add_edge(1, 0).unwrap();
525 assert_eq!(count_multiple(&g).unwrap(), vec![1, 1]);
526 }
527
528 #[test]
529 fn count_multiple_three_parallel_copies() {
530 let mut g = Graph::with_vertices(2);
531 for _ in 0..3 {
532 g.add_edge(0, 1).unwrap();
533 }
534 assert_eq!(count_multiple(&g).unwrap(), vec![3, 3, 3]);
535 }
536
537 #[test]
538 fn count_multiple_self_loops_grouped_per_vertex() {
539 let mut g = Graph::with_vertices(3);
542 g.add_edge(0, 0).unwrap();
543 g.add_edge(0, 0).unwrap();
544 g.add_edge(2, 2).unwrap();
545 assert_eq!(count_multiple(&g).unwrap(), vec![2, 2, 1]);
546 }
547
548 #[test]
549 fn count_multiple_mixed_graph() {
550 let mut g = Graph::with_vertices(4);
552 g.add_edge(0, 1).unwrap(); g.add_edge(1, 2).unwrap(); g.add_edge(0, 1).unwrap(); g.add_edge(3, 3).unwrap(); g.add_edge(2, 3).unwrap(); assert_eq!(count_multiple(&g).unwrap(), vec![2, 1, 2, 1, 1]);
558 }
559
560 #[test]
561 fn count_multiple_length_matches_ecount() {
562 let mut g = Graph::with_vertices(5);
563 for (u, v) in [(0, 1), (1, 2), (2, 3), (3, 4), (0, 4)] {
564 g.add_edge(u, v).unwrap();
565 }
566 assert_eq!(count_multiple(&g).unwrap().len(), g.ecount());
567 }
568
569 #[test]
570 fn count_multiple_consistent_with_is_multiple_and_has_multiple() {
571 let mut g = Graph::with_vertices(3);
572 g.add_edge(0, 1).unwrap();
573 g.add_edge(0, 1).unwrap();
574 g.add_edge(1, 2).unwrap();
575 let mults = count_multiple(&g).unwrap();
576 let is_mult = is_multiple(&g).unwrap();
577 let has_mult_via_count = mults.iter().any(|&m| m > 1);
580 assert_eq!(has_mult_via_count, has_multiple(&g).unwrap());
581 for (e, &flag) in is_mult.iter().enumerate() {
583 if flag {
584 assert!(mults[e] > 1, "is_multiple but mult = {}", mults[e]);
585 }
586 }
587 }
588
589 #[test]
590 fn count_loops_consistent_with_is_loop() {
591 let mut g = Graph::with_vertices(3);
592 g.add_edge(0, 1).unwrap();
593 g.add_edge(2, 2).unwrap();
594 g.add_edge(1, 2).unwrap();
595 let n_loops = count_loops(&g).unwrap();
596 let is_l = is_loop(&g).unwrap();
597 assert_eq!(n_loops, is_l.iter().filter(|&&b| b).count());
598 }
599
600 #[test]
603 fn count_multiple_1_simple() {
604 let mut g = Graph::with_vertices(3);
605 g.add_edge(0, 1).unwrap();
606 g.add_edge(1, 2).unwrap();
607 assert_eq!(count_multiple_1(&g, 0).unwrap(), 1);
608 assert_eq!(count_multiple_1(&g, 1).unwrap(), 1);
609 }
610
611 #[test]
612 fn count_multiple_1_parallel() {
613 let mut g = Graph::with_vertices(2);
614 g.add_edge(0, 1).unwrap();
615 g.add_edge(0, 1).unwrap();
616 g.add_edge(0, 1).unwrap();
617 assert_eq!(count_multiple_1(&g, 0).unwrap(), 3);
618 assert_eq!(count_multiple_1(&g, 1).unwrap(), 3);
619 assert_eq!(count_multiple_1(&g, 2).unwrap(), 3);
620 }
621
622 #[test]
623 fn count_multiple_1_self_loop() {
624 let mut g = Graph::with_vertices(2);
625 g.add_edge(0, 0).unwrap();
626 g.add_edge(0, 0).unwrap();
627 g.add_edge(0, 1).unwrap();
628 assert_eq!(count_multiple_1(&g, 0).unwrap(), 2);
630 assert_eq!(count_multiple_1(&g, 1).unwrap(), 2);
631 assert_eq!(count_multiple_1(&g, 2).unwrap(), 1);
632 }
633
634 #[test]
635 fn count_multiple_1_out_of_range() {
636 let g = Graph::with_vertices(3);
637 assert!(count_multiple_1(&g, 5).is_err());
638 }
639
640 #[test]
641 fn count_multiple_1_consistent_with_count_multiple() {
642 let mut g = Graph::with_vertices(4);
643 g.add_edge(0, 1).unwrap();
644 g.add_edge(0, 1).unwrap();
645 g.add_edge(1, 2).unwrap();
646 g.add_edge(3, 3).unwrap();
647 let all = count_multiple(&g).unwrap();
648 #[allow(clippy::cast_possible_truncation)]
649 let ecount = g.ecount() as u32;
650 for eid in 0..ecount {
651 assert_eq!(
652 count_multiple_1(&g, eid).unwrap(),
653 all[eid as usize],
654 "mismatch at edge {eid}"
655 );
656 }
657 }
658}