物理修复(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 篇根因分析/验证文档
14 KiB
name, description
| name | description |
|---|---|
| tlusty-synspec-test | 规范直接运行 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/ 的输入生成器与物理校验逻辑,统一成一套可执行流程。
核心原则(违反这些 = 测试无效)
- 测试必须建在
dcts/test/下(已被.gitignore排除,不入库)。禁止在仓库根、data/runtime/、共享目录直接跑二进制。 - 二进制优先用
dcts/assets/tlusty_static与dcts/assets/synspec_static——它们比data/runtime/下的旧版新,且是生产链实际使用的版本。data/runtime/的旧版仅用于历史对照。 - 物理性判定必须用 DCTS 权威判据
check_temperature_structure(见 §事后审查),不要用"表层应 0.5–1.5×Teff"等经验标准——后者曾误判物理模型为不物理。 - 执行前必须检查输入(见 §执行前检查)。一次输入错误会浪费一次 20 分钟级的计算。
- 执行后必须回头审查,不能只看"有没有 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 .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_divergencetype∈convergence | physics | synspec | validationgrid=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' 这类带空格引号路径拆碎,破坏输入(实测踩坑)。
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 等),其余一致——这是对照有效的前提。
python3 .agents/skills/tlusty-synspec-test/scripts/diff_configs.py dirA/inputs dirB/inputs --teff 60000
第四步:执行前检查(必做,用脚本)
跑计算前,用校验脚本确认输入无误:
# 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/ 拷好。直接:
cd dcts/test/{test_id}/{type}/{grid}/{content}
bash scripts/run.sh # 可选参数:每阶段超时秒数(默认 1200)
模板 run.sh 已内置规范行为(assets/run_cold_chain.sh 等):
- 切到
run/,复制inputs/*进去,建data软链 - 逐阶段执行,每阶段
timeout 1200 tlusty_static < stage.5 > fort.6 2> fort.14 - 每阶段产物立即按阶段重命名:
fort.6→{stage}.6、fort.7→{stage}.7、fort.9→{stage}.9、fort.14→{stage}.14(避免被下阶段覆盖) - 阶段间种子传递:下一阶段
fort.8= 上一阶段fort.7的复制;冷启动链 lte 阶段删fort.8 - 归档到
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 结束即可放心审查。
# 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)三项全过才算物理:
- 表层 T < 3 × Teff(
surface_ratio = T[0]/Teff,> 3 判不物理) - 每个深度 T ∈ [10, 1e8] K
- 无 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}/ |