年初接到一个小型园区的综合能源系统仿真评估核心任务很明确给一套“燃气轮机余热锅炉溴化锂制冷电制冷蓄水罐”的冷热电联供系统做运行优化。这个题目里有几个词组一直在行业里很热——多目标算法、冷热电联供、综合能源系统、运行优化但它们凑在一起就不是随便跑个模型能解决的问题了。系统内部三种能量流互相耦合设备怎么启停、负荷怎么分配、储能怎么充放全是牵一发动全身的事。这篇探索笔记我想把整个项目从建模、选算法到跑通、排坑的过程完完整整讲一遍适合正在做综合能源调度优化、或者准备用多目标算法解决工程优化问题的朋友参考。目标只有一个让后来者少走点弯路。1. 项目背景与问题本质冷热电联供优化到底在优化什么1.1 冷热电联供不是简单的“发电供热供冷”很多人理解冷热电联供就是燃气轮机发电余热拿去供热再用一部分热驱动吸收式制冷机来供冷能源梯级利用听起来很美好。但真正仿真跑起来就会发现每一路能量都会互相“打架”。燃气轮机发电多了余热也多了但此时如果不缺热不缺冷多余余热只能白白排掉如果为了满足冷负荷强行开吸收式制冷机等于把本来可以发更多电的高品位燃料能量拿去“制冷”经济上不一定划算。这种多能流耦合的特征才是调度决策最头疼的地方。我做的这个小园区系统设备层倒不复杂一台额定电功率100 kW的燃气轮机发电效率大约30%尾部烟气进余热锅炉产生热水/蒸汽热水一路进入溴化锂吸收式制冷机组提供空调冷冻水另一路直接进入供热管网满足冬季供暖。电负荷不够的部分从市电购买电制冷机作为备用的冷源补上吸收式制冷不足的部分。还有一个蓄冷水罐可以在电价低的时候蓄冷电价高的时候释冷。光伏屋顶大约装了50 kW白天有出力但波动大没法指望太多。设备层简单不代表调度简单。因为电价、气价、负荷曲线每天都在变运行人员到底应该什么时候用燃气轮机发电、发多少什么时候买电吸收式制冷和电制冷各顶多少负荷蓄冷罐什么时候蓄、什么时候放这些问题其实就是一个“分蛋糕”的问题把有限的燃料能量和电网购电合理分配到电、热、冷三个篮子里。1.2 运行优化的本质是“多方权衡”不是“求一个最优解”这里必须先说一个我踩过的最大的认知门槛很多第一次接触这个方向的人习惯性地想“我找一个最优运行方案”但多目标优化的问题根本不存在唯一最优解。举个具体例子。我优化这个小园区的运行方式至少有三个目标摆在面前运行成本尽可能低、一次能源消耗尽可能少、碳排放尽可能低。这三个目标天然是冲突的。燃气轮机发电占总电力比例越高买电自然少了运行成本可能下降但此时为了满足冷负荷吸收式制冷机的出力也上来了燃料消耗增加碳排放不一定最优反过来把更多冷负荷交给电制冷机吸收式端负荷小了燃机可以少开成本可能降了但电制冷耗电导致购电增加电网侧的碳排放和电费又上去了。这三路曲线掰扯下来会发现没有任何一个方案能同时让成本、能耗、碳排放都最好。这叫目标冲突。如果非要用传统的单目标加权法做比如把三个目标乘上权重加起来变成一个目标那结果完全取决于权重怎么取。权重是拍脑袋定的运行方案自然被拍脑袋的权重左右。后来我在项目中换了思路不预判权重直接给出一整套互相不支配的Pareto解集让决策者根据当天运营策略从这套解里选最合适的一个。2. 项目建模先把目标函数和约束条件写成“能算的数”2.1 三个目标函数我分别是怎么写的建模之前得先明确时间尺度。我做的是日前优化调度时间颗粒度取1小时一天24个决策断面这样既够工程决策细化又不至于把状态变量膨胀到难以求解。每小时的设备出力、储能充放、与外网交易功率都作为优化变量。第一个目标运行成本最小化。这里头包含购电成本、燃气购气成本、设备运维成本如果有光伏上网和政府补贴可以再减去售电收益。我这张园区系统里没有光伏上网余电光伏基本当地消纳所以只算光伏的边际成本为零。购电成本按峰谷分时电价计算我用的典型分时电价峰段每千瓦时1.05元平段0.63元谷段0.32元。燃气价格按每标准立方米3.0元算燃气轮机的热耗率从厂家数据折算下来大约是0.35立方米每千瓦时电。运维成本按每台设备出力的单位增量费用估算燃气轮机每千瓦时0.08元溴化锂机组每千瓦时0.02元电制冷机每千瓦时0.01元。第二个目标一次能源消耗量最小化。这里不能只看天然气消耗量因为从市电购买的电力在电网侧最终也是消耗煤或气发出来的需要把购电折算成一次能源。我国的电网平均供电煤耗是个动态值我仿真时先用了一个折合系数每千瓦时购电折算为0.27千克标煤天然气按每立方米折合1.1千克标煤然后统一成标煤量衡量。这样做的目的就是让“本地发电”和“外购电”放在一个标尺下面比较。第三个目标碳排放量最小化。思路和一次能源折算类似购电部分按电网平均碳排放因子算比如每千瓦时购电按0.65千克二氧化碳计天然气燃烧排放按其热值和排放因子算。这里注意如果未来电网清洁能源比例上升电网碳排放因子是逐年下降的模型里最好把它设置成一个可以随时改的参数而不是写死不然做场景延展时会很被动。2.2 约束条件里最容易翻车的几个点目标函数写得再好看约束条件不严谨求解结果就是张废纸。综合能源系统的约束条件我按类型拆成四块。第一块是能量平衡约束。这个是最硬的约束电功率平衡、热功率平衡、冷功率平衡都要时刻满足。电功率平衡就是燃气轮机发电加光伏出力加购电等于电负荷加电制冷耗电加蓄能装置用电。热平衡则要留意余热锅炉产的热量怎么分配给供热和制冷。冷平衡要把溴化锂机组制冷量、电制冷量、蓄冷罐放冷量加起来等于冷负荷。第二块是设备出力上下限和爬坡约束。燃气轮机不是想发多少就发多少它有最小技术出力比如30%额定负荷也有每小时爬坡率限制仿真中我按每分钟不超过5%额定出力算换算到小时就是30 kW/h。溴化锂机组和电制冷机也有上下限和启停时间约束。第三块是储能系统约束。蓄冷罐有容量限制有蓄放冷速率限制还要考虑当天内需回到初始状态否则第二天没法用。这里有个容易遗漏的坑蓄冷罐的“自损耗”如果不考虑优化器会把冷存进去一直不用结果明显偏离实际。我按每小时2%的损耗算进去虽然不大但对运行策略是有影响的。第四块是外购电功率约束。园区和电网的联络线容量是有限的比如我这个系统最大购电功率150 kW这一条如果不写死优化算法可能会让系统在极端情况下直接从电网拉几百千瓦算出来的成本当然好看但换到真实系统根本做不到。2.3 决策变量到底该选多少怎么控制维度决策变量的选择直接决定优化问题的规模和难度。我的做法是把每一小时燃气轮机发电功率、吸收式制冷机开启的台数和制冷功率、电制冷机功率、蓄冷罐蓄放冷功率、购电功率都作为连续或整数变量。这里要特别提醒购电功率其实可以由电功率平衡方程反算出来如果把它也单独设成决策变量会增加冗余维度还会让约束关系更难处理。我一开始把购电功率设成独立变量结果总是有些解在平衡方程上差一点点怎么罚都罚不干净。后来学聪明了让购电功率作为平衡方程的计算结果变量减少一个可行性明显改善。这个思路放在各种能源系统优化里都适用能靠守恒方程算出来的量就不要放进变量列表。关于整数变量初始模型里把溴化锂机组的运行台数设为整数变量两台的机组可以一台开一台停调度上更灵活。但这个变量把问题从纯线性问题撑成了混合整数非线性问题求解时间直线上升。后来做测试对比时我没有一开始就全模型求解而是先跑一台机组必须全开的简化版确认目标函数逻辑没问题后才把整数变量加回去。3. 多目标算法选型与求解思路为什么不用单目标加权法3.1 单目标加权的三个毛病写第一篇调度脚本时我也图省事用过线性加权法把成本、能耗、碳排放三个目标乘权重加起来做成一个单目标然后调一个整数规划求解器直接跑。结果只能说勉强能用但有几个问题暴得很明显。第一个毛病权重敏感性太强。成本目标数值通常很大一天运行成本一万多块钱折算碳排放量也是几千千克两者量纲差了很大如果不做归一化光是把目标函数乘到同一个数量级权重早就名存实亡了。我做过一次敏感性实验把成本权重从0.5改成0.6运行方案里燃气轮机出力策略完全变了但如果你问这个权重变化背后的经济学含义是什么我说不出来因为权重就是拍脑袋。第二个毛病加权法只能获得一个Pareto前沿上的点。工程上其实常常不只是要一个解而是希望看到完整的前沿让决策者理解“如果我要再降10%碳排放运行成本大约要涨多少”。这种权衡信息加权法一次只能给一个点要想得到多个点就得反复调权重且调出来的点不一定均匀分布。第三个毛病非凸Pareto前沿情况下加权法会漏解。这个可能有点偏理论但我实际测过当目标函数可行域是非凸的线性加权法会跳过中间的折中区域只能得到两端极端解。这种情况在含储能和整数变量的综合能源系统里还挺常见的。你做了一堆策略分析结果发现中间区域根本没被覆盖这对决策支持来说会漏信息。3.2 我为什么最终选了NSGA-II而不是MOPSO或其他算法多目标算法可以从好几个家族里选比如基于遗传算法的NSGA-II、基于粒子群的MOPSO、基于分解的MOEA/D等。我最终主要用的是NSGA-II原因是综合能源系统约束真的很多NSGA-II的非支配排序加拥挤度距离机制在约束处理上比较从容而且大量开源库实现成熟可复现性也好。MOPSO我也试过一轮。它收敛速度很快我这种24小时调度问题跑几十代前沿就已经很有形态了。但粒子位置更新在离散整数变量面前很笨拙处理启停台数这种0-1变量时要么把位置取整要么用二进制粒子群操作起来都多一层麻烦。另一个问题是约束处理MOPSO对等式约束的可行性维护不如遗传算法的罚函数思路直观经常飞出可行域。NSGA-II最经典也不代表没毛病。它的核心运作方式就是靠“非支配排序”把解按等级分层再用“拥挤度距离”保证前沿上解的分布不要太挤。对于目标数不超过3个的问题这套机制非常合适。如果目标数到了5个以上NSGA-II的支配关系往往会让所有解都变成非支配的那时候就需要NSGA-III这类基于参考点的方法。我这个系统只有3个目标NSGA-II完全够用。3.3 算法参数怎么定直接抄我的经验值我在项目里反复调参最终用的参数组合比较稳定种群规模100进化代数400交叉概率0.8变异概率0.1。你可能在网上看见有人用2000代甚至5000代但对于24小时调度这种中等规模问题400代已经能看到收敛趋势500代之后前沿几乎没有明显变化白花计算时间。种群规模100也够太小了前沿会出现断层太大了每代计算耗时成倍增加没必要。变异算子上我用的多项式变异分布指数取20。交叉用模拟二进制交叉分布指数也取20。这两个是NSGA-II默认配置理论上没问题但实际上对约束的可行性影响很大。我测试过将分布指数改成5、10、30发现指数越小子代越偏离父代约束不可行概率显著增加指数太大种群很快聚集多样性变差。20算是平衡点。约束处理方面我采用经典罚函数法把违反能量平衡、蓄冷罐容量等约束的量作为惩罚项叠加到所有目标函数上。惩罚系数不能太小我初版取值偏小导致一些违反约束的解反而因为“省钱”留在了前沿上后期再修就很麻烦。后来把惩罚系数设到目标数量级的100倍以上才把不可行解基本压出前沿。4. 完整实操案例一个小型园区的日前优化调度跑通全过程4.1 系统结构与基础数据准备为了让你读文章的时候不至于总是看抽象的公式我直接把我这个仿真案例的数据结构摆出来。园区总电负荷峰值大约180 kW日电量约2800 kWh冷负荷峰值约150 kW主要在白天热负荷冬季为主峰值约120 kW。燃气轮机额定功率100 kW最小出力30 kW溴化锂机组总额定制冷量120 kW最低负载率50%电制冷机额定功率60 kW蓄冷罐有效容量300 kWh最大蓄放冷速率80 kW初始容量50%。分时电价我前面已经写了再补一句峰段9点到12点、17点到20点平段8点到9点、12点到17点、20点到23点谷段其余时间。这套峰谷结构对优化结果影响极大如果换一个地方的电价结构最优运行策略可能直接反转。所以做类似项目时一定是本地数据优先别拿网上的公开数据硬套。4.2 求解流程和代码骨架照着改就能用我在代码层面用的还是Python优化框架用的pymoo库这个库对NSGA-II的封装很完整省了自己写选择、交叉、变异的工作。核心结构是这样的from pymoo.algorithms.moo.nsga2 import NSGA2 from pymoo.core.problem import Problem from pymoo.optimize import minimize import numpy as np class CCHPProblem(Problem): def __init__(self): # 24小时 * (燃气轮机功率 制冷机台数 制冷功率 电制冷功率 蓄冷功率) n_var 24 * 5 # 成本、一次能源、碳排放 三个目标 n_obj 3 # 不等式约束能量备用、储能容量范围等 n_ieq_constr 24 * 8 super().__init__(n_varn_var, n_objn_obj, n_ieq_constrn_ieq_constr, xllower_bound, xuupper_bound) def _evaluate(self, x, out, *args, **kwargs): cost, energy, emission [], [], [] g_violation [] for individual in x: # 将决策变量解码计算各功率分配 # 通过电功率平衡反算购电功率 cost_i calc_running_cost(individual) energy_i calc_primary_energy(individual) emission_i calc_carbon(individual) constr_violation calc_constraints(individual) cost.append(cost_i) energy.append(energy_i) emission.append(emission_i) g_violation.extend(constr_violation) out[F] np.column_stack([cost, energy, emission]) out[G] np.array(g_violation) algorithm NSGA2(pop_size100, samplingcreate_initial_population(), crossoverreal_sbx(prob0.8, eta20), mutationreal_pm(prob0.1, eta20)) res minimize(problem, algorithm, seed42, verboseFalse) pareto_points res.F optimal_schedules res.X代码骨架很简单但实际工作量都在calc_running_cost和calc_constraints这种函数里。如果只是照抄这个架子内部逻辑写不清楚出来的结果会非常难看。我建议你自己测试的时候先把目标函数写成单目标版本用一个小求解器验证日总成本是否符合手算预估再塞回多目标框架里跑。4.3 结果怎么读先看Pareto前沿再选折中解算法跑完后我得到了一组非支配解可视化出来是一个三维散点面。每个点对应一套完整的24小时运行策略。第一次看见这个结果时最直观的感受是成本最低的点碳排放基本最高碳排放最低的点运行成本又上去了。中间区域有很多不错的折中方案但你要知道它们各有各的形态有的是燃气轮机全天高负荷运行少买电但燃料成本高有的是白天电制冷晚上蓄冷把冷负荷调到谷电时段。到这里就涉及一个很现实的问题最终调度表到底取哪个解我用的方法是熵权TOPSIS。先用熵权法根据前沿上每个目标在该点的数据波动程度自动计算目标权重再通过TOPSIS计算每个方案到理想解和反理想解的相对贴近度选出综合最满意的那个。简单说就是让数据自己说话而不是靠拍脑袋定权重。选出来的折中解我对比过燃气轮机大概“日间高峰时段满发夜间低负荷运行”吸收式制冷在白天冷负荷高峰时顶主力电制冷只在燃气轮机余热不足的平段补一部分蓄冷罐在夜间的谷电时段蓄冷白天峰电时段放冷。这个结果“看起来合理”是因为优化器自己发现每个设备的运行优势时段而不是运行人员凭经验拍出来的。5. 常见问题与现场排错这些问题你大概率也会遇到5.1 能量平衡约束总是不满足怎么办我在项目初版调试时遇到一个很奇怪的现象明明每个决策变量都在边界内但电功率平衡总有几十kW的差额怎么都压不掉。排查了一圈发现是量纲问题。燃气轮机发电功率的单位是kW但购电功率我按MW算两路加在一起差了一千倍平衡方程自然对不上。这种低级问题写代码时很容易发生建议所有变量统一用kW和kWh再在关键函数里打印检查一下单位。能量平衡约束另一类问题来自“反算变量”的上下限。比如我用平衡方程反算购电功率后没有检查它是否超过联络线容量。如果超过了这个解实际上非法但平衡方程本身是满足的所以算法以为它是可行解结果经常选出明显荒谬的“大买电”策略。解决办法是给反算变量也补上不等式约束不能因为它在方程里而不设边界。5.2 Pareto前沿总是聚成一团分布不均匀怎么办如果跑完发现前沿上的点集中在一小片区域或者干脆只分布在两个极端目标附近多半是种群多样性出了问题。我先检查初始种群只用随机均匀采样时种群在可行域里的分布很散但约束条件往往把大部分个体都罚掉了有效个体太少前沿自然伸展不开。我后来在初始化阶段引入了“启发式初始解”策略把燃气轮机恒定满发、恒定负荷跟随、冷负荷全吸收式制冷、蓄冷罐白天不放冷等几种工程上常见的启发式方案手工塞进初始种群。这相当于给优化器几个不错的“种子”让它从这些靠谱方案附近开始搜索而不是纯随机瞎闯。效果立竿见影前沿分布明显加宽。如果种群初值没问题还是聚成一团我会调节拥挤度算子的选择压力。NSGA-II的拥挤度距离计算基于目标空间如果三个目标的量纲差异太大拥挤度计算基本被量纲大的目标主导那分布也会失衡。解决思路是在计算适应度前对每个目标做线性的归一化处理让三个目标的量纲都在0到1区间这样拥挤度才表达真正的“空间疏密”。5.3 冷负荷峰值时段供冷不足优化结果被罚得一塌糊涂有一次跑出来的结果所有解都不可行排查发现是模型里没有给冷负荷留备用容量约束。冷负荷峰值150 kW但溴化锂机组加电制冷机的最大制冷能力合计只有12060180 kW看起来够用但蓄冷罐放冷速率上限80 kW如果优化器试图在峰时靠蓄冷罐补60 kW以上单一路径放冷速率就不够于是冷功率平衡被违反。这个问题的本质是“多设备联合供给时单体约束和组合约束都要考虑”。不能只看总容量还要看每条能流路径的最大传输能力。我后来把蓄冷罐放冷速率、溴化锂机组的爬坡速率、电制冷机的启动延迟都加进约束并把冷负荷峰值段设置为备用容量不低于10%的附加约束问题就消失了。还有一个相对隐蔽的坑溴化锂机组的制冷量来自余热而余热来自燃气轮机发电。如果燃气轮机在某个时段没开或者低负荷运行溴化锂机组的额定能力就变成了空话制冷能力是要打折扣的。我一开始把溴化锂机组当成“独立设备”参与冷平衡约束结果理论可行但实际供冷是零。后来在模型里加入了“小时级余热可用量计算”先把燃气轮机的排烟热算出来再限制吸收式制冷的实际制冷量上限这个逻辑才算闭环。5.4 算法算得慢如何既不降质量又压缩时间非线性和整数变量叠加一天24小时调度问题算上100个种群跑400代在普通笔记本上可能要好几分钟。但如果你要做蒙特卡洛场景扫描比如光伏出力、负荷曲线等随机波动上百个场景这个时间就完全不能接受了。我的经验是分两步走。第一步先用连续变量版本把溴化锂机组台数当作连续变量跑一遍完整优化第二步把得到的调度结果取整然后用固定台数再跑一次修正优化。这样虽然损失的精度不多但求解时间能减少将近一半。实际上对于工程调度机组的启停台数取整误差完全可接受不必死磕全局整数解。另一个技巧是合理设置收敛判据。pymoo里可以传入一个终止条件比如前沿的世代间变化小于某个阈值就提前停掉。我设过一个容差0.1%——如果连续20代前沿的平均目标值变化量小于0.1%直接结束。这样做常规场景下大约250到300代就收敛了不用硬跑满400代同样省出不少时间。不过要小心设定这个判据后如果出现单峰极端情况优化器可能提前陷入局部区域。稳妥起见我通常先用固定代数跑一遍再对比提前终止结果差异不大才敢放心用。6. 关于这个方向的一点个人体会这个项目折腾了大半个月最深的感觉是写目标函数、调约束、跑算法这些“硬功夫”其实是最快能学会的真正花时间的是理解系统本身。你如果不清楚燃气轮机的排烟余热在某个天气条件下到底能带多少冷负荷那你写的模型再精密计算结果也是没法落地的。另外说句实在话仿真优化做到最后输出一摞Pareto前沿如果只是画几张炫酷的三维散点图价值有限。我做这个项目时花了不少精力把最优的调度方案转成运行人员能看的“设备逐小时出力表”并对比了一版人工经验调度的结果。仿真优化相比人工经验日运行成本大约降了8%碳排放降了6%左右。这种量化对比比“前沿分布很均匀”这种话有说服力得多。以后如果继续往下扩展我会把更多不确定性因素加进来屋顶光伏的预测误差、冷负荷实际波动、天然气价格的随机性这些都会改变最优解。多目标优化的快乐就在于问题永远比模型复杂但你每多考虑一个现实约束结果就离真实世界更近一步。希望这篇记录能给你一点参考也欢迎在实际调试中遇到怪问题的同行来聊聊很多坑真的是大家一起趟出来的。