skills: 加 pymatgen / stats_ml / plot_pub(建材院无机材料场景)
服务中国建材院无机非金属材料 R&D。从 K-Dense AI scientific-agent-skills 仓库挑 3 个 ★★★ skill fork,不走 npx 一键装(138 个 description 入 prompt 噪声 + 误触发)。 - pymatgen: 晶体结构 / XRD / 对称性 / 相图 / Materials Project。helper materials.py 内 CEMENT_PHASES 收 66 条中英文相名映射(水泥熟料 / 水化产物 / 陶瓷耐火 / 玻璃晶相 / 常见矿物)、lookup_phase 大小写不敏感、mp_rester 从 env MP_API_KEY 拿 key - stats_ml: 纯指南 skill,场景导航 sklearn / statsmodels / PyMC 三选一, 5 个工作流(配方-性能回归 / DoE 二阶响应面 / 显著性分析 / 贝叶斯小样本 / DBSCAN 异常配方)+ 16 条反模式 - plot_pub: 出版级 matplotlib,helper style.py 内 apply_pub_style() 一键 设置中文字体跨平台 fallback (SimHei / YaHei / WenQuanYi) + viridis + dpi + PDF Type 42 requirements.txt 加 pymatgen / mp-api / scikit-learn / statsmodels (pymc 注释,装包重按需开)。RUN.md env 段加 MP_API_KEY(可选)。 SCIENTIFIC_SKILLS.md 根目录沉淀整体评估,后续 materials_db 落地参考。 scripts/smoke_scientific_skills.py 三 skill 链路通路验证脚本。 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
1d6a9898e4
commit
52f201404c
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
> 配合 `DESIGN.md`。本文件只记 phase 状态、决策偏差、文件量、下一步。每条 1-2 句:做了啥 + 关键判断;细节查 `git log` / `git diff` / `DESIGN §7.9`。
|
||||
|
||||
最后更新:2026-05-27(ppt skill 歧义反问 + general_v1 加"产物形式歧义先问"通用原则)
|
||||
最后更新:2026-05-28(新增 pymatgen / stats_ml / plot_pub 三个科学计算 skill,服务建材院无机材料 R&D 场景)
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -23,6 +23,7 @@
|
|||
|
||||
### 2026-05-28
|
||||
|
||||
- **新增 3 个科学计算 skill(pymatgen / stats_ml / plot_pub),服务建材院无机非金属材料 R&D**:`SCIENTIFIC_SKILLS.md` 评估完 K-Dense/scientific-agent-skills 仓库后落地选 4 个 ★★★ 中前 3 个动手(materials_db 后置,USPTO 部分留并入 `skills/patent`)。命名取**工具名直接**(`pymatgen` / `plot_pub`)+ **业务前缀**(`stats_ml` 因合三库需要场景导航),贴合现有 skill 命名风格(coding/ppt/research/...)。① **`skills/pymatgen/`**:`SKILL.md`(无机相中文→化学式映射表说明 / XRD 比对 / 对称性 / 相图 / VASP 输入文件,八条反模式)+ `materials.py`(`CEMENT_PHASES` dict 覆盖水泥熟料 / 水化产物 / 石膏 / 碳酸盐 / 陶瓷耐火 / 玻璃晶相 / 常见矿物共 50+ 条目,中英文 / 简写多 key 指同一化学式;`lookup_phase()` 大小写不敏感查找;`mp_rester()` context manager 自动从 env 拿 `MP_API_KEY`,缺则 RuntimeError 带申请链接;mp_api 局部 import 避免装包前 import 即崩)。② **`skills/stats_ml/`**:`SKILL.md` 纯指南(场景导航表选 sklearn / statsmodels / PyMC、5 个工作流示例 A-E 含配方-性能回归 / DoE 二阶响应面 / 显著性分析 / 贝叶斯小样本 / DBSCAN 异常配方、16 条反模式分库列示)+ 无 helper(三库 API 直接用)。③ **`skills/plot_pub/`**:`SKILL.md`(XRD 多相叠图 / TG-DSC 双 Y 轴 / 强度发展曲线 / 多 panel 论文 figure 4 个工作流 + 中文字体说明 + 10 条反模式)+ `style.py`(`apply_pub_style()` 一键设置:中文字体跨平台 fallback SimHei→YaHei→WenQuanYi→DejaVu / dpi 150 屏幕 300 保存 / viridis 默 cmap / 刻度朝内 / legend 无框 / PDF Type 42 字体合规期刊 / `font.size` `linewidth` 等可参数化;`_find_chinese_font()` 在 `font_manager.fontManager.ttflist` 查实装字体而非靠 try-load)。**关键决策**:(a) **不一键装 138 个 skill** —— 上下文噪声 + 误触发(用户"分析一下"模型可能跳 Scanpy),挑 4 个 ★★★ fork 单装;(b) **PyMC 装包延后** —— 带 pytensor 装 5min+ 体积大,真要做贝叶斯再装;requirements.txt 注释掉以 `# pymc>=5.10.0` 形式留接口;(c) **MP_API_KEY 走 .env** —— 跟 DEEPSEEK_API_KEY / ARK_API_KEY 同范式,litellm 不读但 `os.environ.get` 拿到;(d) **化学式映射表对中文 / 简写 / 英文学名同等待遇** —— 用户报相名习惯混杂(C3S / 硅酸三钙 / alite 都常见),多 key 同 value 比强迫归一化体验好;(e) **不写示例数据/单元测试**:开发期 LLM 工作流场景多变,跑了 SKILL.md 工作流验证而非脚本测试 —— skill 是 prompt 不是代码模块。requirements.txt 加 pymatgen / mp-api / scikit-learn / statsmodels(PyMC 注释)。`RUN.md` env 段加 MP_API_KEY 说明(可选 + 申请链接 + 未设抛 RuntimeError)。`DESIGN.md` 不动(纯 skill 加,无架构变化);`SCIENTIFIC_SKILLS.md`(根目录调研笔记)已沉淀整体评估,后续 materials_db 落地参考。装包未执行 —— 等用户跑 `.venv/Scripts/pip install pymatgen mp-api scikit-learn statsmodels` 装上才能验证 import 路径生效。否决:(a) 三个 skill 合并成一个 `science` skill —— 触发语义糊,LLM 难判,各做各的更清;(b) `materials_pymatgen` 这种业务前缀全打 —— pymatgen 本身就是材料库,前缀冗余;(c) helper 过度封装(写 `simulate_xrd(formula)` 全自动)—— 隐藏 pymatgen 真实 API,LLM 学不到本来好用的上游能力,反模式段在 SKILL.md 里讲清更轻;(d) plot_pub 内 `apply_pub_style()` 失败抛错 —— 中文字体没装也应该继续画(图能看就行,只是中文方块),warn 比 raise 友好。
|
||||
- **DESIGN §7.5 增"image 体积 / 多 user 资源 / 后续加包策略"决策段**:dogfood 推进到加 npm + chromium + mermaid-cli 后,sandbox image 1.5G+,后续 domain 包(rdkit / pymatgen / ASE / pandoc-tex 等)还会进一步推大,把"image 大 = 资源占用大 = 多 user 容器爆炸"这条直觉关联的事实链拆开沉淀,免得未来花减肥功夫减错地方。三条认知校准:① **image 大 ≠ 运行时吃更多 RAM**(空载 `tini → sleep infinity` RSS 个位数 MB,layer 共享让磁盘乘数 = 1,真吃 RAM 的是 active exec 行为而非 image 字节);② **多 user 同时在线瓶颈在并发 exec 不在 idle 容器数**(杠杆全在 `docker run --memory --cpus --pids-limit` + 同 user 并发 semaphore + 整机 active user cap + idle 回收,减 image 体积对这条曲线无效);③ **新增依赖采用"base 收敛 + per-user 持久化 venv + 使用频次沉淀"**:base 只放高频共用轻量包(`requirements.txt` 当前形状),长尾 domain 包模型用 `pip install --target=/workspace/.venv/site-packages` 装到 per-user 持久化路径(随 user_root bind mount,idle 回收不丢);加 audit 统计哪些包累计被 >30% user 装过 ≥ 3 次 → 下次 build 合并进 `requirements.txt`,base 跟着真实使用模式收敛而非拍脑袋。否决:(a) 全塞 base —— 重包(torch / texlive)+ 长尾包打死磁盘 + 强迫所有 user 陪绑;(b) 运行时临时装(不持久化)—— 容器 idle 回收即丢冷启重装,高频复用差;(c) 多 image 按场景分 —— per-user 容器模型下 user 不知道选哪个,切 skill 还得换 image 心智不通;(d) per-user venv 用 named volume / 共享 image cache —— 包 install 脚本是任意代码,跨 user 共享 venv 破坏跨 user 隔离边界;(e) 依赖 pip cache 解决问题 —— pip cache 只省网络不省落盘,容器回收照样丢。落地点排进 Stage C Step 6 Executor 实施:cgroup limits / 并发 semaphore / idle 回收 / per-user venv mount 一并进 `DockerExecutor`(audit 沉淀机制延后,需要 dogfood 安装日志积累再开)。`RUN.md` 不动(当前无 CLI / env 变化,真做 venv mount 时再加);`DESIGN.md` §7.5 升级触发信号表后新增该段。
|
||||
|
||||
### 2026-05-27
|
||||
|
|
|
|||
3
RUN.md
3
RUN.md
|
|
@ -21,6 +21,9 @@
|
|||
DOCUMENT_SEARCH_API_KEY=...
|
||||
# 可选:覆盖默认 base_url(默认 https://ai.ctc-zc.com:8100/api)
|
||||
# DOCUMENT_SEARCH_URL=https://ai.ctc-zc.com:8100/api
|
||||
# pymatgen skill 的 Materials Project 接入:可选。设了 pymatgen.materials.mp_rester() 才能用,
|
||||
# 未设调用即抛 RuntimeError。申请 https://materialsproject.org/api(免费)
|
||||
MP_API_KEY=...
|
||||
ZCBOT_DB_URL=postgresql://user:pass@host:5432/zcbot
|
||||
# main.py web 必填(probe/db/user 不验)
|
||||
PLATFORM_KEY=<≥16 字符随机串,platform 机器对机器入口校验>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,189 @@
|
|||
# 科学 Skill 集成评估(scientific-agent-skills → zcbot)
|
||||
|
||||
来源:https://github.com/K-Dense-AI/scientific-agent-skills(MIT,K-Dense AI,138 个 skill)
|
||||
|
||||
适用对象:zcbot,服务于中国建筑材料科学研究总院 —— 无机非金属材料 R&D(水泥/混凝土/玻璃/陶瓷/耐火/新型建材)。
|
||||
|
||||
## 1. 总体结论
|
||||
|
||||
- **不整包装**(`npx skills add`),138 个 description 全进 prompt → 上下文噪声爆炸 + 误触发(用户说"分析一下"模型可能跳去 Scanpy)。
|
||||
- **挑 4 个核心新能力 fork 进来**,适配 zcbot 现有 skill 格式(`SKILL.md` + Python helper + `run_python` 调用)。
|
||||
- **现有 6 个 skill 不替换,抽对方的 best practice 增强**。
|
||||
|
||||
## 2. 现状与目标格式
|
||||
|
||||
zcbot 已有 skill(`D:/projects/zcbot/skills/`):
|
||||
`coding / documents / imagegen / patent / ppt / proposal / research / review / videogen`
|
||||
|
||||
每个 skill = `SKILL.md`(frontmatter + 何时用 + 反模式 + 工作流)+ 配套 Python(走 `run_python` 注入的 PYTHONPATH 直接 import)。
|
||||
|
||||
scientific-agent-skills 的 skill 格式语义基本一致 —— **可以单个 fork,不要批量装**。
|
||||
|
||||
## 3. 推荐移植的 4 个新能力
|
||||
|
||||
### 3.1 Pymatgen ★★★
|
||||
|
||||
无机材料计算核心库。
|
||||
|
||||
**对你们的价值面**
|
||||
- 晶体结构 I/O(CIF/POSCAR)—— 水泥熟料相(C3S/C2S/C3A/C4AF)、玻璃晶化产物、陶瓷相、耐火砖矿物相
|
||||
- `XRDCalculator` —— 给定结构正向算 XRD pattern,跟实测对比
|
||||
- `PhaseDiagram` —— 凝胶/水化产物的热力学稳定性
|
||||
- `SpacegroupAnalyzer` —— 物相对称性
|
||||
- `MPRester` —— Materials Project 直连查无机化合物
|
||||
- 转换工具:VASP / Gaussian / Quantum ESPRESSO 输入输出文件
|
||||
|
||||
**落地动作**
|
||||
```bash
|
||||
.venv/Scripts/pip install pymatgen mp-api
|
||||
```
|
||||
新建 `skills/materials_pymatgen/SKILL.md`,在仓库原版基础上加两段:
|
||||
- **中文相名 → 标准化学式映射表**(给 LLM 看):
|
||||
- C3S → Ca3SiO5,C2S → Ca2SiO4,C3A → Ca3Al2O6,C4AF → Ca4Al2Fe2O10
|
||||
- 钙矾石 → Ca6Al2(SO4)3(OH)12·26H2O
|
||||
- 莫来石 → Al6Si2O13,堇青石 → Mg2Al4Si5O18
|
||||
- **Materials Project API key 配置走 zcbot env** —— 用户得去 https://materialsproject.org/api 申请,写进 `RUN.md` 一次性初始化段
|
||||
|
||||
**坑**
|
||||
- `MPRester` 必须 context manager(`with MPRester() as mpr:`),否则连接泄漏 —— 抄进反模式段
|
||||
- `from_file()` 优先,不要自己 parse CIF
|
||||
|
||||
### 3.2 stats_ml(scikit-learn + statsmodels + PyMC) ★★★
|
||||
|
||||
合并成一个 skill,按场景导航。
|
||||
|
||||
**对你们的价值面**
|
||||
- 配方-性能建模:掺合料比例 → 28 天抗压强度回归
|
||||
- DoE 响应面拟合
|
||||
- 配方聚类 / 异常实验检测
|
||||
- 工艺参数 → 性能的特征重要性
|
||||
|
||||
**落地动作**
|
||||
|
||||
`skills/stats_ml/SKILL.md` 入口先做场景导航:
|
||||
| 你要做 | 用 |
|
||||
|---|---|
|
||||
| 要 p-value、置信区间、假设检验 | statsmodels(OLS / ANOVA) |
|
||||
| 要预测精度高、不在乎模型可解释性 | sklearn(RF / GBDT) |
|
||||
| 样本小(< 30)且要不确定度估计 | PyMC(贝叶斯) |
|
||||
| 要可解释的线性关系 | statsmodels OLS / sklearn LinearRegression |
|
||||
|
||||
**抄仓库的反模式段**(高频踩坑):
|
||||
1. 别在 pipeline 外预处理 → 交叉验证会数据泄漏
|
||||
2. scaler 别 fit 在 test 上
|
||||
3. 分类任务记得 `StratifiedKFold`
|
||||
4. 树模型不用 scale 特征
|
||||
5. 收敛 warning 别忽略,要么加 `max_iter` 要么 scale
|
||||
|
||||
依赖:你们 requirements.txt 多半已有 sklearn,确认下 statsmodels + pymc 装没装。
|
||||
|
||||
### 3.3 matplotlib(出版级) ★★★
|
||||
|
||||
**对你们的价值面**
|
||||
- XRD pattern(底图 + 标峰)
|
||||
- TG-DSC 双 Y 轴曲线
|
||||
- 应力-应变曲线多组样本对比
|
||||
- 多 panel 出版图(Cement and Concrete Research、JACerS 风格)
|
||||
|
||||
**落地动作**
|
||||
|
||||
新建 `skills/plot_pub/SKILL.md`,关键内容(原仓库的反模式 + 你们的中文场景补丁):
|
||||
|
||||
```python
|
||||
# zcbot 出版级绘图规范
|
||||
|
||||
# 1. OO 接口(必须),不用 plt.xxx 隐式
|
||||
fig, ax = plt.subplots(figsize=(6, 4), dpi=150)
|
||||
ax.plot(...)
|
||||
|
||||
# 2. 中文字体(Windows 必配,否则方块)
|
||||
plt.rcParams['font.sans-serif'] = ['SimHei', 'Microsoft YaHei']
|
||||
plt.rcParams['axes.unicode_minus'] = False
|
||||
|
||||
# 3. colormap 用 viridis / cividis,不用 jet(色觉障碍不友好,审稿人会喷)
|
||||
|
||||
# 4. 画完必须 close,run_python 多任务跑容易堆积
|
||||
fig.savefig(out_path, bbox_inches='tight')
|
||||
plt.close(fig)
|
||||
|
||||
# 5. 矢量优先:SVG / PDF 给论文,PNG 给 PPT(dpi >= 300)
|
||||
```
|
||||
|
||||
仓库还提到一个 `scientific-visualization` skill 专门做期刊样式多面板,这个可以一并扒进来作为 `plot_pub` 的进阶段。
|
||||
|
||||
### 3.4 materials_db(COD + NIST + USPTO 三合一) ★★
|
||||
|
||||
**原仓库 `database-lookup` 78 个库,只 5 个对你们直接有用,不要整体抄,做精简子集**:
|
||||
|
||||
| DB | 用途 |
|
||||
|---|---|
|
||||
| **Materials Project** | 已被 Pymatgen 的 MPRester 覆盖,不重复 |
|
||||
| **COD**(Crystallography Open Database) | 免费晶体结构库,补充 MP 没收的实验结构 |
|
||||
| **NIST** | 标准参考数据,材料热物性、相平衡 |
|
||||
| **USPTO** | 美国专利全文检索 → 并入 `skills/patent` |
|
||||
| **PubChem / ChEMBL** | 只在做外加剂(减水剂、缓凝剂)这类有机分子时用 → 不常用,可后置 |
|
||||
|
||||
**落地动作**
|
||||
- `skills/patent` 加一个 `query_uspto(keyword, ...)` helper(USPTO Open Data Portal API)
|
||||
- 新建 `skills/materials_db/SKILL.md`,封 COD + NIST 两个查询函数
|
||||
- Materials Project 让 Pymatgen 走,本 skill 不重复
|
||||
|
||||
**坑**
|
||||
- USPTO 要 API key(免费),申请走 https://developer.uspto.gov/
|
||||
- COD 是 RESTful,免 key,但限速,加 sleep
|
||||
- NIST 各子库 API 不统一(WebBook / Materials Data Repository / ICSD 镜像),要分别封装
|
||||
|
||||
## 4. 现有 6 个 skill 的增强参考(不替换)
|
||||
|
||||
把仓库对应 skill 的 SKILL.md 拿来读,把"何时用 / 反模式 / 标准工作流"段落里你们没写的合理项抽进现有 SKILL.md。**不要整体替换**,你们的 paper_server / proposal 模板 / patent 流程都是定制化的。
|
||||
|
||||
| 仓库 skill | 对应你们 | 可抄的部分 |
|
||||
|---|---|---|
|
||||
| Literature Review / Paper Lookup | `skills/research` | 多库 fallback 策略、引文格式标准化 |
|
||||
| Scientific Writing | `skills/proposal` | 章节结构、claim-evidence 写作规范 |
|
||||
| Scientific Slides / LaTeX Posters | `skills/ppt` | 多 panel 布局、color palette 规范 |
|
||||
| Document Skills(PDF/DOCX/XLSX/PPTX) | `skills/documents` | 表格读取、复杂 PDF 抽取 |
|
||||
| Peer Review | `skills/review` | 审稿 checklist |
|
||||
| Scientific Schematics | (新加) | 工艺流程图、装置示意图自动生成 |
|
||||
|
||||
## 5. 不推荐(直接跳过)
|
||||
|
||||
| 领域 | 仓库代表 skill | 跳过原因 |
|
||||
|---|---|---|
|
||||
| 生物信息 / 基因组 | BioPython / Scanpy / scVelo / Arboreto / anndata / esm | 跟无机材料无交集 |
|
||||
| 药物发现 | DiffDock / DeepChem / Datamol / adaptyv | 同上,RDKit 偶尔有边但太窄 |
|
||||
| 临床研究 | ClinicalTrials.gov / ClinVar / COSMIC / Open Targets | 同上 |
|
||||
| 医学影像 | pydicom / histolab / PathML | 同上 |
|
||||
| 量子计算 | Cirq / Qiskit / PennyLane / QuTiP | 除非真做第一性原理(成本极高) |
|
||||
| 代谢建模 | COBRApy | 同上 |
|
||||
| 地理空间 | GeoPandas | 同上 |
|
||||
| 天文 | astropy | 同上 |
|
||||
|
||||
## 6. 中间档(暂不动,记下来,有场景再说)
|
||||
|
||||
| skill | 触发场景 |
|
||||
|---|---|
|
||||
| **OpenMM + MDAnalysis** | 水泥水化分子模拟、玻璃网络结构 MD —— 门槛高,看院里有没有人真做计算 |
|
||||
| **NetworkX** | 知识图谱、合作网络分析 |
|
||||
| **SymPy** | 推传热扩散、强度模型公式 |
|
||||
| **scikit-image** | SEM 图像分析(粒径分布、形貌识别) —— 仓库没单列,如要做自己装 |
|
||||
|
||||
## 7. 落地排期建议
|
||||
|
||||
| 顺序 | skill | 工作量估计 | 收益 |
|
||||
|---|---|---|---|
|
||||
| 1 | `plot_pub`(matplotlib)SKILL.md | 0.5 day | 立刻见效,所有出图任务受益,中文字体坑也一次扫掉 |
|
||||
| 2 | `materials_pymatgen` 装包 + SKILL.md + 化学式映射 | 1 day | 打开无机材料计算能力(XRD/相图/对称性) |
|
||||
| 3 | `stats_ml` SKILL.md(sklearn + statsmodels + PyMC) | 1 day | 配方-性能建模常态化 |
|
||||
| 4 | `materials_db`(COD + NIST + USPTO) | 2 day(每个 API 都要踩坑) | 中等,可后置;USPTO 部分先并入 `patent` |
|
||||
| 5 | 现有 6 个 skill 抽 best practice 增强 | 各 0.5 day | 渐进改善,不阻塞 |
|
||||
|
||||
## 8. 三个决策点(写代码前需要你确认)
|
||||
|
||||
1. **新 skill 命名风格**:用 `materials_pymatgen` / `stats_ml` / `plot_pub` / `materials_db` 这种业务前缀,还是直接 `pymatgen` / `sklearn` 这种工具名?
|
||||
- 业务前缀:更贴你们院的场景导航
|
||||
- 工具名:跟开源生态对齐,容易回查官方文档
|
||||
2. **Materials Project API key** 走 `.env` 还是单独配置文件?
|
||||
3. **USPTO 并入 `skills/patent` 还是单独建 `skills/uspto`**?(我倾向并入,因为 patent skill 已经有中国专利逻辑)
|
||||
|
||||
定下这三点我就开始动 #1(matplotlib)。
|
||||
|
|
@ -26,3 +26,14 @@ uvicorn[standard]>=0.30.0
|
|||
python-multipart>=0.0.9 # files upload multipart 解析
|
||||
pyjwt>=2.8.0 # /v1/auth/login HS256 token mint/verify(§7 D' 过渡形态)
|
||||
bcrypt>=4.1.0 # /v1/auth/login_password 密码哈希(users.password_hash)
|
||||
|
||||
# 科学计算 skill(2026-05-28 加)
|
||||
# pymatgen skill: 无机材料计算(晶体结构/XRD/相图/Materials Project)
|
||||
pymatgen>=2024.0
|
||||
mp-api>=0.41.0
|
||||
# stats_ml skill: 统计建模与 ML(sklearn 必装,statsmodels 必装,PyMC 可选)
|
||||
scikit-learn>=1.4.0
|
||||
statsmodels>=0.14.0
|
||||
# pymc>=5.10.0 # 贝叶斯小样本估计,装包重(带 pytensor),按需打开
|
||||
# arviz>=0.17.0 # PyMC 后验诊断,跟 pymc 一起开
|
||||
# plot_pub skill: 出版级 matplotlib(中文字体 + viridis 配色),依赖 matplotlib 已在上面
|
||||
|
|
|
|||
|
|
@ -0,0 +1,307 @@
|
|||
"""Smoke: 3 个新加的科学计算 skill 通路验证(pymatgen / stats_ml / plot_pub)。
|
||||
|
||||
跑法: .venv/Scripts/python.exe scripts/smoke_scientific_skills.py
|
||||
|
||||
依赖:`pip install pymatgen mp-api scikit-learn statsmodels`(PyMC 可选,装了就测)。
|
||||
|
||||
不依赖网络默认情况下(MP_API_KEY 没配则跳过 mp_rester 联网那一段)。
|
||||
不动 DB / workspace,产物落系统临时目录,跑完即丢。
|
||||
|
||||
按 skill 顺序 4 段:
|
||||
step A — pymatgen import + CEMENT_PHASES 几个查询 + mp_rester 未配 key 抛错
|
||||
step B — stats_ml 三库装包 + 小 OLS / RandomForest smoke
|
||||
step C — plot_pub apply_pub_style + 最小 XRD-like 图出 PNG
|
||||
step D —(可选)MP_API_KEY 配了就联网拉一条 Materials Project 数据
|
||||
|
||||
任一步异常 [FAIL] 标注后继续下一步,保证整条链路看一遍。
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import io
|
||||
import os
|
||||
import sys
|
||||
import tempfile
|
||||
import time
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parent.parent
|
||||
sys.path.insert(0, str(ROOT))
|
||||
|
||||
# Windows GBK 控制台编码问题: 强制 stdout / stderr utf-8(memory 里这条已踩过)
|
||||
if hasattr(sys.stdout, "buffer"):
|
||||
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding="utf-8", errors="replace")
|
||||
if hasattr(sys.stderr, "buffer"):
|
||||
sys.stderr = io.TextIOWrapper(sys.stderr.buffer, encoding="utf-8", errors="replace")
|
||||
|
||||
|
||||
# 读 .env 注入 MP_API_KEY 等(litellm 链路外手动加载)
|
||||
env_file = ROOT / ".env"
|
||||
if env_file.exists():
|
||||
for line in env_file.read_text(encoding="utf-8").splitlines():
|
||||
line = line.strip()
|
||||
if not line or line.startswith("#") or "=" not in line:
|
||||
continue
|
||||
k, _, v = line.partition("=")
|
||||
os.environ.setdefault(k.strip(), v.strip())
|
||||
|
||||
|
||||
def _hr(title: str) -> None:
|
||||
print()
|
||||
print("=" * 60)
|
||||
print(f"[{title}]")
|
||||
print("=" * 60)
|
||||
|
||||
|
||||
def _ok(msg: str) -> None:
|
||||
print(f"[OK] {msg}")
|
||||
|
||||
|
||||
def _fail(msg: str) -> None:
|
||||
print(f"[FAIL] {msg}")
|
||||
|
||||
|
||||
def _skip(msg: str) -> None:
|
||||
print(f"[SKIP] {msg}")
|
||||
|
||||
|
||||
def _info(msg: str) -> None:
|
||||
print(f"[INFO] {msg}")
|
||||
|
||||
|
||||
def step_a_pymatgen() -> None:
|
||||
_hr("step A: pymatgen skill")
|
||||
|
||||
# A1: helper import
|
||||
try:
|
||||
from skills.pymatgen.materials import CEMENT_PHASES, lookup_phase, mp_rester
|
||||
_ok(f"import skills.pymatgen.materials (CEMENT_PHASES 条目数={len(CEMENT_PHASES)})")
|
||||
except Exception as e:
|
||||
_fail(f"import skills.pymatgen.materials: {type(e).__name__}: {e}")
|
||||
return
|
||||
|
||||
# A2: 典型查询
|
||||
cases = [
|
||||
("C3S", "Ca3SiO5"),
|
||||
("硅酸三钙", "Ca3SiO5"),
|
||||
("alite", "Ca3SiO5"), # 大小写不敏感
|
||||
("钙矾石", "Ca6Al2(SO4)3(OH)12·26H2O"),
|
||||
("莫来石", "Al6Si2O13"),
|
||||
("方镁石", "MgO"),
|
||||
("石英", "SiO2"),
|
||||
]
|
||||
for name, expected in cases:
|
||||
try:
|
||||
got = lookup_phase(name)
|
||||
if got == expected:
|
||||
_ok(f"lookup_phase({name!r}) -> {got}")
|
||||
else:
|
||||
_fail(f"lookup_phase({name!r}) -> {got},期望 {expected}")
|
||||
except Exception as e:
|
||||
_fail(f"lookup_phase({name!r}) raised {type(e).__name__}: {e}")
|
||||
|
||||
# A3: 未命中抛 KeyError
|
||||
try:
|
||||
lookup_phase("根本不存在的相_xyz123")
|
||||
_fail("lookup_phase 未命中应抛 KeyError,没抛")
|
||||
except KeyError as e:
|
||||
_ok(f"lookup_phase 未命中正确抛 KeyError (msg 含建议: {'补到' in str(e)})")
|
||||
except Exception as e:
|
||||
_fail(f"lookup_phase 未命中应抛 KeyError,实际 {type(e).__name__}")
|
||||
|
||||
# A4: pymatgen 本体 import
|
||||
try:
|
||||
from pymatgen.core import Structure, Lattice, Molecule
|
||||
from pymatgen.analysis.diffraction.xrd import XRDCalculator
|
||||
from pymatgen.symmetry.analyzer import SpacegroupAnalyzer
|
||||
_ok("pymatgen 核心类 import 全通 (Structure / XRDCalculator / SpacegroupAnalyzer)")
|
||||
except Exception as e:
|
||||
_fail(f"pymatgen 本体 import: {type(e).__name__}: {e}")
|
||||
return
|
||||
|
||||
# A5: 构造一个简单的立方结构 + XRDCalculator 跑一次
|
||||
try:
|
||||
from pymatgen.core import Structure, Lattice
|
||||
from pymatgen.analysis.diffraction.xrd import XRDCalculator
|
||||
# MgO,方镁石,典型耐火材料相
|
||||
lattice = Lattice.cubic(4.21) # MgO a≈4.21Å
|
||||
struct = Structure(lattice, ["Mg", "O"], [[0, 0, 0], [0.5, 0.5, 0.5]])
|
||||
xrd = XRDCalculator(wavelength="CuKa")
|
||||
pattern = xrd.get_pattern(struct, two_theta_range=(20, 80))
|
||||
_ok(f"XRDCalculator on MgO 结构: {len(pattern.x)} 个峰,2θ 范围 [{pattern.x[0]:.1f}, {pattern.x[-1]:.1f}]")
|
||||
except Exception as e:
|
||||
_fail(f"XRDCalculator smoke: {type(e).__name__}: {e}")
|
||||
|
||||
# A6: mp_rester 未配 key 抛 RuntimeError
|
||||
has_key = bool(os.environ.get("MP_API_KEY"))
|
||||
if has_key:
|
||||
_info("MP_API_KEY 已配置,skip 缺 key 抛错验证(下面 step D 测真实查询)")
|
||||
else:
|
||||
# 显式清掉 env 再测
|
||||
try:
|
||||
with mp_rester() as mpr:
|
||||
_fail("MP_API_KEY 未配置时 mp_rester 应抛 RuntimeError,没抛")
|
||||
except RuntimeError as e:
|
||||
if "MP_API_KEY" in str(e) and "materialsproject" in str(e):
|
||||
_ok("mp_rester 未配 key 正确抛 RuntimeError 含申请链接")
|
||||
else:
|
||||
_fail(f"RuntimeError 抛了但 msg 不对: {e}")
|
||||
except Exception as e:
|
||||
_fail(f"应抛 RuntimeError 实际 {type(e).__name__}: {e}")
|
||||
|
||||
|
||||
def step_b_stats_ml() -> None:
|
||||
_hr("step B: stats_ml skill")
|
||||
|
||||
# B1: sklearn
|
||||
try:
|
||||
import numpy as np
|
||||
from sklearn.ensemble import RandomForestRegressor
|
||||
from sklearn.preprocessing import StandardScaler
|
||||
from sklearn.pipeline import Pipeline
|
||||
from sklearn.model_selection import cross_val_score
|
||||
|
||||
# 造一个 fake 配方-强度数据(50 样本 6 特征)
|
||||
rng = np.random.default_rng(42)
|
||||
X = rng.uniform(0, 1, size=(50, 6)) # 6 个掺合料比例
|
||||
y = (X @ rng.uniform(20, 80, size=6)) + rng.normal(0, 5, size=50) # 强度 MPa
|
||||
pipe = Pipeline([
|
||||
("scaler", StandardScaler()),
|
||||
("model", RandomForestRegressor(n_estimators=50, random_state=42)),
|
||||
])
|
||||
scores = cross_val_score(pipe, X, y, cv=5, scoring="r2")
|
||||
_ok(f"sklearn RandomForest 5-fold R²: mean={scores.mean():.3f} std={scores.std():.3f}")
|
||||
except Exception as e:
|
||||
_fail(f"sklearn smoke: {type(e).__name__}: {e}")
|
||||
|
||||
# B2: statsmodels
|
||||
try:
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
import statsmodels.formula.api as smf
|
||||
rng = np.random.default_rng(42)
|
||||
df = pd.DataFrame({
|
||||
"x1": rng.uniform(0, 1, 50),
|
||||
"x2": rng.uniform(0, 1, 50),
|
||||
})
|
||||
df["y"] = 3 * df["x1"] - 2 * df["x2"] + rng.normal(0, 0.3, 50)
|
||||
model = smf.ols("y ~ x1 + x2", data=df).fit()
|
||||
r2 = model.rsquared
|
||||
p_x1 = model.pvalues["x1"]
|
||||
_ok(f"statsmodels OLS: R²={r2:.3f}, p(x1)={p_x1:.4f} (应 << 0.05)")
|
||||
except Exception as e:
|
||||
_fail(f"statsmodels smoke: {type(e).__name__}: {e}")
|
||||
|
||||
# B3: PyMC(可选)
|
||||
try:
|
||||
import pymc as pm
|
||||
import arviz as az
|
||||
_ok(f"pymc import OK (version={pm.__version__})")
|
||||
# 不真跑采样(慢),只验 import
|
||||
except ImportError:
|
||||
_skip("PyMC / arviz 未装(可选依赖,要做贝叶斯再 pip install pymc arviz)")
|
||||
except Exception as e:
|
||||
_fail(f"PyMC import: {type(e).__name__}: {e}")
|
||||
|
||||
|
||||
def step_c_plot_pub() -> None:
|
||||
_hr("step C: plot_pub skill")
|
||||
|
||||
# C1: import + apply_pub_style
|
||||
try:
|
||||
from skills.plot_pub.style import apply_pub_style, reset_style, _find_chinese_font
|
||||
font = _find_chinese_font()
|
||||
if font:
|
||||
_ok(f"_find_chinese_font 返 {font!r}")
|
||||
else:
|
||||
_info("系统未装中文字体候选 (SimHei/YaHei/WenQuanYi/Heiti),中文将显示方块")
|
||||
|
||||
apply_pub_style()
|
||||
_ok("apply_pub_style() 调用通过")
|
||||
except Exception as e:
|
||||
_fail(f"plot_pub import / apply_pub_style: {type(e).__name__}: {e}")
|
||||
return
|
||||
|
||||
# C2: 跑一个 minimal XRD-like 图
|
||||
try:
|
||||
import numpy as np
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
tmp_dir = Path(tempfile.mkdtemp(prefix="zcbot_smoke_plot_"))
|
||||
out_png = tmp_dir / "smoke_xrd.png"
|
||||
out_pdf = tmp_dir / "smoke_xrd.pdf"
|
||||
|
||||
two_theta = np.linspace(5, 80, 1000)
|
||||
# 假装是 MgO 衍射(几个高斯峰)
|
||||
peaks = [(36.9, 1.0), (42.9, 0.7), (62.3, 0.5), (74.7, 0.3), (78.6, 0.4)]
|
||||
intensity = np.zeros_like(two_theta)
|
||||
for pos, h in peaks:
|
||||
intensity += h * np.exp(-((two_theta - pos) ** 2) / (2 * 0.3 ** 2))
|
||||
intensity += np.random.normal(0, 0.02, len(two_theta)) # 噪声
|
||||
|
||||
fig, ax = plt.subplots(figsize=(6, 4))
|
||||
ax.plot(two_theta, intensity, "k-", lw=1.0, label="MgO 模拟谱")
|
||||
ax.set_xlabel(r"$2\theta$ / °")
|
||||
ax.set_ylabel("强度 / a.u.")
|
||||
ax.set_xlim(5, 80)
|
||||
ax.legend(frameon=False)
|
||||
fig.tight_layout()
|
||||
fig.savefig(out_png, dpi=200)
|
||||
fig.savefig(out_pdf)
|
||||
plt.close(fig)
|
||||
|
||||
png_size = out_png.stat().st_size
|
||||
pdf_size = out_pdf.stat().st_size
|
||||
_ok(f"出图 PNG ({png_size/1024:.1f}KB) + PDF ({pdf_size/1024:.1f}KB) -> {tmp_dir}")
|
||||
except Exception as e:
|
||||
_fail(f"plot_pub 出图: {type(e).__name__}: {e}")
|
||||
|
||||
# C3: 还原 rcParams 防污染后续步骤
|
||||
try:
|
||||
reset_style()
|
||||
_ok("reset_style() 还原 matplotlib defaults")
|
||||
except Exception as e:
|
||||
_fail(f"reset_style: {type(e).__name__}: {e}")
|
||||
|
||||
|
||||
def step_d_mp_online() -> None:
|
||||
_hr("step D: Materials Project 联网(可选,需 MP_API_KEY)")
|
||||
|
||||
if not os.environ.get("MP_API_KEY"):
|
||||
_skip("MP_API_KEY 未配,跳过联网查询(.env 加 MP_API_KEY=... 即可,免费申请 https://materialsproject.org/api)")
|
||||
return
|
||||
|
||||
try:
|
||||
from skills.pymatgen.materials import mp_rester, lookup_phase
|
||||
formula = lookup_phase("C3S") # Ca3SiO5
|
||||
t0 = time.time()
|
||||
with mp_rester() as mpr:
|
||||
docs = mpr.materials.summary.search(
|
||||
formula=formula,
|
||||
fields=["material_id", "formula_pretty", "energy_above_hull"],
|
||||
)
|
||||
dt = (time.time() - t0) * 1000
|
||||
_ok(f"mp_rester 查 {formula}: 返回 {len(docs)} 条 in {dt:.0f}ms")
|
||||
for d in docs[:3]:
|
||||
print(f" {d.material_id} {d.formula_pretty} ehull={d.energy_above_hull:.3f}")
|
||||
except Exception as e:
|
||||
_fail(f"mp_rester 联网查询: {type(e).__name__}: {e}")
|
||||
|
||||
|
||||
def main() -> int:
|
||||
print("=" * 60)
|
||||
print("zcbot scientific skills smoke (pymatgen / stats_ml / plot_pub)")
|
||||
print("=" * 60)
|
||||
for fn in (step_a_pymatgen, step_b_stats_ml, step_c_plot_pub, step_d_mp_online):
|
||||
try:
|
||||
fn()
|
||||
except Exception as e:
|
||||
_fail(f"[{fn.__name__} crashed] {type(e).__name__}: {e}")
|
||||
print()
|
||||
print("=" * 60)
|
||||
print("smoke done")
|
||||
print("=" * 60)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
|
|
@ -0,0 +1,168 @@
|
|||
---
|
||||
name: plot_pub
|
||||
description: 出版级 matplotlib 绘图(论文 / 报告 / 申报书用,中文字体 + viridis 配色 + dpi 设定一键到位)。✅ 触发:用户要画 XRD 谱、TG-DSC 曲线、应力-应变曲线、SEM 标注、多 panel 学术图、要 SVG/PDF 矢量图给论文。⛔ 不触发:PPT 内嵌图(走 `ppt` skill 的 matplotlib 配图流程);只要快速看一眼数据(直接 `df.plot()` 即可,不用本 skill)。
|
||||
---
|
||||
|
||||
# plot_pub
|
||||
|
||||
服务建材院的论文 / 申报书 / 调研报告出图。**核心是 `apply_pub_style()` 一键设置 rcParams**(中文字体、viridis 默认、字号、dpi、矢量优先),避免每次手动设置十几行还忘配中文字体。
|
||||
|
||||
## 何时用
|
||||
|
||||
- 写论文要投 *Cement and Concrete Research* / *J. Am. Ceram. Soc.* / *Construction and Building Materials* 等期刊
|
||||
- 报告 / 申报书要中文标题 + 中文图例的高 dpi 图
|
||||
- 多组样本数据对比(XRD 多谱叠图、强度发展曲线、温度场)
|
||||
- 双 Y 轴(TG-DSC 是典型)
|
||||
|
||||
## 何时不用
|
||||
|
||||
- PPT 里要嵌图 → 走 `skills/ppt`(那边已经有配色 / 尺寸规范)
|
||||
- 交互式探查数据 → 用 plotly / 直接 jupyter
|
||||
- 只要看趋势不出版 → `df.plot()` 一行搞定,不用本 skill 的 ceremony
|
||||
|
||||
## 准备
|
||||
|
||||
```python
|
||||
from skills.plot_pub.style import apply_pub_style
|
||||
import matplotlib.pyplot as plt
|
||||
import numpy as np
|
||||
|
||||
apply_pub_style() # 调一次,后面所有 plt 都按这个 style
|
||||
```
|
||||
|
||||
## `apply_pub_style()` 参数
|
||||
|
||||
| 参数 | 默认 | 说明 |
|
||||
|---|---|---|
|
||||
| `chinese` | `True` | 加载 SimHei / Microsoft YaHei,中文不显示方块 |
|
||||
| `dpi` | `150` | 屏幕显示 dpi;保存图用 `savefig(..., dpi=300)` 单独控 |
|
||||
| `font_size` | `10` | 论文双栏图标准字号 |
|
||||
| `linewidth` | `1.2` | 线粗,出版图细些显精致 |
|
||||
| `cmap` | `"viridis"` | 默认 colormap,**审稿人不会喷**(jet 现在是雷区) |
|
||||
|
||||
## 典型工作流
|
||||
|
||||
### A. XRD pattern(多相叠图 + 标峰)
|
||||
|
||||
```python
|
||||
from skills.plot_pub.style import apply_pub_style
|
||||
import matplotlib.pyplot as plt
|
||||
import numpy as np
|
||||
|
||||
apply_pub_style()
|
||||
|
||||
fig, ax = plt.subplots(figsize=(6, 4))
|
||||
|
||||
# 实测谱
|
||||
ax.plot(two_theta_exp, intensity_exp, "k-", lw=1.0, label="实测")
|
||||
|
||||
# 理论谱(各物相,用 pymatgen XRDCalculator 算的)
|
||||
for phase, pattern in pymatgen_patterns.items():
|
||||
ax.vlines(pattern.x, 0, pattern.y / pattern.y.max() * intensity_exp.max() * 0.3,
|
||||
label=phase, alpha=0.7)
|
||||
|
||||
ax.set_xlabel(r"$2\theta$ / °")
|
||||
ax.set_ylabel("强度 / a.u.")
|
||||
ax.set_xlim(5, 80)
|
||||
ax.legend(frameon=False, loc="upper right")
|
||||
fig.tight_layout()
|
||||
fig.savefig("xrd_compare.pdf", bbox_inches="tight") # PDF 矢量,期刊偏好
|
||||
fig.savefig("xrd_compare.png", dpi=300, bbox_inches="tight") # PNG 给 PPT
|
||||
plt.close(fig) # 关键!run_python 多任务下不关会堆 figure 泄漏内存
|
||||
```
|
||||
|
||||
### B. TG-DSC 双 Y 轴
|
||||
|
||||
```python
|
||||
fig, ax_tg = plt.subplots(figsize=(6, 4))
|
||||
ax_dsc = ax_tg.twinx()
|
||||
|
||||
ax_tg.plot(T, tg_mass, "b-", lw=1.2, label="TG")
|
||||
ax_dsc.plot(T, dsc_flow, "r-", lw=1.2, label="DSC")
|
||||
|
||||
ax_tg.set_xlabel("温度 / °C")
|
||||
ax_tg.set_ylabel("质量 / %", color="b")
|
||||
ax_dsc.set_ylabel("热流 / mW·mg$^{-1}$", color="r")
|
||||
ax_tg.tick_params(axis="y", labelcolor="b")
|
||||
ax_dsc.tick_params(axis="y", labelcolor="r")
|
||||
|
||||
# 合并两个 ax 的 legend
|
||||
lines1, labels1 = ax_tg.get_legend_handles_labels()
|
||||
lines2, labels2 = ax_dsc.get_legend_handles_labels()
|
||||
ax_tg.legend(lines1 + lines2, labels1 + labels2, loc="best", frameon=False)
|
||||
|
||||
fig.tight_layout()
|
||||
fig.savefig("tg_dsc.pdf", bbox_inches="tight")
|
||||
plt.close(fig)
|
||||
```
|
||||
|
||||
### C. 多组样本对比(强度发展曲线)
|
||||
|
||||
```python
|
||||
fig, ax = plt.subplots(figsize=(6, 4))
|
||||
ages = [3, 7, 14, 28, 56, 90]
|
||||
|
||||
# 用 viridis 给多个样本上色,顺序色觉友好
|
||||
import matplotlib.cm as cm
|
||||
colors = cm.viridis(np.linspace(0.1, 0.9, len(samples)))
|
||||
|
||||
for (label, strengths), c in zip(samples.items(), colors):
|
||||
ax.plot(ages, strengths, "o-", color=c, lw=1.2, ms=5, label=label)
|
||||
|
||||
ax.set_xlabel("龄期 / d")
|
||||
ax.set_ylabel("抗压强度 / MPa")
|
||||
ax.set_xscale("log") # 龄期用对数刻度更清楚
|
||||
ax.legend(frameon=False)
|
||||
ax.grid(True, ls="--", alpha=0.3)
|
||||
|
||||
fig.tight_layout()
|
||||
fig.savefig("strength_dev.pdf", bbox_inches="tight")
|
||||
plt.close(fig)
|
||||
```
|
||||
|
||||
### D. 多 panel(2×2 figure,论文 figure 1 标配)
|
||||
|
||||
```python
|
||||
fig, axes = plt.subplots(2, 2, figsize=(10, 7), constrained_layout=True)
|
||||
|
||||
axes[0, 0].plot(...) # (a) XRD
|
||||
axes[0, 1].plot(...) # (b) FTIR
|
||||
axes[1, 0].plot(...) # (c) TG
|
||||
axes[1, 1].plot(...) # (d) 强度
|
||||
|
||||
# 子图编号(论文图 1 标准做法)
|
||||
for ax, letter in zip(axes.flat, "abcd"):
|
||||
ax.text(0.02, 0.95, f"({letter})", transform=ax.transAxes,
|
||||
fontweight="bold", va="top")
|
||||
|
||||
fig.savefig("fig1_characterization.pdf", bbox_inches="tight")
|
||||
plt.close(fig)
|
||||
```
|
||||
|
||||
## 中文字体配置(Windows 注意)
|
||||
|
||||
`apply_pub_style(chinese=True)` 默认按以下顺序找字体:
|
||||
1. `SimHei`(Windows 自带,黑体)
|
||||
2. `Microsoft YaHei`(Windows 自带,雅黑)
|
||||
3. `WenQuanYi Micro Hei`(Linux 兜底)
|
||||
|
||||
都没有 → 退回 DejaVu Sans,中文显示方块。装字体方式:Windows 控制面板 → 字体,丢 .ttf 进去。
|
||||
|
||||
**LaTeX 公式渲染**:matplotlib 用 `$...$` 自动走 mathtext,中英文混排正常。要更精细的 LaTeX(自定义 macro)就 `plt.rcParams["text.usetex"] = True`,但要本地装 TeX Live。
|
||||
|
||||
## 反模式
|
||||
|
||||
1. **用 pyplot 状态机不用 OO 接口** —— 写 `plt.plot(); plt.xlabel(...)` 一时爽,多 figure 时变量乱;一律 `fig, ax = plt.subplots()`
|
||||
2. **不 close figure** —— `run_python` 跑多次会堆 figure 内存涨,必须 `plt.close(fig)` 收尾
|
||||
3. **用 jet colormap** —— 色觉障碍审稿人会喷,近年期刊都偏 viridis / plasma / cividis
|
||||
4. **保存图不 `bbox_inches="tight"`** —— legend / 标题被裁掉,反复看图反复改尺寸
|
||||
5. **同图 dpi 跟 figsize 不匹配** —— figsize=(10,7) dpi=300 给 PPT 嵌入太大,字会很小;论文图 figsize=(6,4) 双栏刚好
|
||||
6. **PNG 给论文** —— 期刊要矢量,PDF / SVG / EPS 优先;PNG 只给 PPT / 网页
|
||||
7. **中文显示方块还坚持跑** —— `apply_pub_style()` 失败会 warn,看到 warning 就停下查字体
|
||||
8. **legend 框线 / 灰底** —— 出版图 `legend(frameon=False)`,干净
|
||||
9. **轴标签没单位** —— `"温度"` 是错的,`"温度 / °C"` 才对(SI 推荐 `/` 分隔)
|
||||
10. **`ax.set_xticks([1,2,3,...])` 手动写一堆** —— matplotlib 自动 tick 已经足够,手动覆盖只在特殊场景(对数轴 / 离散类别轴)
|
||||
|
||||
## 依赖
|
||||
|
||||
matplotlib 已经在 `requirements.txt`(>=3.8.0)。本 skill 无新增依赖。
|
||||
|
|
@ -0,0 +1,114 @@
|
|||
"""
|
||||
plot_pub skill — 出版级 matplotlib rcParams 一键设置。
|
||||
|
||||
LLM 通过 `from skills.plot_pub.style import apply_pub_style; apply_pub_style()` 使用。
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import warnings
|
||||
import matplotlib
|
||||
import matplotlib.pyplot as plt
|
||||
from matplotlib import font_manager
|
||||
|
||||
|
||||
# 中文字体候选,按平台优先级排
|
||||
_CHINESE_FONTS = [
|
||||
"SimHei", # Windows 黑体
|
||||
"Microsoft YaHei", # Windows 雅黑
|
||||
"WenQuanYi Micro Hei", # Linux
|
||||
"Heiti TC", # macOS
|
||||
"Arial Unicode MS", # macOS 兜底
|
||||
]
|
||||
|
||||
|
||||
def _find_chinese_font() -> str | None:
|
||||
"""从候选清单里挑系统装了的第一个,都没有返 None。"""
|
||||
installed = {f.name for f in font_manager.fontManager.ttflist}
|
||||
for name in _CHINESE_FONTS:
|
||||
if name in installed:
|
||||
return name
|
||||
return None
|
||||
|
||||
|
||||
def apply_pub_style(
|
||||
chinese: bool = True,
|
||||
dpi: int = 150,
|
||||
font_size: int = 10,
|
||||
linewidth: float = 1.2,
|
||||
cmap: str = "viridis",
|
||||
) -> None:
|
||||
"""
|
||||
一键设置出版级 matplotlib rcParams。
|
||||
|
||||
Args:
|
||||
chinese: True 时加载中文字体,失败 warn 但不抛(图继续画,中文显示方块)
|
||||
dpi: 屏幕显示 dpi。保存图请用 savefig(..., dpi=300) 单独指定
|
||||
font_size: 基础字号,论文双栏图标准 10pt
|
||||
linewidth: 线粗,出版图细些更精致
|
||||
cmap: 默认 colormap,viridis / plasma / cividis 是色觉友好选择
|
||||
"""
|
||||
rc = matplotlib.rcParams
|
||||
|
||||
# ---- 字体 ----
|
||||
if chinese:
|
||||
font = _find_chinese_font()
|
||||
if font:
|
||||
# 中文字体放第一位,后面接 Arial / DejaVu 处理英文
|
||||
rc["font.sans-serif"] = [font, "Arial", "DejaVu Sans"]
|
||||
else:
|
||||
warnings.warn(
|
||||
"[plot_pub] 未找到中文字体(候选: "
|
||||
+ ", ".join(_CHINESE_FONTS)
|
||||
+ ")。中文将显示为方块。"
|
||||
+ "Windows 控制面板 → 字体,Linux 装 wqy-microhei 包。",
|
||||
RuntimeWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
rc["font.sans-serif"] = ["Arial", "DejaVu Sans"]
|
||||
else:
|
||||
rc["font.sans-serif"] = ["Arial", "DejaVu Sans"]
|
||||
|
||||
rc["axes.unicode_minus"] = False # 负号不显示成方块
|
||||
rc["font.family"] = "sans-serif"
|
||||
rc["font.size"] = font_size
|
||||
|
||||
# ---- 尺寸 / dpi ----
|
||||
rc["figure.dpi"] = dpi
|
||||
rc["savefig.dpi"] = 300 # 默认保存高 dpi,临时图用 savefig(..., dpi=...) 覆盖
|
||||
rc["savefig.bbox"] = "tight"
|
||||
rc["savefig.pad_inches"] = 0.05
|
||||
|
||||
# ---- 线条 ----
|
||||
rc["lines.linewidth"] = linewidth
|
||||
rc["lines.markersize"] = 4
|
||||
rc["axes.linewidth"] = 0.8
|
||||
|
||||
# ---- 刻度 ----
|
||||
rc["xtick.direction"] = "in" # 期刊偏好刻度朝内
|
||||
rc["ytick.direction"] = "in"
|
||||
rc["xtick.major.size"] = 4
|
||||
rc["ytick.major.size"] = 4
|
||||
rc["xtick.minor.size"] = 2
|
||||
rc["ytick.minor.size"] = 2
|
||||
rc["xtick.minor.visible"] = True
|
||||
rc["ytick.minor.visible"] = True
|
||||
|
||||
# ---- legend ----
|
||||
rc["legend.frameon"] = False # 出版图 legend 无框
|
||||
rc["legend.fontsize"] = font_size - 1
|
||||
|
||||
# ---- colormap ----
|
||||
rc["image.cmap"] = cmap
|
||||
|
||||
# ---- 数学字体 ----
|
||||
rc["mathtext.fontset"] = "stix" # 跟 Times / Arial 配,公式不突兀
|
||||
|
||||
# ---- 兜底:防止 PDF 嵌入 Type 3 字体(期刊要求 Type 42) ----
|
||||
rc["pdf.fonttype"] = 42
|
||||
rc["ps.fonttype"] = 42
|
||||
|
||||
|
||||
def reset_style() -> None:
|
||||
"""还原 matplotlib 默认 rcParams(测试 / 切换主题时用)。"""
|
||||
matplotlib.rcdefaults()
|
||||
|
|
@ -0,0 +1,144 @@
|
|||
---
|
||||
name: pymatgen
|
||||
description: 无机材料计算工具(晶体结构 I/O、XRD 模拟、相图、对称性、Materials Project 查询)。✅ 触发:用户问水泥熟料相 / 玻璃陶瓷物相 / 耐火砖矿物相、XRD 谱图反演与正向模拟、晶格参数、空间群、相稳定性、查 Materials Project 数据。⛔ 不触发:用户只问材料宏观性能(强度/导热),不涉及晶体结构;或属于有机分子/药物(那是 RDKit 范畴)。
|
||||
---
|
||||
|
||||
# Pymatgen
|
||||
|
||||
无机材料计算的核心库,服务建材院的水泥 / 混凝土 / 玻璃 / 陶瓷 / 耐火材料场景。**底层用 pymatgen 官方 API,本 skill 提供两个轻量 helper**:`CEMENT_PHASES` 常量(中文相名→化学式映射)和 `mp_rester()`(从 env 拿 MP_API_KEY)。
|
||||
|
||||
## 何时用
|
||||
|
||||
- 用户给晶体结构文件(.cif / POSCAR / .xyz)要分析
|
||||
- 要正向算 XRD pattern 跟实测谱对比
|
||||
- 要查某矿物相的空间群 / 晶格参数 / 配位环境
|
||||
- 要做凝胶相 / 水化产物的相图 / 稳定性分析
|
||||
- 要从 Materials Project 拉某化合物的已知结构 / 性质
|
||||
- 写 VASP / Gaussian / Quantum ESPRESSO 输入文件
|
||||
|
||||
## 何时不用
|
||||
|
||||
- 用户只问宏观性能(28d 抗压、导热系数、热膨胀)→ 走 `stats_ml`(回归建模)
|
||||
- 用户问有机外加剂分子结构 → 走 RDKit(本仓库未集成)
|
||||
- 用户只要画图,没有晶体计算需求 → 走 `plot_pub`
|
||||
|
||||
## 准备
|
||||
|
||||
```python
|
||||
from skills.pymatgen.materials import CEMENT_PHASES, mp_rester
|
||||
from pymatgen.core import Structure, Lattice, Molecule
|
||||
from pymatgen.analysis.diffraction.xrd import XRDCalculator
|
||||
from pymatgen.symmetry.analyzer import SpacegroupAnalyzer
|
||||
from pymatgen.analysis.phase_diagram import PhaseDiagram, PDEntry
|
||||
```
|
||||
|
||||
(import 路径走 `run_python` 注入的 PYTHONPATH,直接写)
|
||||
|
||||
## 关键:中文相名 → 化学式
|
||||
|
||||
`CEMENT_PHASES` dict 收了水泥 / 陶瓷 / 耐火 / 玻璃常见相的中英文 ↔ 化学式映射(见 `materials.py`)。**用户用中文报相名时,先查这张表转化学式再喂 pymatgen / Materials Project**:
|
||||
|
||||
| 用户原话 | 不要这样 | 这样 |
|
||||
|---|---|---|
|
||||
| C3S | `Structure.from_file("C3S")` | 先 `CEMENT_PHASES["C3S"]` → `"Ca3SiO5"`,再走 mp 查或自己拼 |
|
||||
| 钙矾石(AFt) | mp 直接搜 "AFt" | `CEMENT_PHASES["钙矾石"]` → `"Ca6Al2(SO4)3(OH)12·26H2O"` |
|
||||
| 莫来石 | mp 直接搜 "mullite" | `CEMENT_PHASES["莫来石"]` → `"Al6Si2O13"` |
|
||||
| 方镁石 | 直接搜 "magnesite"(错,那是菱镁矿) | `CEMENT_PHASES["方镁石"]` → `"MgO"` |
|
||||
|
||||
表里没有的相,先英文学名 → 化学式后再喂,不要直接把中文丢给 mp。
|
||||
|
||||
## Materials Project 接入
|
||||
|
||||
API key 走 env:`MP_API_KEY`(申请:https://materialsproject.org/api)。**必须用 context manager**:
|
||||
|
||||
```python
|
||||
with mp_rester() as mpr: # 自动从 env 拿 key
|
||||
docs = mpr.materials.summary.search(
|
||||
formula="Ca3SiO5",
|
||||
fields=["material_id", "formula_pretty", "symmetry", "energy_above_hull"],
|
||||
)
|
||||
for d in docs[:5]:
|
||||
print(d.material_id, d.formula_pretty, d.symmetry.symbol, d.energy_above_hull)
|
||||
```
|
||||
|
||||
`MP_API_KEY` 没配 → `mp_rester()` 抛 `RuntimeError("MP_API_KEY not set in env...")`,告诉用户去配,不要继续。
|
||||
|
||||
## 典型工作流
|
||||
|
||||
### A. 实测 XRD 比对(谁是这个峰)
|
||||
|
||||
1. 用户给疑似相清单(中文 / 英文 / 简写都行)
|
||||
2. 各相分别:`CEMENT_PHASES` 查化学式 → `mp_rester()` 拿 Structure → `XRDCalculator().get_pattern(structure)` 算理论谱
|
||||
3. 把各相理论谱跟实测谱(用户给的 xy 数据)叠图(走 `plot_pub`)
|
||||
4. 报"x° 这个峰最可能是 C3S 的 (h k l) 衍射"
|
||||
|
||||
```python
|
||||
from pymatgen.analysis.diffraction.xrd import XRDCalculator
|
||||
|
||||
xrd = XRDCalculator(wavelength="CuKa") # 默认 Cu Kα
|
||||
with mp_rester() as mpr:
|
||||
docs = mpr.materials.summary.search(formula="Ca3SiO5", fields=["material_id"])
|
||||
struct = mpr.get_structure_by_material_id(docs[0].material_id)
|
||||
pattern = xrd.get_pattern(struct, two_theta_range=(5, 80))
|
||||
# pattern.x = 2θ 列表, pattern.y = 强度, pattern.hkls = (h,k,l) 列表
|
||||
```
|
||||
|
||||
### B. 给定结构问对称性
|
||||
|
||||
```python
|
||||
from pymatgen.core import Structure
|
||||
from pymatgen.symmetry.analyzer import SpacegroupAnalyzer
|
||||
|
||||
struct = Structure.from_file("path/to/sample.cif")
|
||||
sga = SpacegroupAnalyzer(struct)
|
||||
print(sga.get_space_group_symbol()) # 例 "P21/c"
|
||||
print(sga.get_space_group_number()) # 例 14
|
||||
print(sga.get_crystal_system()) # 例 "monoclinic"
|
||||
prim = sga.get_primitive_standard_structure() # 简约胞
|
||||
```
|
||||
|
||||
### C. 凝胶 / 水化产物相图稳定性
|
||||
|
||||
```python
|
||||
from pymatgen.analysis.phase_diagram import PhaseDiagram
|
||||
|
||||
with mp_rester() as mpr:
|
||||
entries = mpr.get_entries_in_chemsys(["Ca", "Si", "O", "H"])
|
||||
pd = PhaseDiagram(entries)
|
||||
# pd.get_decomp_and_e_above_hull(some_entry) → 分解路径 + 能量
|
||||
```
|
||||
|
||||
### D. 格式转换(给计算所做 VASP 输入)
|
||||
|
||||
```python
|
||||
struct.to(filename="POSCAR") # 自动按后缀写
|
||||
struct.to(filename="output.cif")
|
||||
```
|
||||
|
||||
## 反模式
|
||||
|
||||
- 用户报中文相名(C3S / 钙矾石 / 莫来石)直接喂 mp / pymatgen,不查 `CEMENT_PHASES` —— mp 不认中文,简写也不认
|
||||
- `MPRester` 不走 context manager(`mpr = MPRester(); ...`) —— 连接泄漏
|
||||
- 手写 CIF parser → 一律 `Structure.from_file()`
|
||||
- 不做 `SpacegroupAnalyzer.get_primitive_standard_structure()` 直接拿原胞做对称性比对(原胞可能是超胞,对称性少看出来)
|
||||
- 大 cutoff 邻居搜索(`get_neighbors(r=20)`)—— 性能差,先 `r=5` 试
|
||||
- 编造 material_id / 化学式 —— mp 查不到就告诉用户"库里没收录",不要凭训练数据脑补
|
||||
- 自己写 INCAR(VASP 输入)—— 用 `MPRelaxSet` / `MPStaticSet` 拿 mp 验证过的参数
|
||||
- 把 pymatgen 算的理论 XRD 当实验值 —— 永远说清是"理论 pattern",实测有择优取向 / 仪器展宽差异
|
||||
|
||||
## 依赖
|
||||
|
||||
`requirements.txt` 加:
|
||||
```
|
||||
pymatgen>=2024.0
|
||||
mp-api>=0.41.0
|
||||
```
|
||||
装:`.venv/Scripts/pip install pymatgen mp-api`
|
||||
|
||||
## env
|
||||
|
||||
```
|
||||
MP_API_KEY=your_key_from_materialsproject_org
|
||||
```
|
||||
|
||||
写到 `.env`(项目根)即可,`mp_rester()` 自动读。
|
||||
|
|
@ -0,0 +1,153 @@
|
|||
"""
|
||||
pymatgen skill helpers — 建材院无机材料场景常用映射 + MPRester 封装。
|
||||
|
||||
LLM 通过 `from skills.pymatgen.materials import CEMENT_PHASES, mp_rester` 使用。
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from contextlib import contextmanager
|
||||
|
||||
|
||||
# 中文/简写 → 化学式映射。覆盖建材院 R&D 高频物相。
|
||||
# 添加新条目时:
|
||||
# - key 用用户最自然的称呼(中文 / 行业简写),value 是 mp 能识别的化学式
|
||||
# - 同一物相多种叫法都加 key,指向同一化学式(C3S / 硅酸三钙 / 阿利特 都是 Ca3SiO5)
|
||||
CEMENT_PHASES: dict[str, str] = {
|
||||
# ---- 硅酸盐水泥熟料矿物 ----
|
||||
"C3S": "Ca3SiO5",
|
||||
"硅酸三钙": "Ca3SiO5",
|
||||
"阿利特": "Ca3SiO5",
|
||||
"alite": "Ca3SiO5",
|
||||
|
||||
"C2S": "Ca2SiO4",
|
||||
"硅酸二钙": "Ca2SiO4",
|
||||
"贝利特": "Ca2SiO4",
|
||||
"belite": "Ca2SiO4",
|
||||
|
||||
"C3A": "Ca3Al2O6",
|
||||
"铝酸三钙": "Ca3Al2O6",
|
||||
|
||||
"C4AF": "Ca4Al2Fe2O10",
|
||||
"铁铝酸四钙": "Ca4Al2Fe2O10",
|
||||
"铁相": "Ca4Al2Fe2O10",
|
||||
|
||||
# ---- 水化产物 ----
|
||||
"C-S-H": "Ca1.5SiO3.5·xH2O",
|
||||
"水化硅酸钙": "Ca1.5SiO3.5·xH2O",
|
||||
|
||||
"CH": "Ca(OH)2",
|
||||
"氢氧化钙": "Ca(OH)2",
|
||||
"portlandite": "Ca(OH)2",
|
||||
"钙矾石": "Ca6Al2(SO4)3(OH)12·26H2O",
|
||||
"AFt": "Ca6Al2(SO4)3(OH)12·26H2O",
|
||||
"ettringite": "Ca6Al2(SO4)3(OH)12·26H2O",
|
||||
|
||||
"AFm": "Ca4Al2(OH)12(SO4)·6H2O",
|
||||
"单硫型水化硫铝酸钙": "Ca4Al2(OH)12(SO4)·6H2O",
|
||||
|
||||
# ---- 石膏 / 硫酸盐 ----
|
||||
"石膏": "CaSO4·2H2O",
|
||||
"二水石膏": "CaSO4·2H2O",
|
||||
"gypsum": "CaSO4·2H2O",
|
||||
"半水石膏": "CaSO4·0.5H2O",
|
||||
"硬石膏": "CaSO4",
|
||||
"anhydrite": "CaSO4",
|
||||
|
||||
# ---- 碳酸盐 / 碳化产物 ----
|
||||
"方解石": "CaCO3",
|
||||
"calcite": "CaCO3",
|
||||
"文石": "CaCO3",
|
||||
"aragonite": "CaCO3",
|
||||
|
||||
# ---- 陶瓷 / 耐火常见相 ----
|
||||
"莫来石": "Al6Si2O13",
|
||||
"mullite": "Al6Si2O13",
|
||||
|
||||
"堇青石": "Mg2Al4Si5O18",
|
||||
"cordierite": "Mg2Al4Si5O18",
|
||||
|
||||
"刚玉": "Al2O3",
|
||||
"α-Al2O3": "Al2O3",
|
||||
"corundum": "Al2O3",
|
||||
|
||||
"方镁石": "MgO",
|
||||
"periclase": "MgO",
|
||||
|
||||
"尖晶石": "MgAl2O4",
|
||||
"spinel": "MgAl2O4",
|
||||
|
||||
"锆英石": "ZrSiO4",
|
||||
"zircon": "ZrSiO4",
|
||||
|
||||
"石英": "SiO2",
|
||||
"quartz": "SiO2",
|
||||
|
||||
"方石英": "SiO2",
|
||||
"cristobalite": "SiO2",
|
||||
|
||||
"鳞石英": "SiO2",
|
||||
"tridymite": "SiO2",
|
||||
|
||||
# ---- 玻璃常见组分晶相 ----
|
||||
"钙长石": "CaAl2Si2O8",
|
||||
"anorthite": "CaAl2Si2O8",
|
||||
"钠长石": "NaAlSi3O8",
|
||||
"albite": "NaAlSi3O8",
|
||||
"硅灰石": "CaSiO3",
|
||||
"wollastonite": "CaSiO3",
|
||||
"透辉石": "CaMgSi2O6",
|
||||
"diopside": "CaMgSi2O6",
|
||||
|
||||
# ---- 其他常见 ----
|
||||
"白云石": "CaMg(CO3)2",
|
||||
"dolomite": "CaMg(CO3)2",
|
||||
"赤铁矿": "Fe2O3",
|
||||
"hematite": "Fe2O3",
|
||||
"磁铁矿": "Fe3O4",
|
||||
"magnetite": "Fe3O4",
|
||||
}
|
||||
|
||||
|
||||
def lookup_phase(name: str) -> str:
|
||||
"""中文/简写相名 → 化学式。命中返回化学式,未命中抛 KeyError(带建议)。"""
|
||||
if name in CEMENT_PHASES:
|
||||
return CEMENT_PHASES[name]
|
||||
# 大小写不敏感再查一遍
|
||||
lower = name.lower()
|
||||
for k, v in CEMENT_PHASES.items():
|
||||
if k.lower() == lower:
|
||||
return v
|
||||
raise KeyError(
|
||||
f"{name!r} 不在 CEMENT_PHASES 映射表里。"
|
||||
f"若是新相,直接把化学式喂给 pymatgen / Materials Project;"
|
||||
f"若高频用,补到 skills/pymatgen/materials.py 的 CEMENT_PHASES。"
|
||||
)
|
||||
|
||||
|
||||
@contextmanager
|
||||
def mp_rester(api_key: str | None = None):
|
||||
"""
|
||||
MPRester 上下文管理器封装,自动从 env(MP_API_KEY)拿 key。
|
||||
|
||||
用法:
|
||||
with mp_rester() as mpr:
|
||||
docs = mpr.materials.summary.search(formula="Ca3SiO5")
|
||||
|
||||
Args:
|
||||
api_key: 显式传入则用,否则读 env MP_API_KEY。
|
||||
|
||||
Raises:
|
||||
RuntimeError: env 未配置且未传入 api_key。
|
||||
"""
|
||||
key = api_key or os.environ.get("MP_API_KEY")
|
||||
if not key:
|
||||
raise RuntimeError(
|
||||
"MP_API_KEY not set in env. "
|
||||
"申请: https://materialsproject.org/api,然后写到项目根 .env 文件。"
|
||||
)
|
||||
from mp_api.client import MPRester # 局部 import,避免装包前 import skill 就崩
|
||||
|
||||
with MPRester(api_key=key) as mpr:
|
||||
yield mpr
|
||||
|
|
@ -0,0 +1,187 @@
|
|||
---
|
||||
name: stats_ml
|
||||
description: 统计建模与机器学习(sklearn / statsmodels / PyMC 三库合一,场景导航选库)。✅ 触发:配方-性能建模、DoE 响应面、强度预测、特征重要性、假设检验、置信区间、贝叶斯小样本估计、聚类异常实验检测。⛔ 不触发:用户只问描述性统计(均值方差),pandas 一行搞定;深度学习场景(走单独的 torch / lightning skill,本仓库未集成)。
|
||||
---
|
||||
|
||||
# stats_ml
|
||||
|
||||
服务建材院典型场景:**掺合料配比 → 性能(强度 / 流动度 / 凝结时间)** 的建模与推断。三库分工:
|
||||
|
||||
| 你要做 | 用 | 一句话理由 |
|
||||
|---|---|---|
|
||||
| 要 p-value、置信区间、假设检验 | **statsmodels** | OLS / ANOVA / 假设检验是 statsmodels 主场,sklearn 不给 p-value |
|
||||
| 要预测精度高,可解释性次要 | **sklearn** | RandomForest / GBDT / XGBoost 是性能预测的现代基线 |
|
||||
| 样本 < 30 且要不确定度估计 | **PyMC** | 贝叶斯给出参数后验分布,小样本下比频率派的置信区间更稳 |
|
||||
| 要可解释的线性关系 | statsmodels OLS / sklearn LinearRegression | 都可以,统计推断走 statsmodels,纯预测走 sklearn |
|
||||
| 要 DoE 响应面拟合 | statsmodels(二次回归 + ANOVA) | 主流做法,系数显著性能直接读 |
|
||||
| 要聚类找异常配方 | sklearn (KMeans / DBSCAN) | 标准工具 |
|
||||
| 要降维可视化(配方空间) | sklearn (PCA / t-SNE) | 标准工具 |
|
||||
|
||||
## 何时用 / 何时不用
|
||||
|
||||
✅ 用:
|
||||
- 用户给一张配方-性能表(N 行配方 + 性能列),要建模 / 预测 / 推断
|
||||
- 要回答"哪个因素影响最大"(特征重要性 / 系数显著性)
|
||||
- 要回答"这个新配方预测强度多少 + 置信区间多大"
|
||||
- 要找异常实验点 / 配方聚类
|
||||
|
||||
⛔ 不用:
|
||||
- 用户只要看均值方差直方图 → pandas + matplotlib 直接搞,杀鸡不用牛刀
|
||||
- 用户给的"数据"只有 3-5 个点 → 任何 ML / 统计都不可靠,告诉用户先补数据
|
||||
- 深度学习需求(CNN / Transformer)→ 不在本 skill 范围
|
||||
|
||||
## 准备
|
||||
|
||||
```python
|
||||
# 按需 import,不要全 import
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
|
||||
# sklearn
|
||||
from sklearn.model_selection import train_test_split, KFold, StratifiedKFold, GridSearchCV
|
||||
from sklearn.preprocessing import StandardScaler
|
||||
from sklearn.pipeline import Pipeline
|
||||
from sklearn.metrics import r2_score, mean_squared_error
|
||||
from sklearn.ensemble import RandomForestRegressor, GradientBoostingRegressor
|
||||
|
||||
# statsmodels
|
||||
import statsmodels.api as sm
|
||||
import statsmodels.formula.api as smf
|
||||
|
||||
# PyMC(重,按需 import)
|
||||
# import pymc as pm
|
||||
# import arviz as az
|
||||
```
|
||||
|
||||
## 典型工作流
|
||||
|
||||
### A. 配方-性能回归(sklearn,要预测精度)
|
||||
|
||||
```python
|
||||
# 数据:每行一个配方,X 是组分比例,y 是 28d 抗压强度
|
||||
X = df[["cement", "fly_ash", "slag", "water", "sand", "stone"]].values
|
||||
y = df["strength_28d"].values
|
||||
|
||||
# Pipeline 把预处理和模型绑死 —— 反模式见末尾
|
||||
pipe = Pipeline([
|
||||
("scaler", StandardScaler()), # 线性 / 距离类模型必须;树模型可省
|
||||
("model", RandomForestRegressor(n_estimators=300, random_state=42)),
|
||||
])
|
||||
|
||||
# 数据小(< 200)用 5-fold CV,不要单次 train/test
|
||||
from sklearn.model_selection import cross_val_score
|
||||
scores = cross_val_score(pipe, X, y, cv=5, scoring="r2")
|
||||
print(f"R2 mean={scores.mean():.3f} std={scores.std():.3f}")
|
||||
|
||||
# 训完看特征重要性
|
||||
pipe.fit(X, y)
|
||||
imp = pipe.named_steps["model"].feature_importances_
|
||||
for name, val in sorted(zip(df.columns, imp), key=lambda t: -t[1]):
|
||||
print(f"{name}: {val:.3f}")
|
||||
```
|
||||
|
||||
### B. 显著性分析(statsmodels,要 p-value)
|
||||
|
||||
```python
|
||||
import statsmodels.formula.api as smf
|
||||
|
||||
# formula API 让模型 spec 看起来跟 R 一样
|
||||
model = smf.ols(
|
||||
formula="strength_28d ~ cement + fly_ash + slag + water + water:cement", # 含交互
|
||||
data=df,
|
||||
).fit()
|
||||
print(model.summary()) # 系数、std err、t、p>|t|、95% CI 全在这
|
||||
|
||||
# ANOVA 比较两个嵌套模型
|
||||
m1 = smf.ols("strength_28d ~ cement + fly_ash", data=df).fit()
|
||||
m2 = smf.ols("strength_28d ~ cement + fly_ash + slag", data=df).fit()
|
||||
from statsmodels.stats.anova import anova_lm
|
||||
print(anova_lm(m1, m2)) # slag 加进去显著吗
|
||||
```
|
||||
|
||||
### C. DoE 响应面(statsmodels 二次回归)
|
||||
|
||||
```python
|
||||
# 中心复合设计后,做二阶模型
|
||||
model = smf.ols(
|
||||
formula="strength ~ x1 + x2 + x3 + I(x1**2) + I(x2**2) + I(x3**2) + x1:x2 + x1:x3 + x2:x3",
|
||||
data=df,
|
||||
).fit()
|
||||
print(model.summary())
|
||||
# 看哪些二阶项 / 交互项显著,定优化方向
|
||||
```
|
||||
|
||||
### D. 小样本贝叶斯(PyMC,N<30 要不确定度)
|
||||
|
||||
```python
|
||||
import pymc as pm
|
||||
import arviz as az
|
||||
|
||||
with pm.Model() as model:
|
||||
# 系数先验:弱信息正态
|
||||
intercept = pm.Normal("intercept", mu=0, sigma=10)
|
||||
beta = pm.Normal("beta", mu=0, sigma=5, shape=X.shape[1])
|
||||
sigma = pm.HalfNormal("sigma", sigma=10)
|
||||
|
||||
mu = intercept + pm.math.dot(X, beta)
|
||||
y_obs = pm.Normal("y_obs", mu=mu, sigma=sigma, observed=y)
|
||||
|
||||
trace = pm.sample(2000, tune=1000, chains=4, target_accept=0.95)
|
||||
|
||||
az.summary(trace) # 后验均值 / std / 94% HDI
|
||||
az.plot_posterior(trace) # 后验分布图
|
||||
```
|
||||
|
||||
### E. 配方聚类找异常实验(sklearn KMeans / DBSCAN)
|
||||
|
||||
```python
|
||||
from sklearn.cluster import DBSCAN
|
||||
from sklearn.preprocessing import StandardScaler
|
||||
|
||||
Xs = StandardScaler().fit_transform(X)
|
||||
labels = DBSCAN(eps=0.5, min_samples=5).fit_predict(Xs)
|
||||
# labels == -1 的点 = 离群,可能是录入错或工艺异常,人工核实
|
||||
```
|
||||
|
||||
## 反模式
|
||||
|
||||
### sklearn
|
||||
|
||||
1. **预处理在 pipeline 外** —— 交叉验证会数据泄漏(test fold 的统计量被 train 看到)
|
||||
2. **scaler 在 test 上 fit** —— 一律 `fit(train)` / `transform(test)`,Pipeline 自动保证
|
||||
3. **分类任务忘 stratified split** —— 类别不平衡时 `train_test_split(..., stratify=y)`
|
||||
4. **不必要地 scale 树模型** —— RF / GBDT / XGBoost 不受单调变换影响
|
||||
5. **忽略 convergence warning** —— LogisticRegression / SVM 不收敛要加 `max_iter` 或 scale 特征
|
||||
6. **小数据集还 80/20 切** —— N<200 用 5-fold CV,N<50 用 LOOCV
|
||||
|
||||
### statsmodels
|
||||
|
||||
7. **直接读系数不看 condition number** —— `summary()` 末尾的 condition number > 30 = 严重共线性,系数解读无效
|
||||
8. **多重比较忘修正** —— 同时检验多个系数显著性时,用 Bonferroni / FDR(`statsmodels.stats.multitest`)
|
||||
9. **OLS 残差不检验** —— `model.resid` 不正态 / 异方差时,OLS 推断不可靠,改 robust SE(`fit(cov_type="HC3")`)
|
||||
|
||||
### PyMC
|
||||
|
||||
10. **不看 R-hat / ESS** —— `az.summary(trace)` 的 `r_hat` > 1.01 或 `ess_bulk` < 400 = 没收敛,加 tune / 调 target_accept,不能用
|
||||
11. **强先验当数据用** —— 先验定太窄会主导后验,先用弱信息先验试
|
||||
12. **PyMC 装包巨大** —— 仅小样本需要时才用,大样本(N>500)频率派结果一样可信且快 100 倍
|
||||
|
||||
### 通用
|
||||
|
||||
13. **没看数据分布就建模** —— 先 `df.describe()` + `df.hist()` 扫一遍,有 NaN / 量纲极差 / 长尾分布要先处理
|
||||
14. **R2 > 0.95 就开香槟** —— 大概率是数据泄漏 / 过拟合 / 训练集等于测试集,先查
|
||||
15. **预测新配方不报置信区间** —— 工程决策不能只给点估计,sklearn 用 `RandomForestRegressor` 的 tree-level prediction std,statsmodels / PyMC 直接给 CI / HDI
|
||||
16. **数据点不到 10 还硬上 ML** —— 告诉用户先做 DoE 扩样本,再建模
|
||||
|
||||
## 依赖
|
||||
|
||||
`requirements.txt` 加:
|
||||
```
|
||||
scikit-learn>=1.4.0
|
||||
statsmodels>=0.14.0
|
||||
pymc>=5.10.0 # 重,装包带 pytensor,可选
|
||||
arviz>=0.17.0 # PyMC 后验诊断
|
||||
```
|
||||
|
||||
装:`.venv/Scripts/pip install scikit-learn statsmodels`
|
||||
PyMC 单独装:`.venv/Scripts/pip install pymc arviz`(确认要做贝叶斯再装,首次装包 5 分钟起)
|
||||
Loading…
Reference in New Issue