遗传算法求解动态经济调度:Python实现与爬坡/网损处理

发布时间:2026/10/10 12:47:28
遗传算法求解动态经济调度:Python实现与爬坡/网损处理 做经济调度研究的人大多会碰过这种情况单机成本模型很简单约束一加就出事。尤其是爬坡约束和输电损耗同时出现以后等约束优化已经不太容易处理很多传统解法要么收敛慢要么直接把约束简化掉算出来一套看似可行、现场却跑不了的结果。这个项目做的就是一件事用遗传算法把动态经济调度问题直接怼进去机组爬坡速率、线路损耗、负荷曲线一起考虑最后用Python把整个流程跑通。文章把模型公式、算法设计、代码实现、参数坑都写清楚适合正在做电力系统优化课题、或者想用遗传算法解决实际约束优化问题的读者参考。1. 先把问题说清楚这到底在优化什么1.1 经济调度是电力系统的“算账问题”电网调度每天的日常工作之一就是决定哪些机组带多少负荷。发电机组数量一多谁多发、谁少发、谁停机、谁启动这些问题没法靠拍脑袋。经济调度Economic DispatchED做的事情就是在满足负荷需求、机组出力上下限、备用容量等约束的前提下让发电总成本最低。单时段的ED非常简单甚至可以手算。可一旦我们面对24小时甚至96个调度时段问题就变成动态经济调度Dynamic Economic DispatchDED。DED比单时段版本难很多核心原因就是机组出力在相邻时段之间不是随便跳的这也就是爬坡约束的由来。本项目的目标函数可以写成min Σₜ Σᵢ (aᵢ bᵢ·Pᵢₜ cᵢ·Pᵢₜ²)其中i是机组编号t是时段编号a、b、c是机组成本系数P是出力。这个二次函数是电力系统调度最经典的机组成本模型虽然实际中有更精细的分段线性或阀点效应模型但二次函数已经足够说明遗传算法的处理方式。约束条件则是这几条功率平衡所有机组出力总和等于负荷需求加上输电损耗。机组出力上下限每台机组都有最小稳定出力和最大出力。爬坡约束相邻时段出力变化不能超过机组允许的爬坡速率。输电损耗损耗不是固定值它随着机组出力变化是出力的非线性函数。很多人初看会觉得这不过是个带约束的数学规划问题。但当机组数和时段数增加再加上输电损耗的非线性耦合整个搜索空间会变得非常复杂传统解析法和部分梯度法很难在合理时间内找到全局较优解。1.2 爬坡约束不是“学术矫情”是设备物理极限爬坡约束这个概念刚接触的人容易低估它。一台大型火电机组从50%负荷升到100%锅炉燃烧、汽轮机热应力、辅机出力都有一个响应过程。你让它在15分钟间隔里从200MW直接跳到400MW机组很可能直接触发保护动作甚至造成设备寿命损伤。在数学上爬坡约束写作|Pᵢₜ - Pᵢₜ₋₁| ≤ RampᵢRampᵢ就是机组i在单位时段内允许的出力变化量。如果调度时段是1小时Rampᵢ的单位就是MW/h。这个约束最大的麻烦在于它把不同时段“粘”在了一起。单时段优化可以各算各的多时段优化就必须把24个时段当成一个整体来看解空间维度一下子从“机组数×1”变成“机组数×24”。本项目里给每台机组设置了不同的爬坡能力比如某台机组每小时只能变化60MW另一台能变100MW。这样的设置更贴近实际不同厂家、不同容量、不同燃料类型的机组响应能力天然不同。如果忽略了爬坡约束经济性最优解很可能要求某台机组在凌晨低负荷时段大幅降出力第二天早上又大幅升出力。这种剧烈变化恰恰是现场最不希望看到的。所以爬坡约束才是让“算出来的最优”变成“能执行的最优”的关键一步。1.3 输电损耗为什么不能忽略很多入门教材在做经济调度时都会做“忽略网损”的假设。这确实省事但实际系统里电能在输电线路上会产生热量损耗尤其是负荷重、送电阻离远的情况下网损占总负荷的比例可能达到5%-8%。忽略掉这部分功率平衡本身就是错的机组发出的电减去损耗才是真正送到负荷端的功率。输电损耗最简单的建模方式是用B系数公式P_L,t Σᵢ Σⱼ Pᵢₜ·Bᵢⱼ·Pⱼₜ Σᵢ Bᵢ₀·Pᵢₜ B₀₀其中B矩阵是损耗系数矩阵B₀是线性项系数B₀₀是常数项。这个公式最早来自经典的网损系数法用系统运行点附近的线性化潮流推导得到。虽然精度不如完整潮流计算但在经济调度这种需要反复求解的场景里B系数模型计算速度极快精度也够用非常适合作为遗传算法适应度函数的一部分。注意B系数公式里的平方项和交叉项意味着每台机组出力的变化会通过损耗影响所有机组的功率平衡。这导致一个问题某一台机组出力改变损耗变了其他机组的平衡出力也要跟着变。这就是典型的非线性耦合也是为什么很多传统方法在这个问题上会碰壁。2. 为什么选择遗传算法来处理这个难题2.1 传统方法的痛点单时段经济调度是凸二次规划用拉格朗日乘子法加迭代修正就能解速度快、稳定性也不错。到了多时段动态经济调度情况就变了。最直接的做法是把所有时段的变量都展开变成一个大规模约束优化问题。理论上可以用序列二次规划、内点法等求解。但这类方法对初值敏感而且一旦目标函数非凸——比如考虑机组阀点效应、禁止运行区、启停变量——很容易陷入局部最优。另一个现实问题是很多科研人员和工程师不是优化算法专家让他们去调内点法的收敛参数成本太高。启发式算法在工程界之所以流行不是因为它们保证找到全局最优而是因为它们在面对复杂约束时非常“皮实”。你不需要推导问题特殊的梯度信息只要能把适应度函数写出来算法就会自己朝好的方向搜索。遗传算法作为启发式算法里最成熟的一支实现简单、灵活性高天然适合这种“前两天还在改约束条件今天就要出结果”的研究场景。2.2 遗传算法怎么帮你“试”出好解遗传算法GA模拟自然选择的过程。一个“个体”就是一组完整调度方案一组个体就是“种群”。每一代里适应度好的个体被选出来进行交叉和变异产生下一代适应度差的个体自然被淘汰。循环几十上百代以后种群整体会朝着低成本、少违规的方向进化。GA对应到本项目有几个关键设计编码方式用浮点数数组表示每个机组在每个时段的出力。适应度函数总发电成本加上各项约束的惩罚项。选择算子锦标赛选择在种群中随机挑几个个体留下最优的。交叉算子把两个父代个体的部分时段出力互换产生新个体。变异算子随机挑某个时段、某个机组加一个小的随机扰动。GA最大的优势在于它不需要目标函数可导。你的成本函数可以是二次函数也可以是分段函数约束可以写成等式也可以写成不等式甚至可以是某种仿真程序的输出结果。这给了建模极大的自由度。当前项目把爬坡约束和损耗同时加进适应度函数里GA都能统一处理。2.3 一个容易出效果的建模技巧松弛机组我建议在GA里引入“松弛机组”的概念。原理很简单功率平衡约束是等式约束如果直接用罚函数处理算法很容易钻空子找到一个成本很低但功率不平衡的“假最优解”。与其让GA自己摸索平衡条件不如在设计阶段就把等式消掉。具体做法是选一台机组作为平衡机组其他机组的出力由基因编码决定平衡机组的出力根据功率平衡反算出来。这样每次评价个体时不需要额外判断平衡约束是否满足天然就满足。代价是平衡机组的出力范围以及爬坡约束需要额外检查一旦反算出的出力越界就要给惩罚。这个想法和实际电网调度中“平衡节点”的概念很像也是本项目代码最核心的部分。很多初学者直接对全变量编码然后寄希望于罚函数结果遗传算法跑几百代还在找可行解原因往往就在这里没有利用问题结构消掉等式约束。3. Python代码实现从模型到可运行结果3.1 数据准备与模型参数定义先给一个具体算例。三台发电机24个时段负荷曲线包含夜间低谷和白天高峰。机组参数如下表机组abcPmin/MWPmax/MW爬坡速率/MW·h⁻¹G15008.00.0045030060G24007.00.0035040080G33006.00.00250500100其中G3作为松弛机组。负荷曲线取一组典型的互联网流传的实测形状数据凌晨负荷低到580MW傍晚高峰到1080MW。三台机组总容量是1200MW考虑损耗后峰值仍有足够裕度。输电损耗B矩阵取3×3对称矩阵对角线是自损耗系数非对角线是交叉损耗系数数值在10⁻⁵到10⁻⁴量级。这个量级对应实际系统中总损耗约占总出力的2%-4%。3.2 适应度函数怎么把约束“揉”进去遗传算法的核心评价函数包含三部分发电成本、机组越限惩罚、爬坡越限惩罚。发电成本部分直接按二次函数计算全部时段、全部机组求和cost Σᵢₜ aᵢ bᵢ·Pᵢₜ cᵢ·Pᵢₜ²越限惩罚处理两种情况。一是松弛机组反算出的出力超出上下限二是爬坡约束被破坏。惩罚量用线性惩罚乘以一个较大的权重penalty Σ (越限量 × 惩罚系数)为什么用线性惩罚而不是二次惩罚我的经验是线性惩罚在GA里更稳定。二次惩罚虽然对大幅越限的惩罚更狠但在小幅越限时几乎不起作用导致算法倾向于“稍微违约一点”来降低成本线性惩罚让每个单位越限都有明确代价配合合适的权重系数搜索方向更清晰。功率平衡因为用了松弛机组反算所以不需要惩罚项这是整个代码实现里的最大简化点。3.3 核心算法代码import numpy as np # 机组参数 N_GEN 3 A np.array([500.0, 400.0, 300.0]) B_COST np.array([8.0, 7.0, 6.0]) C_COST np.array([0.004, 0.003, 0.002]) Pmin np.array([50.0, 50.0, 50.0]) Pmax np.array([300.0, 400.0, 500.0]) Ramp np.array([60.0, 80.0, 100.0]) T 24 # 24小时负荷序列 load np.array([ 700, 650, 620, 600, 580, 600, 650, 750, 850, 950, 1000, 1050, 1080, 1050, 1000, 980, 950, 900, 850, 800, 750, 720, 700, 680 ], dtypefloat) # 输电损耗B系数矩阵 B np.array([ [0.00006, 0.00002, 0.00001], [0.00002, 0.00005, 0.00001], [0.00001, 0.00001, 0.00003] ]) B0 np.array([0.0001, 0.0002, 0.0003]) B00 0.0 # 选择3号机作为松弛机组索引为2 SLACK 2 def loss_value(P): 根据B系数公式计算每时段网损 loss np.einsum(ti,ij,tj-t, P, B, P) loss loss P B0 B00 return loss def decode(genes): 将基因解码为完整的三机组出力矩阵。 genes只包含两台非松弛机组的出力形状为(T, 2)。 松弛机组G3的出力通过功率平衡反算。 P np.zeros((T, N_GEN)) P[:, 0] genes[:, 0] P[:, 1] genes[:, 1] P3 load - P[:, 0] - P[:, 1] P[:, SLACK] P3 # 网损和P3相互耦合做固定点迭代通常几步就收敛 for _ in range(10): loss loss_value(P) P[:, SLACK] load loss - P[:, 0] - P[:, 1] return P def calc_cost(P): 总发电成本Σ(a b*P c*P^2) return float(np.sum(A B_COST * P C_COST * P * P)) def calc_penalty(P): 约束越限惩罚 pen 0.0 w_limit 8000.0 w_ramp 5000.0 # 所有机组上下限越限 for i in range(N_GEN): pen np.sum(np.maximum(0, Pmin[i] - P[:, i]) * w_limit) pen np.sum(np.maximum(0, P[:, i] - Pmax[i]) * w_limit) # 爬坡越限 for i in range(N_GEN): diff np.abs(P[1:, i] - P[:-1, i]) pen np.sum(np.maximum(0, diff - Ramp[i]) * w_ramp) return float(pen) def fitness(genes): P decode(genes) return calc_cost(P) calc_penalty(P) def init_individual(): 初始化个体时利用爬坡限制随机游走。 保证G1/G2从出生起就尽量不违反爬坡约束。 genes np.zeros((T, 2)) for i in range(2): genes[0, i] np.random.uniform(Pmin[i], Pmax[i]) for t in range(1, T): prev genes[t - 1, i] val prev np.random.uniform(-Ramp[i], Ramp[i]) genes[t, i] np.clip(val, Pmin[i], Pmax[i]) return genes def tournament_select(pop, fits): idx np.random.choice(len(pop), 3, replaceFalse) best_idx idx[np.argmin(fits[idx])] return pop[best_idx] def crossover(p1, p2): 单点交叉随机选一个时段点作为分界 if np.random.rand() 0.8: point np.random.randint(1, T) c1 np.concatenate([p1[:point], p2[point:]], axis0) c2 np.concatenate([p2[:point], p1[point:]], axis0) return c1, c2 return p1.copy(), p2.copy() def mutate(ind): 对随机时段、随机机组的基因加一个小扰动 new_ind ind.copy() i np.random.randint(0, 2) t np.random.randint(0, T) delta np.random.uniform(-0.15, 0.15) * (Pmax[i] - Pmin[i]) new_ind[t, i] np.clip(new_ind[t, i] delta, Pmin[i], Pmax[i]) return new_ind def ga_run(pop_size80, generations300, mutation_rate0.15): pop [init_individual() for _ in range(pop_size)] best_fit_history [] best_ind_history [] for gen in range(generations): fits np.array([fitness(ind) for ind in pop]) best_fit_history.append(fits.min()) best_ind_history.append(pop[np.argmin(fits)].copy()) new_pop [] while len(new_pop) pop_size: parent1 tournament_select(pop, fits) parent2 tournament_select(pop, fits) child1, child2 crossover(parent1, parent2) if np.random.rand() mutation_rate: child1 mutate(child1) if np.random.rand() mutation_rate: child2 mutate(child2) new_pop.append(child1) new_pop.append(child2) # 精英保留把上一代最好的个体直接放进下一代 new_pop[0] best_ind_history[gen] pop new_pop[:pop_size] final_fits np.array([fitness(ind) for ind in pop]) best_idx np.argmin(final_fits) P_best decode(pop[best_idx]) return P_best, best_fit_history3.4 运行结果解读P_best, his ga_run() loss loss_value(P_best) cost calc_cost(P_best) print(总发电成本, cost) print(总网损电量, loss.sum()) print(平均网损率, loss.sum() / load.sum() * 100, %) # 查看约束违反情况 for i in range(N_GEN): over_lim np.maximum(P_best[:, i] - Pmax[i], 0).sum() np.maximum(Pmin[i] - P_best[:, i], 0).sum() diff np.abs(P_best[1:, i] - P_best[:-1, i]) over_ramp np.maximum(diff - Ramp[i], 0).sum() print(f机组{i1} 越限总量: {over_lim:.4f} MW爬坡越限总量: {over_ramp:.4f} MW)我这边跑出来的典型结果是成本在几十万元量级网损率在3%左右而且末代个体基本能保证爬坡越限总量很小。收敛曲线会呈现快速下降后进入平台期的形态——前80代成本下降明显后面主要是继续调整约束越限部分的小量改进。从出力结果看夜间低负荷时段三台机组都在较低水平运行白昼高峰时段G2和G3出力明显抬升G1在尖峰时段才被推到较高位置。这正是带了爬坡约束后的典型调度形态机组出力变化是连续的、光滑的而不是在经济最优的“跳变解”。4. 遗传算法跑起来之后的常见坑与排查思路4.1 早熟收敛种群还没搜索够就抱团取暖GA最常见的现象是前几十代收敛很快后面几乎没有改进。这未必是坏事如果目标函数是凸的快速收敛说明算法很快锁定了好区域。但在带强约束的问题里快速收敛往往是“假收敛”种群里的个体高度相似大家都落在某个违规惩罚较小的局部区域而不是真正的经济最优区。我常用的解决办法有三个。第一提高变异率。不要让变异只发生在单个时段可以设计一种“连续变异”随机选一个时间段区间对区间内所有时段的基因都加一个趋势性偏移。这样变异既能改变局部细节也能影响整段爬坡曲线。第二增大种群规模。48维搜索空间不算大但带复杂约束的问题很容易形成大量局部陷阱种群太小根本覆盖不过来。建议从60起步多试80、100观察曲线是否还有明显下降空间。第三调整选择压力。锦标赛规模越大选择压力越强收敛越快但也更容易早熟。我这里用3是很温和的设定如果发现早熟可以降到2试试。4.2 罚函数系数总在对与错之间摇摆惩罚系数是这类方法的灵魂也是最难调的部分。罚得轻了GA会给出明显违约的结果因为违约带来的成本降低大于被罚的数量罚得重了算法会倾向于满足约束但也可能因此错过一些合理的候选解收敛变得很慢。我的经验是先把约束越限的惩罚权重设为发电成本量级的100-1000倍。本算例里单台机组小时成本大约几千元越限1MW如果只罚100元等于让算法“鼓励”越限罚到5000-10000元越限1MW的代价和正常发电成本拉开量级合理。调参技巧是跑一次打印每个约束的越限总量和惩罚量。如果一个约束越限几乎为零但总成本很高说明罚重了如果某个越限频繁出现说明对应权重偏低或者搜索空间根本不好找可行解。4.3 爬坡越限总是消不掉怎么办如果最后结果的成本很低但爬坡越限总量一直不为零问题通常不在权重而在搜索结构。本项目的代码里初始化时用了随机游走这能保证G1和G2的初始爬坡基本满足但单点交叉会直接打断爬坡连续性。父代1的前半段和父代2的后半段拼接后交界处很可能出现一个很大的出力跳变。这个跳变虽然在适应性评估时会被罚但GA需要很多代才能修复这种“结构性伤害”。解决方向有几个改用均匀交叉每个时段独立选择来自哪个父代而不是在时间轴上切一刀。均匀交叉对连续变量的破坏更小。加入启发式修复交叉或变异之后检查每个机组相邻时段是否越限如果越限就把越限方向的出力拉回来。这是一种拉马克式修复能大幅提升解可行性。增加对爬坡约束的权重同时也给变异操作加一个“爬坡感知”变异时优先选相邻时段让变异后的偏移在爬坡允许范围内。实际项目中我最后用了“变异后修复”的做法代价是计算量增加一点但可行性提升非常明显。4.4 为什么代码换一组数据就跑不出好结果GA的性能严重依赖问题规模、约束结构和数据量级。换机组数、换时段数、换负荷曲线之后原先调好的惩罚权重基本都要重调。这里有一个重要原则在写适应度函数时尽量把成本项和惩罚项都做“归一化”。比如发电成本除以一个基准成本越限惩罚也除以一个量纲统一后的数值。这样换数据后参数仍有参考性。如果直接把5000、8000这种绝对权重写死在里面遇到成本量级差100倍的新算例整个算法又要推倒重来。另外时段粒度变细以后比如从1小时间隔变成15分钟间隔爬坡约束的允许变化量要除以4但遗传算法里的变异步长并不会自动缩放。这也是换参数后经常崩溃的原因。5. 这个项目还能怎么扩展5.1 从单目标走向多目标成本和排放一起优化当前版本只优化发电成本。现实调度中碳排放、污染物排放、机组启停次数、燃料消耗量都可以作为优化目标。可以把模型改写成多目标优化用NSGA-II这类算法同时得到一组Pareto前沿解再交给决策者选择。从代码结构上看只需要把适应度函数改成返回一个目标向量然后替换选择算子为基于支配关系的锦标赛选择。其他部分包括解码、松弛机组、爬坡惩罚几乎不用动。这个扩展非常适合作为课题的下一个阶段。5.2 和商业求解器、数学规划方法做对比遗传算法不是万能钥匙。机组数和时段数非常大时比如上百台机组、96个时段纯GA的搜索效率会明显下降因为浮点数编码的搜索空间太大了。此时可以考虑先用拉格朗日松弛把问题拆成分机组子问题再用动态规划求解每个单机组的时序优化问题或者直接用商业求解器处理二次约束规划模型。我建议把这套Python代码当成“原型验证工具”而不是最终的生产求解器。它的价值在于快速验证模型和约束的合理性给后面的精确求解算法提供一个高质量初值。事实上用GA结果当初始解再交给局部优化算法做精修这种“粗搜精修”的组合策略在工程上非常有效。5.3 从离线优化到滚动在线调度上面的代码是离线一次性优化24小时。实际调度场景里负荷预测每天都在更新机组状态也在实时变化。真正能落地的版本应该是“滚动优化”每4小时或者每小时更新一次负荷预测重新跑一次优化只执行未来一个时段的调度决策然后滚动前移。这个扩展对算法的实时性提出了要求。GA作为一种慢速算法直接做小时级在线调度有点吃力但可以结合历史最优解热启动、限制种群规模、缩短迭代代数等方法在可接受的时间内输出一个次优解再交给人工确认。作为研究项目先把离线版本跑通再逐步加滚动窗口是合理的推进路径。我个人在完成这个项目后最深的体会是遗传算法这部分代码真正的门槛不在算法本身而在你把约束梳理清楚、把编码设计合理的那一步。松弛机组、爬坡感知初始化、线性惩罚这三个小设计比后面调几千代参数都管用。如果你正在做类似的经济调度优化建议先把模型公式写漂亮再动手写GA顺序反了的调试成本会非常感人。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询