做综合能源系统仿真的朋友,对粒子群算法在冷热电联供(CCHP)系统运行优化里的应用应该都不陌生。这个项目用Matlab实现了一套完整的粒子群算法冷热电联合综合能源系统运行优化模型,针对现有CCHP系统在实际负荷波动下运行成本偏高、设备出力分配不合理的问题,以24小时调度周期为对象,在分时电价和天然气价格机制下求解各设备的出力计划,给出最优运行成本。适合正在做综合能源、电力系统优化课题的学生,以及准备用智能算法做微电网/虚拟电厂调度优化的工程师参考。
说实话,这类课题如果只是把现成代码跑通,其实不难;难的是把设备模型、约束条件、优化算法三者真正融到一起。项目里我把重点放在设备建模、粒子编码、罚函数处理和PSO收敛性调节上,用的工具是Matlab。下面按我的实际搭建过程逐块拆开讲,包括各个关键模块怎么设计、代码怎么写、踩过哪些坑。
1. 项目整体设计与思路拆解
1.1 冷热电联供系统的基本架构与能量流动
CCHP系统的核心逻辑是先发电、再回收余热、再用余热制冷或供热。典型的架构是:燃气轮机燃烧天然气发电,发电产生的高温烟气进入余热回收装置,回收的热量一部分直接供给热负荷,另一部分进入吸收式制冷机,驱动其制冷;当余热不足时,燃气锅炉补充供热;当电制冷效率更高或电价较低时,电空调参与制冷。加上蓄能装置后,整个系统的供能路径就变成了电、热、冷三条母线,设备之间通过这三条母线交叉耦合。
这个项目里我设定了六类主要设备:燃气轮机(GT)、余热回收换热器(HRSG)、吸收式制冷机(AC)、电空调(EC)、燃气锅炉(GB)、蓄热/蓄冷单元。电母线接入的是燃气轮机发电和市网购电,热母线由余热回收和燃气锅炉提供热量,冷母线由吸收式制冷机和电空调共同制冷。运行优化的任务,就是在满足逐时电、热、冷负荷的前提下,决定每一台设备每个小时的出力,使总运行成本最低。
搞清能量流动后再建模就顺了。一开始我建议先从只含燃气轮机、余热锅炉、吸收式制冷机、电制冷机这个简化版开始,把能量平衡跑通,再逐步加入燃气锅炉和储能。否则一上来把所有设备堆进去,结果变量多、约束多,查错时非常痛苦。
1.2 为什么选择粒子群算法而不是线性规划或遗传算法
冷热电联供运行优化在数学上是一个含等式约束、不等式约束的动态优化问题。目标函数里有购电费用和购气费用,购电电价是分时变化的,设备效率又常常是非线性的,部分设备还有启停状态和出力上下限,因此整个模型是非线性、带耦合约束的。
线性规划和混合整数线性规划在处理小规模、线性化问题时很干净,但模型稍微复杂一点,比如要考虑燃气轮机效率随负载变化、储能荷电状态与时段之间的耦合,就不得不做大量线性化近似,建模周期长,表达式也容易失控。遗传算法也能做,但编码、交叉、变异算子写起来比PSO繁琐,参数多了之后调参成本也高。
粒子群算法的优势在于实现极其简单,核心就是“速度更新+位置更新”两个公式;也不需要计算梯度,适合这类非凸、不可导的优化问题。Matlab里写PSO主循环几十行就能搞定,配合向量化的适应度函数计算,运行速度完全可以接受。当然,PSO本身有早熟收敛、容易陷入局部最优的缺点,这个我后面会专门讲怎么减小影响。
1.3 运行优化的目标函数与约束边界
这个项目的优化目标是整个调度周期内的总运行成本最小,主要包括购电费用、购气费用,另外我在目标函数里额外加了碳排放惩罚项,这样最终的优化结果不光是经济上的最优,也兼顾了环保。目标函数写成:
min F = 购电成本 + 燃气成本 + 碳惩罚成本
其中购电成本等于逐时购电功率乘以对应时段的电价;燃气成本等于燃气轮机耗气量和燃气锅炉耗气量之和乘以天然气价格;碳惩罚成本则用总一次能源消耗折算成碳排放量,再乘一个惩罚系数。
约束条件我分成了三类:设备自身约束、能量平衡约束、储能动态约束。设备自身约束包括每台设备的出力上下限和爬坡速率限制;能量平衡约束是每个时刻电、热、冷三条母线必须满足供用平衡;储能动态约束则把相邻时刻的储/放能状态联系在一起,并限制储能容量上下限和单位时段最大充放速率。这些约束在粒子群算法里主要靠罚函数处理,但罚函数系数如果设置不合理,很容易让优化结果违反约束或收敛变慢,这是一个需要重点调参的地方。
2. 核心细节解析与实操要点
2.1 主要设备模型的数学化方法
设备模型不需要做得很精细,但必须把输入输出关系表达清楚。燃气轮机我采用的是简化模型:给定发电出力P_GT,耗气量按发电效率η_GT折算,余热回收量Q_hr则通过烟气热平衡计算。这里有一个工程上常用的经验公式:
Q_recovered = P_GT × (1 - η_GT) / η_GT × η_hr × α
其中η_hr是余热回收效率,α是余热利用率系数,用来折算实际能被吸收式制冷机或热负荷利用的那部分热量。这个公式不用怀疑,它把“发电越多、余热越多”的耦合关系表达得很直观,而且计算量很小。
吸收式制冷机和电空调的效率用性能系数COP表示。吸收式制冷机消耗的是热能,制冷量等于输入热量乘COP_ac,这个COP通常在0.7到1.2之间;电空调消耗电能,制冷量等于电功率乘COP_ec,COP_ec一般在3到5之间。制冷设备的COP会随部分负载率变化,项目里我用了分段常数近似,维持了线性关系,方便粒子群搜索。
储能装置的模型最关键,因为它在时间维度上引入了状态量。我用SOC表示蓄能状态,SOC第t+1时刻等于第t时刻SOC加储能量减去放能量,再乘一个自损耗系数。充放能速率也要限制在最大允许范围内。这里要提醒一下,SOC的范围如果设成0到1,罚函数里经常会被大量越过,建议在初始化时就把SOC初值、终值都设定在目标值附近,否则优化结果可能通过“清空储能”来偷成本。
2.2 粒子编码方式与约束处理策略
粒子群算法处理运行优化问题时,最核心的设计决策是“粒子到底代表什么”。我的做法是把决策变量直接编码成粒子位置向量,每个粒子就是一套完整的24小时出力方案。向量由几段拼接而成:燃气轮机逐时出力、燃气锅炉逐时供热功率、吸收式制冷机逐时输入热量、电空调逐时制冷功率、储能逐时充放功率,共5段乘24小时,合计120维。这个维度对PSO来说属于中等规模,种群设60到80个粒子,迭代300到500次,几分钟内就能收敛。
一开始我踩过一个坑:直接把所有设备功率都作为变量,结果能量平衡约束很难满足,罚函数项把目标函数拖得很大,粒子群很难找到可行解。后来改成“减少变量+平衡推导”的思路:把电负荷平衡作为等式约束,卖给电网的功率不单独设变量,而是由燃气轮机发电加市网购电之和等于电负荷加电空调耗电加电制冷其他耗电来反推。这样等式约束数量明显减少,粒子搜索空间也更合理。
约束处理我主要采用罚函数法,同时配合越界回拉。设备出力越界时,直接把该维变量拉回到上下限;能量平衡偏差不直接拉回,而是把偏差量乘一个大罚系数叠加到适应度值里。罚系数并不是越大越好,过大会让可行域内外的适应度差异过大,粒子梯度方向会变得不稳定;我最终把罚系数设置在设备运行成本数量级的10到100倍之间,让粒子先找到可行解,再在可行域内精细优化。
2.3 冷热电三网耦合的关键理解
冷热电三网耦合是这个项目最容易出问题的地方。典型场景是夏季傍晚:电负荷高、冷负荷也高,此时燃气轮机已经接近满发,余热量很大,吸收式制冷机应该尽量多用余热制冷;但电空调COP高达4,如果电价偏低,用市电驱动电空调反而更便宜。这种情况下粒子群必须在两种制冷方式之间做权衡,同时还要满足热平衡和冷平衡,决策空间里会产生一组强约束的“褶皱”,搜索难度明显增大。
我的处理思路是把热平衡方程单独拆出来做校验。具体做法是:燃气轮机和燃气锅炉产生的热量,一部分供直接热负荷,一部分进吸收式制冷机。构建代码时,先根据粒子的燃气轮机出力计算余热回收量Q_hr,再根据粒子中吸收式制冷机的输入热量Q_ac计算制冷量,然后把热平衡方程写成罚函数项,而不是直接解方程。
还有一点容易被忽略:冷负荷和热负荷是不同季节完全相反的。冬季主要是热负荷,吸收式制冷机基本不启动;夏季主要是冷负荷,热负荷很小。如果模型里只设置一组固定的负荷曲线,粒子群学到的规律不一定能泛化到其他季节。所以我在数据准备阶段生成多组典型日负荷场景,每次运行优化时指定场景编号,这样后期做算例对比会更方便。
3. 实操过程与核心环节实现
3.1 Matlab程序整体框架搭建
程序我拆成四个文件:主脚本、适应度函数、设备参数初始化函数、结果绘图函数。主脚本负责加载数据、设置PSO参数、初始化种群并进入迭代循环;适应度函数负责把一个粒子解码成各设备出力、计算能量偏差和运行成本;设备参数初始化函数集中管理天然气价格、分时电价、设备额定参数和负荷曲线;结果绘图函数最后把出力曲线和收敛曲线画出来。
整个运行流程是这样的:
- 加载24小时电、热、冷负荷数据以及分时电价、天然气价格;
- 设置粒子数nPop、最大迭代次数maxIter、惯性权重上下限、加速常数c1和c2;
- 在主脚本中初始化粒子位置和速度;
- 循环迭代,每一轮计算每个粒子的适应度,更新每个粒子的个体最优和全局最优;
- 按PSO速度更新公式更新粒子位置,并对位置进行越界回拉;
- 判断是否达到最大迭代次数,输出最优解;
- 将最优解解码为各设备逐时出力,绘制平衡图和收敛曲线。
主脚本用脚本方式写,不封装成函数,方便初学者直接看到全局变量。不过你要做大算例时,建议把主循环封装成函数,因为脚本形式在反复跑多组对比时变量容易互相污染,回报数据也不方便管理。
3.2 PSO核心循环代码解析
下面这段是PSO迭代核心代码,我在多个项目里都是这个框架,改动的地方主要是适应度函数和解码函数。
% PSO参数 nPop = 60; % 种群大小 maxIter = 300; % 最大迭代次数 w_max = 0.9; % 惯性权重上限 w_min = 0.4; % 惯性权重下限 c1 = 1.5; % 个体学习因子 c2 = 2.0; % 社会学习因子 % 初始化 pos = lb + rand(nPop, dim).*(ub-lb); % dim为决策变量维数 vel = zeros(nPop, dim); pbest = pos; pbestFit = inf(nPop,1); [gbestFit, ~] = min(arrayfun(@(i) calFitness(pos(i,:), data), 1:nPop)); gbest = pos(find(arrayfun(@(i) calFitness(pos(i,:), data), 1:nPop), 1), :);上面的初始化用了arrayfun,但注意这里调用了两次calFitness,比较浪费。我实际代码里是先用一个循环计算所有粒子适应度,再更新gbest,逻辑更清晰,也方便断点调试。
迭代部分的核心代码是:
for iter = 1:maxIter w = w_max - (w_max - w_min) * iter / maxIter; for i = 1:nPop % 速度更新 vel(i,:) = w * vel(i,:) + c1 * rand(dim,1)' .* (pbest(i,:)-pos(i,:)) ... + c2 * rand(dim,1)' .* (gbest-pos(i,:)); % 位置更新 pos(i,:) = pos(i,:) + vel(i,:); % 越界回拉 pos(i,:) = max(min(pos(i,:), ub), lb); % 计算适应度 fit_i = calFitness(pos(i,:), data); % 更新个体最优 if fit_i < pbestFit(i) pbestFit(i) = fit_i; pbest(i,:) = pos(i,:); end % 更新全局最优 if fit_i < gbestFit gbestFit = fit_i; gbest = pos(i,:); end end bestHistory(iter) = gbestFit; end这里有几个关键点需要注意。惯性权重w我用的是线性递减策略,从0.9降到0.4。前期w大,粒子速度大、全局搜索能力强;后期w小,粒子在最优解附近精细搜索。这个策略虽然古老,但对于这种120维的中等问题非常有效。c1和c2我分别设成1.5和2.0,个体认知稍弱、社会学习稍强,收敛更主动。
rand(dim,1)'这里每次迭代都会重新生成随机向量,用它作为学习因子的随机扰动。如果写成rand()单个标量,所有维度共享一个随机数,会明显降低解的多样性,这一点我调试时对比过,效果差别很大。
3.3 适应度函数如何解码并计算运行成本
适应度函数是项目最关键的模块,也是最容易写错的地方。一个粒子向量x可以分段解码成燃气轮机逐时出力、燃气锅炉逐时供热功率、吸收式制冷机输入热量、电空调逐时功率、储能充放功率。解码之后,根据设备参数计算耗气量、购电量和碳排放量,然后累加成总成本。
这里给出简化后的适应度函数伪代码:
function totalCost = calFitness(x, data) % 解码粒子 P_GT = x(1:24); Q_GB = x(25:48); Q_AC = x(49:72); P_EC = x(73:96); P_ST = x(97:120); % 储能功率,正为充电 % 根据燃气轮机出力计算回收余热 Q_hr = P_GT * (1-eta_GT)/eta_GT * eta_hr; % 电平衡:购电功率P_Grid = 电负荷 + P_EC - P_GT - P_ST P_Grid = load_elec + P_EC - P_GT - P_ST; % 购电费用(只统计正购电,P_Grid为负按0处理) buyCost = sum(max(P_Grid,0) .* price_elec); % 燃气成本 gasGT = P_GT / eta_GT; % 燃气轮机耗气功率 gasGB = Q_GB / eta_GB; % 燃气锅炉耗气功率 gasCost = sum(gasGT + gasGB) * price_gas; % 冷热平衡偏差罚函数 heatBalance = Q_hr + Q_GB - load_heat - Q_AC; coolBalance = Q_AC * COP_ac + P_EC * COP_ec - load_cool; penalty = p1 * sum(abs(heatBalance)) + p2 * sum(abs(coolBalance)); % 总成本 totalCost = buyCost + gasCost + carbonPenalty + penalty; end注意P_Grid可能是负值,即燃气轮机发电有余量时可以向电网反送电,但项目未考虑卖电收益,所以我把负值截成0。如果你想做余电上网模式,需要把售电价格单独加上,同时要重新设计目标函数。
冷热平衡罚函数里的p1、p2需要试验确定。我一开始把罚系数设成1e6,结果粒子只要稍微偏离平衡,适应度就巨大,粒子群搜索完全走不动。后来把p1、p2改到100左右,与购电成本量级差不多,粒子能在“违反约束代价”和“降低成本”之间自然折中,最终解既可行又稳定。罚系数要量级匹配,这是罚函数法最容易被忽略的细节。
3.4 结果可视化与优化效果对比
跑完PSO之后,我习惯画四张图:第一张是电负荷平衡曲线,包含燃气轮机出力、购电功率、电空调功率、电负荷和储能;第二张是热/冷平衡曲线,展示余热、燃气锅炉供热、吸收式制冷、电空调制冷之间的分配;第三张是储能SOC变化曲线;第四张是迭代收敛曲线。
收敛曲线是最直观判断优化效果的方式。如果曲线在几十代内就基本平坦,说明粒子群可能早熟了,需要提高惯性权重上限或增加粒子多样性;如果300代之后还在明显下降,说明还可以增加迭代次数。这套程序的收敛曲线一般前50代急速下降,150代以后趋于稳定,最终成本相对初始随机方案能降低12%到18%左右。
通过图形还要检查一个关键问题:储能SOC在调度周期末尾是否回到设定初值。如果没有,说明罚函数项对SOC终值的约束不够强,储能在最后时段可能会被强制放空来降低购电成本,这在实际运行中不可行。我加了一个“SOC终值偏差处罚”的项,效果非常好。
4. 常见问题与排查技巧实录
4.1 粒子越界后适应度不减反增
出现这个情况的原因,多半是越界回拉时只回拉了决策变量本身,而没有同步修正相邻时刻的储能SOC或能量平衡关系。比如储能功率超过上限,你只把P_ST拉回上限值,但SOC计算还是按原始粒子的P_ST算,导致同一组决策变量在适应度函数内部前后不一致。
解决办法是在解码前统一做越界处理。我现在的习惯是:在calFitness(适应度函数)开头再调用一次越界回拉函数,虽然会重复计算,但能保证进入能量平衡计算的所有变量都在有效范围内。这个“重复回拉”看着不优雅,却能把积攒很久的疑难bug一次打掉。
4.2 收敛结果不稳定,每次运行成本差距大
PSO是随机算法,每次运行结果不同是正常的,但如果成本波动超过5%,就要检查随机种子和种群初始化。波动大的主要原因是个体最优更新没有加“微小扰动”,以及惯性权重下降速度太快导致早熟。
我的习惯是固定随机种子做重复实验,同时把种群规模提高到80以上。如果还是不稳定,可以在速度更新公式后加上一小步随机扰动,比如vel = vel + 0.01*randn(size(vel)),相当于给粒子群体增加变异能力,能明显改善结果稳定性。代价是计算时间增加约10%,但换来的结果一致性完全值得。
4.3 冷负荷和热负荷同时满足不了
新手最容易踩的坑是只检查冷平衡而忽略热平衡。CCHP里吸收式制冷依赖余热,而余热又来自燃气轮机发电,自身就有强耦合。如果系统整体发电量低,余热量不足,热平衡没问题,但吸收式制冷输入热量受限,冷负荷就可能供不上。
遇到这种问题,建议先把热平衡偏差单独打印出来看。我曾经碰到过一种情况:电负荷满足得很好,冷负荷也满足得很好,但热平衡偏差很大,原因是粒子把燃气锅炉的热量直接“送给”了吸收式制冷机,而热母线平衡公式里又把这部分热量重复计算了。后来我把Q_AC从热负荷中单独扣除,才把账算对。
4.4 适应度函数出现NaN
NaN(非数值)通常来源于除零或log内负数。燃气轮机效率η_GT如果写成0.4还好,但燃气锅炉低负载时效率可能被定义为随负荷变化的函数,负荷接近0时效率接近0,天然气消耗量gasGB会发散成无穷大或NaN。
解决方法是给设备出力设一个技术最小出力下限,比如燃气轮机最低出力为额定值的30%,燃气锅炉最低为20%。低于下限的运行点直接不参与调度。从模型角度这也符合实际,任何机组都有最小稳定运行功率,不会真的有0出力连续运行的情况。
4.5 代码运行慢,迭代一次耗时太长
PSO慢主要是适应度函数反复调用造成的。假设120维、80个粒子、300次迭代,适应度函数被调用次数是24000次。如果calFitness内部还包含大量循环,那就慢得很明显。
我的优化办法有两条:一是把部分计算改写成向量化形式,比如24个时间步的功率计算尽量不要用for循环,而是整体用矩阵运算;二是在迭代循环外部把价格、负荷、设备效率等常数缓存下来,不要在calFitness里每次重复载入参数。这两个改动做完,实际运行时间可以从十几分钟降到两三分钟。
还有一些更彻底的做法,比如把适应度函数编译成MEX文件,或者用并行计算对种群进行分块评估。但前期调试阶段不建议这么干,编译MEX会拖慢迭代开发节奏;等模型稳定了再优化手段也不迟。
5. 一些边界场景的处理与调试建议
5.1 过渡季节只有电负荷和部分热负荷怎么办
春秋季节冷热负荷都不高,设备出力会非常小,如果仍沿用夏季和冬季的初始种群范围,粒子可能在可行域边界飘来飘去,收敛极慢。我的处理方式是设定场景判断开关:根据冷负荷占比自动调整决策变量的搜索上下限,比如冷负荷接近0时,吸收式制冷机的变量上限直接置零。
这样还能减少实际有效变量维数,提升搜索效率。如果再配合“空调制冷量=0”时对冷平衡罚函数自动降权,优化速度会更快。这种根据场景动态裁剪变量空间的技巧,在粒子群这类无约束角度搜索的算法里非常实用。
5.2 分时电价下储能策略容易出现振荡
储能SOC曲线在电价低谷充电、高峰放电,整体趋势没问题。但粒子群可能在相邻时刻出现反常振荡,比如第10小时充电、第11小时放电、第12小时又充电,导致SOC曲线像锯齿一样。原因是没有限制单位时段最大充放功率,或者没有在SOC状态中加自损耗项。
加入自损耗之后这种锯齿会明显减少,因为频繁充放都要付出损耗成本,不是经济最优。还可以在储能方程中加入“同一时段不能既充又放”的逻辑约束,但在连续变量优化里这个约束很难直接被粒子群满足。实际操作中我把SOC平滑项加到目标函数里,作为微小的二次惩罚,效果很好。
5.3 多目标扩展:经济性和碳排放如何权衡
项目目前用的是目标函数叠加法,把碳排放折算成碳价后加到总成本里。这种方法简单,但碳价系数选多少会影响优化结果。我做过一组算例:碳价设0时,系统选择最优经济方案,燃气轮机开得多、大量使用储能在谷电充峰电放;碳价提高后,燃气轮机与市电的出力份额会变化,电空调由于COP高、碳排放低,会替代部分吸收式制冷的份额。
如果你希望做多目标优化,可以直接把目标函数改成两个输出,再换用多目标粒子群算法(MOPSO),但要注意解集的Pareto前端在低维度才容易可视化。第一次做碳经济权衡分析,我建议还是先固定碳价,跑多个场景对比,比直接上多目标算法好调试得多。
6. 个人实操体会
这套基于粒子群算法的CCHP运行优化程序,前前后后我调整了一个多星期,才把设备模型、罚函数、PSO参数收敛到比较满意的状态。回头总结经验,最重要的一条是:先把物理模型写清楚,再去调算法,模型有bug时调参数只会越调越乱。
第二个体会是,粒子群算法虽然不算新,但作为快速验证和基准比较的工具,天然适合综合能源系统的初期研究。那些更复杂的求解器可以留给线性化模型或混合整数规划去做,但如果你想要一个完全灵活、可以随意改设备的框架,Matlab加PSO仍然是非常实用的组合。
最后再分享一个小技巧:跑优化之前,先在Excel或者纸上手动算一遍典型时段的功率平衡,比如选取电负荷最高的一个时刻,手算燃气轮机和电空调的出力分配,再去看优化结果是否在这个区间内。这个“一分钟手算校验”能帮你快速发现设备参数输入错误,比单纯盯收敛曲线有用得多。