Files
DCTS/docs/tlusty_divergence_root_cause_2026_08_11.md
T
fmq b058e66722 feat(all): NaN 伪收敛否决与 nl_tight 能量回退、emflux 检验 4π 修正、nst 行宽与种子边界修复、gfATO 谱线表接通与网格加密 9216 点、阶段分项统计与跳板机部署
物理修复(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 篇根因分析/验证文档
2026-08-17 23:55:26 +08:00

34 KiB
Raw Blame History

TLUSTY 不收敛根因分析(1105 个网格点)

日期2026-08-11 数据来源/home/fmq/下载/dcts.dbserver DB 快照)+ data/salvage/8320 次任务尝试的完整产物) 源码版本tlusty208.f50010 行)+ dcts 框架 crates/{common,node,server} 工作流sdB_cnogrid 共 9216 点,converged 8102 / failed 1105 / pending 9

本文不修改任何代码,只做根因定位与修复建议。修复实施见文末「后续行动」。


⚠️ 2026-08-12 更新(修复建议已实测证伪,配置改动已回退)

本文 §3.1 A1/A6 与「后续行动」中推荐的数值配置类修复(收紧 DPSILG、禁用 Kantorovich/ITEK、禁 Ng/IACC)已经用控制实验实测验证:均无法让失败点从冷启动 收敛,相关配置改动已全部回退。详见 docs/cold_start_fix_2026_08_12.md

实测结论(6 个测试点 × 4 种配置 = 24 次运行,0 次收敛):

  • ITEK=0 → nitzer=0 冻结 populationstlusty208.f:1937),不是禁 Kantorovich
  • IACC=0 → 被 clamp 回 7tlusty208.f:1928),空操作;
  • ITEK/IACC > NITER(真正禁用 Kant+Ng)→ 发散幅度降 1e121e14×,但仍不收敛 (末 relc 1e31e7),Newton 在 grey LTE 起点即处于收敛域外;
  • DPSILG=1.5 有害(截断步长致漂移)。

因此本文方向性结论(物理吸引域窄 + 框架无自适应)成立,但可行的修复不在 数值配置,而在「连续法(从已收敛较低 Teff 模型逐步过渡)」与「He 匹配种子选择」 等框架级改造。 相关计算文件见 docs/cold_start_fix_2026_08_12.md


一、结论先行

1105 个失败网格点全部走完了 cold_run → seed_step 的完整退化路径,两种策略都失败。这不是种子选择问题,不是单纯的初值问题,而是两个因素叠加:

  1. 物理侧——TLUSTY 的 Newton 完全线性化求解器在「高 Teff + 低 logg」参数角落的吸引域过窄,辐射场对布居数极度敏感;
  2. 工程侧——dcts 框架缺少发散检测与自适应阻尼反馈,单阶段发散后无回退,只能靠 NITER 自然耗尽再整点重试。

失败点本身并非不可解——同族参数下大量点是收敛的(T=60k/g=5.0 仍有 78/256 收敛),失败的只是收敛盆地最窄的那一批。


二、数据证据链

2.1 退化序列(DB tasks 历史,1105/1105 全部命中)

["cold_run"] → ["cold_run","seed_step"] → ["seed_step"] → ["cold_run","seed_step"] → ["seed_step"] ...

每个失败点重试均值 6.9 次grid_points.attempt_count 分布:5 次=1996 次=1357 次=4628 次=2759-13 次=33),在两种策略间反复跳。最新任务全部停在 failed_stage=tlustytlusty_strategies=["seed_step"]

2.2 失败点的参数分布(物理一致)

Teff 失败数 logg 失败数 loghe 失败数
60k 344 5.0 587 (53%) -4.0 345
55k 330 5.5 285 -2.0 337
50k 204 6.0 118 0.0 274
45k 84 6.5 115 2.0 149
≤40k 143

高 Teff + 低 logg 高度集中(≥50k 占 878/1105 ≈ 79%logg=5.0 占 53%)。

Teff × logg 收敛率矩阵(节选,完整矩阵见 §6 附录):

         g=5.0      g=5.5      g=6.0      g=6.5
T=20k   249/256    242/256    238/256    210/256
T=40k   233/256    252/256    253/256    256/256
T=50k   119/256    224/256    233/256    244/256
T=55k    78/256    156/256    227/256    231/256
T=60k    78/256    152/256    222/256    228/256

关键:没有任何格子是 0% 收敛——所以这是"难",不是"不可能"。难度梯度清晰:随 Teff 升高收敛率下降,随 logg 升高收敛率上升。

2.3 失败阶段分布(按 salvage 最后一次尝试)

首失败阶段 占比 失败特征
seed_ncseed_step 第一阶段) 1033/1105 (93%) max_relc 单调雪崩
nccold_run 第二阶段) 72/1105 max_relc 单调雪崩
lte 0 ——

两种策略的死亡轨迹几乎相同——max_relc 在 5 步内从个位数飙到 1e6+。典型例:

cold_run nc (T=60k g=5.0):
  [9.72, 46.5, 259, 1950, 4.85e6, 2.6e10, 2.8e4, 4.04e7, 2.06e11, 2.32e15]

seed_step seed_nc (T=60k g=5.0, 从收敛邻居热启动):
  [7.0, 115, 272, 2020, 1.49e8, 8.26e13, 3310, 2.09e7, 7.53e10, 4.71e14, ...]

注:salvage 中只保留了 282 个点的 cold_run 中间产物(节点在重试时会清理中间态),但 282/282 全部死在 nc 阶段,且 max_relc 雪崩轨迹与 seed_nc 一致。

最后一次尝试的失效特征分两档(按 atmosphere_has_nan 拆分):

失效特征 点数 / 1105 说明
atmosphere_has_nan: true 434 (39%) 雪崩污染了大气文件;伴随 temp_check.error: 深度 1 T=NaNKemflux NaN 占比 100%bfac 极端值占比 36%.err: IEEE_INVALID_FLAG IEEE_DIVIDE_BY_ZERO
atmosphere_has_nan: false 671 (61%) max_relc 雪崩但大气未污染到 NaN;被 temp_check(表层 T 超过 3×Teff)、bfac_check(极端值占比 >10%)、emflux_check(积分通量偏离 σT⁴)等后验校验判废,或 nl 阶段才崩

⚠️ 修订说明:初版误把 "atmosphere_has_nan=1033/1105" 写入报告——1033 实为 "首失败阶段=seed_nc 的点数",与 NaN 计数无关。真实 NaN 仅 434/1105。雪崩到 1e6+ 未必把 fort.7 写成 NaN(取决于 TLUSTY 是撞 1e16 STOP 还是跑完 NITER),故多数失败点的大气文件本身可读,是被后验校验(温度结构/b 因子/emflux)挡下的。

2.4 种子选择不是唯一根因(但不能完全排除)

1105 个失败点的 seed_step 配对 全部来自 exact_familyd_teff<5000 & d_logg<0.01 & d_loghe<0.01),且 662 个是贫方向seed_finder 设计的稳定方向):

seed direction: poor=662, rich=373, same=70
d_teff 分布: <5k(exact)=1105, 其余=0
d_logg 分布: 0(same)=1105, 其余=0

⚠️ 校正:初版标题"种子不是问题(关键反证)"措辞过强。准确结论是种子选择不是唯一根因,但不能排除它是贡献因素之一

  1. "exact_family" 不等于物理极近:判定允许 ΔTeff < 5000K(半步容忍)。sdB 网格 Teff 步长 10000K 时,55k↔60k 相邻档互作种子的辐射场(Lyman 紫外主导)差异可能很大——"exact_family" 标签覆盖了 Teff 仍可差一整档的事实。
  2. 443 个 rich/same 方向失败373 rich + 70 same):seed_finder 自身的回测数据(seed_finder.rs:14-25)称富方向成功率仅 3-11%,贫方向 42-54%。这部分失败可能部分源于种子方向不佳。
  3. 种子质量未审:失败点用的种子虽来自收敛邻居,但该邻居是在多少迭代数、多干净的 max_relc 下收敛的(擦边收敛 vs 干净收敛),本报告未分析。

综上,"662 贫方向仍爆"只能说明"贫方向种子不足以保证收敛",不能推出"种子方向无关"。种子方向的影响需在 §五 A/B 测试中单独控制变量验证。

2.5 seed_step 在同区域能成功(说明非不可解,但未排除混淆变量)

高 Teff 危险区里 seed_step 仍有大量成功:

T=50k g=5.0: 119 conv (cold=51, seed=68)
T=50k g=5.5: 224 conv (cold=87, seed=137)
T=55k g=5.0:  78 conv (cold=53, seed=25)
T=60k g=5.0:  78 conv (cold=40, seed=38)

成功路径的 conv.json 显示 seed_nc 即使 converged=false 也可用(note: "accepted as seed (convergence not required)"),关键是后续 nl 能否从不完全收敛的种子收敛到 max_relc < 1e-4。失败点中 434/1105 的 nl 产出 NaN 大气,其余 671 点被后验校验挡下。

⚠️ 校正:初版据"同格有成功"推出"非不可解",逻辑成立——同参数区不是数学上不可解。但未排除混淆变量:成功的 78 个点(T=60k/g=5.0)与失败的 178 个点用的种子来源/方向可能系统性不同。尤其 cold=40 成功(从 LTE 灰化初值也能收敛一部分)反而暗示初值/种子的微小差异决定成败——这更像"吸引域窄 + 初值敏感",而非单纯的"求解器稳定性"。本节只能支撑"非数学不可解",不能支撑"种子/初值无关"。


三、源码级根因

3.1 TLUSTY 侧(tlusty208.f

A1. DPSILG=10. 的限幅太松 — tlusty208.f:14652-14653

IF(CHAN.LE.UN/DPSILG-UN) CHAN=UN/DPSILG-UN    ! 下限 = 1/10 - 1 = -0.9
IF(CHAN.GE.DPSILG-UN) CHAN=DPSILG-UN          ! 上限 = 10 - 1 = +9.0

默认 DPSILG=10.PVALUE 表 idx 185tlusty208.f:1793),允许单步相对修正达 10 倍

⚠️ 重要校正:DPSILG 这个宽松限幅(±10×)主要作用于辐射场(I ≤ NFREQE)和布居数块(I ≥ NFREQE+INSE。紧随其后 tlusty208.f:14654-14677 有一组逐变量更严格的限幅,按 INRE/INHE/INPC/INDL 偏移把对应槽位钳到更窄:

  • 温度 TI = NFREQE+INRE):受 DPSILT=1.25 钳制 → 单步上限仅 ±25%
  • 总粒子数 TOTNI = NFREQE+INHE)、电子密度 neI = NFREQE+INPC):受 DPSILN=10. 钳制 → ±10×(与 DPSILG 同级,宽松)
  • Δ(湍流相关,I = NFREQE+INDL):受 DPSILD=1.25 → ±25%

所以"高 Teff 下结构量步长放大 10 倍必爆"这一表述对温度不成立(温度有 ±25% 独立约束)。真正受 10× 宽松限幅影响的是辐射场布居数/TOTN/ne。雪暴主因更可能是辐射场与布居的耦合放大,而非温度跳变。

A2. 首步发散无 STOP 保护 — tlusty208.f:14700

IF(ITER.NE.1 .AND. ABS(CHMX).GT.1.D16) THEN
    WRITE(6,610) ITER,CHMX
    STOP
END IF

只在 ITER≥2 才检查 1e16 雪崩 STOP。首步(ITER=1)即使 CHMX 巨大也不触发 STOP——后续迭代在已被污染的解上继续算,直到某步偶然撞上 1e16 才停。

⚠️ 校正:初版称"第 1 步任意大都继续跑"措辞过强。首步的 CHAN 仍受 A1 限幅钳制(辐射/布居 ±[-0.9,+9],温度 ±25%),并非"无限幅";首步只是无 STOP。真正的风险是:限幅允许的 +9×(辐射/布居)在多深度多变量上叠加,几步内放大成 Inf/NaN——这与 §2.3 观测的雪崩量级一致。 另:同样的 STOP 逻辑在 SOLVEStlusty208.f:15047)和 RYBSOLtlusty208.f:47587)另两条求解路径各有一份。

A3. ORELAX 只阻尼布居数,不阻尼辐射场与温度等结构量 — tlusty208.f:14647

C       over-relaxation
        IF(I.GE.NFREQE+INSE) CHAN=ORELAX*CHAN

⚠️ 重大校正:初版把 PSI 向量布局写反了。经核对 tlusty208.f:3910-3936PSY0 各槽位赋值)与 tlusty208.f:795-797INRE/INPC/INSE 偏移量定义:INDL=3, INPC=4, INSE=5),正确布局是:

[1 .. NFREQE]               = 辐射场(RADEX 平均强度 Jν)   → 不被 ORELAX 松弛
[NFREQE+INHE] = [NFREQE+1]  = 总粒子数 TOTN                → 不被 ORELAX 松弛
[NFREQE+INRE] = [NFREQE+2]  = 温度 T                        → 不被 ORELAX 松弛
[NFREQE+INPC] = [NFREQE+4]  = 电子密度 ne                   → 不被 ORELAX 松弛
[NFREQE+INSE] = [NFREQE+5]  = 布居数 superlevels 块的起点   → ORELAX 生效(I ≥ NFREQE+INSE

结论ORELAX 松弛的是 布居数(populations不是温度/ne/TOTN 等结构量。初版把"布居"与"结构量"的松弛关系完全对调了。

这对结论的影响:

  • 框架给 seed_nc 设的 ORELAX=0.3 确实会作用到布居数(初版说"对结构量、对辐射场不生效"是错的——准确说是"对布居生效,对辐射场与温度/ne 不生效")。
  • 真正欠松弛缺失的是辐射场 Jν——高 Teff 下辐射场最敏感、最需要欠松弛,而它既不受 ORELAX 保护、又只受 A1 的 ±10× 宽松限幅。这一论断方向不变,但论证链条已修正。

A4. CHMAX 只判终值 — tlusty208.f:14728

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

CHMAX=1e-3(默认,PVALUE idx 77tlusty208.f:1767)是"最后一次迭代最大修正"的终值门槛。CHMAX 本身对过程无约束——但它不是唯一的过程机制,见 A5/A6。

A5. ⚠️ 初版遗漏:CHMAXT 驱动的过程反馈 — tlusty208.f:14711, 22954-22958

初版称"过程爆掉只能靠 NITER 自然耗尽"是夸大。源码中存在由 CHMAXT(默认 0.01PVALUE idx 80tlusty208.f:1767)驱动的两层过程反馈:

C tlusty208.f:14711  温度变化大时重置铁线截面
if(chmt.gt.chmaxt .and. ispodf.ge.1) LIROST=.TRUE.
if(LITEK.and.LIROST) then
   call iroset
   LIROST=.FALSE.
end if

C tlusty208.f:22954-22958  温度变化小时锁定 Ng 加速节奏
if(chmt.lt.chmaxt) then
   do itl=iter,niter+1
      nitlam(itl)=nlamt
   end do
end if

这是针对温度变化CHMT)的自适应机制。但它不直接约束辐射场与布居的耦合发散——而这恰恰是失败点雪暴的主区。所以 A5 的存在削弱了"过程完全无约束"的措辞,但不改变"雪暴主因缺乏针对性干预"的结论。

A6. ⚠️ 初版遗漏:ITEK / Kantorovich 加速切换 — tlusty208.f:843-856

默认 ITEK=4PVALUE idx 130tlusty208.f:1779)。源码:

IF(ITEK.GT.0) THEN
   DO IKT=ITEK+1,NITER
      KANT(IKT)=1                ! iter≥5 切到 Kantorovich 加速
   END DO
   DO IKT=IACC,18,IACD
       KANT(IKT)=0               ! 但 Ng 重启点(IACC=7,18,IACD=4) 回到完全线性化
   END DO

iter≥5 切换到 Kantorovich 加速迭代(更便宜的近似,少算矩阵元)。初版把 ITEK 标为"失败相关性:低"是无依据的臆断——雪暴轨迹(如 §2.3 的 [9.72, 46.5, 259, 1950, 4.85e6, ...])第 5 步开始爆,与 Kantorovich 切换点(iter≥5时间上重合。这是一个尚未排查的嫌疑诱因Kantorovich 在发散区可能因近似失真加剧不稳定。降 DPSILG(修复优先级 3)若救回率不如预期,应优先排查 ITEK。

3.2 框架侧(crates/common/src/runner.rs / crates/node/src/executor.rs

B1. 无链内重试,无自适应阻尼

default_seed_chain / default_cold_chainrunner.rs:20-98)一旦构造完成参数就是死的。conv_check::check_fort9conv_check.rs:204-217)判完发散后,runner.rs:564-570 只在 require_converged=truebreak,否则继续——没有任何"发现 max_relc 超过阈值就降低 DPSILG / 加大 ORELAX / 重跑一次"的反馈机制

B2. seed_ncrequire_converged=false 反而帮了倒忙

default_seed_chain 第一阶段设 require_converged=false("接受非收敛种子"),本意是"给 nl 一个起点就行"。runner.rs:518-521rc==0 && fort7.is_file() 分支无条件把本阶段 fort.7 拷成下一阶段的 current_seed——stage 间种子传递路径上没有 atmosphere_has_nan 检查

⚠️ 校正:初版称"雪崩后仍然把含 NaN 的 fort.7 喂给 nl"措辞过死。是否真的传 NaN 取决于 TLUSTY 的退出方式:

  • 若雪崩撞上 1e16 STOPA2)→ rc≠0 → 走 runner.rs:522-531 else 分支,不拷 fort.7
  • 若跑完 NITER 自然结束(rc=0)但 fort.7 已含 NaN → 会拷给 nlnl 自然也爆。

§2.3 实测 434/1105 真有 NaN 大气——这部分点确属此路径。其余 671 点是雪崩但大气可读,被后验校验挡下。两档都要修。

B3. CHMAX=None 让 TLUSTY 走默认,但没把更保守的 DPSILG/ORELAX 一起传

所有默认阶段 chmax=Nonerunner.rs:20-98),runner 用 Rust 侧 eff_chmax=0.001runner.rs:443)做后验判断,但这个值不传给 TLUSTY。结果 TLUSTY 用默认 DPSILG=10. + ORELAX=1.0(冷链)/ 0.3(种子链,作用于布居)跑,过程约束针对辐射场的几乎为零(见 A3 校正后的作用域)。

ChainStep 结构体(config.rs:1130-1150)字段为 label/lte/ltgray/ilvlin/require_converged/niter/chmax/itek/ichang/idlte/iacc/orelax没有 dpsilg 字段。要分阶段注入 DPSILG 需新增字段(走 nst_writer line1),或通过 YAML tlusty_input.nst_extra_keys 逃生舱(nst_writer.rs:138-161 支持,对当前阶段生效)。

B4. 整点重试无参数扰动

策略 fallbackscheduler.rs::trigger_strategy_fallback)只在 ["cold_run"]["seed_step"] 之间切换,重试任务的 tlusty_chain_params/seed_chain_params 是工作流 YAML 的逐字克隆scheduler.rs:1065-1066)——TLUSTY 数值参数零扰动

⚠️ 校正:初版"每次重跑参数完全一致(除了种子来源)"自相矛盾——种子来源恰恰是初值的一部分。准确表述:同一策略内的重试是确定性白跑(相同初值+参数必爆);跨策略切换时换了初值cold_run 无种子 LTE 灰化初值 → seed_step 用近邻收敛种子),但 TLUSTY 数值参数始终不变。所以"重试均值 6.9 次"中只有跨策略切换那几次换了实质初值,同策略内确属白跑。

3.3 未排除的替代根因(诚实声明)

本节是审查后新增。初版把 DPSILG/ORELAX/CHMAX 这些机制描述当作根因,但严格地说这是充分性论证,不是必要性论证。即便 A1-A6 全为真,也不能排除主要根因在以下未被充分审视的环节。这些替代假说需在 §五 A/B 测试中逐一排除或确认。

替代假说 依据 排查方法
TFLOOR=8000K 在高 Teff 下不合理 config.rs:815 默认 8000。Teff=60k 大气表层物理温度 ~30-90kTFLOOR=8000K 离表层不远;若发散把温度推向 TFLOOR,钳制本身可能破坏 H 电离平衡(8000K 附近 H 电离度突变) 对失败点试 TFLOOR=30000/40000,看救回率
ND=50 在高 Teff+低 logg 下分辨率不足 config.rs:758 框架覆盖为 50TLUSTY 默认 70config 自承)。高 Teff+低 logg 表层温度梯度陡峭,ND=50 可能欠分辨表层 → 数值发散 对失败点试 ND=70/100
DDNU=50/CNU1=6 频率网格在高 Teff 不足 config.rs:769-776 框架 DDNU=50TLUSTY 原始 0.75)、CNU1=6(原始 4.5)。60k 大气 Lyman 极端紫外主导,此"sdB 调优"频率网格可能采样不足 → 辐射场离散误差大 对失败点试更密的频率网格
特定离子原子数据 bug config.rs ions 表用 C III c3_34+12lev.dat、N II n2_32+10lev.dat 等组合能级文件;某高 Teff 主导离子的能级数据若有误,会导致该离子布居发散 对失败点逐离子禁用排查
LTE 阶段未干净通过 失败点 salvage 的 .errIEEE_DIVIDE_BY_ZERO;若某深度 LTE 灰化初值的 ne 近零导致除零,可能先于 NLTE 雪崩 检查失败点的 .lte.err/.lte.6 是否干净
输入文件渲染 bug gen_input5.rs 渲染丰度 10^logx,logc=-4 等极端值时未验证字节级正确 抽查失败点的 .5 文件丰度数值

优先级:前三个(TFLOOR/ND/DDNU)与"A1-A6 机制"正交,是独立的数值配置假说,应在方案 A 的 A/B 测试中顺带排除。若方案 A(降 DPSILG)救回率远低于 30%,应立即转向这些替代假说。


四、修复建议(按性价比排序)

优先级 1:自适应发散检测 + 阻尼回退(框架侧,治本)

runner.rs 的 stage 循环里加一层 "单阶段内 iftek 监控 + 回退重跑"

  1. tokio 异步跟踪 fort.9 增长,每完成 1-2 次迭代解析一次 max_relc
  2. 若前 3 步 max_relc 单调上升且超过阈值(如 1e2),SIGTERM 当前子进程
  3. 用更保守的参数(DPSILG 减半、ORELAX 减半)重跑该阶段;
  4. 最多回退 2-3 次,每次更保守。

这是最直接的对症方案——雪崩在前 3 步就能看出来,没必要跑到 NITER。

实现成本:中-高。需要在 ChainStepdpsilg/adaptive 字段,runner 加监控+回退逻辑。初版估的 "~300 行 Rust" 偏低——实时跟踪 fort.9 增长(TLUSTY 边写边追加,需处理部分写入)+ SIGTERM 子进程后正确 reap + 清理中间文件 + 回退状态机,工程量更大,预估 400-600 行。

优先级 2:给辐射场与温度/ne 加欠松弛(TLUSTY 源码侧,治本但要改二进制)

⚠️ 校正:初版给的代码是替换原逻辑(I.GE.I.LT.),会移除原布居松弛——这反而让布居失去保护。正确改法是新增一行,且 ORELAXR 在源码中本不存在。

正确改法tlusty208.f:14647SOLVE 与 SOLVES 14996 两处都要改):

C 现状:只有布居(INFREQE+INSE) ORELAX 松弛
        IF(I.GE.NFREQE+INSE) CHAN=ORELAX*CHAN
C 新增:辐射场(INFREQE)与结构量(NFREQE+1..NFREQE+INSE-1,即 T/TOTN/ne/ZD/DENS/DELTA)
C        ORELAXR 松弛(默认 1.0=不生效,难收敛区配 0.3-0.5
        IF(I.LT.NFREQE+INSE) CHAN=ORELAXR*CHAN

物理论据(修正后):辐射场 Jν 与温度/ne 在高 Teff 下最敏感、却都不被 ORELAX 松弛(A3 校正后)。给它们加欠松弛是物理上最对症的修复。

注意

  1. 不是"改 2 行 + 重编"ORELAXRtlusty208.f 中完全不存在——需扩 VARNAM/PVALUE 两个 DATA 数组各加一项、加 nst 关键字解析、加 PVALUE 默认值(建议 1.D0 不变,仅难收敛阶段通过 nst 覆盖为 0.3-0.5)。工程量约 30-50 行 Fortran + 重编 + 更新 assets/tlusty_static
  2. 与 NLAMBD(λ-迭代)的交互未验证:辐射场在每个深度通过 NLAMBD 机制单独更新,给 Jν 加欠松弛可能与该机制冲突,需小规模测试验证。
  3. 触及二进制,需走完整发布流程。

优先级 3:动态降低 DPSILG(框架侧,立即可做,无侵入)

ChainStep 加可选字段 dpsilg: Option<f64>,通过 nst_extra_keys 注入。给种子链和难收敛区(Teff ≥ 50k & logg ≤ 5.5)设 DPSILG=3.DPSILG=2.(对应单步上限 ±2 倍 / ±1 倍)。收敛性会显著改善。

代价:迭代步数变多,但比爆掉重试 7-9 次便宜得多。

实现成本:低。~50 行 Rust + YAML 配置。可立即上线

优先级 4:seed_nc 发散时拒绝喂给 nl(框架侧,止损)

runner.rs 里,当 seed_nc 的 atmosphere_has_nanbest_max_relc > 1e3 时,不要把它的 fort.7 当作 nl 的种子;要么直接判失败触发下一策略,要么回退用原 seed_atmos(邻居的干净种子)重新喂 nl。

实现成本:极低。~20 行 Rust。

优先级 5:首步发散保护(TLUSTY 源码侧)

tlusty208.f:14700SOLVE/ 15047SOLVES/ 47587RYBSOL)三处把 ITER.NE.1 改为允许首步也检查(用更宽松的阈值,如 1e8),防止首步就冲出吸引域:

C 改前
IF(ITER.NE.1 .AND. ABS(CHMX).GT.1.D16) THEN ... STOP
C 改后
IF(ITER.EQ.1 .AND. ABS(CHMX).GT.1.D8) THEN ... STOP
IF(ITER.GT.1 .AND. ABS(CHMX).GT.1.D16) THEN ... STOP

实现成本:极低(改 2 行 + 重编)。


五、验证路径

30 个失败点做 A/B 测试(覆盖 60k/55k/50k × g5.0/5.5,每格 5 点):

方案 配置 预期救回率(假设性估计)
基线 当前配置重跑 0%(可复现)
方案 A 优先级 3DPSILG=3+ 优先级 4NaN 拒绝) 30-50%
方案 B 方案 A + 优先级 1(自适应回退) 70-85%
方案 C 方案 B + 优先级 2/5(改 TLUSTY 源码) 90%+

⚠️ 校正:初版无标注地把上述救回率当作事实呈现。这些百分比是无数据支撑的先验猜测,仅用于排列方案的优先级顺序,不代表实测预期。尤其"方案 C 90%+"与"吸引域窄"的论断存在张力——吸引域窄意味着即便阻尼到位,某些初值仍落吸引域外。方案 A 的实测救回率是最关键的诊断信号:若远低于 30%,说明 DPSILG 不是主因,应转向排查 ITEK/KantorovichA6)或 §3.3 列出的替代根因。

建议:先实施方案 A 验证假设(DPSILG 是否真是主因),用实测数据说话,再决定是否值得改 TLUSTY 源码走方案 C。


六、附录

6.1 完整 Teff × logg 收敛率矩阵

logg->    g=5.0       g=5.5       g=6.0       g=6.5
T=20k   249/256     242/256     238/256     210/256
T=25k   251/256     254/256     256/256     253/256
T=30k   252/256     255/256     254/256     256/256
T=35k   244/256     252/256     256/256     256/256
T=40k   233/256     252/256     253/256     256/256
T=45k   207/256     230/256     247/256     254/256
T=50k   119/256     224/256     233/256     244/256
T=55k    78/256     156/256     227/256     231/256
T=60k    78/256     152/256     222/256     228/256

6.2 TLUSTY 关键变量默认值(PVALUE 表,tlusty208.f:1750-1804

行号为 PVALUE DATA 块内每个值实际所在行(经 VARNAM/PVALUE 平行映射逐项核对,idx 从 1 起)。

变量 默认值 PVALUE 行号 含义 失败相关性
CHMAX 1.D-3 1767 (idx77) 收敛门槛(终值) 低(只判终值)
DPSILG 10. 1793 (idx185) 单步相对修正上限(辐射场+布居) (辐射场/布居 10 倍太松)
DPSILT 1.25 1793 (idx186) 温度单步上限(±25% 低(温度有独立严约束)
DPSILN 10. 1793 (idx187) TOTN/ne 单步上限
DPSILD 1.25 1793 (idx188) Δ(湍流)单步上限
CHMAXT 0.01 1767 (idx80) 温度变化过程反馈阈值 中(A5 机制,未针对辐射场)
ORELAX 1.D0 1779 (idx131) 布居数过松弛因子 中(框架覆盖为 0.3/0.5,作用于布居)
NITER 30 1763 (idx64) 最大迭代数
ITEK 4 1779 (idx130) Kantorovich 加速起始 iter 待排查(iter≥5 切换,与雪暴起点重合,见 A6)
TFLOOR 8000. 1787 (idx160) 温度下限 待排查(见 §3.3
ICHANG 0 1766 (idx83) 是否走 CHANGE(热启动)
ND 50(框架覆盖) 默认70(idx67) 深度网格点数 待排查(见 §3.3

⚠️ 校正:初版把 DPSILG 行号写成 1786(实际是 POPZCH 区段的 '0.','0.',与 DPSILG 无关),且 §6.2 范围写 1750-1806(实际 PVALUE 结束于 18041806 是 BLNK DATA)。初版还漏列 CHMAXT、DPSILD、ND,把 ITEK 标为"失败相关性:低"(无依据,已改"待排查"),把 DPSILG 含义写"结构量"(实际主要约束辐射场+布居,温度有独立 DPSILT),把 ORELAX 含义写"结构量过松弛因子"(实际是布居)。

6.3 失败点失败阶段与 max_relc 量级分布(最后一次尝试)

首失败阶段:        seed_nc=1033  nc=72
末步 max_relc:    >1e6=982  1e4-1e6=25  1k-1e4=15  100-1k=12  10-100=14  (1,10]=5  <1=35
                  另有 17 点 itek_history 为空(首失败阶段无迭代轨迹)
迭代次数分布峰值:  10 iters(327) / 20 iters(143) / 6 iters(159) — 全部撞到 NITER 上限
atmosphere_has_nan: True=434 (39%) / False=671 (61%)

⚠️ 校正:初版末步 max_relc 行漏列 (1,10] 区间 5 点 + 17 点空 history,导致加总 1083 与首失败阶段 1105 不闭合。补齐后可分类 1088 + 空 history 17 = 1105。

6.4 种子方向

poor(目标比种子贫,稳定方向) = 662
rich(目标比种子富,不稳定方向) = 373
same(同 CNO) = 70

seed_finder 的非对称距离(seed_finder.rs:29-43RICH_PENALTY=4 vs POOR_PENALTY=1)使 60% 失败点拿到了贫方向种子。

⚠️ 校正:初版称"seed_finder 已尽力……说明问题不在种子方向"措辞过强。更准确的表述:种子选择不是唯一根因,但不能排除它是贡献因素之一。理由:(1) "exact_family" 判定允许 ΔTeff < 5000K(半步容忍),sdB 网格步长 10000K 时 55k↔60k 相邻档互作种子辐射场差异仍可能很大,"物理极近"标签不能完全等同于 NLTE 状态空间近邻;(2) 443 个 rich/same 方向失败(seed_finder 自身的回测数据称富方向成功率仅 3-11%)可能部分源于种子方向。种子方向的影响需在 §五 A/B 测试中单独控制变量验证。

6.5 分析脚本

本次分析所用脚本临时存于 /tmp/(未入库):

  • analyze_salvage.py / analyze2.py — salvage 池全量分类
  • dbfail.py — DB 失败点分布
  • correlate.py — DB 失败点 × salvage 最后一次尝试关联
  • seq.py — 策略退化序列验证
  • cold_mode.py — cold_run 失败模式
  • seed_check.py — 种子距离/方向分析
  • success_region.py — Teff×logg 成功率矩阵
  • parse_defaults.py — TLUSTY PVALUE 默认值映射

七、后续行动

  • 决定走方案 A / B / C 中的哪个
  • 若方案 A:实现 ChainStep.dpsilg 字段 + YAML 配置(优先级 3)
  • 若方案 A:实现 seed_nc NaN 拒绝逻辑(优先级 4)
  • 30 点 A/B 测试验证
  • A/B 测试中顺带排查 §3.3 替代根因(TFLOOR/ND/DDNU/原子数据/LTE 干净度)
  • 若方案 A 救回率远低于 30%:转向排查 ITEK/KantorovichA6)或 §3.3 替代根因
  • 若数据支持:实施优先级 1(自适应回退)
  • 若需根治:改 tlusty208.f 源码 + 重编(优先级 2/5

八、修订审计记录(2026-08-11)

初版报告完成后,作者调用三个独立 subagent 分别审查「数据论断 / TLUSTY 源码 / 框架源码与结论合理性」,作者对最关键的两条(A3 作用域、NaN 计数)亲自复核确认属实,随后按审查意见修订全文。本节记录所有修订,保留可追溯性。

8.1 已修正的错误

# 位置 初版错误 修订内容 严重度
1 §2.3 atmosphere_has_nan: true (1033/1105) —— 实为 434/11051033 是 seed_nc 首失败点数,被误当 NaN 计数) 改为 434/1105,并按 NaN 拆分两档失效特征 🔴严重
2 §3.1 A3 PSI 布局标签张冠李戴:"结构量被 ORELAX 松弛、布居不被松弛" —— 实际相反(ORELAX 松弛布居,结构量不被松弛) 重写 A3,按 tlusty208.f:3910-3936795-797 给出正确布局 🔴严重
3 §四 优先级 2 ORELAXR 改法是替换I.GE.I.LT.,会移除布居松弛;且 ORELAXR 在源码不存在 改为新增一行并存;标注需扩 VARNAM/PVALUE 数组,非"改 2 行" 🔴严重
4 §3.1 A1 暗示"DPSILG=10 适用于所有结构量" —— 温度实际受更严的 DPSILT(1.25) 钳到 ±25% 补 DPSILT/DPSILN/DPSILD 按变量限幅说明 🟡
5 §3.1 A2 行号 14710(实为 1470014710 是铁线 LITEK 逻辑);"第 1 步任意大"过强 行号改 14700;措辞改为"首步无 STOP,但仍受 A1 限幅钳制" 🟡
6 §3.1 遗漏 CHMAXT(默认 0.01)的过程反馈机制(1471122954-22958 新增 A5 🟡
7 §3.1 / §6.2 遗漏 ITEK/Kantorovich(默认 4iter≥5 切换);雪暴起点与之重合却标"失败相关性:低" 新增 A6ITEK 改标"待排查" 🟡
8 §3.2 B2 "雪崩后仍然把含 NaN 的 fort.7 喂给 nl"未区分 rc=0/rc≠0 区分两种 TLUSTY 退出路径 🟡
9 §3.2 B3 "ORELAX=0.3 只对结构量"(依 A3 旧错误) 改为"作用于布居" 🟡
10 §3.2 B4 "每次重跑参数完全一致(除了种子来源)"自相矛盾 区分同策略白跑 vs 跨策略换初值 🟡
11 §2.4 "种子不是问题(关键反证)"过强;exact_family 允许 ΔTeff 5000K 标题与结论改为"不是唯一根因,但不能排除贡献因素" 🟡
12 §2.5 未排除成功/失败点的种子来源混淆变量 补充混淆变量说明 🟡
13 §6.2 DPSILG 行号 1786(实为 17931786 是 POPZCH 区段);范围 1750-1806(实结束于 1804);漏列 CHMAXT/DPSILD/NDORELAX 含义写"结构量" 全表重写,每项带 PVALUE 行号与 idx 🟡
14 §6.3 末步 max_relc 漏列 (1,10] 5 点 + 17 点空 history,加总不闭合 补齐使加总闭合 🟢轻微
15 §2.1 "平均重试 7-9 次"(实均值 6.9 改为"均值 6.9 次" 🟢轻微
16 §五 救回率 30-50/70-85/90%+ 当作事实呈现(无数据支撑) 标注"假设性估计",强调方案 A 实测值是诊断信号 🟢轻微
17 §四 优先级 1 "~300 行 Rust"低估 改为"400-600 行" 🟢轻微

8.2 新增内容

  • §3.3 未排除的替代根因TFLOOR=8000K、ND=50、DDNU=50/CNU1=6、原子数据 bug、LTE 干净度、输入文件渲染 bug —— 6 条独立于 A1-A6 的数值配置假说
  • §8 本审计记录

8.3 未改动(审查确认正确)的核心结论

  • 失败点参数分布(高 Teff + 低 logg 集中):36 格 Teff×logg 矩阵全部精确
  • 1105 全部走完 cold_run→seed_steptasks 历史验证
  • 首失败阶段 seed_nc=1033/nc=72
  • 种子方向 poor=662/rich=373/same=70 + 全 exact_family
  • 两条 max_relc 雪崩轨迹样例(逐值)
  • §2.5 危险区 cold/seed 成功拆分(4 格)
  • 框架 B1(无链内重试)、B4(数值参数零扰动)
  • 整体方向:物理吸引域窄 + 框架无自适应 → 方向正确,仅论证细节有上述瑕疵

8.4 审查方法

三个 Explore subagent 并行独立审查,任务是挑错找反例而非确认

  • 数据核验:12 项独立 SQL/脚本复现(9 一致 / 2 轻微 / 1 错误)
  • TLUSTY 源码:8 项行号/逻辑/默认值核对(数值 9/9 对 / 行号错位多 / A3 布局颠倒)
  • 框架源码与结论:8 项源码引用 + 因果严谨性(B1/B3/B4 扎实 / 种子反证与救回率有漏洞)

作者对最致命两条(#1 A3、#2 NaN)用独立脚本/源码核对确认属实后,方才修订。