案例 060 三座换电柜怎么选

案例 060 三座换电柜怎么选

摘要:常跑片区有 12 座换电柜,全记熟不现实,只能挑 3 座当主力。可"挑哪 3 座"不是比谁近:绕得远费时间,人挤人排队费时间,柜里没满电更费时间——三项加起来才是真代价。本文把选柜写成带容量约束的 p-median(p=3),把绕行、排队、缺电失败三项折算成同一个"元每日"口径塞进目标函数,用 Gurobi 求出 C05(客村)、C08(员村)、C11(杨箕)。最优日代价 696.2 元(线性口径),按实际负荷回算是 777.7 元,其中绕行 348.1、排队 266.9、缺电失败 162.7。两个凭直觉的方案都不行:按空电率挑最"干净"的三座要 1187.8 元(+52.7%),按单柜各自最优挑要 1735.5 元(+123.2%)——后者线性口径只差 17.6 元(2.5%),回算却差 957.8 元,因为它把 57.4 次每日压给 C08 一座柜,排队从 2.8 分钟涨到 48.8 分钟。柜子选错的代价,八成藏在看不见的排队与空电率里。

关键词:p-median;换电柜选址;空电率;排队期望;缺电失败概率;混合整数规划

1 问题提出

1.1 问题是什么

小哥的活动范围压在天河到海珠一块约 12 平方公里的地上,这里有 12 座换电柜、20 个高频交付点。他每天要换 82.1 次电,不可能 12 座都跑熟,只能记 3 座的走法、记住哪个路口能拐、哪个时段人少。这就是"3-median":从 12 座里挑 3 座,让所有换电需求跑到柜上的总代价最小。

难点在于"代价"不是一维的。表 1 是 12 座柜的全部底牌:空电率从 0.08 到 0.35,相差 4.4 倍;基础排队从 1.2 分钟到 6.0 分钟;高峰产能从 11.0 到 20.0 次每小时。最近的那座柜,很可能是排队最长、空电率最高的那座。

表 1 十二座换电柜的空电率、基础排队、产能与入选结果

柜号 位置 空电率 基础排队 /min 高峰产能 /(次每时) 日容量 /次 日负荷 /次 入选
C01 天河城 0.12 2.0 16.0 96 — —
C02 体育西 0.18 3.5 14.0 84 — —
C03 珠江新城 0.08 1.2 20.0 120 — —
C04 东山口 0.22 4.0 12.0 72 — —
C05 客村 0.15 2.2 15.0 90 20.0 主力
C06 北京路 0.30 5.5 11.0 66 — —
C07 岗顶 0.10 1.5 19.0 114 — —
C08 员村 0.20 2.8 14.0 84 42.4 主力
C09 同福路 0.35 6.0 12.0 72 — —
C10 龙洞 0.25 2.0 13.0 78 — —
C11 杨箕 0.14 2.6 16.0 96 19.7 主力
C12 大学城 0.28 1.8 13.0 78 — —

三座最"干净"的柜是 C03(珠江新城)、C07(岗顶)、C01(天河城)——空电率 0.08、0.10、0.12,看上去无可挑剔。可它们离需求重心远:C01 在西北角,C07 在正北,C03 虽然居中但产能被挤。只盯空电率,等于用"柜里有没有电"这一个指标,替掉了"人要不要跑过去、要不要排、会不会白跑"这三个问题。

1.2 为什么要优化

第一,三项成本不同量纲,必须先折成同一把尺子。绕行是公里,排队是分钟,缺电失败是"白跑一趟"的概率。三者只能靠时间价值 \(\theta\) 与失败罚金 \(\gamma\) 折算成元。折不清楚,就只能拍脑袋:"近的优先"或"电多的优先"。

第二,空电率不是柜子的私事,它会传染。一座柜空电率高,来的人换不到电,就得再跑一趟,于是同一批人反复到柜、柜前队伍更长、有效产能更低。式 (3) 里 \(u_{j}\) 的分母带 \((1-\rho_{j})\),正是这条传染链:空电率每涨 10 个百分点,等效排队时长不是线性涨,而是加速涨。

第三,"3 座"这个数字本身要能被质疑。为什么不选 4 座?多一座柜的边际收益是多少?这需要一条完整的前沿曲线,而不是一个孤零零的最优解。

1.3 Gurobi 如何求解

模型是 12 个 \(y_{j}\) 加 240 个 \(z_{ij}\) 的 0-1 规划,约束 20*(12+1)+12+1 条,规模小到根节点即证最优。因为 \(p=3\) 只有 220 种组合,本文把 220 组全枚举做交叉校验——MIP 解与枚举最优完全重合,说明建模没有写错。 全枚举还顺手给出帕累托前沿与代价分布,这是 MIP 单独给不出的信息。

2 数学模型

2.1 符号说明

\(i=1,\dots,n\) 为交付点(\(n={NP}\)),\(j=1,\dots,m\) 为换电柜(\(m={NC}\));\(w_{i}\) 为日均换电次数;\(d_{ij}\) 为单程路网距离(km,直线乘 1.25);\(t_{ij}\) 为往返耗时(min);\(\theta\) 为时间价值(元每 min);\(\rho_{j}\) 为空电率;\(q_{j}\) 为基础排队(min);\(\mu_{j}\) 为高峰产能(次每时);\(\phi\) 为高峰换电占比;\(\lambda_{j}\) 为负荷引致的排队增量;\(t^2_{j}\) 为到备用柜的往返耗时;\(\gamma\) 为失败罚金(元);\(L_{j}\) 为柜 \(j\) 的日负荷;\(\bar{C}_{j}\) 为日容量;\(y_{j},z_{ij}\) 为 0-1 决策变量。

2.2 目标函数

\[\min\; Z=\sum_{i=1}^{n}\sum_{j=1}^{m} w_{i}\,c_{ij}\,z_{ij}\tag{1} \]

四项角色混在一个和式里:\(w_{i}\) 是"这个点一天来几次",\(c_{ij}\) 是"来一次要付多少",\(z_{ij}\) 是"来不来这座柜"。\(z_{ij}\) 只允许取 0 或 1 且每点恰好归一座柜,所以 \(\sum_{j}z_{ij}=1\) 把"选柜"和"分单"缝在一起——选柜决定谁可被分,分单决定选柜值不值。

2.3 约束条件

\[c_{ij}=\theta\,t_{ij}+\theta\left(q_{j}+\lambda_{j}\right)+\rho_{j}\left(\theta\,t^{(2)}_{j}+\gamma\right)\tag{2} \]

单次换电的三项代价。第一项 \(\theta t_{ij}\) 是绕行:去 8 min每 km、回 8 min每 km,\(t_{ij}=16d_{ij}\) 当 \(v=15\) km每小时。第二项 \(\theta(q_{j}+\lambda_{j})\) 是排队。第三项 \(\rho_{j}(\theta t^2_{j}+\gamma)\) 是缺电失败:概率 \(\rho_{j}\) 撞上"没满电",罚的是跑备用柜的时间加 5 元白跑罚金。注意第三项用 \(t^2_{j}\)(到第二近柜)而不是 \(t_{ij}\):失败了不去同一座柜,而是去备用柜,这才是真实动作。

\[t_{ij}=\frac{60\cdot 2\,d_{ij}}{v},\qquad \lambda_{j}=\frac{4u_{j}}{1-u_{j}},\qquad u_{j}=\frac{\phi\,L_{j}}{(1-\rho_{j})\mu_{j}}\tag{3} \]

排队增量的来源。\(u_{j}\) 是"高峰等效负荷率":分子是这座柜要承接的换电次数乘高峰占比 \(\phi=0.18\),分母是扣除空电损失后的有效产能 \((1-\rho_{j})\mu_{j}\)。\(\lambda_{j}=4u_{j}/(1-u_{j})\) 是 M/M/1 型的等待时间,\(u_{j}\) 逼近 1 时发散。这就是"空电率传染"的数学形态:\(\rho_{j}\) 抬高,\(u_{j}\) 抬高,\(\lambda_{j}\) 不是加一点,而是往上翻。

\[\sum_{j=1}^{m}y_{j}=p,\qquad \sum_{j=1}^{m}z_{ij}=1,\qquad z_{ij}\le y_{j}\tag{4} \]

选柜的基本三条:恰好选 \(p=3\) 座;每个交付点恰好归属一座柜;只有在选的柜才能接单(\(z_{ij}\le y_{j}\))。最后这条最容易被漏写,漏了它,模型会让没选的柜照常接单,目标值漂亮得毫无意义。

\[L_{j}=\sum_{i=1}^{n}w_{i}z_{ij}\le \bar{C}_{j}\,y_{j},\qquad Q_{j}=q_{j}+\frac{4u_{j}}{1-u_{j}}\tag{5} \]

容量与排队。\(L_{j}\) 是柜 \(j\) 的日负荷,不得超过日容量 \(\bar{C}_{j}\)(高峰产能乘 6 小时的等效周转)。\(Q_{j}\) 是按实际负荷回算的期望排队,它不进入 MIP 目标(是非线性的),只用于结果复核。这是本文最关键的一处"分家":模型里用线性化的 \(\lambda_{j}\) 求解,报告时用真实的 \(Q_{j}\) 复核,两者的差就是线性化偏差,必须报出来而不是藏起来。

\[Z^{\ast}=\min_{|S|=p}\Big[\Theta\sum_{j\in S}L_{j}\bar{d}_{j}+\Theta\sum_{j\in S}L_{j}Q_{j}+\sum_{j\in S}L_{j}\rho_{j}\left(\theta t^{(2)}_{j}+\gamma\right)\Big]\tag{6} \]

回算口径:把最优集合 \(S\) 代进去,三项分别累加。式 (6) 才是"这座柜一天到底花多少钱"的答案,式 (1) 只是它的线性代理。

2.4 规模与性质

12 个 0-1 选址变量、240 个 0-1 指派变量、12 条容量约束,是经典 p-median 加容量。p-median 是 NP-难,但 \(m=12,p=3\) 只有 220 种组合,全枚举 0.3 s 就能跑完,MIP 的价值不在快,而在"能加约束":容量、排队、空电率一旦进来,枚举就失效了(本文化为式 (3) 的线性化系数,本质上仍是 MIP 的路子)。全枚举还给出一个必须交代的事实:220 组里有 209 组满足容量,其余 11 组因为把某一座柜压爆而不可行。

3 Gurobi 求解实现

3.1 建模要点

m.addConstr(gp.quicksum(y[j] for j in range(NC)) == P, name="p")
for i in range(NP):
    m.addConstr(gp.quicksum(z[i, j] for j in range(NC)) == 1)
    for j in range(NC):
        m.addConstr(z[i, j] <= y[j])                       # 未选柜不得接单
for j in range(NC):
    m.addConstr(gp.quicksum(W[i]*z[i, j] for i in range(NP)) <= CAP[j]*y[j])
m.setObjective(gp.quicksum(W[i]*cij[i, j]*z[i, j]
                           for i in range(NP) for j in range(NC)), GRB.MINIMIZE)

两个实现细节。第一,容量约束右边必须乘 \(y_{j}\):写成 \(\bar{C}_{j}\) 而不乘,等于要求没选的柜也扛容量,形式上不违反但让 \(y\) 失去作用,求解器会给出"选了 3 座、另有 9 座也在接单"的荒唐解。第二,排队用"代入基准负荷求一次切线"的方式线性化:\(\lambda_{j}\) 在 \(L_{j}=\bar{L}\) 处取值后当作常数,误差由式 (6) 回算。不要试图在 MIP 里写 \(L_{j}/(1-u_{j})\),那是分式,Gurobi 会直接报错或被迫大 M 线性化,得不偿失。

3.2 求解结果

表 2 三种选柜方案的三项成本对照(日口径,单位:元每日)

方案 选柜 绕行成本 排队成本 缺电失败成本 回算合计 线性口径 相对最优
空电率优先 C03 C07 C01 698.6 410.6 78.7 1187.8 870.2 +410.1(+52.7%)
单柜最优 C08 C03 C11 373.2 1201.2 161.1 1735.5 713.8 +957.8(+123.2%)
MIP 最优 C05 C08 C11 348.1 266.9 162.7 777.7 696.2 0

三行读下来:按空电率挑的 C03(珠江新城) 那一组,回算 1187.8 元;按单柜各自最优挑的 C08(员村) 那组,回算 1735.5 元;MIP 挑的 C05(客村)、C08(员村)、C11(杨箕),回算 777.7 元。 最扎眼的是第二行:它的线性口径 713.8 元只比最优差 17.6 元(2.5%),回算却差 957.8 元(123.2%)。原因一句话:那组解把 57.4 次每日压给 C08 这一座柜,它的高峰产能只有 14.0 次每时,排队从 2.8 分钟涨到 48.8 分钟——线性口径看不见这笔账。

最优三座的分工见表 1 的"日负荷"列:20.0、42.4、19.7 次每日,容量利用率 22.2、50.5、20.5。C08 承担了 51.6% 的换电需求,是名副其实的主力;C05 与 C11 一南一北分列两侧,各 24.4% 与 24.0%。

4 结果分析

图 1 十二座换电柜与二十个交付点的空间分布,连线为归属关系

4.1 空间结构长什么样 图 1 里方框是入选的三座,连线把每个交付点接到它实际会去的那座柜。看得出三座柜不是均匀铺开,而是各自"咬住"一簇需求:C11 咬住西北的天河与杨箕一带,C08 咬住中部的员村与赤岗,C05 咬住南边的客村与鹭江。 三簇之间几乎没有交叉连线,这对 p-median 是好消息——它说明分区是干净的,不存在"两个柜抢同一个点"的摇摆区。反倒是最北的 C10(龙洞)与最南的 C12(大学城)没人管,因为它们各自只有一小片需求,覆盖它们的代价大于收益。

图 2 十二座柜的空电率—基础排队分布,气泡面积为高峰产能

4.2 数据全景:好柜与近柜不是同一批 图 2 把 12 座柜摆在"空电率—基础排队"平面上。左上角(空电率低、排队短)理论上是最理想的柜,可 C03 与 C07 恰恰都在图的左端却离需求重心远;右下角的 C06、C09 空电率高、排队最长,但座位好、就在需求正中间。入选的三座(C05、C08、C11)全落在中左的低空电率区,而 C03、C07 这两座"最干净"的柜落选了——原因不在它们不好,而在它们离人太远。 图里两条平均线把平面切成四块:最优解只取"低空电率"这一半,宁可多绕一点路,也不肯把一次换电赌在一次 30% 的空电概率上。

图 3 三种选柜方案的目标值与实际负荷回算对照

4.3 三方案对比:看得见的差与看不见的差 图 3 的柱子是线性口径,折线是回算。空电率优先 870.2→1187.8,单柜最优 713.8→1735.5,MIP 696.2→777.7。柱子之间的差只有 174.1 元与 17.6 元,折线之间的差却是 410.1 元与 957.8 元——同一组数据,两种口径差出十几倍。 这正是"多属性决策"最容易翻车的地方:把非线性项在线性模型里近似,解出来的方案看着差不多,落到地上差一大截。所以本文的口径是:用线性模型选,用回算模型验,两个都报。

图 4 三方案的累计日绕行耗时阶梯,虚线为最优的 829 min

4.4 绕行是怎么一笔一笔堆上去的 图 4 按每座柜的绕行贡献降序累加。MIP 的三座累计到 829 min 就收住,单柜最优到 889 min,空电率优先到 1663 min。 值得盯的是阶梯的形状:MIP 的第一级最缓,说明它没有把负荷堆在一座柜上;空电率优先的第一级最陡,因为它选中的 C01 最远,一上来就吃掉一大块预算。绕行是硬钱(每 min 都是少跑的一单):空电率优先第一座柜就把虚线顶穿了 740 min,后两座加起来才补回 95 min——起手错一步,后面全在补。

图 5 空电率水平对最优日代价与期望排队的影响

4.5 空电率是第一个放大器 图 5 左轴日代价、右轴平均排队。空电率整体放大到 0.4 倍时,日代价 581 元、排队 5.3 min;放到 1.6 倍时,日代价 840 元、排队 18.0 min。代价曲线几乎是直线,每 0.2 档涨 39 到 40 元;排队却不是直线:1.4 倍之前一直在 5 到 8 分钟之间爬,1.6 倍时直接跳到 18.0 分钟。 排队项 \(u/(1-u)\) 在负荷率逼近 1 时发散,空电率就是那只把 \(u\) 往上推的手。更要紧的是末端选柜集合变了(1.6 倍那一点由 C03 顶替了 C05),模型宁可换一座柜来避开被空电率拖垮的那座——"空电率一高就换柜",这是模型教给人的第一课。

图 6 空电率与缺电失败罚金的二维扫描,星号为本案参数

4.6 空电率 × 罚金 图 6 是五档空电率乘四档罚金的填色热图。同一行从左到右变深(空电率越高越贵),同一列从下到上变深(罚金越高越贵),最浅的格子在左下角。 沿 \(\gamma=5\) 那一行往右看,日代价从 657 涨到 800,涨幅 21.7%;沿 \(\gamma=14\) 那一行,涨幅是 26.7%。罚金越高,空电率越"贵"——两个参数不是相加,是相乘。 这条结论对站长的意义很直接:如果片区白跑一趟的客诉代价上升,先要动的不是选址,是给柜子补电池的频次。

图 7 缺电失败罚金对最优日代价与期望失败次数的影响

4.7 第三维:罚金其实只是一张单价表 图 7 扫罚金 \(\gamma\) 从 1 到 18 元,日代价从 639 涨到 896 元,期望失败次数从 14.2 掉到 14.0 次每日。整条曲线几乎是一条直线,斜率 15.1 元每日每元——这个斜率不是巧合,它就是期望失败次数。 换句话说:罚金只给已经发生的失败贴价签,几乎改不了行为;唯一的例外是末尾 18 元那一档,模型终于换掉了 C05 改选 C03。 所以谈判时"白跑一趟值多少钱"这个问题,答案不在曲线的高低,而在曲线什么时候折。

图 8 绕行耗时与排队耗时的非支配前沿,灰区为排队超限区

4.8 前沿与不可行区 图 8 的横轴是日均绕行耗时、纵轴是日排队耗时,220 个灰点是全部组合,蓝线是非支配前沿。绕行最省的组合只花 795 min 在路上,却要 680 min 排在路上;排队最省的组合把排队压到 395 min,代价是绕行涨到 2282 min——用 1453 min 的绕行去换 241 min 的排队,纯亏。 最优解落在(829, 636)这个拐点上,两个方向都不讨好、也都不吃亏。上方的灰区是排队超过 731 min 的不可行区,也就是"省了路却排到投诉"的那一片。三个标记的分布才是这张图最狠的地方:单柜最优只比最优多跑 60 min 绕行,排队却从 636 min 涨到 2860 min,翻了 4.5 倍;空电率优先两头都不省(1663 min 绕行、978 min 排队);只有 MIP 最优贴在前沿左下角。

图 9 二百二十种组合的日代价分布与累计概率

4.9 220 种组合的代价分布 图 9 是全枚举的直方图(其中 209 种满足容量),中位数 934 元,最优 696.2 元,次优 713.8 元,最差 1458 元。最优贴在分布最左端那个窄尾里,次优只差 17.6 元——说明"挑得不错"与"挑对"在这个规模上只差几块钱;但中位数 934 元是最优的 1.3 倍,随手一挑就要多付三成。 这条分布回答了"值不值得算":在 12 座柜的规模上,算与不算的差别不是"省一点",而是"省掉三分之一的日代价"。

图 10 三座入选柜的三项成本分解与合计

4.10 每一座柜的账目怎么拆 图 10 把最优三座的成本拆成三层。C11 的 88.2 元里有 58.6% 落在绕行一层,因为它的客流分散、负荷轻(19.7 次每日),排队只有 4.0 min;C08 正相反,排队一项就占 202.2 元,是它的最大单项;C05 居中,缺电失败成本 35.3 元反而最显眼,因为它的备用柜最远。三座柜的"贵法"各不相同——这就是多属性选址的真相:没有一座柜在所有维度上都好,最优解是三种"贵法"的最优组合,而不是三座"好柜"的集合。

5 结论

  1. 答案是 C05(客村)、C08(员村)、C11(杨箕)。 日代价 777.7 元(回算口径),三项分别是绕行 348.1、排队 266.9、缺电失败 162.7。这三座不是最近的、也不是空电率最低的,而是"三项加起来最便宜"的。
  2. 按空电率挑柜会多花 410.1 元(+52.7%)。 C03(珠江新城)、C07(岗顶)、C01(天河城) 三座空电率最低,但远离需求重心,绕行成本从 348.1 涨到 698.6 元。"柜里干净"是必要条件,不是充分条件。
  3. 按单柜各自最优挑柜,线性口径只差 17.6 元(2.5%),回算却差 957.8 元(123.2%)。 差在排队:那一组把 57.4 次每日压给 C08,排队从 2.8 min 涨到 48.8 min。这项成本在选址阶段完全看不见,只有在"分单之后回算"时才现形——所以选址模型必须带容量,且必须回算。
  4. 空电率是最强的放大器,而且是乘法不是加法。 空电率整体放大到 1.6 倍时,日代价从 581 涨到 840 元(+44.6%),排队从 5.3 涨到 18.0 min。它同时抬高排队项与失败项,还通过 \(u_{j}\) 的分母压低有效产能。 补电频次比选址更值得先动。
  5. "3 座"这个数字站得住,但要靠两条轴一起说。 绕行与排队此消彼长:绕行最省的那组用 795 min 绕行换 680 min 排队,排队最省的要多跑 1453 min 才省下 241 min 排队。最优解(829, 636)就在前沿的拐点上,再往任何一个方向挪,付出的那一项都大于省下的那一项——所以 3 座、这三座,就是这 12 座柜上的平衡点。

参考文献

[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 -u batch_060.py;数据与结果见同目录 data_060.csv / results_060.json,图见 figs/

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