ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

基于NSGA-Ⅲ的梯级水电火电联合多目标优化调度研究

基于NSGA-Ⅲ的梯级水电火电联合多目标优化调度研究 接手这个课题的时候我最初的想法其实很简单梯级水电加火电的调调度把两个目标加权成一个调一调系数交给非线性规划求解器就完事了。实际动手之后才发现事情远没有那么乐观。梯级水电站之间隔着流量滞时火电机组又卡着爬坡速率两个目标各量纲还不统一硬做加权等于把物理约束人为揉成了一团。最后我把方案定成了基于NSGA-Ⅲ优化算法的梯级水电和火电机组的联合多目标调度研究也就是用第三代非支配排序遗传算法在Matlab里实现一个真正的多目标求解框架让调度方案在成本、排放这些互相打架的目标之间整体地找平衡而不是靠拍脑袋定权重。这篇文章会把整个研究过程铺开来讲问题是怎么拆解的数学模型是怎么建的NSGA-Ⅲ为什么在这个场景里比NSGA-Ⅱ更合适以及Matlab代码实现里那些教科书没写的细节。打算复现这个方向的朋友可以照着这个框架直接搭。1. 调度场景拆解梯级水电与火电“联合”到底难在哪1.1 从单一水电站到梯级水电站约束升级先看梯级水电站。它和单个水电站最大的区别在于上游电站的出库流量会经过一段滞时之后变成下游电站的入库流量。这个时间上错开的关系让整个调度模型变成了带时滞的动态规划问题。我在建模时用了标准的库容水量平衡方程对第 i 个水库在第 t 个时段V(i,t1) V(i,t) [ I(i,t) Q_up(i-1,t-τ) - Q(i,t) - S(i,t) ] · Δt其中 V 是库容I 是天然来水Q_up 是上游电站出库流量τ 是水流在两站之间的传播滞时Q 是本电站的发电流量S 是弃水流量。看起来只是一行等式但放到 24 个时段、好几个水库连在一起时决策变量之间立刻产生了时间耦合上游今天多放水下游明天才能用上今天超发库容水位就掉下去了未来时段就得补回来。火电那边则是另一个方向的问题。火电机组有最小出力、最大出力限制还有爬坡速率限制上一小时出力 600 MW下一小时想直接跳到 900 MW运行规程就不允许。机组启停更不是一键切换的事涉及到最小开停机时间。所以我在研究里弱化了启停的整数变量先聚焦在连续出力调度上重点观察多目标优化算法在梯级水电带来的时间耦合和火电爬坡限制下到底能不能稳定收敛。1.2 两个目标的冲突关系成本与排放联合调度的核心矛盾在于水电虽然运行成本极低、几乎没有排放但它受水库水量、库容、生态流量这些硬条件限制火电可以灵活补位但一补位就烧煤成本往上走排放也跟着涨。于是可以自然地设计两个目标系统总运行成本最小化以及火电机组总排放量最小化。这两个目标在数学上不是完全对立的但在绝大多数调度场景里就是互相挤兑的。想少花钱就让水电满发、火电少发想少排放还是得让水电多发、火电少发——听起来好像一致问题在于水电不是你想发多少就发多少枯水期来水不够或者梯级电站间流量滞时对不上水电出力上不去火电就必须顶上去。火电顶得越多成本和排放同时上涨但在不同的时间分布下上涨的比例完全不同。有的机组效率高点成本涨得慢但排放高有的机组装了脱硫脱硝排放低却贵。所以到底让哪台火电机组在哪个时段顶负荷就是多目标优化要回答的问题。我一开始也试过把成本和排放加权成一个综合目标用单一目标进化算法做结果发现权重系数一变解就完全变样。调度人员问“重经济还是重环保”我根本给不出一张完整的可选择的方案表。这就是为什么最终要回到NSGA-Ⅲ这样的真多目标框架——它一次性给出一整条Pareto前沿成本和排放的权衡关系看得明明白白。2. 把调度问题变成NSGA-Ⅲ能解的数学模型2.1 决策变量与状态变量选型建模第一步是分清谁是决策变量谁是状态变量。我的做法是把每个时段每台水电机组的出库流量 Q(i,t) 和每台火电机组的出力 Pthermal(j,t) 作为优化变量。水库库容 V 不是独立决策而是由水量平衡方程推算出来的状态量。这样一个 24 时段、3 个水电站、4 台火电机组的小算例决策变量维数就是 24 × (34) 168 维。这个维度对进化算法来说不算夸张但已经足以让常规数学规划方法在带约束非线性目标下非常吃力。火电侧如果只给出力作为变量还需在评估函数里再判断爬坡约束是否满足。爬坡约束是相邻时段之间的耦合约束本质上和梯级水电的时间耦合类似都会产生大量的不可行解。所以在编码上我没有做特殊修复而是让这些约束体现在目标函数和约束违反度里交给NSGA-Ⅲ的锦标赛选择去淘汰。2.2 目标函数设计成本项与排放项运行成本目标我用了经典的火电成本二次函数加上水电的运行维护成本F1 Σ_t Σ_j [ a_j · Pthermal(j,t)² b_j · Pthermal(j,t) c_j ] Σ_t Σ_i [ k_i · Pwater(i,t) ]其中 a、b、c 是火电机组的煤耗特性系数k_i 是水电站单位出力的运行维护成本通常很小但加上去可以让算法不至于在水量充裕时肆无忌惮地超发也符合实际计费逻辑。排放目标则使用带系数的二次排放函数F2 Σ_t Σ_j [ α_j · Pthermal(j,t)² β_j · Pthermal(j,t) γ_j ]这里的 α、β、γ 可以按烟气排放实测拟合也可以把 CO₂、SO₂ 等折算成综合排放当量。实测中排放函数和煤耗函数往往是同向的但因为机组类型不同二者的曲率不一样所以才会有“选择效率一般的机组少带负荷虽然总煤耗上升但让排放低的机组多带负荷”的权衡空间。2.3 约束集合等式约束与不等式约束我把约束分成三层来梳理调试的时候方便逐一排查。约束类型表达式说明负荷平衡ΣP_water(i,t) ΣP_thermal(j,t) D(t)每个时段必须实时满足水量平衡V(i,t1) V(i,t) 来水 上游滞时流量 - 出库 - 弃水梯级耦合关键库容范围V_min(i) ≤ V(i,t) ≤ V_max(i)兼顾防洪与死水位出库流量范围Q_min(i) ≤ Q(i,t) ≤ Q_max(i)生态流量与机组过流能力火电出力范围P_min(j) ≤ Pthermal(j,t) ≤ P_max(j)机组技术出力区间爬坡速率|Pthermal(j,t) - Pthermal(j,t-1)| ≤ RR(j)·Δt相邻时段耦合负荷平衡是最硬的等式约束。NSGA-Ⅲ的遗传操作产生的是随机亲本组合直接满足负荷平衡的概率几乎为零。我的处理方式是把负荷平衡偏差作为一项罚函数加进两个目标函数中罚系数取到目标量级的若干倍让违反负荷平衡的个体在非支配排序中处于明显劣势。2.4 约束处理策略为什么先用惩罚而不是修复理论上优先修复策略更优雅比如调节水电出力来补足负荷缺口。但实际做下来我发现在梯级水电系统里做修复特别容易“按下葫芦浮起瓢”你调了上游电站的出库下游库容水位跟着变下个时段的可用水量又变了火电出力也得跟着动。一个负荷平衡修复下去可能同时破坏了水量平衡和爬坡约束。所以在第一版实现里我用惩罚函数把约束违反量归一化后乘上惩罚系数加到目标值上。这样实现最快也最容易排查算法本身的问题。等NSGA-Ⅲ框架跑稳定之后我才开始考虑可行解修复的问题比如在交叉变异完成后做一轮负荷平衡微调只调整最后一个火电机组的出力来补缺口。但要注意这种修复会引入少量强方向性偏差处理不当会损害种群多样性。这个我后面在调参章节详细展开。3. 为什么最终选了NSGA-Ⅲ而不是NSGA-Ⅱ3.1 NSGA-Ⅱ在目标维度增加时的多样性短板很多做电力调度的老资料还在用NSGA-Ⅱ因为它在两个目标上的表现非常经典。NSGA-Ⅱ靠非支配排序把种群分层层内靠拥挤距离来保持多样性。拥挤距离描述的是个体在目标空间里和邻居的远近这个指标在两个目标时直观有效一旦目标数增加到三个、四个拥挤距离就很容易失效——高维空间里点之间的位置关系变得复杂靠欧氏距离估算的“拥挤度”常常把角落里的极端点和中间平衡点一样对待导致种群过早收敛到几个孤立的区域。我做这个课题时就想着后续可能会加入第三个目标比如弃水量最小或者水库末水位偏差最小所以不想被困在二维目标里。NSGA-Ⅲ正是为解决高维多样性问题被提出来的它不计算拥挤距离而是用一组在目标空间均匀分布的参考点来引导选择种群多样性由参考点的分布来维持。3.2 参考点机制是怎么维持多样性的NSGA-Ⅲ的参考点由Das-Dennis方法生成。简单理解就是在标准单纯形上等分采样比如目标数 m3、等分参数 H4生成的参考点会均匀铺在目标空间的那个三角形面上。算法每次迭代会先把所有个体的目标值归一化到理想点和极值点确定的超平面内然后计算每个个体到每条参考线的垂直距离把个体关联到最近的参考点。关键来了如果某个参考点周围关联的个体很少那么即使这一层的个体在拥挤距离上不是最优NSGA-Ⅲ也会优先保留它的邻近个体从而保住那些“边角区域”的遗传材料。这就像你招人时按岗位轮着挑每个岗位都要有人而不是只看谁最卷。放到调度问题上就是既能保住“低成本高排放”的极端方案也能保住“高成本低排放”的环保方案中间各种折衷方案都有人占位Pareto前沿因此铺得又广又均匀。我在Matlab里对比过同样迭代次数下NSGA-Ⅱ和NSGA-Ⅲ在这个模型上的表现三个目标时NSGA-Ⅱ的前沿明显出现缺口NSGA-Ⅲ则均匀得多。所以如果你也想加第三个目标直接站在NSGA-Ⅲ上会省很多迭代返工的功夫。4. Matlab实现的具体骨架与核心步骤4.1 数据准备把调度对象变成结构化参数代码实现第一步是整理参数。我习惯用结构体数组存电站和机组参数后期改数据不用动算法代码。% 水库结构体数组每个元素代表一个梯级水电站 hydro(1).Vmin 100; hydro(1).Vmax 300; % 库容下限/上限单位百万立方米 hydro(1).Qmin 0; hydro(1).Qmax 80; % 出库流量下限/上限单位立方米每秒 hydro(1).V0 180; hydro(1).Vend 180; % 初始/末库容 hydro(1).inflow [30, 32, 35, 34, 33, ...]; % 24时段天然来水 hydro(1).lag 0; % 相对于上游的滞时时段数 % 火电机组参数n台机组a/b/c煤耗系数alpha/beta/gamma排放系数 thermal.a [0.0035, 0.0028, 0.0041, 0.0032]; thermal.b [20, 18, 22, 19]; thermal.c [100, 120, 110, 130]; thermal.alpha [0.0022, 0.0018, 0.0026, 0.0021]; thermal.beta [12, 11, 13, 12]; thermal.gamma [50, 48, 55, 52]; thermal.Pmin [100, 120, 150, 110]; thermal.Pmax [600, 700, 800, 650]; thermal.RR [120, 130, 140, 125]; % 爬坡速率限制MW/h再把24时段的系统负荷曲线 D 单独放一个向量。数据量不大但格式统一之后后面写评估函数非常顺手不同算例之间切换就是换个数组的事。4.2 NSGA-Ⅲ主循环的代码骨架网上的NSGA-Ⅲ公开代码有很多版本但基本上都遵循同一个流程我写的时候参考了Deb课题组2014年那篇论文的Matlab实现框架再针对调度问题做了裁剪。% 初始化参数 N 80; % 种群规模 maxGen 500; % 最大进化代数 dim T * (Nhydro Nthermal); % 决策变量维数 R das_dennis(3, 4); % 目标数3等分参数4生成15个参考点 % 初始化种群 pop init_population(N, dim, hydro, thermal); pop evaluate_population(pop, hydro, thermal, loadCurve); for gen 1:maxGen % 锦标赛选择基于非支配排序参考点关联的适应度 parents tournament_select(pop, N); % 模拟二进制交叉 SBX offspring sbx_crossover(parents, eta_c15); % 多项式变异 PM offspring polynomial_mutation(offspring, eta_m20, pm1/dim); % 评估子代 offspring evaluate_population(offspring, hydro, thermal, loadCurve); % 环境选择合并父代子代采用NSGA-Ⅲ选择机制截断到N pop nsga3_select([pop; offspring], R, N); end代码里最重要的就是nsga3_select。它的逻辑是先做非支配排序得到一层层前沿然后从第一层开始往下填直到某层 F_k 填进去会超过 N。这时候对这一层的个体计算参考点关联通过小生境选择把该层补齐到 N。参考线距离、小生境计数这些细节建议直接对照Deb的论文图去理解光看代码容易晕。4.3 评估函数里最容易写错的地方时滞流量我来来回回改了好几版评估函数最常出错的点就是梯级电站之间的时滞流量。假如上游电站当前时段出库流量是 Q_up(t)下游电站的入库时段应该是 tτ。在Matlab里处理时要把上游出库序列按滞时移位后再加到下游入库中。function V_next update_reservoir(h, idx, Qrelease, Qup_shifted, dt) inflow_total h.inflow(idx) Qup_shifted(idx); V_next h.V(idx) (inflow_total - Qrelease(idx)) * dt; end这里有两个小坑。第一个是滞时单位换算如果优化时间步长是1小时滞时是20分钟那就得用插值分配到两个时段里不能直接四舍五入成0时段或者1时段否则调度结果的时序会失真。第二个是上游弃水也要算进滞时传递我一开始只传递了发电流量结果下游库容偏小算出来的蓄能明显偏低折腾了一个晚上才发现弃水那一路根本没进下游入库。不同的代码写法、不同算法对参考点参数的处理也不一样建议拿到任何一版NSGA-Ⅲ代码之后先在一个简单的测试函数上跑收敛确认算法本身没问题再接到自己的调度评估函数上。这一步能省掉后面大量“算法和模型哪个有bug”的排查时间。5. 实际调试过程里最容易翻车的几个点5.1 种群规模与参考点数目不匹配NSGA-Ⅲ的环境选择是按参考点来维持多样性的但种群规模和参考点数目如果不匹配参考点的引导作用会大打折扣。比如我用Das-Dennis方法在三目标问题里取等分参数 H4会生成 C(43-1, 3-1)15 个参考点种群规模如果取 80那么平均每个参考点要挂5个个体如果取 150算起来也没问题但每个参考点挂的个体太多选择压力被稀释收敛变慢。我自己的实践经验是种群规模取参考点数目的整数倍比较稳妥比如15对应30、45、60。两目标问题时 H 取大一些例如 H19生成20个参考点种群取40或者60。这样每个参考点平均只挂2~3个个体选择压力适中Pareto前沿也能铺得开。别把 H 设太大参考点一多关联计算的时间也上去了24时段调度如果 N 超过200Matlab单代跑起来明显变卡。5.2 惩罚系数和目标量级的匹配惩罚函数的系数设定是我调试时最头疼的问题之一。成本目标量级常在百万级排放目标量级常在万级如果惩罚系数取固定值 1e6负荷平衡几乎没有任何压力但如果取 1e9种群会把几乎所有进化努力都花在消约束上目标之间的多样性就没了最后得到的Pareto前沿很窄。我的做法是分两步第一步先关掉约束单独跑几十代统计每个个体两个目标的平均值和标准差得出成本大概在哪个量级第二步再设惩罚系数为两个目标平均量级的2~3倍并且给等式约束和不等式约束分别设不同的惩罚权重。等式约束负荷平衡处罚要重不等式约束爬坡、库容可以轻一些因为不等式约束本身就在目标函数里带有自然倾向性。5.3 时滞耦合下的“伪可行解”陷阱这是我在算例验证时发现的一个很隐蔽的问题。有时候算法给出的解在约束违反度上看起来是零但仔细核对各时段水电出力曲线发现上游电站的出库流量和下游电站的入库流量在时间轴上完全对不上。原因是评估函数里时滞移位做错了导致下游电站的入库凭空多了或者少了等于在偷水量。这种解用惩罚函数检查不出任何问题因为它只检查表达式不检查物理逻辑。所以我在后期增加了一步后验校验把最优解里的各水库水位过程线、出库流量过程线、火电出力过程线分别画出来和来水过程线叠在一起看。如果上游出库峰值出现后下游水位响应的时间间隔不是预设的滞时那基本就是时滞处理出了问题。这个校验在调试阶段每跑完一轮都要做比看目标函数值靠谱得多。5.4 从Pareto前沿里选折衷解算法跑完之后一堆非支配解摆在那里调度人员还是要选一个实际执行的方案。我用的方法是模糊隶属度函数法对每个解的每个目标计算满意度然后把满意度最低的那个目标拿出来比较选“短板最高”的方案。简单说就是挑一个各方面都不至于太差的折衷解。方案成本满意度排放满意度弃水满意度最小值A0.620.350.580.35B0.480.720.510.48C0.550.600.440.44上表例子里方案B的最小满意度是0.48高于A和C所以优先选B。这个方法实现起来就几行代码但能把算法输出的一整条前沿浓缩成决策者需要的“一个方案”工程上很好用。如果调度人员主观上更偏向成本那就把成本满意度的权重放大再重新排序整个流程很灵活。6. 从“跑出图”到“工程可用”最后补充几点整个项目做到后期我的体会是NSGA-Ⅲ本身的算法实现难度其实不大真正的功夫都花在目标函数和约束条件的物理建模上。优化算法给的是搜索方向能不能搜到对的解取决于你的评估函数是不是真实反映了水往低处流、机带负荷要调速、锅炉爬坡要时间这些物理规律。如果你也想复现这个方向建议从两个目标、单条梯级链、四到五台火电机组的小规模算例开始把参考点生成、非支配排序、环境选择这套流程完全跑通之后再扩大时段数或者增加目标数。别一上来就搞三目标、十来个水库的大算例一旦结果不对根本分不清是算法问题还是模型问题。代码层面还有两个小建议。第一Matlab里尽量把目标函数和约束计算向量化把24个时段的火电出力一起算而不是一个机组一个机组地循环我测试过同样的评估函数向量化之后单代计算时间能快三四倍。第二保存每次迭代的Pareto前沿快照用动画或者图片序列观察前沿的演化过程。这不仅能直观看到算法是否收敛还能在调试时快速判断是不是某代选择出了异常比只看最终结果图有用得多。调度方案选完之后我还习惯把结果拆成三张图各水库水位过程线、各电站出力与系统负荷平衡曲线、Pareto前沿分布图。水位过程线用来验证梯级水量平衡负荷平衡曲线用来确认每个时段都不缺电Pareto前沿图则用来和调度人员沟通不同决策倾向下的代价。这三张图贴出来项目报告也好实际汇报也好基本都能交代得明明白白。
返回列表