Skip to main content

rust_igraph/algorithms/properties/
is_cograph.rs

1//! Cograph predicate (ALGO-PR-080).
2//!
3//! A cograph (complement-reducible graph) is a graph with no induced
4//! P4 (path on 4 vertices).  Equivalently, a graph G is a cograph
5//! iff:
6//! - G has a single vertex, or
7//! - G is disconnected and each component is a cograph, or
8//! - the complement of G is disconnected and each component of
9//!   the complement is a cograph.
10//!
11//! This module uses the recursive characterization with induced
12//! subgraph extraction and complementation.
13//!
14//! For directed graphs, the function returns `false`.
15
16use crate::algorithms::connectivity::components::connected_components;
17use crate::algorithms::operators::complementer::complementer;
18use crate::core::{Graph, IgraphResult};
19
20/// Check whether a graph is a cograph (P4-free).
21///
22/// A cograph contains no induced path on 4 vertices. Cographs are
23/// also known as complement-reducible graphs.
24///
25/// Uses the recursive characterization: a graph is a cograph iff
26/// either it or its complement is disconnected, and every component
27/// (of whichever is disconnected) is a cograph.
28///
29/// Returns `false` for directed graphs.
30///
31/// An empty graph (0 vertices) is a cograph (vacuously).
32/// A single vertex is a cograph.
33/// Any complete graph is a cograph.
34/// Any edgeless graph is a cograph.
35///
36/// # Examples
37///
38/// ```
39/// use rust_igraph::{Graph, is_cograph};
40///
41/// // K3: cograph
42/// let mut g = Graph::with_vertices(3);
43/// g.add_edge(0, 1).unwrap();
44/// g.add_edge(1, 2).unwrap();
45/// g.add_edge(2, 0).unwrap();
46/// assert!(is_cograph(&g).unwrap());
47///
48/// // P4 (path on 4 vertices) is NOT a cograph
49/// let mut g = Graph::with_vertices(4);
50/// g.add_edge(0, 1).unwrap();
51/// g.add_edge(1, 2).unwrap();
52/// g.add_edge(2, 3).unwrap();
53/// assert!(!is_cograph(&g).unwrap());
54/// ```
55pub fn is_cograph(graph: &Graph) -> IgraphResult<bool> {
56    if graph.is_directed() {
57        return Ok(false);
58    }
59
60    let n = graph.vcount();
61    if n <= 3 {
62        return Ok(true);
63    }
64
65    is_cograph_recursive(graph)
66}
67
68fn is_cograph_recursive(graph: &Graph) -> IgraphResult<bool> {
69    let n = graph.vcount();
70    if n <= 3 {
71        return Ok(true);
72    }
73
74    let cc = connected_components(graph)?;
75
76    if cc.count > 1 {
77        // G is disconnected — each component must be a cograph
78        return check_components_are_cographs(graph, &cc);
79    }
80
81    // G is connected — check if complement is disconnected
82    let comp = complementer(graph, false)?;
83    let cc_comp = connected_components(&comp)?;
84
85    if cc_comp.count <= 1 {
86        // Both G and complement(G) are connected → not a cograph
87        return Ok(false);
88    }
89
90    // Complement is disconnected — each component of complement must be a cograph
91    check_components_are_cographs(&comp, &cc_comp)
92}
93
94fn check_components_are_cographs(
95    graph: &Graph,
96    cc: &crate::algorithms::connectivity::components::ConnectedComponents,
97) -> IgraphResult<bool> {
98    for comp_id in 0..cc.count {
99        let comp_verts: Vec<u32> = (0..graph.vcount())
100            .filter(|&v| cc.membership[v as usize] == comp_id)
101            .collect();
102
103        if comp_verts.len() <= 3 {
104            continue;
105        }
106
107        let subgraph = extract_induced_subgraph(graph, &comp_verts)?;
108        if !is_cograph_recursive(&subgraph)? {
109            return Ok(false);
110        }
111    }
112
113    Ok(true)
114}
115
116fn extract_induced_subgraph(graph: &Graph, vertices: &[u32]) -> IgraphResult<Graph> {
117    let n = vertices.len();
118    let mut sub = Graph::with_vertices(u32::try_from(n).unwrap_or(u32::MAX));
119
120    // Map old vertex IDs to new IDs
121    let mut old_to_new = vec![u32::MAX; graph.vcount() as usize];
122    for (new_id, &old_id) in vertices.iter().enumerate() {
123        old_to_new[old_id as usize] = u32::try_from(new_id).unwrap_or(u32::MAX);
124    }
125
126    // Add edges between vertices in the subset
127    for &v in vertices {
128        let nbrs = graph.neighbors(v)?;
129        for w in nbrs {
130            if w > v && old_to_new[w as usize] != u32::MAX {
131                sub.add_edge(old_to_new[v as usize], old_to_new[w as usize])?;
132            }
133        }
134    }
135
136    Ok(sub)
137}
138
139#[cfg(test)]
140mod tests {
141    use super::*;
142
143    #[test]
144    fn empty_graph() {
145        let g = Graph::with_vertices(0);
146        assert!(is_cograph(&g).unwrap());
147    }
148
149    #[test]
150    fn single_vertex() {
151        let g = Graph::with_vertices(1);
152        assert!(is_cograph(&g).unwrap());
153    }
154
155    #[test]
156    fn single_edge() {
157        let mut g = Graph::with_vertices(2);
158        g.add_edge(0, 1).unwrap();
159        assert!(is_cograph(&g).unwrap());
160    }
161
162    #[test]
163    fn triangle_is_cograph() {
164        let mut g = Graph::with_vertices(3);
165        g.add_edge(0, 1).unwrap();
166        g.add_edge(1, 2).unwrap();
167        g.add_edge(2, 0).unwrap();
168        assert!(is_cograph(&g).unwrap());
169    }
170
171    #[test]
172    fn k4_is_cograph() {
173        let mut g = Graph::with_vertices(4);
174        for u in 0..4u32 {
175            for v in (u + 1)..4 {
176                g.add_edge(u, v).unwrap();
177            }
178        }
179        assert!(is_cograph(&g).unwrap());
180    }
181
182    #[test]
183    fn k5_is_cograph() {
184        let mut g = Graph::with_vertices(5);
185        for u in 0..5u32 {
186            for v in (u + 1)..5 {
187                g.add_edge(u, v).unwrap();
188            }
189        }
190        assert!(is_cograph(&g).unwrap());
191    }
192
193    #[test]
194    fn edgeless_is_cograph() {
195        let g = Graph::with_vertices(5);
196        assert!(is_cograph(&g).unwrap());
197    }
198
199    #[test]
200    fn p4_not_cograph() {
201        // P4: 0-1-2-3 (the defining forbidden subgraph)
202        let mut g = Graph::with_vertices(4);
203        g.add_edge(0, 1).unwrap();
204        g.add_edge(1, 2).unwrap();
205        g.add_edge(2, 3).unwrap();
206        assert!(!is_cograph(&g).unwrap());
207    }
208
209    #[test]
210    fn c4_is_cograph() {
211        // C4 has no induced P4 (every 4-vertex path has a chord)
212        // Complement of C4 is 2K2, disconnected into cograph components
213        let mut g = Graph::with_vertices(4);
214        g.add_edge(0, 1).unwrap();
215        g.add_edge(1, 2).unwrap();
216        g.add_edge(2, 3).unwrap();
217        g.add_edge(3, 0).unwrap();
218        assert!(is_cograph(&g).unwrap());
219    }
220
221    #[test]
222    fn c5_not_cograph() {
223        let mut g = Graph::with_vertices(5);
224        g.add_edge(0, 1).unwrap();
225        g.add_edge(1, 2).unwrap();
226        g.add_edge(2, 3).unwrap();
227        g.add_edge(3, 4).unwrap();
228        g.add_edge(4, 0).unwrap();
229        assert!(!is_cograph(&g).unwrap());
230    }
231
232    #[test]
233    fn star_is_cograph() {
234        // K_{1,n} is a cograph for any n
235        let mut g = Graph::with_vertices(5);
236        for i in 1..5u32 {
237            g.add_edge(0, i).unwrap();
238        }
239        assert!(is_cograph(&g).unwrap());
240    }
241
242    #[test]
243    fn path_3_is_cograph() {
244        // P3: 0-1-2 (3 vertices, always cograph)
245        let mut g = Graph::with_vertices(3);
246        g.add_edge(0, 1).unwrap();
247        g.add_edge(1, 2).unwrap();
248        assert!(is_cograph(&g).unwrap());
249    }
250
251    #[test]
252    fn disjoint_k3_k2_is_cograph() {
253        // K3 + K2: both components are cographs → cograph
254        let mut g = Graph::with_vertices(5);
255        g.add_edge(0, 1).unwrap();
256        g.add_edge(1, 2).unwrap();
257        g.add_edge(2, 0).unwrap();
258        g.add_edge(3, 4).unwrap();
259        assert!(is_cograph(&g).unwrap());
260    }
261
262    #[test]
263    fn complement_of_p4_not_cograph() {
264        // Complement of P4 is also P4 (self-complementary) → not cograph
265        let mut g = Graph::with_vertices(4);
266        // P4: 0-1, 1-2, 2-3
267        // Complement: 0-2, 0-3, 1-3 → which is path 2-0-3-1, also P4
268        g.add_edge(0, 2).unwrap();
269        g.add_edge(0, 3).unwrap();
270        g.add_edge(1, 3).unwrap();
271        assert!(!is_cograph(&g).unwrap());
272    }
273
274    #[test]
275    fn k2_union_k2_is_cograph() {
276        // Two disjoint edges: cograph (complement is C4, but each
277        // component of this graph is K2 which is cograph)
278        let mut g = Graph::with_vertices(4);
279        g.add_edge(0, 1).unwrap();
280        g.add_edge(2, 3).unwrap();
281        assert!(is_cograph(&g).unwrap());
282    }
283
284    #[test]
285    fn petersen_not_cograph() {
286        // Petersen graph contains many induced P4s
287        let mut g = Graph::with_vertices(10);
288        g.add_edge(0, 1).unwrap();
289        g.add_edge(1, 2).unwrap();
290        g.add_edge(2, 3).unwrap();
291        g.add_edge(3, 4).unwrap();
292        g.add_edge(4, 0).unwrap();
293        g.add_edge(5, 7).unwrap();
294        g.add_edge(7, 9).unwrap();
295        g.add_edge(9, 6).unwrap();
296        g.add_edge(6, 8).unwrap();
297        g.add_edge(8, 5).unwrap();
298        g.add_edge(0, 5).unwrap();
299        g.add_edge(1, 6).unwrap();
300        g.add_edge(2, 7).unwrap();
301        g.add_edge(3, 8).unwrap();
302        g.add_edge(4, 9).unwrap();
303        assert!(!is_cograph(&g).unwrap());
304    }
305
306    #[test]
307    fn directed_returns_false() {
308        let mut g = Graph::new(3, true).unwrap();
309        g.add_edge(0, 1).unwrap();
310        g.add_edge(1, 2).unwrap();
311        assert!(!is_cograph(&g).unwrap());
312    }
313
314    #[test]
315    fn join_of_two_edgeless_is_cograph() {
316        // K_{2,3} is NOT a cograph (contains induced P4: 0-2-1-3)
317        // But disjoint union of edgeless graphs IS cograph
318        let g = Graph::with_vertices(5);
319        assert!(is_cograph(&g).unwrap());
320    }
321
322    #[test]
323    fn k23_is_cograph() {
324        // K_{2,3}: complement is K2 + K3, all complete bipartite graphs are cographs
325        let mut g = Graph::with_vertices(5);
326        g.add_edge(0, 2).unwrap();
327        g.add_edge(0, 3).unwrap();
328        g.add_edge(0, 4).unwrap();
329        g.add_edge(1, 2).unwrap();
330        g.add_edge(1, 3).unwrap();
331        g.add_edge(1, 4).unwrap();
332        assert!(is_cograph(&g).unwrap());
333    }
334
335    #[test]
336    fn threshold_graph_is_cograph() {
337        // Threshold graphs are a subclass of cographs
338        // Build one: start with K1, add isolated, add dominating, add isolated
339        // 0: initial vertex
340        // 1: isolated (no edges)
341        // 2: dominating (edges to 0, 1)
342        // 3: isolated (no edges)
343        // 4: dominating (edges to 0, 1, 2, 3)
344        let mut g = Graph::with_vertices(5);
345        g.add_edge(2, 0).unwrap();
346        g.add_edge(2, 1).unwrap();
347        g.add_edge(4, 0).unwrap();
348        g.add_edge(4, 1).unwrap();
349        g.add_edge(4, 2).unwrap();
350        g.add_edge(4, 3).unwrap();
351        assert!(is_cograph(&g).unwrap());
352    }
353}