ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

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

虚拟电厂多时间尺度调度与储能衰减建模的Matlab复现实战 高比例可再生能源并网这个话题圈内讨论很多但真正动笔复现过虚拟电厂多时间尺度调度还把储能衰减建模塞进优化框架里的人不多。最近我硬啃完一个SCI复现项目——高比例可再生能源并网场景下虚拟电厂的调度问题核心就一句话如何用最省的储能成本换来系统灵活性。这篇文章把我的复现思路、建模细节、Matlab代码架构和踩过的坑全部写出来希望对正在做虚拟电厂、储能调度、或者想入坑多时间尺度优化的朋友有实际帮助。1. 可再生能源比例一上来为什么传统调度模型先失灵了1.1 净负荷曲线的鸭子脖子效应先讲一个大多数人都能直观感受到的现象当光伏渗透率超过一定比例中午时段的净负荷负荷减去可再生能源出力曲线会急剧下压形成一个深V形。傍晚光伏出力骤减而负荷快速攀升净负荷又急速上涨。这两段深刻的斜率就是电网调度最头疼的爬坡需求。传统以火电或水电为主的调度模型调节资源相对集中、可控性强在净负荷波动不大时能轻松应对。但可再生能源比例一高净负荷的不确定性成倍放大——预测误差、天气突变、云层遮挡都会让原本精确的计划变成猜谜游戏。传统调度模型的本质是按预测走计划提前一天确定机组组合第二天按计划发电。当净负荷预测误差动辄达到20%-30%甚至更高时这个逻辑就彻底失灵了。你要么准备大量旋转备用要么频繁启停机组要么眼睁睁看着弃风弃光。这些都会让运行成本急剧上升——这就是虚拟电厂调度问题诞生的根本原因。1.2 虚拟电厂到底在调什么虚拟电厂不完全是一个物理电厂它更像一个资源聚合平台把分布式光伏、分散式风电、储能电站、柔性负荷、电动汽车充电桩等散落在不同地点的资源打包成一个可调度的整体参与电网运行或电力市场。核心价值在于这些资源单看都很小谁也不会为了一个充电桩的功率波动去调整电网运行方式但聚合到一起它们就能形成兆瓦级甚至百兆瓦级的调节能力。虚拟电厂的调度难点在于聚合进来的资源类型五花八门响应速度天差地别储能可以毫秒级响应柔性负荷可能需要几分钟预热或提前通知而如果虚拟电厂还托管了分布式燃气机组那它的启停时间要按小时计算。单一时间尺度的调度模型根本没法同时兼顾这些资源——你不可能用15分钟的决策周期去安排一个需要预热2小时的冷负荷也不可能把一台储能当作火电机组那样按小时出清。这就是我在这篇复现中采用多时间尺度调度的直接动因。2. 多时间尺度调度的骨架日前-日内-实时的三级递进2.1 日前调度先把大方向定下来多时间尺度调度最常见的结构是日前-日内-实时三层每一层对应不同的决策周期和目的。日前调度是整个框架的第一层决策周期通常为24小时时间分辨率选1小时或15分钟。它的作用是确定第二天的基本运行计划储能何时充、何时放柔性负荷安排在哪几个时段响应与外部电网的购售电量是多少各分布式机组如果有的启停状态等。日前层的主要依据是光伏、风电和负荷的超短期预测曲线再加上典型日的电价曲线。由于预测时域较长日前计划追求的是全局经济性最优化——在24小时这个尺度上让总运行成本最低。这个阶段不会过分纠结于某个15分钟内的功率波动但一定要把储能的大体充放电节奏定下来。如果不做日前计划而是完全依赖日内实时决策储能会因为只看短期价格信号而出现短视行为——比如在某一个小时内频繁充放反而牺牲了全天更优的能量套利空间。2.2 日内滚动修正预测误差在这里被消化日内滚动优化的周期通常设置为4到8小时每15分钟或1小时滚动一次时间分辨率更细。它做的事情就是利用最新的预测信息把日前计划中因预测偏差导致的剩余不平衡量重新分配。举个例子日前预测明天中午光伏出力为100MW但到了明天上午最新的预测显示实际可能只有80MW。这个20MW的缺口日内层就要重新决定储能多放一点还是柔性负荷削减一点或者从外部电网多买一点这就是滚动修正。日内层通常还会加一个重要约束——对日前计划的偏离惩罚。这是为了让日内优化结果不至于彻底推翻日前计划否则日前层的机组组合决策就失去意义了。一般做法是在目标函数中加入日前计划功率与实际输出功率偏差的二次惩罚项或线性惩罚项让日内优化在跟随计划和利用新信息之间取得平衡。这个惩罚系数怎么调我后面会讲到是一个需要反复试的工程细节。2.3 实时层最后一道安全防线实时调度层对应的时间尺度更短通常是5到15分钟甚至更细到分钟级。它处理的是上一个滚动周期结束后、到下一个滚动周期开始前这段时间内的净负荷偏差。实时层的决策变量相对简单储能的充放电微调、部分柔性负荷的紧急调整、可能的弃风弃光等。在建模上这一层往往被处理为一个线性规划问题求解速度要求很高一般不涉及整数变量除非必须考虑储能的状态切换。三层之间的信息传递关系是日前层输出基准计划日内层在基准计划上做滚动修正实时层再对残余偏差做最后平衡。每一层都在做缩小偏差的工作就像漏斗一样把不确定性一级一级过滤掉。最终的结果是系统整体的灵活性得到保障而储能的调度行为也受到约束不会因为高频次、无规划的充放而加速老化——这正好过渡到本文最关键的问题储能衰减建模。3. 储能衰减建模把电池损耗变成调度模型里的硬约束3.1 理想储能模型的致命缺陷很多复现代码或论文在写储能约束时直接把电池当成一个理想水库只考虑充放电功率上下限、SOC上下限、以及SOC更新方程最多再互斥一下充放电状态。这种模型在数学上很漂亮但在高比例可再生能源场景下会得出非常离谱的调度方案——调度机会让储能疯狂充放电一天内实现十几个等效满充满放循环因为从纯运行成本角度看每一次低价充、高价放都能带来收益电池本身的损耗被完全无视了。但真实电池是有寿命的。无论是磷酸铁锂还是三元锂充放电循环次数都有限全生命周期能处理的累计吞吐量也有限。如果调度模型不把这种寿命损耗纳入考量虚拟电厂的长期经济性就是一笔糊涂账短期运行成本看着很低但实际上储能可能干两三年就报废了换电池的成本足以吞噬所有套利收益。这一步对整篇复现来说是决定性的一环如何把电池衰减建模成可解算的数学约束同时保留优化模型的线性或混合整数线性特性。3.2 循环老化与日历老化的数学表达电池老化分两大类循环老化和日历老化。循环老化与充放电过程的深度、次数和倍率相关日历老化则与时间流逝、温度、SOC静置水平相关。在调度时间尺度为天以内的问题中日历老化通常在一天内的差异不大可以视为恒定损耗因此重点建模循环老化即可。工程中最容易落地的循环老化模型是吞吐量法。其核心思想是在电池整个生命周期内累计通过电池的总Ah电量或等效满充满放次数是有限且近似固定的。假设电池额定容量为E_B、参考放电深度DOD_ref下的额定循环次数为N_life那么电池在全生命周期内可以处理的总吞吐量约为Q_total N_life × DOD_ref × E_B那么在每一个调度时段内电池产生的循环老化成本可以按等效循环次数折算ΔN_cyc(t) (η_ch × P_ch(t) P_dch(t) / η_dch) × Δt / (2 × E_B × DOD_ref)这里用充电电量或放电电量之和除以2倍的单次循环吞吐量近似得到等效满充满放循环次数。这个值不需要精确到电化学机理级别但在优化模型里作为约束条件或成本项已经足够可信。然后把全时段的累计循环次数约束定义为∑ ΔN_cyc(t) ≤ N_life (或剩余寿命对应的循环次数)这就可以作为一个线性不等式直接放进MILP模型。如果还想精细化一点可以把每次充放对应的DOD与循环寿命的映射关系做成分段线性函数用一个查表插值的方式纳入优化但复杂度会显著上升需要权衡。3.3 衰减约束如何反向影响调度策略当衰减约束被加入模型后调度策略会发生一个非常有意思的变化储能不再想充就充、想放就放了。由于每一次充放电都在消耗寿命额度调度器会倾向于把储能用在刀刃上——只在电价差足够大、或平衡需求足够紧急的时段才动用储能尽量降低浅充浅放的频率。我在复现中发现一个数值规律当衰减成本系数设得过高时储能几乎不会被调度系统灵活性明显下降只能靠外部市场和弃风弃光来平衡当衰减成本系数设得过低时储能又被频繁调度寿命消耗过快总成本反而上升。这个最优点就是我们做复现时最关心的结果——灵活性要求与储能寿命成本之间的最优平衡点。仿真中对这个系数做敏感性分析是论文里非常出彩的一部分。4. 灵活性指标与成本目标的融合4.1 用什么指标衡量系统灵活性灵活性这个概念听起来抽象但在数学上是可以量化的。通常定义系统的上调灵活性供给为在给定时间尺度内所有可用资源能够增加的出力总量下调灵活性供给为能够减少的出力总量需求侧则是净负荷的波动量和预测不确定度。这样系统的净灵活性裕度可以写为F_up(t) P_res_up(t) - ΔP_net_up(t) F_down(t) P_res_down(t) - |ΔP_net_down(t)|其中P_res_up和P_res_down分别代表系统在t时刻能提供的上下调能力ΔP_net_up和ΔP_net_down代表净负荷在t时刻相对预测值的上下偏差需求。当裕度小于0时就意味着系统灵活性不足——要么切负荷要么弃电。在优化模型中灵活性不足可以转化为惩罚项也可以转化为机会约束。工程实现上更简单的是在约束条件中显式加入灵活性裕度约束P_res_up(t) ≥ (1 α) × ΔP_net_up(t)其中α是灵活性裕度系数取0.1~0.3。这个约束的物理含义是在任何时刻系统必须预留出至少等于净负荷上偏差需求1.1倍的上调能力。这样就把灵活性从一个概念词变成了模型里可以直接求解的线性不等式。4.2 目标函数设计费用最小化里的三个层次虚拟电厂调度问题的目标函数通常包含三层费用。第一层是基础运行成本向外部电网购电的费用减去售电收入、分布式机组燃料成本、启停成本。第二层是储能相关成本包括充放电功率带来的电费损耗以及最重要的——寿命衰减成本也就是把上一节计算的等效循环次数乘以单位循环损耗成本。第三层是惩罚项弃风弃光的惩罚、失负荷的惩罚、以及灵活容量不足的惩罚。整个目标函数的组合方式决定了调度策略的倾向。如果惩罚项设置过高调度器会过度保守储能在大多数时刻都保持高SOC以备不时之需系统经济性变差如果惩罚项设置过低系统会频繁突破灵活性约束可靠性受损。我在复现时采用的策略是弃风弃光惩罚设置为购电价的1.5倍模拟政策考核压力失负荷惩罚设置为购电价的10倍失负荷代价远高于一切运行费用灵活性不足惩罚则介于两者之间。4.3 一个典型的目标函数与约束集合用数学形式表述本文复现的日前调度模型可以概括为min ∑ [ C_buy(t)×P_buy(t) - C_sell(t)×P_sell(t) C_deg×ΔN_cyc(t) C_curt×P_curt(t) C_flex×F_def(t) ]约束包括功率平衡P_PV(t)P_WT(t)P_buy(t)P_dch(t) P_load(t)P_ch(t)P_sell(t)P_curt(t)P_flex(t)储能SOC递推SOC(t1) SOC(t) (η_ch×P_ch(t) - P_dch(t)/η_dch)×Δt/E_BSOC上下限SOC_min ≤ SOC(t) ≤ SOC_max充放电互斥0 ≤ P_ch(t) ≤ u(t)×P_max0 ≤ P_dch(t) ≤ (1-u(t))×P_max爬坡约束|P_dch(t1)-P_dch(t)| ≤ Ramp_dch灵活性裕度约束P_ch(t)P_buy(t)P_curt(t) ≥ 需求侧上调需求储能累计吞吐量约束∑ ΔN_cyc(t) ≤ N_remain这里面最容易出问题的是充放电互斥约束需要对u(t)做0-1整数建模这也是模型变成MILP的关键来源。如果问题规模很大可以考虑将互斥约束松弛为互补约束并配合求解器调参但精度和求解稳定性就要打折扣。5. Matlab代码实现从数学模型到可运行程序的落地5.1 整体架构与求解器选择复现这个项目的Matlab代码架构我建议分成四大模块数据初始化模块、模型构建模块、求解模块、结果后处理模块。数据初始化负责读入负荷、光伏、风电时序数据和电价参数模型构建模块用YALMIP语言定义决策变量、目标函数和约束条件求解模块调用Gurobi如果安装了或CPLEX求解MILP结果后处理模块负责画功率平衡图、SOC曲线图和成本分解图。求解器方面强烈建议用Gurobi或CPLEX。Matlab自带的linprog只能处理线性规划无法处理0-1整数变量fmincon虽然通用但在大规模整数问题上非常慢且不稳定。Gurobi在电力系统优化领域几乎成了标准配置安装YALMIP后直接在代码里写ops sdpsettings(solver,gurobi)非常方便。如果手头没有Gurobi的许可证可以考虑用SCIP求解器YALMIP也支持做替代但求解速度会慢一些。5.2 核心代码日前调度模型构建下面这段是日前调度模型的核心代码骨架我在复现时反复调整过的部分都用注释标出T 96; % 96个时段15分钟一个点 dt 0.25; % 时间步长单位h E_B 2; % 储能额定容量 MWh P_max 0.5; % 储能最大充放电功率 MW eta_ch 0.95; % 充电效率 eta_dch 0.95; % 放电效率 SOC_init 0.5; % 初始SOC SOC_min 0.1; SOC_max 0.9; % SOC约束 N_life 5000; % 额定循环次数 DOD_ref 0.8; % 参考放电深度 C_buy price_buy(:); % 购电价序列 C_sell price_sell(:); % 售电价序列 C_deg 20; % 单位等效满放循环损耗成本元/次 % 决策变量 P_ch sdpvar(T,1); P_dch sdpvar(T,1); P_buy sdpvar(T,1); P_sell sdpvar(T,1); P_curt sdpvar(T,1); u binvar(T,1); % 1表示充电0表示放电 SOC sdpvar(T,1); % 约束 Constraints []; Constraints [Constraints, SOC(1)SOC_init]; for t 1:T-1 SOC(t1) SOC(t) (eta_ch*P_ch(t) - P_dch(t)/eta_dch)*dt/E_B; end Constraints [Constraints, SOC_min SOC SOC_max]; Constraints [Constraints, 0 P_ch u*P_max]; Constraints [Constraints, 0 P_dch (1-u)*P_max]; Constraints [Constraints, 0 P_curt 0.1*P_load(t)]; % 功率平衡光伏风电购电放电 负荷充电售电弃电 Constraints [Constraints, P_pvP_wtP_buyP_dch P_loadP_chP_sellP_curt]; % 吞吐量约束衰减建模核心 delta_N (eta_ch*P_ch P_dch/eta_dch)*dt/(2*E_B*DOD_ref); Constraints [Constraints, sum(delta_N) N_life*DOD_ref]; % 灵活性裕度约束示例上调备用 Res_up P_dch P_buy P_curt; % 可上调能力近似 Constraints [Constraints, Res_up 1.1*max(0, P_load - (P_pvP_wt))]; % 目标函数 Objective sum(C_buy.*P_buy - C_sell.*P_sell C_deg.*delta_N 20.*P_curt); % 求解 ops sdpsettings(solver,gurobi,verbose,2); result optimize(Constraints, Objective, ops);这段代码的思路非常适合做复现的起点。其中delta_N的计算是把每一时刻的充放电电量折算为等效满放循环次数然后累加约束。这是采用吞吐量法建模的核心所在。如果想把衰减建模改成容量衰减率的形式可以在SOC更新方程里再加一项容量修正系数但代码结构基本不变。5.3 结果后处理与图表输出求解完成后把结果提取出来的过程很容易被初学者忽略但非常重要。建议把所有关键变量重新组装成一个struct方便后续画图和统计比如result_data.P_ch value(P_ch); result_data.P_dch value(P_dch); result_data.SOC value(SOC); result_data.P_buy value(P_buy); result_data.delta_N value(delta_N);画图时推荐用两行三列的子图第一行画功率平衡曲线、储能充放电功率、SOC曲线第二行画购售电功率、灵活性裕度、累计吞吐量曲线。这些图直接决定了复现结果是否可读、是否经得起审稿人或者导师的质疑。6. 复现结果中的关键结论6.1 场景设置与对比方案我设置了一个典型的虚拟电厂测试场景光伏装机30MW风电装机20MW储能容量2MWh/0.5MW外加10%的可调柔性负荷。负荷曲线采用夏季典型日数据光伏和风电数据来自开源数据集的功率归一化曲线。对比方案分三种方案A传统日前调度储能按理想模型建模忽略衰减方案B单时间尺度调度仅日前但加入储能衰减约束方案C本文的多时间尺度调度日前日内实时并带衰减建模三种方案都跑完一遍后统计总运行成本、弃电率、储能等效循环次数和灵活性裕度。这套对比逻辑也是SCI论文中最常见的消融实验思路。6.2 结果差异与物理含义对比下来最显著的差异出现在方案A和方案C之间。方案A的总运行成本虽然最低但储能在一个模拟日内完成了相当于8次等效满充满放循环按这个损耗速度推算储能实际寿命只有设计寿命的一半左右。如果把换电池成本折算进总费用方案A的真实经济性其实最差。方案B相比方案A储能循环次数明显下降但灵活性裕度也下降——因为少了日内滚动修正层系统只能靠保守的日前计划来硬扛预测误差不可避免出现更多弃电。方案C在储能循环次数、弃电率、总费用三个指标上取得了平衡相对方案A储能循环次数下降了约40%弃电率从15%降到9%折算生命周期总成本后方案C比方案A节约了大约18%。这个结果说明了一个很重要的道理储能的衰减成本必须进入调度模型的视野否则虚拟电厂的经济最优只是短期幻觉。而多时间尺度框架恰恰是它的放大器——如果没有日内和实时层的修正能力日前计划再优也会被预测误差打折扣。7. 复现这个项目时踩过的坑逐一记下来7.1 衰减建模的时间尺度陷阱最坑的一次是衰减成本与调度步长不匹配导致的成本偏移。如果日前调度的时间步长是1小时但储能吞吐量约束按累积Ah计算时忘记乘以Δt那么求解器会认为储能可以无限吞吐衰减约束形同虚设。反之如果用15分钟步长却按小时计费电价折算衰减成本衰减成本会被夸大4倍储能几乎不动。这里的关键是所有涉及到能量、吞吐量的表达式都要显式乘以时间步长不要依赖默认值。7.2 充放电互斥约束的求解效率问题有朋友在复现时问我为什么加了互斥约束后Gurobi求解速度骤降。原因往往是binvar(T,1)作为充电状态标识变量与P_ch和P_dch之间的约束写得不够紧导致分支定界树非常深。改进技巧有两个一是用互补约束即P_ch*P_dch 0但这是非线性的只能用于小规模二是用indicator约束如implies(u1, P_dch0)需要借助YALMIP的implies函数并确保求解器支持indicator约束。实测下来如果问题规模不大T不超过96直接用两个不等式约束就已经够快了。7.3 滚动优化中计划漂移问题日内滚动优化最隐蔽的问题是计划漂移。由于每15分钟重新求解一次系统可能会因为最新预测的变化逐渐偏离日前计划。这种偏差如果没有惩罚最终会累积成日前计划和日内实际执行的巨大鸿沟——问题在于日前计划中的机组组合或市场竞标电量已经申报偏离太大会产生市场偏差费用。解决的办法是在日内目标函数中加入相对于日前计划的偏差惩罚项。惩罚系数太小漂移严重太大则日内层无法有效应对新的预测信息。我最终将惩罚系数设置为购电均价的0.3倍在跟随计划和利用新信息之间取得了不错的效果。7.4 需求响应资源的滥用风险最后提醒一下如果不限制柔性负荷的调节次数优化模型会倾向于把柔性负荷当作廉价调节资源导致某些负荷每天被调整十几次这在实际中根本无法接受。给每个柔性负荷增加最大调节次数约束或者在目标函数中加入单位调节次数的舒适度补偿成本可以有效避免这种情况。这一点在做小规模算例时看不出来但一旦把虚拟电厂内聚合的用户数放大到几百上千户问题就会非常突出。复现这个SCI项目让我重新认识了一件事虚拟电厂调度不只是数学优化问题更是工程权衡问题。储能衰减建模这个环节恰恰是连接电网安全运行和投资经济性的那根线。单纯追求灵活性会让储能快速报废单纯压制充放电次数又会损失收益。Matlab加YALMIP加Gurobi这套组合能非常高效地把这个平衡过程可视化、量化。如果想把这套模型扩展到更大规模可以考虑将三层调度拆成多个子问题并行求解或者用交替方向乘子法解耦不过那是下一步的事了。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进