物理修复(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 篇根因分析/验证文档
5.7 KiB
物理校验判据与 fort.7 解析(DCTS 权威)
参考源码:dcts/crates/common/src/conv_check.rs。物理性判定必须以这里的逻辑为准,不要用经验标准。
温度结构判据:check_temperature_structure
conv_check.rs:463。返回 TempStructCheckResult,valid = violations.is_empty()。三项全过才算物理:
- 表层 T < max_factor × Teff
surface_ratio = temps[0] / teff(temps[0]= 最浅深度 = 表层)surface_ratio > max_factor(默认 3.0)→ 违规teff <= 0→ ratio = +Inf → 必违规
- 每个深度 T ∈ [temp_floor, temp_ceiling]
- 默认 [10.0, 1.0e8] K
- 任一深度 T 越界 → 违规
- 无 NaN/Inf
- 非有限 T(NaN/Inf)→ 违规(被第 2 条的
is_finite()捕获)
- 非有限 T(NaN/Inf)→ 违规(被第 2 条的
报告首个违规深度。
物理参考(不是硬判据,仅辅助判断)
- Eddington 灰大气:
T(τ=0) ≈ 0.84 × Teff。60kK 模型表层应约 5 万 K。 - 生产基准:已收敛种子
t60000_g5.0_he-2_c-3_n-4_o-4.7表层约 0.78×Teff。 - 内部温度:无 1e7+ 异常隆起(如某修复前组合曾出现 6.2e9 K 内部尖峰)。
⚠️ 判据修正历史(为何只信 DCTS 三项)
- 旧经验"表层应 0.5–1.5×Teff":过严,曾误判物理模型为不物理(把表层 0.78×Teff 的好模型判死)。
- 旧经验"表层应 3–6 万 K":对 60kK 模型偏低,曾误判。
- 正确做法:只用"表层 < 3×Teff 且各深度 ∈ [10,1e8] 且无 NaN"三项硬判据,Eddington 0.84 仅作物理直觉参考。
fort.7 大气模型文件格式与解析
conv_check.rs:470-502,对应 TLUSTY 源 tlusty208.f:14066-14108(OUTPUT 子程序):
第 1 行: ND NUMPAR
ND = 深度点数; NUMPAR = 每深度参数数 = NLEVEL + NUMLT
接下来 ceil(ND/6) 行: DM 列阵(质量深度),每行 6 个值(FORMAT 502)
接下来 ND 个深度块: 每块 ceil(NUMPAR/5) 行(FORMAT 503,每行 5 值)
每块【第一行第一个 token】= 该深度的温度 TEMP(ID)
块内其余 = 电子密度、质量密度、NLTE 能级布居/ departure 系数
temps[0] = 表层(ID=1,最浅);temps[nd-1] = 底层(最深)。
解析算法(check_temperature.py 复刻)
1. 读第1行 → nd, numpar = 前两个 token。任一为 0 → 跳过(返回 None,不判失败)
2. dm_lines = (nd + 5) // 6 # 跳过 DM 列阵
block_lines = (numpar + 4) // 5 # 每深度块行数
3. 跳过 dm_lines 行
4. 对 nd 个深度,每个:
取块第一行第一个 token,parse_fortran_float → temps[id]
跳过 block_lines - 1 行
5. 检查三层判据
parse_fortran_float(conv_check.rs:44)兼容 Fortran 无-E 记数法:指数 ≥100 时 gfortran 挤掉 E,如 -1.35E+118 输出成 -1.35+118。解析:先标准 parse;失败则正则 ^([+-]?[\d.]+)([+-]\d+)$ 在尾符号前插 E 再 parse;溢出返回 ±Inf。
参考实现见
conv_check.rs:1339 make_dot7(temps)—— 构造测试用 fort.7 的辅助函数(写ND NUMPAR=3,一行 DM 占位,每深度一行T 1.0E+10 1.0E-15)。
收敛轨迹判据:check_fort9
conv_check.rs:68。解析 fort.9(迭代收敛记录)。
fort.9 格式
RELATIVE CHANGES OF VECTOR PSI
ITER ID TEMP NE POP RAD MAXIMUM ilev ifr
1 50 -8.46E-04 1.53E-03 2.51E-02 -2.20E-03 2.51E-02 74 1
1 49 ...
...
2 50 ...
每行 = 一次迭代在一个深度点的相对变化:ITER(迭代号) ID(深度) TEMP NE POP RAD MAXIMUM(本次迭代本深度的最大相对变化) ilev ifr。
判据
- 每次 ITER 的
max_relc= 该 ITER 所有深度行|MAXIMUM|的最大值 - 末次迭代的
max_relc= 整个阶段的结果 - 收敛 =
max_relc < chmax(默认 0.001)且is_finite() - 单调下降 = 健康;突然涨几个数量级 = 雪崩发散
chmax <= 0或非有限 = 非法配置(返回错误,max_relc=Inf)
STOP 检测(fort.6)
fort.6(stdout)里的 **** STOP in SOLVE after ITER N = 求解器发散中止。conv_check.rs 用 SOLVER_STOP_RE 匹配。这是比"非零 rc"更具体的发散信号。
NaN / 伪收敛检测
NaN 匹配模式(与 Rust 一致)
conv_check.rs:18 NAN_RE_PATTERN = (?i)(\bnan\b|\binf(?:inity)?\b|\*{3,}|[eE]\+(?:3\d{2}|[4-9]\d{2,}))
匹配:nan / inf / infinity / ***(3+星) / 正指数 E+300±200 量级但温度/流量场溢出用 E+999(超高正指数视同数值无效,因发散时布居数变化可达 ±100*** 或 E+300+)。
相关函数:
atmosphere_has_nan(L246):扫 fort.7 全部数值 tokenspec_is_valid(L290):扫光谱文件,同正则
伪收敛陷阱(必查)
max_relc 极低 ≠ 收敛。若模型已 NaN,在 NaN 上迭代的相对变化为 0,会伪装成 max_relc≈0 的"收敛"。
正确流程(每次事后审查都要做):
check_fort9.py看轨迹 + STOP + max_relccheck_temperature.py看温度结构三项(含 NaN 检查)- 两者都过才算真收敛;仅 fort.9 看着收敛但 fort.7 有 NaN = 伪收敛
其他 DCTS 校验(了解即可)
conv_check.rs 还提供(生产链用,测试时按需):
check_energy_conservation(L374):能量守恒check_emflux_bolometric(L578):辐射通量check_convergence_trace(L691):收敛轨迹综合check_bfactor(L759):departure 系数(.bfac 文件,同样 ND NUMPAR + DM 头格式)extract_failure_hint(L851):失败原因提取
测试中常用前两类(温度结构 + 收敛轨迹)即可覆盖绝大多数判断。