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 Some(target_undir_x2) = n64.checked_mul(n64 - 1) else {
79        return Ok(false);
80    };
81    let target = if directed {
82        target_undir_x2
83    } else {
84        target_undir_x2 / 2
85    };
86
87    if m64 < target {
88        return Ok(false);
89    }
90
91    // Fast path: a simple graph has no padding, so equality is exact.
92    if is_simple_with_mode(graph, SimpleMode::DirectedAsDirected)? {
93        return Ok(m64 == target);
94    }
95
96    // Slow path: graph has loops or multi-edges. For every vertex v,
97    // count distinct non-self out-neighbours (matches `IGRAPH_OUT`
98    // semantics with NO_LOOPS / NO_MULTIPLE in the C reference). On
99    // an undirected graph `Graph::neighbors` already returns all
100    // neighbours; the directed branch needs the out-direction only.
101    let n_us = n as usize;
102    let mut seen: HashSet<VertexId> = HashSet::with_capacity(n_us);
103    for v in 0..n {
104        seen.clear();
105        if directed {
106            let eids = graph.incident(v)?;
107            for eid in eids {
108                let nei = graph.edge_other(eid, v)?;
109                if nei != v {
110                    seen.insert(nei);
111                }
112            }
113        } else {
114            let neis = graph.neighbors(v)?;
115            for nei in neis {
116                if nei != v {
117                    seen.insert(nei);
118                }
119            }
120        }
121        // n - 1 is required (every other vertex must appear once).
122        if seen.len() + 1 < n_us {
123            return Ok(false);
124        }
125    }
126
127    Ok(true)
128}
129
130#[cfg(test)]
131mod tests {
132    use super::*;
133    use crate::core::Graph;
134
135    #[test]
136    fn null_graph_is_complete() {
137        let g = Graph::with_vertices(0);
138        assert!(is_complete(&g).unwrap());
139    }
140
141    #[test]
142    fn null_directed_is_complete() {
143        let g = Graph::new(0, true).unwrap();
144        assert!(is_complete(&g).unwrap());
145    }
146
147    #[test]
148    fn singleton_undirected_is_complete() {
149        let g = Graph::with_vertices(1);
150        assert!(is_complete(&g).unwrap());
151    }
152
153    #[test]
154    fn singleton_directed_is_complete() {
155        let g = Graph::new(1, true).unwrap();
156        assert!(is_complete(&g).unwrap());
157    }
158
159    #[test]
160    fn singleton_with_self_loop_is_complete() {
161        // n == 1 short-circuits — the loop doesn't matter.
162        let mut g = Graph::with_vertices(1);
163        g.add_edge(0, 0).unwrap();
164        assert!(is_complete(&g).unwrap());
165    }
166
167    #[test]
168    fn k2_undirected_is_complete() {
169        let mut g = Graph::with_vertices(2);
170        g.add_edge(0, 1).unwrap();
171        assert!(is_complete(&g).unwrap());
172    }
173
174    #[test]
175    fn k3_undirected_is_complete() {
176        let mut g = Graph::with_vertices(3);
177        g.add_edges(vec![(0u32, 1u32), (0, 2), (1, 2)]).unwrap();
178        assert!(is_complete(&g).unwrap());
179    }
180
181    #[test]
182    fn k4_undirected_is_complete() {
183        let mut g = Graph::with_vertices(4);
184        g.add_edges(vec![(0u32, 1u32), (0, 2), (0, 3), (1, 2), (1, 3), (2, 3)])
185            .unwrap();
186        assert!(is_complete(&g).unwrap());
187    }
188
189    #[test]
190    fn path_is_not_complete() {
191        // P_3: 0-1-2 — 0 and 2 not adjacent.
192        let mut g = Graph::with_vertices(3);
193        g.add_edges(vec![(0u32, 1u32), (1, 2)]).unwrap();
194        assert!(!is_complete(&g).unwrap());
195    }
196
197    #[test]
198    fn missing_one_edge_is_not_complete() {
199        // K_4 minus the edge (2,3).
200        let mut g = Graph::with_vertices(4);
201        g.add_edges(vec![(0u32, 1u32), (0, 2), (0, 3), (1, 2), (1, 3)])
202            .unwrap();
203        assert!(!is_complete(&g).unwrap());
204    }
205
206    #[test]
207    fn k3_directed_needs_both_directions() {
208        // Three directed cycle 0→1→2→0 has the right edge count
209        // for a complete *undirected* K_3 (3 edges) but only one
210        // direction per pair, so it is NOT a complete directed graph.
211        let mut g = Graph::new(3, true).unwrap();
212        g.add_edges(vec![(0u32, 1u32), (1, 2), (2, 0)]).unwrap();
213        assert!(!is_complete(&g).unwrap());
214    }
215
216    #[test]
217    fn k3_directed_complete() {
218        // n*(n-1) = 6 directed edges for K_3.
219        let mut g = Graph::new(3, true).unwrap();
220        g.add_edges(vec![(0u32, 1u32), (1, 0), (0, 2), (2, 0), (1, 2), (2, 1)])
221            .unwrap();
222        assert!(is_complete(&g).unwrap());
223    }
224
225    #[test]
226    fn k2_directed_not_complete_with_only_one_arc() {
227        let mut g = Graph::new(2, true).unwrap();
228        g.add_edge(0, 1).unwrap();
229        assert!(!is_complete(&g).unwrap());
230    }
231
232    #[test]
233    fn k2_directed_complete_with_both_arcs() {
234        let mut g = Graph::new(2, true).unwrap();
235        g.add_edges(vec![(0u32, 1u32), (1, 0)]).unwrap();
236        assert!(is_complete(&g).unwrap());
237    }
238
239    #[test]
240    fn complete_with_extra_self_loops_still_complete() {
241        // K_3 plus a self-loop at vertex 0: ecount > target but every
242        // vertex still has the other two as unique non-self neighbours.
243        let mut g = Graph::with_vertices(3);
244        g.add_edges(vec![(0u32, 1u32), (0, 2), (1, 2), (0, 0)])
245            .unwrap();
246        assert!(is_complete(&g).unwrap());
247    }
248
249    #[test]
250    fn complete_with_parallel_edges_still_complete() {
251        // K_3 with a duplicate (0,1) edge: ecount > target but unique
252        // neighbour set per vertex still equals the other two.
253        let mut g = Graph::with_vertices(3);
254        g.add_edges(vec![(0u32, 1u32), (0, 2), (1, 2), (0, 1)])
255            .unwrap();
256        assert!(is_complete(&g).unwrap());
257    }
258
259    #[test]
260    fn parallel_edges_padding_does_not_help_missing_pair() {
261        // 4 vertices, edges (0,1) (0,2) (0,3) (1,2) (1,2) (1,2)
262        // ecount = 6 = K_4 target, but {2,3} and {3,1}? Let's check:
263        // vertex 3 only has 0 as neighbour — incomplete.
264        let mut g = Graph::with_vertices(4);
265        g.add_edges(vec![(0u32, 1u32), (0, 2), (0, 3), (1, 2), (1, 2), (1, 2)])
266            .unwrap();
267        assert!(!is_complete(&g).unwrap());
268    }
269
270    #[test]
271    fn ecount_too_low_short_circuit() {
272        // Graph deliberately too sparse — short-circuits on cardinality.
273        let mut g = Graph::with_vertices(10);
274        g.add_edge(0, 1).unwrap();
275        assert!(!is_complete(&g).unwrap());
276    }
277
278    #[test]
279    fn empty_n2_is_not_complete() {
280        // Two isolated vertices — needs 1 undirected edge to be K_2.
281        let g = Graph::with_vertices(2);
282        assert!(!is_complete(&g).unwrap());
283    }
284
285    #[test]
286    fn multi_edge_padding_to_target_but_missing_pair() {
287        // n=3, target_undir = 3 edges. Three parallel (0,1) edges
288        // hit the cardinality bound but vertex 2 is isolated.
289        let mut g = Graph::with_vertices(3);
290        g.add_edges(vec![(0u32, 1u32), (0, 1), (0, 1)]).unwrap();
291        assert!(!is_complete(&g).unwrap());
292    }
293}