Skip to main content

rust_igraph/algorithms/properties/
is_complete.rs

1//! `is_complete` predicate (ALGO-PR-016).
2//!
3//! Counterpart of `igraph_is_complete()` from
4//! `references/igraph/src/properties/complete.c:43`. A graph is *complete*
5//! if all pairs of distinct vertices are adjacent. The null graph and the
6//! singleton graph are considered complete by convention (matches upstream).
7//!
8//! Algorithm: cardinality short-circuit then either a simple-graph
9//! fast path or a per-vertex unique-neighbour scan.
10//!
11//! 1. `n <= 1` → `true`.
12//! 2. Compute the target edge count for a complete graph on `n`
13//!    vertices: `n*(n-1)` (directed) or `n*(n-1)/2` (undirected).
14//!    Fall back to `false` immediately if the multiplication would
15//!    overflow `u64`.
16//! 3. If `ecount < target` → `false`.
17//! 4. If the graph is simple (no loops, no parallel edges) → return
18//!    `ecount == target` (must hold with equality once the lower
19//!    bound is met).
20//! 5. Otherwise the graph has loops / multi-edges that pad `ecount`;
21//!    walk every vertex and require its set of unique non-self
22//!    neighbours to have size at least `n - 1`.
23//!
24//! Time complexity: `O(V + E)` worst case (the unique-neighbour scan
25//! visits every incidence at most once).
26
27use std::collections::HashSet;
28
29use crate::core::Graph;
30use crate::core::IgraphResult;
31use crate::core::graph::VertexId;
32
33use super::is_simple::{SimpleMode, is_simple_with_mode};
34
35/// Returns `true` iff every pair of distinct vertices is adjacent.
36///
37/// The null graph (`n == 0`) and the singleton graph (`n == 1`) are
38/// considered complete (matches `igraph_is_complete`).
39///
40/// On directed graphs both directions must be present for every pair
41/// `(u, v)` with `u != v` — this matches upstream's
42/// `igraph_neighbors(_, _, _, IGRAPH_OUT, NO_LOOPS, NO_MULTIPLE)`
43/// counting and `igraph_is_simple(_, _, IGRAPH_DIRECTED)` short-circuit.
44///
45/// Counterpart of `igraph_is_complete` from
46/// `references/igraph/src/properties/complete.c:43`.
47///
48/// # Examples
49///
50/// ```
51/// use rust_igraph::{Graph, is_complete};
52///
53/// // Null graph — vacuously complete.
54/// let g = Graph::with_vertices(0);
55/// assert!(is_complete(&g).unwrap());
56///
57/// // K_3 — every pair adjacent.
58/// let mut g = Graph::with_vertices(3);
59/// g.add_edges(vec![(0u32, 1u32), (0, 2), (1, 2)]).unwrap();
60/// assert!(is_complete(&g).unwrap());
61///
62/// // Path P_3 (0-1-2) — 0 and 2 not adjacent.
63/// let mut g = Graph::with_vertices(3);
64/// g.add_edges(vec![(0u32, 1u32), (1, 2)]).unwrap();
65/// assert!(!is_complete(&g).unwrap());
66/// ```
67pub fn is_complete(graph: &Graph) -> IgraphResult<bool> {
68    let n = graph.vcount();
69    if n <= 1 {
70        return Ok(true);
71    }
72
73    let n64 = u64::from(n);
74    let m64 = graph.ecount() as u64;
75    let directed = graph.is_directed();
76
77    // n*(n-1) cannot overflow u64 since n is u32 (so ≤ 2^32).
78    let target_undir_x2 = n64.checked_mul(n64 - 1).expect("u64 mul fits");
79    let target = if directed {
80        target_undir_x2
81    } else {
82        target_undir_x2 / 2
83    };
84
85    if m64 < target {
86        return Ok(false);
87    }
88
89    // Fast path: a simple graph has no padding, so equality is exact.
90    if is_simple_with_mode(graph, SimpleMode::DirectedAsDirected)? {
91        return Ok(m64 == target);
92    }
93
94    // Slow path: graph has loops or multi-edges. For every vertex v,
95    // count distinct non-self out-neighbours (matches `IGRAPH_OUT`
96    // semantics with NO_LOOPS / NO_MULTIPLE in the C reference). On
97    // an undirected graph `Graph::neighbors` already returns all
98    // neighbours; the directed branch needs the out-direction only.
99    let n_us = n as usize;
100    let mut seen: HashSet<VertexId> = HashSet::with_capacity(n_us);
101    for v in 0..n {
102        seen.clear();
103        if directed {
104            let eids = graph.incident(v).expect("incident on valid vertex");
105            for eid in eids {
106                let nei = graph.edge_other(eid, v).expect("edge_other on valid edge");
107                if nei != v {
108                    seen.insert(nei);
109                }
110            }
111        } else {
112            let neis = graph.neighbors(v).expect("neighbors on valid vertex");
113            for nei in neis {
114                if nei != v {
115                    seen.insert(nei);
116                }
117            }
118        }
119        // n - 1 is required (every other vertex must appear once).
120        if seen.len() + 1 < n_us {
121            return Ok(false);
122        }
123    }
124
125    Ok(true)
126}
127
128#[cfg(test)]
129mod tests {
130    use super::*;
131    use crate::core::Graph;
132
133    #[test]
134    fn null_graph_is_complete() {
135        let g = Graph::with_vertices(0);
136        assert!(is_complete(&g).unwrap());
137    }
138
139    #[test]
140    fn null_directed_is_complete() {
141        let g = Graph::new(0, true).unwrap();
142        assert!(is_complete(&g).unwrap());
143    }
144
145    #[test]
146    fn singleton_undirected_is_complete() {
147        let g = Graph::with_vertices(1);
148        assert!(is_complete(&g).unwrap());
149    }
150
151    #[test]
152    fn singleton_directed_is_complete() {
153        let g = Graph::new(1, true).unwrap();
154        assert!(is_complete(&g).unwrap());
155    }
156
157    #[test]
158    fn singleton_with_self_loop_is_complete() {
159        // n == 1 short-circuits — the loop doesn't matter.
160        let mut g = Graph::with_vertices(1);
161        g.add_edge(0, 0).unwrap();
162        assert!(is_complete(&g).unwrap());
163    }
164
165    #[test]
166    fn k2_undirected_is_complete() {
167        let mut g = Graph::with_vertices(2);
168        g.add_edge(0, 1).unwrap();
169        assert!(is_complete(&g).unwrap());
170    }
171
172    #[test]
173    fn k3_undirected_is_complete() {
174        let mut g = Graph::with_vertices(3);
175        g.add_edges(vec![(0u32, 1u32), (0, 2), (1, 2)]).unwrap();
176        assert!(is_complete(&g).unwrap());
177    }
178
179    #[test]
180    fn k4_undirected_is_complete() {
181        let mut g = Graph::with_vertices(4);
182        g.add_edges(vec![(0u32, 1u32), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)])
183            .unwrap();
184        assert!(is_complete(&g).unwrap());
185    }
186
187    #[test]
188    fn path_is_not_complete() {
189        // P_3: 0-1-2 — 0 and 2 not adjacent.
190        let mut g = Graph::with_vertices(3);
191        g.add_edges(vec![(0u32, 1u32), (1, 2)]).unwrap();
192        assert!(!is_complete(&g).unwrap());
193    }
194
195    #[test]
196    fn missing_one_edge_is_not_complete() {
197        // K_4 minus the edge (2,3).
198        let mut g = Graph::with_vertices(4);
199        g.add_edges(vec![(0u32, 1u32), (0, 2), (0, 3), (1, 2), (1, 3)])
200            .unwrap();
201        assert!(!is_complete(&g).unwrap());
202    }
203
204    #[test]
205    fn k3_directed_needs_both_directions() {
206        // Three directed cycle 0→1→2→0 has the right edge count
207        // for a complete *undirected* K_3 (3 edges) but only one
208        // direction per pair, so it is NOT a complete directed graph.
209        let mut g = Graph::new(3, true).unwrap();
210        g.add_edges(vec![(0u32, 1u32), (1, 2), (2, 0)]).unwrap();
211        assert!(!is_complete(&g).unwrap());
212    }
213
214    #[test]
215    fn k3_directed_complete() {
216        // n*(n-1) = 6 directed edges for K_3.
217        let mut g = Graph::new(3, true).unwrap();
218        g.add_edges(vec![(0u32, 1u32), (1, 0), (0, 2), (2, 0), (1, 2), (2, 1)])
219            .unwrap();
220        assert!(is_complete(&g).unwrap());
221    }
222
223    #[test]
224    fn k2_directed_not_complete_with_only_one_arc() {
225        let mut g = Graph::new(2, true).unwrap();
226        g.add_edge(0, 1).unwrap();
227        assert!(!is_complete(&g).unwrap());
228    }
229
230    #[test]
231    fn k2_directed_complete_with_both_arcs() {
232        let mut g = Graph::new(2, true).unwrap();
233        g.add_edges(vec![(0u32, 1u32), (1, 0)]).unwrap();
234        assert!(is_complete(&g).unwrap());
235    }
236
237    #[test]
238    fn complete_with_extra_self_loops_still_complete() {
239        // K_3 plus a self-loop at vertex 0: ecount > target but every
240        // vertex still has the other two as unique non-self neighbours.
241        let mut g = Graph::with_vertices(3);
242        g.add_edges(vec![(0u32, 1u32), (0, 2), (1, 2), (0, 0)])
243            .unwrap();
244        assert!(is_complete(&g).unwrap());
245    }
246
247    #[test]
248    fn complete_with_parallel_edges_still_complete() {
249        // K_3 with a duplicate (0,1) edge: ecount > target but unique
250        // neighbour set per vertex still equals the other two.
251        let mut g = Graph::with_vertices(3);
252        g.add_edges(vec![(0u32, 1u32), (0, 2), (1, 2), (0, 1)])
253            .unwrap();
254        assert!(is_complete(&g).unwrap());
255    }
256
257    #[test]
258    fn parallel_edges_padding_does_not_help_missing_pair() {
259        // 4 vertices, edges (0,1) (0,2) (0,3) (1,2) (1,2) (1,2)
260        // ecount = 6 = K_4 target, but {2,3} and {3,1}? Let's check:
261        // vertex 3 only has 0 as neighbour — incomplete.
262        let mut g = Graph::with_vertices(4);
263        g.add_edges(vec![(0u32, 1u32), (0, 2), (0, 3), (1, 2), (1, 2), (1, 2)])
264            .unwrap();
265        assert!(!is_complete(&g).unwrap());
266    }
267
268    #[test]
269    fn ecount_too_low_short_circuit() {
270        // Graph deliberately too sparse — short-circuits on cardinality.
271        let mut g = Graph::with_vertices(10);
272        g.add_edge(0, 1).unwrap();
273        assert!(!is_complete(&g).unwrap());
274    }
275
276    #[test]
277    fn empty_n2_is_not_complete() {
278        // Two isolated vertices — needs 1 undirected edge to be K_2.
279        let g = Graph::with_vertices(2);
280        assert!(!is_complete(&g).unwrap());
281    }
282
283    #[test]
284    fn multi_edge_padding_to_target_but_missing_pair() {
285        // n=3, target_undir = 3 edges. Three parallel (0,1) edges
286        // hit the cardinality bound but vertex 2 is isolated.
287        let mut g = Graph::with_vertices(3);
288        g.add_edges(vec![(0u32, 1u32), (0, 1), (0, 1)]).unwrap();
289        assert!(!is_complete(&g).unwrap());
290    }
291}