遗传算法与粒子群算法求解电力系统潮流:Matlab实现对比
做电力系统研究的朋友应该都遇到过这个尴尬场景导师让你把潮流计算跑通教科书上写的是牛顿-拉夫逊法三分钟收敛你兴冲冲写完代码发现初值给得不好直接发散。这时你听说还有遗传算法和粒子群算法也能算潮流于是想把这两种智能算法拿来对比一下写个Matlab实现。这篇博客就是我当时做这个课题的完整记录从建模思路到代码细节从参数调试到对比结果把GA和PSO在潮流计算上的表现差异讲透。先把结论放在前面免得你看到后面着急遗传算法全局搜索能力强但收敛慢粒子群算法收敛快但容易早熟两者都能把潮流方程算到满足精度但适用场景差别很大。这篇文章适合电力系统专业的学生、刚接触智能优化算法的入门者以及想在Matlab里快速搭一套对比实验框架的工程师。1. 为什么要把潮流计算改造成优化问题问题建模与目标函数设计潮流计算的本质是求解一组节点功率平衡方程。在一个有n个节点的电力系统中每个节点i都满足两个方程P_i V_i * Σ(V_j * (G_ij * cos(θ_ij) B_ij * sin(θ_ij))) Q_i V_i * Σ(V_j * (G_ij * sin(θ_ij) - B_ij * cos(θ_ij)))其中V_i是节点电压幅值θ_ij是节点i和j的相角差G_ij和B_ij是节点导纳矩阵的实部和虚部。传统牛顿法通过迭代求解这个非线性方程组核心依赖一个良好的初值和每次迭代的雅可比矩阵。但智能优化算法不走这条路。它把解方程这个问题转换成最小化误差的问题。具体做法是把每个节点的电压幅值和相角看作决策变量构造一个目标函数当这个目标函数的值趋近于零时就认为找到了潮流解。我当时设计的目标函数是这样的def fitness(x): # 解码从决策向量中恢复所有节点的V和theta # 代入潮流方程计算每个节点的注入功率 # 与给定负荷和发电功率做差求平方和 return sum((delta_P_i)^2 (delta_Q_i)^2)这个函数的值越小说明当前这组电压和相角越接近真实解。当不平衡量小到10^-6量级时就可以认为潮流收敛了。1.1 决策向量的设计与松弛节点处理这里有个关键细节第一次做这个实验的人几乎都会踩坑决策向量不是所有节点的电压和相角都包含的。电力系统里有三类节点PV节点发电机节点给定有功和电压幅值、PQ节点负荷节点给定有功和无功、平衡节点参考节点电压幅值和相角固定。潮流计算中平衡节点的电压幅值通常取1.0相角取0不需要参与优化。所以决策向量只包含x [V_PQ(1), V_PQ(2), ..., V_PQ(nPQ), theta_PV(1), theta_PV(2), ..., theta_PV(nPQnPV)]也就是说所有PQ节点的电压幅值加上所有非平衡节点的相角。这个维度决定了搜索空间的规模。典型IEEE 14节点系统决策向量维度大概是24左右。别小看这24维搜索空间已经是天文数字了这也是为什么智能算法算潮流比牛顿法慢好几个数量级的原因。1.2 约束条件如何处理潮流计算天然带约束发电机无功出力有上下限、节点电压幅值有允许范围。传统牛顿法处理这些约束很麻烦但优化算法反而有天然优势。我在代码里用了两种约束处理方式边界约束决策变量本身的范围限制。电压幅值一般在[0.94, 1.06]之间相角在[-π, π]之间。这个直接在生成初始种群和更新粒子位置时做clip就行。隐含约束比如PV节点的无功越限。这个我放在目标函数里做惩罚如果某个PV节点的无功超出上限就加一个大的惩罚项逼着算法不要让发电机过度出力。这个建模思路是整个实验的地基。地基打好了后面GA和PSO的差异才能公平地体现出来。2. GA和PSO的搜索逻辑差异从机制上理解两者为何表现不同很多教程喜欢直接甩公式和代码不讲清楚两种算法到底在干什么。我觉得先理解机制再写代码调试时才知道往哪个方向使劲。2.1 遗传算法靠种群多样性和选择压力走天下GA模拟的是自然选择过程。在我的实现里每个个体就是一组[V_PQ, theta]的实数向量也就是一个候选解。一个种群就是40个这样的候选解。每一代的核心操作有三个选择我用锦标赛选择。每次随机抽3个个体挑适应度最好的进入下一代。这个操作的目的很明确——让好的解活下来但不是只让最好的活下来保持一定的随机性才能维持多样性。交叉对两个父代个体做算术交叉。子代 父代1 * a 父代2 * (1-a)a是[0,1]的随机数。这个操作相当于在父代之间的连线上采样新解。变异对某个维度加上一个高斯扰动。变异概率我通常设0.05到0.1。这一步是探索新区域的关键也是GA不容易陷入局部最优的根本原因。GA最大的特点是全局搜索能力强。因为变异操作保证了即使种群收敛到一个局部极值附近仍然有机会跳出去。但代价就是后期的收敛非常慢因为纯随机扰动在解空间里就像大海捞针。用个生活化的例子GA像是一群人在一个巨大的迷宫里分头找出口每个人都可能在原地打转但总有人会误打误撞找到新通道。缺点是整体速度慢。2.2 粒子群算法基于群体信息共享的鸟群觅食PSO的灵感来自于鸟群找食物的行为。每只鸟粒子知道自己当前的位置知道自己的历史最佳位置pbest还知道整个群体的历史最佳位置gbest。每次更新就靠这三条信息拽着自己飞。速度更新公式是我反复调过的v(t1) w * v(t) c1 * r1 * (pbest - x(t)) c2 * r2 * (gbest - x(t)) x(t1) x(t) v(t1)w是惯性权重控制粒子保持原来飞行趋势的程度c1和c2是学习因子分别控制向自身经验和群体经验靠拢的程度r1和r2是[0,1]的随机数。我对比GA和PSO时有一个非常直观的感受PSO的信息共享更直接。GA中一个优秀个体的基因传递到整个种群需要好多代而PSO中只要gbest一更新所有粒子立刻收到信息并向它靠拢。这就是PSO收敛速度快的原因。但这也是PSO的致命弱点如果gbest陷入局部最优所有粒子会被迅速吸引过来然后整个群体挤在一个局部极值附近动弹不得。除非初始化的时候某个粒子恰好落在另一个较好的区域否则很难逃脱。用同样的比喻PSO像是一群鸟在空中飞只要有一只鸟发现食物所有鸟都会立刻转向飞过去。但如果最开始那只鸟看到的是假食物整群鸟都会被带偏。2.3 两者核心差异对照表对比维度遗传算法粒子群算法搜索机制选择交叉变异速度更新pbest/gbest引导信息共享方式间接通过交叉和选择传播直接gbest全局广播收敛速度较慢后期尤其慢较快尤其前期全局搜索能力强变异提供跳出能力较弱容易早熟参数数量较多种群规模、交叉概率、变异概率、锦标赛规模较少w、c1、c2、种群规模实现复杂度较复杂算子选择多简单核心公式三行对初值敏感度较低随机初始化即可较低但初始分布影响早熟概率这张表是整个实验的核心框架。参数不是越多越好GA参数多意味着调参空间大但也意味着不确定性高PSO参数少实现方便但要解决早熟问题需要额外的机制。3. Matlab代码实现的关键细节从节点导纳矩阵到适应度函数理论说完了直接上干货。我用Matlab写了一套完整的对比实验代码这里把最关键的部分拆开讲。完整代码量比较大我挑重点展示框架和核心函数。3.1 输入数据与节点导纳矩阵的构建不管用什么算法算潮流第一步永远是构建节点导纳矩阵Y。我用的是IEEE 14节点系统的标准数据线路参数存在一个矩阵里每一行表示一条支路。function Y makeYbus(branch, nbus) % branch: [from_bus, to_bus, R, X, B] % nbus: 节点数 Y zeros(nbus, nbus); for k 1:size(branch, 1) from branch(k, 1); to branch(k, 2); z branch(k, 3) 1j * branch(k, 4); % 阻抗 y 1 / z; Y(from, from) Y(from, from) y 1j * branch(k, 5)/2; Y(to, to) Y(to, to) y 1j * branch(k, 5)/2; Y(from, to) Y(from, to) - y; Y(to, from) Y(to, from) - y; end end这个函数看起来简单但有几个细节需要注意。第一支路导纳要加上线路对地电容的一半很多教材只算阻抗忽视对地导纳导致结果和商业软件对不上。第二Y矩阵是对称的但非对角元素是负的导纳值这个符号搞错了整个潮流就全错了。3.2 适应度函数的矢量化写法适应度函数是整个算法的核心它的计算效率直接决定整个实验能不能跑完。我第一次写的时候用了三重for循环14节点系统跑100代要几分钟。后来改成矢量化写法时间直接降了一个数量级。function f fitness(x, bus, branch, nbus, typeIdx) % 解码决策向量 V decodeV(x, nbus); theta decodeTheta(x, nbus); % 计算注入功率矢量化 V_complex V .* exp(1j * theta); I Ybus * V_complex; S V_complex .* conj(I); % 节点注入复功率 P_calc real(S); Q_calc imag(S); % 与给定值求差 dP P_calc - P_specified; dQ Q_calc - Q_specified; % 只计算非平衡节点的偏差 f sum(dP(2:end).^2) sum(dQ(PQ_idx).^2); end矢量化之后一次适应度计算只需要矩阵乘法快得多。决策向量的解码用了一个技巧因为相角是以平衡节点为参考的所以theta(1)0固定其他节点依次填充。这个顺序必须和初始化解的生成顺序完全一致否则前后对不上算法会一直报奇怪的错误。3.3 遗传算法主循环我用的是自写GA因为Matlab自带的ga()函数在混合整数和非线性约束处理上不太灵活而且想要观察每一代的详细表现自写更方便。function [best_x, best_f, history] ga_powerflow(opt) % 初始化种群 pop initPopulation(opt.npop, opt.nvars, opt.bounds); for gen 1:opt.maxgen % 计算适应度 fit zeros(opt.npop, 1); for i 1:opt.npop fit(i) fitness(pop(i,:), ...); end % 锦标赛选择 newpop zeros(size(pop)); for i 1:opt.npop idx randsample(opt.npop, 3); [~, bestIdx] min(fit(idx)); newpop(i, :) pop(idx(bestIdx), :); end % 算术交叉 for i 1:2:opt.npop-1 if rand opt.pc a rand; temp newpop(i, :); newpop(i, :) a * temp (1-a) * newpop(i1, :); newpop(i1, :) (1-a) * temp a * newpop(i1, :); end end % 高斯变异 for i 1:opt.npop for j 1:opt.nvars if rand opt.pm newpop(i, j) newpop(i, j) opt.sigma * randn; newpop(i, j) clamp(newpop(i, j), opt.bounds(j, :)); end end end pop newpop; [~, bestIdx] min(fit); history(gen) fit(bestIdx); end end这段代码有个细节我想强调交叉和变异之后一定要把越界的变量拉回到边界内。如果不做这个clamp操作很容易出现电压幅值变成负数或者相角乱飞的情况适应度直接变成NaN整个种群就废了。这是我在实验中踩过最大的坑之一后面详细说。3.4 粒子群算法主循环PSO的代码比GA短很多我同样做了完整的注释。function [gbest_x, gbest_f, history] pso_powerflow(opt) % 初始化粒子位置和速度 x initPopulation(opt.npop, opt.nvars, opt.bounds); v zeros(opt.npop, opt.nvars); pbest x; pbest_f zeros(opt.npop, 1); for i 1:opt.npop pbest_f(i) fitness(x(i,:), ...); end [gbest_f, gbestIdx] min(pbest_f); gbest_x pbest(gbestIdx, :); % 主循环 for gen 1:opt.maxgen % 惯性权重线性递减w从0.9降到0.4 w 0.9 - (0.9 - 0.4) * (gen / opt.maxgen); for i 1:opt.npop r1 rand(size(x(i,:))); r2 rand(size(x(i,:))); v(i,:) w * v(i,:) opt.c1 * r1 .* (pbest(i,:) - x(i,:)) ... opt.c2 * r2 .* (gbest_x - x(i,:)); x(i,:) x(i,:) v(i,:); x(i,:) clamp(x(i,:), opt.bounds); % 更新个体最优 fi fitness(x(i,:), ...); if fi pbest_f(i) pbest_f(i) fi; pbest(i,:) x(i,:); % 更新全局最优 if fi gbest_f gbest_f fi; gbest_x x(i,:); end end end history(gen) gbest_f; end endPSO部分最需要调的就是惯性权重w。我采用线性递减策略前期w大粒子惯性大探索范围广后期w小粒子收敛到gbest附近精细搜索。这个策略是在对比了大量固定w和动态w的实验后选定的固定w0.5的情况下14节点系统很难收敛到指定精度递减策略几乎每次都能成功。c1和c2我分别取2.0和2.0这是文献里最常用的配置。不过实际测试时c11.5、c22.0的效果稍微好一点因为潮流计算的目标函数比较崎岖多一点全局探索的权重更有利。4. 同等条件下的公平对比参数配置、实验设计与结果分析做对比实验最怕的不是算法不好而是比较不公平。我遇到很多同学直接拿一套默认参数跑GA另一套默认参数跑PSO然后得出结论说谁好谁坏。这是不对的。要公平必须让两种算法在最接近的资源配置下工作。4.1 实验设置我在IEEE 14节点系统上跑了完整的对比实验参数配置如下表参数项GA配置PSO配置种群规模4040最大迭代次数200200编码方式实数编码连续位置交叉概率0.8—变异概率0.08—惯性权重—0.9线性递减至0.4学习因子—c1c22.0边界处理直接截断直接截断收敛精度适应度10^-5适应度10^-5关键的控制变量是种群规模和最大迭代次数这两个决定了一次实验的计算量上限。为了保证对比公平我还记录了实际运行时间因为GA的单代计算量略大有选择交叉变异三个操作所以相同代数下GA的耗时通常比PSO多10%到20%。4.2 典型收敛过程对比我选一次具有代表性的实验把收敛历史画出来观察到的现象很典型PSO阶段约前50代收敛速度极快。因为粒子通过gbest信息共享整个群体迅速向最优区域收缩。在第20代左右适应度就已经降到10^-2量级很多人会以为快收敛了其实这只是表面现象。GA阶段前50代爬行速度非常慢。初始适应度可能高达几百前50代才降到10左右。但GA有一个特点一旦找到正确的基因组合后面的收敛反而会加速因为交叉操作会把好的片段组合在一起。转折点约80代后PSO的收敛曲线明显放缓甚至从此完全停滞。如果它之前找到的位置已经满足精度那就成功了如果还差一点就很容易陷入僵局。GA则在后半段持续下降虽然慢但稳步推进。我统计了20次独立实验的结果PSO第一次达到收敛精度适应度10^-5的平均代数在40到70代之间而GA平均需要80到120代。但PSO有约25%的概率在200代内完全无法收敛到指定精度早熟这个数字在GA上只有不到5%。这种对比说明了什么问题PSO适合粗粒度的快速搜索GA适合需要精细搜索和高可靠性的场合。如果你只是想要一个不差太多的近似解PSO更好用如果你必须达到严格精度GA更稳妥。4.3 参数敏感性实验哪些参数一碰就崩为了搞清楚参数对算法的影响我做了单因素扫描实验。GA方面我固定其他参数改变变异概率从0.01到0.2。实验发现变异概率低于0.03时算法几乎必然早熟因为种群多样性丧失后没有足够的新基因注入变异概率高于0.15时收敛速度明显退化因为优秀个体被随机扰动破坏的概率太大了。0.05到0.1是一个合适的区间我的推荐值是0.08。另外交叉概率的影响没有变异概率那么敏感。0.6到0.9区间内结果差别不大低于0.5时收敛速度略有下降。锦标赛规模我常用3增大到5时收敛略快但更容易陷入局部最优因为选择压力太大了。PSO方面最敏感的是w。固定w0.5时20次实验有6次不收敛w从0.9递减到0.4时只有1次不收敛。c1和c2的影响相对弱一些但如果c2显著大于c1比如c22.5c11.5会导致所有粒子过早涌向gbest反而不利于找到更好的解。还有一个容易被忽略的参数速度上限vmax。如果不限制速度粒子容易飞出边界然后在边界处震荡浪费大量迭代次数。我通常把vmax设为每维变量范围宽度的10%到20%。4.4 解的质量校验算出来的结果对吗智能算法的结果不能直接信必须和标准方法交叉验证。我把GA和PSO的最优解分别算出来的节点电压与牛顿-拉夫逊法的结果做了对比。以IEEE 14节点系统为例两种算法最优解下的节点电压幅值与牛拉法的最大偏差大约在10^-5到10^-6量级相角偏差在10^-4量级。这说明只要收敛到足够小的适应度值得到的结果和经典方法一致。不过我遇到一个有意思的现象有时候适应度已经很小了比如10^-4但平衡节点的功率算出来和给定的基准值差挺多。这是因为目标函数的权重偏向让不平衡量平方和最小但各个节点权重完全相同导致某些节点的功率偏差被平均化了。如果实际使用中特别关注平衡节点功率的准确性可以在目标函数里给平衡节点的不平衡量加一个更大的权重。5. 收敛失败、早熟与精度陷阱我在对比实验中踩过的坑这个章节是我最想写的因为网上几乎没有人系统整理过智能算法算潮流时的常见坑。我本人经历了至少十几次代码看起来没问题结果一塌糊涂的debug过程把最有代表性的几个问题列出来。5.1 决策变量编码顺序错位导致的目标函数爆炸这个坑我印象太深了。我的决策变量是[所有PQ节点的V, 所有非平衡节点的theta]但在写解码函数的时候我把theta的维度算错了导致从第nPQ1个位置开始取值的时候取到的不是theta而是下一个PQ节点的V。结果就是目标函数在前几十代完全不下降一直维持在一个很高的水平。排查方法很简单但不容易想到单独测试解码函数。你把一个已知的、正确潮流解代入适应度函数如果解码正确适应度应该接近零如果不正确适应度会是一个很大的数。我当时就是因为省略了这个步骤直接跑整个算法导致完全找不到问题所在。5.2 相角初始范围对收敛的影响电压幅值的初始范围好定义[0.94, 1.06]就完事了。但相角的初始范围让我纠结了很久。理论上相角范围是[-π, π]但实际电力系统中正常工况下节点相角差通常不会超过30度约0.52弧度。如果初始相角在[-π, π]内完全随机生成会有大量个体落在物理上极不合理的区域目标函数值巨大算法需要很长时间才能把这些个体拽回来。我的做法是把相角的初始范围压缩到[-0.6, 0.6]弧度这样初始种群质量大幅提高收敛速度有明显改善。约束条件我仍然保留[-π, π]只是初始生成时缩小范围。5.3 PSO早熟的识别与应对策略PSO早熟是它的通病。怎么判断早熟而不是正常收敛我总结了一个实用方法看gbest连续多少代没有变化。如果连续30代gbest的适应度值完全不变基本可以认定早熟了。应对策略我用过三种效果递增重启策略检测到早熟后随机重新初始化30%的粒子保留gbest。简单但有效偶尔能续命成功。速度重置检测到早熟后把所有粒子的速度置零然后加一个随机扰动。这个办法比完全重启温和不容易破坏已有的搜索成果。混合GA的策略每迭代一定代数选取一部分粒子做一次交叉和变异操作把GA的算子借过来。这是我最后采用的方案把PSO的快速收敛和GA的变异探索结合起来收敛成功率高了很多。这个混合策略让我重新认识到一件事算法之间不是竞争关系是互补关系。PSO负责快速找到较优区域GA的变异负责精细区探索中保持多样性的出口。实际工程中完全不必拘泥于纯GA或纯PSO。5.4 适应度函数中数值尺度带来的隐藏问题这是一个很隐蔽的精度陷阱。潮流方程中有功功率的量级可能是几十到几百MW无功功率是几十MVar而电压幅值的偏差是0.01量级。当我把dP和dQ的平方加总时有功部分的量级会把电压偏差的贡献完全淹没。这意味着什么呢算法在优化过程中会优先减小dP因为dP的平方是几十的平方减小一点目标函数就降很多而电压偏差带来的目标函数变化只在10^-4量级算法根本感觉不到。结果就是算出来的解有功功率对得很准但电压幅值可能偏差0.02以上且无法继续优化。我的处理办法是在目标函数里做归一化f sum((dP ./ P_base).^2) sum((dQ ./ Q_base).^2);P_base和Q_base取各节点的额定功率。这样各节点的偏差在相对意义上可比算法才能同时照顾到电压幅值和相角的精度。用这个方法之后所有节点的电压精度都同步提升到了10^-5量级。5.5 计算精度和算法的假收敛还有一个必须提醒的坑Matlab的数值计算默认double精度但适应度函数里如果出现数值特别大的中间量比如计算注入功率时V和Y矩阵相乘可能得到很大数值又减掉一个接近的量会产生严重的浮点误差。我的建议是用norm和稳健的数值方法。具体到潮流计算就是尽量减少不必要的数值运算步骤。我们计算注入功率时直接用复数矩阵乘法IY*V再算SV.*conj(I)这个流程的数值稳定性比逐元素展开公式好得多因为它减少了中间量放大的机会。6. 我的最终结论与实用建议这个实验做完我在一次报告里总结过GA和PSO都能解潮流方程但都只能作为教学和研究的参考实现在实际电力系统调度中牛顿法和PQ分解法仍然是绝对主力。智能算法真正有价值的场景其实是含大量不等式约束的最优潮流问题、配电网重构问题、以及分布式电源接入后的多目标优化问题——这些问题里智能算法的约束处理能力反而成了优势。如果你打算基于我这套代码继续扩展我建议从两个方向入手一是把对比系统换成IEEE 30节点或57节点看看算法在大规模问题上的表现差距是不是拉大了。以我的经验节点规模越大PSO的早熟率越高GA的优势越明显。二是加入约束处理机制的比较比如罚函数法、修复策略、可行性优先排序这些对潮流的约束满足情况影响很大做完之后你会对算法与问题特征匹配有更深的体会。最后分享一个调试小技巧任何启发式算法的代码先用一个极小型系统比如3节点或5节点把全套流程跑通。3节点系统你可以手算出精确潮流解然后拿算法的结果去对比能很快定位各种编码和解码的错误。等小型系统完全成熟了再上14节点乃至更大的系统一次成功率高得多。不要一上来就用大系统不然算法不收敛的时候你根本分不清是参数问题、编码问题还是算法本身的问题。
上一篇/下一篇内容由系统自动关联
返回资讯列表 →