1use crate::core::{Graph, IgraphResult, VertexId};
7
8#[derive(Debug, Clone, PartialEq, Eq)]
10pub struct CycleResult {
11 pub vertices: Vec<VertexId>,
13 pub edges: Vec<u32>,
15}
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub enum CycleMode {
20 Out,
22 In,
24 All,
26}
27
28pub fn find_cycle(graph: &Graph, mode: CycleMode) -> IgraphResult<CycleResult> {
61 let n = graph.vcount();
62 let ecount = graph.ecount();
63
64 if ecount == 0 {
65 return Ok(CycleResult {
66 vertices: Vec::new(),
67 edges: Vec::new(),
68 });
69 }
70
71 let effective_mode = if graph.is_directed() {
72 mode
73 } else {
74 CycleMode::All
75 };
76
77 let inc = build_incidence(graph, effective_mode)?;
78
79 let mut seen: Vec<u8> = vec![0; n as usize];
82 let mut vpath: Vec<VertexId> = Vec::new();
83 let mut epath: Vec<u32> = Vec::new();
84
85 let mut stack: Vec<StackEntry> = Vec::new();
89
90 for start in 0..n {
91 if seen[start as usize] != 0 {
92 continue;
93 }
94
95 stack.push(StackEntry::Visit {
96 vertex: start,
97 edge: u32::MAX,
98 });
99
100 while let Some(entry) = stack.pop() {
101 match entry {
102 StackEntry::Backtrack => {
103 if let Some(v) = vpath.pop() {
104 seen[v as usize] = 2;
105 }
106 epath.pop();
107 }
108 StackEntry::Visit {
109 vertex: va,
110 edge: ea,
111 } => {
112 if seen[va as usize] == 1 {
113 let cycle = extract_cycle(&vpath, &epath, va, ea);
115 return Ok(cycle);
116 }
117 if seen[va as usize] == 2 {
118 continue;
119 }
120
121 seen[va as usize] = 1;
123 vpath.push(va);
124 epath.push(ea);
125
126 stack.push(StackEntry::Backtrack);
128
129 for &(nb, eid) in &inc[va as usize] {
131 if eid == ea {
132 continue;
133 }
134 if seen[nb as usize] == 2 {
135 continue;
136 }
137 stack.push(StackEntry::Visit {
138 vertex: nb,
139 edge: eid,
140 });
141 }
142 }
143 }
144 }
145 }
146
147 Ok(CycleResult {
148 vertices: Vec::new(),
149 edges: Vec::new(),
150 })
151}
152
153#[derive(Debug)]
154enum StackEntry {
155 Backtrack,
156 Visit { vertex: VertexId, edge: u32 },
157}
158
159fn extract_cycle(
160 vpath: &[VertexId],
161 epath: &[u32],
162 target: VertexId,
163 closing_edge: u32,
164) -> CycleResult {
165 let start_idx = vpath.iter().position(|&v| v == target).unwrap_or(0);
167
168 let mut vertices: Vec<VertexId> = vpath[start_idx..].to_vec();
169 vertices.push(target);
170
171 let mut edges: Vec<u32> = epath[(start_idx + 1)..].to_vec();
172 edges.push(closing_edge);
173
174 CycleResult { vertices, edges }
175}
176
177fn build_incidence(graph: &Graph, mode: CycleMode) -> IgraphResult<Vec<Vec<(VertexId, u32)>>> {
179 let n = graph.vcount() as usize;
180 let ecount = graph.ecount();
181 let directed = graph.is_directed();
182 let mut inc: Vec<Vec<(VertexId, u32)>> = vec![Vec::new(); n];
183
184 for eid in 0..ecount {
185 #[allow(clippy::cast_possible_truncation)]
186 let eid_u32 = eid as u32;
187 let (src, tgt) = graph.edge(eid_u32)?;
188
189 if !directed || mode == CycleMode::All {
190 inc[src as usize].push((tgt, eid_u32));
191 if src != tgt {
192 inc[tgt as usize].push((src, eid_u32));
193 }
194 } else if mode == CycleMode::Out {
195 inc[src as usize].push((tgt, eid_u32));
196 } else {
197 inc[tgt as usize].push((src, eid_u32));
199 }
200 }
201
202 Ok(inc)
203}
204
205#[cfg(test)]
206mod tests {
207 use super::*;
208
209 #[test]
210 fn test_empty_graph() {
211 let g = Graph::with_vertices(0);
212 let r = find_cycle(&g, CycleMode::All).unwrap();
213 assert!(r.vertices.is_empty());
214 assert!(r.edges.is_empty());
215 }
216
217 #[test]
218 fn test_no_edges() {
219 let g = Graph::with_vertices(5);
220 let r = find_cycle(&g, CycleMode::All).unwrap();
221 assert!(r.vertices.is_empty());
222 }
223
224 #[test]
225 fn test_tree_no_cycle() {
226 let mut g = Graph::with_vertices(5);
227 g.add_edge(0, 1).unwrap();
228 g.add_edge(0, 2).unwrap();
229 g.add_edge(1, 3).unwrap();
230 g.add_edge(1, 4).unwrap();
231 let r = find_cycle(&g, CycleMode::All).unwrap();
232 assert!(r.vertices.is_empty());
233 }
234
235 #[test]
236 fn test_triangle_undirected() {
237 let mut g = Graph::with_vertices(3);
238 g.add_edge(0, 1).unwrap();
239 g.add_edge(1, 2).unwrap();
240 g.add_edge(0, 2).unwrap();
241 let r = find_cycle(&g, CycleMode::All).unwrap();
242 assert!(!r.vertices.is_empty());
243 assert_eq!(r.vertices.len(), r.edges.len() + 1);
245 assert_eq!(*r.vertices.first().unwrap(), *r.vertices.last().unwrap());
246 }
247
248 #[test]
249 fn test_directed_cycle() {
250 let mut g = Graph::new(3, true).unwrap();
251 g.add_edge(0, 1).unwrap();
252 g.add_edge(1, 2).unwrap();
253 g.add_edge(2, 0).unwrap();
254 let r = find_cycle(&g, CycleMode::Out).unwrap();
255 assert!(!r.vertices.is_empty());
256 assert_eq!(r.vertices.first(), r.vertices.last());
257 }
258
259 #[test]
260 fn test_dag_no_cycle() {
261 let mut g = Graph::new(4, true).unwrap();
262 g.add_edge(0, 1).unwrap();
263 g.add_edge(0, 2).unwrap();
264 g.add_edge(1, 3).unwrap();
265 g.add_edge(2, 3).unwrap();
266 let r = find_cycle(&g, CycleMode::Out).unwrap();
267 assert!(r.vertices.is_empty());
268 }
269
270 #[test]
271 fn test_directed_cycle_in_mode() {
272 let mut g = Graph::new(3, true).unwrap();
274 g.add_edge(0, 1).unwrap();
275 g.add_edge(1, 2).unwrap();
276 g.add_edge(2, 0).unwrap();
277 let r = find_cycle(&g, CycleMode::In).unwrap();
278 assert!(!r.vertices.is_empty());
279 assert_eq!(r.vertices.first(), r.vertices.last());
280 }
281
282 #[test]
283 fn test_self_loop() {
284 let mut g = Graph::with_vertices(3);
285 g.add_edge(0, 1).unwrap();
286 g.add_edge(1, 1).unwrap();
287 let r = find_cycle(&g, CycleMode::All).unwrap();
290 assert!(!r.vertices.is_empty());
291 }
292
293 #[test]
294 fn test_undirected_4_cycle() {
295 let mut g = Graph::with_vertices(4);
296 g.add_edge(0, 1).unwrap();
297 g.add_edge(1, 2).unwrap();
298 g.add_edge(2, 3).unwrap();
299 g.add_edge(3, 0).unwrap();
300 let r = find_cycle(&g, CycleMode::All).unwrap();
301 assert!(!r.vertices.is_empty());
302 assert_eq!(r.vertices.first(), r.vertices.last());
303 assert!(r.vertices.len() >= 4);
305 }
306
307 #[test]
308 fn test_disconnected_one_has_cycle() {
309 let mut g = Graph::with_vertices(6);
310 g.add_edge(0, 1).unwrap();
312 g.add_edge(1, 2).unwrap();
313 g.add_edge(3, 4).unwrap();
315 g.add_edge(4, 5).unwrap();
316 g.add_edge(3, 5).unwrap();
317 let r = find_cycle(&g, CycleMode::All).unwrap();
318 assert!(!r.vertices.is_empty());
319 }
320
321 #[test]
322 fn test_cycle_edges_valid() {
323 let mut g = Graph::new(4, true).unwrap();
324 g.add_edge(0, 1).unwrap(); g.add_edge(1, 2).unwrap(); g.add_edge(2, 3).unwrap(); g.add_edge(3, 1).unwrap(); let r = find_cycle(&g, CycleMode::Out).unwrap();
329 assert!(!r.vertices.is_empty());
330 for &eid in &r.edges {
332 assert!(graph_has_edge(&g, eid));
333 }
334 }
335
336 fn graph_has_edge(g: &Graph, eid: u32) -> bool {
337 g.edge(eid).is_ok()
338 }
339}