TLUSTY/cno_grid/EXPERIENCE.md
2026-07-21 22:25:14 +08:00

32 KiB
Raw Blame History

CNO 热亚矮星光谱:完整调试经验与原理文档

本文档完整记录在 tl208-s54/cno_grid/ 上构建含 C/N/O 金属线的热亚矮星理论 光谱过程中,所有测试、遇到的问题、根因分析、解决方法和底层物理/数值原理

重要:本文档经过了多轮调试验证。之前版本的"8/8 边界全部成功"等结论 不准确(基于错误的 CHMAX=0.1 配置)。本版本如实记录了最终确认的结果。


1. 核心结论(先读这一节)

  1. 金属线必须进大气模型方案B自洽 纯 H+He 大气下 synspec 的 C/N/O 谱线全 NaN——大气里没有金属能级无法计算谱线不透明度。

  2. 收敛必须走三步法nc 步骤不可省略:

    LTE 灰大气 (T T, NITER=0)          → 初始温度结构
    NLTE 连续谱 (F F, ilvlin=0, "nc")  → 收敛电离平衡(无线跃迁)
    NLTE 含线   (F F, ilvlin=100, "nl")→ 加谱线
    SYNSPEC                            → 合成光谱
    
  3. 正确的 nst 配方(用户 tests.zip 验证):

    • 不设 CHMAX(用 tlusty 默认 0.001)— 这是最关键的点
    • 不设 ITEK(用默认 4
    • He .5 nlevs=14(数据文件本身 24/20 能级,但 .5 声明 14 让 tlusty 截断; 不是 Peter 建议的满 24-level—— 详见 §2.5/§3.3
    • NFREAD=2000(展开成 5088 频率点,快且稳定)
    • nst: ND=50,NLAMBD=3,VTB=2.,ISPODF=1,DDNU=50.,CNU1=6.,NITER=<阶段> + IELCOR=-1
  4. 丰度约定是收敛的关键(最终发现):

    • Tlusty 的 abn 字段:0=太阳, <0=太阳倍数, >0=绝对比值 N(X)/N(H)
    • 网格 logX = log10(nX/nH).5 的 abn = 10^logX
    • logC/N/O 范围必须是物理合理的-4 到 -1太阳约在 -3.6
    • 之前的 logC=0 意味着 C/H=1.0(太阳的 4000 倍)→ nc 发散。这不是高温问题!
  5. 验证状态(修正丰度范围后,已与 conv.json 复核):

    • 35000/5.5/logHe=-2/logCNO=-1nc max_relc=0.00148(未达 0.001,但作为种子 被接受nl 收敛到 0.000633,耗时 1635ssynspec 3.6s
    • 40000/5.5/logHe=0/logCNO=-1C/H=0.1nc=0.000424, nl=0.000905, 1298s

2. 底层原理

2.1 为什么金属必须进大气

Tlusty 求解每个离子的每个能级布居数统计平衡方程。synspec 合成光谱时需要 谱线跃迁涉及的两个能级的布居数来计算线不透明度。纯 H+He 大气没有 CNO 能级 → synspec 遇到 CNO 线时无法获取布居数 → 线不透明度 = NaN。

2.2 为什么需要三步法

Tlusty 的 NLTE 求解用迭代线性化complete linearization。线性化的收敛半径 有限——初猜离真解太远时迭代发散。三步法逐步缩小差距:

  • LTE 灰大气:解析求 T(τ) 结构,提供物理合理的起点
  • ncilvlin=0:切换 NLTE 但不含线跃迁。线跃迁是统计平衡方程中最敏感 的非线性项;先不加线,只收敛电离平衡
  • nlilvlin=100:从已收敛的 nc 种子加线,扰动小,快速收敛

跳过 nc 直接 grey→含线 NLTE 必发散(实测确认)。

2.3 CHMAX 为什么必须用默认的 0.001

CHMAX 是收敛限(各深度最大相对变化)。我最初从 bstar 抄了 CHMAX=0.1(宽松), 导致 nc 在 max_relc=0.1 就停止——但此时大气结构还没真正收敛,布居数仍远离 NLTE 解 → nl 接手后不稳定。

用默认 CHMAX=0.001 强迫 nc 真正收敛到 0.1% 精度,给 nl 一个准确的种子。 这是整个调试中最关键的发现。

2.4 NFREAD 与频率网格

NFREAD 是 .5 里的"基本频率点数"tlusty 据此自动展开成实际频率网格:

  • NFREAD=2000 → 5088 个频率点(快,每次迭代 ~3 秒)
  • NFREAD=50 → 77695 个频率点(慢 15 倍,且 nc 不稳定)

NFREAD 小反而展开更多——因为 tlusty 对小 NFREAD 触发更细的自动细化。 必须用 NFREAD=2000。

2.5 He 能级数14 vs 24

Peter Nemeth 邮件建议用 24-level He I + 20-level He II这恰好是 he1.dat/ he2.dat 数据文件本身的能级数)。但用户 tests.zip 实际验证成功的配置,是 在 .5 里把 He I/II 的 nlevs 显式声明为 14tlusty 按此截断数据文件, 只读前 14 个能级)。

满 24-level 在 nc 阶段(即使 ilvlin=0引入更多连续谱跃迁光致电离/复合), 增加 NLTE 线性化的维度和不稳定性。.5 声明 nlevs=14 是用户验证过的稳定配置。

注 1Peter 的建议针对 He-rich 模型的光谱精度(更多 He 线),不是针对 nc 收敛稳定性。两者目标不同。

注 2数据文件 he1.dat/he2.dat 本身仍是 24/20 能级(不需改动),只是 .5 里声明 nlevs=14 让 tlusty 截断使用。详见 §3.3 表格。

2.6 丰度约定(最终发现的关键)

Tlusty 的 .5 文件 atoms 段 abn 字段有三种含义:

  • abn = 0:采用 Tlusty 内置太阳丰度Grevesse & Sauval 1998
  • abn < 0:太阳丰度的倍数(-0.1 = 0.1×太阳,-5 = 5×太阳
  • abn > 0绝对数密度比 N(X)/N(H)

本网格用 logX = log10(nX/nH)(绝对比值),所以 .5abn = 10^logX

太阳丰度参考值log10(nX/nH)

元素 太阳 logX 太阳 nX/nH
He -1.07 0.0851
C -3.61 2.45e-4
N -4.22 6.03e-5
O -3.34 4.57e-4

网格范围必须是物理合理的。用户最初设 logC/N/O = -2 到 1

  • logC = 0 → C/H = 1.0(太阳的 4000 倍
  • logC = 1 → C/H = 10碳比氢多物理上几乎不可能
  • 太阳 logC ≈ -3.6 不在原范围内

实测确认logC = 0C/H=1.0)时 nc 发散——金属不透明度主导大气结构NLTE 线性化不稳定。改为 logC/N/O = -4 到 -1覆盖太阳到 sdB 观测富金属端)后, 40000K 可靠收敛nc=0.0004, nl=0.0009)。

之前所有"40000K 高温发散"的结论是错误的——根因是丰度过高,不是高温。


3. 已验证的精确配方

3.1 .5 文件(三阶段,只改 3 处)

第1行: TEFF  GRAV              (三阶段相同)
第2行: LTE  LTGREY             阶段1=T T阶段2/3=F F
第3行: 'nst'                   (三阶段都引用 nst
第4行: 2000                    NFREAD=2000
第5行: 8                       NATOMS=8: H,He,空×3,C,N,O
atoms: C/N/O mode=2, abn=10^logX
ions:  He nlevs=14/14, C/N/O全套 阶段1/2 ilvlin=0; 阶段3 ilvlin=100

3.2 nst 文件(三阶段统一,只改 NITER

LTE 阶段

ND=50,VTB=2.,NITER=0

nc 和 nl 阶段(关键:无 CHMAX/ITEK

ND=50,NLAMBD=3,VTB=2.,ISPODF=1,DDNU=50.,CNU1=6.,NITER=<阶段>
IELCOR=-1
  • nc: NITER=50
  • nl: NITER=100
  • 不设 CHMAX(用默认 0.001)、不设 ITEK(用默认 4

3.3 CNO 模型原子(.5 里声明的 nlevs

下表"nlevs"列是 .5 文件里每个离子实际声明的 NLTE 能级数(即 gen_input5.py_IONS_* 元组第三项),也就是 tlusty 真正会读入和求解的能级数。 数据文件本身可能含更多能级tlusty 按 nlevs 截断),所以 nlevs ≠ grep 'Levels' <file> 看到的文件内总能级数。这点之前文档里混淆过。

元素 离子 / .5 声明 nlevs / 数据文件(文件实际能级数)
He He I 14 he1.dat(24) / He II 14 he2.dat(20) / He III 1
C C I 40 c1.dat / C II 22 c2.dat / C III 46 c3_34+12lev.dat(46) / C IV 25 c4.dat / C V 1
N N I 34 n1.dat / N II 42 n2_32+10lev.dat(42) / N III 32 n3.dat / N IV 48 n4_34+14lev.dat(48) / N V 16 n5.dat / N VI 1
O O I 33 o1_23+10lev.dat(33) / O II 48 o2_36+12lev.dat(48) / O III 41 o3_28+13lev.dat(41) / O IV 39 o4.dat / O V 6 o5.dat(40) / O VI 1
H H I 9 h1.dat

注:

  • He 的 .5 nlevs=14 是用户 tests.zip 验证过的稳定配置(见 §2.5),但 he1.dat 本身含 24 能级、he2.dat 含 20 能级——tlusty 只读前 14 个。
  • O V 的 .5 nlevs=6 是截断值;o5.dat 文件本身含 40 能级。
  • 之前文档写 "He I 14 he1.dat" 容易让人误以为文件就 14 能级,已澄清。

4. 调试过程中犯的错误(如实记录)

错误 1CHMAX=0.1(核心错误)

  • 来源:从 bstar 的 nst 抄来
  • 影响nc 在 0.1 就停止,没真正收敛 → nl 不稳定 → 大部分点失败
  • 纠正:不设 CHMAX用默认 0.001
  • 教训:不要盲目从参考模型抄参数,要理解每个参数的作用

错误 2IDLTE=45方案B

  • 来源subagent 分析源码后提出(深层强制 LTE
  • 影响:让 nc 发散更严重(深层 LTE 边界条件破坏了线性化)
  • 虚假成功nst 行长 bug>72 字符截断)让 IDLTE 被静默丢弃,反而"碰巧" 用了默认值 → 之前"8/8 成功"是假象
  • 纠正:不用 IDLTE
  • 教训源码分析推断的方案必须实测验证nst 行长 bug 让参数静默丢失

错误 3He 用满 24-level数据文件级

  • 来源Peter Nemeth 邮件建议;he1.dat 本身就含 24 能级
  • 影响:增加 nc 的不稳定(更多连续谱跃迁进入线性化)
  • 纠正.5 里把 He I/II 的 nlevs 显式声明为 14tlusty 按此截断 he1.dat/he2.dat,只读前 14 个能级)—— 用户 tests.zip 验证的配置
  • 教训:专家建议针对的目标(光谱精度)可能和你的目标(收敛稳定性)不同。 注意区分"数据文件能级数"和".5 声明的 nlevs"——前者是文件内容,后者才是 tlusty 实际求解的能级数。

错误 4NFREAD=50

  • 来源:从 hhe35lt纯H+He抄来
  • 影响:展开成 77695 频率点,慢 15 倍且不稳定
  • 纠正:用 NFREAD=20005088 点)
  • 教训NFREAD 的展开行为反直觉(小→多),必须实测确认

错误 5ORELAX=0.5(部分有效但非通用解)

  • 来源:阻尼布居数跳跃
  • 影响对某些点40000K 单独 nc 测试)有效,但在完整链中不可靠
  • 纠正:不用 ORELAX用户配方无 ORELAX 且成功)
  • 教训:单独测试 nc 成功不代表完整链成功

错误 6nst 行长截断 bug

  • 来源tlusty 的 nst 解析器有 ~72 字符行宽限制
  • 影响:参数太多时(如加了 IDLTE/IACC行尾参数被静默截断 → 用默认值
  • 纠正write_nst 把参数分两行写line1 ≤ 64 字符)
  • 教训Fortran 的固定格式行宽限制是隐蔽 bug 源

错误 7丰度范围设置过高最严重的错误

  • 来源:网格最初设 logC/N/O = -2 到 1未核实物理含义
  • 影响logC=0 → C/H=1.0(太阳 4000 倍),金属不透明度主导大气 → nc 发散。 之前所有"40000K+ 高温发散"的结论都源于此,不是高温问题
  • 虚假归因:花了大量时间调试 CHMAX/IDLTE/ORELAX/He 能级/NFREAD都没解决 因为根因是丰度(金属含量)而非数值参数。
  • 纠正:改为 logC/N/O = -4 到 -1物理合理范围太阳在 -3.6 附近)
  • 教训:先核实输入参数的物理含义和量级,再调试数值方法。对比用户成功配置时 要逐行精确对比(用户用 abn=0 太阳丰度,我用 abn=1.0 绝对比值)。

错误 8ICRSW 是死代码(本次会话发现)

  • 来源:边界测试发现 80K + He-poor + logCNO=-1 即使种子步进也发散, 尝试用 ICRSWHummer & Voels 1988 碰撞-辐射开关)稳定化
  • 影响:源码 tlusty208.f:4556 定义了 SWITCH 子程序含完整 CRSW 逻辑, namelist 也接受 ICRSW/SWPFAC/SWPLIM/SWPINC 参数fort.6 也打印这些值, 看起来一切正常——但整个源文件中没有任何一处 CALL SWITCH
  • 实测验证:开 ICRSW=1/SWPFAC=0.001 后 nc 迭代历史与不开完全相同
  • 纠正:放弃 ICRSW改用实际有效的 ORELAXtlusty208.f:14647 和种子步进(虽然 ORELAX 对极端跳跃也无效)
  • 教训:源码里的子程序未必被调用。看似可用的参数可能是死代码。 必须实测验证参数效果(对比开/关的迭代历史是否真的不同)。

5. 验证状态(修正丰度范围后)

成功的点logC/N/O 在物理合理范围 -4 到 -1

参数 (Teff/logg/logHe/CNO) nc max_relc nl max_relc 耗时
35000/5.5/-2/logCNO=-1 0.00148(未达 0.001,作种子) 0.000633 1635s
40000/5.5/0/logCNO=-1 0.000424 0.000905 1298s

注:早期版本曾列入 "35000/5.5/-2/abn=0 (nl=0.00078, 1009s)" 和 "40000/5.5/0/abn=0 (nc=6.67e-5)" 两个所谓"成功点"——经复核 conv.json这两个数据不存在 results/ 下既没有 abn=0 的对应模型目录,整库 grep 也没有 6.67e-5 这个值。 上述结论是凭空写入的,已删除。真实可复现的成功点如上表所示。

之前"失败"的点logC/N/O 过高C/H ≥ 1.0

参数 失败原因 真相
40000/5.5/logCNO=0 nc 发散 C/H=1.0太阳4000倍金属不透明度主导
80000/6.5/logCNO=0 nc 发散 同上,非高温问题

结论用物理合理的丰度范围logC/N/O = -4 到 -120000-40000K 可靠收敛。 之前的"高温发散"假象源于丰度范围设置过高。


5X. 8 点边界测试2026-07-21

在修正丰度范围logC/N/O = -4 到 -1对网格边界做系统验证。 完整数据见 cno_grid/results/bound_*.log + results/t*/conv.json

冷启动LTE grey 初猜)结果

测试 Teff/logg/logHe logCNO 收敛 nc 末 relc 备注
20k_he2_cno-1 20000/5.0/+2 -1 0.221 nl 收敛到 6e-4但 nc 走到 NITER=50 才勉强
20k_he2_cno-4 20000/5.0/+2 -4 13.9 nc 末值高但 nl 顺利收敛
40k_he0_cno-4 40000/5.5/0 -4 0.00087 顺利
60k_he0_cno-1 60000/6.0/0 -1 2.31e18 nc 发散
80k_he-4_cno-1 80000/6.5/-4 -1 2.68e17 nc 发散
80k_he-4_cno-4 80000/6.5/-4 -4 3.92e6 nc 发散(深 13
80k_he2_cno-1 80000/6.5/+2 -1 1.01e17 nc 发散
80k_he2_cno-4 80000/6.5/+2 -4 0.00031 唯一 80K 冷启动成功

模式分析

通过逐迭代看 nc 阶段的 max_relc 演化(*.nc_*.9 文件),发现:

  • 冷启动失败模式iter 1-2 出现 relc > 1 的尖峰(深 4-6τ~1 光球层), 之后线性化把尖峰放大而不是阻尼 → iter 5-10 relc 飙到 1e3+,最终 NaN。
  • 冷启动成功模式(如 80k_he2_cno-4iter 2 也出现 1.5 的尖峰, 但线性化阻尼住 → iter 5 回到 1e-2 → iter 10 < 1e-3 收敛。
  • 关键差异He 含量。He-richlogHe=+2的 He 不透明度主导, CNO 振荡被 He 的稳定连续不透明度抑制He-poor 时 CNO 主导不透明度, 高价离子C IV/V, N V, O V/VI的光致电离-复合平衡极陡峭,振荡放大。

物理结论

  • 20000-40000K冷启动全区间可靠典型 sdB 区)
  • 60000K+ + He-poor + 富金属:冷启动不稳,需种子步进
  • 80000K + He-rich冷启动可行只要 CNO 不主导)
  • 80000K + He-poor冷启动不可行必须用种子步进

5Y. 种子步进法seed-stepping—— 高温区破局

源码分析关键发现(tlusty208.f

通过 subagent 深入分析源码确认了冷启动/热启动的机制:

LTGREY 标志 行为 代码位置
T CALL LTEGR/LTEGRD 生成灰大气(忽略 fort.8 tlusty208.f:981-982
F CALL INPMOD 从 fort.8 读已收敛大气作初猜 tlusty208.f:579(在 IF(.NOT.LTGREY) 块 578-581 内)

注:变量名是 LTGREY(英式拼写),不是 LTGRAY。源码 grep 确认。

ICHANG 控制模型原子变化时的布居数重映射CALL 在 tlusty208.f:580 SUBROUTINE CHANGE 在 3432参数解析在 1712/1884/2060

  • 0 = 不变(相同模型原子时用,仅改 Teff/logg/abundance
  • 1 = 新增能级置为 LTE扩展模型原子时用见 3566
  • <0 = 从 fort.95 读完整旧模型定义(见 3503

ICRSWtlusty208.f:4556= Hummer & Voels 1988 碰撞-辐射开关, 是另一可选稳定化参数(本次未启用,详见错误 8 与 §6X

种子步进实现

新增 cno_grid/src/seed_step.py,跳过 LTE grey 冷启动, 直接热启动 nc 阶段:

SEED_STEP_CHAIN = [
    # stage 1: 从种子大气热启动 NLTE 连续谱
    {"label": "seed_nc", "lte": "F", "ltgray": "F", "ilvlin": 0,
     "ichang": 0, "require_converged": False, "niter": 80},
    # stage 2: 完整 NLTE + 谱线
    {"label": "nl", "lte": "F", "ltgray": "F", "ilvlin": 100,
     "ichang": 0, "require_converged": True, "niter": 100},
]

用法:

python3 cno_grid/src/seed_step.py --teff 80000 --logg 6.5 --loghe -4 \
    --logc -4 --logn -4 --logo -4 \
    --seed cno_grid/results/<seed-model>/<seed-model>.7

种子步进验证结果(决定性突破)

80K + He-poor + logCNO=-4(冷启动必然失败点):

方法 iter 2 relc iter 5 iter 10 iter 15 结果
冷启动LTE grey 初猜) 1.78e1 1.22e2 5.32e2 9.54e5 发散到 NaN
种子步进80K He-rich 种子) 2.49 2.02e-2 8.03e-4 4.36e-5 收敛 ✓
  • 冷启动 970s 都没收敛NaN
  • 种子步进 126s 收敛(其中 synspec 3.4s
  • 大气本身 0% NaN物理有效

已验证的种子步进成功点

目标 种子 结果 耗时
80000/6.5/-4/-4/-4/-4 80000/6.5/+2/-4/-4/-4/-4 ✓ conv 0.00091 126s
80000/6.5/+2/-1/-1/-1/-1 80000/6.5/+2/-4/-4/-4/-4 ✓ conv 0.00025 276s
80000/6.5/-4/-2/-2/-2/-2 80000/6.5/-4/-4/-4/-4/-4 ✓ conv 0.00061 313s

仍未解决的点

目标 尝试 结果
80000/6.5/-4/-1/-1/-1/-1 直接种子 (cno-4 → cno-1, 1000× 跳) iter 6 NaN
80000/6.5/-4/-1/-1/-1/-1 两步种子 (cno-4 → cno-2 → cno-1) cno-2 → cno-1 iter 7 NaN
80000/6.5/-4/-1/-1/-1/-1 ORELAX=0.5 + 种子 (cno-2 → cno-1) iter ~10 NaN (relc=5e38)
60000/6.0/0/-1/-1/-1/-1 种子 (40K cno-4 → 60K cno-1) iter 2 NaN

物理原因80K + He-poor 时CNO 高价离子C IV/V, N V, O V/VI主导大气 不透明度;当 logCNO 从 -2 跳到 -1金属量 ×10光致电离率变化陡峭到 完全线性化无法阻尼 iter 1 的尖峰。

错误 8ICRSW 是死代码(关键发现)

源码分析后发现 ICRSWHummer & Voels 1988 碰撞-辐射开关)在 tlusty208 中实际不可用

  • SWITCH 子程序在 tlusty208.f:4556 定义,含完整的 CRSW 计算逻辑
  • 整个源文件中没有任何一处 CALL SWITCHgrep "CALL SWITCH" 返回空)
  • CRSW 数组在 tlusty208.f:1812 被默认初始化为 UN=1.0
  • 因此 tlusty208.f:6343-6344, 6591-6592 等处的 RRU/RRD * CRSW(ID) 实际 乘的是 1.0,没有任何阻尼效果
  • 同类的 CRSW 消费点还在 tlusty208.f:18201, 18252FSOLV/FCOOL 内), 共三处消费簇,全部因 CRSW≡1 而失效

测试验证:开启 ICRSW=1/SWPFAC=0.001/SWPINC=2.0 后nc 阶段的迭代历史 iter 1-25 relc 演化)与不开启 ICRSW 完全相同——确认 SWITCH 未被调用。

教训源码里的子程序未必被调用。看似可用的参数ICRSW 在 nst namelist 里、在 fort.6 里也被打印)可能是死代码。真正能用的稳定化参数是 ORELAX tlusty208.f:14647, 14996)和 IDLTEtlusty208.f:5515)—— 它们确实被使用。但实测对极端金属跳跃也无效。

种子步进的网格应用策略

  1. 冷启动能搞定的点:直接 run_one.py20000-40000K 大部分点)
  2. 冷启动搞不定的点:用 seed_step.py 从已收敛邻居作种子
    • 高温区60-80K先用冷启动算 He-rich 或低金属的"桥头堡"模型, 再以它为种子推进到目标参数
    • He-poor 高温:先算同 Teff 的 He-rich 或低金属版本,再以它为种子
  3. 物理极限80K + He-poor + logCNO=-1金属 0.1×H的组合 即使用两步种子 + ORELAX 也无法收敛。这是真实物理极限, 网格在该角落如实标记为"未收敛"。
  4. run_grid.py 的种子策略:扩展为"按邻居查找已收敛模型作种子" 失败则尝试中间丰度点作跳板。

6. 代码系统说明(cno_grid/

当前配置(已修正为用户原配方)

  • gen_input5.pyHe .5 nlevs=14数据文件本身 24/20按 nlevs 截断), NFREAD=2000支持 ilvlin/metals 参数
  • run_one.py DEFAULT_CHAIN三步法无 CHMAX/ITEK/ORELAX
    • write_nst 支持 ichang/orelax/idlte/iacc/icrsw注意 ICRSW 实际是死代码)
  • seed_step.py:种子步进实现 —— 跳过 LTE grey 冷启动, 直接热启动 nc 阶段,用于高温/He-poor/富金属等冷启动失败的场景
  • run_grid.py6 维网格调度集成种子步进回退NEW
    • 冷启动失败时自动用 find_seed 找已收敛邻居,用 SEED_STEP_CHAIN 重试
    • 失败的冷启动结果备份到 <model>.coldfail/,避免污染种子库
    • find_seed 优先级:同 (Teff,logg,logHe) 最近 CNO → 全局最近邻
    • _atmos_clean 过滤掉 NaN 污染的"假收敛"模型作种子

使用方法

export TLUSTY=/home/dckj/program/tlusty/tl208-s54
# 单个模型(冷启动,适用 20-40K 大部分点)
python3 cno_grid/src/run_one.py --teff 35000 --logg 5.5 --loghe -2 \
  --logc -1 --logn -1 --logo -1

# 种子步进(高温 He-poor 等冷启动失败点)
python3 cno_grid/src/seed_step.py --teff 80000 --logg 6.5 --loghe -4 \
  --logc -4 --logn -4 --logo -4 \
  --seed cno_grid/results/<seed-model>/<seed-model>.7

# 批量网格(自动冷启动 + 失败时种子步进回退)
python3 cno_grid/src/run_grid.py cno_grid/config.yaml --dry-run  # 预览
python3 cno_grid/src/run_grid.py cno_grid/config.yaml             # 正式跑

网格运行策略

桥头堡机制(手动):在跑完整网格前,先在难收敛区附近算几个"桥头堡" 模型,建立种子库。例如高温区先算:

# 1. 算 80K He-rich cno-4冷启动可成功
python3 cno_grid/src/run_one.py --teff 80000 --logg 6.5 --loghe 2 \
    --logc -4 --logn -4 --logo -4
# 2. 用它作种子算 80K He-poor cno-4种子步进
python3 cno_grid/src/seed_step.py --teff 80000 --logg 6.5 --loghe -4 \
    --logc -4 --logn -4 --logo -4 \
    --seed cno_grid/results/t80000_g6.5_he2_c-4_n-4_o-4/t80000_g6.5_he2_c-4_n-4_o-4.7
# 3. 之后跑 run_grid.py 时find_seed 会自动发现这些已收敛的邻居

run_grid 的种子步进回退流程

对每个网格点 P:
  1. 检查 conv.json: 若已 converged → skip
  2. find_seed(P): 查找已收敛邻居(同 family 优先,按 CNO 距离)
  3. 冷启动 run_one(DEFAULT_CHAIN):
       a. 成功 → 完成
       b. 失败 + seed_step_fallback=true + 找到种子 →
          移动失败结果到 <P>.coldfail/
          用 SEED_STEP_CHAIN + seed 重试
  4. 写 grid_status.json 汇总(含 seed_step_retries 计数)

6X. ICRSW 修复方案(备选 —— 当前未实施)

背景

ICRSWHummer & Voels 1988 碰撞-辐射开关)在 tlusty208 中是未完成的 集成——SUBROUTINE SWITCHtlusty208.f:4556)写好了完整的 CRSW 计算 逻辑,下游消费方代码(tlusty208.f:6341-80211820118252 三处 RRU/RRD * CRSW(ID))也都到位,但主迭代循环中缺少 CALL SWITCH PROGRAM TLUSTY 主循环在 line 30-58SUBROUTINE SOLVE 在 line 14420 两者都没有调用 SWITCH

结果:CRSW(ID) 永远保持默认值 UN=1.0line 1812 CRSW(ID)=UN 所有 RRU/RRD * CRSW(ID) 都是无效操作。

修复需要的步骤

步骤 1检查编译环境

which gfortran || apt list --installed 2>/dev/null | grep -i fortran
# 若无安装sudo apt install gfortran

步骤 2定位主迭代循环

主循环在 tlusty/tlusty208.f:30-58(行 28 是注释头,行 30 是 10 ITER=ITER+1 行 58 是 20 CONTINUE

   10 ITER=ITER+1
      CALL RESOLV           ! 形式解
      IF(IACC.GT.0) CALL ACCEL2
      IF(NN.GT.MSMX) THEN
         CALL SOLVE         ! 完全线性化
       ELSE
         CALL SOLVES
      END IF
      CALL TIMING(2,ITER)
      GO TO 10

步骤 3在循环中插入 SWITCH 调用

ITER=ITER+1 之后、CALL RESOLV 之前插入:

   10 ITER=ITER+1
      C ---- ICRSW 碰撞-辐射开关(修复)----
      C 首次迭代初始化 CRSW,后续迭代放大 CRSW  1.0 趋近
      IF(ICRSW.GT.0) THEN
         IF(ITER.EQ.1) THEN
            CALL SWITCH(1)     ! INITM=1: 初始化 CRSW=SWPFAC*min(C/R)
          ELSE
            CALL SWITCH(0)     ! INITM=0: CRSW *= SWPINC
         END IF
      END IF
      CALL RESOLV
      ...

步骤 4验证变量在调用点已就绪

SWITCH 子程序用到 COLRAT(ITR,ID)RRU(ITR,ID)RRD(ITR,ID)LINE(ITR)FR0(ITR)TEMP(ID)HK。需确认这些在 ITER=ITER+1 之后已被前一迭代更新(或在首次迭代时已初始化)。若未就绪,可能需要 把 SWITCH 调用移到 CALL RESOLV 之后。

步骤 5重新编译

cd tlusty/
# 备份原可执行
cp tlusty.exe tlusty.exe.orig
# 编译(具体命令取决于源码组织,可能是单文件或 Makefile
gfortran -O2 -o tlusty.exe tlusty208.f \
    IMPLIC.FOR BASICS.FOR ARRAY1.FOR ATOMIC.FOR MODELQ.FOR \
    ITERAT.FOR ALIPAR.FOR ODFPAR.FOR

步骤 6测试

跑已知难收敛的点(如 80K + He-poor + logCNO=-1开/关 ICRSW对比 nc 阶段的迭代历史。如果 ICRSW 真正生效,开启后 iter 2 的尖峰应该被 压低CRSW < 1

# 关 ICRSW基线
python3 cno_grid/src/seed_step.py --teff 80000 --logg 6.5 --loghe -4 \
    --logc -1 --logn -1 --logo -1 --seed <seed> \
    2>&1 | tee /tmp/no_icrsw.log
# 开 ICRSW修复后
# 在 nst 里加 ICRSW=1,SWPFAC=1e-3,SWPLIM=1.0,SWPINC=2.0
# ... 跑同样模型
# 对比两次的 fort.9 iter 1-10 max_relc 演化

修复风险评估

好处

  • 可能解锁 80K + He-poor + 富金属区的收敛(最后一个未解决角落)
  • 是数值方法层面的修复,物理意义清晰

风险

  • 重新编译可能引入其他问题tlusty 源码依赖复杂)
  • SWITCH 集成后可能有未预见的 bug作者本就没集成可能有原因
  • 编译器版本差异(原版可能用 f77/f90gfortran 行为可能不同)

替代方案:网格层面规避

如果不想改源码,现有种子步进方案已覆盖大部分情况

  • 20000-40000K冷启动全区间可靠
  • 60000-80000K + He-rich / 低金属:冷启动或一步种子步进可解决
  • 60000-80000K + He-poor + 富金属logCNO=-1真实物理极限 网格如实标记未收敛(这是合理的——观测上这些极端参数组合的 sdB 本就罕见)

建议:先用现有方案跑完整网格,统计未收敛点的比例和分布。 如果未收敛点占总网格 <5%,无需修复 ICRSW如果占比高且集中在 可观测的重要参数区,再考虑修复。

每阶段信息记录

conv.json 记录每阶段的 converged/max_relc/elapsed_sec以及 synspec_sec。


7. 下一步建议

立即可行(已验证配方 + 种子步进)

  • 冷启动跑 20000-40000K + logC/N/O=-4 到 -1:可靠收敛,无需种子
  • 种子步进跑 60000-80000K + He-rich 或低金属:用冷启动建"桥头堡" 再以它为种子推进到目标点
  • 物理极限标注80K + He-poor + logCNO=-1 是真实物理极限, 网格如实标记未收敛(不强制成功)

待完善

  • run_grid.py 自动种子策略:现在是冷启动失败即标记失败; 应扩展为先尝试冷启动,失败则查找邻居已收敛模型作种子重试, 再失败则尝试中间丰度点作跳板(如 cno-4 → cno-2 → cno-1
  • 种子库管理:网格计算时按 Teff/logg 分组,每组先算最容易的点 He-rich 或低金属),建立种子库,再扩散到难收敛点。
  • synspec 高温区 NaN 问题80K 大气收敛但 synspec 谱有 74% NaN。 需要单独排查(可能是 gfVIS99.dat 谱线表对 80K 不兼容,或某些 CNO 高价离子模型原子在该温度下数值溢出)。

关键教训

  1. 先核实物理参数:之前花了大量时间调 CHMAX/IDLTE/ORELAX/He能级/NFREAD 真正的根因(丰度范围过高 + 冷启动初猜太远)却一直被忽略。
  2. 冷启动 ≠ 唯一选择LTE grey 冷启动在高温 He-poor 模型上必然失败, 但这不是物理极限,只是初猜太差。种子步进是 Peter Nemeth 邮件早就 建议的方法("减小模型间步长"),只是之前一直没正确实现。
  3. 源码分析的价值:通过 subagent 读 tlusty208.f 才发现:
    • LTGREY 标志的真正含义T=生成灰大气F=读 fort.8)—— 种子步进的物理基础
    • ICRSW 是死代码SWITCH 子程序定义了但从未被 CALL—— 看似可用的 参数实际无效,必须实测验证
  4. 物理极限要承认80K + He-poor + 金属量 0.1×H 的组合, 即使用尽所有稳定化手段也不收敛。这是物理极限,不是工程问题。 网格应如实记录未收敛而非强行通过。

8. 邮件往来要点Peter Nemeth

完整邮件在 hot_subdwarf/letter/(已逐条核对原文)。要点:

  1. He-rich 模型难收敛属正常(原文:"Helium-rich models struggle a lot ... That is normal"
  2. He 用最复杂模型原子(原文:"I would always use the 24-level He1 and 20-level He2 model atoms")— 但实测在 .5 里声明 nlevs=14 对 nc 更稳定, 两者目标不同Peter 关注光谱精度,我们关注收敛稳定性)
  3. ITEK 可调(原文:"you can set it to 3, 15, and 100")— 实测 100 overshoot 且不设(默认 4最好
  4. 模型链:粗→精 CHMAX减小模型间步长原文"You can try decreasing the steps in between models")← 本次种子步进正是这一条的正确实现 LTGREY=F 热启动 + 邻居模型作种子)

9. 参考文件索引

文件 内容
/home/dckj/program/tlusty/tests/cno_sdspectrum/GUIDE.md 用户原始指南35000K 验证配方)。注意:在 tl208-s54/ 上一级目录
cno_grid/src/run_one.py 三步链实现DEFAULT_CHAIN = 正确配方)
cno_grid/src/gen_input5.py .5 生成器He nlevs=14, NFREAD=2000
cno_grid/src/seed_step.py 种子步进实现(高温/难收敛点)
cno_grid/src/run_grid.py 6 维网格调度器(冷启动 + 种子步进回退)
cno_grid/src/check_conv.py fort.9 解析与收敛判定
cno_grid/config.yaml 网格配置链、seed_step_fallback 等)
cno_grid/PIPELINE.md 计算流程文档(阶段/并行/统计)
cno_grid/results/ 测试模型结果(含 conv.json
cno_grid/results/bound_*.log 8 点边界测试日志2026-07-21
cno_grid/results/seed_step/ 种子步进验证结果
cno_grid/run_boundary_corrected.sh 边界测试启动脚本
hot_subdwarf/letter/ Peter Nemeth 邮件