rust_igraph/algorithms/flow/
mincut_value.rs1use crate::core::{Graph, IgraphError, IgraphResult};
45
46use super::max_flow::max_flow_value;
47
48pub fn mincut_value(graph: &Graph, capacity: Option<&[f64]>) -> IgraphResult<f64> {
101 let n = graph.vcount();
102
103 if let Some(c) = capacity {
107 let m = graph.ecount();
108 if c.len() != m {
109 return Err(IgraphError::InvalidArgument(format!(
110 "capacity length {} does not match edge count {}",
111 c.len(),
112 m
113 )));
114 }
115 for (i, &v) in c.iter().enumerate() {
116 if !v.is_finite() || v < 0.0 {
117 return Err(IgraphError::InvalidArgument(format!(
118 "capacity[{i}] = {v} is not a finite non-negative number"
119 )));
120 }
121 }
122 }
123 if n <= 1 {
124 return Ok(f64::INFINITY);
125 }
126
127 let directed = graph.is_directed();
129 let mut min_cut = f64::INFINITY;
130 for v in 1..n {
131 let f = max_flow_value(graph, 0, v, capacity)?;
132 if f < min_cut {
133 min_cut = f;
134 if min_cut == 0.0 {
135 return Ok(0.0);
136 }
137 }
138 if directed {
139 let f2 = max_flow_value(graph, v, 0, capacity)?;
140 if f2 < min_cut {
141 min_cut = f2;
142 if min_cut == 0.0 {
143 return Ok(0.0);
144 }
145 }
146 }
147 }
148
149 Ok(min_cut)
150}
151
152#[cfg(test)]
153mod tests {
154 use super::*;
155
156 const TOL: f64 = 1e-12;
157
158 fn approx_eq(a: f64, b: f64) -> bool {
159 if a.is_infinite() && b.is_infinite() {
160 a.is_sign_positive() == b.is_sign_positive()
161 } else {
162 (a - b).abs() < TOL
163 }
164 }
165
166 fn ring_n(n: u32, directed: bool) -> Graph {
167 let mut g = Graph::new(n, directed).expect("graph");
168 for i in 0..n {
169 let j = (i + 1) % n;
170 g.add_edge(i, j).expect("edge");
171 }
172 g
173 }
174
175 fn path_n(n: u32, directed: bool) -> Graph {
176 let mut g = Graph::new(n, directed).expect("graph");
177 for i in 0..(n - 1) {
178 g.add_edge(i, i + 1).expect("edge");
179 }
180 g
181 }
182
183 fn complete_undirected(n: u32) -> Graph {
184 let mut g = Graph::new(n, false).expect("graph");
185 for i in 0..n {
186 for j in (i + 1)..n {
187 g.add_edge(i, j).expect("edge");
188 }
189 }
190 g
191 }
192
193 fn complete_directed_mutual(n: u32) -> Graph {
194 let mut g = Graph::new(n, true).expect("graph");
195 for i in 0..n {
196 for j in 0..n {
197 if i != j {
198 g.add_edge(i, j).expect("edge");
199 }
200 }
201 }
202 g
203 }
204
205 #[test]
208 fn empty_graph_returns_infinity() {
209 let g = Graph::new(0, false).expect("graph");
210 let mc = mincut_value(&g, None).expect("mc");
211 assert!(mc.is_infinite() && mc.is_sign_positive());
212 }
213
214 #[test]
215 fn single_vertex_returns_infinity() {
216 let g = Graph::new(1, false).expect("graph");
217 assert!(mincut_value(&g, None).expect("mc").is_infinite());
218 let g2 = Graph::new(1, true).expect("graph");
219 assert!(mincut_value(&g2, None).expect("mc").is_infinite());
220 }
221
222 #[test]
225 fn two_disconnected_vertices_return_zero() {
226 let g = Graph::new(2, false).expect("graph");
227 assert!(approx_eq(mincut_value(&g, None).expect("mc"), 0.0));
228 }
229
230 #[test]
231 fn two_isolated_edges_return_zero() {
232 let mut g = Graph::new(4, false).expect("graph");
233 g.add_edge(0, 1).expect("edge");
234 g.add_edge(2, 3).expect("edge");
235 assert!(approx_eq(mincut_value(&g, None).expect("mc"), 0.0));
236 }
237
238 #[test]
241 fn unit_caps_ring_5v_undirected() {
242 let g = ring_n(5, false);
243 assert!(approx_eq(mincut_value(&g, None).expect("mc"), 2.0));
244 }
245
246 #[test]
247 fn unit_caps_path_5v_undirected() {
248 let g = path_n(5, false);
249 assert!(approx_eq(mincut_value(&g, None).expect("mc"), 1.0));
250 }
251
252 #[test]
253 fn unit_caps_k5_undirected() {
254 let g = complete_undirected(5);
255 assert!(approx_eq(mincut_value(&g, None).expect("mc"), 4.0));
256 }
257
258 #[test]
259 fn unit_caps_directed_3cycle() {
260 let g = ring_n(3, true);
261 assert!(approx_eq(mincut_value(&g, None).expect("mc"), 1.0));
262 }
263
264 #[test]
265 fn unit_caps_complete_directed_mutual_4v() {
266 let g = complete_directed_mutual(4);
267 assert!(approx_eq(mincut_value(&g, None).expect("mc"), 3.0));
268 }
269
270 #[test]
273 fn weighted_k2_returns_capacity() {
274 let mut g = Graph::new(2, false).expect("graph");
275 g.add_edge(0, 1).expect("edge");
276 let caps = vec![7.5];
277 assert!(approx_eq(mincut_value(&g, Some(&caps)).expect("mc"), 7.5));
278 }
279
280 #[test]
281 fn weighted_ring_5v_undirected_min_edge() {
282 let g = ring_n(5, false);
287 let caps = vec![0.25, 10.0, 10.0, 10.0, 10.0];
288 let mc = mincut_value(&g, Some(&caps)).expect("mc");
289 assert!(approx_eq(mc, 10.25), "got {mc}, want 10.25");
290 }
291
292 #[test]
293 fn weighted_path_directed_zero_capacity_returns_zero() {
294 let g = path_n(5, true);
297 let caps = vec![0.0, 1.0, 1.0, 1.0];
298 assert!(approx_eq(mincut_value(&g, Some(&caps)).expect("mc"), 0.0));
299 }
300
301 #[test]
302 fn weighted_directed_3cycle_min_arc() {
303 let g = ring_n(3, true);
308 let caps = vec![3.0, 1.0, 2.0];
309 assert!(approx_eq(mincut_value(&g, Some(&caps)).expect("mc"), 1.0));
310 }
311
312 #[test]
315 fn multigraph_two_parallel_edges_returns_two() {
316 let mut g = Graph::new(2, false).expect("graph");
317 g.add_edge(0, 1).expect("edge");
318 g.add_edge(0, 1).expect("edge");
319 assert!(approx_eq(mincut_value(&g, None).expect("mc"), 2.0));
320 }
321
322 #[test]
325 fn none_matches_unit_caps() {
326 let fixtures: Vec<Graph> = vec![
327 ring_n(6, false),
328 ring_n(6, true),
329 path_n(5, false),
330 complete_undirected(4),
331 complete_directed_mutual(4),
332 ];
333 for g in fixtures {
334 let caps = vec![1.0_f64; g.ecount()];
335 let a = mincut_value(&g, None).expect("mc");
336 let b = mincut_value(&g, Some(&caps)).expect("mc");
337 assert!(
338 approx_eq(a, b),
339 "None={a} != Some(1.0..)={b} (n={}, dir={})",
340 g.vcount(),
341 g.is_directed()
342 );
343 }
344 }
345
346 #[test]
349 fn capacity_wrong_length_errors() {
350 let g = ring_n(4, false);
351 let caps = vec![1.0_f64; 99];
352 let err = mincut_value(&g, Some(&caps)).expect_err("must err");
353 assert!(matches!(err, IgraphError::InvalidArgument(_)));
354 }
355
356 #[test]
357 fn capacity_negative_errors() {
358 let g = ring_n(4, false);
359 let mut caps = vec![1.0_f64; g.ecount()];
360 caps[1] = -0.5;
361 let err = mincut_value(&g, Some(&caps)).expect_err("must err");
362 assert!(matches!(err, IgraphError::InvalidArgument(_)));
363 }
364
365 #[test]
366 fn capacity_nan_errors() {
367 let g = ring_n(4, false);
368 let mut caps = vec![1.0_f64; g.ecount()];
369 caps[0] = f64::NAN;
370 let err = mincut_value(&g, Some(&caps)).expect_err("must err");
371 assert!(matches!(err, IgraphError::InvalidArgument(_)));
372 }
373}
374
375#[cfg(all(test, feature = "proptest-harness"))]
376mod proptests {
377 use super::*;
386 use crate::algorithms::flow::edge_connectivity::edge_connectivity;
387 use crate::core::Graph;
388 use proptest::prelude::*;
389
390 fn xorshift(mut r: u64) -> u64 {
391 r ^= r << 13;
392 r ^= r >> 7;
393 r ^= r << 17;
394 r
395 }
396
397 fn build_random(seed: u64, n: u32, m_max: u32, directed: bool) -> Graph {
398 let mut g = Graph::new(n, directed).expect("graph");
399 let mut state = seed | 1;
400 for _ in 0..m_max {
401 state = xorshift(state);
402 let u = u32::try_from(state % u64::from(n)).expect("modulo fits");
403 state = xorshift(state);
404 let v = u32::try_from(state % u64::from(n)).expect("modulo fits");
405 if u == v {
406 continue;
407 }
408 g.add_edge(u, v).expect("edge");
409 }
410 g
411 }
412
413 proptest! {
414 #[test]
415 fn nonneg_and_finite(
416 seed in any::<u64>(),
417 n in 2u32..6,
418 m in 0u32..10,
419 directed in any::<bool>(),
420 ) {
421 let g = build_random(seed, n, m, directed);
422 let mc = mincut_value(&g, None).expect("mc");
423 prop_assert!(mc.is_finite(), "expected finite mc, got {mc}");
424 prop_assert!(mc >= 0.0, "expected mc >= 0, got {mc}");
425 }
426
427 #[test]
428 fn none_matches_unit_caps(
429 seed in any::<u64>(),
430 n in 2u32..6,
431 m in 0u32..10,
432 directed in any::<bool>(),
433 ) {
434 let g = build_random(seed, n, m, directed);
435 let caps = vec![1.0_f64; g.ecount()];
436 let a = mincut_value(&g, None).expect("mc");
437 let b = mincut_value(&g, Some(&caps)).expect("mc");
438 prop_assert!((a - b).abs() < 1e-12,
439 "None={a} != Some(1.0..)={b} (n={n}, m={m}, dir={directed}, seed={seed})");
440 }
441
442 #[test]
443 fn unit_caps_match_edge_connectivity(
444 seed in any::<u64>(),
445 n in 2u32..6,
446 m in 0u32..10,
447 directed in any::<bool>(),
448 ) {
449 let g = build_random(seed, n, m, directed);
450 let mc = mincut_value(&g, None).expect("mc");
451 let ec = edge_connectivity(&g, true).expect("ec") as f64;
452 prop_assert!((mc - ec).abs() < 1e-12,
453 "mincut={mc} != edge_conn={ec} (n={n}, m={m}, dir={directed}, seed={seed})");
454 }
455
456 #[test]
457 fn doubling_capacities_doubles_mincut(
458 seed in any::<u64>(),
459 n in 2u32..5,
460 m in 1u32..8,
461 directed in any::<bool>(),
462 ) {
463 let g = build_random(seed, n, m, directed);
464 let caps1 = vec![1.0_f64; g.ecount()];
465 let caps2 = vec![2.0_f64; g.ecount()];
466 let mc1 = mincut_value(&g, Some(&caps1)).expect("mc1");
467 let mc2 = mincut_value(&g, Some(&caps2)).expect("mc2");
468 prop_assert!((mc2 - 2.0 * mc1).abs() < 1e-12,
469 "doubling caps should double mc: mc1={mc1}, mc2={mc2}");
470 }
471 }
472}