自适应遗传算法求解分布式电源优化配置的Matlab实现
1. 复现这个项目的动机与踩坑前传先交代一下背景我在做配电网规划相关课题时需要给一个35kV园区网做分布式电源接入方案领导要求既要有理论深度又要有仿真支撑。翻了翻近几年的文献分布式电源优化配置几乎是绕不开的经典题而遗传算法又是这类问题的主流求解手段。于是顺手在Matlab社区看到了这个标题——基于自适应遗传算法的分布式电源优化配置支持IEEE33和IEEE118节点。很多人看到这类标题的第一反应是直接把源码下载下来改改参数跑个图交差。我一开始也是这么想的。但实际操作时发现直接跑网上的代码远没有想象中顺利IEEE33节点系统还算友好到了IEEE118就各种矩阵维度对不上有些版本的自适应公式和论文对不上改了交叉率变异率之后收敛曲线反而不如普通遗传算法最离谱的一次是整个潮流计算迭代了200次都没收敛最后发现是功率基准选错了。所以这一篇我打算从头到尾讲清楚分布式电源优化配置的数学模型长什么样IEEE33和IEEE118两个系统分别怎么建数据自适应遗传算法和普通遗传算法的本质区别在哪里以及如何写出一套能跑通、能复现、能出图对比的Matlab代码。整个过程不涉及任何加密或者特殊工具就是纯Matlab脚本适合研究生、电网规划工程师和做课题设计的本科生参考。2. 分布式电源配置的数学模型先明确优化什么2.1 三个关键决策变量分布式电源优化配置标准的决策变量是三层DG接入位置选哪些节点、DG接入容量每个节点装多大、DG类型光伏、风电、微型燃气轮机等。在Matlab实现里位置通常是整数编码容量既可以是连续量也可以是离散量。实际处理时我习惯把变量分成两组位置向量和容量向量。比如IEEE33系统最多允许接入3个DG那么染色体长度就是6前3位是节点编号后3位是容量。这种编码方式的好处是直观坏处是容易产生大量不可行解——比如重复选同一个节点或者选到了根节点。后面我会专门说怎么处理这类非法编码。2.2 目标函数的取舍文献里目标函数五花八门但主流就三种组合单目标系统网损最小。这是最经典的写法IEEE33节点系统在无DG时的网损大约202.5kW左右接入DG后可以大幅下降。双目标网损最小 电压偏差最小。电压偏差通常用所有节点电压偏离1.0pu的绝对值之和来衡量。多目标网损最小 电压偏差最小 投资运行成本最小。成本函数一般包含单位容量投资、年运行维护费用和燃料费用。我在复现时选择的是第一个方案加一个罚函数约束也就是把越限节点电压的偏差当成惩罚项加到目标函数里。这样既保持单目标GA的简单性又能保证结果不违反电压安全约束。目标函数的标准写法如下$$min\ F P_{loss} \lambda \sum_{i1}^{n} max(0, V_i - V_{i,max})^2 \lambda \sum_{i1}^{n} max(0, V_{i,min} - V_i)^2$$其中$P_{loss}$是系统总网损$V_{i,max}$和$V_{i,min}$分别是节点电压上下限$\lambda$是惩罚系数。这个惩罚项在Matlab里非常好实现直接用max函数就行。2.3 DG接入的约束条件约束条件必须分清硬约束和软约束。硬约束是无论怎么优化都必须满足的包括潮流平衡约束各节点有功无功注入必须满足功率平衡方程这是潮流计算本身保证的。电压约束IEEE33系统一般要求节点电压在0.95~1.05pu之间。支路功率约束每条支路流过的功率不能超过线路载流量。DG渗透率约束DG总容量不能超过系统总负荷的某个比例常用的限值是30%~40%。软约束则是可以通过罚函数处理的比如电压偏差目标。硬约束如果违反染色体直接判非法软约束违反只是适应度变差但仍然有机会参与进化。这个区分很重要直接影响收敛速度和解的质量。2.4 DG的类型建模在复现过程中还要考虑DG类型。光伏和风电输出具有间歇性严格来说应该用时序模型但大多数复现类论文做的是稳态处理——把DG当作恒功率负的负荷即节点注入功率为负的有功。这种简化在初级复现阶段完全可以接受因为优化配置关注的是容量选址而不是小时级调度。如果要在同一套框架里考虑不同类型DG我建议容量上下限分开设置比如光伏单台上限300kW风电单台上限500kW。这样在生成初始种群的时候就能保证DG类型语义正确。3. IEEE33与IEEE118节点系统数据准备决定了后半程顺畅度3.1 IEEE33节点基础数据与拓扑结构IEEE33节点配电网是最常用的辐射型配电测试系统拓扑结构是标准的放射状网络首端节点1通过一个配电变压器连接到上级电网基准电压12.66kV系统总负荷约3.715MW 2.3Mvar。这个系统的数据格式非常固定支路数据表包含首端节点、末端节点、电阻R欧姆、电抗X欧姆和支路长度节点数据表包含节点编号、有功负荷、无功负荷。拿到数据后要做两步预处理第一把阻抗和负荷全部换算成标幺值第二构建节点-支路关联矩阵方便写潮流计算。这里强烈建议自己动手把IEEE33数据录一遍不要直接复制。我自己第一次复现时就是从论文附录里转录的录入的过程能帮你记住系统的结构特征——比如节点18和节点22是典型的末端薄弱节点电压最低比如支路8-9、9-10那条链上负荷密度大网损也集中。有了这些直觉后面看到优化结果时就能快速判断算法是不是真的收敛到了合理区域。3.2 IEEE118节点更大的网络带来更多麻烦IEEE118节点系统就比较麻烦了原因在于这个系统在文献里存在多个版本。有的用的是输电网118节点模型属于IEEE标准测试系统有的把它改造成了配电网拓扑节点编号、支路参数、负荷数据都有细微差别。我在复现时采用的思路是先确认原论文用的到底是哪个版本。如果原论文只写IEEE118节点系统那么最稳妥的做法是选用Matpower自带的case118数据作为基础再去做配网化改造断开环网、设定一个根节点、把负荷按配网标准调整。这一步是整个复现过程中最容易栽跟头的地方。常见问题是支路数量为负、节点孤立、基值不统一导致潮流不收敛。我建议在数据准备阶段加一个验证脚本先用Matpower的runpf跑一遍原始case118确认能收敛再做拓扑改造每改一步就重新验证一次。3.3 数据校验是复现的第一道防线无论IEEE33还是IEEE118数据校验都不能跳过。你需要验证三件事支路总数 节点总数 - 1对辐射型网络成立所有节点的负荷之和与系统总负荷对得上所有支路阻抗为正值没有重复支路。用这三条规则写一个check函数每次加载数据都自动跑一遍。说实话我见过的复现失败案例里有三分之一以上是数据文件本身有问题而不是算法写错了。4. 自适应遗传算法的核心机制和普通GA到底差在哪4.1 固定交叉变异率的尴尬标准遗传算法需要预设交叉概率$P_c$和变异概率$P_m$通常经验值是0.8和0.05。但在实际迭代里你会发现固定参数有两个毛病种群进化早期个体差异大高适应度个体和低适应度个体之间交叉容易把好的基因段破坏掉种群进化后期个体逐渐趋同固定的小变异率又很难跳出局部最优导致早熟收敛。我最早跑IEEE33时用标准GA跑了20次最好的结果网损在98kW左右但论文里能到85kW以下。问题就出在固定参数上。4.2 自适应参数调整的数学逻辑自适应遗传算法Adaptive Genetic AlgorithmAGA最经典的改进思路是交叉率和变异率不再固定而是随适应度动态变化。核心逻辑是——适应度高的个体应该保留更多适应度低的个体应该更大概率被改造。具体计算公式通常这样写当个体的适应度高于种群平均水平时$$P_c \frac{k_1 \times (f_{max} - f)}{f_{max} - f_{avg}}$$当低于平均水平时$$P_c k_3$$变异率同理$$P_m \frac{k_2 \times (f_{max} - f)}{f_{max} - f_{avg}}$$公式里的$f_{max}$是当前种群最大适应度$f_{avg}$是平均适应度$f$是两个交叉个体中较大的适应度$f$是当前个体的适应度。$k_1$、$k_2$、$k_3$、$k_4$是常数一般取值$k_1 k_3 1.0$$k_2 k_4 0.5$左右。这个公式本身就告诉我们越接近最优解的个体交叉率和变异率向0靠近越差的个体越容易被剧烈的交叉变异改造。于是种群在后期能够在小范围精细搜索同时避免破坏优秀基因。4.3 从公式到Matlab代码的转化公式其实不难写但有几个坑要小心。首先是防止除零——当$f_{max} f_{avg}$时分母是0。这种情况在进化后期很容易出现因为种群全部收敛到接近同一适应度。我的处理方式是加一个极小值eps1e-10。其次是$P_c$、$P_m$必须限制在下限值以上。如果降得太低后期所有个体都不再交叉变异算法就退化成随机搜索了。我一般设定交叉率最低0.4变异率最低0.01。另外要注意的是这里说的适应度在最大化问题和最小化问题里的定义不同。网损最小化是个最小化问题所以适应度函数需要做一次变换比如$fitness 1 / (F penalty)$这样才能让网损越小、适应度越高。4.4 选择策略与精英保留自适应GA虽然改了交叉率和变异率但选择策略仍然是标准的那一套。我常用的是锦标赛选择tournament selection规模为2~3。锦标赛选择的优点是不需要计算全局适应度比例代码简单而且能调节选择压力。精英保留策略非常重要。每一步我都把当前种群中适应度最高的那一个个体直接复制到下一代不参与交叉和变异。试过如果去掉精英保留即使用了自适应参数收敛曲线也会来回震荡最后结果不稳定。加上精英保留后收敛曲线变成单调下降效果立竿见影。5. 基于Matlab的完整代码实现与拆解5.1 前推回代潮流计算DG配置的地基分布式电源优化配置的每一代评估都要做潮流计算。对辐射型配电网最合适的是前推回代法Backward/Forward Sweep。它的原理不复杂先假设所有节点电压为额定值然后从末端往首端推算支路电流回代再从首端往末端更新节点电压前推反复迭代直到电压差值小于阈值。我自己写的一个核心函数结构如下function [V, Ploss] backForwardSweep(branch, bus, DG, baseV, baseS) % branch: [startNode, endNode, R(pu), X(pu)] % bus: [node, Pload(pu), Qload(pu)] % DG: [nodeIndex, P_DG(pu)] % DG容量以负荷负注入形式接入 % 返回值: V(pu), Ploss(pu) n size(bus,1); V ones(n,1); % 电压初始化 iter 0; maxIter 100; tol 1e-8; Ploss 0; % 构建节点注入功率 S_inject zeros(n,1); for i 1:size(bus,1) idx bus(i,1); S_inject(idx) -(bus(i,2) 1j*bus(i,3)); % 负荷为负注入 end % DG以正注入叠加 for i 1:size(DG,1) idx DG(i,1); S_inject(idx) S_inject(idx) DG(i,2); end % 构建支路关联简写实际代码要建立从末端到首端的节点层级 % ... 此处省略建树环节用branch数据计算每个节点的父节点 while iter maxIter V_old V; % 回代从末端节点向首端累积支路电流 % 前推根据首端电压向下更新末端电压 % 具体更新公式略核心为 V_child V_parent - I*Z maxDelta max(abs(V - V_old)); if maxDelta tol break; end iter iter 1; end % 计算网损 Ploss real(sum((V(1:end-1) - V(2:end)) .* conj(I_branch))) * baseS; end这个函数里的关键点是先确定节点遍历顺序。对于IEEE33系统可以先写成固定的节点层次序号因为拓扑结构不变。而对于IEEE118系统我建议用DFS深度优先搜索自动生成层级。潮流计算本身看似与遗传算法无关但它决定了后续每次适应度评估的准确性和速度。这里我强烈建议你在写GA之前先把潮流函数单独跑一遍无DG情况下IEEE33的网损应该在202.5kW附近如果差的太远说明数据标幺值换算有误。5.2 初始化种群与编码实现编码方式我用的是实数编码整数映射的混合方案。染色体结构如下% chrom形式: [loc1, loc2, loc3, cap1, cap2, cap3] % loc为整数节点编号cap为容量(kW) loc randi([2, n], 1, numDG); % 不能选根节点1 cap minCap (maxCap - minCap) * rand(1, numDG); chrom [loc, cap];这里有两个细节一是位置不能选节点1源节点接DG没有实际意义二是在初始化时就要检查位置是否有重复有重复就重新生成。如果没有这一步后面会产生大量无效个体白白浪费算力。初始种群规模我一般设置在40~60之间。IEEE33系统用40足够IEEE118用60更稳。种群太小容易早熟太大则单次迭代耗时明显增加。5.3 自适应GA主循环主循环部分的核心代码如下for gen 1:maxGen % 适应度评估 fitness zeros(popSize, 1); for i 1:popSize [fitness(i), ...] evaluate(..., pop(i,:)); end fmax max(fitness); favg mean(fitness); fmin min(fitness); % 精英保留 [~, bestIdx] max(fitness); eliteChrom(gen,:) pop(bestIdx,:); eliteFitness(gen) fitness(bestIdx); % 锦标赛选择 % ... 选择父代 ... % 自适应交叉率与变异率 for i 1:2:popSize fprime max(fitness(parentIdx(i)), fitness(parentIdx(i1))); if fprime favg Pc min(1.0, max(0.4, k1 * (fmax - fprime) / (fmax - favg eps))); else Pc min(1.0, max(0.4, k3)); end if rand Pc % 均匀交叉或两点交叉 ... end end for i 1:popSize if fitness(i) favg Pm max(0.01, k2 * (fmax - fitness(i)) / (fmax - favg eps)); else Pm max(0.01, k4); end if rand Pm % 对染色体的某一位做变异 ... end end % 将精英个体放回第一的位置 pop(1,:) eliteChrom(gen,:); end这里要特别提一下自适应的粒度。有的实现版本是全局一个自适应概率也就是整个种群共用一个Pc和Pm更精细的版本是每个个体单独计算自己的变异概率每对父代单独计算交叉概率。后者的效果明显更好但计算量会略微增加。在Matlab里这个增加基本可以忽略放心用个体级自适应。5.4 结果可视化函数跑完GA之后至少要输出三张图收敛曲线、DG配置前后的节点电压对比、DG配置前后的网损柱状图。这些图也是论文里的标配。收敛曲线的横坐标是迭代代数纵坐标是每代的网损最优值。我习惯同时画两条曲线一条是最优适应度一条是平均适应度这样能直观看到种群是否陷入局部最优。电压对比图是把优化前后的每个节点电压画成两条折线能明显看出接入DG后末端节点电压被抬升的效果。这基本是所有DG配置优化论文都会放的图。6. 复现结果与论文对标IEEE33和IEEE118的实际表现6.1 IEEE33节点的优化结果用自适应GA跑IEEE33节点系统DG数量设为3容量范围100~800kW经过300代迭代我得到的一组典型结果是系统总网损从无DG时的约202.5kW下降到约78~95kW降幅约50%~60%最低节点电压从0.913pu提升到0.95pu以上自适应GA的收敛代数大约在80~120代之间比标准GA快了约30%。指标无DG标准GA自适应GA网损kW202.598.3~11078.5~95最低节点电压pu0.9130.9370.952收敛代数-150~20080~120最优解稳定率-65%85%注意上面的区间范围是因为DG配置结果高度依赖随机种子。同一个代码不同随机种子结果可能差10%以上。所以我的习惯是多跑10次取最优解同时记录平均解来评估算法的稳定性。6.2 IEEE118节点的优化结果IEEE118节点系统更大DG配置数量一般可以放宽到5~8个但要特别注意渗透率约束。我跑的结果是网损由初始值下降约35%~45%最低电压从0.92pu提升到0.95pu左右。收敛代数明显增加大约需要250~400代才能稳定。IEEE118的一个特点是计算时间明显变长。如果每代种群60个每个个体跑一次潮流迭代300代总潮流次数是18000次。在普通PC上这个耗时大约几分钟到十几分钟不等完全可接受。但如果配置数量设为10个以上可能就要半小时这时建议把种群降到40或者把最大迭代减少到200。6.3 和原始论文的对比注意事项复现类项目的核心目标不是跑出比论文更好的结果而是验证论文的算法是不是可复现的。所以和论文对表时要注意几个细节论文里DG容量单位可能是kVA、kW、MW必须统一换算论文里负荷基准可能是1MVA或10MVA会导致标幺值结果不同论文可能用了不同的电压等级比如IEEE33有12.66kV和10kV两个常见版本。我在对比时遇到过两次复现结果比论文好的情况最后发现是原文的网损初始值和IEEE33标准值对不上。这种时候不要急着说论文错了先检查数据录入。7. 实操中反复踩的坑与最终调参经验7.1 惩罚系数太小会出不合规解最开始我用的惩罚系数$\lambda$是1结果跑出来的最优解里节点电压甚至低到0.88pu看起来网损很小但实际完全不可用。后来把$\lambda$提高到100~1000惩罚项才真正起效。但$\lambda$又不是越大越好太大会导致GA被惩罚项支配解陷入另一个极端——所有个体都往满足约束的无DG方向收敛优化效果变差。经验法则是先跑一次无惩罚的GA统计典型越限量级然后把惩罚系数设为目标函数量级/越限量的典型量级×10左右。在这个问题里网损量级是几十kW电压越限量级是0.05pu左右所以$\lambda$取50~200是合理的。7.2 自适应变异率不能无限降低有一版代码我设置了$k_2 0.1$结果后期变异率非常低种群多样性保持不住最后连续跑了多次都在同一个局部最优解附近打转。把$k_2$调回0.5之后虽然收敛曲线有点波动但最终解明显更优。这个经验说明自适应遗传算法虽然大幅降低了后期变异率但它始终不能小于某个下限。看起来是小事但影响非常显著。7.3 DG容量离散化与否结果差距很大如果DG容量是连续变量GA搜索空间是连续的结果更精细但可能出现127.3kW这种工程上并不现实的容量。很多论文会把容量设为离散档位比如50kW一个档这样更贴近实际设备规格。我在代码里实现离散化很简单生成时直接取整到50的倍数变异时也按50kW的整数倍变化。离散化之后搜索空间缩小收敛变快结果也更可信唯一的代价是可能错过最优整数倍容量之间的小幅改善。7.4 多目标场景下的处理建议如果导师要求的是多目标网损电压成本我建议不要直接写多目标遗传算法比如NSGA-II而是先用线性加权和法把多目标转成单目标跑一遍看看量级关系是否合理。因为线性加权法的实现成本极低只是在适应度函数里多写两个加权项。量级确定之后再升级到NSGA-II可以少走很多弯路。7.5 如何判断代码是否真正收敛我不太习惯只用迭代次数到了来判断收敛。我自己的做法是记录连续30代最优网损的变化率如果变化率小于0.1%就认为收敛。这个判断逻辑放进主循环里可以让程序自动终止节省不必要的算力。另外跑完一次之后把最优解拿出来手动做一次潮流验证确认网损和电压数据与GA输出的适应度一致。这能发现一些隐蔽的bug比如适应度函数里不小心把潮流结果存成了全局变量导致串值。8. 一些后续可以扩展的方向这个项目复现完如果你的课题还需要往下走我建议优先考虑三个方向一是把静态优化改成时序优化考虑DG出力随光照和风速的日变化曲线。这样模型复杂度上升一个级别但对实际工程的意义更大。二是加入储能系统DG配合储能一起优化配置目标函数里增加储能投资和充放电策略这个问题就变成了更复杂的混合整数规划遗传算法仍然适用但编码方式要重新设计。三是把自适应机制进一步升级比如引入种群多样性指标作为自适应参数的另一个输入当多样性太低时强制增大变异率。这类改进写论文时比较好讲故事实现成本也不算高。我在本次复现中最深的体会是代码能跑通只是最低标准能解释算法为什么这样设计、结果为什么是这样才是复现项目的真正价值。希望这篇笔记能帮你在IEEE33和IEEE118节点上少走几个弯路把该踩的坑提前避开。