|
#!/usr/bin/env python3 |
|
"""Capacitated Vehicle Routing Problem (CVRP).""" |
|
|
|
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 = {} |
|
data['demands'] = \ |
|
[0, # depot |
|
15, # Pickup |
|
-13, # Delivery |
|
-20, # Delivery |
|
10, # Pickup |
|
15] # Pickup |
|
data['num_vehicles'] = 3 |
|
data['vehicle_capacities'] = [30, 20, 30] |
|
data['depot'] = 0 |
|
return data |
|
|
|
########### |
|
# Printer # |
|
########### |
|
def print_solution(data, manager, routing, solution): # pylint:disable=too-many-locals |
|
"""Prints solution on console""" |
|
print('Objective: {}'.format(solution.ObjectiveValue())) |
|
total_distance = 0 |
|
delivery_dimension = routing.GetDimensionOrDie('Delivery') |
|
load_dimension = routing.GetDimensionOrDie('Load') |
|
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): |
|
delivery_var = delivery_dimension.CumulVar(index) |
|
load_var = load_dimension.CumulVar(index) |
|
plan_output += f' {manager.IndexToNode(index)} Delivery({solution.Value(delivery_var)}) Load({solution.Value(load_var)}) -> ' |
|
previous_index = index |
|
index = solution.Value(routing.NextVar(index)) |
|
distance += routing.GetArcCostForVehicle(previous_index, index, vehicle_id) |
|
delivery_var = delivery_dimension.CumulVar(index) |
|
load_var = load_dimension.CumulVar(index) |
|
plan_output += f' {manager.IndexToNode(index)} Delivery({solution.Value(delivery_var)}) Load({solution.Value(load_var)})\n' |
|
plan_output += 'Distance of the route: {}m\n'.format(distance) |
|
print(plan_output) |
|
total_distance += distance |
|
print('Total Distance of all routes: {}m'.format(total_distance)) |
|
|
|
|
|
######## |
|
# Main # |
|
######## |
|
def main(): |
|
"""Entry point of the program""" |
|
# Instantiate the data problem. |
|
data = create_data_model() |
|
|
|
# Create the routing index manager |
|
manager = pywrapcp.RoutingIndexManager( |
|
len(data['demands']), |
|
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 10 |
|
|
|
transit_callback_index = routing.RegisterTransitCallback(distance_callback) |
|
|
|
# Define cost of each arc. |
|
routing.SetArcCostEvaluatorOfAllVehicles(transit_callback_index) |
|
|
|
# Add Delivery dimension. |
|
def delivery_callback(from_index): |
|
"""Returns the delivery of the node.""" |
|
# Convert from routing variable Index to demands NodeIndex. |
|
from_node = manager.IndexToNode(from_index) |
|
value = data['demands'][from_node] |
|
return value if value < 0 else 0 |
|
|
|
delivery_callback_index = routing.RegisterUnaryTransitCallback(delivery_callback) |
|
routing.AddDimensionWithVehicleCapacity( |
|
delivery_callback_index, |
|
0, # null capacity slack |
|
data['vehicle_capacities'], # vehicle maximum capacities |
|
False, # don't start cumul to zero |
|
'Delivery') |
|
|
|
# Add Load dimension. |
|
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 |
|
False, # don't start cumul to zero |
|
'Load') |
|
|
|
# Force both dimension to start in sync |
|
delivery_dimension = routing.GetDimensionOrDie('Delivery') |
|
load_dimension = routing.GetDimensionOrDie('Load') |
|
for vehicle_id in range(manager.GetNumberOfVehicles()): |
|
index = routing.Start(vehicle_id) |
|
routing.solver().Add(delivery_dimension.CumulVar(index) == load_dimension.CumulVar(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(1) |
|
|
|
# Solve the problem. |
|
solution = routing.SolveWithParameters(search_parameters) |
|
print_solution(data, manager, routing, solution) |
|
|
|
|
|
if __name__ == '__main__': |
|
main() |