im working with the google-OR tools, trying to set up a VRP with a time window and demands constraint.
for this to work, I want a vehicle to go to location A, stay there for a specified amount of time, and then move on to the next location. I have a pretty bad implementation currently, so I'm wondering if someone could help me.
printing the solution sometimes yield in a time that is a very long time apart, more then the maximum waiting time like; 30 Time(1682,8093) while the max waiting time is only 3600, or results in a timewindow like 28 Time(23118,23118) where the specified waiting time of 900 is not calculated in here.
this is the code I have;
Vehicles Routing Problem (VRP) with Time Windows.
from __future__ import print_function
from ortools.constraint_solver import routing_enums_pb2
from ortools.constraint_solver import pywrapcp
import pandas as pd
import numpy as np
import pickle
# df = pd.read_excel("spaarnwoudecoordinates.xlsx")
# houses_cleaning_times = df['Time'].tolist()
# houses_to_clean = df['Index'].tolist()
# distance_matrix = pickle.load(open("distancematrix_spaarnwoude_foot.p", "rb")) # Index 0 is the
depot, the starting point for all cleaners
# distance_matrix = [[round(j) for j in i] for i in distance_matrix] # Distance matrix has to be
integers
penalty = 10000 # Penalty should probably equal maxtime because than it mirrors the cost of an extra
cleaner
def create_data_model(houses_to_clean, number_of_cleaners, maximum_time_capacity,
maximum_waiting_time ,distance_matrix,
houses_cleaning_times, time_windows):
"""Stores the data for the problem."""
if number_of_cleaners == 0:
number_of_cleaners = len(distance_matrix[0])
data = {}
# Filter the distance matrix on houses to be cleaned
rowfilter = np.take(distance_matrix, houses_to_clean, axis=0)
columnfilter = np.take(rowfilter, houses_to_clean, axis=1)
data['time_matrix'] = columnfilter # Multiply columnfilter to create walking times. *3 seems
reasonable
data['num_vehicles'] = number_of_cleaners
data['depot'] = 0
data['demands'] = [round(houses_cleaning_times[i] * 60) for i in houses_to_clean]
data['time_windows'] = time_windows
data['maximum_time_capacity'] = maximum_time_capacity
data['maximum_waiting_time'] = maximum_waiting_time
return data
def findSolution(data, print):
"""Solve the VRP with time windows."""
# Instantiate the data problem.
data = data
# Create the routing index manager.
manager = pywrapcp.RoutingIndexManager(len(data['time_matrix']), data['num_vehicles'], data['depot'])
# 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 data['time_matrix'][from_node][to_node]
traveltime = data['time_matrix'][from_node][to_node]
cleaningtime = data['demands'][to_node]
return traveltime + cleaningtime
transit_callback_index = routing.RegisterTransitCallback(time_callback)
# Define cost of each arc.
routing.SetArcCostEvaluatorOfAllVehicles(transit_callback_index)
# Add Time Windows constraint.
time = 'Time'
routing.AddDimension(
transit_callback_index,
data['maximum_waiting_time'], # allow waiting time
data['maximum_time_capacity'],
# maximum time per vehicle ### THIS ONE SEEMS TO OVERRULE SetSpanUpperBound ---------WORKING DAY
OF 7 HOURS IN SECONDS
True, # True: Do force start cumul to zero.
time)
time_dimension = 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(int(time_window[0]), int(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])
# Instantiate route start and end times to produce feasible times.
for i in range(data['num_vehicles']):
routing.AddVariableMinimizedByFinalizer(time_dimension.CumulVar(routing.Start(i)))
routing.AddVariableMinimizedByFinalizer(time_dimension.CumulVar(routing.End(i)))
# Set time limit for finding a solution
# search_parameters = pywrapcp.DefaultRoutingSearchParameters()
# search_parameters.time_limit.seconds = 10
# Setting first solution heuristic.
search_parameters = pywrapcp.DefaultRoutingSearchParameters()
search_parameters.first_solution_strategy = routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC
# PATH_CHEAPEST_ARC PATH_MOST_CONSTRAINED_ARC CHRISTOFIDES
# Set penalty for vehicle use to optimize amount of vehicles
routing.SetFixedCostOfAllVehicles(penalty)
# Solve the problem.
solution = routing.SolveWithParameters(search_parameters)
# Print solution on console.
if solution:
if print:
print_solution(data, manager, routing, solution)
calculateScore(data, manager, routing, solution, data['maximum_time_capacity'])
return (data, manager, routing, solution)
else:
print("No solution found!")
return None
def getAmountOfCleaners(data, manager, routing, solution):
# Obtain the amount of cleaners in the solution. This can not easily be done by routing.vehicles()
because this
# method does not include vehicles that go directly from a starting point to their endpoint
# in Google OR Tools' definition a vehicle like this is 'unused' see my GitHub issue.
# Therefore we determine used cleaners by load > 0.
cleaners_needed = 0
for cleaner_id in range(data['num_vehicles']):
load = 0
index = routing.Start(cleaner_id)
while not routing.IsEnd(index):
node_index = manager.IndexToNode(index)
load += data['demands'][node_index]
index = solution.Value(routing.NextVar(index))
if load > 0:
cleaners_needed += 1
return cleaners_needed
def print_solution(data, manager, routing, solution):
"""Prints solution on console."""
time_dimension = 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):
time_var = time_dimension.CumulVar(index)
plan_output += '{0} Time({1},{2}) -> '.format(
manager.IndexToNode(index), solution.Min(time_var),
solution.Max(time_var))
index = solution.Value(routing.NextVar(index))
time_var = time_dimension.CumulVar(index)
plan_output += '{0} Time({1},{2})\n'.format(manager.IndexToNode(index),
solution.Min(time_var),
solution.Max(time_var))
plan_output += 'Time of the route: {} sec\n'.format(
solution.Min(time_var))
print(plan_output)
total_time += solution.Min(time_var)
print('Total time of all routes: {} sec'.format(total_time))
def calculateScore(data, manager, routing, solution, maximum_time_capacity):
time_dimension = routing.GetDimensionOrDie('Time')
total_score = 0
for cleaner_id in range(data['num_vehicles']):
index = routing.Start(cleaner_id)
while not routing.IsEnd(index):
time_var = time_dimension.CumulVar(index)
node_index = manager.IndexToNode(index)
if node_index != 0:
if data['time_windows'][node_index][0] <= solution.Min(time_var) and
solution.Max(time_var) <= data['time_windows'][node_index][1]:
total_score += 1
elif data['time_windows'][node_index][0] >= solution.Min(time_var):
total_score += 0.5
else:
total_score -= 1
index = solution.Value(routing.NextVar(index))
print('total score or the route was: ' + str(total_score))
def get_routes(solution, routing, manager):
"""Get vehicle routes from a solution and store them in an array."""
# Get vehicle routes and store them in a two dimensional array whose
# i,j entry is the jth location visited by vehicle i along its route.
routes = []
for route_nbr in range(routing.vehicles()):
index = routing.Start(route_nbr)
route = [manager.IndexToNode(index)]
while not routing.IsEnd(index):
index = solution.Value(routing.NextVar(index))
route.append(manager.IndexToNode(index))
routes.append(route)
return routes
def get_routes_cleaner(solution, routing, manager, cleaner_id):
data = get_routes(solution, routing, manager)
return data[cleaner_id]
with the following input data:
np.random.seed(0) #use seed to have the same random number
every time
A = np.random.randint(low=1, high=10, size=(31, 31))
np.fill_diagonal(A, 0)
distance_matrix = A
houses_to_clean = [i for i in range(31)] # number of the houses that have to be cleaned
houses_cleaning_times2 = [0] + list(np.random.randint(low=14, high=16, size=30)) # cleaning between
14 and 16min
a = [0] + list(np.random.randint(low=27, high=28, size=30) * 15 * 60)
b = [0] + list(np.random.randint(low=0, high=14, size=30) * 15 * 60)
time_windows = list(zip(b, a)) # array of tuples for the time window
data = create_data_model(houses_to_clean, 2, 28800, 3600, distance_matrix, houses_cleaning_times2,
time_windows)
(data, manager, routing, solution) = findSolution(data, True) #set to true to print