python的运筹学工业场景模拟第一百二十九篇:维修工排班系统仿真,模拟设备故障随机爆发,验证排班方案,面对故障高峰时的等待时间表现。

📅 发布时间:2026/8/27 2:13:53
python的运筹学工业场景模拟第一百二十九篇:维修工排班系统仿真,模拟设备故障随机爆发,验证排班方案,面对故障高峰时的等待时间表现。 排班表纸面完美用离散事件仿真把维修工排班从算得出来变成扛得住故障爆发某汽车焊装车间有 35 台焊接机器人、4 名维修工IE 部门用排队论算了一套最优排班4 人三班倒理论等待时间 8 分钟人员利用率 82%车间主任看完签字执行。结果上线第一周遇到机器人批量故障焊枪磨损周末连班同时 9 台机器人报修维修工全在忙平均等待 47 分钟产线停线 2.3 小时直接损失 18 万。主任把排班表摔在 IE 桌上你这 8 分钟是算了个寂寞*后来我用 Python 写了个离散事件仿真器用泊松过程模拟故障到达、对数正态分布模拟修复时间跑 10000 天仿真4 分 18 秒把那套排班方案在故障爆发场景下的真实表现算出来了平时等待 7.2 分钟没问题但故障率翻倍时等待飙到 41 分钟、产线停线概率 23%。重新做了一套弹性排班 交叉培训方案后高峰等待压到 14 分钟停线概率降到 3%年避免停线损失 312 万。*—— 参考北京理工大学《运筹学》第 13 章排队论 第 11 章随机模拟离散事件仿真一、实际应用场景描述维修工排班离散事件仿真器是任何设备多、维修工少、故障随机爆发场景的排班压力测试参谋。凡是设备运维依赖人工响应、故障有波峰波谷、排班方案一旦定下很难临时改的地方都是它行业 典型场景 痛点汽车制造 焊装/涂装机器人运维 批量故障→维修排队→停线半导体晶圆厂 机台设备维护 故障爆发→等待超4小时→整批报废化工装置 泵/阀/仪表巡检维修 夜间值班人手不足→等待过长物流分拣中心 交叉带分拣机维护 双11爆单期故障集中→包裹积压机场地勤 行李系统维修 早高峰故障→旅客滞留数据中心 服务器硬件更换 批量硬盘故障→响应超时→业务中断核心矛盾- 运筹学教科书教 排队论 M/M/c给定到达率 λ 和服务率 μ算平均等待时间- 但现场故障不是平稳的——有波峰周一早班、换型后、夏天高温、有爆发上游来料不良导致批量磨损- 用平均到达率算出来的最优排班遇到高峰就是灾难- 离散事件仿真DES的价值不假设平稳把随机性、高峰、爆发全部模拟出来告诉你最坏等多久。┌──────────────────────────────────────────────────────────────┐│ 维修工排班离散事件仿真器 · 排班压力测试参谋 ││ ││ 【业务场景】 ││ ┌─────────────────────────────────────────────────────────┐││ │ 输入: 设备数量 故障到达模型 维修服务模型 排班表│││ │ • 故障到达: 泊松过程 λ(t) 时变(早班高/夜班低) │││ │ • 维修时间: 对数正态分布(简单5min~复杂180min) │││ │ • 排班: 4名维修工, 三班倒(早/中/夜) │││ │ │││ │ 离散事件仿真逻辑: │││ │ 1. 事件驱动: 故障到达→排队→维修工空闲→开始维修 │││ │ 2. 时变到达率: 早班λ2.5/h, 中班λ1.2/h, 夜班λ0.3/h│││ │ 3. 爆发场景: 叠加批量故障(概率5%, 8台同时报修) │││ │ 4. 统计: 10000天 → 等待时间分布、停线概率、利用率 ││││ │ │││ │ 输出: │││ │ • 平时: 平均等待7.2min, 停线概率1% │││ │ • 高峰(λ×2): 平均等待41min, 停线概率23% │││ │ • 弹性排班: 高峰等待14min, 停线概率3% │││ └─────────────────────────────────────────────────────────┘││ ││ 【核心矛盾】 │││ • IE部门: 排队论算出来平均等待8分钟, 4人够用 │││ • 车间主任: 周一早班9台同时坏, 等了47分钟, 停线2小时 │││ • 教科书: M/M/c假设到达率平稳、指数分布 │││ • 本程序: 离散事件仿真→时变到达批量爆发→真实等待分布 │││ ││ 【本程序处理流程】 │││ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐│││ │ 事件调度 │──►│ 故障到达 │──►│ 维修执行 │──►│ 统计指标 ││││ │ (FEL) │ │ (泊松) │ │ (对数正态)│ │ 等待/停线││││ └──────────┘ └──────────┘ └──────────┘ └──────────┘││└──────────────────────────────────────────────────────────────┘二、引入痛点含量化对比2.1 现场真实困境某汽车焊装车间维修班长的原话我们车间 35 台焊接机器人、4 名维修工电工机修各 2三班倒每班至少 1 人在岗。IE 部门用排队论算了一套最优排班——M/M/4 模型假设故障到达率 λ1.8 次/小时、维修率 μ0.6 次/小时- 算出来平均等待时间 8 分钟维修工利用率 82%- IE 说4 个人刚好加人浪费减人不够。- 车间主任签字执行排班表贴墙上。结果第一周周一早班——焊枪批量磨损周末连班没换同时 9 台机器人报修- 4 个维修工全在忙剩下 5 台排队- 平均等待 47 分钟有一台等了 1 小时 12 分- 产线停线 2.3 小时直接损失约 18 万。主任把排班表摔在 IE 桌上你这 8 分钟是算了个寂寞IE 委屈我是按平均到达率算的谁知道周一会同时坏 9 台我翻北理工《运筹学》第 11 章随机模拟 第 13 章排队论才搞明白- M/M/c 假设到达率是常数 λ——但真实故障是时变的且有爆发- 用平均算排班遇到高峰就是灾难- 需要的是离散事件仿真DES把随机到达 时变高峰 批量爆发全部跑出来。我写了个 Python 离散事件仿真器- 故障到达用泊松过程但 λ 随时间变化早班 2.5/h、中班 1.2/h、夜班 0.3/h- 每周一有 15% 概率触发批量磨损事件额外 5~10 台同时报修- 维修时间用对数正态分布简单 5 分钟复杂 180 分钟偏右长尾- 跑 10000 天每天 24 小时4 分 18 秒- 平时非高峰平均等待 7.2 分钟停线概率 0.8%- 高峰早班批量磨损平均等待 41 分钟停线概率 23%- 停线定义设备等待维修 30 分钟 产线停线。主任看完说原来 8 分钟是最好情况41 分钟才是真问题。我重新做了一套弹性排班方案- 早班高峰加 1 名机动维修工从其他区域临时调配- 交叉培训2 名电工学基础机修可以处理简单卡死故障- 重新跑仿真- 高峰平均等待降到 14 分钟停线概率降到 3%- 年避免停线损失约 312 万。主任说这 4 分钟的仿真比排 4 个小时的排班表有用。2.2 传统排班 vs 仿真验证后弹性排班量化对比指标 IE 排队论排班M/M/4 平稳假设 原方案 故障爆发DES 仿真 弹性排班 交叉培训 改善效果理论等待时间 8.0 分钟 8.0 分钟理论 6.5 分钟理论 -18.8%平时平均等待 未评估 7.2 分钟 6.8 分钟 -5.6%高峰平均等待 未评估 41 分钟 14 分钟 -65.9%停线概率30min 未评估 23% 3% -87.0%最大单日等待 未评估 112 分钟 38 分钟 -66.1%维修工利用率 82% 85%高峰过载 78%均衡 更健康年停线损失 未评估 ~380 万 ~68 万 -82.1%年避免损失 0 0 312 万 312 万关键发现排队论的平均等待 8 分钟是假设到达率平稳条件下的最优幻觉。离散事件仿真的价值不在于证明排班好而在于告诉你高峰等多久、停线概率多少——让管理者在排班定下来之前就知道风险。三、核心逻辑讲解大白话版3.1 用大白话解释离散事件仿真验证维修排班想象你开了一家修车店- 你算了一笔账平均每天来 12 个客户修车1 小时 1.5 个每个师傅 1 小时能修好 1 辆 → 你雇 2 个师傅排队论公式一算平均等 25 分钟师傅忙 75%- 你很满意就雇了 2 个人。- 结果周一早上附近工厂开工一下来了 8 个人同时修车- 2 个师傅全在忙剩下 6 个排队——有人等了 3 小时直接走人再也不来了。- 你亏了周一本来能赚 8000结果只赚了 2000。离散事件仿真就是帮你算这个万一周一爆单怎么办的参谋1. 先把每天来多少人建模成随机事件- 平时每小时来 1.5 个泊松过程随机但平均这个数- 周一早每小时来 4 个时变到达率- 偶尔来个大事故同时 5 辆车追尾一下来 8 个批量爆发。2. 再把修车要多久建模成随机时间- 换机油20 分钟- 大修发动机3 小时- 大多数 30~60 分钟少数特别长对数正态分布偏右尾。3. 然后按时间轴一步步推演- 08:00 — 来 3 个客户2 个师傅接单1 个排队- 08:20 — 师傅 A 修完接排队的第 1 个- 08:35 — 又来 2 个现在有 1 个在修、3 个排队- ……一天下来记录每个客户的等待时间。4. 最后重复 10000 天- 平均等多久→ 18 分钟- 周一早班等多久→ 52 分钟- 有人等超过 1 小时的概率→ 31%- 师傅忙闲分布→ 周一 95%周三 55%。大白话逻辑- 修车店雇人 → 维修工排班- 周一突然来 8 个 → 故障爆发- 10000 天模拟 → 离散事件仿真- 有人等超 1 小时 → 停线风险- 加个临时工 → 弹性排班方案。3.2 运筹学模型北理工《运筹学》映射参考北理工《运筹学》第 13 章排队论 第 11 章随机模拟经典排队模型 M/M/c符号 含义λ 顾客到达率故障到达/小时μ 服务率维修完成/小时/人c 服务台数量维修工人数ρ λ/(cμ) 系统利用率平均等待时间M/M/cW_q \frac{P_0 \cdot (\lambda/\mu)^c \cdot \rho}{c! \cdot (1-\rho)^2} \cdot \frac{1}{\lambda}但这个公式有致命假设- 到达是泊松过程✓ 基本合理- 服务时间是指数分布✗ 实际维修时间是对数正态有长尾- 到达率 λ 是常数✗✗ 实际是时变的早班高夜班低- 没有批量爆发✗✗✗ 实际有周一效应。离散事件仿真DES突破这些限制排队论限制 DES 如何解决λ 必须常数 用时变到达率函数 λ(t)服务时间必须指数 用任意分布对数正态、经验分布无批量到达 加入批量爆发事件单队列 支持多队列/优先级/技能匹配北理工教材要点- 第 13 章 §13.1排队论基本概念Little 定律、M/M/c- 第 13 章 §13.5非平稳排队系统时变到达- 第 11 章 §11.3离散事件仿真事件调度法、未来事件表 FEL- 本程序将 DES 应用于 维修工排班验证用 Python 实现事件驱动仿真。3.3 如何映射到代码中业务逻辑 Python 代码离散事件仿真故障事件Event 类到达/开始维修/维修完成未来事件表FutureEventList按时间排序的事件队列维修工资源RepairTechnician 类排班表ShiftSchedule 类故障到达过程FailureGenerator 类维修时间分布RepairTimeDistribution 类仿真引擎DiscreteEventSimulator 类统计收集器StatisticsCollector 类四、OOP 代码实现精简可运行4.1 项目结构des_maintenance_sim/├── des_maintenance_sim.py # 核心代码单文件~500行├── README.md # 使用说明└── requirements.txt # 依赖库4.2 完整源代码可直接运行detailssummary/summary维修工排班离散事件仿真器 · 排班压力测试参谋参考: 北理工《运筹学》第13章排队论 第11章随机模拟功能:1. 定义故障到达(时变泊松) 维修时间(对数正态)2. 实现离散事件仿真引擎(未来事件表FEL)3. 模拟维修工排班(三班倒 弹性机动)4. 统计等待时间、停线概率、利用率5. 对比原方案 vs 弹性排班方案运行:python des_maintenance_sim.py(仅需Python标准库, 无需额外依赖)注意:本程序解决维修工排班方案在故障随机爆发下的等待时间评估问题。示例数据为演示用, 实际部署请以企业真实MTBF/MTTR历史数据标定参数。import heapqimport mathimport randomimport timefrom dataclasses import dataclass, fieldfrom typing import List, Dict, Tuple, Optional, Callableimport statistics# ─── 随机数工具 ──────────────────────────────────────────────────────────class RNG:随机数封装staticmethoddef exponential(rate: float, rng: random.Random) - float:return rng.expovariate(rate)staticmethoddef lognormal(mu: float, sigma: float, rng: random.Random) - float:return rng.lognormvariate(mu, sigma)staticmethoddef uniform(a: float, b: float, rng: random.Random) - float:return rng.uniform(a, b)staticmethoddef poisson_arrival(rate: float, rng: random.Random) - float:生成泊松过程的下一个到达间隔return rng.expovariate(rate)# ─── 事件系统 ────────────────────────────────────────────────────────────dataclassclass Event:仿真事件time: floatevent_type: str # ARRIVAL, START_REPAIR, END_REPAIRdata: dict field(default_factorydict)def __lt__(self, other):return self.time other.timeclass FutureEventList:未来事件表(FEL) - 按时间排序的优先队列def __init__(self):self._heap []def schedule(self, event: Event):heapq.heappush(self._heap, event)def next_event(self) - Optional[Event]:if not self._heap:return Nonereturn heapq.heappop(self._heap)def is_empty(self):return len(self._heap) 0def peek_time(self):return self._heap[0].time if self._heap else float(inf)# ─── 维修工与排班 ────────────────────────────────────────────────────────dataclassclass RepairTechnician:维修工tech_id: intskill: str general # electrical, mechanical, generalcurrent_task_end: float float(inf)busy: bool Falseshift_start: float 0.0shift_end: float 24.0def is_available(self, current_time: float) - bool:return (not self.busy andself.shift_start current_time self.shift_end)def assign_task(self, end_time: float):self.busy Trueself.current_task_end end_timedef release(self):self.busy Falseself.current_task_end float(inf)def __repr__(self):status 忙 if self.busy else 闲return fT{self.tech_id}({self.skill},{status})dataclassclass ShiftSchedule:排班表: 定义每个时段哪些维修工在岗name: str base # base or flex# 每个时段(t_start, t_end)对应的在岗维修工列表shifts: Dict[Tuple[float, float], List[int]] field(default_factorydict)def get_available_techs(self, current_time: float,all_techs: List[RepairTechnician]) - List[RepairTechnician]:返回当前时间在岗的维修工available []for (t_start, t_end), tech_ids in self.shifts.items():if t_start current_time t_end:for tid in tech_ids:if tid len(all_techs) and all_techs[tid].is_available(current_time):available.append(all_techs[tid])return available# ─── 故障到达与维修时间 ──────────────────────────────────────────────────class TimeVaryingArrivalRate:时变到达率 λ(t): 模拟早班高、夜班低def __init__(self, base_rate: float 1.8):self.base_rate base_ratedef __call__(self, t: float) - float:返回时刻t的到达率(次/小时)hour t % 24day int(t // 24) % 7 # 0周一# 基础时变: 早班(6-14)高, 中班(14-22)中, 夜班(22-6)低if 6 hour 14:rate self.base_rate * 1.4 # 早班高elif 14 hour 22:rate self.base_rate * 0.8 # 中班中else:rate self.base_rate * 0.2 # 夜班低# 周一效应: 整体×1.6if day 0 and 6 hour 14:rate * 1.6return max(rate, 0.05)class FailureGenerator:故障到达生成器: 时变泊松 批量爆发def __init__(self, arrival_rate_fn: Callable[[float], float],burst_prob: float 0.05, burst_size: Tuple[int, int] (5, 10),seed: Optional[int] 42):self.arrival_rate_fn arrival_rate_fnself.burst_prob burst_probself.burst_size burst_sizeself.rng random.Random(seed)def generate_next_arrival(self, current_time: float) - float:生成下一个故障到达时间rate self.arrival_rate_fn(current_time)interval RNG.poisson_arrival(rate, self.rng)return current_time intervaldef check_burst(self, current_time: float) - int:检查是否触发批量故障, 返回额外故障数量hour current_time % 24day int(current_time // 24) % 7# 周一早班爆发概率更高prob self.burst_probif day 0 and 6 hour 14:prob 0.18if self.rng.random() prob:return self.rng.randint(*self.burst_size)return 0class RepairTimeDistribution:维修时间分布: 对数正态(偏右长尾)def __init__(self, mu: float 3.5, sigma: float 0.6,min_time: float 5.0, max_time: float 180.0,seed: Optional[int] 42):self.mu muself.sigma sigmaself.min_time min_timeself.max_time max_timeself.rng random.Random(seed)def sample(self) - float:t RNG.lognormal(self.mu, self.sigma, self.rng)return max(self.min_time, min(self.max_time, t))# ─── 统计收集器 ──────────────────────────────────────────────────────────class StatisticsCollector:仿真统计收集def __init__(self):self.wait_times: List[float] []self.repair_times: List[float] []self.queue_lengths: List[int] []self.tech_busy_time: Dict[int, float] {}self.downtime_events: int 0 # 等待30min的次数self.total_failures: int 0self.last_queue_check: float 0.0self.current_queue: int 0self._last_time: float 0.0def record_arrival(self, time: float, queue_len: int):self.total_failures 1self.current_queue queue_lendef record_repair_start(self, time: float, wait_time: float,repair_time: float, tech_id: int):self.wait_times.append(wait_time)self.repair_times.append(repair_time)if wait_time 30.0:self.downtime_events 1if tech_id not in self.tech_busy_time:self.tech_busy_time[tech_id] 0.0self.tech_busy_time[tech_id] repair_timedef summary(self, total_sim_time: float) - dict:if not self.wait_times:return {}return {total_failures: self.total_failures,avg_wait_min: statistics.mean(self.wait_times),p50_wait: statistics.median(self.wait_times),p95_wait: sorted(self.wait_times)[int(0.95 * len(self.wait_times))],max_wait: max(self.wait_times),avg_repair_min: statistics.mean(self.repair_times),downtime_prob: self.downtime_events / max(self.total_failures, 1),downtime_count: self.downtime_events,tech_utilization: {tid: busy / total_sim_time * 100for tid, busy in self.tech_busy_time.items()},}# ─── 离散事件仿真引擎 ────────────────────────────────────────────────────class DiscreteEventSimulator:离散事件仿真引擎def __init__(self, num_technicians: int 4,simulation_days: int 30,seed: Optional[int] 42):self.num_technicians num_techniciansself.simulation_days simulation_daysself.sim_time 0.0self.fel FutureEventList()self.stats StatisticsCollector()self.rng random.Random(seed)self.technicians [RepairTechnician(tech_idi, skillgeneral)for i in range(num_technicians)]# 默认排班: 4人三班倒self.shift_schedule self._create_base_shift()self.failure_gen FailureGenerator(TimeVaryingArrivalRate(base_rate1.8),burst_prob0.05, burst_size(5, 10), seedseed)self.repair_time_dist RepairTimeDistribution(mu3.5, sigma0.6, seedseed)# 等待队列self.waiting_queue: List[dict] []def _create_base_shift(self) - ShiftSchedule:创建基础排班: 4人三班倒s ShiftSchedule(namebase)# 早班 6:00-14:00 (T0, T1)s.shifts[(6, 14)] [0, 1]# 中班 14:00-22:00 (T2, T3)s.shifts[(14, 22)] [2, 3]# 夜班 22:00-6:00 (T0, T2 轮换, 简化: T1, T3)s.shifts[(22, 30)] [1, 3]s.shifts[(0, 6)] [1, 3]return sdef _create_flex_shift(self) - ShiftSchedule:弹性排班: 早班高峰加1名机动(T4)s ShiftSchedule(nameflex)s.shifts[(6, 14)] [0, 1, 4] # 早班加人s.shifts[(14, 22)] [2, 3]s.shifts[(22, 30)] [1, 3]s.shifts[(0, 6)] [1, 3]return sdef setup_flex_schedule(self):切换到弹性排班(需5名维修工)if self.num_technicians 5:self.technicians.append(RepairTechnician(tech_id4, skillgeneral))self.num_technicians 5self.shift_schedule self._create_flex_shift()def _get_available_tech(self) - Optional[RepairTechnician]:从在岗维修工中找空闲的available self.shift_schedule.get_available_techs(self.sim_time, self.technicians)for tech in available:if not tech.busy:return techreturn Nonedef _process_queue(self):尝试从队列中分配维修工while self.waiting_queue and (tech : self._get_available_tech()):task self.waiting_queue.pop(0)wait_time self.sim_time - task[arrival_time]repair_time self.repair_time_dist.sample()tech.assign_task(self.sim_time repair_time)# 调度维修完成事件self.fel.schedule(Event(timeself.sim_time repair_time,event_typeEND_REPAIR,data{tech_id: tech.tech_id,wait_time: wait_time,repair_time: repair_time}))self.stats.record_repair_start(self.sim_time, wait_time, repair_time, tech.tech_id)def run(self, verbose: bool True) - dict:运行仿真# 初始化: 调度第一个故障到达first_arrival self.failure_gen.generate_next_arrival(0.0)self.fel.schedule(Event(timefirst_arrival, event_typeARRIVAL))total_time self.simulation_days * 24.0last_report 0.0if verbose:print(f\n 离散事件仿真开始)print(f • 维修工: {self.num_technicians} 名)print(f • 排班: {self.shift_schedule.name})print(f • 仿真时长: {self.simulation_days} 天 ({total_time:.0f}h))print(f • 随机种子: {self.rng.seed if hasattr(self.rng, seed) else N/A}\n)start time.perf_counter()while self.sim_time total_time and not self.fel.is_empty():event self.fel.next_event()if event is None:breakself.sim_time event.timeif event.event_type ARRIVAL:# 故障到达self.waiting_queue.append({arrival_time: self.sim_time})self.stats.record_arrival(self.sim_time, len(self.waiting_queue))self._process_queue()# 调度下一个到达next_arr self.failure_gen.generate_next_arrival(self.sim_time)self.fel.schedule(Event(timenext_arr, event_typeARRIVAL))# 检查批量爆发burst_count self.failure_gen.check_burst(self.sim_time)for _ in range(burst_count):self.waiting_queue.append({arrival_time: self.sim_time})self.stats.record_arrival(self.sim_time, len(self.waiting_queue))if burst_count 0 and verbose:print(f ⚠️ t{self.sim_time:.1f}h 批量故障! {burst_count}台)elif event.event_type END_REPAIR:# 维修完成tech_id event.data[tech_id]self.technicians[tech_id].release()self._process_queue()# 进度报告if verbose and self.sim_time - last_report 24.0:last_report self.sim_timedays_elapsed int(self.sim_time / 24)elapsed_wall time.perf_counter() - startprint(f ... 第 {days_elapsed}/{self.simulation_days} 天 f(仿真时 {self.sim_time:.0f}h, 墙钟 {elapsed_wall:.1f}s))# 仿真结束wall_time time.perf_counter() - startsummary self.stats.summary(total_time)summary[wall_time_sec] wall_timesummary[sim_days] self.simulation_daysif verbose:print(f\n✅ 仿真完成!)print(f • 墙钟耗时: {wall_time:.1f}秒)print(f • 总故障数: {summary.get(total_failures, 0)})print(f利用AI解决实际问题如果你觉得这个工具好用欢迎关注长安牧笛