资讯动态

NSGA-Ⅲ求解梯级水电火电联合调度的Matlab实现解析

发布时间:2026/10/13 17:29:40 来源:尧图企业网站定制
梯级水电和火电联调这种话题做优化的人应该不陌生。水电机组启动快、调节灵活但受来水和库容约束火电机组出力稳定但煤耗和排放都是大头。两者放一起做联合调度本质上是个多目标、强约束、非线性的高维优化问题。以往搞加权单目标或者分层序列法最头疼的就是权重怎么定、量纲怎么统一而且一次只能出一组解换一组权重又得重跑一遍。我最近在复现和扩展的这套NSGA-Ⅲ梯级水电火电联合调度项目正好是冲着这些痛点去的。它用非支配排序加参考点机制一次性跑出整个Pareto前沿让调度人员可以从解集里挑符合实际需求的方案而不是被单个最优解绑死。代码用Matlab实现整套逻辑从问题建模、算法设计到结果分析都是通的非常适合电力系统方向的研究生、做运行优化的工程师以及想找多目标算法落地案例的开发者参考。这篇文章我从模型原理、算法改进、代码结构到实际调参踩坑完整拆一遍希望能帮正在看类似项目的你省点时间。1. 项目整体设计与思路拆解1.1 这到底是个什么问题先看清楚这个调度问题的物理本质。梯级水电厂不是孤立的一台机组沿着同一条河流从上到下串联着好几座电站上游出库流量经过一段时间延迟变成下游的入库流量这叫水力耦合。火电则分布在电网的不同节点响应速度比不上水电但胜在稳定、不靠天吃饭。联合调度的意思是在若干个调度时段内把水、火两类机组的出力曲线全部定下来目标是同时满足系统负荷需求、各类运行约束并且让多个性能指标尽可能都好。典型的指标包括系统总煤耗最小、污染物排放最少、水库水位越限惩罚最小或者机组启停成本最低等等。这个问题难在哪首先是非线性程度高——水电机组的出力是水头与发电流量的乘积关系火电煤耗特性曲线通常用二次函数拟合其次是强耦合——上下游电站之间、水电与火电之间、不同时段之间的变量互相牵制最后是解空间大——每个机组的每个时段都是一个决策变量随便一个实例就是几十上百个维度。传统加权法把多个目标用权重线性叠加成一个综合指标但权重本质上靠经验拍脑袋而且如果两个目标的量纲差异太大比如煤耗按“吨”算、排放按“吨”算但数值差几个数量级得先做归一化做完归一化权重的含义又变得不直观了。项目里用NSGA-Ⅲ这类多目标进化算法最核心的逻辑转变就是不做加权直接对多个目标同时优化最终输出一整组互不支配的折中解。1.2 为什么选NSGA-Ⅲ而不是NSGA-Ⅱ很多人在多目标优化里第一个接触的是NSGA-Ⅱ经典确实经典但它在处理高维目标时有明显的短板。NSGA-Ⅱ的多样性保持靠的是拥挤距离——比如两个解在目标空间同一个方向上扎堆拥挤距离就小算法会优先淘汰它们。可一旦目标个数增加到3个、4个甚至5个以上拥挤距离计算会退化解集容易在某个局部区域过度聚集整个Pareto前沿分布不均。NSGA-Ⅲ的改进思路非常直接它不再用拥挤距离而是预先在整个目标空间铺设一系列参考点。比如3个目标时参考点均匀分布在标准单纯形上每个种群个体在环境选择阶段匹配到离自己最近的参考点算法通过统计每个参考点关联的个体数量来维持多样性。这样做的效果是无论目标空间实际形状怎么变化算法始终有明确的多样性引导方向不会因为某个区域角度刁钻就失去覆盖。实际跑下来我的感受是2目标场景下NSGA-Ⅲ的优势还不算特别明显但一旦把碳排放、煤耗、水位偏差三个以上目标放进来NSGA-Ⅲ得到的解集比NSGA-Ⅱ均匀和完整得多特别是在前沿面的边界区域NSGA-Ⅱ经常丢三落四NSGA-Ⅲ能稳定覆盖。这也是这个项目把算法定为NSGA-Ⅲ的主要原因——它和梯级水电火电调度这种典型的高维目标场景非常契合。1.3 项目的整体架构我看到的这套Matlab实现流程主线非常清晰大致可以分成以下几个模块数据输入包含水库特性参数、火电煤耗系数、负荷曲线、来水预测、水位库容关系曲线等。问题建模把决策变量编码处理成适合进化的染色体结构目标函数和约束条件写成独立的m函数。算法主循环按照种群初始化、非支配排序、选择交叉变异生成子代、参考点关联维持多样性、环境选择产生下一代这样的流程循环迭代。结果输出绘制Pareto前沿图、折中解调度曲线、收敛曲线等。这种模块化结构本身也是值得学习的点。它让问题定义和算法框架分离以后你想换一套算法比如把NSGA-Ⅲ换成MOEA/D或者SPEA2不需要动目标函数部分只需要替换算法主循环就可以了。我扩展实验时把NSGA-Ⅲ换成了MOEA/D整个过程中唯一改的就是算法主体和少数参数设置目标函数完全没动。2. 核心数学模型解析与目标函数设计2.1 决策变量与编码方式在这个项目里决策变量是各调度时段内水电机组的发电流量和火电机组的出力。Matlab实现时通常用实数编码一条染色体就是把所有变量串在一起。比如系统有3个水电站、2个火电厂调度周期取24个时段那一个个体就表示成% 假设 nHyd 3, nThermal 2, T 24 % 决策变量排列顺序[水电1各时段流量, 水电2各时段流量, 水电3各时段流量, 火电1各时段出力, 火电2各时段出力] % 染色体长度 nHyd * T nThermal * T 120 nVar nHyd * T nThermal * T;这里有个细节值得注意水电机组的出力其实是通过流量和水头计算出来的不是直接作为决策变量。原因是如果直接把出力当决策变量后续验算库容平衡时经常会矛盾——出力定了流量推不出来水位变化就和流量对不上。项目编码时选择发电流量作为水电子系统的决策变量保证在水力约束层面天然可解。这个设计细节我实际操作中认为非常重要后期约束修正的负担能减轻一大半。2.2 目标函数设计多目标调度问题目标函数直接决定了你会得到一个什么样的Pareto前沿所以这部分的建模要认真对待。这个项目里重点考虑了三个目标第一个目标是系统总煤耗量最小。火电机组的煤耗特性通常用二次函数拟合function f1 calcCoalCost(P_th) % P_th: 火电机组出力向量 % a, b, c: 煤耗系数具体数值由机组特性决定 f1 sum(a .* P_th.^2 b .* P_th c); end这里需要注意的是煤耗系数矩阵a、b、c的维度要和火电机组数量一致。项目数据里火电机组的煤耗系数来自典型机组特性数据这是公开常见的测试数据不是凭空编的。水电部分不直接消耗燃料但可以折算成煤耗节约量不过在目标函数里通常不这么处理而是直接用系统总煤耗来表达水电高了火电自然就低这是间接达到节能效果的。第二个目标是污染物排放量最小。排放量也一般用二次函数拟合和煤耗曲线是独立的另一套系数。有些文献会把煤耗和排放做线性加权合并成一个目标但项目里把它作为独立目标因为煤耗最少时排放不一定最少——高煤耗低排放的机组组合在现实中是存在的两个目标放一起才有让大家权衡的空间。第三个目标是水库水位偏差惩罚最小。梯级水电站在调度过程中不能为了追求发电量就让水位长期偏高或偏低这会影响通航、灌溉、生态流量等需求。这个目标通常写成各时段实际水位和目标水位的偏差平方和最小function f3 calcWaterLevelDeviation(Z) % Z: 各水库各时段实际水位 % Z_ref: 各水库目标水位 f3 sum(sum((Z - Z_ref).^2)); end当然如果你有更多实际场景需求比如电网峰谷差最小、机组启停次数最少、弃水量最小等都可以在这个框架里加进来只需要保证新的目标函数是决策变量的合理映射就行。项目代码里目标函数统一返回一个目标值向量NSGA-Ⅲ对这个向量只做支配关系判断和参考点关联计算不关心目标内部怎么算出值来。2.3 约束条件的处理方式约束处理是多目标进化算法里最考验工程能力的一环。这个项目的约束主要包括电力系统负荷平衡约束各机组出力总和等于系统负荷、水库水量平衡约束、发电流量上下限、库容上下限、火电出力上下限和爬坡速率约束。其中负荷平衡约束是最麻烦的。因为决策变量是随机初始化的要满足“水电出力火电出力系统负荷”这个等式约束在进化算法里很难通过惩罚函数硬来。项目里采用的是修正法算完水电总出力之后剩余的负荷差额由火电分配时统一补足。具体实现是这样的% 计算水电总出力 P_hyd_total calcHydropowerTotal(Q_hyd, H); % 剩余负荷由火电机组承担 P_th_remaining P_load - P_hyd_total; % 按比例分配到各火电机组 P_th distributeThermalLoad(P_th_remaining);这种做法实际上把等式约束从“搜索”问题变成了“函数计算”问题不再需要算法去探索满足等式的可行域因为每一个个体天然满足负荷平衡条件。它保证了种群中大多数个体都是可行或接近可行的。对不等式约束比如出力上下限则在生成初始种群时先限定在边界内交叉变异后用边界吸收法修整也就是越界的变量直接拉到边界值而不是让其留在非法状态。这种方法比单纯加惩罚函数的优势非常明显。惩罚函数法最大的问题是惩罚系数怎么设——设小了约束等于无效设大了相当于又偷偷加了一个目标而且这个目标的权重照旧要靠经验来调。项目里的“等式约束变换不等式边界修正”策略我复现的时候觉得代码逻辑干净对算法收敛速度的负面影响也很小。如果读者将来把这个框架迁移到别的领域比如园区综合能源调度、配电网重构这种做法同样值得沿用。3. NSGA-Ⅲ算法核心机制与Matlab实现要点3.1 参考点怎么生成NSGA-Ⅲ最核心的机制就是参考点。参考点的作用是把目标空间均匀划分成若干个区域种群中的个体按区域分组每个区域至少保留一个个体从而保证多样性。假设目标个数是M每个目标方向上的划分份数是p那么参考点总数是组合数C(Mp-1, p)。比如3目标、每维划分4份参考点总数就是C(43-1, 4)15个如果划分10份参考点数就会到66个。这个数字直接和种群规模相关。通常种群规模N取最接近参考点总数的整数不能随便设否则参考点使用效率会降低。这是很多人在参数设置时最容易忽略的点。Matlab里生成参考点可以用Das和Dennis的边界交叉方法或者用R-NSGA-Ⅲ里的两步法在超平面上生成参考点再映射到单位单纯形上。项目代码里直接实现了Das-Dennis方法核心思路是递归枚举所有满足条件的分量组合得到坐标值再转换到标准化目标空间。生成参考点的算法本身不难难的是理解“参考点要分布在归一化后的目标超平面上”这句话。很多代码生成的是单位立方体里的网格点而不是单纯形上的均匀参考点这两者在高维目标下的多样性维持效果差异很大我的建议是直接照着论文里的公式写一遍不要偷懒用网格点代替。3.2 适应度评估与非支配排序非支配排序在NSGA-Ⅲ中依然承担着分层选择的作用。定义很简单解A支配解B当且仅当A在所有目标上都不比B差并且至少在一个目标上严格优于B。把所有不受别的个体支配的个体划为第1层然后把它们暂时移出再找下一层以此类推。Matlab里常见做法是用“待支配数”和“被支配集合”两个数据结构来判断。每个个体的待支配数初始化为被多少个体支配被支配集合记录它支配了谁。排序时先把待支配数为0的个体放进当前层然后对它们支配的每个个体把待支配数减1减到0了再进下一层如此反复。这个过程的时间复杂度大概在O(N²)数量级对常规种群规模几百到一两千完全够用不需要刻意优化成竞技场选择那种复杂版本。这个环节有个容易错的地方目标函数值是否需要归一化。我测试过如果不做归一化当不同目标的量纲差异悬殊时参考点关联阶段的“垂直距离”计算会失去意义基本上全被量纲大的目标主导整个算法的多样性形同虚设。所以项目里的标准化步骤是必须的它不是锦上添花而是NSGA-Ⅲ能否正常工作的前提。3.3 环境选择从前沿面到固定种群规模环境选择是每一代进化结束时做的事情从“父代子代”的合并种群中选出N个个体组成下一代。这部分具体流程是第一先对合并种群做非支配排序把所有个体划分成F1、F2、F3……若干个非支配层。第二从F1开始逐个整层纳入下一代种群一直加到某一层Fk时累计数量首次超过N。第三此前已经纳入了F1到F(k-1)层数量可能已经接近N了现在需要从Fk中挑选一部分个体凑满N。这一步就是参考点发挥关键作用的地方——统计Fk中每个个体和哪个参考点关联优先选择那些还没有被已入选个体覆盖的参考点所关联的个体。具体到代码层面这个挑选过程不是简单地从Fk里按随机顺序取个体而是要频繁地重算每个参考点已被关联的个体数量。这是整个算法里最费时间的部分但它保证了算法“宁可从临界层多选一点也不要把种群集中在某个区域”。我的调试经验是写这部分时把“每个参考点被关联个体数”做成一个数组每次选择后更新循环里只用这个数组做判断这样逻辑不容易出错运行速度也快一些。3.4 交叉变异参数设置这个项目的遗传操作采用模拟二进制交叉SBX和多项式变异PM这在连续变量优化里是最常见的搭配。SBX的核心参数是分布指数eta_c它控制子代与父代的接近程度。eta_c越大子代越可能在父代附近搜索越局部化eta_c越小子代越可能远离父代搜索范围越大。项目里通常设eta_c为10到20交叉概率0.8到0.9。PM的分布指数eta_m在20左右变异概率取1/nVar比较合适——每个变量有大约1/变量总数的概率发生变异这样既保证了变异的全局搜索能力又不至于让种群变成随机噪声。如果你使用的是Matlab自带的多目标优化工具箱配合自定义问题可以直接用gamultiobj或者paretosearch但我个人的体验是工具箱毕竟是通用实现自由度受限尤其是参考点数量、归一化方式这些细节不好定制。项目里用完整手写NSGA-Ⅲ的实现虽然代码量稍微大一点但对算法每一处行为的掌控力要强得多。做研究要改算法模块、加约束、换目标手写版本的灵活性是工具箱比不了的。4. Matlab代码实现结构与关键函数拆解4.1 代码目录结构项目代码的核心组织方式大致如下├── main.m % 主程序入口 ├── ProblemDef/ │ ├── ObjectiveFunction.m % 目标函数 │ ├── Constraints.m % 约束定义 │ └── ProblemParams.m % 问题参数与数据 ├── Algorithm/ │ ├── NSGA3.m % 算法主循环 │ ├── GenerateReferencePoints.m │ ├── NonDominatedSort.m │ ├── AssociateToReferencePoint.m │ └── EnvironmentalSelection.m └── Plot/ ├── PlotParetoFront.m └── PlotScheduleResult.m这个结构是我见过比较舒服的一种把问题定义、算法主体、后处理分开。万一你要测试不同问题比如换成风-光-水-火联合调度只需要在ProblemDef里改数据算法部分完全不用动。4.2 主循环的关键逻辑下面是算法主循环的最简化结构保留核心步骤注释也写清楚了每步在干什么for gen 1:maxGen % 生成子代种群 offspring zeros(popSize, nVar); for i 1:2:popSize % 锦标赛选择两个父代 p1 tournamentSelect(P, 2); p2 tournamentSelect(P, 2); % SBX交叉 [c1, c2] simulatedBinaryCrossover(p1, p2, eta_c); % 多项式变异 c1 polynomialMutation(c1, eta_m); c2 polynomialMutation(c2, eta_m); % 变量边界修正 c1 boundaryFix(c1, lb, ub); c2 boundaryFix(c2, lb, ub); offspring(i,:) c1; offspring(i1,:) c2; end % 合并父代和子代目标与约束计算 combinedPop [P; offspring]; combinedF evaluateObjectives(combinedPop); % 环境选择生成下一代 P environmentalSelection(combinedPop, combinedF, refPoints, popSize); end这里的tournamentSelect就是用锦标赛方式从父代挑两个较优个体。所谓“较优”不只看非支配层还看参考点关联的小生境计数。在NSGA-Ⅲ中锦标赛选择的标准一般是非支配层编号小者优先层编号相同时优先选择所在小生境个体数量少的那个。这样做能有效避免种群在进化早期就过早收敛到某一区域。耦合到问题的部分是evaluateObjectives函数它对每一个个体调用前面说的目标函数计算模块依次算出总煤耗、总排放和水位偏差三个值。实测中这个评估函数在全部计算耗时里占比往往过半因为它需要把染色体解码、计算水库过渡过程、再算火电分配。如果读者在做更大规模的问题可以用向量化计算或者并行池加速。4.3 关键函数的实现细节环境选择环境选择是整个算法中代码最绕的部分我在这里把逻辑拆分一下第一步对合并种群做非支配排序得到一系列层F1、F2、F3……第二步把F1层开始整层加入新种群newPop直到加入某一层Fk后数量超过popSize记下这一层为临界层并且计算此时newPop中每个参考点关联个体数。第三步从Fk中挑出需要补足的那部分个体先对Fk里的个体做目标归一化找出它们各自最近的参考点然后优先选那些关联数最少最好是0的参考点对应的个体进入新种群。有一个我在调试时踩过的坑如果临界层里的个体都集中在某几个参考点附近那么参考点关联的“最少个体数”可能永远不为0这样算法会把同一参考点附近的其他个体也拉进来导致该区域密度过高。一种有效的应对是引入参考点使用次数的“小生境保护”逻辑即使临界层个体数量不够也优先从它的近邻参考点里补充而不是追求完全填满。虽然这会稍微损失一点纯多样性但换来的是种群不会因为某个区域实在找不到合适个体就一直出现空白。4.4 数据与结果的输入输出项目数据里一般包含这几张表系统负荷曲线24个时段单位MW、火电机组参数煤耗系数、排放系数、出力上下限、水电站参数库容上限下限、发电流量范围、水头-库容关系曲线、梯级拓扑关系上游出流到下游入库的延迟时间。用Matlab实现时数据组织成结构体是最直观的方案sysData struct(); sysData.loadProfile load24h; % 1x24 系统负荷 sysData.thermal struct(a, a, b, b, c, c, ...); % 火电参数 sysData.hydro struct(Vmax, Vmax, Vmin, Vmin, ...); % 水电参数输出结果时至少应该包括这些图Pareto前沿三维散点图3个目标、折中解的水火电出力过程配合负荷曲线、水库水位过程线。Pareto前沿图能直观展示解的分布质量调度曲线能说明解在实际中可不可行水位过程线能揭示梯级水电的运行状态是否健康。项目里这三张图都会画我认为对于写论文和实际汇报非常关键。5. 实验结果分析与决策偏好5.1 从三维Pareto前沿面提取决策信息跑完算法得到的是一组Pareto解。比如3目标场景每个解对应一组煤耗排放水位偏差画出来的就是一个三维前沿曲面或者它的二维投影。有经验的人看这图基本能感受到3个目标之间的制约程度煤耗小的时候排放是不是也小两者如果呈强正相关性说明这两个目标之间的冗余度高反之如果两者在某个区域呈反向走势那就是典型的折中区域决策者要在这里做选择。我个人在做结果分析时的经验是首先要选一个“折中解”最常用的方法是用最小距离法到理想点。理想点的定义是每个目标独立最优时形成的点这个点真实存在但通常不可达。计算每个Pareto解到理想点的欧式距离注意要先对每个目标做归一化否则数值小的目标会被忽略距离最小的那个解就是最合理的折中解。这在Matlab里实现非常简单ideal min(F_pareto, [], 1); % 各目标理想值 normF (F_pareto - ideal) ./ (max(F_pareto, [], 1) - ideal); dist sqrt(sum(normF.^2, 2)); [~, idx] min(dist); bestSolution F_pareto(idx, :);用这个方法选出来的解在多个目标间平衡得最好比从图里肉眼挑或者凭感觉拍板要靠谱得多。5.2 收敛性与分布性指标除了用肉眼看图用数值指标量化算法效果也是学术报告和论文里几乎必须有的内容。常用的有超体积指标HV、世代距离GD、反转世代距离IGD和间距指标SP。项目代码里至少实现了IGD和HV这是多目标优化论文的两个标配指标。IGD的含义是真实Pareto前沿上的每个点到算法所得解集的最小距离的平均值。IGD越小说明解集越接近真实前沿。但问题在于梯级水火电调度这种真实工程问题我们通常不知道真实Pareto前沿长什么样这种情况下一般用参考前沿代替参考前沿可以是所有算法多次运行结果合并后再做一次非支配排序取第1层的集合或者用已知的基准函数前沿。HV则不需要参考前沿它计算解集在目标空间里覆盖的体积。HV越大说明解集越“丰满”综合表现越好。Matlab里用凸包排除法可以计算三维情况下的HV代码量不大但要注意坐标平移把目标值转为正数再算否则结果会失真。实际使用时我的建议是论文里两个指标都放HV评价综合质量IGD评价收敛精度双指标配合不容易被审稿人挑毛病。而且不同算法之间的比较一定要用同一套参考前沿否则IGD没有可比性。5.3 多方案比选与决策偏好分析有时候光有整体权衡还不够工程师在现场还要看具体解。项目做结果分析时可以从Pareto解集里挑三个极端解和一个折中解进行对比最小煤耗解经济性最好火电比例相对低梯级水电发得比较满。最小排放解环保性最好可能多用了水电但某些时段煤电的压负荷导致系统运行约束更紧。最小水位偏差解水位最稳水电在接近目标水位的区间运行灵活性利用最少。折中解三项指标都不冒尖但也都不落后。把这四个解放在同一张图上对比能很直观地看出来不同偏好下的调度方案差异。比如经济最优解可能让梯级水电长时间满发下游水库水位偏高影响生态流量环保最优解让火电频繁调节出力爬坡压力大。这种对比分析是论文讨论部分的重要素材也是实际调度部门做方案预评估时最关心的内容。6. 复现和调参过程中的常见问题与排查技巧6.1 种群规模与参考点数量的匹配NSGA-Ⅲ的种群规模和目标数、参考点划分份数p强相关。如果目标数M等于3p等于10参考点数约66个那么种群规模最好设置在70到100附近。如果种群规模太小比如只有40那么环境选择阶段会出现大量参考点根本没有个体关联的情况算法多样性保障就是空转如果种群规模太大比如500计算量不必要的成倍上涨而且参考点的细分粒度跟不上种群规模最后多样性反而下降。我的经验是先用参考点总数估算一个基础值再把种群规模取成参考点总数的整数倍加一点余量。具体来说参考点总数约等于种群规模稍微多一点或接近相等效果最好。你不需要每一代都精确地让每个参考点都刚好分到一个人但整体数量级一定要对。6.2 目标归一化导致的Pareto形状变形归一化方法是NSGA-Ⅲ里绝对不能忽略的环节。项目里用的是理想点和极端点定义的超平面做归一化。极端点可以通过计算各目标的最小值对应的轴截距获得。如果极端点搜索不准——比如某些目标在进化早期根本没有接近极值——那么整个归一化超平面都会歪掉后续参考点关联、环境选择全都跟着歪。一个实践里很有效的改进是归一化用的极端点不应只从当前种群找而应该用历史所有发现过的极值点来估计也可以每隔若干代重新计算一次极端点而不是每一代都重算。我在复现时发现如果不做这种“极端点记忆”在进化早期前沿面尚未完全打开时归一化很容易把种群引向偏差方向。加记忆机制之后算法激进性明显下降特别是在目标动态变化大的场景下收敛稳定多了。6.3 约束违反程度与算法搜索效率的矛盾约束太多太强会让进化算法在可行域外围徘徊很难深入内部找到好解。项目里为约束做了分类处理这个思路值得保留等式约束尽量通过变量代换消去简单范围约束在初始化时直接限定只有真正没法转换的复杂约束才用惩罚法。通过这种方式算法搜索空间中的可行域占比大幅提高收敛速度自然就上去了。如果你确实遇到约束占比太大、算法半天找不到可行解的问题一个可以临时用的招是先放松约束跑几百代等种群整体进入目标前沿附近后再逐渐把约束加严在生态进化意义上叫“可行性逐步收缩”。这种手段在代码里实现并不复杂无非是加一个随代数变化的约束放大系数但效果往往立竿见影。6.4 Matlab代码调试的几个常见坑Matlab跑这类程序最常见的坑我列一下自己遇到最多的情况第一数组维度不匹配。目标函数里火电出力是1x24行向量但水电流量算出来是3x24矩阵直接拼接成染色体流进算法主循环后如果解码时维度对不上报错很难查。建议在解码函数第一行加上维度断言测试的时候很快就能定位问题。第二参考点生成顺序和个体关联顺序不一致。参考点生成的先后顺序有讲究但很多人没注意结果环境选择里每次都把个体匹配到错误的参考点上。建议把参考点生成函数跑完后打印几个参考点坐标人工检查一下同时把参考点存储顺序固定下来不要在每次运行时重新排列。第三多目标优化结果不稳定。遗传算法的随机性是天性但不同随机种子结果差异太大就说明出了别的问题。如果跑十次的结果前沿形状都不一样优先检查是不是种群规模太小、代数太少或者交叉变异概率设置不合理。一般把代数至少跑到500代以上再观察前沿稳定性。6.5 关于代码效率的优化技巧当系统规模变大、时段数增多时评估一次种群可能要好几秒跑500代就是几千秒这个等待时间很折磨人。项目代码里做了几个优化值得借鉴首先是向量化计算。Matlab循环是出了名的慢尽量把目标函数里的求和、乘除都写成矩阵运算。比如计算火电煤耗直接把整个种群的火电出力矩阵带进二次函数表达式里一次算完不要一个个个体用for循环算。用矩阵运算和用循环的差距有时能达到几十倍。其次是候选解缓存。进化的后期大量个体会重复或者非常接近直奔目标函数算太浪费。可以维护一个Hash表把已经算过的解的目标值存下来遇到相同或极其接近的个体直接查表返回能省不少时间。虽然大部分情况下个体不是严格重复但接近重复的确实不少实测能节省10%到20%的时间。最后是并行计算。Matlab的parfor可以直接用在种群评估循环上但要注意随机数种子管理否则并行后每次跑出来的结果不一致程度会增大。这个优化要谨慎最好在算法基本调通后再开并行。7. 扩展方向这套框架还能往哪里用这个项目虽然目标是梯级水电和火电联调但它的方法论迁移性其实很强。我自己在这个项目的基础上扩展过几个方向这里分享给大家参考。第一个方向是加入风、光等新能源。风电、光伏出力受气象条件影响大具有间歇性和不确定性。在目标函数里加入新能源出力追踪或者弃风弃光惩罚项之后NSGA-Ⅲ的参考点机制在高维目标下依然能保持很好的分布性。我在扩展实验里增加到5个目标煤耗、排放、水位偏差、弃风弃光惩罚、火电爬坡标准差解集质量依然不错这要是用NSGA-Ⅱ来做效果就差强人意了。第二个方向是考虑日前市场的电价信号做一个经济调度和报价策略的联合优化。这时候目标可以改为购电成本最小、市场收益最大、风险最小约束条件里加入潮流约束或备用容量约束。NSGA-Ⅲ天然适合这种多目标多约束的组合优化场景。第三个方向是改成滚动时域调度模型不是一次优化24小时而是每几个小时滚动一次每次更新来水和负荷预测数据。配合NSGA-Ⅲ的快速求解能力基本能控制在可接受的计算时间内对实际运行很有参考价值。第四个方向是如果读者想通过强化学习做调度这套NSGA-Ⅲ代码可以作为专家示范数据生成器。用NSGA-Ⅲ算出的Pareto最优解集来训练智能体做出力决策这在近期学术圈是一个挺有想象力的方向。8. 写在最后的实践心得这个项目前后我反复跑了很多轮。从最开始的复现调试到后来改参数、加目标、换场景整体走下来最大的感受是NSGA-Ⅲ这种算法不是拿来即用的黑盒子它每一处设计都有明确的物理和数学含义。参考点划分份数影响的是解的分布偏好种群规模和参考点数量的匹配影响的是搜索的覆盖广度目标归一化的准确性影响的是进化过程是否跑偏。把这些细节一个个吃透才算真正掌握了这门工具。另外一点代码本身的工程组织方式也非常重要。做这种研究类项目最忌讳的是把所有代码糊在一个m文件里。模块化写清楚目录、函数名、注释后期调试和更换场景的效率能翻倍。我在一开始偷懒把目标函数都放在主循环里等要加一个新目标时改得头皮发麻重构回模块化结构之后整个世界清净了。如果你正在复现或打算复现这个项目我的建议是不急着直接上大算力先在一个小规模测试案例上把算法跑通、把Pareto前沿调出来然后逐步增加机组数和时段数。优化算法这东西小规模能跑通不代表性规模能跑好但小规模能迅速暴露逻辑bug比直接上大规模调试要高效得多。最后再分享一个小技巧调试时每100代保存一次种群中间结果到mat文件不要等到全部跑完再统一保存。有一次我数据算到900代停电没保存全部白干从那以后我都是阶梯式保存这个习惯不知道救了我多少次。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑