Files
ortools-clone/examples/python/cvrptw.py
2016-10-27 17:59:23 +02:00

262 lines
9.1 KiB
Python

# This Python file uses the following encoding: utf-8
# Copyright 2015 Tin Arm Engineering AB
# 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 with Time Windows (and optional orders).
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,
time windows and optional orders, with a penalty cost if orders are not
performed.
Too help explore the problem, two classes are provided Customers() and
Vehicles(): used to randomly locate orders and depots, and to randomly
generate demands, time-window constraints and vehicles.
Distances are computed using the Great Circle distances. Distances are in km
and times in seconds.
A function for the displaying of the vehicle plan
display_vehicle_output
The optimization engine uses local search to improve solutions, first
solutions being generated using a cheapest addition heuristic.
"""
import math
from six.moves import xrange
from ortools.constraint_solver import pywrapcp
from ortools.constraint_solver import routing_enums_pb2
def distance(x1, y1, x2, y2):
# Manhattan distance
dist = abs(x1 - x2) + abs(y1 - y2)
return dist
# Distance callback
class CreateDistanceCallback(object):
"""Create callback to calculate distances and travel times between points."""
def __init__(self, locations):
"""Initialize distance array."""
num_locations = len(locations)
self.matrix = {}
for from_node in xrange(num_locations):
self.matrix[from_node] = {}
for to_node in xrange(num_locations):
if from_node == to_node:
self.matrix[from_node][to_node] = 0
else:
x1 = locations[from_node][0]
y1 = locations[from_node][1]
x2 = locations[to_node][0]
y2 = locations[to_node][1]
self.matrix[from_node][to_node] = distance(x1, y1, x2, y2)
def Distance(self, from_node, to_node):
return self.matrix[from_node][to_node]
# Demand callback
class CreateDemandCallback(object):
"""Create callback to get demands at location node."""
def __init__(self, demands):
self.matrix = demands
def Demand(self, from_node, to_node):
return self.matrix[from_node]
# Service time (proportional to demand) callback.
class CreateServiceTimeCallback(object):
"""Create callback to get time windows at each location."""
def __init__(self, demands, time_per_demand_unit):
self.matrix = demands
self.time_per_demand_unit = time_per_demand_unit
def ServiceTime(self, from_node, to_node):
return self.matrix[from_node] * self.time_per_demand_unit
# Create total_time callback (equals service time plus travel time).
class CreateTotalTimeCallback(object):
def __init__(self, service_time_callback, dist_callback, speed):
self.service_time_callback = service_time_callback
self.dist_callback = dist_callback
self.speed = speed
def TotalTime(self, from_node, to_node):
service_time = self.service_time_callback(from_node, to_node)
travel_time = self.dist_callback(from_node, to_node) / self.speed
return service_time + travel_time
def main():
# Create the data.
data = create_data_array()
locations = data[0]
demands = data[1]
start_times = data[2]
num_locations = len(locations)
depot = 0
num_vehicles = 5
search_time_limit = 400000
# Create routing model.
if num_locations > 0:
# 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.
routing = pywrapcp.RoutingModel(num_locations, num_vehicles, depot)
search_parameters = pywrapcp.RoutingModel.DefaultSearchParameters()
# Setting first solution heuristic: the
# method for finding a first solution to the problem.
search_parameters.first_solution_strategy = (
routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC)
# The 'PATH_CHEAPEST_ARC' method does the following:
# Starting from a route "start" node, connect it to the node which produces the
# cheapest route segment, then extend the route by iterating on the last
# node added to the route.
# Put callbacks to the distance function and travel time functions here.
dist_between_locations = CreateDistanceCallback(locations)
dist_callback = dist_between_locations.Distance
routing.SetArcCostEvaluatorOfAllVehicles(dist_callback)
demands_at_locations = CreateDemandCallback(demands)
demands_callback = demands_at_locations.Demand
# Adding capacity dimension constraints.
VehicleCapacity = 100;
NullCapacitySlack = 0;
fix_start_cumul_to_zero = True
capacity = "Capacity"
routing.AddDimension(demands_callback, NullCapacitySlack, VehicleCapacity,
fix_start_cumul_to_zero, capacity)
# Adding time dimension constraints.
time_per_demand_unit = 300
horizon = 24 * 3600
time = "Time"
tw_duration = 5 * 3600
speed = 10
service_times = CreateServiceTimeCallback(demands, time_per_demand_unit)
service_time_callback = service_times.ServiceTime
total_times = CreateTotalTimeCallback(service_time_callback, dist_callback, speed)
total_time_callback = total_times.TotalTime
# Add a dimension for time-window constraints and limits on the start times and end times.
routing.AddDimension(total_time_callback, # total time function callback
horizon,
horizon,
fix_start_cumul_to_zero,
time)
# Add limit on size of the time windows.
time_dimension = routing.GetDimensionOrDie(time)
for order in xrange(1, num_locations):
start = start_times[order]
time_dimension.CumulVar(order).SetRange(start, start + tw_duration)
# Solve displays a solution if any.
assignment = routing.SolveWithParameters(search_parameters)
if assignment:
data = create_data_array()
locations = data[0]
demands = data[1]
start_times = data[2]
size = len(locations)
# Solution cost.
print ("Total distance of all routes: " , str(assignment.ObjectiveValue()))
# Inspect solution.
capacity_dimension = routing.GetDimensionOrDie(capacity);
time_dimension = routing.GetDimensionOrDie(time);
for vehicle_nbr in xrange(num_vehicles):
index = routing.Start(vehicle_nbr)
plan_output = 'Route {0}:'.format(vehicle_nbr)
while not routing.IsEnd(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}) -> ".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})".format(
node_index=node_index,
load=assignment.Value(load_var),
tmin=str(assignment.Min(time_var)),
tmax=str(assignment.Max(time_var)))
print (plan_output)
else:
print ('No solution found.')
else:
print ('Specify an instance greater than 0.')
def create_data_array():
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]]
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]
start_times = [28842, 50891, 10351, 49370, 22553, 53131, 8908,
56509, 54032, 10883, 60235, 46644, 35674, 30304,
39950, 38297, 36273, 52108, 2333, 48986, 44552,
31869, 38027, 5532, 57458, 51521, 11039, 31063,
38781, 49169, 32833, 7392]
data = [locations, demands, start_times]
return data
if __name__ == '__main__':
main()