Skip to main content

rust_igraph/algorithms/operators/
complementer.rs

1//! Complementer (ALGO-OP-003).
2//!
3//! Counterpart of `igraph_complementer()` from
4//! `references/igraph/src/operators/complementer.c`. The complementer
5//! graph contains every edge `(u, v)` that the input graph does NOT
6//! contain. For undirected graphs, only unordered pairs `u < v` are
7//! considered; for directed graphs, ordered pairs `(u, v)` are
8//! considered for all `u, v`. The `loops` flag toggles whether self-
9//! loops `(v, v)` may appear in the output.
10//!
11//! Phase-1 minimal slice: no attribute copy. Vertex / edge attributes
12//! are dropped (ALGO-AT-*).
13
14use std::collections::BTreeSet;
15
16use crate::core::graph::EdgeId;
17use crate::core::{Graph, IgraphResult, VertexId};
18
19/// Returns the complementer of `graph`.
20///
21/// If `loops == true`, self-loops `(v, v)` are added for every vertex
22/// that does not already have one. If `loops == false`, no self-loops
23/// appear in the output regardless of the input.
24///
25/// The output's vertex count equals the input's; the output's edge
26/// count is `n*(n-1) − ecount` (directed) or `n*(n-1)/2 − ecount`
27/// (undirected) when `loops == false`, plus `n` extra slots if
28/// `loops == true` for self-loops not in the input.
29///
30/// # Examples
31///
32/// ```
33/// use rust_igraph::{Graph, complementer};
34///
35/// // Path 0-1-2 → complementer is the single edge (0, 2).
36/// let mut g = Graph::with_vertices(3);
37/// g.add_edge(0, 1).unwrap();
38/// g.add_edge(1, 2).unwrap();
39/// let c = complementer(&g, false).unwrap();
40/// assert_eq!(c.ecount(), 1);
41/// assert_eq!(c.edge(0).unwrap(), (0, 2));
42/// ```
43pub fn complementer(graph: &Graph, loops: bool) -> IgraphResult<Graph> {
44    let n = graph.vcount();
45    let directed = graph.is_directed();
46    let mut g = Graph::new(n, directed)?;
47    if n == 0 {
48        return Ok(g);
49    }
50
51    // Snapshot original edges as a sorted-set of canonical pairs.
52    // Storage already canonicalises undirected (from <= to), so a single
53    // sort is enough. We also count parallel originals as a single
54    // existing edge — complementer ignores multiplicity.
55    let m = u32::try_from(graph.ecount())
56        .map_err(|_| crate::IgraphError::Internal("ecount exceeds u32::MAX"))?;
57    let mut existing: BTreeSet<(VertexId, VertexId)> = BTreeSet::new();
58    for e in 0..m {
59        existing.insert(graph.edge(e as EdgeId)?);
60    }
61
62    let mut new_edges: Vec<(VertexId, VertexId)> = Vec::new();
63    if directed {
64        for u in 0..n {
65            for v in 0..n {
66                if u == v && !loops {
67                    continue;
68                }
69                if !existing.contains(&(u, v)) {
70                    new_edges.push((u, v));
71                }
72            }
73        }
74    } else {
75        // Undirected: only unordered pairs (u, v) with u <= v. Self-loop
76        // appears only when loops == true.
77        for u in 0..n {
78            let lo = if loops { u } else { u + 1 };
79            for v in lo..n {
80                if !existing.contains(&(u, v)) {
81                    new_edges.push((u, v));
82                }
83            }
84        }
85    }
86
87    g.add_edges(new_edges)?;
88    Ok(g)
89}
90
91#[cfg(test)]
92mod tests {
93    use super::*;
94
95    fn sorted_edges(g: &Graph) -> Vec<(VertexId, VertexId)> {
96        let m = u32::try_from(g.ecount()).unwrap();
97        let mut v: Vec<_> = (0..m).map(|e| g.edge(e).unwrap()).collect();
98        v.sort_unstable();
99        v
100    }
101
102    #[test]
103    fn empty_graph_yields_empty() {
104        let g = Graph::with_vertices(0);
105        let c = complementer(&g, false).unwrap();
106        assert_eq!(c.vcount(), 0);
107        assert_eq!(c.ecount(), 0);
108    }
109
110    #[test]
111    fn no_edges_no_loops_is_complete_graph() {
112        // 3 isolated vertices → complementer (loops=false) = K3.
113        let g = Graph::with_vertices(3);
114        let c = complementer(&g, false).unwrap();
115        assert_eq!(sorted_edges(&c), vec![(0, 1), (0, 2), (1, 2)]);
116    }
117
118    #[test]
119    fn no_edges_with_loops_adds_self_loops() {
120        let g = Graph::with_vertices(2);
121        let c = complementer(&g, true).unwrap();
122        // K2 with both self-loops = (0,0)(0,1)(1,1).
123        assert_eq!(sorted_edges(&c), vec![(0, 0), (0, 1), (1, 1)]);
124    }
125
126    #[test]
127    fn complete_graph_complementer_is_empty() {
128        // K3 → complementer (no loops) is empty.
129        let mut g = Graph::with_vertices(3);
130        g.add_edge(0, 1).unwrap();
131        g.add_edge(0, 2).unwrap();
132        g.add_edge(1, 2).unwrap();
133        let c = complementer(&g, false).unwrap();
134        assert_eq!(c.ecount(), 0);
135    }
136
137    #[test]
138    fn path_complementer_is_single_chord() {
139        let mut g = Graph::with_vertices(3);
140        g.add_edge(0, 1).unwrap();
141        g.add_edge(1, 2).unwrap();
142        let c = complementer(&g, false).unwrap();
143        assert_eq!(c.ecount(), 1);
144        assert_eq!(c.edge(0).unwrap(), (0, 2));
145    }
146
147    #[test]
148    fn directed_complementer_includes_reverses() {
149        // Directed (0,1): complementer (no loops) should add
150        // (1,0), (0,2), (2,0), (1,2), (2,1) on 3 vertices.
151        let mut g = Graph::new(3, true).unwrap();
152        g.add_edge(0, 1).unwrap();
153        let c = complementer(&g, false).unwrap();
154        assert!(c.is_directed());
155        assert_eq!(
156            sorted_edges(&c),
157            vec![(0, 2), (1, 0), (1, 2), (2, 0), (2, 1)]
158        );
159    }
160
161    #[test]
162    fn directed_with_loops_adds_self_loops() {
163        let mut g = Graph::new(2, true).unwrap();
164        g.add_edge(0, 1).unwrap();
165        let c = complementer(&g, true).unwrap();
166        assert_eq!(sorted_edges(&c), vec![(0, 0), (1, 0), (1, 1)]);
167    }
168
169    #[test]
170    fn double_complement_recovers_original_for_simple_undirected() {
171        // For a simple undirected graph, complement(complement(g)) == g.
172        let mut g = Graph::with_vertices(4);
173        g.add_edge(0, 1).unwrap();
174        g.add_edge(1, 2).unwrap();
175        g.add_edge(2, 3).unwrap();
176        let c = complementer(&g, false).unwrap();
177        let cc = complementer(&c, false).unwrap();
178        assert_eq!(sorted_edges(&cc), sorted_edges(&g));
179    }
180
181    #[test]
182    fn singleton_with_loops() {
183        let g = Graph::with_vertices(1);
184        let c = complementer(&g, true).unwrap();
185        assert_eq!(sorted_edges(&c), vec![(0, 0)]);
186    }
187
188    #[test]
189    fn singleton_without_loops() {
190        let g = Graph::with_vertices(1);
191        let c = complementer(&g, false).unwrap();
192        assert_eq!(c.ecount(), 0);
193    }
194
195    #[test]
196    fn parallel_edges_in_input_collapse() {
197        // Multi-edges in input shouldn't change the complementer beyond
198        // suppressing the (0,1) pair once.
199        let mut g = Graph::with_vertices(3);
200        g.add_edge(0, 1).unwrap();
201        g.add_edge(0, 1).unwrap();
202        let c = complementer(&g, false).unwrap();
203        // Surviving: (0,2), (1,2).
204        assert_eq!(sorted_edges(&c), vec![(0, 2), (1, 2)]);
205    }
206}