在微电网调度中的Matlab实现与实战解析)
微电网调度这个领域我见到的很多初学者一上来就是PID、就是线性规划定值优化等真把模型建起来跑仿真才发现自己处理不了光伏波动、负荷突变这类问题。后来接触了模型预测控制MPC之后我才意识到这类问题正确的打开方式是什么把优化放进一个不断滚动、不断校正的闭环里而不是一次性算完拉倒。这篇博文我就结合自己的Matlab实现经历把MPC用于微电网调度的思路、模型怎么搭、代码怎么写、坑在哪里一次说清楚。这篇内容适合正在做微电网能量管理、储能充放电策略、或者对MPC算法感兴趣但不知道怎么在Matlab里落地的读者。我会尽量把步骤拆到可以直接照做的程度也会讲清楚每一步为什么这么做。1. 微电网调度到底难在哪MPC凭什么比传统方法更合适1.1 微电网调度的本质在不确定性中做决策微电网调度优化的核心不是“算出一个最优的发电计划表”而是要在光伏出力、负荷需求、电价信号这些都存在不可准确预知波动的前提下依然能够保证系统安全、经济运行。这里面有两个关键字一个是“经济”一个是“安全”。经济性要求你在电价低的时候多买电、多充电电价高的时候多放电、少购电安全性要求你不管光伏怎么跳、负荷怎么变电压不越限、功率不失衡、储能不过充过放。这两个目标放在一起就会产生一个很有意思的矛盾如果你只做一个开环的日前优化把一整天的调度计划一次性求解出来一旦实际的光伏或者负荷跟预测值出现偏差这在微电网里几乎是一定的那这份计划就废了要么经济性大打折扣要么某些时段直接触碰安全约束。这就是我一开始踩坑的地方。我最早用传统的最优潮流或者线性规划做日前调度在预测完全准确的理想假设下结果确实非常漂亮成本曲线也是很平滑的最优解。但一旦把真实波动数据灌进去效果立刻拉胯储能SOC频繁逼近上下限有些时段的购电功率甚至出现越限。后来我才明白问题不是求解器的精度不够而是开环优化的结构本身就决定了它无法应对不确定性。MPC的思路则完全不同它不是一次性地决定未来24小时的所有动作而是只决定未来一小段时间比如15分钟一个步长未来1小时的动作执行完当前这一步之后用最新的实测数据重新预测、重新优化。这种“边走边算、算完就走”的方式天然就对预测误差有容忍度因为每走一步都在拿真实反馈来纠偏。1.2 MPC的三个核心机制预测模型、滚动优化、反馈校正MPC在微电网调度中的落地本质上就是把三个机制组合起来用预测模型用一个数学模型描述微电网的运行规律给定当前状态和未来的控制输入比如储能充放电功率、与主网交互功率就能推算出未来一段时间的系统状态比如SOC、电压、线路潮流。滚动优化每次只优化未来一个有限时域内的控制序列但只执行第一个控制量等到下一个采样时刻把时域往前推一步重新优化一遍。反馈校正每个采样时刻用实测的系统状态去更新模型的初值或者修正模型预测的偏差保证下一轮优化是从“真实状态”出发而不是从“理想状态”出发。这三点对应的就是你在任何MPC教材里都会看到的“预测—优化—校正”三步闭环。放到微电网场景里你可以很直观地理解为调度员不是把一天的指令全部下发下去而是每个15分钟重新召集一次调度会议看看现在实际的光伏是多少、电池还剩多少电、负荷又涨了多少然后重新定未来1小时的最优计划。1.3 为什么说MPC和微电网是天然匹配的我在做这个项目之前一直觉得MPC被吹得太神等真正跑通之后才认同这种说法。微电网这个对象本身有几个特点决定了它非常适合用MPC采样周期短天然适合“滚动”操作。微电网调度一般不需要做到毫秒级15分钟、5分钟甚至1分钟都可以。这个时间尺度下MPC的计算量完全不是问题。约束条件多且必须硬性满足。储能SOC上下限、充放电功率限值、购售电功率上限这些都是硬约束MPC处理约束的方式比传统PID或者启发式算法要自然得多。对象具有快变动态和慢变动态的耦合。光伏、负荷是快变的储能SOC是慢变的MPC的预测模型天然可以同时包含这两类状态。所以从选型的角度MPC不是备选项而是微电网调度这类问题一个非常顺理成章的落点。接下来我重点说一下模型本身怎么搭建这是整个Matlab实现里最基础也最容易被忽视的部分。2. 调度模型怎么搭状态空间、目标函数与约束的数学表达2.1 预测模型选择状态空间模型MPC的预测模型有很多种写法传递函数、阶跃响应、状态空间都可以。但我在Matlab实现里强烈建议用状态空间模型原因有三点一是状态空间模型可以很自然地处理多输入多输出系统微电网里输入有储能功率、与主网交互功率输出有各个节点的电压幅值、SOC等二是后面用YALMIP或者quadprog求解时状态空间模型可以直接嵌入约束条件三是它的离散化在Matlab里太方便了一个c2dm或者ss命令就搞定。对于一个简化的微电网模型我会把状态变量选为储能系统的荷电状态SOC以及某些关键节点的电压偏差量。控制输入则是储能充放电功率和微电网与主网的交换功率。为了简单起见假设一个单母线微电网只考虑有功功率平衡那么离散状态空间模型可以写成SOC(k1) SOC(k) - (η_charge * P_ch(k) - P_dis(k) / η_discharge) * Δt / E_capacity P_grid(k) P_load(k) - P_pv(k) P_bat(k)这里P_bat等于充电功率减去放电功率充电为正、放电为负η_charge和η_discharge分别是充放电效率E_capacity是储能容量Δt是采样时间。第一个式子描述SOC的演变这是MPC预测模型里的核心动态方程第二个式子是功率平衡关系它不是一个动态方程而是一个代数约束在每个时刻都必须成立。在状态空间模型里后者往往被写进等式约束中。2.2 目标函数经济性与储能寿命如何一起考虑调度优化的目标函数一般不会只写一个“运行成本最小”而是要把购电成本、售电收益、储能老化成本等几项加权在一起。我自己用的目标函数形式如下min J Σ_{k0}^{Np-1} [ c_buy(k) * max(P_grid(k), 0) * Δt - c_sell(k) * max(-P_grid(k), 0) * Δt λ * P_bat(k)^2 ]第一项是向主网购电的成本只有P_grid为正从电网购电时才产生第二项是向主网售电的收益只有当P_grid为负向电网送电时才生效第三项是储能充放电功率的惩罚项用来抑制电池频繁充放或过大的瞬时功率延长电池寿命。用max函数写目标函数在求解时会带来不可导和非线性问题。在Matlab的YALMIP里我一般直接用value判断或者引入辅助变量把它转成线性规划可以处理的形式。更简单的方式是不直接用max(P_grid,0)而是把购电和售电拆成两个非负变量P_buy(k) 0 P_sell(k) 0 P_grid(k) P_buy(k) - P_sell(k)这样目标函数就变成min J Σ ( c_buy(k) * P_buy(k) - c_sell(k) * P_sell(k) ) * Δt λ * P_bat(k)^2这里有个技巧如果同一时刻购电价低于售电价求解器就会“套利”同时买入和卖出。所以必须加一个互补约束或者一个足够小的惩罚项来避免这种无意义的来回倒卖。在工程实现中我见过有同行直接用P_buy * P_sell 0这种非凸约束但这会让问题变成非凸规划求解变慢。更稳妥的做法是在目标函数里给P_buy P_sell加一个很小的正系数这样一来无意义的同买同卖会因为微小代价而被排除。2.3 约束条件哪些必须硬性写进MPC微电网调度优化里的约束条件我认为重要性排序是等式约束功率平衡 储能SOC限值 充放电功率限制 与主网交换功率限制。功率平衡约束每个时刻都必须满足P_grid(k) P_load(k) - P_pv(k) P_bat(k)。这个约束如果不满足后面所有优化结果都没有物理意义。储能SOC约束SOC_min ≤ SOC(k) ≤ SOC_max。我一般取0.2到0.9注意不要取到0到1因为电池在实际运行中不允许完全放空或者完全充满这既是为了安全也是为了延长寿命。充放电功率约束-P_ch_max ≤ P_bat(k) ≤ P_dis_max。这个约束需要放电和充电分开写或者用一个统一限幅具体看电池型号。与主网交换功率约束-P_grid_max ≤ P_grid(k) ≤ P_grid_max。这个值取决于变压器容量或者并网协议是硬约束不能超。把这些约束全部按预测时域Np展开就构成了一组线性不等式和线性等式配合二次目标函数整个问题就是一个标准的二次规划QP问题Matlab里用quadprog就能直接求解。如果你想加更多非线性约束比如电池效率随SOC变化那就要用非线性MPC求解器会换成fmincon但计算量会显著上升。我建议第一版先做线性MPC跑通流程之后再逐步加复杂度。3. 滚动优化和反馈校正在Matlab里怎么落地3.1 预测时域和控制时域怎么定预测时域Np和控制时域Nc是MPC里最直观又最需要经验的两个参数。它们直接影响控制效果和计算耗时。控制时域Nc表示我们允许控制量在未来多少个步长内变化超过这个步长后控制量保持不变。对于微电网储能调度Nc一般不需要太大甚至Nc1或者Nc2都可以工作。因为储能功率是慢变量允许它在很长的未来持续变化没有太大意义反而会增加决策变量数量。预测时域Np则决定了MPC“看得多远”。Np太小控制器就只顾眼前对电价高峰或者负荷峰值没有预判能力Np太大计算量增大而且远处的预测误差很大远期信息不仅没有帮助还可能干扰近期决策。我做实验时对比过对于15分钟采样周期的微电网调度Np取8到16对应2到4小时是比较合理的区间。如果采样周期是5分钟那Np可以取24到48对应的也是2到4小时。具体的取值规律很难一概而论我一般会做一个简单的扫描固定其余参数让Np从4逐渐增加到48观察总运行成本和求解时间的变化曲线。通常曲线会在某个区间出现拐点拐点之后的Np再增大成本改善很有限但耗时明显增加。那个拐点就是适合你这个系统的预测时域。3.2 反馈校正从“理想初值”回到“真实初值”这是我觉得很多初学者最不重视、但实际影响最大的一环。MPC滚动优化有一个前提每一轮优化都应当从系统当前的真实状态出发。但在我的第一个版本里模型是理想化的SOC状态完全按照模型方程往下推结果就是开环仿真跑出来的SOC和加了MPC闭环之后的结果一模一样完全没有体现出MPC的优势。后来我才意识到我压根没有做反馈校正——每个采样时刻我没有用“真实SOC测量值”去覆盖模型的预测值而是继续用上次计算的预测结果往下走。正确的做法是在每个采样时刻k先读取或者仿真生成当前真实的SOC_real(k)然后把它赋值给MPC优化问题中SOC状态变量的初始值。这样即使模型存在误差比如电池效率设错了、自放电没建模控制器也能通过每次的实测值把状态拉回真实轨迹。如果系统里有部分状态无法直接测量比如某些节点的电压你可以用一个状态观测器比如Kalman滤波来估计当前状态再注入MPC。对于微电网调度这个尺度的问题观测器不是必须的但如果你进阶到更高频的电压控制场景就需要补上。3.3 求解器选型YALMIP加quadprog还是fminconMatlab里求解MPC的途径很多我的建议是分阶段选型。对于第一版模型我强烈推荐用YALMIP建模后端求解器先选quadprog。YALMIP的建模语言非常接近数学表达式基本上你在论文里怎么写公式代码里就怎么写能减少大量调试时间。而且YALMIP会自动把你的约束和目标函数整理成标准型不需要自己处理变量展开和矩阵拼装。一旦模型写对换成商业求解器比如Gurobi、CPLEX也是改一行配置的事。示例代码结构如下% 定义优化变量预测时域为Np P_bat sdpvar(1, Np); P_buy sdpvar(1, Np); P_sell sdpvar(1, Np); SOC sdpvar(1, Np1); P_grid P_buy - P_sell; % 初始SOC约束 Constraints [SOC(1) SOC_real]; for k 1:Np Constraints [Constraints, SOC(k1) SOC(k) - (P_bat(k)/eta_dis - eta_ch*P_bat(k))*dt/Cap]; Constraints [Constraints, P_grid(k) P_load(k) - P_pv(k) P_bat(k)]; Constraints [Constraints, P_buy(k) 0, P_sell(k) 0]; Constraints [Constraints, SOC_min SOC(k1) SOC_max]; Constraints [Constraints, -P_bat_max P_bat(k) P_bat_max]; Constraints [Constraints, -P_grid_max P_grid(k) P_grid_max]; end Objective sum(c_buy .* P_buy - c_sell .* P_sell) * dt lambda * sum(P_bat.^2); % 配置求解器并求解 ops sdpsettings(solver, quadprog, verbose, 0); optimize(Constraints, Objective, ops); % 只取第一步控制量 P_bat_opt value(P_bat(1));如果你的目标函数里有非线性的老化模型、或者约束里有非线性的效率曲线那就得升级到fmincon。但那时你最好想清楚这个非线性项对结果的影响到底有多大很多时候线性近似已经足够强行上非线性求解器会让单步求解时间从几十毫秒变成几秒滚动起来非常痛苦。4. Matlab代码实现的结构拆解与结果分析4.1 代码架构数据、模型、求解三层分离一个能长期使用的MPC调度仿真代码不应该写成一个巨型脚本。我自己的习惯是分成四层数据层存放光伏出力序列、负荷序列、电价序列以及系统参数储能容量、效率、功率限制等。模型层定义状态空间方程、约束、目标函数提供build_mpc_problem这样的函数。求解层负责调用求解器、解析结果、返回第一个控制量。仿真层模拟真实系统运行把控制量作用到“真实模型”上更新状态然后在下一个采样时刻重新调用MPC。这样的拆分有个好处当你想从线性MPC换到非线性MPC时只需要改模型层当你想把仿真改成半实物测试时只需要把仿真层的系统模型替换成实际硬件接口。代码的复用性会高很多。4.2 核心函数与关键初始化参数我给出一个初始化参数表方便你对照自己的系统调整参数取值示例说明采样时间 dt15 min 900 s调度周期预测时域 Np16对应4小时预测控制时域 Nc2控制量自由度储能容量 Cap500 kWh电池总容量充电效率 eta_ch0.95充电转换效率放电效率 eta_dis0.92放电转换效率SOC下限/上限0.2 / 0.9硬约束储能功率限值100 kW最大充放功率并网功率限值200 kW与主网交换上限老化惩罚系数 lambda0.01抑制频繁充放这些参数直接影响控制效果不要盲目照搬。特别是lambda如果设得太大储能几乎不动系统完全靠购电解决问题经济性变差如果设得太小电池会被频繁充放寿命受损。我的做法是先设一个接近0的小值跑完一段仿真后观察储能的动作次数和充放电深度再反过来调。4.3 仿真结果怎么对比才能看出MPC的价值MPC的仿真结果要体现出价值单看一条曲线是不够的。我一般会同时跑两个对照组对照组A开环最优调度。用一整天的预测数据一次性求解调度计划然后把这个计划原样执行到底不管实际光伏和负荷如何变化。对照组BMPC闭环调度。每个采样时刻使用最新的实测数据滚动优化只执行第一步。两组用同一组带随机波动的光伏/负荷数据最终对比三项指标总运行成本、SOC越限次数、并网功率越限时间。结果通常很戏剧化MPC的SOC越限次数会大幅下降并网功率也基本不会越限而开环方案的这两项指标会因为预测误差而明显恶化。总运行成本方面MPC可能略高一点但换来的是更高的安全性这个取舍在工程上是完全值得的。如果你的仿真里MPC和开环结果几乎一样那大概率是数据波动太小或者反馈校正没做对可以先把波动调大再看MPC是否能把状态拉回来。5. 让MPC稳定运行的关键经验参数、失配与求解器坑5.1 预测时域、惩罚系数和约束松弛的调节经验MPC在仿真里跑起来不难跑得稳、跑得符合预期才是难点。这里我踩过几个很典型的坑预测时域太短导致的经济性恶化。我曾经把Np设成4对应1小时结果MPC完全看不到傍晚的电价高峰储能一直在低价时段把电放光了到高峰时段只能高价购电。后来把Np调到16控制器才有足够的“远见”在低价时段留住电量整个日运行成本降了不少。约束过紧导致的无解问题。如果SOC上下限设得过窄再加上并网功率限制很紧在某些极端场景下MPC优化问题可能找不到可行解。这时要么放宽约束要么引入约束松弛变量soft constraint在目标函数里对这些越限加上很大的惩罚系数。工程上我一般用后者因为硬约束一旦无解整个仿真就会中断而松弛约束至少能让仿真继续跑下去同时约束被越限的地方也一目了然。5.2 模型失配和状态初值不准时的应对策略MPC号称有反馈校正但校正的强度受限于预测模型的准确性。如果你的模型里储能效率设成了0.95而实际只有0.85那么MPC每次预测的SOC变化都会偏乐观控制器会倾向于多放一些电长期跑下来SOC会越来越低。反馈校正虽然能把每个时刻的SOC拉到实测值但模型偏差依然会影响整个预测时域内的最优决策。应对模型失配有几种思路提高模型准确性比如把效率做成SOC的分段线性函数。在模型里加入一个偏差修正项用当前时刻的预测误差修正未来几个时刻的预测值。这是工程上最常用的一种误差补偿方式实现起来也简单对每个变量维护一个滑动平均误差。把MPC的目标函数从“跟踪参考值”改成“经济优化”让控制器在经济性和状态约束之间自己权衡对模型误差的敏感度会低一些。状态初值不准的问题在调度的场景里通常比模型失配轻一些因为SOC有比较可靠的测量。但如果你做的是多节点配电网级微电网一些节点的电压幅值未必直接可测这时候就需要加状态估计器。我建议先把SOC这类核心状态的反馈校正做好再考虑扩展。5.3 求解失败的排查思路与加速技巧MPC在Matlab里最常见的故障就是求解失败或者求解时间过长。排查思路我一般按三步走检查约束矩阵是否可行。把目标函数暂时设成常数只看约束用求解器跑一遍可行性问题。如果连可行解都找不到说明约束本身冲突比如SOC初值已经低于下限或者功率平衡要求超出了并网功率上限。检查变量维度和索引。YALMIP报错最多的地方就是维度不匹配。尤其是SOC的长度要设成Np1因为你需要从k0到kNp的完整状态轨迹。P_bat、P_grid的长度是Np和SOC(2:end)对应多一个少一个都会出问题。求解放慢时先砍约束数量和时域。把Np临时降到8去掉一些非关键约束看求解时间是否下降。如果下降明显说明你的问题规模偏大可以考虑把一些约束从硬约束改成软约束或者使用更高效的求解器。另外还有一个提速技巧在滚动优化的每个采样时刻上一轮的优化结果通常是一个很好的初值。YALMIP和quadprog都支持热启动把上一轮的解作为初始点传入可以显著减少迭代次数。对于实时性要求比较高的场景这个方法非常实用。我在跑仿真时还发现一个反直觉的情况quadprog在问题规模很小时反而比Gurobi更快因为Gurobi的初始化开销比较大。所以如果你是给学生项目做仿真quadprog完全够用没必要一上来就上重型商业求解器。最后说一点关于代码层面的个人体会。MPC这个算法看起来公式复杂真正落地时其实就是“建模—求解—更新状态”这个循环在转。最容易出错的地方反而不是算法本身而是状态变量索引错位、约束条件漏写、反馈校正没有真正接上这些细节。如果你在实现过程中发现仿真结果不合理先别急着调参数回头检查一下反馈校正有没有生效、SOC是否真的用了实测值多半问题就出在这些不起眼的地方。把这些基础打牢之后MPC的滚动优化才能真正发挥出它应对不确定性的优势。