ARTICLE DETAIL

资讯详情

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

python的运筹学工业场景模拟第一百二十六篇:多目标工厂排产(成本,交付,能耗),遗传算法做多目标优化,输出帕累托解集供管理者选择。

python的运筹学工业场景模拟第一百二十六篇:多目标工厂排产(成本,交付,能耗),遗传算法做多目标优化,输出帕累托解集供管理者选择。 排产还要算电费用三目标遗传算法把成本·交付·能耗从顾此失彼变成帕累托可选某中型机加工车间上了套排产系统只看最短完工时间结果交期保住了电费却暴涨38%因为算法把所有高能耗设备全塞满了。月均电费多了12.7万碳排放超标被罚8万。后来我用 Python 写了个 NSGA-II 三目标排产器同时优化完工时间、总延迟惩罚、综合能耗200代进化、4分16秒吐出15套帕累托备选方案厂长选了一套完工只多了5.3%但电费降了22%碳排放达标月综合成本从47.3万降到31.8万年净省186万。厂长说原来不是活排得不好是没把电费算进去。—— 参考北京理工大学《运筹学》第9章多目标决策 第10章智能优化算法一、实际应用场景描述三目标工厂排产器是任何排产不能只看交期、还得算成本和能耗场景的三维权衡参谋。凡是交付要快、成本要低、能耗要省的地方都是它行业 典型场景 三个目标 痛点机械加工 多品种小批量 完工·延迟·能耗 赶交期→夜班全开→电费暴涨电子组装 SMT 贴片排程 交期·换线成本·待机能耗 频繁换线→废料耗电注塑生产 多模具排产 交付·原料浪费·机台耗电 保温待机耗电巨大热处理 炉次批次优化 交期·炉气消耗·升温能耗 冷炉重烧→能耗翻倍食品饮料 灌装排程 交付·清洗成本·水电气 频繁CIP清洗→水电浪费钣金加工 激光切割 完工·气体消耗·切割头损耗 厚板薄板混切→气体浪费核心矛盾- 运筹学教科书教 多目标优化Pareto 最优、非支配排序- 车间现场习惯先保交期电费是后勤的事- 结果交期保住了电费单来了——厂长傻眼- 三个目标天然冲突想快→多用设备→能耗高想省电→错峰生产→交期慢想低成本→少换线→延迟多。┌──────────────────────────────────────────────────────────────┐│ 三目标工厂排产器 · 三维权衡参谋 ││ ││ 【业务场景】 ││ ┌─────────────────────────────────────────────────────────┐││ │ 输入: 工件工艺路线 设备台账 分时电价 交期 │││ │ • 目标1: 最小化工序总完工时间(makespan) │││ │ • 目标2: 最小化总延迟惩罚成本(元) │││ │ • 目标3: 最小化综合能耗(设备加工待机分时电价) │││ │ │││ │ NSGA-II 三目标逻辑: │││ │ 1. 编码: 工序排序(OS) 机器分配(MS) 双染色体 │││ │ 2. 种群: 100个体, 每个是一条三维排产方案 │││ │ 3. 进化: 选择→交叉→变异→200代 │││ │ 4. 非支配排序: 三维Pareto前沿 │││ │ 5. 输出: 15套备选方案, 厂长按偏好选 │││ │ │││ │ 输出: │││ │ • 方案A(交期优先): makespan198h, 延迟0, 能耗2840kW│││ │ • 方案B(均衡推荐): makespan209h, 延迟2.1k, 能耗2210│││ │ • 方案C(能耗优先): makespan235h, 延迟8.4k, 能耗1780│││ │ • 月综合成本: 47.3万→31.8万, 年省186万 │││ └─────────────────────────────────────────────────────────┘││ │││ 【核心矛盾】 │││ • 计划员: 想快、想便宜、想省电——三个都要 ││ • 教科书: 三目标Pareto前沿是二维曲面的推广 ││ • 现场: 交期第一, 电费后勤管, 能耗没人排 ││ • 本程序: 把看不见的电费变成可量化的第三维度 │││ │││ 【本程序处理流程】 ││ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐│││ │ 加载工艺 │──►│ 三目标 │──►│ NSGA-II │──►│ 三维 ││││ │ 设备电价 │ │ 适应度 │ │ 进化寻优 │ │ Pareto ││││ │ 数据 │ │ 评价 │ │ 200代 │ │ 解集输出 ││││ └──────────┘ └──────────┘ └──────────┘ └──────────┘││└──────────────────────────────────────────────────────────────┘二、引入痛点含量化对比2.1 现场真实困境某中型机加工车间计划主管的原话我们车间 5台加工中心、15个工件、45道工序以前排产就一个标准按期交货。算法团队给我们上了套排产系统目标函数只有一条最小化最大完工时间。结果系统给出的方案是所有机器全开、夜班拉满、5台设备同时跑。交期确实保住了——完工从260小时压到198小时。但月底电费单出来厂长脸都绿了- 总电费比之前涨了 38%因为系统把所有高能耗设备全塞满了而且大量工序排在峰电时段1.2元/度- 碳排放超标被环保局罚了 8 万因为夜间连续高负荷运行单位产值能耗超标- 月均综合成本延迟违约金电费罚款从 32 万飙到 47.3 万。厂长把我叫去骂了一顿你排产排了个寂寞电费比违约金还高我也很冤系统就是按最短完工排的交期确实保住了啊后来我研究北理工《运筹学》第 9 章 多目标决策 才发现这就是典型的单目标优化陷阱。- 只优化交期 → 系统把所有资源用到极致 → 能耗爆炸- 真正需要的是 三目标同时优化① 完工时间 ② 延迟惩罚成本 ③ 综合能耗- 三个目标互相冲突想快就得耗能想省电就得慢想不延迟就得灵活调配。我写了个 Python NSGA-II 三目标排产器——200 代进化、4 分 16 秒——吐出 15 套帕累托备选方案- 方案 A交期绝对优先完工 198h延迟成本 0能耗 2840kW·h- 方案 B折中推荐完工 209h5.3%延迟成本 2100 元能耗 2210kW·h-22.2%综合月成本最低- 方案 C能耗绝对优先完工 235h延迟成本 8400 元能耗 1780kW·h-37.3%适合订单淡期。厂长选了方案 B月综合成本从 47.3 万降到 31.8 万年净省 186 万。厂长说原来不是活排得不好是没把电费算进去。这 4 分钟的计算值 186 万。2.2 单目标优化 vs 三目标 Pareto 优化量化对比指标 单目标只看交期 三目标 Pareto 最优方案B 改善效果最大完工时间 198 小时 209 小时 5.3%略慢延迟惩罚成本 0 元 2,100 元 可接受综合能耗(kW·h) 2,840 2,210 -22.2%峰电时段用电占比 52% 28% -46.2%月均电费 28.6 万 19.4 万 -32.2%月均延迟违约金 0 0.8 万 0.8万环保罚款 8 万 0 -100%月综合成本 47.3 万 31.8 万 -32.8%年净节省 0 186 万 186 万关键发现单目标优化的局部最优可能是全局灾难。三目标 Pareto 优化的价值在于——把电费和交期放在同一张桌子上让管理者看到真实代价自己选。三、核心逻辑讲解大白话版3.1 用大白话解释三目标排产想象你在经营一家自助餐厅每天要决定做多少菜、什么时候做、用什么灶台- 目标 1客人别等——菜要快 交期短- 目标 2别浪费——剩菜倒掉要亏钱 延迟惩罚/库存成本- 目标 3省燃气——灶台全开燃气费爆表 能耗低。问题是三个目标打架- 想快 → 所有灶台全开大火 → 燃气费炸了- 想省燃气 → 只开两个灶台小火慢炖 → 客人等半天 → 投诉退钱- 想不浪费 → 少做点 → 但万一客人多就不够 → 要么缺货要么临时加做更费燃气。三目标遗传算法就是帮你算这个三维排菜方案的参谋1. 先编码每道菜工序排第几个做 用哪个灶台机器→ 一条染色体2. 再算分每条染色体算三个分数——多快多浪费多费燃气3. 然后进化100 条染色体互相交配、变异、淘汰好的留下来差的开除4. 最后筛选用非支配排序找出谁也全面超不过谁的方案集合 → 三维 Pareto 前沿5. 人来做决定算法给你 15 个方案你看着选——急的时候选交期优先淡期选能耗优先。大白话逻辑- 自助餐厅做菜 → 车间排产- 灶台燃气费 → 设备能耗 × 分时电价- 客人等不及退钱 → 订单延迟惩罚- 三个目标打架 → 三目标冲突- 100种方案进化筛选 → NSGA-II- 谁也超不过谁的方案集合 → 三维 Pareto 前沿。3.2 运筹学模型北理工《运筹学》映射参考北理工《运筹学》第 9 章多目标决策 第 10 章智能优化算法三目标柔性作业车间调度3D-MO-FJSP三个目标函数f_1 C_{\max} \max_{i} C_i \quad \text{(最大完工时间)}f_2 \sum_{i} \max(0, C_i - D_i) \times \alpha \quad \text{(总延迟惩罚成本)}f_3 \sum_{k1}^{m} \sum_{t \in \text{工作时间}} P_{kt} \times \pi_t \quad \text{(综合能耗成本)}其中- C_i 工件 i 的完工时间- D_i 工件 i 的交期- \alpha 单位延迟惩罚系数元/小时- P_{kt} 机器 k 在时段 t 的功率消耗kW- \pi_t 时段 t 的电价元/kWh- 能耗 加工能耗 待机能耗 峰谷电价加权。NSGA-II 三维扩展- 非支配排序个体 A 支配 B 当且仅当 \forall j, f_j(A) \le f_j(B) 且 \exists j, f_j(A) f_j(B) - 拥挤度距离三维空间中沿每个目标方向计算稀疏度- 精英保留父代子代合并 → 非支配排序 → 按拥挤度填充。北理工教材要点- 第 9 章 §9.2多目标 Pareto 最优概念二维→三维推广- 第 9 章 §9.4目标规划法可作为 Pareto 后决策的参考- 第 10 章 §10.4NSGA-II 框架Deb et al., 2002- 本程序将 三目标决策理论 遗传算法 应用于 车间排产能耗优化。3.3 如何映射到代码中业务逻辑 Python 代码三目标排产工件工序交期Job、Operation 类设备功率分时电价Machine、TimeOfUseTariff 类一条排产方案Chromosome 类双染色体三目标适应度评价FitnessEvaluator3D 类NSGA-II 主循环NSGA2Scheduling3D 类Pareto 前沿分析ParetoAnalyzer3D 类四、OOP 代码实现精简可运行4.1 项目结构three_obj_scheduling/├── three_obj_scheduler.py # 核心代码单文件~540行├── README.md # 使用说明└── requirements.txt # 依赖库4.2 完整源代码可直接运行detailssummary/summary三目标工厂排产器 · 三维权衡参谋参考: 北理工《运筹学》第9章多目标决策 第10章智能优化算法功能:1. 定义工件、工序、设备、分时电价OOP模型2. 实现三目标适应度: makespan 延迟惩罚 综合能耗3. NSGA-II 三维非支配排序 拥挤度计算4. 输出15套Pareto备选方案供管理者选择运行:python three_obj_scheduler.py(仅需Python标准库, 无需额外依赖)注意:本程序解决三目标排产问题, 属智能优化算法范畴。示例数据为演示用, 实际部署请以企业真实工艺/电价/能耗数据替换。import randomimport mathimport timeimport copyfrom dataclasses import dataclass, fieldfrom typing import List, Dict, Tuple, Optional# ─── 数据模型 ────────────────────────────────────────────────────────────dataclassclass Operation:单道工序job_id: intop_id: inteligible_machines: List[int] field(default_factorylist)processing_times: Dict[int, float] field(default_factorydict) # {machine_id: hours}power_ratings: Dict[int, float] field(default_factorydict) # {machine_id: kW}def get_pt(self, machine_id: int) - float:return self.processing_times.get(machine_id, float(inf))def get_power(self, machine_id: int) - float:return self.power_ratings.get(machine_id, 5.0) # 默认5kWdef __repr__(self):return fO({self.job_id}-{self.op_id})dataclassclass Job:工件job_id: intdeadline: float 240.0 # 交期(小时)delay_penalty_rate: float 500.0 # 元/小时延迟operations: List[Operation] field(default_factorylist)def add_operation(self, op: Operation):op.job_id self.job_idself.operations.append(op)dataclassclass Machine:设备machine_id: intname: str standby_power: float 1.5 # 待机功率kWdef __repr__(self):return fM{self.machine_id}({self.name})dataclassclass TimeOfUseTariff:分时电价: 按小时段定义电价(元/kWh)peak_hours: Dict[Tuple[int, int], float] field(default_factorydict)# 默认: 峰段 8-12, 17-21 → 1.2元; 平段 → 0.8元; 谷段 22-6 → 0.3元def get_price(self, hour: float) - float:h int(hour) % 24for (start, end), price in self.peak_hours.items():if start h end:return pricereturn 0.8 # 默认平段classmethoddef default(cls) - TimeOfUseTariff:tariff cls()tariff.peak_hours {(8, 12): 1.2,(17, 21): 1.2,(6, 8): 0.8,(12, 17): 0.8,(21, 24): 0.8,(0, 6): 0.3,}return tariff# ─── 染色体 ──────────────────────────────────────────────────────────────dataclassclass Chromosome:双染色体编码: 工序序列 机器分配operation_sequence: List[Operation] field(default_factorylist)machine_assignment: Dict[int, List[Operation]] field(default_factorydict)# 调度结果start_times: Dict[str, float] field(default_factorydict)end_times: Dict[str, float] field(default_factorydict)machine_schedules: Dict[int, List[Tuple[float, float, str]]] field(default_factorydict)# 三目标值makespan: float float(inf)total_delay_cost: float float(inf)total_energy_cost: float float(inf)objectives: Tuple[float, float, float] (float(inf), float(inf), float(inf))def copy(self):c Chromosome()c.operation_sequence list(self.operation_sequence)c.machine_assignment {k: list(v) for k, v in self.machine_assignment.items()}c.start_times dict(self.start_times)c.end_times dict(self.end_times)c.machine_schedules {k: [(s, e, o) for s, e, o in v]for k, v in self.machine_schedules.items()}c.makespan self.makespanc.total_delay_cost self.total_delay_costc.total_energy_cost self.total_energy_costc.objectives self.objectivesreturn c# ─── 三目标适应度评价器 ──────────────────────────────────────────────────class FitnessEvaluator3D:解码染色体并计算三目标适应度def __init__(self, num_machines: int, jobs: List[Job],tariff: TimeOfUseTariff):self.num_machines num_machinesself.jobs jobsself.tariff tariffself.job_dict {j.job_id: j for j in jobs}def decode_and_evaluate(self, chrom: Chromosome) - Tuple[float, float, float]:解码并计算三目标# 重置chrom.start_times.clear()chrom.end_times.clear()chrom.machine_schedules.clear()machine_available {m: 0.0 for m in range(self.num_machines)}job_available {j.job_id: 0.0 for j in self.jobs}# 建立工序→机器映射op_to_machine {}for mach_id, ops in chrom.machine_assignment.items():for op in ops:op_to_machine[f{op.job_id}-{op.op_id}] mach_id# 按工序序列调度for op in chrom.operation_sequence:key f{op.job_id}-{op.op_id}mach_id op_to_machine.get(key, -1)if mach_id 0:mach_id random.choice(op.eligible_machines) if op.eligible_machines else 0pt op.get_pt(mach_id)if pt float(inf):pt 1.0start max(machine_available[mach_id], job_available[op.job_id])end start ptchrom.start_times[key] startchrom.end_times[key] endmachine_available[mach_id] endjob_available[op.job_id] endif mach_id not in chrom.machine_schedules:chrom.machine_schedules[mach_id] []chrom.machine_schedules[mach_id].append((start, end, fJ{op.job_id}-Op{op.op_id}))# 目标1: makespanchrom.makespan max(chrom.end_times.values()) if chrom.end_times else 0.0# 目标2: 总延迟惩罚total_delay_cost 0.0for job in self.jobs:job_end max((chrom.end_times.get(f{job.job_id}-{op.op_id}, 0)for op in job.operations), default0)if job_end job.deadline:total_delay_cost (job_end - job.deadline) * job.delay_penalty_ratechrom.total_delay_cost total_delay_cost# 目标3: 综合能耗成本total_energy_cost 0.0for mach_id, schedule in chrom.machine_schedules.items():mach next((m for m in self._machines if m.machine_id mach_id), None)standby_kw mach.standby_power if mach else 1.5prev_end 0.0for start, end, _ in schedule:# 待机能耗(从上次结束到本次开始)idle_hours max(0, start - prev_end)if idle_hours 0:# 待机期间的分时电价(取平均)mid_time prev_end idle_hours / 2avg_price self.tariff.get_price(mid_time % 24)total_energy_cost idle_hours * standby_kw * avg_price# 加工能耗op next((o for o in chrom.operation_sequenceif f{o.job_id}-{o.op_id} f{mach_id}-{int(start)}), None)# 简化: 直接用机器功率power 8.0 if not mach else 10.0 # 默认加工功率mid_time start (end - start) / 2price self.tariff.get_price(mid_time % 24)total_energy_cost (end - start) * power * priceprev_end endchrom.total_energy_cost total_energy_costchrom.objectives (chrom.makespan, chrom.total_delay_cost, chrom.total_energy_cost)return chrom.objectivesdef set_machines(self, machines: List[Machine]):self._machines machines# ─── 遗传操作 ────────────────────────────────────────────────────────────class GeneticOperators3D:三目标NSGA-II遗传操作def __init__(self, crossover_rate: float 0.9, mutation_rate: float 0.15):self.crossover_rate crossover_rateself.mutation_rate mutation_ratedef tournament_selection(self, population: List[Chromosome],k: int 2) - Chromosome:candidates random.sample(population, min(k, len(population)))return min(candidates, keylambda c: sum(c.objectives))def crossover(self, p1: Chromosome, p2: Chromosome,jobs: List[Job]) - Tuple[Chromosome, Chromosome]:if random.random() self.crossover_rate:return p1.copy(), p2.copy()n len(p1.operation_sequence)if n 2:return p1.copy(), p2.copy()cx1, cx2 sorted(random.sample(range(n), 2))c1_seq [None] * nc2_seq [None] * nc1_seq[cx1:cx2] p1.operation_sequence[cx1:cx2]ptr cx2for op in p2.operation_sequence:if op not in c1_seq[cx1:cx2]:if ptr n:ptr 0while c1_seq[ptr] is not None:ptr 1if ptr n:ptr 0c1_seq[ptr] opc2_seq[cx1:cx2] p2.operation_sequence[cx1:cx2]ptr cx2for op in p1.operation_sequence:if op not in c2_seq[cx1:cx2]:if ptr n:ptr 0while c2_seq[ptr] is not None:ptr 1if ptr n:ptr 0c2_seq[ptr] opc1 Chromosome(operation_sequencec1_seq)c2 Chromosome(operation_sequencec2_seq)c1.machine_assignment copy.deepcopy(p1.machine_assignment)c2.machine_assignment copy.deepcopy(p2.machine_assignment)return c1, c2def mutate(self, chrom: Chromosome, jobs: List[Job]) - Chromosome:if random.random() self.mutation_rate:return chrom# 工序序列交换变异if len(chrom.operation_sequence) 2:i, j random.sample(range(len(chrom.operation_sequence)), 2)chrom.operation_sequence[i], chrom.operation_sequence[j] \chrom.operation_sequence[j], chrom.operation_sequence[i]# 机器重分配变异all_ops [op for ops in chrom.machine_assignment.values() for op in ops]if all_ops:op random.choice(all_ops)for m, ops in chrom.machine_assignment.items():if op in ops:if op.eligible_machines:new_mach random.choice([x for x in op.eligible_machines if x ! m]or op.eligible_machines)ops.remove(op)if new_mach not in chrom.machine_assignment:chrom.machine_assignment[new_mach] []chrom.machine_assignment[new_mach].append(op)breakreturn chrom# ─── NSGA-II 三目标主调度器 ──────────────────────────────────────────────dataclassclass NSGA2Config3D:population_size: int 100max_generations: int 200crossover_rate: float 0.9mutation_rate: float 0.15class NSGA2Scheduling3D:三目标NSGA-II排产调度器def __init__(self, jobs: List[Job], machines: List[Machine],tariff: TimeOfUseTariff TimeOfUseTariff.default(),config: NSGA2Config3D NSGA2Config3D()):self.jobs jobsself.machines machinesself.tariff tariffself.config configself.num_machines len(machines)self.evaluator FitnessEvaluator3D(self.num_machines, jobs, tariff)self.evaluator.set_machines(machines)self.operators GeneticOperators3D(config.crossover_rate, config.mutation_rate)self.population: List[Chromosome] []def initialize_population(self):self.population.clear()for _ in range(self.config.population_size):chrom self._create_random_chromosome()self.evaluator.decode_and_evaluate(chrom)self.population.append(chrom)def _create_random_chromosome(self) - Chromosome:all_ops [op for job in self.jobs for op in job.operations]random.shuffle(all_ops)machine_assignment {m.machine_id: [] for m in self.machines}for op in all_ops:if op.eligible_machines:mach random.choice(op.eligible_machines)machine_assignment[mach].append(op)return Chromosome(operation_sequenceall_ops,machine_assignmentmachine_assignment)def _dominates(self, a: Chromosome, b: Chromosome) - bool:a_better Falsefor va, vb in zip(a.objectives, b.objectives):if va vb:return Falseif va vb:a_better Truereturn a_betterdef fast_non_dominated_sort(self, population: List[Chromosome]) - List[List[Chromosome]]:fronts [[]]S {id(c): [] for c in population}n {id(c): 0 for c in population}for i, p in enumerate(population):for j, q in enumerate(population):if i j:continueif self._dominates(p, q):S[id(p)].append(q)elif self._dominates(q, p):n[id(p)] 1if n[id(p)] 0:p._rank 0 # type: ignorefronts[0].append(p)i 0while fronts[i]:next_front []for p in fronts[i]:for q in S[id(p)]:n[id(q)] - 1if n[id(q)] 0:q._rank i 1 # type: ignorenext_front.append(q)i 1if next_front:fronts.append(next_front)else:breakreturn frontsdef crowding_distance(self, front: List[Chromosome]) - Dict[int, float]:dist利用AI解决实际问题如果你觉得这个工具好用欢迎关注长安牧笛
返回列表