rust_igraph/algorithms/properties/
is_windmill.rs1use crate::algorithms::properties::is_simple::is_simple;
13use crate::core::{Graph, IgraphResult};
14
15pub fn is_windmill(graph: &Graph) -> IgraphResult<Option<(u32, u32)>> {
43 if graph.is_directed() {
44 return Ok(None);
45 }
46
47 let n = graph.vcount();
48 if n == 0 {
49 return Ok(None);
50 }
51 if n == 1 {
52 return Ok(Some((2, 0)));
53 }
54
55 if !is_simple(graph)? {
56 return Ok(None);
57 }
58
59 let mut hub = 0u32;
61 let mut hub_deg = graph.degree(0)?;
62 for v in 1..n {
63 let d = graph.degree(v)?;
64 if d > hub_deg {
65 hub_deg = d;
66 hub = v;
67 }
68 }
69
70 if hub_deg != (n - 1) as usize {
72 return Ok(None);
73 }
74
75 let mut non_hub_deg = 0usize;
77 for v in 0..n {
78 if v == hub {
79 continue;
80 }
81 let d = graph.degree(v)?;
82 if non_hub_deg == 0 {
83 non_hub_deg = d;
84 } else if d != non_hub_deg {
85 return Ok(None);
86 }
87 }
88
89 let k_minus_1 = non_hub_deg;
92 if k_minus_1 == 0 {
93 return Ok(None);
94 }
95 let k = u32::try_from(k_minus_1 + 1).unwrap_or(u32::MAX);
96
97 let non_hub_count = (n - 1) as usize;
99 if non_hub_count % k_minus_1 != 0 {
100 return Ok(None);
101 }
102 let num_copies = u32::try_from(non_hub_count / k_minus_1).unwrap_or(u32::MAX);
103
104 let k64 = u64::from(k);
106 let expected_edges =
107 u64::from(num_copies).saturating_mul(k64.saturating_mul(k64.saturating_sub(1)) / 2);
108 if graph.ecount() as u64 != expected_edges {
109 return Ok(None);
110 }
111
112 let mut visited = vec![false; n as usize];
120 visited[hub as usize] = true;
121
122 for v in 0..n {
123 if visited[v as usize] {
124 continue;
125 }
126
127 let nbrs = graph.neighbors(v)?;
128 let clique_mates: Vec<u32> = nbrs.iter().filter(|&&w| w != hub).copied().collect();
129
130 if clique_mates.len() != k_minus_1 - 1 {
131 return Ok(None);
132 }
133
134 for &mate in &clique_mates {
136 if visited[mate as usize] {
137 return Ok(None);
138 }
139 }
140
141 for (i, &u) in clique_mates.iter().enumerate() {
143 let u_nbrs = graph.neighbors(u)?;
144 for &w in &clique_mates[i + 1..] {
145 if !u_nbrs.contains(&w) {
146 return Ok(None);
147 }
148 }
149 }
150
151 visited[v as usize] = true;
153 for &mate in &clique_mates {
154 visited[mate as usize] = true;
155 }
156 }
157
158 Ok(Some((k, num_copies)))
159}
160
161#[cfg(test)]
162mod tests {
163 use super::*;
164
165 #[test]
166 fn empty_graph() {
167 let g = Graph::with_vertices(0);
168 assert_eq!(is_windmill(&g).unwrap(), None);
169 }
170
171 #[test]
172 fn single_vertex() {
173 let g = Graph::with_vertices(1);
174 assert_eq!(is_windmill(&g).unwrap(), Some((2, 0)));
175 }
176
177 #[test]
178 fn single_triangle_wd31() {
179 let mut g = Graph::with_vertices(3);
180 g.add_edge(0, 1).unwrap();
181 g.add_edge(1, 2).unwrap();
182 g.add_edge(2, 0).unwrap();
183 assert_eq!(is_windmill(&g).unwrap(), Some((3, 1)));
184 }
185
186 #[test]
187 fn friendship_wd32() {
188 let mut g = Graph::with_vertices(5);
190 g.add_edge(0, 1).unwrap();
191 g.add_edge(0, 2).unwrap();
192 g.add_edge(1, 2).unwrap();
193 g.add_edge(0, 3).unwrap();
194 g.add_edge(0, 4).unwrap();
195 g.add_edge(3, 4).unwrap();
196 assert_eq!(is_windmill(&g).unwrap(), Some((3, 2)));
197 }
198
199 #[test]
200 fn friendship_wd33() {
201 let mut g = Graph::with_vertices(7);
203 g.add_edge(0, 1).unwrap();
204 g.add_edge(0, 2).unwrap();
205 g.add_edge(1, 2).unwrap();
206 g.add_edge(0, 3).unwrap();
207 g.add_edge(0, 4).unwrap();
208 g.add_edge(3, 4).unwrap();
209 g.add_edge(0, 5).unwrap();
210 g.add_edge(0, 6).unwrap();
211 g.add_edge(5, 6).unwrap();
212 assert_eq!(is_windmill(&g).unwrap(), Some((3, 3)));
213 }
214
215 #[test]
216 fn wd42() {
217 let mut g = Graph::with_vertices(7);
220 for i in 0..4u32 {
222 for j in (i + 1)..4 {
223 g.add_edge(i, j).unwrap();
224 }
225 }
226 for &i in &[0u32, 4, 5, 6] {
228 for &j in &[0u32, 4, 5, 6] {
229 if i < j {
230 g.add_edge(i, j).unwrap();
231 }
232 }
233 }
234 assert_eq!(is_windmill(&g).unwrap(), Some((4, 2)));
235 }
236
237 #[test]
238 fn star_is_wd2n() {
239 let mut g = Graph::with_vertices(4);
241 g.add_edge(0, 1).unwrap();
242 g.add_edge(0, 2).unwrap();
243 g.add_edge(0, 3).unwrap();
244 assert_eq!(is_windmill(&g).unwrap(), Some((2, 3)));
245 }
246
247 #[test]
248 fn path_not_windmill() {
249 let mut g = Graph::with_vertices(4);
250 g.add_edge(0, 1).unwrap();
251 g.add_edge(1, 2).unwrap();
252 g.add_edge(2, 3).unwrap();
253 assert_eq!(is_windmill(&g).unwrap(), None);
254 }
255
256 #[test]
257 fn cycle_c4_not_windmill() {
258 let mut g = Graph::with_vertices(4);
259 g.add_edge(0, 1).unwrap();
260 g.add_edge(1, 2).unwrap();
261 g.add_edge(2, 3).unwrap();
262 g.add_edge(3, 0).unwrap();
263 assert_eq!(is_windmill(&g).unwrap(), None);
264 }
265
266 #[test]
267 fn k4_is_wd41() {
268 let mut g = Graph::with_vertices(4);
269 g.add_edge(0, 1).unwrap();
270 g.add_edge(0, 2).unwrap();
271 g.add_edge(0, 3).unwrap();
272 g.add_edge(1, 2).unwrap();
273 g.add_edge(1, 3).unwrap();
274 g.add_edge(2, 3).unwrap();
275 assert_eq!(is_windmill(&g).unwrap(), Some((4, 1)));
276 }
277
278 #[test]
279 fn directed_returns_none() {
280 let mut g = Graph::new(3, true).unwrap();
281 g.add_edge(0, 1).unwrap();
282 g.add_edge(1, 2).unwrap();
283 g.add_edge(2, 0).unwrap();
284 assert_eq!(is_windmill(&g).unwrap(), None);
285 }
286
287 #[test]
288 fn butterfly_not_windmill() {
289 let mut g = Graph::with_vertices(4);
291 g.add_edge(0, 1).unwrap();
292 g.add_edge(1, 2).unwrap();
293 g.add_edge(2, 0).unwrap();
294 g.add_edge(0, 3).unwrap();
295 g.add_edge(3, 1).unwrap();
296 assert_eq!(is_windmill(&g).unwrap(), None);
303 }
304}