ARTICLE DETAIL

资讯详情

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

基于MPC的微电网调度优化:储能建模与Python实现全解析

基于MPC的微电网调度优化:储能建模与Python实现全解析 一个多月前我把一套基于模型预测控制MPC的微电网调度优化框架完整跑通了从数学建模、Python代码搭建到仿真验证前后迭代了好几版。今天这篇不写教科书式的公式推导只讲我在实际搭建这套框架时踩过的坑、做过的权衡以及可以直接拿去改用的核心代码和调试思路。如果你正准备做微电网调度优化、想了解MPC怎么落地到工程或者手里只有一堆文献和PPT但不知道怎么写代码这篇应该能帮你省下不少自己摸索的时间。我默认你大概率是这样的状态知道MPC三个基本要素——预测模型、滚动优化、反馈校正也知道微电网里无非就是光伏、储能、负荷、可能的柴油机和并网点但真要你把两者结合写出能迭代的代码心里没底。这篇文章就把整个过程拆开揉碎从为什么选MPC开始到建模、代码、仿真和参数整定一条线走完。1. 为什么微电网调度要选MPC而不是查表或一次性求最优1.1 微电网调度本质上是“预期管理”问题微电网调度优化算的账其实不复杂未来24小时里光伏会发多少电、负荷会用多少电、电价什么时候贵什么时候便宜在满足供电可靠性和设备约束的前提下决定储能什么时候充电、什么时候放电、要不要从电网买电最终让总运行成本最低。但问题的难点在于“未来”两个字。光伏出力受天气影响大负荷曲线有随机波动如果只看当前时刻做决策就会出现所谓“短视”问题下午电价贵的时候你正在给储能充电到了晚上电价更贵时储能反而没电可放。这就是典型的只盯着眼前的一块肉没算过后面的饥饱。传统方法里最常见的两个极端一个是if-else规则调度另一个是开环的一天全局优化。规则调度简单直接比如“光伏富余就充电不够就买电”但完全没有跨时段的统筹能力开环全局优化能看到未来24小时的全貌却把预测数据当成了真实值一旦实际光伏比预测低20%第二天预定的放电计划就会落空该省的钱一分省不到。1.2 MPC的滚动优化等于“反复做全局计划但只执行第一步”MPC的做法本质上很朴素在每一个控制时刻基于当前测到的系统状态比如储能SOC结合未来一段时间内的预测数据在线求解一个带约束的有限时域优化问题然后把求解出的控制序列里的第一步交给系统执行。等到下一个采样时刻再重新测量状态、刷新预测数据、重新求解。这个过程叫滚动时域优化也叫反馈校正。说得再直白一点MPC像一个“边干活边看路”的决策者。它每走一步都会重新看一眼未来几十步的路况做出一个综合最优的前进计划但这次只迈一步迈完再重新规划。这样既有全局观又有抗干扰能力两个传统方法的痛点都被掐住了。你只要想一个场景下午3点光伏出力突然被云遮掉一半。开环调度方案可能已经决定从15点开始给储能充电到晚上再放而MPC在15点重新求解时会把削减后的光伏预测数据塞进模型里立刻把策略切换成“尽量少充电、必要时放电”这就是反馈的价值。我实际仿真里测过一次光伏骤降场景下开环方案一天电费比MPC高出将近两成差距就是这么来的。2. 先把被控对象写清楚微电网的数学建模2.1 模型结构选型状态该选什么、控制量该选什么做MPC的第一步永远不是写代码而是建模。模型决定了你在优化里能看见什么、能控制什么。我第一版做的是并网型微电网对象包含光伏、储能、本地负荷和并网点暂不考虑柴油机和风机先把这个框架跑通。被控对象需要提炼成标准的离散时间状态空间形式。状态变量我选了储能荷电状态SOC这是一个带慢动态的积分量也是调度决策的核心。控制变量则选了储能充电功率P_ch和放电功率P_dis、并网点交互功率P_grid、弃光功率P_curtail。扰动变量是光伏预测出力P_pv、负荷预测需求P_load。这样设计的好处是SOC作为状态能自然衔接MPC的滚动初始条件而控制量都对应真实设备指令方便下线执行。有些资料会把储能功率合并成一个变量P_bat可是用正负号区分充放电这会在求解时引入非线性或互补约束的麻烦。我建议直接拆成两个非负变量再加上一个充放电不能同时进行的约束这样整个模型保持线性求解器处理起来非常舒服。2.2 核心约束与目标函数储能SOC的递推方程是整个模型的心脏我做的是离散化后的线性形式SOC(k1) SOC(k) - (P_ch(k) * eta_ch * dt) / C_bat (P_dis(k) * dt) / (eta_dis * C_bat)这里eta_ch和eta_dis分别是充电和放电效率C_bat是储能容量dt是采样步长。功率平衡约束必须满足也就是光伏出力加上电网买入加上储能放电等于负荷用电加上储能充电加上弃光量写成恒等式就是P_pv(k) P_grid(k) P_dis(k) P_load(k) P_ch(k) P_curtail(k)系统里的符号约定要统一我用电网功率为正表示从主网购电为负表示向主网售电。储能放电为正充电为负——但建模时拆成两个非负变量更清晰约束条件列出来就是下面这张表约束项表达式说明充放电互斥P_ch(k) P_dis(k) P_bat_max防止同时充放储能功率上限0 P_ch P_bat_max, 0 P_dis P_bat_max功率钳位SOC范围SOC_min SOC(k) SOC_max一般取0.1~0.9电网交互功率P_grid_min P_grid P_grid_max受并网容量限制弃光量0 P_curtail P_pv_forecast只能弃预测内的光目标函数我用的是一天运行成本最小化包含从电网购电的费用、向电网售电的收益、弃光惩罚以及储能动作惩罚。表达式看起来长但每一项都在回答一个经营问题购电要花多少钱、卖电能赚多少、弃掉的光伏浪费了多少、储能频繁动作对寿命的损耗值多少钱。写进代码之前一定要把所有时间序列的维度对齐我第一版就是因为负荷和光伏预测数组长度差了一个点结果约束矩阵维度不匹配优化器直接报错。这种错误不会出现在你的算法推导里但百分之百会出现在工程调试里。3. Python实现MPC的工程化细节3.1 求解器选型不是非要上重型工具实现MPC最核心的问题就是这个优化问题怎么在Python里求解我的第一反应是查文献里常用的工具但后来发现自己陷入了一个误区——总想着用最强大的优化器而忽略了问题本身的结构。我用的MPC问题在模型线性化后是一个典型的二次规划问题二次规划有大量成熟的高效求解器。如果你想快速出原型直接用scipy.optimize.minimize也能跑但遇到约束边界时收敛速度慢、稳定性差每步要调半天参数。后来我换成了cvxpy用了几分钟把问题声明出来求解速度瞬间快了一个数量级而且代码可读性高很多。如果后续需要处理非线性模型比如电池效率随SOC变化的曲线拟合可以再考虑用casadi它对非线性规划的支持更完善。选择cvxpy还有一个额外的好处它自带建模语言你不需要把二次规划的手动矩阵构造偏差问题考虑太多直接声明变量、添加约束、设置目标求解器自动把问题分发给底层计算。这对做算法验证的工程师来说体验是最舒服的。3.2 核心代码解析预测模型与滚动优化主循环先看最核心的优化问题构建部分。我贴一个简化但能直接用的版本这个函数在每次采样时刻都会被调用一次输入当前SOC和未来24小时的预测曲线输出这一时刻应该执行的储能充放电指令和电网功率指令import cvxpy as cp import numpy as np def solve_mpc(soc_curr, p_pv_forecast, p_load_forecast, price_forecast, params): N len(price_forecast) # 预测时域长度默认24 dt params[dt] # 采样时间 1.0 小时 # 决策变量每步的充、放电功率电网功率弃光功率SOC P_ch cp.Variable(N, nonnegTrue) P_dis cp.Variable(N, nonnegTrue) P_grid cp.Variable(N) P_curtail cp.Variable(N, nonnegTrue) SOC cp.Variable(N 1) # 目标函数购电成本 - 售电收益 弃光惩罚 储能动作惩罚 cost 0.0 for k in range(N): cost price_forecast[k] * P_grid[k] * dt cost 10.0 * P_curtail[k] * dt cost 0.1 * (P_ch[k] P_dis[k]) * dt constraints [ SOC[0] soc_curr, ] for k in range(N): # 功率平衡 constraints.append( P_pv_forecast[k] P_grid[k] P_dis[k] P_load_forecast[k] P_ch[k] P_curtail[k] ) # SOC动态 constraints.append( SOC[k 1] SOC[k] - (P_ch[k] * params[eta_ch] * dt) / params[C_bat] (P_dis[k] * dt) / (params[eta_dis] * params[C_bat]) ) # 充放电互斥 constraints.append(P_ch[k] P_dis[k] params[P_bat_max]) # 功率与SOC范围 constraints.append(P_ch[k] params[P_bat_max]) constraints.append(P_dis[k] params[P_bat_max]) constraints.append(P_grid[k] params[P_grid_min]) constraints.append(P_grid[k] params[P_grid_max]) constraints.append(SOC[k 1] params[SOC_min]) constraints.append(SOC[k 1] params[SOC_max]) constraints.append(P_curtail[k] p_pv_forecast[k]) prob cp.Problem(cp.Minimize(cost), constraints) prob.solve(solvercp.OSQP, verboseFalse) if prob.status not in (optimal, optimal_inaccurate): raise RuntimeError(MPC求解失败状态为{}.format(prob.status)) return { P_ch: P_ch.value[0], P_dis: P_dis.value[0], P_grid: P_grid.value[0], P_curtail: P_curtail.value[0], SOC_traj: SOC.value, }这段代码看起来不长但有几个细节我想重点强调一下都是调试过程中被现实毒打出来的教训。第一个细节是充放电互斥约束我并没有用P_ch * P_dis 0这种非线性的互补写法而是用线性不等式P_ch P_dis P_bat_max间接限制。这样建模可以保持整个优化问题为线性约束的凸问题求解器处理起来又快又稳。在实际仿真中因为目标函数里充放电都有成本系数求解器自然会偏爱较小值所以这个约束在大多数情况下都不会被激活到危险边界。第二个细节是SOC约束附加在了SOC[1]到SOC[N]这个区间而不是只加在最后一步。很多初学者喜欢只约束终值SOC结果中间过程SOC跑到1.1甚至1.2出现离谱的调度方案。MPC每一时刻的状态都应该在物理可行域内这一点从第一天写代码起就要注意。3.3 闭环仿真框架把MPC放进时间循环里有了单步求解函数闭环仿真看起来就顺理成章了。但要注意的是MPC仿真里的“实际系统”和“预测模型”不是同一个东西否则就是在自欺欺人做开环验证。我通常会单独写一个模拟器里面允许实际光伏出力和负荷曲线跟预测曲线存在偏差然后在每个时刻用实际值更新状态再用预测值给MPC做规划。核心循环写法大概长这样for k in range(24 * T_days): # 当前时刻真实状态 soc_real soc_history[-1] # 获取未来预测曲线这里可以加噪声/扰动 pv_fc generate_forecast(pv_real_curve, k, noise_std0.1) load_fc generate_forecast(load_real_curve, k, noise_std0.05) price_fc price_curve[k: k Np] # 已知分时电价 # 求解MPC取第一步控制 action solve_mpc(soc_real, pv_fc, load_fc, price_fc, params) u_step action[P_dis] - action[P_ch] # 净储能放电功率 # 将控制量作用到真实系统加入预测误差 pv_real pv_real_curve[k] load_real load_real_curve[k] soc_next simulate_system(soc_real, u_step, pv_real, load_real, params) # 记录数据 soc_history.append(soc_next) action_history.append(action)这里我踩过一个比较典型的坑仿真步长和控制时域都是1小时预测时域Np取24结果每个时刻都要解一个24维的决策问题虽然用OSQP很快但循环次数一多数据记录不合理绘图时经常出现时间轴错位。建议所有历史数据清零时刻养成分步记录的字典配合DataFrame管理结果后面分析起来轻松得多。3.4 预测时域与控制时域怎么选预测时域Np的选择原则很简单要能覆盖一个完整的调度周期。既然微电网的运行规律是“一天”那就取24小时采样步长dt取1小时。这样储能能在夜间谷电时段充电在日间峰电时段放电MPC能看到电价的周期性变化才有跨时段的统筹空间。但如果你做的是分钟级实时调度比如5分钟一个控制周期24步预测时域就是2小时这时候往往覆盖不了完整的充放电周期。我的经验是在计算时间可控的前提下Np取96到144步对应8到12小时Nc可以缩到10到20步控制变量只在前面几步改变后面保持恒定。这种缩减法能让求解规模小一个量级而且性能损失在多数场景下可忽略。具体参数调整可以先用仿真试跑一轮观察SOC轨迹和总成本的灵敏度再定。4. 一个具体仿真算例分时电价下的经济调度对比4.1 算例参数设置讲完代码来看一个能复现的完整算例。我设计了一个小型并网微电网参数如下表参数数值说明光伏装机100 kW最大出力100kW储能容量200 kWh可用SOC范围0.1~0.9储能最大功率50 kW充放电功率上限充/放电效率0.92 / 0.94电化学损耗并网功率上限100 kW单方向交互限制初始SOC0.5仿真起点采样步长1 h分时电价采用典型的峰平谷三段式谷时0.3元/kWh23:00-07:00平时0.7元/kWh07:00-10:00和15:00-18:00峰时1.2元/kWh10:00-15:00和18:00-21:00。光伏曲线用一条类正态的日间曲线模拟早上8点开始上升中午12点达到峰值的90kW下午5点降到20kW晚间归零。负荷曲线则设置两个峰早高峰8点45kW晚高峰19点80kW夜间负荷低至20kW。我需要特别说明一下算例里的光伏和负荷曲线都是模拟的。如果你想迁移到真实项目只需要把自己的历史出力数据和负荷预测数据读进来替换掉generate_forecast里的数据源即可模型代码不用动。4.2 MPC和规则策略的对比结果为了说明MPC的价值我加了一组对照组规则策略的逻辑是光伏优先自用富余功率先给储能充电充不满再向电网卖电光伏不够时先放电放完再买电。这个策略几乎是行业里最常用的一套逻辑也是很多人直觉上认为“已经挺合理”的方案。跑完一天仿真结果差异非常明显指标规则策略MPC策略总购电费用986.5 元823.2 元总售电收益104.6 元156.8 元总运行成本881.9 元666.4 元弃光电量34.2 kWh9.5 kWh日均成本降幅-24.4%MPC方案一天省下215块钱折算到一年就是近8万元的运行成本节省。差异来源主要有三个第一MPC会在夜间谷电时段主动给储能充满电哪怕当时负荷很低它也要为第二天的峰段做预储备这是规则策略中最缺的远见第二在午间光伏大发而电价还处于平时段时MPC会判断“与其现在充电不如卖给电网赚平段电费”从而主动降低充电功率、增加售电量这个判断完全基于对未来时间的电价和负荷预测第三傍晚峰段来临时MPC会严格按预测安排放电深度确保电量能覆盖整个峰期而不是像规则策略那样一开始放电过猛等晚高峰真正到来时SOC已经见底。我在跑完这个对比以后最大的感受就是MPC在MPC做微电网调度这件事上绝对不只是工具箱里多了一把锤子。它的优势不在单步决策的某个点位上而在它能自然地把“时间维度”纳入优化而时间维度恰好是微电网运行成本的大头这一点是静态策略没法比的。5. 常见问题与参数整定实录5.1 求解器不收敛或状态异常MPC跑起来以后第一个会撞上的问题大概率是求解状态变成infeasible不可行。我遇到过的最常见原因是SOC约束与经济调度的目标冲突如果预测时域内负荷极高、光伏极低而储能初始SOC又偏低系统根本找不到一条满足所有SOC上下限的可行路径。这时候有两个思路。第一是给SOC的边界约束加一个软惩罚具体做法是在目标函数里增加SOC越限的惩罚项再把SOC限值从硬约束改成“尽量不越限”的软约束。第二是调整SOC下限很多工程场景里储能SOC下限设置的0.1在冬天或连续阴雨天气下根本不现实不如直接放宽到0.05甚至允许短时强制关机。我的经验是先把可行性放在第一位再把经济性调优两者顺序不能反。还有一种情况是求解返回optimal_inaccurate这个状态看起来不像错误但你如果直接把控制量拿去执行可能输出会有小幅抖动。我处理的办法很简单——把求解器的精度参数从默认值调高一个量级同时对返回的第一步控制量做限幅处理确保它落在物理范围内再下发。5.2 权重参数到底怎么调MPC的目标函数里往往不只有经济运行成本还有对储能动作的惩罚项以及可能的SOC偏离惩罚项。权重设小了储能会在一天内频繁充放动作次数多到不现实权重设大了储能会“懒得动”明明电价差足够大也不愿意参与套利MPC就退化成了一个只做负荷平移的工具。我建议的调参顺序是这样的第一步先把所有非经济惩罚权重设成0只跑经济目标得到一个纯最优成本第二步加入储能动作惩罚从很小的数值开始比如0.05仿真一天统计储能每天的动作次数第三步逐步增大惩罚系数观察总成本的变化找到一个拐点让成本增幅控制在1%以内同时动作次数降到设备可接受的范围。我调过的几个场景里这个拐点通常在0.1到0.5之间不同参数组合下差异较大认真做一次收益-寿命折中试验是值得的。5.3 预测不准的时候别慌MPC很依赖预测模型质量但预测不准并不意味着框架就废了。我测试过把光伏预测噪声标准差拉到15%的场景MPC依然能比规则策略省下不少费用只是优势从24%缩水到13%左右。这说明滚动时域的反馈机制本身就有很强的纠错能力每次重新求解时实际SOC和新的预测会不断修正过往偏差。如果预测偏差实在太大我的建议是引入场景集或鲁棒MPC的思路但那是进阶方向先把确定性MPC跑稳再考虑不确定性问题这个路线成长曲线最平滑。另外我在调试里发现一个非常有用的小技巧在MPC的投用初期不要让它接管全部控制权而是加一个“保护层”例如限定储能功率不得超过实际可用容量的90%。这个限制能避免模型和现实偏差大的时候调度指令直接导致过充或过放等运行一段时间、积累了更准确的预测数据后再把保护层放宽。最后想说的这套框架跑通以后我最大的体会是MPC的代码实现并不高不可攀难点在于模型的取舍和参数的整定而这恰恰是文献里最不爱写详细的部分。如果你自己动手复现我建议你从最简单的单储能并网模型起步先跑通无扰动场景再逐步加入预测误差、设备约束和更复杂的电价结构。每一步改动后把前一步的结果保留下来做对比你会很清楚每一项改进到底带来了多少收益。另外一个小建议写MPC仿真代码时一定要把“预测模型”和“实际系统模拟器”分开哪怕第一版实际系统就是根据真实曲线直接代进去这个架构也要搭好。因为在后续做鲁棒性分析、实时测试时这两个模块的分离能帮你省下大把重写代码的时间。我目前正在往这个框架里加光伏不确定性场景集等把随机MPC的版本跑出来再来分享一轮新的实测数据。
返回列表