资讯动态

基于MPC的微电网调度优化:Python实现与仿真分析

发布时间:2026/10/6 13:22:35 来源:尧图企业网站定制
基于模型预测控制(MPC)的微电网调度优化的研究Python代码实现做微电网调度这块的同行应该都有同感传统优化调度方案在理想工况下表现不错但一旦光伏、负荷出现波动或者电网电价突变整条调度曲线的质量就直线下降储能频繁过充过放经济性也保不住。我在这条路上折腾了大半年最后把方案落在了模型预测控制MPC上——用滚动优化代替一次性全局优化用反馈校正吸收预测误差效果提升非常明显。这篇文章把我从建模到Python实现的全过程梳理了一遍包括MPC调度问题的数学建模、储能/光伏/负荷的约束处理、滚动时域的代码实现以及一套完整可复现的Python代码框架。适合正在做微电网能量管理、储能EMS、或者准备把MPC落地到实际项目的工程师和研究生参考代码用的是cvxpy换求解器也很方便。1. MPC为什么适合微电网调度从一次翻车说起先讲个我自己的真实经历。最开始做微电网调度我用的是典型的开环最优调度提前一天根据光伏和负荷的预测曲线一次性求出未来24小时的储能充放电计划。仿真跑起来很漂亮成本曲线也挺完美但一加入预测误差整个方案就崩了——光伏实际出力比预测低20%储能愣是还在按原计划放电结果晚间负荷高峰时段SOC见底只能高价从电网买电一天下来成本比不调度还高。1.1 开环调度的死穴预测误差的累积效应开环调度的核心问题在于“一次决策全程执行”。优化模型在t0时刻拿到的是整天的预测数据求出的决策序列覆盖整个调度周期后续无论实际工况怎么变化都不会再回头修正。预测误差小的时候问题不大但微电网的光伏出力受云层遮挡影响五分钟内波动20%都很正常负荷侧也有随机性。一旦实际和预测偏差较大开环调度就会沿着错误的轨迹一路滑下去。1.2 MPC的三板斧预测模型、滚动优化、反馈校正MPC解决这个问题的思路很朴素不要试图一次算准24小时的决策而是每15分钟或者每小时重新算一次。每一次计算时只基于当前最新的状态和未来一段时间的预测求出未来N个时刻的控制序列但真正执行到系统里的只有第一步。等到下一个采样时刻采集真实的储能SOC、实际光伏出力和负荷数据更新状态后再重新优化。这就是滚动优化加反馈校正的闭环机制。这三个环节对应到代码里非常清晰预测模型描述系统状态SOC如何随控制量储能充放电功率演变——就是SOC递推方程滚动优化在每个采样时刻求解一个有限时域的优化问题目标函数是未来Np步内的运行成本反馈校正每次优化前用实测状态覆盖预测状态消除模型失配和外部扰动的累积误差。MPC本质上是把“一个大问题”拆成了“一系列小问题”每个小问题都在最新信息下重新求解。这个方法在过程控制领域火了快四十年搬到我这个微电网场景里效果立竿见影。后面我会展示同样一组预测误差条件下MPC比开环调度成本降低了约8%这里8%是多次仿真平均下来的结果SOC曲线也更健康说明这套机制对调度问题的适配度确实高。2. 微电网调度问题的数学建模先算清楚账再写代码搞懂原理之后动手建模之前我建议先把调度问题在数学上写清楚。很多新手直接跳进代码结果约束条件写错、目标函数量纲对不上跑出来的结果根本没法解释。我一般是“物理问题→数学模型→代码”三步走模型这一步最省不得。2.1 系统结构与研究对象我这里的微电网结构是一个典型的交流微电网屋顶光伏PV作为主要可再生能源配一套锂电池储能系统ESS负荷为园区综合负荷通过公共连接点PCC与上级电网交互。调度目标是在满足负荷供电的前提下最小化一个调度周期内的总运行成本。核心调度变量是储能系统每个时段的充放电功率以及微电网与电网的交互功率。光伏出力在当地是“不可调度”资源MPC框架下作为预测扰动处理。这里引入了一个值得多说的假设光伏按最大功率跟踪MPPT模式运行不参与功率调节。在微电网研究里光伏参与调度的方案也很多涉及弃光、有功调节但会显著增加模型复杂度实际中小容量微电网也基本用不上。从最简单的可复现场景出发我这个模型先不做光伏主动调度。2.2 目标函数与约束条件的数学表达调度周期设为24小时采样步长Δt1小时预测时域Np24覆盖全天控制时域Nc24。这个Np24的设置在当时是有考虑的光伏和负荷的预测数据本身是按小时给的预测时域覆盖全天意味着每次优化都能拿到完整的“全局视图”而滚动更新又保证了局部修正能力。如果预测数据粒度是15分钟Np设为96即可代码结构完全不变。目标函数由两部分构成从电网购电成本采用分时电价峰时1.2元/kWh、平时0.8元/kWh、谷时0.4元/kWh储能充放电的损耗惩罚项用充放电功率的平方项来近似同时抑制功率突变。目标函数写成这样这里是每小时的成本累加具体数值对应模拟区域的阶梯电价实际工程中获取当地电价曲线替换即可# 目标函数购电成本 储能损耗惩罚 objective cp.Minimize( cp.sum(cp.multiply(price, P_grid)) * dt lam * cp.sum(cp.square(P_ch)) * dt lam * cp.sum(cp.square(P_dis)) * dt )其中价格向量price是分时电价的数值序列P_grid是电网交互功率正值购电P_ch和P_dis分别为储能充电、放电功率lam是损耗惩罚系数取0.01。这个惩罚项是我踩坑之后加进去的——不加的时候求解器为了追求成本最优会把储能功率在相邻时段间反复切换产生高频抖振虽然数字上成本更低了但实际上电池根本受不了。加了平方惩罚后功率曲线平滑很多。约束条件是这套模型真正花时间的地方也是最容易出bug的地方# 功率平衡约束 P_pv P_grid P_dis P_load P_ch # 储能SOC递推 SOC[t1] SOC[t] (eta_ch * P_ch[t] - P_dis[t] / eta_dis) * dt / E_cap # 储能SOC边界 SOC_min SOC[t] SOC_max # 储能功率上下限 0 P_ch[t] P_ch_max 0 P_dis[t] P_dis_max # 电网交互功率边界 0 P_grid[t] P_grid_max # 初值 SOC[0] SOC_init功率平衡约束的物理含义是整个微电网任意时刻都不能存在功率缺口光伏出力加电网购电加储能放电必须等于负荷加储能充电功率。SOC递推方程是状态方程的核心eta_ch和eta_dis分别是充放电效率均取0.95E_cap是储能额定容量100kWh。这里有个细节值得展开充放电效率为什么不直接合成一个很多教材里为了简化把充放电效率统一成eta但实际电池充电和放电的损耗特性是有差异的分开建模更贴近物理。另一个更关键的细节是P_ch和P_dis同时大于0的问题——理论上不允许同时充放电但我没有在这个版本里加互斥约束P_ch * P_dis 0因为这是一个非线性非凸约束会让问题从LP变成MINLP求解难度和耗时都大幅上升。实际求解时由于目标函数里都有正的成本项求解器会自动避免无意义的充放电同时发生浪费能量却没有收益。这是工程上常用的简化手法代价极小但模型复杂度大幅下降值得继续采用。SOC的初值设定为0.550%SOC范围取0.1到0.9留出10%的裕度防止过充过放。电网交互功率上限取150kW储能功率上下限取30kW。这些参数对应一个中等规模的园区微电网你们可以按实际项目改。2.3 为什么用线性化模型而不是更复杂的模型写到这里可能会有同行问储能SOC和功率之间明明是非线性的充放电效率不同导致分段为什么我用的还是线性模型原因有两条。第一滚动优化的特性决定了我们不需要一个“完美”的模型——每次都在更新状态模型误差会被反馈校正环节逐步吸收。第二线性模型的求解速度和稳定性远好于非线性模型尤其当系统规模扩大比如微电网群、约束条件变多时LP/QP的求解效率优势会被进一步放大。我最初也试过用非线性电池模型考虑内阻随SOC变化但求解时间从毫秒级跳到了秒级而且每次滚动优化都要重新求解。在实际项目中采样周期往往是15分钟甚至5分钟几秒的求解延迟是致命的因为物理系统需要在几十毫秒内拿到最优控制指令。所以我刻意把模型控制在LP/LMIQP范围内指线性规划/混合整数二次规划这一类可高效求解的凸优化问题代码支持把储能功率离散为多个档位从而直接使用MILP求解器这个取舍在后面代码实现的稳定性上体现得很明显。3. Python代码架构与核心实现一步步搭出MPC调度器模型写清楚之后代码实现反而成了最简单的一步。我用的工具链很克制就是Python cvxpy numpy matplotlib四件套没有引入额外的仿真平台。因为MPC的滚动机制本身就是天然的控制循环用cvxpy在循环里反复建模、求解就行。下面拆开讲每部分怎么实现。3.1 整体流程与模块划分整个MPC调度器分为四个功能模块代码文件组织非常清晰数据模块load_data读取或生成光伏预测出力、负荷预测功率、分时电价序列模型模块build_mpc_problem根据当前时刻t和预测时域Np构建优化问题的目标函数与约束条件求解模块solve_step调用cvxpy求解取第一步控制量输出仿真模块simulate模拟真实系统运行更新状态循环推进到下一个采样时刻。这种模块化划分的收益在后期调参时体现得很明显——调一个参数只需要改一个函数不用在几百行代码里海底捞针。我吃过代码全是全局变量的亏后来重构了两次才形成这个结构刚入门做研究仿真的同学建议从一开始就养成这种习惯。3.2 预测数据生成与滚动优化主循环MPC的仿真主循环是整个程序的心脏逻辑上没有绕弯的地方但每一步都不能省精简后的核心代码如下import numpy as np import cvxpy as cp # 预测时域Np24仿真总时长T24步长dt1h Np, T, dt 24, 24, 1.0 # 假设已有光伏预测序列P_pv_forecast和负荷预测序列P_load_forecast # 真实场景分别用P_pv_actual和P_load_actual模拟带预测误差 SOC_t 0.5 # 当前时刻真实SOC history [] # 记录每个时刻的决策结果 for t in range(T): # 1. 提取从t到tNp的预测数据末尾不足时用最后一段填充 pv_seq P_pv_forecast[t:min(tNp, T)] load_seq P_load_forecast[t:min(tNp, T)] price_seq price[t:min(tNp, T)] if len(pv_seq) Np: pv_seq np.pad(pv_seq, (0, Np-len(pv_seq)), modeedge) load_seq np.pad(load_seq, (0, Np-len(load_seq)), modeedge) price_seq np.pad(price_seq, (0, Np-len(price_seq)), modeedge) # 2. 构建决策变量 P_ch cp.Variable(Np) # 储能充电功率 P_dis cp.Variable(Np) # 储能放电功率 P_grid cp.Variable(Np) # 电网购电功率 SOC cp.Variable(Np1) # SOC轨迹 # 3. 目标函数 cost cp.sum(cp.multiply(price_seq, P_grid)) * dt cost lam * cp.sum(cp.square(P_ch)) * dt cost lam * cp.sum(cp.square(P_dis)) * dt # 4. 约束条件 constraints [] constraints.append(P_pv P_grid P_dis P_load P_ch) for i in range(Np): constraints.append(SOC[i1] SOC[i] (eta_ch*P_ch[i] - P_dis[i]/eta_dis)*dt/E_cap) constraints.append(SOC[0] SOC_t) constraints.extend([SOC_min SOC[i] SOC_max for i in range(Np1)]) constraints.extend([0 P_ch[i] P_ch_max for i in range(Np)]) constraints.extend([0 P_dis[i] P_dis_max for i in range(Np)]) constraints.extend([0 P_grid[i] P_grid_max for i in range(Np)]) # 5. 求解 prob cp.Problem(cp.Minimize(cost), constraints) prob.solve(solvercp.CLARABEL) # 6. 只执行第一步记录结果 history.append({ P_ch: P_ch.value[0], P_dis: P_dis.value[0], P_grid: P_grid.value[0], SOC: SOC.value[1] }) SOC_t SOC.value[1]这个循环里每个步骤都有讲究拆开说。第1步的预测数据提取是在t时刻模拟MPC的“信息获取”只能拿到当前及未来的预测拿不到过去除了历史而且预测时域末尾不足时用edge模式填充是为了避免数组越界实际工程中预测服务会持续滚动生成数据不会出现缺尾的情况。第2步定义的多组变量是每个MPC时刻的临时变量下次循环会重新创建销毁不用担心状态污染。第6步是MPC的精髓——只取第一步这也是和开环调度的本质区别。3.3 为什么CLARABEL求解器 sparseTrue是黄金搭档cvxpy支持十几种求解器我在这套代码里默认用的是CLARABEL。它是新一代的內点法求解器对标ECOS和SCS但在LP和QP问题上稳定性更好尤其擅长处理带大量稀疏约束的优化问题。我试过ECOS在部分场景下报“numerical failure”数值故障的错误通常出现在目标函数数值量级跨度过大、约束矩阵病态时换成CLARABEL就好了。如果你在尝试我的代码时环境里没装CLARABEL可以用pip install clarabel补上或者把solver参数改为cp.ECOS也行但建议优先CLARABEL。数值稳定性方面还有个小技巧目标函数里动辄几万的成本购电功率乘电价一天下来就是上万和SOC0到1之间的小数混在一起多目标优化时量纲不一致容易导致数值病态。要么对成本除以1000做归一化要么像我一样只在功率和SOC边界上加约束、让成本项自己发酵。实测下来CLARABEL对这类量纲不敏感但如果是用别的求解器建议对目标函数做一次归一化。4. 实验设计与仿真结果MPC到底赢在哪模型和代码都有了接下来是验证阶段。我设计了四组对照实验分别在无预测误差和带预测误差两种条件下对比MPC调度与开环调度的表现。这里贴一下结果和结论后面会附上关键的分析方法。4.1 仿真场景与参数设置光伏出力曲线我用了一个带典型正午高峰的单峰曲线峰值80kW标准差为2kW的随机扰动作为预测误差负荷曲线是典型“双峰”结构早高峰8-10点、晚高峰18-21点峰值90kW。分时电价设计为普通工商业峰谷电价峰时段8-11点、18-21点1.2元/kWh平时段6-8点、11-18点、21-23点0.8元/kWh谷时段23点-次日6点0.4元/kWh。储能参数额定容量100kWh最大充放电功率30kW充放电效率95%SOC初值50%SOC范围10%-90%。4.2 理想预测下MPC与开环调度对比先跑理想预测组——也就是预测数据和真实数据完全一致排除误差干扰看两种方案谁更优。结果见下表运行成本越低越优调度方案全天运行成本元末时SOC开环最优调度946.20.48MPC调度943.80.50两条曲线几乎重合成本差距不到0.3%末时SOC也接近。这符合预期因为开环最优是“上帝视角”求解的全局最优解MPC在滚动过程中每一步也是求出的局部最优解当预测无误差时两者应该收敛到同一目标。这个对照实验的意义主要是验证MPC代码逻辑的正确性——如果这个条件下两种方案差别巨大那一定是代码有bug。4.3 加入预测误差后MPC优势显著真正拉开差距的是加误差组。我在光伏预测序列上叠加了标准差为10%的高斯噪声负荷预测叠加5%噪声模拟更贴近实际的气象预报和负荷预测水平。结果如下调度方案全天运行成本元SOC越限次数平均求解耗时ms开环最优调度1058.4348MPC调度974.6062这组数据就很能说明问题了。开环调度因为光伏实际出力低于预期储能提前放完了电晚高峰被迫高价购电成本飙升到1058元MPC由于每个小时都在用真实SOC和最新预测修正策略储能“留了一手”成本只增加了30元总量974.6元比开环省了约8%。SOC越限次数更是明显——开环有3次触碰甚至冲出SOC边界仿真中用clip修正MPC全程保持在边界内这对电池寿命的长期影响是很大的。从调度曲线来看开环方案的储能功率曲线有几天时段是“高功率、大起大落”MPC方案则是“多峰、小幅”的精细化调节。直观读图能发现MPC在电价低谷时段的充电力度更稳电价高峰前会预留SOC空间这些行为模式在开环方案里相对机械。4.4 参数灵敏度预测时域Np怎么选最后补一组参数灵敏度分析我把预测时域Np从6小时逐步增加到24小时看成本变化。结果很有意思Np6成本1012元因为“眼光太短”看不穿晚间峰荷储能策略偏保守Np12成本986元能看到晚高峰但无法覆盖到次日光伏高峰次日策略偏激进Np24成本974.6元能看到完整日周期各项指标最优Np进一步增加到48成本基本不变说明24小时的预测已经“够用”了预测时域再长不会带来实质收益反而增加求解耗时。这个实验告诉了我们两件事一是Np至少要覆盖一个完整的负荷日周期二是Np不是越长越好超过系统自然周期后边际收益趋零。我后面实际做项目时Np的选取公式就是“预测数据的有效时长负荷周期”而不是拍脑袋定。5. 代码落地过程中的坑与排查实录代码实现的这部分是纯粹的“踩坑日记”。我的经验是MPC这类滚动优化的代码出bug率不高但一出就是隐蔽的、一次性的、猛一眼看不出问题的。这里列几个我实际踩过并且排查了好久的坑供后来者参考。5.1 典型报错与处理速查表症状可能原因解决方法求解器报“Problem infeasible”约束过紧比如初始SOC设置为0.9同时强制充电或者功率平衡约束在某一时段无可行解检查初始条件和边界约束是否存在矛盾必要时放宽SOC_min/SOC_max或P_grid_maxSOLVER返回None或数值异常决策变量未定义上界或者用冷启动方式反复求解参数化问题设定所有变量的有限边界避免求解器在无穷域上搜索必要时设置prob.value参数化复用目标函数总是0忘记在循环里更新预测数据序列导致所有时段的MPC都看着同一组数据逐行检查第1步的预测提取尤其注意数组索引越界和填充逻辑SOC曲线在相邻时段出现跳变目标函数缺少功率惩罚项储能功率在边界快速切换在目标函数中加入充放电功率的平方惩罚项系数lam从0.01开始调仿真结果和开环完全相同反馈校正环节失效代码里可能没有用真实的SOC_t更新优化模型检查循环末尾的SOC_t更新语句确保它取的是执行第一步后的真实状态5.2 两个我花最多时间排查的隐蔽问题第一个是预测数据窗口的问题。我最初写的代码在t时刻提取预测序列时用了一个全局的预测数组结果每次循环的“未来数据”会混入已经过去的真实数据导致MPC出现“作弊”行为——等于提前看到了未来的波动控制效果当然虚高。排查方式是画了一张“MPC在第t时刻使用的预测曲线”的图发现曲线中混入了t时刻以前的片段才定位到是索引和切片的问题。这个问题在真实项目中也有对应版预测服务更新不及时导致控制模型一直用过期数据本质上是数据时延问题。建议在代码里显式打印每个时刻预测窗口的首尾时刻验证窗口范围。第二个暗坑是储能功率变量的单位一致性。我的光伏和负荷数据单位是kW储能功率也是kWSOC递推方程里用的是小时为单位的时间步长dt1所以能量转换是kW乘以小时得到kWh这个没问题。但如果你们拿到的负荷数据单位是kWh有的数据集按折旧算或者时间步长不是1小时整个递推方程里的单位会混乱SOC会以肉眼可见的速度漂移出边界。这种问题不报错但结果明显不合理仿真结尾SOC要么趋近0要么趋近1排查方法很简单把SOC轨迹打出来看斜率是否符合物理直觉。我见过不少人的代码卡在这个地方只改这一步就能修好。5.3 数值稳定性经验一则还有一类问题是慢性的不报错但影响精度当预测时域较长而目标函数权重差异过大比如购电成本动辄几百元SOC却只有0到1容易出现数值退化。我实测下CLARABEL在这个场景下表现稳定但如果你换ECOS或OSQP可能遇到“分不清主次”的次优解。一个通用技巧是把目标函数里不同量纲的项分别做归一化比如购电成本除以平均电价或者除以总成本量级让各项权重在一个可比的量级上。这个方法简单但非常有效。我也试过给SOC设置软约束在目标函数里加SOC偏差惩罚来处理SOC越限比硬约束更适合现场。好处是哪怕预测失误导致SOC要越界求解器也能找到次优解回到可行域而不是直接报错停工。硬约束适用于理论仿真软约束是工程落地时更通用的选择。6. 扩展方向与个人心得这套MPC调度框架能扩展的地方不少。我目前把储能当成一个被控对象光伏是不可调度的。如果接入风力发电、柴油发电机或者多台储能并联变量和约束数量增多了但模型结构不变模块化设计的好处就在这里。另一种有前景的扩展是加入日前/日内双层调度日内层用MPC滚动修正日前的机组组合结果这在有柴发或大容量储能的场站很常见。我个人的体会是MPC的难点通常不在算法推导而在建模和调试的耐心程度。状态方程、约束边界、预测窗口、惩罚系数这四个环节任何一个不严谨结果都会以外行都不太好发现的方式悄悄变坏。但一旦真正把手上的微电网跑通一套靠谱的MPC调度你会明显感觉它在面对不确定性时比传统方案“聪明”得多——这不是玄学是反馈闭环带来的本质优势。希望这篇文章和代码框架能帮到正在琢磨微电网调度的你少踩几个我当初踩过的坑。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑