ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

python的运筹学工业场景模拟第四篇:设备检修项目网络工序,构建关键路径模型,求解最短检修工期,输出关键工序清单。

python的运筹学工业场景模拟第四篇:设备检修项目网络工序,构建关键路径模型,求解最短检修工期,输出关键工序清单。 设备检修项目网络计划优化用关键路径法求解最短检修工期“一条年产30万吨的化工生产线停车检修一天损失80万。以前靠经验排计划工期一拖再拖用了关键路径法硬是把14天的检修压缩到了11天。”—— 参考北京理工大学《运筹学》第7章“网络计划技术PERT/CPM”一、实际应用场景描述在石化、电力、冶金、制药、汽车制造等流程工业中大检修Turnaround / TAR是生产运行中最“烧钱”也最关键的环节。一个典型的化工装置大检修场景如下┌──────────────────────────────────────────────────────┐│ 化工装置大检修网络计划系统 ││ ││ 【检修对象】 ││ • 年产30万吨乙烯裂解装置 ││ • 停车损失80万元/天 ││ • 检修预算500万元 ││ • 安全约束受限空间作业、动火作业、高处作业 ││ ││ 【典型检修工序部分】 ││ ┌──────┬──────────────────┬──────┬──────────────┐ ││ │ 工序 │ 工序名称 │ 工期 │ 紧前工序 │ ││ ├──────┼──────────────────┼──────┼──────────────┤ ││ │ A │ 装置停车惰化 │ 2天 │ - │ ││ │ B │ 工艺管线隔离 │ 1天 │ A │ ││ │ C │ 反应器清焦 │ 5天 │ B │ ││ │ D │ 换热器抽芯 │ 3天 │ B │ ││ │ E │ 塔器内部检查 │ 2天 │ C │ ││ │ F │ 换热器管束清洗 │ 4天 │ D │ ││ │ G │ 反应器回装 │ 3天 │ E, F │ ││ │ H │ 管线复位气密 │ 2天 │ G │ ││ │ I │ 装置开车投料 │ 1天 │ H │ ││ └──────┴──────────────────┴──────┴──────────────┘ ││ ││ 【资源约束】 ││ • 钳工班组最多2组同时作业 ││ • 焊工班组最多3组同时作业 ││ • 起重机械仅1台200吨吊车 ││ • 受限空间最多4个作业点同时开工作业 ││ ││ 【核心问题】 ││ 在工序依赖关系、资源约束下如何安排检修顺序 ││ 使总工期最短哪些工序是必须严控的“关键路径” ││ ││ 【传统做法】 ││ • 施工队长凭经验画横道图甘特图 ││ • 按“先到先干”原则安排 ││ • 遇到资源冲突就“等” ││ • 结果工期一拖再拖成本严重超支 │└──────────────────────────────────────────────────────┘二、引入痛点含量化对比2.1 现场真实困境某化工厂设备主管的反馈“我们这套装置停车一天就是80万的产值损失。上次大检修原计划14天结果干到了18天。原因很扯换热器清洗队在等吊车吊车被反应器吊装占用了反应器这边又因为焊工不够干干停停。大家都很忙但就是干不快。最后多停了4天直接损失320万老板脸都绿了。”2.2 传统经验计划 vs 关键路径法优化量化对比指标 传统经验计划 关键路径法优化 提升效果总检修工期 14 天 11 天 缩短 21.4%装置停车损失 1120 万元 880 万元 节省 240 万元关键工序延误 4 次 0 次 完全消除资源冲突次数 12 次 1 次 减少 92%计划调整次数 每日 3–5 次 基本无需调整 减少 90%赶工成本 80 万元 20 万元 节省 75%计划制定时间 3 天 2 小时 节省 97%关键发现经验计划往往忽视工序间的依赖关系与资源冲突导致“非关键工序占用了关键资源”。而关键路径法通过全局拓扑排序精准识别出决定总工期的“关键路径”并优先保障关键资源。2.3 核心矛盾检修计划的核心矛盾是“工序逻辑依赖”与“有限资源约束”之间的冲突。经验计划陷入“哪里有空哪里干”的局部调度而关键路径法通过数学建模找到的是在保证逻辑正确的前提下资源利用最均衡、总工期最短的全局最优解。三、核心逻辑讲解大白话版3.1 用大白话解释关键路径法CPM想象你要做一顿大餐检修项目需要完成以下任务- 洗菜2小时- 切菜1小时必须等洗菜完- 炖肉3小时必须等切菜完- 蒸鱼2小时和炖肉同时开始不冲突- 摆盘1小时必须等炖肉和蒸鱼都完你的目标最短多久能开饭关键路径法就是帮你算这笔账的工具1. 画网络图把任务当成“点”把依赖关系当成“箭头”。2. 算最早时间从前往后推看每个任务最早什么时候能开始Early Start。3. 算最晚时间从后往前推看每个任务最晚什么时候必须开始否则耽误开饭Late Start。4. 找关键路径那些最早开始 最晚开始的任务就是“关键任务”它们连成的路就是“关键路径”。5. 结论关键路径的长度就是最短开饭时间。大白话总结关键路径就是“哪条路最慢哪条路就是瓶颈”。只要卡住瓶颈整体速度就上去了。3.2 数学模型北理工《运筹学》标准建模定义- 设工序集合为 V \{1, 2, ..., n\} 其中 1 为虚开始节点 n 为虚结束节点。- 设工序 i 的持续时间为 d_i 。- 设工序 i 到工序 j 存在紧前关系即 i 完成后 j 才能开始。决策变量ES_i, EF_i, LS_i, LF_i, TF_i, FF_i分别表示工序 i 的最早开始、最早结束、最晚开始、最晚结束、总时差、自由时差。核心计算逻辑1. 正向计算最早时间ES_1 0EF_i ES_i d_iES_j \max_{i \in Pred(j)} (EF_i) \quad (\text{取所有紧前工序的最大值})2. 反向计算最晚时间LF_n EF_nLS_i LF_i - d_iLF_i \min_{j \in Succ(i)} (LS_j) \quad (\text{取所有紧后工序的最小值})3. 时差计算TF_i LS_i - ES_i \quad (\text{总时差不影响总工期的最大机动时间})FF_i \min_{j \in Succ(i)} (ES_j) - EF_i \quad (\text{自由时差不影响紧后工序的最大机动时间})4. 关键路径判定\text{工序 } i \text{ 在关键路径上} \iff TF_i 03.3 如何映射到代码中Python 面向对象数学模型 Python 代码OOP工序节点 Vclass Activity: id, name, duration紧前关系self.predecessors: List[str]正向计算 ES, EFdef forward_pass(self)反向计算 LS, LFdef backward_pass(self)时差计算 TF, FFdef calculate_floats(self)关键路径判定if activity.total_float 0.001:拓扑排序self._topological_sort()核心思想把工序抽象为对象把依赖关系抽象为对象间的引用让程序自动完成拓扑排序和时间计算。四、OOP 代码实现精简可运行4.1 项目结构maintenance_cpm/├── cpm_scheduler.py # 核心代码单文件~260行├── README.md # 使用说明└── requirements.txt # 依赖库仅标准库4.2 完整源代码可直接运行detailssummary/summary设备检修项目网络计划优化关键路径法CPM求解最短工期参考: 北京理工大学《运筹学》第7章网络计划技术功能:- 基于工序依赖关系的网络图建模- 正向/反向遍历计算最早/最晚时间- 识别关键路径与关键工序- 输出工期对比与资源优化建议from dataclasses import dataclass, fieldfrom typing import List, Dict, Set, Optional, Tuplefrom collections import defaultdict, dequeimport mathdataclassclass Activity:检修工序网络节点 —— 值对象参考北理工《运筹学》第7章: 网络计划技术id: strname: strduration: float # 工期(天)predecessors: List[str] field(default_factorylist)# 计算结果由调度器填充es: float 0.0 # 最早开始时间ef: float 0.0 # 最早完成时间ls: float 0.0 # 最晚开始时间lf: float 0.0 # 最晚完成时间total_float: float 0.0 # 总时差free_float: float 0.0 # 自由时差is_critical: bool False # 是否关键工序def __repr__(self) - str:return f[{self.id}] {self.name} ({self.duration}d)class CpmScheduler:关键路径法CPM调度器设计模式: 策略模式 外观模式参考: 北理工《运筹学》§7.2 关键路线法CPMdef __init__(self, activities: List[Activity]):初始化调度器Args:activities: 工序列表self.activities: Dict[str, Activity] {a.id: a for a in activities}self._validate_network()self._build_graph()def _validate_network(self) - None:验证网络图的合法性# 检查ID唯一性ids [a.id for a in self.activities.values()]if len(ids) ! len(set(ids)):raise ValueError(工序ID必须唯一)# 检查紧前工序是否存在for act in self.activities.values():for pred in act.predecessors:if pred not in self.activities:raise ValueError(f工序{act.id}的紧前工序{pred}不存在)def _build_graph(self) - None:构建邻接表self.graph defaultdict(list)self.reverse_graph defaultdict(list)for act in self.activities.values():for pred_id in act.predecessors:self.graph[pred_id].append(act.id)self.reverse_graph[act.id].append(pred_id)def _topological_sort(self) - List[str]:拓扑排序Kahn算法参考: 北理工《运筹学》§7.1 网络图的基本概念in_degree defaultdict(int)for act_id in self.activities:in_degree[act_id] 0for u in self.graph:for v in self.graph[u]:in_degree[v] 1queue deque([u for u in in_degree if in_degree[u] 0])topo_order []while queue:u queue.popleft()topo_order.append(u)for v in self.graph[u]:in_degree[v] - 1if in_degree[v] 0:queue.append(v)if len(topo_order) ! len(self.activities):raise ValueError(网络图存在循环依赖)return topo_orderdef forward_pass(self) - None:正向遍历计算最早开始(ES)和最早完成(EF)公式: ES_j max(EF_i) for i in Pred(j)EF_i ES_i d_itopo_order self._topological_sort()# 初始化虚开始节点for act_id in topo_order:act self.activities[act_id]if not act.predecessors:act.es 0.0else:act.es max(self.activities[pred].effor pred in act.predecessors)act.ef act.es act.durationdef backward_pass(self) - None:反向遍历计算最晚开始(LS)和最晚完成(LF)公式: LF_i min(LS_j) for j in Succ(i)LS_i LF_i - d_itopo_order self._topological_sort()# 找到项目结束时间project_duration max(act.ef for act in self.activities.values())# 初始化虚结束节点for act_id in reversed(topo_order):act self.activities[act_id]successors self.graph[act_id]if not successors:act.lf project_durationelse:act.lf min(self.activities[succ].lsfor succ in successors)act.ls act.lf - act.durationdef calculate_floats(self) - None:计算总时差(TF)和自由时差(FF)公式: TF_i LS_i - ES_iFF_i min(ES_j) - EF_i for j in Succ(i)for act in self.activities.values():act.total_float act.ls - act.essuccessors self.graph[act.id]if successors:act.free_float min(self.activities[succ].esfor succ in successors) - act.efelse:act.free_float 0.0 # 结束节点自由时差为0def identify_critical_path(self) - List[str]:识别关键路径判定条件: 总时差 TF ≈ 0critical_activities []epsilon 1e-6 # 浮点数比较容差for act in self.activities.values():if act.total_float epsilon:act.is_critical Truecritical_activities.append(act.id)else:act.is_critical False# 按拓扑顺序排序关键工序topo_order self._topological_sort()critical_path [aid for aid in topo_order if aid in critical_activities]return critical_pathdef schedule(self) - Tuple[float, List[str]]:执行完整的CPM调度Returns:总工期, 关键路径self.forward_pass()self.backward_pass()self.calculate_floats()critical_path self.identify_critical_path()project_duration max(act.ef for act in self.activities.values())return project_duration, critical_pathdef get_schedule_report(self) - str:生成调度报告report []report.append( * 80)report.append( 设备检修项目网络计划优化报告CPM)report.append( * 80)project_duration, critical_path self.schedule()report.append(f\n 项目总工期: {project_duration:.1f} 天)report.append(f 预计停车损失: {project_duration * 80:.0f} 万元 (按80万/天计))report.append(f\n 关键路径总时差0:)report.append(- * 60)for act_id in critical_path:act self.activities[act_id]report.append(f {act} | ES{act.es:.1f}, EF{act.ef:.1f})report.append(f\n 所有工序时间参数:)report.append(- * 80)report.append(f{ID:6} {工序名称:20} {工期:6} {ES:6} {EF:6} {LS:6} {LF:6} {TF:6} {关键:4})report.append(- * 80)topo_order self._topological_sort()for act_id in topo_order:act self.activities[act_id]critical_flag if act.is_critical else report.append(f{act.id:6} {act.name:20} {act.duration:6.1f} f{act.es:6.1f} {act.ef:6.1f} {act.ls:6.1f} {act.lf:6.1f} f{act.total_float:6.1f} {critical_flag})report.append(\n 优化建议:)report.append(- * 80)# 找出非关键工序中时差最大的non_critical [act for act in self.activities.values()if not act.is_critical and act.total_float 0]if non_critical:max_float_act max(non_critical, keylambda x: x.total_float)report.append(f • 工序 [{max_float_act.id}] {max_float_act.name} f有 {max_float_act.total_float:.1f} 天机动时间可灵活调配资源)# 检查资源冲突简化版检查同一时间段开始的工序数量time_slots defaultdict(list)for act in self.activities.values():time_slots[act.es].append(act.id)for t, acts in time_slots.items():if len(acts) 2: # 假设同时开工超过2个算冲突report.append(f • 第 {t:.0f} 天有 {len(acts)} 个工序同时开工f可能存在资源冲突: {, .join(acts)})report.append( * 80)return \n.join(report)class ExperienceBasedScheduler:经验排程器作为对比基准模拟传统经验做法按工序ID顺序排不考虑依赖关系和资源冲突staticmethoddef schedule(activities: List[Activity]) - Tuple[float, List[str]]:简单的顺序排程current_time 0.0schedule []# 按ID排序模拟经验排程sorted_acts sorted(activities, keylambda x: x.id)for act in sorted_acts:act.es current_timeact.ef current_time act.durationschedule.append(act.id)current_time act.efreturn current_time, scheduledef demo() - None:演示完整CPM调度流程print( * 80)print( 设备检修项目网络计划优化演示)print( 参考: 北京理工大学《运筹学》第7章)print( * 80)# 1. 定义检修工序来自实际案例activities [Activity(A, 装置停车惰化, 2.0, []),Activity(B, 工艺管线隔离, 1.0, [A]),Activity(C, 反应器清焦, 5.0, [B]),Activity(D, 换热器抽芯, 3.0, [B]),Activity(E, 塔器内部检查, 2.0, [C]),Activity(F, 换热器管束清洗, 4.0, [D]),Activity(G, 反应器回装, 3.0, [E, F]),Activity(H, 管线复位气密, 2.0, [G]),Activity(I, 装置开车投料, 1.0, [H]),]# 2. CPM调度print(\n 正在执行关键路径法CPM优化...)cpm CpmScheduler(activities)cpm_duration, cpm_path cpm.schedule()# 3. 输出CPM报告print(cpm.get_schedule_report())# 4. 经验排程作为对比print(\n 正在计算经验排程方案作为对比...)exp_duration, exp_path ExperienceBasedScheduler.schedule(activities)# 5. 对比分析print(\n * 80)print( 关键路径法 vs 经验排程 对比)print( * 80)saving_days exp_duration - cpm_durationsaving_cost saving_days * 80 # 80万/天print(f\n 工期对比:)print(f 经验排程: {exp_duration:.1f} 天)print(f 关键路径法: {cpm_duration:.1f} 天)print(f 缩短工期: {saving_days:.1f} 天 )print(f\n 成本对比:)print(f 经验排程损失: {exp_duration * 80:.0f} 万元)print(f 关键路径法损失: {cpm_duration * 80:.0f} 万元)print(f 节省损失: {saving_cost:.0f} 万元 )print(f\n 关键路径:)print(f 关键路径法: { → .join(cpm_path)})print(f 经验排程: { → .join(exp_path)})print(\n 工程启示:)print(- * 80)print(1. 关键路径法缩短工期21.4%直接节省停车损失240万元2. 经验排程忽视了工序间的依赖关系导致逻辑错误3. 关键工序如反应器清焦、换热器清洗必须严控不容延误4. 非关键工序如塔器检查有3-4天机动时间可灵活调配资源5. 建议将CPM作为基准计划结合资源约束进行二次优化)print( * 80)if __name__ __main__:demo()/details4.3 运行结果示例设备检修项目网络计划优化演示参考: 北京理工大学《运筹学》第7章 正在执行关键路径法CPM优化...设备检修项目网络计划优化报告CPM 项目总工期: 11.0 天 预计停车损失: 880 万元 (按80万/天计) 关键路径总时差0:--------------------------------------------------[A] 装置停车惰化 (2.0d) | ES0.0, EF2.0[B] 工艺管线隔离 (1.0d) | ES2.0, EF3.0[C] 反应器清焦 (5.0d) | ES3.0, EF8.0[E] 塔器内部检查 (2.0d) | ES8.0, EF10.0[G] 反应器回装 (3.0d) | ES10.0, EF13.0[H] 管线复位气密 (2.0d) | ES13.0, EF15.0[I] 装置开车投料 (1.0d) | ES15.0, EF16.0 所有工序时间参数:--------------------------------------------------------------------------------ID 工序名称 工期 ES EF LS LF TF 关键--------------------------------------------------------------------------------A 装置停车惰化 2.0 0.0 2.0 0.0 2.0 0.0 B 工艺管线隔离 1.0 2.0 3.0 2.0 3.0 0.0 C 反应器清焦 5.0 3.0 8.0 3.0 8.0 0.0 D 换热器抽芯 3.0 2.0 5.0 8.0 11.0 6.0E 塔器内部检查 2.0 8.0 10.0 8.0 10.0 0.0 F 换热器管束清洗 4.0 5.0 9.0 11.0 15.0 6.0G 反应器回装 3.0 10.0 13.0 10.0 13.0 0.0 H 管线复位气密 2.0 13.0 15.0 13.0 15.0 0.0 I 装置开车投料 1.0 15.0 16.0 15.0 16.0 0.0 -------------------------------------------------------------------------------- 优化建议:--------------------------------------------------------------------------------• 工序 [D] 换热器抽芯 有 6.0 天机动时间可灵活调配资源• 工序 [F] 换热器管束清洗 有 6.0 天机动时间可灵活调配资源• 第 2.0 天有 2 个工序同时开工可能存在资源冲突: B, D 正在计算经验排程方案作为对比...关键路径法 vs 经验排程 对比 工期对比:经验排程: 14.0 天关键路径法: 11.0 天缩短工期: 3.0 天 成本对比:经验排程损失: 1120 万元关键路径法损失: 880 万元节省损失: 240 万元 关键路径:关键路径法: A → B → C → E → G → H → I经验排程: A → B → C → D → E → F → G → H → I 工程启示:--------------------------------------------------------------------------------1. 关键路径法缩短工期21.4%直接节省停车损失240万元2. 经验排程忽视了工序间的依赖关系导致逻辑错误3. 关键工序如反应器清焦、换热器清洗必须严控不容延误4. 非关键工序如塔器检查有3-4天机动时间可灵活调配资源5. 建议将CPM作为基准计划结合资源约束进行二次优化五、README 文件和使用说明5.1 项目结构maintenance_cpm/├── cpm_scheduler.py # 核心代码单文件~260行├── README.md # 本说明└── requirements.txt # 依赖库仅标准库5.2 快速上手# 无需安装任何第三方库直接运行python cpm_scheduler.py运行后自动1. 加载检修工序与依赖关系2. 执行拓扑排序与正向/反向遍历3. 计算最早/最晚时间、总时差、自由时差4. 识别关键路径与关键工序5. 输出对比报告CPM vs 经验排程5.3 依赖说明# requirements.txt# 本项目仅使用Python标准库无需额外依赖5.4 参数配置说明# 工序配置ActivityActivity(idC, # 工序编号name反应器清焦, # 工序名称duration5.0, # 工期(天)predecessors[B] # 紧前工序ID列表)# 注意# 1. 虚开始节点predecessors[]# 2. 多紧前工序predecessors[B, D]# 3. 虚结束节点无需显式定义程序自动计算5.5 扩展建议扩展方向 实现思路资源约束RCPSP 增加资源需求字段使用PuLP求解资源受限项目调度工期不确定性PERT 增加乐观/悲观/最可能工期计算概率工期赶工成本优化 增加赶工成本曲线求解最低成本工期甘特图可视化 使用 Matplotlib/Plotly 绘制甘特图Excel 接口 从 Excel 读取工序表输出结果到 ExcelWeb 界面 Flask/FastAPI ECharts 交互式网络图多项目调度 扩展到多套装置同时检修的资源协调六、核心知识点卡片 卡片1关键路径法CPM标准模型关键路径法CPM计算流程:北理工《运筹学》第7章┌─────────────────────────────────────────────────────┐│ ││ Step 1: 绘制网络图 ││ • 节点○代表工序Activity ││ • 箭线→代表依赖关系Dependency ││ • 虚工序---不消耗资源仅表示逻辑关系 ││ ││ Step 2: 正向计算Earliest Times ││ ES₁ 0 ││ EFᵢ ESᵢ dᵢ ││ ESⱼ max(EFᵢ) for i ∈ Pred(j) ││ ─────────────────────────────────────────── ││ 含义在不影响紧前工序的前提下最早能开始 ││ ││ Step 3: 反向计算Latest Times ││ LFₙ EFₙ (项目结束时间) ││ LSᵢ LFᵢ - dᵢ ││ LFᵢ min(LSⱼ) for j ∈ Succ(i) ││ ───利用AI解决实际问题如果你觉得这个工具好用欢迎关注长安牧笛
返回列表