Introduction

This project is about solving Vehicle Routing Problem which is common task in transportation planning and logistics.

Vehicle Routing Problem

From wiki:

The vehicle routing problem (VRP) is a combinatorial optimization and integer programming problem which asks "What is the optimal set of routes for a fleet of vehicles to traverse in order to deliver to a given set of customers?". It generalises the well-known travelling salesman problem (TSP).

Determining the optimal solution to VRP is NP-hard, so the size of problems that can be solved, optimally, using mathematical programming or combinatorial optimization may be limited. Therefore, commercial solvers tend to use heuristics due to the size and frequency of real world VRPs they need to solve.

Design

Although performance is constantly in focus, a main idea behind projects' design is extensibility: the project aims to support a very wide range of VRP variations known as Rich VRP. This is achieved through various extension points: custom constraints, objective functions, acceptance criteria, etc.

Getting Started

This chapter describes basic information regarding the project and gives some instructions how to start with it.

Alternative, more example based, tutorial can be found in dedicated jupyter notebook.

Features

The main focus of the project is to support solving multiple variations of VRP within their combination. This non-complete list describes common VRP variations supported by the project:

  • Capacitated VRP (CVRP): designs optimal delivery routes where each vehicle only travels one route, each vehicle has the same characteristics and there is only one central depot.

  • Heterogeneous Fleet VRP (HFVRP) aka Mixed Fleet VRP: extend CVRP problem by varying the capacities. Also vehicle profiles example shows how to use different routing matrix profiles for different vehicle types, e.g. truck and car.

  • VRP with Time Windows (VRPTW): assumes that deliveries to a given customer must occur in a certain time interval, which varies from customer to customer.

  • VRP with Pickup and Delivery (VRPPD): goods need to be picked up from a certain location and dropped off at their destination. The pick-up and drop-off must be done by the same vehicle, which is why the pick-up location and drop-off location must be included in the same route.

  • VRP with backhauls (VRPB): a vehicle does deliveries as well as pick-ups in one route. Some customers require deliveries (referred to as linehauls) and others require pick-ups (referred to as backhauls).

  • Multi-Depot VRP (MDVRP): assumes that multiple depots are geographically spread among the customers.

  • Multi-Trip VRP (MTVRP): extends the VRP by adding the following constraint: routes have to be assigned to M vehicles in such a way that the total cost of the routes assigned to the same vehicle does not exceed a time horizon T (for instance the duration of a typical working day). See multiple reloads example.

  • Multi-Objective VRP (MOVRP): this variant addresses a need of real life applications, where decision maker should consider not only one objective (for example, total cost), but multiple ones simultaneously, such as amount of tours, unassigned jobs, work balance, etc. See multiple objectives example.

  • Open VRP (OVRP): usually, a route beginning at a given depot must finish at this depot, but in this variation vehicle ends at the last served customer.

  • VRP with Lunch Break (VRPLB): this problem arises when drivers must take pauses during their shift, for example, for lunch breaks. The project supports different types of break: with/without location, time window, time period. See multiple breaks example.

  • VRP with Route Balance (VRPRB): the majority of the problems encountered in industry, particularly in logistics, are multi-objective in nature. The VRPRB variant tries to minimize not only for the cost, but also for balancing workloads between routes. See one of examples.

  • Periodic VRP (PVRP): is used when planning is made over a certain period and deliveries to the customer can be made in different days. In current implementation each customer is visited only once. See multiple shifts example which shows multi-day planning scenario when vehicle can be used multiple times, but on different days.

  • Time dependent VRP (TDVRP): the travel time and distance between two customers or between a customer and the depot depends on time of day.

  • Skill VRP (SVRP): associates some skill(-s) with jobs which is requirement for vehicle to have in order to serve the job.

  • Traveling Salesman Problem (TSP): this is a specific case of VRP when there is only one vehicle.

In general, all these variations can be combined together in one single problem definition in pragmatic format.

Installation

Depending on your development environment, you can use different ways to install the solver:

Install with Python

The functionality of vrp-cli is published to pypi.org, so you can just install it using pip and use from python:

pip install vrp-cli
python examples/python-interop/example.py # run test example

Alternatively, you can use maturin tool to build solver locally.

You can find extra information in python example section of the docs. The full source code of python example is available in the repo which contains useful model wrappers with help of pydantic lib.

Install from Docker

Another fast way to try vrp solver on your environment is to use docker image (not performance optimized):

  • run public image from Github Container Registry:
    docker run -it -v $(pwd):/repo --name vrp-cli --rm ghcr.io/reinterpretcat/vrp/vrp-cli:1.23.0
  • build image locally using Dockerfile provided:
docker build -t vrp_solver .
docker run -it -v $(pwd):/repo --rm vrp_solver

Please note that the docker image is built using musl, not glibc standard library. So there might be some performance implications.

Install from Cargo

You can install vrp solver cli tool directly with cargo install:

cargo install vrp-cli

Ensure that your $PATH is properly configured to source the crates binaries, and then run solver using the vrp-cli command.

Install from source

Once pulled the source code, you can build it using cargo:

cargo build --release

Built binaries can be found in the ./target/release directory.

Alternatively, you can try to run the following script from the project root:

./solve_problem.sh examples/data/pragmatic/objectives/berlin.default.problem.json

It will build the executable and automatically launch the solver with the specified VRP definition. Results are stored in the folder where a problem definition is located.

Defining problem

In general, expressing an arbitrary VRP problem in one simple and universal format is a challenging task. pragmatic format aims to do that and there are concept and example sections which describe multiple features it supports in great details. However, it might take some time to get a huge problem with a lot of jobs and vehicles converted into it.

A csv import feature might help here.

CSV import

vrp-cli supports importing jobs and vehicles into pragmatic json format by the following command:

    vrp-cli import csv -i jobs.csv -i vehicles.csv -o problem.json

As you can see from the command, you need to specify jobs and vehicles in two separate csv files in the exact order.

Jobs csv

Jobs csv defines a plan of the problem and should have the following columns:

  • ID (string): an id
  • LAT (float): a latitude
  • LNG (float): a longitude
  • DEMAND (integer): a single dimensional demand. Depending on the value, it models different job activities:
    • positive: pickup
    • negative: delivery
    • zero: service
  • DURATION (integer): job duration in seconds
  • TW_START (date in RFC3999): earliest time when job can be served
  • TW_END (date in RFC3999): latest time when job can be served

To model a job with more than one activity (e.g. pickup + delivery), specify same ID twice. Example:

ID,LAT,LNG,DEMAND,DURATION,TW_START,TW_END
job1,52.52599,13.45413,2,5,2020-07-04T08:00:00Z,2020-07-04T12:00:00Z
job2,52.5225,13.4095,1,3,,
job2,52.5165,13.3808,-1,3,,
job3,52.5316,13.3884,-3,5,2020-07-04T08:00:00Z,2020-07-04T16:00:00Z

job with job2 id specified twice with positive and negative demand, so it will be considered as pickup and delivery job.

Vehicles csv

Vehicles csv defines a fleet of the problem and should have the following columns:

  • ID (string): an unique vehicle type id
  • LAT (float): a depot latitude
  • LNG (float): a depot longitude
  • CAPACITY (unassigned integer): a single dimensional vehicle capacity
  • TW_START (date in RFC3999): earliest time when vehicle can start at depot
  • TW_END (date in RFC3999): latest time when vehicle should return to depot
  • AMOUNT (unassigned integer): a vehicle amount of this type
  • PROFILE (string): a routing profile

This is example of such csv:

ID,LAT,LNG,CAPACITY,TW_START,TW_END,AMOUNT,PROFILE
vehicle1,52.4664,13.4023,40,2020-07-04T08:00:00Z,2020-07-04T20:00:00Z,10,car
vehicle2,52.4959,13.3539,50,2020-07-04T08:00:00Z,2020-07-04T20:00:00Z,20,truck

Limitations

Please note, to keep csv format simple and easy to use, it's limited to just a few, really basic features known as Capacitated Vehicle Routing Problem with Time Windows (CVRPTW). However, for a few jobs/vehices, you can modify the file manually as post-processing step.

Acquiring routing info

Once the problem is represented in pragmatic format, it's time to get matrix routing data.

Routing locations

The solver does not provide routing functionality, that's why you need to get it manually using unique locations from the problem definition. The process of getting locations for matrix routing and its usage is described here.

Routing matrix approximation

For quick prototyping, pragmatic format supports distance approximation using haversine formula within fixed speed for durations. This helps you quickly check how solver works on specific problem variant without need to acquire routing matrix.

The speed is 10m/s by default and can be tweaked by setting optional speed property in a each profile separately.

To use this feature, simply do not pass any matrix by omitting -m parameter.

Running solver

To run the solver, simply use:

vrp-cli solve pragmatic problem.json -o solution.json -g solution.geojson --log

If you specify --log option, it will produce some log output which contains various information regarding refinement process such as costs, amount of routes, time, etc.:

configured to use max-time: 300s
configured to use custom heuristic
preparing initial solution(-s)
[0s] created initial solution in 536ms, fitness: (0.000, 104.000, 70928.735)
[1s] created initial solution in 513ms, fitness: (0.000, 109.000, 74500.133)
[2s] created initial solution in 1097ms, fitness: (0.000, 104.000, 70928.735)
[2s] created initial solution in 168ms, fitness: (0.000, 125.000, 94305.015)
created initial population in 2317ms
[2s] generation 0 took 21ms, fitness: (0.000, 104.000, 70669.056)
[2s] population state (phase: initial, speed: 0.00 gen/sec, improvement ratio: 1.000:1.000):
        rank: 0, fitness: (0.000, 104.000, 70669.056), difference: 0.000%
        rank: 1, fitness: (0.000, 104.000, 70705.550), difference: 0.052%
[5s] generation 100 took 27ms, fitness: (0.000, 96.000, 64007.851)
[7s] generation 200 took 19ms, fitness: (0.000, 95.000, 63087.282)
..
149s] generation 4000 took 44ms, fitness: (0.000, 92.000, 54032.930)
[149s] population state (phase: exploration, speed: 26.78 gen/sec, improvement ratio: 0.235:0.155):
        rank: 0, fitness: (0.000, 92.000, 54032.930), difference: 0.000%
        rank: 1, fitness: (0.000, 92.000, 54032.930), difference: 0.000%
[153s] generation 4100 took 42ms, fitness: (0.000, 92.000, 54021.021)
..
[297s] generation 7200 took 20ms, fitness: (0.000, 92.000, 53264.644)
[299s] population state (phase: exploitation, speed: 24.16 gen/sec, improvement ratio: 0.165:0.058):
        rank: 0, fitness: (0.000, 92.000, 53264.026), difference: 0.000%
        rank: 1, fitness: (0.000, 92.000, 53264.026), difference: 0.000%
[299s] total generations: 7246, speed: 24.16 gen/sec
Route 1: 144 925 689 739 358 32 783 924 461 111 766 842 433
..
Route 92: 837 539 628 847 740 585 328 666 785 745
Cost 53264.03

Once the problem is solved, it will save solution in pragmatic and geojson (optional) format.

Extra options

The vrp-cli supports extra command line arguments which affects behavior of the algorithm.

Search mode

By default, search is mostly performed in exploration mode (broad) which allows the algorithm to perform better exploration of a solution space. However, it has slower overall convergence in better local optimum. You can switch to exploitation mode with deep setting:

vrp-cli solve pragmatic problem.json --search-mode=deep

In this mode, the algorithm memorizes only the last discovered best known solutions, so it can jump quicker to relatively good local optimum, but suffers more from premature convergence.

A general recommendation is to use deep on relatively simple dataset and/or when strict time limits should be applied.

Heuristic mode

At the moment, the solver supports three types of hyper-heuristics:

  • static selective: chooses metaheuristic from the list of predefined within their probabilities
  • dynamic selective: applies reinforcement learning technics to adjust probabilities of predefined metaheuristics
  • multi selective (default): starts with dynamic selective and switches to static selective if the progression speed is slow

You can switch between modes with heuristic setting:

vrp-cli solve pragmatic problem.json --heuristic=static

Termination criteria

Termination criteria defines when refinement algorithm should stop and return best known solution. At the moment, there are three types which can be used simultaneously:

Max time

Max time specifies duration of solving in seconds:

vrp-cli solve pragmatic problem.json --max-time=600

Max generations

Generation is one refinement step and it can be limited via max-generations parameter:

vrp-cli solve pragmatic problem.json --max-generations=1000

Coefficient of variation

This criteria calculates coefficient of variation for each objective over specific amount of generations specified by sample and stops algorithm when all calculated values are below specified threshold. It can be defined by min-cv parameter:

vrp-cli solve pragmatic problem.json --min-cv=sample,200,0.1,true

Here, the first parameter is generation amount, the second - threshold, the third - boolean flag whether the logic is applicable for all search phases, not only exploitation.

Alternatively, period of time in seconds can be specified instead of sample:

vrp-cli solve pragmatic problem.json --min-cv=period,300,0.1,true

Due to internal search heuristic implementation, it is recommended to use this termination criteria with max-time or max-generations.

Default behavior

Default termination criteria is max 3000 generations and 300 seconds at max.

Initial solution

You can supply initial solution to start with using -i option. Amount of initial solutions to be built can be overridden using init-size option.

Writing solution to file

Writing solution into file is controlled by -o or --out-result setting. When it is omitted, then solution is written in std out.

Geojson

Pragmatic format supports option -g or --geo-json which writes solution in separate file in geojson format. If the library iw used from the interop api (e.g. python or c), then solution in geojson can be returned inside extras.features with the config option specified: s

{
  "output": {
    "includeGeojson": true
  }
}

s

Analyzing results

In this example, solution is returned in a pragmatic format which model is described in details here. However, analyzing VRP solution might be a difficult task. That's why pragmatic format supports output in geojson format which can be simply visualized in numerous web based front ends, e.g. geojson.io or using open source tools such as leaflet:

To return solution in geojson format, use extra -g or --geo-json option.

Jupyter notebooks

You might want to look at this project. It provides some scripts and jupyter notebooks in order to perform deeper analysis of algorithm behaviour.

Evaluating performance

This section is mostly intended for developers and researchers who is interested to understand the solver's performance.

A generate command

A generate command is designed to simplify the process of generating realistic problems in pragmatic format. It has the following parameters:

  • type (required): a format of the problem. So far, only pragmatic is supported
  • prototypes (required): a list of files with problem prototype definition. At the moment, it has to be path to problem in pragmatic format. The prototype problem should contain at least three prototype jobs and one vehicle type, their properties are used with equal probability to generate jobs/vehicles in the problem. Other properties like objectives, profiles are copied as is
  • output (required): a path where to store generated problem
  • jobs size (required): amount of jobs to be generated in the plan.
  • vehicles size (required): amount of vehicle types to be generated in the fleet.
  • area size (optional): half size of the bounding box's side (in meters). The center is identified from bounding box of prototype jobs which is used also when the parameter is omitted.
  • locations (optional): a path to the file with list of locations which should be used for jobs instead of generated randomly inside specific bounding box.

Using generate command, you can quickly generate different VRP variants. Usage example:

    vrp-cli generate pragmatic -p prototype.json -o generated.json -j 100 -v 5 -a 10000

This command generates a new problem definition with 100 jobs spread uniformly in bounding box with half side 10000 meters.

A check command

A check command is intended to prove feasibility of calculated solution. Both, the problem definition and calculated solution, are required:

    vrp-cli check pragmatic -p problem.json -s solution.json

Algorithm fine tuning

Actual algorithm parameters can be tweaked by supplying configuration file, e.g.:

    vrp-cli solve pragmatic problem.json -s solution.json --config tweak.json
Configuration file

{
  "evolution": {
    "initial": {
      "method": {
        "type": "cheapest",
        "weight": 1
      },
      "alternatives": {
        "methods": [
          {
            "type": "farthest",
            "weight": 1
          },
          {
            "type": "nearest",
            "weight": 1
          },
          {
            "type": "gaps",
            "min": 2,
            "max": 20,
            "weight": 1
          },
          {
            "type": "skip-best",
            "start": 1,
            "end": 2,
            "weight": 1
          },
          {
            "type": "regret",
            "start": 2,
            "end": 3,
            "weight": 1
          },
          {
            "type": "blinks",
            "weight": 1
          },
          {
            "type": "perturbation",
            "probability": 0.33,
            "min": -0.2,
            "max": 0.2,
            "weight": 1
          }
        ],
        "maxSize": 4,
        "quota": 0.05
      }
    },
    "population": {
      "type": "rosomaxa",
      "selectionSize": 8,
      "maxEliteSize": 2,
      "maxNodeSize": 2,
      "spreadFactor": 0.75,
      "distributionFactor": 0.75,
      "rebalanceMemory": 100,
      "explorationRatio": 0.9
    }
  },
  "hyper": {
    "type": "static-selective",
    "operators": [
      {
        "type": "decomposition",
        "maxSelected": 2,
        "repeat": 4,
        "routes": {
          "min": 2,
          "max": 4
        },
        "probability": {
          "threshold": {
            "jobs": 300,
            "routes": 10
          },
          "phases": [
            {
              "type": "exploration",
              "chance": 0.05
            },
            {
              "type": "exploitation",
              "chance": 0.05
            }
          ]
        }
      },
      {
        "type": "local-search",
        "probability": {
          "scalar": 0.05
        },
        "times": {
          "min": 1,
          "max": 2
        },
        "operators": [
          {
            "weight": 200,
            "type": "swap-star"
          },
          {
            "weight": 100,
            "type": "inter-route-best",
            "noise": {
              "probability": 0.1,
              "min": -0.1,
              "max": 0.1
            }
          },
          {
            "weight": 30,
            "type": "inter-route-random",
            "noise": {
              "probability": 0.1,
              "min": -0.1,
              "max": 0.1
            }
          },
          {
            "weight": 30,
            "type": "intra-route-random",
            "noise": {
              "probability": 1,
              "min": -0.1,
              "max": 0.1
            }
          }
        ]
      },
      {
        "type": "ruin-recreate",
        "probability": {
          "scalar": 1
        },
        "ruins": [
          {
            "weight": 100,
            "methods": [
              {
                "probability": 1,
                "type": "adjusted-string",
                "lmax": 10,
                "cavg": 10,
                "alpha": 0.01
              }
            ]
          },
          {
            "weight": 10,
            "methods": [
              {
                "probability": 1,
                "type": "neighbour",
                "min": 8,
                "max": 16
              }
            ]
          },
          {
            "weight": 10,
            "methods": [
              {
                "probability": 1,
                "type": "worst-job",
                "skip": 4,
                "min": 8,
                "max": 16
              }
            ]
          },
          {
            "weight": 5,
            "methods": [
              {
                "probability": 1,
                "type": "cluster",
                "min": 8,
                "max": 16,
                "minItems": 4
              }
            ]
          },
          {
            "weight": 2,
            "methods": [
              {
                "probability": 1,
                "type": "close-route"
              },
              {
                "probability": 0.1,
                "type": "random-job",
                "min": 8,
                "max": 16
              }
            ]
          },
          {
            "weight": 1,
            "methods": [
              {
                "probability": 1,
                "type": "worst-route"
              },
              {
                "probability": 0.1,
                "type": "random-job",
                "min": 8,
                "max": 16
              }
            ]
          },
          {
            "weight": 1,
            "methods": [
              {
                "probability": 1,
                "type": "random-route",
                "min": 1,
                "max": 4
              },
              {
                "probability": 0.1,
                "type": "random-job",
                "min": 8,
                "max": 16
              }
            ]
          }
        ],
        "recreates": [
          {
            "weight": 50,
            "type": "skip-best",
            "start": 1,
            "end": 2
          },
          {
            "weight": 20,
            "type": "regret",
            "start": 2,
            "end": 3
          },
          {
            "weight": 20,
            "type": "cheapest"
          },
          {
            "weight": 10,
            "type": "perturbation",
            "probability": 0.33,
            "min": -0.2,
            "max": 0.2
          },
          {
            "weight": 5,
            "type": "skip-best",
            "start": 3,
            "end": 4
          },
          {
            "weight": 5,
            "type": "gaps",
            "min": 2,
            "max": 20
          },
          {
            "weight": 5,
            "type": "blinks"
          },
          {
            "weight": 2,
            "type": "farthest"
          },
          {
            "weight": 2,
            "type": "skip-best",
            "start": 4,
            "end": 8
          },
          {
            "weight": 1,
            "type": "nearest"
          },
          {
            "weight": 1,
            "type": "skip-random"
          },
          {
            "weight": 1,
            "type": "slice"
          }
        ]
      },
      {
        "type": "local-search",
        "probability": {
          "scalar": 0.01
        },
        "times": {
          "min": 1,
          "max": 2
        },
        "operators": [
          {
            "weight": 100,
            "type": "inter-route-best",
            "noise": {
              "probability": 0.1,
              "min": -0.1,
              "max": 0.1
            }
          },
          {
            "weight": 30,
            "type": "inter-route-random",
            "noise": {
              "probability": 0.1,
              "min": -0.1,
              "max": 0.1
            }
          },
          {
            "weight": 30,
            "type": "intra-route-random",
            "noise": {
              "probability": 1,
              "min": -0.1,
              "max": 0.1
            }
          },
          {
            "weight": 100,
            "type": "sequence"
          }
        ]
      }
    ]
  },
  "termination": {
    "maxTime": 300,
    "maxGenerations": 3000,
    "variation": {
      "intervalType": "sample",
      "value": 3000,
      "cv": 1,
      "isGlobal": true
    }
  },
  "telemetry": {
    "progress": {
      "enabled": true,
      "logBest": 100,
      "logPopulation": 1000,
      "dumpPopulation": false
    },
    "metrics": {
      "enabled": false,
      "trackPopulation": 1000
    }
  },
  "environment": {
    "parallelism": {
      "numThreadPools": 6,
      "threadsPerPool": 8
    },
    "logging": {
      "enabled": true,
      "prefix": "[config.full]"
    },
    "isExperimental": false
  },
  "output": {
    "includeGeojson": true
  }
}

All main parameters are optional and can be omitted to stick with defaults. Check the source code for details.

Intermediate solutions

You can record parameters of intermediate solutions if you enable telemetry via configuration file.

Concepts

This section describes the ways how VRP problem can be specified in order to run the solver. At the moment, there are two options:

  • pragmatic: a json format used to model a real world VRP problems
  • scientific a set of text formats used to benchmark various algorithms in scientific literature.

Pragmatic

Pragmatic format aims to model a multiple VRP variants through single problem and solution model schemas which are described in details in next sections.

Performance

There is no limit on problem size, solver should be able to solve problems with thousands of jobs in fairly reasonable amount of time depending on your termination criteria (e.g. time or amount of iterations/generations). However, exact performance depends on VRP variant (e.g. VRPPD is slower than CVRPTW).

Quality of results

Although results seems to be comparable with alternative solutions, default metaheuristic still can be improved.

Examples

A various examples can be found in pragmatic examples section.

Problem model

In general a pragmatic problem is split into two required and one optional parts:

  • plan (required) models a work to be performed by vehicles taking into account all related constraints, such as time windows, demand, skills, etc.
  • fleet (required) models available resources defined by vehicle types.
  • objectives (optional) defines objective functions as goal of whole optimization.

Modeling jobs

A work which has to be done is model by list of jobs defined in plan.

Check next job section for detailed explanation.

Modeling vehicles

Vehicles are defined by fleet.vehicles property which specifies array of vehicle types, not specific vehicles.

More details can be found in vehicle type section.

Relation between jobs and vehicles

An optional plan.relations property specifies relations between multiple jobs and single vehicle. It is useful to lock jobs to a specific vehicle in any or predefined order.

Check relations section for more details.

Job and vehicle constraints

There are multiple strict constraints that should be matched on jobs and vehicles.

Demand and capacity

Each job should have demand property which models a good size in abstract integral units:

            "demand": [
              1
            ]

It is required, but you can set demand to zero in case it is not needed. It can be multidimensional array.

A capacity property is a vehicle characteristic which constraints amount of jobs can be served by vehicle of specific type based on accumulated demand value. Total demand should not exceed capacity value.

Time windows

Optionally, each job can have one or more time window:

                "times": [
                  [
                    "2019-07-04T09:00:00Z",
                    "2019-07-04T18:00:00Z"
                  ],
                  [
                    "2019-07-05T09:00:00Z",
                    "2019-07-05T18:00:00Z"
                  ]
                ]

Time windows are strict: if no vehicle can visit a job in given time ranges, then the job is considered as unassigned.

Vehicle time is limited per each shift and has required start optional end time:

            "start": {
              "earliest": "2019-07-04T09:00:00Z",
              "location": {
                "lat": 52.5316,
                "lng": 13.3884
              }
            },
            "end": {
              "latest": "2019-07-04T18:00:00Z",
              "location": {
                "lat": 52.5316,
                "lng": 13.3884
              }
            }

More details about shift property can be found in vehicle type section.

Clustering

Some jobs can be clustered together to have more realistic ETA, check vicinity clustering section.

Job

A job is used to model customer demand, additionally, with different constraints, such as time, skills, etc. A job schema consists of the following properties:

  • id (required): an unique job id
  • pickups (optional): a list of pickup tasks
  • deliveries (optional): a list of delivery tasks
  • replacements (optional): a list of replacement tasks
  • services (optional): a list of service tasks
  • skills (optional): job skills defined by allOf, oneOf or noneOf conditions:
            "skills": {
          "allOf": [
            "fridge"
          ],
          "noneOf": [
            "washing_machine"
          ]
        }
    
    These conditions are tested against vehicle's skills.
  • value (optional): a value associated with the job. With maximize-value objective, it is used to prioritize assignment of specific jobs. The difference between value and order (see in Tasks below) is that order related logic tries to assign jobs with lower order in the beginning of the tour. In contrast, value related logic tries to maximize total solution value by prioritizing assignment value scored jobs in any position of a tour. See job priorities example.
  • group (optional): a group name. Jobs with the same groups are scheduled in the same tour or left unassigned.
  • compatibility (optional): compatibility class. Jobs with different compatibility classes cannot be assigned in the same tour. This is useful to avoid mixing cargo, such as hazardous goods and food.

A job should have at least one task property specified.

Tasks

A delivery, pickup, replacement and service lists specify multiple job tasks and at least one of such tasks has to be defined. Each task has the following properties:

  • places (required): list of possible places from which only one has to be visited
  • demand (optional/required): a task demand. It is required for all job types, except service
  • order (optional): a job task assignment order which makes preferable to serve some jobs before others in the tour. The order property is represented as integer greater than 1, where the lower value means higher priority. By default its value is set to maximum.

Places

Each place consists of the following properties:

  • location (required): a place location
  • duration (required): service (operational) time to serve task here (in seconds)
  • times (optional): time windows
  • tag (optional): a job place tag which will be returned within job's activity in result solution.

Multiple places on single task can help model variable job location, e.g. visit customer at different location depending on time of the day.

Pickup job

Pickup job is a job with job.pickups property specified, without job.deliveries:

      {
        "id": "job2",
        "pickups": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5225,
                  "lng": 13.4095
                },
                "duration": 240.0,
                "times": [
                  [
                    "2019-07-04T10:00:00Z",
                    "2019-07-04T16:00:00Z"
                  ]
                ]
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },

The vehicle picks some good at pickup locations, which leads to capacity consumption growth according to job.pickups.demand value, and brings it till the end of the tour (or next reload). Each pickup task has its own properties such as demand and places.

Delivery job

Delivery job is a job with job.deliveries property specified, without job.pickups:

      {
        "id": "job1",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.52599,
                  "lng": 13.45413
                },
                "duration": 300.0,
                "times": [
                  [
                    "2019-07-04T09:00:00Z",
                    "2019-07-04T18:00:00Z"
                  ],
                  [
                    "2019-07-05T09:00:00Z",
                    "2019-07-05T18:00:00Z"
                  ]
                ]
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },

The vehicle picks some goods at the start stop, which leads to initial capacity consumption growth, and brings it to job's locations, where capacity consumption is decreased based on job.deliveries.demand values. Each delivery task has its own properties such as demand and places.

Pickup and delivery job

Pickup and delivery job is a job with both job.pickups and job.deliveries properties specified:

      {
        "id": "job3",
        "pickups": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5225,
                  "lng": 13.4095
                },
                "duration": 300.0,
                "tag": "p1"
              }
            ],
            "demand": [
              1
            ]
          }
        ],
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5165,
                  "lng": 13.3808
                },
                "duration": 300.0,
                "tag": "d1"
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      }

The vehicle picks some goods at one or multiple job.pickups.location, which leads to capacity growth, and brings them to one or many job.deliveries.location. The job has the following rules:

  • all pickup/delivery tasks should be done or none of them.
  • assignment order is not defined except all pickups should be assigned before any of deliveries.
  • sum of pickup demand should be equal to sum of delivery demand

A good example of such job is a job with more than two places with variable demand:

      {
        "id": "multi_job1",
        "pickups": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5622847,
                  "lng": 13.4023099
                },
                "duration": 240.0,
                "tag": "p1"
              }
            ],
            "demand": [
              1
            ]
          },
          {
            "places": [
              {
                "location": {
                  "lat": 52.5330881,
                  "lng": 13.3973059
                },
                "duration": 240.0,
                "tag": "p2"
              }
            ],
            "demand": [
              1
            ]
          }
        ],
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5252832,
                  "lng": 13.4188422
                },
                "duration": 240.0,
                "tag": "d1"
              }
            ],
            "demand": [
              2
            ]
          }
        ]
      },

This job contains two pickups and one delivery. Interpretation of such job can be "bring two parcels from two different places to one single customer".

Another example is one pickup and two deliveries:

      {
        "id": "multi_job2",
        "pickups": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.52599,
                  "lng": 13.45413
                },
                "duration": 240.0,
                "tag": "p1"
              }
            ],
            "demand": [
              2
            ]
          }
        ],
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.4928,
                  "lng": 13.4597
                },
                "duration": 240.0,
                "tag": "d1"
              }
            ],
            "demand": [
              1
            ]
          },
          {
            "places": [
              {
                "location": {
                  "lat": 52.4989,
                  "lng": 13.3917
                },
                "duration": 240.0,
                "tag": "d2"
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      }
    ]
  },

Replacement job

A replacement job is a job with job.replacement property specified:

      {
        "id": "simple_replacement_job",
        "replacements": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5622847,
                  "lng": 13.4023099
                },
                "duration": 3600.0,
                "times": [
                  [
                    "2019-07-04T09:00:00Z",
                    "2019-07-04T18:00:00Z"
                  ]
                ]
              }
            ],
            "demand": [
              3
            ]
          }
        ]
      },

It models an use case when something big has to be replaced at the customer's location. This task requires a new good to be loaded at the beginning of the journey and old replaced one brought to journey's end.

Service job

A service job is a job with job.service property specified:

      {
        "id": "simple_service_job",
        "services": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5330881,
                  "lng": 13.3973059
                },
                "duration": 3600.0,
                "times": [
                  [
                    "2019-07-04T08:00:00Z",
                    "2019-07-04T12:00:00Z"
                  ],
                  [
                    "2019-07-04T14:00:00Z",
                    "2019-07-04T18:00:00Z"
                  ]
                ]
              }
            ]
          }
        ]
      },

This job models some work without demand (e.g. handyman visit).

Mixing job tasks

You can specify multiple tasks properties to get some mixed job:

      {
        "id": "mixed_job",
        "pickups": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5252832,
                  "lng": 13.4188422
                },
                "duration": 240.0,
                "tag": "p1"
              }
            ],
            "demand": [
              1
            ]
          }
        ],
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.52599,
                  "lng": 13.45413
                },
                "duration": 240.0,
                "tag": "d1"
              }
            ],
            "demand": [
              1
            ]
          }
        ],
        "replacements": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.4928,
                  "lng": 13.4597
                },
                "duration": 2400.0,
                "tag": "r1"
              }
            ],
            "demand": [
              2
            ]
          }
        ],
        "services": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.4989,
                  "lng": 13.3917
                },
                "duration": 1800.0,
                "tag": "s1"
              }
            ]
          }
        ]
      }

Similar pickup and delivery job, all these tasks has to be executed or none of them. The order is not specified except pickups must be scheduled before any delivery, replacement or service.

Hint

Use tag property on each job place if you want to use initial solution or checker features.

Examples

Please refer to basic job usage examples to see how to specify problem with different job types.

Vehicle types

A vehicle types are defined by fleet.vehicles property and their schema has the following properties:

  • typeId (required): a vehicle type id
        "typeId": "vehicle",
  • vehicleIds (required): a list of concrete vehicle ids available for usage.
        "vehicleIds": [
          "vehicle_1"
        ],
  • profile (required): a vehicle profile which is defined by two properties:
    • matrix (required) : a name of matrix profile
    • scale (optional): duration scale applied to all travelling times (default is 1.0)
        "profile": {
          "matrix": "normal_car"
        },
  • costs (required): specifies how expensive is vehicle usage. It has three properties:

    • fixed: a fixed cost per vehicle tour
    • time: a cost per time unit
    • distance: a cost per distance unit
  • shifts (required): specify one or more vehicle shift. See detailed description below.

  • capacity (required): specifies vehicle capacity symmetric to job demand

        "capacity": [
          10
        ]
  • skills (optional): vehicle skills needed by some jobs
        "skills": [
          "handyman"
        ]
  • limits (optional): vehicle limits. There are two:

    • maxDuration (optional): max tour duration
    • maxDistance (optional): max tour distance
    • tourSize (optional): max amount of activities in the tour (without departure/arrival). Please note, that clustered activities are counted as one in case of vicinity clustering.

An example:

      {
        "typeId": "vehicle",
        "vehicleIds": [
          "vehicle_1"
        ],
        "profile": {
          "matrix": "normal_car"
        },
        "costs": {
          "fixed": 22.0,
          "distance": 0.0002,
          "time": 0.004806
        },
        "shifts": [
          {
            "start": {
              "earliest": "2019-07-04T09:00:00Z",
              "location": {
                "lat": 52.5316,
                "lng": 13.3884
              }
            },
            "end": {
              "latest": "2019-07-04T18:00:00Z",
              "location": {
                "lat": 52.5316,
                "lng": 13.3884
              }
            }
          }
        ],
        "capacity": [
          10
        ]
      }

Shift

Essentially, shift specifies vehicle constraints such as time, start/end locations, etc.:

        "shifts": [
          {
            "start": {
              "earliest": "2019-07-04T09:00:00Z",
              "location": {
                "lat": 52.5316,
                "lng": 13.3884
              }
            },
            "end": {
              "latest": "2019-07-04T18:00:00Z",
              "location": {
                "lat": 52.5316,
                "lng": 13.3884
              }
            }
          }
        ],

At least one shift has to be specified. More than one vehicle shift with different times means that this vehicle can be used more than once. This is useful for multi day scenarios. An example can be found here.

Each shift can have the following properties:

  • start (required) specifies vehicle start place defined via location, earliest (required) and latest (optional) departure time

  • end (optional) specifies vehicle end place defined via location, earliest (reserved) and latest (required) arrival time. When omitted, then vehicle ends on last job location

  • breaks (optional) a list of vehicle breaks. There are two types of breaks:

    • required: this break is guaranteed to be assigned at cost of flexibility. It has the following properties:
      • time (required): a fixed time or time offset interval when the break should happen specified by earliest and latest properties. The break will be assigned not earlier, and not later than the range specified.
      • duration (required): duration of the break
    • optional: although such break is not guaranteed for assignment, it has some advantages over required break:
      • arbitrary break location is supported
      • the algorithm has more flexibility for assignment It is specified by:
      • time (required): time window or time offset interval after which a break should happen (e.g. between 3 or 4 hours after start).
      • places: list of alternative places defined by location (optional), duration (required) and tag (optional). If location of a break is omitted then break is stick to location of a job served before break.
      • policy (optional): a break skip policy. Possible values:
        • skip-if-no-intersection: allows to skip break if actual tour schedule doesn't intersect with vehicle time window (default)
        • skip-if-arrival-before-end: allows to skip break if vehicle arrives before break's time window end.

    Please note that optional break is a soft constraint and can be unassigned in some cases due to other hard constraints, such as time windows. You can control its unassignment weight using specific property on minimize-unassigned objective. See example here

    Additionally, offset time interval requires departure time optimization to be disabled explicitly (see E1307).

  • reloads (optional) a list of vehicle reloads. A reload is a place where vehicle can load new deliveries and unload pickups. It can be used to model multi trip routes. Each reload has optional and required fields:

    • location (required): an actual place where reload activity happens
    • duration (required): duration of reload activity
    • times (optional): reload time windows
    • tag (optional): a tag which will be propagated back within the corresponding reload activity in solution
    • resourceId (optional): a shared reload resource id. It is used to limit amount of deliveries loaded at this reload. See examples here.
  • recharges (optional, experimental) specifies recharging stations and max distance limit before recharge should happen. See examples here.

Shared resources

A fleet.resources specifies an optional section which goal is to control distribution of limited shared resource between different vehicles.

Reload resource

An idea of reload resource is to put limit on amount of deliveries in total loaded to the multiple vehicles on specific reload place. A good example is some warehouse which can be visited by multiple vehicles in the middle of their tours, but it has only limited amount of deliveries.

The reload resource definition has the following properties:

  • type (required): should be set to reload
  • id (required): an unique resource id. Put this id in vehicle reload's resourceId property to trigger shared resource behavior
  • capacity (required): total amount of resource. It has the same type as vehicle's capacity property.

An example of a reload resource definition:

    "resources": [
      {
        "type": "reload",
        "id": "warehouse_a",
        "capacity": [1]
      }
    ]

An example of a vehicle reload with a reference to the resource definition:

            "reloads": [
              {
                "location": {
                  "lat": 52.5103,
                  "lng": 13.3898
                },
                "duration": 600.0,
                "resourceId": "warehouse_a"
              }
            ]

The full example can be found here.

Relations

Relation is a mechanism to lock jobs to specific vehicles. List of relations is a part of plan schema and each relation has the following properties:

  • type (required): one of three relation types: tour, fixed, or sequence. See description below.
  • vehicleId (required): a specific vehicle id
  • jobs (required): list of job ids including reserved: departure, arrival, break and reload
  • shiftIndex (optional): a vehicle shift index. If not specified, a first, zero indexed, shift assumed

You can use more than one relation per vehicle.

Any type

A any relation is used to lock specific jobs to certain vehicle in any order:

      {
        "type": "any",
        "jobs": [
          "job1",
          "job3"
        ],
        "vehicleId": "vehicle_1"
      }

Sequence type

A sequence relation is used to lock specific jobs to certain vehicle in fixed order allowing insertion of new jobs in between.

Strict type

In contrast to sequence relation, strict locks jobs to certain vehicle without ability to insert new jobs in between:

      {
        "type": "strict",
        "jobs": [
          "departure",
          "job4",
          "job1"
        ],
        "vehicleId": "vehicle_1"
      }

In this example, new jobs can be inserted only after job with id job1.

Important notes

Please consider the following notes:

  • jobs specified in relations are not checked for constraint violations. This might lead to non-feasible solutions (e.g. routes with capacity or time window violation).
  • relation with jobs which have multiple pickups or deliveries places are not yet supported

Examples

Please refer to complete example to see how to specify problem with relations.

Job clustering

Sometimes, the problem definition has jobs which are close to each other, so it makes sense to serve them together at the same stop. However, typically, job's service time includes extra time costs such as parking, loading/unloading, which should be considered only once in the stop. A clustering algorithm supposed to help to schedule jobs realistically in such scenarios and even in some others like delivery with drones.

Vicinity clustering

An experimental vicinity clustering algorithm is designed to cluster close jobs together and serve them at the same stop considering such aspects of last mile delivery as parking costs, traveling distance/duration between each job in the cluster, service time reduction, etc. To use it, specify clustering property inside the plan with the following properties:

  • type: a clustering algorithm name. At the moment, only vicinity is supported
  • profile: specifies routing profile used to calculate commute durations and distances. It has the same properties as profile on vehicle type.
  • threshold: specifies various parameters which can control how clusters are built. It has the following properties:
    • duration: moving duration limit
    • distance: moving distance limit
    • minSharedTime (optional): minimum shared time for jobs (non-inclusive)
    • smallestTimeWindow (optional): the smallest time window of the cluster after service time shrinking
    • maxJobsPerCluster (optional): the maximum amount of jobs per cluster
  • visiting: specifies job visiting policy type:
    • return: after each job visit, driver has to return to stop location
    • continue: starting from stop location, driver visits each job one by one, returns to it in the end
  • serving: specifies a policy for job's service time in the single stop. All policies have a parking property which specifies how much time has to be reserved at initial parking at stop location. Three policy types are available:
    • original: keep original service time
    • multiplier: multiplies original service time by fixed value
    • fixed: uses a new fixed value instead of original service time
  • filtering: specifies job filtering properties. At the moment, it has a single property:
    • excludeJobIds: ids of the jobs which should not be clustered with others

An example:

    "clustering": {
      "type": "vicinity",
      "profile": {
        "matrix": "car",
        "scale": 10
      },
      "threshold": {
        "duration": 120,
        "distance": 100
      },
      "visiting": "continue",
      "serving": {
        "type": "fixed",
        "value": 180,
        "parking": 120
      }
    }

In the solution, clustered jobs will have extra properties:

  • tour.stop.parking: specifies time of the parking
  • tour.stop.activity.commute: specifies job commute information. It has two properties, forward and backward which specify information about activity place visit:
    • location: a location before/after place visit
    • distance: travelled distance
    • time: time when commute occurs

An example:

              "commute": {
                "forward": {
                  "location": {
                    "lat": 52.5254256,
                    "lng": 13.4527159
                  },
                  "distance": 14.0,
                  "time": {
                    "start": "2020-05-01T09:12:01Z",
                    "end": "2020-05-01T09:12:11Z"
                  }
                },
                "backward": {
                  "location": {
                    "lat": 52.5253342,
                    "lng": 13.4533489
                  },
                  "distance": 54.0,
                  "time": {
                    "start": "2020-05-01T09:15:11Z",
                    "end": "2020-05-01T09:16:01Z"
                  }
                }
              }

Limitations

The vicinity clustering functionality has some limitations:

  • only jobs with single task can be clustered, but their type, such as pickup or delivery, doesn't matter
  • clusters are pre-built using a greedy algorithm which picks the closest by duration job first
  • extra constraints puts extra limitations: e.g. priority, order, skills defined on jobs should match in the cluster
  • jobs with value are not clustered with job without value
  • commute distance is not included into statistics

Examples

Please refer to examples section to see examples.

Objectives

A classical objective function (or simply objective) for VRP is minimization of total cost. However, real life scenarios require different objective function or even more than one considered simultaneously. That's why the solver has a concept of multi objective.

Understanding multi objective structure

A multi objective is defined by objectives property which has array of array type and defines some kind of "hierarchical" objective function where priority of objectives decreases from first to the last element of outer array. Objectives inside the same inner array have the same priority.

Available objectives

The solver already provides multiple built-in objectives distinguished by their type. All these objectives can be split into the following groups.

Cost objectives

These objectives specify how "total" cost of job insertion is calculated:

  • minimize-cost: minimizes total transport cost calculated for all routes. Here, total transport cost is seen as linear combination of total time and distance
  • minimize-distance: minimizes total distance of all routes
  • minimize-duration: minimizes total duration of all routes

One of these objectives has to be set and only one.

Scalar objectives

Besides cost objectives, there are other objectives which are targeting for some scalar characteristic of solution:

  • minimize-unassigned: minimizes amount of unassigned jobs. Although, solver tries to minimize amount of unassigned jobs all the time, it is possible that solution, discovered during refinement, has more unassigned jobs than previously accepted. The reason of that can be conflicting objective (e.g. minimize tours) and restrictive constraints such as time windows. The objective has the following optional parameter:
    • breaks: a multiplicative coefficient to make breaks more preferable for assignment. Default value is 1. Setting this parameter to a value bigger than 1 is useful when it is highly desirable to have break assigned but its assignment leads to more jobs unassigned.
  • minimize-tours: minimizes total amount of tours present in solution
  • maximize-tours: maximizes total amount of tours present in solution
  • minimize-arrival-time: prefers solutions where work is finished earlier
  • fast-service: prefers solutions when jobs are served early in tours

Job distribution objectives

These objectives provide some extra control on job assignment:

  • maximize-value: maximizes total value of served jobs. It has optional parameters:
    • reductionFactor: a factor to reduce value cost compared to max routing costs
    • breaks: a value penalty for skipping a break. Default value is 100.
  • tour-order: controls desired activity order in tours
    • isConstrained: violating order is not allowed, even if it leads to less assigned jobs (default is true).
  • compact-tour: controls how tour is shaped by limiting amount of shared jobs, assigned in different routes, for a given job' neighbourhood. It has the following mandatory parameters:
    • options: options to relax objective:
      • jobRadius: a radius of neighbourhood, minimum is 1
      • threshold: a minimum shared jobs to count
      • distance: a minimum relative distance between counts when comparing different solutions. This objective is supposed to be on the same level within cost ones.

Work balance objectives

There are four work balance objectives available:

  • balance-max-load: balances max load in tour
  • balance-activities: balances amount of activities performed in tour
  • balance-distance: balances travelled distance per tour
  • balance-duration: balances tour durations

Each objective has optional parameters defined by option property:

  • threshold: a target coefficient of variation (scale invariant statistical measure of dispersion) value which specifies desired minimum balancing level. All values below threshold are considered equal which helps the search algorithm to optimize conflicting objectives.

It is recommended to set both option values to guide the search towards optimum for conflicting objectives, e.g. cost minimization and any of work balance.

An usage example:

      {
        "type": "balance-max-load",
        "options": {
          "threshold": 0.01
        }
      }

Default behaviour

By default, decision maker minimizes amount of unassigned jobs, routes and then total cost. This is equal to the following definition:

  "objectives": [
    [
      {
        "type": "minimize-unassigned"
      }
    ],
    [
      {
        "type": "minimize-tours"
      }
    ],
    [
      {
        "type": "minimize-cost"
      }
    ]
  ]

Here, cost minimization is a secondary objective which corresponds to a classical hierarchical objective used by Solomon benchmark.

If at least one job has non-zero value associated, then the following objective is used:

  "objectives": [
    [
      {
        "type": "maximize-value",
        "reductionFactor": 0.1
      }
    ],
    [
      {
        "type": "minimize-unassigned"
      }
    ],
    [
      {
        "type": "minimize-tours"
      }
    ],
    [
      {
        "type": "minimize-cost"
      }
    ]
  ]

If order on job task is specified, then it is also added to the list of objectives after minimize-tours objective.

Hints

  • pay attention to the order of objectives
  • if you're using balancing objective and getting high cost or non-realistic, but balanced routes, try to add a threshold to balancing objective:
"objectives": [
    [
      {
        "type": "minimize-unassigned"
      }
    ],
    [
      {
        "type": "minimize-tours"
      }
    ],
    [
      {
        "type": "minimize-cost"
      },
      {
        "type": "balance-distance",
        "options": {
          "threshold": 0.01
        }
      }
    ]
]

Examples

Please refer to examples section to see more examples.

Routing data

In order to solve real life VRP, you need to provide routing information, such as distances and durations between all locations in the problem. Getting this data is not a part of the solver, you need to use some external service to get it. Once received, it has to be passed within VRP definition in specific routing matrix format.

When no routing matrix information supplied, the solver uses haversine distance approximation. See more information about such behavior here.

Location format

Location can be represented as one of two types:

  • location as geocoodinate
                "location": {
                  "lat": 52.52599,
                  "lng": 13.45413
                },
  • location as index reference in routing matrix
                "location": {
                  "index": 0
                },

Please note, that you cannot mix these types in one problem definition. Also routing approximation cannot be used with location indices.

Routing matrix format

In general, routing matrix has the following schema:

  • profile (required for time dependent VRP) is name of vehicle profile
  • timestamp (optional) a date in RFC3999 for which routing info is applicable. Can be used for time dependent VRP.
  • travelTimes (required) is square matrix of durations in abstract time units represented via single dimensional array
  • distances (required) is square matrix of distances in abstract distance unit represented via single dimensional array
  • errorCodes (optional): must be present if there is no route between some locations. Non-zero value signalizes about routing error.

Both durations and distances are mapped to the list of unique locations generated from the problem definition. In this list, locations are specified in the order they defined. For example, if you have two jobs with locations A and B, one vehicle type with depot location C, then you have the following location list: A,B,C. It corresponds to the matrix (durations or distances):

0ABAC
BA0BC
CACB0

where

  • 0: zero duration or distance
  • XY: distance or duration from X location to Y

As single dimensional array it looks like:

[0,AB,AC,BA,0,BC,CA,CB,0]

vrp-cli command provides a helper command to get it as well as pragmatic lib exposes method to get the list pragmatically:

vrp-cli solve pragmatic problem.json --get-locations -o locations.json

The output format is a simply array of unique geo locations:

[
  {
    "lat": 52.52599,
    "lng": 13.45413
  },
  {
    "lat": 52.5225,
    "lng": 13.4095
  },
  {
    "lat": 52.5165,
    "lng": 13.3808
  },
  {
    "lat": 52.5316,
    "lng": 13.3884
  }
]

You can use it to get a routing matrix from any of routing services of your choice, but the order in resulting matrix should be kept as expected.

Routing matrix example:

{
  "profile": "normal_car",
  "travelTimes": [
    0,
    609,
    981,
    906,
    813,
    0,
    371,
    590,
    1055,
    514,
    0,
    439,
    948,
    511,
    463,
    0
  ],
  "distances": [
    0,
    3840,
    5994,
    5333,
    4696,
    0,
    2154,
    3226,
    5763,
    2674,
    0,
    2145,
    5112,
    2470,
    2152,
    0
  ]
}

If you have already your routing matrix, you can use location indices instead of geocoordinates as described here.

Experimental

Additionally, you can use a custom type of location with type=unknown to model a zero distance/duration to any other location. This could be useful to model unknown location for vehicle start.

Routing matrix profiles

In order to solve VRP, you need to specify at least one routing matrix profile.

Usage

Routing matrix profiles are defined in fleet.profiles:

    "profiles": [
      {
        "name": "normal_car"
      }
    ]

The name must be unique for each matrix profile and it should referenced by profile.matrix property defined on vehicle:

        "profile": {
          "matrix": "normal_car"
        },

Use -m option to pass the matrix:

vrp-cli solve pragmatic problem.json -m routing_matrix.json -o solution.json

If you don't pass any routing matrix, then haversine formula is used to calculate distances between geo locations. Durations are calculated using speed value defined via speed property in each profile. It is optional, default value is 10 which corresponds to 10m/s.

Multiple profiles

In general, you're not limited to one single routing profile. You can define multiple ones and pass their matrices to the solver:

vrp-cli solve pragmatic problem.json -m routing_matrix_car.json -m routing_matrix_truck.json

Make sure that for all profile names in fleet.profiles you have the corresponding matrix specified.

See multiple profiles example.

Time dependent routing

In order to use this feature, specify more than one routing matrix for each profile with timestamp property set.

Pragmatic solution

A pragmatic solution is a result of metaheuristic work and, essentially, consists of three main parts:

  • statistic
  • list of tours
  • list of unassigned jobs

Tour list

List of tours is essentially individual vehicle routes. Each tour consists of the following properties:

  • typeId: id of vehicle type
          "typeId": "vehicle",
    
  • vehicleId: id of used vehicle. Id of the vehicle is generated from the tour using pattern $typeId_sequenceIndex:
          "vehicleId": "vehicle_1",
    
  • shiftIndex: vehicle's shift index:
          "shiftIndex": 0,
    
  • stops: list of stops. See stop structure below
  • statistic: statistic of the tour.
          "statistic": {
        "cost": 41.504842000000004,
        "distance": 13251,
        "duration": 3507,
        "times": {
          "driving": 2367,
          "serving": 1140,
          "waiting": 0,
          "break": 0,
          "commuting": 0,
          "parking": 0
        }
    

Stop structure

Stop represents a location vehicle has to visit within activities to be performed. It has the following properties:

  • location: a stop location
  • time (required): arrival and departure time from the stop
  • distance: distance traveled since departure from start location
  • load: (required) vehicle capacity after departure from the stop
  • parking (optional): parking time. Used only with vicinity clustering.
  • activities (required): list of activities to be performed at the stop. Each stop can have more than one activity. See activity structure below.

Please note, that location and distance are not required: they are omitted in case of the stop for a required break which during traveling.

Please check examples here.

Activity structure

An activity specifies work to be done and has the following structure:

  • jobId (required): id of the job or special id (departure, arrival, break, reload)
  • type (required): activity type: departure, arrival, break, reload, pickup or delivery
  • location (optional): activity location. Omitted if stop list has one activity
  • time (optional): start and end time of activity. Omitted if stop list has one activity
  • jobTag (optional): a job place tag
  • commute (optional): commute information. Used only with vicinity clustering.

Examples

An example of stop with one activity:

        {
          "location": {
            "lat": 52.5316,
            "lng": 13.3884
          },
          "time": {
            "arrival": "2019-07-04T09:00:00Z",
            "departure": "2019-07-04T09:51:29Z"
          },
          "distance": 0,
          "load": [
            1
          ],
          "activities": [
            {
              "jobId": "departure",
              "type": "departure"
            }
          ]
        },

An example of stop with two activities:

        {
          "location": {
            "lat": 52.5225,
            "lng": 13.4095
          },
          "time": {
            "arrival": "2019-07-04T10:22:26Z",
            "departure": "2019-07-04T10:31:26Z"
          },
          "distance": 8952,
          "load": [
            2
          ],
          "activities": [
            {
              "jobId": "job2",
              "type": "pickup",
              "location": {
                "lat": 52.5225,
                "lng": 13.4095
              },
              "time": {
                "start": "2019-07-04T10:22:26Z",
                "end": "2019-07-04T10:26:26Z"
              }
            },
            {
              "jobId": "job3",
              "type": "pickup",
              "location": {
                "lat": 52.5225,
                "lng": 13.4095
              },
              "time": {
                "start": "2019-07-04T10:26:26Z",
                "end": "2019-07-04T10:31:26Z"
              },
              "jobTag": "p1"
            }
          ]
        },

Statistic

A statistic entity represents total statistic for the whole solution or one tour. It has the following structure:

  • cost: a cost in abstract units
  • distance: a total distance in distance units
  • duration: a total duration in duration units
  • times: a duration split into specific groups:
    • driving: a total driving duration
    • serving: a total serving jobs duration
    • waiting: a total waiting time for time windows
    • break: a total break duration
    • commuting: a total commute duration (used only by vicinity clustering)
    • parking: a total parking time (used only by vicinity clustering)

A solution statistic example:

  "statistic": {
   "cost": 41.504842000000004,
   "distance": 13251,
   "duration": 3507,
   "times": {
     "driving": 2367,
     "serving": 1140,
     "waiting": 0,
     "break": 0,
     "commuting": 0,
     "parking": 0
   }
 },

Unassigned jobs

When job cannot be assigned, it goes to the list of unassigned jobs:

  "unassigned": [
    {
      "jobId": "job2",
      "reasons": [
        {
          "code": "TIME_WINDOW_CONSTRAINT",
          "description": "cannot be visited within time window",
          "details": [
            {
              "vehicle_id": "vehicle_1",
              "shift_index": 0
            }
          ]
        }
      ]

Each item in this list has job id, reason code, description and, optionally, some extra details like vehicle id and shift index. You will get as many reasons as tours in the solution. This information can be used to understand why the job was not added to the existing tours.

Reasons of unassigned jobs

codedescriptionpossible action
NO_REASON_FOUNDunknown
SKILL_CONSTRAINTcannot serve required skillallocate more vehicles with given skill?
TIME_WINDOW_CONSTRAINTcannot be visited within time windowallocate more vehicles, relax time windows, etc.?
CAPACITY_CONSTRAINTdoes not fit into any vehicle due to capacityallocate more vehicles?
REACHABLE_CONSTRAINTlocation unreachablechange job location to routable place?
MAX_DISTANCE_CONSTRAINTcannot be assigned due to max distance constraint of vehicleallocate more vehicles?
MAX_DURATION_CONSTRAINTcannot be assigned due to max duration constraint of vehicleallocate more vehicles?
BREAK_CONSTRAINTbreak is not assignablecorrect break location or/and time window?
LOCKING_CONSTRAINTcannot be served due to relation lockreview relations?
AREA_CONSTRAINTcannot be assigned due to area constraintmake sure that jobs inside allowed areas
TOUR_SIZE_CONSTRAINTcannot be assigned due to tour size constraint of vehiclemake sure that there are enough vehicles to serve jobs
TOUR_ORDER_CONSTRAINTcannot be assigned due to tour order constrainttour order might be too strict or not vehicles enough
GROUP_CONSTRAINTcannot be assigned due to group constrainttry to reduce amount of jobs in the group?
COMPATIBILITY_CONSTRAINTcannot be assigned due to compatibility constraintreview job's compatibilities
RELOAD_RESOURCE_CONSTRAINTcannot be assigned due to reload resource constraintreview shared resource allocation for vehicle reloads

Example

An example of problem with unassigned jobs can be found here.

Violations

Some of the constraints, specified by the problem, are considered as soft and can be violated under certain circumstances. Violations are listed in violations collection and divided in to specific groups.

Vehicle Break violation

A vehicle break is considered as soft constraint and can be violated if the solver is not able to assign it. When it is violated, the following object is returned:

{
  "type": "break",
  "vehicleId": "my_vehicle_id",
  "shiftIndex": 0
}

Error Index

This page lists errors produced by the solver.

E0xxx Error

Errors from E0xxx range are generic.

E0000

cannot deserialize problem is returned when problem definition cannot be deserialized from the input stream.

E0001

cannot deserialize matrix is returned when routing matrix definition cannot be deserialized from the input stream.

E0002

cannot create transport costs is returned when problem cannot be matched within routing matrix data passed.

There are two options to consider, when specifying routing matrix data:

  • time dependent VRP requires all matrices to have profile and timestamp properties to be se
  • time agnostic VRP requires timestamp property to be omitted, profile property either set or skipped for all matrices

E0003

cannot find any solution is returned when no solution is found. In this case, please submit a bug and share original problem and routing matrix.

E0004

cannot read config is returned when algorithm configuration cannot be created. To fix it, make sure that config has a valid json schema and valid parameters.

E1xxx: Validation errors

Errors from E1xxx range are used by validation engine which checks logical correctness of the rich VRP definition.

E11xx: Jobs

These errors are related to plan.jobs property definition.

E1100

duplicated job ids error is returned when plan.jobs has jobs with the same ids:

{
  "plan": {
    "jobs": [
      {
        "id": "job1",
        /** omitted **/
      },
      {
        /** Error: this id is already used by another job **/
        "id": "job1",
        /** omitted **/
      }
      /** omitted **/
    ]
  }
}

Duplicated job ids are not allowed, so you need to remove all duplicates in order to fix the error.

E1101

invalid job task demand error is returned when job has invalid demand: pickup, delivery, replacement job types should have demand specified on each job task, service type should have no demand specified:

{
  "id": "job1",
  "deliveries": [
    {
      /** omitted **/
      /** Error: delivery task should have demand set**/
      "demand": null
    }
 ],
 "services": [
   {
     /** omitted **/
     /** Error: service task should have no demand specified**/
     "demand": [1]
   }
 ]
}

To fix the error, make sure that each job task has proper demand.

E1102

invalid pickup and delivery demand error code is returned when job has both pickups and deliveries, but the sum of pickups demand does not match to the sum of deliveries demand:

{
  "id": "job",
  "pickups": [
    {
      "places": [/** omitted **/],
      "demand": [1],
    },
    {
      "places": [/** omitted **/],
      "demand": [1]
    }
  ],
  "deliveries": [
    {
      "places": [/** omitted **/],
      /** Error: should be 2 as the sum of pickups is 2 **/
      "demand": [1]
    }
  ]
}

E1103

invalid time windows in jobs error is returned when there is a job which has invalid time windows, e.g.:

{
  /** Error: end time is one hour earlier than start time**/
  "times": [
    [
      "2020-07-04T12:00:00Z",
      "2020-07-04T11:00:00Z"
    ]
  ]
}

Each time window must satisfy the following criteria:

  • array of two strings each of these specifies date in RFC3339 format. The first is considered as start, the second - as end
  • start date is earlier than end date
  • if multiple time windows are specified, they must not intersect, e.g.:
{
  /** Error: second time window intersects with first one: [13:00, 14:00] **/
  "times": [
    [
      "2020-07-04T10:00:00Z",
      "2020-07-04T14:00:00Z"
    ],
    [
      "2020-07-04T13:00:00Z",
      "2020-07-04T17:00:00Z"
    ]
  ]
}

E1104

reserved job id is used error is returned when there is a job which has reserved job id:

{
  /** Error: 'departure' is reserved job id **/
  "id": "departure"
}

To avoid confusion, the following ids are reserved: departure, arrival, break, and reload. These ids are not allowed to be used within job.id property.

E1105

empty job error is returned when there is a job which has no or empty job tasks:

{
  /** Error: at least one job task has to be defined **/
  "id": "job1",
  "pickups": null,
  "deliveries": []
}

To fix the error, remove job from the plan or add at least one job task to it.

E1106

job has negative duration error is returned when there is a job place with negative duration:

{
  "id": "job",
  "pickups": [
    {
      "places": [{
        /** Error: negative duration does not make sense **/
        "duration": -10,
        "location": {/* omitted */}
       }]
       /* omitted */
    }
  ]
}

To fix the error, make sure that all durations are non negative.

E1107

job has negative demand error is returned when there is a job with negative demand in any of dimensions:

{
  "id": "job",
  "pickups": [
    {
      "places": [/* omitted */],
      /** Error: negative demand is not allowed **/
      "demand": [10, -1]
    }
  ]
}

To fix the error, make sure that all demand values are non negative.

E12xx: Relations

These errors are related to plan.relations property definition.

E1200

relation has job id which does not present in the plan error is returned when plan.relations has relations with job ids, not present in plan.jobs.

E1201

relation has vehicle id which does not present in the fleet error is returned when plan.relations has relations with vehicle ids, not present in plan.fleet.

E1202

relation has empty job id list error is returned when plan.relations has relations with empty jobs list or it has only reserved ids such as departure, arrival, break, reload.

E1203

strict or sequence relation has job with multiple places or time windows error is returned when plan.relations has strict or sequence relation which refers one or many jobs with multiple places and/or time windows.

This is currently not allowed due to matching problem.

E1204

job is assigned to different vehicles in relations error is returned when plan.relations has a job assigned to several relations with different vehicle ids:

{
  "plan": {
    "relations": [
      {
        "vehicleId": "vehicle_1",
        "jobs": ["job1"],
        /** omitted **/
      },
      {
        /** Error: this job id is already assigned to another vehicle **/
        "vehicleId": "vehicle_2",
        "jobs": ["job1"],
        /** omitted **/
      }
    ]
  }
}

To fix this, remove job id from one of relations.

E1205

relation has invalid shift index error is returned when plan.relations has shiftIndex value and no corresponding shift is present in list of shifts.

E1206

relation has special job id which is not defined on vehicle shift error is returned when plan.relations has reserved job id and corresponding property on fleet.vehicles.shifts is not defined. Reserved ids are break, reload and arrival.

E1207

some relations have incomplete job definitions error is returned when plan.relations has relation with incomplete job definitions: e.g. job has two pickups, but in relation its job id is specified only once. To fix the issue, either remove job ids completely or add missing ones.

E13xx: Vehicles

These errors are related to fleet.vehicles property definition.

E1300

duplicated vehicle type ids error is returned when fleet.vehicles has vehicle types with the same typeId:

{
  "fleet": {
    "vehicles": [
      {
        "typeId": "vehicle_1",
        /** omitted **/
      },
      {
        /** Error: this id is already used by another vehicle type **/
        "typeId": "vehicle_1",
        /** omitted **/
      }
      /** omitted **/
    ]
  }
}

E1301

duplicated vehicle ids error is returned when fleet.vehicles has vehicle types with the same vehicleIds:

{
  "fleet": {
    "vehicles": [
      {
        "typeId": "vehicle_1",
        "vehicleIds": [
          "vehicle_1_a",
          "vehicle_1_b",
          /** Error: vehicle_1_b is used second time **/
          "vehicle_1_b"
        ],
        /** omitted **/
      },
      {
        "typeId": "vehicle_2",
        "vehicleIds": [
          /** Error: vehicle_1_a is used second time **/
          "vehicle_1_a",
          "vehicle_2_b"
        ],
        /** omitted **/
      }
      /** omitted **/
    ]
  }
}

Please note that vehicle id should be unique across all vehicle types.

E1302

invalid start or end times in vehicle shift error is returned when vehicle has start/end shift times violating one of time windows rules defined for jobs in E1103.

E1303

invalid break time windows in vehicle shift error is returned when vehicle has invalid time window of a break. List of break should follow time window rules defined for jobs in E1103. Additionally, break time should be inside vehicle shift it is specified:

{
  "start": {
    "time": "2019-07-04T08:00:00Z",
    /** omitted **/
  },
  "end": {
    "time": "2019-07-04T15:00:00Z",
    /** omitted **/
  },
  "breaks": [
    {
      /** Error: break is outside of vehicle shift times **/
      "times": [
        [
          "2019-07-04T17:00:00Z",
          "2019-07-04T18:00:00Z"
        ]
      ],
      "duration": 3600.0
    }
  ]
}

E1304

invalid reload time windows in vehicle shift error is returned when vehicle has invalid time window of a reload. Reload list should follow time window rules defined for jobs in E1003 except multiple reloads can have time window intersections. Additionally, reload time should be inside vehicle shift it is specified:

{
  "start": {
    "time": "2019-07-04T08:00:00Z",
    /** omitted **/
  },
  "end": {
    "time": "2019-07-04T15:00:00Z",
    /** omitted **/
  },
  "reloads": [
    {
      /** Error: reload is outside of vehicle shift times **/
      "times": [
        [
          "2019-07-04T17:00:00Z",
          "2019-07-04T18:00:00Z"
        ]
      ],
      "location": { /** omitted **/ },
      "duration": 3600.0
    }
  ]
}

E1306

time and duration costs are zeros is returned when both time and duration costs are zeros in vehicle type definition:

{
  "typeId": "vehicle",
  "vehicleIds": [
    "vehicle_1"
  ],
  "profile": {
    "matrix": "car"
  },
  "costs": {
    "fixed": 20.0,
    /** Error: distance and time are zero **/
    "distance": 0,
    "time": 0
  },
  /** omitted **/
}

You can fix the error by defining a small value (e.g. 0.0000001) for duration or time costs.

E1307

time offset interval for break is used with departure rescheduling is returned when time offset interval is specified for break, but start.latest is not set equal to start.earliest in the shift.

  {
    "start": {
      "earliest": "2019-07-04T09:00:00Z",
      /** Error: need to set latest to  "2019-07-04T09:00:00Z" explicitely **/
      "location": { "lat": 52.5316, "lng": 13.3884 }
    },
    "breaks": [{
        /** Note: offset time is used here **/
        "time": [3600, 4000],
        "places": [{ "duration": 1800 } ]
     }]
  }

Alternatively, you can switch to time window definition and keep start.latest property as you wish.

E1308

invalid vehicle reload resource is returned when:

  • fleet.resources has vehicle reloads with the same id
  • required vehicle reload is used with resource id, which is not specified in fleet.resources

E15xx: Routing profiles

These errors are related to routing locations and fleet.profiles property definitions.

E1500

duplicate profile names error is returned when fleet.profiles has more than one profile with the same name:

{
  "profiles": [
    {
      "name": "vehicle_profile",
      "type": "car"
    },
    {
      "name": "vehicle_profile",
      "type": "truck"
    }
  ]
}

To fix the issue, remove all duplicates.

E1501

empty profile collection error is returned when fleet.profiles is empty:

{
  "profiles": []
}

E1502

mixing different location types error is returned when problem contains locations in different formats. In order to fix the issue, change the problem definition to use one specific location type: index reference or geocoordinate.

E1503

location indices requires routing matrix to be specified is returned when location indices are used, but no routing matrix provided.

E1504

amount of locations does not match matrix dimension is returned when:

  • location indices are used and max index is greater than matrix size
  • amount of total locations is higher than matrix size

Check locations in problem definition and matrix size.

E1505

unknown matrix profile name in vehicle or vicinity clustering profile is returned when vehicle has in fleet.vehicles.profile.matrix or plan.clustering.profile value which is not specified in fleet.profiles collection. To fix issue, either change value to one specified or add a corresponding profile in profiles collection.

E16xx: Objectives

These errors are related to objectives property definition.

E1600

an empty objective specified error is returned when objective property is present in the problem, but no single objective is set, e.g.:

{
  "objectives": []
}

objectives property is optional, just remove it to fix the problem and use default objectives.

E1601

duplicate objective specified error is returned when objective of specific type specified more than once:

{
  "objectives": [
    [
      {
        "type": "minimize-unassigned"
      },
      {
        "type": "minimize-unassigned"
      }
    ],
    [
      {
        "type": "minimize-cost"
      }
    ]
  ]
}

To fix this issue, just remove one, e.g. minimize-unassigned.

E1602

missing one of cost objectives error is returned when no cost objective specified:

{
  "objectives": [
    [
      {
        "type": "minimize-unassigned"
      }
    ]
  ]
}

To solve it, specify one of the cost objectives: minimize-cost, minimize-distance or minimize-duration.

E1603

redundant value objective error is returned when objectives definition is overridden with maximize-value, but there is no jobs with non-zero value specified. To fix the issue, specify at least one non-zero valued job or simply delete 'maximize-value' objective.

E1604

redundant tour order objective error is returned when objectives definition is overridden with tour-order, but there is no jobs with non-zero order specified. To fix the issue, specify at least one job with non-zero order or simply delete 'tour-order' objective.

E1605

value or order of a job should be greater than zero error is returned when job's order or value is less than 1. To fix the issue, make sure that value or order of all jobs are greater than zero.

E1606

multiple cost objectives specified error is returned when more than one cost objective is specified. To fix the issue, keep only one cost objective in the list of objectives.

E1607

missing value objective error is returned when plan has jobs with value set, but user defined objective doesn't include the maximize-value objective.

Scientific formats

The project supports two text formats widely used for benchmarking various a algorithms in scientific papers:

  • Solomon: specifies CVRPTW
  • Li&Lim: specifies VRPPD
  • tsplib specifies CVRPTW

Solomon problems

To run the problem from solomon set, simply specify solomon as a type. The following command solves solomon problem defined in RC1_10_1.txt and stores solution in RC1_10_1_solution.txt:

vrp-cli solve solomon RC1_10_1.txt -o RC1_10_1_solution.txt

Optionally, you can specify initial solution to start with:

vrp-cli solve solomon RC1_10_1.txt --init-solution RC1_10_1_solution_initial.txt -o RC1_10_1_solution_improved.txt

For details see Solomon benchmark.

Li&Lim

To run the problem from Li&Lim set, simply specify lilim as a type:

vrp-cli solve lilim LC1_10_2.txt -o LC1_10_2_solution.txt

For details see Li&Lim benchmark.

TSPLIB problems

To run the problem from tsplib data set, simply specify tsplib as a type. Please note, only few features of the format are supported.

Some benchmarks can be found here.

Examples

This section contains data and code examples.

Pragmatic examples

Here you can find multiple examples how to use different features such as break, reload, multi job, etc.

Each example consists of:

  • problem definition: a json file with complete problem definition.
  • solution: a json file with one of the possible solutions

For selected examples:

  • geojson visualization with leaflet
  • ordered list of unique locations: a json file with list of locations in specific order which can be used to request a routing matrix. More info can be found here
  • routing matrix: a json file with routing matrix described here
  • CLI command command line which can be used to reproduce results locally using vrp-cli tool. Original data can be found in examples folder or copy-pasted from example pages and stored locally.

Basic usage

This section contains minimalistic examples to demonstrate how to use various features.

Basic job usage

This section demonstrates how to use different job task types.

You can find more details about job type schema on job concept section.

Basic pickup and delivery usage

In this example, there is one delivery, one pickup, and one pickup and delivery job with one dimensional demand.

Problem

{
  "plan": {
    "jobs": [
      {
        "id": "job1",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.52599,
                  "lng": 13.45413
                },
                "duration": 300.0,
                "times": [
                  [
                    "2019-07-04T09:00:00Z",
                    "2019-07-04T18:00:00Z"
                  ],
                  [
                    "2019-07-05T09:00:00Z",
                    "2019-07-05T18:00:00Z"
                  ]
                ]
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job2",
        "pickups": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5225,
                  "lng": 13.4095
                },
                "duration": 240.0,
                "times": [
                  [
                    "2019-07-04T10:00:00Z",
                    "2019-07-04T16:00:00Z"
                  ]
                ]
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job3",
        "pickups": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5225,
                  "lng": 13.4095
                },
                "duration": 300.0,
                "tag": "p1"
              }
            ],
            "demand": [
              1
            ]
          }
        ],
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5165,
                  "lng": 13.3808
                },
                "duration": 300.0,
                "tag": "d1"
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      }
    ]
  },
  "fleet": {
    "vehicles": [
      {
        "typeId": "vehicle",
        "vehicleIds": [
          "vehicle_1"
        ],
        "profile": {
          "matrix": "normal_car"
        },
        "costs": {
          "fixed": 22.0,
          "distance": 0.0002,
          "time": 0.004806
        },
        "shifts": [
          {
            "start": {
              "earliest": "2019-07-04T09:00:00Z",
              "location": {
                "lat": 52.5316,
                "lng": 13.3884
              }
            },
            "end": {
              "latest": "2019-07-04T18:00:00Z",
              "location": {
                "lat": 52.5316,
                "lng": 13.3884
              }
            }
          }
        ],
        "capacity": [
          10
        ]
      }
    ],
    "profiles": [
      {
        "name": "normal_car"
      }
    ]
  }
}

Solution

{
  "statistic": {
    "cost": 41.504842000000004,
    "distance": 13251,
    "duration": 3507,
    "times": {
      "driving": 2367,
      "serving": 1140,
      "waiting": 0,
      "break": 0,
      "commuting": 0,
      "parking": 0
    }
  },
  "tours": [
    {
      "vehicleId": "vehicle_1",
      "typeId": "vehicle",
      "shiftIndex": 0,
      "stops": [
        {
          "location": {
            "lat": 52.5316,
            "lng": 13.3884
          },
          "time": {
            "arrival": "2019-07-04T09:00:00Z",
            "departure": "2019-07-04T09:51:29Z"
          },
          "distance": 0,
          "load": [
            1
          ],
          "activities": [
            {
              "jobId": "departure",
              "type": "departure"
            }
          ]
        },
        {
          "location": {
            "lat": 52.52599,
            "lng": 13.45413
          },
          "time": {
            "arrival": "2019-07-04T10:07:17Z",
            "departure": "2019-07-04T10:12:17Z"
          },
          "distance": 5112,
          "load": [
            0
          ],
          "activities": [
            {
              "jobId": "job1",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5225,
            "lng": 13.4095
          },
          "time": {
            "arrival": "2019-07-04T10:22:26Z",
            "departure": "2019-07-04T10:31:26Z"
          },
          "distance": 8952,
          "load": [
            2
          ],
          "activities": [
            {
              "jobId": "job2",
              "type": "pickup",
              "location": {
                "lat": 52.5225,
                "lng": 13.4095
              },
              "time": {
                "start": "2019-07-04T10:22:26Z",
                "end": "2019-07-04T10:26:26Z"
              }
            },
            {
              "jobId": "job3",
              "type": "pickup",
              "location": {
                "lat": 52.5225,
                "lng": 13.4095
              },
              "time": {
                "start": "2019-07-04T10:26:26Z",
                "end": "2019-07-04T10:31:26Z"
              },
              "jobTag": "p1"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5165,
            "lng": 13.3808
          },
          "time": {
            "arrival": "2019-07-04T10:37:37Z",
            "departure": "2019-07-04T10:42:37Z"
          },
          "distance": 11106,
          "load": [
            1
          ],
          "activities": [
            {
              "jobId": "job3",
              "type": "delivery",
              "jobTag": "d1"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5316,
            "lng": 13.3884
          },
          "time": {
            "arrival": "2019-07-04T10:49:56Z",
            "departure": "2019-07-04T10:49:56Z"
          },
          "distance": 13251,
          "load": [
            0
          ],
          "activities": [
            {
              "jobId": "arrival",
              "type": "arrival"
            }
          ]
        }
      ],
      "statistic": {
        "cost": 41.504842000000004,
        "distance": 13251,
        "duration": 3507,
        "times": {
          "driving": 2367,
          "serving": 1140,
          "waiting": 0,
          "break": 0,
          "commuting": 0,
          "parking": 0
        }
      }
    }
  ]
}


As problem has two job task places with exactly same location, solution contains one stop with two activities.

Multiple pickups and deliveries

This example contains two multi jobs with slightly different parameters.

Problem

{
 "plan": {
   "jobs": [
     {
       "id": "multi_job1",
       "pickups": [
         {
           "places": [
             {
               "location": {
                 "lat": 52.5622847,
                 "lng": 13.4023099
               },
               "duration": 240.0,
               "tag": "p1"
             }
           ],
           "demand": [
             1
           ]
         },
         {
           "places": [
             {
               "location": {
                 "lat": 52.5330881,
                 "lng": 13.3973059
               },
               "duration": 240.0,
               "tag": "p2"
             }
           ],
           "demand": [
             1
           ]
         }
       ],
       "deliveries": [
         {
           "places": [
             {
               "location": {
                 "lat": 52.5252832,
                 "lng": 13.4188422
               },
               "duration": 240.0,
               "tag": "d1"
             }
           ],
           "demand": [
             2
           ]
         }
       ]
     },
     {
       "id": "multi_job2",
       "pickups": [
         {
           "places": [
             {
               "location": {
                 "lat": 52.52599,
                 "lng": 13.45413
               },
               "duration": 240.0,
               "tag": "p1"
             }
           ],
           "demand": [
             2
           ]
         }
       ],
       "deliveries": [
         {
           "places": [
             {
               "location": {
                 "lat": 52.4928,
                 "lng": 13.4597
               },
               "duration": 240.0,
               "tag": "d1"
             }
           ],
           "demand": [
             1
           ]
         },
         {
           "places": [
             {
               "location": {
                 "lat": 52.4989,
                 "lng": 13.3917
               },
               "duration": 240.0,
               "tag": "d2"
             }
           ],
           "demand": [
             1
           ]
         }
       ]
     }
   ]
 },
 "fleet": {
   "vehicles": [
     {
       "typeId": "vehicle",
       "vehicleIds": [
         "vehicle_1"
       ],
       "profile": {
         "matrix": "normal_car"
       },
       "costs": {
         "fixed": 22.0,
         "distance": 0.0002,
         "time": 0.004806
       },
       "shifts": [
         {
           "start": {
             "earliest": "2019-07-04T09:00:00Z",
             "location": {
               "lat": 52.4664257,
               "lng": 13.2812488
             }
           },
           "end": {
             "latest": "2019-07-04T18:00:00Z",
             "location": {
               "lat": 52.4664257,
               "lng": 13.2812488
             }
           }
         }
       ],
       "capacity": [
         10
       ]
     }
   ],
   "profiles": [
     {
       "name": "normal_car"
     }
   ]
 }
}

Solution

{
 "statistic": {
   "cost": 74.412666,
   "distance": 52738,
   "duration": 8711,
   "times": {
     "driving": 7271,
     "serving": 1440,
     "waiting": 0,
     "break": 0
   }
 },
 "tours": [
   {
     "vehicleId": "vehicle_1",
     "typeId": "vehicle",
     "shiftIndex": 0,
     "stops": [
       {
         "location": {
           "lat": 52.4664257,
           "lng": 13.2812488
         },
         "time": {
           "arrival": "2019-07-04T09:00:00Z",
           "departure": "2019-07-04T09:00:00Z"
         },
         "distance": 0,
         "load": [
           0
         ],
         "activities": [
           {
             "jobId": "departure",
             "type": "departure"
           }
         ]
       },
       {
         "location": {
           "lat": 52.5622847,
           "lng": 13.4023099
         },
         "time": {
           "arrival": "2019-07-04T09:32:24Z",
           "departure": "2019-07-04T09:36:24Z"
         },
         "distance": 17547,
         "load": [
           1
         ],
         "activities": [
           {
             "jobId": "multi_job1",
             "type": "pickup",
             "tag": "p1"
           }
         ]
       },
       {
         "location": {
           "lat": 52.5330881,
           "lng": 13.3973059
         },
         "time": {
           "arrival": "2019-07-04T09:49:48Z",
           "departure": "2019-07-04T09:53:48Z"
         },
         "distance": 22014,
         "load": [
           2
         ],
         "activities": [
           {
             "jobId": "multi_job1",
             "type": "pickup",
             "tag": "p2"
           }
         ]
       },
       {
         "location": {
           "lat": 52.5252832,
           "lng": 13.4188422
         },
         "time": {
           "arrival": "2019-07-04T10:00:48Z",
           "departure": "2019-07-04T10:04:48Z"
         },
         "distance": 23849,
         "load": [
           0
         ],
         "activities": [
           {
             "jobId": "multi_job1",
             "type": "delivery",
             "tag": "d1"
           }
         ]
       },
       {
         "location": {
           "lat": 52.52599,
           "lng": 13.45413
         },
         "time": {
           "arrival": "2019-07-04T10:12:17Z",
           "departure": "2019-07-04T10:16:17Z"
         },
         "distance": 26552,
         "load": [
           2
         ],
         "activities": [
           {
             "jobId": "multi_job2",
             "type": "pickup",
             "tag": "p1"
           }
         ]
       },
       {
         "location": {
           "lat": 52.4928,
           "lng": 13.4597
         },
         "time": {
           "arrival": "2019-07-04T10:38:25Z",
           "departure": "2019-07-04T10:42:25Z"
         },
         "distance": 35242,
         "load": [
           1
         ],
         "activities": [
           {
             "jobId": "multi_job2",
             "type": "delivery",
             "tag": "d1"
           }
         ]
       },
       {
         "location": {
           "lat": 52.4989,
           "lng": 13.3917
         },
         "time": {
           "arrival": "2019-07-04T10:57:57Z",
           "departure": "2019-07-04T11:01:57Z"
         },
         "distance": 40617,
         "load": [
           0
         ],
         "activities": [
           {
             "jobId": "multi_job2",
             "type": "delivery",
             "tag": "d2"
           }
         ]
       },
       {
         "location": {
           "lat": 52.4664257,
           "lng": 13.2812488
         },
         "time": {
           "arrival": "2019-07-04T11:25:11Z",
           "departure": "2019-07-04T11:25:11Z"
         },
         "distance": 52738,
         "load": [
           0
         ],
         "activities": [
           {
             "jobId": "arrival",
             "type": "arrival"
           }
         ]
       }
     ],
     "statistic": {
       "cost": 74.412666,
       "distance": 52738,
       "duration": 8711,
       "times": {
         "driving": 7271,
         "serving": 1440,
         "waiting": 0,
         "break": 0
       }
     }
   }
 ],
 "unassigned": []
}

Mixing job task types

You can mix job task types in one job:

Problem

{
 "plan": {
   "jobs": [
     {
       "id": "simple_replacement_job",
       "replacements": [
         {
           "places": [
             {
               "location": {
                 "lat": 52.5622847,
                 "lng": 13.4023099
               },
               "duration": 3600.0,
               "times": [
                 [
                   "2019-07-04T09:00:00Z",
                   "2019-07-04T18:00:00Z"
                 ]
               ]
             }
           ],
           "demand": [
             3
           ]
         }
       ]
     },
     {
       "id": "simple_service_job",
       "services": [
         {
           "places": [
             {
               "location": {
                 "lat": 52.5330881,
                 "lng": 13.3973059
               },
               "duration": 3600.0,
               "times": [
                 [
                   "2019-07-04T08:00:00Z",
                   "2019-07-04T12:00:00Z"
                 ],
                 [
                   "2019-07-04T14:00:00Z",
                   "2019-07-04T18:00:00Z"
                 ]
               ]
             }
           ]
         }
       ]
     },
     {
       "id": "mixed_job",
       "pickups": [
         {
           "places": [
             {
               "location": {
                 "lat": 52.5252832,
                 "lng": 13.4188422
               },
               "duration": 240.0,
               "tag": "p1"
             }
           ],
           "demand": [
             1
           ]
         }
       ],
       "deliveries": [
         {
           "places": [
             {
               "location": {
                 "lat": 52.52599,
                 "lng": 13.45413
               },
               "duration": 240.0,
               "tag": "d1"
             }
           ],
           "demand": [
             1
           ]
         }
       ],
       "replacements": [
         {
           "places": [
             {
               "location": {
                 "lat": 52.4928,
                 "lng": 13.4597
               },
               "duration": 2400.0,
               "tag": "r1"
             }
           ],
           "demand": [
             2
           ]
         }
       ],
       "services": [
         {
           "places": [
             {
               "location": {
                 "lat": 52.4989,
                 "lng": 13.3917
               },
               "duration": 1800.0,
               "tag": "s1"
             }
           ]
         }
       ]
     }
   ]
 },
 "fleet": {
   "vehicles": [
     {
       "typeId": "vehicle",
       "vehicleIds": [
         "vehicle_1"
       ],
       "profile": {
         "matrix": "normal_car"
       },
       "costs": {
         "fixed": 22.0,
         "distance": 0.0002,
         "time": 0.004806
       },
       "shifts": [
         {
           "start": {
             "earliest": "2019-07-04T09:00:00Z",
             "location": {
               "lat": 52.4664257,
               "lng": 13.2812488
             }
           },
           "end": {
             "latest": "2019-07-04T18:00:00Z",
             "location": {
               "lat": 52.4664257,
               "lng": 13.2812488
             }
           }
         }
       ],
       "capacity": [
         10
       ]
     }
   ],
   "profiles": [
     {
       "name": "normal_car"
     }
   ]
 }
}

Solution

{
 "statistic": {
   "cost": 124.587306,
   "distance": 52738,
   "duration": 19151,
   "times": {
     "driving": 7271,
     "serving": 11880,
     "waiting": 0,
     "break": 0
   }
 },
 "tours": [
   {
     "vehicleId": "vehicle_1",
     "typeId": "vehicle",
     "shiftIndex": 0,
     "stops": [
       {
         "location": {
           "lat": 52.4664257,
           "lng": 13.2812488
         },
         "time": {
           "arrival": "2019-07-04T09:00:00Z",
           "departure": "2019-07-04T09:00:00Z"
         },
         "distance": 0,
         "load": [
           5
         ],
         "activities": [
           {
             "jobId": "departure",
             "type": "departure"
           }
         ]
       },
       {
         "location": {
           "lat": 52.5622847,
           "lng": 13.4023099
         },
         "time": {
           "arrival": "2019-07-04T09:32:24Z",
           "departure": "2019-07-04T10:32:24Z"
         },
         "distance": 17547,
         "load": [
           5
         ],
         "activities": [
           {
             "jobId": "simple_replacement_job",
             "type": "replacement"
           }
         ]
       },
       {
         "location": {
           "lat": 52.5330881,
           "lng": 13.3973059
         },
         "time": {
           "arrival": "2019-07-04T10:45:48Z",
           "departure": "2019-07-04T11:45:48Z"
         },
         "distance": 22014,
         "load": [
           5
         ],
         "activities": [
           {
             "jobId": "simple_service_job",
             "type": "service"
           }
         ]
       },
       {
         "location": {
           "lat": 52.5252832,
           "lng": 13.4188422
         },
         "time": {
           "arrival": "2019-07-04T11:52:48Z",
           "departure": "2019-07-04T11:56:48Z"
         },
         "distance": 23849,
         "load": [
           6
         ],
         "activities": [
           {
             "jobId": "mixed_job",
             "type": "pickup",
             "tag": "p1"
           }
         ]
       },
       {
         "location": {
           "lat": 52.52599,
           "lng": 13.45413
         },
         "time": {
           "arrival": "2019-07-04T12:04:17Z",
           "departure": "2019-07-04T12:08:17Z"
         },
         "distance": 26552,
         "load": [
           5
         ],
         "activities": [
           {
             "jobId": "mixed_job",
             "type": "delivery",
             "tag": "d1"
           }
         ]
       },
       {
         "location": {
           "lat": 52.4928,
           "lng": 13.4597
         },
         "time": {
           "arrival": "2019-07-04T12:30:25Z",
           "departure": "2019-07-04T13:10:25Z"
         },
         "distance": 35242,
         "load": [
           5
         ],
         "activities": [
           {
             "jobId": "mixed_job",
             "type": "replacement",
             "tag": "r1"
           }
         ]
       },
       {
         "location": {
           "lat": 52.4989,
           "lng": 13.3917
         },
         "time": {
           "arrival": "2019-07-04T13:25:57Z",
           "departure": "2019-07-04T13:55:57Z"
         },
         "distance": 40617,
         "load": [
           5
         ],
         "activities": [
           {
             "jobId": "mixed_job",
             "type": "service",
             "tag": "s1"
           }
         ]
       },
       {
         "location": {
           "lat": 52.4664257,
           "lng": 13.2812488
         },
         "time": {
           "arrival": "2019-07-04T14:19:11Z",
           "departure": "2019-07-04T14:19:11Z"
         },
         "distance": 52738,
         "load": [
           0
         ],
         "activities": [
           {
             "jobId": "arrival",
             "type": "arrival"
           }
         ]
       }
     ],
     "statistic": {
       "cost": 124.587306,
       "distance": 52738,
       "duration": 19151,
       "times": {
         "driving": 7271,
         "serving": 11880,
         "waiting": 0,
         "break": 0
       }
     }
   }
 ],
 "unassigned": []
}

Job priorities

There are two types of job priorities:

  • assignment priority: the solver tries to avoid such jobs to be unassigned by maximizing their total value. Assignment priority is modeled by value property on the job and used within the maximize-value objective.
  • order priority: the solver tries to assign such jobs prior others, close to beginning of the route. Order priority is modeled by order property on the job.

Basic job value example

The example below demonstrates how to use assignment priority by defining value on the jobs. The source problem has a single vehicle with limited capacity, therefore one job has to be unassigned. The solver is forced to skip the cheapest one as it has no value associated with it.

Please note, that there is no need to redefine objective to include maximize-value one as it will be added automatically on top of default.

Problem

{
  "plan": {
    "jobs": [
      {
        "id": "job1",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.52599,
                  "lng": 13.45413
                },
                "duration": 300
              }
            ],
            "demand": [
              1
            ]
          }
        ],
        "value": 50
      },
      {
        "id": "job2",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5225,
                  "lng": 13.4095
                },
                "duration": 240
              }
            ],
            "demand": [
              1
            ]
          }
        ],
        "value": 100
      },
      {
        "id": "job3",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5320,
                  "lng": 13.3950
                },
                "duration": 60
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      }
    ]
  },
  "fleet": {
    "vehicles": [
      {
        "typeId": "vehicle",
        "vehicleIds": [
          "vehicle_1"
        ],
        "profile": {
          "matrix": "normal_car"
        },
        "costs": {
          "fixed": 22.0,
          "distance": 0.0002,
          "time": 0.005
        },
        "shifts": [
          {
            "start": {
              "earliest": "2019-07-04T09:00:00Z",
              "location": {
                "lat": 52.5316,
                "lng": 13.3884
              }
            }
          }
        ],
        "capacity": [
          2
        ]
      }
    ],
    "profiles": [
      {
        "name": "normal_car"
      }
    ]
  }
}

Solution

{
  "statistic": {
    "cost": 28.060000000000002,
    "distance": 4800,
    "duration": 1020,
    "times": {
      "driving": 480,
      "serving": 540,
      "waiting": 0,
      "break": 0
    }
  },
  "tours": [
    {
      "vehicleId": "vehicle_1",
      "typeId": "vehicle",
      "shiftIndex": 0,
      "stops": [
        {
          "location": {
            "lat": 52.5316,
            "lng": 13.3884
          },
          "time": {
            "arrival": "2019-07-04T09:00:00Z",
            "departure": "2019-07-04T09:00:00Z"
          },
          "distance": 0,
          "load": [
            2
          ],
          "activities": [
            {
              "jobId": "departure",
              "type": "departure"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5225,
            "lng": 13.4095
          },
          "time": {
            "arrival": "2019-07-04T09:02:55Z",
            "departure": "2019-07-04T09:06:55Z"
          },
          "distance": 1752,
          "load": [
            1
          ],
          "activities": [
            {
              "jobId": "job2",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.52599,
            "lng": 13.45413
          },
          "time": {
            "arrival": "2019-07-04T09:12:00Z",
            "departure": "2019-07-04T09:17:00Z"
          },
          "distance": 4800,
          "load": [
            0
          ],
          "activities": [
            {
              "jobId": "job1",
              "type": "delivery"
            }
          ]
        }
      ],
      "statistic": {
        "cost": 28.060000000000002,
        "distance": 4800,
        "duration": 1020,
        "times": {
          "driving": 480,
          "serving": 540,
          "waiting": 0,
          "break": 0
        }
      }
    }
  ],
  "unassigned": [
    {
      "jobId": "job3",
      "reasons": [
        {
          "code": "CAPACITY_CONSTRAINT",
          "description": "does not fit into any vehicle due to capacity"
        }
      ]
    }
  ]
}


Multi day/shift

This example demonstrates how to simulate multi day/shift planning scenario. The problem has jobs with time windows of different days and one vehicle type with two shifts on different days.

Problem

{
  "plan": {
    "jobs": [
      {
        "id": "job1",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.52599,
                  "lng": 13.45413
                },
                "duration": 300.0,
                "times": [
                  [
                    "2019-07-04T09:00:00Z",
                    "2019-07-04T18:00:00Z"
                  ],
                  [
                    "2019-07-05T09:00:00Z",
                    "2019-07-05T18:00:00Z"
                  ]
                ]
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job2",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5225,
                  "lng": 13.4095
                },
                "duration": 240.0,
                "times": [
                  [
                    "2019-07-04T10:00:00Z",
                    "2019-07-04T16:00:00Z"
                  ]
                ]
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job3",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5165,
                  "lng": 13.3808
                },
                "duration": 300.0,
                "times": [
                  [
                    "2019-07-04T10:00:00Z",
                    "2019-07-04T16:00:00Z"
                  ]
                ]
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job4",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5145,
                  "lng": 13.3513
                },
                "duration": 300.0,
                "times": [
                  [
                    "2019-07-05T10:00:00Z",
                    "2019-07-05T16:00:00Z"
                  ]
                ]
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      }
    ]
  },
  "fleet": {
    "vehicles": [
      {
        "typeId": "vehicle",
        "vehicleIds": [
          "vehicle_1"
        ],
        "profile": {
          "matrix": "normal_car"
        },
        "costs": {
          "fixed": 22.0,
          "distance": 0.0002,
          "time": 0.004806
        },
        "shifts": [
          {
            "start": {
              "earliest": "2019-07-04T09:00:00Z",
              "location": {
                "lat": 52.5316,
                "lng": 13.3884
              }
            },
            "end": {
              "latest": "2019-07-04T18:00:00Z",
              "location": {
                "lat": 52.5316,
                "lng": 13.3884
              }
            }
          },
          {
            "start": {
              "earliest": "2019-07-05T09:00:00Z",
              "location": {
                "lat": 52.5316,
                "lng": 13.3884
              }
            },
            "end": {
              "latest": "2019-07-05T18:00:00Z",
              "location": {
                "lat": 52.5316,
                "lng": 13.3884
              }
            }
          }
        ],
        "capacity": [
          2
        ]
      }
    ],
    "profiles": [
      {
        "name": "normal_car"
      }
    ]
  }
}

Solution

{
  "statistic": {
    "cost": 75.30316200000001,
    "distance": 26105,
    "duration": 5427,
    "times": {
      "driving": 4287,
      "serving": 1140,
      "waiting": 0,
      "break": 0
    }
  },
  "tours": [
    {
      "vehicleId": "vehicle_1",
      "typeId": "vehicle",
      "shiftIndex": 1,
      "stops": [
        {
          "location": {
            "lat": 52.5316,
            "lng": 13.3884
          },
          "time": {
            "arrival": "2019-07-05T09:00:00Z",
            "departure": "2019-07-05T09:48:41Z"
          },
          "distance": 0,
          "load": [
            2
          ],
          "activities": [
            {
              "jobId": "departure",
              "type": "departure"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5145,
            "lng": 13.3513
          },
          "time": {
            "arrival": "2019-07-05T10:00:00Z",
            "departure": "2019-07-05T10:05:00Z"
          },
          "distance": 5245,
          "load": [
            1
          ],
          "activities": [
            {
              "jobId": "job4",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.52599,
            "lng": 13.45413
          },
          "time": {
            "arrival": "2019-07-05T10:26:14Z",
            "departure": "2019-07-05T10:31:14Z"
          },
          "distance": 14053,
          "load": [
            0
          ],
          "activities": [
            {
              "jobId": "job1",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5316,
            "lng": 13.3884
          },
          "time": {
            "arrival": "2019-07-05T10:47:22Z",
            "departure": "2019-07-05T10:47:22Z"
          },
          "distance": 19336,
          "load": [
            0
          ],
          "activities": [
            {
              "jobId": "arrival",
              "type": "arrival"
            }
          ]
        }
      ],
      "statistic": {
        "cost": 42.789126,
        "distance": 19336,
        "duration": 3521,
        "times": {
          "driving": 2921,
          "serving": 600,
          "waiting": 0,
          "break": 0
        }
      }
    },
    {
      "vehicleId": "vehicle_1",
      "typeId": "vehicle",
      "shiftIndex": 0,
      "stops": [
        {
          "location": {
            "lat": 52.5316,
            "lng": 13.3884
          },
          "time": {
            "arrival": "2019-07-04T09:00:00Z",
            "departure": "2019-07-04T09:51:52Z"
          },
          "distance": 0,
          "load": [
            2
          ],
          "activities": [
            {
              "jobId": "departure",
              "type": "departure"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5225,
            "lng": 13.4095
          },
          "time": {
            "arrival": "2019-07-04T10:00:00Z",
            "departure": "2019-07-04T10:04:00Z"
          },
          "distance": 2470,
          "load": [
            1
          ],
          "activities": [
            {
              "jobId": "job2",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5165,
            "lng": 13.3808
          },
          "time": {
            "arrival": "2019-07-04T10:10:58Z",
            "departure": "2019-07-04T10:15:58Z"
          },
          "distance": 4624,
          "load": [
            0
          ],
          "activities": [
            {
              "jobId": "job3",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5316,
            "lng": 13.3884
          },
          "time": {
            "arrival": "2019-07-04T10:23:38Z",
            "departure": "2019-07-04T10:23:38Z"
          },
          "distance": 6769,
          "load": [
            0
          ],
          "activities": [
            {
              "jobId": "arrival",
              "type": "arrival"
            }
          ]
        }
      ],
      "statistic": {
        "cost": 32.514036000000004,
        "distance": 6769,
        "duration": 1906,
        "times": {
          "driving": 1366,
          "serving": 540,
          "waiting": 0,
          "break": 0
        }
      }
    }
  ],
  "unassigned": []
}

Vehicle break

In general, there are two break types: optional and required.

Optional break

This example demonstrates how to use optional vehicle break with time window and omitted location.

Problem

{
  "plan": {
    "jobs": [
      {
        "id": "job1",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.52599,
                  "lng": 13.45413
                },
                "duration": 300.0,
                "times": [
                  [
                    "2019-07-04T09:00:00Z",
                    "2019-07-04T12:00:00Z"
                  ]
                ]
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job2",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5225,
                  "lng": 13.4095
                },
                "duration": 240.0,
                "times": [
                  [
                    "2019-07-04T12:00:00Z",
                    "2019-07-04T14:00:00Z"
                  ]
                ]
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job3",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5165,
                  "lng": 13.3808
                },
                "duration": 300.0,
                "times": [
                  [
                    "2019-07-04T16:00:00Z",
                    "2019-07-04T18:00:00Z"
                  ]
                ]
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      }
    ]
  },
  "fleet": {
    "vehicles": [
      {
        "typeId": "vehicle",
        "vehicleIds": [
          "vehicle_1"
        ],
        "profile": {
          "matrix": "normal_car"
        },
        "costs": {
          "fixed": 22.0,
          "distance": 0.0002,
          "time": 0.004806
        },
        "shifts": [
          {
            "start": {
              "earliest": "2019-07-04T09:00:00Z",
              "location": {
                "lat": 52.5316,
                "lng": 13.3884
              }
            },
            "end": {
              "latest": "2019-07-04T18:00:00Z",
              "location": {
                "lat": 52.5316,
                "lng": 13.3884
              }
            },
            "breaks": [
              {
                "time": [
                  "2019-07-04T12:00:00Z",
                  "2019-07-04T14:00:00Z"
                ],
                "places": [
                  {
                    "duration": 3600.0
                  }
                ]
              }
            ]
          }
        ],
        "capacity": [
          10
        ]
      }
    ],
    "profiles": [
      {
        "name": "normal_car"
      }
    ]
  }
}

Solution

{
  "statistic": {
    "cost": 101.964322,
    "distance": 13251,
    "duration": 16087,
    "times": {
      "driving": 2367,
      "serving": 840,
      "waiting": 9280,
      "break": 3600
    }
  },
  "tours": [
    {
      "vehicleId": "vehicle_1",
      "typeId": "vehicle",
      "shiftIndex": 0,
      "stops": [
        {
          "location": {
            "lat": 52.5316,
            "lng": 13.3884
          },
          "time": {
            "arrival": "2019-07-04T09:00:00Z",
            "departure": "2019-07-04T11:44:12Z"
          },
          "distance": 0,
          "load": [
            3
          ],
          "activities": [
            {
              "jobId": "departure",
              "type": "departure"
            }
          ]
        },
        {
          "location": {
            "lat": 52.52599,
            "lng": 13.45413
          },
          "time": {
            "arrival": "2019-07-04T12:00:00Z",
            "departure": "2019-07-04T13:05:00Z"
          },
          "distance": 5112,
          "load": [
            2
          ],
          "activities": [
            {
              "jobId": "job1",
              "type": "delivery",
              "location": {
                "lat": 52.52599,
                "lng": 13.45413
              },
              "time": {
                "start": "2019-07-04T12:00:00Z",
                "end": "2019-07-04T12:05:00Z"
              }
            },
            {
              "jobId": "break",
              "type": "break",
              "location": {
                "lat": 52.52599,
                "lng": 13.45413
              },
              "time": {
                "start": "2019-07-04T12:05:00Z",
                "end": "2019-07-04T13:05:00Z"
              }
            }
          ]
        },
        {
          "location": {
            "lat": 52.5225,
            "lng": 13.4095
          },
          "time": {
            "arrival": "2019-07-04T13:15:09Z",
            "departure": "2019-07-04T13:19:09Z"
          },
          "distance": 8952,
          "load": [
            1
          ],
          "activities": [
            {
              "jobId": "job2",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5165,
            "lng": 13.3808
          },
          "time": {
            "arrival": "2019-07-04T13:25:20Z",
            "departure": "2019-07-04T16:05:00Z"
          },
          "distance": 11106,
          "load": [
            0
          ],
          "activities": [
            {
              "jobId": "job3",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5316,
            "lng": 13.3884
          },
          "time": {
            "arrival": "2019-07-04T16:12:19Z",
            "departure": "2019-07-04T16:12:19Z"
          },
          "distance": 13251,
          "load": [
            0
          ],
          "activities": [
            {
              "jobId": "arrival",
              "type": "arrival"
            }
          ]
        }
      ],
      "statistic": {
        "cost": 101.964322,
        "distance": 13251,
        "duration": 16087,
        "times": {
          "driving": 2367,
          "serving": 840,
          "waiting": 9280,
          "break": 3600
        }
      }
    }
  ],
  "unassigned": []
}

Required break (experimental)

This example demonstrates how to use required vehicle break which has to be scheduled at specific time during travel between two stops.

Problem

{
  "plan": {
    "jobs": [
      {
        "id": "job1",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 51.06251,
                  "lng": 13.72133
                },
                "duration": 300
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      }
    ]
  },
  "fleet": {
    "vehicles": [
      {
        "typeId": "vehicle",
        "vehicleIds": [
          "vehicle_1"
        ],
        "profile": {
          "matrix": "normal_car"
        },
        "costs": {
          "fixed": 22,
          "distance": 0.0002,
          "time": 0.005
        },
        "shifts": [
          {
            "start": {
              "earliest": "2019-07-04T09:00:00Z",
              "latest": "2019-07-04T09:00:00Z",
              "location": {
                "lat": 52.5316,
                "lng": 13.3884
              }
            },
            "breaks": [
              {
                "time": {
                  "earliest": 7200,
                  "latest": 7200
                },
                "duration": 1800
              }
            ]
          }
        ],
        "capacity": [
          10
        ]
      }
    ],
    "profiles": [
      {
        "name": "normal_car"
      }
    ]
  }
}

Solution

{
  "statistic": {
    "cost": 148.0972,
    "distance": 165136,
    "duration": 18614,
    "times": {
      "driving": 16514,
      "serving": 300,
      "waiting": 0,
      "break": 1800,
      "commuting": 0,
      "parking": 0
    }
  },
  "tours": [
    {
      "vehicleId": "vehicle_1",
      "typeId": "vehicle",
      "shiftIndex": 0,
      "stops": [
        {
          "location": {
            "lat": 52.5316,
            "lng": 13.3884
          },
          "time": {
            "arrival": "2019-07-04T09:00:00Z",
            "departure": "2019-07-04T09:00:00Z"
          },
          "distance": 0,
          "load": [
            1
          ],
          "activities": [
            {
              "jobId": "departure",
              "type": "departure"
            }
          ]
        },
        {
          "time": {
            "arrival": "2019-07-04T11:00:00Z",
            "departure": "2019-07-04T11:30:00Z"
          },
          "load": [
            1
          ],
          "activities": [
            {
              "jobId": "break",
              "type": "break"
            }
          ]
        },
        {
          "location": {
            "lat": 51.06251,
            "lng": 13.72133
          },
          "time": {
            "arrival": "2019-07-04T14:05:14Z",
            "departure": "2019-07-04T14:10:14Z"
          },
          "distance": 165136,
          "load": [
            0
          ],
          "activities": [
            {
              "jobId": "job1",
              "type": "delivery"
            }
          ]
        }
      ],
      "statistic": {
        "cost": 148.0972,
        "distance": 165136,
        "duration": 18614,
        "times": {
          "driving": 16514,
          "serving": 300,
          "waiting": 0,
          "break": 1800,
          "commuting": 0,
          "parking": 0
        }
      }
    }
  ]
}

Please note, that departure rescheduling is disabled by setting shift.start.earliest equal to shift.start.latest. At the moment, this is a hard requirement when such break type is used.

Multiple trips

These examples demonstrate how to use vehicle reload feature which is designed to overcome vehicle capacity limitation in order to perform multiple trips (tours).

Essentially, reload is a place where vehicle can unload static pickups and load new static deliveries. Here, static correspond to static demand concept which is defined via standalone pickup or delivery jobs, not by single pickup and delivery job.

Same location reload

In this scenario, once some jobs are delivered, the vehicle returns to the original depot to load next goods.

Problem

{
  "plan": {
    "jobs": [
      {
        "id": "job1",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.52599,
                  "lng": 13.45413
                },
                "duration": 300.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job2",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5225,
                  "lng": 13.4095
                },
                "duration": 240.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job3",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5165,
                  "lng": 13.3808
                },
                "duration": 300.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job4",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5145,
                  "lng": 13.3513
                },
                "duration": 300.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      }
    ]
  },
  "fleet": {
    "vehicles": [
      {
        "typeId": "vehicle",
        "vehicleIds": [
          "vehicle_1"
        ],
        "profile": {
          "matrix": "normal_car"
        },
        "costs": {
          "fixed": 22.0,
          "distance": 0.0002,
          "time": 0.004806
        },
        "shifts": [
          {
            "start": {
              "earliest": "2019-07-04T09:00:00Z",
              "location": {
                "lat": 52.5316,
                "lng": 13.3884
              }
            },
            "end": {
              "latest": "2019-07-04T18:00:00Z",
              "location": {
                "lat": 52.5316,
                "lng": 13.3884
              }
            },
            "reloads": [
              {
                "location": {
                  "lat": 52.5316,
                  "lng": 13.3884
                },
                "duration": 600.0
              }
            ]
          }
        ],
        "capacity": [
          2
        ]
      }
    ],
    "profiles": [
      {
        "name": "normal_car"
      }
    ]
  }
}

Solution

{
  "statistic": {
    "cost": 52.651224000000006,
    "distance": 20995,
    "duration": 5504,
    "times": {
      "driving": 3764,
      "serving": 1740,
      "waiting": 0,
      "break": 0
    }
  },
  "tours": [
    {
      "vehicleId": "vehicle_1",
      "typeId": "vehicle",
      "shiftIndex": 0,
      "stops": [
        {
          "location": {
            "lat": 52.5316,
            "lng": 13.3884
          },
          "time": {
            "arrival": "2019-07-04T09:00:00Z",
            "departure": "2019-07-04T09:00:00Z"
          },
          "distance": 0,
          "load": [
            2
          ],
          "activities": [
            {
              "jobId": "departure",
              "type": "departure"
            }
          ]
        },
        {
          "location": {
            "lat": 52.52599,
            "lng": 13.45413
          },
          "time": {
            "arrival": "2019-07-04T09:14:49Z",
            "departure": "2019-07-04T09:19:49Z"
          },
          "distance": 5090,
          "load": [
            1
          ],
          "activities": [
            {
              "jobId": "job1",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5225,
            "lng": 13.4095
          },
          "time": {
            "arrival": "2019-07-04T09:31:22Z",
            "departure": "2019-07-04T09:35:22Z"
          },
          "distance": 8930,
          "load": [
            0
          ],
          "activities": [
            {
              "jobId": "job2",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5316,
            "lng": 13.3884
          },
          "time": {
            "arrival": "2019-07-04T09:45:02Z",
            "departure": "2019-07-04T09:55:02Z"
          },
          "distance": 12156,
          "load": [
            2
          ],
          "activities": [
            {
              "jobId": "reload",
              "type": "reload"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5165,
            "lng": 13.3808
          },
          "time": {
            "arrival": "2019-07-04T10:02:31Z",
            "departure": "2019-07-04T10:07:31Z"
          },
          "distance": 14308,
          "load": [
            1
          ],
          "activities": [
            {
              "jobId": "job3",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5145,
            "lng": 13.3513
          },
          "time": {
            "arrival": "2019-07-04T10:15:03Z",
            "departure": "2019-07-04T10:20:03Z"
          },
          "distance": 17092,
          "load": [
            0
          ],
          "activities": [
            {
              "jobId": "job4",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5316,
            "lng": 13.3884
          },
          "time": {
            "arrival": "2019-07-04T10:31:44Z",
            "departure": "2019-07-04T10:31:44Z"
          },
          "distance": 20995,
          "load": [
            0
          ],
          "activities": [
            {
              "jobId": "arrival",
              "type": "arrival"
            }
          ]
        }
      ],
      "statistic": {
        "cost": 52.651224000000006,
        "distance": 20995,
        "duration": 5504,
        "times": {
          "driving": 3764,
          "serving": 1740,
          "waiting": 0,
          "break": 0
        }
      }
    }
  ],
  "unassigned": []
}

Multiple reloads with different locations

In this scenario, vehicle picks goods and flushes them on two different locations during single tour. This can be used to model waste collection use case.

Problem

{
  "plan": {
    "jobs": [
      {
        "id": "job1",
        "pickups": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.52599,
                  "lng": 13.45413
                },
                "duration": 300.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job2",
        "pickups": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5225,
                  "lng": 13.4095
                },
                "duration": 240.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job3",
        "pickups": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5165,
                  "lng": 13.3808
                },
                "duration": 300.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job4",
        "pickups": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5145,
                  "lng": 13.3513
                },
                "duration": 300.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job5",
        "pickups": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.4922,
                  "lng": 13.4593
                },
                "duration": 300.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job6",
        "pickups": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.4989,
                  "lng": 13.3917
                },
                "duration": 300.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      }
    ]
  },
  "fleet": {
    "vehicles": [
      {
        "typeId": "vehicle",
        "vehicleIds": [
          "vehicle_1"
        ],
        "profile": {
          "matrix": "normal_car"
        },
        "costs": {
          "fixed": 22.0,
          "distance": 0.0002,
          "time": 0.004806
        },
        "shifts": [
          {
            "start": {
              "earliest": "2019-07-04T09:00:00Z",
              "location": {
                "lat": 52.5316,
                "lng": 13.3884
              }
            },
            "end": {
              "latest": "2019-07-04T18:00:00Z",
              "location": {
                "lat": 52.5316,
                "lng": 13.3884
              }
            },
            "reloads": [
              {
                "location": {
                  "lat": 52.5103,
                  "lng": 13.3898
                },
                "duration": 600.0
              },
              {
                "location": {
                  "lat": 52.5043,
                  "lng": 13.4091
                },
                "duration": 600.0
              }
            ]
          }
        ],
        "capacity": [
          2
        ]
      }
    ],
    "profiles": [
      {
        "name": "normal_car"
      }
    ]
  }
}

Solution

{
  "statistic": {
    "cost": 52.38156800000001,
    "distance": 23876,
    "duration": 5328,
    "times": {
      "driving": 2388,
      "serving": 2940,
      "waiting": 0,
      "break": 0
    }
  },
  "tours": [
    {
      "vehicleId": "vehicle_1",
      "typeId": "vehicle",
      "shiftIndex": 0,
      "stops": [
        {
          "location": {
            "lat": 52.5316,
            "lng": 13.3884
          },
          "time": {
            "arrival": "2019-07-04T09:00:00Z",
            "departure": "2019-07-04T09:00:00Z"
          },
          "distance": 0,
          "load": [
            0
          ],
          "activities": [
            {
              "jobId": "departure",
              "type": "departure"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5225,
            "lng": 13.4095
          },
          "time": {
            "arrival": "2019-07-04T09:02:55Z",
            "departure": "2019-07-04T09:06:55Z"
          },
          "distance": 1752,
          "load": [
            1
          ],
          "activities": [
            {
              "jobId": "job2",
              "type": "pickup"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5165,
            "lng": 13.3808
          },
          "time": {
            "arrival": "2019-07-04T09:10:21Z",
            "departure": "2019-07-04T09:15:21Z"
          },
          "distance": 3808,
          "load": [
            2
          ],
          "activities": [
            {
              "jobId": "job3",
              "type": "pickup"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5103,
            "lng": 13.3898
          },
          "time": {
            "arrival": "2019-07-04T09:16:53Z",
            "departure": "2019-07-04T09:26:53Z"
          },
          "distance": 4729,
          "load": [
            0
          ],
          "activities": [
            {
              "jobId": "reload",
              "type": "reload"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5145,
            "lng": 13.3513
          },
          "time": {
            "arrival": "2019-07-04T09:31:18Z",
            "departure": "2019-07-04T09:36:18Z"
          },
          "distance": 7379,
          "load": [
            1
          ],
          "activities": [
            {
              "jobId": "job4",
              "type": "pickup"
            }
          ]
        },
        {
          "location": {
            "lat": 52.4989,
            "lng": 13.3917
          },
          "time": {
            "arrival": "2019-07-04T09:41:42Z",
            "departure": "2019-07-04T09:46:42Z"
          },
          "distance": 10621,
          "load": [
            2
          ],
          "activities": [
            {
              "jobId": "job6",
              "type": "pickup"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5043,
            "lng": 13.4091
          },
          "time": {
            "arrival": "2019-07-04T09:48:54Z",
            "departure": "2019-07-04T09:58:54Z"
          },
          "distance": 11944,
          "load": [
            0
          ],
          "activities": [
            {
              "jobId": "reload",
              "type": "reload"
            }
          ]
        },
        {
          "location": {
            "lat": 52.4922,
            "lng": 13.4593
          },
          "time": {
            "arrival": "2019-07-04T10:05:00Z",
            "departure": "2019-07-04T10:10:00Z"
          },
          "distance": 15603,
          "load": [
            1
          ],
          "activities": [
            {
              "jobId": "job5",
              "type": "pickup"
            }
          ]
        },
        {
          "location": {
            "lat": 52.52599,
            "lng": 13.45413
          },
          "time": {
            "arrival": "2019-07-04T10:16:18Z",
            "departure": "2019-07-04T10:21:18Z"
          },
          "distance": 19381,
          "load": [
            2
          ],
          "activities": [
            {
              "jobId": "job1",
              "type": "pickup"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5316,
            "lng": 13.3884
          },
          "time": {
            "arrival": "2019-07-04T10:28:48Z",
            "departure": "2019-07-04T10:28:48Z"
          },
          "distance": 23876,
          "load": [
            0
          ],
          "activities": [
            {
              "jobId": "arrival",
              "type": "arrival"
            }
          ]
        }
      ],
      "statistic": {
        "cost": 52.38156800000001,
        "distance": 23876,
        "duration": 5328,
        "times": {
          "driving": 2388,
          "serving": 2940,
          "waiting": 0,
          "break": 0
        }
      }
    }
  ],
  "unassigned": []
}

Shared reload resource

In this scenario, there are two vehicles with limited capacity [2] with reload which has shared resource constraint [1]. The problem has 6 delivery jobs in total.

Problem

{
  "plan": {
    "jobs": [
      {
        "id": "job1",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.52599,
                  "lng": 13.45413
                },
                "duration": 300.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job2",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5225,
                  "lng": 13.4095
                },
                "duration": 240.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job3",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5165,
                  "lng": 13.3808
                },
                "duration": 300.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job4",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5145,
                  "lng": 13.3513
                },
                "duration": 300.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job5",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.4922,
                  "lng": 13.4593
                },
                "duration": 300.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job6",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.4989,
                  "lng": 13.3917
                },
                "duration": 300.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      }
    ]
  },
  "fleet": {
    "vehicles": [
      {
        "typeId": "vehicle",
        "vehicleIds": [
          "vehicle_1",
          "vehicle_2"
        ],
        "profile": {
          "matrix": "normal_car"
        },
        "costs": {
          "fixed": 22.0,
          "distance": 0.0002,
          "time": 0.004806
        },
        "shifts": [
          {
            "start": {
              "earliest": "2019-07-04T09:00:00Z",
              "location": {
                "lat": 52.5316,
                "lng": 13.3884
              }
            },
            "end": {
              "latest": "2019-07-04T18:00:00Z",
              "location": {
                "lat": 52.5316,
                "lng": 13.3884
              }
            },
            "reloads": [
              {
                "location": {
                  "lat": 52.5103,
                  "lng": 13.3898
                },
                "duration": 600.0,
                "resourceId": "warehouse_a"
              }
            ]
          }
        ],
        "capacity": [
          2
        ]
      }
    ],
    "profiles": [
      {
        "name": "normal_car"
      }
    ],
    "resources": [
      {
        "type": "reload",
        "id": "warehouse_a",
        "capacity": [1]
      }
    ]
  }
}

As result, the solution has 5 jobs assigned and one is unassigned as there is not enough capacity and reload resource constraint doesn't allow to reload more than 1 delivery in total for all vehicles.

Solution

{
  "statistic": {
    "cost": 67.17683,
    "distance": 19644,
    "duration": 4005,
    "times": {
      "driving": 1965,
      "serving": 2040,
      "waiting": 0,
      "break": 0,
      "commuting": 0,
      "parking": 0
    }
  },
  "tours": [
    {
      "vehicleId": "vehicle_1",
      "typeId": "vehicle",
      "shiftIndex": 0,
      "stops": [
        {
          "location": {
            "lat": 52.5316,
            "lng": 13.3884
          },
          "time": {
            "arrival": "2019-07-04T09:00:00Z",
            "departure": "2019-07-04T09:00:00Z"
          },
          "distance": 0,
          "load": [
            2
          ],
          "activities": [
            {
              "jobId": "departure",
              "type": "departure"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5145,
            "lng": 13.3513
          },
          "time": {
            "arrival": "2019-07-04T09:05:15Z",
            "departure": "2019-07-04T09:10:15Z"
          },
          "distance": 3152,
          "load": [
            1
          ],
          "activities": [
            {
              "jobId": "job4",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.4989,
            "lng": 13.3917
          },
          "time": {
            "arrival": "2019-07-04T09:15:39Z",
            "departure": "2019-07-04T09:20:39Z"
          },
          "distance": 6394,
          "load": [
            0
          ],
          "activities": [
            {
              "jobId": "job6",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5103,
            "lng": 13.3898
          },
          "time": {
            "arrival": "2019-07-04T09:22:47Z",
            "departure": "2019-07-04T09:32:47Z"
          },
          "distance": 7670,
          "load": [
            1
          ],
          "activities": [
            {
              "jobId": "reload",
              "type": "reload"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5165,
            "lng": 13.3808
          },
          "time": {
            "arrival": "2019-07-04T09:34:19Z",
            "departure": "2019-07-04T09:39:19Z"
          },
          "distance": 8591,
          "load": [
            0
          ],
          "activities": [
            {
              "jobId": "job3",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5316,
            "lng": 13.3884
          },
          "time": {
            "arrival": "2019-07-04T09:42:15Z",
            "departure": "2019-07-04T09:42:15Z"
          },
          "distance": 10349,
          "load": [
            0
          ],
          "activities": [
            {
              "jobId": "arrival",
              "type": "arrival"
            }
          ]
        }
      ],
      "statistic": {
        "cost": 36.25301,
        "distance": 10349,
        "duration": 2535,
        "times": {
          "driving": 1035,
          "serving": 1500,
          "waiting": 0,
          "break": 0,
          "commuting": 0,
          "parking": 0
        }
      }
    },
    {
      "vehicleId": "vehicle_2",
      "typeId": "vehicle",
      "shiftIndex": 0,
      "stops": [
        {
          "location": {
            "lat": 52.5316,
            "lng": 13.3884
          },
          "time": {
            "arrival": "2019-07-04T09:00:00Z",
            "departure": "2019-07-04T09:00:00Z"
          },
          "distance": 0,
          "load": [
            2
          ],
          "activities": [
            {
              "jobId": "departure",
              "type": "departure"
            }
          ]
        },
        {
          "location": {
            "lat": 52.52599,
            "lng": 13.45413
          },
          "time": {
            "arrival": "2019-07-04T09:07:30Z",
            "departure": "2019-07-04T09:12:30Z"
          },
          "distance": 4495,
          "load": [
            1
          ],
          "activities": [
            {
              "jobId": "job1",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5225,
            "lng": 13.4095
          },
          "time": {
            "arrival": "2019-07-04T09:17:35Z",
            "departure": "2019-07-04T09:21:35Z"
          },
          "distance": 7543,
          "load": [
            0
          ],
          "activities": [
            {
              "jobId": "job2",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5316,
            "lng": 13.3884
          },
          "time": {
            "arrival": "2019-07-04T09:24:30Z",
            "departure": "2019-07-04T09:24:30Z"
          },
          "distance": 9295,
          "load": [
            0
          ],
          "activities": [
            {
              "jobId": "arrival",
              "type": "arrival"
            }
          ]
        }
      ],
      "statistic": {
        "cost": 30.92382,
        "distance": 9295,
        "duration": 1470,
        "times": {
          "driving": 930,
          "serving": 540,
          "waiting": 0,
          "break": 0,
          "commuting": 0,
          "parking": 0
        }
      }
    }
  ],
  "unassigned": [
    {
      "jobId": "job5",
      "reasons": [
        {
          "code": "RELOAD_RESOURCE_CONSTRAINT",
          "description": "cannot be assigned due to reload resource constraint",
          "details": [
            {
              "vehicleId": "vehicle_1",
              "shiftIndex": 0
            }
          ]
        },
        {
          "code": "CAPACITY_CONSTRAINT",
          "description": "does not fit into any vehicle due to capacity",
          "details": [
            {
              "vehicleId": "vehicle_2",
              "shiftIndex": 0
            }
          ]
        }
      ]
    }
  ]
}

Recharge stations

This example demonstrates an experimental feature to model a simple scenario of Electric VRP. Here, the vehicles have a distance limit (10km), so that they are forced to visit charging stations. Each station is defined by specific location, charging duration and time windows.

Problem

{
  "plan": {
    "jobs": [
      {
        "id": "job1",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.55770070807453,
                  "lng": 13.47831557890163
                },
                "duration": 300.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job2",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.56043917150327,
                  "lng": 13.300632258878089
                },
                "duration": 300.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job3",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.51645925211421,
                  "lng": 13.310531630245283
                },
                "duration": 300.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job4",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.48382634966174,
                  "lng": 13.431958828477603
                },
                "duration": 300.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job5",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.465611353026226,
                  "lng": 13.448598436078305
                },
                "duration": 300.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job6",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.47033706718906,
                  "lng": 13.319996854028378
                },
                "duration": 300.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job7",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.55268247887361,
                  "lng": 13.381458245337722
                },
                "duration": 300.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job8",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.52515727139781,
                  "lng": 13.488275599400666
                },
                "duration": 300.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job9",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5113790139571,
                  "lng": 13.508014352789077
                },
                "duration": 300.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job10",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.50443783915422,
                  "lng": 13.490517138667146
                },
                "duration": 300.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job11",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.53728559309784,
                  "lng": 13.384345955947186
                },
                "duration": 300.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job12",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.560969920106366,
                  "lng": 13.347428870365968
                },
                "duration": 300.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job13",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.46326762540066,
                  "lng": 13.426293829138386
                },
                "duration": 300.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job14",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.533357673695875,
                  "lng": 13.487291894366974
                },
                "duration": 300.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job15",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5603208042266,
                  "lng": 13.459275950620873
                },
                "duration": 300.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job16",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.52330707366729,
                  "lng": 13.439791508009296
                },
                "duration": 300.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job17",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.48354407179593,
                  "lng": 13.317361234494124
                },
                "duration": 300.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job18",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.53636687412595,
                  "lng": 13.396692271118948
                },
                "duration": 300.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job19",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.46220732686443,
                  "lng": 13.381890620781645
                },
                "duration": 300.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job20",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.52862953792047,
                  "lng": 13.434017943525484
                },
                "duration": 300.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      }
    ]
  },
  "fleet": {
    "vehicles": [
      {
        "typeId": "vehicle",
        "vehicleIds": [
          "vehicle_1",
          "vehicle_2"
        ],
        "profile": {
          "matrix": "car"
        },
        "costs": {
          "fixed": 25.0,
          "distance": 0.0002,
          "time": 0.005
        },
        "shifts": [
          {
            "start": {
              "earliest": "2020-05-01T09:00:00.00Z",
              "location": {
                "lat": 52.5189,
                "lng": 13.4011
              }
            },
            "end": {
              "latest": "2020-05-01T18:00:00.00Z",
              "location": {
                "lat": 52.5189,
                "lng": 13.4011
              }
            },
            "breaks": [
              {
                "time": [
                  "2020-05-01T12:30:00.00Z",
                  "2020-05-01T13:00:00.00Z"
                ],
                "places": [
                  {
                    "duration": 3600.0
                  }
                ]
              }
            ],
            "recharges": {
              "maxDistance": 10000,
              "stations": [
                {
                  "location": {
                    "lat": 52.5459,
                    "lng": 13.5058
                  },
                  "duration": 900
                },
                {
                  "location": {
                    "lat": 52.5502,
                    "lng": 13.3975
                  },
                  "duration": 900
                },
                {
                  "location": {
                    "lat": 52.5204,
                    "lng": 13.3243
                  },
                  "duration": 900
                },
                {
                  "location": {
                    "lat": 52.4936,
                    "lng": 13.4076
                  },
                  "duration": 900
                },
                {
                  "location": {
                    "lat": 52.5064,
                    "lng": 13.5011
                  },
                  "duration": 900
                }
              ]
            }
          }
        ],
        "capacity": [
          10
        ]
      }
    ],
    "profiles": [
      {
        "name": "car"
      }
    ]
  }
}

Solution

{
  "statistic": {
    "cost": 134.25099999999998,
    "distance": 56055,
    "duration": 14608,
    "times": {
      "driving": 5608,
      "serving": 9000,
      "waiting": 0,
      "break": 0,
      "commuting": 0,
      "parking": 0
    }
  },
  "tours": [
    {
      "vehicleId": "vehicle_1",
      "typeId": "vehicle",
      "shiftIndex": 0,
      "stops": [
        {
          "location": {
            "lat": 52.5189,
            "lng": 13.4011
          },
          "time": {
            "arrival": "2020-05-01T09:00:00Z",
            "departure": "2020-05-01T09:00:00Z"
          },
          "distance": 0,
          "load": [
            5
          ],
          "activities": [
            {
              "jobId": "departure",
              "type": "departure"
            }
          ]
        },
        {
          "location": {
            "lat": 52.51645925211421,
            "lng": 13.310531630245285
          },
          "time": {
            "arrival": "2020-05-01T09:10:14Z",
            "departure": "2020-05-01T09:15:14Z"
          },
          "distance": 6141,
          "load": [
            4
          ],
          "activities": [
            {
              "jobId": "job3",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5204,
            "lng": 13.3243
          },
          "time": {
            "arrival": "2020-05-01T09:16:57Z",
            "departure": "2020-05-01T09:31:57Z"
          },
          "distance": 7172,
          "load": [
            4
          ],
          "activities": [
            {
              "jobId": "recharge",
              "type": "recharge"
            }
          ]
        },
        {
          "location": {
            "lat": 52.560969920106366,
            "lng": 13.347428870365968
          },
          "time": {
            "arrival": "2020-05-01T09:39:55Z",
            "departure": "2020-05-01T09:44:55Z"
          },
          "distance": 11952,
          "load": [
            3
          ],
          "activities": [
            {
              "jobId": "job12",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.55268247887361,
            "lng": 13.381458245337722
          },
          "time": {
            "arrival": "2020-05-01T09:49:03Z",
            "departure": "2020-05-01T09:54:03Z"
          },
          "distance": 14433,
          "load": [
            2
          ],
          "activities": [
            {
              "jobId": "job7",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5502,
            "lng": 13.3975
          },
          "time": {
            "arrival": "2020-05-01T09:55:55Z",
            "departure": "2020-05-01T10:10:55Z"
          },
          "distance": 15553,
          "load": [
            2
          ],
          "activities": [
            {
              "jobId": "recharge",
              "type": "recharge"
            }
          ]
        },
        {
          "location": {
            "lat": 52.53728559309784,
            "lng": 13.384345955947186
          },
          "time": {
            "arrival": "2020-05-01T10:13:44Z",
            "departure": "2020-05-01T10:18:44Z"
          },
          "distance": 17244,
          "load": [
            1
          ],
          "activities": [
            {
              "jobId": "job11",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.53636687412595,
            "lng": 13.396692271118948
          },
          "time": {
            "arrival": "2020-05-01T10:20:08Z",
            "departure": "2020-05-01T10:25:08Z"
          },
          "distance": 18086,
          "load": [
            0
          ],
          "activities": [
            {
              "jobId": "job18",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5189,
            "lng": 13.4011
          },
          "time": {
            "arrival": "2020-05-01T10:28:25Z",
            "departure": "2020-05-01T10:28:25Z"
          },
          "distance": 20053,
          "load": [
            0
          ],
          "activities": [
            {
              "jobId": "arrival",
              "type": "arrival"
            }
          ]
        }
      ],
      "statistic": {
        "cost": 55.5356,
        "distance": 20053,
        "duration": 5305,
        "times": {
          "driving": 2005,
          "serving": 3300,
          "waiting": 0,
          "break": 0,
          "commuting": 0,
          "parking": 0
        }
      }
    },
    {
      "vehicleId": "vehicle_2",
      "typeId": "vehicle",
      "shiftIndex": 0,
      "stops": [
        {
          "location": {
            "lat": 52.5189,
            "lng": 13.4011
          },
          "time": {
            "arrival": "2020-05-01T09:00:00Z",
            "departure": "2020-05-01T09:00:00Z"
          },
          "distance": 0,
          "load": [
            10
          ],
          "activities": [
            {
              "jobId": "departure",
              "type": "departure"
            }
          ]
        },
        {
          "location": {
            "lat": 52.52862953792047,
            "lng": 13.434017943525484
          },
          "time": {
            "arrival": "2020-05-01T09:04:08Z",
            "departure": "2020-05-01T09:09:08Z"
          },
          "distance": 2479,
          "load": [
            9
          ],
          "activities": [
            {
              "jobId": "job20",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5603208042266,
            "lng": 13.459275950620873
          },
          "time": {
            "arrival": "2020-05-01T09:15:40Z",
            "departure": "2020-05-01T09:20:40Z"
          },
          "distance": 6399,
          "load": [
            8
          ],
          "activities": [
            {
              "jobId": "job15",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.55770070807453,
            "lng": 13.47831557890163
          },
          "time": {
            "arrival": "2020-05-01T09:22:52Z",
            "departure": "2020-05-01T09:27:52Z"
          },
          "distance": 7720,
          "load": [
            7
          ],
          "activities": [
            {
              "jobId": "job1",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5459,
            "lng": 13.5058
          },
          "time": {
            "arrival": "2020-05-01T09:31:40Z",
            "departure": "2020-05-01T09:46:40Z"
          },
          "distance": 9997,
          "load": [
            7
          ],
          "activities": [
            {
              "jobId": "recharge",
              "type": "recharge"
            }
          ]
        },
        {
          "location": {
            "lat": 52.533357673695875,
            "lng": 13.487291894366974
          },
          "time": {
            "arrival": "2020-05-01T09:49:48Z",
            "departure": "2020-05-01T09:54:48Z"
          },
          "distance": 11873,
          "load": [
            6
          ],
          "activities": [
            {
              "jobId": "job14",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.52515727139781,
            "lng": 13.488275599400666
          },
          "time": {
            "arrival": "2020-05-01T09:56:20Z",
            "departure": "2020-05-01T10:01:20Z"
          },
          "distance": 12788,
          "load": [
            5
          ],
          "activities": [
            {
              "jobId": "job8",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5113790139571,
            "lng": 13.508014352789075
          },
          "time": {
            "arrival": "2020-05-01T10:04:43Z",
            "departure": "2020-05-01T10:09:43Z"
          },
          "distance": 14823,
          "load": [
            4
          ],
          "activities": [
            {
              "jobId": "job9",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.50443783915422,
            "lng": 13.490517138667146
          },
          "time": {
            "arrival": "2020-05-01T10:12:05Z",
            "departure": "2020-05-01T10:17:05Z"
          },
          "distance": 16238,
          "load": [
            3
          ],
          "activities": [
            {
              "jobId": "job10",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5064,
            "lng": 13.5011
          },
          "time": {
            "arrival": "2020-05-01T10:18:20Z",
            "departure": "2020-05-01T10:33:20Z"
          },
          "distance": 16988,
          "load": [
            3
          ],
          "activities": [
            {
              "jobId": "recharge",
              "type": "recharge"
            }
          ]
        },
        {
          "location": {
            "lat": 52.465611353026226,
            "lng": 13.448598436078305
          },
          "time": {
            "arrival": "2020-05-01T10:42:57Z",
            "departure": "2020-05-01T10:47:57Z"
          },
          "distance": 22757,
          "load": [
            2
          ],
          "activities": [
            {
              "jobId": "job5",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.4936,
            "lng": 13.4076
          },
          "time": {
            "arrival": "2020-05-01T10:54:55Z",
            "departure": "2020-05-01T11:09:55Z"
          },
          "distance": 26932,
          "load": [
            2
          ],
          "activities": [
            {
              "jobId": "recharge",
              "type": "recharge"
            }
          ]
        },
        {
          "location": {
            "lat": 52.48382634966174,
            "lng": 13.431958828477605
          },
          "time": {
            "arrival": "2020-05-01T11:13:13Z",
            "departure": "2020-05-01T11:18:13Z"
          },
          "distance": 28909,
          "load": [
            1
          ],
          "activities": [
            {
              "jobId": "job4",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.52330707366729,
            "lng": 13.439791508009296
          },
          "time": {
            "arrival": "2020-05-01T11:25:36Z",
            "departure": "2020-05-01T11:30:36Z"
          },
          "distance": 33336,
          "load": [
            0
          ],
          "activities": [
            {
              "jobId": "job16",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5189,
            "lng": 13.4011
          },
          "time": {
            "arrival": "2020-05-01T11:35:03Z",
            "departure": "2020-05-01T11:35:03Z"
          },
          "distance": 36002,
          "load": [
            0
          ],
          "activities": [
            {
              "jobId": "arrival",
              "type": "arrival"
            }
          ]
        }
      ],
      "statistic": {
        "cost": 78.71539999999999,
        "distance": 36002,
        "duration": 9303,
        "times": {
          "driving": 3603,
          "serving": 5700,
          "waiting": 0,
          "break": 0,
          "commuting": 0,
          "parking": 0
        }
      }
    }
  ],
  "unassigned": [
    {
      "jobId": "job17",
      "reasons": [
        {
          "code": "RECHARGE_CONSTRAINT_CODE",
          "description": "cannot be assigned due to recharge constraint",
          "details": [
            {
              "vehicleId": "vehicle_1",
              "shiftIndex": 0
            }
          ]
        },
        {
          "code": "CAPACITY_CONSTRAINT",
          "description": "does not fit into any vehicle due to capacity",
          "details": [
            {
              "vehicleId": "vehicle_2",
              "shiftIndex": 0
            }
          ]
        }
      ]
    },
    {
      "jobId": "job19",
      "reasons": [
        {
          "code": "RECHARGE_CONSTRAINT_CODE",
          "description": "cannot be assigned due to recharge constraint",
          "details": [
            {
              "vehicleId": "vehicle_1",
              "shiftIndex": 0
            }
          ]
        },
        {
          "code": "CAPACITY_CONSTRAINT",
          "description": "does not fit into any vehicle due to capacity",
          "details": [
            {
              "vehicleId": "vehicle_2",
              "shiftIndex": 0
            }
          ]
        }
      ]
    },
    {
      "jobId": "job2",
      "reasons": [
        {
          "code": "RECHARGE_CONSTRAINT_CODE",
          "description": "cannot be assigned due to recharge constraint",
          "details": [
            {
              "vehicleId": "vehicle_1",
              "shiftIndex": 0
            }
          ]
        },
        {
          "code": "CAPACITY_CONSTRAINT",
          "description": "does not fit into any vehicle due to capacity",
          "details": [
            {
              "vehicleId": "vehicle_2",
              "shiftIndex": 0
            }
          ]
        }
      ]
    },
    {
      "jobId": "job13",
      "reasons": [
        {
          "code": "RECHARGE_CONSTRAINT_CODE",
          "description": "cannot be assigned due to recharge constraint",
          "details": [
            {
              "vehicleId": "vehicle_1",
              "shiftIndex": 0
            }
          ]
        },
        {
          "code": "CAPACITY_CONSTRAINT",
          "description": "does not fit into any vehicle due to capacity",
          "details": [
            {
              "vehicleId": "vehicle_2",
              "shiftIndex": 0
            }
          ]
        }
      ]
    },
    {
      "jobId": "job6",
      "reasons": [
        {
          "code": "RECHARGE_CONSTRAINT_CODE",
          "description": "cannot be assigned due to recharge constraint",
          "details": [
            {
              "vehicleId": "vehicle_1",
              "shiftIndex": 0
            }
          ]
        },
        {
          "code": "CAPACITY_CONSTRAINT",
          "description": "does not fit into any vehicle due to capacity",
          "details": [
            {
              "vehicleId": "vehicle_2",
              "shiftIndex": 0
            }
          ]
        }
      ]
    }
  ]
}

NOTE: the feature is on early stage

Relations

These examples demonstrates how to use relation feature.

Relation of Any type

In this example, any relation locks two jobs to specific vehicle in any order.

Problem

{
  "plan": {
    "jobs": [
      {
        "id": "job1",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.52599,
                  "lng": 13.45413
                },
                "duration": 300.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job2",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5225,
                  "lng": 13.4095
                },
                "duration": 240.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job3",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5165,
                  "lng": 13.3808
                },
                "duration": 300.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job4",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5145,
                  "lng": 13.3513
                },
                "duration": 300.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      }
    ],
    "relations": [
      {
        "type": "any",
        "jobs": [
          "job1",
          "job3"
        ],
        "vehicleId": "vehicle_1"
      }
    ]
  },
  "fleet": {
    "vehicles": [
      {
        "typeId": "vehicle",
        "vehicleIds": [
          "vehicle_1",
          "vehicle_2"
        ],
        "profile": {
          "matrix": "normal_car"
        },
        "costs": {
          "fixed": 22.0,
          "distance": 0.0002,
          "time": 0.004806
        },
        "shifts": [
          {
            "start": {
              "earliest": "2019-07-04T09:00:00Z",
              "location": {
                "lat": 52.4664257,
                "lng": 13.2812488
              }
            },
            "end": {
              "latest": "2019-07-04T18:00:00Z",
              "location": {
                "lat": 52.4664257,
                "lng": 13.2812488
              }
            }
          }
        ],
        "capacity": [
          2
        ]
      }
    ],
    "profiles": [
      {
        "name": "normal_car"
      }
    ]
  }
}

Solution

{
  "statistic": {
    "cost": 107.17656600000001,
    "distance": 64505,
    "duration": 10461,
    "times": {
      "driving": 9321,
      "serving": 1140,
      "waiting": 0,
      "break": 0
    }
  },
  "tours": [
    {
      "vehicleId": "vehicle_2",
      "typeId": "vehicle",
      "shiftIndex": 0,
      "stops": [
        {
          "location": {
            "lat": 52.4664257,
            "lng": 13.2812488
          },
          "time": {
            "arrival": "2019-07-04T09:00:00Z",
            "departure": "2019-07-04T09:00:00Z"
          },
          "distance": 0,
          "load": [
            2
          ],
          "activities": [
            {
              "jobId": "departure",
              "type": "departure"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5145,
            "lng": 13.3513
          },
          "time": {
            "arrival": "2019-07-04T09:21:41Z",
            "departure": "2019-07-04T09:26:41Z"
          },
          "distance": 8996,
          "load": [
            1
          ],
          "activities": [
            {
              "jobId": "job4",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5225,
            "lng": 13.4095
          },
          "time": {
            "arrival": "2019-07-04T09:41:26Z",
            "departure": "2019-07-04T09:45:26Z"
          },
          "distance": 14028,
          "load": [
            0
          ],
          "activities": [
            {
              "jobId": "job2",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.4664257,
            "lng": 13.2812488
          },
          "time": {
            "arrival": "2019-07-04T10:18:32Z",
            "departure": "2019-07-04T10:18:32Z"
          },
          "distance": 27524,
          "load": [
            0
          ],
          "activities": [
            {
              "jobId": "arrival",
              "type": "arrival"
            }
          ]
        }
      ],
      "statistic": {
        "cost": 50.150672,
        "distance": 27524,
        "duration": 4712,
        "times": {
          "driving": 4172,
          "serving": 540,
          "waiting": 0,
          "break": 0
        }
      }
    },
    {
      "vehicleId": "vehicle_1",
      "typeId": "vehicle",
      "shiftIndex": 0,
      "stops": [
        {
          "location": {
            "lat": 52.4664257,
            "lng": 13.2812488
          },
          "time": {
            "arrival": "2019-07-04T09:00:00Z",
            "departure": "2019-07-04T09:00:00Z"
          },
          "distance": 0,
          "load": [
            2
          ],
          "activities": [
            {
              "jobId": "departure",
              "type": "departure"
            }
          ]
        },
        {
          "location": {
            "lat": 52.52599,
            "lng": 13.45413
          },
          "time": {
            "arrival": "2019-07-04T09:41:15Z",
            "departure": "2019-07-04T09:46:15Z"
          },
          "distance": 19648,
          "load": [
            1
          ],
          "activities": [
            {
              "jobId": "job1",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5165,
            "lng": 13.3808
          },
          "time": {
            "arrival": "2019-07-04T10:02:31Z",
            "departure": "2019-07-04T10:07:31Z"
          },
          "distance": 25642,
          "load": [
            0
          ],
          "activities": [
            {
              "jobId": "job3",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.4664257,
            "lng": 13.2812488
          },
          "time": {
            "arrival": "2019-07-04T10:35:49Z",
            "departure": "2019-07-04T10:35:49Z"
          },
          "distance": 36981,
          "load": [
            0
          ],
          "activities": [
            {
              "jobId": "arrival",
              "type": "arrival"
            }
          ]
        }
      ],
      "statistic": {
        "cost": 57.02589400000001,
        "distance": 36981,
        "duration": 5749,
        "times": {
          "driving": 5149,
          "serving": 600,
          "waiting": 0,
          "break": 0
        }
      }
    }
  ],
  "unassigned": []
}

Relation of Strict type

In this example, strict relation locks two jobs to specific vehicle starting from departure.

Problem

{
  "plan": {
    "jobs": [
      {
        "id": "job1",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.52599,
                  "lng": 13.45413
                },
                "duration": 300.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job2",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5225,
                  "lng": 13.4095
                },
                "duration": 240.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job3",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5165,
                  "lng": 13.3808
                },
                "duration": 300.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job4",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5145,
                  "lng": 13.3513
                },
                "duration": 300.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      }
    ],
    "relations": [
      {
        "type": "strict",
        "jobs": [
          "departure",
          "job4",
          "job1"
        ],
        "vehicleId": "vehicle_1"
      }
    ]
  },
  "fleet": {
    "vehicles": [
      {
        "typeId": "vehicle",
        "vehicleIds": [
          "vehicle_1"
        ],
        "profile": {
          "matrix": "normal_car"
        },
        "costs": {
          "fixed": 22.0,
          "distance": 0.0002,
          "time": 0.004806
        },
        "shifts": [
          {
            "start": {
              "earliest": "2019-07-04T09:00:00Z",
              "location": {
                "lat": 52.4664257,
                "lng": 13.2812488
              }
            },
            "end": {
              "latest": "2019-07-04T18:00:00Z",
              "location": {
                "lat": 52.4664257,
                "lng": 13.2812488
              }
            }
          }
        ],
        "capacity": [
          10
        ]
      }
    ],
    "profiles": [
      {
        "name": "normal_car"
      }
    ]
  }
}

Solution

{
  "statistic": {
    "cost": 60.354318000000006,
    "distance": 34303,
    "duration": 6553,
    "times": {
      "driving": 5413,
      "serving": 1140,
      "waiting": 0,
      "break": 0
    }
  },
  "tours": [
    {
      "vehicleId": "vehicle_1",
      "typeId": "vehicle",
      "shiftIndex": 0,
      "stops": [
        {
          "location": {
            "lat": 52.4664257,
            "lng": 13.2812488
          },
          "time": {
            "arrival": "2019-07-04T09:00:00Z",
            "departure": "2019-07-04T09:00:00Z"
          },
          "distance": 0,
          "load": [
            4
          ],
          "activities": [
            {
              "jobId": "departure",
              "type": "departure"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5145,
            "lng": 13.3513
          },
          "time": {
            "arrival": "2019-07-04T09:22:23Z",
            "departure": "2019-07-04T09:27:23Z"
          },
          "distance": 8621,
          "load": [
            3
          ],
          "activities": [
            {
              "jobId": "job4",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.52599,
            "lng": 13.45413
          },
          "time": {
            "arrival": "2019-07-04T09:48:51Z",
            "departure": "2019-07-04T09:53:51Z"
          },
          "distance": 16956,
          "load": [
            2
          ],
          "activities": [
            {
              "jobId": "job1",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5225,
            "lng": 13.4095
          },
          "time": {
            "arrival": "2019-07-04T10:04:40Z",
            "departure": "2019-07-04T10:08:40Z"
          },
          "distance": 20810,
          "load": [
            1
          ],
          "activities": [
            {
              "jobId": "job2",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5165,
            "lng": 13.3808
          },
          "time": {
            "arrival": "2019-07-04T10:14:29Z",
            "departure": "2019-07-04T10:19:29Z"
          },
          "distance": 22964,
          "load": [
            0
          ],
          "activities": [
            {
              "jobId": "job3",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.4664257,
            "lng": 13.2812488
          },
          "time": {
            "arrival": "2019-07-04T10:49:13Z",
            "departure": "2019-07-04T10:49:13Z"
          },
          "distance": 34303,
          "load": [
            0
          ],
          "activities": [
            {
              "jobId": "arrival",
              "type": "arrival"
            }
          ]
        }
      ],
      "statistic": {
        "cost": 60.354318000000006,
        "distance": 34303,
        "duration": 6553,
        "times": {
          "driving": 5413,
          "serving": 1140,
          "waiting": 0,
          "break": 0
        }
      }
    }
  ],
  "unassigned": []
}

Skills

This example demonstrates how to use the skills feature with jobs and vehicles. In general, the skills feature is useful for locking specific jobs to specific vehicles.

Complete problem json

{
  "plan": {
    "jobs": [
      {
        "id": "job1",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.52599,
                  "lng": 13.45413
                },
                "duration": 300.0
              }
            ],
            "demand": [
              1
            ]
          }
        ],
        "skills": {
          "allOf": [
            "fridge"
          ],
          "noneOf": [
            "washing_machine"
          ]
        }
      },
      {
        "id": "job2",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5225,
                  "lng": 13.4095
                },
                "duration": 240.0
              }
            ],
            "demand": [
              1
            ]
          }
        ],
        "skills": {
          "allOf": [
            "handyman"
          ]
        }
      }
    ]
  },
  "fleet": {
    "vehicles": [
      {
        "typeId": "vehicle_with_fridge",
        "vehicleIds": [
          "vehicle_with_fridge_1"
        ],
        "profile": {
          "matrix": "normal_car"
        },
        "costs": {
          "fixed": 22.0,
          "distance": 0.0002,
          "time": 0.004806
        },
        "shifts": [
          {
            "start": {
              "earliest": "2019-07-04T09:00:00Z",
              "location": {
                "lat": 52.5316,
                "lng": 13.3884
              }
            },
            "end": {
              "latest": "2019-07-04T18:00:00Z",
              "location": {
                "lat": 52.5316,
                "lng": 13.3884
              }
            }
          }
        ],
        "capacity": [
          2
        ],
        "skills": [
          "fridge"
        ]
      },
      {
        "typeId": "vehicle_with_handyman",
        "vehicleIds": [
          "vehicle_with_handyman_1"
        ],
        "profile": {
          "matrix": "normal_car"
        },
        "costs": {
          "fixed": 22.0,
          "distance": 0.0002,
          "time": 0.004806
        },
        "shifts": [
          {
            "start": {
              "earliest": "2019-07-04T09:00:00Z",
              "location": {
                "lat": 52.5316,
                "lng": 13.3884
              }
            },
            "end": {
              "latest": "2019-07-04T18:00:00Z",
              "location": {
                "lat": 52.5316,
                "lng": 13.3884
              }
            }
          }
        ],
        "capacity": [
          2
        ],
        "skills": [
          "handyman"
        ]
      }
    ],
    "profiles": [
      {
        "name": "normal_car"
      }
    ]
  }
}

Complete solution json

{
  "statistic": {
    "cost": 64.31331800000001,
    "distance": 16188,
    "duration": 3553,
    "times": {
      "driving": 3013,
      "serving": 540,
      "waiting": 0,
      "break": 0
    }
  },
  "tours": [
    {
      "vehicleId": "vehicle_with_handyman_1",
      "typeId": "vehicle_with_handyman",
      "shiftIndex": 0,
      "stops": [
        {
          "location": {
            "lat": 52.5316,
            "lng": 13.3884
          },
          "time": {
            "arrival": "2019-07-04T09:00:00Z",
            "departure": "2019-07-04T09:00:00Z"
          },
          "distance": 0,
          "load": [
            1
          ],
          "activities": [
            {
              "jobId": "departure",
              "type": "departure"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5225,
            "lng": 13.4095
          },
          "time": {
            "arrival": "2019-07-04T09:08:28Z",
            "departure": "2019-07-04T09:12:28Z"
          },
          "distance": 2470,
          "load": [
            0
          ],
          "activities": [
            {
              "jobId": "job2",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5316,
            "lng": 13.3884
          },
          "time": {
            "arrival": "2019-07-04T09:22:06Z",
            "departure": "2019-07-04T09:22:06Z"
          },
          "distance": 5138,
          "load": [
            0
          ],
          "activities": [
            {
              "jobId": "arrival",
              "type": "arrival"
            }
          ]
        }
      ],
      "statistic": {
        "cost": 29.400356000000002,
        "distance": 5138,
        "duration": 1326,
        "times": {
          "driving": 1086,
          "serving": 240,
          "waiting": 0,
          "break": 0
        }
      }
    },
    {
      "vehicleId": "vehicle_with_fridge_1",
      "typeId": "vehicle_with_fridge",
      "shiftIndex": 0,
      "stops": [
        {
          "location": {
            "lat": 52.5316,
            "lng": 13.3884
          },
          "time": {
            "arrival": "2019-07-04T09:00:00Z",
            "departure": "2019-07-04T09:00:00Z"
          },
          "distance": 0,
          "load": [
            1
          ],
          "activities": [
            {
              "jobId": "departure",
              "type": "departure"
            }
          ]
        },
        {
          "location": {
            "lat": 52.52599,
            "lng": 13.45413
          },
          "time": {
            "arrival": "2019-07-04T09:15:12Z",
            "departure": "2019-07-04T09:20:12Z"
          },
          "distance": 5112,
          "load": [
            0
          ],
          "activities": [
            {
              "jobId": "job1",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5316,
            "lng": 13.3884
          },
          "time": {
            "arrival": "2019-07-04T09:37:07Z",
            "departure": "2019-07-04T09:37:07Z"
          },
          "distance": 11050,
          "load": [
            0
          ],
          "activities": [
            {
              "jobId": "arrival",
              "type": "arrival"
            }
          ]
        }
      ],
      "statistic": {
        "cost": 34.912962,
        "distance": 11050,
        "duration": 2227,
        "times": {
          "driving": 1927,
          "serving": 300,
          "waiting": 0,
          "break": 0
        }
      }
    }
  ],
  "unassigned": []
}

Multiple routing profiles

This example demonstrates how to use multiple routing profiles: car and truck.

List of problem locations

[
  {
    "lat": 52.52599,
    "lng": 13.45413
  },
  {
    "lat": 52.5225,
    "lng": 13.4095
  },
  {
    "lat": 52.5316,
    "lng": 13.3884
  }
]

Routing matrix for car

{
  "profile": "normal_car",
  "travelTimes": [
    0,
    681,
    905,
    750,
    0,
    546,
    891,
    502,
    0
  ],
  "distances": [
    0,
    3840,
    5283,
    4696,
    0,
    2668,
    5112,
    2470,
    0
  ]
}

Routing matrix for truck

{
  "profile": "normal_truck",
  "travelTimes": [
    0,
    906,
    1218,
    970,
    0,
    750,
    1152,
    644,
    0
  ],
  "distances": [
    0,
    3840,
    5283,
    4482,
    0,
    2768,
    5112,
    2357,
    0
  ]
}

Problem

{
  "plan": {
    "jobs": [
      {
        "id": "job1",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.52599,
                  "lng": 13.45413
                },
                "duration": 240.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job2",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5225,
                  "lng": 13.4095
                },
                "duration": 600.0
              }
            ],
            "demand": [
              10
            ]
          }
        ]
      }
    ]
  },
  "fleet": {
    "vehicles": [
      {
        "typeId": "my_car",
        "vehicleIds": [
          "my_car_1"
        ],
        "profile": {
          "matrix": "normal_car"
        },
        "costs": {
          "fixed": 22.0,
          "distance": 0.0002,
          "time": 0.004806
        },
        "shifts": [
          {
            "start": {
              "earliest": "2019-07-04T09:00:00Z",
              "location": {
                "lat": 52.5316,
                "lng": 13.3884
              }
            },
            "end": {
              "latest": "2019-07-04T18:00:00Z",
              "location": {
                "lat": 52.5316,
                "lng": 13.3884
              }
            }
          }
        ],
        "capacity": [
          1
        ]
      },
      {
        "typeId": "my_truck",
        "vehicleIds": [
          "my_truck_1"
        ],
        "profile": {
          "matrix": "normal_truck"
        },
        "costs": {
          "fixed": 44.0,
          "distance": 0.0003,
          "time": 0.005
        },
        "shifts": [
          {
            "start": {
              "earliest": "2019-07-04T09:00:00Z",
              "location": {
                "lat": 52.5316,
                "lng": 13.3884
              }
            },
            "end": {
              "latest": "2019-07-04T18:00:00Z",
              "location": {
                "lat": 52.5316,
                "lng": 13.3884
              }
            }
          }
        ],
        "capacity": [
          10
        ]
      }
    ],
    "profiles": [
      {
        "name": "normal_car"
      },
      {
        "name": "normal_truck"
      }
    ]
  }
}

Solution

{
  "statistic": {
    "cost": 89.371516,
    "distance": 15520,
    "duration": 4030,
    "times": {
      "driving": 3190,
      "serving": 840,
      "waiting": 0,
      "break": 0
    }
  },
  "tours": [
    {
      "vehicleId": "my_car_1",
      "typeId": "my_car",
      "shiftIndex": 0,
      "stops": [
        {
          "location": {
            "lat": 52.5316,
            "lng": 13.3884
          },
          "time": {
            "arrival": "2019-07-04T09:00:00Z",
            "departure": "2019-07-04T09:00:00Z"
          },
          "distance": 0,
          "load": [
            1
          ],
          "activities": [
            {
              "jobId": "departure",
              "type": "departure"
            }
          ]
        },
        {
          "location": {
            "lat": 52.52599,
            "lng": 13.45413
          },
          "time": {
            "arrival": "2019-07-04T09:14:51Z",
            "departure": "2019-07-04T09:18:51Z"
          },
          "distance": 5112,
          "load": [
            0
          ],
          "activities": [
            {
              "jobId": "job1",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5316,
            "lng": 13.3884
          },
          "time": {
            "arrival": "2019-07-04T09:33:56Z",
            "departure": "2019-07-04T09:33:56Z"
          },
          "distance": 10395,
          "load": [
            0
          ],
          "activities": [
            {
              "jobId": "arrival",
              "type": "arrival"
            }
          ]
        }
      ],
      "statistic": {
        "cost": 33.864016,
        "distance": 10395,
        "duration": 2036,
        "times": {
          "driving": 1796,
          "serving": 240,
          "waiting": 0,
          "break": 0
        }
      }
    },
    {
      "vehicleId": "my_truck_1",
      "typeId": "my_truck",
      "shiftIndex": 0,
      "stops": [
        {
          "location": {
            "lat": 52.5316,
            "lng": 13.3884
          },
          "time": {
            "arrival": "2019-07-04T09:00:00Z",
            "departure": "2019-07-04T09:00:00Z"
          },
          "distance": 0,
          "load": [
            10
          ],
          "activities": [
            {
              "jobId": "departure",
              "type": "departure"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5225,
            "lng": 13.4095
          },
          "time": {
            "arrival": "2019-07-04T09:10:44Z",
            "departure": "2019-07-04T09:20:44Z"
          },
          "distance": 2357,
          "load": [
            0
          ],
          "activities": [
            {
              "jobId": "job2",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5316,
            "lng": 13.3884
          },
          "time": {
            "arrival": "2019-07-04T09:33:14Z",
            "departure": "2019-07-04T09:33:14Z"
          },
          "distance": 5125,
          "load": [
            0
          ],
          "activities": [
            {
              "jobId": "arrival",
              "type": "arrival"
            }
          ]
        }
      ],
      "statistic": {
        "cost": 55.5075,
        "distance": 5125,
        "duration": 1994,
        "times": {
          "driving": 1394,
          "serving": 600,
          "waiting": 0,
          "break": 0
        }
      }
    }
  ],
  "unassigned": []
}

Usage with cli

vrp-cli solve pragmatic profiles.basic.problem.json -m profiles.basic.matrix.car.json -m profiles.basic.matrix.truck -o profiles.basic.solution.json

Unassigned jobs

This example demonstrates one job which is unassigned due to time window constraints.

Problem

{
  "plan": {
    "jobs": [
      {
        "id": "job1",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.52599,
                  "lng": 13.45413
                },
                "duration": 300.0,
                "times": [
                  [
                    "2019-07-04T09:00:00Z",
                    "2019-07-04T18:00:00Z"
                  ],
                  [
                    "2019-07-05T09:00:00Z",
                    "2019-07-05T18:00:00Z"
                  ]
                ]
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job2",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 50.4576,
                  "lng": 11.1778
                },
                "duration": 240.0,
                "times": [
                  [
                    "2019-07-04T10:00:00Z",
                    "2019-07-04T16:00:00Z"
                  ]
                ]
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job3",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5165,
                  "lng": 13.3808
                },
                "duration": 300.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      }
    ]
  },
  "fleet": {
    "vehicles": [
      {
        "typeId": "vehicle",
        "vehicleIds": [
          "vehicle_1"
        ],
        "profile": {
          "matrix": "normal_car"
        },
        "costs": {
          "fixed": 22.0,
          "distance": 0.0002,
          "time": 0.004806
        },
        "shifts": [
          {
            "start": {
              "earliest": "2019-07-04T09:00:00Z",
              "location": {
                "lat": 52.5316,
                "lng": 13.3884
              }
            },
            "latest": {
              "time": "2019-07-04T18:00:00Z",
              "location": {
                "lat": 52.5316,
                "lng": 13.3884
              }
            }
          }
        ],
        "capacity": [
          10
        ]
      }
    ],
    "profiles": [
      {
        "name": "normal_car"
      }
    ]
  }
}

Solution

{
  "statistic": {
    "cost": 29.538104,
    "distance": 6836,
    "duration": 1284,
    "times": {
      "driving": 684,
      "serving": 600,
      "waiting": 0,
      "break": 0,
      "commuting": 0,
      "parking": 0
    }
  },
  "tours": [
    {
      "vehicleId": "vehicle_1",
      "typeId": "vehicle",
      "shiftIndex": 0,
      "stops": [
        {
          "location": {
            "lat": 52.5316,
            "lng": 13.3884
          },
          "time": {
            "arrival": "2019-07-04T09:00:00Z",
            "departure": "2019-07-04T09:00:00Z"
          },
          "distance": 0,
          "load": [
            2
          ],
          "activities": [
            {
              "jobId": "departure",
              "type": "departure"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5165,
            "lng": 13.3808
          },
          "time": {
            "arrival": "2019-07-04T09:02:56Z",
            "departure": "2019-07-04T09:07:56Z"
          },
          "distance": 1758,
          "load": [
            1
          ],
          "activities": [
            {
              "jobId": "job3",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.52599,
            "lng": 13.45413
          },
          "time": {
            "arrival": "2019-07-04T09:16:24Z",
            "departure": "2019-07-04T09:21:24Z"
          },
          "distance": 6836,
          "load": [
            0
          ],
          "activities": [
            {
              "jobId": "job1",
              "type": "delivery"
            }
          ]
        }
      ],
      "statistic": {
        "cost": 29.538104,
        "distance": 6836,
        "duration": 1284,
        "times": {
          "driving": 684,
          "serving": 600,
          "waiting": 0,
          "break": 0,
          "commuting": 0,
          "parking": 0
        }
      }
    }
  ],
  "unassigned": [
    {
      "jobId": "job2",
      "reasons": [
        {
          "code": "TIME_WINDOW_CONSTRAINT",
          "description": "cannot be visited within time window",
          "details": [
            {
              "vehicle_id": "vehicle_1",
              "shift_index": 0
            }
          ]
        }
      ]
    }
  ]
}

Clustering examples

The general idea of the clustering feature is to make more realistic ETAs for the same stop jobs, even if their locations are not exactly the same. This section provides a few examples.

Vicinity clustering with job continuation

This examples demonstrates a continue type of visit: jobs are visited one by one with returning to the stop point in the end.

Problem

{
  "plan": {
    "jobs": [
      {
        "id": "job1",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5253538,
                  "lng": 13.4525549
                },
                "duration": 300
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job2",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5254256,
                  "lng": 13.4527159
                },
                "duration": 300
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job3",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5255366,
                  "lng": 13.4530162
                },
                "duration": 300
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job4",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5253342,
                  "lng": 13.4533489
                },
                "duration": 300
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job5",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5239243,
                  "lng": 13.4545827
                },
                "duration": 300
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job6",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5223315,
                  "lng": 13.4555697
                },
                "duration": 300
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job7",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5240157,
                  "lng": 13.4572220
                },
                "duration": 300
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job8",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5243028,
                  "lng": 13.45952868
                },
                "duration": 300
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job9",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5241593,
                  "lng": 13.4589815
                },
                "duration": 300
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job10",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5239896,
                  "lng": 13.4600115
                },
                "duration": 300
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job11",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5250209,
                  "lng": 13.4567821
                },
                "duration": 300
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job12",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5223184,
                  "lng": 13.4551942
                },
                "duration": 300
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      }
    ],
    "clustering": {
      "type": "vicinity",
      "profile": {
        "matrix": "car",
        "scale": 10
      },
      "threshold": {
        "duration": 120,
        "distance": 100
      },
      "visiting": "continue",
      "serving": {
        "type": "fixed",
        "value": 180,
        "parking": 120
      }
    }
  },
  "fleet": {
    "vehicles": [
      {
        "typeId": "vehicle",
        "vehicleIds": [
          "vehicle_1"
        ],
        "profile": {
          "matrix": "car"
        },
        "costs": {
          "fixed": 25,
          "distance": 0.0001,
          "time": 0.005
        },
        "shifts": [
          {
            "start": {
              "earliest": "2020-05-01T09:00:00.00Z",
              "location": {
                "lat": 52.5262872,
                "lng": 13.4532952
              }
            }
          }
        ],
        "capacity": [
          20
        ]
      }
    ],
    "profiles": [
      {
        "name": "car",
        "type": "car"
      }
    ]
  }
}

Solution

{
  "statistic": {
    "cost": 41.648399999999995,
    "distance": 984,
    "duration": 3310,
    "times": {
      "driving": 100,
      "serving": 2520,
      "waiting": 0,
      "break": 0,
      "commuting": 330,
      "parking": 360
    }
  },
  "tours": [
    {
      "vehicleId": "vehicle_1",
      "typeId": "vehicle",
      "shiftIndex": 0,
      "stops": [
        {
          "location": {
            "lat": 52.5262872,
            "lng": 13.4532952
          },
          "time": {
            "arrival": "2020-05-01T09:00:00Z",
            "departure": "2020-05-01T09:00:00Z"
          },
          "distance": 0,
          "load": [
            12
          ],
          "activities": [
            {
              "jobId": "departure",
              "type": "departure"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5253342,
            "lng": 13.4533489
          },
          "time": {
            "arrival": "2020-05-01T09:00:11Z",
            "departure": "2020-05-01T09:16:01Z"
          },
          "distance": 106,
          "load": [
            8
          ],
          "parking": {
            "start": "2020-05-01T09:00:11Z",
            "end": "2020-05-01T09:02:11Z"
          },
          "activities": [
            {
              "jobId": "job4",
              "type": "delivery",
              "location": {
                "lat": 52.5253342,
                "lng": 13.4533489
              },
              "time": {
                "start": "2020-05-01T09:02:11Z",
                "end": "2020-05-01T09:05:11Z"
              },
              "commute": {}
            },
            {
              "jobId": "job3",
              "type": "delivery",
              "location": {
                "lat": 52.5255366,
                "lng": 13.4530162
              },
              "time": {
                "start": "2020-05-01T09:05:41Z",
                "end": "2020-05-01T09:08:41Z"
              },
              "commute": {
                "forward": {
                  "location": {
                    "lat": 52.5253342,
                    "lng": 13.4533489
                  },
                  "distance": 32.0,
                  "time": {
                    "start": "2020-05-01T09:05:11Z",
                    "end": "2020-05-01T09:05:41Z"
                  }
                }
              }
            },
            {
              "jobId": "job2",
              "type": "delivery",
              "location": {
                "lat": 52.5254256,
                "lng": 13.4527159
              },
              "time": {
                "start": "2020-05-01T09:09:01Z",
                "end": "2020-05-01T09:12:01Z"
              },
              "commute": {
                "forward": {
                  "location": {
                    "lat": 52.5255366,
                    "lng": 13.4530162
                  },
                  "distance": 24.0,
                  "time": {
                    "start": "2020-05-01T09:08:41Z",
                    "end": "2020-05-01T09:09:01Z"
                  }
                }
              }
            },
            {
              "jobId": "job1",
              "type": "delivery",
              "location": {
                "lat": 52.5253538,
                "lng": 13.4525549
              },
              "time": {
                "start": "2020-05-01T09:12:11Z",
                "end": "2020-05-01T09:15:11Z"
              },
              "commute": {
                "forward": {
                  "location": {
                    "lat": 52.5254256,
                    "lng": 13.4527159
                  },
                  "distance": 14.0,
                  "time": {
                    "start": "2020-05-01T09:12:01Z",
                    "end": "2020-05-01T09:12:11Z"
                  }
                },
                "backward": {
                  "location": {
                    "lat": 52.5253342,
                    "lng": 13.4533489
                  },
                  "distance": 54.0,
                  "time": {
                    "start": "2020-05-01T09:15:11Z",
                    "end": "2020-05-01T09:16:01Z"
                  }
                }
              }
            }
          ]
        },
        {
          "location": {
            "lat": 52.5239243,
            "lng": 13.4545827
          },
          "time": {
            "arrival": "2020-05-01T09:16:19Z",
            "departure": "2020-05-01T09:21:19Z"
          },
          "distance": 284,
          "load": [
            7
          ],
          "activities": [
            {
              "jobId": "job5",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5223315,
            "lng": 13.4555697
          },
          "time": {
            "arrival": "2020-05-01T09:21:38Z",
            "departure": "2020-05-01T09:30:38Z"
          },
          "distance": 473,
          "load": [
            5
          ],
          "parking": {
            "start": "2020-05-01T09:21:38Z",
            "end": "2020-05-01T09:23:38Z"
          },
          "activities": [
            {
              "jobId": "job6",
              "type": "delivery",
              "location": {
                "lat": 52.5223315,
                "lng": 13.4555697
              },
              "time": {
                "start": "2020-05-01T09:23:38Z",
                "end": "2020-05-01T09:26:38Z"
              },
              "commute": {}
            },
            {
              "jobId": "job12",
              "type": "delivery",
              "location": {
                "lat": 52.5223184,
                "lng": 13.4551942
              },
              "time": {
                "start": "2020-05-01T09:27:08Z",
                "end": "2020-05-01T09:30:08Z"
              },
              "commute": {
                "forward": {
                  "location": {
                    "lat": 52.5223315,
                    "lng": 13.4555697
                  },
                  "distance": 25.0,
                  "time": {
                    "start": "2020-05-01T09:26:38Z",
                    "end": "2020-05-01T09:27:08Z"
                  }
                },
                "backward": {
                  "location": {
                    "lat": 52.5223315,
                    "lng": 13.4555697
                  },
                  "distance": 25.0,
                  "time": {
                    "start": "2020-05-01T09:30:08Z",
                    "end": "2020-05-01T09:30:38Z"
                  }
                }
              }
            }
          ]
        },
        {
          "location": {
            "lat": 52.5240157,
            "lng": 13.457222
          },
          "time": {
            "arrival": "2020-05-01T09:31:00Z",
            "departure": "2020-05-01T09:36:00Z"
          },
          "distance": 691,
          "load": [
            4
          ],
          "activities": [
            {
              "jobId": "job7",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5250209,
            "lng": 13.4567821
          },
          "time": {
            "arrival": "2020-05-01T09:36:12Z",
            "departure": "2020-05-01T09:41:12Z"
          },
          "distance": 807,
          "load": [
            3
          ],
          "activities": [
            {
              "jobId": "job11",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5241593,
            "lng": 13.4589815
          },
          "time": {
            "arrival": "2020-05-01T09:41:30Z",
            "departure": "2020-05-01T09:55:10Z"
          },
          "distance": 984,
          "load": [
            0
          ],
          "parking": {
            "start": "2020-05-01T09:41:30Z",
            "end": "2020-05-01T09:43:30Z"
          },
          "activities": [
            {
              "jobId": "job9",
              "type": "delivery",
              "location": {
                "lat": 52.5241593,
                "lng": 13.4589815
              },
              "time": {
                "start": "2020-05-01T09:43:30Z",
                "end": "2020-05-01T09:46:30Z"
              },
              "commute": {}
            },
            {
              "jobId": "job8",
              "type": "delivery",
              "location": {
                "lat": 52.5243028,
                "lng": 13.45952868
              },
              "time": {
                "start": "2020-05-01T09:47:10Z",
                "end": "2020-05-01T09:50:10Z"
              },
              "commute": {
                "forward": {
                  "location": {
                    "lat": 52.5241593,
                    "lng": 13.4589815
                  },
                  "distance": 40.0,
                  "time": {
                    "start": "2020-05-01T09:46:30Z",
                    "end": "2020-05-01T09:47:10Z"
                  }
                }
              }
            },
            {
              "jobId": "job10",
              "type": "delivery",
              "location": {
                "lat": 52.5239896,
                "lng": 13.4600115
              },
              "time": {
                "start": "2020-05-01T09:51:00Z",
                "end": "2020-05-01T09:54:00Z"
              },
              "commute": {
                "forward": {
                  "location": {
                    "lat": 52.5243028,
                    "lng": 13.45952868
                  },
                  "distance": 48.0,
                  "time": {
                    "start": "2020-05-01T09:50:10Z",
                    "end": "2020-05-01T09:51:00Z"
                  }
                },
                "backward": {
                  "location": {
                    "lat": 52.5241593,
                    "lng": 13.4589815
                  },
                  "distance": 72.0,
                  "time": {
                    "start": "2020-05-01T09:54:00Z",
                    "end": "2020-05-01T09:55:10Z"
                  }
                }
              }
            }
          ]
        }
      ],
      "statistic": {
        "cost": 41.648399999999995,
        "distance": 984,
        "duration": 3310,
        "times": {
          "driving": 100,
          "serving": 2520,
          "waiting": 0,
          "break": 0,
          "commuting": 330,
          "parking": 360
        }
      }
    }
  ]
}


As parking time is specified in clustering settings, each stop with clustered job has an extra parking property:

          "parking": {
            "start": "2020-05-01T09:00:11Z",
            "end": "2020-05-01T09:02:11Z"
          },

Here, two minutes are planned for parking car at the stop location.

Activities in such stops have commute property which contains information about time, location, distance of commute trip. The property is split into forward and backward parts:

            {
              "jobId": "job1",
              "type": "delivery",
              "location": {
                "lat": 52.5253538,
                "lng": 13.4525549
              },
              "time": {
                "start": "2020-05-01T09:12:11Z",
                "end": "2020-05-01T09:15:11Z"
              },
              "commute": {
                "forward": {
                  "location": {
                    "lat": 52.5254256,
                    "lng": 13.4527159
                  },
                  "distance": 14.0,
                  "time": {
                    "start": "2020-05-01T09:12:01Z",
                    "end": "2020-05-01T09:12:11Z"
                  }
                },
                "backward": {
                  "location": {
                    "lat": 52.5253342,
                    "lng": 13.4533489
                  },
                  "distance": 54.0,
                  "time": {
                    "start": "2020-05-01T09:15:11Z",
                    "end": "2020-05-01T09:16:01Z"
                  }
                }
              }

Here forward specifies information how to reach activity and backward - how to get back to the stop after the job is served. Original time on activity specifies actual service time.

Vicinity clustering with return

This examples demonstrates a return type of visit: after each job visit, the driver has to return to the stop point. Check previous example for other details.

Problem

{
  "plan": {
    "jobs": [
      {
        "id": "job1",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5253538,
                  "lng": 13.4525549
                },
                "duration": 300
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job2",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5254256,
                  "lng": 13.4527159
                },
                "duration": 300
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job3",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5255366,
                  "lng": 13.4530162
                },
                "duration": 300
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job4",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5253342,
                  "lng": 13.4533489
                },
                "duration": 300
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job5",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5239243,
                  "lng": 13.4545827
                },
                "duration": 300
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job6",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5223315,
                  "lng": 13.4555697
                },
                "duration": 300
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job7",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5240157,
                  "lng": 13.4572220
                },
                "duration": 300
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job8",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5243028,
                  "lng": 13.45952868
                },
                "duration": 300
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job9",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5241593,
                  "lng": 13.4589815
                },
                "duration": 300
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job10",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5239896,
                  "lng": 13.4600115
                },
                "duration": 300
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job11",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5250209,
                  "lng": 13.4567821
                },
                "duration": 300
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job12",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5223184,
                  "lng": 13.4551942
                },
                "duration": 300
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      }
    ],
    "clustering": {
      "type": "vicinity",
      "profile": {
        "matrix": "car",
        "scale": 10
      },
      "threshold": {
        "duration": 120,
        "distance": 100
      },
      "visiting": "return",
      "serving": {
        "type": "fixed",
        "value": 180,
        "parking": 120
      }
    }
  },
  "fleet": {
    "vehicles": [
      {
        "typeId": "vehicle",
        "vehicleIds": [
          "vehicle_1"
        ],
        "profile": {
          "matrix": "car"
        },
        "costs": {
          "fixed": 25,
          "distance": 0.0001,
          "time": 0.005
        },
        "shifts": [
          {
            "start": {
              "earliest": "2020-05-01T09:00:00.00Z",
              "location": {
                "lat": 52.5262872,
                "lng": 13.4532952
              }
            }
          }
        ],
        "capacity": [
          20
        ]
      }
    ],
    "profiles": [
      {
        "name": "car",
        "type": "car"
      }
    ]
  }
}

Solution

{
  "statistic": {
    "cost": 42.5984,
    "distance": 984,
    "duration": 3500,
    "times": {
      "driving": 100,
      "serving": 2520,
      "waiting": 0,
      "break": 0,
      "commuting": 520,
      "parking": 360
    }
  },
  "tours": [
    {
      "vehicleId": "vehicle_1",
      "typeId": "vehicle",
      "shiftIndex": 0,
      "stops": [
        {
          "location": {
            "lat": 52.5262872,
            "lng": 13.4532952
          },
          "time": {
            "arrival": "2020-05-01T09:00:00Z",
            "departure": "2020-05-01T09:00:00Z"
          },
          "distance": 0,
          "load": [
            12
          ],
          "activities": [
            {
              "jobId": "departure",
              "type": "departure"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5253342,
            "lng": 13.4533489
          },
          "time": {
            "arrival": "2020-05-01T09:00:11Z",
            "departure": "2020-05-01T09:18:11Z"
          },
          "distance": 106,
          "load": [
            8
          ],
          "parking": {
            "start": "2020-05-01T09:00:11Z",
            "end": "2020-05-01T09:02:11Z"
          },
          "activities": [
            {
              "jobId": "job4",
              "type": "delivery",
              "location": {
                "lat": 52.5253342,
                "lng": 13.4533489
              },
              "time": {
                "start": "2020-05-01T09:02:11Z",
                "end": "2020-05-01T09:05:11Z"
              },
              "commute": {}
            },
            {
              "jobId": "job3",
              "type": "delivery",
              "location": {
                "lat": 52.5255366,
                "lng": 13.4530162
              },
              "time": {
                "start": "2020-05-01T09:05:41Z",
                "end": "2020-05-01T09:08:41Z"
              },
              "commute": {
                "forward": {
                  "location": {
                    "lat": 52.5253342,
                    "lng": 13.4533489
                  },
                  "distance": 32.0,
                  "time": {
                    "start": "2020-05-01T09:05:11Z",
                    "end": "2020-05-01T09:05:41Z"
                  }
                },
                "backward": {
                  "location": {
                    "lat": 52.5253342,
                    "lng": 13.4533489
                  },
                  "distance": 32.0,
                  "time": {
                    "start": "2020-05-01T09:08:41Z",
                    "end": "2020-05-01T09:09:11Z"
                  }
                }
              }
            },
            {
              "jobId": "job2",
              "type": "delivery",
              "location": {
                "lat": 52.5254256,
                "lng": 13.4527159
              },
              "time": {
                "start": "2020-05-01T09:09:51Z",
                "end": "2020-05-01T09:12:51Z"
              },
              "commute": {
                "forward": {
                  "location": {
                    "lat": 52.5253342,
                    "lng": 13.4533489
                  },
                  "distance": 44.0,
                  "time": {
                    "start": "2020-05-01T09:09:11Z",
                    "end": "2020-05-01T09:09:51Z"
                  }
                },
                "backward": {
                  "location": {
                    "lat": 52.5253342,
                    "lng": 13.4533489
                  },
                  "distance": 44.0,
                  "time": {
                    "start": "2020-05-01T09:12:51Z",
                    "end": "2020-05-01T09:13:31Z"
                  }
                }
              }
            },
            {
              "jobId": "job1",
              "type": "delivery",
              "location": {
                "lat": 52.5253538,
                "lng": 13.4525549
              },
              "time": {
                "start": "2020-05-01T09:14:21Z",
                "end": "2020-05-01T09:17:21Z"
              },
              "commute": {
                "forward": {
                  "location": {
                    "lat": 52.5253342,
                    "lng": 13.4533489
                  },
                  "distance": 54.0,
                  "time": {
                    "start": "2020-05-01T09:13:31Z",
                    "end": "2020-05-01T09:14:21Z"
                  }
                },
                "backward": {
                  "location": {
                    "lat": 52.5253342,
                    "lng": 13.4533489
                  },
                  "distance": 54.0,
                  "time": {
                    "start": "2020-05-01T09:17:21Z",
                    "end": "2020-05-01T09:18:11Z"
                  }
                }
              }
            }
          ]
        },
        {
          "location": {
            "lat": 52.5239243,
            "lng": 13.4545827
          },
          "time": {
            "arrival": "2020-05-01T09:18:29Z",
            "departure": "2020-05-01T09:23:29Z"
          },
          "distance": 284,
          "load": [
            7
          ],
          "activities": [
            {
              "jobId": "job5",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5223315,
            "lng": 13.4555697
          },
          "time": {
            "arrival": "2020-05-01T09:23:48Z",
            "departure": "2020-05-01T09:32:48Z"
          },
          "distance": 473,
          "load": [
            5
          ],
          "parking": {
            "start": "2020-05-01T09:23:48Z",
            "end": "2020-05-01T09:25:48Z"
          },
          "activities": [
            {
              "jobId": "job6",
              "type": "delivery",
              "location": {
                "lat": 52.5223315,
                "lng": 13.4555697
              },
              "time": {
                "start": "2020-05-01T09:25:48Z",
                "end": "2020-05-01T09:28:48Z"
              },
              "commute": {}
            },
            {
              "jobId": "job12",
              "type": "delivery",
              "location": {
                "lat": 52.5223184,
                "lng": 13.4551942
              },
              "time": {
                "start": "2020-05-01T09:29:18Z",
                "end": "2020-05-01T09:32:18Z"
              },
              "commute": {
                "forward": {
                  "location": {
                    "lat": 52.5223315,
                    "lng": 13.4555697
                  },
                  "distance": 25.0,
                  "time": {
                    "start": "2020-05-01T09:28:48Z",
                    "end": "2020-05-01T09:29:18Z"
                  }
                },
                "backward": {
                  "location": {
                    "lat": 52.5223315,
                    "lng": 13.4555697
                  },
                  "distance": 25.0,
                  "time": {
                    "start": "2020-05-01T09:32:18Z",
                    "end": "2020-05-01T09:32:48Z"
                  }
                }
              }
            }
          ]
        },
        {
          "location": {
            "lat": 52.5240157,
            "lng": 13.457222
          },
          "time": {
            "arrival": "2020-05-01T09:33:10Z",
            "departure": "2020-05-01T09:38:10Z"
          },
          "distance": 691,
          "load": [
            4
          ],
          "activities": [
            {
              "jobId": "job7",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5250209,
            "lng": 13.4567821
          },
          "time": {
            "arrival": "2020-05-01T09:38:22Z",
            "departure": "2020-05-01T09:43:22Z"
          },
          "distance": 807,
          "load": [
            3
          ],
          "activities": [
            {
              "jobId": "job11",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5241593,
            "lng": 13.4589815
          },
          "time": {
            "arrival": "2020-05-01T09:43:40Z",
            "departure": "2020-05-01T09:58:20Z"
          },
          "distance": 984,
          "load": [
            0
          ],
          "parking": {
            "start": "2020-05-01T09:43:40Z",
            "end": "2020-05-01T09:45:40Z"
          },
          "activities": [
            {
              "jobId": "job9",
              "type": "delivery",
              "location": {
                "lat": 52.5241593,
                "lng": 13.4589815
              },
              "time": {
                "start": "2020-05-01T09:45:40Z",
                "end": "2020-05-01T09:48:40Z"
              },
              "commute": {}
            },
            {
              "jobId": "job8",
              "type": "delivery",
              "location": {
                "lat": 52.5243028,
                "lng": 13.45952868
              },
              "time": {
                "start": "2020-05-01T09:49:20Z",
                "end": "2020-05-01T09:52:20Z"
              },
              "commute": {
                "forward": {
                  "location": {
                    "lat": 52.5241593,
                    "lng": 13.4589815
                  },
                  "distance": 40.0,
                  "time": {
                    "start": "2020-05-01T09:48:40Z",
                    "end": "2020-05-01T09:49:20Z"
                  }
                },
                "backward": {
                  "location": {
                    "lat": 52.5241593,
                    "lng": 13.4589815
                  },
                  "distance": 40.0,
                  "time": {
                    "start": "2020-05-01T09:52:20Z",
                    "end": "2020-05-01T09:53:00Z"
                  }
                }
              }
            },
            {
              "jobId": "job10",
              "type": "delivery",
              "location": {
                "lat": 52.5239896,
                "lng": 13.4600115
              },
              "time": {
                "start": "2020-05-01T09:54:10Z",
                "end": "2020-05-01T09:57:10Z"
              },
              "commute": {
                "forward": {
                  "location": {
                    "lat": 52.5241593,
                    "lng": 13.4589815
                  },
                  "distance": 72.0,
                  "time": {
                    "start": "2020-05-01T09:53:00Z",
                    "end": "2020-05-01T09:54:10Z"
                  }
                },
                "backward": {
                  "location": {
                    "lat": 52.5241593,
                    "lng": 13.4589815
                  },
                  "distance": 72.0,
                  "time": {
                    "start": "2020-05-01T09:57:10Z",
                    "end": "2020-05-01T09:58:20Z"
                  }
                }
              }
            }
          ]
        }
      ],
      "statistic": {
        "cost": 42.5984,
        "distance": 984,
        "duration": 3500,
        "times": {
          "driving": 100,
          "serving": 2520,
          "waiting": 0,
          "break": 0,
          "commuting": 520,
          "parking": 360
        }
      }
    }
  ]
}


Objective examples

Examples in this section demonstrate how different objectives affect final solution. All of them use the same problem variant with different objectives.

Essentially, the problem definition has the following parameters:

  • 50 deliveries with demand 1
  • 5 vehicles with capacity 20
  • fixed vehicle cost is 20
  • no time windows

Information regarding objective schema can be found here.

Default behavior: fleet and cost minimization

Problem

{
  "plan": {
    "jobs": [
      {
        "id": "job1",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5697304,
                  "lng": 13.3848221
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job2",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5060419,
                  "lng": 13.5152641
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job3",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5421315,
                  "lng": 13.5189513
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job4",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5243421,
                  "lng": 13.4619776
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job5",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.4629002,
                  "lng": 13.4757055
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job6",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.4960479,
                  "lng": 13.3915876
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job7",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5372914,
                  "lng": 13.3996298
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job8",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5429597,
                  "lng": 13.3989552
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job9",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5678751,
                  "lng": 13.4231417
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job10",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.4945572,
                  "lng": 13.4698049
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job11",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.4989511,
                  "lng": 13.4740528
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job12",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.4658835,
                  "lng": 13.4461224
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job13",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5685168,
                  "lng": 13.3690720
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job14",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.4742821,
                  "lng": 13.3628588
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job15",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5650163,
                  "lng": 13.3027992
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job16",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5496702,
                  "lng": 13.4286263
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job17",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5058684,
                  "lng": 13.4750990
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job18",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5473416,
                  "lng": 13.3327894
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job19",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5276784,
                  "lng": 13.5465640
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job20",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5192039,
                  "lng": 13.3044440
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job21",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5228904,
                  "lng": 13.4418623
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job22",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.4828453,
                  "lng": 13.4363713
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job23",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5291335,
                  "lng": 13.3668934
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job24",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5261554,
                  "lng": 13.5062954
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job25",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5189653,
                  "lng": 13.3890068
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job26",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5090143,
                  "lng": 13.4368189
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job27",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.4940454,
                  "lng": 13.3788834
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job28",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5065998,
                  "lng": 13.3689955
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job29",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5473490,
                  "lng": 13.3733163
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job30",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.4695374,
                  "lng": 13.4914662
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job31",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.4868236,
                  "lng": 13.3353656
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job32",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.4661617,
                  "lng": 13.3226920
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job33",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.4917198,
                  "lng": 13.5251532
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job34",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5431264,
                  "lng": 13.4416407
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job35",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5426716,
                  "lng": 13.5161692
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job36",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.4708241,
                  "lng": 13.3598752
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job37",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.4737341,
                  "lng": 13.3866700
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job38",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5404107,
                  "lng": 13.3914127
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job39",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5492619,
                  "lng": 13.3693560
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job40",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.4827319,
                  "lng": 13.3157235
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job41",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.4711004,
                  "lng": 13.3321906
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job42",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.4871049,
                  "lng": 13.5423247
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job43",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5614441,
                  "lng": 13.4194712
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job44",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5414557,
                  "lng": 13.5276390
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job45",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5425207,
                  "lng": 13.4139155
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job46",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5632095,
                  "lng": 13.2940051
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job47",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5146285,
                  "lng": 13.2852959
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job48",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.4855438,
                  "lng": 13.3832067
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job49",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5279215,
                  "lng": 13.4995315
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job50",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.4959052,
                  "lng": 13.3539713
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      }
    ]
  },
  "fleet": {
    "vehicles": [
      {
        "typeId": "vehicle",
        "vehicleIds": [
          "vehicle_1",
          "vehicle_2",
          "vehicle_3",
          "vehicle_4",
          "vehicle_5"
        ],
        "profile": {
          "matrix": "car"
        },
        "costs": {
          "fixed": 20.0,
          "distance": 0.0002,
          "time": 0.005
        },
        "shifts": [
          {
            "start": {
              "earliest": "1970-01-01T00:00:00Z",
              "location": {
                "lat": 52.4664257,
                "lng": 13.2812488
              }
            },
            "end": {
              "latest": "1970-01-01T23:59:00Z",
              "location": {
                "lat": 52.4664257,
                "lng": 13.2812488
              }
            }
          }
        ],
        "capacity": [
          20
        ]
      }
    ],
    "profiles": [
      {
        "name": "car"
      }
    ]
  },
  "objectives": [
    [
      {
        "type": "minimize-unassigned"
      }
    ],
    [
      {
        "type": "minimize-tours"
      }
    ],
    [
      {
        "type": "minimize-cost"
      }
    ]
  ]
}

Solution

{
  "statistic": {
    "cost": 185.74319999999997,
    "distance": 115366,
    "duration": 20534,
    "times": {
      "driving": 11534,
      "serving": 9000,
      "waiting": 0,
      "break": 0
    }
  },
  "tours": [
    {
      "vehicleId": "vehicle_1",
      "typeId": "vehicle",
      "shiftIndex": 0,
      "stops": [
        {
          "location": {
            "lat": 52.4664257,
            "lng": 13.2812488
          },
          "time": {
            "arrival": "1970-01-01T00:00:00Z",
            "departure": "1970-01-01T00:00:00Z"
          },
          "distance": 0,
          "load": [
            10
          ],
          "activities": [
            {
              "jobId": "departure",
              "type": "departure"
            }
          ]
        },
        {
          "location": {
            "lat": 52.4827319,
            "lng": 13.3157235
          },
          "time": {
            "arrival": "1970-01-01T00:04:56Z",
            "departure": "1970-01-01T00:07:56Z"
          },
          "distance": 2960,
          "load": [
            9
          ],
          "activities": [
            {
              "jobId": "job40",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.4868236,
            "lng": 13.3353656
          },
          "time": {
            "arrival": "1970-01-01T00:10:17Z",
            "departure": "1970-01-01T00:13:17Z"
          },
          "distance": 4367,
          "load": [
            8
          ],
          "activities": [
            {
              "jobId": "job31",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.4959052,
            "lng": 13.3539713
          },
          "time": {
            "arrival": "1970-01-01T00:15:59Z",
            "departure": "1970-01-01T00:18:59Z"
          },
          "distance": 5983,
          "load": [
            7
          ],
          "activities": [
            {
              "jobId": "job50",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.4940454,
            "lng": 13.3788834
          },
          "time": {
            "arrival": "1970-01-01T00:21:49Z",
            "departure": "1970-01-01T00:24:49Z"
          },
          "distance": 7684,
          "load": [
            6
          ],
          "activities": [
            {
              "jobId": "job27",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.4960479,
            "lng": 13.3915876
          },
          "time": {
            "arrival": "1970-01-01T00:26:18Z",
            "departure": "1970-01-01T00:29:18Z"
          },
          "distance": 8573,
          "load": [
            5
          ],
          "activities": [
            {
              "jobId": "job6",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.4855438,
            "lng": 13.3832067
          },
          "time": {
            "arrival": "1970-01-01T00:31:28Z",
            "departure": "1970-01-01T00:34:28Z"
          },
          "distance": 9873,
          "load": [
            4
          ],
          "activities": [
            {
              "jobId": "job48",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.4742821,
            "lng": 13.3628588
          },
          "time": {
            "arrival": "1970-01-01T00:37:34Z",
            "departure": "1970-01-01T00:40:34Z"
          },
          "distance": 11737,
          "load": [
            3
          ],
          "activities": [
            {
              "jobId": "job14",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.4708241,
            "lng": 13.3598752
          },
          "time": {
            "arrival": "1970-01-01T00:41:17Z",
            "departure": "1970-01-01T00:44:17Z"
          },
          "distance": 12172,
          "load": [
            2
          ],
          "activities": [
            {
              "jobId": "job36",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.4711004,
            "lng": 13.3321906
          },
          "time": {
            "arrival": "1970-01-01T00:47:25Z",
            "departure": "1970-01-01T00:50:25Z"
          },
          "distance": 14050,
          "load": [
            1
          ],
          "activities": [
            {
              "jobId": "job41",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.4661617,
            "lng": 13.322692
          },
          "time": {
            "arrival": "1970-01-01T00:51:50Z",
            "departure": "1970-01-01T00:54:50Z"
          },
          "distance": 14897,
          "load": [
            0
          ],
          "activities": [
            {
              "jobId": "job32",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.4664257,
            "lng": 13.2812488
          },
          "time": {
            "arrival": "1970-01-01T00:59:31Z",
            "departure": "1970-01-01T00:59:31Z"
          },
          "distance": 17708,
          "load": [
            0
          ],
          "activities": [
            {
              "jobId": "arrival",
              "type": "arrival"
            }
          ]
        }
      ],
      "statistic": {
        "cost": 41.3966,
        "distance": 17708,
        "duration": 3571,
        "times": {
          "driving": 1771,
          "serving": 1800,
          "waiting": 0,
          "break": 0
        }
      }
    },
    {
      "vehicleId": "vehicle_4",
      "typeId": "vehicle",
      "shiftIndex": 0,
      "stops": [
        {
          "location": {
            "lat": 52.4664257,
            "lng": 13.2812488
          },
          "time": {
            "arrival": "1970-01-01T00:00:00Z",
            "departure": "1970-01-01T00:00:00Z"
          },
          "distance": 0,
          "load": [
            20
          ],
          "activities": [
            {
              "jobId": "departure",
              "type": "departure"
            }
          ]
        },
        {
          "location": {
            "lat": 52.4737341,
            "lng": 13.38667
          },
          "time": {
            "arrival": "1970-01-01T00:12:00Z",
            "departure": "1970-01-01T00:15:00Z"
          },
          "distance": 7195,
          "load": [
            19
          ],
          "activities": [
            {
              "jobId": "job37",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.4828453,
            "lng": 13.4363713
          },
          "time": {
            "arrival": "1970-01-01T00:20:52Z",
            "departure": "1970-01-01T00:23:52Z"
          },
          "distance": 10714,
          "load": [
            18
          ],
          "activities": [
            {
              "jobId": "job22",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.4945572,
            "lng": 13.4698049
          },
          "time": {
            "arrival": "1970-01-01T00:28:13Z",
            "departure": "1970-01-01T00:31:13Z"
          },
          "distance": 13329,
          "load": [
            17
          ],
          "activities": [
            {
              "jobId": "job10",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.4989511,
            "lng": 13.4740528
          },
          "time": {
            "arrival": "1970-01-01T00:32:10Z",
            "departure": "1970-01-01T00:35:10Z"
          },
          "distance": 13897,
          "load": [
            16
          ],
          "activities": [
            {
              "jobId": "job11",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5058684,
            "lng": 13.475099
          },
          "time": {
            "arrival": "1970-01-01T00:36:27Z",
            "departure": "1970-01-01T00:39:27Z"
          },
          "distance": 14670,
          "load": [
            15
          ],
          "activities": [
            {
              "jobId": "job17",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5090143,
            "lng": 13.4368189
          },
          "time": {
            "arrival": "1970-01-01T00:43:49Z",
            "departure": "1970-01-01T00:46:49Z"
          },
          "distance": 17287,
          "load": [
            14
          ],
          "activities": [
            {
              "jobId": "job26",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5228904,
            "lng": 13.4418623
          },
          "time": {
            "arrival": "1970-01-01T00:49:27Z",
            "departure": "1970-01-01T00:52:27Z"
          },
          "distance": 18869,
          "load": [
            13
          ],
          "activities": [
            {
              "jobId": "job21",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5243421,
            "lng": 13.4619776
          },
          "time": {
            "arrival": "1970-01-01T00:54:44Z",
            "departure": "1970-01-01T00:57:44Z"
          },
          "distance": 20241,
          "load": [
            12
          ],
          "activities": [
            {
              "jobId": "job4",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5279215,
            "lng": 13.4995315
          },
          "time": {
            "arrival": "1970-01-01T01:02:01Z",
            "departure": "1970-01-01T01:05:01Z"
          },
          "distance": 22815,
          "load": [
            11
          ],
          "activities": [
            {
              "jobId": "job49",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5261554,
            "lng": 13.5062954
          },
          "time": {
            "arrival": "1970-01-01T01:05:51Z",
            "departure": "1970-01-01T01:08:51Z"
          },
          "distance": 23313,
          "load": [
            10
          ],
          "activities": [
            {
              "jobId": "job24",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5426716,
            "lng": 13.5161692
          },
          "time": {
            "arrival": "1970-01-01T01:12:07Z",
            "departure": "1970-01-01T01:15:07Z"
          },
          "distance": 25269,
          "load": [
            9
          ],
          "activities": [
            {
              "jobId": "job35",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5421315,
            "lng": 13.5189513
          },
          "time": {
            "arrival": "1970-01-01T01:15:27Z",
            "departure": "1970-01-01T01:18:27Z"
          },
          "distance": 25467,
          "load": [
            8
          ],
          "activities": [
            {
              "jobId": "job3",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5414557,
            "lng": 13.527639
          },
          "time": {
            "arrival": "1970-01-01T01:19:26Z",
            "departure": "1970-01-01T01:22:26Z"
          },
          "distance": 26060,
          "load": [
            7
          ],
          "activities": [
            {
              "jobId": "job44",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5276784,
            "lng": 13.546564
          },
          "time": {
            "arrival": "1970-01-01T01:25:46Z",
            "departure": "1970-01-01T01:28:46Z"
          },
          "distance": 28059,
          "load": [
            6
          ],
          "activities": [
            {
              "jobId": "job19",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5060419,
            "lng": 13.5152641
          },
          "time": {
            "arrival": "1970-01-01T01:34:07Z",
            "departure": "1970-01-01T01:37:07Z"
          },
          "distance": 31268,
          "load": [
            5
          ],
          "activities": [
            {
              "jobId": "job2",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.4917198,
            "lng": 13.5251532
          },
          "time": {
            "arrival": "1970-01-01T01:40:00Z",
            "departure": "1970-01-01T01:43:00Z"
          },
          "distance": 32997,
          "load": [
            4
          ],
          "activities": [
            {
              "jobId": "job33",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.4871049,
            "lng": 13.5423247
          },
          "time": {
            "arrival": "1970-01-01T01:45:07Z",
            "departure": "1970-01-01T01:48:07Z"
          },
          "distance": 34269,
          "load": [
            3
          ],
          "activities": [
            {
              "jobId": "job42",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.4695374,
            "lng": 13.4914662
          },
          "time": {
            "arrival": "1970-01-01T01:54:43Z",
            "departure": "1970-01-01T01:57:43Z"
          },
          "distance": 38233,
          "load": [
            2
          ],
          "activities": [
            {
              "jobId": "job30",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.4629002,
            "lng": 13.4757055
          },
          "time": {
            "arrival": "1970-01-01T01:59:53Z",
            "departure": "1970-01-01T02:02:53Z"
          },
          "distance": 39532,
          "load": [
            1
          ],
          "activities": [
            {
              "jobId": "job5",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.4658835,
            "lng": 13.4461224
          },
          "time": {
            "arrival": "1970-01-01T02:06:16Z",
            "departure": "1970-01-01T02:09:16Z"
          },
          "distance": 41566,
          "load": [
            0
          ],
          "activities": [
            {
              "jobId": "job12",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.4664257,
            "lng": 13.2812488
          },
          "time": {
            "arrival": "1970-01-01T02:27:54Z",
            "departure": "1970-01-01T02:27:54Z"
          },
          "distance": 52748,
          "load": [
            0
          ],
          "activities": [
            {
              "jobId": "arrival",
              "type": "arrival"
            }
          ]
        }
      ],
      "statistic": {
        "cost": 74.91959999999997,
        "distance": 52748,
        "duration": 8874,
        "times": {
          "driving": 5274,
          "serving": 3600,
          "waiting": 0,
          "break": 0
        }
      }
    },
    {
      "vehicleId": "vehicle_5",
      "typeId": "vehicle",
      "shiftIndex": 0,
      "stops": [
        {
          "location": {
            "lat": 52.4664257,
            "lng": 13.2812488
          },
          "time": {
            "arrival": "1970-01-01T00:00:00Z",
            "departure": "1970-01-01T00:00:00Z"
          },
          "distance": 0,
          "load": [
            20
          ],
          "activities": [
            {
              "jobId": "departure",
              "type": "departure"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5065998,
            "lng": 13.3689955
          },
          "time": {
            "arrival": "1970-01-01T00:12:24Z",
            "departure": "1970-01-01T00:15:24Z"
          },
          "distance": 7442,
          "load": [
            19
          ],
          "activities": [
            {
              "jobId": "job28",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5189653,
            "lng": 13.3890068
          },
          "time": {
            "arrival": "1970-01-01T00:18:37Z",
            "departure": "1970-01-01T00:21:37Z"
          },
          "distance": 9374,
          "load": [
            18
          ],
          "activities": [
            {
              "jobId": "job25",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5291335,
            "lng": 13.3668934
          },
          "time": {
            "arrival": "1970-01-01T00:24:45Z",
            "departure": "1970-01-01T00:27:45Z"
          },
          "distance": 11251,
          "load": [
            17
          ],
          "activities": [
            {
              "jobId": "job23",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5404107,
            "lng": 13.3914127
          },
          "time": {
            "arrival": "1970-01-01T00:31:13Z",
            "departure": "1970-01-01T00:34:13Z"
          },
          "distance": 13332,
          "load": [
            16
          ],
          "activities": [
            {
              "jobId": "job38",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5372914,
            "lng": 13.3996298
          },
          "time": {
            "arrival": "1970-01-01T00:35:19Z",
            "departure": "1970-01-01T00:38:19Z"
          },
          "distance": 13988,
          "load": [
            15
          ],
          "activities": [
            {
              "jobId": "job7",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5429597,
            "lng": 13.3989552
          },
          "time": {
            "arrival": "1970-01-01T00:39:22Z",
            "departure": "1970-01-01T00:42:22Z"
          },
          "distance": 14621,
          "load": [
            14
          ],
          "activities": [
            {
              "jobId": "job8",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5425207,
            "lng": 13.4139155
          },
          "time": {
            "arrival": "1970-01-01T00:44:03Z",
            "departure": "1970-01-01T00:47:03Z"
          },
          "distance": 15635,
          "load": [
            13
          ],
          "activities": [
            {
              "jobId": "job45",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5431264,
            "lng": 13.4416407
          },
          "time": {
            "arrival": "1970-01-01T00:50:11Z",
            "departure": "1970-01-01T00:53:11Z"
          },
          "distance": 17513,
          "load": [
            12
          ],
          "activities": [
            {
              "jobId": "job34",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5496702,
            "lng": 13.4286263
          },
          "time": {
            "arrival": "1970-01-01T00:55:05Z",
            "departure": "1970-01-01T00:58:05Z"
          },
          "distance": 18656,
          "load": [
            11
          ],
          "activities": [
            {
              "jobId": "job16",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5614441,
            "lng": 13.4194712
          },
          "time": {
            "arrival": "1970-01-01T01:00:30Z",
            "departure": "1970-01-01T01:03:30Z"
          },
          "distance": 20106,
          "load": [
            10
          ],
          "activities": [
            {
              "jobId": "job43",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5678751,
            "lng": 13.4231417
          },
          "time": {
            "arrival": "1970-01-01T01:04:46Z",
            "departure": "1970-01-01T01:07:46Z"
          },
          "distance": 20864,
          "load": [
            9
          ],
          "activities": [
            {
              "jobId": "job9",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5697304,
            "lng": 13.3848221
          },
          "time": {
            "arrival": "1970-01-01T01:12:06Z",
            "departure": "1970-01-01T01:15:06Z"
          },
          "distance": 23465,
          "load": [
            8
          ],
          "activities": [
            {
              "jobId": "job1",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5685168,
            "lng": 13.369072
          },
          "time": {
            "arrival": "1970-01-01T01:16:53Z",
            "departure": "1970-01-01T01:19:53Z"
          },
          "distance": 24539,
          "load": [
            7
          ],
          "activities": [
            {
              "jobId": "job13",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.547349,
            "lng": 13.3733163
          },
          "time": {
            "arrival": "1970-01-01T01:23:50Z",
            "departure": "1970-01-01T01:26:50Z"
          },
          "distance": 26913,
          "load": [
            6
          ],
          "activities": [
            {
              "jobId": "job29",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5492619,
            "lng": 13.369356
          },
          "time": {
            "arrival": "1970-01-01T01:27:24Z",
            "departure": "1970-01-01T01:30:24Z"
          },
          "distance": 27255,
          "load": [
            5
          ],
          "activities": [
            {
              "jobId": "job39",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5473416,
            "lng": 13.3327894
          },
          "time": {
            "arrival": "1970-01-01T01:34:32Z",
            "departure": "1970-01-01T01:37:32Z"
          },
          "distance": 29739,
          "load": [
            4
          ],
          "activities": [
            {
              "jobId": "job18",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5650163,
            "lng": 13.3027992
          },
          "time": {
            "arrival": "1970-01-01T01:42:15Z",
            "departure": "1970-01-01T01:45:15Z"
          },
          "distance": 32566,
          "load": [
            3
          ],
          "activities": [
            {
              "jobId": "job15",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5632095,
            "lng": 13.2940051
          },
          "time": {
            "arrival": "1970-01-01T01:46:18Z",
            "departure": "1970-01-01T01:49:18Z"
          },
          "distance": 33194,
          "load": [
            2
          ],
          "activities": [
            {
              "jobId": "job46",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5192039,
            "lng": 13.304444
          },
          "time": {
            "arrival": "1970-01-01T01:57:33Z",
            "departure": "1970-01-01T02:00:33Z"
          },
          "distance": 38143,
          "load": [
            1
          ],
          "activities": [
            {
              "jobId": "job20",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5146285,
            "lng": 13.2852959
          },
          "time": {
            "arrival": "1970-01-01T02:02:52Z",
            "departure": "1970-01-01T02:05:52Z"
          },
          "distance": 39537,
          "load": [
            0
          ],
          "activities": [
            {
              "jobId": "job47",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.4664257,
            "lng": 13.2812488
          },
          "time": {
            "arrival": "1970-01-01T02:14:49Z",
            "departure": "1970-01-01T02:14:49Z"
          },
          "distance": 44910,
          "load": [
            0
          ],
          "activities": [
            {
              "jobId": "arrival",
              "type": "arrival"
            }
          ]
        }
      ],
      "statistic": {
        "cost": 69.42699999999999,
        "distance": 44910,
        "duration": 8089,
        "times": {
          "driving": 4489,
          "serving": 3600,
          "waiting": 0,
          "break": 0
        }
      }
    }
  ],
  "unassigned": []
}


By default, the first objective for the solver is to minimize amount of unassigned jobs, then fleet usage, and the last is total cost minimization:

  "objectives": [
    [
      {
        "type": "minimize-unassigned"
      }
    ],
    [
      {
        "type": "minimize-tours"
      }
    ],
    [
      {
        "type": "minimize-cost"
      }
    ]
  ]

As result, solution has minimum amount of vehicles used to serve all jobs (3).

Note, that load between these vehicles is not equally distributed as it increases the total cost.

Balance max load

Problem

{
  "plan": {
    "jobs": [
      {
        "id": "job1",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5697304,
                  "lng": 13.3848221
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job2",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5060419,
                  "lng": 13.5152641
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job3",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5421315,
                  "lng": 13.5189513
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job4",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5243421,
                  "lng": 13.4619776
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job5",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.4629002,
                  "lng": 13.4757055
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job6",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.4960479,
                  "lng": 13.3915876
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job7",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5372914,
                  "lng": 13.3996298
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job8",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5429597,
                  "lng": 13.3989552
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job9",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5678751,
                  "lng": 13.4231417
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job10",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.4945572,
                  "lng": 13.4698049
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job11",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.4989511,
                  "lng": 13.4740528
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job12",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.4658835,
                  "lng": 13.4461224
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job13",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5685168,
                  "lng": 13.3690720
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job14",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.4742821,
                  "lng": 13.3628588
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job15",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5650163,
                  "lng": 13.3027992
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job16",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5496702,
                  "lng": 13.4286263
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job17",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5058684,
                  "lng": 13.4750990
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job18",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5473416,
                  "lng": 13.3327894
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job19",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5276784,
                  "lng": 13.5465640
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job20",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5192039,
                  "lng": 13.3044440
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job21",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5228904,
                  "lng": 13.4418623
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job22",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.4828453,
                  "lng": 13.4363713
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job23",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5291335,
                  "lng": 13.3668934
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job24",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5261554,
                  "lng": 13.5062954
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job25",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5189653,
                  "lng": 13.3890068
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job26",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5090143,
                  "lng": 13.4368189
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job27",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.4940454,
                  "lng": 13.3788834
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job28",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5065998,
                  "lng": 13.3689955
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job29",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5473490,
                  "lng": 13.3733163
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job30",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.4695374,
                  "lng": 13.4914662
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job31",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.4868236,
                  "lng": 13.3353656
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job32",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.4661617,
                  "lng": 13.3226920
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job33",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.4917198,
                  "lng": 13.5251532
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job34",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5431264,
                  "lng": 13.4416407
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job35",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5426716,
                  "lng": 13.5161692
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job36",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.4708241,
                  "lng": 13.3598752
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job37",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.4737341,
                  "lng": 13.3866700
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job38",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5404107,
                  "lng": 13.3914127
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job39",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5492619,
                  "lng": 13.3693560
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job40",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.4827319,
                  "lng": 13.3157235
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job41",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.4711004,
                  "lng": 13.3321906
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job42",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.4871049,
                  "lng": 13.5423247
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job43",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5614441,
                  "lng": 13.4194712
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job44",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5414557,
                  "lng": 13.5276390
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job45",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5425207,
                  "lng": 13.4139155
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job46",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5632095,
                  "lng": 13.2940051
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job47",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5146285,
                  "lng": 13.2852959
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job48",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.4855438,
                  "lng": 13.3832067
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job49",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5279215,
                  "lng": 13.4995315
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job50",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.4959052,
                  "lng": 13.3539713
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      }
    ]
  },
  "fleet": {
    "vehicles": [
      {
        "typeId": "vehicle",
        "vehicleIds": [
          "vehicle_1",
          "vehicle_2",
          "vehicle_3",
          "vehicle_4",
          "vehicle_5"
        ],
        "profile": {
          "matrix": "car"
        },
        "costs": {
          "fixed": 20.0,
          "distance": 0.0002,
          "time": 0.005
        },
        "shifts": [
          {
            "start": {
              "earliest": "1970-01-01T00:00:00Z",
              "location": {
                "lat": 52.4664257,
                "lng": 13.2812488
              }
            },
            "end": {
              "latest": "1970-01-01T23:59:00Z",
              "location": {
                "lat": 52.4664257,
                "lng": 13.2812488
              }
            }
          }
        ],
        "capacity": [
          20
        ]
      }
    ],
    "profiles": [
      {
        "name": "car"
      }
    ]
  },
  "objectives": [
    [
      {
        "type": "minimize-unassigned"
      }
    ],
    [
      {
        "type": "minimize-cost"
      },
      {
        "type": "balance-max-load",
        "options": {
          "threshold": 0.075
        }
      }
    ]
  ]
}

Solution

{
  "statistic": {
    "cost": 223.78519999999997,
    "distance": 141126,
    "duration": 23112,
    "times": {
      "driving": 14112,
      "serving": 9000,
      "waiting": 0,
      "break": 0
    }
  },
  "tours": [
    {
      "vehicleId": "vehicle_1",
      "typeId": "vehicle",
      "shiftIndex": 0,
      "stops": [
        {
          "location": {
            "lat": 52.4664257,
            "lng": 13.2812488
          },
          "time": {
            "arrival": "1970-01-01T00:00:00Z",
            "departure": "1970-01-01T00:00:00Z"
          },
          "distance": 0,
          "load": [
            12
          ],
          "activities": [
            {
              "jobId": "departure",
              "type": "departure"
            }
          ]
        },
        {
          "location": {
            "lat": 52.4827319,
            "lng": 13.3157235
          },
          "time": {
            "arrival": "1970-01-01T00:04:56Z",
            "departure": "1970-01-01T00:07:56Z"
          },
          "distance": 2960,
          "load": [
            11
          ],
          "activities": [
            {
              "jobId": "job40",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.4868236,
            "lng": 13.3353656
          },
          "time": {
            "arrival": "1970-01-01T00:10:17Z",
            "departure": "1970-01-01T00:13:17Z"
          },
          "distance": 4367,
          "load": [
            10
          ],
          "activities": [
            {
              "jobId": "job31",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.4959052,
            "lng": 13.3539713
          },
          "time": {
            "arrival": "1970-01-01T00:15:59Z",
            "departure": "1970-01-01T00:18:59Z"
          },
          "distance": 5983,
          "load": [
            9
          ],
          "activities": [
            {
              "jobId": "job50",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5065998,
            "lng": 13.3689955
          },
          "time": {
            "arrival": "1970-01-01T00:21:36Z",
            "departure": "1970-01-01T00:24:36Z"
          },
          "distance": 7549,
          "load": [
            8
          ],
          "activities": [
            {
              "jobId": "job28",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.4940454,
            "lng": 13.3788834
          },
          "time": {
            "arrival": "1970-01-01T00:27:11Z",
            "departure": "1970-01-01T00:30:11Z"
          },
          "distance": 9099,
          "load": [
            7
          ],
          "activities": [
            {
              "jobId": "job27",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.4960479,
            "lng": 13.3915876
          },
          "time": {
            "arrival": "1970-01-01T00:31:40Z",
            "departure": "1970-01-01T00:34:40Z"
          },
          "distance": 9988,
          "load": [
            6
          ],
          "activities": [
            {
              "jobId": "job6",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.4855438,
            "lng": 13.3832067
          },
          "time": {
            "arrival": "1970-01-01T00:36:50Z",
            "departure": "1970-01-01T00:39:50Z"
          },
          "distance": 11288,
          "load": [
            5
          ],
          "activities": [
            {
              "jobId": "job48",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.4737341,
            "lng": 13.38667
          },
          "time": {
            "arrival": "1970-01-01T00:42:04Z",
            "departure": "1970-01-01T00:45:04Z"
          },
          "distance": 12623,
          "load": [
            4
          ],
          "activities": [
            {
              "jobId": "job37",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.4742821,
            "lng": 13.3628588
          },
          "time": {
            "arrival": "1970-01-01T00:47:46Z",
            "departure": "1970-01-01T00:50:46Z"
          },
          "distance": 14239,
          "load": [
            3
          ],
          "activities": [
            {
              "jobId": "job14",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.4708241,
            "lng": 13.3598752
          },
          "time": {
            "arrival": "1970-01-01T00:51:29Z",
            "departure": "1970-01-01T00:54:29Z"
          },
          "distance": 14674,
          "load": [
            2
          ],
          "activities": [
            {
              "jobId": "job36",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.4711004,
            "lng": 13.3321906
          },
          "time": {
            "arrival": "1970-01-01T00:57:37Z",
            "departure": "1970-01-01T01:00:37Z"
          },
          "distance": 16552,
          "load": [
            1
          ],
          "activities": [
            {
              "jobId": "job41",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.4661617,
            "lng": 13.322692
          },
          "time": {
            "arrival": "1970-01-01T01:02:02Z",
            "departure": "1970-01-01T01:05:02Z"
          },
          "distance": 17399,
          "load": [
            0
          ],
          "activities": [
            {
              "jobId": "job32",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.4664257,
            "lng": 13.2812488
          },
          "time": {
            "arrival": "1970-01-01T01:09:43Z",
            "departure": "1970-01-01T01:09:43Z"
          },
          "distance": 20210,
          "load": [
            0
          ],
          "activities": [
            {
              "jobId": "arrival",
              "type": "arrival"
            }
          ]
        }
      ],
      "statistic": {
        "cost": 44.956999999999994,
        "distance": 20210,
        "duration": 4183,
        "times": {
          "driving": 2023,
          "serving": 2160,
          "waiting": 0,
          "break": 0
        }
      }
    },
    {
      "vehicleId": "vehicle_3",
      "typeId": "vehicle",
      "shiftIndex": 0,
      "stops": [
        {
          "location": {
            "lat": 52.4664257,
            "lng": 13.2812488
          },
          "time": {
            "arrival": "1970-01-01T00:00:00Z",
            "departure": "1970-01-01T00:00:00Z"
          },
          "distance": 0,
          "load": [
            12
          ],
          "activities": [
            {
              "jobId": "departure",
              "type": "departure"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5189653,
            "lng": 13.3890068
          },
          "time": {
            "arrival": "1970-01-01T00:15:36Z",
            "departure": "1970-01-01T00:18:36Z"
          },
          "distance": 9357,
          "load": [
            11
          ],
          "activities": [
            {
              "jobId": "job25",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5372914,
            "lng": 13.3996298
          },
          "time": {
            "arrival": "1970-01-01T00:22:12Z",
            "departure": "1970-01-01T00:25:12Z"
          },
          "distance": 11520,
          "load": [
            10
          ],
          "activities": [
            {
              "jobId": "job7",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5425207,
            "lng": 13.4139155
          },
          "time": {
            "arrival": "1970-01-01T00:27:05Z",
            "departure": "1970-01-01T00:30:05Z"
          },
          "distance": 12649,
          "load": [
            9
          ],
          "activities": [
            {
              "jobId": "job45",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5429597,
            "lng": 13.3989552
          },
          "time": {
            "arrival": "1970-01-01T00:31:46Z",
            "departure": "1970-01-01T00:34:46Z"
          },
          "distance": 13663,
          "load": [
            8
          ],
          "activities": [
            {
              "jobId": "job8",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5404107,
            "lng": 13.3914127
          },
          "time": {
            "arrival": "1970-01-01T00:35:44Z",
            "departure": "1970-01-01T00:38:44Z"
          },
          "distance": 14247,
          "load": [
            7
          ],
          "activities": [
            {
              "jobId": "job38",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.547349,
            "lng": 13.3733163
          },
          "time": {
            "arrival": "1970-01-01T00:41:09Z",
            "departure": "1970-01-01T00:44:09Z"
          },
          "distance": 15695,
          "load": [
            6
          ],
          "activities": [
            {
              "jobId": "job29",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5492619,
            "lng": 13.369356
          },
          "time": {
            "arrival": "1970-01-01T00:44:43Z",
            "departure": "1970-01-01T00:47:43Z"
          },
          "distance": 16037,
          "load": [
            5
          ],
          "activities": [
            {
              "jobId": "job39",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5473416,
            "lng": 13.3327894
          },
          "time": {
            "arrival": "1970-01-01T00:51:51Z",
            "departure": "1970-01-01T00:54:51Z"
          },
          "distance": 18521,
          "load": [
            4
          ],
          "activities": [
            {
              "jobId": "job18",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5650163,
            "lng": 13.3027992
          },
          "time": {
            "arrival": "1970-01-01T00:59:34Z",
            "departure": "1970-01-01T01:02:34Z"
          },
          "distance": 21348,
          "load": [
            3
          ],
          "activities": [
            {
              "jobId": "job15",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5632095,
            "lng": 13.2940051
          },
          "time": {
            "arrival": "1970-01-01T01:03:37Z",
            "departure": "1970-01-01T01:06:37Z"
          },
          "distance": 21976,
          "load": [
            2
          ],
          "activities": [
            {
              "jobId": "job46",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5192039,
            "lng": 13.304444
          },
          "time": {
            "arrival": "1970-01-01T01:14:52Z",
            "departure": "1970-01-01T01:17:52Z"
          },
          "distance": 26925,
          "load": [
            1
          ],
          "activities": [
            {
              "jobId": "job20",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5146285,
            "lng": 13.2852959
          },
          "time": {
            "arrival": "1970-01-01T01:20:11Z",
            "departure": "1970-01-01T01:23:11Z"
          },
          "distance": 28319,
          "load": [
            0
          ],
          "activities": [
            {
              "jobId": "job47",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.4664257,
            "lng": 13.2812488
          },
          "time": {
            "arrival": "1970-01-01T01:32:08Z",
            "departure": "1970-01-01T01:32:08Z"
          },
          "distance": 33692,
          "load": [
            0
          ],
          "activities": [
            {
              "jobId": "arrival",
              "type": "arrival"
            }
          ]
        }
      ],
      "statistic": {
        "cost": 54.37839999999999,
        "distance": 33692,
        "duration": 5528,
        "times": {
          "driving": 3368,
          "serving": 2160,
          "waiting": 0,
          "break": 0
        }
      }
    },
    {
      "vehicleId": "vehicle_4",
      "typeId": "vehicle",
      "shiftIndex": 0,
      "stops": [
        {
          "location": {
            "lat": 52.4664257,
            "lng": 13.2812488
          },
          "time": {
            "arrival": "1970-01-01T00:00:00Z",
            "departure": "1970-01-01T00:00:00Z"
          },
          "distance": 0,
          "load": [
            13
          ],
          "activities": [
            {
              "jobId": "departure",
              "type": "departure"
            }
          ]
        },
        {
          "location": {
            "lat": 52.4828453,
            "lng": 13.4363713
          },
          "time": {
            "arrival": "1970-01-01T00:17:48Z",
            "departure": "1970-01-01T00:20:48Z"
          },
          "distance": 10676,
          "load": [
            12
          ],
          "activities": [
            {
              "jobId": "job22",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.4945572,
            "lng": 13.4698049
          },
          "time": {
            "arrival": "1970-01-01T00:25:09Z",
            "departure": "1970-01-01T00:28:09Z"
          },
          "distance": 13291,
          "load": [
            11
          ],
          "activities": [
            {
              "jobId": "job10",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.4989511,
            "lng": 13.4740528
          },
          "time": {
            "arrival": "1970-01-01T00:29:06Z",
            "departure": "1970-01-01T00:32:06Z"
          },
          "distance": 13859,
          "load": [
            10
          ],
          "activities": [
            {
              "jobId": "job11",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5058684,
            "lng": 13.475099
          },
          "time": {
            "arrival": "1970-01-01T00:33:23Z",
            "departure": "1970-01-01T00:36:23Z"
          },
          "distance": 14632,
          "load": [
            9
          ],
          "activities": [
            {
              "jobId": "job17",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5243421,
            "lng": 13.4619776
          },
          "time": {
            "arrival": "1970-01-01T00:40:07Z",
            "departure": "1970-01-01T00:43:07Z"
          },
          "distance": 16872,
          "load": [
            8
          ],
          "activities": [
            {
              "jobId": "job4",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5228904,
            "lng": 13.4418623
          },
          "time": {
            "arrival": "1970-01-01T00:45:24Z",
            "departure": "1970-01-01T00:48:24Z"
          },
          "distance": 18244,
          "load": [
            7
          ],
          "activities": [
            {
              "jobId": "job21",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5431264,
            "lng": 13.4416407
          },
          "time": {
            "arrival": "1970-01-01T00:52:09Z",
            "departure": "1970-01-01T00:55:09Z"
          },
          "distance": 20497,
          "load": [
            6
          ],
          "activities": [
            {
              "jobId": "job34",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5496702,
            "lng": 13.4286263
          },
          "time": {
            "arrival": "1970-01-01T00:57:03Z",
            "departure": "1970-01-01T01:00:03Z"
          },
          "distance": 21640,
          "load": [
            5
          ],
          "activities": [
            {
              "jobId": "job16",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5614441,
            "lng": 13.4194712
          },
          "time": {
            "arrival": "1970-01-01T01:02:28Z",
            "departure": "1970-01-01T01:05:28Z"
          },
          "distance": 23090,
          "load": [
            4
          ],
          "activities": [
            {
              "jobId": "job43",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5678751,
            "lng": 13.4231417
          },
          "time": {
            "arrival": "1970-01-01T01:06:44Z",
            "departure": "1970-01-01T01:09:44Z"
          },
          "distance": 23848,
          "load": [
            3
          ],
          "activities": [
            {
              "jobId": "job9",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5697304,
            "lng": 13.3848221
          },
          "time": {
            "arrival": "1970-01-01T01:14:04Z",
            "departure": "1970-01-01T01:17:04Z"
          },
          "distance": 26449,
          "load": [
            2
          ],
          "activities": [
            {
              "jobId": "job1",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5685168,
            "lng": 13.369072
          },
          "time": {
            "arrival": "1970-01-01T01:18:51Z",
            "departure": "1970-01-01T01:21:51Z"
          },
          "distance": 27523,
          "load": [
            1
          ],
          "activities": [
            {
              "jobId": "job13",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5291335,
            "lng": 13.3668934
          },
          "time": {
            "arrival": "1970-01-01T01:29:10Z",
            "departure": "1970-01-01T01:32:10Z"
          },
          "distance": 31910,
          "load": [
            0
          ],
          "activities": [
            {
              "jobId": "job23",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.4664257,
            "lng": 13.2812488
          },
          "time": {
            "arrival": "1970-01-01T01:47:18Z",
            "departure": "1970-01-01T01:47:18Z"
          },
          "distance": 40988,
          "load": [
            0
          ],
          "activities": [
            {
              "jobId": "arrival",
              "type": "arrival"
            }
          ]
        }
      ],
      "statistic": {
        "cost": 60.38759999999999,
        "distance": 40988,
        "duration": 6438,
        "times": {
          "driving": 4098,
          "serving": 2340,
          "waiting": 0,
          "break": 0
        }
      }
    },
    {
      "vehicleId": "vehicle_5",
      "typeId": "vehicle",
      "shiftIndex": 0,
      "stops": [
        {
          "location": {
            "lat": 52.4664257,
            "lng": 13.2812488
          },
          "time": {
            "arrival": "1970-01-01T00:00:00Z",
            "departure": "1970-01-01T00:00:00Z"
          },
          "distance": 0,
          "load": [
            13
          ],
          "activities": [
            {
              "jobId": "departure",
              "type": "departure"
            }
          ]
        },
        {
          "location": {
            "lat": 52.4658835,
            "lng": 13.4461224
          },
          "time": {
            "arrival": "1970-01-01T00:18:38Z",
            "departure": "1970-01-01T00:21:38Z"
          },
          "distance": 11182,
          "load": [
            12
          ],
          "activities": [
            {
              "jobId": "job12",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.4629002,
            "lng": 13.4757055
          },
          "time": {
            "arrival": "1970-01-01T00:25:01Z",
            "departure": "1970-01-01T00:28:01Z"
          },
          "distance": 13216,
          "load": [
            11
          ],
          "activities": [
            {
              "jobId": "job5",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.4695374,
            "lng": 13.4914662
          },
          "time": {
            "arrival": "1970-01-01T00:30:11Z",
            "departure": "1970-01-01T00:33:11Z"
          },
          "distance": 14515,
          "load": [
            10
          ],
          "activities": [
            {
              "jobId": "job30",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.4871049,
            "lng": 13.5423247
          },
          "time": {
            "arrival": "1970-01-01T00:39:47Z",
            "departure": "1970-01-01T00:42:47Z"
          },
          "distance": 18479,
          "load": [
            9
          ],
          "activities": [
            {
              "jobId": "job42",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.4917198,
            "lng": 13.5251532
          },
          "time": {
            "arrival": "1970-01-01T00:44:54Z",
            "departure": "1970-01-01T00:47:54Z"
          },
          "distance": 19751,
          "load": [
            8
          ],
          "activities": [
            {
              "jobId": "job33",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5060419,
            "lng": 13.5152641
          },
          "time": {
            "arrival": "1970-01-01T00:50:47Z",
            "departure": "1970-01-01T00:53:47Z"
          },
          "distance": 21480,
          "load": [
            7
          ],
          "activities": [
            {
              "jobId": "job2",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5276784,
            "lng": 13.546564
          },
          "time": {
            "arrival": "1970-01-01T00:59:08Z",
            "departure": "1970-01-01T01:02:08Z"
          },
          "distance": 24689,
          "load": [
            6
          ],
          "activities": [
            {
              "jobId": "job19",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5414557,
            "lng": 13.527639
          },
          "time": {
            "arrival": "1970-01-01T01:05:28Z",
            "departure": "1970-01-01T01:08:28Z"
          },
          "distance": 26688,
          "load": [
            5
          ],
          "activities": [
            {
              "jobId": "job44",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5421315,
            "lng": 13.5189513
          },
          "time": {
            "arrival": "1970-01-01T01:09:27Z",
            "departure": "1970-01-01T01:12:27Z"
          },
          "distance": 27281,
          "load": [
            4
          ],
          "activities": [
            {
              "jobId": "job3",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5426716,
            "lng": 13.5161692
          },
          "time": {
            "arrival": "1970-01-01T01:12:47Z",
            "departure": "1970-01-01T01:15:47Z"
          },
          "distance": 27479,
          "load": [
            3
          ],
          "activities": [
            {
              "jobId": "job35",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5261554,
            "lng": 13.5062954
          },
          "time": {
            "arrival": "1970-01-01T01:19:03Z",
            "departure": "1970-01-01T01:22:03Z"
          },
          "distance": 29435,
          "load": [
            2
          ],
          "activities": [
            {
              "jobId": "job24",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5279215,
            "lng": 13.4995315
          },
          "time": {
            "arrival": "1970-01-01T01:22:53Z",
            "departure": "1970-01-01T01:25:53Z"
          },
          "distance": 29933,
          "load": [
            1
          ],
          "activities": [
            {
              "jobId": "job49",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5090143,
            "lng": 13.4368189
          },
          "time": {
            "arrival": "1970-01-01T01:33:47Z",
            "departure": "1970-01-01T01:36:47Z"
          },
          "distance": 34674,
          "load": [
            0
          ],
          "activities": [
            {
              "jobId": "job26",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.4664257,
            "lng": 13.2812488
          },
          "time": {
            "arrival": "1970-01-01T01:56:03Z",
            "departure": "1970-01-01T01:56:03Z"
          },
          "distance": 46236,
          "load": [
            0
          ],
          "activities": [
            {
              "jobId": "arrival",
              "type": "arrival"
            }
          ]
        }
      ],
      "statistic": {
        "cost": 64.06219999999999,
        "distance": 46236,
        "duration": 6963,
        "times": {
          "driving": 4623,
          "serving": 2340,
          "waiting": 0,
          "break": 0
        }
      }
    }
  ],
  "unassigned": []
}


This objective balances max load across vehicles:

  "objectives": [
    [
      {
        "type": "minimize-unassigned"
      }
    ],
    [
      {
        "type": "minimize-cost"
      },
      {
        "type": "balance-max-load",
        "options": {
          "threshold": 0.075
        }
      }
    ]
  ]

As minimize-tours objective is not set, all available vehicles are used serving 10 jobs per vehicle. Result total cost is higher than for default objective.

Balance activities with fleet minimization

Problem

{
  "plan": {
    "jobs": [
      {
        "id": "job1",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5697304,
                  "lng": 13.3848221
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job2",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5060419,
                  "lng": 13.5152641
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job3",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5421315,
                  "lng": 13.5189513
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job4",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5243421,
                  "lng": 13.4619776
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job5",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.4629002,
                  "lng": 13.4757055
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job6",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.4960479,
                  "lng": 13.3915876
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job7",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5372914,
                  "lng": 13.3996298
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job8",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5429597,
                  "lng": 13.3989552
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job9",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5678751,
                  "lng": 13.4231417
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job10",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.4945572,
                  "lng": 13.4698049
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job11",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.4989511,
                  "lng": 13.4740528
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job12",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.4658835,
                  "lng": 13.4461224
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job13",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5685168,
                  "lng": 13.3690720
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job14",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.4742821,
                  "lng": 13.3628588
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job15",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5650163,
                  "lng": 13.3027992
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job16",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5496702,
                  "lng": 13.4286263
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job17",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5058684,
                  "lng": 13.4750990
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job18",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5473416,
                  "lng": 13.3327894
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job19",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5276784,
                  "lng": 13.5465640
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job20",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5192039,
                  "lng": 13.3044440
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job21",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5228904,
                  "lng": 13.4418623
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job22",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.4828453,
                  "lng": 13.4363713
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job23",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5291335,
                  "lng": 13.3668934
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job24",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5261554,
                  "lng": 13.5062954
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job25",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5189653,
                  "lng": 13.3890068
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job26",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5090143,
                  "lng": 13.4368189
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job27",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.4940454,
                  "lng": 13.3788834
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job28",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5065998,
                  "lng": 13.3689955
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job29",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5473490,
                  "lng": 13.3733163
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job30",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.4695374,
                  "lng": 13.4914662
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job31",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.4868236,
                  "lng": 13.3353656
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job32",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.4661617,
                  "lng": 13.3226920
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job33",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.4917198,
                  "lng": 13.5251532
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job34",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5431264,
                  "lng": 13.4416407
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job35",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5426716,
                  "lng": 13.5161692
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job36",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.4708241,
                  "lng": 13.3598752
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job37",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.4737341,
                  "lng": 13.3866700
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job38",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5404107,
                  "lng": 13.3914127
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job39",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5492619,
                  "lng": 13.3693560
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job40",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.4827319,
                  "lng": 13.3157235
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job41",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.4711004,
                  "lng": 13.3321906
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job42",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.4871049,
                  "lng": 13.5423247
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job43",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5614441,
                  "lng": 13.4194712
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job44",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5414557,
                  "lng": 13.5276390
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job45",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5425207,
                  "lng": 13.4139155
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job46",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5632095,
                  "lng": 13.2940051
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job47",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5146285,
                  "lng": 13.2852959
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job48",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.4855438,
                  "lng": 13.3832067
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job49",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5279215,
                  "lng": 13.4995315
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job50",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.4959052,
                  "lng": 13.3539713
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      }
    ]
  },
  "fleet": {
    "vehicles": [
      {
        "typeId": "vehicle",
        "vehicleIds": [
          "vehicle_1",
          "vehicle_2",
          "vehicle_3",
          "vehicle_4",
          "vehicle_5"
        ],
        "profile": {
          "matrix": "car"
        },
        "costs": {
          "fixed": 20.0,
          "distance": 0.0002,
          "time": 0.005
        },
        "shifts": [
          {
            "start": {
              "earliest": "1970-01-01T00:00:00Z",
              "location": {
                "lat": 52.4664257,
                "lng": 13.2812488
              }
            },
            "end": {
              "latest": "1970-01-01T23:59:00Z",
              "location": {
                "lat": 52.4664257,
                "lng": 13.2812488
              }
            }
          }
        ],
        "capacity": [
          20
        ]
      }
    ],
    "profiles": [
      {
        "name": "car"
      }
    ]
  },
  "objectives": [
    [
      {
        "type": "minimize-unassigned"
      }
    ],
    [
      {
        "type": "minimize-tours"
      }
    ],
    [
      {
        "type": "minimize-cost"
      },
      {
        "type": "balance-activities",
        "options": {
          "threshold": 0.01
        }
      }
    ]
  ]
}

Solution

{
  "statistic": {
    "cost": 189.47459999999995,
    "distance": 120698,
    "duration": 21067,
    "times": {
      "driving": 12067,
      "serving": 9000,
      "waiting": 0,
      "break": 0
    }
  },
  "tours": [
    {
      "vehicleId": "vehicle_1",
      "typeId": "vehicle",
      "shiftIndex": 0,
      "stops": [
        {
          "location": {
            "lat": 52.4664257,
            "lng": 13.2812488
          },
          "time": {
            "arrival": "1970-01-01T00:00:00Z",
            "departure": "1970-01-01T00:00:00Z"
          },
          "distance": 0,
          "load": [
            16
          ],
          "activities": [
            {
              "jobId": "departure",
              "type": "departure"
            }
          ]
        },
        {
          "location": {
            "lat": 52.4827319,
            "lng": 13.3157235
          },
          "time": {
            "arrival": "1970-01-01T00:04:56Z",
            "departure": "1970-01-01T00:07:56Z"
          },
          "distance": 2960,
          "load": [
            15
          ],
          "activities": [
            {
              "jobId": "job40",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.4868236,
            "lng": 13.3353656
          },
          "time": {
            "arrival": "1970-01-01T00:10:17Z",
            "departure": "1970-01-01T00:13:17Z"
          },
          "distance": 4367,
          "load": [
            14
          ],
          "activities": [
            {
              "jobId": "job31",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.4959052,
            "lng": 13.3539713
          },
          "time": {
            "arrival": "1970-01-01T00:15:59Z",
            "departure": "1970-01-01T00:18:59Z"
          },
          "distance": 5983,
          "load": [
            13
          ],
          "activities": [
            {
              "jobId": "job50",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5065998,
            "lng": 13.3689955
          },
          "time": {
            "arrival": "1970-01-01T00:21:36Z",
            "departure": "1970-01-01T00:24:36Z"
          },
          "distance": 7549,
          "load": [
            12
          ],
          "activities": [
            {
              "jobId": "job28",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5291335,
            "lng": 13.3668934
          },
          "time": {
            "arrival": "1970-01-01T00:28:47Z",
            "departure": "1970-01-01T00:31:47Z"
          },
          "distance": 10061,
          "load": [
            11
          ],
          "activities": [
            {
              "jobId": "job23",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5404107,
            "lng": 13.3914127
          },
          "time": {
            "arrival": "1970-01-01T00:35:15Z",
            "departure": "1970-01-01T00:38:15Z"
          },
          "distance": 12142,
          "load": [
            10
          ],
          "activities": [
            {
              "jobId": "job38",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5429597,
            "lng": 13.3989552
          },
          "time": {
            "arrival": "1970-01-01T00:39:13Z",
            "departure": "1970-01-01T00:42:13Z"
          },
          "distance": 12726,
          "load": [
            9
          ],
          "activities": [
            {
              "jobId": "job8",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5425207,
            "lng": 13.4139155
          },
          "time": {
            "arrival": "1970-01-01T00:43:54Z",
            "departure": "1970-01-01T00:46:54Z"
          },
          "distance": 13740,
          "load": [
            8
          ],
          "activities": [
            {
              "jobId": "job45",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5372914,
            "lng": 13.3996298
          },
          "time": {
            "arrival": "1970-01-01T00:48:47Z",
            "departure": "1970-01-01T00:51:47Z"
          },
          "distance": 14869,
          "load": [
            7
          ],
          "activities": [
            {
              "jobId": "job7",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5189653,
            "lng": 13.3890068
          },
          "time": {
            "arrival": "1970-01-01T00:55:23Z",
            "departure": "1970-01-01T00:58:23Z"
          },
          "distance": 17032,
          "load": [
            6
          ],
          "activities": [
            {
              "jobId": "job25",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.4940454,
            "lng": 13.3788834
          },
          "time": {
            "arrival": "1970-01-01T01:03:09Z",
            "departure": "1970-01-01T01:06:09Z"
          },
          "distance": 19890,
          "load": [
            5
          ],
          "activities": [
            {
              "jobId": "job27",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.4855438,
            "lng": 13.3832067
          },
          "time": {
            "arrival": "1970-01-01T01:07:48Z",
            "departure": "1970-01-01T01:10:48Z"
          },
          "distance": 20881,
          "load": [
            4
          ],
          "activities": [
            {
              "jobId": "job48",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.4742821,
            "lng": 13.3628588
          },
          "time": {
            "arrival": "1970-01-01T01:13:54Z",
            "departure": "1970-01-01T01:16:54Z"
          },
          "distance": 22745,
          "load": [
            3
          ],
          "activities": [
            {
              "jobId": "job14",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.4708241,
            "lng": 13.3598752
          },
          "time": {
            "arrival": "1970-01-01T01:17:37Z",
            "departure": "1970-01-01T01:20:37Z"
          },
          "distance": 23180,
          "load": [
            2
          ],
          "activities": [
            {
              "jobId": "job36",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.4711004,
            "lng": 13.3321906
          },
          "time": {
            "arrival": "1970-01-01T01:23:45Z",
            "departure": "1970-01-01T01:26:45Z"
          },
          "distance": 25058,
          "load": [
            1
          ],
          "activities": [
            {
              "jobId": "job41",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.4661617,
            "lng": 13.322692
          },
          "time": {
            "arrival": "1970-01-01T01:28:10Z",
            "departure": "1970-01-01T01:31:10Z"
          },
          "distance": 25905,
          "load": [
            0
          ],
          "activities": [
            {
              "jobId": "job32",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.4664257,
            "lng": 13.2812488
          },
          "time": {
            "arrival": "1970-01-01T01:35:51Z",
            "departure": "1970-01-01T01:35:51Z"
          },
          "distance": 28716,
          "load": [
            0
          ],
          "activities": [
            {
              "jobId": "arrival",
              "type": "arrival"
            }
          ]
        }
      ],
      "statistic": {
        "cost": 54.49819999999999,
        "distance": 28716,
        "duration": 5751,
        "times": {
          "driving": 2871,
          "serving": 2880,
          "waiting": 0,
          "break": 0
        }
      }
    },
    {
      "vehicleId": "vehicle_3",
      "typeId": "vehicle",
      "shiftIndex": 0,
      "stops": [
        {
          "location": {
            "lat": 52.4664257,
            "lng": 13.2812488
          },
          "time": {
            "arrival": "1970-01-01T00:00:00Z",
            "departure": "1970-01-01T00:00:00Z"
          },
          "distance": 0,
          "load": [
            17
          ],
          "activities": [
            {
              "jobId": "departure",
              "type": "departure"
            }
          ]
        },
        {
          "location": {
            "lat": 52.4960479,
            "lng": 13.3915876
          },
          "time": {
            "arrival": "1970-01-01T00:13:38Z",
            "departure": "1970-01-01T00:16:38Z"
          },
          "distance": 8175,
          "load": [
            16
          ],
          "activities": [
            {
              "jobId": "job6",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5090143,
            "lng": 13.4368189
          },
          "time": {
            "arrival": "1970-01-01T00:22:17Z",
            "departure": "1970-01-01T00:25:17Z"
          },
          "distance": 11563,
          "load": [
            15
          ],
          "activities": [
            {
              "jobId": "job26",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5228904,
            "lng": 13.4418623
          },
          "time": {
            "arrival": "1970-01-01T00:27:55Z",
            "departure": "1970-01-01T00:30:55Z"
          },
          "distance": 13145,
          "load": [
            14
          ],
          "activities": [
            {
              "jobId": "job21",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5243421,
            "lng": 13.4619776
          },
          "time": {
            "arrival": "1970-01-01T00:33:12Z",
            "departure": "1970-01-01T00:36:12Z"
          },
          "distance": 14517,
          "load": [
            13
          ],
          "activities": [
            {
              "jobId": "job4",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5431264,
            "lng": 13.4416407
          },
          "time": {
            "arrival": "1970-01-01T00:40:22Z",
            "departure": "1970-01-01T00:43:22Z"
          },
          "distance": 17021,
          "load": [
            12
          ],
          "activities": [
            {
              "jobId": "job34",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5496702,
            "lng": 13.4286263
          },
          "time": {
            "arrival": "1970-01-01T00:45:16Z",
            "departure": "1970-01-01T00:48:16Z"
          },
          "distance": 18164,
          "load": [
            11
          ],
          "activities": [
            {
              "jobId": "job16",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5614441,
            "lng": 13.4194712
          },
          "time": {
            "arrival": "1970-01-01T00:50:41Z",
            "departure": "1970-01-01T00:53:41Z"
          },
          "distance": 19614,
          "load": [
            10
          ],
          "activities": [
            {
              "jobId": "job43",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5678751,
            "lng": 13.4231417
          },
          "time": {
            "arrival": "1970-01-01T00:54:57Z",
            "departure": "1970-01-01T00:57:57Z"
          },
          "distance": 20372,
          "load": [
            9
          ],
          "activities": [
            {
              "jobId": "job9",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5697304,
            "lng": 13.3848221
          },
          "time": {
            "arrival": "1970-01-01T01:02:17Z",
            "departure": "1970-01-01T01:05:17Z"
          },
          "distance": 22973,
          "load": [
            8
          ],
          "activities": [
            {
              "jobId": "job1",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5685168,
            "lng": 13.369072
          },
          "time": {
            "arrival": "1970-01-01T01:07:04Z",
            "departure": "1970-01-01T01:10:04Z"
          },
          "distance": 24047,
          "load": [
            7
          ],
          "activities": [
            {
              "jobId": "job13",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.547349,
            "lng": 13.3733163
          },
          "time": {
            "arrival": "1970-01-01T01:14:01Z",
            "departure": "1970-01-01T01:17:01Z"
          },
          "distance": 26421,
          "load": [
            6
          ],
          "activities": [
            {
              "jobId": "job29",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5492619,
            "lng": 13.369356
          },
          "time": {
            "arrival": "1970-01-01T01:17:35Z",
            "departure": "1970-01-01T01:20:35Z"
          },
          "distance": 26763,
          "load": [
            5
          ],
          "activities": [
            {
              "jobId": "job39",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5473416,
            "lng": 13.3327894
          },
          "time": {
            "arrival": "1970-01-01T01:24:43Z",
            "departure": "1970-01-01T01:27:43Z"
          },
          "distance": 29247,
          "load": [
            4
          ],
          "activities": [
            {
              "jobId": "job18",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5650163,
            "lng": 13.3027992
          },
          "time": {
            "arrival": "1970-01-01T01:32:26Z",
            "departure": "1970-01-01T01:35:26Z"
          },
          "distance": 32074,
          "load": [
            3
          ],
          "activities": [
            {
              "jobId": "job15",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5632095,
            "lng": 13.2940051
          },
          "time": {
            "arrival": "1970-01-01T01:36:29Z",
            "departure": "1970-01-01T01:39:29Z"
          },
          "distance": 32702,
          "load": [
            2
          ],
          "activities": [
            {
              "jobId": "job46",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5192039,
            "lng": 13.304444
          },
          "time": {
            "arrival": "1970-01-01T01:47:44Z",
            "departure": "1970-01-01T01:50:44Z"
          },
          "distance": 37651,
          "load": [
            1
          ],
          "activities": [
            {
              "jobId": "job20",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5146285,
            "lng": 13.2852959
          },
          "time": {
            "arrival": "1970-01-01T01:53:03Z",
            "departure": "1970-01-01T01:56:03Z"
          },
          "distance": 39045,
          "load": [
            0
          ],
          "activities": [
            {
              "jobId": "job47",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.4664257,
            "lng": 13.2812488
          },
          "time": {
            "arrival": "1970-01-01T02:05:00Z",
            "departure": "1970-01-01T02:05:00Z"
          },
          "distance": 44418,
          "load": [
            0
          ],
          "activities": [
            {
              "jobId": "arrival",
              "type": "arrival"
            }
          ]
        }
      ],
      "statistic": {
        "cost": 66.38359999999999,
        "distance": 44418,
        "duration": 7500,
        "times": {
          "driving": 4440,
          "serving": 3060,
          "waiting": 0,
          "break": 0
        }
      }
    },
    {
      "vehicleId": "vehicle_2",
      "typeId": "vehicle",
      "shiftIndex": 0,
      "stops": [
        {
          "location": {
            "lat": 52.4664257,
            "lng": 13.2812488
          },
          "time": {
            "arrival": "1970-01-01T00:00:00Z",
            "departure": "1970-01-01T00:00:00Z"
          },
          "distance": 0,
          "load": [
            17
          ],
          "activities": [
            {
              "jobId": "departure",
              "type": "departure"
            }
          ]
        },
        {
          "location": {
            "lat": 52.4737341,
            "lng": 13.38667
          },
          "time": {
            "arrival": "1970-01-01T00:12:00Z",
            "departure": "1970-01-01T00:15:00Z"
          },
          "distance": 7195,
          "load": [
            16
          ],
          "activities": [
            {
              "jobId": "job37",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.4828453,
            "lng": 13.4363713
          },
          "time": {
            "arrival": "1970-01-01T00:20:52Z",
            "departure": "1970-01-01T00:23:52Z"
          },
          "distance": 10714,
          "load": [
            15
          ],
          "activities": [
            {
              "jobId": "job22",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.4945572,
            "lng": 13.4698049
          },
          "time": {
            "arrival": "1970-01-01T00:28:13Z",
            "departure": "1970-01-01T00:31:13Z"
          },
          "distance": 13329,
          "load": [
            14
          ],
          "activities": [
            {
              "jobId": "job10",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.4989511,
            "lng": 13.4740528
          },
          "time": {
            "arrival": "1970-01-01T00:32:10Z",
            "departure": "1970-01-01T00:35:10Z"
          },
          "distance": 13897,
          "load": [
            13
          ],
          "activities": [
            {
              "jobId": "job11",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5058684,
            "lng": 13.475099
          },
          "time": {
            "arrival": "1970-01-01T00:36:27Z",
            "departure": "1970-01-01T00:39:27Z"
          },
          "distance": 14670,
          "load": [
            12
          ],
          "activities": [
            {
              "jobId": "job17",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5279215,
            "lng": 13.4995315
          },
          "time": {
            "arrival": "1970-01-01T00:44:23Z",
            "departure": "1970-01-01T00:47:23Z"
          },
          "distance": 17631,
          "load": [
            11
          ],
          "activities": [
            {
              "jobId": "job49",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5261554,
            "lng": 13.5062954
          },
          "time": {
            "arrival": "1970-01-01T00:48:13Z",
            "departure": "1970-01-01T00:51:13Z"
          },
          "distance": 18129,
          "load": [
            10
          ],
          "activities": [
            {
              "jobId": "job24",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5426716,
            "lng": 13.5161692
          },
          "time": {
            "arrival": "1970-01-01T00:54:29Z",
            "departure": "1970-01-01T00:57:29Z"
          },
          "distance": 20085,
          "load": [
            9
          ],
          "activities": [
            {
              "jobId": "job35",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5421315,
            "lng": 13.5189513
          },
          "time": {
            "arrival": "1970-01-01T00:57:49Z",
            "departure": "1970-01-01T01:00:49Z"
          },
          "distance": 20283,
          "load": [
            8
          ],
          "activities": [
            {
              "jobId": "job3",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5414557,
            "lng": 13.527639
          },
          "time": {
            "arrival": "1970-01-01T01:01:48Z",
            "departure": "1970-01-01T01:04:48Z"
          },
          "distance": 20876,
          "load": [
            7
          ],
          "activities": [
            {
              "jobId": "job44",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5276784,
            "lng": 13.546564
          },
          "time": {
            "arrival": "1970-01-01T01:08:08Z",
            "departure": "1970-01-01T01:11:08Z"
          },
          "distance": 22875,
          "load": [
            6
          ],
          "activities": [
            {
              "jobId": "job19",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5060419,
            "lng": 13.5152641
          },
          "time": {
            "arrival": "1970-01-01T01:16:29Z",
            "departure": "1970-01-01T01:19:29Z"
          },
          "distance": 26084,
          "load": [
            5
          ],
          "activities": [
            {
              "jobId": "job2",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.4917198,
            "lng": 13.5251532
          },
          "time": {
            "arrival": "1970-01-01T01:22:22Z",
            "departure": "1970-01-01T01:25:22Z"
          },
          "distance": 27813,
          "load": [
            4
          ],
          "activities": [
            {
              "jobId": "job33",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.4871049,
            "lng": 13.5423247
          },
          "time": {
            "arrival": "1970-01-01T01:27:29Z",
            "departure": "1970-01-01T01:30:29Z"
          },
          "distance": 29085,
          "load": [
            3
          ],
          "activities": [
            {
              "jobId": "job42",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.4695374,
            "lng": 13.4914662
          },
          "time": {
            "arrival": "1970-01-01T01:37:05Z",
            "departure": "1970-01-01T01:40:05Z"
          },
          "distance": 33049,
          "load": [
            2
          ],
          "activities": [
            {
              "jobId": "job30",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.4629002,
            "lng": 13.4757055
          },
          "time": {
            "arrival": "1970-01-01T01:42:15Z",
            "departure": "1970-01-01T01:45:15Z"
          },
          "distance": 34348,
          "load": [
            1
          ],
          "activities": [
            {
              "jobId": "job5",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.4658835,
            "lng": 13.4461224
          },
          "time": {
            "arrival": "1970-01-01T01:48:38Z",
            "departure": "1970-01-01T01:51:38Z"
          },
          "distance": 36382,
          "load": [
            0
          ],
          "activities": [
            {
              "jobId": "job12",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.4664257,
            "lng": 13.2812488
          },
          "time": {
            "arrival": "1970-01-01T02:10:16Z",
            "departure": "1970-01-01T02:10:16Z"
          },
          "distance": 47564,
          "load": [
            0
          ],
          "activities": [
            {
              "jobId": "arrival",
              "type": "arrival"
            }
          ]
        }
      ],
      "statistic": {
        "cost": 68.59279999999998,
        "distance": 47564,
        "duration": 7816,
        "times": {
          "driving": 4756,
          "serving": 3060,
          "waiting": 0,
          "break": 0
        }
      }
    }
  ],
  "unassigned": []
}


This objective balances amount of activities and minimizes fleet usage at the same time:

  "objectives": [
    [
      {
        "type": "minimize-unassigned"
      }
    ],
    [
      {
        "type": "minimize-tours"
      }
    ],
    [
      {
        "type": "minimize-cost"
      },
      {
        "type": "balance-activities",
        "options": {
          "threshold": 0.01
        }
      }
    ]
  ]

Only three vehicles used approximately 16 jobs per vehicle. If you remove minimize-tours, results should be similar to results on previous page.

Balance travelled distance

Problem

{
  "plan": {
    "jobs": [
      {
        "id": "job1",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5697304,
                  "lng": 13.3848221
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job2",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5060419,
                  "lng": 13.5152641
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job3",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5421315,
                  "lng": 13.5189513
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job4",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5243421,
                  "lng": 13.4619776
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job5",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.4629002,
                  "lng": 13.4757055
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job6",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.4960479,
                  "lng": 13.3915876
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job7",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5372914,
                  "lng": 13.3996298
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job8",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5429597,
                  "lng": 13.3989552
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job9",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5678751,
                  "lng": 13.4231417
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job10",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.4945572,
                  "lng": 13.4698049
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job11",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.4989511,
                  "lng": 13.4740528
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job12",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.4658835,
                  "lng": 13.4461224
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job13",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5685168,
                  "lng": 13.3690720
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job14",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.4742821,
                  "lng": 13.3628588
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job15",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5650163,
                  "lng": 13.3027992
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job16",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5496702,
                  "lng": 13.4286263
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job17",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5058684,
                  "lng": 13.4750990
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job18",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5473416,
                  "lng": 13.3327894
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job19",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5276784,
                  "lng": 13.5465640
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job20",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5192039,
                  "lng": 13.3044440
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job21",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5228904,
                  "lng": 13.4418623
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job22",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.4828453,
                  "lng": 13.4363713
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job23",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5291335,
                  "lng": 13.3668934
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job24",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5261554,
                  "lng": 13.5062954
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job25",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5189653,
                  "lng": 13.3890068
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job26",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5090143,
                  "lng": 13.4368189
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job27",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.4940454,
                  "lng": 13.3788834
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job28",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5065998,
                  "lng": 13.3689955
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job29",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5473490,
                  "lng": 13.3733163
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job30",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.4695374,
                  "lng": 13.4914662
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job31",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.4868236,
                  "lng": 13.3353656
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job32",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.4661617,
                  "lng": 13.3226920
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job33",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.4917198,
                  "lng": 13.5251532
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job34",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5431264,
                  "lng": 13.4416407
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job35",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5426716,
                  "lng": 13.5161692
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job36",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.4708241,
                  "lng": 13.3598752
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job37",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.4737341,
                  "lng": 13.3866700
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job38",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5404107,
                  "lng": 13.3914127
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job39",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5492619,
                  "lng": 13.3693560
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job40",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.4827319,
                  "lng": 13.3157235
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job41",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.4711004,
                  "lng": 13.3321906
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job42",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.4871049,
                  "lng": 13.5423247
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job43",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5614441,
                  "lng": 13.4194712
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job44",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5414557,
                  "lng": 13.5276390
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job45",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5425207,
                  "lng": 13.4139155
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job46",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5632095,
                  "lng": 13.2940051
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job47",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5146285,
                  "lng": 13.2852959
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job48",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.4855438,
                  "lng": 13.3832067
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job49",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5279215,
                  "lng": 13.4995315
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job50",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.4959052,
                  "lng": 13.3539713
                },
                "duration": 180.0
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      }
    ]
  },
  "fleet": {
    "vehicles": [
      {
        "typeId": "vehicle",
        "vehicleIds": [
          "vehicle_1",
          "vehicle_2",
          "vehicle_3",
          "vehicle_4",
          "vehicle_5"
        ],
        "profile": {
          "matrix": "car"
        },
        "costs": {
          "fixed": 20.0,
          "distance": 0.0002,
          "time": 0.005
        },
        "shifts": [
          {
            "start": {
              "earliest": "1970-01-01T00:00:00Z",
              "location": {
                "lat": 52.4664257,
                "lng": 13.2812488
              }
            },
            "end": {
              "latest": "1970-01-01T23:59:00Z",
              "location": {
                "lat": 52.4664257,
                "lng": 13.2812488
              }
            }
          }
        ],
        "capacity": [
          20
        ]
      }
    ],
    "profiles": [
      {
        "name": "car"
      }
    ]
  },
  "objectives": [
    [
      {
        "type": "minimize-unassigned"
      }
    ],
    [
      {
        "type": "minimize-cost"
      },
      {
        "type": "balance-distance",
        "options": {
          "threshold": 0.075
        }
      }
    ]
  ]
}

Solution

{
  "statistic": {
    "cost": 273.0455999999999,
    "distance": 182928,
    "duration": 27292,
    "times": {
      "driving": 18292,
      "serving": 9000,
      "waiting": 0,
      "break": 0
    }
  },
  "tours": [
    {
      "vehicleId": "vehicle_1",
      "typeId": "vehicle",
      "shiftIndex": 0,
      "stops": [
        {
          "location": {
            "lat": 52.4664257,
            "lng": 13.2812488
          },
          "time": {
            "arrival": "1970-01-01T00:00:00Z",
            "departure": "1970-01-01T00:00:00Z"
          },
          "distance": 0,
          "load": [
            10
          ],
          "activities": [
            {
              "jobId": "departure",
              "type": "departure"
            }
          ]
        },
        {
          "location": {
            "lat": 52.4960479,
            "lng": 13.3915876
          },
          "time": {
            "arrival": "1970-01-01T00:13:38Z",
            "departure": "1970-01-01T00:16:38Z"
          },
          "distance": 8175,
          "load": [
            9
          ],
          "activities": [
            {
              "jobId": "job6",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5090143,
            "lng": 13.4368189
          },
          "time": {
            "arrival": "1970-01-01T00:22:17Z",
            "departure": "1970-01-01T00:25:17Z"
          },
          "distance": 11563,
          "load": [
            8
          ],
          "activities": [
            {
              "jobId": "job26",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5261554,
            "lng": 13.5062954
          },
          "time": {
            "arrival": "1970-01-01T00:33:45Z",
            "departure": "1970-01-01T00:36:45Z"
          },
          "distance": 16641,
          "load": [
            7
          ],
          "activities": [
            {
              "jobId": "job24",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5276784,
            "lng": 13.546564
          },
          "time": {
            "arrival": "1970-01-01T00:41:18Z",
            "departure": "1970-01-01T00:44:18Z"
          },
          "distance": 19373,
          "load": [
            6
          ],
          "activities": [
            {
              "jobId": "job19",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5060419,
            "lng": 13.5152641
          },
          "time": {
            "arrival": "1970-01-01T00:49:39Z",
            "departure": "1970-01-01T00:52:39Z"
          },
          "distance": 22582,
          "load": [
            5
          ],
          "activities": [
            {
              "jobId": "job2",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.4989511,
            "lng": 13.4740528
          },
          "time": {
            "arrival": "1970-01-01T00:57:29Z",
            "departure": "1970-01-01T01:00:29Z"
          },
          "distance": 25484,
          "load": [
            4
          ],
          "activities": [
            {
              "jobId": "job11",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.4945572,
            "lng": 13.4698049
          },
          "time": {
            "arrival": "1970-01-01T01:01:26Z",
            "departure": "1970-01-01T01:04:26Z"
          },
          "distance": 26052,
          "load": [
            3
          ],
          "activities": [
            {
              "jobId": "job10",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.4828453,
            "lng": 13.4363713
          },
          "time": {
            "arrival": "1970-01-01T01:08:47Z",
            "departure": "1970-01-01T01:11:47Z"
          },
          "distance": 28667,
          "load": [
            2
          ],
          "activities": [
            {
              "jobId": "job22",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.4742821,
            "lng": 13.3628588
          },
          "time": {
            "arrival": "1970-01-01T01:20:14Z",
            "departure": "1970-01-01T01:23:14Z"
          },
          "distance": 33741,
          "load": [
            1
          ],
          "activities": [
            {
              "jobId": "job14",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.4711004,
            "lng": 13.3321906
          },
          "time": {
            "arrival": "1970-01-01T01:26:45Z",
            "departure": "1970-01-01T01:29:45Z"
          },
          "distance": 35851,
          "load": [
            0
          ],
          "activities": [
            {
              "jobId": "job41",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.4664257,
            "lng": 13.2812488
          },
          "time": {
            "arrival": "1970-01-01T01:35:34Z",
            "departure": "1970-01-01T01:35:34Z"
          },
          "distance": 39345,
          "load": [
            0
          ],
          "activities": [
            {
              "jobId": "arrival",
              "type": "arrival"
            }
          ]
        }
      ],
      "statistic": {
        "cost": 56.538999999999994,
        "distance": 39345,
        "duration": 5734,
        "times": {
          "driving": 3934,
          "serving": 1800,
          "waiting": 0,
          "break": 0
        }
      }
    },
    {
      "vehicleId": "vehicle_5",
      "typeId": "vehicle",
      "shiftIndex": 0,
      "stops": [
        {
          "location": {
            "lat": 52.4664257,
            "lng": 13.2812488
          },
          "time": {
            "arrival": "1970-01-01T00:00:00Z",
            "departure": "1970-01-01T00:00:00Z"
          },
          "distance": 0,
          "load": [
            10
          ],
          "activities": [
            {
              "jobId": "departure",
              "type": "departure"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5146285,
            "lng": 13.2852959
          },
          "time": {
            "arrival": "1970-01-01T00:08:57Z",
            "departure": "1970-01-01T00:11:57Z"
          },
          "distance": 5373,
          "load": [
            9
          ],
          "activities": [
            {
              "jobId": "job47",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5192039,
            "lng": 13.304444
          },
          "time": {
            "arrival": "1970-01-01T00:14:16Z",
            "departure": "1970-01-01T00:17:16Z"
          },
          "distance": 6767,
          "load": [
            8
          ],
          "activities": [
            {
              "jobId": "job20",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5632095,
            "lng": 13.2940051
          },
          "time": {
            "arrival": "1970-01-01T00:25:31Z",
            "departure": "1970-01-01T00:28:31Z"
          },
          "distance": 11716,
          "load": [
            7
          ],
          "activities": [
            {
              "jobId": "job46",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5650163,
            "lng": 13.3027992
          },
          "time": {
            "arrival": "1970-01-01T00:29:34Z",
            "departure": "1970-01-01T00:32:34Z"
          },
          "distance": 12344,
          "load": [
            6
          ],
          "activities": [
            {
              "jobId": "job15",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5473416,
            "lng": 13.3327894
          },
          "time": {
            "arrival": "1970-01-01T00:37:17Z",
            "departure": "1970-01-01T00:40:17Z"
          },
          "distance": 15171,
          "load": [
            5
          ],
          "activities": [
            {
              "jobId": "job18",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5685168,
            "lng": 13.369072
          },
          "time": {
            "arrival": "1970-01-01T00:45:57Z",
            "departure": "1970-01-01T00:48:57Z"
          },
          "distance": 18575,
          "load": [
            4
          ],
          "activities": [
            {
              "jobId": "job13",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5697304,
            "lng": 13.3848221
          },
          "time": {
            "arrival": "1970-01-01T00:50:44Z",
            "departure": "1970-01-01T00:53:44Z"
          },
          "distance": 19649,
          "load": [
            3
          ],
          "activities": [
            {
              "jobId": "job1",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5492619,
            "lng": 13.369356
          },
          "time": {
            "arrival": "1970-01-01T00:57:55Z",
            "departure": "1970-01-01T01:00:55Z"
          },
          "distance": 22156,
          "load": [
            2
          ],
          "activities": [
            {
              "jobId": "job39",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5291335,
            "lng": 13.3668934
          },
          "time": {
            "arrival": "1970-01-01T01:04:40Z",
            "departure": "1970-01-01T01:07:40Z"
          },
          "distance": 24403,
          "load": [
            1
          ],
          "activities": [
            {
              "jobId": "job23",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5065998,
            "lng": 13.3689955
          },
          "time": {
            "arrival": "1970-01-01T01:11:51Z",
            "departure": "1970-01-01T01:14:51Z"
          },
          "distance": 26915,
          "load": [
            0
          ],
          "activities": [
            {
              "jobId": "job28",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.4664257,
            "lng": 13.2812488
          },
          "time": {
            "arrival": "1970-01-01T01:27:15Z",
            "departure": "1970-01-01T01:27:15Z"
          },
          "distance": 34357,
          "load": [
            0
          ],
          "activities": [
            {
              "jobId": "arrival",
              "type": "arrival"
            }
          ]
        }
      ],
      "statistic": {
        "cost": 53.0464,
        "distance": 34357,
        "duration": 5235,
        "times": {
          "driving": 3435,
          "serving": 1800,
          "waiting": 0,
          "break": 0
        }
      }
    },
    {
      "vehicleId": "vehicle_4",
      "typeId": "vehicle",
      "shiftIndex": 0,
      "stops": [
        {
          "location": {
            "lat": 52.4664257,
            "lng": 13.2812488
          },
          "time": {
            "arrival": "1970-01-01T00:00:00Z",
            "departure": "1970-01-01T00:00:00Z"
          },
          "distance": 0,
          "load": [
            10
          ],
          "activities": [
            {
              "jobId": "departure",
              "type": "departure"
            }
          ]
        },
        {
          "location": {
            "lat": 52.547349,
            "lng": 13.3733163
          },
          "time": {
            "arrival": "1970-01-01T00:18:16Z",
            "departure": "1970-01-01T00:21:16Z"
          },
          "distance": 10957,
          "load": [
            9
          ],
          "activities": [
            {
              "jobId": "job29",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5404107,
            "lng": 13.3914127
          },
          "time": {
            "arrival": "1970-01-01T00:23:41Z",
            "departure": "1970-01-01T00:26:41Z"
          },
          "distance": 12405,
          "load": [
            8
          ],
          "activities": [
            {
              "jobId": "job38",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5429597,
            "lng": 13.3989552
          },
          "time": {
            "arrival": "1970-01-01T00:27:39Z",
            "departure": "1970-01-01T00:30:39Z"
          },
          "distance": 12989,
          "load": [
            7
          ],
          "activities": [
            {
              "jobId": "job8",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5614441,
            "lng": 13.4194712
          },
          "time": {
            "arrival": "1970-01-01T00:34:47Z",
            "departure": "1970-01-01T00:37:47Z"
          },
          "distance": 15471,
          "load": [
            6
          ],
          "activities": [
            {
              "jobId": "job43",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5678751,
            "lng": 13.4231417
          },
          "time": {
            "arrival": "1970-01-01T00:39:03Z",
            "departure": "1970-01-01T00:42:03Z"
          },
          "distance": 16229,
          "load": [
            5
          ],
          "activities": [
            {
              "jobId": "job9",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5496702,
            "lng": 13.4286263
          },
          "time": {
            "arrival": "1970-01-01T00:45:29Z",
            "departure": "1970-01-01T00:48:29Z"
          },
          "distance": 18289,
          "load": [
            4
          ],
          "activities": [
            {
              "jobId": "job16",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5431264,
            "lng": 13.4416407
          },
          "time": {
            "arrival": "1970-01-01T00:50:23Z",
            "departure": "1970-01-01T00:53:23Z"
          },
          "distance": 19432,
          "load": [
            3
          ],
          "activities": [
            {
              "jobId": "job34",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5425207,
            "lng": 13.4139155
          },
          "time": {
            "arrival": "1970-01-01T00:56:31Z",
            "departure": "1970-01-01T00:59:31Z"
          },
          "distance": 21310,
          "load": [
            2
          ],
          "activities": [
            {
              "jobId": "job45",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5372914,
            "lng": 13.3996298
          },
          "time": {
            "arrival": "1970-01-01T01:01:24Z",
            "departure": "1970-01-01T01:04:24Z"
          },
          "distance": 22439,
          "load": [
            1
          ],
          "activities": [
            {
              "jobId": "job7",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5189653,
            "lng": 13.3890068
          },
          "time": {
            "arrival": "1970-01-01T01:08:00Z",
            "departure": "1970-01-01T01:11:00Z"
          },
          "distance": 24602,
          "load": [
            0
          ],
          "activities": [
            {
              "jobId": "job25",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.4664257,
            "lng": 13.2812488
          },
          "time": {
            "arrival": "1970-01-01T01:26:36Z",
            "departure": "1970-01-01T01:26:36Z"
          },
          "distance": 33959,
          "load": [
            0
          ],
          "activities": [
            {
              "jobId": "arrival",
              "type": "arrival"
            }
          ]
        }
      ],
      "statistic": {
        "cost": 52.77179999999999,
        "distance": 33959,
        "duration": 5196,
        "times": {
          "driving": 3396,
          "serving": 1800,
          "waiting": 0,
          "break": 0
        }
      }
    },
    {
      "vehicleId": "vehicle_2",
      "typeId": "vehicle",
      "shiftIndex": 0,
      "stops": [
        {
          "location": {
            "lat": 52.4664257,
            "lng": 13.2812488
          },
          "time": {
            "arrival": "1970-01-01T00:00:00Z",
            "departure": "1970-01-01T00:00:00Z"
          },
          "distance": 0,
          "load": [
            10
          ],
          "activities": [
            {
              "jobId": "departure",
              "type": "departure"
            }
          ]
        },
        {
          "location": {
            "lat": 52.4661617,
            "lng": 13.322692
          },
          "time": {
            "arrival": "1970-01-01T00:04:41Z",
            "departure": "1970-01-01T00:07:41Z"
          },
          "distance": 2811,
          "load": [
            9
          ],
          "activities": [
            {
              "jobId": "job32",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.4708241,
            "lng": 13.3598752
          },
          "time": {
            "arrival": "1970-01-01T00:11:58Z",
            "departure": "1970-01-01T00:14:58Z"
          },
          "distance": 5385,
          "load": [
            8
          ],
          "activities": [
            {
              "jobId": "job36",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.4737341,
            "lng": 13.38667
          },
          "time": {
            "arrival": "1970-01-01T00:18:03Z",
            "departure": "1970-01-01T00:21:03Z"
          },
          "distance": 7231,
          "load": [
            7
          ],
          "activities": [
            {
              "jobId": "job37",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.4658835,
            "lng": 13.4461224
          },
          "time": {
            "arrival": "1970-01-01T00:27:56Z",
            "departure": "1970-01-01T00:30:56Z"
          },
          "distance": 11356,
          "load": [
            6
          ],
          "activities": [
            {
              "jobId": "job12",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.4629002,
            "lng": 13.4757055
          },
          "time": {
            "arrival": "1970-01-01T00:34:19Z",
            "departure": "1970-01-01T00:37:19Z"
          },
          "distance": 13390,
          "load": [
            5
          ],
          "activities": [
            {
              "jobId": "job5",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.4695374,
            "lng": 13.4914662
          },
          "time": {
            "arrival": "1970-01-01T00:39:29Z",
            "departure": "1970-01-01T00:42:29Z"
          },
          "distance": 14689,
          "load": [
            4
          ],
          "activities": [
            {
              "jobId": "job30",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.4871049,
            "lng": 13.5423247
          },
          "time": {
            "arrival": "1970-01-01T00:49:05Z",
            "departure": "1970-01-01T00:52:05Z"
          },
          "distance": 18653,
          "load": [
            3
          ],
          "activities": [
            {
              "jobId": "job42",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.4917198,
            "lng": 13.5251532
          },
          "time": {
            "arrival": "1970-01-01T00:54:12Z",
            "departure": "1970-01-01T00:57:12Z"
          },
          "distance": 19925,
          "load": [
            2
          ],
          "activities": [
            {
              "jobId": "job33",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5058684,
            "lng": 13.475099
          },
          "time": {
            "arrival": "1970-01-01T01:03:26Z",
            "departure": "1970-01-01T01:06:26Z"
          },
          "distance": 23665,
          "load": [
            1
          ],
          "activities": [
            {
              "jobId": "job17",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.4855438,
            "lng": 13.3832067
          },
          "time": {
            "arrival": "1970-01-01T01:17:29Z",
            "departure": "1970-01-01T01:20:29Z"
          },
          "distance": 30291,
          "load": [
            0
          ],
          "activities": [
            {
              "jobId": "job48",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.4664257,
            "lng": 13.2812488
          },
          "time": {
            "arrival": "1970-01-01T01:32:32Z",
            "departure": "1970-01-01T01:32:32Z"
          },
          "distance": 37524,
          "load": [
            0
          ],
          "activities": [
            {
              "jobId": "arrival",
              "type": "arrival"
            }
          ]
        }
      ],
      "statistic": {
        "cost": 55.264799999999994,
        "distance": 37524,
        "duration": 5552,
        "times": {
          "driving": 3752,
          "serving": 1800,
          "waiting": 0,
          "break": 0
        }
      }
    },
    {
      "vehicleId": "vehicle_3",
      "typeId": "vehicle",
      "shiftIndex": 0,
      "stops": [
        {
          "location": {
            "lat": 52.4664257,
            "lng": 13.2812488
          },
          "time": {
            "arrival": "1970-01-01T00:00:00Z",
            "departure": "1970-01-01T00:00:00Z"
          },
          "distance": 0,
          "load": [
            10
          ],
          "activities": [
            {
              "jobId": "departure",
              "type": "departure"
            }
          ]
        },
        {
          "location": {
            "lat": 52.4827319,
            "lng": 13.3157235
          },
          "time": {
            "arrival": "1970-01-01T00:04:56Z",
            "departure": "1970-01-01T00:07:56Z"
          },
          "distance": 2960,
          "load": [
            9
          ],
          "activities": [
            {
              "jobId": "job40",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.4868236,
            "lng": 13.3353656
          },
          "time": {
            "arrival": "1970-01-01T00:10:17Z",
            "departure": "1970-01-01T00:13:17Z"
          },
          "distance": 4367,
          "load": [
            8
          ],
          "activities": [
            {
              "jobId": "job31",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.4959052,
            "lng": 13.3539713
          },
          "time": {
            "arrival": "1970-01-01T00:15:59Z",
            "departure": "1970-01-01T00:18:59Z"
          },
          "distance": 5983,
          "load": [
            7
          ],
          "activities": [
            {
              "jobId": "job50",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5228904,
            "lng": 13.4418623
          },
          "time": {
            "arrival": "1970-01-01T00:30:06Z",
            "departure": "1970-01-01T00:33:06Z"
          },
          "distance": 12653,
          "load": [
            6
          ],
          "activities": [
            {
              "jobId": "job21",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5243421,
            "lng": 13.4619776
          },
          "time": {
            "arrival": "1970-01-01T00:35:23Z",
            "departure": "1970-01-01T00:38:23Z"
          },
          "distance": 14025,
          "load": [
            5
          ],
          "activities": [
            {
              "jobId": "job4",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5426716,
            "lng": 13.5161692
          },
          "time": {
            "arrival": "1970-01-01T00:45:23Z",
            "departure": "1970-01-01T00:48:23Z"
          },
          "distance": 18224,
          "load": [
            4
          ],
          "activities": [
            {
              "jobId": "job35",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5421315,
            "lng": 13.5189513
          },
          "time": {
            "arrival": "1970-01-01T00:48:43Z",
            "departure": "1970-01-01T00:51:43Z"
          },
          "distance": 18422,
          "load": [
            3
          ],
          "activities": [
            {
              "jobId": "job3",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5414557,
            "lng": 13.527639
          },
          "time": {
            "arrival": "1970-01-01T00:52:42Z",
            "departure": "1970-01-01T00:55:42Z"
          },
          "distance": 19015,
          "load": [
            2
          ],
          "activities": [
            {
              "jobId": "job44",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.5279215,
            "lng": 13.4995315
          },
          "time": {
            "arrival": "1970-01-01T00:59:45Z",
            "departure": "1970-01-01T01:02:45Z"
          },
          "distance": 21442,
          "load": [
            1
          ],
          "activities": [
            {
              "jobId": "job49",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.4940454,
            "lng": 13.3788834
          },
          "time": {
            "arrival": "1970-01-01T01:17:45Z",
            "departure": "1970-01-01T01:20:45Z"
          },
          "distance": 30444,
          "load": [
            0
          ],
          "activities": [
            {
              "jobId": "job27",
              "type": "delivery"
            }
          ]
        },
        {
          "location": {
            "lat": 52.4664257,
            "lng": 13.2812488
          },
          "time": {
            "arrival": "1970-01-01T01:32:55Z",
            "departure": "1970-01-01T01:32:55Z"
          },
          "distance": 37743,
          "load": [
            0
          ],
          "activities": [
            {
              "jobId": "arrival",
              "type": "arrival"
            }
          ]
        }
      ],
      "statistic": {
        "cost": 55.42359999999999,
        "distance": 37743,
        "duration": 5575,
        "times": {
          "driving": 3775,
          "serving": 1800,
          "waiting": 0,
          "break": 0
        }
      }
    }
  ],
  "unassigned": []
}


This objective balances tour distances for all tours:

  "objectives": [
    [
      {
        "type": "minimize-unassigned"
      }
    ],
    [
      {
        "type": "minimize-cost"
      },
      {
        "type": "balance-distance",
        "options": {
          "threshold": 0.075
        }
      }
    ]
  ]

All used vehicles should have total tour distance close to each other.

The same way you can balance by travel duration using balance-duration objective.

Programmatic usage

This section contains examples which show how to call the solver from other languages.

Java

This is example how to call solver methods from java. You need to make sure that vrp-cli library is available in runtime, e.g. by copying corresponding binary (libvrp_cli.so on Linux) to resources directory. To build it, use the following command:

cargo build --release
package vrp.example.java;

import com.sun.jna.Callback;
import com.sun.jna.Library;
import com.sun.jna.Native;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;

/** Encapsulate Vehicle Routing Problem solver behavior.  */
interface Solver extends Library {
    /** Gets list of routing matrix locations. **/
    void get_routing_locations(String problem, OnSuccess onSuccess, OnError onError);
    /** Converts problem to pragmatic format. **/
    void convert_to_pragmatic(String format, String[] inputs, int inputsLen, OnSuccess onSuccess, OnError onError);
    /** Solves pragmatic problem. **/
    void solve_pragmatic(String problem, String[] matrices,
                         int matricesSize,
                         String config,
                         OnSuccess onSuccess, OnError onError);
}

interface OnSuccess extends Callback {
    void result(String json);
}

interface OnError extends Callback {
    void result(String error);
}

class Application {
    public static void main(String[] args) throws IOException {
        if (args.length < 1) {
            throw new IllegalStateException("Specify problem and, optionally, routing matrices paths");
        }

        String problem = new String(Files.readAllBytes(Paths.get(args[0])));
        String[] matrices = new String[args.length - 1];
        for (int i = 1; i < args.length; i++) {
            matrices[i - 1] = new String(Files.readAllBytes(Paths.get(args[i])));
        }

        Solver solver = Native.load("vrp_cli", Solver.class);

        solver.get_routing_locations(problem,
                new OnSuccess() {
                    @Override
                    public void result(String json) {
                        System.out.println(json);
                    }
                }, new OnError() {
                    @Override
                    public void result(String error) {
                        System.out.println(error);
                    }
                });

        solver.solve_pragmatic(problem, matrices, matrices.length, "{}",
                new OnSuccess() {
                    @Override
                    public void result(String json) {
                        System.out.println(json);
                    }
                }, new OnError() {
                    @Override
                    public void result(String error) {
                        System.out.println(error);
                    }
                });
    }
}

You can check the project repository for complete example.

Kotlin

This is example how to call solver methods from kotlin. You need to make sure that vrp-cli library is available in runtime, e.g. by copying corresponding binary (libvrp_cli.so on Linux) to resources directory. To build it, use the following command;

cargo build --release
package vrp.example.kotlin

import com.sun.jna.Callback
import com.sun.jna.Library
import com.sun.jna.Native
import java.nio.file.Files
import java.nio.file.Paths

/** Encapsulate Vehicle Routing Problem solver behavior.  */
private interface Solver : Library {
    /** Gets list of routing matrix locations **/
    fun get_routing_locations(problem: String, onSuccess: OnSuccess, onError: OnError)
    /** Converts problem to pragmatic format. **/
    fun convert_to_pragmatic(format: String, inputs: Array<String>, inputsLen: Int, onSuccess: OnSuccess, onError: OnError)
    /** Solves pragmatic problem. **/
    fun solve_pragmatic(problem: String,
                        matrices: Array<String>,
                        matricesLen: Int,
                        config: String,
                        onSuccess: OnSuccess, onError: OnError)
}

private interface OnSuccess : Callback {
    fun result(json: String)
}

private interface OnError : Callback {
    fun result(error: String)
}

fun main(args: Array<String>) {
    if (args.count() < 1) {
        throw IllegalStateException("Specify problem and, optionally, routing matrices paths")
    }

    val problem = String(Files.readAllBytes(Paths.get(args[0])))
    val matrices = args.drop(1).map { String(Files.readAllBytes(Paths.get(it))) }.toTypedArray()

    val solver = Native.load("vrp_cli", Solver::class.java)

    solver.get_routing_locations(problem,
            onSuccess = object : OnSuccess {
                override fun result(json: String) {
                    println("locations: $json")
                }
            },
            onError = object : OnError {
                override fun result(error: String) {
                    println(error)
                }
            }
    )

    solver.solve_pragmatic(problem, matrices, matrices.size, "{}",
            onSuccess = object : OnSuccess {
                override fun result(json: String) {
                    println("solution: $json")
                }
            },
            onError = object : OnError {
                override fun result(error: String) {
                    println(error)
                }
            }
    )
}

You can check the project repository for complete example.

Javascript

This is example how to call solver methods from javascript in browser. You need to build vrp-cli library for WebAssembly target. To do this, you can use wasm-pack:

cd vrp-cli
wasm-pack build --target web

It should generate wasm build + some javascript files for you. If you want to have a smaller binary, you can try to build without default features: csv-format, hre-format, scientific-format.

To test it, use the following index.html file:

<html>
<head>
    <meta content="text/html;charset=utf-8" http-equiv="Content-Type"/>
</head>
<body>
<script type="module">
    import init, { get_routing_locations, solve_pragmatic } from './pkg/vrp_cli.js';

    async function run() {
        await init();

        const pragmatic_problem = JSON.parse(`
{
  "plan": {
    "jobs": [
      {
        "id": "job1",
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.52599,
                  "lng": 13.45413
                },
                "duration": 300.0,
                "times": [
                  [
                    "2019-07-04T09:00:00Z",
                    "2019-07-04T18:00:00Z"
                  ],
                  [
                    "2019-07-05T09:00:00Z",
                    "2019-07-05T18:00:00Z"
                  ]
                ]
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job2",
        "pickups": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5225,
                  "lng": 13.4095
                },
                "duration": 240.0,
                "times": [
                  [
                    "2019-07-04T10:00:00Z",
                    "2019-07-04T16:00:00Z"
                  ]
                ]
              }
            ],
            "demand": [
              1
            ]
          }
        ]
      },
      {
        "id": "job3",
        "pickups": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5225,
                  "lng": 13.4095
                },
                "duration": 300.0
              }
            ],
            "demand": [
              1
            ],
            "tag": "p1"
          }
        ],
        "deliveries": [
          {
            "places": [
              {
                "location": {
                  "lat": 52.5165,
                  "lng": 13.3808
                },
                "duration": 300.0
              }
            ],
            "demand": [
              1
            ],
            "tag": "d1"
          }
        ]
      }
    ]
  },
  "fleet": {
    "vehicles": [
      {
        "typeId": "vehicle",
        "vehicleIds": [
          "vehicle_1"
        ],
       "profile": {
          "matrix": "normal_car"
        },
        "costs": {
          "fixed": 22.0,
          "distance": 0.0002,
          "time": 0.004806
        },
        "shifts": [
          {
            "start": {
              "earliest": "2019-07-04T09:00:00Z",
              "location": {
                "lat": 52.5316,
                "lng": 13.3884
              }
            },
            "end": {
              "latest": "2019-07-04T18:00:00Z",
              "location": {
                "lat": 52.5316,
                "lng": 13.3884
              }
            }
          }
        ],
        "capacity": [
          10
        ]
      }
    ],
    "profiles": [
      {
        "name": "normal_car"
      }
    ]
  }
}
`);

        const locations = get_routing_locations(pragmatic_problem);
        console.log(`routing locations are:\n ${locations}`);

        // NOTE let's assume we got routing matrix data for locations somehow
        // NOTE or just pass an empty array to use great-circle distance approximation
        const matrix_data= [
            {
                "matrix": "normal_car",
                "travelTimes": [
                   0,    609, 981, 906,
                   813,  0,   371, 590,
                   1055, 514, 0,   439,
                   948,  511, 463,   0
                ],
                "distances": [
                   0,    3840,  5994,  5333,
                   4696, 0,     2154,  3226,
                   5763, 2674,  0,     2145,
                   5112, 2470,  2152,  0
                ]
            }
        ];

        // config provides the way to tweak algorithm behavior
        const config = {
            "termination": {
                 "maxTime": 10,
                 "maxGenerations": 1000
            }
        };

        const solution = solve_pragmatic(pragmatic_problem, matrix_data, config);
        console.log(`solution is:\n ${solution}`);
    }

    run();
</script>
</body>
</html>

Python

Using pip

This is the easiest way to start using the solver's latest version:

pip install vrp-cli
python examples/python-interop/example.py # test example

See python code example in repo or in next section.

Using maturin

You can use maturin tool to build solver locally for you. Here are the steps:

  1. Create a virtual environment and install maturin (and pydantic):

    cd vrp-cli # directory of the crate where with python bindings are located
    python3 -m venv .venv
    source .venv/bin/activate
    pip install -U pip maturin[patchelf] pydantic
    pip freeze
    
  2. Use maturin to build and install the solver library in your current environment:

    maturin develop --release --features "py_bindings"
    
  3. Import and use the library in your python code:

import vrp_cli
import pragmatic_types as prg
import config_types as cfg
import json
from pydantic.json import pydantic_encoder

# if you want to use approximation, you can skip this definition and pass empty list later
# also there is a get_locations method to get list of locations in expected order.
# you can use this list to fetch routing matrix externally
matrix = prg.RoutingMatrix(
    profile='normal_car',
    durations=[0, 609, 981, 906, 813, 0, 371, 590, 1055, 514, 0, 439, 948, 511, 463, 0],
    distances=[0, 3840, 5994, 5333, 4696, 0, 2154, 3226, 5763, 2674, 0, 2145, 5112, 2470, 2152, 0]
)


# specify termination criteria: max running time in seconds or max amount of refinement generations
config = cfg.Config(
    termination=cfg.Termination(
        maxTime=5,
        maxGenerations=1000
    )
)

# specify test problem
problem = prg.Problem(
    plan=prg.Plan(
        jobs=[
            prg.Job(
                id='delivery_job1',
                deliveries=[
                    prg.JobTask(
                        places=[
                            prg.JobPlace(
                                location=prg.Location(lat=52.52599, lng=13.45413),
                                duration=300,
                                times=[['2019-07-04T09:00:00Z', '2019-07-04T18:00:00Z']]
                            ),
                        ],
                        demand=[1]
                    )
                ]
            ),
            prg.Job(
                id='pickup_job2',
                pickups=[
                    prg.JobTask(
                        places=[
                            prg.JobPlace(
                                location=prg.Location(lat=52.5225, lng=13.4095),
                                duration=240,
                                times=[['2019-07-04T10:00:00Z', '2019-07-04T16:00:00Z']]
                            )
                        ],
                        demand=[1]
                    )
                ]
            ),
            prg.Job(
                id="pickup_delivery_job3",
                pickups=[
                    prg.JobTask(
                        places=[
                            prg.JobPlace(
                                location=prg.Location(lat=52.5225, lng=13.4095),
                                duration=300,
                                tag="p1"
                            )
                        ],
                        demand=[1]
                    )
                ],
                deliveries=[
                    prg.JobTask(
                        places=[
                            prg.JobPlace(
                                location=prg.Location(lat=52.5165, lng=13.3808),
                                duration=300,
                                tag="d1"
                            ),
                        ],
                        demand=[1]
                    )
                ]
            )
        ]
    ),
    fleet=prg.Fleet(
        vehicles=[
            prg.VehicleType(
                typeId='vehicle',
                vehicleIds=['vehicle_1'],
                profile=prg.VehicleProfile(matrix='normal_car'),
                costs=prg.VehicleCosts(fixed=22, distance=0.0002, time=0.005),
                shifts=[
                    prg.VehicleShift(
                        start=prg.VehicleShiftStart(
                            earliest="2019-07-04T09:00:00Z",
                            location=prg.Location(lat=52.5316, lng=13.3884),
                        ),
                        end=prg.VehicleShiftEnd(
                            latest="2019-07-04T18:00:00Z",
                            location=prg.Location(lat=52.5316, lng=13.3884),
                        )
                    )
                ],
                capacity=[10]
            )
        ],
        profiles=[prg.RoutingProfile(name='normal_car')]
    )

)

# run solver and deserialize result into solution model
solution = prg.Solution(**json.loads(vrp_cli.solve_pragmatic(
    problem=json.dumps(problem, default=pydantic_encoder),
    matrices=[json.dumps(matrix, default=pydantic_encoder)],
    config=json.dumps(config, default=pydantic_encoder),
)))

print(solution)

You can check the project repository for complete example.

Please note, that type wrappers, defined in examples with pydantic, are incomplete. However, it should be enough to get started, and you can tweak them according to the documentation or rust source code.

Using local build

Another way to run the solver, built locally, from python is to use subprocess to run vrp-cli directly:

import subprocess
import json

# NOTE: ensure that paths are correct on your environment
cli_path = "./target/release/vrp-cli"
problem_path = "./examples/data/pragmatic/simple.basic.problem.json"
solution_path = "./examples/data/pragmatic/simple.basic.solution.json"
geojson_solution_path = "./examples/data/pragmatic/simple.basic.solution.geojson"

class Deserializer:
    @classmethod
    def from_dict(cls, dict):
        obj = cls()
        obj.__dict__.update(dict)
        return obj

class SolverClient:
    def __init__(self, cli_path):
        self.cli_path = cli_path

    def solve_pragmatic(self, problem_path, solution_path, geojson_solution_path):
        # NOTE: modify example to pass matrix, config, initial solution, etc.
        p = subprocess.run([self.cli_path, 'solve', 'pragmatic', problem_path,
            '-o', solution_path, '-g', geojson_solution_path, '--log'])

        if p.returncode == 0:
            with open(solution_path, 'r') as f:
                solution_str = f.read()
                return json.loads(solution_str, object_hook=Deserializer.from_dict)
        else:
            pass

solver = SolverClient(cli_path)
solution = solver.solve_pragmatic(problem_path, solution_path, geojson_solution_path)

print(f"Total cost is {solution.statistic.cost}, tours: {len(solution.tours)}")

Please note, that the solver expects file paths instead of json strings as input arguments.

Internals

This chapter describes some internals of the project.

Project structure

The project consists of the following parts:

  • vrp solver code: the source code of the solver is split into four crates:
    • rosomaxa: a crate with various metaheuristic building blocks and its default implementation
    • vrp-core: a core crate for vrp domain
    • vrp-scientific: a crate with functionality to solve problems from some of scientific benchmarks on top of the core crate
    • vrp-pragmatic: a crate which provides logic to solve rich VRP using pragmatic json format on top of the core crate
    • vrp-cli: a crate which aggregates logic of others crates and exposes them as a library and application
  • docs: a source code of the user guide documentation published here. Use mdbook tool to build it locally.
  • examples: provides various examples:
    • data: a data examples such as problem definition, configuration, etc.
    • json-pragmatic: an example how to solve problem in pragmatic json format from rust code using the project crates
    • jvm-interop: a gradle project which demonstrates how to use the library from java and kotlin
  • experiments: a playground for various experiments

Overview

Mind map which describes in short project's goals, used algorithms, and challenges.

@startmindmap
<style>
mindmapDiagram {
    :depth(1) {
      BackGroundColor lightGreen
    }
}
</style>

*[#white] solver

right side
 * mission/goal
  * best feature set
   *_ as many features as possible
   *_ out of the box
  * good quality
   *_ close to best known
  * fast
   *_ return acceptable solutions fast
  * low resource consumption
   *_ memory
   *_ cpu


 * features
  * variants
   *_ Capacitated VRP (CVRP)
   *_ Heterogeneous Fleet VRP (HFVRP)
   *_ VRP with Time Windows (VRPTW)
   *_ VRP with Pickup and Delivery (VRPPD)
   *_ VRP with backhauls (VRPB)
   *_ Multi-Depot VRP (MDVRP)
   *_ Multi-Trip VRP (MTVRP)
   *_ Multi-Objective VRP (MOVRP)
   *_ Open VRP (OVRP)
   *_ VRP with Lunch Break (VRPLB)
   *_ VRP with Route Balance (VRPRB)
   *_ Periodic VRP (PVRP)
   *_ Time dependent VRP (TDVRP)
   *_ Skill VRP (SVRP)
   *_ Traveling Salesman Problem (TSP)
   *_ ...

  * informal
   * stable
    *_ pickups, deliveries, skills, etc.
    *_ multi-location job
    *_ initial solution
    *_ scientific formats
    *_ ...
   * experimental
    * job type
     *_ service
     *_ replacement
     * multi-job
      *_ minor perf. improv.
    * vehicle place
     *_ reload
    * break
     *_ legal break
     *_ multiple breaks
     *_ unassigned break weight
    *_ multi tour
    *_ tour balancing
    *_ time dependent routing
    *_ multiple solutions
    *_ ...


 * heuristics

  * constructive
   * insertion
    *_ cheapest
    *_ n-regret
    *_ skip n-best
    *_ blinks
    *_ + 3 more    
   *_ nearest-neighbour

  * meta

   * mutation
    * ruin recreate (LNS)
     * ruin
      *_ adjusted string removal (SISR)
      *_ cluster removal (DBSCAN)
      *_ random job removal
      *_ + 3 more
     * recreate
      *_ reuse constructuve heuristics
    * local search
     *_ inter route exch.
     *_ intra route exch.
    * decomposition
     *_ decompose solution into smaller ones
     *_ create and solve smaller problems independently
     *_ compose a new solution from partial ones

   * diversification
    * rosomaxa
     *_ cluster solutions by ANN (GSOM)
     *_ 2D search process visualization
     *_ diversity tuning
    *_ elite
    *_ greedy

   * objective
    * kind
     *_ multi (NSGA-II)
     *_ hierarchical
    * types
     *_ minimize/maximize routes
     *_ minimize cost
     *_ minimize unassigned
     *_ tour balancing

  * hyper
   * kind
    * selection
     * fixed probabilities
      *_ select from the list
      *_ combine multiple
 
     * dynamic probabilities
      *_ MDP model
      *_ apply RL

    * generative
     *_ TBD

 * challenges
  * exploration/exploitation dilemma
   * issues
    *_ stagnation
    *_ unstable quality results
   * solutions
    * improve meta-heuristic
     *_ more ruin/recreates
     *_ optimal deconstruction (removal) size
     *_ more local search operators (e.g. 2-opt.)
     *_ extra mutation types
    * improve hyper-heuristic
     *_ RL/MDP: dynamic probabilities [WIP]
     *_ ROSOMAXA: dynamic parameters

  * algorithm optimizations
   *_ data parallelism control
   *_ caching

  * feature requirements
   * issues
    * algorithm extensibility
     *_ insertion heuristic assumptions
     *_ ruin/recreate approach
    *_ feature interference
    *_ common format representation

@endmindmap

Development

This chapter describes development topics, such as high level architecture, project structure, code style, testing, etc.

Project structure

The project consists of the main and auxiliary crates. Additionally, there is some logical separation inside each group.

Main crates

The following crates are "the heart" of VRP solver:

  • rosomaxa: contains key algorithms for solving optimization problems without locking to the VRP domain such as hyper heuristics, evolution strategies, etc.
  • vrp_core: this crate provides all core VRP models with various meta heuristics to solve rich VRP
  • vrp_scientific: has a building blocks to solve problems from some of scientific benchmarks. It is useful to evaluate the solver performance in terms of solution quality, search stability and running time.
  • vrp_pragmatic: provides models and features to support rich VRP. It includes:
    • pragmatic model, serializable in json
    • solution checker
    • problem validator
  • vrp_cli: exposes VRP solve as command line interface or static library. Additionally, has some extra features, such as:
    • various extra commands
    • pyO3 bindings to make library usable from Python
    • WASM bindings to run solver directly in the browser
    • ..

For these crates, you can find extra information normally published on docs.rs.

Helper crates/functionality

There are few:

  • experiments/heuristic-research: my way to experiment with heuristic using some hooks and visualizations. Live version is exposed here
  • examples/json-pragmatic: provides example how to use the library as a crate + contains tests and benchmarks on test data
  • examples/jvm-interop / python-interop: some examples how to call library from other languages
  • examples/data: various examples of problem definitions. Mostly used for testing and documentation

How to extend solver

This section's intention is to give a very brief explanation of some key concepts which might be needed for adding an extra feature on top of existing logic. For more detailed information, check the corresponding main crates documentation or source code

Constrains & objectives

A Vehicle Routing Problem used to consist of various constraints and objectives functions, such as:

  • capacity constraint
  • time window constraint
  • job's total value maximization
  • cost/duration/distance minimization
  • etc.

Internally, they can be divided into different groups:

  • hard constraints: a problem invariants which must be hold. Examples: vehicle capacity, job time window.
  • soft constraints: a problem variants which should be either maximized or minimized. Examples: job assignment, served job's total value.
  • objectives: an objective of optimization. Typically, it is hierarchical: first, try to assign all jobs, then reduce the total cost of serving them
  • state: an auxiliary logic to cache some important state and retrieve it faster during search

Under the hood, a feature concept combines all these groups. This is based on observation, that many features requires constraint and objective defined at the same time.

A feature concept

Let's take a brief look at some example: a total job value feature, which purpose to maximize value of assigned jobs. Here, each job has some associated value (or zero if it is not specified) and the purpose is to maximize it.

The following code below utilizes FeatureBuilder to construct the feature:

    FeatureBuilder::default()
        .with_name(name)
        .with_objective(MaximizeTotalValueObjective {
            estimate_value_fn: Arc::new({
                let job_read_value_fn = job_read_value_fn.clone();
                let sign = -1.;
                move |route_ctx, job| {
                    sign * match &job_read_value_fn {
                        JobReadValueFn::Left(left_fn) => (left_fn)(job),
                        JobReadValueFn::Right(right_fn) => (right_fn)(route_ctx.route().actor.as_ref(), job),
                    }
                }
            }),
        })
        .with_constraint(MaximizeTotalValueConstraint { merge_code, job_read_value_fn, job_write_value_fn })
        .build()

This builder gets:

  • a unique feature name
  • dedicated objective function which counts value and prefers solutions where it is maximized
  • a dedicated constraint which enforces some problem invariants regarding job value (in this case, only for proximity clustering)

Additionally, the builder accepts FeatureState. Check existing features for more details.

TODO: expand example

Development practices

This article describes some development practices used in the project.

Code style

File level

try to keep size of the source file small

Ideally, the maximum file size is good to have in [300,500] range of lines in total.

use * import to avoid long import lines.

Advantages:

  • shorter import
  • less lines in total

Disadvantages:

  • it is bad for version control: it’s harder to track what has been added to the local file namespace. Although it is valid, I believe it is not a big issue.
  • it can lead to unnecessary naming collisions. Can be solved using aliasing (alias/as keywords)

NOTE: on crate level, preludes can be used to have a collection of names that are automatically brought into scope of every module in a crate.

Function level

prefer functional style over imperative

  • declarative approach which describes what to do rather how to do
  • more readability as code is naturally grouped.

For example, use list comprehensions over loops:

#![allow(unused)]
fn main() {
let mut sum = 0;
for i in 1..11 {
    sum += i;
}
println!("{}", sum);
}

vs

#![allow(unused)]
fn main() {
println!("{}", (1..11).fold(0, |a, b| a + b));
}

prefer linear style to multiple one-several lines functions which are called just once

Advantages (personal taste):

  • code is easier to follow (fewer jumps here and there over code base)
  • call stack is less nested, so debug is easier
  • benefit of not making it possible to call the function from other places

However, this is not a hard rule. In some cases, you might prefer to split:

  • multiple usages
  • separate function provides a good abstraction over complex logic
  • make sense to test it separately
  • ..

In general, don’t be over-eager to abstract, or offended by a few lines of duplication. Premature abstraction often ends up coupling code that should not have to evolve together.

Please note, that this is not about proposing a single 1000-lines god function.

Additional resources:

  • http://number-none.com/blow/blog/programming/2014/09/26/carmack-on-inlined-code.html

Code organization level

prefer directory/file hierarchy over flat structure

use variable name shadowing

This helps to reduce hassle in some degree by allowing:

  • reusing variable names rather than creating unique ones;
  • transforming variables without making them mutable;
  • converting type without manually creating two variables of different types (compiler does it automatically)

Comments

write comments on public api

It is enforced by #![warn(missing_docs)]

comment non trivial logic, use NOTE if necessary

use TODO prefix to signalize about missing implementation

Toolchain

General

use code formatter

Cargo formatter can be used:

cargo fmt

Please note, that the project has some default rules in overridden. Check .rustfmt.toml file for details.

use static code analyzer

Cargo clippy is default tool:

cargo clippy --all-features -- -D warnings

This command runs clippy tool with the setting which interprets all warning as errors. This should be a default strategy.

automate some steps on CI

  • run unit/component/feature tests
  • measure code coverage

analyze hot-spots

The following command is useful to get a list of most modified files for last two years according to the git history:

git log --pretty=format: --since="2 years ago" --name-only -- "*.rs" | sed '/^\s*$/d' | sort | uniq -c | sort -rg | head -20

The idea is to look closely at top of the list and refactor the big files to have more fine-grained and isolated abstractions in separate modules (and files). This will help to reduce amount of changes in the same files, potentially done simultaneously by the different team members.

Testing (WIP)

This article contains a collection of various rules, suggestions and thoughts learned while working on vrp solver project.

Code style

The first important aspect is a style specific for testing code only. Here the following aspects are considered:

test organization

  • tests are split into different types;

    • unit tests
    • feature (integration) tests
    • discovery tests
    • performance tests
    • documentation tests
  • where code is located

    Official documentation suggests to put unit tests to the same file with the code that they’re testing. This way you can test private interfaces too. In my opinion, keeping testing code within production decreases maintainability of the later and it is better to separate them. So, the project uses the different approach:

    • create a tests folder in the crate root. This folder is known by cargo as it is used for integration tests
    • based on test type, create a specific folder inside, e.g. unit
    • create a separate file using the pattern <filename>_test.rs
    • put it as a child of tests folder keeping the same directory tree, e.g. my_crate/tests/unit/algorithms/clustering/dbscan_test.rc
    • in production code, include it as a child module using path directive:
#![allow(unused)]
fn main() {
#[cfg(test)]
#[path = "../../../tests/unit/algorithms/clustering/dbscan_test.rs"]
mod dbscan_test;
}

The testing module is considered as child module, that's why it has full access to the production private interface.

what is tested (TODO)

shared functionality

  • fake/stubs
    • fake constraints
  • add helper logic for repeatable setup/validation functionality
    • simple functions
    • builder pattern
    • should be easily discoverable
      • add separate helpers module
  • ..

test types

unit & component testing

  • data-driven testing
    • check exact conditions and verify import
  • mocks vs fakes

feature testing

  • user acceptance tests

importance of discovery tests

  • last resort
  • requires solution checker
  • might be difficult to debug
    • huge output
    • unable to minimize the problem
      • how to research such problems
        • minimize manually
        • disable parallelism
        • try to reduce amount of heuristics used (more predictable)
  • proptest library

regression tests

  • very specific use cases which were not handled by unit/component testing due to their complexity
  • ideally amount of such tests should be minimized
  • can be replaced by discovery tests?

performance testing

  • libraries
    • criterion
  • can be run using cargo bench command
  • difficult to have results stable
    • no isolation
    • non-determinism

quality testing

  • benchmarks
    • use scientific data
      • CVRP
      • VRPTW
    • challenge: no information about how long it can be run
  • script automation

documentation tests

  • very few at the moment as the main focus is on standalone usage, not on as a crate lib.

metrics

code coverage

  • aim to have all significant logic covered
  • use generated reports to understand gaps in tested code
  • never write tests just to increase code coverage
  • 90% is fine

tests stability

  • aim for no random failures

Algorithms

This chapter describes some used algorithms.

References

An incomplete list of important references:

  • Clarke, G & Wright, JW 1964: Scheduling of vehicles from a Central Depot to a Number of the Delivery Point. Operations Research, 12 (4): 568-581

  • Pisinger, David; Røpke, Stefan: A general heuristic for vehicle routing problems

  • Schrimpf, G., Schneider, K., Stamm-Wilbrandt, H., Dueck, V.: Record Breaking Optimization Results Using the Ruin and Recreate Principle. J. of Computational Physics 159 (2000) 139–171

  • Jan Christiaens, Greet Vanden Berghe: Slack Induction by String Removals for Vehicle Routing Problems

  • Thibaut Vidal: Hybrid Genetic Search for the CVRP: Open-Source Implementation and SWAP* Neighborhood

  • Richard F. Hartl, Thibaut Vidal: Workload Equity in Vehicle Routing Problems: A Survey and Analysis

  • K. Deb; A. Pratap; S. Agarwal; T. Meyarivan: A fast and elitist multiobjective genetic algorithm: NSGA-II

  • Damminda Alahakoon, Saman K Halgamuge, Srinivasan Bala: Dynamic self-organizing maps with controlled growth for knowledge discovery

  • Daniel J. Russo, Benjamin Van Roy, Abbas Kazerouni, Ian Osband and Zheng Wen: A Tutorial on Thompson Sampling https://web.stanford.edu/~bvr/pubs/TS_Tutorial.pdf

  • Florian Arnold, Kenneth Sörensen: What makes a solution good? The generation of problem-specific knowledge for heuristics

  • Flavien Lucas, Romain Billot, Marc Sevaux: A comment on "what makes a VRP solution good? The generation of problem-specific knowledge for heuristics"

  • Erik Pitzer, Michael Affenzeller: A Comprehensive Survey on Fitness Landscape Analysis

Heuristics

This page provides some high level overview of general heuristic used to solve VRP. Some information can be found on vrp-core crate's documentation page

Starting from scratch: constructive heuristics

To build initial solutions to start with, the solver internally can use different built-in constructive heuristics, such as:

  • variation of Clark & Wright Savings algorithm
  • regret insertion
  • insertion with blinks
  • nearest neighbor
  • random insertions
  • etc.

Related documentation

To support faster evaluation of jobs insertion in large tours, insertion algorithm uses a search optimization to prevent greedy evaluation. This is useful in case of large scale VRPs when greedy evaluation significantly increases running time.

Related documentation

Typically, the solver builds four initial solutions, then they are memorized as initial state by the one of the population algorithms:

  • greedy: only the best solution is kept
  • elitism: n best solutions are kept using some diversification criteria
  • rosomaxa: a custom population-based algorithm which focuses on improving exploration/exploitation ratio.

The latter is default, however, others can be used if amount of available CPU is low.

Related documentation

Searching for better solution: meta heuristics

The goal of metaheuristic (or just heuristic for simplicity) is to refine one (or many) of the known solutions. Currently available heuristics:

  • ruin and recreate principle (Adaptive Large Neighborhood Search): ruin parts of solution and recreates them. Key ideas:
    • use multiple ruin/recreate methods and combine them differently
    • make a larger moves in solution space
  • local search: use different local search operators. The main difference from R&R:
    • avoids making big steps in a solution space
    • target to improve specific aspects in solution
  • explorative heuristics: these can be seen as generators for explorative moves in solutions space:
    • redistribute search: removes jobs from specific route and prevents their insertion back to it
    • infeasible search: allows constraint violations to explore infeasible solutions space. It has recovery step to move back to feasible space.
  • decomposition search (some kind of Divide and Conquer algorithm): splits existing solution into multiple smaller ones (e.g. not more than 2-4 routes) and tries to improve them in isolation. Typically, it uses all heuristics just mentioned.

Each heuristic accepts one of solutions from the population (not necessary the best known) and tries to improve it (or diversify). During one of refinement iterations, many solutions are picked at the same time and many heuristics are called then in parallel. Such incremental step is called a generation. Once it is completed, all found solutions are introduced to the population, which decides how to store them. With elitism/rosomaxa population types, to order solution from Pareto optimal front, Non-Dominated Sorting Genetic Algorithm II is used.

Related documentation

What heuristic to pick: hyper heuristic

As the solver is not limited to one heuristic, there is a problem: what is the best strategy to pick one of the pre-defined heuristics at given search state? To solve that problem, there are two heuristics are available:

  • static selective: associate with every heuristic some probability weight and use it to decide which one to pick next
  • dynamic selective: try to learn probability dynamically based on search progression over time.

The latter is used by default.

Related documentation

When to stop: termination criteria

The search is terminated and the best known solution is returned when some termination criteria is met. The following termination criteria are supported:

  • time: stop after some specified amount of seconds
  • generation: stop after some specified amount of generations
  • coefficient of variation: stop if there is no significant improvement in specific time or amount of generations
  • user interrupted from command line, e.g. by pressing Ctrl + C

Interruption when building initial solutions is supported. Default is 300 seconds or 3000 generations max.

Related documentation

ROSOMAXA

ROSOMAXA stands for Routing Optimizations with Self-Organizing Maps And EXtrAs - a custom evolutionary algorithm which tries to address the problem of population diversity: ability to retain different individuals in the population and use them as an input for the search procedure. Additionally, it utilizes reinforcement learning technics to dynamically pick suitable meta-heuristics for given problem formulation to avoid premature convergence.

Key ideas

The rosomaxa algorithm is based on the following key ideas:

  • use Growing Self-Organizing Map(GSOM) to cluster discovered solutions and retain good, but different ones
  • choice clustering characteristics which are specific to solution geometry rather to objectives
  • utilize reinforcement learning technics in dynamic hyper-heuristic to choose one of pre-defined meta-heuristics on each solution refinement step.
  • use 2D visualization to analyze and understand algorithm behavior. See an interactive demo here

Clustering

Solution clustering is preformed by custom implementation of GSOM which is a growing variant of a self-organizing map. In rosomaxa, it has the following characteristics:

  • each node maintains a small population which keeps track of a few solutions selected by elitism approach
  • nodes are created and split based on selected solution characteristics. For VRP domain, they are such as:
    • vehicle max load variance
    • standard deviation of the number of customer per tour
    • mean of route durations
    • mean of route distances
    • mean of route waiting times
    • average distance between route medoids
    • amount of routes
  • periodically, the network is compacted and rebalanced to keep search analyzing most prominent local optimum. Compaction is done using a "decimation" approach: remove every second-third (configurable) column/row and move survived cells towards the center (a node with (0, 0) as a coordinate). Overall, this approach seems help to maintain a good exploration-exploitation ratio.

Visualization

This old animation shows some insights how algorithms performs over time:

Visualization example

Here:

  • u_matrix is unified distance matrix calculated using solution characteristics
  • t_matrix and l_matrix shows how often nodes are updated
  • objective_0, objective_1, objective_2: objective values such as amount of unassigned jobs, tours, and cost

The new experimental visualization tool is part of the repo: experiments/heuristic-research. Online version is available here: https://reinterpretcat.github.io/heuristics/www/vector.html

Dynamic hyper-heuristic

Essentially, a built-in dynamic hyper-heuristic uses Multi-Armed Bandit with Thompson sampling approach to pick meta-heuristic for the list. This helps to address exploration-exploitation dilemma in applying a strategy of picking heuristics.

Implementation can be found here

Additional used techniques

TODO: describe additional explorative techniques:

  • tabu list usage in ruin methods
  • alternative objectives manipulation
  • ..

Further research

  • experiment with different solution characteristics
  • rebalance GSOM parameters based on search progression
  • analyze "heat" map dynamically to adjust GSOM parameters
  • more fine-grained control of exploration vs exploitation ratio
  • try to calculate gradients based on GSOM nodes