开写之前先聊两句。我本身就一直在做微电网能量管理相关的事情,MPC这一套在仿真里跑通很容易,真正让它贴近工程逻辑却要踩不少坑。这篇东西我会按照一个完整项目的思路来写,从理论框架到Matlab代码再到问题排查,争取让你拿着就能在自己的数据上复现。
1. 微电网调度到底难在哪儿,MPC为什么吃香
1.1 调度的本质:一个带约束的经济问题
微电网调度说白了就是回答一个问题:在未来一段时间里,光伏、风电、储能、柴油发电机和主网交换这些设备各自该发多少电、充多少电、买多少电,才能既保证负荷供给,又把运行成本压到最低。这个问题的数学形式其实不复杂——它本质上是一个多时段的带约束优化问题,目标函数通常是购电费用加燃料成本再加储能损耗惩罚,约束则包括功率平衡、设备出力上下限、储能SOC递推关系、爬坡约束等等。
但真正把它放到实际场景里,麻烦就来了。微电网里的光伏和负荷都是随机波动的,你如果只做一次全天24小时的开环优化,早上9点算出来的计划表中午12点就已经严重偏离实际。尤其是那种带有高比例可再生能源的微电网,午间光伏大发、傍晚负荷尖峰、夜间风电不稳,时间尺度跨越非常大,用固定计划的思路根本跟不上变化。
我最早做这个方向的时候也走过弯路。当时的第一版方案是典型的“先预测、再优化”两段式:用历史数据预测出全天负荷和光伏曲线,然后一次性求解24小时的机组组合和经济调度,得到一个全天计划表。仿真结果很漂亮,成本曲线下行、储能SOC曲线光滑,既能削峰填谷又能提升新能源消纳率,看着也像那么回事。可一旦把预测误差加进去,哪怕只有正负10%,开环计划就会导致某些时段储能过放、柴发出力越限,系统直接失去可行性。这个痛点恰恰是MPC能解决的。
1.2 开环优化和闭环滚动的差别
MPC和传统开环优化的核心区别就四个字:滚动、反馈。它不试图一次性算出全天的计划,而是每个控制周期(通常15分钟或1小时)都重新求解一次优化问题,而且每次只考虑未来一个有限时域,比如未来24小时。求解完之后,只执行当前时刻的第一个控制动作,下一时刻再重新采集状态、刷新预测、再优化,形成一个不断向前滚动的闭环。
这里值得多说一句:MPC本质上不是一种具体的优化算法,它是一种控制框架。你可以在这个框架下用线性规划、二次规划、混合整数规划等不同求解器来处理模型。这也是Matlab实现起来最舒服的地方——工具箱里有现成的quadprog、linprog,配合Yalmip可以把模型写得像数学公式,而不是手推一堆矩阵系数。
从效果上说,滚动优化的好处非常直观。预测误差进来了,下一轮优化会根据最新状态重新调整计划,把误差带来的偏移及时修正掉。反馈校正的存在让每一步优化都建立在实测数据上,而不是完全依赖上一轮预测。这套逻辑在微电网这种预测精度有限又要求安全稳定的场景里,是天然契合的。
2. 调度模型设计与参数设定
2.1 系统结构:都有谁参与调度
工程上的微电网结构千差万别,但用于MPC调度研究,我一般保留五个核心部分:光伏阵列、风力机组、储能系统、柴油发电机以及主网联络线。负荷侧作为一个不可控的功率节点参与平衡。光伏和风电在调度模型里是“负的负荷”,它们的出力预测值直接带入功率平衡方程,不参与决策;真正需要优化决策的是储能充放电功率、柴油发电机出力以及主网交互功率。
在Matlab实现中,我会把整个系统定义成一组结构体来管理,这样代码的可读性和扩展性都更好。比如system.ess.capacity、system.ess.pMax、system.ess.eff等字段分别存储储能的容量、功率上限和效率,system.grid.pMax和system.grid.price记录主网交互上限和分时电价。这样写的好处是,后面做参数敏感性分析时,只需要改结构体里的数值,不需要去代码里到处搜索。
设备选型方面,储能我通常设定为容量1000kWh、最大充放电功率200kW、充放电效率0.95;柴油发电机单台额定功率300kW,最小技术出力50kW,燃料成本近似为二次函数;主网交互功率限制在±500kW。这些数值并不需要多精确,关键是要保证它们之间的匹配关系——储能容量和功率上限不能脱节,柴发的最小出力和爬坡约束必须在一个量级上,否则优化问题非常容易无解。
2.2 预测模型怎么搭:光伏、负荷与电价
MPC的预测模型是整个框架中最微妙的一环。最常见的做法是直接使用数值天气预报和负荷预测模块的输出,在仿真里则可以直接用“真实曲线叠加随机误差”来模拟——比如把真实光伏功率乘上一个服从正态分布的误差因子,负荷则加入一个零均值的随机扰动项。这种做法在学术论文里很常见,但在Matlab代码里有一个容易被忽视的细节:误差必须逐时段独立抽取,且在滚动过程中每个时刻只能使用“当时已知”的预测值,绝不可以使用未来的真实数据,否则就成了作弊式的“上帝视角”调度。
电价数据我建议使用分时电价结构,比如峰平谷三个时段,价格分别为1.2、0.8、0.4元/kWh。为什么要用分时电价而不是固定电价?因为分时电价会直接驱动储能“低充高放”的行为模式,让调度结果呈现出明显的经济性特征。如果电价恒定,储能在优化里就没有套利空间,模型倾向于不用储能,MPC的优势体现不出来。
我在这里还要补充一个工业经验:光伏和负荷的预测误差标准差不要设置得太小,一般取真实值的8%到15%比较合理。误差太小,MPC和开环计划的效果几乎一样,你很难看出滚动优化的价值;误差太大,系统为了保守运行会付出过高的经济代价。在研究和演示场景下,10%是一个不错的折中值。
2.3 目标函数和约束条件的“坑”
目标函数我通常写成三部分之和:主网购电费用、柴油发电机燃料费用、储能充放电的退化惩罚。前两项是主要成本,第三项的目的不是真的计算电池损耗,而是防止优化结果在不需要的时候也频繁充放。它的系数要比电价小一个数量级,比如电价为1元/kWh时,储能惩罚系数取0.05元/kWh,这样它只会影响次优解之间的取舍,不会扭曲电价驱动的主策略。
约束条件里最容易出问题的是储能SOC的递推方程。这里需要特别注意单位换算和时间步长的一致性。如果你的采样周期是1小时,储能容量是1000kWh,充放电功率单位是kW,那么SOC变化量就是功率乘以时间间隔再除以容量。简化为百分比形式就是deltaSOC = P_batt * deltaT / capacity。我看过太多人的代码在这一点上栽跟头——时间步长用的是15分钟,公式里却直接把功率除以容量,导致SOC每步跳变,结果完全不可用。
除了SOC递推,柴发的爬坡约束也值得重视。爬坡率限制的本质是物理系统的响应速度上限,在仿真代码里不能省略,否则优化器会让柴发出力在一分钟内从50kW跳到300kW,这在真实柴油发电机上是不可想象的,所以必须在约束中加入相邻时刻出力的差值限制。
除此之外,储能还有一个容易被忽略的“末端约束”问题。如果MPC的预测时域只有24小时,而优化结束时SOC被丢弃,下一轮从当前实际SOC继续,那么优化器很可能在预测时域末端把电池放到接近下限,以获得短期的经济收益。要缓解这个问题,可以在目标函数中加入对末端SOC偏离初始值的软惩罚,或者设定一个SOC终值约束。具体取哪种要看你的研究目的:如果关注日内经济运行,软惩罚更合理;如果关注多日连续运行的可行性,则需要硬约束或保证滚动之间SOC初值的正确传递。
3. Matlab完整实现:从模型到滚动优化
3.1 代码框架与数据准备
Matlab实现MPC调度,我建议分成四个文件:主脚本、参数初始化、优化问题构建、结果可视化。主脚本负责滚动循环,参数初始化脚本集中定义系统参数和预测数据,优化问题构建部分负责把约束和目标函数写进求解器,结果可视化则用来绘制功率曲线和SOC曲线。这种划分不是为了好看,而是为了调试方便——模型一旦报错,你能迅速定位是数据问题还是约束建模问题,不用在几百行的单文件里翻找。
第一步是生成全天真实的“实际”数据曲线,并在此基础上生成每个滚动时刻的预测数据。这里我特别强调数据的时间对齐问题。很多初学者直接用矩阵索引操作数据,把不同时段的预测数据拼接错位,导致每个MPC时刻看到的光伏预测和负荷预测并不是同一个时间窗口,仿真结果出现莫名其妙的功率缺额。解决的办法是统一用时间戳索引:在初始化时生成一个1x96的时间轴向量(15分钟一个点,全天96个点),所有数据生成和提取都基于这个时间轴。
3.2 核心:滚动时域优化的实现
下面给一个核心的滚动优化实现思路,代码风格尽量贴近实际工程。这里我用了Yalmip来建模,原因是它可以让我们直接用数学表达式写约束,不需要手动推导矩阵形式。你如果没有Yalmip,用纯Matlab的quadprog也可以,但代码会失去可读性,后期扩展也更麻烦。
% 主滚动循环 for k = 1:N_total - Np % 生成当前时刻对未来Np步的预测数据 pv_pred = pv_actual(k:k+Np-1) .* (1 + sigma_pv * randn(1, Np)); load_pred = load_actual(k:k+Np-1) .* (1 + sigma_load * randn(1, Np)); price_pred = price_profile(k:k+Np-1); % 构建并求解优化问题 [P_grid_opt, P_batt_opt, P_dg_opt, SOC_opt] = solve_mpc( ...); % 只执行当前步,更新储能真实SOC SOC_actual(k+1) = SOC_actual(k) - (P_batt_opt(1) * dt) / capacity; end在solve_mpc函数内部,用Yalmip定义决策变量并写约束。需要注意,每一次滚动循环里都要重新定义sdpvar变量,不能直接复用上一次的变量对象,否则会出现历史约束残留的问题。这一点在实际运行中非常容易踩坑,我在后面的问题排查部分会专门提。
另一个实际运行中的要点是求解器参数设定。Yalmip的sdpsettings里,我习惯设置verbose=0关掉求解器的冗余输出,设置solver根据问题类型选择。线性约束加二次目标用quadprog或OSQP;如果引入了整数变量——比如柴发启停状态——就必须换成cplex或gurobi这类混合整数求解器。我个人在快速验证时首选OSQP,因为它在处理中等规模二次规划时速度优势非常明显。
3.3 反馈校正:让MPC真正跑起来
MPC能成为一个闭环框架,离不开反馈校正环节。在调度仿真中,这一步具体表现就是:每轮优化执行第一个控制动作后,要更新储能SOC的实际值,下一轮优化时将这个SOC作为新的初始状态传入。这样,即使预测数据出现偏差,SOC的状态信息也会通过反馈通道修正后续优化决策。
需要特别说明的是,反馈校正并不等同于每个时刻都用真实SOC替代模型内部的SOC。更实际的做法是,在每轮优化前用真实的SOC测量值重置优化问题的SOC初值,但预测时域内部的SOC递推仍然依赖模型计算。这在Matlab里的实现很轻量,只需要在每次调用solve_mpc之前把当前真实SOC赋值给优化函数即可。
还有一个升级方案,如果预测误差较大,可以在SOC递推方程中引入一个修正项,用当前时刻的测量SOC与模型预测SOC的差值对未来的SOC估计做在线校正。这种方法在电池容量估算存在偏差时非常有效,值得在研究中尝试。
4. 仿真结果怎么看、参数怎么调
4.1 典型参数表与结果分析
我惯用的参数配置如下表所示:
| 参数 | 数值 | 说明 |
|---|---|---|
| 采样周期 | 1h | 控制步长,可改为15min |
| 预测时域Np | 24h | 24个采样周期 |
| 控制时域Nc | 1h | 只执行第一步 |
| 储能容量 | 1000kWh | 额定容量 |
| 储能功率上限 | 200kW | 充放电对称 |
| 储能效率 | 0.95 | 充放电综合效率 |
| 柴发出力范围 | 50~300kW | 最小和最大技术出力 |
| 柴发爬坡率 | 30kW/h | 相邻时刻出力变化限制 |
| 主网交互上限 | ±500kW | 联络线功率约束 |
| 分时电价 | 0.4/0.8/1.2元/kWh | 谷/平/峰 |
在这个配置下,跑完一天仿真的典型结果是:午间光伏大发时段,储能充电,主网少买电甚至不买电;傍晚负荷尖峰而电价处于高峰时段,储能放电,柴发满发;夜间负荷低谷且电价便宜,主网购电为主,储能尽量保持在高SOC备用。这正好符合我们对MPC调度行为的预期——它不仅能平抑功率波动,还能利用电价差套利。
如果你拿到的结果完全不符合这个规律,很可能不是MPC算法的问题,而是模型约束或数据时序有误。常见错误包括:电价序列与负荷峰值时段错位、储能效率被设置成1导致的“永动机”行为、SOC初始值过低导致全时段储能无法充电。
4.2 敏感性分析与参数调整方向
做完基准仿真后,建议做几组敏感性分析来验证模型行为的合理性。我通常会从三个维度入手:改变预测误差标准差、改变储能容量、改变电价差幅。每组只改一个变量,其他保持不变,对比全天运行成本和储能SOC曲线。
预测误差从5%增加到20%,结果应该呈现一个趋势:运行成本略有上升,但系统不会出现失稳或约束违例。如果误差增大后出现大量不可行时段,说明MPC框架里缺少对约束松弛的处理,或者预测修正环节没有生效。储能容量从500kWh增加到1500kWh,成本应该逐渐下降并趋于饱和——容量超过某一点后继续扩容的边际收益很低。如果容量增加了成本反而上升,要检查储能惩罚系数是否过大,压过了电价套利的收益。
电价差幅这项更直观。电价差从0.4元扩大到0.8元,储能充放电循环深度会明显加深,主网购电成本占比显著下降。但如果电价差继续拉大到1元以上,储能可能会在一天内多次满充满放,这在实际工程中是不合理的——此时需要通过加强储能退化惩罚来限制循环次数。
5. 摸底实录:常见问题与排查指南
5.1 无可行解:九成是约束自己打架
MPC调度仿真里最让人头疼的错误就是求解器报“Infeasible problem”。经验告诉我,这种问题绝大多数不是求解器坏了,而是约束之间互相矛盾。最常见的几类矛盾源我列一下。
第一,SOC初始值、充电功率和容量三者不匹配。比如SOC初始值是10%,但接下来几个时段光伏大发、电价又低,优化器想让储能充电,但充电功率乘以时间间隔除以容量之后超出SOC上限,于是无解。解决办法是给SOC约束加入一个松弛变量,或者在初始化时就设定一个合理的SOC起点。
第二,柴发爬坡约束与负荷需求不匹配。负荷在相邻时段跳变超过柴发爬坡能力,而储能和主网又设置了功率上限,导致无法满足功率平衡。这需要检查负荷曲线是否有不合理突变,或者适当放宽储能功率上限。
第三,末端SOC约束设置得太死板。如果硬性要求每个优化时域结束时SOC必须回到80%,在预测时域末端负荷很高时容易无解。这个问题的排查方法非常简单:把末端SOC约束去掉再跑一遍,如果可行,就是约束设置问题。
5.2 结果反直觉与求解缓慢
还有一种常见情况是优化能出结果,但结果不符合直觉。比如,电价高峰时段储能不放电,反而在充电。排查思路分两步走:先检查电价序列的方向是否写反,这是个很低级但很容易犯的错误;再检查储能效率是否写成了充电效率1.2、放电效率0.8这种错误值——如果效率乘积大于1,优化器会利用它“凭空发电”。另外,储能退化惩罚系数如果高于峰谷电价差,储能就会选择不参与套利,这也是结果看起来“不正常”的合理原因之一。
求解速度方面,如果你的MPC每个滚动时刻要花几十秒才能求解完毕,首先要检查是不是把所有历史约束都叠加进了当前优化。这是因为很多人把变量定义放在滚动循环外部,导致每轮循环都累加新的约束。解决办法是每轮在定义变量后清空约束集,确保当前优化只包含当前时域内的约束。其次,检查是否无意中引入了大量整数变量。在没有柴油机启停需求时,应该保持所有决策变量连续,这样用quadprog求解是毫秒级的;一旦把连续变量声明成binvar和integer,问题复杂度会指数上升,完全没必要。
5.3 对不齐的时间索引
时间索引对齐问题的隐蔽性非常高,而且一旦发生,仿真结果往往看起来“合理但略怪”——比如光伏出力曲线和负荷曲线的峰值时刻错位一两个小时,或者电价峰值出现在光伏峰值之后导致储能充电时段异常。这类问题的诊断方法是画出三条曲线的原始数据图和MPC实际使用的预测曲线图,放在一起对比。
我的经验是,在代码里尽量避免用循环索引去操作预测窗口,改用向量切片加偏移量的方式。例如,从k时刻开始的Np步预测数据写为data(k:k+Np-1),而不是data(k+1:k+Np)再加上一堆手工偏移修正。同时,在所有数据初始化完成后,打印一次关键数据的前后几个时刻数值,人工目验一遍,确认每条曲线的起点和时标一致,再进入滚动循环。
判断时间对齐是否正确的另一个技巧是滚动累加统计。把所有时刻实际执行的第一控制结果(比如储能实际功率)连成完整曲线,和真实光伏负荷曲线放在同一张图里;如果储能功率峰值出现在光伏峰值之后一到两个采样周期,时间轴通常就是对的;如果偏差超过半天,就要回头检查数据索引了。
6. 写在后面的话
因为我自己在这些坑里爬过很多次,所以最后分享一点个人体会。MPC做微电网调度,真正区分有效研究和无效仿真的,往往不是算法的数学证明,而是预测模块、反馈校正、滚动框架和参数匹配这些工程细节。很多初始设计看起来用不着的东西,恰恰决定了仿真结果能不能反映真实系统。
如果你在复现代码的过程中遇到问题,我的建议是先简化:把预测误差设为零、去掉末端约束、只用储能和主网两个决策变量,跑通之后再逐步加回光伏、柴发、爬坡约束和预测误差。每加一个模块,就确认一次结果行为是否仍然合理。这样做的效率远高于一次性构建完整模型然后反复调试。
还有一个小技巧值得分享:在滚动循环里加一个“缓存”机制,把每轮的预测数据、优化结果和实际执行值保存成结构体数组。仿真结束后随时可以回看任一时段的输入输出对应关系,对排查那些“时对时不对”的偶发问题非常有效,也能帮助你在写论文时直接提取关键数据。这个习惯我到现在一直在用。