做电力调度优化的朋友应该都有同感纯水电优化相对好处理纯火电优化也相对好处理可一旦把梯级水电和火电机组放在一起做联合多目标调度问题复杂度立刻上了一个台阶。上游电站放多少水、下游电站能发多少电、火电该在什么时段多出力、成本跟排放怎么平衡——这些变量互相牵扯任何一环单独定下来其他环节都会跟着变。这也是为什么大家越来越关注基于NSGA-Ⅲ优化算法的梯级水电和火电机组联合多目标调度研究这类问题用单目标优化根本没法给出能落地的方案而Matlab代码实现又让算法变得可以快速验证和迭代。这篇文章我不打算复述论文里那些套话而是从建模、算法、代码实现和调参踩坑这几块把我实际做过的一整套流程掰开讲清楚。先说明一点这篇文章面向的是有一定电力系统或智能优化基础、但还没真正把一个NSGA-Ⅲ调度代码跑通的读者。我会先拆解梯级水火联合调度这个问题本身再讲清楚为什么选NSGA-Ⅲ而不是NSGA-Ⅱ然后重点讲Matlab代码怎么写最后分享几个我实测下来最容易让结果失真的细节。1. 梯级电站不是孤立机组水头、流量与库容的联动约束1.1 水力耦合为什么会让调度问题变难一条江上建了三座梯级水电站表面上它们是三个独立电站实际上水力联系让它们变成了一个整体。上游电站某时段加大出库流量这部分水并不会立刻出现在下游电站的入库断面而是有一段流达时间下游电站入库流量变了库水位就会跟着变而水位一变发电水头也变了单位水量能发出的电自然就变了。这个连锁反应在数学上表达起来很麻烦因为它同时包含了空间耦合和时间耦合。空间耦合指的是上下游电站之间的流量传递关系上游出库流量减去区间来水经过一定时滞构成了下游电站的入库流量的一部分。时间耦合则是水库自身蓄水与放水的动态平衡本时段库容等于上一时段库容加上入库流量再减去出库流量这些状态量在相邻时段之间形成了强关联。写调度模型时这两层耦合都必须显式刻画不然你跑出来的所谓最优调度方案在实际中根本行不通。我在实际建模时把这些关系拆成了四组约束——水量平衡约束、库容与水位上下限约束、出库流量约束、电站出力约束。其中最难处理的是水量平衡约束因为它是一个等式约束而且贯穿整个调度期。很多初学NSGA-Ⅲ的人都会在等式约束处理上栽跟头后面我会专门说这个事。1.2 火电与水电机组在调节能力上的天然差异水电的特点是响应快、启停灵活、成本低但受来水限制和水库调节能力限制不能无限发。火电的特点是出力可以比较稳定地控制但机组有最小技术出力、有爬坡速率约束、有最短启停时间约束而且燃料成本和碳排放都跟出力水平呈非线性关系。联合调度要解决的本质上是谁来承担系统负荷波动的问题水电尽量在高峰时段多发、火电在低谷时段压低出力但火电压低出力的速度不是无限快的水电提高出力的能力也不是无限大的。这个问题的复杂度还体现在负荷平衡约束上。任意时段所有水电站出力加上所有火电站出力必须等于系统负荷需求。我最初做实验时遇到的一个典型困境是某时段负荷很高水电站因为上游来水不够或者库容受限最大可发功率凑不满负荷缺口火电又因为爬坡约束不能马上顶上去——这个时候调度方案里会出现大量不满足约束的个体。NSGA-Ⅲ本身并不会帮你处理这些约束它只负责在可行域内寻找Pareto非支配解所以约束处理策略在整个代码里至关重要。1.3 联合调度问题的决策空间规模预估假设一个典型算例3座梯级水电站、2台火电机组、调度周期24小时。如果以1小时为时段决策变量就是每座水电站在24个时段的出力值加上每台火电机组在24个时段的出力值合计3×242×24120维。你想想看一个120维的实数变量空间还要同时满足时间耦合的等式约束、不等式约束和变量边界可行域的几何形状会非常复杂。我见过不少同学用NSGA-II做同样的问题结果在高维目标下表现很差原因我下一节展开。总之先记住一个结论这类问题不是随便拿个多目标算法就能跑的算法选型、编码设计、约束修正三者缺一不可。这也是为什么现在很多人做梯级水电和火电机组联合多目标调度研究时都会优先考虑用NSGA-Ⅲ因为它在高维目标场景下保持解集多样性的能力确实更好。2. 目标函数的选择成本、排放与弃水风险的三方博弈2.1 第一目标系统运行成本多目标调度第一件事情是想清楚要优化哪几个目标。我见过很多论文把目标堆得很多四个五个甚至更多但实际跑下来发现目标之间高度相关不仅没有带来更多有效信息反而让算法搜索空间被稀释。我的建议是先用三个目标把系统的核心矛盾覆盖住。第一个目标是系统运行成本主要由火电机组的煤耗成本构成。火电煤耗和出力之间通常用一个二次函数近似单台机组在t时段的成本可以写成a乘以出力平方加b乘以出力加c的形式a、b、c是机组特性参数调度周期内所有火电机组求和就得到总成本。水电机组的发电成本很低通常简化为一小笔维护成本跟电量成线性比例在总成本里占比很小但建模时建议保留方便后续扩展成水火电竞价模式。我之前看到有些资料直接忽略水电成本只算火电煤耗这在纯技术研究里成立但如果这套代码后续要接经济调度或者现货市场模拟水电成本就不好忽略。所以我的建议是一开始就把成本项写全后面想去掉只是注释一行代码的事想再加回来却要重新梳理整个目标函数的结构。2.2 第二目标燃煤排放总量第二个目标是碳排放总量或者污染物排放总量具体用CO₂折算系数就行。排放量和火电出力之间同样有一个非线性关系常见处理方式是每个机组给一组排放系数α、β、γ排放是关于出力的二次函数。这样处理的最直接好处是排放成本函数和煤耗成本函数在形式上保持了一致性代码实现上可以复用很多逻辑。需要提醒的是排放目标和成本目标并不完全正相关。同样的总发电量如果让煤耗高但排放系数低的机组多发电可能成本升高但排放相对降低反过来也一样。再加上水电出力在不同时段的分布不同水火之间的出力置换空间就会出现很多Pareto解这正是多目标优化想要的局面。如果两个目标完全正相关那跑出来的前沿就退化成一个点算法再先进也没意义。2.3 第三目标梯级电站的弃水风险惩罚第三个目标我建议用弃水量最小化或者期末蓄水量最大化。为什么选这个因为梯级水电调度不仅要考虑发电还要考虑水资源利用效率。汛期来水大、水位顶到上限的时候多发电可能是最经济的消纳方式但受限于装机容量和电力系统消纳能力有时候不得不弃水。弃水本身不产生任何收益代表着水资源浪费所以把弃水惩罚列为第三个目标调度方案才会尽量避免这种浪费。在代码里弃水量不需要单独建一个复杂的物理模型。你可以通过水量平衡方程反推假设某时段水位已经达到上限且入库流量大于最大可发流量那么多出来的部分就是弃水。或者更简单一点在约束处理时对超出库容上限的水量进行惩罚计入目标。我实测下来用后一种方式做目标函数的连贯性更好具体是把这个惩罚项乘以一个较大权重加到第三目标里还是作为一个独立的最小化目标取决于你的目标个数设计。2.4 目标之间的冲突关系验证我建议在正式跑NSGA-Ⅲ之前先做一个简单验证固定其中两个目标的最优解看第三个目标的变化趋势。什么意思呢比如固定系统负荷分配模式只调整水电在不同时段间转移电量你会发现成本目标、排放目标和弃水风险目标并不是单调同向的——这才是值得用多目标优化的前提。一个反例是我实验室A同学踩过的坑他把三个目标中的两个设成互相包含的关系第三个约束又限得太死结果跑出来的Pareto前沿几乎是一条竖线解和单目标没区别。他调了两周都没有改善后来一排查发现是目标函数里把弃水量同时算进了成本和排放导致双重计算解集多样性被系统性地消掉了。这种细节特别容易忽略所以我每次写目标函数之前都会先拿两个极端解的数值算一遍确认三个目标不会因为同一个原因同步上升或同步下降。3. NSGA-Ⅲ的参考点机制它比NSGA-Ⅱ强在哪里3.1 NSGA-Ⅱ拥挤距离在高维目标下的失效很多人的第一反应是我有现成的NSGA-II代码直接用不行吗NSGA-II在多目标优化领域确实经典它靠拥挤距离来保持解的多样性。但这里有一个很关键的问题拥挤距离在2目标和3目标下表现尚可到了更高的目标维度上它会出现非常明显的失效。原因并不难理解。拥挤距离本质上是把解按目标值排序后计算两个相邻解在各目标方向上的归一化距离之和。可以把每个解想象成分布在目标空间里的若干个点拥挤距离描述的是这个点周围有多挤。目标维度升高以后空间体积的增长速度远超解的个数增长速度几乎所有解之间的距离都变得很大拥挤距离的区分度就急剧下降——解与解之间谁更拥挤基本分不出来了。更麻烦的是NSGA-II在选择下一代的时候如果同一非支配层的个体数量超过种群规模它会靠拥挤距离来淘汰一部分当拥挤距离区分度不足时淘汰机制就近似随机选择算法进化方向变得随机最终得到的解集在目标空间的分布很不均匀。这类现象在梯级水火联合调度这种高维目标问题里特别常见所以我最后把算法换成了NSGA-Ⅲ。3.2 参考点集的生成方法与代码实现NSGA-III核心改进在于用参考点替代拥挤距离。参考点本质上是目标空间里均匀分布的一组方向向量算法会要求解集尽量均匀地靠近这些方向从而让解在目标空间里整体铺开而不是挤在某一小片区域。参考点集生成最常用的方法是Das-Dennis方法。假设目标个数是M把每个目标轴划分成H等份那么参考点的数量由组合数C(HM-1M-1)决定。比如M3、H12时参考点数量为C(142)91如果把H增大到20参考点数量就是231。所以在设定H的时候心里要有数H越大解集分布越细但计算量也越大需要的种群规模也要相应增大。这里给一段Matlab风格的参考点生成思路帮助大家理解它其实不神秘% 示意代码递归生成参考点权重向量 function ref_points generateReferencePoints(M, H) if M 1 ref_points 1; else % 对归一化超平面上均匀取值生成和为1的权重组合 % 实际实现可参考nchoosek组合枚举 归一化处理 end end真正实现时要注意生成的权重向量必须满足每个维度非负且总和为1否则后续归一化和关联计算会对不上。标准实现里会用nchoosek枚举组合数再转成权重矩阵网上开源实现很多建议找MathWorks社区的几份成熟函数对比一下。3.3 归一化、极值点与关联选择参考点有了怎么用它来指导选择这一步是NSGA-Ⅲ的精髓分两条腿走先做归一化再做关联选择。归一化的目的是消除不同目标之间的量纲差异。成本可能是几十万量级排放可能是几万吨量级弃水可能只有几千立方米量纲差别巨大如果不归一化参考点的均匀分布就没有意义。归一化需要先计算理想点——也就是每个目标当前的全局最小值然后平移目标空间接着计算每个目标方向的极值点用极值点构造超平面再算出超平面在每个目标轴上的截距用截距来做缩放。关联选择则是把种群中的每个个体映射到目标空间中的某个参考线方向通过计算个体到参考线的垂直距离找到离它最近的参考点统计每个参考点关联的个体数。在选择下一代时优先保留生态位关联个体数少的参考点附近的个体这样就能鼓励算法探索那些还没被充分覆盖的方向。这两部分我在实际写代码时最常出bug的就是截距计算。极值点要用ASFAchievement Scalarization Function来找函数里会涉及一个很小的常数项eps部分实现为保证数值稳定性还会加一个极小的罚项。很多代码跑着跑着出现NaN就是因为某个目标的极值点定义没处理好截距算出来是负值或者无穷大。这类问题一定要用单元测试去验证——拿一个已知Pareto前沿的测试函数比如DTLZ系列先跑通再移植到梯级水火调度问题上。4. Matlab实现中的决策变量编码与约束修正4.1 决策变量结构设计Matlab代码落地时第一个决策就是决策变量的编码方式。我强烈建议直接用实数编码不用二进制编码因为调度问题天然是连续变量。这里唯一的争议是决策变量到底编码成各电站各时段的出力还是各电站各时段的发电流量。我两个方案都试过最终推荐用发电流量作为决策变量。原因非常简单梯级电站之间存在水量平衡约束如果直接用功率作为决策变量那么功率到流量的换算是非线性的受水头影响你很难保证相邻时段的水量衔接而如果用流量作为决策变量入库流量和出库流量都明确库容变化过程非常流畅水量平衡约束的校核就变成简单的累加校验。功率可以后算出来不影响决策空间。决策变量结构我建议组织成一个矩阵的扁平化版本3座水电站×24时段2台火电机组×24时段共120维。初始化时对每个维度按上下限均匀随机生成但有一个技巧先用一个简单的负荷平均分配方案作为初始个体混入种群。这个操作能让算法一开始就有一个相对不坏的起点大幅提升收敛速度。别小看这一个体它在很多算例中能帮助算法在200代内就找到边界区域而不是靠随机初始化从零摸索几千代。4.2 负荷平衡约束与水量平衡约束的处理约束处理是整个代码的关键。先说最简单的一类——变量边界约束。决策变量本身的上下界很好处理越界就拉到边界俗称裁剪法。这是NSGA-Ⅲ代码里最简单的步骤但要注意裁剪时机如果在交叉变异之后马上就裁剪会影响变异算子的搜索步长分布如果放在目标函数计算之前统一处理则实现起来更清晰我建议用后者。再说等式约束这才是难点。负荷平衡是一个等式约束要求所有水电机组和火电机组在任意时段的出力总和等于系统负荷。这个约束的常用处理思路是合并调整法先固定水电机组的出力让火电机组去补齐剩余的负荷缺口如果火电也越界了再反过来调整水电。总之要选一个优先级由调节能力强的机组先做确定性补偿剩下的误差再用罚函数兜底。水量平衡约束的处理则需要区分瞬时出库流量和时段平均出库流量的概念。实际模型里我们可以用各时段初末库容的平均值来近似计算发电水头同时用下泄流量递推库容变化。如果调度期只有24小时水分流达时间小于1小时那就可以忽略时滞不做跨时段延迟但如果瀑布群之间距离远、流达时间达到2到3小时就必须在水量平衡方程里加入时滞项否则下游电站的库容变化过程会和实际严重错位。关于时滞的实战细节我在第6节单独讲。4.3 主循环框架从初始化到收敛一个可复现的NSGA-III主循环通常由五个模块组成种群初始化、目标函数计算与约束修正、非支配排序、参考点归一化与关联选择、交叉变异产生子代。这里给出主循环的骨架思路方便大家对号入座% NSGA-III主循环伪代码 for gen 1:maxGen % 交叉变异生成子代种群 offspring mutation(crossover(parents)); % 父代与子代合并 combined [parents; offspring]; % 计算目标与约束修正 [objs, cons] evaluate(combined); % 代入NSGA-III选择机制选出下一代 parents nsga3_select(combined, objs, cons, refPoints); end这个主循环看起来简单但有两个容易翻车的细节。第一个是交叉变异的对象。如果决策变量是120维实数向量SBX交叉和多项式变异是默认选择。SBX的分布指数一般取15~20多项式变异的分布指数取20左右变异概率可以设为维数的倒数也就是1/120左右。这些参数在大量文献里都验证过比较可靠。第二个是精英保留是否真的保留了。NSGA-Ⅲ的精英保留体现在父代和子代合并后统一进行非支配排序和选择。这个逻辑我见过不少人写错他们用了父代挑一半、子代挑一半然后合并的方式这样保留了上一代的所有个体但子代中本该有机会进入的个体被提前淘汰了探索能力大打折扣。正确做法一定是先合并再选择你可以在代码里加一个早期代目跟踪观察历史最优解有没有震荡如果震荡明显大概率是选择机制写错了。5. 参数配置与实验设计种群、迭代和参考点怎么配5.1 种群规模和迭代次数的匹配关系NSGA-Ⅲ对种群规模有一个隐性要求每个参考点附近至少要分配到一定数量的个体才能让解集均匀覆盖目标空间。所以种群规模不是拍脑袋定的而是要看参考点的个数。参考点数的计算方式前面提到过如果M3、H12参考点为91个那么种群规模原则上不应低于91太少了分布性无从谈起。实际使用中由于部分参考点方向在目标空间中可能没有可行解种群规模通常会设置成参考点数的2到3倍。比如91个参考点种群规模设到200左右比较稳妥迭代次数则要看问题的非线性程度一般300到500代能收敛到比较稳定的前沿形态复杂约束场景可能要600代以上。我建议做实验时不要只跑一个配置而是至少跑三组150代、300代、500代对比Pareto前沿的收敛程度用超体积指标HV来量化。5.2 Division参数对解的分布影响Division参数H对解集分布的影响值得单独说一说。H越大参考点越多解集在目标空间里的分辨率越高但也带来两个副作用一是计算量增大二是对归一化精度更敏感。我做过一组对比实验H从8增加到12时解集分布明显更均匀前沿的分层感更好但H从12增加到16时改善就非常有限计算时间却涨了接近40%。这里还容易踩一个坑如果某个目标方向上的Pareto前沿很短过密的参考点会导致大量参考方向没有解关联种群多样性反而下降。解决办法是让H匹配目标空间的真实形状通常三目标问题取10~15是比较合理的区间。如果发现Pareto前沿上出现大片空白不要急着加H先检查是不是归一化或者极值点计算出了问题。5.3 交叉变异算子的参数设置SBX交叉和多项式变异这两个算子直接决定搜索的步长特征参数设置不当会让算法要么过早收敛要么退化随机搜索。我给的实用参数范围SBX的分布指数为20交叉概率0.9左右多项式变异的分布指数为20变异概率按维数倒数取。有一个实验现象值得留意当优化模型中含有大量活跃约束时变异概率不能设置太高因为变异会频繁破坏约束修正机制刚刚建立起来的可行结构。我在实验中把变异概率从1/120提高到1/60时HV指标显著下降说明过强的变异让种群在可行域边缘反复震荡。相反如果问题相对松弛适当提高变异概率可以帮助跳出局部区域。这个松弛度不好预先判断只能通过灵敏度分析确定。6. 跑完代码之后的教训三条最容易让结果失真的细节6.1 水位-库容曲线的拟合边界问题我最初实现目标函数时用了一个插值表来表示水位-库容关系看起来没什么问题查表、线性插值果断干脆。但跑出来的调度方案出现了一个非常明显的病态现象某些时段的库容值恰好落在插值表的边界上——表的最左边或者最右边——导致水位计算值不连续发电水头出现异常跳变目标函数值也跟着毛刺频出。查了一晚上才定位到原因我用的库容-水位曲线数据范围本身有限而随机初始化的决策变量经常会越出这个范围。NSGA-III的裁剪机制只保证决策变量在边界内——这里注意它保证的是流量边界并不保证库容边界库容边界是由水量平衡方程推出来的。也就是说即使流量值合法经过多时段累加后库容也可能超出拟合数据范围。解决方案是在目标函数计算里加一个水位计算保护如果库容超出拟合表范围就用表端点的斜率外插而不是线性插值表返回NaN或者截断值。这个细节看上去不起眼但在优化问题上影响巨大因为目标函数的不连续会把原本平滑的Pareto前沿硬生生切出很多不可解释的凹陷。6.2 流达时间的处理方式前面说过流达时间再讲一个更隐蔽的问题。如果梯级电站之间的流达时间是2小时而你用1小时时段建模那么上游某时段的下泄流量要经过2个时段才完全反映在下游入库流量里。这个部分水量延迟的处理方式初学者经常弄成要么延迟0个时段要么延迟2个时段的方波式处理跟实际相差很大。比较合理的做法是按比例分配将上游某个时段的下泄流量拆成两部分一部分在1小时后到达另一部分在2小时后到达。具体拆分比例可以根据河道长度和流速粗略估计通常用0.5/0.5或者0.3/0.7起步再根据实际调度方案微调。这个处理能让下游电站的入库流量过程平滑很多水量平衡校验的连续性也更好。6.3 罚函数的两个极端最后说罚函数。很多人在NSGA-Ⅲ里用罚函数去处理等式约束最常见的形式是目标函数加上一个大数乘以约束违反量。罚系数设小了不行约束违反量根本不疼不痒算法乐得在不可行域里待着跑出来的方案全是花瓶方案负荷平衡纯靠罚项在凑数罚系数设大了更麻烦目标函数数值量级被抬得很高Pareto支配关系被破坏算法花大量精力去微调那些违反约束的个体而不是探索可行域内的真实最优解。我的经验是罚系数的设定不要用静态常数改用自适应策略——从一个小值开始随着迭代代数逐渐增大让算法前期先自由探索约束边界后期再收紧约束可行性。实现起来也不难只要把罚系数设成初始值的线性增长即可。我实测对比过自适应罚系数方案的HV指标比最优固定罚系数方案还要高出5%~8%左右。另外一个补充建议对等式约束的违反量不要用绝对值之和最好用相对值违反量占该时段负荷或流量的比例这样不同量纲的约束之间可以共用一套罚系数不需要分别标定代码可维护性高很多。7. 往更复杂的方向扩展时需要注意什么如果这套梯级水电和火电机组联合调度代码已经跑通后续想往更贴近工程实际的方向扩展有几点值得提前规划。第一是机组启停变量的引入。当前模型默认火电机组在整个调度期内始终在线只是改变出力水平。但实际中低谷时段火电机组可能关掉一台更经济。引入启停变量后问题从连续优化变成混合整数优化NSGA-Ⅲ的编码和遗传算子都要相应调整直接在原有实数编码上叠加二进制编码会方便很多但交叉变异要分开处理否则启停与出力之间的相关性会被破坏。第二是来水不确定性的处理。梯级水电调度最大的风险来源于来水预测误差尤其是枯水期和汛期交替时。可以引入场景法生成多个来水场景每个场景下运行一次调度模拟然后用期望目标值作为个体适应度。这样解集天然就具备了对不同来水场景的稳健性。第三是目标函数的可解释性。三目标情况下Pareto前沿还可以通过三维散点图直观观察目标数量增加后可视化越来越难需要借助平行坐标图或者热力图来分析解集的分布特征。这种可视化代码建议尽早准备因为调试NSGA-Ⅲ的时候看不到前沿形态几乎是瞎调。个人经验是不要指望一版代码就能直接用于所有电站群。每个流域的水力特性、每个电站的水位库容曲线、每台火电机组的煤耗参数都不相同算法框架可以复用但参数和数据一定要逐一校核。我后期做其他流域算例时光是整理和校验数据就花了一大半时间反而算法本身很少再改。数据质量对多目标优化结果的影响往往比算法参数更大这一点经常被忽视。如果你正准备复现一个基于NSGA-Ⅲ的梯级水火联合调度算例我建议不要从高复杂度问题起步。先把目标函数简化成两目标、约束只剩负荷平衡和水力边界跑通整个循环再逐步加入第三目标、水量平衡和有功爬坡等约束。每加一层约束就用一个已知最优解的简单算例去验证这样排查问题会清晰很多。NSGA-Ⅲ本身其实没什么神秘的把参考点机制的每个环节通过测试函数校验一遍再面对梯级水电和火电机组的联合调度问题时它会变成一个相当顺手的工具。
阅读完成 · 觉得有帮助?