ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

MPS动态调度的建模与Matlab实现:配电网韧性提升核心

MPS动态调度的建模与Matlab实现:配电网韧性提升核心 1. 项目概述MPS动态调度到底在调度什么1.1 配电网韧性与移动电源的定位台风过后配电网抢修现场最先到的往往不是抢修车而是移动储能车。这句话不是夸张——我刚完整复现完一篇SCI一区的论文基于配电网韧性提升的应急移动电源预配置与动态调度里面最核心的思路就是把救急这件时间敏感的事交给可移动电源来干。MPSMobile Power Source移动电源正是这套体系里最灵活的一张牌。在展开之前先用两句话把配电网韧性resilience和可靠性reliability的区别讲明白。可靠性回答的是平时停不停电韧性回答的是极端事件下停多久、能不能快速把关键负荷恢复。台风、冰灾、暴雨这类极端事件面前配电网出现大面积故障几乎不可避免但医院、通信基站、供水泵站这些关键负荷等不起几个小时的故障排查。于是在抢修完成之前的窗口期MPS就要顶上。MPS按类型大致分两类移动储能车电池型能充能放和应急发电车柴油型只能单向出力。两者的共同特点是数量有限、容量有限、转场需要时间、同一时刻一台MPS只能待在一个节点。这些约束叠加上配电网的潮流、拓扑和负荷时序特性就构成了一个典型的多时段混合整数规划问题。我复现的框架分两个阶段上一阶段是MPS预配置在灾害来临前依据场景预测决定每台MPS放在哪个节点待命本阶段是MPS动态调度在故障信息逐步清晰后依据实际网络拓扑和负荷状态实时决定每一台MPS的供电对象、出力和转移时机。前一篇我已经把预配置的场景生成和选址决策讲完了这篇专攻动态调度从建模到Matlab实现完整走一遍。1.2 预配置与动态调度为什么必须分两步我第一次看到这个框架的时候心里也在犯嘀咕既然最终目标是确定MPS的最优位置为什么不干脆等灾害发生后一次性优化非要多做一道预配置后来真正动手实现才明白这里有一个绕不开的现实因素——MPS的响应时间。以一台移动储能车为例从接到调度指令到车辆启动、开赴目标节点、现场接线、并网送电整个过程少说要三四十分钟路远的话得按小时计。如果灾害发生后才开始优化位置等MPS到位关键负荷已经停电很久了。所以预配置的价值在于提前待命根据气象预警和故障概率预测把MPS提前部署到风险高的区域边缘把首轮响应时间压缩到最短。但预配置毕竟是基于预测的实际灾情和预测往往有出入。台风路径偏一点、某条线路实际损坏比预期严重都会让预配置的位置不再最优。这时候动态调度就要接手根据最新的故障信息和负荷状态重新判断每台MPS是原地供电、转场到别处、还是调整出力大小。动态调度的核心是信息更新后的滚动修正它和预配置的关系可以类比成先扎营、再机动预配置保证了反应速度的下限动态调度把决策质量的上限拉高。需要提醒的是两个阶段并不是各自独立求解完就完事。动态调度的初始条件就是预配置给出的MPS位置预配置的收益评估也要靠动态调度来模拟验证。我复现时是把两段代码放在同一套数据体系里的只在入口处分别用预置位置矩阵和实际故障场景矩阵来切换。如果你只关心动态调度建议至少把预配置的输出接口MPS初始位置、初始SOC用固定参数文件传给调度模块否则模型缺一个起点后续所有优化结果都是无根之木。1.3 这篇内容适合谁看这篇博客的定位是复现过程中的完整笔记。内容适合三类人第一类是正在做配电网韧性、应急电源优化调度相关课题的研究生尤其是想把论文里的公式变成能跑的Matlab代码的第二类是准备用移动储能、应急发电车做实际调度策略的工程技术人员可以参考这里的目标函数设置和约束建模方式第三类是看到MPS动态调度这个概念但对优化建模不熟的人我会尽量把每个公式背后的物理含义讲清楚不让你对着符号猜。复现环境直接交代在开头Matlab R2022b旧版本也兼容关键看工具箱 YALMIP Gurobi 10.0。前两个是建模和求解的黄金组合。Gurobi是收费的但学术许可免费学生申请很方便如果暂时没有Gurobi也可以把求解器换成CPLEX或Matlab自带的intlinprog后面我会专门说不同求解器的坑在哪里。2. MPS动态调度核心建模目标函数与约束的取舍2.1 目标函数权重系数不是随便拍的动态调度的目标函数绝大多数论文都写成同一个形式在调度周期内最小化加权失负荷量或者等价地最大化加权恢复供电量。以最小化失负荷为例min Σ(t1..T) Σ(i1..N) w_i × P_shed(i,t) × Δt其中P_shed(i,t)是节点i在时段t被切除的负荷功率w_i是节点i的负荷权重Δt是调度步长。看起来简单但这个w_i是整个模型里最需要斟酌的参数。论文里常见做法是给负荷分三级一级关键负荷医院、应急指挥中心权重100二级重要负荷通信基站、供水泵站权重10三级一般负荷居民、商业权重1。这个10倍、100倍的差距不是拍脑袋拍的它直接决定了优化器在MPS容量不够时先保谁、后保谁。权重差距太小模型可能为了多保几个一般负荷而牺牲关键负荷权重差距太大会导致一般负荷几乎永远得不到恢复结果上又显得不真实。另一个容易忽视的维度是恢复时间。同样是关键负荷停电1小时和停电8小时的社会影响天差地别。有些论文会在目标里加上对关键负荷最大允许停电时间的惩罚项比如给每个关键负荷设一个最大停电时长T_max超过后施加额外惩罚。我建议在复现时把这个加上否则模型可能出现某个医院在调度的最后时段才被恢复的荒谬结果。形式也简单引入二进制变量表示关键负荷是否在T_max内恢复再加一个足够大的惩罚系数M乘以未按时恢复的指示变量。我实测下来这个修正对调度路径的影响非常大尤其是MPS数量少、转场距离长的场景。2.2 时序约束MPS能量和位置的状态转移动态调度和静态选址最大的区别就在于时序。MPS不是一插上就能永远供电的它的能量会消耗它会转移转移期间对电网来说它是离线状态。所以建模的核心是两台状态机能量状态SOC荷电状态和位置状态。先看能量状态。对移动储能车最简单的SOC递推式是SOC_m(t1) SOC_m(t) - P_out(m,t) × Δt / Cap_m其中P_out(m,t)是第m台MPS在时段t的输出功率Cap_m是它的电池容量。这个式子表示每放一个小时的电SOC就按放电功率在容量中的占比下降。要注意单位一致性——功率用MW、容量用MWh、时间用小时三者必须配套否则差一个数量级求解器直接给你整出负SOC。如果你处理的是应急发电车柴油型没有SOC概念但有一种等效的剩余燃料/剩余发电量约束形式完全一样剩余可发电量 初始值 - 累计放电量。所以代码层面可以统一处理只不过储能车还多一组充电约束可以用电网低谷电回充发电车没有。再看位置状态。每台MPS在任意时刻必须且只能在一个节点用二进制变量z(m,i,t)表示第m台MPS在时段t是否连接到节点i那么必须有Σ_i z(m,i,t) 1, 对所有m、tMPS的输出功率必须和位置耦合没有连接到节点i的那台MPS不能往i送电。这是典型的big-M约束P_out(m,i,t) ≤ P_rated(m) × z(m,i,t)这里的P_out(m,i,t)表示第m台MPS向节点i注入的功率。这样就把位置和出力两个变量绑在一起。转场约束是动态调度里最容易被忽略但最影响实际性的部分。论文里通常用一个转场时间矩阵D(i,j)表示MPS从节点i到节点j所需的时间不是直线距离应该是路网距离除以平均车速。如果MPS从i转到j在D(i,j)个时段内它不能向任何节点供电。逻辑上等价于如果z(m,i,t)1且z(m,j,t)1那么t必须大于等于t D(i,j)。用二进制变量y(m,i,j,t)表示第m台MPS在时段t从i转场到j再配合一个转场期间不能出力的约束。这个约束写起来绕我建议用中间变量表达不要试图在一个约束里写全那样YALMIP建模容易出错后面排查也难。具体写法我放在第三节的代码部分。2.3 网络约束DistFlow能简化到什么程度动态调度里MPS接的是配电网不是孤岛所以功率注入后要通过网络的潮流方程来约束。配电网三相不平衡、R/X比值大用传统的牛顿法潮流算太慢优化模型里几乎都用DistFlow支路潮流方程。标准的DistFlow是非线性的但做动态调度时绝大多数论文都做线性化处理只保留有功部分忽略网损和电压降落中的非线性项。线性化后的节点功率平衡可以写成对每个节点i注入功率等于流出到下游支路的功率之和再加上本节点的负荷需求减去本地电源出力。写成优化约束就是一组线性等式Σ_(k∈δ(i)) P_flow(k,t) P_inj(i,t), 对所有i、t其中δ(i)表示以i为父节点的支路集合P_inj(i,t) P_sub(i,t) P_mps(i,t) - P_load(i,t) P_shed(i,t)。这里的P_sub(i,t)是变电站向节点i的供电P_mps是MPS注入P_load是原始负荷P_shed是切负荷量。也就是说切负荷本质上是允许部分负荷不被满足的松弛变量。如果没有这个松弛变量模型在极端故障下必然不可行——因为总负荷可能超过变电站MPS的总供电能力。加上松弛优化器才能优先切掉权重最小的负荷这是整个模型的泄压阀。电压约束在动态调度里通常简化为节点电压幅值范围约束比如0.95到1.05标幺值。线性化的电压降落公式是V_j(t) V_i(t) - (r_ij × P_flow_ij(t)) / V_0如果忽略支路网损这个公式是线性的可以放进MILP。不过我要提醒如果你的测试系统是IEEE 33节点这类辐射网这个线性化精度完全够用但如果是网孔结构或者含环网线性化的误差会变大得考虑用SOCP松弛。对复现而言先用线性化跑通整个流程再横向对比一两个算例的精度差异比一开始就上高级方法更稳妥。3. Matlab工程实现从YALMIP建模到求解器调用3.1 代码架构与数据组织动态调度模块的Matlab实现最怕的就是把约束全堆在一个脚本里跑完就散。我复现时采用了参数结构体 约束生成函数 求解主程序三件套结构整体分四个文件case33b.m定义IEEE 33节点系统数据包括节点坐标、支路阻抗、基准负荷、负荷等级mps_params.m定义MPS参数容量、额定功率、初始位置、初始SOC、转场速度build_DT_constraints.m根据输入场景和参数生成YALMIP约束集合run_DT_schedule.m主程序组装模型、调用求解器、保存结果数据组织上的关键是把场景和系统分开。故障场景只体现为支路状态向量1表示正常运行0表示因故障断开同一套约束生成代码可以无缝适配不同场景这对后面做蒙特卡洛验证非常关键。MPS参数我用结构体数组存储每个元素对应一台MPS字段包括cap容量MWh、p_rated额定功率MW、init_loc初始节点、init_soc初始SOC、travel_time转场时间矩阵索引。3.2 约束构建的矩阵化技巧动态调度最核心的就是SOC递推和位置约束。SOC递推直接用向量化写法不要写循环% 参数 nMPS length(mps); T 24; dt 1; % 决策变量P_out(m,t) 第m台MPS在时段t的出力SOC(m,t) 荷电状态 P_out sdpvar(nMPS, T, full); SOC sdpvar(nMPS, T, full); z binvar(nBus, nMPS, T, full); % z(i,m,t) Constraints []; % 每台MPS每个时段只能在一个位置 Constraints [Constraints, sum(z, 1) 1]; % 维度1 x nMPS x T % SOC递推 for t 1:T-1 Constraints [Constraints, ... SOC(:, t1) SOC(:, t) - P_out(:, t) * dt ./ cap_vec]; end % 出力上限与位置耦合 for m 1:nMPS for t 1:T Constraints [Constraints, P_out(m, t) p_rated(m) * sum(z(:, m, t))]; end end注意sum(z, 1) 1这一句YALMIP对多维二进制变量的sum运算支持得很好但输出维度容易搞混我建议在写完后用size()逐个检查约束的维度是不是nMPS × T这个习惯能省掉很多排查时间。z这个变量是整个模型里最耗内存的。33节点、3台MPS、24个时段就是33×3×242376个二进制变量Gurobi处理这个规模毫无压力。但如果测试系统换成123节点、5台MPS二进制变量直接破万求解时间会明显拉长。后面我会专门讲怎么优化这个维度。节点功率平衡约束用关联矩阵写最干净。先构建一个nBus × nBranch的节点-支路关联矩阵A_inc第i行第k列为1表示支路k的潮流流入节点i-1表示流出0表示无关。那么对每个时段t% P_flow(k,t)支路k在时段t的有功潮流 % P_inj(i,t)节点i在时段t的净注入 Constraints [Constraints, A_inc * P_flow P_inj];其中P_inj是变电站供电、MPS出力、负荷和切负荷的组合P_inj P_sub MPS_supply - P_load P_shed;MPS_supply要把P_out(m,t)按位置z映射到节点上这个映射我用一个三维张量做矩阵乘法实现循环写出来也可以但向量化之后求解器的预求解时间会短很多。3.3 求解器选择与参数调优建模用YALMIP求解我强烈建议Gurobi。原因很简单动态调度本质是MILP而Gurobi的MILP求解器在分支定界和割平面这块比开源求解器强太多。我拿同一个33节点算例对比过Gurobi在200秒内达到1%最优间隙而intlinprog跑满600秒还是6%左右的间隙。如果你没有Gurobi用CPLEX效果接近用intlinprog则要做好等待的心理准备。调用Gurobi的关键参数就这么几个ops sdpsettings(solver, gurobi, verbose, 2, ... gurobi.MIPGap, 0.01, ... gurobi.TimeLimit, 600, ... gurobi.Threads, 8); result optimize(Constraints, Objective, ops);MIPGap设成0.01表示允许1%最优间隙不要设0。动态调度的目标函数数值本身就有模型误差追求0间隙只会白白消耗计算时间。TimeLimit设成600秒对论文复现完全够用。Threads根据你电脑核心数设置8线程跑出来的速度比单线程快很多这是我实测下来的经验。如果求解器报错或者结果异常先看result.info字段YALMIP会返回Infeasible problem、Numerical problems、Solved等状态。很多新手直接忽略这个返回值结果拿一个烂结果当最优解用这个习惯非常危险。我每次跑完的第一件事不是看结果曲线而是看result.info是不是Successfully solved。3.4 滚动时域调度的实现前面讲的是单次优化整个调度周期。但实际场景中故障信息和负荷恢复是逐步更新的因此论文里一般会做滚动时域也是动态调度真正的动态所在。做法是把调度周期切成多个窗口每个窗口只优化未来H个时段执行当前窗口的第一个时段决策然后窗口向前滑动。for k 0:step:T-H % 提取从 k1 到 kH 的窗口数据 window_load load_data(:, k1 : kH); window_fault fault_info(:, k1 : kH); % 构建并求解窗口子问题 [Objective, Constraints] build_window_model(window_load, window_fault, x_init); result optimize(Constraints, Objective, ops); % 只取出第一个时段决策执行 x_exec(:, k1) value(z(:, :, 1)); % 更新状态 x_init update_state(x_exec(:, k1), mps); end滚动时域有两个细节值得注意。一是窗口长度H的选择H太短MPS只看眼前可能做出刚转场过去又立刻转场走的短视决策H太长又回到单次优化的高耗时。我试下来对于24小时周期、1小时步长的场景H6到8是比较合理的。二是相邻窗口之间的状态传递SOC和位置必须从上一步的实际执行结果更新而不是用窗口初始解的预测值这个不一致会导致模型说谎——规划得很好执行时对不上。这个坑我在复现时踩过状态不同步时调度结果会显得很不稳定。4. 常见问题与调试经验实录4.1 模型全是不可行解别急着删约束复现动态调度遇到不可行解基本是必然的关键是别慌。我第一次跑出来Infeasible problem的时候第一反应是删约束结果删到最后模型连功率平衡都没了依然不可行——这说明问题根本不在约束数量而在约束之间的逻辑矛盾。不可行问题最常出现在几个地方第一是SOC初始值和放电需求不匹配。比如MPS初始SOC只有0.5而模型要求它在前几个时段大功率放电导致SOC降为负值。检查方法是在YALMIP里用check函数逐个约束看残差% 先求解再用check检查每条约束的残差 optimize(Constraints, Objective, ops); residuals check(Constraints); % 残差为负且绝对值很大的约束就是冲突源 neg_idx find(residuals -1e-6);第二个常见问题是big-M取值不当。位置耦合约束里P_out p_rated * sum(z)如果p_rated取的是兆瓦级数值而别的约束用千瓦两者相差1000倍M的取值就会在太小导致约束太紧和太大导致数值病态之间摇摆。统一单位是第一要务我建议所有功率用标幺值基准功率100MVA表示SOC用0到1的百分数这样M取值接近1数值条件最好。第三个问题是转场时序约束里的时间冲突。比如你设定MPS转场需要3个时段但在第2时段又强迫它在新位置输出功率这必然冲突。这类逻辑冲突靠check函数只能定位到单条约束很难直接看出跨约束的矛盾。我的调试习惯是先把转场约束整体注释掉跑通再逐步加回每次只加一类约束确认依然可行再加下一类。虽然笨但快。4.2 求解时间爆炸的四个对策33节点3台MPS24时段的动态调度Gurobi一般几分钟内能出结果。但如果你把测试系统换大或者把MPS数量增加求解时间可能会指数级爆炸。这里分享四个实测有效的对策第一个对策是加有效不等式valid inequalities。最常见的有效不等式是从能量角度切断不可能的解——比如某台MPS的剩余总能量根本不足以支撑某个关键负荷的全部供电需求那就在优化前把这台MPS和这个负荷之间的连接变量全部固定为0。这类逻辑裁剪在预处理阶段就能砍掉大量无用分支Gurobi的presolve也会做一部分但你自己加的更精准。第二个对策是收紧MIP Gap。从0.01放到0.02或0.03求解时间可能缩短一半以上。对动态调度来说1%和3%的间隙在结果图上几乎看不出差异但求解时间差异巨大。第三个对策是缩短调度时域。如果发现24个时段全优化太慢可以先用粗粒度比如2小时一个时段跑一遍看MPS大致调度策略再对关键时段加密。我自己复现时就是先用12个时段的粗模型调通逻辑再用24时段跑最终结果。第四个对策是加对称性约束。多台MPS如果参数完全相同模型会认为把A和B互换位置也是不同的解导致分支定界过程中大量重复搜索。加一条约束比如MPS编号按初始位置升序排列就能消除这种对称性。这个技巧在MPS数量超过3台时效果显著。4.3 结果验证怎么确认算出来的不是幻觉求解器说最优解已找到不代表结果真的正确我见过太多最优解在物理解释上荒谬至极的案例。验证分三层每一层都不能省。第一层是可行性核查。优化结束后把决策变量代回约束人工检查几个关键点SOC是否始终在0和1之间、每台MPS是否任一时刻只在一个节点、负荷切除量是否为非负。用Matlab的value函数取出变量值后逐条核对这一步虽然机械但能拦住90%的错误。第二层是能量守恒检查。把整个调度周期内的总负荷需求、变电站供电、MPS输出和切负荷量按时间求和理论上必须满足总供电 总负荷需求 - 总切负荷 总网损忽略网损时就是等号如果两边差得明显说明功率平衡约束建模有问题。我在复现时曾因为把P_shed的符号搞反导致这个恒等式差了一大截排查了一下午才找到。第三层是场景回测。对同一个故障场景用不同的MPS初始位置跑动态调度观察恢复曲线。合理的模型应该是MPS初始位置越靠近关键负荷恢复效果越好切负荷量越小。如果出现初始位置越差结果反而越好的情况那你的动态调度模型有可能在利用某个建模bug作弊比如切负荷权重设置错误或者允许了负的切负荷量。回测是验证模型逻辑是否正确的金标准比看任何指标都可靠。4.4 参数灵敏度论文里不会写的细节复现完能跑出和论文一致的结果只算是第一步。我在实际调试中发现动态调度的结果对几个次要参数非常敏感这些细节论文往往一笔带过但实际影响很大。第一个是转场时间矩阵。很多论文只给一句平均速度40km/h但实际车辆在灾后路况下根本跑不到这个速度。我试过把平均车速从40改到25结果MPS的调度路径完全变了——本来可以从A点转到B点结果因为转场时间太长模型选择放弃转移原地多供电几个小时。如果你做敏感性分析这个参数值得优先测。第二个是负荷权重的相对比例。我前面强调过权重差距是100:10:1但不同论文用的比例不完全一样有些是50:5:1有些是100:20:1。权重不同最优调度策略甚至可能出现质的区别50:5:1时优化器可能选择保重要负荷而牺牲一部分关键负荷因为重要负荷数量多、累计权重更高。这不是对错问题而是决策偏好问题。复现时一定要把论文用的权重比例和你的对应上否则结果对不上很正常。第三个是初始SOC。预配置阶段结束后MPS并不是满电状态的——如果预配置阶段要求MPS提前在节点待命并参与了初期供电实际SOC可能只有70%或80%。动态调度模型如果假设初始SOC是100%会高估MPS的供电能力导致前几个时段出力计划过于激进后面SOC跟不上。我复现时踩过这个坑结果曲线前半段看起来完美后半段MPS集体没电了后来才发现是初始SOC传错。最后一个细节是关于最后时段的尾效应。单次优化下优化器会在最后一个时段把MPS能量全部放光因为目标函数里没有对调度结束后SOC剩余的惩罚。这会导致最后几个时段的调度策略过度激进。解决方法是把目标函数改成末时段SOC加一个很小的正奖励系数Objective 原目标 - λ × Σ_m SOC(m, T)λ取一个很小的值比如0.001乘以关键负荷权重既不影响前中期决策又能抑制尾效应。这个技巧论文里几乎没有写但对工程应用非常重要——调度结束后MPS还要留着应对次生灾害总不能把电全放光。滚动时域实现时尾效应会自然减轻因为每个窗口都只执行第一步、不断重新优化但单次优化的基准算例仍然建议加上这个修正否则你拿它做对比基准时会发现你的算法看起来比论文里的差其实只是基准模型的尾效应在干扰。
返回列表