samyama_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 { max_flow: total_flow })
105}
106
107#[cfg(test)]
108mod tests {
109 use super::*;
110 use std::collections::HashMap;
111
112 #[test]
113 fn test_edmonds_karp() {
114 // 0 -> 1 (10)
115 // 0 -> 2 (10)
116 // 1 -> 2 (2)
117 // 1 -> 3 (4)
118 // 2 -> 4 (9)
119 // 3 -> 5 (10)
120 // 4 -> 3 (6)
121 // 4 -> 5 (10)
122
123 // Let's use a simpler diamond graph
124 // S(1) -> A(2) (100)
125 // S(1) -> B(3) (50)
126 // A(2) -> B(3) (50)
127 // A(2) -> T(4) (50)
128 // B(3) -> T(4) (100)
129
130 // Expected max flow:
131 // S->A->T: 50
132 // S->B->T: 50
133 // S->A->B->T: 50
134 // Total: 150?
135 // S->A (100) splits to A->B (50) and A->T (50).
136 // S->B (50) joins A->B (50) making 100 at B? No, capacity constraints.
137
138 // S->A->T: Flow 50. Residual: S->A 50, A->T 0.
139 // S->B->T: Flow 50. Residual: S->B 0, B->T 50.
140 // S->A->B->T: S->A (50 left), A->B (50), B->T (50 left). Flow 50.
141 // Total 150. Correct.
142
143 let node_count = 4;
144 let index_to_node = vec![1, 2, 3, 4];
145 let mut node_to_index = HashMap::new();
146 for (i, &id) in index_to_node.iter().enumerate() { node_to_index.insert(id, i); }
147
148 let mut outgoing = vec![vec![]; 4];
149 let mut weights = vec![vec![]; 4];
150
151 // S->A
152 outgoing[0].push(1); weights[0].push(100.0);
153 // S->B
154 outgoing[0].push(2); weights[0].push(50.0);
155 // A->B
156 outgoing[1].push(2); weights[1].push(50.0);
157 // A->T
158 outgoing[1].push(3); weights[1].push(50.0);
159 // B->T
160 outgoing[2].push(3); weights[2].push(100.0);
161
162 let view = GraphView::from_adjacency_list(
163 node_count,
164 index_to_node,
165 node_to_index,
166 outgoing,
167 vec![vec![]; 4],
168 Some(weights),
169 );
170
171 let result = edmonds_karp(&view, 1, 4).unwrap();
172 assert_eq!(result.max_flow, 150.0);
173 }
174}