多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

虚拟电厂多时间尺度调度与储能衰减建模的Matlab复现指南

虚拟电厂多时间尺度调度与储能衰减建模的Matlab复现指南 1. 从调度失灵说起高比例可再生能源并网到底难在哪里先抛一个我在项目里反复遇到的现象当风电、光伏装机占比超过一定水平之后电网调度员最头疼的其实不是电量不够而是出力曲线变得太野。传统的机组组合和经典经济调度还能靠火电、水电去压舱但可再生能源的间歇性、波动性叠加负荷本身的随机性会让净负荷负荷减去风光出力出现非常陡峭的爬坡段。我见过某地夏季午间光伏大发时段净负荷骤降傍晚又快速拉升这种鸭型曲线一旦爬坡率接近甚至超过传统机组的爬坡能力系统频率和电压的稳定就非常脆弱。这个问题的本质是灵活性不足而不是装机不足。灵活性指系统在短时间内调整出力的能力包括向上爬坡和向下爬坡两个方向。高比例可再生能源场景下净负荷的波动幅度和变化速率都被放大原先按小时级甚至半小时级做计划的方式已经不够用了。为了提高灵活性行业内普遍想到的办法就是配储能但储能是昂贵的资产如果调度策略不合理可能一个调度周期内就把电池循环寿命消耗得七七八八账面上看着灵活实际综合成本高得吓人。这就引出了虚拟电厂的概念。虚拟电厂不是物理上建一座电厂而是通过调度系统把分散的风机、光伏、储能、可控负荷聚合起来作为一个整体接受调度。它做的事情说白了就是在灵活性和储能成本之间找一个最佳平衡点。本篇博文要复现的核心就是这样一套面向高比例可再生能源的虚拟电厂多时间尺度调度模型并在调度模型里显式加入储能衰减建模——这也是很多同类论文里反复强调、但在实际复现时最容易忽略的部分。我会先讲建模逻辑再给出Matlab实现的完整思路最后聊一聊我在跑代码过程中实测踩过的坑。2. 为什么单时间尺度调度扛不住净负荷波动与爬坡缺口的量化分析2.1 先量化灵活性缺口再谈调度我习惯在做调度之前先把净负荷的特征量化出来。设 (P_L(t)) 为负荷功率(P_R(t)P_W(t)P_P(t)) 为风光预测出力则净负荷 (P_{net}(t)P_L(t)-P_R(t))。灵活性缺口可以从两个维度看一个是幅值波动定义为 (\Delta P_{net}(t)P_{net}(t)-P_{net}(t-1))另一个是爬坡速率用单位时间内的变化量来衡量。当这个变化量超过常规机组爬坡总和时就需要额外灵活性资源出力。以我在算例里用的数据为例把风光渗透率从30%逐步调到60%净负荷最大爬坡率从约12%/15min上升到45%/15min而传统火电机组的爬坡能力通常只有3%-5%/15min。这个差距就是为什么必须在调度中引入储能和需求响应也是为什么调度时间尺度必须细化。2.2 单时间尺度的三个死穴很多早期研究只用日前调度即提前24小时做一次计划全天不更新。这样做的第一个死穴是预测误差无法应对风电预测误差在4-6小时尺度上可能超过15%如果全部由备用承担备用容量需求会非常大。第二个死穴是日内突发事件没有兜底手段比如光伏云层快速移动导致出力骤降日前计划完全无法响应。第三个死穴更隐蔽——如果只在日前层面调度储能SOC会被锁死在一个日前的预期轨迹上完全没有余量消化实际运行中的偏差。我们常说的多时间尺度调度本质就是把调度决策拆成三层日前做机组组合和储能日级排程日内做滚动修正实时做秒级到分钟级的偏差平抑。每一层各管一段才能既保证计划的经济性又保证执行的可行性。这也正是虚拟电厂系统相较于单个物理电厂的优势——它可以跨资源类型、跨时间尺度调配灵活性。用一个通俗类比日前调度像每周做一次购物清单日内调度像到了超市根据当天促销随时调整购物车实时调度则像付款前把买错的东西拿出来。3. 虚拟电厂多时间尺度调度架构日前、日内、实时三层如何分工3.1 时间尺度分层与决策变量设计我复现的模型采用经典的三层结构。第一层是日前调度时间分辨率为1小时调度周期24小时决策变量包括各机组启停状态、各时段出力、储能每小时充放电功率以及SOC轨迹。第二层是日内滚动调度时间分辨率压缩到15分钟滚动周期4小时每15分钟更新一次未来4小时的计划这一层只调整出力功率不再改变机组启停状态因为日内再改启停往往来不及也代价高。第三层是实时控制层分辨率1分钟到5分钟由储能和需求响应资源快速响应平抑分钟级波动。这里有一个关键设计原则上层决策为下层划定可行域下层决策把上层的预测误差消化掉。具体来说日前层定下来的机组组合和储能SOC基线日内层不能轻易推翻日内层更新的计划实时层只能在偏差信号基础上做小幅度修正。不然各层职责混乱系统反而失稳。3.2 目标函数与约束的分层建模日前层的目标函数是一个典型的经济调度目标我写成[ \min \sum_{t1}^{T} \left[ \sum_{i} (a_i P_{i,t}^2 b_i P_{i,t} c_i u_{i,t}) C_{ess} C_{DR} C_{curtail} \right] ]其中 (a_i, b_i, c_i) 是火电或等效机组成本系数(u_{i,t}) 是启停状态(C_{ess}) 是储能运行成本(C_{DR}) 是需求响应成本(C_{curtail}) 是弃风弃光惩罚。约束包括功率平衡约束、机组出力上下限约束、爬坡约束、储能SOC递推约束、充放电功率约束、联络线功率上限约束等。日内层采用模型预测控制MPC的思路在时刻 (t_0) 以最新预测数据重新求解未来 (H) 步的调度问题只执行第一个控制步到下一个时刻滚动推进。目标函数除了运行成本外还需要加入对日前计划的偏离惩罚防止日内计划漂移太远。实时层我用的是一种简化MPC不做优化求解只做基于偏差的比例修正把储能功率设定为偏差信号的比例响应。这种分层方式效率高工程上更稳。3.3 衔接机制松弛变量与惩罚系数三层模型能不能跑通很多时候不取决于模型本身而取决于层与层之间的衔接。我在复现时采用的方法是在各层目标函数里加松弛变量和惩罚项。比如日内层在功率平衡约束里加入松弛变量 (\delta_t)并让目标函数包含 (M \cdot |\delta_t|_2)这样当日内预测偏差超出执行能力时模型不会硬性地无解而是以极小代价放开平衡约束但一旦放开就会在目标函数上体现出来。这个 (M) 的选择直接影响调度质量——设得太大会导致约束被死扛设得太小又会让模型随便弃电。我实测下来取运行成本的10-20倍是个经验区间。另外一个衔接细节是SOC轨迹的传递。日前层优化出来的SOC曲线日内层应作为参考轨迹而不是硬约束。硬约束会导致日内层没有调度空间软约束则更合理。我把SOC偏差也写成二次惩罚项实测下来日内滚动可以比日前计划少5%-8%的储能越限行为。4. 储能衰减建模让看不见的成本进入目标函数4.1 为什么储能成本不只是一度电多少钱很多初做调度的同学容易把储能成本简化成一个常数比如每次充放电成本固定0.2元/kWh。但这忽略了一个关键事实储能的损耗是累积性的、非线性的跟充放电深度、放电倍率、工作温度都有关系。锂电池循环寿命与放电深度DoD之间近似满足幂函数关系[ N_{life} N_0 \cdot DOD^{-k_p} ]其中 (N_0) 是100%放电深度下的循环寿命(k_p) 是衰减指数通常在1.1-1.5之间。意思是如果你每次只放10%的容量循环次数可以很多但如果你每次都满充满放寿命会快速消耗。这个非线性是调度策略必须显式建模的原因。4.2 我的衰减建模实现循环计数寿命折算在项目里我用了两个层次的衰减建模。第一层是循环寿命折算。每次调度周期结束后对储能功率曲线做雨流计数法或简化的事件计数法提取每个循环的深度和次数代入上面的循环寿命公式算出消耗的寿命比例再折算成成本叠加到目标函数里。这个方法的优点是相对精确缺点是雨流计数法在滚动优化中非线性太强很难直接嵌入在线求解。所以我用了第二层——功率相关退化模型这是我在复现实践中真正嵌进优化问题里的那一个。思路是把每一次充放电的寿命损耗近似为放电深度 (DOD(t)) 的线性或二次函数于是瞬时退化成本可以写成[ C_{deg}(t) \lambda_{deg} \cdot \left( \alpha \cdot DOD(t)^2 \beta \cdot |P_{ess}(t)| \right) ]这里的 (\lambda_{deg}) 将寿命损耗折算为货币成本。因为调度模型本来就是离散的SOC的变化量可以直接近似为DOD的变化量所以这个退化成本函数可以顺利嵌入优化问题不会破坏模型的凸性。实测下来加入退化成本项之后模型会主动把SOC轨迹控制在20%-80%区间内而不是频繁深度充放。4.3 衰减建模对调度结果的影响有多大我做过一组对比实验同样的风光负荷数据一组目标函数不含衰减成本一组含衰减成本。对比结果很直观——不含衰减成本的方案储能一天内完成等效完整循环2.3次SOC频繁到0和1边界调度总成本看着低但把电池寿命损耗折算成成本摊进去之后综合成本反而比含衰减建模方案高出18%。含衰减建模的方案SOC被压在0.25-0.85区间循环深度明显变浅虽然即时运行成本略高但全生命周期成本显著降低。用一句话总结不加衰减建模调度模型会过度信任储能加了之后模型才真正理解储能的真实成本边界。5. Matlab复现从问题搭建到求解器调用的完整工程化路径5.1 工程架构与数据准备我建议把整个工程拆成四个脚本main_VPP_Schedule.m负责总控与数据载入build_dayahead.m建立日前优化模型build_intraday.m建立日内滚动优化模型build_realtime.m建立实时控制逻辑。这样每条逻辑都独立出问题时好排查。数据准备主要是三类风电出力预测序列、光伏出力预测序列和负荷预测序列我使用的是和论文同样生成方式的模拟数据也可以替换成实际测量数据。% main_VPP_Schedule.m clear; clc; close all; rng(42); % 固定随机种子保证可复现 % 时间参数 T_day 24; % 日前调度时段数小时 T_intra 96; % 日内时段数15分钟*24h H_intra 16; % 日内滚动窗口4小时 % 资源参数 P_wind_max 200; % 风电额定容量 MW P_pv_max 150; % 光伏额定容量 MW P_ess_max 100; % 储能功率容量 MW E_ess_max 400; % 储能能量容量 MWh SOC_min 0.2; SOC_max 0.9; eta_ch 0.95; eta_dis 0.95; % 预测数据输入归一化后的序列 load(forecast_data.mat, P_wind_fc, P_pv_fc, P_load_fc);5.2 日前调度Yalmip建模与求解日前层我用Yalmip建模求解器选用Cplex或Gurobi。机组组合部分包含整数变量是个典型的混合整数二次规划MIQP。储能部分我把它建模成连续变量加二进制状态变量比如同一时段内充放电不能同时进行的约束用一个大M法实现% 储能充放电互斥约束示例 u_ch binvar(T_day,1); % 二进制充电状态 u_dis binvar(T_day,1); % 二进制放电状态 Constraints [Constraints, u_ch u_dis 1]; Constraints [Constraints, 0 P_ch P_ess_max * u_ch]; Constraints [Constraints, 0 P_dis P_ess_max * u_dis]; % SOC递推及上下限约束 SOC sdpvar(T_day,1); Constraints [Constraints, SOC(1) SOC_init (eta_ch*P_ch(1) - P_dis(1)/eta_dis)*dt/E_ess_max]; for t 2:T_day Constraints [Constraints, SOC(t) SOC(t-1) (eta_ch*P_ch(t) - P_dis(t)/eta_dis)*dt/E_ess_max]; end Constraints [Constraints, SOC_min SOC SOC_max, SOC(end) SOC_terminal];功率平衡约束是全场最核心的等式约束它要求所有电源出力之和等于负荷如果加上灵活性改造之后的机组还要加上联络线功率% 功率平衡约束火电风电光伏储能放电-储能充电联络线 负荷 Constraints [Constraints, P_g P_wind P_pv P_dis - P_ch P_tie P_load_fc];这里额外注意给风电和光伏预留弃电变量。实际中如果风光的预测出力高于系统消纳能力模型必须允许弃风弃光所以在等式约束右侧加入弃电变量 (P_{curtail})并在目标函数里设置惩罚因子。实测下来这个惩罚因子设置在200-500元/MWh之间比较合理太高了会导致模型不惜让储能充满到极限去消纳每一度风光太低则会让模型随便弃电。5.3 日内滚动优化MPC式滚动窗口实现日内层的整体结构和日前层类似但多了两个改动一是时间分辨率从1小时细化到15分钟二是目标函数里加入对日前计划的偏离惩罚。% build_intraday.m 内层滚动优化核心循环 for k 0:(T_intra/H_intra - 1) t0 k * H_intra 1; t1 t0 H_intra - 1; % 抽取当前窗口内的预测数据 P_w_win P_wind_fc_intra(t0:t1); P_p_win P_pv_fc_intra(t0:t1); P_l_win P_load_fc_intra(t0:t1); P_g_ref P_g_ref_dayahead(t0:t1); % 日前计划的参考值 % 重新求解... % 只执行第一个控制步 P_g_execute(t0) value(P_g(1)); P_ess_execute(t0) value(P_dis(1)) - value(P_ch(1)); SOC_real(t01) SOC_real(t0) (P_ess_execute(t0)/eta * (P_ess_execute(t0)0) ... P_ess_execute(t0)*eta * (P_ess_execute(t0)0)) * dt / E_ess_max; end贴近真实运行的做法是每滚到一个新窗口就用最新预测替换原预测重新求解只执行第一个时刻的决策。这种求解-执行-更新的闭环和模型预测控制的标准范式完全一致。我在实现中还加入了一个技巧对储能SOC的终端约束做了松弛处理即要求滚动窗口结束时的SOC落在一个区间而不是一个点避免为了满足末端SOC硬约束导致储能功率在窗口末段出现极端动作。5.4 实时层简化偏差修正逻辑实时层不需要重新做优化求解否则计算太慢。我用的是带死区的比例控制器根据日内计划功率与实际功率的偏差调整储能出力% build_realtime.m 实时层逻辑 delta_P P_actual - P_schedule; if abs(delta_P) dead_band P_ess_real 0; else P_ess_real K_p * delta_P; end P_ess_real max(min(P_ess_real, P_ess_max), -P_ess_max);这里的死区参数很关键。设大了储能频繁不动作偏差全靠其他机组扛设小了储能过度频繁响应影响寿命。我实测的合理死区是额定功率的2%-3%Kp取0.5-0.8。实时层虽然简单但它是多时间尺度框架里兜底的一环没有它的仿真日内层和实时层之间的预测误差就无处安放。6. 算例设计与结果分析看看衰减建模是怎么改变调度决策的6.1 算例场景设置我用的系统规模是火电机组3台、风电场1个额定200MW、光伏电站1个额定150MW、储能电站1个100MW/400MWh、可调负荷2组。为了模拟高比例可再生能源场景把风光渗透率设为55%典型日选夏季晴天和冬季大风天两组。求解工具是Matlab R2023a Yalmip Gurobi。MIQP求解速度在同题中大概需要40-90秒日内滚动96个窗口约需10-15分钟。6.2 三组关键实验结果第一组实验比较单时间尺度仅日前和多时间尺度调度的运行效果。单时间尺度方案因为无法响应日内预测变化实际运行中出现7个时段的功率不平衡最大偏差达到45MW多时间尺度方案的最大偏差压缩到5MW以内。第二组实验比较含与不含衰减成本两个日前的储能SOC轨迹。不含衰减成本时SOC在0到1之间来回摆动等效满循环次数2.3次含衰减成本时SOC轨迹被约束在0.22-0.87区间等效满循环次数降为1.4次。第三组实验把三种方案的全生命周期成本做归一化对比如下方案实时功率不平衡偏差储能等效满循环次数正常运行成本(相对值)全生命周期总成本(相对值)仅日前调度45MW2.11.001.00多尺度调度(忽略衰减)5MW2.30.951.18多尺度调度(含衰减建模)5MW1.41.020.87这个表格是我最想强调的结果不考虑衰减时多尺度调度方案虽然真实运行成本低但电池寿命损耗大总成本反而比基准高18%。加入衰减建模后总成本比基准方案降低13%而且灵活调节能力一点没丢。这就是衰减建模的价值所在。6.3 结果可视化与代码输出我习惯在main_VPP_Schedule.m末尾统一输出三张图第一张是日前计划与日内执行结果的对比画功率曲线第二张是SOC轨迹对比含衰减和不含衰减方案画在同一个子图里第三张是各类电源的出力堆叠图。绘图的细节是把弃风弃光量单独画成一个负向条形这样可以看出系统中哪个时段最缺灵活性。Matlab的stairs函数画SOC效果比plot更好因为SOC在离散调度中本身就是阶梯变化的。7. 复现路上最容易被绊倒的七个坑第一坑是SOC递推式的正负号错误。我第一次实现时把充电效率放在放电项上导致储能既不充电也不放电系统直接崩溃。检查方法很简单把SOC递推单独拎出来手动给一组序列用disp打印每个时段的SOC值核对。效率项位置不对、充放电方向定义相反这类问题都会立刻暴露。第二坑是衰减成本项的系数量纲。目标函数里C_deg(t)是元/h而SOC是标幺值两者的数量级可能差出几个零。如果惩罚系数取得太大模型会把储能当成绝对不能碰的资产灵活性反而不够取得太小储能又被过度使用。我建议先把所有成本项做标幺化甚至可以先跑一次不含衰减的调度看储能在峰值时段的SOC变化幅度再来设定惩罚系数的量级。第三坑是日内与日前之间的SOC参考值不匹配。日内滚动第一时刻的SOC必须从日前计划对应时段取值否则整个SOC轨迹会被平移优化完全失真。我在这个坑上卡过两天最后查出来是索引问题——Matlab数组从1开始而日前计划的时段0对应的是初始SOC索引错一格后面全错。第四坑是二进制变量过多导致求解变慢。如果把储能充放电状态拆得太细加上机组启停变量Gurobi求解MIQP达到几分钟以上很正常。我的经验是尽量减小整数变量的规模储能充放电互斥可以在大多数时段用SOC变化方向间接约束必要时只在关键时段引入二进制量。把搜索空间降下来求解速度能提升一个数量级。第五坑是滚动优化的末端效应。日内滚动窗口只有16步如果SOC终端约束设得太紧储能会在一个窗口的末尾疯狂动作出现扎堆现象。我的处理方式是终端约束用软约束惩罚项替代同时让终端SOC参考值等于日前计划对应时段的值这样两个尺度之间的衔接会自然很多。第六坑是风光预测误差的策略性设置。如果预测误差分布设成均匀分布或完全没有误差日内层的存在意义就不大。要验证多时间尺度的优势建议设定跳变式误差场景比如在某几个时段加入±20%的阶跃偏差否则仿真结果会虚假地美好。第七坑是结果展示时掉进数值陷阱。有一次我发现储能功率曲线出现高频震荡排查半天发现不是逻辑问题而是Gurobi的最优性容差设置太紧导致MIQP求解结果在一个平台区域内反复跳动。把容差从1e-6放宽到1e-4问题立刻消失。这类数值层面的问题不解决再漂亮的模型也跑不出稳定结果。8. 后续可以怎么扩展我觉得这套框架的扩展空间很大。一个是把电储能换成更精细的多元储能组合比如锂电池加液流电池两者衰减特性差异很大衰减建模的优化空间更大。另一个是引入更准确的实时寿命模型把温度、放电倍率的影响加进来目前我只做到DoD维度温度维度的提升在锂电池场景下也很关键。还可以在实时层加入更高级的MPC算法考虑不确定性集而非简单的比例修正。在Matlab代码架构上可以考虑用OOP把调度资源封装成类后续加资源时不用改主程序逻辑。这些方向上我都是在现有代码上渐进式修改每加一个功能就跑一组对比实验确保新模块不会把原模型带偏。如果你也想复现类似工作建议先跑到本文这个版本再把口子打开。模型的乐趣就在这些对比和改进里。
返回列表