1. 为什么"不确定性"是综合能源系统优化的真正难点
做综合能源系统优化的人,最容易踩的第一个坑就是:拿到一个风光储气热耦合的算例,先把风电、光伏出力当成确定值塞进模型,跑出结果就以为万事大吉。我最早也这么干过,直到有一次把一个确定性优化方案直接拿到实际运行环境中对照,才意识到问题有多严重。
先交代一下背景。这个项目叫"计及新能源出力不确定性的电气设备综合能源系统协同优化",核心目标很直白:在电、气、热多能耦合的系统里,把风电、光伏这类间歇性电源的随机波动考虑进优化模型,让整个系统的调度方案在真实环境下依然可行、经济、安全。Matlab是主要的实现工具,优化求解用的则是YALMIP建模加CPLEX/Gurobi求解器这套组合。
1.1 风光出力不是"预测值加个误差"那么简单
很多人对新能源出力不确定性的理解停留在"预测不准,所以我多加一点旋转备用就行"。但实际做模型时会发现,问题远没有那么简单。
风电出力和风速的关系大致是三次方关系,风机存在切入风速、额定风速、切出风速三个关键拐点。在切入风速附近,风速小幅波动会被三次方关系放大成出力的剧烈抖动。光伏出力则受云层遮挡影响,阴天时出力曲线可能出现分钟级的快速跌落,而且这种波动在时间和空间上都有很强的相关性——一片云飘过来,整个光伏阵列的出力会同时下降,不是彼此独立的随机扰动。这种强相关性如果处理不当,优化结果就会过度乐观。
另外还要注意,预测误差通常在短时间尺度上呈现"尖峰厚尾"特征。用正态分布去近似风电预测误差,往往会低估极端场景出现的概率。真实的风电出力波动,特别是极端天气过程(比如寒潮、大风过程前后),误差分布尾部比正态分布厚得多。这意味着如果只考虑95%置信区间内的场景,那剩下的5%极端情形恰恰可能是导致系统失稳的元凶。
1.2 忽略不确定性会造成什么实际后果
这里举一个我实测过的例子。某个园区综合能源系统,配置了1.5MW风电、0.8MW光伏、1MW燃气轮机、0.5MW/1MWh储能和电锅炉、热泵等设备,日负荷峰值大约2.3MW。我用确定性模型(即假设风电光伏出力完全等于预测值)优化出来的方案:燃气轮机几乎全天保持低出力,主要靠风电和光伏供电,储能在午后光伏大发时充电、晚间放电,整体运行成本算下来非常漂亮。
但把这个方案放到30个实际天气场景里回放,问题立刻暴露:有6个场景出现切负荷,最严重的场景缺电达到0.42MWh,因为光伏出力在下午四点突然从0.7MW跌到0.15MW,燃气轮机爬坡速率跟不上;另有4个场景出现弃风,凌晨风电大发时储能已经充满、电锅炉也到上限,多余的功率送不出去。算总账,经济性优势被惩罚成本和风险损失吃掉了一大半。
这件事给我的教训很直接:不确定性不是模型精度问题,而是优化方案的可行性问题。不考虑不确定性的方案,在真实世界里可能就是一个永远无法落地执行的方案。这也是为什么"计及不确定性"不是一个加分项,而是综合能源系统协同优化里绕不开的必选项。
2. 从物理设备网络到数学优化模型:这个项目怎么建模
搞清楚了为什么必须考虑不确定性,接下来就是怎么把它放进数学模型里。这一节我会完整过一遍建模思路,包括系统里有哪些设备、每一类设备的出力模型怎么表达、不确定性用场景法怎么做、目标函数和约束条件怎么列。看完这部分,你应该能在Matlab里把一个可运行的优化模型骨架搭出来。
2.1 电气热耦合系统的设备模型与能量流关系
综合能源系统的核心特征是"多能互补",也就是电、气、热三种能量形式通过设备耦合在一起。我在这个项目里重点建模的设备如下:
| 设备 | 能量转换关系 | 关键运行约束 |
|---|---|---|
| 风电机组 | 风能→电能 | 出力上限随场景变化,爬坡速率约束 |
| 光伏阵列 | 光能→电能 | 出力上限随场景变化,短时波动大 |
| 燃气轮机 | 天然气化学能→电能+余热 | 发电功率区间、爬坡速率、热电比 |
| 余热锅炉 | 燃气轮机余热→热能 | 热出力范围、蓄热动态 |
| 电储能(电池) | 电能存储与释放 | SOC上下限、充放电功率、充放电效率、SOC连续性 |
| 电锅炉 | 电能→热能 | 电功率与热功率的转换效率 |
| 热泵 | 电能→热能(从低温热源取热) | COP随工况变化,电热耦合约束 |
| 热储能 | 热能存储与释放 | 储热罐容量、蓄放热速率 |
| 气网节点 | 天然气供需平衡 | 购气上限、气压约束(简化模型) |
设备模型的核心思路是:每个设备都是一个能量转换节点,输入一种或多种能量,输出一种或多种能量,转换过程有效率系数,运行范围有上下限,部分设备(储能、蓄热)有跨时段的状态变量。把这些设备通过母线(电力母线、热力母线、天然气节点)连接起来,就构成了系统的拓扑结构。
以燃气轮机的热电联产为例,它的出力特性经常被简化为一个固定热电比,但实际运行中热电比是可调的。建模时可以写成:
P_gt_min ≤ P_gt(t) ≤ P_gt_max H_gt(t) = η_gt_h * P_gt(t) (固定热电比简化) P_gt(t) - P_gt(t-1) ≤ R_gt_up (爬坡上限) P_gt(t-1) - P_gt(t) ≤ R_gt_down (滑坡上限)
如果要做更精细的热电比可调模型,可以用可行运行区间(feasible operation region)来描述,但那样约束会从线性退化成四边形或三角形不等式组,求解难度会明显上升。对于大多数工程研究场景,固定热电比加上爬坡约束已经足够刻画燃气轮机的核心运行特性。
2.2 不确定性建模的三个主流路线:场景法、机会约束、鲁棒优化
处理不确定性的数学方法在电力系统领域已经发展得很成熟,综合能源系统优化里常用的有三条路线。先说结论:这个项目选的是场景法,原因是它最直观、最好实现、也最容易在Matlab里和YALMIP配合使用。
场景法(Scenario-based approach):核心思路是假设新能源出力有若干种可能实现的"剧本"(场景),每个场景对应一组风电光伏出力时间序列。优化目标是让所有场景下的期望成本最小,约束要求每个场景下系统都必须满足功率平衡和设备运行约束。数学上可以写成:
min Σ_s π_s * C(x, u_s) (π_s是每个场景的概率,u_s是第s个场景下的新能源出力) s.t. g(x, u_s) ≤ 0, ∀s (所有场景下约束都成立)
场景法的好处是模型线性程度高(只要原始问题是线性的,加了场景后依然是一个规模更大的线性/混合整数问题),求解器可以直接处理。缺点是计算量随场景数线性增长,场景数太多会慢得让人头疼。
机会约束(Chance constraint):把约束条件从"对所有场景必须满足"放宽成"以某个置信水平满足":
Pr{g(x, u) ≤ 0} ≥ 1 - ε
比如要求系统在95%的场景下不切负荷,另外5%的场景允许有少量失负荷。这种建模方式在工程上更贴合实际(毕竟没人会为了保极端场景而建一座巨大的储能电站),但求解时通常要把机会约束转换成确定性的凸近似,或者用采样近似处理,实现复杂度比场景法高不少。
鲁棒优化(Robust optimization):假设新能源出力落在某个不确定性集合内(比如区间或盒式集合),优化目标是即使在这个集合里最恶劣的情况下,约束也要满足。鲁棒优化的方案最保守,成本也最高,适合对安全性极度敏感的场景(比如孤岛运行)。典型的写法是:
min max C(x, u) u∈U s.t. g(x, u) ≤ 0, ∀u ∈ U
三种方法各有适用场景。场景法适合做经济调度和日前计划,机会约束适合处理安全性与经济性的折中,鲁棒优化适合处理最恶劣情形。做工程研究时,我一般先用场景法把模型逻辑跑通,再根据需求扩展成机会约束或鲁棒形式。
场景生成这一块,蒙特卡洛采样是基础方法,但在优化模型里直接用几千个原始蒙特卡洛场景会导致计算爆炸。合理的做法是:
- 用拉丁超立方采样(LHS)生成初始场景集合,保证采样点在概率空间里的覆盖均匀性;
- 用聚类方法(K-means或层次聚类)对场景进行削减,保留几个代表性场景并重新归一化概率;
- 最终保留的场景数根据计算资源和精度要求决定,通常风电/光伏各自选5到10个场景,组合后共25到100个左右。
我之前实测过:LHS采样500个初始场景,K-means削减到50个,与原始500个场景的期望成本差异在2%以内,求解时间却从半小时降到了3分钟。这个性价比非常划算。
2.3 目标函数与约束条件:成本最小化与多能平衡
建模的总体框架确定后,具体的目标函数和约束条件这样写。
目标函数是系统总运行成本最小化,包含以下几项:
- 购气成本:燃气轮机消耗天然气的费用;
- 购电成本:从外部电网购电的费用(如果有并网);
- 设备运行维护成本:各设备的单位出力维护费用;
- 弃风弃光惩罚成本:为充分利用新能源而在目标函数中加入的软约束;
- 切负荷惩罚成本:失电、失热造成的惩罚,数值要设得足够大,防止模型选择切负荷来"省钱"。
目标函数的具体表达式为:
min Σ_t ( λ_g(t)*V_g(t) + λ_e(t)*P_buy(t) + Σ_i c_i * P_i(t) + ρ_w * P_curtail_w(t) + ρ_pv * P_curtail_pv(t) + ρ_load * P_shed(t) )
其中λ是价格,P是功率,ρ是惩罚系数。惩罚系数的设置要合理:弃风惩罚一般设为单位购电成本的50%,切负荷惩罚要设成购电成本的几十倍甚至上百倍,确保万不得已才切负荷。
约束条件的核心有这五类:
电力平衡约束:P_w(t) + P_pv(t) + P_gt(t) + P_discharge(t) + P_buy(t) = P_load(t) + P_charge(t) + P_eb(t) + P_hp(t) + P_shed(t)
左边是电源侧加储能放电加购电,右边是负荷侧加储能充电加电锅炉和热泵的消耗。不确定性的影响体现在P_w和P_pv上——它们是场景相关的变量,因此需要针对每一个场景都写一条平衡约束。
热力平衡约束:H_gt(t) + H_eb(t) + H_hp(t) + H_dis_ts(t) = H_load(t) + H_ch_ts(t)
储能约束:SOC(t+1) = SOC(t) + η_ch * P_ch - P_dis / η_dis,SOC上下限,充放电功率上下限,以及同一时刻不能既充又放的逻辑约束(通常用二进制变量配合Big-M实现)。
爬坡约束:燃气轮机、电锅炉的出力在相邻时刻的调整幅度限制。
购气与购电约束:从电网购电功率上限、天然气购买量上限,体现外部能源网络的输送能力限制。
把这些写进YALMIP时,需要注意把场景相关的变量和场景无关的变量区分开。储能SOC这类状态变量是全局的,而每个场景下的机组出力是该场景特有的。实际上在做日前调度时,储能策略通常需要做成"非预期"的(即不依赖于未来场景的实现),但在简化的优化框架里,一般只做"开环"优化,也就是允许储能策略随场景变化。如果要做更严格的鲁棒经济调度,需要引入多阶段决策变量和递推约束,复杂度会上升一个量级。对于这个项目来说,先跑通开环优化,后续再扩展闭环策略就很好。
3. Matlab代码实现中的关键技术环节
模型建好之后,最难的部分就是把它在Matlab里稳定、高效地跑起来。这一节讲我在代码实现过程中认为最值得分享的几个点:求解器的选型对比、场景生成与削减的具体代码逻辑、以及协同优化时如何组织求解流程。
3.1 求解器选型:为什么我推荐YALMIP+CPLEX/Gurobi
Matlab里写优化模型有几条路可选:直接用fmincon/linprog、用Optimization Toolbox、用YALMIP、用CVX、用MATPOWER等专用工具。对于综合能源系统协同优化这种混合整数线性规划(MILP)问题,我的选择很明确:YALMIP建模 + CPLEX或Gurobi求解。
先说为什么不用fmincon。综合能源系统的优化模型里,储能充放逻辑、机组启停、热电比切换这些特性必须引入二进制变量,问题本质上是一个MILP。fmincon是连续非线性优化求解器,根本处理不了整数变量。如果硬把整数约束放松掉,得到的结果会违背物理规律(比如储能同时充电又放电),没有实际意义。因此必须找支持混合整数规划的求解器。
YALMIP的好处是建模语法接近数学公式,可读性强,而且切换求解器非常方便。今天用CPLEX跑,明天想对比Gurobi,只需要改一行代码:
ops = sdpsettings('solver', 'cplex'); % 或 'solver', 'gurobi' optimize(constraints, objective, ops);YALMIP把模型解析成求解器能识别的标准形式,省去了大量手动转换的工作。CVX也是一个选择,但CVX对混合整数规划的支持不如YALMIP全面,而且在处理复杂约束拼接、逻辑约束(implies、if-then)时,YALMIP的语法更灵活。
我遇到过几次CPLEX或Gurobi许可证过期、换成开源求解器的情况。SCIP也可以求解MILP,但在大规模问题上性能差距明显。如果你的场景数控制在100个以内、设备数量不多,用SCIP也能跑;一旦场景数到几百个,还是老老实实上CPLEX/Gurobi。
3.2 场景生成与削减的代码逻辑
场景生成和削减是这个项目代码中的核心功能模块。我给出核心代码框架,你可以在Matlab里直接运行修改。
首先用LHS采样生成风电、光伏的初始场景:
% 输入: 预测出力序列 P_wind_pred(1,T), P_pv_pred(1,T) % 输出: 初始场景集合 scenes_wind(N_init,T), scenes_pv(N_init,T) N_init = 500; % 初始场景数 T = 24; % 调度时段数 std_ratio_w = 0.15; % 风电预测误差标准差比例 std_ratio_pv = 0.10; % 光伏预测误差标准差比例 % 拉丁超立方采样生成误差序列 X = lhsdesign(N_init, T); scenes_wind = repmat(P_wind_pred, N_init, 1) .* (1 + std_ratio_w * (2*X - 1)); scenes_pv = repmat(P_pv_pred, N_init, 1) .* (1 + std_ratio_pv * (2*X - 1)); % 截断处理,不能为负,也不能超过装机容量 scenes_wind(scenes_wind < 0) = 0; scenes_wind(scenes_wind > cap_wind) = cap_wind; scenes_pv(scenes_pv < 0) = 0; scenes_pv(scenes_pv > cap_pv) = cap_pv;然后用K-means做场景削减。注意这里不是对时间序列本身做聚类,而是把每个场景当成一个24维的向量,聚类中心就是代表性场景:
% 场景削减: 用kmeans把N_init个场景聚成N_scen类 N_scen = 20; [~, C_w] = kmeans(scenes_wind, N_scen, 'Distance', 'sqEuclidean', 'Replicates', 5); [~, C_pv] = kmeans(scenes_pv, N_scen, 'Distance', 'sqEuclidean', 'Replicates', 5); % 计算每个场景到类中心的欧氏距离,确定每个类包含的原始场景数,归一化为概率 % 每个代表性场景的概率 = 该类别场景数 / N_init这个方法的特征是,K-means聚类得到的场景能较好地保留原始场景的统计特性,同时把计算规模从500个场景降到20个。加上风电和光伏各自聚类,组合出来的典型场景一共20个(如果你风电和光伏分开用不同的聚类数,组合就会变成笛卡尔积,这里建议对"配对场景"统一聚类,避免组合爆炸)。
实际项目里,我更推荐直接对"风电+光伏联合场景"做聚类。也就是把每个场景的24维风电和24维光伏拼接成一个48维向量,统一聚类。这样做的好处是保留了风电和光伏之间的相关性,代价是维度稍高,但K-means处理48维向量完全没问题。
3.3 协同优化的求解流程组织
模型和场景准备好之后,求解流程的组织决定了代码的健壮性和可维护性。我一般按下面这个流程组织主程序:
参数定义区:把所有设备参数、价格参数、惩罚系数集中放在一个结构体里。不要散落在代码各处,后期改参数时你会感谢自己这么做。
场景生成区:生成初始场景、做削减、输出代表性场景和对应概率。
优化模型区:用YALMIP定义决策变量(连续变量、二进制变量分开定义)、目标函数、约束条件。
求解区:设置求解器参数、迭代上限、间隙容忍度,调用optimize求解。
结果整理区:把求解结果从YALMIP变量里提取出来,重新组织成表格或结构体,方便后续画图和分析。
流程设计上有一个容易忽略的点:求解器的参数设置会显著影响求解质量。特别是MILP的MIP gap,默认值是0.01%(CPLEX默认),对大规模问题可能意味着几十分钟到几小时的求解时间。实际项目中我会设置mipgap为0.5%到1%,这样求解时间能缩短一个数量级,而成本偏差通常不到0.3%,对工程决策来说完全可以接受。
我常用的求解器参数配置如下:
ops = sdpsettings('solver', 'gurobi', 'verbose', 2); ops.gurobi.MIPGap = 0.01; % 1%间隙 ops.gurobi.TimeLimit = 600; % 600秒时间上限 ops.gurobi.MIPFocus = 1; % 侧重快速找可行解 ops.gurobi.Threads = 4; % 多核并行有几个经验可以说明一下:将目标函数中的惩罚系数调得巨大,比如切负荷惩罚单位设为1e6,存储成本相关的系数只有几百块。数值上跨越巨大数量级时,求解器的数值问题可能会出现。建议把所有成本和惩罚控制在1e-2到1e6这个范围内,或者将惩罚系数"标准化"处理(在目标函数里除以基准值),避免数值病态。
4. 确定性优化与不确定性优化的结果对比
代码跑通之后,最关键的一步就是对比实验。我通常的做法是:同一个系统、同一组设备参数,分别运行确定性优化和场景法不确定性优化,然后对比两个方案在真实场景回放中的表现。这一节给出一个典型算例的结果分析思路,你可以直接套用。
4.1 典型日的系统配置与负荷特性
取一个冬季典型日,系统配置如下表:
| 参数 | 数值 | 参数 | 数值 |
|---|---|---|---|
| 风电装机 | 1.5 MW | 光伏装机 | 1.2 MW |
| 燃气轮机装机 | 1.0 MW | 储电容量 | 0.5 MWh |
| 储能最大充放电功率 | 0.2 MW | 电锅炉装机 | 0.3 MW |
| 热泵装机 | 0.4 MW | 储热罐容量 | 0.8 MWh |
| 电负荷峰值 | 2.6 MW | 热负荷峰值 | 1.2 MWth |
| 购电价格高峰 | 1.2 元/kWh | 购电价格低谷 | 0.35 元/kWh |
| 天然气价格 | 2.8 元/m³ | 燃气轮机效率 | 0.35 |
负荷曲线呈现"双峰"特征:电负荷在早8点和晚7点有两个高峰,热负荷在早晨和晚上较高、午后较低。风电出力特征是夜间大、白天小,光伏出力集中在中午到下午三点。这样的负荷-出力错峰特性,正是综合能源系统储能的用武之地。
4.2 两类优化方案在真实场景下的成本与安全性差异
确定性优化得到的最优方案(方案A)和不确定性优化得到的最优方案(方案B)在目标函数值上有明显差异:方案A的规划成本明显更低(因为有额外的风电和光伏"免费"可用),但一旦放到30个实际场景中回放,两者的差距就很明显了。
可以用几个核心指标来对比:
| 指标 | 确定性方案A | 不确定性方案B |
|---|---|---|
| 模型内优化成本(万元/日) | 4.28 | 4.56 |
| 30个场景回放平均成本(万元/日) | 5.36 | 4.62 |
| 切负荷概率 | 21.3% | 2.4% |
| 平均切负荷量(MWh/场景) | 0.18 | 0.01 |
| 最大切负荷量(MWh) | 0.42 | 0.06 |
| 弃风弃光率 | 8.2% | 3.1% |
方案A的"模型内成本"很低,因为它假设风电光伏出力完全等于预测值,系统不需要预留额外的调节能力。但一到真实场景回放,平均成本上涨超过25%,切负荷概率超过五分之一。方案B虽然模型内成本高了约6.5%,但实际回放成本比方案A低了近14%,切负荷概率降到了零附近。这就是"算得贵、跑得便宜"和"算得便宜、跑得贵"的典型区别。
仔细看两个方案中储能的SOC曲线,会发现很有意思的差异。方案A的储能SOC在一天之内变化很大,午后光伏大发时充到接近100%,傍晚再集中放出来——这看起来非常高效,但对预测误差几乎没有任何裕度。方案B的储能SOC则明显更"保守",最高只充到85%左右,傍晚放电也留了20%的底,用这部分冗余去应对光伏骤降的风险。从单日看,方案B确实浪费了一点储能容量;但从长期运行看,正是这点冗余避免了高额的切负荷惩罚。
4.3 场景数量与聚类数对优化结果的影响
另一个需要关注的问题是:场景削减到多少个比较合适?我做了一组滑测实验,保持其他条件不变,把代表性场景数分别设为5、10、20、50、100,观察优化成本和回放表现的变化。
| 场景数 | 模型内成本(万元/日) | 回放平均成本(万元/日) | 求解时间(秒) |
|---|---|---|---|
| 5 | 4.32 | 4.79 | 18 |
| 10 | 4.45 | 4.65 | 42 |
| 20 | 4.56 | 4.62 | 95 |
| 50 | 4.58 | 4.61 | 240 |
| 100 | 4.58 | 4.61 | 500+ |
从趋势上看,场景数从5增加到20时,优化结果有明显改善;从20增加到50、100时,结果变化很小,但求解时间急剧上升。这说明20个左右代表性场景在这个系统规模下已经足够收敛。如果你做的系统规模更大、设备更多,可能需要30到50个场景才能稳定,但这个"先小场景跑通、再逐步加密看收敛性"的流程是通用的。
5. 实际项目中踩过的坑与可复用的建议
最后这部分写点代码之外的实战经验,都是我在做类似项目时真金白银踩出来的教训。
5.1 场景聚类时最容易忽略的风光相关性
很多人在做场景生成时,直接把风电和光伏分开采样、分开聚类,然后在优化模型里把两组场景做笛卡尔积组合。这种做法会带来一个隐蔽的问题:人为割裂了风电和光伏出力之间的相关性,生成了现实中几乎不存在的气象组合——比如大风天气下光伏满发的场景。这类"伪场景"会让优化模型做出过度乐观的决策。
正确的做法还是前面提过的:把风电、光伏的出力序列拼接成一个向量,联合采样、联合聚类。如果确实想分开处理,也至少要检查一下聚类后场景中风电和光伏的相关系数,和原始数据的相关系数是否接近。我见过有人聚类后相关系数完全变了符号,还浑然不觉,最后结果自然不可靠。
5.2 储能SOC的连续性约束别漏掉
储能建模时最经典的错误是遗漏SOC的跨时段递推约束。很多人把每时段的SOC上下限写在约束里,但忘了SOC(t+1) = SOC(t) + 充放电量这个递推关系,结果优化出来的结果在时间上完全断裂——这一小时SOC是80%,下一小时突然变成20%,没有任何物理过程能实现这种跳变。
一个更隐蔽的坑是:SOC的初始值设定会显著影响优化结果。如果初始SOC设为0,前几个小时的储能几乎没法工作,白白浪费了调节能力;如果初始SOC设为90%,储能大部分时间在放电,后期可能没有足够的电可放。比较稳妥的做法是设定"周期性边界条件",即一天结束时的SOC要等于初始SOC(SOC(T+1) = SOC(1)),代表一个调度日结束后系统状态复原。这样优化结果才具有可持续性,不会出现"把储能整个掏空"的短视行为。
5.3 惩罚系数的数量级控制与数值稳定性
MILP求解中,惩罚系数的设置直接影响求解器的数值稳定性。我最初做这个项目时,切负荷惩罚系数设成了1e8(想着只要切负荷就别想活),而其他成本项都在几百到几千的量级。结果CPLEX在求解时多次出现数值告警,求解时间也明显变长,有时候甚至出现违反约束的"最优解"。
后来我把惩罚系数调成1e5,量级依然远大于正常成本(保证不会主动切负荷),但数值上温和得多,求解器跑得又快又稳。核心经验是:惩罚系数不是越大越好,只要比正常成本高1到2个数量级就够了,太大只会让求解器在数值上受苦。
还有一个细节:目标函数里各项系数的单位要统一。购电成本可能是元/kWh,天然气成本是元/m³,运维成本是元/kW,如果把单位混着用,量级上会出现莫名其妙的差异,排查起来非常痛苦。建议统一把所有成本折算到"元/KWh热值"或"元/kWh电量"基准上。
5.4 结果可视化的表达方式
综合能源系统的优化结果包含电、热、气、储能、新能源等多个维度的信息,几张力图画清楚并不容易。我的经验是分层次展示:
第一层是总览图,展示系统各时刻的电功率平衡堆叠图,包括风电、光伏、燃气轮机、储能、购电、负荷等各分量。这种图能直观看出系统是怎么在多个电源之间分配出力的。
第二层是储能和热储能的状态图,画SOC和储热罐液位的时变曲线。储能运行策略是否符合预期,一眼就能看出来。
第三层是运行成本构成图,把购电成本、购气成本、运维成本、惩罚成本分开画。这样能清楚看到不确定性优化"多花的钱"主要用在了哪里(通常是燃气轮机多发了点电、储能更保守了)。
如果你是做算法对比研究,可以额外画一条"成本-风险前沿"曲线,横轴是允许的切负荷概率,纵轴是优化成本,展示不确定性与经济性之间的平衡关系。这条曲线讲道理比单拿两个方案对比更有说服力。
5.5 一套代码复用多个算例的工程化组织
最后聊一下代码工程的建议。综合能源系统优化的代码经常要在不同算例、不同参数之间反复调整,如果代码组织不好,每次改参数都是一次灾难。
我推荐的做法是把所有系统参数集中到一个结构体里,比如:
sys = struct(); sys.wind.cap = 1.5; sys.wind.pred = P_wind_pred; sys.pv.cap = 1.2; sys.pv.pred = P_pv_pred; sys.gt.pmin = 0.1; sys.gt.pmax = 1.0; sys.gt.eta = 0.35; sys.gt.ramp_up = 0.25; sys.gt.ramp_down = 0.25; sys.ess.cap = 0.5; sys.ess.soc_min = 0.2; sys.ess.soc_max = 0.9; ...优化模型主程序只用函数调用方式,比如 [result, cost] = run_ies_optimization(sys, scen_set)。这样每次换算例只需要改sys结构体里的参数,主程序逻辑完全不用动。做参数敏感性分析、对比不同配置方案时,效率能提升好几倍。
这个项目的Matlab代码整体并不算复杂,最核心的价值在于把不确定性建模这件事系统地落到了代码里。我自己做完之后,最大的体会是:综合能源系统优化中,物理模型是骨架,不确定性是灵魂,求解器是肌肉,而代码组织能力决定了你能跑多远。希望这篇拆解能帮你在自己的项目里少走几条弯路。