#graph #algorithm #max-flow #network-flow #push-relabel

bin+lib tide-maxflow

Tide max flow algorithm — a push-pull-relabel variant with O(1) array-based data structures

1 unstable release

0.1.0 Mar 9, 2026

#836 in Algorithms

LGPL-3.0-or-later

100KB
2K SLoC

tide-maxflow

A Rust implementation of the Tide max-flow algorithm — a push-pull-relabel variant that uses global sweeps and O(1) array-based data structures.

Note: This crate was AI-transpiled from the reference Haskell implementation in algraph (Data.Graph.AdjacencyList.PushRelabel.Pure) and then manually reviewed, optimized, and validated against 12,000+ random graphs.

Algorithm

Each iteration ("tide") consists of three phases:

  1. Global Relabel — BFS from source and sink on the residual graph to compute exact vertex heights.
  2. Global Pull — Right-fold over vertices with excess (descending level), pulling flow along reverse edges.
  3. Global Push — Left-fold over vertices with excess (ascending level), pushing flow along forward edges.

The algorithm terminates when net flow and the set of overflowing vertices are unchanged between tides.

Solver variants

Function Description
max_flow Standard BFS-based tide solver
max_flow_hybrid Full BFS on the first tide, then local relabeling until flow stalls
max_flow_adaptive Starts with standard tides; switches to hybrid mode if not converged after 5 iterations

Each variant has a _timed counterpart (e.g. max_flow_timed) that returns a TimedMaxFlowResult with per-phase timing breakdowns.

Optimizations

  • Skips global relabel when no edge crossed a saturation boundary
  • Precomputed level buckets
  • Contiguous adjacency storage (CSR-like layout)
  • Reused BFS buffers across tides
  • Allocation-free convergence check via excess hash

Usage

Add to your Cargo.toml:

[dependencies]
tide-maxflow = "0.1"
use tide_maxflow::max_flow;

let result = max_flow(4, 0, 3, &[
    (0, 1, 10),
    (0, 2, 10),
    (1, 3, 10),
    (2, 3, 10),
]);
assert_eq!(result.flow, 20);

The input is a list of (from, to, capacity) tuples with i64 capacities. Vertices are 0-indexed. The result includes the max flow value, iteration count, and per-edge flow decomposition.

Benchmarks

97 DIMACS graphs across 11 families (layered, grid, chains, bipartite, random, washington, vision2d, vision3d, rfim2d, rfim3d, Waterloo vision benchmarks). Single-threaded, Apple Silicon, --release. All solver phases ran sequentially.

Compared against Google OR-Tools (C++ push-relabel), and HPF (C, Hochbaum Pseudoflow -- the best serial max-flow on vision graphs per Jensen et al. 2022 TPAMI).

Full interactive benchmark report (HTML)

Tide vs HPF (Pseudoflow)

Tide wins 63 of 87 comparable graphs. Ratio = Tide / HPF (< 1 = Tide faster).

Graph Family Geo Mean Tide Wins Fastest
layered (15) 0.01x 15/15 Tide (100x faster)
grid (11) 0.07x 11/11 Tide (15x faster)
chains (9) 0.07x 9/9 Tide (14x faster)
washington (4) 0.24x 4/4 Tide (4x faster)
random (15) 0.13x 15/15 Tide (8x faster)
bipartite (9) 0.10x 7/9 Tide (10x faster)
vision2d (4) 1.4x 1/3 HPF
rfim2d (4) 12.2x 0/4 HPF
waterloo (9) 6.7x 0/9 HPF

Tide vs OR-Tools (C++ Push-Relabel)

Tide wins 26 of 87 comparable graphs. Ratio = Tide / OR-Tools.

Graph Family Geo Mean Tide Wins Fastest
layered (15) 0.92x 9/15 Tide (1.1x faster)
grid (11) 1.02x 7/11 Tied
chains (9) 0.89x 5/9 Tide (1.1x faster)
random (15) 1.7x 2/15 OR-Tools
bipartite (9) 2.1x 2/9 OR-Tools
vision2d (4) 6.4x 0/4 OR-Tools
waterloo (9) 6.7x 0/9 OR-Tools

Selected results

Times in milliseconds (lower is better).

| Graph | |V| | |E| | Tide (ms) | OR-Tools (ms) | HPF (ms) | |:---|---:|---:|---:|---:|---:| | layered_1000x1000 | 1.0M | 3.0M | 94 | 183 | 10,421 | | layered_5000x1000 | 5.0M | 15.0M | 358 | 560 | 227,647 | | layered_3000x3000 | 9.0M | 27.0M | 700 | 1,284 | 305,944 | | layered_70000x1000 | 70.0M | 210.0M | 6,836 | - | - | | grid_1000x1000 | 1.0M | 2.0M | 92 | 134 | 550 | | grid_4500x4500 | 20.3M | 40.5M | 3,359 | 2,332 | 13,709 | | chains_5000x1000 | 5.0M | 5.0M | 613 | 634 | 902 | | bipartite_1000x1000 | 2K | 1.0M | 555 | 21 | 97 | | random_10000_5pct | 10K | 5.0M | 919 | 299 | 518 | | vision2d_1000x1000 | 1.0M | 6.0M | 1,006 | 227 | 920 | | rfim2d_1000 | 1.0M | 5.0M | 13,985 | 1,549 | 1,680 |

Key findings

  • Tide dominates on structured graphs: 10-100x faster than HPF, competitive with OR-Tools on layered, grid, chains, and random families.
  • OR-Tools and HPF win on vision/RFIM graphs: graphs with t-links at every vertex or random capacities require many tides (20-400+), where the global BFS sweep becomes the bottleneck.
  • Tide count predicts performance: graphs converging in 2-5 tides are Tide's sweet spot; graphs needing >30 tides favor local-operation solvers.
  • Scales to 210M edges: layered_70000x1000 solves in 6.8s (3 tides).

Running benchmarks

cargo bench                         # Criterion benchmarks (Tide vs rs-graph)
cargo build --release
./target/release/solve_dimacs FILE.max            # standard
./target/release/solve_dimacs --adaptive FILE.max  # adaptive

Complexity

Per-tide cost

Each tide performs BFS + a full sweep over all edges:

O(V + E) per tide

Number of tides

Capacity regime Tides Total
Polynomial capacities O(1) O(V + E)
Sub-exponential (base < 2) O(V) O(VE)
Exponential (base >= 2) O(V^2) O(V^2 E)

Worst case: O(V^2) tides, giving O(V^2 E) total. Achieved only by a specific pathological family with exponentially-varying capacities (base >= 2).

Practical case: O(V) tides on all tested graph families with polynomially-bounded capacity ratios, giving O(VE) total — matching Orlin's optimal strongly polynomial bound.

The O(V) tide bound for sub-exponential capacities is empirically robust but remains an open conjecture. The gap between the proven O(V^2) worst case and the empirical O(V) is entirely in the stalling tide analysis.

Comparison with classical algorithms

Algorithm Time complexity
Edmonds-Karp O(V E^2)
Dinic O(V^2 E)
Push-relabel (highest-label) O(V^2 sqrt(E))
Orlin O(V E)
Tide (worst case) O(V^2 E)
Tide (practical) O(V E)

Termination proof

The algorithm is proven to terminate for all networks with positive capacities. The proof uses a potential function argument showing that (1) net flow is non-decreasing, (2) no flow cycles are possible within a stalling phase, and (3) the finite state space forces convergence. See the complexity report for the full proof and empirical verification.

Rust ecosystem

Three other Rust crates provide max-flow solvers:

Crate Algorithm(s) Complexity API style
petgraph Dinic, Ford-Fulkerson (Edmonds-Karp) O(V^2 E), O(V E^2) Graph type + method
rs-graph Edmonds-Karp, Dinic, push-relabel O(V E^2), O(V^2 E), O(V^2 sqrt(E)) Graph trait + function
pathfinding Edmonds-Karp O(V E^2) Edge list + function
tide-maxflow Tide (push-pull-relabel) O(V^2 E) worst, O(VE) practical Edge list + function

tide-maxflow differs from these in several ways:

  • Performance: Tide is significantly faster than all other Rust max-flow implementations across tested graph families (layered, grid, bipartite, random, chains). The CSR-based flat-array layout and global-sweep structure give it a substantial constant-factor advantage over the linked-list and adjacency-map representations used by other crates.
  • Algorithm: The only push-relabel variant available as a standalone Rust crate. rs-graph has a push-relabel implementation, but it is part of a larger graph library.
  • No graph type required: Takes a plain &[(usize, usize, i64)] edge list. No need to construct a graph data structure first.
  • CSR-based internals: Flat arrays with paired forward/reverse edges for cache locality, rather than adjacency-list or linked-list representations.
  • Multiple solver strategies: Standard, hybrid, and adaptive variants with optional per-phase timing instrumentation.

Testing

Correctness is validated by comparing all three solver variants (standard, hybrid, adaptive) against the reference Dinic implementation from the rs-graph crate on thousands of random graphs with varying size, density, and capacity ranges:

cargo test

The test suite (tests/random_comparison.rs) generates 5,000 seeded random graphs (4-30 vertices, sparse to dense, capacities 1-1000) and asserts that every Tide variant produces the same max flow value as rs-graph's Dinic solver.

Binaries

The crate includes two binaries:

  • solve_dimacs -- Reads a DIMACS max-flow file and solves it. Supports --adaptive and --hybrid flags.
  • gen_graphs -- Generates random test graphs in DIMACS format.

License

LGPL-3.0-or-later

No runtime deps