ortools系列:時間窗約束VRP問題

ortools系列:時間窗約束VRP問題

1. VRPTW

許多車輛路徑問題都涉及到安排送貨時間,這些問題的一個自然約束是,客戶隻能在指定的時間窗口內接收交付或服務調用,這些問題稱為時間窗車輛路徑問題(VRPTW)。

下圖以藍色表示客戶地點,黑色表示倉庫。時間窗口(以分鐘為單位)顯示在每個位置的上方。

目標是最小化在時間窗口內為所有客戶提供所需的服務時間和等待時間之和。

前面我們講瞭普通VRP和CVRP問題,這裡的VRPTW問題相信你也明白,隻需要添加時間窗口約束接口。

from ortools.constraint_solver import pywrapcp
from ortools.constraint_solver import routing_enums_pb2

# 創建數據集
def create_data_model():
data = {}
# 不同客戶節點之間的距離
_distances =
[
[0, 548, 776, 696, 582, 274, 502, 194, 308, 194, 536, 502, 388, 354, 468, 776, 662],
[548, 0, 684, 308, 194, 502, 730, 354, 696, 742, 1084, 594, 480, 674, 1016, 868, 1210],
[776, 684, 0, 992, 878, 502, 274, 810, 468, 742, 400, 1278, 1164, 1130, 788, 1552, 754],
[696, 308, 992, 0, 114, 650, 878, 502, 844, 890, 1232, 514, 628, 822, 1164, 560, 1358],
[582, 194, 878, 114, 0, 536, 764, 388, 730, 776, 1118, 400, 514, 708, 1050, 674, 1244],
[274, 502, 502, 650, 536, 0, 228, 308, 194, 240, 582, 776, 662, 628, 514, 1050, 708],
[502, 730, 274, 878, 764, 228, 0, 536, 194, 468, 354, 1004, 890, 856, 514, 1278, 480],
[194, 354, 810, 502, 388, 308, 536, 0, 342, 388, 730, 468, 354, 320, 662, 742, 856],
[308, 696, 468, 844, 730, 194, 194, 342, 0, 274, 388, 810, 696, 662, 320, 1084, 514],
[194, 742, 742, 890, 776, 240, 468, 388, 274, 0, 342, 536, 422, 388, 274, 810, 468],
[536, 1084, 400, 1232, 1118, 582, 354, 730, 388, 342, 0, 878, 764, 730, 388, 1152, 354],
[502, 594, 1278, 514, 400, 776, 1004, 468, 810, 536, 878, 0, 114, 308, 650, 274, 844],
[388, 480, 1164, 628, 514, 662, 890, 354, 696, 422, 764, 114, 0, 194, 536, 388, 730],
[354, 674, 1130, 822, 708, 628, 856, 320, 662, 388, 730, 308, 194, 0, 342, 422, 536],
[468, 1016, 788, 1164, 1050, 514, 514, 662, 320, 274, 388, 650, 536, 342, 0, 764, 194],
[776, 868, 1552, 560, 674, 1050, 1278, 742, 1084, 810, 1152, 274, 388, 422, 764, 0, 798],
[662, 1210, 754, 1358, 1244, 708, 480, 856, 514, 468, 354, 844, 730, 536, 194, 798, 0]
]

# 每個客戶的需求量,0 表示改節點是倉庫,倉庫是沒有需求
demands = [0, 1, 1, 2, 1, 2, 4, 8, 8, 1, 2, 1, 2, 4, 4, 8, 8]

# 每個節點的時間窗口約束
time_windows =
[(0, 0),
(75, 85),
(75, 85),
(60, 70),
(45, 55),
(0, 8),
(50, 60),
(0, 10),
(10, 20),
(0, 10),
(75, 85),
(85, 95),
(5, 15),
(15, 25),
(10, 20),
(45, 55),
(30, 40)]

# 距離矩陣
data["distances"] = _distances
# 網絡節點數量
data["num_locations"] = len(_distances)
# 車輛數量
data["num_vehicles"] = 4
# 倉庫索引,我們假設所有的車輛都從同一地點出發,也就是車場。
# 或者,你可以允許車輛在任何位置啟動和結束。
data["depot"] = 0
# 需求
data["demands"] = demands
# 時間窗
data["time_windows"] = time_windows
# 假設每個客戶的服務時間的5分鐘
data["time_per_demand_unit"] = 5
# 車輛平均行駛速度
data["vehicle_speed"] = 83.33
return data

#######################
# Problem Constraints #
#######################
def create_distance_callback(data):
"""Creates callback to return distance between points."""
distances = data["distances"]

def distance_callback(from_node, to_node):
"""Returns the manhattan distance between the two nodes"""
return distances[from_node][to_node]

return distance_callback

# 時間窗口約束的回調函數
def create_time_callback(data):
"""Creates callback to get total times between locations."""

def service_time(node):
"""Gets the service time for the specified location."""
return data["demands"][node] * data["time_per_demand_unit"]

def travel_time(from_node, to_node):
"""Gets the travel times between two locations."""
travel_time = data["distances"][from_node][to_node] / data["vehicle_speed"]
return travel_time

def time_callback(from_node, to_node):
"""Returns the total time between the two nodes"""
serv_time = service_time(from_node)
trav_time = travel_time(from_node, to_node)
return serv_time + trav_time

return time_callback

# 和容量約束的維度約束類似,這裡需要添加時間窗口的約束,也是用 routing.AddDimension 函數
def add_time_window_constraints(routing, data, time_callback):
"""Add time window constraints."""
time = "Time"
horizon = 120
routing.AddDimension(
time_callback,
horizon, # allow waiting time
horizon, # maximum time per vehicle
False, # Don't force start cumul to zero. This doesn't have any effect in this example,
# since the depot has a start window of (0, 0).
time)

time_dimension = routing.GetDimensionOrDie(time)
for location_node, location_time_window in enumerate(data["time_windows"]):
index = routing.NodeToIndex(location_node)
time_dimension.CumulVar(index).SetRange(location_time_window[0], location_time_window[1])

# 打印結果
def print_solution(data, routing, assignment):
"""Prints assignment on console"""
# Inspect solution.
time_dimension = routing.GetDimensionOrDie('Time')
total_dist = 0
time_matrix = 0

for vehicle_id in range(data["num_vehicles"]):
index = routing.Start(vehicle_id)
plan_output = 'Route for vehicle {0}:n'.format(vehicle_id)
route_dist = 0
while not routing.IsEnd(index):
node_index = routing.IndexToNode(index)
next_node_index = routing.IndexToNode(
assignment.Value(routing.NextVar(index)))
route_dist += routing.GetArcCostForVehicle(node_index, next_node_index, vehicle_id)
time_var = time_dimension.CumulVar(index)
time_min = assignment.Min(time_var)
time_max = assignment.Max(time_var)
plan_output += ' {0} Time({1},{2}) ->'.format(node_index, time_min, time_max)
index = assignment.Value(routing.NextVar(index))

node_index = routing.IndexToNode(index)
time_var = time_dimension.CumulVar(index)
route_time = assignment.Value(time_var)
time_min = assignment.Min(time_var)
time_max = assignment.Max(time_var)
total_dist += route_dist
time_matrix += route_time
plan_output += ' {0} Time({1},{2})n'.format(node_index, time_min, time_max)
plan_output += 'Distance of the route: {0} mn'.format(route_dist)
plan_output += 'Time of the route: {0} minn'.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(time_matrix))

# 主函數
def main():

# 創建數據集
data = create_data_model()

# 創建VRP路由模型
routing = pywrapcp.RoutingModel(data["num_locations"], data["num_vehicles"], data["depot"])

# Define weight of each edge
# 提供距離回調,以便解決程序可以計算位置之間的距離。
distance_callback = create_distance_callback(data)
routing.SetArcCostEvaluatorOfAllVehicles(distance_callback)

# Add Time Window constraint
# 添加時間窗口約束
time_callback = create_time_callback(data)
add_time_window_constraints(routing, data, time_callback)

# 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)
if assignment:
printer = print_solution(data, routing, assignment)

if __name__ == '__main__':
main()

# 結果
Route for vehicle 0:
0 Time(0,0) -> 12 Time(5,13) -> 13 Time(17,25) -> 15 Time(45,52) -> 11 Time(88,95) -> 0 Time(99,120)
Distance of the route: 1780 m
Time of the route: 99 min

Route for vehicle 1:
0 Time(0,3) -> 5 Time(3,6) -> 8 Time(15,18) -> 6 Time(57,60) -> 2 Time(80,85) -> 0 Time(94,120)
Distance of the route: 1712 m
Time of the route: 94 min

Route for vehicle 2:
0 Time(0,8) -> 7 Time(2,10) -> 4 Time(46,55) -> 3 Time(60,70) -> 1 Time(75,85) -> 0 Time(86,120)
Distance of the route: 1552 m
Time of the route: 86 min

Route for vehicle 3:
0 Time(0,8) -> 9 Time(2,10) -> 14 Time(10,18) -> 16 Time(32,40) -> 10 Time(76,85) -> 0 Time(92,120)
Distance of the route: 1552 m
Time of the route: 92 min

Total Distance of all routes: 6596 m
Total Time of all routes: 371 min

赞(0)