☰
计及新能源不确定性的综合能源系统协同优化Matlab实现与场景削减
2026/10/14 7:50:43 网站建设 项目流程

我最近在做一个“计及新能源出力不确定性的电气设备综合能源系统协同优化”项目,用Matlab从头搭了一套优化框架。这个题目听起来绕,其实说白了就是在电、气、热、冷这些能源耦合在一起的系统里,把风电、光伏这类出力波动很大的新能源考虑进去之后,再去协调各种设备怎么运行,才能让整体费用最低、又保证供电可靠。如果你也在做综合能源系统、微电网或者园区能源调度的研究,或者刚拿到类似课题想快速跑通一个可复现的算例,这篇文章应该能帮上忙,我会把建模思路、不确定性处理方式、代码实现结构和调试时踩过的坑都盘一遍。

1. 项目整体设计与思路拆解

1.1 综合能源系统协同优化到底在解决什么问题

综合能源系统不是简单的“光伏+储能”,而是把电力网络、天然气网络、热力网络甚至冷力网络耦合在一起。比如一台燃气轮机,它烧天然气发电,同时余热可以回收给吸收式制冷机供冷,也可以经换热器供热。电、气、热三种能量流在设备层面产生强耦合,所以就不能再像传统电力调度那样只盯着有功功率平衡,必须把多个能源载体放在一个模型里同时决策,这就是“协同优化”的核心。

协同优化天然是一个多时段决策问题。通常以1小时为步长,调度周期取24小时,每个时段里所有设备的出力、储能充放功率、从电网购电功率、从气网购气流量都要确定下来。目标函数是运行成本最小化,包括购电成本、购气成本、设备启停成本、弃风弃光惩罚等。约束包括每个节点的功率平衡、设备出力上下限、爬坡约束、储能荷电状态连续性、能量网络传输容量等。这里最难处理的不是设备数量多,而是新能源出力的随机性——风电光伏的预测曲线不可能完全准确,如果直接把预测值当成真实值去做优化,得到的调度方案在实际运行中很可能是不可行的,或者需要频繁调整。

1.2 为什么必须计及不确定性

我在项目初期做过对比:用确定性模型(风电光伏取预测值)优化出来的总成本看起来很低,但把优化得到的设备出力放到50个随机场景里做模拟,大概有30%的时段会出现弃风或者切负荷。这意味着确定性最优解是一个“纸面最优”,实际运行要不断补买电力或削减负荷,最终成本反而更高。

处理不确定性的主流思路有三类:随机规划(场景法)、鲁棒优化、分布鲁棒优化。场景法最直观,用多个可能的风光出力场景来逼近真实分布,然后优化期望成本;鲁棒优化考虑最坏情况,得到的解更保守,成本会高一些;分布鲁棒优化介于两者之间,但建模复杂度高。我选用的是场景法加场景削减,原因很简单:Matlab配合Yalmip工具箱,建模方便,而且场景法的结果容易解释——每一项成本都能追溯到具体的场景概率,审稿人和导师都爱看这种结果。如果你要发高水平论文,可以考虑加一层分布鲁棒约束,但工程复现时场景法已经足够说明问题。

1.3 系统架构与设备选型

我的算例参考了一个典型的园区综合能源系统,包含风力发电、光伏发电、燃气轮机、燃气锅炉、电锅炉、吸收式制冷机、电制冷机、储电、储气、储热和储冷设备。电网和气网作为外部能源输入,负荷包括电负荷、热负荷、冷负荷和气负荷(这里气负荷指直接供给燃气用户的量)。所有设备通过一个能量母线网络耦合,母线节点上满足能量平衡即可,不需要详细建模交流潮流,这样问题规模可控,也能突出协同优化的核心。

设备模型基本是线性化的。燃气轮机的发电效率和气耗率取常数,发电量加上余热回收量构成热电联产特性;燃气锅炉只产热;电锅炉产热;电制冷机产冷;吸收式制冷机利用余热产冷;储能设备用简单的能量平衡方程描述,储能的充放效率分方向取常数,荷电状态保持在上下限之间。这样的模型在工程上足够用,也比纯黑箱模拟更透明。

2. 新能源出力不确定性建模与场景生成

2.1 如何生成风光出力场景

场景法第一步是把风光出力的预测误差统计特性转化成多个场景。我在Matlab里用的是蒙特卡洛抽样的思路:先给每个时段的风电、光伏出力设定一个预测值,假设实际出力在预测值附近服从截断正态分布或Beta分布,然后用拉丁超立方抽样生成大量样本,最后用场景削减算法挑出有代表性的少量场景。

具体操作是这样的:风电出力在额定容量范围内,一般可以用Weibull分布描述风速,再通过风机功率曲线折算到出力;光伏出力则与光照强度相关,光照强度通常用Beta分布建模。但在做日尺度调度时,我们手里往往已经有一份预测曲线和预测误差的统计参数(比如预测误差标准差占预测值的15%),这时更简单的做法就是围绕预测曲线加误差扰动。我用normrnd生成误差,再截断到[0, 额定容量]区间,每个时段独立抽样。初始场景数量取500个,削减到10个左右,既保留随机特征,又不至于让优化模型大到无法求解。

场景削减我用的是同步回代削减算法,核心思路是不断合并距离最近的场景对,把被削减场景的概率累加到最近保留场景上,直到场景数量满足要求。Matlab实现这个算法不需要复杂工具包,自己写个循环就行,距离度量我用的是每个时段出力的欧氏距离,外加一个权重系数来平衡风电和光伏的幅值差异。

2.2 场景削减的Matlab实现要点

场景削减是很容易踩坑的地方,我一开始直接调用kmeans聚类,虽然速度快,但聚类出来的场景无法保留时序相关性,而且概率赋值不够自然。后来改用同步回代削减,代码逻辑反而更清晰。

下面是我的核心削减函数片段,数据矩阵X每一行是一个场景,每一列对应一个时段,输入np是期望保留的场景数:

function [Xs, ws] = scenario_reduction(X, np) % X: 初始场景矩阵 (Ns x T) % np: 需要保留的场景数 Ns = size(X, 1); active = true(Ns, 1); w = ones(Ns, 1) / Ns; % 等概率场景 while sum(active) > np dmin = inf; % 寻找距离最近的两个场景 idx = find(active); for i = 1:length(idx) for j = i+1:length(idx) d = norm(X(idx(i), :) - X(idx(j), :)); if d < dmin dmin = d; i_min = idx(i); j_min = idx(j); end end end % 合并两个场景, 概率转移到保留的一个 w(i_min) = w(i_min) + w(j_min); active(j_min) = false; end Xs = X(active, :); ws = w(active); end

这里有个细节:如果场景数量大,双重循环计算距离会很慢,可以先算距离矩阵dist = pdist(X),再循环更新有效场景之间的距离。不过500个场景规模不大,直接循环也能接受。另外,每次合并后距离矩阵要更新,我建议在循环外预先算好初始距离矩阵,合并时把被削减场景的行列距离置为无穷大,避免重复计算。

2.3 场景概率的合理性调整

场景削减后,每个场景的概率并不相等。为了保证优化目标函数里的期望成本计算正确,还要做一个概率规范化。这个步骤看起来简单,但很容易被忽略。我处理的方法是:

ws = ws / sum(ws);

同时打印出削减前后的累计概率偏差,确保所有场景的概率总和为1。实际测试中,场景数从500削减到10,期望成本跟500个全场景优化相比差异在2%以内,这个精度完全可以接受。如果你的研究对精度要求极高,可以减少削减数量到20~30个,只是求解时间会成倍增加。

3. 协同优化模型构建与Matlab实现

3.1 目标函数与约束条件的数学表达

优化模型写成标准形式:

目标函数(所有场景下的期望运行成本最小化):

[ \min \sum_{s \in S} \pi_s \left( \sum_{t \in T} [c_{e,t} P_{grid,t,s} + c_{g,t} F_{gas,t,s} + \sum_{u} C_{om,u} P_{u,t,s} + C_{curtail} (P_{wind,cur,t,s}+P_{pv,cur,t,s})] \right) ]

其中P_grid是从电网购电功率,F_gas是购入天然气流量,C_om是设备运行维护成本,C_curtail是弃风弃光惩罚。π_s是场景概率。

等式约束主要是能量平衡:

  • 电平衡:风电实际出力 + 光伏实际出力 + 燃气轮机发电 + 电储能放电 - 电储能充电 + 购电 = 电负荷 + 电锅炉耗电 + 电制冷机耗电。
  • 热平衡:燃气轮机余热回收 + 燃气锅炉产热 + 电锅炉产热 + 储热放热 - 储热充热 = 热负荷。
  • 冷平衡:吸收式制冷机制冷 + 电制冷机制冷 + 储冷放冷 - 储冷充冷 = 冷负荷。
  • 气平衡:购气 = 燃气轮机耗气 + 燃气锅炉耗气 + 储气充放 + 气负荷。

不等式约束就是设备出力上下限、爬坡约束、储能荷电状态上下限、购电购气容量上限。特别注意,风电机光伏的实际出力在场景里已经是确定值,但我们可以设置弃风弃光变量,让它们在极端场景里通过惩罚成本自动找到最优弃置量,这样模型就不会因为场景出力过高而不可行。

3.2 Yalmip工具箱与求解器配置

我在Matlab里用Yalmip建模,求解器选择Cplex或者Gurobi。如果你的机器装了Gurobi,Yalmip的调用代码几乎不变。下面是我常用的配置模板:

% 定义决策变量(以场景s为例) P_grid = sdpvar(T, 1, 'full'); % 购电功率 F_gas = sdpvar(T, 1, 'full'); % 购气流量 P_gt = sdpvar(T, 1, 'full'); % 燃气轮机发电 P_wind_use = sdpvar(T, 1, 'full'); % 风电实际使用 P_pv_use = sdpvar(T, 1, 'full'); % 光伏实际使用 SOC_e = sdpvar(T+1, 1, 'full'); % 储电荷电状态(首尾各多一个状态点) % ... 其他变量同理 % 约束拼接 cons = []; cons = [cons, P_grid >= 0, P_grid <= P_grid_max]; cons = [cons, P_gt >= P_gt_min, P_gt <= P_gt_max]; % ... 能量平衡约束 % 目标函数 obj = sum(c_e .* P_grid * dt) + sum(c_g .* F_gas * dt) + ...; % 求解 ops = sdpsettings('solver', 'gurobi', 'verbose', 2, 'showprogress', 1); sol = optimize(cons, obj, ops);

注意sdpvar的维度:如果不指定第三维参数,默认是方阵。我习惯用full参数明确告诉Yalmip这是一个全变量矩阵,避免不必要的稀疏处理。所有决策变量里,储能荷电状态我习惯定义成T+1维,这样初始和末尾的SOC都能纳入约束,写储能平衡方程时也更自然。

3.3 场景耦合与“非预期性”约束

场景法的一个理论细节是“非预期性”:每个时段的决策只能依赖当前及之前可获得的信息,不能“预见”未来场景。在实际建模时,如果把所有场景的决策变量完全独立,求解出来的方案在每个场景里都能提前知道未来随机情况,这在学术上是不严谨的。不过现实中,我们做的是“两步制”决策或者“开环调度”,通常只优化第一时段的决策,后续时段采用滚动更新。为了简化,我采用了“所有场景共享第一阶段决策”的策略:在第一时段,各场景的决策变量必须相等,后期时段可以不同。实现方式就是加一组场景间的等式约束,比如:

for s_idx = 2:N_scenarios cons = [cons, P_grid(:, 1) == P_grid_s1(:, 1)]; cons = [cons, P_gt(:, 1) == P_gt_s1(:, 1)]; % ... 其他变量同理 end

这个约束保证了在当前时刻的决策不依赖未来场景,又保留了后期调整的灵活性。实际运行中效果很好,调度结果的鲁棒性明显提升。

4. 实操过程与核心环节实现

4.1 数据准备:负荷曲线与新能源预测数据

没有一套好的算例数据,优化代码跑出来也没有说服力。我用的是公开数据集拼出来的典型日场景:电负荷峰值在晚上19~21点,热负荷在凌晨和傍晚出现双峰,冷负荷在中午最高。风电出力通常是夜间大、白天小,光伏出力在中午有峰值。我建议你把这些曲线先画出来,和调度结果对照着看,能最快发现模型有没有问题。

数据用Excel存成CSV,Matlab里用readtable读取。注意要把单位统一,功率统一用MW,天然气用量用kcf/h或者m³/h,成本用$或者¥,只要内部计算一致就行。我在算例里把所有能量都折算成kW和h,成本系数用元/(kW·h)或者元/m³。

4.2 主程序框架:从场景生成到结果输出的完整流程

我把整个流程封装成一个主脚本,方便调参和复现。大概结构如下:

%% 1. 参数初始化 [T, N_scen, dt] = deal(24, 10, 1); load('load_data.mat'); % 负荷曲线 load('forecast_data.mat'); % 风电光伏预测曲线 %% 2. 生成并削减场景 wind_scenarios = generate_scenarios(wind_forecast, wind_error); pv_scenarios = generate_scenarios(pv_forecast, pv_error); [wind_scen_final, pv_scen_final, prob] = cross_scenario_reduction(wind_scenarios, pv_scenarios, N_scen); %% 3. 构建优化模型 [cons, obj, vars] = build_optimization_model(...); %% 4. 求解 sol = optimize(cons, obj, ops); if sol.problem ~= 0 disp('求解失败,请检查模型和求解器'); yalmiperror(sol.problem); return; end %% 5. 结果分析与画图 plot_results(vars, load_data);

主程序里最关键的是数据组织的维度。所有与场景相关的变量,我都用三维以上矩阵存储:第一维是时段,第二维是场景,如果还有设备维度就放第三维。Yalmip对多维sdpvar支持得很好,但要注意索引顺序和数学表达式一致,否则很容易出现维度不匹配错误。

4.3 协同优化代码的具体实现

设备模型代码虽然多,但规律性很强。以储能设备为例,我统一写成如下模板,电储热储冷储气共用:

% 储能通用约束 % SOC(t+1) = SOC(t) * (1 - loss) + eta_ch * P_ch * dt - P_dis / eta_dis * dt for t = 1:T cons = [cons, SOC(t+1) == SOC(t)*(1-loss) + eta_ch*P_ch(t)*dt - P_dis(t)/eta_dis*dt]; cons = [cons, SOC(t+1) >= SOC_min, SOC(t+1) <= SOC_max]; cons = [cons, P_ch(t) >= 0, P_ch(t) <= P_ch_max]; cons = [cons, P_dis(t) >= 0, P_dis(t) <= P_dis_max]; cons = [cons, P_ch(t) + P_dis(t) <= P_max_total]; % 防同时充放 end

这里有个经典坑:如果不约束P_ch + P_dis <= P_max_total,优化器可能同时给储能充电又放电,凭空增大成本或违反平衡。加上这个总功率上限约束后,同时充放自然会被排除。

燃气轮机的热电联产模型:

% 发电出力 -> 气耗 (线性关系) F_gt = (P_gt + P_gt_min) / eta_gt_e * rating_gas; % 余热回收 Q_recover = P_gt * cop_recover; % 余热可以分给热负荷和吸收式制冷 Q_heat = sdpvar(T, 1); Q_cool_abs = sdpvar(T, 1); cons = [cons, Q_heat + Q_cool_abs <= Q_recover];

这里用到一个典型操作:把热电联产的产热量用“上限约束”而不是“等式约束”表达,因为余热可以放散掉,只要不超过上限就行。这样可以避免系统必须耗散余热的强制约束,给优化器更多自由度。

4.4 结果可视化与调度规律分析

跑完模型后,我习惯输出四类图:第一类是各个设备电功率的堆叠图,第二类是热/冷平衡图,第三类是储能SOC曲线,第四类是对比不同场景下的机组出力区间。堆叠图用area函数画最直观,能一眼看出光伏覆盖白天负荷,燃气轮机在晚高峰启动,储能削峰填谷。

我从结果里得到几个典型的调度规律,分享出来供你对照检查自己的模型是否合理:

  • 在风电出力高的时段(凌晨),电锅炉和储电会主动增加用电,既消化了风电,又为白天供热蓄热。
  • 燃气轮机通常只在电负荷高峰和热电联产收益最大的时段运行,因为它气耗成本高,但余热回收价值可观。
  • 储能SOC曲线呈锯齿状,充电高峰通常在电价低谷和新能源出力高峰,放电高峰在电价高峰和负荷晚高峰,这符合基本的“低充高放”经济逻辑。
  • 在所有场景下,弃风弃光量都很小,说明惩罚系数设置合理;如果发现大量弃风弃光,优先检查风电渗透率是否过高或电储能容量不足。

5. 常见问题与排查技巧实录

5.1 求解器报错“Infeasible problem”怎么办

这个问题我遇到太多次了,几乎90%的建模错误都会以不可行告终。排查顺序很重要:先检查能量平衡约束的符号方向,再检查边界约束是否矛盾,最后检查场景数据是否出现负值或超过额定容量。我强烈建议在建模时把每个平衡约束都打印出来核对:

% 调试技巧:临时注释掉部分约束,观察求解器是否返回可行 ops = sdpsettings('solver', 'gurobi', 'verbose', 1, 'showprogress', 1, 'debug', 1); sol = optimize(cons, obj, ops); if sol.problem == 1 % 用Yalmip的推荐方案调试 diagnose(cons, obj, ops); end

Yalmip的diagnose函数会给出冲突约束的最小集合,这一招能帮你快速定位是哪几个约束打架。有一次我找了半小时,最后发现是储热的SOC初值设成了100%但最大容量也只有100,而热负荷从第一个时段就需要放热,导致第一时段无法满足下界约束,把初值改成50%问题立刻消失。

5.2 求解时间过长怎么办

场景数量和变量维度是求解时间的主要因素。10个场景、24个时段、大约15类设备,变量数在102415 = 3600个左右,Gurobi求解通常是秒级完成。如果求解超过几分钟,先看是不是把场景间非预期约束写成了对所有变量强制相等,或者把连续变量意外变成了整数变量。比如P_gt一旦定义成binvar,问题就从LP变成MILP,求解时间可能指数级上升。

另一个常见问题是目标函数里出现了大系数差异:充电成本系数大约是10^(-3)量级,而弃风惩罚可能设成10^4,这种数值差异会让求解器内部出现病态。处理办法是把所有系数都归一化到0.1~100之间,或者给惩罚项单独乘以1e-4后再作为成本项。我在实际项目中就把弃风弃光惩罚从5000改成5,结果求解时间缩短了一半。

5.3 Matlab环境下求解器安装与调用问题

很多刚接触用Matlab做优化的人卡在Yalmip和Gurobi的配置上,这里给几点经验。Yalmip是一个纯Matlab工具箱,下载后把整个文件夹添加到路径下即可,不需要编译。Gurobi需要先安装软件包和许可证,然后Yalmip会自动识别。如果你的机器装了多个求解器,Yalmip默认会挑一个可用的,但X在sdpsettings里最好指定,免得同一个脚本在一台机器上结果不同。

如果你在学校实验室用正版Gurobi,注意Matlab版本兼容性。我用过Matlab 2023b配Gurobi 11,一切正常;但换成Matlab 2025a时出现过找不到gurobi_mex的问题,原因是Gurobi安装目录下matlab文件夹没有添加到Matlab路径。解决办法是在startup.m里加上:

addpath('C:\gurobi1100\win64\matlab');

再验证:

yalmip('solver', 'gurobi');

出现Gurobi is installed就说明配置成功。

5.4 场景削减后结果波动异常

有时候削减后的期望成本比全场景优化还低,这看起来反常,其实是削减过程去掉了高成本的极端场景,导致期望成本下降。如果发现这种情况,不要急着怀疑算法,先看削减后的场景概率分布:是不是只剩下一片“乐观”场景,把高风电场景都留下了。解决办法是调整距离度量时的权重,给风电误差更大的权重,或者在削减前把场景概率按原始误差方向重采样。我实践下来最稳妥的方案是用“管式距离”而不是欧氏距离,也就是只关注少数关键时段的偏差,这样保留的场景能覆盖更多极端情形。

6. 一些实操心得与扩展建议

项目做完后最大的体会是,综合能源系统协同优化的成败并不完全取决于算法多么花哨,而在于对物理约束的理解和数据质量的控制。场景法虽然理论上比确定性模型复杂,但代码实现难度并没有增加多少,只要会用sdpvar和Yalmip,整个建模过程比手写拉格朗日对偶简单太多。

如果你想把这套代码扩展到自己的研究里,我建议从以下几个方向入手:

  • 把单目标运行成本扩展到多目标(成本+碳排放),只需要在目标函数里加一个碳排放项,权重系数做敏感性分析。
  • 把场景法替换成分布鲁棒优化,使用Yalmip的uncertain命令定义模糊集,核心代码改动集中在场景概率部分。
  • 把设备线性模型替换成分段线性模型,用binary变量和big-M约束实现,但要注意整数变量数量上升对求解速度的影响。
  • 把调度结果接入滚动时域控制,每个时段重新优化未来24小时,这也是工程上最常用的实时调度方案。

最后分享一个小技巧:跑完优化后,记得把每类设备的总成本拆开看,而不是只看总成本。我曾经遇到过燃气轮机成本占比异常高,一排查发现是气耗率参数那边把单位从m³/h写成了kg/h,导致成本放大了一倍多。这种数据单位错误通常隐蔽在结果图里,画堆叠图时多看几眼各个设备的量级,就能及时发现明显不合理的地方。反正我自己现在跑完第一件事就是先看三个数:总成本、弃风率、储能不能越限,这几个数正常,模型才敢说基本靠谱。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询