这几年凡是做能量管理、园区微网、多能互补方向的,基本都绕不开综合能源系统这个筐。但真正上手做优化调度的人心里都清楚,这个题目最大的坎儿——不是模型多复杂,而是“多个目标互相打架”的时候怎么办。经济性要花钱最少,环保性要碳排放最低,外加还要看弃风弃光率、看设备寿命损耗,这些指标往往是冲突的。你要么牺牲一点经济性换低碳,要么牺牲低碳指标保运行成本,没有一个解能让所有目标同时最优。所以多目标优化在综合能源调度里不是花活,是真需求。
这篇要讲的多目标退火算法求解含P2X综合能源系统调度问题研究(Matlab代码实现),做的就是这件事。P2X(Power-to-X)是当前综合能源系统里很关键的一类灵活性资源,它把电能转化成氢气、天然气、热能等其他形式的能量,给系统提供额外的调节手段。但P2X设备的引入会让模型的非线性、耦合关系明显变复杂,这时候用多目标退火算法去求解,能在Pareto前沿上找出一组分布良好的折中方案,给决策者提供“怎么选都不亏”的备选集合。整套东西用Matlab实现,适合正在做综合能源课题的研究生、兼顾工程落地的工程师,以及所有想在多目标优化上换个算法思路试试的人。
这篇我就按自己做这个项目的完整思路来讲:怎么描述系统、怎么处理P2X设备的模型、多目标退火算法到底是怎么从单目标退火演化过来的、Matlab代码结构怎么搭、调参要注意什么、仿真结果怎么分析,最后把踩过的坑也都交代一遍。代码不是只给一个可跑的demo,我会把关键环节的原理讲透,这样你拿回去不管是改目标函数、换案例系统,还是换成别的P2X设备类型,都能自己动手改。
1. 系统建模:含P2X的综合能源调度问题到底在优化什么
1.1 P2X设备在系统中扮演什么角色
先说说P2X。X代表什么?常见的可以是氢气(Power-to-Hydrogen,P2H)、天然气(Power-to-Gas,P2G)、热(Power-to-Heat,P2H)、甚至液体燃料(Power-to-Liquid,P2L)。我们知道电力系统最难处理的问题之一就是“发用不平衡”,而风电、光伏这类可再生能源出力又有很强的波动性和不确定性,电网调节压力很大。P2X设备就像是给电能找了一条“可变负荷+可存储”的出路:电多的时候把电转成其他形式储存起来,电少的时候这些能源再通过燃料电池、燃气轮机等设备反向发电,或者直接供给热负荷、气负荷。
在综合能源系统里,P2X设备最大的价值在于“跨能流解耦”。你不光在电网上做功率平衡,还能把电能时空平移。比如夜间风电大发、电价又低,此时电制氢设备开起来把多余风电变成氢气储存,白天用电高峰再用氢燃料电池发电参与顶峰,或者直接把氢气卖给工业用户。这种做法既降低了弃风率,又优化了系统运行成本。所以把P2X放进综合能源调度模型里,问题就从一个单纯的“电力经济调度”,升级成电、气、热、氢多能流耦合的优化问题。
1.2 目标函数怎么定:经济、环保、消纳三者兼顾
调度问题的第一步是明确优化目标。综合能源系统调度里常见的几个指标,包括运行经济性、碳排放量和可再生能源消纳水平,有时候还会加上设备启停次数。
运行成本方面,典型构成包括购电费用、天然气购买费用、设备运维费用等。碳排放则可以从外部购电的间接排放和系统内燃气设备的直接排放两方面计算。可再生能源消纳水平,最直接的是看弃风弃光量,也就是实际出力与可发功率之差的总和。
这三个目标之间存在明显的制约关系。比如你要降低碳排放,就得多用风电光伏,但新能源出力不稳定,需要P2X设备把多余电转成其他形式,设备频繁调节增加运维成本。你要保供,就可能不得不在高价时段购电,经济指标就不好看。多目标优化的意义就在这里:不再追求单点最优,而是产出一组Pareto最优解集。
Pareto最优的概念用大白话说就是:在这个解上,你没法在不恶化任何一个目标的情况下,让至少一个其他目标变得更好。也就是说,你省了钱就得多排碳,环保了就得花钱。这些解组成一条Pareto前沿,决策者根据自己的偏好选点。
另外再多提一句,有些研究会把多目标通过线性加权合成单目标来求解。这个做法简单,但权重系数怎么定是个大问题,而且对非凸Pareto前沿,权重法根本找不全拐点区域的解。这也是我建议直接用多目标算法而不是加权求和的原因。
1.3 约束条件:别忘了网络的物理约束
目标函数定了,接下来是约束。综合能源系统的约束比单纯电力系统要复杂得多,至少要覆盖四个层面:
- 电功率平衡:系统购电功率加各类发电设备出力、储能放电功率,等于电负荷加P2X设备电耗、储能充电功率。母线节点有拓扑的话还要考虑潮流约束。
- 热/气/氢平衡:热网、气网、氢能网络各有各的供需平衡方程,P2X设备的产出要分配出去,燃料电池和燃气轮机的消耗也要计入负荷侧。
- 设备出力约束:每个设备的出力上、下限,爬坡速率限制,启停状态约束,还有P2X设备本身的能量转换效率曲线。
- 储能约束:储电、储氢、储热设备的容量范围、充放能速率、以及调度周期末的储能状态约束(一般要求回到初始值附近,保证调度策略可周期重复)。
还有一个非常关键的细节:P2X设备的转换效率往往不是常数。比如电解槽在部分负荷工况下效率低于额定工况,这就让约束里有非线性项。实际处理时要么分段线性化,要么在智能优化算法的框架里直接计算非线性函数值,这也是为什么这种问题适合用启发式算法求解——你不一定需要给它求导,很多非线性关系都能在适应度函数里直接算。
1.4 为什么这类问题难求解
你如果尝试用线性规划去解上述模型,会发现要么做了一堆近似简化(效率常数化、网络忽略),要么计算规模爆炸(一旦把开关状态变量加进去就变成混合整数非线性规划)。中小规模场景用求解器还能对付,但到了多节点、多设备、含储能的复杂园区,求解时间完全不可控。而且多目标产生的解集,对求解器来说需要反复迭代枚举,效率也不理想。
启发式算法这时就有优势。它不依赖梯度信息,能用“个体适应度”统摄所有非线性和非凸关系;它对目标函数的表达方式宽容,可以同时算成本、算排放、算弃风率;它天然是种群/多链迭代,适合生成一组多样化的Pareto解。模拟退火算法在这类问题上还有一个别人没有的好处——它原理直观,实现门槛低,不像遗传算法要处理一大堆交叉变异算子,也不像粒子群要调一堆速度惯性参数。
2. 多目标退火算法的思路与细节
2.1 从单目标模拟退火到多目标,核心变化在哪
模拟退火(Simulated Annealing,SA)的思想源自金属退火过程:高温下原子活动能力强,能跳到能量较高的状态;随着温度降低,原子慢慢落入低能状态。算法里对应地就是——一开始以较大概率接受劣解,避免困在局部最优;后期温度下降,接受劣解的概率越来越小,逐步收敛。
传统SA针对单目标函数,能量就是适应度值。但到多目标问题,能量不再是单个数值,而是一个目标向量。怎么比较两个解的好坏?这里引入了Pareto支配关系的比较准则。解A支配解B,当且仅当A在所有目标上都不比B差,且至少在一个目标上严格优于B。如果两个解互不支配,就说明它们各自在某个目标上有优势。
多目标退火算法的几个关键设计点:
- 初始解的生成与多链并行:可以使用多个初始解(多条退火链)同时搜索,保证不同区域都被探索到。
- 邻居解的产生:在当前解的邻域内扰动得到新解,扰动策略直接影响搜索效率。
- 接受准则的改进:新解支配旧解,肯定接受;两个互不支配,往往也接受,以增加解的多样性;新解被旧解支配,则以Metropolis概率接受,模拟退火的核心机制保留。
- 外部档案维护:在搜索过程中维护一个非支配解集合(外部档案),迭代结束后这个档案就是Pareto前沿的近似。
- 降温策略:线性降温、指数降温、自适应降温都有人用,需要根据问题规模调。
2.2 邻居解生成:决定搜索效率的细节
邻居解的生成在多目标退火里是个可以被玩出花的地方。调度问题的决策变量通常是各个设备在各时段的出力。如果我直接对整个向量做随机扰动,维度太高,收敛会很慢。实际项目里我习惯用“时段-设备-类型”三层扰动策略:
- 随机挑一个时段;
- 随机挑一种设备;
- 对该设备的出力在可行范围内做小幅随机变化。
比如,电解槽在t=5时刻的输入功率220kW,扰动后变为225kW,同时要检查前后时段的爬坡约束和储能状态约束。每次只动一个点,保证扰动后的解大部分情况下仍然可行,有效减少了修复不可行解的代价。
当然完全只动单点会降低跳出局部的能力。我的做法是以80%概率做单点小扰动,20%概率做多点扰动(比如连续两个时段的出力同时改动,或两个设备联动改动),兼顾了局部精细搜索与较大步长的跳跃。
2.3 外部档案:Pareto前沿的“记忆库”
外部档案是一个专门存放非支配解的集合。每迭代一轮,把当前解和新产生的邻居解拿出来,和档案里的解比较支配关系,剔除被支配的,加入新的非支配解。档案大小要限制,否则解集越来越密,计算负担增加。常用的策略是拥挤距离——算每个解在目标空间里与相邻解的距离,距离小说明过于聚集,剔除它保留分散性更好的解。
我实际用的档案容量是50到100之间,根据目标个数和计算量调整。目标3个以下50够用,目标增加到5个以上,建议扩到100以上,否则前沿覆盖会不够均匀。
2.4 参数设置经验:初始温度、降温系数与终止条件
模拟退火的参数直接决定收敛质量和运行时间。我项目里的经验做法是:
初始温度T0:设得足够高,让初始阶段接受劣解的概率在0.9左右。可以直接根据初始解与若干随机邻居解之间的目标函数差值来估计:先随机生成一批邻居解,统计它们与当前解的最大目标值差距Δmax,然后通过接受概率公式反推T0。比如想让初始接受概率P0≈0.9,可以用T0=Δmax/ln(1/P0)来估算。
降温系数α:每轮迭代后乘上α,我一般取0.90到0.98之间。取值越接近0.98,搜索越充分但耗时越长。如果调度周期是24小时,设备数量10个以上,决策变量上百,α取0.95比较稳妥。
每温度下的迭代次数L:也叫Markov链长度。我习惯设为决策变量维度的1.5到2倍。变量维度100,就迭代150到200次再降温。
终止条件:设置最大迭代代数,或者连续若干代外部档案不再更新就终止。我项目里常用的是两个条件结合,最大代数作为硬保底,档案连续20代无更新就提前结束。
这些参数没有绝对的标准,换一个系统就需要重新调。我的经验是先跑一遍小规模算例,观察档案解的数量和覆盖范围,如果解太少或者收敛慢,优先调高L和α,不要一上来就猛加初始温度。
3. Matlab代码实现:核心模块与关键函数
3.1 代码整体结构设计
用Matlab实现整个求解框架,代码组织我建议按模块化思路,五到六个主文件就够了:
- main.m:主脚本,负责参数设置、数据导入、算法流程控制、结果输出。
- initSystem.m:初始化系统参数,设备数量、负荷曲线、能源价格、效率数据都在这定义。
- initPopulation.m:生成初始解和初始外部档案。
- energyEval.m:计算某组决策变量的目标函数值(成本、排放、弃风量)和约束可行性。
- perturbSolution.m:对一组决策变量做邻居扰动。
- updateArchive.m:维护外部档案,剔除支配解,限制规模。
下面几个文件是最核心的,我一个个讲实现要点。
3.2 决策变量定义与编码
综合能源系统调度问题的决策变量,最常见的编码方式是按设备类型×时段组合排列成一个一维向量。比如你的系统有5台可调度设备,调度周期24小时,决策向量长度为5×24=120。每个位置上是对应设备某个时段的出力值(kW或MW)。
P2X设备在这一步有一个特殊性:它的输入和输出量之间存在非线性变换。比如电解槽输入电功率P_elec,输出氢功率P_hydrogen,两者不是简单乘一个常数效率,很多情况下是P_hydrogen = eta(P_elec) * P_elec,而eta本身是P_elec的函数。编码时决策变量只需要存输入电功率,输出氢功率在目标计算时实时算。这样既减少变量维度,又把非线性关系包在了评估函数内部。
同时还要注意储能设备的决策变量。储电/储氢/储热的充放功率在决策向量里占两块:充电/充能功率为正数区间,放电/放能功率若用正负号区分,需要约定规则。我的编码习惯是单个变量充为正值,放为负值,这样维度能省一半。代价是惩罚项里要处理“同设备同时充放”这种不合理的解——在目标函数里加一项大惩罚,稍微降低这种方案被接受的概率。
3.3 目标函数与约束的处理方式
目标函数我写成矩阵化计算,尽量少用for循环,效率高不少。核心代码如下:
function [cost, emission, curtail] = energyEval(x, sys) % x为决策变量向量,sys为系统参数结构体 % 解析决策变量 P_elec = x(1:24); % 购电功率 P_p2g = x(25:48); % P2G设备输入电功率 P_chp = x(49:72); % 热电联产机组出力 P_hfc = x(73:96); % 氢燃料电池出力 ... % 计算P2G输出 H_out = P_p2g .* sys.eta_p2g(P_p2g); % eta_p2g是效率函数句柄 % 电平衡约束 elec_balance = P_re + P_chp + P_hfc + P_dis - P_load - P_p2g - P_ch; if any(abs(elec_balance) > sys.eps) cost = cost + sys.penalty * sum(abs(elec_balance)); end % 经济成本 cost = sum(P_elec .* sys.price_elec) + ... sum(sys.price_gas * P_chp / sys.eta_chp) + ... sum(sys.om_cost_p2g .* P_p2g); % 碳排放 emission = sum(P_elec .* sys.co2_elec) + ... sum(sys.co2_gas * P_chp / sys.eta_chp); % 弃风弃光量 curtail = sum(max(0, P_re_avail - P_re_used)); end这里有个关键点:对于约束条件,我没有严格写成“必须满足”,而是用惩罚项方式处理。原因很简单,启发式算法在邻居扰动时很容易产生轻度越限,如果直接判不可行丢掉,搜索效率会低很多。惩罚系数设多少是个经验活——太小,算法会给出大量不可行解;太大,相当于把搜索空间硬性切割,容易跳不出去。我的做法是先跑一个小规模测试,统计目标函数正常量级,把惩罚系数设为正常成本的10到100倍量级。
3.4 多目标退火主循环实现
主循环是整个代码的心脏。我贴一下核心结构:
function paretoSet = multiObjSA(sys, params) % 初始化 current = initSolution(sys); archive = initArchive(current); T = params.T0; % 主迭代 for iter = 1:params.maxIter for k = 1:params.markovLen new = perturbSolution(current, sys); % 计算目标值 [f_new, feasible] = evaluate(new, sys); if ~feasible continue; end [f_cur, ~] = evaluate(current, sys); % 支配关系判断 if dominates(f_cur, f_new) % 新解被支配,Metropolis准则 delta = max(f_new - f_cur); if rand < exp(-delta / T) current = new; end elseif dominates(f_new, f_cur) current = new; else % 互不支配,直接接受并尝试加入档案 current = new; end archive = updateArchive(archive, new, f_new, params.maxSize); end T = T * params.alpha; % 档案连续无更新则提前终止 if archive.lastUpdate > params.maxIdle break; end end paretoSet = archive.solutions; end这里面有个容易被新手忽略的细节:互不支配时直接接受新解是为了维持搜索多样性,但如果它离当前解太远,可能导致“跳来跳去”不收敛。所以有的实现会在互不支配时按一定概率(比如0.5到0.8)接受,而不是100%接受。我实测下来,100%接受对最终档案质量影响不大,但会让收敛曲线抖动明显。如果你需要监控平稳性,可以把概率设为0.8试试。
3.5 updateArchive怎么写:避免档案膨胀和丢失边界解
function archive = updateArchive(archive, sol, fval, maxSize) % 1. 检查新解是否被档案中某个解支配 dominated = false; for i = 1:length(archive.solutions) if dominates(archive.objectives(:,i), fval) dominated = true; break; end end if dominated return; end % 2. 剔除被新解支配的旧解 keep = ~all(repmat(fval,1,length(archive.solutions)) <= archive.objectives, 1) ... | any(repmat(fval,1,length(archive.solutions)) < archive.objectives, 1); archive.solutions = archive.solutions(:, keep); archive.objectives = archive.objectives(:, keep); % 3. 加入新解 archive.solutions = [archive.solutions, sol]; archive.objectives = [archive.objectives, fval]; % 4. 若超过容量,使用拥挤距离裁剪 if size(archive.solutions, 2) > maxSize archive = pruneByDensity(archive, maxSize); end end注意第2步的keep逻辑——它要筛选出那些不被新解支配的旧解。这里比较的是“所有旧解的目标值并不是都劣于新解”,或者“至少一个目标严格优于新解”,这两个条件用或组合,确保保留下来的旧解确实和新解互不支配或是支配新解的。
拥挤距离裁剪的实现,是对目标空间的每个维度做归一化,然后计算每个解和最近两个邻居的距离之和,距离最小的被删除。这个做法和NSGA-II里的拥挤距离是同一个思路,代码实现起来也比较直接。
3.6 Matlab运行环境与工具箱问题
关于Matlab版本,这套代码不依赖任何特定的工具箱,基础优化工具箱都用不到,只用循环、矩阵运算和绘图函数。所以R2020b以上的版本都能跑,2026a/2026b这些新版更没问题。不过有一点要注意:如果系统里有神经网络拟合的P2X效率曲线,才需要Deep Learning Toolbox;常规做法是用多项式拟合或查表插值,根本不需要额外工具箱。
很多人跑不起代码,问题不在于算法逻辑,而是数据导入。比如负荷曲线、电价数据存在Excel或CSV里,用readtable或readmatrix导入就可以。我建议把所有基础数据放在一个xlsx里,不同sheet放不同类别,主脚本里统一读取,这样换场景时只需要改数据文件,代码一行不用动。
4. 仿真结果分析与案例对比
4.1 测试场景设置
我用一个典型的园区综合能源系统作为测试算例,说明算法效果和代码使用方式。系统主要包含:一台燃气轮机热电联产机组(CHP),一台电解槽(功率范围50-500kW),一套储氢装置(容量200kg),一台氢燃料电池(额定功率200kW),外购电通道,以及光伏和风机作为可再生能源电源。
整个调度周期取24小时,时间间隔1小时。电价采用峰谷平三段式:峰时1.2元/kWh,平时0.8元/kWh,谷时0.4元/kWh。天然气价格2.8元/Nm³,碳排放因子按电网0.581kg CO₂/kWh、天然气2.16kg CO₂/Nm³计算。
决策变量维度:购电24个,CHP出力24个,电解槽输入24个,燃料电池出力24个,储能充放24个,共计120维。目标函数为:总运行成本最小化、碳排放量最小化、弃风弃光电量最小化。
4.2 Pareto前沿结果解读
跑完之后,得到一组Pareto前沿解。三维目标画成三维散点图最直观。你会发现前沿在成本-碳排放平面上呈现一条明显的递减曲线:成本降低伴随着碳排放升高,这和直觉是吻合的——多用电网的高碳电力当然比使用天然气和可再生能源更便宜,但碳排放增加。而弃风量指标则在另一个维度上呈现独特的分布,说明P2X设备的引入对消纳有显著影响。
从解集中挑一个折中方案看调度曲线:
凌晨0点到6点,电价处于谷段,风电出力相对较大。此时算法选择让电解槽以较高功率运行,把多余风电转化为氢气存储。CHP机组维持低负荷,避免浪费。6点到10点早高峰,电价上升,氢燃料电池开机,利用储氢发电参与顶峰;CHP逐步升负荷,补充热负荷需求。中午光伏大发时段,购电功率降至最低,电解槽再次提高功率吸纳多余光伏。晚间高峰时段,储电和氢能发电共同出力,最大限度减少高价购电。
这个调度曲线很直观地显露出算法的能力:它会自己学着在“电价低的时候多买电制氢储能,电价高的时候放能”之间形成最优组合策略,而不是人为固定某个设备的工作模式。
4.3 与遗传算法对比:多目标退火强在哪
我跑同样的算例,和带Pareto排序的遗传算法(类似NSGA-II的思路,用Matlab自带的gamultiobj做对比)进行比较,主要看两个指标:解集收敛性和分布性。
收敛性方面,多目标退火在小规模、高维非线性问题上的表现并不逊色于遗传算法,甚至因为邻域搜索的特性,在许多局部区域能找到更精细的改进。分布性方面,外部档案配合拥挤距离裁剪,前沿覆盖均匀度比gamultiobj默认参数更好一些。gamultiobj测试下来解容易扎堆,特别是前沿弯曲较大的区域,覆盖不够均匀。
不过退火算法有一个明显的短板——它本质上是单点搜索扩展出的多链版本,全局探索能力不如种群算法。解决办法就是增加多链并行,或者与遗传算法做混合。我在项目里试过一种简单有效的混合策略:前期用遗传算法跑若干代产生初始解群,再把这些解分别作为退火链的起始点做精细搜索。这一改,解集质量提升很明显。
4.4 P2X设备容量灵敏度分析
再多说一个利用仿真结果做扩展分析的方向。我们可以通过改变电解槽的额定容量,观察Pareto前沿的变化。当电解槽容量从200kW增加到500kW时,前沿整体向左下移动——同等成本下碳排放更低,同等碳排放下成本也更低。这是因为更大的电解槽能更充分地消纳可再生能源,减少高价购电和燃气发电。但当容量超过某个阈值(我这个算例里是600kW左右),前沿变化就不明显了,说明边际效益递减。
这种“设备容量-系统效益”的灵敏度曲线,是综合能源系统规划阶段非常实用的参考。算法本身不用改,只要把容量参数换一下重新跑一遍就行,这也是模块化代码结构的优势。
5. 常见问题与调试避坑实录
5.1 档案里解太少或者一直不更新,怎么办
这是我被问得最多的问题。档案解太少,多半是初始温度设置太低或者降温太快,算法过早收敛,Pareto前沿只覆盖了很小一块区域。先把降温系数alpha调大,比如从0.95调到0.98,同时调高初始温度。还有一个容易忽略的原因是拥挤距离裁剪时机太激进——档案一旦满了就不断压缩,导致边界解被误删。注意裁剪时要保留目标函数极值对应的边界解,这些解虽然拥挤距离小,但对覆盖范围至关重要。
如果是档案一直不更新,先检查目标函数计算是不是出了问题——比如某个目标在迭代过程中始终不变,或者两个目标完全线性相关,那说明模型里有个变量没起到作用,或者目标写错了。
5.2 约束惩罚系数该怎么调
惩罚系数这个坑,我一开始也栽过。系数太大,搜索空间被分割成碎片,算法很难在大范围内探索;系数太小,最后解出来的Pareto前沿里混着不少约束轻微越限的解,看起来很好实际上工程上根本不可用。
我的经验做法是分两步走:先用一个较小的惩罚系数跑一轮,看一下解集中每个解的约束越限量。如果最大越限量在合理范围内(比如功率不平衡量不超过总负荷的1%),就保持系数;如果越限严重,每跑一轮把惩罚系数放大1.5倍重新来。反复两三轮就能找到合适的量级。
还有一个细节:不同类型的约束要分开惩罚。电功率平衡和热平衡的越限不可以统一用一个系数,因为两者的量纲和容错空间不同。我会按设备额定容量的比例做归一化后再乘各自惩罚权重,这样调节起来更直观。
5.3 算法运行速度慢,如何优化
Matlab跑优化程序慢,最常见的瓶颈不是算法循环,而是目标函数里的for循环过多。解决思路有两个方向:
方向一是向量化。把所有24个时段的计算尽量用矩阵运算一步完成。比如计算储能SOC变化,不需要逐小时迭代,用cumsum函数一行算完。这类改写对运行速度的提升是数量级的。
方向二是减少目标函数的重复计算。我在主循环里看过很多低效写法——每产生一个新解,就把整个决策变量重新解析一遍,旧解的适应度值没有缓存。其实SA的迭代逻辑,每次只对当前解做一次小扰动,你完全可以缓存当前解的目标值,新解的适应度只需要在旧值基础上做局部更新计算。比如只变了电解槽t=8时段的输入功率,那成本计算里只有这一个时段的相关项变化了,其他23个时段的结果完全不用重算。我做了一个局部更新版本的评估函数,150维变量规模下,整体耗时大概缩减到原始版本的35%。
5.4 初始解怎么给,影响大不大
初始解的影响确实有,但没有那么大。因为模拟退火在高温阶段有较强的“跳出”能力,只要初始温度足够高,从哪开始都会被拉回来。但为了节约前期计算量,我建议给一个相对有效的初始解——比如所有设备出力设置在各自额定功率的中点,储能初始SOC设在50%。这样做的好处是初始解大概率可行,目标函数值不会太离谱,初始温度估算也更有参考价值。
还有一种更省事的做法:先忽略多目标,用单目标(比如只优化经济成本)快速跑一遍SA,把得到的最优解作为多目标退火的初始解之一。这样前面算例里Pareto前沿的“经济最优端点”会很可靠,整个前沿的覆盖质量也会提升。
5.5 三维目标怎么可视化,怎么展示结果
做了三个目标,画图就比二维要讲究一些。我一般输出三种图:
第一种是3D散点图,用scatter3画出三个目标的Pareto前沿。多转几个视角,找一个能同时看到三个轴分布的方位,保存为论文用的图。
第二种是二维投影图,把三个目标两两组合画三张投影图。论文里放这种更稳,评审看着也清楚。特别是当三维图旋转不好看或遮挡严重时,投影图是必须的。
第三种是选中的折中方案调度曲线图。用stacked plot或者多子图分别画各设备的出力时序曲线,同一张图时间轴对齐。注意图上标注P2X设备的输入/输出功率曲线,方便直观看出储氢/放氢的时序逻辑。
写在最后:调这个算法的几个心得
最后分享一点我在这个项目里真金白银换来的体会。
第一,多目标退火在综合能源调度问题上,最舒服的应用场景是中小规模的系统,设备数量在5到15台之间、调度周期24小时。规模太大(上百台设备、分钟级调度),退火算法的计算速度会吃亏,那种场景建议优先考虑改用分解类方法或者并行化改写。
第二,外部档案的质量决定了最终论文里Pareto前沿好不好看。多花点时间优化修剪策略,比盲目增加迭代次数有效得多。我后来甚至把档案修剪逻辑做成可以根据前沿分布动态调整拥挤距离的版本,效果是前沿端点不再丢失,中间区域密度也够,出图非常好用。
第三,Matlab代码的实现没有太多“高难度”动作,真正拉开差距的是模型描述的细腻程度——P2X设备用非线性的分段效率、储能考虑了自放电损耗、热负荷考虑了热惯性按小时延时、甚至加上了设备最小启停时间的约束。这些细节每加一个,算法搜出来的解就离工程实际更近一步,论文的说服力也随之提升。
希望这套多目标退火求解含P2X综合能源调度的思路和代码实现,能帮你少走点弯路。有调试细节要讨论的,欢迎在评论区沟通,我尽量把我踩过的坑都如实说出来。