17 KiB
CNO 热亚矮星光谱:调试经验与批量网格操作手册
本文档记录在
tl208-s54/cno_grid/上构建含 C/N/O 金属线的热亚矮星理论光谱 过程中积累的全部经验,重点是收敛调试中踩过的坑和已验证可行的配方。 基础背景知识见tests/cno_sdspectrum/GUIDE.md(必读)。
1. 核心结论(先读这一节)
-
金属线必须进大气模型。 实测证实:纯 H+He 大气下跑 synspec,C/N/O 谱线 全部产生 NaN——因为大气里没有金属能级,synspec 无法计算谱线不透明度。 所以 C/N/O 必须作为显式 NLTE 原子(mode=2)写进
.5的大气和 ions 段。 这正是tests/cno_sdspectrum/GUIDE.md的方案 B(自洽)。 -
收敛必须走三步法,
nc步骤不可省略:LTE 灰大气 (T T, NITER=0) → 初始温度结构 NLTE 连续谱 (F F, ilvlin=0, "nc") → 收敛电离平衡+布居数(不含谱线扰动) NLTE 含线 (F F, ilvlin=100, "nl")→ 加谱线,从 nc 种子快速收敛 SYNSPEC → 合成可观测光谱nc是关键:它在没有谱线扰动的情况下先收敛 NLTE 电离平衡,给nl一个 稳定种子。跳过 nc(grey-LTE → 直接含线 NLTE)必定发散(见第 3 节)。 -
端到端已验证跑通(35000K / logg 5.5 / logHe=-2 / CNO=-1): lte✓ → nc✓ → nl 收敛 (max_relc=0.0043) → synspec✓,产出 111500 个有效 通量点,CNO 吸收线清晰可见(3000–3500Å 吸收深度 68%)。单模型约 14 分钟。
2. 已验证的精确配方
2.1 .5 文件三阶段配置
三个阶段用相同的 NATOMS(8)和 ions 能级数,只改三处:第 2 行 LTE/LTGRAY
标志、第 3 行 nst 文件名、ions 段第 5 列 ilvlin。
| 阶段 | 第2行 | nst | ilvlin | 说明 |
|---|---|---|---|---|
| lte | T T |
nst (NITER=0) | 0/100 均可(LTE忽略线) | 灰大气,不迭代 |
| nc | F F |
nst_nc (NITER=50) | 0 | NLTE 连续谱,无线跃迁 |
| nl | F F |
nst_nl (NITER=100) | 100 | NLTE 全谱线 |
ilvlin=0让该离子的束缚-束缚跃迁不参与计算(只保留连续谱),这正是 nc 步骤 稳定收敛的原因。ilvlin=100恢复全部线跃迁。
2.2 CNO 模型原子(能级数,照搬 BSTAR2006)
| 元素 | 离子 / 能级 / 数据文件 |
|---|---|
| C (Z=6) | C I 40 c1.dat / C II 22 c2.dat / C III 46 c3_34+12lev.dat / C IV 25 c4.dat / C V 1 |
| N (Z=7) | N I 34 n1.dat / N II 42 n2_32+10lev.dat / N III 32 n3.dat / N IV 48 n4_34+14lev.dat / N V 16 n5.dat / N VI 1 |
| O (Z=8) | O I 33 o1_23+10lev.dat / O II 48 o2_36+12lev.dat / O III 41 o3_28+13lev.dat / O IV 39 o4.dat / O V 6 o5.dat / O VI 1 |
| He | He I 24 he1.dat / He II 20 he2.dat(Peter Nemeth 邮件建议,勿用 14-level) |
| H | H I 9 h1.dat |
合计约 530 个能级,全部文件在 $TLUSTY/data/ 下已确认存在。
2.3 nst 非标准参数(已验证)
ND=50,NLAMBD=3,VTB=2.,ISPODF=1,DDNU=50.,CNU1=6.,CHMAX=<阶段>,ITEK=3,NITER=<阶段>
IELCOR=-1
- lte 阶段:NITER=0(灰大气不迭代)
- nc 阶段:NITER=50, CHMAX=0.1(不必完全收敛,作种子即可)
- nl 阶段:NITER=100, CHMAX=0.01(要求收敛)
NLAMBD=3, ISPODF=1, DDNU=50., CNU1=6.是频率网格细化参数,不能省IELCOR=-1关闭电子密度修正(这些 sdB 模型需要)
2.4 丰度约定
.5 的 atoms 段 abn 列:
0= 太阳丰度(Tlusty 内置 Grevesse & Sauval 1998)>0= 绝对比值 N(elem)/N(H),即10^logX(logX = −1 → abn=0.1)<0= 太阳丰度的倍数(−0.1 = 0.1×太阳,−5 = 5×太阳)
本次网格统一用 abn = 10^logX(logX 为 −4..2 / −2..1)。
2.5 synspec 波长窗口
fort.55.lin 第 6 行 WLMIN WLMAX WLSTEP ...。验证用的是 3000–7000Å(光学,
覆盖 C II 4267、C III 4647 等)。要 UV 段(含更多 CNO 线)用 fort.55.uvopt
(900–8000Å,见 tests/cno_sdspectrum/)。谱线表用 data/gfVIS99.dat(含
C 1412 / N 2396 / O 1885 条线)或 data/gfATO.dat(更全)。
3. 调试踩坑记录(避免重蹈覆辙)
坑 1:跳过 nc 步骤 → NLTE 发散 ❌→✅
现象:从 grey-LTE 种子直接跑含线 NLTE(ilvlin=100),max_relc 在 iter 4 起 爆炸到 1e20 并产生 NaN。 根因:grey-LTE 的 LTE 布居数与 NLTE 解差距巨大,加上数千条谱线的扰动同时 涌入,线性化必然爆。 解决:插入 nc 步骤(ilvlin=0),先无谱线地收敛 NLTE 电离平衡。 这是最重要的经验。
坑 2:ITEK=100 反而更差 ❌
现象:nc/nl 不收敛时把 ITEK 升到 100(Peter 邮件提过 3/15/100)。 结果:ITEK=100 导致 overshoot,max_relc 飙到 1e29,比 ITEK=3 更糟。 解决:ITEK 回退链上限设为 15,不要用 100。
坑 3:纯 H+He 种子喂含 CNO 模型 → 全 NaN ❌
现象:把纯 H+He 的 .7(38 能级)作 fort.8 种子跑含 CNO 模型(530 能级),
fort.7 全 NaN。
根因:种子与目标的能级结构不匹配,tlusty 无法映射布居数。
解决:三步法的三个阶段用相同 NATOMS 和 ions 配置(只改 ilvlin/LTE 标志),
保证种子结构兼容。lte 的 .7 已经包含全部 CNO 能级位置(LTE 填充),所以能
正确喂给 nc。
坑 4:NITER=0 的 LTE 阶段不产生 fort.9 ❌→✅
现象:lte 阶段 NITER=0(灰大气不迭代),不输出 fort.9 收敛日志,自动化脚本 判定"失败"并跳过种子复制。 解决:脚本里对 fort.7(大气)单独判存在;NITER=0 时无 fort.9 是正常的, 直接把 fort.7 当种子传给下一阶段。
坑 5:grey-LTE 单独跑 synspec → 全 NaN ❌
现象:grey-LTE 大气(未做 nc/nl)直接跑 synspec,谱线全 NaN。 根因:grey 温度结构错误(标准深度 T=127834K),所有线被拒。 结论:synspec 必须用经过 nc→nl 收敛的 NLTE 大气,不能用 grey 或纯 LTE 大气。
坑 6:分析脚本读错列 → 误判"整数发散"❌
现象:监控脚本显示 max_relc = 1.0, 2.0, 3.0... 精确整数线性增长,一度怀疑
二进制有未初始化变量 bug。
真相:正则捕获了迭代号(第 1 列)而非 max_relc(第 7 列)。
教训:fort.9 列顺序是 ITER ID TEMP NE POP RAD MAXIMUM ilev ifr,
max_relc 在第 7 列。check_conv.py 一直是对的,是临时监控脚本错了。
始终用 check_conv.py 判收敛,不要手写解析。
坑 7:logg 跨度太大的种子 → 发散 ❌
现象:用 logg=4.0 的种子跑 logg=5.5 的模型(重力差 100 倍),发散。
解决:网格扩展时用"种子步进"——相邻参数点之间步长要小(logg 每步 ≤0.5,
Teff 每步 ≤5000K),用最近邻已收敛模型作种子。run_grid.py 已实现此逻辑。
4. 自动化系统使用说明(cno_grid/)
4.1 目录结构
cno_grid/
├── config.yaml # 6维网格 + 收敛链配置
├── templates/
│ ├── cno_atmos.5.tpl # (备用)模板
│ ├── nst # (备用)nst
│ └── fort.55.lin # synspec 波长窗口
├── src/
│ ├── gen_input5.py # 6维参数 → .5(支持 ilvlin / metals 子集)
│ ├── check_conv.py # fort.9 收敛判定
│ ├── run_one.py # 单模型三步链 + synspec(核心)
│ ├── run_grid.py # 6维网格调度(断点续算/并行/种子复用)
│ └── plot_spec.py # 归一化光谱 + CNO 诊断线标注
└── results/<model_name>/ # 每个模型的 .5/.7/.9/.spec/.cont/conv.json
4.2 跑单个模型
export TLUSTY=/home/dckj/program/tlusty/tl208-s54
python3 cno_grid/src/run_one.py \
--teff 35000 --logg 5.5 --loghe -2 --logc -1 --logn -1 --logo -1
# 结果在 cno_grid/results/t35000_g5.5_he-2_c-1_n-1_o-1/
# conv.json - 收敛状态(converged: true/false + 各阶段 max_relc)
# *.spec/.cont - synspec 光谱/连续谱
# *.lte.7/nc.7/nl.7 - 各阶段大气快照
4.3 批量网格
# 编辑 config.yaml 的 grid 轴(各维点列表),然后:
python3 cno_grid/src/run_grid.py cno_grid/config.yaml --dry-run # 先看总数
python3 cno_grid/src/run_grid.py cno_grid/config.yaml # 正式跑
python3 cno_grid/src/run_grid.py cno_grid/config.yaml --only teff=35000,logg=5.5 # 子集
python3 cno_grid/src/run_grid.py cno_grid/config.yaml --limit 3 # 只跑3个(测试)
# 特性:
# - resume: 跳过 conv.json 里 converged=true 的模型(断点续算)
# - 种子复用: 新模型自动找最近邻已收敛 .7 作种子
# - nworkers: config.yaml 里设并行进程数
# - 失败隔离: 单点失败不中断网格,记入 grid_status.json
4.4 检查收敛 / 画图
python3 cno_grid/src/check_conv.py results/<model>/<model>.nl.9 --chmax 0.01
python3 cno_grid/src/plot_spec.py results/<model> # 出 spectrum.png + CNO 线标注
4.5 收敛链调参(config.yaml 的 chain 段)
默认链已验证可行。若某些参数点(He-rich / 高金属)不收敛,可调:
- 放宽 nl 的 CHMAX(0.01 → 0.05)
- 增加 nc 的 NITER(50 → 200,GUIDE 提到 200 可完全收敛)
- 在 nc/nl 加 ORELAX 阻尼(
orelax: 0.5) - He-rich 点可能物理上无法收敛到 0.01(Peter 邮件预警),如实记录即可
5. 已知的 NaN 问题(非阻塞)
synspec 输出的 .spec 在部分波长区有 NaN(典型 ~70% 点有效)。这是 synspec54
在致密谱线区的数值行为("lines rejected based on opacities")。有效区间的
光谱是可信的——35000K 模型在 3000–3500Å 有 73627 个有效点,吸收深度 68%,
CNO 线清晰。降低波长采样密度或换 gfATO.bin 谱线表可能减少 NaN,但不影响
已验证的物理结论。
5.5 网格边界测试结果(8/8 全部成功)
在网格边界挑 8 个代表性难点(覆盖 Teff/logg/logHe 和 CNO 丰度两个轴),用三步法 (lte→nc→nl)+ 稳定化(方案 B,对所有 NLTE 阶段无条件启用)实测,全部成功:
Teff / logg / logHe 边界(CNO=0 太阳丰度)
| # | 参数 (Teff/logg/logHe/CNO) | nl 收敛 | max_relc | 有效点 | 结论 |
|---|---|---|---|---|---|
| 1 | 20000/5.0/+2/0(低温He-rich,最难) | YES | 0.00654 | 115362 | ✓ |
| 2 | 80000/6.5/+2/0(高温He-rich) | YES | 0.00915 | 105183 | ✓ 修复后 |
| 3 | 80000/6.5/−4/0(高温He-poor) | YES | 0.00769 | 106469 | ✓ 修复后 |
| 4 | 40000/6.0/0/+1(高金属CNO=10×H) | YES | 0.0069 | 108693 | ✓ |
| 5 | 20000/6.5/−4/0(低温贫He) | YES | 0.00927 | 115479 | ✓ |
CNO 丰度边界(含非对称丰度)
| # | 参数 (Teff/logg/logHe/C/N/O) | nl 收敛 | max_relc | 有效点 | 结论 |
|---|---|---|---|---|---|
| 6 | 40000/5.5/0/−2/−2/−2(极贫金属,0.01×H) | YES | 0.00764 | 108693 | ✓ 修复后 |
| 7 | 40000/5.5/0/+1/−2/−2(C富N/O贫,sdB典型) | YES | 0.00376 | 108576 | ✓ |
| 8 | 80000/6.5/−4/−2/−2/−2(高温+极贫金属) | YES | 0.00394 | 105300 | ✓ |
关键发现
-
8/8 全部收敛,全部产出有效光谱(10万+ 有效点)——整个网格边界(Teff 20000-80000、logg 5.0-6.5、logHe −4..2、CNO −2..1,含非对称丰度)均可计算。
-
发散不只与高温有关:测试 #6(40000K/logg5.5/logHe=0)初次也发散了, 证明发散触发条件是 Teff/logg/logHe 的组合,不只高温。因此方案 B(IDLTE=45 等)改为对所有 NLTE 阶段无条件启用——物理上深层 LTE 对所有 sdB 都成立。
-
非对称丰度可行:测试 #7(C=10×H, N=O=0.01×H)这种 sdB 典型的 C-rich 组合收敛良好(relc=0.00376),说明 C/N/O 可独立取不同丰度值。
-
耗时:80000K 点因方案 B 的 nc 快速收敛,仅 58s;40000K 含线 nl 较慢 (~20-30 min)。24 核并行约每小时 72 个模型。
5.6 80000K 高温发散:源码级根因与解决方案
问题
初次边界测试中,两个 80000K 点(He-rich 和 He-poor)在 nc 阶段发散:
max_relc 从 iter1 的 ~40 单调爆炸到 iter10 的 1e13–1e17(触发 tlusty208.f:14700
的 ABS(CHMX).GT.1.D16 硬停止)。35000K 同配方则正常收敛。
根因(tlusty208.f 源码分析)
通过分析源码确认发散由三个叠加因素导致,全部集中在深层(ID 1-8)的能级 布居数(POP 列),温度变化很小:
-
ORELAX=1.0 无阻尼(
tlusty208.f:14647):ORELAX 只乘到布居数修正上。 默认 1.0 = 纯线性化无阻尼,首次迭代就把深层 CNO 高价离子(O IV/V、C IV、 N IV/V)布居数猛推向 NLTE,每步最多变 10 倍(DPSILG=10 限幅器,14652)。 -
Ng 加速 iter7 起雪上加霜(IACC=7 默认,
tlusty208.f:29704):Ng 外推 假定迭代已近线性收敛;在强烈非线性区外推会放大误差。fort.10 显示 iter7 正是 深层 POP 从 1e2 跳到 1e4 的转折点。 -
深层无 LTE 保护(IDLTE=1000 默认,
tlusty208.f:5515):所有 50 层都解 NLTE。80000K 深层高电离能级的光致电离-复合平衡极陡峭,完全线性化给出的 修正方向不稳定。
物理本质:80000K 下 CNO 多次电离,LTE 灰大气初猜的布居数与真实 NLTE 解差太远。 与 He 丰度无关(He-rich 和 He-poor 都发散)。
解决方案 B(已验证,已集成进代码)
IDLTE=45, IACC=NITER+1 (对所有 NLTE 阶段无条件启用)
- IDLTE=45(
tlusty208.f:5515):强制最深的 5 层走 LTE。物理合理——深层 τ≫1 本就该接近 LTE,且消除深层高电离能级的发散源。对所有 sdB 都成立。 - IACC=NITER+1(
tlusty208.f:29704):完全关闭 Ng 加速(设大于 NITER 使 ACCEL2 早返回),避免外推放大误差。
效果(边界点3,80000/He-poor):
- 修复前:nc iter1=38.8 → iter10=1.5e13(发散),大气全 NaN,0 有效光谱点
- 修复后:nc iter1=2.39 → iter5=0.067(5 次收敛),nl iter6=0.0078(收敛), 106469 有效光谱点,零 NaN
注:ORELAX 当前未默认启用(保持 1.0),因为 IDLTE+IACC 已足够稳定。若遇极端 点仍发散,可在 config.yaml 的 chain 阶段加
orelax: 0.5进一步阻尼。
run_one.py 已自动处理:所有 lte=="F" 的阶段(nc/nl)自动加 IDLTE=45 和
IACC=NITER+1;LTE 灰大气阶段(NITER=0)保持干净不加。
备选方案(若方案 B 仍不够,按推荐度)
- 方案 A(碰撞-辐射开关):
ICRSW=1, SWPFAC=0.1, SWPINC=3.0(SWITCH子程序tlusty208.f:4556)。首轮压低辐射率使布居数接近 LTE,逐步恢复真 NLTE。Hummer & Voels (1988) 标准稳定化手段。 - 方案 C(强限幅):
DPSILG=3.0, ORELAX=0.3, ITEK=5。把单步布居数变化 上限从 10× 降到 3×,更保守但迭代更多。
关键源码位置(供进一步调参参考)
| 参数 | 源码行 | 作用 |
|---|---|---|
| ORELAX | 14647, 844 | 布居数过松弛(<1 阻尼) |
| IDLTE | 5515 | 深层强制 LTE 的深度阈值 |
| IACC/IACD | 29704, 29750 | Ng 加速起始/间隔 |
| ITEK | 836-860 | 完全线性化迭代数(之后转 Kantorovich) |
| DPSILG/DPSILT | 14652-14659 | 单步修正限幅(通用/温度) |
| ICRSW/SWPFAC | 4556-4642 | 碰撞-辐射开关(逐步引入 NLTE) |
| POPZCH | 22910 | max_relc 计算时跳过的小布居数阈值 |
6. 邮件往来要点(Peter Nemeth,2024-10~11)
完整邮件在 hot_subdwarf/letter/。关键技术建议:
- 收敛判定:fort.9 各深度相对变化都 < CHMAX(用
check_conv.py) - He 用最复杂模型原子(24-level He I + 20-level He II)
- He-rich 模型难收敛属正常;用模型链(粗→精 CHMAX);可减小模型间步长
- ITEK 可调(3/15/100)提升稳定性——但实测 100 会 overshoot,勿超 15
- CHMAX:普通星 0.001 可达;He-dominated 可放到几个%;先 10% 再 1% 逐步收紧
- synspec 后处理(卷积、vsini)仅在比对观测时需要;纯理论光谱可直接用 fort.7
7. 参考文件索引
| 文件 | 内容 |
|---|---|
tests/cno_sdspectrum/GUIDE.md |
你写的原始指南(基础流程、.5 格式、nst 说明)—— 必读 |
tests/cno_sdspectrum/R1_full |
你验证过的完整四步运行脚本 |
tests/cno_sdspectrum/sdB35000g550_{lte,nc,nl}.5 |
三阶段的范例 .5 文件 |
tests/sdB_spectra/runs/test_cno/ |
已算好的 35000/5.5 CNO 模型结果 |
tl208-s54/tests/tlusty/bstar/BGA20000g400v2a.5 |
BSTAR2006 含全套金属参考模型 |
hot_subdwarf/letter/ |
与 Peter Nemeth 的全部邮件 |
cno_grid/src/run_one.py |
三步链实现(DEFAULT_CHAIN 即验证配方) |