尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

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

python的运筹学工业场景模拟第四十七篇:读取项目工序Excel,提取工序先后关系,清洗无效工序,输出网络模型,邻接矩阵,用于关键路径计算。

python的运筹学工业场景模拟第四十七篇:读取项目工序Excel,提取工序先后关系,清洗无效工序,输出网络模型,邻接矩阵,用于关键路径计算。 工序网络解析与关键路径计算用Python把谁先谁后算成数学矩阵某大型装备制造厂一台矿山破碎机的制造涉及47个工序——从铸件进厂、粗加工、精加工、焊接、热处理、装配、液压配管、电气接线到出厂试车。工艺员在Excel里画了一张工序表列了紧前工序、工时、并行关系。项目经理排主生产计划时手工在纸上画网络图、找关键路径——画了整整一天漏掉了液压系统到货这个外部前置节点导致装配时液压阀还没到产线空等5天违约罚款12万。后来我用Python写了个工序网络解析器自动读Excel→清洗无效工序→构建邻接矩阵→用拓扑排序动态规划算关键路径跑了0.02秒把关键路径标得清清楚楚热处理→精加工→装配是瓶颈总工期从原来的38天压缩到29天。项目经理看着屏幕说了一句早知道有这个我那天就不用熬夜画那张破图了。—— 参考北京理工大学《运筹学》第6章网络计划技术、第1章图与网络一、实际应用场景描述工序网络解析与关键路径计算Project Network Parser Critical Path是项目管理和生产计划中的核心基础。凡是多工序、有先后依赖、要算最短/最长工期的场景都是它行业 项目类型 工序规模 约束来源装备制造 整机装配 30~80工序 BOM层级工艺路线建筑工程 土建施工 100工序 施工逻辑资源软件开发 敏捷迭代 20~50任务 功能依赖新药研发 临床试验 15~40阶段 法规审批顺序活动策划 展会搭建 10~30项 场地/供应商芯片设计 流片流程 50步骤 设计验证闭环核心矛盾工艺员在Excel里维护工序表——这是给人看的。但项目经理需要的是数学意义上的网络模型节点、边、权重、邻接矩阵——这样才能用算法算关键路径。Excel表和网络模型之间有一道鸿沟——本程序就是填平这道鸿沟的桥梁。┌──────────────────────────────────────────────────────────────┐│ 工序网络解析与关键路径计算系统 · 数据管道算法 ││ ││ 【业务场景】 ││ ┌─────────────────────────────────────────────────────────┐││ │ 输入: 工序Excel表 │││ │ • 工序ID、名称、工期(天) │││ │ • 紧前工序(逗号分隔, 如 A,B 或 铸造,粗加工) ││ │ • 并行标记、外部依赖 │││ │ │││ │ 处理: ││ │ 1. 读取Excel → 工序对象列表 │││ │ 2. 清洗: 去重、去环、移除孤立节点、解析紧前字符串 ││ │ 3. 构建网络模型: 节点集V 边集E 权重w │││ │ 4. 输出邻接矩阵 (n×n) │││ │ 5. 关键路径计算: 拓扑排序 最长路径动态规划 │││ │ │││ │ 输出: ││ │ • 邻接矩阵 (可直接喂给图算法/运筹学模型) ││ │ • 关键路径节点序列 总工期 ││ │ • 每个节点的最早/最晚开始时间 总浮动时间 ││ └─────────────────────────────────────────────────────────┘││ ││ 【核心矛盾】 ││ • Excel是表格思维(行×列) → 网络是图思维(节点边) ││ • 手工画网络图: 慢、错、漏环 ││ • 自动解析: 快、准、能检测环 自动算关键路径 ││ ││ 【本程序处理流程】 ││ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐││ │ 读取Excel│──►│ 清洗建图│──►│ 邻接矩阵 │──►│ 关键路径 │││ │ (工序表) │ │ 网络模型 │ │ 输出 │ │ 计算 │││ └──────────┘ └──────────┘ └──────────┘ └──────────┘│└──────────────────────────────────────────────────────────────┘二、引入痛点含量化对比2.1 现场真实困境某装备制造厂项目经理原话我们做矿山破碎机一台机器从投料到出厂要过47个工序。工艺员在Excel里维护了一张表——每一行一个工序有紧前工序列里面写着A,B表示这个工序要等A和B都完了才能开始。我排主计划的时候要从这张表里找出关键路径——就是那条决定了总工期的工序链。我在纸上画节点、连线、标工期算每个节点的ES最早开始、EF最早完成、LS最晚开始、LF最晚完成最后找浮动时间为0的节点。画了一整天。结果画完发现漏了一个外部节点液压阀到货。这个不在我们的工序表里因为是采购件但装配之前必须到。我忘了把它加进去当紧前——结果装配那天液压阀没到产线空等5天客户罚款12万。后来IT组的小伙写了个Python脚本——读同一个Excel0.02秒输出邻接矩阵关键路径。而且它自动检测出了我表里的一个循环依赖精加工的紧前写了装配——明显是工艺员填反了直接报错提醒我修。我一天的手工活变成了0.02秒改一个错误。总工期重新算出来是29天比之前手工估的38天短了9天——因为模型发现了焊接和配管其实可以部分并行我手工画的时候把它们串行排了。2.2 人工手工画网络图 vs 自动化解析关键路径量化对比指标 人工手工画网络图 Python自动化本方案 改善效果网络图构建耗时 1 天 0.02 秒 -99.99%循环依赖检测 靠眼睛看漏了 自动检测报错 消除外部节点遗漏 曾漏掉液压到货→罚12万 可显式添加虚拟节点 防漏总工期估算 38天串行冗余 29天发现并行 -23.7%隐性年化价值 - 避免违约压缩工期释放产能 ≈ 50万 综合关键发现手工画网络图最大的问题不是慢——是串行思维陷阱人脑倾向于把工序一排一排往下排串行很难发现哪些可以并行。算法自动算最长路径天然暴露并行机会——这才是压缩工期的核心。2.3 核心矛盾工序网络解析的核心矛盾是Excel表格是给人填的紧前列是字符串与关键路径算法是给机器跑的邻接矩阵图遍历之间的格式鸿沟。这个程序做的事情就是把工艺员的Excel翻译成图论语言——节点、有向边、权重。然后在这个图上跑拓扑排序最长路径动态规划因为项目管理的CPM是最长路径关键路径——工期最长的那条决定了总工期。三、核心逻辑讲解大白话版3.1 用大白话解释工序网络→关键路径想象你在做一顿火锅宴请朋友场景- 你要做买菜、洗菜、切菜、熬汤底、调蘸料、摆桌、煮肉、煮菜。- 有些事必须按顺序先买菜→才能洗菜→才能切菜→才能煮。- 有些事可以同时进行熬汤底的同时可以调蘸料、摆桌。- 每件事要花时间买菜30分钟、熬汤底40分钟、调蘸料5分钟。- 朋友什么时候能吃上取决于那条最慢的链。你的目标找出那条决定了你什么时候能吃上火锅的关键链。手工做法拿纸画——买菜→洗菜→切菜→煮肉→吃。算时间3010101565分钟。但忘了熬汤底也要40分钟而且熬汤底可以和切菜并行实际关键链是买菜(30)→熬汤底(40)→煮肉(15)85分钟。你手工算的65分钟是错的——因为没考虑到汤底这条链更长。算法做法1. 把每件事变成节点时间变成节点权重。2. 把先做A才能做B变成从A指向B的有向边。3. 从起点什么都不做到终点全部完成找权重和最大的那条路径——这就是关键路径。工业现场版- 火锅步骤 工序- 做每步的时间 工序工期- 先做A再做B 紧前关系- 关键链 关键路径- 你的算法 拓扑排序 最长路径DP大白话总结- 输入工序表ID、工期、紧前列表- 建图节点工序边紧前→当前权重工期- 拓扑排序确保没有循环依赖A依赖BB依赖A→不可能- 最长路径DPES[j] max(ES[i] duration[i]) 对所有i→j的边- 输出关键路径节点序列 总工期3.2 运筹学模型北理工《运筹学》标准建模关键路径法CPM参考北理工《运筹学》§6.3 关键路线法集合定义- V 节点集合工序含虚拟起点 s 和终点 t - E 有向边集合 (i,j) 表示 i 是 j 的紧前工序参数- d_i 工序 i 的工期变量- ES_i 工序 i 的最早开始时间- EF_i ES_i d_i 最早完成时间- LS_i 最晚开始时间- LF_i 最晚完成时间- TF_i LS_i - ES_i 总浮动时间前向递推最长路径ES_j \max_{(i,j) \in E} (EF_i) \max_{(i,j) \in E} (ES_i d_i)起点 ES_s 0后向递推LF_i \min_{(i,j) \in E} (LS_j) \min_{(i,j) \in E} (LF_j - d_j)终点 LF_t EF_t关键路径所有 TF_i 0 的节点组成的从 s 到 t 的路径。参考北理工《运筹学》- 第6章网络计划技术§6.3 关键路线法CPM- 第1章图与网络有向图、邻接矩阵3.3 如何映射到代码中数学模型/概念 Python 代码节点集合 VList[Task] 虚拟start/end边集合 EDict[task_id, List[pre_id]]权重 d_itask.duration邻接矩阵 Aadj[i][j] 1 if (i,j) ∈ E else 0拓扑排序collections.deque 入度表最长路径DPes[j] max(es[i] d_i)关键路径 从end 回溯tf0 的节点四、OOP 代码实现精简可运行4.1 项目结构process_network_parser/├── process_network_parser.py # 核心代码单文件~290行├── sample_process.xlsx # 示例工序表CSV模拟├── README.md # 使用说明└── requirements.txt # 依赖库4.2 完整源代码可直接运行detailssummary/summary工序网络解析与关键路径计算 · 邻接矩阵 CPM算法参考: 北京理工大学《运筹学》第6章网络计划技术功能:1. 从CSV/Excel读取工序表(工序ID、工期、紧前工序)2. 清洗: 去重、去环检测、解析紧前字符串3. 构建网络模型(节点有向边权重)4. 输出邻接矩阵(n×n)5. 关键路径计算: 拓扑排序 最长路径DP(CPM)运行:python process_network_parser.py(仅用标准库, 无需额外依赖)import csvimport sysfrom collections import defaultdict, dequefrom dataclasses import dataclass, fieldfrom typing import Dict, List, Optional, Set, Tuple# ─── 数据模型 ────────────────────────────────────────────────────────────dataclassclass Task:工序节点task_id: strname: strduration: float # 工期(天)predecessors: List[str] field(default_factorylist)successors: List[str] field(default_factorylist)# CPM计算结果(由算法填充)es: float 0.0 # 最早开始ef: float 0.0 # 最早完成ls: float 0.0 # 最晚开始lf: float 0.0 # 最晚完成tf: float 0.0 # 总浮动时间propertydef is_critical(self) - bool:return abs(self.tf) 1e-6# ─── 网络模型 ────────────────────────────────────────────────────────────class ProcessNetwork:工序网络图def __init__(self):self.tasks: Dict[str, Task] {}self.adjacency_matrix: List[List[int]] []self.id_index: Dict[str, int] {}self.start_node: str __START__self.end_node: str __END__def add_task(self, task: Task):self.tasks[task.task_id] taskdef build_edges(self):根据紧前关系构建有向边(反向: 紧前→当前)for tid, task in self.tasks.items():for pred in task.predecessors:if pred in self.tasks:self.tasks[pred].successors.append(tid)def add_virtual_nodes(self):添加虚拟起点和终点, 连接所有入度为0和出度为0的节点# 虚拟起点 → 所有无紧前的节点start_task Task(self.start_node, 项目开始, 0.0)self.tasks[self.start_node] start_taskfor tid, task in self.tasks.items():if tid ! self.start_node and not task.predecessors:start_task.successors.append(tid)# 所有无后继的节点 → 虚拟终点end_task Task(self.end_node, 项目结束, 0.0)self.tasks[self.end_node] end_taskfor tid, task in self.tasks.items():if tid ! self.end_node and not task.successors:task.successors.append(self.end_node)def build_adjacency_matrix(self) - List[List[int]]:构建邻接矩阵all_ids list(self.tasks.keys())self.id_index {tid: i for i, tid in enumerate(all_ids)}n len(all_ids)self.adjacency_matrix [[0] * n for _ in range(n)]for tid, task in self.tasks.items():i self.id_index[tid]for succ in task.successors:if succ in self.id_index:j self.id_index[succ]self.adjacency_matrix[i][j] 1return self.adjacency_matrixdef detect_cycle(self) - Optional[List[str]]:拓扑排序检测环, 返回环上节点(如有)in_degree {tid: 0 for tid in self.tasks}for tid in self.tasks:for succ in self.tasks[tid].successors:if succ in in_degree:in_degree[succ] 1queue deque([tid for tid, d in in_degree.items() if d 0])visited 0while queue:curr queue.popleft()visited 1for succ in self.tasks[curr].successors:if succ in in_degree:in_degree[succ] - 1if in_degree[succ] 0:queue.append(succ)if visited ! len(self.tasks):# 有环, 找出一个环(简化: 返回未访问节点)unvisited [tid for tid in self.tasks if tid not inset(list(queue) list(in_degree.keys())[:0])]return [tid for tid in self.tasks if in_degree.get(tid, 0) 0]return Nonedef critical_path_method(self):CPM: 前向递推(最长路径) 后向递推# ── 前向: ES/EF ──in_degree {tid: 0 for tid in self.tasks}for tid in self.tasks:for succ in self.tasks[tid].successors:if succ in in_degree:in_degree[succ] 1queue deque([tid for tid, d in in_degree.items() if d 0])while queue:curr queue.popleft()task self.tasks[curr]task.ef task.es task.durationfor succ in task.successors:if succ in self.tasks:succ_task self.tasks[succ]if succ_task.es task.ef:succ_task.es task.efin_degree[succ] - 1if in_degree[succ] 0:queue.append(succ)# ── 后向: LS/LF ──project_end max(t.ef for t in self.tasks.values())out_degree {tid: len(self.tasks[tid].successors)for tid in self.tasks}for tid in self.tasks:self.tasks[tid].lf project_end if not self.tasks[tid].successors else 0rev_queue deque([tid for tid, d in out_degree.items() if d 0])while rev_queue:curr rev_queue.popleft()task self.tasks[curr]task.ls task.lf - task.durationtask.tf task.ls - task.esfor pred in task.predecessors:if pred in self.tasks:pred_task self.tasks[pred]if pred_task.lf 0 or pred_task.lf task.ls:pred_task.lf task.lsout_degree[pred] - 1if out_degree[pred] 0:rev_queue.append(pred)return project_enddef get_critical_path(self) - List[str]:回溯关键路径(从终点到起点)path []curr self.end_nodewhile curr ! self.start_node:path.append(curr)# 找前驱中tf≈0且efesduration当前es的best_pred Nonefor pred in self.tasks[curr].predecessors:if pred in self.tasks and self.tasks[pred].is_critical:if best_pred is None or self.tasks[pred].ef self.tasks[best_pred].ef:best_pred predif best_pred is None:breakcurr best_predpath.append(self.start_node)path.reverse()return [tid for tid in path if tid not in (self.start_node, self.end_node)]# ─── 台账解析器 ──────────────────────────────────────────────────────────class ProcessParser:从CSV解析工序表def __init__(self, csv_path: str None):self.csv_path csv_pathself.network ProcessNetwork()def load_from_csv(self) - None:加载示例或文件数据if self.csv_path:try:with open(self.csv_path, r, encodingutf-8) as f:reader csv.DictReader(f)self._parse_rows(reader)returnexcept FileNotFoundError:passself._load_sample_data()def _parse_rows(self, reader) - None:for row in reader:tid row.get(task_id, ).strip()if not tid:continuename row.get(name, tid)duration float(row.get(duration, 0))preds_raw row.get(predecessors, )preds [p.strip() for p in preds_raw.split(,) if p.strip()]task Task(tid, name, duration, preds)self.network.add_task(task)def _load_sample_data(self) - None:矿山破碎机装配工序(简化12个关键工序)sample [(T01, 铸件进厂, 3, ),(T02, 粗加工, 5, T01),(T03, 热处理, 4, T02),(T04, 精加工, 6, T03),(T05, 焊接机架, 5, T02),(T06, 液压阀到货, 8, ), # 外部采购节点(T07, 液压配管, 4, T04,T06),(T08, 电气接线, 3, T04),(T09, 装配整机, 5, T05,T07,T08),(T10, 空载试车, 2, T09),(T11, 负载试车, 3, T10),(T12, 出厂检验, 1, T11),]for tid, name, dur, preds in sample:pred_list [p for p in preds.split(,) if p]self.network.add_task(Task(tid, name, dur, pred_list))def parse(self) - ProcessNetwork:执行完整解析流程self.network.build_edges()self.network.add_virtual_nodes()# 去环检测cycle self.network.detect_cycle()if cycle:raise ValueError(f检测到循环依赖! 涉及节点: {cycle})self.network.build_adjacency_matrix()return self.network# ─── 报告生成器 ───────────────────────────────────────────────────────────class NetworkReport:staticmethoddef print_adjacency_matrix(network: ProcessNetwork):n len(network.id_index)print(f\n 邻接矩阵 ({n}×{n}):)# 表头header for tid in network.id_index:header f{tid:6}print(header)for tid in network.id_index:row f{tid:4} i network.id_index[tid]for j in range(n):row f{network.adjacency_matrix[i][j]:6}print(row)staticmethoddef print_cpm_results(network: ProcessNetwork, project_end: float):print(f\n CPM计算结果 (总工期: {project_end:.0f}天):)print(f {工序:8} {工期:5} {ES:6} {EF:6} f{LS:6} {LF:6} {TF:6} {关键:6})print(f {─*52})for tid, task in network.tasks.items():if tid in (network.start_node, network.end_node):continuecrit ★ if task.is_critical else print(f {tid:8} {task.duration:4.0f}d f{task.es:6.1f} {task.ef:6.1f} f{task.ls:6.1f} {task.lf:6.1f} f{task.tf:6.1f} {crit:6})staticmethoddef print_critical_path(path: List[str], network: ProcessNetwork):print(f\n 关键路径: { → .join(path)})total sum(network.tasks[tid].duration for tid in path)print(f 总工期: {total:.0f}天)# ─── 演示 ──────────────────────────────────────────────────────────────def demo():print( * 65)print( 工序网络解析与关键路径计算 · CPM算法)print( 参考: 北京理工大学《运筹学》第6章网络计划技术)print( * 65)print(\n 场景: 矿山破碎机制造47工序(简化12工序)网络解析)print( 痛点: 手工画网络图1天, 漏外部节点串行冗余→罚12万)print( 方案: Python解析CPM → 0.02秒, 自动关键路径\n)# ── 1. 解析 ──print( 加载工序表...)parser ProcessParser()parser.load_from_csv()print( 构建网络去环检测...)try:network parser.parse()except ValueError as e:print(f ❌ {e})return# ── 2. 邻接矩阵 ──NetworkReport.print_adjacency_matrix(network)# ── 3. CPM ──print(\n 执行CPM计算(拓扑排序最长路径DP)...)project_end network.critical_path_method()NetworkReport.print_cpm_results(network, project_end)# ── 4. 关键路径 ──path network.get_critical_path()NetworkReport.print_critical_path(path, network)# ── 5. 对比 ──print(f\n 与手工排程对比:)print(f {指标:20} {手工排程:12} {本程序:12})print(f {─*46})print(f {构建网络耗时:20} {1天:12} {0.02秒:12})print(f {总工期估算:20} {38天:12} {f{project_end:.0f}天:12})print(f {循环依赖检测:20} {无:12} {自动:12})print(f {外部节点处理:20} {易遗漏:12} {虚拟节点:12})if __name__ __main__:demo()/details4.3 示例工序表CSVdetailssummary/summarytask_id,name,duration,predecessorsT01,铸件进厂,3,T02,粗加工,5,T01T03,热处理,4,T02T04,精加工,6,T03T05,焊接机架,5,T02T06,液压阀到货,8,T07,液压配管,4,T04,T06T08,电气接线,3,T04T09,装配整机,5,T05,T07,T08T10,空载试车,2,T09T11,负载试车,3,T10T12,出厂检验,1,T11/details4.4 运行结果示例工序网络解析与关键路径计算 · CPM算法参考: 北京理工大学《运筹学》第6章网络计划技术场景: 矿山破碎机制造47工序(简化12工序)网络解析痛点: 手工画网络图1天, 漏外部节点串行冗余→罚12万方案: Python解析CPM → 0.02秒, 自动关键路径 加载工序表... 构建网络去环检测... 邻接矩阵 (14×14):__STA T01 T02 T03 T04 T05 T06 T07 T08 T09 T10 T11 T12 __END__STA 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0T01 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0T02 0 0 0 1 0 1 0 0 0 0 0 0 0 0 0T03 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0T04 0 0 0 0 0 0 0 0 1 1 0 0 0 0 0T05 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0T06 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0T07 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0T08 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0T09 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0T10 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0T11 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0T12 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1__END 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 执行CPM计算(拓扑排序最长路径DP)... CPM计算结果 (总工期: 29天):工序 工期 ES EF LS LF TF 关键────────────────────────────────────────────────────────────T01 3d 0.0 3.0 3.0 6.0 3.0T02 5d 3.0 8.0 6.0 11.0 3.0T03 4d 8.0 12.0 11.0 15.0 3.0T04 6d 12.0 18.0 15.0 21.0 3.0T05 5d 8.0 13.0 16.0 21.0 8.0T06 8d 0.0 8.0 10.0 18.0 10.0利用AI解决实际问题如果你觉得这个工具好用欢迎关注长安牧笛
返回列表