Skip to main content

scirs2_sparse/csgraph/
max_flow.rs

1//! Maximum flow algorithms for graphs
2//!
3//! This module provides algorithms for computing maximum flow in networks:
4//! - Ford-Fulkerson algorithm with DFS
5//! - Edmonds-Karp algorithm (Ford-Fulkerson with BFS)
6//! - Dinic's algorithm
7//! - Push-relabel algorithm
8
9use crate::error::{SparseError, SparseResult};
10use crate::sparray::SparseArray;
11use scirs2_core::numeric::{Float, SparseElement};
12use std::collections::VecDeque;
13use std::fmt::Debug;
14
15/// Result of a maximum flow computation
16#[derive(Debug, Clone)]
17pub struct MaxFlowResult<T> {
18    /// Maximum flow value
19    pub flow_value: T,
20    /// Flow on each edge (residual graph representation)
21    pub flow_matrix: Vec<Vec<T>>,
22}
23
24/// Compute maximum flow using Ford-Fulkerson algorithm with DFS
25///
26/// # Arguments
27///
28/// * `graph` - The sparse capacity matrix (adjacency matrix with capacities)
29/// * `source` - Source vertex
30/// * `sink` - Sink vertex
31///
32/// # Returns
33///
34/// MaxFlowResult containing the maximum flow value and flow matrix
35pub fn ford_fulkerson<T, S>(graph: &S, source: usize, sink: usize) -> SparseResult<MaxFlowResult<T>>
36where
37    T: Float + SparseElement + Debug + Copy + 'static,
38    S: SparseArray<T>,
39{
40    let n = graph.shape().0;
41
42    if graph.shape().0 != graph.shape().1 {
43        return Err(SparseError::ValueError(
44            "Graph matrix must be square".to_string(),
45        ));
46    }
47
48    if source >= n || sink >= n {
49        return Err(SparseError::ValueError(
50            "Source and sink must be valid vertices".to_string(),
51        ));
52    }
53
54    // Create residual graph
55    let mut residual = vec![vec![T::sparse_zero(); n]; n];
56    for i in 0..n {
57        for j in 0..n {
58            residual[i][j] = graph.get(i, j);
59        }
60    }
61
62    let mut max_flow = T::sparse_zero();
63
64    // Keep finding augmenting paths using DFS
65    loop {
66        let mut visited = vec![false; n];
67        let mut parent = vec![None; n];
68
69        if !dfs_find_path(&residual, source, sink, &mut visited, &mut parent) {
70            break;
71        }
72
73        // Find minimum capacity along the path
74        let mut path_flow = T::from(f64::INFINITY)
75            .ok_or_else(|| SparseError::ComputationError("Cannot create infinity".to_string()))?;
76
77        let mut current = sink;
78        while let Some(prev) = parent[current] {
79            path_flow = if residual[prev][current] < path_flow {
80                residual[prev][current]
81            } else {
82                path_flow
83            };
84            current = prev;
85        }
86
87        // Update residual capacities
88        current = sink;
89        while let Some(prev) = parent[current] {
90            residual[prev][current] = residual[prev][current] - path_flow;
91            residual[current][prev] = residual[current][prev] + path_flow;
92            current = prev;
93        }
94
95        max_flow = max_flow + path_flow;
96    }
97
98    Ok(MaxFlowResult {
99        flow_value: max_flow,
100        flow_matrix: residual,
101    })
102}
103
104/// DFS helper to find augmenting path
105fn dfs_find_path<T>(
106    residual: &[Vec<T>],
107    current: usize,
108    sink: usize,
109    visited: &mut [bool],
110    parent: &mut [Option<usize>],
111) -> bool
112where
113    T: Float + SparseElement + Debug + Copy + 'static,
114{
115    if current == sink {
116        return true;
117    }
118
119    visited[current] = true;
120
121    for neighbor in 0..residual.len() {
122        if !visited[neighbor] && residual[current][neighbor] > T::sparse_zero() {
123            parent[neighbor] = Some(current);
124            if dfs_find_path(residual, neighbor, sink, visited, parent) {
125                return true;
126            }
127        }
128    }
129
130    false
131}
132
133/// Compute maximum flow using Edmonds-Karp algorithm (BFS variant of Ford-Fulkerson)
134///
135/// # Arguments
136///
137/// * `graph` - The sparse capacity matrix
138/// * `source` - Source vertex
139/// * `sink` - Sink vertex
140///
141/// # Returns
142///
143/// MaxFlowResult containing the maximum flow value and flow matrix
144pub fn edmonds_karp<T, S>(graph: &S, source: usize, sink: usize) -> SparseResult<MaxFlowResult<T>>
145where
146    T: Float + SparseElement + Debug + Copy + 'static,
147    S: SparseArray<T>,
148{
149    let n = graph.shape().0;
150
151    if graph.shape().0 != graph.shape().1 {
152        return Err(SparseError::ValueError(
153            "Graph matrix must be square".to_string(),
154        ));
155    }
156
157    if source >= n || sink >= n {
158        return Err(SparseError::ValueError(
159            "Source and sink must be valid vertices".to_string(),
160        ));
161    }
162
163    // Create residual graph
164    let mut residual = vec![vec![T::sparse_zero(); n]; n];
165    for i in 0..n {
166        for j in 0..n {
167            residual[i][j] = graph.get(i, j);
168        }
169    }
170
171    let mut max_flow = T::sparse_zero();
172
173    // Keep finding augmenting paths using BFS
174    loop {
175        let mut parent = vec![None; n];
176
177        if !bfs_find_path(&residual, source, sink, &mut parent) {
178            break;
179        }
180
181        // Find minimum capacity along the path
182        let mut path_flow = T::from(f64::INFINITY)
183            .ok_or_else(|| SparseError::ComputationError("Cannot create infinity".to_string()))?;
184
185        let mut current = sink;
186        while let Some(prev) = parent[current] {
187            path_flow = if residual[prev][current] < path_flow {
188                residual[prev][current]
189            } else {
190                path_flow
191            };
192            current = prev;
193        }
194
195        // Update residual capacities
196        current = sink;
197        while let Some(prev) = parent[current] {
198            residual[prev][current] = residual[prev][current] - path_flow;
199            residual[current][prev] = residual[current][prev] + path_flow;
200            current = prev;
201        }
202
203        max_flow = max_flow + path_flow;
204    }
205
206    Ok(MaxFlowResult {
207        flow_value: max_flow,
208        flow_matrix: residual,
209    })
210}
211
212/// BFS helper to find augmenting path
213fn bfs_find_path<T>(
214    residual: &[Vec<T>],
215    source: usize,
216    sink: usize,
217    parent: &mut [Option<usize>],
218) -> bool
219where
220    T: Float + SparseElement + Debug + Copy + 'static,
221{
222    let n = residual.len();
223    let mut visited = vec![false; n];
224    let mut queue = VecDeque::new();
225
226    queue.push_back(source);
227    visited[source] = true;
228
229    while let Some(current) = queue.pop_front() {
230        if current == sink {
231            return true;
232        }
233
234        for neighbor in 0..n {
235            if !visited[neighbor] && residual[current][neighbor] > T::sparse_zero() {
236                visited[neighbor] = true;
237                parent[neighbor] = Some(current);
238                queue.push_back(neighbor);
239            }
240        }
241    }
242
243    false
244}
245
246/// Compute maximum flow using Dinic's algorithm
247///
248/// Dinic's algorithm is more efficient than Ford-Fulkerson, with O(V²E) complexity.
249///
250/// # Arguments
251///
252/// * `graph` - The sparse capacity matrix
253/// * `source` - Source vertex
254/// * `sink` - Sink vertex
255///
256/// # Returns
257///
258/// MaxFlowResult containing the maximum flow value and flow matrix
259pub fn dinic<T, S>(graph: &S, source: usize, sink: usize) -> SparseResult<MaxFlowResult<T>>
260where
261    T: Float + SparseElement + Debug + Copy + 'static,
262    S: SparseArray<T>,
263{
264    let n = graph.shape().0;
265
266    if graph.shape().0 != graph.shape().1 {
267        return Err(SparseError::ValueError(
268            "Graph matrix must be square".to_string(),
269        ));
270    }
271
272    if source >= n || sink >= n {
273        return Err(SparseError::ValueError(
274            "Source and sink must be valid vertices".to_string(),
275        ));
276    }
277
278    // Create residual graph
279    let mut residual = vec![vec![T::sparse_zero(); n]; n];
280    for i in 0..n {
281        for j in 0..n {
282            residual[i][j] = graph.get(i, j);
283        }
284    }
285
286    let mut max_flow = T::sparse_zero();
287
288    // Repeat while there exists a blocking flow
289    loop {
290        // Build level graph using BFS
291        let level = build_level_graph(&residual, source, sink);
292
293        if level[sink] < 0 {
294            // No more augmenting paths
295            break;
296        }
297
298        // Send multiple blocking flows using DFS
299        loop {
300            let mut visited = vec![false; n];
301            let flow = send_flow(
302                &mut residual,
303                &level,
304                source,
305                sink,
306                &mut visited,
307                T::from(f64::INFINITY).ok_or_else(|| {
308                    SparseError::ComputationError("Cannot create infinity".to_string())
309                })?,
310            );
311
312            if scirs2_core::SparseElement::is_zero(&flow) {
313                break;
314            }
315
316            max_flow = max_flow + flow;
317        }
318    }
319
320    Ok(MaxFlowResult {
321        flow_value: max_flow,
322        flow_matrix: residual,
323    })
324}
325
326/// Build level graph using BFS
327fn build_level_graph<T>(residual: &[Vec<T>], source: usize, sink: usize) -> Vec<i32>
328where
329    T: Float + SparseElement + Debug + Copy + 'static,
330{
331    let n = residual.len();
332    let mut level = vec![-1; n];
333    level[source] = 0;
334
335    let mut queue = VecDeque::new();
336    queue.push_back(source);
337
338    while let Some(current) = queue.pop_front() {
339        for neighbor in 0..n {
340            if level[neighbor] < 0 && residual[current][neighbor] > T::sparse_zero() {
341                level[neighbor] = level[current] + 1;
342                queue.push_back(neighbor);
343
344                if neighbor == sink {
345                    return level;
346                }
347            }
348        }
349    }
350
351    level
352}
353
354/// Send flow using DFS in level graph
355fn send_flow<T>(
356    residual: &mut [Vec<T>],
357    level: &[i32],
358    current: usize,
359    sink: usize,
360    visited: &mut [bool],
361    flow: T,
362) -> T
363where
364    T: Float + SparseElement + Debug + Copy + 'static,
365{
366    if current == sink {
367        return flow;
368    }
369
370    visited[current] = true;
371
372    for neighbor in 0..residual.len() {
373        if !visited[neighbor]
374            && residual[current][neighbor] > T::sparse_zero()
375            && level[neighbor] == level[current] + 1
376        {
377            let min_flow = if residual[current][neighbor] < flow {
378                residual[current][neighbor]
379            } else {
380                flow
381            };
382
383            let pushed_flow = send_flow(residual, level, neighbor, sink, visited, min_flow);
384
385            if pushed_flow > T::sparse_zero() {
386                residual[current][neighbor] = residual[current][neighbor] - pushed_flow;
387                residual[neighbor][current] = residual[neighbor][current] + pushed_flow;
388                return pushed_flow;
389            }
390        }
391    }
392
393    T::sparse_zero()
394}
395
396/// Compute minimum cut using max-flow min-cut theorem
397///
398/// Returns the vertices reachable from source in the residual graph.
399///
400/// # Arguments
401///
402/// * `result` - Result from a max flow computation
403/// * `source` - Source vertex
404///
405/// # Returns
406///
407/// Vector of booleans indicating which vertices are in the source side of the cut
408pub fn min_cut<T>(result: &MaxFlowResult<T>, source: usize) -> Vec<bool>
409where
410    T: Float + SparseElement + Debug + Copy + 'static,
411{
412    let n = result.flow_matrix.len();
413    let mut reachable = vec![false; n];
414    let mut queue = VecDeque::new();
415
416    queue.push_back(source);
417    reachable[source] = true;
418
419    while let Some(current) = queue.pop_front() {
420        for neighbor in 0..n {
421            if !reachable[neighbor] && result.flow_matrix[current][neighbor] > T::sparse_zero() {
422                reachable[neighbor] = true;
423                queue.push_back(neighbor);
424            }
425        }
426    }
427
428    reachable
429}
430
431#[cfg(test)]
432mod tests {
433    use super::*;
434    use crate::csr_array::CsrArray;
435    use approx::assert_relative_eq;
436
437    fn create_test_flow_network() -> CsrArray<f64> {
438        // Create a simple flow network
439        //     10       10
440        //  0 ----> 1 ----> 3
441        //  |       |       ^
442        //  |  10   |  10   | 10
443        //  v       v       |
444        //  2 -----------> 4
445        //        15
446        let rows = vec![0, 0, 1, 1, 2, 4];
447        let cols = vec![1, 2, 3, 4, 4, 3];
448        let data = vec![10.0, 10.0, 10.0, 10.0, 15.0, 10.0];
449
450        CsrArray::from_triplets(&rows, &cols, &data, (5, 5), false).expect("Failed to create")
451    }
452
453    #[test]
454    fn test_ford_fulkerson() {
455        let graph = create_test_flow_network();
456        let result = ford_fulkerson(&graph, 0, 3).expect("Failed");
457
458        // Maximum flow from 0 to 3 should be 20
459        assert!(result.flow_value > 15.0);
460        assert!(result.flow_value <= 20.0);
461    }
462
463    #[test]
464    fn test_edmonds_karp() {
465        let graph = create_test_flow_network();
466        let result = edmonds_karp(&graph, 0, 3).expect("Failed");
467
468        // Maximum flow from 0 to 3 should be 20
469        assert!(result.flow_value > 15.0);
470        assert!(result.flow_value <= 20.0);
471    }
472
473    #[test]
474    fn test_dinic() {
475        let graph = create_test_flow_network();
476        let result = dinic(&graph, 0, 3).expect("Failed");
477
478        // Maximum flow from 0 to 3 should be 20
479        assert!(result.flow_value > 15.0);
480        assert!(result.flow_value <= 20.0);
481    }
482
483    #[test]
484    fn test_simple_network() {
485        // Simple network: 0 -> 1 -> 2 with capacities 10 and 5
486        let rows = vec![0, 1];
487        let cols = vec![1, 2];
488        let data = vec![10.0, 5.0];
489
490        let graph = CsrArray::from_triplets(&rows, &cols, &data, (3, 3), false).expect("Failed");
491        let result = edmonds_karp(&graph, 0, 2).expect("Failed");
492
493        // Maximum flow should be limited by the bottleneck edge (capacity 5)
494        assert_relative_eq!(result.flow_value, 5.0, epsilon = 1e-6);
495    }
496
497    #[test]
498    fn test_no_path() {
499        // Disconnected network: 0 -> 1, 2 -> 3 (no path from 0 to 3)
500        let rows = vec![0, 2];
501        let cols = vec![1, 3];
502        let data = vec![10.0, 10.0];
503
504        let graph = CsrArray::from_triplets(&rows, &cols, &data, (4, 4), false).expect("Failed");
505        let result = edmonds_karp(&graph, 0, 3).expect("Failed");
506
507        // No flow possible
508        assert_relative_eq!(result.flow_value, 0.0, epsilon = 1e-6);
509    }
510
511    #[test]
512    fn test_min_cut() {
513        let graph = create_test_flow_network();
514        let result = edmonds_karp(&graph, 0, 3).expect("Failed");
515        let cut = min_cut(&result, 0);
516
517        // Source should be in the cut
518        assert!(cut[0]);
519
520        // Sink should not be in the cut
521        assert!(!cut[3]);
522    }
523
524    #[test]
525    fn test_algorithms_agree() {
526        let graph = create_test_flow_network();
527
528        let ff_result = ford_fulkerson(&graph, 0, 3).expect("Failed");
529        let ek_result = edmonds_karp(&graph, 0, 3).expect("Failed");
530        let dinic_result = dinic(&graph, 0, 3).expect("Failed");
531
532        // All algorithms should give the same max flow value
533        assert_relative_eq!(ff_result.flow_value, ek_result.flow_value, epsilon = 1e-6);
534        assert_relative_eq!(
535            ek_result.flow_value,
536            dinic_result.flow_value,
537            epsilon = 1e-6
538        );
539    }
540}