
把电动汽车电池换电站的选址和定容放在一个模型里求解是我这两年在新能源补能网络规划里做得最多的课题之一。“选址与定容”听上去像个教科书术语落到工程上其实就是两个问题站点建在哪儿站里配多少块电池、几条换电通道。而一旦用 Matlab 把这两件事统一编码进优化模型你就等于有了一块可以反复调参数、改约束、对比方案的沙盘。这篇内容我按自己的实际做法来写适合正在做新能源充电网络布局、物流节点选址、或者拿“选址容量分配”当毕业设计题目的同学参考代码可以直接改数据复现。1. 选址与定容的问题背景先想清楚为什么这两个决策不能分开做很多初学者容易把选址和定容拆成两段独立的任务第一步先选出站点位置第二步再根据预估客流给每个站点配容量。这种做法在计算上省事但在工程上会踩大坑。举个我实际见过的例子。某城市计划铺换电网络初选阶段挑了一个离主干道近、地价也还合适的点位建站成本算下来很漂亮。结果后做定容时发现这个点位周边电网报装容量不够要扩容得额外拉一条专用线路成本直接翻倍。反过来也有站点位置选得远、覆盖需求少但容量按照均值配了结果设备常年闲置。这就说明选址和定容之间存在强耦合站点位置决定了它能吸引多少需求需求又决定了该站要配多少电池和充电设备而这些设备成本又反过来影响“建多少个站、建在哪”的决策。从建模角度看这个耦合关系可以抽象成三类变量。第一类是 0-1 变量表示某个候选点位是否建站第二类是需求分配变量表示某个需求点由哪个站服务第三类是容量变量表示每个站配多少电池、多少通道。三类变量互相牵制需求点要被覆盖需求点只能分配给已建站点而所有分配给站点的需求之和又不能超过该站的容量上限。三个约束条件放在一起就是一个完整的混合整数规划问题。所以我在 Matlab 里落地时没有用朴素的两阶段方案而是把“选址-分配-容量”放进同一个目标函数里。目标函数不只有建站固定成本还有用户到站绕行的折算成本、单位容量建设成本。这样求解出的方案不会出现“站建了但没人去”“需求到了但容量不够”的尴尬局面。这个问题的典型应用场景有三个一是城市级换电网络从零开始规划适合用集合覆盖模型先保底盘二是已经有了一批站点想优化扩容适合用最大覆盖模型或在此基础上的增量模型三是面向物流车队、网约车平台这类需求相对可控、追求服务效率的专用网络p-中位模型和排队定容公式更实用。下文我会逐个展开。2. 选址模型选型集合覆盖、最大覆盖与p-中位如何取舍换电站选址本质上是一个设施选址问题最常用的三类模型分别是集合覆盖模型、最大覆盖模型和 p-中位模型。这三个模型解决的是不同场景下的不同诉求不能上来就无脑套一个。2.1 集合覆盖模型用最少的站覆盖全部需求集合覆盖模型的目标是在给定覆盖半径 R 和候选站点集合的条件下选择尽可能少的站点使所有需求点都至少被一个站覆盖。公式上可以写成决策变量 y_j ∈ {0, 1}表示候选点 j 是否建站目标函数 min ∑ y_j约束条件对每个需求点 i至少存在一个 j 使其覆盖即 ∑ a_ij y_j ≥ 1其中 a_ij 1 表示候选点 j 能覆盖需求点 i这个模型的优点是结构清晰、求解速度快尤其适合城市换电网络建设初期的布点保证“每个热点区域至少有一个站”。缺点也很明显它不区分需求量的差异一个每天只有 20 次换电需求的郊区点和一个每天 400 次换电需求的物流园区在覆盖约束里权重是一样的这会导致资源错配。我在实际项目中通常会把集合覆盖模型当做一个前置过滤器来用先求出最小站点数量把明显冗余的候选点剔除再进入下一轮更精细的优化。2.2 最大覆盖模型预算有限时优先保重点如果城市已经有一部分换电站或者预算只够新建有限数量的站点集合覆盖模型就不合适了因为你根本无力覆盖所有需求。这时候应该用最大覆盖模型固定建站数量 P选择这 P 个站点使被覆盖的需求总量最大化。最大覆盖模型适合那种“钱只够建 5 个站但需求点有 30 个”的场景。目标函数会换成 ∑ w_i z_i其中 w_i 是需求点 i 的需求量z_i 是 0-1 变量表示该点是否被覆盖。约束里增加 ∑ y_j P。这个模型跑完之后你会很清楚哪些需求点被放弃了哪些区域被重点覆盖了。2.3 p-中位模型让所有用户的平均绕行距离最小p-中位模型不再关注“是否覆盖”而是关注“服务距离是否够近”。目标是在候选点中选 P 个设施将每个需求点分配给其中一个设施使所有需求点到其服务设施的加权总距离最小。这个模型对换电站有一个很实际的指导意义用户去换电最怕的不是多跑几百米而是跑了之后还要排队但绕行距离仍然是体验的核心指标。不过纯 p-中位模型有一个问题它可能出现两个站点距离太近、或者某些需求点被分配到极远站点的情况。我一般会在 p-中位模型里叠加覆盖半径约束即需求点只能被分配给 5 公里以内的候选站。这样既保留 p-中位模型“追求总距离最小”的优点又不会出现离谱的远距离分配。2.4 三类模型怎么选我整理了一个简单的选型逻辑表你在拿到一个实际问题时可以快速对号入座场景特征推荐模型例子从零布点必须全区域覆盖集合覆盖模型新城区换电网络规划预算固定站数有限想覆盖最大需求最大覆盖模型老城区补建换电站需求集中且波动小追求平均服务距离短p-中位模型叠加覆盖约束物流车队专用换电网络已经有站想知道扩容哪些站点最大覆盖模型的增量版本换电网络一期转二期实际项目里我从来不只用单一模型而是按“集合覆盖初筛—最大覆盖定站址—p-中位微调分配—定容校验”的顺序走一遍。后面的 Matlab 算例就是按这个思路做的一次浓缩实现。3. 定容计算的关键参数从日需求到电池数量与通道数选址模型解决的是“站在哪”定容解决的是“站配多少东西”。这部分我单独拿出来讲是因为定容公式看起来简单但参数选不好结果基本等于瞎猜。3.1 站内需求量的来源不管是选址还是定容输入的核心数据都是需求。具体到换电站需求通常用“每月的换电次数”或者“每天的换电次数”来衡量。数据可以通过三种渠道获得第一区域内的电动汽车保有量乘以单车月均换电次数再折算到日均值第二运营平台实际的换电订单记录这是最可靠的数据但往往只有存量区域才有第三用交通流量数据间接推算适合完全没有历史订单的城市新区。需求的空间分布也要做热点化处理。我不建议直接把几十万个经纬度点扔进模型更稳妥的做法是先聚类把全城的换电需求聚成若干个“需求质心点”每个点附带一个需求量数值。这样能显著降低模型规模同时需求量的加总误差也在可接受范围内。3.2 电池数量怎么估算一个换电站的电池配置数量核心思路是“站内电池要在高峰期撑起充电周转”而不是简单地等于日均需求除以服务时长。我常用的估算公式是B_j ceil( (D_j × η_peak × T_charge) / (H_day × ρ) )其中D_j 是站点 j 的日均换电需求次η_peak 是高峰时段集中系数一般取 1.2 到 2.0取决于早高峰和晚高峰的挤兑程度T_charge 是单块电池的平均充电时长小时取决于电池容量和充电功率H_day 是站点日服务时长小时如果是无人值守站通常按 16 到 24 小时计ρ 是电池利用率系数一般取 0.7 到 0.85因为有部分电池可能在检修、在车上未回站举个例子某站日均需求 300 次高峰系数 1.5电池充电时长 4 小时日服务时长 16 小时利用率 0.8。那么站内电池数量就是B (300 × 1.5 × 4) / (16 × 0.8) 1800 / 12.8 ≈ 140.6向上取整需要配置至少 141 块电池。这个数字明显高于“日需求 300 ÷ 16 小时 × 4 小时 75 块”的简单估算原因就是高峰系数放大了需求集中度利用率系数又要求留有余量。这正是定容和“平均主义”算法的本质区别。3.3 换电通道数量与电力容量换电通道数量主要由高峰小时到达率决定。假设单条通道平均 5 分钟完成一次换电那么单通道每小时服务能力是 12 次。如果估算该站高峰小时到达率为 40 次那么至少需要 4 条通道才不至于排长队。电力容量是另一个容易被忽略的约束。换电站的电力容量主要决定同时能给多少块电池充电。如果单块电池充电功率是 60kW站内同时充电的电池数量最多为 10 块那么该站报装容量至少需要 600kW再算上换电设备本身的用电实际建议预留 10% 到 15% 的裕量。这块数据在国际化项目里往往决定了站点选址的可行性因为电网扩容成本有时比建站成本还高。3.4 用排队论做更精细的容量校验上面的公式是工程速算适合初排。如果要做更精细的容量校验我会引入简单的排队论模型。把每一座换电站看成 M/M/c 排队系统车辆到达服从泊松分布服务时间近似指数分布c 就是通道数。通过 Erlang-C 公式可以算出目标等待概率下的最低通道数。这里的关键约束是高峰 30 分钟内车辆平均等待时间不超过 15 分钟或者 95 百分位的等待时间不超过 30 分钟。把这些服务水平约束翻译成排队论参数再反推通道数量和电池数量比单纯乘系数更可靠。不过排队模型需要标定到达分布如果没有实际订单数据标定误差反而比工程速算更大。所以我的习惯是前期用速算公式定初值后期有了运营数据再做排队论验证。4. Matlab 代码实战一套可复现的最小成本选址定容算例这一部分我们直接动手。我会给一个完整可运行的算例代码分成四块输入数据、模型构建、求解调用、后处理定容与可视化。4.1 算例设置假设某区域有 30 个需求点对应 30 个小型换电需求聚集区每个点有日均需求量 q_i。候选站点有 10 个从需求点中随机抽样产生每个站有建站固定成本。覆盖半径设为 5 公里所有需求点在欧氏距离内满足覆盖约束。目标是最小化总成本包含建站成本、用户绕行折算成本和容量建设成本。先看输入参数部分% 随机种子保证结果可复现 rng(42); % 需求点坐标与需求量 n_demand 30; demand_xy rand(n_demand, 2) * 40; % 需求点坐标单位 km q randi([80, 400], n_demand, 1); % 每个需求点日均换电次数 % 候选站点坐标与建站成本 n_cand 10; cand_idx randperm(n_demand, n_cand); cand_xy demand_xy(cand_idx, :); build_cost 500 rand(n_cand, 1) * 300; % 建站成本单位万元 % 覆盖半径和单位绕行成本 cover_R 5; % 单位 km alpha 0.5; % 单位距离绕行折算成本万元/万人次/km我这里把需求量 q 设在 80 到 400 之间是为了模拟不同热度区域的需求差异。建站成本设置为 500 到 800 万元的不等值反映不同候选点的地价、场地改造成本差异。绕行折算成本 alpha 的作用是让模型在“多建站减少绕行”和“少建站降低固定成本”之间做权衡。4.2 距离矩阵与可达性判断选址模型的核心输入是距离矩阵。直接用欧氏距离是初筛阶段的常用做法但实际路网距离通常要除以一个曲折系数来修正。我会在代码里保留欧氏距离同时注释掉一行“路网修正”的代码方便你按城市路况调整% 距离矩阵 D zeros(n_demand, n_cand); for i 1:n_demand for j 1:n_cand D(i, j) norm(demand_xy(i, :) - cand_xy(j, :)); % 如果考虑路网曲折可以改成 D(i,j) D(i,j) / 0.65; end end % 覆盖可达矩阵 reachable D cover_R;这段代码后面所有约束都基于 reachable 矩阵。如果某个需求点到某个候选站的距离超过 5 公里那么 x_ij 对应的变量就没有存在意义我会在变量上下界里直接把它锁死为 0这样既符合模型逻辑又减少了无效搜索空间。4.3 变量定义与约束矩阵构建这个算例的决策变量分两组。第一组是 10 个建站变量 y_j第二组是 30×10300 个分配变量 x_ij总变量数为 310 个。这里我把 x_ij 定义为一个 30×10 的矩阵变量用一个大下标来索引% 变量排列顺序y_j (1..n_cand)然后 x_iji外层j内层 Nvar n_cand n_demand * n_cand; % 目标函数系数 f zeros(Nvar, 1); f(1:n_cand) build_cost; % 建站固定成本 % 分配变量的绕行成本系数 for i 1:n_demand for j 1:n_cand idx n_cand (i-1) * n_cand j; f(idx) alpha * q(i) * D(i, j); end end % 变量类型设置y 和 x 都是 0-1 变量 vtype char(ones(1, Nvar) * B);为了避免 x_ij 被分配到不可达的组合我直接把不可达变量的 ub 设为 0可达变量的 ub 设为 1lb zeros(Nvar, 1); ub ones(Nvar, 1); for i 1:n_demand for j 1:n_cand idx n_cand (i-1) * n_cand j; if ~reachable(i, j) ub(idx) 0; end end end然后是约束矩阵。我分成三类第一类每个需求点必须被分配到一个站Aeq zeros(n_demand, Nvar); beq ones(n_demand, 1); for i 1:n_demand for j 1:n_cand idx n_cand (i-1) * n_cand j; Aeq(i, idx) 1; end end第二类需求点只能分配给已建站。即 x_ij - y_j ≤ 0。这一组约束有 n_demand × n_cand 条其中不可达组合由于 ub 为 0 可以跳过n_cons2 n_demand * n_cand; Aineq sparse(n_cons2, Nvar); bineq zeros(n_cons2, 1); row 0; for i 1:n_demand for j 1:n_cand if ~reachable(i, j) continue; end row row 1; idx_x n_cand (i-1) * n_cand j; Aineq(row, idx_x) 1; Aineq(row, j) -1; end end Aineq(row1:end, :) []; bineq(row1:end, :) [];第三类容量约束。预先设定每个站每天最大服务 700 次换电所有分配给该站的需求之和不能超过这个上限cap_max 700; % 单个站点最大日服务能力 A_cap zeros(n_cand, Nvar); b_cap zeros(n_cand, 1); for j 1:n_cand for i 1:n_demand idx n_cand (i-1) * n_cand j; A_cap(j, idx) q(i); end A_cap(j, j) -cap_max; % y_j * cap_max 的那个约束 % 因为如果 y_j 0则右侧为 0x 全为 0 end这里注意容量约束本身和“只能分配给已建站”有重叠逻辑但当企业有明确容量上限时这组约束必不可少。如果求解提示不可行大概率是某个候选站的覆盖需求总量超过了 cap_max这时要么放宽 cap_max要么增加候选站数量。求解调用用 intlinprogMatlab 的优化工具箱可以直接处理混合整数线性规划问题。310 个变量、几百条约束对 intlinprog 来说是小规模问题一般几秒内能出结果% 合并约束矩阵 A [sparse(Aineq); sparse(A_cap)]; b [bineq; b_cap]; % 求解 intcon 1:Nvar; % 全部变量都是整数 0-1 options optimoptions(intlinprog, Display, final); [x_opt, fval, exitflag] intlinprog(f, intcon, A, b, Aeq, beq, lb, ub, options); % 提取结果 y_opt round(x_opt(1:n_cand)); opened find(y_opt 1);4.4 后处理按分配结果算定容选址和分配变量求解出来后我来做定容后处理。对每个已建站点统计它被分配到的需求点、累加日均需求量再套用前面讲的定容公式计算电池数量和换电通道数量% 定容参数 peak_factor 1.5; % 高峰系数 charge_hour 4; % 电池充电时长小时 business_hour 16; % 日服务时长小时 utilization 0.8; % 电池利用率 service_time 5; % 单车换电时间分钟 fprintf(最优建站数%d总成本%.2f 万元\n, length(opened), fval); for j 1:n_cand if y_opt(j) 0 continue; end % 找到分配给 j 站的需求点 served zeros(n_demand, 1); for i 1:n_demand idx n_cand (i-1) * n_cand j; served(i) round(x_opt(idx)); end D_j sum(q(served 1)); if D_j 0 continue; end battery_num ceil(D_j * peak_factor * charge_hour / (business_hour * utilization)); lambda_peak D_j * peak_factor / business_hour; % 高峰小时到达率 channel_num ceil(lambda_peak / (60 / service_time)); fprintf(站 %d覆盖需求点 %d 个日均需求 %d 次电池 %d 块换电通道 %d 条\n, ... j, sum(served), D_j, battery_num, channel_num); end这块代码输出的结果是模型层面的最终成果可以直接用于可研报告。我一般还会把每个站点的服务需求点列表、距离统计导出成 Excel方便后续做站点设计和电力容量对接。4.5 结果可视化可视化对于选址问题特别重要因为决策者和评审不会只看表格。我一般画两张图第一张展示需求点、选中站点和覆盖圆第二张用柱状图展示各站点的电池数量和通道数对比。figure; hold on; % 画覆盖圆 for j opened theta linspace(0, 2*pi, 100); x_c cand_xy(j, 1) cover_R * cos(theta); y_c cand_xy(j, 2) cover_R * sin(theta); fill(x_c, y_c, [0.9 0.95 1], EdgeColor, none, FaceAlpha, 0.3); end % 画需求点 scatter(demand_xy(:, 1), demand_xy(:, 2), 80, q, filled); colorbar; % 画候选点 scatter(cand_xy(:, 1), cand_xy(:, 2), 120, ks, LineWidth, 2); % 画选中站点 scatter(cand_xy(opened, 1), cand_xy(opened, 2), 200, rp, LineWidth, 2); xlabel(x (km)); ylabel(y (km)); title(换电站选址结果与覆盖范围); axis equal;用散点大小和颜色同时表示需求量可以直观看出站点是否真的落在“需求密集”的位置。覆盖圆用半透明填充避免遮挡下面的需求点。这套可视化代码在论文里也经常能直接用。5. 实操中的典型坑求解报错、结果不合理怎么办代码能跑通不代表结果合理。我自己在调试这种选址定容模型时经常遇到下面几个问题整理成速查表供大家对照。现象可能原因排查与解决办法intlinprog 报 “No feasible solution”有需求点未被任何候选站覆盖或容量约束过紧打印 reachable 矩阵检查是否存在行为全 0 的需求点临时调大 cap_max 或减少约束确认可行域是否打开求解很快但结果全是 0不建站目标函数的绕行成本系数 alpha 过小建站成本过高模型发现不建站最便宜检查单建站成本和绕行成本量纲是否合理换电站不可能一个不建给模型加入“至少建 3 个站”的约束或调大 alpha有些需求点被分配给超远站点覆盖约束被无意跳过或覆盖半径设得太大检查 ub 设置确认不可达组合的 ub0把 cover_R 从 5km 缩小到 3km看分配是否变化定容得到的电池数量太大建设成本超预算高峰因子取太高或充电时长太长把高峰系数从 1.5 调到 1.2充电时长按快充实际曲线折算后再试也可以引入错峰充电策略降低峰值同时充电电池数同一批数据跑多次结果不一样随机数种子没固定或 intlinprog 的启发式存在随机性在代码开头加 rng(固定值)对关键算例用不同的 rng 跑 10 次统计方案稳定性模型里只选了距离近的站但没有考虑地价差异建站成本没进目标函数或者没把地价差异体现在 build_cost 里把候选站的造价改成“固定建设成本地价×面积”地价差异会对选址产生决定性影响需求点只有 30 个但全城地图上有几千个点没有做需求点聚合先用 kmeans 或者 DBSCAN 聚类把经纬度点聚合成需求质心再代入模型几个额外的实操心得都是文档里不会写的第一个心得覆盖半径的设置一定要区分欧氏距离和路网距离。城市里两点直线距离会比实际驾车距离短得多如果不做修正模型会认为用户 5 分钟就能到站实际可能开 15 分钟。我习惯直接取欧氏距离除以 0.65 作为路网修正具体修正系数按城市道路密度和数据统计标定。第二个心得高峰系数不要拍脑袋取 2.0。如果项目有平台历史数据我会先把一天按小时拆成 24 个时段统计每个站点潜在覆盖区域内换电需求的分布再用 P90第 90 百分位那个小时的需求除以日均小时需求得到的高峰系数才是真正可解释的。用拍脑袋的系数算定容算出来的电池数量要么浪费要么不够。第三个心得intlinprog 在变量规模超过几千个时求解时间会明显上升。如果候选点非常多建议先用覆盖率、地价、电力容量做一次人工或者代码初筛把候选点压到 30 个以内再进模型。算法不是万能的一个好的候选点集合比一味加大模型规模更有效。第四个心得模型结果一定要做敏感性分析。我最常做的就是把建站成本上下浮动 20%、覆盖半径上下浮动 1 公里看最优选址方案变化大不大。如果某个站点的建设状态对参数波动非常敏感说明这个站点的经济性处于临界状态实际决策时要格外谨慎也可能需要重新调研现场条件。敏感性分析在 Matllab 里只需写个循环批量跑模型非常顺手。我个人在实际项目里跑这套模型的最大体会是换电站选址定容的难点从来不在算法代码本身而在于把模型的目标函数、约束条件与实际业务逻辑对齐。比如绕行成本怎么折算成钱、排队时间能不能量化进目标函数、建站成本里有没有算入电力增容费用这些变量才是决定模型结果可靠性的关键。代码框架只是把这些逻辑高效地转成可计算的数学表达式但业务判断仍然要由人来做。最后再分享一个小技巧这套 Matlab 代码跑完后我会把每个站点的覆盖需求点、平均距离、电池数量、通道数量全部导出成 CSV 文件再用地图软件做一次人工复核。重点看那些“模型选了但周边实际是农田或限制建设区域”的候选点把明显不合适的点位换成备选点重新求解。这样一两轮迭代下来最终方案基本就可以进入可研阶段了。