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

19 KiB
Raw Blame History

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 §2task_engine_decoupling_design.md §4.2


2. 每个网格点的计算阶段

每个网格点(一组 Teff/logg/logHe/logC/logN/logO 参数)经过 TLUSTY 大气求解 + SYNSPEC 光谱合成。TLUSTY 阶段按执行策略选用不同收敛链(common::runnerdefault_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 + 灰色不透明度假设下解析求温度结构。提供物理 合理的起点,不需要种子(从零开始)。
  • 阶段2nc 连续谱):切换到 NLTE,但 ilvlin=0 不含束缚-束缚线跃迁。 线跃迁是统计平衡方程中最敏感的非线性项;先不加线,只收敛电离平衡(光致电离 +复合),得到稳定的 NLTE 布居数结构。跳过此步直接 grey→含线 NLTE 必发散。
  • 阶段3nl 含线):加入全部线跃迁(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 下载种子 .7GET /api/seed/<name>),作 fort.8 热启动跑 seed_nc → nl

原理tlusty208.f 源码确认,详见 EXPERIENCE.md):

  • LTGREY=TCALL LTEGR 生成灰大气(忽略 fort.8,冷启动);
  • LTGREY=FCALL 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 大气深度点数 50sdB 标准配置)
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(服务端)记录:

{
  "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 指向邻居 .7stages 里没有 lte,而是 seed_ncltgray=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_secsynspec 也有单独的 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:进度-时间序列曲线
  • 种子步进命中数 = tasksjson_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/.yaml 的 grid 段)

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

# 创建/保存工作流(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)、逐点表、平行集合分析图。
  • APIGET /api/workflows/:name/stats / :name/points / :name/progress
  • 服务端日志:RUST_LOG=info ./server

第4步:断点续算 / 增量

  • 工作流默认 policy: skip_converged:启动时跳过已收敛点、重试已失败点。
  • 加密网格:往 grid 各维列表加更多点后保存并重启工作流(自动跳过已完成,只算新点)。
  • 高温区加密建议先冷启动算 He-rich/低金属的"桥头堡"模型,建立种子库再扩散到 难收敛点(见 §7.1)。

单点调试

# 把单个点置回 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"]),冷启动失败的点会自动回退到种子步进链:服务端在全局 种子库找近邻已收敛 .7ltgray=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.jsonconverged=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)。 ICRSWHummer & Voels 切换)在 tlusty208 原版中是死代码 SUBROUTINE SWITCH 从未被 CALLCRSW≡1.0),不要依赖它稳定化; 即便源码修复启用后实测对难点也无帮助。详见 EXPERIENCE.md。