Skip to content

Instantly share code, notes, and snippets.

@Mizux
Last active November 22, 2022 18:33
Show Gist options
  • Save Mizux/a18d4103cd165bed4f0e63f5d670e80c to your computer and use it in GitHub Desktop.
Save Mizux/a18d4103cd165bed4f0e63f5d670e80c to your computer and use it in GitHub Desktop.
VRP with Collects and Deliveries
#!/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()

possible output:

[0]─[~/work/tmp]
[^v^]─mizux@nuc10i7 %./vrp_collect_deliveries.py
Objective: 70
Route for vehicle 0:
 0 Delivery(0) Load(0) ->  0 Delivery(0) Load(0)
Distance of the route: 0m

Route for vehicle 1:
 0 Delivery(20) Load(20) ->  3 Delivery(20) Load(20) ->  1 Delivery(0) Load(0) ->  0 Delivery(0) Load(15)
Distance of the route: 30m

Route for vehicle 2:
 0 Delivery(13) Load(13) ->  2 Delivery(13) Load(13) ->  5 Delivery(0) Load(0) ->  4 Delivery(0) Load(15) ->  0 Delivery(0) Load(25)
Distance of the route: 40m

Total Distance of all routes: 70m
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment