
做配电网规划的朋友十有八九都会撞上分布式电源选址定容这道坎。我最近把一套基于多目标遗传算法的分布式电源选址定容代码从头到尾捋了一遍也补了不少工程细节。先说实话这类代码的难点不在遗传算法本身而在把网架结构、潮流约束、多个优化目标拧成同一个可计算的数学问题。只要建模建模得清楚算法拿来用就顺模型搭得模棱两可后面调参能调到你怀疑人生。这篇文章就把我的建模思路、NSGA-II实现细节、代码模块划分以及调试中踩过的坑一次性讲透适合正在写论文、做课程设计或者刚接触配电网规划项目的同学参考。1. 这个项目到底在解决什么问题1.1 分布式电源为什么不能想装哪就装哪分布式电源常见的就是屋顶光伏、小型风电、燃气轮机、储能这几种。它们接入配电网后最直接的影响有两个一是改变节点电压分布二是改变支路潮流方向和大小。听起来都是好事毕竟分布式电源能就近供电、减轻主网压力。问题在于配电网网架设计是按传统单向潮流来规划的DG接入的位置和容量一旦不合适就会出现末端电压越上限、线路过载、继电保护误动这些麻烦。我见过一个很典型的案例在某条10千伏馈线的末端强行接入一个容量偏大的光伏电站中午出力最大时线路末端电压直接顶到1.08倍额定值用户侧电压越限不说并网逆变器频繁报故障脱网。这就是典型的“位置不合理”。反过来如果只在变电站出口附近装DG虽然对电压没什么坏影响却根本发挥不了降低网损、提升线路供电能力的作用投资回报率很低。所以选址定容本质上是个“空间决策容量决策”的组合优化问题。通俗点说就像开连锁店店开在哪、店面开多大决定你能不能赚钱也决定会不会影响周围路况。电网里每一个备选节点都有可能装DG容量也从几十千瓦到几兆瓦不等组合数量是指数级的靠人工一个个试根本不现实。1.2 单目标优化不够多目标才有工程意义很多人第一反应是把全部要求揉成一个公式把电压偏离和网损的成本折算成钱再和目标投资成本加权求和做成单目标遗传算法。这样做不是不行但有个很大的隐患网损成本、电压惩罚系数、DG投资回报周期这些权重都靠拍脑袋定权重不同得到的方案完全不一样评审或导师问你“为什么取这个权重”解释起来非常被动。多目标遗传算法处理这个问题要优雅得多。它把“网损最小”“电压偏差最小”“综合成本最小”等目标并列保留最后得到一整套帕累托前沿解而不是唯一的答案。你可以看到网损和成本之间的博弈关系比如再投100万可以多降多少网损、电压质量能改善多少决策者完全可以结合工程预算来选择折中方案。这也是这类代码在论文和实际项目中特别受欢迎的根本原因。这套代码的核心逻辑就是用多目标遗传算法去搜索“在哪些节点装、各装多大容量”的方案集合在满足潮流约束和节点电压约束的前提下尽量压低网损、抬高电压质量、减少总投入。2. 数学模型拆解目标函数、约束和编码2.1 目标函数怎么定才不飘目标函数是整套代码的灵魂。我在这套代码里默认按最常见的三目标来做第一是网络损耗最小化。配电网线损通常用潮流计算结果里的支路有功损耗求和得到。数学上就是每条支路电流平方乘电阻再累加公式写出来大概是Ploss sum(I_ij^2 * R_ij)前提是潮流算出来以后我们手里已经有一组准确的支路电流。网损直接关系着经济运行水平也是投资方最关心的指标之一。第二是节点电压偏差最小化。电网人讲究电压质量一般用所有节点和额定电压的偏差绝对值累加也可以用平方和。公式类似Vdev sum( abs(V_i - V_n) )或者sum( (V_i - V_n)^2 )。这个目标一旦加上优化算法就会偏向在那些电压偏低、偏高的节点附近装DG起到支撑作用非常符合现场工程直觉。第三是综合成本最小化。包括DG单位容量的投资费用、运行维护费用有时候还叠加网损折价成本。我们在代码里可以简化为“安装容量越大投资成本越高”即Cost sum(C_install * S_DG_i C_om * E_i)。如果想更精细化还可以把不同DG类型光伏、风电、燃气轮机区分成不同单价。这三个目标之间天然存在矛盾。网损最小的方案可能要求把DG装在负荷最重的末端而末端节点往往电压偏高继续装DG就可能让电压越限成本最低的方案则是什么都不装但网损和电压目标明显变差。多目标算法存在的意义就是把这些矛盾摊开给你看而不是强行用权重掩盖掉。2.2 约束条件是怎么落到代码里的选址定容的约束条件大概有四类缺一类代码就会跑出离谱的结果。潮流等式约束必须满足也就是每个节点的有功、无功必须平衡。这个约束不需要我们手动去写表达式因为只要调用了潮流计算函数算出来的结果自然满足潮流方程。但要注意潮流必须收敛不收敛的方案要直接判成不可行解。节点电压约束是边界约束比如配电网要求电压在0.93到1.07倍额定电压之间。代码里常见的做法有两种硬约束直接丢弃或者软约束用罚函数。我倾向于用罚函数因为分布式电源接入后系统原本可能就有电压越限的趋势如果直接丢掉所有略微越限的方案搜索空间会变得支离破碎。DG渗透率和容量约束也很关键。很多接入规则要求分布式电源总容量不能超过配电网最大负荷的一定比例或者单个接入点的容量有上限比如不能超过该节点变压器容量的25%。这些约束如果不体现在代码里算法很可能给你一个“整个馈线全装满了”的疯狂方案看着目标很漂亮实际根本过不了并网审批。还有一种容易忽略的约束是DG接入数量的限制。现实中一条馈线不太可能在30多个节点都装DG运维成本太高。代码里通常加一个“最多允许接入N个DG”的限制这个参数直接决定了染色体里基因分布的稀疏程度。2.3 染色体编码选址和容量怎么表达编码方式决定了搜索效率也决定了交叉变异算子怎么写。我见过两种主流编码各有各的坑。第一种是二进制编码。把所有备选节点编号每个节点用一个二进制位表示装或不装再用另一段二进制位表示容量等级。这种编码简单直观但缺点非常明显节点很多时染色体会变得特别长而且“容量等级”和实际容量之间是离散映射优化精度受限。另一个麻烦是交叉和变异之后很容易产生非法的“节点重复安装”状态需要大量修复操作。第二种是整数实数混合编码也是我个人更推荐的做法。染色体分两段第一段是安装节点的编号序列比如[17, 25, 31, 4, 9]表示第1个DG装在节点17第2个装在节点25第二段是对应的容量序列比如[0.6, 0.8, 0.5, 0.3, 0.2]兆瓦。这种编码做选择、交叉都很自然只要保证节点编号互不重复解码就很干净。我在代码里默认使用第二种编码并且在交叉变异后写了一个“去重”函数如果某个节点被重复选择就随机替换成另一个没被选中的节点。实际跑下来这个修复操作比在目标函数里做大幅惩罚要稳定得多因为惩罚会导致很多重复方案被保留白白浪费计算量。3. 多目标遗传算法的设计与实现要点3.1 为什么选NSGA-II这种套路多目标遗传算法家族里有很多选择NSGA-II、NSGA-III、SPEA2、MOEA/D等等。对于中低压配电网选址定容这个规模NSGA-II是最成熟、最容易实现的选项。它不依赖目标个数特别多的场景两到三个目标时效果很好代码资料也多出了问题容易排查。NSGA-II的核心思想可以概括成一句话在每一代进化过程中先把父代和子代合并成一个大小为2N的种群然后对这2N个个体做“非支配排序”也就是把所有互不支配的解挑出来排成第一层再去掉它们再找第二层以此类推。每一层里的解再按“拥挤度距离”排序最后从排名靠前、拥挤度小的个体里选出N个组成下一代。这个机制的高明之处在于既保持了种群的多样性又不至于让某一代的优秀个体直接消失。精英保留策略保证了最好的解不会因为交叉变异被破坏掉非常适合工程优化这种对收敛性要求较高的场景。3.2 非支配排序和拥挤度的工程实现非支配排序的原理不复杂如果解A的所有目标都不差于解B并且至少有一个目标严格优于B就说A支配B反过来也一样互相都有优势就说这两个解互不支配。扫描式实现虽然简单但复杂度是O(MN^2)种群200个个体的三层目标问题倒也能忍如果要跑500个个体、上千次迭代性能就有点吃力了。我在代码里用了经典的“快速非支配排序”思路是先计算每个个体被哪些个体支配、支配了几个个体然后从没有被任何个体支配的那批开始逐层剥离。这个算法写起来也不难关键是数据结构要清晰我一般用两个列表dominated_solutions存“我被谁支配”dominate_count存“我支配了几个人”。每一层扫描后更新计数计数归零的个体进入下一层。拥挤度距离的计算更直白对每个目标把同一非支配层里的个体按该目标值排序首尾个体的拥挤度设为无穷大中间个体的拥挤度取相邻两个个体在目标空间上的归一化距离之和。这个值越大表示这个解在目标空间里越“孤僻”保留它能维持多样性。注意每个目标都要先归一化不然数值大的目标会支配拥挤度计算。3.3 潮流计算前推回代法怎么嵌进目标函数选址定容的每一次目标评估都要算潮流。配电网通常是辐射状网络用前推回代法比用牛顿-拉夫逊法简单得多也快得多。基本步骤是先给各个节点电压赋初始值通常取额定电压从末端节点开始根据负荷功率和注入功率倒推每条支路电流得到支路电流后再从首端向后推算各节点电压反复迭代直到两次电压差小于容差。前推回代法在代码里实现细节决定成败。第一DG接入的节点注入功率方向是“从节点流向网络”所以前推时该节点的净功率应该是负荷减去DG出力第二要处理无功功率光伏逆变器和风电通常以单位功率因数运行也就是无功出力设为0但这不代表节点无功平衡不用算线路充电电容和负荷无功仍然要计算第三迭代收敛判据除了电压幅值差最好也看一下有功功率的不平衡量。这套代码里我默认使用IEEE 33节点配电网系统做测试基准容量取10MVA基准电压12.66kV。为什么用这个系统因为它是配电网优化领域公认的测试算例节点支路数据到处都能找到对比文献时大家用的都是同一个网架结果可复现性很强。很多论文里直接把测试系统的参数写死运行代码前记得确认数据文件格式。我后面的讨论也围绕33节点系统展开如果你的场景是50节点、69节点思路完全一致只需要替换拓扑和负荷数据。4. 代码结构与关键模块实现4.1 总体架构怎么组织才不乱拿到一套代码先别急着看遗传算法函数我的习惯是先把目录结构捋清楚。这套代码我按功能分成五个模块主入口脚本、初始化模块、目标评估模块、NSGA-II进化模块、结果输出模块。主入口脚本做三件事加载配电网参数、设置遗传算法参数、调用进化循环。通常运行完还会自动画三张图Pareto前沿、最优网损对应方案的电压分布、目标收敛曲线。初始化模块负责两件事随机生成初始种群、把真实配电系统的节点数据格式化成算法可用的结构体或字典。这里有个很关键的细节所有参与优化的量都要标幺化。用有名值计算容易造成数值病态比如12.66千伏电压和0.1兆瓦网损放一起数量级差太大拥挤度计算会失衡。标幺化以后电压接近1.0功率、阻抗都在个位数级别遗传算法内部比较起来公平得多。我这里展示一段大概的初始化思路用MATLAB伪代码写换成Python numpy也完全一样pop_size 100; % 种群规模 n_dg 3; % 最多装3个DG n_node 33; % 33节点系统 candidate_nodes 2:33; % 节点1通常是电源不装DG pop_nodes zeros(pop_size, n_dg); pop_cap zeros(pop_size, n_dg); for i 1:pop_size pop_nodes(i,:) datasample(candidate_nodes, n_dg, Replace, false); pop_cap(i,:) 0.1 0.9 * rand(1, n_dg); % 0.1~1 MW end注意候选节点必须去掉平衡节点和联络开关节点不然算法会把DG装在根本不允许接入的位置。这个坑在我第一次跑代码的时候就踩了平衡节点装DG会导致潮流计算直接崩溃。4.2 目标函数和约束罚函数的关键写法目标函数是整个算法里最容易被写乱的部分。我的建议是把它拆成三层第一层解码染色体把节点编号和容量映射到网络参数上第二层调用潮流计算获取网损和各节点电压第三层计算三个目标值并附加惩罚值。解码这一步亲切地叫做“基因型到表现型转换”。代码里要严格检查节点编号数组里不能有重复容量数组不能出现负值。一旦发现染色体不合法可以有两种处理一是在解码阶段直接修复二是返回一个极端大的目标值淘汰它。我在代码里选择先修复再计算因为如果淘汰太多个体种群多样性会很快崩掉。潮流计算返回的结果要保存好后面画电压分布图还要用。目标评估时顺手做一些中间结果的记录这样可以避免整个进化过程结束后重新算一遍所有个体省下不少时间。约束罚函数部分我习惯按节点电压越限程度罚公式类似penalty lambda * max(0, V_min - V_i) lambda * max(0, V_i - V_max)其中lambda取一个较大的正数比如1000。要注意的是罚函数不要罚到完全掩盖目标本身的差异否则算法会盲目追求不越限而忽略经济性。我一般让罚函数占目标量级的10%到30%具体数值可以多次试。潮流不收敛的个体也要处理。常见的做法是给所有目标赋一个很大的值比如[1e6, 1e6, 1e6]相当于直接淘汰。我试过给不收敛个体零目标值结果遗传算法把一堆坏解全当成完美解保留了下来场面非常难看。4.3 进化流程中那些容易写错的小地方NSGA-II主循环的流程是评估目标、非支配排序、选择父代、交叉变异产生子代、父子合并、再排序截断。听起来很顺但有几个细节特别容易写错。第一个是锦标赛选择。很多初学者为了省事直接随机挑两个个体比支配关系但锦标赛的规模通常取2也就是“二元锦标赛”。如果两个个体都在同一非支配层光比支配关系就没法分高下了这时候必须比拥挤度拥挤度大的胜出。这两层判定缺一不可不然选择压力不够种群收敛慢。第二个是交叉变异算子的写法。因为染色体是“节点编号容量”的混合结构我分别处理容量部分用模拟二进制交叉SBX加多项式变异节点编号部分用部分映射交叉来避免重复。没有现成算子的编程语言手写部分映射交叉会有点绕建议先画两个例子推演一遍再写函数。第三个是交叉概率和变异概率不要拍脑袋。交叉率我一般设0.9左右这样大部分个体都会参与基因交换变异率如果按每个基因位算取1除以染色体长度左右比较合适太高会让算法退化成随机搜索。容量变量还要设一个步长上下界防止变异幅度过大导致容量越界。下面这段是去掉细节后NSGA-II的核心循环骨架重点是体会它的流程for gen in range(max_gen): # 1. 评估目标 objectives evaluate(pop_nodes, pop_cap, sys_data) # 2. 非支配排序 fronts fast_non_dominated_sort(objectives) # 3. 计算拥挤度并建立下一代 next_pop_nodes, next_pop_cap [], [] i 0 while len(next_pop_nodes) len(fronts[i]) pop_size: overcrowding_distance(objectives, fronts[i]) next_pop_nodes fronts[i] i 1 # 4. 从第i层按拥挤度补齐 # 5. 锦标赛选择 - 交叉 - 变异 - 得到子代 # 6. 父子合并进入下一轮真正跑起来以后你会发现最耗时的其实是第1步的目标评估和潮流计算遗传算子本身反而是秒级的。所以优化重点要放在潮流函数的向量化上不要循环内套循环用矩阵运算一次算出所有支路电流。5. 结果解读与常见问题排查5.1 Pareto前沿图怎么看、怎么选方案运行完毕最直观的结果是三维Pareto前沿散点图。如果算法没问题你看到的应该是一张漂亮的曲面分布点图点与点之间没有明显的“谁完全支配谁”关系。如果所有点都聚集在一个很小的区域说明早熟了如果点乱得不成形状说明约束或目标函数有bug。选方案的手段有很多。最简单的是从Pareto前沿里找离理想点最近的解所谓理想点就是每个目标分别取所有解中的最优值构成的虚拟点。计算每个解到理想点的欧氏距离距离最小的就是折中解。这个方法虽然朴素但非常直观在论文里也好解释。如果想更严谨可以用模糊隶属度方法。对每个目标定义一个隶属度函数把每个解在每个目标上的满意度转换成0到1之间的数然后求和取最大。这种做法能体现决策偏好比如你更看重投资少就可以给成本目标更高的权重。我在代码里附带了折中解计算函数不用额外改算法直接跑完就能输出一个推荐方案。我还强烈建议把折中解对应的节点电压分布画出来和没装DG的初始电压曲线叠在一起对比。这张图在论文里比Pareto前沿还有说服力它直接展示了优化的电压改善效果评审一看就懂。5.2 我踩过的几个典型坑最大的一次教训是潮流计算里迭代容差写错了。前推回代法的收敛判据我一开始只判断节点电压增量结果算出来的网损稳定在一个错误值上目标函数曲线平滑得很但解集里的网损数值比理论值大了百分之三十。排查了很久才发现支路电流迭代和电压迭代需要同时判断收敛只判电压在一些情况下会提前退出。第二个坑是种群初始化没有考虑初始方案。完全随机生成的种群很多个体把所有DG容量都堆到1兆瓦上限导致初始Pareto前沿严重偏向成本大的区域。解决办法很简单初始化时把容量分布设成从0到上限的均匀随机而不是固定在0.1到1之间。这样初始解集覆盖广收敛也快。第三个坑是安装节点编号选择和导线型号耦合的问题。在配电网模型里不同节点处线路的载流量和阻抗都不一样容量上限如果统一设1兆瓦某些支路可能已经过载了。我后来在约束里加了“支路电流不超过载流量”的硬约束这才让优化结果在实际配电网中真正具备可行性。第四个坑比较隐蔽多目标遗传算法的性能指标比如世代距离、超体积指标很多人根本不算只看Pareto前沿图就下结论。我建议在代码里补一个简单的世代距离统计每一代记录当前Pareto前沿到参考前沿的平均距离距离单调下降说明收敛正常。另一个是超体积指标计算起来稍复杂但对“解集分布是否均匀”非常敏感。5.3 参数调整和结果稳定性建议我常用的参数组合是这样的种群100迭代200交叉率0.9容量变量变异率0.1节点编号变异率0.05罚函数系数1000。这个组合在33节点系统上跑一次大概几十秒到几分钟取决于你的潮流计算实现。如果电脑性能一般可以先调成种群60、迭代100看趋势再把参数加大跑最终结果。多目标遗传算法的随机性很大单次运行不足以支撑论文中的结论。建议同一组参数至少跑10次独立实验统计10次里Pareto前沿的覆盖范围、折中解目标值的均值和标准差。代码里我预留了一个随机种子循环接口每次运行固定不同的种子方便复现和统计。没有这个稳定性统计盲审专家很可能质疑你的结果。还有一个工程经验DG数量上限从2个改到5个Pareto前沿会明显变好但单个DG的容量会变小。如果你发现前沿分布特别稀疏试着把DG数量上限调高1个再看反之如果算法跑得很慢而且方案里很多DG容量只有0.05兆瓦就说明上限设太高了。容量粒度也值得调容量连续优化太慢的话可以离散化成0.1、0.2、0.3兆瓦三档问题规模小很多工程上更接地气。最后再分享一个实在的心得代码能不能跑通只是第一步能不能说服别人才是硬功夫。我每次拿到这类代码都会先盯着潮流模块看半小时因为网上的仿真算例千奇百怪节点编号从1开始还是从0开始、首端是电源还是末端是电源都会让结果直接翻车。真正稳定的代码应该是把“网络拓扑参数”和“遗传算法逻辑”完全解耦这样随换配电网算例只要改一个数据文件就够了。如果你打算长期做配电网优化方向建议从一开始就按这个标准来设计后面会省下大量返工时间。