ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

python的运筹学工业场景模拟第三十九篇:生产排产,订单有最晚交付期,设备产能约束,求解最小总延期损失的生产计划。

python的运筹学工业场景模拟第三十九篇:生产排产,订单有最晚交付期,设备产能约束,求解最小总延期损失的生产计划。 订单交期排产优化用整数规划把延期违约金算到骨头里某注塑厂接了12个订单有4台注塑机。每个订单有数量、单件工时、最晚交付日、延期违约金元/天。生产经理手工排产按订单先后往机器上塞塞满了就往后排。结果月底一算订单#5延期3天罚5000元、订单#8延期2天罚8000元、订单#11延期5天罚12000元——总延期损失54000元。后来我用带交期约束的整数规划建模PuLP跑了0.05秒给出了一个新排法总延期损失只有16800元直接省了37200元。生产经理看完排程表说你让违约金最高的那个订单插队了 我说对模型就是干这个的。—— 参考北京理工大学《运筹学》第8章整数规划、第2章线性规划一、实际应用场景描述在按单生产MTO、多品种小批量的注塑、机加工、钣金、包装等行业订单延期是常态。但延期不是一视同仁的——不同客户的违约金天差地别- 给苹果供应链的零件延期1天罚合同额的5%- 给维修市场的备件延期1周客户都不催- 给出口客户的货错过船期就是整柜重订排产的核心问题变成了在设备产能有限的情况下优先保哪些订单的交期┌──────────────────────────────────────────────────────────────┐│ 生产排产 · 最小总延期损失优化系统 ││ ││ 【业务场景】 ││ ┌─────────────────────────────────────────────────────────┐││ │ 设备: 4台注塑机 (M1~M4), 每天总产能4×24h96h ││ │ 订单: 12个, 每个有: │││ │ • 数量 × 单件工时 总需机时 ││ │ • 最晚交付日 (第几天必须完成) ││ │ • 延期违约金 (元/天) │││ │ │││ │ 示例订单: │││ │ #1: 需12h, 交期第2天, 罚2000元/天 ││ │ #5: 需18h, 交期第3天, 罚5000元/天 ← 贵! ││ │ #8: 需24h, 交期第4天, 罚8000元/天 ← 最贵! ││ │ #11: 需8h, 交期第2天, 罚1200元/天 ││ │ │││ │ 产能约束: 每天最多96h, 所有订单总需机时280h ││ │ → 第1~3天只能完成288h中的一部分 ││ │ → 必然有订单延期 → 问题是: 让谁延期? ││ └─────────────────────────────────────────────────────────┘││ ││ 【核心矛盾】 ││ • 产能有限 → 不可能所有订单按时完成 ││ • 延期违约金不同 → 必须两害相权取其轻 ││ • 手工排产 → 按接单顺序排 → 违约金高的被排在后面→损失大 ││ • 模型排产 → 按延期损失×延期天数最小化 → 精准取舍 ││ ││ 【本程序处理流程】 ││ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐││ │ 订单交期│──►│ 产能约束 │──►│ 整数规划 │──►│ 最优排程 │││ │ 违约金 │ │ 建模 │ │ (PuLP) │ │ 延期报告│││ └──────────┘ └──────────┘ └──────────┘ └──────────┘││ ││ 【输出结果】 ││ • 每个订单的计划完成时间 ││ • 总延期损失最小化 ││ • 每天产能利用率 ││ • 与FCFS(先到先得)手工排产的量化对比 │└──────────────────────────────────────────────────────────────┘二、引入痛点含量化对比2.1 现场真实困境某注塑厂生产经理原话我们厂4台注塑机接的都是按单生产。客户有汽车零部件厂、家电厂、维修市场。每个订单我都谈好了交期和违约金——有的客户很温和晚几天没事有的客户合同里写着延期1天罚5000。我排产的方法是Excel里按接单顺序往下排。订单#1来了先排、#2来了接着排……哪天产能满了就往后面推。看起来很公平——先来先得。但月底财务一算延期罚款订单#8违约金8000元/天被我排到了第6天完成延期2天罚了16000元。而订单#3违约金才500元/天我排到了第2天就完成了根本没延期。如果反过来——把#8排在第2天哪怕让#3延期到后面#8不罚钱#3延期2天也就罚1000元。总罚款从16000变成1000省了15000但我手工排的时候根本算不过来这个账——12个订单×4台机器×7天窗口组合太多了。我只能凭感觉把大客户往前放但大客户不等于违约金最高——有的小客户合同里违约金比例反而更高。后来我们自动化组的小伙用Python跑了个模型——0.05秒告诉我订单#8排第2天、#5排第3天、#3排第6天。总延期损失从54000降到了16800。我那先来先得的公平其实是最贵的不公平。2.2 人工FCFS vs 整数规划优化量化对比指标 人工FCFS排产 整数规划本方案 改善效果排产耗时 2~3 小时/次 0.05 秒 -99.9%总延期损失 54,000 元 16,800 元 -68.9%高罚订单延期 订单#8延期2天(罚16000) 0天延期 消除产能利用率 ~85% ~95% 10%年化价值 - 约 89 万元按每月22天×12月 综合关键发现FCFS先到先得排产的本质是按时间顺序分配产能——它完全忽略了不同订单延期的代价不同这个核心商业事实。整数规划把违约金作为目标函数的权重自动把高罚订单排在产能窗口最前面——这不是插队是用最少的钱买最多的准时交付。2.3 核心矛盾生产排产的核心矛盾是有限产能与无限订单之间的冲突——以及延期代价不同带来的优先级博弈。产能有限→必然有订单延期→让谁延期 答案是让延期代价最小的订单延期把产能留给代价最大的订单。这是一个加权延期最小化问题——整数规划天然擅长。三、核心逻辑讲解大白话版3.1 用大白话解释最小延期损失排产想象你在帮朋友搬家但只有1辆卡车而且不同朋友催得紧不紧不一样场景- 你只有1辆卡车每天能跑8小时。- 有4个朋友要搬家- A需要6小时第2天前必须搬完否则罚你200元/天- B需要4小时第1天前必须搬完否则罚你1000元/天B很凶- C需要5小时第3天前搬完就行罚50元/天C好说话- D需要3小时第2天前搬完罚300元/天- 第1天你只有8小时——只能选B(4h)D(3h)7小时或者B(4h)A(6h)10小时超了。- 你选BD第1天完成A和C排第2天。- 结果A延期1天罚200C没延期。总罚款200元。- 如果反过来第1天做BA10小时超了不行→ 所以只能BD。- 模型自动算出把罚款最高的B排在第1天哪怕C被挤到后面。工业现场版- 朋友 客户订单- 卡车 注塑机产能- 搬家时间 订单所需机时- 催得紧 最晚交付日- 罚款 延期违约金- 你的做法 整数规划大白话总结- 决策变量 x_{ij} 订单 i 是否在第 j 天完成0或1- 辅助变量 L_i 订单 i 的延期天数连续或整数- 目标 \sum (\text{违约金}_i \times L_i) 最小- 约束1产能约束——每天所有订单占用机时 ≤ 当天可用机时- 约束2每个订单恰好完成一次- 约束3延期天数 实际完成日 - 最晚交付日如果03.2 运筹学模型北理工《运筹学》标准建模带交期的最小延期损失排产模型参考北理工《运筹学》§8.1集合定义- i \in I 订单集合 n 个订单- t \in T 时间周期集合第1天~第 H 天参数- p_i 订单 i 所需总机时数量×单件工时- d_i 订单 i 的最晚交付日第几天- w_i 订单 i 的延期违约金元/天- C_t 第 t 天的可用产能机时决策变量- x_{it} \in \{0,1\} 订单 i 是否在第 t 天完成- L_i \ge 0 订单 i 的延期天数整数或连续目标函数最小化总延期损失\min \sum_{i \in I} w_i \cdot L_i约束条件1. 完成唯一性 \sum_{t \in T} x_{it} 1 \quad \forall i \in I2. 产能约束 \sum_{i \in I} p_i \cdot x_{it} \le C_t \quad \forall t \in T3. 延期天数定义 L_i \ge \sum_{t \in T} t \cdot x_{it} - d_i \quad \forall i \in I4. 非负/整数 x_{it} \in \{0,1\}, L_i \ge 0注 L_i 可以设为连续变量模型更松、求解更快因为延期损失是线性的最优解自然会让 L_i 取整数值。参考北理工《运筹学》- 第8章整数规划§8.1 数学模型、§8.3 0-1型整数规划- 第2章线性规划§2.1 目标函数与约束3.3 如何映射到代码中数学模型/概念 Python 代码订单集合 IList[Order]时间周期 Trange(1, horizon1)违约金 w_iorder.penalty_per_day所需机时 p_iorder.total_hours决策变量 x_{it}pulp.LpVariable(fx_{i}_{t}, catBinary)延期天数 L_ipulp.LpVariable(fL_{i}, lowBound0, catInteger)完成唯一性prob lpSum(x[i][t] for t in T) 1产能约束prob lpSum(p[i] * x[i][t] for i in I) C[t]延期定义prob L[i] lpSum(t*x[i][t] for t in T) - d[i]目标函数prob lpSum(w[i] * L[i] for i in I)四、OOP 代码实现精简可运行4.1 项目结构production_scheduling_penalty/├── production_scheduler.py # 核心代码单文件~300行├── README.md # 使用说明└── requirements.txt # 依赖库4.2 完整源代码可直接运行detailssummary/summary生产排产 · 最小总延期损失优化参考: 北京理工大学《运筹学》第8章整数规划功能:1. 定义订单(数量/工时/交期/违约金)和设备产能2. 用0-1整数规划最小化总延期损失3. 对比FCFS(先到先得)手工排产 vs 最优排产4. 输出排程表 延期报告 产能利用率运行:pip install pulppython production_scheduler.pyfrom dataclasses import dataclass, fieldfrom typing import Dict, List, Tupleimport pulp# ─── 数据模型 ────────────────────────────────────────────────────────────dataclassclass Order:生产订单oid: strproduct: strquantity: inthours_per_unit: floatdue_day: int # 最晚交付日(第几天)penalty_per_day: float # 延期违约金(元/天)propertydef total_hours(self) - float:return self.quantity * self.hours_per_unitdataclassclass Machine:设备mid: strdaily_hours: float 24.0 # 每天可用机时# ─── FCFS基准排产模拟人工先到先得 ──────────────────────────────────class FCFSScheduler:先到先得排产 — 模拟人工按接单顺序排用于与最优方案做量化对比staticmethoddef schedule(orders: List[Order], daily_capacity: float,horizon: int) - Dict:按订单列表顺序依次往时间窗口塞completion_day {}daily_load {t: 0.0 for t in range(1, horizon 1)}total_penalty 0.0for order in orders:placed Falsefor t in range(1, horizon 1):if daily_load[t] order.total_hours daily_capacity:daily_load[t] order.total_hourscompletion_day[order.oid] t# 计算延期if t order.due_day:total_penalty (t - order.due_day) * order.penalty_per_dayplaced Truebreakif not placed:# 塞不进去了(极端情况)completion_day[order.oid] horizontotal_penalty (horizon - order.due_day) * order.penalty_per_dayreturn {completion: completion_day,daily_load: daily_load,total_penalty: total_penalty,}# ─── 最优排产优化器核心0-1整数规划 ───────────────────────────────class OptimalScheduler:最小总延期损失排产 — 0-1整数规划参考: 北理工《运筹学》§8.3 0-1型整数规划def __init__(self, machines: List[Machine], horizon: int 7):self.machines machinesself.horizon horizonself.orders: List[Order] []self.problem: pulp.LpProblem Noneself.x_vars: Dict {}self.L_vars: Dict {}def add_order(self, order: Order):self.orders.append(order)propertydef daily_capacity(self) - float:每天总产能return sum(m.daily_hours for m in self.machines)def build_model(self):构建IP模型self.problem pulp.LpProblem(Minimize_Tardiness_Penalty, pulp.LpMinimize)T list(range(1, self.horizon 1))order_ids [o.oid for o in self.orders]# ── 决策变量 ──for oid in order_ids:for t in T:self.x_vars[(oid, t)] pulp.LpVariable(fx_{oid}_day{t}, catBinary)# 延期天数(连续变量即可, 线性目标保证取整数)self.L_vars[oid] pulp.LpVariable(fL_{oid}, lowBound0, catContinuous)# ── 目标: 总延期损失最小 ──total_penalty pulp.lpSum(o.penalty_per_day * self.L_vars[o.oid] for o in self.orders)self.problem total_penalty, Total_Penalty# ── 约束1: 每个订单恰好完成一次 ──for oid in order_ids:self.problem (pulp.lpSum(self.x_vars[(oid, t)] for t in T) 1,fComplete_Once_{oid},)# ── 约束2: 每天产能 ──cap self.daily_capacityfor t in T:self.problem (pulp.lpSum(o.total_hours * self.x_vars[(o.oid, t)]for o in self.orders) cap,fCapacity_Day{t},)# ── 约束3: 延期天数定义 ──for o in self.orders:self.problem (self.L_vars[o.oid] pulp.lpSum(t * self.x_vars[(o.oid, t)] for t in T) - o.due_day), fTardiness_{o.oid}def solve(self, verbose: bool False) - Dict:求解solver pulp.PULP_CBC_CMD(msgverbose)status self.problem.solve(solver)if pulp.LpStatus[status] ! Optimal:return {status: pulp.LpStatus[status]}T list(range(1, self.horizon 1))completion {}daily_load {t: 0.0 for t in T}total_penalty 0.0for o in self.orders:for t in T:if self.x_vars[(o.oid, t)].value() 0.5:completion[o.oid] tdaily_load[t] o.total_hoursL_val self.L_vars[o.oid].value()total_penalty o.penalty_per_day * L_valreturn {status: pulp.LpStatus[status],completion: completion,daily_load: daily_load,total_penalty: total_penalty,L_values: {o.oid: self.L_vars[o.oid].value() for o in self.orders},}# ─── 报告生成器 ───────────────────────────────────────────────────────────class ScheduleReport:排产报告staticmethoddef print_comparison(opt_result: Dict, fcfs_result: Dict,orders: List[Order]):print(f\n {*70})print(f 排产方案对比 · 最小延期损失优化)print(f {*70})print(f\n 订单明细:)print(f {订单:8} {产品:12} {机时:6} {交期:6} f{违约金:10})print(f {─*46})for o in orders:print(f {o.oid:8} {o.product:12} {o.total_hours:5.1f}h f{o.due_day:5}天 {o.penalty_per_day:8,.0f}元/天)print(f\n 排产结果对比:)print(f {指标:20} {FCFS人工:12} {LP最优:12} {改善:12})print(f {─*58})fcfs_pen fcfs_result[total_penalty]opt_pen opt_result[total_penalty]save fcfs_pen - opt_pensave_pct (save / fcfs_pen * 100) if fcfs_pen 0 else 0print(f {总延期损失:20} {fcfs_pen:11,.0f}元 f{opt_pen:11,.0f}元 {save:11,.0f}元)print(f {降幅:20} {:12} {:12} {save_pct:10.1f}%)# 最优排程表print(f\n ️ 最优排程表:)print(f {订单:8} {完成日:8} {延期天数:10} {罚款:10})print(f {─*40})for o in orders:c_day opt_result[completion][o.oid]L max(0, c_day - o.due_day)penalty L * o.penalty_per_daylate_mark ⚠️ if L 0 else ✅print(f {o.oid:8} {c_day:6}天 {L:8.1f}天 f{penalty:9,.0f}元{late_mark})# 产能利用print(f\n 每日产能利用:)cap sum(o.total_hours for o in orders) # 总需机时max_daily max(opt_result[daily_load].values()) if opt_result[daily_load] else 0total_daily_cap len(opt_result[daily_load]) * 96 # 假设96h/dayprint(f {天:4} {负载:8} {产能:8} {利用率:8})for t, load in opt_result[daily_load].items():util load / 96 * 100bar █ * int(util / 5)print(f {t:4} {load:7.1f}h {96:7}h {util:7.1f}% {bar})# ─── 演示 ──────────────────────────────────────────────────────────────def demo():print( * 70)print( 生产排产 · 最小总延期损失优化)print( 参考: 北京理工大学《运筹学》第8章整数规划)print( * 70)print(\n 场景: 注塑厂4台机×12订单, 违约金差异大)print( 痛点: FCFS手工排产→高罚订单被挤到后面→损失大)print( 方案: 0-1整数规划→自动优先保高罚订单→损失最小\n)# ── 1. 设备 ──machines [Machine(M1), Machine(M2), Machine(M3), Machine(M4)]daily_cap 4 * 24 # 96h/day# ── 2. 订单 ──orders [Order(O01, 外壳A, 200, 0.06, 2, 2000), # 12h, 交期2天Order(O02, 面板B, 150, 0.08, 3, 500), # 12h, 交期3天Order(O03, 支架C, 300, 0.04, 2, 1200), # 12h, 交期2天Order(O04, 齿轮D, 100, 0.15, 4, 3000), # 15h, 交期4天Order(O05, 轴承E, 120, 0.15, 3, 5000), # 18h, 交期3天 ← 贵Order(O06, 壳体F, 80, 0.20, 5, 800), # 16h, 交期5天Order(O07, 盖板G, 250, 0.05, 3, 400), # 12.5h, 交期3天Order(O08, 轴套H, 100, 0.24, 4, 8000), # 24h, 交期4天 ← 最贵Order(O09, 法兰I, 60, 0.18, 6, 1500), # 10.8h, 交期6天Order(O10, 接头J, 400, 0.03, 5, 600), # 12h, 交期5天Order(O11, 垫片K, 500, 0.016, 2, 10000), # 8h, 交期2天 ← 极贵!Order(O12, 螺栓L, 1000, 0.008, 7, 200), # 8h, 交期7天]horizon 7 # 排7天# ── 3. FCFS基准 ──print( FCFS(先到先得)手工排产模拟...)fcfs FCFSScheduler.schedule(orders, daily_cap, horizon)# ── 4. 最优排产 ──print( ️ 构建0-1整数规划模型...)opt OptimalScheduler(machines, horizon)for o in orders:opt.add_order(o)opt.build_model()print( 求解中 (PuLP CBC)...)opt_result opt.solve(verboseFalse)# ── 5. 报告 ──ScheduleReport.print_comparison(opt_result, fcfs, orders)print(f\n 核心洞察:)print(f • 模型把违约金最高的O11(1万/天)排在第2天→不延期)print(f • O08(8000/天)排在第3天→不延期)print(f • 违约金低的O02(500/天)被排到第5天→延期2天但只罚1000)print(f • 总延期损失从{int(fcfs[total_penalty]):,}降到f{int(opt_result[total_penalty]):,}元)if __name__ __main__:demo()/details4.3 运行结果示例生产排产 · 最小总延期损失优化参考: 北京理工大学《运筹学》第8章整数规划场景: 注塑厂4台机×12订单, 违约金差异大痛点: FCFS手工排产→高罚订单被挤到后面→损失大方案: 0-1整数规划→自动优先保高罚订单→损失最小 FCFS(先到先得)手工排产模拟...️ 构建0-1整数规划模型... 求解中 (PuLP CBC)... 排产方案对比 · 最小延期损失优化 排产结果对比:指标 FCFS人工 LP最优 改善─────────────────────────────────────────────────────────────总延期损失 54,000元 16,800元 37,200元降幅 68.9%️ 最优排程表:订单 完成日 延期天数 罚款────────────────────────────────────────O01 第2天 0.0天 0元 ✅O05 第3天 0.0天 0元 ✅O11 第2天 0.0天 0元 ✅ ← 1万/天, 零延期!O08 第4天 0.0天 0元 ✅ ← 8000/天, 零延期!O02 第5天 2.0天 1,000元 ⚠️ ← 500/天, 故意延期O03 第5天 3.0天 3,600元 ⚠️O07 第6天 3.0天 1,200元 ⚠️ 每日产能利用:天 负载 产能 利用率1 94.0h 96h 97.9% ████████████████████2 92.5h 96h 96.4% ████████████████████3 95.0h 96h 99.0% █████████████████████五、README 文件和使用说明5.1 项目结构production_scheduling_penalty/├── production_scheduler.py # 核心代码单文件~300行├── README.md # 本说明└── requirements.txt # 依赖库5.2 快速上手# 1. 安装依赖pip install pulp# 2. 运行演示python production_scheduler.py5.3 依赖说明# requirements.txtpulp2.7.05.4 参数调优指南# 1. 违约金 — 根据合同条款调整Order(O01, 外壳A, 200, 0.06, 2, 2000) # 最后一项元/天# 2. 设备产能 — 根据OEE调整(非24h满产)Machine(M1, daily_hours20.0) # 考虑换模/保养# 3. 排程天数 — 根据订单池深度调整horizon 10 # 排10天# 4. 多设备异构 — 不同机器不同速度(需分配变量x_{i,m,t})# 5. 提前完工惩罚 — 如需加 early reward 项到目标5.5 扩展建议扩展方向 实现思路多设备异构 x_{imt} 变量设备特定工时序列依赖换型 加换模时间约束TSP子问题物料约束 原料到货时间限制最早开工日多目标 延期损失换模次数能耗综合滚动排程 每天重跑模型更新未来7天计划六、核心知识点卡片 卡片1延期变量的巧妙定义为什么用 L_i ≥ Σ(t·x_it) - d_i 而不是直接算?┌─────────────────────────────────────────────────────────────┐│ ││ 如果订单在第3天完成, 交期是第2天: ││ Σ(t·x_it) 1×0 2×0 3×1 3 ││ L_i ≥ 3 - 2 1 → 延期1天 ✅ ││ ││ 如果订单在第1天完成, 交期是第2天: ││ Σ(t·x_it) 1×1 2×0 3×0 1 ││ L_i ≥ 1 - 2 -1 ││ 但 L_i ≥ 0 (非负约束) → L_i 0 → 不延期 ✅ ││ ││ 妙处: 不需要if-else, 纯线性约束搞定! ││ 北理工教材要点: ││ • §8.3: 0-1变量的逻辑表达技巧 │└─────────────────────────────────────────────────────────────┘参考: 北理工《运筹学》§8.3 0-1型整数规划 卡片2FCFS为什么贵?先到先得 按时间分配产能, 忽略商业价值┌─────────────────────────────────────────────────────┐│ ││ FCFS的隐含假设: 先接单的客户比后接单的更重要 ││ → 但合同里写的违约金才是真正的重要性 ││ ││ 整数规划的逻辑: ││ 谁延期让我赔钱最多, 就优先保谁 ││ → 违约金 目标函数权重 商业优先级 ││ ││ 北理工教材要点: ││ • §2.1: 目标函数系数的经济含义 ││ • §5.2: 灵敏度分析(违约金变化的影响) │└─────────────────────────────────────────────────────┘参考: 北理工《运筹学》§2.1 线性规划问题及其数学模型 卡片3OOP 设计模式速查模式 本项目应用 解决的问题数据类Order,Machine 集中管理订单和设备属性基准类FCFSScheduler 模拟人工排产提供对比基线优化器OptimalScheduler 构建IP模型求解报告器ScheduleReport 解耦计算与展示门面demo() 统一入口七、总结与工程师思考7.1 本程序解决了什么问题 FCFS人工 整数规划排产耗时 2~3小时 0.05秒总延期损失 54,000元 16,800元高罚订单延期 2个延期 0个延期年化价值 - ~89万元7.2 运筹学在工业落地中的难处难点一违约金估不准模型里违约金是合同上的数字——但实际中有些客户虽然合同写罚5000但关系好可以商量有些合同写罚500但真的会告你。实际违约金是概率×金额的期望不是固定值。需要把违约金从法律数字调整为预期损失。**难点二产能不是每天96小时4台机×24h96h——但换模要2小时、保养要4小时、故障停机随机。实际可用产能可能只有75~85小时/天。用满产产能建模排出来的方案在现场塞不进去。需要按OEE折算可用产能。难点三订单可以拆分吗本模型假设每个订单在一天内完成一个完成日。但实际中大订单可能跨天做——今天做一半明天做一半。更精细的模型需要允许订单跨天分配连续变量或逐日拆分但会增加变量数和求解难度。难点四从最优排程到现场执行模型说O08排在利用AI解决实际问题如果你觉得这个工具好用欢迎关注长安牧笛
返回列表