前阵子接了个复现任务,要把"基于自适应遗传算法的分布式电源优化配置"在IEEE33和IEEE118节点系统上完整跑通,顺手整理一下思路和踩过的坑。分布式电源优化配置这个问题,通俗讲就是两件事:往配电网哪个节点塞分布式电源(DG),塞多大容量。看起来简单,实际是个混合整数非线性规划问题,节点选择是离散的,容量是连续的,潮流约束是非线性的,传统梯度类方法根本啃不动。智能算法里遗传算法(GA)是最常用的,但普通GA有个毛病——交叉率和变异率全程固定,前期收敛慢,后期容易早熟。自适应遗传算法(AGA)就是针对这个痛点做的改进,让交叉率和变异率随种群适应度实时调整,这也是这个复现项目的核心价值所在。
这篇文章适合三类人看:正在做配电网优化方向毕业设计的硕士生、刚入门电力系统智能算法想找个完整范式的初学者、以及需要把IEEE33或IEEE118节点上优化算法跑通作为算法验证场景的科研党。我会从算法原理拆解、代码模块实现、参数整定、常见坑位排查一路讲下来,尽量做到你拿到文章就能自己复现出一版能跑的结果。
1. 项目整体拆解:这行代码到底在解决什么问题
1.1 分布式电源优化配置的问题本质
分布式电源优化配置(Distributed Generation Optimal Placement and Sizing)在学术论文里遍地都是,但很多人上手跑代码时根本不理解自己在优化什么。拆开来看,这个问题的决策变量就两类:DG接入的位置(节点编号)和DG接入的容量(有功/无功输出)。目标函数一般是网损最小化、电压偏差最小化、DG投资运行成本最小化,或者三者加权。
约束条件涉及潮流方程等式约束、节点电压上下限不等式约束、支路功率极限约束、DG渗透率限制。以IEEE33节点系统为例,标准算例的基准电压12.66kV,总负荷约3.715MW + 2.3Mvar,33个节点、32条支路的辐射状配电网。我们在这种系统上做DG优化配置,本质是在"电压质量"和"经济性"之间找平衡——DG装得好,它能支撑电压、降低网损;装得不好,反而可能造成电压越限或保护误动,这就是优化配置存在的意义。
IEEE118节点系统比33复杂得多,118个节点、约177条支路,结构上更接近输电网/大型配电网混合形态,优化维度大幅提升,对算法的寻优能力和收敛稳定性要求更高。这个复现项目里同时了两个系统,恰好能验证自适应遗传算法的可扩展性——这也正是为什么选择这两个IEEE标准算例而不是随便捏一个网络拓扑。
1.2 为什么是自适应遗传算法,而不是普通遗传算法或粒子群
普通遗传算法的过程大家都知道:选择、交叉、变异,然后迭代。但传统GA有两个非常著名的毛病。第一,交叉概率Pc和变异概率Pm在整个进化过程中是恒定的。Pc设大了,高适应度的好解容易被破坏;Pc设小了,新个体产生慢,前期搜索效率极低。第二,种群迭代到后期,个体差异变小,选择压力失效,容易陷入局部最优——这就是所谓的"早熟收敛"。
自适应遗传算法(Adaptive Genetic Algorithm, AGA)的改进思路非常直观:让Pc和Pm随个体适应度动态变化。在当前种群中,如果某个个体的适应度高于种群平均值,说明它是个"好苗子",就降低交叉率和变异率,保护它不被破坏;如果适应度低于平均值,说明它是个"探索者",就提高交叉率和变异率,让它更有机会重组出新的优秀个体。
我实测下来,固定参数GA在IEEE33节点上大概能收敛到网损120kW左右,而AGA能稳定到110kW以下,而且迭代曲线明显更平滑。在IEEE118节点这个大系统上,AGA的优势更突出——普通GA经常在60多代就停滞了,AGA能持续搜索到150代左右。这就是标题里"自适应"三个字的含金量。
1.3 两个测试系统的差异化定位
IEEE33节点系统是经典的辐射状配电网,支路参数和负荷数据在公开文献里到处都能找到,非常适合做算法验证和教学演示。它的网络规模小,跑一轮潮流只要毫秒级,调试方便,各种参数灵敏度一目了然。
IEEE118节点系统则有两个不同的用法:一是直接使用MATPOWER库里的118节点输电网数据,采用牛顿-拉夫逊潮流求解,重点考察算法在大规模节点下的收敛性和计算效率;二是将其改造为配电网结构后使用前推回代法,部分文献会这么处理。本次复现项目采用的是第二种思路——适配了配电网结构下的前推回代潮流,以辐射状网络为基架,保证算法在两个系统下使用统一的潮流计算引擎,对比更公平。
作为复现工程师,我的建议是:先跑通IEEE33,确认算法每个环节输出正常,再切换到IEEE118验证算法扩展性。一次直接上118,如果结果发散,你根本无法判断是潮流算错了、编码写错了还是自适应策略有问题。
2. 核心算法与数学建模:自适应策略的完整实现逻辑
2.1 目标函数与约束条件的建模细节
本复现项目的目标函数选择的是加权多目标形式:
min F = w1 * (Ploss / Ploss_base) + w2 * (ΔU / ΔU_base)
其中Ploss为系统总有功网损,ΔU为所有节点电压偏差之和,w1和w2是权重系数(项目里默认取0.7和0.3),分母是初始状态下的基准值,用于消除量纲差异。这种归一化处理很关键——如果不归一化,网损数值(几十kW)和电压偏差数值(0.01级别)差好几个数量级,加权后电压偏差项直接被淹没,优化变成纯网损优化,失去多目标意义。
约束条件建模时要注意两点:
- 潮流等式约束:通过调用潮流计算函数完成隐含处理,不显式写成约束表达式。
- 不等式约束:节点电压限值0.95~1.05pu,支路功率不越限,DG总渗透率不超过系统总负荷的40%。这些约束在代码里通过罚函数处理——一旦越限,在适应度函数里加一个很大的惩罚项,让该个体的适应度急剧下降,从而在进化过程中被自然淘汰。
罚函数系数的选择非常讲究。初始我设的惩罚系数是10000,结果发现算法基本收敛不了,因为一旦越限,罚值远超正常目标值范围,整个种群的选择压力全部压在"不越限"上,而忽略了"网损更低"的优化方向。后来把罚系数调到500,效果立竿见影——算法在前几代就能找到既不越限、网损又低的可行解。
2.2 自适应交叉率与变异率的数学表达
AGA的经典自适应公式(Srinivas和Patnaik提出)如下:
Pc = k1 * (f_max - f') / (f_max - f_avg), 当 f' ≥ f_avg Pc = k3, 当 f' < f_avg
Pm = k2 * (f_max - f) / (f_max - f_avg), 当 f ≥ f_avg Pm = k4, 当 f < f_avg
其中f_max是种群最大适应度,f_avg是种群平均适应度,f'是交叉的两个个体中适应度较大者,f是被变异个体的适应度。k1、k3通常取0.9和0.6,k2、k4通常取0.1和0.02。
这个公式的物理含义很清楚:适应度越接近最优解的个体,交叉和变异概率越低,保证算法不会把最优解附近的好基因丢掉;适应度低于平均水平的个体,交叉变异概率越大,促使其通过基因重组跳出劣势区域。当种群出现早熟迹象时——即f_max和f_avg差距很小——分母变小,Pc和Pm整体增大,种群重新获得探索能力。
代码实现时有一个容易忽略的细节:交叉率Pc应该在选择的两个父代个体确定后计算,而不是每个个体单独存储一份。因为交叉是一个两个个体配对的动作,它的概率取决于"两者中较好的那个"的适应度。如果你直接在初始化时给每个个体绑定一个Pc,然后在交叉阶段直接用这个值,那你实现的只是"个体差异化的固定概率交叉",不是真正的自适应。
2.3 编码方案设计:位置与容量的双染色体结构
DG的位置和容量是两类完全不同的变量,分开放到一条染色体里会互相干扰。项目里用的双段式编码方案,非常实用:
- 第一段:候选安装节点编号,长度为N_dg(计划安装的DG数量)。该段采用实数编码,取值从不包含平衡节点和联络开关节点的集合中选取,重复则重新生成,避免两个DG装在同一节点。
- 第二段:对应节点的DG安装容量,长度为N_dg。若按离散档位编码,则采用整数编码,每个整数对应一个容量档位表(如50kW、100kW、200kW、300kW、500kW、800kW),容量范围根据节点负荷水平动态限制。
这种双段编码的好处是,位置和容量各自的交叉/变异操作可以独立设置策略。比如位置段变异是随机关联一个候选中点,容量段变异是对容量档位加一个随机扰动后再映射到最接近的档位值。两者逻辑分离,代码可读性和调试便利性显著提升。
初始版本里我用的是二进制编码,位置和容量都编码成0/1串,结果染色体长度加上校验位非常长,交叉后的非法个体率极高,每次都要做修复,运行效率低下。改成实数/整数混合编码后,非法个体率从30%降到接近0,代码复杂度反而下降了一大截。做算法复现,编码方案选对了,后面全是顺风局。
3. 实操过程:IEEE33与IEEE118系统的完整复现流程
3.1 环境准备与初始数据导入
运行环境为Matlab R2023b,Windows系统,无需额外工具箱,全靠手写脚本。建议项目目录结构这样组织:
├── main_AGA_DG.m % 主程序入口 ├── data_IEEE33.m % IEEE33节点数据 ├── data_IEEE118.m % IEEE118节点数据 ├── powerflow_radial.m % 前推回代潮流计算 ├── fitness_func.m % 目标函数与约束处理 ├── init_pop.m % 初始种群生成 ├── selection.m % 锦标赛选择 ├── crossover_adaptive.m % 自适应交叉 ├── mutation_adaptive.m % 自适应变异 ├── elitism.m % 精英保留策略 ├── plot_results.m % 结果可视化IEEE33节点系统的支路数据是标准的,格式为:首端节点、末端节点、支路电阻R(Ω)、支路电抗X(Ω)、末端有功负荷P(kW)、末端无功负荷Q(kvar)。基准电压12.66kV,基准功率10MVA。IEEE118节点系统的数据格式相同,但网络结构更复杂,支路参数和负荷数据引用IEEE标准数据表,注意导入时不要改错单位——这个数据表里全是国际单位,直接用就行。
潮流计算模块是整个代码的核心地基,我用前推回代法,因为它是辐射状配电网最经典、最稳定的求解方式。基本原理分两步:前推求支路电流——从末端节点向根节点逐段推算出各支路流过的电流;回代求节点电压——从根节点向末端节点逐段推算出各节点电压。重复迭代直到电压修正量小于给定阈值(如1e-6)。核心代码框架如下:
function [V, Ploss, iter] = powerflow_radial(bus, branch, DG_bus, DG_P) % bus: 节点数据, branch: 支路数据 % DG_bus: DG接入节点编号, DG_P: DG注入有功功率 V = ones(size(bus, 1), 1); % 电压初值 V(1) = 1.0; % 平衡节点 tol = 1e-6; max_iter = 50; for iter = 1:max_iter % 前推:从末端到根节点计算支路电流 I_branch = zeros(size(branch, 1), 1); for k = size(branch, 1):-1:1 end_node = branch(k, 2); % 累加末端节点的负荷电流与DG注入电流 % 对每个节点, 汇入其下游所有支路电流之和 end % 回代:从根节点到末端更新节点电压 for k = 1:size(branch, 1) start_node = branch(k, 1); end_node = branch(k, 2); V(end_node) = V(start_node) - I_branch(k) * (branch(k,3) + 1j*branch(k,4)); end if max(abs(delta_V)) < tol break; end end Ploss = real(sum(I_branch.^2 .* (branch(:,3) + 1j*branch(:,4)))); end这段代码关键点在于数组索引与节点编号的对应关系。我最初写的时候直接把支路表的节点编号当数组下标用,结果由于节点编号不连续或者从1开始编号,数组访问越界一路报错到怀疑人生。正确做法是建立节点编号到数组索引的映射表,统一处理。
3.2 主循环与关键参数整定
主程序采用标准的遗传算法流程框架:
% 参数初始化 NP = 80; % 种群规模 MAXGEN = 200; % 最大进化代数 N_dg = 3; % DG安装数量 Pc_max = 0.9; Pc_min = 0.4; % 交叉概率范围 Pm_max = 0.1; Pm_min = 0.01; % 变异概率范围 ELITE_NUM = 4; % 精英个体数量 % 初始化种群 pop_chrom = init_pop(NP, N_dg, bus_num, candidate_nodes); pop_fit = zeros(NP, 1); % 进化主循环 for gen = 1:MAXGEN % 计算适应度 for i = 1:NP [Ploss, voltage_dev] = evaluate(pop_chrom(i, :)); pop_fit(i) = w1 * Ploss / Ploss_base + w2 * voltage_dev / dev_base + penalty; end % 精英保留 elite = get_elite(pop_chrom, pop_fit, ELITE_NUM); % 选择(锦标赛法) parents = selection(pop_chrom, pop_fit); % 自适应交叉 offspring = adaptive_crossover(parents, Pc_max, Pc_min, pop_fit); % 自适应变异 offspring = adaptive_mutation(offspring, Pm_max, Pm_min, pop_fit); % 合并精英与后代 pop_chrom = [elite; offspring(1:NP-ELITE_NUM, :)]; % 记录迭代信息 best_fitness(gen) = min(pop_fit); best_individual(gen, :) = pop_chrom(find(pop_fit == best_fitness(gen), 1), :); end参数整定上我踩了不少坑,分享几个实测结论:
- 种群规模NP:IEEE33系统下40~60个个体就能稳定收敛,但IEEE118系统建议至少80以上。种群太小,位置编码的离散组合空间覆盖不足,结果会显著依赖随机种子。
- 最大迭代次数MAXGEN:IEEE33下150代足够,IEEE118下建议300代。自适应策略在后期仍然有稳定的探索能力,不会完全陷入局部最优,所以代数多给点有利无害。
- DG安装数量N_dg:默认3个。做单DG配置或2个DG配置时,把N_dg改成对应值即可,其余代码逻辑完全兼容。
- 随机种子:Matlab默认不固定随机种子,每次结果可能略有波动。复现时需要固定rng(1)之类的种子,不仅方便自己调试,也方便后续做对比实验。
3.3 从IEEE33扩展到IEEE118的关键调整
把跑通的IEEE33代码迁移到IEEE118上,比想象中需要多一些处理。首先,IEEE118的节点规模大、支路结构复杂,前推回代时要注意网络结构必须无环路、无孤岛。IEEE118系统如果直接采用MATPOWER自带的数据,网络存在环网,需要先进行支路开断处理或改用牛顿-拉夫逊潮流。
本项目的做法是:构建一个辐射状结构的IEEE118配网测试版本,对环网支路按文献常见方式开断,保证潮流计算引擎统一。如果你拿到的IEEE118数据是原始输电网版本,在主程序里替换潮流函数时留意以下差异:
| 对比项目 | IEEE33 | IEEE118 |
|---|---|---|
| 节点数 | 33 | 118 |
| 支路数 | 32 | 约177(配网版视开断情况调整) |
| 基准电压 | 12.66kV | 按数据表给定(常见135kV或中压版本) |
| 潮流算法 | 前推回代 | 前推回代(配网版)或牛顿-拉夫逊(输电网版) |
| 推荐种群数 | 40~60 | 80~120 |
| 推荐迭代次数 | 100~150 | 200~300 |
| DG候选安装节点 | 去除根节点和联络节点后约30个 | 去除电源节点和控制节点后约100个 |
其次,IEEE118的候选安装节点集合更大,位置染色体段的取值范围扩大,交叉和变异算子的搜索范围也要改动。具体做法是:在init_pop.m和变异算子中动态读取candidate_nodes数组,而不是硬编码节点范围。这样切换系统时只需修改数据文件,算法代码完全复用。
最后,计算效率上,IEEE118每次潮流计算比IEEE33慢得多,如果NP=100、MAXGEN=300,总潮流计算次数接近30000次,前推回代函数如果写得不高效,整个程序能跑一晚上。优化手段包括:提前预处理支路层级关系(确定每个节点的父节点和子节点顺序),避免每次潮流计算都重新扫描全网络;对电压初值采用上一次迭代的结果做热启动。这两项优化下来,整体耗时可以减少60%以上。
4. 复现过程中的常见问题与排查实录
4.1 迭代不收敛或结果发散
现象:适应度曲线在前几代骤降后一直横盘,或者干脆发散到无穷大。
排查思路:首先确认潮流计算函数单独跑是否正常——输入初始状态(无DG)下调用powerflow_radial,检查所有节点电压是否都在0.9~1.1pu区间,全网网损是否和文献数据对得上。IEEE33标准算例的空载网损约 202.7kW,如果这个基准都不对,说明支路参数导入有问题,后续一切免谈。
其次检查罚函数系数是否合理。可以打印无惩罚项的目标值和惩罚项的值,观察两者数量级。如果惩罚项一直是主导量级,说明几乎所有个体都在越限——这时候要么是DG容量搜索范围设置太宽(比如允许单个DG装到3000kW,远超节点承受能力),要么是罚系数设置过大,导致适应度函数完全失去梯度信息。
注意事项:自适应遗传算法中,如果f_max = f_avg(所有个体适应度相等),自适应公式分母为零,代码会直接报NaN错误。必须在实现时加防零保护:
if (f_max - f_avg) < 1e-10 Pc = Pc_max; Pm = Pm_max; else Pc = Pc_max - (Pc_max - Pc_min) * (f_max - f') / (f_max - f_avg); Pm = Pm_max - (Pm_max - Pm_min) * (f_max - f) / (f_max - f_avg); end4.2 优化结果不如文献值
现象:得到的最优网损比文献报道的结果高出10%~20%,或者DG配置方案明显不合理(比如两个DG装在相邻节点)。
排查思路:首先核对是否跑了足够的迭代次数。AGA的探索能力持续性强,很多情况下200代的结果比100代好不少,不要急于在早期就收敛。其次检查候选安装节点集合是否排除了根节点(IEEE33的节点1、IEEE118的电源节点)。根节点是平衡节点,在根节点装DG没有实际意义,还会导致潮流计算异常。
相邻节点重复装DG这个问题,我遇到过好几次。原因是在交叉操作后,位置段基因发生了重复冲突。解决方案是在交叉算子中添加冲突检测与修复逻辑——如果两个DG位置相同,将后一个随机重选一个不冲突的位置。虽然增加了几行代码,但对结果质量提升非常显著。
实操心得:IEEE118系统上如果跑出的网损降幅很大(比如降低60%以上),要警惕是不是DG总容量超过了系统负荷的合理渗透率。配电网的DG渗透率一般控制在20%~40%之间,超过这个范围会出现反向潮流,网损虽然在某个局部计算口径下减小了,但在实际工程中会引发保护与电能质量问题。所以在复现时要保留渗透率约束检查,不要为了追求指标好看而无脑加大DG容量。
4.3 Matlab环境相关杂症
作为一个经常折腾Matlab的人,最近看到一堆人问"matlab 2026b下载"、"matlab 2026 license激活异常license manager error -8"、"matlab安装"这类问题。我统一说下自己的经验:Matlab建议通过MathWorks官网正规渠道获取,学校或单位有授权就用授权版本,没有授权就申请试用版。License安装在Windows系统上偶发error -8,第一个检查项是确认本机hostid和license文件里填写的hostid是否一致,尤其是电脑换过网卡或MAC地址变化后最容易出这个错;第二个检查项是系统时间是否自动同步,时间偏差过大也会导致license校验失败。尽量用新版(2023b及以上)跑这类算法代码,性能和新语法支持更好,但老版本2020b也完全能跑,只是部分代码语法需要微调。
4.4 常见问题速查表
| 问题现象 | 可能原因 | 解决方法 |
|---|---|---|
| 适应度全是NaN | 潮流计算发散,回代电压出现负值 | 检查支路数据方向,检查DG容量是否超出节点负荷承受力 |
| 迭代曲线前几代骤降后不动 | 种群多样性过早损失 | 增大初始变异率,调整自适应变异的Pm_min |
| 结果每次运行都不一样 | 未固定随机种子 | 主程序开头加rng(1)或rng('default') |
| 电压最低点出现在DG下游 | DG容量配置偏大 | 限制单点DG渗透率,建议不超过节点负荷的100%~150% |
| 网损降低但电压偏差变大 | 权重设置不当 | 适当增大w2,或改成网损和电压偏差的配合约束 |
| 算法运行极慢 | 潮流函数重复扫描节点 | 预处理支路层级关系,用热启动电压初值 |
| 无法从IEEE33切换到IEEE118 | 候选节点集合硬编码 | 将候选节点改为从数据文件动态读取 |
| License报错-8 | hostid变更或系统时间异常 | 核对hostid,同步系统时间 |
5. 扩展方向与个人体会
这个项目跑通之后可以做很多方向的扩展。目前版本采用的还是单时段静态优化,没有考虑DG出力随光照、风速变化的时序特性。要升级的话,把目标函数里的网损和电压偏差改成年时序期望值,加入典型日24小时的时序潮流,问题就从"静态选址定容"升级为"考虑时序特性的优化配置",难度和论文价值都上一个台阶。
另一个值得试的方向是多目标形式的改版。把当前位置定容的加权单目标改为Pareto最优前沿求解,用NSGA-II或MOPSO替代AGA,得到网损和电压偏差的非支配解集,在做电网规划时可以给决策者更丰富的选择空间。我之前试过在这个框架上接入NSGA-II,核心改动只在选择算子和存档策略,潮流计算和编码完全复用,改起来非常顺手。
还可以把约束处理从罚函数法换成可行性规则法或ε-约束法。罚函数求出的解是"近似可行",对于电网运行场景,因为约束违反是硬性的,可行性规则在一些严格工程场景下更稳妥。工程应用维度上,可以考虑把负荷模型换成更贴近实际的ZIP模型(恒定阻抗、恒定电流、恒定功率的混合),以及加入分布式储能、电动汽车充电桩等新型负荷的建模——这些都是当前配电网规划研究的热点。
在我个人实际操作中的体会是:复现一个项目,最大的难点永远不是公式推导,而是细节工程的把控——数据单位是否统一、编号映射是否一致、防零保护有没有加、交叉冲突修不修复,这些代码层面的小事决定了算法性能的上下限。自适应遗传算法本身的数学形式并不复杂,但真正把它用得得心应手,还需要理解每种算子背后的统计意义和搜索意图。希望这份梳理能帮你少走弯路,把精力留在更有价值的算法改造和应用场景挖掘上。