Files
DCTS/docs/fort14_nan_analysis.md
T
fmq 43b82b1ae2 feat(all): 物理正确性五重硬门槛、输入文件结构化与 fort.55 错位修复、conv 诊断 DB 化与阶段归因修复、ORELAX 收敛修复与导入工具下线
物理正确性校验体系(common/conv_check.rs +494 行)
- 新增 5 类硬门槛:能量守恒(.6)、温度结构(.7)、emflux 积分校验(.emflux,含全 NaN 判失败)、假收敛排查(itek 轨迹首末比)、b 因子合理性(.bfac)
- runner 在 TLUSTY 阶段结束后执行全部校验,任一失败判 final_converged=false
- GridConfig 新增 8 个可配阈值,经 scheduler→executor→runner 全链路透传

输入文件配置结构化重构(config.rs +1453 行)
- TlustyInput 拆为 dot5/nst 分层结构,字段名严格映射 tlusty208.f READ 语句;SynspecInput 重构为 9 个 Fort55Line 子结构体
- 移除 ChainStep.metals 字段,元素集改由 dot5.atoms/ions 显式声明(gen_input5/nst_writer 同步重写为三源融合 / 分层覆盖)
- fort.55 修复行结构 bug:补全分子表行(7→9 行),IDSTD 50→0 错位修正(影响全部光谱线强归一化,需重算 SYNSPEC 阶段)

conv 诊断 DB 化与阶段归因修复(server)
- 单点详情 conv 面板从磁盘 conv.json 改读 DB grid_points.summary_json;grid_points 新增 summary_json/last_elapsed_sec 两列(旧库幂等 ALTER)
- record_task_report 阶段归因列加 CASE 守卫 + clear_synspec 对称处理,修复 synspec-only/TLUSTY-only 重跑污染统计
- 新增 summary_merge.rs 点级增量合并,避免重跑覆盖诊断字段

收敛性 ORELAX 修复与 seed_chain 可配(sdB_cno.yaml + node)
- nl 阶段加 orelax=0.5、seed_nc 加 orelax=0.3,阻尼中温区 relc 振荡发散
- seed_chain 块可配,executor 优先采用用户配置而非内置默认链

导入工具下线
- 删除 import_results 客户端工具及 Windows 推送脚本;移除 /admin/import_seed 端点
- 改为服务端临时 migrate_conv 端点(扫 conv.json 增量合并入库,迁移后可删)

文档与分析
- 新增 1305 失败点根因分析、fort.14 全 NaN 物理含义分析两份深度文档
- spectrum_correctness_analysis 两次修订标注已修复项;fetch_results.sh 修 trap RETURN 的 set -u 报错
2026-08-09 12:09:48 +08:00

12 KiB
Raw Blame History

fort.14.emflux)全 NaN 现象分析

调研日期:2026-08-08 范围:TLUSTY 208 输出文件 fort.14(出射谱 λ-Fλ,runner 快照为 .emflux)中出现全 NaN 的物理与数值含义。 核心问题:.emflux 全 NaN 意味着什么?是否代表 TLUSTY 大气模型收敛失败?


0. TL;DR

.emflux 全文件 NaN = TLUSTY 大气模型结构(TEMP/POPUL/ELEC)数值发散到不可用程度的可靠标志。 源码机制与真实数据双重佐证:

  • 源码层面fort.14 的流量 FLUX 由辐射转移求解器(RTEFR1)基于大气结构(TEMP/POPUL)计算。大气发散 → 不透明度计算产生 Inf/0 → 源函数 Inf/Inf=NaN → 整列 FLUX 全 NaN → OUTPRI 在耗尽 NITER 上限后写进 fort.14。全 NaN 只能由大气发散引起,不存在"大气收敛良好但辐射转移单独失败"的路径
  • 数据层面162 个网格点中,62/62100%)的 emflux-NaN 点 converged=falseconverged=true 且 emflux 含 NaN 的点 = 0。没有任何局部 NaN 样本。

对校验设计的结论emflux bolometric 校验把"全 NaN 判失败"是正确的,语义应表述为"大气收敛失败(辐射转移求解发散)",而非仅"辐射转移失败"。它与 atmosphere_has_nan 同级、互为冗余,且能捕获后者的检测盲区。


1. fort.14 的写出机制

1.1 写出位置与格式

fort.14 由 SUBROUTINE OUTPRItlusty208.f:14156)写出,唯一活跃写出语句在 tlusty208.f:14188

14181      DO IJ=1,NFREQ
14182         IJP=IJ
14183         IF(ispodf.eq.0) IJP=JIK(IJ)
14184         IF(IJX(IJP).NE.-1) THEN
14185            WRITE(13,602) FREQ(IJP),FLUX(IJP),FH(IJP)
14186            TOTF=TOTF+FLUX(IJP)*W(IJP)
14187            FLAM=FLUX(IJP)*FREQ(IJP)*FREQ(IJP)/2.997925E18
14188            write(14,614) 2.997925e18/freq(ijp),slam
14189         END IF
14190      END DO
  • FORMAT 614 FORMAT(F15.3,1pe15.3)tlusty208.f:14406)—— 第一列波长 λ(Å),第二列 Fλ。
  • 写出变量FLUX(IJP)COMMON/SURFAC/MODELQ.FOR:135),不是局部量。FLAM 是从 Fν 到 Fλ 的换算(Fλ = Fν · ν² / c)。
  • 关键FLUX 是直接从 COMMON 块读已存的值,OUTPRI 本身不重新求解辐射转移

1.2 写出时机

OUTPRI 仅在 IF(LFIN) 块内被调用(tlusty208.f:3887-3889):

3887:      IF(LFIN) THEN
3888:         CALL OUTPRI
3889:      END IF

LFIN 的判定(tlusty208.f:47596):

47596:      LFIN=ABS(CHMX).LE.CHMAX.OR.ITER.GE.NITER

最后一次迭代(收敛 CHMX≤CHMAX 或耗尽迭代上限 ITER≥NITER)才写 fort.14。这意味着:fort.14 反映的是最终大气状态下的辐射转移解。


2. NaN 的产生链(源码追踪)

2.1 FLUX 的计算路径

FLUX(IJ) 的赋值有三处(都在辐射转移求解子程序中):

位置 子程序 语句
tlusty208.f:31800 RTEFR1Feautrier 形式解) FLUX(IJ)=AHout*half
tlusty208.f:32225 RTEDF2 FLUX(IJ)=AH
tlusty208.f:32848 RTEDF1 FLUX(IJ)=FH(IJ)*RAD1(1)-half*hextrd(ij)

这些在每次迭代的频率循环中被调用(如 tlusty208.f:4269-4271):

4269:      DO IJ=1,NFREQ
4270:         CALL OPACF1(IJ)     不透明度计算
4271:         CALL RTEFR1(IJ)     辐射转移求解

2.2 NaN 的具体产生点

第一步:不透明度计算 OPACF1tlusty208.f:4800)依赖大气结构

源码行 语句 发散时
tlusty208.f:4857 / :45375 HKT1(ID)=HK/TT=TEMP(ID) TEMP→0 时 HKT1→Inf
tlusty208.f:4858 XKF(ID)=EXP(-HKT1(ID)*FR) HKT1·FR 极大时下溢到 0
tlusty208.f:31444 / :33455 ABTRA(ITR,ID)=POPUL(II,ID) POPUL 含 NaN → ABTRA 含 NaN
tlusty208.f:31446 EMTRA(ITR,ID)=POPUL(JJ,ID)*ANE*SBF(II)*... POPUL 极大值 × 其他 → Inf

第二步:辐射转移 RTEFR1tlusty208.f:32265)源函数计算

源码行 语句 发散时
tlusty208.f:32352 ST0(ID)=EMIS1(ID)/AB0(ID) 两个无穷大相除 = NaN(典型源头)
tlusty208.f:32361 SS0(ID)=-SCAT1(ID)/AB0(ID) 同上

NaN 一旦进入 RAD1(辐射转移矩阵解),就通过 AH/AHout 传播到 FLUX(IJ),然后波及整列所有频率点。

2.3 为什么发散后不会提前 STOP

TLUSTY 有一个发散护栏(tlusty208.f:47587-47591):

47587:      IF(ITER.NE.1 .AND. ABS(CHMX).GT.1.D16) THEN
47588:         WRITE(6,610) ITER,CHMX
47590:         STOP
47591:      END IF

CHMX 是温度相对变化的最大值tlusty208.f:47468 CHT=CHANGT(ID)/TEMP(ID)),当 TEMP 含 NaN 时 CHMX 也变 NaN。而 NaN 与任何数比较都返回 .FALSE.

  • ABS(NaN).GT.1.D16 为假 → STOP 不触发(护栏对 NaN 失效)。
  • ABS(NaN).LE.CHMAX 为假 → LFIN 只能靠 ITER.GE.NITER(耗尽上限)成立。

因此程序一路跑到 NITER,然后带着含 NaN 的 TEMP/POPUL 走完最后一次频率循环,把整列 FLUX 全写成 NaN,最后 OUTPRI 把全 NaN 的 FLUX 倒进 fort.14


3. 两种 NaN 场景的区分

fort.14 的 NaN 理论上有两种可能来源:

场景 描述 实际是否存在
A. 大气模型发散 TEMP/POPUL 含 NaN → FLUX 全 NaN 存在(62 例)
B. 大气收敛良好,局部辐射转移溢出 某些波长点不透明度极值 → 局部 NaN 数据中不存在

源码判定:场景 B 在机制上极不可能。FLUX 全 NaN 要求所有频率点的源函数都变为 NaN,这只能由大气结构整体发散引起。局部 NaN(少数频率点)需要大气结构有效但个别频率点溢出——但 TLUSTY 的频率循环对每个频率点用同一套 TEMP/POPUL,大气有效时不会让某个频率点单独产生 NaN(除非该频率点恰好触发了吸收系数的精确除零,但这在实测数据中从未出现)。

数据判定:在 162 个有 .emflux 的网格点中,所有 62 个 emflux-NaN 点都是 100% 全 NaN(如 t55000_g5.04514/4514 行全 NaNt60000_g6.54420/4420 行全 NaN)。没有任何一个点是局部/特定波长 NaN。这直接排除了场景 B。


4. 真实数据验证

4.1 全量统计(162 个网格点)

情形 数量 conv.json converged
.emflux 全 NaN + .7 含 NaN 53 全部 false
.emflux 全 NaN + .7 检测无 NaN 9 全部 false
.emflux 正常 + .7 含 NaN 0
两者都正常(0 NaN 100 混合

决定性事实

  1. 62/62100%)的 emflux-NaN 点 converged=false
  2. converged=true 且 emflux 含 NaN 的点 = 0
  3. 全 NaN 点都是 100% 整文件 NaN,无局部 NaN。

4.2 典型全 NaN 点

t55000_g5.0_he-2_c-1_n-4_o-4

  • .7 从第 11 行(depth 1 的 TEMP/ELEC/DENS/POPUL)开始全是 NaN5200/5210 行 NaN)。
  • conv.jsonconverged:falseatmosphere_has_nan:truebest_max_relc:39100(seed_nc 第 3 次迭代发散)。
  • .nl.errIEEE_INVALID_FLAG IEEE_UNDERFLOW_FLAG IEEE_DENORMAL 浮点异常。

4.3 检测器盲区:9 个".7 检测无 NaN 但 emflux 全 NaN"的点

这 9 个点最关键——它们决定了 emflux 校验是否有独立价值:

  • .7 不含字面 NaN/Infatmosphere_has_nan 检测器通过),但实际已发散成巨大但有限的非物理值
    • TEMP ≈ 1.015e-15(趋近 0
    • POPUL 达 5e12(极大)
    • final_max_relc 在 1e16 ~ 1e27(灾难性发散)
  • 这些值不是字面 NaN,但在 OPACF1 里 HK/TEMP→巨大、POPUL 乘积→Inf、EMIS1/AB0→Inf/Inf=NaN。
  • synspec_error"spec 含 NaN/Inf/溢出行 (共 303554 行)" —— 发散大气喂给 SYNSPEC 后整谱也是 NaN/溢出。
  • 原因atmosphere_has_nanconv_check.rs:18)的正则 (?i)(\bnan\b|\binf(?:inity)?\b|\*{3,}|[eE]\+(?:3\d{2}|[4-9]\d{2,})) 只匹配 nan/inf/***/E+300+抓不到"巨大但有限的发散值"(如 5e12 POPUL、1e-15 TEMP)。

这 9 个点靠 converged=falsemax_relc 极大)+ emflux 全 NaN + synspec NaN 三重否决才被正确标记。emflux 全 NaN 校验在这类点上恰好补位了 atmosphere_has_nan 的盲区。


5. 对校验设计的影响

5.1 emflux bolometric 校验的语义修正

现有 check_emflux_bolometricconv_check.rs)把"全 NaN(占比>50%"判失败。根据本调研:

  • 判定正确:全 NaN 是大气发散的充分(且几乎必要的)标志。
  • 语义需精确化:错误描述从 "辐射转移可能失败" 改为 "大气收敛失败(辐射转移求解发散)"
// 修正后(conv_check.rs
error: Some(format!(
    "emflux NaN/不可解析占比 {:.0}% 过高(>{:.0}%),大气收敛失败(辐射转移求解发散)",
    nan_ratio * 100.0, 50.0
)),

5.2 与 atmosphere_has_nan 的关系

emflux 全 NaN 校验与 atmosphere_has_nan 同级、互为冗余

校验 覆盖的发散形态 盲区
atmosphere_has_nan .7 含字面 NaN/Inf/***/E+300+ 巨大但有限的发散值(如 5e12 POPUL、1e-15 TEMP
check_emflux_bolometricNaN 分支) 大气发散导致的辐射转移全 NaN 需 TLUSTY 启用且 fort.14 存在
check_fort9max_relc max_relc 极大(收敛失败) 无法区分真收敛/假收敛

三者交叉覆盖,任一独立即可拦截大部分发散点。emflux 全 NaN 校验的独特价值在于捕获那 9 个 atmosphere_has_nan 盲区点。

5.3 不影响的部分

  • 局部 NaN(如 .spec 中 Balmer 线区域的 NaN:这是 SYNSPEC 处理 Balmer 线 Stark 加宽时的正常行为(与 tests/synspec/hhe/results/hhe35nc.spec 一致),不是收敛失败。emflux 校验不涉及 .spec,且阈值设在 50%(容忍局部 NaN,只拦全 NaN)。

6. 结论

  1. .emflux 全 NaN 的物理含义TLUSTY 大气模型结构(TEMP/POPUL/ELEC)已数值发散到不可用程度,导致最后一次频率循环里 OPACF1→RTEFR1 把整列 FLUX 全部污染为 NaNOUTPRI 在耗尽 NITER 上限后写入 fort.14。

  2. 是否等于 TLUSTY 收敛失败?—— 是。源码机制(CHMX 变 NaN 后 STOP 护栏与收敛判定双双失效,全 NaN 只能在大气发散时产生)+ 数据(62/62 全 NaN 点 converged=falseconverged=true 点 0 个有 NaN)双重佐证。全 NaN emflux 是大气收敛失败的充分(且几乎必要的)标志。

  3. ".7 大气无 NaN(按检测器)且 converged=true,但 .emflux 全 NaN"是否可能?—— 不可能。162 点中此组合为 0 例。出现".7 检测无 NaN + emflux 全 NaN"的 9 例,都是 .7 实际已发散成巨大有限值/近零温度(检测器盲区),converged 全为 false。

  4. emflux 全 NaN 校验的独立价值:能捕获 atmosphere_has_nan 正则盲区(巨大有限发散值),起到交叉验证补位作用。


附录:关键源码行号

均在 /home/fmq/program/tlusty/tl208-s54/tlusty/tlusty208.f

功能 行号
fort.14 写出(OUTPRI 14188
FORMAT 614 14406
FLUX 来源 COMMON MODELQ.FOR:135
FLUX 赋值(辐射转移解) 31800, 32225, 32848
NaN 产生(不透明度 HK/TEMP 4857, 45375
NaN 产生(POPUL 布居) 31444, 31446, 33455
NaN 传播(源函数 EMIS1/AB0 32352, 32361
发散 STOP 护栏(对 NaN 失效) 47587-47591
LFIN 收敛判定 47596
OUTPRI 调用时机(LFIN 3887-3889