1. 问题拆解:为什么高比例可再生能源让虚拟电厂调度变难
1.1 灵活性到底是什么,为什么缺它
先说个直觉:电网最怕的不是“电不够”,而是“电忽多忽少”。光伏夜里没出力,风电一阵大一阵小,负荷侧又是早高峰晚高峰,两边一叠加,系统就需要有人能快速顶上或退下。这个“快速顶上或退下”的能力,就是灵活性。
传统火电机组虽然也能调,但爬坡速率有限,最小技术出力不低,而且启停一次成本高。高比例可再生能源并网以后,净负荷(负荷减去风光出力)的波动幅度和变化速率都明显加大,传统机组越来越扛不住。虚拟电厂这时候的价值就体现出来了:把分散的储能、可调负荷、分布式电源、电动汽车聚合起来,统一调度,相当于给电网装了一块“虚拟的调峰机组”。
但聚合不是白拿的。储能设备有容量和功率限制,可调负荷有舒适度和生产约束,分布式电源有出力上限。你要让这些资源听话,就得有一个调度策略。这个策略既要算得准,又要反应快,还要让储能别用得太狠——用太狠寿命就短,成本就高。这就是标题里“灵活性与储能成本”这对矛盾的来源。
1.2 储能成本与寿命:衰减建模为什么不能省
绝大多数初期做虚拟电厂调度的人,会把储能当成一个“大电池”,只设一个SOC上下限就扔进约束里。这种简化在短时间尺度、低频调度里勉强能跑,但一放到全年运行或实际投运,问题就来了。
锂电池的寿命不是固定的,它和放电深度、循环次数、工作温度、充放电倍率都有关系。同一块电池,如果每天都满充满放,可能三年就报废;如果只浅充浅放,用十年都没问题。调度策略会直接影响电池的充放电深度和循环次数,反过来这些又决定了储能的更换成本和项目经济性。所以,一个真正“能用”的虚拟电厂调度模型,必须把衰减成本写进目标函数里,而不是算完之后再后悔。
顶刊里常见的做法是引入“循环老化成本系数”,用等效循环次数或者累积吞吐量来刻画衰减。比如把每次充放电折算成等效满充循环,再乘以单位循环成本,这样优化器就会自行权衡“多用储能赚灵活性”和“少用储能省寿命”。我复现的那篇SCI就是在这个思路上做了多时间尺度的扩展,把日前、日内、实时三个层级的决策和衰减模型耦合起来。
1.3 多时间尺度调度要解决什么
风电光伏预测误差随时间尺度缩小而变化。提前24小时的预测可能偏差20%以上,提前4小时可能偏10%,而实时前15分钟则相对准。如果只用一套模型、一个时间颗粒度去应对所有决策,要么因为预测太粗导致计划不可行,要么因为频繁调整导致计算量爆炸。
多时间尺度调度的本质是“分层决策、逐级修正”:
- 日前调度:根据预测曲线,决定机组启停、储能充放电计划、可调负荷基线,目标是全周期经济性最优。
- 日内调度:每15分钟或1小时滚动更新,修正风光预测偏差,调整储能出力、可调负荷。
- 实时控制:秒级到分钟级,通过AGC或者本地控制器处理突发扰动,保证功率平衡和电压稳定。
层级之间不是孤立,而是通过“传递值”衔接。比如日前决定了储能SOC的参考轨迹,日内就要跟踪这个轨迹,但允许在安全范围内偏离;实时控制则在日内计划基础上做微调。这种结构既保证全局最优性,又兼顾了响应速度。Matlab里实现这套逻辑,核心就是写好每个时间尺度的优化模型,然后在外层用循环控制时间窗口滚动求解。
2. 模型设计:从SCI论文中提炼核心框架
2.1 虚拟电厂聚合了什么资源
虚拟电厂不是新电厂,它更像一个“资源经纪人”。在我的复现里,聚合对象分四类:
| 资源类型 | 典型设备 | 建模关键点 |
|---|---|---|
| 储能系统 | 锂离子电池、飞轮 | SOC动态、充放电功率、衰减成本 |
| 可调负荷 | 空调、热水器、工业负载 | 可削减量、可转移时段、舒适度约束 |
| 分布式电源 | 屋顶光伏、小型风电、柴油机 | 出力曲线、燃料成本、启停约束 |
| 电动汽车 | 充电桩集群 | 充电需求、可调度时间窗、V2G能力 |
不同类型的资源,响应速度和可控维度差别很大。储能和柴油机可以毫秒级响应,但柴油机有燃料成本和排放;空调负荷响应快但持续时间有限,而且不能把人热着;电动汽车如果车主第二天要开车,你最多只能把充电挪到凌晨,不能反向放电太多。所以调度模型必须是混合整数规划:0-1变量管启停和充放电状态,连续变量管功率和SOC。Matlab里用Yalmip工具箱建模非常顺手,先把约束一行行写出来,再用gurobi或cplex求解就行。
2.2 多时间尺度上下层模型怎么搭
我复现的论文用的是“日前主问题—日内子问题—实时修正”三层结构。用大白话翻译:主问题先定大调子,子问题在给定调子下随机应变,实时修正再查漏补缺。
日前层(24小时,单位小时)的目标函数包含购电成本、储能老化成本、可调负荷补偿成本和柴油机燃料成本。约束包括功率平衡、各资源上限、储能SOC递推、爬坡约束。这层求解结果输出:各时段储能充放电计划、SOC参考值、可调负荷削减量。
日内层(4小时滚动,单位15分钟)在日前计划的基础上,接收最新的风光预测值,用滚动窗口重新优化。目标函数加一个“防抖项”,惩罚对日前计划的偏离,否则日内层每次都在推倒重来,储能会频繁充放,实际没法执行。这个惩罚系数不需要设太大,它在目标函数里的单位是元/MW,我一般设成日前单位购电成本的0.1倍左右。
实时层(15分钟内的秒级采样)用本地PI控制或模型预测控制跟踪日内计划的出力点,这里一般不重新解优化,而是用规则:当实际出力低于计划且频率偏低,储能释放;高于计划,储能充电。我在Matlab里用一个简单的状态机实现,运行速度非常快,适合做仿真验证。
三层之间传递的参数可以用结构体(struct)管理,比如schedule.day_ahead.p_storage、schedule.intraday.soc_ref,这样每层代码都很干净,不会因为变量名太多把自己绕晕。
2.3 储能衰减怎么嵌入优化目标
衰减建模最常用的实用方法是“等效循环老化”。具体公式是这样的:
每时段储能充放电行为折算成一个等效满充循环次数:
[ N_{cyc}(t) = \frac{|P_{ch}(t)|\Delta t + |P_{dis}(t)|\Delta t}{2 \cdot E_{rate}} ]
除以2是因为一个完整循环包含一次充电和一次放电。然后用这个次数乘上单次循环成本:
[ C_{deg} = \sum_t N_{cyc}(t) \cdot \frac{C_{bat}}{N_{life}} ]
其中 ( C_{bat} ) 是电池总投资成本,( N_{life} ) 是标称循环寿命(比如6000次)。这个方法没有用复杂的半经验模型,但已经抓住了“用得越狠、衰减越快、成本越高”的核心,而且非常适合嵌入线性规划。因为折算公式里只有绝对值,我们可以用两个非负变量 ( P_{ch} )、( P_{dis} ) 分别表示充放电,绝对值自然就拆开了。这样目标函数里所有项都是线性的,求解器就是混合整数线性规划(MILP),不用碰非线性求解,稳定性高很多。
如果你还想更精确,可以参考GB/T 44260-2024《虚拟电厂资源配置与评估技术规范》里对储能寿命评估的要求,在模型外部加一个事后校验模块,把优化出的SOC曲线再跑一遍深度充放循环统计,看看是否超出厂商质保条件。我自己在做项目时,会把这两者结合:优化内部用线性折算,优化后输出一份储能寿命消耗报告,这样既快又不失严谨。
3. Matlab实现:关键代码模块与落地细节
3.1 数据准备与参数设置
复现的第一步必须是数据。我自己没有真实VPP运行数据,所以用了公开的IEEE 33节点系统改了一下,配合比利时Elia电网的公开风光数据(可以下载)。Matlab代码里用readmatrix读CSV,统一处理成时间序列格式。
参数设置建议单独用一个脚本或函数init_params.m,把所有参数集中管理。我给一个参数清单:
%% 虚拟电厂参数 % 储能 param.E_rate = 5; % 额定容量 MWh param.P_rate = 1; % 额定功率 MW param.soc_min = 0.2; % 最低SOC param.soc_max = 0.9; % 最高SOC param.soc_init = 0.5; % 初始SOC param.eta_ch = 0.95; % 充电效率 param.eta_dis = 0.95; % 放电效率 param.c_bat = 5000; % 电池投资成本 CNY/kWh param.n_life = 6000; % 标称循环次数 % 可调负荷 param.load_flex_ratio = 0.2; % 可削减比例 param.compensation = 0.8; % 削减补偿单价 CNY/kWh % 柴油机 param.diesel_pmax = 2; % 最大出力 MW param.diesel_ramp = 0.5; % 爬坡速率 MW/h param.diesel_cost = 1.2; % 燃料成本 CNY/kWh % 电网购电 param.grid_buy_price = [0.5, 0.8, 1.2]; % 分时电价单位一定要统一。我习惯所有功率用MW,能量用MWh,电价用元/kWh的话要除以1000换算。否则算出来的成本差三个数量级,排查半天才发现是单位问题。
3.2 约束条件怎么写
这里展现日前调度模型的核心约束。用Yalmip定义变量:
P_ch = sdpvar(T, 1); % 充电功率 P_dis = sdpvar(T, 1); % 放电功率 u_ch = binvar(T, 1); % 充电状态 u_dis = binvar(T, 1); % 放电状态 SOC = sdpvar(T+1, 1);% SOC P_diesel = sdpvar(T, 1); P_flex = sdpvar(T, 1); P_grid = sdpvar(T, 1);充放电互斥约束:
0 <= P_ch <= param.P_rate * u_ch; 0 <= P_dis <= param.P_rate * u_dis; u_ch + u_dis <= 1;SOC递推约束:
SOC(1) = param.soc_init; for t = 1:T SOC(t+1) == SOC(t) + (P_ch(t)*param.eta_ch - P_dis(t)/param.eta_dis) / param.E_rate; param.soc_min <= SOC(t+1) <= param.soc_max; end功率平衡约束:
P_grid(t) + P_dis(t) + P_diesel(t) + P_wind(t) + P_pv(t) == ... P_load(t) - P_flex(t) + P_ch(t);可调负荷约束:
0 <= P_flex(t) <= param.load_flex_ratio * P_load(t);柴油机爬坡约束:
-P_diesel_ramp <= P_diesel(t+1) - P_diesel(t) <= P_diesel_ramp;这里有个容易忽略的坑:Yalmip里SOC的长度如果是T+1,那么在写循环时索引要对齐。我试过从SOC(1)对应t=0,但很多论文的SOC(1)就是t=1的初始值。建议在代码注释里写清楚“SOC(t)表示t时段开始时的电量”,否则后面跟踪参考轨迹时会错位。
3.3 求解器选择与Yalmip调参
Yalmip只是建模语言,真正求解要靠外部求解器。强烈推荐Gurobi,免费学术许可很好申请,而且对MILP的求解速度比Cplex快不少。Matlab里调用方式:
ops = sdpsettings('solver', 'gurobi', 'verbose', 2, ... 'gurobi.TimeLimit', 120, ... 'gurobi.MIPGap', 0.01); optimize(constraints, objective, ops);MIPGap设置到1%就够了。对于虚拟电厂日前调度,几十个0-1变量、几百个连续变量的规模,Gurobi通常在几秒内就能找到最优解。如果遇到大规模算例(比如上百个节点、上千个时段),建议开启gurobi.MIPFocus = 1加快目标搜索。
另外,Yalmip里写目标函数时,尽量用sum()向量化表达,不要写for循环逐项累加,否则建模时间比求解时间还长。我见过有人用for循环写2000个时段的约束,Yalmip建个模型跑了5分钟,改成向量化20秒就建好了。
3.4 衰减模型的代码实现
衰减成本在目标函数里这样写:
% 等效循环次数 N_cyc = (P_ch + P_dis) * dt / (2 * param.E_rate); C_deg_cost = N_cyc * (param.c_bat * param.E_rate * 1000 / param.n_life); objective = sum(P_grid .* grid_price * dt) ... + sum(P_flex .* param.compensation * dt) ... + sum(P_diesel .* param.diesel_cost * dt) ... + sum(C_deg_cost);注意param.c_bat如果单位是元/kWh,E_rate是MWh,要乘1000换成kWh。这样算出来的单位才是元。我在初版复现时这里少乘了1000,导致衰减成本几乎不起作用,优化器疯狂用储能,结果SOC曲线在上下边界来回撞,后来查出来是单位换算错了。
如果你想更精细一点,可以给衰减成本加一个权重系数λ,代表“调度策略对寿命的重视程度”。λ=0就是完全忽略衰减,λ=1是经济最优。实际项目里,λ可以从储能度电成本推导出来。比如电池1元/Wh储能系统,循环寿命6000次,那每次满充放的成本约为1元/Wh / 6000 ≈ 0.167元/Wh,即167元/kWh。再换算成每MWh充放成本,这样目标函数里的衰减项就有了真实物理意义。
4. 实测结果与平衡逻辑
4.1 一个算例看灵活性-成本权衡
我设计了一个48小时的算例:可再生能源占比60%,风电光伏曲线人为加了一个陡坡,下午16点到18点光伏骤降,而负荷正好进入晚高峰。对比两组设置:
- 方案A:忽略衰减成本,储能随便用。
- 方案B:加入衰减成本,λ按真实电池度电成本计算。
结果很有意思。方案A中,储能在16:00-16:30以满功率放电,SOC从90%掉到20%,然后在18:00迅速充满,再放电到20%。两天下来,储能等效循环次数是3.2次,购电成本低,但折算的电池寿命损耗非常高。如果把电池更换成本摊进去,总成本反而比方案B高15%。
方案B中,优化器主动把储能出力削减了一部分,改用柴油机顶上高负荷。虽然柴油机有燃料成本,但避免了储能在陡坡期的深度循环。两天内等效循环次数降到1.8次,总成本反而更低。这就是灵活性与储能成本平衡的直观体现:不是“用越多越好”,而是“用得值不值”。
4.2 衰减建模对调度策略的影响
加入衰减建模后,有几个调度行为会发生变化:
- 储能充电不会一味追求“充满”,而是充到SOC上限附近就停。因为最后那10%电量对应更高的折损,性价比低。
- 放电深度受限。优化器倾向于从SOC 60%放到40%,而不是90%放到10%。浅充浅放能显著延长电池寿命,这是衰减项在起作用。
- 柴油机在部分时段启动。哪怕燃料成本贵,只要替代了深度循环,整体还是划算。
- 可调负荷的削减时段会发生转移。原来可能在光伏出力高峰削减负荷(因为电价便宜),现在会转移到储能SOC较低时削减,降低储能压力。
如果你拿到那篇SCI论文的图,会看到储能SOC曲线有明显的“平缓化”趋势。我复现后画的SOC曲线基本复现了这个特征:高SOC时放电到中等SOC就不继续了,而不是像没加衰减模型一样一放到底。
4.3 运行时间与收敛性经验
三时间尺度模型全部跑完,做30天仿真,Matlab运行时间大约在10分钟到半小时之间,取决于求解器。日前层每天解一次,日内层每15分钟解一次所有资源。实际测试中,Gurobi求解单次日前模型平均2.3秒,日内单次平均0.6秒,实时修正不做优化,采用规则决策。
收敛性方面,两个容易踩的坑:
- 如果
MIPGap设得太严格(比如0.0001),求解器容易在局部最优附近反复切换整数变量,迟迟不收敛。设到0.01或0.005就够了,对总成本影响不到1%,但速度能快5倍。 - 如果模型中写了太多
sum( ... ) <= 0这类冗余约束,会拖慢求解速度。建议先不加冗余约束,跑通了再逐步加,验证约束是否真的有作用。
5. 常见问题与避坑清单
5.1 求解超时怎么办
超时基本是模型太大或整数变量太散。我在复现时遇到过一次,把日前24小时的充放电状态分别用两个0-1变量(充、放)表示,其实可以合并成一个三态变量?Yalmip不支持直接建三态,但可以用u_ch + u_dis <= 1来限制,这个没问题。真正让我超时的是可调负荷的分档,我给了10个档位,每个时段都要选一档,结果整数变量暴增。后来改成连续变量加线性化近似,速度立刻上来了。教训是:能用连续变量解决的事,别轻易加整数变量。
5.2 双线性项怎么处理
如果你在模型里写“储能SOC乘以某个价格系数”,或者“充放电状态与功率相乘”,就会产生双线性项,变成MIQP或MINLP,求解困难。处理手法基本是两种:
- 大M法:把双线性项拆成多个不等式,用一个足够大的M强制约束。比如当状态为1时,功率可以自由;状态为0时,功率为0。这个就是我们在3.2里写的写法,其实已经拆好了。
- McCormick包络:用于两个连续变量相乘的线性逼近,更精细但复杂度高。对于储能调度,用大M法就够了。
我自己一次踩坑是:想写“储能只在电价高时放电”,直接在目标函数里加price * P_dis,这其实是线性项,没问题。但想写“SOC*P_dis”表示衰减,这就拆不开了。最终我用的是等效循环法而不是SOC加权法,绕开了双线性。
5.3 单位统一和标幺值
Matlab没有量纲检查功能,单位错误是隐蔽性最高的错误。一个小建议:在代码开头用assert检查关键参数量级。
assert(param.E_rate < 1000, 'E_rate units should be MWh, got generic value'); assert(max(grid_price) < 10, 'grid_price units should be CNY/kWh');如果你习惯用标幺值,把基准功率设成100MW,储能容量变成0.05 p.u.,SOC范围0.2-0.9,这样计算时数字都在0到1之间,不容易溢出。但输出结果时要记得乘回基准值,否则写报告时容易看成小数。我一般只对IEEE节点系统用标幺值,对虚拟电厂这种聚合模型直接用有名值,逻辑更直观。
5.4 与标准GB/T 44260-2024的衔接
有不少网友在问虚拟电厂资源配置怎么评估,其实GB/T 44260-2024提供了很好的框架。这个标准在2024年发布,名字是《虚拟电厂资源配置与评估技术规范》,里面规定了虚拟电厂应具备的调节能力、响应时间、容量配置等要求。我们在做调度仿真的时,可以把标准里的关键指标转换成约束。比如标准规定虚拟电厂调节能力应不低于系统峰值负荷的5%,那么算例里就可以加一条约束:
sum(param.flex_up_all_resources) >= 0.05 * peak_load;这样仿真结果可以直接对照标准,论文或项目报告中更有说服力。我在复现SCI时虽然没有直接引用标准,但做项目评审时把这个约束加进去,专家反馈很好。
写在最后的一个小技巧
这套代码跑通以后,最值得扩展的方向是“滚动时域+随机场景”。目前用的是确定性预测,实际中可以改成场景法:每次优化用10组风光场景,把目标函数写成期望成本,约束则同时满足所有场景。改造起来也不难,Yalmip里把每个场景的变量加下标循环就行。我个人实际测试,加10个场景后求解时间涨了大约8倍,但结果鲁棒性明显提升,再遇到预测偏差时,日内修正的偏离量能减少一半。做研究或做落地项目,这个方向绝对值回票价。