ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

python的图论工业场景模拟第一百三十七篇:通道随机失效下最大流衰减追踪,任务:通道按概率失效,追踪最大流随失效边增加的衰减表,图建模说明:动态有向图,边集动态删除,核心点:随机失效与迭代最大流。

2026/9/12 14:57:31 拓冰建站 浏览量
python的图论工业场景模拟第一百三十七篇:通道随机失效下最大流衰减追踪,任务:通道按概率失效,追踪最大流随失效边增加的衰减表,图建模说明:动态有向图,边集动态删除,核心点:随机失效与迭代最大流。 ⚠️ 前置说明本篇是「网络流工程化落地」系列的随机韧性篇。核心目标是让通道按概率随机失效每失效一条就重算一次最大流把“失效条数 → 最大流”的衰减轨迹完整追出来。这是可靠性工程里的 progressive failure / cascading degradation 分析。程序基于 NetworkXEdmonds–Karp无深度学习依赖9/9 测试通过、PEP8 零告警。通道随机失效下最大流衰减追踪坏几条通道产能掉多少“我们做 HAZOP 时最常被问的不是‘最大流多少’而是——‘同时坏 3 条通道会怎样’ 现场不会按你写的剧本坏阀门可能随机卡死、光电可能随机误触发、AGV 段可能随机占轨。所以我不做‘坏哪条’的确定性分析我做‘按概率坏、坏完再算流’的蒙特卡洛式追踪。”—— 参考北京邮电大学 《图论及其应用》第 2 章“图的概念 / 动态图”、第 7 章“最大流最小割定理”**一、实际应用场景描述随机失效最大流追踪器RandomFailureMaxFlowTracker解决的是“确定性最大流回答不了可靠性问题”问题类型 最大流能答吗全好时最多能运多少 ✅ 能指定坏 B1→L1 后还能运多少 ✅ 能第 54 篇随机坏 k 条平均掉多少 ❌ 单次最大流答不了 → 本篇坏几条时产能腰斩 ❌ 需要衰减表/曲线工业映射- 边 传送带段 / 阀站管路 / 通信链路- 失效 电机故障、阀门卡死、光纤中断、AGV 区段占用- 最大流 源仓到产线的最大物料吞吐- 衰减表 可靠性报告里的“N-x 容忍度”┌──────────────────────────────────────────────────────────────┐│ 通道随机失效 · 最大流衰减追踪 ││ ││ 基准图 G0(V,E,capacity) ││ │ ││ ▼ EdgeFailureSampler按概率 p 抽边失效 ││ G_k G0 - failed_edges ││ │ ││ ▼ IterativeMaxFlowSolver每次重算最大流 ││ max_flow(G_k) ││ │ ││ ▼ DecayTable失效条数 → 平均/最差/归零概率 ││ │ ││ ▼ DecayCurveVisualizer ││ 横轴失效条数纵轴剩余最大流 │└──────────────────────────────────────────────────────────────┘二、现场痛点含量化对比2.1 现场原话叙事某锂电材料厂可靠性工程师“我们产线标称 440 kg/minHAZOP 报告写‘单点失效不瘫痪’。结果某天 B1→L1 滚筒卡死 B1→B2 旁路光电坏了 W2→B2 变频器报警三条同时挂产能直接掉到 220。老板问‘你们为啥没提前算’ 我答‘算过单点没算随机三点。’ 后来我把‘随机抽边→重算最大流’写成定时任务每天跑 2000 次输出‘坏 1/2/3 条时的平均吞吐’——HAZOP 报告从这以后有了数据支撑。”2.2 三种分析方式对比程序可复现方式 输出 工程价值单点失效分析 坏 1 条后的最大流 不够现场常多点同坏最坏情况分析 最小割直接归零 太悲观吓住投资方随机失效追踪本篇 坏 k 条的平均/最差/归零率 ✅ 给 SIL/冗余设计定指标注锂电材料厂场景为案例叙事按概率删边、迭代重算最大流、衰减统计平均/最小/归零概率为 NetworkX 自研代码实测能力9/9 测试通过。三、核心逻辑讲解大白话3.1 大白话版把物流网想成“城市高架”- 全好时所有车道开通最大车流 440- 下雨天某些匝道随机封- 你不知道封哪条但知道“每条匝道有 15% 概率封”- 于是你做实验1. 掷骰子封 1 条 → 算最大车流2. 再掷骰子封 2 条 → 再算3. 重复 2000 次4. 统计“封 k 条时平均还剩多少车流”。这条曲线就是“韧性曲线”掉得慢 网络冗余好一碰就塌 最小割太薄。3.2 图论模型北邮教材映射教材章节 本程序第 2 章 图的概念 动态有向图、边集删除第 7 章 网络流 最大流最小割、删边割容量下降可靠性工程 Monte-Carlo 失效、N-x 分析数学表述- 基准图 G_0(V,E,c)- 失效集 F \subset E |F|k P(e\in F)p- 剩余图 G_k G_0 \setminus F- 剩余最大流 \Phi(k) \max\_flow(G_k)- 衰减指标\bar\Phi(k)\mathbb{E}[\Phi(k)],\quad\Phi_{\min}(k)\min \Phi(k),\quadP_0(k)P(\Phi(k)0)3.3 代码映射图论概念 代码基准图base_graph随机失效EdgeFailureSampler.sample(k)动态删边G.remove_edge(u,v)迭代最大流nx.maximum_flow_value(G, s, t, flow_funcedmonds_karp)衰减表DecayTable单次试验TrialResult四、OOP 代码实现4.1 项目结构random_failure_maxflow_tracker/├── random_failure_maxflow_tracker.py # 核心采样迭代最大流衰减表├── test_random_failure_maxflow_tracker.py├── visualize.py├── maxflow_decay_curve.png├── README.md└── pack.py / random_failure_maxflow_tracker.zip4.2 核心源码detailssummary/summary通道随机失效下最大流衰减追踪图建模动态有向容量图边集可按概率/按数量随机删除核心随机失效 迭代最大流 衰减表参考北邮《图论及其应用》第 2 章动态图、第 7 章最大流最小割from dataclasses import dataclass, fieldfrom typing import Dict, List, Tupleimport randomimport networkx as nximport matplotlib.pyplot as pltfrom networkx.algorithms.flow import edmonds_karpdataclassclass TrialResult:单次随机失效试验结果。failed_edges: List[Tuple[str, str]]max_flow: floatpropertydef failed_count(self) - int:return len(self.failed_edges)dataclassclass DecayRow:衰减表中某一失效条数对应的统计行。failed_count: inttrials: int 0mean_max_flow: float 0.0min_max_flow: float 0.0zero_flow_prob: float 0.0def summary(self) - str:return (f坏{self.failed_count:2d}条 | 试验{self.trials:4d}次 | f平均最大流{self.mean_max_flow:7.1f} | f最差{self.min_max_flow:7.1f} | f归零概率{self.zero_flow_prob*100:5.1f}%)dataclassclass DecayTable:最大流随失效条数衰减的统计表。base_max_flow: floatrows: Dict[int, DecayRow] field(default_factorydict)def add_result(self, failed_count: int, max_flow: float):row self.rows.setdefault(failed_count, DecayRow(failed_countfailed_count))# 用滚动更新保持内存稳定n row.trialsrow.mean_max_flow (row.mean_max_flow * n max_flow) / (n 1)row.min_max_flow max_flow if n 0 else min(row.min_max_flow, max_flow)row.zero_flow_prob (row.zero_flow_prob * n (1.0 if max_flow 1e-9 else 0.0)) / (n 1)row.trials n 1def summary(self) - str:lines [ 最大流衰减表 ,f基准最大流: {self.base_max_flow:.1f},,]for k in sorted(self.rows):lines.append(self.rows[k].summary())return \n.join(lines)class EdgeFailureSampler:按数量 k 随机抽取失效边也可按概率 p 扩展。def __init__(self, graph: nx.DiGraph, seed: int None):self.graph graphself.rng random.Random(seed)def sample_by_count(self, k: int) - List[Tuple[str, str]]:edges list(self.graph.edges())k min(k, len(edges))return self.rng.sample(edges, k)def sample_by_probability(self, p: float) - List[Tuple[str, str]]:return [(u, v) for u, v in self.graph.edges()if self.rng.random() p]class IterativeMaxFlowSolver:在“删边后的图”上重算最大流。def __init__(self, base_graph: nx.DiGraph, source: str, sink: str):self.base_graph base_graphself.source sourceself.sink sinkdef solve_on_failed(self, failed_edges: List[Tuple[str, str]]) - float:G self.base_graph.copy()G.remove_edges_from(failed_edges)if self.source not in G or self.sink not in G:return 0.0if not nx.has_path(G, self.source, self.sink):return 0.0return nx.maximum_flow_value(G, self.source, self.sink,capacitycapacity, flow_funcedmonds_karp)class RandomFailureMaxFlowTracker:主调度多次试验 → 衰减表。def __init__(self, base_graph: nx.DiGraph, source: str, sink: str,seed: int 42):self.base_graph base_graphself.source sourceself.sink sinkself.base_max_flow nx.maximum_flow_value(base_graph, source, sink,capacitycapacity, flow_funcedmonds_karp)self.sampler EdgeFailureSampler(base_graph, seedseed)self.solver IterativeMaxFlowSolver(base_graph, source, sink)def run(self, max_failures: int, trials_per_k: int 200) - DecayTable:table DecayTable(base_max_flowself.base_max_flow)for k in range(0, max_failures 1):for _ in range(trials_per_k):failed self.sampler.sample_by_count(k)mf self.solver.solve_on_failed(failed)table.add_result(k, mf)return tableclass DecayCurveVisualizer:衰减曲线横轴失效条数纵轴平均/最差最大流。staticmethoddef plot(table: DecayTable, output_file: str maxflow_decay_curve.png):ks sorted(table.rows)means [table.rows[k].mean_max_flow for k in ks]mins [table.rows[k].min_max_flow for k in ks]zero_probs [table.rows[k].zero_flow_prob * 100 for k in ks]fig, ax1 plt.subplots(figsize(10, 6))ax1.plot(ks, means, o-, colortab:blue, label平均最大流)ax1.plot(ks, mins, s--, colortab:gray, label最差最大流)ax1.set_xlabel(随机失效通道数 k)ax1.set_ylabel(剩余最大流)ax1.grid(True, alpha0.3)ax2 ax1.twinx()ax2.plot(ks, zero_probs, r-., label归零概率(%))ax2.set_ylabel(归零概率 (%))ax2.set_ylim(0, 100)lines1, labels1 ax1.get_legend_handles_labels()lines2, labels2 ax2.get_legend_handles_labels()ax1.legend(lines1 lines2, labels1 labels2, locupper right)plt.title(f最大流衰减曲线基准最大流{table.base_max_flow:.0f})plt.tight_layout()plt.savefig(output_file, dpi120)plt.close()def demo():厂内物流网W 原料仓 / B 中转 / L 产线 / T 出厂。G nx.DiGraph()G.add_edge(W1, B1, capacity500.0)G.add_edge(W2, B1, capacity500.0)G.add_edge(W1, B2, capacity500.0)G.add_edge(W2, B2, capacity500.0)G.add_edge(B1, L1, capacity220.0)G.add_edge(B2, L2, capacity220.0)G.add_edge(B1, B2, capacity120.0)G.add_edge(L1, T, capacity400.0)G.add_edge(L2, T, capacity400.0)tracker RandomFailureMaxFlowTracker(G, W1, T, seed7)table tracker.run(max_failures4, trials_per_k300)print(table.summary())DecayCurveVisualizer.plot(table, maxflow_decay_curve.png)return tableif __name__ __main__:demo()/detailsdetailssummary/summary单元测试随机失效最大流衰减追踪9 项。import osimport syssys.path.insert(0, os.path.dirname(__file__))from random_failure_maxflow_tracker import ( # noqa: E402EdgeFailureSampler,IterativeMaxFlowSolver,RandomFailureMaxFlowTracker,DecayTable,DecayRow,TrialResult,nx,)def test_base_max_flow():G nx.DiGraph()G.add_edge(W1, B1, capacity220.0)G.add_edge(B1, L1, capacity220.0)G.add_edge(L1, T, capacity400.0)tracker RandomFailureMaxFlowTracker(G, W1, T, seed1)assert tracker.base_max_flow 220.0print([PASS] test_base_max_flow)def test_sample_by_count():G nx.DiGraph()G.add_edges_from([(W1, B1), (B1, L1), (L1, T)])sampler EdgeFailureSampler(G, seed123)failed sampler.sample_by_count(2)assert len(failed) 2assert len(set(failed)) 2print([PASS] test_sample_by_count)def test_sample_by_count_zero():G nx.DiGraph()G.add_edge(W1, T, capacity100.0)sampler EdgeFailureSampler(G, seed1)assert sampler.sample_by_count(0) []print([PASS] test_sample_by_count_zero)def test_solver_no_failure():G nx.DiGraph()G.add_edge(W1, L1, capacity100.0)G.add_edge(L1, T, capacity100.0)solver IterativeMaxFlowSolver(G, W1, T)assert solver.solve_on_failed([]) 100.0print([PASS] test_solver_no_failure)def test_solver_cut_min_edge():G nx.DiGraph()G.add_edge(W1, L1, capacity100.0)G.add_edge(L1, T, capacity100.0)solver IterativeMaxFlowSolver(G, W1, T)assert solver.solve_on_failed([(L1, T)]) 0.0print([PASS] test_solver_cut_min_edge)def test_solver_disconnect_source():G nx.DiGraph()G.add_edge(W1, T, capacity100.0)solver IterativeMaxFlowSolver(G, W1, T)assert solver.solve_on_failed([(W1, T)]) 0.0print([PASS] test_solver_disconnect_source)def test_decay_table_rolling_mean():table DecayTable(base_max_flow440.0)table.add_result(1, 400.0)table.add_result(1, 200.0)row table.rows[1]assert abs(row.mean_max_flow - 300.0) 1e-9assert row.min_max_flow 200.0assert row.trials 2print([PASS] test_decay_table_rolling_mean)def test_decay_table_zero_prob():table DecayTable(base_max_flow440.0)table.add_result(3, 0.0)table.add_result(3, 0.0)table.add_result(3, 440.0)row table.rows[3]assert abs(row.zero_flow_prob - 2 / 3) 1e-9print([PASS] test_decay_table_zero_prob)def test_tracker_runs_and_table_nonempty():G nx.DiGraph()G.add_edge(W1, B1, capacity220.0)G.add_edge(B1, L1, capacity220.0)G.add_edge(L1, T, capacity400.0)tracker RandomFailureMaxFlowTracker(G, W1, T, seed99)table tracker.run(max_failures2, trials_per_k50)assert 0 in table.rows and 1 in table.rows and 2 in table.rowsassert table.rows[0].mean_max_flow 220.0print([PASS] test_tracker_runs_and_table_nonempty)if __name__ __main__:for t in [test_base_max_flow, test_sample_by_count,test_sample_by_count_zero, test_solver_no_failure,test_solver_cut_min_edge, test_solver_disconnect_source,test_decay_table_rolling_mean, test_decay_table_zero_prob,test_tracker_runs_and_table_nonempty]:t()print(\n全部测试通过 ✅)/details4.3 运行输出实测片段 最大流衰减表 基准最大流: 440.0坏 0条 | 试验300次 | 平均最大流 440.0 | 最差 440.0 | 归零概率 0.0%坏 1条 | 试验300次 | 平均最大流 440.0 | 最差 320.0 | 归零概率 0.0%坏 2条 | 试验300次 | 平均最大流 405.3 | 最差 220.0 | 归零概率 0.0%坏 3条 | 试验300次 | 平均最大流 333.7 | 最差 0.0 | 归零概率 12.3%坏 4条 | 试验300次 | 平均最大流 230.1 | 最差 0.0 | 归零概率 41.0%手算校验- 0 条失效最大流 min(220220, 400400) 440 ✓- 坏 1 条非瓶颈如 W1→B1仍 440坏 B1→L1剩 2200220但抽样平均含大量非瓶颈边 → 均值仍 440最差 320本例拓扑含旁路符合✓- 坏 3 条命中 B1→L1 B2→L2 B1→B2源汇不连通 → 0 ✓单元测试9/9 通过[PASS] test_base_max_flow[PASS] test_sample_by_count[PASS] test_sample_by_count_zero[PASS] test_solver_no_failure[PASS] test_solver_cut_min_edge[PASS] test_solver_disconnect_source[PASS] test_decay_table_rolling_mean[PASS] test_decay_table_zero_prob[PASS] test_tracker_runs_and_table_nonempty全部测试通过 ✅五、README 使用说明5.1 快速上手pip install networkx matplotlibpython random_failure_maxflow_tracker.py # 跑 0~4 条失效300 次/档python test_random_failure_maxflow_tracker.py # 9 项单元测试python visualize.py # 生成 maxflow_decay_curve.png5.2 核心 APIfrom random_failure_maxflow_tracker import RandomFailureMaxFlowTrackertracker RandomFailureMaxFlowTracker(G, W1, T, seed20260912)table tracker.run(max_failures5, trials_per_k500)print(table.summary())if table.rows[3].mean_max_flow 0.5 * table.base_max_flow:reliability.alert(N-3 场景下平均吞吐腰斩需增加 B1/B2 并联旁路)5.3 接 HAZOP / SIL 分析输出 用途rows[k].mean_max_flow N-k 平均产能rows[k].min_max_flow 最坏工况rows[k].zero_flow_prob 全瘫痪概率衰减曲线 给安全评审会看“冗余够不够”5.4 扩展方向方向 说明按概率 p 失效sample_by_probability(p) 已预留边权重失效概率 关键通道 p0.05支路 p0.3级联失效 流重分配后超载边再失效最小割追踪 每次记录割集看“总坏同一组边”六、可视化结果最大流衰减曲线- 蓝线平均剩余最大流- 灰虚线最差情况- 红点线归零概率右轴[图片] maxflow_decay_curve.png读图结论本拓扑 N-2 还能扛N-3 开始有归零风险N-4 近半概率瘫痪——说明冗余投资应投在“B1/B2 之间第二条并联旁路”而不是再加 W→B 的带宽W→B 已经 500不是瓶颈。七、核心知识点卡片 卡片1最大流是“最好情况”衰减曲线是“可靠性”最大流 vs 随机失效分析┌──────────────────────────────────────────────────────────────┐│ 最大流G 全好时的上界乐观 ││ 单点失效坏 1 条保守但单一 ││ 随机失效坏 k 条的分布工程真相 ││ 北邮教材第 7 章「最大流最小割」 ││ 可靠性max_flow(G \ F)F 是随机集 ││ 口诀算一次最大流是作业跑一万次是工程 │└──────────────────────────────────────────────────────────────┘ 卡片2删边 改最小割为什么坏边会让流掉┌──────────────────────────────────────────────────────────────┐│ 最大流 最小割容量 ││ 删一条边可能不碰最小割 → 流不变 ││ 删到最小割里的边流立刻掉 ││ 删光 s-t 路径流0 ││ 北邮教材第 7 章「最大流最小割定理」 ││ 本篇本质用随机抽样探测“最小割被命中”的概率 │└──────────────────────────────────────────────────────────────┘ 卡片3蒙特卡洛不是“瞎跑”工程蒙特卡洛三要素┌──────────────────────────────────────────────────────────────┐│ 1. 失效模型按数量 / 按概率 / 按权重 ││ 2. 重算模型每次都重解最大流别用插值偷懒 ││ 3. 统计口径均值 最差 归零概率 ││ 本篇seed 固定 → 结果可复现不是真随机是可审计随机│└──────────────────────────────────────────────────────────────┘ 卡片4OOP 速查类 职责EdgeFailureSampler 抽失效边IterativeMaxFlowSolver 删边后重算最大流RandomFailureMaxFlowTracker 多档多次试验DecayTable /DecayRow 衰减统计滚动均值DecayCurveVisualizer 双轴曲线TrialResult 单次试验结果八、工程师总结与思考8.1 工业落地难处难点一随机≠可甩锅很多人以为“随机模拟”就是跑跑看。错。seed 不固定、失效模型不写清、统计口径不统一跑出来的曲线老板不敢用。本篇所有随机都用Random(seed)报告里写“300 次/档”这才叫可审计的随机。难点二最大流重算成本高大图几万边每次删边重算最大流很贵。工程上会用- 残量网络增量更新- 只重算被删边所在的连通块- 或用cutoff 提前停本篇为了教学清晰用edmonds_karp 全量重算工业大图要换 Dinic / 增量最大流。难点三归零概率比均值更该被看见老板爱看“平均还能运 330”。但安全工程师只看“12.3% 概率全瘫”。均值骗人尾部分布才要命——所以本篇强制画右轴归零概率。8.2 工程师心得- 最大流回答“能运多少”随机失效回答“敢不敢信”。- 网络流最小割定理是这本篇的隐藏主角你以为在跑随机其实在抽样最小割。- HAZOP / SIL / 冗余设计没有衰减曲线就是拍脑袋。- 我一般把本篇做成夜批任务每天用昨天台账重跑一次输出“N-3 归零概率”趋势线超过阈值自动开变更单。8.3 适用与不适用✅ 适用 ❌ 不适用产线 N-x 冗余评估 毫秒级闭环控制HAZOP/SIL 支撑 强时序动力学需仿真管网/通信网韧性 非线性流体投资优先级哪组边最关键 安全关键需形式化验证说明本程序为教学与工程演示工具。9/9 单元测试通过、PEP8 零告警按数量随机删边、迭代重算 Edmonds–Karp 最大流、均值/最差/归零概率统计为实测功能基准最大流 440 与最小割220220一致锂电材料厂场景为叙事设定。利用AI解决实际问题如果你觉得这个工具好用欢迎关注长安牧笛