OR-TOOLS: PCVRP with delivery patterns - python

Please help me on my periodic capacitated vehicle routing problem.
Find the same question here on google-groups.
What I am trying to model:
1 depot
multiple customers (let's assume 4)
multiple days (let's assume 3)
Multiple vehicle types with price per km and specific capacity
Every customer has: delivery frequency and demand per delivery
Every customer has to be assigned to a delivery pattern. Multiple delivery patterns are possible for each frequency. The pattern indicates if a delivery is possible on that day or not. For a frequency of 1 and 3 working days: [[1,0,0],[0,1,0],[0,0,1]], a frequency of 2 [[1,1,0],[1,0,1],[1,1,0]], a frequency of 3: [1,1,1]these are the possible patterns.
The demand of each customer per delivery is the same, for every day a delivery is performed
So we have a set of customers that have to be delivered by one depo. Multiple Vehicle Types are available for the job. The day of service is flexible because only the frequency is fixed to a customer, but there are multiple possibilities to fulfill the demand.
What I did so far:
I copied every node by the number of working days.
I restrict the start and end to the depo nodes of each day.
I multiply the vehicles by the number of days.
Furthermore, I handle the days as different kinds of cargo. I do assign a capacity of zero to the vehicle when it should not be used on that day.
[[10, 15, 15, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 10, 15, 15, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 10, 15, 15]]
The first 3 vehicles are for day one, the second three are day two
and the last 3 are day 3.
For the demand matrix I use the same trick. In a case of 4 customers + 1 depot the demand matrix could look like this
[[0, 3, 0, 5, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 3, 4, 5, 2, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 2]]
The second entry in the first list (3) and the 7th entry in the second list (3) are for the same customer just on two different days. The nodes were copied.
This works good so far. But it's not what I want to do.
I do assign a demand for every customer and day by hand, but I want to assign the demand by a chosen delivery pattern for each customer.
The model could assign pattern [1,0,1] to one customer with a frequency of 2 and a pattern of [0,0,1] to another customer. This would result in the possibility to plan a tour on day 3 with both demands combined.
I need help to define a dimension that manages the assignment of patterns to customers which results in "flexible" demand matrix.
Thank you for your help.
Full Code:
"""Periodic Capacited Vehicles Routing Problem (PCVRP)."""
from ortools.constraint_solver import routing_enums_pb2
from ortools.constraint_solver import pywrapcp
import numpy as np
def dublicate_nodes(base_distance_matrix, num_days):
matrix = []
for i in range(num_days):
day_element = []
for node in range(len(base_distance_matrix)):
node_element = []
for day in range(num_days):
node_element.append(base_distance_matrix[node])
day_element.append(np.concatenate(node_element).tolist())
matrix.extend(day_element)
#print(matrix)
return matrix
def depo_nodes(num_days, num_verhicles, num_nodes):
depo_array = []
for day in range(num_days):
for veh in range(int(num_verhicles/num_days)):
depo_array.append(day*num_nodes)
#print(depo_array)
return depo_array
def veh_types_costs(cost_array, num_per_type, num_days):
cost_array_km = []
for day in range(num_days):
for i in range(len(cost_array)):
for num in range(num_per_type[i]):
cost_array_km.append(cost_array[i])
#print(cost_array_km)
return(cost_array_km)
def veh_capacity_matrix(num_vehicle_per_type, vehicle_capacity_type, num_days):
matrix= []
for index in range(num_days):
matrix_element = []
for day in range(num_days):
for i, num in enumerate(num_vehicle_per_type):
for times in range(num):
if day == index:
matrix_element.append(vehicle_capacity_type[i]);
else:
matrix_element.append(0)
matrix.append(matrix_element)
#print(matrix)
return matrix
def create_data_model():
data = {}
data["num_days"] = 3 #Anzahl Tage
### Definition der Fahrezugtypen
data["num_vehicle_per_type"] = [1, 2] #Anzahl Fahrzeuge pro Typ
data["vehicle_costs_type_per_km"] = [1, 0.01] #Kosten pro km pro Fahrzeugtyp
data["vehicle_costs_type_per_stop"] = [1, 1000] #Kosten pro Stop pro Fahrzeugtyp
data['price_per_km'] = veh_types_costs(data["vehicle_costs_type_per_km"], data["num_vehicle_per_type"], data["num_days"]) #Matrix für price_per_km je Fahrzeug ertsellen
data["price_per_stop"] = veh_types_costs(data["vehicle_costs_type_per_stop"], data["num_vehicle_per_type"], data["num_days"])
data["vehicle_capacity_type"] = [10, 15] # Kapaität pro Fahrzeugtyp
data['vehicle_capacities_matrix'] = veh_capacity_matrix(data["num_vehicle_per_type"], data["vehicle_capacity_type"], data["num_days"]) #Kapazitäten pro Fahrzeugs pro Tag
print('vehicle_capacities_matrix')
print(data['vehicle_capacities_matrix'])
data["num_vehicles_per_day"] = sum(data["num_vehicle_per_type"]) #Gesamtanzahl der Fahrzeuge pro Tag
data['num_vehicles'] = data["num_days"] * data["num_vehicles_per_day"]# Gesamtanzahl der Fahrzeuge in gesamten Zeitraum
###Distanzmatrix bestimmen
data["base_distance_matrix"] = [
[
0, 548, 776, 696, 582
],
[
548, 0, 684, 308, 194
],
[
776, 684, 0, 992, 878
],
[
696, 308, 992, 0, 114
],
[
582, 194, 878, 114, 0
]
] #Distanzmatrix mit allen Kunden einzeln
data['distance_matrix'] = dublicate_nodes(data["base_distance_matrix"], data["num_days"]) # Distanzmatrix mit mehrfachen Nodes pro Kunden, je Tag ein Node
###Start und Ende festlegen
data["num_nodes"] = len(data["base_distance_matrix"]) # Anzahl Kunden
data['starts'] = depo_nodes(data["num_days"], data['num_vehicles'], data["num_nodes"]) #Deponodes (Start) für die einzelnen Fahrzeuge (Tage)
data['ends'] = depo_nodes(data["num_days"], data['num_vehicles'], data["num_nodes"]) #Deponodes (Ende) für die einzelnen Fahrzeuge (Tage)
###Demand pro Kunde
data['demands_matrix'] = [[0, 3, 0, 5, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],[0, 0, 0, 0, 0, 0, 3, 4, 5, 2, 0, 0, 0, 0, 0],[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 2]] #Demandmatrix mit je einer list pro Tag
return data
def print_solution(data, manager, routing, solution):
"""Prints solution on console."""
total_distance = 0
total_load = 0
for vehicle_id in range(data['num_vehicles']):
if vehicle_id % data["num_vehicles_per_day"] == 0:
print(("------Tag {}------").format(int(vehicle_id/data["num_vehicles_per_day"])))
if routing.IsVehicleUsed(assignment= solution, vehicle=vehicle_id) == False:
continue
index = routing.Start(vehicle_id)
if vehicle_id >= data["num_vehicles_per_day"]:
plan_output = 'Route for vehicle {}:\n'.format(abs(vehicle_id-data["num_vehicles_per_day"]*int(vehicle_id/data["num_vehicles_per_day"])))
else:
plan_output = 'Route for vehicle {}:\n'.format(vehicle_id)
route_costs = 0
route_load = 0
while not routing.IsEnd(index):
node_index = manager.IndexToNode(index)
capacity_ID = int(vehicle_id/data["num_vehicles_per_day"])
route_load += data['demands_matrix'][capacity_ID][node_index]
plan_output += ' {0} Load({1}) -> '.format(node_index, route_load)
previous_index = index
index = solution.Value(routing.NextVar(index))
route_costs += routing.GetArcCostForVehicle(
previous_index, index, vehicle_id)
plan_output += ' {0} Load({1})\n'.format(manager.IndexToNode(index),
route_load)
plan_output += 'Costs of the route: {}€\n'.format(route_costs)
plan_output += 'Load of the route: {}\n'.format(route_load)
print(plan_output)
total_distance += route_costs
total_load += route_load
print('Total costs of all routes: {}€'.format(total_distance))
print('Total load of all routes: {}'.format(total_load))
def main():
"""Periodic CVRP problem."""
# Instantiate the data problem.
data = create_data_model()
# Create the routing index manager.
manager = pywrapcp.RoutingIndexManager(len(data['distance_matrix']), data['num_vehicles'], data['starts'],
data['ends'])
# Create Routing Model.
routing = pywrapcp.RoutingModel(manager)
### Kostenfunktion je Fahrzeug festlegen ###
def create_cost_callback(dist_matrix, km_costs, stop_costs):
# Create a callback to calculate distances between cities.
def distance_callback(from_index, to_index):
from_node = manager.IndexToNode(from_index)
to_node = manager.IndexToNode(to_index)
return int(dist_matrix[from_node][to_node]) * (km_costs) + (stop_costs)
return distance_callback
for i in range(data['num_vehicles']):
cost_callback = create_cost_callback(data['distance_matrix'], data["price_per_km"][i],
data["price_per_stop"][i]) # Callbackfunktion erstellen
cost_callback_index = routing.RegisterTransitCallback(cost_callback) # registrieren
routing.SetArcCostEvaluatorOfVehicle(cost_callback_index, i) # Vehicle zuordnen
#Define Capacities for Vehicles for every Day
def create_demand_callback(demand_matrix, demand_index):
# Create a callback to calculate capacity usage.
def demand_callback(from_index):
#Returns the demand of the node.
# Convert from routing variable Index to demands NodeIndex.
from_node = manager.IndexToNode(from_index)
return demand_matrix[demand_index][from_node]
return demand_callback
for i in range(data["num_days"]): #Jedes Fahrzeug hat pro Tag eine andere Kapazität
demand_callback = create_demand_callback(data['demands_matrix'], i)
demand_callback_index = routing.RegisterUnaryTransitCallback(demand_callback)
dimension_name = 'Capacity_day_{}'.format(i)
routing.AddDimensionWithVehicleCapacity(
demand_callback_index,
0, # null capacity slack
data['vehicle_capacities_matrix'][i], # vehicle maximum capacities
True, # start cumul to zero
dimension_name)
# Setting first solution heuristic.
search_parameters = pywrapcp.DefaultRoutingSearchParameters()
search_parameters.first_solution_strategy = (
routing_enums_pb2.FirstSolutionStrategy.AUTOMATIC)
search_parameters.local_search_metaheuristic = (
routing_enums_pb2.LocalSearchMetaheuristic.AUTOMATIC)
search_parameters.time_limit.FromSeconds(1)
# Solve the problem.
solution = routing.SolveWithParameters(search_parameters)
# Print solution on console.
if solution:
print_solution(data, manager, routing, solution)
return solution
if __name__ == '__main__':
solution_array = []
solution = main()

So I found a solution for my problem.
The solving time for problems greater than 40 customers could be better.
I would appreciate improvement suggestions.
"""Periodic Capacited Vehicles Routing Problem with delivery pattern(PCVRP)."""
from ortools.constraint_solver import routing_enums_pb2
from ortools.constraint_solver import pywrapcp
import numpy as np
import random
import math
import pandas as pd
def dublicate_nodes(base_distance_matrix, num_days):
matrix = []
for i in range(num_days):
day_element = []
for node in range(len(base_distance_matrix)):
node_element = []
for day in range(num_days):
node_element.append(base_distance_matrix[node])
day_element.append(np.concatenate(node_element).tolist())
matrix.extend(day_element)
#print(matrix)
return matrix
def depo_nodes(num_days, num_verhicles, num_nodes):
depo_array = []
for day in range(num_days):
for veh in range(int(num_verhicles/num_days)):
depo_array.append(day*num_nodes)
#print(depo_array)
return depo_array
def veh_types_costs(cost_array, num_per_type, num_days):
cost_array_km = []
for day in range(num_days):
for i in range(len(cost_array)):
for num in range(num_per_type[i]):
cost_array_km.append(cost_array[i])
#print(cost_array_km)
return(cost_array_km)
def veh_capacity_matrix(num_vehicle_per_type, vehicle_capacity_type, num_days):
matrix= []
for index in range(num_days):
matrix_element = []
for day in range(num_days):
for i, num in enumerate(num_vehicle_per_type):
for times in range(num):
if day == index:
matrix_element.append(vehicle_capacity_type[i]);
else:
matrix_element.append(0)
matrix.append(matrix_element)
#print(matrix)
return matrix
def dist_matrix(koordianten):
matrix =[]
for k_1 in koordianten:
matrix_element = []
for k_2 in koordianten:
matrix_element.append(np.linalg.norm(k_1-k_2))
matrix.append(matrix_element)
#print(matrix)
return matrix
def demand_matrix(demand_matrix_day,num_days):
matrix = []
for index in range(num_days):
matrix_element = []
for day in range(num_days):
if day == index:
matrix_element = matrix_element +demand_matrix_day
else:
matrix_element = matrix_element +(list(np.zeros(len(demand_matrix_day))))
matrix.append(matrix_element)
return matrix
def kunden_indizes(ID_list, num_days):
indizes = {}
for ID in ID_list:
indizes[ID] = []
for index, key in enumerate(indizes):
for i in range(len(ID_list)*num_days):
if (i % len(ID_list)) == index:
indizes[key].append(i)
#print(indizes)
return indizes
def create_data_model(node_num):
data = {}
data["num_days"] = 5 #Anzahl Tage
###Kundenset
num_IDs = node_num
base = [0]
data["IDs"] = list(range(0, num_IDs))
#data["IDs"] = [0, 1, 2, 3, 4]
print(data["IDs"])
data["demand"] = [random.randint(1,30) for i in range(0,num_IDs)]
#data["demand"] =[0, 16, 8, 1, 7]
data["demand"][0] = 0
print("demand", data["demand"])
data["frequenz"] = [random.randint(1,5) for i in range(0,num_IDs)]
#data["frequenz"] =[0, 3, 5, 2, 1]
data["frequenz"][0] = 0
print("freq ",data["frequenz"])
summe_dem =0
for index, demand in enumerate(data["demand"]):
summe_dem += data["demand"][index] * data["frequenz"][index]
print("DEMANDSUMME: ",summe_dem)
data["koordianten"] = np.random.rand(num_IDs, 2)*1000
#data["koordianten"] = np.array([(0, 0), (4, 0), (4, 4), (0, 4), (3, 3)])*100
data["koordianten"][0] = (0,0)
#print("koord ", data["koordianten"])
data["PAT"] = {
5:
[[1, 1, 1, 1, 1]],
4:
[[0, 1, 1, 1, 1],
[1, 0, 1, 1, 1],
[1, 1, 0, 1, 1],
[1, 1, 1, 0, 1],
[1, 1, 1, 1, 0]],
3: [[0, 1, 0, 1, 1],
[1, 0, 1, 0, 1],
[1, 1, 0, 1, 0],
[0, 1, 1, 0, 1],
[1, 0, 1, 1, 0],
],
2: [[1, 0, 1, 0, 0],
[0, 1, 0, 1, 0],
[0, 0, 1, 0, 1],
[1, 0, 0, 1, 0],
[0, 1, 0, 0, 1]],
1: [[1, 0, 0, 0, 0],
[0, 1, 0, 0, 0],
[0, 0, 1, 0, 0],
[0, 0, 0, 1, 0],
[0, 0, 0, 0, 1]],
}
### Definition der Fahrezugtypen
data["vehicle_costs_type_per_km"] = [1, 0.01] #Kosten pro km pro Fahrzeugtyp
data["vehicle_costs_type_per_stop"] = [1, 1000] #Kosten pro Stop pro Fahrzeugtyp
data["vehicle_capacity_type"] = [100, 200] # Kapaität pro Fahrzeugtyp
data["num_vehicle_per_type"] = [math.ceil(summe_dem /data["vehicle_capacity_type"][0]), math.ceil(summe_dem /data["vehicle_capacity_type"][1])] # Anzahl Fahrzeuge pro Typ
data['price_per_km'] = veh_types_costs(data["vehicle_costs_type_per_km"], data["num_vehicle_per_type"], data["num_days"]) #Matrix für price_per_km je Fahrzeug ertsellen
data["price_per_stop"] = veh_types_costs(data["vehicle_costs_type_per_stop"], data["num_vehicle_per_type"], data["num_days"])
data['vehicle_capacities_matrix'] = veh_capacity_matrix(data["num_vehicle_per_type"], data["vehicle_capacity_type"], data["num_days"]) #Kapazitäten pro Fahrzeugs pro Tag
#print('vehicle_capacities_matrix')
#print(data['vehicle_capacities_matrix'])
data["num_vehicles_per_day"] = sum(data["num_vehicle_per_type"]) #Gesamtanzahl der Fahrzeuge pro Tag
data['num_vehicles'] = data["num_days"] * data["num_vehicles_per_day"]# Gesamtanzahl der Fahrzeuge in gesamten Zeitraum
###Distanzmatrix bestimmen
data["base_distance_matrix"] = dist_matrix(data["koordianten"])
data['distance_matrix'] = dublicate_nodes(data["base_distance_matrix"], data["num_days"]) # Distanzmatrix mit mehrfachen Nodes pro Kunden, je Tag ein Node
###Start und Ende festlegen
data["num_nodes"] = len(data["base_distance_matrix"]) # Anzahl Kunden
data['starts'] = depo_nodes(data["num_days"], data['num_vehicles'], data["num_nodes"]) #Deponodes (Start) für die einzelnen Fahrzeuge (Tage)
data['ends'] = depo_nodes(data["num_days"], data['num_vehicles'], data["num_nodes"]) #Deponodes (Ende) für die einzelnen Fahrzeuge (Tage)
###Demand pro Kunde
data["kunden_indizes"] = kunden_indizes(data["IDs"], data["num_days"])
data["demands_matrix"] = demand_matrix(demand_matrix_day=data["demand"], num_days=data["num_days"])
#print(data["demands_matrix"])
return data
def print_solution(data, manager, routing, solution):
"""Prints solution on console."""
total_distance = 0
total_load = 0
for vehicle_id in range(data['num_vehicles']):
if vehicle_id % data["num_vehicles_per_day"] == 0:
print(("------Tag {}------").format(int(vehicle_id/data["num_vehicles_per_day"])))
if routing.IsVehicleUsed(assignment= solution, vehicle=vehicle_id) == False:
continue
index = routing.Start(vehicle_id)
if vehicle_id >= data["num_vehicles_per_day"]:
plan_output = 'Route for vehicle {}:\n'.format(abs(vehicle_id-data["num_vehicles_per_day"]*int(vehicle_id/data["num_vehicles_per_day"])))
else:
plan_output = 'Route for vehicle {}:\n'.format(vehicle_id)
route_costs = 0
route_load = 0
while not routing.IsEnd(index):
node_index = manager.IndexToNode(index)
capacity_ID = int(vehicle_id/data["num_vehicles_per_day"])
route_load += data['demands_matrix'][capacity_ID][node_index]
plan_output += ' {0} Load({1}) -> '.format(node_index, route_load)
previous_index = index
index = solution.Value(routing.NextVar(index))
route_costs += routing.GetArcCostForVehicle(
previous_index, index, vehicle_id)
plan_output += ' {0} Load({1})\n'.format(manager.IndexToNode(index),
route_load)
plan_output += 'Costs of the route: {}€\n'.format(route_costs)
plan_output += 'Load of the route: {}\n'.format(route_load)
print(plan_output)
total_distance += route_costs
total_load += route_load
print('Total costs of all routes: {}€'.format(total_distance))
print('Total load of all routes: {}'.format(total_load))
def main(node_num):
"""Periodic CVRP problem."""
# Instantiate the data problem.
data = create_data_model(node_num)
# Create the routing index manager.
manager = pywrapcp.RoutingIndexManager(len(data['distance_matrix']), data['num_vehicles'], data['starts'],
data['ends'])
# Create Routing Model.
routing = pywrapcp.RoutingModel(manager)
### Kostenfunktion je Fahrzeug festlegen ###
def create_cost_callback(dist_matrix, km_costs, stop_costs):
# Create a callback to calculate distances between cities.
def distance_callback(from_index, to_index):
from_node = manager.IndexToNode(from_index)
to_node = manager.IndexToNode(to_index)
return int(dist_matrix[from_node][to_node]) * (km_costs) + (stop_costs)
return distance_callback
for i in range(data['num_vehicles']):
cost_callback = create_cost_callback(data['distance_matrix'], data["price_per_km"][i],
data["price_per_stop"][i]) # Callbackfunktion erstellen
cost_callback_index = routing.RegisterTransitCallback(cost_callback) # registrieren
routing.SetArcCostEvaluatorOfVehicle(cost_callback_index, i) # Vehicle zuordnen
#Define Capacities for Vehicles for every Day
def create_demand_callback(demand_matrix, demand_index):
# Create a callback to calculate capacity usage.
def demand_callback(from_index):
#Returns the demand of the node.
# Convert from routing variable Index to demands NodeIndex.
from_node = manager.IndexToNode(from_index)
return demand_matrix[demand_index][from_node]
return demand_callback
for i in range(data["num_days"]): #Jedes Fahrzeug hat pro Tag eine andere Kapazität
demand_callback = create_demand_callback(data['demands_matrix'], i)
demand_callback_index = routing.RegisterUnaryTransitCallback(demand_callback)
dimension_name = 'Capacity_day_{}'.format(i)
routing.AddDimensionWithVehicleCapacity(
demand_callback_index,
0, # null capacity slack
data['vehicle_capacities_matrix'][i], # vehicle maximum capacities
True, # start cumul to zero
dimension_name)
#Drop visits that would exceed the frequency
for index, key in enumerate(data["kunden_indizes"]):
if index == 0:
continue
#routing.solver().Add(sum(routing.ActiveVar(manager.NodeToIndex(i)) for i in data["kunden_indizes"][key]) == data["frequenz"][index])
bool_array = []
for index, freq in enumerate(data["frequenz"]):
if index == 0:
continue
bool_array_part =[]
for index_pat, pat in enumerate(data["PAT"][freq]):
#print(index,freq,index_pat)
bool_name = str(index) +str(freq) + str(index_pat)
bool_array_part.append(routing.solver().BoolVar(bool_name))
bool_array.append(bool_array_part)
#print(bool_array)
for i in bool_array:
routing.solver().Add(sum(i) == 1)
node_array = []
for index, freq in enumerate(data["frequenz"]):
if index == 0:
continue
node_array_part = []
for index_pat, pat in enumerate(data["PAT"][freq]):
node_array_sub_part = []
for index_day, day_value in enumerate(pat):
if day_value == 1:
node_array_sub_part.append(data["kunden_indizes"][index][index_day])
#print(node_array_part)
node_array_part.append(node_array_sub_part)
node_array.append(node_array_part)
#print(node_array)
for index, bool in enumerate(bool_array):
for i in range(len(bool)):
#print(node_array[index][i])
#print(bool_array[index][i])
routing.solver().Add(sum(routing.ActiveVar(manager.NodeToIndex(i)) for i in node_array[index][i]) * bool_array[index][i] == bool_array[index][i] * len(node_array[index][i]))
#routing.solver().Add(routing.ActiveVar(manager.NodeToIndex(5)) == 1)
penalty = 0
for node in range(0, len(data['distance_matrix'])):
if node in data["kunden_indizes"][0]:
continue
else:
routing.AddDisjunction([manager.NodeToIndex(node)], penalty)
# Setting first solution heuristic.
search_parameters = pywrapcp.DefaultRoutingSearchParameters()
search_parameters.first_solution_strategy = (
routing_enums_pb2.FirstSolutionStrategy.AUTOMATIC)
search_parameters.local_search_metaheuristic = (
routing_enums_pb2.LocalSearchMetaheuristic.AUTOMATIC)
#search_parameters.time_limit.FromSeconds(300)
# Solve the problem.
solution = routing.SolveWithParameters(search_parameters)
# Print solution on console.
if solution:
print_solution(data, manager, routing, solution)
print()
print("Statistics")
print(' - wall time : %f s' % (int(routing.solver().WallTime())/1000))
solvingtime = int(routing.solver().WallTime())/1000
return solvingtime
if __name__ == '__main__':
node_num_array = [10,20,30,40,50,60,70,80,90,100]
solvingtime_array = []
num_array =[]
for i in node_num_array:
for j in range(0,4):
solvingtime = main(i)
solvingtime_array.append(solvingtime)
num_array.append(i)
print(i, "-->",solvingtime)
print(solvingtime_array)
print(num_array)
df_results = pd.DataFrame(data= {"num_nodes":num_array, "solvingtime":solvingtime_array})
#encoding="latin_1", sep=";")
print(df_results)

Related

OR-Tools CVRP_reload with Pickup-Delivery

I use or-tools to solve problems such as picking up products from EC warehouses.
What I want to do is very similar to the cvrp_reload sample.
https://github.com/google/or-tools/blob/stable/ortools/constraint_solver/samples/cvrp_reload.py
The difference from the sample is that instead of bringing the load back to the depot, the picked up load is placed at a specific delivery point.
I changed the source of cvrp_reload while looking at the Pickup & Delivery sample in the guide. (Assuming that Node.1 to 5 are the delivery points)
Add Pickup & Delivery point list to data
data['pickups_deliveries'] = [
[6, 1], [7, 1],
[8, 1], [9, 1],
[10, 2], [11, 2],
[12, 2], [13, 3],
[14, 3], [15, 3],
[16, 4], [17, 4],
[18, 4], [19, 5],
[20, 5], [21, 5]
]
Simplification of demands and time_windows in data
data['demands'] = \
[0, # depot
-_capacity, # unload depot_first
-_capacity, # unload depot_second
-_capacity, # unload depot_third
-_capacity, # unload depot_fourth
-_capacity, # unload depot_fifth
4, 4, # 1, 2 changed from original
4, 4, # 3, 4 changed from original
4, 4, # 5, 6 changed from original
4, 4, # 7, 8 changed from original
4, 4, # 9,10 changed from original
4, 4, # 11,12 changed from original
4, 4, # 13, 14 changed from original
4, 4] # 15, 16 changed from original
data['time_windows'] = \
[(0, 0), # depot
(0, 1500), # unload depot_first. changed from original
(0, 1500), # unload depot_second. changed from original
(0, 1500), # unload depot_third. changed from original
(0, 1500), # unload depot_fourth. changed from original
(0, 1500), # unload depot_fifth. changed from original
(0, 750), (0, 750), # 1, 2 changed from original
(0, 750), (0, 750), # 3, 4 changed from original
(0, 750), (0, 750), # 5, 6 changed from original
(0, 750), (0, 750), # 7, 8 changed from original
(0, 750), (0, 750), # 9, 10 changed from original
(750, 1500), (750, 1500), # 11, 12 changed from original
(750, 1500), (750, 1500), # 13, 14 changed from original
(750, 1500), (750, 1500)] # 15, 16 changed from original
Define transportation requests in add_distance_dimension function
for request in data['pickups_deliveries']:
pickup_index = manager.NodeToIndex(request[0])
delivery_index = manager.NodeToIndex(request[1])
routing.AddPickupAndDelivery(pickup_index, delivery_index)
routing.solver().Add(
routing.VehicleVar(pickup_index) == routing.VehicleVar(
delivery_index))
routing.solver().Add(
distance_dimension.CumulVar(pickup_index) <=
distance_dimension.CumulVar(delivery_index))
Running this code will drop all nodes. However, limiting the list of P&Ds to just a few gives a solution.
data['pickups_deliveries'] = [
[6, 1], [7, 1],
[8, 1], [9, 1],
[10, 2], [11, 2],
# [12, 2], [13, 3],
# [14, 3], [15, 3],
# [16, 4], [17, 4],
# [18, 4], [19, 5],
# [20, 5], [21, 5]
]
Please give me a hint where my method is wrong.
This is the entire code. Thanks for reading my question.
from functools import partial
from ortools.constraint_solver import pywrapcp
from ortools.constraint_solver import routing_enums_pb2
###########################
# Problem Data Definition #
###########################
def create_data_model():
"""Stores the data for the problem"""
data = {}
_capacity = 15
# Locations in block unit
_locations = [
(4, 4), # depot
(4, 4), # unload depot_first
(4, 4), # unload depot_second
(4, 4), # unload depot_third
(4, 4), # unload depot_fourth
(4, 4), # unload depot_fifth
(2, 0),
(8, 0), # locations to visit
(0, 1),
(1, 1),
(5, 2),
(7, 2),
(3, 3),
(6, 3),
(5, 5),
(8, 5),
(1, 6),
(2, 6),
(3, 7),
(6, 7),
(0, 8),
(7, 8)
]
# Compute locations in meters using the block dimension defined as follow
# Manhattan average block: 750ft x 264ft -> 228m x 80m
# here we use: 114m x 80m city block
data['locations'] = [(l[0] * 114, l[1] * 80) for l in _locations]
data['num_locations'] = len(data['locations'])
data['demands'] = \
[0, # depot
-_capacity, # unload depot_first
-_capacity, # unload depot_second
-_capacity, # unload depot_third
-_capacity, # unload depot_fourth
-_capacity, # unload depot_fifth
4, 4, # 1, 2 changed from original
4, 4, # 3, 4 changed from original
4, 4, # 5, 6 changed from original
4, 4, # 7, 8 changed from original
4, 4, # 9,10 changed from original
4, 4, # 11,12 changed from original
4, 4, # 13, 14 changed from original
4, 4] # 15, 16 changed from original
data['time_per_demand_unit'] = 5 # 5 minutes/unit
data['time_windows'] = \
[(0, 0), # depot
(0, 1500), # unload depot_first. changed from original
(0, 1500), # unload depot_second. changed from original
(0, 1500), # unload depot_third. changed from original
(0, 1500), # unload depot_fourth. changed from original
(0, 1500), # unload depot_fifth. changed from original
(0, 750), (0, 750), # 1, 2 changed from original
(0, 750), (0, 750), # 3, 4 changed from original
(0, 750), (0, 750), # 5, 6 changed from original
(0, 750), (0, 750), # 7, 8 changed from original
(0, 750), (0, 750), # 9, 10 changed from original
(750, 1500), (750, 1500), # 11, 12 changed from original
(750, 1500), (750, 1500), # 13, 14 changed from original
(750, 1500), (750, 1500)] # 15, 16 changed from original
#added p&d list.##############################
data['pickups_deliveries'] = [
[6, 1], [7, 1],
[8, 1], [9, 1],
[10, 2], [11, 2],
[12, 2], [13, 3],
[14, 3], [15, 3],
[16, 4], [17, 4],
[18, 4], [19, 5],
[20, 5], [21, 5]
]
##############################################
data['num_vehicles'] = 3
data['vehicle_capacity'] = _capacity
data['vehicle_max_distance'] = 10_000
data['vehicle_max_time'] = 1_500
data[
'vehicle_speed'] = 5 * 60 / 3.6 # Travel speed: 5km/h to convert in m/min
data['depot'] = 0
return data
#######################
# Problem Constraints #
#######################
def manhattan_distance(position_1, position_2):
"""Computes the Manhattan distance between two points"""
return (abs(position_1[0] - position_2[0]) +
abs(position_1[1] - position_2[1]))
def create_distance_evaluator(data):
"""Creates callback to return distance between points."""
_distances = {}
# precompute distance between location to have distance callback in O(1)
for from_node in range(data['num_locations']):
_distances[from_node] = {}
for to_node in range(data['num_locations']):
if from_node == to_node:
_distances[from_node][to_node] = 0
# Forbid start/end/reload node to be consecutive.
elif from_node in range(6) and to_node in range(6):
_distances[from_node][to_node] = data['vehicle_max_distance']
else:
_distances[from_node][to_node] = (manhattan_distance(
data['locations'][from_node], data['locations'][to_node]))
def distance_evaluator(manager, from_node, to_node):
"""Returns the manhattan distance between the two nodes"""
return _distances[manager.IndexToNode(from_node)][manager.IndexToNode(
to_node)]
return distance_evaluator
def add_distance_dimension(routing, manager, data, distance_evaluator_index):
"""Add Global Span constraint"""
distance = 'Distance'
routing.AddDimension(
distance_evaluator_index,
0, # null slack
data['vehicle_max_distance'], # maximum distance per vehicle
True, # start cumul to zero
distance)
distance_dimension = routing.GetDimensionOrDie(distance)
# Try to minimize the max distance among vehicles.
# /!\ It doesn't mean the standard deviation is minimized
distance_dimension.SetGlobalSpanCostCoefficient(100)
# Define Transportation Requests. #######################################
for request in data['pickups_deliveries']:
pickup_index = manager.NodeToIndex(request[0])
delivery_index = manager.NodeToIndex(request[1])
routing.AddPickupAndDelivery(pickup_index, delivery_index)
routing.solver().Add(
routing.VehicleVar(pickup_index) == routing.VehicleVar(
delivery_index))
routing.solver().Add(
distance_dimension.CumulVar(pickup_index) <=
distance_dimension.CumulVar(delivery_index))
##########################################################################
def create_demand_evaluator(data):
"""Creates callback to get demands at each location."""
_demands = data['demands']
def demand_evaluator(manager, from_node):
"""Returns the demand of the current node"""
return _demands[manager.IndexToNode(from_node)]
return demand_evaluator
def add_capacity_constraints(routing, manager, data, demand_evaluator_index):
"""Adds capacity constraint"""
vehicle_capacity = data['vehicle_capacity']
capacity = 'Capacity'
routing.AddDimension(
demand_evaluator_index,
vehicle_capacity,
vehicle_capacity,
True, # start cumul to zero
capacity)
# Add Slack for reseting to zero unload depot nodes.
# e.g. vehicle with load 10/15 arrives at node 1 (depot unload)
# so we have CumulVar = 10(current load) + -15(unload) + 5(slack) = 0.
capacity_dimension = routing.GetDimensionOrDie(capacity)
# Allow to drop reloading nodes with zero cost.
for node in [1, 2, 3, 4, 5]:
node_index = manager.NodeToIndex(node)
routing.AddDisjunction([node_index], 0)
# Allow to drop regular node with a cost.
for node in range(6, len(data['demands'])):
node_index = manager.NodeToIndex(node)
capacity_dimension.SlackVar(node_index).SetValue(0)
routing.AddDisjunction([node_index], 100_000)
def create_time_evaluator(data):
"""Creates callback to get total times between locations."""
def service_time(data, node):
"""Gets the service time for the specified location."""
return abs(data['demands'][node]) * data['time_per_demand_unit']
def travel_time(data, from_node, to_node):
"""Gets the travel times between two locations."""
if from_node == to_node:
travel_time = 0
else:
travel_time = manhattan_distance(
data['locations'][from_node], data['locations'][to_node]) / data['vehicle_speed']
return travel_time
_total_time = {}
# precompute total time to have time callback in O(1)
for from_node in range(data['num_locations']):
_total_time[from_node] = {}
for to_node in range(data['num_locations']):
if from_node == to_node:
_total_time[from_node][to_node] = 0
else:
_total_time[from_node][to_node] = int(
service_time(data, from_node) + travel_time(
data, from_node, to_node))
def time_evaluator(manager, from_node, to_node):
"""Returns the total time between the two nodes"""
return _total_time[manager.IndexToNode(from_node)][manager.IndexToNode(
to_node)]
return time_evaluator
def add_time_window_constraints(routing, manager, data, time_evaluator):
"""Add Time windows constraint"""
time = 'Time'
max_time = data['vehicle_max_time']
routing.AddDimension(
time_evaluator,
max_time, # allow waiting time
max_time, # maximum time per vehicle
False, # don't force start cumul to zero since we are giving TW to start nodes
time)
time_dimension = routing.GetDimensionOrDie(time)
# Add time window constraints for each location except depot
# and 'copy' the slack var in the solution object (aka Assignment) to print it
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])
routing.AddToAssignment(time_dimension.SlackVar(index))
# Add time window constraints for each vehicle start node
# and 'copy' the slack var in the solution object (aka Assignment) to print it
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])
routing.AddToAssignment(time_dimension.SlackVar(index))
# Warning: Slack var is not defined for vehicle's end node
#routing.AddToAssignment(time_dimension.SlackVar(self.routing.End(vehicle_id)))
###########
# Printer #
###########
def print_solution(data, manager, routing, assignment): # pylint:disable=too-many-locals
"""Prints assignment on console"""
print(f'Objective: {assignment.ObjectiveValue()}')
total_distance = 0
total_load = 0
total_time = 0
capacity_dimension = routing.GetDimensionOrDie('Capacity')
time_dimension = routing.GetDimensionOrDie('Time')
dropped = []
for order in range(6, routing.nodes()):
index = manager.NodeToIndex(order)
if assignment.Value(routing.NextVar(index)) == index:
dropped.append(order)
print(f'dropped orders: {dropped}')
for reload in range(1, 6):
index = manager.NodeToIndex(reload)
if assignment.Value(routing.NextVar(index)) == index:
dropped.append(reload)
print(f'dropped reload stations: {dropped}')
for vehicle_id in range(data['num_vehicles']):
index = routing.Start(vehicle_id)
plan_output = f'Route for vehicle {vehicle_id}:\n'
distance = 0
while not routing.IsEnd(index):
load_var = capacity_dimension.CumulVar(index)
time_var = time_dimension.CumulVar(index)
plan_output += ' {0} Load({1}) Time({2},{3}) ->'.format(
manager.IndexToNode(index),
assignment.Value(load_var),
assignment.Min(time_var), assignment.Max(time_var))
previous_index = index
index = assignment.Value(routing.NextVar(index))
distance += routing.GetArcCostForVehicle(previous_index, index,
vehicle_id)
load_var = capacity_dimension.CumulVar(index)
time_var = time_dimension.CumulVar(index)
plan_output += ' {0} Load({1}) Time({2},{3})\n'.format(
manager.IndexToNode(index),
assignment.Value(load_var),
assignment.Min(time_var), assignment.Max(time_var))
plan_output += f'Distance of the route: {distance}m\n'
plan_output += f'Load of the route: {assignment.Value(load_var)}\n'
plan_output += f'Time of the route: {assignment.Value(time_var)}min\n'
print(plan_output)
total_distance += distance
total_load += assignment.Value(load_var)
total_time += assignment.Value(time_var)
print('Total Distance of all routes: {}m'.format(total_distance))
print('Total Load of all routes: {}'.format(total_load))
print('Total Time of all routes: {}min'.format(total_time))
########
# Main #
########
def main():
"""Entry point of the program"""
# Instantiate the data problem.
data = create_data_model()
# Create the routing index manager
manager = pywrapcp.RoutingIndexManager(data['num_locations'],
data['num_vehicles'], data['depot'])
# Create Routing Model
routing = pywrapcp.RoutingModel(manager)
# Define weight of each edge
distance_evaluator_index = routing.RegisterTransitCallback(
partial(create_distance_evaluator(data), manager))
routing.SetArcCostEvaluatorOfAllVehicles(distance_evaluator_index)
# Add Distance constraint to minimize the longuest route
add_distance_dimension(routing, manager, data, distance_evaluator_index)
# Add Capacity constraint
demand_evaluator_index = routing.RegisterUnaryTransitCallback(
partial(create_demand_evaluator(data), manager))
add_capacity_constraints(routing, manager, data, demand_evaluator_index)
# Add Time Window constraint
time_evaluator_index = routing.RegisterTransitCallback(
partial(create_time_evaluator(data), manager))
add_time_window_constraints(routing, manager, data, time_evaluator_index)
# Setting first solution heuristic (cheapest addition).
search_parameters = pywrapcp.DefaultRoutingSearchParameters()
search_parameters.first_solution_strategy = (
routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC) # pylint: disable=no-member
search_parameters.local_search_metaheuristic = (
routing_enums_pb2.LocalSearchMetaheuristic.GUIDED_LOCAL_SEARCH)
search_parameters.time_limit.FromSeconds(3)
# Solve the problem.
solution = routing.SolveWithParameters(search_parameters)
if solution:
print_solution(data, manager, routing, solution)
else:
print("No solution found !")
if __name__ == '__main__':
main()
I got advice comments from Mizux and fixed the code.
I have a pair of Pickup and Delivery points and map the same Delivery points to the same coordinates. Thanks to Mizux for the great advice.
from functools import partial
from ortools.constraint_solver import pywrapcp
from ortools.constraint_solver import routing_enums_pb2
###########################
# Problem Data Definition #
###########################
def create_data_model():
"""Stores the data for the problem"""
data = {}
_capacity = 15
# Locations in block unit
_locations = [
(4, 4), # depot
(7, 3), (5, 5), #1,2 pickup_loc, delivery_loc
(6, 6), (5, 5), #3,4 pickup_loc, delivery_loc
(3, 4), (5, 5), #5,6 pickup_loc, delivery_loc
(3, 5), (3, 3), #7,8 pickup_loc, delivery_loc
(2, 7), (3, 3), #9,10 pickup_loc, delivery_loc
(6, 3), (3, 3), #11,12 pickup_loc, delivery_loc
(8, 5), (7, 7), #13,14 pickup_loc, delivery_loc
(2, 6), (7, 7), #15,16 pickup_loc, delivery_loc
(6, 7), (7, 7), #17,18 pickup_loc, delivery_loc
(7, 8), (8, 8), #19,20 pickup_loc, delivery_loc
(5, 7), (8, 8), #20,21 pickup_loc, delivery_loc
]
# Compute locations in meters using the block dimension defined as follow
# Manhattan average block: 750ft x 264ft -> 228m x 80m
# here we use: 114m x 80m city block
data['locations'] = [(l[0] * 114, l[1] * 80) for l in _locations]
data['num_locations'] = len(data['locations'])
data['demands'] = \
[0, # depot
4, -4, # 1, 2 load, unload
4, -4, # 3, 4 load, unload
4, -4, # 5, 6 load, unload
4, -4, # 7, 8 load, unload
4, -4, # 9, 10 load, unload
4, -4, # 11, 12 load, unload
4, -4, # 13, 14 load, unload
4, -4, # 15, 16 load, unload
4, -4, # 17, 18 load, unload
4, -4, # 19, 20 load, unload
4, -4] # 21, 22 load, unload
data['time_per_demand_unit'] = 5 # 5 minutes/unit
data['time_windows'] = \
[(0, 0), # depot
(0, 1500), (0, 750), # 1, 2 specify delivery time
(0, 1500), (0, 750), # 3, 4 specify delivery time
(0, 1500), (0, 750), # 5, 6 specify delivery time
(0, 1500), (0, 750), # 7, 8 specify delivery time
(0, 1500), (0, 750), # 9, 10 specify delivery time
(0, 1500), (0, 750), # 11, 12 specify delivery time
(0, 1500), (750, 1500), # 13, 14 specify delivery time
(0, 1500), (750, 1500), # 15, 16 specify delivery time
(0, 1500), (750, 1500), # 17, 18 specify delivery time
(0, 1500), (750, 1500), # 19, 20 specify delivery time
(0, 1500), (750, 1500)] # 21, 22 specify delivery time
#added p&d list.##############################
data['pickups_deliveries'] = [
[1, 2], [3, 4],
[5, 6], [7, 8],
[9, 10], [11, 12],
[13, 14], [15, 16],
[17, 18], [19, 20],
[21, 22]
]
##############################################
data['num_vehicles'] = 3
data['vehicle_capacity'] = _capacity
data['vehicle_max_distance'] = 10_000
data['vehicle_max_time'] = 1_500
data[
'vehicle_speed'] = 5 * 60 / 3.6 # Travel speed: 5km/h to convert in m/min
data['depot'] = 0
return data
#######################
# Problem Constraints #
#######################
def manhattan_distance(position_1, position_2):
"""Computes the Manhattan distance between two points"""
return (abs(position_1[0] - position_2[0]) +
abs(position_1[1] - position_2[1]))
def create_distance_evaluator(data):
"""Creates callback to return distance between points."""
_distances = {}
# precompute distance between location to have distance callback in O(1)
for from_node in range(data['num_locations']):
_distances[from_node] = {}
for to_node in range(data['num_locations']):
if from_node == to_node:
_distances[from_node][to_node] = 0
# commented out for unused duplicate depot######################
# elif from_node in range(6) and to_node in range(6):
# _distances[from_node][to_node] = data['vehicle_max_distance']
else:
_distances[from_node][to_node] = (manhattan_distance(
data['locations'][from_node], data['locations'][to_node]))
def distance_evaluator(manager, from_node, to_node):
"""Returns the manhattan distance between the two nodes"""
return _distances[manager.IndexToNode(from_node)][manager.IndexToNode(
to_node)]
return distance_evaluator
def add_distance_dimension(routing, manager, data, distance_evaluator_index):
"""Add Global Span constraint"""
distance = 'Distance'
routing.AddDimension(
distance_evaluator_index,
0, # null slack
data['vehicle_max_distance'], # maximum distance per vehicle
True, # start cumul to zero
distance)
distance_dimension = routing.GetDimensionOrDie(distance)
# Try to minimize the max distance among vehicles.
# /!\ It doesn't mean the standard deviation is minimized
distance_dimension.SetGlobalSpanCostCoefficient(100)
# Define Transportation Requests. #######################################
for request in data['pickups_deliveries']:
pickup_index = manager.NodeToIndex(request[0])
delivery_index = manager.NodeToIndex(request[1])
routing.AddPickupAndDelivery(pickup_index, delivery_index)
routing.solver().Add(
routing.VehicleVar(pickup_index) == routing.VehicleVar(
delivery_index))
routing.solver().Add(
distance_dimension.CumulVar(pickup_index) <=
distance_dimension.CumulVar(delivery_index))
##########################################################################
def create_demand_evaluator(data):
"""Creates callback to get demands at each location."""
_demands = data['demands']
def demand_evaluator(manager, from_node):
"""Returns the demand of the current node"""
return _demands[manager.IndexToNode(from_node)]
return demand_evaluator
def add_capacity_constraints(routing, manager, data, demand_evaluator_index):
"""Adds capacity constraint"""
vehicle_capacity = data['vehicle_capacity']
capacity = 'Capacity'
routing.AddDimension(
demand_evaluator_index,
vehicle_capacity,
vehicle_capacity,
True, # start cumul to zero
capacity)
# Add Slack for reseting to zero unload depot nodes.
# e.g. vehicle with load 10/15 arrives at node 1 (depot unload)
# so we have CumulVar = 10(current load) + -15(unload) + 5(slack) = 0.
capacity_dimension = routing.GetDimensionOrDie(capacity)
# Allow to drop reloading nodes with zero cost.
# for node in [1, 2, 3, 4, 5]:
# node_index = manager.NodeToIndex(node)
# routing.AddDisjunction([node_index], 0)
# Allow to drop regular node with a cost. (Pickup & Delivery node)
for node in range(1, len(data['demands'])):
node_index = manager.NodeToIndex(node)
capacity_dimension.SlackVar(node_index).SetValue(0)
routing.AddDisjunction([node_index], 100_000)
def create_time_evaluator(data):
"""Creates callback to get total times between locations."""
def service_time(data, node):
"""Gets the service time for the specified location."""
return abs(data['demands'][node]) * data['time_per_demand_unit']
def travel_time(data, from_node, to_node):
"""Gets the travel times between two locations."""
if from_node == to_node:
travel_time = 0
else:
travel_time = manhattan_distance(
data['locations'][from_node], data['locations'][to_node]) / data['vehicle_speed']
return travel_time
_total_time = {}
# precompute total time to have time callback in O(1)
for from_node in range(data['num_locations']):
_total_time[from_node] = {}
for to_node in range(data['num_locations']):
if from_node == to_node:
_total_time[from_node][to_node] = 0
else:
_total_time[from_node][to_node] = int(
service_time(data, from_node) + travel_time(
data, from_node, to_node))
def time_evaluator(manager, from_node, to_node):
"""Returns the total time between the two nodes"""
return _total_time[manager.IndexToNode(from_node)][manager.IndexToNode(
to_node)]
return time_evaluator
def add_time_window_constraints(routing, manager, data, time_evaluator):
"""Add Time windows constraint"""
time = 'Time'
max_time = data['vehicle_max_time']
routing.AddDimension(
time_evaluator,
max_time, # allow waiting time
max_time, # maximum time per vehicle
False, # don't force start cumul to zero since we are giving TW to start nodes
time)
time_dimension = routing.GetDimensionOrDie(time)
# Add time window constraints for each location except depot
# and 'copy' the slack var in the solution object (aka Assignment) to print it
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])
routing.AddToAssignment(time_dimension.SlackVar(index))
# Add time window constraints for each vehicle start node
# and 'copy' the slack var in the solution object (aka Assignment) to print it
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])
routing.AddToAssignment(time_dimension.SlackVar(index))
# Warning: Slack var is not defined for vehicle's end node
#routing.AddToAssignment(time_dimension.SlackVar(self.routing.End(vehicle_id)))
###########
# Printer #
###########
def print_solution(data, manager, routing, assignment): # pylint:disable=too-many-locals
"""Prints assignment on console"""
print(f'Objective: {assignment.ObjectiveValue()}')
total_distance = 0
total_load = 0
total_time = 0
capacity_dimension = routing.GetDimensionOrDie('Capacity')
time_dimension = routing.GetDimensionOrDie('Time')
dropped = []
for order in range(6, routing.nodes()):
index = manager.NodeToIndex(order)
if assignment.Value(routing.NextVar(index)) == index:
dropped.append(order)
print(f'dropped orders: {dropped}')
for reload in range(1, 6):
index = manager.NodeToIndex(reload)
if assignment.Value(routing.NextVar(index)) == index:
dropped.append(reload)
print(f'dropped reload stations: {dropped}')
for vehicle_id in range(data['num_vehicles']):
index = routing.Start(vehicle_id)
plan_output = f'Route for vehicle {vehicle_id}:\n'
distance = 0
while not routing.IsEnd(index):
load_var = capacity_dimension.CumulVar(index)
time_var = time_dimension.CumulVar(index)
plan_output += ' {0} Load({1}) Time({2},{3}) ->'.format(
manager.IndexToNode(index),
assignment.Value(load_var),
assignment.Min(time_var), assignment.Max(time_var))
previous_index = index
index = assignment.Value(routing.NextVar(index))
distance += routing.GetArcCostForVehicle(previous_index, index,
vehicle_id)
load_var = capacity_dimension.CumulVar(index)
time_var = time_dimension.CumulVar(index)
plan_output += ' {0} Load({1}) Time({2},{3})\n'.format(
manager.IndexToNode(index),
assignment.Value(load_var),
assignment.Min(time_var), assignment.Max(time_var))
plan_output += f'Distance of the route: {distance}m\n'
plan_output += f'Load of the route: {assignment.Value(load_var)}\n'
plan_output += f'Time of the route: {assignment.Value(time_var)}min\n'
print(plan_output)
total_distance += distance
total_load += assignment.Value(load_var)
total_time += assignment.Value(time_var)
print('Total Distance of all routes: {}m'.format(total_distance))
print('Total Load of all routes: {}'.format(total_load))
print('Total Time of all routes: {}min'.format(total_time))
########
# Main #
########
def main():
"""Entry point of the program"""
# Instantiate the data problem.
data = create_data_model()
# Create the routing index manager
manager = pywrapcp.RoutingIndexManager(data['num_locations'],
data['num_vehicles'], data['depot'])
# Create Routing Model
routing = pywrapcp.RoutingModel(manager)
# Define weight of each edge
distance_evaluator_index = routing.RegisterTransitCallback(
partial(create_distance_evaluator(data), manager))
routing.SetArcCostEvaluatorOfAllVehicles(distance_evaluator_index)
# Add Distance constraint to minimize the longuest route
add_distance_dimension(routing, manager, data, distance_evaluator_index)
# Add Capacity constraint
demand_evaluator_index = routing.RegisterUnaryTransitCallback(
partial(create_demand_evaluator(data), manager))
add_capacity_constraints(routing, manager, data, demand_evaluator_index)
# Add Time Window constraint
time_evaluator_index = routing.RegisterTransitCallback(
partial(create_time_evaluator(data), manager))
add_time_window_constraints(routing, manager, data, time_evaluator_index)
# Setting first solution heuristic (cheapest addition).
search_parameters = pywrapcp.DefaultRoutingSearchParameters()
search_parameters.first_solution_strategy = (
routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC) # pylint: disable=no-member
search_parameters.local_search_metaheuristic = (
routing_enums_pb2.LocalSearchMetaheuristic.GUIDED_LOCAL_SEARCH)
search_parameters.time_limit.FromSeconds(3)
# Solve the problem.
solution = routing.SolveWithParameters(search_parameters)
if solution:
print_solution(data, manager, routing, solution)
else:
print("No solution found !")
if __name__ == '__main__':
main()

Problem understanding Pickups & delivery array using OR-TOOLS

So, I've been trying to understand what is the problem with the following experiment, but wasn't able to find the issue..
So, below is the code I am using (note that there is a duplicate node, as that node is both a pick and a delivery node):
"""Capacited Vehicles Routing Problem (CVRP)."""
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['distance_matrix'] = [
[ 0, 220, 460, 460, 700, 280, 610],
[220, 0, 570, 570, 670, 500, 500],
[460, 570, 0, 0, 350, 550, 450],
[460, 570, 0, 0, 350, 550, 450],
[700, 670, 350, 350, 0, 850, 250],
[280, 500, 550, 550, 850, 0, 830],
[610, 500, 450, 450, 250, 830, 0]
]
data['pickups_deliveries'] = [
[1, 2], #1,2
[3, 6], #3,4
[4, 5], #5,6
]
data['demands'] = [
0, # departs empty
1, # load in 1
-1, # unload in 2
1, # load in 3 (duplicate of 2)
1, # load in 4
-1, # unload in 5
-1] # unload in 5
data['vehicle_capacities'] = [1]
data['num_vehicles'] = 1
data['depot'] = 0
return data
def print_solution(data, manager, routing, solution):
"""Prints solution on console."""
print(f'Objective: {solution.ObjectiveValue()}')
total_distance = 0
total_load = 0
for vehicle_id in range(data['num_vehicles']):
index = routing.Start(vehicle_id)
plan_output = 'Route for vehicle {}:\n'.format(vehicle_id)
route_distance = 0
route_load = 0
while not routing.IsEnd(index):
node_index = manager.IndexToNode(index)
route_load += data['demands'][node_index]
plan_output += ' {0} Load({1}) -> '.format(node_index, route_load)
previous_index = index
index = solution.Value(routing.NextVar(index))
route_distance += routing.GetArcCostForVehicle(
previous_index, index, vehicle_id)
plan_output += ' {0} Load({1})\n'.format(manager.IndexToNode(index),
route_load)
plan_output += 'Distance of the route: {}m\n'.format(route_distance)
plan_output += 'Load of the route: {}\n'.format(route_load)
print(plan_output)
total_distance += route_distance
total_load += route_load
print('Total distance of all routes: {}m'.format(total_distance))
print('Total load of all routes: {}'.format(total_load))
def main():
"""Solve the CVRP problem."""
# Instantiate the data problem.
data = create_data_model()
# Create the routing index manager.
manager = pywrapcp.RoutingIndexManager(len(data['distance_matrix']),
data['num_vehicles'], data['depot'])
# Create Routing Model.
routing = pywrapcp.RoutingModel(manager)
# Create and register a transit callback.
def distance_callback(from_index, to_index):
"""Returns the distance between the two nodes."""
# Convert from routing variable Index to distance matrix NodeIndex.
from_node = manager.IndexToNode(from_index)
to_node = manager.IndexToNode(to_index)
return data['distance_matrix'][from_node][to_node]
transit_callback_index = routing.RegisterTransitCallback(distance_callback)
# Define cost of each arc.
routing.SetArcCostEvaluatorOfAllVehicles(transit_callback_index)
# Add Capacity constraint.
def demand_callback(from_index):
"""Returns the demand of the node."""
# Convert from routing variable Index to demands NodeIndex.
from_node = manager.IndexToNode(from_index)
return data['demands'][from_node]
demand_callback_index = routing.RegisterUnaryTransitCallback(demand_callback)
routing.AddDimensionWithVehicleCapacity(
demand_callback_index,
0, # null capacity slack
data['vehicle_capacities'], # vehicle maximum capacities
True, # start cumul to zero
'Capacity')
# Add Distance constraint.
dimension_name = 'Distance'
routing.AddDimension(
transit_callback_index,
0, # no slack
3000, # vehicle maximum travel distance
True, # start cumul to zero
dimension_name)
distance_dimension = routing.GetDimensionOrDie(dimension_name)
distance_dimension.SetGlobalSpanCostCoefficient(100)
# Define Transportation Requests.
for request in data['pickups_deliveries']:
pickup_index = manager.NodeToIndex(request[0])
delivery_index = manager.NodeToIndex(request[1])
routing.AddPickupAndDelivery(pickup_index, delivery_index)
routing.solver().Add(
routing.VehicleVar(pickup_index) == routing.VehicleVar(
delivery_index))
routing.solver().Add(
distance_dimension.CumulVar(pickup_index) <=
distance_dimension.CumulVar(delivery_index))
# Setting first solution heuristic.
search_parameters = pywrapcp.DefaultRoutingSearchParameters()
search_parameters.first_solution_strategy = (routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC)
search_parameters.local_search_metaheuristic = (routing_enums_pb2.LocalSearchMetaheuristic.GUIDED_LOCAL_SEARCH)
search_parameters.time_limit.FromSeconds(1)
# Solve the problem.
solution = routing.SolveWithParameters(search_parameters)
# Print solution on console.
if solution:
print_solution(data, manager, routing, solution)
if __name__ == '__main__':
main()
When run, the algorithm returns:
Objective: 264620
Route for vehicle 0:
0 Load(0) -> 1 Load(1) -> 2 Load(0) -> 3 Load(1) -> 6 Load(0) -> 4 Load(1) -> 5 Load(0) -> 0 Load(0)
Distance of the route: 2620m
Load of the route: 0
Total distance of all routes: 2620m
Total load of all routes: 0
which is fine. Now, If I only change the order of the pickups_deliveries from nodes 4->5 to 5->4 (just reversing they roles), say from this
data['pickups_deliveries'] = [
[1, 2], #1,2
[3, 6], #3,4
[4, 5], #5,6
]
to this
data['pickups_deliveries'] = [
[1, 2], #1,2
[3, 6], #3,4
[5, 4], #5,6
]
and change the demands accordingly from this:
data['demands'] = [
0, # departs empty
1, # load in 1
-1, # unload in 2
1, # load in 3 (duplicate of 2)
1, # load in 4
-1, # unload in 5
-1] # unload in 6
to this:
data['demands'] = [
0, # departs empty
1, # load in 1
-1, # unload in 2
1, # load in 3 (duplicate of 2)
-1, # unload in 4
1, # load in 5
-1] # unload in 6
the algorithm is unable to find a solution. When the only thing that has been changed is the order of the last segment. What am I doing wrong here? Any help would be very much appreciated!
Thanks in advance!
PD: A posible solution would be:
Depart at (0),
load in 1 ---> unload in 2,
load in 3(same node as 2) ---> unload in 6
load in 5---> unload in 4
If you look at the original solution we can see
Distance of the route: 2620m
And we have
routing.AddDimension(
transit_callback_index,
0, # no slack
3000, # vehicle maximum travel distance
So I'm pretty sure increasing the limit could help here, so let's increase it to 3500
and you'll get:
./plop.py
Objective: 346430
Route for vehicle 0:
0 Load(0) -> 5 Load(1) -> 4 Load(0) -> 1 Load(1) -> 2 Load(0) -> 3 Load(1) -> 6 Load(0) -> 0 Load(0)
Distance of the route: 3430m
Load of the route: 0
Total distance of all routes: 3430m
Total load of all routes: 0
notice the:
Distance of the route: 3430m
ps: next time, please keep import statement
pps: your snippet didn't work since you've added some float in the distance matrix, please use only int matrix or in your callback round it using return int(data[...][][])

Getting KeyError when assigning value with __getitem__ method

I want to realize the bert model.
So I built a class with __getitem__ in it.
I can print something like test[0], but when I assign a value, like data = test[0], a KeyError occurs.
import random
"""
corpus_file = 'vocab'
vocab_size = 6
vocab_freq = 1
save_path = 'obj/'
max_sentence = 16
corpus -> org_line -> ope_line
corpus -> org_line -> token_list -> idx_to_token + token_to_idx
"""
class vocab():
def __init__(self, corpus_file, vocab_size, vocab_freq,save_path,max_sentence):
self.max_sentence = max_sentence
self.special_labels = ['PAD', 'UNK', 'SEP', 'CLS', 'MASK']
# output
self.data = []
self.idx_to_token = []
self.token_to_idx = {}
# ope
self.pre_ope(corpus_file,vocab_size,vocab_freq)
#self.save_data(save_path)
#self.print_data()
def pre_ope(self,corpus_file,vocab_size,vocab_freq):
token_list = {}
with open(corpus_file, 'r') as f:
while 1:
new_org_line = f.readline()
if new_org_line != '':
new_org_line = new_org_line.strip('\n')
new_sentence = new_org_line.split('\t')
sentence = []
for tmp in new_sentence:
token_sentence = tmp.split()
sentence.append(token_sentence)
for token in token_sentence:
if token_list.get(token):
token_list[token] += 1
else:
new_token = {token: 1}
token_list.update(new_token)
self.data.append(sentence)
else:
break
f.close()
token_list = sorted(token_list.items(), key=lambda i: (-i[1], i[0]))
self.build_dictionary(token_list,vocab_freq,vocab_size)
'''
Special labels:
PAD
UNK
SEP sentence separator
CLS classifier token
MASK
'''
def build_dictionary(self,token_list,vocab_freq,vocab_size):
for idx, label in enumerate(self.special_labels):
self.idx_to_token.append(label)
self.token_to_idx[label] = idx
for idx, (token, freq) in enumerate(token_list):
if freq >= vocab_freq :
self.idx_to_token.append(token)
self.token_to_idx[token] = idx + len(self.special_labels)
if len(self.idx_to_token) >= vocab_size + len(self.special_labels) and vocab_size != 0 :
break
def __len__(self):
return len(self.data)
def print_data(self):
print(self.data)
print(self.idx_to_token)
print(self.token_to_idx)
def __getitem__(self, item):
s1,s2,is_next_sentence = self.get_random_next_sentence(item)
s1,s1_label = self.get_random_sentence(s1)
s2,s2_label = self.get_random_sentence(s2)
sentence = [self.token_to_idx['CLS']] +s1 +[self.token_to_idx['SEP']] +s2 +[self.token_to_idx['SEP']]
label = [-1] +s1_label +[-1] +s2_label +[-1]
if len(sentence) > self.max_sentence :
print('sentence is greater than the setting of max sentence')
for pos in range(len(sentence),self.max_sentence):
sentence.append(self.token_to_idx['PAD'])
label.append(-1)
return {
'token' : sentence,
'label' : label,
'is_next' : is_next_sentence
}
def get_random_next_sentence(self,item):
s1 = self.data[item][0]
s2 = self.data[item][1]
if random.random() < 0.5 :
is_next = 0
s2 = self.data[self.get_random_line(item)][1]
else:
is_next = 1
return s1,s2,is_next
def get_random_line(self,item):
rand = random.randint(0,len(self.data)-1)
while rand == item :
rand = random.randint(0,len(self.data)-1)
return rand
def get_random_sentence(self,sentence):
label = []
for idx,token in enumerate(sentence):
rand = random.random()
if rand < 0.15:
rand = rand/0.15
if rand < 0.8: #mask
sentence[idx] = self.token_to_idx['MASK']
elif rand < 0.9: #rand
sentence[idx] = random.randint(len(self.special_labels),len(self.token_to_idx)-1)
else: # still
sentence[idx] = self.token_to_idx[token]
label.append(self.token_to_idx[token])
else:
sentence[idx] = self.token_to_idx[token]
label.append(-1)
return sentence,label
if __name__ == '__main__':
test = vocab('vocab', 0, 1,'obj/',16)
print(len(test))
print(test[0])
print(test[1])
data = test[0]
Result:
2
{'token': [3, 4, 18, 12, 15, 11, 2, 7, 9, 13, 2, 0, 0, 0, 0, 0], 'label': [-1, 10, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], 'is_next': 0}
{'token': [3, 6, 4, 5, 8, 5, 17, 2, 16, 5, 14, 20, 2, 0, 0, 0], 'label': [-1, -1, 19, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1], 'is_next': 0}
Traceback (most recent call last):
File "vocab.py", line 146, in <module>
data = test[0]
File "vocab.py", line 90, in ```__getitem__```
s1,s1_label = self.get_random_sentence(s1)
File "vocab.py", line 136, in get_random_sentence
sentence[idx] = self.token_to_idx[token]
KeyError: 4
vocab file:
hello this is my home nice to meet you
I want to go to school and have lunch
change the code:
def get_random_next_sentence(self,item):
s1 = self.data[item][0]
s2 = self.data[item][1]
if random.random() < 0.5 :
is_next = 0
s2 = self.data[self.get_random_line(item)][1]
else:
is_next = 1
return s1,s2,is_next
to:
def get_random_next_sentence(self,item):
s1 = copy.deepcopy(self.data[item][0])
s2 = copy.deepcopy(self.data[item][1])
if random.random() < 0.5 :
is_next = 0
s2 = copy.deepcopy(self.data[self.get_random_line(item)][1])
print(s2)
else:
is_next = 1
return s1,s2,is_next

Merging two Dictionaries without overwrite values in the nested one

I tried to update main dictionary with every new one in the loop, but every time it overwrite values inside the nested dictionary.
I mean I want smth like this:
{'Barbour': {1900: 73041, 1910: 895427, 1920: 1531624, 1930: 1617086, 1940: 1420561, 1950: 1853223,
1960: 3092728, 1970: 3505193, 1980: 3659797, 1990: 2575561, 2000: 743757, 2010: 1730711},
'Berkeley': {1900: 0, 1910: 0, 1920: 0, 1930: 0, 1940: 0, 1950: 0, 1960: 0, 1970: 0, 1980: 0, 1990:
0, 2000: 0, 2010: 0}}
(for all of the cities)
def read_file_contents_coal():
return ['Barbour,73041,895427,1531624,1617086,1420561,1853223,3092728,3505193,3659797,2575561,743757,1730711\n',
'Berkeley,0,0,0,0,0,0,0,0,0,0,0,0\n',
'Boone,0,50566,1477560,3045056,3804527,5851267,6278609,11607216,13842525,27618152,32446186,23277998\n',
'Braxton,0,114422,286955,123991,13751,38414,218087,0,459517,3256906,1196489,439662\n',
]
def process_file_contents():
lst = read_file_contents_coal()
dic = {}
coal_dic = {}
yearcoaldic = {}
ycdic = {}
for stringdata in lst:
city_data = stringdata.strip().split(',')
year = 1900
for j in range(1, 13):
ycdic = {year: int(city_data[j])}
year += 10
yearcoaldic.update(ycdic)
dic = {city_data[0]: yearcoaldic}
coal_dic.update(dic)
print(coal_dic)
return coal_dic
[EDIT]: the issue is that you have to move yearcoaldic to the first loop and always set it to en empty dictionary otherwise you will always overwrite your values as you have experienced.
def process_file_contents():
lst = read_file_contents_coal()
dic = {}
coal_dic = {}
ycdic = {}
for stringdata in lst:
yearcoaldic = {}
city_data = stringdata.strip().split(',')
year = 1900
for j in range(1, 13):
ycdic = {year: int(city_data[j])}
year += 10
yearcoaldic.update(ycdic)
# dic = {city_data[0]: yearcoaldic}
dic[city_data[0]] = yearcoaldic
# coal_dic.update(dic)
# print(coal_dic)
return dic

Camera calibration on Python

my problem is camera calibration. Now Im trying to write a system of equations from 2 list of my 3D and 2D points in the form of matrix A and then I need to solve it for P. Could you help me? what the error is?
pts2d = np.array ( [1.0486, -0.3645], [-1.6851, -0.4004], [-0.9437, -0.4200],
[1.0682, 0.0699], [1.0682, 0.0699], [1.0682, 0.0699],
[0.6077, -0.0771], [1.2543, -0.6454], [-0.2709, 0.8635],
[-0.4571, -0.3645], [-0.7902, 0.0307], [0.7318, 0.6382],
[-1.0580, 0.3312], [0.3464, 0.3377], [0.3137, 0.1189],
[-0.4310, 0.0242], [-0.4310, 0.0242], [-0.4799, 0.2920],
[-0.4799, 0.2920], [0.6109, 0.0830], [-0.4081, 0.2920],
[-0.1109, -0.2992], [0.5129, -0.0575], [0.1406, -0.4527])
pts3d = np.array ( [1.5706, -0.149, 0.2598], [-1.5282, 0.9695, 0.3802], [-0.6821, 1.2856, 0.4078],
[0.4124, -1.0201, -0.0915], [1.2095, 0.2812, -0.128], [0.8819, -0.8481, 0.5255],
[-0.9442, -1.1583, -0.3759], [0.0415, 1.3445, 0.324], [-0.7975, 0.3017, -0.0826],
[-0.4329, -1.4151, -0.2774], [-1.1475, -0.0772, -0.2667], [-0.5149, -1.1784, -0.1401],
[0.1993, -0.2854, -0.2114], [-0.432, 0.2143, -0.1053], [-0.7481, -0.384, -0.2408],
[0.8078, -0.1196, -0.2631], [-0.7605, -0.5792, -0.1936], [0.3237, 0.797, 0.217],
[1.3089, 0.5786, -0.1887], [1.2323, 1.4421, 0.4506] )
u = pts2d([:,1])
IndentationError: unexpected indent: u = pts2d([:,1])
v = pts2d([:,2])
X = pts3d([:,1])
Y = pts3d([:,2])
Z = pts3d([:,3])
A = np.zeros(40,12)
for i in [1:2:40]:
j = ((i + 1) / 2)
A([i:i + 1], [:]) = mcat
([X(j), Y(j), Z(j) [ 1, 0, 0, 0, 0] - u(j)*X(j)-u(j)*Y(j)-u(j)*Z(j)-u(j),
[0, 0, 0, 0] X(j), Y(j), Z(j), [1] - v(j)*X(j)-v(j)*Y(j)-v(j)*Z(j)-v(j)])
end
U, D, Q = np.linalg.svd(A, full_matrices=False)
return A

Categories