You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

VRPTW中Depot节点的时间窗与松弛变量处理问题咨询

关于OR-Tools时间窗路径规划的两个问题解答

我来帮你拆解这两个在使用OR-Tools做带时间窗VRP时遇到的常见问题,这些细节确实容易踩坑:


问题1:能否为Depot分别定义出发和到达时间窗?

答案是可以的,但不能直接给同一个depot节点设置单一的时间窗——因为你的路线里depot既是起点又是终点,直接用time_dimension.CumulVar(node).SetRange()会同时约束车辆出发和到达depot的时间,这显然不是你想要的。

这里有两种靠谱的解决思路:

思路1:拆分Depot为“出发节点”和“到达节点”

把原来的depot(比如node 0)拆成两个逻辑节点:

  • 用node 0作为车辆的出发 depot,设置它的时间窗为早晨出发时段(比如(7*60, 8*60))
  • 新增一个虚拟节点(比如node 5,假设你原来有5个站点)作为到达 depot,设置它的时间窗为傍晚到达时段(比如(18*60, 20*60))
  • 然后设置车辆的起始节点为0,终止节点为5,同时给这两个节点之间添加一条虚拟路径(成本为0,时间为0)

代码示例大概是这样:

# 假设原来有5个节点(0-4),新增到达depot为5
routing.AddNode(5)
# 设置车辆的起始和终止节点
routing.SetStart(vehicle, 0)
routing.SetEnd(vehicle, 5)
# 添加0到5的虚拟路径(如果需要的话,或者让求解器自动处理)
routing.AddEdge(0, 5, 0)
# 设置出发depot的时间窗
time_dimension.CumulVar(0).SetRange(7*60, 8*60)
# 设置到达depot的时间窗
time_dimension.CumulVar(5).SetRange(18*60, 20*60)

思路2:直接给车辆的起始/结束累积时间加约束

如果你不想新增节点,可以直接针对车辆的起始和结束状态单独加约束,而不是给depot节点加全局时间窗:

# 设置车辆出发depot的时间窗(仅约束起始累积时间)
routing.solver().Add(
    time_dimension.CumulVar(routing.Start(vehicle)) >= 7*60
)
routing.solver().Add(
    time_dimension.CumulVar(routing.Start(vehicle)) <= 8*60
)

# 设置车辆返回depot的时间窗(约束结束累积时间)
routing.solver().Add(
    time_dimension.CumulVar(routing.End(vehicle)) >= 18*60
)
routing.solver().Add(
    time_dimension.CumulVar(routing.End(vehicle)) <= 20*60
)

# 然后给其他非depot节点设置时间窗
for node in range(1, 5):
    time_dimension.CumulVar(node).SetRange(9*60, 10*60)

这种方法更简洁,不需要修改节点结构,直接通过求解器的约束来分别控制出发和到达时间。


问题2:如何正确读取包括Depot在内的所有节点的时间窗松弛变量?

你遇到的SystemError: returned NULL without setting an error,本质原因是:你遍历的indices列表里包含了路由的End虚拟索引,而这个索引对应的不是实际节点,没有对应的松弛变量。

当你用routing.IsEnd(index)判断结束时,最后一个添加到indices里的是routing.End(vehicle)对应的索引,这个索引是OR-Tools内部的虚拟节点,不存在SlackVar,所以调用time_dimension.SlackVar(x)会返回NULL,导致程序崩溃。

解决方法很简单:过滤掉虚拟的End索引,只处理实际存在的节点对应的索引,并且注意Depot节点的松弛变量是否存在(默认情况下,起始节点如果设置了fix_start_cumul_to_zero=True,可能没有松弛变量,需要判断)。

正确的读取代码可以这样写:

# 先获取车辆的实际路线索引(排除End虚拟节点)
index = routing.Start(vehicle)
valid_indices = [index]
while not routing.IsEnd(index):
    next_index = assignment.Value(routing.NextVar(index))
    # 只添加非End的索引
    if not routing.IsEnd(next_index):
        valid_indices.append(next_index)
    index = next_index

# 然后读取每个有效索引对应的松弛变量
slacks = []
for idx in valid_indices:
    node = routing.IndexToNode(idx)
    # 检查该节点是否有松弛变量(避免起始节点的情况)
    try:
        slack_var = time_dimension.SlackVar(node)
        slack_val = assignment.Value(slack_var)
        slacks.append(slack_val)
    except:
        # 如果没有松弛变量(比如起始节点),可以添加0或者跳过
        slacks.append(0)

另外还要注意:当你设置fix_start_cumul_to_zero=False时,起始节点(depot)是有松弛变量的;如果设置为True,起始节点的累积时间被固定为0,所以没有松弛变量,这时候读取会报错,需要跳过。


内容的提问来源于stack exchange,提问作者Leevi L

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.29 07:43:06