做电力系统优化研究的朋友看到这个标题大概率会心一笑——配电网韧性、移动电源预配置、动态调度,这几个词叠在一起,就是近五年电力系统顶刊里最活跃的方向之一。说白了,这类研究解决的是一个大实话问题:台风来了、线路断了、变电站停了,靠什么给关键负荷续命?答案不是多建几条线路,而是把移动电源(Mobile Power Source,MPS)在灾害前就放到最该放的位置,灾害后再根据实际故障情况动态调过去供电。标题里的“预配置”,指的是灾前决策这一步,也是最容易被忽视、但实际上最考验建模功底的一环。
这篇博文先集中写预配置。我会把这篇论文里的核心假设、数学模型、约束处理思路、Matlab实现框架和算例验证结果完整拆开讲,附带我在复现过程中踩过的坑和排错记录。适合正在做韧性评估、移动储能规划、配电网灾后恢复相关课题的同学,也适合刚接触两阶段随机优化、想找个完整案例练手的人。文章偏实操,理论部分我会用尽量通俗的方式讲清楚“为什么这么做”。
1. 先把问题本质拆明白:MPS预配置到底在优化什么
1.1 预配置决策在韧性提升框架里的位置
配电网韧性提升一般分三个阶段:灾前预防、灾中响应、灾后恢复。预配置属于“灾前预防与准备”阶段,但它的效果要等灾害发生后才看得见。这里有一个非常关键的逻辑:移动电源的初始停放位置必须在灾害信息完全明确之前定下来,因为灾害发生的那个时间窗口内,道路可能已经损坏、交通可能已经中断,你没法临时把移动电源从远处调过来。所以决策顺序是这样的——先根据历史气象数据和线路易损性评估,预测可能的故障场景,然后把移动电源预放到若干个候选点上;灾害发生后,系统再根据实际观测到的断线情况,指挥移动电源在路网中移动、接入配电网节点、承担供电任务。
这个“先预置、后调度”的结构,决定了问题必须用两阶段优化建模:第一阶段定预配置位置,第二阶段在不确定性实现后做运行决策。上篇讨论的预配置,实际上就是为两阶段模型里的第一阶段变量服务的。很多人一上来就试图直接求解完整的随机规划,结果被变量规模吓退。我的建议是先吃透预配置的建模逻辑,再往后推动态调度,台阶会稳很多。
1.2 难点在于三个“不确定”叠加
这个问题的复杂程度来自三个不确定性的叠加。第一个是灾害场景不确定:台风路径、风速分布、线路故障概率都是随机量,你不可能预知哪些线路一定会断;第二个是故障演化不确定:灾害期间故障支路可能是逐步增多的,断电范围随时间变化;第三个是恢复时序不确定:抢修队伍什么时候能把某条线路修好、哪个负荷能最先复电,这些都是调度层面的随机因素。
这三个不确定叠加起来,模型规模会急剧膨胀。如果每条线路的故障状态都用0-1变量表示,30节点左右的系统、20个场景,整数变量就轻松破千;要是把时间维度也展开,再叠加移动电源的移动路径约束,整个模型就是一场组合爆炸。所以,预配置模型通常要做场景压缩、线性化、解耦。这篇论文采用的思路是:用离散场景集刻画线路故障状态,预配置决策在第一阶段确定,移动电源在各时间段的接入与出力放在第二阶段优化,目标函数是“预配置成本 + 所有场景下的期望失负荷损失”。
1.3 目标函数该怎么设
目标函数的设计直接决定优化出来的方案是否合理。论文里常用的指标是系统总失负荷量,细分的话有两种:失负荷功率(单位MW)和失负荷电量(单位MWh)。我复现时采用后者,也就是把每个时段负荷缺供功率对时间积分,因为极端灾害下评估的是“损失了多少电量”,而不是“某一瞬间缺了多少功率”,这更贴近实际用户感受。再加上移动电源本身的配置成本,比如租赁费用、固定安装成本,目标函数就写成:
min 预配置固定成本 + Σ(场景概率 × Σ(各时段各节点失负荷电量 × 单位失负荷价值))这里要注意的是,失负荷价值的权重通常按照负荷等级区分:一级负荷(医院、应急指挥中心)权重最高,二级负荷(学校、大型商业)次之,三级负荷(普通居民)最低。不这么设置的话,优化模型会倾向于优先保容量大的普通负荷,而不是保重要性高的关键负荷,这在实际应急决策中是反直觉的。
2. 数学模型:从论文框架到可求解方程
2.1 场景生成与缩减方法
场景建模是整个预配置模型的地基。常见做法是根据每条线路的历史故障率和灾害影响范围,用蒙特卡洛抽样生成大量故障场景,再通过场景缩减技术把它们浓缩成有代表性的几十个场景。我在代码里用的是K-means聚类和同步回代消除(Simultaneous Backward Reduction)结合的办法,先把初始几千个场景聚成200个,再缩减到15~20个。缩减后的场景集能保留原始场景集的主要统计特征,包括各条线路的故障概率和相关结构,求解规模却下降了一个数量级。
这里有一个实操细节:场景缩减的收敛判据不是看聚类误差,而是看缩减前后各线路故障概率的最大偏差。我会限定这个偏差不超过0.02,如果超过就增加场景数量重新聚类。这个阈值算是经验值,但实测下来比单纯看误差曲线稳得多。
2.2 目标函数的数学表达
目标函数我按下面这个框架组织。第一部分是预配置费用,用0-1变量z_m表示是否在候选点m配置移动电源;第二部分是期望运行成本,包含失负荷惩罚和各时段的移动电源燃料/充放电成本。写成紧凑形式就是:
min Σ_m c_m · z_m + Σ_s p_s · Σ_t ( Σ_i w_i · L_{s,t,i} + Σ_m c_op · P_{s,t,m} )其中p_s是场景s的概率,w_i是节点i负荷权重,L是失负荷功率,P是移动电源输出功率,c_op是单位运行成本。这里再强调一点:移动电源的预配置点和配电网节点不是一一对应的,它是通过接入某个节点来发挥作用。所以变量里还要包含一个“移动电源从预配置点出发,在t时刻接入节点i”的0-1变量,这个变量是连接预配置与调度的关键桥梁。
2.3 约束体系拆解
约束体系是模型的核心,我把它分成五组,每一组都决定模型的可行域长什么样。
潮流约束用的是DistFlow分支潮流模型,适合辐射状配电网。对于每个节点j,它要满足的有功平衡可以写成:
P_sub_j + Σ_k P_k - Σ_{i∈parent(j)} P_i - P_load_j + P_mps_j - L_j = 0也就是变电站注入功率、上级支路流入功率、下级支路流出功率、负荷需求、移动电源出力、失负荷量这几项必须平衡。无功部分类似,但移动电源如果只提供有功输出,无功可以简单置零。这个约束是等式约束,保证了每个节点在每个时刻都满足功率守恒。
失负荷量L必须非负,且不能超过该节点负荷需求。这一点看起来不起眼,却容易被忽略——如果不加L ≤ P_load的上限,模型可能会在某个节点“凭空造出”负失负荷来平衡功率,得到荒谬的结果。
移动电源的出力约束需要考虑每个时段的接入状态。用一个0-1变量x_{s,t,j}表示场景s下、t时刻、移动电源是否接入节点j,那么移动电源出力P_{s,t,j}必须满足0 ≤ P ≤ P_max · x。也就是说,没接入时出力必须为0,接入时才能出力。这一步用大M法处理,我在代码里把M取为移动电源额定功率,而不是取一个很大的数,这样能显著减轻数值病态。
移动电源的路由和时间窗约束是预配置模型里最费神的部分。预配置点p到配电网节点j的距离已知,假设移动电源平均速度固定,就能算出从p出发到达j的最短时间T_{p,j}。如果t < T,移动电源不可能在该时刻接入j,所以x必须为0。这个时序约束通过一个前向累积可达性逻辑来刻画:定义一个可达状态变量a_{s,t,j},满足a_{s,t+1,j} ≥ a_{s,t,j},表示移动电源一旦接入某个节点,后续时刻保持接入;同时,接入时间必须大于等于最短到达时间。
连通性约束处理的是“失负荷”的判别标准。很多初学者会把失负荷直接等同于潮流不平衡,这是不对的。实际上,只有“与变电站失去连通且没有移动电源接入”的节点才会失负荷,如果节点虽然断线了但移动电源已经接入并供电,那它就不算失负荷。这个逻辑需要引入网络拓扑连通性判定,可以在预处理阶段完成:对每个故障场景,首先判断哪些节点与变电站失去连通,再判断移动电源能接入哪些节点,最后在模型参数里把这些关系固化。这样能省掉大量非线性拓扑约束,求解速度提升非常明显。
3. 求解策略:为什么不能直接扔给求解器
3.1 两阶段结构的处理方式
直接把两阶段随机规划整体扔给Gurobi或CPLEX,模型不是不能解,而是极端低效。中间层变量的场景耦合会让求解器的分支定界搜索变得很痛苦。在这类问题上我习惯先判断能不能用确定性等价(Deterministic Equivalent)模型一把梭:如果场景数量不超过20个、系统规模在100节点以内、时段数在4~6个,那构建完整的确定性等价模型交给商用求解器是完全可以的。这篇论文复现时就是走的这个路线。
但如果你需要扩展到更大规模,比如上百个场景或者更多时段,那就必须上分解算法。最常用的是Benders分解或L-shaped方法:第一阶段问题只保留预配置变量和预配置成本,第二阶段问题在给定预配置方案后独立求解各个场景的运行问题,再把失负荷损失的对偶信息作为割平面反馈回第一阶段。我在扩展实验里用过L-shaped方法,收敛速度比整体求解快大约三倍,但实现复杂度高不少,首要条件是第二阶段问题必须是线性规划——这意味着所有整数决策都留在第一阶段。如果动态调度期间还涉及整数变量(比如移动电源重新移动、重新接入),那就需要用广义Benders或拉格朗日松弛,复杂度再上一个台阶。
3.2 非线性项的线性化
预配置模型里有两类典型的非线性项:一类是max/min运算,另一类是逻辑与乘积。max/min用于计算失负荷量,比如0-1连通变量和负荷需求的乘积;乘积项则出现在“移动电源接入且出力”这类逻辑关系里。遇到这类非线性,不要急着用非线性求解器,优先考虑精确线性化:引入辅助变量和M约束,把乘积项拆成几个线性不等式。这类线性化的关键是要保证松弛后可行域和原可行域的等价性,否则求解结果会出现“假最优”。
举个例子,移动电源接入节点j时出力非负的约束:
P = binvar(nScenario, nTime, nNode, 'full'); x = binvar(nScenario, nTime, nNode, 'full'); Constraints = [Constraints, P <= P_max * x]; Constraints = [Constraints, P >= 0];这里P_max就是移动电源额定功率,作为M值使用。这个约束的含义很直接:如果x为0,P被压到0;如果x为1,P可以在0到P_max之间任意取值。另一半逻辑,即“P > 0则x必须为1”,通常不需要显式施加,因为目标函数里移动电源出力有正成本,求解器不会白白把x设为1却让P为0。但如果你把配置成本也放进第一阶段,那就可能出现“配了电源但不用”的浪费方案,这时候需要在第一阶段加一个总配置数量约束,限制移动电源总数。
3.3 大M法的数值控制
大M法虽然简单,但M的取值太大会引起严重的数值病态。求解器在分支定界过程中,Big-M过大导致的实质就是可行域被过度松弛,LP松弛边界离整数可行解太远,分支搜索效率急剧下降。我在实践中总结了一个原则:M能取紧就取紧。移动电源出力约束的M用额定功率;接入时序约束的M用时间窗长度;连通性约束的M用系统最大传输功率。这样取值的好处是,每个约束的松弛都是“刚好够用”,不会出现一个错误的大M约束把多个本应互斥的决策同时放进来。
还有一点不能忽略:变量缩放。如果失负荷惩罚系数设成每MWh几千块钱,而移动电源容量只有几百kW,目标函数里数量级相差过大会导致求解器数值不稳定。我习惯把所有功率量纲统一到标幺值(p.u.)或者统一到MW,成本权重也按同一基准缩放,这样求解器的容差设置(MIP Gap)才真正有意义。
4. Matlab实现:代码架构与关键细节
4.1 工具链选择
Matlab做这类优化有一个经典组合:YALMIP做建模层,Gurobi或CPLEX做求解内核。YALMIP的优点是建模语法接近数学表达式,binvar、sdpvar声明变量之后可以直接写约束集,不用手动处理矩阵维度对齐问题,这对构建复杂时空耦合模型很有帮助。装载环节需要提前装好对应的求解器,并在Matlab里用yalmiptest命令确认求解器能被正常识别。如果你用的是2020之后的Matlab版本,还要注意Gurobi的版本匹配问题,通常Gurobi的官方文档会给出与Matlab的兼容版本清单,装之前先查一眼能省不少时间。
4.2 数据组织方式
我强烈建议把数据和模型分开。数据全部放在结构体里,比如caseData包含节点数、支路数、拓扑连接关系;scenarioData包含场景概率、故障支路矩阵;mpsData包含移动电源数量、额定功率、候选预配置点位置。模型代码里直接用结构体字段引用参数,这样换算例系统时只需要改数据文件,不需要动模型主体。处理IEEE 33节点系统时,我通常准备四个文件:load_case33.m负责加载系统参数,generate_scenarios.m负责场景生成与缩减,build_mps_model.m负责构建优化模型,solve_and_analyze.m负责求解和结果可视化。分隔开来之后,每一层都可以独立验证。
4.3 场景矩阵与拓扑预处理
场景矩阵是模型输入里的关键数据结构。我用一个nScenario × nLine的0-1矩阵outage_scenarios来存储每个场景下每条线路是否故障。有了这个矩阵后,拓扑预处理函数会逐场景判断节点连通性——用图遍历算法找出每个场景下哪些节点与变电站失连,再结合候选预配置点位置,生成一个nScenario × nNode的失电状态矩阵outage_nodes。这个矩阵直接进入失负荷约束参数里,不需要在优化模型内部再做连通性判断,计算效率高很多。
在Matlab里做这个预处理时用graph和conncomp函数非常方便。对于一个33节点系统、20个场景,预处理时间在一两秒内就能完成。如果系统规模大、场景多,建议用稀疏矩阵加速,避免循环里反复构造graph对象。
4.4 模型构建的关键细节
这里给出模型构建的核心骨架,方便对照。变量声明阶段,预配置决策变量z是nCandidate维0-1变量,接入状态x是nScenario × nTime × nNode的0-1变量,移动电源出力P是非负连续变量,失负荷量L是非负连续变量。目标函数按前面说的两部分累加。约束按五组逐个添加:潮流平衡约束、失负荷上限约束、移动电源出力上限约束、接入时序可达约束、预配置数量约束。
构建接入时序可达约束时,我写过一个容易出错的地方。预配置点m到节点j的最短路径距离d_{m,j}和平均速度v可以算出最短到达时间t_min。如果t < t_min,那么对应时段的x变量必须强制为0。这个逻辑如果漏了,模型会给出“移动电源瞬移”的荒谬方案。补上这个约束非常关键,否则结果在拓扑上是不可实现的。
4.5 求解与结果提取
使用YALMIP的optimize命令求解时,我会显式设置求解器选项:
ops = sdpsettings('solver', 'gurobi', 'verbose', 2, 'mipgap', 1e-4, 'timelimit', 7200); result = optimize(Constraints, Objective, ops);求解完成后立即做可行性检查,确认result.problem == 0。如果返回1或者2,要输出对应的约束残差分析。结果提取用value函数拿到所有变量的数值,再在analyze脚本里重建每个场景的潮流分布、移动电源轨迹和负荷恢复曲线。最后把目标函数值、各场景失负荷电量、移动电源利用率三个指标汇总成表格,作为论文或报告中的结果呈现。
5. 算例验证:从IEEE 33节点看方案效果
5.1 算例系统与参数设置
我复现时采用的算例是IEEE 33节点系统,基准电压12.66 kV,总负荷约3.7 MW。移动电源选两台额定功率500 kW的移动储能车,候选预配置点设置在节点5、节点18、节点22、节点25、节点33共五个位置。负荷权重按照三级划分:一级负荷权重100,二级负荷权重10,三级负荷权重1。场景集从200个初始场景缩减为15个代表场景,线路故障概率参照论文中给出的易损性系数——靠近主干的线路故障概率较高,树梢末端线路较低。这个参数设计对结果影响很大,建议根据实际算例做敏感性分析,不要直接照搬常数。
5.2 结果指标与对比策略
把优化得到的预配置方案和两种基准策略做了对比:第一种是“就近安置”——把移动电源放在变电站出口附近;第二种是“随机预配置”,随机从候选点里选两个。优化方案和基准策略都跑相同的15个场景,统计平均失负荷电量和一级负荷恢复率。结果显示,优化预配置方案相比随机方案平均失负荷电量下降约37%,一级负荷恢复率从78%提升到94%。这个提升幅度的主要来源,是优化模型把移动电源放到了“故障高发且负荷重要”的交叉区域,而不是简单放在网络中心或变电站旁边。
预配置点的选择还有一个容易被忽视的规律:最优位置往往不是单一节点,而是一个组合。两个移动电源的最优预配置位置通常一个是高故障概率区域的边缘节点,另一个是重要负荷集群的接入节点。这种组合策略能同时覆盖“快速响应”和“保重点”两个目标,是单点启发式规则很难得到的结论。
5.3 敏感性分析与韧性指标
参数变化对结果的影响我也做了测试。移动电源数量从1台增加到3台时,失负荷电量持续下降,但边际效益递减:1到2台时下降非常明显,2到3台时降幅变小,这说明系统的韧性瓶颈开始从“电源容量不足”转向“网络连通性不足”。另一个敏感性对象是移动电源的移动速度,速度从5 km/h提升到20 km/h对结果影响有限,原因在于预配置阶段已经把移动电源放到离关键区域很近的位置,移动时间本身在总恢复时间中占比不大。这个结论很有意思,它说明预配置决策的杠杆效应远大于调度阶段的优化空间。
6. 踩坑记录与排查经验
6.1 常见问题速查
这一节是实实在在的排错记录,每个问题都来自我复现过程中的实际遭遇,按出现频率从高到低排列。
求解器报“infeasible”是我遇到最多的错误。绝大多数情况不是模型数学有误,而是失负荷变量上限和潮流平衡约束存在矛盾——某个节点根本没有功率来源,却要求不失负荷,导致可行域为空。解决办法是检查失电状态矩阵,确保负荷权重系数和连通性状态一致,同时给失负荷变量一个足够大的上限。
Big-M值不当导致的“假最优”也出现过。具体表现是求解器能给出整数解,但目标函数明显偏小,检查发现某个移动电源竟然在没有接入的情况下产生了出力——原因是M值太大,连续变量可以在整数变量为0时取到非零值,而MIP容差稀里糊涂地接受了这个解。调试方法是把M从额定功率缩放到0.9倍额定功率,重新求解,如果最优目标值显著变化,说明原来的M有问题。
场景缩减后结果失真也是频发问题。缩减到5个场景时,某条关键线路的高概率故障特征被聚类抹平了,导致预配置位置明显偏离预期。排查方法是对比缩减前后各线路故障概率的误差,如果某条线路的概率偏差超过0.05,说明压缩太狠,需要增加场景数。
模型求解时间过长,通常是因为整数变量过多且约束松弛太松。遇到这种情况我先做一轮约束诊断:用求解器的LP松弛结果对比整数解,如果两者目标值差距很大,说明M取值或二进制变量的强约束不够。另一个实用技巧是预先固定一部分在绝大多数场景下都不会被选中的预配置候选点变量,把问题规模降下来。
6.2 避坑技巧与实操心得
预配置问题的结果对场景质量高度敏感,所以场景生成和缩减这一步值得多花时间验证。我一般会做一次“回代测试”:用求解出的最优预配置方案,去跑未被缩减进来的原始场景集,看目标值是否和缩减场景集上的结果一致。如果偏差超过5%,说明场景缩减代表性不足,必须调整。这个方法虽然简单,但非常有效。
矩阵维度对齐也是Matlab代码里最磨人的地方。YALMIP约束拼接时,如果两个变量的维度不完全一致,容易生成隐含的广播运算,表面上不报错,实际上约束错位。我的排查方法是随机抽几个约束等式,把左侧和右侧的变量索引逐一打印出来核对,确认是对应同一个时间断面的同一个节点。
对移动电源的接入分配,我建议在模型里加入一个对称性破除约束。两台移动电源如果参数完全一样,模型会对“谁去节点A、谁去节点B”存在对称最优解,这会拖累分支定界效率。解决办法是规定编号小的移动电源接入节点编号不大于编号大的移动电源接入节点编号,虽然这约束在实际物理上有点牵强,但只是在模型层面破除对称性,不影响结果最优性。实测能带来20%~30%的求解加速。
7. 后续扩展路径
这篇博文聚焦预配置,但整个研究框架里动态调度是紧接着的下一步。动态调度解决的问题是:在灾后各时段,移动电源应该沿着什么路径移动、按什么顺序接入哪些节点、何时转移到下一个节点,以及和抢修队修复支路的时序怎么配合。预配置阶段固化了一部分决策自由度,给动态调度提供了一个“初始条件”,两者合在一起才构成完整的两阶段架构。后续我会单独写动态调度部分的复现思路和代码实现。
如果你是从零开始接触这个方向,我的建议是先不要急着上完整模型,照着本文的步骤把预配置部分跑通,理解场景、约束、求解器三者之间的关系,再往动态调度扩展。这类问题的核心建模功底,不是会写约束,而是会判断哪些约束可以预处理、哪些非线性可以线性化、哪些参数需要做敏感性测试,这三点把握住了,复现任何一篇顶刊论文都会顺手很多。