
配电网里加光伏前几年大家的想法很简单——有地方能装装了就完事。这几年分布式光伏渗透率一上来问题就藏不住了午间光伏大发时功率倒送、末端节点电压被抬得很高、变电站变压器过载甚至出现大面积弃光。储能是解决这些问题的关键手段但储能系统的投资不便宜一个100kW/200kWh的储能柜算上PCS、BMS和施工轻轻松松几十万块。正因为钱花得多才更要算清楚储能到底应该装在哪些节点、装多大容量平时怎么充放电才最划算。这就是标题里“选址定容与运行调度联合优化”要解决的问题。这篇文章写给正在做配电网规划、分布式光伏项目设计、储能系统方案的研究生和工程师。我会把一个完整的双层优化模型拆开讲从为什么必须用双层、数学模型怎么建到粒子群算法怎么编码、参数怎么调再到基于Matlab的实操流程和踩坑记录尽量做到你拿过去就能照着复现而不是看完一堆公式还是不知道从哪下手。1. 先拆问题为什么选址定容和运行调度必须放一起做1.1 光伏高渗透率下配电网的三大典型痛点分布式光伏接入配电网后最烦人的现象基本就三类。第一类是功率倒送。中午光照最强的时候分布式光伏满发但本地负荷可能正处于低谷比如工业园区的午休时段办公楼的周末时段。光伏出力用不完剩余功率只能顺着馈线往上一级变电站倒送。倒送功率一旦超过变压器允许的反向输送能力变压器就要面临过载保护动作整个供电区域都得跟着遭殃。第二类是电压越限。配电网的电压调节手段本来就有限靠的是有载调压变压器和并联电容器都是慢速设备。光伏出力波动快光伏大发时线路末端电压被硬生生顶起来很容易超过1.07倍额定电压也就是国标允许的电压上限。电压一旦越限逆变器会主动降额光伏电站被迫少发这就是所谓的电压约束弃光。第三类是弃光。除了电压越限导致的被动降额还有调度侧给的发电空间不够光伏想发不让发。在配电网层面弃光就是实实在在的经济损失。储能在这三个场景里都能派上用场。光伏大发的时候充电相当于给线路“泄压”减少倒送和电压抬升。晚高峰再放电替变电站分担一部分负荷同时利用峰谷价差赚一笔。所以储能的角色很清楚问题是装哪、装多少、怎么运行这仨问题互相耦合。1.2 顺序优化为什么容易“省钱买罪受”很多项目一开始走的都是顺序优化的老路先算负荷和光伏的时序曲线拍一个储能容量出来再根据典型日曲线设计充放电策略。听着合理但有隐患。比如规划阶段假设储能每天做两充两放满充满放算出来的回收期很好看。实际跑起来之后发现因为负荷曲线和光伏出力曲线没那么匹配储能一天只能完成一次有效循环等效利用小时数直接腰斩投资回收期翻倍。反过来如果你先把运行策略定死了再去找安装位置和容量很可能发现某个节点根本没有足够的充电空间或者放电消纳能力策略执行不了。这就是顺序优化的死穴规划方案影响运行空间运行结果反过来决定规划方案的经济性两者必须同时考虑。1.3 双层优化的本质设计师和物业的博弈我用个比方能帮你快速理解双层优化。上层模型像一个建筑设计师负责决定楼盖在哪、盖多高、用多少钢筋。下层模型像一个物业经理负责在楼盖好之后按水电价格和入住率去安排空调、照明怎么开最后把每月的电费账单交回给设计师。设计师一开始不知道哪种方案电费最低只能先凭经验出几个候选方案让物业算账。物业算完账把电费反馈给设计师设计师根据反馈改方案再让物业重新算。来回迭代直到最后找到一个“建设成本运行电费”总和最小的方案。对应到我们的问题里上层做的是选址定容决策变量是“在哪些节点装储能、装多大功率、配多少容量”。下层做的是运行调度决策变量是储能在每个时间段的充放电功率、光伏的实际出力、向上级电网的购电量。上层每提出一个候选配置下层就针对这个配置去优化典型日的运行策略返回最小运行成本。上层再把所有候选配置的投资费用和运行费用加起来用粒子群算法继续搜新的配置。跑完设定的迭代次数之后输出的就是兼顾投资和运行的全局最优方案。2. 双层优化模型到底怎么建2.1 上层模型选址定容的数学表达上层模型的目标函数是最小化整个规划周期内的等年值总成本包括三块储能投资折算到每年的费用、年运行维护费用、以及年综合运行费用下层回传的结果。投资等年值这块要用到资金回收系数。假设储能系统的单位功率成本是Cp元/kW单位容量成本是Ce元/kWh单个候选节点的投资成本就是Cp乘以额定功率加上Ce乘以额定容量。把所有候选节点的投资加起来再乘以资金回收系数就得到等年值投资成本。资金回收系数的公式是CRF r * (1 r)^n / ((1 r)^n - 1)其中r是折现率一般取6%到8%n是储能系统的寿命年限磷酸铁锂储能系统通常按10年算。比如你用8%折现率、10年寿命算出来的CRF大约是0.149意思是一笔一次性投入摊到每一年相当于总投资的14.9%。上层模型的决策变量有两类。一类是0-1变量表示第i个候选节点是否安装储能。一类是连续变量表示该节点储能的额定功率和额定容量。这里有个常见的约束额定功率和额定容量必须同时大于0或者同时为0不能出现只有容量没有功率这种诡异配置。另外还要加上总投资预算约束、单个节点的最大安装容量约束、以及整个配电网储能渗透率约束。渗透率约束在工程里很常见一般会限制储能总功率不超过光伏总装机的一定比例防止过度配置。2.2 下层模型运行调度的约束和细节下层模型的目标函数是最小化典型日的运行成本主要包括向上级电网购电的费用、弃光惩罚费用、以及储能运行导致的网损费用。约束条件这就要细说。首先是最基本的节点功率平衡约束配电网的DistFlow潮流模型在这里特别好用。简单写一下对于每条支路ij有功功率和无功功率要满足如下关系P_ij - sum(P_jk) - r_ij * I_ij^2 P_j_load - P_j_pv - P_j_dis P_j_chQ_ij - sum(Q_jk) - x_ij * I_ij^2 Q_j_loadU_j^2 U_i^2 - 2 * (r_ij * P_ij x_ij * Q_ij) (r_ij^2 x_ij^2) * I_ij^2看起来有点复杂但你只需要知道它的物理含义线路上的有功和无功要时刻守恒节点电压由上下游的功率流决定。这个方程是非线性的实际求解时一般会用二阶锥松弛把它转成凸优化问题Matlab里用YALMIP工具箱写起来很顺手。然后是储能自身的约束。每个时段的充放电功率不能超过额定功率荷电状态SOC要在安全区间内一般取0.1到0.9。SOC的递推关系是SOC(t1) SOC(t) eta_ch * P_ch(t) * dt / E - P_dis(t) * dt / (eta_dis * E)这里eta_ch和eta_dis分别是充电和放电效率锂电池一般取0.95左右。dt是时间步长如果做24小时优化每小时一个点dt就是1小时。还有两个容易被忽略的约束。一个是SOC的初值要和终值一致否则储能会在优化里偷偷“免费放电”算出来的运行成本偏低结果不真实。另一个是变压器容量约束从上级电网购入的功率不能超过主变压器额定容量。2.3 上下层模型是怎么咬合的上层给下层传一组参数储能的安装位置、额定功率、额定容量。下层收到之后把这些参数代入运行优化模型求解出典型日24个时段的充放电功率、购电功率等运行变量然后返回给上层三个关键指标典型日最小运行成本、典型日网损电量、弃光量。上层拿到这三个指标之后把典型日的运行成本折算成全年运行成本与投资等年值相加得到一个候选配置的总成本。然后粒子群算法根据这个总成本去评价当前粒子好不好更新个体最优和全局最优再继续生成新的候选配置。有一点要说明这里的“双层”不是把两个优化问题简单套在一起而是上下层有明确的主从递阶关系。下层优化必须以上层给定的参数为前提上层的目标函数必须包含下层最优运行的结果。只有这样规划结果才真正考虑了运行层面的可行性。3. 粒子群算法实现的关键细节3.1 为什么选粒子群而不是梯度算法选址定容是一个混合整数非线性优化问题目标函数不连续、不可导带有0-1整数变量传统的梯度下降类算法根本无从下手。粒子群算法的好处在于它不需要目标函数的梯度信息只要能把候选方案映射成一个适应度值就能跑。而且粒子群算法的实现逻辑在启发式算法里算简单的。每个粒子代表一个候选方案粒子在搜索空间里飞飞的方向受两个因素影响一个是粒子自己历史上找到过的最好位置一个是整个种群找到过的最好位置。这个机制像一群鸟在找食物每只鸟既凭自己的记忆又参考同伴的信号慢慢收敛到食物最多的地方。和遗传算法对比粒子群不需要编码解码、不需要选择交叉变异那套操作参数也更少调起来快。对于双层优化这种跑一次很费时间的场景粒子群的收敛速度优势就很明显了。3.2 编码设计粒子与储能配置怎么对应编码是整个算法能不能收敛的基础。我在做IEEE 33节点配电网的算例时候选储能节点一般选6到10个比如选择馈线末端和负荷重的节点。每个粒子的位置向量长度等于候选节点数的两倍。前半段用来表示“这个节点装不装储能”。粒子群算法本身处理的是连续变量所以前半段每个维度是一个0到1之间的实数用一个阈值函数判断比如大于0.5就认为该节点安装储能否则不装。我建议你在解码时不要用严格的0-1映射可以考虑按概率解释比如0.8对应安装倾向更强。后半段用来表示“装了多大容量”。这一段的维度范围根据实际工程预设比如单个储能点容量上限是500kWh那这个维度的取值范围就设成[0, 500]。解码之后还需要检查粒子对应的配置是否违反约束比如总安装容量是否超过预算。如果违反直接给这个粒子一个很大的适应度惩罚值让它没有竞争力自然淘汰。3.3 参数配置经验值直接抄粒子群参数的选择说实话没有绝对最优但有非常成熟的默认区间。种群数量N取30到50够用迭代次数取100到200。如果你为了赶时间80代也行但结果稳定性差一点。惯性权重w建议从0.9线性递减到0.4。初始阶段w大粒子飞得快利于全局搜索。后期w小粒子飞得慢利于局部精细搜索。这个递减策略比固定w效果好得多实测下来收敛速度能提升30%以上。加速常数c1和c2都取2.0这是论文里最常见的取值表现均衡。最大速度Vmax设为变量范围的10%到20%。比如容量维度范围是500那Vmax就设成50到100防止粒子飞过头。一个容易被忽视的细节是安装标志位和容量维度的取值范围不同收敛速度会有差异。我建议你对粒子每一维的速度分别设置上限甚至可以考虑对位置向量做归一化处理让所有维度都在[0, 1]范围内解码时再还原成实际值。这样算法稳定性好很多。3.4 约束处理的技巧罚函数怎么罚才不飘粒子群处理约束一般不做复杂的约束处理直接上罚函数。罚函数法的核心思想是把违反约束的程度折算成罚项加进目标函数让不可行解的适应度变差。关键在罚系数的选取。罚系数太小大量不可行解混进来算法在不可行区域里瞎转悠。罚系数太大目标函数被罚项主导粒子即使在可行区域内也会因为微小的约束违反被过度惩罚导致过早收敛到局部最优。我实践下来一个还行的做法对于电压越限罚系数从0.001开始每次增加10倍跑几组小规模算例看目标函数变化选那个刚好能保证所有最优解都满足约束的最小值。这个方法要单独试但效果确实比拍脑袋定系数靠谱。对于潮流不收敛的情况直接给一个非常大的常数罚值比如1e8这类方案基本就失去竞争力了。4. 基于Matlab的实操流程从数据到结果4.1 测试系统与数据准备配电网规划领域最经典的测试系统是IEEE 33节点系统我把这个系统的配置说清楚方便你直接跑通流程。IEEE 33节点系统基准电压12.66kV基准功率10MVA总负荷大概3715kW加上2300kvar。系统首端节点1通过变压器接上级电网其余节点都是PQ节点。在这个系统上我建议这样布置分布式光伏在节点18、22、25各接入一组400kW的光伏总装机1200kW。这三个节点分别在三条馈线的末端附近电压支撑弱最能体现光伏接入后的电压问题。储能候选节点不要选太多选6个就够了比如节点8、12、18、22、25、33。这些节点要么在负荷中心要么在馈线末端要么是大光伏接入点工程上最有可能装储能的位置。每个候选节点储能容量上限设为600kWh功率上限设为150kW。数据这一块光伏出力和负荷的时序曲线是核心。出力的形状可以用典型日曲线比如夏季晴天的光伏出力从早上6点开始爬升中午12点达到峰值下午5点归零形状像一个钟形。负荷曲线则要分工业、商业、居民类型别拍脑袋瞎填。如果你手上没有实测数据可以从一些公开的开源数据集里找相近地区的典型日曲线或者用HOMER软件生成的典型负荷曲线。光伏功率预测如果做短期优化还可以接超短期预测平台的接口但作为规划阶段的模型验证用典型日曲线就够了不用一上来就把问题搞复杂。4.2 双层求解框架怎么搭整个求解框架分三层主程序、上层粒子群、下层运行优化。主程序负责初始化参数、循环调用、收集结果。上层粒子群负责生成候选配置并更新种群。下层运行优化负责计算单个候选配置的适应度。我建议把下层运行优化单独封装成一个函数输入是储能配置参数输出是运行成本和惩罚项。这样上层粒子群每一次适应度计算只需要调用这个函数代码结构清爽也方便后续用并行计算加速。下层运行优化的求解器选择上我的建议是用YALMIP建模底层求解器根据问题的规模决定。如果配电网规模小、时间段少用fmincon也能跑。如果问题规模大建议用cplex或gurobi求解速度能快一到两个数量级。特别是当你需要跑全年8760小时的运行优化时求解器的差距非常明显。4.3 核心代码模块实例先看粒子群主循环这段代码是整个算法的主心骨。%% PSO主循环 N_pop 40; % 种群数量 MaxIter 100; % 最大迭代次数 dim 2 * N_candidate; % 候选节点数的两倍 % 初始化位置和速度 pos rand(N_pop, dim); pos(:, N_candidate1:end) pos(:, N_candidate1:end) * cap_max; vel zeros(N_pop, dim); % 初始适应度 for i 1:N_pop fitness(i) calFitness(pos(i,:), sysData); end pbest pos; pbest_fitness fitness; [gbest_fitness, idx] min(fitness); gbest pos(idx,:); %% 主循环 for t 1:MaxIter w 0.9 - 0.5 * t / MaxIter; for i 1:N_pop vel(i,:) w * vel(i,:) ... 2.0 * rand * (pbest(i,:) - pos(i,:)) ... 2.0 * rand * (gbest - pos(i,:)); pos(i,:) pos(i,:) vel(i,:); % 边界处理 pos(i,:) max(pos(i,:), 0); pos(i, N_candidate1:end) min(pos(i, N_candidate1:end), cap_max); % 适应度更新 fitness(i) calFitness(pos(i,:), sysData); if fitness(i) pbest_fitness(i) pbest(i,:) pos(i,:); pbest_fitness(i) fitness(i); end if fitness(i) gbest_fitness gbest_fitness fitness(i); gbest pos(i,:); end end end然后是适应度计算函数也就是下层运行优化的入口。function totalCost calFitness(x, sysData) % 解码判断哪些节点安装储能容量多大 installFlag x(1:N_candidate) 0.5; cap x(N_candidate1:end) .* installFlag; % 如果总容量为0整年运行成本就按无储能方案算 if sum(cap) 1e-6 runCost baseRunCost(sysData); else runCost runOptimization(cap, sysData); end % 投资等年值 运行成本 invCost sum(investEqAnnual(cap, installFlag)); totalCost invCost runCost penalty; end重点在runOptimization这个函数我用YALMIP写一个简化版。这个版本不做二阶锥线性化只求解LP但你可以看到模型主体长什么样。function runCost runOptimization(cap, sysData) T 24; % 24小时 ess_power sysData.ess_power_rated; % 每节点储能额定功率 % 定义变量 P_buy sdpvar(1, T); % 购电功率 P_ch sdpvar(N_c, T); % 充电功率 P_dis sdpvar(N_c, T); % 放电功率 SOC sdpvar(N_c, T); % 荷电状态 % 目标购电费用 网损 弃光惩罚 obj sum(P_buy .* sysData.price) ...; % 约束功率平衡、SOC递推、上下限 Constraints []; Constraints [Constraints, SOC(:,2:T) SOC(:,1:T-1) ...]; Constraints [Constraints, 0 P_ch ess_power * repmat(cap0,1,T)]; Constraints [Constraints, 0 P_dis ess_power * repmat(cap0,1,T)]; Constraints [Constraints, sysData.SOC_min SOC sysData.SOC_max]; optimize(Constraints, obj); runCost value(obj); end这段代码里有一点要注意储能功率约束那块我用了cap0来屏蔽未安装节点的充放电变量否则这些节点也会参与优化会得出一些不存在的储能出力。4.4 结果怎么分析才能体现双层优化的优势跑完算法之后一定要做对比。我建议至少跑三个方案方案一是不装储能方案二是顺序优化也就是先定一个容量再优化运行方案三是双层优化。这样对比才有说服力。我用一个示意性的结果说明问题数据不一定精确但趋势是真实的。不装储能时年网损约180MWh弃光率约5%末端电压越限的节点有3个。顺序优化时储能装在一个集中位置年网损降到150MWh弃光率降到2%但因为配置不合理储能年循环次数只有180次投资回收期长达9年。双层优化时储能被分散安装到3到4个节点年网损降到130MWh弃光率降到0.8%储能年循环次数达到320次投资回收期缩短到5年以内。你从表格里能很直观地看到双层优化的方案往往会把储能“分散化”而不是集中在一个点。原因很简单分散布置能让储能在不同馈线上同时发挥作用既缓解电压问题又能让每个储能都有足够的充放电空间运行效率更高。5. 常见问题与排查技巧实录5.1 粒子群不收敛或者早熟怎么办这是最常遇到的问题现象是适应度曲线迭代到30代左右就平了再也下不去或者跑几次结果差异巨大。排查顺序我建议这样。先检查编码范围和解码逻辑。很多时候不是算法问题是粒子位置和实际方案对不上导致适应度计算混乱。其次看罚函数如果不可行解罚得太狠粒子群会快速收敛到第一个勉强可行的区域这就是典型的早熟。最后看惯性权重如果你用的是固定w而不是递减后期粒子速度减不下来很难做局部精细搜索。如果上面都正常还是收敛不佳可以给粒子群加“变异”操作每迭代一定代数随机挑几个粒子重新初始化让种群保持多样性。这个方法简单粗暴但对付早熟非常有效。5.2 SOC初值不一致导致储能“偷电”这是我踩过的坑必须单独说一下。如果你做典型日优化SOC的初值设成0.5优化结束之后SOC可能跑到0.1这相当于储能白赚了0.4倍容量的电能。这个电能没有成本优化器当然会让储能尽可能放电得到的运行成本会严重偏低。解决办法是加一个SOC周期约束让调度周期结束时的SOC等于起始值SOC(T) SOC(1)这样才能保证储能每个循环的净充电量等于净放电量运行成本才是真实可信的。5.3 罚系数选择没有头绪罚系数的选择没有万能公式但我有一个快速试算方法。把储能配置固定在几个典型位置上比如集中式配置和分散式配置各试几组然后让罚系数从0.001、0.01、0.1、1、10这样变化记录目标函数值的变化曲线。你会发现目标函数值先随着罚系数增大而增加因为约束被逐步满足然后又因为罚项过大而显著增大。选择那个“约束刚好满足、目标函数增加最小”的罚系数即可。这个方法不严谨但比拍脑袋可靠得多。5.4 运算速度太慢怎么加速双层优化的计算量不容小觑。假设外层粒子群40个粒子、迭代100代内层每次运行优化耗时0.5秒总耗时就是40乘以100乘以0.5等于2000秒半小时还多。如果还跑全年8760小时那更是灾难。我建议三个加速方向。第一把时间维度从全年8760小时缩减为典型日集合比如春秋、夏、冬三个典型日每个典型日乘以对应天数。第二内层求解器用线性化后的LP别用非线性求解器。第三Matlab的Parallel Computing Toolbox直接对粒子群的适应度计算做并行化代码改动很小基本就是把for改成parfor速度能提升接近CPU核心数倍。5.5 光伏和负荷数据的时序错位这是个新手容易犯的错误。光伏出力曲线和负荷曲线的数据必须保证时间戳对齐单位统一。比如光伏出力是按分钟采样的负荷是按小时平均的直接混用会导致功率不平衡约束解不出来或者算出离谱的弃光量。实际做的时候先把所有时序数据统一到同一个时间分辨率比如都按15分钟或者1小时重采样再检查数据是否有一小时的平移错位。这种问题最隐蔽报错信息不明确排查起来特别耗时间有一次我整整浪费了一天最后发现是光伏数据比负荷数据早了1小时。最后分享一点实战经验做完整个双层优化模型之后我的体会是算法本身并不难难的是把工程约束理解透、把数据处理好。每多加一个实际约束模型复杂度就上一个台阶但对结果的修正效果往往是决定性的。比如SOC周期约束、变压器容量约束缺一个结果就会偏。另外如果你最后是要做项目落地的建议把优化出来的充放电策略和储能系统的EMS对接一下。规划算得再好调度指令执行不到位也是白搭。粒子群算法在整个流程里其实只是个高效的搜索工具真正值钱的是你能不能把配电网、光伏、储能这三者的运行逻辑用数学模型说清楚。把这个想透了换什么算法、换什么算例都不是大问题。