云计算百科
云计算领域专业知识百科平台

python的先进制造技术工业场景模拟第二十四篇:读取不同硬质合金刀具加工记录,统计每种刀具可加工零件数量,评估刀具耐用度。

周三夜班,机加车间。

"这批件干到 180 件,刀又崩了,"刀具管理员老周把几根硬质合金刀摆桌上,"系统里记的是'换刀事件',可我想知道:同牌号的刀,干这个材料到底能撑多少件?是 150 件就到头,还是能到 220?现在全靠老师傅拍脑袋,换早了浪费刀,换晚了出废品。"

我打开 MES 的导出表。

"这表里有什么?"老周问。

"每把刀一个编号,加工零件时记开始件号、结束件号、加工材料、切削参数、是否崩刃,"我指着 CSV,"一把刀一行,或者按寿命事件一行。可系统不聚合'同型号刀平均能加工几件',也不告诉你是后刀面磨损先到,还是崩刃先到。"

"我就想问一句,"老周说,"WC-Co 细晶的、超细晶的、涂层 PVD 的,各统计一下平均寿命件数,画个分布,再告诉我哪批刀寿命离散大,哪批稳。最好能按加工材料分开看,钢件和不锈钢不能混着算。"

"所以你要的不是换刀记录表,是'同型号刀具耐用度统计 + 寿命分布 + 离散度评估 + 异常刀识别'?"

"对,"老周点头,"比如同是 PVD 涂层刀干 45 钢,平均 195 件,但有一把只干了 80 件就崩了,我想把它挑出来,看是不是那台机床主轴跳动了。"

"明白了,"我开 VS Code,"用 pandas 读刀具加工记录,numpy 算寿命件数,scipy 做分布检验和置信区间,matplotlib 画寿命箱线图+威布尔图,scikit-learn 聚类找异常刀,networkx 把'刀具型号→加工材料→机床'建关系网看瓶颈。数据自包含——合成 5 类硬质合金刀×多把,模拟正常磨损与少量早损,下载就能跑。"

我敲了段原型:

import pandas as pd

life = df.groupby("tool_id").apply(

    lambda g: g["end_part"].max() – g["start_part"].min() + 1)

 

"完整版用 OOP 封好,"我说,"一个类管刀具记录加载,一个类算单刀寿命,一个类做型号级统计,一个类做威布尔分析,一个类找异常刀,一个类画关系网,一个类出图。输出每种刀平均件数、寿命分布、早损清单、威布尔斜率。"

老周凑近看:"那我以后看报告:PVD 涂层干 45 钢,均值 195 件,威布尔斜率 8.2,说明寿命挺集中;有一把 82 件崩刃,标红,关联机床 M3,就知道去查 M3 主轴了。"

"对,"我接话,"刀具耐用度不是标称值,是统计出来的分布。数字孪生里刀是消耗品模型,得先有这张统计底表,才能做换刀预测。"

 

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

 

场景设定:多型号硬质合金刀具(不同牌号/涂层)加工不同材料零件,MES 记录每把刀的启用件号、停用件号、加工材料、机床号、停用原因(正常磨损/崩刃/崩角/达到寿命)。现场只记录事件,不提供同型号刀具寿命聚合统计,也不区分材料工况,导致换刀策略全凭经验。

 

现场原话(叙事化):

 

"我不是舍不得换刀,"老周说,"是没数。同是 PVD 涂层刀,有的干 210 件,有的 160 件就崩,系统里就是两条换刀记录,没人帮我算均值。有次我按 150 件换,结果后面刀都还能用,一年白扔几百根;另一次按 220 件换,出了批振纹废品。后来我把每把刀寿命手记在本子上,可本子跟 MES 对不上,也分不了材料。"

核心矛盾:"换刀事件流水"与"同型号刀具耐用度统计 + 寿命分布 + 威布尔评估 + 早损异常识别 + 工况归因"之间的断层。需要一个"硬质合金刀具耐用度统计与评估程序",用 

"pandas" 聚合、

"numpy" 算寿命、

"scipy" 做分布拟合与检验、

"matplotlib" 画箱线图/威布尔图、

"scikit-learn" 聚类异常刀、

"networkx" 建工况关系网。

 

二、痛点分析(映射到滨州职业学院《先进制造技术》课程模型)

 

《先进制造技术》模块 本篇痛点对应

数控加工与CAD/CAM技术:刀具材料与切削参数优化 硬质合金牌号/涂层耐用度量化统计

先进制造技术基础:制造过程资源消耗管理 刀具作为关键消耗资源寿命建模

FMS与先进生产管理:单元级刀具管理与换刀策略 按型号聚合寿命,支撑换刀阈值设定

智能制造与数字孪生:刀具数字孪生 寿命分布→可预测换刀模型底座

先进制造新模式:成本与质量协同(零缺陷) 早损识别避免批量振纹废品

 

一句话总结:我们需要构建一个"硬质合金刀具耐用度统计与评估程序",用 

"pandas" 按型号聚合,

"scipy" 做威布尔拟合,

"scikit-learn" 找早损异常,

"networkx" 关联工况,实现从"换刀流水账"到"可统计、可预测、可归因的刀具寿命模型"。

 

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

 

3.1 问题本质:把刀具想成"灯泡寿命"

 

把每把刀想成一个灯泡:

 

* 刀具型号 = 灯泡品牌(细晶 WC-Co / 超细晶 / PVD涂层…)

* 加工件数 = 亮了多少小时

* 停用原因 = 怎么灭的(自然暗下去=正常磨损,炸了=崩刃)

* 同型号多把刀 = 同一品牌一堆灯泡,有的亮久点有的短点

* 平均件数 = 品牌标称寿命

* 威布尔斜率 = 这批灯泡"齐不齐心":斜率大(>5)说明寿命很集中,斜率小说明忽长忽短

* 早损刀 = 同批里早早炸掉的那个灯泡,得拎出来看是不是装错机床了

* 工况关系网 = 哪种刀在哪种材料哪台机床上最费

 

工业应用:

 

* 

"pandas.groupby(["tool_grade","material"])" 聚合寿命件数

* 

"scipy.stats.weibull_min" 拟合寿命分布,取形状参数 k

* 

"scikit-learn" 用寿命+斜率偏差做早损聚类

* 

"networkx" 建 

"刀具型号—材料—机床" 三层图,找高消耗路径

 

3.2 业务逻辑 → 代码映射

 

定义刀具加工记录模型

   │

   ▼ ToolLogLoader (pandas)

导入 CSV:

  tool_id, tool_grade, coating, material, machine,

  start_part, end_part, stop_reason

  校验件号连续、去重

   │

   ▼ ToolLifeCalculator (numpy/pandas)

单刀寿命:

  life_parts = end_part – start_part + 1

  按刀聚合,标记早损

   │

   ▼ GradeStatistics (pandas/numpy)

型号级统计:

  按 (牌号,涂层,材料) 分组

  均值/中位数/标准差/P10/P90/置信区间

   │

   ▼ WeibullAnalyzer (scipy)

寿命分布:

  weibull_min.fit -> 形状k, 尺度η

  k>5 集中, k<2 离散大

  计算 B10 寿命(10%失效件数)

   │

   ▼ AnomalyDetector (scikit-learn)

早损识别:

  特征=[寿命, 偏离同组均值比例]

  IsolationForest / KMeans 找异常刀

   │

   ▼ ToolConditionGraph (networkx)

工况关系网:

  刀具型号-材料-机床 三层图

  边权=平均消耗件数倒数(越费权重越大)

   │

   ▼ ToolVisualizer (matplotlib)

可视化:

  1. 各型号寿命箱线图

  2. 威布尔概率图

  3. 早损刀散点标记

  4. 工况关系网图

  5. 寿命直方图+标称线

   │

   ▼ SyntheticToolGenerator (numpy)

合成数据:

  5类硬质合金刀,每类多把

  干45钢/不锈钢/铸铁,含正常磨损+少量早损

 

3.3 为什么用威布尔而不是正态分布?

 

* 问题:寿命数据有下限 0、右偏,正态会算出负寿命,且对早损不敏感。

* 处理策略:威布尔分布是可靠性工程标准模型,形状参数直接反映失效模式。

* 工程合理性:跟刀具厂寿命样本报告口径一致,B10 寿命可直接用于换刀策略。

 

3.4 分析前后对比

 

维度 MES换刀记录 本程序

同型号均值 手算 自动分组聚合

寿命分布 无 威布尔拟合 + B10

早损识别 靠肉眼 聚类自动标红

工况归因 无 networkx 关系网

换刀阈值 拍脑袋 P90 + B10 双参考

 

四、OOP 代码实现

 

4.1 项目结构

 

tool_life_eval/

├── tool_life_eval/

│ ├── __init__.py

│ ├── log_loader.py # 刀具记录加载

│ ├── life_calculator.py # 单刀寿命计算

│ ├── grade_statistics.py # 型号级统计

│ ├── weibull_analyzer.py # 威布尔分析

│ ├── anomaly_detector.py # 早损异常识别

│ ├── condition_graph.py # 工况关系网

│ ├── visualizer.py # 可视化

│ └── synthetic_data.py # 合成数据

├── tests/

│ ├── __init__.py

│ └── test_tool_life.py

├── results/

│ ├── box_life_by_grade.png # 箱线图

│ ├── weibull_probplot.png # 威布尔概率图

│ ├── anomaly_scatter.png # 早损散点

│ ├── condition_graph.png # 工况关系网

│ ├── life_hist.png # 寿命直方图

│ ├── grade_life_stats.csv # 型号统计表

│ ├── early_failure_list.csv # 早损清单

│ └── weibull_report.txt

└── run_tool_life.py

 

4.2 核心源码

 

<details>

 

<summary></summary>

 

"""刀具加工记录加载器"""

 

import pandas as pd

from pathlib import Path

from typing import Optional

 

 

class ToolLogLoader:

    """加载刀具加工流水记录"""

 

    STOP_REASONS = ("normal_wear", "chipping", "edge_break", "reach_life")

 

    def __init__(self, filepath: str = "tool_logs.csv",

                 encoding: str = "utf-8"):

        self.filepath = Path(filepath)

        self.encoding = encoding

        self._raw: Optional[pd.DataFrame] = None

 

    def load(self) -> pd.DataFrame:

        if not self.filepath.exists():

            raise FileNotFoundError(f"文件不存在: {self.filepath}")

        self._raw = pd.read_csv(self.filepath, encoding=self.encoding)

 

        rename = {}

        for tgt, al in {

            "tool_id": ["tool_id", "刀号", "id"],

            "tool_grade": ["tool_grade", "牌号", "grade"],

            "coating": ["coating", "涂层", "coat"],

            "material": ["material", "加工材料", "mat"],

            "machine": ["machine", "机床", "mc"],

            "start_part": ["start_part", "起始件", "sp"],

            "end_part": ["end_part", "结束件", "ep"],

            "stop_reason": ["stop_reason", "停用原因", "reason"],

        }.items():

            if tgt not in self._raw.columns:

                for a in al:

                    if a in self._raw.columns:

                        rename[a] = tgt

                        break

        self._raw = self._raw.rename(columns=rename)

 

        req = ["tool_id", "tool_grade", "coating", "material",

               "machine", "start_part", "end_part", "stop_reason"]

        miss = [c for c in req if c not in self._raw.columns]

        if miss:

            raise ValueError(f"缺少必要列: {miss}")

 

        for c in ["start_part", "end_part"]:

            self._raw[c] = pd.to_numeric(self._raw[c], errors="coerce")

        self._raw = self._raw.dropna(subset=["start_part", "end_part"])

        self._raw = self._raw[(self._raw["end_part"] >= self._raw["start_part"])]

        self._raw = self._raw.sort_values(["tool_grade", "tool_id"]).reset_index(drop=True)

        return self._raw.copy()

 

</details>

 

<details>

 

<summary></summary>

 

"""单刀寿命计算"""

 

import numpy as np

import pandas as pd

from typing import Optional

 

 

class ToolLifeCalculator:

    """计算每把刀可加工零件数"""

 

    def __init__(self):

        pass

 

    def per_tool(self, df: pd.DataFrame) -> pd.DataFrame:

        rows = []

        for tool_id, g in df.groupby("tool_id"):

            head = g.iloc[0]

            # 支持一把刀分段加工时取全局起止

            sp = int(g["start_part"].min())

            ep = int(g["end_part"].max())

            life = ep – sp + 1

            rows.append({

                "tool_id": tool_id,

                "tool_grade": head["tool_grade"],

                "coating": head["coating"],

                "material": head["material"],

                "machine": head["machine"],

                "start_part": sp,

                "end_part": ep,

                "life_parts": int(life),

                "stop_reason": g["stop_reason"].mode().iloc[0],

            })

        out = pd.DataFrame(rows)

        return out.sort_values("tool_id").reset_index(drop=True)

 

    def tag_early_failure(self, life_df: pd.DataFrame,

                          group_keys=("tool_grade", "coating", "material"),

                          k_sigma: float = 2.0) -> pd.DataFrame:

        """按同工况组均值-2σ标记早损"""

        df = life_df.copy()

        stats = df.groupby(list(group_keys))["life_parts"].agg(["mean", "std"]).reset_index()

        stats["std"] = stats["std"].fillna(0.0)

        df = df.merge(stats, on=list(group_keys), how="left")

        df["is_early"] = (df["life_parts"] < df["mean"] – k_sigma * df["std"]) | \\

                         (df["stop_reason"].isin(["chipping", "edge_break"]))

        df["dev_from_mean"] = (df["life_parts"] – df["mean"]).round(2)

        for c in ["mean", "std"]:

            df[c] = df[c].round(2)

        return df

 

</details>

 

<details>

 

<summary></summary>

 

"""型号级耐用度统计"""

 

import numpy as np

import pandas as pd

from scipy import stats

from typing import Optional

 

 

class GradeStatistics:

    """按 (牌号,涂层,材料) 聚合统计"""

 

    def __init__(self, conf: float = 0.95):

        self.conf = conf

 

    def summarize(self, life_df: pd.DataFrame) -> pd.DataFrame:

        keys = ["tool_grade", "coating", "material"]

        g = life_df.groupby(keys)["life_parts"]

 

        n = g.count()

        mean = g.mean()

        med = g.median()

        std = g.std(ddof=1).fillna(0.0)

        p10 = g.quantile(0.10)

        p90 = g.quantile(0.90)

 

        # 均值置信区间

        rows = []

        for key_tuple in mean.index:

            sub = life_df[(life_df["tool_grade"] == key_tuple[0]) &

                          (life_df["coating"] == key_tuple[1]) &

                          (life_df["material"] == key_tuple[2])]["life_parts"].values

            if len(sub) > 1:

                ci = stats.t.interval(self.conf, len(sub) – 1,

                                      loc=sub.mean(), scale=stats.sem(sub))

                ci_lo, ci_hi = round(ci[0], 1), round(ci[1], 1)

            else:

                ci_lo = ci_hi = round(float(sub.mean()), 1)

 

            rows.append({

                "tool_grade": key_tuple[0],

                "coating": key_tuple[1],

                "material": key_tuple[2],

                "n_tools": int(n[key_tuple]),

                "mean_life": round(mean[key_tuple], 1),

                "median_life": round(med[key_tuple], 1),

                "std_life": round(std[key_tuple], 1),

                "p10_life": round(p10[key_tuple], 1),

                "p90_life": round(p90[key_tuple], 1),

                "ci95_lo": ci_lo,

                "ci95_hi": ci_hi,

                "early_fail_cnt": int(((life_df[

                    (life_df["tool_grade"] == key_tuple[0]) &

                    (life_df["coating"] == key_tuple[1]) &

                    (life_df["material"] == key_tuple[2])

                ]["is_early"] == True).sum()),

            })

        return pd.DataFrame(rows).sort_values("mean_life", ascending=False).reset_index(drop=True)

 

</details>

 

<details>

 

<summary></summary>

 

"""威布尔寿命分析 (scipy)"""

 

import numpy as np

import pandas as pd

from scipy import stats

from typing import Dict, Optional

 

 

class WeibullAnalyzer:

    """对每组寿命做威布尔最小分布拟合"""

 

    def __init__(self):

        pass

 

    def fit_group(self, life_values: np.ndarray) -> Dict:

        x = np.asarray(life_values, dtype=float)

        x = x[x > 0]

        if len(x) < 3:

            return {"k": np.nan, "eta": np.nan, "b10": np.nan}

        # weibull_min(c, loc, scale), c即形状k

        c, loc, scale = stats.weibull_min.fit(x, floc=0)

        k = float(c)

        eta = float(scale)

        b10 = float(eta * np.log(1 / 0.9) ** (1 / k)) if k > 0 else np.nan

        return {"k": round(k, 3), "eta": round(eta, 1), "b10": round(b10, 1)}

 

    def fit_all_groups(self, life_df: pd.DataFrame) -> pd.DataFrame:

        keys = ["tool_grade", "coating", "material"]

        rows = []

        for key_tuple, g in life_df.groupby(keys):

            res = self.fit_group(g["life_parts"].values)

            rows.append({

                "tool_grade": key_tuple[0],

                "coating": key_tuple[1],

                "material": key_tuple[2],

                "weibull_k": res["k"],

                "weibull_eta": res["eta"],

                "b10_life": res["b10"],

                "dispersion": "集中" if (res["k"] and res["k"] > 5)

                              else ("离散" if (res["k"] and res["k"] < 2) else "中等"),

            })

        return pd.DataFrame(rows).sort_values("weibull_k", ascending=False).reset_index(drop=True)

 

    def probplot_data(self, life_values: np.ndarray):

        """返回威布尔概率图坐标"""

        x = np.sort(np.asarray(life_values, dtype=float))

        x = x[x > 0]

        n = len(x)

        if n < 2:

            return x, np.array([])

        f = (np.arange(1, n + 1) – 0.3) / (n + 0.4)

        y = np.log(-np.log(1 – f))

        return x, y

 

</details>

 

<details>

 

<summary></summary>

 

"""早损异常刀识别 (scikit-learn)"""

 

import numpy as np

import pandas as pd

from sklearn.cluster import KMeans

from sklearn.preprocessing import StandardScaler

from typing import Optional

 

 

class AnomalyDetector:

    """基于同工况偏离度聚类找早损刀"""

 

    def __init__(self, n_clusters: int = 2, random_state: int = 42):

        self.n_clusters = n_clusters

        self.random_state = random_state

        self.scaler = StandardScaler()

 

    def detect(self, tagged_df: pd.DataFrame) -> pd.DataFrame:

        df = tagged_df.copy()

        # 特征:寿命 + 偏离均值比例

        df["dev_ratio"] = df["dev_from_mean"] / (df["mean"] + 1e-9)

        X = df[["life_parts", "dev_ratio"]].values

        Xs = self.scaler.fit_transform(X)

 

        k = min(self.n_clusters, len(df))

        km = KMeans(n_clusters=k, random_state=self.random_state, n_init=10)

        df["cluster"] = km.fit_predict(Xs)

 

        # 寿命低的簇标记为异常簇

        cmean = df.groupby("cluster")["life_parts"].mean()

        low_cluster = cmean.idxmin()

        df["cluster_is_early"] = df["cluster"] == low_cluster

 

        # 综合标记:规则早损 OR 聚类异常簇

        df["final_early_flag"] = df["is_early"] | df["cluster_is_early"]

        return df.sort_values("life_parts").reset_index(drop=True)

 

    def summary(self, df: pd.DataFrame) -> pd.DataFrame:

        sub = df[df["final_early_flag"]]

        return sub[["tool_id", "tool_grade", "coating", "material",

                    "machine", "life_parts", "mean", "stop_reason",

                    "dev_from_mean"]].copy()

 

</details>

 

<details>

 

<summary></summary>

 

"""工况关系网 (networkx)"""

 

import networkx as nx

import pandas as pd

from typing import Optional

 

 

class ToolConditionGraph:

    """刀具型号-材料-机床 三层关系网"""

 

    def __init__(self):

        self.G = nx.Graph()

 

    def build(self, life_df: pd.DataFrame, stats_df: pd.DataFrame) -> nx.Graph:

        self.G.clear()

        # 节点

        for _, r in stats_df.iterrows():

            gid = f"{r['tool_grade']}|{r['coating']}"

            self.G.add_node(gid, ntype="grade",

                            mean_life=r["mean_life"])

            self.G.add_node(r["material"], ntype="material")

        for m in life_df["machine"].unique():

            self.G.add_node(m, ntype="machine")

 

        # 边:grade-material, 权=1/均值寿命(越费越大)

        for _, r in stats_df.iterrows():

            gid = f"{r['tool_grade']}|{r['coating']}"

            w = round(1.0 / (r["mean_life"] + 1e-9), 5)

            self.G.add_edge(gid, r["material"], weight=w,

                            mean_life=r["mean_life"])

 

        # 机床级平均寿命

        mc = life_df.groupby(["machine", "tool_grade", "coating"])["life_parts"].mean().reset_index()

        for _, r in mc.iterrows():

            gid = f"{r['tool_grade']}|{r['coating']}"

            self.G.add_edge(gid, r["machine"],

                            weight=round(1.0 / (r["life_parts"] + 1e-9), 5),

                            mean_life=round(r["life_parts"], 1))

        return self.G

 

    def bottleneck_paths(self) -> pd.DataFrame:

        """找平均寿命最低的 grade-material-machine 组合"""

        rows = []

        for u, v, d in self.G.edges(data=True):

            if "mean_life" in d:

                rows.append({"node_a": u, "node_b": v,

                             "mean_life": d["mean_life"],

                             "weight": d["weight"]})

        df = pd.DataFrame(rows)

        if df.empty:

            return df

        return df.sort_values("mean_life").reset_index(drop=True)

 

</details>

 

<details>

 

<summary></summary>

 

"""可视化"""

 

import numpy as np

import pandas as pd

import matplotlib.pyplot as plt

import networkx as nx

from pathlib import Path

 

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

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

 

 

class ToolVisualizer:

    def __init__(self, results_dir: str = "results"):

        self.results_dir = Path(results_dir)

        self.results_dir.mkdir(exist_ok=True)

 

    def box_life(self, life_df, stats_df):

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

        labels = stats_df.apply(lambda r: f"{r['tool_grade']}\\n{r['coating']}\\n{r['material']}", axis=1)

        data = []

        for _, r in stats_df.iterrows():

            sub = life_df[(life_df["tool_grade"] == r["tool_grade"]) &

                          (life_df["coating"] == r["coating"]) &

                          (life_df["material"] == r["material"])]["life_parts"].values

            data.append(sub)

        ax.boxplot(data, labels=labels, showmeans=True)

        ax.set_ylabel("可加工件数", fontsize=12)

        ax.set_title("各型号刀具寿命箱线图", fontsize=14, fontweight="bold")

        plt.xticks(rotation=30, ha="right", fontsize=8)

        ax.grid(axis="y", alpha=0.3)

        plt.tight_layout()

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

                     dpi=150, bbox_inches="tight")

        plt.close()

 

    def weibull_probplot(self, life_df, weibull_df):

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

        for _, w in weibull_df.iterrows():

            sub = life_df[(life_df["tool_grade"] == w["tool_grade"]) &

                          (life_df["coating"] == w["coating"]) &

                          (life_df["material"] == w["material"])]["life_parts"].values

            x, y = WeibullProbHelper().probplot_data(sub)

            if len(x) < 2:

                continue

            ax.plot(x, y, ".-", label=f"{w['tool_grade']}|{w['coating']}|{w['material']} k={w['weibull_k']}")

        ax.set_xscale("log")

        ax.set_xlabel("寿命件数 (log)", fontsize=12)

        ax.set_ylabel("ln(-ln(1-F))", fontsize=12)

        ax.set_title("威布尔概率图", fontsize=14, fontweight="bold")

        ax.legend(fontsize=7)

        ax.grid(alpha=0.3)

        plt.tight_layout()

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

                     dpi=150, bbox_inches="tight")

        plt.close()

 

    def anomaly_scatter(self, det_df):

        fig, ax = plt.subplots(figsize=(12, 6))

        normal = det_df[~det_df["final_early_flag"]]

        early = det_df[det_df["final_early_flag"]]

        ax.scatter(normal["mean"], normal["life_parts"], c="#3498DB",

                   label="正常刀", s=30, alpha=0.7)

        ax.scatter(early["mean"], early["life_parts"], c="#E74C3C",

                   label="早损刀", s=60, edgecolor="k")

        # 参考线 y=mean

        allx = det_df["mean"].values

        ax.plot(allx, allx, "g–", alpha=0.5, label="同组均值线")

        for _, r in early.iterrows():

            ax.annotate(r["tool_id"], (r["mean"], r["life_parts"]),

                        fontsize=7, color="red")

        ax.set_xlabel("同工况组平均寿命(件)", fontsize=12)

        ax.set_ylabel("本刀寿命(件)", fontsize=12)

        ax.set_title("早损刀具识别散点图", fontsize=14, fontweight="bold")

        ax.legend()

        ax.grid(alpha=0.3)

        plt.tight_layout()

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

                     dpi=150, bbox_inches="tight")

        plt.close()

 

    def condition_graph(self, G):

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

        pos = nx.spring_layout(G, seed=42, k=0.6)

        ncolors = []

        for n, d in G.nodes(data=True):

            t = d.get("ntype", "grade")

            ncolors.append({"grade": "#E74C3C", "material": "#2ECC71",

                            "machine": "#3498DB"}.get(t, "#999"))

        sizes = [300 + (G.nodes[n].get("mean_life", 50) * 3 if G.nodes[n].get("ntype")=="grade" else 300) for n in G.nodes()]

        nx.draw_networkx_nodes(G, pos, node_color=ncolors,

                               node_size=sizes, ax=ax, alpha=0.9)

        edges = [(u, v) for u, v, d in G.edges(data=True)]

        ws = [G.edges[u, v]["weight"] * 8000 for u, v in edges]

        nx.draw_networkx_edg

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

赞(0)
未经允许不得转载:网硕互联帮助中心 » python的先进制造技术工业场景模拟第二十四篇:读取不同硬质合金刀具加工记录,统计每种刀具可加工零件数量,评估刀具耐用度。
分享到: 更多 (0)

评论 抢沙发

评论前必须登录!