rust_igraph/algorithms/paths/
eulerian_construct.rs1use crate::algorithms::paths::eulerian::is_eulerian;
13use crate::core::graph::EdgeId;
14use crate::core::{Graph, IgraphError, IgraphResult, VertexId};
15
16pub fn eulerian_cycle(graph: &Graph) -> IgraphResult<Vec<EdgeId>> {
45 let cls = is_eulerian(graph)?;
46 if !cls.has_cycle {
47 return Err(IgraphError::InvalidArgument(
48 "the graph does not have an Eulerian cycle".to_string(),
49 ));
50 }
51
52 let m = graph.ecount();
53 if m == 0 {
54 return Ok(Vec::new());
55 }
56
57 match eulerian_path(graph)? {
61 Some(edges) => Ok(edges),
62 None => Err(IgraphError::Internal(
63 "has_cycle is true but eulerian_path returned None",
64 )),
65 }
66}
67
68pub fn eulerian_path(graph: &Graph) -> IgraphResult<Option<Vec<EdgeId>>> {
106 let cls = is_eulerian(graph)?;
107 if !cls.has_path {
108 return Ok(None);
109 }
110
111 let n = graph.vcount();
112 let m = graph.ecount();
113 if m == 0 {
114 return Ok(Some(Vec::new()));
116 }
117
118 let n_us = n as usize;
119 let m_us = m;
120 let directed = graph.is_directed();
121
122 let mut inc: Vec<Vec<EdgeId>> = Vec::with_capacity(n_us);
128 for v in 0..n {
129 let raw = graph.incident(v)?;
130 if directed {
131 inc.push(raw);
133 } else {
134 let mut seen: std::collections::HashSet<EdgeId> = std::collections::HashSet::new();
135 let mut out: Vec<EdgeId> = Vec::with_capacity(raw.len());
136 for e in raw {
137 if seen.insert(e) {
138 out.push(e);
139 }
140 }
141 inc.push(out);
142 }
143 }
144
145 let mut visited: Vec<bool> = vec![false; m_us];
150 let mut next_idx: Vec<usize> = vec![0; n_us];
151
152 let start_of_path = pick_start_vertex(graph, cls)?;
156
157 let mut tracker: Vec<VertexId> = Vec::with_capacity(n_us);
160 let mut edge_tracker: Vec<EdgeId> = Vec::with_capacity(m_us);
161 let mut path: Vec<VertexId> = Vec::with_capacity(n_us);
162 let mut edge_path: Vec<EdgeId> = Vec::with_capacity(m_us);
163
164 tracker.push(start_of_path);
165 let mut curr = start_of_path;
166
167 loop {
168 let curr_us = curr as usize;
170 while next_idx[curr_us] < inc[curr_us].len()
172 && visited[inc[curr_us][next_idx[curr_us]] as usize]
173 {
174 next_idx[curr_us] += 1;
175 }
176 if next_idx[curr_us] < inc[curr_us].len() {
177 let edge = inc[curr_us][next_idx[curr_us]];
178 visited[edge as usize] = true;
179 next_idx[curr_us] += 1;
180 tracker.push(curr);
181 edge_tracker.push(edge);
182 curr = if directed {
183 graph.edge_target(edge)?
185 } else {
186 graph.edge_other(edge, curr)?
187 };
188 } else {
189 path.push(curr);
191 if let Some(prev) = tracker.pop() {
192 if let Some(curr_e) = edge_tracker.pop() {
193 edge_path.push(curr_e);
194 }
195 curr = prev;
196 } else {
197 break;
198 }
199 }
200 }
201
202 edge_path.reverse();
206 let _ = path; Ok(Some(edge_path))
209}
210
211fn pick_start_vertex(
212 graph: &Graph,
213 cls: crate::algorithms::paths::eulerian::EulerianClassification,
214) -> IgraphResult<VertexId> {
215 let n = graph.vcount();
216 let directed = graph.is_directed();
217 if cls.has_cycle {
218 for v in 0..n {
220 let has_edges = if directed {
221 !graph.incident(v)?.is_empty()
222 } else {
223 !graph.neighbors(v)?.is_empty()
224 };
225 if has_edges {
226 return Ok(v);
227 }
228 }
229 Err(IgraphError::Internal("no edges but cls.has_cycle"))
231 } else if directed {
232 for v in 0..n {
235 let out_deg = graph.incident(v)?.len();
236 let in_deg = compute_in_degree(graph, v)?;
238 if out_deg > in_deg && (out_deg - in_deg) == 1 {
239 return Ok(v);
240 }
241 }
242 Err(IgraphError::Internal(
243 "directed path: no vertex with outgoing_excess == 1",
244 ))
245 } else {
246 for v in 0..n {
248 let deg = graph.degree(v)?;
249 if deg % 2 != 0 {
250 return Ok(v);
251 }
252 }
253 Err(IgraphError::Internal(
254 "has_path && !has_cycle but no odd-degree vertex found",
255 ))
256 }
257}
258
259fn compute_in_degree(graph: &Graph, v: VertexId) -> IgraphResult<usize> {
260 let mut count = 0usize;
264 let m = u32::try_from(graph.ecount()).expect("edge count fits in u32");
265 for e in 0..m {
266 if graph.edge_target(e)? == v {
267 count += 1;
268 }
269 }
270 Ok(count)
271}
272
273#[cfg(test)]
274mod tests {
275 use super::*;
276
277 fn walk_validates(graph: &Graph, walk: &[EdgeId]) -> bool {
278 let mut seen: Vec<bool> = vec![false; graph.ecount()];
280 for &edge in walk {
281 let idx = edge as usize;
282 if idx >= graph.ecount() || seen[idx] {
283 return false;
284 }
285 seen[idx] = true;
286 }
287 if walk.len() < 2 {
289 return true;
290 }
291 for i in 0..walk.len() - 1 {
292 let (a, b) = graph.edge(walk[i]).unwrap();
293 let (c, d) = graph.edge(walk[i + 1]).unwrap();
294 if !(a == c || a == d || b == c || b == d) {
295 return false;
296 }
297 }
298 true
299 }
300
301 #[test]
302 fn empty_graph_returns_empty_walk() {
303 let g = Graph::with_vertices(0);
304 assert_eq!(eulerian_path(&g).unwrap(), Some(Vec::new()));
305 }
306
307 #[test]
308 fn isolated_vertices_return_empty_walk() {
309 let g = Graph::with_vertices(5);
310 assert_eq!(eulerian_path(&g).unwrap(), Some(Vec::new()));
311 }
312
313 #[test]
314 fn triangle_yields_three_edge_walk() {
315 let mut g = Graph::with_vertices(3);
316 g.add_edge(0, 1).unwrap();
317 g.add_edge(1, 2).unwrap();
318 g.add_edge(2, 0).unwrap();
319 let walk = eulerian_path(&g).unwrap().unwrap();
320 assert_eq!(walk.len(), 3);
321 assert!(walk_validates(&g, &walk));
322 }
323
324 #[test]
325 fn path_3_yields_two_edge_walk() {
326 let mut g = Graph::with_vertices(3);
327 g.add_edge(0, 1).unwrap();
328 g.add_edge(1, 2).unwrap();
329 let walk = eulerian_path(&g).unwrap().unwrap();
330 assert_eq!(walk.len(), 2);
331 assert!(walk_validates(&g, &walk));
332 }
333
334 #[test]
335 fn k4_has_no_eulerian_walk() {
336 let mut g = Graph::with_vertices(4);
337 for u in 0..4u32 {
338 for v in (u + 1)..4 {
339 g.add_edge(u, v).unwrap();
340 }
341 }
342 assert_eq!(eulerian_path(&g).unwrap(), None);
343 }
344
345 #[test]
346 fn disconnected_components_no_walk() {
347 let mut g = Graph::with_vertices(4);
348 g.add_edge(0, 1).unwrap();
349 g.add_edge(2, 3).unwrap();
350 assert_eq!(eulerian_path(&g).unwrap(), None);
351 }
352
353 #[test]
354 fn ring_5_walk_visits_all_edges() {
355 let mut g = Graph::with_vertices(5);
356 for i in 0..5u32 {
357 g.add_edge(i, (i + 1) % 5).unwrap();
358 }
359 let walk = eulerian_path(&g).unwrap().unwrap();
360 assert_eq!(walk.len(), 5);
361 assert!(walk_validates(&g, &walk));
362 }
363
364 #[test]
365 fn directed_3_cycle_yields_3_edge_walk() {
366 let mut g = Graph::new(3, true).unwrap();
368 g.add_edge(0, 1).unwrap();
369 g.add_edge(1, 2).unwrap();
370 g.add_edge(2, 0).unwrap();
371 let walk = eulerian_path(&g).unwrap().unwrap();
372 assert_eq!(walk.len(), 3);
373 }
374
375 #[test]
376 fn directed_path_yields_chain_walk() {
377 let mut g = Graph::new(3, true).unwrap();
379 g.add_edge(0, 1).unwrap();
380 g.add_edge(1, 2).unwrap();
381 let walk = eulerian_path(&g).unwrap().unwrap();
382 assert_eq!(walk, vec![0, 1]); }
384
385 #[test]
386 fn directed_no_eulerian_returns_none() {
387 let mut g = Graph::new(3, true).unwrap();
389 g.add_edge(0, 1).unwrap();
390 g.add_edge(1, 2).unwrap();
391 g.add_edge(0, 2).unwrap();
392 assert_eq!(eulerian_path(&g).unwrap(), None);
393 }
394
395 #[test]
398 fn cycle_triangle() {
399 let mut g = Graph::with_vertices(3);
400 g.add_edge(0, 1).unwrap();
401 g.add_edge(1, 2).unwrap();
402 g.add_edge(2, 0).unwrap();
403 let cycle = eulerian_cycle(&g).unwrap();
404 assert_eq!(cycle.len(), 3);
405 assert!(walk_validates(&g, &cycle));
406 }
407
408 #[test]
409 fn cycle_ring5() {
410 let mut g = Graph::with_vertices(5);
411 for i in 0..5u32 {
412 g.add_edge(i, (i + 1) % 5).unwrap();
413 }
414 let cycle = eulerian_cycle(&g).unwrap();
415 assert_eq!(cycle.len(), 5);
416 assert!(walk_validates(&g, &cycle));
417 }
418
419 #[test]
420 fn cycle_empty_graph() {
421 let g = Graph::with_vertices(0);
422 let cycle = eulerian_cycle(&g).unwrap();
423 assert!(cycle.is_empty());
424 }
425
426 #[test]
427 fn cycle_isolated_vertices() {
428 let g = Graph::with_vertices(4);
429 let cycle = eulerian_cycle(&g).unwrap();
430 assert!(cycle.is_empty());
431 }
432
433 #[test]
434 fn cycle_path_graph_errors() {
435 let mut g = Graph::with_vertices(3);
437 g.add_edge(0, 1).unwrap();
438 g.add_edge(1, 2).unwrap();
439 assert!(eulerian_cycle(&g).is_err());
440 }
441
442 #[test]
443 fn cycle_k4_errors() {
444 let mut g = Graph::with_vertices(4);
446 for u in 0..4u32 {
447 for v in (u + 1)..4 {
448 g.add_edge(u, v).unwrap();
449 }
450 }
451 assert!(eulerian_cycle(&g).is_err());
452 }
453
454 #[test]
455 fn cycle_directed_3cycle() {
456 let mut g = Graph::new(3, true).unwrap();
457 g.add_edge(0, 1).unwrap();
458 g.add_edge(1, 2).unwrap();
459 g.add_edge(2, 0).unwrap();
460 let cycle = eulerian_cycle(&g).unwrap();
461 assert_eq!(cycle.len(), 3);
462 }
463
464 #[test]
465 fn cycle_directed_path_errors() {
466 let mut g = Graph::new(3, true).unwrap();
468 g.add_edge(0, 1).unwrap();
469 g.add_edge(1, 2).unwrap();
470 assert!(eulerian_cycle(&g).is_err());
471 }
472
473 #[test]
474 fn complex_eulerian_path_test_eulerian_r() {
475 let mut g = Graph::with_vertices(6);
478 for &(u, v) in &[
479 (0, 1),
480 (1, 2),
481 (2, 3),
482 (3, 4),
483 (4, 0),
484 (0, 5),
485 (5, 3),
486 (3, 1),
487 (1, 5),
488 (5, 4),
489 ] {
490 g.add_edge(u, v).unwrap();
491 }
492 let walk = eulerian_path(&g).unwrap().unwrap();
493 assert_eq!(walk.len(), 10, "must visit every edge exactly once");
494 assert!(walk_validates(&g, &walk));
495 }
496}