Skip to main content

tide_maxflow/
lib.rs

1//! # Tide Flow
2//!
3//! Implementation of the **Tide** max flow algorithm — a push-pull-relabel variant.
4//!
5//! The algorithm performs global sweeps (BFS relabeling, then global pull, then
6//! global push) in each iteration ("tide"). It uses O(1) array-based data
7//! structures with `i64` capacities for maximum performance.
8//!
9//! ## Algorithm Overview
10//!
11//! Each tide consists of three phases:
12//! 1. **globalRelabel** — BFS from source and sink on the residual graph to
13//!    compute exact vertex heights.
14//! 2. **globalPull** — Right-fold over overflowing vertices (descending level),
15//!    pulling flow on reverse edges.
16//! 3. **globalPush** — Left-fold over overflowing vertices (ascending level),
17//!    pushing flow on forward edges.
18//!
19//! Terminates when net flow and the overflowing set are unchanged between tides.
20//!
21//! ## Optimizations
22//!
23//! - Skips `globalRelabel` when no edge crossed a saturation boundary.
24//! - Precomputed level buckets (levels are constant).
25//! - Contiguous adjacency storage (CSR-like head/next arrays).
26//! - Reused BFS buffers across tides.
27//! - Allocation-free convergence check via excess hash.
28
29/// Result of the Tide max flow computation.
30#[derive(Debug, Clone)]
31pub struct MaxFlowResult {
32    /// Maximum flow value
33    pub flow: i64,
34    /// Number of tides (iterations)
35    pub steps: usize,
36    /// Flow on each edge (indexed by edge index; even = forward, odd = reverse)
37    pub edge_flow: Vec<i64>,
38}
39
40/// Per-phase timing breakdown (in microseconds) for profiling.
41#[derive(Debug, Clone, Default)]
42pub struct PhaseTiming {
43    /// Total time in globalRelabel (BFS), microseconds
44    pub relabel_us: u64,
45    /// Total time in globalPull, microseconds
46    pub pull_us: u64,
47    /// Total time in globalPush, microseconds
48    pub push_us: u64,
49    /// Total time in convergence checks, microseconds
50    pub convergence_us: u64,
51    /// Number of tides where globalRelabel was skipped
52    pub relabel_skipped: usize,
53}
54
55/// Result with timing breakdown.
56#[derive(Debug, Clone)]
57pub struct TimedMaxFlowResult {
58    /// Standard max flow result
59    pub result: MaxFlowResult,
60    /// Per-phase timing breakdown
61    pub timing: PhaseTiming,
62}
63
64/// Internal state for the Tide algorithm.
65///
66/// Uses flat arrays with paired edges: forward edge `2*i`, reverse edge `2*i+1`.
67/// Adjacency stored as CSR-like head[v]/adj_edges[] for cache locality.
68/// Within each vertex's adjacency list, forward edges come first, then reverse.
69struct TideState {
70    n: usize,
71    source: usize,
72    sink: usize,
73
74    // --- CSR-like adjacency ---
75    /// head[v]..head[v+1] gives the range into adj_edges for vertex v
76    head: Vec<usize>,
77    /// Flat array of edge indices, grouped by source vertex.
78    /// Forward edges (even) are stored before reverse edges (odd) within each group.
79    adj_edges: Vec<usize>,
80    /// fwd_end[v] is the end of forward edges for vertex v within adj_edges.
81    /// Forward edges: head[v]..fwd_end[v], reverse: fwd_end[v]..head[v+1].
82    fwd_end: Vec<usize>,
83
84    // --- Edge data (flat, paired) ---
85    /// Target vertex of each edge
86    to: Vec<usize>,
87    /// Capacity of each edge
88    cap: Vec<i64>,
89    /// Current flow on each edge (residual = cap[e] - flow[e])
90    flow: Vec<i64>,
91
92    // --- Vertex data ---
93    /// Height (from globalRelabel BFS)
94    height: Vec<i32>,
95    /// Excess flow
96    excess: Vec<i64>,
97    /// Level (BFS distance from source in original graph, constant)
98    #[allow(dead_code)]
99    level: Vec<i32>,
100
101    // --- Precomputed level buckets ---
102    /// max_level value
103    max_level: usize,
104    /// level_buckets[l] = list of vertices at level l (excluding source/sink)
105    level_buckets: Vec<Vec<usize>>,
106
107    // --- Reusable BFS buffers ---
108    /// Two flat vectors used as alternating current/next-level buffers
109    bfs_cur: Vec<usize>,
110    bfs_next: Vec<usize>,
111    /// Distance buffers (reset to -1 each BFS)
112    dist_buf1: Vec<i32>,
113    dist_buf2: Vec<i32>,
114
115    // --- State ---
116    topology_changed: bool,
117    steps: usize,
118}
119
120impl TideState {
121    fn new(n: usize, source: usize, sink: usize, edges: &[(usize, usize, i64)]) -> Self {
122        let m = edges.len();
123        let total = 2 * m;
124
125        let mut to = vec![0usize; total];
126        let mut cap = vec![0i64; total];
127
128        for (i, &(u, v, c)) in edges.iter().enumerate() {
129            let fwd = 2 * i;
130            let rev = 2 * i + 1;
131            to[fwd] = v;
132            to[rev] = u;
133            cap[fwd] = c;
134            cap[rev] = 0;
135        }
136
137        // Two-pass CSR construction with forward edges first per vertex.
138        // Pass 1: count forward and reverse degree per vertex.
139        let mut fwd_deg = vec![0u32; n];
140        let mut rev_deg = vec![0u32; n];
141        for (i, &(u, v, _)) in edges.iter().enumerate() {
142            let _ = i;
143            fwd_deg[u] += 1;
144            rev_deg[v] += 1;
145        }
146
147        // Compute head offsets and fwd_end boundary
148        let mut head = vec![0usize; n + 1];
149        let mut fwd_end = vec![0usize; n];
150        for v in 0..n {
151            head[v + 1] = head[v] + (fwd_deg[v] + rev_deg[v]) as usize;
152            fwd_end[v] = head[v] + fwd_deg[v] as usize;
153        }
154        let total_adj = head[n];
155        let mut adj_edges = vec![0usize; total_adj];
156
157        // Pass 2: fill CSR. Use fwd_deg/rev_deg as running insertion pointers.
158        // Reset to use as write cursors: fwd starts at head[v], rev at fwd_end[v].
159        let mut fwd_pos: Vec<usize> = (0..n).map(|v| head[v]).collect();
160        let mut rev_pos: Vec<usize> = fwd_end.clone();
161
162        for (i, &(u, v, _)) in edges.iter().enumerate() {
163            let fwd = 2 * i;
164            let rev = 2 * i + 1;
165            adj_edges[fwd_pos[u]] = fwd;
166            fwd_pos[u] += 1;
167            adj_edges[rev_pos[v]] = rev;
168            rev_pos[v] += 1;
169        }
170        drop(fwd_deg);
171        drop(rev_deg);
172        drop(fwd_pos);
173        drop(rev_pos);
174
175        let flow = vec![0i64; total];
176        let height = vec![0i32; n];
177        let excess = vec![0i64; n];
178
179        // Compute levels: BFS from source on original graph using flat buffer
180        let mut level = vec![-1i32; n];
181        level[source] = 0;
182        let mut bfs_cur = vec![source];
183        let mut bfs_next = Vec::new();
184        while !bfs_cur.is_empty() {
185            for &v in &bfs_cur {
186                // Only forward edges for level BFS
187                for idx in head[v]..fwd_end[v] {
188                    let e = adj_edges[idx];
189                    let w = to[e];
190                    if level[w] == -1 {
191                        level[w] = level[v] + 1;
192                        bfs_next.push(w);
193                    }
194                }
195            }
196            std::mem::swap(&mut bfs_cur, &mut bfs_next);
197            bfs_next.clear();
198        }
199
200        // Precompute level buckets
201        let max_level = level.iter().copied().filter(|&l| l >= 0).max().unwrap_or(0) as usize;
202        let mut level_buckets: Vec<Vec<usize>> = vec![Vec::new(); max_level + 1];
203        for v in 0..n {
204            if v != source && v != sink && level[v] >= 0 {
205                level_buckets[level[v] as usize].push(v);
206            }
207        }
208
209        let dist_buf1 = vec![-1i32; n];
210        let dist_buf2 = vec![-1i32; n];
211
212        TideState {
213            n,
214            source,
215            sink,
216            head,
217            adj_edges,
218            fwd_end,
219            to,
220            cap,
221            flow,
222            height,
223            excess,
224            level,
225            max_level,
226            level_buckets,
227            bfs_cur,
228            bfs_next,
229            dist_buf1,
230            dist_buf2,
231            topology_changed: true,
232            steps: 0,
233        }
234    }
235
236    /// Saturate all source edges to initialize the preflow.
237    fn initialize(&mut self) {
238        let source = self.source;
239        for idx in self.head[source]..self.fwd_end[source] {
240            let e = self.adj_edges[idx]; // guaranteed forward (even)
241            let c = self.cap[e];
242            if c > 0 {
243                self.flow[e] = c;
244                self.flow[e ^ 1] = -c;
245                let v = self.to[e];
246                self.excess[v] += c;
247                self.excess[source] -= c;
248            }
249        }
250        self.height[source] = self.n as i32;
251    }
252
253    /// Global relabel: BFS from sink (and source if needed) on residual graph.
254    /// Uses flat level-synchronous BFS with separate forward/reverse CSR lists.
255    fn global_relabel(&mut self) {
256        let n = self.n;
257        let source = self.source;
258        let sink = self.sink;
259
260        // Reset sink dist buffer
261        let dist_sink = &mut self.dist_buf1;
262        for x in dist_sink.iter_mut() {
263            *x = -1;
264        }
265        dist_sink[sink] = 0;
266
267        // Sink BFS using reverse-only CSR: at vertex v, iterate incoming forward
268        // edges (stored as reverse edges in rev_edges). The reverse edge e (odd)
269        // has e^1 = forward edge. To traverse backward on residual, we need
270        // residual of the reverse edge from w→v, i.e. cap[e^1] - flow[e^1] > 0.
271        // Wait — for BFS from sink, we go against the flow direction.
272        // At vertex v in sink BFS, we want to reach neighbors w such that there
273        // is residual capacity from w to v (so flow can reach sink via w→v).
274        // That means: residual(w→v) > 0, i.e. cap[e_wv] - flow[e_wv] > 0.
275        // Using the all-edges CSR at v: for each edge e from v, the reverse e^1
276        // goes from to[e] back to v. Residual on e^1 = cap[e^1] - flow[e^1].
277        // If that > 0, then to[e] can reach v on the residual graph.
278        //
279        // With separate CSR: at v, fwd_edges gives forward edges from v (we need
280        // their reverses to check if someone can reach v), and rev_edges gives
281        // reverse edges at v (original edges pointing TO v — their residual
282        // gives backward reachability).
283        //
284        // Sink BFS wants: from v, find all w such that res(w→v) > 0.
285        // Edge from w to v is a forward edge e (even), stored in fwd_edges of w
286        // and as reverse edge e^1 (odd) in rev_edges of v.
287        // res(w→v) via forward edge = cap[e] - flow[e] (the original forward edge).
288        // The reverse edge at v is e^1 = e|1, and the forward is e^1^1 = e.
289        // So: for rev edge r in rev_edges[v], fwd = r^1, w = to[r],
290        //     res = cap[fwd] - flow[fwd]. But wait, that's residual on the
291        //     original forward edge (w→v direction). For BFS from sink going
292        //     backward, we want edges where flow can still be sent toward sink,
293        //     meaning residual from w to v on the residual graph.
294        //
295        // Actually, let's think simply. In the residual graph, edge (w,v) exists
296        // if cap[e_wv] - flow[e_wv] > 0 for any edge e_wv (forward or reverse).
297        // BFS from sink traverses the REVERSE of residual edges.
298        // So from v in BFS, we want w such that (w,v) is a residual edge.
299        //
300        // Using all-edges CSR at v: edge e from v→to[e]. The reverse e^1 is
301        // from to[e]→v. If cap[e^1]-flow[e^1] > 0, then to[e] has residual to v.
302        // This is the original code. Let's just use all-edges CSR for BFS
303        // (it iterates all edges anyway) but with the flat buffer.
304        self.bfs_cur.clear();
305        self.bfs_cur.push(sink);
306        let mut reached = 1usize;
307
308        while !self.bfs_cur.is_empty() {
309            self.bfs_next.clear();
310            for i in 0..self.bfs_cur.len() {
311                let v = self.bfs_cur[i];
312                let d_next = dist_sink[v] + 1;
313                for idx in self.head[v]..self.head[v + 1] {
314                    let e = self.adj_edges[idx];
315                    let w = self.to[e];
316                    let rev = e ^ 1;
317                    if dist_sink[w] == -1 && self.cap[rev] - self.flow[rev] > 0 {
318                        dist_sink[w] = d_next;
319                        self.bfs_next.push(w);
320                        reached += 1;
321                    }
322                }
323            }
324            std::mem::swap(&mut self.bfs_cur, &mut self.bfs_next);
325        }
326
327        // Source BFS: only needed if sink BFS didn't reach all vertices
328        let need_source_bfs = reached < n;
329        if need_source_bfs {
330            let dist_source = &mut self.dist_buf2;
331            for x in dist_source.iter_mut() {
332                *x = -1;
333            }
334            dist_source[source] = 0;
335
336            self.bfs_cur.clear();
337            self.bfs_cur.push(source);
338
339            while !self.bfs_cur.is_empty() {
340                self.bfs_next.clear();
341                for i in 0..self.bfs_cur.len() {
342                    let v = self.bfs_cur[i];
343                    let d_next = dist_source[v] + 1;
344                    for idx in self.head[v]..self.head[v + 1] {
345                        let e = self.adj_edges[idx];
346                        let w = self.to[e];
347                        if dist_source[w] == -1 && self.cap[e] - self.flow[e] > 0 {
348                            dist_source[w] = d_next;
349                            self.bfs_next.push(w);
350                        }
351                    }
352                }
353                std::mem::swap(&mut self.bfs_cur, &mut self.bfs_next);
354            }
355        }
356
357        // Set heights
358        let n_i32 = n as i32;
359        for v in 0..n {
360            if v == source {
361                self.height[v] = n_i32;
362            } else if v == sink {
363                self.height[v] = 0;
364            } else if self.dist_buf1[v] >= 0 {
365                self.height[v] = self.dist_buf1[v];
366            } else if need_source_bfs && self.dist_buf2[v] >= 0 {
367                self.height[v] = n_i32 + self.dist_buf2[v];
368            }
369        }
370    }
371
372    /// Global push: ascending level order, forward edges only.
373    /// Uses fwd_end boundary to iterate only forward edges.
374    fn global_push(&mut self) {
375        for lvl in 0..=self.max_level {
376            for i in 0..self.level_buckets[lvl].len() {
377                let v = self.level_buckets[lvl][i];
378                if self.excess[v] <= 0 {
379                    continue;
380                }
381                let h_target = self.height[v] - 1;
382                for idx in self.head[v]..self.fwd_end[v] {
383                    let e = self.adj_edges[idx]; // guaranteed even (forward)
384                    let w = self.to[e];
385                    let res_cap = self.cap[e] - self.flow[e];
386                    if self.height[w] == h_target && res_cap > 0 {
387                        let delta = self.excess[v].min(res_cap);
388
389                        let old_bwd = self.cap[e ^ 1] - self.flow[e ^ 1] > 0;
390
391                        self.flow[e] += delta;
392                        self.flow[e ^ 1] -= delta;
393                        self.excess[v] -= delta;
394                        self.excess[w] += delta;
395
396                        let new_fwd = self.cap[e] - self.flow[e] > 0;
397                        let new_bwd = self.cap[e ^ 1] - self.flow[e ^ 1] > 0;
398                        if !new_fwd || (old_bwd != new_bwd) {
399                            self.topology_changed = true;
400                        }
401
402                        if self.excess[v] <= 0 {
403                            break;
404                        }
405                    }
406                }
407            }
408        }
409    }
410
411    /// Global pull: descending level order, reverse edges only.
412    /// Uses fwd_end boundary to iterate only reverse edges.
413    fn global_pull(&mut self) {
414        for lvl in (0..=self.max_level).rev() {
415            for i in 0..self.level_buckets[lvl].len() {
416                let v = self.level_buckets[lvl][i];
417                if self.excess[v] <= 0 {
418                    continue;
419                }
420                let h_target = self.height[v] - 1;
421                for idx in self.fwd_end[v]..self.head[v + 1] {
422                    let e = self.adj_edges[idx]; // guaranteed odd (reverse)
423                    let fwd = e ^ 1; // the forward edge
424                    let u = self.to[e]; // = to[rev] = source of forward edge
425                    let res_cap = self.cap[e] - self.flow[e]; // residual on reverse edge
426
427                    if self.height[u] == h_target && res_cap > 0 {
428                        let delta = self.excess[v].min(res_cap);
429
430                        let old_fwd = self.cap[fwd] - self.flow[fwd] > 0;
431
432                        self.flow[e] += delta;
433                        self.flow[fwd] -= delta;
434                        self.excess[v] -= delta;
435                        self.excess[u] += delta;
436
437                        let new_fwd = self.cap[fwd] - self.flow[fwd] > 0;
438                        let new_bwd = self.cap[e] - self.flow[e] > 0;
439                        if (old_fwd != new_fwd) || !new_bwd {
440                            self.topology_changed = true;
441                        }
442
443                        if self.excess[v] <= 0 {
444                            break;
445                        }
446                    }
447                }
448            }
449        }
450    }
451
452    /// Local relabel: scan all residual neighbors of v, return
453    /// min(neighbor height) + 1, or None if no residual neighbors.
454    #[inline]
455    fn local_relabel(&self, v: usize) -> Option<i32> {
456        let mut min_h = i32::MAX;
457
458        // Forward residual: v -> w
459        for idx in self.head[v]..self.fwd_end[v] {
460            let e = self.adj_edges[idx];
461            if self.cap[e] - self.flow[e] > 0 {
462                min_h = min_h.min(self.height[self.to[e]]);
463            }
464        }
465        // Reverse residual: u -> v (stored as reverse edge at v)
466        for idx in self.fwd_end[v]..self.head[v + 1] {
467            let e = self.adj_edges[idx];
468            if self.cap[e] - self.flow[e] > 0 {
469                min_h = min_h.min(self.height[self.to[e]]);
470            }
471        }
472
473        if min_h == i32::MAX {
474            None
475        } else {
476            Some(min_h + 1)
477        }
478    }
479
480    /// Global pull with local relabeling.
481    fn global_pull_relabel(&mut self) {
482        for lvl in (0..=self.max_level).rev() {
483            for i in 0..self.level_buckets[lvl].len() {
484                let v = self.level_buckets[lvl][i];
485                if self.excess[v] <= 0 {
486                    continue;
487                }
488
489                let h_target = self.height[v] - 1;
490                let mut pulled = false;
491                for idx in self.fwd_end[v]..self.head[v + 1] {
492                    let e = self.adj_edges[idx];
493                    let fwd = e ^ 1;
494                    let u = self.to[e];
495                    let res_cap = self.cap[e] - self.flow[e];
496                    if self.height[u] == h_target && res_cap > 0 {
497                        let delta = self.excess[v].min(res_cap);
498                        let old_fwd = self.cap[fwd] - self.flow[fwd] > 0;
499                        self.flow[e] += delta;
500                        self.flow[fwd] -= delta;
501                        self.excess[v] -= delta;
502                        self.excess[u] += delta;
503                        let new_fwd = self.cap[fwd] - self.flow[fwd] > 0;
504                        let new_bwd = self.cap[e] - self.flow[e] > 0;
505                        if (old_fwd != new_fwd) || !new_bwd {
506                            self.topology_changed = true;
507                        }
508                        pulled = true;
509                        if self.excess[v] <= 0 {
510                            break;
511                        }
512                    }
513                }
514
515                if !pulled && self.excess[v] > 0 {
516                    if let Some(new_h) = self.local_relabel(v) {
517                        if new_h > self.height[v] {
518                            self.height[v] = new_h;
519                            let h_target = new_h - 1;
520                            for idx in self.fwd_end[v]..self.head[v + 1] {
521                                let e = self.adj_edges[idx];
522                                let fwd = e ^ 1;
523                                let u = self.to[e];
524                                let res_cap = self.cap[e] - self.flow[e];
525                                if self.height[u] == h_target && res_cap > 0 {
526                                    let delta = self.excess[v].min(res_cap);
527                                    let old_fwd = self.cap[fwd] - self.flow[fwd] > 0;
528                                    self.flow[e] += delta;
529                                    self.flow[fwd] -= delta;
530                                    self.excess[v] -= delta;
531                                    self.excess[u] += delta;
532                                    let new_fwd = self.cap[fwd] - self.flow[fwd] > 0;
533                                    let new_bwd = self.cap[e] - self.flow[e] > 0;
534                                    if (old_fwd != new_fwd) || !new_bwd {
535                                        self.topology_changed = true;
536                                    }
537                                    if self.excess[v] <= 0 {
538                                        break;
539                                    }
540                                }
541                            }
542                        }
543                    }
544                }
545            }
546        }
547    }
548
549    /// Global push with local relabeling.
550    fn global_push_relabel(&mut self) {
551        for lvl in 0..=self.max_level {
552            for i in 0..self.level_buckets[lvl].len() {
553                let v = self.level_buckets[lvl][i];
554                if self.excess[v] <= 0 {
555                    continue;
556                }
557
558                let h_target = self.height[v] - 1;
559                let mut pushed = false;
560                for idx in self.head[v]..self.fwd_end[v] {
561                    let e = self.adj_edges[idx];
562                    let w = self.to[e];
563                    let res_cap = self.cap[e] - self.flow[e];
564                    if self.height[w] == h_target && res_cap > 0 {
565                        let delta = self.excess[v].min(res_cap);
566                        let old_bwd = self.cap[e ^ 1] - self.flow[e ^ 1] > 0;
567                        self.flow[e] += delta;
568                        self.flow[e ^ 1] -= delta;
569                        self.excess[v] -= delta;
570                        self.excess[w] += delta;
571                        let new_fwd = self.cap[e] - self.flow[e] > 0;
572                        let new_bwd = self.cap[e ^ 1] - self.flow[e ^ 1] > 0;
573                        if !new_fwd || (old_bwd != new_bwd) {
574                            self.topology_changed = true;
575                        }
576                        pushed = true;
577                        if self.excess[v] <= 0 {
578                            break;
579                        }
580                    }
581                }
582
583                if !pushed && self.excess[v] > 0 {
584                    if let Some(new_h) = self.local_relabel(v) {
585                        if new_h > self.height[v] {
586                            self.height[v] = new_h;
587                            let h_target = new_h - 1;
588                            for idx in self.head[v]..self.fwd_end[v] {
589                                let e = self.adj_edges[idx];
590                                let w = self.to[e];
591                                let res_cap = self.cap[e] - self.flow[e];
592                                if self.height[w] == h_target && res_cap > 0 {
593                                    let delta = self.excess[v].min(res_cap);
594                                    let old_bwd = self.cap[e ^ 1] - self.flow[e ^ 1] > 0;
595                                    self.flow[e] += delta;
596                                    self.flow[e ^ 1] -= delta;
597                                    self.excess[v] -= delta;
598                                    self.excess[w] += delta;
599                                    let new_fwd = self.cap[e] - self.flow[e] > 0;
600                                    let new_bwd = self.cap[e ^ 1] - self.flow[e ^ 1] > 0;
601                                    if !new_fwd || (old_bwd != new_bwd) {
602                                        self.topology_changed = true;
603                                    }
604                                    if self.excess[v] <= 0 {
605                                        break;
606                                    }
607                                }
608                            }
609                        }
610                    }
611                }
612            }
613        }
614    }
615
616    /// Hybrid Tide: alternates between full BFS tides and local-relabel tides.
617    ///
618    /// Structure: outer loop does full BFS tide, then inner loop does
619    /// local-relabel tides until flow stops increasing. Terminates when
620    /// a full BFS tide makes no progress.
621    fn tide_hybrid(&mut self) -> MaxFlowResult {
622        self.initialize();
623
624        // Step budget: if hybrid exceeds this, fall back to standard tides
625        // for the remainder. On small graphs (n<=30) standard takes <=50 steps,
626        // so 10*n^2 is a generous hybrid budget.
627        let step_budget = 10 * self.n * self.n;
628        let mut bfs_stall_count = 0u32;
629
630        loop {
631            // Full BFS tide
632            let (old_flow, old_hash) = self.convergence_state();
633            self.global_relabel();
634            self.topology_changed = false;
635            self.global_pull();
636            self.global_push();
637            self.steps += 1;
638
639            let (bfs_flow, bfs_hash) = self.convergence_state();
640
641            // Terminate if this BFS tide made no progress at all
642            if bfs_flow == old_flow && bfs_hash == old_hash {
643                break;
644            }
645
646            // If flow didn't increase, count as BFS stall; bail after 3
647            if bfs_flow <= old_flow {
648                bfs_stall_count += 1;
649                if bfs_stall_count >= 3 {
650                    // Fall back to standard tides for the rest
651                    break;
652                }
653            } else {
654                bfs_stall_count = 0;
655            }
656
657            // If we've used too many steps in hybrid mode, stop local-relabeling
658            if self.steps > step_budget {
659                continue; // skip local-relabel, just do BFS tides
660            }
661
662            // Local-relabel tides: continue while flow keeps increasing.
663            // Bail as soon as flow stalls for 2 consecutive tides.
664            let mut prev_flow = bfs_flow;
665            let mut stall_count = 0u32;
666            loop {
667                let (_, old_hash2) = self.convergence_state();
668                self.topology_changed = false;
669                self.global_pull_relabel();
670                self.global_push_relabel();
671                self.steps += 1;
672
673                let (new_flow2, new_hash2) = self.convergence_state();
674
675                // Full convergence: no change at all
676                if new_flow2 == prev_flow && new_hash2 == old_hash2 {
677                    break;
678                }
679
680                if new_flow2 > prev_flow {
681                    // Flow increased — reset stall counter
682                    prev_flow = new_flow2;
683                    stall_count = 0;
684                } else {
685                    // Flow flat but excess moved — allow a few more tries
686                    stall_count += 1;
687                    if stall_count >= 2 {
688                        break;
689                    }
690                }
691            }
692        }
693
694        // If we broke out of the hybrid loop due to BFS stall,
695        // finish with standard tides to guarantee correctness.
696        loop {
697            let (old_flow, old_hash) = self.convergence_state();
698            self.global_relabel();
699            self.topology_changed = false;
700            self.global_pull();
701            self.global_push();
702            self.steps += 1;
703            let (new_flow, new_hash) = self.convergence_state();
704            if new_flow == old_flow && new_hash == old_hash {
705                break;
706            }
707        }
708
709        let (flow, _) = self.convergence_state();
710        MaxFlowResult {
711            flow,
712            steps: self.steps,
713            edge_flow: self.flow.clone(),
714        }
715    }
716
717    /// Hybrid Tide with per-phase timing.
718    fn tide_hybrid_timed(&mut self) -> TimedMaxFlowResult {
719        use std::time::Instant;
720        self.initialize();
721        let mut timing = PhaseTiming::default();
722        let step_budget = 10 * self.n * self.n;
723        let mut bfs_stall_count = 0u32;
724
725        loop {
726            // Full BFS tide
727            let tc0 = Instant::now();
728            let (old_flow, old_hash) = self.convergence_state();
729            let tc1 = Instant::now();
730            timing.convergence_us += tc1.duration_since(tc0).as_micros() as u64;
731
732            let tb0 = Instant::now();
733            self.global_relabel();
734            let tb1 = Instant::now();
735            timing.relabel_us += tb1.duration_since(tb0).as_micros() as u64;
736            self.topology_changed = false;
737
738            let tp0 = Instant::now();
739            self.global_pull();
740            let tp1 = Instant::now();
741            timing.pull_us += tp1.duration_since(tp0).as_micros() as u64;
742
743            let ts0 = Instant::now();
744            self.global_push();
745            let ts1 = Instant::now();
746            timing.push_us += ts1.duration_since(ts0).as_micros() as u64;
747            self.steps += 1;
748
749            let tc2 = Instant::now();
750            let (bfs_flow, bfs_hash) = self.convergence_state();
751            let tc3 = Instant::now();
752            timing.convergence_us += tc3.duration_since(tc2).as_micros() as u64;
753
754            if bfs_flow == old_flow && bfs_hash == old_hash {
755                break;
756            }
757
758            if bfs_flow <= old_flow {
759                bfs_stall_count += 1;
760                if bfs_stall_count >= 3 {
761                    break;
762                }
763            } else {
764                bfs_stall_count = 0;
765            }
766
767            if self.steps > step_budget {
768                continue;
769            }
770
771            // Local-relabel tides
772            let mut prev_flow = bfs_flow;
773            let mut stall_count = 0u32;
774            loop {
775                let tc4 = Instant::now();
776                let (_, old_hash2) = self.convergence_state();
777                let tc5 = Instant::now();
778                timing.convergence_us += tc5.duration_since(tc4).as_micros() as u64;
779                timing.relabel_skipped += 1;
780                self.topology_changed = false;
781
782                let t2 = Instant::now();
783                self.global_pull_relabel();
784                let t3 = Instant::now();
785                timing.pull_us += t3.duration_since(t2).as_micros() as u64;
786
787                let t4 = Instant::now();
788                self.global_push_relabel();
789                let t5 = Instant::now();
790                timing.push_us += t5.duration_since(t4).as_micros() as u64;
791                self.steps += 1;
792
793                let tc6 = Instant::now();
794                let (new_flow2, new_hash2) = self.convergence_state();
795                let tc7 = Instant::now();
796                timing.convergence_us += tc7.duration_since(tc6).as_micros() as u64;
797
798                if new_flow2 == prev_flow && new_hash2 == old_hash2 {
799                    break;
800                }
801
802                if new_flow2 > prev_flow {
803                    prev_flow = new_flow2;
804                    stall_count = 0;
805                } else {
806                    stall_count += 1;
807                    if stall_count >= 2 {
808                        break;
809                    }
810                }
811            }
812        }
813
814        // Finish with standard tides to guarantee correctness
815        loop {
816            let tc0 = Instant::now();
817            let (old_flow, old_hash) = self.convergence_state();
818            let tc1 = Instant::now();
819            timing.convergence_us += tc1.duration_since(tc0).as_micros() as u64;
820
821            let tb0 = Instant::now();
822            self.global_relabel();
823            let tb1 = Instant::now();
824            timing.relabel_us += tb1.duration_since(tb0).as_micros() as u64;
825            self.topology_changed = false;
826
827            let tp0 = Instant::now();
828            self.global_pull();
829            let tp1 = Instant::now();
830            timing.pull_us += tp1.duration_since(tp0).as_micros() as u64;
831
832            let ts0 = Instant::now();
833            self.global_push();
834            let ts1 = Instant::now();
835            timing.push_us += ts1.duration_since(ts0).as_micros() as u64;
836            self.steps += 1;
837
838            let tc2 = Instant::now();
839            let (new_flow, new_hash) = self.convergence_state();
840            let tc3 = Instant::now();
841            timing.convergence_us += tc3.duration_since(tc2).as_micros() as u64;
842
843            if new_flow == old_flow && new_hash == old_hash {
844                break;
845            }
846        }
847
848        let (flow, _) = self.convergence_state();
849        TimedMaxFlowResult {
850            result: MaxFlowResult {
851                flow,
852                steps: self.steps,
853                edge_flow: self.flow.clone(),
854            },
855            timing,
856        }
857    }
858
859    /// Compute a hash of the overflowing set for cheap convergence detection.
860    /// Returns (net_flow, excess_hash).
861    fn convergence_state(&self) -> (i64, u64) {
862        // Sum flow on forward edges arriving at sink (iterate reverse edges at sink)
863        let mut flow = 0i64;
864        for idx in self.fwd_end[self.sink]..self.head[self.sink + 1] {
865            let e = self.adj_edges[idx]; // reverse edge (odd)
866            flow += self.flow[e ^ 1]; // flow on the forward edge
867        }
868
869        // Hash of overflowing set: XOR of (vertex * golden_ratio_prime + excess)
870        let mut hash = 0u64;
871        for v in 0..self.n {
872            if v != self.source && v != self.sink && self.excess[v] > 0 {
873                hash ^= (v as u64).wrapping_mul(0x9E3779B97F4A7C15)
874                    ^ (self.excess[v] as u64).wrapping_mul(0x517CC1B727220A95);
875            }
876        }
877
878        (flow, hash)
879    }
880
881    /// Adaptive Tide: starts with standard BFS tides, switches to hybrid
882    /// (local-relabel) mode after `WARMUP` tides if the algorithm is still running.
883    ///
884    /// This gives the best of both worlds:
885    /// - Graphs converging in few tides (layered, chains, grids) use only standard
886    ///   tides with zero local-relabel overhead
887    /// - Graphs needing many tides (bipartite, random, washington) get BFS-skip
888    ///   savings from local relabeling
889    fn tide_adaptive(&mut self) -> MaxFlowResult {
890        const WARMUP: usize = 5;
891        self.initialize();
892
893        // Phase 1: standard BFS tides for warmup period
894        for _ in 0..WARMUP {
895            let (old_flow, old_hash) = self.convergence_state();
896            if self.topology_changed {
897                self.global_relabel();
898            }
899            self.topology_changed = false;
900            self.global_pull();
901            self.global_push();
902            self.steps += 1;
903            let (new_flow, new_hash) = self.convergence_state();
904            if new_flow == old_flow && new_hash == old_hash {
905                let (flow, _) = self.convergence_state();
906                return MaxFlowResult {
907                    flow,
908                    steps: self.steps,
909                    edge_flow: self.flow.clone(),
910                };
911            }
912        }
913
914        // Phase 2: hybrid mode — BFS tide then local-relabel tides
915        let step_budget = 10 * self.n * self.n;
916        let mut bfs_stall_count = 0u32;
917
918        loop {
919            // Full BFS tide
920            let (old_flow, old_hash) = self.convergence_state();
921            self.global_relabel();
922            self.topology_changed = false;
923            self.global_pull();
924            self.global_push();
925            self.steps += 1;
926
927            let (bfs_flow, bfs_hash) = self.convergence_state();
928            if bfs_flow == old_flow && bfs_hash == old_hash {
929                break;
930            }
931
932            if bfs_flow <= old_flow {
933                bfs_stall_count += 1;
934                if bfs_stall_count >= 3 {
935                    break;
936                }
937            } else {
938                bfs_stall_count = 0;
939            }
940
941            if self.steps > step_budget {
942                continue;
943            }
944
945            // Local-relabel tides
946            let mut prev_flow = bfs_flow;
947            let mut stall_count = 0u32;
948            loop {
949                let (_, old_hash2) = self.convergence_state();
950                self.topology_changed = false;
951                self.global_pull_relabel();
952                self.global_push_relabel();
953                self.steps += 1;
954
955                let (new_flow2, new_hash2) = self.convergence_state();
956                if new_flow2 == prev_flow && new_hash2 == old_hash2 {
957                    break;
958                }
959                if new_flow2 > prev_flow {
960                    prev_flow = new_flow2;
961                    stall_count = 0;
962                } else {
963                    stall_count += 1;
964                    if stall_count >= 2 {
965                        break;
966                    }
967                }
968            }
969        }
970
971        // Safety: finish with standard tides
972        loop {
973            let (old_flow, old_hash) = self.convergence_state();
974            self.global_relabel();
975            self.topology_changed = false;
976            self.global_pull();
977            self.global_push();
978            self.steps += 1;
979            let (new_flow, new_hash) = self.convergence_state();
980            if new_flow == old_flow && new_hash == old_hash {
981                break;
982            }
983        }
984
985        let (flow, _) = self.convergence_state();
986        MaxFlowResult {
987            flow,
988            steps: self.steps,
989            edge_flow: self.flow.clone(),
990        }
991    }
992
993    /// Adaptive Tide with per-phase timing.
994    fn tide_adaptive_timed(&mut self) -> TimedMaxFlowResult {
995        use std::time::Instant;
996        const WARMUP: usize = 5;
997        self.initialize();
998        let mut timing = PhaseTiming::default();
999
1000        // Phase 1: standard BFS tides for warmup period
1001        for _ in 0..WARMUP {
1002            let t0 = Instant::now();
1003            let (old_flow, old_hash) = self.convergence_state();
1004            let t1 = Instant::now();
1005            timing.convergence_us += t1.duration_since(t0).as_micros() as u64;
1006
1007            if self.topology_changed {
1008                let t2 = Instant::now();
1009                self.global_relabel();
1010                let t3 = Instant::now();
1011                timing.relabel_us += t3.duration_since(t2).as_micros() as u64;
1012            } else {
1013                timing.relabel_skipped += 1;
1014            }
1015            self.topology_changed = false;
1016
1017            let t4 = Instant::now();
1018            self.global_pull();
1019            let t5 = Instant::now();
1020            timing.pull_us += t5.duration_since(t4).as_micros() as u64;
1021
1022            let t6 = Instant::now();
1023            self.global_push();
1024            let t7 = Instant::now();
1025            timing.push_us += t7.duration_since(t6).as_micros() as u64;
1026            self.steps += 1;
1027
1028            let t8 = Instant::now();
1029            let (new_flow, new_hash) = self.convergence_state();
1030            let t9 = Instant::now();
1031            timing.convergence_us += t9.duration_since(t8).as_micros() as u64;
1032
1033            if new_flow == old_flow && new_hash == old_hash {
1034                let (flow, _) = self.convergence_state();
1035                return TimedMaxFlowResult {
1036                    result: MaxFlowResult {
1037                        flow,
1038                        steps: self.steps,
1039                        edge_flow: self.flow.clone(),
1040                    },
1041                    timing,
1042                };
1043            }
1044        }
1045
1046        // Phase 2: hybrid mode
1047        let step_budget = 10 * self.n * self.n;
1048        let mut bfs_stall_count = 0u32;
1049
1050        loop {
1051            let tc0 = Instant::now();
1052            let (old_flow, old_hash) = self.convergence_state();
1053            let tc1 = Instant::now();
1054            timing.convergence_us += tc1.duration_since(tc0).as_micros() as u64;
1055
1056            let tb0 = Instant::now();
1057            self.global_relabel();
1058            let tb1 = Instant::now();
1059            timing.relabel_us += tb1.duration_since(tb0).as_micros() as u64;
1060            self.topology_changed = false;
1061
1062            let tp0 = Instant::now();
1063            self.global_pull();
1064            let tp1 = Instant::now();
1065            timing.pull_us += tp1.duration_since(tp0).as_micros() as u64;
1066
1067            let ts0 = Instant::now();
1068            self.global_push();
1069            let ts1 = Instant::now();
1070            timing.push_us += ts1.duration_since(ts0).as_micros() as u64;
1071            self.steps += 1;
1072
1073            let tc2 = Instant::now();
1074            let (bfs_flow, bfs_hash) = self.convergence_state();
1075            let tc3 = Instant::now();
1076            timing.convergence_us += tc3.duration_since(tc2).as_micros() as u64;
1077
1078            if bfs_flow == old_flow && bfs_hash == old_hash {
1079                break;
1080            }
1081
1082            if bfs_flow <= old_flow {
1083                bfs_stall_count += 1;
1084                if bfs_stall_count >= 3 {
1085                    break;
1086                }
1087            } else {
1088                bfs_stall_count = 0;
1089            }
1090
1091            if self.steps > step_budget {
1092                continue;
1093            }
1094
1095            // Local-relabel tides
1096            let mut prev_flow = bfs_flow;
1097            let mut stall_count = 0u32;
1098            loop {
1099                let tc4 = Instant::now();
1100                let (_, old_hash2) = self.convergence_state();
1101                let tc5 = Instant::now();
1102                timing.convergence_us += tc5.duration_since(tc4).as_micros() as u64;
1103                timing.relabel_skipped += 1;
1104                self.topology_changed = false;
1105
1106                let t2 = Instant::now();
1107                self.global_pull_relabel();
1108                let t3 = Instant::now();
1109                timing.pull_us += t3.duration_since(t2).as_micros() as u64;
1110
1111                let t4 = Instant::now();
1112                self.global_push_relabel();
1113                let t5 = Instant::now();
1114                timing.push_us += t5.duration_since(t4).as_micros() as u64;
1115                self.steps += 1;
1116
1117                let tc6 = Instant::now();
1118                let (new_flow2, new_hash2) = self.convergence_state();
1119                let tc7 = Instant::now();
1120                timing.convergence_us += tc7.duration_since(tc6).as_micros() as u64;
1121
1122                if new_flow2 == prev_flow && new_hash2 == old_hash2 {
1123                    break;
1124                }
1125                if new_flow2 > prev_flow {
1126                    prev_flow = new_flow2;
1127                    stall_count = 0;
1128                } else {
1129                    stall_count += 1;
1130                    if stall_count >= 2 {
1131                        break;
1132                    }
1133                }
1134            }
1135        }
1136
1137        // Safety: finish with standard tides
1138        loop {
1139            let tc0 = Instant::now();
1140            let (old_flow, old_hash) = self.convergence_state();
1141            let tc1 = Instant::now();
1142            timing.convergence_us += tc1.duration_since(tc0).as_micros() as u64;
1143
1144            let tb0 = Instant::now();
1145            self.global_relabel();
1146            let tb1 = Instant::now();
1147            timing.relabel_us += tb1.duration_since(tb0).as_micros() as u64;
1148            self.topology_changed = false;
1149
1150            let tp0 = Instant::now();
1151            self.global_pull();
1152            let tp1 = Instant::now();
1153            timing.pull_us += tp1.duration_since(tp0).as_micros() as u64;
1154
1155            let ts0 = Instant::now();
1156            self.global_push();
1157            let ts1 = Instant::now();
1158            timing.push_us += ts1.duration_since(ts0).as_micros() as u64;
1159            self.steps += 1;
1160
1161            let tc2 = Instant::now();
1162            let (new_flow, new_hash) = self.convergence_state();
1163            let tc3 = Instant::now();
1164            timing.convergence_us += tc3.duration_since(tc2).as_micros() as u64;
1165
1166            if new_flow == old_flow && new_hash == old_hash {
1167                break;
1168            }
1169        }
1170
1171        let (flow, _) = self.convergence_state();
1172        TimedMaxFlowResult {
1173            result: MaxFlowResult {
1174                flow,
1175                steps: self.steps,
1176                edge_flow: self.flow.clone(),
1177            },
1178            timing,
1179        }
1180    }
1181
1182    /// Run the Tide algorithm to completion.
1183    fn tide(&mut self) -> MaxFlowResult {
1184        self.initialize();
1185
1186        loop {
1187            let (old_flow, old_hash) = self.convergence_state();
1188
1189            if self.topology_changed {
1190                self.global_relabel();
1191            }
1192            self.topology_changed = false;
1193
1194            self.global_pull();
1195            self.global_push();
1196            self.steps += 1;
1197
1198            let (new_flow, new_hash) = self.convergence_state();
1199
1200            if new_flow == old_flow && new_hash == old_hash {
1201                break;
1202            }
1203        }
1204
1205        let (flow, _) = self.convergence_state();
1206        MaxFlowResult {
1207            flow,
1208            steps: self.steps,
1209            edge_flow: self.flow.clone(),
1210        }
1211    }
1212
1213    /// Run the Tide algorithm with per-phase timing instrumentation.
1214    fn tide_timed(&mut self) -> TimedMaxFlowResult {
1215        use std::time::Instant;
1216
1217        self.initialize();
1218        let mut timing = PhaseTiming::default();
1219
1220        loop {
1221            let t0 = Instant::now();
1222            let (old_flow, old_hash) = self.convergence_state();
1223            let t1 = Instant::now();
1224            timing.convergence_us += t1.duration_since(t0).as_micros() as u64;
1225
1226            if self.topology_changed {
1227                let t2 = Instant::now();
1228                self.global_relabel();
1229                let t3 = Instant::now();
1230                timing.relabel_us += t3.duration_since(t2).as_micros() as u64;
1231            } else {
1232                timing.relabel_skipped += 1;
1233            }
1234            self.topology_changed = false;
1235
1236            let t4 = Instant::now();
1237            self.global_pull();
1238            let t5 = Instant::now();
1239            timing.pull_us += t5.duration_since(t4).as_micros() as u64;
1240
1241            let t6 = Instant::now();
1242            self.global_push();
1243            let t7 = Instant::now();
1244            timing.push_us += t7.duration_since(t6).as_micros() as u64;
1245
1246            self.steps += 1;
1247
1248            let t8 = Instant::now();
1249            let (new_flow, new_hash) = self.convergence_state();
1250            let t9 = Instant::now();
1251            timing.convergence_us += t9.duration_since(t8).as_micros() as u64;
1252
1253            if new_flow == old_flow && new_hash == old_hash {
1254                break;
1255            }
1256        }
1257
1258        let (flow, _) = self.convergence_state();
1259        TimedMaxFlowResult {
1260            result: MaxFlowResult {
1261                flow,
1262                steps: self.steps,
1263                edge_flow: self.flow.clone(),
1264            },
1265            timing,
1266        }
1267    }
1268}
1269
1270/// Compute the maximum flow in a network using the Tide algorithm.
1271///
1272/// # Arguments
1273/// * `n` - Number of vertices (0-indexed)
1274/// * `source` - Source vertex
1275/// * `sink` - Sink vertex
1276/// * `edges` - List of (from, to, capacity) tuples
1277///
1278/// # Returns
1279/// A `MaxFlowResult` containing the max flow value, step count, and per-edge flows.
1280///
1281/// # Example
1282/// ```
1283/// use tide_maxflow::max_flow;
1284/// let result = max_flow(4, 0, 3, &[(0, 1, 10), (0, 2, 10), (1, 3, 10), (2, 3, 10)]);
1285/// assert_eq!(result.flow, 20);
1286/// ```
1287pub fn max_flow(
1288    n: usize,
1289    source: usize,
1290    sink: usize,
1291    edges: &[(usize, usize, i64)],
1292) -> MaxFlowResult {
1293    let mut state = TideState::new(n, source, sink, edges);
1294    state.tide()
1295}
1296
1297/// Compute max flow with per-phase timing instrumentation.
1298///
1299/// Same as `max_flow` but returns a `TimedMaxFlowResult` with timing breakdown
1300/// for each phase (relabel, pull, push, convergence check).
1301pub fn max_flow_timed(
1302    n: usize,
1303    source: usize,
1304    sink: usize,
1305    edges: &[(usize, usize, i64)],
1306) -> TimedMaxFlowResult {
1307    let mut state = TideState::new(n, source, sink, edges);
1308    state.tide_timed()
1309}
1310
1311/// Compute max flow using the hybrid Tide algorithm.
1312///
1313/// Full BFS on the first tide, then local relabeling during pull/push
1314/// until flow stalls, then full BFS again. Terminates when a full BFS
1315/// tide makes no progress.
1316pub fn max_flow_hybrid(
1317    n: usize,
1318    source: usize,
1319    sink: usize,
1320    edges: &[(usize, usize, i64)],
1321) -> MaxFlowResult {
1322    let mut state = TideState::new(n, source, sink, edges);
1323    state.tide_hybrid()
1324}
1325
1326/// Compute max flow using adaptive Tide.
1327///
1328/// Starts with standard BFS tides. If the algorithm hasn't converged after
1329/// 5 tides, switches to hybrid mode (local relabeling between BFS tides).
1330/// Best of both worlds: no overhead on easy graphs, BFS-skip savings on hard ones.
1331pub fn max_flow_adaptive(
1332    n: usize,
1333    source: usize,
1334    sink: usize,
1335    edges: &[(usize, usize, i64)],
1336) -> MaxFlowResult {
1337    let mut state = TideState::new(n, source, sink, edges);
1338    state.tide_adaptive()
1339}
1340
1341/// Compute max flow using adaptive Tide with per-phase timing.
1342pub fn max_flow_adaptive_timed(
1343    n: usize,
1344    source: usize,
1345    sink: usize,
1346    edges: &[(usize, usize, i64)],
1347) -> TimedMaxFlowResult {
1348    let mut state = TideState::new(n, source, sink, edges);
1349    state.tide_adaptive_timed()
1350}
1351
1352/// Compute max flow using the hybrid Tide algorithm with timing.
1353pub fn max_flow_hybrid_timed(
1354    n: usize,
1355    source: usize,
1356    sink: usize,
1357    edges: &[(usize, usize, i64)],
1358) -> TimedMaxFlowResult {
1359    let mut state = TideState::new(n, source, sink, edges);
1360    state.tide_hybrid_timed()
1361}
1362
1363#[cfg(test)]
1364mod tests {
1365    use super::*;
1366
1367    #[test]
1368    fn test_simple_path() {
1369        let result = max_flow(3, 0, 2, &[(0, 1, 10), (1, 2, 5)]);
1370        assert_eq!(result.flow, 5);
1371    }
1372
1373    #[test]
1374    fn test_parallel_paths() {
1375        let result = max_flow(4, 0, 3, &[(0, 1, 10), (1, 3, 10), (0, 2, 15), (2, 3, 15)]);
1376        assert_eq!(result.flow, 25);
1377    }
1378
1379    #[test]
1380    fn test_diamond() {
1381        let result = max_flow(
1382            4,
1383            0,
1384            3,
1385            &[(0, 1, 10), (0, 2, 10), (1, 2, 1), (1, 3, 10), (2, 3, 10)],
1386        );
1387        assert_eq!(result.flow, 20);
1388    }
1389
1390    #[test]
1391    fn test_bottleneck() {
1392        let result = max_flow(4, 0, 3, &[(0, 1, 100), (1, 2, 1), (2, 3, 100)]);
1393        assert_eq!(result.flow, 1);
1394    }
1395
1396    #[test]
1397    fn test_no_path() {
1398        let result = max_flow(4, 0, 3, &[(0, 1, 10), (2, 3, 10)]);
1399        assert_eq!(result.flow, 0);
1400    }
1401
1402    #[test]
1403    fn test_single_edge() {
1404        let result = max_flow(2, 0, 1, &[(0, 1, 42)]);
1405        assert_eq!(result.flow, 42);
1406    }
1407
1408    #[test]
1409    fn test_multiple_paths_shared_edge() {
1410        let result = max_flow(
1411            5,
1412            0,
1413            4,
1414            &[(0, 1, 10), (0, 2, 10), (1, 3, 10), (2, 3, 10), (3, 4, 15)],
1415        );
1416        assert_eq!(result.flow, 15);
1417    }
1418
1419    #[test]
1420    fn test_layered_graph() {
1421        let mut edges = Vec::new();
1422        for i in 0..3 {
1423            edges.push((0, i + 1, 10));
1424        }
1425        for i in 0..3 {
1426            for j in 0..3 {
1427                edges.push((i + 1, j + 4, 5));
1428            }
1429        }
1430        for j in 0..3 {
1431            edges.push((j + 4, 7, 10));
1432        }
1433        let result = max_flow(8, 0, 7, &edges);
1434        assert_eq!(result.flow, 30);
1435    }
1436
1437    #[test]
1438    fn test_clrs() {
1439        let result = max_flow(
1440            6,
1441            0,
1442            5,
1443            &[
1444                (0, 1, 16),
1445                (0, 2, 13),
1446                (1, 2, 10),
1447                (1, 3, 12),
1448                (2, 1, 4),
1449                (2, 4, 14),
1450                (3, 2, 9),
1451                (3, 5, 20),
1452                (4, 3, 7),
1453                (4, 5, 4),
1454            ],
1455        );
1456        assert_eq!(result.flow, 23);
1457    }
1458}