Files
DCTS/docs/PIPELINE.md
T
fmq d16b3d3cdc feat(all): 数据库模块化拆分与版本化迁移、任务引擎命名体系收敛、物理输出校验加固与用户配置接通
- server/db: 拆 4929 行 db.rs 单体为 db/ 目录,migrations.rs 引入 PRAGMA user_version
    版本化迁移运行器(M1~M13)
  - 任务引擎 Phase 6/7b/7c 改名收敛:EngineStageConfig→PhaseConfig、StagePolicy→ResumePolicy、
    Converged→Completed、删除 task_type 列、success_method 拆 tlusty_/synspec_ 双列、
    新增 tlusty_status/synspec_status 半失败阶段守卫
  - 科学正确性加固:conv_check 任意行 NaN/Inf/溢出判无效(0 行容忍)、新增 spec_is_valid
    校验 SYNSPEC 脏谱、itek_history 逐次迭代全量保真、fmt_abn powf 溢出饱和
  - 用户配置真正接通:tlusty_chain/tlusty_input 由死字段经 调度器→TaskSpec→executor→runner
    透传生效;config 加载期 validate + deny_unknown_fields + 解析失败记 warn
  - 调度修复:H1 活锁(pending_strategies 跳过已失败策略)、种子查找错误不再静默降级冷启动
  - dashboard: 阶段配置面板 tlusty_stage/synspec_stage、"已完成"标签、迭代诊断展示
  - docs: 新增 database_refactor_design.md,同步 database/api/PIPELINE/workflow_detail
2026-08-06 20:51:21 +08:00

386 lines
19 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/并行机制、以及如何统计每个阶段的信息(时间、收敛等)。
> 当前实现为 **Rust 分布式 C/S + MQ 架构**(非早期 Python 单机脚本),调度与阶段配置
> 详见 `architecture.md` / `task_engine_decoupling_design.md`。
---
## 1. 总体架构
```
workflows/<wf>.yaml (网格点 + 阶段独立配置 + 策略链)
server GridScheduler (schedule_pending_tasks)
│ 原子选点(claim_pending_grid_points) → 生成 TaskSpec
MQ task_queue (SQLitepending/claimed 状态机)
│ Worker 轮询 claim 抢占(pull 模型)
node executor(每任务独立沙盒 task_<id>/
│ 按 tlusty_strategies[0] 执行收敛链
├── ColdRun : lte → nc → nl → synspec
├── SeedStep : seed_nc → nl → synspec(凭 seed_point_name 下载种子 .7 热启动)
结果上报(report)→ 白名单归档 result_dir/<name>/ → 清理沙盒
│ 收敛且干净 → .7 入种子库 seeds(供他点热启动)
```
策略链回退:当前策略失败 → 服务端弹出链首 → 下一顺位(如 `seed_step`)注入种子重试,
详见 `design.md §2``task_engine_decoupling_design.md §4.2`
---
## 2. 每个网格点的计算阶段
每个网格点(一组 Teff/logg/logHe/logC/logN/logO 参数)经过 **TLUSTY 大气求解 + SYNSPEC
光谱合成**。TLUSTY 阶段按执行策略选用不同收敛链(`common::runner``default_cold_chain` /
`default_seed_chain`)。
**冷启动链**(适用 20-40K 及大部分易收敛点):
| 阶段 | 程序 | 做什么 | NITER | 典型耗时 |
|------|------|--------|-------|---------|
| 1. LTE 灰大气 | tlusty | `lte=T, ltgray=T` 模式,解析求灰色 T(τ) 结构 | 0 | 1-3 秒 |
| 2. ncNLTE 连续谱)| tlusty | `lte=F, ltgray=F` + `ilvlin=0`,收敛电离平衡(无线跃迁)| **10** | 1-5 分钟 |
| 3. nlNLTE 含线)| tlusty | `lte=F, ltgray=F` + `ilvlin=100`,加全部谱线跃迁(要求收敛)| 100 | 5-20 分钟 |
| 4. synspec | synspec | 用 nl 大气合成可观测光谱 | — | 3-10 秒 |
**阶段间依赖**:1→2→3→4 严格顺序。每阶段用上一阶段的 `.7` 大气作种子(fort.8)。
> 冷启动链在高温/He-poor/富金属区会发散,此时按策略链回退走 **种子步进链**
> seed_nc→nl,跳过 LTE grey 直接热启动)。
### 为什么是这 4 个阶段(原理)
Tlusty 的 NLTE 求解用**迭代线性化**complete linearization)。线性化的收敛半径
有限——当初猜离真解太远时迭代发散。四个阶段逐步缩小初猜与真解的差距:
- **阶段1LTE 灰大气)**:LTE + 灰色不透明度假设下解析求温度结构。提供物理
合理的起点,不需要种子(从零开始)。
- **阶段2(nc 连续谱)**:切换到 NLTE,但 `ilvlin=0` 不含束缚-束缚线跃迁。
线跃迁是统计平衡方程中最敏感的非线性项;先不加线,只收敛电离平衡(光致电离
+复合),得到稳定的 NLTE 布居数结构。**跳过此步直接 grey→含线 NLTE 必发散。**
- **阶段3(nl 含线)**:加入全部线跃迁(ilvlin=100)。从已收敛的 nc 种子起步,
线扰动小,快速收敛(典型 ~15 次迭代)。
- **阶段4synspec)**:用 nl 阶段收敛的大气模型,计算指定波长范围的合成光谱。
### 2.1 种子步进链(seed_step)—— 高温区破局
当冷启动链发散时(典型:60000-80000K + He-poor + 富金属),策略链回退切换到
**种子步进链**`default_seed_chain`),跳过 LTE grey 冷启动,直接用邻居已收敛的
`.7` 热启动:
| 阶段 | 做什么 | 关键标志 | NITER |
|------|--------|---------|-------|
| seed_nc | 从种子热启动 NLTE 连续谱 | `ltgray=F`(读 fort.8) + `ichang=0` | 20 |
| nl | 含谱线完整 NLTE(要求收敛)| `ilvlin=100` | 100 |
**触发流程**(服务端策略链机制,非节点端自行判断):
1. 初始派发链首策略(如 `cold_run`);失败上报(`failed_stage="tlusty"`)后,
服务端 `trigger_strategy_fallback` 弹出链首,重置点 pending,再派发下一顺位。
2. 若下一顺位为 `seed_step`:服务端在全局种子库按**有向 CNO 距离**找最近邻收敛种子,
注入 `seed_point_name` 后派发;无种子则暂存顺位待种子出现。
3. 节点凭 `seed_point_name` 下载种子 `.7``GET /api/seed/<name>`),作 fort.8 热启动跑
`seed_nc → nl`
**原理**`tlusty208.f` 源码确认,详见 EXPERIENCE.md):
- `LTGREY=T``CALL LTEGR` 生成灰大气(**忽略 fort.8**,冷启动);
- `LTGREY=F``CALL INPMOD` 读 fort.8 作初猜(**热启动**)。
- `ICHANG=0`:种子与目标模型原子完全一致(同为 H/He/CNO 设置),只改
Teff/logg/丰度,不需要重映射布居数。
> **物理极限(如实标注)**80000K + He-poor + logCNO=-1 即使种子步进也发散
> (CNO 高价离子主导不透明度,金属 ×10 跳跃线性化无法阻尼)。网格如实标
> `converged=false`,不强制成功——这些极端参数组合观测上本就罕见。
>
> **ICRSW 是死代码**:源码中 `SUBROUTINE SWITCH` 定义但从未被 CALL
> 默认 CRSW≡1.0 无阻尼效果。不要依赖 ICRSW 稳定化(见 §7.8)。
---
## 3. 每阶段的配置信息与原理
### 3.1 `.5` 文件(每阶段一份,三阶段相同 NATOMS/ions,只改 3 处)
```
第1行: TEFF GRAV (三阶段相同:目标参数)
第2行: LTE LTGRAY (阶段1=T T,阶段2/3=F F;种子步进链全 F)
第3行: nst 文件名 (固定写 'nst',内容每阶段由 nst_writer 生成)
第4行: NFREAD =2000 → 展开约 75443 个频率点;不要用 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/seed_nc: ilvlin=0; 阶段3/nl: ilvlin=100 ← 关键区别)
```
**为什么 NATOMS/ions 三阶段必须相同**:每阶段的 `.7` 大气记录了每个能级的
布居数。种子与目标的能级结构必须一一对应,否则读取时索引错位 → NaN。
### 3.2 nst 文件(非标准参数,每阶段统一结构,NITER 不同)
`nst_writer::generate_nst_content` 对**所有阶段(含 lte)统一生成两行**:
```
ND=50,NLAMBD=3,VTB=2.,ISPODF=1,DDNU=50.,CNU1=6.,[CHMAX=..][,ITEK=..],NITER=<阶段>
[ORELAX=..][,IDLTE=..][,IACC=..][,ICHANG=..],IELCOR=-1
```
- lte: `NITER=0`(灰大气不迭代,只做一次形式解)
- nc: `NITER=10`, nl: `NITER=100`, seed_nc: `NITER=20`
- **不设 CHMAX**(用默认 0.001,强迫 nc 真正收敛)
- **不设 ITEK**(用默认 4
- 尾部统一 `IELCOR=-1`(电子密度修正关闭)
- 参数分行写(line1 ≤64c, line2 余下),避免 tlusty nst 解析器 ~72 字符行宽截断
每个参数的作用与原理:
| 参数 | 作用 | 为什么这样设 |
|------|------|-------------|
| `ND` | 大气深度点数 | 50(sdB 标准配置) |
| `NLAMBD` | lambda 迭代频率点数 | 3(频率网格细化,用户验证配方) |
| `VTB` | 微湍流速度 km/s | 2.sdB 典型值) |
| `ISPODF` | 频率网格开关 | 1(启用细化频率网格) |
| `DDNU` | 频率间隔因子 | 50.(频率网格细化参数) |
| `CNU1` | 频率网格起点 | 6.(频率网格细化参数) |
| `NITER` | 最大迭代数 | nc=10(实测最优),nl=100seed_nc=20 |
| `IELCOR` | 电子密度修正 | -1(关闭) |
> **关键:不设 CHMAX(用默认 0.001)、不设 ITEK(用默认 4)、不设 IDLTE/ORELAX。**
> 之前版本设了 CHMAX=0.1 导致 nc 没真正收敛,是大部分失败的根本原因。
> 详见 EXPERIENCE.md §4 的错误记录。
>
> **nc 的 NITER=10 是实测最优**GUIDE.md NITER 扫描结论):
> - nc(纯连续谱)缺少谱线约束,外层温度永不真正收敛,只在外层漂移;
> - NITER=10 vs NITER=50 的最终光谱差异仅 6e-6(完全等价);
> - nl(含谱线)会自修正到正确解,无论 nc 给什么初值;
> - NITER=10 总耗时 ~12 分钟(35000K CNO),NITER=50 浪费 2.2× 时间。
### 3.3 synspec 配置(fort.55 + 谱线表)
```
fort.55 控制卡: 波长窗/展宽/截断(由 workflow YAML 的 `synspec_input:` 块或代码默认配置)
谱线表 fort.19: data 下 gf 谱线数据(含 C/N/O 线)
```
- 代码默认波长窗为 **14001410 Å**`SynspecInput` 默认),实际使用通常配置到
目标波段(如 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 如何做到并行(分布式 Pull 模型)
并行由 **多计算节点 + 每节点多槽位** 构成:
- **节点**:每个物理设备跑一个 `node` 进程,`DCTS_MAX_SLOTS` 控制该节点并发槽位数
(管理员可经心跳配额动态下调)。
- **队列**:服务端把 pending 点推入 MQWorker 只要 `active_slots < effective_max_slots`
就持续 `claim` 抢占(pull 模型,天然负载均衡)。
- **沙盒隔离**:每任务独立工作目录 `data/work/task_<id>/`fort.* 文件互不冲突,
这是并行安全的基础。
- 早期 Python 单机 `multiprocessing.Pool` 架构已废弃。
### 4.3 吞吐量估算(参考)
| 模型类型 | 单点耗时 | 参考吞吐(若干节点) | 收敛性 |
|---------|---------|-------------|--------|
| 20000-40000K(标准 sdB 区)| ~12-25 分钟 | 随节点槽位总数线性扩展 | 冷启动全区间可靠 |
| 60000K + He-rich/低金属 | ~10-15 分钟 | 同上 | 冷启动或一步种子步进 |
| 80000K + He-rich/低金属 | ~3-5 分钟(种子步进)| 同上 | 冷启动失败→种子步进成功 |
| 80000K + He-poor + logCNO=-1 | — | — | **真实物理极限,发散**(如实标注) |
---
## 5. 如何统计每阶段信息
### 5.1 当前已记录的信息(conv.json
每个网格点完成后,`result_dir/<模型名>/conv.json`(节点归档)与
`seeds_dir/<模型名>/conv.json`(服务端)记录:
```json
{
"name": "t40000_g6.0_he0_c-1_n-1_o-1",
"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": 715.0, 总耗时(所有阶段+synspec之和)
"seed": null, 冷启动为 null;走种子步进时为邻居 .7 路径
"stages": [
{
"label": "lte",
"converged": true,
"max_relc": 0.0,
"note": "NITER=0 grey start",
"elapsed_sec": 2.1
},
{
"label": "nc",
"converged": false, nc 不要求收敛,作种子即可(NITER=10)
"max_relc": 0.957,
"worst_depth": 1, "last_iter": 10, "n_depths": 50,
"elapsed_sec": 62.4
},
{
"label": "nl",
"converged": true,
"max_relc": 0.0069,
"worst_depth": 1, "last_iter": 17, "n_depths": 50,
"elapsed_sec": 7.8
}
]
}
```
> **走种子步进链时**`seed` 指向邻居 `.7``stages` 里没有 `lte`,而是
> `seed_nc``ltgray=F` 热启动,NITER=20)→ `nl`。
> 注意:`conv.json` 不含 `seed_step_used` / `coldfail_backup` 布尔字段(旧版字段已移除);
> 收敛手段归因由服务端 `tasks` 表的 `tlusty_strategies[0]`cold_run / seed_step)聚合统计
> Phase 6 起 `task_type` 列已删除,策略链首项即当前执行策略的权威快照)。
每阶段记录:`converged`(是否收敛)、`max_relc`(最大相对变化)、
`worst_depth`(最差深度点)、`last_iter`(迭代次数)、`n_depths`(深度点数)、
`elapsed_sec`(本阶段耗时)。
### 5.2 每阶段时间记录(已实现)
runner 在每个阶段的循环开始/结束处计时,conv.json 里每个 stage 有
`elapsed_sec`synspec 也有单独的 `synspec_sec`。另归档保留各阶段快照:
`<name>.<label>.5/.6/.err/.nst/.7` 与收敛诊断 `<name>.<label>_chmax*.9`(见
`tlusty_result_artifacts.md`)。
### 5.3 统计整个网格的信息
早期 `grid_status.json` 文件已废弃;当前状态统计由服务端 API 实时查询 DB 提供:
- `GET /api/workflows`:各工作流内联进度(total/completed/failed/running…,`completed` 由 7c 改名自 `converged`
- `GET /api/workflows/:name/stats`:单工作流统计(含 `cold_run_converged` / `seed_step_converged`
- `GET /api/workflows/:name/points`:逐点列表(可过滤 status/method/wave/q,分页)
- `GET /api/workflows/:name/progress`:进度-时间序列曲线
- 种子步进命中数 = `tasks``json_extract(tlusty_strategies,'$[0]')='seed_step'``status='completed'` 的聚合
前端 Dashboard 直接消费以上端点渲染(首页卡片、详情页概览/点表/平行集合分析图)。
### 5.4 单个网格点的详细收敛诊断
- 从节点 `result_dir/<模型>/` 读取 `<模型>.<label>_chmax*.9`(每阶段收敛诊断,
含最大相对变化随迭代下降过程)。
- 阶段日志 `<模型>.<label>.6/.err` 查看 tlusty 输出与报错。
- 光谱 `*.spec` / 连续谱 `*.cont` / b 因子 `*.bfac` / 出射谱 `*.emflux` 供物理分析。
---
## 6. 完整操作步骤
### 第1步:配置工作流(workflows/<wf>.yaml 的 grid 段)
```yaml
grid:
teff: [20000, 30000, 40000, 60000] # 各维采样点列表
logg: [5.0, 6.0]
loghe: [-2, 0]
logc: [-4, -2, -1] # 亚太阳范围(已修正;旧版 -1..1 会发散)
logn: [-4, -2, -1]
logo: [-4, -2, -1]
# 共 4*2*2*3*3*3 = 432 个点
tlusty_stage: # TLUSTY 阶段独立配置(enabled/policy/strategies
enabled: true
policy: skip_converged
strategies: ["cold_run", "seed_step"] # 策略链:冷启动失败回退种子步进
synspec_stage: # SYNSPEC 阶段独立配置(enabled/policy/strategies
enabled: true
policy: skip_converged
strategies: ["standard"]
synspec_input: # SYNSPEC 数值参数(fort.55 波长范围等)
wstart: 3000.0
wend: 7000.0
```
### 第2步:创建并启动工作流(HTTP API / Dashboard
```bash
# 创建/保存工作流(config_yaml 上传)
curl -X POST localhost:8090/api/workflows -H "Authorization: Bearer $ADMIN_TOKEN" \
-F name=sdB_cno -F config_yaml=@workflows/sdB_cno.yaml
# 启动(进入 initializing → running,后台异步建网格并派发)
curl -X POST localhost:8090/api/workflows/sdB_cno/start \
-H "Authorization: Bearer $ADMIN_TOKEN"
```
也可直接在 Dashboard「工作流 → 启动配置 → 启动」操作。启动后调度器按 wave(难度)
顺序把点推入 MQ,各节点 pull 抢占计算。
### 第3步:监控
- Dashboard:工作流卡片进度条、详情页概览(统计/进度曲线/ETA)、逐点表、平行集合分析图。
- API`GET /api/workflows/:name/stats` / `:name/points` / `:name/progress`
- 服务端日志:`RUST_LOG=info ./server`
### 第4步:断点续算 / 增量
- 工作流默认 `policy: skip_converged`:启动时跳过已收敛点、重试已失败点。
- 加密网格:往 `grid` 各维列表加更多点后保存并重启工作流(自动跳过已完成,只算新点)。
- 高温区加密建议先冷启动算 He-rich/低金属的"桥头堡"模型,建立种子库再扩散到
难收敛点(见 §7.1)。
### 单点调试
```bash
# 把单个点置回 pending 重跑(或在 Dashboard 详情页查看该点尝试历史/阶段诊断)
curl -X POST localhost:8090/api/workflows/sdB_cno/points \
-H "Authorization: Bearer $ADMIN_TOKEN" -F point_name=t40000_...
```
> 早期 Python 的 `run_grid.py` / `run_one.py` / `seed_step.py` 脚本与 `src/` 目录已废弃。
---
## 7. 注意事项
1. **种子步进回退(核心机制)**`tlusty_strategies` 链含 `seed_step` 时(默认推荐
`["cold_run", "seed_step"]`),冷启动失败的点会自动回退到种子步进链:服务端在全局
种子库找近邻已收敛 `.7``ltgray=F + ichang=0` 热启动重跑。这是高温/He-poor/富金属区的
决定性破局手段(详见 §2.1)。
**种子匹配**:两级匹配——exact_family(同 Teff/logg/logHe 族,CNO 用有向距离:富方向
重罚 4×、贫方向 1×)与 global 加权距离(`d_teff/5000 + d_logg×2 + d_loghe×0.5 +
d_cno×0.1 ≤ 3.0`),不再是早期简单的绝对跨度阈值(见 `seed_finder.rs` / `design.md §3`)。
跨度过大(如 cno-4 直接跳 cno-1,1000× 金属跳跃)即便种子步进也发散 → 这是真实物理
极限,网格如实标注。
**种子库建立策略**:高温区建议先冷启动算 He-rich 或低金属的"桥头堡"模型,建立种子库
再扩散到难收敛点(如 cno-4 → cno-2 → cno-1 多步跳板)。
2. **断点续算判定**`conv.json``converged=true` 的点会被跳过。假收敛
`atmosphere_has_nan=true`)的点会被重算。
3. **失败处理**:单点失败(发散/崩溃)不中断整个网格;当前策略失败触发策略链回退,
链耗尽则标记 failed。高温难点的失败大多是真实物理极限(见 §2.1),不强制成功。
4. **磁盘空间**:节点端归档 `result_dir` 永久保留(无 LRU 淘汰),每个模型约
2-10MB 白名单产物(`.spec/.cont/.iden/.7/.bfac/.emflux/各阶段快照/日志`)。沙盒
`task_*` 工作目录结算后清理,节点重启时清理残留。
5. **并行安全**:每任务独立沙盒(`data/work/task_<id>/`),fort.* 文件不冲突。可安全并行。
6. **nst 文件行长限制(已修复)**tlusty 的 nst 解析器有 ~72 字符的行宽限制。
如果参数太多写在一行(如加了 IDLTE/IACC 后 >72 字符),行尾的参数会被
静默截断。`nst_writer` 把参数分两行写(line1≤64c, line2 余下参数)。
这是个隐蔽 bug——截断后 tlusty 不报错而是用默认值,导致"看似成功实则参数
没生效"。
7. **fort.84 残留(已修复)**:tlusty 运行时会在工作目录写 fort.84(nst 参数的
内部表示)。如果下一次 tlusty 运行(不同 NATOMS)读到旧的 fort.84,会报
"Bad integer for item 48" 崩溃。runner 每次运行前删除 fort.84。
8. **收敛可靠性(如实)**:用正确配方(无 CHMAX/ITEK, NFREAD=2000, nc NITER=10
后,20000-40000K 全区间冷启动可靠收敛;60000-80000K + He-rich/低金属用
种子步进可解决;**80000K + He-poor + logCNO=-1 是真实物理极限**
(CNO 高价离子主导不透明度,金属 ×10 跳跃线性化无法阻尼),网格如实标记
未收敛。之前版本的"8/8 边界全部成功"不准确(基于错误的 CHMAX=0.1)。
`ICRSW`Hummer & Voels 切换)在 tlusty208 **原版中是死代码**
SUBROUTINE SWITCH 从未被 CALLCRSW≡1.0),不要依赖它稳定化;
即便源码修复启用后实测对难点也无帮助。详见 EXPERIENCE.md。