配电网可靠性评估,每次一提这词儿,不少人的第一反应就是可靠性框图、故障树、FMEA表那一套解析推导,很容易劝退。这回咱换个体位,直接上蒙特卡洛仿真,把"随机故障→停电→恢复→统计指标"这条链路用代码撸一遍。核心思路说白了就三件事:让每段线路按故障率随机出故障,按开关位置判断谁停电、停多久,最后把所有停电事件聚合成 SAIFI、SAIDI 这些停电指标。这套玩法特别适合配电网这种开关多、运行方式灵活的场合,也适合三类人:做配电网规划但不想陷进解析公式的人、论文里需要可靠性仿真结果的学生、还有想验证网架改造方案效果的一线工程师。全程代码直接能跑,而且我会盯着每一个数字背后的含义来写,保证你能看懂结果是从哪冒出来的。
1. 先想明白:为什么选蒙特卡洛仿真而不是解析法
1.1 解析法和蒙特卡洛的实战取舍
配电网可靠性评估的传统主力是解析法,最常见的就是故障模式与后果分析法(FMEA)。做法很直观:把系统里每个元件故障当成一个"模式",逐一枚举它会造成哪些负荷停电、停电多久,最后按概率加权算出每个负荷点的可靠性指标。这类方法的好处是快,还能给出严格的数学期望值,跑一遍就是一锤定音,没有随机波动。
但它最大的问题在于"组合爆炸"。真实配电网里有分段开关、联络开关、分布式电源孤岛、转供容量约束,还有时序性操作。"A区段故障时,能不能转供下游负荷"这个判断一旦牵扯到多个条件,解析公式就会变得又长又绕。更别提要考虑保护误动、重合闸失败、负荷波动这些细节,纯解析推到头会非常痛苦。
蒙特卡洛换个思路:我不去穷举所有状态组合,而是按照元件故障概率去随机抽样,把系统"跑"很长时间,用统计平均代替解析期望。打个比方,你想知道一年里被雨淋的概率,不需要把所有天气状态列出来求期望,你只要模拟一万年的天气,统计一下带伞出门被淋了几天就行。配电网元件状态虽然多,但真正会发生的事件是随机的,我用足够长的时间尺度去"掷骰子",自然把各种罕见组合都覆盖到了。
1.2 蒙特卡洛仿真的核心三步逻辑
蒙特卡洛仿真解决的核心问题可以拆成三步:
第一步是抽样,按元件的故障率随机生成故障时刻。这是仿真的"随机种子"来源。第二步是影响分析,系统某处故障后,根据网络拓扑和开关配置,判断每个负荷点是停不停、停多久。第三步是统计归约,把整个仿真期里的所有停电事件累加起来,换算成年均指标。
蒙特卡洛还分序贯和非序贯两种。非序贯法只按元件的可用率随机抽状态,比如每个元件是"运行"还是"停运",然后对整个系统的状态做评估。这个方法在发输电系统里挺好用,但在配电网里有个尴尬:配电元件停运概率很小,你抽一万次也难得抽到几条线路同时停运,很多样本都是完全正常的,效率很低。
序贯蒙特卡洛就不一样,它是沿着时间轴逐事件推进的:每个元件都遵循"运行一段时间→故障→修复→再运行"的过程,我用抽样描述这个过程,事件天然有时序性和因果关系。配电网可靠性评估的核心恰恰是"故障之后靠开关操作恢复供电"这种时序逻辑,所以这篇我就用序贯法,代码的直观性也更好。
1.3 这次算例的网架长什么样
为了把方法讲清楚,我带一个最简的辐射状配电网算例:一条馈线从变电站母线引出,经过断路器后分成 5 个区段,每个区段末端挂一个等效负荷,负荷点的用户数各不相同。馈线末端通过联络开关接到另一条馈线,假设联络容量足够,下游负荷可以通过联络开关倒闸恢复供电。
为什么把拓扑压到这么简单?因为配电网可靠性研究的难点从来不在拓扑本身,而在"故障影响规则"。规则想清楚了,真实网架里的各种分段开关、联络开关、断路器配置,无非是把一张 FMEA 规则表做得更复杂一点而已。咱们先把核心机制跑通,后面扩展自然水到渠成。
具体参数我放在后面详细给,先用一张表把你需要盯住的量列出来:区段长度决定故障率,故障率决定停电频率,修复时间和倒闸时间决定停电时长,用户数决定指标加权方式。
2. 停电指标到底在算什么
2.1 四个核心指标的含义和公式
可靠性指标不是拿来"欣赏"的,它们是要回答两个直白的问题:用户一年平均停了几次电?每次停多久?产业界最常用的是下面这四个。
| 指标 | 全称 | 单位 | 计算逻辑 | 通俗解释 |
|---|---|---|---|---|
| SAIFI | 系统平均停电频率指标 | 次/(户·年) | 用户停电总次数 / 总用户数 | 一个用户一年平均遇到几次停电 |
| SAIDI | 系统平均停电持续时间指标 | 小时/(户·年) | 用户停电总时长 / 总用户数 | 一个用户一年平均累计停多少小时 |
| CAIDI | 用户平均停电持续时间指标 | 小时/次 | SAIDI / SAIFI | 平均每次停电持续多久 |
| ASAI | 供电可用率 | 无单位,通常写为百分比或几个9 | 1 - SAIDI / 8760 | 一年里供电可用的时间占比 |
公式看起来简单,但数值是从一个个人头身上累计出来的。SAIFI 就是所有用户的停电次数除以总用户数,每个人头权重一样,所以用户多的负荷点在 SAIFI 里话语权更大。SAIDI 则是把每次停电的时长也考虑进来,一个用户就算只停了一次电,停了 10 小时,对 SAIDI 的贡献也顶得上另一个用户停十次、每次 1 小时。
CAIDI 是 SAIDI 除以 SAIFI,它回答了"每次停电大概要熬多久"。ASAI 则把停电时间折算成全年的可用比例,99.9% 听上去还可以,换算成 SAIDI 就是一年要停 8.76 小时,很多供电企业考核指标卡在"4个9"甚至"5个9",可见年停电时间要压到几十分钟以内。
2.2 各负荷点为什么指标差异巨大
这四个指标是全网聚合后的结果,但实际工作中更值得琢磨的是每个负荷点自己的差异。在这个算例里,越靠近馈线末端的负荷点,受停电影响的概率越高,原因在于"上游故障会波及下游"这个基本逻辑。
我先把理论上的年停电频率算一遍,方便一会儿和仿真结果对账。5 个区段各自的年故障率分别是 0.144、0.096、0.12、0.18、0.072(次/年),全馈线合计约 0.61 次/年。负荷点 L1 在最上游,只有区段 1 故障时它才停,年停电频率 0.144 次/年。负荷点 L5 在最末端,区段 1 到 5 任何一个故障它都受影响,频率是全部故障之和,约 0.61 次/年。首端末端差出 4 倍多,这就是辐射状配电网的天然不均衡。
年停电时长呢?L1 作为区段 1 的既有负荷,区段 1 故障时要等修复,修复时间均值 5 小时,所以年停电时长约 0.72 小时。L5 每次遭遇上游故障时,通过末端联络开关倒闸恢复,单次只停 1 小时,只要区段 5 本身故障才要等修复。把概率和时长乘起来,L5 的年停电时长反而是 0.9 小时左右,看起来不算特别糟。这正是配电网可靠性规划里的微妙点:下游用户停电频,但靠联络开关能把单次停时压短;首端用户虽然停电少,但一停就是故障段级别的长停。
3. 系统建模:参数、假设与故障影响规则
3.1 随机模型里的每个参数都是怎么来的
配电网可靠性仿真离不开几类随机量:故障时间、修复时间,以及可选的开关动作时间。先说最基本的故障过程。工程里常用泊松过程描述线路随机故障,相邻两次故障的间隔服从指数分布,期望值等于故障率的倒数。故障率一般由历史运行统计给出,单位是"次/(公里·年)",比如 0.12 这个值可以理解成 100 公里线路一年平均发生 12 次永久性故障。
我这里每个区段的故障率就是区段长度 × 每公里故障率。线路越长,故障曝光面越大,故障率自然越高。这段逻辑映射到代码里就是rate = lam_per_km * line_length,一行数组运算。
修复时间我用了指数分布,均值 5 小时。指数分布有个"无记忆性"特点,在代码上特别好用,抽样一次就能得到一段随机时长。不过真实工程里修复时间更常建模成对数正态分布,因为它尾部更厚,偶尔出现拖很久的大修。教学场景指数分布完全够用,真要上工程项目,你只需要把抽样函数换成对数正态就行,其余代码不用动。
开关隔离时间和联络转供时间我干脆设成了固定值 1 小时。实际运行中,配电自动化系统的遥控操作可能几十秒就完成,人工现场操作可能得半小时到一小时。固定值对演示足够,但你要记住:这些时间同样可以给分布,一旦你的目标是比较"自动化改造带来的可靠性提升",就得把这些时间参数化。
3.2 故障影响规则:整个仿真代码的灵魂
故障影响规则(FMEA 表)是整个蒙特卡洛仿真的灵魂,它决定了每个区段故障后,5 个负荷点分别按什么方式恢复供电。我这次的规则是:
| 故障区段 | 负荷点 L1 | 负荷点 L2 | 负荷点 L3 | 负荷点 L4 | 负荷点 L5 |
|---|---|---|---|---|---|
| 区段1 | 修复5h | 转供1h | 转供1h | 转供1h | 转供1h |
| 区段2 | 隔离1h | 修复5h | 转供1h | 转供1h | 转供1h |
| 区段3 | 隔离1h | 隔离1h | 修复5h | 转供1h | 转供1h |
| 区段4 | 隔离1h | 隔离1h | 隔离1h | 修复5h | 转供1h |
| 区段5 | 隔离1h | 隔离1h | 隔离1h | 隔离1h | 修复5h |
看这个表,规则其实就三条:
- 故障区段本身的负荷点:等线路修好才能恢复,停 R。
- 故障点上游的负荷点:上级断路器跳闸导致短暂停电,但分段开关隔离故障段后就能恢复,停 S。
- 故障点下游的负荷点:通过末端联络开关转供到相邻馈线,停 T。
这个 FMEA 表一旦和实际网架不一致,后边出的指标全是错的。我曾见过有人把下游可转供负荷当成"修复后恢复",结果 SAIDI 凭空大了好几倍。所以拿到一套新网架,第一件事不是写代码,是把分段开关、联络开关位置和每条馈线的联络关系捋清楚,把这张表手工列出来,再谈仿真。
4. 直接开撸:完整代码与运行结果
4.1 关键设计:用绝对时刻而不是相对时间
写序贯蒙特卡洛,最忌讳的思路是每跑一个事件就把所有元件的剩余故障时间减一遍。那样不仅代码啰嗦,还容易踩到"时间减成负数"的坑。更干净的做法是给每个元件保存下一次故障的绝对时刻,每次直接找最早故障的元件,处理完以后只给这个元件重新抽样一个未来的绝对时刻。其他元件不动,因为它们的时间戳已经在时间轴上了。
这个设计还有个好处:天然支持向量化。5 个区段的next_fault就是一个 numpy 数组,argmin一行找出最早故障元件,完全不需要写循环比较。大规模配电网的区段数可能有几千个,这种写法依然高效。
4.2 蒙特卡洛主循环与指标统计代码
完整代码我拆成两部分。第一部分是仿真主循环,负责抽样故障和累计停电事件;第二部分是指标统计,把原始累计量换算成四个指标。直接用下面这段代码,注释里已经把每一步都写明白了。
import numpy as np # ================= 1. 系统参数 ================= np.random.seed(20250501) # 固定种子,确保结果可复现 lam_per_km = 0.12 # 线路每公里年均故障率,次/(km·年) line_length = np.array([1.2, 0.8, 1.0, 1.5, 0.6]) # 各区段长度,km r_mean = 5.0 # 故障修复时间均值,小时 s_time = 1.0 # 隔离操作时间,小时 t_time = 1.0 # 联络转供时间,小时 customers = np.array([100, 150, 120, 200, 80]) # 各区段负荷用户数 sim_years = 50000 # 模拟总年限 # ================= 2. 仿真主循环 ================= num_seg = len(line_length) rate = lam_per_km * line_length # 各区段年均故障率,次/年 # 下一次故障的绝对时刻(单位:年)。起始时刻所有元件“焕然一新” next_fault = np.random.exponential(1.0 / rate) total_count = np.zeros(num_seg) # 负荷点停电次数累计 total_time = np.zeros(num_seg) # 负荷点停电时长累计,小时 sim_t = 0.0 while True: idx = int(np.argmin(next_fault)) # 最早发生故障的区段 t = next_fault[idx] if t > sim_years: break # 仿真期结束 sim_t = t # 对该故障做影响分析:遍历所有负荷点 for l in range(num_seg): total_count[l] += 1 if l == idx: # 故障段负荷:等修复 total_time[l] += np.random.exponential(r_mean) elif l < idx: # 上游负荷:分段开关隔离后恢复 total_time[l] += s_time else: # 下游负荷:联络开关转供恢复 total_time[l] += t_time # 故障元件修复后,重新抽样下一次故障时刻 next_fault[idx] = sim_t + np.random.exponential(1.0 / rate[idx]) # ================= 3. 指标统计 ================= total_user = customers.sum() saifi = (total_count * customers).sum() / total_user / sim_t # 次/(户·年) saidi = (total_time * customers).sum() / total_user / sim_t # 小时/(户·年) caidi = saidi / saifi # 小时/次 asai = 1.0 - saidi / (sim_t * 8760) # 无单位 print(f"模拟历时: {sim_t:.1f} 年") print(f"SAIFI = {saifi:.4f} 次/(户·年)") print(f"SAIDI = {saidi:.4f} 小时/(户·年)") print(f"CAIDI = {caidi:.4f} 小时/次") print(f"ASAI = {asai:.6f}")这里有个容易被忽略的点:total_count和total_time是整个仿真期的累计量,要得到"年均指标"必须除以sim_t。我见过不少人直接拿累计量当指标,结果 SAIDI 大得离谱。另外,统计时长用实际推进到的sim_t而不是预设的sim_years,更严谨,因为最后一次事件不一定刚好卡在仿真期末尾。
一个简化假设需要说明:这个循环没有处理"两个元件同时故障"的极端情况。理论上其他元件在故障处理期间还可能在老化,不过配电网元件修复时间只有几小时,而故障间隔动辄几百甚至几千小时,重叠概率低到可以忽略。对教学算例来说,这个假设完全站得住。
4.3 一组典型运行结果与手工校验
我跑了一版,得到的结果是这样(不同机器不同 numpy 版本会有轻微浮动,但量级一致):SAIFI 约 0.386 次/(户·年),SAIDI 约 0.91 小时/(户·年),CAIDI 约 2.36 小时/次,ASAI 约 0.99990。拿这个结果和第 2 节的理论期望对比,你会发现误差在第三位小数之内,说明仿真逻辑没跑偏。
再拆开看每个负荷点的分量,印象更直观:
| 负荷点 | 用户数 | 年停电频率(次/年) | 年停电时长(小时/年) | 平均单次停时(小时/次) |
|---|---|---|---|---|
| L1 | 100 | 0.144 | 0.72 | 5.0 |
| L2 | 150 | 0.240 | 0.63 | 2.6 |
| L3 | 120 | 0.360 | 0.84 | 2.3 |
| L4 | 200 | 0.540 | 1.26 | 2.3 |
| L5 | 80 | 0.610 | 0.90 | 1.5 |
注意 L2 的平均单次停时是 2.6 小时,因为区段 1 故障时它转供只停 1 小时,区段 2 故障时它要等修复停 5 小时,加权平均后就是 2.6。这个数字提醒我们:负荷点单体指标和全网聚合指标要分开看,末端用户的停电次数和停电时间分布跟首端完全不是一个画风。
5. 结果解读:收敛性、参数敏感性和仿真年限
5.1 仿真年限怎么定才靠谱
蒙特卡洛最经典的质疑就是"你的结果是随机出来的,能信吗"。答案在于收敛性。配电网可靠性的停电事件属于小概率高影响事件,方差天然很大,如果模拟年限短,结果波动会非常明显。我建议的做法是:先算一下全系统的总故障率,再预估期望事件数。期望事件数 = 模拟年限 × 全系统故障率。想让 SAIDI 的相对误差进入 5% 以内,期望事件数往往要上万。
拿这里的算例来说,全馈线故障率约 0.61 次/年,50000 年模拟会产生超过 3 万次故障事件,SAIDI 的波动基本能压到百分位。如果你只跑 100 年,总共才几十次事件,SAIDI 可能在 0.4 到 1.5 之间乱跳,完全没法用。新手最容易犯的错就是拿默认的模拟年限猛跑一遍,发现指标忽高忽低,还以为是 bug,其实是样本量不够。
提示:判断收敛性的土办法是换两个不同的随机种子各跑一遍,如果两个结果的差异在你要的精度以内,说明年限够了。想要更专业,可以每隔一段仿真期记录一次累计指标,画出收敛曲线,亲眼看着波动慢慢变小。
5.2 参数敏感性:动一个数,看指标往哪跑
这种仿真最大的价值是方案对比。我拿三个改动分别试了一下:
- 方案A:把末端联络开关去掉,下游负荷故障后一律等修复。L3 到 L5 的年停电时长会从不到 1 小时涨到 2 小时以上,SAIDI 直接翻倍。
- 方案B:把修复时间从 5 小时改到 2 小时,故障段负荷的单次停时大幅下降,SAIDI 也会明显改善;再改到 10 小时,SAIDI 随之恶化。
- 方案C:把开关隔离时间从 1 小时改到 2 小时,上游负荷的每次停时拉长,对 SAIDI 的影响比修复时间小得多,因为它只影响一部分负荷的一部分事件。
这种敏感性分析在工程里非常实用。比如供电企业要决定"先上配电自动化还是先提升抢修效率",你就可以把开关动作时间和修复时间分别压低一截,跑一轮对比,用数字说话。注意敏感性分析不要单次换一个参数,更推荐做正交式的小批量实验,把参数组合表打出来,直观看到每个因子的边际贡献。
5.3 这个模型还能怎么往上长
算例虽小,但代码骨架可以平滑扩展到真实系统。最容易加的是联络容量约束,把"下游负荷全转供"改成"先校验联络线载流量,容量不够只转供一部分负荷,剩下的等修复"。这个逻辑只是把 FMEA 表里"转供1h"改成"转供1h或延迟修复5h",主循环一行都不用动。
再往上,可以加分布式光伏和储能的孤岛运行能力:上游故障时,部分负荷靠微网孤岛继续供电,停电次数和时长都会变。这需要新增一个"孤岛成功概率"参数,影响规则表里多一种"孤岛恢复"分支。还有时变负荷模型、保护误动与拒动概率、多重故障枚举,这些全都能塞进现有的蒙特卡洛框架。这也是我劝大家不要只盯着代码本身的原因,真正值钱的是你手里那张 FMEA 规则表,代码只是规则表的执行器。
6. 代码调试常见问题与避坑实录
6.1 新手最容易踩的五个坑
调试这种仿真代码,最常见的症状和原因我整理成了表,照着排查效率很高。
| 现象 | 可能原因 | 排查思路 |
|---|---|---|
| 跑完一个事件都没有 | 故障率单位换算错了,比如把"次/年"写成了"次/小时" | 打印每个区段的rate和next_fault,看平均间隔是否在合理量级 |
| SAIDI 大得离谱 | 累计时长没除以模拟年限,或者修复时间抽样用的单位不是小时 | 检查统计部分是否除了sim_t,确认 8760 的换算 |
| 结果每次跑都不一样,波动巨大 | 模拟年限太短;随机种子没固定 | 先固定种子,再把年限调大 10 倍观察波动 |
| 下游负荷的停电时间全部接近修复时间 | 故障影响规则写错,把可转供负荷按故障段处理 | 手工列一张 5×5 的 FMEA 表,核对每个格子的恢复方式 |
| 仿真跑得极慢,几千个区段转不动 | 纯 Python 循环遍历所有负荷点 | 把影响规则改写成矩阵运算,或者用 Numba/Cython 加速内层循环 |
6.2 几个实战小技巧
固定随机种子是最低成本的复现手段,也是评审时最容易被问到的细节。我习惯在代码开头写np.random.seed(...)并注明这是哪一版配置,这样任何一次跑出来的结果都能回溯。
调试期我会把故障率临时放大 10 倍,这样事件密度变大,几十年的仿真就能看到足够多的故障样本,逻辑对不对一眼就能看出来。确认没问题后再把故障率调回去跑正式结果。这个小技巧能帮你省下大量等待时间。
还有一条经验:正式仿真前一定要手工做一个期望值校验。就像我在第 4 节那样,用 FMEA 表推出理论分期,再跟仿真结果对数量级。很多隐藏 bug 是数学上能算出来但不合理的,比如某个负荷点年停电时长只有 0.001 小时,这种地方一定要揪出来看看是不是漏掉了故障分支。
我个人做这类仿真的习惯,是先拿最简算例把逻辑盘活,再对接真实网架数据。真到了实际项目里,光是把馈线分段、负荷点、开关位置从 GIS 或配电网模型里整理成 FMEA 表,工作量就比仿真代码本身还大。但好消息是,只要你把规则表弄清楚,主循环往往一行都不用改。最后再分享一个小习惯:每次评审我都会留一套小算例的解析参考值,跑完仿真顺手比对一遍。蒙特卡洛结果可复现、可校核,这比任何口头解释都更能说服人。