--- 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}/` |