1#![allow(clippy::cast_possible_truncation)]
28
29use std::collections::VecDeque;
30
31use crate::algorithms::connectivity::strong::strongly_connected_components;
32use crate::algorithms::flow::max_flow::max_flow;
33use crate::algorithms::flow::provan_shier::{EStack, MarkedQueue, Pivot, provan_shier_list};
34use crate::algorithms::operators::contract_vertices::contract_vertices;
35use crate::algorithms::operators::permute_vertices::permute_vertices;
36use crate::algorithms::operators::residual_graph::reverse_residual_graph;
37use crate::algorithms::operators::simplify::simplify;
38use crate::algorithms::paths::dijkstra::DijkstraMode;
39use crate::algorithms::properties::topological_sorting::topological_sorting;
40use crate::core::graph::EdgeId;
41use crate::core::{Graph, IgraphError, IgraphResult, VertexId};
42
43#[derive(Clone, Debug, PartialEq)]
52pub struct StMinCuts {
53 pub value: f64,
55 pub cuts: Vec<Vec<EdgeId>>,
57 pub partition1s: Vec<Vec<VertexId>>,
59}
60
61struct MincutsPivot {
67 in_adj: Vec<Vec<VertexId>>,
69 active: Vec<bool>,
71}
72
73impl MincutsPivot {
74 fn new(residual: &Graph, active: Vec<bool>) -> IgraphResult<Self> {
75 let n = residual.vcount() as usize;
76 let mut in_adj = vec![Vec::new(); n];
77 for (v, slot) in in_adj.iter_mut().enumerate() {
78 *slot = residual.in_neighbors_vec(v as VertexId)?;
79 }
80 Ok(Self { in_adj, active })
81 }
82
83 fn minimal(&self, s: &MarkedQueue) -> Vec<VertexId> {
88 let n = self.in_adj.len();
89 let mut connected = vec![false; n];
90 let mut minimal: Vec<VertexId> = Vec::new();
91 for i in 0..n {
92 if s.iselement(i as VertexId) {
93 continue;
94 }
95 let conn = self.in_adj[i].iter().any(|&nei| connected[nei as usize]);
96 if conn {
97 connected[i] = true;
98 } else if self.active[i] {
99 minimal.push(i as VertexId);
100 connected[i] = true;
101 }
102 }
103 minimal
104 }
105
106 fn bfs_in(&self, root: VertexId, keep: &[bool]) -> Vec<VertexId> {
108 let n = self.in_adj.len();
109 let mut visited = vec![false; n];
110 let mut order: Vec<VertexId> = Vec::new();
111 if !keep[root as usize] {
112 return order;
113 }
114 let mut queue: VecDeque<VertexId> = VecDeque::new();
115 visited[root as usize] = true;
116 order.push(root);
117 queue.push_back(root);
118 while let Some(u) = queue.pop_front() {
119 for &nei in &self.in_adj[u as usize] {
120 if keep[nei as usize] && !visited[nei as usize] {
121 visited[nei as usize] = true;
122 order.push(nei);
123 queue.push_back(nei);
124 }
125 }
126 }
127 order
128 }
129}
130
131impl Pivot for MincutsPivot {
132 fn pivot(
133 &mut self,
134 s: &MarkedQueue,
135 t: &EStack,
136 _source: VertexId,
137 target: VertexId,
138 ) -> IgraphResult<(VertexId, Vec<VertexId>)> {
139 let n = self.in_adj.len();
140 if s.size() == n {
141 return Ok((0, Vec::new()));
142 }
143
144 let mut keep = vec![false; n];
145 for (i, slot) in keep.iter_mut().enumerate() {
146 *slot = !s.iselement(i as VertexId);
147 }
148
149 let m = self.minimal(s);
150
151 let chosen = m
153 .iter()
154 .copied()
155 .find(|&min| min != target && !t.iselement(min));
156
157 if let Some(v) = chosen {
158 let isv_min = self.bfs_in(v, &keep);
161 let isv: Vec<VertexId> = isv_min.into_iter().filter(|&u| !s.iselement(u)).collect();
162 return Ok((v, isv));
163 }
164
165 Ok((0, Vec::new()))
166 }
167}
168
169pub fn all_st_mincuts(
207 graph: &Graph,
208 source: VertexId,
209 target: VertexId,
210 capacity: Option<&[f64]>,
211) -> IgraphResult<StMinCuts> {
212 let n = graph.vcount();
213 let m = graph.ecount();
214
215 if !graph.is_directed() {
216 return Err(IgraphError::InvalidArgument(
217 "all_st_mincuts: listing s-t cuts is only implemented for directed graphs".to_string(),
218 ));
219 }
220 if source >= n {
221 return Err(IgraphError::VertexOutOfRange { id: source, n });
222 }
223 if target >= n {
224 return Err(IgraphError::VertexOutOfRange { id: target, n });
225 }
226 if source == target {
227 return Err(IgraphError::InvalidArgument(
228 "all_st_mincuts: source and target must differ".to_string(),
229 ));
230 }
231 if let Some(cap) = capacity {
232 if cap.len() != m {
233 return Err(IgraphError::InvalidArgument(format!(
234 "all_st_mincuts: capacity length {} != edge count {m}",
235 cap.len()
236 )));
237 }
238 if cap.iter().any(|&c| !c.is_finite() || c <= 0.0) {
239 return Err(IgraphError::InvalidArgument(
240 "all_st_mincuts: all capacities must be strictly positive".to_string(),
241 ));
242 }
243 }
244
245 let mf = max_flow(graph, source, target, capacity)?;
247 let value = mf.value;
248 let flow = mf.flow;
249
250 let residual = reverse_residual_graph(graph, capacity, &flow)?;
252
253 let scc = strongly_connected_components(&residual)?;
256 let mut ntol = scc.membership; let residual = contract_vertices(&residual, &ntol)?;
258 let residual = simplify(&residual, true, true)?;
259
260 let n_res = residual.vcount() as usize;
261 let order = topological_sorting(&residual, DijkstraMode::Out)?; let mut inv_order = vec![0u32; n_res];
263 for (pos, &v) in order.iter().enumerate() {
264 inv_order[v as usize] = pos as u32;
265 }
266 for slot in &mut ntol {
268 *slot = inv_order[*slot as usize];
269 }
270 let residual = permute_vertices(&residual, &order)?;
274
275 let newsource = ntol[source as usize];
276 let newtarget = ntol[target as usize];
277
278 let mut active = vec![false; n_res];
280 for (e, &f) in flow.iter().enumerate() {
281 if f > 0.0 {
282 let (from, to) = graph.edge(e as EdgeId)?;
283 active[ntol[from as usize] as usize] = true;
284 active[ntol[to as usize] as usize] = true;
285 }
286 }
287
288 let mut pivot = MincutsPivot::new(&residual, active)?;
290 let closedsets = provan_shier_list(n_res, newsource, newtarget, &mut pivot)?;
291
292 let mut revmap: Vec<Vec<VertexId>> = vec![Vec::new(); n_res];
294 for i in 0..n as usize {
295 revmap[ntol[i] as usize].push(i as VertexId);
296 }
297
298 let mut partition1s: Vec<Vec<VertexId>> = Vec::with_capacity(closedsets.len());
299 for supercut in &closedsets {
300 let mut part: Vec<VertexId> = Vec::new();
301 for &vtx in supercut {
302 part.extend_from_slice(&revmap[vtx as usize]);
303 }
304 part.sort_unstable();
305 partition1s.push(part);
306 }
307
308 let mut cuts: Vec<Vec<EdgeId>> = Vec::with_capacity(partition1s.len());
310 let mut in_s = vec![false; n as usize];
311 for part in &partition1s {
312 for &v in part {
313 in_s[v as usize] = true;
314 }
315 let mut cut: Vec<EdgeId> = Vec::new();
316 for (e, &f) in flow.iter().enumerate() {
317 if f > 0.0 {
318 let (from, to) = graph.edge(e as EdgeId)?;
319 if in_s[from as usize] && !in_s[to as usize] {
320 cut.push(e as EdgeId);
321 }
322 }
323 }
324 cut.sort_unstable();
325 cuts.push(cut);
326 for &v in part {
327 in_s[v as usize] = false;
328 }
329 }
330
331 Ok(StMinCuts {
332 value,
333 cuts,
334 partition1s,
335 })
336}
337
338#[cfg(test)]
339mod tests {
340 use super::*;
341 use std::collections::HashSet;
342
343 fn assert_close(actual: f64, expected: f64) {
344 assert!(
345 (actual - expected).abs() < 1e-12,
346 "actual = {actual}, expected = {expected}"
347 );
348 }
349
350 fn build(n: u32, edges: &[(u32, u32)]) -> Graph {
351 let mut g = Graph::new(n, true).expect("directed graph");
352 for &(u, v) in edges {
353 g.add_edge(u, v).expect("add edge");
354 }
355 g
356 }
357
358 type CanonCut = (Vec<u32>, Vec<(u32, u32)>);
360 type ExpectedPairs<'a> = [(&'a [u32], &'a [(u32, u32)])];
362
363 fn cut_set(g: &Graph, res: &StMinCuts) -> HashSet<CanonCut> {
367 let mut out = HashSet::new();
368 for (part, cut) in res.partition1s.iter().zip(res.cuts.iter()) {
369 let mut endpoints: Vec<(u32, u32)> =
370 cut.iter().map(|&e| g.edge(e).expect("edge")).collect();
371 endpoints.sort_unstable();
372 out.insert((part.clone(), endpoints));
373 }
374 out
375 }
376
377 fn expected_set(pairs: &ExpectedPairs) -> HashSet<CanonCut> {
378 pairs
379 .iter()
380 .map(|(part, cut)| {
381 let mut p = part.to_vec();
382 p.sort_unstable();
383 let mut c = cut.to_vec();
384 c.sort_unstable();
385 (p, c)
386 })
387 .collect()
388 }
389
390 fn assert_consistent(g: &Graph, source: VertexId, target: VertexId, res: &StMinCuts) {
393 assert_eq!(res.cuts.len(), res.partition1s.len());
394 let n = g.vcount() as usize;
395 for (part, cut) in res.partition1s.iter().zip(res.cuts.iter()) {
396 assert!(part.contains(&source), "source missing from {part:?}");
397 assert!(!part.contains(&target), "target present in {part:?}");
398 let removed: HashSet<EdgeId> = cut.iter().copied().collect();
400 let mut adj = vec![Vec::new(); n];
401 for e in 0..g.ecount() as EdgeId {
402 if removed.contains(&e) {
403 continue;
404 }
405 let (from, to) = g.edge(e).expect("edge");
406 adj[from as usize].push(to);
407 }
408 let mut visited = vec![false; n];
409 let mut stack = vec![source];
410 visited[source as usize] = true;
411 while let Some(u) = stack.pop() {
412 for &w in &adj[u as usize] {
413 if !visited[w as usize] {
414 visited[w as usize] = true;
415 stack.push(w);
416 }
417 }
418 }
419 assert!(
420 !visited[target as usize],
421 "target reachable after removing cut {cut:?}"
422 );
423 }
424 }
425
426 #[test]
427 fn graph1_path_four_cuts() {
428 let g = build(5, &[(0, 1), (1, 2), (2, 3), (3, 4)]);
429 let res = all_st_mincuts(&g, 0, 4, None).expect("compute");
430 assert_close(res.value, 1.0);
431 let expected = expected_set(&[
432 (&[0], &[(0, 1)]),
433 (&[0, 1], &[(1, 2)]),
434 (&[0, 1, 2], &[(2, 3)]),
435 (&[0, 1, 2, 3], &[(3, 4)]),
436 ]);
437 assert_eq!(cut_set(&g, &res), expected);
438 assert_consistent(&g, 0, 4, &res);
439 }
440
441 #[test]
442 fn graph2_diamond_two_cuts() {
443 let g = build(6, &[(0, 1), (1, 2), (1, 3), (2, 4), (3, 4), (4, 5)]);
444 let res = all_st_mincuts(&g, 0, 5, None).expect("compute");
445 assert_close(res.value, 1.0);
446 let expected = expected_set(&[(&[0], &[(0, 1)]), (&[0, 1, 2, 3, 4], &[(4, 5)])]);
447 assert_eq!(cut_set(&g, &res), expected);
448 assert_consistent(&g, 0, 5, &res);
449 }
450
451 #[test]
452 fn graph3_single_cut() {
453 let g = build(6, &[(0, 1), (1, 2), (1, 3), (2, 4), (3, 4), (4, 5)]);
454 let res = all_st_mincuts(&g, 0, 4, None).expect("compute");
455 assert_close(res.value, 1.0);
456 let expected = expected_set(&[(&[0], &[(0, 1)])]);
457 assert_eq!(cut_set(&g, &res), expected);
458 assert_consistent(&g, 0, 4, &res);
459 }
460
461 #[test]
462 fn graph4_parallel_middle_three_cuts() {
463 let g = build(
464 9,
465 &[
466 (0, 1),
467 (0, 2),
468 (1, 3),
469 (2, 3),
470 (1, 4),
471 (4, 2),
472 (1, 5),
473 (5, 2),
474 (1, 6),
475 (6, 2),
476 (1, 7),
477 (7, 2),
478 (1, 8),
479 (8, 2),
480 ],
481 );
482 let res = all_st_mincuts(&g, 0, 3, None).expect("compute");
483 assert_close(res.value, 2.0);
484 let expected = expected_set(&[
485 (&[0], &[(0, 1), (0, 2)]),
486 (&[0, 2], &[(0, 1), (2, 3)]),
487 (&[0, 1, 2, 4, 5, 6, 7, 8], &[(1, 3), (2, 3)]),
488 ]);
489 assert_eq!(cut_set(&g, &res), expected);
490 assert_consistent(&g, 0, 3, &res);
491 }
492
493 #[test]
494 fn graph5_reverse_two_cuts() {
495 let g = build(4, &[(1, 0), (2, 0), (2, 1), (3, 2)]);
496 let res = all_st_mincuts(&g, 2, 0, None).expect("compute");
497 assert_close(res.value, 2.0);
498 let expected = expected_set(&[(&[2], &[(2, 0), (2, 1)]), (&[2, 1], &[(1, 0), (2, 0)])]);
499 assert_eq!(cut_set(&g, &res), expected);
500 assert_consistent(&g, 2, 0, &res);
501 }
502
503 #[test]
504 fn graph7_chain_five_cuts() {
505 let g = build(
506 9,
507 &[
508 (0, 4),
509 (0, 7),
510 (1, 6),
511 (2, 1),
512 (3, 8),
513 (4, 0),
514 (4, 2),
515 (4, 5),
516 (5, 0),
517 (5, 3),
518 (6, 7),
519 (7, 8),
520 ],
521 );
522 let res = all_st_mincuts(&g, 0, 8, None).expect("compute");
523 assert_close(res.value, 2.0);
524 let expected = expected_set(&[
525 (&[0], &[(0, 4), (0, 7)]),
526 (&[0, 7], &[(0, 4), (7, 8)]),
527 (&[0, 7, 4, 2, 1, 6], &[(4, 5), (7, 8)]),
528 (&[0, 7, 4, 2, 1, 6, 5], &[(5, 3), (7, 8)]),
529 (&[0, 7, 4, 2, 1, 6, 5, 3], &[(3, 8), (7, 8)]),
530 ]);
531 assert_eq!(cut_set(&g, &res), expected);
532 assert_consistent(&g, 0, 8, &res);
533 }
534
535 #[test]
536 fn rejects_undirected_graph() {
537 let g = Graph::with_vertices(4);
538 let err = all_st_mincuts(&g, 0, 3, None).expect_err("must reject undirected");
539 assert!(matches!(err, IgraphError::InvalidArgument(_)));
540 }
541
542 #[test]
543 fn rejects_equal_source_and_target() {
544 let g = build(3, &[(0, 1), (1, 2)]);
545 let err = all_st_mincuts(&g, 1, 1, None).expect_err("must reject s == t");
546 assert!(matches!(err, IgraphError::InvalidArgument(_)));
547 }
548
549 #[test]
550 fn rejects_out_of_range_endpoints() {
551 let g = build(3, &[(0, 1), (1, 2)]);
552 match all_st_mincuts(&g, 5, 1, None).expect_err("bad source") {
553 IgraphError::VertexOutOfRange { id, n } => assert_eq!((id, n), (5, 3)),
554 other => panic!("unexpected error: {other:?}"),
555 }
556 match all_st_mincuts(&g, 0, 9, None).expect_err("bad target") {
557 IgraphError::VertexOutOfRange { id, n } => assert_eq!((id, n), (9, 3)),
558 other => panic!("unexpected error: {other:?}"),
559 }
560 }
561
562 #[test]
563 fn rejects_bad_capacity() {
564 let g = build(3, &[(0, 1), (1, 2)]);
565 let err = all_st_mincuts(&g, 0, 2, Some(&[1.0])).expect_err("bad length");
567 assert!(matches!(err, IgraphError::InvalidArgument(_)));
568 let err = all_st_mincuts(&g, 0, 2, Some(&[1.0, 0.0])).expect_err("non-positive");
570 assert!(matches!(err, IgraphError::InvalidArgument(_)));
571 }
572}