刚开始接触微电网能量管理那阵子,我特别困惑:为什么一篇模型预测算法相关的仿真要起名叫“双层”?后来真去搭混合储能微电网的仿真,才发现双层能量管理系统不是论文灌水,而是被物理问题逼出来的。这条路上不少同学卡在同一个地方:代码能跑,但说不清上层在干什么、下层在干什么、两层之间传什么;或者反过来,原理明白,一写Matlab就不知道从哪个文件开始。这篇东西就把整套架构掰开揉碎讲一遍——从为什么需要双层,到MPC怎么嵌入,再到Matlab代码长什么样、调参踩了哪些坑,一次性讲清楚。适合正在做微电网/储能方向课题的研究生,也适合刚接手EMS项目的工程师;如果你手头正好有一份基于模型预测算法的混合储能微电网双层能量管理系统的Matlab代码,那这篇文章就是配套的“阅读说明书”。
1. 从“为什么非得双层”开始:单层调度到底卡在哪
1.1 单层调度为什么顾此失彼
我见过很多刚接触微电网仿真的同学,拿到代码第一反应是:为什么不干脆写一个大的优化问题,一次性把所有变量都求出来?非要分两层,不是多此一举吗?说实话,如果系统只有一台柴油机和一组蓄电池,单层确实够用——优化变量少,模型简单,一个MPC控制器全包了。可一旦引入光伏、并网交互、混合储能,你再尝试把所有问题塞进一个优化层,马上会撞上两个矛盾。
第一个矛盾是时间尺度。经济调度关心的是“未来几个小时到一天,怎么买电卖电、怎么安排储能充放才能省钱”,它的决策频率通常是15分钟到1小时一次。而实时功率控制关心的是“这一秒光伏跳了10kW,负荷跳了5kW,谁来扛这个偏差”,它的决策频率是秒级甚至毫秒级。把这两个问题放进同一个优化里,要么只能按慢尺度跑,结果实时波动完全没人管;要么只能按快尺度跑,预测窗口短到看不出经济性问题,优化求解还慢得要命。单层方案无论怎么选,总有一头顾不住。
第二个矛盾是目标函数打架。经济目标希望电池在电价低谷多充电、高峰多放电,怎么划算怎么来;安全目标希望电池SOC别太高、别太低、充放电功率变化率别太陡,最好所有波动都让超级电容去扛。这两类目标在数学上是互相较劲的——你把经济权重调大,电池就像一个“拼命三郎”,天天深充深放,循环寿命肉眼可见地衰减;你把安全权重调大,系统又变得唯唯诺诺,电价再高也不肯多出力,经济性一塌糊涂。单层优化得靠一个巨大的权重系数矩阵去调和这些矛盾,而权重这东西本来就是最难调的,调两天两夜都未必收敛出理想结果。
1.2 混合储能让控制对象“异构化”
单层不够用,还有一层原因是混合储能本身。绝大多数论文里说的混合储能,指的是“蓄电池+超级电容器”组合,也有少数用锂电池+飞轮、锂电池+液流电池。我这里按最常见的电池+超容来讲,两者特性差异极大,根本不是一个“储能”就能概括的。
| 特性 | 蓄电池 | 超级电容器 |
|---|---|---|
| 能量密度 | 高(能存很久) | 低(几分钟就放完) |
| 功率密度 | 低(不能猛充猛放) | 高(瞬间大电流没问题) |
| 响应速度 | 慢(秒级到分钟级) | 快(毫秒级) |
| 循环寿命 | 约2000~5000次 | 数十万次以上 |
| 成本 | 相对低(按能量算) | 相对高(按功率算) |
| 适合承担的任务 | 长时间充放电、削峰填谷 | 短时冲击、高频波动平抑 |
这个对比表几乎是所有混合储能论文的理论基础。电池是“大水缸”,能装很多水,但倒水速度不能太猛;超级电容是“高压水枪”,装不了多少水,但喷射速度极快。微电网里的波动是分层的:负荷随机波动、光伏云层遮挡引起的出力突变,这些高频分量变化快、幅度小;而电价变化、日负荷峰谷转移这些低频分量变化慢、能量大。让电池去硬扛高频突变,一次两次没事,长期循环下来寿命掉得特别快;让超容去顶长时间的能量缺口,它能量密度又不够,很快会耗尽。唯一合理的做法,是把不同频率的功率需求拆开,各干各的活——这就天然产生了“层”的需求。
1.3 双层结构到底在分什么
实际项目里,双层能量管理常见的划分是:上层做经济调度和经济优化,下层做功率分配和动态响应。上层面对的模型是“把微电网当成一个整体”,决策变量是并网功率、储能系统总出力、光伏出力等宏观量;下层面对的是“储能内部怎么分工”,决策变量是电池出力多少、超级电容出力多少。
上层关心“做什么”,下层关心“怎么做”——一句话就能说清。上层给出的是参考值或设定值,下层负责在满足物理约束的前提下实时跟踪。这个架构和工业界经典的“计划+调度”“协调层+执行层”是一脉相承的,不是微电网领域凭空造出来的概念。理解了这一点,后面看代码就会轻松很多:你看到的两个优化函数、两组循环、两套时间尺度,不是在重复算同一个东西,而是在处理两个不同频段的问题。
2. MPC的底层逻辑:预测、滚动、反馈三件套
2.1 三件套拆开看
说回核心算法。模型预测控制(Model Predictive Control)并不是一个新算法,它在上世纪七八十年代就从工业过程控制里发展起来了,石油化工、发电厂里用得非常多。它的核心就三件事:预测模型、滚动优化、反馈校正。
预测模型,就是用数学方程把被控对象的动态描述出来。对微电网储能来说,最核心的动态就是SOC的更新方程,可以写成状态空间的形式:
SOC(k+1) = SOC(k) - η·P_storage(k)·Δt / E_capacity其中η是充放电效率,P_storage是储能功率(充电为正或放电为正,取决于你定义的习惯),Δt是采样周期,E_capacity是储能容量。这个方程的意思是:这一时刻的SOC,决定了下一时刻能放出多少电、还能存多少电。光伏和负荷的未来曲线也需要建模,一般直接用预测序列,不写进状态方程。
滚动优化,是MPC最灵魂的部分。它不像传统控制那样只解一个当前时刻的控制量,而是在每个采样时刻、向前看一个有限时域(比如N步),求解一个带约束的优化问题,得到未来N步的控制序列,然后只执行序列里的第一步。到下一个采样时刻,重新测量状态、重新预测、重新求解、再执行第一步。这个过程就像开车走陌生路段——不是出发前把整条路线一次性规划到小数点后三位,而是每个路口都重新看前方几百米,不断修正方向盘。这种“走一步看一步”的策略,让MPC天然具备对抗模型失配和外部扰动的能力。
反馈校正,是指每次滚动前都用最新的实测数据去更新状态初值。预测模型不可能十全十美,光伏预测明天中午有100kW,实际可能只有80kW;MPC不靠模型自己纠偏,而是靠下一次采样时把实际SOC、实际功率测回来,作为新一轮优化的初值。这个“实测值顶上来”的机制,是MPC鲁棒性的基本盘。
2.2 MPC怎么处理约束
相比PID,MPC最大的工程优势是能“光明正大”地处理约束。电池不能超过最大充放电功率,SOC不能超过[0.1, 0.9],并网功率受变压器容量限制,电池功率变化率不能太陡——这些在PID里全是靠限幅器、斜坡限制器这些外部附加模块来凑合实现的,限幅器一旦动起来,控制品质就崩了。但在MPC里,这些约束直接写进优化问题的约束条件,求解器会在一组可行域里找最优解,天然不会触碰边界。
如果目标函数是二次型,约束是线性的,那这就是一个标准的二次规划(QP)问题;如果加入了绝对值、max这类非光滑项,可以线性化;如果目标函数写成线性函数加线性约束,那就是线性规划(LP)。微电网里的MPC绝大多数是LP或QP,Matlab里用Optimization Toolbox自带的quadprog或者linsolve那一套就能解决,也可以接第三方求解器。这里要插一句:约束写得越真实,求解器压力越大;想象一下电池SOC约束按0.1~0.9写,但控制量一算就把SOC顶到0.95——这说明你少写了一条“SOC末端加罚”的软约束,后面调参部分我会专门讲。
2.3 和规则控制、PI控制放一起比
很多论文会把MPC方案和“规则控制策略”做对比。规则控制说白了就是一组经验查表逻辑:电池SOC低于30%就只充不放,光伏多了就充电,负荷高了就放电,超容SOC高于50%就平抑高频分量。这套策略实现简单、计算量几乎为零,但它没有任何预测能力,遇到未来两小时电价会大涨这回事,它完全看不见,只能等到电价已经涨上去了才反应过来。
PI控制比规则控制聪明一点,能用偏差驱动校正,但同样没有预测,而且参数整定困难。给电池一个PI控制器,如果比例系数调得大,功率会来回震荡;调得小,响应又慢吞吞。MPC把“未来预测”和“约束处理”都做进了算法本体,付出的代价就是计算量大、建模成本高。我把三者放在对比表里,方便你判断什么场合该用谁。
| 对比维度 | 规则控制 | PI控制 | 模型预测控制 |
|---|---|---|---|
| 预测能力 | 无 | 无 | 有,滚动向前看N步 |
| 约束处理 | 靠逻辑硬限幅 | 靠外部限幅,易震荡 | 写进优化,天然满足 |
| 计算量 | 极小 | 小 | 较大,需实时求解 |
| 参数整定 | 规则多、靠经验 | P/I参数,玄学 | 权重+时域,有逻辑可循 |
| 适应复杂场景 | 差 | 一般 | 强 |
| 适用场景 | 简单离网微网 | 单一对象快速调节 | 含预测、含约束的多对象协调 |
从我实际做仿真的感受来说,MPC强是强,但别把它神化。它强在“把问题转化成优化问题”这件事上,一旦你把物理问题量化好了,剩下就是数学求解;而规则控制强在“永远不出幺蛾子”,即使极端工况也能靠优先级逻辑兜底。所以工程上常见的做法是MPC做常态运行 + 规则做越限保护,两个方案并不是你死我活的关系。
3. 上层调度层:以经济性为核心的滚动规划
3.1 上层管什么
上层是整个能量管理系统的“大脑”。它的任务是在一个滚动的时间窗口内,决定未来一段时间微电网跟外部电网买多少电、卖多少电、储能系统整体充放多少功率、光伏要不要弃光。
具体来说,典型的上层模型以15分钟为一个步长,向前预测24个点(也就是6小时,或者按需要设成96点预测一天)。每个调度周期开始,上层接收光伏出力预测曲线、负荷预测曲线、分时电价,然后求解一个经济优化问题。优化完,它只把“当前时刻”的储能总功率设定值Pess_ref下发到下层,剩下那些未来时段的计算结果并不直接执行,而是等到下一轮滚动时重新计算。这个“只执行第一步”的习惯,和MPC的滚动优化思想完全一致。
3.2 目标函数怎么写:四类费用
上层目标函数通常包含三类到四类费用,我按实际代码里的常见写法拆开:
min J_upper = ∑ [购电成本 - 售电收入 + 电池老化折算 + 弃光惩罚]第一项是购电成本。并网功率为正(从电网买电)时,按分时电价乘以该时段电量计费;第二项是售电收入,并网功率为负(微网向电网卖电)时,按上网电价计算。这两项合起来就是微电网跟电网做交易的平均成本。分时电价机制下,这条费用项会引导储能在电价低谷充电、高峰放电——经济调度的本质就是让电价信号指挥储能。
第三项是电池老化折算。电池每充放一次,寿命就少一点,这个成本在工程项目里不可忽略。怎么折?工程上常用一个简化模型:循环一次按一个固定成本折算,或者用吞吐量(即功率×时间的累加)乘一个损耗系数。学术界写法更多,有的用DoD(放电深度)的幂函数,有的用充放电循环次数的查表,但仿真代码里最稳的还是线性折算——把每次充放电的功率积分乘以一个损耗单价,加到目标函数里。这样做的好处是保持问题为线性或二次,方便求解。
第四项是弃光惩罚。如果预测到光伏出力太多、负荷消耗不了、储能也装不下,系统只能弃掉一部分光伏。弃光意味着浪费了清洁能源,目标函数里要给一个较高的惩罚系数,让求解器优先选择储能吸收,实在装不下了才弃光。这个惩罚系数的量级通常远高于购电电价,否则求解器会“偷懒”选择弃光而不是充储。
3.3 约束条件怎么写才算严谨
上层约束里几个是必写的。第一个是功率平衡约束:并网功率+光伏出力+储能功率=负荷功率。这一条是物理守恒,不写就不用玩了。第二个是储能SOC边界约束:SOC要落在[下限,上限]区间,通常是0.1到0.9,防止过充过放。第三个是并网功率限制:受变压器容量或并网协议约束,Pgrid有上下限。第四个是储能功率限制:电池当前允许的最大充放电功率不仅受额定功率约束,还受SOC影响——低SOC时充电效率或最大充电功率可能要打折,严谨一点会写成SOC的函数。第五个是储能在相邻时段的功率变化率约束,防止给下层的指令发生阶梯式跳变,给下层执行造成负担。
除了硬约束,工程上还有一种常用的软约束技巧:给SOC末端加一个惩罚项。比如预测窗口末端SOC应该在0.5附近,这样储能才能“留有余量”迎接下一轮滚动。如果不加这条,求解器可能在窗口末尾把SOC算到极度接近边界,下轮滚动就要用很高的成本把它拉回来,控制品质很差。把末端SOC的偏差平方加进目标函数,只要权重适当,系统就会自己规划一条不那么激进的充放电路径,这个细节对整体控制稳定性影响非常大。
4. 下层功率分配层:电池与超级电容的最优分工
4.1 总指令到了下层,为什么不能直接给电池
上层算出来的是储能系统“总功率Pess_ref”,这个信号直接丢给电池行不行?最直观的问题有两个。一是电池响应速度不够:上层指令按15分钟尺度给出,如果光伏在几秒内骤降,总功率指令看起来没问题,但实际功率平衡瞬间被打乱,电池又跟不上这个速度,系统就会产生明显的电压/频率波动。二是电池受不了:即便是同一条总功率曲线,里面也包含大量高频波动成分,电池去承担这些高频分量,每秒钟都在快速改变充放电状态,循环寿命会被严重透支。
所以下层存在的意义,就是给总功率指令“分频”。高频波动部分让超级电容去扛,电池只承担变化平缓的低频部分。一句话:电池负责“量”的搬运,超容负责“波动”的平抑。
4.2 低通滤波:经典但不够
最经典的下层分配方案就是一阶低通滤波。基本原理是:
P_bat(k) = α·P_bat(k-1) + (1-α)·P_ess_ref(k) P_sc(k) = P_ess_ref(k) - P_bat(k)其中α = Ts / (Tf + Ts),Ts是控制周期,Tf是滤波时间常数。低通滤波把Pess_ref中的高频成分滤掉,剩下的低频成分给电池;超容负责总功率和电池功率之间的差值,也就是高频成分。这个方案的好处是简单、直观、计算量几乎为零。
但用久了你会发现它有三个毛病。第一,滤波系数固定,不感知SOC。如果超容SOC已经很低了,又来一个大高频波动,它想扛也没能量可扛,只能眼看着SOC归零。第二,低通滤波没有预测能力,不知道未来十分钟会有大波动,所以不会提前给超容充电留余量。第三,电池功率的爬坡速率完全由滤波时间常数决定,Tf调大了电池波动小但超容压力大,Tf调小了超容轻松但电池受累,这个矛盾在固定系数下无解。
4.3 下层MPC:把SOC安全写进优化
更进阶的做法,是下层也用MPC。下层优化问题的决策变量是未来M步的电池功率和超容功率,目标函数通常包含三项:总指令跟踪偏差、电池功率变化率惩罚、超容SOC偏离期望值的惩罚。约束包括功率平衡(P_bat + P_sc = P_ess_ref)、电池/超容各自的功率限幅、SOC上下限。这个优化问题同样是LP或QP,求解压力不大,因为下层预测时域一般很短(几秒到几分钟),变量远少于上层。
下层MPC相比低通滤波的强大之处在于:它能看到未来,所以可以提前动作。比如预测到5秒后光伏会大幅波动、超容SOC届时可能不够,MPC在现在就会让超容提前充一点电、留出裕量;低通滤波则完全不管未来,只能被动挨打。再比如电池功率变化率约束,低通滤波是隐式地通过Tf控制,效果模糊;MPC是硬性写进约束的,说好电池每步最多变5kW,就是5kW,严格执行。这些特性让“下层MPC”几乎成了混合储能论文里标配的高级方案。
当然,如果项目时间紧,用低通滤波+超容SOC修正环也能交差,而且效果不至于太差。原理是:低通滤波给出初值后,检测超容SOC是否偏离中点,如果偏高就多分一点功率给超容放电,偏低就少分一点,形成一个外环比例修正。这个方案实现快,但调参同样费神,而且上限明显低于MPC。我的建议是,追求代码可复现性和论文说服力,直接上MPC;追求快速验证,先用滤波版跑通流程再替换核心。
5. 双层怎么交互:时间尺度、数据流与两种实现方式
5.1 两个时间尺度怎么安排
双层的交互,最核心的是时间尺度的设计。我做仿真时习惯这样设置:上层调度步长15分钟,预测时域6小时;下层控制步长1秒,预测时域30秒到2分钟。两者相差两个数量级以上。这个差距是有讲究的:上层要看到足够长的经济信号(电价、负荷峰谷),所以步长不可能太短、时域不可能太短;下层要响应实时波动,所以步长必须足够快,但预测时域也不能太长,否则还没预测到未来,实际情况已经变了。
在Matlab仿真里,这个时间尺度差用嵌套循环或者事件触发的模式来实现:主仿真循环按1秒步长跑,每累计到15分钟就触发一次上层优化,其余时刻只跑下层分配和系统状态更新。这样写代码很自然,也符合工业上DCS(分散控制系统)里不同控制周期任务调度的思路。
5.2 数据流和反馈闭环
上层和下层的交互关系可以用几条关键数据流概括:
- 上层→下层:下发储能总功率设定值Pess_ref(也可以下发整个未来序列,但执行时只用第一个值)。
- 下层→上层:反馈当前电池SOC、超容SOC、实际功率执行结果。
- 外部→上层:光伏预测功率、负荷预测功率、分时电价。
- 外部→下层:实测光伏功率、实测负荷功率、电网频率或电压偏差。
这个结构里最容易被忽视的是“反馈闭环”。很多初版代码只做了上层→下层的单向传递,上层算完就不管了,永远用上一轮的SOC初值,结果上层优化和下层执行完全脱节,仿真跑到后面SOC越偏越离谱。正确做法是:每个上层调度周期开始时,先把下层反馈回来的实际SOC作为优化初值SOC(0),然后再求解。下层反馈的不仅是一个数值,它代表了过去15分钟里微电网真实发生的充放电对储能状态的累积影响——不带这个反馈,上层就是睁眼瞎。
5.3 真双层与简化双层
做Matlab代码的时候,“双层”还有两种实现方式,很多论文不会明说,但你看代码能看出来。
第一种是真双层,上下层各自是独立的优化问题,各自调用求解器。上层优化完把Pess_ref传给下层,下层再优化自己的功率分配,两层之间通过变量和反馈衔接。这种实现最贴合标题里的“双层能量管理系统”,代码结构也最清晰——两个函数、两个求解器、两层循环,调试起来很容易定位问题。缺点是计算量翻倍,仿真的实时性差一些,但对离线仿真来说完全不是问题。
第二种是简化双层,上层用MPC,下层用一个明确的映射公式或滤波函数代替优化。比如下层直接用低通滤波+SOC修正环,不做实时优化。这种实现计算快、参数少,代码量大概是真双层的一半,但学术上不太“硬”,审稿人可能质疑下层的“最优性”。我自己写代码时通常做两套:先写简化的跑通整个仿真,验证上层逻辑;再替换成真双层,提升论文的说服力。两条路最后联调的时候注意接口保持一致,Pess_ref的物理含义别换,切换就非常顺。
6. Matlab代码落地:一套可跑的框架长什么样
6.1 环境准备与工具选型
写这套系统,Matlab版本建议R2023b及以上,新版2026b我也试过,核心工具箱兼容性没问题。必须装的工具箱是Optimization Toolbox和Control System Toolbox;如果要用第三方求解器,再装YALMIP和Gurobi(或CPLEX)。这里我多说一句选型逻辑:
- YALMIP+Gurobi:建模方便,约束写起来像数学公式,求解速度快,适合上层的大规模LP/QP。缺点是YALMIP和Gurobi都要配环境,新手容易在安装步骤卡住。
- Optimization Toolbox自带quadprog:不装第三方也能跑,对小型问题完全够用,避免一堆环境配置的麻烦。缺点是大规模时求解速度一般,而且你得手动把问题写成矩阵形式。
- fmincon:能处理非线性约束,但在微电网MPC里基本用不上——LP/QP足够,非线性反而拖慢求解速度。
我的建议是:先把所有问题写成线性或二次(通过分段线性化、绝对值替身变量等技巧),然后用quadprog跑通验证,最后再换Gurobi做大规模场景。这样既不耽误学习原理,又不至于一开始就被工具安装劝退。
6.2 一套典型系统参数
下面这套参数是我仿真时常用的,你可以直接改写成你自己的算例。电池容量和超容容量的选择依据是:电池需要覆盖一个调度周期内的能量转移需求,超容只需覆盖秒级波动的冲击能量,所以超容容量通常只有电池的1/10左右。
| 参数 | 数值 | 说明 |
|---|---|---|
| 光伏额定容量 | 100 kW | 可弃光 |
| 负荷峰值 | 80 kW | 含随机波动 |
| 电池容量 | 200 kWh | 荷电状态范围0.1~0.9 |
| 电池最大功率 | ±50 kW | 充放对称 |
| 超级电容容量 | 20 kWh | 荷电状态范围0.05~0.95 |
| 超级电容最大功率 | ±30 kW | 瞬时功率能力 |
| 并网变压器上限 | 80 kW | 向上级电网购电/售电上限 |
| 上层步长/预测时域 | 15 min / 24步 | 对应6小时滚动窗口 |
| 下层步长/预测时域 | 1 s / 60步 | 对应1分钟滚动窗口 |
| 仿真总时长 | 24 h | 86400秒 |
6.3 主循环框架代码
下面是整个仿真的主框架,按“1秒主循环 + 15分钟触发上层”的方式写。这段代码是一个整体骨架,核心逻辑都在注释里,每个函数可以独立维护。
%% 双层能量管理主循环 % 变量初始化 SOC_bat = 0.5; SOC_sc = 0.5; P_bat_last = 0; P_sc_last = 0; Ts = 1; % 下层控制周期 1s T_up = 900; % 上层调度周期 15min T_end = 86400; % 仿真24小时 for t = 1: Ts : T_end % 1) 每T_up秒触发一次上层经济调度 if mod(t-1, T_up) == 0 % 获取未来6小时的预测曲线(简化:可加扰动) pv_pred = get_pv_prediction(t, T_up); load_pred = get_load_prediction(t, T_up); price = get_price_profile(t, T_up); % 上层MPC优化,返回储能总功率设定值 [Pess_ref, Pgrid_ref, SOC_ref] = upper_MPC(... SOC_bat, SOC_sc, pv_pred, load_pred, price); end % 2) 读取当前实测功率 pv_real = get_pv_real(t); load_real = get_load_real(t); % 3) 下层功率分配:在Pess_ref基础上分配电池/超容 [P_bat, P_sc] = lower_dispatch(... Pess_ref, SOC_bat, SOC_sc, P_bat_last, P_sc_last, Ts); % 4) 更新储能SOC SOC_bat = SOC_bat - P_bat * Ts / (E_bat * 3600); SOC_sc = SOC_sc - P_sc * Ts / (E_sc * 3600); % 5) 记录数据,画图用 log(t, :) = [P_bat, P_sc, SOC_bat, SOC_sc, pv_real, load_real]; P_bat_last = P_bat; P_sc_last = P_sc; end这个框架最重要的特征是“两层的时间尺度分离”:上层触发频率低、代价高;下层每个时刻都在跑,计算量小。你可以把上层看成一个“每隔15分钟开一次会做计划的管理者”,下层是“每秒都在干活的执行者”。
6.4 上层MPC优化代码
上层用YALMIP建模是比较舒服的写法,约束和目标函数几乎跟数学公式一一对应。下面这段是简化版上层优化:只考虑购售电成本、电池老化折算和弃光惩罚,电池和超容先合并成一个总储能对象,SOC跟踪的是总SOC。
function [Pess_ref, Pgrid_ref] = upper_MPC(SOC0, pv_pred, load_pred, price) % 参数 N = 24; % 预测时域步数 dt = 0.25; % 每步15分钟,单位小时 E_total = 220; % 总储能容量 kWh eta = 0.95; % 储能效率近似 Pmax_storage = 80; % 储能最大功率 kW Pgrid_max = 80; % 并网功率上限 kW SOC_min = 0.1; SOC_max = 0.9; % 优化变量 Pgrid = sdpvar(1, N); Pst = sdpvar(1, N); % 储能充电为正、放电为负 Ppv = sdpvar(1, N); SOC = sdpvar(1, N+1); % 约束 Cons = []; Cons = [Cons, SOC(1) == SOC0]; % SOC递推:Pst>0充电,SOC上升 Cons = [Cons, SOC(2:end) == SOC(1:end-1) - Pst*dt/E_total]; % 功率平衡 Cons = [Cons, Pgrid + Ppv + Pst == load_pred]; % 并网限制 Cons = [Cons, -Pgrid_max <= Pgrid <= Pgrid_max]; % 储能功率限制 Cons = [Cons, -Pmax_storage <= Pst <= Pmax_storage]; % SOC边界 Cons = [Cons, SOC_min <= SOC <= SOC_max]; % 光伏出力范围 Cons = [Cons, 0 <= Ppv <= pv_pred]; % 目标函数:购电成本 - 售电收入 + 电池老化 + 弃光惩罚 cost_buy = sum(max(Pgrid,0) .* price) * dt; income_sell = sum(min(Pgrid,0) .* price_sell) * dt; % price_sell为上网电价 aging = 0.01 * sum(abs(Pst)) * dt; % 电池老化系数 curtail = 0.5 * sum(pv_pred - Ppv) * dt; % 弃光惩罚,系数远大于电价 Objective = cost_buy - income_sell + aging + curtail; % 求解 ops = sdpsettings('solver','gurobi','verbose',0); diagnosis = optimize(Cons, Objective, ops); if diagnosis.problem ~= 0 warning('上层优化失败: %s', diagnosis.info); end % 返回第一时刻的设定值 Pess_ref = value(Pst(1)); Pgrid_ref = value(Pgrid(1)); end这里注意几个细节:Pst充电为正,SOC更新方程就用“减号”;放电为负时,SOC更新方程自动变成加法。max/min函数的非光滑性,在YALMIP里会自动做线性化处理,底层求解器不会直接碰到non-smooth项。如果你不用YALMIP,需要手动引入两个非负变量把max/min拆开,就麻烦不少——这也是我推荐用YALMIP的原因。
6.5 下层功率分配代码
下层我用“低通滤波+超容SOC修正”这个带修正项的版本做个示例,因为它代码短、容易理解,跑通之后你自然能看出为什么MPC更优。把这段换成下层MPC的代码,本质上就是把下面这段的修正逻辑替换成一个小的quadprog求解。
function [P_bat, P_sc] = lower_dispatch(... Pess_ref, SOC_bat, SOC_sc, P_bat_last, P_sc_last, Ts) % 参数 Tf = 120; % 低通时间常数,单位秒 alpha = Ts / (Tf + Ts); % 滤波系数 SOC_sc_ref = 0.5; % 超容SOC期望值 K_soc = 10; % 外环修正系数 % 第一步:低通滤波,低频给电池 P_bat_lpf = alpha * P_bat_last + (1-alpha) * Pess_ref; % 第二步:超容功率初步为总功率减电池功率 P_sc = Pess_ref - P_bat_lpf; % 第三步:超容SOC修正——偏高则多放电,偏低则少放电 SOC_err = SOC_sc - SOC_sc_ref; P_sc_corr = K_soc * SOC_err; P_sc = P_sc - P_sc_corr; P_bat = Pess_ref - P_sc; % 限幅 P_bat = min(max(P_bat, -50), 50); P_sc = min(max(P_sc, -30), 30); end这段代码很直观,但要注意第三部的修正方向:SOC偏高时SOC_err为正,P_sc_corr为正,那么Psc减去一个正值、出力变小(如果Psc是放电为正的约定,这里相当于让它少放点电、或者说相对充电),会让SOC向0.5回归。不同代码里功率正负号约定不一样,最容易出错,检查SOC是否收敛到参考值就行。真正用下层MPC时,这套“比例修正”的逻辑被目标函数里的SOC偏离惩罚项取代,逆向思想是一样的,只是从“即时响应”变成了“预见性优化”。
7. 仿真结果怎么解读:哪些图能证明系统有效
7.1 三张必看的图
代码跑完,别急着看数字报表,先画三张图。
第一张是功率平衡图:并网功率、光伏出力、电池功率、超容功率、负荷功率画在同一张图里,或者直接画“光伏+电池+超容+并网-负荷”这条误差曲线,理想情况下应该在0附近小幅波动。这张图证明系统没有违背物理守恒,是最基本的合法性检验。误差曲线波动幅度如果能控制在负荷波动的10%以内,基本可以说明双层协调没白做。
第二张是SOC轨迹图:电池SOC和超容SOC各自画一条曲线。合格的曲线应该是:电池SOC走得像一条缓坡,涨跌都是长周期的,充放循环次数少、深度可控;超容SOC则像一条活跃的锯齿线,高频上下跳动,但始终维持在0.1~0.9区间里,最好不碰上下限。如果你看到电池SOC像锯齿一样高频抖动,说明下层分频没做好,高频分量漏给电池了;如果超容SOC长期贴着上限或下限,说明SOC修正机制或者下层MPC的SOC罚项权重不够。
第三张是并网功率和电价对照图:把分时电价曲线和并网功率曲线叠在一起看。电价低谷时段并网功率应该偏正(买电充电),电价高峰时段并网功率应该偏负(放电卖电),储能充放电的方向要和电价信号对齐。如果电价高峰时系统还在买电,说明上层经济优化的预测或约束出了问题。
7.2 对比实验怎么设计
论文需要对比实验,仿真心得里也需要。我最常用的对照组有三个:规则控制策略、单层MPC(不做功率分配,直接让电池执行总指令)、以及本文的双层MPC。对比维度选日均运行成本、SOC越限次数、电池等效循环次数、功率波动平抑率。表格列出来才直观:
| 方案 | 日均成本/元 | SOC越限次数 | 等效电池循环/天 | 并网波动平抑率 |
|---|---|---|---|---|
| 规则控制 | 185 | 3 | 2.8 | 55% |
| 单层MPC | 142 | 1 | 3.1 | 48% |
| 双层MPC(低通下层) | 136 | 0 | 2.2 | 82% |
| 双层MPC(MPC下层) | 130 | 0 | 2.0 | 91% |
这个表是我仿真里的典型趋势,不是精确数值,但规律是有普适性的:双层能显著提升波动平抑率,MPC下层进一步压低了电池循环次数。SOC越限次数这项,双层方案要做到0次才算合格,不然就说不上“约束处理能力强”。
7.3 三个量化指标怎么算
平抑率可以这样算:取并网功率波动(或母线功率偏差)的标准差,跟无储能时的标准差做对比,(1 - 有储能标准差/无储能标准差)×100%。标准差越小,平抑越有效。等效循环次数算起来稍微麻烦点:把一天的充放电吞吐量加起来,除以两倍的额定容量,得到等效满充满放次数——比如电池一天累计充电量100 kWh、放电量95 kWh,额定容量200 kWh,等效循环就是(100+95)/(2×200) ≈ 0.49次。SOC越限次数就简单了,统计SOC超出[0.1,0.9]区间的采样点个数或连续事件次数。
这些指标不仅用来证明“系统有效”,更是调参的直接反馈。比如你改了某个权重,看等效循环次数上升了、但购电成本下降了,那就要权衡电池寿命和经济性哪个更重要。这套量化逻辑也是论文里“某某权重对整体性能影响分析”那一节的基本盘。
8. 踩坑与调参:六个后来者最容易翻车的地方
8.1 预测时域不是越大越好
刚开始做MPC的人有个直觉:预测时域越长,看得越远,效果越好。实际跑起来你会发现不是。上层预测时域从24步加到96步,求解时间从0.3秒飙到3秒,而优化结果提升可能只有1%。更严重的是,如果预测曲线后段质量不高,时域越长,整个计划里“远方”的坏数据对当前动作的影响越大,反而拖累第一步决策。我的经验是:上层时域能覆盖一个完整的电价周期就够了,比如峰谷电价一个周期是24小时,那就按96步×15min跑;下层时域覆盖最大波动事件的持续时长,比如云层遮挡造成的光伏波动持续2分钟,那下层预测60秒到120秒就够。
8.2 SOC权重的“震颤陷阱”
下层目标函数里SOC偏离惩罚权重是最大的坑。权重太小,超容SOC容易越界;权重太大,整个系统会“神经质”——稍微偏离一点点0.5就拼命充放,电池跟着高频抖动,功率指令像锯齿一样颤动。这种震颤在仿真图上一眼就能看出来,功率曲线密密麻麻全是毛刺。解决方法是分两步:先把SOC权重设成零,只调功率跟踪偏差,让曲线平滑;再逐步加大SOC权重,每加一次都看SOC曲线是否光滑,一旦开始震颤就退回上一个档位。我最终用的权重通常是功率跟踪偏差的1/5~1/10,这个比例不是公式算出来的,是反复跑出来的。
8.3 超容SOC漂移问题
只做低通滤波不做SOC修正的下层,跑几个小时就会发现超容SOC慢慢漂移到边界,且一去不复返。原因是滤波只分频、不管能量均衡,超容每时每刻都在做高频充放,净能量交换不一定为零,长期积少成多就漂了。解决思路就是前面代码里的SOC修正环,或者下层MPC目标函数加SOC回归项。有个取巧的办法是在上层目标函数里也加入对超容SOC的考虑,把超容SOC的期望值(0.5)作为上层的一个软约束,这样下层收到的Pess_ref本身就带了“帮超容回归中点”的倾向。两条路可以同时用,效果最稳。
8.4 求解器的数值问题
Matlab联合YALMIP+Gurobi仿真时,最常报的错误不是模型写错,而是数值问题,比如“Numerical issues”或者“Infeasible”。罪魁祸首是变量量纲差距太大——功率是kW量级(好几十),SOC是0~1量级,电量是kWh量级(好几百,甚至上千),放在同一个目标函数里,权重差的量级很容易达到10^6以上,求解器的数值容差就扛不住了。处理办法是归一化:把所有变量和约束除以各自的基准值。功率除以额定功率P_base,SOC保持0~1,能量除以E_base。这样所有数值都在0.1到10的范围内,求解器舒服多了,速度也能快不少。
8.5 预测误差的鲁棒性验证
很多初版代码假设光伏预测和负荷预测是完美的,直接拿真实曲线当预测曲线送进上层。这样仿真结果漂亮是漂亮,但完全不能说明MPC的“预测优势”——MPC抗扰动能力恰恰体现在“预测不准但还能稳”。我的建议是,在仿真脚本里给预测曲线加上一个随时间变化的误差项,比如施加一个幅值20%的阶跃误差,或者带漂移的随机误差,再看系统是否还能保持SOC在界内、成本不失控。这一步不仅能验证MPC的反馈校正机制是否真的在工作,也是论文里“鲁棒性分析”和“预测误差敏感性分析”的来源。
8.6 双层联调时的“冷启动”问题
最后是一个特别容易被忽略的问题:双层联调的第一轮。如果仿真从t=0开始,第一个上层调度周期触发时,下层还从没跑过,Pess_ref拿的是什么历史数据?如果你初始化了一个零矩阵,上层会以为过去15分钟储能完全没有动作,SOC初值还是初始值0.5,这没问题。但如果你的代码是随机初始化或者用了上一轮仿真的末尾数据,第一轮优化就会“抽风”。我的做法是:在仿真启动前先手动预热一段,用固定规则控制跑一个调度周期,让下层状态充分稳定,再启动正式的双层MPC循环。别小看这一步,很多“为什么我跑出来的SOC一开始就乱跳”的问题,根源就是冷启动没处理好。
把上面这些坑整理成一张检查表,贴在代码文件夹的README里,比我在这啰嗦一整章都管用:
| 现象 | 可能原因 | 优先排查方向 |
|---|---|---|
| SOC曲线毛刺多、功率锯齿 | SOC权重过大 | 调小SOC偏离权重 |
| 超容SOC长期贴边界 | 缺SOC修正环节 | 检查下层修正环或SOC回归项 |
| 求解器报Numerical/Infeasible | 变量量纲差异过大 | 归一化变量与约束 |
| 上层指令每轮跳变剧烈 | 缺功率变化率约束 | 上层加斜坡限制 |
| 仿真开头SOC乱跳 | 双层冷启动 | 预热一个周期再启动MPC |
| 成本比规则控制还高 | 电价信号或预测曲线有误 | 校验价格曲线方向和预测入口 |
| 跑24小时很慢 | 预测时域过长 | 缩短预测时域,分层降频 |
这套系统我前前后后改过三轮,最深的一条体会是:别一上来就追求“完美算法”,先把上层经济调度单独跑通,不接下层,把成本曲线调到合理;再把下层独立拉出来,手动喂几条典型功率曲线,把SOC安全调到不越界;最后才联调。联调最容易出问题的地方不是优化本身,而是时间不同步——上下层采样周期差了几百倍,一不留神就把上一时刻的变量当成了这一时刻,尤其注意mod触发、缓存清零、SOC初值传递这三处。能把这几个数据交接点盯紧,这套双层MPC代码基本就跑得又稳又好看了。