0

0

Google OR-Tools 中实现节点位置依赖的动态路径成本建模

碧海醫心

碧海醫心

发布时间:2026-01-07 13:53:00

|

870人浏览过

|

来源于php中文网

原创

Google OR-Tools 中实现节点位置依赖的动态路径成本建模

本文介绍如何在 or-tools 的 vrp 求解器中建模「同一节点在路径中不同位置(中间 or 终点)具有不同成本」的场景,通过节点复制、活动变量约束与惩罚型 disjunction 技术实现动态成本嵌入。

在标准车辆路径问题(VRP)中,节点成本通常被建模为边权重(如 distance_matrix[i][j])或固定服务成本,但 Google OR-Tools 原生不支持「节点成本随其在路径中的逻辑位置(如是否为最后一个非仓库节点)动态变化」的直接表达。上述问题中,每个客户节点 i 有两个成本:part(作为中间节点时的开销)和 final(作为该车辆路径的最终客户时的开销),而仓库(depot)始终为起点和终点(索引 0)。目标是最小化路径中所有“被访问且承担对应角色”的节点成本之和。

核心思路是将语义上“一个节点两种角色”转化为物理上“两个独立节点”,再通过求解器约束强制互斥选择与角色绑定:

✅ 实现步骤详解

  1. 节点复制(Node Duplication)
    对每个客户节点 i ∈ {1, 2, ..., n},创建两个索引:

    • regular_i:代表“作为中间节点出现”(即路径中非末尾客户)
    • final_i:代表“作为该车辆路径最后一个客户出现”

    Depot(索引 0)保持唯一,但需特殊处理:它既是起点,也作为每条路径的显式终点(0 → ... → final_i → 0),但 final_i → 0 这段弧的成本设为 0,不计入目标函数。

    墨狐AI
    墨狐AI

    5分钟生成万字小说,人人都是小说家!

    下载
  2. 约束节点后继关系(NextVar Control)
    使用 routing.NextVar(index) 显式限制每个节点的合法后继:

    • regular_i 的后继只能是其他 regular_j 或 final_j(不能是 depot),确保它不会成为路径终点;
    • final_i 的后继只能是 depot(0)或 -1(表示被丢弃),即:routing.AddToAssignment(routing.NextVar(final_i) == 0) 或通过 AllowedValues 限定。
  3. 互斥访问约束(Disjunction / ActiveVar Sum)
    防止同一客户被重复访问(如既选 regular_1 又选 final_1):

    # 确保 regular_i 和 final_i 至多被激活一个
    routing.AddVariableMinimizedByFinalizer(routing.ActiveVar(regular_i))
    routing.AddVariableMinimizedByFinalizer(routing.ActiveVar(final_i))
    routing.solver().Add(
        routing.ActiveVar(regular_i) + routing.ActiveVar(final_i) <= 1
    )
  4. 动态成本嵌入(Penalty-based Objective)
    利用 AddDisjunction() 引入软约束,将“未被选中”转化为目标函数中的显式惩罚:

    • 对 regular_i:若未被激活(即未作为中间节点使用),则需支付 part[i] 成本(因客户必须被服务,未被中间服务就只能由 final 承担)→ 但更合理的是:对 final_i 设置 part[i] 的惩罚,表示“若不选 final_i,则必须以 part 方式服务,但此时它无法成为终点” —— 实际建模中,我们反向设计:
      • 为 final_i 添加 disjunction,惩罚值为 part[i]:若 final_i 被丢弃(ActiveVar=0),则加罚 part[i](意味着该客户只能以“中间”方式服务,但需保证其确实在某路径中);
      • 为 regular_i 添加 disjunction,惩罚值为 final[i]:若 regular_i 被丢弃,加罚 final[i](意味着它必须作为终点被服务)。
    • 最终目标函数 = 所有被激活节点的 part/final 成本之和 + 所有触发的惩罚项。
  5. Depot 处理技巧
    将 depot(0)同时设为 Start 和 End:

    for vehicle_id in range(num_vehicles):
        routing.Start(vehicle_id)  # = 0
        routing.End(vehicle_id)    # = 0

    并设置 distance_callback(i, 0) = 0 对所有客户 i,使 final_i → 0 不增加成本,仅用于拓扑闭合。

? 示例代码片段(Python + OR-Tools)

from ortools.constraint_solver import pywrapcp, routing_enums_pb2

# 假设 costs = [{"part":0,"final":0}, {"part":2,"final":3}, ...]
n = len(costs) - 1  # 客户数(不含 depot 0)

# 创建路由模型
manager = pywrapcp.RoutingIndexManager(n * 2 + 1, num_vehicles, 0, 0)
routing = pywrapcp.RoutingModel(manager)

# 定义回调:distance from i to j (0 for any -> depot)
def distance_callback(from_index, to_index):
    from_node = manager.IndexToNode(from_index)
    to_node = manager.IndexToNode(to_index)
    if to_node == 0:  # depot
        return 0
    # 实现客户间距离逻辑(此处简化为 0,重点在 node cost)
    return 0

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

# --- 关键:添加 dynamic node cost via penalties ---
for i in range(1, n + 1):
    regular_idx = manager.NodeToIndex(i)           # original node i
    final_idx   = manager.NodeToIndex(i + n)       # duplicated final node

    # 1. 互斥:regular_i 和 final_i 至多一个 active
    routing.solver().Add(
        routing.ActiveVar(regular_idx) + routing.ActiveVar(final_idx) <= 1
    )

    # 2. 约束 next:regular_i 不能指向 depot;final_i 只能指向 depot
    routing.AddToAssignment(routing.NextVar(regular_idx) != 0)
    routing.AddToAssignment(routing.NextVar(final_idx) == 0)

    # 3. Disjunction penalties
    # 若 final_i 未被选,则罚 part[i](客户需以 part 方式服务)
    routing.AddDisjunction([final_idx], costs[i]["part"], 1)
    # 若 regular_i 未被选,则罚 final[i](客户需以 final 方式服务)
    routing.AddDisjunction([regular_idx], costs[i]["final"], 1)

# 求解...
search_parameters = pywrapcp.DefaultRoutingSearchParameters()
search_parameters.first_solution_strategy = (
    routing_enums_pb2.FirstSolutionStrategy.PATH_CHEAPEST_ARC
)
solution = routing.SolveWithParameters(search_parameters)

⚠️ 注意事项与最佳实践

  • 索引管理务必清晰:建议用字典映射逻辑节点 i 到 regular_idx / final_idx,避免硬编码错误;
  • 惩罚值需足够大:AddDisjunction(penalty) 中的 penalty 应显著大于任何可行路径成本差异,否则求解器可能“故意丢弃节点”来降低总成本(违背服务全覆盖前提);
  • 验证路径结构:解析解时,需识别 final_i 是否被激活,并确认其前驱是否为 regular_j 或其他 final_k(实际应只接 regular 或起始 depot);
  • 性能考量:节点数量翻倍会增加搜索空间,对大规模实例建议结合 LocalSearchMetaheuristic.GUIDED_LOCAL_SEARCH 提升鲁棒性。

该方法虽引入额外变量,但完全兼容 OR-Tools 的 CP-SAT 与 RoutingModel 架构,是解决“位置敏感节点成本”问题的成熟工程实践。完整可运行示例见官方 Gist:https://www.php.cn/link/d42e8faa72ba342df9147902bebb0aac

相关专题

更多
python开发工具
python开发工具

php中文网为大家提供各种python开发工具,好的开发工具,可帮助开发者攻克编程学习中的基础障碍,理解每一行源代码在程序执行时在计算机中的过程。php中文网还为大家带来python相关课程以及相关文章等内容,供大家免费下载使用。

734

2023.06.15

python打包成可执行文件
python打包成可执行文件

本专题为大家带来python打包成可执行文件相关的文章,大家可以免费的下载体验。

631

2023.07.20

python能做什么
python能做什么

python能做的有:可用于开发基于控制台的应用程序、多媒体部分开发、用于开发基于Web的应用程序、使用python处理数据、系统编程等等。本专题为大家提供python相关的各种文章、以及下载和课程。

755

2023.07.25

format在python中的用法
format在python中的用法

Python中的format是一种字符串格式化方法,用于将变量或值插入到字符串中的占位符位置。通过format方法,我们可以动态地构建字符串,使其包含不同值。php中文网给大家带来了相关的教程以及文章,欢迎大家前来阅读学习。

617

2023.07.31

python教程
python教程

Python已成为一门网红语言,即使是在非编程开发者当中,也掀起了一股学习的热潮。本专题为大家带来python教程的相关文章,大家可以免费体验学习。

1258

2023.08.03

python环境变量的配置
python环境变量的配置

Python是一种流行的编程语言,被广泛用于软件开发、数据分析和科学计算等领域。在安装Python之后,我们需要配置环境变量,以便在任何位置都能够访问Python的可执行文件。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

547

2023.08.04

python eval
python eval

eval函数是Python中一个非常强大的函数,它可以将字符串作为Python代码进行执行,实现动态编程的效果。然而,由于其潜在的安全风险和性能问题,需要谨慎使用。php中文网给大家带来了相关的教程以及文章,欢迎大家前来学习阅读。

577

2023.08.04

scratch和python区别
scratch和python区别

scratch和python的区别:1、scratch是一种专为初学者设计的图形化编程语言,python是一种文本编程语言;2、scratch使用的是基于积木的编程语法,python采用更加传统的文本编程语法等等。本专题为大家提供scratch和python相关的文章、下载、课程内容,供大家免费下载体验。

705

2023.08.11

C++ 高性能计算与并行编程
C++ 高性能计算与并行编程

本专题专注于 C++ 在高性能计算(HPC)与并行编程中的应用,涵盖多线程、并发数据处理、OpenMP、MPI、GPU加速等技术。通过实际案例,帮助开发者掌握 如何利用 C++ 进行大规模数据计算和并行处理,提高程序的执行效率,适应高性能计算与数据密集型应用场景。

5

2026.01.08

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
最新Python教程 从入门到精通
最新Python教程 从入门到精通

共4课时 | 0.6万人学习

Django 教程
Django 教程

共28课时 | 2.9万人学习

SciPy 教程
SciPy 教程

共10课时 | 1万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号