ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

NSGA2求解分布式电源选址定容:建模、实现与参数调优

NSGA2求解分布式电源选址定容:建模、实现与参数调优 做配电网规划、分布式电源接入研究或者微电网优化方向的朋友对“分布式电源选址定容”这个词应该都不陌生。简单说就是在一个配电网里哪里装光伏/风电/储能、每个位置装多大容量能达到网损最小、电压质量最好、投资最省这几个目标的最优平衡。这个问题做过的都知道麻烦在它是典型的多目标组合优化目标和目标之间互相打架——投资少了电压可能不行电压好了网损又上来了而且变量稍微多一点搜索空间就是指数级膨胀。NSGA2非支配排序遗传算法第二代正是解决这类问题的经典工具。这篇博文结合一套可运行的MATLAB程序从数学建模、NSGA2原理到程序框架、参数整定和常见坑把“分布式电源选址定容”这个问题从头到尾讲一遍。适合正在写论文需要算法基线的研究生也适合刚接触配电网优化、想在MATLAB里快速跑通一个可扩展程序的工程师。1. 问题建模先把选址定容写成计算机能算的数学形式1.1 这个问题的本质是什么选址定容说白了就是两件事定“在哪装”和“装多大”。在数学上这是一个混合整数非线性规划问题MINLP。位置变量是离散的比如IEEE 33节点系统有33个节点你只能选其中若干节点接入容量变量可以取连续值也可以按变压器的标准容量等级离散化。难在哪三个层面首先目标函数是潮流计算结果的高度非线性函数。你把DG功率注入节点后全网各支路的潮流重新分布有功损耗变化不是简单的线性叠加电压分布也牵一发动全身。其次约束条件多且相互耦合潮流等式约束实际上是一大堆非线性方程。最后多个目标之间往往存在冲突不存在一个万能的解只存在一组“帕累托最优解集”——你没法在不牺牲某个目标的前提下让所有目标同时变得更好。我在实际项目里最常用的测试算例是IEEE 33节点配电网10kV辐射网基准容量10MVA基准电压12.66kV。备选安装节点一般取15到20个容量档位按50kVA、100kVA、200kVA、300kVA、500kVA几档离散。这样组合规模就已经是天文数字了所以必须上启发式算法这也是NSGA2在这里的价值所在。1.2 目标函数和约束条件怎么写以我常用的三目标模型为例你可以根据自己需求增删目标1有功网损最小化$$ f_1 \sum_{ij \in \Omega_b} G_{ij}(V_i^2 V_j^2 - 2V_i V_j \cos\theta_{ij}) $$这个公式看起来长其实意思就是配电网里每一条支路由于电流流过电阻产生的焦耳损耗加起来就是网络总损耗。网损率通常在5%~10%之间DG接入位置、容量如果合适能显著减少潮流在长线路上的输送距离网损就能降下来。目标2电压偏差最小化$$ f_2 \sum_{i1}^{N} \left| \frac{V_i - V_{ref}}{V_{ref}} \right| $$配电线路末端电压偏低是常规电网的老毛病DG接入如果位置合理可以起到局部电压支撑的作用。为什么不用最大电压偏差而用总偏差因为作为优化目标可导、平滑、包含全局信息算法搜索时更容易比较两个解的优劣。目标3年综合费用最小化$$ f_3 C_{inv} \cdot \frac{r(1r)^n}{(1r)^n - 1} C_{OM} C_{loss} $$这个公式是设备全寿命周期的等年值法。$C_{inv}$是设备总投资$r$是折现率通常取8%$n$是设备寿命光伏取20年、风机取20年、储能取10年等号右边第一项把一次性投资折算成每年的成本再加上年运行维护费用和年网损费用。这样三个目标的单位不一致也没关系NSGA2做非支配排序不依赖量纲统一这是它比加权法省心的地方。约束条件包括潮流等式约束即各节点的有功、无功功率平衡方程这是硬约束潮流不收敛的方案直接放弃节点电压上下限工业上一般取0.93~1.07p.u.研究场景可以放宽到0.95~1.05p.u.支路电流不越限保证线路不发热过载DG安装总容量不超过系统最大负荷的一定比例通常取20%~30%防止倒送功率过大单个节点的DG容量不超过该节点负荷的某个倍数提示约束条件的处理方式直接影响算法效率。在NSGA2框架里最好用“罚函数法”把越限程度加到目标上而不是简单丢弃个体——否则种群中可行解太少算法很容易陷入局部最优。2. NSGA2核心机制为什么它适合这种多目标问题2.1 从帕累托支配说起先说一个关键概念帕累托支配。假设解A和解B如果A在所有目标上都不比B差而且在至少一个目标上严格优于B那么就说“A支配B”。如果一组解之间互不支配它们就构成了帕累托前沿Pareto front。举个例子目标函数只有两个网损$f_1$和投资费用$f_3$。方案A网损小但投资高方案B投资低但网损大那A和B互不支配都在前沿上方案C网损和投资都比A差C就被A支配了搜索过程中应该被淘汰。为什么不能用传统遗传算法直接跑因为单目标GA需要把多个目标加权合并成一个适应度值。问题在于权重的选择非常主观而且对于帕累托前沿是凹集的问题加权法根本找不到凹区间的解会漏掉很大一块有意义的折中方案。NSGA2一次运行就能得到整条帕累托前沿决策者事后根据偏好挑方案这才是工程上合理的逻辑。2.2 三个核心算子怎么协同工作NSGA2相比第一代NSGA改进了三处每一处都值得理解快速非支配排序。每一代都要给种群里的每个个体分层第一层是不被任何其他个体支配的个体第二层是去掉第一层后剩下的个体里不被支配的依此类推。这个排序算法的复杂度是$O(MN^2)$$M$是目标数$N$是种群规模。只要种群不设得过于夸张比如超过500MATLAB跑起来完全没有压力。拥挤度距离。同一层的个体之间怎么区分好坏看它周围挤不挤。把同一层个体按照某个目标函数排序计算相邻个体在每个目标上的归一化距离之和。拥挤度大的个体说明周围解稀疏优先保留这样能让解集均匀铺满整条前沿而不是扎堆在某一段。精英保留策略。每一代把父代和子代合并成一个2N大小的临时种群排序后只取前N个。这样做的好处是每代最好的解不会被交叉变异破坏掉——即使子代整体变差父代中的优秀个体也能保底进入下一代。这也是NSGA2比很多早期多目标算法稳定、不容易“退化”的核心原因。2.3 遗传算子交叉和变异在连续/离散变量上怎么用标准NSGA2用模拟二进制交叉SBX和多项式变异。SBX的特点是子代在父代附近且有一定概率远离搜索分布比较合理。对选址这种离散变量直接把实数结果取整即可对定容这类连续/离散混合变量我习惯把个体编码成两段前段是安装位置编号后段是容量档位编号交叉变异时对两段分别操作。这里有一个很实际的坑要提醒你如果对“位置编号”这段直接做SBX交叉两个相邻整数之间做插值后取整结果大概率会跳到中间节点上这本身没问题但如果两个位置编号跨度大SBX会过度放大探索破坏多样性。所以我对离散段改用“均匀交叉”即每个位置以0.5概率从父代A、0.5概率从父代B取值。容量档位段用单点交叉变异用随机置换。实践证明混合编码用混合算子收敛速度比一刀切好很多。3. MATLAB程序整体框架从函数分工到核心代码3.1 程序模块怎么拆一套能跑通且便于二次开发的MATLAB程序我建议至少拆成以下几个文件main_nsga2_dg.m主程序入口定义系统参数、算法参数、循环调用init_pop.m初始化种群evaluate_obj.m对每个个体做潮流计算返回三个目标函数值powerflow_dist.m前推回代法配电网潮流计算fast_nondominated_sort.m快速非支配排序crowding_distance.m拥挤度计算selection_crossover_mutation.m选择、交叉、变异入口plot_pareto.m绘制帕累托前沿模块化的意义在于你后续换算例、换目标函数、换算法比如改成NSGA3、SPEA2只需要动对应的单一函数不至于改一处崩一片。3.2 个体编码与种群初始化我的个体编码是定长实数向量长度为 $2N_{max}$其中前 $N_{max}$ 个变量是安装节点编号后 $N_{max}$ 个变量是容量档位编号。注意$N_{max}$ 是预设的最大DG安装个数上限比如5个实际方案允许少于上限这时候用编号0表示“该位置为空”。初始化时要注意纯随机生成会导致大量个体不满足总容量渗透率约束算法前期大量算力花在淘汰不可行解上。我的做法初始化时先随机生成节点和容量然后检查渗透率约束不满足就对容量逐个削减直到满足为止。这样种群起步就是可行解收敛速度快很多。3.3 潮流计算前推回代法的嵌入配电网大多是辐射状结构前推回代法比牛顿-拉夫逊法好用得多——不用求雅可比矩阵内存占用小只要网络拓扑不变稳定性很高。做优化算法时每个个体都要算一次网络潮流计算速度直接决定整个程序能不能在可接受时间内跑完所以这一步要尽量高效。简化版的潮流计算思路function [loss, vdev] powerflow_dist(branch, load, Vg, Dg_Node, Dg_P, Dg_Q) % branch: [from, to, R, X] % load: 各节点有功、无功负荷 % Dg_Node: DG接入的节点编号向量 % Dg_P / Dg_Q: DG注入的有功、无功 nbus length(load); V ones(nbus, 1); % 构建节点顺序表从根节点到末端节点、从末端回推到根节点 % 前推从末端节点开始累加支路功率 % 回代从根节点开始计算各节点电压 % 迭代直到电压偏差小于阈值 end如果你没做过不要一上来就自己写潮流MATLAB的Matpower工具箱里带runpf可以直接算潮流。把潮流算子替换成Matpower目标函数计算部分就变成一个简单的接口调用。缺点是每次调用要解析输入结构批量计算时会慢一些优点是代码几乎不会出错验证方便。我前几个版本都是先用Matpower验证模型正确性确认无误后再把前推回代法写进去提速。3.4 NSGA2主循环的代码骨架这里是整个程序的核心递进逻辑% 初始参数 pop_size 100; max_gen 200; pc 0.9; % 交叉概率 pm 0.1; % 变异概率 n_var 2 * N_max; % 初始化种群 population init_pop(pop_size, n_var, sysdata); % 主迭代 for gen 1:max_gen % 计算目标函数 obj zeros(pop_size, 3); for i 1:pop_size obj(i, :) evaluate_obj(population(i, :), sysdata); end % 非支配排序 拥挤度 [rank, crowd] fast_nondominated_sort(obj); [rank, crowd] sort_by_rank_and_crowding(rank, crowd); % 生成子代 offspring selection_crossover_mutation(... population, rank, crowd, pc, pm); % 合并父代和子代重新排序选前 pop_size 个作为下一代 combined_pop [population; offspring]; combined_obj zeros(size(combined_pop, 1), 3); for i 1:size(combined_pop, 1) combined_obj(i, :) evaluate_obj(combined_pop(i, :), sysdata); end [rank2, crowd2] fast_nondominated_sort(combined_obj); % 截断选择 next_gen truncate(population, rank2, crowd2, pop_size); population next_gen; % 记录当前代的最优前沿 pareto_front{gen} population(rank2 1, :); end这段代码里的evaluate_obj是整个程序的开销大头。优化思路有两个方向一是用并行计算工具箱的parfor替代循环算目标函数二是对潮流计算做向量化批处理。如果你只在单机跑parfor是最简单的提速手段改成并行后速度提升几乎是线性的。注意fast_nondominated_sort在MATLAB里要小心用循环嵌套很容易写出$O(MN^3)$的低效版本。标准实现里用“支配计数”数组记录每个个体被谁支配、支配了几个一个双层循环就够了。4. 参数设置与整定这些参数值我是怎么调出来的4.1 关键参数推荐范围NSGA2不是那种“参数一设错结果全崩”的算法但参数不合适会明显影响收敛速度和前沿质量。下面是我在多个算例上验证过的推荐范围参数推荐范围说明种群规模80~150节点数越多、目标数越多取越大33节点系统100就够最大进化代数150~300看收敛曲线前沿不再变化时再停交叉概率0.85~0.95高交叉保持搜索能力变异概率0.05~0.15太大破坏良好模式太小会早熟分布指数SBX15~25控制子代离父代远近分布指数多项式变异15~20控制变异步长怎么判断当前参数是否合适我最常用的方法是跑完看帕累托前沿图前沿如果是一条光滑、均匀分布的弧线说明收敛得不错前沿断成一坨一坨或者挤在某个角落说明种群多样性有问题优先加大变异概率、增大种群规模而不是盲目加代数。4.2 收敛判据怎么做看进化曲线有个很实用的经验方法每10代记录一次当前最优前沿的目标值把每一代的最优网损画成曲线。曲线前期快速下降后期走平说明参数基本靠谱。如果曲线仍然剧烈波动大概率是变异概率太大或者种群没合并好导致父代最优解丢失。用种群二代间的HV超体积指标差值来判断更严格。HV指标在MATLAB里没有现成函数但计算逻辑不复杂以参考点可以用所有目标分别取最大值构成为顶点计算前沿上所有解与参考点之间构成的超体积。每一代HV单调递增是正常现象连续20代HV变化小于0.1%我就认为算法收敛了。这个做法比拍脑袋定代数靠谱得多。4.3 最终解怎么从帕累托前沿里挑跑完NSGA2你得到的不是单个解而是一组解。挑哪个取决于你实际诉求。三个常用方法模糊隶属度法对每个前沿解计算所有目标的综合满意度取满意度最大的方案。这个方法指标均衡适合写论文当“推荐方案”。最小距离法把每个目标归一化后选距离理想点各目标分别取最优值组成的点欧氏距离最近的那个解。直观、容易解释。偏重某个目标比如工程预算紧张就可以在前沿中筛选投资费用最小的几个解再看网损和电压偏差能不能接受。实操心得论文里画帕累托前沿图时最好把“推荐方案”在图上额外用显眼的标记点标出来再附一张它对应的DG接入位置和容量表。评审和读者一眼就能看出你的程序确实解决了问题而不是只扔出一堆抽象的点。5. 结果分析与方案解读从散点图到工程结论5.1 帕累托前沿长什么样、怎么解读用IEEE 33节点系统跑200代、种群100典型的双目标结果这样分布横轴是年综合费用万元/年纵轴是有功网损kW。前沿从左下角到右上角呈单调递增的L形弧线。左下角的点网损低但投资高右上角的点投资低但网损高中间有一片“性价比”比较高的区域。我自己的经验最优网损点往往是把DG布置在远离根节点的线路中后段比如节点17、18、32、33这些末端附近因为这能显著减小末端长线路的输送电流而投资最小的方案倾向于只在负荷最重、电压最低的几个节点装小容量机组起到“四两拨千斤”的作用。这两个结论在工程上也说得通写报告时可以结合网络拓扑展开解释。5.2 多目标结果与单目标加权结果的对比一个值得在论文里展现的对比实验把三个目标按0.4、0.3、0.3加权合成单目标用普通遗传算法跑同样的代数再把最优解放回帕累托坐标里看它落在哪。大部分情况下加权法的解只落在帕累托前沿的某个局部——因为权重固定它永远只搜索一个方向。而NSGA2得到的整条前沿能告诉你如果在某个方向稍微让步另一个方向能改善多少这个“权衡信息”是决策者最需要的。我曾在一个实际算例里把加权法的“最优解”和NSGA2前沿上的点做了对比加权法给出的方案在某目标上略优但另一个目标差了将近20%。做工程决策时这种隐性代价很致命。5.3 不同DG类型和不同渗透率的影响如果DG类型改成光伏还要考虑其出力的随机性和时序性如果改成储能则还要增加充放电策略的优化维度问题会复杂很多。同一套程序框架下最简单的变体是设定一个“日等效出力系数”比如光伏取0.18、风机取0.28把DG的有功出力和无功出力按典型运行方式折算成“等效恒定功率”来处理。这样做虽然简化了时序特性但作为规划阶段的研究是够用的。渗透率这个参数的影响值得单独分析把DG总容量上限从15%逐步提高到40%前沿会整体向“低网损”方向移动但当渗透率超过某个值后网损改善的边际收益骤降甚至可能出现末端电压过高的现象因为DG出力大于本地负荷后会倒送功率。这能帮你判断这个算例系统能承载多少DG写结论时是一个很有说服力的数据。6. 常见问题与排查技巧实录6.1 MATLAB环境中常见的程序运行问题在使用这套程序的过程中我踩过不少坑最典型的几种整理成速查表现象直接原因解决方案程序报错“Matrix dimensions must agree”目标函数向量维度不对通常是evaluate_obj返回的行向量和obj矩阵拼接出错单步调试时打印size(obj)确认每个个体返回1行3列的向量前推回代潮流迭代不收敛负荷单位kW/MW和基准值不一致DG容量远大于节点负荷先检查全系统总负荷与基准容量是否匹配检查DG注入功率是否超过该节点负荷数倍帕累托前沿挤成一团拥挤度计算标准差为零或参考点设置不合理目标值做归一化处理检查初始化种群是否过窄可用Sobol序列代替纯随机初始化迭代后期前沿停止演化种群早熟变异概率过低加大变异概率可设自适应的变异概率或引入一小部分随机重初始化个体中文注释显示乱码MATLAB读取UTF-8编码文件时默认按GBK解析统一用英文注释或者重新设置编辑器编码为UTF-8中文注释乱码这个问题搞MATLAB的基本都遇到过。最省心的做法是程序核心代码全部用英文注释算法思路、公式推导、结论放到博文或者文档里用中文写。实验程序是给自己看的注释只要能触发记忆就行不必追求中文界面化。6.2 算法层面NSGA2结果不稳定怎么办同一套参数跑两次结果不一样这是启发式算法的正常特性因为初始化种群是随机的。要降低方差固定随机种子rng(42)放在主程序开头确保可复现性多次独立运行取统计结果而不是单次结果每次运行结束后把最优个体保存到.mat文件方便复跑对比但要注意固定随机种子虽然能复现结果也容易掩盖算法的不稳定性。写论文或出报告时建议至少跑10次独立实验报告均值和标准差审稿人更看重这个。6.3 给新手的几个上手建议第一跑通例子最重要。网上有不少开源的IEEE 33节点配电网数据先拿标准算例把程序跑通画出一张像样的帕累托前沿图再去做改动。第二改模型时一次只改一个地方。我见过很多同学把目标函数、约束条件、算法参数同时改了三处结果程序出问题后根本不知道是什么改崩的。第三不要只是下载代码然后跑完就结束把核心的fast_nondominated_sort和evaluate_obj自己手写一遍哪怕写得不够高效至少能保证你真懂算法——答辩或者写论文时被问到细节答不上来比代码写不出来更尴尬。另外提一句环境问题MATLAB程序本身不需要额外工具箱就能运行只要有一个基础版本即可学校或单位提供的授权版本就足够了。别在网上找什么破解资源ESSearch风险大不说还容易下到捆绑木马完全得不偿失。最后再分享一个我自己的习惯。做这类优化项目我一般不会等到全部代码写完才验证结果而是先把目标函数单独测试——给定一个已知合理的DG接入方案看计算出的网损、电压是否在合理范围内再接入NSGA2主循环。目标函数有问题而算法再正确最终也是一堆没有意义的漂亮曲线。真正花时间把数学模型和物理背景吃透程序写起来其实很快。
返回列表