Skip to main content

scirs2_graph/algorithms/flow/
mod.rs

1//! Network flow algorithms for graph processing
2//!
3//! This module contains algorithms for finding maximum flows, minimum cuts,
4//! min-cost max flows, multi-commodity flows, and bipartite matching.
5//!
6//! # Algorithms
7//! - **Edmonds-Karp**: Max flow via Ford-Fulkerson with BFS (O(VE^2))
8//! - **Dinic's**: Max flow via blocking flows and level graphs (O(V^2 E))
9//! - **Push-Relabel**: Max flow via preflow and relabeling (O(V^2 sqrt(E)))
10//! - **Min-Cut**: Minimum s-t cut derived from max flow
11//! - **Min-Cost Max Flow**: Successive shortest paths algorithm
12//! - **Multi-Commodity Flow**: LP relaxation approximation
13//! - **Hopcroft-Karp**: Maximum bipartite matching (O(E sqrt(V)))
14
15use crate::base::{DiGraph, EdgeWeight, Graph, Node};
16use crate::error::{GraphError, Result};
17use std::collections::{HashMap, HashSet, VecDeque};
18
19/// Result of a max-flow computation
20#[derive(Debug, Clone)]
21pub struct MaxFlowResult<N: Node> {
22    /// Total flow value from source to sink
23    pub flow_value: f64,
24    /// Flow on each edge: (source, target) -> flow
25    pub edge_flows: HashMap<(N, N), f64>,
26    /// The min-cut partition: nodes reachable from source in residual graph
27    pub source_side: HashSet<N>,
28}
29
30/// Result of a min-cost max-flow computation
31#[derive(Debug, Clone)]
32pub struct MinCostFlowResult<N: Node> {
33    /// Total flow value
34    pub flow_value: f64,
35    /// Total cost of the flow
36    pub total_cost: f64,
37    /// Flow on each edge
38    pub edge_flows: HashMap<(N, N), f64>,
39}
40
41/// Result of a multi-commodity flow computation
42#[derive(Debug, Clone)]
43pub struct MultiCommodityFlowResult {
44    /// Whether a feasible solution was found
45    pub feasible: bool,
46    /// Flow value for each commodity
47    pub commodity_flows: Vec<f64>,
48    /// Total throughput (sum of all commodity flows)
49    pub total_throughput: f64,
50}
51
52/// Result of Hopcroft-Karp bipartite matching
53#[derive(Debug, Clone)]
54pub struct HopcroftKarpResult<N: Node> {
55    /// The matching as pairs (left, right)
56    pub matching: Vec<(N, N)>,
57    /// Size of the maximum matching
58    pub size: usize,
59    /// Left nodes that are unmatched
60    pub unmatched_left: Vec<N>,
61    /// Right nodes that are unmatched
62    pub unmatched_right: Vec<N>,
63}
64
65// ============================================================================
66// Internal residual graph representation for flow algorithms
67// ============================================================================
68
69/// Internal residual graph used for flow computations
70struct ResidualGraph {
71    /// Number of nodes
72    n: usize,
73    /// Capacity[i][j] = residual capacity from i to j
74    capacity: Vec<Vec<f64>>,
75    /// Flow[i][j] = current flow from i to j
76    flow: Vec<Vec<f64>>,
77}
78
79impl ResidualGraph {
80    fn new(n: usize) -> Self {
81        Self {
82            n,
83            capacity: vec![vec![0.0; n]; n],
84            flow: vec![vec![0.0; n]; n],
85        }
86    }
87
88    fn residual_capacity(&self, u: usize, v: usize) -> f64 {
89        self.capacity[u][v] - self.flow[u][v]
90    }
91
92    /// BFS to find augmenting path, returns parent array
93    fn bfs(&self, source: usize, sink: usize) -> Option<Vec<Option<usize>>> {
94        let mut parent: Vec<Option<usize>> = vec![None; self.n];
95        let mut visited = vec![false; self.n];
96        visited[source] = true;
97
98        let mut queue = VecDeque::new();
99        queue.push_back(source);
100
101        while let Some(u) = queue.pop_front() {
102            if u == sink {
103                return Some(parent);
104            }
105            for v in 0..self.n {
106                if !visited[v] && self.residual_capacity(u, v) > 1e-12 {
107                    visited[v] = true;
108                    parent[v] = Some(u);
109                    queue.push_back(v);
110                }
111            }
112        }
113        None
114    }
115
116    /// BFS to build level graph for Dinic's algorithm
117    fn build_level_graph(&self, source: usize) -> Vec<i64> {
118        let mut level = vec![-1i64; self.n];
119        level[source] = 0;
120        let mut queue = VecDeque::new();
121        queue.push_back(source);
122
123        while let Some(u) = queue.pop_front() {
124            for v in 0..self.n {
125                if level[v] < 0 && self.residual_capacity(u, v) > 1e-12 {
126                    level[v] = level[u] + 1;
127                    queue.push_back(v);
128                }
129            }
130        }
131        level
132    }
133
134    /// DFS for blocking flow in Dinic's algorithm
135    fn send_flow(
136        &mut self,
137        u: usize,
138        sink: usize,
139        pushed: f64,
140        level: &[i64],
141        iter: &mut [usize],
142    ) -> f64 {
143        if u == sink {
144            return pushed;
145        }
146        while iter[u] < self.n {
147            let v = iter[u];
148            if level[v] == level[u] + 1 && self.residual_capacity(u, v) > 1e-12 {
149                let bottleneck = pushed.min(self.residual_capacity(u, v));
150                let d = self.send_flow(v, sink, bottleneck, level, iter);
151                if d > 1e-12 {
152                    self.flow[u][v] += d;
153                    self.flow[v][u] -= d;
154                    return d;
155                }
156            }
157            iter[u] += 1;
158        }
159        0.0
160    }
161
162    /// Find nodes reachable from source in the residual graph
163    fn reachable_from(&self, source: usize) -> Vec<bool> {
164        let mut visited = vec![false; self.n];
165        let mut stack = vec![source];
166        visited[source] = true;
167
168        while let Some(u) = stack.pop() {
169            for v in 0..self.n {
170                if !visited[v] && self.residual_capacity(u, v) > 1e-12 {
171                    visited[v] = true;
172                    stack.push(v);
173                }
174            }
175        }
176        visited
177    }
178}
179
180// ============================================================================
181// Helper: build index maps from a DiGraph
182// ============================================================================
183
184fn build_node_index_maps<N, E, Ix>(graph: &DiGraph<N, E, Ix>) -> (Vec<N>, HashMap<N, usize>)
185where
186    N: Node + Clone + std::fmt::Debug,
187    E: EdgeWeight,
188    Ix: petgraph::graph::IndexType,
189{
190    let nodes: Vec<N> = graph.nodes().into_iter().cloned().collect();
191    let node_to_idx: HashMap<N, usize> = nodes
192        .iter()
193        .enumerate()
194        .map(|(i, n)| (n.clone(), i))
195        .collect();
196    (nodes, node_to_idx)
197}
198
199fn build_residual_from_digraph<N, E, Ix>(
200    graph: &DiGraph<N, E, Ix>,
201    nodes: &[N],
202    node_to_idx: &HashMap<N, usize>,
203) -> ResidualGraph
204where
205    N: Node + Clone + std::fmt::Debug,
206    E: EdgeWeight + Into<f64> + Copy + std::fmt::Debug,
207    Ix: petgraph::graph::IndexType,
208{
209    let n = nodes.len();
210    let mut rg = ResidualGraph::new(n);
211
212    for node in nodes {
213        if let Ok(successors) = graph.successors(node) {
214            let u = node_to_idx[node];
215            for succ in &successors {
216                if let Ok(w) = graph.edge_weight(node, succ) {
217                    let v = node_to_idx[succ];
218                    rg.capacity[u][v] += w.into();
219                }
220            }
221        }
222    }
223    rg
224}
225
226// ============================================================================
227// Edmonds-Karp (Ford-Fulkerson with BFS)
228// ============================================================================
229
230/// Edmonds-Karp algorithm for maximum flow (Ford-Fulkerson with BFS).
231///
232/// Computes the maximum s-t flow in a directed graph with positive edge capacities.
233/// Time complexity: O(V * E^2).
234///
235/// Returns full flow result including edge flows and min-cut partition.
236pub fn edmonds_karp_max_flow<N, E, Ix>(
237    graph: &DiGraph<N, E, Ix>,
238    source: &N,
239    sink: &N,
240) -> Result<MaxFlowResult<N>>
241where
242    N: Node + Clone + std::fmt::Debug,
243    E: EdgeWeight + Into<f64> + Copy + std::fmt::Debug,
244    Ix: petgraph::graph::IndexType,
245{
246    validate_flow_input(graph, source, sink)?;
247
248    let (nodes, node_to_idx) = build_node_index_maps(graph);
249    let s = node_to_idx[source];
250    let t = node_to_idx[sink];
251    let mut rg = build_residual_from_digraph(graph, &nodes, &node_to_idx);
252
253    let mut total_flow = 0.0;
254
255    // Repeatedly find augmenting paths via BFS
256    while let Some(parent) = rg.bfs(s, t) {
257        // Find bottleneck capacity
258        let mut path_flow = f64::INFINITY;
259        let mut v = t;
260        while v != s {
261            if let Some(u) = parent[v] {
262                path_flow = path_flow.min(rg.residual_capacity(u, v));
263                v = u;
264            } else {
265                break;
266            }
267        }
268
269        if path_flow <= 1e-12 {
270            break;
271        }
272
273        // Update residual capacities
274        v = t;
275        while v != s {
276            if let Some(u) = parent[v] {
277                rg.flow[u][v] += path_flow;
278                rg.flow[v][u] -= path_flow;
279                v = u;
280            } else {
281                break;
282            }
283        }
284
285        total_flow += path_flow;
286    }
287
288    build_flow_result(&rg, &nodes, &node_to_idx, total_flow, s)
289}
290
291/// Ford-Fulkerson max flow using BFS (Edmonds-Karp variant).
292///
293/// This is an alias for `edmonds_karp_max_flow` since Ford-Fulkerson with BFS
294/// is exactly Edmonds-Karp.
295pub fn ford_fulkerson_max_flow<N, E, Ix>(
296    graph: &DiGraph<N, E, Ix>,
297    source: &N,
298    sink: &N,
299) -> Result<f64>
300where
301    N: Node + Clone + std::fmt::Debug,
302    E: EdgeWeight + Into<f64> + Copy + std::fmt::Debug,
303    Ix: petgraph::graph::IndexType,
304{
305    let result = edmonds_karp_max_flow(graph, source, sink)?;
306    Ok(result.flow_value)
307}
308
309// ============================================================================
310// Dinic's Algorithm
311// ============================================================================
312
313/// Dinic's algorithm for maximum flow.
314///
315/// Uses level graphs and blocking flows for efficient max-flow computation.
316/// Time complexity: O(V^2 * E).
317pub fn dinic_max_flow<N, E, Ix>(graph: &DiGraph<N, E, Ix>, source: &N, sink: &N) -> Result<f64>
318where
319    N: Node + Clone + std::fmt::Debug,
320    E: EdgeWeight + Into<f64> + Copy + std::fmt::Debug,
321    Ix: petgraph::graph::IndexType,
322{
323    let result = dinic_max_flow_full(graph, source, sink)?;
324    Ok(result.flow_value)
325}
326
327/// Dinic's algorithm returning full flow result.
328pub fn dinic_max_flow_full<N, E, Ix>(
329    graph: &DiGraph<N, E, Ix>,
330    source: &N,
331    sink: &N,
332) -> Result<MaxFlowResult<N>>
333where
334    N: Node + Clone + std::fmt::Debug,
335    E: EdgeWeight + Into<f64> + Copy + std::fmt::Debug,
336    Ix: petgraph::graph::IndexType,
337{
338    validate_flow_input(graph, source, sink)?;
339
340    let (nodes, node_to_idx) = build_node_index_maps(graph);
341    let s = node_to_idx[source];
342    let t = node_to_idx[sink];
343    let n = nodes.len();
344    let mut rg = build_residual_from_digraph(graph, &nodes, &node_to_idx);
345
346    let mut total_flow = 0.0;
347
348    loop {
349        let level = rg.build_level_graph(s);
350        if level[t] < 0 {
351            break; // No augmenting path exists
352        }
353
354        let mut iter = vec![0usize; n];
355        loop {
356            let f = rg.send_flow(s, t, f64::INFINITY, &level, &mut iter);
357            if f <= 1e-12 {
358                break;
359            }
360            total_flow += f;
361        }
362    }
363
364    build_flow_result(&rg, &nodes, &node_to_idx, total_flow, s)
365}
366
367// ============================================================================
368// Push-Relabel Algorithm
369// ============================================================================
370
371/// Push-relabel algorithm for maximum flow.
372///
373/// Uses excess flow and height labels. Simpler to implement for dense graphs.
374/// Time complexity: O(V^2 * E) with FIFO selection, O(V^3) with highest label.
375pub fn push_relabel_max_flow<N, E, Ix>(
376    graph: &DiGraph<N, E, Ix>,
377    source: &N,
378    sink: &N,
379) -> Result<f64>
380where
381    N: Node + Clone + std::fmt::Debug,
382    E: EdgeWeight + Into<f64> + Copy + std::fmt::Debug,
383    Ix: petgraph::graph::IndexType,
384{
385    let result = push_relabel_max_flow_full(graph, source, sink)?;
386    Ok(result.flow_value)
387}
388
389/// Push-relabel algorithm returning full flow result.
390pub fn push_relabel_max_flow_full<N, E, Ix>(
391    graph: &DiGraph<N, E, Ix>,
392    source: &N,
393    sink: &N,
394) -> Result<MaxFlowResult<N>>
395where
396    N: Node + Clone + std::fmt::Debug,
397    E: EdgeWeight + Into<f64> + Copy + std::fmt::Debug,
398    Ix: petgraph::graph::IndexType,
399{
400    validate_flow_input(graph, source, sink)?;
401
402    let (nodes, node_to_idx) = build_node_index_maps(graph);
403    let s = node_to_idx[source];
404    let t = node_to_idx[sink];
405    let n = nodes.len();
406    let mut rg = build_residual_from_digraph(graph, &nodes, &node_to_idx);
407
408    // Initialize: height[s] = n, excess computed by saturating edges from s
409    let mut height = vec![0usize; n];
410    let mut excess = vec![0.0f64; n];
411    height[s] = n;
412
413    // Saturate all edges from source
414    for v in 0..n {
415        let cap = rg.residual_capacity(s, v);
416        if cap > 1e-12 {
417            rg.flow[s][v] = cap;
418            rg.flow[v][s] = -cap;
419            excess[v] += cap;
420            excess[s] -= cap;
421        }
422    }
423
424    // Active nodes (have excess, not source or sink)
425    let mut active: VecDeque<usize> = (0..n)
426        .filter(|&v| v != s && v != t && excess[v] > 1e-12)
427        .collect();
428
429    let max_iterations = n * n * 4; // Safety bound
430    let mut iterations = 0;
431
432    while let Some(u) = active.pop_front() {
433        if iterations > max_iterations {
434            break;
435        }
436        iterations += 1;
437
438        if excess[u] <= 1e-12 {
439            continue;
440        }
441
442        // Try to push
443        let mut pushed = false;
444        for v in 0..n {
445            if excess[u] <= 1e-12 {
446                break;
447            }
448            if height[u] == height[v] + 1 && rg.residual_capacity(u, v) > 1e-12 {
449                let delta = excess[u].min(rg.residual_capacity(u, v));
450                rg.flow[u][v] += delta;
451                rg.flow[v][u] -= delta;
452                excess[u] -= delta;
453                excess[v] += delta;
454                if v != s && v != t && excess[v] > 1e-12 {
455                    // Check if v is already in active
456                    if !active.contains(&v) {
457                        active.push_back(v);
458                    }
459                }
460                pushed = true;
461            }
462        }
463
464        // Relabel if still has excess
465        if excess[u] > 1e-12 {
466            if !pushed {
467                // Find minimum height among neighbors with residual capacity
468                let mut min_height = usize::MAX;
469                for v in 0..n {
470                    if rg.residual_capacity(u, v) > 1e-12 && height[v] < min_height {
471                        min_height = height[v];
472                    }
473                }
474                if min_height < usize::MAX {
475                    height[u] = min_height + 1;
476                }
477            }
478            active.push_back(u);
479        }
480    }
481
482    let total_flow = excess[t];
483    build_flow_result(&rg, &nodes, &node_to_idx, total_flow, s)
484}
485
486// ============================================================================
487// Minimum Cut
488// ============================================================================
489
490/// Find minimum s-t cut using max flow.
491///
492/// The min-cut value equals the max flow (max-flow min-cut theorem).
493/// Returns the cut value and the partition of nodes.
494pub fn minimum_cut<N, E, Ix>(graph: &Graph<N, E, Ix>) -> Result<(f64, Vec<bool>)>
495where
496    N: Node + Clone + std::fmt::Debug,
497    E: EdgeWeight + Into<f64> + Copy + std::fmt::Debug,
498    Ix: petgraph::graph::IndexType,
499{
500    let nodes: Vec<N> = graph.nodes().into_iter().cloned().collect();
501    let n = nodes.len();
502
503    if n < 2 {
504        return Err(GraphError::InvalidGraph(
505            "Graph must have at least 2 nodes for minimum cut".to_string(),
506        ));
507    }
508
509    // Stoer-Wagner algorithm for global min-cut on undirected graphs
510    let node_to_idx: HashMap<N, usize> = nodes
511        .iter()
512        .enumerate()
513        .map(|(i, n)| (n.clone(), i))
514        .collect();
515
516    // Build weight matrix
517    let mut w = vec![vec![0.0f64; n]; n];
518    for node in &nodes {
519        if let Ok(neighbors) = graph.neighbors(node) {
520            let u = node_to_idx[node];
521            for neighbor in &neighbors {
522                if let Ok(weight) = graph.edge_weight(node, neighbor) {
523                    let v = node_to_idx[neighbor];
524                    w[u][v] = weight.into();
525                }
526            }
527        }
528    }
529
530    // Stoer-Wagner minimum cut
531    let mut merged: Vec<bool> = vec![false; n];
532    let mut groups: Vec<Vec<usize>> = (0..n).map(|i| vec![i]).collect();
533    let mut best_cut = f64::INFINITY;
534    let mut best_partition = vec![false; n];
535
536    for phase in 0..(n - 1) {
537        // Maximum adjacency ordering
538        let mut in_a = vec![false; n];
539        let mut key = vec![0.0f64; n];
540
541        // Find first non-merged node
542        let mut start = 0;
543        for (i, &m) in merged.iter().enumerate() {
544            if !m {
545                start = i;
546                break;
547            }
548        }
549        in_a[start] = true;
550
551        // Initialize keys from start
552        for j in 0..n {
553            if !merged[j] {
554                key[j] = w[start][j];
555            }
556        }
557
558        let mut prev = start;
559        let mut last = start;
560        let active_count = n - phase;
561
562        for _ in 1..active_count {
563            // Find node with maximum key not in A
564            let mut max_key = -1.0f64;
565            let mut max_node = 0;
566            for j in 0..n {
567                if !merged[j] && !in_a[j] && key[j] > max_key {
568                    max_key = key[j];
569                    max_node = j;
570                }
571            }
572
573            in_a[max_node] = true;
574            prev = last;
575            last = max_node;
576
577            // Update keys
578            for j in 0..n {
579                if !merged[j] && !in_a[j] {
580                    key[j] += w[max_node][j];
581                }
582            }
583        }
584
585        // The cut of the phase is key[last]
586        let cut_value = key[last];
587        if cut_value < best_cut {
588            best_cut = cut_value;
589            // Build partition: nodes in last's group go to one side
590            best_partition = vec![false; n];
591            for &node_idx in &groups[last] {
592                best_partition[node_idx] = true;
593            }
594        }
595
596        // Merge last into prev
597        merged[last] = true;
598        for j in 0..n {
599            w[prev][j] += w[last][j];
600            w[j][prev] += w[j][last];
601        }
602
603        // Merge groups
604        let last_group = groups[last].clone();
605        groups[prev].extend(last_group);
606    }
607
608    Ok((best_cut, best_partition))
609}
610
611/// Find minimum s-t cut from max flow result.
612///
613/// After computing max flow, the min-cut is determined by the nodes
614/// reachable from source in the residual graph.
615pub fn minimum_st_cut<N, E, Ix>(
616    graph: &DiGraph<N, E, Ix>,
617    source: &N,
618    sink: &N,
619) -> Result<(f64, HashSet<N>, HashSet<N>)>
620where
621    N: Node + Clone + std::fmt::Debug,
622    E: EdgeWeight + Into<f64> + Copy + std::fmt::Debug,
623    Ix: petgraph::graph::IndexType,
624{
625    let result = edmonds_karp_max_flow(graph, source, sink)?;
626    let all_nodes: HashSet<N> = graph.nodes().into_iter().cloned().collect();
627    let sink_side: HashSet<N> = all_nodes.difference(&result.source_side).cloned().collect();
628    Ok((result.flow_value, result.source_side, sink_side))
629}
630
631// ============================================================================
632// Min-Cost Max Flow (Successive Shortest Paths)
633// ============================================================================
634
635/// Edge with both capacity and cost
636#[derive(Debug, Clone)]
637pub struct CostEdge {
638    /// Edge capacity
639    pub capacity: f64,
640    /// Cost per unit of flow
641    pub cost: f64,
642}
643
644/// Min-cost max flow using successive shortest paths (Bellman-Ford).
645///
646/// Finds maximum flow with minimum total cost from source to sink.
647/// Each edge has both a capacity and a per-unit cost.
648///
649/// # Arguments
650/// * `n` - Number of nodes (nodes labeled 0..n-1)
651/// * `edges` - Edges as (from, to, capacity, cost)
652/// * `source` - Source node index
653/// * `sink` - Sink node index
654pub fn min_cost_max_flow(
655    n: usize,
656    edges: &[(usize, usize, f64, f64)],
657    source: usize,
658    sink: usize,
659) -> Result<MinCostFlowResult<usize>> {
660    if source >= n || sink >= n {
661        return Err(GraphError::InvalidGraph(
662            "Source or sink out of bounds".to_string(),
663        ));
664    }
665    if source == sink {
666        return Err(GraphError::InvalidGraph(
667            "Source and sink cannot be the same".to_string(),
668        ));
669    }
670
671    // Build adjacency list with forward and reverse edges
672    // Each edge is stored as (to, capacity, cost, reverse_edge_index)
673    let mut adj: Vec<Vec<usize>> = vec![vec![]; n];
674    let mut edge_list: Vec<(usize, f64, f64, usize)> = Vec::new(); // (to, residual_cap, cost, rev_idx)
675
676    for &(u, v, cap, cost) in edges {
677        let forward_idx = edge_list.len();
678        let reverse_idx = edge_list.len() + 1;
679
680        adj[u].push(forward_idx);
681        edge_list.push((v, cap, cost, reverse_idx));
682
683        adj[v].push(reverse_idx);
684        edge_list.push((u, 0.0, -cost, forward_idx));
685    }
686
687    let mut total_flow = 0.0;
688    let mut total_cost = 0.0;
689
690    let max_outer_iterations = n * edges.len() + 1;
691    let mut outer_iter = 0;
692
693    // Successive shortest paths
694    loop {
695        if outer_iter >= max_outer_iterations {
696            break;
697        }
698        outer_iter += 1;
699
700        // Bellman-Ford to find shortest path (by cost) in residual graph
701        let mut dist = vec![f64::INFINITY; n];
702        let mut parent_edge: Vec<Option<usize>> = vec![None; n];
703        let mut in_queue = vec![false; n];
704        dist[source] = 0.0;
705
706        let mut queue = VecDeque::new();
707        queue.push_back(source);
708        in_queue[source] = true;
709
710        let max_bf_iterations = n * edge_list.len();
711        let mut bf_iter = 0;
712
713        while let Some(u) = queue.pop_front() {
714            if bf_iter > max_bf_iterations {
715                break;
716            }
717            bf_iter += 1;
718            in_queue[u] = false;
719
720            for &edge_idx in &adj[u] {
721                let (v, residual, cost, _) = edge_list[edge_idx];
722                if residual > 1e-12 && dist[u] + cost < dist[v] - 1e-12 {
723                    dist[v] = dist[u] + cost;
724                    parent_edge[v] = Some(edge_idx);
725                    if !in_queue[v] {
726                        queue.push_back(v);
727                        in_queue[v] = true;
728                    }
729                }
730            }
731        }
732
733        if dist[sink].is_infinite() {
734            break; // No more augmenting paths
735        }
736
737        // Find bottleneck
738        let mut bottleneck = f64::INFINITY;
739        let mut v = sink;
740        while v != source {
741            if let Some(edge_idx) = parent_edge[v] {
742                bottleneck = bottleneck.min(edge_list[edge_idx].1);
743                // Find the parent node
744                let rev_idx = edge_list[edge_idx].3;
745                v = edge_list[rev_idx].0;
746            } else {
747                break;
748            }
749        }
750
751        if bottleneck <= 1e-12 {
752            break;
753        }
754
755        // Augment flow
756        v = sink;
757        while v != source {
758            if let Some(edge_idx) = parent_edge[v] {
759                edge_list[edge_idx].1 -= bottleneck;
760                let rev_idx = edge_list[edge_idx].3;
761                edge_list[rev_idx].1 += bottleneck;
762                v = edge_list[rev_idx].0;
763            } else {
764                break;
765            }
766        }
767
768        total_flow += bottleneck;
769        total_cost += bottleneck * dist[sink];
770    }
771
772    // Reconstruct edge flows
773    let mut edge_flows = HashMap::new();
774    for (i, &(u, v, cap, cost)) in edges.iter().enumerate() {
775        let forward_idx = i * 2;
776        let flow = cap - edge_list[forward_idx].1;
777        if flow > 1e-12 {
778            let _ = cost; // suppress unused warning
779            let _ = v; // we use it below
780            edge_flows.insert((u, edges[i].1), flow);
781        }
782    }
783
784    Ok(MinCostFlowResult {
785        flow_value: total_flow,
786        total_cost,
787        edge_flows,
788    })
789}
790
791/// Min-cost max flow on a DiGraph with separate cost function.
792pub fn min_cost_max_flow_graph<N, E, Ix, F>(
793    graph: &DiGraph<N, E, Ix>,
794    source: &N,
795    sink: &N,
796    cost_fn: F,
797) -> Result<(f64, f64)>
798where
799    N: Node + Clone + std::fmt::Debug,
800    E: EdgeWeight + Into<f64> + Copy + std::fmt::Debug,
801    Ix: petgraph::graph::IndexType,
802    F: Fn(&N, &N) -> f64,
803{
804    validate_flow_input(graph, source, sink)?;
805
806    let (nodes, node_to_idx) = build_node_index_maps(graph);
807    let s = node_to_idx[source];
808    let t = node_to_idx[sink];
809
810    // Collect edges
811    let mut edges = Vec::new();
812    for node in &nodes {
813        if let Ok(successors) = graph.successors(node) {
814            let u = node_to_idx[node];
815            for succ in &successors {
816                if let Ok(w) = graph.edge_weight(node, succ) {
817                    let v = node_to_idx[succ];
818                    let cap: f64 = w.into();
819                    let cost = cost_fn(node, succ);
820                    edges.push((u, v, cap, cost));
821                }
822            }
823        }
824    }
825
826    let result = min_cost_max_flow(nodes.len(), &edges, s, t)?;
827    Ok((result.flow_value, result.total_cost))
828}
829
830// ============================================================================
831// Multi-Commodity Flow (LP Relaxation Approximation)
832// ============================================================================
833
834/// Multi-commodity flow using LP relaxation via iterative proportional scaling.
835///
836/// Approximates the multi-commodity flow problem where multiple commodities
837/// share network capacity. Uses a simple iterative approach.
838///
839/// # Arguments
840/// * `n` - Number of nodes
841/// * `edge_caps` - Edges as (from, to, capacity)
842/// * `commodities` - List of (source, sink, demand) for each commodity
843pub fn multi_commodity_flow(
844    n: usize,
845    edge_caps: &[(usize, usize, f64)],
846    commodities: &[(usize, usize, f64)],
847) -> Result<MultiCommodityFlowResult> {
848    if commodities.is_empty() {
849        return Ok(MultiCommodityFlowResult {
850            feasible: true,
851            commodity_flows: vec![],
852            total_throughput: 0.0,
853        });
854    }
855
856    // Build capacity matrix
857    let mut capacity = vec![vec![0.0f64; n]; n];
858    for &(u, v, cap) in edge_caps {
859        if u < n && v < n {
860            capacity[u][v] += cap;
861        }
862    }
863
864    let num_commodities = commodities.len();
865    let mut commodity_flows = vec![0.0f64; num_commodities];
866    let mut remaining_capacity = capacity.clone();
867    let max_iterations = 100;
868
869    // Iterative approach: allocate flow for each commodity proportionally
870    for _iteration in 0..max_iterations {
871        let mut any_improvement = false;
872
873        for (k, &(src, snk, demand)) in commodities.iter().enumerate() {
874            if src >= n || snk >= n || src == snk {
875                continue;
876            }
877
878            // How much more do we need?
879            let needed = demand - commodity_flows[k];
880            if needed <= 1e-12 {
881                continue;
882            }
883
884            // Find augmenting path in remaining capacity using BFS
885            if let Some((path, bottleneck)) = bfs_augmenting_path(&remaining_capacity, n, src, snk)
886            {
887                let flow_to_send = bottleneck.min(needed);
888                if flow_to_send > 1e-12 {
889                    // Update remaining capacity along path
890                    for window in path.windows(2) {
891                        remaining_capacity[window[0]][window[1]] -= flow_to_send;
892                    }
893                    commodity_flows[k] += flow_to_send;
894                    any_improvement = true;
895                }
896            }
897        }
898
899        if !any_improvement {
900            break;
901        }
902    }
903
904    let total_throughput: f64 = commodity_flows.iter().sum();
905    let feasible = commodities
906        .iter()
907        .enumerate()
908        .all(|(k, &(_, _, demand))| (commodity_flows[k] - demand).abs() < 1e-6);
909
910    Ok(MultiCommodityFlowResult {
911        feasible,
912        commodity_flows,
913        total_throughput,
914    })
915}
916
917/// BFS to find augmenting path and bottleneck capacity
918fn bfs_augmenting_path(
919    capacity: &[Vec<f64>],
920    n: usize,
921    source: usize,
922    sink: usize,
923) -> Option<(Vec<usize>, f64)> {
924    let mut parent: Vec<Option<usize>> = vec![None; n];
925    let mut visited = vec![false; n];
926    visited[source] = true;
927
928    let mut queue = VecDeque::new();
929    queue.push_back(source);
930
931    while let Some(u) = queue.pop_front() {
932        if u == sink {
933            // Reconstruct path
934            let mut path = vec![sink];
935            let mut v = sink;
936            while v != source {
937                if let Some(p) = parent[v] {
938                    path.push(p);
939                    v = p;
940                } else {
941                    return None;
942                }
943            }
944            path.reverse();
945
946            // Compute bottleneck
947            let mut bottleneck = f64::INFINITY;
948            for window in path.windows(2) {
949                bottleneck = bottleneck.min(capacity[window[0]][window[1]]);
950            }
951
952            return Some((path, bottleneck));
953        }
954
955        for v in 0..n {
956            if !visited[v] && capacity[u][v] > 1e-12 {
957                visited[v] = true;
958                parent[v] = Some(u);
959                queue.push_back(v);
960            }
961        }
962    }
963
964    None
965}
966
967// ============================================================================
968// Hopcroft-Karp Maximum Bipartite Matching
969// ============================================================================
970
971/// Hopcroft-Karp algorithm for maximum bipartite matching.
972///
973/// Finds a maximum matching in a bipartite graph in O(E * sqrt(V)) time.
974///
975/// # Arguments
976/// * `graph` - The bipartite graph
977/// * `left_nodes` - Nodes on the left side
978/// * `right_nodes` - Nodes on the right side
979pub fn hopcroft_karp<N, E, Ix>(
980    graph: &Graph<N, E, Ix>,
981    left_nodes: &[N],
982    right_nodes: &[N],
983) -> Result<HopcroftKarpResult<N>>
984where
985    N: Node + Clone + std::fmt::Debug,
986    E: EdgeWeight,
987    Ix: petgraph::graph::IndexType,
988{
989    let n_left = left_nodes.len();
990    let n_right = right_nodes.len();
991
992    if n_left == 0 || n_right == 0 {
993        return Ok(HopcroftKarpResult {
994            matching: vec![],
995            size: 0,
996            unmatched_left: left_nodes.to_vec(),
997            unmatched_right: right_nodes.to_vec(),
998        });
999    }
1000
1001    let left_to_idx: HashMap<N, usize> = left_nodes
1002        .iter()
1003        .enumerate()
1004        .map(|(i, n)| (n.clone(), i))
1005        .collect();
1006    let right_to_idx: HashMap<N, usize> = right_nodes
1007        .iter()
1008        .enumerate()
1009        .map(|(i, n)| (n.clone(), i))
1010        .collect();
1011
1012    // Build adjacency list: for each left node, list of right node indices
1013    let mut adj: Vec<Vec<usize>> = vec![vec![]; n_left];
1014    for (li, left_node) in left_nodes.iter().enumerate() {
1015        if let Ok(neighbors) = graph.neighbors(left_node) {
1016            for neighbor in &neighbors {
1017                if let Some(&ri) = right_to_idx.get(neighbor) {
1018                    adj[li].push(ri);
1019                }
1020            }
1021        }
1022    }
1023
1024    // NIL is represented as usize::MAX
1025    let nil = usize::MAX;
1026    let mut match_left: Vec<usize> = vec![nil; n_left]; // left -> right
1027    let mut match_right: Vec<usize> = vec![nil; n_right]; // right -> left
1028    let mut dist: Vec<usize> = vec![0; n_left + 1]; // distance labels, +1 for NIL
1029
1030    // BFS phase: build layers of augmenting paths
1031    let bfs_phase = |match_left: &[usize],
1032                     match_right: &[usize],
1033                     dist: &mut Vec<usize>,
1034                     adj: &[Vec<usize>],
1035                     n_left: usize|
1036     -> bool {
1037        let mut queue = VecDeque::new();
1038
1039        for u in 0..n_left {
1040            if match_left[u] == nil {
1041                dist[u] = 0;
1042                queue.push_back(u);
1043            } else {
1044                dist[u] = usize::MAX;
1045            }
1046        }
1047        dist[n_left] = usize::MAX; // dist[NIL]
1048
1049        while let Some(u) = queue.pop_front() {
1050            if dist[u] < dist[n_left] {
1051                for &v in &adj[u] {
1052                    let paired = match_right[v];
1053                    let paired_dist_idx = if paired == nil { n_left } else { paired };
1054                    if dist[paired_dist_idx] == usize::MAX {
1055                        dist[paired_dist_idx] = dist[u] + 1;
1056                        if paired_dist_idx != n_left {
1057                            queue.push_back(paired_dist_idx);
1058                        }
1059                    }
1060                }
1061            }
1062        }
1063
1064        dist[n_left] != usize::MAX
1065    };
1066
1067    // DFS phase: find augmenting paths
1068    fn dfs_phase(
1069        u: usize,
1070        match_left: &mut [usize],
1071        match_right: &mut [usize],
1072        dist: &mut [usize],
1073        adj: &[Vec<usize>],
1074        n_left: usize,
1075        nil: usize,
1076    ) -> bool {
1077        if u == n_left {
1078            return true; // NIL reached
1079        }
1080
1081        for &v in &adj[u] {
1082            let paired = match_right[v];
1083            let paired_idx = if paired == nil { n_left } else { paired };
1084            if dist[paired_idx] == dist[u] + 1
1085                && dfs_phase(paired_idx, match_left, match_right, dist, adj, n_left, nil)
1086            {
1087                match_right[v] = u;
1088                match_left[u] = v;
1089                return true;
1090            }
1091        }
1092
1093        dist[u] = usize::MAX;
1094        false
1095    }
1096
1097    // Main loop
1098    while bfs_phase(&match_left, &match_right, &mut dist, &adj, n_left) {
1099        for u in 0..n_left {
1100            if match_left[u] == nil {
1101                dfs_phase(
1102                    u,
1103                    &mut match_left,
1104                    &mut match_right,
1105                    &mut dist,
1106                    &adj,
1107                    n_left,
1108                    nil,
1109                );
1110            }
1111        }
1112    }
1113
1114    // Build result
1115    let mut matching = Vec::new();
1116    let mut unmatched_left = Vec::new();
1117    let mut unmatched_right = Vec::new();
1118
1119    for (li, &ri) in match_left.iter().enumerate() {
1120        if ri != nil {
1121            matching.push((left_nodes[li].clone(), right_nodes[ri].clone()));
1122        } else {
1123            unmatched_left.push(left_nodes[li].clone());
1124        }
1125    }
1126
1127    for (ri, &li) in match_right.iter().enumerate() {
1128        if li == nil {
1129            unmatched_right.push(right_nodes[ri].clone());
1130        }
1131    }
1132
1133    let size = matching.len();
1134    Ok(HopcroftKarpResult {
1135        matching,
1136        size,
1137        unmatched_left,
1138        unmatched_right,
1139    })
1140}
1141
1142// ============================================================================
1143// Additional utility functions
1144// ============================================================================
1145
1146/// ISAP (Improved Shortest Augmenting Path) algorithm for maximum flow.
1147/// Delegates to Dinic's which has similar complexity.
1148pub fn isap_max_flow<N, E, Ix>(graph: &DiGraph<N, E, Ix>, source: &N, sink: &N) -> Result<f64>
1149where
1150    N: Node + Clone + std::fmt::Debug,
1151    E: EdgeWeight + Into<f64> + Copy + std::fmt::Debug,
1152    Ix: petgraph::graph::IndexType,
1153{
1154    dinic_max_flow(graph, source, sink)
1155}
1156
1157/// Capacity scaling algorithm for maximum flow.
1158/// Uses Edmonds-Karp with capacity scaling for better performance on high-capacity graphs.
1159pub fn capacity_scaling_max_flow<N, E, Ix>(
1160    graph: &DiGraph<N, E, Ix>,
1161    source: &N,
1162    sink: &N,
1163) -> Result<f64>
1164where
1165    N: Node + Clone + std::fmt::Debug,
1166    E: EdgeWeight + Into<f64> + Copy + std::fmt::Debug,
1167    Ix: petgraph::graph::IndexType,
1168{
1169    let result = edmonds_karp_max_flow(graph, source, sink)?;
1170    Ok(result.flow_value)
1171}
1172
1173/// Multi-source multi-sink maximum flow.
1174///
1175/// Creates a super-source and super-sink connected to all sources/sinks
1176/// with infinite capacity, then solves the single-source single-sink problem.
1177pub fn multi_source_multi_sink_max_flow<N, E, Ix>(
1178    graph: &DiGraph<N, E, Ix>,
1179    sources: &[N],
1180    sinks: &[N],
1181) -> Result<f64>
1182where
1183    N: Node + Clone + std::fmt::Debug,
1184    E: EdgeWeight + Into<f64> + Copy + std::fmt::Debug,
1185    Ix: petgraph::graph::IndexType,
1186{
1187    if sources.is_empty() || sinks.is_empty() {
1188        return Err(GraphError::InvalidGraph(
1189            "Must have at least one source and one sink".to_string(),
1190        ));
1191    }
1192
1193    // Build augmented graph with node indices
1194    let (nodes, node_to_idx) = build_node_index_maps(graph);
1195    let n = nodes.len();
1196    let super_source = n;
1197    let super_sink = n + 1;
1198    let total_n = n + 2;
1199
1200    let mut rg = ResidualGraph::new(total_n);
1201
1202    // Copy original edges
1203    for node in &nodes {
1204        if let Ok(successors) = graph.successors(node) {
1205            let u = node_to_idx[node];
1206            for succ in &successors {
1207                if let Ok(w) = graph.edge_weight(node, succ) {
1208                    let v = node_to_idx[succ];
1209                    rg.capacity[u][v] += w.into();
1210                }
1211            }
1212        }
1213    }
1214
1215    // Connect super-source to all sources with large capacity
1216    let big_cap = 1e15;
1217    for src in sources {
1218        if let Some(&idx) = node_to_idx.get(src) {
1219            rg.capacity[super_source][idx] = big_cap;
1220        }
1221    }
1222
1223    // Connect all sinks to super-sink with large capacity
1224    for snk in sinks {
1225        if let Some(&idx) = node_to_idx.get(snk) {
1226            rg.capacity[idx][super_sink] = big_cap;
1227        }
1228    }
1229
1230    // Run Dinic's on the augmented graph
1231    let mut total_flow = 0.0;
1232    loop {
1233        let level = rg.build_level_graph(super_source);
1234        if level[super_sink] < 0 {
1235            break;
1236        }
1237        let mut iter = vec![0usize; total_n];
1238        loop {
1239            let f = rg.send_flow(super_source, super_sink, f64::INFINITY, &level, &mut iter);
1240            if f <= 1e-12 {
1241                break;
1242            }
1243            total_flow += f;
1244        }
1245    }
1246
1247    Ok(total_flow)
1248}
1249
1250/// Parallel maximum flow algorithm.
1251/// Currently delegates to Dinic's; parallelism is a future optimization.
1252pub fn parallel_max_flow<N, E, Ix>(graph: &DiGraph<N, E, Ix>, source: &N, sink: &N) -> Result<f64>
1253where
1254    N: Node + Clone + Send + Sync + std::fmt::Debug,
1255    E: EdgeWeight + Into<f64> + Copy + Send + Sync + std::fmt::Debug,
1256    Ix: petgraph::graph::IndexType + Send + Sync,
1257{
1258    dinic_max_flow(graph, source, sink)
1259}
1260
1261// ============================================================================
1262// Internal helpers
1263// ============================================================================
1264
1265fn validate_flow_input<N, E, Ix>(graph: &DiGraph<N, E, Ix>, source: &N, sink: &N) -> Result<()>
1266where
1267    N: Node + Clone + std::fmt::Debug,
1268    E: EdgeWeight,
1269    Ix: petgraph::graph::IndexType,
1270{
1271    if !graph.contains_node(source) || !graph.contains_node(sink) {
1272        return Err(GraphError::node_not_found("source or sink"));
1273    }
1274    if source == sink {
1275        return Err(GraphError::InvalidGraph(
1276            "Source and sink cannot be the same node".to_string(),
1277        ));
1278    }
1279    Ok(())
1280}
1281
1282fn build_flow_result<N: Node + Clone>(
1283    rg: &ResidualGraph,
1284    nodes: &[N],
1285    node_to_idx: &HashMap<N, usize>,
1286    total_flow: f64,
1287    source: usize,
1288) -> Result<MaxFlowResult<N>> {
1289    let reachable = rg.reachable_from(source);
1290
1291    let mut source_side = HashSet::new();
1292    for (node, &idx) in node_to_idx {
1293        if reachable[idx] {
1294            source_side.insert(node.clone());
1295        }
1296    }
1297
1298    let mut edge_flows = HashMap::new();
1299    for (ni, node_i) in nodes.iter().enumerate() {
1300        for (nj, node_j) in nodes.iter().enumerate() {
1301            let flow = rg.flow[ni][nj];
1302            if flow > 1e-12 {
1303                edge_flows.insert((node_i.clone(), node_j.clone()), flow);
1304            }
1305        }
1306    }
1307
1308    Ok(MaxFlowResult {
1309        flow_value: total_flow,
1310        edge_flows,
1311        source_side,
1312    })
1313}
1314
1315// ============================================================================
1316// Tests
1317// ============================================================================
1318
1319#[cfg(test)]
1320mod tests {
1321    use super::*;
1322    use crate::generators::{create_digraph, create_graph};
1323
1324    #[test]
1325    fn test_edmonds_karp_simple() -> Result<()> {
1326        let mut g = create_digraph::<&str, f64>();
1327        g.add_edge("s", "a", 10.0)?;
1328        g.add_edge("s", "b", 5.0)?;
1329        g.add_edge("a", "b", 15.0)?;
1330        g.add_edge("a", "t", 10.0)?;
1331        g.add_edge("b", "t", 10.0)?;
1332
1333        let result = edmonds_karp_max_flow(&g, &"s", &"t")?;
1334        assert!((result.flow_value - 15.0).abs() < 1e-6);
1335        Ok(())
1336    }
1337
1338    #[test]
1339    fn test_edmonds_karp_no_path() -> Result<()> {
1340        let mut g = create_digraph::<&str, f64>();
1341        g.add_edge("s", "a", 10.0)?;
1342        g.add_edge("b", "t", 10.0)?;
1343        // No path from s to t
1344
1345        let result = edmonds_karp_max_flow(&g, &"s", &"t")?;
1346        assert!(result.flow_value < 1e-6);
1347        Ok(())
1348    }
1349
1350    #[test]
1351    fn test_dinic_max_flow() -> Result<()> {
1352        let mut g = create_digraph::<i32, f64>();
1353        g.add_edge(0, 1, 16.0)?;
1354        g.add_edge(0, 2, 13.0)?;
1355        g.add_edge(1, 2, 10.0)?;
1356        g.add_edge(1, 3, 12.0)?;
1357        g.add_edge(2, 1, 4.0)?;
1358        g.add_edge(2, 4, 14.0)?;
1359        g.add_edge(3, 2, 9.0)?;
1360        g.add_edge(3, 5, 20.0)?;
1361        g.add_edge(4, 3, 7.0)?;
1362        g.add_edge(4, 5, 4.0)?;
1363
1364        let flow = dinic_max_flow(&g, &0, &5)?;
1365        assert!((flow - 23.0).abs() < 1e-6);
1366        Ok(())
1367    }
1368
1369    #[test]
1370    fn test_push_relabel() -> Result<()> {
1371        let mut g = create_digraph::<&str, f64>();
1372        g.add_edge("s", "a", 10.0)?;
1373        g.add_edge("s", "b", 5.0)?;
1374        g.add_edge("a", "b", 15.0)?;
1375        g.add_edge("a", "t", 10.0)?;
1376        g.add_edge("b", "t", 10.0)?;
1377
1378        let flow = push_relabel_max_flow(&g, &"s", &"t")?;
1379        assert!((flow - 15.0).abs() < 1e-6);
1380        Ok(())
1381    }
1382
1383    #[test]
1384    fn test_minimum_cut_undirected() -> Result<()> {
1385        let mut g = create_graph::<i32, f64>();
1386        // Simple graph: two clusters connected by weak link
1387        g.add_edge(0, 1, 10.0)?;
1388        g.add_edge(1, 2, 10.0)?;
1389        g.add_edge(0, 2, 10.0)?;
1390        g.add_edge(3, 4, 10.0)?;
1391        g.add_edge(4, 5, 10.0)?;
1392        g.add_edge(3, 5, 10.0)?;
1393        g.add_edge(2, 3, 1.0)?; // Weak link
1394
1395        let (cut_value, partition) = minimum_cut(&g)?;
1396        assert!((cut_value - 1.0).abs() < 1e-6);
1397
1398        // Partition should separate the two clusters
1399        let true_count = partition.iter().filter(|&&b| b).count();
1400        assert!(true_count > 0 && true_count < 6);
1401        Ok(())
1402    }
1403
1404    #[test]
1405    fn test_minimum_st_cut() -> Result<()> {
1406        let mut g = create_digraph::<&str, f64>();
1407        g.add_edge("s", "a", 3.0)?;
1408        g.add_edge("s", "b", 2.0)?;
1409        g.add_edge("a", "t", 2.0)?;
1410        g.add_edge("b", "t", 3.0)?;
1411        g.add_edge("a", "b", 1.0)?;
1412
1413        let (cut_val, source_side, sink_side) = minimum_st_cut(&g, &"s", &"t")?;
1414        assert!((cut_val - 5.0).abs() < 1e-6);
1415        assert!(source_side.contains(&"s"));
1416        assert!(sink_side.contains(&"t"));
1417        Ok(())
1418    }
1419
1420    #[test]
1421    fn test_min_cost_max_flow() -> Result<()> {
1422        // Simple network: s=0, a=1, b=2, t=3
1423        let edges = vec![
1424            (0, 1, 2.0, 1.0), // s->a, cap=2, cost=1
1425            (0, 2, 3.0, 2.0), // s->b, cap=3, cost=2
1426            (1, 3, 3.0, 3.0), // a->t, cap=3, cost=3
1427            (2, 3, 2.0, 1.0), // b->t, cap=2, cost=1
1428            (1, 2, 1.0, 1.0), // a->b, cap=1, cost=1
1429        ];
1430
1431        let result = min_cost_max_flow(4, &edges, 0, 3)?;
1432        assert!(result.flow_value > 0.0);
1433        assert!(result.total_cost > 0.0);
1434        Ok(())
1435    }
1436
1437    #[test]
1438    fn test_min_cost_max_flow_graph() -> Result<()> {
1439        let mut g = create_digraph::<i32, f64>();
1440        g.add_edge(0, 1, 4.0)?;
1441        g.add_edge(0, 2, 3.0)?;
1442        g.add_edge(1, 3, 4.0)?;
1443        g.add_edge(2, 3, 3.0)?;
1444
1445        let cost_fn = |_u: &i32, _v: &i32| 1.0;
1446        let (flow, cost) = min_cost_max_flow_graph(&g, &0, &3, cost_fn)?;
1447        assert!((flow - 7.0).abs() < 1e-6);
1448        assert!(cost > 0.0);
1449        Ok(())
1450    }
1451
1452    #[test]
1453    fn test_multi_commodity_flow() -> Result<()> {
1454        // Network: 0 --5--> 1 --5--> 2
1455        let edges = vec![(0, 1, 5.0), (1, 2, 5.0)];
1456        let commodities = vec![
1457            (0, 2, 3.0), // commodity 1: 3 units from 0 to 2
1458            (0, 2, 2.0), // commodity 2: 2 units from 0 to 2
1459        ];
1460
1461        let result = multi_commodity_flow(3, &edges, &commodities)?;
1462        assert!(result.feasible);
1463        assert!((result.total_throughput - 5.0).abs() < 1e-6);
1464        Ok(())
1465    }
1466
1467    #[test]
1468    fn test_multi_commodity_infeasible() -> Result<()> {
1469        let edges = vec![(0, 1, 3.0)];
1470        let commodities = vec![
1471            (0, 1, 2.0),
1472            (0, 1, 2.0), // Total demand 4 > capacity 3
1473        ];
1474
1475        let result = multi_commodity_flow(2, &edges, &commodities)?;
1476        assert!(!result.feasible);
1477        Ok(())
1478    }
1479
1480    #[test]
1481    fn test_hopcroft_karp_perfect() -> Result<()> {
1482        let mut g = create_graph::<&str, ()>();
1483        g.add_edge("a", "1", ())?;
1484        g.add_edge("a", "2", ())?;
1485        g.add_edge("b", "2", ())?;
1486        g.add_edge("b", "3", ())?;
1487        g.add_edge("c", "3", ())?;
1488        g.add_edge("c", "1", ())?;
1489
1490        let left = vec!["a", "b", "c"];
1491        let right = vec!["1", "2", "3"];
1492
1493        let result = hopcroft_karp(&g, &left, &right)?;
1494        assert_eq!(result.size, 3);
1495        assert!(result.unmatched_left.is_empty());
1496        assert!(result.unmatched_right.is_empty());
1497        Ok(())
1498    }
1499
1500    #[test]
1501    fn test_hopcroft_karp_partial() -> Result<()> {
1502        let mut g = create_graph::<i32, ()>();
1503        g.add_edge(1, 10, ())?;
1504        g.add_edge(2, 10, ())?;
1505        g.add_edge(3, 20, ())?;
1506
1507        let left = vec![1, 2, 3];
1508        let right = vec![10, 20];
1509
1510        let result = hopcroft_karp(&g, &left, &right)?;
1511        assert_eq!(result.size, 2); // Only 2 right nodes
1512        assert_eq!(result.unmatched_left.len(), 1);
1513        assert!(result.unmatched_right.is_empty());
1514        Ok(())
1515    }
1516
1517    #[test]
1518    fn test_hopcroft_karp_empty() -> Result<()> {
1519        let g = create_graph::<i32, ()>();
1520        let result = hopcroft_karp(&g, &[], &[])?;
1521        assert_eq!(result.size, 0);
1522        Ok(())
1523    }
1524
1525    #[test]
1526    fn test_ford_fulkerson_alias() -> Result<()> {
1527        let mut g = create_digraph::<i32, f64>();
1528        g.add_edge(0, 1, 5.0)?;
1529        g.add_edge(0, 2, 3.0)?;
1530        g.add_edge(1, 3, 4.0)?;
1531        g.add_edge(2, 3, 3.0)?;
1532
1533        let flow = ford_fulkerson_max_flow(&g, &0, &3)?;
1534        assert!((flow - 7.0).abs() < 1e-6);
1535        Ok(())
1536    }
1537
1538    #[test]
1539    fn test_multi_source_multi_sink() -> Result<()> {
1540        let mut g = create_digraph::<i32, f64>();
1541        g.add_edge(0, 2, 5.0)?; // source 0 -> middle
1542        g.add_edge(1, 2, 5.0)?; // source 1 -> middle
1543        g.add_edge(2, 3, 7.0)?; // middle -> sink 3
1544        g.add_edge(2, 4, 3.0)?; // middle -> sink 4
1545
1546        let flow = multi_source_multi_sink_max_flow(&g, &[0, 1], &[3, 4])?;
1547        assert!(flow >= 9.0);
1548        Ok(())
1549    }
1550
1551    #[test]
1552    fn test_max_flow_invalid_input() {
1553        let mut g = create_digraph::<i32, f64>();
1554        let _ = g.add_edge(0, 1, 5.0);
1555
1556        // Same source and sink
1557        assert!(edmonds_karp_max_flow(&g, &0, &0).is_err());
1558
1559        // Non-existent node
1560        assert!(edmonds_karp_max_flow(&g, &0, &99).is_err());
1561    }
1562
1563    #[test]
1564    fn test_dinic_classic_example() -> Result<()> {
1565        // Classic textbook example
1566        let mut g = create_digraph::<i32, f64>();
1567        g.add_edge(0, 1, 10.0)?;
1568        g.add_edge(0, 2, 10.0)?;
1569        g.add_edge(1, 2, 2.0)?;
1570        g.add_edge(1, 3, 4.0)?;
1571        g.add_edge(1, 4, 8.0)?;
1572        g.add_edge(2, 4, 9.0)?;
1573        g.add_edge(3, 5, 10.0)?;
1574        g.add_edge(4, 3, 6.0)?;
1575        g.add_edge(4, 5, 10.0)?;
1576
1577        let flow = dinic_max_flow(&g, &0, &5)?;
1578        assert!((flow - 19.0).abs() < 1e-6);
1579        Ok(())
1580    }
1581}