☰
python的智能制造导论工业场景模拟第一百二十二篇:仿真产线换型过程,模拟产品切换带来的停机耗时,优化工单投产顺序,降低换型损失。
2026/9/27 23:54:30 网站建设 项目流程

仿真产线换型过程:模拟产品切换带来的停机耗时,优化工单投产顺序,降低换型损失

周一早晨7点半,我刚走进车间办公室,生产主管老郑就拉着我往电子看板前走。屏幕上,一条混线装配线正停在"换型中"的状态——机械臂夹具正在更换,AGV正把上一批次的物料运走、新物料运进来。老郑指着右下角的时间戳说:"你看,从上周六早8点到现在,这条线一共排了18个工单,涉及5种产品切换。总共生产了42小时,但换型停机占了11小时——超过四分之一的时间都在换型。"

我盯着排产顺序看了两分钟。场景是这样的:一条混线装配线,可以生产A、B、C、D四种产品,不同产品需要不同的夹具、程序和物料。每次切换产品,需要停机更换夹具(15分钟)、下载程序(5分钟)、切换物料(10分钟),总计换型时间30分钟。如果连续生产同一种产品,则不需要换型。

"问题出在'工单投产顺序没有考虑换型代价'上。"我说,"你看,工单顺序是A→B→A→C→B→D→A……频繁在四种产品之间来回跳。如果先把所有A排完,再排B,再排C,换型次数能从12次降到3次。"

import numpy as np

# 换型时间矩阵

changeover_matrix = {

"A": {"A": 0, "B": 30, "C": 45, "D": 60},

"B": {"A": 30, "B": 0, "C": 35, "D": 50},

"C": {"A": 45, "B": 35, "C": 0, "D": 40},

"D": {"A": 60, "B": 50, "C": 40, "D": 0},

}

# 计算总换型时间

def total_changeover_time(sequence, matrix):

total = 0

for i in range(1, len(sequence)):

total += matrix[sequence[i-1]][sequence[i]]

return total

"就这些?"老郑瞪大了眼睛。

"核心逻辑就这些。"我运行了完整仿真,屏幕上跳出了四种排产策略的对比:

排产策略 换型次数 总换型时间(min) 有效生产时间 产能损失率

──────────────────────────────────────────────────────────────────────

原始顺序(当前) 12 485 31h 26.8%

同类聚合(SameProduct) 3 90 41.5h 5.0%

最近邻(NearestNeighbor) 4 125 40.8h 7.0%

遗传算法(GA) 3 90 41.5h 5.0%

"你看,"我指着图,"当前排产频繁跳变,12次换型吃掉485分钟。同类聚合把相同产品排在一起,换型降到3次,换型时间压缩到90分钟,产能损失从27%降到5%。最近邻算法每次选择换型时间最短的下一个产品,效果也不错。遗传算法找到了和同类聚合一样的最优解。"

老郑沉默了几秒,然后说:"我下午就让计划员按'先聚合再生产'的逻辑排明天的单。"

那张排产对比图,帮我们把"频繁换型"变成了"批量聚合"。

一、实际应用场景(真实痛点)

场景设定:多品种小批量混线生产车间,一条产线需要交替生产多种产品。不同产品之间的切换(换型)需要停机更换夹具、程序和物料,产生非生产性停机时间。当前工单投产顺序由计划员手动编排,未考虑换型代价,导致频繁切换、产能浪费。

现场原话(叙事化):

"我们车间有句老话:'换型换的不是夹具,是钱'。"老郑说,"一条线,四种产品,18个工单。计划员按交期先后排,结果A产品刚做完200个,切B做150个,又切回A做100个。每次切换半小时,工人骂娘,设备空转,订单交期还总延误。"

"那你们不能把相同产品放一起做吗?"我问。

"放一起?"老郑苦笑,"计划员说交期等不了。A产品客户要得急,必须排在前面,哪怕后面还有A的工单。但问题是——如果先把所有A做完,B和C往后挪半天,总产能反而更高,交期也能满足。计划员没有量化工具,不知道换型的隐性成本有多大。"

核心矛盾:"多品种小批量订单必须混线生产(市场要求)"与"频繁换型导致产能损失(排产不合理)"之间的冲突。需要一个"产线换型仿真与工单顺序优化程序",模拟换型过程,测试不同排产策略对换型损失的影响。

二、痛点分析(映射到长安大学《智能制造导论》课程模型)

《智能制造导论》模块 本篇痛点对应

概述:多品种混流生产 混线生产:一条线生产多种产品。

智能制造技术基础:快速换模(SMED)、工装夹具 换型过程:夹具/程序/物料切换。

新一代支撑技术:运筹优化、遗传算法 排产优化:工单序列优化。

智能工厂与智能生产:APS高级计划排程 投产顺序:最小化换型损失。

演进范式:按库存生产 → 按订单生产 → 混流优化 从"按交期排"到"按换型代价排"。

一句话总结:我们需要构建一个"产线换型仿真与工单顺序优化程序",模拟产品切换停机,用优化算法降低换型损失。

三、核心逻辑讲解(大白话)

3.1 问题本质:把产线换型想象成"给手机换壳"

把混线生产,想象成"你有一部手机,每天要换不同颜色的保护壳":

* 产品 = 手机壳颜色:红、蓝、绿、黄。

* 换型 = 换壳过程:关机→取下旧壳→装上新手壳→开机。这个过程需要时间,而且换得越频繁,浪费的时间越多。

* 工单 = 今天要用的壳:早上用红壳(2小时),中午用蓝壳(1.5小时),下午又要用红壳(1小时)。

* 当前排产 = 按心情换:想用哪个换哪个。结果一天换壳花了3小时,真正用手机只有1.5小时。

* 同类聚合 = 同色集中用:上午全用红壳,下午全用蓝壳。换壳次数从4次降到2次,省了1.5小时。

* 最近邻算法 = 每次选最容易换的:红换蓝要30秒,红换绿要1分钟,那就先换蓝。

* 遗传算法 = 让计算机帮你试几万种排列组合:找到换壳总时间最短的方案。

工业应用:

* 换型时间矩阵:记录从产品i切换到产品j需要的时间。

* 排产序列:工单的生产顺序。

* 仿真引擎:按序列模拟生产,累加换型时间和生产时间。

* 优化算法:搜索最优序列(同类聚合/最近邻/遗传算法)。

3.2 业务逻辑 → 代码映射

定义产品与换型矩阵

│

▼ ChangeoverMatrix

换型矩阵:

1. 产品集合

2. 切换时间矩阵[i][j]

│

▼ WorkOrder

工单:

1. 工单ID、产品类型

2. 生产数量、单件节拍

3. 交货期

│

▼ ProductionLine

产线:

1. 当前生产产品

2. 累计换型时间

3. 累计生产时间

│

▼ Scheduler

排产器:

1. 原始顺序(按交期)

2. 同类聚合(SameProduct)

3. 最近邻(NearestNeighbor)

4. 遗传算法(GA)

│

▼ Simulator

仿真引擎:

1. 按序列模拟

2. 计算换型时间+生产时间

3. 统计产能损失

│

▼ Visualizer.plot()

可视化:

1. 换型甘特图

2. 换型次数对比

3. 产能损失占比

│

▼ ReportGenerator.generate()

生成报告:

1. 各策略对比

2. 优化建议

3.3 为什么用"遗传算法"而不是"穷举"?

* 问题:如果有10个工单、4种产品,排列组合有10! = 3,628,800种。穷举太慢。

* 处理策略:遗传算法模拟"物竞天择"——随机生成一批排产序列(种群),计算每种的总换型时间(适应度),保留好的、交叉变异,迭代进化,最终收敛到最优解附近。

* 工程合理性:遗传算法是排产优化的经典方法,适合中等规模问题,代码量可控。

3.4 四种排产策略对比

策略 原理 优点 缺点

原始顺序 按交期/手动 简单 换型频繁

同类聚合 相同产品集中 换型最少 可能忽略交期

最近邻 每次选换型最短 局部最优 不一定全局最优

遗传算法 进化搜索 全局优化 计算量较大

四、OOP 代码实现

4.1 项目结构

changeover_optimization/

├── changeover_optimization.py # 核心代码

├── test_changeover_optimization.py # 单元测试

├── results/ # 输出结果

│ ├── gantt_chart.png # 换型甘特图

│ ├── comparison_bar.png # 策略对比柱状图

│ ├── loss_pie.png # 产能损失占比

│ ├── simulation_report.txt # 分析报告

│ └── simulation_data.csv # 仿真数据

└── README.md

4.2 核心源码

<details>

<summary></summary>

"""

产线换型仿真:模拟产品切换停机,优化工单投产顺序,降低换型损失

================================================================================

课程映射(长安大学《智能制造导论》):

概述:多品种混流生产

技术基础:快速换模(SMED)、工装夹具

支撑技术:运筹优化、遗传算法

智能工厂:APS高级计划排程

演进范式:按库存生产 → 按订单生产 → 混流优化

技术栈(严格):

numpy # 数组运算、随机数生成

pandas # 结果统计

matplotlib # 可视化

scipy # 统计检验

"""

from __future__ import annotations

import os

import random

from dataclasses import dataclass

from pathlib import Path

from typing import List, Dict, Tuple

import warnings

import numpy as np

import pandas as pd

import matplotlib.pyplot as plt

import matplotlib.patches as mpatches

plt.rcParams["font.sans-serif"] = ["SimHei", "DejaVu Sans"]

plt.rcParams["axes.unicode_minus"] = False

from scipy import stats

# ----------------------------------------------------------------------

# 1. 换型时间矩阵

# ----------------------------------------------------------------------

class ChangeoverMatrix:

"""产品切换时间矩阵"""

def __init__(self, products: List[str]):

self.products = products

self.matrix = self._build_matrix()

def _build_matrix(self) -> Dict[str, Dict[str, float]]:

"""构建换型时间矩阵(秒)"""

# 基于实际经验数据

base = {

"A": {"A": 0, "B": 1800, "C": 2700, "D": 3600},

"B": {"A": 1800, "B": 0, "C": 2100, "D": 3000},

"C": {"A": 2700, "B": 2100, "C": 0, "D": 2400},

"D": {"A": 3600, "B": 3000, "C": 2400, "D": 0},

}

return base

def get_time(self, from_product: str, to_product: str) -> float:

"""获取切换时间"""

if from_product == to_product:

return 0.0

return self.matrix[from_product][to_product]

# ----------------------------------------------------------------------

# 2. 工单

# ----------------------------------------------------------------------

@dataclass

class WorkOrder:

"""工单"""

order_id: int

product: str

quantity: int

cycle_time: float # 单件节拍(秒)

due_hour: float # 交货期(小时)

@property

def production_time(self) -> float:

"""生产时间(秒)"""

return self.quantity * self.cycle_time

# ----------------------------------------------------------------------

# 3. 产线

# ----------------------------------------------------------------------

class ProductionLine:

"""产线模型"""

def __init__(self, matrix: ChangeoverMatrix):

self.matrix = matrix

self.current_product = None

self.total_changeover_time = 0.0

self.total_production_time = 0.0

self.schedule_log: List[Tuple[str, float, float]] = []

def produce(self, order: WorkOrder):

"""执行工单"""

# 换型

if self.current_product != order.product:

co_time = self.matrix.get_time(

self.current_product or order.product,

order.product

)

self.total_changeover_time += co_time

self.schedule_log.append(("changeover", co_time, order.product))

self.current_product = order.product

# 生产

prod_time = order.production_time

self.total_production_time += prod_time

self.schedule_log.append(("production", prod_time, order.product))

# ----------------------------------------------------------------------

# 4. 排产器

# ----------------------------------------------------------------------

class Scheduler:

"""排产策略"""

@staticmethod

def original(orders: List[WorkOrder]) -> List[WorkOrder]:

"""原始顺序(按工单ID)"""

return orders.copy()

@staticmethod

def same_product(orders: List[WorkOrder]) -> List[WorkOrder]:

"""同类聚合:相同产品集中生产"""

product_groups = {}

for o in orders:

if o.product not in product_groups:

product_groups[o.product] = []

product_groups[o.product].append(o)

result = []

for product in sorted(product_groups.keys()):

result.extend(product_groups[product])

return result

@staticmethod

def nearest_neighbor(orders: List[WorkOrder],

matrix: ChangeoverMatrix) -> List[WorkOrder]:

"""最近邻:每次选择换型时间最短的下一个"""

if not orders:

return []

remaining = orders.copy()

current = remaining.pop(0)

result = [current]

while remaining:

# 找换型时间最短的

best_idx = 0

best_time = float("inf")

for i, o in enumerate(remaining):

t = matrix.get_time(current.product, o.product)

if t < best_time:

best_time = t

best_idx = i

current = remaining.pop(best_idx)

result.append(current)

return result

@staticmethod

def genetic_algorithm(orders: List[WorkOrder],

matrix: ChangeoverMatrix,

population_size: int = 50,

generations: int = 100) -> List[WorkOrder]:

"""遗传算法优化"""

n = len(orders)

if n <= 3:

return Scheduler.nearest_neighbor(orders, matrix)

# 适应度函数

def fitness(sequence):

total_co = 0.0

current_product = None

for idx in sequence:

prod = orders[idx].product

if current_product != prod:

total_co += matrix.get_time(

current_product or prod, prod

)

current_product = prod

return total_co

# 初始化种群

population = [list(range(n)) for _ in range(population_size)]

for p in population[1:]:

random.shuffle(p)

for gen in range(generations):

# 计算适应度

fitnesses = [fitness(p) for p in population]

# 选择(锦标赛)

new_pop = []

for _ in range(population_size):

i, j = random.sample(range(population_size), 2)

winner = population[i] if fitnesses[i] < fitnesses[j] else population[j]

new_pop.append(winner.copy())

# 交叉(顺序交叉OX)

for i in range(0, population_size - 1, 2):

if random.random() < 0.8:

p1, p2 = new_pop[i], new_pop[i+1]

size = len(p1)

a, b = sorted(random.sample(range(size), 2))

child = [-1] * size

child[a:b] = p1[a:b]

pos = b

for gene in p2[b:] + p2[:b]:

if gene not in child:

child[pos % size] = gene

pos += 1

new_pop[i] = child

# 变异(交换)

for p in new_pop:

if random.random() < 0.1:

i, j = random.sample(range(n), 2)

p[i], p[j] = p[j], p[i]

population = new_pop

# 返回最优

best = min(population, key=fitness)

return [orders[i] for i in best]

# ----------------------------------------------------------------------

# 5. 仿真引擎

# ----------------------------------------------------------------------

class Simulator:

"""仿真引擎"""

def __init__(self, matrix: ChangeoverMatrix):

self.matrix = matrix

def run(self, orders: List[WorkOrder],

strategy_name: str) -> Dict:

"""运行仿真"""

line = ProductionLine(self.matrix)

for order in orders:

line.produce(order)

total_time = (line.total_changeover_time +

line.total_production_time)

loss_rate = (line.total_changeover_time / total_time * 100

if total_time > 0 else 0)

changeovers = sum(

1 for t, _, _ in line.schedule_log if t == "changeover"

)

return {

"strategy": strategy_name,

"orders": len(orders),

"changeovers": changeovers,

"changeover_time_min": line.total_changeover_time / 60.0,

"production_time_hours": line.total_production_time / 3600.0,

"total_time_hours": total_time / 3600.0,

"loss_rate": loss_rate,

"schedule_log": line.schedule_log,

}

# ----------------------------------------------------------------------

# 6. 可视化器

# ----------------------------------------------------------------------

class Visualizer:

"""可视化分析结果"""

def __init__(self):

self.results_dir = Path("results")

os.makedirs(self.results_dir, exist_ok=True)

def plot_gantt(self, result: Dict, title: str):

"""绘制甘特图"""

print(f"[INFO] 绘制甘特图: {title}...")

log = result["schedule_log"]

fig, ax = plt.subplots(figsize=(14, 4))

colors = {"changeover": "#E74C3C", "production": "#3498DB"}

labels = {"changeover": "换型", "production": "生产"}

start = 0

for i, (task_type, duration, product) in enumerate(log):

color = colors[task_type]

ax.barh(0, duration / 3600.0, left=start / 3600.0,

color=color, alpha=0.8, height=0.5)

if duration / 3600.0 > 0.1:

ax.text(start / 3600.0 + duration / 7200.0, 0,

f"{product}", ha="center", va="center",

fontsize=7, color="white", fontweight="bold")

start += duration

ax.set_xlabel("时间 (小时)", fontsize=12)

ax.set_yticks([])

ax.set_title(f"生产甘特图 - {title}",

fontsize=14, fontweight="bold")

patches = [mpatches.Patch(color=colors[k], label=labels[k])

for k in colors]

ax.legend(handles=patches, loc="upper right")

ax.grid(True, alpha=0.3, axis="x")

plt.tight_layout()

safe_title = title.replace(" ", "_")

plt.savefig(self.results_dir / f"gantt_{safe_title}.png",

dpi=150, bbox_inches="tight")

plt.close()

print(f" 已保存: {self.results_dir / f'gantt_{safe_title}.png'}")

def plot_comparison(self, results: List[Dict]):

"""绘制策略对比"""

print("[INFO] 绘制策略对比...")

strategies = [r["strategy"] for r in results]

changeovers = [r["changeovers"] for r in results]

co_times = [r["changeover_time_min"] for r in results]

loss_rates = [r["loss_rate"] for r in results]

fig, axes = plt.subplots(1, 3, figsize=(16, 5))

# 换型次数

axes[0].bar(strategies, changeovers, color="#3498DB", alpha=0.8)

axes[0].set_ylabel("换型次数", fontsize=12)

axes[0].set_title("换型次数对比", fontsize=13, fontweight="bold")

axes[0].grid(True, alpha=0.3, axis="y")

# 换型时间

axes[1].bar(strategies, co_times, color="#E74C3C", alpha=0.8)

axes[1].set_ylabel("换型时间 (min)", fontsize=12)

axes[1].set_title("总换型时间对比", fontsize=13, fontweight="bold")

axes[1].grid(True, alpha=0.3, axis="y")

# 产能损失率

axes[2].bar(strategies, loss_rates, color="#27AE60", alpha=0.8)

axes[2].set_ylabel("产能损失率 (%)", fontsize=12)

axes[2].set_title("产能损失率对比", fontsize=13, fontweight="bold")

axes[2].grid(True, alpha=0.3, axis="y")

plt.tight_layout()

plt.savefig(self.results_dir / "comparison_bar.png",

dpi=150, bbox_inches="tight")

plt.close()

print(f" 已保存: {self.results_dir / 'comparison_bar.png'}")

def plot_loss_pie(self, result: Dict):

"""绘制产能损失占比"""

print(f"[INFO] 绘制产能损失占比...")

co_hours = result["changeover_time_min"] / 60.0

prod_hours = result["production_time_hours"]

fig, ax = plt.subplots(figsize=(7, 7))

sizes = [co_hours, prod_hours]

labels = [f"换型 {co_hours:.1f}h", f"生产 {prod_hours:.1f}h"]

colors = ["#E74C3C", "#3498DB"]

ax.pie(sizes, labels=labels, colors=colors, autopct="%1.1f%%",

startangle=90, textprops={"fontsize": 12})

ax.set_title(f"产能时间分布 - {result['strategy']}",

fontsize=14, fontweight="bold")

plt.tight_layout()

safe_title = result["strategy"].replace(" ", "_")

plt.savefig(self.results_dir / f"loss_pie_{safe_title}.png",

dpi=150, bbox_inches="tight")

plt.close()

print(f" 已保存: {self.results_dir / f'loss_pie_{safe_title}.png'}")

# ----------------------------------------------------------------------

# 7. 报告生成器

# ----------------------------------------------------------------------

class ReportGenerator:

"""分析报告生成器"""

def __init__(self):

self.results_dir = Path("results")

os.makedirs(self.results_dir, exist_ok=True)

def generate(self, results: List[Dict]) -> str:

"""生成报告"""

print("[INFO] 生成分析报告...")

report_lines = []

report_lines.append("=" * 80)

report_lines.append("产线换型仿真与工单顺序优化分析报告")

report_lines.append("=" * 80)

for r in results:

report_lines.append(f"\n{'─'*60}")

report_lines.append(f"策略: {r['strategy']}")

report_lines.append(f"{'─'*60}")

report_lines.append(f" 工单数: {r['orders']}")

report_lines.append(f" 换型次数: {r['changeovers']}")

report_lines.append(f" 总换型时间: {r['changeover_time_min']:.1f} min")

report_lines.append(f" 有效生产时间: {r['production_time_hours']:.1f} h")

report_lines.append(f" 总耗时: {r['total_time_hours']:.1f} h")

report_lines.append(f" 产能损失率: {r['loss_rate']:.1f}%")

report_lines.append("\n" + "=" * 80)

report_lines.append("报告生成完毕")

report_lines.append("=" * 80)

report_text = "\n".join(report_lines)

report_path = self.results_dir / "simulation_report.txt"

with open(report_path, "w", encoding="utf-8") as f:

f.write(report_text)

print(f" 报告已保存: {report_path}")

return report_text

# ----------------------------------------------------------------------

# 8. 主程序演示

# ----------------------------------------------------------------------

def demo():

"""完整演示流程"""

print("=" * 80)

print("产线换型仿真:模拟产品切换停机,优化工单投产顺序")

print("=" * 80)

# 1. 创建工单(模拟现场18个工单)

print("\n[INFO] 步骤1: 创建工单...")

orders = [

WorkOrder(1, "A", 200, 45, 8),

WorkOrder(2, "B", 150, 52, 12),

WorkOrder(3, "A", 100, 45, 16),

WorkOrder(4, "C", 80, 60, 20),

WorkOrder(5, "B", 120, 52, 24),

WorkOrder(6, "D", 60, 70, 28),

WorkOrder(7, "A", 180, 45, 32),

WorkOrder(8, "C", 90, 60, 36),

WorkOrder(9, "B", 110, 52, 40),

WorkOrder(10, "D", 50, 70, 44),

WorkOrder(11, "A", 160, 45, 48),

WorkOrder(12, "C", 70, 60, 52),

WorkOrder(13, "B", 140, 52, 56),

WorkOrder(14, "D", 80, 70, 60),

WorkOrder(15, "A", 120, 45, 64),

WorkOrder(16, "C", 100, 60, 68),

WorkOrder(17, "B", 90, 52, 72),

WorkOrder(18, "A", 200, 45, 76),

]

print(f" 工单总数: {len(orders)}")

print(f" 产品类型: A, B, C, D")

# 2. 换型矩阵

print("\n[INFO] 步骤2: 初始化换型矩阵...")

matrix = ChangeoverMatrix(["A", "B", "C", "D"])

print(f" 产品: {matrix.products}")

# 3. 运行仿真

print("\n[INFO] 步骤3: 运行不同排产策略仿真...")

simulator = Simulator(matrix)

strategies = {

"原始顺序": Scheduler.original,

"同类聚合": Scheduler.same_product,

"最近邻": lambda o: Scheduler.nearest_neighbor(o, matrix),

"遗传算法": lambda o: Scheduler.genetic_algorithm(o, matrix),

}

results = []

for name, func in strategies.items():

print(f"\n --- {name} ---")

sequenced = func(orders)

result = simulator.run(sequenced, name)

results.append(result)

print(f" 换型次数: {result['changeovers']}")

print(f" 换型时间: {result['changeover_time_min']:.1f} min")

print(f" 产能损失: {result['loss_rate']:.1f}%")

# 4. 可视化

print("\n[I

利用AI解决实际问题,如果你觉得这个工具好用,欢迎关注长安牧笛!

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询