use crate::algorithms::paths::eulerian::is_eulerian;
use crate::core::graph::EdgeId;
use crate::core::{Graph, IgraphError, IgraphResult, VertexId};
pub fn eulerian_cycle(graph: &Graph) -> IgraphResult<Vec<EdgeId>> {
let cls = is_eulerian(graph)?;
if !cls.has_cycle {
return Err(IgraphError::InvalidArgument(
"the graph does not have an Eulerian cycle".to_string(),
));
}
let m = graph.ecount();
if m == 0 {
return Ok(Vec::new());
}
match eulerian_path(graph)? {
Some(edges) => Ok(edges),
None => Err(IgraphError::Internal(
"has_cycle is true but eulerian_path returned None",
)),
}
}
pub fn eulerian_path(graph: &Graph) -> IgraphResult<Option<Vec<EdgeId>>> {
let cls = is_eulerian(graph)?;
if !cls.has_path {
return Ok(None);
}
let n = graph.vcount();
let m = graph.ecount();
if m == 0 {
return Ok(Some(Vec::new()));
}
let n_us = n as usize;
let m_us = m;
let directed = graph.is_directed();
let mut inc: Vec<Vec<EdgeId>> = Vec::with_capacity(n_us);
for v in 0..n {
let raw = graph.incident(v)?;
if directed {
inc.push(raw);
} else {
let mut seen: std::collections::HashSet<EdgeId> = std::collections::HashSet::new();
let mut out: Vec<EdgeId> = Vec::with_capacity(raw.len());
for e in raw {
if seen.insert(e) {
out.push(e);
}
}
inc.push(out);
}
}
let mut visited: Vec<bool> = vec![false; m_us];
let mut next_idx: Vec<usize> = vec![0; n_us];
let start_of_path = pick_start_vertex(graph, cls)?;
let mut tracker: Vec<VertexId> = Vec::with_capacity(n_us);
let mut edge_tracker: Vec<EdgeId> = Vec::with_capacity(m_us);
let mut path: Vec<VertexId> = Vec::with_capacity(n_us);
let mut edge_path: Vec<EdgeId> = Vec::with_capacity(m_us);
tracker.push(start_of_path);
let mut curr = start_of_path;
loop {
let curr_us = curr as usize;
while next_idx[curr_us] < inc[curr_us].len()
&& visited[inc[curr_us][next_idx[curr_us]] as usize]
{
next_idx[curr_us] += 1;
}
if next_idx[curr_us] < inc[curr_us].len() {
let edge = inc[curr_us][next_idx[curr_us]];
visited[edge as usize] = true;
next_idx[curr_us] += 1;
tracker.push(curr);
edge_tracker.push(edge);
curr = if directed {
graph.edge_target(edge)?
} else {
graph.edge_other(edge, curr)?
};
} else {
path.push(curr);
if let Some(prev) = tracker.pop() {
if let Some(curr_e) = edge_tracker.pop() {
edge_path.push(curr_e);
}
curr = prev;
} else {
break;
}
}
}
edge_path.reverse();
let _ = path;
Ok(Some(edge_path))
}
fn pick_start_vertex(
graph: &Graph,
cls: crate::algorithms::paths::eulerian::EulerianClassification,
) -> IgraphResult<VertexId> {
let n = graph.vcount();
let directed = graph.is_directed();
if cls.has_cycle {
for v in 0..n {
let has_edges = if directed {
!graph.incident(v)?.is_empty()
} else {
!graph.neighbors(v)?.is_empty()
};
if has_edges {
return Ok(v);
}
}
Err(IgraphError::Internal("no edges but cls.has_cycle"))
} else if directed {
for v in 0..n {
let out_deg = graph.incident(v)?.len();
let in_deg = compute_in_degree(graph, v)?;
if out_deg > in_deg && (out_deg - in_deg) == 1 {
return Ok(v);
}
}
Err(IgraphError::Internal(
"directed path: no vertex with outgoing_excess == 1",
))
} else {
for v in 0..n {
let deg = graph.degree(v)?;
if deg % 2 != 0 {
return Ok(v);
}
}
Err(IgraphError::Internal(
"has_path && !has_cycle but no odd-degree vertex found",
))
}
}
fn compute_in_degree(graph: &Graph, v: VertexId) -> IgraphResult<usize> {
let mut count = 0usize;
let m = u32::try_from(graph.ecount()).expect("edge count fits in u32");
for e in 0..m {
if graph.edge_target(e)? == v {
count += 1;
}
}
Ok(count)
}
#[cfg(test)]
mod tests {
use super::*;
fn walk_validates(graph: &Graph, walk: &[EdgeId]) -> bool {
let mut seen: Vec<bool> = vec![false; graph.ecount()];
for &edge in walk {
let idx = edge as usize;
if idx >= graph.ecount() || seen[idx] {
return false;
}
seen[idx] = true;
}
if walk.len() < 2 {
return true;
}
for i in 0..walk.len() - 1 {
let (a, b) = graph.edge(walk[i]).unwrap();
let (c, d) = graph.edge(walk[i + 1]).unwrap();
if !(a == c || a == d || b == c || b == d) {
return false;
}
}
true
}
#[test]
fn empty_graph_returns_empty_walk() {
let g = Graph::with_vertices(0);
assert_eq!(eulerian_path(&g).unwrap(), Some(Vec::new()));
}
#[test]
fn isolated_vertices_return_empty_walk() {
let g = Graph::with_vertices(5);
assert_eq!(eulerian_path(&g).unwrap(), Some(Vec::new()));
}
#[test]
fn triangle_yields_three_edge_walk() {
let mut g = Graph::with_vertices(3);
g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
g.add_edge(2, 0).unwrap();
let walk = eulerian_path(&g).unwrap().unwrap();
assert_eq!(walk.len(), 3);
assert!(walk_validates(&g, &walk));
}
#[test]
fn path_3_yields_two_edge_walk() {
let mut g = Graph::with_vertices(3);
g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
let walk = eulerian_path(&g).unwrap().unwrap();
assert_eq!(walk.len(), 2);
assert!(walk_validates(&g, &walk));
}
#[test]
fn k4_has_no_eulerian_walk() {
let mut g = Graph::with_vertices(4);
for u in 0..4u32 {
for v in (u + 1)..4 {
g.add_edge(u, v).unwrap();
}
}
assert_eq!(eulerian_path(&g).unwrap(), None);
}
#[test]
fn disconnected_components_no_walk() {
let mut g = Graph::with_vertices(4);
g.add_edge(0, 1).unwrap();
g.add_edge(2, 3).unwrap();
assert_eq!(eulerian_path(&g).unwrap(), None);
}
#[test]
fn ring_5_walk_visits_all_edges() {
let mut g = Graph::with_vertices(5);
for i in 0..5u32 {
g.add_edge(i, (i + 1) % 5).unwrap();
}
let walk = eulerian_path(&g).unwrap().unwrap();
assert_eq!(walk.len(), 5);
assert!(walk_validates(&g, &walk));
}
#[test]
fn directed_3_cycle_yields_3_edge_walk() {
let mut g = Graph::new(3, true).unwrap();
g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
g.add_edge(2, 0).unwrap();
let walk = eulerian_path(&g).unwrap().unwrap();
assert_eq!(walk.len(), 3);
}
#[test]
fn directed_path_yields_chain_walk() {
let mut g = Graph::new(3, true).unwrap();
g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
let walk = eulerian_path(&g).unwrap().unwrap();
assert_eq!(walk, vec![0, 1]); }
#[test]
fn directed_no_eulerian_returns_none() {
let mut g = Graph::new(3, true).unwrap();
g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
g.add_edge(0, 2).unwrap();
assert_eq!(eulerian_path(&g).unwrap(), None);
}
#[test]
fn cycle_triangle() {
let mut g = Graph::with_vertices(3);
g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
g.add_edge(2, 0).unwrap();
let cycle = eulerian_cycle(&g).unwrap();
assert_eq!(cycle.len(), 3);
assert!(walk_validates(&g, &cycle));
}
#[test]
fn cycle_ring5() {
let mut g = Graph::with_vertices(5);
for i in 0..5u32 {
g.add_edge(i, (i + 1) % 5).unwrap();
}
let cycle = eulerian_cycle(&g).unwrap();
assert_eq!(cycle.len(), 5);
assert!(walk_validates(&g, &cycle));
}
#[test]
fn cycle_empty_graph() {
let g = Graph::with_vertices(0);
let cycle = eulerian_cycle(&g).unwrap();
assert!(cycle.is_empty());
}
#[test]
fn cycle_isolated_vertices() {
let g = Graph::with_vertices(4);
let cycle = eulerian_cycle(&g).unwrap();
assert!(cycle.is_empty());
}
#[test]
fn cycle_path_graph_errors() {
let mut g = Graph::with_vertices(3);
g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
assert!(eulerian_cycle(&g).is_err());
}
#[test]
fn cycle_k4_errors() {
let mut g = Graph::with_vertices(4);
for u in 0..4u32 {
for v in (u + 1)..4 {
g.add_edge(u, v).unwrap();
}
}
assert!(eulerian_cycle(&g).is_err());
}
#[test]
fn cycle_directed_3cycle() {
let mut g = Graph::new(3, true).unwrap();
g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
g.add_edge(2, 0).unwrap();
let cycle = eulerian_cycle(&g).unwrap();
assert_eq!(cycle.len(), 3);
}
#[test]
fn cycle_directed_path_errors() {
let mut g = Graph::new(3, true).unwrap();
g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
assert!(eulerian_cycle(&g).is_err());
}
#[test]
fn complex_eulerian_path_test_eulerian_r() {
let mut g = Graph::with_vertices(6);
for &(u, v) in &[
(0, 1),
(1, 2),
(2, 3),
(3, 4),
(4, 0),
(0, 5),
(5, 3),
(3, 1),
(1, 5),
(5, 4),
] {
g.add_edge(u, v).unwrap();
}
let walk = eulerian_path(&g).unwrap().unwrap();
assert_eq!(walk.len(), 10, "must visit every edge exactly once");
assert!(walk_validates(&g, &walk));
}
}