资讯详情

资讯详情

虚拟电厂多时间尺度调度优化:日前日内MPC滚动修正与Matlab实现

做虚拟电厂VPP调度优化的同学应该都有过这种体会看到论文里“日前调度日内调度”的双层框架觉得思路很清晰但真要自己在Matlab里复现面对一堆时间尺度、约束条件和非线性项往往不知道从哪下手。这篇博客我想把一套复现过的多时间尺度虚拟电厂调度优化完整拆开来讲包括日前和日内两个优化模型怎么设计、目标函数和约束怎么写、源荷不确定性怎么处理、以及MatlabYalmip实现过程中的关键代码骨架和调试心得。内容偏实操适合正在做虚拟电厂、微电网优化调度课题的硕博生和工程师参考。先说清楚这套框架到底解决什么问题。虚拟电厂聚合了光伏、风电、燃气轮机、储能和可调负荷不同设备的时间响应特性差异很大——燃气轮机开机需要提前几个小时做计划储能响应是分钟级的而光伏和风电出力随时在波动。如果只做一次性的日前优化没法应对第二天实际运行时的预测偏差如果全部放到日内去做分钟级优化又面临“短视”问题只顾眼前经济性而偏离中长期计划。所以主流做法就是拆成两个时间尺度日前做小时级的经济调度确定机组启停和运行计划日内做分钟级的滚动修正跟踪日前计划的同时平抑功率波动。下面我按完整的复现流程来展开。1. 多时间尺度调度框架设计思路1.1 为什么非得拆成日前和日内两个尺度在动手写代码之前需要先理解两个时间尺度各自的职责边界不然模型写到一半很容易混。日前调度Day-Ahead Scheduling的定位是“计划层”。它基于第二天的负荷预测、光伏和风电出力预测以系统运行成本最小为目标把24小时划分成24个时段典型分辨率1小时求解出各机组每个小时的启停状态、出力大小、储能充放电功率、以及与上级电网的交互功率。因为分辨率较低、变量规模可接受可以引入0-1整数变量来刻画机组启停构建混合整数线性规划MILP模型求解。它回答的是“明天大致怎么运行”的问题。日内调度Intraday Scheduling的定位是“修正层”。当时间推进到当天最新一版的超短期预测通常精确到未来4小时的15分钟间隔已经拿到日前计划里的预测误差和突发波动都暴露出来了。日内调度基于实时更新的数据以更细的时间分辨率典型15分钟重新优化修正机组出力、调整储能充放电同时保证修正后的结果不会偏离日前计划太远。这两层的关系不是独立优化而是层级递进日前调度给日内调度提供计划参考值日内调度在跟踪参考值的基础上做经济修正。用通俗的话说日前调度是“先定大方向”日内调度是“方向盘微调”二者缺一不可。1.2 框架结构与信息交互流程我自己复现时采用的是三层信息流结构代码里也按照这个逻辑组织模块第一层数据输入。包括24小时负荷预测曲线、光伏/风电预测出力曲线、分时电价、设备参数燃气轮机容量/爬坡率、储能容量/充放电功率上限等。第二层日前调度优化。输出日前计划矩阵包含各时段燃气轮机出力、启停状态、储能SOC、联络线功率。第三层日内滚动优化。采用模型预测控制MPC思想每15分钟触发一次向前优化4小时即16个时段每次只执行当前时段的指令下一时刻重新来过。这套结构的核心逻辑是滚动时域Receding Horizon每次都用一个更新了最新预测数据的“滑动窗口”去求解不是一次算完就结束。窗口长度取4小时是工程上比较常用的折中——太短比如1小时会让日内修正缺乏前瞻性储能容易把电量耗尽太长比如8小时计算量增大且远期预测精度下降参考意义不大。注意日前调度和日内调度的模型并不是完全割裂的日内模型的目标函数中必须包含“对日前计划的跟踪”这一项否则日内模型会发现偏离计划更“省钱”进而造成两个时间尺度的结果互相矛盾。这个是很多初学者容易踩的坑。2. 日前调度数学建模与求解要点2.1 日前调度的目标函数与约束条件怎么写以一个简化虚拟电厂为例假设系统包含2台微型燃气轮机容量500kW、1套储能电池容量1MWh、最大充放电功率250kW、光伏阵列装机800kW、风电装机500kW并且可以与上级电网交换功率。日前调度的目标函数包含以下几项成本购电成本从上级电网购电的费用分时电价不同燃气轮机燃料成本出力越大成本越高可以用分段线性函数近似启停成本机组状态变化产生的费用属于固定成本弃风弃光惩罚为了让模型优先消纳可再生能源而设置的虚拟惩罚项目标函数可以写成如下形式这里只展示核心表达式省略具体系数矩阵% Yalmip风格的目标函数示意日前调度 Objective sum(sum(Price_DA * P_buy)) ... % 购电成本 sum(sum(Cost_MT(P_mt))) ... % 燃气轮机燃料成本 sum(sum(StartCost * z_start)) ... % 启动成本 sum(sum(ShutCost * z_stop)) ... % 停机成本 Penalty_W * sum(sum(P_wind_curt)) ... % 弃风惩罚 Penalty_S * sum(sum(P_pv_curt)); % 弃光惩罚约束条件这块重要性排序如下我踩过坑的地方都标了星号功率平衡约束任意时刻系统内所有电源出力加购电功率等于负荷加充电功率加卖给电网的功率。这是最基本的等式约束写不好一定无解或者结果异常。燃气轮机出力上下限与启停耦合约束机组在停机状态下出力必须为0。这个约束必须借助0-1变量来表达写成P_mt P_max * u_mt其中u_mt是启停二进制变量。少了这个约束模型会让停着的机组出力结果完全不对。爬坡约束相邻时段出力变化受爬坡率限制这和机组启停状态无关是纯不等式约束。储能约束包括充电功率上下限、放电功率上下限、SOC状态递推关系。其中SOC递推必须写成% SOC递推等式 soc(t1) soc(t) - (P_ch * eta_ch - P_dis / eta_dis) / Cap_E * dt;同时要限制SOC上下限一般不允许到0或满容量留10%安全余量。2.2 混合整数线性化的几个关键写法虚拟电厂模型本身是带非线性的比如储能充放电同时产生损耗、燃气轮机效率随负载率变化但为了用成熟的MILP求解器需要做线性化处理。实际操作中我用到最多的三个写法一是充放电不能同时发生的约束。储能同一时刻要么充要么放用互补约束P_ch M * u_ch、P_dis M * (1-u_ch)其中u_ch是0-1变量M是足够大的正数。注意M不能取得太大否则会引入数值稳定性问题一般取储能最大功率即可。二是燃气轮机分段线性成本。燃料成本本质上是一条凸曲线用分段线性函数逼近每段引入一个权重变量权重之和为1且相邻段权重有SOS2约束。Yalmip里直接支持SOS2但我建议初学者先用最简单的两段线性逼近精度对研究场景完全够用而且调试方便很多。三是日前调度里的时间耦合。储能SOC跨时段传递是典型的时序约束初学者容易写成只含当前时段的表达。正确的是把SOC作为状态变量在约束中连续传递让能量在时间维度“流动”起来。这里我推荐用矩阵形式批量写约束而不是for循环逐条添加既能减少出错概率求解速度也会快很多。3. 日内滚动调度的动态修正机制3.1 MPC滚动窗口与时域设置日内调度模型的核心是如何设置滚动窗口。我采用的参数设置是滚动周期15分钟预测时域4小时即16个时段控制时域15分钟即每个优化周期只执行第一个时段的结果。整个日内运行过程持续24小时相当于做了96次滚动优化。这里有个实操细节虽然控制时域是15分钟但在每次优化中这16个时段的控制变量都会同时求解出来只是除了第一个时段外其余结果会被丢弃。这种策略保证每一次决策都利用了最新信息原理和自动驾驶里的MPC控制一模一样。执行流程可以这样描述初始化读取日前调度计划将其作为日内调度的参考值。数据更新获取当前时刻的超短期负荷/光伏/风电预测值。模型求解以“尽可能贴合日前计划运行成本最小”为目标在16个时段上求解优化问题。指令下发只执行当前时段的那组控制指令。滚动推进窗口前移15分钟回到步骤2重新循环。3.2 日前计划跟踪项的权重怎么设计日内调度目标函数由两部分构成一是运行经济性购电成本、储能损耗等二是对日前计划的偏离惩罚。二者是矛盾的需要用权重系数平衡。这个权重如果设得过大日内模型就变成了“死板的执行器”完全失去了修正预测误差的能力设得过小日内结果会大幅偏离日前计划相当于日前调度白做了。我实测下来的经验是先在最简单场景下预测误差为0把权重调到基本不影响经济性优化结果的水平再叠加误差场景微调。具体的取值经验% 目标函数中的跟踪惩罚项 Objective_Intra Objective_DA_Ref ... % 经济性目标 Lambda_track * ... % 跟踪权重 sum(sum(abs(P_mt - P_mt_DA_ref))) ... Lambda_soc * ... % SOC跟踪权重 sum(sum(abs(soc - soc_DA_ref)));对于Lambda_track取值为燃料成本系数的1/3到1/5量级比较合理——既能让机组出力朝日前计划靠拢又保留对最新预测的响应能力。SOC的跟踪权重可以稍大一些因为储能的跨时段耦合强SOC跑偏容易导致后续时段无法调节。实测提示如果日内调度结果中燃气轮机出现频繁的启停切换大概率是Lambda_track取值过小。试着增大跟踪权重或者给启停状态也增加“相对于日前状态不变”的惩罚项就能让日内出力曲线平稳很多。4. 源荷不确定性与场景生成处理4.1 场景生成与缩减的方法选择多时间尺度调度的“两端”都受不确定性影响日前调度用的预测与实际偏差大日内调度用的超短期预测偏差小但仍存在。为了在优化模型中体现这种偏差有两种做法第一种是确定性方法用预测值直接参与优化靠日内调度来不断修正这是入门级也够用的方案计算快、代码简单第二种是随机优化方法通过生成大量场景来覆盖不确定性范围让优化结果对“可能发生的各种情况”更稳健。我在复现时采用了随机优化的思路用拉丁超立方采样对光伏、风电、负荷的预测误差进行场景生成然后用同步回代消除法或者直接k-means聚类把上千个场景缩减成10个典型场景让每个场景赋予一定概率权重参与优化。场景缩减这个环节是必做的直接跑上千个场景的MILP求解时间会爆炸到无法接受。10个场景的规模在计算精度和时间上的性价比最高。缩减的原则是让缩减后的场景集与原场景集的概率距离最小通俗讲就是保留那些差异最大、最具代表性的情况。4.2 确定性优化与随机优化的效果对比我分别跑过两种方案结果差异很直观确定性方案求解时间大约是1分钟级别日内需要频繁大幅修正出力燃气轮机的总启停次数偏多运行成本偏高随机优化方案求解时间会到510分钟甚至更久但日内修正幅度明显变小运行轨迹更平滑总成本大约降低了3%6%取决于预测误差的标准差设置。从研究角度建议论文中同时给出两种方案的结果用对比数据说明随机优化的必要性。如果只是工程实现确定性方案加上日内滚动修正已经完全够用毕竟随机优化的收益在误差较大时才明显误差小时反而增加不必要的保守性和计算成本。还有个重要细节随机优化的日前计划往往比确定性方案更“保守”——它会预留更多储能空间、设置更高的爬坡能力备用。这个在结果图上表现为SOC曲线更平缓上下边界不会触顶触底这其实是正确的稳健性表现。5. Matlab代码实现与工程结构解析5.1 工程文件结构与模块划分这套代码如果只有一个脚本从头写到尾调试会非常痛苦。我采用模块化工程组织每个模块一个.m文件主要有main_DA.m日前调度主程序加载数据、构建模型、调用求解器、保存结果。main_ID.m日内滚动调度主程序循环调用滚动优化函数。build_DA_model.m日前调度模型构建函数输入数据结构体返回Yalmip优化问题。build_ID_model.m日内调度模型构建函数结构与日前类似。data/generate_scenarios.m场景生成与缩减输出各场景概率。plot_results.m结果可视化绘制日前/日内出力对比、SOC曲线等。params.m集中设置所有参数方便批量修改。这里有一个设计经验把模型构建函数和主循环分开是关键。因为日内调度每次滚动都要调用一次build_ID_model如果模型构建过程里参数写死测试不同窗口长度、不同跟踪权重时就要频繁改函数内部代码非常容易被误改。用结构体统一传参后主循环代码几乎不用变只需要修改params.m里的配置。5.2 核心代码骨架与关键参数设置下面给出一段代码骨架是日内滚动调度主循环的核心逻辑可以作为自己实现的参考%% 日内滚动调度主循环骨架 horizon 16; % 滚动优化窗口16个15分钟时段4小时 period 0.25; % 时段长度0.25小时 N_slots 96; % 全天96个时段 x_opt_history []; % 记录所有时段的控制指令 for k 1:N_slots % 1. 更新当前时刻的超短期预测 P_load_pred get_ultra_short_load(k, horizon); P_pv_pred get_ultra_short_pv(k, horizon); P_wind_pred get_ultra_short_wind(k, horizon); % 2. 构建日内优化模型 [model, sol] build_ID_model(P_load_pred, P_pv_pred, P_wind_pred, ... params, DA_plan); % 3. 求解Yalmip Cplex/Gurobi optimize(model.Constraints, model.Objective, sdpsettings(solver,cplex)); % 4. 只取第一个时段的控制指令 x_opt value(sol.x); x_opt_history [x_opt_history; x_opt(1, :)]; % 5. 更新系统状态SOC等 params.initial_soc update_soc(params.initial_soc, x_opt(1, :)); end线性规划求解器我推荐用Cplex或Gurobi在Yalmip里配置非常简单。在求解器验证上一个非常实用的建议是先跑通一个没有整数变量的版本即先假设机组始终在线如果这个版本结果正确再引入0-1变量。这样可以把“模型本身的非线性和我自己新增的整数建模错误”这两个因素隔离开来。我自己调试过程中80%的报错都是整数变量相关的逻辑错误比如M矩阵取值太小导致可行域被错误压缩。6. 复现过程中的常见问题与排查技巧6.1 高频问题速查与解决方案这里把我在复现过程中实际遇到的高频问题整理成一张速查表排查方向都很具体问题现象可能原因排查与解决思路求解器提示无解Infeasible功率平衡约束写错或约束过紧先去掉与整数相关的约束测试连续模型是否可行储能全天不充电也不放电SOC递推约束没有跨时段耦合检查SOC约束是否用for循环逐时添加且初始条件是否传递机组停机时仍有出力启停耦合约束缺失检查是否写了P_mt P_max * u_mt日内修正幅度剧烈抖动跟踪权重过小或预测数据异常调大Lambda_track用平滑滤波器处理预测数据求解时间超过10分钟整数变量太多或M取值过大减少场景数量、增大MILP的MIPGap、换Gurobi试一下结果曲线出现明显的非物理跳变分辨率转换错误检查时段换算关系1小时等于4个15分钟时段系数换算务必正确6.2 几个提高调试效率的实战技巧最后一个部分分享几个调试技巧这些在多数论文附录和代码注释里都不会写。第一个技巧分段验证法。把整个程序分成“只调日前”、“只调日内但不加跟踪惩罚”、“日内加上跟踪项”三个阶段每完成一个阶段都画图检查一遍。这种分段验证法大大缩短了定位bug的时间比一次性跑完再从头查要高效得多。第二个技巧结果可视化是排查问题最有效的手段没有之一。把日前调度结果和日内调度实际执行结果画在同一张图上偏差一目了然——如果两条线大幅分离优先检查跟踪权重和数据更新逻辑如果日内曲线出现毛刺优先检查预测数据质量和模型约束设置。研究过程中把所有优化结果画出来看一遍比我对着数据看半天发现问题快太多了。第三个技巧一定要用真实场景测试边界条件。比如在光伏曲线为0的阴雨天场景下观察系统是否开启了燃气轮机且储能是否正常充电在负荷尖峰时段观察是否出现购电功率越限。边界场景能检验模型到底有没有“理解”物理逻辑比看统计指标靠谱得多。结尾几点实际体会整套框架从我最初开始复现到现在前前后后跑了快两个月中间推翻了三次建模方案。回头看最深的体会是多时间尺度调度的难点不在于某一层模型写不出来而在于两层模型之间的参数传递和权重平衡——你自己的日前计划做得好不好直接在日内修正幅度和总成本上体现出来。如果按从“框架理解-数学建模-场景处理-Matlab实现-调试验证”这样的顺序一步步推进大部分问题都能在动手写代码阶段就规避掉。另外建议拿到任何复现项目时先自己手动推导一遍小规模算例比如把24小时缩短成6小时、去掉一个机组确认逻辑无误后再放大规模这个习惯能帮你节省大量的排查时间。如果你也在做相关课题希望这篇内容能帮你少走一些弯路。
觉得有用,分享给同行:

为您的企业打造数字门面

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

立即咨询 →