资讯动态

虚拟电厂多时间尺度调度:储能衰减建模与灵活性平衡

发布时间:2026/10/14 3:09:40 来源:尧图企业网站定制
上周和一个做园区微网的工程师朋友吃饭他问我一个特别现实的问题调度方案里储能电池到底能不能当成“免费调节池”来用他那边分布式光伏占比超过一半中午净负荷断崖式下跌傍晚又直线飙升只能靠储能顶着。可原来的模型只要发现储能好用就会拼命用结果投运不到三年电池容量衰减到80%以下换电池的钱直接把收益吃掉一大半。这个问题恰好就是高比例可再生能源并网里最扎心的一组矛盾要灵活性就得让储能频繁出力要控制成本就必须把衰减建模放进优化目标。我最近刚照着某篇顶刊论文的方法用Matlab把一套虚拟电厂多时间尺度调度框架完整复现了一遍核心就是把储能衰减建模、灵活性量化、弃电惩罚和运行成本全部放进同一个优化问题里再通过日前、日内、实时三层调度去反复权衡。今天这篇文章就把里面的数学建模、衰减成本折算逻辑、代码骨架和复现过程中踩过的坑全部写出来特别适合正在做电力系统调度、虚拟电厂运营策略、储能经济性分析的研究生和相关工程师参考。1. 高比例新能源并网的两难越灵活越烧钱越省钱越脆弱1.1 一个园区微网工程师的真实困惑先说那个工程师朋友遇到的怪事。他们园区里装了约4MW分布式光伏、2MW风机外加一套2MWh储能柴油发电机基本是备用状态。以前净负荷波动小的时候调度员手工操作也忙得过来光伏和风机尽量全发储能只在电价高的时候充放两轮。后来新能源占比提上来中午光伏大发导致倒送功率逼近变压器上限调度系统会自动让储能大量充电傍晚光伏出力快速回落时又要求储能紧急放电撑住负荷。问题就出在这里调度策略越“聪明”储能循环次数越多、放电深度越大。他们做了个统计储能年等效满循环次数从最初的200次一下子涨到500多次平均放电深度从40%升到接近80%第二年电池容量衰减明显加快。工程师自己也清楚如果不改调度模型再过一两年就要发生更换储能模组的费用整个项目的投资回报模型全部推翻。这其实不是个例。高比例可再生能源并网后系统净负荷曲线会呈现典型的“鸭型曲线”爬坡速率非常快火电、燃气机组往往跟不上灵活性缺口必须靠储能、可调负荷甚至需求响应来补。可是储能不是“无耗材”的调节资源每一次充放电都在消耗循环寿命。问题从“能不能调”变成了“调多少次才划算”于是调度模型必须把储能衰减成本作为一项真实的、可量化的账目放进去。1.2 复现顶刊模型前先想清楚“灵活性”是什么很多人一听到灵活性就说“就是备用容量呗”其实不够。调度场景里的灵活性是指在任意时段系统可调资源能够跟随净负荷变化的速率和方向。我们做优化模型时用一个很直接的约束来量化[ \sum_i R_i^{up}(t) R_{ess}^{up}(t) \ge \Delta L_{net}^{up}(t) ]简单解释净负荷在某一段时间内突然上升了那么所有可调机组和储能能在对应时间内增加的出力总和必须不小于这个上升量否则就要切负荷反过来净负荷突然下降系统要有能力压低出力否则就要弃风弃光。这个约束看起来简单可一旦加入调度模型就会自动区分哪些时刻灵活性是“紧张的”哪些时刻是“富余的”。我习惯把灵活性通俗比作开车。前方路况突然收窄你既要有动力储备爬坡能力又要有刹车冗余降出力能力还要保证这些储备在关键时刻真的可用约束生效。如果不建灵活性约束哪怕备用容量设置得很高也可能出现“备而不用、用而不达”的情况。1.3 为什么大多数代码复现只复现了“结果”我读一些复现代码时经常发现大家把原文的目标函数、约束条件抄得很完整求解器跑出来的结果也很漂亮但一到储能寿命部分就含糊了要么用一个固定折旧率要么把电池折旧当成线性损耗。结果就是同样一套调度策略在论文模型里“总成本最优”放到真实项目里却因为电池提前报废而亏钱。原因主要有三个。第一原文的衰减模型往往嵌入在某个迭代或分段线性化结构里不像其他约束那样能一眼看清楚复现时容易跳过。第二仿真数据随机生成没有刻意构造不同DOD工况导致退化模型无法体现效果。第三有些复现只做了日前调度没有把多时间尺度滚动机制和衰减迭代耦合起来等于只复现了半套系统。所以我这次刻意按照“先建模、再拆解、后编码”的顺序写把储能衰减如何进入目标函数、如何在滚动调度中迭代更新完整地讲一遍。如果你也想复现类似论文直接按这个链路去对号入座。2. 把调度问题写成数学虚拟电厂优化模型的主干2.1 虚拟电厂里到底聚合并控制了什么虚拟电厂不是一个物理电厂而是把分布式光伏、风电、储能、可调负荷、小容量燃气机组等分散资源聚合起来统一参与调度和交易。建模时每种资源都有各自的数学描述光伏和风电预测出力上限可以弃电弃电有惩罚或机会成本储能充放电功率、SOC上下限、充放电效率、循环寿命约束可调负荷可平移或可削减有成本和响应速度限制燃气/柴油机组出力上下限、爬坡速率、启动成本和运行成本。在Matlab代码里我会把每个资源的变量统一用“时段索引”定义比如P_g(gen, t)表示第gen台机组在第t时段的出力SOC(t)表示储能荷电状态。时间分辨率在日前是1小时日内可以细化到15分钟。把这些变量放在同一个优化问题里用约束把它们连接起来就得到了虚拟电厂的“骨架”。2.2 目标函数运行成本、电池损耗、弃电与失负荷的四本账我复现时用的目标函数大概长这样简写[ \min \sum_t \left[ C_{op}(t) C_{age}(t) \lambda_{curtail} \cdot P_{curtail}(t) \lambda_{loss} \cdot P_{loss}(t) \right] ]第一项C_op是运行成本包括燃气机组燃料成本、从电网购电成本、储能运维成本第二项C_age是储能老化成本也就是这一轮调度中储能循环寿命的折算金额第三项是弃电惩罚代表可再生能源没有消纳完第四项是失负荷惩罚代表灵活性不足导致负荷损失。很多人问我这四个账目到底怎么权衡。答案是通过惩罚系数。如果你把弃电惩罚设得特别高模型会优先消纳新能源储能会被调用得更频繁如果你把老化成本系数设得准确模型又会尽量避免深度充放。调参过程本质上就是在“消纳率、可靠性、储能寿命”三件事之间找平衡点而这恰恰是标题里“平衡灵活性与储能成本”的核心。2.3 约束条件里最容易翻车的三个细节第一功率平衡方程的符号方向。净负荷等于系统负荷减去新能源出力但是新能源出力到底是“预测可用出力”还是“实际消纳出力”很多新手把两者混用导致弃电变量形同虚设。正确写法是功率平衡中用的是调度后实际消纳的新能源出力可用出力 - 实际出力 弃电量这样弃电惩罚才能生效。第二SOC的状态转移方程。写成[ SOC(t1) SOC(t) \eta_{ch} \cdot P_{ch}(t) \cdot \Delta t - \frac{1}{\eta_{dis}} \cdot P_{dis}(t) \cdot \Delta t ]要特别注意Δt的单位和功率单位是否一致。如果功率是MW、时间步长是小时那么充进去的能量就是MWh可是储能容量单位往往也是MWh这时直接算没毛病如果时间步长改成15分钟就一定要把Δt写成0.25小时否则SOC每步都会偏差很大。第三充放电互斥约束。很多模型允许充电功率和放电功率同时取正数这样目标函数会通过“充放同时发生”来刷成本得到无意义结果。要引入二进制变量或线性化约束确保同一时刻P_ch和P_dis至多一个为正。2.4 我用的最小算例参数表为了代码能跑通我设计了一个规模不大但能体现核心矛盾的系统。参数如下参数数值说明系统峰值负荷5 MW算例最大值光伏装机4 MW可弃风电装机2 MW可弃储能额定容量2 MWh磷酸铁锂类储能最大功率0.5 MW充放同值充放电效率0.95双向储能初始SOC0.5百分比燃气机组容量1 MW爬坡0.3 MW/h时间段数9615分钟一点弃电惩罚系数0.3 货币单位/kWh相对较高失负荷惩罚系数5 货币单位/kWh远高于运行成本这个参数表看起来很朴素但跑出来的结果能很清楚地看到灵活性约束什么时候起作用储能老化项什么时候改变调度决策。3. 储能衰减建模一道绕不过去的“寿命税”3.1 衰减为什么会被调度模型漏掉储能衰减之所以经常被忽略是因为它在短期内“看不见”。运行成本、购电成本每小时都在发生而电池寿命衰减是几个月甚至几年后才显现的优化模型天然喜欢把看不见的成本当成零。问题是高比例新能源并网后储能会成为灵活性主力每天几百次循环衰减速度远远超出预期。我跟踪过一组仿真数据如果不考虑老化成本储能在净负荷波动最大的场景下几乎每天都是“满充满放”SOH以一个夸张的速度下滑把老化成本加入目标函数后模型自动把部分高成本深度放电换成燃气机组出力和负荷侧响应储能的等效循环次数下降了接近30%总运行成本略高但全生命周期成本明显降低。这就是“寿命税”的作用让调度决策者看见远期代价。3.2 等效循环次数与充放电深度DOD的计算方式要计算储能老化先得把不规则的充放电行为折算成等效循环次数。工程上最常用的做法是[ N_{eq} \frac{\sum_t P_{dis}(t) \cdot \Delta t}{E_{rated}} ]即把一段时间内累计放电量除以额定容量得到的数值就是这段时间的等效满循环次数。这个指标简单、透明很适合放进调度模型里做线性处理。但问题是它没有考虑放电深度DOD的影响。DOD对寿命的影响不是线性的。同样放1 MWh电分成10次浅放和分成2次深放对电池寿命的伤害完全不同。大量实验数据能拟合出循环寿命与DOD的近似关系[ L_{cycle}(DOD) a \cdot DOD^{-p}其中a和p是电池特性参数典型范围p在0.8到1.5之间。从公式能看出DOD越高允许的循环次数越少而且是加速变少。这就是为什么我在目标函数里不能只放一个固定循环次数而是要把每一次充放电的DOD折算成对应的寿命消耗。 具体做调度优化时DOD和决策变量耦合后模型会变成非线性。我的处理办法是分段线性化把DOD分成几个区间每个区间用不同的线性老化系数逼近然后引入分段插值约束。这样既能保留“深度放电更伤电池”的核心特征又不会把求解速度拖垮。 ### 3.3 把SOH变化翻译成目标函数里的货币量 更精细的做法是建一个SOH状态变量让它随调度决策逐步下降 \[ SOH(t1) SOH(t) - \frac{N_{eq}(t)}{L_{ref}} \cdot k_{stress} \] L_ref是某个参考工况下的额定循环寿命k_stress是考虑DOD、温度等因素的修正系数。当SOH降到80%时一般认为电池寿命终结需要更换或退役。 但调度模型不能直接用SOH做目标还要把SOH损失换算成钱。我的做法是在每次调度前算一个“平均老化成本系数” - 假设更换整组储能需要C_replace - 在参考DOD下电池总循环次数为L_ref - 那么每个等效满循环的老化成本近似为 C_replace / L_ref - 再把每时段的放电量除法折算成“等效循环数”乘上该成本得到C_age(t)。 举个例子如果电池更换成本是100万元参考条件下能循环4000次那么每个等效满循环的寿命折旧就是250元。某次调度让储能多放了0.5 MWh额定容量2 MWh则等效循环数是0.25对应老化成本就是62.5元。每15分钟都算一笔账目标函数自然不敢乱用储能。 ### 3.4 加入衰减项后灵活性-成本曲线开始真正被“看见” 我做一个实验在同样的新能源出力曲线下把老化成本系数从0逐步调到实际值再记录系统的弃电率、储能循环次数和总成本能看到一条典型的“权衡曲线”老年化系数刚开始上升时调度会转向燃气机组弃电率稍微上升但总成本下降这是因为减少的电池更换费用比多烧的气费还要明显随着系数继续增大储能几乎不再被调度灵活性缺口只能靠更高弃电和少量失负荷来弥补总成本又开始上升。 这条曲线的转折点就是“灵活性-储能成本”最优平衡点。很多顶级论文复现的最终价值并不是得到某个固定解而是能画出这条曲线并解释每个转折背后的物理原因。我在Matlab里只需要循环调用优化求解器每次修改老化系数就能把这条曲线完整描出来。 ## 4. 多时间尺度调度日前、日内、实时到底怎么配合 ### 4.1 为什么要拆成三个时间尺度而不是一个尺度跑到底 新能源预测误差随着时间尺度的延长急剧变大。提前24小时预测的光伏出力可能和实际差30%以上提前4小时预测误差能降到10%左右到了实时分钟级误差相对小很多。如果把所有决策都压在日前一步做完一旦预测不准就会因为机组启停不够、储能SOC预留不当在运行中出现灵活性缺口。 多时间尺度调度的思路是把决策分层日前决定“哪些机组开、哪些不开”日内用滚动窗口去修正“每台机组出多少、储能充放多少”实时再做最后的偏差纠正。这样每一层只决策它能决策好的事整体鲁棒性会强很多。 ### 4.2 滚动时域控制的基本信息流 我在Matlab里用的是非常标准的滚动时域控制流程可以概括成四步 1. 在时刻t0获取最新预测曲线覆盖t0到t0H 2. 求解从t0到t0H的优化问题 3. 只执行第一步的调度指令例如只下发t0到t0ΔT的储能功率 4. 到了t0ΔT更新预测重新执行第1步。 这四步循环起来日内调度就能跟随最新信息不断修正。代码结构也很简单for循环加一个solveWindow函数即可。关键在于每轮滚动之后储能SOC要传下去作为下一轮的初始状态否则计划会整体漂移。 ### 4.3 跨尺度计划如何对齐避免指令“打架” 我一开始做滚动调度时踩过一个很蠢的坑日前计划让机组在晚上20点停机但日内预测发现20点负荷上升滚动优化又把机组重新启停结果启停成本被算了两次调度指令前后矛盾。后来我采用两种方式解决 第一把日前确定的整数变量机组启停、储能是否深度充放区间锁定为硬约束日内只优化连续变量。这种方法牺牲一点日内灵活性但保证了计划的稳定执行。第二在日内目标函数里加一个“追踪日前计划”的软惩罚项 \[ \min \sum_t \left[ C_{op}(t) C_{age}(t) \beta \cdot \left( P(t) - P^{DA}(t) \right)^2 \right] \] 系数β取一个合适的值让日内设备出力尽量贴近日前计划除非遇到特别大的预测偏差才能偏离。储能成本在不同时间尺度里也要注意重复计算如果在日前已经把未来24小时的老化成本都算过了日内滚动就应该只计算“新增的、超过日前预计”的等效循环部分否则相当于给储能寿命算了两次账成本偏高。 ## 5. Matlab复现的代码骨架与完整流程 ### 5.1 数据准备96点曲线和参数表 我建议用struct把算例参数集中管理别散落在脚本各处。例如 matlab % 基础参数设置 mpc struct(); mpc.T 96; % 15分钟一个点共24小时 mpc.dt 0.25; % 时间步长小时 mpc.Pload loadProfile(1:96); % 负荷曲线单位MW mpc.Ppv pvProfile(1:96); % 光伏预测出力上限 mpc.Pwt windProfile(1:96); % 风电预测出力上限 mpc.Erated 2; % 储能额定容量MWh mpc.Pmax 0.5; % 储能最大功率MW mpc.eta 0.95; % 充放电效率 mpc.SOC0 0.5; % 初始SOC mpc.curtailPrice 0.3; % 弃电惩罚 mpc.lossPrice 5; % 失负荷惩罚 mpc.replaceCost 1e6; % 更换储能成本 mpc.Lref 4000; % 参考循环寿命负荷和新能源曲线我是先用正弦叠加随机扰动生成的。这样做的好处是曲线形状可控能突出净负荷陡升陡降的区间缺点是预测误差分布不够真实。你迁移到真实项目时直接从数据中台导出CSV替换即可。5.2 日前调度模型怎么落地我用YALMIP搭建优化模型因为它写约束比手拼矩阵容易读很多。核心代码如下%% 定义决策变量 P_g sdpvar(1, mpc.T); % 燃气机组出力 P_ch sdpvar(1, mpc.T); % 储能充电功率 P_dis sdpvar(1, mpc.T); % 储能放电功率 SOC sdpvar(1, mpc.T1); % 荷电状态 P_cur sdpvar(1, mpc.T); % 弃电功率 P_loss sdpvar(1, mpc.T); % 失负荷功率 z binvar(1, mpc.T); % 充放电互斥变量1表示充电 %% 约束条件 Constraints []; % 功率平衡负荷充电 光伏风电燃气放电购电 Constraints [Constraints, ... mpc.Pload - P_pv_used - P_wt_used - P_g - P_dis P_ch P_loss 0]; % SOC状态转移 Constraints [Constraints, ... SOC(2:end) SOC(1:end-1) mpc.eta * P_ch * mpc.dt ... - P_dis / mpc.eta * mpc.dt]; % 充放电互斥与功率限制 Constraints [Constraints, ... 0 P_ch mpc.Pmax * z, ... 0 P_dis mpc.Pmax * (1 - z)]; % SOC上下限 Constraints [Constraints, 0.2 SOC 0.9, SOC(1) mpc.SOC0]; %% 目标函数 C_op sum(P_g * gasPrice P_ch * omPrice P_dis * omPrice); C_cur sum(P_cur * mpc.curtailPrice); C_loss sum(P_loss * mpc.lossPrice); N_eq sum(P_dis * mpc.dt) / mpc.Erated; % 等效循环 C_age N_eq * mpc.replaceCost / mpc.Lref; Objective C_op C_age C_cur C_loss; %% 求解 ops sdpsettings(solver, auto, verbose, 0); sol optimize(Constraints, Objective, ops);这里要注意P_pv_used和P_wt_used不能直接用预测上限要额外定义同时加约束P_pv_used mpc.Ppv - P_cur_pv; Constraints [Constraints, 0 P_cur_pv mpc.Ppv];这样弃电变量才真正进入模型。5.3 日内滚动和实时修正的循环结构日内滚动我写成一个循环每次只更新窗口数据并求解。伪代码如下mpc.T_window 24; % 滚动窗口长度比如6小时 mpc.T_step 1; % 每步执行1个点 for k 1:mpc.T_step:mpc.T % 截取从当前时刻开始的预测窗口 idx k:min(kmpc.T_window-1, mpc.T); data_window prepareWindow(mpc, idx); % 求解窗口优化问题日前整数变量锁定 [plan, SOC_next] solveWindow(data_window, fixed_int); % 只执行第一步调度 dispatch(k) plan(1); % 更新储能SOC和系统状态 mpc.SOC0 SOC_next; end滚动优化的关键是要把“窗口末尾SOC可自由变化但需要在最终时刻回到合理区间”这件事处理好。我在目标函数里加了一个终值约束或惩罚Constraints [Constraints, SOC(end) mpc.SOC0 - 0.05];避免前几个窗口疯狂放电后面无电可用。5.4 衰减迭代更新SOH的实现细节衰减迭代我没有放进单次优化里因为那样会把DOD非线性直接塞给求解器速度很慢。我的做法是“调度-更新SOH-再调度”的外层迭代SOH 1.0; for iter 1:maxIter % 用当前SOH修正老化成本系数 mpc.cycleCost (mpc.replaceCost / mpc.Lref) / SOH; % 执行日前和滚动调度 [result] runMultiTimeScale(mpc); % 根据实际充放电历史更新SOH N_eq_total sum(result.P_dis) * mpc.dt / mpc.Erated; SOH_new SOH - N_eq_total / mpc.Lref * stressFactor(result); if abs(SOH_new - SOH) 1e-4 break; end SOH SOH_new; end内层调度是线性优化外层更新SOH是迭代修正。这样既保住了求解速度又让老化成本系数随电池健康状态逐步变化比固定寿命折旧要真实得多。实测时两三轮迭代就能收敛。5.5 三种方案的结果对比我跑了三个版本的模型作对比结果很有说服力方案年等效循环次数弃电率年化运行成本储能更换成本不建衰减模型约520次4.1%低极高第3年触发更换固定折旧系数约430次5.2%中中寿命约5年动态SOH迭代滚动约360次5.8%中高低寿命约7年动态方案放弃了一点弃电率换来储能寿命几乎翻倍把全生命周期总成本拉到最低。这组数据说明只盯“当前运行成本”的调度方案在高比例新能源场景下很容易自欺欺人。复现论文的真正价值就是让这种长期成本显性化。6. 实测中的坑与迁移方法6.1 我踩过的三个典型坑第一个坑发生在SOC状态转移上。我把dt直接写成1后来换到15分钟数据忘了改结果SOC每步都过冲第二天直接越界。现在所有时间相关参数都从mpc.dt引用再也不手动填数。第二个坑是充放电互斥约束。最开始漏了z变量求解器钻空子让P_ch和P_dis同时为0.2MW目标函数里老化成本被无数倍的循环次数刷爆结果调度结果完全不可解释。加上二进制变量后模型才恢复正常。第三个坑出现在滚动窗口目标函数。如果只让SOC0.2不限制终值前几个窗口会尽可能放电等净负荷高峰时SOC已经偏低后面会频繁弃电。这相当于“寅吃卯粮”。后来我在滚动窗口的末尾加软约束才把调度曲线拉回合理区间。6.2 从“能跑”到“跑得稳”的三次迭代第一次跑通代码时结果很漂亮但把储能循环次数一统计等效循环高得吓人因为模型为了压低运行成本让储能几乎包办所有爬坡。这就是缺乏衰减建模的典型症状。第二次加入固定老化成本系数后储能使用明显收敛但电池实际劣化速度并非恒定固定系数无法反映后期电池加速老化的规律。第三次把动态SOH迭代和滚动窗口耦合后调度策略才真正稳定下来储能浅充浅放燃气机组承担基荷弃电率虽然略升但全寿命成本最优。每次调参数时我都习惯先把老化系数和灵活性惩罚系数画成二维扫参图先看清趋势再定参数。6.3 把这段代码迁移到你的项目前要改什么如果你的项目有真实负荷和新能源预测数据第一步是替换loadProfile、pvProfile、windProfile三个函数的返回值同时检查时间分辨率是否一致。如果原始数据是5分钟颗粒度建议先按15分钟聚合别直接塞进模型。如果虚拟电厂里还有温控负荷、电动汽车充电桩适当增加对应的功率上下限和可调量约束即可。新增资源时要同步修改变量定义、约束拼接、成本项、灵活性供给项四处缺一不可。尤其注意别把新增资源只加进约束忘了加进灵活性表达式否则会出现“能出力但不算备用”的矛盾。如果想把代码从研究环境搬到生产环境建议把求解器从通用求解器换成适合嵌入式部署的轻量MILP求解器并将滚动窗口从6小时压缩到2小时减少单次求解时间。模型精度会有少量下降但实时性会好很多。复现完这套模型后我最大的感受是储能衰减不是给调度“添麻烦”而是帮调度看清真实的账。以前我也习惯把电池当大号电容用直到看见SOH迭代后储能主动从深度放电退回到浅充浅放才发现顶刊论文里那些复杂的建模本质上都是在回答一个朴素的问题——怎么让系统在新能源高占比下既保持灵活又不让未来为今天买单。如果你也正被“多装电池还是少用电池”卡住我建议先别急着改电池配置把衰减建模加进调度目标函数里跑一遍你会看到不一样的答案。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑