# 物理校验判据与 fort.7 解析(DCTS 权威) 参考源码:`dcts/crates/common/src/conv_check.rs`。**物理性判定必须以这里的逻辑为准**,不要用经验标准。 ## 温度结构判据:`check_temperature_structure` `conv_check.rs:463`。返回 `TempStructCheckResult`,`valid = violations.is_empty()`。三项全过才算物理: 1. **表层 T < max_factor × Teff** - `surface_ratio = temps[0] / teff`(`temps[0]` = 最浅深度 = 表层) - `surface_ratio > max_factor`(默认 **3.0**)→ 违规 - `teff <= 0` → ratio = +Inf → 必违规 2. **每个深度 T ∈ [temp_floor, temp_ceiling]** - 默认 **[10.0, 1.0e8] K** - 任一深度 T 越界 → 违规 3. **无 NaN/Inf** - 非有限 T(NaN/Inf)→ 违规(被第 2 条的 `is_finite()` 捕获) 报告**首个**违规深度。 ### 物理参考(不是硬判据,仅辅助判断) - **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`~`E+999`(超高正指数视同数值无效,因发散时布居数变化可达 ±100~±200 量级但温度/流量场溢出用 `***` 或 `E+300+`)。 相关函数: - `atmosphere_has_nan`(L246):扫 fort.7 全部数值 token - `spec_is_valid`(L290):扫光谱文件,同正则 ### 伪收敛陷阱(必查) `max_relc` 极低 ≠ 收敛。若模型已 NaN,**在 NaN 上迭代的相对变化为 0**,会伪装成 max_relc≈0 的"收敛"。 **正确流程**(每次事后审查都要做): 1. `check_fort9.py` 看轨迹 + STOP + max_relc 2. `check_temperature.py` 看温度结构三项(含 NaN 检查) 3. **两者都过才算真收敛**;仅 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):失败原因提取 测试中常用前两类(温度结构 + 收敛轨迹)即可覆盖绝大多数判断。