做配电网规划项目这几年,我最大的体会是:方案评审会上最难回答的问题不是"IEEE 33节点系统怎么算潮流",而是领导轻描淡写的一句——"这个方案到底比那个方案便宜多少?可靠性又高了多少?"以前我们习惯把经济性指标和可靠性指标分开算,报告里各占一章,但真要比选方案时,两者根本对不上。后来把"基于经济与可靠性双目标的混合配电系统规划及可靠性评估"整套模型用Python落地,才真正解决了方案之间的可比性问题。这篇就完整记录一下我在这个课题里的建模思路、代码实现和踩过的坑,适合正在做配电网规划、分布式电源接入方向研究的研究生,也适合工程上想用Python做多目标方案比选的从业者。
1. 混合配电系统双目标规划:一张帕累托前沿解决"省钱"还是"可靠"之争
1.1 混合配电系统到底"混合"在哪里
先说清楚对象。"混合配电系统"这个叫法在文献里有几种指法,我做的这套侧重的是交直流混合配电网:交流馈线网络里接入AC/DC换流装置,下面挂直流母线,直流侧接光伏、储能,甚至直流充电桩;交流侧继续带传统负荷和分布式电源。为什么会有这种结构?因为现在光伏直流发电、储能天然直流、电动车充电也有直流需求,硬要全部经过DC/AC两次变换再接入交流网,损耗高、控制复杂,还不如在局部直接成环或成网。
但规划层面就麻烦了。原来配电网规划只需要回答"选哪条路、架什么型号的线、在哪建变电站",现在多出来的变量包括换流器位置和容量、储能位置与容量、光伏选址定容、馈线是否要改造升级、联络开关怎么布置。决策变量里有二进制(某个位置要不要新建/升级),有离散整数(设备容量选哪个档位),还有连续量(储能额定功率、能量),混合整数非线性优化是跑不掉的。
1.2 经济性目标和可靠性目标为什么天然打架
经济性目标通常写成全寿命周期内的年均综合费用,包括投资年值、运行维护费、网损购电费,再算上停电损失。可靠性目标则用系统缺供电量期望值EENS,或者用户平均停电时间SAIDI这类指标。这两个目标放在一起,矛盾是非常直观的:想让EENS变小,就得提高冗余,多建联络线、多装储能、增加DG备用容量,投资运维成本必然上去;反过来压投资,砍掉冗余,线路一故障就没法转供,可靠性肯定下降。
很多初学者会问:为什么不用线性加权把两个目标合成一个?这恰恰是双目标建模的核心逻辑。经济量和可靠量量纲不同,万元和MWh/年强行相加,必须给一个权重,而这个权重本质上代表"一度电的停电损失值多少钱"的主观判断。不同地区、不同用户性质,这个值差出几倍甚至一个数量级。加权成一个目标,每调一次权重就得重跑一遍优化,最后拿到的永远只是曲线上一个点,看不到"多花100万能把EENS压到什么水平"这种边际关系。做双目标优化,一次性画出整条帕累托前沿,等于把决策空间完整摆到桌面上。
1.3 双目标问题里的解不是"一个最优方案"
双目标优化的结果不是唯一最优,而是一组非支配解。什么叫"非支配"?通俗讲就是:A方案比B方案总投资高,但EENS更低,那么A和B谁更好,取决于你更在意哪个目标;只要没有一个方案能在两个目标上都打败另一个,它们就都留在帕累托前沿上。我实际做项目时,对前沿的处理方式是:先算前沿,再结合单位停电损失费用从前沿里挑综合费用最低点作为推荐方案,同时把前沿两端的经济最优解、可靠性最优解都附在报告里给决策者参考。这套做法比单一加权法稳得多,评审会上也能直观展示"可靠性提升的边际成本"。
2. 可靠性评估不是算个概率,而是把故障场景逐个"跑"一遍
2.1 常用可靠性指标怎么选
做规划层面的可靠性评估,指标不需要太多,但每个都要能落到可以计算的数值上。我重点用这几个:
| 指标 | 全称 | 含义 | 单位 |
|---|---|---|---|
| SAIFI | 系统平均停电频率 | 每个用户平均一年停几次电 | 次/(户·年) |
| SAIDI | 系统平均停电持续时间 | 每个用户平均一年停多久 | h/(户·年) |
| CAIDI | 用户平均停电持续时间 | 每次停电平均持续多久,等于SAIDI/SAIFI | h/次 |
| EENS | 期望缺供电量 | 一年里无法供给的电量期望值 | MWh/年 |
| ASAI | 供电可用率 | 用户实际用电小时数占需求时间的比例 | % |
优化目标里F1(经济性)和F2(可靠性)我都用了年均口径:F1是万元/年,F2直接取EENS,单位MWh/年。EENS的好处是它本身带有"电量"属性,乘上单位停电损失费用就能折进经济性目标,做灵敏度分析时只需要改一个系数。SAIFI和SAIDI更适合作为约束条件,比如"规划后SAIDI不得超过1小时/户·年",在代码里作为罚项处理。
2.2 解析法和蒙特卡洛,我为什么选时序蒙特卡洛
可靠性评估有两条传统路线。一条是解析法:典型如故障模式后果分析法,把每条线路、每个开关、每台变压器当成一个元件,遍历所有可能故障事件,按网络拓扑判断哪些负荷点受影响,乘上故障率和修复时间求期望。对拓扑固定、没有分布式电源的传统辐射网,这个方法快且精确,工程上用了很多年。
但加入光伏和储能之后,解析法开始吃力。问题是时序性的:DG出力一天24小时在变,储能荷电状态随时间累加,故障恰好发生在晚上光伏出力为零、储能又放空的时候,和中午晴空高发时段相比,孤岛能带起来的负荷完全两码事。解析法很难把这些时序状态塞进一个解析期望公式里。所以我的方案是序贯蒙特卡洛模拟:把一年8760个小时变成仿真时间轴,逐小时抽样元件运行/故障状态,故障发生后模拟保护动作、故障隔离、联络转供和孤岛运行,统计每个负荷点的停电事件和缺电量,最后除以仿真年数得到年均指标。代价是计算量大,但在Python里用numpy批量处理,几十个元件的系统跑几百次评估是可以接受的。
2.3 故障隔离、转供、孤岛运行这三个阶段决定EENS算得准不准
EENS的精度其实不取决于抽样本身,而取决于故障后恢复过程建模得细不细。我按工程实际把一次故障拆成三步:
- 隔离:保护动作后,故障元件两端开关断开,故障段被隔离。
- 上游恢复:故障点靠电源侧的上游区域,只要故障隔离完成就能第一时间恢复供电,停的时间很短,一般只算开关动作时间。
- 下游处理:故障点下游区域,看有没有联络开关;如果有且对侧变压器和线路容量够,就转供恢复;如果没有,就只能等故障修复。如果下游含有分布式电源,还能形成孤岛,故障期间继续供电。
建模时最容易漏的是孤岛约束。孤岛能不能成立,取决于故障时刻DG出力、储能剩余电量、孤岛内负荷大小三者匹配。我一开始只判断"下游有没有DG",结果EENS明显偏小,后来把时序功率匹配加进去,结果才合理。储能SOC在故障前已经放电到很低的话,孤岛撑不了一两个小时就瓦解,这里必须模拟到位,否则评估结果会偏乐观,规划方案也就跟着冒进。
3. 经济性与可靠性两个目标在Python里怎么落地成可计算函数
3.1 经济性目标函数的四块成本
经济性目标我用全寿命周期年均费用,写出来是:
F1 = C_inv + C_om + C_loss + C_ic- C_inv:设备投资的年折算值。不能用一次性建设成本直接比较,因为不同方案设备寿命不同。标准做法是乘等年值系数:
CRF = r * (1 + r)^n / ((1 + r)^n - 1) C_inv = CRF * 一次性投资总额r是折现率,n是设备寿命。电缆、变压器、换流器、储能寿命不一样,我习惯分开按寿命算,再累加。
- C_om:每年运行维护费,通常按设备投资的1%~3%估算。
- C_loss:网损费用。用典型年8760时序潮流算出的总网损电量,乘购电电价。规划方案里线路型号变了、电源位置变了,网损差异会很大,这笔钱不能省。
- C_ic:停电损失,等于EENS乘以单位停电损失费用。这里注意别和F2重复——F2是纯可靠性指标,F1里把可靠性折算成钱,这样决策者看前沿时能同时理解"物理缺电水平"和"折算经济成本"。
3.2 可靠性目标函数的定义方式
可靠性目标F2我直接用EENS,但代码里还会同步统计SAIFI和SAIDI,方便后面加约束条件或出报告。EENS的计算在蒙特卡洛模块里完成:每次故障事件发生后,把故障期间各时刻的缺供电量累加。伪代码逻辑是:
def evaluate_reliability(system_state, load_seq, pv_seq, soc_seq): eens = 0.0 for faulted_component in sampled_faults: isolated_loads = find_isolated_loads(faulted_component) restored_loads, shed_loads = analyze_restoration(...) eens += shed_loads * outage_duration return eens / simulation_years3.3 数据结构:用图把拓扑讲清楚,别用一维矩阵硬算
配电网规划代码里最容易烂的是一开始把拓扑塞进高维矩阵。我用networkx的无向图描述拓扑:
import networkx as nx G = nx.Graph() for br in branches: G.add_edge(br["from"], br["to"], r=br["r_ohm"], x=br["x_ohm"], type=br["line_type"], status=1)节点属性挂负荷、DG、储能候选信息,支路属性挂阻抗、容量、故障率、修复时间。这样判断连通性、查转供路径都能直接调用图算法,切负荷后的孤岛分割用连通分量搜索就能完成。决策变量单独用一个一维数组表示,比如前N个位置存线路是否升级(0/1),中间M个位置存DG容量档位(整数),最后K个存储能容量档位。这个数组会直接作为NSGA-II的个体基因,后面做适应度计算时再解码成具体的规划方案。
潮流计算我用的前推回代法,适合辐射状配电网,代码短、收敛稳。时序评估时每个小时都要算一次潮流,为了速度,负荷和DG功率都预先组织成二维numpy数组,形状是(8760, node_count),按列切片喂给潮流函数,避免在Python循环里逐小时重建网络。
4. 双目标求解的算法选型与Python环境搭建实战
4.1 为什么选NSGA-II而不是穷举或单纯加权
这个问题的答案从工程角度非常现实。规划问题的决策变量是混合整数,目标函数带非线性潮流,穷举根本不可能;传统非线性规划对整数变量和拓扑约束的适配很痛苦;单纯加权法前面说过,一个权重对应一个点,画前沿要反复求解,而且前沿凹段会被漏掉。NSGA-II的核心价值在于它一次进化就能维护一整组候选解,非支配排序把解按层级分开,拥挤距离保证前沿分布均匀。我用的就是最经典的版本,Python里通过pymoo库可以直接调用,稳定可靠。
4.2 Python环境配置:一个干净环境能少踩一半坑
这部分写给刚入门的朋友。Windows下装Python别去改系统默认环境,我建议直接建虚拟环境:
conda create -n dsp python=3.10 conda activate dsp pip install numpy pandas scipy matplotlib networkx pymoo如果还没有装Python,就去Python官网下载安装包,安装时记得勾选"Add Python to PATH"。numpy是后面所有数值计算的基础,scipy用来做统计和插值,matplotlib画帕累托前沿图,networkx处理拓扑图,pymoo提供NSGA-II。一个容易出问题的地方是pymoo的版本,旧版本API和现在不一样,装的时候直接pip install pymoo装最新的即可,如果看到import pymoo报错,多半是conda默认环境太老或者没激活虚拟环境。
4.3 用pymoo把规划问题包装成标准优化问题
pymoo的使用逻辑很简单:继承Problem类,定义变量个数、边界和目标个数,在_evaluate方法里计算两个目标值;然后初始化NSGA2算法,调用minimize。核心代码框架:
from pymoo.core.problem import Problem from pymoo.algorithms.moo.nsga2 import NSGA2 from pymoo.optimize import minimize import numpy as np class DistributionPlanningProblem(Problem): def __init__(self): n_var = 20 # 线路升级0/1 + DG容量档位 + 储能容量档位 super().__init__(n_var=n_var, n_obj=2, xl=0, xu=1) # 下界上界,离散解码在内部做 def _evaluate(self, X, out, *args, **kwargs): f1 = np.zeros(X.shape[0]) f2 = np.zeros(X.shape[0]) for i in range(X.shape[0]): f1[i], f2[i] = evaluate_plan(X[i]) out["F"] = np.column_stack([f1, f2]) problem = DistributionPlanningProblem() algorithm = NSGA2(pop_size=60) res = minimize(problem, algorithm, ("n_gen", 100), seed=42, verbose=True)注意这里evaluate_plan是我自定义的核心评估函数,它需要完成编码解码、潮流计算、蒙特卡洛可靠性评估、费用统计四件事,是整个代码里计算开销最大的部分。
4.4 决策变量编码与约束罚函数
NSGA-II本身不区分连续还是离散,所以我在解码时统一处理。比如x数组里有一部分是连续的[0,1],解码时按阈值映射成0或1作为"是否升级线路";另一部分映射成DG容量档位,例如区间[0,1)映射为档位0、[1,2)映射为档位1。这样做的好处是算法内部不用改交叉变异算子,坏处是浮点编码会带来少量映射噪音,但实测对规划结果影响很小。
约束处理是真正决定算法能不能收敛到可行域的地方。我处理三类硬约束:节点电压不越限、支路电流不越限、网络保持辐射状。每个约束都转成罚项加到目标上:
penalty = lambda v: large_number * max(0, v)越限量越大罚得越狠。辐射状校验用numpy快速算连通性和边的数量,不满足就直接加大罚项。罚函数系数要不要自适应?我试过固定系数,也试过慢慢加大,工程上固定大系数已经够用,关键是不要让约束被目标函数淹没导致进化后期大量不可行解继续繁殖。
5. 跑通完整规划项目的实操流程与排坑记录
5.1 从IEEE 33节点系统起步,数据和代码要一次对齐
我建议第一次做这个课题的读者用IEEE 33节点系统,节点和支路参数在公开文献里都能查到,系统是12.66kV,负荷3490kW左右。构造方法是:先用networkx搭建基础辐射网,把IEEE标准的支路阻抗、负荷数据填进去;再在几条末端线路上设置联络备用通道(通常是5号线、18号线、25号线附近的联络),模拟真实配电网的转供能力;最后选3个候选节点装光伏,1个候选位置装储能。一开始不要贪多,算例规模小,跑起来才快,调试也容易。
数据输入我统一用CSV,项目里建好分支数据表、负荷数据表、DG出力时序表、储能运行参数表。每次改变决策变量后重新读基础数据太慢,正确的做法是基础数据启动时加载一次,决策方案作为参数传入评估函数,只在评估函数内部修改图结构和分布式电源配置。
5.2 时序蒙特卡洛的随机抽样:固定种子是第一原则
序贯蒙特卡洛的随机性会让目标函数每次计算结果不同,如果同一套方案两次评估EENS差5%,NSGA-II比较个体优劣时就会被噪声干扰,整个前沿都会飘。我的做法是全局固定numpy随机种子,比如np.random.seed(2024),但这只能保证单次进程内一致性。跨进程并行时每个进程需要不同的种子,我会用进程编号加随机偏移,保证不同工作进程的模拟序列不重复。抽样逻辑要点是:对每个元件按小时状态转移抽样,故障状态持续时间服从指数分布,期望是修复时间;用numpy批量生成状态序列,而不是写for循环逐小时判断,不然8760小时乘几十个元件会非常慢。
5.3 最容易翻车的几个地方和对应排查
| 踩坑点 | 表现 | 原因 | 解决办法 |
|---|---|---|---|
| 单位混乱 | EENS算出来是几千E+06 | 负荷用kW、电量却按MWh计费 | 统一换算,最后除以1e3 |
| 潮流发散 | 网损负值或电压越限离谱 | 初始电压全0,支路阻抗写错 | 用平启动1.0+j0,核对IEEE参数 |
| 优化不收敛 | 前沿分散没有规律 | 蒙特卡洛随机性太大或模拟年数太少 | 固定种子,把模拟年数提到50年以上 |
| 孤岛评估偏乐观 | EENS偏低 | 没考虑故障时刻DG出力和SOC | 在故障时刻读取时序出力,加入SOC约束 |
| 无可行解 | 前沿全在罚函数区 | 硬约束罚项太小 | 罚项系数提到让不可行解成本差出一个数量级 |
第一个单位坑我印象最深。EENS统计的是千瓦时,但我停电损失费用表里的单位是元/千瓦时,网络潮流算出来的损耗是kW乘时间后自然过渡到千瓦时,偏偏在写报告时想用MWh展示,换算漏了一次,导致经济性目标里停电损失大得离谱。为了不再翻车,我在代码里强制要求所有功率量纲写进块注释,并在数据读入口加单位检查和换算。
5.4 结果可视化:前沿图和最终拓扑缺一不可
跑完优化,画帕累托前沿是最直观的验证方式:
import matplotlib.pyplot as plt F = res.F plt.scatter(F[:, 0], F[:, 1], c="#1f77b4", s=30, alpha=0.8) plt.xlabel("Annual total cost / 10k CNY") plt.ylabel("EENS / MWh") plt.grid(True, linestyle="--", alpha=0.5) plt.tight_layout() plt.savefig("pareto_front.png", dpi=150)再单独画一个最优方案的网络拓扑图,把升级线路、DG位置、储能位置用不同颜色标出来,方便和评审人员沟通。这个组合比单纯贴数据表格有说服力得多。
6. 帕累托前沿怎么看:方案决策与工程落地经验
6.1 前沿形态本身就是结论
正常计算出来的前沿是一条单调下降的曲线:总成本从低到高,EENS从高到低,形成L形拐点。拐点左侧意味着预算略微增加就能换来大量可靠性提升;拐点右侧则是花大价钱只能换来一点点EENS下降。我在报告里一定会在前沿图上标注三个点:经济最优方案、拐点附近推荐方案、可靠性最优方案,对应给出各自的投资额、运维成本、网损、EENS和SAIDI。三个方案放在一张表里,比讨论算法细节更能说服决策者。
6.2 停电损失费用的灵敏度分析最常被问到
评审会上高频问题就是"单位停电损失费用你取了多少,谁定的?"这个数确实没有全国统一标准,工业用户、居民用户、商业用户差一个数量级。正确答案是:不要试图给一个"权威值",而是把它当参数扫一遍,比如取1元/kWh、5元/kWh、10元/kWh各跑一次优化,看推荐方案是否稳健。如果不同取值下推荐方案都落在同一区域,说明方案对停电损失费用不敏感,结论就硬;如果差别很大,说明决策高度依赖用户对停电的主观评价,这时需要把这一结论单独拿出来讨论。
6.3 模型简化边界与扩展方向
最后说点个人体会。这套基于经济与可靠性双目标的分析框架核心价值在于把决策问题结构化,但它有明确边界:故障率我按常数处理,没有考虑线路老化、台风暴雨等极端天气;储能运行策略是简化定则,没有做完整的经济调度;换流器等设备也没有建详细损耗模型。这些简化会让绝对数值有偏差,但方案比选趋势是可信的。如果你后续想深挖,可以扩展的方向有两个比较实际:一是把时序运行模拟嵌入评估,让储能在故障前按经济调度充电,而不是随机SOC;二是引入不确定性参数分析,比如DG出力和负荷的多场景描述,在双目标基础上再做鲁棒优化。
我自己的习惯是:搞定NSGA-II之后,少花时间调算法参数,把精力放在可靠性评估模块的故障场景细节上,回报率最高。毕竟帕累托前沿的形状,是评估模型一笔一笔刻画出来的,模型粗糙,算法再花哨也输出不了可信的决策支持。