the four-customer exampledepot 0 at $(0, 0)$, window $[0, 400]$ customer 1 at $(0, 30)$, window $[0, 50]$ customer 2 at $(40, 30)$, window $[60, 100]$ customer 3 at $(40, 0)$, window $[100, 200]$ customer 4 at $(80, 0)$, window $[0, 300]$ service 10 and demand 1 each, capacity 5; optimum 200, on one route $0 \to 1 \to 2 \to 4 \to 3 \to 0$customer 1at $(0, 30)$, window $[0, 50]$, service 10 first on both routes: $d_{01} = 30$, so it starts at 30customer 2at $(40, 30)$, window $[60, 100]$, service 10 $d_{12} = 40$, $d_{23} = 30$, $d_{24} = 50$; starts at 80 on both routescustomer 3at $(40, 0)$, window $[100, 200]$, service 10 $d_{23} = 30$, $d_{34} = 40$, $d_{30} = 40$; starts at 120 on $0 \to 1 \to 2 \to 3 \to 0$, at 190 on the optimumcustomer 4at $(80, 0)$, window $[0, 300]$, service 10 $d_{24} = 50$, $d_{34} = 40$, $d_{14} = 85$, $d_{04} = 80$; the customer left out of $0 \to 1 \to 2 \to 3 \to 0$route 0 → 1 → 2 → 3 → 0$0 \to 1 \to 2 \to 3 \to 0$, distance 140 starts 30, 80, 120, back at the depot at 170; customer 4 not yet servedthe optimum$0 \to 1 \to 2 \to 4 \to 3 \to 0$, distance 200 all four customers on one route; starts 30, 80, 140, 190, back at 240
Capstone · everything converges
∗
One problem, four traditions
Vehicle routing with time windows, solved by branch-and-cut, branch-and-price, CP-SAT and ALNS, then refereed on Solomon's instances with unit 28's method.
needs 07, 08, 10, 18–19, 26, 28~1¼ h slides + 12–15 h lablab: uv run co lab capstone
capstone · the problem
VRPTW
vehicle routing with time windows
VRPTW: a depot 0 and customers $1..n$. Customer $i$ has demand $q_i$, a window $[a_i, b_i]$ in which service must start, and a service time $s_i$. Identical vehicles of capacity $Q$ leave the depot no earlier than $a_0$ and return by $b_0$. Travel time equals distance $d_{ij}$. Arriving early means waiting.
Find routes that serve every customer once, within capacity and windows, minimising total distance. The fleet is unlimited.
Along a route, service starts satisfy
$$t_j = \max\bigl(a_j,\; t_i + s_i + d_{ij}\bigr) \le b_j.$$
Waiting is free, so this start-time recurrence is the entire time-window model.
Solomon (1987) series
customers
windows
C1, C2
clustered
series 1: short horizon, tight windows, short routes series 2: long horizon, wide windows, long routes
R1, R2
uniform random
RC1, RC2
mixed
56 instances of 100 customers, and the literature's 25- and 50-customer versions: the first $n$ customers of each file. uv run co data downloads them from SINTEF's mirror.
getting around
Words with a dotted underline are glossary terms: click one. G lists them all, N shows the notes. Optional extra: interactive figures, linked from the slides they support by a small ▸ chip; skip them freely.
capstone · the problem · example
The recurrence, on four customers
vertex
position
window
service
depot 0
(0, 0)
[0, 400]
0
1
(0, 30)
[0, 50]
10
2
(40, 30)
[60, 100]
10
3
(40, 0)
[100, 200]
10
4
(80, 0)
[0, 300]
10
Demand 1 each, capacity 5, so only time matters. Distances are Euclidean: $d_{01} = 30$, $d_{12} = 40$, $d_{23} = 30$, $d_{30} = 40$, $d_{24} = 50$, $d_{34} = 40$.
route 0 → 1 → 2 → 3 → 0
at
$t = \max(a,\ \text{previous} + s + d)$
window ok?
1
$\max(0,\ 0 + 0 + 30) = 30$
$30 \le 50$ ✓
2
$\max(60,\ 30 + 10 + 40) = 80$
$80 \le 100$ ✓
3
$\max(100,\ 80 + 10 + 30) = 120$
$120 \le 200$ ✓
depot
$120 + 10 + 40 = 170$
$170 \le 400$ ✓
Distance $30 + 40 + 30 + 40 = 140$. Customer 4 still needs a place.
The optimum serves all four on one route, $0 \to 1 \to 2 \to 4 \to 3 \to 0$, distance 200: starts 30, 80, 140, 190, back at 240.
Minimise vehicles first, then distance (a hierarchical objective), with double-precision Euclidean distances. SINTEF's best-known table uses this: R101 at 100 customers is 19 vehicles, 1650.80.
the exact literature, and this lab
Minimise distance only (the distance-only objective), with $d_{ij} = \lfloor 10 \cdot \mathrm{euclid}(i, j) \rfloor / 10$, truncated to one decimal (Kallehauge et al. 2005; Baldacci, Mingozzi & Roberti 2011). Everything is stored as integers in tenths.
Integers keep CP-SAT exact, make "equal cost" mean equal, and let a bound be rounded up: a node whose LP is 617.03 can't hold a solution under 617.1.
Truncation can break the triangle inequality by up to 0.1. So set partitioning uses $= 1$, not $\ge 1$, where dropping a customer from a route could lengthen it.
The two objectives' results aren't comparable. A distance-only optimum may use more vehicles and less distance than the hierarchical best-known.
Check on convention: all four methods give R101.25 = 617.1, C101.25 = 191.3, RC101.25 = 461.1 and R201.25 = 463.3. As far as can be checked from memory, these match the optima reported in the exact literature.
capstone · overview
The four traditions, and the referee
(a) polyhedral · step 2
Arc variables on SCIP, big-M time propagation, and your rounded capacity cuts through a constraint handler. Units 05, 08, 09, 27.
(b) decomposition · step 3
Set partitioning over routes, priced by an elementary shortest path with capacity and time windows, branching on arcs. Units 07, 10.
(c) constraint programming · step 4
CP-SAT's multiple-circuit constraint with enforced time and load, plus a hint and a chosen strategy. Units 17–21.
(d) local search · step 5
ALNS with constant-time feasible insertion, Shaw removal and regret repair. Units 19, 26.
the referee · steps 1 and 6
A checker that trusts no method, primal gaps against the best any method found, paired tests with Holm's correction, and the verdict "indistinguishable" when the data say so. Units 00, 28.
section 1
Four models of one problem
What each tradition writes down, and what it leaves to the solver.
capstone · (a) branch-and-cut
A compact model whose capacity lives in your cuts
The arc MIP: $x_{ij} \in \{0,1\}$ for arcs that some route could use, and start times $t_i \in [a_i, b_i]$:
with $M_{ij} = \max(0,\ b_i + s_i + d_{ij} - a_j)$, the smallest $M$ that's valid.
The time constraints already rule out subtours: around a cycle, each start would exceed the previous one by $s_i + d_{ij} > 0$. They don't limit load.
rounded capacity inequalities
For every customer set $S$: $\;x(\delta^-(S)) \ge \lceil q(S) / Q \rceil$.
Separation by connected components of the support graph. On an integer point the components are the routes, so an overloaded route always yields a violated cut. That makes the handler exact as a lazy constraint. On a fractional point it's a heuristic: exact separation is NP-hard (CVRPSEP is the standard code).
colib.vrptw.lazy_cut_handler supplies SCIP's plumbing; you supply separate(values).
Set partitioning over routes: $\Omega$ is every feasible route, too many to write down. Unit 10's loop: solve over a few, read duals $\pi$, and find a route with $c_r - \sum_{i \in r} \pi_i < 0$.
a label at a vertex
(reduced cost, load, start time, unreachable set, route). Extend along allowed arcs. $L$ dominates $L'$ when it's no worse in cost, load and time, and its unreachable set is a subset of $L'$'s.
Feillet et al. (2004): count customers you can't reach any more as visited. Dominance then fires between labels whose routes differ.
Branch on arc flows $\sum_r [ij \in r] \lambda_r$. Forbidding an arc keeps pricing the same problem on a smaller graph; forcing $ij$ forbids its alternatives.
Integral arc flows mean integral $\lambda$: each customer has one arc in and one out with flow 1, so every column in the support follows them.
Bound $\lceil LP \rceil$ because distances are integers. A restricted master IP at the root gives the first incumbent.
capstone · (c) CP-SAT
A circuit constraint, enforced implications, and a strategy
m.add_multiple_circuit([(i, j, x[i, j]) for i, j in arcs])
for (i, j), b in x.items():
if i and j:
m.add(t[j] >= t[i] + s[i] + d[i][j]).only_enforce_if(b)
m.add(u[j] >= u[i] + q[j]).only_enforce_if(b)
m.minimize(sum(d[i][j] * b for (i, j), b in x.items()))
The multiple-circuit constraint: vertex 0 may carry many circuits; every other vertex is on exactly one. That replaces the degree constraints and subtour elimination at once. No big-M: the enforced implication is propagated directly, and lazy clause generation (unit 21) learns from its failures.
search choices you make
A hint: a complete solution (from ALNS) that CP-SAT tries first.
A decision strategy: branch on short arcs first, value 1. With one worker it's only followed under FIXED_SEARCH.
Workers: CP-SAT is a portfolio. One worker is a fair comparison with the single-threaded methods; eight is how it's meant to be run.
The brief asks for a strategy chosen deliberately. Deliberate includes measuring whether it helps and dropping it if it doesn't.
capstone · (d) ALNS
Feasible insertion in constant time
Keep, per route, the forward starts $t_k$ and the latest starts $\ell_k$:
Inserting $c$ between $p$ and $q$ is feasible iff
$$\begin{aligned}
t_c &= \max(a_c,\ t_p + s_p + d_{pc}) \le b_c, \\
&\max(a_q,\ t_c + s_c + d_{cq}) \le \ell_q,
\end{aligned}$$
plus the load. Waiting absorbs any push up to $\ell_q$, so nothing after $q$ needs rechecking: a constant-time feasible insertion.
Shaw removal: take out customers related in space, time and demand, so repair has room to rearrange them.
Regret-k repair: insert first the customer with most to lose, $\sum_{h=2}^{k} (\text{cost}_h - \text{cost}_1)$, over its best position per route.
A new route is always an option. Without it, repair can never increase the number of routes, and minimising distance sometimes needs more of them. The first prototype lacked it and got stuck 13 % above optimal on R201.25.
Annealing acceptance, and three destroy × three repair operators with adaptive weights (unit 26).
capstone · (d) ALNS · example
Constant-time insertion, with numbers
Route $0 \to 1 \to 2 \to 3 \to 0$ from the four-customer example. Insert customer 4 (window $[0, 300]$).
latest starts, computed backwards
position
$\ell = \min(b,\ \ell_{\text{next}} - s - d)$
start $t$
3
$\min(200,\ 400 - 10 - 40) = 200$
120
2
$\min(100,\ 200 - 10 - 30) = 100$
80
1
$\min(50,\ 100 - 10 - 40) = 50$
30
Customer 2 has only 20 of slack for anything inserted before it: it starts at 80, and its latest start is 100, its window's end.
every position for customer 4
between
4 starts at
next start vs its latest
extra distance
0 and 1
80
at 1: $175 > 50$ ✗
—
1 and 2
125
at 2: $185 > 100$ ✗
—
2 and 3
140
at 3: $190 \le 200$ ✓
60
3 and depot
170
back at $260 \le 400$ ✓
80
Two numbers per position, no rescheduling of the rest. The cheapest feasible insertion, 60, gives the optimal route of distance $140 + 60 = 200$.
What the harness holds fixed, and what it measures
held fixed
how
budget
60 s wall clock per run; the method gets the limit, the harness 5 s of grace
threads
one each: SCIP, HiGHS, CP-SAT with num_workers=1, Python
instances
all 56 Solomon instances at 25, 50 and 100 customers
trust
every solution re-checked, every cost recomputed
runaways
killed 30 s past the limit or beyond 2.5 GB, and recorded
The referee also runs a violation checker on every answer. Six runs side by side on eight cores, so absolute seconds include some contention. Every method pays it.
primal gap (Berthold 2013)
$\gamma(v, v^*) = |v - v^*| / \max(|v|, |v^*|)$, 1 with no solution. $v^*$ is the best value any method found.
On the example ($v^* = 200$): inserting 4 at the end of $0 \to 1 \to 2 \to 3 \to 0$ gives $v = 220$, $\gamma = 20/220 = 0.091$. Serving 4 on its own route gives $140 + 160 = 300$, $\gamma = 100/300 = 0.333$. No solution: 1.
two questions, two metrics
Who finds good solutions? Primal gap at the limit, all four methods. Who proves optimality fastest?PAR1 seconds, the three exact methods. ALNS never proves anything, so it has no place in the second.
Paired over instances, Wilcoxon signed-rank, Holm over all pairs at once.
section 3
What the referee found
672 runs, and no claim refuted.
capstone · measured
Proofs and gaps, by size
Proven optimal of 56 (mean primal gap at 60 s against the best found):
n
(a) B&C
(b) B&P
(c) CP-SAT
(d) ALNS
any
25
33 (1.6%)
49 (9.2%)
36 (0.08%)
– (0.43%)
51
50
16 (11.2%)
16 (53.9%)
14 (6.2%)
– (0.20%)
21
100
11 (28.9%)
0 (96.5%)
6 (18.9%)
– (0.15%)
11
Integrity: no invalid solution, no crash, no "optimal" refuted (no refuted claim), no two different proven optima.
B&P is all or nothing. Its gaps are runs with no solution: 3, 26 and 49 of 56. Ten runs overran the limit inside one uninterruptible pricing call.
ALNS's gaps flatter it, because the "best found" is often its own value. Against proven optima: 47 of 51 at n = 25, 20 of 21 at 50, 9 of 11 at 100. The worst miss is 7.7%, on C205.100.
Proven at n = 100: C101 827.3, C201 589.1, R101 1637.7. As far as can be checked from memory, these match the exact literature.
capstone · measured
Which characteristics favour which method
series
window / horizon
n = 25
n = 50
n = 100
C1
0.25
B&C 8, B&P 1
B&C 6, ALNS 2, B&P 1
ALNS 5, B&C 4
C2
0.27
B&C 4, CP-SAT 4
B&C 5, ALNS 2, CP-SAT 1
B&C 6, ALNS 2
R1
0.38
B&P 11, B&C 1
B&P 5, ALNS 5, B&C 1, CP-SAT 1
ALNS 11, B&C 1
R2
0.45
B&P 5, CP-SAT 4, B&C 1, ALNS 1
ALNS 9, B&C 1, CP-SAT 1
ALNS 10, CP-SAT 1
RC1
0.36
B&P 5, B&C 2, CP-SAT 1
ALNS 7, CP-SAT 1
ALNS 8
RC2
0.38
B&P 4, CP-SAT 3, B&C 1
ALNS 6, B&C 1, CP-SAT 1
ALNS 8
Per instance: smallest primal gap, then fastest proof (the head-to-head verdict).
supported by the paired tests
Tight windows, random, 25 customers → B&P: proves all 20 R1/RC1, fastest proofs overall (Holm p ≤ 0.02).
Clustered → the compact MIP makes most proofs. At n = 100, SCIP made all 11: ten clustered, plus R101.
Past 25 customers → ALNS for solution quality on every series; every pair decisive (Holm p ≤ 6·10⁻⁶).
indistinguishable
n = 25, solution quality: CP-SAT vs ALNS, and B&P vs each of the other three.
n = 50, time to proof: B&P vs B&C, and B&P vs CP-SAT.
capstone · honest
What this run can't tell you
limitation
why it matters
the extra experiment
one seed per run
SCIP and CP-SAT times move a lot with the seed (unit 27)
five seeds, averaged per instance
six jobs on eight cores
C104.25: B&P proved it in 39.5 s in a lighter prototype run, and found nothing in 60 s here
one job at a time
Python against C++
B&P's and ALNS's results are partly implementation findings
state it; or ng-routes and bidirectional labelling
one thread for CP-SAT
a portfolio solver, run against its design
8 workers, reported beside the 1-worker result
gap at one time limit
ignores when each method got there
log incumbents; primal integral
the report's rule
Every sentence of NOTES.md ties a method to an instance characteristic, points at a table, and names the test behind it. Where the test says indistinguishable, the sentence says so.
capstone · checkpoint
Done when
checkpoint
All six lab steps are ✓, and then reports no invalid solution and no refuted claim.
NOTES.md says, for each series and size, which method wins on quality and on proof time, with the paired test behind each claim and "indistinguishable" where it applies.
It states its limitations, and the experiment that would remove each.
Reading
Solomon, Oper. Res. 35 (1987).
Kallehauge et al., in Column Generation (2005).
Feillet et al., Networks 44 (2004).
Baldacci, Mingozzi & Roberti, Oper. Res. 59 (2011).
Savelsbergh, ORSA J. Comput. 4 (1992).
Ropke & Pisinger, Transp. Sci. 40 (2006).
Berthold, Oper. Res. Lett. 41 (2013).
After the course
VRPSolver (Pessoa et al. 2020) for exact routing, HGS (Vidal 2022) for heuristics, and Gehring–Homberger's 200–1000 customers to see where each one stops.