做配电网优化调度的同学,对IEEE33节点系统应该都不陌生。这个经典的33节点辐射型配电测试系统,几乎成了配电网潮流计算、重构、故障恢复研究的“标准试验田”。我最近在复现一套移动储能预布局与动态调度的完整策略,标题里写着“完美复现”,实际动手才发现,论文里的数学公式和代码落地之间隔着不少坑。这个项目要解决的问题很明确:极端灾害发生前,怎么把有限的移动储能资源提前放到最关键的节点;灾害发生后,又怎么根据实时故障情况和负荷需求,动态调度这些储能去保供电。整套策略面向的是配电网韧性提升,属于近些年电力系统领域被反复讨论的热点问题。内容适合刚入门配电网韧性研究的学生,也适合想用IEEE33节点验证自己算法的工程师。
1. 问题拆解:为什么是移动储能,为什么是预布局
1.1 配电网韧性到底在说什么
韧性这个概念,在电力系统里已经讨论了很多年,核心就一句话:系统对极端扰动(小概率、高影响事件)的抵御、吸收、适应和恢复能力。
常规配电网可靠性评估研究的是N-1这类大概率故障,重点是平均停电时间;韧性研究面对的则是台风过境、冰灾、暴雨这类会让大量线路同时断掉的极端事件。这类事件的特点是:故障台次多、空间上成片、时间上持续。更重要的是,它的发生往往有前期征兆——气象预报能提前24到72小时发出预警。这个“预警窗口”正是移动储能预布局能够发挥作用的时间窗口。
我见过不少研究者把韧性和可靠性混为一谈,其实处理思路完全不同。可靠性研究通常假设系统稳态运行,故障是小概率事件;韧性研究必须考虑灾难的动态演化过程,比如配电网被划分为多个孤岛、线路分段隔离、负荷逐步切除或恢复。这些场景下,传统的“计算潮流—校核安全—给出方案”的静态流程是行不通的,必须引入时序调度,让电源资源跟着故障动态走。
1.2 移动储能相比固定储能的三个优势
移动储能(Mobile Energy Storage,简称MESS)并不是一个新概念,但用在韧性提升上是近几年的研究热点,原因很直接。
第一个优势是“资源跟着故障走”。固定储能只能安装在规划好的位置,一旦故障点不在储能附近,就需要配电线路重构把电送过去。但在极端灾害下,很多联络线路本身也处于故障状态,固定储能可能变成一个“孤岛上的电源”,只能给周边一小片负荷供电。移动储能则可以根据实际故障分布,在灾前预置、灾中转场,把能量送到最需要的节点。
第二个优势是投资效率。固定储能为了应对极端场景,必须按最恶劣情况配置容量,日常利用率很低。移动储能平时还可以用于削峰填谷、需求响应甚至跨区域调峰,资源池共享,算总账划算得多。
第三个优势是时间维度。固定储能的容量一旦确定,灾中只能靠自身SOC(荷电状态)硬扛;移动储能可以在灾害持续期间回到有电的节点补电,再去新的故障区域支援,相当于给系统增加了一条“能量补给线”。这一点对持续数天的极端灾害尤其重要。
1.3 IEEE33节点系统怎么支撑这个研究
IEEE33节点是配电网研究绕不开的标准算例。它的基本参数如下表:
| 特性 | 数值 |
|---|---|
| 节点数 | 33 |
| 支路数 | 32 |
| 联络开关数 | 5(常开) |
| 基准电压 | 12.66 kV |
| 基准功率 | 10 MVA |
| 总有功负荷 | 3715 kW |
| 总无功负荷 | 2300 kvar |
这套系统的特点是:电压等级贴近实际中压配网,负荷分布不均衡(末端节点电压偏低),且带联络开关可以做重构。所以无论做故障恢复、网络重构还是储能接入,都能找到清晰的研究问题。模拟极端故障时,只需要把某些支路置为断开,系统就自然出现孤岛,这时候就能看到移动储能的价值了。
2. 两层决策框架:先预布局,再动态调度
2.1 阶段一:灾前预布局在做什么
预布局可以理解为一个“基于预测的资源配置问题”。在气象灾害预报发出后、灾害实际到达前,系统调度中心有一个时间窗口(通常6到24小时),需要决定三件事:
- 移动储能车部署到哪些节点;
- 每辆车配置多少可用容量;
- 接入的节点是否需要提前切换出孤岛运行方式。
目标是在所有可能的故障场景下,最小化预期的负荷损失,或者最大化关键负荷(医院、通信基站、水泵站)的供电保障率。
这里有个关键点:预布局阶段并不知道故障的精确位置,只知道概率分布或典型场景集合。所以这个阶段本质上是一个随机优化或鲁棒优化问题。论文里常见做法是给定若干典型灾害场景,每个场景对应一组故障线路,然后最小化所有场景下的期望损失。
我在复现时发现,如果把每个场景都当成确定性模型来解,再求平均,计算量并不大,但结果会偏向“中庸”;而如果采用两阶段随机规划——预布局对应第一阶段、动态调度对应第二阶段——就需要Benders分解或改进的L-shaped方法,实现难度会上升一个台阶。初学者建议先跑通确定性场景,再扩展随机场景。
2.2 阶段二:动态调度在做什么
当灾害真正发生,故障线路信息逐步明确后,动态调度阶段开始发挥作用。
这个阶段是一个滚动更新的时序优化问题。典型时间尺度是小时级,整个灾中过程持续12到48小时。在每个时段t,调度中心要根据当时的系统拓扑、线路故障状态、节点负荷预测和储能车的当前位置与SOC,决定:
- 每辆移动储能车停留在当前节点还是转移到其他节点;
- 停留时是充电、放电还是待机;
- 每个节点的切负荷量;
- 联络开关和分段开关的开合状态(如果考虑重构)。
这里最重要的事情是“移动”建模。储能车是空间上可移动的资源,把它和配电网时序潮流放在同一个优化模型里,就构成了一个路径-调度耦合问题,这也是这个项目和单纯储能调度最大的区别。
2.3 移动与接入的时间逻辑:容易被忽略的约束
移动储能从一个节点转到另一个节点,不是瞬时的,需要考虑转移时间。假设储能车在各节点之间移动所需的时间可以用离散时段折算,模型就会变成包含时间窗的路径规划与调度耦合问题。
我复现时踩过的一个坑是:很多论文把“移动”建模成离散时间下节点间的跳转,实际代码中对应的就是二元变量。如果不加“同一时刻一辆车最多只能在一个节点”的约束,求解器给出的结果可能是“瞬移”的——上一小时在节点5,下一小时就到节点20,中间没有任何转移时间和能量消耗。
正确做法是加入形如:
- 车在t时刻要么停留在某个节点,要么正在转移,二者互斥;
- 转移会消耗一个或多个时段;
- SOC变化要考虑转移过程的能量消耗,虽然多数研究模型会忽略这部分,但在实际工程中建议显式建模。
这种约束写起来不复杂,但漏掉的后果很严重:优化出的调度方案在实际中根本无法执行。
3. 数学模型:目标函数、约束条件与参数设置
3.1 目标函数怎么定
这个项目中,目标函数的选择直接决定模型是线性、混合整数线性还是非线性。
最常见的设定是“最小化系统时变加权切负荷量”:
min sum_t sum_i w_i * P_i^shed(t)
其中 w_i 是负荷权重,P_i^shed(t) 是节点i在t时段被切除的有功负荷。对于关键负荷,w可以设到普通负荷的10倍以上,这样即使普通负荷全被切掉,也要保住关键负荷。
有的论文还会加一个快速性指标,比如“负荷恢复速率”。这个指标定义成单位时间内恢复的负荷量,韧性曲线中每个时刻的斜率就是恢复速率。把这个目标加入多目标框架后,模型变成双目标优化,可以用加权求和或帕累托前沿分析。
我在复现时发现,双目标虽然论文里好看,但实际求解调试难度大得多。如果你只是想先跑通流程,单目标最小化加权切负荷就够了,后面再加目标。不要一上来就追求大而全,先把主链跑通。
3.2 配电网潮流约束:DistFlow方程与线性化
配电网的潮流计算和输电网不一样。辐射状结构下最常用的是DistFlow方程:
P_ij(t) - r_ij * l_ij(t) - sum P_jk(t) = P_j^load(t) - P_j^gen(t)
Q_ij(t) - x_ij * l_ij(t) - sum Q_jk(t) = Q_j^load(t) - Q_j^gen(t)
U_j(t) = U_i(t) - 2(r_ij * P_ij(t) + x_ij * Q_ij(t)) + (r_ij^2 + x_ij^2) * l_ij(t)
这三个方程里面有二次项和电流幅值变量,直接求解是非线性规划,规模大一点就非常慢。工程上几乎都做线性化处理:
- 忽略线路损耗项 r_ij * l_ij,保留电压降公式;
- 电压幅值用标幺值后,约等于1,二次项可以近似简化;
- 最终得到线性的DistFlow。
这样处理后,整个模型就变成混合整数线性规划(MILP),可以用通用求解器直接解。这个线性化的精度对IEEE33节点来说完全够用,因为12.66kV配网的电压偏差一般不会超过±6%,高精度非线性模型带来的收益微乎其微。
3.3 储能SOC约束、功率约束与转移时序约束
储能本身的约束是整个模型的核心,列举一下我代码里实际用到的关键约束:
- 充放电功率约束:0 <= P_ch(t) <= P_ch_max,0 <= P_dch(t) <= P_dch_max,且同一时段不能同时充放电。这里需要一个二元变量对充放电状态进行互斥建模;
- SOC更新方程:SOC(t+1) = SOC(t) + eta_ch * P_ch(t) * delta_t - P_dch(t) * delta_t / eta_dch;
- SOC上下限:SOC_min <= SOC(t) <= SOC_max;
- 起始SOC:灾前预布局结束时,储能应该已经被充到较高的水平,通常设为SOC_max的80%到100%;
- 转移约束:从一个节点转移到另一个节点需要消耗一定时间,转移过程中储能既不充电也不放电;
- 容量与数量约束:调度中心可用的移动储能车数量有限,预布局最多使用N辆车,每辆车可携带的额定容量固定。
参数方面,我用的典型配置是:额定容量500 kWh、额定功率100 kW、充放电效率0.92、SOC上下限0.1到0.9、转移时间按相邻节点1个时段计。
当然这些参数在不同论文里会不一样,你要复现别人的结果,第一步就是去找原文的参数表,而不是套用我的数值。参数对了,结果才谈得上“复现”。
4. MATLAB代码实现:从数据到结果的可复现路径
4.1 代码架构与文件组织
拿到一个“完美复现”项目,我习惯先把代码文件按功能拆开,不追求一个脚本跑到底。这个项目的核心文件组织如下:
| 文件 | 职责 |
|---|---|
main.m | 主程序入口,设置参数并调用各模块 |
load_case33.m | 读取IEEE33节点数据,生成线路参数结构体 |
generate_scenarios.m | 生成灾害故障场景集合 |
stage1_preposition.m | 求解灾前预布局模型 |
stage2_dispatch.m | 求解灾中动态调度模型 |
plot_voltage.m | 绘制各时段电压分布 |
plot_soc_route.m | 绘制储能SOC曲线与移动路径 |
这样划分的好处是:测试某个模块时不需要跑完整流程,改参数只改对应文件。我见过不少同学把全部代码塞进一个几百行的脚本里,调试的时候非常痛苦。模块化在科研代码里同样重要。
4.2 数据准备:IEEE33节点怎么加载
如果用的是标准配电网数据格式,可以直接读现成算例;但更常见的是自己准备两个矩阵:
branch(:, 1:4) = [bus_i, bus_j, r_pu, x_pu]load(:, 1:3) = [bus_i, P_kW, Q_kvar]
需要特别注意的是单位。IEEE33节点的原始数据在很多论文里是以kW和kvar给出的,基准功率取10MVA时,标幺化后负荷值是0.03715到0.2左右的量级。我就犯过把kW当成MW的错,导致潮流结果全部偏小,电压几乎不跌落,结果完全失真。
另外,配电网数据里支路顺序往往从节点1开始,节点0是根节点还是节点1是根节点,不同版本数据不一样。建议先把数据导进Matlab,直接用潮流函数验算一遍,确认总负荷和网损跟IEEE33基准值一致,再继续后面的优化。数据基础没打好,后面所有结果都不可信。
4.3 求解器的选择与YALMIP建模
整个模型是MILP,最方便的建模方式是用YALMIP这个MATLAB建模工具箱,求解器可以用开源的混合整数线性规划求解器,也可以用商业求解器。我这里不展开具体品牌,只说结论:对于IEEE33节点这种小规模系统,开源求解器完全够用;但如果你以后要扩展到上百节点的实际馈线,商业求解器在整数变量超过几千个时优势明显。
YALMIP的关键步骤我简化描述一下:
- 用
sdpvar定义连续变量(节点注入功率、SOC、切负荷量); - 用
binvar定义二元变量(储能位置、转移状态、充放电状态); - 用
optimize(constraints, objective, options)求解; - 用
value(variable)取结果。
这里有个经验:二元变量的数量直接决定求解时间。预布局阶段如果有3辆储能车,每个时段每辆车在所有节点上的位置变量就是 3 × T × 33 个二元变量,T取24小时就是2376个。这个规模对MILP来说是中等偏小,但如果不加合理的big-M约束,求解器会做大量无效分支,时间可能从几分钟膨胀到一小时以上。
4.4 核心约束的代码写法与技巧
我看过很多版本的复现代码,同一套模型,不同人的求解速度和稳定性差别很大,问题基本都出在约束的写法上。
以“不能同时充放电”为例,最直接的写法是两个变量各配一个二元开关:
u_ch = binvar(T, 1); u_dch = binvar(T, 1); Constraints = [0 <= P_ch <= P_ch_max * u_ch]; Constraints = [0 <= P_dch <= P_dch_max * u_dch]; Constraints = [u_ch + u_dch <= 1];这种写法的缺点是引入了两个额外的二元变量,如果时段多、储能数量多,变量数会翻倍。另一种技巧是只用一个二元变量,令其等于1表示放电、0表示充电,充电功率用一个负的净功率变量表示。这样变量数少一半,求解速度能提升10%到30%。
再比如节点电压约束,IEEE33节点如果正常运行,很多节点电压偏低,一旦灾害状况下切负荷,电压约束容易过约束导致无解。我的处理方式是把电压下限放宽到0.85 p.u.(论文中常见范围),并在目标函数里尽量保留关键负荷,这样既能保证可解性,又不会偏离物理规律太远。
4.5 结果可视化与复现校验
复现完一个项目,最关键的验证环节是对照论文里的图。
一般需要画三张图:
- 韧性曲线(横轴时间,纵轴累计供电负荷或累计电量)。这个曲线能直观说明策略的恢复速度;
- 电压分布图。选几个关键时段(灾害冲击时刻、储能接入时刻、恢复末期),画出33个节点的电压曲线,看是否和论文趋势一致;
- 储能路径图。把每个时段储能车所在的节点画出来,形成一条移动轨迹,看是否符合预设的故障场景逻辑。
我第一次复现时,韧性曲线和论文对不上,反复排查后发现是故障场景生成时的随机种子问题。论文里可能用的是特定场景集合,我重新生成的场景集合分布不同,结果自然不同。后来我改成从原文描述中推断故障线路范围,结果就基本对齐了。所以复现时要留意:场景集合的差异会直接改变曲线形态,这不一定是代码错误。
5. 复现过程中的常见坑与排查清单
5.1 数值问题:标幺化、单位、大M取值
这一类问题最隐蔽,因为程序不报错,但结果错得离谱。
最典型的是单位错误,前面说过kW和MW的问题。还有一个高频问题是大M取值。大M取得太大,会降低MILP的数值稳定性;取得太小,可能把可行解排除掉。一个实用的原则是:M取对应变量的最大可能量级的5到10倍。比如储能传输功率上限是0.1 p.u.,M取1到2就够了,不用取1e6。
5.2 求解速度慢:如何下手
碰到求解慢,先不要怀疑求解器质量问题,按这个顺序排查:
- 移除所有非必需的约束,看是否瞬间变快,这就说明约束写法导致求解器分支困难;
- 检查是否有冗余的二元变量,能不能合并;
- 把时间粒度从1小时放宽到2小时,看结果趋势是否不变,确认模型基本正确后再提高时间分辨率;
- 给求解器设置合理的MIP gap(比如1%或2%),绝大多数工程场景根本不需要严格最优解。
5.3 常见错误速查表
| 现象 | 可能原因 | 处理方式 |
|---|---|---|
| 电压全部接近1,切负荷为0 | 负荷单位错误,kW当成MW | 重新标幺化,核对基准功率 |
| 储能路径出现“瞬移” | 缺少转移时间约束 | 加入移动耗时与位置互斥约束 |
| 求解几小时不收敛 | 大M取值过大或二元变量过多 | 减小M,合并充放电状态变量 |
| 预布局结果和动态调度结果脱节 | 两个阶段参数或场景不统一 | 统一算例数据文件和参数表 |
| SOC出现负值 | 忽略了转移过程耗能或起始SOC偏低 | 检查SOC更新方程与转移时间 |
5.4 关于“完美复现”的个人建议
要说“完美复现”,我的体会是:论文复现要区分两种目标。
如果你是为了写论文、验证自己的新算法,那不需要跟原文结果一字不差,只要保证在同一个算例上,你的方法相对基准方法有提升即可。此时重点是控制变量,让对比公平。
如果你是为了学习某个方法,或者为了在代码基础上做二次开发,那就要仔细核对每个参数,甚至联系作者要数据文件。IEEE33节点这类标准算例的好处是公开数据多,你可以交叉验证。
我还想特别提醒一点:很多论文里的图是“挑过”的。同一个模型,换一组故障场景,结果曲线形状可能完全不同。复现时不要死磕曲线形状,要看结论趋势是否一致。
最后分享一条自己的经验:这类韧性优化项目,真正花时间的往往不是优化模型本身,而是把潮流数据、故障场景、储能参数和时序逻辑对齐。我一开始直接在完整模型上调试,出了错完全不知道是哪个模块的问题。后来改成“先跑通固定储能、再开启移动约束、最后加转移时间”的三步走,每步都验证结果,效率反而高很多。
如果你现在正要开始复现类似的工作,我建议你也先别急着堆代码,花半天时间把IEEE33节点的数据在Matlab里算一遍常规潮流,把基态电压、网损和节点注入功率都记在纸上。所有的优化参数都从这份“基准校核表”出发,后面的坑会少一半。这套流程跑通之后,你再去扩展实际的馈线拓扑、接入更多储能资源、甚至换用鲁棒优化的框架,都会有一个扎实的起点。