#!/usr/bin/env python # This Python file uses the following encoding: utf-8 # Copyright 2015 Tin Arm Engineering AB # Copyright 2018 Google LLC # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. """Vehicle Routing Problem (VRP). This is a sample using the routing library python wrapper to solve a VRP problem. A description of the problem can be found here: http://en.wikipedia.org/wiki/Vehicle_routing_problem. Distances are in meters and time in seconds. Manhattan average block: 750ft x 264ft -> 228m x 80m src: https://nyti.ms/2GDoRIe "NY Times: Know Your distance" here we use: 114m x 80m city block """ from __future__ import print_function from six.moves import xrange from ortools.constraint_solver import pywrapcp from ortools.constraint_solver import routing_enums_pb2 ########################### # Problem Data Definition # ########################### class CityBlock(): """City block definition""" @property def width(self): """Gets Block size West to East""" return 228/2 @property def height(self): """Gets Block size North to South""" return 80 class DataProblem(): """Stores the data for the problem""" def __init__(self): """Initializes the data for the problem""" self._num_vehicles = 4 # Locations in block unit locations = \ [(4, 4), # depot (2, 0), (8, 0), # row 0 (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)] # locations in meters using the block dimension defined city_block = CityBlock() self._locations = [( loc[0]*city_block.width, loc[1]*city_block.height) for loc in locations] self._depot = 0 @property def num_vehicles(self): """Gets number of vehicles""" return self._num_vehicles @property def locations(self): """Gets locations""" return self._locations @property def num_locations(self): """Gets number of locations""" return len(self.locations) @property def depot(self): """Gets depot location index""" return self._depot ####################### # 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])) class CreateDistanceEvaluator(object): # pylint: disable=too-few-public-methods """Creates callback to return distance between points.""" def __init__(self, data): """Initializes the distance matrix.""" self._distances = {} # precompute distance between location to have distance callback in O(1) for from_node in xrange(data.num_locations): self._distances[from_node] = {} for to_node in xrange(data.num_locations): if from_node == to_node: self._distances[from_node][to_node] = 0 else: self._distances[from_node][to_node] = ( manhattan_distance( data.locations[from_node], data.locations[to_node])) def distance_evaluator(self, from_node, to_node): """Returns the manhattan distance between the two nodes""" return self._distances[from_node][to_node] ########### # Printer # ########### class ConsolePrinter(): """Print solution to console""" def __init__(self, data, routing, assignment): """Initializes the printer""" self._data = data self._routing = routing self._assignment = assignment @property def data(self): """Gets problem data""" return self._data @property def routing(self): """Gets routing model""" return self._routing @property def assignment(self): """Gets routing model""" return self._assignment def print(self): """Prints assignment on console""" # Inspect solution. total_dist = 0 for vehicle_id in xrange(self.data.num_vehicles): index = self.routing.Start(vehicle_id) plan_output = 'Route for vehicle {0}:\n'.format(vehicle_id) route_dist = 0 while not self.routing.IsEnd(index): node_index = self.routing.IndexToNode(index) next_node_index = self.routing.IndexToNode( self.assignment.Value(self.routing.NextVar(index))) route_dist += manhattan_distance( self.data.locations[node_index], self.data.locations[next_node_index]) plan_output += ' {0} -> '.format(node_index) index = self.assignment.Value(self.routing.NextVar(index)) node_index = self.routing.IndexToNode(index) total_dist += route_dist plan_output += ' {0}\n'.format(node_index) plan_output += 'Distance of the route: {0}m\n'.format(route_dist) print(plan_output) print('Total Distance of all routes: {0}m'.format(total_dist)) ######## # Main # ######## def main(): """Entry point of the program""" # Instantiate the data problem. data = DataProblem() # Create Routing Model routing = pywrapcp.RoutingModel(data.num_locations, data.num_vehicles, data.depot) # Define weight of each edge distance_evaluator = CreateDistanceEvaluator(data).distance_evaluator routing.SetArcCostEvaluatorOfAllVehicles(distance_evaluator) # Setting first solution heuristic (cheapest addition). search_parameters = pywrapcp.RoutingModel.DefaultSearchParameters() search_parameters.first_solution_strategy = ( routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC) # Solve the problem. assignment = routing.SolveWithParameters(search_parameters) printer = ConsolePrinter(data, routing, assignment) printer.print() if __name__ == '__main__': main()