algraph: Graph library using adjacency list representation

This is a package candidate release! Here you can preview how this package release will appear once published to the main package index (which can be accomplished via the 'maintain' link below). Please note that once a package has been published to the main package index it cannot be undone! Please consult the package uploading documentation for more information.

[maintain] [Publish]

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


[Skip to Readme]

Properties

Versions 0.7.0.0, 0.7.0.0
Change log ChangeLog.md
Dependencies base (>=4.8 && <4.21), binary (>=0.8.6.0 && <0.9), containers (>=0.5.10.2 && <0.8), either-unwrap (>=1.1 && <1.2), mtl (>=2.2.1 && <2.4), text (>=1.2.2.2 && <2.2), Unique (>=0.4.7.9 && <0.5) [details]
License LGPL-3.0-only
Copyright Thodoris Papakonstantinou, 2017-2026
Author Thodoris Papakonstantinou
Maintainer dev@tpapak.com
Category Graphs, Algorithms
Home page https://github.com/tpapak/algraph#readme
Bug tracker https://github.com/tpapak/algraph/issues
Source repo head: git clone https://github.com/tpapak/algraph
Uploaded by tpapak at 2026-03-09T18:32:51Z

Modules

[Index] [Quick Jump]

Downloads

Maintainer's Corner

Package maintainers

For package maintainers and hackage trustees


Readme for algraph-0.7.0.0

[back to package description]

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

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 algorithmpushRelabel :: 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:

  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 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:

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:

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:

License

LGPL-3 -- see LICENSE.