ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

多目标遗传算法在分布式电源选址定容中的应用与实现

多目标遗传算法在分布式电源选址定容中的应用与实现 我先说个实在话这类“多目标遗传算法做分布式电源选址定容”的项目乍一看是个算法题但真正跑过一遍的人都知道难点根本不在遗传算法本身而在“怎么把电力系统的约束和成本算清楚”以及“多目标怎么权衡”。我今年刚完整做完一个类似的课题从IEEE 33节点系统入手把代码从单目标一路改到多目标踩了不少坑也总结出一套能直接复用的套路今天一次性说清楚。1. 分布式电源选址定容问题的本质是什么要做代码之前先把数学模型想明白。很多新手一上来就翻NSGA-II的源码改两个函数就以为自己搞定了结果跑出来的结果连潮流都不收敛这就是典型的“重算法、轻模型”。1.1 为什么选址定容这么关键分布式电源Distributed GenerationDG指的通常是接入配电网末端的光伏、风电、储能、微型燃气轮机这些中小型发电单元。它们不像传统火电机组那样集中在远方而是贴近负荷侧所以“装在哪”“装多大”直接决定了配电网的运行状态。这里有个很直观的类比一个小区里要建充电站如果都挤在同一个入口高峰期必然拥堵如果分散均匀大家充电方便电网压力也小。DG选址定容就是干这个事只不过把“充电站”换成“分布式电源”把“拥堵”换成“网损、电压越限、投资成本”这些电气指标。从工程角度讲选址定容考虑的核心问题有几个网损DG如果放在负荷中心附近能减少功率长距离输送网损自然下降但放错位置反而会增加损耗。电压质量配电网末端电压偏低是常见问题DG能提供就地无功支撑提升电压水平可如果容量过大又可能造成电压越上限。投资与运行成本不是装机容量越大越好设备成本、运维成本都要算进总账。环保效益光伏、风电这类清洁能源有环境价值这也是很多论文里第三个目标函数的来源。你打开任何一篇相关论文目标函数基本就是网损最小、电压偏差最小、成本最小这三个的组合。我的建议是第一步先把这三个目标用数学公式写出来再谈算法。1.2 单目标 vs 多目标为什么必须多目标有些初版代码为了省事把网损、电压、成本加权成一个综合指标跑一次遗传算法就得出一组解。这种“加权法”不是不行但有个致命问题权重怎么定网损重要还是成本重要这本质上是决策者的偏好不是算法能替你定的。多目标优化则不同。它一次跑出一组“帕累托最优解集”Pareto前沿每个解都是“在某个目标上更好、同时另一个目标上不差”的折中方案。比如解A网损更低但成本高解B成本低但电压偏差大两个解没有绝对优劣决策者可以结合实际需求选。这就像买车有人看重油耗有人看重动力有人看重空间。单目标优化等于提前说“油耗最重要”而多目标优化是把“不同配置的备选车型都列出来”让你自己挑。对于配电网规划这种多利益主体博弈的问题后者的工程价值显然更大。2. 多目标遗传算法选型解析NSGA-II凭什么成为默认解解决了“为什么多目标”之后下一个问题是“用哪种多目标算法”。现在主流的选择是NSGA-IINon-dominated Sorting Genetic Algorithm II也就是带精英策略的非支配排序遗传算法。我项目里用的就是它跑下来稳定性确实不错。2.1 NSGA-II的核心机制与选型理由NSGA-II在2002年由Deb等人提出到现在二十多年了依然是工程界应用最广的多目标进化算法。它最核心的三个机制对应解决了三个痛点第一快速非支配排序。每代种群里的个体按照Pareto支配关系分层。比如个体A在所有目标上都优于个体B那B就被A支配。排序后第1层是最优解第2层次之依此类推。选择时优先保留层级靠前的个体保证收敛方向正确。第二拥挤度距离。同一层里可能有很多个体但不能全留下来。拥挤度距离衡量的是“这个个体周围有多少邻居”距离越大说明这个区域解越稀疏越值得保留。这保证了Pareto前沿的均匀分布不会挤在一团。第三精英保留策略。父代和子代合并后再排序那么上一代的最优解不会因为随机操作丢失。这保证了算法不会越跑越差。这三板斧组合下来NSGA-II在收敛性和分布性之间取得了不错的平衡。虽然这几年也出现了NSGA-III、MOEA/D这些新算法但我选NSGA-II的原因很简单matlab和python的现成库都成熟社区案例多而且对于节点数不超过100的配电网模型NSGA-II的精度已经足够了。2.2 关键参数设置与工程经验参数设置是遗传算法里“玄学”感最强的地方但也是有规律可循的。我以标准IEEE 33节点系统为例给出我调试好的参数模板参数推荐取值调整经验种群规模100~200节点多、变量多时取大值但过大会拖慢计算速度迭代代数100~200看适应度曲线是否收敛没收敛就加代数交叉概率0.85~0.95太小搜索速度慢太大破坏优良个体变异概率0.01~0.1这个值很敏感我用0.05跑得最稳编码方式实数编码每个基因位存DG接入节点编号不要用二进制这里我想特别强调变异概率。我一开始用默认的0.1跑出来Pareto前沿倒是很分散但很多个体的潮流不收敛因为变异把DG容量改得太大超出了节点承受能力。后来把变异概率降到0.03~0.05并加了约束修复效果明显改善。这印证了一个理念非支配排序只管目标函数排序不管解是否物理可行所以约束处理必须单独设计。3. 核心代码实现从约束到Pareto前沿的完整链路代码部分是这个项目的重头戏。我的实现思路很直接MATLAB做主框架潮流计算用Matpower遗传算法逻辑自己写不调工具箱方便改。这样每一步都在自己掌控中调试起来不抓瞎。3.1 编码方式与种群初始化选址定容问题有两个决策变量在哪些节点装DG和每个节点装多大容量。我用一个二维实数数组表示个体行是染色体序号列的一半是节点编号另一半是对应容量。% 种群初始化 - 以IEEE 33节点为例 n_pop 200; % 种群规模 n_dg 3; % 预设DG接入数量 n_node 33; % 配电网节点数 % 每个个体结构: [接入节点1, 节点2, 节点3, 容量1, 容量2, 容量3] pop zeros(n_pop, 2 * n_dg); for i 1:n_pop % 随机选取3个不同节点排除变电站节点1 nodes randperm(n_node - 1) 1; pop(i, 1:n_dg) nodes(1:n_dg); % 容量在[0.1, 1.5]MW之间随机避免过小或过大 pop(i, n_dg1:end) 0.1 1.4 * rand(1, n_dg); end注意两个容易踩坑的点。第一同一染色体里不能出现重复节点一个节点装两个DG在物理上可以但对优化无意义最好初始化时就去重。第二容量范围要参考系统负荷水平IEEE 33节点系统总负荷约3.7MW单个DG取0.1~1.5MW比较合理取太大后续潮流计算必炸。3.2 潮流计算与适应度评估适应度评估是整段代码的灵魂。每个个体要被解码成“DG接入方案”然后代入潮流计算得到网损、电压等指标。%% 适应度函数伪代码 function [cost_net, v_dev, cost_invest] evaluateIndividual(genes, systemData) % 1. 解码提取接入节点和容量 % 2. 修改系统数据在对应节点增加PQ注入功率 % 3. 调用Matpower潮流计算得到节点电压和支路损耗 % 4. 计算目标函数值并返回 end我用Matpower的原因主要有两点一是它经过了大量测试验证比手写牛顿-拉夫逊法稳得多二是它支持直接修改bus和gen矩阵接口友好。改造时只需要新建一个case文件把选定节点改成PQ节点然后填入DG的出力值即可。潮流计算完成后三个目标函数这样计算网损PlossMatpower里直接读loss结果或按下式统计所有支路首末端功率差之和。电压偏差ΔU计算所有节点电压与额定值1.0p.u.之差的绝对值累加这个指标比只看最低电压更全面。综合成本C等年值投资成本加运行维护成本贴现率按8%、运行年限按20年折算这个公式很多论文里有我直接按标准做了。3.3 非支配排序与拥挤度距离的实现这一块是NSGA-II的机制核心。非支配排序的关键是判断两个个体之间的支配关系。用MATLAB实现时我先把每个个体的目标值存成一个矩阵然后两两比较。function [fronts, rank] nonDominatedSort(obj_values) % obj_values: n_pop x n_obj 的目标值矩阵 % 假设所有目标都是求最小 n_pop size(obj_values, 1); dominated_count zeros(n_pop, 1); % 被支配次数 dominate_set cell(n_pop, 1); % 支配的个体集合 for i 1:n_pop for j 1:n_pop if i j continue; end % 判断i是否支配j if all(obj_values(i, :) obj_values(j, :)) ... any(obj_values(i, :) obj_values(j, :)) dominate_set{i} [dominate_set{i}, j]; dominated_count(j) dominated_count(j) 1; end end end % 分层被支配次数为0的是第1层依次递推 front 1; fronts {}; while sum(dominated_count -1) n_pop % 全部处理完 current_front find(dominated_count 0); if isempty(current_front) break; end fronts{front} current_front; for k current_front dominated_count(k) -1; % 标记已处理 for m dominate_set{k} dominated_count(m) dominated_count(m) - 1; end end front front 1; end end拥挤度距离这部分思路是“先按某一目标排序再把边界的个体距离设为无穷大中间个体算相邻个体目标值的差”。需要特别注意的是每个目标都要先归一化再算距离否则网损动辄几百千瓦、成本动辄几百万数值上会把电压偏差这样的“小量”完全淹没。3.4 完整算法主流程与选择策略整合把前面各部分串起来的完整主流程可以用这个套路套任何NSGA-II问题1. 初始化种群P0 2. 迭代 t 1 to maxGen: a. 对种群Pt做二元锦标赛选择得到父代池 b. 交叉模拟二进制交叉SBX和变异多项式变异 c. 生成子代Qt d. 合并 Pt 和 Qt得到2倍规模种群Rt e. 对Rt做非支配排序按层级从低到高放入新种群Pt1 f. 如果当前层放不下全部个体计算拥挤度选距离大的保留 3. 输出最终Pareto前沿解集我常用的选择操作是二元锦标赛随机抽两个个体优先取层级低Pareto排序靠前的如果层级相同取拥挤度大的。这样写出来代码逻辑清晰速度也快。下面给出交叉和变异两个算子的关键代码片段。我用的是模拟二进制交叉SBX因为它在实数编码问题上表现稳定而且参数η_c控制着子代与父代的相似程度。% 模拟二进制交叉 function [c1, c2] sbx_crossover(p1, p2, eta_c) u rand(); if u 0.5 beta (2*u)^(1/(eta_c1)); else beta (1/(2*(1-u)))^(1/(eta_c1)); end c1 0.5 * ((1beta)*p1 (1-beta)*p2); c2 0.5 * ((1-beta)*p1 (1beta)*p2); % 边界检查 c1 max(min(c1, 1.5), 0.1); c2 max(min(c2, 1.5), 0.1); end多项式变异也类似用一个分布指数η_m控制扰动的集中程度。我这里的η_c和η_m一般取15和20这是Deb论文里的推荐值实测效果挺好。如果你发现自己跑出来的Pareto前沿很窄优先检查是不是交叉概率太小、或者选择压太强而不是急着调变异。3.5 约束处理的罚函数技巧前面提到遗传算法生成的解很可能违反约束。比如DG总容量过大导致某个节点电压超过1.05p.u.或者DG容量为负虽然初始化限定了但交叉变异后可能越界。我的做法是越限即罚不直接丢弃该个体而是给它的目标函数加上一个很大惩罚项。% 约束违反检测示例 voltage_violation sum(max(0, voltage - 1.05).^2) ... sum(max(0, 0.95 - voltage).^2); if voltage_violation 0 % 电压越限惩罚系数经验值设为10000 cost_net cost_net 10000 * voltage_violation; v_dev v_dev 10000 * voltage_violation; end为什么不直接丢弃因为遗传算法本质上是在“大范围试探”超过边界一点点的新个体其基因可能携带了某些优秀片段直接丢弃会损失多样性。罚函数相当于告诉算法“这条路错得太离谱但保留下来看看有没有可用的基因信息”。这一招看着简单却是约束优化里最好用也最常用的工程手段比各种花哨的可行性修复方法省心得多。4. 调试经验与常见问题速查代码写完之后真正的工程挑战才开始怎么确认Pareto前沿是对的怎么加快收敛怎么判断自己算错了这一章把我这次项目里掉过的坑和最终的解决方案如实列出来省得你再走一遍弯路。4.1 收敛性判断与Pareto前沿的可靠性很多同学跑完算法一看Pareto前沿画出来挺漂亮就觉得完了。我踩坑之后总结出一个关键校验方法拿一个极端解跟单目标优化结果对比。比如Pareto前沿里应该存在一个“网损最小”的极端解。你用单目标遗传算法只优化网损把其他目标权重设成0算出来的最优结果应该和Pareto前沿里网损方向的最端点非常接近理论上同等或更优。如果两者差异很大说明多目标代码里适应度评估或排序逻辑有bug。第二个校验是观察算法的收敛曲线。多目标算法因为没有单一适应度值我喜欢记录“第1层Pareto前沿的个体数量”和“网损目标的中位数随代数变化”。如果前沿数量从很多逐渐减少到稳定在一个区间同时目标中位数单调下降后不再明显变化说明算法基本收敛。如果前沿数量持续剧烈抖动大概率是约束罚函数签数设得太小或者变异概率过大。第三个是直观判断法合法的Pareto前沿在目标空间里应该是一条单调下凹的曲线或曲面。如果画出来的散点是杂乱无章的、甚至很多点明显被其他点支配那就说明非支配排序有问题。4.2 计算耗时问题与加速方案IEEE 33节点系统的潮流计算非常快单次要不到0.05秒所以200个体跑200代总体耗时也就在几十秒量级。但如果你换成IEEE 123节点系统或者加了负荷不确定性要做多次抽样计算量会呈指数增长。这时候要考虑三个加速手段并行计算MATLAB的parfor是把种群中个体评价并行的最简单手段。适应度评估互相独立天然适合并行。稀疏存储潮流计算中雅可比矩阵是稀疏的确认Matpower用的是稀疏矩阵运算不要自己写稠密求逆。减少潮流次数如果目标函数里需要用多次潮流结果比如24小时负荷曲线可以先用聚类把负荷曲线压缩成3~5个典型场景加权计算目标值精度损失不大但速度提升非常可观。4.3 常见错误速查表这里整理一张速查表都是我在帮别人检查代码时遇到的高频问题问题现象可能原因解决方法Pareto前沿只有一两个点种群多样性不足增大种群规模降低变异概率前沿分布偏在某一侧目标函数未归一化按各自范围归一化后再排序结果比单目标优化差非支配排序方向写反检查是min还是max统一方向潮流不收敛警告很多DG容量越界检查编码边界交叉变异后加边界修复每次运行结果差异巨大随机种子未固定初调时固定随机种子正式跑多组取统计电压全部偏低DG接入容量过小适当放宽容量上下限范围其中“每次运行结果差异巨大”这点我想多说一句。遗传算法本质是随机算法不同种子跑出不同结果完全正常。但如果你需要复现就在代码开头写死随机种子如果是为了验证算法稳定性就固定几个种子跑5~10次统计Pareto前沿的分布范围。别指望两次运行结果一模一样那是没理解随机优化。4.4 多目标结果的后处理与决策建议跑出Pareto前沿只是第一步工程上还得从多个解里挑一个实施。这个环节很多代码模板不给出来但恰恰是实际项目里对方最关心的。我常用两个方法一个是模糊满意度法把所有目标归一化后取每个个体满意度最高的作为推荐方案另一个更简单粗暴——直接看工程偏好。比如电网公司如果当前网损率超标严重就选网损最小的解如果项目预算有限就选成本最低的解。给出手算结果Pareto前沿图同时把两三个典型方案的指标列成表格决策者能看明白数值上又有说服力这就是这个算法的交付价值。5. 项目复盘与后续扩展方向整个项目跑完最深的体会是不要迷信算法本身的高深程度而要把精力花在“问题建模是否符合物理规律”和“代码逻辑是否可验证”上。NSGA-II只是优化引擎真正决定结果上限的是你把配电网的网架结构、负荷特性、DG运行约束考虑得有多完整。如果有后续课题我建议从三个方向扩展。第一把确定性优化改成鲁棒优化。实际运行中光伏出力和负荷都有很强的随机性用典型日数据代替全年会使结果偏乐观。可以引入场景概率法或者用信息间隙决策理论IGDT建模不确定性但代码复杂度会上一个台阶。第二加入储能和电动汽车充电桩。很多项目现在都要求DG和储能协同规划因为储能能平抑DG的波动性。这需要把决策变量扩展到“储能容量”“充放电策略”甚至“日内时序”变量维度高了很多但NSGA-II依然处理得动。第三考虑动态重构。网架重构和多时段运行是配电网智慧化的方向规划层的选址定容结果如果能在运行层配合开关动作预期收益会进一步提升。这类问题更适合用两阶段优化或双层优化架构也是未来一段时间的热点。最后再分享一个小技巧拿本函数的非支配排序那一段如果越看越绕建议画一张三目标的散点图用颜色深浅表示支配层级一目了然。调试多目标算法的视觉化工具远比硬看数字高效得多。这个习惯我保留到今天遇到多目标优化问题先画图、再调参能省一半调试时间。
返回列表