案例 069 换电顺路做
案例 069 换电顺路做
摘要:换电柜不是目的地。把 12 单的送餐路径与 5 座换电柜放进同一个有向图,柜子与客户点同构、只是收益为 0、停留 3 min,模型自己决定「在哪一单之后拐进去」。三种方案对比:不换电只能跑 6 单、净收入 51.1 元;专程去 2.40 km 外的柜子、全程 64.7 km,跑满 12 单、净收入 109.4 元;顺路插进 0.35 km 处的柜子、全程 58.3 km,同样 12 单、净收入 111.3 元。同一批订单、同一次换电,位置换一下就是 6.5 km、19 min 的差;而顺路换电真正的价值是把单量从 6 单抬到 12 单——多接 6 单、多赚 60.2 元。
关键词:可选站路径;换电位置;电量递推;混合整数规划;绕行成本;灵敏度分析
1 问题提出
1.1 问题是什么
午高峰四个小时,系统推来 12 单,毛收入 130.3 元,跑完要 58.3 km。车的满电续航 42 km,扣掉 15% 安全边际只剩 35.7 km 可用——一管电跑不完全部单,必须换一次电。麻烦在于换电柜不在路线上:最近的柜离第 5 单客户只有 200 m,最远的单子离开柜子有 8.94 km,取餐一趟连柜都见不到。
所以真正要决定的不是「换不换」,而是「在哪一单之后拐进去」。拐进 200 m 外的柜,来回 0.4 km、一分多钟;拐进 2.40 km 外的柜,来回 4.8 km、十几分钟——同样的电,代价差十倍。
表 1 八单代表数据(全 12 单见 data_069.csv,柜距取最近换电柜)
| 订单 | 区域 | \(D_{i}\) /km | \(w_{i}\) /元 | \(q_{i}\) /份 | \(\delta_{i}\) /km | 顺路柜 | 所在段 |
|---|---|---|---|---|---|---|---|
| O01 | 天河 | 4.20 | 9.5 | 2 | 3.03 | 否 | 1 |
| O03 | 越秀 | 10.23 | 8.5 | 2 | 3.90 | 否 | 2 |
| O05 | 海珠 | 15.21 | 12.5 | 3 | 0.20 | 是 | 2 |
| O06 | 海珠 | 17.68 | 10.0 | 2 | 3.67 | 否 | 2 |
| O08 | 番禺 | 18.98 | 9.0 | 2 | 0.35 | 是 | 2 |
| O10 | 越秀 | 13.67 | 11.5 | 3 | 5.96 | 否 | 1 |
| O11 | 天河 | 9.84 | 8.8 | 2 | 6.25 | 否 | 1 |
| O12 | 天河 | 10.10 | 10.5 | 3 | 8.94 | 否 | 1 |
1.2 为什么要优化
第一,不换电直接砍掉一半单。12 单的路径里程 58.3 km 需要 139% 的电,一管电只有 85%,图 3 里不换电只完成 6 单、51.1 元。
第二,「专程」和「顺路」的差别被低估了。专程方案总里程 64.7 km,比顺路多 6.5 km,按 20 km/h 折 19 min,按 0.30 元/km 折 1.9 元;多跑的里程本身还要耗电。
第三,换电点选错会挡掉后面的单。换电把电量复位到 100%,复位之后剩下的路必须在一管电内跑完;换早了浪费电,换晚了电压到 15% 以下,模型里 \(e_{i}\ge e_{\min}\) 是硬约束,违反就是推车。
1.3 Gurobi 如何求解
决策三组:\(y_{ij}\in\{0,1\}\) 走不走弧 \((i,j)\)、\(x_{i}\in\{0,1\}\) 接不接单、\(w_{s}\in\{0,1\}\) 用不用柜 \(s\)。12 个客户点加 5 个候选柜加起终点共 19 个节点,弧变量约 323 个。电量 \(e_{i}\) 沿弧递推,经过柜点时用「复位项」\((100-e_{i})w_{i}\) 线性化(式 4);子回路用 MTZ 割掉(式 5)。柜子与客户点在模型里完全同构,唯一区别是经过它时收益为 0、耗电被清零、时间加 3 min——这就是「可选站」写法的全部技巧,一套 \(y_{ij}\) 同时管路径与换电。
规模不大,但电量递推让 LP 松弛变松。三方案与 32 次扫参求解合计 47 s 内跑完,主方案在 1.5 s 内证到 gap 0。
2 数学模型
2.1 符号说明
\(I\) 为客户点集合(\(|I|=12\)),\(S\) 为候选换电柜集合(\(|S|=5\)),\(0\) 为取餐区、\(n+1\) 为回程终点(与 \(0\) 同址);\(A\) 为可行弧集;\(d_{ij}\) 为弧长,\(D_{i}\) 为订单 \(i\) 到取餐区的直线距离,\(\delta_{s}\) 为柜 \(s\) 到最近客户点的距离,\(\bar{\delta}\) 为顺路阈值(本案 0.5 km);\(w_{i}\) 为单收入、\(\tau_{i}\) 为单占用时长;\(R\) 为满电续航、\(e_{i}\) 为到达 \(i\) 时的电量、\(e_{\min}\) 为安全边际、\(\tau_{s}\) 为单次换电耗时、\(\kappa\) 为换电次数上限;\(c_{k}\) 为里程成本、\(c_{s}\) 为换电固定成本、\(v\) 为车速、\(T\) 为班次时长。
2.2 目标函数
收入按单计,里程按 0.30 元/km 折(电费加折旧),换电按 1.5 元/次折(3 min 的机会成本)。里程项是这一例的关键:它让「专程」与「顺路」在同一把尺子上比较,专程方案多跑的 6.5 km 直接从利润里扣。
2.3 约束条件
每个接了的单必须一进一出;起终点各一条弧;换电次数不超过 \(\kappa\)(本案 1 次)。
式 (3) 是「可选站」的开关:柜子被选中才一进一出,且只有 \(\delta_{s}\le\bar{\delta}\) 的柜子才进入候选集——这一条把「顺路」写成了可行性约束,而不是目标里的软惩罚。阈值之外的柜子在专程方案里才放开,此时它的插入代价 \(d_{a,s}+d_{s,b}-d_{a,b}\) 自动接近 \(2\delta_{s}\)。
到达 \(j\) 的电量等于到达 \(i\) 的电量减去这段耗电;若在 \(i\) 处换了电,则先复位到 100% 再扣。\((100-e_{i})w_{i}\) 是双线性项,用两条大 M 约束替代:\(w_{i}=1\) 时强制 \(e_{j}=100-100d_{ij}/R\),\(w_{i}=0\) 时强制 \(e_{j}=e_{i}-100d_{ij}/R\)。安全边际对每个访问点(含柜)生效。
MTZ 消子回路(\(u_{i}\) 为访问序);时间约束把送餐停留、换电、骑行三项加起来不超过 240 min。
2.4 规模与性质
19 个节点、323 个弧变量、12 个选单变量、1 个换电开关,约束约 700 条。这是一条带电量约束的奖金收集 TSP(prize-collecting TSP with battery),NP 难;但本案节点少、电量是路径的单调函数,分支定界秒级就能证到 gap 0。与经典燃料约束 VRP 的差别在于:柜子是可选的中间点,插不插由模型定,而不是预先给定的固定站点。
3 Gurobi 求解实现
3.1 建模要点
柜子与客户点共用一套 \(y_{ij}\),靠节点编号区间区分角色:
for k, s in enumerate(stas): # 柜点:一进一出由 w_s 控制
md.addConstr(gp.quicksum(yv[a, b] for (p, b) in yv if p == a) == ws[k])
for (a, b) in yv: # 电量递推,柜点带复位项
if a >= 1 + K:
md.addConstr(e[b] >= e[a] - c - ME*ws[k] - ME*(1 - yv[a, b]))
md.addConstr(e[b] >= 100 - c - ME*(1 - ws[k]) - ME*(1 - yv[a, b]))
一个实现细节:换电点的两组约束必须互补门控——「不换电」那一对用 \(w_{s}\) 放松、「换电」那一对用 \(1-w_{s}\) 放松。两对同时生效会把 \(e_{j}\) 逼到 \(100-c\) 与 \(e_{i}-c\) 两个值上,只要换电前电量不是满的就不可行,表现出来是「模型宁可少接单也不换电」。另一个坑是起终点同址——若共用一个节点,出度与入度会互相污染,必须拆成 \(0\) 与 \(n+1\) 两个节点。
3.2 求解结果
表 2 三方案结果($R=42$ km,$\tau_{s}=3.0$ min,$\delta\le0.5$ km,$\kappa=1$)
| 方案 | 单量 | 总里程 /km | 毛收入 /元 | 净收入 /元 | 用时 /min | 换电柜 |
|---|---|---|---|---|---|---|
| 不换电 | 6 | 34.0 | 61.3 | 51.1 | 117 | — |
| 专程去换 | 12 | 64.7 | 130.3 | 109.4 | 227 | C5(2.4 km) |
| 顺路插入 | 12 | 58.3 | 130.3 | 111.3 | 208 | C2(0.35 km) |
三行读下来:不换电 6 单 / 51.1 元;专程去换 12 单 / 109.4 元、64.7 km;顺路插入 12 单 / 111.3 元、58.3 km。换电本身只值 1.5 元,值钱的是位置:专程比顺路多跑 6.5 km、多花 19 min、少赚 1.9 元。
4 结果分析

图 1 顺路换电方案的实际路径:第 1 段 5 单,在柜 C2 换电后跑第 2 段 7 单
4.1 路径与换电点 图 1 把顺路方案的完整路径画出来:第 1 段 5 单向东扫到柜 C2(离客户点 0.35 km),换电后第 2 段 7 单绕回取餐区。柜 C2 几乎压在 O08 客户身上(0.35 km),箭头在这里只多拐了一个小三角——这一段就是全部的「绕行」,0.00 km。 对比图里南侧的柜 C5(2.40 km 外),换电点若换成它,同样 12 单要多跑 6.5 km。

图 2 订单到最近换电柜的距离与单收入的散点,气泡面积为份数
4.2 绕行—收益画像 图 2 横轴是订单到最近柜的距离、纵轴是单收入、气泡是份数。2 单的 \(\delta_{i}\le0.5\) km(绿色),其余 10 单在 0.6—8.94 km 之间(灰色)。关键不是「哪单收入高」,而是「哪单旁边有柜」:O05 收入 12.5 元只排中游,但它旁边的柜距只有 0.20 km,是整条路线上少有的免费换电机会。

图 3 三种换电方案的单量、净收入与总里程对比
4.3 三方案对比 图 3:6 单 / 51.1 元、12 单 / 109.4 元、12 单 / 111.3 元。不换电被一管电卡死在 6 单;一旦允许换电,单量天花板顶到 12 单,剩下的差别全是位置成本。专程与顺路的净收入差 1.9 元、里程差 6.5 km——不是订单质量的差别,是柜子位置的差别。

图 4 累计里程与剩余电量:顺路换电把曲线在零绕行处拉回 100%
4.4 电量曲线 图 4 横轴累计里程、纵轴剩余电量。不换电那条(点线)在 34.0 km 处停住,剩余 19.0%,再走一单就破 15% 线;专程那条(虚线)在 31.6 km 处有一个 100% 的竖跳,跳之前的谷底是 21.3%——绕行本身耗电,为了换电先把电用在路上,这是「专程」的隐藏成本。顺路那条(实线)在 27.6 km 处竖跳,谷底 27.1%,跳前后几乎不占横轴。

图 5 满电续航对单量与方案总里程的影响
4.5 续航灵敏度 图 5 左轴单量、右轴总里程。续航从 30 km 提到 60 km,顺路方案从 7 单升到 12 单并很快封顶,不换电方案从 3 升到 9 单、一路缓升——续航越短,换电越值钱;越短,两条线的差越大。右轴的总里程随续航上升而缓慢增加,说明长续航下模型愿意为多接单走更远的路。

图 6 续航 × 可用柜距阈值的热图,黄框为本案参数
4.6 续航 × 柜距阈值 图 6 是 4×4 热图,行是可用柜距阈值 \(\bar{\delta}\)、列是续航。同一列自上而下(续航 42 km 列):109.7 → 111.3 → 111.3 → 111.3 元,阈值从 0.25 km 放到 2.40 km 只值 1.7 元;同一行自左向右(阈值 2.40 km 行)从 80.1 到 111.3 元,值 31.2 元。柜距阈值的作用远小于续航:多记十座远处的柜,不如把电池换新。 本案参数(42 km、0.5 km)落在拐点附近(星标)。

图 7 平均车速对净收入与单量的影响(第三维)
4.7 车速(第三维) 图 7 左轴净收入、右轴单量,横轴是平均车速。从 15 km/h 提到 23 km/h,净收入从 94.4 元升到 111.3 元、单量从 10 单升到 12 单,23 km/h 之后完全走平。这是一条「先断后平」的曲线:车速低于 17 km/h 时骑行时间挤占班次,被迫少接 2 单;高于 17 km/h 后瓶颈交回给电量,再快也没用——与图 5 的续航曲线互为镜像,一个管「一管电跑多远」,一个管「四小时跑多远」。

图 8 里程预算与可接单量的前沿,灰色为单池上界以上的不可达区
4.8 里程—单量前沿 图 8 横轴是路径里程预算、纵轴是能给到的最多单量。预算 28 km 时只能 4 单,60 km 时给到 12 单(单池上限 12 单,灰区是上界以上不可达)。前沿在 36—52 km 之间最陡:每多 8 km 预算能换回 1—3 单,超过 52 km 后平掉——本案实际里程 58.3 km 已经站在平的这一侧,说明里程不是本案的瓶颈,电量分配才是。

图 9 订单到最近换电柜距离的分布与累计占比
4.9 柜距分布 图 9 是订单到最近柜的距离直方图加累计曲线。12 单里只有 2 单在 0.5 km 内,中位数 3.10 km,最长 8.94 km——柜子集中在送货带的两三个角落。累计曲线在 0.5 km 处只爬到 17%,也就是说八成订单旁边没有柜——「顺路」不是普遍机会,而是少数订单才有的运气。这也解释了图 6 里柜距阈值那一条为什么几乎不值钱:阈值从 0.25 放到 2.5 km,能用的柜从 1 座变成 5 座,但真正贴着路线的那一座一直是同一座。

图 10 顺路方案逐单插入边际贡献,虚线为均值
4.10 逐单边际贡献 图 10 按插入边际(\(w_{i}-c_{k}\cdot\)插入距离)从高到低排。最高 13.42 元、最低 8.04 元、均值 10.45 元,极差只有 5.38 元。12 单的边际贡献高度平均,说明最优解不是「挑肥拣瘦」挑出来的,而是「电够了就全接」——单量的天花板是电量,不是订单质量。
5 结论
- 换电要顺路做,专程的代价可以精确到公里和分钟。 同样 12 单、同样一次换电,专程去 2.40 km 外的柜要多跑 6.5 km、多花 19 min、少赚 1.9 元(图 3、图 4)。绕行不只是一段路,它同时吃掉电量和班次时间。
- 顺路换电真正买回的是单量。 不换电 6 单 / 51.1 元,顺路换电 12 单 / 111.3 元,多 6 单、多 60.2 元;1.5 元的换电固定成本可以忽略,6 单才是主收益。
- 柜子应该建成「客户点的邻居」,而不是「目的地」。 把柜与客户点同构进同一个 \(y_{ij}\) 图,模型自己算出绕行 0.00 km 的插法;柜距阈值从 0.25 放到 2.5 km 只值 1.7 元(图 6),位置比数量重要。
- 最优换电点落在「两段里程的均衡点」上,而不是最近的那座柜。 本案最近的柜离客户 200 m(柜 C1),模型却在 0.35 km 的柜 C2 上换电,因为切分点要卡在总量程的一半附近;换完后谷底 27.1%,仍高于 15% 硬线(图 4)。
- 瓶颈是电量,不是里程、不是柜子数量。 里程—单量前沿在 58.3 km 处已经走平(图 8),车速到 17 km/h 以上不再掉单(图 7)——要提升单量,先换电池,再谈记柜子。
参考文献
[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_069.py;数据与结果见同目录 .csv / .json,图见 figs/

浙公网安备 33010602011771号