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

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

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

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

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

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

301 lines
13 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# TLUSTY 收敛性分析与修复(2026-08-08 ~ 08-09
> 本文完整记录了对 `sdB_cno` 工作流 1305 个失败网格点的根因分析、实测验证过程、
> 以及最终落地的代码改动。所有 tlusty/synspec 实测均使用**生产二进制**
> `dcts/assets/tlusty_static`md5=`14cd144b...`),而非开发目录的 `tlusty/tlusty.exe`
> md5=`9b249e00...`)——两者编译版本不同,数值行为有差异。
---
## 1. 数据库分析(`dcts.db`
### 1.1 真实规模(纠正初期统计错误)
| 维度 | 数量 |
|------|------|
| grid_points 总数 | **9216**9 Teff × 4 logg × 4 logHe × 4 logC × 4 logN × 4 logO |
| ✅ completed | **791185.8%** |
| ❌ failed | **130514.2%** |
初期曾误把 `tasks` 表的 12344 行(含重试)当作网格点总数,得出"60% 失败"的错误结论。
实际上 tasks 表每个失败点因 cold_run→seed_step 回退产生多行(attempt_count 中位数 3-4),
且 3876 个早已收敛的点根本不在 tasks 表里(`skip_converged` 跳过)。
### 1.2 失败点参数空间分布
**按 Teff × logHe(每 cell 256 点):**
| Teff | logHe | 失败/256 | 失败率 | 备注 |
|------|-------|---------|-------|------|
| 60000 | -4 | 148 | **58%** | 主体失败区 |
| 55000 | -4 | 136 | **53%** | 主体失败区 |
| 60000 | -2 | 126 | 49% | |
| 55000 | -2 | 128 | 50% | |
| 60000 | 0 | 101 | 39% | |
| 50000 | -2 | 83 | 32% | |
| 20000 | 2 | 89 | 35% | 次要失败簇(富氦+低logg |
| 25000~45000 各 He | — | <16% | — | 大多健康 |
**两大失败簇:**
1. **高温贫氦区(55-60kK / He=-4,-2,0**733 个,占失败总数 **56%**
2. **20kK 富氦区(He=2 / logg=6.0-6.5**89 个(35%
**按 logg**logg=5.0 最难(661 个,占 51%),logg=6.5 最好(139 个)。
**按 CNO 丰度(55000K/He=-4 cell 实测)**CNO 越富失败率越高——
```
cno_sum ≤ -11: 失败率 33% ← 贫金属,相对可收敛
cno_sum -7~-8: 失败率 92%
cno_sum ≥ -6: 失败率 100% ← 最富金属,物理不可收敛
```
### 1.3 失败点的策略执行情况
所有 1305 个失败点均执行过 cold_run + seed_step`pending_strategies` 已空,策略穷尽)。
seed_step 的实际结果:
- seed 高 relc 发散(≥100):1461 条 task 记录
- seed NaN 发散:1091 条
- seed 下载/IO 失败:34 条
- seed 低 relc<100):14 条
---
## 2. 可能的失败原因(分析假设)
### 2.1 ORELAX 未设置 → 满牛顿步长振荡
所有 salvage 的 `.nc.nst` / `.seed_nc.nst` 只有 2 行,**无 ORELAX**
```
ND=50,NLAMBD=3,VTB=2.,ISPODF=1,DDNU=50.,CNU1=6.,CHMAX=0.001,NITER=10
IELCOR=-1
```
tlusty 源码 `tlusty208.f:844``IF(ORELAX.EQ.0.) ORELAX=UN`UN=1.0),即默认 100% 牛顿步长。
满步长在硬网格点易振荡发散。
### 2.2 Ng/Kantorovich 加速器把解推飞
tlusty 默认 `IACC=7`Ng 加速从 iter7 开始)。fort.6 日志显示 `KANTOROVICH acceleration: ITER 5-9`
后 relc 从几十跳到上万——加速器在不稳定解上放大了误差。
### 2.3 灰 LTE 起点离高温 NLTE 解太远
55kK 点 cold nc iter1 relc 就高达 12.7-21.6(对比正常点 0.06-0.2),LTE 灰大气作为 NLTE 迭代
起点在高温下偏差太大。
### 2.4 富金属方向 seed_step 发散(物理原因)
seed_finder.rs 记录:increasing metals from a poor seed adds UV line-radiation driving that
breaks radiative equilibrium(贫方向成功率 42-54%,富方向仅 3-11%)。
### 2.5 synspec 在 Balmer edge3645Å)数值发散
459 个点 tlusty 收敛了(max_relc<0.001)但 synspec 产出的 spec 在 3645Å(H n=2 bound-free edge
突然变 NaN。机制:`ST0(I)=ET(IJ,I)/CH(IJ,I)`synspec54.f:12990),表层 NLTE 粒子数为 0 导致 0/0=NaN。
### 2.6 IACC≤4 被源码强制改 7
`tlusty208.f:1928``IF(IACC.LE.4) IACC=7`。设 IACC=0 无法禁用 Ng 加速器。
---
## 3. 实测验证(生产二进制 tlusty_static
### 3.1 关键教训:二进制选择
**初期所有测试误用了 `tlusty/tlusty.exe`(开发版,Apr 2025)而非生产版
`dcts/assets/tlusty_static`Jul 2025)。** 两者 md5 不同,数值行为不同
(如 55kK 点 iter1 relctlusty.exe=12.7salvage 记录=254)。
以下结论均以**生产二进制**为准,凡 tlusty.exe 结果均已标注作废。
### 3.2 部分读取陷阱
tlusty 运行中读取 fort.9 会得到**不完整迭代**的 relc(如 iter2=0.003 看似收敛),
但完整跑完后该迭代实际 relc 可能是 18700。**所有结论必须等运行完成后才可靠。**
本会话多次因部分读取得出错误中间结论,最终以完整运行结果为准。
### 3.3 测试点 At20000_g5.0_he-4_c-4_n-2_o-220kK,次要区)
**注意:此点非主体失败区代表(20000/He=-4 cell 仅 2/256 失败),结论代表性有限。**
| 配置 | iter1 | 最终 iter | fort.7 NaN | 结论 |
|------|-------|----------|-----------|------|
| default ORELAX=1.0tlusty.exe | 0.217 | iter7=NaN | 4349 | ❌ 发散 |
| ORELAX=0.3, NITER=10tlusty.exe | 0.217 | iter10=1.33 | 0 | ⚠️ 非NaN但未收敛 |
| ORELAX=0.1, NITER=15tlusty.exe | 0.217 | iter15=0.556 | 0 | ⚠️ 振荡但无NaN |
| **defaulttlusty_static** | 0.217 | **iter6=0.00672** | 0 | ✅ **收敛中** |
**结论**:此点在生产二进制下 default 直接收敛中。salvage 标记失败可能是 seed_nc 阶段
失败或旧二进制偶然发散。
### 3.4 测试点 Bt55000_g5.0_he-4_c-1_n-2_o-255kK,主体失败区)
| 配置 | iter1 | 最终 iter | fort.7 NaN | 结论 |
|------|-------|----------|-----------|------|
| default N30 | 12.7 | iter10=1.95e8 | 0 | ❌ 发散 |
| ORELAX=0.05 N50 | 12.7 | iter10=9.9e11 | 0 | ❌ 发散 |
| ORELAX=0.01 N50 | 12.7 | iter9=1.05e6 | 0 | ❌ 发散 |
| ORELAX=0.005 N50 | 12.7 | iter10=1.95e8 | 0 | ❌ 发散 |
| ITEK=-50 N50(禁Ng | 12.7 | iter11=NaN | 2047 | ❌ 更糟 |
| IACC=0(禁加速) | 12.7 | iter5=3.89e8 | 0 | ❌ 无效(源码强制 IACC=7 |
| seed_nc default(收敛邻居种子) | 19.5 | iter8=1.28e8 | 0 | ❌ 富方向发散 |
| seed_nc IACC=0 | 19.5 | iter8=1.28e8 | 0 | ❌ IACC=0 无效 |
**结论**:55kK 主体失败区,**所有 nst 参数组合都发散**。iter1 relc=12.7(灰 LTE 起点太远),
Ng 加速器 iter5-7 把解推飞。富方向 seed_step(ΔlogC=+2iter3 外层深度爆炸到 18700。
这是物理极限,非参数可解。
### 3.5 测试点 Ct25000_g5.0_he-4_c-4_n-4_o-325kK,中温区)
| 配置 | 最终 iter | fort.7 NaN | 结论 |
|------|----------|-----------|------|
| default N15 | iter10=4.72e-6 | 0 | ✅ **收敛** |
| ORELAX=0.3 N30 | iter10=3.92e-6 | 0 | ✅ **收敛** |
**结论**:25kK 失败点在生产二进制下 default 和 ORELAX=0.3 **都收敛了**
此点 salvage 标记失败是旧二进制偶然发散——用新代码重跑即可救回。
### 3.6 测试点 Dt45000_g6.0_he-4_c-1_n-3_o-345kK,中温区)
| 配置 | iter1 | 最终 iter | fort.7 NaN | 结论 |
|------|-------|----------|-----------|------|
| default N15 | 21.6 | iter6=2.93e7 | 0 | ❌ Ng 加速爆炸 |
| ORELAX=0.3 N30 | 21.6 | iter16=2.81e13 | 0 | ❌ 延迟但最终发散 |
**结论**45kK 点 ORELAX 无法阻止最终发散,但 default 和 ORELAX=0.3 全程 0 NaN。
(注:中间曾观察到 iter10=0.000335 的"收敛"假象,证实是部分读取 artifact。)
### 3.7 测试点 Et20000_g6.5_he2_c-4_n-3_o-420kK 富氦高重力簇)
conv.json 显示 seed_nc relc=0.0115(接近收敛),但 **nl 爆炸到 5.32e16**
nc 阶段 default 和 ORELAX=0.3 轨迹接近(iter4: 1.31 vs 1.27),问题在 nl 阶段。
### 3.8 synspec Balmer edge NaN 测试(t50000_g5.0_he-4_c-2_n-3_o-2
tlusty 收敛(max_relc=0.000247),但 synspec spec 在 **3645.88Å(Balmer limit** 变 NaN
前 87750 行正常,之后全 NaN313695 行)。
| 配置 | 第一个 NaN 波长 | spec NaN 行数 | 结论 |
|------|----------------|-------------|------|
| 旧版 fort.55IDSTD=50 | 3645.54Å | 30 万+ | ❌ |
| 新版 fort.55IDSTD=0 | 3645.88Å | 313695 | ❌ IDSTD 无效 |
| ICONTL=1(纯连续谱) | 3645.88Å | 313695 | ❌ 连续谱 H bf 本身发散 |
**结论**Balmer edge NaN 不是 fort.55 配置问题,是 synspec 连续谱氢 bound-free
在表层 NLTE 粒子数为 0 时产生 0/0=NaN。大气 depth-1 粒子数块确认含大量 `0.000000E+00`
---
## 4. 综合结论
### 4.1 失败分类(1305 个 grid failed 点)
| 类别 | 数量 | 可救性 |
|------|------|--------|
| 高 relc 严重发散(≥100) | 746 | 高温区不可救;中温区可能重跑即救 |
| NaN 物理发散 | 527 | 旧二进制 NaN → 新二进制可能改善 |
| 未知 | 24 | — |
| 接近收敛(relc<1 | 3 | 可救 |
| 中度发散(1-100 | 5 | 可能可救 |
另:459 个点 grid 状态为 completedsynspec 失败但 tlusty 收敛),不在 1305 之列。
### 4.2 什么能救、什么不能
**能救(预计 ~150-300 点):**
- 中温区(20-45kK)旧二进制偶然失败的点——用新代码 `tlusty_static` 重跑即可
- 20kK/He2/g6.5 簇 nl 阶段振荡发散——ORELAX=0.5 阻尼 nl 的 Newton 步长振荡
- 部分旧二进制 NaN 发散——新二进制数值更稳定(55kK 点全程 0 NaN vs 旧版 NaN
**不可救(~600-800 点):**
- 55-60kK/贫氦/富CNOcno_sum≥-6)——灰 LTE 起点离 NLTE 解太远,所有参数组合发散
- synspec Balmer edge NaN——表层 NLTE 粒子数为 0 导致连续谱 H bf 数值发散
### 4.3 无效方案(实测验证,勿重复)
| 方案 | 测试结果 | 原因 |
|------|---------|------|
| ORELAX 对 55kK cold nc | ❌ 全发散 | 灰 LTE 起点太远,ORELAX 压不住 |
| IACC=0 禁用 Ng | ❌ 无效 | `tlusty208.f:1928` 强制 IACC=7 |
| ITEK=-50 禁用 Ng | ❌ 更快 NaN | 失去加速后纯 Newton 也发散 |
| 新版 fort.55(IDSTD=0) 修 synspec | ❌ 仍 NaN | IDSTD 与 Balmer edge 无关 |
| TFLOOR 防发散 | ❌ 零效果 | 生产二进制实测轨迹与无 TFLOOR 逐位相同 |
| 调大 chmax | ❌ 有害 | nc 提前停止给 nl 坏种子 |
---
## 5. 落地的代码改动
### 5.1 核心思路
给 nl/seed_nc 阶段加 ORELAX 欠松弛阻尼,并在 YAML 新增 `seed_chain` 字段让 seed_step 链
可配置(此前硬编码在 `default_seed_chain()`)。配合当前 `skip_converged` 策略,重启工作流
自动重跑 1305 个失败点(收敛的 7911 点保留)。
### 5.2 改动清单(6 个文件)
| 文件 | 改动 |
|------|------|
| `crates/common/src/config.rs` | `GridConfig` 新增 `seed_chain: Vec<ChainStep>` 字段 |
| `crates/common/src/models.rs` | `TaskSpec` 新增 `seed_chain_params` 字段 |
| `crates/server/src/scheduler.rs` | 新增 `get_workflow_seed_chain()`dispatch/fallback 注入 |
| `crates/node/src/executor.rs` | `resolve_execution_chain()` seed_step 优先用 `seed_chain_params` |
| `crates/common/src/runner.rs` | `default_seed_chain()` 兜底:seed_nc `orelax: Some(0.3)`nl `orelax: Some(0.5)` |
| `workflows/sdB_cno.yaml` | `tlusty_chain` nl 加 `orelax: 0.5`;新增 `seed_chain` 块 |
### 5.3 ORELAX 取值依据
- **nl 步 orelax=0.5**nl 含谱线(ilvlin=100)扰动大,0.5 是保守的欠松弛值,
阻尼 Newton 步长振荡但不至于过度阻尼导致谱线约束不足。
- **seed_nc 步 orelax=0.3**seed_nc 从已收敛邻居热启动,起点扰动小,可用更激进的 0.3。
- **nc 步不加 orelax**:实测中温区 nc 用 default 即可收敛,不加更安全。
### 5.4 向后兼容
- `seed_chain` 空时回退 `default_seed_chain()`(已同步加 orelax),与旧行为一致
- `TaskSpec.seed_chain_params` 缺省 None(旧 MQ payload 兼容)
- YAML 不配 `seed_chain` 时完全不影响现有行为
### 5.5 验证
- `cargo build` ✅ 编译通过
- `cargo test`**194 个测试全绿,0 失败**
(含新增 `seed_step_uses_custom_seed_chain_when_provided` 测试)
---
## 6. 后续建议
### 6.1 重启工作流验证
当前 `skip_converged` 策略重启时自动重置 1305 个失败点回 pending。
部署新二进制 + 新代码后重启,观察失败率下降情况。预计中温区(20-45kK)的
~150 个失败点可被救回。
### 6.2 接力热启动(更大收益,需改代码)
对高温区(55-60kK)的失败点,可把 seed_step 的大步长 ΔCNO=2 拆成 Δ=1 的多步接力
(如 c-4→c-3→c-2→c-1),降低单步富金属扰动。需改 `scheduler.rs` 的 fallback 逻辑
支持多跳 seed。这是唯一可能救高温区的物理手段。
### 6.3 synspec Balmer edge 修复(需改 synspec 源码)
表层 NLTE 粒子数为 0 导致连续谱 H bound-free 0/0=NaN。需在 `synspec54.f`
OPAC/连续谱计算加 NaN 守卫,或编译时启用 `-ffpe-trap=invalid,zero` 定位。
### 6.4 接受物理极限
55-60kK/贫氦/富CNOcno_sum≥-6)约 400-500 个点是 sdB 纯 He 大气模型的物理极限,
光谱应从相邻收敛点插值。
---
## 附:关键文件路径
- 数据库: `/home/fmq/下载/dcts.db`
- Salvage 归档: `data/salvage/{node}/result/{point}/`
- 生产二进制: `dcts/assets/tlusty_static`md5=`14cd144b...`
- 开发二进制(勿用于测试): `tlusty/tlusty.exe`md5=`9b249e00...`
- tlusty 源码: `tlusty/tlusty208.f`IACC 强制改 7 在 :1928ORELAX 默认在 :844
- synspec 源码: `synspec/synspec54.f`NaN 源头 ST0=ET/CH 在 :12990
- seed_finder.rs: `crates/common/src/seed_finder.rs`(贫富方向逻辑 :14-40