简介:这是一套基于Python的遗传算法微电网优化调度完整项目,面向电力系统、能源管理及智能算法学习者与开发者。项目将光伏、风电、储能与常规机组统一建模,可支持并网与孤岛两种运行模式,以运行成本、碳排放和供需平衡为约束,利用遗传算法的选择、交叉与变异迭代搜索最优发电组合及负荷分配策略,适用于课程设计、毕业设计或科研对照实验。压缩包共555个文件,约2.91MB,以85个py源码为算法核心,配以140个js、94个html与41个css等前端资源构成可视化界面,另含pyc编译文件和图片、字体等素材,目录结构清晰,上手门槛较低。目前已有2000余人学习下载。资源内置配置、模型、个体编码、适应度计算、种群管理与主程序等模块,使用者可调整各能源单元参数,通过前端页面查看并分析调度结果,也能够在此框架上扩展多目标优化或不确定性场景,对理解遗传算法工程落地及微电网节能降碳调度均有直接帮助。
1. 遗传算法微电网优化调度,先讲清楚这个标题到底在求什么
微电网优化调度要回答的问题很具体:拿到未来24小时的负荷、光伏出力、风电出力预测曲线以后,每一小时电池该充多少放多少、柴油机发多少电、向大电网买或卖多少电,才能在满足所有运行约束的前提下让总运行成本最低。难点在于这是个带连续变量和离散开关的非凸问题,柴油机有出力与爬坡限制,电池有SOC上下限,电网交互还叠加峰谷分时电价,传统线性规划要做大量线性化,而遗传算法直接对变量编码进化,绕开那一大堆凸化处理。下面这套实现只依赖numpy,完整的遗传算法python代码详解在第三章,算例与参数整定在第四章。适合已经能写Python、想把手头调度问题快速跑成第一版内核的工程师和研究生。
2. 建立微电网调度模型:目标函数、约束与Python数据结构
2.1 决策变量怎么划分,为什么把电网功率当松弛变量
微电网的典型拓扑包含光伏、风电、蓄电池、柴油发电机和联络线。调度周期取24小时、步长1小时,每个时段都有负荷、光伏、风电的预测值,以及从电网买电和向电网卖电两套分时电价。
决策变量我选两个序列:电池出力P_bat(t)(放电为正、充电为负)和柴油机出力P_diesel(t)。电网交换功率P_grid(t)不参与编码,而是由功率平衡方程推导出来,当作松弛变量。这样设计的好处是遗传算法个体维度固定为48(两个24维序列),搜索空间可控;坏处是P_grid可能越限,必须在适应度函数里用罚函数处理。如果反过来把三个序列全部编码,个体变成72维,收敛速度会明显变慢,第一版不建议这么干。
2.2 目标函数和四条硬约束
目标函数是日运行成本最小化,包含四项:
- 柴油燃料成本:
fuel_price × P_diesel(t),按线性折算,实际中柴油机有最低技术出力,线性近似后可在第六章做分段线性修正; - 电池退化成本:
α × |P_bat(t)|,把充放电循环折算成每kWh的损耗; - 购电成本:
max(P_grid(t), 0) × price_buy(t); - 售电收入:
max(-P_grid(t), 0) × price_sell(t),从总成本里扣除。
约束一共四条:功率平衡、电池SOC递推与上下限、柴油机出力与爬坡限制、联络线功率上限。SOC的递推关系是SOC(t) = SOC(t-1) - P_bat(t) × Δt / Cap,充放电一小时,SOC就相应变化P_bat / Cap。
2.3 用dataclass把算例参数组织起来
写遗传算法之前,先把参数结构定下来,后面所有函数都引用它,避免散落一地的魔法数字。
from dataclasses import dataclass import numpy as np @dataclass class MGParam: load: np.ndarray # 24h 负荷预测,单位 kW pv: np.ndarray # 24h 光伏预测出力,kW wind: np.ndarray # 24h 风电预测出力,kW price_buy: np.ndarray # 24h 购电价,元/kWh price_sell: np.ndarray # 24h 售电价,元/kWh cap: float = 1000.0 # 电池容量 kWh soc_min: float = 0.2 # SOC 下限 soc_max: float = 0.9 # SOC 上限 soc_init: float = 0.5 # 初始 SOC p_bat_max: float = 250.0 # 电池最大充/放功率 kW diesel_min: float = 0.0 # 柴油机最小出力 kW diesel_max: float = 300.0 # 柴油机最大出力 kW ramp_max: float = 120.0 # 柴油机每小时爬坡上限 kW/h grid_max: float = 400.0 # 联络线功率上限 kW fuel_price: float = 0.68 # 柴油折算成本 元/kWh bat_degrade: float = 0.12 # 电池退化折算 元/kWh这里把电池容量设为1000kWh、最大充放功率250kW,意味着满功率充放一小时SOC变化0.25,不会出现一个时段内数值突变。柴油机爬坡上限120kW/h,峰谷切换时出力变化被限制住。grid_max用来约束倒送功率和购电上限,分布式光伏大发时段容易触及。
3. 遗传算法python代码详解:实数编码、进化算子与主循环
3.1 个体编码和罚函数:最省事的约束处理方法
个体是一个48维实数向量,前24维是电池各时段出力,后24维是柴油机各时段出力。约束处理采用罚函数法:SOC越限、电网功率越限、爬坡越限都按超限程度累加一个很大的惩罚量。惩罚量级要高于正常日运行成本,这个场景下日成本是几千元量级,所以用1e4到1e5,保证任何带约束违规的个体都不可能成为全局最优。
为什么不直接限制编码范围来避免越界?SOC是跨时段递推量,单点修改一维变量会连锁影响后续时段的SOC,没法靠简单clip解决,只能罚。
3.2 锦标赛选择、单点交叉、高斯变异的具体实现
选择算子用锦标赛,每次从种群随机抽3个个体,留下适应度最小的那个;交叉用单点交叉,交叉点落在48维向量中间;变异用高斯扰动,每个维度以一定概率叠加正态分布噪声。
def tournament_select(pop, fits, k=3): idx = np.random.choice(len(pop), k, replace=False) best = idx[np.argmin(fits[idx])] return pop[best].copy() def crossover(p1, p2, rate=0.85): if np.random.rand() > rate: return p1.copy(), p2.copy() point = np.random.randint(1, len(p1) - 1) c1 = np.concatenate([p1[:point], p2[point:]]) c2 = np.concatenate([p2[:point], p1[point:]]) return c1, c2 def mutate(ind, rate=0.15, sigma=12.0): ind = ind.copy() mask = np.random.rand(len(ind)) < rate ind[mask] += np.random.normal(0, sigma, mask.sum()) return ind锦标赛选择里fits[idx]是适应度数组的子集,argmin取最小值的索引,再映射回原种群下标,这一步容易写错的地方是忘记.copy(),导致后续交叉变异直接改掉了父代。变异里的sigma=12.0需要和决策变量量级匹配,电池和柴油机出力都是几十到几百kW,12kW的扰动在合理范围;如果把sigma设成50,后代容易被随机噪声带偏。
3.3 适应度函数与带精英保留的遗传算法python主循环
适应度函数按2.2节的目标函数和罚函数逐项累加:
def fitness(ind, p: MGParam): n = 24 p_bat = ind[:n] p_dz = ind[n:] penalty = 0.0 # 1) SOC 递推并加罚 soc = p.soc_init for t in range(n): soc -= p_bat[t] / p.cap # 步长1小时 if soc < p.soc_min: penalty += 1e5 * (p.soc_min - soc) elif soc > p.soc_max: penalty += 1e5 * (soc - p.soc_max) # 2) 电网交换功率越限加罚 p_grid = p.load - p.pv - p.wind - p_bat - p_dz penalty += 1e5 * np.sum(np.maximum(0, p_grid - p.grid_max)) penalty += 1e5 * np.sum(np.maximum(0, -p_grid)) # 3) 柴油机爬坡越限加罚 ramp = np.abs(np.diff(p_dz)) penalty += 1e4 * np.sum(np.maximum(0, ramp - p.ramp_max)) # 4) 运行成本:燃料 + 电池退化 + 购电 - 售电 cost_fuel = (p_dz * p.fuel_price).sum() cost_bat = p.bat_degrade * np.abs(p_bat).sum() cost_grid = (np.maximum(p_grid, 0) * p.price_buy).sum() \ + (np.minimum(p_grid, 0) * p.price_sell).sum() return cost_fuel + cost_bat + cost_grid + penalty注意p_grid是功率平衡的剩余项,正值表示从电网买电,负值表示倒送。np.minimum(p_grid, 0) * p.price_sell得到的是负数,加到总成本里等于扣掉售电收入。爬坡约束用了np.diff,只对相邻小时做差,24维柴油机序列产生23个差分值。
主循环加入精英保留,每代把适应度最好的5个个体原样复制进下一代,防止最优解在交叉变异中丢失:
def ga_run(p, pop_size=100, max_gen=200, elite=5): n = 24 pop = np.zeros((pop_size, 2 * n)) pop[:, :n] = np.random.uniform(-p.p_bat_max, p.p_bat_max, (pop_size, n)) pop[:, n:] = np.random.uniform(p.diesel_min, p.diesel_max, (pop_size, n)) best_hist = [] best_fit = np.inf best_ind = None for gen in range(max_gen): fits = np.array([fitness(ind, p) for ind in pop]) order = np.argsort(fits) best_hist.append(fits[order[0]]) if fits[order[0]] < best_fit: best_fit = fits[order[0]] best_ind = pop[order[0]].copy() # 精英直接进入下一代 new_pop = [pop[i].copy() for i in order[:elite]] while len(new_pop) < pop_size: p1 = tournament_select(pop, fits) p2 = tournament_select(pop, fits) c1, c2 = crossover(p1, p2) c1 = mutate(c1) c2 = mutate(c2) new_pop.append(c1) new_pop.append(c2) pop = np.array(new_pop[:pop_size]) return best_ind, best_hist初始化时电池出力在两倍最大充放电功率区间内随机,柴油机在最小和最大出力之间随机。best_ind单独保存,因为每代结束后pop会被新种群覆盖,不另存就会丢历史最优。new_pop[:pop_size]是为了处理while循环可能多塞一个个体的边界情况。
4. 24小时算例与参数设定:遗传算法收敛性分析与必调参数
4.1 构造可复现的负荷、光伏、风电与分时电价场景
用固定随机种子生成一组典型日数据:负荷是早晚双峰曲线,光伏是中午单峰,风电叠加低频波动,电价按峰平谷三档设计。所有曲线都是24维数组,直接传给MGParam。
rng = np.random.default_rng(42) t = np.arange(24) # 早晚双峰负荷,单位 kW load = 520 + 180 * np.exp(-((t - 12) ** 2) / 10) \ + 150 * np.exp(-((t - 19) ** 2) / 6) + rng.normal(0, 15, 24) # 光伏:只在白天出力的钟形曲线 pv = np.clip(240 * np.sin(np.pi * (t - 6) / 12), 0, None) # 风电:均值90kW附近波动 wind = np.clip(90 + 60 * np.sin(2 * np.pi * t / 24) + rng.normal(0, 12, 24), 0, None) # 分时电价:高峰1.3元,平段1.1元,低谷0.42元 price_buy = np.where((t >= 8) & (t < 12), 1.1, np.where((t >= 17) & (t < 22), 1.3, 0.42)) price_sell = price_buy * 0.6 param = MGParam(load=load, pv=pv, wind=wind, price_buy=price_buy, price_sell=price_sell)负荷最低点出现在凌晨,约520kW上下,晚高峰约850kW。光伏在正午达到240kW峰值,风电全天在60到150kW之间。这个场景的容量配比是刻意设计的:电池、柴油机、联络线任何单一资源都不足以覆盖晚高峰,必须多资源协同,遗传算法才有真正的优化空间。
4.2 必调的六个参数与推荐区间
| 参数 | 推荐区间 | 设置依据与常见坑 |
|---|---|---|
pop_size | 80 ~ 200 | 48维连续变量,80以下容易陷局部最优,超过200收益递减 |
max_gen | 150 ~ 400 | 150代内通常明显收敛,400代后基本不变 |
cross_rate | 0.8 ~ 0.9 | 低于0.7进化太慢,后代与父代高度相似 |
mut_rate | 0.1 ~ 0.2 | 每个个体约5到10个维度被扰动,过高会退化成随机搜索 |
mut_sigma | 5 ~ 15 | 与决策变量量级匹配,一般不超过变量范围的十分之一 |
elite | 3 ~ 10 | 精英太少最优解可能回退,太多会压制种群多样性 |
上面这套参数对应种群100、迭代200代时,每次运行约2万次适应度评估。纯numpy实现跑完大概几秒到十几秒,性能不是瓶颈,瓶颈在参数不匹配导致的收敛停滞。
4.3 收敛曲线怎么看:区分真收敛和提前停滞
运行完把best_hist画出来,横轴是代数,纵轴是适应度。适应度包含罚分量,量级跨度大,建议用对数坐标:
import matplotlib.pyplot as plt best_ind, hist = ga_run(param, pop_size=120, max_gen=250) plt.plot(hist) plt.yscale("log") plt.xlabel("generation") plt.ylabel("best fitness (log)") plt.grid(alpha=0.3) plt.show()正常的收敛曲线是前50代快速下降、之后斜率趋缓、最后水平。如果曲线在前20代就完全走平,多半是提前收敛:种群多样性耗尽,此时先调小mut_sigma或调大mut_rate,而不是加迭代次数。如果曲线一直在波动没有下降趋势,先检查罚函数是不是太高,导致可行域被惩罚梯度完全屏蔽,再检查初值范围是否明显偏离合理区间。
5. 调度结果验证与落地时的三个实用技巧
5.1 用谷充峰放规则法给遗传算法结果做下限校验
遗传算法结果对不对,最直接的办法是写一个无优化的规则策略做对比。最简单有效的规则是谷段充电、峰段放电,柴油机关停,缺电全部由电网补充:
def rule_baseline(p): p_bat = np.zeros(24) p_bat[0] = -p.p_bat_max # 00:00-01:00 低谷充电 p_bat[20:22] = p.p_bat_max # 20:00-22:00 高峰放电 p_dz = np.zeros(24) p_grid = p.load - p.pv - p.wind - p_bat - p_dz cost = np.maximum(p_grid, 0).dot(p.price_buy) \ + np.minimum(p_grid, 0).dot(p.price_sell) \ + p.bat_degrade * np.abs(p_bat).sum() return cost这个规则策略的SOC变化是:初始0.5,充1小时到0.75,峰时放2小时到0.25,正好不低于0.2,约束不越界。遗传算法优化出的成本应当低于或接近这个规则法结果,如果反而更高,优先检查SOC初始值和罚函数系数,大概率是模型公式写错而不是算法问题。
5.2 惩罚系数量级要和日成本同量级以上
罚函数最容易被忽视的是量级匹配。日运行成本是几千元,罚系数用1或10的话,越界个体照样可能评上最优;用1e6又会让适应度曲面出现悬崖,遗传算法被大量罚分吓住,所有个体都缩在远离边界的区域,反而找不到贴着约束边界的最优解。1e4到1e5是这类调度题目的经验区间。另外,对电池和柴油机变量做边界修复比纯罚更高效,个体代入适应度前先做一次np.clip,把越界变量拉回范围,能显著改善收敛性,代价是损失一小部分搜索随机性。
5.3 末段SOC约束与自适应变异,让调度结果可直接落地
日常滚动调度要求一天结束时SOC回到初始值附近,否则第二天的调度起点漂移,日复一日误差累积。加上终端约束:在适应度里追加|SOC(24) - soc_init|的罚分,就能迫使算法给出可循环的调度方案。配合自适应变异提高后期局部搜索精度:
def mutate_adaptive(ind, gen_rate, base_sigma=12.0): sigma = base_sigma * (1.0 - gen_rate) + 2.0 * gen_rate return mutate(ind, rate=0.15, sigma=max(sigma, 1.5))gen_rate是当前代数占总代数的比例,进化前期用大步长探索,后期自动收窄到小步长精修,比固定sigma更容易在最后几十代里压出几百元的成本差。
本文还有配套的精品资源,点击获取