Pick one of 195 real Albuquerque schools. We'll simulate who rides, cluster them into corner stops, and route the buses. The exact answer does not exist — not for this school, not on any machine ever built. Here is what districts do instead, stage by stage, with the cost after each one.
Where a child lives is protected information. It isn't public, it isn't ours, and if it were handed to us it still would not belong on a web page. So every home, every stop and every rider count here is generated by a seeded random model — plausible in shape, invented in fact. The schools are real and cited; everything about the students is not. The map says so on its face, permanently, because a simulation that doesn't announce itself is just a lie with good graphics.
And we did not synthesise demographics. We could have painted this map with invented income or race by neighbourhood, and it would have looked authoritative. Inventing that for real, named Albuquerque neighbourhoods — in front of a teacher who lives in one — would be indefensible. So the only rider attribute we model is the one that actually drives routing: how many riders are at a stop, and whether a student is inside the walk-shed. If you want a demographic frame for a student project, pull real public aggregates (Census/ACS block groups, NCES district data) and cite them. Don't make them up. That rule is the lesson, not the fine print.
The service areas are approximate. They're a nearest-school partition — a Voronoi diagram — which is a decent first guess and NOT APS's real attendance boundaries. The real ones exist, they're public, and they differ: they follow arterials, ditches and the river, they're redrawn by a board, and they carry history a bisector knows nothing about. Charters and private schools get no area at all here, because they genuinely don't have attendance boundaries — that's not a gap in our data, it's a fact about how they enroll.
Get the names right, because your students will meet them in the wild and the names are load-bearing.
| Name | Shorthand | What it adds |
|---|---|---|
| Travelling Salesman Problem | TSP | One vehicle. Visit every stop exactly once, come back. Shortest tour wins. |
| Multiple Travelling Salesman | mTSP | Several vehicles share the stops, all from one depot. Now you must also decide who takes what — and that choice is most of the difficulty. |
| Capacitated Vehicle Routing | CVRP | mTSP + each stop has a demand (riders) and each bus has a capacity (72). This is our problem. This is the district's problem. |
| VRP with Time Windows | VRPTW | + each stop and the school have time windows (the bell). This is what real routing software solves. We do not. See the honesty markers. |
The word "capacitated" is doing real work. Pick a school on tab 1 and this sentence will do the arithmetic for it.
Capacity is the constraint that turns a puzzle into a job — and it is the one thing on this page that can make the answer not exist.
One bus, n stops. Fix the depot, walk the stops in some
order, come home. That's (n−1)! orders — and each tour is identical to its own reverse, so
halve it: (n−1)! / 2. Nothing below is a lookup. Every digit is computed
in your browser with BigInt, right now.
With m buses you must also choose which stops go to which bus before you order any of them, and the number of ways to do that is itself enormous. We're not writing that formula down — the one-bus number already ended the argument, and a formula you can't check is worse than no formula. Also: big ≠ impossible. Exact CVRP solvers (branch-and-cut-and-price) do prove optimality on hundreds of stops. They do it by never enumerating — they prove whole regions can't contain the answer. That's a real branch, and nobody walked it on this page.
This table is live output from the school you picked on tab 1. Click any row to see that stage's routes on the map. Watch the crossings column: it's the one that tells the truth.
| Stage | Total km | Δ | Crossings in a route | Crossings between routes |
Longest ride | Buses | Max load |
|---|---|---|---|---|---|---|---|
| solving… | |||||||
Greedy takes the closest unvisited stop, every time, with no idea it's stranding a stop behind it. It gets away with it for a while and then pays all at once: the last legs are long dashes back across ground it already covered. You can see that failure — the route crosses itself. Count in the table above right now: nearest-neighbour left – self-crossings across all buses.
A crossing is never optimal. If edges A→B and C→D cross, then swapping them for A→C and B→D is always shorter — the triangle inequality says so, with no assumptions about the map at all. So a crossing isn't just ugly. It's a receipt: proof, on sight, that a shorter tour exists. That's the whole idea of 2-opt, and here it is running.
A nearest-neighbour tour over 11 seeded points. Every press finds one improving swap: it drops two edges, reverses the segment between them, and reconnects. The reversal is the trick — it's the only way to swap two edges of a tour and still have a tour.
Watch for the end: it stops with zero crossings, every time. That's not luck — it's the definition of a 2-opt local optimum.
Not pseudocode. This is copied from the script in this file — the same functions that produced the table above. Nothing here is magic, and you can read all of it.
// 1 · CLUSTER — sweep by angle from the depot, respecting capacity. // Fan a ray around the school. Fill a bus as it passes stops. Cut when full. // Target is re-balanced at each bus so the loads come out even. function sweep(stops, dem, K, cap){ const order = stops.slice().sort((a,b) => ang[a] - ang[b]); // ang = atan2 from depot let b = 0, load = 0, left = total, busesLeft = K; let target = Math.min(cap, Math.ceil(left / busesLeft)); for (const s of order){ const d = dem[s]; if (load > 0 && (load + d > cap || (b < K-1 && load + d > target)) && b < K-1){ b++; busesLeft--; left -= load; load = 0; target = Math.min(cap, Math.ceil(left / Math.max(1, busesLeft))); } if (load + d > cap){ unserved.push(s); continue; } // ← the fleet is too small. say so. routes[b].push(s); load += d; } } // 2 · CONSTRUCT — nearest neighbour. Greedy, fast, and it will regret this. function nnTour(stops, D){ let at = DEPOT, left = new Set(stops), out = []; while (left.size){ let best = null, bd = Infinity; for (const s of left) if (D[at][s] < bd){ bd = D[at][s]; best = s; } out.push(best); left.delete(best); at = best; // ← no lookahead. ever. } return out; } // 2b · CONSTRUCT — Clarke-Wright savings (1964). The classic, and worth the name. // Start with every stop on its own out-and-back. Merging i and j saves: // s(i,j) = d(depot,i) + d(depot,j) − d(i,j) // …the two half-trips you no longer drive. Merge biggest saving first, // while capacity allows. Notice what it does NOT need to be told: how many // buses. It discovers that — sometimes more than you own. const S = pairs.map(([i,j]) => [ D[0][i] + D[0][j] - D[i][j], i, j ]) .sort((a,b) => b[0] - a[0]); for (const [saving, i, j] of S){ if (saving <= 0) break; // merging costs more than it saves if (endpoint(i) && endpoint(j) && route(i) !== route(j) && load(i) + load(j) <= cap) merge(i, j); } // 3 · IMPROVE — 2-opt. THE line of this entire page is the delta: function twoOpt(route, D){ const t = [DEPOT, ...route, DEPOT]; let improved = true; while (improved){ improved = false; for (let i = 1; i < t.length-2; i++){ for (let j = i+1; j < t.length-1; j++){ const a = t[i-1], b = t[i], c = t[j], d = t[j+1]; // drop edges a→b and c→d, add a→c and b→d, reverse everything between. const delta = (D[a][c] + D[b][d]) - (D[a][b] + D[c][d]); if (delta < -1e-9){ reverse(t, i, j); improved = true; } } } } return t.slice(1, -1); // ← halts only when NO pair-swap helps. a local optimum. } // 4 · IMPROVE — Or-opt. Lift a run of 1–3 stops out and re-insert it elsewhere, // forwards or backwards. Catches what 2-opt structurally cannot: a single // stop sitting in the wrong cluster of the same route.
Why can't the later stages fix it? Because every move we implemented — 2-opt, Or-opt — rearranges stops within one bus. Nothing in this pipeline ever hands a stop from bus 2 to bus 5. The sweep drew those wedges at the very first stage, from nothing but an angle, and no later stage can question them. An early decision that nobody is allowed to revisit is the most expensive kind of mistake in this whole business.
Fixing it takes a move that crosses between routes — λ-interchange, cross-exchange, ruin-and-recreate. We didn't implement one. We just measured the hole it would fill and left it there, in the open, with a number on it. That is what an honest local optimum looks like.
One row per bus and driver. Blocks are stops — taller means more riders. The red line is the bell, and everyone has to be left of it. Riders whose ride is longer than your threshold turn amber.
Nothing on this page finds the best answer, and nothing on this page can tell you how far off it is. A heuristic gives you a good tour and no bound — no receipt that says "within 4% of optimal". Getting that receipt is a different discipline (a lower bound from an LP relaxation), and we don't do it. Real districts run OR-Tools, Edulog, Transfinder, VersaTrans or BusPlanner: exact-ish solvers with time windows, mixed loads across schools, wheelchair positions, driver hours, contracted runs, and bell-time tiering — the trick where one bus runs a high-school route, then a middle, then an elementary, which is why the bells are staggered in the first place and which cuts the fleet by more than half. We model exactly none of that.
Everything in this section is about the map on tab 1. Put it on the projector — the ⚠ SIMULATED banner stays on the layer where it belongs.
Loading the road network…
Loading…
We start and end every route at the school building. Real buses sleep at a bus barn and deadhead to their first stop — often several miles — and deadhead home again at the end. That's real fuel, real driver-hours and real money that this model simply doesn't count. It's also why real districts optimise the barn location, a whole separate problem (facility location) sitting on top of this one.
Master seed 20260715. Each school's students are generated from a hash of that seed and
the school's own OSM id — so a school's simulated kids are the same on every reload, in every
browser, in any order you click, forever. Run this lesson Monday and again Thursday and the class gets
the same answer. That is a deliberate design choice, and if you want to teach one thing about
simulation on this page, teach that: an unseeded model is not an experiment, it's an anecdote.
Homes are scattered around each school with exponential distance decay (a level-dependent mean), then rejection-sampled against school density — we keep a home with probability rising with how many schools are within 2 miles. That's a deliberate choice, and it's the ethics rule made operational: we needed a population proxy, and we refused to invent demographics for real neighbourhoods, so we used the only real data we have — where the schools are. Schools are where people are. It keeps the fake houses off the volcanoes and out of the empty mesa. It is still a proxy, and it is still fake. Real parcel data exists (Bernalillo County assessor) and would be the honest upgrade.
| Assumption | Value | Where it came from |
|---|---|---|
| Enrollment | – | Seeded per school, in a plausible range for its level. Not real enrollment — real numbers are public from NMPED/NCES, and a student project should use those. |
| Home spread | – | Mean distance from school, exponential decay. Bigger schools draw from further out. |
| Ridership rate | – | Of those outside the walk-shed, the share who actually ride. Older students drive, get rides, or walk further. An assumption, not a measurement. |
| Stop grid | 0.80 km | Riders snap to a shared corner — a walk of ~500 m at worst. Real districts do exactly this: stops are corners, not doors. Fewer stops, shorter routes, a short walk each. Shrink this and you get door-to-door service and a three-hour ride. |
| Dwell time | 25 s + 4 s/rider | Door open, kids board, door closed. A rough but real shape. |
| Assumed speed (no maxspeed) | – | Adjustable on tab 1, and the slider is the point. A real share of the road-kilometres here carry no speed limit in OSM. We do not fill that in quietly; you set it, and every travel time on the page moves. The size of that movement is the size of the hole. |
| Stop access radius | – | Adjustable on tab 1. A stop is a grid-cell centroid, not a real corner, so we let it be served from any road node within this radius — default 400 m, chosen as half the stop grid because that is how far the real corner could be, not for the number it produces. Set it to 0 and the measured asymmetry roughly quadruples, almost entirely as an artefact of our own snapping. §4 on this tab. |
| Access stub cost | straight-line | The dashed bit on the map, from the stop to the road. Charged at its straight-line length — a lower bound, since no real street is shorter than the crow flies. Charging zero (our first build) invented a teleport and produced a road distance shorter than straight-line, which is impossible. The page now checks road ≥ straight on every pair, every solve. |
| 2-opt delta | – | Adjustable on tab 1. Whether 2-opt prices a reversal with the textbook formula (which assumes d(A,B)=d(B,A) and is false on a one-way network) or the exact one. Default: exact — because with the textbook formula the stage table can go backwards. §5 on this tab measures both. |
| Bell time | – | Illustrative placeholder, tiered by level (high 7:30 / middle 8:15 / elementary 9:00). Not APS's actual bells — look those up; tiering is real and it's the whole reason the fleet is affordable. |
Every line of the pipeline is in this one file. It can fetch and show itself — nothing here is hidden, and nothing here is magic.
—
The transferable move. "Route the school buses" is not a question a computer can answer. It became answerable the moment we named it — CVRP — because the name is a handle on a hundred years of other people's work. That's the skill worth taking back to your students: the first job isn't solving it, it's finding out what it's called.
And then: cluster, construct, improve, and measure every stage. That shape solves an enormous number of problems that look nothing like a school bus.