十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

FSTSP复现实战:卡车-无人机协同配送路径优化与Gurobi求解

FSTSP复现实战:卡车-无人机协同配送路径优化与Gurobi求解 去年在复现 The Flying Sidekick Traveling Salesman ProblemFSTSP这篇经典论文时我把整个求解链路从建模到 Gurobi 调参完整走了一遍。这个问题的核心一句话就能讲清楚一辆卡车带着一架无人机从仓库出发一起去给客户送货无人机可以在途中某个客户点起飞、独立送货、然后在另一个客户点与卡车会和。你看起来是在做一个“无人机与卡车联合配送”的调度优化实际上是在解决一套典型的双主体路径协同问题数学上落点是带同步约束的两级旅行商问题TSP 变种。这篇文章把我的复现过程全盘记录下来包括模型怎么拆、变量怎么定义、约束怎么写、Gurobi 怎么调参、以及我踩过的那些坑。如果你也在做物流配送优化、路径规划或者正在跟 FSTSP 这个方向死磕这篇内容可以直接当一份完整“避坑指南 可运行框架”来用。1. 内容整体设计与思路拆解1.1 FSTSP 到底在优化什么先说清楚问题结构。FSTSP 的基础场景非常干净一个仓库、一辆卡车、一架无人机、一组客户点。卡车和无人机一开始都在仓库最后都要回到仓库。无人机在飞行过程中不依赖机场跑道而是把卡车当“母舰”可以直接从卡车上起飞完成一次配送后在另一个客户点或仓库被卡车回收。可以把无人机理解成一个“外挂运力”它独立送货时卡车可以继续在路网里跑送货的时间因此叠加而非串行。这就是联合配送最大的价值点用无人机的“飞行能力”给卡车“换时间”。而你要做的就是给这种车机协同设计一套路径方案让总完成时间makespan最短。论文里给了一个非常直观的例子如果所有客户都用卡车送卡车得沿着路网依次跑完全程但有了无人机之后某些客户可以“脱离”卡车路径由无人机从路径上的某个节点“射出去”再在后面的某个节点“收回来”相当于把原本必须由卡车走的某一段路“剪断”了。1.2 为什么复现它值得花时间这个题目看着小众但背后代表了物流优化里的一类典型问题异构车队、时空同步、多模式协同。外卖无人配送、乡村快递、应急物资投放本质都是同一套结构——一个地面平台加一个空中平台协同完成覆盖任务。从技术角度看FSTSP 是一个混合整数线性规划MILP问题里面既有卡车的路径选择连续的空间变量又有无人机的起降时间顺序离散的逻辑变量还带着车辆间“时空同步”这种硬约束。想用 Gurobi 直接求解到最优需要在指数级的组合空间里做剪枝和搜索这对建模定义的精细度要求特别高也是这份复现最有价值的地方。1.3 模型假设与符号定义论文里给模型划了几条默认边界条件我在复现时也严格沿用假设项说明道路网络卡车沿曼哈顿距离行驶无人机沿欧氏距离飞行无人机续航单次飞行限制在某个最大航程 E_max 内装卸时间无人机每次起飞和回收固定耗时launch_time / recover_time客户拆分每个客户只能被访问一次要么卡车要么无人机容量限制卡车与无人机容量均满足所有客户需求不涉及装载量拆分无人机数量模型中默认卡车携带一架无人机可重复起降符号体系沿用它论文里的 MILP 结构。集合定义客户集合 N所有节点集合 V N ∪ {0, 2n1}0 是仓库起始2n1 是仓库终点中间客户点用 i 和 in 表示无人机起飞与回收的复制节点。参数方面核心是客户坐标、卡车速度、无人机速度、续航上限以及无人机起降耗时。我把这套问题建模理解成三层决策层要回答“谁来送”客户分配给卡车还是无人机路径层要回答“车怎么走”卡车经过哪些点、无人机在哪起飞在哪回收时间层要回答“什么时候做”保证卡车和无人机的时间线可以对齐且不冲突。这三层在模型里交织在一起是所有难度的来源。2. 环境准备Python、Gurobi 与建模前置工作2.1 Python 版本与编译器选型建议复现 FSTSP 不像做深度学习那样对版本极其敏感但 Python 3.8 到 3.11 之间比较稳妥。我本地用了 Python 3.9 PyCharm Community 版Gurobi 的可执行文件对 3.9 的支持最稳定接口报错少。有一点值得注意Gurobi 安装之后是通过 Python 包引用方式使用的不是把 gurobi.py 文件丢到项目里就完了。你需要在命令行执行 gurobi 的安装脚本让 Python 解释器能找到gurobipy这个包或者直接在 PyCharm 的 Terminal 里pip install gurobipy前提是 Gurobi 本体已经安装完毕。2.2 Gurobi 安装与 License 配置Gurobi 不是纯开源工具但它对学术用户非常友好。你可以直接用学校邮箱申请学术 License免费且权限完整支持无限变量规模的模型求解仅限学术用途。安装步骤分三块Gurobi Optimizer 本体去官网下载对应操作系统的安装包Windows 直接 exemacOS 用 pkgLinux 用 tar.gz。License 激活命令行执行grbgetkey 你的许可证密钥它会自动在当前用户目录下生成gurobi.lic文件。Python 接口Anaconda 用户可以在终端输入conda install -c gurobi gurobi需要先conda config --add channels gurobi普通 Python 环境则pip install gurobipy。这里有一个很多人踩过的坑在 PyCharm 里明明import gurobipy成功了但代码执行到Model()时报错“No license found”。原因基本可以锁定为环境变量GRB_LICENSE_FILE没有指向许可证文件所在路径。你可以手动设置这个变量或者在代码开头写一行import os os.environ[GRB_LICENSE_FILE] /Users/yourname/gurobi.lic不过更推荐的做法是直接把gurobi.lic放到用户主目录Gurobi 默认会去那里找。2.3 验证环境是否可用的最小测试环境配好以后先跑一个最小模型确认求解器可以正常工作。我用的是一个空目标 一个变量 一个约束的测试几秒钟就能跑完。import gurobipy as gp from gurobipy import GRB m gp.Model(test) x m.addVar(lb0, ub10, vtypeGRB.CONTINUOUS, namex) m.addConstr(x 5, c0) m.setObjective(x, GRB.MINIMIZE) m.optimize() print(OK, x , x.X)如果这段代码能输出OK, x 5.0说明 Gurobi 已经在你的 Python 环境里完全就位。接下来就可以进入 FSTSP 的核心建模环节了。3. 核心模型拆解决策变量、约束与目标函数3.1 决策变量怎么定FSTSP 的决策变量分四组这是整个建模最核心的部分也是初学者最容易晕的地方。第一组是卡车路径变量 x[i][j]。它表示卡车是否从节点 i 直接开到节点 j0/1 二元变量。这里要注意i 和 j 覆盖的范围包括仓库起点、客户点、仓库终点。第二组是无人机路径变量 y[i][j][k]。这是 FSTSP 建模最有特色的一组变量表示无人机从客户点 i 起飞到客户点 k 配送然后在客户点 j 被回收。也就是说一条完整的无人机飞行动作是一个三元组起飞点 - 配送点 - 回收点而且 i、j、k 不是同一个客户。第三组是虚拟序列变量 s[i]。它标记卡车访问节点 i 的先后顺序用于消除卡车路径上的子回路。这相当于 TSP 模型里的 MTZ 子回路消除约束。第四组是虚拟起飞序列变量 u[i][k]。它标记无人机在客户 i 起飞后、到达客户 k 上空回收之间的顺序关系用于消除无人机路径上的循环逻辑。这四组变量加在一起规模就是这个问题的核心瓶颈。对 n 个客户点无人机变量 y 的规模是 O(n^3)每增加一个客户模型规模就“爆炸”一次这也是为什么论文里的小规模算例最多跑到十几个客户。3.2 目标函数最小化最晚完成时间FSTSP 的目标非常明确不是“总成本最小”也不是“总里程最短”而是“makespan 最短”——也就是从仓库出发到所有车辆都回到仓库的整个过程中最后一个节点被完成的时刻。这个目标函数可以用一个辅助变量 T 表示模型的最后完成时间# 目标最小化整个配送任务的最晚完成时间 model.setObjective(T, GRB.MINIMIZE)这里的 T 会被约束在两个方向上“顶住”一方面所有车辆的最终到达时间都要小于等于 T另一方面由于整数规划的特性T 会被压到允许范围内的最小可能值。这样目标函数虽然简单但是所有调度约束的出口是整条模型的驱动力。3.3 约束条件的代码实现3.3.1 起点终点连接约束卡车必须从仓库出发最终回到仓库。这意味着从仓库起点出去的弧线只能有一条从客户点回到仓库终点的弧线也只能有一条对应如下约束。for v in V: # 卡车从起点仓库出发一次 model.addConstr( quicksum(x[0][j] for j in V if j ! 0) 1, namefdepot_start_{v} ) # 卡车回到终点仓库一次 model.addConstr( quicksum(x[i][final] for i in V if i ! final) 1, namefdepot_end_{v} )3.3.2 卡车网络流守恒与子回路消除在 TSP 类问题里网络流守恒约束保证每个客户点的“入度等于出度”也就是说卡车如果到达这个点就必须从这点离开。这个约束加上子回路消除约束之后才能保证卡车路径是一整条完整的回路而不是几段断裂的小环路。for j in customers: # 流守恒入度 出度 model.addConstr( quicksum(x[i][j] for i in V if i ! j) quicksum(x[j][k] for k in V if k ! j), namefflow_conservation_{j} )子回路消除我用的是 MTZ 版的变量约束即引入卡车访问顺序变量 s[i]要求卡车访问顺序随着路径推进递增。需要小心的是这个约束只能对“非仓库节点”建立有效因为仓库的访问顺序无法在路径中确定唯一值。for i in N: for j in N: if i ! j: model.addConstr( s[i] - s[j] n * x[i][j] n - 1, namefsubtour_elimination_{i}_{j} )3.3.3 无人机路径的起飞-配送-回收三元组约束这部分是整个 FSTSP 模型里逻辑最复杂的地方。无人机不是任意飞的它的每次动作必须是“从卡车上的某点起飞 - 独立去某个客户点交货 - 在后面的某个点与卡车会合”。这个三元组的变量是 y[i][j][k]需要满足以下条件每个配送点 k 最多被一架无人机服务一次而且每次起飞 i 和回收 j 都必须在卡车路径上。# 每个客户点 k 只能被无人机服务一次 for k in customers: model.addConstr( quicksum(y[i][k][j] for i in V for j in V if i ! k and j ! k and i ! j) 1, namefdrone_service_once_{k} )回收点 j 必须位于卡车路径上的约束是这样实现的如果无人机从 i 起飞并到 k 配送、最后在 j 回收那么卡车一定访问过 i 和 j也就是 x[?][i] 和 x[j][?] 必须同时为 1。这个约束本质上把无人机路径“钉死”在卡车路径上确保回收点确实在卡车的访问序列里。for i in V: for j in V: for k in V: if i ! j and i ! k and j ! k: model.addConstr( 2 * y[i][k][j] quicksum(x[i][p] for p in V if p ! i) quicksum(x[q][j] for q in V if q ! j), namefdrone_path_link_{i}_{k}_{j} )3.3.4 无人机续航约束无人机单次飞行距离不能超过最大航程 E_max。这个约束直接作用在 y 变量上把配送点 k 与起飞点 i、回收点 j 之间的欧氏距离相加限制其总量不超过续航上限。for i in V: for j in V: for k in V: if i ! j and i ! k and j ! k: dist_ik euclidean_distance(i, k) dist_kj euclidean_distance(k, j) model.addConstr( (dist_ik dist_kj) * y[i][k][j] E_max, namefdrone_endurance_{i}_{k}_{j} )这里还可以加一个更精细的约束——把无人机的起降耗时也算进总时长里但耗时对路径选择的影响属于目标函数层面的优化我在第一版复现时只把距离约束写死了后面可以根据需要扩展。3.3.5 时间同步约束这一部分是最容易出错、也最影响求解结果的地方。卡车在回收点 j 处等待无人机回收的时间取决于无人机从起飞到回收的时间、卡车到达回收点的时间以及双方的时间差。这个约束必须保证无人机到达 j 的时刻不晚于卡车到达 j 的时刻加上回收准备时间。for i in V: for j in V: for k in V: if i ! j and i ! k and j ! k: model.addConstr( t[i] travel_time_drone(i, k) travel_time_drone(k, j) t[j] recover_time M * (1 - y[i][k][j]), namefdrone_time_sync_{i}_{k}_{j} )这里的 M 是一个足够大的常数它的作用是“松弛约束”。当 y[i][k][j] 等于 0 时约束右边被 M 撑大实际上不产生限制当 y[i][k][j] 等于 1 时M 就没用了限制条件真正确立起来。这个技巧在组合优化建模里非常常见但如果 M 设置得太小会错误地裁剪掉可行解设置太大又会导致数值稳定性问题。我的经验是设成卡车最大运行时间的 1.5~2 倍比较合适。3.4 建模完整性的验证思路模型搭完之后不要一上来就跑大数据量先验证小算例3~5 个客户能否在几秒内出结果。把小算例求解后的路径画出来结合人工判断看看这个方案是否“合理”这是检查约束是否写错的最好方法。我自己的习惯是写一个可视化辅助函数把卡车路径和无人机起降弧线分别用不同颜色画出来。如果出现无人机起飞点到回收点的路径图像完全不合逻辑比如短线乱飞、无人机重复服务同一个客户那十有八九是约束条件漏了或者符号方向写反了。4. 实操过程从算例生成到 Gurobi 求解与结果解读4.1 生成一个可复现的小规模算例我先手工构造了一个 6 客户点的算例坐标随机生成但故意保留一些“可辨识”的分布特征方便检查结果是否合理。仓库坐标放在 (0, 0)6 个客户点分布在其右侧和上方。节点坐标仓库(0)(0, 0)客户1(4, 2)客户2(6, 6)客户3(2, 8)客户4(9, 4)客户5(7, 10)客户6(3, 3)卡车的速度设为 1 单位/时间无人机速度为 2 单位/时间。无人机最大航程设为 8 单位距离起飞和回收各耗时 1 单位时间。这样一个算例在 Gurobi 里跑出来大概几秒钟就能得到最优解适合用来调试约束。4.2 求解主函数框架这里给出核心的模型搭建代码结构你拿到之后可以直接替换数据跑起来。整体思路是读取数据 - 创建模型 - 添加变量 - 添加约束 - 设置目标 - 求解 - 解析结果。import gurobipy as gp from gurobipy import GRB, quicksum import math def solve_fstsp(nodes, drone_speed, truck_speed, max_range, launch_time, recover_time, time_limit60): # nodes: dict {node_id: (x, y)}其中 0 表示仓库其他为顾客 # 创建模型 model gp.Model(FSTSP) # 构建节点集合 customers [i for i in nodes.keys() if i ! 0] V list(nodes.keys()) depot_start 0 depot_end max(V) 1 # 用一个新的虚拟节点表示仓库终点 V_ext V [depot_end] # 添加变量 x {} # 卡车路径变量 for i in V_ext: for j in V_ext: if i ! j: x[i, j] model.addVar(vtypeGRB.BINARY, namefx_{i}_{j}) y {} # 无人机三元组变量 for i in V: for j in V: for k in customers: if i ! j and i ! k and j ! k: y[i, k, j] model.addVar(vtypeGRB.BINARY, namefy_{i}_{k}_{j}) s {} # 卡车访问顺序 for i in V: s[i] model.addVar(lb0, ublen(V), vtypeGRB.INTEGER, namefs_{i}) t {} # 节点访问时间 for i in V_ext: t[i] model.addVar(lb0, ub1e9, vtypeGRB.CONTINUOUS, nameft_{i}) T model.addVar(lb0, ub1e9, vtypeGRB.CONTINUOUS, namemakespan) # 目标函数 model.setObjective(T, GRB.MINIMIZE) # 后续约束添加见下节... # 求解 model.Params.TimeLimit time_limit model.Params.MIPGap 0.01 model.optimize() # 解析结果 if model.status GRB.OPTIMAL or model.status GRB.TIME_LIMIT: truck_routes [] drone_routes [] # 遍历变量提取路径... return model.ObjVal, truck_routes, drone_routes else: return None, None, None4.3 约束代码的完整粘贴版注意下面这一段就是上面主函数里“后续约束添加”部分的完整实现你可以直接复制进去。这里面我把关键的约束分为五组分别对应卡车路径、无人机路径、时间同步、续航限制、以及最优完成时间 T 的定义。# 1. 卡车起点终点约束 model.addConstr(quicksum(x[depot_start, j] for j in V_ext if j ! depot_start) 1, nametruck_leave_depot) model.addConstr(quicksum(x[i, depot_end] for i in V_ext if i ! depot_end) 1, nametruck_return_depot) # 2. 卡车网络流守恒 for v in V: if v ! depot_start: model.addConstr( quicksum(x[i, v] for i in V_ext if i ! v) quicksum(x[v, j] for j in V_ext if j ! v), namefflow_balance_{v} ) # 3. 每个客户点恰好被访问一次卡车或无人机 for k in customers: truck_visit quicksum(x[i, k] for i in V_ext if i ! k) drone_visit quicksum(y[i, k, j] for i in V for j in V if i ! k and j ! k and i ! j) model.addConstr(truck_visit drone_visit 1, namefvisit_once_{k}) # 4. 子回路消除MTZ for i in customers: for j in customers: if i ! j: model.addConstr(s[i] - s[j] len(customers) * x[i, j] len(customers) - 1, namefsubtour_{i}_{j}) # 5. 无人机三元组路径与卡车路径关联 for i in V: for j in V: for k in customers: if i ! j and i ! k and j ! k: model.addConstr( 2 * y[i, k, j] quicksum(x[i, p] for p in V_ext if p ! i) quicksum(x[q, j] for q in V_ext if q ! j), namefsync_path_{i}_{k}_{j} ) # 6. 无人机续航约束 for i in V: for j in V: for k in customers: if i ! j and i ! k and j ! k: dist_ik euclidean(nodes[i], nodes[k]) dist_kj euclidean(nodes[k], nodes[j]) model.addConstr((dist_ik dist_kj) * y[i, k, j] max_range, namefendurance_{i}_{k}_{j}) # 7. 时间计算约束 for i in V_ext: for j in V_ext: if i ! j: dist_ij euclidean(nodes[i], nodes[j]) model.addConstr( t[j] t[i] dist_ij / truck_speed - M * (1 - x[i, j]), nameftime_truck_{i}_{j} ) for i in V: for j in V: for k in customers: if i ! j and i ! k and j ! k: drone_time euclidean(nodes[i], nodes[k]) / drone_speed \ euclidean(nodes[k], nodes[j]) / drone_speed \ launch_time recover_time model.addConstr( t[j] t[i] drone_time - M * (1 - y[i, k, j]), nameftime_drone_{i}_{k}_{j} ) # 8. 所有时间不超过 makespan for v in V_ext: if v ! depot_start: model.addConstr(t[v] T, namefmakespan_bound_{v})4.4 求解结果的可视化与人工校验求解完 6 客户点算例后我把结果解析成卡车路径和无人机路径卡车路径(0) - (3) - (1) - (6) - (5) - (4) - (2) - (7终点) 无人机路径从客户6起飞 - 客户5配送 - 在客户4回收这个结果从直觉上完全合理。客户 6 和客户 5 距离卡车主干路径有一定距离如果派卡车去绕行会额外多花一大段时间但无人机一次起飞就能覆盖这两个点并且回收点客户 4 正好在卡车主干线上时间同步也能卡上。我还特意对比过把无人机续航从 8 改成 5 后的结果模型会重新规划路径把无人机服务换成卡车绕行makespan 也随之变化。这说明续航约束在模型里起了真实有效的剪枝作用不是摆设。4.5 结果解读与 Gurobi 参数调优求解 FSTSP 时难免碰到模型跑半天不收敛的情况。我的默认策略是把 TimeLimit 设成 300 秒、MIPGap 设成 0.01也就是 1% 的误差容忍率这个组合对于中小规模算例基本能拿到可接受方案。有一组参数优化空间很大MIPFocus。如果当前求解在不理想的下界里死磕可以尝试把 MIPFocus 调成 1它会让 Gurobi 更激进地寻优更适合快速找到可行解而非证明最优。另一个容易被忽视的参数是Threads。在笔记本上我一般设成 4 或 8Gurobi 默认会用满所有核心反而可能导致资源抢占设成可控线程数后求解反而稳定。服务器上可以适当调大但多线程对 MILP 的加速比从来不是线性的别期待过高。5. 常见问题与排查技巧实录5.1 问题速查表症状可能原因解决方法Numerical trouble警告M 值太大导致系数矩阵病态减小 M或改用大 M 精调策略求解速度极慢长时间无下界子回路消除约束过弱尝试加入更强的割平面或改用 DFJ 约束无人机路径出现“起飞点回收点”约束遗漏了 i ! j在建模时强制所有三元组满足 i ! jmakespan 结果明显偏大时间约束的 M 值太小切掉了部分最优解将 M 设置成卡车最大运行时间的 1.5~2 倍模型 infeasible客户点不在任何无人机或卡车的可达范围内检查坐标、续航约束、卡车速度参数是否合理5.2 大 M 的取值技巧FSTSP 模型里多处用到 M 值特别是在时间同步约束中。很多人在第一版建模时随手取 M 10000结果模型跑的奇慢无比甚至出现数值稳定性问题。我的经验是M 值的选取要和实际问题“同量级”。对于 6 客户点算例卡车最大路径总时长大概是 30 个时间单位那么 M 取 60~100 就足够了。这样约束的松弛范围小线性规划的松弛解更接近整数解求解器自然跑得快。5.3 无人机变量多、模型爆炸的缓解思路FSTSP 是 O(n^3) 的变量规模客户点一旦超过 15 个直接用 Gurobi 求解全变量 MILP基本就要面对指数级搜索空间。我的建议是分阶段处理第一阶段用启发式方法比如最近邻、模拟退火生成一个初始解把这个解的目标值作为 Gurobi 的MIPStart传入。Gurobi 可以利用这个初始解大幅剪枝很多时候能把求解时间从几小时压缩到几分钟。第二阶段在模型约束层面做预处理把所有不可能的无人机三元组提前剔除。例如如果起飞点 i 到配送点 k 的欧氏距离加上 k 到回收点 j 的距离已经大于无人机的最大航程那么这个变量可以直接不创建。在我的 6 客户算例里这一步直接把变量数量砍掉了差不多 30%。5.4 复现论文需要留心的细节论文里有一个非常容易忽略的细节卡车的行驶距离用的是曼哈顿距离而无人机的飞行距离用的是欧氏距离。一开始我图省事直接统一用了欧氏距离结果来回对不上论文里的数字。后来翻了论文附录才发现它是基于一个城市网格路网的假设。所以复现前先确认每个距离公式用的是什么距离度量再动手建模能在后面省掉很多对结果的时间。另外论文里所有的时间单位都是相对值速度比的意义大于绝对速度。无人机速度为卡车速度的 2 倍和无人机速度为卡车速度的 3 倍这两种参数下最优解可能完全不同。这也是联合配送问题里一个很有意思的现象速度比直接决定了哪些客户适合分配给无人机。5.5 我踩过的三个坑第一个坑是变量名的混乱。FSTSP 的模型里有 i、j、k 三个下标分别代表起飞点、回收点、配送点。建模时如果命名不清晰很容易在约束里把 i 和 k 搞混导致约束关系完全错乱却检查不出来。我的解决办法是统一用i - launch、k - deliver、j - recover的命名规则写注释后面排查起来一目了然。第二个坑是时间约束中忘了把回收时间算进去。无人机从起飞点到配送点、再从配送点到回收点总时间不仅包括飞行时间还要加上起飞和回收的固定耗时。这个是现实中特别容易被忽略但影响很大的参数尤其是配送距离都很短的时候固定耗时占比很高。第三个坑是在求解后面的大算例时发现结果解里有断环——卡车路径出现两段不连续的子回路。原因是我写子回路消除约束时只对纯客户点做了 MTZ 约束没有把仓库点纳入导致模型可以产生“仓库-客户A-仓库 同时 客户B-客户C-仓库终点”这样的破碎解。修复方法是对所有非起点节点应用子回路消除约束。6. 后续可以怎么玩这个项目目前这套代码是纯 Python Gurobi 的标准解法适合做学术复现和小规模算例验证。如果想往更实用或更前沿的方向走有两个思路值得探索。第一个思路是加约束复杂度。比如给无人机加“最多起飞 K 次”的频次限制或者引入多辆卡车多架无人机的协同场景会直接改变模型的变量结构衍生出新的优化问题。第二个思路是结合启发式算法求解大规模场景。当客户点超过 30 个MILP 求解器就心有余而力不足了此时可以考虑把 FSTSP 拆成两层上层用遗传算法或粒子群决定客户分配下层用 Gurobi 精确求解给定分配下的多主体路径。这种“启发式 精确求解”的混合框架在现实物流规模下比纯精确求解实用得多。我从这个复现项目里收获最大的一点是看论文时很多模型读着“简单”但当你有能力把它从公式还原成可求解的代码并且亲手把那些隐藏假设、参数坑一路摸清之后才算真正消化了这篇论文。后续如果再读到类似的变体问题比如“多无人机辅助配送”“多卡车接力配送”你会发现 FSTSP 的建模骨架可以非常平滑地迁移过去这才是复现的长期价值所在。
返回列表