资讯动态

配电网韧性提升:应急移动电源动态调度与Matlab滚动时域实现

发布时间:2026/10/17 10:28:30 来源:尧图企业网站定制
做配电网韧性提升方向的复现最常被问到的就是应急移动电源MPS到底该怎么动态调度这个问题看起来简单真上手做Matlab实现时你会发现它同时牵扯潮流、时序、移动路径和抢修进度四条线哪一条没处理好结果都会被带偏。先说个实际场景。台风过境后的凌晨调度大屏上还有七八条线路处于故障状态库房里的应急移动电源车早就到位了但停在哪、什么时候出发、先去哪个负荷点都不是一眼能看出来的。如果只做预配置那就相当于把移动电源当成固定储能放在一个猜好的位置等着——这在灾后修复时间不确定的现实里几乎不可用。这篇博文专门聊动态调度部分也就是MPS在灾中如何根据实时故障状态、抢修进度和自身电量一步步滚动作出下一站去哪、出多少功率的决策。文章里所有代码框架都以Matlab实现为主线参考了某篇SCI一区论文中关于两阶段韧性能源调度的思路但实现细节是我自己重构过的算例用的是公开的33节点配电网测试系统。读完之后你应该能直接照着搭出一套可运行的MPS动态调度仿真。1. 从放在哪到走去哪动态调度到底在解什么问题1.1 极端事件下的三个时间窗口配电网韧性研究和传统可靠性研究最大的区别在于它面对的是小概率、高影响的极端事件。台风、冰灾、地震这类事件会造成多条线路同时段故障而且故障是陆续被发现、陆续被修复的。整个过程大致分成三段灾前预配置阶段、灾中持续响应阶段、灾后逐步恢复阶段。灾前阶段能做的事情有限主要是根据气象预报和灾害模型把移动电源、抢修队伍、应急物资预先布置到风险最高的区域。这也就是上篇讲的预配置问题从哪里调移动电源、放到哪个变电站、放几台。到了灾中情况开始变得不可控实际受损位置和预测往往对不上某条线路可能提前修复也可能比预计多停一天。这时候如果移动电源还傻乎乎地待在预配置的位置很可能错过真正需要供电的负荷点。所以动态调度的价值是在灾害展开之后根据已经确认的故障信息 短期修复预测 自身剩余电量不断重新规划移动电源接下来的行动轨迹。它把静态放置变成了动态路由这也是配电网韧性和传统配电网调度之间最本质的差异。1.2 移动电源与固定储能的核心区别时空强耦合固定储能接入电网后位置不变优化的只是充放电时序。移动电源多了一个自由度——位置决策但它不是白来的移动需要时间在途中的时段无法提供供电转移需要消耗自身电量赶到新地点之后SOC可能已经下降移动到某个节点之后该节点能否形成有效供电孤岛取决于周边配电网的拓扑连通性MPS被一个节点锁住的时间越长它支援其他节点的机会成本就越高。我复现过程中最直观的感受是移动电源调度本质上是把空间路由问题和能量管理问题叠在一起再加上配电网潮流的约束。如果只用经验规则比如哪里负荷重就往哪走大概率会在某个时段把MPS卡在一个既没负荷、又离故障点很远的中间位置。1.3 为什么要动态调度而不是一次性算出全局方案理论上如果灾后故障集合、修复时间全部已知完全可以用一个全时段的大模型一次性算出MPS全生命周期的最优路径。但现实是故障信息是逐步确认的修复时间也经常变化。全时段模型一旦信息更新就必须重新求解而窗口一大变量数和整数变量规模会急剧膨胀常规商业求解器在合理时间内根本解不动。滚动时域rolling horizon是对这个问题最自然的妥协每一步只求解未来若干个时段的小规模子问题执行当前时段的决策然后滚动推进。论文里面它经常被包装成MPC模型预测控制或者滚动优化底层逻辑都一样。我的实现也采用这个框架代码结构非常清晰更新状态、构建窗口模型、求解、执行、再更新。2. 动态调度的数学内核时序耦合与移动约束建模2.1 目标函数加权失负荷量是最直观的韧性度量我在这套实现里采用的目标函数是最小化加权失负荷量。写成文字化的公式就是min ∑(t1..T) ∑(i1..N) ω_i · P_i^L · (1 - u_i,t) · Δt其中ω_i是节点i的负荷权重P_i^L是该节点的有功负荷需求u_i,t表示该节点在t时段是否恢复供电Δt是时段长度我取1小时。权重怎么设非常关键。一级负荷比如医院、通信基站、供水系统的权重可以给到50以上普通居民负荷给5一般工商业给1。权重设平了MPS会变得就近原则哪里近就先去哪失去了负荷重要性维度权重差距拉太大模型又会过度偏向少数几个节点导致部分区域完全不管。算例中我会固定三组对比场景无MPS、静态预置、动态调度。目标函数值越小说明加权失负荷量越小韧性表现越好。2.2 关键约束组一个都不能少光有目标函数还不够动态调度的核心难度在约束。我复现时整理的约束组大致分五类潮流约束采用DistFlow线性化。节点功率平衡、支路功率、电压递推关系都用线性等式约束描述电压幅值限制在0.95到1.05 pu之间。MPS位置唯一性约束每一个时段MPS只能待在一个节点所以∑ x_i,t 1x_i,t是二进制变量。移动时间约束MPS从节点i转移到节点j需要一定的旅行时间 τ_ij这段旅行时间内它既不能在i出力也不能在j出力。实际建模时我会预先算好所有节点对之间的旅行时间然后用一个禁止快速跨越的时序约束表达。SOC耦合约束SOC(t1) SOC(t) − (p_t^dis / η_dis − p_t^ch · η_ch) · Δt外加SOC上下限一般0.1~0.95充放电功率不能超过额定值且同一时段不能同时充放电。供电路径与恢复状态约束某节点在t时段若由主网供电前提是上游配电路径已修复若由孤岛供电前提是MPS当前接入该孤岛内的一个节点且孤岛内部连通。2.3 一个最小示例看懂约束耦合拿一个三节点系统举例节点1连变电站节点2和节点3各带一个医院负荷。故障发生后线路1-2断开节点2、3全部失电。MPS在节点3容量能支撑两个负荷半小时但转移去节点2需要1小时。这时候模型会怎么决策如果只看当前时段MPS留在节点3给医院3供电是最优的因为目标函数立刻下降。但如果再往未来一个时段看抢修队预计会在2小时之后修复线路1-2那么节点3很快可以恢复主网供电而节点2还需要再等更久。滚动窗口看到这一点后就会倾向于让MPS尽早转移到节点2哪怕当前时段节点3短暂失电也在所不惜。这就是动态调度牺牲局部、换取全局的典型场景。如果只用单时段贪婪策略这种跨时段的决策根本做不出来。3. Matlab代码实现滚动时域求解框架的完整链路3.1 数据准备33节点测试系统与故障场景生成我用的是经典33节点配电网测试系统的公开数据支路阻抗、节点负荷都是现成的。导入时为了避免脚本到处是魔法数我封装了一个函数% load_case33.m function CASE load_case33() CASE.N 33; % 节点数 CASE.NB 32; % 支路数 CASE.refbus 1; % 变电站根节点 BASE.MVA 10; BASE.KV 12.66; BASE.ZBASE (BASE.KV^2) / BASE.MVA; % 阻抗标幺化基准 % 支路数据首端、末端、电阻(ohm)、电抗(ohm) br [... 1 2 0.0922 0.0470; 2 3 0.4930 0.2511; 3 4 0.3660 0.1864; ... 31 32 0.3720 0.5740; 32 33 0.2090 0.3280]; CASE.br br; CASE.r_pu br(:,3) / BASE.ZBASE; CASE.x_pu br(:,4) / BASE.ZBASE; % 节点有功、无功负荷kW/kVar转标幺 Pload [... 0.1; 0.09; ... ]; % 实际按测试系统数据填 CASE.Pload Pload / BASE.MVA; CASE.Qload Qload / BASE.MVA; CASE.w ones(CASE.N, 1); % 权重后续按负荷等级覆盖 end故障场景生成我建议做成独立的结构体方便做多场景对比。包括故障线路编号和发生时段、每条故障线路的修复时间分布、负荷波动系数。我的做法是写一个随机生成器种子固定保证每次复现结果一致% generate_scenario.m function SC generate_scenario(rng_seed) rng(rng_seed); SC.fault_branch [4; 9; 18]; % 三条线路同时故障 SC.fault_time [1; 1; 1]; % 从第1时段开始 SC.repair_time [5; 8; 11]; % 分别在第5、8、11时段修复 SC.load_factor 0.85 0.3 * rand(CASE.N, 1); % 节点负荷波动 end3.2 YALMIP模型构建变量定义与约束组装要点Matlab端我采用的是 YALMIP 建模求解器用商业MILP求解器。变量定义如下% build_rolling_model.m function [Constraints, Objective, vars] build_rolling_model(CASE, MPS, SC, t_cur, H) N CASE.N; T H; u binvar(N, T, full); % 节点供电恢复状态 x_mps binvar(N, T, full); % MPS所在节点位置 p_dch sdpvar(1, T, full); % 放电功率 p_ch sdpvar(1, T, full); % 充电功率 soc sdpvar(1, T1, full); % SOC轨迹 v sdpvar(1, N, T); % 节点电压可选向量化写法 Constraints []; % 初始SOC由外部传入 Constraints [Constraints, soc(1) MPS.soc_init]; for t 1:T Constraints [Constraints, 0 p_dch(t) MPS.Pmax]; Constraints [Constraints, 0 p_ch(t) MPS.Pmax]; Constraints [Constraints, soc_min soc(t) soc_max]; end % SOC递推 for t 1:T Constraints [Constraints, ... soc(t1) soc(t) - (p_dch(t)/MPS.eta_dis - p_ch(t)*MPS.eta_ch) * CASE.dt]; end % 位置唯一性 Constraints [Constraints, sum(x_mps, 1) 1]; % 移动时间约束示意版 % travel(i,j) 是MPS从i到j需要的时段数 for t 1:T for i 1:N for j 1:N if travel(i,j) 0 t travel(i,j) Constraints [Constraints, ... x_mps(j,t) 1 - x_mps(i,t-travel(i,j))]; end end end end % 潮流约束、电压约束、恢复状态约束此处省略 % 完整代码里用 DistFlow 线性化逐时段的等式约束。 ... end这里有一个非常关键的点移动时间约束的时序对齐。很多第一次复现的人会直接写成当前位置变量在连续两个时段不能跨越但忽略了一个事实——如果MPS从节点5移动到节点9需要2个时段那么它在中途的第1个时段实际上出现在系统的任何节点之外可如果强制∑ x 1这个时段就不可能合法。我简化处理的办法是引入一个虚拟节点表示在途MPS在途中时段就落在虚拟节点上这样位置唯一性约束始终满足同时虚拟节点不允许出力。3.3 主循环求解流程滚动时域的主循环是所有流程的中枢。我把它拆得很清楚% run_mps_dynamic_dispatch.m CASE load_case33(); SC generate_scenario(20240831); MPS init_mps(); % capacity1.0MWh, Pmax0.5MW, soc01.0 H 4; % 滚动预测窗长度 T 24; % 仿真总时段 result.model_traj struct(); result.soc_hist zeros(1, T1); result.served_hist zeros(1, T); for t 1:T % 1. 根据当前实际发生的事件更新场景信息 SC_now update_scenario(SC, t); % 2. 生成未来H个时段的预测场景可以带误差 SC_pred forecast_scenario(SC_now, t, H); % 3. 构建并求解当前窗口的MILP [Constraints, Objective, vars] build_rolling_model(... CASE, MPS, SC_pred, t, H); ops sdpsettings(solver, gurobi, verbose, 0, mipgap, 1e-3); diagnostics optimize(Constraints, Objective, ops); if diagnostics.problem ~ 0 warning(时段%d求解失败: %s, t, diagnostics.info); break; end % 4. 只执行当前时段决策 x_now value(vars.x_mps(:,1)); p_now value(vars.p_dch(1) - vars.p_ch(1)); % 5. 更新MPS真实状态 MPS execute_mps_decision(MPS, x_now, p_now, CASE.dt); result.soc_hist(t1) MPS.soc; % 6. 记录结果 result.model_traj(t).x x_now; result.model_traj(t).u value(vars.u(:,1)); result.model_traj(t).p p_now; end这套流程你跑通之后会发现真正让代码变复杂的是第1步和第2步的状态更新而不是优化模型本身。因为每个时段都要确认哪些线路在这个时段突然恢复了、哪些负荷点需要重新从MPS转向主网、MPS当前到底在哪个节点这些状态如果不同步滚动优化就变成了睁着眼睛跳崖。4. 动态调度与抢修进度的联动逻辑4.1 抢修进度如何改变MPS下一步去向灾后恢复过程中MPS的动态调度和抢修队进度是强耦合的。线路修复后原本靠MPS支撑的孤岛负荷会重新切回主网供电路径这时候MPS就多了一个可选行动要么原地降低出力待命要么立刻转移到另一个仍然失电的区域。我在代码里用一个接口函数解决这个联动% update_scenario.m function SC_now update_scenario(SC, t) SC_now SC; % 根据修复时间表更新当前已恢复线路集合 SC_now.repaired_lines SC.repair_branch(SC.repair_time t); % 重新计算当前失电节点集合基于已修复线路做一次尾部可达判断 SC_now.outage_nodes compute_outage_nodes(SC_now); end这里最值得注意的细节是节点是否恢复供电不要只在模型里用二进制变量硬推。配电网是辐射状结构某个节点能否恢复取决于它到变电站的路径上所有故障线路是否都已经修复。我最初直接在YALMIP里用大量连通性变量建模模型规模爆炸求解器跑得非常吃力。后来改成在Matlab侧用可达集预计算把每个时段的可恢复节点集合算出来再以参数形式传入优化模型速度和稳定性都好了很多。4.2 线路修复与MPS转移时机谁该等谁抢修进度和MPS转移这两个动作在时间上经常存在一个岔路口如果MPS先转移到某孤岛但故障线路迟迟修不好MPS电量耗尽后就只能陪跑如果MPS在一个即将修复的区域死守等线路一修好它就立刻想去下一站转移时间又可能来不及赶上当晚的负荷高峰。这种问题在纯优化模型里靠目标函数自动权衡但工程上我会加一个额外约束MPS在某个节点接入后至少停留两个时段才能撤离。这个最小停留时长约束虽然会牺牲一点点目标函数值但能避免滚动优化因为预测误差导致MPS频繁移动实际运行中完全不可执行。实测下来加了这个约束之后SOC曲线更平稳MPS路径也更像一个有经验的人会做的选择去一个地方稳定供一段电然后才转移。如果完全没有这个约束MPS在极端情况下甚至会出现每过一个时段就换一个节点的抖动目标函数数字倒是好看工程上一看就不现实。5. 结果可视化与韧性指标定量评价5.1 三条曲线负荷恢复率、MPS位置、SOC复现论文代码时可视化不是最后画个图那么简单它其实是你校验模型行为是否合理的最直接手段。我每次跑完都强制自己看三张子图% plot_dispatch_result.m figure(Color,w,Position,[100 100 800 700]); subplot(3,1,1); stairs(0:T, [1, served_percent], LineWidth, 1.5); xlabel(时段); ylabel(负荷恢复比例); title(系统加权负荷恢复率时序); ylim([0 1]); grid on; subplot(3,1,2); stairs(0:T, [MPS.soc0, result.soc_hist(2:end)], LineWidth, 1.5); xlabel(时段); ylabel(SOC); title(MPS 荷电状态轨迹); ylim([0 1]); grid on; subplot(3,1,3); stem(0:T, [MPS.node0, node_hist], LineWidth, 1.2); xlabel(时段); ylabel(节点编号); title(MPS 位置轨迹); grid on;负荷恢复率曲线看的是宏观韧性表现SOC曲线看的是能量管理是否合理MPS位置轨迹看的是空间决策是不是有解释性。我见过不少复现版本结果数字很漂亮但画出来位置轨迹乱跳说明约束漏了或者滚动窗口设置有误。可视化在复现过程中的作用是作为合理性检验器而不是论文配图工具。5.2 韧性提升怎么量化曲线下面积对比SCI一区论文里常见的韧性指标之一是系统恢复过程中的负荷时间曲线下面积反映整个事件过程的总供电水平。我的实现中归一化处理方式是% compute_resilience_index.m function RI compute_resilience_index(served_percent) RI sum(served_percent) / length(served_percent); end也就是把曲线下面积除以总时段数得到一个0到1之间的韧性指数。对比时固定同一故障场景跑三组实验方案加权失负荷量韧性指数说明无MPS基准值较低纯粹靠抢修恢复静态预置MPS略有改善中等MPS全程固定在预配置节点动态调度MPS明显降低最高MPS跟随抢修进度不断转移从我的算例看动态调度相对静态预置的加权失负荷量降幅一般在10%到20%之间具体取决于故障点的空间分布和修复时间差。如果故障点非常集中两者差距会被压缩如果故障点分散且修复时间错开动态调度的优势会非常显著。6. 复现过程中踩过的四个坑与处理经验6.1 大M参数过大导致的决策错位很多经典教材喜欢用大M法把逻辑约束转成线性约束但大M取了1e6这种量级求解器数值上会非常难受。我遇到过一次很典型的怪象模型求解结果里MPS明明在某个时段报告已接入节点3但节点3的功率平衡等式却完全不包含MPS注入功率。排查到最后发现就是大M取值过大导致二进制变量和连续功率之间的逻辑约束数值病态求解器在默认数值容差下认为约束已经满足了。我的处理原则大M值永远按实际物理上界取且不超过该上界的1.2倍。比如放电功率上界0.5MW大M就取0.6电压偏差上界0.05pu大M就取0.06。这样既保留了逻辑约束的效力又不给求解器埋数值炸弹。6.2 初始SOC和预配置阶段脱节这是做上篇下篇两阶段复现时最容易犯的错误。如果上篇预配置阶段算完MPS到达某个节点时已经用掉了30%的电量下篇动态调度开始时就必须把它接过来而不是默认满电。我写代码时专门给init_mps()加了一个soc_init字段由预配置阶段的终态SOC传入。如果忽略这一步动态调度会把本不该存在的电量白白释放第一时段的最优决策完全失真。这个坑的隐蔽性在于单跑动态调度看不出问题只有把两阶段串起来做整体仿真时结果才对不上。6.3 滚动窗口太短导致MPS反复横跳我最早把滚动窗口H设成1纯粹是在线决策结果MPS路径像喝醉了一样在两个失电区域之间来回跑。原因很直观窗口太短时模型看不到未来区域2的修复时间更长只觉得现在区域1有负荷、去区域1有收益等下个时段又觉得区域2也有负荷、返回去也有收益。把H扩到4个小时后路径才稳定下来。这个经验也直接呼应了4.2节说到的最小停留时间约束——两件事配合起来动态调度结果才具备工程可行性。6.4 对比实验的求解精度不一致做有MPS、无MPS、静态预置三种方案的对比时如果每个场景都用默认的求解器收敛设置某些场景会停在不同的MIP gap上结果差异可能来自求解精度而不是调度策略本身。我把所有场景统一设置为mipgap1e-3同时加一个maxtime, 300的兜底限制任何一个场景超时都会在日志里明确标注而不是默默输出一个次优解。这样处理之后至少保证对比结果在数值上是一致的。学术复现尤其要注意这个细节否则审稿人或者同行拿你的代码重跑很可能得到完全不同的趋势。6.5 补充提醒孤岛电压校验别只信线性化结果最后再补一个不算坑但容易忽略的提醒。DistFlow线性化在小幅电压偏差下比较可靠但MPS在某个孤岛内满载支撑时节点电压可能逼近甚至低于0.95pu这时候线性化结果会偏乐观。我建议在动态调度仿真结束后对每个时段的断面对MPS所在孤岛做一次前推回代精确校验看是否出现低电压。如果出现就把该时段的MPS出力上限稍微调低或者把电压下界在优化模型里收紧一点例如设成0.96pu留出线性化误差的余量。整套代码跑下来我对滚动时域调度感触最深的一句话是优化模型给的是决策逻辑的上限而真正让系统落地的是状态更新、时序对齐和数值处理这些看不见的细节。你现在拿到的这个骨架换掉数据加载函数、改一下MPS参数就可以去接自己的算例如果再往上加入抢修队路径协同优化这套框架也完全撑得住。之后如果有时间我打算把预配置阶段和抢修队模型的实现也像这样整理一遍把两阶段韧性调度完整串起来。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑