use crate::graph::core::Graph;
struct Residual {
n: usize,
head: Vec<usize>,
cap: Vec<f64>,
out: Vec<Vec<usize>>,
original: Vec<f64>,
}
impl Residual {
fn new(n: usize) -> Self {
Self {
n,
head: Vec::new(),
cap: Vec::new(),
out: vec![Vec::new(); n],
original: Vec::new(),
}
}
fn add(&mut self, u: usize, v: usize, c: f64) {
let i = self.head.len();
self.head.push(v);
self.cap.push(c);
self.original.push(c);
self.out[u].push(i);
self.head.push(u);
self.cap.push(0.0);
self.original.push(0.0);
self.out[v].push(i + 1);
}
fn add_both(&mut self, u: usize, v: usize, c: f64) {
let i = self.head.len();
self.head.push(v);
self.cap.push(c);
self.original.push(c);
self.out[u].push(i);
self.head.push(u);
self.cap.push(c);
self.original.push(c);
self.out[v].push(i + 1);
}
fn from_graph(g: &Graph) -> Self {
let mut r = Residual::new(g.n);
for (u, v, c) in g.edges() {
assert!(
c >= 0.0 && c.is_finite(),
"capacities must be finite and non-negative"
);
if u == v {
continue;
}
if g.directed {
r.add(u, v, c);
} else {
r.add_both(u, v, c);
}
}
r
}
fn reachable(&self, s: usize) -> Vec<bool> {
let mut seen = vec![false; self.n];
seen[s] = true;
let mut stack = vec![s];
while let Some(v) = stack.pop() {
for &i in &self.out[v] {
if self.cap[i] > EPS && !seen[self.head[i]] {
seen[self.head[i]] = true;
stack.push(self.head[i]);
}
}
}
seen
}
}
const EPS: f64 = 1e-9;
#[must_use]
pub fn max_flow_dinic(g: &Graph, s: usize, t: usize) -> (f64, Vec<Vec<f64>>) {
assert!(s < g.n && t < g.n, "endpoints must be vertices");
assert!(s != t, "source and sink must differ");
let mut r = Residual::from_graph(g);
let mut total = 0.0;
loop {
let mut level = vec![usize::MAX; r.n];
level[s] = 0;
let mut queue = std::collections::VecDeque::from(vec![s]);
while let Some(v) = queue.pop_front() {
for &i in &r.out[v] {
let w = r.head[i];
if r.cap[i] > EPS && level[w] == usize::MAX {
level[w] = level[v] + 1;
queue.push_back(w);
}
}
}
if level[t] == usize::MAX {
break;
}
let mut cursor = vec![0usize; r.n];
loop {
let pushed = dinic_augment(&mut r, s, t, f64::INFINITY, &level, &mut cursor);
if pushed <= EPS {
break;
}
total += pushed;
}
}
(total, flow_matrix(&r))
}
fn dinic_augment(
r: &mut Residual,
v: usize,
t: usize,
limit: f64,
level: &[usize],
cursor: &mut [usize],
) -> f64 {
if v == t {
return limit;
}
while cursor[v] < r.out[v].len() {
let i = r.out[v][cursor[v]];
let w = r.head[i];
if r.cap[i] > EPS && level[w] == level[v] + 1 {
let pushed = dinic_augment(r, w, t, limit.min(r.cap[i]), level, cursor);
if pushed > EPS {
r.cap[i] -= pushed;
r.cap[i ^ 1] += pushed;
return pushed;
}
}
cursor[v] += 1;
}
0.0
}
fn flow_matrix(r: &Residual) -> Vec<Vec<f64>> {
let mut m = vec![vec![0.0; r.n]; r.n];
for v in 0..r.n {
for &i in &r.out[v] {
let used = r.original[i] - r.cap[i];
if used > EPS {
m[v][r.head[i]] += used;
}
}
}
for u in 0..r.n {
for v in u + 1..r.n {
let net = m[u][v] - m[v][u];
m[u][v] = net.max(0.0);
m[v][u] = (-net).max(0.0);
}
}
m
}
#[must_use]
pub fn max_flow_push_relabel(g: &Graph, s: usize, t: usize) -> f64 {
assert!(s < g.n && t < g.n, "endpoints must be vertices");
assert!(s != t, "source and sink must differ");
let mut r = Residual::from_graph(g);
let n = r.n;
let mut height = vec![0usize; n];
let mut excess = vec![0.0f64; n];
height[s] = n;
for idx in 0..r.out[s].len() {
let i = r.out[s][idx];
let c = r.cap[i];
if c > EPS {
r.cap[i] -= c;
r.cap[i ^ 1] += c;
excess[r.head[i]] += c;
excess[s] -= c;
}
}
let mut cursor = vec![0usize; n];
while let Some(v) = (0..n)
.filter(|&v| v != s && v != t && excess[v] > EPS)
.max_by_key(|&v| height[v])
{
if cursor[v] == r.out[v].len() {
let min_h = r.out[v]
.iter()
.filter(|&&i| r.cap[i] > EPS)
.map(|&i| height[r.head[i]])
.min();
match min_h {
Some(h) => height[v] = h + 1,
None => break,
}
cursor[v] = 0;
continue;
}
let i = r.out[v][cursor[v]];
let w = r.head[i];
if r.cap[i] > EPS && height[v] == height[w] + 1 {
let delta = excess[v].min(r.cap[i]);
r.cap[i] -= delta;
r.cap[i ^ 1] += delta;
excess[v] -= delta;
excess[w] += delta;
} else {
cursor[v] += 1;
}
}
excess[t]
}
#[must_use]
pub fn min_cut(g: &Graph, s: usize, t: usize) -> (f64, Vec<bool>) {
assert!(s < g.n && t < g.n, "endpoints must be vertices");
assert!(s != t, "source and sink must differ");
let mut r = Residual::from_graph(g);
let mut total = 0.0;
loop {
let mut level = vec![usize::MAX; r.n];
level[s] = 0;
let mut queue = std::collections::VecDeque::from(vec![s]);
while let Some(v) = queue.pop_front() {
for &i in &r.out[v] {
let w = r.head[i];
if r.cap[i] > EPS && level[w] == usize::MAX {
level[w] = level[v] + 1;
queue.push_back(w);
}
}
}
if level[t] == usize::MAX {
break;
}
let mut cursor = vec![0usize; r.n];
loop {
let pushed = dinic_augment(&mut r, s, t, f64::INFINITY, &level, &mut cursor);
if pushed <= EPS {
break;
}
total += pushed;
}
}
(total, r.reachable(s))
}
#[must_use]
pub fn global_min_cut_stoer_wagner(g: &Graph) -> (f64, Vec<usize>) {
assert!(!g.directed, "Stoer-Wagner is for undirected graphs");
assert!(g.n >= 2, "a cut needs at least two vertices");
let n = g.n;
let mut w = vec![vec![0.0f64; n]; n];
for (u, v, c) in g.edges() {
if u != v {
w[u][v] += c;
w[v][u] += c;
}
}
let mut group: Vec<Vec<usize>> = (0..n).map(|v| vec![v]).collect();
let mut alive: Vec<usize> = (0..n).collect();
let mut best = f64::INFINITY;
let mut best_side: Vec<usize> = Vec::new();
while alive.len() > 1 {
let mut added = vec![false; n];
let mut weight = vec![0.0f64; n];
let mut order: Vec<usize> = Vec::with_capacity(alive.len());
for _ in 0..alive.len() {
let v = *alive
.iter()
.filter(|&&v| !added[v])
.max_by(|&&a, &&b| weight[a].total_cmp(&weight[b]))
.expect("a vertex remains");
added[v] = true;
order.push(v);
for &u in &alive {
if !added[u] {
weight[u] += w[v][u];
}
}
}
let last = *order.last().unwrap();
let prev = order[order.len() - 2];
if weight[last] < best {
best = weight[last];
best_side = group[last].clone();
}
let merged: Vec<usize> = group[last].clone();
group[prev].extend(merged);
for &u in &alive {
if u != last && u != prev {
w[prev][u] += w[last][u];
w[u][prev] = w[prev][u];
}
}
alive.retain(|&v| v != last);
}
best_side.sort_unstable();
(best, best_side)
}
#[must_use]
pub fn min_cost_max_flow(g: &Graph, costs: &[f64], s: usize, t: usize) -> (f64, f64) {
assert!(s < g.n && t < g.n, "endpoints must be vertices");
assert!(s != t, "source and sink must differ");
let edges = g.edges();
assert_eq!(costs.len(), edges.len(), "one cost per edge is required");
let mut r = Residual::new(g.n);
let mut arc_cost: Vec<f64> = Vec::new();
for (&(u, v, c), &cost) in edges.iter().zip(costs) {
assert!(
c >= 0.0 && c.is_finite(),
"capacities must be finite and non-negative"
);
if u == v {
continue;
}
r.add(u, v, c);
arc_cost.push(cost);
arc_cost.push(-cost);
if !g.directed {
r.add(v, u, c);
arc_cost.push(cost);
arc_cost.push(-cost);
}
}
let mut flow = 0.0;
let mut cost_total = 0.0;
loop {
let mut dist = vec![f64::INFINITY; r.n];
let mut from: Vec<Option<usize>> = vec![None; r.n];
dist[s] = 0.0;
for _ in 0..r.n {
let mut changed = false;
for v in 0..r.n {
if !dist[v].is_finite() {
continue;
}
for &i in &r.out[v] {
if r.cap[i] <= EPS {
continue;
}
let cand = dist[v] + arc_cost[i];
if cand < dist[r.head[i]] - 1e-12 {
dist[r.head[i]] = cand;
from[r.head[i]] = Some(i);
changed = true;
}
}
}
if !changed {
break;
}
}
if !dist[t].is_finite() {
break;
}
let mut push = f64::INFINITY;
let mut v = t;
while let Some(i) = from[v] {
push = push.min(r.cap[i]);
v = r.head[i ^ 1];
if v == s {
break;
}
}
if push <= EPS || !push.is_finite() {
break;
}
let mut v = t;
while let Some(i) = from[v] {
r.cap[i] -= push;
r.cap[i ^ 1] += push;
cost_total += push * arc_cost[i];
v = r.head[i ^ 1];
if v == s {
break;
}
}
flow += push;
}
(flow, cost_total)
}
#[must_use]
pub fn circulation_with_demands(
g: &Graph,
demand: &[f64],
lower: &[f64],
) -> Option<Vec<f64>> {
let edges = g.edges();
assert_eq!(demand.len(), g.n, "one demand per vertex is required");
assert_eq!(lower.len(), edges.len(), "one lower bound per edge");
assert!(
demand.iter().sum::<f64>().abs() < 1e-9,
"demands must sum to zero for a circulation to exist"
);
assert!(
edges.iter().zip(lower).all(|(&(_, _, c), &l)| l >= 0.0 && l <= c + 1e-12),
"each lower bound must lie within its capacity"
);
let src = g.n;
let snk = g.n + 1;
let mut net = Graph::new(g.n + 2, true);
let mut adjusted = demand.to_vec();
for (&(u, v, c), &l) in edges.iter().zip(lower) {
net.add_edge(u, v, c - l);
adjusted[u] += l;
adjusted[v] -= l;
}
let mut required = 0.0;
for v in 0..g.n {
if adjusted[v] > 0.0 {
net.add_edge(v, snk, adjusted[v]);
required += adjusted[v];
} else if adjusted[v] < 0.0 {
net.add_edge(src, v, -adjusted[v]);
}
}
let (value, matrix) = max_flow_dinic(&net, src, snk);
if (value - required).abs() > 1e-6 {
return None;
}
Some(
edges
.iter()
.zip(lower)
.map(|(&(u, v, _), &l)| l + matrix[u][v])
.collect(),
)
}
#[must_use]
pub fn max_bipartite_matching_via_flow(g: &Graph, left: &[usize]) -> Vec<Option<usize>> {
let mut is_left = vec![false; g.n];
for &v in left {
assert!(v < g.n, "vertex {v} is outside 0..{}", g.n);
assert!(!is_left[v], "vertex {v} appears twice");
is_left[v] = true;
}
let src = g.n;
let snk = g.n + 1;
let mut net = Graph::new(g.n + 2, true);
for (u, v, _) in g.edges() {
if u == v {
continue;
}
assert!(
is_left[u] != is_left[v],
"edge ({u}, {v}) joins the same side"
);
let (a, b) = if is_left[u] { (u, v) } else { (v, u) };
net.add_edge(a, b, 1.0);
}
for v in 0..g.n {
if is_left[v] {
net.add_edge(src, v, 1.0);
} else {
net.add_edge(v, snk, 1.0);
}
}
let (_, matrix) = max_flow_dinic(&net, src, snk);
let mut partner = vec![None; g.n];
for u in 0..g.n {
if !is_left[u] {
continue;
}
for v in 0..g.n {
if !is_left[v] && matrix[u][v] > 0.5 {
partner[u] = Some(v);
partner[v] = Some(u);
break;
}
}
}
partner
}
#[must_use]
pub fn edge_disjoint_paths(g: &Graph, s: usize, t: usize) -> usize {
let mut unit = Graph::new(g.n, g.directed);
for (u, v, _) in g.edges() {
if u != v {
unit.add_edge(u, v, 1.0);
}
}
max_flow_dinic(&unit, s, t).0.round() as usize
}
#[must_use]
pub fn vertex_disjoint_paths(g: &Graph, s: usize, t: usize) -> usize {
assert!(s < g.n && t < g.n, "endpoints must be vertices");
assert!(s != t, "source and sink must differ");
let n = g.n;
let mut split = Graph::new(2 * n, true);
for v in 0..n {
let cap = if v == s || v == t { n as f64 } else { 1.0 };
split.add_edge(v, v + n, cap);
}
for (u, v, _) in g.edges() {
if u == v {
continue;
}
split.add_edge(u + n, v, 1.0);
if !g.directed {
split.add_edge(v + n, u, 1.0);
}
}
max_flow_dinic(&split, s + n, t).0.round() as usize
}
#[must_use]
pub fn gomory_hu_tree(g: &Graph) -> Graph {
assert!(!g.directed, "a Gomory-Hu tree is defined for undirected graphs");
let n = g.n;
let mut tree = Graph::new(n, false);
if n < 2 {
return tree;
}
let mut parent = vec![0usize; n];
for i in 1..n {
let (value, side) = min_cut(g, i, parent[i]);
tree.add_edge(i, parent[i], value);
for j in i + 1..n {
if side[j] && parent[j] == parent[i] {
parent[j] = i;
}
}
}
tree
}
#[must_use]
pub fn closure_problem(g: &Graph, weights: &[f64]) -> (f64, Vec<bool>) {
assert_eq!(weights.len(), g.n, "one weight per vertex is required");
let n = g.n;
let src = n;
let snk = n + 1;
let mut net = Graph::new(n + 2, true);
let mut positive_total = 0.0;
for v in 0..n {
if weights[v] > 0.0 {
net.add_edge(src, v, weights[v]);
positive_total += weights[v];
} else if weights[v] < 0.0 {
net.add_edge(v, snk, -weights[v]);
}
}
let big = positive_total * 2.0 + 1.0;
for (u, v, _) in g.edges() {
if u != v {
net.add_edge(u, v, big);
}
}
let (cut, side) = min_cut(&net, src, snk);
let members: Vec<bool> = (0..n).map(|v| side[v]).collect();
(positive_total - cut, members)
}
#[must_use]
pub fn project_selection(
project_revenue: &[f64],
machine_cost: &[f64],
requires: &[Vec<usize>],
) -> f64 {
assert_eq!(
requires.len(),
project_revenue.len(),
"one requirement list per project"
);
let (p, m) = (project_revenue.len(), machine_cost.len());
let mut g = Graph::new(p + m, true);
for (i, reqs) in requires.iter().enumerate() {
for &j in reqs {
assert!(j < m, "machine {j} is outside 0..{m}");
g.add_edge(i, p + j, 1.0);
}
}
let mut weights = project_revenue.to_vec();
weights.extend(machine_cost.iter().map(|c| -c));
closure_problem(&g, &weights).0
}
#[must_use]
pub fn max_flow(g: &Graph, s: usize, t: usize) -> f64 {
max_flow_dinic(g, s, t).0
}
#[must_use]
pub fn cut_capacity(g: &Graph, side: &[bool]) -> f64 {
assert_eq!(side.len(), g.n, "one side flag per vertex is required");
g.edges()
.into_iter()
.filter(|&(u, v, _)| {
(side[u] && !side[v]) || (!g.directed && side[v] && !side[u])
})
.map(|(_, _, c)| c)
.sum()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::graph::core::{complete_bipartite, complete_graph, cycle_graph, path_graph};
use crate::monte_carlo::Rng;
fn close(a: f64, b: f64) -> bool {
(a - b).abs() < 1e-6 * a.abs().max(b.abs()).max(1.0)
}
fn random_network(n: usize, p: f64, directed: bool, rng: &mut Rng) -> Graph {
let mut g = Graph::new(n, directed);
for u in 0..n {
let start = if directed { 0 } else { u + 1 };
for v in start..n {
if u != v && rng.next_f64() < p {
g.add_edge(u, v, 1.0 + (10.0 * rng.next_f64()).floor());
}
}
}
g
}
fn check_flow(g: &Graph, m: &[Vec<f64>], s: usize, t: usize, value: f64) {
let n = g.n;
let mut cap = vec![vec![0.0f64; n]; n];
for (u, v, c) in g.edges() {
if u == v {
continue;
}
cap[u][v] += c;
if !g.directed {
cap[v][u] += c;
}
}
for u in 0..n {
for v in 0..n {
assert!(
m[u][v] <= cap[u][v] + 1e-6,
"flow {} on ({u}, {v}) exceeds capacity {}",
m[u][v],
cap[u][v]
);
assert!(m[u][v] >= -1e-9, "negative flow on ({u}, {v})");
}
}
for v in 0..n {
if v == s || v == t {
continue;
}
let inflow: f64 = (0..n).map(|u| m[u][v]).sum();
let outflow: f64 = (0..n).map(|w| m[v][w]).sum();
assert!(
close(inflow, outflow),
"vertex {v} leaks: in {inflow}, out {outflow}"
);
}
let out_s: f64 = (0..n).map(|w| m[s][w]).sum();
let in_s: f64 = (0..n).map(|u| m[u][s]).sum();
assert!(close(out_s - in_s, value), "source net is not the value");
let in_t: f64 = (0..n).map(|u| m[u][t]).sum();
let out_t: f64 = (0..n).map(|w| m[t][w]).sum();
assert!(close(in_t - out_t, value), "sink net is not the value");
}
#[test]
fn max_flow_equals_min_cut() {
let mut rng = Rng::new(0x_F10A);
for directed in [false, true] {
for n in 2..=7usize {
for _ in 0..8 {
let g = random_network(n, 0.45, directed, &mut rng);
for s in 0..n {
for t in 0..n {
if s == t {
continue;
}
let (value, m) = max_flow_dinic(&g, s, t);
check_flow(&g, &m, s, t, value);
let pr = max_flow_push_relabel(&g, s, t);
assert!(close(value, pr), "dinic {value} vs push-relabel {pr}");
let (cut, side) = min_cut(&g, s, t);
assert!(close(value, cut), "flow {value} vs cut {cut}");
assert!(side[s] && !side[t], "the cut does not separate s from t");
assert!(
close(cut, cut_capacity(&g, &side)),
"the reported capacity is not the cut's"
);
if n <= 6 {
let best = brute_min_cut(&g, s, t);
assert!(close(cut, best), "n = {n}: {cut} vs brute {best}");
}
}
}
}
}
}
}
fn brute_min_cut(g: &Graph, s: usize, t: usize) -> f64 {
let n = g.n;
let mut best = f64::INFINITY;
for mask in 0u64..(1u64 << n) {
let side: Vec<bool> = (0..n).map(|v| mask >> v & 1 == 1).collect();
if !side[s] || side[t] {
continue;
}
best = best.min(cut_capacity(g, &side));
}
best
}
#[test]
fn known_networks_give_known_flows() {
let g = Graph::from_edges(
6,
&[
(0, 1, 16.0),
(0, 2, 13.0),
(1, 2, 10.0),
(2, 1, 4.0),
(1, 3, 12.0),
(3, 2, 9.0),
(2, 4, 14.0),
(4, 3, 7.0),
(3, 5, 20.0),
(4, 5, 4.0),
],
true,
);
let (value, m) = max_flow_dinic(&g, 0, 5);
assert!(close(value, 23.0), "expected 23, got {value}");
check_flow(&g, &m, 0, 5, value);
assert!(close(max_flow_push_relabel(&g, 0, 5), 23.0));
let (cut, side) = min_cut(&g, 0, 5);
assert!(close(cut, 23.0));
assert!(side[0] && !side[5]);
let chain = Graph::from_edges(
4,
&[(0, 1, 5.0), (1, 2, 3.0), (2, 3, 7.0)],
true,
);
assert!(close(max_flow_dinic(&chain, 0, 3).0, 3.0));
let parallel = Graph::from_edges(
4,
&[(0, 1, 5.0), (1, 3, 5.0), (0, 2, 2.0), (2, 3, 2.0)],
true,
);
assert!(close(max_flow_dinic(¶llel, 0, 3).0, 7.0));
assert!(close(max_flow_dinic(&Graph::new(3, true), 0, 2).0, 0.0));
}
#[test]
fn stoer_wagner_matches_the_best_st_cut() {
let mut rng = Rng::new(0x_570E);
for n in 2..=8usize {
for _ in 0..15 {
let g = random_network(n, 0.5, false, &mut rng);
let (global, side) = global_min_cut_stoer_wagner(&g);
let mut best = f64::INFINITY;
for s in 0..n {
for t in s + 1..n {
best = best.min(min_cut(&g, s, t).0);
}
}
assert!(close(global, best), "n = {n}: global {global} vs best {best}");
assert!(!side.is_empty() && side.len() < n, "not a proper cut");
let flags: Vec<bool> = (0..n).map(|v| side.contains(&v)).collect();
assert!(
close(global, cut_capacity(&g, &flags)),
"the reported side does not have the reported capacity"
);
}
}
let mut split = Graph::new(4, false);
split.add_edge(0, 1, 5.0);
split.add_edge(2, 3, 5.0);
assert!(close(global_min_cut_stoer_wagner(&split).0, 0.0));
let c = cycle_graph(6);
assert!(close(global_min_cut_stoer_wagner(&c).0, 2.0));
for n in 2..=7usize {
let k = complete_graph(n);
assert!(
close(global_min_cut_stoer_wagner(&k).0, (n - 1) as f64),
"K{n}"
);
}
}
#[test]
fn min_cost_flow_is_max_flow_at_least_cost() {
let g = Graph::from_edges(
4,
&[(0, 1, 1.0), (1, 3, 1.0), (0, 2, 5.0), (2, 3, 5.0)],
true,
);
let costs: Vec<f64> = g
.edges()
.iter()
.map(|&(u, v, _)| if u == 1 || v == 1 { 1.0 } else { 10.0 })
.collect();
let (flow, cost) = min_cost_max_flow(&g, &costs, 0, 3);
assert!(close(flow, 6.0), "max flow is 6");
assert!(close(cost, 1.0 * 2.0 + 5.0 * 20.0), "got {cost}");
assert!(close(flow, max_flow_dinic(&g, 0, 3).0));
let chain = Graph::from_edges(3, &[(0, 1, 4.0), (1, 2, 2.0)], true);
let (f, c) = min_cost_max_flow(&chain, &[3.0, 5.0], 0, 2);
assert!(close(f, 2.0));
assert!(close(c, 2.0 * 8.0));
let neg = Graph::from_edges(
4,
&[(0, 1, 2.0), (1, 3, 2.0), (0, 2, 2.0), (2, 3, 2.0)],
true,
);
let (f2, c2) = min_cost_max_flow(&neg, &[1.0, 1.0, -3.0, 1.0], 0, 3);
assert!(close(f2, 4.0));
assert!(close(c2, 2.0 * 2.0 + 2.0 * (-2.0)), "got {c2}");
let mut rng = Rng::new(0x_C155);
for n in 2..=6usize {
for _ in 0..12 {
let g = random_network(n, 0.5, true, &mut rng);
let costs: Vec<f64> = g
.edges()
.iter()
.map(|_| (5.0 * rng.next_f64()).floor())
.collect();
for s in 0..n {
for t in 0..n {
if s == t {
continue;
}
let (f, _) = min_cost_max_flow(&g, &costs, s, t);
let mf = max_flow_dinic(&g, s, t).0;
assert!(close(f, mf), "n = {n}: mcmf {f} vs maxflow {mf}");
}
}
}
}
}
#[test]
fn mengers_theorem_holds_in_both_forms() {
let mut rng = Rng::new(0x_3E17);
for n in 2..=6usize {
for _ in 0..10 {
let g = random_network(n, 0.4, false, &mut rng);
for s in 0..n {
for t in 0..n {
if s == t {
continue;
}
let k = edge_disjoint_paths(&g, s, t);
let cut = brute_edge_cut(&g, s, t);
assert_eq!(k, cut, "edge Menger at {s}->{t}, n = {n}");
let kv = vertex_disjoint_paths(&g, s, t);
let cutv = brute_vertex_cut(&g, s, t);
assert_eq!(kv, cutv, "vertex Menger at {s}->{t}, n = {n}");
}
}
}
}
let c = cycle_graph(7);
for s in 0..7 {
for t in 0..7 {
if s != t {
assert_eq!(edge_disjoint_paths(&c, s, t), 2);
assert_eq!(vertex_disjoint_paths(&c, s, t), 2);
}
}
}
let p = path_graph(5);
assert_eq!(edge_disjoint_paths(&p, 0, 4), 1);
assert_eq!(vertex_disjoint_paths(&p, 0, 4), 1);
for n in 2..=6usize {
let k = complete_graph(n);
assert_eq!(vertex_disjoint_paths(&k, 0, 1), n - 1, "K{n}");
}
}
fn brute_edge_cut(g: &Graph, s: usize, t: usize) -> usize {
let edges = g.edges();
for k in 0..=edges.len() {
for combo in crate::discrete::combinatorics::combinations_iter(edges.len(), k) {
let mut h = Graph::new(g.n, g.directed);
for (i, &(u, v, _)) in edges.iter().enumerate() {
if !combo.contains(&i) && u != v {
h.add_edge(u, v, 1.0);
}
}
if h.bfs(s)[t].is_none() {
return k;
}
}
}
edges.len()
}
fn brute_vertex_cut(g: &Graph, s: usize, t: usize) -> usize {
let paths = all_simple_paths(g, s, t);
let mut best = 0usize;
for k in (1..=paths.len()).rev() {
if k <= best {
break;
}
let mut feasible = false;
for combo in crate::discrete::combinatorics::combinations_iter(paths.len(), k) {
let mut used = vec![false; g.n];
let mut direct = 0usize;
let mut ok = true;
for &i in &combo {
let p = &paths[i];
if p.len() == 2 {
direct += 1;
continue;
}
for &v in &p[1..p.len() - 1] {
if used[v] {
ok = false;
break;
}
used[v] = true;
}
if !ok {
break;
}
}
let copies = g
.edges()
.iter()
.filter(|&&(u, v, _)| (u == s && v == t) || (u == t && v == s))
.count();
if ok && direct <= copies {
feasible = true;
break;
}
}
if feasible {
best = k;
break;
}
}
best
}
fn all_simple_paths(g: &Graph, s: usize, t: usize) -> Vec<Vec<usize>> {
fn go(
g: &Graph,
cur: usize,
t: usize,
on_path: &mut Vec<bool>,
path: &mut Vec<usize>,
out: &mut Vec<Vec<usize>>,
) {
if cur == t {
out.push(path.clone());
return;
}
for idx in 0..g.adj[cur].len() {
let w = g.adj[cur][idx].0;
if !on_path[w] {
on_path[w] = true;
path.push(w);
go(g, w, t, on_path, path, out);
path.pop();
on_path[w] = false;
}
}
}
let mut on_path = vec![false; g.n];
on_path[s] = true;
let mut path = vec![s];
let mut out = Vec::new();
go(g, s, t, &mut on_path, &mut path, &mut out);
out.sort();
out.dedup();
out
}
#[test]
fn gomory_hu_encodes_every_pairwise_cut() {
let mut rng = Rng::new(0x_6017);
for n in 2..=7usize {
for _ in 0..12 {
let g = random_network(n, 0.55, false, &mut rng);
if !g.is_connected() {
continue;
}
let tree = gomory_hu_tree(&g);
assert_eq!(tree.n, n);
assert_eq!(tree.edge_count(), n - 1, "not a tree");
assert!(tree.is_tree());
for s in 0..n {
for t in s + 1..n {
let direct = min_cut(&g, s, t).0;
let on_tree = lightest_on_tree_path(&tree, s, t);
assert!(
close(direct, on_tree),
"n = {n}, {s}-{t}: cut {direct} vs tree {on_tree}"
);
}
}
}
}
}
fn lightest_on_tree_path(t: &Graph, s: usize, e: usize) -> f64 {
let mut prev: Vec<Option<usize>> = vec![None; t.n];
let mut seen = vec![false; t.n];
seen[s] = true;
let mut queue = std::collections::VecDeque::from(vec![s]);
while let Some(v) = queue.pop_front() {
for &(w, _) in &t.adj[v] {
if !seen[w] {
seen[w] = true;
prev[w] = Some(v);
queue.push_back(w);
}
}
}
let mut best = f64::INFINITY;
let mut cur = e;
while let Some(p) = prev[cur] {
let w = t.adj[cur]
.iter()
.filter(|&&(x, _)| x == p)
.map(|&(_, w)| w)
.fold(f64::INFINITY, f64::min);
best = best.min(w);
cur = p;
if cur == s {
break;
}
}
best
}
#[test]
fn circulation_respects_bounds_and_demands() {
let g = Graph::from_edges(3, &[(0, 1, 3.0), (1, 2, 3.0)], true);
let demand = vec![-2.0, 0.0, 2.0];
let lower = vec![0.0, 0.0];
let f = circulation_with_demands(&g, &demand, &lower).expect("feasible");
assert!(close(f[0], 2.0) && close(f[1], 2.0), "got {f:?}");
let g2 = Graph::from_edges(3, &[(0, 1, 5.0), (1, 2, 5.0), (0, 2, 5.0)], true);
let e2 = g2.edges();
let lower2: Vec<f64> = e2
.iter()
.map(|&(u, v, _)| if (u, v) == (0, 2) { 0.0 } else { 3.0 })
.collect();
let f2 = circulation_with_demands(&g2, &[-4.0, 0.0, 4.0], &lower2).expect("feasible");
for (i, &(u, v, c)) in e2.iter().enumerate() {
assert!(f2[i] >= lower2[i] - 1e-9, "({u}, {v}) below its lower bound");
assert!(f2[i] <= c + 1e-9, "({u}, {v}) exceeds its capacity");
}
for v in 0..3 {
let inflow: f64 = e2
.iter()
.enumerate()
.filter(|(_, &(_, b, _))| b == v)
.map(|(i, _)| f2[i])
.sum();
let outflow: f64 = e2
.iter()
.enumerate()
.filter(|(_, &(a, _, _))| a == v)
.map(|(i, _)| f2[i])
.sum();
let want = [-4.0, 0.0, 4.0][v];
assert!(
close(inflow - outflow, want),
"vertex {v}: net {} but demand {want}",
inflow - outflow
);
}
let all_three: Vec<f64> = vec![3.0; 3];
assert!(circulation_with_demands(&g2, &[-4.0, 0.0, 4.0], &all_three).is_none());
assert!(circulation_with_demands(&g, &[-5.0, 0.0, 5.0], &[0.0, 0.0]).is_none());
let g3 = Graph::from_edges(2, &[(0, 1, 2.0)], true);
assert!(circulation_with_demands(&g3, &[0.0, 0.0], &[1.0]).is_none());
}
#[test]
fn closure_and_project_selection_match_brute_force() {
let mut rng = Rng::new(0x_C105);
for n in 1..=8usize {
for _ in 0..15 {
let mut g = Graph::new(n, true);
for u in 0..n {
for v in 0..n {
if u != v && rng.next_f64() < 0.25 {
g.add_edge(u, v, 1.0);
}
}
}
let weights: Vec<f64> =
(0..n).map(|_| (20.0 * rng.next_f64() - 10.0).round()).collect();
let (best, members) = closure_problem(&g, &weights);
for (u, v, _) in g.edges() {
if members[u] {
assert!(members[v], "closure omits successor {v} of {u}");
}
}
let got: f64 = (0..n).filter(|&v| members[v]).map(|v| weights[v]).sum();
assert!(close(got, best), "reported weight {best} vs actual {got}");
let mut brute = f64::NEG_INFINITY;
for mask in 0u64..(1u64 << n) {
let inside: Vec<bool> = (0..n).map(|v| mask >> v & 1 == 1).collect();
if g.edges().iter().any(|&(u, v, _)| inside[u] && !inside[v]) {
continue;
}
let w: f64 = (0..n).filter(|&v| inside[v]).map(|v| weights[v]).sum();
brute = brute.max(w);
}
assert!(close(best, brute), "n = {n}: {best} vs brute {brute}");
}
}
let profit = project_selection(&[10.0, 10.0], &[15.0], &[vec![0], vec![0]]);
assert!(close(profit, 5.0), "20 revenue minus one 15 machine, got {profit}");
let none = project_selection(&[5.0], &[15.0], &[vec![0]]);
assert!(close(none, 0.0), "got {none}");
let free = project_selection(&[3.0, -1.0, 4.0], &[], &[vec![], vec![], vec![]]);
assert!(close(free, 7.0), "got {free}");
}
#[test]
fn bipartite_matching_via_flow_is_valid_and_maximum() {
let mut rng = Rng::new(0x_B1A4);
for l in 1..=5usize {
for r in 1..=5usize {
for _ in 0..15 {
let n = l + r;
let mut g = Graph::new(n, false);
for a in 0..l {
for b in 0..r {
if rng.next_f64() < 0.5 {
g.add_edge(a, l + b, 1.0);
}
}
}
let left: Vec<usize> = (0..l).collect();
let m = max_bipartite_matching_via_flow(&g, &left);
for v in 0..n {
if let Some(w) = m[v] {
assert_eq!(m[w], Some(v), "not symmetric at {v}");
assert!(
g.adj[v].iter().any(|&(x, _)| x == w),
"matched a non-edge"
);
}
}
let size = m.iter().filter(|x| x.is_some()).count() / 2;
let brute = brute_max_matching(&g);
assert_eq!(size, brute, "l = {l}, r = {r}");
}
}
}
for m in 1..=4usize {
for n in 1..=4usize {
let g = complete_bipartite(m, n);
let left: Vec<usize> = (0..m).collect();
let matching = max_bipartite_matching_via_flow(&g, &left);
let size = matching.iter().filter(|x| x.is_some()).count() / 2;
assert_eq!(size, m.min(n), "K_{{{m},{n}}}");
}
}
}
fn brute_max_matching(g: &Graph) -> usize {
let edges: Vec<(usize, usize)> = g
.edges()
.into_iter()
.filter(|&(u, v, _)| u != v)
.map(|(u, v, _)| (u, v))
.collect();
let mut best = 0usize;
for k in (1..=edges.len()).rev() {
if k <= best {
break;
}
for combo in crate::discrete::combinatorics::combinations_iter(edges.len(), k) {
let mut used = vec![false; g.n];
let mut ok = true;
for &i in &combo {
let (u, v) = edges[i];
if used[u] || used[v] {
ok = false;
break;
}
used[u] = true;
used[v] = true;
}
if ok {
best = best.max(k);
break;
}
}
}
best
}
}