rust_igraph/algorithms/properties/
is_wheel.rs1use crate::algorithms::properties::is_simple::is_simple;
12use crate::core::{Graph, IgraphResult};
13
14pub fn is_wheel(graph: &Graph) -> IgraphResult<bool> {
49 if graph.is_directed() {
50 return Ok(false);
51 }
52
53 let n = graph.vcount();
54 if n < 4 {
55 return Ok(false);
56 }
57
58 if !is_simple(graph)? {
59 return Ok(false);
60 }
61
62 let n_usize = n as usize;
64 let expected_edges = 2 * (n_usize - 1);
65 if graph.ecount() != expected_edges {
66 return Ok(false);
67 }
68
69 let mut hub: Option<u32> = None;
71 for v in 0..n {
72 let deg = graph.degree(v)?;
73 if deg == n_usize - 1 {
74 if hub.is_some() {
75 }
80 if hub.is_none() {
81 hub = Some(v);
82 }
83 }
84 }
85
86 let Some(hub) = hub else {
87 return Ok(false);
88 };
89
90 for v in 0..n {
92 if v == hub {
93 continue;
94 }
95 if graph.degree(v)? != 3 {
96 return Ok(false);
97 }
98 }
99
100 let rim_start = u32::from(hub == 0);
104 let rim_size = n - 1;
105
106 let mut visited = vec![false; n as usize];
107 visited[hub as usize] = true;
108 visited[rim_start as usize] = true;
109
110 let first_nbrs = graph.neighbors(rim_start)?;
112 let first_rim: Vec<u32> = first_nbrs.into_iter().filter(|&w| w != hub).collect();
113 if first_rim.len() != 2 {
114 return Ok(false);
115 }
116
117 let mut current = first_rim[0];
118 visited[current as usize] = true;
119 let mut count: u32 = 2;
120
121 while count < rim_size {
122 let nbrs = graph.neighbors(current)?;
123 let rim_nbrs: Vec<u32> = nbrs
124 .into_iter()
125 .filter(|&w| w != hub && !visited[w as usize])
126 .collect();
127
128 if rim_nbrs.len() != 1 {
129 return Ok(false);
130 }
131
132 current = rim_nbrs[0];
133 visited[current as usize] = true;
134 count = count.saturating_add(1);
135 }
136
137 let last_nbrs = graph.neighbors(current)?;
139 Ok(last_nbrs.contains(&rim_start))
140}
141
142#[cfg(test)]
143mod tests {
144 use super::*;
145
146 #[test]
147 fn empty_graph() {
148 let g = Graph::with_vertices(0);
149 assert!(!is_wheel(&g).unwrap());
150 }
151
152 #[test]
153 fn three_vertices_too_small() {
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_wheel(&g).unwrap());
159 }
160
161 #[test]
162 fn w4_hub_0() {
163 let mut g = Graph::with_vertices(4);
165 g.add_edge(0, 1).unwrap();
166 g.add_edge(0, 2).unwrap();
167 g.add_edge(0, 3).unwrap();
168 g.add_edge(1, 2).unwrap();
169 g.add_edge(2, 3).unwrap();
170 g.add_edge(3, 1).unwrap();
171 assert!(is_wheel(&g).unwrap());
172 }
173
174 #[test]
175 fn w5() {
176 let mut g = Graph::with_vertices(5);
178 g.add_edge(0, 1).unwrap();
179 g.add_edge(0, 2).unwrap();
180 g.add_edge(0, 3).unwrap();
181 g.add_edge(0, 4).unwrap();
182 g.add_edge(1, 2).unwrap();
183 g.add_edge(2, 3).unwrap();
184 g.add_edge(3, 4).unwrap();
185 g.add_edge(4, 1).unwrap();
186 assert!(is_wheel(&g).unwrap());
187 }
188
189 #[test]
190 fn w6() {
191 let mut g = Graph::with_vertices(6);
193 for i in 1..6u32 {
194 g.add_edge(0, i).unwrap();
195 }
196 g.add_edge(1, 2).unwrap();
197 g.add_edge(2, 3).unwrap();
198 g.add_edge(3, 4).unwrap();
199 g.add_edge(4, 5).unwrap();
200 g.add_edge(5, 1).unwrap();
201 assert!(is_wheel(&g).unwrap());
202 }
203
204 #[test]
205 fn hub_not_vertex_0() {
206 let mut g = Graph::with_vertices(4);
208 g.add_edge(3, 0).unwrap();
209 g.add_edge(3, 1).unwrap();
210 g.add_edge(3, 2).unwrap();
211 g.add_edge(0, 1).unwrap();
212 g.add_edge(1, 2).unwrap();
213 g.add_edge(2, 0).unwrap();
214 assert!(is_wheel(&g).unwrap());
215 }
216
217 #[test]
218 fn c4_not_wheel() {
219 let mut g = Graph::with_vertices(4);
221 g.add_edge(0, 1).unwrap();
222 g.add_edge(1, 2).unwrap();
223 g.add_edge(2, 3).unwrap();
224 g.add_edge(3, 0).unwrap();
225 assert!(!is_wheel(&g).unwrap());
226 }
227
228 #[test]
229 fn star_not_wheel() {
230 let mut g = Graph::with_vertices(5);
232 for i in 1..5u32 {
233 g.add_edge(0, i).unwrap();
234 }
235 assert!(!is_wheel(&g).unwrap());
236 }
237
238 #[test]
239 fn directed_returns_false() {
240 let mut g = Graph::new(4, true).unwrap();
241 g.add_edge(0, 1).unwrap();
242 g.add_edge(0, 2).unwrap();
243 g.add_edge(0, 3).unwrap();
244 g.add_edge(1, 2).unwrap();
245 g.add_edge(2, 3).unwrap();
246 g.add_edge(3, 1).unwrap();
247 assert!(!is_wheel(&g).unwrap());
248 }
249
250 #[test]
251 fn k4_is_wheel() {
252 let mut g = Graph::with_vertices(4);
254 for u in 0..4u32 {
255 for v in (u + 1)..4 {
256 g.add_edge(u, v).unwrap();
257 }
258 }
259 assert!(is_wheel(&g).unwrap());
260 }
261
262 #[test]
263 fn k5_not_wheel() {
264 let mut g = Graph::with_vertices(5);
266 for u in 0..5u32 {
267 for v in (u + 1)..5 {
268 g.add_edge(u, v).unwrap();
269 }
270 }
271 assert!(!is_wheel(&g).unwrap());
272 }
273
274 #[test]
275 fn broken_rim_not_wheel() {
276 let mut g = Graph::with_vertices(5);
279 g.add_edge(0, 1).unwrap();
280 g.add_edge(0, 2).unwrap();
281 g.add_edge(0, 3).unwrap();
282 g.add_edge(0, 4).unwrap();
283 g.add_edge(1, 2).unwrap();
284 g.add_edge(2, 3).unwrap();
285 g.add_edge(3, 4).unwrap();
286 g.add_edge(1, 3).unwrap();
287 assert!(!is_wheel(&g).unwrap());
288 }
289
290 #[test]
291 fn petersen_not_wheel() {
292 let mut g = Graph::with_vertices(10);
294 g.add_edge(0, 1).unwrap();
295 g.add_edge(1, 2).unwrap();
296 g.add_edge(2, 3).unwrap();
297 g.add_edge(3, 4).unwrap();
298 g.add_edge(4, 0).unwrap();
299 g.add_edge(5, 7).unwrap();
300 g.add_edge(7, 9).unwrap();
301 g.add_edge(9, 6).unwrap();
302 g.add_edge(6, 8).unwrap();
303 g.add_edge(8, 5).unwrap();
304 g.add_edge(0, 5).unwrap();
305 g.add_edge(1, 6).unwrap();
306 g.add_edge(2, 7).unwrap();
307 g.add_edge(3, 8).unwrap();
308 g.add_edge(4, 9).unwrap();
309 assert!(!is_wheel(&g).unwrap());
310 }
311}