☰
配电网韧性研究:应急移动电源动态调度Matlab代码实现详解
2026/10/18 5:31:52 网站建设 项目流程

復现一篇 SCI 一区论文的配电网韧性研究,最难的不是看懂模型,而是把论文里轻描淡写的约束还原成能跑出结果的 Matlab 代码。这篇专门讲应急移动电源动态调度(MPS Dynamic Dispatch)的实现,接上篇的预配置问题。

上篇我们解决了“灾前把移动电源放哪”,下篇解决的是“灾中每台移动电源该去哪、该发多少电”。这个问题和静态预配置完全不同:预配置是在灾害之前基于预测场景做一次性选址,本质是一个随机优化或鲁棒优化问题;动态调度则要在灾害发生后,面对不断更新的线路故障信息和负荷状态,滚动地给每台移动电源规划接入节点和出力,既要照顾电网潮流约束,又要兼顾车辆移动时间、电池容量甚至交通条件。如果这两块叠在一起,模型复杂度立刻上一个台阶。

我这次复现用的是 Matlab + YALMIP + CPLEX 的组合,算例是经典的 33 节点辐射状配电网。下面从数学模型开始,一步步说到代码实现,中间穿插我在调试时踩过的六个坑。如果你也在复现韧性调度类论文,或者在做应急电源配置课题,这篇大概率能帮你省下至少两周的调代码时间。

1. MPS动态调度到底在解决什么:从“放哪”到“怎么跑”的衔接

1.1 韧性研究的四个阶段与MPS的定位

配电网韧性研究一般把极端事件过程拆成四个阶段:灾前预防、灾中抵御、灾后响应、恢复重建。传统可靠性关心的是日常故障的平均停电时长,而韧性关心的是“台风、冰灾这种低概率高影响事件下,系统能不能扛住、能不能快速恢复”。

MPS(应急移动电源,可以是移动储能车,也可以是应急发电车)在其中的角色很有意思。固定储能装在变电站,一块极端事件把线路打断,末端重要负荷照样带不上。MPS的价值恰恰在于“机动”:故障点在哪,电源就能跟到哪,通过计划孤岛方式直接接入断线区附近的关键负荷。在抢修班组还没到达现场的时候,MPS就是恢复供电最快的手段。

1.2 预配置和动态调度的边界划分

很多论文把预配置和动态调度放在一个两阶段框架里看。预配置是“灾前决策”,输入是台风的路径预报、历史负荷数据,输出是每辆 MPS 的初始停靠位置;动态调度是“灾中决策”,输入是已经暴露的故障线路集合、当前各节点负荷状态、预配置给出的 MPS 初始位置,输出是未来若干时段内每辆 MPS 的完整行动计划。

要特别提示的是,动态调度并不是一次求解就完事。灾害过程中的故障信息是滚动披露的,比如台风路径变了、某条线路提前抢通、某条新线路断开了,这些信息很难在建模型时全部提前知道,所以主流方案是用滚动时域的方式做模型预测控制:每个时段求解一次未来时间窗内的优化问题,但只执行当前时段的决策,下一时段再带着最新状态重新求解。

1.3 一个可复现的输入输出框架

动手写代码前,最好先把输入输出理清楚。我这次复现时给模型定义了这样一套边界:

输入具体内容
网络拓扑节点数、支路数、线路电阻电抗、线路传输容量上限
负荷数据各节点 24 小时有功/无功负荷曲线、负荷权重
故障场景断线支路集合、断线发生的时段、预计抢通时段
MPS 参数电池容量、最大注入功率、移动速度、初始位置
预配置结果上篇算出的每辆 MPS 灾前停靠节点
输出具体内容
时段调度结果每辆 MPS 在每时段接入的节点编号
出力计划各时段 MPS 的有功、无功注入量
储能状态每辆 MPS 的 SOC 变化曲线
负荷恢复结果各节点每时段的切负荷比例、恢复比例
韧性指标系统总失负荷能量 ENS、重要负荷恢复时长等

这套输入输出框架先定下来,后面无论改成 123 节点还是 118 节点系统,代码结构都不用重写,只需要改数据表。

2. 动态调度模型:把“MPS该去哪”写成可求解的数学问题

2.1 时段离散化与决策变量定义

把调度周期分成 T 个时段,每个时段时长 Δt。以 24 小时调度为例,T=24,Δt=1 小时。节点集合用 N 表示,支路集合用 E 表示,MPS 的集合用 M 表示。

决策变量我分成四组:

  • 接入状态变量 z(m,i,t) ∈ {0,1}:第 m 辆 MPS 在时段 t 是否接入节点 i;
  • 出力变量 p(m,i,t)、q(m,i,t):第 m 辆 MPS 在时段 t 接入节点 i 时注入的有功、无功功率;
  • 储能状态变量 soc(m,t):第 m 辆 MPS 在时段 t 末的荷电状态;
  • 切负荷变量 u(i,t):节点 i 在时段 t 被切除的负荷比例。有些论文把 u 写成 0-1 二进制,表示“接上/没接上”,但第一版建议先用连续变量 [0,1],跑通后再改二进制,因为二进制会让求解时间成倍增加。

此外还有潮流变量:支路功率 P(ij,t)、Q(ij,t),节点电压幅值平方 V(i,t)。在 DistFlow 模型里,V 直接表示电压幅值的平方,这样做的好处是很多约束变成线性或二阶锥,求解器友好。

2.2 潮流约束:DistFlow 与二阶锥松弛

配电网是辐射状结构,经典做法是用 DistFlow 方程描述潮流:

对于每条支路 (i,j),有功和无功的流动关系写成:

  • P(ij,t) - Σ P(jk,t) = p_j_inj(t)
  • Q(ij,t) - Σ Q(jk,t) = q_j_inj(t)
  • V(j,t) = V(i,t) - 2(r·P(ij,t) + x·Q(ij,t)) + 电压损耗项

这里的 r、x 是支路电阻和电抗。电压损耗项包含支路电流平方,直接写会导致非凸约束,所以标准做法是用二阶锥松弛:

|| [2·P(ij,t), 2·Q(ij,t), V(i,t)-V(j,t)] ||_2 ≤ V(i,t)+V(j,t)

这个锥约束把非凸问题松弛成凸问题,对辐射状配电网在大多数工况下是精确的,误差在可接受范围内。在 YALMIP 里写法非常直接:

for t = 1:T for k = 1:nl con = [con, cone([2*P(k,t); 2*Q(k,t); V(bus_i(k),t)-V(bus_j(k),t)], ... V(bus_i(k),t)+V(bus_j(k),t))]; end end

节点注入功率由本地负荷和 MPS 出力共同决定:

p_j_inj(t) = p_j_load(t)·(1 - u(j,t)) - Σ_m p(m,j,t)

这个式子的意思是:节点注入功率等于“没有被切除的负荷”减去“MPS 注入的功率”。如果 MPS 从节点往外供负荷,对网络来说相当于一个负的负荷。

2.3 MPS时空耦合约束:防止车辆“瞬移”

这部分是我复现时最容易出问题的环节,也是论文里往往一笔带过的地方。

先加最基本的约束:每辆 MPS 每个时段最多只能接入一个节点:

Σ_i z(m,i,t) ≤ 1

再加“接入才能出力”的约束:

p(m,j,t) ≤ P_max · z(m,j,t) q(m,j,t) ≤ Q_max · z(m,j,t)

这两条是线性大M约束的紧凑形式,本质上就是告诉求解器:车不在节点,就不能往网络里送功率。

然后是移动时间约束。假设车辆从节点 i 移动到节点 j 需要 K(i,j) 个时段,那么从 i 出发后,K(i,j) 个时段之内是不可能出现在 j 的。翻译成约束就是:

对任意 m、任意 i≠j、任意 t、任意 k=1,…,K(i,j)-1,成立:

z(m,i,t) + z(m,j,t+k) ≤ 1

这个不等式的含义很直白:如果车辆在 t 时段停在 i,那么在 t+1 到 t+K-1 这些时段里都不能出现在 j。换个角度看,这就是“车辆不会瞬移”的数学表达。它不会限制车辆一直停在原地,也不会限制它在满足移动时长之后正常出发,只管住“不该出现的时候别出现”。

这个时间窗写法的好处是:不需要额外引入大量“移动中”状态变量,约束数量虽然因为循环嵌套会多一些,但对 33 节点系统完全可控。如果你的论文用了更严格的时空网络流模型,原理是一样的,只是变量更多、求解更慢。

2.4 储能SOC约束

MPS 作为移动储能车时,SOC 的计算不复杂:

soc(m,t) = soc(m,t-1) - [Σ_i p(m,i,t)] · Δt / C_m

其中 C_m 是第 m 辆车的电池容量。同时加上容量边界:

soc_min ≤ soc(m,t) ≤ soc_max

如果论文里考虑了 MPS 应急发电车的情况,原理也一样——只不过容量上限换成燃料储量,SOC 换成油量百分比。多数韧性论文里 MPS 只对外放电,不反向充电,所以这里不需要充放电的二进制切换变量。

2.5 目标函数:把韧性变成可最小化的数值

韧性提升最终要落成一个可以优化的指标。最常用的目标是最小化加权失负荷能量 ENS:

min Σ_t Σ_i w(i) · p_i_load(t) · u(i,t) · Δt

其中 w(i) 是节点 i 的负荷权重。医院、通信基站、供水这些重要负荷权重取大,比如 100;普通居民负荷权重取 1。这样求解器在资源有限时会优先恢复重要负荷。

目标函数的符号方向特别容易搞反。有些论文写的是“最大化恢复电量”,也就是 Σ w(i)·p_i_load(t)·(1-u(i,t))·Δt,和最小化失负荷只差一个常数项(总负荷固定时两者等价)。但如果总负荷随时间变化,就不完全等价。复现时我建议先按论文原文的目标写,算出来的数值再和论文表格对比,方向和量级都对齐之后再做优化调整。

3. Matlab代码实现:从模型到可运行程序

3.1 数据准备:33节点算例与MPS参数

我这次用的算例是经典的 33 节点辐射状配电网,基准电压 12.66kV,总负荷约 3.7MW。数据表格里需要:节点编号、节点有功/无功负荷、支路首端/末端节点、支路电阻电抗、传输容量上限。

MPS 参数表我设计成这样的形式:

MPS 编号容量/kWh最大有功/kW最大无功/kVar移动单位时间/(节点/时段)初始位置
MPS1150050030018
MPS21000350200125
MPS3800250150130

故障场景我设为:时段 8 起,线路 5-6、9-10、14-15 断开,预计抢通时间分别为第 16、18、14 小时。这里要提醒一句:不同论文的故障场景差异很大,复现时务必逐条核对论文表格里的断线集合和时段,错一个数字结果就对不上。

3.2 YALMIP建模:变量声明与约束组装

YALMIP 的建模思路是声明变量、写约束、设目标、调求解器。核心代码结构如下:

%% 决策变量声明 z = binvar(M, Nb, T, 'full'); % 接入状态 pM = sdpvar(M, Nb, T, 'full'); % MPS有功出力 qM = sdpvar(M, Nb, T, 'full'); % MPS无功出力 soc = sdpvar(M, T, 'full'); % 荷电状态 u = sdpvar(Nb, T, 'full'); % 切负荷比例 0~1 P = sdpvar(Nl, T, 'full'); % 支路有功 Q = sdpvar(Nl, T, 'full'); % 支路无功 V = sdpvar(Nb, T, 'full'); % 节点电压幅值平方 %% 约束组装 con = []; % 每个时段最多接入一个节点 for t = 1:T for m = 1:M con = [con, sum(z(m,:,t)) <= 1]; end end % MPS出力限制(接入才能出力) for t = 1:T for m = 1:M for i = 1:Nb con = [con, pM(m,i,t) <= P_max(m) * z(m,i,t)]; con = [con, qM(m,i,t) <= Q_max(m) * z(m,i,t)]; end end end % SOC更新 for t = 2:T for m = 1:M con = [con, soc(m,t) == soc(m,t-1) - sum(pM(m,:,t))*dt / C(m)]; end end % 二阶锥潮流约束 for t = 1:T for k = 1:Nl i = branch_i(k); j = branch_j(k); con = [con, cone([2*P(k,t); 2*Q(k,t); V(i,t)-V(j,t)], ... V(i,t)+V(j,t))]; end end

写完约束后,还需要在支路首末端功率、节点功率平衡之间建立关系式。这部分代码比较机械,核心是遍历每条支路和每个节点,把 DistFlow 的线性等式写进去。我建议先把节点注入功率表达式抽出来做一个中间变量。比如用 inj_p(node,t) 表示节点净注入,再用它去写潮流平衡,索引出问题时会容易定位。

3.3 求解器配置与求解

求解配置我建议写成这样:

ops = sdpsettings('solver','cplex', ... 'verbose',2, ... 'cplex.mip.tolerances.mipgap',0.01, ... 'cplex.timelimit',600); sol = optimize(con, objective, ops); if sol.problem == 0 disp('求解成功'); else % 这里一定要看,很多隐性错误会通过problem flag暴露 sol.info yalmiperror(sol.problem) end

如果机器上没有 CPLEX license,可以把 solver 换成 Gurobi,或者用开源的 SCIP。但提醒一下:带二阶锥约束的混合整数优化,规模一大开源求解器速度会明显差一截,33 节点 24 时段内还可以接受,再大的算例就要认真考虑求解器选型。

3.4 滚动时域调度的循环结构

动态调度的核心不是单次求解,而是多次滚动。我的实现思路是这样:

% 初始化MPS状态 soc_now = soc_init; z_now = z_init; results = {}; for t0 = 1:T % 1. 根据t0时刻已知信息,生成预测窗口:t0 到 t0+H-1 % H是预测窗口长度,比如6;越往后信息越全,可以适当延长 % 2. 建立窗口内的优化问题(变量、约束、目标) % 窗口内的起始状态直接用当前实际状态 soc_now, z_now % 3. 调用求解器求解窗口问题 % 4. 只提取 t0 时段的决策: % z_execute = value(z(:,:,1)); % p_execute = value(pM(:,:,1)); % soc_execute = value(soc(:,1)); % 5. 更新当前状态 soc_now、z_now,把执行结果存入 results end

这里最容易踩的坑是“把整个窗口的求解结果都当成执行结果”。记住,MPC 原则永远是求解未来 H 个时段、只执行当前 1 个时段。下一时刻重新求解时,窗口里的第一个时段就是新的当前时段。索引上稍微偷懒,整个结果就会错位。

4. 复现中踩过的六个坑,以及完整排查链路

4.1 坑一:MPS瞬移

我的第一版模型没写时间窗约束,只写了“每时段最多接入一个节点”,结果求解出来的轨迹完全离谱:MPS1 时段 3 还在节点 8,时段 4 直接出现在节点 30,中间隔着半个配电网,明显不合理。

排查方法很简单:把每辆车的 z 矩阵抽出来,横轴画时段、纵轴画节点编号,车辆的运动轨迹一眼就能看出有没有瞬移。我当时画完图就明白了,然后补上第 2.3 节的时间窗约束,轨迹立刻变得物理可行。这里给个建议:无论模型多复杂,先把 z 出行轨迹图,再做其他分析。

4.2 坑二:非线性潮流数值病态

第一版我直接用有名值写潮流参数,电压单位是 V,功率单位是 W,电阻是 Ω,结果二阶锥约束数值跨度从 1e-4 到 1e4,CPLEX 求解时反复出现数值警告,有些锥约束干脆不闭合。

解决办法是把整个系统转到标幺制:电压基准取 12.66kV,功率基准取 1MVA。改完之后数值量级基本在 0.1~2 之间,求解器收敛很快,稳定性明显提升。规则很简单:所有跟电压、功率、阻抗相关的参数和变量,统一换算到标幺值,不要混用单位。

4.3 坑三:大M参数选得太大

接入与出力的关联约束里,Z 取 0 时要强制 p=0。常规写法是加一个足够大的 M,于是很多新手会写 M=1e6。结果 M 太大,LP 松弛后的可行域过松,整数变量分支剪枝效率很差,求解时间翻了几倍。

我最终把 M 取成对应 MPS 最大出力的 1.2 倍,比如最大出力 500kW,M 就取 600kW。约束依然能有效钳制变量,但数值特性好很多。经验法则:大M取物理量上限的 1.2~1.5 倍就够,不需要无穷大。

4.4 坑四:求解时间爆炸

33 节点、24 时段、3 辆 MPS,z 变量就有 3×33×24≈2376 个二进制,加上切负荷二进制变量,规模不小。第一次全二进制建模,CPLEX 跑了半小时还没出最优解。

我的压缩手段按顺序进行了三种:

  1. 缩小候选接入节点:把断线区域之外、且不承担重要负荷的纯末端节点从候选集里删掉,z 变量减少约 40%;
  2. u 变量从 0-1 改成连续 [0,1],二进制变量数量大幅下降;
  3. 设置 mipgap=0.01,允许 1% 最优性间隙。

改完后单次求解稳定在 2~4 分钟,滚动 24 次总时长约 80 分钟。如果你的课题更强调“足够快”,还可以在求解前用贪心策略给 CPLEX 喂一个初始可行解,效果立竿见影。

4.5 坑五:结果和论文对不上

当我终于跑出曲线后,和论文里的数值对比,发现 ENS 差了不少。当时第一反应是模型错了,后来逐项核对才发现,论文用的是“切负荷 0-1 状态”而不是“连续比例”,而且故障时段比我设置的晚了一个时段。调整之后数值基本吻合了。

我建议的排查顺序是:故障线路表 → 故障时段 → MPS 初始位置 → 负荷权重 → 目标函数符号 → 移动时间定义。还有一个隐蔽点:论文里“移动一个节点”的时间到底是 1 个时段还是 0.5 个时段,代码里 K(i,j) 矩阵的定义会影响整个调度结果,必须核对清楚。

4.6 坑六:滚动时域首时段执行逻辑

这个问题非常隐蔽。我第一次实现滚动时域时,求解完一个窗口后直接把窗口内所有时段的 z 都记录进了 results,导致后面的车辆轨迹跟“上帝视角”一样提前知道了未来故障,结果优于论文,当时还挺高兴——后来一分析才发现逻辑错了。

正确做法是每次只取窗口第一个时段的决策,更新当前 MPS 状态和 SOC,然后进入下一轮。判断自己的滚动是否正确的简单方法:故意在第二个时段增加一个突发故障,如果车辆轨迹在故障发生前就有了反应,说明你记录错了执行时段。

5. 结果分析与扩展方向:从复现到真正做研究

5.1 结果怎么呈现,能读出什么

复现完代码之后,最需要的是把结果可视化出来,方便和论文对比,也方便自己理解动态调度的价值。

我个人比较推荐的三个图:

  • 韧性曲线:横轴时间、纵轴累计失负荷能量。动态调度、固定接入、无 MPS 三种方案画在同一张图里,曲线之间的空隙就是论文的核心贡献。
  • MPS 轨迹图:横轴时段、纵轴节点,每辆车一条折线,配合断线节点标注。能直观看到 MPS 如何从预配置位置出发、移动到关键负荷区域、最后撤离。
  • SOC 曲线:每辆车一个子图,看储能是否在关键时段保持足够余量。如果某辆车总是处于满电或空电的极端状态,说明容量配置或者调度策略有问题。

绘图代码本身不复杂:

figure; plot(1:T, ens_dynamic, 'LineWidth', 2); hold on; plot(1:T, ens_fixed, '--', 'LineWidth', 2); plot(1:T, ens_nomeps, ':', 'LineWidth', 2); legend('动态调度','固定接入','无MPS'); xlabel('时段'); ylabel('累计失负荷能量(kWh)');

5.2 从复现代码出发的三个扩展方向

代码跑通之后,如果想把这套逻辑延伸到自己的研究里,我建议从三个方向切入。

第一,多类型移动资源协同。把 MPS 从“只能放电”扩展为“可充可放的移动储能”,甚至加入应急发电车、移动制氢车等对象,车辆之间可以交接供电任务。模型只需要增加充放电二进制变量和交接时序约束,整体框架不用动。

第二,MPS 与配电网其他灵活资源联合调度。配电网里往往还有分布式光伏、固定储能、软开关 SOP。把它们纳入同一个调度模型,MPS 可以只负责“补缺口”,其他资源负责削峰填谷,恢复效果和经济性都可能更好,但模型量级会上去,计算复杂度也会明显增加。

第三,交通网络与配电网耦合。现实中的 MPS 移动时间不是欧氏距离决定的,而是受路网可达性、道路拥堵甚至洪水淹没道路影响。可以预先用路网最短路径算法生成节点间移动时间矩阵 K(i,j),替换掉原来的直线距离矩阵,本质上不改变模型结构,但结果会贴近实际。

5.3 一点经验收尾

这次复现最花的部分,不是数学模型推导,不是求解器调参,而是把约束条件一条条翻译成 YALMIP 时保持索引一致。变量一多,任何一个维度错位都会导致求解器报不可行,而报错信息又往往不直接指向问题根源。

我后来养成一个习惯:写完约束先打印每个 sdpvar 的 dimension,再用一个极小的 3 节点 3 时段算例跑通,最后才上 33 节点全规模。多时段模型出了问题,90% 出在时间索引和 MPS 移动约束上,这个判断在之后好几个项目里都成立。

如果你刚跑通预配置的静态模型,我建议先别急着上 MPC,先把单时段动态调度模型跑稳、确认目标值和论文对得上,再往多时段滚动扩展。方法再先进,还是要从能复现的第一行代码开始。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询