案例 045 一单崩,全崩:级联风险与冗余/隔离设计

案例 045 一单崩,全崩:级联风险与冗余/隔离设计

摘要:8 单串成一趟,最优点序把最大延误压到 0.7 min,代价是整条链的余量几乎为零。一旦某一单出事——商家取消、客户失联、或干脆晚了 12 min——延误沿链向后传递,后面每一单都跟着崩。本文用 Gurobi 先解 \(\min L^{\max}\) 的极小极大排序,再把"单点故障"写成沿链传播的延误算子,对串联全损、分批隔离、预留冗余三种方案分别算最坏情景损失与期望损失。结果:串联方案最坏一次性损失 123.5 元,是单均损失的 8.0 倍;4 批隔离把期望损失从 68.8 元降到 23.4 元(降 66%),只多跑 6.9 km;冗余 12 min/单 把最坏损失压到 17.0 元(1 单),但运行成本升到 41.8 元。若服务底线是"最坏损失不超过 40 元",只有冗余方案可行;若只看期望总成本,隔离最划算——这正是 \(\varepsilon\)-约束要表达的取舍。

关键词:级联失效;极小极大延误;分批隔离;冗余时间;期望损失;\(\varepsilon\)-约束


1 问题提出

1.1 问题是什么

午高峰一趟 8 单,出餐口只有一条通道。第 1 单商家临时取消,骑手在店里解释、等退款、再联系客户,前后耗掉 12 min;等他冲出去,后面 7 单的承诺时刻已经全部失效。这不是"某一单晚了",而是一条串联链被一个节点拖垮。

题目给的是极小极大结构:让最惨的那一单尽量不惨。但真正要回答的是商业问题——要不要为小概率的单点故障付出常态成本。

1.2 为什么要优化

第一,串联系统的损失是阶跃式的。8 单的余量都在 0 到 3 min 之间,任何超过 4 min 的扰动都不是"晚一点",而是整条链同时击穿。

第二,三种防崩手段的成本结构完全不同:隔离付的是里程(回驻点重新出发),冗余付的是时间(早到等待),串联什么都不付,但它把风险全部留给了最坏情景。

第三,只有一个口径会骗人。只看期望,串联每趟平均亏 68.8 元,似乎还能忍;只看最坏,一次 123.5 元,一天几趟就吃掉全天利润。两个口径必须同时摆出来。

1.3 Gurobi 如何求解

两层结构。第一层是排序:二进制 \(x_{ij}\) 选弧、连续变量 \(C_i\) 记到达时刻、标量 \(L^{\max}\) 记最大延误,目标是最小化 \(L^{\max}\),时间沿弧递推自然消除子回路。第二层是情景评估:固定最优序,枚举"哪一单出事"(8 个位置),按各方案的传播规则算损失,取最大值与等权期望。第二层是纯代数,不需要再解 MIP,所以整个案例的求解墙钟不到 2 s。

2 数学模型

2.1 符号说明

\(i,j\) 为节点(0 为驻点,\(N+1\) 为回程虚拟终点),\(D_{i}\) 为承诺时刻,\(C_{i}\) 为到达时刻,\(d_{ij}\) 为弧长,\(\tau_{ij}=3\,d_{ij}\) 为骑行时间(3 min/km),\(s_{i}=2\) min 为停靠交付,\(s\) 为故障延误,\(b\) 为每单冗余,\(g\) 为客户容忍,\(\ell_{i}\) 为单均损失,\(z_{jk}\) 为击穿指示,\(L^{\max}\) 为最大延误。

2.2 目标函数

第一层是极小极大延误,用"里程"做微小的次序打破(避免多解):

\[\min\ Z_{1}=1000\,L^{\max}+\rho_{d}\sum_{i\ne j}d_{ij}x_{ij}\tag{1} \]

其中 \(\rho_{d}=1.2\) 元/km,\(d_{ij}\) 为弧长。系数 1000 保证 \(L^{\max}\) 绝对优先,里程只在 \(L^{\max}\) 相同的解之间起区分作用。

第二层对每个故障位置 \(k\) 算损失,再取两个口径:

\[\Lambda_{k}=\sum_{j\in\mathcal{B}(k)}\ell_{j}\,z_{jk},\qquad L^{\mathrm{wc}}=\max_{k}\Lambda_{k},\qquad L^{\mathrm{ex}}=\frac{1}{N}\sum_{k=1}^{N}\Lambda_{k}\tag{2}\]

\(\mathcal{B}(k)\) 是"被 \(k\) 拖下水"的订单集合,\(\ell_{j}=r_{j}+c_{p}\) 为单均损失(收入 \(r_j\) 加赔付 \(c_{p}=8\) 元),\(z_{jk}\in\{0,1\}\) 是击穿指示。

2.3 约束条件

到达时刻沿弧递推,\(M\) 取 500 保证大 M 有效:

\[C_{j}\ge C_{i}+s_{i}+\tau_{ij}-M(1-x_{ij}),\qquad \forall (i,j)\tag{3} \]

极小极大把每单的延误压到同一个上界之下:

\[C_{i}-D_{i}\le L^{\max},\qquad \forall i\in\{1,\dots,N\}\tag{4} \]

级联是本案的核心。故障造成 \(s\) 分钟延误,每经过一个停靠点被冗余 \(b\) 吸收一部分,剩余延误单调不增:

\[\delta_{j}=\max\{0,\ \delta_{j-1}-b\},\qquad \delta_{k}=s\tag{5} \]

剩余延误超过"余量加客户容忍"就击穿,\(g=3\) min:

\[\delta_{j}-(D_{j}-C_{j})>g\ \Longrightarrow\ z_{jk}=1\tag{6} \]

分批隔离改的是 \(\mathcal{B}(k)\) 的范围而不是延误本身:把序列切成 \(m\) 段,每段独立回驻点,故障只污染本段。运行成本的差别全部来自回程:

\[Z_{2}=\rho_{d}\Big(\sum_{m}\text{批内里程}+\sum_{m}\text{回程}\Big)+\omega\,b\,N\tag{7} \]

\(\omega=0.30\) 元/min 为冗余时间的机会成本。

2.4 规模与性质

9 个节点、72 条候选弧、9 个时间变量、1 个连续目标变量,第一层是标准的不对称 TSP 加极小极大时间约束。由于 \(C\) 沿弧严格递增,\(x\) 的任何子回路都会与式 (3) 矛盾,不需要额外子回路约束。第二层是 \(O(N^{2})\) 的枚举,8 单只有 8 个故障位置,直接用 numpy 扫完。

3 Gurobi 求解实现

3.1 建模要点

极小极大天然线性,这是它比 \(\sum\) 型目标更稳的原因:不需要绝对值、不需要分段,只要一个标量 \(L^{\max}\) 和 \(N\) 条线性不等式。

L = m.addVar(lb=-GRB.INFINITY, name="L")
for c in range(1, N + 1):
    m.addConstr(L >= t[c] - DUE[c - 1])
m.setObjective(1000.0 * L + km, GRB.MINIMIZE)

第二个要点是别把级联写成线性。式 (5) 的 \(\max\{0,\cdot\}\) 如果改写成"延误恒定",冗余的作用会被抹平——每多一分钟冗余都应该少拖垮一单,这个"逐步吸收"必须逐跳算。

3.2 求解结果

表 1 最优序的逐单到达与余量(时刻以出发为 0,单位 min)

顺序 订单 送达点 距店 km 承诺 到达 余量 单均损失 / 元
1 08 岗顶 1.0 10 3.0 +7.0 15.0
2 01 天河城 0.7 7 6.9 +0.1 16.0
3 02 珠江新城 1.3 14 11.2 +2.8 15.5
4 03 猎德大道 2.0 18 16.2 +1.8 17.0
5 04 冼村路 0.9 23 22.0 +1.0 14.5
6 05 员村二横 2.4 30 28.9 +1.1 16.5
7 06 车陂南 2.9 35 34.8 +0.2 15.0
8 07 棠下村口 1.6 41 41.7 -0.7 14.0

表 2 三种防崩方案的两个口径(单位:元)

方案 运行成本 最坏损失 期望损失 最坏合计 期望合计 波及单数
A 串联(不设防) 13.0 123.5 68.8 136.5 81.7 8
B 分批隔离(4 批) 21.3 32.5 23.4 53.8 44.6 2
C 预留冗余(12 min/单) 41.8 17.0 15.4 58.8 57.2 1

最优点序 08 → 01 → 02 → 03 → 04 → 05 → 06 → 07,最大延误 0.7 min,总里程 10.82 km。注意表 1 的余量列:8 单里有 7 单的余量不足 3 min,最后一单(07 棠下村口)甚至已经晚了 0.7 min。这条链没有任何吸收扰动的能力。

4 结果分析

图 1 最优点序的时序甘特(斜纹为剩余余量)

4.1 时序结构 图 1 是 08 → 01 → 02 → 03 → 04 → 05 → 06 → 07 的甘特。蓝色骑行段和绿色停靠段几乎首尾相接,斜纹的"剩余余量"薄得像纸——只有第一单(岗顶,离店 1 km)还有 7 min 的缓冲,其余全在 3 min 以内。这就是"串联"的物理含义:链条的强度等于最弱的一环,而这里每一环都很弱。

图 2 距离—到达时刻散点(气泡为单均损失)

4.2 数据全景 图 2 把 8 单摆在"距离—到达时刻"平面上,气泡大小是单均损失。余量最薄的三单(07、06、01)都在 1.0 到 1.6 km 的中近距离,而最远的 05(2.4 km)反而还有 1 min 余量;07 的余量为负,正是 \(L^{\max}=0.7\) 的来源。余量与距离不成正比,只与排位和承诺时刻有关——所以"挑近单"解决不了级联问题。

图 3 三种防崩方案的运行成本与损失对比

4.3 三方案对比 图 3 把运行成本与最坏损失并排放。串联的成本几乎为零(13.0 元),但最坏一次赔 123.5 元;4 批隔离多花 8 元里程,最坏损失砍到 32.5 元;冗余方案运行成本 41.8 元,最坏损失只剩 17.0 元。绿色菱形是"运行成本 + 期望损失",三个点分别是 82、45、57 元——期望口径下隔离最优,最坏口径下冗余最优,两个口径给出两个答案。

图 4 单点故障后延误沿链传播

4.4 级联过程 图 4 是延误沿链的传播。红线是"第 1 单崩后"的到达时刻,从第 2 单起整体抬升 12 min,全程压在承诺线之上,粉色区域就是被击穿的部分。分批隔离不在图上,因为它改的是"谁陪葬"而不是"延误多久":4 批方案的总时长被拉长到 60 min 以上,多出来的时间就是回驻点的代价。虚线是常态时序,它贴着承诺线走——余量本来就只够"不出事"。

图 5 故障强度灵敏度与波及单数

4.5 主灵敏度:故障强度 图 5 让延误从 0 扫到 24 min。串联方案在 \(s\approx 4\) min 就触顶(123.5 元,8 单全崩),此后加多少都一样——它没有阈值,只有一次性崩塌。冗余 12 min 把阈值推到 15 min 左右,曲线在 15 min 之后才抬头。隔离方案的曲线是台阶:每跨过一段余量就多崩一单,但永远只崩本批。

图 6 冗余时间 × 故障强度的最坏损失热图

4.6 二维灵敏度 图 6 把冗余 \(b\) 与故障强度 \(s\) 铺成 5×4 网格。左上角(\(b\) 小、\(s\) 大)是最坏损失 124 元的深红区,右下角(\(b=12\)、\(s\) 小)是 17 元的浅色区。等高线在 \(b\) 方向几乎垂直、在 \(s\) 方向倾斜,说明冗余对损失的边际作用在 \(b\approx 9\) min 之后就饱和:再加时间也压不动那"第一单必损"的 17.0 元底噪。

图 7 分批数的损失—里程权衡

4.7 第三维:分批数 图 7 把批数从 1 拉到 4。期望损失从 68.8 元单调降到 23.4 元,最坏损失从 123.5 元降到 32.5 元,但额外里程从 0 涨到 6.9 km,边际收益明显递减:第 2 批砍掉一半损失,第 4 批只再砍 6 元。隔离的性价比在 \(m=2\) 到 \(m=3\) 之间最高,这与"每批 3 到 4 单"的行业经验一致。

图 8 冗余扫描的前沿与不可接受区

4.8 前沿与不可行区 图 8 是冗余扫描的(运行成本,最坏损失)前沿。曲线从左上(0 冗余、124 元)陡降到右下(12 min、17 元),拐点在 \(b\approx 6\) min:此前每加 5 元成本能换回 40 元损失,此后每加 5 元只换 15 元。阴影是"最坏损失超过 40 元"的不可行区,串联方案整个落在里面。

图 9 故障位置损失分布与累计分布

4.9 损失分布 图 9 是 8 个故障位置的损失分布。串联(红)从 17 元一路排到 123 元,累计曲线几乎是一条斜线——故障位置越靠前,损失越大,且没有上限。冗余方案(蓝)的分布被压成一根 17 元的柱子,累计曲线在 0.13 处直接跳到 1.0:无论哪一单出事,损失都是一单。这就是冗余的本质——把"位置相关"的损失变成"位置无关"的常数。

图 10 逐位置对期望损失的边际贡献

4.10 逐位置贡献 图 10 把每个位置的期望贡献(\(\Lambda_{k}/N\))按方案拆开。串联的贡献从首单的 15.4 元线性衰减到末单的 1.5 元,说明风险集中在链条前段:越早出事,后面陪葬的越多。隔离把这条斜线压成锯齿(每批第一单贵、第二单便宜),冗余把它压成一条 1.9 元的平线。治理级联,本质上就是把这条斜线拉平。

5 结论

  1. 极小极大排序给出 0.7 min 的最大延误与 10.82 km 的最短里程,但 8 单中 7 单余量不足 3 min,这条链在结构上没有抗扰动能力。

  2. 一次 12 min 的单点故障在串联结构下造成 123.5 元损失,等于 8 单全损,是单均损失的 8.0 倍——级联的放大倍数等于链长。

  3. 分批隔离把期望损失从 68.8 元降到 23.4 元(降 66%),代价是 6.9 km 额外里程与约 8 元运行成本,是期望口径下的最优解。

  4. 预留冗余 12 min/单 把最坏损失压到 17.0 元、把崩溃阈值从 4 min 推到 15 min,但运行成本 41.8 元,期望总成本 57 元反而最高——冗余买的是尾部,不是均值。

  5. 两个口径不能互相替代。若公司把"最坏损失不超过 40 元"写成服务底线(即 \(\varepsilon\)-约束),4 批隔离(32.5 元)就已越界,只有冗余方案可行,代价是每天多付 21 元常态成本。

参考文献

[1] Gurobi Optimization LLC. Gurobi Optimizer Reference Manual[Z]. 2024.

[2] Kellerer H, Pferschy U, Pisinger D. Knapsack Problems[M]. Berlin: Springer, 2004.

[3] Martello S, Toth P. Knapsack Problems: Algorithms and Computer Implementations[M]. Chichester: John Wiley & Sons, 1990.

[4] 司守奎, 孙玺菁. 数学建模算法与应用[M]. 3 版. 北京: 国防工业出版社, 2021.


复现方式:python batch_045.py;数据与结果见同目录 .csv / .json,图见 figs/

posted @ 2026-11-11 18:01  小青蛙电气  阅读(3)  评论(0)    收藏  举报