371 lines
14 KiB
Markdown
371 lines
14 KiB
Markdown
# 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. nc(NLTE 连续谱)| tlusty | `F F` + `ilvlin=0`,收敛电离平衡(无线跃迁)| 1-10 分钟 |
|
||
| 3. nl(NLTE 含线)| 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)。线性化的收敛半径
|
||
有限——当初猜离真解太远时迭代发散。四个阶段逐步缩小初猜与真解的差距:
|
||
|
||
- **阶段1(LTE 灰大气)**:LTE + 灰色不透明度假设下解析求温度结构。提供物理
|
||
合理的起点,不需要种子(从零开始)。
|
||
- **阶段2(nc 连续谱)**:切换到 NLTE,但 `ilvlin=0` 不含束缚-束缚线跃迁。
|
||
线跃迁是统计平衡方程中最敏感的非线性项;先不加线,只收敛电离平衡(光致电离
|
||
+复合),得到稳定的 NLTE 布居数结构。**跳过此步直接 grey→含线 NLTE 必发散。**
|
||
- **阶段3(nl 含线)**:加入全部线跃迁(ilvlin=100)。从已收敛的 nc 种子起步,
|
||
线扰动小,快速收敛(典型 ~15 次迭代)。
|
||
- **阶段4(synspec)**:用 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 文件(非标准参数,每阶段不同)
|
||
|
||
**阶段1(LTE 灰大气)—— 保持干净,不加稳定化参数**:
|
||
```
|
||
ND=50,VTB=2.,NITER=0
|
||
```
|
||
- `NITER=0`:灰大气不迭代,只做一次形式解。
|
||
|
||
**阶段2(nc)和阶段3(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,强迫 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.84(nst 参数的
|
||
内部表示)。如果下一次 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。
|