graph_flow/ford_fulkerson.rs
1//! Ford-Fulkerson maximum flow via DFS augmenting paths.
2
3use crate::FlowGraph;
4
5/// Computes the **maximum flow** from `source` to `sink` using the
6/// Ford-Fulkerson algorithm with DFS augmenting paths.
7///
8/// Repeatedly finds an augmenting path from `source` to `sink` in the
9/// residual graph using DFS, then pushes the bottleneck capacity along that
10/// path. Terminates when no augmenting path exists.
11///
12/// # When to use this vs [`edmonds_karp`]
13///
14/// Ford-Fulkerson with DFS is simple but has a worst-case complexity of
15/// O(E · max_flow) — it can be very slow if max_flow is large and augmenting
16/// paths carry only 1 unit of flow each. Prefer [`edmonds_karp`] for general
17/// use since BFS augmentation guarantees polynomial time regardless of
18/// capacity values.
19///
20/// Ford-Fulkerson is primarily useful as an educational reference and for
21/// small graphs or integer capacities where max_flow is bounded.
22///
23/// # Arguments
24///
25/// - `graph` — a mutable [`FlowGraph`]; flow values are updated in place.
26/// - `source` — index of the source node.
27/// - `sink` — index of the sink node.
28///
29/// # Returns
30///
31/// The total maximum flow value sent from `source` to `sink`.
32///
33/// # Complexity
34///
35/// O(E · max_flow). Not guaranteed to terminate with irrational capacities
36/// (use integer or rational capacities).
37///
38/// # Examples
39///
40/// ```
41/// use graph_flow::{FlowGraph, ford_fulkerson};
42///
43/// // Simple two-path network:
44/// // 0 --10-- 1 --10-- 3
45/// // | |
46/// // +---5--- 2 --5----+
47/// let mut g = FlowGraph::new(4);
48/// g.add_edge(0, 1, 10.0);
49/// g.add_edge(0, 2, 5.0);
50/// g.add_edge(1, 3, 10.0);
51/// g.add_edge(2, 3, 5.0);
52///
53/// let flow = ford_fulkerson(&mut g, 0, 3);
54/// assert_eq!(flow, 15.0);
55/// ```
56///
57/// [`edmonds_karp`]: crate::edmonds_karp::edmonds_karp
58pub fn ford_fulkerson(graph: &mut FlowGraph, source: usize, sink: usize) -> f64 {
59 // Trivial case: no flow is possible when source and sink are the same node.
60 if source == sink {
61 return 0.0;
62 }
63
64 let mut total_flow = 0.0;
65
66 loop {
67 // Find an augmenting path via DFS and push flow along it.
68 let mut visited = vec![false; graph.node_count()];
69 let pushed = dfs_augment(graph, source, sink, f64::INFINITY, &mut visited);
70
71 if pushed == 0.0 {
72 break; // No augmenting path found — done.
73 }
74 total_flow += pushed;
75 }
76
77 total_flow
78}
79
80/// DFS that finds one augmenting path and returns the bottleneck flow pushed.
81///
82/// Returns `0.0` if no path from `node` to `sink` exists in the residual
83/// graph.
84fn dfs_augment(
85 graph: &mut FlowGraph,
86 node: usize,
87 sink: usize,
88 pushed: f64,
89 visited: &mut Vec<bool>,
90) -> f64 {
91 if node == sink {
92 return pushed;
93 }
94
95 visited[node] = true;
96
97 // Iterate by index so we can call push_flow after the recursive call
98 // without holding a borrow on graph.adjacency.
99 for edge_idx in 0..graph.adjacency[node].len() {
100 let neighbour = graph.adjacency[node][edge_idx].to;
101 let residual = graph.adjacency[node][edge_idx].residual();
102
103 if visited[neighbour] || residual <= 0.0 {
104 continue;
105 }
106
107 let bottleneck = dfs_augment(graph, neighbour, sink, pushed.min(residual), visited);
108
109 if bottleneck > 0.0 {
110 graph.push_flow(node, edge_idx, bottleneck);
111 return bottleneck;
112 }
113 }
114
115 0.0
116}