ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

基于NSGA-II的水光互补优化调度Python代码实现

基于NSGA-II的水光互补优化调度Python代码实现 先讲一个电站调度员几乎每天都会碰到的两难场景早上太阳一出光伏出力噌噌往上涨水库来水也刚好这时候水电机组到底是压着出力给光伏让路还是维持满发等着晚间负荷如果压了晚上光伏一“下班”负荷高峰全靠水电一个人扛爬坡压力直接拉满如果照发中午总出力大概率超过负荷要么弃光要么弃水眼睁睁看着清洁能源白白丢掉。这个看似简单的选择本质上是典型的多目标优化问题——经济性、消纳率、出力平稳性互相牵制怎么选都绕不开取舍。这篇文章要解决的就是如何用非支配排序遗传算法NSGA-II来求解水光互补优化调度问题并给出一套可以在Python里直接跑起来的完整代码框架。核心思路很简单别再纠结权重怎么定算法一次性给你一个Pareto解集让决策者从“被迫选一个唯一答案”变成“在多个最优方案里做权衡”。适合三类人看正在做水光互补或新能源调度方向课题的研究生、需要做日前发电计划的电网或电站工程师、以及对多目标优化落地感兴趣的程序员。不需要你有很深的专业背景懂一点Python基础和基本的优化概念就够了。1. 水光互补调度为什么绕不开多目标优化1.1 单目标调度的局限性加权法并非好出路很多年前大家做水库调度、电力调度习惯把问题简化成单目标。目标是成本最小就建线性规划目标是发电量最大就上动态规划。这些手段都很成熟但一旦涉及水光互补麻烦就来了你没法只追求一个目标。最常见的做法是加权求和把成本、消纳率、出力波动打个分乘以权重加在一起变成单个目标函数。听起来很合理实际一用就发现三个坑。第一权重怎么定说不清。你说经济性权重0.6光伏消纳权重0.4那为什么不是0.7和0.3不同调度场景下权重完全不一样汛期可能更在意弃水枯期更在意成本权重一改优化结果就完全变样最后变成调权重比调调度方案还费劲。第二加权法对非凸的Pareto前沿无能为力。简单说如果目标空间的可行域形状不是凸的加权法再怎么遍历权重都只能找到一部分最优解另一部分会被系统性漏掉。多目标问题的解集通常是非凸的靠加权法搜索结果不完整。第三每次加权只能得到唯一一个解。想知道“成本最低方案”和“消纳最好方案”之间的中间态长什么样就得反复改权重重跑效率极低。1.2 水光互补的三方博弈经济、消纳、平稳把目标拆开看水光互补调度的矛盾就很具体了。光靠一个水电站和一个光伏电站配合想同时满足三个方面经济性水电边际成本低光伏边际成本几乎为零理论上能发多少发多少最省钱可电网消纳能力有限。资源利用弃光、弃水都算浪费光伏多发、水电少弃才是好方案但光伏高峰未必是负荷高峰。出力平稳水电调峰能力强可库容、流量都有限制光伏波动又剧烈联合出力要想平稳跟随负荷曲线难度不小。这三个目标天然冲突。举个具体数字某地区负荷峰谷差40MW午间光伏出力30MW水电最大出力20MW。中午光伏30MW全部发出来负荷只有20MW水电如果维持10MW总出力就超出10MW要么弃掉要么外送。水电如果要让路晚间负荷冲到50MW水电能不能从低谷快速爬坡补上这中间全是取舍。这正是多目标优化的典型舞台——不存在一个方案同时做到成本最低、消纳最大、波动最小只存在一组互有优劣的Pareto最优解让决策者根据当日实际情况选择。1.3 谁需要这套方法和代码说实在的我最初做这个项目就是被课题逼的后来发现实际应用场景比想象中宽电力系统方向的研究生写论文需要一个不落俗套的调度方法多目标优化加智能算法是热门方向。水电站或新能源集控中心的工程师做日前发电计划时需要评估不同调度策略。做智能优化算法应用的程序员想找一套能落地的多目标优化案例练手。这套代码对基础的要求不高Python会写基本循环和函数知道Pareto是非劣解就行。水电运行特性比如出力公式、库容约束我会在建模部分讲清楚。2. NSGA-II在调度问题里的角色定位2.1 算法选型为什么是NSGA-II多目标优化算法不少我最早也纠结过后来做一个选型对比就清楚了。方法优点在这个调度问题里的痛点加权求合法实现简单权重难定非凸前沿找不全一次只能得一个解动态规划单目标最优理论成熟多目标需要标量化维度灾难明显枚举搜索全局最优决策变量48维枚举空间爆炸NSGA-II一次运行得到整个Pareto前沿参数相对少收敛速度依赖种群规模约束处理要自己写水光互补调度的目标通常是2到3个决策变量是24个时段的出力序列维数不算高但也不低。NSGA-II引入了非支配排序和拥挤度距离一个保证解的收敛性一个保证解的分散性恰好对上了调度的需求。顺带说一句如果你的目标函数超过3个我建议换NSGA-III它用参考点机制在更高维空间维持多样性更稳。水光互补这种典型的两到三个目标问题NSGA-II性价比最高工程实践也多出了问题好查资料。2.2 NSGA-II的四个核心机制很多人一提到NSGA-II就觉得高大上拆开看其实就四块东西非支配排序把所有个体按支配关系分层。如果一个解的所有目标都不差于另一个解而且至少有一个目标严格更优就称前者支配后者。第一层是不被任何解支配的个体也就是真正的Pareto前沿。这个机制解决的是“收敛性”问题让种群向真正的最优前沿逼近。拥挤度距离同一层里个体也要分优劣。如果两个解都在Pareto前沿上但一个周围挤满邻居一个孤零零在角落显然角落那个更有保留价值。拥挤度距离大的个体说明它所在区域其他解少保留它能维持解的分布性防止所有解挤成一团。锦标赛选择从父代随机抽几个个体先比非支配层级层级低胜出层级相同则比拥挤度拥挤度大胜出。这个机制模拟了自然选择让好的个体有更高概率繁衍后代。精英保留策略父代子代合并一起做非支配排序取前N个进入下一代。保证了最好的解永远不会被丢掉算法收敛性有理论保障。这四个机制配合起来效果就是种群逐渐逼近真实的Pareto前沿同时在前沿上均匀铺开。2.3 调度问题不是套个算法就完事必须强调一点NSGA-II只是一个搜索骨架真正的难点在评价函数的设计和约束处理。你可以用现成的pymoo库一行命令调用NSGA-II但调度问题特有的水量平衡递推、水位边界、发电流量反算这些逻辑库不会帮你写。把这些动态约束硬塞进目标函数里如果处理不对跑出来一堆物理上不可行的调度方案算法再聪明也没用。所以我建议这一版实现里自己写核心算子。不是因为第三方库不好而是自己写一遍你才能真正理解每个算子在调度问题里的行为后面排查问题才有据可循。3. 调度模型的数学表达从目标到约束3.1 问题场景与决策变量先定义清楚我们要解决的场景一个水电站和一个光伏电站组成联合发电系统向电网供电目标是为未来24小时制定发电计划。时间尺度取1小时一个时段共24个时段。决策变量选两个水电出力序列P_h[t]t1到24表示每小时水电站的出力单位MW。光伏实际出力序列P_pv[t]t1到24表示每小时光伏电站实际发出的功率受预测值上限约束实际小于预测的部分就是弃光。联络线功率我不作为决策变量而是作为松弛变量。功率不平衡的时候缺的向电网买电多的返送电网这个变量由平衡条件直接算出来。有人会问为什么不用水库发电流量作为决策变量理论上更符合水电站运行习惯但初版模型直接用出力避免在编码里来回换算功率和流量先把框架跑通。后面要精确模拟水库调度再把出力通过水头效率公式转成发电流量即可。3.2 目标函数设计我采用两个典型目标便于展示Pareto前沿。第一个目标是运行成本最小化第二个目标是联合出力波动最小化。目标1运行成本最小min f1 Σ c_ope_h * P_h[t] Σ c_buy[t] * max(0, P_load[t] - P_h[t] - P_pv[t]) Σ c_curtail * (P_pv_forecast[t] - P_pv[t])这里c_ope_h是水电运维成本c_buy[t]是时段t的购电电价c_curtail是弃光惩罚成本。弃光惩罚这个系数很关键它代表的是清洁能源浪费的社会成本和政策压力定得越高算法越倾向于多发光伏哪怕多买点常规电。目标2联合出力波动最小min f2 Σ | (P_h[t] P_pv[t]) - (P_h[t-1] P_pv[t-1]) |水电和光伏联合出力如果剧烈波动会给电网调峰和频率控制带来巨大压力。这个目标就是希望联合出力曲线尽量平滑。它和第一个目标明显冲突成本低要求多用水电水电可能跟着负荷峰谷频繁调整波动自然大波动小要求出力平稳就得牺牲一些经济性。如果你还想加第三个目标也很容易比如下游生态流量满意度、水库弃水量最小化无非是在评价函数里多算一个值非支配排序的逻辑完全不需要改动。3.3 约束条件约束条件是调度模型的骨架所有参数都来自水电站和光伏电站的实际物理限制。水电出力边界P_h_min ≤ P_h[t] ≤ P_h_max。对应水轮机技术出力下限和最大出力上限。爬坡约束|P_h[t] - P_h[t-1]| ≤ R_h。水电不能瞬间从零冲到满发水头变化、引水系统都会限制出力调整速度。这是很多人初版模型容易漏掉的约束漏掉的后果是调度曲线出现大量物理上不可能的直角跳变。光伏出力边界0 ≤ P_pv[t] ≤ P_pv_forecast[t]。光伏出力不可能超过预测上限下限是0意味着可以主动弃光。功率平衡约束P_h[t] P_pv[t] P_buy[t] - P_sell[t] P_load[t]。这个是等式约束通过松弛变量处理购买和返送电网不能同时为正。水量平衡约束V[t1] V[t] (Q_in[t] - Q_turbine[t] - Q_spill[t]) * Δt。库容变化等于入流减去发电流量和弃水流量。水电出力通过公式反算发电流量P_h η * ρ * g * Q_turbine * H。库容边界V_min ≤ V[t] ≤ V_max。水库不是无底洞也不能放空必须落在正常蓄水位和死水位之间的库容范围。末水位约束V[24] V[0]。日调节水库一天一圈调度周期结束后水位要回到起点否则第二天没法继续调度。这个约束我建议直接写成软约束加进评价函数否则Pareto前沿会出现一堆“把水位放空换取经济性”的假解。3.4 约束处理策略修复优先于惩罚约束处理直接决定算法能不能收敛到可行域。我踩过的坑是初期用罚函数违反约束就往目标函数上加惩罚项结果惩罚系数小了约束形同虚设系数大了算法只顾着躲惩罚Pareto前沿严重偏向一侧。最终方案是分三类处理边界约束直接裁剪。水电出力和光伏出力越界就切割到边界值简单可靠。爬坡约束也做裁剪。差分超过爬坡速率就在可调范围内压缩出力变化量。水量平衡和末水位约束用修复法。评价函数里先模拟一整天水库蓄水量变化如果某时段水位越界把出力序列在时空上重新分配让水位回到可行区间。修复法比罚函数稳得多因为它保证最终解是物理可行的Pareto前沿上的点都能直接拿去执行。4. Python完整实现基于NSGA-II的水光互补调度核心代码4.1 数据准备与参数设置先定义输入数据。负荷曲线、光伏预测出力这些在实际工程中来自调度自动化系统和数值天气预报。我这里造一组合理的示例数据格式跟实际系统导出后一致。import numpy as np # 基础参数 T 24 # 调度周期时段数 # 负荷预测曲线单位MW load np.array([28, 26, 24, 23, 24, 26, 30, 36, 42, 46, 48, 50, 48, 46, 45, 44, 46, 50, 52, 55, 54, 50, 44, 36], dtypefloat) # 光伏出力预测值单位MW夜间为0 pv_forecast np.array([0, 0, 0, 0, 0, 1, 5, 12, 20, 26, 30, 32, 31, 28, 24, 18, 12, 6, 1, 0, 0, 0, 0, 0], dtypefloat) # 水电参数 h_params { p_min: 5.0, # 最小技术出力 MW p_max: 25.0, # 最大出力 MW ramp: 8.0, # 爬坡速率 MW/h v_init: 50.0, # 初始库容 万m3 v_min: 30.0, # 最小库容 v_max: 80.0, # 最大库容 q_in: 6.0, # 入库流量 折算为单位时段水量 eta: 0.85, # 综合效率 head: 20.0, # 水头 m } # 价格参数 c_ope_h 0.02 # 水电运维成本 元/kWh c_buy np.array([0.5, 0.48, 0.45, 0.45, 0.46, 0.5, 0.6, 0.75, 0.85, 0.9, 0.92, 0.9, 0.85, 0.82, 0.8, 0.78, 0.82, 0.9, 0.95, 1.0, 0.95, 0.85, 0.7, 0.55]) c_curtail 0.4 # 弃光惩罚 元/kWh # NSGA-II 参数 POP_SIZE 100 # 种群规模 MAX_GEN 400 # 最大迭代代数 P_CROSS 0.9 # 交叉概率 P_MUT 1.0 / (2 * T) # 变异概率约等于每染色体变异一个基因 ETA_C 15 # SBX交叉分布指数 ETA_M 20 # 多项式变异分布指数个体编码就是两个数组拼接前24维是水电出力后24维是光伏实际出力总维数48。4.2 约束修复与评价函数评价函数是整个算法的核心它把一条染色体翻译成两个目标值。水量平衡修复合在评价里面一起做。def repair_and_evaluate(individual): # 拆出水电和光伏出力 p_h individual[:T] p_pv individual[T:] # 边界约束与爬坡约束 p_h np.clip(p_h, h_params[p_min], h_params[p_max]) for t in range(1, T): p_h[t] np.clip(p_h[t], p_h[t-1] - h_params[ramp], p_h[t-1] h_params[ramp]) p_pv np.clip(p_pv, 0, pv_forecast) # 模拟水库水量平衡使用修复策略 V np.zeros(T 1) V[0] h_params[v_init] # 出力反算发电流量P_h eta * rho * g * Q * H rho_g 1000 * 9.81 / 1e6 # 单位折算 for t in range(T): Q_turbine p_h[t] / (h_params[eta] * rho_g * h_params[head]) V[t1] V[t] h_params[q_in] - Q_turbine # 修复超上限则增加弃水量但弃水也会有惩罚低于下限则在可行出力区内调整 if V[t1] h_params[v_max]: V[t1] h_params[v_max] if V[t1] h_params[v_min]: # 提高当前时段出力减少蓄水不足 needed h_params[v_min] - V[t1] add_power min(needed * h_params[eta] * rho_g * h_params[head], h_params[p_max] - p_h[t]) p_h[t] add_power V[t1] V[t] h_params[q_in] - p_h[t] / (h_params[eta] * rho_g * h_params[head]) # 目标1运行成本 p_short np.maximum(0, load - p_h - p_pv) # 缺额购电 curtail pv_forecast - p_pv # 弃光电量 f1 np.sum(c_ope_h * p_h c_buy * p_short c_curtail * curtail) # 目标2联合出力波动 combined p_h p_pv f2 np.sum(np.abs(np.diff(combined))) # 末水位偏差作为软约束加进目标1 end_deviation abs(V[T] - h_params[v_init]) f1 50.0 * end_deviation return np.array([f1, f2]), p_h, p_pv注意几个细节爬坡约束逐时段裁剪所以即使随机初始化产生毛刺也能被修复末水位偏差用软约束处理系数50是初期试验得到的值太小末水位补偿不动太大会直接把所有解拉向经济性方向。4.3 非支配排序与拥挤度距离这是NSGA-II的标志性代码。非支配排序负责分层层级拥挤度负责同层内排序。def fast_non_dominated_sort(fitness): n len(fitness) domination_count [0] * n dominated_set [[] for _ in range(n)] fronts [[]] for i in range(n): for j in range(n): if i j: continue # 判断i是否支配j if all(fitness[i] fitness[j]) and any(fitness[i] fitness[j]): dominated_set[i].append(j) elif all(fitness[j] fitness[i]) and any(fitness[j] fitness[i]): domination_count[i] 1 if domination_count[i] 0: fronts[0].append(i) k 0 while fronts[k]: next_front [] for i in fronts[k]: for j in dominated_set[i]: domination_count[j] - 1 if domination_count[j] 0: next_front.append(j) k 1 if next_front: fronts.append(next_front) return fronts def crowding_distance(fitness, front): dist {idx: 0.0 for idx in front} n_obj fitness.shape[1] for m in range(n_obj): ordered sorted(front, keylambda idx: fitness[idx, m]) dist[ordered[0]] np.inf dist[ordered[-1]] np.inf if fitness[ordered[-1], m] fitness[ordered[0], m]: continue fmin fitness[ordered[0], m] fmax fitness[ordered[-1], m] for k in range(1, len(ordered) - 1): dist[ordered[k]] (fitness[ordered[k1], m] - fitness[ordered[k-1], m]) / (fmax - fmin) return dist拥挤度距离计算里有个细节目标值范围差异大直接累加会把量纲大的目标主导所以每个目标都先按该目标的值归一化再累加这样两个目标对拥挤度的贡献才均衡。4.4 遗传算子与主循环SBX交叉是实数编码遗传算法的首选交叉算子擅长保持父代相近性避免子代跑得太野。多项式变异则保证了局部扰动的可控性。def sbx_crossover(p1, p2, eta_cETA_C): child1 np.copy(p1) child2 np.copy(p2) for i in range(len(p1)): if np.random.rand() 0.5: if abs(p1[i] - p2[i]) 1e-6: continue u np.random.rand() if u 0.5: beta (2 * u) ** (1 / (eta_c 1)) else: beta (1 / (2 * (1 - u))) ** (1 / (eta_c 1)) child1[i] 0.5 * ((1 beta) * p1[i] (1 - beta) * p2[i]) child2[i] 0.5 * ((1 - beta) * p1[i] (1 beta) * p2[i]) return child1, child2 def polynomial_mutation(child, p_min, p_max, eta_mETA_M): for i in range(len(child)): if np.random.rand() P_MUT: u np.random.rand() delta p_max - p_min if u 0.5: delta_q (2 * u) ** (1 / (eta_m 1)) - 1 else: delta_q 1 - (2 * (1 - u)) ** (1 / (eta_m 1)) child[i] delta_q * delta child[i] np.clip(child[i], p_min, p_max) return child主循环就是按精英保留策略不断迭代。父代和子代合并总共2倍种群规模做非支配排序逐层填充下一代直到满员最后一层按拥挤度距离排序填进去。# 初始化种群 population np.random.rand(POP_SIZE, 2 * T) population[:, :T] * (h_params[p_max] - h_params[p_min]) population[:, :T] h_params[p_min] population[:, T:] * pv_forecast # 光伏部分按预测上限缩放 # 主循环 for gen in range(MAX_GEN): fitness_list [] for ind in population: fitness, _, _ repair_and_evaluate(ind) fitness_list.append(fitness) fitness_arr np.array(fitness_list) # 生成子代 offspring [] while len(offspring) POP_SIZE: p1_idx tournament_selection(fitness_arr) p2_idx tournament_selection(fitness_arr) c1, c2 sbx_crossover(population[p1_idx], population[p2_idx]) c1 polynomial_mutation(c1, h_params[p_min], h_params[p_max]) c2 polynomial_mutation(c2, h_params[p_min], h_params[p_max]) offspring.extend([c1, c2]) # 合并父代子代精英保留 combined np.vstack([population, np.array(offspring[:POP_SIZE])]) combined_fitness np.vstack([fitness_arr, np.array([repair_and_evaluate(ind)[0] for ind in offspring[:POP_SIZE]])]) fronts fast_non_dominated_sort(combined_fitness) new_pop [] for front in fronts: if len(new_pop) len(front) POP_SIZE: new_pop.extend(front) else: dist crowding_distance(combined_fitness, front) sorted_front sorted(front, keylambda idx: dist[idx], reverseTrue) new_pop.extend(sorted_front[:POP_SIZE - len(new_pop)]) break population combined[new_pop]这里tournament_selection就是标准的二元锦标赛按非支配层级升序、拥挤度降序比较代码我留给大家自己补逻辑很短。整段代码跑完最终种群的第一层非支配解就是Pareto前沿。5. 结果分析Pareto前沿怎么读、怎么决策5.1 从Pareto前沿看经济性和平稳性的权衡算法跑完把第一前沿解的f1和f2画成散点图会得到一条从左上到右下倾斜的曲线。左上角是成本最低但波动很大的解右下角是波动最小但成本很高的解。这条曲线一眼就能看出矛盾的强度如果曲线很平直说明两个目标权衡很小如果曲线呈明显下凹说明存在“性价比很高”的折中区域。实际调度中前沿中间段的斜率通常会出现一个明显变缓的拐点。拐点左侧稍微多花一点钱就能大幅降低波动这个区域就是优先考虑的候选区拐点右侧再花大价钱也只能换来一点点平稳性提升除非有特殊原因不建议选。这个拐点不是靠公式算出来的就是你盯着散点图看出来的。我在实际项目里发现一个规律如果Pareto前沿整体偏向“高成本高消纳”一侧往往是购电电价设置偏高水电主动让路给光伏导致需要高价购电补缺如果前沿偏向“低成本高波动”说明弃光惩罚还不够高。看前沿形状反过来能帮你校正目标函数参数是否合理。5.2 从一堆最优解里挑一个可执行方案Pareto解集提供了选择空间但也把决策压力交给了人。我推荐用模糊隶属度法做自动筛选简单有效。对每个目标定义一个满意隶属函数。目标值越小越好的情况下取该目标在整个前沿中的最小值和最大值某个解的隶属度就是μ (f_max - f) / (f_max - f_min)每个解都有两个隶属度综合满意度取两个隶属度的最小值或者取平均值。满意度最高的解就是自动筛选的折中解。这个方法的好处是给每个解都打了一个0到1的分数决策者可以设定门槛比如“我要求成本满意度不低于0.6、波动满意度不低于0.5”直接过滤掉不合适的方案。如果调度员有个人偏好比如汛期更关注消纳可以把弃光目标的隶属度权重调大再求加权求和。5.3 调度曲线的解读方法选了折中解之后把水电出力曲线、光伏出力曲线、联合出力曲线画在一起对照负荷曲线看。典型的好方案长这样光伏在午间尽量满发水电在光伏大发的时段压到较低水平腾出库容晚间光伏归零水电快速爬坡顶上去联合出力曲线整体贴合负荷而且没有剧烈毛刺。注意对比两个极端解会更有意思。最低成本解往往水电整天贴着上限跑光伏能发就发联合出力曲线犬牙交错跟负荷曲线匹配很差。最低波动解则把水电出力调得像一条平直线联合出力和负荷的偏差全靠买电补成本自然高。看懂了这两个极端解你就理解为什么说多目标优化解决的是“选择权”问题。6. 实测中容易踩的坑和参数调优6.1 种群规模和迭代次数怎么搭配合适这组参数我试过很多组合。48维决策变量种群100、迭代400代前沿基本稳定再往上加迭代收益很小。如果你的前沿明显还没收敛先加迭代次数不要急着加种群规模迭代从400加到800时间大概翻倍但前沿质量能肉眼可见地变好。种群从100加到200时间也翻倍效果往往不如加迭代明显。一个实用经验先跑个60代快速看前沿轮廓如果散点图还是乱糟糟没成形说明种群规模可能不够如果已经形成清晰曲线再跑完整400代精修。6.2 约束修复的顺序问题我早期在水量平衡修复里犯过一个错误先修下限后修上限结果某个时段为了不突破库容上限强制弃水下一时段水位又掉到下限以下来回修了好几轮最后末水位偏差还是很大。后来改成先按时间顺序正向模拟一遍把下限越界当场修复等整轮结束后再做一次反向扫描修上限效果立刻好了。核心思想是修复要有方向性不能每时段孤立地修。6.3 交叉变异参数别贪SBX分布指数ETA_C取15、多项式变异分布指数ETA_M取20是实测比较稳的起点。变异概率设为1除以染色体长度也就是2%左右不要贪大。变异概率一旦超过10%调度曲线就全是毛刺爬坡约束虽然能裁剪出物理可行的形状但会破坏遗传算子生成的优质基因结构收敛变慢不说前沿多样性也会下降。判断遗传算子是否过猛有个土办法把跑出来的最优解画成曲线如果相邻时段出力像锯齿一样来回跳先别怀疑约束没写好大概率是变异太猛或者交叉产生的子代远离了父代所在可行区域。6.4 监控收敛性和多样性我建议每10代人记录一次当前Pareto前沿的超体积指标HV画成曲线看走势。HV是前沿覆盖面积数值越大越好。如果HV曲线到后半段还在明显上升说明还没收敛继续加迭代如果HV曲线震荡但停滞说明种群多样性丢了需要调大拥挤度在锦标赛里的权重或者引入部分外部随机个体。多样性还有一个直观检查方式看前沿解在f1轴和f2轴上的覆盖跨度。如果覆盖范围只占目标空间的一半说明种群集中在某个区域需要检查是否约束修复把大量解都压向了同一个方向。想扩展的话后面可以这样玩这套代码框架最大的价值在于可扩展性。我在实际测试中体会最深的是评价函数和约束修复的可靠性优先于算法本身的优化——NSGA-II再聪明也救不了一个物理逻辑错误的目标函数。建议拿到代码先随机生成几条染色体手动检查修复后的水量平衡和爬坡约束是不是合理再挂上遗传算子跑完整流程。后续要扩展方向也很成熟水电换成梯级水电站决策变量变成每个电站的出力序列目标函数加一个梯级协调惩罚即可加入抽水蓄能或者储能把储能的充放电功率也编码进染色体水量平衡换成电量平衡想在模型里考虑碳排放、生态流量就是多写一个f3的问题非支配排序代码一行都不用改。这套东西跑通之后你会发现水光互补调度的难点不在算法而在怎么把一个真实的物理系统简洁地装进优化框架里。希望这篇文章能给正在做这个方向的朋友省下几个晚上的摸索时间。
返回列表