配电网韧性提升这个方向,在电力系统研究里已经不算冷门了,但真正能把“移动储能预布局”和“动态调度”串成一条完整链路、并且在标准测试系统上跑出可用结果的项目,其实没有想象中那么多。这个项目选择IEEE33节点系统做仿真验证,用Matlab实现从场景生成、两阶段建模到求解分析的全流程,是一个很经典的参照样本。我前前后后做过几个同类问题,对这类模型踩过的坑不少,这篇把设计逻辑、数学模型、代码实现细节和调试经验一起拆开聊一聊,特别适合正在做配电网韧性、储能优化调度、或者刚接触两阶段随机规划的同学参考。
1. 项目整体思路与设计拆解
1.1 配电网韧性的核心痛点:为什么普通应急手段不够用
配电网韧性,通俗点说就是电网在遭遇极端灾害之后,能不能扛得住、恢复得快。台风、冰灾这类极端事件一旦发生,配电线路往往是大面积断线,传统的应急手段无非是抢修队出动、固定储能放充、备供线路转带。但这里面有一个很现实的矛盾:固定储能的位置是长期确定的,灾害场景却不是固定的,如果故障点恰好远离固定储能所在馈线,储能就算有电也送不进去。
移动储能的思路不一样,它的价值不在于“容量有多大”,而在于“可以挪到最需要的地方”。灾前根据气象预报和系统状态提前把储能车布置到关键节点附近,灾后再根据实际断线情况调整充放电策略,这就是所谓预布局与动态调度的协同。这个项目把这两个阶段放进同一个优化框架里,本质上是在回答三个问题:储能提前放在哪些节点收益期望最大?故障发生后每一台储能往哪里跑、什么时候充放电?配电网的网络拓扑要不要配合储能一起调整?
1.2 两阶段策略框架:先预布局再动态调度
整个项目采用典型的两阶段随机规划结构。阶段一发生在灾害来临之前,决策变量是移动储能的初始部署位置和初始荷电状态,目标是最小化所有可能故障场景下的期望失负荷,但不依赖于具体某一个故障场景是否发生。阶段二发生在故障状态确定之后,针对每一个具体场景,决策储能车的移动路径、各时段充放电功率,以及配电网的开关状态,目标是最大化该场景下的负荷恢复量。
这个“先决策、后观察、再决策”的结构,和现实中的调度流程是对得上的。灾前你能拿到的只有气象预警和故障概率,不可能精确知道哪条线会断,硬要做一个确定性的“最优部署”反而失真。用场景集合去描述不确定性,再在第二阶段针对每个场景做精细调度,既符合工程操作习惯,也方便用成熟的数学规划求解器去处理。
1.3 固定储能、移动储能与动态重构:方案选型对比
很多人在建模之前会问,既然都做动态调度了,为什么还需要“预布局”这一层?直接把储能车放到一个固定的应急充电站不行吗?我习惯用下面这张表来说明三个方案的区别:
| 方案 | 灵活性 | 建模难度 | 适用场景 | 对韧性的提升方式 |
|---|---|---|---|---|
| 固定储能 | 低,位置固定 | 低 | 常态削峰填谷、事故备用 | 依赖储能与故障点的空间距离 |
| 移动储能不加预布局 | 中,灾后临时调度 | 中 | 故障信息明确的恢复阶段 | 响应有延迟,容易错过黄金恢复窗口 |
| 移动储能+预布局 | 高,灾前优化布置 | 高 | 台风预警、冰雪灾害前的中长期防控 | 同时优化灾前防御与灾后响应 |
实际操作中,移动储能车从驻地赶到目标节点需要时间,如果灾害已经发生了再去调度,往往要损失好几个小时的响应窗口。预布局的意义就在于把储能的“等待时间成本”提前付掉,用空间换时间。当然,代价是要承担“放错位置”的风险,所以目标函数里一定要用多场景期望而不是单一场景的确定性指标。
2. 数学模型与求解路线
2.1 目标函数:从单场景最小失负荷到多场景期望
项目目标函数用两阶段形式表达。第一阶段在预布局约束集合X下决定部署方案x,第二阶段则针对每个场景s求最优调度收益Q(x,s):
$$\min_{x} \left{ C_{pre}(x) + \sum_{s \in S} p_s \cdot Q(x,s) \right}$$
其中p_s是场景s的权重,C_pre表示灾前部署成本,比如储能租赁费用、移动成本等。第二阶段目标函数Q(x,s)更具体,一般写成所有节点、所有时段的加权失负荷量最小:
$$Q(x,s) = \min \sum_{t=1}^{T} \sum_{i \in \Omega_N} \omega_i \cdot P_i^{load}(t) \cdot \left(1 - \alpha_{i,t}\right)$$
ω_i是节点i的负荷权重,反映医院、通信基站这类重要负荷的优先级,α_i,t是该节点的供电恢复比例,取值范围0到1。带权重的最小失负荷目标,比单纯统计“一共掉了多少千瓦”更能反映实际调度偏好,这个细节在代码实现时也直接影响约束怎么构建。
2.2 约束条件逐项拆解:潮流、拓扑、储能与移动时序
动态调度阶段不是简单的“哪边缺电就放哪边”,而是要把配电网物理特性完全约束进去。整个模型里最核心的约束有四类。
第一类是辐射状配电网的潮流约束。这里通常用DistFlow方程,对支路(i,j)写出有功、无功流动关系和电压降落关系,然后通过引入辅助变量把非线性部分转成二阶锥约束。在Matlab实现时,这一块建议用标幺值,否则电压和功率数量级差异太大,求解器容易数值不稳。
第二类是网络拓扑约束。灾害场景下部分线路断线,联络开关可以合上形成新的供电路径,但配电网必须保持辐射状。代码里用二进制变量表示开关状态,并加入“支路数等于节点数减一”的约束,同时用生成树方向的辅助变量避免出现闭环或孤岛。这个约束看着简单,实际是最容易写错的地方。
第三类是储能设备的运行约束。每一台移动储能在任意时刻只能处于充电、放电、空闲、移动四种状态之一,充放电功率各自不能超过额定值,SOC要保持在上下限之间,且充电和放电不能同时进行。移动状态则通过位置变量来描述,位置变化的那段时间不允许充放电,这保证储能车在赶路时不会违规参与功率交互。
第四类是节点安全约束。电压幅值、支路电流都要落在允许范围内,通常用电压平方值的上下界和支路功率上限来近似表达。考虑到移动储能接入后局部电压抬升,这一组约束对结果的影响很大,不能省略。
2.3 非线性问题怎么处理:线性化与二阶锥松弛
DistFlow方程里有两个自然出现的非线性项——电流平方乘电阻的压降项,以及功率平方与电压的耦合项。如果直接抛给求解器,问题会变成非凸的,工业级求解器很难保证收敛。项目代码采用标准做法:把电压平方V_i^2和电流平方I_ij^2设成独立变量,再把约束
$$V_i^2 \cdot I_{ij}^2 \ge P_j^2 + Q_j^2$$
松弛成旋转锥的形式。注意,这里的“松弛”不是偷懒,而是经过严谨证明的凸松弛,在绝大多数辐射状配电网场景下,松弛间隙小到可以忽略,解是精确收紧的。
求解器选型方面,我推荐YALMIP作为建模层,搭配Gurobi或CPLEX做底层求解器。YALMIP的建模语法简洁,SOCP约束、二进制变量、大M约束都能直接表达,省去手写求解器接口的麻烦。早期我试过纯用MATLAB自带的fmincon做内点法求解,模型非凸的时候就非常痛苦,收敛极慢且结果不可复现,换到混合整数二阶锥规划(MISOCP)路线之后,整个实验才稳定下来。
3. IEEE33节点系统与Matlab实现细节
3.1 IEEE33节点系统参数与负荷数据
IEEE33节点系统是配电网分析的标准测试系统,基准电压12.66kV,基准容量10MVA。系统包含33个节点、32条常规支路、5条联络开关支路,正常运行时联络开关打开,整个网络以节点1为变电站出口,呈辐射状供电。系统总有功负荷3715kW,总无功负荷2300kvar。
在写数据文件时有一条经验:不要在Matlab里手工一个个敲支路阻抗和负荷数据,那既容易出错又没法复用。比较规范的做法是把节点编号、支路首末端节点、电阻电抗、有功无功负荷放在一个struct里,或者直接写成CSV表格再读取。代码里一般用两个数组,busData保存节点编号和有功无功负荷,branchData保存支路首末端编号、电阻电抗和初始开关状态。移动储能则单独用一份essData配置,包括储能数量、容量、额定功率、SOC上下限和初始位置约束。
3.2 代码结构与数据准备
整个项目按功能拆成几个互相独立的文件,跑起来逻辑清晰:
main_pre_dispatch.m % 主程序,统筹场景生成、预布局、调度、出图 data_ieee33.m % IEEE33节点数据定义 gen_scenarios.m % 故障场景生成 pre_position_optim.m % 预布局优化函数 dispatch_optim.m % 动态调度优化函数 plot_results.m % 结果可视化场景生成这一步不要小看它。常见做法是对每条支路赋予一个故障概率,然后用蒙特卡洛抽样生成一批场景集。如果想要更强的解释性,也可以用“选择性检修”思路,构造几个典型断线场景——比如支路1-2断线、支路6-7断线、多条支路同时断开。这个项目采用的是概率场景加典型场景混合的方式,有一部分场景用于模型训练,另一部分用于结果稳健性检验。
3.3 预布局阶段的实现要点
预布局模型的决策量是储能在哪些节点“待命”。候选节点通常要人工筛选:变电站出口节点、联络开关两端节点、重要负荷集中区节点是优先对象。全部33个节点都放开让储能跑虽然可行,但候选节点太多会极大增加二进制变量数量,拖慢求解速度。
我用YALMIP定义预布局变量的典型代码如下:
% 定义储能位置变量:Nc 为候选节点数量,Ne 为储能台数 X = binvar(Nc, Ne, 'full'); % 每个储能只能部署在一个节点 Constraints = [Constraints, sum(X,1) == 1]; % 每个节点最多部署一台储能 Constraints = [Constraints, sum(X,2) <= 1]; % 储能初始SOC设为一个连续变量,介于上下限之间 E0 = sdpvar(Ne,1, 'full'); Constraints = [Constraints, soc_min*E_rated <= E0 <= soc_max*E_rated];注意这里的X其实是一个二维分配矩阵,X(i,j)=1表示第j台储能部署在候选节点i。第二阶段的所有场景都要用到这个X,所以预布局模型和调度模型之间的变量传递,是通过函数参数完成的,而不是简单的全局变量复制。
3.4 动态调度阶段的实现要点
第二阶段动态调度在给定预布局和故障场景后执行。核心变量包括各时段的充放电功率、储能位置指示变量、支路潮流、电压平方值、负荷恢复比例和开关状态。时段划分上,我通常取24个时段,每个时段1小时,模拟灾后一天的恢复过程。
支路潮流和节点电压的约束代码大概是这样的:
for t = 1:T % 节点功率平衡:注入等于负荷与储能功率之和 Constraints = [Constraints, ... sum(P_inject(:,t),1) == P_load(:,t) .* alpha(:,t) ... + p_ch(:,t) - p_dis(:,t)]; % 电压降落约束,使用线性DistFlow近似(调试阶段) for b = 1:N_branch i = branchData(b, 1); j = branchData(b, 2); Constraints = [Constraints, ... V(j,t) == V(i,t) - 2*(r(b)*P_branch(b,t) + x(b)*Q_branch(b,t))]; end % 开关状态与潮流上下限的耦合 Constraints = [Constraints, -M*z(b,t) <= P_branch(b,t) <= M*z(b,t)]; end这里有一个重要的写作建议:第一版代码可以先跑线性化DistFlow,确保整个调度框架逻辑正确、结果满足直觉,然后再加入二阶锥约束做精确求解。不要一上来就上全精度模型,否则出问题时你分不清是建模错误、求解器数值问题还是场景本身无解。
动态调度里储能移动行为的建模,是这个项目里最考验逻辑的部分。我的做法是给每一台储能定义一组位置指示变量loc_ess(i,t),t时刻只能在一个节点。t-1时刻在节点m、t时刻在节点n,如果m≠n,就认为储能在这两个时段之间发生了移动,此时该时段不允许充放电。移动是否可行,再单独用一个移动时间矩阵T_travel(m,n)去约束,T_travel大于步长时表示一次移动需要跨越多个时段,期间储能不能接入配网。
3.5 求解配置与结果可视化
求解配置有两个地方值得注意。一是要显式设置MIPGap,像我一般是预留一个较小的gap,比如1e-2,既能保证结果质量又可以显著缩短求解时间。二是要对不同的目标函数选择不同的求解器参数,预布局阶段因为含大量二进制变量,会更依赖MIP求解能力。
结果可视化部分通常包括三张图:第一张是预布局结果图,把储能部署位置标注在IEEE33节点拓扑图上;第二张是各时段储能SOC变化曲线;第三张是失负荷量与负荷恢复比例的时序曲线。如果做了多场景对比,还可以叠加一条不同场景下的失负荷柱状图,用来分析部署策略的稳健性。
4. 实际调试与问题排查记录
4.1 常见报错与处理方案速查
| 错误现象 | 可能原因 | 解决方案 |
|---|---|---|
| YALMIP提示“No suitable solver” | 建模类型超出了指定求解器支持范围 | 确认求解器是Gurobi或CPLEX,且安装了对应的Matlab接口 |
| 求解器返回NaN或不收敛 | 数值尺度问题,电压标幺值和功率标幺值混用 | 统一使用标幺值,Big-M别取巨值,优先用100或200 |
| 目标函数恒为0 | 约束构建错位,负荷恢复比例变量没有关联到潮流的负荷项 | 检查功率平衡方程中是否乘了alpha向量 |
| 预布局位置全挤在同一个节点 | 缺少“每节点最多一台储能”的约束 | 补上sum(X,2)<=1的约束 |
| 某场景下无可行解 | 孤岛约束与开关状态冲突 | 检查辐射状约束和连通性约束,先试放开部分线路检修路由限制 |
4.2 数值问题:Big-M取值与松弛间隙
Big-M在开关状态与潮流上下限耦合时是绕不开的。M取多少大有讲究。取得太小,比如M小于实际潮流峰值,会错误地截断可行域;取得太大,比如上万,又会引入大量数值噪声,导致求解器精度下降。更稳妥的做法不是对所有支路统一用一个M,而是每条支路根据自身载流能力和电压水平分别设置M值,通常取该支路潮流上限的1.1到1.5倍即可。
二阶锥松弛在大多数运行点上表现很好,但偶尔也会出现松弛间隙过大的情况,这时候最优解可能是“物理不可实现”的。调试时可以打印松弛间隙报表,如果间隙超过1%,优先检查是不是负荷恢复比例变量造成的不收敛耦合,再决定是否增加罚项强迫间隙收紧。
4.3 场景数量与计算量怎么平衡
场景法是两阶段随机规划最直接的落地方式,但场景数量上去之后,计算时间是指数增长的。我做过一个对比:10个场景的模型大概两三分钟出结果,30个场景就要半小时以上,100个场景基本要按小时算。而在实际工程中,预布局决策真正关心的只是少数几个典型工况,强行加一百个场景并不会成比例提升解质量。
比较实用的做法是先跑5个代表性场景,快速定位模型问题,确认结果合理后再扩大到15个场景做正式实验。如果想做得更严谨,可以用样本均值近似理论,画一张“场景数-目标函数值”的收敛曲线,选目标值趋于平稳的最小场景数量即可。
4.4 参数敏感性分析:哪些参数最影响结果
移动储能容量、移动速度、候选节点集合、负荷权重,这四个参数对最终结果的敏感度最高。储能容量直接影响预布局解的空间分布,容量越大越倾向于集中布置,因为单台储能就能覆盖更多负荷;容量小时反而要分散开覆盖更多节点。移动时间约束越严苛,预布局结果越偏向于靠近潜在故障点的节点,说明“时间窗”比“容量”很多时候更是瓶颈。
负荷权重的影响也很明显。把权重集中在少数重要负荷节点上,模型会毫不犹豫牺牲一般负荷去保重点负荷。这一点在使用中要特别小心,权重设置必须和管理层确认,否则调度结果在工程上过不了评审。
5. 经验总结与扩展方向
5.1 实操中踩过的几个坑
第一,两阶段模型中第一阶段的路径决策和第二阶段调度之间的时序衔接。早期我把储能初始位置设成了连续变量,结果优化出一个“储能悬浮在两点之间”的荒谬解,改成二进制分配矩阵之后问题才消失。任何涉及离散物理状态的东西,必须用整数变量,这是一个铁律。
第二,场景生成要保留故障线路的清单。动态调度阶段必须知道哪些支路是断开的、断开的时段有多长,否则拓扑约束会与潮流约束冲突。不少初学者把场景简化成“某条线路不可用”,但没有把对应支路的z变量固定在0,结果优化器把断线支路又“合上”了,跑出来的恢复方案完全失去意义。
第三,移动储能的SOC初值设定会影响预布局结果。多场景下如果所有场景共享同一套储能SOC初始值,预布局结果相对稳健;如果允许每个场景各自调整初值,模型会“作弊”,算出的期望失负荷会偏低,看起来更漂亮,但实际上做不到。做实验时要明确在报告中说明,两种设定方法对应不同的物理假设,没有对错但必须前后一致。
5.2 模型可以往哪些方向扩展
这个项目做的是储能侧的单向优化,后续有几个很好的扩展出口。一是考虑配电网重构与储能调度的联合优化,断线发生后不只是储能移动,还可以通过联络开关重构出一个局部“孤岛微网”,让储能带动更多负荷。二是引入交通网信息,把储能车的行驶距离、路口等待时间真正建模进去,形成交通-配电耦合的联合优化,这在城市配电网里很有实际意义。三是如果数据量足够,可以用分布式鲁棒优化替代场景法,把场景概率本身的不确定性也纳入优化范围,决策会更保守、更抗扰动。
另外,计算性能优化方面可以尝试Benders分解,把第一阶段预布局和第二阶段多场景调度拆成主子问题交替迭代。我在一个40节点的扩展案例上对比过,分解后同等Gap下的求解速度能提升三到五倍,这在大型网络中是值得投入时间的方向。
最后分享一个自己做这类项目的心得:不要一上来就追求模型“大而全”,先把线性化模型跑通,再逐步加入二阶锥精度、移动时序、拓扑重构这些细节。每加一个模块,就重新跑一遍全部的测试场景,确保新模块没有破坏旧结果。模型复杂度的增加一定是渐进式的,想一口吃成胖子,最后基本都要返工重来。这个项目的代码结构和思路,可以当作一个随取随用的起点,重点是把预布局和动态调度之间的信息流理解透,你就能在它的基础上做出属于自己的扩展版本。