☰
基于YALMIP的配电网韧性应急移动电源预配置Matlab实现
2026/10/8 15:37:36 网站建设 项目流程

最近在复现一篇配电网韧性方向的SCI一区论文,做的是应急移动电源(Mobile Power Source, MPS)的预配置和动态调度。这个课题很有意思,也很有工程价值:台风、冰灾这类极端灾害来临前,怎么提前把移动电源放到最合适的位置、配多大的容量,灾害发生后再怎么动态调度它们去恢复供电。整个研究分上下两部分,这篇先聊上篇——MPS预配置的建模和Matlab实现。我尽量把思路、公式、代码细节和踩过的坑都摊开来讲,希望能帮到正在做韧性电网、移动储能、应急电源优化配置方向的同学。

先说清楚这篇博文适合谁:如果你已经在做配电网优化、韧性评估,或者刚接触移动储能调度,手头有Matlab和基本的优化求解器(YALMIP、Gurobi或CPLEX),那这篇文章可以直接当复现笔记用;如果你是刚入门的小白,也不用慌,我会把数学模型里的每个约束都解释一遍,再把代码结构拆开讲,你跟着搭一遍基本能跑通。

1. 项目概述与核心问题

1.1 配电网韧性与应急移动电源

先交代一下背景。配电网韧性(Resilience)指电网对极端事件的预防、吸收、适应和快速恢复能力。传统可靠性分析更多考虑随机故障,比如线路老化、设备失效,但极端天气下的多重故障、大规模停电是另一套逻辑。台风可能同时吹断几十条馈线,变电站也可能受灾,这时候光靠静态的网架重构是不够的,必须有额外的移动资源进场。

应急移动电源就是这么个角色。它可以是一台移动柴油发电机、一辆储能车,也可以是一台可移动的分布式电源,能在灾前部署到关键节点,灾后给重要负荷供电。它的核心优势是“灵活”——配电网的网络拓扑是固定的,但移动电源可以提前放到预测的故障区域附近,等故障隔离后迅速接入,恢复供电。

这里有个关键点:预配置和动态调度是两个时间尺度的问题。预配置发生在灾前,决策变量是“每个候选节点放多少台MPS、容量多大”;动态调度发生在灾中及灾后,决策变量是“什么时候移动到哪个节点、接入哪个负荷、输出多少功率”。上篇只做预配置,表示的是“把资源提前布下去”这个动作;下篇再考虑动态调度,这里先不展开。

1.2 为什么需要“预先配置”而不是事后调配

可能有同学会问:既然MPS是移动的,灾害发生后根据实际故障情况再调配不行吗?为什么非得在灾前预配置?这个问题我在复现时也问过自己,细想之后发现逻辑很严密。

第一,交通网络在灾害中同样可能损毁。台风过后道路受阻、桥梁中断,移动电源未必能及时从仓库运到现场。如果灾前提前部署到易受灾区域附近,就能规避交通不确定性。第二,预配置可以结合气象预测。灾害路径和强度预测通常提前24到72小时发布,这段时间足够把移动电源从驻地移动到候选节点。第三,从优化角度来说,预配置决策是一个“随机规划”问题,需要在不确定故障情景下做资源分配,目标是最大化工后恢复效果。静态事后调度只能针对已发生的故障做优化,但资源数量、位置已经固定,可能错过最佳恢复机会。

所以论文里通常构建一个两阶段随机优化模型:第一阶段是预配置决策,第二阶段是故障情景确定后的负荷恢复优化。上篇实现的就是第一阶段。

1.3 复现目标与整体思路

我复现的目标很明确:给定一个配电网拓扑、负荷数据、候选MPS节点集合、灾害故障情景集合,求解“在哪些节点配置多少台MPS”这个0-1整数规划问题,目标是最小化极端事件下的失负荷期望值,同时兼顾经济成本。输出结果包括:各候选节点的MPS配置数量、预期失负荷量、关键负荷恢复率,以及敏感性分析表。

整体思路是:先建立物理模型,把配电网线性化(DistFlow模型);再把随机故障情景融入优化;然后用Matlab + YALMIP建模,调用商用求解器求精确解。如果问题规模大,可以改用启发式算法,但上篇先不追求大规模,重点是把数学模型和求解链路跑通。

2. MPS预配置数学模型拆解

2.1 目标函数:经济性还是可靠性优先?

预配置模型的核心目标不是单纯最小化投资成本,也不是单纯最大化恢复负荷,而是一个权衡。我这里参考了原论文的思路,目标函数包含两部分:预配置投资成本和灾后失负荷惩罚。

目标函数可以写成:

[ \min \left( \sum_{i \in \Omega_{MPS}} c_{inv} \cdot x_i + \sum_{s \in \Omega_S} \pi_s \cdot \sum_{t} \sum_{j \in \Omega_L} w_j \cdot (P_{j,t}^0 - P_{j,t}^s) \right) ]

其中:

  • ( x_i ) 是节点 ( i ) 处配置的MPS数量,非负整数;
  • ( c_{inv} ) 是单台MPS的日折算投资/租赁成本;
  • ( \pi_s ) 是故障情景 ( s ) 的发生概率;
  • ( P_{j,t}^0 ) 是负荷 ( j ) 在时段 ( t ) 的原始有功需求;
  • ( P_{j,t}^s ) 是情景 ( s ) 下该负荷实际获得的有功功率;
  • ( w_j ) 是负荷权重,反映负荷的重要程度。

注意,这里没有把MPS运行成本放进第一阶段目标,因为运行成本属于第二阶段的调度问题,如果在预配置阶段就考虑,会让模型变得笨重。从工程角度看,预配置阶段的核心矛盾是“投资多少资源”和“预期能减少多少失负荷”,所以目标函数里两类成本的量纲要统一。如果投资成本单位是万元/MWh,失负荷惩罚单位也必须折算成万元/kWh,我当时就是在这里被单位坑了,后面细说。

2.2 约束条件:网络、资源与负荷恢复

预配置模型不直接调度MPS出力,但需要保证“配置方案在任意故障情景下都有可行的调度空间”。所以约束条件要涵盖配电网运行约束、资源数量约束、负荷恢复约束三块。

第一类是配电网网络约束。这里采用线性化的DistFlow方程,对于每条支路 ( (i,j) ) 有:

[ P_{ij} = \sum_{k \in N(j)} P_{jk} + P_j^L ]

[ Q_{ij} = \sum_{k \in N(j)} Q_{jk} + Q_j^L ]

[ V_j = V_i - \frac{r_{ij} P_{ij} + x_{ij} Q_{ij}}{V_0} ]

其中 ( P_j^L, Q_j^L ) 是节点净注入,( V_0 ) 是基准电压。这个线性化忽略了网损和电压降的二次项,在配电网辐射状结构下精度是够用的,而且能大幅降低求解复杂度。实际中如果节点电压偏差超过5%,建议用二阶锥松弛版本,不过上篇用线性版本就够了。

第二类是MPS资源约束。每个候选节点能配置的MPS数量有上限:

[ 0 \le x_i \le \bar{x}i, \quad x_i \in \mathbb{Z}{\ge 0} ]

还有一个总资源约束,比如总共最多可调用的MPS台数或总容量:

[ \sum_{i \in \Omega_{MPS}} x_i \le \bar{N} ]

第三类是负荷恢复约束。在情景 ( s ) 下,如果节点 ( j ) 失电,可以由MPS供电,也可以由电网其他带电区域通过联络开关转供。但预配置阶段不详细建模时序,一般只要求:

[ 0 \le P_{j,t}^s \le P_{j,t}^0 ]

[ \sum_{j: j \in \Omega_{MPS}} P_{j,t}^s \le \sum_{i: i \in \Omega_{MPS}} x_i \cdot P_{MPS}^{rated} ]

这个约束的意思是:MPS总容量不能超过预配置资源的总和,负荷恢复量不能超过原始需求。有些模型还会加入“一个节点只有配置了MPS才能恢复”的逻辑约束,比如:

[ P_{j,t}^s \le M \cdot \sum_{i \in \Gamma(j)} x_i ]

其中 ( \Gamma(j) ) 表示能为节点 ( j ) 供电的MPS候选节点集合,( M ) 是一个足够大的数。这个约束非常关键,它能防止模型在不配置MPS的情况下“凭空”恢复负荷。

2.3 不确定性建模:场景生成与概率

灾害故障场景怎么来?论文通常用蒙特卡洛模拟生成。基本思路是:根据历史灾害数据或气象预测,给出每条线路的故障概率 ( p_e ),然后随机抽样得到一组故障线路集合,对应一个故障场景。重复生成 ( N_s ) 个场景,就构成场景集合。

场景生成这一步影响非常大,但很容易被忽略。如果场景数太少,预配置方案对真实故障的适应性差;场景数太多,求解时间爆炸。我在复现时用了100个场景,YALMIP建模后求解时间大约在2到5分钟,还能接受。如果场景数到500个,CPLEX可能半小时都算不完,这时就要考虑场景削减技术,比如用概率距离快速前推法(Fast Forward Selection)把场景从500个缩减到50个,损失很小,但求解效率能提升近10倍。

原论文里对不确定性还有更精细的处理,比如考虑负荷波动和MPS容量衰退。不过上篇复现时我先假设负荷是确定性曲线,故障场景是唯一的随机源,这样模型更干净,也更容易验证代码正确性。

3. Matlab实现思路与代码架构

3.1 参数定义与数据准备

我的代码结构分成四个部分:参数定义、场景生成、模型构建、求解与后处理。参数定义放在一个config.m脚本里,方便统一修改。包括网络参数、负荷参数、MPS参数和场景参数。

网络参数我直接用IEEE 33节点配电网测试系统,这是最常用的算例。节点坐标、支路阻抗、负荷数据网上都能找到,就不贴完整数据表了,只说明格式:bus是节点编号,branch是支路起止节点、电阻、电抗,load是节点有功/无功需求。

MPS参数简化为:单台容量500 kW,最大配置节点数5个,总配置台数上限8台,单台日成本根据文献取800元。这里要注意,容量和成本都要折算成同一时段的量纲。如果负荷数据是24小时曲线,投资成本也要折算成“日成本”,否则目标函数里两个量纲不一致,优化结果会偏向某一项。

场景生成我用了randsample函数。假设每条线路故障概率为0.05,生成一个0/1向量表示线路状态,然后从故障线路集合中随机选若干条作为同时故障线路。考虑到灾害场景通常伴随多条线路同时故障,我设定故障线路数量服从泊松分布,期望值5,再随机抽取对应数量的线路。代码大致是:

num_lines = size(branch, 1); lambda = 5; Ns = 100; scenarios = zeros(Ns, num_lines); for s = 1:Ns n_fault = min(poissrnd(lambda), num_lines); fault_lines = randperm(num_lines, n_fault); scenarios(s, fault_lines) = 1; end

注意,randperm要求第二个参数不超过总数量,所以要用min截断。这个细节我一开始没注意,运行时报错“n must be less than or equal to N”,查了半天才发现是泊松抽样可能大于线路总数。

3.2 优化求解器选择:YALMIP + CPLEX/Gurobi

Matlab下建优化模型,我强烈推荐 YALMIP。它是建模工具箱,可以无缝对接CPLEX、Gurobi、MOSEK等求解器。这个问题的决策变量有整数变量 ( x_i ) 和连续变量 ( P_{j,t}^s ),属于混合整数线性规划(MILP),首选求解器是Gurobi或CPLEX。学术用户可以用Gurobi的免费License,性能很给力。

YALMIP里用binvar或intvar声明变量。注意,MPS数量理论上可以是2台、3台,所以要用整数变量intvar,而不是二值变量。不过为了简化,我也可以把每台MPS作为一个独立的二值变量,比如x_{i,k}表示节点 ( i ) 的第 ( k ) 台候选电源是否配置,这样模型会自动满足整数性,而且后续添加逻辑约束更方便。两种写法本质上等价,但后者在YALMIP里更容易处理“一个节点是否配置了任意电源”这类约束。

我最终选用了整数变量写法,配合value()函数取值。代码核心部分大概是:

x = intvar(length(candidate_nodes), 1); P_rec = sdpvar(n_load, Ns, 'full'); cons = []; % 资源约束 cons = [cons, 0 <= x <= max_per_node]; cons = [cons, sum(x) <= total_mps]; % 负荷恢复约束 for s = 1:Ns cons = [cons, 0 <= P_rec(:,s) <= load_demand(:,s)]; cons = [cons, sum(P_rec(:,s)) <= sum(x) * P_mps_rated]; % 关联约束:没配置MPS时不能恢复负荷 for j = 1:n_load % MPS coverage matrix if cover(j) == 0 cons = [cons, P_rec(j,s) <= 0]; end end end

注意,上面的load_demand我用了确定值,但实际上应该根据故障情景判断节点是否失电。如果某个节点本身没故障且与上级电网连通,那么即使不配置MPS也能从电网取电。所以正确的建模需要区分“正常供电节点”和“由MPS恢复的节点”,否则模型会误伤正常负荷。这个问题我在3.4节详细讲。

3.3 网络连通性与失负荷判断

这里有个建模难点:要确定在故障情景 ( s ) 下,哪些负荷是失电的。最直观的方法是对每个场景做连通性分析:断开故障线路后,从上级变电站(松弛节点)出发,用深度优先搜索或graph对象的conncomp函数找出连通区域。只有与松弛节点相连的节点才能从主网获得电能,其余节点只能靠MPS供电。

我当时用Matlab自带图函数实现:

for s = 1:Ns G = graph(branch(:,1), branch(:,2)); % 删除故障线路 fault_idx = find(scenarios(s,:) == 1); G = rmedge(G, branch(fault_idx,1), branch(fault_idx,2)); bins = conncomp(G); % 松弛节点编号 slack bin_slack = bins(slack_bus); is_connected = (bins == bin_slack); % 生成节点失电标识 outage_nodes{s} = find(~is_connected); end

这个思路是对的,但有一个坑:graph的节点编号必须连续,而且rmedge需要指定端点编号,如果你的 branch 数据里节点编号不是从1开始连续排列,要先重映射。IEEE 33节点系统编号正好连续,所以没问题。

连通性分析得到失电节点后,就可以设置恢复约束了。只有满足下面条件的节点才能在场景 ( s ) 中恢复负荷:

  • 节点本身属于MPS覆盖范围(候选节点附近一定距离内);
  • 或者通过与故障区域相邻的联络开关从其他馈线转供。

后者涉及网络重构,建模复杂,我这版先只考虑MPS恢复,联络转供留到下篇的动态调度里做。因此,对于失电节点,如果它不在任何MPS候选节点的覆盖半径内,则对应恢复功率强制为0;如果在覆盖半径内,则可以由配置在该候选节点的MPS供电。

3.4 核心模型求解与结果输出

把目标函数和约束都放进YALMIP后,调用Gurobi求解:

ops = sdpsettings('solver', 'gurobi', 'verbose', 2); optimize(cons, objective, ops); x_opt = value(x); P_rec_opt = value(P_rec);

求解时间取决于场景数和整数变量个数。我是33节点系统、5个候选节点、100个场景,整数变量5个,连续变量3300个,约束7000多个,Gurobi跑下来大概2分钟。如果场景数再翻倍,时间指数上升,就需要削减场景。

求解完建议马上做合法性检查:x是否整数、是否满足总台数限制、总恢复功率是否超过总MPS容量。还要检查每个场景的失负荷是否非负。我当时跑完第一版,发现某些场景恢复功率高于该场景失电负荷,查了半天发现是目标函数里只惩罚了失负荷,没有限制恢复功率不能超过原始需求,导致模型“多恢复”以降低惩罚。加上约束P_rec <= demand后问题就没了。现在总结起来很简单,但实际排查花了几个小时。

结果输出我建议存成三张表:

  • 配置方案表:候选节点编号、配置台数、总容量;
  • 场景指标表:每个场景的恢复负荷量、失负荷量、恢复率;
  • 综合评价表:期望失负荷、重要负荷恢复率、目标函数值。

可视化方面,我画了系统拓扑图,用颜色标记MPS配置位置,用柱状图展示各节点恢复率。Matlab的plot加scatter就够了,不用额外工具包。图的价值在于方便写论文汇报,但自检时更重要还是看数据表。

4. 关键细节与避坑指南

4.1 大M约束的正确姿势

前面提到关联约束 ( P_{j,t}^s \le M \cdot y_j ) 需要大M,但这个M不能随便选。如果M太小,可能把可行域切掉;如果M太大,会导致求解器数值问题,出现奇怪的割平面和长时间收敛。经验做法是:M取该节点最大负荷的1.1或者2倍,只要能保证当 y=1 时约束不紧,y=0 时 P 被迫为0就行。宁小勿大。我一开始图省事设M=10000,Gurobi求解时间是M=100时的3倍多,换适当M后快多了。

另外,如果要表达“某个节点只有配置了MPS才能恢复”,大M要乘以该候选节点是否存在配置的变量。如果 ( x_i ) 是连续整数变量,直接写 ( P_j \le M \cdot x_i ) 会引入非线性,因为 ( x_i ) 是变量。稳妥做法是引入指示变量 ( y_i = 1 ) 当且仅当 ( x_i > 0 ):

y = binvar(n_candidate, 1); for i = 1:n_candidate cons = [cons, x(i) <= max_per_node * y(i)]; cons = [cons, x(i) >= y(i)]; end

这样就建立了整数变量和二值变量的等价关系。再用 y 去约束 P 就安全了。这个技巧非常实用,很多初学YALMIP的人会卡在这里。

4.2 场景削减与求解速度的平衡

在复现过程中,我对比了三组场景配置:50个场景、100个场景、200个场景。50个场景求解约30秒,100个场景约2分钟,200个场景约8分钟。但结果差异很小,因为故障场景重合度高,冗余场景太多。

想要既保留精度又提速,可以用语义丰富的场景削减。YALMIP本身不做削减,但可以用Matlab写一个简单的快速前推算法:

% 简化的场景削减示意 distance = pdist2(scenarios, scenarios, 'squaredeuclidean'); selected = 1:Ns; while length(selected) > N_target % 找与其他场景距离最近的场景 min_dist = min(distance(selected, selected), [], 2); [~, idx] = min(min_dist); selected(idx) = []; end

这个算法把聚类中心附近的冗余场景剔除,保留代表性场景。注意,削减后的概率要重新归一化。削减前后目标函数值差异控制在2%以内,完全可接受。

4.3 线性潮流模型的选择

原论文可能用了二阶锥潮流,但我在做预配置复现时用了更简单的线性DistFlow,因为预配置阶段不涉及详细的电压无功调整,主要关注有功平衡。如果你要对比电压分布,建议改用二阶锥松弛,YALMIP可以用optimize直接处理SOCP。不过在33节点系统下,线性模型的电压误差很小,因为馈线负载率不高、电压降落不明显。

这里有个个人体会:复现论文第一步,不要一上来就重建整个复杂模型。先把“预配置 + 失负荷最小化”这个主干实现,跑通后再逐步增加约束,比如电压约束、无功约束、MPS移动时间窗。这样每一步都有清晰的验证节点,出问题也好定位。我就是一开始想一步到位加上所有细节,结果模型写了几百行,一跑就是错误,最后删掉重来才顺利。

4.4 参数量纲与单位的一致性

这是一个看似不起眼但杀伤力极大的坑。原论文里投资成本可能是$/台,负荷功率是MW,失负荷惩罚可能是$/MWh。你如果不做统一,优化结果要么全配置MPS,要么一台都不配,完全失真。

我采用的方式是:把MPS成本折算为单台套日成本,单位元/(台·日),负荷按日电量(kWh)统计,失负荷价值按元/kWh折算。以某重要负荷为例,失负荷惩罚取10元/kWh,而一台500kW MPS日成本800元,意味着这台电源如果一天能恢复200 kWh以上重要负荷就是划算的。这样把量纲统一后,目标函数才有可比性。建议代码里增加一行注释,标清楚所有单位,否则隔两周自己再看都容易懵。

5. 常见问题与排查技巧实录

5.1 求解器报错“License”问题

YALMIP调用Gurobi时,最常见的是License expired或者No license found。学校如果有校园网License还好办,个人用户去Gurobi官网申请学术License需要学校域名邮箱。CPLEX也有类似限制。如果实在搞不到商业求解器,可以先用开源的HiGHS,YALMIP也支持,性能比Gurobi差一些,但小规模算例完全够用。安装方式是下载HiGHS二进制文件,然后设置sdpsettings('solver','highs')。我试过33节点100场景,HiGHS也能在三分钟内解出来,够复现用。

5.2 出现“Infeasible”问题怎么找原因

MILP不可行是新手最头疼的事。我的排查顺序是:先去掉目标函数,只求解约束,看是否可行;用check(cons)检查每条约束的残差;逐步注释掉约束组,二分定位不可行约束。最常见的原因有二:一是总MPS容量小于某些关键场景的最小恢复需求,导致约束矛盾;二是关联约束写错,导致模型认为“不配置电源也能恢复负荷”或“配置了电源却无法供出功率”。

我实际遇到过一种有趣情况:某个失电节点既不在MPS覆盖范围,又没有联络线路,但我把它的恢复功率上限设成了原始负荷值。模型发现无论如何都无法恢复该负荷,但又被目标函数引导去恢复它,于是只能报不可行。解决办法是把该节点的恢复功率直接强制为0,不参与优化。也就是说,先做连通性分析,把不可达负荷识别出来,再用约束固定P=0,模型立刻可行。

5.3 结果与直觉不符:为什么配置了MPS但恢复率不高

有次跑完结果,发现某候选节点配置了三台MPS,但该节点覆盖的负荷恢复率只有40%。我检查后发现,问题出在故障场景里该节点的联络线路也断了,MPS接入后孤岛内只有部分负荷处于覆盖范围,其余负荷电气距离太远,线路上无法传输足够功率。

这说明预配置不能只看节点位置,还要考虑局部网架的供电路径。如果候选节点的下游只有一条馈线,且该馈线在故障中受损,那这台MPS的效用就打折扣。所以后来我加入了一个“有效覆盖范围”预筛选:先把故障概率高的线路和重要负荷节点挑出来,再逆向选取MPS候选节点,确保候选节点的下游网架相对可靠或至少有多条供电路径。这个思路在论文里可能就一句话,但实际工程中非常关键。

5.4 如何验证模型正确性

我在复现时做了几个自检实验,大家也可以参考。第一,把投资成本设为零,此时目标函数退化为最小化失负荷,应该会在所有候选节点配置满上限台数。第二,把所有故障概率设为零,只有一个正常场景,那么最优配置应该是0台,因为没有灾害就不需要预置电源。第三,把总MPS台数设成1,而且所有候选节点离重要负荷都非常远,此时结果应该是不配置任何MPS,因为配置了也无法恢复负荷,只会增加成本。这几种极端情况能快速发现模型约束写错或参数设置异常。

我用这套自检流程,抓到过两个隐蔽bug:一个是场景概率没归一化导致期望失负荷偏大,另一个是P_rec的维度写反导致负荷恢复张冠李戴。

6. 上篇实现中的个人体会

到这里,MPS预配置的核心模型和Matlab实现就讲得差不多了。整个过程走下来,我最大的体会是:复现一篇论文,真正花时间的不是抄代码,而是理解每个约束为什么这么写。比如大M约束、场景生成、连通性判断,这些细节如果照搬论文而不理解,遇到问题完全无从下手。

另外一个很实用的经验是:先跑通一个小规模算例,再逐步扩大。我一开始直接上了IEEE 123节点系统,结果模型几十万个变量,求解器跑了一晚上都没收敛,差点放弃。后来回到33节点系统把逻辑理顺,再重新做大系统就顺手多了。如果你也在复现,强烈建议从小算例开始。

关于下篇的动态调度,我已经在构思了。动态调度比预配置更复杂,因为要引入时间维度和MPS的移动路径约束,目标也不再是简单的期望失负荷最小,而是多时段的切负荷和恢复顺序优化。等我把下篇复现完,会继续写一篇关于MPS动态调度的Matlab实现笔记,包括移动时间窗、交通约束、联络开关重构等内容。这篇先到这儿,如果你对某一部分有疑问,欢迎在评论区留言,我尽量针对实际问题回复。

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

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

立即咨询