MyNixOS website logo
Description

Graph library using adjacency list representation.

Please see the README on GitHub at https://github.com/tpapak/algraph#readme

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

ModuleDescription
Data.Graph.AdjacencyListCore types (Vertex, Edge, Graph, Neighbors, EdgeMap) and graph constructors
Data.Graph.AdjacencyList.NetworkFlow network type (Network, Capacities, Capacity = Rational)
Data.Graph.AdjacencyList.PushRelabel.PureTide algorithmpushRelabel :: Network -> Either String ResidualGraph
Data.Graph.AdjacencyList.PushRelabel.InternalResidual graph types, netFlow, stCut, push/pull primitives
Data.Graph.AdjacencyList.BFSBreadth-first search
Data.Graph.AdjacencyList.DFSDepth-first search, topological sort, longest path
Data.Graph.AdjacencyList.WFIFloyd-Warshall all-pairs shortest paths
Data.Graph.AdjacencyList.MetricsEccentricity, radius, diameter, density
Data.Graph.AdjacencyList.Gridd-dimensional cubic lattices with periodic boundary conditions

The Tide algorithm

Each iteration ("tide") performs three global sweeps on the residual graph:

  1. globalRelabel — BFS from sink (and source) to recompute vertex heights
  2. globalPull — right fold over active vertices: pull flow on reverse edges
  3. 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 casePractical
TidesO(V^2)O(V)
Per-tideO((V+E) log V)O((V+E) log V)
TotalO(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

AlgorithmBest known complexity
Edmonds-Karp (FGL)O(VE^2)
DinicO(V^2 E)
Highest-label push-relabelO(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

LibraryMax flowShortest pathsMetricsGenerators
containers--------
fglEdmonds-Karp O(VE^2)Dijkstra, BF----
algebraic-graphs--------
algraphTide O(V(V+E) log V)Floyd-Warshall APSPeccentricity, radius, diameter, densityd-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.

BFS performance vs fgl

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:

GraphVEalgraphfglfgl/algraph
Grid 100x10010K20K51 ms53 ms1.0x
Grid 200x20040K80K258 ms337 ms1.3x
Grid 500x500250K500K1675 ms2848 ms1.7x
Grid 1000x10001M2M6956 ms24316 ms3.5x
Layered 20x501K48K60 ms296 ms5.0x
Layered 50x1005K490K695 ms1487 ms2.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.

Metadata

Version

0.7.0.0

Platforms (80)

    Darwin
    FreeBSD
    Genode
    GHCJS
    Linux
    MMIXware
    NetBSD
    none
    OpenBSD
    Redox
    Solaris
    uefi
    WASI
    Windows
Show all
  • aarch64-darwin
  • aarch64-freebsd
  • aarch64-genode
  • aarch64-linux
  • aarch64-netbsd
  • aarch64-none
  • aarch64-uefi
  • aarch64-windows
  • aarch64_be-none
  • arc-linux
  • arm-none
  • armv5tel-linux
  • armv6l-linux
  • armv6l-netbsd
  • armv6l-none
  • armv7a-linux
  • armv7a-netbsd
  • armv7l-linux
  • armv7l-netbsd
  • avr-none
  • i686-cygwin
  • i686-freebsd
  • i686-genode
  • i686-linux
  • i686-netbsd
  • i686-none
  • i686-openbsd
  • i686-windows
  • javascript-ghcjs
  • loongarch64-linux
  • m68k-linux
  • m68k-netbsd
  • m68k-none
  • microblaze-linux
  • microblaze-none
  • microblazeel-linux
  • microblazeel-none
  • mips-linux
  • mips-none
  • mips64-linux
  • mips64-none
  • mips64el-linux
  • mipsel-linux
  • mipsel-netbsd
  • mmix-mmixware
  • msp430-none
  • or1k-none
  • powerpc-linux
  • powerpc-netbsd
  • powerpc-none
  • powerpc64-linux
  • powerpc64le-linux
  • powerpcle-none
  • riscv32-linux
  • riscv32-netbsd
  • riscv32-none
  • riscv64-linux
  • riscv64-netbsd
  • riscv64-none
  • rx-none
  • s390-linux
  • s390-none
  • s390x-linux
  • s390x-none
  • sh4-linux
  • vc4-none
  • wasm32-wasi
  • wasm64-wasi
  • x86_64-cygwin
  • x86_64-darwin
  • x86_64-freebsd
  • x86_64-genode
  • x86_64-linux
  • x86_64-netbsd
  • x86_64-none
  • x86_64-openbsd
  • x86_64-redox
  • x86_64-solaris
  • x86_64-uefi
  • x86_64-windows