储能选址定容这个方向,我在配电网优化和新能源消纳的项目里扎了有好几年。说实话,早期用纯数学规划方法做,节点一多、约束一复杂,求解时间长得让人怀疑人生;后来转到遗传算法方向,尤其是针对“安装位置、容量、数量可以同时可变”这类落地需求,才真正找到一套工程上可复用的套路。今天就把一套基于改进遗传算法、支持任意设定储能数量的Matlab实现方案,从头到尾拆开讲清楚。
这套方案解决的是这么一类问题:在给定的配电网拓扑结构、负荷分布和潮流运行方式下,确定哪几个节点装储能、每个节点储能按多大容量配置、一共装几套,使得全网在满足电压和潮流约束的前提下,投资与运行的综合经济性最优。它适合刚开始接触储能优化调度的研究生、做配网规划的系统工程师,以及想用Matlab跑通完整算例又不想在求解器选型上折腾的人。
1. 储能选址定容问题:先搞清楚要优化的是什么
1.1 数学模型层面的常规拆分
储能选址定容,英文常叫ESS Planning,中文翻译过来就是“在哪装、装多大、装几个”。很多刚接触这个题目的人容易把问题简化成纯“容量分配”,但实际工程项目里,位置和容量是强耦合的:同一个分布式储能容量,装在馈线末端和装在变电站出口,对网损的改善效果天差地别。所以选址和定容必须放到同一个优化框架里一起求解,这就是所谓的“选址定容联合优化”。
标准的数学模型由一个目标函数和若干约束条件构成。目标函数通常是年综合费用最小化,包括储能设备投资等年值、运行维护费用、网损费用,有时候还会加上电压偏差的惩罚项。约束条件则包括潮流方程约束、节点电压上下限约束、支路电流或容量约束,以及储能本身的功率和容量上下限约束。
我在项目里常用的目标函数简化形式是:
min F = C_loss * (Ploss_after - Ploss_base) * T_year + C_ess * ΣS_i + λ1 * Σ(ΔV_i^2) + λ2 * (违反约束的惩罚项)
其中C_loss是电价,T_year是年运行小时数,C_ess是储能单位容量折算到每年的费用,ΣS_i是安装储能的总容量,λ1和λ2是罚系数。这个目标函数的逻辑很清楚:储能带来的收益必须大于它本身的投资成本,同时电压质量也要兜底。注意,网损费用这里其实是用“改造前后网损差值”来衡量的,而不是绝对网损值,这更符合实际规划业务的评估习惯。
1.2 为什么要强调“可任意设定储能数量”
市面上不少储能定容模型是固定储能数量的,比如代码里写死“本算例安装3套储能”,换个需求就得改编码结构。但实际项目里,“装几套”往往不是预先知道的——可能业主预算只能支持2套,也可能某条馈线节点分散,装4套比装3套更划算。所以我在编码设计时,将储能数量N作为模型输入参数,放在配置文件里,通过调整种群染色体的长度来适应不同规模。
这个功能看着不起眼,但直接决定了算法代码的通用性。固定数量模型用定长染色体就能跑,而可任意设定数量的模型,要么用变长染色体,要么在染色体中预留最大数量的位置、用有效位标记实际安装数量。我在这个项目中采用了后者:预留位置au_max,每个位置附带一个0/1状态位,状态位为0则该组不参与计算。这样既兼容变数量场景,又不需要处理变长染色体的交叉变异兼容问题,程序实现要简单太多。
1.3 为什么选择遗传算法而不是穷举或者传统规划方法
储能选址定容是一个典型的非线性混合整数规划问题。位置变量是离散整数,容量变量是连续实数,潮流约束又是非线性的。穷举法在这类问题上不现实:33节点系统选3个安装位置,组合数就已经过万,每个组合还要做潮流计算,时间成本爆炸。传统梯度类优化算法又对不连续、非凸的离散混合问题无能为力,容易掉进局部最优解。
遗传算法天然适合这种问题,因为它的编码方式可以把整数位置变量、实数容量变量和数量状态位统一封装到一条染色体里,目标函数只需要作为适应度函数被调用,不需要提供梯度信息。改进遗传算法的价值在于,传统遗传算法在这个问题上的收敛速度和稳定性都不够,容易早熟,也容易因为选择压力不够而迟迟不收敛。下面我详细说做了哪些改进。
2. 改进遗传算法:每个改动点都有它的存在理由
2.1 基础遗传算法在选址定容上的三个硬伤
第一,标准遗传算法使用固定的交叉概率和变异概率,种群在迭代初期收敛快,但到后期多样性急剧下降,经常陷入局部最优。传统交叉率一般取0.7到0.9,变异率取0.01到0.1,我在实际调参中发现,同样的参数在储能数量少(2套)时没问题,到了数量多(5套以上)时,晚期种群的染色体高度同质化,基本失去全局搜索能力。
第二,容量变量的实数编码如果直接用二进制编码,会引起巨大的编码长度膨胀。比如容量上限1500 kW,精度要求1 kW,需要11位二进制位,多套储能加在一起,染色体长度飙升,计算效率堪忧。
第三,约束处理不够精细。直接丢弃不可行解的方案在选址定容中效果很差,因为储能容量越大、安装数量越多,不可行解的比例越高,会丢失大量有价值的部分可行解。必须采用罚函数或可行性修复策略。
2.2 对外层遗传算法的四项核心改进
这套代码的改进点可以归纳为四块:精英保留策略、自适应交叉变异、锦标赛选择初筛、模拟退火式Metropolis接受准则。
精英保留策略很好理解:每一代中适应度排名前若干个体不参与交叉和变异,直接复制到下一代。这保证了算法收敛不会回退。我在项目里设精英比为0.1~0.2,效果比设太大导致收敛过快要好。
自适应交叉变异是我做的最重要改动。设计思路是,将代际的种群适应度统计值(最大值、平均值)作为反馈信号,在迭代初期维持较高的交叉率和变异率,以增强探索;当种群趋于收敛(平均适应度接近最优适应度)时,自动降低交叉率、增大变异率,避免陷入局部最优。具体公式我用的如下:
交叉率Pc = Pc_max - (Pc_max - Pc_min) * (f_max - f_avg) / (f_max - f_min)
变异率Pm = Pm_min + (Pm_max - Pm_min) * (f_max - f_avg) / (f_max - f_min)
注意看变异率公式里的符号,它和交叉率是反向的:当种群差异小(f_max - f_avg接近0)时,变异率拉高,增加扰动;当种群差异大时,变异率压低,减小对优秀个体的破坏。这套自适应机制比固定变异率稳定很多,尤其在不同储能数量下切换时,不需要反复手动调参。
第三项改进是锦标赛选择。每次从种群中随机抽取k个个体,取其中适应度最高的作为父本,这样选择压力可控。k取3到4效果不错,k太小会让随机性过强,k太大又会导致种群快速集中退化。
第四项借用了模拟退火的思路。每次交叉变异后生成的新个体,并不无条件进入下一代,而是按一定概率接受劣质解。接受概率表达式为 exp(-Δf/T),其中T是退火温度,随迭代次数线性下降。这设计主要是为了应对那些“暂时变差、但最终能跳出局部最优”的中间解。实测下来,加了这一项后,在包含DG(分布式电源)和储能联合规划的场景里,同时降低网损和电压偏差的效果更为稳定。
2.3 编码方案与染色体结构的设计细节
编码我用了整数+实数的混合编码方案,染色体形式如下:
[ 位置1, 位置2, ..., 位置N; 容量1, 容量2, ..., 容量N; 状态位1, ..., 状态位N ]
位置部分为整数,表示储能接入的节点编号;容量部分为实数,代表对应节点的储能额定容量(单位可以取kWh或kW);状态位为0或1,1表示该储能实际投运,0表示不投运。对于“任意设定储能数量”的需求,读入数量上限N后,程序会自动生成这种三段式染色体。
举个例子,如果你设定最多可安装3套储能,某条染色体的编码可能为: [12, 25, 19; 500.0, 800.0, 680.0; 1, 0, 1]
这条染色体代表的含义是:分别在节点12和节点19安装500 kWh和680 kWh的储能,节点25上的设计虽然预留了,但状态位为0,不参与计算。这样做的好处是让算法有机会同时评估“装2套”和“装3套”的方案(通过状态位变1或0实现),从而真正实现最优储能数量的自动判断,而不是人为固定死。
3. Matlab代码整体架构与核心函数拆解
3.1 整个Matlab工程的模块划分
工程结构上,我将代码划分为六大模块,便于替换和改进:参数设置模块、系统数据读取模块、初始种群生成模块、适应度评估模块、遗传算子模块和结果输出模块。目录结构是这样的:
|-- main.m |-- config_params.m |-- load_power_data.m |-- calc_powerflow.m |-- fitness_eval.m |-- selection.m |-- crossover.m |-- mutation.m |-- plot_results.m |-- data/ | `-- nodes33.matmain.m是统一入口,调一下config_params读取参数,再调load_power_data载入系统数据,然后执行主循环。
3.2 参数初始化代码与含义
config_params.m里面会设置优化基本参数,包括最大允许安装储能数量N_max、容量上下限Cap_min/Cap_max、种群规模pop_size、最大迭代代数max_gen、自适应交叉变异的上下限等。我给的默认参考值如下:
% 遗传算法基础参数 pop_size = 80; % 种群规模 max_gen = 150; % 最大迭代代数 N_max = 4; % 最多可安装的储能套数 N_real = 2; % 本案例实际想评估的套数(可修改) Cap_max = 1000; % 单套储能容量上限(kWh) Cap_min = 50; % 单套储能容量下限(kWh) % 自适应参数 Pc_max = 0.95; Pc_min = 0.65; Pm_max = 0.08; Pm_min = 0.005; % 目标函数权重 weight_loss = 10.0; % 网损费用权重 weight_cap = 1.0; % 储能投资权重 weight_vol = 5.0; % 电压偏差权重这些参数不是拍脑袋定的,我解释一下思路。种群80,迭代150,对付33节点系统足够了,复杂度属于中等。如果系统扩展到200个节点以上,我建议种群150到200、迭代400以上。权重方面,理想做法是用真实电价和储能单价折算后的数量级来定,你如果拿不到真实数据,就先量级设置,后续通过灵敏度分析修正。
3.3 初始种群生成与状态位逻辑
初始种群生成函数中,对于位置向量,我在不重复节点编号的前提下从1到33随机抽取N_max个;对于容量向量,在Cap_min到Cap_max之间均匀随机采样;状态位则按一定概率随机生成0或1,但至少要保证有一个状态位为1,否则这条染色体代表“一个储能都不装”,连可行域都进不去。
这一步需要留意:位置向量里的节点编号应当剔除无法安装储能的节点。比如有些节点是纯联络节点、开关节点,或者已经接了分布式电源的节点,实际工程中往往不允许再装储能,要从候选节点集中删掉。我一般在load_power_data阶段就生成好一个allowed_nodes数组,初始种群生成时直接从allowed_nodes里抽样。
3.4 适应度函数的关键步骤
适应度函数是整个代码的核心,评价一次染色体优劣要做一次潮流计算。潮流计算用的是基于Matpower的牛顿-拉夫逊方法,为了不影响主程序体积,我把潮流计算封装在calc_powerflow.m里。
适应度函数分四步走:
第一步,根据染色体的位置和状态位,确定当前方案中实际参与计算的储能节点和容量。 第二步,在负荷数据上叠加储能充放电功率,形成新的注入功率,重新计算潮流,得到各节点电压和支路潮流。 第三步,计算目标函数值,也就是网损费用、储能投资费用和电压偏差的加权和。 第四步,检查约束,包括节点电压上下限和支路潮流限制,对违反项加上惩罚项。
注意,储能既有充放电状态,在规划阶段并不需要模拟24小时的充放电时序。一般来说,我们假设储能参与调度,主要作用是削峰填谷或者支撑电压,建模时用一日等效或典型日的运行曲线来表达。我在代码里为了简化,将储能节点看成可调的PQ节点,充电时为负荷、放电时为电源,功率因数设为0.9到0.98。
适应度函数示例:
function [fit, info] = fitness_eval(chromo, sys, param) install_pos = chromo.pos(chromo.state == 1); install_cap = chromo.cap(chromo.state == 1); n_ess = length(install_pos); if n_ess == 0 fit = 1e6; % 惩罚大值 info = []; return; end % 计算储能等效注入功率 S_ess = install_cap * 0.25; % 典型系数,取储能用容量25%作为持续输出能力 % 初始化母线注入 P_bus = sys.Pd_bus; Q_bus = sys.Qd_bus; P_bus(install_pos) = P_bus(install_pos) - S_ess .* 0.95; Q_bus(install_pos) = Q_bus(install_pos) - S_ess .* 0.31; % 潮流计算 result = calc_powerflow(sys.branch, P_bus, Q_bus); % 目标函数 Ploss = result.Ploss; VolDev = sum((result.Vm - 1.0).^2); F = param.weight_loss * Ploss + param.weight_cap * sum(install_cap) + param.weight_vol * VolDev; % 约束罚函数 penalty = 0; if any(result.Vm < 0.95) || any(result.Vm > 1.05) penalty = penalty + 10000 * (sum(max(0, 0.95 - result.Vm)) + sum(max(0, result.Vm - 1.05))); end fit = F + penalty; end这段代码虽然只有二十来行,但有几个细节要特别说明:
第一,S_ess取电容量的25%作为持续充放电功率,这个是简化估算,实际做规划需要根据储能倍率(比如0.5C、1C)来确定。0.5C的储能,500 kWh容量对应250 kW输出功率;2小时倍率对应250 kW持续输出。你千万别把容量直接当成功率写进去,那是初学者常犯的错误。
第二,罚款函数里用大常数10000乘以所有违反幅度的总和,而不是简单地加一个固定常数。这样有一个好处:离可行域越远的解,受到的惩罚越大,算法会逐步引导种群朝可行方向演进。
第三,潮流计算如果因为输入功率过大导致不收敛,必须直接返回一个极大值,不要让程序中断。我在calc_powerflow里加了max_iter限制,超过迭代次数就返回失败标志。
3.5 选择、交叉、变异的具体实现
选择部分用锦标赛方法。每轮从当前种群中抽取3或4个个体,挑适应度最好的那个作为父本,直到选出pop_size个父本。
交叉操作需要同时处理位置段、容量段和状态位。因为位置段要求节点不重复,普通单点交叉很容易产生重复节点,这里我用的是“部分匹配交叉(PMX)”的方法,只对位置段做一次映射修复,容量段用算术交叉,状态位用随机交换位。
算术交叉的实现:
alpha = 0.6 + 0.2 * rand; % 随机融合系数 child_cap = alpha * parent1_cap + (1-alpha) * parent2_cap; if child_cap > Cap_max, child_cap = Cap_max; end if child_cap < Cap_min, child_cap = Cap_min; end变量alpha随机取0.2到0.8之间,是为了让子代容量不完全偏向一个父代,增加解的多样性。容量越界后直接裁剪到上限或下限,这是因为储能容量越界并没有任何物理意义,超出部分只会抬高成本、拖低适应度,直接裁剪是最干净的处理。
变异操作同样分三段做。位置段以一定概率随机替换成allowed_nodes中不重复的节点编号;容量段用非均匀变异,也就是说在迭代初期变异步长取大范围,后期大幅缩小变异步长,这能保证前期探索、后期精细搜索。状态位则按很小概率翻转,这个概率我一般设置在0.02到0.05之间,翻太快会让算法在“装几套”这个离散维度上震荡。
4. 算例演示与参数敏感性测试
4.1 33节点标准系统下的配置和结果
我在实际测试中先跑了一个33节点的经典辐射状配电系统,节点电压等级12.66 kV,基础负荷总量约3715 kW加上2300 kvar。储能可选容量范围设置为50到1000 kWh,最多允许装4套,先按固定装2套场景测试算法基线。
系统改造前潮流的初始网损大约是202.5 kW,最低节点电压出现在末端节点18附近,标幺值约0.9038,已经明显越下限。在这个基础上运行改进遗传算法,种群里最优结果在约60代后开始稳定,最终优化结果是:节点18装850 kWh、节点33装715 kWh,网损降到148.6 kW,最低电压抬升到0.9521,正好压到约束限值附近。目标函数值从初始随机方案的约4800降到约3050。
这个结果符合物理直觉:末端节点是电压最薄弱的环节,储能放在末端对电压支撑效果最明显。同时,容量分配虽然没有达到理论最优(因为还有投资成本在权衡),但确实实现了用较小容量解决电压问题的效果。
4.2 允许2套、3套、4套时的对比
随后我对比了可安装套数分别为2、3、4时的优化结果。允许套数越多,优化得到的综合目标函数理论上越低,因为自由度变大了,但每多一套带来的边际收益在快速递减。
从实测结果看:
- 装2套:网损降到148.6 kW,最低电压0.9521
- 装3套:网损降到137.2 kW,最低电压0.9587,目标函数比2套下降约11%
- 装4套:网损降到134.5 kW,最低电压0.9599,目标函数比3套只下降约2.5%
这说明从工程经济性角度,这套系统最优储能数量可能就落在3套左右,4套带来的收益已经可以被投资成本抵消。这个结果也验证了“可任意设定储能数量”的必要性——如果你直接把代码固定成最多2套,你在项目报告中只能交出148.6 kW的方案,但明明3套能跑出137.2 kW的更好结果。
4.3 种群规模、迭代次数与终止判据的实测对比
参数敏感性测试我用三组对照来做的:
- 组A:种群40,迭代100,收敛较快,但结果稳定性差,多次运行最优值波动可达10%
- 组B:种群80,迭代150,结果稳定,波动在3%以内
- 组C:种群200,迭代300,结果略有提升,但耗时是组B的约4倍
因此我建议,如果你只是验证算法正确性,用组A;如果要出论文或工程报告数据,至少用组B;组C只在节点数超过100时才有必要。这里有个更经济的做法,就是把终止判据从固定代数改成“最优适应度连续20代变化小于0.001就提前终止”,实测能节省40%以上的计算时间,而结果几乎无损失。
5. 常见问题与排查技巧实录
5.1 适应度函数不收敛或者震荡极大
这类问题在刚跑通代码时非常常见,原因往往是多个细节叠加:种群规模过小、选择压力过大导致早熟;变异率过高导致后期最优解被反复破坏;或者适应度函数里网损和投资成本两个量级差太远,一个量级是10^3,另一个是10^7,加权后被大数项完全主导。
我排这类问题的顺序是:先检查目标函数的数量级,把网损费用和投资成本的权weight事先归一化;再检查状态位变异概率,如果高于0.1,基本可以确定收敛曲线会乱跳;最后把精英保留比例从0调到0.15左右,看收敛曲线是否明显稳定。
5.2 约束没违反却被“误杀”
很多人在罚函数里写:如果电压越限,就罚10000。问题是,如果所有初始解都越限,惩罚后适应度全部一样大,选择阶段就退化成完全随机选择,算法根本无法启动。
更好的策略是“分级罚函数”,根据越限程度连续施加惩罚值,就像我前面的代码那样,越限越大罚得越重。另外,初始种群生成之前先做一次节点电压扫描,把少数几个严重越限的低电压节点设为必选安装节点,再让算法在其余节点上自由选择,这样可以极大提高初始种群可行率,收敛速度直接升一个台阶。
5.3 运行效率瓶颈与加速思路
如果33节点系统跑一次150代需要几分钟,超过100节点系统一次就要几十分钟,这个性能很多时候不可接受。瓶颈几乎永远是潮流计算。我的提速方法有三个: 一是把潮流计算里的矩阵稀疏化,去掉不必要的完整雅可比矩阵存储; 二是对大量初始种群中的重复或相似染色体做适应度缓存,命中缓存直接返回结果,不重复计算; 三是在改进遗传算法里增加一个粗筛操作,先用线性化近似潮流计算粗筛一批明显不可行的解,只对初筛通过的解调用精确潮流。
创新点特别适用于“任意设定储能数量”的扩展场景。因为当N最大设为10时,染色体总数并不大增,但组合到单个候选节点方案的离散可能性呈指数增加,缓存机制能显著减少重复潮流的次数。
5.4 充放电策略简化带来结果偏差
规划代码里如果直接把储能当成恒功率电源,计算结果往往会偏乐观。真实储能功率输出受SOC(荷电状态)约束,在连续时段内可能无法一直维持额定功率。
我处理这个问题的方法是,在适应度评估中加入时序校验:用一个24典型日曲线,按照储能SOC变化规则模拟一日的充放电过程,如果某时刻SOC越界,则缩减该储能的实际输出功率。这个改动让结果更接近工程实际。如果你不想把问题复杂化,至少要在报告中注明你的分析基于理想的恒功率模型,否则别人按你的结论去做施工设计,很可能发现电压支撑不达标。
6. 一些心得和后续可以怎么扩展
这套改进遗传算法框架我用在很多不同的系统配置上,给我最大体会有三点。
第一点,遗传算法能不能用得好,往往不在于算法本身,而在于你封印了多少物理直觉进去。把候选节点预筛掉、把储能倍率算准确、把罚函数设计成分级式,这些工程性的细节远比把交叉率从0.8改成0.85有用。
第二点,“任意设定储能数量”这个需求看似是简单功能,但它对编码设计和算子操作的灵活度要求不低。状态位的引入解决了这个问题,但它也带来了新的问题:算法可能会把状态位全置0,然后得到一个什么都不装的空解。我后来在初始种群和变异操作中都强制要求有效储能数量不低于1,算是做了一个很实用的补丁。
第三点,这套代码后续扩展方向是很多。可以把遗传算法与电力系统仿真软件接起来做联合仿真,可以加入储能运行策略优化层,做成“规划-运行”双层优化模型,也可以把单目标加权改成真正意义上的Pareto多目标优化。如果你要在自己的研究中继续深入,我建议从双层模型入手,把上层定位容规划、下层做典型日运行优化,这样结果的实际指导意义会好很多。
最后分享一个小技巧:不管你用什么改进策略,一定要记得把随机种子固定下来。我在调试参数的时候被这个坑过——同样的代码、同样的参数,两次运行结果居然差出20%。后来才发现,是初始种群随机性导致的正常波动。固定随机种子之后,每次调试的结果才可复现、可对比。这个小小习惯,能让你排错效率至少翻倍。