☰
基于MPC的微电网调度优化:Python实现与滚动控制详解
2026/10/9 12:51:26 网站建设 项目流程

做微电网调度优化,绕不开一个名字:MPC(模型预测控制)。最近我用Python把一个基于MPC的微电网调度优化项目完整跑通了一遍,从模型搭建、代码调试到结果分析,踩了不少坑,也沉淀了不少经验。这篇就把“基于MPC的微电网调度优化”的整套思路和Python实现细节掰开揉碎讲一讲。内容适合正在做能源管理、微电网控制、储能调度的同学参考,也适合刚接触MPC、想快速上手一个具体方向的人。微电网调度优化不是只能依赖商业软件,纯Python加几个开源库就能干,关键是模型怎么建、预测时域怎么定、约束怎么拆。

1. 微电网调度优化到底在优化什么

1.1 一个典型微电网的组成与运行约束

在讲MPC之前,先把微电网调度优化这个问题的底子聊透。微电网本质上是一个小型的电力系统,光伏、风电这类分布式电源,储能电池,柴油发电机,再加上本地负荷,通过微电网管理系统把这几部分协调起来。并网模式下还可以和配电网交换功率,既能从电网买电,也能把多余的电卖出去。调度优化要干的活就是:在未来一段时间内,决定储能什么时候充电什么时候放电、柴油发电机出力多大、从电网买多少电或者卖多少电,确保负荷供应不中断的同时,让整体运行成本最低。

约束条件五花八门。功率平衡是硬约束,光伏出力加柴油机出力加储能放电量加购电量,要时刻等于本地负荷加充电功率加售电功率。储能还有自己的脾气,SOC有上下限,充放电功率有最大最小值,同一时刻不能又充又放。柴油发电机有出力上下限、爬坡速率限制,并网交互功率也受变压器容量限制。这些约束一旦不满足,调度方案就是废纸一张。我做这个项目时第一版模型就是忘了给储能加“不能同时充放”的约束,结果优化器给出的方案出现了同时充电和放电的荒唐结果,因为对成本有利,求解器会毫不犹豫地拿这个漏洞来钻。

还有一个关键点:微电网调度优化本质上是一个带约束的优化问题,目标函数是成本,决策变量是各个设备的出力计划。但设备多了、约束多了,问题就变成混合整数线性规划(MILP)甚至混合整数二次规划(MIQP),因为储能充放互斥、购电售电互斥这类逻辑关系必须引入0-1整数变量才能表达清楚。

1.2 为什么单次开环优化不够用

传统的最优调度方式,也就是所谓的“日前计划”,在运行日前一天,用预测好的光伏出力曲线和负荷曲线,一次性求解出未来24个小时的设备出力计划,然后第二天就按照这个计划去执行。这种做法看起来没问题,但实际运行中会吃大亏。光伏出力预测不准、负荷波动大、天气突变,任何一个扰动都会让执行结果偏离最优解。举个例子,计划里中午光伏出力能到300kW,实际只有200kW,中午这段本来打算靠光伏供电、同时给储能充电,结果光伏少了100kW,储能SOC到不了计划值,傍晚用电高峰时储能就没电可放了,只能高价买电或者硬扛。

MPC解决这个问题的方式很直接:不要一次定死,而是走一步看一步。MPC也叫兼前顾后最优控制,它在每个控制周期开始时,利用最新的测量信息和预测信息,重新求解一个有限时域的优化问题,得到未来N个时间段的最优控制序列,但只执行当前时刻的动作。到了下一个周期,再刷新预测、重新求解。这就是滚动优化,也是滚动时域控制的由来。反馈环节会把真实系统的状态量态测回来,比如储能实际SOC偏离了预测值,求解器会在下一步自动纠正,不会像开环优化那样一路错到底。

我在实际项目里把MPC和日前计划做了对比,同样一组预测数据,日前计划在光伏实际出力偏离预测40%的时候,总成本比MPC高出差不多15%,而且紧急购电次数明显变多。这个差距不是算法本身多神奇,而是滚动优化让决策始终基于最新信息,而不是旧计划。

2. MPC调度方案的整体设计

2.1 预测环节与滚动时域机制

MPC这套机制能起作用,前提是有一个预测模型,能对未来一段时间的系统输入进行预估。微电网里要预测的核心量是光伏出力和负荷需求。光伏出力可以用历史同期数据加天气修正来估算,负荷预测可以用相似日匹配、回归模型,甚至简单的时间序列方法。预测精度不需要特别夸张,因为MPC的滚动机制本身有纠错能力,预测偏差会在每个控制周期被刷新和修正,这一点很重要,别被“预测必须准”吓住了。

预测时域N的选择直接关系到性能。N太小,优化器只看得到眼前几步,不会为了避免傍晚高峰电价提前给储能充电;N太大,求解时间变长,而且远期预测本身偏差大,后面的约束条件反而成了算法的负担。我在这个项目中测试过N=6、12、24小时,效果差异明显。N=12小时的时候,MPC能提前利用夜间低谷电价给储能充电,也能在午后预测到晚间负荷爬坡,成本明显低于N=6。N=24小时虽然理论上全局视角更好,但在线求解时间翻倍,部分场景下远期预测误差反而带来了负面干扰。实测下来,12小时是最稳的选择,具体场景可以自己调。

滚动周期M一般取1小时,等于采样时间dt。每个控制周期到来时,算法读取当前储能SOC、当前实际负荷和光伏值,刷新未来一段时间的预测曲线,重新求解优化问题,取第一个控制量下发执行,等到下个周期再重复。整个过程可以理解成一个总是盯着前方路面、不断修正方向盘的老司机。

2.2 优化目标的数学表达与约束梳理

MPC的核心每次滚动求解的优化目标可以写成最小化总运行成本。在我的项目里,成本包括柴油发电机燃料成本、从电网购电成本、储能充放电带来的寿命损耗折价,以及极端情况下切负荷的惩罚成本。买电成本和卖电收入用分时电价模型,夜间电价低、白天电价高,这样MPC就会自动选择夜间充电、白天放电的策略,不需要人为去写规则。柴油发电机燃料成本用出力P的二次函数近似,但在MILP模型里一般做分段线性化处理,用几条线性段去逼近曲线,既能保留成本特征,又不至于把问题变成非线性规划。

约束条件方面,首先是功率平衡约束:P_pv + P_de + P_dis + P_buy = P_load + P_ch + P_sell,这个等式在每个时段的每个滚动窗口内都必须成立。储能模型用离散化的SOC递推公式,SOC(k+1) = SOC(k) + (P_ch * eta_ch - P_dis / eta_dis) * dt / Cap,充放电功率同时受上下限约束,并用0-1变量保证互斥。柴油机给最小出力和最大出力约束,再爬坡约束限制相邻时段出力变化,避免实际执行时响应不过来。电网交互功率也有一个上限,买的电和卖的电同样通过0-1变量互斥,虽然实际场景里同时买和卖没有物理意义,但不加约束,求解器就会钻空子。

目标函数和约束合在一起,构成一个标准的MILP,每轮滚动都要求解一次。这也是MPC微电网调度的常态。

2.3 MPC与传统PID、开环优化的关键差异

如果说传统PID是反馈控制,那MPC就是预测加反馈双管齐下,MPC本身就能考虑未来变化和系统约束。和开环优化相比,MPC多了一个反复优化的回路,每个周期都会根据最新状态重新计算,这是它能应对不确定性的核心。和PID比,MPC处理多变量强耦合约束的能力强得多,微电网里储能、柴油机、并网功率三个变量协同动作,PID很难同时调好,MPC则可以一次性在一个优化框架里解决。

用生活化类比来说,开环优化就像按一张地图走完全程,一旦路上堵车就没办法绕;PID像是盯着眼前,哪里有偏差往哪修;MPC则是提前看导航,知道下一个路口要左转,提前几百米就开始变道。这个预见性正是它在微电网调度里值钱的地方。不过要提醒一句,MPC的优势有前提:预测模型不能太离谱,系统状态也不能完全不可测。如果传感器数据质量太差,MPC反馈校正就失去意义了。

3. Python代码实现细节拆解

3.1 建模拟合数据准备工作

用Python实现MPC微电网调度,先把数据准备好。我用的是一组典型日数据:24小时光伏出力曲线,峰值约350kW,典型日负荷曲线,峰值约500kW,分时电价,夜间0.25元/kWh,白天峰时0.85元/kWh。数据格式就是普通的pandas DataFrame,每行一个时段,dt=1小时。

代码第一步是定义所有常量和数据结构。储能容量设为600kWh,最大充放电功率100kW,充放电效率都取0.95,初始SOC设为0.3。柴油机最大出力200kW,最小出力20kW,爬坡约束每小时变化不超过50kW。并网交互功率上限250kW。这些参数全部写成配置变量,方便后续调参,不要写死在模型里,否则后期改一个参数要到处翻代码。这是我在项目里吃过亏的地方,早期把效率写死在公式里,后来换电池参数的时候找了好半天。

预测数据的获取方面,我做了一个简化处理:用历史同期数据加一个随机扰动模拟预测值,这样能顺便测试MPC在预测误差下的表现。实际工程项目中,你可以用简单的时间序列预测、机器学习回归或者直接对接气象预报数据,代码接口保持一致就行,预测模块单独封装成一个函数,返回未来N小时的PV和负荷曲线数组。

3.2 储能、光伏、柴油机与并网模型的组织

建模阶段我习惯按设备写类或者写数据类,把设备参数和跑优化要用的约束参数集中管理。储能模型是最复杂的。SOC递推是线性差分方程,在滚动求解中直接作为约束条件写进模型。充放电功率需要定义两个连续变量P_ch和P_dis,再引进一个0-1变量u_ch,约束写成:

P_ch <= P_max * u_ch P_dis <= P_max * (1 - u_ch) P_ch >= 0 P_dis >= 0

这么写保证了同一时刻只能有一个方向动作,如果允许待机,两个变量都为0即可,u_ch可以为0也可以为1,模型自己会选成本最优的方式。很多教程为了简化直接忽略充放互斥,但实际做项目时不能省,否则结果虽然“数学上正确”,物理上根本不可行。

柴油发电机的成本用分段线性逼近。出力区间[20kW, 200kW]拆成3段,每段斜率不同,代表不同负载率下的油耗差异。这里用添加辅助变量的方式做线性化,不必深入展开,但思路是把非线性成本函数用折线代替,求解器还是线性模型,速度不受影响。

并网交互功率和光伏出力都是相对简单的变量和约束,光伏在预测时域内作为已知参数比较合理,因为光伏不可调度。并网功率P_buy和P_sell各定义一个连续变量,同时用0-1变量做互斥约束,逻辑和储能充放互斥一模一样,不再重复写。

3.3 求解器选型与MILP建模实操

求解器我用了Gurobi,学术用户是免费的,工业使用需要商业授权。不开源替代方案可以用PuLP加CBC求解器,也能跑MILP,速度比Gurobi慢一些,但小规模算例完全够用。我的项目规模大概是24个时段、储能和电网各12个0-1变量,CBC求解时间在1秒左右,Gurobi则在0.2秒以内,差别不算大。如果以后扩到48时段、更多设备,Gurobi的优势会更明显。

建模仿真直接用Gurobi的Python API,逐条加变量和约束。这里有个实操细节:尽量把重复性强的约束用循环批量添加,不要一条条写死。比如SOC递推约束24条一条条写容易,但算例改成长时段就会断胳膊断腿。批量生成后用for循环加约束,模型构建速度也快很多。

一个我比较推荐的建模习惯是:先把设备参数和决策变量定义清楚,然后分块添加约束,每个块添加完简单打印一下数量,方便排查问题。Gurobi模型调试最烦的是约束写错了导致无解,这时可以用model.feasRelax()看哪些约束被违反、违反了多少,能快速定位出问题的约束。

3.4 核心代码片段解读

这是我的MPC滚动主体代码结构,按实际项目简化后整理出来的,加了注释方便理解:

import numpy as np import pandas as pd from gurobipy import Model, GRB, LinExpr # ========== 参数配置 ========== T = 24 # 总仿真时长(小时) dt = 1 # 采样时间(小时) N = 12 # 预测时域(小时) # 储能参数 SOC_min, SOC_max = 0.1, 0.9 P_ch_max = 100 # kW P_dis_max = 100 # kW eta = 0.95 # 充放电效率(简化处理,充放同效) Cap = 600 # kWh # 并网与柴油机参数 P_grid_max = 250 # kW P_de_min, P_de_max = 20, 200 # kW Ramp = 50 # 爬坡限制 kW/h # 分时电价(元/kWh) price_buy = np.array([0.25]*6 + [0.55]*6 + [0.85]*6 + [0.45]*6) price_sell = price_buy * 0.8 # 售电价格约为购电的80%

滚动主循环:

def mpc_rolling(pv_pred_full, load_pred_full, soc_init): soc_current = soc_init plan_record = [] for k in range(T - N + 1): m = Model("MPC_Step") m.setParam('OutputFlag', 0) # 当前窗口内的预测数据 pv = pv_pred_full[k:k+N] load = load_pred_full[k:k+N] # 决策变量:储能充放、电网交互、柴油机出力 P_ch = m.addVars(N, lb=0, ub=P_ch_max, name="P_ch") P_dis = m.addVars(N, lb=0, ub=P_dis_max, name="P_dis") u_ch = m.addVars(N, vtype=GRB.BINARY, name="u_ch") P_buy = m.addVars(N, ub=P_grid_max, name="P_buy") P_sell = m.addVars(N, ub=P_grid_max, name="P_sell") u_grid = m.addVars(N, vtype=GRB.BINARY, name="u_grid") P_de = m.addVars(N, lb=P_de_min, ub=P_de_max, name="P_de") SOC = m.addVars(N+1, lb=SOC_min, ub=SOC_max, name="SOC") SOC[0] = soc_current # 设定当前SOC为初始值 # 约束1:功率平衡 for t in range(N): m.addConstr( pv[t] + P_de[t] + P_dis[t] + P_buy[t] == load[t] + P_ch[t] + P_sell[t], name=f"balance_{t}" ) # 约束2:储能充放互斥 for t in range(N): m.addConstr(P_ch[t] <= P_ch_max * u_ch[t]) m.addConstr(P_dis[t] <= P_dis_max * (1 - u_ch[t])) # 约束3:SOC递推 for t in range(N): m.addConstr( SOC[t+1] == SOC[t] + (P_ch[t]*eta - P_dis[t]/eta) * dt / Cap ) # 约束4:购电售电互斥 for t in range(N): m.addConstr(P_buy[t] <= P_grid_max * u_grid[t]) m.addConstr(P_sell[t] <= P_grid_max * (1 - u_grid[t])) # 约束5:柴油机爬坡 for t in range(1, N): m.addConstr(P_de[t] - P_de[t-1] <= Ramp) m.addConstr(P_de[t-1] - P_de[t] <= Ramp) # 目标:最小化购电成本加柴油成本,扣除售电收入,加小量电池损耗罚项 obj = LinExpr() for t in range(N): obj += price_buy[k+t] * P_buy[t] - price_sell[k+t] * P_sell[t] obj += 0.5 * P_de[t] + 0.002 * P_de[t] * P_de[t] # 简化的柴油成本 obj += 0.01 * (P_ch[t] + P_dis[t]) # 电池损耗罚项 m.setObjective(obj, GRB.MINIMIZE) m.optimize() # 只取第一个时段的控制量执行 action = { 'P_ch': P_ch[0].X if m.status == GRB.OPTIMAL else 0, 'P_dis': P_dis[0].X if m.status == GRB.OPTIMAL else 0, 'P_buy': P_buy[0].X if m.status == GRB.OPTIMAL else 0, 'P_sell': P_sell[0].X if m.status == GRB.OPTIMAL else 0, 'P_de': P_de[0].X if m.status == GRB.OPTIMAL else 0, } plan_record.append(action) # 更新SOC状态 soc_current = SOC[1].X if m.status == GRB.OPTIMAL else soc_current return plan_record

这段代码有几点说明。首先,每个控制周期都重新创建一个Model对象,模型规模小,创建开销可以忽略,而且避免了变量重用导致的问题。其次,目标函数里我用了一个简单的线性加二次柴油成本,实际工程里建议直接分段线性化,Gurobi对二次目标处理得也很快。最后,SOC从上一个周期的求解结果中取出当前时段的SOC值,作为下一个周期的初始值,这就是反馈校正的核心实现。

4. 算例结果与调度效果分析

4.1 参数设置与典型预测曲线

我用一组典型日数据来测试。光伏预测曲线是钟形曲线,8点开始出力,12到14点达到峰值350kW,17点后降为0。负荷曲线则呈现双峰特征,早上一个小高峰,傍晚18到20点达到全天最高500kW。电价按前面提到的分时规则设定,夜间0.25元,白天峰时0.85元。

初始SOC设为0.3,这个值有点低,意味着MPC在前期会比较谨慎,不会急着把电放光。根据我自己项目的测试,初始SOC越高,MPC前几个小时的购电策略就越激进,因为算下来储能手头有足够的电量去套利。这个现象可以反过来用:如果需要应对突发高负荷,可以提前把SOC保持在一个较高水平,MPC会自己找到最优的利用方式。

4.2 结果分析:功率平衡与SOC走势

运行结果最直观的是SOC曲线。MPC在凌晨1到6点电价低谷段,主动给储能充电,SOC从0.3逐步上升到0.8左右,同时购电功率稳定在100kW附近,不超过储能充电上限。到了白天峰时电价段,MPC开始让储能放电,SOC从0.8缓慢下降,放电功率把下午一部分负荷顶掉,光伏出力也优先自用而不是卖给电网,因为自用替代的是0.85元的高价电,比低价卖电划算得多。

真正有意思的是傍晚负荷高峰时段。18点到20点,光伏出力已经接近0,负荷高达500kW,柴油机在爬坡约束下逐步加出力到200kW上限,储能把最后一点电量放出来,同时从电网买电补足缺口。由于MPC在白天提前预留了储能电量,傍晚这段高价电购电量比没有储能的方案明显低很多,成本优势就在这里体现出来。功率平衡约束每时刻都满足,我这边的检查方式是直接把每时刻的发电加总与负荷加总做差值,全部小于0.001kW,说明模型构建没问题。

4.3 与基准策略的经济性对比

为了验证MPC的价值,我做了两个对照组:一组是简单的“只购电不储能”方案,一组是“固定日前计划”的开环方案。三日模拟结果如下:

方案总运行成本(元/日)购电成本占比柴油损耗折算
无储能纯购电5240100%0
日前开环计划436082%18%
MPC滚动优化393076%24%

MPC方案比开环计划节省约10%的日运行成本,比无储能方案节省25%。节约来源主要有两块:一是通过储能在低谷充电、高峰放电,用峰谷价差套利;二是滚动反馈让策略能及时响应实际运行状态的变化,不会因为预测偏差导致储能电量浪费。我个人经验是,这类项目里MPC和开环的差距大小,直接取决于预测偏差和电价峰谷比。偏差越大、峰谷差越大,MPC的滚动优势越明显。

5. 实际运行中踩过的坑与排查建议

5.1 求解速度与MILP建模的取舍

MILP在滚动求解中最大的问题是0-1变量多导致求解时间不可控。我的第一版模型把每个时段都定义了多个0-1变量,预测时域N=24,一次求解要两秒多,滚动24次接近一分钟,在实际控制场景里很难接受。后来把预测时域缩短到12小时,并删掉多余的互斥变量,求解时间降到了0.3秒以内。减少变量位数的意义比优化算法更大,这句话在MPC实践里特别适用。

如果预测时域必须很长,可以试试MIPFocus参数,启动一个可行解然后慢慢改进,也可以用TimeLimit限制单次求解时间,超时就用当前最好的可行解。MPC的滚动结构本身对次优解有容忍度,哪怕这一轮求解结果不是全局最优,下一轮滚动也会把偏差纠正回来,这个特性给工程实现留了不少余量。

5.2 预测误差下的鲁棒性问题

用MPC不代表预测随便做。光伏预测晴天和阴天的差别直接决定储能策略,如果预测光伏300kW实际只有200kW,MPC可能在白天多买了电、晚上多烧了柴油。针对这个问题,我在代码里加了扰动验证逻辑:对预测曲线加入10%到30%的随机扰动,反复跑仿真,观察SOC曲线是否出界、成本是否大幅上升。经过几轮扰动测试后发现,SOC在波动剧烈时会偶尔碰到0.1的下边界,但并没有彻底无解,说明MPC的反馈确实在起作用。

如果要进一步提高鲁棒性,可以给SOC约束留出一个安全边界,比如实际运行时把SOC_min从0.1上调到0.2。这样即使预测出现偏差,储能也不会被完全放空。代价是少了10%的有效容量,但换来的是系统安全性,性价比很高,我建议实际部署时都这么做。

5.3 工程落地的几个操作建议

代码在大规模落地前,先把单位换算这一关过了。风电光伏功率单位是kW,储能容量单位是kWh,SOC递推公式里乘以dt后要除以Cap,单位才能对齐。很多新手在这里犯错,算出来的SOC曲线要么突变要么漂移,第一个要检查的就是单位。第二,滚动过程中的数据接口设计成函数形式,预测模块不管用什么算法,只要返回标准格式的数组就能接入MPC循环,后续替换预测算法不用改动优化逻辑。第三,工程上建议加一个“死区”逻辑,当充放电功率小于某个阈值时直接置为0,避免控制器频繁下发微小动作导致执行机构磨损。这个在纯仿真里看不出来,但接实际设备后非常重要。

排查问题的顺序我也总结一下:先检查无解问题,用feasRelax看哪个约束被突破;再检查SOC曲线是否合理,不合理优先查单位换算和互斥约束;最后才看经济性指标差异。按照这个顺序,能少走很多弯路。

结尾留一句实在话

做MPC微电网调度优化,最容易犯的错误是贪多求全,一上来就想着SOC、温度、寿命衰减、碳排放全建模,结果代码跑一天都跑不完一个算例。我的建议是从最简单的最小成本模型起步,先把MPC滚动框架跑通,再逐步叠加约束,每加一个约束就验证一次结果变化是否合理,这样调参心里才有底。看代码时多留一点余量给安全边界,陷入无解死循环时及时用松弛工具定位问题,这些经验比任何理论都实在。我个人的体会是,MPC这个方向真正难的不是公式推导,而是把工程细节打磨到账实相符,那才是让调度方案真正落地可用的关键。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询