Google OR-Tools TSP spanning multiple days with start/stop times
You may have something like that:
python plop.py
Objective: 93780
droped: []
Route for vehicle 0:
0 [21600;21600] -> 38 [21902;57722] -> 33 [23897;59717] -> 34 [25935;61755] -> 28 [28562;64382] -> 41 [31374;67194] -> 39 [33520;69340] -> 40 [35840;71660] -> 36 [38315;74135] -> 37 [40745;76565] -> 5 [44115;79935] -> 25 [46810;82630] -> 20 [49163;84983] -> 21 [51370;790+1day] -> 35 [53471;2891+1day] -> 26 [55707;5127+1day] -> 18 [57768;7188+1day] -> 19 [60001;9421+1day] -> 17 [62278;11698+1day] -> 6 [64588;14008+1day] -> 4 [67569;16989+1day] -> 2 [70020;19440+1day] -> 3 [72377;21797+1day] -> 7 [74828;24248+1day] -> 24 [77187;26607+1day] -> 8 [79509;28929+1day] -> 9 [82281;31701+1day] -> 30 [84660;34080+1day] -> 31 [778+1day;36598+1day] -> 32 [3163+1day;38983+1day] -> 27 [5213+1day;41033+1day] -> 22 [7579+1day;43399+1day] -> 29 [9715+1day;45535+1day] -> 23 [11863+1day;47683+1day] -> 13 [14055+1day;49875+1day] -> 11 [16224+1day;52044+1day] -> 12 [18239+1day;54059+1day] -> 10 [20332+1day;56152+1day] -> 15 [22559+1day;58379+1day] -> 14 [24818+1day;60638+1day] -> 16 [26901+1day;62721+1day] -> 1 [64800+1day;64800+1day]
%diff -u plop.py plop_final.py
--- plop.py 2020-12-01 17:48:15.187255138 +0100
+++ plop_final.py 2020-12-01 17:47:41.033692899 +0100
@@ -7,6 +7,7 @@
Penalties = [576460752303423487, 576460752303423487, 100000, 100000, 100000, 100000, 100000, 100000, 100000, 100000, 100000, 100000, 100000, 100000, 100000, 100000, 100000, 100000, 100000, 100000, 100000, 100000, 100000, 100000, 100000, 100000, 100000, 100000, 100000, 100000, 100000, 100000, 100000, 100000, 100000, 100000, 100000, 100000, 100000, 100000, 100000, 100000]
Slack_Max = 86400
Capacity = 86400
+OneDay = 86400
# The inputs to RoutingIndexManager are:
# 1. The number of rows of the time matrix, which is the number of locations (including the depot).
@@ -36,7 +37,7 @@
routing.AddDimension(
transit_callback_index,
Slack_Max, # An upper bound for slack (the wait times at the locations).
- Capacity, # An upper bound for the total time over each vehicle's route.
+ 2*Capacity, # An upper bound for the total time over each vehicle's route.
False, # Determine whether the cumulative variable is set to zero at the start of the vehicle's route.
'Time')
time_dimension = routing.GetDimensionOrDie('Time')
@@ -50,13 +51,14 @@
if location_idx == 0 or location_idx == 1:
continue
index = manager.NodeToIndex(location_idx)
- time_dimension.CumulVar(index).SetRange(time_window[0], time_window[1])
+ time_dimension.CumulVar(index).SetRange(time_window[0], time_window[1]+OneDay)
+ time_dimension.CumulVar(index).RemoveInterval(time_window[1], time_window[0]+OneDay)
# Add time window constraints for each vehicle start node.
index = routing.Start(0)
time_dimension.CumulVar(index).SetRange(Windows[0][0],Windows[0][1])
index = routing.End(0)
-time_dimension.CumulVar(index).SetRange(Windows[1][0],Windows[1][1])
+time_dimension.CumulVar(index).SetRange(Windows[1][0]+OneDay,Windows[1][1]+OneDay)
# Instantiate route start and end times to produce feasible times.
routing.AddVariableMinimizedByFinalizer(time_dimension.CumulVar(routing.Start(0)))
@@ -73,28 +75,24 @@
# Solve the problem.
solution = routing.SolveWithParameters(search_parameters)
-
-# Print the results
-result = {
- 'Dropped': [],
- 'Scheduled': []
-}
+print(f"Objective: {solution.ObjectiveValue()}")
# Return the dropped locations
+dropped = []
for node in range(routing.Size()):
if routing.IsStart(node) or routing.IsEnd(node):
continue
if solution.Value(routing.NextVar(node)) == node:
- result['Dropped'].append(manager.IndexToNode(node))
+ dropped.append(manager.IndexToNode(node))
+print(f"droped: {dropped}")
# Return the scheduled locations
-time = 0
index = routing.Start(0)
+plan_output = 'Route for vehicle 0:\n'
while not routing.IsEnd(index):
time = time_dimension.CumulVar(index)
- result['Scheduled'].append([manager.IndexToNode(index), solution.Min(time),solution.Max(time)])
+ tw_min = solution.Min(time)
+ if tw_min > OneDay:
+ tw_min = f"{tw_min%OneDay}+1day"
+ tw_max = solution.Max(time)
+ if tw_max > OneDay:
+ tw_max = f"{tw_max%OneDay}+1day"
+
+ plan_output += f'{manager.IndexToNode(index)} [{tw_min};{tw_max}] -> '
index = solution.Value(routing.NextVar(index))
time = time_dimension.CumulVar(index)
-result['Scheduled'].append([manager.IndexToNode(index), solution.Min(time),solution.Max(time)])
-
-print(result)
+tw_min = solution.Min(time)
+tw_max = solution.Max(time)
+if tw_min > OneDay:
+ tw_min = f"{tw_min%OneDay}+1day"
+tw_max = solution.Max(time)
+if tw_max > OneDay:
+ tw_max = f"{tw_max%OneDay}+1day"
+plan_output += f'{manager.IndexToNode(index)} [{tw_min};{tw_max}]'
+print(plan_output)
My changes:
Add const
OneDay = 86400
Change Vehicle horizon to two day i.e.
Capacity+OneDay
You can remove a range of value using
RemoveInterval()
method.
ref: https://github.com/google/or-tools/blob/84c35244c9bdd635609af703bbf3053c16c487c0/ortools/constraint_solver/constraint_solver.h#L3980-L3988
So the idea is SetRange to[FirstTW start; LastTW end]
then remove[FirstTW end; LastTW start]
Then I rewrite the output part (cleaner to me by removing your "unused struct result for this snippet")
ps: If you have question, please join our or-tools discord (link in the README on github) ;)
Step 2
Currently:
- your TW for location is
[0;86400]
while your vehicle is working from21600
to64800
. - Your end TW is
[64800, 64800]
instead I would use[21600;64800]
i.e. finish ASAP instead of dispatching visit until 6pm ?
So let's hack your TW data as follow:
Windows = [[21600, 21600], [21600, 64800], [21600, 86400], [21600, 86400], [21600, 86400], [21600, 86400], [21600, 86400], [21600, 86400], [21600, 86400], [21600, 86400], [21600, 86400], [21600, 86400], [21600, 86400], [21600, 86400], [21600, 86400], [21600, 86400], [21600, 86400], [21600, 86400], [21600, 86400], [21600, 86400], [21600, 86400], [21600, 86400], [21600, 86400], [21600, 86400], [21600, 86400], [21600, 86400], [21600, 86400], [21600, 86400], [21600, 86400], [21600, 86400], [21600, 86400], [21600, 86400], [21600, 86400], [21600, 86400], [21600, 86400], [21600, 86400], [21600, 86400], [21600, 86400], [21600, 86400], [21600, 86400], [21600, 86400], [21600, 86400]]
Then you'll get the following result:
python plop_final.py
Objective: 93780
droped: []
Route for vehicle 0:
0 [21600;21600] -> 38 [21902;23641] -> 33 [23897;25636] -> 34 [25935;27674] -> 28 [28562;30301] -> 41 [31374;33113] -> 39 [33520;35259] -> 40 [35840;37579] -> 36 [38315;40054] -> 37 [40745;42484] -> 5 [44115;45854] -> 25 [46810;48549] -> 20 [49163;50902] -> 21 [51370;53109] -> 35 [53471;55210] -> 26 [55707;57446] -> 18 [57768;59507] -> 19 [60001;61740] -> 17 [62278;64017] -> 6 [64588;66327] -> 4 [67569;69308] -> 2 [70020;71759] -> 3 [72377;74116] -> 7 [74828;76567] -> 24 [77187;78926] -> 8 [79509;81248] -> 9 [82281;84020] -> 30 [84660;86399] -> 31 [21601+1day;21601+1day] -> 32 [23986+1day;23986+1day] -> 27 [26036+1day;26036+1day] -> 22 [28402+1day;28402+1day] -> 29 [30538+1day;30538+1day] -> 23 [32686+1day;32686+1day] -> 13 [34878+1day;34878+1day] -> 11 [37047+1day;37047+1day] -> 12 [39062+1day;39062+1day] -> 10 [41155+1day;41155+1day] -> 15 [43382+1day;43382+1day] -> 14 [45641+1day;45641+1day] -> 16 [47724+1day;47724+1day] -> 1 [49803+1day;49803+1day]
note: You can find my fork here: https://github.com/Mizux/tsp_multiple_days