use crate::core::{Graph, IgraphError, IgraphResult};
#[derive(Debug, Clone)]
pub struct EvenTarjanResult {
pub graph: Graph,
pub capacity: Vec<f64>,
}
pub fn even_tarjan_reduction(graph: &Graph) -> IgraphResult<EvenTarjanResult> {
let n = graph.vcount();
let m = graph.ecount();
let new_n = n.checked_mul(2).ok_or_else(|| {
IgraphError::InvalidArgument("even_tarjan_reduction: vertex count overflow".into())
})?;
let edges_from_vertices = n as usize;
let edges_from_edges = m.checked_mul(2).ok_or_else(|| {
IgraphError::InvalidArgument("even_tarjan_reduction: edge count overflow".into())
})?;
let total_edges = edges_from_vertices
.checked_add(edges_from_edges)
.ok_or_else(|| {
IgraphError::InvalidArgument("even_tarjan_reduction: total edge count overflow".into())
})?;
let mut new_graph = Graph::new(new_n, true)?;
let mut capacity: Vec<f64> = Vec::with_capacity(total_edges);
for i in 0..n {
new_graph.add_edge(i, i + n)?;
capacity.push(1.0);
}
for eid in 0..m {
let eid32 = u32::try_from(eid).map_err(|_| {
IgraphError::InvalidArgument("even_tarjan_reduction: edge id overflow".into())
})?;
let (from, to) = graph.edge(eid32)?;
new_graph.add_edge(from + n, to)?;
capacity.push(f64::INFINITY);
new_graph.add_edge(to + n, from)?;
capacity.push(f64::INFINITY);
}
Ok(EvenTarjanResult {
graph: new_graph,
capacity,
})
}
#[cfg(test)]
#[allow(clippy::float_cmp)]
mod tests {
use super::*;
#[test]
fn empty_graph() {
let g = Graph::new(0, true).unwrap();
let result = even_tarjan_reduction(&g).unwrap();
assert_eq!(result.graph.vcount(), 0);
assert_eq!(result.graph.ecount(), 0);
assert!(result.capacity.is_empty());
}
#[test]
fn single_vertex() {
let g = Graph::new(1, true).unwrap();
let result = even_tarjan_reduction(&g).unwrap();
assert_eq!(result.graph.vcount(), 2);
assert_eq!(result.graph.ecount(), 1);
assert_eq!(result.capacity, vec![1.0]);
let (from, to) = result.graph.edge(0).unwrap();
assert_eq!((from, to), (0, 1));
}
#[test]
fn single_edge() {
let mut g = Graph::new(2, true).unwrap();
g.add_edge(0, 1).unwrap();
let result = even_tarjan_reduction(&g).unwrap();
assert_eq!(result.graph.vcount(), 4);
assert_eq!(result.graph.ecount(), 4);
assert_eq!(
result.capacity,
vec![1.0, 1.0, f64::INFINITY, f64::INFINITY]
);
assert_eq!(result.graph.edge(0).unwrap(), (0, 2));
assert_eq!(result.graph.edge(1).unwrap(), (1, 3));
assert_eq!(result.graph.edge(2).unwrap(), (2, 1));
assert_eq!(result.graph.edge(3).unwrap(), (3, 0));
}
#[test]
fn triangle() {
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 result = even_tarjan_reduction(&g).unwrap();
assert_eq!(result.graph.vcount(), 6);
assert_eq!(result.graph.ecount(), 9);
assert_eq!(result.capacity.len(), 9);
for i in 0..3 {
assert_eq!(result.capacity[i], 1.0);
}
for i in 3..9 {
assert_eq!(result.capacity[i], f64::INFINITY);
}
}
#[test]
fn undirected_graph() {
let mut g = Graph::with_vertices(3);
g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
let result = even_tarjan_reduction(&g).unwrap();
assert_eq!(result.graph.vcount(), 6);
assert_eq!(result.graph.ecount(), 7);
assert!(result.graph.is_directed());
}
#[test]
fn self_loop() {
let mut g = Graph::new(2, true).unwrap();
g.add_edge(0, 0).unwrap(); g.add_edge(0, 1).unwrap();
let result = even_tarjan_reduction(&g).unwrap();
assert_eq!(result.graph.vcount(), 4);
assert_eq!(result.graph.ecount(), 6);
}
#[test]
fn capacity_values() {
let mut g = Graph::new(4, true).unwrap();
g.add_edge(0, 1).unwrap();
g.add_edge(2, 3).unwrap();
let result = even_tarjan_reduction(&g).unwrap();
assert_eq!(result.capacity.len(), 8);
let ones: Vec<f64> = result
.capacity
.iter()
.filter(|&&c| c == 1.0)
.copied()
.collect();
let infs: Vec<f64> = result
.capacity
.iter()
.filter(|c| c.is_infinite())
.copied()
.collect();
assert_eq!(ones.len(), 4);
assert_eq!(infs.len(), 4);
}
}