Skip to main content

rust_igraph/algorithms/properties/
is_cricket_free.rs

1//! Cricket-free graph predicate (ALGO-PR-104).
2//!
3//! A graph is cricket-free if it contains no induced cricket. The
4//! cricket has 5 vertices: a triangle {a, b, c} plus two pendant edges
5//! from the *same* triangle vertex, say b-d and b-e (d and e each have
6//! degree 1 in the cricket). It has 5 edges total.
7//!
8//! The cricket differs from the bull: in a bull the two pendants hang
9//! from *different* triangle vertices; in a cricket they hang from the
10//! *same* vertex.
11//!
12//! For directed graphs, the function returns `false`.
13
14use crate::core::{Graph, IgraphResult};
15
16/// Check whether a graph is cricket-free.
17///
18/// The cricket is a triangle with two pendant edges from the same
19/// triangle vertex. A cricket-free graph has no induced cricket.
20///
21/// Returns `false` for directed graphs.
22///
23/// # Examples
24///
25/// ```
26/// use rust_igraph::{Graph, is_cricket_free};
27///
28/// // Triangle is cricket-free
29/// let mut g = Graph::with_vertices(3);
30/// g.add_edge(0, 1).unwrap();
31/// g.add_edge(1, 2).unwrap();
32/// g.add_edge(2, 0).unwrap();
33/// assert!(is_cricket_free(&g).unwrap());
34///
35/// // Cricket: triangle {0,1,2}, pendants 1-3 and 1-4
36/// let mut g = Graph::with_vertices(5);
37/// g.add_edge(0, 1).unwrap();
38/// g.add_edge(0, 2).unwrap();
39/// g.add_edge(1, 2).unwrap();
40/// g.add_edge(1, 3).unwrap();
41/// g.add_edge(1, 4).unwrap();
42/// assert!(!is_cricket_free(&g).unwrap());
43/// ```
44pub fn is_cricket_free(graph: &Graph) -> IgraphResult<bool> {
45    if graph.is_directed() {
46        return Ok(false);
47    }
48
49    let n = graph.vcount();
50    if n < 5 {
51        return Ok(true);
52    }
53
54    let n_usize = n as usize;
55    let mut adj = vec![vec![false; n_usize]; n_usize];
56
57    for v in 0..n {
58        let nbrs = graph.neighbors(v)?;
59        for &w in &nbrs {
60            adj[v as usize][w as usize] = true;
61        }
62    }
63
64    // Induced cricket: triangle {a, b, c} with two pendant vertices d, e
65    // adjacent to b (the "hub") but not to a, c, or each other.
66    // For each triangle, check each of the 3 vertices as potential hub.
67    for a in 0..n {
68        let ai = a as usize;
69        for b in (a + 1)..n {
70            let bi = b as usize;
71            if !adj[ai][bi] {
72                continue;
73            }
74            for c in (b + 1)..n {
75                let ci = c as usize;
76                if !adj[ai][ci] || !adj[bi][ci] {
77                    continue;
78                }
79
80                // Triangle {a, b, c}. Try each vertex as the pendant hub.
81                if has_cricket_pendants(&adj, n_usize, ai, bi, ci) {
82                    return Ok(false);
83                }
84                if has_cricket_pendants(&adj, n_usize, bi, ai, ci) {
85                    return Ok(false);
86                }
87                if has_cricket_pendants(&adj, n_usize, ci, ai, bi) {
88                    return Ok(false);
89                }
90            }
91        }
92    }
93
94    Ok(true)
95}
96
97/// Check if vertex `hub` (part of triangle {hub, t1, t2}) has two
98/// pendant neighbors d, e that form an induced cricket.
99/// d and e must be adjacent to hub only, not to t1, t2, or each other.
100fn has_cricket_pendants(adj: &[Vec<bool>], n: usize, hub: usize, t1: usize, t2: usize) -> bool {
101    let row_hub = &adj[hub];
102    let row_t1 = &adj[t1];
103    let row_t2 = &adj[t2];
104
105    let pendants: Vec<usize> = row_hub
106        .iter()
107        .enumerate()
108        .take(n)
109        .filter(|&(d, &is_adj)| {
110            is_adj && d != hub && d != t1 && d != t2 && !row_t1[d] && !row_t2[d]
111        })
112        .map(|(d, _)| d)
113        .collect();
114
115    if pendants.len() < 2 {
116        return false;
117    }
118
119    for (i, &d) in pendants.iter().enumerate() {
120        for &e in &pendants[(i + 1)..] {
121            if !adj[d][e] {
122                return true;
123            }
124        }
125    }
126
127    false
128}
129
130#[cfg(test)]
131mod tests {
132    use super::*;
133
134    #[test]
135    fn empty_graph() {
136        let g = Graph::with_vertices(0);
137        assert!(is_cricket_free(&g).unwrap());
138    }
139
140    #[test]
141    fn small_graphs() {
142        let g = Graph::with_vertices(4);
143        assert!(is_cricket_free(&g).unwrap());
144    }
145
146    #[test]
147    fn triangle() {
148        let mut g = Graph::with_vertices(3);
149        g.add_edge(0, 1).unwrap();
150        g.add_edge(1, 2).unwrap();
151        g.add_edge(2, 0).unwrap();
152        assert!(is_cricket_free(&g).unwrap());
153    }
154
155    #[test]
156    fn cricket() {
157        // Triangle {0,1,2}, pendants 1-3 and 1-4
158        let mut g = Graph::with_vertices(5);
159        g.add_edge(0, 1).unwrap();
160        g.add_edge(0, 2).unwrap();
161        g.add_edge(1, 2).unwrap();
162        g.add_edge(1, 3).unwrap();
163        g.add_edge(1, 4).unwrap();
164        assert!(!is_cricket_free(&g).unwrap());
165    }
166
167    #[test]
168    fn bull_is_cricket_free() {
169        // Bull: triangle {0,1,2}, pendants from DIFFERENT vertices: 1-3, 2-4
170        // Not a cricket (pendants from same vertex)
171        let mut g = Graph::with_vertices(5);
172        g.add_edge(0, 1).unwrap();
173        g.add_edge(0, 2).unwrap();
174        g.add_edge(1, 2).unwrap();
175        g.add_edge(1, 3).unwrap();
176        g.add_edge(2, 4).unwrap();
177        assert!(is_cricket_free(&g).unwrap());
178    }
179
180    #[test]
181    fn k5_cricket_free() {
182        // `K_5`: all edges present → no induced cricket (pendants would need non-edges)
183        let mut g = Graph::with_vertices(5);
184        for i in 0..5u32 {
185            for j in (i + 1)..5 {
186                g.add_edge(i, j).unwrap();
187            }
188        }
189        assert!(is_cricket_free(&g).unwrap());
190    }
191
192    #[test]
193    fn paw_cricket_free() {
194        // Paw: triangle {0,1,2} + pendant 2-3. Only one pendant → not a cricket
195        let mut g = Graph::with_vertices(4);
196        g.add_edge(0, 1).unwrap();
197        g.add_edge(1, 2).unwrap();
198        g.add_edge(2, 0).unwrap();
199        g.add_edge(2, 3).unwrap();
200        assert!(is_cricket_free(&g).unwrap());
201    }
202
203    #[test]
204    fn pendants_adjacent_not_cricket() {
205        // Triangle {0,1,2}, "pendants" 1-3, 1-4 but 3-4 adjacent
206        // Induced subgraph has edge 3-4 → not a cricket (cricket has no d-e edge)
207        let mut g = Graph::with_vertices(5);
208        g.add_edge(0, 1).unwrap();
209        g.add_edge(0, 2).unwrap();
210        g.add_edge(1, 2).unwrap();
211        g.add_edge(1, 3).unwrap();
212        g.add_edge(1, 4).unwrap();
213        g.add_edge(3, 4).unwrap();
214        assert!(is_cricket_free(&g).unwrap());
215    }
216
217    #[test]
218    fn pendant_connected_to_triangle_vertex() {
219        // Triangle {0,1,2}, 1-3, 1-4, but 3-0 also present
220        // d=3 is adjacent to a=0, so not a pendant → not a cricket on this subgraph
221        // But check: triangle {0,1,2} with hub=1: pendant candidates must NOT
222        // be adjacent to 0 or 2. d=3 is adjacent to 0 → d=3 not a pendant.
223        // So only e=4 is a pendant of hub=1, and we need ≥ 2.
224        let mut g = Graph::with_vertices(5);
225        g.add_edge(0, 1).unwrap();
226        g.add_edge(0, 2).unwrap();
227        g.add_edge(1, 2).unwrap();
228        g.add_edge(1, 3).unwrap();
229        g.add_edge(1, 4).unwrap();
230        g.add_edge(0, 3).unwrap();
231        assert!(is_cricket_free(&g).unwrap());
232    }
233
234    #[test]
235    fn directed_returns_false() {
236        let mut g = Graph::new(3, true).unwrap();
237        g.add_edge(0, 1).unwrap();
238        g.add_edge(1, 2).unwrap();
239        g.add_edge(2, 0).unwrap();
240        assert!(!is_cricket_free(&g).unwrap());
241    }
242
243    #[test]
244    fn star_cricket_free() {
245        // Star: no triangles → no cricket
246        let mut g = Graph::with_vertices(6);
247        g.add_edge(0, 1).unwrap();
248        g.add_edge(0, 2).unwrap();
249        g.add_edge(0, 3).unwrap();
250        g.add_edge(0, 4).unwrap();
251        g.add_edge(0, 5).unwrap();
252        assert!(is_cricket_free(&g).unwrap());
253    }
254
255    #[test]
256    fn path_cricket_free() {
257        let mut g = Graph::with_vertices(5);
258        g.add_edge(0, 1).unwrap();
259        g.add_edge(1, 2).unwrap();
260        g.add_edge(2, 3).unwrap();
261        g.add_edge(3, 4).unwrap();
262        assert!(is_cricket_free(&g).unwrap());
263    }
264
265    #[test]
266    fn three_pendants_from_hub() {
267        // Triangle {0,1,2}, three pendants from 1: 1-3, 1-4, 1-5
268        // Any pair of non-adjacent pendants forms a cricket
269        let mut g = Graph::with_vertices(6);
270        g.add_edge(0, 1).unwrap();
271        g.add_edge(0, 2).unwrap();
272        g.add_edge(1, 2).unwrap();
273        g.add_edge(1, 3).unwrap();
274        g.add_edge(1, 4).unwrap();
275        g.add_edge(1, 5).unwrap();
276        assert!(!is_cricket_free(&g).unwrap());
277    }
278}