1. 选址定容问题的数学模型重述:为什么单一算法不够用
做分布式电源规划的人应该都有同感:选址定容这个问题的麻烦,不在于“算不出来”,而在于“算出来你也不敢信”。我最初接手这类项目时用的是加权系数法,把网损、电压偏差、投资成本三个目标揉成一个标量函数,用传统优化算法一跑,得到一个“最优解”。结果拿给现场做配网规划的老师傅看,人家直接问了一句:你这个方案在夏季高峰和冬季低谷的电压分布完全不一样,哪个才是我要的?
这一问把我问住了。后来我才真正意识到,选址定容本质上不是一个单目标最优化问题,而是一个典型的多目标决策问题。分布式电源的接入位置和容量配置,会同时影响系统网损、节点电压质量、DG投资运行成本、以及故障情况下的供电可靠性。更麻烦的是,这几个目标之间存在明显的冲突:把DG集中在负荷中心附近,电压支撑效果好,但可能加剧局部线路过载;把DG分散布置,网损降了,但投资成本和运维难度又上去了。你不可能让所有目标同时达到最优,你能做的,是在一组相互冲突的目标之间找出一组“谁也不完全支配谁”的折中解,也就是Pareto最优解集。
这组Pareto解集就是决策空间里真正的宝藏。它不会告诉你“唯一正确答案”,而是给你一张“可能性地图”。在这张地图上,你既可以看到网损最低的方案长什么样,也可以看到投资成本最小的方案有多保守,还能看到电压质量最好的方案倾向于把DG放在哪些节点。至于最终选哪个,那是调度策略、政策导向和现场约束共同作用的结果,不是优化算法该替你拍板的事。
所以当我改用NSGA2来做这个项目时,核心转变其实不是“换了个算法”,而是“换了一套决策思维方式”。NSGA2给到我的,不再是一个点,而是一整条Pareto前沿曲线。这条曲线让我能直观地告诉客户:在投资预算内,你能达到的最优网损水平是多少;在电压质量要求提高10%的前提下,你要多付出多少成本。这种“看得见代价”的决策方式,才是工程实践中真正需要的东西。
2. NSGA2算法在MATLAB环境下的落地实现要点
2.1 算法流程的工程化改造
NSGA2的标准流程大家都很熟悉:种群初始化、快速非支配排序、计算拥挤度、锦标赛选择、模拟二进制交叉(SBX)、多项式变异、精英保留策略。但在MATLAB里把这套流程从论文变成能跑double、能出图的代码,中间有好几个工程层面的坑要填。
首先是种群编码方式。分布式电源选址定容问题的决策变量是“接入位置”和“接入容量”的组合。我最初的编码方式是二进制编码,每个节点用0/1表示是否接入DG,容量用一组离散档位对应。后来发现这种编码在交叉和变异之后会产生大量非法个体——比如两个父代交叉,子代可能出现“同一节点被重复选中”或者“总装机超出配网承载力上限”的情况。每次都靠修复算子去纠正,种群多样性损失严重。
最终我改成了实数编码加约束分层的方案。具体来说:决策变量是一个2N维的实数向量,N是候选节点数。前N维是各节点的接入容量(0表示不接),后N维是接入标志位(0/1),但标志位不参与交叉变异,而是在每次生成新个体后由容量值硬性推导:容量大于某个阈值(比如50kW)则为1,否则为0。这样既保留了实数编码在SBX交叉中的连续搜索能力,又避免了产生逻辑上不自洽的个体。
2.2 约束处理策略:罚函数之外的思路
配电网选址定容的约束条件一般是这几类:潮流方程约束(等式约束)、节点电压上下限、支路电流热稳定极限、DG总装机容量上限、单节点接入容量上限。等式约束在潮流计算中天然满足(因为用的是前推回代法求潮流),真正麻烦的是不等式约束。
我遇到过不少人直接用罚函数,把违反约束的程度乘以一个大罚因子加进目标函数。这个方法不是不能用,但有两个问题:一是罚因子太小约束等于摆设,罚因子太大又会让Pareto前沿严重偏向可行域边缘,解集分布的均匀性变得很差;二是NSGA2的Pareto支配关系在罚函数框架下会被扭曲,一个网损极小但电压轻微越限的解,可能被一个网损稍大但完全可行的解支配掉,而我们恰恰需要保留那些“轻微越限但潜力巨大”的中间形态个体去参与进化。
我自己最终的方案是“约束支配”加“可行性优先”的混合策略。具体实现上,在快速非支配排序之前先判断每个个体的约束违反总量,两个个体比较支配关系时,违反约束少的个体优先;只有两者违反量相同(包括都为0),才进行正常的Pareto支配比较。这个思路在NSGA2社区已经有很多变体,实测下来比单纯罚函数稳得多,也保留了足够多的边缘探索个体。
2.3 关键函数的MATLAB性能调优
NSGA2跑出的瓶颈一般不在算法本身,而在潮流计算。一个30节点的配电网算一次潮流只要几毫秒,但种群规模100、进化200代,总潮流计算量是两万多次。如果每次潮流都用同一套前推回代函数去处理,光潮流计算就能占掉总运行时间的八成以上。
我做的第一个优化是潮流计算的向量化。MATLAB最忌讳的就是在循环里嵌套低效的标量运算。我把前推回代法里面的支路潮流递推改成矩阵运算,用稀疏矩阵存储节点导纳矩阵和支路参数,潮流的单次计算时间直接从毫秒级压到亚毫秒级。第二个优化是并行化。如果电脑有多核,直接用parfor把种群中每个个体的潮流计算分摊到多个worker上。但这里有个细节:parfor的循环体里不能有随机数产生,否则每个worker的随机序列不受主控,实验结果无法复现。需要在循环外先预生成好交叉变异要用的随机数矩阵,或者给每个worker显式指定随机种子。
3. 仿真参数设置与收敛性调试经验
3.1 从一个跑不动的Demo到一个能出结果的系统
实话说,我第一次拿NSGA2跑选址定容模型,结果非常难看。种群进化到第60代左右就完全停滞了,Pareto前沿只有稀稀拉拉五六个解,而且全部挤在目标空间的左下角,网损低得离谱、电压却越限得离谱。排了半天错发现是两件事叠加的结果:一是交叉概率太高(0.9),导致种群在进化后期失去多样性,所有个体都向同一个方向集中;二是变异步长太大,多项式变异算子的分布指数eta_m设置成了10,在实数编码下变异扰动可以让单节点容量从100kW直接跳到2MW,大量个体掉进不可行域。
在NSGA2的参数体系里,交叉概率、变异概率、SBX分布指数eta_c、多项式变异分布指数eta_m这四个参数相互耦合,不能孤立地调任何一个。我最终的参数组合是:种群规模Np=100,进化代数Gen=150,交叉概率Pc=0.85,变异概率Pm=1/决策变量维数(这是NSGA2原文建议的动态取值方式),eta_c=15,eta_m=20。记住一个经验:eta_m比eta_c大一些会更有利于维持种群多样性,因为变异阶段承担着局部精细搜索的任务,步长大一点不容易陷入局部Pareto前沿。
3.2 如何判断算法已经收敛而非陷入早熟
判断NSGA2收敛的方法和单目标算法完全不一样。单目标算法看目标函数值有没有变化,NSGA2得看整条Pareto前沿的演化趋势。我常用的一个指标是“前沿面积法”:把每一代得到的所有非支配解按目标1升序排列,然后计算相邻解之间由目标1和目标2差值构成的矩形面积之和。这个面积在前沿扩张阶段会持续增大,等到面积增幅小于某个阈值(比如连续5代增幅小于1%),基本可以认为前沿已经稳定。
还有一个更直观的检查方法:把每一代的最优个体拿去做潮流校验,看它的两个目标值和约束违反量的变化趋势。如果目标值还在波动、约束违反量却已经降到零,说明种群还在朝可行域边缘进化,离收敛还早;如果目标值几乎不动、约束违反量也在零附近反复跳,说明算法已经卡在一个局部前沿上,这时候单纯加大进化代数意义不大,不如调高变异概率或者干脆重新初始化种群。
3.3 典型算例的期望输出与实际对比
我在一个改进的IEEE 33节点系统上做了验证测试。系统基准电压12.66kV,总负荷约3715kW+2300kvar,每个节点设置了DG接入容量上限(最大500kW),总装机上限设为负荷峰值的25%。运行NSGA2之后,Pareto前沿上取了一个折中解:在节点16接入一台容量300kW的分布式光伏,在节点22接入一台容量450kW的分布式风机,在节点28接入一台容量200kW的储能型DG。相比无DG接入的基准场景,系统网损从202.3kW降到了138.6kW,降幅约31.5%;全网最低电压从0.913p.u.提升到了0.956p.u.,电压越限节点数从5个降到了0个。
这个结果和论文里报告的典型结论一致。需要特别说明的是,不同DG类型(光伏、风电、燃气轮机、储能)的出力特性差异会对目标函数计算产生很大影响。光伏只在白天出力、风电出力波动大、储能则具备时空转移能力,在建模阶段一定要把时序特性考虑进去,否则得到的“最优容量”在实际运行中一定会打折扣。我这个算例里简化处理为恒定功率因数下的确定性出力,属于比较保守的初步估算,后续如果要精细化,建议引入典型日时序曲线把一年8760小时聚合成几个典型时段。
4. 结果分析与Pareto前沿的工程决策应用
4.1 从Pareto前沿读出决策信息
算法跑完之后,得到的一堆非支配解本身不会说话,需要人去读。我的习惯是先把Pareto前沿画在二维目标空间里(横轴是年综合费用或网损、纵轴是电压偏差或DG渗透率),然后看几个关键位置:
前沿的左端点通常是“网损最优解”,它的DG往往集中在大负荷节点附近,单位容量的降损效果最明显。前沿的右端点一般是“投资最省解”,它的DG接入容量刚好压着约束下限,或者是完全不接入DG的原始电网状态。前沿的中间区域才是工程上最值得关注的部分——这里往往存在一个“拐点”,在拐点左侧,增加一点投资就能换来较大幅度的网损下降;在拐点右侧,再追加投资换来的收益就明显减弱了。这个拐点实质上就是边际效益递减的分界线,工程选型时取拐点附近的解,通常是性价比最高的。
4.2 折中解选取的技术手段
Pareto前沿选解有三种常用方法,我按实际工程场景逐个说:
模糊隶属度函数法最省事。把所有目标归一化之后,对每一个Pareto解计算各目标的满意度,满意度函数一般取线性或指数形式。整个前沿上满意度综合最高的解就是折中解。这个方法的问题在于函数形式和权重系数都得靠主观设定,不同人算出来可能给出不同的推荐位置。
与理想点距离最小法是另一种做法。先算出各个目标单独最优时构成的“理想点”,然后找Pareto前沿上欧氏距离最小的解。这种方法的好处是没有额外参数,缺点是前沿形状不规则时容易选到边缘解。
基于实际约束的筛选法是我最推荐的。把现场工程约束(比如投资预算上限、DG渗透率不应超过某个安全值、接入节点不能是重要负荷的哨点节点)作为硬性条件,先在Pareto前沿上筛掉一批不合规的解,剩下的再看哪几个处于拐点附近。这个方法不追求数学上的“最优折中”,而是把决策者视角真正放进了算法结果的后处理环节。
4.3 我用这套流程做成的实际决策报告
去年帮一个工业园区做分布式光伏选址规划,我把这套NSGA2流程封装成了一套MATLAB脚本。从跑完的Pareto前沿(一共37个非支配解)里做了一轮工程筛选:先去掉投资超过预算上限的13个解,再去掉接入节点位于冷冻机房母线上的4个解(有意避开敏感负荷),剩下的解里重点考察网损降幅在25%~35%之间的那批。最后推荐的方案是把光伏分散布置在三栋厂房屋顶,单点容量在200~400kW之间,年综合费用比“全部集中在一栋楼”的方案高出约6%,但网损下降率多了近9个百分点。这个结果在项目评审会上拿给投资方看,Pareto前沿上各个方案的代价和收益一眼就能看清,比单纯的“最优解”有说服力得多。
5. 避坑实录:NSGA2求解选址定容的八个常见问题
5.1 潮流计算与NSGA2主循环的接口设计
这个坑踩得比较深。最初我把潮流计算写成了一个独立的m文件函数,NSGA2主程序每次调用它都需要重新加载网络参数、初始化节点数据。看起来没毛病,但跑起来之后发现速度慢得离谱。排查了半天才明白,MATLAB的函数调用开销加上重复加载参数的损耗,在几万次调用下会被无限放大。正确的做法是把配电网的基础参数(支路阻抗、节点负荷、拓扑结构)定义成主程序里的结构体变量,用匿名函数捕获这些变量生成潮流计算句柄,整个进化过程都使用同一个函数句柄,参数读取只做一次。
5.2 随机数种子对实验结果的影响
很多人跑进化算法不设置随机种子,结果每次跑出来的Pareto前沿形状都有细微差别。如果只是自己在做研究倒还好,但如果要写论文、做项目汇报或者对比算法性能,不固定随机种子等于给评审留了个大把柄。我在主程序最前面加了一行rng(42),所有随机数都从这个种子派生。要注意的是,用了parfor并行计算之后,随机数管理会变得更复杂,建议把随机数生成操作全部移出parfor循环体,在循环外生成好之后以矩阵形式传入。
5.3 网络安全约束与分布式电源出力的时空耦合
这个问题比较隐蔽,也是我在做到第三个项目时才真正重视起来的。选址定容的优化结果往往只考虑了正常工况下的网损和电压指标,没有把N-1预想事故(某条支路退出运行)下的电压和载流量约束纳入目标函数。实际运行中,DG接入位置和容量会深刻影响故障后潮流的转移路径,某个在正常工况下看起来很合理的方案,在N-1条件下可能直接导致某条线路严重过载。
处理思路是在目标函数或约束条件中增加N-1校验项:对每个候选方案,枚举关键支路开断后的潮流分布,统计越限程度作为惩罚项加入目标函数。这会增加大量潮流计算量,实际操作中可以对支路做预筛选,只对重载率排名前30%的支路做N-1校验,在精度和耗时之间取平衡。这个点虽然在很多论文里被一笔带过,但在真实的配电网规划项目里几乎是必答题。
5.4 算法早熟与种群的多样性维护
NSGA2比传统遗传算法好在精英保留策略可以避免最优解被破坏,但同样存在早熟风险。我在实践中发现一个规律:前沿陷入早熟的时候,种群中绝大多数个体都聚集在目标空间的某一小片区域,拥挤度距离普遍很小。这时候单纯增大变异概率效果有限,更有效的手段是采用NSGA2的变体——每隔若干代检测一次拥挤度距离的平均值,如果连续多代低于阈值,就把前10%的最优个体原样保留,剩下的90%重新随机初始化,强制注入新鲜血液。这个“精英保留+周期性重启”的策略在我测试过的16节点、33节点、69节点系统上都表现稳健。
5.5 代码结构模块化与参数脚本化
很多初学者把NSGA2的代码写成一个大而全的脚本,所有参数都硬编码在正文里。这表面上省事,但一旦需要换网络拓扑、调整约束条件或者对比不同参数组合,就得满地找变量名改数据,极易出错。我的建议是把代码拆成既明确的模块:参数配置脚本、潮流计算函数、目标函数计算函数、NSGA2主循环、结果后处理脚本。参数配置脚本单独成文件,所有需要调整的数值都集中放在一个结构体里,改参数只需动一个文件,出问题也好排查。
5.6 多目标指标的评价与统计分析方法
NSGA2跑完后的评价指标至少要看三个:非支配解的数量、前沿的均匀性、前沿与真实Pareto前沿(如果能获得的话)的贴近程度。前两个指标可以直接从解集分布算出来,第三个指标在选址定容问题上通常没有解析解,只能拿大种群、大代数跑出的结果作为近似参考。实际操作中我用一个简单实用的方式:同一问题分别用50代、100代、200代的结果做对比,如果100代和200代的前沿已经很接近,就认为200代的结果可以作为近似真实前沿使用。
5.7 有多少种分布式电源类型就要建多少套出力模型
这点我在第三节提过,但值得再强调。用恒定功率模型去近似光伏和风电会带来系统性的优化偏差。光伏的出力集中在10:00-14:00,风电的典型出力高峰往往在夜间,储能则可以根据电价信号灵活调整充放电时段。不同电源的时序互补特性,恰恰是选址定容模型中最能“薅羊毛”的部分——把光伏放在白天负荷重的工商业区,把储能放在电压支撑薄弱但电价波动大的节点,效果会显著好于把所有DG一视同仁地建模。建议至少按“光伏+风电+储能”三种类型分别建模,各带一条典型日出力曲线。
5.8 MATLAB版本兼容性和第三方依赖
最后提醒一个容易翻车的地方:MATLAB不同版本对函数语法和工具箱的兼容性差别不小。我最早用的版本是R2018b,后来换到R2022a,发现某些旧版写的函数句柄用法在新版本里被警告甚至废弃。如果你要把这份代码发给别人用,务必注明MATLAB版本号,并且尽量少依赖特定版本的专属函数。另外,如果用了并行计算工具箱、优化工具箱之外的第三方函数,一定要提供独立的依赖清单。我自己在项目交付时会把所有用到的自建函数打包成一个文件夹,附带一个versions.txt文件,避免接收方因为环境差异跑不出结果而来回扯皮。
6. 扩展方向与算法对比思考
NSGA2在分布式电源选址定容上的应用,这几年已经被大量论文验证过了,但“能跑”不等于“跑得好”。做项目时我经常被问:怎么不试试别的算法?其实多目标优化的算法家族很大,NSGA2只是其中一个经典代表。多目标粒子群(MOPSO)在多峰问题上的搜索能力不弱,但它对Pareto前沿的均匀性维护不如NSGA2;基于分解的多目标进化算法(MOEA/D)在大规模决策变量场景下效率更高,但它的权重向量设计对问题维度很敏感;多目标灰狼算法(MOGWO)在某些配网算例上收敛速度快,但稳定性口碑一般。
我个人的观点是,算法选型应该服从问题特性。选址定容问题有离散决策变量空间和连续潮流计算的混合特征,NSGA2的锦标赛选择机制配合精英保留策略,对这类问题的搜索性能是最稳妥的。如果追求极致效果,可以考虑在NSGA2基础上引入局部搜索操作(类似memetic算法),对Pareto前沿上的每个个体做一次局部微调,通常能在不破坏多样性的前提下进一步提升前沿质量。
另外补充一个实用的想法:NSGA2虽然名叫“非支配排序”,但真正的瓶颈往往不在排序而在种群评估,对选址定容问题来说也就是潮流计算的耗时。如果项目对计算速度有硬性要求,可以试试用响应面模型或Kriging代理模型替代部分潮流计算——先用少量精确潮流计算训练一个代理模型,再在进化过程中用它快速排除明显劣质的个体,只在候选个体进保留集之前才做精确校验。这个思路在配电商用规划工具里用得越来越多。
回到代码本身,到目前为止这套MATLAB程序对16节点、33节点、69节点三个经典辐射状配电网系统都做过测试,稳定性和结果质量都合格。代码里每个函数都带注释说明输入输出格式和物理含义,路径和参数变量也做了统一管理。如果你有具体的目标场景(比如手头有一个实际的工业园区拓扑图,或者园区有特定的峰谷电价策略),直接在参数脚本里改网络数据和成本系数就能跑起来。
最后再分享一个小技巧:跑完NSGA2得到Pareto前沿后,用MATLAB里的scatter函数把前沿点画出来,再用plot函数把初始单目标最优解、纯投资最省解、纯网损最优解分别标成不同颜色。这一张图放进项目报告里,比任何文字都管用。好的优化结果不是用来“惊艳”谁,而是让人一眼看清各种代价之间的真实关系——这才是选址定容这个问题的终极价值所在。