674 lines
32 KiB
Markdown
674 lines
32 KiB
Markdown
# 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=-1:nc max_relc=0.00148(未达 0.001,但作为种子
|
||
被接受),nl 收敛到 0.000633,耗时 1635s(synspec 3.6s)
|
||
- ✅ 40000/5.5/logHe=0/logCNO=-1(C/H=0.1):nc=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(τ) 结构,提供物理合理的起点
|
||
- **nc(ilvlin=0)**:切换 NLTE 但不含线跃迁。线跃迁是统计平衡方程中最敏感
|
||
的非线性项;先不加线,只收敛电离平衡
|
||
- **nl(ilvlin=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 显式声明为 14**(tlusty 按此截断数据文件,
|
||
只读前 14 个能级)。
|
||
|
||
满 24-level 在 nc 阶段(即使 ilvlin=0)引入更多连续谱跃迁(光致电离/复合),
|
||
增加 NLTE 线性化的维度和不稳定性。`.5` 声明 nlevs=14 是用户验证过的稳定配置。
|
||
|
||
> 注 1:Peter 的建议针对 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)`(绝对比值),所以 `.5` 里 `abn = 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 = 0(C/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. 调试过程中犯的错误(如实记录)
|
||
|
||
### 错误 1:CHMAX=0.1(核心错误)
|
||
- **来源**:从 bstar 的 nst 抄来
|
||
- **影响**:nc 在 0.1 就停止,没真正收敛 → nl 不稳定 → 大部分点失败
|
||
- **纠正**:不设 CHMAX,用默认 0.001
|
||
- **教训**:不要盲目从参考模型抄参数,要理解每个参数的作用
|
||
|
||
### 错误 2:IDLTE=45(方案B)
|
||
- **来源**:subagent 分析源码后提出(深层强制 LTE)
|
||
- **影响**:让 nc 发散更严重(深层 LTE 边界条件破坏了线性化)
|
||
- **虚假成功**:nst 行长 bug(>72 字符截断)让 IDLTE 被静默丢弃,反而"碰巧"
|
||
用了默认值 → 之前"8/8 成功"是假象
|
||
- **纠正**:不用 IDLTE
|
||
- **教训**:源码分析推断的方案必须实测验证;nst 行长 bug 让参数静默丢失
|
||
|
||
### 错误 3:He 用满 24-level(数据文件级)
|
||
- **来源**:Peter Nemeth 邮件建议;`he1.dat` 本身就含 24 能级
|
||
- **影响**:增加 nc 的不稳定(更多连续谱跃迁进入线性化)
|
||
- **纠正**:`.5` 里把 He I/II 的 nlevs 显式声明为 **14**(tlusty 按此截断
|
||
`he1.dat`/`he2.dat`,只读前 14 个能级)—— 用户 tests.zip 验证的配置
|
||
- **教训**:专家建议针对的目标(光谱精度)可能和你的目标(收敛稳定性)不同。
|
||
注意区分"数据文件能级数"和"`.5` 声明的 nlevs"——前者是文件内容,后者才是
|
||
tlusty 实际求解的能级数。
|
||
|
||
### 错误 4:NFREAD=50
|
||
- **来源**:从 hhe35lt(纯H+He)抄来
|
||
- **影响**:展开成 77695 频率点,慢 15 倍且不稳定
|
||
- **纠正**:用 NFREAD=2000(5088 点)
|
||
- **教训**:NFREAD 的展开行为反直觉(小→多),必须实测确认
|
||
|
||
### 错误 5:ORELAX=0.5(部分有效但非通用解)
|
||
- **来源**:阻尼布居数跳跃
|
||
- **影响**:对某些点(40000K 单独 nc 测试)有效,但在完整链中不可靠
|
||
- **纠正**:不用 ORELAX(用户配方无 ORELAX 且成功)
|
||
- **教训**:单独测试 nc 成功不代表完整链成功
|
||
|
||
### 错误 6:nst 行长截断 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 绝对比值)。
|
||
|
||
### 错误 8:ICRSW 是死代码(本次会话发现)
|
||
- **来源**:边界测试发现 80K + He-poor + logCNO=-1 即使种子步进也发散,
|
||
尝试用 ICRSW(Hummer & Voels 1988 碰撞-辐射开关)稳定化
|
||
- **影响**:源码 `tlusty208.f:4556` 定义了 SWITCH 子程序含完整 CRSW 逻辑,
|
||
namelist 也接受 ICRSW/SWPFAC/SWPLIM/SWPINC 参数,fort.6 也打印这些值,
|
||
看起来一切正常——但**整个源文件中没有任何一处 CALL SWITCH**。
|
||
- **实测验证**:开 ICRSW=1/SWPFAC=0.001 后 nc 迭代历史与不开完全相同
|
||
- **纠正**:放弃 ICRSW,改用实际有效的 ORELAX(`tlusty208.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 到 -1),20000-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-4):iter 2 也出现 1.5 的尖峰,
|
||
但线性化阻尼住 → iter 5 回到 1e-2 → iter 10 < 1e-3 收敛。
|
||
- **关键差异**:He 含量。He-rich(logHe=+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)
|
||
|
||
`ICRSW`(`tlusty208.f:4556`)= Hummer & Voels 1988 碰撞-辐射开关,
|
||
是另一可选稳定化参数(本次未启用,详见错误 8 与 §6X)。
|
||
|
||
### 种子步进实现
|
||
|
||
新增 `cno_grid/src/seed_step.py`,跳过 LTE grey 冷启动,
|
||
直接热启动 nc 阶段:
|
||
|
||
```python
|
||
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},
|
||
]
|
||
```
|
||
|
||
用法:
|
||
```bash
|
||
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 的尖峰。
|
||
|
||
### 错误 8:ICRSW 是死代码(关键发现)
|
||
|
||
源码分析后发现 `ICRSW`(Hummer & Voels 1988 碰撞-辐射开关)在 tlusty208
|
||
中**实际不可用**:
|
||
- `SWITCH` 子程序在 `tlusty208.f:4556` 定义,含完整的 CRSW 计算逻辑
|
||
- 但**整个源文件中没有任何一处 `CALL SWITCH`**(`grep "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, 18252`(FSOLV/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`)和 `IDLTE`(`tlusty208.f:5515`)——
|
||
它们确实被使用。但实测对极端金属跳跃也无效。
|
||
|
||
### 种子步进的网格应用策略
|
||
|
||
1. **冷启动能搞定的点**:直接 `run_one.py`(20000-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.py`:He `.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.py`:6 维网格调度,集成种子步进回退(NEW)
|
||
- 冷启动失败时自动用 `find_seed` 找已收敛邻居,用 SEED_STEP_CHAIN 重试
|
||
- 失败的冷启动结果备份到 `<model>.coldfail/`,避免污染种子库
|
||
- `find_seed` 优先级:同 (Teff,logg,logHe) 最近 CNO → 全局最近邻
|
||
- `_atmos_clean` 过滤掉 NaN 污染的"假收敛"模型作种子
|
||
|
||
### 使用方法
|
||
```bash
|
||
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 # 正式跑
|
||
```
|
||
|
||
### 网格运行策略
|
||
|
||
**桥头堡机制**(手动):在跑完整网格前,先在难收敛区附近算几个"桥头堡"
|
||
模型,建立种子库。例如高温区先算:
|
||
```bash
|
||
# 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 修复方案(备选 —— 当前未实施)
|
||
|
||
### 背景
|
||
`ICRSW`(Hummer & Voels 1988 碰撞-辐射开关)在 tlusty208 中是**未完成的
|
||
集成**——`SUBROUTINE SWITCH`(`tlusty208.f:4556`)写好了完整的 CRSW 计算
|
||
逻辑,下游消费方代码(`tlusty208.f:6341-8021`、`18201`、`18252` 三处
|
||
`RRU/RRD * CRSW(ID)`)也都到位,但**主迭代循环中缺少 `CALL SWITCH`**
|
||
(`PROGRAM TLUSTY` 主循环在 line 30-58,`SUBROUTINE SOLVE` 在 line 14420,
|
||
两者都没有调用 SWITCH)。
|
||
|
||
结果:`CRSW(ID)` 永远保持默认值 `UN`(=1.0,line 1812 `CRSW(ID)=UN`),
|
||
所有 `RRU/RRD * CRSW(ID)` 都是无效操作。
|
||
|
||
### 修复需要的步骤
|
||
|
||
**步骤 1:检查编译环境**
|
||
```bash
|
||
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`):
|
||
```fortran
|
||
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` 之前插入:
|
||
```fortran
|
||
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:重新编译**
|
||
```bash
|
||
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)。
|
||
|
||
```bash
|
||
# 关 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/f90,gfortran 行为可能不同)
|
||
|
||
### 替代方案:网格层面规避
|
||
|
||
如果不想改源码,**现有种子步进方案已覆盖大部分情况**:
|
||
- 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 邮件 |
|