Skip to main content

rust_igraph/algorithms/properties/
is_paw_free.rs

1//! Paw-free graph predicate (ALGO-PR-099).
2//!
3//! A graph is paw-free if it contains no induced subgraph isomorphic
4//! to the paw graph. The paw is a triangle with one pendant edge
5//! (4 vertices, 4 edges): vertices {a, b, c, d} with edges
6//! {ab, ac, bc, cd} (d is pendant, adjacent only to c).
7//!
8//! Paw-free graphs are exactly the graphs where every connected
9//! component is either triangle-free or complete.
10//!
11//! For directed graphs, the function returns `false`.
12
13use crate::core::{Graph, IgraphResult};
14
15/// Check whether a graph is paw-free.
16///
17/// The paw is a triangle plus a pendant edge. A paw-free graph has
18/// no induced paw. Equivalently, every connected component is
19/// either triangle-free or complete.
20///
21/// Returns `false` for directed graphs.
22///
23/// # Examples
24///
25/// ```
26/// use rust_igraph::{Graph, is_paw_free};
27///
28/// // Triangle is paw-free (no pendant vertex)
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_paw_free(&g).unwrap());
34///
35/// // Triangle + pendant: paw!
36/// let mut g = Graph::with_vertices(4);
37/// g.add_edge(0, 1).unwrap();
38/// g.add_edge(1, 2).unwrap();
39/// g.add_edge(2, 0).unwrap();
40/// g.add_edge(2, 3).unwrap();
41/// assert!(!is_paw_free(&g).unwrap());
42/// ```
43pub fn is_paw_free(graph: &Graph) -> IgraphResult<bool> {
44    if graph.is_directed() {
45        return Ok(false);
46    }
47
48    let n = graph.vcount();
49    if n < 4 {
50        return Ok(true);
51    }
52
53    let n_usize = n as usize;
54    let mut adj = vec![vec![false; n_usize]; n_usize];
55    for v in 0..n {
56        let nbrs = graph.neighbors(v)?;
57        for &w in &nbrs {
58            adj[v as usize][w as usize] = true;
59        }
60    }
61
62    // Check for induced paw: find a triangle {a, b, c} where some
63    // vertex d is adjacent to exactly one of {a, b, c}.
64    // Equivalently: for each edge (a, b), find common neighbor c
65    // (forming a triangle), then check if any neighbor of a, b, or c
66    // is adjacent to exactly one member of the triangle.
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
75            // Find common neighbors of a and b (forming triangles)
76            for c in (b + 1)..n {
77                let ci = c as usize;
78                if !adj[ai][ci] || !adj[bi][ci] {
79                    continue;
80                }
81
82                // Triangle {a, b, c} found. Check if any vertex d
83                // is adjacent to exactly one of {a, b, c}.
84                for d in 0..n {
85                    if d == a || d == b || d == c {
86                        continue;
87                    }
88                    let di = d as usize;
89                    let count =
90                        u32::from(adj[ai][di]) + u32::from(adj[bi][di]) + u32::from(adj[ci][di]);
91                    if count == 1 {
92                        return Ok(false);
93                    }
94                }
95            }
96        }
97    }
98
99    Ok(true)
100}
101
102#[cfg(test)]
103mod tests {
104    use super::*;
105
106    #[test]
107    fn empty_graph() {
108        let g = Graph::with_vertices(0);
109        assert!(is_paw_free(&g).unwrap());
110    }
111
112    #[test]
113    fn single_vertex() {
114        let g = Graph::with_vertices(1);
115        assert!(is_paw_free(&g).unwrap());
116    }
117
118    #[test]
119    fn triangle() {
120        let mut g = Graph::with_vertices(3);
121        g.add_edge(0, 1).unwrap();
122        g.add_edge(1, 2).unwrap();
123        g.add_edge(2, 0).unwrap();
124        assert!(is_paw_free(&g).unwrap());
125    }
126
127    #[test]
128    fn paw() {
129        // Triangle 0-1-2 + pendant 2-3
130        let mut g = Graph::with_vertices(4);
131        g.add_edge(0, 1).unwrap();
132        g.add_edge(1, 2).unwrap();
133        g.add_edge(2, 0).unwrap();
134        g.add_edge(2, 3).unwrap();
135        assert!(!is_paw_free(&g).unwrap());
136    }
137
138    #[test]
139    fn k4_paw_free() {
140        // K_4 is complete → paw-free (no vertex adjacent to exactly 1 of a triangle)
141        let mut g = Graph::with_vertices(4);
142        g.add_edge(0, 1).unwrap();
143        g.add_edge(0, 2).unwrap();
144        g.add_edge(0, 3).unwrap();
145        g.add_edge(1, 2).unwrap();
146        g.add_edge(1, 3).unwrap();
147        g.add_edge(2, 3).unwrap();
148        assert!(is_paw_free(&g).unwrap());
149    }
150
151    #[test]
152    fn path_paw_free() {
153        // Path: no triangles → no paw
154        let mut g = Graph::with_vertices(5);
155        g.add_edge(0, 1).unwrap();
156        g.add_edge(1, 2).unwrap();
157        g.add_edge(2, 3).unwrap();
158        g.add_edge(3, 4).unwrap();
159        assert!(is_paw_free(&g).unwrap());
160    }
161
162    #[test]
163    fn c5_paw_free() {
164        // C_5 is triangle-free → paw-free
165        let mut g = Graph::with_vertices(5);
166        g.add_edge(0, 1).unwrap();
167        g.add_edge(1, 2).unwrap();
168        g.add_edge(2, 3).unwrap();
169        g.add_edge(3, 4).unwrap();
170        g.add_edge(4, 0).unwrap();
171        assert!(is_paw_free(&g).unwrap());
172    }
173
174    #[test]
175    fn diamond_not_paw_free() {
176        // Diamond: K_4 minus edge 2-3. Triangle {0,1,2} with vertex 3
177        // adjacent to 0 and 1 but not 2 → vertex 3 adj to 2 of triangle
178        // Actually: diamond = {01, 02, 03, 12, 13}. Triangle {0,1,2}.
179        // Vertex 3 is adjacent to 0 and 1 (count=2) → not exactly 1.
180        // Triangle {0,1,3}: vertex 2 adjacent to 0 and 1 (count=2) → not 1.
181        // So diamond might actually be paw-free? Let me check:
182        // Triangle {0,1,2}: vertex 3 adj to 0(yes) 1(yes) 2(no) → count=2
183        // Triangle {0,1,3}: vertex 2 adj to 0(yes) 1(yes) 3(no) → count=2
184        // No triangle has a vertex adjacent to exactly 1 member → paw-free!
185        let mut g = Graph::with_vertices(4);
186        g.add_edge(0, 1).unwrap();
187        g.add_edge(0, 2).unwrap();
188        g.add_edge(0, 3).unwrap();
189        g.add_edge(1, 2).unwrap();
190        g.add_edge(1, 3).unwrap();
191        assert!(is_paw_free(&g).unwrap());
192    }
193
194    #[test]
195    fn bull_not_paw_free() {
196        // Bull: triangle 0-1-2, pendants 1-3 and 2-4
197        // Triangle {0,1,2}, vertex 3 adjacent to 1 only → count=1 → paw!
198        let mut g = Graph::with_vertices(5);
199        g.add_edge(0, 1).unwrap();
200        g.add_edge(0, 2).unwrap();
201        g.add_edge(1, 2).unwrap();
202        g.add_edge(1, 3).unwrap();
203        g.add_edge(2, 4).unwrap();
204        assert!(!is_paw_free(&g).unwrap());
205    }
206
207    #[test]
208    fn disconnected_triangle_plus_edge() {
209        // Triangle + isolated edge: no paw (the edge vertex is in
210        // a different component from the triangle)
211        let mut g = Graph::with_vertices(5);
212        g.add_edge(0, 1).unwrap();
213        g.add_edge(1, 2).unwrap();
214        g.add_edge(2, 0).unwrap();
215        g.add_edge(3, 4).unwrap();
216        // Triangle {0,1,2}: vertex 3 adj to 0(no) 1(no) 2(no) → count=0
217        // vertex 4 adj to 0(no) 1(no) 2(no) → count=0
218        // No paw!
219        assert!(is_paw_free(&g).unwrap());
220    }
221
222    #[test]
223    fn directed_returns_false() {
224        let mut g = Graph::new(3, true).unwrap();
225        g.add_edge(0, 1).unwrap();
226        g.add_edge(1, 2).unwrap();
227        g.add_edge(2, 0).unwrap();
228        assert!(!is_paw_free(&g).unwrap());
229    }
230
231    #[test]
232    fn isolated_vertices() {
233        let g = Graph::with_vertices(10);
234        assert!(is_paw_free(&g).unwrap());
235    }
236
237    #[test]
238    fn windmill_not_paw_free() {
239        // Wd(3,2): two triangles sharing vertex 0
240        // 0-1-2-0, 0-3-4-0
241        // Triangle {0,1,2}: vertex 3 adj to 0 only → count=1 → paw!
242        let mut g = Graph::with_vertices(5);
243        g.add_edge(0, 1).unwrap();
244        g.add_edge(0, 2).unwrap();
245        g.add_edge(1, 2).unwrap();
246        g.add_edge(0, 3).unwrap();
247        g.add_edge(0, 4).unwrap();
248        g.add_edge(3, 4).unwrap();
249        assert!(!is_paw_free(&g).unwrap());
250    }
251}