ARTICLE DETAIL

资讯详情

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

虚拟电厂多时间尺度调度:模型、代码与滚动优化实践

虚拟电厂多时间尺度调度:模型、代码与滚动优化实践 1. 虚拟电厂多时间尺度调度到底在解决什么问题先别急着打开Matlab敲代码这个方向的核心问题得先想明白。虚拟电厂Virtual Power PlantVPP这个概念在新型电力系统里已经不算新词了它本质上就是把分散的分布式电源、储能系统、可控负荷这些资源通过协调控制技术“聚沙成塔”让它们在电力市场上以一个整体身份参与交易和调度。问题在于这些分布式资源的随机性和波动性都很强——光伏跟着云走风电看天吃饭负荷又跟着人的作息走——用一套固定不变的调度计划去应对这种多变的运行环境结果大概率是计划赶不上变化。多时间尺度调度就是冲着这个痛点来的。它把调度决策拆成不同时间粒度的层次日前调度Day-ahead Scheduling在24小时前基于预测数据做一整天的资源分配相当于给系统定一个基准运行计划日内调度Intra-day Scheduling则在前一天计划的基础上用更新的超短期预测数据做滚动修正把偏差拉回来。这种“先粗后细、逐级逼近”的思路本质上是一种递进式的决策机制——用长时间尺度的计划保证经济性用短时间尺度的调整保证可靠性。学术界管这个叫“多时间尺度协调调度”说实话这个概念我最早是在风电并网的相关文献里接触到的后来逐步扩展到微电网、综合能源系统、虚拟电厂这些方向。因为风电功率预测在不同时间尺度上的精度差异非常大——提前24小时预测的误差可能达到20%以上而提前4小时的预测误差往往能控制在10%以内甚至更低。这意味着日内调度可以利用精度更高的预测数据来修正日前计划从而在不过度牺牲经济性的前提下提升系统运行的可靠性。这个思想调度领域的同行应该能get到本质上就是**“用信息换成本用滚动换精度”**。对做研究的同学来说这类题目是典型的高效产出方向理论模型清晰、求解方法成熟、Matlab代码实现路径明确而且复现SCI论文的思路本身就是一个快速学习前沿方法论的路径。对刚入门虚拟电厂研究的人来说把多时间尺度调度框架吃透相当于打通了虚拟电厂优化调度方向最核心的一条主脉。接下来我带着大家把数学模型、代码架构和实操调试逐层拆开讲清楚。2. 模型框架与数学建模的核心逻辑2.1 虚拟电厂内部资源建模的细节考量构建虚拟电厂的多时间尺度调度模型第一步是把内部各类资源用数学语言描述清楚。常见的虚拟电厂聚合资源包括风力发电WT、光伏发电PV、储能系统ESS、柴油发电机DE以及可控负荷CL。每种资源的时间常数、调节特性和成本特性都不一样建模的时候各有各的门道这里需要逐一拆解。风光出力模型。说实话在处理风电和光伏的出力模型时学术界通常采用“预测出力曲线预测误差”的方式——即认为风光的实际可用出力等于预测值加上一个随机误差项。但在调度模型内部风光通常是作为“负的负荷”处理的它们的发电优先消纳不进优化变量只作为已知参数给定。这一点很多初学者会搞混非得给风光加一堆约束条件结果把问题复杂化了。简单处理就好在某时段t风电的可用功率是 P_{wt}^{fore}(t)光伏是 P_{pv}^{fore}(t)这些都是调度模型的外部输入来自预测系统。储能系统建模。储能是虚拟电厂里最重要的灵活性资源在建模时核心就是充放电功率约束和SOCState of Charge荷电状态递推关系。充放电功率约束要同时考虑功率上限和充放电状态互斥——不能同一时刻既充电又放电这在数学上是引入一个二进制变量来约束的。SOC递推关系则是SOC(t1) SOC(t) (η_ch * P_ch(t) - P_dis(t) / η_dis) * Δt / Cap其中η_ch和η_dis分别是充放电效率Cap是储能容量。这个递推式是储能建模的核心求解完整个调度周期后还需要加上调度周期始末SOC相等的约束否则模型会把储能能量一次性耗尽来降低总成本这在长期运行中是不可持续的。柴油发电机模型。柴油机在虚拟电厂里扮演的是“保底电源”的角色它的运行成本包括燃料成本和启停成本。燃料成本一般用二次函数近似C_fuel a * P^2 b * P c。但因为引入了二次项这个模型就成了混合整数二次规划MIQP问题。如果你用的求解器处理不了非线性目标函数可以做分段线性化处理。实际操作中很多代码用的是线性成本系数近似——反正研究重点是调度框架的协调逻辑成本函数的精度不用过分纠结。可控负荷模型。这里常用的是“可转移负荷”模型——即负荷在时间轴上可以被整体平移但总用电量保持不变。这种模型的本质约束是负荷的运行状态是0-1整数变量并且有规定的持续运行时间。算起来比单纯的功率约束要复杂些但是更接近现实。比如某工业负荷功率为P_load需要连续运行L个时段那约束条件可以写成在第t时段如果开始运行则后续L个时段状态都为1。这种建模需要引入辅助二进制变量增加求解复杂度但是实话说这部分恰恰是文章创新点最容易出彩的地方。从整体上看虚拟电厂内部资源的数学建模就是上述四类资源的整合。把这些模型组装起来然后套上系统功率平衡约束、联络线功率约束、旋转备用约束等系统级约束条件就构成了完整的调度优化模型。2.2 日前与日内调度的时间尺度衔接机制这两天我特意把日前调度和日内调度的区别往细了捋了一遍觉得可以从三个维度来对比时间分辨率、数据基础和优化目标。日前调度的典型时间分辨率是1小时决策变量数量相对少调度目标是明天的总运行成本最小化同时确定各机组的启停状态和基点功率。它的“坐标系”是粗粒度的不需要精确到分钟级别但能看到一整天的完整轮廓。因为基于24小时前的预测数据其精度有限所以日前计划本质上是个“基准轨迹”——先把大框架定好具体细节留给日内解决。日内调度的典型时间分辨率是15分钟甚至可到5分钟。它每15分钟滚动一次每次优化未来4小时的资源分配用最新的超短期预测数据做修正。日内调度要解决的问题是预测变了、负荷抖了、风光波动了怎么用储能、柴油机和可控负荷的调节能力把这些偏差消掉。两个时间尺度的衔接是这类代码的灵魂所在。常见的做法是分层递进第一层日前调度求解出24小时各机组出力计划 P_ref(t) 和储能SOC参考轨迹 SOC_ref(t)第二层日内调度在运行过程中把日前计划值作为“参考基线”在目标函数中加入对基线偏移的惩罚项min Σ( C_operation λ * ||P(t) - P_ref(t)|| )这个惩罚项的含义是日内调度不会完全推倒重来而是在尽量贴近日前计划的前提下做局部调整。λ参数的大小反映了两者之间的耦合紧密度——λ越大日内调度越不敢偏离日前计划系统运行越保守λ越小日内调度越激进会充分利用新预测信息去修正计划。这一个细节决定了你的代码是“两个独立的优化问题”还是“真正意义上的多时间尺度协调调度”。好多论文里写着多时间尺度协调实际上就是把日前和日内分开跑完全没有任何信息交互这其实是打擦边球。真正的协调调度必须要有这个偏差惩罚或者等效的物理约束。从数学角度看这个结构属于**“模型预测控制”MPCModel Predictive Control框架**在电力系统优化调度中的应用。MPC的思想就是滚动优化、反馈校正、局部最优逼近全局最优。所以如果你把日内调度部分用MPC的术语去描述审稿人会觉得你的工作更加规范、有深度。3. Matlab代码实现从数学到可运行的程序架构3.1 求解工具选型与参数配置模型建立起来之后关键在于用Matlab把它写成可运行的代码。求解工具的选择直接影响代码的简洁性和求解效率这块需要格外注意。求解混合整数线性规划问题MILP的主流方案有两种一是用Matlab自带的intlinprog求解器二是用YALMIP工具箱作为建模语言再外接Gurobi或CPLEX求解器。我个人的建议是除非你已经很熟练否则优先选择YALMIPGurobi这套组合理由有三个。理由之一是YALMIP的语法非常接近数学表达式的写法写约束条件基本可以直接照搬公式出错率极低。举个例子功率平衡约束 ΣP_gen ΣP_load在YALMIP里就写成sum(P_gen) sum(P_load)跟写数学公式差不多逻辑清晰查问题也方便。而用intlinprog的话你得把所有约束拼成矩阵形式 Ax ≤ b转成矩阵的那一步非常容易出错尤其当约束条件有几十条上百条的时候光调试矩阵维度就能折腾一下午。理由之二是Gurobi的求解效率比Matlab内置求解器高出一个量级求解整数变量较多的大规模MILP问题优势更加明显。对于15分钟分辨率的日内调度问题一个优化周期内会涉及几十个二进制变量加几百个连续变量Gurobi能在秒级内找到全局最优解而Matlab内置求解器可能要跑上几分钟甚至更久。理由之三是严格来说YALMIPGurobi这套组合在学术界基本是标配流程照着主流论文的配置走后续拿来跑对比实验也好交代。安装Gurobi的时候有几个容易踩的坑这里重点提醒一下注意Gurobi和Matlab的版本兼容性必须提前确认。比较稳妥的做法是先装Gurobi再在Matlab里运行gurobi_setup命令然后执行savepath把路径保存下来。每次重新开机或者换了电脑之后记得重新运行一遍gurobi_setup否则会出现“找不到Gurobi”或者“license error”的报错。3.2 代码整体架构与数据流设计接下来聊聊代码本身的组织方式。我在复现这类论文时习惯于把代码拆成四个模块文件主脚本main.m负责总控流程——定义案例参数、加载预测数据、调用日前调度函数、调用日内滚动调度函数、汇总结果并绘图。数据处理模块load_data.m负责把风/光/负荷的预测数据从Excel或MAT文件读入按日前和日内的时间分辨率重新组织排列。日前调度模块day_ahead_scheduling.m输入为24小时预测数据输出为日前计划——各机组每小时的出力计划、储能SOC参考轨迹、机组启停状态。日内调度模块intraday_scheduling.m输入为滚动窗口内的最新预测数据、日前计划参考值、当前储能SOC输出为当前时段各资源调整后的出力指令。这四个部分串起来数据流的走向是这样的load_data → day_ahead_scheduling → 得到日前计划 load_data(滚动更新) 日前计划 → intraday_scheduling → 得到实时调整指令这种模块化的结构看起来简单但好处非常多。最直观的好处是你写日内调度的时候不需要关心日前调度内部是怎么算出来的只需要定义好输入输出接口。这种低耦合的设计后期改参数、换场景、加新约束条件的时候会非常省事。3.3 日前调度的核心代码实现下面重点拆解日前调度模块的实现细节。假设我们的虚拟电厂包含一个风电场、一个光伏电站、一套储能系统、一台柴油发电机、一批可控负荷。先定义决策变量。用YALMIP定义变量的方式如下% 时间分辨率1小时调度周期24小时 N 24; dt 1; % 单位小时 % 决策变量定义 P_de sdpvar(1, N); % 柴油机出力 P_ch sdpvar(1, N); % 储能充电功率 P_dis sdpvar(1, N); % 储能放电功率 SOC sdpvar(1, N1); % 储能荷电状态 u_de binvar(1, N); % 柴油机启停状态 0/1 u_ch binvar(1, N); % 储能充电状态 0/1 u_dis binvar(1, N); % 储能放电状态 0/1 P_cl sdpvar(1, N); % 可控负荷功率 u_cl binvar(1, N); % 可控负荷运行状态 0/1这里变量类型的选择有门道。柴油机启停、储能充放电状态、可控负荷运行状态都是典型的离散决策变量必须用binvar定义而功率、SOC这些连续调节量用sdpvar就行。接下来是约束条件。功率平衡约束是核心constraints []; constraints [constraints, P_wt P_pv P_de P_dis - P_ch P_load_base P_cl];其中P_wt和P_pv是预测值P_load_base是基础负荷。这个等式的物理含义是在任意时刻系统发电和用电必须严格相等。储能约束要同时考虑充放电功率上下限、SOC递推关系和SOC边界% 充电功率约束 constraints [constraints, 0 P_ch u_ch * P_ch_max]; % 放电功率约束 constraints [constraints, 0 P_dis u_dis * P_dis_max]; % 充放电状态互斥 constraints [constraints, u_ch u_dis 1]; % SOC递推 constraints [constraints, SOC(2:N1) SOC(1:N) (eta_ch * P_ch - P_dis / eta_dis) * dt / Cap]; % SOC边界 constraints [constraints, SOC_min SOC SOC_max]; % 调度周期始末SOC一致 constraints [constraints, SOC(1) SOC_initial, SOC(N1) SOC_initial];注意u_ch u_dis 1这个约束是必须的——它防止储能同时处于充电和放电状态。如果不加这个约束MILP求解器有可能钻空子利用同时充放电来“刷”SOC这在物理上是不可能的。柴油机的约束包括出力上下限、最小启停时间和启停成本% 出力上下限 constraints [constraints, u_de * P_de_min P_de u_de * P_de_max]; % 爬坡约束 constraints [constraints, -R_down P_de(2:N) - P_de(1:N-1) R_up];最小启停时间约束写起来稍微麻烦一些需要用循环生成。基本思路是如果机组在t时刻启动u_de(t)-u_de(t-1)1那么t到t最小运行时间内的所有状态都必须是1。这个约束可以用下面的循环实现min_up 3; % 最小连续运行时间 for t 2:N-min_up1 constraints [constraints, u_de(t) - u_de(t-1) u_de(tmin_up-1)]; end目标函数方面日前调度的目标是把全天总运行成本降到最低——包括柴油机燃料成本、启停成本、储能退化成本以及与外部电网交易的成本% 柴油机燃料成本线性近似 C_fuel sum(a * P_de b * u_de); % 柴油机启停成本 C_start sum(c_start * max(0, u_de(2:N) - u_de(1:N-1))); % 储能退化成本 C_batt sum(c_batt * (P_ch P_dis) * dt); % 与主网交易成本购电为正、售电为负 C_grid sum(price_buy .* P_grid_buy - price_sell .* P_grid_sell); Objective C_fuel C_start C_batt C_grid;有些文献还会在日前目标函数中加入弃风弃光惩罚项这取决于你的模型假设。如果要鼓励消纳就在目标函数里加一个弃风弃光成本项。求解调用ops sdpsettings(solver, gurobi, verbose, 1); optimize(constraints, Objective, ops);3.4 日内滚动调度的实现细节日内调度的时间范围比日前短得多而且每15分钟滚动一次。一个常见的日内调度窗口长度是4小时16个调度时段每15分钟执行一次优化但实际上只执行第一个时段的指令下一个周期再重新计算——这就是滚动时域控制的核心机制。日内调度相对日前的关键区别在于目标函数里多了一个对日前计划基线偏移的惩罚项。% 日内窗口长度 H 16; % 4小时15分钟分辨率 dt_intra 0.25; % 15分钟 % 决策变量定义类似日前但只定义当前滚动窗口 P_de_intra sdpvar(1, H); ... % 从日前计划中提取当前时刻对应的参考值 % 注意时间对齐日前是1小时分辨率日内是15分钟分辨率 % 所以日前第t小时的参考值对应日内第4t-3到4t共4个时段 P_de_ref day_ahead_result.P_de(ceil((t_now (1:H)*dt_intra - 1e-6))); ... P_de_intra_ref P_de_ref; % 目标函数 运行成本 日前计划偏移惩罚 Objective_intra C_operation lambda * sum((P_de_intra - P_de_intra_ref).^2);这里有一个重要的实现细节时间对齐。日前调度的结果是1小时间隔而日内调度是15分钟间隔两者之间不是简单的矩阵索引对应而要做插值或者“零阶保持”处理——即日内15分钟窗口内的四个时段对应的日前值都是同一小时的值。这个环节写代码时容易搞错而且错了以后不容易发现因为优化照样能跑结果看着也合理实际上会出细微的偏差。我建议在做时间对齐时单独写一个函数并加注释明确说明索引映射关系function ref interpolate_day_ahead(day_ahead_data, t_start) % day_ahead_data: 24x1向量日前计划的整点参考值 % t_start: 当前时刻小时例如10.25表示10:15 % 返回未来H个时段对应的日前参考值 % % 时间对齐逻辑日前是整点数据日内是15分钟数据 % 每个日内时段找到它落在哪两个日前整点之间 % 这里用零阶保持——即使用该时段起始时刻所在的整点的日前值 ... end关于λ日前计划偏移惩罚系数的设定这个参数会直接影响日内调度的调节激进程度。我的经验是先跑几组不同λ的对比实验观察储能SOC轨迹和联络线功率波动再确定出相对合理的值。λ太小日内调度会疯狂利用新预测信息调整出力导致实际出力曲线和日前计划差别太大虚拟电厂在市场上竞标时可能面临偏差考核λ太大日内调度基本等于重复日前计划失去了滚动修正的意义。一种可操作的方法是λ取日前计划总成本的某个比例比如总成本的0.1%然后在仿真里微调。3.5 结果可视化脚本的设计思路调度类论文最直观的展示方式就是结果图。好的结果图能立刻让审稿人看到你算法的效果所以可视化脚本值得认真写。我一般输出三张核心图第一张是调度结果堆叠图——横轴是时间纵轴是功率用stack绘图把风电、光伏、柴油机、储能、购电各个来源的功率堆叠起来直观展示系统的功率构成和平衡关系。第二张是日前与日内出力对比图——同一台机组在日前计划和日内实际调度下的出力曲线放在同一张图里对比。这张图最能直观展示日内调度的修正作用曲线应该是日内实际值围绕日前计划值波动偏离不大但能捕捉到新预测信息的变化。第三张是储能SOC轨迹图——横轴时间纵轴SOC百分比可以看到储能在低电价时段充电、高电价时段放电的“低充高放”行为模式这也是检验模型是否合理的一个重要观测维度。绘图用的代码我习惯用subplot拼图统一设置字体大小和线条粗细保证图形美观。关于绘图风格如果投的是IEEE期刊图的要求是黑白色也能区分——所以曲线不要只靠颜色区分要配合线型实线、虚线、点划线和标记圆圈、方块、三角。4. 实操过程中那些让人抓狂的报错与排查思路4.1 求解器报错与预测数据对齐问题的处理代码写完后调试阶段才是真正考验耐心的时候。下面这些坑是我做复现时踩过的也是身边同学问我最多的整理成速查表希望大家少走弯路。报错“License Error”或“Gurobi not found”。这大概率是Gurobi没有正确配置到Matlab路径中。解决方案是重新运行gurobi_setup并执行savepath或者检查系统环境变量中是否添加了Gurobi的bin目录。如果用了校园网或者公司内网的license还需要确认网络连接正常。报错“Inconsistent constraints”或者“Infeasible problem”。这个问题头疼指数很高。一般是某个约束条件同时给得太紧导致没有可行解。一个比较常见的情况是储能SOC初值设置得很低但在功率平衡约束下又要强制储能大量放电于是SOC守恒约束撑不住整个问题直接不可行。排查方法先把模型简化——消掉新能源波动、去掉可控负荷看看问题能不能求解如果简化后能跑通再逐步加回约束条件一步步排查出是哪个约束导致冲突。这个“二分法定位”的思路很笨但确实是最有效的。预测数据时间维度对不上。这个是多时间尺度模型特有的坑。日前的数据是24×1的向量日内数据可能是96×1的向量15分钟分辨率如果在日内调度的代码里直接访问day_ahead_result.P_de(t)而循环变量t的范围是按15分钟设置的96个时段就会导致两个问题——一是数组越界报错二是算出来的结果压根不对。我的解决方法是在代码里统一使用一个时间戳向量例如t_hour表示小时t_step表示步长序号并用上述的插值函数做数据转换保证所有模块在统一的时间坐标系下运行。4.2 预设参数不合理导致的无解问题这个坑我在复现各类调度代码时遇到过太多次了——参数之间自相矛盾。比如把P_ch_max设成100kW但同时把SOC_min设得过高储能根本充不进去多少电这时候如果风光预测值特别高系统必须通过储能充电来消纳但充电功率受限功率平衡方程就解不出来了——无解。处理这类问题有一招很好用把所有边界参数单独跑一个“可行性检查脚本”。这个脚本不设目标函数只是把约束条件丢给求解器看看问题是否可行。如果不可行就用上面说的二分法逐步定位冲突的约束。通过这种方式我在正式跑优化之前就能排除掉大部分参数冲突问题。调试贴士不要追求一次跑通。我个人的习惯是——先在小的调度周期比如3个时段上测试代码逻辑是不是正确的然后再扩展到24小时或96小时。因为3个时段的MILP问题变量很少求解器瞬间解完即使有问题也很容易看出来。等小规模问题验证通过了再跑完整的调度周期。4.3 松弛变量与硬约束的优先级排序在建立了所有硬约束之后有时候依然会发现实际场景中的数据根本无法同时满足所有约束。比如极端天气导致风电预测偏差特别大日内调度时怎么调都平衡不了功率。这时候怎么办引入松弛变量是务实的选择。在功率平衡约束中加入松弛项ΣP_gen slack_pos - slack_neg ΣP_load其中slack_pos和slack_neg是非负变量在目标函数中施以很大的惩罚系数。这样求解器优先满足功率平衡的硬约束实在不行才启用松弛变量“兜底”同时也保留了问题的可行性。这种做法在工程实践里很常见而且代码实现只比原来多3到4行slack_pos sdpvar(1, H); slack_neg sdpvar(1, H); constraints [constraints, P_wt_intra P_de_intra P_dis_intra - P_ch_intra P_load_intra slack_pos - slack_neg]; constraints [constraints, slack_pos 0, slack_neg 0]; Objective_intra Objective_intra 10000 * sum(slack_pos slack_neg);说实话在实际复现代码时引入松弛变量并不会影响论文的主要结论但能显著提升模型的鲁棒性。很多高水平的论文在模型描述里也会提到“罚函数法”或者“软约束”来保证模型可行性这本身就是一种常见且正当的处理方式。5. 从复现到创新的三个方向与后期扩展思路复现一篇SCI论文的代码最大的收获不是那一堆跑通的代码而是你在这个过程中彻底理解了模型的逻辑脉络和数据流的走向。做完了基本复现之后如果想更进一步把这个工作延伸到自己的研究方向中去下面这几个方向我觉得都值得花时间去尝试。方向一是引入不确定性建模。目前的模型假设风光预测误差完全被日内调度兜住但日内调度的预测也有误差。更进一步的做法是引入随机优化或者鲁棒优化——比如用场景法生成一大批风光出力场景在每个场景下都做可行性和成本的验证最终输出一个对所有场景都稳健的调度策略。这种方法在顶级期刊上越来越多见本质上就是“考虑预测误差分布”的多时间尺度调度。方向二是加入碳交易机制。在双碳目标下虚拟电厂的调度不仅要考虑经济性还要考虑碳排放成本。碳交易机制引入后目标函数会多出一项碳排放成本或碳配额收益而柴油发电机的碳排放量跟它的出力大小直接挂钩因此优化问题会自然地倾向于多用清洁能源、少用柴油机。这个扩展实现起来很容易——给柴油机加一个碳排放系数目标函数加一项碳价乘以碳排放量——但故事的完整性和政策贴合度一下子提高了。方向三是多虚拟电厂协同调度。单个虚拟电厂的调度优化解决的是“自己怎么运行好”的问题但多虚拟电厂协同调度解决的是“多个主体之间如何互惠互利”的问题。这种分布式优化问题通常可以用交替方向乘子法ADMM或者一致性算法来求解每个虚拟电厂只跟邻居交换耦合变量隐私性和扩展性都好。在这个方向里多时间尺度调度的框架仍然适用但每个时间尺度下都要多一层“多主体协调”的逻辑。在实际复现过程中我的体会是不要着急把代码写到完美而是先跑通一个简单的版本看到结果图再一步步完善模型和代码结构。把“跑通→理解→改造→扩展”这个过程完整走一遍你对虚拟电厂多时间尺度调度的理解会比读十篇论文都深刻。如果你也在复现类似的工作遇到了什么别的坑欢迎交流讨论。
返回列表