Skip to main content

rust_igraph/algorithms/operators/
even_tarjan.rs

1//! Even-Tarjan reduction (ALGO-OP-029).
2//!
3//! Counterpart of `igraph_even_tarjan_reduction()` from
4//! `references/igraph/src/flow/st-cuts.c:87-148`.
5//!
6//! Creates a directed graph with twice as many vertices where each
7//! vertex `i` is split into `i'` and `i''`, connected by an edge.
8//! Used for vertex-connectivity via edge-connectivity reduction.
9
10use crate::core::{Graph, IgraphError, IgraphResult};
11
12/// Result of the Even-Tarjan reduction.
13#[derive(Debug, Clone)]
14pub struct EvenTarjanResult {
15    /// The reduced directed graph with `2 * vcount` vertices.
16    pub graph: Graph,
17    /// Edge capacities: `1.0` for vertex-split edges, `f64::INFINITY`
18    /// for edges derived from original edges.
19    pub capacity: Vec<f64>,
20}
21
22/// Compute the Even-Tarjan reduction of a graph.
23///
24/// For each vertex `i` in the original graph (with `n` vertices), the
25/// reduced graph contains vertices `i' = i` and `i'' = i + n`, connected
26/// by a directed edge `i' → i''` with capacity 1.
27///
28/// For each directed edge `(from, to)` in the original graph, two edges
29/// are added: `from'' → to'` and `to'' → from'`, both with infinite
30/// capacity.
31///
32/// This reduction converts vertex-connectivity problems into
33/// edge-connectivity problems on the reduced graph.
34///
35/// # Examples
36///
37/// ```
38/// use rust_igraph::{Graph, even_tarjan_reduction};
39///
40/// let mut g = Graph::new(3, true).unwrap();
41/// g.add_edge(0, 1).unwrap();
42/// g.add_edge(1, 2).unwrap();
43/// let result = even_tarjan_reduction(&g).unwrap();
44/// // 2*3 = 6 vertices
45/// assert_eq!(result.graph.vcount(), 6);
46/// // 3 vertex-split edges + 2*2 = 7 edges
47/// assert_eq!(result.graph.ecount(), 7);
48/// assert_eq!(result.capacity.len(), 7);
49/// // First 3 capacities are 1.0 (vertex splits)
50/// assert_eq!(result.capacity[0], 1.0);
51/// assert_eq!(result.capacity[1], 1.0);
52/// assert_eq!(result.capacity[2], 1.0);
53/// // Remaining are infinity
54/// assert_eq!(result.capacity[3], f64::INFINITY);
55/// ```
56pub fn even_tarjan_reduction(graph: &Graph) -> IgraphResult<EvenTarjanResult> {
57    let n = graph.vcount();
58    let m = graph.ecount();
59
60    let new_n = n.checked_mul(2).ok_or_else(|| {
61        IgraphError::InvalidArgument("even_tarjan_reduction: vertex count overflow".into())
62    })?;
63
64    let edges_from_vertices = n as usize;
65    let edges_from_edges = m.checked_mul(2).ok_or_else(|| {
66        IgraphError::InvalidArgument("even_tarjan_reduction: edge count overflow".into())
67    })?;
68    let total_edges = edges_from_vertices
69        .checked_add(edges_from_edges)
70        .ok_or_else(|| {
71            IgraphError::InvalidArgument("even_tarjan_reduction: total edge count overflow".into())
72        })?;
73
74    let mut new_graph = Graph::new(new_n, true)?;
75    let mut capacity: Vec<f64> = Vec::with_capacity(total_edges);
76
77    // For each vertex i: add edge i' → i'' (capacity 1)
78    for i in 0..n {
79        new_graph.add_edge(i, i + n)?;
80        capacity.push(1.0);
81    }
82
83    // For each original edge (from, to): add from'' → to' and to'' → from'
84    // (both with infinite capacity)
85    for eid in 0..m {
86        let eid32 = u32::try_from(eid).map_err(|_| {
87            IgraphError::InvalidArgument("even_tarjan_reduction: edge id overflow".into())
88        })?;
89        let (from, to) = graph.edge(eid32)?;
90
91        new_graph.add_edge(from + n, to)?;
92        capacity.push(f64::INFINITY);
93
94        new_graph.add_edge(to + n, from)?;
95        capacity.push(f64::INFINITY);
96    }
97
98    Ok(EvenTarjanResult {
99        graph: new_graph,
100        capacity,
101    })
102}
103
104#[cfg(test)]
105#[allow(clippy::float_cmp)]
106mod tests {
107    use super::*;
108
109    #[test]
110    fn empty_graph() {
111        let g = Graph::new(0, true).unwrap();
112        let result = even_tarjan_reduction(&g).unwrap();
113        assert_eq!(result.graph.vcount(), 0);
114        assert_eq!(result.graph.ecount(), 0);
115        assert!(result.capacity.is_empty());
116    }
117
118    #[test]
119    fn single_vertex() {
120        let g = Graph::new(1, true).unwrap();
121        let result = even_tarjan_reduction(&g).unwrap();
122        assert_eq!(result.graph.vcount(), 2);
123        assert_eq!(result.graph.ecount(), 1);
124        assert_eq!(result.capacity, vec![1.0]);
125        let (from, to) = result.graph.edge(0).unwrap();
126        assert_eq!((from, to), (0, 1));
127    }
128
129    #[test]
130    fn single_edge() {
131        let mut g = Graph::new(2, true).unwrap();
132        g.add_edge(0, 1).unwrap();
133        let result = even_tarjan_reduction(&g).unwrap();
134        // 4 vertices: 0', 1', 0'', 1''
135        assert_eq!(result.graph.vcount(), 4);
136        // 2 vertex-split + 2 from the edge = 4
137        assert_eq!(result.graph.ecount(), 4);
138        assert_eq!(
139            result.capacity,
140            vec![1.0, 1.0, f64::INFINITY, f64::INFINITY]
141        );
142
143        // Vertex splits: 0→2 (0'→0''), 1→3 (1'→1'')
144        assert_eq!(result.graph.edge(0).unwrap(), (0, 2));
145        assert_eq!(result.graph.edge(1).unwrap(), (1, 3));
146        // Edge (0,1): 0''→1' (2→1), 1''→0' (3→0)
147        assert_eq!(result.graph.edge(2).unwrap(), (2, 1));
148        assert_eq!(result.graph.edge(3).unwrap(), (3, 0));
149    }
150
151    #[test]
152    fn triangle() {
153        let mut g = Graph::new(3, true).unwrap();
154        g.add_edge(0, 1).unwrap();
155        g.add_edge(1, 2).unwrap();
156        g.add_edge(2, 0).unwrap();
157        let result = even_tarjan_reduction(&g).unwrap();
158        assert_eq!(result.graph.vcount(), 6);
159        // 3 vertex-split + 3*2 = 9
160        assert_eq!(result.graph.ecount(), 9);
161        assert_eq!(result.capacity.len(), 9);
162        // First 3 are vertex splits
163        for i in 0..3 {
164            assert_eq!(result.capacity[i], 1.0);
165        }
166        // Rest are infinite
167        for i in 3..9 {
168            assert_eq!(result.capacity[i], f64::INFINITY);
169        }
170    }
171
172    #[test]
173    fn undirected_graph() {
174        // Should still work — treats edges as directed
175        let mut g = Graph::with_vertices(3);
176        g.add_edge(0, 1).unwrap();
177        g.add_edge(1, 2).unwrap();
178        let result = even_tarjan_reduction(&g).unwrap();
179        assert_eq!(result.graph.vcount(), 6);
180        // 3 vertex-split + 2*2 = 7
181        assert_eq!(result.graph.ecount(), 7);
182        assert!(result.graph.is_directed());
183    }
184
185    #[test]
186    fn self_loop() {
187        let mut g = Graph::new(2, true).unwrap();
188        g.add_edge(0, 0).unwrap(); // self-loop
189        g.add_edge(0, 1).unwrap();
190        let result = even_tarjan_reduction(&g).unwrap();
191        assert_eq!(result.graph.vcount(), 4);
192        // 2 vertex-split + 2*2 = 6
193        assert_eq!(result.graph.ecount(), 6);
194    }
195
196    #[test]
197    fn capacity_values() {
198        let mut g = Graph::new(4, true).unwrap();
199        g.add_edge(0, 1).unwrap();
200        g.add_edge(2, 3).unwrap();
201        let result = even_tarjan_reduction(&g).unwrap();
202        // 4 vertex-split (cap 1) + 4 edge-derived (cap inf)
203        assert_eq!(result.capacity.len(), 8);
204        let ones: Vec<f64> = result
205            .capacity
206            .iter()
207            .filter(|&&c| c == 1.0)
208            .copied()
209            .collect();
210        let infs: Vec<f64> = result
211            .capacity
212            .iter()
213            .filter(|c| c.is_infinite())
214            .copied()
215            .collect();
216        assert_eq!(ones.len(), 4);
217        assert_eq!(infs.len(), 4);
218    }
219}