Files
fmq b058e66722 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 篇根因分析/验证文档
2026-08-17 23:55:26 +08:00

213 lines
14 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
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.51.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 阶段=0nl 阶段=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.51.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=124timeout)感知**:三个 `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}/` |