Professional Documents
Culture Documents
PDC Review 3
PDC Review 3
CSE 4001
Review - 3
Project Members
Submitted to
Prof. Manoov R.
Abstract
Vehicle routing problems (VRPs) are integral to logistics management both in the
private and public sectors. It involves determining the correct vehicle routes using
a set of users, subject to additional terms.
The most common operational constraints while solving VRPs are the total
demand carried by a vehicle at any time does not exceed a given capacity, the
total duration of any route is not greater than a predetermined bound, and service
time windows set by customers are respected. In long haul routing, vehicles are
usually assigned one task at a time, while in short-haul routing, tasks are of short
duration (much shorter than a work shift) and a tour that is built through a
sequence of tasks. There exist several important problems that need to be
solved in real-time. Given below are some of the key applications that promote
research in the field of real-time VRPs.
● Anaconda Spyder
● Parallel Python Module
● Pyro library
● Intel's Vtune amplifier (for debugging and fixing performance bottlenecks)
● Datasets to use as test values to simulate real-time situations
(http://vrp-rep.org/datasets.html)
Literature Review
Given a graph and a source vertex in the graph, we find shortest paths from
source to all vertices in the given graph.
For this project we will use Dijkstra's algorithm, but implement it in a way which
takes all the constraints regarding VRP in consideration.
Dijkstra’s algorithm involves creating a minimum spanning tree. Like Prim’s MST,
we generate a SPT (shortest path tree) with a given source as the root. We
maintain two sets, one set contains vertices included in the shortest path tree,
and another set includes vertices not yet included in the shortest path tree. At
every step of the algorithm, we find a vertex which is in the other set (set of not
yet included) and has a minimum distance from the source.
Algorithm
1) Create a set called the shortest path tree set (sptSet) that keeps track of
vertices included in shortest path tree, i.e., whose minimum distance from source
is calculated and finalized.
2) Assign INFINITE value to all vertices in the input graph. Assign the distance
value for source vertex as 0
3) While the Set doesn’t include all vertices
a) pick any vertex u which is not in shortest path tree set and having
minimum distance value.
b) Add u to sptSet.
c) Update distance value of all adjacent vertices of u. To update the
distance values, iterate through all adjacent vertices. For every adjacent vertex v,
if the sum of distance value of u (from source) and weight of edge u-v, is less
than the distance value of v, then update the distance value of v.
Serial Code
###########################
# Problem Data Definition #
###########################
# Vehicle declaration
Vehicle = namedtuple('Vehicle', ['capacity'])
# City block declaration
CityBlock = namedtuple('CityBlock', ['width', 'height'])
class DataProblem():
"""Stores the data for the problem"""
def __init__(self):
"""Initializes the data for the problem"""
# Locations in block unit
locations = \
[(4, 4), # depot
(2, 0), (8, 0), # order location
(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 dimensiondefined as follow
# Manhattan average block: 750ft x 264ft -> 228m x 80m
# here we use: 114m x 80m city block
city_block = CityBlock(width=228 / 2, height=80)
self._locations = [(loc[0] * city_block.width, loc[1] *city_block.height) for loc
in locations]
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
@property
def vehicle(self):
"""Gets a vehicle"""
return Vehicle(capacity=15)
@property
def num_vehicles(self):
"""Gets number of vehicles"""
return 4
@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 0
@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 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
###########
# Printer #
###########
def print_solution(data, routing, assignment):
"""Prints assignment on console"""
print('Objective: {}'.format(assignment.ObjectiveValue()))
total_distance = 0
total_load = 0
capacity_dimension = routing.GetDimensionOrDie('Capacity')
for vehicle_id in xrange(data.num_vehicles):
index = routing.Start(vehicle_id)
plan_output = 'Route for vehicle {}:\n'.format(vehicle_id)
distance = 0
while not routing.IsEnd(index):
load_var = capacity_dimension.CumulVar(index)
plan_output += ' {} Load({}) -> '.format(
routing.IndexToNode(index),
assignment.Value(load_var))
previous_index = index
index = assignment.Value(routing.NextVar(index))
distance += routing.GetArcCostForVehicle(previous_index, index,
vehicle_id)
load_var = capacity_dimension.CumulVar(index)
plan_output += ' {0} Load({1})\n'.format(
routing.IndexToNode(index),
assignment.Value(load_var))
plan_output += 'Distance of the route: {}m\n'.format(distance)
plan_output += 'Load of the route:
{}\n'.format(assignment.Value(load_var))
print(plan_output)
total_distance += distance
total_load += assignment.Value(load_var)
print('Total Distance of all routes: {}m'.format(total_distance))
print('Total Load of all routes: {}'.format(total_load))
########
# 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)
# pylint: disable=no-member
# Solve the problem.
assignment =routing.SolveWithParameters(search_parameters)
print_solution(data, routing, assignment)
if __name__ == '__main__':
tic = time.perf_counter()
main()
toc = time.perf_counter()
###########################
# 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 capacity(self):
"""Gets vehicle capacity"""
return self._capacity
@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()
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._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 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
@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 CreateDistanceEvaluator(object):
"""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]))
class CreateDemandEvaluator(object):
"""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, # 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):
"""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 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
False, # don't force start cumul to zero since we are giving TW to start
nodes
time)
time_dimension = routing.GetDimensionOrDie(time)
for location_idx, time_window in enumerate(data.time_windows):
if location_idx == 0:
continue
index = routing.NodeToIndex(location_idx)
time_dimension.CumulVar(index).SetRange(time_window[0],
time_window[1])
routing.AddToAssignment(time_dimension.SlackVar(index))
for vehicle_id in xrange(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))
###########
# 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.
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)
slack_var = time_dimension.SlackVar(index)
slack_min = self.assignment.Min(slack_var)
slack_max = self.assignment.Max(slack_var)
plan_output += ' {0} Load({1}) Time({2},{3})Slack({4},{5})
->'.format(node_index,route_load,time_min, time_max,slack_min, slack_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)
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"""
# 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)
# 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)
printer = ConsolePrinter(data, routing, assignment)
printer.print()
if __name__ == '__main__':
tic = time.perf_counter()
main()
toc = time.perf_counter()
Output
Result Obtained
Conclusion
In recent times, a lot of literature has been devoted to VRPs. For further scope
and advancement in solving VRPs, research work in the future may be directed
towards some of the issues presented ahead.
One issue that can be looked upon in the future is adding capabilities in
heuristics for future predictions to handle current requests optimally not only with
respect to current timestamp but in such a way that future demands can also be
serviced near- optimally. Routing and dispatching problems pertaining to varying
travel durations in real-time can also be looked upon which might help in the
development of intelligent transportation systems. VRPs related to emergency
services are also a vast area of research as their implications and impacts are
global. Finally, automated guided vehicle routing and dispatching problems,
which mostly are observed in port terminals and in manufacturing plants, can be
addressed. The results obtained on parallel python are more time constraint and
has a better result.
References
[1] Juan, A. A., Faulin, J., Jorba, J., Caceres, J., & Marquès, J. M. (2011).
Using parallel & distributed computing for real-time solving of vehicle
routing problems with stochastic demands. Annals of Operations Research,
207(1), 43–65. doi:10.1007/s10479-011-0918-z
[4] Y. Jin and Y. Z. Ding, “Research on VRPTW model with the restricts of
both weight and volume,” Logistics Technology, vol. 32, no.4, pp. 53-56,
2009.
[5] G. Zhao and Y. F. Zhang, “On the cold chain logistics routing
optimization,” in 30th Chinese Control Conference, Yantai, People’s Republic
Of China, 2011, pp. 2063-2068.
[6] M. Solomon, “Algorithms for the Vehicle Routing and Scheduling Problem
with Time Window Constraints,” Operations Research, Vol. 35, No. 2, 1987, pp.
254-265.doi:10.1287/opre.35.2.254
[8] P. Toth and D. Vigo, “An Overview of Vehicle Routing Problems,” SIAM
Monographs on Discrete Mathematics and Applications, 2002,
pp.1-26.doi:10.1137/1.9780898718515.ch1
[10] M.L. Balinski and R.E. Quandt (1964): On an Integer Program for a
Delivery Problem. Operations Research 12, 300–30
[11] U. Blasum and W. Hochst ̈attler (2000): Application of the Branch and Cut
Method to the Vehicle Routing Problem. Zentrum f ̈ur Angewandte Informatik,
K ̈oln, Technical Report zpr2000-386
[12] V. Campos,A. Corber ́an, and E. Mota (1991): Polyhedral Results for
aVehicle Routing Problem. European Journal of Operations Research 52, 75–85
[15] G. Cornu ́ejols and F. Harche (1993): Polyhedral Study of the Capacitated
Vehicle Routing Problem. Mathematical Programming 60, 21–52
[16] F.H. Cullen, J.J. Jarvis and H.D. Ratliff (1981): Set Partitioning Based
Heuristic for Interactive Routing. Networks 11, 125–144
[17] G.B. Dantzig and R.H. Ramser (1959): The Truck Dispatching Problem.
Management Science 6, 80–9155
[18] M.L. Fisher (1988): Optimal Solution of Vehicle Routine Problems Using
Minimum k-Trees. Operations Research 42, 626–642
[20] B.A. Foster and D.M. Ryan (1976): An Integer Programming Approach to the
Vehicle Scheduling Problem. Operational Research Quarterly 27, 367–384
[21] M.R. Garey and D.S. Johnson (1979): Computers and Intractability: A
Guide to the Theory of NP-Completeness. W.H. Freeman and Co., San Francisco
[22] M. Held and R.M. Karp (1969): The Traveling Salesman Problem and Minimal
Spanning Trees. Operations Research 18, 1138–1162
[23] L. Kopman (1999): A New Generic Separation Routine and Its Application
in a Branch and Cut Algorithm for the Vehicle Routing Problem. Ph.D
Dissertation, Field of Operations Research, Cornell University, Ithaca, NY,
USA
[24] L. Lad ́anyi (1996): Parallel Branch and Cut and Its Application to the
Traveling Salesman Problem. Ph.D. Dissertation, Field of Operations Research,
Cornell University, Ithaca, NY, USA
[25] L. Lad ́anyi, T.K. Ralphs, and L.E. Trotter (2001): Branch, Cut, and
Price: Sequential and Parallel. Computational Combinatorial Optimization, D.
Naddef and M. J ̈unger, eds., Springer, Berlin, 223–260
[26] https://pythonhosted.org/Pyro/
[27] https://pythonhosted.org/Pyro/1-intro.html
[28] https://www.geeksforgeeks.org/dijkstras-shortest-path-algorithm-greedy-algo-7/