rust_igraph/algorithms/properties/
is_acyclic.rs1use crate::algorithms::properties::is_dag::is_dag;
15use crate::core::Graph;
16
17#[must_use]
54pub fn is_acyclic(graph: &Graph) -> bool {
55 if graph.is_directed() {
56 return is_dag(graph);
57 }
58
59 let n = graph.vcount();
60 let m = graph.ecount();
61 let n_us = n as usize;
62
63 if n == 0 || m == 0 {
65 return true;
66 }
67
68 let mut parent: Vec<u32> = (0..n).collect();
70 let mut size: Vec<u32> = vec![1; n_us];
71
72 let Ok(m_u32) = u32::try_from(m) else {
73 return false;
77 };
78 for eid in 0..m_u32 {
79 let Ok((u, v)) = graph.edge(eid) else {
82 return false;
83 };
84 if u == v {
85 return false;
87 }
88 let mut ru = u as usize;
89 while parent[ru] as usize != ru {
90 parent[ru] = parent[parent[ru] as usize];
91 ru = parent[ru] as usize;
92 }
93 let mut rv = v as usize;
94 while parent[rv] as usize != rv {
95 parent[rv] = parent[parent[rv] as usize];
96 rv = parent[rv] as usize;
97 }
98 if ru == rv {
99 return false;
103 }
104 let (parent_idx, child_idx) = if size[ru] < size[rv] {
106 (rv, ru)
107 } else {
108 (ru, rv)
109 };
110 let Ok(parent_u32) = u32::try_from(parent_idx) else {
111 return false;
112 };
113 parent[child_idx] = parent_u32;
114 size[parent_idx] = size[parent_idx].saturating_add(size[child_idx]);
115 }
116
117 true
118}
119
120#[cfg(test)]
121mod tests {
122 use super::*;
123 use crate::core::Graph;
124
125 #[test]
128 fn empty_undirected_is_acyclic() {
129 let g = Graph::with_vertices(0);
130 assert!(is_acyclic(&g));
131 }
132
133 #[test]
134 fn isolated_vertices_only_is_acyclic() {
135 let g = Graph::with_vertices(5);
136 assert!(is_acyclic(&g));
137 }
138
139 #[test]
140 fn undirected_tree_is_acyclic() {
141 let mut g = Graph::with_vertices(4);
142 g.add_edges(vec![(0u32, 1u32), (1, 2), (2, 3)]).unwrap();
143 assert!(is_acyclic(&g));
144 }
145
146 #[test]
147 fn undirected_forest_is_acyclic() {
148 let mut g = Graph::with_vertices(5);
150 g.add_edges(vec![(0u32, 1u32), (1, 2), (3, 4)]).unwrap();
151 assert!(is_acyclic(&g));
152 }
153
154 #[test]
155 fn undirected_triangle_is_not_acyclic() {
156 let mut g = Graph::with_vertices(3);
157 g.add_edges(vec![(0u32, 1u32), (1, 2), (2, 0)]).unwrap();
158 assert!(!is_acyclic(&g));
159 }
160
161 #[test]
162 fn undirected_self_loop_is_not_acyclic() {
163 let mut g = Graph::with_vertices(2);
164 g.add_edge(0, 0).unwrap();
165 g.add_edge(0, 1).unwrap();
166 assert!(!is_acyclic(&g));
167 }
168
169 #[test]
170 fn undirected_parallel_edge_is_not_acyclic() {
171 let mut g = Graph::with_vertices(2);
173 g.add_edge(0, 1).unwrap();
174 g.add_edge(0, 1).unwrap();
175 assert!(!is_acyclic(&g));
176 }
177
178 #[test]
179 fn undirected_star_is_acyclic() {
180 let mut g = Graph::with_vertices(5);
182 g.add_edges(vec![(0u32, 1u32), (0, 2), (0, 3), (0, 4)])
183 .unwrap();
184 assert!(is_acyclic(&g));
185 }
186
187 #[test]
190 fn directed_dag_chain_is_acyclic() {
191 let mut g = Graph::new(3, true).unwrap();
192 g.add_edges(vec![(0u32, 1u32), (1, 2)]).unwrap();
193 assert!(is_acyclic(&g));
194 }
195
196 #[test]
197 fn directed_cycle_is_not_acyclic() {
198 let mut g = Graph::new(3, true).unwrap();
199 g.add_edges(vec![(0u32, 1u32), (1, 2), (2, 0)]).unwrap();
200 assert!(!is_acyclic(&g));
201 }
202
203 #[test]
204 fn directed_self_loop_is_not_acyclic() {
205 let mut g = Graph::new(2, true).unwrap();
206 g.add_edge(0, 0).unwrap();
207 g.add_edge(0, 1).unwrap();
208 assert!(!is_acyclic(&g));
209 }
210
211 #[test]
212 fn directed_diamond_dag_is_acyclic() {
213 let mut g = Graph::new(4, true).unwrap();
215 g.add_edges(vec![(0u32, 1u32), (0, 2), (1, 3), (2, 3)])
216 .unwrap();
217 assert!(is_acyclic(&g));
218 }
219
220 #[test]
221 fn undirected_no_edges_with_many_vertices_is_acyclic() {
222 let g = Graph::with_vertices(100);
223 assert!(is_acyclic(&g));
224 }
225}