scirs2_graph/algorithms/
flow.rs

1//! Network flow and cut algorithms
2//!
3//! This module contains algorithms for network flow problems and graph cuts.
4
5use crate::base::{DiGraph, EdgeWeight, Graph, IndexType, Node};
6use crate::error::{GraphError, Result};
7use std::collections::{HashMap, VecDeque};
8use std::hash::Hash;
9
10/// Finds a minimum cut in a graph using Karger's algorithm
11///
12/// Returns the minimum cut value and a partition of nodes.
13/// This is a randomized algorithm, so multiple runs may give different results.
14pub fn minimum_cut<N, E, Ix>(graph: &Graph<N, E, Ix>) -> Result<(f64, Vec<bool>)>
15where
16    N: Node + Clone + Hash + Eq,
17    E: EdgeWeight + Into<f64> + Clone,
18    Ix: IndexType,
19{
20    let nodes: Vec<N> = graph.nodes().into_iter().cloned().collect();
21    let n = nodes.len();
22
23    if n < 2 {
24        return Err(GraphError::InvalidGraph(
25            "Graph must have at least 2 nodes".to_string(),
26        ));
27    }
28
29    // For small graphs, try all possible cuts
30    if n <= 10 {
31        let mut min_cut_value = f64::INFINITY;
32        let mut best_partition = vec![false; n];
33
34        // Try all possible bipartitions (except empty and full sets)
35        for mask in 1..(1 << n) - 1 {
36            let mut partition = vec![false; n];
37            for (i, p) in partition.iter_mut().enumerate().take(n) {
38                *p = (mask & (1 << i)) != 0;
39            }
40
41            // Calculate cut value
42            let cut_value = calculate_cut_value(graph, &nodes, &partition);
43
44            if cut_value < min_cut_value {
45                min_cut_value = cut_value;
46                best_partition = partition;
47            }
48        }
49
50        Ok((min_cut_value, best_partition))
51    } else {
52        // For larger graphs, use a heuristic approach
53        // This is a simplified version - a full implementation would use Karger's algorithm
54        minimum_cut_heuristic(graph, &nodes)
55    }
56}
57
58fn calculate_cut_value<N, E, Ix>(graph: &Graph<N, E, Ix>, nodes: &[N], partition: &[bool]) -> f64
59where
60    N: Node + Clone + Hash + Eq,
61    E: EdgeWeight + Into<f64>,
62    Ix: IndexType,
63{
64    let mut cut_value = 0.0;
65
66    for (i, node_i) in nodes.iter().enumerate() {
67        if let Ok(neighbors) = graph.neighbors(node_i) {
68            for neighbor in neighbors {
69                if let Some(j) = nodes.iter().position(|n| n == &neighbor) {
70                    // Only count edges going from partition A to partition B
71                    if partition[i] && !partition[j] {
72                        if let Ok(weight) = graph.edge_weight(node_i, &neighbor) {
73                            cut_value += weight.into();
74                        }
75                    }
76                }
77            }
78        }
79    }
80
81    cut_value
82}
83
84fn minimum_cut_heuristic<N, E, Ix>(graph: &Graph<N, E, Ix>, nodes: &[N]) -> Result<(f64, Vec<bool>)>
85where
86    N: Node + Clone + Hash + Eq,
87    E: EdgeWeight + Into<f64> + Clone,
88    Ix: IndexType,
89{
90    let n = nodes.len();
91    let mut best_cut_value = f64::INFINITY;
92    let mut best_partition = vec![false; n];
93
94    // Try a few random partitions
95    use rand::Rng;
96    let mut rng = rand::rng();
97
98    for _ in 0..10 {
99        let mut partition = vec![false; n];
100        let size_a = rng.random_range(1..n);
101
102        // Randomly select nodes for partition A
103        let mut indices: Vec<usize> = (0..n).collect();
104        use rand::seq::SliceRandom;
105        indices.shuffle(&mut rng);
106
107        for i in 0..size_a {
108            partition[indices[i]] = true;
109        }
110
111        let cut_value = calculate_cut_value(graph, nodes, &partition);
112
113        if cut_value < best_cut_value {
114            best_cut_value = cut_value;
115            best_partition = partition;
116        }
117    }
118
119    Ok((best_cut_value, best_partition))
120}
121
122/// Dinic's algorithm for maximum flow
123///
124/// Implements Dinic's algorithm for finding maximum flow in a directed graph.
125/// This algorithm has time complexity O(V²E) which is better than Ford-Fulkerson
126/// for dense graphs.
127///
128/// # Arguments
129/// * `graph` - The directed graph representing the flow network
130/// * `source` - The source node
131/// * `sink` - The sink node
132///
133/// # Returns
134/// * The maximum flow value from source to sink
135pub fn dinic_max_flow<N, E, Ix>(graph: &DiGraph<N, E, Ix>, source: &N, sink: &N) -> Result<f64>
136where
137    N: Node + Clone + Hash + Eq,
138    E: EdgeWeight + Into<f64> + Clone + std::ops::Sub<Output = E> + num_traits::Zero + PartialOrd,
139    Ix: IndexType,
140{
141    if !graph.contains_node(source) {
142        return Err(GraphError::NodeNotFound);
143    }
144    if !graph.contains_node(sink) {
145        return Err(GraphError::NodeNotFound);
146    }
147    if source == sink {
148        return Err(GraphError::InvalidGraph(
149            "Source and sink cannot be the same".to_string(),
150        ));
151    }
152
153    // Build residual graph with capacities
154    let mut residual_graph = DinicResidualGraph::new(graph, source, sink)?;
155    let mut max_flow = 0.0f64;
156
157    // Main Dinic's algorithm loop
158    loop {
159        // Build level graph using BFS
160        if !residual_graph.build_level_graph() {
161            break; // No augmenting path exists
162        }
163
164        // Find blocking flows using DFS
165        loop {
166            let flow = residual_graph.send_flow(f64::INFINITY);
167            if flow == 0.0 {
168                break;
169            }
170            max_flow += flow;
171        }
172    }
173
174    Ok(max_flow)
175}
176
177/// Internal structure for Dinic's algorithm residual graph
178struct DinicResidualGraph<N: Node> {
179    /// Adjacency list representation: node -> (neighbor, capacity, reverse_edge_index)
180    adj: HashMap<N, Vec<(N, f64, usize)>>,
181    source: N,
182    sink: N,
183    /// Level of each node in the level graph (-1 means unreachable)
184    level: HashMap<N, i32>,
185    /// Current edge index for each node (for DFS optimization)
186    current: HashMap<N, usize>,
187}
188
189impl<N: Node + Clone + Hash + Eq> DinicResidualGraph<N> {
190    fn new<E, Ix>(graph: &DiGraph<N, E, Ix>, source: &N, sink: &N) -> Result<Self>
191    where
192        E: EdgeWeight + Into<f64> + Clone,
193        Ix: IndexType,
194    {
195        let mut adj: HashMap<N, Vec<(N, f64, usize)>> = HashMap::new();
196
197        // Initialize adjacency lists for all nodes
198        for node in graph.nodes() {
199            adj.insert(node.clone(), Vec::new());
200        }
201
202        // Add edges to residual graph
203        for node in graph.nodes() {
204            if let Ok(successors) = graph.successors(node) {
205                for successor in successors {
206                    if let Ok(weight) = graph.edge_weight(node, &successor) {
207                        let capacity: f64 = weight.into();
208
209                        // Forward edge
210                        let forward_idx = adj.get(&successor).map(|v| v.len()).unwrap_or(0);
211                        adj.entry(node.clone()).or_default().push((
212                            successor.clone(),
213                            capacity,
214                            forward_idx,
215                        ));
216
217                        // Backward edge (initially 0 capacity)
218                        let backward_idx = adj.get(node).map(|v| v.len() - 1).unwrap_or(0);
219                        adj.entry(successor.clone()).or_default().push((
220                            node.clone(),
221                            0.0,
222                            backward_idx,
223                        ));
224                    }
225                }
226            }
227        }
228
229        Ok(DinicResidualGraph {
230            adj,
231            source: source.clone(),
232            sink: sink.clone(),
233            level: HashMap::new(),
234            current: HashMap::new(),
235        })
236    }
237
238    /// Build level graph using BFS
239    fn build_level_graph(&mut self) -> bool {
240        self.level.clear();
241        self.current.clear();
242
243        // Initialize all nodes to level -1 (unreachable)
244        for node in self.adj.keys() {
245            self.level.insert(node.clone(), -1);
246            self.current.insert(node.clone(), 0);
247        }
248
249        // BFS from source
250        let mut queue = VecDeque::new();
251        queue.push_back(self.source.clone());
252        self.level.insert(self.source.clone(), 0);
253
254        while let Some(node) = queue.pop_front() {
255            if let Some(edges) = self.adj.get(&node) {
256                for (neighbor, capacity, _) in edges {
257                    if *capacity > 0.0 && self.level.get(neighbor) == Some(&-1) {
258                        self.level.insert(neighbor.clone(), self.level[&node] + 1);
259                        queue.push_back(neighbor.clone());
260                    }
261                }
262            }
263        }
264
265        // Return true if sink is reachable
266        self.level.get(&self.sink) != Some(&-1)
267    }
268
269    /// Send flow from source to sink using DFS
270    fn send_flow(&mut self, max_flow: f64) -> f64 {
271        self.dfs(&self.source.clone(), max_flow)
272    }
273
274    fn dfs(&mut self, node: &N, flow: f64) -> f64 {
275        if node == &self.sink {
276            return flow;
277        }
278
279        let current_idx = *self.current.get(node).unwrap_or(&0);
280        let node_level = *self.level.get(node).unwrap_or(&-1);
281
282        if let Some(edges) = self.adj.get(node).cloned() {
283            for i in current_idx..edges.len() {
284                let (neighbor, capacity, rev_idx) = &edges[i];
285                let neighbor_level = *self.level.get(neighbor).unwrap_or(&-1);
286
287                if *capacity > 0.0 && neighbor_level == node_level + 1 {
288                    let bottleneck = flow.min(*capacity);
289                    let pushed = self.dfs(neighbor, bottleneck);
290
291                    if pushed > 0.0 {
292                        // Update capacities
293                        if let Some(node_edges) = self.adj.get_mut(node) {
294                            node_edges[i].1 -= pushed; // Forward edge
295                        }
296                        if let Some(neighbor_edges) = self.adj.get_mut(neighbor) {
297                            neighbor_edges[*rev_idx].1 += pushed; // Backward edge
298                        }
299                        return pushed;
300                    }
301                }
302
303                // Update current pointer
304                self.current.insert(node.clone(), i + 1);
305            }
306        }
307
308        0.0
309    }
310}
311
312/// Push-relabel algorithm for maximum flow
313///
314/// Implements the push-relabel algorithm for finding maximum flow.
315/// This algorithm has time complexity O(V³) and works well for dense graphs.
316///
317/// # Arguments
318/// * `graph` - The directed graph representing the flow network
319/// * `source` - The source node
320/// * `sink` - The sink node
321///
322/// # Returns
323/// * The maximum flow value from source to sink
324pub fn push_relabel_max_flow<N, E, Ix>(
325    graph: &DiGraph<N, E, Ix>,
326    source: &N,
327    sink: &N,
328) -> Result<f64>
329where
330    N: Node + Clone + Hash + Eq,
331    E: EdgeWeight + Into<f64> + Clone,
332    Ix: IndexType,
333{
334    if !graph.contains_node(source) {
335        return Err(GraphError::NodeNotFound);
336    }
337    if !graph.contains_node(sink) {
338        return Err(GraphError::NodeNotFound);
339    }
340    if source == sink {
341        return Err(GraphError::InvalidGraph(
342            "Source and sink cannot be the same".to_string(),
343        ));
344    }
345
346    // Build capacity matrix and other data structures
347    let nodes: Vec<N> = graph.nodes().into_iter().cloned().collect();
348    let n = nodes.len();
349    let mut capacity = vec![vec![0.0f64; n]; n];
350    let mut flow = vec![vec![0.0f64; n]; n];
351    let mut height = vec![0; n];
352    let mut excess = vec![0.0f64; n];
353
354    // Node to index mapping
355    let node_to_idx: HashMap<N, usize> = nodes
356        .iter()
357        .enumerate()
358        .map(|(i, node)| (node.clone(), i))
359        .collect();
360
361    let source_idx = *node_to_idx.get(source).unwrap();
362    let sink_idx = *node_to_idx.get(sink).unwrap();
363
364    // Initialize capacity matrix
365    for node in &nodes {
366        if let Ok(successors) = graph.successors(node) {
367            let from_idx = node_to_idx[node];
368            for successor in successors {
369                let to_idx = node_to_idx[&successor];
370                if let Ok(weight) = graph.edge_weight(node, &successor) {
371                    capacity[from_idx][to_idx] = weight.into();
372                }
373            }
374        }
375    }
376
377    // Initialize preflow
378    height[source_idx] = n;
379    for i in 0..n {
380        if capacity[source_idx][i] > 0.0 {
381            flow[source_idx][i] = capacity[source_idx][i];
382            flow[i][source_idx] = -capacity[source_idx][i];
383            excess[i] = capacity[source_idx][i];
384        }
385    }
386
387    // Main push-relabel loop
388    loop {
389        // Find active node (excess > 0 and not source or sink)
390        let mut active_node = None;
391        for (i, &excess_val) in excess.iter().enumerate().take(n) {
392            if i != source_idx && i != sink_idx && excess_val > 0.0 {
393                active_node = Some(i);
394                break;
395            }
396        }
397
398        if let Some(u) = active_node {
399            let mut pushed = false;
400
401            // Try to push to neighbors
402            for v in 0..n {
403                if capacity[u][v] - flow[u][v] > 0.0 && height[u] == height[v] + 1 {
404                    let delta = excess[u].min(capacity[u][v] - flow[u][v]);
405                    flow[u][v] += delta;
406                    flow[v][u] -= delta;
407                    excess[u] -= delta;
408                    excess[v] += delta;
409                    pushed = true;
410
411                    if excess[u] == 0.0 {
412                        break;
413                    }
414                }
415            }
416
417            // If no push was possible, relabel
418            if !pushed {
419                let mut min_height = usize::MAX;
420                for (v, &height_val) in height.iter().enumerate().take(n) {
421                    if capacity[u][v] - flow[u][v] > 0.0 {
422                        min_height = min_height.min(height_val);
423                    }
424                }
425                if min_height < usize::MAX {
426                    height[u] = min_height + 1;
427                }
428            }
429        } else {
430            break; // No active nodes, algorithm terminates
431        }
432    }
433
434    // Calculate total flow leaving source
435    let total_flow: f64 = (0..n).map(|i| flow[source_idx][i]).sum();
436    Ok(total_flow)
437}
438
439#[cfg(test)]
440mod tests {
441    use super::*;
442    use crate::error::Result as GraphResult;
443    use crate::generators::create_graph;
444
445    #[test]
446    fn test_minimum_cut_simple() -> GraphResult<()> {
447        let mut graph = create_graph::<&str, f64>();
448
449        // Create two clusters connected by a single edge
450        // Cluster 1: A-B-C
451        graph.add_edge("A", "B", 10.0)?;
452        graph.add_edge("B", "C", 10.0)?;
453        graph.add_edge("C", "A", 10.0)?;
454
455        // Cluster 2: D-E-F
456        graph.add_edge("D", "E", 10.0)?;
457        graph.add_edge("E", "F", 10.0)?;
458        graph.add_edge("F", "D", 10.0)?;
459
460        // Bridge between clusters
461        graph.add_edge("C", "D", 1.0)?;
462
463        let (cut_value, partition) = minimum_cut(&graph)?;
464
465        // The minimum cut should be 1.0 (the bridge edge)
466        assert!((cut_value - 1.0).abs() < 1e-6);
467
468        // Check that the partition separates the two clusters
469        let nodes: Vec<&str> = graph.nodes().into_iter().cloned().collect();
470        let cluster1: Vec<bool> = nodes.iter().map(|n| ["A", "B", "C"].contains(n)).collect();
471        let cluster2: Vec<bool> = nodes.iter().map(|n| ["D", "E", "F"].contains(n)).collect();
472
473        // Partition should match one of the clusters
474        let matches_cluster1 = partition.iter().zip(&cluster1).all(|(a, b)| a == b);
475        let matches_cluster2 = partition.iter().zip(&cluster2).all(|(a, b)| a == b);
476
477        assert!(matches_cluster1 || matches_cluster2);
478
479        Ok(())
480    }
481
482    #[test]
483    fn test_minimum_cut_single_edge() -> GraphResult<()> {
484        let mut graph = create_graph::<&str, f64>();
485
486        // Simple two-node graph
487        graph.add_edge("A", "B", 5.0)?;
488
489        let (cut_value, partition) = minimum_cut(&graph)?;
490
491        // The only cut has value 5.0
492        assert_eq!(cut_value, 5.0);
493
494        // One node in each partition
495        assert_eq!(partition.iter().filter(|&&x| x).count(), 1);
496        assert_eq!(partition.iter().filter(|&&x| !x).count(), 1);
497
498        Ok(())
499    }
500
501    #[test]
502    fn test_dinic_max_flow() -> GraphResult<()> {
503        let mut graph = crate::generators::create_digraph::<&str, f64>();
504
505        // Create a simple flow network:
506        //   A --2--> B --3--> D
507        //   |        |        ^
508        //   1        1        |
509        //   v        v        2
510        //   C -------4------> E
511        graph.add_edge("A", "B", 2.0)?;
512        graph.add_edge("A", "C", 1.0)?;
513        graph.add_edge("B", "D", 3.0)?;
514        graph.add_edge("B", "E", 1.0)?;
515        graph.add_edge("C", "E", 4.0)?;
516        graph.add_edge("E", "D", 2.0)?;
517
518        let max_flow = dinic_max_flow(&graph, &"A", &"D")?;
519
520        // Maximum flow from A to D should be 3.0
521        // Path 1: A->B->D (flow 2)
522        // Path 2: A->C->E->D (flow 1)
523        assert!((max_flow - 3.0).abs() < 1e-6);
524
525        Ok(())
526    }
527
528    #[test]
529    fn test_push_relabel_max_flow() -> GraphResult<()> {
530        let mut graph = crate::generators::create_digraph::<&str, f64>();
531
532        // Create a simple flow network
533        graph.add_edge("S", "A", 10.0)?;
534        graph.add_edge("S", "B", 10.0)?;
535        graph.add_edge("A", "T", 10.0)?;
536        graph.add_edge("B", "T", 10.0)?;
537        graph.add_edge("A", "B", 1.0)?;
538
539        let max_flow = push_relabel_max_flow(&graph, &"S", &"T")?;
540
541        // Maximum flow should be 20.0
542        assert!((max_flow - 20.0).abs() < 1e-6);
543
544        Ok(())
545    }
546
547    #[test]
548    fn test_dinic_single_path() -> GraphResult<()> {
549        let mut graph = crate::generators::create_digraph::<&str, f64>();
550
551        // Simple path: A -> B -> C
552        graph.add_edge("A", "B", 5.0)?;
553        graph.add_edge("B", "C", 3.0)?;
554
555        let max_flow = dinic_max_flow(&graph, &"A", &"C")?;
556
557        // Bottleneck is 3.0
558        assert!((max_flow - 3.0).abs() < 1e-6);
559
560        Ok(())
561    }
562}