|
#!/usr/bin/env python3 |
|
"""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 = {} |
|
# 1 Depot: S, 10 Deliveries D0-D9, 6 Pickups P0-P5 |
|
# 17 nodes |
|
data['distance_matrix'] = [ |
|
[ #S D0 D1 D2 D3 D4 D5 D6 D7 D8 D9 P0 P1 P2 P3 P4 P5 |
|
0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 |
|
], |
|
[ |
|
1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 |
|
], |
|
[ |
|
1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 |
|
], |
|
[ |
|
1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 |
|
], |
|
[ |
|
1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 |
|
], |
|
[ |
|
1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 |
|
], |
|
[ |
|
1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 |
|
], |
|
[ |
|
1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1 |
|
], |
|
[ |
|
1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1 |
|
], |
|
[ |
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1 |
|
], |
|
[ |
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1 |
|
], |
|
[ |
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1 |
|
], |
|
[ |
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1 |
|
], |
|
[ |
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1 |
|
], |
|
[ |
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1 |
|
], |
|
[ |
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1 |
|
], |
|
[ |
|
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0 |
|
], |
|
] |
|
# [S D0 D1 D2 D3 D4 D5 D6 D7 D8 D9 P0 P1 P2 P3 P4 P5 |
|
data['deliveries'] = [0, -1,-1,-1,-1,-1,-1,-1,-1,-1,-1, 0, 0, 0, 0, 0, 0] |
|
data['loads'] = [0, -1,-1,-1,-1,-1,-1,-1,-1,-1,-1,+1,+1,+1,+1,+1,+1] |
|
data['vehicle_capacities'] = [5, 5] |
|
data['num_vehicles'] = 2 |
|
data['depot'] = 0 |
|
return data |
|
|
|
|
|
def print_solution(data, manager, routing, solution): |
|
"""Prints solution on console.""" |
|
total_distance = 0 |
|
total_load = 0 |
|
deliveries_dimension = routing.GetDimensionOrDie('Deliveries') |
|
loads_dimension = routing.GetDimensionOrDie('Loads') |
|
for vehicle_id in range(data['num_vehicles']): |
|
index = routing.Start(vehicle_id) |
|
delivery_var = deliveries_dimension.CumulVar(index) |
|
load_var = loads_dimension.CumulVar(index) |
|
plan_output = 'Route for vehicle {}:\n'.format(vehicle_id) |
|
route_distance = 0 |
|
route_deliveries = solution.Value(delivery_var) |
|
route_load = solution.Value(load_var) |
|
while not routing.IsEnd(index): |
|
node_index = manager.IndexToNode(index) |
|
delivery_var = deliveries_dimension.CumulVar(index) |
|
load_var = loads_dimension.CumulVar(index) |
|
route_deliveries = solution.Value(delivery_var) |
|
route_load = solution.Value(load_var) |
|
previous_index = index |
|
index = solution.Value(routing.NextVar(index)) |
|
route_distance += routing.GetArcCostForVehicle( |
|
previous_index, index, vehicle_id) |
|
plan_output += ' {0} Deliveries({1}) Load({2}) ->'.format(node_index, route_deliveries,route_load) |
|
delivery_var = deliveries_dimension.CumulVar(index) |
|
load_var = loads_dimension.CumulVar(index) |
|
route_deliveries = solution.Value(delivery_var) |
|
route_load = solution.Value(load_var) |
|
plan_output += ' {0} Deliveries({1}) Load({2})\n'.format( |
|
manager.IndexToNode(index), |
|
route_deliveries, |
|
route_load) |
|
plan_output += 'Distance of the route: {}m\n'.format(route_distance) |
|
plan_output += 'Deliveries of the route: {}\n'.format(route_deliveries) |
|
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 Deliveries constraint. |
|
def delivery_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['deliveries'][from_node] |
|
delivery_callback_index = routing.RegisterUnaryTransitCallback(delivery_callback) |
|
routing.AddDimensionWithVehicleCapacity( |
|
delivery_callback_index, |
|
0, # null capacity slack |
|
data['vehicle_capacities'], # vehicle maximum capacities |
|
False, # start cumul to zero |
|
'Deliveries') |
|
|
|
# Add Load constraint. |
|
def load_callback(from_index): |
|
"""Returns the load of the node.""" |
|
# Convert from routing variable Index to demands NodeIndex. |
|
from_node = manager.IndexToNode(from_index) |
|
return data['loads'][from_node] |
|
load_callback_index = routing.RegisterUnaryTransitCallback(load_callback) |
|
routing.AddDimensionWithVehicleCapacity( |
|
load_callback_index, |
|
0, # null capacity slack |
|
data['vehicle_capacities'], # vehicle maximum capacities |
|
False, # start cumul to zero |
|
'Loads') |
|
|
|
# Add Constraint Both cumulVar are identical at start |
|
deliveries_dimension = routing.GetDimensionOrDie('Deliveries') |
|
loads_dimension = routing.GetDimensionOrDie('Loads') |
|
for vehicle_id in range(data['num_vehicles']): |
|
index = routing.Start(vehicle_id) |
|
routing.solver().Add( |
|
deliveries_dimension.CumulVar(index) == loads_dimension.CumulVar(index)) |
|
|
|
# Setting first solution heuristic. |
|
search_parameters = pywrapcp.DefaultRoutingSearchParameters() |
|
search_parameters.log_search = True |
|
#search_parameters.first_solution_strategy = ( |
|
# routing_enums_pb2.FirstSolutionStrategy.AUTOMATIC) |
|
search_parameters.first_solution_strategy = ( |
|
routing_enums_pb2.FirstSolutionStrategy.GLOBAL_CHEAPEST_ARC) |
|
#search_parameters.first_solution_strategy = ( |
|
# routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC) |
|
#search_parameters.first_solution_strategy = ( |
|
# routing_enums_pb2.FirstSolutionStrategy.PARALLEL_CHEAPEST_INSERTION) |
|
#search_parameters.first_solution_strategy = ( |
|
# routing_enums_pb2.FirstSolutionStrategy.LOCAL_CHEAPEST_INSERTION) |
|
|
|
|
|
# Solve the problem. |
|
solution = routing.SolveWithParameters(search_parameters) |
|
|
|
# Print solution on console. |
|
if solution: |
|
print_solution(data, manager, routing, solution) |
|
|
|
|
|
if __name__ == '__main__': |
|
main() |
Hi @Mizux ,
I have a couple of questions if you don't mind me asking:
What does
[5, 5]
stands for invehicle_capacities
? one of them will prob. refer to items but what about the other one?What does
0, # null capacity slack
refer to?Is there a way to add time limits for each pickup? ie. The truck is on pickup
P0
, the driver has 15 mins to pick up all items.Is there a way to add time limits for each delivery? ie. The truck is delivering the item, the driver has <5 mins to accomplish that.
The current example assumes we have 10 deliveries and 6 pickups.
I have this scenario: Load P0 has 10 items and Load P1 has 8. The driver has to deliver these items to 18 different locations. The driver grabs all items on P0 and starts his route. When the driver is closer to P1, he goes there and starts picking up all items on P1. After that, he proceeds with his route. Now, he's delivering items from P0 and P1. Can I handle this scenario with this implementation?
Regards,