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.24.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 idLAT
(float): a latitudeLNG
(float): a longitudeDEMAND
(integer): a single dimensional demand. Depending on the value, it models different job activities:- positive:
pickup
- negative:
delivery
- zero:
service
- positive:
DURATION
(integer): job duration in secondsTW_START
(date in RFC3999): earliest time when job can be servedTW_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 idLAT
(float): a depot latitudeLNG
(float): a depot longitudeCAPACITY
(unassigned integer): a single dimensional vehicle capacityTW_START
(date in RFC3999): earliest time when vehicle can start at depotTW_END
(date in RFC3999): latest time when vehicle should return to depotAMOUNT
(unassigned integer): a vehicle amount of this typePROFILE
(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.
Code usage
You can use the library from the code, check code examples to see how.
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 probabilitiesdynamic selective
: applies reinforcement learning technics to adjust probabilities of predefined metaheuristicsmulti 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
ornoneOf
conditions:
These conditions are tested against vehicle's skills."skills": { "allOf": [ "fridge" ], "noneOf": [ "washing_machine" ] }
- 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 inTasks
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.
Related errors
- E1100 duplicated job ids
- E1101 invalid job task demand
- E1102 invalid pickup and delivery demand
- E1103 invalid time windows in jobs
- E1104 reserved job id is used
- E1105 empty job
- E1106 job has negative duration
- E1107 job has negative demand
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 byearliest
andlatest
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 bylocation
(optional),duration
(required) andtag
(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 hereAdditionally, offset time interval requires departure time optimization to be disabled explicitly (see E1307).
- required: this break is guaranteed to be assigned at cost of flexibility. It has the following properties:
-
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.
Related errors
- E1300 duplicated vehicle type ids
- E1301 duplicated vehicle ids
- E1302 invalid start or end times in vehicle shift
- E1303 invalid break time windows in vehicle shift
- E1304 invalid reload time windows in vehicle shift
- E1306 time and duration costs are zeros
- E1307 time offset interval for break is used with departure rescheduling
- E1308 invalid vehicle reload resource
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 toreload
id
(required): an unique resource id. Put this id in vehicle reload'sresourceId
property to trigger shared resource behaviorcapacity
(required): total amount of resource. It has the same type as vehicle'scapacity
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
andreload
- 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
Related errors
- E1200 relation has job id which does not present in the plan
- E1201 relation has vehicle id which does not present in the fleet
- E1202 relation has empty job id list
- E1203 strict or sequence relation has job with multiple places or time windows
- E1204 job is assigned to different vehicles in relations
- E1205 relation has invalid shift index
- E1206 relation has special job id which is not defined on vehicle shift
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, onlyvicinity
is supportedprofile
: 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 limitdistance
: moving distance limitminSharedTime
(optional): minimum shared time for jobs (non-inclusive)smallestTimeWindow
(optional): the smallest time window of the cluster after service time shrinkingmaxJobsPerCluster
(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 locationcontinue
: 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 aparking
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 timemultiplier
: multiplies original service time by fixedvalue
fixed
: uses a new fixedvalue
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 parkingtour.stop.activity.commute
: specifies job commute information. It has two properties,forward
andbackward
which specify information about activity place visit:location
: a location before/after place visitdistance
: travelled distancetime
: 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 objectives and defines lexicographical ordered
objective function. Here, priority of objectives decreases from first to the last element of the array. For the same
priority (or in other words, competitive) objectives, a special multi-objective
type can be used.
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 distanceminimize-distance
: minimizes total distance of all routesminimize-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 solutionmaximize-tours
: maximizes total amount of tours present in solutionminimize-arrival-time
: prefers solutions where work is finished earlierfast-service
: prefers solutions when jobs are served early in tours. Optional parameter:tolerance
: an objective tolerance specifies how different objective values have to be to consider them different. Relative distance metric is used.
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 costsbreaks
: a value penalty for skipping a break. Default value is 100.
tour-order
: controls desired activity order in toursisConstrained
: 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 1threshold
: a minimum shared jobs to countdistance
: 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 tourbalance-activities
: balances amount of activities performed in tourbalance-distance
: balances travelled distance per tourbalance-duration
: balances tour durations
Typically, you need to use these objective with one from the cost group combined under single multi-objective
.
An usage example:
{
"type": "multi-objective",
"strategy": {
"name": "sum"
},
"objectives": [
{
"type": "minimize-cost"
},
{
"type": "balance-max-load"
}
]
}
Default behaviour
By default, decision maker minimizes the number 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 that corresponds to a classical hierarchical objective used, for example,
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 use multi-objective:
"objectives": [
{
"type": "minimize-unassigned"
},
{
"type": "minimize-tours"
},
{
"type": "multi-objective",
"strategy": {
"name": "sum"
},
"objectives": [
{
"type": "minimize-cost"
},
{
"type": "balance-max-load"
}
]
}
]
Related errors
- E1600 an empty objective specified
- E1601 duplicate objective specified
- E1602 missing one of cost objectives
- E1603 redundant value objective
- E1604 redundant tour order objective
- E1605 value or order of a job should be greater than zero
- E1606 multiple cost objectives specified
- E1607 missing value objective
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.
Related errors
- E0002 cannot create transport costs
- E1500 duplicate profile names
- E1501 empty profile collection
- E1502 mixing different location types
- E1503 location indices requires routing matrix to be specified
- E1504 amount of locations does not match matrix dimension
- E1505 unknown matrix profile name in vehicle or vicinity clustering profile
Routing matrix format
In general, routing matrix has the following schema:
profile
(required for time dependent VRP) is name of vehicle profiletimestamp
(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 arraydistances
(required) is square matrix of distances in abstract distance unit represented via single dimensional arrayerrorCodes
(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):
0 | AB | AC |
BA | 0 | BC |
CA | CB | 0 |
where
0
: zero duration or distanceXY
: 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
ordelivery
- 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
code | description | possible action |
---|---|---|
NO_REASON_FOUND | unknown | |
SKILL_CONSTRAINT | cannot serve required skill | allocate more vehicles with given skill? |
TIME_WINDOW_CONSTRAINT | cannot be visited within time window | allocate more vehicles, relax time windows, etc.? |
CAPACITY_CONSTRAINT | does not fit into any vehicle due to capacity | allocate more vehicles? |
REACHABLE_CONSTRAINT | location unreachable | change job location to routable place? |
MAX_DISTANCE_CONSTRAINT | cannot be assigned due to max distance constraint of vehicle | allocate more vehicles? |
MAX_DURATION_CONSTRAINT | cannot be assigned due to max duration constraint of vehicle | allocate more vehicles? |
BREAK_CONSTRAINT | break is not assignable | correct break location or/and time window? |
LOCKING_CONSTRAINT | cannot be served due to relation lock | review relations? |
AREA_CONSTRAINT | cannot be assigned due to area constraint | make sure that jobs inside allowed areas |
TOUR_SIZE_CONSTRAINT | cannot be assigned due to tour size constraint of vehicle | make sure that there are enough vehicles to serve jobs |
TOUR_ORDER_CONSTRAINT | cannot be assigned due to tour order constraint | tour order might be too strict or not vehicles enough |
GROUP_CONSTRAINT | cannot be assigned due to group constraint | try to reduce amount of jobs in the group? |
COMPATIBILITY_CONSTRAINT | cannot be assigned due to compatibility constraint | review job's compatibilities |
RELOAD_RESOURCE_CONSTRAINT | cannot be assigned due to reload resource constraint | review 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
andtimestamp
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 sameid
- 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 inexamples
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 demand1
5
vehicles with capacity20
- 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-tours"
},
{
"type": "multi-objective",
"strategy": {
"name": "sum"
},
"objectives": [
{
"type": "minimize-cost"
},
{
"type": "balance-max-load"
}
]
}
]
}
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-tours"
},
{
"type": "multi-objective",
"strategy": {
"name": "sum"
},
"objectives": [
{
"type": "minimize-cost"
},
{
"type": "balance-max-load"
}
]
}
]
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": "multi-objective",
"strategy": {
"name": "sum"
},
"objectives": [
{
"type": "minimize-cost"
},
{
"type": "balance-activities"
}
]
}
]
}
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": "multi-objective",
"strategy": {
"name": "sum"
},
"objectives": [
{
"type": "minimize-cost"
},
{
"type": "balance-activities"
}
]
}
]
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-tours"
},
{
"type": "multi-objective",
"strategy": {
"name": "sum"
},
"objectives": [
{
"type": "minimize-cost"
},
{
"type": "balance-distance"
}
]
}
]
}
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-tours"
},
{
"type": "multi-objective",
"strategy": {
"name": "sum"
},
"objectives": [
{
"type": "minimize-cost"
},
{
"type": "balance-distance"
}
]
}
]
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:
-
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
-
Use maturin to build and install the solver library in your current environment:
maturin develop --release --features "py_bindings"
-
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
*_ lexicographical
* 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 / features with various meta heuristics to solve rich VRPvrp_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 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 hereexamples/json-pragmatic
: provides example how to use the library as a crate + contains tests and benchmarks on test dataexamples/jvm-interop
/python-interop
: some examples how to call library from other languagesexamples/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 themstate
: 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 and vrp-core/examples
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
ratherhow 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:
- create a
#![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
- add separate
- ..
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)
- how to research such problems
- 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
- use scientific data
- 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
-
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.
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.
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 keptelitism
: n best solutions are kept using some diversification criteriarosomaxa
: 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.
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 itinfeasible 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.
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 nextdynamic selective
: try to learn probability dynamically based on search progression over time.
The latter is used by default.
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 secondsgeneration
: stop after some specified amount of generationscoefficient of variation
: stop if there is nosignificant
improvement in specific time or amount of generationsuser 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.
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:
Here:
u_matrix
is unified distance matrix calculated using solution characteristicst_matrix
andl_matrix
shows how often nodes are updatedobjective_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
vsexploitation
ratio - try to calculate gradients based on GSOM nodes