1. 项目背景与问题定位:为什么综合能源系统需要NSGA-II
近几年“双碳”目标推着综合能源系统从概念走向落地,很多高校和设计院都在做园区级冷热电联供的优化调度研究。我接手这个项目时,甲方给的诉求很明确:在一个包含风电、光伏、燃气轮机、燃气锅炉、电储能、蓄热罐的园区里,以全天24小时为调度周期,同时最小化运行成本和碳排放量,并给出每台设备的出力曲线。这个问题的麻烦之处在于目标和约束都很多:成本与碳排放两个目标天然冲突,电热冷三种能量要实时平衡,设备爬坡、容量、储能荷电状态一大堆约束。单目标优化完全hold不住,必须上多目标算法。
NSGA-II(非支配排序遗传算法)就是为这种场景设计的。它通过“非支配排序 + 拥挤度距离”两个核心机制,一次性得到一整条Pareto前沿,而不是像加权法那样只能得到一个妥协解。这一点在工程上非常重要:调度员拿到的不再是一个“死答案”,而是一组可选的运行方案,偏经济还是偏环保,决策者自己权衡。
具体到这个项目,仿真对象是一个典型“电-热-冷”耦合的园区综合能源系统,设备包括燃气轮机(GT)、燃气锅炉(GB)、吸收式制冷机、电制冷机、光伏(PV)、风电(WT)、蓄电池(ESS)和蓄热罐(HST)。调度周期为24小时,步长1小时。目标函数有两个:系统总运行成本和总碳排放量。约束包括能量平衡约束、设备出力上下限约束、爬坡约束、储能SOC约束等。
整套代码我用Matlab R2021a编写,基于NSGA-II算法框架,结合具体设备模型,实现了从问题建模、约束处理到Pareto前沿可视化的完整流程。项目文件结构包括主程序、设备模型函数、约束检查函数和结果绘图脚本。以下是完整的技术拆解。
2. 综合能源系统建模:目标函数、设备模型与约束条件
2.1 优化目标:成本与碳排放的双目标数学表达
综合能源调度首先要把物理问题变成数学问题。本项目采用双目标函数,第一目标是日运行成本最小化,公式如下:
min F1 = sum( C_fuel * (P_GT(t)/η_GT + P_GB(t)/η_GB) / LHV * Price_gas + Price_grid * P_grid_buy(t) - Price_sell * P_grid_sell(t) + C_om_GT * P_GT(t) + C_om_GB * P_GB(t) + C_om_ESS * |P_ESS(t)| + C_om_PV * P_PV(t) + C_om_WT * P_WT(t) )其中燃料成本按天然气低位热值折算,购电成本按分时电价计算,售电收益则体现“余电上网”的经济回报,设备运行维护成本按输出功率线性折算。模型里分时电价分为峰、平、谷三个时段,峰段电价大约是谷段的2.5倍,这是引导储能“谷充峰放”的关键经济信号。
第二目标函数是碳排放总量最小化,碳排放来源包括燃气轮机燃烧天然气产生的直接碳排放、燃气锅炉的直接碳排放,以及外购电能对应的间接碳排放(按电网排放因子折算)。光伏和风电不产生运行碳排放,这也是新能源优先消纳的环保驱动力。
2.2 核心设备建模:关键参数的设定逻辑
设备建模的精细程度直接决定优化结果的可信度。我以燃气轮机为核心设备:其发电效率随负载率变化,我使用分段线性函数近似,额定效率35%,部分负载时效率下降;热电比取1.2,排烟余热进入溴化锂吸收式制冷机和热回收换热器。燃气锅炉则作为热力补充源,额定效率90%,响应速度快但碳排放较高,在Pareto前沿上它往往是“经济解”里才会被大量启用的设备。
储能设备是调度的“调节器”。蓄电池模型采用常见的SOC递推公式:
SOC(t+1) = SOC(t) + (P_char(t) * η_char - P_dis(t) / η_dis) * Δt / E_cap充放电效率分别取0.95和0.95,SOC运行范围限制在[0.1, 0.9],这个区间设定是为了兼顾电池寿命和调节空间。蓄热罐的模型类似,但损耗更大,每小时有2%的热损失,这一细节让蓄热罐倾向于“短时存储、及时释放”,而不是长时间囤热。
电网交互模型包含购电和售电两条路径。购电功率上限设定为园区峰值负荷的1.2倍,售电功率上限为燃气轮机额定功率的60%。订阅电价数据采用某典型北方城市的工商业分时电价:峰段(10:00-15:00、18:00-21:00)1.05元/kWh,平段 0.65元/kWh,谷段(23:00-次日7:00)0.38元/kWh。
2.3 约束条件清单:能量平衡与设备边界
约束是优化问题的骨架,处理不好算法跑出来的结果就是空中楼阁。本项目约束分四类:
一、电力平衡约束。每个时刻光伏、风电、燃气轮机发电、蓄电池放电和购电之和,必须等于电负荷加上电制冷耗电和蓄电池充电功率。这里我允许轻微的功率不平衡(误差限0.01MW),避免过严格约束导致算法难以收敛。
二、热力平衡约束。燃气轮机余热回收、燃气锅炉产热和蓄热罐放热之和,等于热负荷加上吸收式制冷机耗热。热力平衡是纯等式约束,处理上比电力平衡更难,因为热负荷波动通常比电负荷更剧烈。
三、冷力平衡约束。吸收式制冷机产冷与电制冷机产冷之和等于冷负荷。这里有一个设备耦合点:电制冷机消耗电能,吸收式制冷机消耗热能,两种制冷方式的比例由优化算法自行决定,这就天然形成了电-冷和热-冷两个能量网络的耦合。
四、设备运行约束。包括设备出力上下限、爬坡速率约束、储能SOC初末值约束(要求调度周期结束SOC回到0.5附近,保证日内循环可用性)等。燃气轮机的爬坡约束设定为每分钟0.03MW,意味着一个小时内最大出力变化不超过1.8MW,这个约束极大影响了燃气轮机能否快速响应负荷骤变,实际运行中必须考虑。
3. NSGA-II算法核心机制与Matlab实现架构
3.1 非支配排序和拥挤度距离的精髓
NSGA-II的代码实现看起来不难,但真正吃透需要理解两个核心概念的物理意义。
非支配排序解决的是“如何比较两个解的优劣”问题。在多目标问题里,解A优于解B必须同时满足:在所有目标上A不差于B,且至少在一个目标上A严格优于B。如果A在某些目标上优于B,又在其他目标上劣于B,两者就互为非支配解,它们属于同一Pareto层级。算法把种群中不被任何解支配的解划为第一层,然后去掉这些解后继续划分第二层、第三层……层级越靠前,解越接近真实Pareto前沿。
拥挤度距离解决的是“同一层级内如何保持解的多样性”问题。每个个体的拥挤度等于其相邻个体在各目标方向上的归一化距离之和。直观理解就是:如果一个解两侧离它最近的邻居都很远,说明周围区域探索不足,这个解应当被保留以维持解的均匀分布。这也是NSGA-II区别于普通GA的核心——它同时追求收敛性和多样性,防止种群过早集中在单一目标方向。
3.2 编码与决策变量设计:48维实数编码的细节
综合能源调度问题的决策变量是整个调度周期内各设备的出力序列。本项目选择48维实数编码结构:前24维是燃气轮机各时刻发电功率,后24维是蓄电池各时刻的充放电功率(正值放电、负值充电)。为什么不把锅炉、制冷机、储能都放进染色体?因为变量太多会导致搜索空间维度灾难。解码时采用的策略是按能量平衡方程顺序解出其他设备出力:电力平衡推出电制冷机功率,热力平衡推出锅炉出力,冷力平衡校验吸收式制冷机。这是一种“降维解码”策略,通过等式约束自动消去部分变量,只保留真正需要全局搜索的自由度。
种群规模取100,最大进化代数取200,交叉概率0.9,变异概率0.1,交叉分布指数和变异分布指数分别取20和20。这些参数经过反复实验调优:种群太小容易陷入局部Pareto前沿,种群太大会导致单次迭代计算量过大,200代之后Pareto前沿的改善已经非常有限。
3.3 Matlab代码实现的关键函数片段
主程序框架如下:
% NSGA-II 主循环 % 参数设置 pop_size = 100; max_gen = 200; n_var = 48; n_obj = 2; % 初始化种群 population = init_population(pop_size, n_var, data); for gen = 1:max_gen % 计算目标函数值 objectives = evaluate_objectives(population, data); % 非支配排序 fronts = nondominated_sorting(objectives); % 计算拥挤度 crowding = crowding_distance(fronts, objectives); % 选择父代(锦标赛选择) parents = tournament_selection(population, fronts, crowding, pop_size); % 交叉变异 offspring = sbx_crossover(parents, 0.9, 20); offspring = polynomial_mutation(offspring, 0.1, 20); % 合并父代与子代,精英保留 combined_pop = [parents; offspring]; combined_obj = evaluate_objectives(combined_pop, data); [fronts, crowding] = sort_population(combined_pop, combined_obj, pop_size); population = combined_pop(fronts, :); end非支配排序函数我采用快速排序方法,复杂度O(N²),种群100时性能完全够用。拥挤度计算时注意一个细节:边界个体的拥挤度需要设为无穷大,保证Pareto两端的解必定被保留,否则前沿两端容易被截断。
约束处理采用惩罚函数法,惩罚系数设为1000。这里有个工程经验:惩罚系数太小约束违反的个体容易混入前沿,太大则会导致可行域附近搜索效率骤降。我测试了从100到10000的多个量级,最终锁定1000——既能将不可行解严格排挤出前沿,又不至于让算法在可行域边界上“走不动”。
3.4 为何选NSGA-II而不是加权和法或粒子群
很多人在做综合能源调度时习惯用线性加权把两个目标合并成单目标,再用线性规划或粒子群求解。这个做法有一个致命缺陷:经济性和碳排放量纲不同,权重系数几乎无法客观确定。而且当Pareto前沿是非凸形状时,加权法无论如何调整权重都无法覆盖凹区域的解,会漏掉一大片有价值的运行方案。
NSGA-II直接面向多目标,一次性均匀生成整个Pareto前沿。和粒子群的多目标版本MOPSO相比,NSGA-II的非支配排序和拥挤度机制在处理离散变量、不可微目标时更稳定,代码框架也更清晰。具体到这个项目的设备启停边界问题,NSGA-II的交叉变异操作比PSO的速度-位置更新机制更容易保留设备出力的可行域信息。
4. 算例仿真条件与关键运行参数设置
4.1 典型日负荷与新能源出力曲线设定
仿真选用夏季典型日数据。电负荷曲线呈两峰一谷形态:早高峰出现在9:00-12:00约1.4MW,晚高峰出现在18:00-22:00约1.6MW,凌晨在0.6MW附近。热负荷主要来自生活热水,峰值出现在清晨和晚间,最高0.8MW。冷负荷与电负荷高相关,正午时达到1.2MW峰值。光伏出力在11:00-14:00达到峰值0.8MW,风电夜间出力较高、白天较低,最大0.4MW。这些数据都取自园区历史运行数据,是优化结果的现实基础。
4.2 天然气价格、电价与排放因子的设定
天然气价格取2.8元/立方米,天然气低位热值取9.7kWh/立方米。电网购电排放因子取0.785kg/kWh,天然气燃烧排放因子取0.198kg/kWh。这个设定决定了Pareto前沿的走向:当天然气价格相对电价的比值在合理区间内时,燃气轮机会在电价峰段满发,既降低购电成本又利用余热供冷供热,实现“热电联供”的经济与环境双收益。
4.3 算法迭代收敛曲线的判读
从收敛曲线看,算法前50代Pareto前沿急剧下降,这是从随机初始种群快速搜索可行区域的过程;50到120代前沿逐渐逼近真实前沿;150代以后改善幅度已经很小。用超体积指标(Hypervolume)定量评估,200代相比150代提升不到1.5%。因此把最大代数设定为200是一个计算精度与耗时之间的均衡选择。单次运行在配置为i5-10400、16GB内存的机器上耗时约180秒。
5. 典型结果解读:Pareto前沿与最优调度方案分析
5.1 Pareto前沿的经济-碳排放权衡关系
运行得到的Pareto前沿是一条平滑下降的曲线,横轴是日运行成本(元),纵轴是碳排放量(kg)。曲线呈现明显的凸形特征。曲线左端是“低碳方案”:燃气锅炉基本不启用,大量从电网购电配套新能源发电;右端是“经济方案”:燃气轮机几乎全天高负载运行,天然气热电联供替代电网购电。两个极端方案的成本差异约12%,碳排放差异约22%,说明在现有价格机制下,环保目标需要以一定经济代价换取,这种量化结论是单目标优化给不了的。
5.2 折中解与经济解的设备出力对比
选取Pareto前沿的折中解(距离原点最近点)做详细分析。折中解下燃气轮机一天运行16小时,峰段和平时段基本满发,谷段因为电价太低而降低出力、增加购电。蓄电池形成了“谷充峰放”的日内循环:23:00-5:00充电,10:00-15:00和18:00-21:00放电,日循环净收益约320元。蓄热罐在热负荷峰值前蓄热,峰值时放热,有效削减了燃气锅炉的出力峰值。
对比经济解,经济解燃气轮机运行时间延长到20小时,几乎是全天满发,碳排放比折中解增加8%,成本却只降低4%。这说明在当前电价和天然气价的比值下,燃气轮机接近满发是经济上最优的策略,但环保侧希望限制它的运行时间。这种矛盾只有在Pareto前沿上才能清晰呈现。
5.3 储能系统的日内调度行为分析
储能的充放电行为受到分时电价的强驱动。蓄电站在谷时电价0.38元/kWh时充电,峰时电价1.05元/kWh时放电,单次循环的价差收益可观。值得注意的是,在低碳方案中,蓄电池的部分充电量来自光伏午间出力,实现了“光伏转移”,降低了碳排放。蓄热罐的行为则更多受热负荷驱动,在热负荷低谷蓄热、高峰放热。运行中我还发现一个现象,蓄热罐的2%热损失导致它在Pareto前沿的“低碳端”使用频率低于“经济端”——经济方案更愿意用蓄热罐降低锅炉燃料消耗,而低碳方案更倾向于直接限热。
5.4 24小时电力与热力平衡验证
对折中解逐时检验电力平衡:所有时刻电功率不平衡量绝对值均小于0.01MW,验证了等式约束的有效性。热力平衡在早晨和傍晚的两个热负荷峰值时刻出现了最紧的约束边界——燃气轮机余热回收能力有限,蓄热罐放热达到上限,此时锅炉被强制启动,而锅炉的碳排放惩罚在低碳方案中被算法以降低冷负荷供给的方式规避,博弈特征非常明显。
6. 常见问题与Matlab调试经验实录
6.1 种群过早收敛陷入局部前沿
第一期代码调试时,我发现50代后前沿基本不再发生变化,检查发现是变异概率偏低造成遗传多样性丢失。将变异概率从0.05提高到0.1,并将变异分布指数从10调整到20,后代解的分散性明显改善,前沿的覆盖范围显著扩大。另外一个隐藏问题是选择压力过大:锦标赛选择的参赛个体数从2提高到3虽然加速收敛,但同时也损失了多样性。最终恢复为参赛个体数2。
6.2 约束处理不当导致大量不可行解
初始版本采用静态惩罚函数,发现部分解在Pareto前沿上虽然目标值很好,但能量平衡误差达到0.1MW——这意味着可行性检验不严格,不可行解混入了前沿。排查发现原因是惩罚系数过小(初始设定为100)。将惩罚系数从100提高到1000后,前沿上的不可行解比例从7%降到0。这里有个经验,判断是否可行不能只看目标函数值,必须单独写一个可行性验证函数逐项检查所有约束。前期调参时这个验证函数是调试效率的放大器。
6.3 乱码与变量覆盖:Matlab脚本管理的两个坑
这是两个容易被忽视但实际很常见的坑。一是Matlab 2021a之前的版本在读取含中文注释的.m文件时容易显示乱码,解决办法是统一使用UTF-8编码并在文件头部加入feature('DefaultCharacterSet','UTF-8')。二是工作区变量覆盖问题:主脚本中定义的data结构体如果不小心在某个子函数中被赋值为临时变量,后续所有读取负荷数据的部分都会出错。我踩过一次后,给所有全局数据统一加前缀g_,比如g_data、g_price,有效避免了命名空间冲突。
6.4 计算耗时过长的优化技巧
11800的计算耗时主要分布在全球目标函数评估上(占比约85%),这是Matlab处理循环的天生短板。我做了三个优化:一是利用向量化运算替代循环,48维变量一次解码;二是对功耗算中不变的负荷、价格等参数预计算并缓存,避免每次解码重复计算;三是对设备模型采用内置函数fcn而不是eval调用,减少解释执行开销。优化后单次运行耗时从320秒降到180秒,提升幅度显著。后续如果用户对象大到小时级8760点,还需要考虑并行化或改用C编译的MEX。
6.5 实用技巧:Pareto前沿均匀性的人工评估
拥挤度距离机制保证前沿均匀分布,但Matlab默认的plot连线有时会误导对分布均匀性的判断。我通常在绘制Pareto前沿时叠加scatter散点图,直观观察点间距。如果发现某一段明显空档,可以适当增大该区域的交叉变异强度或重新设置随机种子。另外,Pareto前沿上的“台阶”往往对应储能状态的定性跳变,这些台阶是解释方案差异的关键节点,报告中可以重点分析。
7. 代码扩展方向与后续改进建议
7.1 扩展到含氢储能与电转气的综合能源系统
当前代码框架的设备模型是模块化的,增加新设备只需添加对应的目标函数项和约束。我最近在尝试加入电解槽与氢燃料电池,氢气作为一种长时间尺度的储能介质,与蓄电池形成互补。每小时氢平衡方程成为新的等式约束,SOC状态则需要增加氢气罐气压上下限约束。这种扩展在NSGA-II框架下是自然的,但决策变量会从48维升到60维左右,收敛速度会有所下降,需要相应调整进化代数。
7.2 考虑需求响应的柔性负荷调度
现行模型中所有负荷都是刚性给定。接下来一个潜力很大的方向是把部分工业和商业负荷变成可平移负荷:用户在电价峰段削减用电,平段恢复,实现负荷曲线整形。这个扩展需要在编码中增加负荷平移量变量并修改电力平衡约束形式,NSGA-II同样可以处理。
7.3 与鲁棒优化或强化学习的结合
NSGA-II处理的是确定性场景,而实际运行面临风光出力和负荷的不确定性。常见的改进思路是将场景集引入目标函数计算中——用多个典型场景的期望成本和期望碳排放替代单场景目标值,这在国际文献中叫随机多目标优化。另外还可以与强化学习结合:用NSGA-II离线生成最优调度策略库,在线运行依据当前状态匹配,匹配效率高且保留了多目标权衡能力。
8. 实操总结与个人体会
整段调试下来,我的核心体会是:NSGA-II在综合能源调度场景中的价值不只是算法本身,更在于如何把一个工程问题转化成算法能理解的数学模型。设备建模的粒度(比如燃气轮机效率随负载率变化)、约束的放松程度(等效平衡允许的误差)、编码方式的选择(降维解码策略),这些工程细节对最终结果的影响往往大于算法参数的微调。
我建议新人入门时先跑通一个2设备3时段的简单案例,把非支配排序和拥挤度计算在Excel里手工演算一遍,彻底理解每一步的意义,再扩展到24小时的实际案例。盲目套完整代码而不理解内部逻辑,遇到问题时会非常被动。
关于Pareto前沿的选择,我提供了一个交互式脚本,点击前沿上的点即可查看对应的52个决策变量和24小时出力曲线,方便工程人员直接选择调度方案。这个脚本当时是在项目汇报演示中最实用的工具。
最后分享一个细节:240小时调度问题中,燃气轮机启停状态是0-1变量,NSGA-II的连续实数编码无法直接表达这个离散状态。我采取了连续化策略——用输出功率高于某个小阈值的状态视为运行,低于阈值视为停机,并在约束中限制启停次数。这个近似处理在工程上是合理的,功率低到1%以下时的燃气轮机热效率和排放特性本身误差就很大。如果读者对精确的机组组合问题感兴趣,我需要明确说明当前代码不适用于机组启停状态的满自由度优化,那是另一个方向的课题。