ARTICLE DETAIL

资讯详情

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

python的先进制造技术工业场景模拟第八十七篇:构建FMS缓冲站仿真调整缓冲区容量,对比不同容量下夹具等待与零件等待时间。

python的先进制造技术工业场景模拟第八十七篇:构建FMS缓冲站仿真调整缓冲区容量,对比不同容量下夹具等待与零件等待时间。 周二上午FMS控制室。“老郑2号加工中心又空转了前面缓冲站堆了5个托盘后面却等夹具”老郑盯着调度屏“缓冲区设了4位混流一来A类件占满B类件没地方放只能堵在线上等可夹具库那边也排着队两边都在等产能就漏了。”我接上导出的托盘到达日志、缓冲区占用序列、夹具占用、MC状态、零件等待时长、夹具等待时长。“这里面有啥”我问。“托盘ID、到达时刻、零件类型A/B/C、缓冲区位占用、目标MC、所需夹具号、夹具释放时刻都有”老郑说“可系统只画个Gantt不模拟‘缓冲区容量从4改到8改到12夹具等待和零件等待怎么此消彼长’。想定容量得真跑几天产线看堵不堵。”“最亏的是拐点区”老郑补一句“容量4的时候零件等得久加到8零件等待降了但夹具被占着不释放夹具等待反而涨加到12两边都没明显改善还占场地。中间有个最优解系统算不出来。”“我就想干一件事”老郑说“给到达过程服务时间夹具约束扫不同缓冲区容量仿真出零件等待、夹具等待、利用率画出来对比标出最优容量像个小缓冲站容量仿真器不用真改产线布局。”“FMS不是看设备转没转”我接话“是看‘托盘到了没位子、有位子没夹具、有夹具MC又忙’这条链。用 numpyheapq 做事件驱动离散仿真scipy 拟合到达分布pandas 管工单matplotlib 画容量对比曲线等待分布networkx 建‘到达-缓冲-夹具-MC’二部图sklearn 做等待等级分类。”“对”老郑点头“要能说清‘容量4零件等12.4min夹具等5.1min容量8零件4.8min夹具6.3min容量10零件4.1min夹具6.5min拐点就在8~10之间主因是B类密集到达夹具数不足’。”“OOP 封好”我开工程“工单加载器、事件调度器、缓冲区模型、夹具资源池、等待分析器、容量扫描器、可视化器合成多容量对比下载就能跑。”敲了行原型# 目标: 扫缓冲区容量 → 事件驱动仿真 → 零件等待 vs 夹具等待 → 最优容量# 方法: 离散事件(heapq)二部资源图RF分类对比曲线老郑凑近看“那以后看报告容量-等待对比曲线零件/夹具等待分布直方图资源竞争二部图最优容量标绿分类散点。新线设计前先扫容量不用先焊货架。”“对”我接话“缓冲站仿真不是‘画个队列’是‘提前看见容量加到几刚好不堵’。数字孪生里挂这个缓冲看板就是老郑的‘容量标尺’。”一、实际应用场景真实痛点场景设定柔性制造系统FMS含 3 台加工中心MC、1 个中央缓冲站托盘位、共享夹具库F1×2、F2×1。混流加工 A/B/C 三类零件B 类到货密集且需 F2。缓冲站容量固定为 4 位时混流高峰频繁出现“托盘堵在入口 / 夹具排着队释放”并存现象设备空转与在制品积压同时发生。现场原话叙事化“不是设备不够”老郑说“是缓冲位卡得死。B类一波来5个位子被占满A类进不来可F2就1套B类占着F2加工完还不释放后面B类在缓冲里干等夹具也在等空托盘位回流两头耗。”“最亏的是定容量”老郑说“以前按经验焊了4个位后来加焊到12个发现零件等待是降了可夹具等待没降反升场地还浪费。到底几个位最划算没人算得清。”核心矛盾“固定缓冲容量看Gantt” 与 “容量扫描→事件驱动仿真→零件等待/夹具等待解耦量化→最优容量资源竞争图” 之间的断层。二、痛点分析映射到滨州职业学院《先进制造技术》课程模型《先进制造技术》课程模块 本篇痛点对应柔性制造系统FMS与先进生产管理缓冲站、托盘系统、资源调度、在制品控制、产能平衡 容量-等待权衡全链建模先进制造技术基础系统效率、瓶颈分析、排队现象 零件/夹具双等待量化数控加工与CAD/CAM技术MC加工节拍、装夹基准 夹具占用与装夹耦合智能制造与数字孪生FMS状态数字映射、缓冲看板 缓冲资源挂孪生先进制造新模式数据驱动产线配置优化 容量知识库沉淀一句话总结我们需要一个“缓冲区容量扫描→离散事件仿真→零件等待 vs 夹具等待→资源竞争二部图→最优容量判定分类”程序实现从“凭经验焊货架”到“仿真定容量”的闭环。三、核心逻辑讲解大白话3.1 问题本质把缓冲站想成“车间门口的候车室”把缓冲站想成一个候车室托盘是乘客夹具是登机口MC是飞机* 容量4 候车室只有4把椅子人多了站门口堵着 → 零件等待长* 零件等待 托盘到了没位子或有位子但MC忙* 夹具等待 有托盘有位子但对应夹具被别的件占着* 夹具释放卡住 加工完的件占着位子不流转夹具也跟着卡* 容量加大 椅子多了零件不站门口了但登机口夹具还是那几个反而被“占座式”占用夹具等待涨* 最优容量 椅子加到某个数两边等待都收敛再多加就是浪费* 试产定容量 真焊货架试几天贵且不可逆3.2 业务逻辑 → 代码映射输入工单流资源配置│▼ WorkOrderLoader (pandas)读取:工单号, 到达时刻, 类型A/B/C, 目标MC, 所需夹具, 加工时长│▼ EventScheduler (numpy heapq)离散事件引擎:事件: 到达 / 入缓冲 / 申请夹具 / 开工 / 完工 / 出缓冲按仿真钟推进, 记录各阶段时间戳│▼ BufferStation (纯numpy结构)缓冲站模型:容量C, FIFO入站, 满则入口排队统计入站等待(零件等待组成)│▼ FixturePool (numpy)夹具资源池:F1×2, F2×1, 按类型绑定申请不到则进夹具等待队列│▼ WaitAnalyzer (numpy scipy)等待分析:零件等待 到达→开工夹具等待 可加工→拿到夹具拟合等待分布(lognorm), 算P95│▼ CapacitySweeper容量扫描:C ∈ [4,6,8,10,12] 各跑N次复现输出等待随容量曲线│▼ WaitClassifier (sklearn)等待等级分类:特征: 容量, 类型, 到达间隔, 夹具竞争度标签: 优(≤3min)/良(3~8min)/劣(8min)RF分类 5折宏F1│▼ FMSViz (matplotlib networkx)可视化:1. 容量-零件等待/夹具等待对比曲线2. 不同容量等待分布直方图3. 资源竞争二部图(托盘↔夹具↔MC)4. 容量-利用率热力条5. 等待等级预测vs实际散点6. 单容量甘特示意│▼ SyntheticOrders (numpy)合成数据:60工单 A/B/C混流, B类密集到达, 需F23.3 为什么不能“看Gantt”视角 问题看MC甘特 空转时看不出是等件还是等夹具看缓冲占用 只知满了不知零件等还是夹具等凭经验加位 加到12才发现夹具等待反升容量扫描曲线 看见零件等待↓夹具等待↑的拐点二部图 谁在跟谁抢资源一眼清RF分类 新工单流直接判等待等级3.4 分析前后对比维度 传统方式 本程序定容量 经验焊4位 扫容量出拐点等待归因 混在一起看 零件等待/夹具等待解耦最优解 拍脑袋 加权成本最小资源竞争 靠调度屏猜 二部图量化知识沉淀 产线改完才知 容量知识库可复用四、OOP 代码实现4.1 项目结构fms_buffer_sim/├── fms_buffer_sim/│ ├── __init__.py│ ├── work_order_loader.py # 工单加载│ ├── event_scheduler.py # 离散事件引擎(heapqnumpy)│ ├── buffer_station.py # 缓冲站模型│ ├── fixture_pool.py # 夹具资源池│ ├── wait_analyzer.py # 等待分析(scipy)│ ├── capacity_sweeper.py # 容量扫描│ ├── wait_classifier.py # 等待分类(sklearn)│ ├── fms_viz.py # 可视化│ └── synthetic_orders.py # 合成工单├── tests/│ ├── __init__.py│ └── test_buffer.py├── results/│ ├── capacity_wait_curve.png│ ├── wait_dist_hist.png│ ├── resource_bipartite.png│ ├── util_heatbar.png│ ├── wait_pred_scatter.png│ ├── gantt_sample.png│ ├── buffer_detail.csv│ └── buffer_report.txt└── run_buffer.py4.2 核心源码detailssummary/summaryFMS工单加载器。import pandas as pdfrom pathlib import Pathclass WorkOrderLoader:加载混流工单表。def __init__(self, filepath: str orders.csv,encoding: str utf-8):self.filepath Path(filepath)self.encoding encodingdef load(self) - pd.DataFrame:if not self.filepath.exists():raise FileNotFoundError(self.filepath)df pd.read_csv(self.filepath, encodingself.encoding)req [oid, arr_t, ptype, mc, fixture, proc_t]miss [c for c in req if c not in df.columns]if miss:raise ValueError(f缺列: {miss})for c in [arr_t, proc_t, mc]:df[c] pd.to_numeric(df[c], errorscoerce)return df.dropna(subsetreq).reset_index(dropTrue)def summary(self, df: pd.DataFrame) - str:s f工单数: {len(df)}\ns f类型分布: {df[ptype].value_counts().to_dict()}\ns f到达时段: {df.arr_t.min():.0f}~{df.arr_t.max():.0f}s\ns f夹具需求: {df[fixture].value_counts().to_dict()}return s.rstrip()/detailsdetailssummary/summary离散事件调度引擎 (heapq numpy)。import heapqimport numpy as npfrom dataclasses import dataclass, fieldfrom typing import List, Dictdataclassclass OrderTrace:oid: strptype: strarr_t: floatin_buf_t: floatreq_fix_t: floatstart_t: floatend_t: floatpart_wait: floatfix_wait: floatmc: intfixture: strclass EventScheduler:事件类型:ARR 到达, BUF_IN 入缓冲, REQ_FIX 申请夹具,START 开工, END 完工, BUF_OUT 出缓冲仿真钟用堆维护def __init__(self, buffer_cap: int, fixture_pool, buffer_station,rng: np.random.RandomState None):self.cap buffer_capself.fp fixture_poolself.bs buffer_stationself.rng rng or np.random.RandomState(42)self.traces: List[OrderTrace] []def run(self, orders: List[dict]) - List[OrderTrace]:heap []for o in orders:heapq.heappush(heap, (o[arr_t], ARR, o))clock 0.0mc_free {1: 0.0, 2: 0.0, 3: 0.0}pending [] # 已入缓冲待夹具while heap:clock, typ, payload heapq.heappop(heap)if typ ARR:o payloadin_buf_t clockself.bs.occupy()heapq.heappush(pending, o)o[_in_buf] in_buf_t# 尝试分配heapq.heappush(heap, (clock, TRY_ALLOC, o))elif typ TRY_ALLOC:o payloadmc int(o[mc])fix o[fixture]if self.bs.count self.cap and clock mc_free[mc]:got self.fp.try_acquire(fix, clock)if got is not None:req_fix_t clockstart max(clock, mc_free[mc])end start float(o[proc_t])mc_free[mc] endself.bs.release()self.fp.release(fix, end)trace OrderTrace(oido[oid], ptypeo[ptype],arr_to[arr_t], in_buf_to[_in_buf],req_fix_treq_fix_t, start_tstart,end_tend,part_waitstart - o[arr_t],fix_waitstart - req_fix_t,mcmc, fixturefix)self.traces.append(trace)else:# 等夹具, 推后重试heapq.heappush(heap, (clock0.5, TRY_ALLOC, o))else:# 缓冲满或MC忙, 推后heapq.heappush(heap, (clock0.5, TRY_ALLOC, o))return self.traces/detailsdetailssummary/summary缓冲站模型。class BufferStation:def __init__(self, cap: int):self.cap capself.count 0def occupy(self):if self.count self.cap:self.count 1return Truereturn Falsedef release(self):if self.count 0:self.count - 1def reset(self):self.count 0/detailsdetailssummary/summary夹具资源池。from dataclasses import dataclassfrom typing import Dict, Optionalclass FixturePool:F1: 2套, F2: 1套def __init__(self):self.stock: Dict[str, int] {F1: 2, F2: 1}self.busy: Dict[str, int] {F1: 0, F2: 0}def try_acquire(self, fix: str, t: float) - Optional[float]:if self.busy[fix] self.stock[fix]:self.busy[fix] 1return treturn Nonedef release(self, fix: str, t: float):if self.busy[fix] 0:self.busy[fix] - 1def reset(self):self.busy {F1: 0, F2: 0}/detailsdetailssummary/summary等待分析 (numpy scipy)。import numpy as npfrom dataclasses import dataclassfrom scipy.stats import lognormdataclassclass WaitStat:part_mean: floatpart_p95: floatfix_mean: floatfix_p95: floatparams: tupleclass WaitAnalyzer:staticmethoddef stat(part_wait, fix_wait):pw np.array(part_wait, dtypefloat)fw np.array(fix_wait, dtypefloat)# lognorm拟合零件等待pos np.clip(pw[pw0], 1e-3, None)s, loc, scale lognorm.fit(pos, floc0)p95 lognorm.ppf(0.95, s, loc, scale)return WaitStat(float(pw.mean()), float(p95),float(fw.mean()), float(np.percentile(fw,95)),(s,loc,scale))staticmethoddef cost(part_mean, fix_mean, cap,cost_part1.0, cost_fix1.2, cost_slot0.4):# 综合成本: 等待损失占位成本return part_mean*cost_part fix_mean*cost_fix cap*cost_slot/detailsdetailssummary/summary容量扫描器。import numpy as npimport pandas as pdfrom typing import List, Dictfrom .event_scheduler import EventSchedulerfrom .buffer_station import BufferStationfrom .fixture_pool import FixturePoolfrom .wait_analyzer import WaitAnalyzerclass CapacitySweeper:def __init__(self, orders: List[dict], caps: List[int] None,reps: int 5, seed: int 42):self.orders ordersself.caps caps or [4,6,8,10,12]self.reps repsself.seed seeddef sweep(self) - pd.DataFrame:rows []for cap in self.caps:part_all, fix_all [], []for r in range(self.reps):rng np.random.RandomState(self.seed r)bs BufferStation(cap)fp FixturePool()sch EventScheduler(cap, fp, bs, rngrng)orders [dict(o) for o in self.orders]traces sch.run(orders)part_all [t.part_wait for t in traces]fix_all [t.fix_wait for t in traces]st WaitAnalyzer.stat(part_all, fix_all)c WaitAnalyzer.cost(st.part_mean, st.fix_mean, cap)rows.append({cap: cap,part_wait_mean: st.part_mean,part_wait_p95: st.part_p95,fix_wait_mean: st.fix_mean,fix_wait_p95: st.fix_p95,total_cost: c,})return pd.DataFrame(rows)/detailsdetailssummary/summary等待等级分类 (sklearn)。import numpy as npimport pandas as pdfrom typing import Dictfrom sklearn.ensemble import RandomForestClassifierfrom sklearn.model_selection import cross_val_score, KFoldclass WaitClassifier:优(≤3min)/良(3~8min)/劣(8min) 三分类, 按零件等待。def __init__(self, random_state: int 42):self.model_ Noneself.feat [cap, arr_gap, fix_contend, ptype_enc, mc]staticmethoddef _label(w: float) - str:w w/60.0 # 秒转分if w 3: return 优if w 8: return 良return 劣def fit(self, df: pd.DataFrame, part_wait_sec: np.ndarray):y np.array([self._label(w) for w in part_wait_sec])self.model_ RandomForestClassifier(n_estimators300, max_depth6, min_samples_leaf2,random_state42, n_jobs-1)self.model_.fit(df[self.feat].values, y)return selfdef cv(self, df: pd.DataFrame, part_wait_sec: np.ndarray) - Dict:y np.array([self._label(w) for w in part_wait_sec])kf KFold(5, shuffleTrue, random_state42)sc cross_val_score(self.model_, df[self.feat].values, y,cvkf, scoringf1_macro)imp dict(zip(self.feat, self.model_.feature_importances_))return {f1_macro: float(sc.mean()),importance: dict(sorted(imp.items(),keylambda x:x[1], reverseTrue))}def predict(self, df: pd.DataFrame) - np.ndarray:return self.model_.predict(df[self.feat].values)/detailsdetailssummary/summaryFMS缓冲站可视化 (matplotlib networkx)。import numpy as npimport pandas as pdimport matplotlib.pyplot as pltfrom pathlib import Pathimport networkx as nxplt.rcParams[font.sans-serif] [SimHei, WenQuanYi Micro Hei, DejaVu Sans]plt.rcParams[axes.unicode_minus] FalseGRADE {优:#27AE60,良:#F39C12,劣:#E74C3C}class FMSViz:def __init__(self, results_dir: str results):self.results_dir Path(results_dir)self.results_dir.mkdir(exist_okTrue)def capacity_curve(self, df: pd.DataFrame):fig, ax1 plt.subplots(figsize(11,6))ax1.plot(df.cap, df.part_wait_mean/60, o-, color#2980B9, lw2,label零件等待(分))ax1.plot(df.cap, df.fix_wait_mean/60, s--, color#E67E22, lw2,label夹具等待(分))ax1.set_xlabel(缓冲区容量 (位), fontsize12)ax1.set_ylabel(平均等待 (min), fontsize12)ax2 ax1.twinx()ax2.plot(df.cap, df.total_cost, ^-, color#7F8C8D, lw1.5,label综合成本)ax1.set_title(容量-等待对比曲线(找拐点),fontsize13, fontweightbold)# 标最优best df.loc[df.total_cost.idxmin()]ax1.axvline(best.cap, color#27AE60, ls:, lw2,labelf最优容量{best.cap})lines1,labels1 ax1.get_legend_handles_labels()lines2,labels2 ax2.get_legend_handles_labels()ax1.legend(lines1lines2, labels1labels2, locupper right)ax1.grid(alpha0.3)plt.tight_layout()plt.savefig(self.results_dir/capacity_wait_curve.png,dpi150, bbox_inchestight)plt.close()def wait_hist(self, traces_by_cap: dict):fig, axes plt.subplots(1,2, figsize(14,5))for cap, arr in traces_by_cap.items():axes[0].hist(np.array(arr[part])/60, bins20, alpha0.5,labelfcap{cap})axes[1].hist(np.array(arr[fix])/60, bins20, alpha0.5,labelfcap{cap})axes[0].set_title(零件等待分布, fontsize12, fontweightbold)axes[1].set_title(夹具等待分布, fontsize12, fontweightbold)axes[0].set_xlabel(min); axes[1].set_xlabel(min)axes[0].legend(); axes[1].legend(); axes[0].grid(alpha0.3)plt.tight_layout()plt.savefig(self.results_dir/wait_dist_hist.png,dpi150, bbox_inchestight)plt.close()def bipartite(self, traces):fig, ax plt.subplots(figsize(11,7))G nx.Graph()for i,t in enumerate(traces[:40]):pn fP{i}G.add_node(pn, bipartite0)fn t.fixtureG.add_node(fn, bipartite1)G.add_edge(pn, fn)G.add_node(fMC{t.mc}, bipartite1)G.add_edge(pn, fMC{t.mc})pos {}pos.update({n:(0, i*0.3) for i,n in enumerate([n for n in G if G.nodes[n][bipartite]0])})pos.update({n:(1, i*0.6) for i,n in enumerate([n for n in G if G.nodes[n][bipartite]1])})nx.draw_networkx_nodes(G,pos,nodelist[n for n in G if G.nodes[n][bipartite]0],node_color#3498DB,node_size300,axax)nx.draw_networkx_nodes(G,pos,nodelist[n for n in G if G.nodes[n][bipartite]1],node_color#E67E22,node_size600,axax)nx.draw_networkx_edges(G,pos,edge_color#888,axax,alpha0.6)nx.draw_networkx_labels(G,pos,font_size7,axax)ax.set_title(托盘-夹具-MC 资源竞争二部图,fontsize14, fontweightbold)ax.axis(off)plt.tight_layout()plt.savefig(self.results_dir/resource_bipartite.png,dpi150, bbox_inchestight)plt.close()def util_bar(self, df, mc_util):fig, ax plt.subplots(figsize(10,5))x np.arange(len(df))ax.bar(x-0.2, df.part_wait_mean/60, 0.4, label零件等待, color#2980B9)ax.bar(x0.2, df.fix_wait_mean/60, 0.4, label夹具等待, color#E67E22)ax.set_xticks(x); ax.set_xticklabels(df.cap)for i,u in enumerate(mc_util):ax.text(i, 0.1, fMC{u:.2f}, hacenter, fontsize8)ax.set_xlabel(容量, fontsize12)ax.set_ylabel(等待(min), fontsize12)ax.set_title(容量-等待柱状MC利用率标注,fontsize13, fontweightbold)ax.legend(); ax.grid(alpha0.3)plt.tight_layout()plt.savefig(self.results_dir/util_heatbar.png,dpi150, bbox_inchestight)plt.close()def pred_scatter(self, y_true, y_pred):fig, ax plt.subplots(figsize(8,8))labels[优,良,劣]ctnp.array([labels.index(y) for y in y_true])cpnp.array([labels.index(y) for y in y_pred])ax.scatter(ct,cp,c#2980B9,s50,edgecolorsk,alpha0.8)ax.plot([-0.5,2.5],[-0.5,2.5],r--,lw2,label理想)ax.set_xticks([0,1,2]);ax.set_xticklabels(labels)ax.set_yticks([0,1,2]);ax.set_yticklabels(labels)ax.set_xlabel(实际等级,fontsize12)ax.set_ylabel(预测等级,fontsize12)ax.set_title(等待等级 预测vs实际,fontsize13,fontweightbold)ax.legend();ax.grid(alpha0.3)plt.tight_layout()plt.savefig(self.results_dir/wait_pred_scatter.png,dpi150,bbox_inchestight)plt.close()def gantt(self, traces, cap):fig, ax plt.subplots(figsize(12,4))for i,t in enumerate(traces[:25]):ax.broken_barh([(t.start_t, t.end_t-t.start_t)], (t.mc*10, 8),facecolors#27AE60 if t.ptypeA else(#2980B9 if t.ptypeB else #8E44AD))ax.plot([t.arr_t,t.start_t],[t.mc*104,t.mc*104],color#E74C3C,lw1.2,alpha0.7)ax.set_yticks([10,20,30])ax.set_yticklabels([MC1,MC2,MC3])ax.set_xlabel(仿真时间 (s), fontsize12)ax.set_title(f单容量甘特示意(cap{cap}) 红线等待段,fontsize13, fontweightbold)ax.grid(alpha0.3)plt.tight_layout()plt.savefig(self.results_dir/gantt_sample.png,dpi150,bbox_inchestight)plt.close()/detailsdetailssummary/summary合成FMS工单流。import numpy as npimport pandas as pdfrom pathlib import Pathfrom typing import Optional, List, Dictclass SyntheticOrders:60工单 A/B/C混流B类密集到达, 需F2A类需F1, C类需F1def __init__(self, rng: Optional[np.random.RandomState] None):self.rng rng or np.random.Rand利用AI解决实际问题如果你觉得这个工具好用欢迎关注长安牧笛
返回列表