Skip to main content

rust_igraph/algorithms/properties/
multiplicity.rs

1//! Companion predicates to [`super::is_simple::is_simple`] (ALGO-PR-014).
2//!
3//! - [`has_loop`]      — does the graph contain at least one self-loop?
4//! - [`has_multiple`]  — does the graph contain at least one parallel
5//!   (multi-) edge?
6//! - [`is_loop`]       — per-edge: which edges are self-loops?
7//! - [`is_multiple`]   — per-edge: which edges are parallel duplicates?
8//! - [`count_loops`]   — number of self-loop edges (PR-014c).
9//! - [`count_multiple`] — per-edge multiplicity, the number of edges
10//!   sharing this edge's endpoint pair (PR-014c).
11//!
12//! Counterparts of `igraph_has_loop()` / `igraph_count_loops()` from
13//! `references/igraph/src/properties/loops.c` and `igraph_has_multiple()` /
14//! `igraph_count_multiple()` from `references/igraph/src/properties/multiplicity.c`.
15//!
16//! Both predicate variants run in O(|V| + |E|) (the upstream cache
17//! subsystem we don't have yet would shortcut to O(1) on subsequent
18//! calls — that's an ALGO-CORE-001f responsibility).
19
20use crate::core::graph::EdgeId;
21use crate::core::{Graph, IgraphResult};
22
23/// Returns `true` iff `graph` has at least one self-loop edge.
24///
25/// # Examples
26///
27/// ```
28/// use rust_igraph::{Graph, has_loop};
29///
30/// let mut g = Graph::with_vertices(2);
31/// g.add_edge(0, 1).unwrap();
32/// assert!(!has_loop(&g).unwrap());
33/// g.add_edge(1, 1).unwrap();
34/// assert!(has_loop(&g).unwrap());
35/// ```
36pub 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
48/// Returns `true` iff `graph` has at least one parallel edge.
49///
50/// For undirected graphs, two self-loops at the same vertex *do* count
51/// as parallel (matching upstream `igraph_has_multiple()`), but a single
52/// self-loop does not. For directed graphs, `(a, b)` and `(b, a)` are
53/// distinct so only same-direction repeats count.
54///
55/// O(|E| log |E|) via sort-and-scan over stored edges. Storage already
56/// canonicalises undirected endpoints to `from <= to`, so `(a,b)` and
57/// `(b,a)` collapse to the same canonical pair, which is exactly the
58/// behaviour we want.
59///
60/// # Examples
61///
62/// ```
63/// use rust_igraph::{Graph, has_multiple};
64///
65/// let mut g = Graph::with_vertices(3);
66/// g.add_edge(0, 1).unwrap();
67/// g.add_edge(1, 2).unwrap();
68/// assert!(!has_multiple(&g).unwrap());
69/// g.add_edge(0, 1).unwrap();
70/// assert!(has_multiple(&g).unwrap());
71/// ```
72pub 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
91/// Returns a per-edge boolean vector marking self-loops.
92///
93/// `result[e] == true` iff `graph.edge(e) == (v, v)` for some `v`.
94/// Counterpart of `igraph_is_loop()` from
95/// `references/igraph/src/properties/loops.c` (with `es =
96/// igraph_ess_all()`).
97///
98/// # Examples
99///
100/// ```
101/// use rust_igraph::{Graph, is_loop};
102///
103/// let mut g = Graph::with_vertices(3);
104/// g.add_edge(0, 1).unwrap();
105/// g.add_edge(2, 2).unwrap();
106/// assert_eq!(is_loop(&g).unwrap(), vec![false, true]);
107/// ```
108pub 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
119/// Returns a per-edge boolean vector marking multiple (parallel) edges.
120///
121/// `result[e] == true` iff there is another edge with the same canonical
122/// endpoint pair *and a smaller edge id*. Per upstream
123/// `igraph_is_multiple()`'s contract (loops.c:230): the result is true
124/// "only for the second or more appearances" — the canonical/first
125/// occurrence stays `false`, parallel copies after it are `true`.
126///
127/// O(|E| log |E|) via sort by canonical pair (within each pair group
128/// we keep edges in their natural id order, so the first id stays
129/// `false`).
130///
131/// # Examples
132///
133/// ```
134/// use rust_igraph::{Graph, is_multiple};
135///
136/// let mut g = Graph::with_vertices(3);
137/// g.add_edge(0, 1).unwrap();
138/// g.add_edge(0, 1).unwrap();
139/// g.add_edge(1, 2).unwrap();
140/// // Edge 0 is the canonical (0,1); edge 1 is the duplicate.
141/// assert_eq!(is_multiple(&g).unwrap(), vec![false, true, false]);
142/// ```
143pub 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    // Pull the original edges, then sort by canonical (from, to) with
151    // edge id as tiebreaker so the first-occurring id stays first.
152    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        // Skip the canonical (first) edge in this group — leave it false.
165        for entry in &pairs[i + 1..j] {
166            out[entry.1 as usize] = true;
167        }
168        i = j;
169    }
170    Ok(out)
171}
172
173/// Counts self-loop edges in `graph`.
174///
175/// A self-loop is an edge `(v, v)`. Returns the total number of such
176/// edges, counting parallel self-loops separately.
177///
178/// Counterpart of `igraph_count_loops()` from
179/// `references/igraph/src/properties/loops.c`.
180///
181/// O(|E|) linear scan.
182///
183/// # Examples
184///
185/// ```
186/// use rust_igraph::{Graph, count_loops};
187///
188/// let mut g = Graph::with_vertices(3);
189/// g.add_edge(0, 1).unwrap();
190/// g.add_edge(1, 1).unwrap();
191/// g.add_edge(2, 2).unwrap();
192/// g.add_edge(2, 2).unwrap();
193/// assert_eq!(count_loops(&g).unwrap(), 3);
194/// ```
195pub 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
208/// Per-edge multiplicity: how many edges share each edge's endpoint pair.
209///
210/// `result[e]` is the number of edges `e'` (including `e` itself) whose
211/// endpoint pair matches `e`'s. Storage already canonicalises undirected
212/// pairs to `(min, max)`, so undirected `(a,b)` and `(b,a)` collapse to
213/// the same group; directed pairs are kept ordered.
214///
215/// Self-loops at the same vertex group together — each such loop has
216/// multiplicity equal to the number of loops at that vertex (matching
217/// upstream's `IGRAPH_LOOPS_ONCE` lazy-adjlist semantics).
218///
219/// Counterpart of `igraph_count_multiple()` from
220/// `references/igraph/src/properties/multiplicity.c`.
221///
222/// O(|E| log |E|) via sort-and-group over stored edges.
223///
224/// # Examples
225///
226/// ```
227/// use rust_igraph::{Graph, count_multiple};
228///
229/// let mut g = Graph::with_vertices(3);
230/// g.add_edge(0, 1).unwrap();
231/// g.add_edge(0, 1).unwrap();
232/// g.add_edge(1, 2).unwrap();
233/// // Edge 0 and edge 1 share (0,1) → multiplicity 2 each.
234/// // Edge 2 is alone → multiplicity 1.
235/// assert_eq!(count_multiple(&g).unwrap(), vec![2, 2, 1]);
236/// ```
237pub 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        // A lone self-loop should NOT count as a multi-edge.
304        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        // Directed (a,b) and (b,a) are distinct → not parallel.
327        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        // Two self-loops on the same vertex: both has_loop and has_multiple
336        // return true (matches upstream igraph_has_multiple).
337        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        // Per upstream's "second-or-more" contract, edge 0 (canonical
362        // (0,1)) stays false; edge 1 (the duplicate) is true; edge 2
363        // (lone (1,2)) is false.
364        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        // Three parallel edges → first one stays canonical, the next
382        // two flip to true.
383        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        // Simple graphs have neither.
399        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        // Undirected (1,0) canonicalises to (0,1); both share group → multiplicity 2.
466        assert_eq!(count_multiple(&g).unwrap(), vec![2, 2, 1]);
467    }
468
469    #[test]
470    fn count_multiple_directed_mutual_pair_distinct() {
471        // Directed (0,1) and (1,0) are distinct pairs → multiplicity 1 each.
472        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        // Two self-loops at v=0 → each has multiplicity 2.
490        // One self-loop at v=2 → multiplicity 1.
491        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        // Mix of simple, parallel, and loop edges.
501        let mut g = Graph::with_vertices(4);
502        g.add_edge(0, 1).unwrap(); // 0
503        g.add_edge(1, 2).unwrap(); // 1
504        g.add_edge(0, 1).unwrap(); // 2 — parallel of 0
505        g.add_edge(3, 3).unwrap(); // 3 — lone loop
506        g.add_edge(2, 3).unwrap(); // 4
507        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        // is_multiple is "second-or-more occurrence"; multiplicity > 1
528        // exactly when at least one of the group's edges is is_multiple.
529        let has_mult_via_count = mults.iter().any(|&m| m > 1);
530        assert_eq!(has_mult_via_count, has_multiple(&g).unwrap());
531        // For each edge e: is_multiple[e] implies count_multiple[e] > 1.
532        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}