物理修复(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 篇根因分析/验证文档
34 KiB
TLUSTY 不收敛根因分析(1105 个网格点)
日期:2026-08-11
数据来源:/home/fmq/下载/dcts.db(server DB 快照)+ data/salvage/(8320 次任务尝试的完整产物)
源码版本:tlusty208.f(50010 行)+ dcts 框架 crates/{common,node,server}
工作流:sdB_cno(grid 共 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 冻结 populations(tlusty208.f:1937),不是禁 Kantorovich;IACC=0→ 被 clamp 回 7(tlusty208.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 的完整退化路径,两种策略都失败。这不是种子选择问题,不是单纯的初值问题,而是两个因素叠加:
- 物理侧——TLUSTY 的 Newton 完全线性化求解器在「高 Teff + 低 logg」参数角落的吸引域过窄,辐射场对布居数极度敏感;
- 工程侧——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 次=199,6 次=135,7 次=462,8 次=275,9-13 次=33),在两种策略间反复跳。最新任务全部停在 failed_stage=tlusty、tlusty_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_nc(seed_step 第一阶段) |
1033/1105 (93%) | max_relc 单调雪崩 |
nc(cold_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=NaNK、emflux 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_family(d_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
⚠️ 校正:初版标题"种子不是问题(关键反证)"措辞过强。准确结论是种子选择不是唯一根因,但不能排除它是贡献因素之一:
- "exact_family" 不等于物理极近:判定允许
ΔTeff < 5000K(半步容忍)。sdB 网格 Teff 步长 10000K 时,55k↔60k 相邻档互作种子的辐射场(Lyman 紫外主导)差异可能很大——"exact_family" 标签覆盖了 Teff 仍可差一整档的事实。- 443 个 rich/same 方向失败(373 rich + 70 same):seed_finder 自身的回测数据(
seed_finder.rs:14-25)称富方向成功率仅 3-11%,贫方向 42-54%。这部分失败可能部分源于种子方向不佳。- 种子质量未审:失败点用的种子虽来自收敛邻居,但该邻居是在多少迭代数、多干净的 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 185,tlusty208.f:1793),允许单步相对修正达 10 倍。
⚠️ 重要校正:DPSILG 这个宽松限幅(±10×)主要作用于辐射场(
I ≤ NFREQE)和布居数块(I ≥ NFREQE+INSE)。紧随其后tlusty208.f:14654-14677有一组逐变量更严格的限幅,按INRE/INHE/INPC/INDL偏移把对应槽位钳到更窄:
- 温度 T(
I = NFREQE+INRE):受DPSILT=1.25钳制 → 单步上限仅 ±25%- 总粒子数 TOTN(
I = NFREQE+INHE)、电子密度 ne(I = 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 逻辑在 SOLVES(
tlusty208.f:15047)和 RYBSOL(tlusty208.f:47587)另两条求解路径各有一份。
A3. ORELAX 只阻尼布居数,不阻尼辐射场与温度等结构量 — tlusty208.f:14647
C over-relaxation
IF(I.GE.NFREQE+INSE) CHAN=ORELAX*CHAN
⚠️ 重大校正:初版把 PSI 向量布局写反了。经核对
tlusty208.f:3910-3936(PSY0 各槽位赋值)与tlusty208.f:795-797(INRE/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 77,tlusty208.f:1767)是"最后一次迭代最大修正"的终值门槛。CHMAX 本身对过程无约束——但它不是唯一的过程机制,见 A5/A6。
A5. ⚠️ 初版遗漏:CHMAXT 驱动的过程反馈 — tlusty208.f:14711, 22954-22958
初版称"过程爆掉只能靠 NITER 自然耗尽"是夸大。源码中存在由 CHMAXT(默认 0.01,PVALUE idx 80,tlusty208.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=4(PVALUE idx 130,tlusty208.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_chain(runner.rs:20-98)一旦构造完成参数就是死的。conv_check::check_fort9(conv_check.rs:204-217)判完发散后,runner.rs:564-570 只在 require_converged=true 时 break,否则继续——没有任何"发现 max_relc 超过阈值就降低 DPSILG / 加大 ORELAX / 重跑一次"的反馈机制。
B2. seed_nc 的 require_converged=false 反而帮了倒忙
default_seed_chain 第一阶段设 require_converged=false("接受非收敛种子"),本意是"给 nl 一个起点就行"。runner.rs:518-521 在 rc==0 && fort7.is_file() 分支无条件把本阶段 fort.7 拷成下一阶段的 current_seed——stage 间种子传递路径上没有 atmosphere_has_nan 检查。
⚠️ 校正:初版称"雪崩后仍然把含 NaN 的 fort.7 喂给 nl"措辞过死。是否真的传 NaN 取决于 TLUSTY 的退出方式:
- 若雪崩撞上 1e16 STOP(A2)→ rc≠0 → 走
runner.rs:522-531else 分支,不拷 fort.7;- 若跑完 NITER 自然结束(rc=0)但
fort.7已含 NaN → 会拷给 nl,nl 自然也爆。§2.3 实测 434/1105 真有 NaN 大气——这部分点确属此路径。其余 671 点是雪崩但大气可读,被后验校验挡下。两档都要修。
B3. CHMAX=None 让 TLUSTY 走默认,但没把更保守的 DPSILG/ORELAX 一起传
所有默认阶段 chmax=None(runner.rs:20-98),runner 用 Rust 侧 eff_chmax=0.001(runner.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. 整点重试无参数扰动
策略 fallback(scheduler.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-90k,TFLOOR=8000K 离表层不远;若发散把温度推向 TFLOOR,钳制本身可能破坏 H 电离平衡(8000K 附近 H 电离度突变) |
对失败点试 TFLOOR=30000/40000,看救回率 |
| ND=50 在高 Teff+低 logg 下分辨率不足 | config.rs:758 框架覆盖为 50(TLUSTY 默认 70,config 自承)。高 Teff+低 logg 表层温度梯度陡峭,ND=50 可能欠分辨表层 → 数值发散 |
对失败点试 ND=70/100 |
| DDNU=50/CNU1=6 频率网格在高 Teff 不足 | config.rs:769-776 框架 DDNU=50(TLUSTY 原始 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 的 .err 含 IEEE_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 监控 + 回退重跑":
- 用
tokio异步跟踪fort.9增长,每完成 1-2 次迭代解析一次max_relc; - 若前 3 步
max_relc单调上升且超过阈值(如 1e2),SIGTERM 当前子进程; - 用更保守的参数(
DPSILG减半、ORELAX减半)重跑该阶段; - 最多回退 2-3 次,每次更保守。
这是最直接的对症方案——雪崩在前 3 步就能看出来,没必要跑到 NITER。
实现成本:中-高。需要在 ChainStep 加 dpsilg/adaptive 字段,runner 加监控+回退逻辑。初版估的 "~300 行 Rust" 偏低——实时跟踪 fort.9 增长(TLUSTY 边写边追加,需处理部分写入)+ SIGTERM 子进程后正确 reap + 清理中间文件 + 回退状态机,工程量更大,预估 400-600 行。
优先级 2:给辐射场与温度/ne 加欠松弛(TLUSTY 源码侧,治本但要改二进制)
⚠️ 校正:初版给的代码是替换原逻辑(
I.GE.→I.LT.),会移除原布居松弛——这反而让布居失去保护。正确改法是新增一行,且ORELAXR在源码中本不存在。
正确改法(tlusty208.f:14647,SOLVE 与 SOLVES 14996 两处都要改):
C 现状:只有布居(I≥NFREQE+INSE)被 ORELAX 松弛
IF(I.GE.NFREQE+INSE) CHAN=ORELAX*CHAN
C 新增:辐射场(I≤NFREQE)与结构量(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 校正后)。给它们加欠松弛是物理上最对症的修复。
注意:
- 不是"改 2 行 + 重编"。
ORELAXR在tlusty208.f中完全不存在——需扩 VARNAM/PVALUE 两个 DATA 数组各加一项、加 nst 关键字解析、加 PVALUE 默认值(建议 1.D0 不变,仅难收敛阶段通过 nst 覆盖为 0.3-0.5)。工程量约 30-50 行 Fortran + 重编 + 更新assets/tlusty_static。 - 与 NLAMBD(λ-迭代)的交互未验证:辐射场在每个深度通过 NLAMBD 机制单独更新,给 Jν 加欠松弛可能与该机制冲突,需小规模测试验证。
- 触及二进制,需走完整发布流程。
优先级 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_nan 或 best_max_relc > 1e3 时,不要把它的 fort.7 当作 nl 的种子;要么直接判失败触发下一策略,要么回退用原 seed_atmos(邻居的干净种子)重新喂 nl。
实现成本:极低。~20 行 Rust。
优先级 5:首步发散保护(TLUSTY 源码侧)
tlusty208.f:14700(SOLVE)/ 15047(SOLVES)/ 47587(RYBSOL)三处把 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 | 优先级 3(DPSILG=3)+ 优先级 4(NaN 拒绝) | 30-50% |
| 方案 B | 方案 A + 优先级 1(自适应回退) | 70-85% |
| 方案 C | 方案 B + 优先级 2/5(改 TLUSTY 源码) | 90%+ |
⚠️ 校正:初版无标注地把上述救回率当作事实呈现。这些百分比是无数据支撑的先验猜测,仅用于排列方案的优先级顺序,不代表实测预期。尤其"方案 C 90%+"与"吸引域窄"的论断存在张力——吸引域窄意味着即便阻尼到位,某些初值仍落吸引域外。方案 A 的实测救回率是最关键的诊断信号:若远低于 30%,说明 DPSILG 不是主因,应转向排查 ITEK/Kantorovich(A6)或 §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 结束于 1804,1806 是 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-43,RICH_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/Kantorovich(A6)或 §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/1105(1033 是 seed_nc 首失败点数,被误当 NaN 计数) |
改为 434/1105,并按 NaN 拆分两档失效特征 | 🔴严重 |
| 2 | §3.1 A3 | PSI 布局标签张冠李戴:"结构量被 ORELAX 松弛、布居不被松弛" —— 实际相反(ORELAX 松弛布居,结构量不被松弛) | 重写 A3,按 tlusty208.f:3910-3936 与 795-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(实为 14700,14710 是铁线 LITEK 逻辑);"第 1 步任意大"过强 | 行号改 14700;措辞改为"首步无 STOP,但仍受 A1 限幅钳制" | 🟡中 |
| 6 | §3.1 | 遗漏 CHMAXT(默认 0.01)的过程反馈机制(14711、22954-22958) |
新增 A5 | 🟡中 |
| 7 | §3.1 / §6.2 | 遗漏 ITEK/Kantorovich(默认 4,iter≥5 切换);雪暴起点与之重合却标"失败相关性:低" | 新增 A6;ITEK 改标"待排查" | 🟡中 |
| 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(实为 1793,1786 是 POPZCH 区段);范围 1750-1806(实结束于 1804);漏列 CHMAXT/DPSILD/ND;ORELAX 含义写"结构量" | 全表重写,每项带 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_step:tasks 历史验证 ✅
- 首失败阶段 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)用独立脚本/源码核对确认属实后,方才修订。