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 报错
This commit is contained in:
fmq
2026-08-09 12:09:48 +08:00
parent d16b3d3cdc
commit 43b82b1ae2
45 changed files with 6059 additions and 3184 deletions
+127 -46
View File
@@ -27,14 +27,99 @@ grid:
logo: [-4, -3, -2, -1]
# 共 4*2*2*3*3*3 = 432 个点
# ---- TLUSTY 输入文件全局参数(.5 + nst 的非阶段差异部分)----
# 与 .5 文件 + nst 文件全字段一一对应(见 config.rs 的 Dot5Input / NstInput)。
# gridteff/logg/丰度)和 tlusty_chainlte/ltgray/ilvlin 等)在运行时覆写对应字段。
# 删掉本块 → 走 TlustyInput::default()(与下方声明完全等价)。
tlusty_input:
# .5 文件配置(tlusty208.f:178-450 的 READ(IBUFF,*) 顺序)
dot5:
# 行1 几何(grid 覆写 teff/loggtype=stellar 恒星大气,disk 吸积盘)
geometry: {type: stellar, teff: 35000.0, logg: 5.5}
# 行2 LTE/LTGRAYchain 覆写)
lte: false
ltgray: false
# 行3 nst 文件名(空串=不读 nst)
finstd: "nst"
# 行4 NFREAD 连续频率网格点数(>0 从预设表读)
nfread: 2000
# 行5 IABSET 丰度集选择(不列出=默认 01=abun1 表,2=abun0 表)
# iabset: null
# 行5+ atoms 块(grid 覆写 He/C/N/O 的 abn;列出哪些行就渲染哪些元素)
# mode: 2=显式统计平衡(H/He/CNO), 0=不参与(Li/Be/B); abn=grid 注入或 "0." 太阳默认
atoms:
- {iat: 1, mode: 2, abn: "0.", modpf: 0} # H
- {iat: 2, mode: 2, modpf: 0} # He (abn 由 grid)
- {iat: 3, mode: 0, abn: "0.", modpf: 0} # Li
- {iat: 4, mode: 0, abn: "0.", modpf: 0} # Be
- {iat: 5, mode: 0, abn: "0.", modpf: 0} # B
- {iat: 6, mode: 2, modpf: 0} # C (abn 由 grid)
- {iat: 7, mode: 2, modpf: 0} # N (abn 由 grid)
- {iat: 8, mode: 2, modpf: 0} # O (abn 由 grid)
# ions 块(chain 覆写每行 ilvlin;列出哪些行就渲染哪些元素)
# 显式列出完整 22 行表(H/He/C/N/O)。如需去掉某元素,删对应 iat 行即可;
# 如需加 Fe 等,追加对应行。nonstd≠0 时配 nonstd_extra 附加行(ODF/NONSTD)。
# 注:ilvlin 由 tlusty_chain 的各阶段注入(nlevs==1 裸核强制 0),此处不列。
ions:
# H (iat=1)
- {iat: 1, iz: 0, nlevs: 9, nonstd: 0, typion: " H 1", filei: "data/h1.dat"}
- {iat: 1, iz: 1, nlevs: 1, nonstd: 0, typion: " H 2", filei: " "}
# He (iat=2)
- {iat: 2, iz: 0, nlevs: 14, nonstd: 0, typion: "He 1", filei: "data/he1.dat"}
- {iat: 2, iz: 1, nlevs: 14, nonstd: 0, typion: "He 2", filei: "data/he2.dat"}
- {iat: 2, iz: 2, nlevs: 1, nonstd: 0, typion: "He 3", filei: " "}
# C (iat=6)
- {iat: 6, iz: 0, nlevs: 40, nonstd: 0, typion: " C 1", filei: "data/c1.dat"}
- {iat: 6, iz: 1, nlevs: 22, nonstd: 0, typion: " C 2", filei: "data/c2.dat"}
- {iat: 6, iz: 2, nlevs: 46, nonstd: 0, typion: " C 3", filei: "data/c3_34+12lev.dat"}
- {iat: 6, iz: 3, nlevs: 25, nonstd: 0, typion: " C 4", filei: "data/c4.dat"}
- {iat: 6, iz: 4, nlevs: 1, nonstd: 0, typion: " C 5", filei: " "}
# N (iat=7)
- {iat: 7, iz: 0, nlevs: 34, nonstd: 0, typion: " N 1", filei: "data/n1.dat"}
- {iat: 7, iz: 1, nlevs: 42, nonstd: 0, typion: " N 2", filei: "data/n2_32+10lev.dat"}
- {iat: 7, iz: 2, nlevs: 32, nonstd: 0, typion: " N 3", filei: "data/n3.dat"}
- {iat: 7, iz: 3, nlevs: 48, nonstd: 0, typion: " N 4", filei: "data/n4_34+14lev.dat"}
- {iat: 7, iz: 4, nlevs: 16, nonstd: 0, typion: " N 5", filei: "data/n5.dat"}
- {iat: 7, iz: 5, nlevs: 1, nonstd: 0, typion: " N 6", filei: " "}
# O (iat=8)
- {iat: 8, iz: 0, nlevs: 33, nonstd: 0, typion: " O 1", filei: "data/o1_23+10lev.dat"}
- {iat: 8, iz: 1, nlevs: 48, nonstd: 0, typion: " O 2", filei: "data/o2_36+12lev.dat"}
- {iat: 8, iz: 2, nlevs: 41, nonstd: 0, typion: " O 3", filei: "data/o3_28+13lev.dat"}
- {iat: 8, iz: 3, nlevs: 39, nonstd: 0, typion: " O 4", filei: "data/o4.dat"}
- {iat: 8, iz: 4, nlevs: 6, nonstd: 0, typion: " O 5", filei: "data/o5.dat"}
- {iat: 8, iz: 5, nlevs: 1, nonstd: 0, typion: " O 6", filei: " "}
# CHANGE 块(ICHANG<0 时每能级行;不列出 = 无)
change: null
# nst 高频关键字(236 个中物理相关的,按用途分组)
# 默认值为 sdB 调优值(非 TLUSTY 原始默认)。ChainStep 的 niter/chmax 等阶段级覆盖。
nst:
depth: {nd: 50, nmu: 3, nlambd: 3, isplin: 0}
frequency: {ispodf: 1, ddnu: 50.0, cnu1: 6.0, cnu2: 3.0, nftail: 21, dftail: 0.25,
frcmax: 0.0, frcmin: 0.0, frlmax: 0.0, frlmin: 0.0}
physics: {vtb: 2.0, hmix0: -1.0, mltype: 1, ipturb: 1, ielcor: -1, ibfint: 1, ihecor: 0}
radiative_transfer: {irte: 0, ibc: 3, ifryb: 0, ioptab: 0, ifmol: 0}
nlte: {ifali: 5, ifpopr: 4, jali: 1}
irradiation: {trad: 0.0, wdil: 0.0}
temperature: {tfloor: 8000.0, tdisk: 0.0, tmolim: 9000.0}
# nst 冷门关键字逃逸口(诊断打印 IPR*/内部开关 IOP* 等 ~200 个)
# 示例(当前未启用):
# nst_extra_keys:
# - [CUTBAL, "0.3"]
# - [NDGREY, "20"]
nst_extra_keys: []
# ---- 收敛链 ----
# 参与计算的元素集 / 电离态 **不再由链配置(metals)控制**,而是在上方
# tlusty_input.dot5.atoms8 元素)与 dot5.ions(22 行)中显式声明;此处每个
# step 只控制阶段参数(lte/ltgray/ilvlin/niter/chmax 等)。
# 已验证的三步配方 (tests/sdB_spectra/GUIDE.md 实测, Teff=35000 logg=5.5):
# LTE grey (T T, NITER=0) -> NLTE 连续谱 (nc, ilvlin=0) -> NLTE 谱线 (nl, ilvlin=100)
# nc 步是关键:在不考虑谱线扰动下收敛 NLTE 电离平衡,给 nl 一个稳定种子。
# 跳过 ncgrey -> 完整 NLTE)会发散。
#
# 各 step 的隐式默认值在此显式写出(与代码兜底等价,见下):
# - metals: cno —— C/N/O 参与 NLTErunner.rs 兜底默认;缺省时 .5 输入也按此处理)
# - chmax: 0.001 —— 收敛阈值(runner.rs 兜底默认;缺省时与原兜底完全一致)
# - itek / ichang / idlte / iacc / orelax 留空 → 用 tlusty 内部默认(未结构化为可写值)。
# 注意:设 CHMAX=0.1 会让 nc 提前停止,给 nl 一个坏种子导致发散——勿调大。
@@ -46,56 +131,44 @@ grid:
# 物理上 nc(纯连续谱)缺少谱线约束,外层温度永不真正收敛——追求高 NITER
# 没意义。nl(含谱线)会自修正到正确解(流量差异 <3e-12)。
tlusty_chain:
- {label: lte, lte: T, ltgray: T, ilvlin: 0, require_converged: false, niter: 0, metals: cno, chmax: 0.001}
- {label: nc, lte: F, ltgray: F, ilvlin: 0, require_converged: false, niter: 10, metals: cno, chmax: 0.001}
- {label: nl, lte: F, ltgray: F, ilvlin: 100, require_converged: true, niter: 100, metals: cno, chmax: 0.001}
- {label: lte, lte: T, ltgray: T, ilvlin: 0, require_converged: false, niter: 0, chmax: 0.001}
- {label: nc, lte: F, ltgray: F, ilvlin: 0, require_converged: false, niter: 10, chmax: 0.001}
# nl 步加 orelax=0.5:谱线(ilvlin=100)扰动大,欠松弛阻尼 Newton 步长振荡,
# 避免中温区(20-45kK) nl 阶段 relc 振荡发散(实测验证)。
- {label: nl, lte: F, ltgray: F, ilvlin: 100, require_converged: true, niter: 100, chmax: 0.001, orelax: 0.5}
# ---- TLUSTY 输入文件全局参数(.5 + nst 的非阶段差异部分)----
# 这些参数不随收敛阶段变化。此处显式声明当前生效值,作为可配置项的文档。
# 如需调整(如降 nfread 加速、换 ions 能级文件、暴露 nst 高级关键字),改本块即可
# 删掉本块 → 走代码内硬编码默认(与下方声明完全等价)。
tlusty_input:
# .5 frequencies 块:连续频率网格点数。>0 读预设表(高精度、慢)。
# 2000 是高精度档(官方 hhe 样本用 50);降到 1000 可加速但牺牲精度。
nfread: 2000
# atoms 块每元素的 mode2=显式统计平衡(H/He/CNO),0=不参与(Li/Be/B)。
# 缺省元素走默认。abn(丰度)由 grid 的 loghe/logc/logn/logo 计算,不在此配。
atoms:
H: {mode: 2}
He: {mode: 2}
C: {mode: 2}
N: {mode: 2}
O: {mode: 2}
# ions 能级数据表(空 = 用代码默认的 H/He/C/N/O 共 23 行标准表)。
# 如需换能级文件或调整 nlevs,列出完整表(完全替换默认)。
ions: []
# nst 额外关键字(逃逸口):自由传入任意 KEY=VALUE,追加到 nst 末尾。
# 用于暴露未结构化的 nst 开关(FRCMAX/CUTBAL/TAU/NDGREY 等,见 tlusty 手册)。
# 示例(当前未启用):
# nst_extra_keys:
# - [FRCMAX, "0.01"]
# - [CUTBAL, "0.3"]
nst_extra_keys: []
# 种子热启动链(seed_step 策略用,cold_run 失败后回退)。
# seed_nc 从已收敛邻居热启动,起点扰动小,用更激进的 orelax=0.3;
# nl 与 cold_run 链一致 orelax=0.5。空时 executor 回退 default_seed_chain()(代码内置同值)
seed_chain:
- {label: seed_nc, lte: F, ltgray: F, ilvlin: 0, require_converged: false, niter: 20, chmax: 0.001, orelax: 0.3}
- {label: nl, lte: F, ltgray: F, ilvlin: 100, require_converged: true, niter: 100, chmax: 0.001, orelax: 0.5}
# ---- 种子步进回退(旧字段,保留兼容;新配置见下方 tlusty_stage 块)----
# 冷启动失败时,自动用已收敛的邻居模型作种子,用 LTGRAY=F 热启动重试。
# 这是高温 He-poor / 富金属区收敛的关键(见 EXPERIENCE.md §5Y)。
# 失败的冷启动结果会备份到 <model>.coldfail/ 目录。
seed_step_fallback: true
# ---- 执行参数 ----
timeout_sec: 7200 # 单模型墙钟时间上限(120分钟)
# ---- 光谱合成 SYNSPEC 输入参数(fort.55 数值参数;与 tlusty_input 对称)----
# ---- 光谱合成 SYNSPEC 输入参数(fort.55 控制卡,9 行子结构体)----
# 启用开关见下方 synspec_stage 块。
#
# fort.55 字段与 synspec54.f 的 READ(55,*) 语句逐行一一对应,字段名严格使用
# SYNSPEC 源码变量名。各行含义见 crates/common/src/config.rs 的 Fort55Line* 文档注释。
# 仅列出与默认值不同的字段;未列出的字段走 SynspecInput::default()(见 config.rs)。
#
# 重要修复:
# - line1.idstd 从旧实现的 50(错位 bug,强制最深层)修正为 0(自动取 2*ND/3≈33,
# τ≈1 参考层)。此变更影响光谱线强归一化,修正后所有光谱需重算(见 §5)。
# - 新增 line_mol(分子表行):SYNSPEC 在 IMODE∈[-3,1] 时会额外读这一行,旧实现漏生成
# 导致 VTB 行被吞、vturb 被错误覆盖。现默认 nmlist=0(无分子线)。
# - line_vtb 默认 -1.0(不覆盖大气 vturb=2 km/s);如需自定义湍流速度设非负值。
synspec_input:
wstart: 3000.0 # 光谱波长起始点 (Å)
wend: 7000.0 # 光谱波长终止点 (Å)
imode: 0
idrv: 50
ifreq: 1
rel_cutoff: 0.0001
abs_cutoff: 0.01
line1: {imode: 0, idstd: 0, iprin: 1} # 行1: IMODE, IDSTD(=0 自动 2ND/3), IPRIN
# line2-line5 走默认(INMOD=1 TLUSTY 模型 / IOPHLI 被 SYNSPEC 强制 0 / IFREQ=1 INLTE=1 / Stark 表全 0
line6:
alam0: 3000.0 # 光谱波长起始点 (Å)
alast: 7000.0 # 光谱波长终止点 (Å)
cutoff0: 10 # 谱线截止距离(推荐 5-10
cutoffs: 0 # 连续谱间距(0→内部取 3
relop: 0.0001 # 线心/连续不透明度最小比
space: 0.01 # 相邻频率点最大间距 (Å)
# line_mol (nmlist=0 无分子表) / line_vtb (vtb=-1 不覆盖大气 vturb) / line_ang (NMU0=0 不算比强度) 走默认
# ====================================================================
# 阶段独立配置(见 docs/task_engine_decoupling_design.md §2-§3
@@ -113,8 +186,16 @@ tlusty_stage:
policy: skip_converged
strategies: [cold_run, seed_step]
# ---- 种子步进回退(旧字段,保留兼容;新配置见下方 tlusty_stage 块)----
# 冷启动失败时,自动用已收敛的邻居模型作种子,用 LTGRAY=F 热启动重试。
# 这是高温 He-poor / 富金属区收敛的关键(见 EXPERIENCE.md §5Y)。
# 失败的冷启动结果会备份到 <model>.coldfail/ 目录。
seed_step_fallback: true
synspec_stage:
enabled: true
policy: skip_converged
strategies: [standard]
# ---- 执行参数 ----
timeout_sec: 7200 # 单模型墙钟时间上限(120分钟)