Skip to main content

graphmind_graph_algorithms/
flow.rs

1//! Network flow algorithms
2//!
3//! Implements Max Flow using Edmonds-Karp algorithm (BFS-based Ford-Fulkerson).
4
5use super::common::{GraphView, NodeId};
6use std::collections::{HashMap, VecDeque};
7
8pub struct FlowResult {
9    pub max_flow: f64,
10}
11
12/// Edmonds-Karp Algorithm for Max Flow
13///
14/// Assumes `view.weights` represents capacity.
15/// If weights are missing, assumes capacity 1.0.
16pub fn edmonds_karp(view: &GraphView, source: NodeId, sink: NodeId) -> Option<FlowResult> {
17    let s_idx = *view.node_to_index.get(&source)?;
18    let t_idx = *view.node_to_index.get(&sink)?;
19
20    let n = view.node_count;
21
22    // Build residual graph capacity matrix (or adjacency map for sparse)
23    // Since GraphView is adjacency list, let's map it to a residual structure
24    // (u_idx, v_idx) -> capacity
25    // We need backward edges too.
26
27    // Using a vector of hashmaps for residual graph: residual[u][v] = capacity
28    let mut residual: Vec<HashMap<usize, f64>> = vec![HashMap::new(); n];
29
30    for u in 0..n {
31        let edges = view.successors(u);
32        let weights = view.weights(u);
33
34        for (i, &v) in edges.iter().enumerate() {
35            let cap = if let Some(w) = weights { w[i] } else { 1.0 };
36            *residual[u].entry(v).or_insert(0.0) += cap;
37            // Ensure backward edge exists in map with 0 capacity if not present
38            residual[v].entry(u).or_insert(0.0);
39        }
40    }
41
42    let mut total_flow = 0.0;
43
44    loop {
45        // Find path using BFS
46        let mut parent = vec![None; n];
47        let mut queue = VecDeque::new();
48        queue.push_back(s_idx);
49
50        // visited array not strictly needed if we check parent (but source has no parent)
51        // using a separate visited set or checking if u == s or parent[u] is set
52        let mut found_path = false;
53
54        // Special marker for source parent to distinguish from unvisited
55        // Actually, just use a visited bitset or map
56        let mut visited = vec![false; n];
57        visited[s_idx] = true;
58
59        while let Some(u) = queue.pop_front() {
60            if u == t_idx {
61                found_path = true;
62                break;
63            }
64
65            for (&v, &cap) in &residual[u] {
66                if !visited[v] && cap > 1e-9 {
67                    visited[v] = true;
68                    parent[v] = Some(u);
69                    queue.push_back(v);
70                }
71            }
72        }
73
74        if !found_path {
75            break;
76        }
77
78        // Calculate path flow
79        let mut path_flow = f64::INFINITY;
80        let mut curr = t_idx;
81        while curr != s_idx {
82            let prev = parent[curr].unwrap();
83            let cap = residual[prev][&curr];
84            if cap < path_flow {
85                path_flow = cap;
86            }
87            curr = prev;
88        }
89
90        // Update residual capacities
91        curr = t_idx;
92        while curr != s_idx {
93            let prev = parent[curr].unwrap();
94
95            *residual[prev].get_mut(&curr).unwrap() -= path_flow;
96            *residual[curr].get_mut(&prev).unwrap() += path_flow;
97
98            curr = prev;
99        }
100
101        total_flow += path_flow;
102    }
103
104    Some(FlowResult {
105        max_flow: total_flow,
106    })
107}
108
109#[cfg(test)]
110mod tests {
111    use super::*;
112    use std::collections::HashMap;
113
114    #[test]
115    fn test_edmonds_karp() {
116        // 0 -> 1 (10)
117        // 0 -> 2 (10)
118        // 1 -> 2 (2)
119        // 1 -> 3 (4)
120        // 2 -> 4 (9)
121        // 3 -> 5 (10)
122        // 4 -> 3 (6)
123        // 4 -> 5 (10)
124
125        // Let's use a simpler diamond graph
126        // S(1) -> A(2) (100)
127        // S(1) -> B(3) (50)
128        // A(2) -> B(3) (50)
129        // A(2) -> T(4) (50)
130        // B(3) -> T(4) (100)
131
132        // Expected max flow:
133        // S->A->T: 50
134        // S->B->T: 50
135        // S->A->B->T: 50
136        // Total: 150?
137        // S->A (100) splits to A->B (50) and A->T (50).
138        // S->B (50) joins A->B (50) making 100 at B? No, capacity constraints.
139
140        // S->A->T: Flow 50. Residual: S->A 50, A->T 0.
141        // S->B->T: Flow 50. Residual: S->B 0, B->T 50.
142        // S->A->B->T: S->A (50 left), A->B (50), B->T (50 left). Flow 50.
143        // Total 150. Correct.
144
145        let node_count = 4;
146        let index_to_node = vec![1, 2, 3, 4];
147        let mut node_to_index = HashMap::new();
148        for (i, &id) in index_to_node.iter().enumerate() {
149            node_to_index.insert(id, i);
150        }
151
152        let mut outgoing = vec![vec![]; 4];
153        let mut weights = vec![vec![]; 4];
154
155        // S->A
156        outgoing[0].push(1);
157        weights[0].push(100.0);
158        // S->B
159        outgoing[0].push(2);
160        weights[0].push(50.0);
161        // A->B
162        outgoing[1].push(2);
163        weights[1].push(50.0);
164        // A->T
165        outgoing[1].push(3);
166        weights[1].push(50.0);
167        // B->T
168        outgoing[2].push(3);
169        weights[2].push(100.0);
170
171        let view = GraphView::from_adjacency_list(
172            node_count,
173            index_to_node,
174            node_to_index,
175            outgoing,
176            vec![vec![]; 4],
177            Some(weights),
178        );
179
180        let result = edmonds_karp(&view, 1, 4).unwrap();
181        assert_eq!(result.max_flow, 150.0);
182    }
183}