rust_igraph/algorithms/properties/
is_bull_free.rs1use crate::core::{Graph, IgraphResult};
11
12pub fn is_bull_free(graph: &Graph) -> IgraphResult<bool> {
41 if graph.is_directed() {
42 return Ok(false);
43 }
44
45 let n = graph.vcount();
46 if n < 5 {
47 return Ok(true);
48 }
49
50 let n_usize = n as usize;
51 let mut adj = vec![vec![false; n_usize]; n_usize];
52 for v in 0..n {
53 let nbrs = graph.neighbors(v)?;
54 for &w in &nbrs {
55 adj[v as usize][w as usize] = true;
56 }
57 }
58
59 for a in 0..n {
65 let ai = a as usize;
66 for b in (a + 1)..n {
67 let bi = b as usize;
68 if !adj[ai][bi] {
69 continue;
70 }
71 for c in (b + 1)..n {
72 let ci = c as usize;
73 if !adj[ai][ci] || !adj[bi][ci] {
74 continue;
75 }
76
77 if has_bull_pendants(&adj, n, ai, bi, ci) {
81 return Ok(false);
82 }
83 if has_bull_pendants(&adj, n, bi, ai, ci) {
85 return Ok(false);
86 }
87 if has_bull_pendants(&adj, n, ci, ai, bi) {
89 return Ok(false);
90 }
91 }
92 }
93 }
94
95 Ok(true)
96}
97
98fn has_bull_pendants(adj: &[Vec<bool>], _n: u32, apex: usize, v1: usize, v2: usize) -> bool {
103 let row_v1 = &adj[v1];
104 let row_apex = &adj[apex];
105 let row_v2 = &adj[v2];
106
107 let d_candidates: Vec<usize> = row_v1
108 .iter()
109 .enumerate()
110 .filter(|&(d, &is_adj)| {
111 is_adj && d != apex && d != v1 && d != v2 && !row_apex[d] && !row_v2[d]
112 })
113 .map(|(d, _)| d)
114 .collect();
115
116 if d_candidates.is_empty() {
117 return false;
118 }
119
120 for (e, &v2_to_e) in row_v2.iter().enumerate() {
121 if !v2_to_e || e == apex || e == v1 || e == v2 {
122 continue;
123 }
124 if !row_apex[e] && !row_v1[e] {
125 for &d in &d_candidates {
126 if d != e && !adj[d][e] {
127 return true;
128 }
129 }
130 }
131 }
132
133 false
134}
135
136#[cfg(test)]
137mod tests {
138 use super::*;
139
140 #[test]
141 fn empty_graph() {
142 let g = Graph::with_vertices(0);
143 assert!(is_bull_free(&g).unwrap());
144 }
145
146 #[test]
147 fn small_graphs() {
148 let g = Graph::with_vertices(4);
149 assert!(is_bull_free(&g).unwrap());
150 }
151
152 #[test]
153 fn triangle() {
154 let mut g = Graph::with_vertices(3);
155 g.add_edge(0, 1).unwrap();
156 g.add_edge(1, 2).unwrap();
157 g.add_edge(2, 0).unwrap();
158 assert!(is_bull_free(&g).unwrap());
159 }
160
161 #[test]
162 fn bull() {
163 let mut g = Graph::with_vertices(5);
165 g.add_edge(0, 1).unwrap();
166 g.add_edge(0, 2).unwrap();
167 g.add_edge(1, 2).unwrap();
168 g.add_edge(1, 3).unwrap();
169 g.add_edge(2, 4).unwrap();
170 assert!(!is_bull_free(&g).unwrap());
171 }
172
173 #[test]
174 fn k5_bull_free() {
175 let mut g = Graph::with_vertices(5);
177 for i in 0..5u32 {
178 for j in (i + 1)..5 {
179 g.add_edge(i, j).unwrap();
180 }
181 }
182 assert!(is_bull_free(&g).unwrap());
183 }
184
185 #[test]
186 fn c5_bull_free() {
187 let mut g = Graph::with_vertices(5);
189 g.add_edge(0, 1).unwrap();
190 g.add_edge(1, 2).unwrap();
191 g.add_edge(2, 3).unwrap();
192 g.add_edge(3, 4).unwrap();
193 g.add_edge(4, 0).unwrap();
194 assert!(is_bull_free(&g).unwrap());
195 }
196
197 #[test]
198 fn path_bull_free() {
199 let mut g = Graph::with_vertices(5);
200 g.add_edge(0, 1).unwrap();
201 g.add_edge(1, 2).unwrap();
202 g.add_edge(2, 3).unwrap();
203 g.add_edge(3, 4).unwrap();
204 assert!(is_bull_free(&g).unwrap());
205 }
206
207 #[test]
208 fn paw_bull_free() {
209 let mut g = Graph::with_vertices(4);
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(2, 3).unwrap();
216 assert!(is_bull_free(&g).unwrap());
217 }
218
219 #[test]
220 fn diamond_bull_free() {
221 let mut g = Graph::with_vertices(4);
223 g.add_edge(0, 1).unwrap();
224 g.add_edge(0, 2).unwrap();
225 g.add_edge(0, 3).unwrap();
226 g.add_edge(1, 2).unwrap();
227 g.add_edge(1, 3).unwrap();
228 assert!(is_bull_free(&g).unwrap());
229 }
230
231 #[test]
232 fn directed_returns_false() {
233 let mut g = Graph::new(3, true).unwrap();
234 g.add_edge(0, 1).unwrap();
235 g.add_edge(1, 2).unwrap();
236 g.add_edge(2, 0).unwrap();
237 assert!(!is_bull_free(&g).unwrap());
238 }
239
240 #[test]
241 fn star_bull_free() {
242 let mut g = Graph::with_vertices(6);
243 g.add_edge(0, 1).unwrap();
244 g.add_edge(0, 2).unwrap();
245 g.add_edge(0, 3).unwrap();
246 g.add_edge(0, 4).unwrap();
247 g.add_edge(0, 5).unwrap();
248 assert!(is_bull_free(&g).unwrap());
249 }
250
251 #[test]
252 fn triangle_with_two_pendants_on_same_vertex() {
253 let mut g = Graph::with_vertices(5);
256 g.add_edge(0, 1).unwrap();
257 g.add_edge(1, 2).unwrap();
258 g.add_edge(2, 0).unwrap();
259 g.add_edge(1, 3).unwrap();
260 g.add_edge(1, 4).unwrap();
261 assert!(is_bull_free(&g).unwrap());
265 }
266
267 #[test]
268 fn connected_pendants_not_bull() {
269 let mut g = Graph::with_vertices(5);
272 g.add_edge(0, 1).unwrap();
273 g.add_edge(0, 2).unwrap();
274 g.add_edge(1, 2).unwrap();
275 g.add_edge(1, 3).unwrap();
276 g.add_edge(2, 4).unwrap();
277 g.add_edge(3, 4).unwrap();
278 assert!(is_bull_free(&g).unwrap());
279 }
280}