├── CVRPTW.py ├── Data.xlsx ├── Distance.py ├── README.md └── VRP.py /CVRPTW.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # This Python file uses the following encoding: utf-8 3 | # Copyright 2015 Tin Arm Engineering AB 4 | # Copyright 2018 Google LLC 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | """Capacitated Vehicle Routing Problem with Time Windows (CVRPTW). 17 | This is a sample using the routing library python wrapper to solve a CVRPTW 18 | problem. 19 | A description of the problem can be found here: 20 | http://en.wikipedia.org/wiki/Vehicle_routing_problem. 21 | Distances are in meters and time in minutes. 22 | """ 23 | 24 | from __future__ import print_function 25 | 26 | from functools import partial 27 | from six.moves import xrange 28 | 29 | from ortools.constraint_solver import pywrapcp 30 | from ortools.constraint_solver import routing_enums_pb2 31 | from scipy.spatial import distance_matrix 32 | import math 33 | from math import pow 34 | import pandas as pd 35 | df=pd.read_excel(r'/Users/MuditPaliwal/Desktop/Data.xlsx') 36 | 37 | print(df) 38 | 39 | x = df.iloc[:,1] 40 | y = df.iloc[:,2] 41 | d = df.iloc[:,3] 42 | data = [(f,b) for(f,b) in zip(x,y)] 43 | ctys = range(51) 44 | df = pd.DataFrame(data, columns=['xcord', 'ycord'], index=ctys) 45 | dist = pd.DataFrame(distance_matrix(df.values, df.values), index=df.index, columns=df.index) 46 | 47 | 48 | ########################### 49 | # Problem Data Definition # 50 | ########################### 51 | def create_data_model(): 52 | """Stores the data for the problem""" 53 | data = {} 54 | # Locations in block unit 55 | _locations = [(0,0), (-8.2, -96.68), (-16.01, 14.95), (-1.26, 82.05), (-93.17, 78.92), (-57.1, 18.67), (89.57, 13.12), (-99.8, 27.28), (0.79, -33.16), (78.95, 78.61), (80.92, -39.92), (-95.76, 79.96), (-31.53, 54.57), (-18.47, -31.8), (-96.29, -53.0), (85.06, -45.54), (37.84, 75.75), (-2.38, -40.04), (-37.7, 25.98), (-74.47, 74.73), (85.66, 88.39), (-97.99, 67.34), (-66.24, 47.94), (49.66, -9.73), (66.59, -84.24), (9.82, 22.22), (38.45, -6.1), (-24.67, -77.81), (5.55, 88.13), (85.59, 14.86), (89.28, 89.99), (-47.88, 76.3), (19.9, -47.07), (11.24, 62.22), (16.26, 88.32), (79.24, -44.43), (-99.97, 27.93), (1.01, -60.47), (-51.98, -42.88), (8.71, -23.44), (-82.64, 87.2), (30.8, -8.29), (6.28, 44.43), (25.02, 78.73), (31.69, 62.16), (-36.28, -25.46), (45.35, 77.33), (67.7, -89.31), (-2.42, 71.68), (10.45, 66.92), (-21.02, 30.19)] 56 | # Compute locations in meters using the block dimension defined as follow 57 | # Manhattan average block: 750ft x 264ft -> 228m x 80m 58 | # here we use: 114m x 80m city block 59 | # src: https://nyti.ms/2GDoRIe "NY Times: Know Your distance" 60 | data['locations'] = [(l[0], l[1]) for l in _locations] 61 | data['num_locations'] = len(data['locations']) 62 | data['time_windows'] =[(0,0), (222, 234), (1378, 1433), (1115, 1136), (1289, 1312), (656, 698), (221, 253), (1086, 1105), (560, 627), (1084, 1143), (1168, 1187), (337, 340), (756, 809), (1316, 1328), (124, 189), (1286, 1313), (154, 221), (21, 78), (834, 853), (735, 816), (386, 425), (1277, 1341), (1202, 1262), (1269, 1336), (1238, 1251), (1045, 1057), (99, 153), (380, 400), (569, 644), (933, 985), (401, 429), (1027, 1073), (959, 1019), (558, 626), (1245, 1269), (603, 642), (1088, 1144), (127, 172), (764, 785), (473, 517), (875, 890), (597, 641), (460, 484), (23, 97), (403, 463), (299, 348), (1018, 1093), (1078, 1120), (1369, 1396), (487, 503), (1133, 1170)] 63 | data['demands'] = [(0), (10), (13), (22), (50), (6), (13), (3), (43), (8), (8), (10), (13), (25), (18), (39), (30), (16), (41), (43), (28), (40), (19), (26), (27), (45), (30), (10), (15), (44), (13), (33), (48), (13), (5), (6), (8), (23), (35), (35), (42), (27), (41), (33), (26), (38), (14), (28), (16), (30), (14)] 64 | data['time_per_demand_unit'] = [(0), (2), (3), (5), (10), (2), (3), (1), (9), (2), (2), (3), (3), (5), (4), (8), (7), (4), (9), (9), (6), (9), (4), (6), (6), (9), (7), (2), (3), (9), (3), (7), (10), (3), (1), (2), (2), (5), (8), (7), (9), (6), (9), (7), (6), (8), (3), (6), (4), (7), (3)] 65 | data['num_vehicles'] = 20 66 | data['vehicle_capacity'] = 80 67 | data['depot'] = 0 68 | return data 69 | 70 | 71 | ####################### 72 | # Problem Constraints # 73 | ####################### 74 | def euclidean_distance(position_1, position_2): 75 | """Computes the Euclidean distance between two points""" 76 | return ( 77 | abs(math.sqrt((pow(position_1[0] - position_2[0],2))+(pow(position_1[1] - position_2[1],2)))) 78 | ) 79 | 80 | 81 | def create_distance_evaluator(data): 82 | """Creates callback to return distance between points.""" 83 | _distances = {} 84 | # precompute distance between location to have distance callback in O(1) 85 | for from_node in xrange(data['num_locations']): 86 | _distances[from_node] = {} 87 | for to_node in xrange(data['num_locations']): 88 | if from_node == to_node: 89 | _distances[from_node][to_node] = 0 90 | else: 91 | _distances[from_node][to_node] = (euclidean_distance( 92 | data['locations'][from_node], data['locations'][to_node])) 93 | 94 | def distance_evaluator(manager, from_node, to_node): 95 | """Returns the manhattan distance between the two nodes""" 96 | return _distances[manager.IndexToNode(from_node)][manager.IndexToNode( 97 | to_node)] 98 | 99 | return distance_evaluator 100 | 101 | 102 | def create_demand_evaluator(data): 103 | """Creates callback to get demands at each location.""" 104 | _demands = data['demands'] 105 | 106 | def demand_evaluator(manager, node): 107 | """Returns the demand of the current node""" 108 | return _demands[manager.IndexToNode(node)] 109 | 110 | return demand_evaluator 111 | 112 | 113 | def add_capacity_constraints(routing, data, demand_evaluator_index): 114 | """Adds capacity constraint""" 115 | capacity = 'Capacity' 116 | routing.AddDimension( 117 | demand_evaluator_index, 118 | 0, # null capacity slack 119 | data['vehicle_capacity'], 120 | True, # start cumul to zero 121 | capacity) 122 | 123 | 124 | def create_time_evaluator(data): 125 | """Creates callback to get total times between locations.""" 126 | 127 | def service_time(data, node): 128 | """Gets the service time for the specified location.""" 129 | return data['time_per_demand_unit'][node] 130 | def travel_time(data, from_node, to_node): 131 | """Gets the travel times between two locations.""" 132 | if from_node == to_node: 133 | travel_time = 0 134 | else: 135 | travel_time = euclidean_distance(data['locations'][from_node], data[ 136 | 'locations'][to_node]) 137 | return travel_time 138 | 139 | _total_time = {} 140 | # precompute total time to have time callback in O(1) 141 | for from_node in xrange(data['num_locations']): 142 | _total_time[from_node] = {} 143 | for to_node in xrange(data['num_locations']): 144 | if from_node == to_node: 145 | _total_time[from_node][to_node] = 0 146 | else: 147 | _total_time[from_node][to_node] = int( 148 | service_time(data, from_node) + travel_time( 149 | data, from_node, to_node)) 150 | 151 | def time_evaluator(manager, from_node, to_node): 152 | """Returns the total time between the two nodes""" 153 | return _total_time[manager.IndexToNode(from_node)][manager.IndexToNode( 154 | to_node)] 155 | 156 | return time_evaluator 157 | 158 | 159 | def add_time_window_constraints(routing, manager, data, time_evaluator_index): 160 | """Add Global Span constraint""" 161 | time = 'Time' 162 | horizon = 1500 163 | routing.AddDimension( 164 | time_evaluator_index, 165 | horizon, # allow waiting time 166 | horizon, # maximum time per vehicle 167 | False, # don't force start cumul to zero since we are giving TW to start nodes 168 | time) 169 | time_dimension = routing.GetDimensionOrDie(time) 170 | # Add time window constraints for each location except depot 171 | # and 'copy' the slack var in the solution object (aka Assignment) to print it 172 | for location_idx, time_window in enumerate(data['time_windows']): 173 | if location_idx == 0: 174 | continue 175 | index = manager.NodeToIndex(location_idx) 176 | time_dimension.CumulVar(index).SetRange(time_window[0], time_window[1]) 177 | routing.AddToAssignment(time_dimension.SlackVar(index)) 178 | # Add time window constraints for each vehicle start node 179 | # and 'copy' the slack var in the solution object (aka Assignment) to print it 180 | for vehicle_id in xrange(data['num_vehicles']): 181 | index = routing.Start(vehicle_id) 182 | time_dimension.CumulVar(index).SetRange(data['time_windows'][0][0], 183 | data['time_windows'][0][1]) 184 | routing.AddToAssignment(time_dimension.SlackVar(index)) 185 | # Warning: Slack var is not defined for vehicle's end node 186 | #routing.AddToAssignment(time_dimension.SlackVar(self.routing.End(vehicle_id))) 187 | 188 | 189 | ########### 190 | # Printer # 191 | ########### 192 | def print_solution(data, manager, routing, assignment): # pylint:disable=too-many-locals 193 | """Prints assignment on console""" 194 | print('Objective: {}'.format(assignment.ObjectiveValue())) 195 | total_distance = 0 196 | total_load = 0 197 | total_time = 0 198 | capacity_dimension = routing.GetDimensionOrDie('Capacity') 199 | time_dimension = routing.GetDimensionOrDie('Time') 200 | for vehicle_id in xrange(data['num_vehicles']): 201 | index = routing.Start(vehicle_id) 202 | plan_output = 'Route for vehicle {}:\n'.format(vehicle_id) 203 | distance = 0 204 | while not routing.IsEnd(index): 205 | load_var = capacity_dimension.CumulVar(index) 206 | time_var = time_dimension.CumulVar(index) 207 | slack_var = time_dimension.SlackVar(index) 208 | plan_output += ' {0} Load({1}) Time({2},{3}) Slack({4},{5}) ->'.format( 209 | manager.IndexToNode(index), 210 | assignment.Value(load_var), 211 | assignment.Min(time_var), 212 | assignment.Max(time_var), 213 | assignment.Min(slack_var), assignment.Max(slack_var)) 214 | previous_index = index 215 | index = assignment.Value(routing.NextVar(index)) 216 | distance += routing.GetArcCostForVehicle(previous_index, index, 217 | vehicle_id) 218 | load_var = capacity_dimension.CumulVar(index) 219 | time_var = time_dimension.CumulVar(index) 220 | slack_var = time_dimension.SlackVar(index) 221 | plan_output += ' {0} Load({1}) Time({2},{3})\n'.format( 222 | manager.IndexToNode(index), 223 | assignment.Value(load_var), 224 | assignment.Min(time_var), assignment.Max(time_var)) 225 | plan_output += 'Distance of the route: {0}m\n'.format(distance) 226 | plan_output += 'Load of the route: {}\n'.format( 227 | assignment.Value(load_var)) 228 | plan_output += 'Time of the route: {}\n'.format( 229 | assignment.Value(time_var)) 230 | print(plan_output) 231 | total_distance += distance 232 | total_load += assignment.Value(load_var) 233 | total_time += assignment.Value(time_var) 234 | print('Total Distance of all routes: {0}m'.format(total_distance)) 235 | print('Total Load of all routes: {}'.format(total_load)) 236 | print('Total Time of all routes: {0}min'.format(total_time)) 237 | 238 | 239 | ######## 240 | # Main # 241 | ######## 242 | def main(): 243 | """Entry point of the program""" 244 | # Instantiate the data problem. 245 | data = create_data_model() 246 | 247 | # Create the routing index manager 248 | manager = pywrapcp.RoutingIndexManager(data['num_locations'], 249 | data['num_vehicles'], data['depot']) 250 | 251 | # Create Routing Model 252 | routing = pywrapcp.RoutingModel(manager) 253 | 254 | # Define weight of each edge 255 | distance_evaluator_index = routing.RegisterTransitCallback( 256 | partial(create_distance_evaluator(data), manager)) 257 | routing.SetArcCostEvaluatorOfAllVehicles(distance_evaluator_index) 258 | 259 | # Add Capacity constraint 260 | demand_evaluator_index = routing.RegisterUnaryTransitCallback( 261 | partial(create_demand_evaluator(data), manager)) 262 | add_capacity_constraints(routing, data, demand_evaluator_index) 263 | 264 | # Add Time Window constraint 265 | time_evaluator_index = routing.RegisterTransitCallback( 266 | partial(create_time_evaluator(data), manager)) 267 | add_time_window_constraints(routing, manager, data, time_evaluator_index) 268 | 269 | # Setting first solution heuristic (cheapest addition). 270 | search_parameters = pywrapcp.DefaultRoutingSearchParameters() 271 | search_parameters.local_search_metaheuristic = ( 272 | routing_enums_pb2.LocalSearchMetaheuristic.GUIDED_LOCAL_SEARCH) 273 | search_parameters.time_limit.seconds = 30 274 | search_parameters.log_search = True 275 | # Solve the problem. 276 | assignment = routing.SolveWithParameters(search_parameters) 277 | print_solution(data, manager, routing, assignment) 278 | 279 | 280 | if __name__ == '__main__': 281 | main() 282 | -------------------------------------------------------------------------------- /Data.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/muditp19/Vehicle-Route-Optimization-TSP/24105e1756febb3086dc4d132acfb47fa35d66a0/Data.xlsx -------------------------------------------------------------------------------- /Distance.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # This Python file uses the following encoding: utf-8 3 | # Copyright 2015 Tin Arm Engineering AB 4 | # Copyright 2018 Google LLC 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | """Capacitated Vehicle Routing Problem with Time Windows (CVRPTW). 17 | This is a sample using the routing library python wrapper to solve a CVRPTW 18 | problem. 19 | A description of the problem can be found here: 20 | http://en.wikipedia.org/wiki/Vehicle_routing_problem. 21 | Distances are in meters and time in minutes. 22 | """ 23 | 24 | from __future__ import print_function 25 | 26 | from functools import partial 27 | from six.moves import xrange 28 | 29 | from ortools.constraint_solver import pywrapcp 30 | from ortools.constraint_solver import routing_enums_pb2 31 | from scipy.spatial import distance_matrix 32 | 33 | import pandas as pd 34 | df=pd.read_excel(r'/Users/MuditPaliwal/Desktop/Data.xlsx') 35 | 36 | print(df) 37 | 38 | x = df.iloc[:,1] 39 | y = df.iloc[:,2] 40 | d = df.iloc[:,3] 41 | data = [(f,b) for(f,b) in zip(x,y)] 42 | ctys = range(51) 43 | df = pd.DataFrame(data, columns=['xcord', 'ycord'], index=ctys) 44 | dist = pd.DataFrame(distance_matrix(df.values, df.values), index=df.index, columns=df.index) 45 | 46 | 47 | ########################### 48 | # Problem Data Definition # 49 | ########################### 50 | def create_data_model(): 51 | """Stores the data for the problem""" 52 | data = {} 53 | # Locations in block unit 54 | _locations = [(0.0, 0.0), (-8.2, -96.68), (-16.01, 14.95), (-1.26, 82.05), (-93.17, 78.92), (-57.1, 18.67), (89.57, 13.12), (-99.8, 27.28), (0.79, -33.16), (78.95, 78.61), (80.92, -39.92), (-95.76, 79.96), (-31.53, 54.57), (-18.47, -31.8), (-96.29, -53.0), (85.06, -45.54), (37.84, 75.75), (-2.38, -40.04), (-37.7, 25.98), (-74.47, 74.73), (85.66, 88.39), (-97.99, 67.34), (-66.24, 47.94), (49.66, -9.73), (66.59, -84.24), (9.82, 22.22), (38.45, -6.1), (-24.67, -77.81), (5.55, 88.13), (85.59, 14.86), (89.28, 89.99), (-47.88, 76.3), (19.9, -47.07), (11.24, 62.22), (16.26, 88.32), (79.24, -44.43), (-99.97, 27.93), (1.01, -60.47), (-51.98, -42.88), (8.71, -23.44), (-82.64, 87.2), (30.8, -8.29), (6.28, 44.43), (25.02, 78.73), (31.69, 62.16), (-36.28, -25.46), (45.35, 77.33), (67.7, -89.31), (-2.42, 71.68), (10.45, 66.92), (-21.02, 30.19)] 55 | # Compute locations in meters using the block dimension defined as follow 56 | # Manhattan average block: 750ft x 264ft -> 228m x 80m 57 | # here we use: 114m x 80m city block 58 | # src: https://nyti.ms/2GDoRIe "NY Times: Know Your distance" 59 | data['locations'] = [(l[0] * 114, l[1] * 80) for l in _locations] 60 | data['num_locations'] = len(data['locations']) 61 | data['time_windows'] =[(0, 5), (222, 234), (1378, 1433), (1115, 1136), (1289, 1312), (656, 698), (221, 253), (1086, 1105), (560, 627), (1084, 1143), (1168, 1187), (337, 340), (756, 809), (1316, 1328), (124, 189), (1286, 1313), (154, 221), (21, 78), (834, 853), (735, 816), (386, 425), (1277, 1341), (1202, 1262), (1269, 1336), (1238, 1251), (1045, 1057), (99, 153), (380, 400), (569, 644), (933, 985), (401, 429), (1027, 1073), (959, 1019), (558, 626), (1245, 1269), (603, 642), (1088, 1144), (127, 172), (764, 785), (473, 517), (875, 890), (597, 641), (460, 484), (23, 97), (403, 463), (299, 348), (1018, 1093), (1078, 1120), (1369, 1396), (487, 503), (1133, 1170)] 62 | data['demands'] = d 63 | data['time_per_demand_unit'] = 0.16666667 # 5 minutes/unit 64 | data['num_vehicles'] = 20 65 | data['vehicle_capacity'] = 80 66 | data['vehicle_speed'] = 1666.67 # Travel speed: 5km/h converted in m/min 67 | data['depot'] = 0 68 | return data 69 | 70 | 71 | ####################### 72 | # Problem Constraints # 73 | ####################### 74 | def euclidean_distance(position_1, position_2): 75 | """Computes the Manhattan distance between two points""" 76 | return ( 77 | abs(position_1[0] - position_2[0]) + abs(position_1[1] - position_2[1])) 78 | 79 | 80 | def create_distance_evaluator(data): 81 | """Creates callback to return distance between points.""" 82 | _distances = {} 83 | # precompute distance between location to have distance callback in O(1) 84 | for from_node in xrange(data['num_locations']): 85 | _distances[from_node] = {} 86 | for to_node in xrange(data['num_locations']): 87 | if from_node == to_node: 88 | _distances[from_node][to_node] = 0 89 | else: 90 | _distances[from_node][to_node] = (euclidean_distance( 91 | data['locations'][from_node], data['locations'][to_node])) 92 | 93 | def distance_evaluator(manager, from_node, to_node): 94 | """Returns the manhattan distance between the two nodes""" 95 | return _distances[manager.IndexToNode(from_node)][manager.IndexToNode( 96 | to_node)] 97 | 98 | return distance_evaluator 99 | print (distance_evaluator) 100 | 101 | data['demands'][node] * data['time_per_demand_unit'] 102 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Vehicle-Route-Optimization-TSP 2 | OR-Tools is an open source software suite for optimization, tuned for tackling the world's toughest problems in vehicle routing, flows, integer and linear programming, and constraint programming. 3 | An example of a node-routing problem is vehicle routing. Suppose that a company needs to deliver packages to various locations, using a fleet of vehicles. 4 | In the graph for this problem, nodes represent locations, and arcs represent routes between them. Each arc has a weight, corresponding to the cost of traveling that route. The problem: find a set of paths in the graph (corresponding to delivery routes for each vehicle) that includes every destination while minimizing the total cost. This differs from the arc-routing problem because the paths don't have to traverse every arc, just include every node. 5 | 6 | One of the most important things in using the OR-Tools is to use the right searching strategy and the right parameters to stop the search. Search limits terminate the solver after it reaches a specified limit, such as the maximum length of time, or number of solutions found. You can set a search limit through the solver's search parameters. 7 | 8 | Results 9 | Without time windows 10 | VEHICLE NUMBER ROUTE: NODE (LOAD) DISTANCE OF THE ROUTE (MILE) LOAD OF THE ROUTE 11 | 1 0 (0) → 32 (48) → 37 (71) → 0 (71) 134 71 12 | 2 0 (0) → 26 (30) → 29 (74) → 0 (74) 175 74 13 | 3 0 (0) → 49 (30) → 42 (71) → 0 (71) 133 71 14 | 4 15 | 0 (0) → 40 (42) → 3 (64) → 48 (80) → 0 (80) 16 | 282 80 17 | 5 0 (0) → 44 (26) → 30 (39) → 20 (67) → 9 (75) → 0 (75) 257 75 18 | 6 0 (0) → 38 (35) → 14 (53) → 27 (63) → 17 (79) → 0 (79) 270 79 19 | 7 0 (0) → 46 (14) → 16 (44) → 43 (77) → 0 (77) 191 77 20 | 8 0 (0) → 33 (13) → 34 (18) → 28 (33) → 31 (66) → 12 (79) → 0 (79) 243 79 21 | 9 0 (0) → 35 (6) → 15 (45) → 10 (53) → 23 (79) → 0 (79) 194 79 22 | 10 0 (0) → 22 (19) → 4 (69) → 11 (79) → 0 (79) 248 79 23 | 11 0 (0) → 1 (10) → 47 (38) → 24 (65) → 6 (78) → 0 (78) 368 78 24 | 12 0 (0) → 18 (41) → 45 (79) → 0 (79) 140 79 25 | 13 0 (0) → 25 (45) → 41 (72) → 0 (72) 92 72 26 | 14 0 (0) → 8 (43) → 39 (78) → 0 (78) 70 78 27 | 15 0 (0) → 2 (13) → 50 (27) → 19 (70) → 5 (76) → 0 (76) 224 76 28 | TOTAL DISTANCE OF ALL ROUTES (MILE) 3,314 29 | TOTAL LOAD OF ALL ROUTES 1,223 30 | TOTAL COST ($) : DISTANCE TRAVELLED + TRUCK USED 19,314 31 | 32 | With Time windows 33 | 34 | VEHICLE NUMBER ROUTE: NODE (LOAD) / TIME / SLACK DISTANCE OF THE ROUTE (MILE) LOAD OF THE ROUTE TIME OF THE ROUTE (MIN) 35 | 1 0 (0) / (0,0) / (566,610) → 41 (0) / (597,641) / (342,428) → 47 (27) / (1078,1120) / (87,141) → 13 (55) / (1316,1328) / (0,143) → 0 (80) / (1357,1500) 259 80 1357 36 | 2 0 (0) / (0,0) / (213,216) → 11 (0) / (337,340) / (944,970) → 4 (10) / (1289,1312) / (0,34) → 2 (60) / (1399,1433) / (0,77) → 0 (73) / (1423,1500) 247 73 1423 37 | 3 0 (0) / (0,0) / (420,436) → 49 (0) / (487,503) / (44,128) → 33 (30) / (558,626) / (84,205) → 12 (43) / (756,809) / (295,385) → 50 (56) / (1133,1170) / (0,328) → 0 (70) / (1172,1500) 176 70 1172 38 | 4 0 (0) / (0,0) / (847,899) → 29 (0) / (933,985) / (232,351) → 23 (44) / (1269, 1336) / (0,175) → 0 (70) / (1325,1500) 179 70 1325 39 | 5 0 (0) / (0,0) / (131,163) → 6 (0) / (221,253) / (69,119) → 30 (13) / (401,419) / (0,18) → 20 (26) / (407,425) / (642,719) → 9 (54) / (1084,1143) / (0,303) → 0 (62) / (1197,1500) 291 62 1197 40 | 6 0 (0) / (0,0) / (67,112) → 37 (0) / (127,172) / (8,65) → 1 (23) / (222,234) / (119,151) → 27 (33) / (380,400) / (318,359) → 38 (43) / (764,785) / (0,661) → 0 (78) / (839,1500) 233 78 839 41 | 7 0 (0) / (0,0) / (0,15) → 43 (0) / (82,97) / (444,534) → 28 (33) / (569,644) / (459,555) → 3 (48) / (1115,1136) / (0,298) → 0 (70) / (1202,1500) 194 70 1202 42 | 8 0 (0) / (0,0) / (448,492) → 39 (0) / (473,517) / (24,135) → 8 (35) / (560,627) / (0,898) → 0 (78) / (602,1500) 70 78 602 43 | 9 0 (0) / (0,0) / (70,137) → 16 (0) / (154,221) / (783,925) → 46 (30) / (1018,1093) / (118,217) → 34 (44) / (1245,1269) / (74,125) → 48 (49) / (1369,1396) / (0,56) → 0 (65) / (1444,1500) 218 65 1444 44 | 10 0 (0) / (0,0) / (334,379) → 44 (0) / (403,448) / (0,45) → 42 (26) / (460,484) / (0,987) → 0 (67) / (513,1500) 143 67 513 45 | 11 0 (0) / (0,0) / (513,552) → 35 (0) / (603,642) / (520,578) → 10 (6) / (1168,1187) / (3,35) → 24 (14) / (1238, 1251) / (0,27) → 15 (41) / (1286,1313) / (0,110) → 0 (80) / (1390,1500) 278 80 1390 46 | 12 0 (0) / (0,0) / (61,115) → 26 (0) / (99,153) / (845,911) → 25 (30) / (1045,1057) / (0,422) → 0 (75) / (1078,1500) 102 75 1078 47 | 13 0 (0) / (0,0) / (755,770) → 40 (0) / (875,890) / (92,153) → 31 (42) / (1027,1073) / (0,376) → 0 (75) / (1124,1500) 246 75 1124 48 | 14 0 (0) / (0,0) / (0,38) → 17 (0) / (40,78) / (854,952) → 32 (16) / (959,1019) / (0,480) → 0 (64) / (1020,1500) 114 64 1020 49 | 15 0 (0) / (0,0) / (596,638) → 5 (0) / (656,698) / (0,100) → 19 (6) / (735,816) / (349,490) → 22 (49) / (1202,1262) / (0,213) → 0 (68) / (1287,1500) 227 68 1287 50 | 16 0 (0) / (0,0) / (15,80) → 14 (0) / (!24,189) / (813,897) → 7 (18) / (1086,1105) / (0,57) → 36 (21) / (1088,1144) / (92,212) → 21 (29) / (1277,1341) / (0,96) → 0 (69) / (1404,1500) 346 69 1404 51 | 17 0 (0) / (0,0) / (255,304) → 45 (0) / (299,348) / (427,495) → 18 (38) / (834,853) / (0,612) → 0 (79) / (888,1500) 140 79 888 52 | TOTAL DISTANCE OF ALL ROUTES (MILE) 3,463 53 | TOTAL LOAD OF ALL ROUTES 1,223 54 | TOTAL TIME OF ALL ROUTES (MIN) 19,265 55 | TOTAL COST ($) : DISTANCE TRAVELLED + TRUCK USED 20,463 56 | 57 | -------------------------------------------------------------------------------- /VRP.py: -------------------------------------------------------------------------------- 1 | """Vehicles Routing Problem (VRP).""" 2 | 3 | from __future__ import print_function 4 | from ortools.constraint_solver import routing_enums_pb2 5 | from ortools.constraint_solver import pywrapcp 6 | from scipy.spatial import distance_matrix 7 | 8 | import pandas as pd 9 | df=pd.read_excel(r'/Users/MuditPaliwal/Desktop/Data.xlsx') 10 | 11 | print(df) 12 | 13 | x = df.iloc[:,1] 14 | y = df.iloc[:,2] 15 | d = df.iloc[:,3] 16 | data = [(f,b) for(f,b) in zip(x,y)] 17 | ctys = range(51) 18 | df = pd.DataFrame(data, columns=['xcord', 'ycord'], index=ctys) 19 | dist = pd.DataFrame(distance_matrix(df.values, df.values), index=df.index, columns=df.index) 20 | 21 | 22 | def create_data_model(): 23 | """Stores the data for the problem.""" 24 | data = {} 25 | data['distance_matrix'] = dist 26 | data['demands'] = d 27 | data['vehicle_capacities'] = [80, 80, 80, 80,80, 80, 80, 80,80, 80, 80, 80,80, 80, 80,80] 28 | data['num_vehicles'] = 16 29 | data['depot'] = 0 30 | return data 31 | 32 | 33 | def print_solution(data, manager, routing, assignment): 34 | """Prints assignment on console.""" 35 | total_distance = 0 36 | total_load = 0 37 | for vehicle_id in range(data['num_vehicles']): 38 | index = routing.Start(vehicle_id) 39 | plan_output = 'Route for vehicle {}:\n'.format(vehicle_id) 40 | route_distance = 0 41 | route_load = 0 42 | while not routing.IsEnd(index): 43 | node_index = manager.IndexToNode(index) 44 | route_load += data['demands'][node_index] 45 | plan_output += ' {0} Load({1}) -> '.format(node_index, route_load) 46 | previous_index = index 47 | index = assignment.Value(routing.NextVar(index)) 48 | route_distance += routing.GetArcCostForVehicle( 49 | previous_index, index, vehicle_id) 50 | plan_output += ' {0} Load({1})\n'.format(manager.IndexToNode(index), 51 | route_load) 52 | plan_output += 'Distance of the route: {}m\n'.format(route_distance) 53 | plan_output += 'Load of the route: {}\n'.format(route_load) 54 | print(plan_output) 55 | total_distance += route_distance 56 | total_load += route_load 57 | print('Total distance of all routes: {}m'.format(total_distance)) 58 | print('Total load of all routes: {}'.format(total_load)) 59 | 60 | 61 | def main(): 62 | """Solve the CVRP problem.""" 63 | # Instantiate the data problem. 64 | data = create_data_model() 65 | 66 | # Create the routing index manager. 67 | manager = pywrapcp.RoutingIndexManager(len(data['distance_matrix']), 68 | data['num_vehicles'], data['depot']) 69 | 70 | # Create Routing Model. 71 | routing = pywrapcp.RoutingModel(manager) 72 | 73 | 74 | # Create and register a transit callback. 75 | def distance_callback(from_index, to_index): 76 | """Returns the distance between the two nodes.""" 77 | # Convert from routing variable Index to distance matrix NodeIndex. 78 | from_node = manager.IndexToNode(from_index) 79 | to_node = manager.IndexToNode(to_index) 80 | return data['distance_matrix'][from_node][to_node] 81 | 82 | transit_callback_index = routing.RegisterTransitCallback(distance_callback) 83 | 84 | # Define cost of each arc. 85 | routing.SetArcCostEvaluatorOfAllVehicles(transit_callback_index) 86 | 87 | 88 | # Add Capacity constraint. 89 | def demand_callback(from_index): 90 | """Returns the demand of the node.""" 91 | # Convert from routing variable Index to demands NodeIndex. 92 | from_node = manager.IndexToNode(from_index) 93 | return data['demands'][from_node] 94 | 95 | demand_callback_index = routing.RegisterUnaryTransitCallback( 96 | demand_callback) 97 | routing.AddDimensionWithVehicleCapacity( 98 | demand_callback_index, 99 | 0, # null capacity slack 100 | data['vehicle_capacities'], # vehicle maximum capacities 101 | True, # start cumul to zero 102 | 'Capacity') 103 | 104 | # Setting first solution heuristic. 105 | search_parameters = pywrapcp.DefaultRoutingSearchParameters() 106 | search_parameters.local_search_metaheuristic = ( 107 | routing_enums_pb2.LocalSearchMetaheuristic.AUTOMATIC) 108 | search_parameters.time_limit.seconds = 200 109 | search_parameters.log_search = True 110 | 111 | # Solve the problem. 112 | assignment = routing.SolveWithParameters(search_parameters) 113 | 114 | # Print solution on console. 115 | if assignment: 116 | print_solution(data, manager, routing, assignment) 117 | 118 | 119 | if __name__ == '__main__': 120 | main() 121 | --------------------------------------------------------------------------------