Skip to main content
A plan’s objectives list isn’t a set of weights. It’s a ranking: the engine compares two candidate solutions one objective at a time, in the order you give, and the first objective that separates them decides which one wins. This page explains that comparison, what each objective measures, and how to choose an order for your own operation.

How two solutions are compared

The engine looks at the first objective in the list. If one solution is better on it, that solution wins — whatever happens on every later objective. Only when the two solutions are tied on the first objective does the engine look at the second, and so on down the list. No amount of improvement on a later objective can make up for a loss on an earlier one. There are no weights to tune and no exchange rate between objectives: the position in the list is the only lever. Take three candidate solutions for the same plan: With the default order (maximizeMandatoryStops, then minimizeDelay, then … minimizeResources):
  • C loses straight away: it plans one mandatory stop fewer, even though it’s better on every other line.
  • A and B tie on mandatory stops, so delay decides: B wins, even though it needs an extra vehicle.
Move minimizeResources ahead of minimizeDelay and A beats B instead: you’ve told the engine that fleet size matters more than on-time delivery, so it lets stops run late to avoid dispatching an extra vehicle. C still loses under both orders, because maximizeMandatoryStops stays first.

Objectives split by priority

Three objectives depend on priority: maximizeMandatoryStops and maximizeOptionalStops (from each order’s priority) and minimizeResources (from each resource’s priority). For these, the engine generates one objective per priority value present in the plan, and keeps them at the position of the original objective in the list. Lower numbers matter more, and negative values are allowed.
  • Stops are maximized from the most important level down. With mandatory stops at priorities 0 and 1, maximizeMandatoryStops becomes two successive objectives: first maximize the priority-0 mandatory stops, then the priority-1 ones. One more planned priority-0 stop always wins over any number of priority-1 stops: priority levels are strict tiers, not weights.
  • Resources are minimized from the least important level up. With resources at priorities 0, 1, and 2, minimizeResources first minimizes the priority-2 resources in use, then the priority-1 ones, then the priority-0 ones — so the least important resources are the first left unused.

The default list

If a plan doesn’t set objectives, the engine uses this order:
In plain terms: first plan as many mandatory stops as possible; among solutions that do, prefer less customer delay; then lower cost; then fewer vehicles; then fewer breaches of overlappingCapacitiesByStopTag limits; then more optional stops; then more stops matched to a resource’s preferredStopTags; then less total working time; then less distance. Treat it as a starting point to check against your plan, not a template to copy. Several entries do nothing unless your plan declares the data they measure (see each objective below), and an objective your operation cares about may be missing from it.

Built-in objectives

Each built-in objective is a plain string in the list. The ones marked needs data measure something your plan has to declare: without it, the objective has nothing to optimize, doesn’t raise an error, and is removed from the optimization — it then doesn’t appear in the solution’s objectives either. Drop those from the list rather than keeping a guaranteed no-op.

maximizeMandatoryStops

Maximizes the number of mandatory (non-optional) stops planned. It comes first in the default list, so the engine never leaves a mandatory stop unplanned to improve a later objective such as delay, cost, or distance: a mandatory stop goes unplanned only when the engine finds no valid tour for it. A mandatory order with a low priority still counts, in a later tier (see Objectives split by priority): priority doesn’t let an order be dropped, only "optional": true does (see maximizeOptionalStops). priority and optional are set on the order and apply to every stop it contains — see Order, stop.

minimizeDelay

Minimizes the sum of delays against stops’ preferredTimeWindows. Only lateness counts: the engine never plans a stop before its preferred window opens, so arriving early is never traded against it. Without any preferredTimeWindows, it stays in the list and in the solution’s objectives, with a value of 0. A plan whose windows are all hard authorizedTimeWindows has no delay to minimize — see Hard vs soft constraints.

minimizeCosts

Minimizes the plan’s total cost, as defined by each resource’s cost object (a flat using cost per resource used, per-kilometre km, per-hour workedHours, per-capacity costs, and so on). Needs data: at least one resource with a cost. See Cost modeling.

minimizeResources

Minimizes the number of resources used. Split by Resource.priority and minimized from the least important level (highest number) up, so those resources are the first left unused (see Objectives split by priority). It counts resources directly: no using cost is needed for it to work.

minimizeOverOverlappingCapacitiesOnStops

Minimizes how far the limits set in overlappingCapacitiesByStopTag are exceeded — the number of resources simultaneously present at stops sharing a tag, beyond the limit set for that tag. The limit is a soft target, not a hard cap: whether the engine accepts an overrun to do better elsewhere depends on where this objective sits in the list. Needs data: plan-level overlappingCapacitiesByStopTag. See Limiting simultaneous depot visits.

maximizeOptionalStops

Maximizes the number of optional stops planned, in addition to the mandatory ones, split by priority like maximizeMandatoryStops. Marking an order "optional": true takes it out of maximizeMandatoryStops, so this objective is the only reason the engine plans it: without it in the list, an optional stop is unlikely to be planned at all, since it adds distance and time without serving any other objective. In the default order it comes after minimizeResources, so the engine never adds a vehicle just to plan an optional stop. Needs data: at least one order marked "optional": true.

maximizePreferredStops

Maximizes the stops served by a resource whose preferredStopTags match the stop’s tags — a soft preference, where another resource can still take the stop when that does better on a higher-ranked objective. Needs data: at least one resource with preferredStopTags matching some stops. See Driver skills and qualifications.

minimizeLargestTourDuration

Minimizes the working duration of the single longest tour in the fleet — travel, service, and waiting time included — which narrows the gap between that tour and the others. Not in the default list. It’s an objective the engine improves as far as it can, not a limit it enforces: use a resource’s maxWorkingDuration for a hard bound.

minimizeWorkingDuration

Minimizes the total working duration summed across every tour, travel, service, and waiting time included.

minimizeDistance

Minimizes the total distance travelled, summed across every tour.

minimizeEarlyLoadings

Minimizes early loadings, as declared per resource in avoidEarlyLoadings or per resource tag in the plan-level avoidEarlyLoadingsByResourceTag: each declaration names a stop tag, and optionally the capacities to consider. Not in the default list: add it explicitly, or the declarations have no effect. Needs data: at least one such declaration.

Object entries

Two kinds of entries are objects rather than names.

maximizePrecedences

Rewards the engine for respecting “this stop tag before that stop tag” rules. It takes a precedences array of pairs, each with a previous and a next stop tag, and the engine tries to satisfy as many pairs as possible. As an objective, it’s a preference, not a hard rule: a stop’s position inside an order’s stops array is the only precedence the engine always enforces.

Custom objectives

A custom entry optimizes a cost expression of your own, built from costsByResourceTag, in the direction you choose (minimize or maximize), under a name that must not collide with a built-in objective. When a custom objective replaces minimizeCosts, put it at the exact position minimizeCosts held: anywhere else, it’s traded off against different objectives and can return a different plan. See Cost modeling.

Choosing an order

  • Put first what you’d never trade away. For most operations that’s maximizeMandatoryStops: no later objective can then make the engine drop a mandatory stop.
  • Check every adjacent pair, not just the first two. Swapping any two neighbours changes which solutions win. minimizeCosts before minimizeResources prioritizes total cost even if reaching it takes an extra vehicle; the reverse caps fleet size first and lets cost settle wherever that leaves it.
  • Drop the objectives your plan gives no data to, and add the ones it needs: minimizeLargestTourDuration to balance tours, minimizeEarlyLoadings for declared early loading rules.
  • Keep preferences in objectives, rules in constraints. A hard constraint is never traded off, whatever the order; only soft constraints are arbitrated by the list. See Hard vs soft constraints.

Reading objectives in a solution

A solution reports the achieved value of each objective in objectives, with its name, direction (minimize or maximize), and value. Values are in each objective’s natural unit: for example a count of planned stops for maximizeMandatoryStops, a count of resources for minimizeResources, kilometres for minimizeDistance, and hours for minimizeWorkingDuration. The three objectives split by priority have one entry per priority level, each with a priority field giving the level it covers — a plan with mandatory stops at priorities 0 and 1 returns two maximizeMandatoryStops entries. Objectives removed for lack of data don’t appear. Comparing these values between two versions of a plan shows which objective an update actually moved.

See also