
做微电网调度有一段时间的人应该都会有同感真正想把一套调度策略落地难点往往不在建模本身而卡在“实时性”和“不确定性”这两座大山上。传统开环优化能算出未来一天的机组出力和储能计划但光伏、负荷的预测曲线总会被现实打脸——天气一变、预测偏差一大日前的原计划就基本作废。模型预测控制MPC之所以在微电网调度优化里越来越受重视就是因为它把“预测 优化 反馈”拧成了一股绳每一步都在用最新测量值重新修正控制计划。这篇文章围绕我在 Matlab 里实现的一整套基于 MPC 的微电网调度优化框架展开把问题建模、控制参数选取、代码结构、仿真验证和调试踩坑都摊开讲清楚。适合正在做微电网能量管理、储能调度、虚拟电厂相关课题的读者参考也适合刚接触 MPC、想快速上手仿真验证的同学作为入门路径。1. 微电网调度难在哪目标、约束与不确定性1.1 表面是优化问题本质是多目标权衡微电网调度这件事说穿了就是在满足负荷需求和安全约束的前提下决定每个时段“谁发电、发多少、储能充还是放、跟主网交换多少功率”。但实际一建模你就会发现这从来不是一个单目标问题。最典型的目标至少有三个维度。经济性是最直接的目标包括柴油发电机或燃气轮机的燃料成本、从主网购电的电费、向主网售电的收益以及储能充放电带来的电池寿命折损。安全性和供电质量则是硬约束比如电压、频率、线路容量、储能SOC上下限、机组出力上下限。第三个维度往往是环保或新能源消纳比如优先消纳光伏和风电、减少弃光弃风。我在实际建模时习惯把第二个和第三个目标折算成惩罚项放进目标函数而不是单独做多目标优化。比如给弃电量一个惩罚价格给电池深度充放一个惩罚系数这样既能照顾实际偏好又避免多目标Pareto前沿求解带来的计算负担。对于微电网这种规模不大的系统这种“字典式权重”的做法工程上最实用也最容易调。1.2 为什么开环优化不够用很多入门者会先做一个“日前调度”给定明天 24 小时的光伏、负荷预测曲线一次性解出一个全天 96 个时段的机组出力和储能计划。这种做法的问题不是模型错而是它的假设太乐观——它假设预测曲线是准的假设系统不会遭遇突发扰动。但在真实微电网里光伏出力可能因为云层遮挡在几分钟内掉一半负荷也可能因为一台大设备启动突然跳增。开环优化把未来 24 小时的计划全部锁死一旦实际跟预测偏差拉大储能SOC就会慢慢偏离预期甚至越界柴油机可能被迫频繁启停联络线功率也可能超出合同上限。于是问题变成能不能每隔一段时间就重新算一次优化每次只取第一步执行下次再基于最新实测数据重新算这就是模型预测控制滚动优化的朴素思路也是它相对传统开环优化的核心区别。2. MPC凭什么适合做调度从开环到闭环的思路转变2.1 预测模型、滚动优化、反馈校正三件套理解 MPC 其实不用搬公式想一个开车导航的例子就够了。出发前你规划了一条全程路线这相当于日前开环计划。但路上遇到堵车、临时封路导航会基于当前位置重新规划接下来的路线每次只引导你走下一段路。这个“每到一个路口就看一眼最新路况、重新算一遍未来路径”的过程就是 MPC 的滚动优化。具体到微电网有三个环节缺一不可。预测模型用来描述系统状态怎么演化一般包括光伏预测、负荷预测以及储能SOC的状态递推方程。滚动优化就是在每个采样时刻基于当前实测状态对未来有限时域比如未来两小时内的控制序列求解一个带约束优化问题但只执行第一步。反馈校正则保证控制器不会“一条道走到黑”它用当前时刻的实际量测值作为下一轮优化的初值把之前的预测误差即时修正掉。这三个环节合起来让 MPC 既能像开环优化一样提前规划又能像闭环控制一样抵抗扰动这是它适合微电网调度最根本的原因。2.2 微电网调度问题如何写成MPC标准形式要把被控对象塞进 MPC 框架第一步是把物理过程写成离散状态空间方程。对典型的并网微电网我习惯这样选变量。控制变量是每个采样时刻需要决策的量通常包括储能充放电功率、柴油机出力、与主网的交换功率。状态变量主要是储能荷电状态SOC因为它是唯一带“记忆”的动态变量。有些模型还会把机组爬坡、温度等纳进状态但第一版不建议搞太复杂。离散状态递推方程的核心就一条SOC(k1) SOC(k) - η_c·P_ch(k)·Ts / Cap P_dis(k)·Ts / (η_d·Cap)这里的 P_ch 和 P_dis 分别是充电功率和放电功率η_c、η_d 是充放电效率Ts 是采样周期Cap 是电池容量。这条式子看着简单但特别容易出错因为充放电效率放的位置不对仿真跑起来 SOC 会莫名其妙地漂移。功率平衡约束则是每个采样时刻都要满足的代数方程P_pv(k) P_wind(k) P_gen(k) P_grid(k) P_bat(k) P_load(k)目标函数我一般写成四段加权和柴油机燃料成本、与主网购售电的净费用、储能充放电损耗惩罚项、以及SOC偏离参考值的软约束项。值得多提一句的是MPC 和控制变量相关的约束例如储能功率上下限、SOC上下限、机组出力上限、联络线传输上限都必须对预测时域内的每一个步长都列出来不能只约束当前时刻。这是新手代码里最常见的漏洞——只约束了第一步或者只约束了稳态点结果仿真结果跑出来全是约束违反而不自知。2.3 预测时域、控制时域和采样周期的经验取值参数怎么取几乎是每个做 MPC 的人都会问的问题。先说采样周期。微电网调度跟电机控制那种毫秒级 MPC 完全是两个物种日内调度用 15 分钟或 30 分钟比较常见。15 分钟对应一天 96 个点既能匹配光伏辐照度和负荷波动的特征尺度又不至于让求解规模爆炸。如果做实时平衡控制可能缩到 5 分钟甚至 1 分钟但那就得配合更快的求解器。预测时域 Np 我一般取 8 到 24。取 8预测窗口是两小时取 24预测窗口到六小时。太短MPC 只看得到眼前遇到负荷爬坡容易反应过度太长预测误差累积反而让优化结果失真求解时间也跟着涨。控制时域 Nc 很多情况下可以直接跟 Np 相等因为微电网的决策变量本身就是逐时段的不需要像连续过程控制那样去参数化降维。我自己的经验是第一版先用 Ts15、Np8、Nc8 跑通闭环确认没问题后再慢慢拉伸 Np 对比效果。不要一上来就追求大时域否则调试的时候光是算得慢就能让你怀疑人生。3. Matlab实现框架搭建与核心代码拆解3.1 先搭骨架数据、模型、求解器三分离做 Matlab 代码实现我最重视的一件事就是“数据、模型、求解器三分离”。很多人一上来就把光伏曲线、负荷曲线、约束、目标函数全塞进一个脚本最后改一个参数要翻几十行代码调试周期拖得极长。我的项目结构基本这样组织mpc_microgrid/ ├── main.m % 主循环控制仿真流程 ├── data/ │ ├── load_profile.mat % 负荷预测与实测数据 │ └── pv_profile.mat % 光伏预测与实测数据 ├── models/ │ ├── build_mpc_matrix.m % 构造优化问题的矩阵 │ ├── cost_function.m % 目标函数各成本分量 │ └── constraints.m % 等式与不等式约束 ├── solver/ │ ├── solve_qp.m % 调用quadprog │ └── mpc_step.m % 单步滚动优化 └── utils/ └── plot_results.m % 结果可视化数据文件统一读入模型文件夹只负责把约束和目标函数写成矩阵形式求解器负责调 quadprog 或 fmincon。改参数的时候只需要动 main.m 顶部的参数区不会牵连其他代码。3.2 滚动优化的主循环怎么写整个 MPC 仿真的核心就是一段从仿真起点走到终点的大循环。每个采样时刻循环里做四件事读入当前实测状态、生成未来 Np 步的预测序列、求解当前时段的优化问题、只取第一步控制量执行到下一时刻。%% MPC仿真主循环 Ts 15; % 采样周期分钟 Np 8; % 预测时域步 T 96; % 全天总调度时段24h*4 % 状态初值 SOC(1) 0.5; % 储能初始SOC u_hist zeros(3, T); % 控制量记录储能、机组、联络线 for k 1:T-Np % 1. 读取当前实测信息模拟测量得到的当前值 x0 SOC(k); t_now k * Ts; % 2. 生成未来Np步预测序列此处为简化直接取已知曲线 Ppv_pred Ppv_forecast(k:kNp-1); Pload_pred Pload_forecast(k:kNp-1); % 3. 调用单步MPC求解函数 [u_seq, SOC_seq] mpc_step(x0, Ppv_pred, Pload_pred, MPC_param); % 4. 只执行第一步控制量更新系统状态 u_hist(:, k) u_seq(:, 1); SOC(k1) SOC_seq(2); % 用模型递推做纯仿真时 % 如果有更精细的仿真模型这里应该用实际仿真输出更新SOC end这里有个特别容易犯的错纯仿真时第二步的 SOC 到底是用 MPC 内部模型的递推结果还是用外部“更真实”的仿真模型我的做法是只要不是算法验证阶段就应该在系统模型和预测模型之间故意制造一点差异比如给负荷实测曲线加随机扰动、给 SOC 递推加一点过程噪声。这样才能验证 MPC 反馈校正的效果而不是让控制器和仿真世界共用同一个“上帝模型”自欺欺人。3.3 目标函数和约束的实现细节MPC 每步要解的优化问题如果模型和约束都是线性的目标函数是二次的那就直接转成标准二次规划用 quadprog 求解。这比用 fmincon 解非线性问题快得多也稳定得多。我习惯用矩阵形式把问题拼出来。决策变量 x 按时间展开x [u(1); u(2); ...; u(Np)]其中每个 u(k) 是一个向量包含第 k 步的储能功率、柴油机出力、联络线功率。SOC 则是状态变量通过递推方程表达成 u 的线性函数然后约掉最后的优化变量就只剩控制量序列矩阵维度大幅下降。目标函数写成J u * H * u f * u这里的 H 来自储能的损耗惩罚项和购电成本二次项f 来自燃料成本、购电电价的线性项。电池损耗我通常用充放电功率的平方项近似因为深度大倍率充放会加速寿命衰减用二次惩罚比线性项更能体现这种“越猛越亏”的特性。约束分两块。等式约束是功率平衡它的本质是给定预测的光伏和负荷控制量之间必须满足代数关系。不等式约束则包括储能功率上下限、SOC上下限、机组出力范围、联络线功率限制。function [u_seq, SOC_seq] mpc_step(x0, Ppv_pred, Pload_pred, param) % 简化的MPC求解函数示意核心逻辑 Np param.Np; % 决策变量边界依次为储能功率、机组出力、联络线功率 lb [param.Pbat_min * ones(Np,1); param.Pgen_min * ones(Np,1); param.Pgrid_min * ones(Np,1)]; ub [param.Pbat_max * ones(Np,1); param.Pgen_max * ones(Np,1); param.Pgrid_max * ones(Np,1)]; % 目标函数这里用价格向量构造f price_buy param.price_buy * ones(Np,1); price_sell param.price_sell * ones(Np,1); cost_fuel param.fuel_cost * ones(Np,1); % 为了简洁省去矩阵展开重点看思路 H blkdiag(param.lambda_bat*eye(Np), 0*eye(Np), 0*eye(Np)); f [zeros(Np,1); cost_fuel; price_buy]; % 大概意会实际还要处理购售电分段 % 求解 options optimoptions(quadprog, Display, off); [x_opt, ~] quadprog(H, f, [], [], Aeq, beq, lb, ub, [], options); % 解析结果返回第一时刻控制序列和SOC轨迹 u_seq reshape(x_opt, [3, Np]); SOC_seq zeros(1, Np1); SOC_seq(1) x0; for i 1:Np SOC_seq(i1) SOC_seq(i) - u_seq(1,i)*param.Ts/param.Cap/param.eta_ch ...; end end抱歉这段代码里我是“意会式”写法真跑起来还需要补全很多矩阵推导但核心思想很明确把 SOC 通过递推关系代入功率平衡然后整理成标准的二次规划结构。新手最容易掉进去的坑是约束矩阵 Aeq、beq 维度对不上我建议先构造一个小规模例子Np3把矩阵打印出来人工验算一遍等式约束比对着错误提示瞎猜高效得多。4. 仿真结果怎么评价对比实验与调参心得4.1 让结果有说服力的三组对比仿真做完最怕的就是只贴一条“我的策略 SOC 曲线挺平滑”就完事。评价调度策略好不好必须有对照。我常用三组对比。第一组是 MPC 调度策略对“开环日前计划”的对比。用同一组预测数据一条曲线按日前计划开环执行一条曲线跑 MPC 闭环比较总运行成本、联络线冲击幅度和 SOC 越界情况。第二组是引入预测误差后的对比。给负荷和光伏的实际曲线加扰动看 MPC 在反馈校正作用下能不能把系统拉回安全范围而开环策略会不会越界。这是 MPC 最核心的卖点一定要单独做。第三组是不同的权重参数灵敏性分析。比如把电池损耗权重提高一倍看系统是不是更少深度充放把弃光惩罚提高看是不是消纳率更高。这种分析能让读者看出来你对模型的理解而不是只会跑代码。4.2 从SOC和联络线功率曲线看调度质量结果图我一般至少看四张功率平衡图、SOC 曲线、机组与联络线功率曲线、成本累积曲线。功率平衡图是最直观的。光伏、负荷、储能出力和联络线功率叠在一起正常情况下每个时刻都应该满足“源 荷”。如果某些时刻功率对不上说明约束矩阵写错了这是最容易发现代码 bug 的窗口。SOC 曲线重点看两件事。一是是否始终在上下限内二是变化是否平滑。如果 SOC 曲线频繁顶到边界还硬生生反弹说明惩罚权重或者时域设置不合理。一个调得好 MPCSOC 应该在目标值附近缓幅波动而不是跟锯齿一样来回拉。联络线功率曲线则看“毛刺”。如果每步都在买电和卖电之间反复切换说明电价信号或者预测扰动使得目标函数在边界附近抖动这时候可以加一个联络线变化率约束或者小幅度的死区系统会平稳很多。4.3 权重因子和时域参数的调参逻辑调参这件事网上能查到的说法很多但真正有效率的路径只有一个从“量纲和尺度”入手而不是凭感觉瞎试。举个例子。如果你把柴油机燃料成本设在每千瓦时 0.8 元储能损耗权重却设成 10000那优化器会疯狂回避充放电最后 SOC 推成一条直线整个储能废掉。反过来损耗权重设成 0.0001储能就会被滥用到每次波动都深度充放。我的经验是先把所有成本折算成“每千瓦时的价差”换算成同一量纲。然后赋一组初始权重跑一次仿真观察 SOC 的方差和充放电次数再根据物理预期微调。比如我希望储能在低电价时充电、高电价时放电那权重的设置必须保证价格差能覆盖损耗代价。如果价格差只有几分钱损耗惩罚一高系统就不充放了这是符合预期的行为不一定是 bug。预测时域 Np 的调整也有规律可循。小 Np 系统反应快但“短视”大 Np 更平滑但计算慢。实际测试时我从 Np4 一路试到 Np24发现成本下降在 Np 从 4 提到 8 时最明显之后趋于饱和。如果追求计算速度Np8 往往是性价比最高的甜点区。5. 调试中常见的坑与排查速查表5.1 几个典型问题与解决记录我自己从零把这个项目写通前前后后遇到不少问题挑几个有代表性的列一下。最典型的是求解器报 infeasible。第一次遇到这个错误很多人会下意识怀疑求解器但我排查后发现八成是约束自相矛盾。比如 SOC 上限设了 0.8而功率平衡约束又要求某个时段必须再放 100kW导致状态递推算出 SOC 超出上限。解决思路有两层第一层是检查当前时刻 SOC 初值和未来 Np 步内功率需求是否真的可行第二层是给 SOC 约束加松弛变量改为软约束让 MPC 在极端情况下选择最轻的“违规”而不是直接无解崩溃。第二个常见问题是 SOC 曲线持续漂移。明明一天结束 SOC 应该回到 0.5结果仿真完变成 0.2。原因通常是充放电效率建模不一致或者目标函数里缺少 SOC 日终恢复项。我后来在目标函数里加上末端SOC跟踪惩罚并保证每次充电、放电都折算相同的效率系数漂移问题就消失了。第三个是结果振荡。光伏和负荷预测稍有扰动联络线功率就在正负之间疯狂跳变。我在约束里加了联络线相邻时段变化率限制并在目标函数中对联络线变化率加了小平滑项振荡立刻收敛很多。注意这里的关键是光靠提高储能损耗权重也能缓和振荡但那会让储能不敢动作治标不治本。5.2 从零搭建时的分步调试建议给刚开始做这个方向的同学一个可执行的调试路径。第一步先做“退化成开环”的验证。把 MPC 的预测时域拉长到全天NpT滚动只做一次这时 MPC 退化成日前开环优化。先用这个模式跑出合理的计划曲线确认目标函数和约束本身没有大问题。第二步再恢复正常滚动。把 Np 缩到 8启动逐时循环先不引入预测误差看闭环结果和开环是否基本一致。如果此时出现矛盾通常是滚动更新的初值传递写错了。第三步才引入预测误差和扰动。给预测曲线叠加随机偏差给实际负荷加入随机波动验证反馈校正能力。第四步做参数敏感性分析和对比实验。这一步就是为了写论文或者做汇报准备素材也顺便检验你对系统的理解。这样分步走每一步如果出问题范围都限定得很小不会出现“哪哪都不对”的绝望状态。排查速查表现象可能原因排查方向 / 解决手段求解器报 infeasibleSOC硬约束与功率需求冲突加软约束松弛检查SOC初值是否落在可行域SOC曲线全天漂移充放电效率建模不一致缺末端项统一效率折算公式目标函数加SOC终值跟踪联络线功率频繁振荡预测扰动直接冲击目标函数加联络线变化率限制目标函数加平滑项求解时间太长Np过大或约束矩阵过于密集尝试Np8压缩决策变量换quadprog成本对比没有优势目标函数缺关键项或权重失衡检查电价时段划分确认损耗惩罚量纲一致最后分享一点个人感觉这套项目做完我最大的体会是MPC 的理论框架看着高大上但真正的功力全在“系统的物理感知”和“矩阵层面的耐心调试”上。如果只有公式没有代码你永远不知道 SOC 递推方程里一个小数点位置错误会让一天的仿真结果全部不可信如果只有代码没有理论你也很难解释清楚为什么权重系数要这么调。建议刚开始接触这个方向的同学不要急着追求复杂的模型和花哨的算法先用一个储能加一条联络线的小系统把“预测—滚动—反馈”这个闭环跑通跑稳再逐步加光伏、加不确定性、加多目标权衡。这套骨架一旦立住了后续想扩展多微电网协调、电动汽车充放电或者日内实时调度都只是在这个框架上填不同的约束和对象而已。