ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

基于Python的MPC微电网调度优化实战:从滚动优化到工程落地

基于Python的MPC微电网调度优化实战:从滚动优化到工程落地 搞微电网调度的人大多经历过这种场面前一天用优化算法排出来的调度曲线看起来无懈可击第二天光伏被云一挡负荷又往上跳曲线当场废掉——该买电的时候没买该放储的时候没放月底电费单比预期高出一截。问题就出在“开环”这两个字上所有计划只基于预测做一次之后就不再修正。MPC模型预测控制恰恰是冲着这个痛点来的。这篇文章用Python把基于MPC的微电网调度优化完整走一遍从数学建模、滚动求解、仿真对比到项目里踩过的坑都讲透适合正在做微电网优化方向课题、或者刚接触光储微电网项目的朋友参考。如果你只是想要一个能跑的demo文中的核心代码可以直接抄如果你是想弄明白为什么非要用MPC、为什么模型要写成那个样子建议把前两章也看完。我不会堆一堆公式就完事重点放在“为什么这样建模、代码怎么落地、仿真怎么设计才公平”这三件事上。1. 为什么微电网调度要把“开环计划”改成“滚动优化”1.1 日前计划为什么会在现场失效先还原常见做法。调度员或者优化程序在前一天基于光伏预测、负荷预测和电价信息一次性求出未来24小时的机组出力和储能充放电计划全天按这套计划执行。这在预测完全准确的情况下当然没问题但现场不是这样。光伏预测最常见的误差是辐照突然下降十分钟内出力从800kW掉到400kW预测模型根本来不及在当天修正。负荷侧的波动虽然没有光伏那么剧烈但叠加起来计划值和实际值对不上是常态。对不上的后果有两种一种是储能被过度放电SOC提前触底晚高峰想放电时没电可用另一种是并网点实际功率偏离计划由大电网被动兜底多出来的购电成本记在电费单上。做过现场项目的人都知道所谓的“优化调度”在偏差一大时结算成本可能比简单规则策略还高就是因为开环结构没有反馈。1.2 MPC真正改变的不是“预测”而是“反馈”MPC的英文全称叫Model Predictive Control中文叫模型预测控制。很多人第一反应是“它预测能力很强”其实不是。MPC的核心动作是“每走一步重算一次”把预测只是当作出力规划的依据。具体流程是这样的在k时刻以当前实测储能状态为初值基于未来N步的预测数据求解一个有限时域优化问题得到未来N步的完整控制序列但只执行序列里的第一步等到k1时刻用实测状态更新初值重新生成预测再求解再只执行第一步。这个过程就是滚动优化。类比一下就是导航出发时规划的路线只作为参考实际驾驶中每两分钟根据实时拥堵情况重新算一次路而不是死守着出发时那条路线。所以MPC本质上是一个闭环控制器状态反馈进了优化问题的初始条件。这也是它跟“日前计划实时调整”最大的区别后者是两套系统硬拼前者把反馈直接内置在优化框架里。1.3 这套方法适合什么场景基于MPC的微电网调度比较适合源荷波动大、储能占比较高、又有多时段电价的环境。典型场景是园区光储微电网、光储充一体化场站、带柴油发电机备用的离网/并网混合微网。这些场景的共同特点是分钟级的调节需求明显但对计算时间相对宽容15分钟一个控制周期完全来得及跑完一个中等规模的优化问题。它不适合做什么不适合做秒级甚至毫秒级的动态控制那是实时控制和一次调频的范畴。MPC在这里解决的是经济性调度问题不是电力电子层面的暂态稳定问题。明白这一点后续建模才不会用力过猛。2. MPC调度优化的数学模型先写清楚问题再写代码2.1 系统拓扑与决策变量我习惯先把微电网简化成一张最小拓扑再决定哪些是决策变量、哪些是外部参数。这张最小拓扑包含光伏、负荷、储能、一个可调的柴油机组可选项、与大电网的并网点。光伏和负荷的预测值是外部参数优化程序要决定的是从电网买多少电、卖多少电、柴油机发多少电、储能充多少电、放多少电、是否需要弃掉一部分光伏。决策变量整理成表格写代码时可以照着定义cvxpy变量变量含义单位备注P_buy[t]时刻t向电网购电功率kW非负P_sell[t]时刻t向电网售电功率kW非负P_dg[t]柴油机组出力kW非负P_ch[t]储能充电功率kW非负P_dis[t]储能放电功率kW非负P_curt[t]弃光功率kW非负E[t]储能剩余能量kWh状态量一个容易被新手卡住的点储能状态用能量E[t]而不是SOC。SOC是百分比乘上容量才是能量建模时如果把SOC当状态变量约束里到处要除容量效率系数也好算错。用E更直接最后画图再转成SOC E / E_cap。2.2 目标函数每一项成本都对应一个真实问题调度优化的目标一般是“运行成本最小”。不同项目会加不同惩罚项但骨架很固定写成min Σ_{t} [ c_buy[t]·P_buy[t] − c_sell[t]·P_sell[t] f(P_dg[t]) λ_ch·P_ch[t] λ_dis·P_dis[t] M·P_curt[t] ] terminal_penalty逐项拆一下。c_buy[t]·P_buy[t]是购电成本分时电价下峰时高、谷时低−c_sell[t]·P_sell[t]是售电收益注意通常卖电价要低于买电价否则会有套利空间模型会把储能当提款机反复充放。f(P_dg[t])是柴油机发电成本真实机组是二次凸函数示例代码里我用线性近似实际项目可以分段线性化或保留二次项。λ_ch和λ_dis是储能磨损成本这一项很有用后面讲同充同放时会提到。M·P_curt[t]是弃光惩罚给得比购电成本高模型才会优先消纳光伏但也不要高到离谱否则在光伏严重过剩、电价又低的时段模型会强行把光伏卖给电网反而亏钱。最后那个terminal_penalty是终端软约束。如果只优化有限时域而不约束终点模型很可能在窗口最后把储能放空因为最后一步之后的状态它不关心。加上“终点能量尽量回到参考值”的惩罚滚动起来才有可持续性。示例里我用线性惩罚e_loss ≥ E_ref − E[N]e_loss ≥ E[N] − E_ref目标里加0.05·e_loss。2.3 约束条件功率平衡和储能动态是骨架约束条件的写法直接决定求解器能不能跑通。最少需要四类第一条是功率平衡等式。任意时刻光伏实际可用功率加柴油机出力、储能放电、购电等于负荷加充电加售电P_pv[t] − P_curt[t] P_dg[t] P_dis[t] P_buy[t] P_load[t] P_ch[t] P_sell[t]这里的P_pv[t]和P_load[t]是预测值在优化问题里作为参数传入不是决策变量。第二条是储能能量动态方程。相邻两个控制周期的能量关系E[t1] E[t] Δt·η_ch·P_ch[t] − Δt·P_dis[t] / η_disΔt是控制周期比如15分钟就是0.25小时。充放电效率分开写η_ch一般取0.95左右η_dis取0.9左右。注意这个方程是等式的核心很多同学在更新SOC时漏掉效率导致仿真里储能越用越“多电”。第三条是运行边界。储能能量上下限、充放电功率上限、柴油机出力上限、购售电功率上限都是直接写死的线性约束。第四条是储能不能同时充电和放电。数学上要表达成P_ch[t]·P_dis[t] 0这是一个非线性约束。处理有讲究简单做法是在目标函数里给充放电都加一个小成本系数求解器发现同充同放只会增加成本自然就避开严格做法是引入二元变量把问题变成混合整数线性规划。两种方案后面都会给代码。2.4 滚动窗口下的“同一套模型”上面写的是任意时刻t的约束。在MPC里k时刻求解的是从k到kN−1的窗口所有下标t都换成从k开始取。比如储能初值E[0] E_real(k)E_real(k)是k时刻从现场或者仿真器里读回来的实测储能能量这就是反馈信息的入口。预测数据则从预测序列里取k到kN−1的切片。求解出的P_ch[0]就是当前要执行的第一步其他P_ch[1]到P_ch[N−1]只是未来几步的参考。等一个控制周期结束用实测值更新E_real(k1)重新预测、重新求解。模型本身从头到尾没变变的只是初值、预测窗口和求解时刻。3. Python实现MPC调度闭环环境、预测、循环三步走3.1 环境准备与求解器选择Python侧只需要四个库cvxpy、numpy、pandas、matplotlib。安装命令pip install cvxpy numpy pandas matplotlibcvxpy是我在这个场景下最常用的建模库好处是语法贴近数学表达式不用像用Pyomo那样写一堆Set和Param抽象层。调度级问题规模很小变量几百个cvxpy默认带的开源求解器足够。如果后面的模型里加了整数变量建议显式指定HiGHS求解器它是开源MILP求解器里性能非常能打的一个。3.2 构造带预测误差的仿真数据要验证MPC效果数据里必须同时有“预测值”和“真实值”。我习惯用一条真实曲线加噪声生成预测曲线简单直接import numpy as np T 96 # 一天96个15分钟 load_real 800 200 * np.sin(np.linspace(0, 2 * np.pi, T)) np.random.normal(0, 30, T) pv_real np.maximum(0, 800 * np.sin(np.linspace(0, np.pi, T)) ** 2 np.random.normal(0, 50, T)) np.random.seed(1) load_fc load_real np.random.normal(0, 80, T) pv_fc pv_real np.random.normal(0, 80, T)这里load_fc和pv_fc是MPC滚动优化过程中能看到的数据load_real和pv_real是“现场”真实发生的值。预测误差幅度约10%已经足够说明问题。3.3 MPC主循环构建一次问题循环里只更新参数求解循环长这样E_hist [E0] P_ch_hist, P_dis_hist, P_buy_hist [], [], [] for k in range(T - N): p_buy, p_sell, p_ch, p_dis, p_dg, p_curt, E_pred \ solve_mpc(k, E_hist[-1], load_fc, pv_fc, price) # 只执行第一步 P_ch_hist.append(p_ch[0]) P_dis_hist.append(p_dis[0]) P_buy_hist.append(p_buy[0]) # 按储能动态更新状态仿真中认为储能按指令执行 E_next E_hist[-1] dt * eta_ch * p_ch[0] - dt * p_dis[0] / eta_dis E_next min(max(E_next, E_min), E_max) E_hist.append(E_next)关键点只有一个每个k循环里重新求解一遍问题但每次只取第一步。E_hist[-1]就是当前实测储能能量它把反馈带进了下一次优化。如果控制周期是15分钟一天下来T−N1≈96次求解每次都是LP或MILP规模计算时间完全不是问题。3.4 核心建模函数solve_mpcsolve_mpc是核心我给出一个能跑通的骨架def solve_mpc(k, E_start, load_fc, pv_fc, price): N 24 dt 0.25 E_max, E_min 2000.0, 400.0 E_ref 1000.0 P_ch_max P_dis_max 500.0 eta_ch, eta_dis 0.95, 0.9 P_grid_max 800.0 P_buy cp.Variable(N, nonnegTrue) P_sell cp.Variable(N, nonnegTrue) P_ch cp.Variable(N, nonnegTrue) P_dis cp.Variable(N, nonnegTrue) P_dg cp.Variable(N, nonnegTrue) P_curt cp.Variable(N, nonnegTrue) E cp.Variable(N 1) e_loss cp.Variable(N) objective 0 for t in range(N): objective price[k t] * P_buy[t] - 0.8 * price[k t] * P_sell[t] objective 0.15 * P_dg[t] objective 0.002 * (P_ch[t] P_dis[t]) objective 10.0 * P_curt[t] objective 0.05 * e_loss constraints [] for t in range(N): constraints [pv_fc[k t] - P_curt[t] P_dg[t] P_dis[t] P_buy[t] load_fc[k t] P_ch[t] P_sell[t]] constraints [E[t 1] E[t] dt * eta_ch * P_ch[t] - dt * P_dis[t] / eta_dis] constraints [E_min E[t], E[t] E_max] constraints [P_ch[t] P_ch_max, P_dis[t] P_dis_max] constraints [P_dg[t] 600.0] constraints [P_buy[t] P_grid_max, P_sell[t] P_grid_max] constraints [P_curt[t] pv_fc[k t]] constraints [E[0] E_start] constraints [e_loss E_ref - E[N], e_loss E[N] - E_ref] prob cp.Problem(cp.Minimize(objective), constraints) prob.solve(solvercp.HiGHS) return (P_buy.value, P_sell.value, P_ch.value, P_dis.value, P_dg.value, P_curt.value, E.value)说明一句这个版本没有加二元变量所以是线性规划用HiGHS解非常快。我靠0.002的磨损成本压制同充同放实测在绝大多数时段不会出现。如果你想彻底杜绝可以把二元变量方案加上第5章会给写法。3.5 结果结算别只看调度曲线要看实际成本仿真跑完成本用“实际发生值”结算不是用MPC计划值否则对比就没意义。简单做法是把每个时刻的实际购电功率累乘电价再减去实际售电收益total_cost 0.0 for k in range(len(P_buy_hist)): total_cost price[k] * P_buy_hist[k] total_cost - 0.8 * price[k] * P_sell_hist[k]P_buy_hist和P_sell_hist是每个MPC周期实际执行的第一个值。更精细的仿真可以在功率平衡里加一个实时偏差项把预测误差导致并网点实际功率偏移也计进来调度级研究一般不做这层知道边界在哪就行。4. 仿真实验怎么设计用数据说话才能证明MPC的价值4.1 三套对比策略必须同台竞技评价MPC效果光看自己的调度曲线好看没用必须有基线对比。我至少会跑三套规则策略最简单白天光伏给储能充电或者卖电晚间高峰放电储能按固定时间表动作不依赖预测。日前开环优化是基准用完整预测数据一次性求解96步全序列执行过程中不更新。MPC就是前面写的滚动方案。注意一个公平性问题三套方案面对的是同一套“真实”负荷和光伏序列、同一个分时电价、同一个初始SOC。如果MPC多给了信息对比结果就没有说服力。4.2 关键参数怎么调预测时域N是最值得调的参数。N取24覆盖6小时很多项目够用N取48覆盖12小时光伏夜间时段不再盲目充电但后半段预测数据可信度下降。批量测试时可以从N4、8、16、24、48各跑一遍看总成本曲线拐点。储能磨损成本λ也是敏感参数。λ太小储能频繁动作、寿命损耗大λ太大储能根本不舍得用晚高峰全去高价买电。工程上我一般先设0.002左右再看SOC曲线是不是在合理区间微调。终端惩罚权重的设置比较粗暴目标里e_loss前乘0.05。想严格一些可以改成不等式E[N] ≥ 900E[N] ≤ 1100把它变成硬约束。代价是预测偏差大时容易无解所以工程上还是软惩罚稳。4.3 结果怎么读一张表的对比就够我在某组随机数据上跑出来的结果大概长这样示意值策略运行成本元弃光率SOC越限次数固定规则2458012.6%3日前开环优化221006.5%2MPCN24205803.1%0MPC成本最低SOC也没有越限滚动优化带来的收益是看得见的。但这里有个很容易误导人的点如果预测误差设成0也就是MPC每一步看到的“预测”和“真实”完全一样那么MPC和日前开环优化的结果会完全一致。MPC的价值恰恰体现在预测有偏差时它每15分钟用实测状态纠一次偏把偏差造成的成本损失压到最低。还有一个值得留意的现象预测数据的“形状”比“精度”更影响MPC效果。比如说真实光伏峰值在13点预测峰值却画到了11点哪怕幅值完全一样MPC也会在11点提前安排少买电13点实际峰值来临时只能靠储能补救。这类形状错误比单纯的幅值偏差更不容易发现画图对比预测和实际曲线时重点看峰值位置。5. 实操中踩过最多的五个坑从不可行解到求解器选型5.1 求解器报infeasible先检查是不是把约束写死了MPC项目里最常见的报错不是语法错误而是“problem infeasible”。比如给定了储能能量范围400~2000 kWh又要求终端能量不低于1600 kWh中午预测光伏又差窗口内根本没有可行轨迹。解决思路是把硬约束放宽成软约束SOC上下限留2%保护带终端约束用前面写的e_loss软惩罚弃光功率允许一定程度超过上限。调度优化是经济问题不是二值逻辑问题宁可让求解器多花一点惩罚也不要报无解。5.2 储能同充同放数学上隐蔽实际成本不小如果不加任何处理优化器有可能在同一个时段同时让P_ch和P_dis都等于一个正数。从平衡等式看两边都被抬高等式照样成立但能量动态方程里充放电效率导致白白损耗目标函数里如果只算购电成本根本看不出来。处理方式有两种。第一种是目标函数加磨损成本就是我主代码里的做法简单有效但不严格保证。第二种是二元变量严格保证同一时段最多只有一个动作z_ch cp.Variable(N, booleanTrue) z_dis cp.Variable(N, booleanTrue) for t in range(N): constraints [P_ch[t] P_ch_max * z_ch[t]] constraints [P_dis[t] P_dis_max * z_dis[t]] constraints [z_ch[t] z_dis[t] 1]加了二元变量后问题是MILP把求解器换成HiGHS即可。预测时域24步、变量几百个的规模HiGHS解起来也是几十毫秒级别不用怕。5.3 测试时把预测误差设成0MPC的优势完全消失这是写论文和汇报时最容易翻车的地方。如果测试工况给的是确定性光伏、确定性负荷MPC滚动反馈等于没用刚刚好跟开环优化结果一样体现不出创新点。做仿真对比时一定要给预测加噪声而且噪声要落在真实数据上因为MPC的反馈价值在偏差里才能兑现。噪声幅度建议10%~20%太小看不出区别太大会让结果随机性过大每次跑的成本都不一样。5.4 求解器选型连续问题用默认整数问题指定HiGHScvxpy默认的求解器虽然是自动选择但实际项目的坑是不同问题类型它选出来的求解器可能完全不同同样的代码换台机器可能卡住。我的建议是显式指定。纯LP/QP用CLARABEL或OSQP加了二元变量就用HiGHS。如果以后要把MPC推到1分钟甚至更短的控制周期建议换casadi加OSQP并做参数热启动cvxpy的建模开销在这种频率下会显得偏重。5.5 调度层和执行层的边界要分清MPC给出的充放电指令是经济调度层面的设定值它不是实时功率控制器。15分钟窗口内光伏突然掉电微电网里要靠储能逆变器的下垂控制或者并网点功率控制来瞬时兜底MPC下一个周期才介入修正。如果拿MPC去背“秒级有功不平衡”这个锅那方向就错了。做项目时最好在方案里明确MPC负责分钟级经济分配实时平衡由底层控制负责两者通过状态上传接起来。回到文章开头那个问题为什么开环日前计划一到现场就废因为它没有反馈。MPC只是把优化问题从“一次性执行”改成了“滚动反馈”但这个改动在不确定环境里价值巨大。如果你在做的微电网项目里发现优化模型越复杂效果越差先别急着加算法回头检查一下你的反馈链路通不通、状态量有没有更新——这比我见过绝大多数所谓的“模型升级”都管用。
返回列表