ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

微电网调度优化实战:MPC模型预测控制的Matlab实现与避坑指南

微电网调度优化实战:MPC模型预测控制的Matlab实现与避坑指南 做微电网调度优化前期论文看了几百篇真正动手跑Matlab的时候才发现MPC这个方向理论好懂实现起来坑不少。模型预测控制MPC应用于微电网调度核心思路就是“走一步看一步每步都重新规划”它把预测、优化、反馈拧成一条完整的闭环特别适合微电网这种带储能、带分布式电源、还时刻受负荷波动和电价变化干扰的系统。这篇文章把调度问题的本质、MPC原理、Matlab代码实现、常见坑和调试经验都过一遍适合刚入门微电网调度优化、或者有一定控制基础但第一次用MPC做能量管理的同学参考。1. 微电网调度到底在优化什么——先想清楚再写代码1.1 微电网调度的标准画画像微电网这个概念本身不复杂本质上就是一个中小型的电力自治系统里面装了几类东西光伏或者风电这类分布式电源、储能电池、本地负荷居民、商业或工业负载再通过公共连接点跟大电网交换功率。调度问题要回答的核心问题是未来一段时间里储能该充多少电、放多少电跟电网买多少电、卖多少电光伏该不该弃掉一部分才能在满足负荷需求的前提下让运行成本最低、能源利用率最高、储能寿命损耗还不至于太难看。这里的关键在于储能是“可调度的”光伏和负荷是“半可预测的”电价是“波动的”。三个因素叠加在一起把调度问题变成一个带约束的动态优化问题。以最常见的并网型微电网为例调度变量主要是储能充放电功率和电网交互功率调度周期从一小时到一天不等。写代码之前我建议先做一件事把你要解决的微电网结构画出来标清楚每一条功率流的方向然后写清楚每个设备的模型。这一步做扎实了后面建模和写约束都不会乱。1.2 传统方法的死穴无法应对不确定性很多初学者会问为什么非要用MPC直接把未来24小时当成已知做一次全局优化不行吗这个思路就是开环调度理论上可行但现实里有个致命问题——我们拿到的负荷预测、光伏出力预测不可能百分之百准确。中午预测下午光伏出力是50kW结果一朵云飘过来实际只剩20kW这时候如果储能昨天已经把电放光了电网交互功率又顶满上限那整个微电网就失衡了。开环优化的逻辑是“拍脑袋计划一次性执行”它对预测误差没有容忍度一旦实际跟预测有偏差后面整个时段的策略都会跟着出问题。另一个极端是纯反馈控制比如用PID或者简单的恒功率控制这类方法响应快、能稳压但没有预测能力没办法根据“未来三个小时电价会涨到峰值”提前把储能充满。它只会看见当前电价高就放电看不到未来电价更高结果就是该多卖电的时候储能已经亏空了。MPC恰好站在两者中间它每一次都基于最新测量值重新做一次优化既有全局眼光又保留实时反馈的修正能力。这也是为什么近十年MPC在微电网能量管理里越来越被重视几乎所有相关研究都绕不开这套框架。2. MPC三大件预测模型、滚动优化、反馈校正2.1 预测模型把未来的状态“猜”出来MPC的第一个核心要素是预测模型。简单说我们需要一个离散时间状态方程来描述系统从一个时步到下一个时步的状态变化。在微电网调度这个场景里最典型的状态变量就是储能电池的荷电状态SOC控制量是储能功率和电网交互功率扰动输入是负荷功率和光伏出力。预测模型的作用很直接给定当前SOC、当前控制指令以及未来一段时间的负荷和光伏预测序列就能推算出未来每个时步的SOC会是多少、功率平衡能不能维持。以最常用的储能模型来说离散递推关系是SOC(k1) SOC(k) - η_dis * P_bat(k) * Ts / C_rated放电时 SOC(k1) SOC(k) - P_bat(k) * Ts / (η_chg * C_rated)充电时其中η是充放电效率Ts是采样时间C_rated是储能额定容量。这里注意充放电效率通常不对称很多初学代码的人为了省事把效率写成同一个值结果跑完仿真发现SOC趋势慢慢漂移这就是模型没写对。预测模型不需要特别复杂能用就行。光伏和负荷的预测序列可以直接用历史数据加扰动或者用简单的持久性预测、时间序列预测生成。MPC对预测模型的要求是“能反映主要动态趋势”而不是要一个完美的物理仿真模型。2.2 滚动优化只看眼前一段路边走边修正滚动优化是MPC的核心机制也是理解整个算法的关键。它不追求一次性算出全天的完美计划而是时刻只关注一个有限的预测时域比如未来6个小时。具体流程是这样的当前时刻k基于最新测量的SOC、最新更新的负荷和光伏预测序列求解一个从k到kNp的有限时域优化问题得到一个最优控制序列。但注意这个序列不会全部执行只执行第一步然后到下一个采样时刻k1重新测量状态、重新更新预测、重新求解优化问题。打个比方你从北京开车去上海导航不会在出发那一刻就把全程所有决策定死。每到一个路口导航都会根据当前的交通状况重新规划接下来的路线。这就是滚动优化的本质——计划永远在更新决策永远基于最新信息。用数学语言描述每个采样时刻求解的优化问题形式大致是min J Σ_{i0}^{Np-1} L(x(ki|k), u(ki|k))约束包括状态递推方程、SOC上下限、储能充放电功率限制、电网交互功率限制、功率平衡等式约束。其中x(ki|k)表示在k时刻预测的第ki步状态这个记号是MPC论文里的标准写法看文献时遇到别慌意思就是“当前知道的未来的估计”。2.3 反馈校正误差来了就吃掉它懂行的人都知道模型永远不等于真实系统。储能内阻会随温度变化电池老化导致容量衰减预测误差更是常态。反馈校正这个环节保证模型跑偏之后系统能拉回来。在实现上反馈校正就是“每一时刻都用实际测量值重新初始化状态”。比如模型预测下一时刻SOC是0.65但实际传感器测得只有0.62那就用0.62作为下一轮优化的初始状态而不是继续用模型预测的0.65。这一步看起来简单却是MPC闭环稳定性的核心保障。更严谨的做法是可以给SOC预测加一个校正项比如用实际输出与预测输出的偏差来修正未来多步预测这就是反馈校正的增强版。不过对微电网调度问题来说只要保证每个采样周期都重新采集SOC和实时功率数据就已经具备基本的反馈校正能力了。3. Matlab代码实现从数学模型到可跑通的调度程序3.1 标准调度模型怎么列变量和约束都给你写代码的第一步是把目标函数和约束用规范的数学形式列清楚这一步做不好后面找bug会非常痛苦。目标函数通常包含三块跟电网交互的购电成本或者售电收益、储能的充放电损耗成本、弃光弃风惩罚。以最小化总运行成本为例min J Σ [ λ_grid(k) * P_grid(k) * Ts λ_bat * |P_bat(k)| * Ts λ_curt * P_curtail(k) * Ts ]其中λ_grid是分时电价购电为正、售电为负λ_bat是储能运行成本系数等效于电池寿命损耗λ_curt是弃光弃风惩罚系数。P_curtail是光伏/风电被削减的部分。约束条件分成几类功率平衡约束P_pv(k) - P_curtail(k) P_bat(k) P_grid(k) P_load(k)这个等式必须严格满足储能SOC约束SOC_min ≤ SOC(k) ≤ SOC_max储能充放电功率约束-P_bat_max ≤ P_bat(k) ≤ P_bat_max电网交互功率约束-P_grid_import_max ≤ P_grid(k) ≤ P_grid_export_max终端SOC约束SOC(Np) 一般要求不低于某个值目的是防止MPC为了省钱把储能掏空造成下个周期无电可用。3.2 关键参数怎么定给你一份可直接抄的表参数设置直接影响MPC效果我把一套工程上比较常用的初始值列成表格你可以先跑通再慢慢调。这里假设微电网规模是200kW负荷储能容量200kWh采样周期15分钟。参数取值说明采样时间Ts15分钟0.25小时太快求解压力大太慢PV波动抓不住预测时域Np24步6小时能覆盖电价峰谷周期又不至于求解过慢控制时域Nc24步调度问题一般NcNp即可不用特意减少储能SOC范围0.2 ~ 0.9留裕量避免过充过放储能最大功率100kW根据储能PCS容量定电网交互上限150kW并网协议一般有约定终端SOC约束≥0.5防止调度末端储能掏空这套配置建议先用预测误差很小的理想数据跑通再逐步加入预测误差观察结果的变化。3.3 主循环框架怎么写代码级拆解在Matlab里实现MPC调度我推荐用YALMIP工具箱来建模再用Gurobi或者quadprog求解。YALMIP的优势是变量声明、约束表达接近自然数学语法调试起来非常直观比手动拼矩阵方便不止一个数量级。代码主循环的逻辑分成三个模块状态测量与更新、优化问题求解、控制指令执行。核心框架如下%% MPC主循环 N_step 96; % 仿真一天96个时步每步15分钟 SOC 0.6; % 初始SOC SOC_history zeros(N_step1,1); P_grid_history zeros(N_step,1); P_bat_history zeros(N_step,1); for k 1:N_step % 1. 获取当前测量状态 SOC_history(k) SOC; % 2. 获取预测序列这里用假定的预测数据 P_load_pred load_forecast(k:kNp-1); P_pv_pred pv_forecast(k:kNp-1); price_pred tariff(k:kNp-1); % 3. 声明优化变量 P_bat sdpvar(1,Np); % 储能功率放电为正 P_grid sdpvar(1,Np); % 电网交互功率买电为正 P_curt sdpvar(1,Np); % 弃光功率 SOC_pred sdpvar(1,Np1); % 预测SOC轨迹 % 4. 约束条件 cons []; cons [cons, SOC_pred(1) SOC]; for i 1:Np % 储能递推方程 Z P_bat(i); % 中间变量处理充放电效率 cons [cons, SOC_pred(i1) SOC_pred(i) - Z*Ts/C_rated]; % 功率平衡 cons [cons, P_pv_pred(i) - P_curt(i) P_bat(i) P_grid(i) P_load_pred(i)]; % 变量边界 cons [cons, SOC_min SOC_pred(i1) SOC_max]; cons [cons, -P_bat_max P_bat(i) P_bat_max]; cons [cons, -P_grid_max P_grid(i) P_grid_max]; cons [cons, 0 P_curt(i) P_pv_pred(i)]; end % 终端SOC约束 cons [cons, SOC_pred(Np1) 0.5]; % 5. 目标函数 objective 0; for i 1:Np objective objective price_pred(i)*P_grid(i)*Ts ... lambda_bat*abs(P_bat(i))*Ts ... lambda_curt*P_curt(i)*Ts; end % 6. 求解 optimize(cons, objective, sdpsettings(solver,gurobi)); % 7. 只执行第一步 P_bat_exec value(P_bat(1)); P_grid_exec value(P_grid(1)); % 8. 用真实模型仿真一步得到实际SOC SOC SOC - P_bat_exec*Ts/(eta_chg*C_rated); % 充电时 % 若P_bat_exec为正放电则 SOC SOC - eta_dis*P_bat_exec*Ts/C_rated P_bat_history(k) P_bat_exec; P_grid_history(k) P_grid_exec; end上面代码里的充放电效率处理我简化成单行注释了实际工程中建议先判断P_bat_exec的正负再套对应效率公式。另外YALMIP里尽量用线性不等式别引入非线性约束不然求解速度会成倍下降。4. 实操中容易踩的五个坑4.1 预测时域不是越大越好很多初学者觉得预测时域越长越“全局最优”于是把Np设成96步一整天结果求解时间暴涨而且效果并不比Np24更好。原因在于预测越远预测误差越大远端那些“优化决策”本身就不可靠花大力气优化一个本来就不可靠的远端是徒劳的。一般来说预测时域设到能覆盖电价峰谷周期和储能充放一个完整循环就够了。分时电价场景下6小时到12小时的预测时域比较合理太小则储能策略容易“近视”看不到夜间低谷电价永远在峰值电价时段恐慌性买电。4.2 储能SOC的硬约束写太满直接无解我用过很多次Gurobi报infeasible最后排查下来90%是SOC约束太紧。比如你设SOC_min0.2但某个时步负荷很高、光伏很少、电网上限又顶着储能必须以-50kW充电SOC就会跌破0.2这时候优化问题直接无解模型给出Infeasible problem程序崩溃。处理办法有两种一是把SOC约束改软约束加松弛变量SOC_pred(i1) SOC_min - epsilon1(i) SOC_pred(i1) SOC_max epsilon2(i)并且把松弛变量加上惩罚项进目标函数这样即使约束被突破问题也能解只是会产生惩罚成本。二是终端SOC约束刚开始别设太高先把模型调通再加0.5这种约束。4.3 权重系数量纲没归一化结果一团糟目标函数里电价单位是元/kWh、储能成本单位也是元/kWh、弃光惩罚也是元/kWh看起来量纲一致但数值差异惊人。假设购电价是0.8元/kWh储能损耗成本是0.1元/kWh弃光惩罚是2元/kWh三个系数放一起优化器会天然偏向成本最小的那个但可能产生违背直觉的调度行为。比如弃光惩罚设得比购电价低结果就是愿意弃光也不愿意买电。这个设定有时候是故意的比如光伏特别充裕、电网交互有上限但大多数情况是因为没仔细看量纲。所以我习惯一切功率相关量先折算成分15分钟而不是小时电价也按对应的时段折算确保目标函数每一项的量纲完全统一。4.4 求解器报错Data must be numeric多半是YALMIP变量混用新手在调试YALMIP时容易犯的错是在约束或者目标函数里混入未定义的变量比如P_load_pred取的是真实数据矩阵的某一列但忘了索引结果往约束里塞了一整个向量。YALMIP对维度不匹配的处理方式是报错或者静默扩展后一种更坑你的约束矩阵维度悄悄变了结果全对不上。我的排错习惯是每写一段约束就disp一下sdpvar变量的size确认维度和预期一致。还有一点绝对值项abs(P_bat)在YALMIP里能自动引入额外变量做线性化但要在目标函数里使用别在约束里配合非线性操作容易把问题变成非凸。4.5 仿真初期的“盲目掏空”现象如果你把初始SOC设成0.9而夜间电价很低MPC会立刻以最大功率充电把SOC卡在上限附近因为未来白天的预测让它觉得“多存点就是赚”。这本身没问题问题在于如果在滚动优化里不设终端SOC约束每个周期都会倾尽所能把SOC推到边界仿真结果看起来像储能疯了一样极不自然。我的做法是给终端SOC加一个折扣惩罚项鼓励MPC在预测时域末端保有一定电量。这个技巧在工程上非常常用能有效避免储能策略过于“贪婪”。5. 仿真结果怎么分析才有说服力5.1 对比策略怎么设计才能显出MPC的价值单独跑一条MPC的SOC曲线出来并不能说明算法好。要有说服力必须做对照实验。我常用的三组基准对比是无预测的即时策略比如按实时电价高低决定充放电、单次开环全局优化、以及MPC滚动优化。即时策略很好实现电价低就充、电价高就放SOC到了边界就停下。开环优化就是把全天预测数据一次性建成一个大优化问题求解这个作为“理论上限”参考非常有价值。画图的时候把三组数据的日运行成本、可再生能源利用率、储能充放电次数列一张表。一般情况下MPC的成本介于即时策略和开环最优之间但会明显优于即时策略且更接近开环最优。如果MPC比开环还便宜别急着高兴大概率是代码bug或者目标函数写错了。5.2 核心评估指标我建议至少盯四个第一个是日运行成本这是最直观的经济指标。第二个是可再生能源消纳率算弃光的能量占理论可发能量的比例MPC如果频繁弃光说明约束或者惩罚项设置有猫腻。第三个是储能等效循环次数连续运行时能看出来算法是否过度激进地充放电锂电池循环寿命跟这个直接相关工程上特别在意。第四个是联络线功率的波动率峰值负载率MPC的优势之一就是能把电网交互功率削峰填谷如果联络线功率曲线毛刺特别多说明目标函数里缺了“平抑”项。5.3 从曲线到系统行为的异常检查我跑完仿真之后习惯先看储能SOC曲线。正常的MPC调度SOC应该在上下限之间平滑波动充电段、放电段、维持段清晰可辨。如果SOC曲线出现频繁锯齿状抖动多半是目标函数里储能损耗项太小MPC把储能当成“免费资源”反复充放如果SOC在预测时域末端一直贴着最低值说明终端约束或终端惩罚没设好。负荷和光伏预测误差大的时候还要检查实际功率平衡是否被破坏。我一般在仿真里加入±20%的预测误差扰动观察MPC能否把SOC轨迹修正回来。修正动作应该是平滑的如果出现大幅振荡调试的目标函数权重时优先调大反馈项或减小预测时域。6. 再往前走一步几种常见的MPC扩展6.1 分布式MPC与多微网协调单微网有单微网的玩法多微网互联时MPC的建模思路要变。分布式MPC的思路是把一个大全局问题拆成每个微网的子问题微网之间通过共享边界功率或价格信息迭代协调。Matlab里实现分布式MPC可以用YALMIP为每个子微网单独建模型然后外层加一个协调循环让各微网交换联络线功率计划直到收敛。计算量大不少但对通信依赖低更像真实系统。6.2 鲁棒MPC与随机MPC面对预测不确定性比直接MPC更进阶的是鲁棒MPC和随机MPC。鲁棒MPC的做法是假设光伏和负荷预测误差有界用不确定集描述然后优化最坏情况下的成本。随机MPC则利用预测误差的概率分布把机会约束松弛成确定性约束。这两个方向写论文价值高代码实现也比基本MPC复杂不少需要处理场景生成、情景缩减这些额外工作。6.3 多目标MPC纯成本最小有时候会引发过度平抑波动导致的储能寿命损失。多目标MPC就是在目标函数里同时考虑经济性、储能健康、碳排放几个目标的加权组合。用Matlab做多目标MPC最简单的方式是加权和法但对权重的选择非常敏感。工程上常用NSGA-II这类算法离线扫一组权重生成帕累托前沿然后在线调度时根据实际偏好选点跑MPC。这个方向更适合作为后续研究扩展初版代码不推荐直接上。说一个我自己的体会MPC这套东西90%的精力花在建模和调参上真正的控制算法反而简单。最开始做这个项目时我花了整整一周让MPC的SOC曲线“看起来合理”后来发现只是省了忽略检查SOC是否触碰终端下限这个问题。所以看代码跑通不是终点多从系统行为去观察、去质疑比多写几个模型更管用。如果你正在跑MPC微电网调度的仿真或者刚被某个infeasible错误卡住拿着这篇文章慢慢调基本能绕开我踩过的大多数坑。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进