移动储能预布局与动态调度的复现,说实话刚接到这个题目时,我的第一反应是:这不就是把几台储能车提前放到配电网的关键节点,等故障发生了再动态调配一下嘛,听起来并不难。真正在IEEE33节点系统上动手建模我才发现,它其实是“移动储能的物理可移动属性+配电网拓扑约束+不确定故障场景”三个问题叠加起来的大规模组合优化,光是把变量层理清楚就花了不少时间。这篇文章是我从零复现这个策略的完整记录,覆盖两层优化模型的构建思路、IEEE33节点上的数学表达、Matlab+YALMIP的落地流程,以及我求解过程中踩过的那些坑。如果你正在做课程设计、毕业论文,或者想评估移动储能对配电网韧性的实际提升效果,这套框架可以直接拿来当参考地图。
文章里的代码思路和参数设置都以“能在Matlab里跑通、结果能解释”为第一目标,不追求花哨,只求每一步都经得起推敲。
1. 为什么“预布局+动态调度”比我一开始想的复杂
1.1 移动储能与固定储能在韧性场景里的根本差异
配电网韧性这个概念很宏观,落到模型里其实就是一句话:在极端故障下尽量少切负荷、尽快恢复供电。传统可靠性关注的是随机故障的频率和持续时间,而韧性关注的是低概率、高影响事件,比如台风把一条主干线吹断,让系统分裂成多个孤岛,配电网要靠自身手段扛过最坏的那几个小时。
在这个背景下,固定储能和移动储能的定位完全不同。固定储能只能从安装节点发力,离故障点远了就鞭长莫及,而且一旦它所在的支路被切除,整台储能跟着失联。移动储能最大的价值在于物理可移动:灾前可以停在预测关键节点待命,灾后可以根据实际断线情况重新规划路径,相当于把“备用电源”变成了一支可以灵活调度的应急队伍。但这份灵活是有代价的——优化模型里多了一组空间位置决策,问题从连续优化变成了混合整数规划,难度直接上了一个台阶。
1.2 时间线上,预布局和动态调度分别解决什么
灾害过程通常被划分为灾前预警期、灾中扰动期和灾后恢复期。预布局做在灾前,依靠气象预报和线路脆弱性评估,在灾害真正切断线路之前把储能车放到最可能发挥作用的位置;动态调度做在灾中和灾后,根据已经发生的断线状态,实时决定储能车往哪走、在哪个节点出力、充多少放多少。
这两个阶段不是独立的。预布局的决策质量直接决定动态调度的可行空间,而动态调度的效果反过来验证预布局是否合理。我建模时统一把灾害时间离散成24个时隙,每个时隙1小时,预布局决策发生在t=0,动态调度从t=1开始一路滚动到t=24。时间轴一旦定下来,后面的变量下标、约束写法全部围绕它展开,后面调模型时要改代价很大,所以开始前最好把时间粒度想清楚。
1.3 两阶段决策耦合,是模型难度的真正来源
如果只做单阶段调度,把移动储能当成普通分布式电源放进潮流模型,问题会简单很多,但会忽略一个关键事实:储能车从A节点挪到B节点需要时间,而且不同故障场景下可用的移动路径不一样。这两点直接导致“预布局位置”和“动态位置”之间存在强制耦合——第一时隙必须停在预布局节点,之后的位置变化必须遵守时空可达性。
这种两层决策嵌套的结构,最自然的数学表达是两阶段优化:第一阶段变量在每个故障场景里是公共的,第二阶段变量在场景间独立。两者通过“初始位置一致”这条耦合约束绑在一起。我在后面的模型里用的就是这个框架,它既保留了预布局的“全局性”,又给了动态调度足够的自由空间。
2. IEEE33节点上,怎样把“韧性提升”翻译成可计算的模型
2.1 算例数据、故障场景和基础假设
IEEE33节点系统是配电网领域常用的放射状测试算例:一个变电站根节点接32条支路,5个常开联络开关,总负荷约3.715MW加2.3Mvar,基准电压12.66kV。这个规模不大不小,既适合手推潮流验证,又能撑起带多场景、多时隙的优化模型,所以学术界做配电网韧性研究经常拿它当起点。
复现时最关键的是统一基准值。我习惯把基准功率取100MVA,基准电压取12.66kV,所有阻抗、功率、电压都换算成标幺值参与运算。节点0作为变电站根节点,有功注入能力我设成3MW,这样既模拟了上级电网的供电上限,也避免优化器把所有负荷都甩给变电站。线路参数方面,IEEE33的标准数据网上到处都能找到,但注意不同来源的版本可能略有差异,复现时最好固定一个版本。
移动储能的参数我按常见工程配置取,这里是一张参考表:
| 参数 | 取值 | 说明 |
|---|---|---|
| MESS容量 | 500 kWh | 单台储能车电池容量 |
| 最大充放电功率 | 100 kW | 功率上限,充电与放电共用 |
| 初始SOC | 0.5 | 预布局出发时的荷电状态 |
| SOC上下限 | 0.1 / 0.9 | 避免过充过放 |
| 移动速度 | 折算为时隙数 | 节点间迁移耗时按拓扑距离估算 |
故障场景我用蒙特卡洛方式生成:给每条架空线路一个受损概率,比如主干线0.08、分支线0.05,抽样得到2到4条断线的组合,再按概率归一化保留8个左右典型场景。场景太多求解会变慢,太少又覆盖不了典型故障形态,8个是我试下来比较平衡的数量。
2.2 从预布局到动态调度的变量设计
我把变量分成三层,这个分层直接对应后面的Matlab代码结构。
第一层是预布局变量:每台MESS初始停在哪个节点,用一个二进制变量x_pre表示。第二层是动态调度变量:每个时隙、每个场景下MESS在哪个节点、是否正在路上、充放电功率多大、当前SOC是多少。第三层是网络运行变量:每时隙每条线路的有功无功、节点电压幅值平方、线路开断状态、各节点切负荷比例。
变量命名看起来多,但每层都有明确归属。实践中最容易乱的是MESS的位置变量和充放电功率变量的对应关系——放电功率应该只在该MESS接入的节点非零,这个耦合要靠大M约束实现,后面细说。
预布局和动态调度之间的耦合约束我写成:第一时隙的MESS位置必须等于预布局位置。换句话说,灾害发生的第一刻,储能车已经在预布局节点就位。如果允许第一时隙移动,那就变成了“预布局候选点”,与原题目的“预布局”含义有偏差,复现时要注意这里。
2.3 以加权切负荷最小化的目标函数搭建
韧性目标我采用加权切负荷最小化,表达式写成:
目标 = 场景概率之和 * 时隙权重之和 * 节点权重之和 * 节点有功负荷 * 切负荷比例
节点权重体现负荷重要性:医院、通信枢纽、应急指挥这类关键负荷权重取5,普通居民负荷取1,可中断工业负荷取0.5。时隙权重体现“尽快恢复”的韧性偏好,我设置灾害后前8个小时权重为2,之后降为1,这样优化器会优先保证早期时段的供电恢复。
在目标函数里,MESS自身的运行成本我选择放在约束条件中处理——只要充放电功率不超过上下限,不产生额外罚项。这样做的原因是核心问题聚焦在“韧性提升”上,如果目标里同时塞进储能磨损成本、网络重构代价,会把切负荷这个主目标稀释掉,结果解释起来反而麻烦。真要在工程里考虑经济性,可以把它作为多目标或者惩罚项逐步加回来。
3. 核心约束全拆解:潮流、时空轨迹与荷电状态
3.1 DistFlow线性化潮流约束
配电网潮流我用线性化的DistFlow方程,这是配电网优化里最主流的处理方式。完整版DistFlow是非线性的,含线路损耗项和电流平方项,直接丢给求解器求解非常吃力。我的做法是忽略线路损耗项,把电压降关系近似为线性。
线性化后的节点有功平衡长这样:上级变电站注入 + 分布式电源出力 + MESS放电 - MESS充电 - 流入下游支路的有功 = 节点负荷 * (1 - 切负荷比例)。无功平衡类似,MESS假设不参与无功调节,只影响有功。
电压约束我采用简化的平方形式:节点电压平方 = 上级节点电压平方 - 2*(支路电阻有功 + 支路电抗无功)。电压幅值上下限设定为0.95到1.05pu。这套线性化在12.66kV系统中误差一般不超过2%,对韧性评估这种偏宏观的决策场景完全够用。
3.2 MESS“移动不能瞬移”的时空约束
MESS的时空迁移约束是整个模型里最容易写错的部分。它要表达三件事:同一时刻一台MESS只能在一个地方;从一个节点到另一个节点需要消耗时间;在路上的时候不能参与充放电。
我定义了三组二值变量:位置变量x_pos表示时刻t在节点n;出发变量y_go表示t时刻从a出发前往b;到达变量y_arr表示t时刻到达b。核心约束是:若t时刻从a出发去b,则t时刻必须在a,且在t+travel(a,b)时刻必须到达b。travel表是根据拓扑距离预先算好的整数时隙数,比如从节点8到节点21需要2个时隙。
再配合“任意时隙位置状态与在途状态之和等于1”这一条约束,就完整表达了MESS的时空运动。这套建模思想本质上是车辆路径问题里的时空网络流,理解它比死记代码重要得多。后续要扩展移动充电车、抢修队路径规划,都是同一套路。
3.3 SOC与充放电功率约束,以及容易被忽略的工程细节
SOC递推约束按照物理过程写:当前SOC=上一时隙SOC + 充电效率充电功率时隙时长/容量 - 放电功率时隙时长/(放电效率容量)。充电效率和放电效率我分别取0.95和0.92,不统一取值,否则能量不守恒。
充放电功率要限制:只有MESS在某个节点时,该节点上的充放电功率才能非零,写作功率上限乘位置变量。另外必须要加“不能同时充放电”的约束。这一点很多人忽略,结果优化器会在同一个节点一边充一边放,“白嫖”能量平衡,导致结果完全不合理。
还有一个工程细节:同一节点同一时隙接入的MESS台数要限制,我设成不超过2台。如果不加,优化器会把所有储能车堆到单一重要负荷点,虽然切负荷指标好看,但不符合实际——应急车集中停放既浪费覆盖范围,也存在安全隐患。
3.4 故障场景、网络重构与MESS路径的耦合
极端故障下部分线路强制开断,这个状态用场景参数固定。联络开关是否闭合则作为优化变量,和MESS路径形成耦合。
线路通断和功率流动的耦合用大M约束表达:线路闭合时功率可以流动且不超过上限,线路断开时功率必须为零。网络重构后要保证系统依然是放射状连通网络,完整连通性约束在数学上很繁琐,IEEE33节点规模不大,我采用了一个工程化简化:先用生成树思路的约束条件保证连通性,再在结果验证环节用深度优先搜索检查最终拓扑是否满足要求,不满足再回头加约束。
MESS移动路径和故障场景的耦合容易被忽略——如果储能车要经过的线路恰好断了,那这条路径就不成立。我复现时直接把MESS移动限制在同一个连通分区内,通过预计算的场景分区表做掩码,代码简单且不容易出现解释不了的结果。
4. 求解上的几个选择:场景法、大M和求解器
4.1 为什么先用场景法变成一个大MILP
严格的两阶段鲁棒优化可以用列与约束生成算法(C&CG)迭代求解,但这套算法对初学者不友好,代码量大,而且收敛判据设置不好很容易出现次优解。我复现时采用的是场景法:枚举或抽样足够多的典型故障场景,每个场景带概率,把两阶段问题展开成一个大规模确定性混合整数线性规划一次求解。
场景法的本质是“用有限场景近似不确定性”。它的优点是一次求解、结果可解释、代码量可控;缺点是只能覆盖你枚举出来的场景,不能像鲁棒优化那样保证所有未枚举场景的安全。对于IEEE33节点上的课程设计和论文复现来说,场景法是最合理的起点。先把场景法跑通,再去研究C&CG,心里对模型结构和求解逻辑会有底气得多。
4.2 大M怎么取值,以及求解器的实战参数
这个模型里到处是大M:线路功率上下限、功率与位置的耦合、切负荷比例上限。大M取值非常影响求解器表现,应该是MILP建模里最需要经验的地方。我的原则是:每个大M对应实际物理量纲。线路功率的大M按最大可能注入的3倍取,比如根节点最大注入3MW,那么线路大M取9;MESS功率与位置耦合的大M按最大充放电功率的2倍取。
求解器我用的是YALMIP+Gurobi的组合。YALMIP写约束直观,适合约束多、变量维度高的模型;Gurobi求解MILP性能强,学术许可免费。求解器参数方面,MIPGap设到0.001比较稳妥,如果模型太大可以放宽到0.01看趋势,再逐步收紧。
4.3 规模太大时的降载思路与实测观察
以我的复现配置为例:33节点、4台MESS、8个场景、24个时隙,模型规模大约是十几万到几十万个变量,其中整数变量两万多个。Gurobi默认参数直接跑,耗时大概几分钟到十几分钟,取决于MIPGap设置和大M松紧。
如果模型太大,我建议先砍时隙数而不是场景数——把24个时隙聚合为6个代表时隙,验证算法逻辑;逻辑验证通过后再恢复到完整时隙。另一个有效技巧是热启动:先把松弛解作为MIP start喂给求解器,通常能省掉接近一半求解时间。规模问题说到底不是“能不能解”,而是“解多少次、怎么快速迭代”,不要在第一次就跑完整配置,分步验证能省大量调参时间。
5. Matlab实现的结构与代码骨架
5.1 算例文件怎么做,数据接口如何设计
我习惯把IEEE33节点数据写在一个独立的脚本里,返回结构体d。这个脚本包含节点数、支路首末端编号、支路电阻电抗、节点有功无功负荷、联络开关列表、基准值等所有信息。另外加一个逻辑数组,记录每条线路是否易损,方便生成故障场景时按概率抽样。
数据接口设计有个原则:主程序只读结构体d,不直接接触原始数据。这样换算例时不需要改动主程序,只需要替换算例脚本,比如后面想升级到IEEE123节点,只要把数据接口对齐就行。故障场景生成函数也单独写,输入线路易损概率和场景数量,输出每个场景的断线集合。
5.2 YALMIP建模的核心代码骨架
下面这段代码是建模的核心骨架,展示了变量声明、关键约束和目标函数的YALMIP写法:
% ===== 决策变量声明 ===== x_pre = binvar(d.nb, nM, 'full'); % 预布局位置 x_pos = binvar(d.nb, nM, nS, nT, 'full'); % 动态位置 y_tr = binvar(nM, nS, nT, 'full'); % 在途标志 p_dis = sdpvar(d.nb, nM, nS, nT, 'full'); % 放电功率 p_ch = sdpvar(d.nb, nM, nS, nT, 'full'); % 充电功率 soc = sdpvar(nM, nS, nT, 'full'); % 荷电状态 r_cut = sdpvar(d.nb, nS, nT, 'full'); % 切负荷比例 Pline = sdpvar(d.nbr, nS, nT, 'full'); % 线路有功 Qline = sdpvar(d.nbr, nS, nT, 'full'); % 线路无功 V2 = sdpvar(d.nb, nS, nT, 'full'); % 电压平方 C = []; % ===== 预布局与第一时隙耦合 ===== for m = 1:nM C = [C, sum(x_pre(:,m)) == 1]; C = [C, x_pos(:,m,:,1) == x_pre(:,m)]; % 第一时隙必须在预布局位置 end % ===== SOC递推(简化示意,需按场景/时隙展开)===== % soc(m,s,t+1) == soc(m,s,t) + eta_ch * sum(p_ch(:,m,s,t)) * dt / E_rate ... % - sum(p_dis(:,m,s,t)) * dt / (eta_dis * E_rate); % ===== 充放电功率与位置耦合 ===== % p_dis(:,m,s,t) <= Pmax * x_pos(:,m,s,t); % p_ch(:,m,s,t) <= Pmax * x_pos(:,m,s,t); % p_ch(:,m,s,t) + p_dis(:,m,s,t) <= Pmax; % 防止同时充放电 % ===== 求解 ===== ops = sdpsettings('solver', 'gurobi', 'verbose', 2, 'gurobi.MIPGap', 0.001); sol = optimize(C, Objective, ops);这段代码只是骨架,完整的潮流约束、线路通断约束、切负荷上下限、连通性约束等都需要逐条补全。但我想强调的是:变量定义比参数调优重要,约束结构比代码技巧重要。我写这段代码的顺序是先变量、再约束组、最后目标函数,严格按照前面模型思路的层次来,这样即使模型里有几百条约束也不会乱。
5.3 结果验证:韧性提升的“有无对比”实验
求解结束不是终点。我拿到结果至少会画三样可视化输出:系统总负荷恢复率随时间变化的曲线、各台MESS的时空轨迹图、每个场景下切负荷的空间分布。
最重要的验证是做反事实对比实验:把MESS数量设成0,跑一遍同样的模型,得到“无移动储能”时的切负荷量,然后和“有移动储能”的结果做差,得到韧性提升值。这个对比是论文里最核心的“韧性提升证据”,也是判断模型有没有BUG的试金石。
我遇到过一个典型问题:有MESS和没MESS的切负荷结果完全一样。排查后发现是切负荷惩罚权重设得太低,加上MESS容量小、位置又都被放在无关紧要的节点,模型认为用不用移动储能无所谓。所以第一次跑通后,先做敏感性分析,调整节点权重或者储能容量,如果结果方向完全不变,那就要怀疑模型本身有问题。
6. 复现路上我掉进去的坑,以及现在的对策
6.1 大M过小导致不可行、过大导致求解慢
第一次完整建模时,我图省事把大M统一取成1e6,结果一个看似规模不大的模型跑了半个多小时还没收敛。后来我把线路功率的大M降到9,位置耦合的大M按功率上限的2倍取,求解时间直接降了一个数量级。反过来,如果大M取得太小,比如位置耦合大M小于最大充放电功率,约束就会冲突,模型直接报不可行。
大M取值没有万能的公式,只有原则:按物理量纲来,适当地留出20%~50%的余量。每一条大M约束都应该能对应到实际物理含义,如果某条约束的大M值你解释不清楚它对应的物理上限,那多半就是你把约束写错了。
6.2 时隙粒度和移动耗时不对齐
我曾经设过时隙1小时、移动耗时1.5小时的情况。结果储能车每移动一次就要在路上待整整两个时隙,有效出力时间被大幅压缩,优化结果里储能车几乎原地不动,本质上等于退回了固定储能。这个坑非常隐蔽,因为模型没有报错,数字也收敛,但结果反映出的物理行为是错误的。
解决办法有两个:一是把时隙缩短到15分钟或30分钟,让移动耗时和时隙对齐,代价是模型规模按比例增大;二是保持1小时时隙,但接受移动耗时会高估,适合教学复现。我自己做正式实验时会把时隙设为0.5小时,做教学演示时用1小时,两者各有取舍。
6.3 如何判断复现结果是否“靠谱”
我自己总结了一套判断结果合理性的流程,按顺序执行能挡掉大多数低级错误。
先看能量平衡:把所有MESS的初始SOC、充放电电量、切负荷量加起来粗算一遍,如果能量严重不守恒,约束组一定有问题。再看电网物理约束:节点电压是否都在0.95~1.05之间,变压器根节点注入有没有超上限。最后看决策行为是否合理:MESS是否优先停靠在重要负荷附近、是否在低负荷时充电高负荷时放电、恢复曲线是否平滑。如果这些特征全部符合,结果可信度就很高。
对于“完美复现”这个概念,我还想多说一句:不同文献对权重、场景、储能容量、是否允许网络重构等细节设置都可能不同,复现的重点是复现出“移动储能能提升韧性”的核心趋势和结论,而不是追求跟某篇论文的数值完全一致。趋势正确、曲线形态相符,就已经说明你的复现框架是成功的。
最后再分享一个小技巧:动手写代码之前,先把IEEE33节点的拓扑图和MESS的时空网络图画在纸上,把决策变量直接标到图上。我就是靠这张图理顺了预布局、动态调度、切负荷之间的逻辑关系。复杂优化问题最忌讳上来就敲代码,先让结构在脑子里长出来,Matlab代码只是把已经想清楚的结构翻译成语言而已。这套建模思路不只在IEEE33节点上有效,换到40节点、123节点甚至实际馈线系统,核心框架都可以平滑迁移。