feat(all): 任务引擎双阶段解耦、僵尸涡旋修复、动态 CPU 配额与前端详情页重构
将 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 笔误
This commit is contained in:
+167
-247
@@ -2,56 +2,56 @@
|
||||
|
||||
> 本文档说明完整理论光谱网格的计算流程:每个网格点的计算阶段、每阶段的配置、
|
||||
> 配置原理、CPU/并行机制、以及如何统计每个阶段的信息(时间、收敛等)。
|
||||
> 当前实现为 **Rust 分布式 C/S + MQ 架构**(非早期 Python 单机脚本),调度与阶段配置
|
||||
> 详见 `architecture.md` / `task_engine_decoupling_design.md`。
|
||||
|
||||
---
|
||||
|
||||
## 1. 总体架构
|
||||
|
||||
```
|
||||
config.yaml (网格点 + 收敛链配置)
|
||||
workflows/<wf>.yaml (网格点 + 阶段独立配置 + 策略链)
|
||||
│
|
||||
▼
|
||||
run_grid.py ── 生成 6 维笛卡尔积参数点
|
||||
│ 断点续算(跳过已成功) / 种子复用(最近邻) /
|
||||
│ 失败隔离 / 冷启动失败→种子步进回退
|
||||
│
|
||||
├── worker 1 ── run_one.py ── 点 A
|
||||
├── worker 2 ── run_one.py ── 点 B 每个 worker 独立工作目录
|
||||
├── ... 互不干扰,并行(默认16核)
|
||||
└── worker N ── run_one.py ── 点 X
|
||||
│
|
||||
▼
|
||||
冷启动链(lte→nc→nl) + synspec
|
||||
│ 冷启动发散且有干净邻居种子?
|
||||
▼ → 移失败结果到 <model>.coldfail/,
|
||||
种子步进链(seed_nc→nl) + synspec 改用 LTGRAY=F 热启动重试
|
||||
│
|
||||
▼
|
||||
results/<模型名>/
|
||||
conv.json ← 阶段信息(收敛/迭代/时间/seed_step_used)
|
||||
*.spec/.cont ← 光谱
|
||||
*.7 ← 各阶段大气
|
||||
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 参数)经过 **4 个阶段**。
|
||||
每个网格点(一组 Teff/logg/logHe/logC/logN/logO 参数)经过 **TLUSTY 大气求解 + SYNSPEC
|
||||
光谱合成**。TLUSTY 阶段按执行策略选用不同收敛链(`common::runner` 的 `default_cold_chain` /
|
||||
`default_seed_chain`)。
|
||||
|
||||
**默认走"冷启动链"**(DEFAULT_CHAIN,适用 20-40K 及大部分易收敛点):
|
||||
**冷启动链**(适用 20-40K 及大部分易收敛点):
|
||||
|
||||
| 阶段 | 程序 | 做什么 | NITER | 典型耗时 |
|
||||
|------|------|--------|-------|---------|
|
||||
| 1. LTE 灰大气 | tlusty | `T T` 模式,解析求灰色 T(τ) 结构 | 0 | 1-3 秒 |
|
||||
| 2. nc(NLTE 连续谱)| tlusty | `F F` + `ilvlin=0`,收敛电离平衡(无线跃迁)| **10** | 1-5 分钟 |
|
||||
| 3. nl(NLTE 含线)| tlusty | `F F` + `ilvlin=100`,加全部谱线跃迁(要求收敛)| 100 | 5-20 分钟 |
|
||||
| 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/富金属区会发散,此时 run_grid 自动改走下文 §2.1
|
||||
> 的**种子步进链**(seed_nc→nl,跳过 LTE grey 直接热启动)。
|
||||
> 冷启动链在高温/He-poor/富金属区会发散,此时按策略链回退走 **种子步进链**
|
||||
> (seed_nc→nl,跳过 LTE grey 直接热启动)。
|
||||
|
||||
### 为什么是这 4 个阶段(原理)
|
||||
|
||||
@@ -67,33 +67,31 @@ Tlusty 的 NLTE 求解用**迭代线性化**(complete linearization)。线
|
||||
线扰动小,快速收敛(典型 ~15 次迭代)。
|
||||
- **阶段4(synspec)**:用 nl 阶段收敛的大气模型,计算指定波长范围的合成光谱。
|
||||
|
||||
### 2.1 种子步进链(seed_step)—— 高温区破局(NEW)
|
||||
### 2.1 种子步进链(seed_step)—— 高温区破局
|
||||
|
||||
当冷启动链发散时(典型:60000-80000K + He-poor + 富金属),run_grid 自动
|
||||
切换到**种子步进链**(`seed_step.SEED_STEP_CHAIN`),跳过 LTE grey 冷启动,
|
||||
直接用邻居已收敛的 `.7` 热启动:
|
||||
当冷启动链发散时(典型:60000-80000K + He-poor + 富金属),策略链回退切换到
|
||||
**种子步进链**(`default_seed_chain`),跳过 LTE grey 冷启动,直接用邻居已收敛的
|
||||
`.7` 热启动:
|
||||
|
||||
| 阶段 | 做什么 | 关键标志 | NITER |
|
||||
|------|--------|---------|-------|
|
||||
| seed_nc | 从种子热启动 NLTE 连续谱 | `LTGRAY=F`(读 fort.8) + `ICHANG=0` | 80 |
|
||||
| seed_nc | 从种子热启动 NLTE 连续谱 | `ltgray=F`(读 fort.8) + `ichang=0` | 20 |
|
||||
| nl | 含谱线完整 NLTE(要求收敛)| `ilvlin=100` | 100 |
|
||||
|
||||
**触发流程**(`run_grid.py` 的 `_worker`):
|
||||
1. 先跑冷启动链(DEFAULT_CHAIN)。
|
||||
2. 若 `converged=false` 且 `seed_step_fallback=true` 且找到了干净邻居种子:
|
||||
- 把失败结果整体移到 `results/<model>.coldfail/`(避免污染种子库);
|
||||
- 用 `SEED_STEP_CHAIN`(`seed=<邻居>.7`)重算;
|
||||
- conv.json 里记 `seed_step_used=true`、`coldfail_backup=<路径>`。
|
||||
**触发流程**(服务端策略链机制,非节点端自行判断):
|
||||
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 §5Y):
|
||||
**原理**(`tlusty208.f` 源码确认,详见 EXPERIENCE.md):
|
||||
- `LTGREY=T` → `CALL LTEGR` 生成灰大气(**忽略 fort.8**,冷启动);
|
||||
- `LTGREY=F` → `CALL INPMOD` 读 fort.8 作初猜(**热启动**)。
|
||||
- `ICHANG=0`:种子与目标模型原子完全一致(同为 H/He/CNO 设置),只改
|
||||
Teff/logg/丰度,不需要重映射布居数。
|
||||
|
||||
**实测突破**(80K + He-poor + logCNO=-4,冷启动必败点):种子步进 126s 收敛
|
||||
(冷启动 970s 发散到 NaN)。详见 EXPERIENCE.md §5Y 验证表。
|
||||
|
||||
> **物理极限(如实标注)**:80000K + He-poor + logCNO=-1 即使种子步进也发散
|
||||
> (CNO 高价离子主导不透明度,金属 ×10 跳跃线性化无法阻尼)。网格如实标
|
||||
> `converged=false`,不强制成功——这些极端参数组合观测上本就罕见。
|
||||
@@ -110,7 +108,7 @@ Tlusty 的 NLTE 求解用**迭代线性化**(complete linearization)。线
|
||||
```
|
||||
第1行: TEFF GRAV (三阶段相同:目标参数)
|
||||
第2行: LTE LTGRAY (阶段1=T T,阶段2/3=F F;种子步进链全 F)
|
||||
第3行: nst 文件名 (固定写 'nst',内容每阶段由 write_nst 生成)
|
||||
第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)
|
||||
@@ -121,35 +119,32 @@ ions段: iat iz nlevs ilast ilvlin nonstd typion filei
|
||||
**为什么 NATOMS/ions 三阶段必须相同**:每阶段的 `.7` 大气记录了每个能级的
|
||||
布居数。种子与目标的能级结构必须一一对应,否则读取时索引错位 → NaN。
|
||||
|
||||
### 3.2 nst 文件(非标准参数,每阶段不同)
|
||||
### 3.2 nst 文件(非标准参数,每阶段统一结构,NITER 不同)
|
||||
|
||||
**阶段1(LTE 灰大气)—— 保持干净,不加稳定化参数**:
|
||||
`nst_writer::generate_nst_content` 对**所有阶段(含 lte)统一生成两行**:
|
||||
```
|
||||
ND=50,VTB=2.,NITER=0
|
||||
ND=50,NLAMBD=3,VTB=2.,ISPODF=1,DDNU=50.,CNU1=6.,[CHMAX=..][,ITEK=..],NITER=<阶段>
|
||||
[ORELAX=..][,IDLTE=..][,IACC=..][,ICHANG=..],IELCOR=-1
|
||||
```
|
||||
- `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=10, nl: NITER=100
|
||||
- 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 字符行宽截断
|
||||
|
||||
每个参数的作用与原理:
|
||||
|
||||
| 参数 | 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` | **10** | 100 | 最大迭代数 | nc 给 10 次足够(实测最优);nl 给 100 次 |
|
||||
| `IELCOR` | -1 | -1 | 电子密度修正 | 关闭 |
|
||||
| 参数 | 作用 | 为什么这样设 |
|
||||
|------|------|-------------|
|
||||
| `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 没真正收敛,是大部分失败的根本原因。
|
||||
@@ -161,13 +156,14 @@ IELCOR=-1
|
||||
> - nl(含谱线)会自修正到正确解,无论 nc 给什么初值;
|
||||
> - NITER=10 总耗时 ~12 分钟(35000K CNO),NITER=50 浪费 2.2× 时间。
|
||||
|
||||
### 3.3 synspec 配置(fort.55.lin + 谱线表)
|
||||
### 3.3 synspec 配置(fort.55 + 谱线表)
|
||||
|
||||
```
|
||||
fort.55.lin 第6行: WLMIN WLMAX WLSTEP ... CUTOFF ...
|
||||
谱线表 fort.19: data/gfVIS99.dat (含 C 1412 / N 2396 / O 1885 条线)
|
||||
fort.55 控制卡: 波长窗/展宽/截断(由 workflow YAML 的 synspec: 块或代码默认配置)
|
||||
谱线表 fort.19: data 下 gf 谱线数据(含 C/N/O 线)
|
||||
```
|
||||
- 当前用 3000-7000Å(光学波段,覆盖 C II 4267、C III 4647 等)。
|
||||
- 代码默认波长窗为 **1400–1410 Å**(`SynspecConfig` 默认),实际使用通常配置到
|
||||
目标波段(如 3000-7000Å 光学波段,覆盖 C II 4267、C III 4647 等)。
|
||||
- 大气来自 nl 阶段的 `.7`(复制为 fort.8)。
|
||||
|
||||
---
|
||||
@@ -177,47 +173,38 @@ fort.55.lin 第6行: WLMIN WLMAX WLSTEP ... CUTOFF ...
|
||||
### 4.1 每个网格点只用一个 CPU 核
|
||||
|
||||
**是的。** tlusty.exe 和 synspec.exe 是 Fortran 编译的单线程程序,每个实例只用
|
||||
1 个 CPU 核。网格点的并行不是靠程序内部的多线程,而是靠**同时启动多个程序实例**。
|
||||
1 个 CPU 核。网格点的并行不是靠程序内部的多线程,而是靠**同时启动多个程序实例**
|
||||
(分布在多台计算节点上)。
|
||||
|
||||
### 4.2 如何做到并行
|
||||
### 4.2 如何做到并行(分布式 Pull 模型)
|
||||
|
||||
`run_grid.py` 用 Python 的 `multiprocessing.Pool`(`run_grid.py` 的 `_worker`):
|
||||
并行由 **多计算节点 + 每节点多槽位** 构成:
|
||||
|
||||
```python
|
||||
with Pool(nworkers) as pool:
|
||||
for res in pool.imap_unordered(_worker, worker_args):
|
||||
...
|
||||
```
|
||||
- **节点**:每个物理设备跑一个 `node` 进程,`DCTS_MAX_SLOTS` 控制该节点并发槽位数
|
||||
(管理员可经心跳配额动态下调)。
|
||||
- **队列**:服务端把 pending 点推入 MQ,Worker 只要 `active_slots < effective_max_slots`
|
||||
就持续 `claim` 抢占(pull 模型,天然负载均衡)。
|
||||
- **沙盒隔离**:每任务独立工作目录 `data/work/task_<id>/`,fort.* 文件互不冲突,
|
||||
这是并行安全的基础。
|
||||
- 早期 Python 单机 `multiprocessing.Pool` 架构已废弃。
|
||||
|
||||
- `nworkers`(config.yaml,当前=**16**):同时运行的 worker 进程数。
|
||||
- 每个 worker 是一个独立的 Python 子进程,调用 `run_one.py` 跑一个网格点
|
||||
(在独立的工作目录里,互不干扰)。
|
||||
- `imap_unordered`:哪个点先完成就先回收,立即分配下一个点(动态负载均衡)。
|
||||
- 16 核机器跑 16 个 worker = 16 个 tlusty 实例同时跑 = 满载利用。
|
||||
(按机器核数调整;每个 tlusty 运行是单线程的,调大 nworkers 即可吃更多核。)
|
||||
### 4.3 吞吐量估算(参考)
|
||||
|
||||
**关键:每个 worker 用独立工作目录**(`results/<模型名>/`),避免 fort.* 文件
|
||||
冲突。这是并行安全的基础。
|
||||
|
||||
### 4.3 吞吐量估算
|
||||
|
||||
| 模型类型 | 单点耗时 | 16核并行吞吐 | 收敛性 |
|
||||
| 模型类型 | 单点耗时 | 参考吞吐(若干节点) | 收敛性 |
|
||||
|---------|---------|-------------|--------|
|
||||
| 20000-40000K(标准 sdB 区)| ~12-25 分钟 | ~48-80 点/小时 | 冷启动全区间可靠 |
|
||||
| 60000K + He-rich/低金属 | ~10-15 分钟 | ~64-96 点/小时 | 冷启动或一步种子步进 |
|
||||
| 80000K + He-rich/低金属 | ~3-5 分钟(种子步进)| 约同上 | 冷启动失败→种子步进成功 |
|
||||
| 20000-40000K(标准 sdB 区)| ~12-25 分钟 | 随节点槽位总数线性扩展 | 冷启动全区间可靠 |
|
||||
| 60000K + He-rich/低金属 | ~10-15 分钟 | 同上 | 冷启动或一步种子步进 |
|
||||
| 80000K + He-rich/低金属 | ~3-5 分钟(种子步进)| 同上 | 冷启动失败→种子步进成功 |
|
||||
| 80000K + He-poor + logCNO=-1 | — | — | **真实物理极限,发散**(如实标注) |
|
||||
|
||||
当前 config.yaml 共 432 点(4×2×2×3×3*3);中等参数区冷启动为主,
|
||||
高温区走种子步进回退,整体约需数小时到一天。
|
||||
|
||||
---
|
||||
|
||||
## 5. 如何统计每阶段信息
|
||||
|
||||
### 5.1 当前已记录的信息(conv.json)
|
||||
|
||||
每个网格点完成后,`results/<模型名>/conv.json` 记录:
|
||||
每个网格点完成后,`result_dir/<模型名>/conv.json`(节点归档)与
|
||||
`seeds_dir/<模型名>/conv.json`(服务端)记录:
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -229,33 +216,36 @@ with Pool(nworkers) as pool:
|
||||
"synspec_rc": 0,
|
||||
"elapsed_sec": 715.0, ← 总耗时(所有阶段+synspec之和)
|
||||
"seed": null, ← 冷启动为 null;走种子步进时为邻居 .7 路径
|
||||
"seed_step_used": false, ← true=种子步进链跑成功的(含 coldfail_backup 路径)
|
||||
"stages": [
|
||||
{
|
||||
"label": "lte",
|
||||
"converged": true,
|
||||
"final": {"itek":null, "rc":0, "max_relc":0.0,
|
||||
"note":"NITER=0 grey start (no iterations)"}
|
||||
"max_relc": 0.0,
|
||||
"note": "NITER=0 grey start",
|
||||
"elapsed_sec": 2.1
|
||||
},
|
||||
{
|
||||
"label": "nc",
|
||||
"converged": false, ← nc 不要求收敛,作种子即可(NITER=10)
|
||||
"final": {"itek":null, "rc":0, "max_relc":0.957,
|
||||
"worst_depth":1, "last_iter":10, "n_depths":50}
|
||||
"max_relc": 0.957,
|
||||
"worst_depth": 1, "last_iter": 10, "n_depths": 50,
|
||||
"elapsed_sec": 62.4
|
||||
},
|
||||
{
|
||||
"label": "nl",
|
||||
"converged": true,
|
||||
"final": {"itek":null, "rc":0, "max_relc":0.0069,
|
||||
"worst_depth":1, "last_iter":17, "n_depths":50}
|
||||
"max_relc": 0.0069,
|
||||
"worst_depth": 1, "last_iter": 17, "n_depths": 50,
|
||||
"elapsed_sec": 7.8
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
> **走种子步进链时**:`seed` 指向邻居 `.7`,`seed_step_used=true`,
|
||||
> `coldfail_backup` 指向 `<model>.coldfail/`,`stages` 里没有 `lte`,而是
|
||||
> `seed_nc`(LTGRAY=F 热启动,NITER=80)→ `nl`。
|
||||
> **走种子步进链时**:`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`(深度点数)、
|
||||
@@ -263,90 +253,35 @@ with Pool(nworkers) as pool:
|
||||
|
||||
### 5.2 每阶段时间记录(已实现)
|
||||
|
||||
`run_one.py` 现在在每个阶段的循环开始/结束处计时,conv.json 里每个 stage 有
|
||||
`elapsed_sec`,synspec 也有单独的 `synspec_sec`:
|
||||
|
||||
```json
|
||||
"stages": [
|
||||
{"label":"lte", "elapsed_sec": 2.1, "converged":true, ...},
|
||||
{"label":"nc", "elapsed_sec": 62.4, "converged":false, ...},
|
||||
{"label":"nl", "elapsed_sec": 7.8, "converged":true, ...}
|
||||
],
|
||||
"synspec_sec": 3.1,
|
||||
"elapsed_sec": 75.4
|
||||
```
|
||||
|
||||
统计所有模型的阶段时间分布:
|
||||
```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']))
|
||||
"
|
||||
```
|
||||
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 统计整个网格的信息
|
||||
|
||||
`run_grid.py` 完成后写 `results/grid_status.json`:
|
||||
早期 `grid_status.json` 文件已废弃;当前状态统计由服务端 API 实时查询 DB 提供:
|
||||
|
||||
```json
|
||||
{
|
||||
"total": 432,
|
||||
"elapsed_sec": 36000,
|
||||
"counts": {"converged": 400, "unfinished": 18, "error": 3, "skipped": 11},
|
||||
"seed_step_retries": 47,
|
||||
"models": [
|
||||
{"name":"t20000_...", "status":"converged", "max_relc":0.0065,
|
||||
"seed_step_used": false},
|
||||
{"name":"t80000_...", "status":"converged", "max_relc":0.00091,
|
||||
"seed_step_used": true},
|
||||
...
|
||||
]
|
||||
}
|
||||
```
|
||||
- `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'` 的聚合
|
||||
|
||||
汇总统计命令:
|
||||
```bash
|
||||
# 成功率 + 种子步进命中数
|
||||
python3 -c "import json; j=json.load(open('results/grid_status.json')); print(j['counts'], 'seed_step_retries=', j['seed_step_retries'])"
|
||||
# 所有收敛模型的 max_relc 分布(标注是否走了种子步进)
|
||||
python3 -c "
|
||||
import json,glob
|
||||
for f in sorted(glob.glob('results/*/conv.json')):
|
||||
j=json.load(open(f))
|
||||
if j['converged']:
|
||||
tag='SEED' if j.get('seed_step_used') else 'cold'
|
||||
print(j['name'], tag, 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',''))
|
||||
"
|
||||
```
|
||||
前端 Dashboard 直接消费以上端点渲染(首页卡片、详情页概览/点表/平行集合分析图)。
|
||||
|
||||
### 5.4 单个网格点的详细收敛诊断
|
||||
|
||||
```bash
|
||||
# 看某阶段的迭代收敛趋势(fort.9)
|
||||
python3 src/check_conv.py results/<模型>/<模型>.nl.9 --chmax 0.01
|
||||
|
||||
# 画光谱(标出 CNO 诊断线位置)
|
||||
python3 src/plot_spec.py results/<模型>
|
||||
```
|
||||
- 从节点 `result_dir/<模型>/` 读取 `<模型>.<label>_chmax*.9`(每阶段收敛诊断,
|
||||
含最大相对变化随迭代下降过程)。
|
||||
- 阶段日志 `<模型>.<label>.6/.err` 查看 tlusty 输出与报错。
|
||||
- 光谱 `*.spec` / 连续谱 `*.cont` / b 因子 `*.bfac` / 出射谱 `*.emflux` 供物理分析。
|
||||
|
||||
---
|
||||
|
||||
## 6. 完整操作步骤
|
||||
|
||||
### 第1步:配置网格密度(config.yaml 的 grid 段)
|
||||
### 第1步:配置工作流(workflows/<wf>.yaml 的 grid 段)
|
||||
```yaml
|
||||
grid:
|
||||
teff: [20000, 30000, 40000, 60000] # 各维采样点列表
|
||||
@@ -356,100 +291,85 @@ grid:
|
||||
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步:设置环境变量
|
||||
### 第2步:创建并启动工作流(HTTP API / Dashboard)
|
||||
```bash
|
||||
export TLUSTY=/home/dckj/program/tlusty/tl208-s54
|
||||
```
|
||||
# 创建/保存工作流(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
|
||||
|
||||
### 第3步:预览(dry-run)
|
||||
# 启动(进入 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
|
||||
cd $TLUSTY/cno_grid
|
||||
python3 src/run_grid.py config.yaml --dry-run
|
||||
# 输出:grid: 432 points total, N already done, M to compute
|
||||
# 把单个点置回 pending 重跑(或在 Dashboard 详情页查看该点尝试历史/阶段诊断)
|
||||
curl -X POST localhost:8090/api/workflows/sdB_cno/points \
|
||||
-H "Authorization: Bearer $ADMIN_TOKEN" -F point_name=t40000_...
|
||||
```
|
||||
|
||||
### 第4步:启动批量计算(后台并行)
|
||||
```bash
|
||||
nohup python3 src/run_grid.py config.yaml > results/grid_run.log 2>&1 &
|
||||
# 冷启动失败的点会自动尝试种子步进回退(seed_step_fallback: true)。
|
||||
# 想关闭回退:在 config.yaml 设 seed_step_fallback: false。
|
||||
```
|
||||
|
||||
### 第5步:监控
|
||||
```bash
|
||||
tail -f results/grid_run.log # 实时进度
|
||||
grep seed_step results/grid_run.log # 看哪些点走了种子步进
|
||||
cat results/grid_status.json # 汇总(完成后才有)
|
||||
```
|
||||
|
||||
### 第6步:断点续算(中断后恢复,自动跳过已成功的)
|
||||
```bash
|
||||
python3 src/run_grid.py config.yaml # 重跑同一命令即可
|
||||
```
|
||||
|
||||
### 第7步:检查结果 + 画图
|
||||
```bash
|
||||
# 成功率 + 种子步进命中数
|
||||
python3 -c "import json;j=json.load(open('results/grid_status.json'));print(j['counts'],'seed_step=',j['seed_step_retries'])"
|
||||
# 画某个模型光谱
|
||||
python3 src/plot_spec.py results/<模型名>
|
||||
```
|
||||
|
||||
### 单点调试(不走批量)
|
||||
```bash
|
||||
# 冷启动单点(适用 20-40K 大部分点)
|
||||
python3 src/run_one.py --teff 40000 --logg 6.0 --loghe 0 --logc -1 --logn -1 --logo -1
|
||||
# 种子步进单点(高温 He-poor 等冷启动失败点)
|
||||
python3 src/seed_step.py --teff 80000 --logg 6.5 --loghe -4 \
|
||||
--logc -4 --logn -4 --logo -4 \
|
||||
--seed results/<seed-model>/<seed-model>.7
|
||||
```
|
||||
|
||||
### 第8步:加密网格(Phase 2)
|
||||
在 config.yaml 的各维列表里加更多点,重跑 `run_grid.py`(自动跳过已完成的,
|
||||
只算新点)。高温区加密时建议先冷启动算 He-rich/低金属的"桥头堡"模型,
|
||||
建立种子库再扩散到难收敛点(见 §7.1)。
|
||||
> 早期 Python 的 `run_grid.py` / `run_one.py` / `seed_step.py` 脚本与 `src/` 目录已废弃。
|
||||
|
||||
---
|
||||
|
||||
## 7. 注意事项
|
||||
|
||||
1. **种子步进回退(核心机制)**:`seed_step_fallback: true`(默认开)时,
|
||||
冷启动失败的点会自动改走种子步进链(`seed_step.SEED_STEP_CHAIN`):
|
||||
把失败结果备份到 `<model>.coldfail/`,用 `find_seed` 找最近邻已收敛的 `.7`
|
||||
作种子,`LTGRAY=F + ICHANG=0` 热启动重跑。这是高温/He-poor/富金属区的
|
||||
1. **种子步进回退(核心机制)**:`tlusty_strategies` 链含 `seed_step` 时(默认推荐
|
||||
`["cold_run", "seed_step"]`),冷启动失败的点会自动回退到种子步进链:服务端在全局
|
||||
种子库找近邻已收敛 `.7`,`ltgray=F + ichang=0` 热启动重跑。这是高温/He-poor/富金属区的
|
||||
决定性破局手段(详见 §2.1)。
|
||||
**种子跨度限制**:种子与目标的参数差不能太大(logg ≤0.5/步,
|
||||
Teff ≤5000K/步;logCNO 一步 ≤100×)。跨度过大(如 cno-4 直接跳 cno-1,
|
||||
1000× 金属跳跃)即便种子步进也发散 → 这是真实物理极限,网格如实标注。
|
||||
**种子库建立策略**:高温区建议先冷启动算 He-rich 或低金属的"桥头堡"
|
||||
模型,建立种子库再扩散到难收敛点(如 cno-4 → cno-2 → cno-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)的点会被重算。
|
||||
(`atmosphere_has_nan=true`)的点会被重算。
|
||||
|
||||
3. **失败隔离**:单点失败(发散/崩溃)不中断整个网格,记入 grid_status.json
|
||||
的 error/unfinished 列表。高温难点的失败大多是真实物理极限(见 §2.1),
|
||||
不强制成功;如确需重试,可用 `seed_step.py` 手动从更近的种子起跳。
|
||||
3. **失败处理**:单点失败(发散/崩溃)不中断整个网格;当前策略失败触发策略链回退,
|
||||
链耗尽则标记 failed。高温难点的失败大多是真实物理极限(见 §2.1),不强制成功。
|
||||
|
||||
4. **磁盘空间**:每个模型约 50-100MB(含中间文件,走种子步进的点还会留
|
||||
`<model>.coldfail/` 备份)。432 点约需 20-40GB。如不够可定期清理中间文件
|
||||
(保留 .spec/.cont/.7/conv.json)。
|
||||
4. **磁盘空间**:节点端归档 `result_dir` 永久保留(无 LRU 淘汰),每个模型约
|
||||
2-10MB 白名单产物(`.spec/.cont/.iden/.7/.bfac/.emflux/各阶段快照/日志`)。沙盒
|
||||
`task_*` 工作目录结算后清理,节点重启时清理残留。
|
||||
|
||||
5. **并行安全**:每个 worker 用独立工作目录(results/<模型名>/),fort.* 文件
|
||||
不冲突。可安全并行。
|
||||
5. **并行安全**:每任务独立沙盒(`data/work/task_<id>/`),fort.* 文件不冲突。可安全并行。
|
||||
|
||||
6. **nst 文件行长限制(已修复)**:tlusty 的 nst 解析器有 ~72 字符的行宽限制。
|
||||
如果参数太多写在一行(如加了 IDLTE/IACC 后 >72 字符),行尾的参数会被
|
||||
静默截断。`write_nst()` 现在把参数分两行写(line1≤64c, line2 余下参数)。
|
||||
静默截断。`nst_writer` 把参数分两行写(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。
|
||||
"Bad integer for item 48" 崩溃。runner 每次运行前删除 fort.84。
|
||||
|
||||
8. **收敛可靠性(如实)**:用正确配方(无 CHMAX/ITEK, NFREAD=2000, nc NITER=10)
|
||||
后,20000-40000K 全区间冷启动可靠收敛;60000-80000K + He-rich/低金属用
|
||||
@@ -458,4 +378,4 @@ python3 src/seed_step.py --teff 80000 --logg 6.5 --loghe -4 \
|
||||
未收敛。之前版本的"8/8 边界全部成功"不准确(基于错误的 CHMAX=0.1)。
|
||||
`ICRSW`(Hummer & Voels 切换)在 tlusty208 **原版中是死代码**
|
||||
(SUBROUTINE SWITCH 从未被 CALL,CRSW≡1.0),不要依赖它稳定化;
|
||||
即便源码修复启用后实测对难点也无帮助。详见 EXPERIENCE.md §5Y。
|
||||
即便源码修复启用后实测对难点也无帮助。详见 EXPERIENCE.md。
|
||||
|
||||
Reference in New Issue
Block a user