将 TLUSTY/SYNSPEC 拆为各自独立的 enabled/policy/strategies 阶段,
以策略链自动弹栈取代单级 seed_step 布尔回退;定向修复 2026-08-02
僵尸任务涡旋事故;新增节点并发配额热调;前端详情页从 1412 行巨型
视图拆为薄控制器 + detail 子模块,并补齐工具层与单测。
引擎与调度(task_engine_decoupling_design.md)
- models.rs: 新增 StagePolicy / EngineStageConfig / TaskSpec 阶段字段、
normalize_compat() 校正旧版在途消息策略链、failed_stage 归因
- scheduler.rs: resolve_dispatchable_chain 派发门控、
trigger_strategy_fallback 按 failed_stage 精确弹栈;启动期
force_recompute/skip_converged(默认)/skip_failed 三策略
- db.rs: tasks 表 +7 列持久化阶段配置;终态守卫
(mark_grid_point_running 仅 pending/queued→running;
record_task_report 拒绝迟到失败翻黑 converged);策略弹栈快照
僵尸涡旋修复(runbook-20260802-zombie-vortex-fix.md)
- 全链路跨库活性交叉校验:派发/claim/孤儿回收/回退统一查 MQ 队列活性,
活则放行、死则清僵尸,结构性消除"每点重复派发"
- stop/重启卫生:清队列同步 delete_tasks_by_ids,杜绝遗留 pending 行
- report_task: 幂等吸收 + 409 区分迟到冗余结果,仅 state_changed 时回退
- MQ: NULL workflow_name 回填 __legacy__、requeue 后迟到上报被 403 竞态修复
动态 CPU 配额(dynamic_cpu_slots_design.md)
- admin.rs: POST /admin/nodes/:id/quota(Option<Option<i32>> 区分
缺字段/显式 null);nodes 表 +admin_max_slots
- worker.rs: effective_max_slots = min(admin, physical),心跳下发原子生效
科学产物保全(tlusty_result_artifacts.md)
- runner.rs: SYNSPEC 启动前快照 fort.12/fort.14 → .bfac/.emflux 防覆盖
- 半失败点(大气收敛+光谱失败)改判 Failed 并写入 note;仅 SYNSPEC
场景不再恒判失败;撤销归档 LRU 200 上限改为永久保留
- executor.rs: 透传 synspec_params 数值参数(此前固定 None)
前端(dashboard/)
- workflowDetail.js 1412→328 行,拆出 views/detail/{ctx,overview,
pointsTable,parSets,pointPanel}.js,AbortController 治理监听/请求生命周期
- 删除 wfActions.js,新增 wfEnginePanel.js(双阶段三维配置编辑面板)
- 新增 utils/{errors,format,icons,polling,yamlStage}.js 纯函数模块
- 路由级动态 import 代码分割;节点配额三点菜单 + Modal 管理
- 首次引入 node:test 单测(format/polling/yamlStage/psCache,644 行)
- 系统性补齐 a11y:skip-link、ARIA、Tab 键盘漫游、toast 关闭、退出动画
文档与工具
- 新增 6 篇设计/调研:引擎解耦、动态配额、涡旋 runbook、
光谱正确性分析、收敛判断、产物归档
- PIPELINE/design/api/database 等协同重写为分布式 C/S 架构口径
- scripts/fetch_results.sh 跨节点产物备份;import_results 按 cno 升序导入
- workflows/sdB_cno.yaml: 新增 tlusty/synspec_stage 配置块,修正 wstart 笔误
19 KiB
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 (SQLite,pending/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. nc(NLTE 连续谱) | tlusty | lte=F, ltgray=F + ilvlin=0,收敛电离平衡(无线跃迁) |
10 | 1-5 分钟 |
| 3. nl(NLTE 含线) | 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)。线性化的收敛半径 有限——当初猜离真解太远时迭代发散。四个阶段逐步缩小初猜与真解的差距:
- 阶段1(LTE 灰大气):LTE + 灰色不透明度假设下解析求温度结构。提供物理 合理的起点,不需要种子(从零开始)。
- 阶段2(nc 连续谱):切换到 NLTE,但
ilvlin=0不含束缚-束缚线跃迁。 线跃迁是统计平衡方程中最敏感的非线性项;先不加线,只收敛电离平衡(光致电离 +复合),得到稳定的 NLTE 布居数结构。跳过此步直接 grey→含线 NLTE 必发散。 - 阶段3(nl 含线):加入全部线跃迁(ilvlin=100)。从已收敛的 nc 种子起步, 线扰动小,快速收敛(典型 ~15 次迭代)。
- 阶段4(synspec):用 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 |
触发流程(服务端策略链机制,非节点端自行判断):
- 初始派发链首策略(如
cold_run);失败上报(failed_stage="tlusty")后, 服务端trigger_strategy_fallback弹出链首,重置点 pending,再派发下一顺位。 - 若下一顺位为
seed_step:服务端在全局种子库按有向 CNO 距离找最近邻收敛种子, 注入seed_point_name后派发;无种子则暂存顺位待种子出现。 - 节点凭
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=100,seed_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: 块或代码默认配置)
谱线表 fort.19: data 下 gf 谱线数据(含 C/N/O 线)
- 代码默认波长窗为 1400–1410 Å(
SynspecConfig默认),实际使用通常配置到 目标波段(如 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 点推入 MQ,Worker 只要
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指向邻居.7,stages里没有lte,而是seed_nc(ltgray=F热启动,NITER=20)→nl。 注意:conv.json不含seed_step_used/coldfail_backup布尔字段(旧版字段已移除); 收敛手段归因由服务端tasks表的task_type(cold_run / seed_step)聚合统计。
每阶段记录: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/converged/failed/running…)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表task_type='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:
enabled: true
policy: skip_converged
strategies: ["cold_run", "seed_step"] # 策略链:冷启动失败回退种子步进
synspec:
enabled: true
wstart: 3000
wend: 7000
第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)、逐点表、平行集合分析图。
- API:
GET /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. 注意事项
-
种子步进回退(核心机制):
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 多步跳板)。 -
断点续算判定:
conv.json里converged=true的点会被跳过。假收敛 (atmosphere_has_nan=true)的点会被重算。 -
失败处理:单点失败(发散/崩溃)不中断整个网格;当前策略失败触发策略链回退, 链耗尽则标记 failed。高温难点的失败大多是真实物理极限(见 §2.1),不强制成功。
-
磁盘空间:节点端归档
result_dir永久保留(无 LRU 淘汰),每个模型约 2-10MB 白名单产物(.spec/.cont/.iden/.7/.bfac/.emflux/各阶段快照/日志)。沙盒task_*工作目录结算后清理,节点重启时清理残留。 -
并行安全:每任务独立沙盒(
data/work/task_<id>/),fort.* 文件不冲突。可安全并行。 -
nst 文件行长限制(已修复):tlusty 的 nst 解析器有 ~72 字符的行宽限制。 如果参数太多写在一行(如加了 IDLTE/IACC 后 >72 字符),行尾的参数会被 静默截断。
nst_writer把参数分两行写(line1≤64c, line2 余下参数)。 这是个隐蔽 bug——截断后 tlusty 不报错而是用默认值,导致"看似成功实则参数 没生效"。 -
fort.84 残留(已修复):tlusty 运行时会在工作目录写 fort.84(nst 参数的 内部表示)。如果下一次 tlusty 运行(不同 NATOMS)读到旧的 fort.84,会报 "Bad integer for item 48" 崩溃。runner 每次运行前删除 fort.84。
-
收敛可靠性(如实):用正确配方(无 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 从未被 CALL,CRSW≡1.0),不要依赖它稳定化; 即便源码修复启用后实测对难点也无帮助。详见 EXPERIENCE.md。