Update routing python examples

- add vrp.py
- add vrpgs.py
- add cvrp.py
- update cvrptw.py
This commit is contained in:
Corentin Le Molgat
2018-04-04 11:26:38 +02:00
parent fe9ddb78c8
commit 786bfce05f
6 changed files with 1086 additions and 158 deletions

View File

@@ -9,6 +9,10 @@ foreach(TEST
linear_programming
pyflow_example
tsp
vrp
vrpgs
cvrp
cvrptw
)
add_test(py${TEST}_venv ${VENV_BIN_DIR}/python ${CMAKE_CURRENT_SOURCE_DIR}/${TEST}.py)
set_tests_properties(py${TEST}_venv PROPERTIES DEPENDS build_venv)

259
examples/python/cvrp.py Executable file
View File

@@ -0,0 +1,259 @@
#!/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.
"""Capacitated Vehicle Routing Problem (CVRP).
This is a sample using the routing library python wrapper to solve a CVRP 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 Vehicle():
"""Stores the property of a vehicle"""
def __init__(self):
"""Initializes the vehicle properties"""
self._capacity = 15
@property
def capacity(self):
"""Gets vehicle capacity"""
return self._capacity
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._vehicle = Vehicle()
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
self._demands = \
[0, # depot
1, 1, # row 0
2, 4,
2, 4,
8, 8,
1, 2,
1, 2,
4, 4,
8, 8]
@property
def vehicle(self):
"""Gets a vehicle"""
return self._vehicle
@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
@property
def demands(self):
"""Gets demands at each location"""
return self._demands
#######################
# 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]
class CreateDemandEvaluator(object): # pylint: disable=too-few-public-methods
"""Creates callback to get demands at each location."""
def __init__(self, data):
"""Initializes the demand array."""
self._demands = data.demands
def demand_evaluator(self, from_node, to_node):
"""Returns the demand of the current node"""
del to_node
return self._demands[from_node]
def add_capacity_constraints(routing, data, demand_evaluator):
"""Adds capacity constraint"""
capacity = "Capacity"
routing.AddDimension(
demand_evaluator,
0, # null capacity slack
data.vehicle.capacity,
True, # start cumul to zero
capacity)
###########
# 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
route_load = 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])
route_load += self.data.demands[node_index]
plan_output += ' {0} Load({1}) -> '.format(node_index, route_load)
index = self.assignment.Value(self.routing.NextVar(index))
node_index = self.routing.IndexToNode(index)
total_dist += route_dist
plan_output += ' {0} Load({1})\n'.format(node_index, route_load)
plan_output += 'Distance of the route: {0}m\n'.format(route_dist)
plan_output += 'Load of the route: {0}\n'.format(route_load)
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)
# Add Capacity constraint
demand_evaluator = CreateDemandEvaluator(data).demand_evaluator
add_capacity_constraints(routing, data, demand_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()

353
examples/python/cvrptw.py Normal file → Executable file
View File

@@ -1,6 +1,7 @@
#!/usr/bin/env python
# This Python file uses the following encoding: utf-8
# Copyright 2015 Tin Arm Engineering AB
# Copyright 2017 Google LLC
# 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
@@ -13,36 +14,33 @@
# See the License for the specific language governing permissions and
# limitations under the License.
"""Capacitated Vehicle Routing Problem with Time Windows.
This is a sample using the routing library python wrapper to solve a
CVRPTW problem.
"""Capacitated Vehicle Routing Problem with Time Windows (CVRPTW).
This is a sample using the routing library python wrapper to solve a CVRPTW problem.
A description of the problem can be found here:
http://en.wikipedia.org/wiki/Vehicle_routing_problem.
The variant which is tackled by this model includes a capacity dimension
and time windows.
Distances are computed using the Manhattan distances. Distances are in km
and times in seconds.
The optimization engine uses local search to improve solutions, first
solutions being generated using a cheapest addition heuristic.
Distances are in meters and time in minutes.
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
import sys
from six.moves import xrange
from ortools.constraint_solver import pywrapcp
from ortools.constraint_solver import routing_enums_pb2
# Problem Data Definition
###########################
# Problem Data Definition #
###########################
class Vehicle():
"""Stores the property of a vehicle"""
def __init__(self):
"""Initializes the vehicle properties"""
self._capacity = 100
# Travel speed: 80km/h to convert in km/s
self._speed = 80 / 3600.
self._capacity = 15
# Travel speed: 5km/h to convert in m/min
self._speed = 5 * 60 / 3.6
@property
def capacity(self):
@@ -54,50 +52,65 @@ class Vehicle():
"""Gets the average travel speed of a vehicle"""
return self._speed
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._vehicle = Vehicle()
self._num_vehicles = 5
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 city block dimension
city_block = CityBlock()
self._locations = [(
loc[0]*city_block.width,
loc[1]*city_block.height) for loc in locations]
self._locations = \
[[82, 76], [96, 44], [50, 5], [49, 8], [13, 7], [29, 89], [58, 30], [84, 39],
[14, 24], [12, 39], [3, 82], [5, 10], [98, 52], [84, 25], [61, 59], [1, 65],
[88, 51], [91, 2], [19, 32], [93, 3], [50, 93], [98, 14], [5, 42], [42, 9],
[61, 62], [9, 97], [80, 55], [57, 69], [23, 15], [20, 70], [85, 60], [98, 5]]
self._depot = 0
self._demands = \
[0, 19, 21, 6, 19, 7, 12, 16,
6, 16, 8, 14, 21, 16, 3, 22,
18, 19, 1, 24, 8, 12, 4, 8,
24, 24, 2, 20, 15, 2, 14, 9]
# Time to deliver a package to a customer: 3min/unit
self._time_per_demand_unit = 3 * 60
[0, # depot
1, 1, # 1, 2
2, 4, # 3, 4
2, 4, # 5, 6
8, 8, # 7, 8
1, 2, # 9,10
1, 2, # 11,12
4, 4, # 13, 14
8, 8] # 15, 16
start_times = \
[0, 5080, 1030, 4930, 2250, 5310, 890, 5650,
5400, 1080, 6020, 4660, 3560, 3030, 3990, 3820,
3620, 5210, 230, 4890, 4450, 3180, 3800, 550,
5740, 5150, 1100, 3100, 3870, 4910, 3280, 730]
# The width of the time window: 5 hours.
tw_duration = 5 * 60 * 60
# In this example, the time window widths is the same at each location, so we define the end
# times to be start times + tw_duration.
# For problems in which the time window widths vary by location, you can explicitly define
# the list of end_times, as we have done for start_times.
self._time_windows = [(start, start + tw_duration) for start in start_times]
# Check data coherency
if self.num_locations == 0:
raise ValueError('Locations must be greater than 0.')
if (len(self._locations) != len(self._demands) or
len(self._locations) != len(self._time_windows)):
raise RuntimeError("Inconsistent data problem!")
self._time_windows = \
[(0, 0),
(75, 85), (75, 85), # 1, 2
(60, 70), (45, 55), # 3, 4
(0, 8), (50, 60), # 5, 6
(0, 10), (10, 20), # 7, 8
(0, 10), (75, 85), # 9, 10
(85, 95), (5, 15), # 11, 12
(15, 25), (10, 20), # 13, 14
(45, 55), (30, 40)] # 15, 16
@property
def vehicle(self):
@@ -119,11 +132,6 @@ class DataProblem():
"""Gets number of locations"""
return len(self.locations)
def manhattan_distance(self, from_node, to_node):
"""Computes the Manhattan distance between two nodes"""
return (abs(self.locations[from_node][0] - self.locations[to_node][0]) +
abs(self.locations[from_node][1] - self.locations[to_node][1]))
@property
def depot(self):
"""Gets depot location index"""
@@ -131,59 +139,71 @@ class DataProblem():
@property
def demands(self):
"""Gets demands for each locations"""
"""Gets demands at each location"""
return self._demands
@property
def time_per_demand_unit(self):
"""Gets the average time per demand unit"""
return self._time_per_demand_unit
"""Gets the time (in min) to load a demand"""
return 5 # 5 minutes/unit
@property
def time_windows(self):
"""Gets (start time, end time) for each locations"""
return self._time_windows
@property
def horizon(self):
"""Maximum times to perform all deliveries"""
return 24 * 3600
#######################
# 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]))
# Distance callback
class CreateDistanceCallback(object): # pylint: disable=too-few-public-methods
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._distance = {}
self._distances = {}
# precompute distance between location to have distance callback in O(1)
for from_node in xrange(data.num_locations):
self._distance[from_node] = {}
self._distances[from_node] = {}
for to_node in xrange(data.num_locations):
if from_node == to_node:
self._distance[from_node][to_node] = 0
self._distances[from_node][to_node] = 0
else:
self._distance[from_node][to_node] = (
data.manhattan_distance(from_node, to_node))
self._distances[from_node][to_node] = (
manhattan_distance(
data.locations[from_node],
data.locations[to_node]))
def distance(self, from_node, to_node):
def distance_evaluator(self, from_node, to_node):
"""Returns the manhattan distance between the two nodes"""
return self._distance[from_node][to_node]
return self._distances[from_node][to_node]
# Demand callback
class CreateDemandCallback(object): # pylint: disable=too-few-public-methods
class CreateDemandEvaluator(object): # pylint: disable=too-few-public-methods
"""Creates callback to get demands at each location."""
def __init__(self, data):
"""Initializes the demand array."""
self._demands = data.demands
def demand(self, from_node, to_node):
def demand_evaluator(self, from_node, to_node):
"""Returns the demand of the current node"""
del to_node
return self._demands[from_node]
# Time callback (equals to: service time + travel time).
class CreateTimeCallback(object):
def add_capacity_constraints(routing, data, demand_evaluator):
"""Adds capacity constraint"""
capacity = "Capacity"
routing.AddDimension(
demand_evaluator,
0, # null capacity slack
data.vehicle.capacity, # vehicle maximum capacity
True, # start cumul to zero
capacity)
class CreateTimeEvaluator(object):
"""Creates callback to get total times between locations."""
@staticmethod
def service_time(data, node):
@@ -196,13 +216,14 @@ class CreateTimeCallback(object):
if from_node == to_node:
travel_time = 0
else:
travel_time = data.manhattan_distance(from_node, to_node) / data.vehicle.speed
travel_time = manhattan_distance(
data.locations[from_node],
data.locations[to_node]) / data.vehicle.speed
return travel_time
def __init__(self, data):
"""Initializes the total time matrix."""
self._total_time = {}
# precompute total time to have time callback in O(1)
for from_node in xrange(data.num_locations):
self._total_time[from_node] = {}
@@ -210,109 +231,125 @@ class CreateTimeCallback(object):
if from_node == to_node:
self._total_time[from_node][to_node] = 0
else:
self._total_time[from_node][to_node] = (
self._total_time[from_node][to_node] = int(
self.service_time(data, from_node) +
self.travel_time(data, from_node, to_node))
def time(self, from_node, to_node):
def time_evaluator(self, from_node, to_node):
"""Returns the total time between the two nodes"""
return self._total_time[from_node][to_node]
def print_assignment(data, routing, assignment, capacity, time):
"""Prints solution"""
# Solution cost.
print("Total distance of all routes: {0}\n".format(assignment.ObjectiveValue()))
# Inspect solution.
capacity_dimension = routing.GetDimensionOrDie(capacity)
def add_time_window_constraints(routing, data, time_evaluator):
"""Add Global Span constraint"""
time = "Time"
horizon = 120
routing.AddDimension(
time_evaluator,
horizon, # allow waiting time
horizon, # maximum time per vehicle
True, # start cumul to zero
time)
time_dimension = routing.GetDimensionOrDie(time)
for location_idx, time_window in enumerate(data.time_windows):
time_dimension.CumulVar(location_idx).SetRange(time_window[0], time_window[1])
for vehicle_id in xrange(data.num_vehicles):
index = routing.Start(vehicle_id)
plan_output = 'Route for vehicle {0}:\n'.format(vehicle_id)
route_dist = 0
###########
# 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
while not routing.IsEnd(index):
node_index = routing.IndexToNode(index)
next_node_index = routing.IndexToNode(assignment.Value(routing.NextVar(index)))
route_dist += data.manhattan_distance(node_index, next_node_index)
@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.
capacity_dimension = self.routing.GetDimensionOrDie('Capacity')
time_dimension = self.routing.GetDimensionOrDie('Time')
total_dist = 0
total_time = 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])
load_var = capacity_dimension.CumulVar(index)
route_load = self.assignment.Value(load_var)
time_var = time_dimension.CumulVar(index)
time_min = self.assignment.Min(time_var)
time_max = self.assignment.Max(time_var)
plan_output += ' {0} Load({1}) Time({2},{3}) ->'.format(node_index, route_load, time_min, time_max)
index = self.assignment.Value(self.routing.NextVar(index))
node_index = self.routing.IndexToNode(index)
load_var = capacity_dimension.CumulVar(index)
route_load = self.assignment.Value(load_var)
time_var = time_dimension.CumulVar(index)
plan_output += ' {node_index} Load({load}) Time({tmin}, {tmax}) -> '.format(
node_index=node_index,
load=assignment.Value(load_var),
tmin=str(assignment.Min(time_var)),
tmax=str(assignment.Max(time_var)))
index = assignment.Value(routing.NextVar(index))
node_index = routing.IndexToNode(index)
load_var = capacity_dimension.CumulVar(index)
time_var = time_dimension.CumulVar(index)
plan_output += ' {node_index} Load({load}) Time({tmin}, {tmax})\n'.format(
node_index=node_index,
load=assignment.Value(load_var),
tmin=str(assignment.Min(time_var)),
tmax=str(assignment.Max(time_var)))
plan_output += 'Distance of the route {0}: {dist}\n'.format(
vehicle_id,
dist=route_dist)
plan_output += 'Demand met by vehicle {0}: {load}\n'.format(
vehicle_id,
load=assignment.Value(load_var))
print(plan_output, '\n')
route_time = self.assignment.Value(time_var)
time_min = self.assignment.Min(time_var)
time_max = self.assignment.Max(time_var)
total_dist += route_dist
total_time += route_time
plan_output += ' {0} Load({1}) Time({2},{3})\n'.format(node_index, route_load, time_min, time_max)
plan_output += 'Distance of the route: {0}m\n'.format(route_dist)
plan_output += 'Load of the route: {0}\n'.format(route_load)
plan_output += 'Time of the route: {0}min\n'.format(route_time)
print(plan_output)
print('Total Distance of all routes: {0}m'.format(total_dist))
print('Total Time of all routes: {0}min'.format(total_time))
########
# Main #
########
def main():
"""Entry point of the program"""
# Instanciate the data problem.
# Instantiate the data problem.
data = DataProblem()
# Create routing model.
# The number of nodes of the VRP is num_locations.
# Nodes are indexed from 0 to num_locations - 1.
# By default the start of a route is node 0.
# Create Routing Model
routing = pywrapcp.RoutingModel(data.num_locations, data.num_vehicles, data.depot)
# Adding the custom distance function.
dist_callback = CreateDistanceCallback(data).distance
routing.SetArcCostEvaluatorOfAllVehicles(dist_callback)
# Adding a Capacity dimension constraints.
demands_callback = CreateDemandCallback(data).demand
null_capacity_slack = 0
fix_start_cumul_to_zero = True
capacity = "Capacity"
routing.AddDimension(demands_callback,
null_capacity_slack,
data.vehicle.capacity,
fix_start_cumul_to_zero,
capacity)
# Adding a Time dimension for time-window constraints.
time_callback = CreateTimeCallback(data).time
time = "Time"
routing.AddDimension(time_callback,
data.horizon,
data.horizon,
fix_start_cumul_to_zero,
time)
time_dimension = routing.GetDimensionOrDie(time)
for count, time_window in enumerate(data.time_windows):
time_dimension.CumulVar(count).SetRange(time_window[0], time_window[1])
# Define weight of each edge
distance_evaluator = CreateDistanceEvaluator(data).distance_evaluator
routing.SetArcCostEvaluatorOfAllVehicles(distance_evaluator)
# Add Capacity constraint
demand_evaluator = CreateDemandEvaluator(data).demand_evaluator
add_capacity_constraints(routing, data, demand_evaluator)
# Add Time Window constraint
time_evaluator = CreateTimeEvaluator(data).time_evaluator
add_time_window_constraints(routing, data, time_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)
# Display a solution if any.
if assignment:
print_assignment(data, routing, assignment, capacity, time)
else:
print('No solution found.')
sys.exit(2)
printer = ConsolePrinter(data, routing, assignment)
printer.print()
if __name__ == '__main__':
main()

215
examples/python/transit_time.py Executable file
View File

@@ -0,0 +1,215 @@
#!/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.
"""Display Transit Time
Distances are in meters and time in minutes.
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 Vehicle():
"""Stores the property of a vehicle"""
def __init__(self):
"""Initializes the vehicle properties"""
self._capacity = 15
# Travel speed: 5km/h to convert in m/min
self._speed = 5 * 60 / 3.6
@property
def speed(self):
"""Gets the average travel speed of a vehicle"""
return self._speed
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._vehicle = Vehicle()
# 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 city block dimension
city_block = CityBlock()
self._locations = [(
loc[0]*city_block.width,
loc[1]*city_block.height) for loc in locations]
self._depot = 0
self._demands = \
[0, # depot
1, 1, # 1, 2
2, 4, # 3, 4
2, 4, # 5, 6
8, 8, # 7, 8
1, 2, # 9,10
1, 2, # 11,12
4, 4, # 13, 14
8, 8] # 15, 16
self._time_windows = \
[(0, 0),
(75, 85), (75, 85), # 1, 2
(60, 70), (45, 55), # 3, 4
(0, 8), (50, 60), # 5, 6
(0, 10), (10, 20), # 7, 8
(0, 10), (75, 85), # 9, 10
(85, 95), (5, 15), # 11, 12
(15, 25), (10, 20), # 13, 14
(45, 55), (30, 40)] # 15, 16
@property
def vehicle(self):
"""Gets a vehicle"""
return self._vehicle
@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
@property
def demands(self):
"""Gets demands at each location"""
return self._demands
@property
def time_per_demand_unit(self):
"""Gets the time (in min) to load a demand"""
return 5 # 5 minutes/unit
@property
def time_windows(self):
"""Gets (start time, end time) for each locations"""
return self._time_windows
#######################
# 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 CreateTimeEvaluator(object):
"""Creates callback to get total times between locations."""
@staticmethod
def service_time(data, node):
"""Gets the service time for the specified location."""
return data.demands[node] * data.time_per_demand_unit
@staticmethod
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
def __init__(self, data):
"""Initializes the total time matrix."""
self._total_time = {}
# precompute total time to have time callback in O(1)
for from_node in xrange(data.num_locations):
self._total_time[from_node] = {}
for to_node in xrange(data.num_locations):
if from_node == to_node:
self._total_time[from_node][to_node] = 0
else:
self._total_time[from_node][to_node] = int(
self.service_time(data, from_node) +
self.travel_time(data, from_node, to_node))
def time_evaluator(self, from_node, to_node):
"""Returns the total time between the two nodes"""
return self._total_time[from_node][to_node]
def print_transit_time(route, time_evaluator):
"""Print transit time between nodes of a route"""
total_time = 0
for i, j in route:
total_time += time_evaluator(i, j)
print('{0} -> {1}: {2}min'.format(i, j, time_evaluator(i, j)))
print('Total time: {0}min\n'.format(total_time))
########
# Main #
########
def main():
"""Entry point of the program"""
# Instantiate the data problem.
data = DataProblem()
# Print Transit Time
time_evaluator = CreateTimeEvaluator(data).time_evaluator
print('Route 0:')
print_transit_time([[0, 5], [5, 8], [8, 6], [6, 2], [2, 0]], time_evaluator)
print('Route 1:')
print_transit_time([[0, 9], [9, 14], [14, 16], [16, 10], [10, 0]], time_evaluator)
print('Route 2:')
print_transit_time([[0, 12], [12, 13], [13, 15], [15, 11], [11, 0]], time_evaluator)
print('Route 3:')
print_transit_time([[0, 7], [7, 4], [4, 3], [3, 1], [1, 0]], time_evaluator)
if __name__ == '__main__':
main()

199
examples/python/vrp.py Executable file
View File

@@ -0,0 +1,199 @@
#!/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()

214
examples/python/vrpgs.py Executable file
View File

@@ -0,0 +1,214 @@
#!/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]
def add_distance_dimension(routing, distance_evaluator):
"""Add Global Span constraint"""
distance = "Distance"
routing.AddDimension(
distance_evaluator,
0, # null slack
3000, # 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)
###########
# 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)
add_distance_dimension(routing, 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()