rust_igraph/algorithms/properties/
is_forest.rs1use crate::algorithms::paths::dijkstra::DijkstraMode;
32use crate::core::Graph;
33use crate::core::cache::CachedProperty;
34use crate::core::error::IgraphResult;
35use crate::core::graph::{EdgeId, VertexId};
36
37pub fn is_forest(graph: &Graph, mode: DijkstraMode) -> IgraphResult<Option<Vec<VertexId>>> {
83 let n = graph.vcount();
84 let m = graph.ecount();
85
86 if n == 0 {
88 if matches!(
89 (graph.is_directed(), mode),
90 (false, _) | (true, DijkstraMode::All)
91 ) {
92 graph.cache_set(CachedProperty::IsForest, true);
93 }
94 return Ok(Some(Vec::new()));
95 }
96
97 if m == 0 {
99 if matches!(
100 (graph.is_directed(), mode),
101 (false, _) | (true, DijkstraMode::All)
102 ) {
103 graph.cache_set(CachedProperty::IsForest, true);
104 }
105 return Ok(Some((0..n).collect()));
106 }
107
108 let directed = graph.is_directed();
109 let effective_mode = if directed { mode } else { DijkstraMode::All };
110 let cache_eligible = matches!(effective_mode, DijkstraMode::All);
111
112 if cache_eligible {
113 if let Some(false) = graph.cache_get(CachedProperty::IsForest) {
114 return Ok(None);
115 }
116 }
117
118 if m as u64 > u64::from(n).saturating_sub(1) {
120 if cache_eligible {
121 graph.cache_set(CachedProperty::IsForest, false);
122 }
123 return Ok(None);
124 }
125
126 let n_us = n as usize;
127 let mut visited = vec![false; n_us];
128 let mut visited_count: u32 = 0;
129
130 let roots_opt = collect_roots_and_traverse(
131 graph,
132 n,
133 effective_mode,
134 directed,
135 &mut visited,
136 &mut visited_count,
137 )?;
138
139 let Some(roots) = roots_opt else {
140 if cache_eligible {
141 graph.cache_set(CachedProperty::IsForest, false);
142 }
143 return Ok(None);
144 };
145
146 if visited_count == n {
148 if cache_eligible {
149 graph.cache_set(CachedProperty::IsForest, true);
150 }
151 Ok(Some(roots))
152 } else {
153 if cache_eligible {
154 graph.cache_set(CachedProperty::IsForest, false);
155 }
156 Ok(None)
157 }
158}
159
160fn collect_roots_and_traverse(
165 graph: &Graph,
166 n: VertexId,
167 mode: DijkstraMode,
168 directed: bool,
169 visited: &mut [bool],
170 visited_count: &mut u32,
171) -> IgraphResult<Option<Vec<VertexId>>> {
172 let mut roots: Vec<VertexId> = Vec::new();
173
174 match mode {
175 DijkstraMode::All => {
176 for v in 0..n {
177 if !visited[v as usize] {
178 roots.push(v);
179 if !is_forest_visitor(graph, v, mode, directed, visited, visited_count)? {
180 return Ok(None);
181 }
182 }
183 }
184 }
185 DijkstraMode::Out | DijkstraMode::In => {
186 let use_in_degree = matches!(mode, DijkstraMode::Out);
190 for v in 0..n {
191 let d = counter_degree(graph, v, use_in_degree)?;
192 if d > 1 {
193 return Ok(None);
194 }
195 if d == 0 {
196 roots.push(v);
197 if !is_forest_visitor(graph, v, mode, directed, visited, visited_count)? {
198 return Ok(None);
199 }
200 }
201 }
202 }
203 }
204
205 Ok(Some(roots))
206}
207
208fn counter_degree(graph: &Graph, v: VertexId, use_in_degree: bool) -> IgraphResult<usize> {
214 if use_in_degree {
215 Ok(graph.incident_in(v)?.len())
216 } else {
217 Ok(graph.incident(v)?.len())
218 }
219}
220
221fn is_forest_visitor(
228 graph: &Graph,
229 root: VertexId,
230 mode: DijkstraMode,
231 directed: bool,
232 visited: &mut [bool],
233 visited_count: &mut u32,
234) -> IgraphResult<bool> {
235 let mut stack: Vec<VertexId> = Vec::new();
236 stack.push(root);
237
238 while let Some(u) = stack.pop() {
239 if visited[u as usize] {
240 return Ok(false);
244 }
245 visited[u as usize] = true;
246 *visited_count = visited_count.saturating_add(1);
247
248 for eid in incident_edges(graph, u, mode, directed)? {
249 let nei = graph.edge_other(eid, u)?;
250 match mode {
251 DijkstraMode::All => {
252 if !visited[nei as usize] {
253 stack.push(nei);
254 } else if nei == u {
255 return Ok(false);
257 }
258 }
260 DijkstraMode::Out | DijkstraMode::In => {
261 stack.push(nei);
264 }
265 }
266 }
267 }
268
269 Ok(true)
270}
271
272fn incident_edges(
275 graph: &Graph,
276 u: VertexId,
277 mode: DijkstraMode,
278 directed: bool,
279) -> IgraphResult<Vec<EdgeId>> {
280 match mode {
281 DijkstraMode::Out => graph.incident(u),
282 DijkstraMode::In => graph.incident_in(u),
283 DijkstraMode::All => {
284 if directed {
285 let mut out = graph.incident(u)?;
286 out.extend(graph.incident_in(u)?);
287 Ok(out)
288 } else {
289 graph.incident(u)
290 }
291 }
292 }
293}
294
295#[cfg(test)]
296mod tests {
297 use super::*;
298
299 #[test]
302 fn null_graph_is_forest_with_empty_roots() {
303 let g = Graph::with_vertices(0);
304 let roots = is_forest(&g, DijkstraMode::All).unwrap().unwrap();
305 assert!(roots.is_empty());
306 }
307
308 #[test]
309 fn no_edges_undirected_every_vertex_is_a_root() {
310 let g = Graph::with_vertices(4);
311 let roots = is_forest(&g, DijkstraMode::All).unwrap().unwrap();
312 assert_eq!(roots, vec![0, 1, 2, 3]);
313 }
314
315 #[test]
316 fn no_edges_directed_out_every_vertex_is_a_root() {
317 let g = Graph::new(3, true).unwrap();
318 let roots = is_forest(&g, DijkstraMode::Out).unwrap().unwrap();
319 assert_eq!(roots, vec![0, 1, 2]);
320 }
321
322 #[test]
325 fn undirected_path_is_forest_one_root() {
326 let mut g = Graph::with_vertices(4);
327 g.add_edges(vec![(0u32, 1u32), (1, 2), (2, 3)]).unwrap();
328 let roots = is_forest(&g, DijkstraMode::All).unwrap().unwrap();
329 assert_eq!(roots, vec![0]);
330 }
331
332 #[test]
333 fn undirected_two_components_each_a_tree() {
334 let mut g = Graph::with_vertices(5);
335 g.add_edges(vec![(0u32, 1u32), (2, 3), (3, 4)]).unwrap();
336 let roots = is_forest(&g, DijkstraMode::All).unwrap().unwrap();
337 assert_eq!(roots, vec![0, 2]);
338 }
339
340 #[test]
341 fn undirected_triangle_is_not_a_forest() {
342 let mut g = Graph::with_vertices(3);
343 g.add_edges(vec![(0u32, 1u32), (1, 2), (2, 0)]).unwrap();
344 assert!(is_forest(&g, DijkstraMode::All).unwrap().is_none());
345 }
346
347 #[test]
348 fn undirected_self_loop_breaks_forest() {
349 let mut g = Graph::with_vertices(3);
351 g.add_edge(0, 0).unwrap();
352 g.add_edge(1, 2).unwrap();
353 assert!(is_forest(&g, DijkstraMode::All).unwrap().is_none());
356 }
357
358 #[test]
359 fn undirected_parallel_edges_break_forest() {
360 let mut g = Graph::with_vertices(2);
362 g.add_edge(0, 1).unwrap();
363 g.add_edge(0, 1).unwrap();
364 assert!(is_forest(&g, DijkstraMode::All).unwrap().is_none());
366 }
367
368 #[test]
371 fn directed_out_two_arborescences_are_a_forest() {
372 let mut g = Graph::new(4, true).unwrap();
373 g.add_edges(vec![(0u32, 1u32), (2, 3)]).unwrap();
374 let roots = is_forest(&g, DijkstraMode::Out).unwrap().unwrap();
375 assert_eq!(roots, vec![0, 2]);
376 }
377
378 #[test]
379 fn directed_out_v_pattern_is_not_an_out_forest() {
380 let mut g = Graph::new(3, true).unwrap();
382 g.add_edges(vec![(0u32, 2u32), (1, 2)]).unwrap();
383 assert!(is_forest(&g, DijkstraMode::Out).unwrap().is_none());
384 }
385
386 #[test]
387 fn directed_in_two_anti_arborescences_are_a_forest() {
388 let mut g = Graph::new(4, true).unwrap();
390 g.add_edges(vec![(1u32, 0u32), (3, 2)]).unwrap();
391 let roots = is_forest(&g, DijkstraMode::In).unwrap().unwrap();
392 assert_eq!(roots, vec![0, 2]);
393 }
394
395 #[test]
396 fn directed_cycle_is_not_a_forest_in_any_mode() {
397 let mut g = Graph::new(3, true).unwrap();
398 g.add_edges(vec![(0u32, 1u32), (1, 2), (2, 0)]).unwrap();
399 assert!(is_forest(&g, DijkstraMode::Out).unwrap().is_none());
400 assert!(is_forest(&g, DijkstraMode::In).unwrap().is_none());
401 assert!(is_forest(&g, DijkstraMode::All).unwrap().is_none());
402 }
403
404 #[test]
405 fn directed_all_mode_treats_as_undirected() {
406 let mut g = Graph::new(3, true).unwrap();
408 g.add_edges(vec![(0u32, 2u32), (1, 2)]).unwrap();
409 assert!(is_forest(&g, DijkstraMode::Out).unwrap().is_none());
410 let roots = is_forest(&g, DijkstraMode::All).unwrap().unwrap();
411 assert_eq!(roots, vec![0]);
412 }
413
414 #[test]
417 fn too_many_edges_is_not_a_forest() {
418 let mut g = Graph::with_vertices(5);
420 g.add_edges(vec![(0u32, 1u32), (1, 2), (2, 3), (3, 4), (4, 0)])
421 .unwrap();
422 assert!(is_forest(&g, DijkstraMode::All).unwrap().is_none());
423 }
424
425 #[test]
426 fn correct_edge_count_but_disconnected_with_cycle_is_not_a_forest() {
427 let mut g = Graph::with_vertices(4);
431 g.add_edge(0, 1).unwrap();
432 g.add_edge(0, 1).unwrap();
433 g.add_edge(2, 3).unwrap();
434 assert!(is_forest(&g, DijkstraMode::All).unwrap().is_none());
435 }
436
437 #[test]
440 fn single_tree_is_also_a_forest() {
441 let mut g = Graph::with_vertices(4);
442 g.add_edges(vec![(0u32, 1u32), (0, 2), (0, 3)]).unwrap();
443 let roots = is_forest(&g, DijkstraMode::All).unwrap().unwrap();
444 assert_eq!(roots, vec![0]);
445 }
446
447 #[test]
448 fn single_out_arborescence_is_also_an_out_forest() {
449 let mut g = Graph::new(4, true).unwrap();
450 g.add_edges(vec![(0u32, 1u32), (0, 2), (1, 3)]).unwrap();
451 let roots = is_forest(&g, DijkstraMode::Out).unwrap().unwrap();
452 assert_eq!(roots, vec![0]);
453 }
454}