algraph
A pure Haskell graph library using adjacency list representation, featuring
the Tide algorithm — a level-synchronous push-pull-relabel solver for the
maximum flow problem.
Quick start
import Data.Graph.AdjacencyList
import Data.Graph.AdjacencyList.Network
import Data.Graph.AdjacencyList.PushRelabel.Pure
import Data.Graph.AdjacencyList.PushRelabel.Internal (netFlow, stCut)
import qualified Data.Map.Strict as M
main :: IO ()
main = do
-- Build a directed graph: 1 -> 2 -> 4
-- 1 -> 3 -> 4
let g = graphFromEdges [Edge 1 2, Edge 1 3, Edge 2 4, Edge 3 4]
caps = M.fromList [ (Edge 1 2, 10), (Edge 1 3, 5)
, (Edge 2 4, 8), (Edge 3 4, 7) ]
net = Network { graph = g, source = 1, sink = 4
, capacities = caps, flow = M.empty }
case pushRelabel net of
Left err -> putStrLn $ "Error: " ++ err
Right rg -> do
putStrLn $ "Max flow: " ++ show (netFlow rg) -- 13
putStrLn $ "Min cut: " ++ show (stCut rg) -- s-t cut edges
Features
- Maximum flow via the Tide algorithm — the only push-relabel
implementation in the Haskell ecosystem, and the only sub-O(VE^2) pure
functional max-flow solver available
- Exact arithmetic — capacities and flows use
Rational, guaranteeing
correct results for arbitrary inputs (no floating-point rounding)
- s-t minimum cut extracted directly from the max-flow residual graph
- BFS and DFS with level maps, parent maps, spanning trees, topological
sort, longest path, and connectivity queries
- Floyd-Warshall all-pairs shortest paths (weighted and unweighted)
- Graph metrics — eccentricity, radius, diameter, density
- d-dimensional lattice generator — cubic lattices with periodic boundary
conditions (toroidal topology) in arbitrary dimension
- QuickCheck-verified — Tide tested against FGL on 10,000 random graphs
Modules
| Module |
Description |
Data.Graph.AdjacencyList |
Core types (Vertex, Edge, Graph, Neighbors, EdgeMap) and graph constructors |
Data.Graph.AdjacencyList.Network |
Flow network type (Network, Capacities, Capacity = Rational) |
Data.Graph.AdjacencyList.PushRelabel.Pure |
Tide algorithm — pushRelabel :: Network -> Either String ResidualGraph |
Data.Graph.AdjacencyList.PushRelabel.Internal |
Residual graph types, netFlow, stCut, push/pull primitives |
Data.Graph.AdjacencyList.BFS |
Breadth-first search |
Data.Graph.AdjacencyList.DFS |
Depth-first search, topological sort, longest path |
Data.Graph.AdjacencyList.WFI |
Floyd-Warshall all-pairs shortest paths |
Data.Graph.AdjacencyList.Metrics |
Eccentricity, radius, diameter, density |
Data.Graph.AdjacencyList.Grid |
d-dimensional cubic lattices with periodic boundary conditions |
The Tide algorithm
Each iteration ("tide") performs three global sweeps on the residual graph:
- globalRelabel — BFS from sink (and source) to recompute vertex heights
- globalPull — right fold over active vertices: pull flow on reverse edges
- globalPush — left fold over active vertices: push flow on forward edges
The algorithm terminates when both the net flow and the set of overflowing
vertices stabilize. A skip-globalRelabel optimization tracks whether any
edge crosses a saturation boundary during push/pull; when none do, the BFS
is skipped (1.25--1.6x speedup in practice).
Complexity
|
Worst case |
Practical |
| Tides |
O(V^2) |
O(V) |
| Per-tide |
O((V+E) log V) |
O((V+E) log V) |
| Total |
O(V^2 (V+E) log V) |
O(V(V+E) log V) |
The log V factor comes from IntMap lookups in the pure Haskell
implementation. The O(V^2) worst case requires exponentially-varying
capacities; on graphs with polynomially-bounded capacity ratios (covering
virtually all practical inputs), the tide count is empirically O(V).
How it compares
| Algorithm |
Best known complexity |
| Edmonds-Karp (FGL) |
O(VE^2) |
| Dinic |
O(V^2 E) |
| Highest-label push-relabel |
O(V^2 sqrt(E)) |
| Tide (as implemented) |
O(V(V+E) log V) practical |
A companion Rust implementation (tide-maxflow)
achieves O(VE) practical complexity using O(1) array-based data structures and
has been benchmarked against Hi-PR, Google OR-Tools, LEMON, and Boost on 63
DIMACS graph instances.
Context in the Haskell graph ecosystem
| Library |
Max flow |
Shortest paths |
Metrics |
Generators |
| containers |
-- |
-- |
-- |
-- |
| fgl |
Edmonds-Karp O(VE^2) |
Dijkstra, BF |
-- |
-- |
| algebraic-graphs |
-- |
-- |
-- |
-- |
| algraph |
Tide O(V(V+E) log V) |
Floyd-Warshall APSP |
eccentricity, radius, diameter, density |
d-dim lattices (PBC) |
Key differences from fgl:
- Faster max flow — Tide is asymptotically better than fgl's Edmonds-Karp
at all graph densities (O(V(V+E) log V) vs O(VE^2))
- Exact arithmetic — fgl uses
Double for max flow; algraph uses
Rational, guaranteeing correct results for arbitrary capacity values
- Faster traversals — algraph's BFS/DFS are O((V+E) log V) vs fgl's
O(V^2) due to fgl's O(V)-per-vertex
match decomposition
- APSP — Floyd-Warshall is built in; fgl only offers single-source
algorithms
fgl has broader algorithm coverage (SCC, dominators, MST, Dijkstra,
transitive closure) and supports labeled nodes/edges.
fgl's BFS uses match at each vertex, making it O(V^2) instead of the
textbook O(V+E). algraph's BFS is O((V+E) log V) using IntMap/IntSet. The
gap widens with graph size:
| Graph |
V |
E |
algraph |
fgl |
fgl/algraph |
| Grid 100x100 |
10K |
20K |
51 ms |
53 ms |
1.0x |
| Grid 200x200 |
40K |
80K |
258 ms |
337 ms |
1.3x |
| Grid 500x500 |
250K |
500K |
1675 ms |
2848 ms |
1.7x |
| Grid 1000x1000 |
1M |
2M |
6956 ms |
24316 ms |
3.5x |
| Layered 20x50 |
1K |
48K |
60 ms |
296 ms |
5.0x |
| Layered 50x100 |
5K |
490K |
695 ms |
1487 ms |
2.1x |
Building
Requires Stack:
git clone https://github.com/tpapak/algraph
cd algraph
stack build
Testing
stack test
The test suite includes:
- Unit tests for graph construction, BFS, DFS, grid lattices, Floyd-Warshall,
and graph metrics
- Tide max-flow correctness test against FGL on a reference network
- QuickCheck property: Tide vs FGL max-flow agreement on 10,000 random graphs
License
LGPL-3 -- see LICENSE.