Skip to main content

rust_igraph/algorithms/properties/
is_dart_free.rs

1//! Dart-free graph predicate (ALGO-PR-107).
2//!
3//! A graph is dart-free if it contains no induced dart. The dart is a
4//! diamond (`K_4` minus one edge) plus one pendant edge from a
5//! degree-2 vertex of the diamond: 5 vertices, 6 edges.
6//!
7//! Concretely: vertices {a, b, c, d, e} where {a, b, c, d} form a
8//! diamond with edges a-b, a-c, a-d, b-c, b-d (missing c-d), and
9//! pendant edge c-e (or d-e). Vertex e is adjacent only to c.
10//!
11//! For directed graphs, the function returns `false`.
12
13use crate::core::{Graph, IgraphResult};
14
15/// Check whether a graph is dart-free (no induced dart).
16///
17/// The dart is a diamond plus a pendant from a degree-2 vertex of the
18/// diamond. A dart-free graph has no induced dart.
19///
20/// Returns `false` for directed graphs.
21///
22/// # Examples
23///
24/// ```
25/// use rust_igraph::{Graph, is_dart_free};
26///
27/// // Diamond is dart-free (only 4 vertices)
28/// let mut g = Graph::with_vertices(4);
29/// g.add_edge(0, 1).unwrap();
30/// g.add_edge(0, 2).unwrap();
31/// g.add_edge(0, 3).unwrap();
32/// g.add_edge(1, 2).unwrap();
33/// g.add_edge(1, 3).unwrap();
34/// assert!(is_dart_free(&g).unwrap());
35///
36/// // Dart: diamond {0,1,2,3} (missing 2-3) + pendant 2-4
37/// let mut g = Graph::with_vertices(5);
38/// g.add_edge(0, 1).unwrap();
39/// g.add_edge(0, 2).unwrap();
40/// g.add_edge(0, 3).unwrap();
41/// g.add_edge(1, 2).unwrap();
42/// g.add_edge(1, 3).unwrap();
43/// g.add_edge(2, 4).unwrap();
44/// assert!(!is_dart_free(&g).unwrap());
45/// ```
46pub fn is_dart_free(graph: &Graph) -> IgraphResult<bool> {
47    if graph.is_directed() {
48        return Ok(false);
49    }
50
51    let n = graph.vcount();
52    if n < 5 {
53        return Ok(true);
54    }
55
56    let n_usize = n as usize;
57    let mut adj = vec![vec![false; n_usize]; n_usize];
58    let mut nbrs_list: Vec<Vec<u32>> = Vec::with_capacity(n_usize);
59
60    for v in 0..n {
61        let nbrs = graph.neighbors(v)?;
62        for &w in &nbrs {
63            adj[v as usize][w as usize] = true;
64        }
65        nbrs_list.push(nbrs);
66    }
67
68    // Diamond {a, b, c, d}: a-b, a-c, a-d, b-c, b-d edges, c-d missing.
69    // a and b are the degree-3 vertices (the "spine"), c and d are
70    // degree-2 (the "wings").
71    //
72    // Dart: pendant e adjacent to c (or d) but not to a, b, or d (or c).
73    //
74    // Strategy: for each edge (a,b) that share ≥ 2 common neighbors,
75    // find pairs (c,d) of common neighbors where c-d is NOT an edge
76    // (forming a diamond). Then check if c or d has a pendant neighbor
77    // outside {a,b,c,d} not adjacent to the other 3.
78    for a in 0..n {
79        let ai = a as usize;
80        for &b in &nbrs_list[ai] {
81            if b <= a {
82                continue;
83            }
84            let bi = b as usize;
85            // Common neighbors of a and b
86            let common: Vec<usize> = nbrs_list[ai]
87                .iter()
88                .filter(|&&w| w != b && adj[bi][w as usize])
89                .map(|&w| w as usize)
90                .collect();
91
92            if common.len() < 2 {
93                continue;
94            }
95
96            for (i, &ci) in common.iter().enumerate() {
97                for &di in &common[(i + 1)..] {
98                    if adj[ci][di] {
99                        continue;
100                    }
101                    // Diamond: {a, b} spine, {c, d} wings (c-d missing)
102                    // Check c for pendant
103                    if has_dart_pendant(&adj, &nbrs_list, ci, ai, bi, di) {
104                        return Ok(false);
105                    }
106                    // Check d for pendant
107                    if has_dart_pendant(&adj, &nbrs_list, di, ai, bi, ci) {
108                        return Ok(false);
109                    }
110                }
111            }
112        }
113    }
114
115    Ok(true)
116}
117
118/// Check if wing vertex has a neighbor outside the diamond
119/// that is not adjacent to the other three diamond vertices.
120fn has_dart_pendant(
121    adj: &[Vec<bool>],
122    nbrs_list: &[Vec<u32>],
123    wing: usize,
124    s1: usize,
125    s2: usize,
126    other_wing: usize,
127) -> bool {
128    for &e_u32 in &nbrs_list[wing] {
129        let e = e_u32 as usize;
130        if e == s1 || e == s2 || e == other_wing {
131            continue;
132        }
133        if !adj[s1][e] && !adj[s2][e] && !adj[other_wing][e] {
134            return true;
135        }
136    }
137    false
138}
139
140#[cfg(test)]
141mod tests {
142    use super::*;
143
144    #[test]
145    fn empty_graph() {
146        let g = Graph::with_vertices(0);
147        assert!(is_dart_free(&g).unwrap());
148    }
149
150    #[test]
151    fn small_graphs() {
152        let g = Graph::with_vertices(4);
153        assert!(is_dart_free(&g).unwrap());
154    }
155
156    #[test]
157    fn triangle() {
158        let mut g = Graph::with_vertices(3);
159        g.add_edge(0, 1).unwrap();
160        g.add_edge(1, 2).unwrap();
161        g.add_edge(2, 0).unwrap();
162        assert!(is_dart_free(&g).unwrap());
163    }
164
165    #[test]
166    fn diamond_dart_free() {
167        let mut g = Graph::with_vertices(4);
168        g.add_edge(0, 1).unwrap();
169        g.add_edge(0, 2).unwrap();
170        g.add_edge(0, 3).unwrap();
171        g.add_edge(1, 2).unwrap();
172        g.add_edge(1, 3).unwrap();
173        assert!(is_dart_free(&g).unwrap());
174    }
175
176    #[test]
177    fn dart() {
178        // Diamond {0,1,2,3}: edges 0-1, 0-2, 0-3, 1-2, 1-3 (missing 2-3)
179        // Pendant: 2-4
180        let mut g = Graph::with_vertices(5);
181        g.add_edge(0, 1).unwrap();
182        g.add_edge(0, 2).unwrap();
183        g.add_edge(0, 3).unwrap();
184        g.add_edge(1, 2).unwrap();
185        g.add_edge(1, 3).unwrap();
186        g.add_edge(2, 4).unwrap();
187        assert!(!is_dart_free(&g).unwrap());
188    }
189
190    #[test]
191    fn dart_pendant_from_other_wing() {
192        // Diamond {0,1,2,3}, pendant from d=3 instead of c=2
193        let mut g = Graph::with_vertices(5);
194        g.add_edge(0, 1).unwrap();
195        g.add_edge(0, 2).unwrap();
196        g.add_edge(0, 3).unwrap();
197        g.add_edge(1, 2).unwrap();
198        g.add_edge(1, 3).unwrap();
199        g.add_edge(3, 4).unwrap();
200        assert!(!is_dart_free(&g).unwrap());
201    }
202
203    #[test]
204    fn k5_dart_free() {
205        // `K_5`: no missing edges in any 4-vertex subgraph → no diamond → no dart
206        let mut g = Graph::with_vertices(5);
207        for i in 0..5u32 {
208            for j in (i + 1)..5 {
209                g.add_edge(i, j).unwrap();
210            }
211        }
212        assert!(is_dart_free(&g).unwrap());
213    }
214
215    #[test]
216    fn pendant_from_spine_not_dart() {
217        // Diamond {0,1,2,3} with pendant from spine vertex 0 (degree-3 vertex)
218        // Induced subgraph: {0,1,2,3,4} has edges 0-1,0-2,0-3,1-2,1-3,0-4
219        // Vertex 4 connects to 0 (spine) → this is a diamond + pendant from
220        // spine, which is NOT a dart (dart needs pendant from wing).
221        // Actually, the induced subgraph on {1,0,2,3,4}: 1-0, 0-2, 0-3, 1-2,
222        // 1-3, 0-4 → diamond {1,0,2,3} missing 2-3, pendant from 0 to 4.
223        // 0 is a spine vertex (deg 3 in diamond). Not a wing vertex.
224        // But wait: {2,3} are common neighbors of {0,1}. Missing edge 2-3.
225        // Wings are 2 and 3. Pendant 4 is from vertex 0 (spine), not wing.
226        // So it IS NOT a dart... but we need to check: does 2 or 3 have a
227        // pendant? Only if 2 or 3 has a neighbor outside {0,1,2,3} that
228        // is not adjacent to 0, 1, or the other wing. Vertex 4 is not a
229        // neighbor of 2 or 3, so no dart.
230        let mut g = Graph::with_vertices(5);
231        g.add_edge(0, 1).unwrap();
232        g.add_edge(0, 2).unwrap();
233        g.add_edge(0, 3).unwrap();
234        g.add_edge(1, 2).unwrap();
235        g.add_edge(1, 3).unwrap();
236        g.add_edge(0, 4).unwrap();
237        assert!(is_dart_free(&g).unwrap());
238    }
239
240    #[test]
241    fn pendant_connected_to_other_wing_not_dart() {
242        // Diamond {0,1,2,3} + vertex 4 adjacent to both wings 2 and 3
243        // → not a pendant (adjacent to two diamond vertices)
244        let mut g = Graph::with_vertices(5);
245        g.add_edge(0, 1).unwrap();
246        g.add_edge(0, 2).unwrap();
247        g.add_edge(0, 3).unwrap();
248        g.add_edge(1, 2).unwrap();
249        g.add_edge(1, 3).unwrap();
250        g.add_edge(2, 4).unwrap();
251        g.add_edge(3, 4).unwrap();
252        assert!(is_dart_free(&g).unwrap());
253    }
254
255    #[test]
256    fn path_dart_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_dart_free(&g).unwrap());
263    }
264
265    #[test]
266    fn star_dart_free() {
267        let mut g = Graph::with_vertices(6);
268        g.add_edge(0, 1).unwrap();
269        g.add_edge(0, 2).unwrap();
270        g.add_edge(0, 3).unwrap();
271        g.add_edge(0, 4).unwrap();
272        g.add_edge(0, 5).unwrap();
273        assert!(is_dart_free(&g).unwrap());
274    }
275
276    #[test]
277    fn directed_returns_false() {
278        let mut g = Graph::new(3, true).unwrap();
279        g.add_edge(0, 1).unwrap();
280        g.add_edge(1, 2).unwrap();
281        g.add_edge(2, 0).unwrap();
282        assert!(!is_dart_free(&g).unwrap());
283    }
284
285    #[test]
286    fn c4_dart_free() {
287        // No diamond in `C_4` → dart-free
288        let mut g = Graph::with_vertices(4);
289        g.add_edge(0, 1).unwrap();
290        g.add_edge(1, 2).unwrap();
291        g.add_edge(2, 3).unwrap();
292        g.add_edge(3, 0).unwrap();
293        assert!(is_dart_free(&g).unwrap());
294    }
295
296    #[test]
297    fn c5_dart_free() {
298        let mut g = Graph::with_vertices(5);
299        g.add_edge(0, 1).unwrap();
300        g.add_edge(1, 2).unwrap();
301        g.add_edge(2, 3).unwrap();
302        g.add_edge(3, 4).unwrap();
303        g.add_edge(4, 0).unwrap();
304        assert!(is_dart_free(&g).unwrap());
305    }
306}