1use crate::core::{Graph, IgraphError, IgraphResult, VertexId};
22
23use super::max_flow::max_flow_value;
24
25pub fn edge_disjoint_paths(graph: &Graph, source: VertexId, target: VertexId) -> IgraphResult<i64> {
77 let flow = max_flow_value(graph, source, target, None)?;
78 #[allow(
83 clippy::cast_precision_loss,
84 clippy::cast_possible_truncation,
85 clippy::cast_sign_loss
86 )]
87 {
88 if !flow.is_finite() || flow < 0.0 || flow > i64::MAX as f64 {
89 return Err(IgraphError::Internal(
90 "unit-capacity max-flow value is not representable as i64",
91 ));
92 }
93 Ok(flow.round() as i64)
94 }
95}
96
97#[cfg(test)]
98mod tests {
99 use super::*;
100 use crate::core::IgraphError;
101
102 #[test]
103 fn rejects_source_equals_target() {
104 let mut g = Graph::new(2, true).expect("graph");
105 g.add_edge(0, 1).expect("edge");
106 let err = edge_disjoint_paths(&g, 0, 0).unwrap_err();
107 match err {
108 IgraphError::InvalidArgument(_) => {}
109 other => panic!("expected InvalidArgument, got {other:?}"),
110 }
111 }
112
113 #[test]
114 fn rejects_out_of_range_source() {
115 let g = Graph::new(2, true).expect("graph");
116 let err = edge_disjoint_paths(&g, 5, 0).unwrap_err();
117 match err {
118 IgraphError::VertexOutOfRange { id, n } => {
119 assert_eq!(id, 5);
120 assert_eq!(n, 2);
121 }
122 other => panic!("expected VertexOutOfRange, got {other:?}"),
123 }
124 }
125
126 #[test]
127 fn rejects_out_of_range_target() {
128 let g = Graph::new(2, true).expect("graph");
129 let err = edge_disjoint_paths(&g, 0, 5).unwrap_err();
130 match err {
131 IgraphError::VertexOutOfRange { id, n } => {
132 assert_eq!(id, 5);
133 assert_eq!(n, 2);
134 }
135 other => panic!("expected VertexOutOfRange, got {other:?}"),
136 }
137 }
138
139 #[test]
140 fn isolated_endpoints_have_no_paths() {
141 let g = Graph::new(4, true).expect("graph");
142 assert_eq!(edge_disjoint_paths(&g, 0, 3).expect("paths"), 0);
143 }
144
145 #[test]
146 fn single_edge_one_path() {
147 let mut g = Graph::new(2, true).expect("graph");
148 g.add_edge(0, 1).expect("edge");
149 assert_eq!(edge_disjoint_paths(&g, 0, 1).expect("paths"), 1);
150 }
151
152 #[test]
153 fn two_parallel_paths() {
154 let mut g = Graph::new(4, true).expect("graph");
156 for (s, t) in [(0u32, 1u32), (1, 3), (0, 2), (2, 3)] {
157 g.add_edge(s, t).expect("edge");
158 }
159 assert_eq!(edge_disjoint_paths(&g, 0, 3).expect("paths"), 2);
160 }
161
162 #[test]
163 fn parallel_arcs_count_each_as_path() {
164 let mut g = Graph::new(2, true).expect("graph");
165 for _ in 0..4 {
166 g.add_edge(0, 1).expect("edge");
167 }
168 assert_eq!(edge_disjoint_paths(&g, 0, 1).expect("paths"), 4);
169 }
170
171 #[test]
172 fn directed_no_reverse_path() {
173 let mut g = Graph::new(4, true).expect("graph");
175 for (s, t) in [(0u32, 1u32), (1, 2), (2, 3)] {
176 g.add_edge(s, t).expect("edge");
177 }
178 assert_eq!(edge_disjoint_paths(&g, 0, 3).expect("paths"), 1);
179 assert_eq!(edge_disjoint_paths(&g, 3, 0).expect("paths"), 0);
181 }
182
183 #[test]
184 fn c_unit_fixture_directed_0_to_5() {
185 let mut g = Graph::new(6, true).expect("graph");
188 let arcs = [
189 (0u32, 1u32),
190 (0, 2),
191 (1, 2),
192 (1, 3),
193 (2, 4),
194 (3, 4),
195 (3, 5),
196 (4, 5),
197 (3, 3),
198 ];
199 for (s, t) in arcs {
200 g.add_edge(s, t).expect("edge");
201 }
202 assert_eq!(edge_disjoint_paths(&g, 0, 5).expect("paths"), 2);
203 assert_eq!(edge_disjoint_paths(&g, 0, 3).expect("paths"), 1);
204 assert_eq!(edge_disjoint_paths(&g, 3, 0).expect("paths"), 0);
205 assert_eq!(edge_disjoint_paths(&g, 3, 5).expect("paths"), 2);
206 }
207
208 #[test]
209 fn c_unit_fixture_undirected_4_to_3() {
210 let mut g = Graph::new(6, false).expect("graph");
213 for (s, t) in [
214 (0u32, 1u32),
215 (0, 2),
216 (1, 2),
217 (1, 3),
218 (2, 4),
219 (3, 4),
220 (3, 5),
221 (4, 5),
222 (3, 3),
223 ] {
224 g.add_edge(s, t).expect("edge");
225 }
226 assert_eq!(edge_disjoint_paths(&g, 4, 3).expect("paths"), 3);
227 }
228
229 #[test]
230 fn matches_st_edge_connectivity() {
231 use super::super::st_edge_connectivity::st_edge_connectivity;
234 let mut g = Graph::new(5, false).expect("graph");
235 for i in 0u32..5 {
236 for j in (i + 1)..5 {
237 g.add_edge(i, j).expect("edge");
238 }
239 }
240 assert_eq!(
241 edge_disjoint_paths(&g, 0, 4).expect("paths"),
242 st_edge_connectivity(&g, 0, 4).expect("ec")
243 );
244 }
245}
246
247#[cfg(all(test, feature = "proptest-harness"))]
248mod proptests {
249 use super::super::st_edge_connectivity::st_edge_connectivity;
257 use super::*;
258 use crate::core::Graph;
259 use proptest::prelude::*;
260
261 fn xorshift(mut r: u64) -> u64 {
262 r ^= r << 13;
263 r ^= r >> 7;
264 r ^= r << 17;
265 r
266 }
267
268 fn build_random(seed: u64, n: u32, m_max: u32, directed: bool) -> Graph {
269 let mut g = Graph::new(n, directed).expect("graph");
270 let mut state = seed | 1;
271 for _ in 0..m_max {
272 state = xorshift(state);
273 let u = u32::try_from(state % u64::from(n)).expect("modulo fits");
274 state = xorshift(state);
275 let v = u32::try_from(state % u64::from(n)).expect("modulo fits");
276 if u == v {
277 continue;
278 }
279 g.add_edge(u, v).expect("edge");
280 }
281 g
282 }
283
284 proptest! {
285 #[test]
286 fn menger_equals_st_edge_connectivity(
287 seed in any::<u64>(),
288 n in 2u32..8,
289 m in 1u32..16,
290 directed in any::<bool>(),
291 ) {
292 let g = build_random(seed, n, m, directed);
293 let s = u32::try_from(seed % u64::from(n)).expect("modulo fits");
294 let t_raw = u32::try_from(xorshift(seed) % u64::from(n)).expect("modulo fits");
295 let t = if t_raw == s { (s + 1) % n } else { t_raw };
296 prop_assume!(s != t);
297
298 let paths = edge_disjoint_paths(&g, s, t).expect("paths");
299 let ec = st_edge_connectivity(&g, s, t).expect("ec");
300 prop_assert_eq!(
301 paths,
302 ec,
303 "Menger violated: paths={} ec={} (n={}, m={}, directed={}, seed={})",
304 paths, ec, n, m, directed, seed
305 );
306 }
307 }
308}