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_iter(v)?.len() > 0
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())
265 .map_err(|_| IgraphError::InvalidArgument("edge count exceeds u32 range".into()))?;
266 for e in 0..m {
267 if graph.edge_target(e)? == v {
268 count += 1;
269 }
270 }
271 Ok(count)
272}
273
274#[cfg(test)]
275mod tests {
276 use super::*;
277
278 fn walk_validates(graph: &Graph, walk: &[EdgeId]) -> bool {
279 let mut seen: Vec<bool> = vec![false; graph.ecount()];
281 for &edge in walk {
282 let idx = edge as usize;
283 if idx >= graph.ecount() || seen[idx] {
284 return false;
285 }
286 seen[idx] = true;
287 }
288 if walk.len() < 2 {
290 return true;
291 }
292 for i in 0..walk.len() - 1 {
293 let (a, b) = graph.edge(walk[i]).unwrap();
294 let (c, d) = graph.edge(walk[i + 1]).unwrap();
295 if !(a == c || a == d || b == c || b == d) {
296 return false;
297 }
298 }
299 true
300 }
301
302 #[test]
303 fn empty_graph_returns_empty_walk() {
304 let g = Graph::with_vertices(0);
305 assert_eq!(eulerian_path(&g).unwrap(), Some(Vec::new()));
306 }
307
308 #[test]
309 fn isolated_vertices_return_empty_walk() {
310 let g = Graph::with_vertices(5);
311 assert_eq!(eulerian_path(&g).unwrap(), Some(Vec::new()));
312 }
313
314 #[test]
315 fn triangle_yields_three_edge_walk() {
316 let mut g = Graph::with_vertices(3);
317 g.add_edge(0, 1).unwrap();
318 g.add_edge(1, 2).unwrap();
319 g.add_edge(2, 0).unwrap();
320 let walk = eulerian_path(&g).unwrap().unwrap();
321 assert_eq!(walk.len(), 3);
322 assert!(walk_validates(&g, &walk));
323 }
324
325 #[test]
326 fn path_3_yields_two_edge_walk() {
327 let mut g = Graph::with_vertices(3);
328 g.add_edge(0, 1).unwrap();
329 g.add_edge(1, 2).unwrap();
330 let walk = eulerian_path(&g).unwrap().unwrap();
331 assert_eq!(walk.len(), 2);
332 assert!(walk_validates(&g, &walk));
333 }
334
335 #[test]
336 fn k4_has_no_eulerian_walk() {
337 let mut g = Graph::with_vertices(4);
338 for u in 0..4u32 {
339 for v in (u + 1)..4 {
340 g.add_edge(u, v).unwrap();
341 }
342 }
343 assert_eq!(eulerian_path(&g).unwrap(), None);
344 }
345
346 #[test]
347 fn disconnected_components_no_walk() {
348 let mut g = Graph::with_vertices(4);
349 g.add_edge(0, 1).unwrap();
350 g.add_edge(2, 3).unwrap();
351 assert_eq!(eulerian_path(&g).unwrap(), None);
352 }
353
354 #[test]
355 fn ring_5_walk_visits_all_edges() {
356 let mut g = Graph::with_vertices(5);
357 for i in 0..5u32 {
358 g.add_edge(i, (i + 1) % 5).unwrap();
359 }
360 let walk = eulerian_path(&g).unwrap().unwrap();
361 assert_eq!(walk.len(), 5);
362 assert!(walk_validates(&g, &walk));
363 }
364
365 #[test]
366 fn directed_3_cycle_yields_3_edge_walk() {
367 let mut g = Graph::new(3, true).unwrap();
369 g.add_edge(0, 1).unwrap();
370 g.add_edge(1, 2).unwrap();
371 g.add_edge(2, 0).unwrap();
372 let walk = eulerian_path(&g).unwrap().unwrap();
373 assert_eq!(walk.len(), 3);
374 }
375
376 #[test]
377 fn directed_path_yields_chain_walk() {
378 let mut g = Graph::new(3, true).unwrap();
380 g.add_edge(0, 1).unwrap();
381 g.add_edge(1, 2).unwrap();
382 let walk = eulerian_path(&g).unwrap().unwrap();
383 assert_eq!(walk, vec![0, 1]); }
385
386 #[test]
387 fn directed_no_eulerian_returns_none() {
388 let mut g = Graph::new(3, true).unwrap();
390 g.add_edge(0, 1).unwrap();
391 g.add_edge(1, 2).unwrap();
392 g.add_edge(0, 2).unwrap();
393 assert_eq!(eulerian_path(&g).unwrap(), None);
394 }
395
396 #[test]
399 fn cycle_triangle() {
400 let mut g = Graph::with_vertices(3);
401 g.add_edge(0, 1).unwrap();
402 g.add_edge(1, 2).unwrap();
403 g.add_edge(2, 0).unwrap();
404 let cycle = eulerian_cycle(&g).unwrap();
405 assert_eq!(cycle.len(), 3);
406 assert!(walk_validates(&g, &cycle));
407 }
408
409 #[test]
410 fn cycle_ring5() {
411 let mut g = Graph::with_vertices(5);
412 for i in 0..5u32 {
413 g.add_edge(i, (i + 1) % 5).unwrap();
414 }
415 let cycle = eulerian_cycle(&g).unwrap();
416 assert_eq!(cycle.len(), 5);
417 assert!(walk_validates(&g, &cycle));
418 }
419
420 #[test]
421 fn cycle_empty_graph() {
422 let g = Graph::with_vertices(0);
423 let cycle = eulerian_cycle(&g).unwrap();
424 assert!(cycle.is_empty());
425 }
426
427 #[test]
428 fn cycle_isolated_vertices() {
429 let g = Graph::with_vertices(4);
430 let cycle = eulerian_cycle(&g).unwrap();
431 assert!(cycle.is_empty());
432 }
433
434 #[test]
435 fn cycle_path_graph_errors() {
436 let mut g = Graph::with_vertices(3);
438 g.add_edge(0, 1).unwrap();
439 g.add_edge(1, 2).unwrap();
440 assert!(eulerian_cycle(&g).is_err());
441 }
442
443 #[test]
444 fn cycle_k4_errors() {
445 let mut g = Graph::with_vertices(4);
447 for u in 0..4u32 {
448 for v in (u + 1)..4 {
449 g.add_edge(u, v).unwrap();
450 }
451 }
452 assert!(eulerian_cycle(&g).is_err());
453 }
454
455 #[test]
456 fn cycle_directed_3cycle() {
457 let mut g = Graph::new(3, true).unwrap();
458 g.add_edge(0, 1).unwrap();
459 g.add_edge(1, 2).unwrap();
460 g.add_edge(2, 0).unwrap();
461 let cycle = eulerian_cycle(&g).unwrap();
462 assert_eq!(cycle.len(), 3);
463 }
464
465 #[test]
466 fn cycle_directed_path_errors() {
467 let mut g = Graph::new(3, true).unwrap();
469 g.add_edge(0, 1).unwrap();
470 g.add_edge(1, 2).unwrap();
471 assert!(eulerian_cycle(&g).is_err());
472 }
473
474 #[test]
475 fn complex_eulerian_path_test_eulerian_r() {
476 let mut g = Graph::with_vertices(6);
479 for &(u, v) in &[
480 (0, 1),
481 (1, 2),
482 (2, 3),
483 (3, 4),
484 (4, 0),
485 (0, 5),
486 (5, 3),
487 (3, 1),
488 (1, 5),
489 (5, 4),
490 ] {
491 g.add_edge(u, v).unwrap();
492 }
493 let walk = eulerian_path(&g).unwrap().unwrap();
494 assert_eq!(walk.len(), 10, "must visit every edge exactly once");
495 assert!(walk_validates(&g, &walk));
496 }
497}