Skip to main content

rust_igraph/algorithms/properties/
is_clique.rs

1//! Clique and independent set tests (ALGO-PR-040).
2//!
3//! Tests whether a given set of vertices forms a clique (all pairs
4//! adjacent) or an independent set (no pairs adjacent).
5//!
6//! Counterpart of `igraph_is_clique` and `igraph_is_independent_vertex_set`
7//! from `references/igraph/src/properties/complete.c`.
8
9use crate::core::{Graph, IgraphResult, VertexId};
10
11/// Check whether a set of vertices forms a clique.
12///
13/// A clique is a set of vertices in which every pair is adjacent.
14/// An empty set and a singleton set are always considered cliques.
15///
16/// For directed graphs, if `directed` is true, requires edges in both
17/// directions (u→v and v→u) for every pair. If false, an edge in
18/// either direction suffices.
19///
20/// # Examples
21///
22/// ```
23/// use rust_igraph::{Graph, is_clique};
24///
25/// let mut g = Graph::with_vertices(4);
26/// g.add_edge(0, 1).unwrap();
27/// g.add_edge(0, 2).unwrap();
28/// g.add_edge(1, 2).unwrap();
29/// g.add_edge(2, 3).unwrap();
30/// assert!(is_clique(&g, &[0, 1, 2], false).unwrap());
31/// assert!(!is_clique(&g, &[0, 1, 2, 3], false).unwrap());
32/// ```
33pub fn is_clique(graph: &Graph, vertices: &[VertexId], directed: bool) -> IgraphResult<bool> {
34    let directed_eff = directed && graph.is_directed();
35    check_subset(graph, vertices, directed_eff, false)
36}
37
38/// Check whether a set of vertices forms an independent set.
39///
40/// An independent set is a set of vertices in which no pair is adjacent.
41/// An empty set and a singleton set are always considered independent sets.
42///
43/// Edge directions are ignored — any edge between two vertices in the
44/// set disqualifies it.
45///
46/// # Examples
47///
48/// ```
49/// use rust_igraph::{Graph, is_independent_vertex_set};
50///
51/// let mut g = Graph::with_vertices(4);
52/// g.add_edge(0, 1).unwrap();
53/// g.add_edge(2, 3).unwrap();
54/// assert!(is_independent_vertex_set(&g, &[0, 2]).unwrap());
55/// assert!(!is_independent_vertex_set(&g, &[0, 1]).unwrap());
56/// ```
57pub fn is_independent_vertex_set(graph: &Graph, vertices: &[VertexId]) -> IgraphResult<bool> {
58    check_subset(graph, vertices, false, true)
59}
60
61fn check_subset(
62    graph: &Graph,
63    vertices: &[VertexId],
64    directed: bool,
65    independent_set: bool,
66) -> IgraphResult<bool> {
67    let n = vertices.len();
68    if n <= 1 {
69        return Ok(true);
70    }
71
72    let vcount = graph.vcount();
73    for &v in vertices {
74        if v >= vcount {
75            return Err(crate::core::IgraphError::InvalidArgument(format!(
76                "vertex {v} out of range (vcount = {vcount})"
77            )));
78        }
79    }
80
81    let ecount = graph.ecount();
82    let mut out_adj: Vec<Vec<VertexId>> = vec![Vec::new(); vcount as usize];
83    let mut in_adj: Vec<Vec<VertexId>> = vec![Vec::new(); vcount as usize];
84
85    for eid in 0..ecount {
86        #[allow(clippy::cast_possible_truncation)]
87        let (from, to) = graph.edge(eid as u32)?;
88        out_adj[from as usize].push(to);
89        if graph.is_directed() {
90            in_adj[to as usize].push(from);
91        }
92    }
93
94    for adj in &mut out_adj {
95        adj.sort_unstable();
96    }
97    if graph.is_directed() {
98        for adj in &mut in_adj {
99            adj.sort_unstable();
100        }
101    }
102
103    for (i, &u) in vertices.iter().enumerate() {
104        let j_start = if directed { 0 } else { i + 1 };
105        for &v in &vertices[j_start..] {
106            if u == v {
107                continue;
108            }
109
110            let has_edge = if directed {
111                out_adj[u as usize].binary_search(&v).is_ok()
112            } else {
113                out_adj[u as usize].binary_search(&v).is_ok()
114                    || out_adj[v as usize].binary_search(&u).is_ok()
115                    || (graph.is_directed()
116                        && (in_adj[u as usize].binary_search(&v).is_ok()
117                            || in_adj[v as usize].binary_search(&u).is_ok()))
118            };
119
120            if independent_set {
121                if has_edge {
122                    return Ok(false);
123                }
124            } else if !has_edge {
125                return Ok(false);
126            }
127        }
128    }
129
130    Ok(true)
131}
132
133#[cfg(test)]
134mod tests {
135    use super::*;
136
137    #[test]
138    fn clique_empty_set() {
139        let g = Graph::with_vertices(5);
140        assert!(is_clique(&g, &[], false).unwrap());
141    }
142
143    #[test]
144    fn clique_singleton() {
145        let g = Graph::with_vertices(5);
146        assert!(is_clique(&g, &[2], false).unwrap());
147    }
148
149    #[test]
150    fn clique_pair_connected() {
151        let mut g = Graph::with_vertices(3);
152        g.add_edge(0, 1).unwrap();
153        assert!(is_clique(&g, &[0, 1], false).unwrap());
154    }
155
156    #[test]
157    fn clique_pair_not_connected() {
158        let mut g = Graph::with_vertices(3);
159        g.add_edge(0, 1).unwrap();
160        assert!(!is_clique(&g, &[0, 2], false).unwrap());
161    }
162
163    #[test]
164    fn clique_triangle() {
165        let mut g = Graph::with_vertices(4);
166        g.add_edge(0, 1).unwrap();
167        g.add_edge(0, 2).unwrap();
168        g.add_edge(1, 2).unwrap();
169        g.add_edge(2, 3).unwrap();
170        assert!(is_clique(&g, &[0, 1, 2], false).unwrap());
171        assert!(!is_clique(&g, &[0, 1, 2, 3], false).unwrap());
172    }
173
174    #[test]
175    fn clique_directed_both_directions() {
176        let mut g = Graph::new(3, true).unwrap();
177        g.add_edge(0, 1).unwrap();
178        g.add_edge(1, 0).unwrap();
179        g.add_edge(0, 2).unwrap();
180        g.add_edge(1, 2).unwrap();
181        // directed=true: need both 0→2 and 2→0, but 2→0 missing
182        assert!(!is_clique(&g, &[0, 1, 2], true).unwrap());
183        // directed=false: any direction suffices (0→2 and 1→2 exist)
184        assert!(is_clique(&g, &[0, 1, 2], false).unwrap());
185    }
186
187    #[test]
188    fn clique_directed_full() {
189        let mut g = Graph::new(3, true).unwrap();
190        g.add_edge(0, 1).unwrap();
191        g.add_edge(1, 0).unwrap();
192        g.add_edge(0, 2).unwrap();
193        g.add_edge(2, 0).unwrap();
194        g.add_edge(1, 2).unwrap();
195        g.add_edge(2, 1).unwrap();
196        assert!(is_clique(&g, &[0, 1, 2], true).unwrap());
197    }
198
199    #[test]
200    fn clique_with_self_loop() {
201        let mut g = Graph::with_vertices(3);
202        g.add_edge(0, 0).unwrap();
203        g.add_edge(0, 1).unwrap();
204        g.add_edge(1, 2).unwrap();
205        g.add_edge(0, 2).unwrap();
206        assert!(is_clique(&g, &[0, 1, 2], false).unwrap());
207    }
208
209    #[test]
210    fn clique_vertex_out_of_range() {
211        let g = Graph::with_vertices(3);
212        assert!(is_clique(&g, &[0, 5], false).is_err());
213    }
214
215    #[test]
216    fn independent_empty_set() {
217        let g = Graph::with_vertices(5);
218        assert!(is_independent_vertex_set(&g, &[]).unwrap());
219    }
220
221    #[test]
222    fn independent_singleton() {
223        let g = Graph::with_vertices(5);
224        assert!(is_independent_vertex_set(&g, &[3]).unwrap());
225    }
226
227    #[test]
228    fn independent_no_edges_between() {
229        let mut g = Graph::with_vertices(4);
230        g.add_edge(0, 1).unwrap();
231        g.add_edge(2, 3).unwrap();
232        assert!(is_independent_vertex_set(&g, &[0, 2]).unwrap());
233        assert!(is_independent_vertex_set(&g, &[1, 3]).unwrap());
234        assert!(is_independent_vertex_set(&g, &[0, 3]).unwrap());
235    }
236
237    #[test]
238    fn independent_has_edge() {
239        let mut g = Graph::with_vertices(4);
240        g.add_edge(0, 1).unwrap();
241        g.add_edge(2, 3).unwrap();
242        assert!(!is_independent_vertex_set(&g, &[0, 1]).unwrap());
243        assert!(!is_independent_vertex_set(&g, &[0, 1, 2]).unwrap());
244    }
245
246    #[test]
247    fn independent_directed_ignores_direction() {
248        let mut g = Graph::new(3, true).unwrap();
249        g.add_edge(0, 1).unwrap(); // only 0→1, not 1→0
250        assert!(!is_independent_vertex_set(&g, &[0, 1]).unwrap());
251    }
252
253    #[test]
254    fn independent_all_isolated() {
255        let g = Graph::with_vertices(5);
256        assert!(is_independent_vertex_set(&g, &[0, 1, 2, 3, 4]).unwrap());
257    }
258}