ortools系列:時間窗約束VRP問題
許多車輛路徑問題都涉及到安排送貨時間,這些問題的一個自然約束是,客戶只能在指定的時間窗口內接收交付或服務調用,這些問題稱為時間窗車輛路徑問題(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, # Dont force start cumul to zero. This doesnt 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}: .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}) .format(node_index, time_min, time_max) plan_output += Distance of the route: {0} m .format(route_dist) plan_output += Time of the route: {0} min .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
注意add_time_window_constraints函數中的routing.AddDimension調用,在容量約束問題中我們調用了routing.AddDimensionWithVehicleCapacity,相應的,在時間約束問題中,也要在相應的維度上做約束。
add_time_window_constraints
routing.AddDimension
routing.AddDimensionWithVehicleCapacity
為了創建時間維度,我們需要計算如下:
服務時間
地點之間行駛時間
地點間總時間
這三個時間分別對應時間回調函數中的:service_time(node),travel_time(from_node, to_node),time_callback(from_node, to_node).
service_time(node)
travel_time(from_node, to_node)
time_callback(from_node, to_node)
然後我們看如何添加時間窗口的維度約束。和容量約束的維度約束類似,這裡需要添加時間窗口的約束,也是用 routing.AddDimension 函數,看下面的代碼片段:
routing.AddDimension( time_callback, horizon, # allow waiting time horizon, # maximum time per vehicle False, # Dont force start cumul to zero. This doesnt have any effect in this example, # since the depot has a start window of (0, 0). time)
AddDimension方法的參數如下:
AddDimension
大家看完記得關注點贊.
master蘇.