OR-Tools v8.2 Python实现多行程CVRP:约束车辆返回depot前须到独立卸货点卸货
适配OR-Tools v8.2 Python的多行程带容量VRP实现方案
核心目标是修正官方cvrp_reload示例的路线缺陷,强制车辆返回depot前必须完成卸货,不得携带载重回depot。
核心约束逻辑拆解
对比官方示例的不足,需要额外落地两个强制规则:
- 车辆抵达depot终点时,实时载重必须为0
- 单条完整路线内,至少到访1次独立卸货点
- 保留原有业务逻辑:车辆收集需求点货物达到容量上限时,自主前往卸货点清空载重后继续收集
分步实现流程
1. 基础维度配置
容量维度配置
- 按常规CVRP逻辑配置车辆最大载重上限,每个需求点对应固定的收集量
- 参考官方cvrp_reload的逻辑,配置卸货点载重清零规则:只要路线经过独立卸货点,就将当前累计载重直接重置为0,清空后继续收集剩余需求
- 记得给独立卸货点添加可选访问配置,调用
routing.AddDisjunction([unloading_node_id], penalty=0),允许求解器在路线中多次插入卸货点行程,不要把卸货点设置成单次必须访问的节点
卸货计数维度配置
也就是你之前推测可用的count_dimension用法:
- 新建一个累计计数维度,规则为:路线每经过一次卸货点,累计计数+1,经过其他节点时计数不变
- 维度设置时把最大累计值设为足够大的数(比如100,远大于单条路线可能的最大卸货次数),避免求解约束冲突
2. 核心强制约束添加
遍历每一辆车的路线终点(即depot节点),给求解器加两个硬约束:
- 从容量维度取车辆抵达depot时的载重变量值,强制要求该值等于0,不允许带载重入depot
- 从计数维度取车辆抵达depot时的累计计数值,强制要求该值≥1,不允许全程一次卸货点都没去就直接回depot
3. 细节注意事项
- 不要把depot节点纳入载重清零逻辑的触发范围,否则车辆会直接从最后一个需求点回depot时就清空载重,完全绕过卸货点
- 卸货点要配置正确的时间窗:如果有运营时段限制,要和需求节点一样给卸货点配时间窗,避免求解器安排在非运营时段安排卸货行程
- 车辆的最大行程时间约束要覆盖多次往返卸货点的路程,不要把总时间设得过短,否则求解器会判定问题不可行
关键代码片段参考
# 初始化需求回调,读取每个节点的固定收集量 def demand_callback(from_index): from_node = routing.IndexToNode(from_index) return demands[from_node] # 创建容量维度,绑定车辆最大载重能力 CAPACITY_DIM = "Capacity" routing.AddDimensionWithVehicleCapacity( demand_callback, slack_max=0, vehicle_capacity=vehicle_capacities, fix_start_cumul_to_zero=True, dimension_name=CAPACITY_DIM ) cap_dim = routing.GetDimensionOrDie(CAPACITY_DIM) # 配置卸货点载重清零逻辑 def cap_transit_callback(from_index, to_index): to_node = routing.IndexToNode(to_index) # 如果下一个节点是独立卸货点,清空当前累计载重 if to_node == unloading_node_id: return -cap_dim.CumulVar(from_index) return 0 cap_dim.SetTransitionCallback(cap_transit_callback) # 创建卸货计数维度 COUNT_DIM = "UnloadCounter" def count_callback(from_index): from_node = routing.IndexToNode(from_index) return 1 if from_node == unloading_node_id else 0 routing.AddDimension( evaluator=count_callback, slack_max=0, cumul_max=100, fix_start_cumul_to_zero=True, dimension_name=COUNT_DIM ) count_dim = routing.GetDimensionOrDie(COUNT_DIM) # 添加核心硬约束 for vehicle_id in range(manager.GetNumberOfVehicles()): end_route_index = routing.End(vehicle_id) # 回depot时载重必须为0 solver.Add(cap_dim.CumulVar(end_route_index) == 0) # 回depot前至少经过1次卸货点 solver.Add(count_dim.CumulVar(end_route_index) >= 1)
效果说明
加上上述约束后,求解器会自动避开官方示例中「从最后一个需求点直接回depot」的低成本路线选择,强制在回depot前插入最后一次卸货行程,最终生成的路线会完全匹配合规示例:0 > 2 > 3 > 4 > 1 (unload) > 5 > 6 > 7 > 1 (unload) > 0。
内容的提问来源于stack exchange,提问作者mert
相关产品推荐
相关产品推荐

