1use crate::core::{Graph, IgraphError, IgraphResult};
25
26use super::st_mincut::st_mincut;
27
28#[derive(Debug, Clone)]
36pub struct GomoryHuTree {
37 pub tree: Graph,
40 pub flows: Vec<f64>,
48}
49
50pub fn gomory_hu_tree(graph: &Graph, capacity: Option<&[f64]>) -> IgraphResult<GomoryHuTree> {
115 if graph.is_directed() {
117 return Err(IgraphError::InvalidArgument(
118 "Gomory-Hu tree can only be calculated for undirected graphs".into(),
119 ));
120 }
121
122 let n = graph.vcount();
123
124 if n == 0 {
129 if let Some(c) = capacity {
130 if !c.is_empty() {
131 return Err(IgraphError::InvalidArgument(format!(
132 "capacity length {} does not match edge count 0",
133 c.len()
134 )));
135 }
136 }
137 return Ok(GomoryHuTree {
138 tree: Graph::new(0, false)?,
139 flows: Vec::new(),
140 });
141 }
142
143 if n == 1 {
145 if let Some(c) = capacity {
146 let m = graph.ecount();
147 if c.len() != m {
148 return Err(IgraphError::InvalidArgument(format!(
149 "capacity length {} does not match edge count {m}",
150 c.len()
151 )));
152 }
153 }
154 return Ok(GomoryHuTree {
155 tree: Graph::new(1, false)?,
156 flows: Vec::new(),
157 });
158 }
159
160 let n_usize = n as usize;
164 let mut neighbors: Vec<u32> = vec![0; n_usize];
165 let mut flow_values: Vec<f64> = vec![0.0; n_usize];
166
167 for source in 1..n {
171 let target = neighbors[source as usize];
172
173 let cut = st_mincut(graph, source, target, capacity)?;
174 let value = cut.value;
175 flow_values[source as usize] = value;
176
177 for &mid in &cut.partition {
184 if mid == source {
185 continue;
186 }
187 let mid_us = mid as usize;
188 let target_us = target as usize;
189 if neighbors[mid_us] == target {
190 neighbors[mid_us] = source;
191 } else if neighbors[target_us] == mid {
192 neighbors[target_us] = source;
199 neighbors[source as usize] = mid;
200 flow_values[source as usize] = flow_values[target_us];
201 flow_values[target_us] = value;
202 }
203 }
204 }
205
206 let mut tree = Graph::new(n, false)?;
208 let mut flows: Vec<f64> = Vec::with_capacity(n_usize - 1);
209 for i in 1..n {
210 tree.add_edge(i, neighbors[i as usize])?;
213 flows.push(flow_values[i as usize]);
214 }
215
216 Ok(GomoryHuTree { tree, flows })
217}
218
219#[cfg(test)]
220mod tests {
221 use super::*;
228 use crate::core::IgraphError;
229 use crate::max_flow_value;
230
231 const TOL: f64 = 1e-9;
232
233 fn validate_tree(graph: &Graph, gh: &GomoryHuTree, caps: Option<&[f64]>) {
237 let n = gh.tree.vcount();
238 let mut adj: Vec<Vec<(u32, f64)>> = vec![Vec::new(); n as usize];
240 for eid in 0..gh.tree.ecount() {
241 let eid_u32 = u32::try_from(eid).expect("tree.ecount() within u32 in validate_tree");
242 let (u, v) = gh.tree.edge(eid_u32).expect("tree edge");
243 let weight = gh.flows[eid];
244 adj[u as usize].push((v, weight));
245 adj[v as usize].push((u, weight));
246 }
247 for src in 0..n {
248 for dst in (src + 1)..n {
249 let mut parent: Vec<i64> = vec![-1; n as usize];
253 let mut parent_w: Vec<f64> = vec![0.0; n as usize];
254 let mut queue: Vec<u32> = vec![src];
255 let mut head = 0_usize;
256 parent[src as usize] = i64::from(src);
257 while head < queue.len() {
258 let u = queue[head];
259 head += 1;
260 if u == dst {
261 break;
262 }
263 for &(w, weight) in &adj[u as usize] {
264 if parent[w as usize] < 0 {
265 parent[w as usize] = i64::from(u);
266 parent_w[w as usize] = weight;
267 queue.push(w);
268 }
269 }
270 }
271 let mut min_w = f64::INFINITY;
272 let mut cur = dst;
273 while cur != src {
274 if parent_w[cur as usize] < min_w {
275 min_w = parent_w[cur as usize];
276 }
277 cur = u32::try_from(parent[cur as usize])
278 .expect("parent within u32 (set by BFS above)");
279 }
280 let mf = max_flow_value(graph, src, dst, caps).expect("max_flow_value");
281 assert!(
282 (min_w - mf).abs() <= TOL,
283 "Gomory-Hu property failed for (src={src}, dst={dst}): tree min={min_w}, max-flow={mf}"
284 );
285 }
286 }
287 }
288
289 #[test]
290 fn empty_graph_returns_empty_tree() {
291 let g = Graph::new(0, false).expect("graph");
292 let gh = gomory_hu_tree(&g, None).expect("gh");
293 assert_eq!(gh.tree.vcount(), 0);
294 assert_eq!(gh.tree.ecount(), 0);
295 assert!(gh.flows.is_empty());
296 }
297
298 #[test]
299 fn single_vertex_returns_one_vertex_tree() {
300 let g = Graph::new(1, false).expect("graph");
301 let gh = gomory_hu_tree(&g, None).expect("gh");
302 assert_eq!(gh.tree.vcount(), 1);
303 assert_eq!(gh.tree.ecount(), 0);
304 assert!(gh.flows.is_empty());
305 }
306
307 #[test]
308 fn rejects_directed_graph() {
309 let mut g = Graph::new(3, true).expect("graph");
310 g.add_edge(0, 1).expect("edge");
311 let err = gomory_hu_tree(&g, None).expect_err("must reject directed");
312 match err {
313 IgraphError::InvalidArgument(_) => {}
314 other => panic!("expected InvalidArgument, got {other:?}"),
315 }
316 }
317
318 #[test]
319 fn six_vertex_weighted_matches_c_reference() {
320 let mut g = Graph::new(6, false).expect("graph");
325 let edges = [
326 (0u32, 1u32),
327 (0, 2),
328 (1, 2),
329 (1, 3),
330 (1, 4),
331 (2, 4),
332 (3, 4),
333 (3, 5),
334 (4, 5),
335 ];
336 for (u, v) in edges {
337 g.add_edge(u, v).expect("edge");
338 }
339 let caps = [1.0, 7.0, 1.0, 3.0, 2.0, 4.0, 1.0, 6.0, 2.0];
340 let gh = gomory_hu_tree(&g, Some(&caps)).expect("gh");
341 assert_eq!(gh.tree.vcount(), 6);
342 assert_eq!(gh.tree.ecount(), 5);
343 assert_eq!(gh.flows.len(), 5);
344 validate_tree(&g, &gh, Some(&caps));
345 }
346
347 #[test]
348 fn k4_unit_capacities_matches_c_reference() {
349 let mut g = Graph::new(4, false).expect("graph");
352 for i in 0..4u32 {
353 for j in (i + 1)..4u32 {
354 g.add_edge(i, j).expect("edge");
355 }
356 }
357 let gh = gomory_hu_tree(&g, None).expect("gh");
358 assert_eq!(gh.tree.vcount(), 4);
359 assert_eq!(gh.tree.ecount(), 3);
360 for &w in &gh.flows {
361 assert!((w - 3.0).abs() <= TOL, "expected weight 3, got {w}");
362 }
363 validate_tree(&g, &gh, None);
364 }
365
366 #[test]
367 fn two_vertices_no_edge_zero_flow() {
368 let g = Graph::new(2, false).expect("graph");
370 let gh = gomory_hu_tree(&g, None).expect("gh");
371 assert_eq!(gh.tree.vcount(), 2);
372 assert_eq!(gh.tree.ecount(), 1);
373 assert!(gh.flows[0].abs() <= TOL);
374 }
375
376 #[test]
377 fn two_vertices_one_edge_unit_flow() {
378 let mut g = Graph::new(2, false).expect("graph");
379 g.add_edge(0, 1).expect("edge");
380 let gh = gomory_hu_tree(&g, None).expect("gh");
381 assert_eq!(gh.tree.ecount(), 1);
382 assert!((gh.flows[0] - 1.0).abs() <= TOL);
383 validate_tree(&g, &gh, None);
384 }
385
386 #[test]
387 fn two_vertices_parallel_edges_multigraph() {
388 let mut g = Graph::new(2, false).expect("graph");
390 g.add_edge(0, 1).expect("edge");
391 g.add_edge(0, 1).expect("edge");
392 g.add_edge(0, 1).expect("edge");
393 let gh = gomory_hu_tree(&g, None).expect("gh");
394 assert!((gh.flows[0] - 3.0).abs() <= TOL);
395 validate_tree(&g, &gh, None);
396 }
397
398 #[test]
399 fn path_5v_unit_caps_all_pairs_one() {
400 let mut g = Graph::new(5, false).expect("graph");
403 for i in 0..4u32 {
404 g.add_edge(i, i + 1).expect("edge");
405 }
406 let gh = gomory_hu_tree(&g, None).expect("gh");
407 assert_eq!(gh.tree.ecount(), 4);
408 for &w in &gh.flows {
409 assert!((w - 1.0).abs() <= TOL, "expected 1.0, got {w}");
410 }
411 validate_tree(&g, &gh, None);
412 }
413
414 #[test]
415 fn ring_5v_unit_caps_all_pairs_two() {
416 let mut g = Graph::new(5, false).expect("graph");
419 for i in 0..5u32 {
420 g.add_edge(i, (i + 1) % 5).expect("edge");
421 }
422 let gh = gomory_hu_tree(&g, None).expect("gh");
423 for &w in &gh.flows {
424 assert!((w - 2.0).abs() <= TOL, "expected 2.0, got {w}");
425 }
426 validate_tree(&g, &gh, None);
427 }
428
429 #[test]
430 fn disconnected_two_components_zero_cross_flows() {
431 let mut g = Graph::new(4, false).expect("graph");
436 g.add_edge(0, 1).expect("edge");
437 g.add_edge(2, 3).expect("edge");
438 let gh = gomory_hu_tree(&g, None).expect("gh");
439 assert_eq!(gh.tree.vcount(), 4);
440 assert_eq!(gh.tree.ecount(), 3);
441 validate_tree(&g, &gh, None);
442 }
443
444 #[test]
445 fn weighted_bridge_dominates_min_cut() {
446 let mut g = Graph::new(6, false).expect("graph");
451 for (u, v) in [(0u32, 1u32), (1, 2), (2, 0), (3, 4), (4, 5), (5, 3), (2, 3)] {
452 g.add_edge(u, v).expect("edge");
453 }
454 let caps = [10.0, 10.0, 10.0, 10.0, 10.0, 10.0, 0.5];
455 let gh = gomory_hu_tree(&g, Some(&caps)).expect("gh");
456 let min_flow = gh.flows.iter().copied().fold(f64::INFINITY, f64::min);
457 assert!(
458 (min_flow - 0.5).abs() <= TOL,
459 "min flow should be 0.5, got {min_flow}"
460 );
461 validate_tree(&g, &gh, Some(&caps));
462 }
463
464 #[test]
465 fn capacity_wrong_length_errors() {
466 let mut g = Graph::new(3, false).expect("graph");
467 g.add_edge(0, 1).expect("edge");
468 g.add_edge(1, 2).expect("edge");
469 let bad = vec![1.0_f64; 99];
470 let err = gomory_hu_tree(&g, Some(&bad)).expect_err("must err");
471 assert!(matches!(err, IgraphError::InvalidArgument(_)));
472 }
473
474 #[test]
475 fn capacity_negative_errors() {
476 let mut g = Graph::new(3, false).expect("graph");
477 g.add_edge(0, 1).expect("edge");
478 g.add_edge(1, 2).expect("edge");
479 let caps = [1.0_f64, -0.5];
480 let err = gomory_hu_tree(&g, Some(&caps)).expect_err("must err");
481 assert!(matches!(err, IgraphError::InvalidArgument(_)));
482 }
483
484 #[test]
485 fn capacity_nan_errors() {
486 let mut g = Graph::new(3, false).expect("graph");
487 g.add_edge(0, 1).expect("edge");
488 g.add_edge(1, 2).expect("edge");
489 let caps = [1.0_f64, f64::NAN];
490 let err = gomory_hu_tree(&g, Some(&caps)).expect_err("must err");
491 assert!(matches!(err, IgraphError::InvalidArgument(_)));
492 }
493
494 #[test]
495 fn empty_graph_rejects_nonempty_capacity() {
496 let g = Graph::new(0, false).expect("graph");
497 let caps = [1.0_f64];
498 let err = gomory_hu_tree(&g, Some(&caps)).expect_err("must err");
499 assert!(matches!(err, IgraphError::InvalidArgument(_)));
500 }
501
502 #[test]
503 fn unit_caps_match_explicit_unit_capacity_vector() {
504 let mut g = Graph::new(5, false).expect("graph");
509 for i in 0..5u32 {
510 for j in (i + 1)..5u32 {
511 g.add_edge(i, j).expect("edge");
512 }
513 }
514 let gh_none = gomory_hu_tree(&g, None).expect("gh_none");
515 let caps = vec![1.0_f64; g.ecount()];
516 let gh_unit = gomory_hu_tree(&g, Some(&caps)).expect("gh_unit");
517 let mut a = gh_none.flows.clone();
518 let mut b = gh_unit.flows.clone();
519 a.sort_by(|x, y| x.partial_cmp(y).expect("no NaN"));
520 b.sort_by(|x, y| x.partial_cmp(y).expect("no NaN"));
521 for (x, y) in a.iter().zip(b.iter()) {
522 assert!((x - y).abs() <= TOL, "weight mismatch: {x} vs {y}");
523 }
524 }
525
526 #[test]
527 fn signature_symbol_is_stable() {
528 const SYMBOL: fn(&Graph, Option<&[f64]>) -> IgraphResult<GomoryHuTree> = gomory_hu_tree;
530 let _ = SYMBOL;
531 }
532}
533
534#[cfg(all(test, feature = "proptest-harness"))]
535mod proptests {
536 use super::*;
552 use crate::max_flow_value;
553 use proptest::prelude::*;
554
555 const TOL: f64 = 1e-9;
556
557 fn xorshift(mut r: u64) -> u64 {
558 r ^= r << 13;
559 r ^= r >> 7;
560 r ^= r << 17;
561 r
562 }
563
564 fn build_random_undirected(seed: u64, n: u32, m_max: u32) -> Graph {
565 let mut g = Graph::new(n, false).expect("graph");
566 let mut state = seed | 1;
567 for _ in 0..m_max {
568 state = xorshift(state);
569 let u = u32::try_from(state % u64::from(n)).expect("modulo fits");
570 state = xorshift(state);
571 let v = u32::try_from(state % u64::from(n)).expect("modulo fits");
572 if u == v {
573 continue;
574 }
575 g.add_edge(u, v).expect("edge");
576 }
577 g
578 }
579
580 fn tree_min_weight_on_path(gh: &GomoryHuTree, src: u32, dst: u32) -> f64 {
581 let n = gh.tree.vcount();
582 let mut adj: Vec<Vec<(u32, f64)>> = vec![Vec::new(); n as usize];
583 for eid in 0..gh.tree.ecount() {
584 let eid_u32 = u32::try_from(eid).expect("ecount fits in u32");
585 let (u, v) = gh.tree.edge(eid_u32).expect("edge");
586 let w = gh.flows[eid];
587 adj[u as usize].push((v, w));
588 adj[v as usize].push((u, w));
589 }
590 let mut parent: Vec<i64> = vec![-1; n as usize];
591 let mut parent_w: Vec<f64> = vec![0.0; n as usize];
592 let mut queue: Vec<u32> = vec![src];
593 let mut head = 0_usize;
594 parent[src as usize] = i64::from(src);
595 while head < queue.len() {
596 let u = queue[head];
597 head += 1;
598 if u == dst {
599 break;
600 }
601 for &(w, weight) in &adj[u as usize] {
602 if parent[w as usize] < 0 {
603 parent[w as usize] = i64::from(u);
604 parent_w[w as usize] = weight;
605 queue.push(w);
606 }
607 }
608 }
609 let mut min_w = f64::INFINITY;
610 let mut cur = dst;
611 while cur != src {
612 if parent_w[cur as usize] < min_w {
613 min_w = parent_w[cur as usize];
614 }
615 cur = u32::try_from(parent[cur as usize]).expect("parent within u32");
616 }
617 min_w
618 }
619
620 proptest! {
621 #[test]
622 fn shape_invariants(
623 seed in any::<u64>(),
624 n in 1u32..7,
625 m in 0u32..12,
626 ) {
627 let g = build_random_undirected(seed, n, m);
628 let gh = gomory_hu_tree(&g, None).expect("gh");
629 prop_assert_eq!(gh.tree.vcount(), n);
630 prop_assert_eq!(gh.tree.ecount(), (n - 1) as usize);
631 prop_assert_eq!(gh.flows.len(), (n - 1) as usize);
632 for &w in &gh.flows {
633 prop_assert!(w.is_finite(), "flow {w} not finite");
634 prop_assert!(w >= 0.0, "flow {w} negative");
635 }
636 }
637
638 #[test]
639 fn gomory_hu_property_holds(
640 seed in any::<u64>(),
641 n in 2u32..6,
642 m in 0u32..10,
643 ) {
644 let g = build_random_undirected(seed, n, m);
645 let gh = gomory_hu_tree(&g, None).expect("gh");
646 for src in 0..n {
647 for dst in (src + 1)..n {
648 let tree_min = tree_min_weight_on_path(&gh, src, dst);
649 let mf = max_flow_value(&g, src, dst, None).expect("mf");
650 prop_assert!(
651 (tree_min - mf).abs() <= TOL,
652 "Gomory-Hu property violated for (src={}, dst={}): tree min={}, max-flow={}, seed={}",
653 src, dst, tree_min, mf, seed
654 );
655 }
656 }
657 }
658
659 #[test]
660 fn tree_is_connected(
661 seed in any::<u64>(),
662 n in 1u32..7,
663 m in 0u32..12,
664 ) {
665 let g = build_random_undirected(seed, n, m);
666 let gh = gomory_hu_tree(&g, None).expect("gh");
667 let mut seen = vec![false; n as usize];
671 seen[0] = true;
672 let mut queue: Vec<u32> = vec![0];
673 let mut head = 0_usize;
674 let mut adj: Vec<Vec<u32>> = vec![Vec::new(); n as usize];
675 for eid in 0..gh.tree.ecount() {
676 let eid_u32 = u32::try_from(eid).expect("ecount fits");
677 let (u, v) = gh.tree.edge(eid_u32).expect("edge");
678 adj[u as usize].push(v);
679 adj[v as usize].push(u);
680 }
681 while head < queue.len() {
682 let v = queue[head];
683 head += 1;
684 for &w in &adj[v as usize] {
685 if !seen[w as usize] {
686 seen[w as usize] = true;
687 queue.push(w);
688 }
689 }
690 }
691 for (v, &s) in seen.iter().enumerate() {
692 prop_assert!(s, "vertex {v} not reachable from 0 in the tree (seed={seed})");
693 }
694 }
695
696 #[test]
697 fn unit_caps_parity(
698 seed in any::<u64>(),
699 n in 1u32..6,
700 m in 0u32..10,
701 ) {
702 let g = build_random_undirected(seed, n, m);
703 let caps = vec![1.0_f64; g.ecount()];
704 let gh_none = gomory_hu_tree(&g, None).expect("gh_none");
705 let gh_unit = gomory_hu_tree(&g, Some(&caps)).expect("gh_unit");
706 let mut a = gh_none.flows.clone();
707 let mut b = gh_unit.flows.clone();
708 a.sort_by(|x, y| x.partial_cmp(y).expect("no NaN in a"));
709 b.sort_by(|x, y| x.partial_cmp(y).expect("no NaN in b"));
710 prop_assert_eq!(a.len(), b.len());
711 for (x, y) in a.iter().zip(b.iter()) {
712 prop_assert!((x - y).abs() <= TOL, "weight mismatch: {} vs {}", x, y);
713 }
714 }
715 }
716}