ARTICLE DETAIL

资讯详情

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

python的先进制造技术工业场景模拟第八十三篇:搭建FMS夹具资源调度仿真,多工单竞争夹具,模拟夹具等待冲突与资源分配。

python的先进制造技术工业场景模拟第八十三篇:搭建FMS夹具资源调度仿真,多工单竞争夹具,模拟夹具等待冲突与资源分配。 周三上午FMS控制室。“这批混流订单A件要液压夹具F1B件要组合夹具F2C件两个都能用”调度老郑盯着排产屏“排产表上看着都排开了一跑起来就堵。三台加工中心闲着F2却卡在B件上后面A件也跟着等像高速路出口只开一个闸口。”我接上导出的工单表、夹具占用日志、设备状态流。“这里面有啥”我问。“工单号、零件族、可用夹具集合、到达时间、加工时长、机床绑定关系都有”老郑说“可系统只做顺序派工不模拟‘多工单抢同一套夹具’‘夹具释放时刻冲突’‘等待时长怎么滚雪球’。想加一套夹具值不值得真跑一周看OEE。”“最亏的是隐性等待”老郑补一句“机床利用率0.71看着还行其实全耗在等夹具上F2利用率0.96等于它快冒烟了MC还在空转。排产软件算的是机床时间不算夹具占用时间。”“我就想干一件事”老郑说“给工单流夹具资源池用事件驱动模拟多工单抢夹具算出每单等夹具多久、哪套是瓶颈、加一套能降多少等待像个小资源调度仿真器不用真改产线。”“FMS不是看机床转没转”我接话“是看‘夹具这个稀缺资源怎么被抢’。用 numpy 做事件时钟pandas 管工单heapq 做最早事件队列networkx 画资源竞争图scipy 做等待分布统计sklearn 预测等待等级matplotlib 画甘特竞争热力瓶颈图。”“对”老郑点头“要能说清‘F2是瓶颈等夹具占总换产等待39%加1套F2后等夹具降60%工单W07等夹具9.1min变3.2minF2利用率0.96→0.61’。”“OOP 封好”我开工程“工单加载器、事件调度引擎、夹具分配器、等待分析器、瓶颈识别器、可视化器合成多工单混流数据下载就能跑。”敲了行原型# 目标: 工单流 → 事件驱动抢夹具 → 等待冲突 → 瓶颈识别# 方法: heapq事件时钟 networkx竞争图 RF等待分级 甘特图老郑凑近看“那以后看报告机床-夹具甘特图工单等夹具散点夹具竞争热力图资源关联网络加夹具前后对比。新工单进来先跑红条就是卡点。”“对”我接话“FMS仿真不是‘画排产表’是‘提前看见哪套夹具先冒烟、哪个工单先排队’。数字孪生里挂这个资源看板就是调度的‘疏堵镜’。”一、实际应用场景真实痛点场景设定FMS 柔性加工单元3台加工中心MC1/MC2/MC32类专用夹具 F1液压锁A类件、F2组合锁B类件1套共用逻辑1台AGV做上下料。混流工单 A/B/C 同时流入C 类可兼容 F1/F2。现场按“先到先服务”派工未建模夹具作为独立资源约束导致机床空转等夹具。现场原话叙事化“不是机床不够”老郑说“是夹具不够。三台MC亮绿灯F2却卡在B件上拆装后面排的A件本来能用F1可它跟B件共用了一道AGV也跟着堵。排产表算的是机床分钟数没算夹具被占的分钟数。”“最亏的是加夹具的决策”老郑说“领导问加一套F2值不值我们没法答只能先买回来跑一周OEE掉一截才知道对了没。”核心矛盾“按机床排产先到先服务” 与 “事件驱动夹具资源调度竞争图等待量化瓶颈识别” 之间的断层。二、痛点分析映射到滨州职业学院《先进制造技术》课程模型《先进制造技术》课程模块 本篇痛点对应柔性制造系统FMS与先进生产管理资源调度、夹具管理、工单派工、瓶颈识别、OEE 多工单抢夹具等待量化加夹具决策数控加工与CAD/CAM技术夹具与装夹方案、工序绑定 夹具-工单-机床绑定关系先进制造技术基础制造系统节拍、资源约束理论 夹具为瓶颈资源智能制造与数字孪生资源状态数字映射、调度看板 夹具占用/等待挂孪生先进制造新模式按订单混流、资源知识库 夹具配置知识沉淀一句话总结我们需要一个“工单流→事件驱动抢夹具→等待冲突量化→瓶颈识别→加资源对比”程序实现从“按机床排产”到“按夹具资源约束动态调度”的闭环。三、核心逻辑讲解大白话3.1 问题本质把FMS想成“几个插座不够用”把 FMS 想成一个办公室3台电脑机床都开着但只有2个专用充电器夹具F1/F2手机工单一堆人排队充* 工单 要充电的手机有的只能用苹果头F1有的只能用安卓头F2有的都行C类* 夹具 充电器数量有限被占着就释放不了* 机床 电脑闲着也没用没充电器插不上* 等夹具 手机排队等充电器的时间跟电脑闲不闲无关* 先到先服务 谁先来谁先插结果苹果头空着安卓头排长队* 事件驱动仿真 按“某时刻某夹具释放”推演不是按分钟扫表* 加一套F2 多一个安卓头排队直接短一截3.2 业务逻辑 → 代码映射输入工单流夹具资源池机床池│▼ WorkOrderLoader (pandas)读取表:工单号, 零件族(A/B/C), 可用夹具集, 到达时刻,加工时长, 绑定机床偏好│▼ EventDrivenScheduler (numpy heapq)事件时钟:(时间, 事件类型, 工单号) 最小堆事件: 到达 / 夹具释放 / 加工完成每次弹最早事件, 更新资源状态│▼ FixtureAllocator (networkx辅助)夹具分配:工单就绪后, 在可用夹具集中找空闲且匹配者无空闲→入等待队列, 累计 wait_fixture支持C类兼容择优(选先释放的)│▼ WaitAnalyzer (scipy numpy)等待分析:按工单统计 wait_fixture / wait_queue / 总等待拟合等待时长分布, 算分位数识别“等夹具占比”│▼ BottleneckDetector (networkx numpy)瓶颈识别:建 工单-夹具-机床 二部图算各夹具利用率 占用时长/仿真总时长利用率最高且0.85 → 瓶颈│▼ WaitClassifier (sklearn)等待等级预测:特征: 零件族, 到达密度, 夹具兼容数, 同刻竞争工单数标签: 低等(2min)/中等/高等(6min)RF三分类 5折宏F1│▼ FmsVisualizer (matplotlib networkx)可视化:1. 机床-夹具甘特图(标等待段)2. 工单等夹具散点3. 夹具竞争热力图(时刻×夹具)4. 工单-夹具-机床资源网络5. 加夹具前后等待对比柱状6. 夹具利用率雷达/条形│▼ SyntheticFmsOrders (numpy)合成数据:多工单混流, 机制: B类集中到货→F2挤, C类兼容可疏解3.3 为什么不能“按机床排产”视角 问题看机床利用率 0.71看着健康掩盖等夹具按先到先服务 兼容夹具不择优资源错配看排产表 静态不反映释放冲突事件驱动 精确到夹具释放时刻等待拆解 等夹具/等队列分开算竞争图 谁跟谁抢一眼看清加资源仿真 先算再加不盲买3.4 分析前后对比维度 传统方式 本程序瓶颈发现 凭感觉说F2忙 F2利用率0.96量化等待归因 混在换产里 等夹具占39%可拆加夹具决策 买了再试 仿真预测降60%派工策略 先到先服务 C类兼容择优知识沉淀 调度经验 夹具配置知识库四、OOP 代码实现4.1 项目结构fms_fixture_sched/├── fms_fixture_sched/│ ├── __init__.py│ ├── work_order_loader.py # 工单加载│ ├── event_scheduler.py # 事件驱动调度(heapqnumpy)│ ├── fixture_allocator.py # 夹具分配│ ├── wait_analyzer.py # 等待分析(scipy)│ ├── bottleneck_detector.py # 瓶颈识别(networkx)│ ├── wait_classifier.py # 等待分级(sklearn)│ ├── fms_visualizer.py # 可视化│ └── synthetic_fms_orders.py # 合成工单├── tests/│ ├── __init__.py│ └── test_fms.py├── results/│ ├── gantt_mc_fixture.png│ ├── wait_fixture_scatter.png│ ├── fixture_compete_heatmap.png│ ├── resource_network.png│ ├── add_fixture_compare.png│ ├── fixture_util_bar.png│ ├── fms_detail.csv│ └── fms_report.txt└── run_fms.py4.2 核心源码detailssummary/summaryFMS工单加载器。import pandas as pdfrom pathlib import Pathclass WorkOrderLoader:加载混流工单与夹具可用关系。def __init__(self, filepath: str fms_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 [order_id, family, fixtures, arrive_t,process_t, mc_pref]miss [c for c in req if c not in df.columns]if miss:raise ValueError(f缺列: {miss})for c in [arrive_t, process_t]:df[c] pd.to_numeric(df[c], errorscoerce)df[fixtures] df[fixtures].apply(lambda s: s.split(|))return df.dropna(subset[arrive_t, process_t]).reset_index(dropTrue)def summary(self, df: pd.DataFrame) - str:s f工单数: {len(df)}\ns f零件族分布: {df[family].value_counts().to_dict()}\ns f到达窗口: {df[arrive_t].min():.0f}~{df[arrive_t].max():.0f}min\ns f平均加工时长: {df[process_t].mean():.1f}minreturn s.rstrip()/detailsdetailssummary/summary事件驱动调度引擎 (heapq numpy)。import heapqimport numpy as npfrom dataclasses import dataclass, fieldfrom typing import List, Tupledataclassclass OrderState:order_id: strfamily: strfixtures: List[str]arrive_t: floatprocess_t: floatmc_pref: strstart_t: float -1.0fixture_id: str mc_id: str finish_t: float -1.0wait_fixture: float 0.0wait_queue: float 0.0class EventDrivenScheduler:事件类型:0 arrive, 1 fixture_release, 2 process_done最小堆按时间弹事件, 更新夹具/机床占用。def __init__(self, fixture_pool: dict, mc_pool: list):# fixture_pool: {F1:2, F2:1} 数量self.fixture_pool fixture_poolself.mc_pool mc_poolself.clock 0.0self.fix_busy {k: [] for k in fixture_pool} # 占用列表 (order, release_t)self.mc_busy {m: -1.0 for m in mc_pool}self.fix_util_time {k: 0.0 for k in fixture_pool}self.log []def run(self, orders: List[OrderState]) - List[OrderState]:heap []for o in orders:heapq.heappush(heap, (o.arrive_t, 0, o.order_id, o))pending {o.order_id: o for o in orders}last_fix_release {k: 0.0 for k in self.fixture_pool}while heap:t, typ, oid, o heapq.heappop(heap)self.clock tif typ 0: # 到达self._try_assign(o, heap, last_fix_release)elif typ 1: # 夹具释放触发重分配self._release_fixture(oid, t)self._scan_pending(heap, last_fix_release, t)elif typ 2: # 加工完成self._release_mc(o, t)self._scan_pending(heap, last_fix_release, t)# 收尾利用率total self.clockfor k in self.fix_util_time:self.fix_util_time[k] / max(total, 1e-9)return list(pending.values())def _try_assign(self, o, heap, last_fix_release):# 找可用夹具avail [f for f in o.fixturesif len(self.fix_busy[f]) self.fixture_pool[f]]if avail:# C类兼容择优: 选最早释放的f min(avail, keylambda x: last_fix_release[x])mc self._pick_mc()if mc is None:o.wait_queue 0.0heapq.heappush(heap, (self.clock0.1, 0, o.order_id, o))returno.start_t self.clocko.fixture_id fo.mc_id mcrel self.clock o.process_tself.fix_busy[f].append((o.order_id, rel))self.mc_busy[mc] relself.fix_util_time[f] o.process_to.finish_t relheapq.heappush(heap, (rel, 2, o.order_id, o))self.log.append((o.order_id, f, mc, o.start_t, rel))else:# 等夹具wait_start self.clocko.wait_fixture 0.0 # 累积在扫描时补o._wait_start wait_startheapq.heappush(heap, (min(last_fix_release[f] for f in o.fixtures),1, o.order_id, o))def _scan_pending(self, heap, last_fix_release, t):# 简化: 对已在堆里等待的重新尝试(教学级)for item in list(heap):if item[1] 1:o item[3]if o.start_t 0:avail [f for f in o.fixturesif len(self.fix_busy[f]) self.fixture_pool[f]]if avail:f min(avail, keylambda x: last_fix_release[x])mc self._pick_mc()if mc:o.wait_fixture t - o._wait_starto.start_t to.fixture_id fo.mc_id mcrel t o.process_tself.fix_busy[f].append((o.order_id, rel))self.mc_busy[mc] relself.fix_util_time[f] o.process_to.finish_t rellast_fix_release[f] relheapq.heappush(heap, (rel, 2, o.order_id, o))self.log.append((o.order_id, f, mc, o.start_t, rel))heap.remove(item)heapq.heapify(heap)def _release_fixture(self, oid, t):for f in self.fix_busy:self.fix_busy[f] [x for x in self.fix_busy[f] if x[0] ! oid]def _pick_mc(self):free [m for m in self.mc_pool if self.mc_busy[m] self.clock]if not free:return Nonereturn sorted(free)[0]def _release_mc(self, o, t):if o.mc_id in self.mc_busy:self.mc_busy[o.mc_id] -1.0/detailsdetailssummary/summary夹具分配策略封装。import numpy as npfrom .event_scheduler import OrderStateclass FixtureAllocator:封装兼容择优逻辑, 便于扩展:- compat_first: C类选最早释放夹具- load_balance: 选当前占用数最少def __init__(self, strategy: str compat_first):self.strategy strategydef choose(self, avail_fixtures, last_release: dict,busy_count: dict) - str:if self.strategy compat_first:return min(avail_fixtures, keylambda x: last_release[x])if self.strategy load_balance:return min(avail_fixtures, keylambda x: busy_count[x])return avail_fixtures[0]/detailsdetailssummary/summary等待分析 (scipy numpy)。import numpy as npimport pandas as pdfrom dataclasses import dataclassfrom scipy import statsdataclassclass WaitStats:mean_fix: floatp95_fix: floatratio_fix: floatdist_name: strdist_params: tupleclass WaitAnalyzer:拆解等夹具/等队列, 拟合分布。def __init__(self):passdef analyze(self, df: pd.DataFrame) - WaitStats:wf df[wait_fixture].valueswq df[wait_queue].valuestotal wf wqratio float(wf.sum() / max(total.sum(), 1e-9))mean_fix float(wf.mean())p95 float(np.percentile(wf[wf 0], 95)) if np.any(wf 0) else 0.0# 拟合对数正态if np.any(wf 0):try:params stats.lognorm.fit(wf[wf 0], floc0)dist_name lognormexcept Exception:params (0, 0, 1)dist_name fallbackelse:params (0, 0, 1)dist_name nonereturn WaitStats(mean_fixmean_fix, p95_fixp95,ratio_fixratio, dist_namedist_name,dist_paramsparams)/detailsdetailssummary/summary瓶颈识别 (networkx numpy)。import numpy as npimport networkx as nxfrom dataclasses import dataclassdataclassclass BottleneckResult:util: dictbottleneck: strgraph: nx.Graphclass BottleneckDetector:建工单-夹具-机床二部图, 算夹具利用率。def __init__(self, util_threshold: float 0.85):self.th util_thresholddef detect(self, log, util: dict, orders) - BottleneckResult:G nx.Graph()for fid, u in util.items():G.add_node(fid, kindfixture, utilu)for mc in set(r[2] for r in log):G.add_node(mc, kindmc)for r in log:oid, fid, mc, st, en rG.add_edge(fid, mc, weightround(en-st, 1))G.add_node(oid, kindorder)G.add_edge(oid, fid)bn max(util, keyutil.get) if max(util.values()) self.th else nonereturn BottleneckResult(utilutil, bottleneckbn, graphG)/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:低等/中等/高等 三分类。def __init__(self, random_state: int 42):self.model_ Noneself.feat [family_code, arrive_density,compat_n, concurrent_comp]def _label(self, w: float) - str:if w 2:return 低等if w 6:return 中等return 高等def build_feature(self, df: pd.DataFrame,concurrent: np.ndarray) - pd.DataFrame:d df.copy()d[family_code] d[family].map({A:0,B:1,C:2})d[compat_n] d[fixtures].apply(len)d[arrive_density] 1.0 / (d[arrive_t].diff().abs().fillna(5)1)d[concurrent_comp] concurrentreturn ddef fit(self, df: pd.DataFrame, wait_arr: np.ndarray):d df.copy()d[family_code] d[family].map({A:0,B:1,C:2})d[compat_n] d[fixtures].apply(len)d[arrive_density] 1.0/(d[arrive_t].diff().abs().fillna(5)1)d[concurrent_comp] wait_arry np.array([self._label(w) for w in wait_arr])self.model_ RandomForestClassifier(n_estimators300, max_depth6, min_samples_leaf2,random_state42, n_jobs-1)self.model_.fit(d[self.feat].values, y)return selfdef cv(self, df: pd.DataFrame, wait_arr: np.ndarray) - Dict:d df.copy()d[family_code] d[family].map({A:0,B:1,C:2})d[compat_n] d[fixtures].apply(len)d[arrive_density] 1.0/(d[arrive_t].diff().abs().fillna(5)1)d[concurrent_comp] wait_arry np.array([self._label(w) for w in wait_arr])kf KFold(5, shuffleTrue, random_state42)sc cross_val_score(self.model_, d[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, concurrent: np.ndarray) - np.ndarray:d self.build_feature(df, concurrent)return self.model_.predict(d[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] FalseMC_COLOR {MC1:#3498DB,MC2:#2980B9,MC3:#1ABC9C}FIX_COLOR {F1:#E67E22,F2:#E74C3C}class FmsVisualizer:def __init__(self, results_dir: str results):self.results_dir Path(results_dir)self.results_dir.mkdir(exist_okTrue)def gantt(self, log, orders):fig, ax plt.subplots(figsize(13, 5))rows {MC1:0,MC2:1,MC3:2}for r in log:oid, fid, mc, st, en ry rows.get(mc, 0)ax.barh(y, en-st, leftst, height0.6,colorMC_COLOR.get(mc,#999), edgecolork, alpha0.85)ax.text(st(en-st)/2, y, oid, vacenter, hacenter,fontsize6, colorwhite)# 等待段for o in orders:if o.wait_fixture 0:ax.barh(rows.get(o.mc_id,0), o.wait_fixture, lefto.start_t-o.wait_fixture,height0.2, color#F1C40F, alpha0.9)ax.set_yticks(list(rows.values()))ax.set_yticklabels(list(rows.keys()))ax.set_xlabel(时间 (min), fontsize12)ax.set_title(机床-夹具加工甘特图(黄等夹具),fontsize13, fontweightbold)ax.grid(alpha0.25)plt.tight_layout()plt.savefig(self.results_dir/gantt_mc_fixture.png,dpi150, bbox_inchestight)plt.close()def wait_scatter(self, df, wait):fig, ax plt.subplots(figsize(10, 6))cmap {A:#3498DB,B:#E74C3C,C:#2ECC71}for fam in [A,B,C]:m df[family]famif m.any():ax.scatter(df.loc[m,arrive_t], wait[m], ccmap[fam],s40, labelf{fam}类, alpha0.8, edgecolorsk)ax.set_xlabel(到达时刻 (min), fontsize12)ax.set_ylabel(等夹具时长 (min), fontsize12)ax.set_title(工单等夹具散点(按零件族),fontsize13, fontweightbold)ax.legend(); ax.grid(alpha0.3)plt.tight_layout()plt.savefig(self.results_dir/wait_fixture_scatter.png,dpi150, bbox_inchestight)plt.close()def compete_heatmap(self, log, fix_list, t_max, step2):grid np.zeros((len(fix_list), int(t_max//step)1))for r in log:oid, fid, mc, st, en rfi fix_list.index(fid)a, b int(st//step), int(en//step)grid[fi, a:b] 1fig, ax plt.subplots(figsize(12, 3.5))im ax.imshow(grid, aspectauto, cmapYlOrRd,extent[0, t_max, len(fix_list), 0])ax.set_yticks(range(len(fix_list)))ax.set_yticklabels(fix_list)ax.set_xlabel(时间 (min), fontsize12)ax.set_title(夹具竞争热力图(亮占用密集),fontsize13, fontweightbold)plt.colorbar(im, axax, label并发占用数)plt.tight_layout()plt.savefig(self.results_dir/fixture_compete_heatmap.png,dpi150, bbox_inchestight)plt.close()def resource_network(self, G):fig, ax plt.subplots(figsize(11, 8))pos nx.spring_layout(G, seed42, k0.5)fix_nodes [n for n,d in G.nodes(dataTrue) if d.get(kind)fixture]mc_nodes [n for n,d in G.nodes(dataTrue) if d.get(kind)mc]order_nodes [n for n,d in G.nodes(dataTrue) if d.get(kind)order]nx.draw_networkx_nodes(G,pos,nodelistfix_nodes,node_color#E74C3C,node_size1800,alpha0.9,axax)nx.draw_networkx_nodes(G,pos,nodelistmc_nodes,node_color#3498DB,node_size1500,alpha0.9,axax)nx.draw_networkx_nodes(G,pos,nodelistorder_nodes,node_color#BDC3C7,node_size120,alpha0.6,axax)nx.draw_networkx_edges(G,pos,alpha0.25,axax)lbl {**{n:n for n in fix_nodesmc_nodes}}nx.draw_networkx_labels(G,pos,labelslbl,font_size10,font_colorwhite,利用AI解决实际问题如果你觉得这个工具好用欢迎关注长安牧笛
返回列表