VRP 時間窗 (VRPTW)

問題描述

每個客戶指定了「希望送達時間區間」。早到要等,晚到會被客訴。如何規劃路線讓所有客戶都在時間窗內收到貨?

這正是 UberEats、Foodpanda 每日面對的核心問題。

實作 OR-Tools VRPTW

from ortools.constraint_solver import routing_enums_pb2, pywrapcp

def create_data_model():
    """建立含時間窗的資料"""
    data = {}
    # 地點 0 = 餐廳,1-9 = 客戶
    data["time_matrix"] = [
        [0, 6, 9, 8, 7, 3, 6, 2, 3, 4],
        [6, 0, 8, 3, 2, 6, 8, 4, 8, 8],
        [9, 8, 0, 11, 10, 6, 3, 9, 5, 7],
        [8, 3, 11, 0, 1, 7, 10, 6, 10, 9],
        [7, 2, 10, 1, 0, 6, 9, 4, 8, 7],
        [3, 6, 6, 7, 6, 0, 2, 3, 2, 2],
        [6, 8, 3, 10, 9, 2, 0, 6, 2, 3],
        [2, 4, 9, 6, 4, 3, 6, 0, 4, 5],
        [3, 8, 5, 10, 8, 2, 2, 4, 0, 2],
        [4, 8, 7, 9, 7, 2, 3, 5, 2, 0],
    ]
    # 時間窗 [(最早, 最晚), ...] (分鐘)
    data["time_windows"] = [
        (0, 60),    # 餐廳(不須時間窗)
        (5, 20),    # 客戶 1
        (10, 30),   # 客戶 2
        (0, 15),    # 客戶 3
        (20, 40),   # 客戶 4
        (15, 35),   # 客戶 5
        (30, 50),   # 客戶 6
        (0, 25),    # 客戶 7
        (25, 45),   # 客戶 8
        (35, 55),   # 客戶 9
    ]
    data["num_vehicles"] = 3
    data["depot"] = 0
    return data

def print_solution(data, manager, routing, solution):
    print(f"\n=== VRPTW 求解結果 ===")
    for vehicle_id in range(data["num_vehicles"]):
        index = routing.Start(vehicle_id)
        print(f"\n外送員 {vehicle_id} 配送路線:")
        route = []
        while not routing.IsEnd(index):
            node_index = manager.IndexToNode(index)
            time_var = time_dimension.CumulVar(index)
            time_min = solution.Min(time_var)
            time_max = solution.Max(time_var)
            route.append((node_index, time_min, time_max))
            index = solution.Value(routing.NextVar(index))
        
        node_index = manager.IndexToNode(index)
        time_var = time_dimension.CumulVar(index)
        time_min = solution.Min(time_var)
        time_max = solution.Max(time_var)
        route.append((node_index, time_min, time_max))
        
        for node, tmin, tmax in route:
            if node == 0:
                print(f"  餐廳 (回到) [{tmin}-{tmax}]")
            else:
                print(f"  客戶 {node} [{tmin}-{tmax}] (時間窗: {data['time_windows'][node]})")

manager = pywrapcp.RoutingIndexManager(
    len(data["time_matrix"]), data["num_vehicles"], data["depot"]
)
routing = pywrapcp.RoutingModel(manager)

def time_callback(from_index, to_index):
    from_node = manager.IndexToNode(from_index)
    to_node = manager.IndexToNode(to_index)
    return data["time_matrix"][from_node][to_node]

transit_callback_index = routing.RegisterTransitCallback(time_callback)
routing.SetArcCostEvaluatorOfAllVehicles(transit_callback_index)

# 加入時間維度
routing.AddDimension(
    transit_callback_index, 30, 60, False, "Time"
)
time_dimension = routing.GetDimensionOrDie("Time")

# 設定時間窗
for location_idx, (early, late) in enumerate(data["time_windows"]):
    if location_idx == data["depot"]:
        continue
    index = manager.NodeToIndex(location_idx)
    time_dimension.CumulVar(index).SetRange(early, late)

# 設定搜尋參數
search_parameters = pywrapcp.DefaultRoutingSearchParameters()
search_parameters.first_solution_strategy = (
    routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC
)

solution = routing.SolveWithParameters(search_parameters)

if solution:
    print_solution(data, manager, routing, solution)
else:
    print("找不到解!")

解鎖完整教學內容

本章為付費內容。加入專案即可解鎖超過 5000 字的深度解析,包含 10 個以上神級 Prompt 與真實 Source Code 範例!