The shelf
One page per paper implemented in routelab. Each says what the paper observed, sketches its algorithm, then runs it — set up an environment, bind the technique, ask a question — with a section per variant or refinement the paper adds.
Start with Dijkstra for the shape; every other page assumes it.
Road networks
| Paper | Technique | Page |
|---|---|---|
| Dijkstra, A note on two problems in connexion with graphs (1959) | Dijkstra() |
Dijkstra's algorithm |
| Moore, The shortest path through a maze (1959) | BFS() |
Breadth-first search |
| Hart, Nilsson & Raphael, A formal basis for the heuristic determination of minimum cost paths (1968) | AStar(Euclidean()), AStar(Zero()) |
A* |
| Goldberg & Harrelson, Computing the shortest path: A* search meets graph theory (2005) | AStar(Landmarks(16)) |
ALT landmarks |
| Geisberger, Sanders, Schultes & Delling, Contraction hierarchies (2008) | ContractionHierarchy(EdgeDifference()) |
Contraction hierarchies |
| Dreyfus, An appraisal of some shortest-path algorithms (1969) | TimeDependentDijkstra() |
Time-dependent Dijkstra |
Timetables
| Paper | Technique | Page |
|---|---|---|
| Pyrga, Schulz, Wagner & Zaroliagis, Efficient models for timetable information in public transportation systems (2007) | TimeExpanded(), TimeDependent() |
Two models of a timetable |
| Delling, Pajor & Werneck, Round-based public transit routing (2012) | RAPTOR() |
RAPTOR |
| Dibbelt, Pajor, Strasser & Wagner, Intriguingly simple and fast transit routing (2013) | CSA() |
Connection scan |
| Witt, Trip-based public transit routing (2015) | TripBased() |
Trip-based routing |
| Delling, Dibbelt, Pajor & Werneck, Public transit labeling (2015) | PTL() |
Public transit labeling |
Multimodal
| Paper | Technique | Page |
|---|---|---|
| Baum, Buchhold, Sauer, Wagner & Zündorf, UnLimited TRAnsfers for multi-modal route planning (2019) | ULTRA(RAPTOR()), ULTRA(CSA()) |
ULTRA |
| Barrett, Jacob & Marathe, Formal-language-constrained path problems (2000) | LabelConstrained(), Modes(...) |
Label-constrained routing |
| Dibbelt, Pajor & Wagner, User-constrained multi-modal route planning (2012) §3 — UCCH | UCCH() |
UCCH |
The shelf's to do list — Delling, Pajor & Wagner (2009), Geisberger (2010), transfer patterns (2010) — appears in the README; those papers have no page because they have no implementation.
Side by side
- What preprocessing buys — every technique in each class as one point: what it paid at bind time against what a query cost, measured on the same instance and checked to agree before it was timed.
The shape every page shares
Three steps, the same for a road network and a timetable.
>>> import routelab as rl
>>> env = rl.Environment() # describe a world
>>> env.register(rl.ScalarEdges(("a", "b", 1), ("b", "c", 15)))
Environment(1 layer)
>>> technique = rl.Dijkstra() # a configuration, costing nothing
>>> planner = technique.bind(env) # preprocessing, if any
>>> planner.route("a", "c").routes[0] # the question
Journey('a' → 'b' → 'c', cost=16)
An environment is layers. A GTFS feed, an OSM extract, a table of walks, a
hand-written list of edges — each is a layer, and nodes take whatever names
you already use: stop ids, OSM node ids, ("bike", 42). Every leg of a journey
remembers its layer, which makes a multimodal answer readable rather than just
a number.
Configuring and binding are separate because the middle step gets expensive. Sixteen landmarks over a city cost a second and 33 MB; that belongs to a verb, not a constructor. The split also makes a technique a value — something you can name, put in a dictionary, and point at more than one dataset.
A query answers with everything it worked out. route returns an Answer:
routes (every journey worth having, best first — usually one), searchspace()
(what the search looked at) and raw (the kernel's own table). All three come
from the one search, so a route and the picture behind it never cost two.
A technique takes the options its problem needs, on the signature of its
own bound planner. DijkstraPlanner.route takes max_cost;
RAPTORPlanner.route takes departing and max_transfers; asking either for
the other's is a TypeError from Python, and an error a type checker reports
before anything runs. There is no registry of option names, so nothing can go
stale as the shelf grows.
Reading the code blocks
Two kinds appear on these pages; the prompt tells them apart.
A block with >>> prompts is a test. tests/test_docs.py runs every one
as part of the ordinary suite, so a page describing an API that has since moved
fails the suite rather than misleading you. They run against fixtures the
repository ships: a handful of hand-written edges, or the three-stop GTFS feed
under crates/routelab-gtfs/tests/data/tiny, which the timetable pages name
TINY_GTFS.
A block without prompts is written to be read. Those run on a real city —
Seattle's 258,029 nodes, King County Metro's 421,604 connections — where the
numbers are the point and no test suite should download a 65 MB extract to
check them. Their outputs are pasted as comments from the benchmarks under
benchmarks/.
To run them — or any snippet on these pages — install routelab first. It builds a Rust kernel, so you need a Rust toolchain alongside Python 3.9+:
git clone https://github.com/bmander/routelab && cd routelab
python -m venv .venv && source .venv/bin/activate
pip install -e '.[dev]' # builds the Rust kernel via maturin
pytest tests/test_docs.py # run every prompted block on this shelf
Where else to look
- How it is built — what routelab is for, the layout of the code, and the decisions the whole shelf rests on. Its contract says what every kernel here is checked against, and why an independent oracle rather than a golden file.
- Seeing it run — the node board: layers, a technique, a query, wired up on a page, with the search drawn under a map.
- README — the one-page version: install it, and the shelf as a table.
benchmarks/— the source of the real-network numbers on these pages.