Logistics & Mobility// diagnostic

The plan came back complete and forty stops are not in it

In short

Unassigned stops mean one constraint family is binding, and arguing about which one wastes a morning. Re-solve the same instance with a single family relaxed each time — drop penalty, time windows, capacity, shift length, vehicle eligibility, depot hours — and the relaxation that admits the stops names the cause. 6 runs settle it.

Key takeaways

  • Check the input count first. A stop that never reached the solver is a pipeline bug, and it looks identical to a dropped one on the dispatch screen.
  • Most engines let a stop be skipped for a penalty, so infeasibility is reported as economics. Raise the penalty far above any routing cost and see which stops still refuse to fit.
  • Relax exactly one constraint family per run, always from the same baseline. Two relaxations at once tells you nothing about either.
  • Solve each unassigned stop alone with the full fleet. A stop no single vehicle can serve is impossible on its own terms, and no amount of extra capacity fixes it.
  • A genuine capacity shortfall is provable in arithmetic before any solver runs: total demand against total fleet capacity, and total work against total shift minutes.

Forty unassigned stops means one constraint family is binding, and the fastest route to knowing which is not discussion. It is re-solving the same instance five or six times, relaxing exactly one family per run, and reading off which relaxation lets the stops in. The procedure takes an afternoon, produces an answer nobody can argue with, and ends at a lever the operation can pull rather than at a theory about the solver.

Before any of that, one check that catches a surprising share of these mornings: count the stops that went in against the stops that came back, assigned and unassigned combined. If the totals do not reconcile, nothing was dropped — something never arrived, and you are debugging an import rather than a model. Order feeds that silently discard records without a geocode are the usual culprit, and the failure looks identical on a dispatch board to a genuine constraint problem. Everything else in routing, dispatch and optimisation assumes the instance you solved is the instance you meant to solve.

Why the plan reports success while stops sit outside it

A strict vehicle routing model with hard constraints has only two outcomes: a plan that serves every stop, or no plan at all. Production systems find the second outcome unusable — a dispatch desk cannot be handed nothing at 06:00 — so almost every implementation makes visits optional at a price. In Google's OR-Tools the mechanism is AddDisjunction, which takes a node and a penalty and permits the solver to skip that node by paying it. The penalty is expressed in the same units as the arc costs the model already accumulates.

The consequence is that infeasibility stops being reported as infeasibility. It arrives as economics: the solver decided that serving those forty stops cost more than the penalty for abandoning them, and returned a solution it considers optimal. Whether a stop was impossible or merely expensive is a distinction the output does not draw for you, and it is the first thing the ladder resolves.

A solver that can skip a stop for a fee will never tell you the day was impossible. It will tell you what it decided to give up.

The ladder: one constraint family per run

Every run starts from the same frozen baseline instance and changes exactly one thing. Combining relaxations is the mistake that turns a clean diagnosis into a week of guessing, because two relaxations that each admit twenty stops may be admitting the same twenty.

  1. Baseline. Record the exact unassigned set, the objective value, and the drop penalty in force. Keep the list of stop identifiers — every later run is compared against it.
  2. Raise the drop penalty by a factor of 100 above the largest plausible routing cost in the instance. Stops that now get served were never infeasible; they were unprofitable, and the answer is a penalty scale that reflects what a missed delivery actually costs you.
  3. Widen every time window by 60 minutes at each end, leaving everything else alone. A large recovery here means windows and travel time are mutually exclusive as recorded — and the enforcement class matters as much as the times, which is the subject of hard and soft time windows in a route plan.
  4. Add one virtual vehicle with the same profile and capacity as your most common unit, starting at the busiest depot. Stops that appear only now are a fleet-size result, not a modelling one.
  5. Extend every shift by 60 minutes, keeping the fleet unchanged. This separates a capacity shortfall in volume from a shortfall in time, and the two have completely different remedies.
  6. Remove every vehicle-to-stop eligibility rule — skills, profiles, allowed-vehicle sets. A large recovery here means an eligibility rule is excluding more vehicles than whoever wrote it realised.
  7. Open the depot 60 minutes earlier and close it 60 minutes later. Depot hours clip the working day at both ends and are routinely modelled as an afterthought.

Reading a rung back into something the operation can change

Relaxation that workedWhat was actually bindingThe lever, in order of preference
Higher drop penaltyNothing. The model was trading these stops away deliberatelyReprice the penalty against the real cost of a missed delivery, then re-solve
Wider time windowsPromises and travel time cannot both be true as recordedAudit the windows against recorded arrivals; reclassify preferences currently recorded as hard
One more vehicleVolume or weight exceeds what the fleet can carry todayMove stops to another day, hire a unit, or split the largest orders
Longer shiftsTotal driving plus service minutes exceeds total shift minutesAttack service time first — it is usually the larger and the more wrong of the two
Eligibility rules removedA skill or profile rule excludes almost every vehicleFind the rule, check whether anyone has ever served the stop with a different unit, and narrow it
Longer depot hoursThe working day is being clipped before the first stop and after the lastStagger departures, or model the loading bay as the constraint it actually is
Which relaxation admitted the stops, and what it means on the ground

The one stop that makes the whole day impossible

A single badly recorded stop can force dozens of others out, and the ladder can point at the wrong family when it does. The isolating test is direct: solve each unassigned stop on its own, with the entire fleet available and no other work in the instance. A stop that cannot be served even then is impossible on its own terms — the window closes before any vehicle can physically reach it, or its demand exceeds the largest vehicle you own, or no vehicle is eligible.

The causes are mundane and almost always data. A window typed as 09:00 to 09:00. A weight in kilograms in a field the model reads as tonnes. A coordinate that landed in the wrong country and made the leg a twelve-hour drive — a fault that also produces sequences that look absurd on a map, which is the diagnosis in the route crosses its own path twice. Run the single-stop test before the ladder if the unassigned set is small and scattered; run it after if the set is large and clustered.

When the fleet really is short, and how to say so credibly

The uncomfortable outcome of the ladder is that nothing was wrong: the constraints were accurate, the data was clean, and there was more work than fleet. That answer is worth as much as any of the others, provided it comes with the numbers. Present the demand-to-capacity ratio per dimension, the number of stops recovered by exactly one extra vehicle, and the marginal utilisation that extra vehicle would run at. A desk that receives an unexplained short plan blames the software; a desk that receives a ratio and a one-vehicle experiment argues about the fleet instead.

Watch the capacity dimensions carefully here. A load that is simultaneously heavy, bulky and carried in fixed-footprint cages is short in whichever dimension binds first, and models that track only weight will report plenty of room while the vehicle is physically full — the failure taken apart in modelling weight, volume, pallets and cages together.

Three things the ladder will not tell you

  • Whether the travel times were right in the first place. Every rung assumes the matrix is honest. A matrix built at free-flow speeds, or built once at 09:00 and reused all day, makes afternoon legs look cheaper than they are and pushes the infeasibility somewhere else entirely — the caching decisions that keep it honest are in building a travel-time matrix you can afford to rebuild every morning.
  • Whether the vehicles exist. If the fleet list includes a unit that is off the road, or starts a route from a depot the vehicle left last night, the ladder will happily report that capacity was sufficient. Stale fleet state has its own diagnosis in the map is open and the vehicles have stopped moving.
  • Whether the solve simply ran out of time. A search cut short can leave stops unassigned that a longer run would have placed, which looks exactly like a constraint problem and is not — separated properly in the solve still has not finished and the first van is loaded.

Run the ladder as a script rather than by hand, and keep the results. The same seven runs against a normal Tuesday and against a peak Friday tell you which constraint binds first as volume rises, which is the most useful piece of planning intelligence a routing system can produce and one almost nobody extracts from it. Wiring that into a repeatable job is straightforward product and platform work, and it turns a recurring argument into a standing report for any logistics and mobility operation running daily plans.

Frequently asked questions

Short answers to the follow-ups this page tends to raise.

Why does route optimisation software return a plan with stops unassigned instead of an error?

Because most production configurations make visits optional at a penalty, so the solver always has a valid answer available. In OR-Tools that is done with AddDisjunction, which lets a node be skipped in exchange for a cost. The result is that an infeasible day is reported as an economic decision rather than as a failure, and you cannot tell from the output whether a stop was impossible or merely expensive.

How do I find which constraint is causing unassigned stops?

Re-solve the same instance once per constraint family, relaxing exactly one family each time and always starting from the same baseline. Raise the drop penalty, widen time windows, add a vehicle, extend shifts, remove eligibility rules, and open depot hours — in separate runs. The relaxation that admits the stops identifies the binding family, usually within six runs.

One stop is unassigned and nothing else. What is wrong with it?

Solve that stop on its own with the whole fleet available. If it still cannot be served, the stop itself is impossible as recorded, and the causes are almost always data: a zero-width time window, a demand larger than any vehicle, a unit-of-measure error, or a coordinate in the wrong place. If it is served fine alone, it is competing with other work and belongs back in the ladder.

Does adding vehicles always fix unassigned stops?

No, and reaching for it first hides the real cause. Extra vehicles only help when volume or weight is binding; they do nothing when a time window is mutually exclusive with travel time, when an eligibility rule excludes every unit, or when a single stop is impossible on its own terms. The one-extra-vehicle run exists to test that hypothesis, not to implement it.

  • route optimisation
  • infeasibility
  • constraints
  • dispatch
// shipped work

The work behind this page

Builds from our portfolio that this page draws on.

Working on something in this space?

Tell us where you are in a sentence or two. We'll tell you honestly whether we're the right team, and what a sensible first slice of the work looks like.

Start the conversation