2 years ago
#17823

Nimesh Madusanka
How to replace _locations array with distance matrix array and modify create_time_evaluator according to matrix array in or-tools samples/cvrptw.py
I have a question with multiple locations, time windows for each location, multiple vehicles, and weight capacity for each vehicle. I need to get a vehicle routing result for each vehicle considering the capacity and time windows of locations.
I started with the or-tool example cvrptw.py. and try to add distance matrix as input using the or-tool example vrp_capacity.py. I added the changed code below.
from functools import partial
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, 548, 776, 696, 582, 274, 502, 194, 308, 194, 536, 502, 388, 354,
468, 776, 662
],
[
548, 0, 684, 308, 194, 502, 730, 354, 696, 742, 1084, 594, 480, 674,
1016, 868, 1210
],
[
776, 684, 0, 992, 878, 502, 274, 810, 468, 742, 400, 1278, 1164,
1130, 788, 1552, 754
],
[
696, 308, 992, 0, 114, 650, 878, 502, 844, 890, 1232, 514, 628, 822,
1164, 560, 1358
],
[
582, 194, 878, 114, 0, 536, 764, 388, 730, 776, 1118, 400, 514, 708,
1050, 674, 1244
],
[
274, 502, 502, 650, 536, 0, 228, 308, 194, 240, 582, 776, 662, 628,
514, 1050, 708
],
[
502, 730, 274, 878, 764, 228, 0, 536, 194, 468, 354, 1004, 890, 856,
514, 1278, 480
],
[
194, 354, 810, 502, 388, 308, 536, 0, 342, 388, 730, 468, 354, 320,
662, 742, 856
],
[
308, 696, 468, 844, 730, 194, 194, 342, 0, 274, 388, 810, 696, 662,
320, 1084, 514
],
[
194, 742, 742, 890, 776, 240, 468, 388, 274, 0, 342, 536, 422, 388,
274, 810, 468
],
[
536, 1084, 400, 1232, 1118, 582, 354, 730, 388, 342, 0, 878, 764,
730, 388, 1152, 354
],
[
502, 594, 1278, 514, 400, 776, 1004, 468, 810, 536, 878, 0, 114,
308, 650, 274, 844
],
[
388, 480, 1164, 628, 514, 662, 890, 354, 696, 422, 764, 114, 0, 194,
536, 388, 730
],
[
354, 674, 1130, 822, 708, 628, 856, 320, 662, 388, 730, 308, 194, 0,
342, 422, 536
],
[
468, 1016, 788, 1164, 1050, 514, 514, 662, 320, 274, 388, 650, 536,
342, 0, 764, 194
],
[
776, 868, 1552, 560, 674, 1050, 1278, 742, 1084, 810, 1152, 274,
388, 422, 764, 0, 798
],
[
662, 1210, 754, 1358, 1244, 708, 480, 856, 514, 468, 354, 844, 730,
536, 194, 798, 0
],
]
data['num_locations'] = len(data['distance_matrix'])
data['time_windows'] = \
[(0, 0),
(75, 85), (75, 85),
(60, 70), (45, 55),
(0, 8), (50, 60),
(0, 10), (10, 20),
(0, 10), (75, 85),
(85, 95), (5, 15),
(15, 25), (10, 20),
(45, 55), (30, 40)]
data['demands'] = \
[0,
1, 1,
2, 4,
2, 4,
8, 8,
1, 2,
1, 2,
4, 4,
8, 8]
data['time_per_demand_unit'] = 5
data['num_vehicles'] = 4
data['vehicle_capacity'] = 15
data['vehicle_speed'] = 83
data['depot'] = 0
return data
def manhattan_distance(position_1, position_2):
return (
abs(position_1[0] - position_2[0]) + abs(position_1[1] - position_2[1]))
def create_demand_evaluator(data):
_demands = data['demands']
def demand_evaluator(manager, node):
return _demands[manager.IndexToNode(node)]
return demand_evaluator
def add_capacity_constraints(routing, data, demand_evaluator_index):
capacity = 'Capacity'
routing.AddDimension(
demand_evaluator_index,
0, # null capacity slack
data['vehicle_capacity'],
True, # start cumul to zero
capacity)
def create_time_evaluator(data):
def service_time(data, node):
return 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_index):
"""Add Global Span constraint"""
time = 'Time'
horizon = 120
routing.AddDimension(
time_evaluator_index,
horizon, # allow waiting time
horizon, # 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)))
def print_solution(manager, routing, assignment): # pylint:disable=too-many-locals
"""Prints assignment on console"""
print(f'Objective: {assignment.ObjectiveValue()}')
time_dimension = routing.GetDimensionOrDie('Time')
capacity_dimension = routing.GetDimensionOrDie('Capacity')
total_distance = 0
total_load = 0
total_time = 0
for vehicle_id in range(manager.GetNumberOfVehicles()):
index = routing.Start(vehicle_id)
plan_output = 'Route for vehicle {}:\n'.format(vehicle_id)
distance = 0
while not routing.IsEnd(index):
load_var = capacity_dimension.CumulVar(index)
time_var = time_dimension.CumulVar(index)
slack_var = time_dimension.SlackVar(index)
plan_output += ' {0} Load({1}) Time({2},{3}) Slack({4},{5}) ->'.format(
manager.IndexToNode(index),
assignment.Value(load_var),
assignment.Min(time_var),
assignment.Max(time_var),
assignment.Min(slack_var), assignment.Max(slack_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)
slack_var = time_dimension.SlackVar(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 += 'Distance of the route: {0}m\n'.format(distance)
plan_output += 'Load of the route: {}\n'.format(
assignment.Value(load_var))
plan_output += 'Time of the route: {}\n'.format(
assignment.Value(time_var))
print(plan_output)
total_distance += distance
total_load += assignment.Value(load_var)
total_time += assignment.Value(time_var)
print('Total Distance of all routes: {0}m'.format(total_distance))
print('Total Load of all routes: {}'.format(total_load))
print('Total Time of all routes: {0}min'.format(total_time))
# [END solution_printer]
def main():
"""Solve the Capacitated VRP with time windows."""
# Instantiate the data problem.
# [START data]
data = create_data_model()
# [END data]
# Create the routing index manager.
# [START index_manager]
manager = pywrapcp.RoutingIndexManager(data['num_locations'],
data['num_vehicles'], data['depot'])
# [END index_manager]
# Create Routing Model.
# [START routing_model]
routing = pywrapcp.RoutingModel(manager)
# [END routing_model]
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)
# [END transit_callback]
# Define cost of each arc.
# [START arc_cost]
routing.SetArcCostEvaluatorOfAllVehicles(transit_callback_index)
# Add Capacity constraint.
# [START capacity_constraint]
demand_evaluator_index = routing.RegisterUnaryTransitCallback(
partial(create_demand_evaluator(data), manager))
add_capacity_constraints(routing, data, demand_evaluator_index)
# [END capacity_constraint]
# Add Time Window constraint.
# [START time_constraint]
time_evaluator_index = routing.RegisterTransitCallback(
partial(create_time_evaluator(data), manager))
add_time_window_constraints(routing, manager, data, time_evaluator_index)
# [END time_constraint]
# Setting first solution heuristic (cheapest addition).
# [START parameters]
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(2)
search_parameters.log_search = True
# [END parameters]
# Solve the problem.
# [START solve]
solution = routing.SolveWithParameters(search_parameters)
# [END solve]
# Print solution on console.
# [START print_solution]
if solution:
print_solution(manager, routing, solution)
else:
print('No solution found!')
# [END print_solution]
if __name__ == '__main__':
main()
I changed,
- Replace _locations array with distance matrix array
- Remove distance_evaluator_index and add distance callback.
Now I need to modify create_time_evaluator according to the distance matrix and apply it to time_evaluator_index. How can I achieve this and get better results?
I need to achieve Capacity constraints and time window constraints using distance matrix as a single solution with minimum cost. Is this the better way for my problem?
python
or-tools
vehicle-routing
0 Answers
Your Answer