rust_igraph/algorithms/properties/
multiplicity.rs1use crate::core::graph::EdgeId;
21use crate::core::{Graph, IgraphResult};
22
23pub fn has_loop(graph: &Graph) -> IgraphResult<bool> {
37 let m = u32::try_from(graph.ecount())
38 .map_err(|_| crate::IgraphError::Internal("ecount exceeds u32::MAX"))?;
39 for e in 0..m {
40 let (u, v) = graph.edge(e as EdgeId)?;
41 if u == v {
42 return Ok(true);
43 }
44 }
45 Ok(false)
46}
47
48pub fn has_multiple(graph: &Graph) -> IgraphResult<bool> {
73 let m = u32::try_from(graph.ecount())
74 .map_err(|_| crate::IgraphError::Internal("ecount exceeds u32::MAX"))?;
75 if m < 2 {
76 return Ok(false);
77 }
78 let mut pairs: Vec<(u32, u32)> = Vec::with_capacity(m as usize);
79 for e in 0..m {
80 pairs.push(graph.edge(e as EdgeId)?);
81 }
82 pairs.sort_unstable();
83 for i in 1..pairs.len() {
84 if pairs[i] == pairs[i - 1] {
85 return Ok(true);
86 }
87 }
88 Ok(false)
89}
90
91pub fn is_loop(graph: &Graph) -> IgraphResult<Vec<bool>> {
109 let m = u32::try_from(graph.ecount())
110 .map_err(|_| crate::IgraphError::Internal("ecount exceeds u32::MAX"))?;
111 let mut out = Vec::with_capacity(m as usize);
112 for e in 0..m {
113 let (u, v) = graph.edge(e as EdgeId)?;
114 out.push(u == v);
115 }
116 Ok(out)
117}
118
119pub fn is_multiple(graph: &Graph) -> IgraphResult<Vec<bool>> {
144 let m = u32::try_from(graph.ecount())
145 .map_err(|_| crate::IgraphError::Internal("ecount exceeds u32::MAX"))?;
146 let m_us = m as usize;
147 if m_us == 0 {
148 return Ok(Vec::new());
149 }
150 let mut pairs: Vec<((u32, u32), u32)> = Vec::with_capacity(m_us);
153 for e in 0..m {
154 pairs.push((graph.edge(e as EdgeId)?, e));
155 }
156 pairs.sort_unstable_by(|a, b| a.0.cmp(&b.0).then(a.1.cmp(&b.1)));
157 let mut out = vec![false; m_us];
158 let mut i = 0usize;
159 while i < m_us {
160 let mut j = i + 1;
161 while j < m_us && pairs[j].0 == pairs[i].0 {
162 j += 1;
163 }
164 for entry in &pairs[i + 1..j] {
166 out[entry.1 as usize] = true;
167 }
168 i = j;
169 }
170 Ok(out)
171}
172
173pub fn count_loops(graph: &Graph) -> IgraphResult<usize> {
196 let m = u32::try_from(graph.ecount())
197 .map_err(|_| crate::IgraphError::Internal("ecount exceeds u32::MAX"))?;
198 let mut count = 0usize;
199 for e in 0..m {
200 let (u, v) = graph.edge(e as EdgeId)?;
201 if u == v {
202 count += 1;
203 }
204 }
205 Ok(count)
206}
207
208pub fn count_multiple(graph: &Graph) -> IgraphResult<Vec<usize>> {
238 let m = u32::try_from(graph.ecount())
239 .map_err(|_| crate::IgraphError::Internal("ecount exceeds u32::MAX"))?;
240 let m_us = m as usize;
241 if m_us == 0 {
242 return Ok(Vec::new());
243 }
244 let mut pairs: Vec<((u32, u32), u32)> = Vec::with_capacity(m_us);
245 for e in 0..m {
246 pairs.push((graph.edge(e as EdgeId)?, e));
247 }
248 pairs.sort_unstable_by_key(|p| p.0);
249 let mut out = vec![0usize; m_us];
250 let mut i = 0usize;
251 while i < m_us {
252 let mut j = i + 1;
253 while j < m_us && pairs[j].0 == pairs[i].0 {
254 j += 1;
255 }
256 let group_size = j - i;
257 for entry in &pairs[i..j] {
258 out[entry.1 as usize] = group_size;
259 }
260 i = j;
261 }
262 Ok(out)
263}
264
265#[cfg(test)]
266mod tests {
267 use super::*;
268
269 #[test]
270 fn empty_graph_has_no_loop() {
271 let g = Graph::with_vertices(0);
272 assert!(!has_loop(&g).unwrap());
273 }
274
275 #[test]
276 fn empty_graph_has_no_multi() {
277 let g = Graph::with_vertices(0);
278 assert!(!has_multiple(&g).unwrap());
279 }
280
281 #[test]
282 fn no_edge_graph_has_neither() {
283 let g = Graph::with_vertices(5);
284 assert!(!has_loop(&g).unwrap());
285 assert!(!has_multiple(&g).unwrap());
286 }
287
288 #[test]
289 fn path_has_neither() {
290 let mut g = Graph::with_vertices(4);
291 g.add_edge(0, 1).unwrap();
292 g.add_edge(1, 2).unwrap();
293 g.add_edge(2, 3).unwrap();
294 assert!(!has_loop(&g).unwrap());
295 assert!(!has_multiple(&g).unwrap());
296 }
297
298 #[test]
299 fn detects_self_loop() {
300 let mut g = Graph::with_vertices(2);
301 g.add_edge(0, 0).unwrap();
302 assert!(has_loop(&g).unwrap());
303 assert!(!has_multiple(&g).unwrap());
305 }
306
307 #[test]
308 fn detects_parallel_undirected() {
309 let mut g = Graph::with_vertices(2);
310 g.add_edge(0, 1).unwrap();
311 g.add_edge(1, 0).unwrap();
312 assert!(!has_loop(&g).unwrap());
313 assert!(has_multiple(&g).unwrap());
314 }
315
316 #[test]
317 fn detects_parallel_directed() {
318 let mut g = Graph::new(2, true).unwrap();
319 g.add_edge(0, 1).unwrap();
320 g.add_edge(0, 1).unwrap();
321 assert!(has_multiple(&g).unwrap());
322 }
323
324 #[test]
325 fn directed_mutual_pair_not_parallel() {
326 let mut g = Graph::new(2, true).unwrap();
328 g.add_edge(0, 1).unwrap();
329 g.add_edge(1, 0).unwrap();
330 assert!(!has_multiple(&g).unwrap());
331 }
332
333 #[test]
334 fn duplicate_self_loops_count_as_parallel() {
335 let mut g = Graph::with_vertices(1);
338 g.add_edge(0, 0).unwrap();
339 g.add_edge(0, 0).unwrap();
340 assert!(has_loop(&g).unwrap());
341 assert!(has_multiple(&g).unwrap());
342 }
343
344 #[test]
345 fn is_loop_per_edge_marks_self_loops_only() {
346 let mut g = Graph::with_vertices(3);
347 g.add_edge(0, 1).unwrap();
348 g.add_edge(2, 2).unwrap();
349 g.add_edge(1, 2).unwrap();
350 assert_eq!(is_loop(&g).unwrap(), vec![false, true, false]);
351 }
352
353 #[test]
354 fn is_loop_empty_graph() {
355 let g = Graph::with_vertices(0);
356 assert!(is_loop(&g).unwrap().is_empty());
357 }
358
359 #[test]
360 fn is_multiple_per_edge_marks_only_duplicates_after_first() {
361 let mut g = Graph::with_vertices(3);
365 g.add_edge(0, 1).unwrap();
366 g.add_edge(0, 1).unwrap();
367 g.add_edge(1, 2).unwrap();
368 assert_eq!(is_multiple(&g).unwrap(), vec![false, true, false]);
369 }
370
371 #[test]
372 fn is_multiple_directed_mutual_pair_not_multiple() {
373 let mut g = Graph::new(2, true).unwrap();
374 g.add_edge(0, 1).unwrap();
375 g.add_edge(1, 0).unwrap();
376 assert_eq!(is_multiple(&g).unwrap(), vec![false, false]);
377 }
378
379 #[test]
380 fn is_multiple_three_copies_first_canonical_only() {
381 let mut g = Graph::with_vertices(2);
384 for _ in 0..3 {
385 g.add_edge(0, 1).unwrap();
386 }
387 assert_eq!(is_multiple(&g).unwrap(), vec![false, true, true]);
388 }
389
390 #[test]
391 fn is_multiple_empty_graph() {
392 let g = Graph::with_vertices(0);
393 assert!(is_multiple(&g).unwrap().is_empty());
394 }
395
396 #[test]
397 fn matches_is_simple_negation_for_simple_graphs() {
398 let mut g = Graph::with_vertices(4);
400 for u in 0..4u32 {
401 for v in (u + 1)..4 {
402 g.add_edge(u, v).unwrap();
403 }
404 }
405 assert!(!has_loop(&g).unwrap());
406 assert!(!has_multiple(&g).unwrap());
407 assert!(crate::is_simple(&g).unwrap());
408 }
409
410 #[test]
411 fn count_loops_empty_graph() {
412 let g = Graph::with_vertices(0);
413 assert_eq!(count_loops(&g).unwrap(), 0);
414 }
415
416 #[test]
417 fn count_loops_no_loops() {
418 let mut g = Graph::with_vertices(4);
419 for (u, v) in [(0, 1), (1, 2), (2, 3)] {
420 g.add_edge(u, v).unwrap();
421 }
422 assert_eq!(count_loops(&g).unwrap(), 0);
423 }
424
425 #[test]
426 fn count_loops_counts_each_self_loop() {
427 let mut g = Graph::with_vertices(3);
428 g.add_edge(0, 1).unwrap();
429 g.add_edge(1, 1).unwrap();
430 g.add_edge(2, 2).unwrap();
431 g.add_edge(2, 2).unwrap();
432 assert_eq!(count_loops(&g).unwrap(), 3);
433 }
434
435 #[test]
436 fn count_loops_directed_self_loops() {
437 let mut g = Graph::new(3, true).unwrap();
438 g.add_edge(0, 0).unwrap();
439 g.add_edge(0, 1).unwrap();
440 g.add_edge(2, 2).unwrap();
441 assert_eq!(count_loops(&g).unwrap(), 2);
442 }
443
444 #[test]
445 fn count_multiple_empty_graph() {
446 let g = Graph::with_vertices(0);
447 assert!(count_multiple(&g).unwrap().is_empty());
448 }
449
450 #[test]
451 fn count_multiple_simple_graph_all_ones() {
452 let mut g = Graph::with_vertices(4);
453 for (u, v) in [(0, 1), (1, 2), (2, 3)] {
454 g.add_edge(u, v).unwrap();
455 }
456 assert_eq!(count_multiple(&g).unwrap(), vec![1, 1, 1]);
457 }
458
459 #[test]
460 fn count_multiple_groups_parallel_undirected() {
461 let mut g = Graph::with_vertices(3);
462 g.add_edge(0, 1).unwrap();
463 g.add_edge(1, 0).unwrap();
464 g.add_edge(1, 2).unwrap();
465 assert_eq!(count_multiple(&g).unwrap(), vec![2, 2, 1]);
467 }
468
469 #[test]
470 fn count_multiple_directed_mutual_pair_distinct() {
471 let mut g = Graph::new(2, true).unwrap();
473 g.add_edge(0, 1).unwrap();
474 g.add_edge(1, 0).unwrap();
475 assert_eq!(count_multiple(&g).unwrap(), vec![1, 1]);
476 }
477
478 #[test]
479 fn count_multiple_three_parallel_copies() {
480 let mut g = Graph::with_vertices(2);
481 for _ in 0..3 {
482 g.add_edge(0, 1).unwrap();
483 }
484 assert_eq!(count_multiple(&g).unwrap(), vec![3, 3, 3]);
485 }
486
487 #[test]
488 fn count_multiple_self_loops_grouped_per_vertex() {
489 let mut g = Graph::with_vertices(3);
492 g.add_edge(0, 0).unwrap();
493 g.add_edge(0, 0).unwrap();
494 g.add_edge(2, 2).unwrap();
495 assert_eq!(count_multiple(&g).unwrap(), vec![2, 2, 1]);
496 }
497
498 #[test]
499 fn count_multiple_mixed_graph() {
500 let mut g = Graph::with_vertices(4);
502 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]);
508 }
509
510 #[test]
511 fn count_multiple_length_matches_ecount() {
512 let mut g = Graph::with_vertices(5);
513 for (u, v) in [(0, 1), (1, 2), (2, 3), (3, 4), (0, 4)] {
514 g.add_edge(u, v).unwrap();
515 }
516 assert_eq!(count_multiple(&g).unwrap().len(), g.ecount());
517 }
518
519 #[test]
520 fn count_multiple_consistent_with_is_multiple_and_has_multiple() {
521 let mut g = Graph::with_vertices(3);
522 g.add_edge(0, 1).unwrap();
523 g.add_edge(0, 1).unwrap();
524 g.add_edge(1, 2).unwrap();
525 let mults = count_multiple(&g).unwrap();
526 let is_mult = is_multiple(&g).unwrap();
527 let has_mult_via_count = mults.iter().any(|&m| m > 1);
530 assert_eq!(has_mult_via_count, has_multiple(&g).unwrap());
531 for (e, &flag) in is_mult.iter().enumerate() {
533 if flag {
534 assert!(mults[e] > 1, "is_multiple but mult = {}", mults[e]);
535 }
536 }
537 }
538
539 #[test]
540 fn count_loops_consistent_with_is_loop() {
541 let mut g = Graph::with_vertices(3);
542 g.add_edge(0, 1).unwrap();
543 g.add_edge(2, 2).unwrap();
544 g.add_edge(1, 2).unwrap();
545 let n_loops = count_loops(&g).unwrap();
546 let is_l = is_loop(&g).unwrap();
547 assert_eq!(n_loops, is_l.iter().filter(|&&b| b).count());
548 }
549}