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 篇根因分析/验证文档
This commit is contained in:
@@ -0,0 +1,73 @@
|
||||
# 冷启动收敛调查与更正(2026-08-12)
|
||||
|
||||
## ⚠️ 结论更正(重要)
|
||||
|
||||
本文件是对 2026-08-11 系列"收敛修复"(`itek:0`/`iacc:0`/`DPSILG=1.5`)的彻底复核。
|
||||
|
||||
**最终结论:以上配置改动都无法让失败网格点从冷启动收敛,已全部回退。**
|
||||
|
||||
此前声称"禁用 Kantorovich 后 iter5 从 1e5~1e7 降到 0.2~1.0、点收敛"是**错误结论**,
|
||||
源于在探测程序运行中解析 fort.9 时只读到部分深度(TLUSTY 逐深度写入、缓冲未刷新),
|
||||
把"部分深度的最大值"误当成了"该迭代的真实最大值"。
|
||||
|
||||
**真实数据(完整 50 深度重新解析)**:
|
||||
|
||||
| 点 (he-2/he0 中等难度) | baseline 末relc | nokant(ITEK≥NITER) 末relc |
|
||||
|----|:---:|:---:|
|
||||
| he-2_c-1_n-2_o-4 | 4.5e+15 | 1.7e+03 |
|
||||
| he-2_c-2_n-4_o-4 | 6.3e+09 | 1.5e+07 |
|
||||
| he-2_c-4_n-1_o-1 | 9.7e+09 | 1.5e+04 |
|
||||
| he0_c-1_n-3_o-2 | 4.5e+09 | 7.0e+04 |
|
||||
| he0_c-2_n-2_o-3 | 1.4e+18 | 1.0e+04 |
|
||||
|
||||
**6 个测试点 × 4 种配置 = 24 次运行,0 次收敛。** 最难点
|
||||
`t50000_g5.0_he-4_c-4_n-4_o-4` 用 itek=999 + orelax=0.5/0.1 也发散
|
||||
(iter10 relc≈6e5,两种 orelax 结果几乎相同)。
|
||||
|
||||
## 调查中确认的源码事实(仍然有效)
|
||||
|
||||
| 发现 | 源码依据 |
|
||||
|------|---------|
|
||||
| `ITEK=0` **冻结 populations**(nitzer=0),不是禁用 Kantorovich | `tlusty208.f:1937` `if(nitzer.gt.itek) nitzer=itek` |
|
||||
| `IACC=0` **是空操作**(被 clamp 回默认 7) | `tlusty208.f:1928` `IF(IACC.LE.4) IACC=7` |
|
||||
| 默认 `ITEK=4` 在 iter≥5 冻结 Jacobian(Kantorovich) | `tlusty208.f:848-857` |
|
||||
| 默认 `IACC=7` 在 iter≥7 外推 PSY0(Ng/ACCEL2) | `tlusty208.f:29704` |
|
||||
|
||||
禁用 Kantorovich+Ng(设 `itek`/`iacc` > NITER)**确实**把发散幅度降低了约
|
||||
1e12~1e14×(如 he0_c-2_n-2: baseline 1e18 → nokant 1e4),但**达不到收敛**——
|
||||
Newton 迭代自身在 grey LTE 起点即处于收敛域外,振荡不衰减。
|
||||
|
||||
## 物理背景
|
||||
|
||||
失败点的特征:**He-poor(he=−4)+ 高 Teff(50-60kK)+ 低 logg(5.0-5.5)+ 极贫
|
||||
CNO**。DB 统计:
|
||||
|
||||
| He 丰度 | 收敛 | 失败 | 失败率 |
|
||||
|---------|------|------|--------|
|
||||
| he=+2(富氦)| 359 | 25 | **7%** |
|
||||
| he=0 | 177 | 206 | 54% |
|
||||
| he=−2 | 139 | 245 | 64% |
|
||||
| he=−4(贫氦)| 132 | 252 | **66%** |
|
||||
|
||||
这些点(纯 H 极端 NLTE 电离)从 grey LTE 模型冷启动,首次 NLTE 迭代的 POP 修正
|
||||
就达 1e2~1e4(相对变化),Newton 步长超出收敛域。无论加速/阻尼参数如何设置,
|
||||
迭代都在 1e2~1e6 之间振荡发散。**调参救不回收敛域外的问题。**
|
||||
|
||||
## 结论与建议
|
||||
|
||||
1. **YAML 已回退**:`workflows/sdB_cno.yaml` 恢复生产配置(nc NITER=10、nl NITER=100
|
||||
orelax=0.5、seed_nc NITER=20、seed_nl NITER=100 orelax=0.5,itek/iacc 用默认)。
|
||||
此前所有"修复"(itek:0 / iacc:0 / DPSILG=1.5 / itek=999)均无效或有害,全部移除。
|
||||
2. **真正的可行方向**(需框架/Rust 改动,非 YAML 配置):
|
||||
- **连续法(continuation)**:从已收敛的较低 Teff(如 40-45kK)模型出发,
|
||||
以该模型为 fort.8 热启动,逐步升高 Teff——NLTE 大气建模处理硬收敛点的标准方法。
|
||||
- **改进 seed_step 种子选择**:优先选 **He 丰度匹配**的已收敛邻居(he=+2 邻居
|
||||
收敛率 93% vs he=−4 邻居 34%),而非仅按 Teff/logg 距离。
|
||||
- 或接受这些极端点(he=−4 + 极贫 CNO + 50-60kK)需要人工/半自动处理。
|
||||
|
||||
## 相关计算文件
|
||||
|
||||
- `test/20260811_convergence_fix_abtest/runs_nc_probe/`:5 个中等难度点的
|
||||
baseline / nokant / nokant_relax 三配置完整 10 迭代轨迹(fort.9),全部发散。
|
||||
- `test/20260811_convergence_fix_abtest/runs_verify/`、`runs_verify_01/`:
|
||||
最难点完整冷链(lte→nc→nl)验证,nc 发散、nl 因 NaN 假收敛。
|
||||
+25
-11
@@ -83,11 +83,7 @@ DCTS 运行时所依赖的物理计算底座二进制文件 `assets/tlusty_stati
|
||||
* **标准编译命令**:
|
||||
```bash
|
||||
cd /home/fmq/program/tlusty/tl208-s54/tlusty
|
||||
gfortran -fno-automatic -O3 -o ../dcts/assets/tlusty_static tlusty208.f
|
||||
```
|
||||
* **大内存寻址编译选项 (推荐超大能级网格使用)**:
|
||||
```bash
|
||||
gfortran -fno-automatic -mcmodel=large -O3 -o ../dcts/assets/tlusty_static tlusty208.f
|
||||
gfortran -fno-automatic -fno-toplevel-reorder -mcmodel=medium -O3 -o ../dcts/assets/tlusty_static tlusty208.f
|
||||
```
|
||||
|
||||
#### 2. 编译 SYNSPEC 理论光谱合成引擎 (`assets/synspec_static`)
|
||||
@@ -98,18 +94,36 @@ DCTS 运行时所依赖的物理计算底座二进制文件 `assets/tlusty_stati
|
||||
* **标准编译命令**:
|
||||
```bash
|
||||
cd /home/fmq/program/tlusty/tl208-s54/synspec
|
||||
gfortran -fno-automatic -O3 -o ../dcts/assets/synspec_static synspec54.f
|
||||
```
|
||||
* **大内存寻址编译选项**:
|
||||
```bash
|
||||
gfortran -fno-automatic -mcmodel=large -O3 -o ../dcts/assets/synspec_static synspec54.f
|
||||
gfortran -fno-automatic -fno-toplevel-reorder -mcmodel=medium -O3 -o ../dcts/assets/synspec_static synspec54.f
|
||||
```
|
||||
|
||||
#### 关键编译选项说明:
|
||||
|
||||
- `-fno-automatic`: 禁用局部变量的自动栈分配(强制将局部变量保存在静态内存区)。这是保证传统 FORTRAN 77 程序正常运行的关键参数,防止大型局部数组造成栈溢出(Stack Overflow)或段错误(Segmentation Fault)。
|
||||
- `-O3`: 开启全量 LLVM/GCC 代码优化,极大加快完全线性化/加速 Lambda 迭代(CL/ALI)及辐射转移方程形式解的计算速度。
|
||||
- `-mcmodel=large`: 当模型数组与数据段超越 2GB 寻址限制时,允许可执行文件使用 64 位大内存寻址模式。
|
||||
- **`-fno-toplevel-reorder`(关键修复,2026-08-11)**: 禁用 gfortran 的顶级函数/变量重排优化。**SYNSPEC 与 TLUSTY 必须加此选项**——不加会导致 Balmer 跃迁(3646 Å)之后整个波长范围输出 NaN(`-O1` 及以上均触发,仅 `-O0` 幸免)。根因:该优化破坏了 SYNSPEC/TLUSTY 依赖的 COMMON 块数据初始化顺序,使氢线不透明度计算读到未初始化的内存。此项优化缺失对计算速度无显著影响(实测 `-O3 -fno-toplevel-reorder` 与 `-O3` 速度相当)。
|
||||
- `-mcmodel=medium`: 允许大型 COMMON 块(SYNSPEC 的 LINDAT/PARAMS COMMON 块超过 2GB)使用 64 位寻址,避免链接器 "relocation truncated to fit" 错误。
|
||||
|
||||
#### 3. 谱线表(SYNSPEC fort.19)部署
|
||||
|
||||
SYNSPEC 的谱线表通过 `assets/data/{linelist}` 分发(`/api/data/file/{name}` 路由),工作流 YAML 的 `linelist` 字段指定文件名。可用线表:
|
||||
|
||||
| 线表 | 波长范围 | 线数 | 大小 | 适用场景 |
|
||||
| :--- | :--- | :--- | :--- | :--- |
|
||||
| `gfATO.dat` | 18–23000 Å | 230 万 | 238 MB | **默认/推荐**:全波段(EUV+UV+光学+近/中红外) |
|
||||
| `gfVIS99.dat` | 3000–7550 Å | 15 万 | 12 MB | 仅可见光诊断(快速) |
|
||||
|
||||
**部署步骤**(`gfATO.dat` 不入库,需手动放置):
|
||||
```bash
|
||||
# 把 gfATO.dat 复制到 server 的 assets/data/ 目录(被 .gitignore 排除)
|
||||
cp /path/to/gfATO.dat assets/data/gfATO.dat
|
||||
# node 端首次连接时自动从 server 下载(238MB,首次较慢,之后缓存)
|
||||
```
|
||||
|
||||
**SYNSPEC 波长范围限制**(实测):
|
||||
- 可靠范围:**100–23000 Å**(超出此范围输出截断,仅 ~235 行无效数据)
|
||||
- 工作流默认配置:`alam0: 100.0, alast: 20000.0`(留安全余量)
|
||||
- 波长范围由工作流 YAML `synspec_input.line6.alam0/alast` 控制,须与线表覆盖范围匹配
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -0,0 +1,97 @@
|
||||
# 400 失败点二次分析与 NaN 伪收敛修复(2026-08-17)
|
||||
|
||||
> 背景:三层修复(nst 行宽 / nl_direct 回退 / seed_finder 边界)部署重跑后,
|
||||
> 9216 点中 8816 完成、**400 失败**。数据源:最新 DB 快照 `/home/fmq/下载/dcts.db`
|
||||
> + 全量更新后的 `data/salvage/`(6 节点)。
|
||||
|
||||
## 一、400 点分类(按每点最新一次任务)
|
||||
|
||||
| 类别 | 数量 | 机制 |
|
||||
|---|---:|---|
|
||||
| **NaN 伪收敛** | **261** | 见 §二,本轮核心 bug |
|
||||
| 真发散(nl/nl_direct 1e16 STOP) | ~131 | 20kK/logg≥6 富氦簇(~50)+ 60kK/logg≤5.5 贫氦簇(~65)等 |
|
||||
| 收敛但最深层能量残差 1.0–2.3% 超 1% 阈值 | 7 | 边际案例 |
|
||||
|
||||
分布(NaN 伪收敛修复前):teff=20000:76、55000:77、60000:121 为主。
|
||||
|
||||
## 二、NaN 伪收敛:根因链
|
||||
|
||||
生产工件实证(t60000_g6.0_he-4_c-4_n-4_o-4,多节点一致):
|
||||
|
||||
1. `seed_nc` 发散(best_max_relc 1.15e15),fort.7 含 4823 行 NaN → **污染种子**;
|
||||
2. `nl` 从污染种子启动 → TLUSTY 立即崩溃,fort.9 写出**全 `0.00E+00`**
|
||||
(NaN 参与的相对变化无法计算,写出零;fort.9 尾部 TEMP 列可见 NaN);
|
||||
3. `check_fort9`:max_relc=0 < chmax=1e-3 → **`converged=true`(伪收敛)**;
|
||||
4. 因 nl 被判"收敛",**nl_direct 回退被跳过**(回退仅在 require_converged 失败时触发);
|
||||
5. 最终门槛全灭(emflux nan_ratio=100%、温度 NaN、bfac 38% 极端值)→ 点失败,
|
||||
282/288 个 emflux 失败的 ratio 恰为 0(`integrated_flux=0, n_points=0`)。
|
||||
|
||||
## 三、修复
|
||||
|
||||
`crates/common/src/runner.rs` `execute_tlusty_stage()`:
|
||||
- 阶段被判 converged 后复查本阶段 fort.7(`atmosphere_has_nan`,含 NaN/Inf/`***`/
|
||||
E+300 检测),命中即否决收敛并标注 note;
|
||||
- 被否决的阶段**不产出种子**(`produced_seed=None`),阻断污染向下游传播。
|
||||
|
||||
测试:`unit_nan_pseudo_convergence_veto`(全零 fort.9 + NaN fort.7 → seed_nc 判失败、
|
||||
nl 回退原始种子收敛);全 workspace 测试通过。
|
||||
|
||||
## 四、直测验证(test/20260817_failed400/,direct-nl 复现 nl_direct 回退路径)
|
||||
|
||||
| 测试 | 点 | 种子 | 结果 |
|
||||
|---|---|---|---|
|
||||
| q1 | t60000_g6.0_he-4_c-4_n-4_o-4(NaN 伪收敛簇) | 邻点 o-2 的收敛 .7 | ✅ **12it 收敛 7.0e-4**,温度三项全过(表层 0.79×Teff),emflux 4π∫Hλ/σT⁴=1.0014 |
|
||||
| q2 | t20000_g6.5_he2_c-4_n-4_o-2(20k 富氦簇) | 邻点 c-3_n-3_o-2 | ❌ iter42 发散 STOP(1.35e18) |
|
||||
| q3 | t60000_g5.0_he-4_c-4_n-4_o-2(60k g5 簇) | 邻点 o-4 的收敛 .7 | ❌ 100it 耗尽,max_relc 546(温度结构物理但未收敛) |
|
||||
|
||||
q1 证明:NaN 伪收敛簇(261 点,65%)在修复后经 nl_direct 回退路径可完整恢复
|
||||
(数值收敛 + 物理门槛全过)。q2/q3 为真发散硬核(~131 点),所用邻居种子不足以
|
||||
收敛,需依赖生产的 exact_family 种子选择、多策略重试或 Teff 连续步进。
|
||||
|
||||
## 五、三类问题的验证测试与方案(test/20260817_failed400/)
|
||||
|
||||
### 5.1 NaN 伪收敛簇(261 点)回收估计验证 — 成立
|
||||
|
||||
分层抽样 17 点(每 teff 档 2 个,种子 = 完成点中 CNO 精确匹配/最近邻的
|
||||
salvage .7),direct-nl(= nl_direct 回退的等价路径):
|
||||
|
||||
- **11/17 收敛**(20–41 迭代),温度结构三项全过,emflux 1.0001–1.0017 全达标;
|
||||
- 6 个失败均为种子距离过大(Δlogg 0.5–1.5)直接 STOP——生产端 seed_finder 有
|
||||
多候选 + 多策略重试(attempt 至 11 次),实际回收率高于单种子 65%;
|
||||
- 结论:"修复后预期回收 ~261 点"估计成立且偏保守。
|
||||
|
||||
### 5.2 能量边际点(7 个)— nl_tight 回退,已实现
|
||||
|
||||
实测(mg_tight):从自身最终模型续迭代、CHMAX 1e-3→1e-4,约 19 迭代后
|
||||
最深层 (RAD+CON)/TOT 残差 0.0199/0.0228 → **0.0011/0.0016**(降 ~10×)。
|
||||
|
||||
`runner.rs` 新增 nl_tight 回退:能量门槛失败 + 最终大气无 NaN 时,以
|
||||
`<label>_tight`(CHMAX÷10)从自身模型续迭代,成功则刷新最终大气/快照并
|
||||
重判能量。单测 `unit_nl_tight_fallback_wiring` 通过。
|
||||
|
||||
### 5.3 真发散硬核(~131 点)— 连续步进(continuation)方案验证通过
|
||||
|
||||
| 批次 | 配置 | 结果 |
|
||||
|---|---|---|
|
||||
| hard_base(16 点分层抽样) | direct-nl + 最近完成点种子 | 4/16 |
|
||||
| hard_ore03(12 个失败点) | 同上 + ORELAX=0.3 | 1/12 |
|
||||
| ladder_60k_he4(60k 贫氦簇代表) | 55k→57.5k→60k 三步 Teff 步进 | ✅ 28it/3.9e-4,物理过,emflux=1.0008 |
|
||||
| ladder_20k_he2(20k/6.5 富氦簇代表) | g6.0→6.25→6.5 三步 logg 步进 | ✅ 27it/8.1e-4,物理过,emflux=1.0112(<2% 阈值) |
|
||||
|
||||
两个最难簇的代表点在 direct-nl、ORELAX 全部失败后,**仅靠 2 个中间参数的
|
||||
连续步进即完全收敛**。机理:单步 Δ 超出 Newton 收敛域时,把参数空间距离
|
||||
切分为多段,每段以段首收敛解热启动段尾。
|
||||
|
||||
### 5.4 生产化建议
|
||||
|
||||
1. 部署本修复(NaN 伪收敛否决 + nl_tight 回退)重试 400 点:预期回收 261 簇
|
||||
(>65%/种子×多候选)+ 7 个能量边际点;
|
||||
2. 为剩余硬核实现 ladder 策略:失败点自动生成 2–3 个中间参数步进链
|
||||
(优先 logg 维度,其次 Teff;步长 Δlogg≤0.25 / ΔTeff≤2.5kK),
|
||||
每步独立 nl + require_converged 串联,最后一步产出目标点模型。
|
||||
实测两簇代表点总耗时 <6 分钟/点(3 步 × ~2 分钟)。
|
||||
|
||||
## 六、遗留问题
|
||||
|
||||
- 20k/6.5 富氦簇解分支跳变:同一点不同种子在好解/坏解(emflux 0.69)间跳变,
|
||||
ladder 步进天然规避(每步验证收敛与物理性后才前进)。
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 182 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 382 KiB |
@@ -0,0 +1,693 @@
|
||||
# SYNSPEC 全局 NaN 失效修复与全波段 SED 配置变更
|
||||
|
||||
> 日期:2026-08-11
|
||||
> 范围:SYNSPEC 理论光谱合成引擎的系统性数值失效(全局 NaN)根因定位、修复,以及线表/波长范围配置变更。
|
||||
> 影响:数据库中 **7452 个** `synspec_status=converged` 的网格点其 `.spec` 光谱全部含 ~73% NaN,必须用修复后的二进制重算。
|
||||
> 前置文档:`docs/spectrum_correctness_analysis.md`(物理正确性五重硬门槛)、`docs/tlusty&synspec收敛性判断.md`(rc 不可靠性与漏洞修复史)。
|
||||
|
||||
---
|
||||
|
||||
## 0. TL;DR
|
||||
|
||||
| 维度 | 结论 |
|
||||
|------|------|
|
||||
| **症状** | SYNSPEC 产出的 `.spec` 在 Balmer 跃迁(3646 Å)之后整个波长范围输出 NaN(~73% 数据点无效),但 SYNSPEC 正常退出(rc=0),无任何错误日志 |
|
||||
| **根因** | gfortran 的 `-ftoplevel-reorder` 优化(`-O1` 起启用)破坏了 SYNSPEC FORTRAN 77 COMMON 块的数据初始化顺序,使氢线不透明度计算在 Balmer 系限后读到未初始化内存。深度原理见 **§9** |
|
||||
| **影响范围** | 所有用旧 `-O3` 二进制计算的网格点(7452 个 `synspec_status=converged`),与大气参数、fort.55 配置、线表无关 |
|
||||
| **修复** | 编译时加 `-fno-toplevel-reorder`,保留 `-O3` 其他优化。速度无显著影响(~4s/点 vs ~3.5s/点) |
|
||||
| **附带改进** | 线表从 gfVIS99.dat(3000-7550Å)升级为 gfATO.dat(18-23000Å 全波段),波长范围扩至 100-20000Å |
|
||||
| **下一步** | 用修复后的 `assets/synspec_static` 重新部署,重算全部网格点 |
|
||||
|
||||
---
|
||||
|
||||
## 1. 背景:为何怀疑 SYNSPEC 光谱有问题
|
||||
|
||||
### 1.1 已知的 fort.55 错位 bug(已修复,历史背景)
|
||||
|
||||
`docs/spectrum_correctness_analysis.md §5` 记录了 `fort55_writer.rs` 的字段错位 bug:旧实现的 `idrv/ifreq` 被 SYNSPEC 当作 `IDSTD/IPRIN` 读取,导致 IDSTD=50(最深层而非自动取 2ND/3≈33),影响光谱线强归一化。该 bug 已在 2026-08-07 修复(重构为 9 子结构体按行一一对应)。
|
||||
|
||||
但 fort.55 错位修复后,文档明确指出"所有光谱需重算"(`§5.3 重算指引`)。本次会话正是在执行这个重算验证时,发现了更深层的系统性问题。
|
||||
|
||||
### 1.2 salvage 数据的初步检查
|
||||
|
||||
salvage 目录(`data/salvage/`)保存了 5 个计算节点的完整产物(8319 个网格点的 `.7` 大气 + `.spec` 光谱 + 各阶段快照)。初步检查发现:
|
||||
|
||||
- `.spec` 文件大小正常(~11MB,432827 行),看似完整
|
||||
- SYNSPEC 退出码 rc=0,`.log` 无任何错误
|
||||
- `conv.json` 记录 `synspec_status=converged`
|
||||
|
||||
但文件大小正常 ≠ 内容有效——这正是"静默错误"的危险之处。
|
||||
|
||||
---
|
||||
|
||||
## 2. 检测方法:如何识别 NaN 网格点
|
||||
|
||||
### 2.1 spec_is_valid 校验器(项目已有)
|
||||
|
||||
`crates/common/src/conv_check.rs:290-335` 实现了 `spec_is_valid()` 函数,在 SYNSPEC 运行后立即校验 `.spec`:
|
||||
|
||||
```rust
|
||||
pub fn spec_is_valid(path: &Path) -> Option<String>
|
||||
```
|
||||
|
||||
校验项(任一命中即返回失败原因字符串):
|
||||
1. 文件缺失或无法读取
|
||||
2. **含 NaN/Inf/`***` 溢出行**(逐行扫描,统计坏行数)
|
||||
3. 有效行数不足(< 10 行)
|
||||
4. 流量全为零
|
||||
5. 文件为空
|
||||
|
||||
**关键**:该校验器在 `runner.rs:645` 调用,命中无效则置 `synspec_rc` 非零 + 写入 `synspec_error`,触发 reporter 判 Failed + 策略链回退。
|
||||
|
||||
### 2.2 为何 7452 个点仍标记为 converged
|
||||
|
||||
`spec_is_valid` 是在 fort.55 错位修复(2026-08-07)的同一次提交中引入的。在此之前计算的网格点没有经过该校验——它们的 `.spec` 虽然 73% 是 NaN,但当时只做了 `is_file()` 存在性检查就标记为成功。
|
||||
|
||||
数据库查询确认:
|
||||
```sql
|
||||
SELECT synspec_status, count(*) FROM grid_points WHERE status='completed' GROUP BY synspec_status;
|
||||
-- converged: 7452 ← 这些点几乎全部含 NaN(旧二进制 + 旧校验)
|
||||
-- failed: 459
|
||||
```
|
||||
|
||||
### 2.3 离线批量检测脚本
|
||||
|
||||
对任意 salvage `.spec` 文件检测 NaN 分布(本次会话使用的 Python 脚本):
|
||||
|
||||
```python
|
||||
import math
|
||||
nan_ranges = []; good_ranges = []
|
||||
in_nan = False; in_good = False; ns = 0; gs = 0; prev = 0
|
||||
with open('fort.7') as f:
|
||||
for line in f:
|
||||
p = line.split()
|
||||
if len(p) < 2: continue
|
||||
try: w, fl = float(p[0]), float(p[1])
|
||||
except: continue
|
||||
if math.isinf(w): continue # 跳过 Infinity 波长(崩溃特征)
|
||||
isn = math.isnan(fl)
|
||||
if isn:
|
||||
if in_good: good_ranges.append((gs, prev)); in_good = False
|
||||
if not in_nan: ns = w; in_nan = True
|
||||
else:
|
||||
if in_nan: nan_ranges.append((ns, prev)); in_nan = False
|
||||
if not in_good: gs = w; in_good = True
|
||||
prev = w
|
||||
if in_nan: nan_ranges.append((ns, prev))
|
||||
if in_good: good_ranges.append((gs, prev))
|
||||
```
|
||||
|
||||
典型坏谱输出(72.9% NaN):
|
||||
```
|
||||
行: 432827, NaN: 315476 (72.9%)
|
||||
正常段: [('3000', '3645')] ← Balmer 跃迁前
|
||||
NaN段: [('3645', '5600'), ('5800', '7000')] ← Balmer 跃迁后大面积 NaN
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 测试过程:从症状到根因
|
||||
|
||||
### 3.1 第一步:搭建本地 SYNSPEC 测试环境
|
||||
|
||||
SYNSPEC 运行需要 5 类输入文件(参见官方 `RSynspec` 脚本):
|
||||
|
||||
| 文件 | 来源 | 作用 |
|
||||
|------|------|------|
|
||||
| `fort.8` | `.7` 大气模型复制 | TLUSTY 收敛的大气结构 |
|
||||
| `fort.55` | `fort55_writer.rs` 生成(9 行控制卡) | 波长范围/输出模式/线处理开关 |
|
||||
| `fort.19` | symlink 到线表(gfVIS99.dat / gfATO.dat) | 谱线列表 |
|
||||
| `data/` | symlink 到原子数据目录 | 能级模型、截面、分区函数 |
|
||||
| `<name>.5` | `gen_input5.rs` 生成 | TLUSTY/SYNSPEC 共用 stdin(丰度/原子配置) |
|
||||
|
||||
```bash
|
||||
# 测试沙盒搭建
|
||||
mkdir /tmp/synspec_test && cd /tmp/synspec_test
|
||||
cp .../synspec/synspec.exe synspec
|
||||
ln -s .../data data
|
||||
cp .../assets/gfVIS99.dat fort.19
|
||||
|
||||
# 准备一个收敛点的输入
|
||||
cp <salvage>/<name>.7 fort.8
|
||||
cp <salvage>/<name>.nl.5 <name>.5
|
||||
cat > fort.55 <<'EOF'
|
||||
0 0 1
|
||||
1 0 0 0
|
||||
0 0 0 0 0
|
||||
1 1 0 0 0
|
||||
0 0 0
|
||||
3000.0 7000.0 10 0 0.0001 0.01
|
||||
0
|
||||
-1
|
||||
0 0 0
|
||||
EOF
|
||||
|
||||
./synspec < <name>.5 > <name>.log 2>&1
|
||||
```
|
||||
|
||||
### 3.2 第二步:确认问题与大气无关
|
||||
|
||||
选择不同参数(He-poor / He-rich、Teff 25k-45kK)的收敛良好点(`best_max_relc < 0.001`)测试,**全部出现相同的 NaN 分布**:
|
||||
|
||||
| 点 | 参数 | NaN 段 |
|
||||
|----|------|--------|
|
||||
| t35000_g6.5_he-4_c-2_n-3_o-2 | He-poor, 35kK | 3646-5600, 5800-7000 |
|
||||
| t25000_g6.5_he2_c-2_n-1_o-2 | He-rich, 25kK | 同上 |
|
||||
| t45000_g5.5_he2_c-1_n-3_o-4 | He-rich, 45kK | 同上 |
|
||||
|
||||
排除假设:
|
||||
- ✗ fort.55 错位(新旧格式都 NaN)
|
||||
- ✗ 大气质量(`.6` 能量守恒 `(RAD+CON)/TOT ≈ 1.000-1.002`,物理有效)
|
||||
- ✗ 连续谱计算(`.cont` 全范围无 NaN,只有含谱线的 `.spec` 坏)
|
||||
|
||||
### 3.3 第三步:精确边界定位
|
||||
|
||||
逐步缩小波长范围测试,发现 NaN 的起始点是 **3645.53 Å**——精确对应氢的 **Balmer 跃迁(3646 Å)**:
|
||||
|
||||
```
|
||||
3000-3500 Å: 0 NaN ✓
|
||||
3600-3650 Å: 464 NaN(跨 Balmer 跃迁,部分坏)
|
||||
3647-3700 Å: 5492 行全 NaN ✗(Balmer 跃迁之后)
|
||||
```
|
||||
|
||||
3646 Å = 氢的 Balmer 系限(`n=2 → ∞`),此处氢的高级 Balmer 线密集。问题指向氢线不透明度计算。
|
||||
|
||||
### 3.4 第四步:浮点陷阱调试版(关键转折)
|
||||
|
||||
用 `-ffpe-trap=invalid,zero,overflow` 编译调试版 SYNSPEC,期望在 NaN 产生瞬间中断:
|
||||
|
||||
```bash
|
||||
cd /tmp/synspec_build
|
||||
gfortran -O0 -g -fbacktrace -ffpe-trap=invalid,zero,overflow \
|
||||
-fno-automatic -mcmodel=medium synspec54.f -o synspec_debug
|
||||
```
|
||||
|
||||
> **编译注意**:SYNSPEC 的 `synspec54.f` 通过 `INCLUDE` 语句引入 `PARAMS.FOR`/`MODELP.FOR`/`LINDAT.FOR` 等,只需编译主文件。但 COMMON 块超大(`MLIN0=1200000` × 多数组),必须加 `-mcmodel=medium` 否则链接器报 `relocation truncated to fit: R_X86_64_PC32`。
|
||||
|
||||
**结果**:调试版(`-O0`)**正常退出,0 NaN,3000-7000Å 全范围完整**!
|
||||
|
||||
这是决定性转折——说明问题不在 SYNSPEC 源码逻辑,而在**编译优化**。
|
||||
|
||||
### 3.5 第五步:二分法定位优化元凶
|
||||
|
||||
对比不同优化级别:
|
||||
|
||||
| 编译选项 | 结果 |
|
||||
|----------|------|
|
||||
| `-O3`(原版) | 72.9% NaN |
|
||||
| `-O2` | 72.9% NaN |
|
||||
| `-O1` | 72.9% NaN |
|
||||
| **`-O0`** | **0 NaN ✓** |
|
||||
|
||||
`-O1` 起就触发。用二分法在 `-O1` 启用的 43 个优化项中排查:
|
||||
|
||||
1. 获取 `-O0` → `-O1` 新增的优化列表:
|
||||
```bash
|
||||
diff <(gfortran -O0 -Q --help=optimizers) <(gfortran -O1 -Q --help=optimizers) \
|
||||
| grep "^>" | grep enabled
|
||||
# 43 项
|
||||
```
|
||||
|
||||
2. 分两组测(`-O0 + HALF1` / `-O0 + HALF2`)→ HALF2 段错误
|
||||
|
||||
3. HALF2 逐项测 → **`-ftoplevel-reorder` 单独触发段错误**
|
||||
|
||||
4. 反向验证:`-O1 -fno-toplevel-reorder` → **0 NaN ✓**
|
||||
|
||||
5. 最终验证:`-O3 -fno-toplevel-reorder` → **0 NaN ✓,速度 4s/点**
|
||||
|
||||
> `-ftoplevel-reorder` 是 gfortran 的顶级函数/变量重排优化,它改变了程序单元(subroutine/function)和 DATA 块的初始化顺序。SYNSPEC 是传统 FORTRAN 77 代码,大量使用 COMMON 块,初始化散布在 305 条 DATA 语句中(无 BLOCK DATA),重排后某些 COMMON 变量在使用时尚未初始化,导致氢线 Stark 轮廓计算读到垃圾值 → NaN。
|
||||
>
|
||||
> **深度技术分析见 §9**(源码级 + 编译器原理 + FORTRAN 77 标准 + 社区经验)。
|
||||
|
||||
---
|
||||
|
||||
## 4. 修复实施
|
||||
|
||||
### 4.1 重编译 SYNSPEC 与 TLUSTY
|
||||
|
||||
```bash
|
||||
cd /home/fmq/program/tlusty/tl208-s54
|
||||
|
||||
# 备份原版
|
||||
cp synspec/synspec.exe ~/tlusty_O3_backups_20260811/synspec.exe.O3.bak
|
||||
cp dcts/assets/synspec_static ~/tlusty_O3_backups_20260811/synspec_static.O3.bak
|
||||
cp tlusty/tlusty.exe ~/tlusty_O3_backups_20260811/tlusty.exe.O3.bak
|
||||
|
||||
# 重编译 SYNSPEC (-O3 -fno-toplevel-reorder)
|
||||
cd synspec
|
||||
gfortran -O3 -fno-toplevel-reorder -fno-automatic -mcmodel=medium \
|
||||
synspec54.f -o synspec.exe.fixed
|
||||
|
||||
# 重编译 TLUSTY(预防性,同样问题)
|
||||
cd ../tlusty
|
||||
gfortran -O3 -fno-toplevel-reorder -fno-automatic -mcmodel=medium \
|
||||
tlusty208.f -o tlusty.exe.fixed
|
||||
|
||||
# 安装到所有位置
|
||||
cp synspec/synspec.exe.fixed synspec/synspec.exe
|
||||
cp synspec/synspec.exe.fixed dcts/assets/synspec_static
|
||||
cp tlusty/tlusty.exe.fixed tlusty/tlusty.exe
|
||||
cp tlusty/tlusty.exe.fixed dcts/assets/tlusty_static
|
||||
```
|
||||
|
||||
### 4.2 二进制兼容性验证
|
||||
|
||||
Dockerfile.node 的运行镜像是 `debian:bookworm-slim`(glibc 2.36)。修复版在本机(Ubuntu, glibc 2.43)编译,验证兼容性:
|
||||
|
||||
```bash
|
||||
# 检查 glibc 符号需求
|
||||
objdump -T assets/synspec_static | grep -oE "GLIBC_2\.[0-9]+" | sort -V | tail
|
||||
# 最高 GLIBC_2.35 ≤ bookworm 的 2.36 ✓
|
||||
|
||||
# Docker 端到端验证
|
||||
docker run --rm -v /tmp/test:/work debian:bookworm-slim bash -c "
|
||||
apt-get install -y libgfortran5
|
||||
./work/synspec < input.5 > /dev/null 2>&1
|
||||
echo RC=\$?
|
||||
wc -l fort.7 # 432827 行 ✓
|
||||
"
|
||||
```
|
||||
|
||||
### 4.3 网格边界点批量验证
|
||||
|
||||
用修复版测试 6 个网格边界点(各维极端值),全部通过:
|
||||
|
||||
| 网格点 | 参数特征 | gfVIS99 3000-7000 | gfATO 100-20000 |
|
||||
|--------|---------|--------------------|----|
|
||||
| t20000_g5.0_he-4_c-1_n-1_o-1 | 最低Teff+贫He+富金属 | ✓ 0 NaN | ✓ 0 NaN |
|
||||
| t60000_g6.5_he2_c-1_n-1_o-1 | 最高Teff+富He+高logg | ✓ 0 NaN | ✓ 0 NaN |
|
||||
| t30000_g5.0_he2_c-1_n-1_o-1 | He-rich极端 | ✓ 0 NaN | ✓ 0 NaN |
|
||||
| t30000_g5.5_he-2_c-1_n-1_o-1 | 富金属 | ✓ 0 NaN | ✓ 0 NaN |
|
||||
| t40000_g5.0_he-4_c-4_n-4_o-4 | 贫金属+贫He | ✓ 0 NaN | ✓ 0 NaN |
|
||||
| t50000_g5.0_he-2_c-1_n-2_o-2 | 低logg+高Teff | ✓ 0 NaN | ✓ 0 NaN |
|
||||
|
||||
---
|
||||
|
||||
## 5. 线表与波长范围配置变更
|
||||
|
||||
### 5.1 可用线表对比
|
||||
|
||||
| 线表 | 波长范围 | 线数 | 文件大小 | 适用场景 |
|
||||
|------|---------|------|---------|---------|
|
||||
| `gfVIS99.dat` | 3000–7550 Å | 15 万 | 12 MB | 仅可见光诊断(快速,~4s/点) |
|
||||
| `gfATO.dat` | 18–23000 Å | 230 万 | 238 MB | **全波段**:EUV+UV+光学+近/中红外(~62s/点) |
|
||||
| `gfMOL.dat` | 844–999932 Å | 605 万 | 248 MB | 分子线(sdB 星非必需) |
|
||||
| `gfTiO.dat` | 3738–999990 Å | 833 万 | 342 MB | TiO 分子线(冷星用) |
|
||||
|
||||
### 5.2 SYNSPEC 实际波长上限实测
|
||||
|
||||
SYNSPEC 源码 `RESOLV` 子程序(`synspec54.f:2403`)的频率网格构建逻辑决定了单次运行的波长上下限:
|
||||
|
||||
```
|
||||
ALAM0 = 1.D-1 * ALAM0 ← 波长乘 0.1(单位转换)
|
||||
ALAST = 1.D-1 * ALAST
|
||||
NFREQ = 2 ← 只取首尾两点
|
||||
FREQ(1) = c / ALAM0
|
||||
FREQ(2) = c / ALAST
|
||||
```
|
||||
|
||||
当波长范围过宽时,线表扫描(`synspec54.f:8207`)在某条线之后判定超出频率窗口,输出截断为 ~235 行无效数据。
|
||||
|
||||
实测边界(两个不同大气一致):
|
||||
|
||||
| 请求范围 | 实际输出 | 判定 |
|
||||
|---------|---------|------|
|
||||
| 50-20000 Å | 50-50 Å(235 行) | ✗ 截断 |
|
||||
| 70-20000 Å | 70-70 Å(235 行) | ✗ 截断 |
|
||||
| **100-20000 Å** | **100-20000 Å(540 万行)** | **✓** |
|
||||
| 100-23000 Å | 100-23000 Å(640 万行) | ✓ |
|
||||
| 100-24000 Å | 100-100 Å(235 行) | ✗ 截断 |
|
||||
|
||||
**可靠范围:100–23000 Å**。工作流配置取 **100-20000 Å**(留安全余量)。
|
||||
|
||||
### 5.3 配置变更
|
||||
|
||||
`workflows/sdB_cno.yaml`:
|
||||
```yaml
|
||||
# 新增:谱线表选择
|
||||
linelist: gfATO.dat
|
||||
|
||||
synspec_input:
|
||||
line6:
|
||||
alam0: 100.0 # 原 3000.0 → 100.0
|
||||
alast: 20000.0 # 原 7000.0 → 20000.0
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. 代码改动:打通 linelist 配置链路
|
||||
|
||||
### 6.1 问题:linelist 是孤儿字段
|
||||
|
||||
`GridConfig.linelist: Option<String>`(`config.rs:1644`)在 YAML 中声明了但**从未被传递到 TaskSpec,也从未到达 node 端**。`embedded.rs` 硬编码 `"gfVIS99.dat"`。链路在 scheduler 第一步就断了。
|
||||
|
||||
### 6.2 改动清单(7 个文件)
|
||||
|
||||
完整链路(参照 `tlusty_input` 的传递模式):
|
||||
|
||||
```
|
||||
YAML linelist → config.rs GridConfig.linelist → scheduler get_workflow_linelist
|
||||
→ TaskSpec.linelist(serde_json 下发)→ executor 按 task.linelist 覆盖 + 按需下载
|
||||
→ runner symlink fort.19
|
||||
```
|
||||
|
||||
#### `crates/common/src/models.rs` — TaskSpec 加字段
|
||||
```rust
|
||||
pub struct TaskSpec {
|
||||
...
|
||||
#[serde(default)]
|
||||
pub linelist: Option<String>, // 新增
|
||||
}
|
||||
```
|
||||
`#[serde(default)]` 保证旧 MQ payload 反序列化为 None → 用默认线表,向后兼容。
|
||||
|
||||
#### `crates/server/src/scheduler.rs` — 注入 linelist
|
||||
```rust
|
||||
async fn get_workflow_linelist(&self, workflow_name: &str) -> Option<String> {
|
||||
let wf = self.db.get_workflow(workflow_name).await.ok()??;
|
||||
let cfg = parse_grid_config_or_warn(&wf.config_yaml, workflow_name, "linelist")?;
|
||||
cfg.linelist.clone()
|
||||
}
|
||||
```
|
||||
在 3 个生产 TaskSpec 构造点注入 `linelist: linelist.clone()`。
|
||||
|
||||
#### `crates/common/src/embedded.rs` — ensure_runtime 接收默认线表名
|
||||
```rust
|
||||
pub async fn ensure_runtime(
|
||||
runtime_dir: &Path,
|
||||
server_url: &str,
|
||||
client: &Client,
|
||||
default_linelist: &str, // 新增参数
|
||||
) -> Result<RuntimePaths> { ... }
|
||||
```
|
||||
下载逻辑改用 `/api/data/file/{name}` 路由(复用 `ensure_specific_data_files` 的原子写机制)。
|
||||
|
||||
#### `crates/node/src/main.rs` — 传默认线表名
|
||||
```rust
|
||||
let runtime = ensure_runtime(runtime_dir, &node_cfg.server_url, &client, "gfATO.dat")
|
||||
.await?;
|
||||
```
|
||||
|
||||
#### `crates/node/src/executor.rs` — 按 task.linelist 覆盖 + 按需下载
|
||||
在创建 `ExecutionRunner` 前,若 `task.linelist` 与默认不同,按需从服务端下载并构造覆盖了 `linelist` 路径的 `RuntimePaths`。
|
||||
|
||||
#### `workflows/sdB_cno.yaml` — 配置更新
|
||||
```yaml
|
||||
linelist: gfATO.dat
|
||||
synspec_input:
|
||||
line6:
|
||||
alam0: 100.0
|
||||
alast: 20000.0
|
||||
```
|
||||
|
||||
#### `docs/deployment.md` — 编译命令 + 线表部署说明
|
||||
编译命令更新为 `-O3 -fno-toplevel-reorder -mcmodel=medium`,新增谱线表部署章节。
|
||||
|
||||
### 6.3 验证
|
||||
|
||||
```bash
|
||||
cargo test --workspace # 194 个测试全部通过
|
||||
```
|
||||
|
||||
端到端(修复版 SYNSPEC + gfATO.dat + 100-20000Å):
|
||||
```
|
||||
波长范围: 100.0 - 20000.0 Å
|
||||
总点数: 5428868, NaN: 0, 负值: 0
|
||||
分波段: EUV 224万点 / UV 125万点 / 光学 87万点 / 近IR 107万点
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. 部署清单
|
||||
|
||||
### 7.1 二进制替换
|
||||
|
||||
| 位置 | 旧版(-O3) | 新版(-O3 -fno-toplevel-reorder) |
|
||||
|------|------------|----------------------------------|
|
||||
| `dcts/assets/synspec_static` | 803512 bytes | 1000504 bytes |
|
||||
| `dcts/assets/tlusty_static` | 1546432 bytes | 1957488 bytes |
|
||||
| `synspec/synspec.exe` | 1044928 bytes | 同 assets/synspec_static |
|
||||
| `tlusty/tlusty.exe` | 1997416 bytes | 同 assets/tlusty_static |
|
||||
|
||||
原版备份在 `~/tlusty_O3_backups_20260811/`。
|
||||
|
||||
### 7.2 线表部署
|
||||
|
||||
`gfATO.dat`(238MB)不入 git(`.gitignore` 排除 `assets/data/`),需手动放置:
|
||||
```bash
|
||||
cp /path/to/gfATO.dat assets/data/gfATO.dat
|
||||
```
|
||||
node 端首次连接时自动从 server `/api/data/file/gfATO.dat` 下载并缓存。
|
||||
|
||||
### 7.3 重算全部网格点
|
||||
|
||||
重新部署后,用项目本身的分布式计算重算。salvage 中有 8319 个最终大气(`.7`)可复用(TLUSTY 大气不受 SYNSPEC bug 影响),只需重跑 SYNSPEC 阶段。
|
||||
|
||||
---
|
||||
|
||||
## 8. 关键源码位置索引
|
||||
|
||||
| 内容 | 文件:行号 |
|
||||
|------|----------|
|
||||
| spec_is_valid 校验器 | `crates/common/src/conv_check.rs:290` |
|
||||
| spec_is_valid 调用点 | `crates/common/src/runner.rs:645` |
|
||||
| fort.55 生成器 | `crates/common/src/fort55_writer.rs:26` |
|
||||
| SynspecInput 配置(9 子结构体) | `crates/common/src/config.rs:1184` |
|
||||
| GridConfig.linelist 字段 | `crates/common/src/config.rs:1644` |
|
||||
| TaskSpec.linelist 字段(新增) | `crates/common/src/models.rs:473` |
|
||||
| ensure_runtime(默认线表参数) | `crates/common/src/embedded.rs:34` |
|
||||
| executor linelist 覆盖(新增) | `crates/node/src/executor.rs:166` |
|
||||
| SYNSPEC fort.7 写出(FLAM=FLUX*FREQ²*CAS) | `synspec/synspec54.f:3364` |
|
||||
| SYNSPEC RESOLV 频率网格构建 | `synspec/synspec54.f:2403` |
|
||||
| SYNSPEC IDSTD=0 自动取 2ND/3 | `synspec/synspec54.f:2152` |
|
||||
| SYNSPEC Balmer 线系处理 | `synspec/synspec54.f:5310` |
|
||||
| SYNSPEC OPAC 吸收系数计算 | `synspec/synspec54.f:4541` |
|
||||
| PARAMS.FOR MFREQ=2000 | `synspec/PARAMS.FOR:11` |
|
||||
| LINDAT.FOR MLIN0=1200000 | `synspec/LINDAT.FOR:1` |
|
||||
|
||||
---
|
||||
|
||||
## 9. 技术原理:`-ftoplevel-reorder` 为何破坏 SYNSPEC
|
||||
|
||||
> 本节是源码级 + 编译器原理的深度分析,基于 GCC 官方文档、FORTRAN 77 标准、gfortran 社区经验与 SYNSPEC 源码核查。
|
||||
|
||||
### 9.1 `-ftoplevel-reorder` 具体做什么
|
||||
|
||||
**GCC 官方定义**:
|
||||
|
||||
> `-ftoplevel-reorder`(默认,`-O1` 起启用):允许重排顶层(top-level)函数、变量和 `asm` 语句,使它们不必按源文件中出现顺序输出。
|
||||
>
|
||||
> `-fno-toplevel-reorder`:不要重排,按源文件顺序输出。
|
||||
|
||||
| 属性 | 说明 |
|
||||
|------|------|
|
||||
| **重排对象** | 翻译单元内所有具有外部链接的程序单元(subroutine/function)和全局数据对象 |
|
||||
| **默认启用** | `-O1` 及以上(精确解释了 `-O1`/`-O2`/`-O3` 全部 NaN、`-O0` 正常) |
|
||||
| **优化目标** | 改善代码局部性(i-cache 命中率)、热点函数聚集、配合 profile-guided 优化 |
|
||||
| **与 `-freorder-functions` 关系** | 后者是子集,只在有 profile 数据时按执行频率排序;`-ftoplevel-reorder` 是更通用版本,无 profile 时也基于编译器启发式重排 |
|
||||
|
||||
### 9.2 FORTRAN 77 的初始化语义与 SYNSPEC 的反模式
|
||||
|
||||
**FORTRAN 77 标准规则**:
|
||||
1. DATA 语句在程序开始执行前完成初始化(load-time 静态初始化),只执行一次,隐含 `SAVE` 语义
|
||||
2. COMMON 块的内存布局由各程序单元中 COMMON 语句的声明顺序决定
|
||||
3. **BLOCK DATA 是标准指定的初始化命名 COMMON 块的唯一机制**;社区最佳实践是"每个 COMMON 块只在一个 BLOCK DATA 中初始化一次"
|
||||
|
||||
**SYNSPEC 源码核查结果**(subagent 对 `synspec54.f` 全文分析):
|
||||
|
||||
| 指标 | SYNSPEC | TLUSTY | 规范性 |
|
||||
|------|---------|--------|--------|
|
||||
| BLOCK DATA 单元 | **0 个** | 2 个(集中式初始化) | SYNSPEC 反模式 |
|
||||
| DATA 语句 | **305 条**,散布在各子程序 | 集中在 BLOCK DATA | SYNSPEC 脆弱 |
|
||||
| COMMON 声明 | **93 处**,跨程序单元复用 | 规范 | — |
|
||||
|
||||
SYNSPEC 把本应集中在 BLOCK DATA 里的 COMMON 块初始化**散布到 305 条子程序内 DATA 语句**中。这是 FORTRAN 77 的反模式,放大了内存布局对编译器符号排序的敏感度。
|
||||
|
||||
### 9.3 重排如何导致 NaN(破坏机理)
|
||||
|
||||
`-ftoplevel-reorder` 改变所有顶层符号在目标文件 `.o` 中的输出顺序。对 SYNSPEC 这类 F77 代码,破坏路径有两条:
|
||||
|
||||
**(A) COMMON 块合并/布局偏移(最可能)**
|
||||
|
||||
gfortran 对 COMMON 块的内部表示依赖符号在 `.o` 中的出现顺序来解析等价(EQUIVALENCE)和重复定义。SYNSPEC 不同子程序里同一 COMMON 块的声明在类型/长度上存在细微不一致(F77 代码中极常见)。重排改变了首次遇到的声明位置,使合并后的 COMMON 块布局偏移变化——**某些本应被 DATA 初始化的数组元素落到未初始化区域**。
|
||||
|
||||
**(B) 静态初始化符号被链接器丢弃**
|
||||
|
||||
DATA 初始化在某些目标格式下生成独立的初始化符号/构造器。重排可能让链接器认为某个初始化符号无引用而不被拉入(类似 BLOCK DATA 从静态库丢失的经典问题)。
|
||||
|
||||
### 9.4 为什么 NaN 恰好从 Balmer 跃迁(3646 Å)开始
|
||||
|
||||
SYNSPEC `HYLSET` 子程序(`synspec54.f:5310`)有明确判断:
|
||||
|
||||
```fortran
|
||||
IF(FREQ(2).GE.3.28805E15) RETURN ! 短于 Balmer 系限就跳过
|
||||
...
|
||||
IF(AL1.LT.364.6) THEN ! 波长 > 3646 Å(364.6 nm)
|
||||
ILOWH=2 ! 启用完整 Balmer 线系 Stark 轮廓计算
|
||||
```
|
||||
|
||||
波长长于 3646 Å 后,SYNSPEC 额外读取以下被散布式 DATA 初始化的 COMMON 数组:
|
||||
|
||||
| COMMON 块 | 用途 | 初始化子程序 | 源码位置 |
|
||||
|-----------|------|-------------|---------|
|
||||
| `GOMOPA` | Gomez 氢线不透明度表 | `ghydop` | `synspec54.f:21700` |
|
||||
| `callarda/b/g/c` | Allard 准分子卫星线数据 | `getlal` | `synspec54.f:12438` |
|
||||
| `VOITAB` | Voigt 函数表 | `PRETAB` | `synspec54.f:12900` |
|
||||
|
||||
这些数组若有未初始化元素(NaN/垃圾值),`EXP(abl)`、对数插值等运算会瞬间传播 NaN 到整个输出。这**精确解释了 NaN 从 3646 Å 往长波方向蔓延(73% 失效),而非全局性失效或短波失效**。
|
||||
|
||||
### 9.5 为什么禁用后速度几乎不受影响(~4s vs ~3.5s)
|
||||
|
||||
`toplevel-reorder` 的设计目标对 SYNSPEC 完全不适用:
|
||||
|
||||
| 优化假设 | SYNSPEC 实际 |
|
||||
|---------|-------------|
|
||||
| 大量短函数、密集互调 → i-cache 局部性 | 单文件 23917 行,几十个大子程序 |
|
||||
| 函数调用频繁,调用开销显著 | 运行时间全在少数热点子程序的**内部长循环**(Voigt 积分、辐射转移求解) |
|
||||
| i-cache miss 是瓶颈 | 函数调用开销可忽略,无 i-cache 压力 |
|
||||
|
||||
真正的瓶颈是浮点运算和数组寻址,与符号顺序无关。测到的 ~14% 差异(4s vs 3.5s)更可能是测量噪声 + 其他 `-O3` 优化的副作用,**而非 toplevel-reorder 本身的收益**。
|
||||
|
||||
> **普遍结论**:对计算密集型 Fortran 科学代码,禁用 `-ftoplevel-reorder` 几乎零代价——社区已形成共识。
|
||||
|
||||
### 9.6 这是 gfortran bug 还是 F77 代码的问题
|
||||
|
||||
**双方都有责任**:
|
||||
|
||||
- **F77 代码(根本原因)**:SYNSPEC 用散布式 DATA 替代集中式 BLOCK DATA,违反"每个 COMMON 块只在一个 BLOCK DATA 中初始化"的最佳实践。FORTRAN 标准不保证 COMMON 块在不一致声明下的行为,所以编译器重排不算违规。
|
||||
- **gfortran(暴露问题)**:对 F77 legacy 代码默认开启 `-ftoplevel-reorder` 是容易踩坑的默认值。
|
||||
|
||||
**社区已形成共识**:对传统 F77 代码,`-fno-toplevel-reorder` 与 `-std=legacy`、`-fno-automatic`、`-fno-align-commons` 并列为推荐编译选项。
|
||||
|
||||
遭遇同类问题的科学计算项目:
|
||||
- **CMAQ / I/O API**:COMMON 块初始化受重排影响(CMAS Center 文档明确讨论)
|
||||
- **Open MPI Fortran 代码**:`-O1`(启用 toplevel-reorder)导致问题,`-O0` 正常(Stack Overflow)
|
||||
- **GEOS-Chem**:推荐 `-ffpe-trap` 排查此类 NaN + 保守优化(Harvard Wiki)
|
||||
- **Intel IFX**:同样的 BLOCK DATA 丢失问题,连商业编译器也难幸免
|
||||
|
||||
### 9.7 相关 GCC Bugzilla 与社区参考
|
||||
|
||||
- GCC bugzilla fortran/29537、fortran/47030、fortran/96839(COMMON + BLOCK DATA 边缘相关)
|
||||
- Gentoo Bug 724314(验证 `-ftoplevel-reorder` 在 `-O2` 启用)
|
||||
- Stack Overflow "What does Top level reordering mean?"(Open MPI 类似问题)
|
||||
- `gui/synple/synspec/makefile` 的原版 Makefile `OPT = -fno-automatic -mcmodel=medium` **未含 `-fno-toplevel-reorder`**——这就是 bug 触发点,本次修复补上了这个遗漏
|
||||
|
||||
### 9.8 长期修复方向
|
||||
|
||||
短期:`-fno-toplevel-reorder` 是性价比最高的方案,无性能损失。
|
||||
|
||||
长期(若要彻底修复代码而非依赖编译开关):把 SYNSPEC 中散落在各子程序的 305 条 COMMON + DATA 初始化**集中到统一的 BLOCK DATA 单元**(参照 TLUSTY 的做法),或迁移到 Fortran 90 MODULE。但对 23917 行的生产代码工程量大,非必要不折腾。
|
||||
|
||||
### 9.9 推荐编译选项(传统 F77 代码通用)
|
||||
|
||||
```bash
|
||||
gfortran -O3 -fno-toplevel-reorder -fno-automatic -mcmodel=medium \
|
||||
[-std=legacy] [-fno-align-commons] \
|
||||
[-ffpe-trap=invalid,zero,overflow -fbacktrace -g] # 调试时加
|
||||
synspec54.f -o synspec_static
|
||||
```
|
||||
|
||||
| 选项 | 作用 | 必要性 |
|
||||
|------|------|--------|
|
||||
| `-fno-toplevel-reorder` | **关键**:禁用符号重排,恢复 COMMON 块布局 | 必需 |
|
||||
| `-fno-automatic` | 局部变量静态化(F77 隐式 SAVE 语义) | 必需 |
|
||||
| `-mcmodel=medium` | 大型 COMMON 块 64 位寻址 | 必需(SYNSPEC) |
|
||||
| `-std=legacy` | 允许 F77 过时语法 | 可选 |
|
||||
| `-fno-align-commons` | 禁止 COMMON 块对齐填充 | 可选 |
|
||||
| `-ffpe-trap` + `-fbacktrace` | 调试:NaN 即时 SIGFPE + 回溯到源码行 | 仅调试 |
|
||||
|
||||
---
|
||||
|
||||
## 10. TLUSTY 冷启动未收敛网格点归因分析(-ftoplevel-reorder 洗清嫌疑)
|
||||
|
||||
> 本节回答用户追问:"tlusty 会因为这个原因(`-ftoplevel-reorder`)出现问题吗?"以及"目前 tlusty 收敛失败的网格点失败原因会不会有这个的原因?"。
|
||||
> 结论先行:**TLUSTY 收敛失败与 `-ftoplevel-reorder` 无关**——冷启动(`cold_run`)链中 lte→nc→nl 的发散是 TLUSTY 物理/数值收敛问题,由灰色启动的迭代放大导致,与编译符号重排无关。
|
||||
|
||||
### 10.1 为什么先前用种子步进点测试是误导(用户纠正)
|
||||
|
||||
先前对种子步进(`seed_step`)策略点 `t20000_g6.5_he2_c-1_n-2_o-2` 做了两版二进制对照:原版 `-O3` 与修复版 `-O3 -fno-toplevel-reorder` 分别重跑 `seed_nc` + `nl`,两版 `fort.9` 逐字节相同(`seed_nc` max_relc=7.45E-03),`nl` 阶段都产生完全相同的 6750 个 NaN。结论虽是"无关",但测试对象选错了:
|
||||
|
||||
- 种子步进只在**冷启动失败后**才启动(`pending_strategies` 升级链:`cold_run` → `cold_run,seed_step` → `seed_step`)。
|
||||
- 当前网格(稀疏)下,从最近收敛点做种子步进本就难以收敛,属预期行为——**种子步进失败不能代表冷启动失败**。
|
||||
- 用户要求:对比分析**冷启动(`cold_run`,lte→nc→nl)未收敛的网格点**,看这些失败是否与 `-ftoplevel-reorder` 有关。
|
||||
|
||||
### 10.2 冷启动失败的规模与模式(DB + salvage 统计)
|
||||
|
||||
**DB(`/home/fmq/下载/dcts.db`,grid_points)**:
|
||||
- `tlusty_status='failed'`:1105 个网格点,全部经过多次重试(attempt_count 5-25),最终失败点均无 `tlusty_success_method`。
|
||||
- 失败点全部经历过冷启动失败后进入种子步进(无一个"纯冷启动"失败点,这是升级链策略使然)。
|
||||
|
||||
**salvage 冷启动产物(`nc_chmax0.001.9` 是纯冷启动文件——种子步进用 `seed_nc` 前缀,不覆盖 `.nc.*`)**:
|
||||
- 3986 个含冷启动链(lte/nc/nl 阶段)的目录中:lte 全部收敛(灰色启动,NITER=0),**nc 阶段 3983/3986 失败**。
|
||||
- 解析冷启动 `nc` 的 `fort.9` 逐迭代 max_relc:
|
||||
|
||||
| nc 冷启动结局 | 数量 | 占比 | 特征 |
|
||||
|--------------|------|------|------|
|
||||
| 有限发散(无 NaN) | 6170 | 88% | max_relc 从 iter1 的 ~1-1000 单调放大到 iter10 的 1e12~1e29 |
|
||||
| NaN 发散 | 699 | 10% | iter k 出现 NaN,之后全部 NaN |
|
||||
| 收敛 | 0 | 0% | 冷启动 nc 零收敛(这就是冷启动失败的全部来源) |
|
||||
|
||||
- 有限发散的典型轨迹(`t45000_g5.0_he-2_c-1_n-3_o-4`):iter1→15.4,iter2→35.9,iter3→244,…,iter9→1.02e11,iter10→4.46e15。**逐迭代单调放大**是牛顿迭代从远初始猜测发散的经典形态,且发散集中在深层(worst_depth≈44-50/50,POP 列最大)。
|
||||
- NaN 发散在高 Teff 区更常见(45000-60000K 占 699 例中的 545 例)。
|
||||
|
||||
### 10.3 冷启动失败的完整链条机制(两类)
|
||||
|
||||
**A. 真发散(无 NaN,~88%)**:nc 从灰色 LTE 大气出发,POP/TEMP/NE 的相对变化逐迭代放大(iter1 已 >1),10 次迭代后 max_relc 达 1e14~1e29 量级但数值仍有限。nl 从该发散大气继续,iter 6 内 `**** STOP in SOLVE` 终止(`t50000_g5.0_he-4_c-2_n-3_o-1`:nc 3.04e14 → nl 1.3e18,无 NaN)。
|
||||
|
||||
**B. NaN 污染 + 假收敛(~10%)**:nc 在 iter k 突发 NaN(POP 溢出)→ `fort.7` 大气含 NaN → nl 从 NaN 大气求解,`fort.9` 全零(`0.00E+00`、ilev=0、ifr=9 失败标志)→ check_fort9 判 max_relc=0.0 < chmax → **nl 假收敛**。最终靠 `atmosphere_has_nan` 兜底判失败(`t50000_g5.0_he-2_c-2_n-3_o-1`:nc iter2→NaN,nl max_relc=0.0 1 迭代)。
|
||||
|
||||
### 10.4 对照实验:两版二进制重跑完整冷启动链
|
||||
|
||||
**重放 harness**(`/tmp/replay_cold.sh`):对 salvage 中挑选的冷启动失败点,用其 `.lte.5/.lte.nst/.nc.5/.nc.nst/.nl.5/.nl.nst` 输入,按 runner 的 stage 语义重放:
|
||||
- lte:`fort.5`=lte.5,`nst`=lte.nst,ltgray=T 无需 `fort.8`(灰色启动)
|
||||
- nc:`fort.5`=nc.5,`fort.8`=lte 的 `fort.7`
|
||||
- nl:`fort.5`=nl.5,`fort.8`=nc 的 `fort.7`
|
||||
- `data` 软链 → 运行时原子数据集(与 node 端相同)
|
||||
|
||||
**对照组设置**:初版对照用"原版备份二进制"(`~/tlusty_O3_backups_20260811/tlusty.exe.O3.bak`,纯 `-O3`),但发现该备份带有 `READ LINE` 输入回显(源码 `tlusty208.f:30627` 的注释调试行,旧构建遗留,仅影响输出不影响数值),属混叠源。为严谨起见,用**当前源码**重编译一对干净二进制:`gfortran -O3 -fno-automatic -mcmodel=medium tlusty208.f`(开 toplevel-reorder,即"原版语义")与 `gfortran -O3 -fno-toplevel-reorder -fno-automatic -mcmodel=medium`(修复版)——**唯一差异就是 `-ftoplevel-reorder` 这一个 flag**(后者的 md5 与生产 `assets/tlusty_static` 完全一致 77721c4c,验证编译可复现)。
|
||||
|
||||
**harness 自校验**:
|
||||
- 重放的 lte.7 与历史 lte.7 逐字节比较,5210 行中仅 3 行不同且差异为 ~1e-8~1e-6 相对量级(机器级舍入噪声)。
|
||||
- 重放的 nc.9 与历史 `nc_chmax0.001.9` 比较:iter1 50 行仅 16 行末位舍入差异(如 -9.05E-03 vs -9.04E-03),iter2 NaN 行逐字节相同 → 重放忠实复现历史冷启动失败。
|
||||
|
||||
**点 B `t50000_g5.0_he-2_c-2_n-3_o-1`(NaN 污染类,10% 类)结果**:
|
||||
|
||||
| 阶段 | 历史结局 | 干净原版(-O3) vs 修复版(-O3-fno-toplevel-reorder) 重放 |
|
||||
|------|---------|------------------------------------------------------|
|
||||
| lte | 灰色启动 OK | **fort.7 逐字节一致** |
|
||||
| nc | iter1=1490 → iter2 全 NaN | **fort.9 逐字节一致**(103 行,iter1=1.490e+03、iter2 全 NaN);**fort.7 逐字节一致**(3659 行 NaN/坏行) |
|
||||
| nl | max_relc=0.0 假收敛 + NaN 大气 | **fort.9 逐字节一致**(53 行全 0.00E+00);**fort.7 逐字节一致**(5200 行 NaN/坏行) |
|
||||
|
||||
> 点 B 的三阶段**全部逐字节一致**。nc 从灰色启动发散到 NaN(iter2)、nl 假收敛、NaN 大气——这一整套冷启动失败在两种编译配置下完全相同的位级复现。`-ftoplevel-reorder` 对该点冷启动失败**零影响**。
|
||||
> 注:初版对照(原版备份二进制 vs 修复版)在 nc.7 出现 16 行 NaN↔0.0 位置差异——那是已发散垃圾值行的格式差异(语义等价:大气同样死亡),且来源于备份二进制的旧源码状态混叠;干净对照对消除了该混叠后完全逐字节一致。
|
||||
|
||||
**点 A `t50000_g5.0_he-4_c-2_n-3_o-1`(真发散类,88% 类)结果**:
|
||||
|
||||
| 阶段 | 历史结局(生产二进制) | 干净原版(-O3) vs 修复版(-O3-fno-toplevel-reorder) 重放 |
|
||||
|------|---------------------|------------------------------------------------------|
|
||||
| lte | 灰色启动 OK | **fort.7 逐字节一致** |
|
||||
| nc | iter1..10 = 789→338→2120→498→…→7.49e9→3.04e14 发散 | **fort.9 逐字节一致**(503 行,迭代序列 7.89e2→3.38e2→2.12e3→4.98e2→3.91e6→3.61e10→3.04e2→4.5e5→7.48e9→2.95e14,与历史仅末位舍入差);**fort.7 逐字节一致**(0 NaN 行) |
|
||||
| nl | iter6 STOP,max_relc 1.3e18 | **fort.9 逐字节一致**(303 行,序列 7.76e5→4.22e6→1.96e6→1.02e7→1.89e9→1.80e18,iter6 STOP);**fort.7 逐字节一致**(0 NaN 行) |
|
||||
|
||||
> 点 A 三阶段**全部逐字节一致**。真发散的 10 次 nc 迭代轨迹、nl 的 iter6 求解器 STOP、无 NaN 大气——在两种编译配置下完全相同的位级复现。
|
||||
|
||||
### 10.5 结论
|
||||
|
||||
两个代表点覆盖了冷启动失败的两种主要形态(**88% 有限发散** + **10% NaN 污染**),用**同源码仅差 `-ftoplevel-reorder` 一个 flag** 的干净二进制对照对重跑完整冷启动链(lte→nc→nl),三个阶段的 `fort.9` 与 `fort.7` **全部逐字节一致**:
|
||||
|
||||
1. **TLUSTY 冷启动失败与 `-ftoplevel-reorder` 无关**——发散轨迹(逐迭代 max_relc 序列)、NaN 模式(何时出现 NaN、多少行)、求解器 STOP 行为、最终大气,均在开关两侧完全一致。
|
||||
2. 冷启动失败的本质:**灰色 LTE 启动(lte 阶段)在稀疏高 Teff/logg 网格上,nc 阶段 NLTE 牛顿迭代物理发散**(逐迭代单调放大 1e1→1e14,或突发 NaN),nl 从发散大气出发随即失败/假收敛。这是 TLUSTY 求解器的数值收敛特性,不是编译产物差异。
|
||||
3. 两种失败形态的链机制:**A(真发散)**= nc 有限放大到 1e14+ → nl iter≤10 内 `STOP in SOLVE`;**B(NaN 污染)**= nc iter k 突发 NaN → nl 从 NaN 大气求解 `fort.9` 全零(ilev=0/ifr=9 失败标志)→ check_fort9 误判 max_relc=0.0 **假收敛** → 由 `atmosphere_has_nan` 兜底判失败。
|
||||
4. **项目侧含义**:修复版二进制在 TLUSTY 阶段可安全替代原版——salvage 中 8319 个已收敛大气可复用(结果舍入噪声级一致,已由 lte.7/nc.9 逐字节验证),重算全部网格点时 TLUSTY 结果的失败/收敛行为与旧版完全一致,不存在"换了二进制就多了失败点/少了收敛点"的风险。
|
||||
5. **方法论教训**:种子步进点不能用于归因冷启动失败——种子步进只在冷启动失败后才启用,稀疏网格下种子难收敛是预期行为。归因必须用冷启动(lte→nc→nl)产物(salvage 中 `.nc.*`/`.lte.*` 文件是纯冷启动产物,种子链用 `seed_nc` 前缀不覆盖)。
|
||||
|
||||
---
|
||||
|
||||
## 11. 经验教训
|
||||
|
||||
1. **文件大小正常 ≠ 内容有效**:SYNSPEC 的 `.spec` 都是 ~11MB/432827 行,NaN 混在其中不改变文件大小,必须做内容校验。
|
||||
2. **gfortran -O3 对老 Fortran 代码不安全**:`-ftoplevel-reorder` 破坏 COMMON 块初始化顺序是已知类别的 bug。传统 FORTRAN 77 代码(TLUSTY/SYNSPEC)应加 `-fno-toplevel-reorder`。
|
||||
3. **浮点陷阱调试法高效**:`-ffpe-trap=invalid,zero,overflow` + `-O0` 能快速区分"源码 bug"vs"编译器 bug"——本例中 `-O0` 版正常立即排除了源码逻辑错误。
|
||||
4. **二分法定位优化项**:gfortran `-O1` 启用 43 项优化,逐步二分比逐项全测高效。本例中 `-ftoplevel-reorder` 单独就触发段错误,是明确元凶。
|
||||
5. **孤儿字段是配置断裂的常见模式**:`linelist` 在 YAML 声明了但从未下发——这种"看似可配实则死字段"在大型项目中容易潜伏,需要端到端验证。
|
||||
@@ -0,0 +1,394 @@
|
||||
# 冷启动 nc 阶段不收敛:源码根因 + 穷举复测(2026-08-13)
|
||||
|
||||
**数据来源**:`data/salvage/`(8320 任务产物)+ `/home/fmq/下载/dcts.db`(server 快照)
|
||||
**源码**:`tlusty/tlusty208.f`
|
||||
**复测目录**:`test/20260813_cold_nc_trace_fix/convergence/t60000_g5.0_he-4_c-4_n-4_o-3_cold/`(代表点)
|
||||
**前序文档**:`tlusty_divergence_root_cause_2026_08_11.md`、`cold_start_fix_2026_08_12.md`
|
||||
|
||||
> 本文是对前序"参数调优无效、需连续法"结论的**源码级复核 + 穷举重测**。
|
||||
> 用户明确指示:前序测试结果不可信,需重测;不要测种子步进,聚焦冷启动失败。
|
||||
|
||||
---
|
||||
|
||||
## 一、结论先行
|
||||
|
||||
1. **失败特征(已锁定)**:冷启动链 `lte(灰化,NITER=0)→nc(NLTE 连续,NITER=10)`,**nc 阶段从第 1 次迭代即在表层(深度 2–5)发散**。POP(布居数)列始终是 MAXIMUM 且雪崩(iter1≈140→iter10≈1e16),RAD(辐射场)列全程很小(0.01–0.26)。lte 阶段正常。
|
||||
|
||||
2. **源码根因**:nc 阶段的 Newton 完全线性化求解器在「高 Teff(≥50kK) + 低 logg(≤5.5) + 贫氦(he≤−2)」参数角的表层 NLTE 方程**雅可比矩阵病态**,灰化 LTE 初值不在收敛域内。是**布居数(尤其表层过渡电离级的 C/N/O)**驱动,不是辐射场。
|
||||
|
||||
3. **🔴 关键更正——为何前序所有"调参无效"结论不可信**:TLUSTY 的收敛判据 `CHMX`(fort.9 的 max_relc)取自 `BET(I,ID)=CHAN`(`tlusty208.f:14643`),该赋值在 **ORELAX 阻尼(:14647) 与 DPSILG 限幅(:14652) 之前**。即 **CHMX 用的是未阻尼、未限幅的原始 Newton 修正量**,对 ORELAX / DPSILG / ITEK / IACC **完全不敏感**。前序文档测试的恰恰全是这几个参数,并用 CHMX 判定"无效"——**测了一个对这些参数不敏感的指标**。这正是用户判定"前序结果不可信"的底层原因。
|
||||
|
||||
4. **穷举复测(11 个变体,本代表点)**:POPZER/POPZCH/ND=70/非灰LTE中间阶/trace金属置LTE/IFALI=5/logg连续/he连续/nokant——**无一收敛**。nc 的 Newton CL 在该点确实无单参数解。
|
||||
|
||||
5. **nc 自身确实无解(nc 阶段的 Newton+Kantorovich 在该参数角失效)**:14+ 变体(含 He-ladder 1 dex 细步、IFALI=6、禁 Kant+Ng)无一让 **nc** 收敛。不稳性在 nc 迭代的加速环节,换起点救不回 nc 自身。
|
||||
|
||||
6. **✅ 但找到可行修复(2026-08-14 验证)——nc 不需收敛,nl 能从「有界 nc」接住**:用同点 **he=0 收敛模型 warm-start 跑 nc**(得有界、不撞 1e16 STOP 的 nc),再跑 **nl** → **18 迭代收敛(max_relc=8.06e-4<1e-3)、温度结构物理(表层 0.77×Teff)**。冷启动 nc 因撞 1e16 STOP 模型损坏,nl 接不住。**判据是"nc 不撞 STOP 保持有界",不是"nc 收敛"。**
|
||||
|
||||
7. **生产没走通是 seed_finder 两个 bug**:(a) `seeds.rs:144-193` exact_family 优先级压倒一切,给 he-4 点选了富方向 CNO 邻居(坏种子)而漏掉经验证有效的 he0(贫 He 方向)种子;(b) `seed_finder.rs:51` `d_teff<5000.0`(严格)排除相邻 Teff 档(网格步长正好 5000K)。**这正是用户"多步种子步进/向四周扩散"思路要解决的,且已验证机制有效。**
|
||||
|
||||
> 注:前序文档结论"Newton 从 grey 起点不稳定、振荡不衰减"方向正确,但 (i) "nokant 降到 1e4"是中等点(he-2/he0)的数,最难点(he-4)禁 Kant+Ng 仍振荡到 1e9;(ii) 该结论只针对 **nc 阶段**——nl 阶段从有界 nc 能收敛,这点前序文档没测,是本报告的关键新增。
|
||||
|
||||
---
|
||||
|
||||
## 二、数据证据
|
||||
|
||||
### 2.1 失败分布(DB)
|
||||
|
||||
| 维度 | 分布 |
|
||||
|---|---|
|
||||
| 总失败 | tlusty_status=failed: **1105** 个网格点 |
|
||||
| 冷启动 nc 阶段失败(summary 中 nc 未收敛) | **273** 个(其余 832 为 cold 失败后退化为 seed_step,最终也失败) |
|
||||
| Teff 集中 | ≥50kK 占 ~82%(60k:81, 55k:91, 50k:53, …) |
|
||||
| logg 集中 | 5.0 最难 |
|
||||
| **He 丰度梯度(关键)** | he=+2 失败率 **7%**;he=0 **54%**;he=−2 **64%**;he=−4 **66%** |
|
||||
|
||||
> He 越贫越难收敛——物理上贫 He 即近纯 H 大气,60kK 下 H 的 NLTE 电离辐射主导、碰撞耦合弱,灰化初值偏差大。
|
||||
|
||||
### 2.2 代表点失败轨迹(baseline,实际 fort.9 逐迭代)
|
||||
|
||||
代表点 `t60000_g5.0_he-4_c-4_n-4_o-3`(60k/g5.0/he−4),nc 阶段 NITER=30:
|
||||
|
||||
```
|
||||
iter depth TEMP NE POP RAD MAX 发散变量
|
||||
1 2 1.25E+01 -1.83E+01 -1.40E+02 2.60E-01 1.40e+02 ilev75(C)
|
||||
2 3 3.16E+02 -2.68E+02 -1.95E+03 -6.19E-02 1.95e+03 ilev75(C)
|
||||
...
|
||||
10 4 1.47E+05 -4.45E+08 -2.19E+15 -1.15E-01 2.19e+15
|
||||
14 5 7.29E+05 -6.57E+08 -2.70E+16 2.60E+00 2.70e+16 → STOP(>1e16)
|
||||
```
|
||||
|
||||
- **表层(深度 2–5)**驱动;深层(>15)iter1 max|POP| 仅 0.99(正常)。
|
||||
- POP 始终是 MAXIMUM;RAD 全程 ≤2.6(直到 POP 到 1e15 才被带坏)。
|
||||
- T、NE 的未限幅修正也很大(iter1 T=+12.5 即 +1250%),但都小于 POP。
|
||||
|
||||
### 2.3 灰化 LTE 初值本身是物理的
|
||||
|
||||
`check_temperature_structure`(DCTS 权威判据)对 lte.7:
|
||||
- 表层 T=48670K(0.81×Teff,Eddington 灰化参考 0.84)✅
|
||||
- 无 NaN ✅
|
||||
|
||||
→ **初值不是病态的**,只是不在 NLTE 收敛域内。
|
||||
|
||||
---
|
||||
|
||||
## 三、源码级根因(逐行核对)
|
||||
|
||||
### 3.1 修正量计算与诊断脱钩 —— `tlusty208.f:14639-14681`(SOLVE)
|
||||
|
||||
```fortran
|
||||
DO I=1,N
|
||||
DPSI(I)=BET(I,ID)-VECL(I)
|
||||
CHAN=0.
|
||||
IF(PSI0(I).GT.0.) CHAN=DPSI(I)/PSI0(I) ! 原始 Newton 相对修正(无下限保护)
|
||||
BET(I,ID)=CHAN ! :14643 存【未阻尼、未限幅】原始值 ← CHMX 来源
|
||||
IF(I.GE.NFREQE+INSE) CHAN=ORELAX*CHAN ! :14647 ORELAX 只作用于布居,且在 BET 之后
|
||||
IF(CHAN.LE.UN/DPSILG-UN) CHAN=UN/DPSILG-UN ! :14652 DPSILG 限幅(在 BET 之后)
|
||||
...(DPSILT/DPSILN/DPSILD 逐变量更严限幅)...
|
||||
PSI0(I)=PSI0(I)*(CHAN+UN) ! :14681 实际更新(限幅后,有界)
|
||||
END DO
|
||||
```
|
||||
|
||||
**关键推论**:
|
||||
- `BET`(:14643)= **原始**修正 → PRCHAN(:22876) 据此算 `CHMX` → 收敛判据 `LFIN=ABS(CHMX).LE.CHMAX`(:14728) 与雪崩 STOP `ABS(CHMX).GT.1.D16`(:14700) **全用这个未限幅值**。
|
||||
- ORELAX(:14647)、DPSILG(:14652) **都作用在 BET 之后**,**不改变 CHMX**。
|
||||
- 实际模型更新(:14681)有限幅、始终正、**不会 NaN**(实测:所有发散运行的 nc.7 都无 NaN、过温度结构判据)。
|
||||
- **所以"STOP after ITER N, Max change 1e16"是诊断(未限幅)触发的,不是模型真炸**。但模型也确实没收敛(表层 T 偏离收敛参考)。
|
||||
|
||||
**POP 分母无下限**:`IF(PSI0(I).GT.0.)`(:14642) 是唯一保护;无 `DMAX1(PSI0,eps)`。表层贫 He 下 C I 的 PSI0 极小 → CHAN 极大。
|
||||
|
||||
### 3.2 POPZER/POPZCH —— 本应保护 trace 物种,默认值形同虚设
|
||||
|
||||
| 参数 | 默认 | 作用 | 行号 |
|
||||
|---|---|---|---|
|
||||
| `POPZER` | 1e-20 | 布居/POPM<POPZER 的组冻结(POPGRP=0, IGZERO=1),其 Newton 修正强制为 0(VECL=0, :22750/:22756) | :6160 |
|
||||
| `POPZCH` | 1e-15 | 收敛报告阈值:RPOP0<POPZCH 的物种**排除出 CHMX/STOP 判据** | :22910/:22925 |
|
||||
| `NITZER` | 1 | 冻结/超零化的激活迭代数 | :5864 |
|
||||
|
||||
表层过渡电离级 C I/C II 在 60kK 的 RPOP0 ≈ 1e-10~1e-12 —— **高于** POPZCH(1e-15) 与 POPZER(1e-20),故两项保护都**不触发**。理论上调高 POPZER/POPZCH 能把它们排除——但实测不行(见 §四)。
|
||||
|
||||
### 3.3 ORELAX 作用域(更正前序 A3)
|
||||
|
||||
`ORELAX` 只作用于布居(`I≥NFREQE+INSE`)。辐射场(Jν)、T、ne、TOTN **不受 ORELAX 保护**——但本问题主发散量正是布居,所以 ORELAX 作用域其实**对得上**;问题在于它在 BET 之后,诊断看不到。
|
||||
|
||||
---
|
||||
|
||||
## 四、穷举复测(代表点,test/20260813_cold_nc_trace_fix/)
|
||||
|
||||
判据:fort.9 末次 max_relc **且** nc.7 温度结构/NaN。
|
||||
|
||||
| # | 变体 | 末 max_relc | STOP | nc.7 NaN? | 结论 |
|
||||
|---|---|---|---|---|---|
|
||||
| 1 | baseline (NITER=30) | 2.7e16 | iter14 STOP | 否 | 复现失败 ✅ |
|
||||
| 2 | POPZER=1e-9 | 2.78e16 | iter8 | 否 | 冻结 trace 反把 RAD 带崩 |
|
||||
| 3 | POPZER=1e-12 | 4.22e17 | iter20 | 否 | 太松,无效 |
|
||||
| 4 | POPZCH=1e-9 | 2.7e16 | iter14 | 否 | 发散物种 RPOP0>1e-9,未排除 |
|
||||
| 5 | ND=70 | 3.79e17 | iter10 | 否 | 非分辨率问题 |
|
||||
| 6 | 非灰 LTE 中间阶 (lte→lte_ng→nc) | — | LTE 阶自身 iter25 发散 | — | LTE 非灰迭代也不稳 |
|
||||
| 7 | trace 金属置 LTE (C/N/O I+II 负 nlevs) | 7.82e17 | iter5 | 否 | 剩余 NLTE 物种仍崩 |
|
||||
| 8 | 完整生产 nst (IFALI=5 等) | 2.7e16 | iter14 | 否 | 与 baseline 逐迭代一致 |
|
||||
| 9 | warm-start from g5.5 (Δlogg 0.5) | 3.19e16 | iter8 | 否 | iter1=6.49(小)但 iter2 爆 |
|
||||
| 10 | **warm-start from he0 (ΔlogHe 2)** | 1.13e6 | 无STOP(跑满30) | **否** | **iter1=4.3(最优)**,全程有界 |
|
||||
| 11 | warm he0 + ITEK=999 (禁 Kantorovich) | 2.09e4 | — | **是(全 NaN)** | 禁 Kant 反而 NaN——Kantorovich 实为稳定项 |
|
||||
|
||||
**全部 11 个变体无一数值收敛(max_relc<1e-3)。**
|
||||
|
||||
### 4.1 关键观察:Kantorovich 是稳定项,不是祸首
|
||||
|
||||
- 变体 10(warm he0,默认 ITEK=4):full-CL 迭代(1–4,7,11…)max_relc≈5–25(小!),Kantorovich 迭代(5–6,8–10…)飙到 1e4–1e10——但模型**始终有界无 NaN**。
|
||||
- 变体 11(warm he0,ITEK=999 纯 full-CL):10 迭代后**全深度 NaN**。
|
||||
- → **Kantorovich 近似算子反而在防止 NaN**。前序"禁 Kantorovich 能降 1e12"的观察只看到了 CHMX(未限幅诊断)下降,没看到纯 full-CL 会 NaN。
|
||||
|
||||
### 4.2 He-ladder 细步长也救不回(关键否定)
|
||||
|
||||
为验证"换更近的起点能否收敛",实测了 **He-ladder 1 dex 细步** he0(conv)→he−1:
|
||||
|
||||
```
|
||||
he-1 (1 dex He step from converged he0): iter1=6.82, iter2-4=100/92/152, iter5=1.1e6(Kant), iter18=2.9e17(STOP)
|
||||
```
|
||||
|
||||
**iter1 仍很小(6.82)**——说明 1 dex He 步长的起点质量很好;但 **iter5 起 Kantorovich 照样爆炸**。这与 2 dex 单跳(he0→he−4,iter1=4.3)、logg 连续(g5.5→g5.0,iter1=6.49)的模式完全一致:
|
||||
|
||||
| warm-start | iter1 | iter5(Kant) | 结局 |
|
||||
|---|---|---|---|
|
||||
| g5.5→g5.0 (Δlogg 0.5) | 6.49 | 爆 | STOP iter8 |
|
||||
| he0→he−4 (ΔlogHe 2) | 4.29 | 爆 | 跑满30, 无STOP, 有界 |
|
||||
| he0→he−1 (ΔlogHe 1) | 6.82 | 爆 | STOP iter18 |
|
||||
|
||||
**结论**:**不稳性在 nc 迭代的 Kantorovich 加速环节(iter≥5),不在起点**。无论起点多好(iter1 多小),iter5 起的 Kantorovich 都会爆炸。换起点/He 连续/步长都救不回——**这是 nc 阶段 Newton+Kantorovich 求解器在该参数角的结构性失效**。
|
||||
|
||||
> warm-start 模型全程**有界无 NaN**(Kantorovich 近似算子反而在防 NaN,见 §4.1),但 CHMX 恒高(未真收敛),表层 T 偏离收敛参考(0.70×Teff vs 0.78×Teff),**不可直接当科学结果用**。
|
||||
|
||||
---
|
||||
|
||||
## 五、可行修复方向
|
||||
|
||||
### 🔴 重大更新(2026-08-14):nc 不需收敛,nl 能从「有界 nc」接住 — 已验证
|
||||
|
||||
重新认识生产的真实收敛机制后,**找到了可行修复**。DB 显示 55k 同族点(已收敛)的成功路径是:
|
||||
```
|
||||
seed_nc: converged=False (max_relc=0.00461) ← nc 没收敛但有界
|
||||
nl: converged=True (max_relc=0.0004) ← nl 阶段补上最终收敛
|
||||
```
|
||||
即生产**接受未收敛的 seed_nc,靠 nl(带谱线、NITER=100、ORELAX=0.5)做最终收敛**。瓶颈不是 nc 收不收敛,而是 **nc 模型是否足够有界(不撞 1e16 STOP)让 nl 接得住**。
|
||||
|
||||
**验证**(代表点 `t60000_g5.0_he-4_c-4_n-4_o-3`,所有单参数法都失败的最难点):
|
||||
|
||||
| 路径 | nc 状态 | nl 结果 |
|
||||
|---|---|---|
|
||||
| 冷启动 nc → nl | STOP iter14(撞 1e16,模型损坏) | ❌ NaN 发散 |
|
||||
| **he0 warm-start nc → nl** | 跑满 30 迭代**有界无 STOP**(CHMX~1e6) | ✅ **18 迭代收敛, max_relc=8.06e-4 < 1e-3;表层 T=0.77×Teff(与收敛参考一致),无 NaN** |
|
||||
|
||||
→ **He-rich(he=0)同族 warm-start 给出有界 nc,nl 从中收敛到物理解。** 这正是用户提出的"多步种子步进/向四周扩散"思路——**机制已验证有效,但是部分点有效(非万能)**。
|
||||
|
||||
### 多点验证(2026-08-14,5 个不同失败点,he-rich warm nc + ORELAX=0.3 + nl)
|
||||
|
||||
| 点 | Teff/logg/He | 种子 | nc | nl | 结论 |
|
||||
|---|---|---|---|---|---|
|
||||
| first | 60k/g5.0/he-4 | he0 | 有界(无STOP) | 18it **8.06e-4** | ✅ **收敛,表层0.77×Teff** |
|
||||
| p1 | 50k/g5.0/he-4 | he0, ORELAX=0.3 | 有界(5e-3) | 15it **8.27e-4** | ✅ **收敛,表层0.81×Teff** |
|
||||
| p4 | 60k/g5.5/he-2 | he0, ORELAX=0.3 | 有界(4e8) | 100it 卡 1e5 | ⚠️ nl.7 物理(0.79×Teff)但未达0.001 |
|
||||
| p2 | 60k/g5.0/he-2 | he0 / he2 | STOP(1e26/1e16) | 发散 | ❌ 两种种子都 STOP |
|
||||
| p3 | 50k/g5.5/he-4 | he0, ORELAX=0.3/0.1 | STOP(~1e14) | NaN | ❌ |
|
||||
|
||||
**诚实结论:he-rich warm-start + ORELAX=0.3 + nl 是部分修复,非万能**:
|
||||
- **2/5 完全收敛**(都是 he-4/g5.0,50k & 60k 都行);
|
||||
- **1/5 接近**(p4:nl.7 物理但 nl 未达 0.001);
|
||||
- **2/5 失败**(p2 he-2、p3 he-4/g5.5:nc 仍 STOP)。
|
||||
|
||||
> 即:当前 production 对这批点 seed_step 恢复率 **0%**;本路径把它提到约 **40% 完全 + 20% 接近**。剩下的 p2/p3 类需更近种子(如 Teff 邻居,受 seed_finder bug 挡住未试)或源码级工作。**机制成立(nc 有界→nl 收敛),但"让 nc 有界"本身仍点相关。**
|
||||
|
||||
### 追加(2026-08-14 深夜):Teff 多步梯子 + 全方向种子——p2/p3 仍抵抗
|
||||
|
||||
对两个抵抗点(p2: 60k/g5.0/he-2_c-4_n-3_o-3;p3: 50k/g5.5/he-4_c-2_n-3_o-3)追加测试:
|
||||
|
||||
| 尝试 | 方向 | 结果 |
|
||||
|---|---|---|
|
||||
| **Teff 梯子 40k→45k→50k→55k→60k**(nc 链,ORELAX=0.3) | 5kK/步 | ❌ **第一步 45k nc 即 NaN**(iter1=12 好,iter5 Kant 爆到 7.7e13,nc.7 全 NaN) |
|
||||
| p2 对角种子(55k/g5.5/**同He同CNO**) | ΔTeff5k+Δlogg0.5 | ❌ nc STOP(1e17);nl.7 物理(0.76×Teff)但 max_relc 2e16 |
|
||||
| p2 g5.5 CNO-1 种子 | Δlogg0.5 | ❌ NaN |
|
||||
| p3 45k_he-2 种子 | ΔTeff5k+ΔlogHe1 | ❌ nc STOP(4e16);nl.7 物理(0.76×Teff)但 1e25 |
|
||||
| p3 g5.0 同丰度种子 | Δlogg0.5 | ❌ nc STOP(4e19);nl.7 物理(0.76×Teff)但 8e16 |
|
||||
|
||||
**p2 共 6 种尝试、p3 共 5 种尝试,全部未数值收敛**(he2/he0/对角/g5.5/Teff 梯子/多 ORELAX)。
|
||||
|
||||
**值得注意**:3 个"失败"运行的 nl.7 **温度结构物理且正确**(表层 0.76×Teff ≈ 收敛参考 0.77-0.78),无 NaN——只是 max_relc 被痕量物种的未限幅修正污染(联系 §3.1:CHMX 取自 BET 未限幅值)。即**结构可能已对、布居数判据不过**。这批"物理有界但数值未收敛"模型是否可用(或加 POPZCH 过滤判据后可用)值得进一步评估。
|
||||
|
||||
### 最终总账(全部验证)
|
||||
|
||||
| 点 | 结果 | 尝试数 |
|
||||
|---|---|---|
|
||||
| 60k/g5.0/he-4_c-4_n-4_o-3 | ✅ nl 收敛(8.06e-4)+物理 | he0 种子 1 次成功 |
|
||||
| 50k/g5.0/he-4_c-4_n-4_o-4 | ✅ nl 收敛(8.27e-4)+物理 | he0+ORELAX0.3 成功(ORELAX1.0 失败) |
|
||||
| 60k/g5.5/he-2_c-4_n-4_o-4 | ⚠️ nl.7 物理但未达0.001 | he0 |
|
||||
| 60k/g5.0/he-2_c-4_n-3_o-3 (p2) | ❌(3 个 nl.7 物理但未收敛/NaN) | **6 种** |
|
||||
| 50k/g5.5/he-4_c-2_n-3_o-3 (p3) | ❌(2 个 nl.7 物理但未收敛) | **5 种** |
|
||||
|
||||
### 🏆 决定性解锁(2026-08-14 凌晨):跳过 nc,直接从种子跑 nl —— 5/5 全部恢复
|
||||
|
||||
在 p2/p3 连续 11 种组合失败后,测试了最后一个未试变体:**跳过会发散的 nc 阶段,把邻居收敛模型直接作为 nl 的 fort.8**(nl 自带 ORELAX=0.5、NITER=100、谱线耦合,数值上比 nc 稳得多):
|
||||
|
||||
| 点 | 之前 11 种组合 | 直接 nl(跳过 nc)| nl.7 物理 |
|
||||
|---|---|---|---|
|
||||
| p2 (60k/g5.0/he-2_c-4_n-3_o-3) | 全失败 | ✅ 对角种子(55k/g5.5 同He同CNO) 99it **6.8e-4** | ✅ 0.765×Teff |
|
||||
| p3 (50k/g5.5/he-4_c-2_n-3_o-3) | 全失败 | ✅ 45k_he-2 种子 19it **8.6e-4**(近单调收敛) | ✅ 0.760×Teff |
|
||||
| p4 (60k/g5.5/he-2_c-4_n-4_o-4) | 卡 1e5 | ✅ **CNO-1 exact_family 种子** 13it **6.3e-4**;he0 种子 19it 8.4e-4 | ✅ 0.786×Teff |
|
||||
|
||||
**最终总账:5 个验证点全部恢复(5/5)**:
|
||||
- first (60k/g5.0/he-4)、p1 (50k/g5.0/he-4):he0 warm nc(ORELAX=0.3) → nl ✅
|
||||
- p2、p3、p4:**直接 nl(跳过 nc)** ✅
|
||||
|
||||
**🔴 最关键的发现(p4 案例)**:p4 直接 nl 用的 CNO-1 种子 **正是生产 seed_finder 本来就选的 exact_family 种子**——生产失败不是种子选错,而是 **seed_nc 中间阶段发散后把污染的 fort.7 喂给了 nl**。同一个种子跳过 seed_nc 直接喂 nl,13 次迭代收敛。
|
||||
|
||||
**生产的真正病灶**:`default_seed_chain` 是 `seed_nc → nl`,`seed_nc` 对难点发散(Kantorovich),其损坏输出毒死了 nl。而 **nl 从干净种子出发自身完全有能力收敛**。
|
||||
|
||||
### 修复方案(框架侧,runner.rs)
|
||||
|
||||
在 `default_seed_chain` / runner 的阶段传递逻辑(`runner.rs:518-531`)加一层:
|
||||
|
||||
1. **当 seed_nc 发散(STOP/NaN/best_max_relc 超阈)时,不要把 seed_nc 的 fort.7 传给 nl——回退用原始种子(邻居的干净 .7)直接喂 nl**(nl 参数不变:ORELAX=0.5、NITER=100)。
|
||||
2. 更激进(同样有效):对已知难点直接加一条 `nl_direct` 策略(跳过 seed_nc)。
|
||||
3. 配合修 seed_finder 两 bug(exact_family 优先级 + `d_teff<5000.0`),扩大可用种子池(he0/对角/Teff 邻居)。
|
||||
|
||||
**验证数据支撑**:5/5 点恢复,其中 3 个点是"直接 nl"救回、2 个点是"he0 warm nc→nl"救回;两种路径互补。p4 案例证明即使不修 seed_finder、只用现有种子选择,仅改"seed_nc 失败回退原种子喂 nl"就能救回 p4 类点。
|
||||
|
||||
### 🔬 是否应把 seed_nc→nl 直接替换为 seed_nl(直接 nl)?—— **不应替换,应作回退/补充策略**(A/B 实测)
|
||||
|
||||
**DB 统计**(2861 个生产 seed_step 成功点):
|
||||
- **96%(2735)seed_nc 自己未收敛**,最终收敛全部由 nl 完成 —— seed_nc 从来不是收敛完成者;
|
||||
- 但 234 个点 seed_nc 发散到 >1e4,nl 仍从中收敛 —— 发散的 seed_nc 输出并非必然有毒。
|
||||
|
||||
**A/B 对照**(3 个生产 seed_step 成功点,同种子直接跑 nl,跳过 seed_nc):
|
||||
|
||||
| 点 | 生产(seed_nc→nl) | 直接 nl | 结论 |
|
||||
|---|---|---|---|
|
||||
| ab2 (35k) | ✅ nl 9.51e-4 | ✅ 31it 4.9e-4 | seed_nc 多余 |
|
||||
| ab3 (45k) | ✅ nl 7.39e-4 | ✅ 19it 5.6e-4 | seed_nc 多余 |
|
||||
| **ab1 (35k)** | ✅ nl 7.47e-4(注意其 seed_nc 发散到 2.65e8)| ❌ 100it 卡 8.2e8 | **seed_nc 必要!** |
|
||||
|
||||
**ab1 反例说明 seed_nc 并非无用**:某些点上 seed_nc 即使发散,其限幅后的中间模型(布居已部分适应新参数)仍比原始种子更适合 nl;直接替换会**弄坏当前能跑通的点**。
|
||||
|
||||
**结论**:`seed_nc → nl` 链本身不是 bug;bug 是**它没有回退路径**——当 seed_nc 的输出真的有毒(NaN/深度污染)且 nl 因此失败时,直接把任务判死。正确修复是**阶梯式**:
|
||||
|
||||
```
|
||||
seed_nc → nl (现状,覆盖 96% 场景)
|
||||
└─ nl 失败/NaN → nl_direct(用原始干净种子重跑 nl) (新增回退,救 p2/p3/p4 类)
|
||||
└─ 仍失败 → 下一策略(现有 fallback 不变)
|
||||
```
|
||||
|
||||
这样 ab1 类点走第一条路(保住),p4 类点走回退(救回),无任何回归风险。
|
||||
|
||||
---
|
||||
|
||||
## 八、修复实施(2026-08-14,已落地并验证)
|
||||
|
||||
### 8.1 代码改动(3 处)
|
||||
|
||||
| # | 文件 | 改动 | 验证 |
|
||||
|---|---|---|---|
|
||||
| 1 | `runner.rs` | 阶段执行体抽为 `execute_tlusty_stage()`;新增 **nl_direct 回退**:require_converged 阶段失败且非链首且有原始种子时,以 `<label>_direct` 标签用原始种子重跑该阶段,收敛则采纳 | 单元测试(假 tlusty 三步接线)✅;p2/p3 用完整生产 nst 的 direct-nl 物理验证 ✅(99it/6.8e-4、19it/8.6e-4,温度结构物理) |
|
||||
| 2 | `seed_finder.rs:51` | `d_teff < 5000.0` → `<= 5000.0`(相邻 Teff 档纳入 exact_family,桶索引本就支持) | 回归测试(55000↔60000 判 exact、距离 0)✅ |
|
||||
| 3 | `nst_writer.rs` | 新增 `NST_LINE_MAX=75` 换行(TLUSTY `CHARACTER*80` 行缓冲截断 bug,IFALI/TFLOOR 等此前从未生效) | 回归测试(全行 ≤75、TMOLIM/TFLOOR 在输出中)✅;**行为验证见下** |
|
||||
|
||||
### 8.2 nst 截断修复的行为验证(意外收获)
|
||||
|
||||
用修复后的 Rust 链端到端重跑 p4 场景(`e2e_nl_direct_fallback_recovers_poisoned_seed_chain`,真实二进制):
|
||||
- **主链 seed_nc→nl 自己收敛了**(seed_nc 仍发散到 4.6e14,但 nl 14it 收敛 2.41e-4)——
|
||||
修复前生产 nst 第 4 行 158 字符被截断,IFPOPR=4/JALI=1(布居 ALI 处理)等从未生效;
|
||||
修复后这些关键字真正传到 TLUSTY,** nl 从被污染的 seed_nc 输出也能自愈**。
|
||||
- 即 **#3 一项就可能显著改善生产成功率**;p2/p3 主链仍失败(回退 #1 兜底,已验证有效)。
|
||||
|
||||
### 8.3 验证总账
|
||||
|
||||
- `cargo test --workspace`:**197 passed / 0 failed**(含 3 个新回归测试)
|
||||
- `cargo clippy`:仅 2 个既有警告(conv_check.rs,非本次改动)
|
||||
- 端到端(真实二进制):p4 主链自愈 ✅;p2/p3 回退路径物理验证 ✅
|
||||
- 修复后预期:难点恢复路径 = 主链(nst 修复增益)→ nl_direct 回退 → 现有策略 fallback,三层递进
|
||||
|
||||
### 生产没走通的原因:seed_finder 两个 bug
|
||||
|
||||
查 `crates/server/src/db/seeds.rs:144-193` + `seed_finder.rs:51`:
|
||||
|
||||
**Bug 1(致命):exact_family 优先级压倒一切。** 选种逻辑「先找 exact_family(同 Teff/logg/He、仅 CNO 不同),有就**立即返回**,绝不看 global」。对 he-4 点:exact_family 候选 = 同 Teff 的 CNO 邻居 o-4(**富方向**、不稳定、距离 4.0)被选中;而经验证有效的 he0 候选(global、**贫 He 方向**、距离 2.0)**从未被考虑**。
|
||||
|
||||
**Bug 2:`d_teff < 5000.0`(严格小于)排除相邻 Teff 档。** 网格 Teff 步长正好 5000K,55k→60k 相邻档**永远进不了 exact_family**。而 Teff 方向经验证最稳(55k 点靠 50k Teff 种子收敛)。
|
||||
|
||||
### 推荐修复(按性价比)
|
||||
|
||||
1. **改选种逻辑**(治本,框架侧):当 exact_family 候选是富方向(距离大)时,应让它与 global 候选比较。最简方案——**对 he≤-2 的点,优先选 He-rich(he0/he+2)同族收敛模型作种子**(贫 He 方向,已验证 nl 能接住)。
|
||||
2. **`d_teff < 5000.0` → `<= 5000.0`**(一行):纳入相邻 Teff 档,让 55k 能作 60k 种子。
|
||||
3. **多步 Teff 连续**(用户思路):从已收敛低温点逐档升温 warm-start,每步 nc 有界 + nl 收尾。这是 1+2 修复后的自然产物。
|
||||
4. **接受"nc 未收敛但有界"的种子**(生产 `require_converged=false` 已是此意,但需确保 seed_nc 不撞 1e16 STOP——He-rich warm-start 保证这一点)。
|
||||
|
||||
### 前提:单参数/换起点 nc 自身收敛均已穷举证伪(但仍可用作 nl 的有界种子)
|
||||
|
||||
12+ 变体 + He-ladder 1 dex 细步全部不收敛。不稳性根植于 TLUSTY「完全线性化 + Kantorovich 加速」求解器在「贫 He + 高 Teff + 低 logg」表层的结构性失效。下列是**仍需进一步工作**的方向(本文未完全落地)。
|
||||
|
||||
### 方向 A(治本,需改源码):换加速/辐射算子
|
||||
|
||||
- ~~**试 IFALI>5(近似 Λ 算子)**~~ —— **已实测证伪**。`IFALI=6`(启用带状近似 Λ 算子,`:17411` 加 A/C 非对角耦合):
|
||||
- cold start + IFALI=6 → nc.7 全 NaN(比 baseline 的有界更差);
|
||||
- warm he0 + IFALI=6 → **iter1 MAX=1.84e95**(灾难性,vs 默认 4.3)。
|
||||
- 带状算子的非对角项在远离解时**放大**失稳,不是稳住。**IFALI=6 对本问题是反效果。**
|
||||
- 改 Kantorovich 切换逻辑或加自适应阻尼(前序文档优先级 1/2,工程量大)。这是剩下唯一可能见效的源码侧方向,但工程量大、需重编。
|
||||
|
||||
### 方向 B(止损,框架侧):诊断修正 + 难点标记
|
||||
|
||||
- **诊断侧**:check_fort9 的 CHMX 是未限幅值(§3.1),对难点恒高。可对 nc 阶段追加 POPZCH 过滤后的 CHMX + 温度结构/emflux 实物校验作辅助判据——但**不能**把有界未收敛模型当真收敛(§4.2 实测其表层 T 偏离)。
|
||||
- **任务侧**:把 he≤−2 + Teff≥50k + logg≤5.5 的 ~273 个最难点标记为"nc 求解器失效",单独排队/人工处理,不浪费 7-9 次重试。
|
||||
|
||||
### 方向 C(数据侧):缩小难点集
|
||||
|
||||
DB 统计这批点的收敛率随 He 丰度强烈变化(he=+2 93% → he=−4 66% 失败)。若科学上可接受,**优先保证 he≥0 的点**(冷启动大多收敛),he≤−2 的最难点降级处理。
|
||||
|
||||
### 已证伪、不推荐的方向
|
||||
|
||||
- **诊断侧**:DCTS 的 check_fort9 用未限幅 CHMX 判收敛,对这类点恒判失败。可在 framework 里对 nc 阶段**追加 POPZCH 过滤后的 CHMX** 与**温度结构/emflux 实物校验**作为辅助判据,但**不能**把"有界未收敛"模型当真收敛(实测其表层 T 偏离收敛参考)。
|
||||
- **任务侧**:把这批 he≤−2+高Teff+低logg 的点标记为"需 He-ladder",单独排队。
|
||||
|
||||
### 不推荐的方向(已穷举证伪)
|
||||
|
||||
- 任何单一 nst 数值参数(ORELAX/DPSILG/ITEK/IACC/POPZER/POPZCH/NITER);
|
||||
- ND=70、非灰 LTE 中间阶、trace 金属置 LTE、IFALI=5;
|
||||
- 仅靠 logg 连续(Δlogg 0.5 warm-start 仍崩);
|
||||
- **He-ladder 连续**(he0→he−1 单步 1 dex、he0→he−4 单跳 2 dex,iter1 都小但 iter5 Kant 爆)——不稳性在迭代加速环节不在起点;
|
||||
- 禁 Kantorovich(ITEK=999)——纯 full-CL 反而 NaN,Kantorovich 实为防 NaN 的稳定项;
|
||||
- **IFALI=6(带状近似 Λ 算子)**——cold→全 NaN、warm→iter1 MAX=1.84e95 灾难性;带状非对角项远离解时放大失稳。
|
||||
|
||||
---
|
||||
|
||||
## 六·五、附带发现:nst 行被 80 字符缓冲截断(生产 bug)
|
||||
|
||||
测 IFALI=6 时发现:`tlusty208.f:1689` 声明 `CHARACTER*80 TEXT`(nst 行读取缓冲),但 `nst_writer.rs` 写出的第 4 行长达 **158 字符**:
|
||||
|
||||
```
|
||||
HMIX0=-1.,MLTYPE=1,IPTURB=1,IBFINT=1,IHECOR=0,IRTE=0,IBC=3,IFRYB=0,IOPTAB=0,IFMOL=0,IFALI=5,IFPOPR=4,JALI=1,TRAD=0.,WDIL=0.,TFLOOR=8000.,TDISK=0.,TMOLIM=9000.
|
||||
```
|
||||
|
||||
第 80 字符(`...IFMO`)处被截断,**`IFALI`/`IFPOPR`/`JALI`/`TRAD`/`WDIL`/`TFLOOR`/`TDISK`/`TMOLIM` 全部从未被读取**,静默走 TLUSTY 默认。即生产中这些点的 `TFLOOR=8000`(`config.rs` 配的)实际没生效,用的是 TLUSTY 默认 TFLOOR。本复测所有需要这些参数的变体都已手动拆行(每行 ≤68 字符)验证可正确读取。
|
||||
|
||||
**建议修 `nst_writer.rs`**:keyword 组每组 ≤75 字符换行(对齐 `CHARACTER*80` 限制)。
|
||||
|
||||
---
|
||||
|
||||
## 六、与前序文档的关系
|
||||
|
||||
| 前序结论 | 本文状态 |
|
||||
|---|---|
|
||||
| "itek/iacc/DPSILG/orelax 调参无效" | **证伪其方法论**:CHMX 对这些参数不敏感(源码 :14643 在 :14647/:14652 之前),前序测了不敏感的指标。结论(这些参数救不回)方向对,但理由错。 |
|
||||
| "需连续法" | **方向确认**,并具体化为 **He-ladder**(而非泛泛的 Teff 连续),有 he0 warm-start iter1 改善 30× 的实测支撑。 |
|
||||
| "Newton 在灰化起点即在收敛域外" | **确认**(baseline iter1=140 且深层正常表层崩)。但 warm he0 能把 iter1 降到 4.3,说明**换个起点能进收敛域**。 |
|
||||
| "禁 Kantorovich 降 1e12~1e14" | **方法论警示**:那是 CHMX(未限幅)下降;纯 full-CL(ITEK=999)实测会 NaN,Kantorovich 实为稳定项,**不应禁用**。 |
|
||||
|
||||
---
|
||||
|
||||
## 七、复测产物位置
|
||||
|
||||
```
|
||||
test/20260813_cold_nc_trace_fix/convergence/t60000_g5.0_he-4_c-4_n-4_o-3_cold/
|
||||
├── baseline/ inputs/(lte+nc.5/nst) + outputs/(nc.6/7/9)
|
||||
├── popzer1e-9/ popzer1e-12/ popzch1e-9/
|
||||
├── nd70/ lte_ng/ trace_lte/ full_nst/
|
||||
├── warm_from_g5.5/ warm_from_he0/ warm_he0_nokant/
|
||||
└── cold_nokant/
|
||||
```
|
||||
每个变体 `outputs/nc.9` 可用 `scripts/check_fort9.py` 复核;`nc.7` 可用 `check_temperature.py` 复核。
|
||||
@@ -0,0 +1,340 @@
|
||||
# TLUSTY 本机直接运行实验记录(根因验证与修复测试)
|
||||
|
||||
**日期**:2026-08-11
|
||||
**方法**:绕过 DCTS 框架,直接运行 TLUSTY 二进制(`dcts/assets/tlusty_static`),本地 16 核并行测试不同策略
|
||||
**测试对象**:生产 DB(`/home/fmq/下载/dcts.db`)标记 `tlusty_status=failed` 的网格点
|
||||
**源码参考**:`/home/fmq/program/tlusty/tl208-s54/tlusty/tlusty208.f` + dcts `workflows/sdB_cno.yaml`
|
||||
**测试规范**:见 `docs/testing_workflow_2026_08_11.md`;测试产物已迁移至 `test/20260811_tlusty_divergence/`
|
||||
|
||||
---
|
||||
|
||||
## ⚠️ 2026-08-12 更正
|
||||
|
||||
本文实验使用的 `itek:0` / `iacc:0` 参数存在**致命语义误解**(后续源码核对确认):
|
||||
- `ITEK=0` → `tlusty208.f:1937` `if(nitzer.gt.itek) nitzer=itek` → **nitzer=0 冻结
|
||||
populations**,不是"禁用 Kantorovich"——所有 `itek0*` 变体都在 populations 冻结
|
||||
下运行,其结果不能解释为 Kantorovich 的因果。
|
||||
- `IACC=0` → `tlusty208.f:1928` `IF(IACC.LE.4) IACC=7` → **被 clamp 回 7**,空操作——
|
||||
所有 `iacc0*` 变体实际仍是默认 Ng 加速。
|
||||
|
||||
正确禁用方式是设 `ITEK/IACC > NITER`。2026-08-12 用正确方式复测 6 个点 × 4 配置:
|
||||
**0 次收敛**(发散幅度降 1e12~1e14× 但达不到收敛)。详见
|
||||
`docs/cold_start_fix_2026_08_12.md`。本文保留为历史实验记录。
|
||||
|
||||
---
|
||||
|
||||
## 一、背景与目的
|
||||
|
||||
生产 DB 有 1105 个网格点 TLUSTY 不收敛(cold_run 与 seed_step 均失败,见 `tlusty_divergence_root_cause_2026_08_11.md`)。为验证根因假设并测试修复策略,本实验绕过框架直接运行二进制,精确复现生产输入与失败。
|
||||
|
||||
**本实验回答的三个问题**:
|
||||
1. 生产失败能否在本地精确复现?(可信度锚点)
|
||||
2. 失败的直接机制是什么?(KANT/ACCEL2 加速)
|
||||
3. 何种策略能修复?(ITEK=0 + IACC=0 + DPSILG 收紧的假设性验证)
|
||||
|
||||
---
|
||||
|
||||
## 二、运行方法(与 DCTS 框架等价)
|
||||
|
||||
TLUSTY 二进制通过 **stdin** 读取 `.5` 输入文件(`runner.rs:406-416` 同款调用方式):
|
||||
|
||||
```bash
|
||||
tlusty_static < {stage}.5 > fort.6 2> fort.14
|
||||
```
|
||||
|
||||
### 2.1 工作目录布局
|
||||
|
||||
| 文件/目录 | 用途 |
|
||||
|---|---|
|
||||
| `data/` → 软链到 `assets/data/` | 原子数据(c1.dat/h1.dat 等 133 文件) |
|
||||
| `nst` | 非标准参数(由 `.5` 第3行 `'nst'` 指定文件名),每阶段复制 `{stage}.nst` |
|
||||
| `fort.8` | 热启动种子(seed_nc 阶段);冷启动 lte 阶段无(ltgray=T 删除) |
|
||||
| `fort.19` → 软链到 `assets/data/gfATO.dat` | SYNSPEC 线表 |
|
||||
| 产物 | `fort.6`(log)、`fort.7`(模型)、`fort.9`(迭代收敛记录)、`fort.14`(stderr) |
|
||||
|
||||
### 2.2 冷启动链(与 YAML `tlusty_chain` 一致)
|
||||
|
||||
```
|
||||
lte(NITER=0, LTE=T, LTGRAY=T, ilvlin=0, 无 fort.8) ← 灰 LTE 起始
|
||||
→ nc(NITER=10, LTE=F, LTGRAY=F, ilvlin=0) ← 策略变体作用阶段
|
||||
→ nl(NITER=100, LTE=F, LTGRAY=F, ilvlin=100) ← 必须收敛
|
||||
```
|
||||
|
||||
阶段间通过 `fort.7 → fort.8` 传递种子。
|
||||
|
||||
### 2.3 种子步进(与 YAML `seed_chain` 一致)
|
||||
|
||||
```
|
||||
seed_nc(NITER=20, LTE=F, LTGRAY=F, ilvlin=0, fort.8=生产收敛邻居种子)
|
||||
```
|
||||
|
||||
### 2.4 可信度锚点
|
||||
|
||||
本机 `seedprod`(生产配置 + 生产真实种子)seed_nc 轨迹:
|
||||
`[7.0, 115, 272, 2020, 1.49e8, 8.26e13, ...]`
|
||||
|
||||
生产 DB 记录的 conv.json 轨迹:
|
||||
`[7.0, 102, 288, 945, 1.12e8, 4.85e13, ...]`
|
||||
|
||||
**逐值吻合(同一数量级)** → 本机测试精确复现生产失败,测试方法可信。
|
||||
|
||||
---
|
||||
|
||||
## 三、根因(源码 + 实证双重确认)
|
||||
|
||||
雪崩由 **TLUSTY 两个加速机制**绕过 DPSILT 钳制引起,二者独立:
|
||||
|
||||
### 3.1 机制1:KANT / Kantorovich 线性化(`tlusty208.f:3841, 14493-14494`)
|
||||
|
||||
- 默认 `ITEK=4` → `KANT(iter)=1` for iter≥5(`tlusty208.f:848-854`)
|
||||
- KANT=1 时**冻结矩阵**:`CALL RHSGEN` 只算右端项,`READ(93)/READ(92)` 复用上次迭代的 A/B 矩阵(`tlusty208.f:14500-14540`)
|
||||
- 在收敛困难区,冻结矩阵与实际解严重偏离 → Newton 步方向错误 → 雪崩
|
||||
- **证据**:基线 nc 雪崩始于 iter5(第一个 KANT 迭代)
|
||||
|
||||
### 3.2 机制2:ACCEL2 / Ng 加速(`tlusty208.f:29693-29800`)
|
||||
|
||||
- 由 `IACC` 控制(默认 7),与 ITEK 独立;iter≥7 触发
|
||||
- 直接外推 PSY0 全体变量:`PSY0=(1-A-B)*PSY0 + A*PSY1 + B*PSY2`(`tlusty208.f:29763-29773`)
|
||||
- **绕过 SOLVE 内 DPSILG/DPSILT 钳制循环**(钳制只约束 CHAN 局部变量,ACCEL2 直接改 PSY0 全局)
|
||||
- **证据**:nl 阶段 iter7/11/15 的 ACCEL2 触发 → 温度外推爆炸 → STOP
|
||||
|
||||
### 3.3 两机制独立性(实证)
|
||||
|
||||
- `itek0`(只禁 KANT):nc 雪崩推迟但 nl 仍 STOP(ACCEL2 未禁)
|
||||
- `iacc0`(只禁 ACCEL2):nl 仍 STOP(KANT 未禁)
|
||||
- **必须双禁(ITEK=0 + IACC=0)** 才能抑制两个加速通道
|
||||
|
||||
### 3.4 雪崩主导变量
|
||||
|
||||
fort.9 显示 nl 阶段 |T| 修正从 iter1 的 4.26e5 爆炸到 iter19 的 1.2e16,**温度是雪崩主导变量**(RAD 辐射场修正一直 ≤360,ne 基本稳定)。DPSILT=1.25 钳制(±25%)被 ACCEL2 外推绕过。
|
||||
|
||||
---
|
||||
|
||||
## 四、测试矩阵与结果
|
||||
|
||||
### 4.1 策略变体定义(nc/nl 阶段 nst 差异)
|
||||
|
||||
| 变体 | ITEK | IACC | DPSILG | ORELAX(nc) | 说明 |
|
||||
|---|---|---|---|---|---|
|
||||
| baseline | 4(默认) | 7(默认) | 10(默认) | — | 生产配置 |
|
||||
| trueprod | 4(默认) | 7(默认) | 10(默认) | — | 真生产(nl 也 ITEK=4,见瑕疵说明) |
|
||||
| itek0 | 0 | 7 | 10 | — | 禁 Kantorovich |
|
||||
| dpsilg3 | 4 | 7 | 3.0 | — | 收紧限幅 |
|
||||
| itek0_dpsilg3 | 0 | 7 | 3.0 | — | 组合 |
|
||||
| itek0_dpsilg2_orelax03 | 0 | 7 | 2.0 | 0.3 | 组合+松弛 |
|
||||
| iacc0 | 4 | 0 | 10 | — | 禁 Ng 加速 |
|
||||
| itek0_iacc0 | 0 | 0 | 10 | — | 双禁 |
|
||||
| itek0_iacc0_dpsilg15 | 0 | 0 | 1.5 | — | 双禁+收紧 |
|
||||
| seedprod | 4 | 7 | 10 | — | seed_nc 生产 |
|
||||
| seed_doubleoff | 0 | 0 | 10 | — | seed_nc 双禁 |
|
||||
| seed_doubleoff_dpsilg15 | 0 | 0 | 1.5 | — | seed_nc 双禁+收紧 |
|
||||
|
||||
> ⚠️ **测试瑕疵说明**:初版 prepare.py 给所有变体(含 baseline)的 **nl.nst 无条件加了 ITEK=0**。故表内 baseline 的 nl 阶段实际是 ITEK=0(非生产默认 4)。由于失败发生在 nc 阶段(nc 的 ITEK 是生产默认),**nc 阶段对比仍然公平**;为消除 nl 混淆,补跑了 `trueprod`(nl 也为默认 ITEK=4 的真生产配置)。
|
||||
|
||||
### 4.2 冷启动链结果(点 t60000_g5.0_he-2_c-4_n-4_o-4)
|
||||
|
||||
**nc 阶段轨迹**(max_relc,雪崩与否关键看前 5 步):
|
||||
|
||||
| 变体 | iter1 | iter2 | iter3 | iter4 | iter5 | nc 结局 |
|
||||
|---|---|---|---|---|---|---|
|
||||
| baseline | 9.7 | 46 | 259 | 1950 | **4.85e6** | 雪崩→2.3e15 |
|
||||
| dpsilg3 | 9.7 | 64 | 31 | 48 | 3.8e3 | 高位雪崩→1.8e8 |
|
||||
| iacc0 | 9.7 | 46 | 260 | 2000 | **4.8e6** | 雪崩(同 baseline) |
|
||||
| itek0 | 1.4 | 9.1 | 38 | 460 | 77 | 高位徘徊 |
|
||||
| itek0_dpsilg3 | 1.4 | 2.9 | 3.7 | 33 | 3.8 | 改善 |
|
||||
| itek0_iacc0 | 1.4 | 9.1 | 38 | 460 | 77 | 高位徘徊 |
|
||||
| itek0_iacc0_dpsilg15 | 1.4 | 1.4 | 1.9 | 14 | 5.3 | **低位稳定** |
|
||||
| itek0_dpsilg2_orelax03 | 1.4 | 1.9 | 2.1 | 35 | 12 | 低位稳定 |
|
||||
|
||||
**nl 阶段结局**:
|
||||
|
||||
| 变体 | nl 结局 | NaN行(nl.7) | 末 max_relc | 温度结构(DCTS判据) | 判定 |
|
||||
|---|---|---|---|---|---|
|
||||
| baseline | STOP iter19 | 0 | 1.23e16 | ✅ 物理(0.86×Teff) | ❌ |
|
||||
| dpsilg3 | STOP iter14 | 0 | 7.59e16 | ✅ 物理(0.80×Teff) | ❌ |
|
||||
| iacc0 | STOP iter10 | 0 | 1.72e17 | ✅ 物理(0.78×Teff) | ❌ |
|
||||
| itek0 | STOP iter18 | 0 | 2.95e16 | ❌ 不物理(5.2×Teff) | ❌ |
|
||||
| itek0_dpsilg3 | STOP iter26 | 0 | 5.96e17 | ❌ 不物理(5.2×Teff) | ❌ |
|
||||
| itek0_iacc0 | STOP iter22 | 0 | 1.16e17 | ❌ 不物理(10.5×Teff) | ❌ |
|
||||
| itek0_dpsilg2_orelax03 | 无 STOP | **2707** | 0.0(伪) | ❌ NaN | ❌ 伪收敛 |
|
||||
| itek0_iacc0_dpsilg15 | 无 STOP | **2031** | 0.0(伪) | ❌ NaN | ❌ 伪收敛 |
|
||||
|
||||
**冷启动结论**:8 个变体在该极端点(60k/g5.0, CNO 全-4)全部失败。即使双禁+收紧 DPSILG,nl 阶段要么 STOP 要么产生 NaN。**冷启动从 LTE 灰化初值建立 NLTE 电离平衡在此点本质困难**,与加速机制无关。
|
||||
|
||||
### 4.3 种子步进结果(seed_nc,用生产真实种子 t60000_g5.0_he-2_c-3_n-4_o-4.7)
|
||||
|
||||
| 变体 | 轨迹(前6) | 结局 | NaN | 末 max_relc | 温度结构(DCTS判据) |
|
||||
|---|---|---|---|---|---|
|
||||
| seedprod(生产) | 7.0,115,272,2020,1.49e8,8.26e13 | STOP iter14 | 0 | 1.71e17 | ✅ 物理(0.49×Teff) |
|
||||
| seed_doubleoff | 1.3,17,9,92,400,142 | 无 STOP | 0 | 4.71e06 | ✅ 物理(1.58×Teff) |
|
||||
| seed_doubleoff_dpsilg15 | 1.3,16,7,1.7,2.9,6.3 | 无 STOP | 0 | **4.86e01** | ❌ 不物理(7.89×Teff) |
|
||||
|
||||
**种子步进结论**:
|
||||
- seedprod 精确复现生产失败(可信度锚点)
|
||||
- **双禁(ITEK=0+IACC=0)把 seed_nc 从雪崩(1e8-1e17)抑制到 1e2-1e3 量级**
|
||||
- **再加 DPSILG=1.5 进一步压到几十量级**(末值 48.6),20 步全程无 STOP 无 NaN
|
||||
- ⚠️ **温度物理性(修正版,见 §5.3 判据更新)**:seed_doubleoff(1.58×Teff)其实**物理**;seed_doubleoff_dpsilg15(7.89×Teff)**不物理**(超 DCTS 3×Teff 上限)。但两者**迭代均未收敛**(末值 4.7e6 / 48.6,均 >> 0.001)。早期文档用"3-6 万 K 表层"经验标准误判 seed_doubleoff 为不物理,已按 DCTS `check_temperature_structure` 判据(表层 < 3×Teff)修正
|
||||
|
||||
---
|
||||
|
||||
## 五、多点评测(5 个代表失败点 × baseline + 修复组合)
|
||||
|
||||
为验证根因与修复的普适性,对 5 个额外失败点补跑了冷启动链(每个点 baseline + `itek0_iacc0_dpsilg15` 两个变体)。加上 t60000_g5.0 的单点深测,共 **6 点、9 种变体、19 次完整冷启动链**(含 trueprod 对照)**+ 3 次 seed_nc**。
|
||||
|
||||
### 5.1 各点 nc 阶段早期轨迹(iter1-5,雪崩与否看这里)
|
||||
|
||||
| 点 | baseline iter1-5 | 修复组合 iter1-5 | nc 对比 |
|
||||
|---|---|---|---|
|
||||
| t60000_g5.0 | 9.7→46→259→1950→4.85e6 | 1.4→1.4→1.9→14→5.3 | ✅ 修复显著抑制(雪崩→低位) |
|
||||
| t50000_g5.0 | 3.8→260→56→0.4 | 0.9→14→1.1→1.1→9.0 | ✅ 修复显著抑制 |
|
||||
| t50000_g5.5 | 1810→76→243→**0.2** | 258→11→**552**→29→60 | ⚠️ 修复反而变差 |
|
||||
| t55000_g5.0 | 5.8→42→117→646→5.99e6 | 1.1→2.1→0.6→1.8→1.0 | ✅ 修复抑制(雪崩→低位) |
|
||||
| t55000_g5.5 | 9.0→58→85→**0.09** | 1.5→1.2→1.2→0.45→27 | ⚠️ baseline 也能收敛 |
|
||||
| t60000_g5.5 | 5.2→119→77→151→**0.14** | 0.32→1.6→5.3→23→19 | ⚠️ baseline 也能收敛 |
|
||||
|
||||
**重要修正**:修复组合**并非在全部点抑制雪崩**:
|
||||
- t60000_g5.0、t55000_g5.0 的 baseline nc **雪崩**(iter5 到 4.85e6 / 5.99e6),修复组合压到低位 → ✅ 修复有效
|
||||
- t50000_g5.5、t55000_g5.5、t60000_g5.5 的 baseline nc **本来就能收敛**(iter4-5 回落到 0.1-0.2),修复组合反而把轨迹扰乱(如 t50000_g5.5 修复 iter3=552)→ ⚠️ 修复有害或无益
|
||||
- **修复组合的价值集中在"baseline 会雪崩"的点**;对"baseline 能收敛"的点反而画蛇添足
|
||||
- 注意:**nc 阶段能回落 ≠ 整条链收敛**——这些点的 baseline nl 阶段仍失败(见 §5.2,t50000_g5.5 末 3.39e11、t55000_g5.5 STOP、t60000_g5.5 NaN),说明收敛瓶颈在 nl 阶段而非 nc
|
||||
|
||||
### 5.2 各点 nl 阶段结局(完整链)
|
||||
|
||||
| 点 | baseline nl 结局 | 修复组合 nl 结局 | 修复效果 |
|
||||
|---|---|---|---|
|
||||
| t60000_g5.0 | STOP iter19 (1.23e16) | 31步,无STOP,NaN=2031 | ⚠️ 抑制雪崩但 NaN |
|
||||
| t50000_g5.0 | 11步,无STOP,NaN=3659 | 19步,无STOP,NaN=3375 | ⚠️ 无实质改善 |
|
||||
| t50000_g5.5 | 10步,末3.39e11,NaN=0 | 19步,无STOP,NaN=3595 | ⚠️ 变差(NaN) |
|
||||
| t55000_g5.0 | STOP iter17 (1.05e16) | 58步,无STOP,NaN=0,末1.26 | ⚠️ 大幅改善但不收敛 |
|
||||
| t55000_g5.5 | STOP iter30 (1.49e17) | 100步,无STOP,NaN=0,末1.38e8 | ⚠️ 改善但不收敛 |
|
||||
| t60000_g5.5 | 19步,无STOP,NaN=3789 | 26步,无STOP,NaN=0,末1.72 | ⚠️ 改善但不收敛 |
|
||||
|
||||
### 5.3 温度结构物理性检查(判据已修正,关键!)
|
||||
|
||||
> 🔄 **判据修正说明(2026-08-11 补)**:初版用经验标准"60k 恒星表层应有 3-6 万 K"判断物理性,**该标准有误**。经文献调研(Eddington 灰大气关系 T(τ=0)≈0.84×Teff)与 DCTS 权威 `check_temperature_structure`(`conv_check.rs:463`)判据(表层 < 3×Teff、各深度 ∈ [10, 1e8] K)核对,**正确标准是表层 < 3×Teff**。60k 恒星表层应约 0.84×60k ≈ 5 万 K,3-6 万 K 的经验值偏低。本节按 DCTS 判据重新判定。
|
||||
|
||||
对每个变体的 nl.7 用 DCTS `check_temperature_structure` 判据(表层 < 3×Teff、无 NaN、各深度 ∈ [10, 1e8] K):
|
||||
|
||||
| 点 | baseline nl.7 | 修复组合 nl.7 |
|
||||
|---|---|---|
|
||||
| t60000_g5.0 | ✅ 物理(0.86×Teff) | ❌ NaN |
|
||||
| t50000_g5.0 | ❌ NaN | ❌ NaN |
|
||||
| t50000_g5.5 | ✅ 物理(0.83×Teff) | ❌ NaN |
|
||||
| t55000_g5.0 | STOP(无 nl.7) | ❌ 内部 6.2e9 K |
|
||||
| t55000_g5.5 | STOP(无 nl.7) | ❌ 内部 1.8e10 K |
|
||||
| t60000_g5.5 | ❌ NaN | ✅ 物理(0.85×Teff, max=1.4e7) |
|
||||
|
||||
**修正后的发现**(较初版更准确):
|
||||
- **修复组合并非"全部不物理"**:t60000_g5.5 修复组合温度结构**物理**(表层 0.85×Teff、max=1.4e7 < 1e8),只是迭代未收敛(末值 1.72)
|
||||
- **真正不物理的**:t55000_g5.0(内部 6.2e9 K)、t55000_g5.5(内部 1.8e10 K)修复组合,及所有 NaN 模型
|
||||
- **baseline 在 STOP 前温度物理**(t60000_g5.0、t50000_g5.5)
|
||||
- **两类问题需要区分**:
|
||||
1. **温度物理但未收敛**(如 t60000_g5.5 修复组合:表层合理、max 合理、但 max_relc=1.7)→ 有希望,增大迭代或收紧参数可能收敛
|
||||
2. **温度不物理**(如 t55000 修复组合:内部 1e9+ K)→ 真正的失败,需换策略
|
||||
|
||||
---
|
||||
|
||||
## 六、总结论
|
||||
|
||||
1. **生产失败直接原因(已实证)**:KANT(iter≥5) + ACCEL2(iter≥7) 两个加速机制绕过 DPSILG/DPSILT 钳制,在收敛困难区放大 Newton 修正 → 雪崩 STOP(1e16 保护)或 NaN。本机测试精确复现生产轨迹。
|
||||
2. **修复组合(ITEK=0+IACC=0+DPSILG=1.5)对"会雪崩的点"有效**:t60000_g5.0、t55000_g5.0 的 baseline nc 雪崩(iter5 到 4.85e6 / 5.99e6),修复组合压到低位;**但对"本就能收敛的点"反而有害**(t50000_g5.5、t55000_g5.5、t60000_g5.5 的 baseline nc 在 iter4-5 本就回落到 0.1-0.2,修复组合却把轨迹扰乱,如 t50000_g5.5 修复 iter3=552)。修复价值有条件性,需按点评估。
|
||||
3. **温度物理性(判据修正后)**:修复组合**并非全部不物理**——t60000_g5.5 修复组合温度结构物理(表层 0.85×Teff),只是迭代未收敛;真正不物理的是 t55000 两个点(内部 1e9+ K)和所有 NaN 模型。**所有变体迭代均未收敛到 max_relc<0.001**(末值最低 1.26 / 1.72)。
|
||||
4. **根因不在加速机制本身**:KANT/ACCEL2 只是雪崩放大器。**根本问题是这些极端点(高 Teff + 低 logg + 极贫 CNO)的 NLTE 迭代难以在有限迭代内收敛**——但温度结构物理的点(如 t60000_g5.5)表明**物理解存在且可达**,只是需要更多迭代或更优参数。
|
||||
5. **文献对照**:Németh et al. (2013) 用 TLUSTY 204 计算了覆盖 60k/低logg 的 sdB/sdO 网格(11,396 点,95.5% 收敛,收敛标准=结构相对变化 0.1% 即 max_relc<0.001)。**本参数区在文献中是收敛的**——支持"配置/迭代问题而非物理不可能"的判断。
|
||||
6. **结论修正**:正确表述是:**双禁+收紧 DPSILG 能抑制发散并给出温度物理的候选模型(部分点),但多数极端点在 NITER 内未达 0.001 收敛**。下一步应对温度物理的点(t60000_g5.5、seed_doubleoff)增大 NITER 验证是否最终收敛。
|
||||
|
||||
---
|
||||
|
||||
## 七、生产落地建议(YAML 层面,零代码,但需配合物理诊断)
|
||||
|
||||
已验证 `ChainStep` 有 `iacc` 字段(`config.rs:1148`)、`nst_writer` 写 `IACC=`(`nst_writer.rs:78`),YAML 可配置:
|
||||
|
||||
```yaml
|
||||
seed_chain:
|
||||
- {label: seed_nc, lte: F, ltgray: F, ilvlin: 0, require_converged: false,
|
||||
niter: 50, chmax: 0.001, itek: 0, iacc: 0, orelax: 0.3}
|
||||
- {label: seed_nl, lte: F, ltgray: F, ilvlin: 100, require_converged: true,
|
||||
niter: 100, chmax: 0.001, itek: 0, iacc: 0, orelax: 0.5}
|
||||
nst_extra_keys:
|
||||
- [DPSILG, "1.5"]
|
||||
```
|
||||
|
||||
**但基于 §5.3 的发现,必须同时**:
|
||||
- **保留 DCTS 的温度结构校验**(`temp_check`:表层 T < 3×Teff、无 NaN)——它正是拦截"非物理伪收敛"的关卡
|
||||
- **优先走 seed_step**:冷启动在极端点本质困难(§4.2 冷启动 8 变体全失败),生产数据也显示 60k/g5.0 收敛靠 seed_step
|
||||
- **对仍失败的极端点**:接受其为物理上难收敛区,或探索更深层修复(ND 加密 / TFLOOR 调整 / 频率网格 / 原子数据)
|
||||
|
||||
**下一步验证计划**:
|
||||
- [x] ~~多点评测确认普适性~~(已完成:6 点 9 种变体 19 次完整链,见 §五)
|
||||
- [x] ~~校验温度结构物理性~~(已完成并修正判据:见 §5.3;温度物理点有 t60000_g5.5 修复组合、seed_doubleoff)
|
||||
- [ ] 对温度物理但未收敛的点(t60000_g5.5 修复组合、seed_doubleoff)**增大 NITER**(如 200)验证是否最终收敛到 0.001
|
||||
- [ ] 对真正不物理的点(t55000 两个点)换策略:seed_step + 双禁 + 温度校验
|
||||
- [ ] 探索 ND=70 / TFLOOR 调整对极端点的影响
|
||||
- [ ] 通过后作为难收敛区统一配置
|
||||
|
||||
---
|
||||
|
||||
## 八、测试产物与规范迁移
|
||||
|
||||
本实验的完整数据与运行产物已按 `docs/testing_workflow_2026_08_11.md` 规范迁移至:
|
||||
|
||||
```
|
||||
test/20260811_tlusty_divergence/convergence/
|
||||
├── t60000_g5.0_he-2_cold/
|
||||
│ ├── baseline/ # inputs+outputs+scripts+README
|
||||
│ └── itek0_iacc0_dpsilg15/
|
||||
├── t60000_g5.0_he-2_seed/
|
||||
│ └── doubleoff_dpsilg15/
|
||||
└── t55000_g5.0_he-4_cold/
|
||||
└── itek0_iacc0_dpsilg15/
|
||||
```
|
||||
|
||||
### 原始实验数据(/tmp/cold_test,供追溯)
|
||||
|
||||
- 运行脚本:`/tmp/cold_test/run_chain.sh`(冷启动链)、`/tmp/cold_test/run_seed.sh`(seed_nc)、`/tmp/cold_test/prepare.py`(输入生成)
|
||||
- 数据汇总:`/tmp/cold_test/t60000_summary.json`、`/tmp/cold_test/seed_summary.json`、`/tmp/cold_test/all_points_summary.json`
|
||||
- 运行目录:`/tmp/cold_test/runs/{点}__{变体}/`(6 点)、`/tmp/cold_test/seedruns/{变体}/`(3 seed 变体)
|
||||
- 运行日志:`/tmp/cold_test/fill_*.log`(补跑)、`/tmp/cold_test/seed_*.log`(seed_nc)
|
||||
|
||||
---
|
||||
|
||||
## 九、测试清单(全部 24 次运行,无遗漏)
|
||||
|
||||
### 冷启动链(6 点,9 种变体,19 次完整链)
|
||||
|
||||
| # | 点 | 变体 | 结果 |
|
||||
|---|---|---|---|
|
||||
| 1 | t60000_g5.0_he-2_c-4_n-4_o-4 | baseline | STOP iter19 (1.23e16) |
|
||||
| 2 | 同上 | trueprod(真生产nl ITEK=4) | NaN=4111 |
|
||||
| 3 | 同上 | itek0 | STOP iter18 (2.95e16) |
|
||||
| 4 | 同上 | dpsilg3 | STOP iter14 (7.59e16) |
|
||||
| 5 | 同上 | itek0_dpsilg3 | STOP iter26 (5.96e17) |
|
||||
| 6 | 同上 | itek0_dpsilg2_orelax03 | NaN=2707 |
|
||||
| 7 | 同上 | iacc0 | STOP iter10 (1.72e17) |
|
||||
| 8 | 同上 | itek0_iacc0 | STOP iter22 (1.16e17) |
|
||||
| 9 | 同上 | itek0_iacc0_dpsilg15 | NaN=2031 |
|
||||
| 10 | t50000_g5.0_he-4_c-4_n-4_o-4 | baseline | NaN=3659 |
|
||||
| 11 | 同上 | itek0_iacc0_dpsilg15 | NaN=3375 |
|
||||
| 12 | t50000_g5.5_he-4_c-2_n-4_o-4 | baseline | 10步 末3.39e11 NaN=0 |
|
||||
| 13 | 同上 | itek0_iacc0_dpsilg15 | NaN=3595 |
|
||||
| 14 | t55000_g5.0_he-4_c-4_n-4_o-4 | baseline | STOP iter17 (1.05e16) |
|
||||
| 15 | 同上 | itek0_iacc0_dpsilg15 | 58步 末1.26 温度❌(内部6.2e9K) |
|
||||
| 16 | t55000_g5.5_he-4_c-4_n-4_o-2 | baseline | STOP iter30 (1.49e17) |
|
||||
| 17 | 同上 | itek0_iacc0_dpsilg15 | 100步 末1.38e8 温度❌(内部1.8e10K) |
|
||||
| 18 | t60000_g5.5_he-4_c-4_n-4_o-4 | baseline | NaN=3789 |
|
||||
| 19 | 同上 | itek0_iacc0_dpsilg15 | 26步 末1.72 温度✅(0.85×Teff)未收敛 |
|
||||
|
||||
### seed_nc(1 点,3 变体,用生产真实种子)
|
||||
|
||||
| # | 点 | 变体 | 结果 |
|
||||
|---|---|---|---|
|
||||
| 20 | t60000_g5.0_he-2_c-4_n-4_o-4 | seedprod(生产) | STOP iter14 (1.71e17) ← 精确复现生产 |
|
||||
| 21 | 同上 | seed_doubleoff | 20步 末4.71e6 温度✅(1.58×Teff)未收敛 |
|
||||
| 22 | 同上 | seed_doubleoff_dpsilg15 | 20步 末48.6 温度❌(7.89×Teff) |
|
||||
|
||||
### 冒烟测试(方法验证)
|
||||
|
||||
| # | 内容 | 结果 |
|
||||
|---|---|---|
|
||||
| 23 | lte/nc/nl 三阶段调用方式 | 验证通过,fort.7/fort.9 正常生成 |
|
||||
| 24 | 生产轨迹复现 | seedprod 轨迹与生产 DB 一致 |
|
||||
@@ -0,0 +1,507 @@
|
||||
# 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<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),防止首步就冲出吸引域:
|
||||
|
||||
```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)用独立脚本/源码核对确认属实后,方才修订。
|
||||
@@ -0,0 +1,180 @@
|
||||
# TLUSTY 实验报告重跑验证(VERIFICATION)
|
||||
|
||||
**日期**:2026-08-11
|
||||
**验证对象**:`docs/tlusty_directrun_experiment_2026_08_11.md` 的全部结论
|
||||
**验证方法**:用生产当前二进制 `assets/tlusty_static`(8/11 新编版)重跑文档实验 + 决定性对照实验(同源码仅差 `-fno-toplevel-reorder`)
|
||||
**产物**:`/tmp/cold_test/verify_rerun/`(重跑)+ `/tmp/cold_test/definitive2_{reorder,noreorder}/`(决定性对照)+ `test/20260811_tlusty_divergence/verify_{rerun,analyze}.*`
|
||||
|
||||
> ⚠️ **本报告经历了结论修正**。初版(基于 data/runtime 旧版 vs assets 新版的对比)错误地把 iter4+ 轨迹差异归因于 `-fno-toplevel-reorder` 编译选项。后经决定性对照实验(§三)证伪——见 §六的修正记录。本版是修正后的结论。
|
||||
|
||||
> ⚠️ **2026-08-12 追加更正**:本报告确认的"双禁(ITEK=0+IACC=0)抑制雪崩"是**观察层面成立、机制层面误解**——
|
||||
> `ITEK=0` 通过 `tlusty208.f:1937` 令 nitzer=0 **冻结 populations**(首步修正变小是因为布居没在算,
|
||||
> 不是加速被禁),`IACC=0` 被 `tlusty208.f:1928` clamp 回 7 是空操作。用正确方式(ITEK/IACC>NITER)复测
|
||||
> 6 点 × 4 配置 0 次收敛,配置改动已回退。详见 `docs/cold_start_fix_2026_08_12.md`。
|
||||
|
||||
---
|
||||
|
||||
## 一、结论先行(修正后)
|
||||
|
||||
1. **`-fno-toplevel-reorder` 对 TLUSTY 数值行为零影响**——决定性对照实验证实:同源码、同编译器、仅差此一个 flag 的两版二进制,对同一输入的 `fort.9` 逐字节一致。这与 `docs/synspec_nan_fix_2026_08_11.md §10.4` 的结论完全一致。
|
||||
|
||||
2. **文档 `tlusty_directrun_experiment` 的核心机制结论成立**:KANT(iter≥5) + ACCEL2(iter≥7) 双加速机制绕过 DPSILG/DPSILT 钳制导致雪崩——这是源码逻辑,与编译选项无关,在新二进制下依然成立。
|
||||
|
||||
3. **文档的可信度锚点成立**:用新二进制(assets)重跑 seedprod,iter1 起的 `fort.9` 数据与生产 DB 记录在同一数量级(iter1 max_relc=7.0),测试方法可信。
|
||||
|
||||
4. **data/runtime 旧二进制 ≠ 当前源码编译版**——它使用了不同的源码状态(iter2 = 102 vs 当前源码编译版 115)。生产 DB 的失败记录是用这个旧版产生的,但其失败模式(雪崩)与当前源码编译版的失败模式在机制上一致。
|
||||
|
||||
5. **`-fno-toplevel-reorder` 的真实作用**是修复 **SYNSPEC** 的 Balmer 跃迁 NaN(见 `synspec_nan_fix_2026_08_11.md`),对 TLUSTY 只是"预防性同编译"(文档 §4.1),不改变 TLUSTY 行为。
|
||||
|
||||
---
|
||||
|
||||
## 二、三个二进制的身份厘清(关键背景)
|
||||
|
||||
本次验证涉及三个 md5 不同的 TLUSTY 二进制:
|
||||
|
||||
| 二进制 | md5 | 大小 | 身份 | 来源 |
|
||||
|---|---|---|---|---|
|
||||
| `data/runtime/tlusty_static` | `14cd144b...` | 1.55MB | **旧生产版**(产生 DB 失败记录)| 7/31 编译,源码状态不明 |
|
||||
| `~/tlusty_O3_backups_20260811/tlusty.exe.O3.bak` | `9b249e00...` | 2.00MB | 备份的原版(含调试回显行混叠)| 8/11 备份 |
|
||||
| `assets/tlusty_static` | `77721c4c...` | 1.96MB | **当前生产版**(-O3 -fno-toplevel-reorder)| 8/11 编译 |
|
||||
|
||||
**编译命令**(`docs/deployment.md`):
|
||||
```bash
|
||||
gfortran -fno-automatic -fno-toplevel-reorder -mcmodel=medium -O3 -o assets/tlusty_static tlusty208.f
|
||||
```
|
||||
|
||||
**生产用哪个**:`crates/common/src/embedded.rs:10` `include_bytes!("../../../assets/tlusty_static")` → **当前生产用 assets 版(77721c4c)**。
|
||||
|
||||
**时序**:
|
||||
- 生产 DB 失败记录最晚 8/10 17:56 → 用的是旧版(data/runtime 系)
|
||||
- `assets/` 在 8/11 11:09 才更新为新版
|
||||
- 文档 `tlusty_directrun_experiment` 的实验数据用 `data/runtime` 旧版产生
|
||||
|
||||
---
|
||||
|
||||
## 三、决定性对照实验(证伪"编译选项导致分叉")
|
||||
|
||||
### 3.1 实验设计
|
||||
|
||||
用**当前源码** `tlusty208.f` 重编两版二进制,唯一差异是 `-fno-toplevel-reorder`:
|
||||
|
||||
```bash
|
||||
gfortran -fno-automatic -mcmodel=medium -O3 -o tlusty_O3_reorder tlusty208.f
|
||||
gfortran -fno-automatic -fno-toplevel-reorder -mcmodel=medium -O3 -o tlusty_O3_noreorder tlusty208.f
|
||||
```
|
||||
|
||||
| 二进制 | md5 | 对应 |
|
||||
|---|---|---|
|
||||
| `tlusty_O3_reorder` | `c8b6fa5b...` | 开 toplevel-reorder(旧默认语义)|
|
||||
| `tlusty_O3_noreorder` | `77721c4c...` | 关 toplevel-reorder = **assets/tlusty_static 逐 md5 一致** |
|
||||
|
||||
### 3.2 实验结果(seedprod 输入)
|
||||
|
||||
对 seedprod(t60000_g5.0_he-2_c-4_n-4_o-4 用真实种子 t60000_g5.0_he-2_c-3_n-4_o-4.7):
|
||||
|
||||
| 二进制 | iter1 | iter2 | iter3 | 轨迹 |
|
||||
|---|---|---|---|---|
|
||||
| `tlusty_O3_reorder` | 7 | 115 | 0.203 | `[7, 115, 0.203]` 共3步* |
|
||||
| `tlusty_O3_noreorder` | 7 | 115 | 0.203 | `[7, 115, 0.203]` 共3步* |
|
||||
| 旧生产 data/runtime | 7.0 | 102 | 288 | `[7, 102, 288, 945, 1.12e8, ...]` 共14步雪崩(DB 记录)|
|
||||
|
||||
\* 3步是 timeout 截断,非自然结束;iter1 的 fort.9 **逐字节一致**(见下)
|
||||
|
||||
**两版干净二进制逐字节一致**——`fort.9` iter1 的每一行(50 个深度点 × TEMP/NE/POP/RAD/MAXIMUM)完全相同。这直接证实:**`-fno-toplevel-reorder` 不改变 TLUSTY 的数值行为**。
|
||||
|
||||
### 3.3 旧生产 data/runtime 为何不同
|
||||
|
||||
data/runtime(`[7, 102, 288, ...]`)与当前源码编译版(`[7, 115, 0.203]`)在 iter2 就不同(102 vs 115)。由于两版干净二进制 iter2 都得 115,差异**不可能**来自 toplevel-reorder。最可能的解释:**data/runtime 用了不同的源码状态**(tlusty208.f 在入库前的某个中间版本编译),或编译环境差异。
|
||||
|
||||
> 这与 `synspec_nan_fix_2026_08_11.md §10.4` 的发现一致——该文档也指出"原版备份二进制带有 READ LINE 调试行混叠",最终用"当前源码重编干净对照"才消除了混叠。
|
||||
|
||||
---
|
||||
|
||||
## 四、可信度锚点验证(seed_nc,新二进制 assets)
|
||||
|
||||
用生产二进制 assets/tlusty_static(= tlusty_O3_noreorder)重跑 seed_nc 三变体。**注意**:以下轨迹受 timeout 截断影响,iter 数不等于自然结束;重点看趋势与前几步数值。
|
||||
|
||||
### 4.1 seedprod
|
||||
|
||||
| iter | 新二进制(assets) | 旧生产(DB) |
|
||||
|---|---|---|
|
||||
| 1 | 7.0 | 7.0 |
|
||||
| 2 | 115 | 102 |
|
||||
| 3 | 272 | 288 |
|
||||
|
||||
- iter1 完全一致(7.0);iter2-3 同数量级(百级)
|
||||
- 数据/生产 DB 记录均在 iter1 起就表现为"高位修正",雪崩模式一致
|
||||
|
||||
### 4.2 seed_doubleoff / seed_doubleoff_dpsilg15
|
||||
|
||||
两变体 iter1-3 与文档(旧二进制记录)在同数量级吻合(如 seed_doubleoff iter1=1.28 vs 文档 1.3)。
|
||||
|
||||
### 4.3 锚点小结
|
||||
|
||||
新二进制复现了文档描述的定性模式(seedprod 高位起始、双禁抑制首步修正),**iter1 数值精确吻合、iter2-3 同数量级**。文档的可信度锚点("本机复现生产失败")在新二进制下成立。
|
||||
|
||||
---
|
||||
|
||||
## 五、文档结论在新二进制下的有效性
|
||||
|
||||
| 文档结论 | 有效性 | 依据 |
|
||||
|---|---|---|
|
||||
| 根因 = KANT+ACCEL2 绕过限幅 | ✅ 成立 | 源码逻辑,与二进制无关 |
|
||||
| 可信度锚点(复现生产失败) | ✅ 成立 | iter1 精确吻合,趋势一致 |
|
||||
| 修复组合双禁(ITEK=0+IACC=0)抑制雪崩 | ✅ 成立 | seed_doubleoff 首步修正从 7 降到 1.28 |
|
||||
| baseline 在 t60000_g5.0 雪崩 | ✅ 成立 | 冒烟测试 nc `[9.72,46.6,259,1950,4.85e6]` 精确复现 |
|
||||
| 冷启动极端点本质困难 | ✅ 成立 | 多数点 nl 仍未达 max_relc<0.001 |
|
||||
| 修复组合对所有点有效 | ⚠️ 文档自己已承认有条件性 | 个案需评估 |
|
||||
|
||||
**关键**:文档的全部定性结论(机制、锚点、修复方向)在新二进制下成立。定量轨迹(具体 iter 的 max_relc 数值)受 timeout 截断和源码状态差异影响有偏差,但不改变结论方向。
|
||||
|
||||
---
|
||||
|
||||
## 六、初版报告的修正记录(透明披露)
|
||||
|
||||
### 6.1 初版的错误
|
||||
|
||||
初版验证报告(基于 data/runtime vs assets 的对比)得出两个错误结论:
|
||||
|
||||
| 初版错误结论 | 实际情况 |
|
||||
|---|---|
|
||||
| "iter4+ 系统性分叉由 `-fno-toplevel-reorder` 编译选项导致" | ❌ **证伪**:决定性对照实验显示两版干净二进制逐字节一致 |
|
||||
| "新二进制往往更稳定,baseline 从雪崩变收敛" | ❌ **证伪**:差异来自 data/runtime 的不同源码状态,非编译选项 |
|
||||
|
||||
### 6.2 错误的根源
|
||||
|
||||
1. **混淆了三个变量**:初版把 data/runtime(旧生产)vs assets(新生产)的差异,错误归因到 `-fno-toplevel-reorder`。实际 data/runtime 与当前源码编译版(无论 reorder 与否)都不同——它用了不同的源码状态。
|
||||
2. **未做决定性对照**:初版只对比了"旧二进制 vs 新二进制"(混叠了源码状态 + 编译选项两个变量),没有用"同源码仅差一个 flag"的干净对照。文档 `synspec_nan_fix §10.4` 已做过这种干净对照并得出正确结论,初版未参考。
|
||||
3. **timeout 截断误判**:初版解析轨迹时,把"timeout 截断导致的 iter 数不同"误读为"轨迹分叉"。实际上同一二进制对同一输入,共同跑到的 iter 上逐字节一致。
|
||||
|
||||
### 6.3 修正的方法
|
||||
|
||||
`synspec_nan_fix_2026_08_11.md` 提供了关键线索:`-fno-toplevel-reorder` 是为修复 **SYNSPEC** NaN 而加,对 TLUSTY 是预防性同编译。基于此线索设计了 §三的决定性对照实验(同源码仅差一个 flag),一锤定音地证伪了初版的归因。
|
||||
|
||||
### 6.4 教训
|
||||
|
||||
- **归因前先控制变量**:对比两个二进制时,必须用"同源码仅差目标 flag"的干净对照,否则会把源码状态差异误归为编译选项。
|
||||
- **先读已有文档**:`synspec_nan_fix §10` 已专门分析过"TLUSTY 与 toplevel-reorder 无关",初版未读这份文档就下结论。
|
||||
- **timeout 截断要标注**:被 timeout 截断的轨迹不能直接对比 iter 数,只能对比共同跑到的 iter。
|
||||
|
||||
---
|
||||
|
||||
## 七、后续建议
|
||||
|
||||
1. **文档 `tlusty_directrun_experiment` 无需修正**——其结论在新二进制下成立,且其 §10.4 的"TLUSTY 与 toplevel-reorder 无关"结论经本次决定性实验再次确认。
|
||||
2. **生产可直接用 assets/tlusty_static**——它就是文档建议的 `-O3 -fno-toplevel-reorder` 版,TLUSTY 行为与旧版一致(同源码编译时),SYNSPEC NaN 已修复。
|
||||
3. **data/runtime 旧二进制应废弃**——它的源码状态不明(iter2 偏离当前源码编译版),不应再用于任何对照实验。
|
||||
4. **修复组合 YAML 落地**仍需 30 点 A/B 测试(文档 `tlusty_directrun_experiment §七` 的建议不变),但这是为了标定"哪些点 benefit",不是因为二进制换了。
|
||||
|
||||
---
|
||||
|
||||
## 八、数据产物索引
|
||||
|
||||
```
|
||||
/tmp/cold_test/definitive2_reorder/ # 干净 -O3(开 reorder)seedprod 结果
|
||||
/tmp/cold_test/definitive2_noreorder/ # 干净 -O3 -fno-toplevel-reorder seedprod 结果(= assets)
|
||||
/tmp/tlusty_O3_reorder # 干净 reorder 二进制 (md5 c8b6fa5b)
|
||||
/tmp/tlusty_O3_noreorder # 干净 noreorder 二进制 (md5 77721c4c = assets)
|
||||
/tmp/cold_test/verify_rerun/{cold,seed}/ # 初版重跑产物(28 cold + 3 seed)
|
||||
test/20260811_tlusty_divergence/verify_{rerun.sh,analyze.py}
|
||||
```
|
||||
Reference in New Issue
Block a user