cch
Customizable Contraction Hierarchies for fast road routing — the whole pipeline in pure, safe Rust.
cch builds a metric-independent contraction hierarchy from a road graph, customizes it per weight profile (distance, travel-time, …), serializes it to memory-mappable bundles, and answers point-to-point distance, many-to-many distance-matrix, and shortest-path queries — with no C++ and no FFI in the published library.
It is a from-scratch Rust reimplementation of RoutingKit's CCH, validated bit-for-bit against it: same contraction order quality, bit-identical structure and customization arrays, byte-identical on-disk bundles, and identical query results. It was extracted from — and now powers — a production distance-matrix routing service.
[]
= "0.1"
Quick start
use Graph;
use ;
// Input graph in CSR form: a 4-node path 0—1—2—3 with unit-weight arcs.
// (CCH treats the input as symmetric / undirected internally.)
let graph = Graph ;
// 1. Contraction order (metric-independent). `degree_order` needs no
// coordinates; use `inertial_order` for production-quality orders.
let order = degree_order;
// 2. Build the CCH structure once.
let cch = build;
// 3. Customize a metric (cheap — repeat per weight profile).
let metric = cch.customize;
// 4. Query in memory, via zero-copy views.
let dm = distance_matrix;
assert_eq!; // shortest distance 0 -> 3
let path = node_path;
assert_eq!; // unpacked node path
Why CCH?
A Customizable Contraction Hierarchy is a two-phase shortest-path index built for road networks where the topology is fixed but edge weights change often (live traffic, vehicle profiles, time-of-day):
- Build (once, metric-independent) — pick a contraction order, contract the graph into a chordal supergraph, and record the shortcut structure.
- Customize (cheap, per metric) — push concrete edge weights through the structure to compute every shortcut's weight.
- Query (very fast) — answer point-to-point, one-to-many, and many-to-many shortest paths over the customized hierarchy; unpack shortcuts to recover the full node path.
The expensive build is amortized across many cheap customizations, which is exactly what a routing service with frequently-changing weights needs.
Highlights
- Pure Rust, no FFI. The published library has zero C++ in its dependency tree — embed it in a Rust service with no C/C++ toolchain. (A C++ RoutingKit build is used only as a dev-time differential-test oracle; it is not part of the crate you depend on.)
- The complete pipeline — contraction order, structure build, per-metric customization, bundle reader and writer, distance / distance-matrix / path queries, and shortcut unpacking.
- Two ordering strategies — a lightweight
degree_order, andinertial_order: a full geometric nested-dissection (inertial-flow max-flow / min-cut) that produces hierarchies of the same quality as RoutingKit (identical shortcut counts in testing). - Zero-copy mmap bundles. Build once, write
.cch-struct/.cch-metricfiles, then serve them memory-mapped — the OS page cache backs the query slices directly, so many regions can be served within a bounded memory budget. The format is byte-compatible with RoutingKit-produced bundles. - Proven correct. Every stage is gated by a differential test against the C++ oracle (see Correctness).
- 100% line coverage, enforced in CI.
API at a glance
| Step | API |
|---|---|
| Input graph (CSR) | cch::graph::Graph |
| Contraction order | degree_order, inertial_order |
| Build structure | Cch::build |
| Customize a metric | Cch::customize → Metric |
| Serialize / load | Cch::save_struct, Cch::load_struct, Metric::save |
| Open bundles (mmap) | CchBundle, MetricBundle |
| Query | distance_matrix, node_path, ElimTreeQuery |
Unreachable distances are reported as cch::INF_WEIGHT (2_147_483_647). Queries take borrowed CchView / MetricViews, so you can query a freshly-built Cch/Metric in memory (.view()) or a memory-mapped bundle (.view() on CchBundle/MetricBundle) through the same functions.
Serving from bundles
Production serving builds once and memory-maps the result:
use ;
let cch = open?;
let metric = open?;
// Zero-copy: the slices borrow straight from the mmap'd pages.
let matrix = distance_matrix;
# Ok::
Bundles are immutable, shareable across processes via the page cache, and let a single host serve many regions without loading them all into heap.
Performance
Indicative numbers, Rust vs the C++ RoutingKit oracle, on a 24×24 bidirectional grid (576 nodes, ~3 000 CCH arcs), measured with Criterion. Both sides use the same contraction order so the comparison is apples-to-apples. Run cargo bench to reproduce on your hardware.
| Operation | Rust | C++ | Rust / C++ |
|---|---|---|---|
| contraction order | 5.22 µs | 7.10 µs | 0.74× (faster) |
Cch::build |
439 µs | 444 µs | ~1.00× (parity) |
customize |
1.00 ms | 1.00 ms | ~1.00× (parity) |
distance_matrix (576×576) |
12.9 ms | 12.5 ms | ~1.00× (parity) |
node_path (×200) |
3.11 ms | 3.06 ms | ~1.00× (parity) |
Every operation is at parity with (or faster than) RoutingKit. The query paths reach parity by eliding the per-arc bounds checks in the hot relaxation loops — the elision-able accesses via sliced iterators, and the data-dependent distance-array access via a get_unchecked guarded by a one-time structural validation. (Numbers are indicative; hardware is not standardized — run cargo bench for your own.)
Correctness
The reference C++ RoutingKit is vendored as a dev-only differential-test oracle, and each stage of the pipeline is gated against it:
- Contraction order —
inertial_orderproduces the same shortcut count as RoutingKit's inertial-flow order (e.g. identical on a 24×24 grid), and yields correct shortest paths. - Structure —
Cch::buildis bit-identical to RoutingKit's structure arrays given the same graph + order. - Customization —
customizeis bit-identical to RoutingKit's forward/backward shortcut weights. - Bundles —
save_struct/Metric::saveproduce byte-identical files to RoutingKit's writer, and RoutingKit can load bundles written bycch(and vice-versa). - Queries — distance-matrix and a 200-pair shortest-path suite match RoutingKit exactly, end-to-end.
The whole crate maintains 100% line coverage, enforced by a CI gate (cargo llvm-cov --fail-under-lines 100), alongside clippy -D warnings and rustfmt.
Status & roadmap
0.1 ships the full pipeline — both orderings, build, customize, bundle read/write, and all query types — validated against RoutingKit. The API may still evolve before 1.0.
Planned: narrowing the query-path performance gap, parallel customization, and broader benchmark coverage on real continental graphs.
Relationship to RoutingKit
RoutingKit (BSD-2-Clause) is the canonical C++ CCH implementation and the basis for this work. cch reimplements its CCH construction, customization, bundle format, and query algorithms in Rust, and is differential-tested against it for exact equivalence. The on-disk bundle format is interoperable with RoutingKit-produced artifacts.
License
MIT. The algorithm and bundle format derive from RoutingKit (BSD-2-Clause); see NOTICE.