1. 为什么是“日前-日内”两阶段:单阶段调度到底缺了什么
先聊点实际的。
做电力系统优化的人,最早接触的调度模型大概率是单阶段:明天24小时,负荷预测曲线给出来,光伏、风电预测曲线给出来,电价曲线也给出来,然后一次性把明天144个点(或者96个点)的机组出力、储能充放电全部求出来。结果摆出来很漂亮,运行成本最小、弃风弃光为零,调度员看了也满意。
但你把这个方案真正丢到第二天去执行,很快就会发现问题:预测是预测,实际是实际。光伏预测出力100MW,上午十点实际只有70MW;负荷预测曲线下午两点是峰值,结果当天高温,午高峰提前了一个小时。单阶段模型把所有预测值当成确定性输入,算出来的“最优解”在真实运行场景里往往是次优的,甚至可能直接违反功率平衡约束。
这就是两阶段优化调度的核心动机:不是把调度问题变复杂,而是把“不确定性”从模型假设里摆到明面上来处理。
所谓日前-日内两阶段,本质上是把调度决策拆成两个时间尺度各司其职:
- 日前阶段:在每一天开始前(通常是前一天下午或者晚上),基于次日的光伏、风电、负荷、电价预测曲线,做24小时(或96个时段)的全局优化,确定机组的启停计划、联络线购电计划、储能日级充放电策略。这一步的特点是时间长、信息粗、决策偏“战略”。
- 日内阶段:在当天运行中,以15分钟或1小时为滚动窗口(典型的是15分钟刷新一次,向前看4小时),结合最新的超短期预测数据,对日前计划进行滚动修正。这一步的特点是时间短、预测准、决策偏“战术”。
两阶段的核心思想一句话就能概括:用日前的“粗计划”锁定不可快速改变的决策,用日内的“细修正”应对不断更新的信息。
反过来看单阶段的问题就清楚了:它把“计划”和“修正”混在一层里做,既要求机组启停这类慢决策有足够的预见性,又要求它能对分布式光伏的瞬间抖动做出响应——这在数学上不是不行,但在工程上你会陷入两难:窗口拉长了,预测误差累积,日内修正能力差;窗口缩短了,又看不到全天的峰谷形势,储能套利和机组启停规划很容易做出短视决策。
做Matlab实现之前,建议先把这层逻辑想透。两阶段调度在编程上不是“两个模型拼在一起”那么简单,它涉及两个优化模型之间数据传递、变量衔接、滚动刷新机制的设计,这些处理方式直接决定你的代码框架长什么样。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 数学建模:目标函数、约束条件与不确定性处理
两阶段优化落实到代码,第一步永远是建模。写Matlab之前,把模型写清楚能省掉后面大量的调试时间。
我这里的建模以微电网/区域综合能源系统为背景,包含燃气轮机、储能、联络线购电、光伏、风电和负荷。你也可以按自己的系统替换对应单元,模型框架是通用的。
2.1 日前阶段的数学模型
日前阶段的目标函数通常是全天运行总成本最小,包含三项:购电成本、机组发电燃料成本、弃风弃光惩罚。
目标函数:
[
\min \sum_{t=1}^{T} \left( C_{grid,t} P_{buy,t} + \sum_{g} C_g P_{g,t} + \lambda_{curtail} (P_{PV,fore}^{t} + P_{WT,fore}^{t} - P_{PV,dispatch}^{t} - P_{WT,dispatch}^{t}) \right)
]
其中 (T) 是日前时段数(96或24,看你取15分钟还是1小时间隔),(C_{grid,t}) 是分时电价,(P_{buy,t}) 是联络线购电功率,(C_g) 是第 (g) 台机组的燃料成本系数,(P_{g,t}) 是机组出力,(\lambda_{curtail}) 是弃风弃光惩罚系数,(P_{PV,dispatch}^{t}) 和 (P_{WT,dispatch}^{t}) 是实际调度消纳的光伏和风电功率。
约束条件分几类:
第一类是功率平衡约束,这个最简单,也是所有调度模型的底线:
[
P_{buy,t} + \sum_{g} P_{g,t} + P_{PV,dispatch}^{t} + P_{WT,dispatch}^{t} + P_{dis,t} = P_{load,t} + P_{ch,t}
]
(P_{dis,t}) 和 (P_{ch,t}) 分别是储能放电和充电功率。这个等式的意思是:所有源侧出力之和等于负荷加上储能充电功率。
第二类是机组约束,包括出力上下限和爬坡约束:
[
P_{g}^{min} \le P_{g,t} \le P_{g}^{max}
]
[
-R_g^{down} \le P_{g,t} - P_{g,t-1} \le R_g^{up}
]
爬坡约束在日前阶段容易被忽略,但它实际是保障调度方案可执行性的关键。如果不加爬坡约束,优化器可能在相邻时段让机组出力从10MW直接跳到50MW,现实中机组根本做不到。
第三类是储能约束,包括SOC递推方程、SOC上下限、充放电功率限制、以及充放电不能同时进行的约束:
[
SOC_{t+1} = SOC_t + \eta_{ch} P_{ch,t} - \frac{P_{dis,t}}{\eta_{dis}}
]
[
SOC^{min} \le SOC_t \le SOC^{max}
]
[
0 \le P_{ch,t} \le P_{ch}^{max} \cdot u_{ch,t}
]
[
0 \le P_{dis,t} \le P_{dis}^{max} \cdot u_{dis,t}
]
[
u_{ch,t} + u_{dis,t} \le 1
]
这里 (u_{ch,t}) 和 (u_{dis,t}) 是0-1变量,用来禁止储能同时充电和放电。这个约束引入整数变量后,模型就从LP变成了MILP,求解难度上一个台阶,但这是必要的,现实中储能装置不可能同时充放电。
2.2 日内阶段的数学模型
日内阶段我建议采用模型预测控制(MPC)的滚动优化框架。设当前时刻为 (k),预测时域为 (H)(通常取4小时,15分钟一个点就是16个时段),日内阶段求解如下问题:
[
\min \sum_{t=k}^{k+H-1} \left( C_{grid,t} P_{buy,t} + \sum_{g} C_g P_{g,t} + \omega_1 (P_{PV,fore}^{t} - P_{PV,dispatch}^{t})^2 + \omega_2 \Delta P_{g,t}^2 \right)
]
注意日内阶段的目标函数和日前有个关键区别:多了两个二次惩罚项。(\omega_1) 是弃光惩罚权重,(\omega_2) 是机组出力变化量惩罚权重。之所以要加 (\omega_2 \Delta P_{g,t}^2),是为了避免滚动优化导致机组出力在相邻两个滚动窗口之间大幅跳变——如果不在目标函数里加这个惩罚,日内修正阶段会为了贴合最新预测而频繁调整机组出力,执行层面机组会累死,磨损也会加剧。
日内阶段的约束条件与日前基本一致,但有两个关键差异:
- 预测值换成了超短期预测(如未来4小时每15分钟刷新一次的光伏、风电、负荷预测);
- 日前阶段优化的机组启停状态 (u_{g,t}) 在日内阶段作为固定参数输入,不再重新优化。机组启停是慢决策,一天之内频繁启停不现实,所以在日内阶段锁定启停状态,只优化出力水平。
这两个模型之间的关系可以这样理解:日前阶段做“决策”,日内阶段做“跟踪”。 日前确定的是哪些机组开着、储能的日级充电窗口大致在哪几个时段;日内做的是在这些大框架下,根据最新信息调整每个时段的具体出力值和储能的精细充放电功率。
2.3 不确定性处理:场景法与鲁棒优化的取舍
两阶段模型解决了“信息逐步更新”的问题,但还没有完全解决“预测误差怎么量化”的问题。在你的敏感性分析研究里,这个点更加绕不开,因为你要对光伏、风电、负荷做扰动测试,本质上就是在模拟不同程度的预测误差。
处理预测误差,常见三种思路:
- 确定性等价:直接用预测值作为输入,不做任何误差建模。这是最朴素的方案,实现简单,但对预测误差大的场景(比如强对流天气下的光伏出力)完全没办法。
- 随机场景法:对预测误差的概率分布进行采样(比如用拉丁超立方抽样生成500组场景),对每个场景求解优化问题,然后取期望最优。这个方案最接近真实随机性,但计算量巨大,适合离线规划,不适合日内滚动实时求解。
- 鲁棒优化:考虑最坏情况——光伏出力取区间下限,负荷取区间上限,求最坏情况下的最优解。鲁棒解保守,但保证了任何场景下都不越限,适合安全性要求高的场景(比如孤岛微电网的保底调度)。
我做Matlab实现时,主体框架采用确定性等价,但在敏感性分析模块里会结合场景法思路:对扰动后的参数重新求解两阶段模型,分析结果变化。这是我的推荐做法——两阶段调度的框架要简洁高效,敏感性分析作为离线研究模块可以额外承担更重的计算负担。
3. Matlab实现框架:从数据准备到求解器调用
说句实话,Matlab调优模型这块,很多人卡住的不是数学建模,而是代码结构。两阶段调度涉及到数据传递、变量拼接、滚动刷新,如果一开始没把数据结构设计好,写了一半就会陷入各种维度不匹配的报错里。
3.1 问题数据结构设计
我的建议是把所有输入数据封装成结构体,别到处用全局变量。举个实测的例子,我会建一个 params 结构体:
matlab复制params.T = 96; % 日前时段数(15分钟一个点)
params.dt = 0.25; % 时段长度,单位小时
params.PV_forecast = ...; % 96x1 光伏预测出力
params.WT_forecast = ...; % 96x1 风电预测出力
params.Load_forecast = ...; % 96x1 负荷预测
params.Price = ...; % 96x1 分时电价
params.Pg_max = 30; % 机组最大出力 MW
params.Pg_min = 5; % 机组最小出力 MW
params.R_up = 10; % 爬坡上限 MW/h
params.R_down = 10; % 爬坡下限 MW/h
params.SOC_max = 0.9; % 储能SOC上限
params.SOC_min = 0.1; % 储能SOC下限
params.SOC_init = 0.5; % 储能初始SOC
params.eta_ch = 0.95; % 充电效率
params.eta_dis = 0.95; % 放电效率
params.P_ch_max = 10; % 最大充电功率 MW
params.P_dis_max = 10; % 最大放电功率 MW
为什么这么设计?因为两阶段模型的日内滚动循环里,需要不断用新的预测数据覆盖 params.PV_forecast 和 params.WT_forecast,如果数据散落在脚本的各个变量里,你会在循环里写出一大堆 temp_var_01、temp_var_02 之类的丑陋代码。封装成结构体后,每次滚动只需要更新 params.PV_forecast(k:k+H-1) 这一段就行。
3.2 基于Yalmip的建模实践
建模和求解,我一直用Yalmip工具箱配合Gurobi或Cplex求解器。Yalmip的最大优势是建模语法接近数学表达式,不懂底层建模技术的同学也能快速上手。
日前阶段的Yalmip建模代码大致是这样:
matlab复制% 决策变量
P_buy = sdpvar(params.T, 1); % 购电功率
P_g = sdpvar(params.T, 1); % 机组出力
P_PV = sdpvar(params.T, 1); % 光伏实际消纳
P_WT = sdpvar(params.T, 1); % 风电实际消纳
P_ch = sdpvar(params.T, 1); % 储能充电
P_dis = sdpvar(params.T, 1); % 储能放电
SOC = sdpvar(params.T+1, 1); % SOC轨迹
u_ch = binvar(params.T, 1); % 充电状态
u_dis = binvar(params.T, 1); % 放电状态
% 约束集合
C = [];
% 功率平衡
C = [C, P_buy + P_g + P_PV + P_WT + P_dis == params.Load_forecast + P_ch];
% 机组约束
C = [C, params.Pg_min <= P_g <= params.Pg_max];
for t = 2:params.T
C = [C, -params.R_down*params.dt <= P_g(t) - P_g(t-1) <= params.R_up*params.dt];
end
% 新能源消纳约束(不能超过预测值)
C = [C, 0 <= P_PV <= params.PV_forecast];
C = [C, 0 <= P_WT <= params.WT_forecast];
% 储能约束
C = [C, SOC(1) == params.SOC_init];
C = [C, SOC(2:end) == SOC(1:end-1) + params.eta_ch*P_ch*params.dt - P_dis/params.eta_dis*params.dt];
C = [C, params.SOC_min <= SOC <= params.SOC_max];
C = [C, 0 <= P_ch <= params.P_ch_max * u_ch];
C = [C, 0 <= P_dis <= params.P_dis_max * u_dis];
C = [C, u_ch + u_dis <= 1];
% 目标函数
Objective = sum(params.Price .* P_buy * params.dt) + ...
sum(Cg * P_g * params.dt) + ...
lambda_curtail * sum((params.PV_forecast - P_PV) + (params.WT_forecast - P_WT)) * params.dt;
% 求解
ops = sdpsettings('solver', 'gurobi', 'verbose', 0);
result = optimize(C, Objective, ops);
操作里的几个细节值得强调:
- 爬坡约束里乘上
params.dt是因为爬坡率单位是MW/h,而优化时段是15分钟,要换算成每时段内的爬坡能力。 - SOC递推方程里,充电加效率,放电除以效率,这个方向千万别搞反。很多人的结果跑出来SOC曲线一直往下掉,多半就是效率项放错了位置。
- 目标函数里的价格和功率都乘以
params.dt,把功率(MW)乘以时间(h)换算成能量(MWh),再乘以价格得到成本。如果忘了乘时间,你的目标函数会直接差4倍(96个15分钟时段)。
3.3 日内滚动优化的循环结构
日内阶段的核心是一个 for 循环,每次迭代求解一个窗口内的MPC问题。关键是每个窗口之间的状态传递:
matlab复制% 日内滚动优化主循环
H = 16; % 预测时域:16个15分钟 = 4小时
k_step = 1; % 滚动步长:1个15分钟
for k = 1:k_step:(params.T - H + 1)
% 更新当前窗口的超短期预测
params_window.PV_forecast = PV_ultra_short(k:k+H-1);
params_window.WT_forecast = WT_ultra_short(k:k+H-1);
params_window.Load_forecast = Load_ultra_short(k:k+H-1);
params_window.Price = Price(k:k+H-1);
% 固定日前阶段的机组启停状态
% 求解窗口内MPC问题(代码与日前类似,但约束条件增加u_g固定)
% 记录第一个时段的决策值,作为实际执行值
% 更新储能SOC状态:根据执行结果递推
params_window.SOC_init = SOC_exec(k+1);
end
这个循环结构里最核心的操作是“只执行第一个时段的决策”。滚动优化的各个教科书里都强调这个哲学:虽然你求出了未来4小时的完整出力计划,但真正执行的是第一个15分钟的决策,到下一个15分钟,你会拿到新的预测数据,重新优化一遍。如果不这么干,滚动优化就退化成了“每隔4小时做一次开环优化”,预测误差照样累积。
还有一点,SOC初始化在每个窗口里要改成上一个窗口的实际执行值,而不是沿用预测值。这个细节如果不注意,日内模型和日前模型在储能充放电策略上会产生系统性偏差,导致滚动优化结果越来越失真。
4. 敏感性分析:四个参数独立扰动怎么设计才科学
接下来是这篇工作最有区分度的部分——敏感性分析。我看到不少人在做敏感性分析时,做法就是“把光伏出力乘个0.8,跑一遍模型,看总成本变了多少”,然后画一个柱状图就完事。这种做法不能说是错的,但远远不够。敏感性分析如果做得严谨,能帮你回答很多“如果……会怎样”的问题:
- 如果明天的电价峰谷差拉大,储能的套利空间会增加多少?
- 如果光伏出力高估了20%,弃光量会恶化到什么程度?
- 如果负荷预测偏低,日内购电成本会怎么激增?
4.1 敏感性分析的正确打开方式
我的建议是,对电价、光伏、风电、负荷这四个参数,分别独立做以下几点:
第一,确定扰动范围。 不是随便拍一个“±10%”,应该结合历史预测误差的统计特征。我常用的是±5%、±10%、±20%、±30%四档扰动,覆盖从正常运行偏差到极端场景的范围。如果系统的历史预测误差RMS(均方根误差)已经在15%左右,那你的扰动范围至少要覆盖到30%才有分析意义。
第二,明确评价指标。 这是关键中的关键。不能只盯一个总成本指标,建议同时记录以下几类:
- 系统总运行成本(元)
- 弃风弃光率(%)
- 储能日充放电循环次数(次)
- 高峰时段联络线购电功率(MW)
- 机组出力均方差(反映机组调节压力)
这么做的原因是,不同参数对不同指标的影响方向可能完全不同。举个例子,电价上升会让总成本上升,但也可能因为储能套利空间增大而让弃光率下降——如果你只看总成本,会得出“电价越高越差”的片面结论,看不到电价信号对新能源消纳的正面引导作用。
第三,每次只扰动一个参数,其他三个保持基准值。 这在数学上是常规的单因子敏感性分析,但实际操作里容易跑偏。比如你同时改了电价和光伏,然后发现总成本变了,你根本说不清是哪个参数引起的。独立扰动是敏感性分析的最低伦理标准。
4.2 电价敏感性:储能套利空间与购电策略
电价的敏感性分析重点看两个方向:一是电价整体水平变化(所有时段电价同时乘一个系数),二是电价峰谷差变化(峰时电价上浮,谷时电价下浮)。
整体水平变化对调度结果的影响比较直观:电价涨了,购电成本上升,优化器会更倾向于让燃气机组多发电,储能更积极地在谷时段充电、峰时段放电。但有个不那么直观的现象值得关注——当电价整体下降到某个阈值以下时,燃气机组的发电成本高于购电成本,优化器会直接选择减少本地发电、增加购电,这时候机组可能面临长时间停机。
峰谷差变化对储能调度策略的影响更有意思。我实测下来,峰谷价差从0.4元/kWh逐步拉大到1.0元/kWh的过程中,储能套利收益不是线性增加的。价差很小时,储能充放电的损耗(充放电效率带来的能量损失)可能超过套利收益,优化器会倾向于让储能待机;价差超过某个阈值后,储能才开始频繁进行峰谷套利。这个“阈值效应”在敏感性分析里特别值得挖掘,因为它对应着储能投资的经济性边界。
4.3 光伏、风电、负荷:三个功率型参数的扰动设计
这三个参数放在一起说,因为它们的扰动方式类似,但分析关注点完全不同。
光伏出力扰动:主要关注弃光率和储能充电策略的变化。光伏出力下降时,弃光率自然会下降,但注意别忽略另一个连锁反应——光伏出力下降会导致系统净负荷(负荷-光伏)升高,高峰时段可能需要增加购电或机组出力,这会传导到总成本和储能放电策略上。我做光伏敏感性时发现一个值得注意的现象:光伏出力下降20%左右,系统总成本上升的幅度往往超过20%——因为光伏的边际成本接近零,失去这部分“免费电量”后,替代电源的边际成本远高于光伏。
风电扰动:风电和光伏的差异在于出力波动特性。风电出力通常夜间较大,和负荷曲线错位明显,所以风电敏感性分析要多关注“反调峰”情景——风电出力越高,低谷时段的弃风压力越大,储能的充电窗口越紧张。我做风电敏感性时会把扰动设置为两种情景分开测试:一种是整体按比例缩放(模拟风电资源年景好坏),另一种是只扰动某些时段的出力(模拟阵风天气下的局部波动)。后者对日内阶段的考验更大——日前计划假设风电平稳,结果午后突然一阵大风,日内修正要靠储能和机组爬坡快速吸纳,这时候你要看系统能不能扛住。
负荷扰动:负荷是所有调度决策的“需求侧驱动”,它的扰动影响是全方位的。负荷下降时,系统可能面临“必须停机”的困境——燃气机组有最小出力限制,储能有SOC上限,如果负荷太低,过剩电力无处安放,只能弃风弃光。负荷上升时,问题变成“容量够不够”——联络线购电上限、机组最大出力、储能放电功率,任何一个环节卡脖子,都会导致切负荷惩罚。我建议在做负荷敏感性时额外记录“切负荷量”这个指标,它能帮你评估系统的备用容量是否充裕。
4.4 敏感性分析结果的量化与呈现
敏感性分析的数据整理阶段,我通常用表格来量化影响程度。推荐一个实用的量化指标——弹性系数:
[
E = \frac{\Delta Y / Y_0}{\Delta X / X_0}
]
其中 (Y) 是评价指标(如总成本),(X) 是扰动参数(如光伏预测值),(\Delta X / X_0) 是扰动比例,(\Delta Y / Y_0) 是指标变化比例。弹性系数大于1说明该参数对指标有放大效应,小于1说明有衰减效应,负数说明反向影响。
举个例子,如果负荷扰动+10%导致总成本上升15%,弹性系数就是1.5,说明负荷对总成本的影响具有放大效应——这背后的机理是负荷增加后,系统需要调用更高边际成本的电源(比如从燃气机组切换到购电,甚至从经济性差的机组获取额外出力)。
在我的Matlab代码里,敏感性分析模块会输出一张主表,格式大致如下:
| 扰动参数 | 扰动比例 | 总成本(万元) | 弃风弃光率(%) | 储能循环次数 | 峰值购电(MW) |
|---|---|---|---|---|---|
| 基准值 | 0% | 32.8 | 4.2 | 1.8 | 35.6 |
| 电价 | +10% | 35.1 | 3.9 | 2.2 | 33.2 |
| 电价 | +20% | 37.7 | 3.6 | 2.7 | 30.1 |
| 光伏 | -10% | 35.6 | 3.1 | 1.5 | 39.8 |
| 光伏 | -20% | 38.9 | 2.4 | 1.2 | 44.3 |
| ... | ... | ... | ... | ... | ... |
这种表格放在论文里、技术报告里,都是拉满专业度的呈现方式,比单独画几条曲线要直观得多。
5. 踩坑记录与工程化建议
两阶段优化调度模型跑起来不算难,但要做到结果可靠、代码健壮、可复现,坑还是不少。有些坑我自己踩过,有些是带学生时看他们踩的,都值得记录。
5.1 整数变量导致的求解时间爆炸
MILP问题带0-1变量(储能充放电状态、机组启停状态),求解规模上来后,计算时间从几秒冲到几分钟很常见。尤其做敏感性分析时要跑几十组实验,每个实验都等很久,效率惨不忍睹。
我的处理策略有三条:
第一,能用连续变量近似的地方尽量不用整数变量。比如储能充放电互斥约束 (u_{ch,t} + u_{dis,t} \le 1),在SOC递推约束完整的前提下,可以用一组互补约束近似处理,或者干脆把充放电功率定义为一个有符号变量 (P_{ess,t} \in [-P_{ch}^{max}, P_{dis}^{max}]),正为放电负为充电。这样唯一要检查的是“同时充放电”场景在目标函数里有没有套利空间——如果电价差合理,优化器没有动机同时充放电(白白损失效率),这个简化在大部分场景下成立。
第二,给定求解时间上限。Gurobi的 TimeLimit 参数设置为30秒或60秒,让求解器在可接受的时间内给出近优解。做敏感性分析的大规模实验时,这个参数救了我很多次。
第三,敏感性分析时可以只跑日前模型,不做日内滚动。日内MPC的求解频率是15分钟一次,跑一天就是96次优化,每组实验要跑1-2分钟。而日前模型一次求解只要几秒——先做完日前阶段的敏感性扫描,再有针对性地对关键情景做日内仿真,能省下大量时间。
5.2 大M参数的选取
0-1变量和连续变量的耦合约束(比如 (P_ch \le P_ch^{max} \cdot u_ch))需要引入大M参数。大M取太大,数值稳定性会出问题,求解器容易陷入病态条件,结果出现诡异的小数值;取太小,又会把可行域错误地截断,导致找不到最优解。
实务经验是:大M取该约束物理量上限的1.1到1.5倍就够了。比如充电功率上限是10MW,M取12;如果这个约束还同时限定了U_ch=0时P_ch必须为0,那M取P_ch_max的1.2倍就是安全的。别迷信“M越大越好”,这在数值优化里是大忌。Yalmip其实会在内部帮你处理很多这种binvar约束,但如果遇到自定义大M,宁可在约束里写 P_ch <= P_ch_max * u_ch 这样明确的上限形式,也别用一个通用的 M * (1 - u_ch) 处理所有情况。
5.3 日间与日内模型衔接的SOC初值漂移
滚动优化里最常被忽视的问题是SOC初值漂移。日前模型在凌晨时段可能让储能以一个较低的SOC过夜(因为夜间电便宜,没必要提前充),但日内模型实际运行时,如果储能因为上一个窗口的决策已经充到了较高的SOC,下一个窗口的优化起点就会和日前计划的假设发生偏差。
解决思路有两个:
- 在日前模型中增加SOC终值约束,让储能一天结束后的SOC回到初始值附近,保证“可持续调度”。这相当于给储能一个日循环约束,不允许它一天内只放不充或者只充不放。
- 在日内模型的优化目标中增加“SOC参考轨迹跟踪”项,让SOC尽量贴近日前计划值,避免日内决策与日前的长期规划偏离太多。权重系数可以调,不需要太大,只要能起到“吸引”作用就行。
这两个方案配合使用,效果最稳。
5.4 结果可视化:让曲线会说话
Matlab的调度结果输出,我固定画这么几张图:
- 功率平衡堆叠图:x轴是时段,y轴是功率,用堆叠面积图展示购电、机组、光伏、风电、储能各自在每个时段的出力,负荷用一条粗线叠加。这张图能把一天的功率平衡关系一眼看穿。
- SOC曲线图:单独画储能的SOC轨迹和充放电功率棒状图。我调试模型时第一先看这张图——如果SOC曲线出现尖刺或者陡升陡降,基本可以断定SOC递推约束写错了。
- 敏感性分析雷达图/柱状图:把各参数的弹性系数画在一起,能直观看出哪个参数对系统影响最大。
Matlab画堆叠面积图用 area 函数,画SOC轨迹和充放电功率用双y轴 plotyy 或者 yyaxis 命令,都是很成熟的操作,不展开了。有一点值得提醒:画图前先把数据单位统一。我见过很多人功率用MW、成本用万元、SOC用百分比,混在一起画图后坐标轴乱到完全没法看。
5.5 一个小众但实用的排查技巧
排查模型错误时,最快的办法是“跑一个平凡场景”:把预测数据全部设成常数,或者把电价设成24小时都一样,看看优化器会给出什么结果。如果平凡场景下结果符合你的物理直觉(比如电价不变时储能应该几乎不充放电,因为套利空间为零),那模型大概率是好的;如果平凡场景下结果仍然异常,赶紧回头查约束条件或者目标函数,别浪费时间在复杂场景上调试。
做敏感性分析时同理——先把基准值稳定复现出来,再开始扰动。基准跑不出来对,扰动出来的数字全是垃圾。
写在最后
Matlab做日前-日内两阶段优化调度,代码本身并不复杂,真正难的是把两阶段的衔接逻辑、不确定性处理和敏感性分析的设计想明白。这套工作做完之后,你手里不仅仅是一个能跑的调度模型,更是一套可以反复使用的分析工具——换一组数据、换一个系统配置,马上就能回答“这个系统对哪个参数最敏感”“储能扩容/加机组对成本改善多大”这类运营方最关心的问题。
我自己的习惯是:做完敏感性分析,永远先看弹性系数最大的那个参数,然后针对它做更细致的场景测试。调度模型的用户(调度员、运营经理)不会关心你的优化算法多巧妙,但他们一定关心“明天要是光伏突然少了30%,我要做什么准备”。敏感性分析给你的,正是这个问题的定量答案。
这套代码和框架拿过去用,多跑几组数据,你大概率会发现自己系统里最值得优化的环节和最初设想的不一样——这是正常现象,也是做敏感性分析最大的价值。
