Trying with the sample:
https://github.com/google/or-tools/blob/master/ortools/constraint_solver/doc/vrp.md#time-window-constraints
The output is:
Objective: 70
Route for Vehicle 0:
0 Time(0,0) Slack(0,3) -> 9 Time(2,5) Slack(0,3) -> 14 Time(5,8) Slack(0,3) -> 16 Time(7,10) Slack(0,16) -> 0 Time(14,30)
Time of the route: 14m
Route for Vehicle 1:
0 Time(0,0) Slack(0,1) -> 12 Time(4,5) Slack(0,1) -> 13 Time(6,7) Slack(0,1) -> 15 Time(11,12) Slack(0,1) -> 11 Time(14,15) Slack(0,10) -> 0 Time(20,30)
Time of the route: 20m
Route for Vehicle 2:
0 Time(0,0) Slack(0,3) -> 7 Time(2,5) Slack(0,3) -> 4 Time(6,9) Slack(0,3) -> 3 Time(7,10) Slack(0,5) -> 1 Time(10,15) Slack(0,14) -> 0 Time(16,30)
Time of the route: 16m
Route for Vehicle 3:
0 Time(0,0) Slack(0,1) -> 5 Time(3,4) Slack(0,1) -> 8 Time(5,6) Slack(0,1) -> 6 Time(7,8) Slack(0,1) -> 2 Time(10,11) Slack(0,1) -> 10 Time(14,15) Slack(0,10) -> 0 Time(20,30)
Time of the route: 20m
Total Distance of all routes: 70m
How can we read the solution in an understandable way?
What is exactly the Time here? For example, taking the Time for first vehicle between 14 and 16:
Location (14) Time(5,8) -> Location (16) Time(7,10)
The time ends at location 14 at 8 but it start at 7 on location next location 16!
For second vehicle, at the first route, Slack is (0, 1). What is 0 here? And what is 1? It suppose to be the waiting time because this is a time dimension (ref). But the vehicle will arrive to site 12 at 4 not at 1.
So, what 1 represent here?
And why all slacks first value is 0?
Viewing the time needed between every two sites. It can be seen as this, isn't it?
聽 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16
-- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | --
0 | 0 | 6 | 9 | 8 | 7 | 3 | 6 | 2 | 3 | 2 | 6 | 6 | 4 | 4 | 5 | 9 | 7
1 | 6 | 0 | 8 | 3 | 2 | 6 | 8 | 4 | 8 | 8 | 13 | 7 | 5 | 8 | 12 | 10 | 14
2 | 9 | 8 | 0 | 11 | 10 | 6 | 3 | 9 | 5 | 8 | 4 | 15 | 14 | 13 | 9 | 18 | 9
3 | 8 | 3 | 11 | 0 | 1 | 7 | 10 | 6 | 10 | 10 | 14 | 6 | 7 | 9 | 14 | 6 | 16
4 | 7 | 2 | 10 | 1 | 0 | 6 | 9 | 4 | 8 | 9 | 13 | 4 | 6 | 8 | 12 | 8 | 14
5 | 3 | 6 | 6 | 7 | 6 | 0 | 2 | 3 | 2 | 2 | 7 | 9 | 7 | 7 | 6 | 12 | 8
6 | 6 | 8 | 3 | 10 | 9 | 2 | 0 | 6 | 2 | 5 | 4 | 12 | 10 | 10 | 6 | 15 | 5
7 | 2 | 4 | 9 | 6 | 4 | 3 | 6 | 0 | 4 | 4 | 8 | 5 | 4 | 3 | 7 | 8 | 10
8 | 3 | 8 | 5 | 10 | 8 | 2 | 2 | 4 | 0 | 3 | 4 | 9 | 8 | 7 | 3 | 13 | 6
9 | 2 | 8 | 8 | 10 | 9 | 2 | 5 | 4 | 3 | 0 | 4 | 6 | 5 | 4 | 3 | 9 | 5
10 | 6 | 13 | 4 | 14 | 13 | 7 | 4 | 8 | 4 | 4 | 0 | 10 | 9 | 8 | 4 | 13 | 4
11 | 6 | 7 | 15 | 6 | 4 | 9 | 12 | 5 | 9 | 6 | 10 | 0 | 1 | 3 | 7 | 3 | 10
12 | 4 | 5 | 14 | 7 | 6 | 7 | 10 | 4 | 8 | 5 | 9 | 1 | 0 | 2 | 6 | 4 | 8
13 | 4 | 8 | 13 | 9 | 8 | 7 | 10 | 3 | 7 | 4 | 8 | 3 | 2 | 0 | 4 | 5 | 6
14 | 5 | 12 | 9 | 14 | 12 | 6 | 6 | 7 | 3 | 3 | 4 | 7 | 6 | 4 | 0 | 9 | 2
15 | 9 | 10 | 18 | 6 | 8 | 12 | 15 | 8 | 13 | 9 | 13 | 3 | 4 | 5 | 9 | 0 | 9
16 | 7 | 14 | 9 | 16 | 14 | 8 | 5 | 10 | 6 | 5 | 4 | 10 | 8 | 6 | 2 | 9 | 0
From the this table, the time needed for first vehicle is as follow:
Site | 0 | 9 | 14 | 16 | 0
-- | -- | -- | -- | -- | --
Travel Time from previous | 聽 | 2 | 3 | 2 | 7
But the algorithm prints:
14 Time(5,8) Slack(0,3) -> 16 Time(7,10) Slack(0,16)
How is this print anyway related to the travel time above?
Is there bug(s) that made the solution numbers inconsistent?
Many thanks,
WARNING: DRAFT (extracted from an email)
First you must read https://developers.google.com/optimization/routing/vrp#dimension_details to understand what is a Dimension and the CumulVar.
Q: I am particularly not sure about the methods:
time_dimension.CumulVar(routing.NodeToIndex(location)).SetRange(time_window[0], time_window[1])
A: Since we have create a "travel time" dimension, each time you travel from A to B, you add the transit value (ed transit is the callback you pass when building the time dimension) to a CumulVar.
e.g. transit(A, B) return 2 and transit(B, C) return 3.
supposing you have the route A -> B -> C (with start node being node A).
then you'll have:
time_dimension.Cumul(A) == 0 # if you set_cumul_var_to_zero in dimension constructortime_dimension.Cumul(B) == time_dimension.Cumul(A) + time_dimension.transit(A,B) == 0 + 2 == 2time_dimension.Cumul(C) == time_dimension.Cumul(B) + time_dimension.transit(B, C) == 2 + 3 == 5.Supposing you have a time windows at location X [time_0, time_1] which mean we want the value of Cumul(X) to be in range [time_0, time_1]
-> Cumul(X).SetRange(time_0, time_1)
and
Q: SlackVar you must add it to the solution using
routing.AddToAssignment(time_dimension.SlackVar(index)) # what is SlackVar?
A: SlackVar is a waiting time, actually the relation between two consecutive location is
time_dimension.Cumul(B) == time_dimension.Cumul(A) + time_dimension.transit(A,B) + slackVar(A)
Suppose:
9 at node B (i.e. Cumul(B).SetRange(9,9)),transit(A, B) = 5 andA at time 0 (i.e. Cumul(A) == 0)Cumul(B) == 9 # TW aka SetRange() constraintCumul(B) == Cumul(A) + transit(A,B) + slackVar(A) => 9 == 0 + 5 + slackVar(A) => 9 - 5 == 4 == slackVar(A)A before traveling to B.Now suppose, Cumul(A) is not a value but a range of integral value (e.g. [0,3]), as well as Cumul(B) (e.g. [9,11]).
So for each value in Cumul(A) and each value of Cumul(B) you have a range of SlackVar values. Then you make the union of all SlackVar values and you have the range returned by the solver.
So here the min SlackVar value according to Value of Cumul(A) and Cumul(B).
| A \ B | 9 | 10 | 11 |
|----------|----|----|----|
| 0 | 4 | 5 | 6 |
| 1 | 3 | 4 | 5 |
| 2 | 2 | 3 | 4 |
| 3 | 1 | 2 | 3 |
so the solver may return Slack(A) == [1,6] while you can see 6 is only valid for Cumul(A) == 0 and Cumul(B) == 11.
All time are the arrival time at Node X.
So in 14 Time(5,8) Slack(0,3):
Time(5, 8) means vehicle must reach node 14 between 5 and 8 to be on time for the next visit (node 16 here) i.e. this represent the CumulVar value (here a range) at node 14.Slack(0,3) is the slackVar value (here a range) at node 14.seems related to https://github.com/google/or-tools/issues/765#issuecomment-436557147
Thanks for your valuable answer.
And, I hope to see this clarifications available in the website.
Going from the same VRPTW example as the original post, I added the time_dimension.TransitVar and time_dimension.SlackVar to the assignment (via routing.AddToAssignment) to print them in the output in order to fully understand how they are supposed to be interpreted.
I then print the Min, Max and Value for each of CumulVar, TransitVar, SlackVar in the following format:
var [assignment.Min(var),assignment.Max(var)] assignment.Value(var)
The results seem to not always respect the following equation taken from @Mizux originals answer:
time_dimension.Cumul(B) == time_dimension.Cumul(A) + time_dimension.transit(A,B) + slackVar(A)
see for example the route of vehicle 1:
0: Cumul[0,0] 0 -> 7: Cumul[2,4] 2; Transit[4,9] 4; Slack[0,5] 0 -> 1: Cumul[7,11] 7; Transit[2,6] 2; Slack[0,4] 0 -> 4: Cumul[10,13] 10; Transit[3,6] 3; Slack[2,5] 2 -> 3: Cumul[16,16] 16; Transit[8,8] 8; Slack[0,0] 0 -> 0 (Time[24,24])
From my understanding, the assignment.Value() for the CumulVar of node 1 should be CumulVar(7)+TransitVar(7) = 2 + 4 = 6. However, CumulVar(1)=7
assignment.Value(time_dimension.CumulVar(i)) in this context?0: Cumul[0,0] 0 -> 9: Cumul[2,3] 2; Transit[4,6] 4; Slack[1,3] 1 -> 14: Cumul[7,8] 7; Transit[3,4] 3; Slack[1,2] 1 -> 16: Cumul[11,11] 11; Transit[7,7] 7; Slack[0,0] 0 -> 0 (Time[18,18])
Why does assignment.Value(time_dimension.TransitVar(9)) return 4 instead of 2? 2 should be the correct arc cost according to the time matrix given by @Muhammad-Altabba and according to the guide about dimensions:
Transit variables: The increase or decrease of the quantity at each step of a route. If i -> j is one step in a route, the transit variable is either the the i, j entry of the transit matrix (for a transit callback), or simply the callback value at location i (if the callback depends on just one location).
- Eventually I want to put a constraint on the _departure_ times of some nodes. Assuming there is no service time, would this be possible by simply saying:
routing.solver().Add(time_dimension.CumulVar(i) + time_dimension.SlackVar(i) >= min_departure_time)
Here is the full code I have been using:
from __future__ import print_function
from ortools.constraint_solver import routing_enums_pb2
from ortools.constraint_solver import pywrapcp
def create_data_model():
"""Stores the data for the problem."""
data = {}
data['time_matrix'] = [
[0, 6, 9, 8, 7, 3, 6, 2, 3, 2, 6, 6, 4, 4, 5, 9, 7],
[6, 0, 8, 3, 2, 6, 8, 4, 8, 8, 13, 7, 5, 8, 12, 10, 14],
[9, 8, 0, 11, 10, 6, 3, 9, 5, 8, 4, 15, 14, 13, 9, 18, 9],
[8, 3, 11, 0, 1, 7, 10, 6, 10, 10, 14, 6, 7, 9, 14, 6, 16],
[7, 2, 10, 1, 0, 6, 9, 4, 8, 9, 13, 4, 6, 8, 12, 8, 14],
[3, 6, 6, 7, 6, 0, 2, 3, 2, 2, 7, 9, 7, 7, 6, 12, 8],
[6, 8, 3, 10, 9, 2, 0, 6, 2, 5, 4, 12, 10, 10, 6, 15, 5],
[2, 4, 9, 6, 4, 3, 6, 0, 4, 4, 8, 5, 4, 3, 7, 8, 10],
[3, 8, 5, 10, 8, 2, 2, 4, 0, 3, 4, 9, 8, 7, 3, 13, 6],
[2, 8, 8, 10, 9, 2, 5, 4, 3, 0, 4, 6, 5, 4, 3, 9, 5],
[6, 13, 4, 14, 13, 7, 4, 8, 4, 4, 0, 10, 9, 8, 4, 13, 4],
[6, 7, 15, 6, 4, 9, 12, 5, 9, 6, 10, 0, 1, 3, 7, 3, 10],
[4, 5, 14, 7, 6, 7, 10, 4, 8, 5, 9, 1, 0, 2, 6, 4, 8],
[4, 8, 13, 9, 8, 7, 10, 3, 7, 4, 8, 3, 2, 0, 4, 5, 6],
[5, 12, 9, 14, 12, 6, 6, 7, 3, 3, 4, 7, 6, 4, 0, 9, 2],
[9, 10, 18, 6, 8, 12, 15, 8, 13, 9, 13, 3, 4, 5, 9, 0, 9],
[7, 14, 9, 16, 14, 8, 5, 10, 6, 5, 4, 10, 8, 6, 2, 9, 0],
]
data['time_windows'] = [
(0, 5), # depot
(7, 12), # 1
(10, 15), # 2
(16, 18), # 3
(10, 13), # 4
(0, 5), # 5
(5, 10), # 6
(0, 4), # 7
(5, 10), # 8
(0, 3), # 9
(10, 16), # 10
(10, 15), # 11
(0, 5), # 12
(5, 10), # 13
(7, 8), # 14
(10, 15), # 15
(11, 15), # 16
]
data['num_vehicles'] = 4
data['depot'] = 0
return data
def print_solution(data, manager, routing, assignment):
"""Prints assignment on console."""
time_dimension: pywrapcp.RoutingDimension = routing.GetDimensionOrDie('Time')
total_time = 0
for vehicle_id in range(data['num_vehicles']):
index = routing.Start(vehicle_id)
plan_output = 'Route for vehicle {}:\n'.format(vehicle_id)
while not routing.IsEnd(index):
plan_output += f'{manager.IndexToNode(index)}: '
cumul_var = time_dimension.CumulVar(index)
plan_output +=f'Cumul[{assignment.Min(cumul_var)},{assignment.Max(cumul_var)}] {assignment.Value(cumul_var)}'
if not routing.IsStart(index):
trans_var = time_dimension.TransitVar(index)
plan_output +=f'; Transit[{assignment.Min(trans_var)},{assignment.Max(trans_var)}] {assignment.Value(trans_var)}'
slack_var = time_dimension.SlackVar(index)
plan_output +=f'; Slack[{assignment.Min(slack_var)},{assignment.Max(slack_var)}] {assignment.Value(slack_var)}'
plan_output+= ' -> '
index = assignment.Value(routing.NextVar(index))
cumul_var = time_dimension.CumulVar(index)
plan_output += '{0}\t(Time[{1},{2}])\n'.format(manager.IndexToNode(index),
assignment.Min(cumul_var),
assignment.Max(cumul_var))
plan_output += 'Time of the route: {}min\n'.format(
assignment.Min(cumul_var))
print(plan_output)
total_time += assignment.Min(cumul_var)
print('Total time of all routes: {}min'.format(total_time))
def main():
"""Solve the VRP with time windows."""
data = create_data_model()
manager = pywrapcp.RoutingIndexManager(len(data['time_matrix']),
data['num_vehicles'], data['depot'])
routing = pywrapcp.RoutingModel(manager)
def time_callback(from_index, to_index):
"""Returns the travel time between the two nodes."""
# Convert from routing variable Index to time matrix NodeIndex.
from_node = manager.IndexToNode(from_index)
to_node = manager.IndexToNode(to_index)
return data['time_matrix'][from_node][to_node]
transit_callback_index = routing.RegisterTransitCallback(time_callback)
routing.SetArcCostEvaluatorOfAllVehicles(transit_callback_index)
time = 'Time'
routing.AddDimension(
transit_callback_index,
30, # allow waiting time
30, # maximum time per vehicle
False, # Don't force start cumul to zero.
time)
time_dimension: pywrapcp.RoutingDimension = routing.GetDimensionOrDie(time)
# Add time window constraints for each location except depot.
for location_idx, time_window in enumerate(data['time_windows']):
if location_idx == 0:
continue
index = manager.NodeToIndex(location_idx)
time_dimension.CumulVar(index).SetRange(time_window[0], time_window[1])
# Add time window constraints for each vehicle start node.
for vehicle_id in range(data['num_vehicles']):
index = routing.Start(vehicle_id)
time_dimension.CumulVar(index).SetRange(data['time_windows'][0][0],
data['time_windows'][0][1])
# Add trans and slack var to assignment for every node but the end nodes
for location_index in range(routing.nodes()):
index = manager.NodeToIndex(location_index)
if routing.IsEnd(index) or routing.IsStart(index):
continue
routing.AddToAssignment(time_dimension.TransitVar(index))
routing.AddToAssignment(time_dimension.SlackVar(index))
for i in range(data['num_vehicles']):
routing.AddVariableMinimizedByFinalizer(time_dimension.CumulVar(routing.Start(i)))
routing.AddVariableMinimizedByFinalizer(time_dimension.CumulVar(routing.End(i)))
search_parameters = pywrapcp.DefaultRoutingSearchParameters()
search_parameters.first_solution_strategy = routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC
assignment = routing.SolveWithParameters(search_parameters)
if assignment:
print_solution(data, manager, routing, assignment)
if __name__ == '__main__':
main()
ortools version 7.4.7247
python 3.7.4
Any help is highly appreciated! Many thanks!
I'm trying to set data['num_vehicles']=1, for lesser node but I'm not getting any output.
I'm trying to set data['num_vehicles']=1, for lesser node but I'm not getting any output.
me too
@Pranjulcr7 and @jonassonjp this example as been tailored to perfectly work with 4 vehicles, if your reduce the number of vehicle to 1, the problem become infeasible...
Potential solution:
Most helpful comment
WARNING: DRAFT (extracted from an email)
First you must read https://developers.google.com/optimization/routing/vrp#dimension_details to understand what is a
Dimensionand theCumulVar.Q: I am particularly not sure about the methods:
A: Since we have create a "travel time" dimension, each time you travel from A to B, you add the transit value (ed transit is the callback you pass when building the time dimension) to a CumulVar.
e.g.
transit(A, B)return2andtransit(B, C)return3.supposing you have the route
A -> B -> C(with start node being nodeA).then you'll have:
time_dimension.Cumul(A) == 0# if you set_cumul_var_to_zero in dimension constructortime_dimension.Cumul(B) == time_dimension.Cumul(A) + time_dimension.transit(A,B) == 0 + 2 == 2time_dimension.Cumul(C) == time_dimension.Cumul(B) + time_dimension.transit(B, C) == 2 + 3 == 5.Supposing you have a time windows at location X
[time_0, time_1]which mean we want the value ofCumul(X)to be in range[time_0, time_1]->
Cumul(X).SetRange(time_0, time_1)and
Q: SlackVar you must add it to the solution using
A: SlackVar is a waiting time, actually the relation between two consecutive location is
time_dimension.Cumul(B) == time_dimension.Cumul(A) + time_dimension.transit(A,B) + slackVar(A)Example
Suppose:
9at nodeB(i.e.Cumul(B).SetRange(9,9)),transit(A, B) = 5andAat time0(i.e.Cumul(A) == 0)Then you'll have:
Cumul(B) == 9# TW akaSetRange()constraintand you know
Cumul(B) == Cumul(A) + transit(A,B) + slackVar(A) => 9 == 0 + 5 + slackVar(A) => 9 - 5 == 4 == slackVar(A)i.e. the equality is respected if we can wait 4 unit at
Abefore traveling toB.That the goal of SlackVar and you can control the max allowed slackVar i.e. the max waiting time allowed at each node...
Now suppose,
Cumul(A)is not a value but a range of integral value (e.g.[0,3]), as well asCumul(B)(e.g.[9,11]).So for each value in
Cumul(A)and each value ofCumul(B)you have a range of SlackVar values. Then you make the union of all SlackVar values and you have the range returned by the solver.So here the min SlackVar value according to Value of
Cumul(A)andCumul(B).|
A \ B| 9 | 10 | 11 ||----------|----|----|----|
| 0 | 4 | 5 | 6 |
| 1 | 3 | 4 | 5 |
| 2 | 2 | 3 | 4 |
| 3 | 1 | 2 | 3 |
so the solver may return
Slack(A) == [1,6]while you can see6is only valid forCumul(A) == 0andCumul(B) == 11.All time are the arrival time at Node X.
So in
14 Time(5,8) Slack(0,3):Time(5, 8)means vehicle must reach node14between 5 and 8 to be on time for the next visit (node16here) i.e. this represent the CumulVar value (here a range) at node14.Slack(0,3)is the slackVar value (here a range) at node14.seems related to https://github.com/google/or-tools/issues/765#issuecomment-436557147