
做配电系统规划的人应该都有同感这几年项目是越来越难做了。以前靠经验套规标就能拿方案现在光伏、风电、储能、充电桩一批不确定性电源涌进配电网规划问题一下子从“算负荷、选接线”变成了“多源协同、多目标权衡”的硬仗。我最近啃完并复现了一个“基于经济与可靠性双目标的混合配电系统规划及可靠性评估”项目全程用Python实现收获非常大。这篇博文想把模型的数学设计、NSGA-II优化、序贯蒙特卡洛可靠性评估、代码组织方式以及调试时踩过的各种坑一次性讲透。不管你是刚接触配电网规划的研究生还是想用Python把可靠性评估落到工程里的从业者都可以直接照着这套思路抄作业。1. 先盘一盘混合配电系统规划为什么成了硬骨头1.1 “混合”二字背后的多源博弈传统配电网的结构其实很简单变电站出线辐射状供电末端接用户潮流单向流动。规划人员要做的无非是选线径、定容量、校电压边界条件清晰方案好坏一目了然。但一旦把分布式光伏、风电、储能、可控负荷这些东西接进去问题性质完全变了。“混合配电系统”这个叫法听起来挺专业本质就是传统配电网加一堆新型元素。它们不是简单的并联关系而是互相影响、彼此耦合的复杂系统分布式电源出力受气象条件影响具有强随机性中午光伏大发、夜间突然消失潮流方向和大小都在动态变化储能既可以充电消纳多余电量又可以在高峰时段放电支撑负荷运行策略不同对规划结果的影响也截然不同电动汽车充电桩、温控负荷这类可调资源让负荷曲线不再是刚性的需求响应改变了系统的峰值和谷值特征。这就带来一个核心矛盾规划方案面对的不再是确定的工况而是一整年内成千上万种可能的运行状态。选错方案可能在某个极端场景下电压越限、设备过载甚至大面积停电。所以现代配电系统规划本质上是在“多场景、多状态、多约束”下做决策复杂度比传统方法高出一个量级。1.2 经济性和可靠性不是简单的“既要又要”我刚接触这个题目的时候第一反应是经济性就是总费用最低可靠性就是不停电两个目标分别优化再选个平衡点不就行了实际操作之后才发现事情远没那么简单。经济性目标关心的是钱设备投资、运行维护、网损、购电费用、因停电造成的损失一项项都要算清楚。可靠性目标关心的是“电稳不稳”用户一年停几次电、停多久、缺供了多少电量。这两个目标之间的关系很微妙。在某些场景下它们是一致的。比如合理配置储能既能削峰填谷降低购电成本又能在故障时提供应急支撑一举两得。但在更多场景下它们相互冲突为了把可靠性从99.9%提高到99.99%需要额外增加一倍的备用设备和冗余线路这笔钱花下去可靠性确实上去了但经济指标就难看了。这种冲突决定了不能简单靠加权求和把两个目标揉成一个数。权重怎么定谁来定不同决策者的偏好不同权重稍有变化最优方案就会面目全非。所以我用多目标优化的思路做直接求一整套Pareto最优解集让决策者看到“多花多少钱能换来多少可靠性”再根据项目资金的实际情况挑一个最合适的方案。这才是双目标规划真正的价值所在。2. 把规划翻译成数学问题目标函数、变量与约束2.1 经济目标别只算设备价格全寿命周期成本才叫算账经济性建模的第一步是明确“钱”的口径。很多新手上来就把光伏板单价乘容量、储能电池单价乘能量加在一起作为总投资然后开始优化——这是典型的只算设备价格、不算全寿命成本。实际工程里一套规划方案要用20年甚至更久不同设备的寿命不同资金有时间价值。今年投入的1块钱和二十年后花的1块钱实际购买力完全不一样。所以必须做“等年值折算”。折算公式其实不复杂核心是一个资本回收系数CRFCRF r(1r)^n / ((1r)^n - 1)其中r是折现率工程上通常取6%~10%n是设备寿命。光伏组件寿命一般按20~25年储能系统由于电池循环寿命限制通常按8~10年考虑。把总投资乘以CRF就得到了每年平摊的投资成本这样才能和年度运行费用相加、比较。经济目标函数我最后搭成了这样一个年度费用结构min f1 C_inv C_om C_loss C_purchase C_outage每一项的含义分别是C_inv投资成本的等年值包括光伏、储能等新建设备C_om年运行维护费用通常按设备投资的一定比例估算光伏取1%~2%储能取2%~3%C_loss配电网年网损费用由全年逐时潮流计算得到按购电电价折算C_purchase向上级电网的购电费用这部分要扣除分布式电源的发电贡献C_outage停电损失费用和后面可靠性评估的结果直接挂钩是经济性和可靠性耦合的桥梁。特别要说一下停电损失。很多纯经济规划都不算这笔账但我觉得必须算。因为只要系统存在故障概率停电就是必然事件只是发生多少的问题。把缺供电量乘上单位停电损失成本比如说每千瓦时20元就变成了经济损失。这样可靠性指标EENS就直接进入了经济目标两个目标都不是孤立的了。2.2 可靠性从停电次数到期望缺供电量指标到底怎么选可靠性评估不像经济性那样直接算一笔总账它是一套成体系的指标。国际上有成熟的标准体系国内配电网主要关注三个维度频率、持续时间、缺供电量。常用指标包括SAIFI系统平均停电频率指标单位是“次/用户·年”反映用户一年平均停几次电SAIDI系统平均停电持续时间指标单位是“小时/用户·年”反映用户一年平均停多长时间CAIDI用户平均停电持续时间等于SAIDI/SAIFI反映单次停电平均持续多久EENS期望缺供电量单位是“MWh/年”反映系统全年平均缺供了多少电量LOLP失负荷概率反映系统不能满足负荷需求的概率。我在项目里把EENS作为第二个优化目标函数。原因是SAIFI和SAIDI是频率类指标偏重“停了几次、停了多久”而EENS直接体现“缺了多少能量”这就和经济损失能对应起来便于和中止成本挂钩。当然评估完毕后SAIFI、SAIDI、CAIDI也要全部输出因为它们对运行部门更有参考意义。可靠性的计算方法我选的是序贯蒙特卡洛模拟。原理不玄乎把系统中的每个元件线路、变压器、分布式电源、储能建模为“可用/故障”交替的状态故障持续时间、修复时间分别服从指数分布用随机抽样模拟元件状态在时间轴上的变化然后推进时间在每一段系统状态稳定的时间里判断运行是否满足负荷需求累计缺供电量和停电用户数最后把所有场景的结果做统计平均得到可靠性指标的期望值。这个方法比解析法更贴近物理实际能处理分布式电源随机出力、储能充放电策略等复杂逻辑代价是计算量比较大这个问题我会在后面专门讲排查优化。2.3 双目标求解为什么一定要上Pareto前沿和NSGA-II前面说过双目标优化的核心不是找“一个”最优解而是找“一组”互有优劣的Pareto最优解集合。Pareto最优的概念理解起来不难假设有两个方案A和B如果A在各项指标上都不比B差且至少有一项比B好就说A支配B所有不被任何其他方案支配的方案就构成Pareto前沿。在这条前沿上你无法在不牺牲一个目标的前提下改善另一个目标所以前沿上的每个点都有其合理性选哪个取决于决策者的偏好。既然要找到整条前沿就不能用单目标优化器跑一次了事需要一个真正的多目标进化算法。我选了NSGA-II全称是“带精英策略的非支配排序遗传算法”围城圈里最经典、最稳的工具。NSGA-II的核心机制有三块非支配排序把种群按支配关系分成多个层级第一层是当前最优的Pareto前沿第二层次之依此类推拥挤距离计算同一层里按目标空间分布的稀疏程度排序优先保留那些周围没有那么多相似方案的个体维持种群多样性精英保留策略父代和子代合并后挑选最好的个体直接进入下一代保证优秀基因不丢失。这套机制的直观效果就是进化过程中算法会自动“摊开”搜索范围尽量均匀地覆盖Pareto前沿不会一窝蜂挤在某个角落。实操里NSGA-II对大部分配电网规划问题的适应性都很好我也不建议新手一上来就换SPEA2、MOEA/D这些更复杂的变体先把NSGA-II吃透后面再按需调整。3. Python代码怎么组织一套能直接跑的工程架构3.1 整体流程规划层、评估层、寻优层三闭环代码结构是整个项目成败的关键。我见过不少同学把目标函数、约束、优化器、潮流计算全写在一个脚本里几百行挤在一起改一个参数要翻半天代码最后还要从头跑一遍——这种代码跑通就算赢但根本不具备扩展性。我的做法是把整个程序拆成三层逻辑闭环外层是寻优层负责运行NSGA-II生成候选规划方案种群中层是评估层接收一个完整的规划方案进行潮流计算和网络性能评估内层是可靠性评估层对给定方案做序贯蒙特卡洛模拟输出EENS、SAIFI等指标。这样拆的好处是每一层可以独立调试。外层只关心目标函数值是多少不关心目标函数内部怎么算中层负责把“配置”翻译成“运行表现”内层专注把随机过程统计准确。哪个环节出错直接定位到对应模块不需要全盘排查。3.2 决策变量编码与约束处理的几种思路配电网规划的决策变量简单说就是“在哪装、装多大”。我用了混合编码二进制变量节点是否安装光伏、是否安装储能0/1表示连续变量光伏装机容量、储能容量在实际约束范围内取连续值有时候还有整数变量比如储能的充放电时间参数档位、变压器分接头档位。编码映射到NSGA-II里我采用实数编码二进制部分用阈值映射比如个体基因值大于0.5就视为1否则为0连续部分直接用基因值。这样避免了在遗传操作中频繁转换编码类型交叉变异都统一在实数域做代码简洁了很多。约束处理是规划项目里最容易翻车的地方。配电网规划常见的约束包括节点电压约束各节点电压保持在允许范围一般取0.95~1.05倍额定电压支路容量约束线路载流量不超过热稳定极限功率平衡约束每个时刻系统必须满足有功无功平衡设备安装上限受场地和投资限制光伏储能容量不能无限大。我在代码里采用罚函数法约束越限的程度越大目标函数值罚得越狠。罚因子需要调试太小约束形同虚设太大则会让可行域边缘信息丢失导致算法效率低下。调试的时候建议先宽松后收紧观察种群逐渐向可行域收敛的过程心里才有底。3.3 成本评估模块一个等年值函数算清全周期账单经济性计算是所有模块里最容易理解的但细节也最容易错。核心是等年值折算函数和年度费用汇总逻辑。import numpy as np def crf(r, n): 资本回收系数把总投资折算为等年值 return r * (1 r) ** n / ((1 r) ** n - 1) def annual_cost(pv_cap, ess_cap, pv_price, ess_price, r0.08, pv_life20, ess_life10): 计算光伏和储能的年化投资成本 pv_cap: 光伏容量(kW) ess_cap: 储能容量(kWh) pv_price: 光伏单位投资(元/kW) ess_price: 储能单位投资(元/kWh) inv_pv pv_cap * pv_price inv_ess ess_cap * ess_price ann_pv inv_pv * crf(r, pv_life) ann_ess inv_ess * crf(r, ess_life) return ann_pv ann_ess这只是一个基础版本。实际算例里每年的运行费用需要做逐时计算。我用8760小时的时间序列表示负荷、光伏出力、电价循环累加网损费用和购电费用。虽然逐时计算跑起来比较慢但信息量足能真实反映季节性、昼夜性差异对规划结果的影响。如果只想做快速测试可以把8760小时聚类成典型日场景比如夏季晴、夏季阴、冬季晴、冬季阴四个典型日乘以对应天数计算量能下降一个数量级精度损失在可接受范围内。3.4 可靠性评估模块手把手实现序贯蒙特卡洛序贯蒙特卡洛模拟的核心逻辑是“逐元件抽样状态序列逐时推进评估”。我先解释原理再给关键代码。每个元件比如一条线路随时间在“运行”和“故障”两种状态之间切换。从运行进入故障的时间间隔称为TTFtime to failure从故障恢复到运行的时间间隔称为TTRtime to repair。电力系统元件失效通常用指数分布描述所以TTF -1/λ * ln(U) TTR -1/μ * ln(U)其中λ是故障率μ是修复率1/平均修复时间U是0到1之间的均匀随机数。知道每个元件的TTF/TTR后模拟流程是给所有元件分别抽样一套TTF找出最小的TTF把系统时钟推进到该元件故障时刻对该元件抽样TTR在其修复前判断系统是否能满足负荷需求记录缺供电量和停电用户数为该元件重新抽样下一个TTF重复步骤2~4直到累计模拟时间达到设定年限比如8760小时×模拟年数。直接给一段简化但能跑的主循环框架import numpy as np def sequential_mcs(components, load_series, years10, seed42): components: list of dict, each has lam故障率和mu修复率 load_series: 8760小时的负荷有功功率 rng np.random.default_rng(seed) hours 8760 * years # 初始化每个元件的TTF ttf {i: -1 / comp[lam] * np.log(rng.random()) for i, comp in enumerate(components)} ttr {i: 0 for i in range(len(components))} # 统计量 energy_not_supplied 0.0 outage_steps 0 for t in range(hours): # 找到当前故障元件应该有更好的数据结构管理这里简化 for i, comp in enumerate(components): # 判断元件状态 pass # 实际需要根据ttf/ttr判断 # 判断系统是否失负荷 available ... if available load_series[t % 8760]: deficit load_series[t % 8760] - available energy_not_supplied deficit outage_steps 1 eens energy_not_supplied / years # MWh/年 return eens上面的代码框架离能跑还差得远真正工程化时还要补潮流计算来判断系统是否满足电压约束和支路容量约束。关键思路是判断系统是否失负荷不能只看总功率够不够必须做潮流计算看网络是否安全运行。这一步千万不能省。4. 算例实测IEEE 33节点系统完整跑一遍4.1 算例参数怎么设置从节点负荷到候选方案为了验证代码逻辑我用了经典的IEEE 33节点配电系统做算例。这个系统参数公开总负荷约3715 kW和2300 kvar基准电压12.66 kV网络上随便搜都能找到详细节点和支路数据非常方便复现。候选安装位置我选了系统末端和重负荷节点节点18、节点22、节点25、节点33。为什么选这几处因为末端节点电压波动大光伏接入可以就地支撑电压储能可以削峰填谷效果最明显。光伏装机容量候选范围设为0~500 kW储能容量候选范围设为0~300 kWh。新能源渗透率上限设为系统总负荷的40%避免过度装机造成严重的反送电问题。算法参数方面种群规模设为60进化代数设为100交叉概率0.9变异概率0.05。这些参数不是拍脑袋定的而是预实验跑里比较出来的——种群太小容易早熟太大计算时间爆炸60和100在我机器上刚好能接受。4.2 运行结果解读Pareto前沿与折中解选取程序跑完后我得到了一组Pareto解集。把每个解的两个目标值画出来就是一条典型的Pareto前沿曲线。横轴是年度总费用纵轴是EENS前沿从左上方到右下方倾斜——费用越高EENS越低直观展示了“花钱买可靠性”的权衡关系。举个例子我跑出的80个Pareto解里取了三个典型方案做对比方案年度总费用万元EENSMWh/年光伏配置kW储能配置kWh经济优先81248.521060折中方案87623.6320180高可靠94511.2410260从数据能明显看出经济优先方案把钱花在了投资最少的组合上储能配得很小缺供电量大但总费用最低高可靠方案光伏储能都上得足EENS降到11.2 MWh/年代价是费用多出130多万折中方案费用和可靠性都在中间位置适合大多数项目场景。值得注意的是前沿不是均匀分布的费用从876万升到945万只换了约12 MWh/年的缺电减少而费用从812万升到876万就少了约25 MWh/年。这个“边际收益递减”现象说明可靠性不是花钱越多越划算前沿拐点附近的方案性价比最高这也是Pareto解集相比单点优化最大的价值它让人眼看到钱花在哪一段最值。4.3 三种方案横向对比钱花在哪才值从Pareto前沿挑解我用的是模糊隶属度函数。把每个目标值做归一化处理对每个解计算“满意度”满意度分布最均衡的解就是折中解。简单说既要费用别太高又要可靠性别太差取综合平衡。再往深里看三个方案里EENS的差距来自哪里我分析了故障场景发现系统最脆弱的环节是末端支路。IEEE 33系统的末端节点18和33附近供电半径长、负荷集中一旦上游线路故障末端负荷几乎必然停电。经济优先方案在节点18只装了60 kW光伏故障时支撑能力极弱折中方案配了180 kWh储能可以在孤岛运行时支撑一部分重要负荷高可靠方案的光储组合几乎覆盖了末端全部关键负荷所以EENS最低。这个结果给了我一个很重要的启示可靠性的瓶颈往往在于“末端薄弱点”而不是“平均冗余”。规划时如果盲目平均地到处装设备钱花了但可靠性提升有限应该先用可靠性评估定位薄弱环节再有针对性地配置光储资源——这才叫把钱花在刀刃上。5. 实战中最容易踩的坑以及我的排查思路5.1 潮流计算那一关不收敛、精度差怎么定位潮流计算是整个评估链路的基础但这块最容易出幺蛾子。我在项目早期用Matpower的Python移植版做潮流一遇到分布式电源出力波动大的场景就闹脾气。后来检测发现问题基本集中在几个地方一是单位混乱。IEEE 33系统的基准功率和节点负荷数据有人用标幺值有人用有名值对接时少乘一个基准功率结果潮流分布完全不对。我的排查方法是做一个简单的功率平衡检查所有节点注入功率之和必须等于网损误差超过0.1%就说明输入数据有问题。二是迭代初值差。配电网线路R/X比值大普通牛顿法收敛困难。解决方法是改用前推回代法这是配电网潮流计算的标准算法稳定性好、编程简单。如果坚持用通用潮流工具记得设置合适的容差和迭代上限别默认值跑到底。三是PV节点处理。分布式电源不一定能按恒功率因数运行有些控制模式下要当PV节点处理在配电网里处理不好很容易振荡。好在大多数规划算例里可以简化成PQ节点功率因数取0.9或1.0这个简化带来的误差在工程可接受范围内。5.2 蒙特卡洛慢到怀疑人生收敛判据与方差缩减序贯蒙特卡洛的计算量是最大的坑。我一开始老老实实模拟1000年时序每代种群60个个体×100代总计算量60×100×1000600万次年模拟触目惊心。跑起来之后光等结果就等了一晚上。后来我采取了三招降计算量。第一招是设定收敛判据。蒙特卡洛模拟不一定模拟固定年份可以边模拟边计算EENS的方差当变异系数β小于某个阈值比如2%时提前终止。β就是标准差除以均值β越小结果越稳定。这样大多数方案只需要模拟200~400年就能达到精度不用死磕1000年。第二招是分层抽样。把一年分为春夏秋冬四个季节每个季节单独抽样、单独统计比一整年混着抽更快收敛。原因很简单负荷特性和光伏出力在季节内的方差比全年小分层相当于把方差“关”在小盒子里整体统计效率自然高了。第三招是两阶段评估。进化过程前期不需要非常精确的EENS值可以用解析近似法快速筛选明显差的方案只对晋级的高潜力方案做精细蒙特卡洛模拟。这样总计算量可以再降一个量级。5.3 进化算法早熟多样性丢失和参数敏感问题NSGA-II跑了两三次就发现Pareto前沿经常挤在一个小区间覆盖宽度不够特别缺低可靠高经济或者高可靠高费用两端极端解。典型症状是种群平均目标值还在下降但前沿的扩展速度很慢。核心原因是参数没调好。交叉概率太高导致子代和父代太像变异概率太低无法产生新基因。我把常规变异概率从0.05降到0.01结果反而更差——种群迅速收敛到局部区域。后来改成了自适应策略进化前期变异概率高一些促进探索后期逐渐降低促进收敛。这样跑出的前沿覆盖率明显改善两端都有解分布。另一个细节是选择压力。锦标赛选择的天数tournament size我一开始取2种群多样性不太好改成3以后选择压入适中优秀个体保留率提高前沿收敛速度和多样性都上了台阶。当然这需要小规模预实验来确认不能盲目照搬。5.4 Python实现细节可复现性和性能优化的几个习惯最后回归到代码本身。几个习惯我强烈建议一开始就养成。第一所有随机过程固定随机种子。无论是NSGA-II的初始种群、交叉变异还是蒙特卡洛的元件抽样都要用np.random.default_rng(seed)并显式设置seed。不然程序模跑一遍、再跑一遍结果完全不同连自己写没写对都无法判断更别说和同行比较结果。第二把循环向量化。蒙特卡洛模拟里大量运算是用NumPy向量化做的比如一次性生成一整年所有元件的TTF序列而不是一条一条生成。我试过把一个1万小时模拟从for循环改成批量生成运行时间缩短了不只10倍。如果还不够快可以用Numba的njit装饰器加速纯数值函数效果立竿见影。第三做好结果记录。每一代Pareto前沿都导出CSV记录当时的参数配置每次实验都做日志归档。我的习惯是在项目目录下建results/文件夹每次运行按时间戳建子目录把配置文件和结果文件一起存进去。这样后面回看哪个参数组合出了什么结果一目了然不迷路。就我个人实际操作中的体会来说这类配电系统规划项目的难点不是某一步特别难而是每一步都有隐含的前提假设一步没想清楚后面全盘推倒。最好从一个小系统入手先把单目标经济规划跑通再加入可靠性评估最后再把两者耦合起来做多目标。我最初也想一步到位结果各种报错和不合常理的输出轮番问候走了不少弯路。希望这篇博客能帮后来人把路走直一点——先把模型吃透代码反而没那么难。