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

371 lines
14 KiB
Markdown
Raw 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.

# CNO 网格完整计算流程
> 本文档说明完整理论光谱网格的计算流程:每个网格点的计算阶段、每阶段的配置、
> 配置原理、CPU/并行机制、以及如何统计每个阶段的信息(时间、收敛等)。
---
## 1. 总体架构
```
config.yaml (网格点 + 收敛链配置)
run_grid.py ── 生成 6 维笛卡尔积参数点
│ 断点续算(跳过已成功) / 种子复用(最近邻) / 失败隔离
├── worker 1 ── run_one.py ── 点 A
├── worker 2 ── run_one.py ── 点 B 每个 worker 独立工作目录
├── ... 互不干扰24 核并行
└── worker 24 ── run_one.py ── 点 X
三步链(lte→nc→nl) + synspec
results/<模型名>/
conv.json ← 阶段信息(收敛/迭代/时间)
*.spec/.cont ← 光谱
*.7 ← 各阶段大气
```
---
## 2. 每个网格点的计算阶段
每个网格点(一组 Teff/logg/logHe/logC/logN/logO 参数)经过 **4 个阶段**
| 阶段 | 程序 | 做什么 | 典型耗时 |
|------|------|--------|---------|
| 1. LTE 灰大气 | tlusty | `T T` 模式,解析求灰色 T(τ) 结构 | 1-3 秒 |
| 2. ncNLTE 连续谱)| tlusty | `F F` + `ilvlin=0`,收敛电离平衡(无线跃迁)| 1-10 分钟 |
| 3. nlNLTE 含线)| tlusty | `F F` + `ilvlin=100`,加全部谱线跃迁 | 5-20 分钟 |
| 4. synspec | synspec | 用 nl 大气合成可观测光谱 | 3-10 秒 |
**阶段间依赖**1→2→3→4 严格顺序。每阶段用上一阶段的 `.7` 大气作种子fort.8)。
### 为什么是这 4 个阶段(原理)
Tlusty 的 NLTE 求解用**迭代线性化**complete linearization。线性化的收敛半径
有限——当初猜离真解太远时迭代发散。四个阶段逐步缩小初猜与真解的差距:
- **阶段1LTE 灰大气)**LTE + 灰色不透明度假设下解析求温度结构。提供物理
合理的起点,不需要种子(从零开始)。
- **阶段2nc 连续谱)**:切换到 NLTE`ilvlin=0` 不含束缚-束缚线跃迁。
线跃迁是统计平衡方程中最敏感的非线性项;先不加线,只收敛电离平衡(光致电离
+复合),得到稳定的 NLTE 布居数结构。**跳过此步直接 grey→含线 NLTE 必发散。**
- **阶段3nl 含线)**加入全部线跃迁ilvlin=100。从已收敛的 nc 种子起步,
线扰动小,快速收敛(典型 ~15 次迭代)。
- **阶段4synspec**:用 nl 阶段收敛的大气模型,计算指定波长范围的合成光谱。
---
## 3. 每阶段的配置信息与原理
### 3.1 `.5` 文件(每阶段一份,三阶段相同 NATOMS/ions只改 3 处)
```
第1行: TEFF GRAV (三阶段相同:目标参数)
第2行: LTE LTGRAY 阶段1=T T阶段2/3=F F
第3行: nst 文件名 阶段1=nst_lte, 阶段2=nst_nc, 阶段3=nst_nl
第4行: NFREAD =50, 频率点数)
第5行: NATOMS =8: H,He,空×3,C,N,O
第6+行: atoms (mode abn modpf) C/N/O 的 mode=2 显式NLTE, abn=10^logX
ions段: iat iz nlevs ilast ilvlin nonstd typion filei
阶段1/2: ilvlin=0; 阶段3: ilvlin=100 ← 关键区别)
```
**为什么 NATOMS/ions 三阶段必须相同**:每阶段的 `.7` 大气记录了每个能级的
布居数。种子与目标的能级结构必须一一对应,否则读取时索引错位 → NaN。
### 3.2 nst 文件(非标准参数,每阶段不同)
**阶段1LTE 灰大气)—— 保持干净,不加稳定化参数**
```
ND=50,VTB=2.,NITER=0
```
- `NITER=0`:灰大气不迭代,只做一次形式解。
**阶段2nc和阶段3nl—— 频率细化(用户验证配方,不设 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,强迫 nc 真正收敛)
- **不设 ITEK**(用默认 4
每个参数的作用与原理:
| 参数 | nc值 | nl值 | 作用 | 为什么这样设 |
|------|------|------|------|-------------|
| `ND` | 50 | 50 | 大气深度点数 | sdB 标准配置 |
| `NLAMBD` | 3 | 3 | lambda 迭代频率点数 | 频率网格细化(用户验证配方) |
| `VTB` | 2. | 2. | 微湍流速度 km/s | sdB 典型值 |
| `ISPODF` | 1 | 1 | 频率网格开关 | 启用细化频率网格 |
| `DDNU` | 50. | 50. | 频率间隔因子 | 频率网格细化参数 |
| `CNU1` | 6. | 6. | 频率网格起点 | 频率网格细化参数 |
| `NITER` | 50 | 100 | 最大迭代数 | nc 给 50 次nl 给 100 次 |
| `IELCOR` | -1 | -1 | 电子密度修正 | 关闭 |
> **关键:不设 CHMAX用默认 0.001)、不设 ITEK用默认 4、不设 IDLTE/ORELAX。**
> 之前版本设了 CHMAX=0.1 导致 nc 没真正收敛,是大部分失败的根本原因。
> 详见 EXPERIENCE.md §4 的错误记录。
### 3.3 synspec 配置fort.55.lin + 谱线表)
```
fort.55.lin 第6行: WLMIN WLMAX WLSTEP ... CUTOFF ...
谱线表 fort.19: data/gfVIS99.dat (含 C 1412 / N 2396 / O 1885 条线)
```
- 当前用 3000-7000Å光学波段覆盖 C II 4267、C III 4647 等)。
- 大气来自 nl 阶段的 `.7`(复制为 fort.8)。
---
## 4. CPU 与并行机制
### 4.1 每个网格点只用一个 CPU 核
**是的。** tlusty.exe 和 synspec.exe 是 Fortran 编译的单线程程序,每个实例只用
1 个 CPU 核。网格点的并行不是靠程序内部的多线程,而是靠**同时启动多个程序实例**。
### 4.2 如何做到并行
`run_grid.py` 用 Python 的 `multiprocessing.Pool``run_grid.py:271`
```python
with Pool(nworkers) as pool:
for res in pool.imap_unordered(_worker, worker_args):
...
```
- `nworkers`config.yaml当前=24同时运行的 worker 进程数。
- 每个 worker 是一个独立的 Python 子进程,调用 `run_one.py` 跑一个网格点
(在独立的工作目录里,互不干扰)。
- `imap_unordered`:哪个点先完成就先回收,立即分配下一个点(动态负载均衡)。
- 24 核机器跑 24 个 worker = 24 个 tlusty 实例同时跑 = 满载利用。
**关键:每个 worker 用独立工作目录**`results/<模型名>/`),避免 fort.* 文件
冲突。这是并行安全的基础。
### 4.3 吞吐量估算
| 模型类型 | 单点耗时 | 24核并行吞吐 |
|---------|---------|-------------|
| 80000K待解决| — | 目前 nc 发散,需专业策略 |
| 20000-40000K标准| ~15-25 分钟 | ~72-110 点/小时 |
| 高金属 CNO=10×H | ~30 分钟 | ~48 点/小时 |
128 点疏网格约需 2-3 小时8192 点完整网格约需 5 天。
---
## 5. 如何统计每阶段信息
### 5.1 当前已记录的信息conv.json
每个网格点完成后,`results/<模型名>/conv.json` 记录:
```json
{
"name": "t40000_g6.0_he0_c1_n1_o1",
"params": {"teff":40000, "logg":6.0, "loghe":0, "logc":1, "logn":1, "logo":1},
"converged": true,
"final_max_relc": 0.0069,
"atmosphere_has_nan": false,
"synspec_rc": 0,
"elapsed_sec": 1714.1, 总耗时(所有阶段+synspec之和
"seed": null,
"stages": [
{
"label": "lte",
"converged": true,
"final": {"itek":3, "rc":0, "max_relc":0.0,
"note":"NITER=0 grey start"}
},
{
"label": "nc",
"converged": false, nc 不要求收敛,作种子即可
"final": {"itek":3, "rc":0, "max_relc":0.957,
"worst_depth":1, "last_iter":50, "n_depths":50}
},
{
"label": "nl",
"converged": true,
"final": {"itek":3, "rc":0, "max_relc":0.0069,
"worst_depth":1, "last_iter":17, "n_depths":50}
}
]
}
```
每阶段记录:`converged`(是否收敛)、`max_relc`(最大相对变化)、
`worst_depth`(最差深度点)、`last_iter`(迭代次数)、`n_depths`(深度点数)。
### 5.2 每阶段时间记录(已实现)
`run_one.py` 现在在每个阶段的循环开始/结束处计时conv.json 里每个 stage 有
`elapsed_sec`synspec 也有单独的 `synspec_sec`
```json
"stages": [
{"label":"lte", "elapsed_sec": 27.3, "converged":true, ...},
{"label":"nc", "elapsed_sec": 408.4, "converged":false, ...},
{"label":"nl", "elapsed_sec": 7.8, "converged":true, ...}
],
"synspec_sec": 3.1,
"elapsed_sec": 446.2
```
统计所有模型的阶段时间分布:
```bash
python3 -c "
import json,glob
for f in sorted(glob.glob('results/*/conv.json')):
j=json.load(open(f))
times = {s['label']:s.get('elapsed_sec',0) for s in j['stages']}
print('%-30s lte=%5.0fs nc=%5.0fs nl=%5.0fs syn=%4.0fs total=%5.0fs' % (
j['name'], times.get('lte',0), times.get('nc',0), times.get('nl',0),
j.get('synspec_sec',0), j['elapsed_sec']))
"
```
### 5.3 统计整个网格的信息
`run_grid.py` 完成后写 `results/grid_status.json`
```json
{
"total": 128,
"elapsed_sec": 9600,
"counts": {"converged": 120, "unfinished": 5, "error": 2, "skipped": 1},
"models": [
{"name":"t20000_...", "status":"converged", "max_relc":0.0065},
...
]
}
```
汇总统计命令:
```bash
# 成功率
python3 -c "import json; j=json.load(open('results/grid_status.json')); print(j['counts'])"
# 所有收敛模型的 max_relc 分布
python3 -c "
import json,glob
for f in sorted(glob.glob('results/*/conv.json')):
j=json.load(open(f))
if j['converged']:
print(j['name'], j['final_max_relc'], str(j['elapsed_sec'])+'s')
"
# 失败/未收敛的模型
python3 -c "
import json,glob
for f in sorted(glob.glob('results/*/conv.json')):
j=json.load(open(f))
if not j['converged']:
print(j['name'], 'FAILED', j.get('note',''))
"
```
### 5.4 单个网格点的详细收敛诊断
```bash
# 看某阶段的迭代收敛趋势fort.9
python3 src/check_conv.py results/<模型>/<模型>.nl.9 --chmax 0.01
# 画光谱(标出 CNO 诊断线位置)
python3 src/plot_spec.py results/<模型>
```
---
## 6. 完整操作步骤
### 第1步配置网格密度config.yaml 的 grid 段)
```yaml
grid:
teff: [20000, 30000, 40000, 60000] # 各维采样点列表
logg: [5.0, 6.0]
loghe: [-2, 0]
logc: [-1, 1]
logn: [-1, 1]
logo: [-1, 1]
```
### 第2步设置环境变量
```bash
export TLUSTY=/home/dckj/program/tlusty/tl208-s54
```
### 第3步预览dry-run
```bash
cd $TLUSTY/cno_grid
python3 src/run_grid.py config.yaml --dry-run
# 输出grid: 64 points total, N already done, M to compute
```
### 第4步启动批量计算后台并行
```bash
nohup python3 src/run_grid.py config.yaml > results/grid_run.log 2>&1 &
```
### 第5步监控
```bash
tail -f results/grid_run.log # 实时进度
cat results/grid_status.json # 汇总(完成后才有)
```
### 第6步断点续算中断后恢复自动跳过已成功的
```bash
python3 src/run_grid.py config.yaml # 重跑同一命令即可
```
### 第7步检查结果 + 画图
```bash
# 成功率
python3 -c "import json;print(json.load(open('results/grid_status.json'))['counts'])"
# 画某个模型光谱
python3 src/plot_spec.py results/<模型名>
```
### 第8步加密网格Phase 2
在 config.yaml 的各维列表里加更多点,重跑 `run_grid.py`(自动跳过已完成的,
只算新点)。
---
## 7. 注意事项
1. **种子复用**run_grid.py 会自动找最近邻已收敛的 `.7` 作种子,省去 LTE 阶段。
但种子与目标的参数差不能太大logg ≤0.5/步Teff ≤5000K/步),否则发散。
建议网格各维步长不要太大。
2. **断点续算判定**`conv.json` 里 `converged=true` 的点会被跳过。假收敛
atmosphere_has_nan=true的点会被重算。
3. **失败隔离**:单点失败(发散/崩溃)不中断整个网格,记入 grid_status.json
的 error 列表。可在 config.yaml 放宽 CHMAX 或加 orelax 重试失败点。
4. **磁盘空间**:每个模型约 50-100MB含中间文件。8192 点约需 400-800GB。
当前 932GB 可用,够完整网格。如不够可定期清理中间文件(保留 .spec/.cont/.7/conv.json
5. **并行安全**:每个 worker 用独立工作目录results/<模型名>/fort.* 文件
不冲突。可安全并行。
6. **nst 文件行长限制(已修复)**tlusty 的 nst 解析器有 ~72 字符的行宽限制。
如果参数太多写在一行(如加了 IDLTE/IACC 后 >72 字符),行尾的参数会被
静默截断。`write_nst()` 现在把参数分两行写line1≤64c, line2 余下参数)。
这是个隐蔽 bug——截断后 tlusty 不报错而是用默认值,导致"看似成功实则参数
没生效"。
7. **fort.84 残留(已修复)**tlusty 运行时会在工作目录写 fort.84nst 参数的
内部表示)。如果下一次 tlusty 运行(不同 NATOMS读到旧的 fort.84,会报
"Bad integer for item 48" 崩溃。`run_tlusty()` 现在每次运行前删除 fort.84。
8. **收敛可靠性(如实)**:用正确配方(无 CHMAX/ITEK, 14-level He, NFREAD=2000
重新验证后35000K 成功nl=0.00078),但 40000K+ 的 nc 仍然震荡发散。
之前版本的"8/8 边界全部成功"不准确(基于错误的 CHMAX=0.1)。
40000K+ 高温区需要种子步进或 ICRSW 等专业策略。详见 EXPERIENCE.md。