I have a working Vehicle Routing Problem solution implemented using Google's OR Tools python library. I have a time matrix of 9 locations, and time windows for each location. All values are in units of seconds.
(For example, the first time window is from 28800 to 28800. 28800 seconds is equivalent to 8:00am. I want this location, the depot, to be visited at exactly 8:00am)
I am intentionally solving this with just one vehicle (essentially solving a Traveling Salesperson Problem). I believe that I have added my dimension correctly, but I could certainly have made a mistake with that - my intention is to allow the vehicle to wait at any location for as long as it would like, as along as it allows it to solve the vehicle routing problem. I set the upper bound maximum value as 86400 because there are 86400 seconds in a day, and I figure that would be a sufficiently high number given this data.
Source
from ortools.constraint_solver import pywrapcp
from ortools.constraint_solver import routing_enums_pb2
Matrix = [
[0,557,763,1156,813,618,822,700,112], # Depot
[523,0,598,1107,934,607,658,535,589], # 1 - Location
[631,480,0,968,960,570,451,135,582], # 2 - Location
[1343,1247,1367,0,1270,1289,809,1193,1253], # 3 - Location
[746,1000,1135,1283,0,1003,1186,1071,776], # 4 - Location
[685,627,810,1227,990,0,712,709,550], # 5 - Location
[869,718,558,732,1105,650,0,384,821], # 6 - Location
[679,528,202,878,1008,618,412,0,630], # 7 - Location
[149,626,762,1124,696,532,821,698,0] # 8 - Location
]
Windows = [
[ 28800, 28800 ], # Depot
[ 43200, 43200 ], # 1 - Location
[ 50400, 50400 ], # 2 - Location
[ 21600, 79200 ], # 3 - Location
[ 21600, 79200 ], # 4 - Location
[ 21600, 79200 ], # 5 - Location
[ 21600, 79200 ], # 6 - Location
[ 21600, 79200 ], # 7 - Location
[ 21600, 79200 ] # 8 - Location
]
# Create the routing index manager.
manager = pywrapcp.RoutingIndexManager(len(Matrix), 1, 0)
# Create Routing Model.
routing = pywrapcp.RoutingModel(manager)
# Create and register a transit callback.
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 Matrix[from_node][to_node]
transit_callback_index = routing.RegisterTransitCallback(time_callback)
# Define cost of each arc.
routing.SetArcCostEvaluatorOfAllVehicles(transit_callback_index)
# Add Time Windows constraint.
routing.AddDimension(
transit_callback_index,
86400, # An upper bound for slack (the wait times at the locations).
86400, # 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')
# Add time window constraints for each location except depot.
for location_idx, time_window in enumerate(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.
index = routing.Start(0)
time_dimension.CumulVar(index).SetRange(Windows[0][0],Windows[0][1])
# Instantiate route start and end times to produce feasible times.
routing.AddVariableMinimizedByFinalizer(time_dimension.CumulVar(routing.Start(0)))
routing.AddVariableMinimizedByFinalizer(time_dimension.CumulVar(routing.End(0)))
# Setting first solution heuristic.
search_parameters = pywrapcp.DefaultRoutingSearchParameters()
search_parameters.first_solution_strategy = (routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC)
# Setting local search metaheuristics:
search_parameters.local_search_metaheuristic = (routing_enums_pb2.LocalSearchMetaheuristic.GUIDED_LOCAL_SEARCH)
search_parameters.time_limit.seconds = 5
search_parameters.log_search = False
# Solve the problem.
solution = routing.SolveWithParameters(search_parameters)
# Return the solution.
time = 0
index = routing.Start(0)
print("Locations:")
while not routing.IsEnd(index):
time = time_dimension.CumulVar(index)
print("{0} ({1}, {2})".format(manager.IndexToNode(index),solution.Min(time),solution.Max(time)))
index = solution.Value(routing.NextVar(index))
print("{0} ({1}, {2})".format(manager.IndexToNode(index),solution.Min(time),solution.Max(time)))
Output
Locations:
0 (28800, 28800)
8 (28912, 42041)
5 (29444, 42573)
1 (43200, 43200)
2 (50400, 50400)
7 (50535, 50535)
6 (50947, 50947)
3 (51679, 51679)
4 (52949, 52949)
0 (52949, 52949)
My question is in regards to the output that the solution has calculated for me. I am confused about the time windows for the second and third locations in the solution. I expected all of the time windows to look like the rest of the result. What do the solution.Min()
and solution.Max()
values mean in the scope of this problem when I'm processing my solution? Are there any blatant errors in my usage of OR Tools?