资讯动态

改进遗传算法求解储能选址定容问题:Matlab实现与约束设计

发布时间:2026/10/9 12:54:57 来源:尧图企业网站定制
做配电网规划的同行应该都遇到过这样的需求要在几十上百个候选节点里选出几个位置装储能还要定容量、定功率。位置选错了、容量定大了投资打水漂容量定小了削峰填谷、改善电压的作用又发挥不出来。这个问题就是典型的储能选址定容。之前接手过一个Matlab项目最初是用标准遗传算法做的跑了几轮发现结果不稳定要么早熟收敛到一个很差的局部解要么种群多样性下降太快。后来把遗传算法做了针对性改进又加入了“可任意设定储能数量”的灵活接口效果才真正能用到工程参考里。这篇文章就把整个思路和代码实现讲清楚适合正在做配电网储能规划、微电网优化配置或者刚接触改进遗传算法的读者参考。1. 储能选址定容问题到底在优化什么三个主目标与四层约束先说清楚这个问题的数学本质。很多人一上来就写遗传算法代码但适应度函数本身都没定义明白后面全是白费功夫。储能选址定容本质上是一个混合整数非线性规划问题需要同时决策安装位置整数变量、安装容量和额定功率连续或离散变量。1.1 三个主目标投资成本、网损费用、电压质量怎么放进同一个目标函数通常要考虑三个目标但实际工程里我们一般用加权法把它们压成一个单目标。这里有个容易踩坑的地方三个目标的量纲完全不一样投资成本是万元/年网损费用是万元/年电压偏差是个无量纲的相对量。直接相加等于把一百万和百分之一放在一起比谁大那个量级大的必然主导整个优化另一个目标基本形同虚设。我的做法是先分别归一化再加权求和。目标函数可以写成min F w1 * C_inv w2 * C_loss w3 * V_dev其中C_inv是储能年均投资成本包括容量成本和功率成本然后用等年值法折算C_loss是系统年网损费用V_dev是各节点电压偏离1.0pu的累计值。w1、w2、w3是权重系数一般取0.4、0.4、0.2具体要看工程项目更看重哪个经济性指标还是供电质量指标。储能年均投资成本这块很多人都忘了做等年值折算。电池寿命不是一年不能把一次性建设成本直接算进目标函数。正确的做法是把总建设成本按照资金回收系数折算到每一年A P * (r * (1r)^n) / ((1r)^n - 1)P是初始投资总额r是折现率n是储能设备寿命年限。比如一套初始投资300万的储能系统折现率取8%寿命10年资金回收系数算出来是0.149那年均投资成本就是44.7万。如果直接拿300万去和目标函数里的其他项相加整个优化方向就会被投资成本完全绑架最终一定是什么都不安装才是“最优解”。1.2 四层约束潮流平衡、节点电压、储能出力、数量限制约束条件比目标函数更容易被忽略但实际上约束才是这个问题的灵魂。我总结下来至少需要四层约束第一层是潮流平衡约束这是最基础的物理约束安装储能以后系统各节点的注入功率必须满足有功、无功平衡关系。第二层是节点电压约束各节点电压幅值要维持在0.95pu到1.05pu之间。配电网末端馈线经常电压偏低储能所在节点的电压会被抬升但离储能远的节点改善有限所以要做全局校验。第三层是储能自身的运行约束包括充放电功率不能超过额定值SOC荷电状态要在0.1到0.9之间不能让电池过充过放。很多简化模型里只限制功率不限制SOC结果优化出来的是一个永远在放电的“假储能”实际根本没法运行。第四层是安装数量约束也就是题目里说的“可任意设定储能数量”。当我们需要限定整个系统最多安装N个储能时这个约束就变成了选址变量的和等于N。这部分在编码和算子设计上有个很关键的细节后面专门用一章讲清楚。1.3 为什么说这是一个混合整数非线性规划选址变量是0/1二进制定容变量是连续实数潮流方程是非线性方程目标函数里有乘积项这个问题的组合特性决定了它很难用传统数学规划方法直接求解。节点数少的时候可以穷举33节点的配电网如果选3个站址组合数C(33,3)5456种顺着潮流算一遍还行但实际系统动辄上百节点选5个、8个、10个站址组合数直接爆表。启发式算法在这个场景下几乎成为默认选择遗传算法是应用最广的一种。2. 标准遗传算法在选址定容上的三个短板以及针对性改进策略我没打算从零写一个花哨的新算法而是把标准遗传算法做针对性改进。为什么因为标准GA在这个问题上确实会吃瘪但吃瘪的原因非常明确改进空间也很大。2.1 短板一早熟收敛种群在20代之内就失去多样性标准GA最典型的问题就是早熟。选址定容问题的解空间很不规则存在大量局部最优解标准GA前期靠适应度差异来压榨好的个体很快种群里的染色体就长得越来越像。到后面交叉算子基本是在“近亲繁殖”变异概率又设得低种群多样性格局崩了算法就困在局部最优点出不来。我的改进方案是自适应交叉变异概率。核心思路是让适应度高于种群平均值的个体交叉概率和变异概率都小一些保护它们适应度低于平均值的个体交叉概率和变异概率大一些促它们进化。这样可以显著减缓早熟发生。实测在一个IEEE 33节点模型上标准GA大概在第18代就收敛不动了改进后能持续探索到第60代以上。2.2 短板二二进制编码下的无效交叉把好不容易找到的好位置组合打散了标准GA用二进制编码时单点交叉特别容易把两个父代的好基因“拦腰截断”。比如父代A已经找到“节点8、节点21装储能容量都是500kWh”这个优秀组合父代B找到了“节点8、节点3装储能容量是300kWh”交叉点如果落在选址段的中间很可能子代变成了“节点8、节点3”把21这个好位置搞丢了而且这种破坏是随机不可控的。我在这里做了两个调整一是对选址段改用多点均匀交叉每个基因位独立决定来源于哪个父代保留优秀位置组合的概率更高二是引入精英保留策略每一代的最优个体不参与交叉变异直接复制进下一代。这个操作看似简单但能把两个父代都具备的优良模式锁住这是遏制早熟和收敛到全局最优的关键。2.3 短板三约束处理太粗糙大量个体在潮流计算阶段就被判“死刑”标准遗传算法不考虑约束全靠罚函数在适应度函数后端做缓冲。问题是变异的基因位多了很容易产生“完全不合物理规律”的个体比如容量编码是负数、SOC越界、潮流不收敛。如果只是简单加罚函数这些个体罚完以后依然留存在种群中占用后代名额拖慢收敛。我的改进是在算子层面对非法个体直接做修复而不是事后罚。比如SOC越界就裁剪到边界值选址段1的数量不等于设定储能数量就直接用修复算子补足或删减潮流不收敛的个体在适应度函数中直接给一个极大的罚值让它自动被淘汰。这里的关键思路是“能在算子层修好的绝不拖到适应度函数里罚”只有那些算子无法修复的严重不可行解才交给罚函数兜底。2.4 改进组合最终是什么样综合下来我的改进遗传算法包含了四项主要机制锦标赛选择每次随机抽3-5个个体取最优者进入下一代选择压力可调避免适应度最高的个体一直霸占所有后代名额。自适应交叉变异概率根据个体适应度与种群平均值的差距动态调整算子概率。精英保留策略每代最优个体直接保送保证算法单调收敛。混沌初始化用Logistic混沌映射生成初始种群替代纯均匀随机分布让初始解更均匀地散布在解空间里减少从同一起点出发的“扎堆”问题。这四项改进的实现都不复杂累计多写大概100行Matlab代码但对结果稳定性的提升非常明显。后面算例章节会给出对比数据。3. 可任意设定储能数量的关键技术染色体编码与修复算子设计这是整个代码里最值得讲清楚的部分。项目标题里单独把“可任意设定储能数量”拿出来说说明实现者在这一点上下了功夫。我实际测试下来这个功能的关键在于染色体编码不是写死的初始化、变异、修复算子都要围绕“选址段恰好有N个1”来做文章。3.1 染色体三段式编码选址段、容量段、功率段我把一条染色体分成三段拼接选址段长度等于配电系统节点总数比如33节点系统就是33位二进制数组1表示该节点安装储能0表示不安装。容量段长度等于设定的储能数量N存放每个储能站的额定容量。功率段长度等于设定的储能数量N存放每个储能站的额定功率。这个编码有个隐性约定容量段和功率段的第k个元素对应的就是选址段里第k个非零位所指的节点。也就是说染色体内隐含了“位置-容量-功率”的对应关系修改时必须保持三段同步否则会出现“容量数组和位置数组对不上号”的混乱。这里有一个编码上的细节标准做法是容量段、功率段直接用实数编码代表从100kWh到1000kWh之间连续取值。但实际工程里储能容量通常是离散规格档位比如50kWh一档。所以我在代码里有个参数叫capacity_step如果设为50编码阶段就把连续变量取整到最近的50的倍数这样优化出来的结果能直接对应市售产品型号。3.2 怎么保证“恰好选了N个安装点”初始化与变异的配合初始化种群这一步很多人是随机生成二进制串预设有1的概率。问题在于这样生成的选址段1的数量是个随机数不一定等于N。然后通过罚函数强行逼着算法往“选N个”的方向靠效果很差算法要话很多代才学会“恰好选N个”。我的做法是在初始化时就保证精确先随机选N个互不相同的节点编号把这几个位置置1其他位置全部置0。这样生成的每一个个体选址段天然满足安装数量约束算法从一开始就不用学习这个约束。变异算子这里也有名堂。如果直接用标准的位翻转变异——随机挑一个基因位1变0、0变1——那么N一旦变了约束就被破坏了。一个位置从0变成1系统数量就多了一个另一个位置从1变成0数量就少了一个。我的做法是设计了一个“成对变异的交换算子”在选址段里随机挑一个为0的位置和一个为1的位置然后同时翻转——0变1、1变0。这样选址段中1的总数N恒定不变既引入了变异扰动又保住了数量约束。这个小小的设计是“可任意设定储能数量”能稳定工作的核心。3.3 修复算子非法个体怎么拉回来即使有了上面的设计交叉操作中还是可能破坏选址段的“恰好N个1”的性质特别是多点交叉后两个子代的1数量都会偏离N。我在交叉之后紧接一个修复函数check_and_repair统计选址段中1的数量如果1的数量大于N就随机把多出来的1改成0如果1的数量小于N就随机把位置为0的地方补成1。这个修复本质上是把种群中所有非法个体一次拉回约束域让遗传算法全程只在可行域内搜索。这是我认为比罚函数法更高效的地方罚函数让算法自己摸索要花几十代修复算子是一次到位。3.4 参数传入与代码接口设计代码接口部分给使用者留的入口非常简单。主函数只需要改两个参数node_loads节点负荷数据数组storage_num想安装的储能数量直接传整数比如3、5、8。storage_num一变初始化、容量段、功率段的长度全都跟着变内部的交叉变异范围自动适配。我封装了一个函数叫init_chromosome(pop_size, storage_num)内部根据storage_num动态生成染色体三段结构不需要使用者改任何编码逻辑。4. Matlab代码实现全流程拆解主循环、潮流计算与结果输出在实际写代码时我建议按下面这个顺序搭骨架先从主函数开始再逐层细化子函数。4.1 主函数结构从数据输入到结果输出主函数分成五个模块参数区、数据区、初始化种群区、遗传循环区、结果输出区。大概框架如下% 主函数 main_storage_location.m clc; clear; rng(0); %% 参数区 pop_size 50; % 种群规模 max_gen 150; % 最大迭代次数 pc_base 0.85; % 基础交叉概率 pm_base 0.05; % 基础变异概率 storage_num 3; % 要安装的储能数量可任意修改 price_elec 0.65; % 电价 元/kWh %% 数据区IEEE 33节点配电网数据 [bus_data, branch_data] load_case33(); node_total 33; %% 初始化种群 pop init_population(pop_size, node_total, storage_num); fitness zeros(pop_size, 1); %% 遗传主循环 for gen 1:max_gen for i 1:pop_size fitness(i) calc_fitness(pop(i,:), bus_data, branch_data, ... storage_num, price_elec); end elite pop(find(fitness min(fitness), 1), :); % 精英保留 avg_fit mean(fitness); max_fit min(fitness); new_pop zeros(pop_size, 2*storage_num node_total); for i 1:pop_size % 锦标赛选择 parent1 tournament_select(pop, fitness, 3); parent2 tournament_select(pop, fitness, 3); % 计算自适应概率 pc adaptive_pc(fitness(i), avg_fit, max_fit, pc_base); pm adaptive_pm(fitness(i), avg_fit, max_fit, pm_base); % 交叉与变异 child crossover(parent1, parent2, pc, node_total, storage_num); child mutation(child, pm, node_total, storage_num); new_pop(i, :) child; end new_pop(1, :) elite; % 精英保送 pop new_pop; fprintf(第%d代最优适应度: %.4f\n, gen, min(fitness)); end %% 结果输出 [best_fit, idx] min(fitness); best_chrom pop(idx, :); output_solution(best_chrom, node_total, storage_num);这里有个实际建议在主循环里加上绘图功能每代记录最优适应度迭代结束后plot出来。不要等到全部跑完再画因为GA一旦早熟收敛曲线是会非常明显的“一条平线”你要能实时看到它是不是真在进化。实测中经常遇到前30代下降明显后面100代几乎不动的情况这时候就该想想是不是改进策略没起作用。4.2 适应度函数里的潮流计算为什么用前推回代法适应度函数要算网损和节点电压这就要解潮流。配电网是辐射状结构用牛顿-拉夫逊法是杀鸡用牛刀。我这里用的是前推回代法Backward/Forward Sweep它对辐射状配电网收敛性好、快关键是代码短适合在遗传算法循环里被反复调用上万次。前推回代的核心流程就三步从馈线末端向首端前推根据各节点负荷和支路功率逐段计算支路上的功率分布从首端向后回代根据已知的首端电压和支路功率逐段计算各节点电压反复迭代直到前后两次计算的功率差小于误差阈值。装储能之后储能所在节点相当于一个“负的负荷”在高峰期放电时注入有功功率充电时从电网吸收功率。我在前推计算之前先做一步处理把储能的充放电功率叠加到对应节点的负荷上然后再走标准的前推回代流程。适应度函数结构大致这样function fitness calc_fitness(chrom, bus_data, branch_data, storage_num, price) % 解码染色体 [location, capacity, power] decode_chrom(chrom, bus_data, storage_num); % 计算潮流 [loss, volt_dev, converged] backward_forward_sweep(bus_data, branch_data, location, capacity, power); if ~converged fitness 1e10; % 潮流不收敛极大罚值 return; end % 目标函数 annual_cost cal_annual_cost(capacity, power); loss_cost loss * 8760 * price; weight [0.4, 0.4, 0.2]; fitness weight(1) * annual_cost/max(annual_cost, 1) ... weight(2) * loss_cost/max(loss_cost, 1) ... weight(3) * volt_dev; end4.3 罚函数设计的几个细节罚函数不是随便给个大数就行它直接影响收敛行为。我的经验是分三个等级轻微违规SOC越界少于5%给较小罚值相当于警告中等违规电压在0.92-0.95之间给中等罚值把个体往可行域里推严重违规潮流不收敛、储能功率超过线路传输极限给1e10级别的极大罚值等于直接淘汰。这样做的好处是种群中保留了一部分“接近可行但略越界”的个体它们携带了有用的基因信息直接全死会把算法逼进死胡同。4.4 结果输出模块最终输出需要包含这些内容最优选址节点编号、对应每个节点的储能容量和功率、安装前后系统网损对比、安装前后电压最低点对比、以及收敛曲线图。我在output_solution函数里还顺带生成一张柱状图把各节点的电压改善幅度可视化。交流项目时这张图比目标函数数字本身更能说明问题。5. 算例实测与参数调校以33节点配电网为例下面的实测数据都是在IEEE 33节点标准配电网模型上跑出来的。这个系统本身是配电网规划研究中最常用的验证模型一共有33个节点、32条支路根节点电压12.66kV总负荷大约3715kW加2300kvar初始状态下系统网损约202kW末端节点电压最低约0.903pu——注意这个初始电压其实已经偏低了这也是为什么需要装储能来支撑电压的原因。5.1 算例设置与对照组设计为了检验“改进”到底改进在哪我把方法设了三组对比方案A不装储能单纯看初始网损和电压方案B标准遗传算法交叉概率固定0.9、变异概率固定0.01单点交叉、位翻转变异罚函数处理约束方案C本文改进算法自适应交叉变异、锦标赛选择、精英保留、混沌初始化、修复算子。储能参数设置如下容量候选范围100-800kWh功率范围50-400kW储能效率92%充放电深度上限90%折现率8%寿命10年。5.2 参数怎么定种群规模、迭代代数、交叉变异概率的取舍参数选择上我踩过不少坑。种群规模如果设太小比如10-20选择、交叉、变异的随机性都覆盖不了复杂解空间算法基本靠运气。设到50-80会比较平衡33节点系统配50个个体跑150代单次求解时间在2-5分钟级别可以接受。交叉概率是另一个关键。设太高种群的优良结构被反复打散设太低新个体生成速度太慢。我实测推荐基础交叉概率pc_base取0.8左右基础变异概率pm_base取0.05左右。但是关键的是要用自适应调整——前30代让变异概率偏大帮助探索后30代让变异概率减小帮助收敛。自适应里的缩放系数根据个体适应度和种群平均值的比值来决定代码很简单function pc adaptive_pc(fit, avg_fit, best_fit, pc_base) if fit avg_fit pc pc_base; else pc pc_base * 0.5; % 优秀个体保护 end end5.3 收敛过程与结果解读我实际跑的数据大约是这样各套随机种子下略有差异但趋势一致方案最优网损/kW网损降幅电压最低点/pu目标函数值A 不装储能2020%0.9030.086B 标准GA装3台16816.8%0.9320.062C 改进GA装3台15921.3%0.9410.051改进算法在网损和电压两方面都优于标准算法。更关键的差异在指标的稳定性我把同一组参数连续跑10次标准GA最优结果和最差结果的差异能到30%以上改进后差异基本控制在5%以内这个稳定性对工程参考来说甚至比单次最优值更重要。5.4 不同储能数量下的结果对比这个项目的亮点功能就是任意设定储能数量我专门做了储能数量从2台到6台的对比测试装2台储能网损下降约12%投资最少装3台储能网损下降约21%目标函数最优装4台储能网损继续下降约25%但投资成本增长更快目标函数反而略升装5台以上网损降幅开始递减系统已经有“消化不了”的迹象投进去的每一块钱产生的边际效益明显减弱。这个U型趋势说明储能数量不是越多越好而我们的代码框架允许规划人员快速尝试不同数量观察目标函数拐点从而找到最佳配置方案。如果每次换数量都要改一遍代码结构那这个“试数量找拐点”的工作就会很痛苦所以我把storage_num做成参数的意义就在这里。6. 调参避坑实录六个我在实际调试中踩过的坑最后这部分是纯实操经验。我把调试过程中最典型的六个坑列出来每个都是能直接影响代码能不能收敛、结果合不合理的问题供大家参考。6.1 电压越限罚函数权重设太小算法直接无视了电压约束第一次跑出来最优解里系统最低电压0.89pu明显越限了。查原因发现罚函数权重才设了1e3而网损费用和目标函数值都在1e5级别罚函数那点“痛感”算法根本感知不到。解决办法是把罚函数权重按目标函数的数量级去匹配比如罚函数乘到1e6。原则很简单罚值至少要比正常目标函数值大10到100倍算法才会真正“敬畏”约束。6.2 交叉概率设太高种群好不容易出现的优良模式反复被打乱这个坑有趣。交叉概率0.95的时候收敛曲线前期下降很快但到了40代左右就僵住了。我把每一代的精英个体基因打印出来检查发现第15代已经找到了一个很不错的布局但每一代交叉都会把它打破精英保留虽然保住了它本身却无法阻止下一代其他个体丢失这个模式。后来把交叉概率降到0.8并加上自适应策略结果好多了。6.3 初始种群全随机生成导致前几代有一半个体潮流不收敛随机生成的容量和功率组合经常超出系统承受范围潮流前推回代迭代几百次都收敛不了。这些个体在适应度函数里全被给了1e10的罚值表面上没影响最优解但实际上一代里一半个体等于失效选择算子的可用基因池大幅缩水。解决办法是在初始化时给一个约束容量和功率的初始值限制在合理区间内并有意识地让前几代的个体偏向小容量、低功率保证潮流的可行性。6.4 变异算子破坏了“恰好N个1”导致安装了错误数量的储能这个坑在加上“可任意设定储能数量”之后彻底暴露了。一开始我用标准位翻转变异明明设定了storage_num3解出来的染色体却经常有4个选址位是1然后再靠罚函数去惩罚——算法绕了一大圈才学会保持数量约束。后来就是第3章讲的那个“成对变异”方案直接从操作层面保证1的数量不变再也没出现过这个问题。6.5 罚函数设得过大正常个体和轻微违规个体的适应度差异被完全淹没前脚刚说要罚狠一点后脚就发现罚太狠也有问题。罚到1e12以后所有轻微违规个体的适应度都被压在1e12级别而正常个体是1e4级别两边的适应度差异被完全抹平选择算子根本分不出好坏。这时候算法进入“摆烂”状态——反正都是罚值随机选一个就行了。我的经验是罚值要有层次1e6级别罚电压越限1e10级别罚潮流不收敛而不是一刀切全罚到1e12。6.6 网损费用和投资成本量纲不一致目标函数形同虚设有个同行朋友拿我代码去跑说优化出来永远倾向不装储能。我看了一下他把容量成本4000元/kWh直接乘以总容量得到几百万的一次性投资再直接跟年网损费用十几万相加投资成本完全碾压目标函数当然偏向少装储能。200万的一次性投资除以10年寿命等效年成本其实只有20万左右量纲对齐后投资和网损才是可以彼此权衡的关系。建议做等年值折算这是个非常基础但也非常容易错的细节。7. 一点个人体会做了这个项目之后我最大的体会是遗传算法的改进不一定非要追求“听起来很高端”的算子变换最有效的往往是把约束处理从“罚”变成“修”把数量约束做进编码里让算法从第一代开始就在可行域内搜索。这套代码框架目前在我这边用于配电网规划方案的初步比选后续我还打算加入时序运行模拟把储能每天的充放电策略也考虑进去。如果只是做科研验证或者课程项目建议先在小系统上把收敛曲线调顺再上大系统——不要一上来就在200节点模型上调试那样只会把时间浪费在无意义的等待里。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑