资讯详情

资讯详情

微电网调度中的MPC实践:从建模到Python滚动优化

这几年做微电网调度相关项目我最大的感受是很多方案不是输在了算法精度上而是输在了“假设太完美”。早上根据光伏预测做出了一份24小时调度计划下午一场云飘过来全部作废晚上电价低谷想给储能充满结果负荷预测偏了20%SOC没顶上去晚高峰只能高价买电。这些问题传统基于规则的控制处理不了一次性离线优化也处理不了。于是我把目光转向了模型预测控制Model Predictive ControlMPC——用滚动优化替代一次性求解用反馈更新对冲预测误差。这篇文章记录了整个研究过程为什么选MPC、微电网怎么建模、Python里怎么把MPC转成一个实际可求解的优化问题以及算例里看到的真实收益和工程上必须注意的坑。1. 为什么是MPC微电网调度不是“把明天的计划做对就行”1.1 接力棒式的调度从“事前最优”到“滚动最优”先还原我最初踩过的那个坑。当时我的方案很简单提前一天用预测数据跑一个线性规划把第二天96个时段的储能充放电功率和电网交互功率定下来。思路没有错但它默认“预测准、模型准、执行准”。现实的微电网里光伏预测误差随晴雨变化可以达到20%至40%负荷噪声也不小。一旦实际值和预测出现偏离日前计划就失去了最优性如果偏差累积到SOC的约束边界上连可行解都不是。这就引出了MPC的核心思想不要一锤子定死未来一整天而是每15分钟重算一次未来几小时的计划并且只执行第一个控制动作。就像开车去一个陌生地方导航不会一次给你画出全程然后闭眼开而是每隔一段路根据你的实时位置重新规划前方路线。你用的还是同一张地图、同一个目的地但决策是滚动更新的。滚动带来的好处理论上有两点第一利用最新的实测信息修正状态估计和预测曲线减少不确定性带来的模型失配第二把优化问题限制在一个较短的预测时域内计算量可控可以做得更精细。这两点正好对应微电网调度的核心矛盾——既要全局最优又要实时响应。1.2 MPC的三件套预测模型、滚动优化、反馈校正MPC不是某个具体算法而是一套控制框架。真正落地时这套框架由三个部分构成。第一部分是预测模型。在微电网里我们需要描述储能SOC怎么随时间更新、功率平衡怎么成立、联络线交互功率受哪些因素影响。最常用的是离散状态空间方程x(k1) A·x(k) B·u(k) W·d(k)其中x是状态向量比如SOCu是控制向量储能充放电功率、购售电功率d是扰动向量光伏出力、负荷功率的预测值。A、B、W是需要根据物理规律和张量单位换算确定性推导的。第二部分是滚动优化。在每一个采样时刻k基于当前状态和未来N个时段的预测序列求解一个从k到kN-1的有限时域优化问题得到一串最优控制动作序列。注意这一步求解出来的是整个时域的控制计划但我们只提取第一个分量真正下发执行。第三部分是反馈校正。到了下一个采样时刻k1用实测值修正状态变量和各设备的预测把时域窗口整体向前平移一步重新求解。这个过程被称为“后退时域”。三件套缺了哪个都不行。没有预测模型优化无从谈起不滚动就等于退化成离线规划不做反馈预测误差会不断累积到失控。1.3 和规则策略、日前大计划比一比我把这几种思路放到同一个对比例里它们的分工差异会更直观对比维度基于规则控制日前离线优化MPC滚动优化优化目标无明确目标按预设优先级放电一次求解全天计划目标明确滚动求解目标随时间修正预测依赖低极高预测误差直接影响执行中等滚动更新能缓解实时修正能力靠阈值触发动作粗糙无通常按计划开环执行强每步根据实测重算储能约束处理靠逻辑判断容易进入SOC禁区约束写在优化里但无法应对突变约束写在优化里且随窗口更新计算复杂度最低中等中等或偏高视时域长度而定典型适用场景小型独立微网、应急切换日前电力市场、计划型运行含有高比例不确定性电源的并网微网这张表里最关键的信息是规则控制不是“没有思想”而是它的思想太刚性——比如“SOC低于40%就停止放电”这种策略完全没有考虑当下电价高不高、未来几小时光伏冲不冲得上。日前优化有全局视野但它是开环的。MPC在两者之间找到了一个很好的平衡点用滚动时域保留了全局协调能力用反馈回去了局部响应能力。2. 建一个能写进代码的微电网模型2.1 设备级方程储能、光伏/风电、负荷、联络线在写MPC之前我们得先把微电网翻译成一组数学方程。以我经常用的典型结构为例一个园区级并网微电网包含分布式光伏、风力发电机、储能系统和接入上级电网的联络线负荷由这些分布式电源和电网共同满足。储能是最重要的状态型设备。它的核心约束是荷电状态SOC的递推关系。如果用一个统一的充放电功率P_b符号为正表示充电负表示放电那么SOC(k1) SOC(k) - P_b(k) · Δt / E_b 简化模型但实际储能充放电效率不同这个模型误差很大。我推荐拆成两个变量P_ch和P_dis并显式加入效率系数SOC(k1) SOC(k) η_ch·P_ch(k)·Δt / E_b - P_dis(k)·Δt / (η_dis·E_b)其中η_ch是充电效率η_dis是放电效率E_b是储能额定容量。为什么要拆开因为如果不拆同一时段可能同时出现“充电”和“放电”SOC看似没变化实际能量却被白白损耗掉了优化器会钻这个空子。拆开后我们可以在约束里加入P_ch·P_dis 0或通过在目标函数中设置极小额惩罚来避免同时充放。功率平衡是微电网调度必须严格满足的等式约束。并网微电网的功率平衡写成P_pv P_wind P_dis P_grid_buy P_load P_ch P_grid_sell即所有电源出力加购电功率等于负荷加储能充电和售电功率。这里的P_grid_buy和P_grid_sell也是一对互补变量不能同时大于零。为了让问题可解多数情况下允许购售电同时存在但实际上被电价的价差天然抑制——不过在模型里最好仍然显式处理避免出现两台“变压器”一边买一边卖的怪象。光伏和风机的出力在小时级调度中一般被视为不可控扰动直接取预测值。高频控制里可能还需要给它们留可调度余量但那是后话。联络线的约束主要是传输功率上限以及购电、售电方向的表达。2.2 状态量、控制量与扰动量怎么切分MPC建模最容易被新手搞混的就是哪些量是变量哪些量是参数把这里理清楚后面写代码的时候会省很多事。状态量State描述系统内部记忆的量必须能够由一段时间内的控制序列递推得到。微电网里核心状态量是储能SOC若需平滑滤波还可能引入滤波状态。状态量的更新方程是被优化的等式约束。控制量Controlled Variable调度决策可以直接改变的量。典型的是储能充放电功率P_ch/P_dis、联络线购售电功率P_grid_buy/P_grid_sell。如果有可控负荷也可以把可平移负荷纳入控制变量。扰动量Disturbance无法控制但必须纳入平衡的量。典型是光伏、风电预测出力和负荷预测功率。它们作为已知参数序列进入MPC问题。我经常用一个简单原则帮助区分如果把它作为变量的地位抬高了可能会导致求解器出现“拿未来满足现在”的幻觉。比如有人把光伏出力也当作变量优化器会为了让成本好看让光伏“在某些时段少发”但实际上光伏在运行中是不可控的除非有弃光手段这会得到一个不可执行的方案。所以除非明确建模了弃光控制否则光伏和风机出力都应该是给定序列。2.3 预测曲线不完美怎么办扰动模型的设计MPC的一大优势是能用反馈应对预测误差但这不代表预测模型可以随便拍脑袋。预测曲线作为MPC问题中的参数序列直接影响优化结果的方向和质量。工程上常见的做法是采用“外部预测自适应修正”两层结构。外部预测来自数值天气预报或负荷预测模型提供基准曲线自适应修正使用当前时刻的实测误差来修正未来几小时P_pred_修正(ki) P_pred_base(ki) (P_actual(k) - P_pred_base(k))这个修正操作本质上是一个简化的积分校正能显著消除稳态偏差。如果用卡尔曼滤波或指数平滑来做逐时段修正效果会更平滑但调参成本也随之上升。还有一个必须认识到的现实预测误差会随预测时域增长而变大因此预测时域N的选择不应过长。光伏波动显著的场景预测时域取4到8个小时通常比取24小时更稳健。这也是MPC一个反直觉的设计点——更长的预测时域并不总是带来更好的表现。3. Python里把MPC落地用cvxpy描述一个二次规划调度器3.1 目标函数的四项成本怎么写成数学式当模型建立好之后MPC的核心就变成了一个数学优化问题。我采用的思路是用一个二次规划QP结构来描述调度目标min ∑_{k0}^{N-1} [ c_buy(k)·P_grid_buy(k)·Δt - c_sell(k)·P_grid_sell(k)·Δt α·(P_grid_buy-P_grid_sell)² ] β·∑_{k0}^{N-1} (P_ch(k)P_dis(k))² γ·∑_{k0}^{N-1} slack(k)第一项是购电成本第二项是售电收入。第三项是联络线功率波动惩罚——这一项非常重要如果没有它MPC可能在每个时段都剧烈调整购电功率对电网冲击很大。第四项代表电池充放电损耗的二次惩罚避免频繁充放。最后的slack是软约束变量后面细说。要注意的是量纲统一问题。电价单位通常是元/kWhΔt单位是小时功率单位是kW那么时间能量相乘得到的是kWh再乘电价就是元。为了让各惩罚权重不会因为量纲差异占据主导我通常先把功率基准设为储能最大功率把价格基准设为平均电价再进行权重调试。3.2 约束矩阵平衡约束和状态约束在cvxpy中我们不写矩阵A、B而是直接声明变量和表达式约束。但为了保持MPC的工程规范我强烈建议把每一个约束分类成三条一是平衡约束。对应功率平衡等式。在cvxpy里就是一条等式列表对每个k都成立。二是设备物理约束。包括储能充放电功率上下限、购售电功率上下限、联络线功率限制、SOC上下限以及SOC递推约束。三是逻辑约束。包括P_ch和P_dis的互补关系、P_grid_buy和P_grid_sell的互补关系。前面提到的互补关系通常是非凸的但这在长时间尺度调度中会显著拖慢求解速度。工程上有两种近似一是加入极小惩罚项例如对P_ch·P_dis的乘积添加一个很小的正值目标项二是用整数变量强行限制方向。前者计算快但可能存在微小同时充放后者更精确但是混合整数规划。对于纯实时调度我一般优先用第一种方法。3.3 核心代码滚动优化循环怎么实现这里给出一个结构完整的核心代码段使用cvxpy实现MPC滚动优化。代码省略了一些数据预处理重点展示建模和滚动执行的骨架import cvxpy as cp import numpy as np def build_mpc_problem(N, dt, E_b, soc_min, soc_max, soc_start, pv_pred, load_pred, price_buy, price_sell, p_ch_max, p_dis_max, p_grid_max): # 变量声明 P_ch cp.Variable(N, nonnegTrue) P_dis cp.Variable(N, nonnegTrue) P_buy cp.Variable(N, nonnegTrue) P_sell cp.Variable(N, nonnegTrue) SOC cp.Variable(N) slack cp.Variable(N, nonnegTrue) # 约束集合 constraints [] constraints.append(SOC[0] soc_start - P_ch[0] * dt / E_b P_dis[0] * dt / E_b) for k in range(1, N): constraints.append(SOC[k] SOC[k-1] - P_ch[k] * dt / E_b P_dis[k] * dt / E_b) for k in range(N): constraints.append(SOC[k] soc_min) constraints.append(SOC[k] soc_max) constraints.append(P_ch[k] p_ch_max) constraints.append(P_dis[k] p_dis_max) constraints.append(P_buy[k] p_grid_max) constraints.append(P_sell[k] p_grid_max) # 功率平衡 constraints.append(pv_pred[k] P_dis[k] P_buy[k] load_pred[k] P_ch[k] P_sell[k]) # 软约束示例SOC可越界但受罚 constraints.append(SOC[k] soc_min - slack[k]) constraints.append(SOC[k] soc_max slack[k]) # 目标函数 objective cp.Minimize( cp.sum(cp.multiply(price_buy, P_buy) * dt) - cp.sum(cp.multiply(price_sell, P_sell) * dt) 100 * cp.sum(cp.square(P_buy - P_sell)) 10 * cp.sum(cp.square(P_ch P_dis)) 1000 * cp.sum(slack) ) prob cp.Problem(objective, constraints) return prob, P_ch, P_dis, P_buy, P_sell, SOC, slack def run_mpc(pv_actual, load_actual, pv_forecast_all, load_forecast_all, price_buy_all, price_sell_all, N, dt, soc_start): # 这里用一个非常简单的持久性预测做演示 horizon N horizon N P_buy_seq, P_sell_seq, P_ch_seq, P_dis_seq [], [], [], [] soc soc_start for k in range(len(pv_actual)): # 取预测实际代码中建议外部预测模型给出 pv_pred_k pv_forecast_all[k:khorizon] load_pred_k load_forecast_all[k:khorizon] price_buy_k price_buy_all[k:khorizon] price_sell_k price_sell_all[k:khorizon] prob, P_ch, P_dis, P_buy, P_sell, SOC, slack build_mpc_problem( Nhorizon, dtdt, E_b200, soc_min0.2, soc_max0.9, soc_startsoc, pv_predpv_pred_k, load_predload_pred_k, price_buyprice_buy_k, price_sellprice_sell_k, p_ch_max100, p_dis_max100, p_grid_max300) prob.solve(solvercp.CLARABEL) # 只执行第一时段的动作 P_buy_seq.append(P_buy.value[0]) P_sell_seq.append(P_sell.value[0]) P_ch_seq.append(P_ch.value[0]) P_dis_seq.append(P_dis.value[0]) # 用真实状态更新下一轮SOC模拟反馈校正 soc_next soc - P_ch.value[0]*dt/200 P_dis.value[0]*dt/200 # 在真实工程中这里应使用SOC实测值 soc soc_next return P_buy_seq, P_sell_seq, P_ch_seq, P_dis_seq这段代码最值得注意的结构是光伏和负荷的预测序列应该是外部传入且能滚动的而实际状态更新用的是实测值或真实仿真值。在实际部署时soc那一步千万不要用一次开环的模型递推全程代替否则就失去了MPC反馈校正的意义。3.4 变量初始化与时间步的常见坑很多人把MPC代码写出来后第一轮求解就报infeasible几乎所有情况都出在这几个点上。第一个是初始化时域不足。预测序列长度如果小于N切片出来的长度不够导致平衡约束数量与预测序列不匹配。处理办法是补零或以最后一个预测值重复填充至N。第二个是储能SOC递推的初始值问题。滚动过程每个时刻的SOC必须和上一时刻的实际值对齐不能直接沿用开环计算值。我建议在仿真循环开始前定义一个全局变量soc_actual每轮求解后根据执行功率和效率更新它然后作为下一轮约束的soc_start。第三个是时间步的单位。ΔT到底是小时还是分钟直接决定SOC递推式中为什么乘以Δt/E_b。如果代码里把单位混了储能充放一会儿SOC就漂到2.3这类错误通常极难排查。我的习惯是在代码开头统一用小时作为时间单位外部数据的15分钟间隔就写成0.25。4. 算例把MPC跑起来看它能省多少4.1 算例参数一个100kW储能级别的小微网为了便于复现我设计了一个足够典型又不复杂的场景。微电网结构如下光伏装机200kW风机装机150kW储能额定功率100kW容量200kWh负荷峰值约180kW与上级电网的联络线功率上限为300kW。采样周期15分钟单日调度周期96个时段。光伏和负荷曲线我使用了带随机噪声的典型夏季日数据。为了让三个策略差异更明显我在光伏曲线中设置了一段云遮效应午后14点到15点半之间光伏出力从满发掉到额定30%再恢复。负荷则在晚高峰18点到20点有一个明显峰值。购电价采用峰平谷三段式峰时0.85元/kWh平时0.55元/kWh谷时0.25元/kWh售电价固定为0.40元/kWh。在这个场景下对比了基于规则调度、日前开环优化和MPC滚动优化三种方案。MPC的预测时域设为8个时段即未来2小时滚动优化。4.2 三种调度策略同台对比我先说明一下三种方案的实现口径避免出现“算法欺负人”的对比偏差。基于规则策略采用常见的优先级逻辑光伏优先自发自用余电先充储能SOC高于85%再售电负荷高峰优先放储能。日前优化假设当天预测完全准确一次性给出96时段计划并严格开环执行。MPC则每15分钟获取一次最新预测和实测SOC重新优化未来8个时段。下面是模拟得到的关键指标表基于虚构的仿真数据潮汐取整指标规则策略日前离线优化MPC滚动优化当日购电成本元236178164当日售电收入元213641净购电成本元215142123联络线峰值功率kW192165148SOC日末值%425049负荷失配次数000MPC相对规则策略净购电成本下降了约43%相对日前优化下降了约13%。成本降低的大头出现在云遮时段和晚高峰。规则策略在云遮时只能依赖电网高价购电日前优化因为开环执行无法在云遮发生后重新调整储能释放策略MPC则在每个时间点根据新预测实时重新分配利用储能把云遮期间的缺口补上并把晚高峰前的SOC留得恰到好处。联络线峰值也被明显抑制了。这个指标的改善来自目标函数里的功率波动惩罚项——它让MPC刻意避免了在某个时段突然大量购电。对于一个并网友好型微电网来说减少峰值功率往往比单纯降低电费账单更重要。4.3 预测时域、惩罚权重和SOC预留怎么调MPC落地之后调参占了我一半以上的时间。这里分享几个最关键的调参经验。预测时域N的选择。这个参数对系统行为影响极大。N太小MPC只看得到眼前两三个时段容易在高价时段猛放电下一时段发现SOC不够N太大预测误差带来的影响会被放大且求解时间成倍增长。我在这个算例里比较过N4、N8和N16N4时净购电成本反而比N8高约9%而N16和N8差别不大。复合场景建议先取2到4小时对应步数再根据预测质量调整。联络线波动惩罚项。这个权重α如果设为零MPC的控制序列会在价格跳变点附近出现明显振荡设太大又会影响正常购电的经济性。经验做法是先把α设为一个很小的正数让它只起到正则化作用然后观察功率曲线的平滑程度逐步增大。电池磨损惩罚项β。没有这项权重时优化器会用尽一切机会反复充放储能来套利这在物理上会严重缩短电池寿命。我用的是一个简单假设电池循环寿命带来的等效成本折旧折算到每次充放电功率平方中。β的量级需要通过计算电池更换成本除以总可循环能量得到不能凭空拍。SOC预留区间。这是我从实际运行中吸取的最大教训。MPC如果不加终端约束预测时域结束时SOC会倾向于贴在下边界上因为优化器只关心窗口内的成本。解决方法是给SOC约束留一个安全缓冲区例如物理允许0.1到0.9模型约束设0.2到0.8再配合slack软约束兜底。这个缓冲区相当于给下一轮优化留了一条后路。5. 工程化要注意的四个细节5.1 求解器选择和计算时延仿真里求解速度无所谓但实时控制里求解器必须在采样周期内完成。15分钟采样周期看起来绰绰有余但当模型加上整数变量比如燃气机组启停、储能状态切换求解时间可能从毫秒级飙升到分钟级。我的建议是能线性就线性能连续就连续。先把储能、电网交互这些连续变量跑通把启停等整数逻辑用规则或参数分支处理避免一开始就上混合整数规划。如果非用不可优先选开源但够用的求解器组合比如cvxpy搭配Clarabel或HiGHS它们在中小规模问题上速度表现很好。实测计算时延还有一个容易被忽略的点cvxpy建模本身也耗时。每次滚动循环都重新build一个Problem对象数据量大时会带来不必要的开销。更工程化的做法是提前把变量和约束结构定义好循环中只更新参数值例如直接修改外部data这会显著降低每轮求解前的建模时间。5.2 软约束比硬约束更“硬道理”MPC最怕的一件事是某一步约束写死而预测出了偏差整个问题直接infeasible。实时系统中infeasible比“不太优”严重得多意味着控制器没有输出。解决标准做法是软约束。把SOC约束、联络线功率约束中的关键等式或不等式改写为含变量slack的形式并在目标函数里给予较大惩罚SOC(k) ≥ SOC_min - slack(k) 0 ≤ slack(k) ≤ slack_max这样优化器永远能找到可行解。代价是可能越界但越界受罚它会尽量少越界。对一个实时调度系统来说“可以越一点点但及时拉回来”远远好于“求解失败导致无输出”。实际工程中通常将SOC越界惩罚设为目标经济项的10到100倍。5.3 从仿真到实物控制器要补的几块仿真程序跑通一个MPC离能装到控制器还差几步。我在从Simulink/Python模型转向实时系统时补了这几块内容状态估计。仿真里SOC是假定的真值实机里SOC来自BMS估算通常存在5%到10%的误差。MPC对SOC误差很敏感一旦状态偏差大最优动作就可能“打偏”。建议加入SOC在线校正或定期校准逻辑。通信与执行延迟。从控制器下发指令到变流器实际改变功率存在几十到几百毫秒延迟。在15分钟调度层这个延迟可忽略但如果MPC要下探到秒级辅助控制层就必须在建模时加入延迟补偿否则闭环震荡。预测数据接口。实时系统的预测曲线必须能和MPC的时间步严格对齐。时区、日历、夏令时这些都是我在调试中遇到过的实际问题建议统一使用UTC时间戳并在预测数据接口层做差值和清洗。5.4 扩展方向多时间尺度与分布式MPCMPC并不是只能做一种时间尺度。我在项目后面就开始考虑“双层MPC”结构上层用小时级MPC做日内滚动计划输出给负荷和储能的参考值下层用分钟级MPC跟踪上层参考并处理电网波动。这种分层结构的价值在于不同尺度的预测准确度和控制响应速度是不同的分层可以把它们分开处理。多园区联合调度时还可以做分布式MPC各微电网先独立求解再通过交互变量迭代协调联络线功率和共享储能容量。这种方法能在保护各园区运营隐私的前提下实现联合最优。不过这些扩展都要建立在单层MPC跑稳的基础上。我目前的经验是先把单层线性模型跑到极致再考虑非线性、整数变量和分布式——每加一层复杂度调参难度不是加法增长而是乘法增长。最后再分享一个个人习惯我在调试MPC时一定会先画一条“理想曲线”——假设预测完全准确让MPC在开环模式下跑一遍看最优控制轨迹长什么样。再把预测加噪声看重调度结果如何偏离。如果没有这条理想基线作参照一旦开环模型的效果就已经很差后面无论怎么调反馈参数都是在错误的地基上打补丁。先把模型方程和约束一项项对清楚再让MPC的滚动机制发挥作用调试效率会高很多。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →