1

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
Igor D
  • 108
  • 2
  • 12
  • 1
    If you want a vehicle to stay at a location for a given period of time you need to add that value to the distance matrix. OR-Tools doesn't have a built-in method that allows you to add this value to node otherwise. – k88 Mar 26 '21 at 15:03

0 Answers0