feat(all): NaN 伪收敛否决与 nl_tight 能量回退、emflux 检验 4π 修正、nst 行宽与种子边界修复、gfATO 谱线表接通与网格加密 9216 点、阶段分项统计与跳板机部署
物理修复(400 失败点归因,见 docs/failed400_nan_pseudo_convergence_2026_08_17.md): - runner: 阶段 converged 后复查 fort.7,含 NaN/Inf 即否决(fort.9 全零伪收敛, 曾致 261 点误跳过 nl_direct 回退);否决阶段不产出种子,阻断污染传播 - runner: nl_tight 回退——仅能量边际失败时以 CHMAX 收紧 10× 从自身模型续迭代, 残差降幅 ~10×;execute_tlusty_stage 抽取供主链与回退共用 - conv_check: fort.14 为 Eddington 通量 Hλ,积分需乘 4π 再比 σTeff⁴ (旧版 ratio 稳定 0.0796=1/4π,全点系统性假阳性)+ 回归测试 - nst_writer: 单行超 80 字符被 TLUSTY 静默截断,IFALI/JALI/TRAD 等从未生效; 按 75 字符自动换行 - seed_finder: Teff 容忍度改含边界 <=,相邻 5000K 档恢复互为种子 + 回归测试 谱线表与网格: - 默认线表 gfVIS99 → gfATO(全波段 18-23000Å),TaskSpec.linelist 支持工作流 级覆盖,节点按需下载(进程互斥锁防并发重复下载 238MB) - sdB_cno Teff 加密至 5000K 步长,432 → 9216 点;tlusty/synspec 静态二进制更新 统计与部署: - grid 汇总改按 tlusty_status/synspec_status 分项计数,新增 tlusty_failed/ synspec_failed/synspec_pending,前端详情页双视图适配 - deploy/fetch_results 支持跳板机 ProxyJump 与 SSH 主连接复用,fetch 新增 --force; - Docker 构建支持 CARGO_MIRROR/USE_MIRRORS 国内镜像参数;移除 tools/ 拷贝 - 新增 tlusty-synspec-test skill 与 6 篇根因分析/验证文档
This commit is contained in:
@@ -0,0 +1,212 @@
|
||||
---
|
||||
name: tlusty-synspec-test
|
||||
description: 规范直接运行 TLUSTY / SYNSPEC 二进制做物理验证测试的全流程——目录脚手架、输入文件生成与校验、冷启动链(lte→nc→nl)/种子步进链(seed_nc→nl)/SYNSPEC 执行、以及基于 DCTS 权威判据的事后物理审查。只要用户要在 DCTS 框架之外直接跑 tlusty_static / synspec_static 做收敛性、发散复现、物理解、光谱合成、fort.7/fort.9 分析、冷启动/种子链、网格点验证等测试,或在 dcts/test/ 下建测试目录,就必须用本 skill——即使用户没说"测试流程"四个字。
|
||||
---
|
||||
|
||||
# TLUSTY / SYNSPEC 物理测试流程
|
||||
|
||||
本 skill 把 `docs/testing_workflow_2026_08_11.md` 的规范、源码侧 `tests/{tlusty,synspec}/` 的官方 Runtest 结构、以及 DCTS `crates/common/src/` 的输入生成器与物理校验逻辑,统一成一套可执行流程。
|
||||
|
||||
## 核心原则(违反这些 = 测试无效)
|
||||
|
||||
1. **测试必须建在 `dcts/test/` 下**(已被 `.gitignore` 排除,不入库)。禁止在仓库根、`data/runtime/`、共享目录直接跑二进制。
|
||||
2. **二进制优先用 `dcts/assets/tlusty_static` 与 `dcts/assets/synspec_static`**——它们比 `data/runtime/` 下的旧版新,且是生产链实际使用的版本。`data/runtime/` 的旧版仅用于历史对照。
|
||||
3. **物理性判定必须用 DCTS 权威判据** `check_temperature_structure`(见 §事后审查),不要用"表层应 0.5–1.5×Teff"等经验标准——后者曾误判物理模型为不物理。
|
||||
4. **执行前必须检查输入**(见 §执行前检查)。一次输入错误会浪费一次 20 分钟级的计算。
|
||||
5. **执行后必须回头审查**,不能只看"有没有 STOP"。有 STOP 但模型含 NaN = 非物理;无 STOP 但温度结构不物理 = 仍未完成。
|
||||
|
||||
## 第一步:判断测试类型(决策树)
|
||||
|
||||
先问用户(或从任务推断)属于哪类,再去读对应的 reference:
|
||||
|
||||
| 用户在做什么 | 测试类型 | 链类型 | 读哪个 reference |
|
||||
|---|---|---|---|
|
||||
| 从零算一个网格点的大气(LTE 灰化起步 → NLTE 连续 → NLTE 谱线) | `convergence`/`physics` | **冷启动链** lte→nc→nl | `references/tlusty-stages.md` |
|
||||
| 从一个**已收敛邻居模型**热启动算新网格点 | `convergence` | **种子步进链** seed_nc→nl | `references/tlusty-stages.md` |
|
||||
| 复现生产的发散/失败,对比生产 DB 轨迹 | `validation` | 冷启动或种子链(看生产记录) | `references/tlusty-stages.md` + `physics-checks.md` |
|
||||
| 给定一个收敛大气(fort.7),合成光谱 | `synspec` | SYNSPEC 单步 | `references/synspec-inputs.md` |
|
||||
|
||||
> 区分冷启动 vs 种子链的关键:冷启动 lte 阶段 `ltgray=T`,会**删除 fort.8**从灰大气重建;种子链第一阶 `seed_nc` 把**邻居的 fort.7 复制成 fort.8** 热启动。如果输入里有 `fort.8`(邻居种子)→ 种子链;如果没有、从 lte 开始 → 冷启动链。
|
||||
|
||||
## 第二步:建目录(用脚手架脚本)
|
||||
|
||||
四级目录结构(详见 `references/directory-convention.md`):
|
||||
|
||||
```
|
||||
dcts/test/{test_id}/{type}/{grid}/{content}/
|
||||
├── inputs/ # 执行前生成并检查的全部输入
|
||||
├── scripts/ # run.sh(必有)
|
||||
├── run/ # 执行工作目录,fort.* 在此生成
|
||||
└── outputs/ # 阶段产物(按阶段重命名)+ RESULT.md
|
||||
```
|
||||
|
||||
**直接用脚手架,不要手敲 mkdir**:
|
||||
|
||||
```bash
|
||||
bash .agents/skills/tlusty-synspec-test/scripts/new_test.sh \
|
||||
20260813_my_purpose convergence t60000_g5.0_he-2_cold baseline
|
||||
```
|
||||
|
||||
`new_test.sh` 会创建四级目录、按链类型拷贝对应 `assets/run_*.sh` 模板为 `scripts/run.sh`、放一个 README 骨架。它会询问链类型(cold/seed/synspec)来选模板。
|
||||
|
||||
### 命名规范
|
||||
|
||||
- `test_id` = `{日期}_{目的}`,如 `20260813_tlusty_divergence`
|
||||
- `type` ∈ `convergence | physics | synspec | validation`
|
||||
- `grid` = `t{teff}_g{logg}_he{loghe}`,冷启动链加 `_cold`、种子链加 `_seed`(如 `t60000_g5.0_he-2_cold`、`t55000_g5.0_he-4_seed`)
|
||||
- `content` = 描述测试变量,如 `baseline` / `itek0_iacc0_dpsilg15` / `niter50` / `nd70`
|
||||
|
||||
## 第三步:生成输入文件
|
||||
|
||||
**优先级**:有 DCTS 代码生成器就用代码生成(保证与生产一致) > 无生成器则从已知正确模板 sed/python 改(必须留 diff 或注释) > 绝不凭空手写。
|
||||
|
||||
### 各链需要的输入(速查)
|
||||
|
||||
| 链 | inputs/ 文件 |
|
||||
|---|---|
|
||||
| 冷启动链 | `lte.5` `nc.5` `nl.5` `lte.nst` `nc.nst` `nl.nst`(**无 fort.8**) |
|
||||
| 种子链 | `seed_nc.5` `seed_nc.nst` `fort.8`(邻居收敛模型的 `.7`);若跑到 nl 还需 `nl.5` `nl.nst` |
|
||||
| SYNSPEC | `fort.5` `fort.55` `fort.8`(收敛大气)+ `fort.19`/`data` 软链 |
|
||||
|
||||
各文件的逐字段含义、各阶段差异、ilvlin/DPSILG/ORELAX/NITER 默认值——见 `references/tlusty-stages.md`(TLUSTY)与 `references/synspec-inputs.md`(SYNSPEC fort.55 九行)。
|
||||
|
||||
**关键不变量**(违反即输入错误,先记这几条):
|
||||
- 冷启动链 `.5` 第 2 行:`lte.5`=`T T`,`nc.5`/`nl.5`=`F F`
|
||||
- ions 行 `ilvlin`:lte/nc 阶段=0,nl 阶段=100,**裸核(nlevs=1)永远=0**
|
||||
- `.5` 第 3 行的 `'nst'` 文件名必须与实际 nst 文件名一致(生产默认就是字面量 `nst`)
|
||||
- nst 第 1 行含 `NITER=`;第 2 行可含 `ORELAX=/IACC=/ICHANG=/IELCOR=`;DPSILG 通过 escape hatch 注入(默认无,生产常用 `DPSILG=1.5` 抑制雪崩)
|
||||
- `data/` 软链必须指向 `assets/data/`(133 个原子数据文件)
|
||||
|
||||
### 阶段变换与 A/B 对照
|
||||
|
||||
**nc.5 → nl.5**(阶段变换,ilvlin 0→100)用 `scripts/stage_transform.py`,它字节级安全地改写离子行的 ilvlin 列(裸核保持 0)并按需改第 2 行 LTE/LTGRAY。**不要手写 awk 做这事**——awk 按空格切分会把 `'data/h1.dat'` 这类带空格引号路径拆碎,破坏输入(实测踩坑)。
|
||||
|
||||
```bash
|
||||
python3 .agents/skills/tlusty-synspec-test/scripts/stage_transform.py inputs/nc.5 -o inputs/nl.5 --to nl --diff
|
||||
```
|
||||
|
||||
**A/B 测试**:同 `{grid}` 下放多个 `{content}` 目录(baseline / 变体),生成后必须用 `scripts/diff_configs.py` 确认各组**只有你刻意改的参数不同**(带 `*` 的行应只有 ITEK/ORELAX/NITER 等),其余一致——这是对照有效的前提。
|
||||
|
||||
```bash
|
||||
python3 .agents/skills/tlusty-synspec-test/scripts/diff_configs.py dirA/inputs dirB/inputs --teff 60000
|
||||
```
|
||||
|
||||
## 第四步:执行前检查(必做,用脚本)
|
||||
|
||||
跑计算前,用校验脚本确认输入无误:
|
||||
|
||||
```bash
|
||||
# TLUSTY 链:校验 .5 第2行/ions、nst 关键参数、软链
|
||||
python3 .agents/skills/tlusty-synspec-test/scripts/check_inputs.py \
|
||||
path/to/{content}/inputs --chain cold --teff 60000
|
||||
```
|
||||
|
||||
`check_inputs.py` 会检查上面"关键不变量"那几条。**建议再做一次冒烟**(`timeout 30` 跑 lte 阶段),确认二进制能启动、fort.6 里回显的 NITER/ITEK/DPSILG 与输入一致,再全量跑。
|
||||
|
||||
## 第五步:执行(用 run.sh 模板)
|
||||
|
||||
每个测试目录的 `scripts/run.sh` 已由 `new_test.sh` 从 `assets/` 拷好。直接:
|
||||
|
||||
```bash
|
||||
cd dcts/test/{test_id}/{type}/{grid}/{content}
|
||||
bash scripts/run.sh # 可选参数:每阶段超时秒数(默认 1200)
|
||||
```
|
||||
|
||||
模板 run.sh 已内置规范行为(`assets/run_cold_chain.sh` 等):
|
||||
1. 切到 `run/`,复制 `inputs/*` 进去,建 `data` 软链
|
||||
2. **逐阶段执行**,每阶段 `timeout 1200 tlusty_static < stage.5 > fort.6 2> fort.14`
|
||||
3. **每阶段产物立即按阶段重命名**:`fort.6→{stage}.6`、`fort.7→{stage}.7`、`fort.9→{stage}.9`、`fort.14→{stage}.14`(避免被下阶段覆盖)
|
||||
4. **阶段间种子传递**:下一阶段 `fort.8` = 上一阶段 `fort.7` 的复制;冷启动链 lte 阶段删 `fort.8`
|
||||
5. 归档到 `outputs/`
|
||||
|
||||
> **为什么按阶段重命名是关键**:TLUSTY 每次都写同名 `fort.7`/`fort.9`,不重命名的话 lte 的产物会被 nc 覆盖,事后无法分阶段审查。官方 `RTlusty` 脚本用 `{model}.{ext}` 命名(如 `hhe35lt.7`)解决同样问题;我们用 `{stage}.7` 等价。
|
||||
|
||||
## 第六步:事后审查(必做,用脚本 + 判据)
|
||||
|
||||
执行完,用脚本做数值与物理审查,**不要只看 rc=0**。
|
||||
|
||||
> ⚠️ **审查时机**:**必须等进程完全退出后再审查**(`bash scripts/run.sh` 完成、或 `pgrep -f tlusty_static` 为空)。TLUSTY 边算边写 fort.9——运行中实时读,最新一拍的迭代行是半写状态,max_relc 不可信(实测踩坑:运行中读到 iter6=0.477 貌似收敛,进程退出后同一迭代实为 1060)。run.sh 里每个阶段是串行的(一个阶段跑完才进下一阶段),所以等 run.sh 结束即可放心审查。
|
||||
|
||||
```bash
|
||||
# 1. 收敛轨迹:解析 {stage}.9 的 max_relc 演化、检测 STOP/NaN。
|
||||
# --expected-niter N 传 nst 里配的 NITER:若末次 iter 远小于 N 且未收敛,
|
||||
# 会提示"疑似 timeout 截断"(轨迹不完整,结论需谨慎)。rc=124 时的
|
||||
# outputs/{stage}.TIMEOUT 标记也会被自动发现。
|
||||
python3 .agents/skills/tlusty-synspec-test/scripts/check_fort9.py outputs/nl.9 --teff 60000 --expected-niter 100
|
||||
|
||||
# 2. 温度结构物理性(DCTS 权威判据,复刻 conv_check.rs:463)
|
||||
python3 .agents/skills/tlusty-synspec-test/scripts/check_temperature.py outputs/nl.7 --teff 60000
|
||||
```
|
||||
|
||||
### 收敛判据(数值)
|
||||
- `{stage}.9` 每次 ITER 跨所有深度的 `max_relc` = `|MAXIMUM|` 列最大值
|
||||
- **收敛** = 末次迭代 `max_relc < chmax`(默认 `0.001`)且有限
|
||||
- 单调下降 = 好;雪崩(突然涨几个数量级)= 发散
|
||||
- `{stage}.6` 里的 `**** STOP in SOLVE after ITER N` = 求解器发散中止
|
||||
|
||||
### 物理判据(DCTS 权威,必须用这套)
|
||||
`check_temperature_structure`(`conv_check.rs:463`)三项全过才算物理:
|
||||
1. **表层 T < 3 × Teff**(`surface_ratio = T[0]/Teff`,> 3 判不物理)
|
||||
2. **每个深度 T ∈ [10, 1e8] K**
|
||||
3. **无 NaN/Inf**
|
||||
|
||||
物理参考(不是硬判据):Eddington 灰大气 `T(τ=0) ≈ 0.84×Teff`(60kK 模型表层应约 5 万 K);已收敛生产种子 `t60000_g5.0_he-2_c-3_n-4_o-4.7` 表层约 0.78×Teff 可作基准。
|
||||
|
||||
> ⚠️ **判据修正历史**:旧经验"表层 0.5–1.5×Teff"过严(误判物理模型为不物理);"表层 3–6 万 K"对 60kK 偏低(误判)。**只信 DCTS 三项判据**。详见 `references/physics-checks.md`。
|
||||
|
||||
### 伪收敛陷阱
|
||||
`max_relc` 极低 ≠ 收敛。若模型已 NaN,在 NaN 上迭代相对变化为 0,会伪装成收敛。务必同时跑温度结构检查 + NaN 检查。NaN 匹配模式(与 Rust 一致):`nan`/`inf`/`infinity`/`***`(3+星)/正指数 `E+300`+。
|
||||
|
||||
### 测试不算完成的情况(需继续)
|
||||
- 有 STOP 但模型含 NaN(非物理)
|
||||
- 无 STOP 但温度结构不物理
|
||||
- 无 STOP 且 `max_relc` 未达 0.001
|
||||
|
||||
## 第七步:记录结论(用 RESULT 模板)
|
||||
|
||||
在 `outputs/` 写 `RESULT.md`(模板见 `assets/result_template.md`),诚实区分:
|
||||
- **数值收敛**(max_relc 达标)vs **物理收敛**(温度结构三项全过)——只有后者才是真正成功
|
||||
- 记录:测试配置、各阶段迭代/末 max_relc/STOP/NaN/温度结构表、审查结论
|
||||
|
||||
完整的 README/RESULT 字段、阶段产物重命名表、目录示例——见 `references/directory-convention.md`。
|
||||
|
||||
---
|
||||
|
||||
## 何时读哪个 reference
|
||||
|
||||
| 想知道 | 读 |
|
||||
|---|---|
|
||||
| `.5` 每行含义、各阶段(lte/nc/nl)参数差异、ilvlin/DPSILG/ORELAX/NITER 默认、冷启动 vs 种子链、fort.7↔fort.8 传递 | `references/tlusty-stages.md` |
|
||||
| fort.55 九行控制卡逐行、fort.5/fort.8/fort.19、SYNSPEC 产物(.spec/.cont/.iden)、NaN 修复背景 | `references/synspec-inputs.md` |
|
||||
| `check_temperature_structure` 完整判据、fort.7 解析算法(`(nd+5)/6` 行 DM、每块 `(numpar+4)/5` 行)、NaN/伪收敛/STOP、判据修正史 | `references/physics-checks.md` |
|
||||
| 四级目录结构、命名规范、阶段文件重命名表、README/RESULT 模板 | `references/directory-convention.md` |
|
||||
|
||||
## 脚本速查
|
||||
|
||||
| 脚本 | 作用 |
|
||||
|---|---|
|
||||
| `scripts/new_test.sh` | 脚手架:建四级目录 + 拷 run.sh 模板 + README 骨架 |
|
||||
| `scripts/check_inputs.py` | 执行前校验 `.5`/nst/软链(TLUSTY 链) |
|
||||
| `scripts/check_fort9.py` | 解析 `{stage}.9` 收敛轨迹、max_relc 演化、STOP/NaN;`--expected-niter N` 检测 timeout 截断 |
|
||||
| `scripts/check_temperature.py` | 复刻 `check_temperature_structure`,对 `{stage}.7` 做温度结构物理判定 |
|
||||
| `scripts/stage_transform.py` | 冷启动链 `.5` 阶段变换:`nc.5 → nl.5`(非裸核 ilvlin 0→100,字节级安全,避免手写 awk 踩坑) |
|
||||
| `scripts/diff_configs.py` | A/B 测试配置对照:并排显示多组 nst/.5 参数,`*` 标出不一致项(应只有你刻意改的字段带 `*`) |
|
||||
|
||||
**rc=124(timeout)感知**:三个 `run_*.sh` 模板在阶段被 `timeout` 杀死时会写 `outputs/{stage}.TIMEOUT` 标记并打印告警;`check_fort9.py` 会自动发现该标记(或用 `--expected-niter` 推断截断),在结论里提示"轨迹不完整,结论需谨慎"。**不要把 timeout 截断的轨迹当成完整发散/收敛结论**。
|
||||
|
||||
所有脚本都自定位 skill 目录,可从任意测试目录调用,传相对路径即可。
|
||||
|
||||
## 固定路径速查
|
||||
|
||||
| 项 | 路径 |
|
||||
|---|---|
|
||||
| 测试根 | `dcts/test/` |
|
||||
| TLUSTY 二进制 | `dcts/assets/tlusty_static` |
|
||||
| SYNSPEC 二进制 | `dcts/assets/synspec_static` |
|
||||
| 原子数据 | `dcts/assets/data/`(133 文件,运行时 `data` 软链指此) |
|
||||
| 全波段线表 | `dcts/assets/data/gfATO.dat`(SYNSPEC `fort.19` 软链) |
|
||||
| 物理判据源码 | `dcts/crates/common/src/conv_check.rs`(`check_temperature_structure` @ L463、`check_fort9` @ L68) |
|
||||
| 输入生成器源码 | `dcts/crates/common/src/{gen_input5,nst_writer,fort55_writer}.rs` |
|
||||
| 链定义源码 | `dcts/crates/common/src/runner.rs`(`default_cold_chain` @ L20、`default_seed_chain` @ L67) |
|
||||
| 流程规范文档 | `dcts/docs/testing_workflow_2026_08_11.md` |
|
||||
| 源码侧参考测试 | `tests/tlusty/{hhe,cwd,disk,bstar,optab}/`、`tests/synspec/{hhe,bstar,kurucz,hybrid,pseudonlt,optab}/` |
|
||||
Reference in New Issue
Block a user