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}