#![allow(clippy::cast_possible_truncation)]
use crate::core::{Graph, IgraphError, IgraphResult, VertexId};
pub fn max_flow_value(
graph: &Graph,
source: VertexId,
target: VertexId,
capacity: Option<&[f64]>,
) -> IgraphResult<f64> {
max_flow_with_residual(graph, source, target, capacity).map(|(v, _)| v)
}
pub fn max_flow(
graph: &Graph,
source: VertexId,
target: VertexId,
capacity: Option<&[f64]>,
) -> IgraphResult<MaxFlow> {
let m = graph.ecount();
let directed = graph.is_directed();
let (value, net) = max_flow_with_residual(graph, source, target, capacity)?;
let mut flow_vec: Vec<f64> = Vec::with_capacity(m);
for e in 0..m {
let fwd = 2 * e;
let rev = fwd + 1;
if directed {
let initial = capacity.map_or(1.0, |c| c[e]);
flow_vec.push(initial - net.cap[fwd]);
} else {
flow_vec.push((net.cap[rev] - net.cap[fwd]) / 2.0);
}
}
let n = net.n;
let mut in_source = vec![false; n];
in_source[source as usize] = true;
let mut queue: Vec<u32> = Vec::with_capacity(n);
queue.push(source);
let mut head_ptr = 0_usize;
while head_ptr < queue.len() {
let v = queue[head_ptr] as usize;
head_ptr += 1;
for &arc in &net.arcs_out[v] {
let arc_us = arc as usize;
if net.cap[arc_us] <= 0.0 {
continue;
}
let w = net.head[arc_us] as usize;
if !in_source[w] {
in_source[w] = true;
queue.push(w as u32);
}
}
}
let mut partition: Vec<u32> = Vec::with_capacity(queue.len());
let mut partition2: Vec<u32> = Vec::with_capacity(n.saturating_sub(queue.len()));
for (v, &is_src) in in_source.iter().enumerate() {
if is_src {
partition.push(v as u32);
} else {
partition2.push(v as u32);
}
}
let edge_count = u32::try_from(m).map_err(|_| IgraphError::Internal("ecount overflows u32"))?;
let mut cut: Vec<u32> = Vec::new();
for eid in 0..edge_count {
let (u, v) = graph.edge(eid)?;
let u_in = in_source[u as usize];
let v_in = in_source[v as usize];
let crosses = if directed {
u_in && !v_in
} else {
u_in != v_in
};
if crosses {
cut.push(eid);
}
}
Ok(MaxFlow {
value,
flow: flow_vec,
cut,
partition,
partition2,
})
}
#[derive(Debug, Clone)]
pub struct MaxFlow {
pub value: f64,
pub flow: Vec<f64>,
pub cut: Vec<u32>,
pub partition: Vec<u32>,
pub partition2: Vec<u32>,
}
pub(crate) fn max_flow_with_residual(
graph: &Graph,
source: VertexId,
target: VertexId,
capacity: Option<&[f64]>,
) -> IgraphResult<(f64, Network)> {
let n = graph.vcount();
if n == 0 || source >= n {
return Err(IgraphError::VertexOutOfRange { id: source, n });
}
if target >= n {
return Err(IgraphError::VertexOutOfRange { id: target, n });
}
if source == target {
return Err(IgraphError::InvalidArgument(
"source and target must be distinct".to_string(),
));
}
let m = graph.ecount();
if let Some(c) = capacity {
if c.len() != m {
return Err(IgraphError::InvalidArgument(format!(
"capacity length {} does not match edge count {}",
c.len(),
m
)));
}
for (i, &v) in c.iter().enumerate() {
if !v.is_finite() || v < 0.0 {
return Err(IgraphError::InvalidArgument(format!(
"capacity[{i}] = {v} is not a finite non-negative number"
)));
}
}
}
let net = Network::build(graph, capacity)?;
let mut state = DinicState::new(net);
let value = state.run(source, target);
Ok((value, state.into_network()))
}
pub(crate) struct Network {
pub(crate) n: usize,
pub(crate) head: Vec<u32>,
pub(crate) cap: Vec<f64>,
pub(crate) arcs_out: Vec<Vec<u32>>,
}
impl Network {
fn build(graph: &Graph, capacity: Option<&[f64]>) -> IgraphResult<Self> {
let n = graph.vcount() as usize;
let m = graph.ecount();
let directed = graph.is_directed();
let arc_count = m
.checked_mul(2)
.ok_or(IgraphError::Internal("arc count overflows usize"))?;
let mut head = vec![0_u32; arc_count];
let mut cap = vec![0.0_f64; arc_count];
let mut arcs_out: Vec<Vec<u32>> = vec![Vec::new(); n];
let edge_count =
u32::try_from(m).map_err(|_| IgraphError::Internal("ecount overflows u32"))?;
for eid in 0..edge_count {
let (src, dst) = graph.edge(eid)?;
let e_us = eid as usize;
let cap_val = capacity.map_or(1.0, |c| c[e_us]);
let fwd = 2 * e_us;
let rev = fwd + 1;
head[fwd] = dst;
head[rev] = src;
cap[fwd] = cap_val;
cap[rev] = if directed { 0.0 } else { cap_val };
arcs_out[src as usize].push(fwd as u32);
arcs_out[dst as usize].push(rev as u32);
}
Ok(Self {
n,
head,
cap,
arcs_out,
})
}
}
struct DinicState {
net: Network,
level: Vec<u32>,
iter: Vec<u32>,
queue: Vec<u32>,
}
impl DinicState {
fn new(net: Network) -> Self {
let n = net.n;
Self {
net,
level: vec![u32::MAX; n],
iter: vec![0_u32; n],
queue: Vec::with_capacity(n),
}
}
fn into_network(self) -> Network {
self.net
}
fn run(&mut self, source: u32, target: u32) -> f64 {
let mut total = 0.0_f64;
let src = source as usize;
let dst = target as usize;
while self.bfs(src, dst) {
for it in &mut self.iter {
*it = 0;
}
loop {
let pushed = self.dfs(src, dst, f64::INFINITY);
if pushed <= 0.0 {
break;
}
total += pushed;
}
}
total
}
fn bfs(&mut self, source: usize, target: usize) -> bool {
for l in &mut self.level {
*l = u32::MAX;
}
self.level[source] = 0;
self.queue.clear();
self.queue.push(source as u32);
let mut head_ptr = 0_usize;
while head_ptr < self.queue.len() {
let v = self.queue[head_ptr] as usize;
head_ptr += 1;
let next_level = self.level[v].saturating_add(1);
for &a in &self.net.arcs_out[v] {
let a_us = a as usize;
if self.net.cap[a_us] <= 0.0 {
continue;
}
let w = self.net.head[a_us] as usize;
if self.level[w] == u32::MAX {
self.level[w] = next_level;
self.queue.push(w as u32);
}
}
}
self.level[target] != u32::MAX
}
fn dfs(&mut self, v: usize, target: usize, limit: f64) -> f64 {
if v == target {
return limit;
}
let next_level = self.level[v].saturating_add(1);
while (self.iter[v] as usize) < self.net.arcs_out[v].len() {
let arc_idx = self.net.arcs_out[v][self.iter[v] as usize] as usize;
let w = self.net.head[arc_idx] as usize;
let cap_here = self.net.cap[arc_idx];
if cap_here > 0.0 && self.level[w] == next_level {
let send = limit.min(cap_here);
let pushed = self.dfs(w, target, send);
if pushed > 0.0 {
self.net.cap[arc_idx] -= pushed;
self.net.cap[arc_idx ^ 1] += pushed;
return pushed;
}
}
self.iter[v] += 1;
}
0.0
}
}
#[cfg(test)]
mod tests {
use super::*;
fn assert_close(actual: f64, expected: f64, tol: f64) {
assert!(
(actual - expected).abs() < tol,
"actual = {actual}, expected = {expected}"
);
}
#[test]
fn rejects_out_of_range_source() {
let g = Graph::with_vertices(2);
let err = max_flow_value(&g, 5, 1, None).unwrap_err();
match err {
IgraphError::VertexOutOfRange { id, n } => {
assert_eq!(id, 5);
assert_eq!(n, 2);
}
_ => panic!("expected VertexOutOfRange"),
}
}
#[test]
fn rejects_out_of_range_target() {
let g = Graph::with_vertices(2);
let err = max_flow_value(&g, 0, 9, None).unwrap_err();
assert!(matches!(err, IgraphError::VertexOutOfRange { id: 9, n: 2 }));
}
#[test]
fn rejects_source_equals_target() {
let g = Graph::with_vertices(2);
let err = max_flow_value(&g, 0, 0, None).unwrap_err();
assert!(matches!(err, IgraphError::InvalidArgument(_)));
}
#[test]
fn rejects_wrong_capacity_length() {
let mut g = Graph::with_vertices(2);
g.add_edge(0, 1).unwrap();
let cap = vec![1.0, 2.0];
let err = max_flow_value(&g, 0, 1, Some(&cap)).unwrap_err();
assert!(matches!(err, IgraphError::InvalidArgument(_)));
}
#[test]
fn rejects_negative_capacity() {
let mut g = Graph::with_vertices(2);
g.add_edge(0, 1).unwrap();
let cap = vec![-1.0];
let err = max_flow_value(&g, 0, 1, Some(&cap)).unwrap_err();
assert!(matches!(err, IgraphError::InvalidArgument(_)));
}
#[test]
fn isolated_source_and_target() {
let g = Graph::with_vertices(2);
let f = max_flow_value(&g, 0, 1, None).unwrap();
assert_close(f, 0.0, 1e-12);
}
#[test]
fn single_edge_directed_unit() {
let mut g = Graph::new(2, true).unwrap();
g.add_edge(0, 1).unwrap();
let f = max_flow_value(&g, 0, 1, None).unwrap();
assert_close(f, 1.0, 1e-12);
}
#[test]
fn single_edge_directed_wrong_direction() {
let mut g = Graph::new(2, true).unwrap();
g.add_edge(0, 1).unwrap();
let f = max_flow_value(&g, 1, 0, None).unwrap();
assert_close(f, 0.0, 1e-12);
}
#[test]
fn single_edge_undirected_unit() {
let mut g = Graph::with_vertices(2);
g.add_edge(0, 1).unwrap();
let f = max_flow_value(&g, 0, 1, None).unwrap();
assert_close(f, 1.0, 1e-12);
let f_rev = max_flow_value(&g, 1, 0, None).unwrap();
assert_close(f_rev, 1.0, 1e-12);
}
#[test]
fn two_parallel_paths_directed() {
let mut g = Graph::new(4, true).unwrap();
g.add_edge(0, 1).unwrap();
g.add_edge(1, 3).unwrap();
g.add_edge(0, 2).unwrap();
g.add_edge(2, 3).unwrap();
let f = max_flow_value(&g, 0, 3, None).unwrap();
assert_close(f, 2.0, 1e-12);
}
#[test]
fn bottleneck_directed() {
let mut g = Graph::new(4, true).unwrap();
g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
g.add_edge(2, 3).unwrap();
let cap = vec![5.0, 2.0, 5.0];
let f = max_flow_value(&g, 0, 3, Some(&cap)).unwrap();
assert_close(f, 2.0, 1e-12);
}
#[test]
fn classic_max_flow_textbook() {
let mut g = Graph::new(6, true).unwrap();
g.add_edge(0, 1).unwrap();
g.add_edge(0, 2).unwrap();
g.add_edge(1, 3).unwrap();
g.add_edge(2, 1).unwrap();
g.add_edge(2, 4).unwrap();
g.add_edge(3, 2).unwrap();
g.add_edge(3, 5).unwrap();
g.add_edge(4, 3).unwrap();
g.add_edge(4, 5).unwrap();
let cap = vec![16.0, 13.0, 12.0, 4.0, 14.0, 9.0, 20.0, 7.0, 4.0];
let f = max_flow_value(&g, 0, 5, Some(&cap)).unwrap();
assert_close(f, 23.0, 1e-12);
}
#[test]
fn multigraph_parallel_edges() {
let mut g = Graph::new(2, true).unwrap();
g.add_edge(0, 1).unwrap();
g.add_edge(0, 1).unwrap();
g.add_edge(0, 1).unwrap();
let cap = vec![1.0, 2.0, 4.0];
let f = max_flow_value(&g, 0, 1, Some(&cap)).unwrap();
assert_close(f, 7.0, 1e-12);
}
#[test]
fn self_loop_does_not_contribute() {
let mut g = Graph::new(2, true).unwrap();
g.add_edge(0, 0).unwrap();
g.add_edge(0, 1).unwrap();
let cap = vec![100.0, 3.0];
let f = max_flow_value(&g, 0, 1, Some(&cap)).unwrap();
assert_close(f, 3.0, 1e-12);
}
#[test]
fn undirected_two_paths_share_capacity() {
let mut g = Graph::with_vertices(3);
g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
let f = max_flow_value(&g, 0, 2, None).unwrap();
assert_close(f, 1.0, 1e-12);
}
#[test]
fn weighted_fractional_flow() {
let mut g = Graph::new(4, true).unwrap();
g.add_edge(0, 1).unwrap();
g.add_edge(1, 3).unwrap();
g.add_edge(0, 2).unwrap();
g.add_edge(2, 3).unwrap();
let cap = vec![0.5, 0.5, 0.25, 0.25];
let f = max_flow_value(&g, 0, 3, Some(&cap)).unwrap();
assert_close(f, 0.75, 1e-12);
}
fn validate_flow(graph: &Graph, result: &MaxFlow, capacity: Option<&[f64]>) {
let m = graph.ecount();
let n = graph.vcount();
let directed = graph.is_directed();
assert_eq!(result.flow.len(), m, "flow.len() must equal ecount()");
for e in 0..m {
let cap_e = capacity.map_or(1.0, |c| c[e]);
assert!(
result.flow[e].abs() <= cap_e + 1e-12,
"flow[{e}] = {} exceeds capacity {cap_e}",
result.flow[e]
);
if directed {
assert!(
result.flow[e] >= -1e-12,
"directed flow[{e}] = {} must be non-negative",
result.flow[e]
);
}
}
if directed {
for v in 0..n {
if v == result.partition[0]
|| !result.partition2.is_empty()
&& v == *result.partition2.last().unwrap_or(&u32::MAX)
{
continue;
}
let mut net = 0.0_f64;
for e in 0..m {
let (src, dst) = graph.edge(e as u32).unwrap();
if dst == v {
net += result.flow[e];
}
if src == v {
net -= result.flow[e];
}
}
assert!(
net.abs() < 1e-9,
"flow conservation violated at vertex {v}: net = {net}"
);
}
}
assert_eq!(result.partition.len() + result.partition2.len(), n as usize);
assert!(result.partition.windows(2).all(|w| w[0] < w[1]));
assert!(result.partition2.windows(2).all(|w| w[0] < w[1]));
let cut_sum: f64 = result
.cut
.iter()
.map(|&e| capacity.map_or(1.0, |c| c[e as usize]))
.sum();
assert_close(cut_sum, result.value, 1e-9 * result.value.abs().max(1.0));
let mut in_source = vec![false; n as usize];
for &v in &result.partition {
in_source[v as usize] = true;
}
for &eid in &result.cut {
let (u, v) = graph.edge(eid).unwrap();
if directed {
assert!(
in_source[u as usize] && !in_source[v as usize],
"directed cut edge {eid} must go S→V\\S"
);
} else {
assert_ne!(
in_source[u as usize], in_source[v as usize],
"cut edge {eid} must cross partition"
);
}
}
}
#[test]
fn max_flow_two_parallel_paths() {
let mut g = Graph::new(4, true).unwrap();
g.add_edge(0, 1).unwrap();
g.add_edge(1, 3).unwrap();
g.add_edge(0, 2).unwrap();
g.add_edge(2, 3).unwrap();
let cap = vec![1.0, 1.0, 1.0, 1.0];
let r = max_flow(&g, 0, 3, Some(&cap)).unwrap();
assert_close(r.value, 2.0, 1e-12);
validate_flow(&g, &r, Some(&cap));
}
#[test]
fn max_flow_bottleneck() {
let mut g = Graph::new(4, true).unwrap();
g.add_edge(0, 1).unwrap();
g.add_edge(1, 2).unwrap();
g.add_edge(2, 3).unwrap();
let cap = vec![5.0, 2.0, 7.0];
let r = max_flow(&g, 0, 3, Some(&cap)).unwrap();
assert_close(r.value, 2.0, 1e-12);
assert_close(r.flow[0], 2.0, 1e-12);
assert_close(r.flow[1], 2.0, 1e-12);
assert_close(r.flow[2], 2.0, 1e-12);
validate_flow(&g, &r, Some(&cap));
}
#[test]
fn max_flow_classic_textbook() {
let mut g = Graph::new(6, true).unwrap();
g.add_edge(0, 1).unwrap();
g.add_edge(0, 2).unwrap();
g.add_edge(1, 3).unwrap();
g.add_edge(2, 1).unwrap();
g.add_edge(2, 4).unwrap();
g.add_edge(3, 2).unwrap();
g.add_edge(3, 5).unwrap();
g.add_edge(4, 3).unwrap();
g.add_edge(4, 5).unwrap();
let cap = vec![16.0, 13.0, 12.0, 4.0, 14.0, 9.0, 20.0, 7.0, 4.0];
let r = max_flow(&g, 0, 5, Some(&cap)).unwrap();
assert_close(r.value, 23.0, 1e-12);
validate_flow(&g, &r, Some(&cap));
}
#[test]
fn max_flow_isolated_endpoints() {
let g = Graph::with_vertices(4);
let r = max_flow(&g, 0, 3, None).unwrap();
assert_close(r.value, 0.0, 1e-12);
assert!(r.flow.iter().all(|&f| f.abs() < 1e-12));
assert!(r.cut.is_empty());
}
#[test]
fn max_flow_single_edge() {
let mut g = Graph::new(2, true).unwrap();
g.add_edge(0, 1).unwrap();
let r = max_flow(&g, 0, 1, None).unwrap();
assert_close(r.value, 1.0, 1e-12);
assert_close(r.flow[0], 1.0, 1e-12);
validate_flow(&g, &r, None);
}
#[test]
fn max_flow_undirected() {
let mut g = Graph::with_vertices(4);
g.add_edge(0, 1).unwrap();
g.add_edge(0, 2).unwrap();
g.add_edge(1, 3).unwrap();
g.add_edge(2, 3).unwrap();
let r = max_flow(&g, 0, 3, None).unwrap();
assert_close(r.value, 2.0, 1e-12);
assert_eq!(r.flow.len(), 4);
validate_flow(&g, &r, None);
}
#[test]
fn max_flow_value_matches_full() {
let mut g = Graph::new(5, true).unwrap();
for (s, t) in [(0u32, 1u32), (0, 2), (1, 3), (2, 3), (3, 4), (1, 4)] {
g.add_edge(s, t).unwrap();
}
let caps = [3.0, 5.0, 2.0, 4.0, 6.0, 1.0];
let scalar = max_flow_value(&g, 0, 4, Some(&caps)).unwrap();
let full = max_flow(&g, 0, 4, Some(&caps)).unwrap();
assert_close(scalar, full.value, 1e-12);
validate_flow(&g, &full, Some(&caps));
}
}
#[cfg(all(test, feature = "proptest-harness"))]
mod proptest_tests {
use super::*;
use proptest::prelude::*;
fn edmonds_karp(graph: &Graph, source: u32, target: u32, cap: &[f64]) -> f64 {
let net = Network::build(graph, Some(cap)).expect("net builds");
let n = net.n;
let mut residual = net.cap.clone();
let mut total = 0.0_f64;
loop {
let mut parent_arc = vec![u32::MAX; n];
let mut visited = vec![false; n];
visited[source as usize] = true;
let mut queue = vec![source];
let mut head = 0_usize;
let mut found = false;
'bfs: while head < queue.len() {
let v = queue[head] as usize;
head += 1;
for &a in &net.arcs_out[v] {
let a_us = a as usize;
let w = net.head[a_us] as usize;
if !visited[w] && residual[a_us] > 0.0 {
visited[w] = true;
parent_arc[w] = a;
if w == target as usize {
found = true;
break 'bfs;
}
queue.push(w as u32);
}
}
}
if !found {
break;
}
let mut bottleneck = f64::INFINITY;
let mut cur = target as usize;
while cur != source as usize {
let a = parent_arc[cur] as usize;
if residual[a] < bottleneck {
bottleneck = residual[a];
}
cur = net.head[a ^ 1] as usize;
}
let mut cur = target as usize;
while cur != source as usize {
let a = parent_arc[cur] as usize;
residual[a] -= bottleneck;
residual[a ^ 1] += bottleneck;
cur = net.head[a ^ 1] as usize;
}
total += bottleneck;
}
total
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(80))]
#[test]
fn matches_edmonds_karp_directed(
n in 2u32..8,
seed in any::<u64>(),
) {
let mut rng_state = seed | 1;
let mut next = || {
rng_state ^= rng_state << 13;
rng_state ^= rng_state >> 7;
rng_state ^= rng_state << 17;
rng_state
};
let mut g = Graph::new(n, true).unwrap();
let edge_count = next() as u32 % (n * 3 + 1);
let mut caps = Vec::with_capacity(edge_count as usize);
for _ in 0..edge_count {
let u = (next() as u32) % n;
let v = (next() as u32) % n;
g.add_edge(u, v).unwrap();
let c = f64::from((next() as u32 % 16) + 1);
caps.push(c);
}
let source = 0;
let target = n - 1;
if source == target {
return Ok(());
}
let dinic = max_flow_value(&g, source, target, Some(&caps)).unwrap();
let ref_val = edmonds_karp(&g, source, target, &caps);
prop_assert!(
(dinic - ref_val).abs() < 1e-9,
"dinic={dinic} ref={ref_val}"
);
}
#[test]
fn matches_edmonds_karp_undirected(
n in 2u32..8,
seed in any::<u64>(),
) {
let mut rng_state = seed | 1;
let mut next = || {
rng_state ^= rng_state << 13;
rng_state ^= rng_state >> 7;
rng_state ^= rng_state << 17;
rng_state
};
let mut g = Graph::with_vertices(n);
let edge_count = next() as u32 % (n * 3 + 1);
let mut caps = Vec::with_capacity(edge_count as usize);
for _ in 0..edge_count {
let u = (next() as u32) % n;
let v = (next() as u32) % n;
g.add_edge(u, v).unwrap();
let c = f64::from((next() as u32 % 16) + 1);
caps.push(c);
}
let source = 0;
let target = n - 1;
if source == target {
return Ok(());
}
let dinic = max_flow_value(&g, source, target, Some(&caps)).unwrap();
let ref_val = edmonds_karp(&g, source, target, &caps);
prop_assert!(
(dinic - ref_val).abs() < 1e-9,
"dinic={dinic} ref={ref_val}"
);
}
}
}