Problem understanding Pickups & delivery array using OR-TOOLS - python

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[...][][])

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()

How to test function that has more than outputs lines

I want to test a function that has more than outputs lines.
What I want to test is the if of the function. If the condition happens, it will print a string and it will return a key and a value of a dict.
The function is this:
def targe_region(self, bed, chro, position):
"""
Tool 1: Look is position is covered by the Bed file input.
"""
for key, value in bed.items():
if key == chro:
for values in value:
if int(position) in range(values[0], values[1]):
print("Your position is covered by this bed file")
return print(key, values)
return "Your position is not covered by this bed file"
This is part of a big script and the input of this has been added with the test code I show below.
def test_target_region():
"""
chr20 3 4 Neg2 0 - 127477031 127478198 0,0,255
chrX 1 50 Neg3 0 - 127478198 127479365 0,0,255
chrX 50 100 Pos5 0 + 127479365 127480532 255,0,0
"""
BED = Read_file("./test_bed_file_with_errors4.bed")
data_loaded = BED.load_data()
bed_data = Bed_tools(data_loaded)
user_input2 = "chrX:55"
chro = user_input2.split(":")[0]
position = user_input2.split(":")[1]
assert (
bed_data.targe_region(data_loaded, chro, position)
== """Your position is coveraged by this bed file
chrX [50, 100]"""
)
The output is this:
FAILURES =====================================================
______________________________________________________________
test_target_region
______________________________________________________________
def test_target_region():
BED = Read_file("./test_bed_file_with_errors4.bed")
data_loaded = BED.load_data()
bed_data = Bed_tools(data_loaded)
user_input2 = "chrX:55"
chro = user_input2.split(":")[0]
position = user_input2.split(":")[1]
> assert bed_data.targe_region(data_loaded, chro, position) == hola
E AssertionError: assert None == 'Your position is coveraged by this bed file\nchrX [50, 100]'
E + where None = <bound method Bed_tools.targe_region of <BED_toolkit.Class.bed_class.Bed_tools object at 0x7f9c713c0280>>({'chr1': [[1, 2], [3, 4], [5, 6], [7, 8]], 'chr20': [[1, 2], [3, 4]], 'chrX': [[1, 50], [50, 100]]}, 'chrX', '55')
E + where <bound method Bed_tools.targe_region of <BED_toolkit.Class.bed_class.Bed_tools object at 0x7f9c713c0280>> = <BED_toolkit.Class.bed_class.Bed_tools object at 0x7f9c713c0280>.targe_region
test.py:66: AssertionError
------------------------------------------------------------- Captured stdout call -------------------------------------------------------------
Your position is coveraged by this bed file
chrX [50, 100]
=========================================================== short test summary info ============================================================
FAILED test.py::test_target_region - AssertionError: assert None == 'Your position is coveraged by this bed file\nchrX [50, 100]'
What can I do to match the Captured stdout call with my test
Pytest provides different fixures that allow you to capture the output of a test function.
So, assuming your current test triggers the if statement, you could take advantage of the capfd fixture like this:
def test_target_region(capfd):
"""
chr20 3 4 Neg2 0 - 127477031 127478198 0,0,255
chrX 1 50 Neg3 0 - 127478198 127479365 0,0,255
chrX 50 100 Pos5 0 + 127479365 127480532 255,0,0
"""
BED = Read_file("./test_bed_file_with_errors4.bed")
data_loaded = BED.load_data()
bed_data = Bed_tools(data_loaded)
user_input2 = "chrX:55"
chro = user_input2.split(":")[0]
position = user_input2.split(":")[1]
captured = capfd.readouterr()
bed_data.targe_region(data_loaded, chro, position)
assert (
captured.out
== """Your position is coveraged by this bed file
chrX [50, 100]"""
)

How to add a time dimension for each node in Google OR-Tools

I am solving the multi-depot multiple vehicles vehicle routing problem using OR-Tools and so far I have managed to set up a model in which one vehicle leaves from each depot and between them they visit each node within their maximum time allowance and then exit via the dummy node 0. However, as this is a real-world problem, at each node the vehicle is required to stop for a set time to carry out a service, with each node having a set waiting time, such as in the table below:
Node
Duration
4
9
38
8
...
...
8545
17
I am unsure how to add these to each node, although the arc lengths between nodes so far represent the travel time between each node (with the assumption that all vehicles are travelling at the same speed). So my question is how I can add these wait times into the model, while still minimizing the travel time for each vehicle and keeping them below their maximum threshold of 25200 seconds (corresponding to a 7-hour shift). I have included my working code below:
from ortools.constraint_solver import routing_enums_pb2
from ortools.constraint_solver import pywrapcp
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
The distance matrix was fed in from a csv laid out like so:
Blank
4
38
71
90
94
...
8545
4
0
1280
1762
1406
1589
...
1017
38
1280
0
681
202
385
...
1433
71
1762
681
0
503
0
...
0
90
1406
202
503
0
0
...
1559
94
1589
385
0
0
0
...
1742
..
..
...
...
...
...
0
...
8545
1017
1433
0
1559
1742
...
0
CareDist_Matrix = np.loadtxt(open("CareDistances-FULL.csv", "rb"), dtype=int, delimiter=",", skiprows=1)
CareDist_Matrix
And the main body of the model follows:
def create_data_model():
"""Stores the data for the problem."""
data = {}
data['distance_matrix'] = CareDist_Matrix
data['num_vehicles'] = 6
data['starts'] = [3, 7, 14, 104, 185, 220]
data['ends'] = [0, 0, 0, 0, 0, 0]
return data
def print_solution(data, manager, routing, solution):
"""Prints solution on console."""
max_route_distance = 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
while not routing.IsEnd(index):
plan_output += ' {} -> '.format(manager.IndexToNode(index))
previous_index = index
index = solution.Value(routing.NextVar(index))
route_distance += routing.GetArcCostForVehicle(
previous_index, index, vehicle_id)
plan_output += '{}\n'.format(manager.IndexToNode(index))
plan_output += 'Distance of the route: {}m\n'.format(route_distance)
print(plan_output)
max_route_distance = max(route_distance, max_route_distance)
print('Maximum of the route distances: {}m'.format(max_route_distance))
"""Entry point of the program."""
# 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)
# 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 Distance constraint.
dimension_name = 'Distance'
routing.AddDimension(
transit_callback_index,
0, # no slack
25200, # vehicle maximum travel distance
True, # start cumul to zero
dimension_name)
distance_dimension = routing.GetDimensionOrDie(dimension_name)
distance_dimension.SetGlobalSpanCostCoefficient(100)
# Setting first solution heuristic.
search_parameters = pywrapcp.DefaultRoutingSearchParameters()
search_parameters.first_solution_strategy = (
routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC)
# Solve the problem.
solution = routing.SolveWithParameters(search_parameters)
# Print solution on console.
if solution:
print_solution(data, manager, routing, solution)
if __name__ == '__main__':
main()
You can simply add the duration to the travel in the callback function. So, total time to visit a node is the travel time to that node plus the action time in that node:
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] + duration['to_index']

OR-TOOLS: PCVRP with delivery patterns

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)

How to use for loop inside a capacitated vehicle routing problem (Google OR tools)

I want to find the shortest route distance and vehicle load carrying capacity a vehicle can have. It is a capacitated vehicle routing problem (Google OR tools). I want to use a for- loop in between the code so that it gives the distance_optimization for all the district which gets saved in one location.
for example : out_1 = get_sol( "banswara",1) shows the route for the fos (vehicle) for just "banswara" district. I want to write a for-loop so that output shows the same result for all the districts.
my code:
dist = DistanceMetric.get_metric('haversine')
def create_data_model(df_user,no_fos):
"""Stores the data for the problem."""
data = {}
data['distance_matrix'] = dist.pairwise(df_user [['lat','lon']].to_numpy())*6373
data['num_vehicles'] = no_fos
data['depot'] = 0
return data
def print_solution(data, manager, routing, solution):
"""Prints solution on console."""
max_route_distance = 0
for vehicle_id in range(data['num_vehicles']):
index = routing.Start(vehicle_id)
plan_output = 'Route for FOS {}:\n'.format(vehicle_id)
route_distance = 0
while not routing.IsEnd(index):
plan_output += ' {} -> '.format(manager.IndexToNode(index))
previous_index = index
index = solution.Value(routing.NextVar(index))
route_distance += routing.GetArcCostForVehicle(
previous_index, index, vehicle_id)
plan_output += '{}\n'.format(manager.IndexToNode(index))
plan_output += 'Distance of the route: {}m\n'.format(route_distance)
print(plan_output)
max_route_distance = max(route_distance, max_route_distance)
print('Maximum of the route distances: {}Km'.format(max_route_distance))
def distance(x,y,data):
dis = data['distance_matrix'][x][y]
return dis
def get_routes(solution, routing, manager,df_user,data):
"""Get vehicle routes from a solution and store them in an array."""
# Get vehicle routes and store them in a two dimensional array whose
# i,j entry is the jth location visited by vehicle i along its route.
routes = []
#routes_dist = []
for route_nbr in range(routing.vehicles()):
index = routing.Start(route_nbr)
route = [manager.IndexToNode(index)]
while not routing.IsEnd(index):
index = solution.Value(routing.NextVar(index))
route.append(manager.IndexToNode(index))
routes.append(route)
#routes = get_routes(solution, routing, manager)
routes_t = pd.DataFrame(routes).T
col_to_iter = routes_t.columns
routes_t['route_info'] = routes_t.index
routes_t = pd.melt(routes_t, id_vars=['route_info'], value_vars=col_to_iter)
routes_t = routes_t.drop_duplicates(subset='value', keep="first")
df_user['value'] = df_user.index
df_user_out = pd.merge(df_user, routes_t, on="value")
df_user_out = df_user_out.sort_values(by=['variable','route_info'])
df_user_out['route_lag'] = df_user_out.groupby('variable')['value'].shift(-1).fillna(0)
df_user_out['route_lag'] = df_user_out['route_lag'].astype(np.int64)
df_user_out['route_info'] = df_user_out['route_info'].astype(np.int64)
df_user_out['dist'] = df_user_out.apply(lambda row: distance(row['route_lag'], row['value'],data), axis=1)
return df_user_out
def get_sol(sub_dist_fil,fos_cnt):
df_user_org_sub = df_user_org[(df_user_org.sub_district == sub_dist_fil) ]
df_user_org_sub.reset_index( inplace=True,drop=True)
fos_cnt=fos_cnt
data = create_data_model(df_user_org_sub,fos_cnt)
# 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 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)
# Setting first solution heuristic.
search_parameters = pywrapcp.DefaultRoutingSearchParameters()
search_parameters.first_solution_strategy = (
routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC)
# Solve the problem.
solution = routing.SolveWithParameters(search_parameters)
out_df = get_routes(solution, routing, manager,df_user_org_sub,data)
# Print solution on console.
if solution:
print_solution(data, manager, routing, solution)
return out_df
out_1 = get_sol( "banswara",1)
The output is as follows:
Route for FOS 0:
0 -> 14 -> 1 -> 9 -> 8 -> 7 -> 10 -> 11 -> 16 -> 13 -> 15 -> 12 -> 4 -> 3 -> 2 -> 5 -> 6 -> 0
Distance of the route: 217m
Maximum of the route distances: 217Km
So, the above output is just for banswara district, but i want the same result for all the district by writing a for loop in between.

Categories