# 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)→ 发散幅度降 1e12~1e14×,但仍不收敛 (末 relc 1e3~1e7),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 次=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 ``` > ⚠️ **校正**:初版标题"种子不是问题(关键反证)"措辞过强。准确结论是**种子选择不是唯一根因,但不能排除它是贡献因素之一**: > 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` ```fortran 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` ```fortran 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` ```fortran 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` ```fortran 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`)驱动的两层过程反馈: ```fortran 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`)。源码: ```fortran 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-531` else 分支,**不拷** 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 监控 + 回退重跑"**: 1. 用 `tokio` 异步跟踪 `fort.9` 增长,每完成 1-2 次迭代解析一次 `max_relc`; 2. 若前 3 步 `max_relc` 单调上升且超过阈值(如 1e2),**SIGTERM 当前子进程**; 3. 用更保守的参数(`DPSILG` 减半、`ORELAX` 减半)重跑该阶段; 4. 最多回退 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` 两处都要改): ```fortran 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 校正后)。给它们加欠松弛是物理上最对症的修复。 **注意**: 1. **不是"改 2 行 + 重编"**。`ORELAXR` 在 `tlusty208.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`,通过 `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),防止首步就冲出吸引域: ```fortran 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)用独立脚本/源码核对确认属实后,方才修订。