☰
混合配电系统经济与可靠性双目标规划及Python实现
2026/10/4 21:52:42 网站建设 项目流程

做配电网规划的人都有同一个体会:网架方案好不好,不能只看初始投资,更要看它运行若干年后扛不扛得住故障。尤其是近几年,分布式电源、储能、电动汽车充电桩大量接入,配电网从传统的“单电源、辐射状、单向潮流”变成了多电源、多运行方式并存的混合配电系统。这种情况下,规划一个项目要回答两个很现实的问题:这个网在故障时究竟能保多少负荷?多花一笔钱下去,可靠性又能换来多少提升?这两个问题绑在一起,就是“经济与可靠性双目标”的规划问题,也是我这次想完整拆解的内容。

“基于经济与可靠性双目标的混合配电系统规划及可靠性评估研究(Python代码实现)”这个项目,我前后迭代了三轮:第一轮只做可靠性评估,第二轮补上经济性模型,第三轮才把NSGA-II双目标优化完整跑通。这篇文章把我最终的实现思路、参数设置、代码组织和踩过的坑完整整理出来。内容偏工程研究,适合正在做配电网规划、分布式电源接入方案设计、可靠性评估的研究生或工程师,也适合想搞明白蒙特卡洛法和多目标优化在真实工程问题中怎么落地的Python用户。下面按我实际推进的顺序来讲。

1. 项目的定位与核心思路拆解

1.1 这个项目到底在解决什么问题

把标题拆开看,三个关键词构成一条完整的逻辑链:混合配电系统是研究对象,可靠性评估是核心手段,经济与可靠性双目标规划是最终决策口径。

先说“混合配电系统”。在工程研究里,这个词通常有两种指代:一种是交流/直流混合配电系统,另一种是传统交流网架与新能源电源、储能、微电网边界并存的系统。这两类都可以做双目标规划,但实现难度差别很大。AC/DC混合系统涉及换流器建模、交直流潮流迭代、控制策略切换,如果还要套进蒙特卡洛模拟,计算量会非常惊人。我这次实现的是第二种,也是目前工程可落地性最强的形态:保留原有交流辐射网架,在若干候选节点上增加分布式电源和储能,必要时增设联络线,节点性质从纯负荷节点变成含电源的混合节点。这样建模既反映真实改造后的系统形态,又不会把复杂度推到无法收敛的程度。

可靠性评估要解决的核心问题是:系统中任意设备故障后,哪些用户会停电、停多久、损失多少电量。这件事涉及故障模式分析、网络连通性判断、转供路径搜索、负荷切除与恢复等一连串操作。常用的做法有两种,一种基于N-1准则做确定性校核,一种基于蒙特卡洛法做随机模拟。做双目标规划时,我建议用后者,因为随机模拟能输出期望意义上的可靠性指标,比如用户平均停电时间(SAIDI)、用户平均停电频率(SAIFI)、电量不足期望值(ENS),这些指标可以和投资成本一起直接写进目标函数,构成一对互相冲突的优化目标。

双目标规划的任务,则是在候选方案空间中找到Pareto前沿。前沿上的每一个点都代表一种“投资成本-可靠性水平”的权衡结果,工程上最终怎么选,取决于预算约束和考核指标要求,但优化模型能把整个可行边界画出来,这就比拍脑袋定一个单方案稳健得多。

1.2 为什么把可靠性与经济性放在同一层决策

有人会问,既然可靠性可以折算成钱,停电损失费用等于ENS乘以单位停电损失单价,那把“建设成本+运维成本+停电损失成本”合成一个单目标不就行了吗?这种方法确实存在,很多工程软件也是这么做的,教科书里叫“全寿命周期成本最小化”。但它有一个明显短板:单位停电损失这个参数在不同用户类型、不同地区、不同时段差异极大。工业用户停电一分钟的损失可能是居民用户的几十倍,同一个城市的不同供电分区也完全不同。

把这个参数固定成一个常数,等于把决策者的价值判断硬编码进模型。你算出来的最优方案,很可能是被这个参数主导的,换个停电单价,最优网架结构就变了。双目标优化的价值在于,不预设两个目标之间的权重,而是把经济性和可靠性作为互相矛盾的维度同时交给算法搜索,最后返回一组Pareto解,由规划人员根据实际偏好在边界上选点。这样既保住了模型的客观性,也留存了对不确定性偏好的解释空间。

从算法实现角度看,双目标并不会显著增加难度。NSGA-II这类算法本身就是为多目标问题设计的,把单目标遗传算法改成多目标,核心只差两件事:一是用Pareto支配关系替代适应度排序,二是引入拥挤度距离保证解集分布均匀。后面我会展开说这两点在Python里怎么落。

1.3 技术路线和模块边界

整个项目我建议按四个模块拆分,这也是我反复实现后觉得最清晰的结构:

  • 系统建模模块:读入拓扑数据,建立节点、支路、负荷、电源、储能、开关的抽象数据结构;
  • 可靠性评估模块:输入网架和设备参数,输出SAIDI、SAIFI、ENS等指标;
  • 经济性评估模块:根据设备选型和容量计算年化投资与运维费用,结合可靠性结果给出停电损失;
  • 多目标优化模块:调用NSGA-II对网架结构和设备容量进行搜索,输出Pareto前沿及对应方案。

分层最大的好处是可独立测试。我强烈建议先把可靠性评估模块单独验证正确,再把它作为目标函数嵌入优化循环。实际项目里,九成以上的调试时间都花在“评估算得对不对”上,如果评估和优化耦合在一起,一旦结果不对,连是评估错了还是优化收敛失败都分不清楚。

2. 配电系统建模与可靠性指标计算口径

2.1 网络拓扑怎么建才便于做故障状态枚举

可靠性评估最核心的建模对象是网络拓扑。常见测试系统用IEEE 33节点配电网,基准电压12.66kV,总负荷大约3715kW加2300kvar,节点和支路数据都有现成文献可查。Python里建这种图有两个选择:直接调networkx,或者自建节点和支路类。我的建议是,如果后续要做大量蒙特卡洛模拟,最好自建轻量级的数据结构,因为networkx的图遍历接口虽然方便,但节点和边的属性存取在大规模循环里会产生不少开销。

以IEEE 33节点为例,我会为每个节点保存如下属性:

@dataclass class Bus: idx: int load_kw: float = 0.0 load_kvar: float = 0.0 dg_kw: float = 0.0 # 分布式电源接入容量 storage_kwh: float = 0.0 # 储能配置容量 zone: int = 0 # 馈线分区,故障隔离判断用 is_connected: bool = True

支路则保存首末端节点、线路长度、故障率、修复时间、是否装开关等。故障率和修复时间是可靠性评估的两个关键输入,线路故障率一般按每公里每年0.05到0.1次来设,修复时间取2到5小时。这个参数一定要结合自己算例的实际数据做敏感性分析,哪怕基准数据相同,不同取值对ENS的影响能差出好几倍。

拓扑建好后,所有后续操作都围绕两个基本算法展开:一个是广度优先搜索(BFS)判断连通性,一个是深度优先搜索(DFS)从故障隔离点往外找负荷节点。很多教材会把这两个算法讲得很复杂,实际工程里就是循环加队列的事,重点在于节点和支路的数据结构是否支撑快速查询。

2.2 核心可靠性指标的计算口径

可靠性指标的计算口径必须一开始就定死,否则后面所有结果都没法比较。我在这个项目里用了三个最常用的指标:

  • SAIFI,系统平均停电频率,单位次/(户·年),等于用户总停电次数除以用户总数;
  • SAIDI,系统平均停电持续时间,单位小时/(户·年),等于用户总停电时间除以用户总数;
  • ENS,系统电量不足期望值,单位kWh/年,等于各负荷点停电电量之和。

前两个指标反映的是“停电发生多不多、停多久”,第三个指标直接反映经济损失,是和经济性目标对接的桥梁。工程研究中有时还会用AENS,即ENS除以用户总数,用于不同规模系统之间的横向对比。

还有一个很容易被忽略的细节:用户数怎么定义。有些文献按节点算,认为一个节点就是一个用户;有些文献按变压器台数折算;更精细的做法是考虑节点负荷量和典型用户容量的比值。不同定义直接影响SAIFI和SAIDI的量级,所以投稿或出报告时一定要写清楚口径。我这次按“节点即用户集聚点”处理,每个节点的用户数用负荷值除以单位用户平均负荷来估算,这比简单按节点数计数更贴近实际。

2.3 经济性成本模型的组成和参数选择

经济性模型在整个项目里承担“投资-运行-损失”三段成本的计算。投资成本包括线路改造费用、开关设备费用、分布式电源单位容量造价、储能单位容量造价。注意,一次性投资需要年化,不能直接把总造价和目标函数里的年可靠性指标相加。年化的常用方法是等年值法,把总造价按折现率和设备寿命摊到每年。

运维成本一般按投资的固定比例估算,具体比例看设备类型,线路通常在1%到2%,储能略高,可能在2%到3%。停电损失成本等于ENS乘以单位停电损失单价,这个单价在不同研究里有不同取值,居民负荷和商业负荷要分开设。为了不让单价参数过度左右结论,我在第三轮迭代里把停电损失从单目标“加总项”改成了双目标里的可靠性目标载体,这样即使单价取不准,Pareto前沿的形态依然稳定。

下面是一组我在算例里用过的经济性参数:

参数取值备注
线路单位造价25万元/km含电缆与架空线折算
分布式电源单位造价4500元/kW光伏典型值
储能单位造价1800元/kWh按容量计
折现率8%工程经济评价常用
设备寿命20年光伏与储能普遍取20年
运维费比例1.5%按投资年化额计
单位停电损失15元/kWh居民与商业平均值

这些参数不一定是你的算例最优值,但它们能保证模型跑出来的优化趋势是合理的。实际操作中,参数变化会影响Pareto前沿的绝对位置,但不会改变“增加投资换来可靠性提升”的基本权衡结构。

3. 蒙特卡洛可靠性评估的Python实现

3.1 序贯模拟和非序贯抽样,怎么选

蒙特卡洛法在可靠性评估里有两类主流做法:序贯蒙特卡洛和时间无关的非序贯抽样。序贯模拟以小时为步长,模拟一整年的运行过程,设备的“运行-故障-修复”状态随时间演化,能捕捉状态持续时间和时序相关性,适合计及储能充放电策略、时序负荷曲线的场景。非序贯抽样则是从状态概率分布中直接抽一个系统状态,评估一次,重复几万次再聚合统计,实现简单、计算快,适合求解稳态期望指标。

我这次选的是非序贯抽样,原因是计算效率。双目标优化要对每一个候选方案反复调用可靠性评估,每个方案内部还要做上万次状态抽样,如果全部用序贯模拟,一轮优化跑下来要几天甚至几周。非序贯抽样在精度足够的前提下,计算时间能压缩一个数量级。如果后续需要细化到储能运行策略或时序潮流,再在这个框架上改造成序贯枚举,也完全可行。

非序贯抽样的核心逻辑是:每个设备按自身故障概率独立抽样,得到一个系统状态;然后判断这个状态是否导致负荷失电;最后把大量状态的评估结果做期望聚合。这里有个关键点,设备故障概率要按“在某个随机时刻处于故障状态的概率”来算,而不是按年故障次数直接算。年故障次数除以8760小时得到小时故障概率,再与抽样持续时间相乘,才是单次抽样中设备处于故障状态的基准概率。

3.2 故障区域划分与切负荷量计算

在非序贯抽样中,一次系统状态评估的流程是这样的:先随机确定哪些设备故障,然后做故障影响分析。配电系统通常是辐射状或弱环网,单个设备故障时,故障点上游由变电站电源继续供电,故障点下游到最近的开关之间会失电,如果故障点下游有联络开关可以合闸转供,则合闸后部分下游负荷可以恢复,最终剩余的失电负荷就是要切掉的负荷。

这个逻辑落到代码里,就是一套区域划分规则:沿供电路径从变电站出发做遍历,遇到故障支路就停下来,记录故障点下游的节点集合,同时检查是否有联络路径可以从其他馈线反供。注意,不是所有下游负荷都能被转供,转供容量受联络线和变电站容量限制。如果反供路径过载,就要按负荷等级切掉部分负荷,直到满足容量约束。切负荷量的计算思路,我建议优先级从高到低排列:一级负荷优先保,二级负荷次之,三级负荷最先切。

这个部分最容易出bug的地方是环路判断。有些联络开关合闸后,系统会短暂变成环网,如果遍历逻辑没有处理“已访问节点”标记,DFS就会陷入死循环。我习惯在遍历函数的开头维护一个visited集合,每次访问节点先检查是否已访问,这个细节能省大量调试时间。

3.3 代码结构:从采样到指标聚合

单次状态评估的代码骨架可以写成下面这样:

def evaluate_system_state(buses, lines, fault_set): # 1. 标记故障支路 for line_id in fault_set: lines[line_id].faulted = True # 2. 从变电站节点开始BFS,构建上游供电可达集合 upstream = bfs(buses, lines, source_bus=0, blocked_lines=fault_set) # 3. 对每个含联络开关的边界节点,判断能否向失电区域反供 restored_nodes = set() for tie_line in tie_lines: if tie_line.is_open and tie_line.in_zone(upstream): restored_nodes.update(attempt_restoration(...)) # 4. 计算失电负荷和切负荷量 lost_power = 0.0 for bus in buses: if bus.idx not in upstream and bus.idx not in restored_nodes: lost_power += bus.load_kw return lost_power # 单位kW

外层蒙特卡洛循环就是反复调用这个函数,把每次的失电功率累加,最后除以抽样次数乘以模拟时长,就得到ENS。SAIDI和SAIFI的统计也类似,统计每次失电涉及的节点数即可。抽样次数一般取1万到5万次,要提前设定随机种子,否则每次跑出来的指标都不同,后面的优化结果没法复现。

我在代码里会刻意把“单次状态评估”和“大批量抽样循环”分开,评估函数只做计算,不做随机抽样,这样既能单测也能并行加速。Python里做并行时,最简单的方式是用multiprocessing的Pool,把样本分批丢给多个进程,再把结果汇总。但要注意,并行计算里每个子进程都要重新设置一次随机种子,否则并行模式下的抽样序列和串行模式不具可比性。

3.4 提速经验和边界条件处理

可靠性评估模块是整个项目的性能瓶颈,在NSGA-II优化循环里,每一代要评估几十上百个候选方案,每个方案要跑上万次抽样,嵌套循环下来计算量非常大。我在实际代码里用了三个提速手段:

第一,把节点负荷、故障率、修复时间等静态数据预先存成numpy数组,评估时用数组索引替代Python对象属性访问,速度能快不少。第二,把高频复用的BFS和DFS函数做局部变量绑定,减少属性查找开销。第三,如果条件允许,把内层评估逻辑做向量化或者用numba的njit装饰器加速。我在一个场景里测试过,numba加速后的评估函数比纯Python版本快大约8到10倍,这对优化迭代的收敛速度至关重要。

边界条件方面,要特别注意故障概率极低时的抽样稳定性。设备故障率通常是每公里每年0.05到0.1次,折到小时概率后是非常小的小数,普通Python随机数生成器能处理,但如果用了不合适的抽样函数,在极端条件下可能产生偏差。另外,当系统规模变大,候选方案数量增多时,可靠性评估结果的方差会影响Pareto前沿的平滑度,我建议每个方案至少抽样3万次,并且在优化结束后,对前沿上的每个点再做一次高精度复算。

4. 双目标规划与NSGA-II的落地

4.1 决策变量:网架、容量、位置怎么编码

双目标规划的决策变量在工程上通常分两类:离散决策变量和连续决策变量。离散变量包括线路是否新建、联络开关是否装设、某节点是否接入分布式电源;连续变量包括分布式电源的接入容量、储能的配置容量。不同性质的变量在优化算法里编码方式不同,NSGA-II本身支持混合变量编码,但实现时要特别注意交叉和变异算子对不同编码类型的处理。

我这次选的决策变量组合是:

  • 二进制变量:表示候选线路是否建设,候选节点是否配置储能;
  • 实数变量:表示光伏容量和储能容量,范围根据节点安装条件设定上限。

举个例子,IEEE 33节点系统里预设12条候选新建线路、8个候选光伏节点、5个候选储能节点,那一个个体的编码长度就是12个二进制位加13个实数,总共25维。这个维数不算高,NSGA-II的搜索空间完全能支撑。如果线路候选数增加到几十条,交叉算子的效果就会下降,需要引入局部搜索策略或分阶段优化。

决策变量长度直接决定了优化的收敛速度。我建议第一版先把候选方案控制在20到30个变量以内,等整个流程跑通,再逐步扩大搜索空间。一上来就想做全网大规模混合规划,通常连收敛性都很难判断。

4.2 NSGA-II的Python实现选型与参数设置

Python生态里做NSGA-II有三条路:用现成的pymoo库、用deap库、自己写算法框架。我强烈建议用pymoo,它内置了NSGA-II、SBX交叉、多项式变异等标准算子,而且支持自定义问题类,正好适合把可靠性评估函数封装成目标函数。

用pymoo定义双目标问题的骨架大概是这样的:

from pymoo.core.problem import Problem import numpy as np class PlanningProblem(Problem): def __init__(self, n_var, n_ieq_constr=1): super().__init__(n_var=n_var, n_obj=2, n_ieq_constr=n_ieq_constr, xl=0.0, xu=1.0) def _evaluate(self, X, out, *args, **kwargs): cost_list = [] ens_list = [] for individual in X: cost = calc_investment(individual) # 经济性目标 ens = calc_reliability(individual) # 可靠性目标,用ENS或SAIDI cost_list.append(cost) ens_list.append(ens) out["F"] = np.column_stack([cost_list, ens_list]) out["G"] = compute_constraints(X) # 不等式约束

这里有个关键点:目标1用年化经济成本,目标2用ENS或SAIDI,两者量纲不同,NSGA-II的支配关系直接比较目标向量,不受量纲影响,这正是双目标比加权单目标方便的地方。

参数设置上,我推荐种群规模取100到200,进化代数取200到500代,交叉概率0.9,变异概率按变量个数动态调整,通常在0.05到0.15之间。实际调试时建议先跑100代观察Pareto前沿是否稳定,再逐步加大代数。不要一上来就跑500代,万一可靠性评估模块有bug,你会在几小时后才发现整个优化逻辑跑偏了。

4.3 约束处理和Pareto解集的后处理

约束条件是双目标规划里最容易被忽略的环节。常见约束包括节点电压偏移上限、支路容量上限、分布式电源渗透率上限、储能投资总预算等。pymoo里可以将约束写在_evaluate方法的G输出中,算法会把违反约束的个体排除在支配关系之外。

电压约束和潮流计算绑定,这里要注意效率。可靠性评估内部已经做了大量BFS/DFS,如果再叠加潮流计算,开销非常大。我的做法是:在可靠性评估的同一次状态遍历里,顺带把支路负载率和节点电压近似计算出来,转供后如果出现过载,就调整切负荷顺序。这样做虽然牺牲了潮流精度,但能保证优化方向正确。对于规划层面的研究,这个精度通常足够。

优化结束后,Pareto前沿上可能有几十个方案,还需要做后处理。我会把每个方案还原成可视化网架图,并用高精度蒙特卡洛重新评估一次,剔除掉那些因优化过程中抽样方差而误判为优秀的点。后处理的另一项工作是检查网络连通性,确保所有负荷节点在正常状态和单故障状态下都具备可达性,某些优化方案为了省钱会得出岛状运行的结构,这在常规配电网规划里是必须修正的。

5. 实操中的坑与排查技巧实录

5.1 高频问题速查表

这轮项目做下来,我把最常踩的坑整理成了一张速查表,基本覆盖了同类项目调试中的大部分问题:

症状可能原因排查方法
SAIFI/SAIDI结果异常大故障概率计算口径错误,把年故障率当成小时故障率核对设备故障概率是否除以8760后作为抽样基准
ENS为0故障集合为空或BFS未检测到失电节点打印单次状态的fault_set和上游可达集合,手动验算一个简单故障场景
优化迭代结果不变决策变量编码范围冲突,随机数种子固定导致个体全部相同检查交叉变异算子是否对二进制和实数变量分别处理
Pareto前沿全是单点惩罚函数设置过强,可行解被全部淘汰放宽约束惩罚系数,单独查看约束违反度分布
并行计算结果与串行不一致子进程随机种子未初始化在Pool初始化函数中重新设置numpy随机种子
电压约束频繁越限约束计算只用了潮流近似,忽略了无功分布增加支路导纳参数,或改用线性化潮流校验

第一行的坑最容易犯。我在第一轮实现时,直接把文献里的年故障次数当作单次抽样概率,结果ENS偏大了近两个数量级,输出结果一眼看过去就不合理。后来把所有故障率先除以8760小时单位,再乘以模拟时长,结果才正常。这类参数口径错误很难通过代码逻辑层面发现,一定要用简单算例手算验证。

5.2 参数敏感性与结果校验经验

结果校验是我最想强调的部分。很多文章把蒙特卡洛评估结果直接写进优化流程,却从不用已知结果去校验,这是大忌。我的做法是,先在一个简化网络上算理论值,再对比程序输出。比如一个三节点单电源辐射网,只有一条线路,手动推导出该线路故障时的失电负荷和恢复方案,然后看程序输出是否一致。只有程序在简单案例上完全正确,才能用它去算IEEE 33节点这种复杂系统。

参数敏感性分析也值得做。我分别测试了故障率取0.05和0.1次/(km·a)、修复时间取2小时和5小时、单位停电损失取8元和20元/kWh等组合,观察Pareto前沿的形态变化。结论是:故障率和修复时间主要影响可靠性目标的绝对量级,单位停电损失主要影响单目标加总方案的排序,但对双目标前沿的整体形状影响较小。这说明双目标框架比单目标框架对参数误差的鲁棒性更强,这也是该项目最终坚持双目标的原因之一。

5.3 代码组织与工程化建议

最后说一点代码组织。这个项目涉及的模块多、计算密度高,我建议按包结构组织目录,至少包含model、reliability、economic、optimizer、postprocess五个子模块。可靠性评估函数用独立类封装,输入输出都用dataclass或命名元组,这样后续扩展不同评估方法时,接口不用大改。优化模块里,把NSGA-II的配置参数集中放在一个配置字典里,方便批量跑实验。

代码注释也要写清楚参数单位。我吃过一次亏,把储能容量的单位在某个函数里写成了kWh,另一个函数里又按MW处理,结果优化出来的方案储能配置量级完全不对。像这种单位混用问题,只用眼读代码很难发现,最终靠的就是调试输出里储能曲线的不合理跳变才暴露出来。

还有一个实用技巧:优化过程要做好中间结果保存。每次进化迭代结束后,把当前代的最优个体和目标值写入CSV或npy文件,万一程序中途崩溃,至少还能接着上次的结果继续跑,不用从头再来。我在跑一轮大规模方案时经常要耗时几个小时,这个习惯帮我省下了大量重复计算时间。

我个人在实际操作中的另一个体会是,单目标向双目标切换并没有想象中复杂,反而是把“评估”和“优化”两个环节彻底解耦,让代码结构更清楚,后续换算法、换约束条件的扩展成本都低了很多。这个项目目前已经能稳定输出考虑成本和可靠性的Pareto前沿,下一阶段如果要把时序储能策略放进来,我会把非序贯抽样改成序贯模拟,再把评估结果嵌入到同样的双目标框架里。对正在做同类项目的人来说,建议先把可靠性评估做实,再谈优化,这个顺序几乎不会错。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询