feat(all): 重炼 crates/common 核心组件、上线 Web 运维看板与 Docker 容器化部署

This commit is contained in:
fmq
2026-07-28 10:31:57 +08:00
commit 4b4238d702
71 changed files with 163402 additions and 0 deletions
+761
View File
@@ -0,0 +1,761 @@
# CNO 热亚矮星光谱:完整调试经验与原理文档
> 本文档完整记录在 `tl208-s54/cno_grid/` 上构建含 C/N/O 金属线的热亚矮星理论
> 光谱过程中,**所有测试、遇到的问题、根因分析、解决方法和底层物理/数值原理**。
>
> **重要**:本文档经过了多轮调试验证。之前版本的"8/8 边界全部成功"等结论
> **不准确**(基于错误的 CHMAX=0.1 配置)。本版本如实记录了最终确认的结果。
---
## 1. 核心结论(先读这一节)
1. **金属线必须进大气模型(方案B,自洽)。** 纯 H+He 大气下 synspec 的 C/N/O
谱线全 NaN——大气里没有金属能级,无法计算谱线不透明度。
2. **收敛必须走三步法,nc 步骤不可省略:**
```
LTE 灰大气 (T T, NITER=0) → 初始温度结构
NLTE 连续谱 (F F, ilvlin=0, "nc") → 收敛电离平衡(无线跃迁)
NLTE 含线 (F F, ilvlin=100, "nl")→ 加谱线
SYNSPEC → 合成光谱
```
3. **正确的 nst 配方(用户 tests.zip 验证):**
- **不设 CHMAX**(用 tlusty 默认 0.001)— 这是最关键的点
- **不设 ITEK**(用默认 4
- **He `.5` nlevs=14**(数据文件本身 24/20 能级,但 `.5` 声明 14 让 tlusty 截断;
不是 Peter 建议的满 24-level)—— 详见 §2.5/§3.3
- **NFREAD=2000**(展开成 75443 频率点(tests/sdB_spectra/GUIDE.md 实测),快且稳定)
- nst: `ND=50,NLAMBD=3,VTB=2.,ISPODF=1,DDNU=50.,CNU1=6.,NITER=<阶段>` + `IELCOR=-1`
4. **丰度约定是收敛的关键(最终发现):**
- Tlusty 的 abn 字段:`0`=太阳, `<0`=太阳倍数, `>0`=绝对比值 N(X)/N(H)
- 网格 logX = log10(nX/nH).5 的 abn = 10^logX
- **logC/N/O 范围必须是物理合理的**:-4 到 -1(太阳约在 -3.6
- 之前的 logC=0 意味着 C/H=1.0(太阳的 4000 倍)→ nc 发散。这不是高温问题!
5. **验证状态(修正丰度范围后,已与 conv.json 复核):**
- ✅ 35000/5.5/logHe=-2/logCNO=-1nc max_relc=0.00148(未达 0.001,但作为种子
被接受),nl 收敛到 0.000633,耗时 1635ssynspec 3.6s
- ✅ 40000/5.5/logHe=0/logCNO=-1C/H=0.1):nc=0.000424, nl=0.000905, 1298s
---
## 2. 底层原理
### 2.1 为什么金属必须进大气
Tlusty 求解每个离子的每个能级布居数(统计平衡方程)。synspec 合成光谱时需要
谱线跃迁涉及的两个能级的布居数来计算线不透明度。纯 H+He 大气没有 CNO 能级 →
synspec 遇到 CNO 线时无法获取布居数 → 线不透明度 = NaN。
### 2.2 为什么需要三步法
Tlusty 的 NLTE 求解用迭代线性化(complete linearization)。线性化的收敛半径
有限——初猜离真解太远时迭代发散。三步法逐步缩小差距:
- **LTE 灰大气**:解析求 T(τ) 结构,提供物理合理的起点
- **ncilvlin=0**:切换 NLTE 但不含线跃迁。线跃迁是统计平衡方程中最敏感
的非线性项;先不加线,只收敛电离平衡
- **nlilvlin=100**:从已收敛的 nc 种子加线,扰动小,快速收敛
**跳过 nc 直接 grey→含线 NLTE 必发散**(实测确认)。
### 2.3 CHMAX 为什么必须用默认的 0.001
CHMAX 是收敛限(各深度最大相对变化)。我最初从 bstar 抄了 CHMAX=0.1(宽松),
导致 nc 在 max_relc=0.1 就停止——但此时大气结构还没真正收敛,布居数仍远离
NLTE 解 → nl 接手后不稳定。
用默认 CHMAX=0.001 强迫 nc 真正收敛到 0.1% 精度,给 nl 一个准确的种子。
**这是整个调试中最关键的发现。**
### 2.4 NFREAD 与频率网格
NFREAD 是 `.5` 里的"基本频率点数",tlusty 据此自动展开成实际频率网格:
- **NFREAD=2000** → 75443 个频率点(实测,见 tests/sdB_spectra/GUIDE.md
- **NFREAD=50** → 77695 个频率点(NFREAD=50 反而更多,因 tlusty 对小 NFREAD 触发更细的自动细化)(慢 15 倍,且 nc 不稳定)
NFREAD 小反而展开更多——因为 tlusty 对小 NFREAD 触发更细的自动细化。
**必须用 NFREAD=2000。**
### 2.5 He 能级数:14 vs 24
Peter Nemeth 邮件建议用 24-level He I + 20-level He II(这恰好是 `he1.dat`/
`he2.dat` 数据文件本身的能级数)。但用户 tests.zip 实际验证成功的配置,是
在 `.5` 里把 He I/II 的 **nlevs 显式声明为 14**tlusty 按此截断数据文件,
只读前 14 个能级)。
满 24-level 在 nc 阶段(即使 ilvlin=0)引入更多连续谱跃迁(光致电离/复合),
增加 NLTE 线性化的维度和不稳定性。`.5` 声明 nlevs=14 是用户验证过的稳定配置。
> 注 1Peter 的建议针对 He-rich 模型的**光谱精度**(更多 He 线),不是针对
> nc 收敛稳定性。两者目标不同。
>
> 注 2:数据文件 `he1.dat`/`he2.dat` 本身仍是 24/20 能级(不需改动),只是
> `.5` 里声明 nlevs=14 让 tlusty 截断使用。详见 §3.3 表格。
### 2.6 丰度约定(最终发现的关键)
Tlusty 的 `.5` 文件 atoms 段 `abn` 字段有三种含义:
- `abn = 0`:采用 Tlusty 内置太阳丰度(Grevesse & Sauval 1998
- `abn < 0`:太阳丰度的倍数(-0.1 = 0.1×太阳,-5 = 5×太阳)
- `abn > 0`**绝对数密度比** N(X)/N(H)
本网格用 `logX = log10(nX/nH)`(绝对比值),所以 `.5` 里 `abn = 10^logX`。
**太阳丰度参考值**log10(nX/nH)):
| 元素 | 太阳 logX | 太阳 nX/nH |
|------|-----------|------------|
| He | -1.07 | 0.0851 |
| C | -3.61 | 2.45e-4 |
| N | -4.22 | 6.03e-5 |
| O | -3.34 | 4.57e-4 |
**网格范围必须是物理合理的**。用户最初设 logC/N/O = -2 到 1,但:
- logC = 0 → C/H = 1.0(太阳的 **4000 倍**!)
- logC = 1 → C/H = 10(碳比氢多,物理上几乎不可能)
- 太阳 logC ≈ -3.6 **不在原范围内**
实测确认:logC = 0C/H=1.0)时 nc 发散——金属不透明度主导大气结构,NLTE
线性化不稳定。改为 logC/N/O = -4 到 -1(覆盖太阳到 sdB 观测富金属端)后,
40000K 可靠收敛(nc=0.0004, nl=0.0009)。
**之前所有"40000K 高温发散"的结论是错误的——根因是丰度过高,不是高温。**
---
## 3. 已验证的精确配方
### 3.1 `.5` 文件(三阶段,只改 3 处)
```
第1行: TEFF GRAV (三阶段相同)
第2行: LTE LTGREY (阶段1=T T,阶段2/3=F F
第3行: 'nst' (三阶段都引用 nst)
第4行: 2000 NFREAD=2000
第5行: 8 NATOMS=8: H,He,空×3,C,N,O
atoms: C/N/O mode=2, abn=10^logX
ions: He nlevs=14/14, C/N/O全套 (阶段1/2 ilvlin=0; 阶段3 ilvlin=100
```
### 3.2 nst 文件(三阶段统一,只改 NITER)
**LTE 阶段**
```
ND=50,VTB=2.,NITER=0
```
**nc 和 nl 阶段**(关键:无 CHMAX/ITEK):
```
ND=50,NLAMBD=3,VTB=2.,ISPODF=1,DDNU=50.,CNU1=6.,NITER=<阶段>
IELCOR=-1
```
- nc: NITER=10tests/sdB_spectra/GUIDE.md NITER 扫描实测最优;vs NITER=50
光谱差异仅 6e-6 但快 2.2×。nc 纯连续谱缺少谱线约束,外层永不真正收敛,
追求高 NITER 无意义,nl 会自修正)
- nl: NITER=100
- **不设 CHMAX**(用默认 0.001)、**不设 ITEK**(用默认 4
### 3.3 CNO 模型原子(`.5` 里声明的 nlevs
> 下表"nlevs"列是 `.5` 文件里每个离子实际声明的 NLTE 能级数(即 `gen_input5.py`
> 的 `_IONS_*` 元组第三项),也就是 tlusty 真正会读入和求解的能级数。
> 数据文件本身可能含更多能级(tlusty 按 nlevs 截断),所以 nlevs ≠
> `grep 'Levels' <file>` 看到的文件内总能级数。这点之前文档里混淆过。
| 元素 | 离子 / `.5` 声明 nlevs / 数据文件(文件实际能级数)|
|------|---------------------------------------------------|
| He | He I **14** `he1.dat`(24) / He II **14** `he2.dat`(20) / He III 1 |
| C | C I 40 `c1.dat` / C II 22 `c2.dat` / C III **46** `c3_34+12lev.dat`(46) / C IV 25 `c4.dat` / C V 1 |
| N | N I 34 `n1.dat` / N II **42** `n2_32+10lev.dat`(42) / N III 32 `n3.dat` / N IV **48** `n4_34+14lev.dat`(48) / N V 16 `n5.dat` / N VI 1 |
| O | O I **33** `o1_23+10lev.dat`(33) / O II **48** `o2_36+12lev.dat`(48) / O III **41** `o3_28+13lev.dat`(41) / O IV 39 `o4.dat` / O V **6** `o5.dat`(40) / O VI 1 |
| H | H I 9 `h1.dat` |
注:
- He 的 `.5` nlevs=14 是用户 tests.zip 验证过的稳定配置(见 §2.5),但 `he1.dat`
本身含 24 能级、`he2.dat` 含 20 能级——tlusty 只读前 14 个。
- O V 的 `.5` nlevs=6 是截断值;`o5.dat` 文件本身含 40 能级。
- 之前文档写 "He I 14 `he1.dat`" 容易让人误以为文件就 14 能级,已澄清。
---
## 4. 调试过程中犯的错误(如实记录)
### 错误 1CHMAX=0.1(核心错误)
- **来源**:从 bstar 的 nst 抄来
- **影响**:nc 在 0.1 就停止,没真正收敛 → nl 不稳定 → 大部分点失败
- **纠正**:不设 CHMAX,用默认 0.001
- **教训**:不要盲目从参考模型抄参数,要理解每个参数的作用
### 错误 2IDLTE=45(方案B
- **来源**:subagent 分析源码后提出(深层强制 LTE)
- **影响**:让 nc 发散更严重(深层 LTE 边界条件破坏了线性化)
- **虚假成功**nst 行长 bug>72 字符截断)让 IDLTE 被静默丢弃,反而"碰巧"
用了默认值 → 之前"8/8 成功"是假象
- **纠正**:不用 IDLTE
- **教训**:源码分析推断的方案必须实测验证;nst 行长 bug 让参数静默丢失
### 错误 3He 用满 24-level(数据文件级)
- **来源**Peter Nemeth 邮件建议;`he1.dat` 本身就含 24 能级
- **影响**:增加 nc 的不稳定(更多连续谱跃迁进入线性化)
- **纠正**`.5` 里把 He I/II 的 nlevs 显式声明为 **14**tlusty 按此截断
`he1.dat`/`he2.dat`,只读前 14 个能级)—— 用户 tests.zip 验证的配置
- **教训**:专家建议针对的目标(光谱精度)可能和你的目标(收敛稳定性)不同。
注意区分"数据文件能级数"和"`.5` 声明的 nlevs"——前者是文件内容,后者才是
tlusty 实际求解的能级数。
### 错误 4NFREAD=50
- **来源**:从 hhe35lt(纯H+He)抄来
- **影响**:展开成 77695 频率点,慢 15 倍且不稳定
- **纠正**:用 NFREAD=200075443 点,实测)
- **教训**:NFREAD 的展开行为反直觉(小→多),必须实测确认
### 错误 5ORELAX=0.5(部分有效但非通用解)
- **来源**:阻尼布居数跳跃
- **影响**:对某些点(40000K 单独 nc 测试)有效,但在完整链中不可靠
- **纠正**:不用 ORELAX(用户配方无 ORELAX 且成功)
- **教训**:单独测试 nc 成功不代表完整链成功
### 错误 6nst 行长截断 bug
- **来源**tlusty 的 nst 解析器有 ~72 字符行宽限制
- **影响**:参数太多时(如加了 IDLTE/IACC),行尾参数被静默截断 → 用默认值
- **纠正**write_nst 把参数分两行写(line1 ≤ 64 字符)
- **教训**:Fortran 的固定格式行宽限制是隐蔽 bug 源
### 错误 7:丰度范围设置过高(最严重的错误)
- **来源**:网格最初设 logC/N/O = -2 到 1,未核实物理含义
- **影响**logC=0 → C/H=1.0(太阳 4000 倍),金属不透明度主导大气 → nc 发散。
之前所有"40000K+ 高温发散"的结论都源于此,**不是高温问题**。
- **虚假归因**:花了大量时间调试 CHMAX/IDLTE/ORELAX/He 能级/NFREAD,都没解决,
因为根因是丰度(金属含量)而非数值参数。
- **纠正**:改为 logC/N/O = -4 到 -1(物理合理范围,太阳在 -3.6 附近)
- **教训**:先核实输入参数的物理含义和量级,再调试数值方法。对比用户成功配置时
要逐行精确对比(用户用 abn=0 太阳丰度,我用 abn=1.0 绝对比值)。
### 错误 8:ICRSW 是死代码(本次会话发现 —— 后已修复并测试)
- **来源**:边界测试发现 80K + He-poor + logCNO=-1 即使种子步进也发散,
尝试用 ICRSWHummer & Voels 1988 碰撞-辐射开关)稳定化
- **影响**:源码 `tlusty208.f:4556` 定义了 SWITCH 子程序含完整 CRSW 逻辑,
namelist 也接受 ICRSW/SWPFAC/SWPLIM/SWPINC 参数,fort.6 也打印这些值,
看起来一切正常——但**整个源文件中没有任何一处 CALL SWITCH**。
- **实测验证**:开 ICRSW=1/SWPFAC=0.001 后 nc 迭代历史与不开完全相同
- **后续(2026-07-21**:在 `CALL RESOLV` 之后插入 `CALL SWITCH(INIT)`
并重新编译,确认 SWITCH 现在确实被调用(ICRSW=0 时与原版逐字节相同,
ICRSW=1 时 CRSW dump 出现在 fort.6 且 iter 1 尖峰被压低 4-5×)。
**但对 80K + He-poor + 富金属难点仍无帮助**(甚至更早发散到 NaN)。
默认管线仍用未修改的 `tlusty.exe`,详见 §6X
- **教训**:源码里的子程序未必被调用。看似可用的参数可能是死代码。
必须实测验证参数效果(对比开/关的迭代历史是否真的不同)。修复死代码
时要注意 INSERT 位置(本例放在 RESOLV 之前会在 iter 1 除以未初始化的
RRU,必须放在 RESOLV 之后)。即使修复"正确"也要再验证是否真能解决
目标问题。
---
## 5. 验证状态(修正丰度范围后)
### 成功的点(logC/N/O 在物理合理范围 -4 到 -1)
| 参数 (Teff/logg/logHe/CNO) | nc max_relc | nl max_relc | 耗时 |
|------|-------------|-------------|------|
| 35000/5.5/-2/logCNO=-1 | 0.00148(未达 0.001,作种子)| 0.000633 | 1635s |
| 40000/5.5/0/logCNO=-1 | 0.000424 | 0.000905 | 1298s |
> **⚠️ 耗时说明**:上表和 §5X/§5Y 的所有耗时都是用 **nc NITER=50**(旧 config
> 跑出来的,**不能用作网格耗时估算**。修正后用 **NITER=10**tests/sdB_spectra/GUIDE.md
> 实测最优),35000K CNO 单点总耗时 ~12 分钟。网格耗时估算应以 NITER=10 为准。
> nc max_relc 也是 NITER=50 时的中间值,NITER=10 时 nc 不会真正收敛(这是正常的,
> 见 §3.2),但 nl 最终解与 NITER=50 完全等价(流量差异 <3e-12)。
> 注:早期版本曾列入 "35000/5.5/-2/abn=0 (nl=0.00078, 1009s)" 和 "40000/5.5/0/abn=0
> (nc=6.67e-5)" 两个所谓"成功点"——经复核 conv.json,这两个数据**不存在**
> results/ 下既没有 abn=0 的对应模型目录,整库 grep 也没有 6.67e-5 这个值。
> 上述结论是凭空写入的,已删除。真实可复现的成功点如上表所示。
### 之前"失败"的点(logC/N/O 过高,C/H ≥ 1.0
| 参数 | 失败原因 | 真相 |
|------|---------|------|
| 40000/5.5/logCNO=0 | nc 发散 | C/H=1.0(太阳4000倍),金属不透明度主导 |
| 80000/6.5/logCNO=0 | nc 发散 | 同上,非高温问题 |
**结论**:用物理合理的丰度范围(logC/N/O = -4 到 -1),20000-40000K 可靠收敛。
之前的"高温发散"假象源于丰度范围设置过高。
---
## 5X. 8 点边界测试(2026-07-21
在修正丰度范围(logC/N/O = -4 到 -1)后,对网格边界做系统验证。
完整数据见 `cno_grid/results/bound_*.log` + `results/t*/conv.json`。
### 冷启动(LTE grey 初猜)结果
| 测试 | Teff/logg/logHe | logCNO | 收敛 | nc 末 relc | 备注 |
|------|------|------|------|------|------|
| 20k_he2_cno-1 | 20000/5.0/+2 | -1 | ✓ | 0.221 | nl 收敛到 6e-4,但 nc 走到 NITER=50 才勉强 |
| 20k_he2_cno-4 | 20000/5.0/+2 | -4 | ✓ | 13.9 | nc 末值高但 nl 顺利收敛 |
| 40k_he0_cno-4 | 40000/5.5/0 | -4 | ✓ | 0.00087 | 顺利 |
| **60k_he0_cno-1** | 60000/6.0/0 | -1 | ✗ | 2.31e18 | nc 发散 |
| **80k_he-4_cno-1** | 80000/6.5/-4 | -1 | ✗ | 2.68e17 | nc 发散 |
| **80k_he-4_cno-4** | 80000/6.5/-4 | -4 | ✗ | 3.92e6 | nc 发散(深 13|
| **80k_he2_cno-1** | 80000/6.5/+2 | -1 | ✗ | 1.01e17 | nc 发散 |
| 80k_he2_cno-4 | 80000/6.5/+2 | -4 | ✓ | 0.00031 | **唯一 80K 冷启动成功** |
### 模式分析
通过逐迭代看 nc 阶段的 max_relc 演化(`*.nc_*.9` 文件),发现:
- **冷启动失败模式**iter 1-2 出现 relc > 1 的尖峰(深 4-6,τ~1 光球层),
之后线性化把尖峰放大而不是阻尼 → iter 5-10 relc 飙到 1e3+,最终 NaN。
- **冷启动成功模式**(如 80k_he2_cno-4):iter 2 也出现 1.5 的尖峰,
但线性化阻尼住 → iter 5 回到 1e-2 → iter 10 < 1e-3 收敛。
- **关键差异**He 含量。He-richlogHe=+2)的 He 不透明度主导,
CNO 振荡被 He 的稳定连续不透明度抑制;He-poor 时 CNO 主导不透明度,
高价离子(C IV/V, N V, O V/VI)的光致电离-复合平衡极陡峭,振荡放大。
### 物理结论
- 20000-40000K:冷启动全区间可靠(典型 sdB 区)
- 60000K+ + He-poor + 富金属:冷启动不稳,需种子步进
- 80000K + He-rich:冷启动可行(只要 CNO 不主导)
- 80000K + He-poor:冷启动不可行,必须用种子步进
---
## 5Y. 种子步进法(seed-stepping)—— 高温区破局
### 源码分析关键发现(`tlusty208.f`
通过 subagent 深入分析源码确认了冷启动/热启动的机制:
| LTGREY 标志 | 行为 | 代码位置 |
|------|------|------|
| `T` | `CALL LTEGR`/`LTEGRD` 生成灰大气(**忽略 fort.8**| `tlusty208.f:981-982` |
| `F` | `CALL INPMOD` 从 fort.8 读已收敛大气作初猜 | `tlusty208.f:579`(在 `IF(.NOT.LTGREY)` 块 578-581 内)|
> 注:变量名是 **LTGREY**(英式拼写),不是 LTGRAY。源码 grep 确认。
`ICHANG` 控制模型原子变化时的布居数重映射(CALL 在 `tlusty208.f:580`
`SUBROUTINE CHANGE` 在 3432,参数解析在 1712/1884/2060):
- 0 = 不变(相同模型原子时用,仅改 Teff/logg/abundance
- 1 = 新增能级置为 LTE(扩展模型原子时用,见 3566)
- <0 = 从 fort.95 读完整旧模型定义(见 3503)
`ICRSW``tlusty208.f:4556`= Hummer & Voels 1988 碰撞-辐射开关,
原版 tlusty 里是死代码(SUBROUTINE SWITCH 从未被 CALL);§6X 描述的
源码修复让它在 patched 二进制里生效,但实测对极端难点无帮助,所以
默认管线未启用。详见错误 8 与 §6X。
### 种子步进实现
新增 `cno_grid/src/seed_step.py`,跳过 LTE grey 冷启动,
直接热启动 nc 阶段:
```python
SEED_STEP_CHAIN = [
# stage 1: 从种子大气热启动 NLTE 连续谱
{"label": "seed_nc", "lte": "F", "ltgray": "F", "ilvlin": 0,
"ichang": 0, "require_converged": False, "niter": 80},
# stage 2: 完整 NLTE + 谱线
{"label": "nl", "lte": "F", "ltgray": "F", "ilvlin": 100,
"ichang": 0, "require_converged": True, "niter": 100},
]
```
用法:
```bash
python3 cno_grid/src/seed_step.py --teff 80000 --logg 6.5 --loghe -4 \
--logc -4 --logn -4 --logo -4 \
--seed cno_grid/results/<seed-model>/<seed-model>.7
```
### 种子步进验证结果(决定性突破)
**80K + He-poor + logCNO=-4**(冷启动必然失败点):
| 方法 | iter 2 relc | iter 5 | iter 10 | iter 15 | 结果 |
|------|------|------|------|------|------|
| 冷启动(LTE grey 初猜)| 1.78e1 | 1.22e2 | 5.32e2 | 9.54e5 | **发散到 NaN** |
| **种子步进**80K He-rich 种子)| 2.49 | 2.02e-2 | 8.03e-4 | 4.36e-5 | **收敛 ✓** |
- 冷启动 970s 都没收敛(NaN)
- 种子步进 **126s 收敛**(其中 synspec 3.4s
- 大气本身 0% NaN,物理有效
### 已验证的种子步进成功点
| 目标 | 种子 | 结果 | 耗时 |
|------|------|------|------|
| 80000/6.5/-4/-4/-4/-4 | 80000/6.5/+2/-4/-4/-4/-4 | ✓ conv 0.00091 | 126s |
| 80000/6.5/+2/-1/-1/-1/-1 | 80000/6.5/+2/-4/-4/-4/-4 | ✓ conv 0.00025 | 276s |
| 80000/6.5/-4/-2/-2/-2/-2 | 80000/6.5/-4/-4/-4/-4/-4 | ✓ conv 0.00061 | 313s |
### 仍未解决的点
| 目标 | 尝试 | 结果 |
|------|------|------|
| 80000/6.5/-4/-1/-1/-1/-1 | 直接种子 (cno-4 → cno-1, 1000× 跳) | iter 6 NaN |
| 80000/6.5/-4/-1/-1/-1/-1 | 两步种子 (cno-4 → cno-2 → cno-1) | cno-2 → cno-1 iter 7 NaN |
| 80000/6.5/-4/-1/-1/-1/-1 | ORELAX=0.5 + 种子 (cno-2 → cno-1) | iter ~10 NaN (relc=5e38) |
| 60000/6.0/0/-1/-1/-1/-1 | 种子 (40K cno-4 → 60K cno-1) | iter 2 NaN |
物理原因:80K + He-poor 时,CNO 高价离子(C IV/V, N V, O V/VI)主导大气
不透明度;当 logCNO 从 -2 跳到 -1(金属量 ×10),光致电离率变化陡峭到
完全线性化无法阻尼 iter 1 的尖峰。
### 错误 8:ICRSW 是死代码(关键发现 —— 后已修复并测试)
源码分析后发现 `ICRSW`Hummer & Voels 1988 碰撞-辐射开关)在 tlusty208
**原版**中**实际不可用**
- `SWITCH` 子程序在 `tlusty208.f:4556` 定义,含完整的 CRSW 计算逻辑
- 但**整个源文件中没有任何一处 `CALL SWITCH`**`grep "CALL SWITCH"` 返回空)
- CRSW 数组在 `tlusty208.f:1812` 被默认初始化为 `UN`=1.0
- 因此 `tlusty208.f:6343-6344, 6591-6592` 等处的 `RRU/RRD * CRSW(ID)` 实际
乘的是 1.0,没有任何阻尼效果
- 同类的 CRSW 消费点共 12 处(含 `tlusty208.f:18201, 18252` 等),
全部因 CRSW≡1 而失效
测试验证(原版):开启 ICRSW=1/SWPFAC=0.001/SWPINC=2.0 后,nc 阶段的迭代历史
iter 1-25 relc 演化)与不开启 ICRSW **完全相同**——确认 SWITCH 未被调用。
**后续(2026-07-21):已实施修复并重新测试**。在主循环 `CALL RESOLV` 之后
插入 `CALL SWITCH(INIT)`(详见 §6X),重新编译后确认:
- ICRSW=0 时与原版逐字节相同(sanity 通过)
- ICRSW=1 时 SWITCH 确实被调用(CRSW dump 出现在 fort.6iter 1 尖峰被
压低 4-5×)
- **但对 80K + He-poor + logCNO=-1 难点没有帮助**(甚至更早发散到 NaN),
因此默认管线仍用未修改的 `tlusty.exe`。完整测试数据见 §6X。
**教训**:源码里的子程序未必被调用。看似可用的参数(ICRSW 在 nst namelist
里、在 fort.6 里也被打印)可能是死代码。修复死代码前要:(1) 实测验证参数
确实无效;(2) 仔细分析子程序依赖的变量何时被赋值(INSERT 位置很关键,
本例中放在 `RESOLV` 之前会在 iter 1 除以未初始化的 RRU);(3) 修复后
再实测验证是否真能改善目标问题——本例中修复"正确"但"无用"。真正对极端
金属跳跃有效的稳定化手段仍然是种子步进(减小模型间步长)。
### 种子步进的网格应用策略
1. **冷启动能搞定的点**:直接 `run_one.py`20000-40000K 大部分点)
2. **冷启动搞不定的点**:用 `seed_step.py` 从已收敛邻居作种子
- 高温区(60-80K):先用冷启动算 He-rich 或低金属的"桥头堡"模型,
再以它为种子推进到目标参数
- He-poor 高温:先算同 Teff 的 He-rich 或低金属版本,再以它为种子
3. **物理极限**80K + He-poor + logCNO=-1(金属 0.1×H)的组合,
即使用两步种子 + ORELAX 也无法收敛。这是真实物理极限,
网格在该角落如实标记为"未收敛"。
4. **run_grid.py 的种子策略**:扩展为"按邻居查找已收敛模型作种子",
失败则尝试中间丰度点作跳板。
---
## 6. 代码系统说明(`cno_grid/`
### 当前配置(已修正为用户原配方)
- `gen_input5.py`He `.5` nlevs=14(数据文件本身 24/20,按 nlevs 截断),
NFREAD=2000,支持 ilvlin/metals 参数
- `run_one.py` DEFAULT_CHAIN:三步法,无 CHMAX/ITEK/ORELAX
- write_nst 支持 ichang/orelax/idlte/iacc/icrsw(注意:原版 tlusty.exe
里 ICRSW 是死代码;§6X 描述的源码修复让它在 patched 二进制里生效,
但默认管线仍用原版 tlusty.exe
- `seed_step.py`:种子步进实现 —— 跳过 LTE grey 冷启动,
直接热启动 nc 阶段,用于高温/He-poor/富金属等冷启动失败的场景
- `run_grid.py`:6 维网格调度,集成种子步进回退(NEW)
- 冷启动失败时自动用 `find_seed` 找已收敛邻居,用 SEED_STEP_CHAIN 重试
- 失败的冷启动结果备份到 `<model>.coldfail/`,避免污染种子库
- `find_seed` 优先级:同 (Teff,logg,logHe) 最近 CNO → 全局最近邻
- `_atmos_clean` 过滤掉 NaN 污染的"假收敛"模型作种子
### 使用方法
```bash
export TLUSTY=/home/dckj/program/tlusty/tl208-s54
# 单个模型(冷启动,适用 20-40K 大部分点)
python3 cno_grid/src/run_one.py --teff 35000 --logg 5.5 --loghe -2 \
--logc -1 --logn -1 --logo -1
# 种子步进(高温 He-poor 等冷启动失败点)
python3 cno_grid/src/seed_step.py --teff 80000 --logg 6.5 --loghe -4 \
--logc -4 --logn -4 --logo -4 \
--seed cno_grid/results/<seed-model>/<seed-model>.7
# 批量网格(自动冷启动 + 失败时种子步进回退)
python3 cno_grid/src/run_grid.py cno_grid/config.yaml --dry-run # 预览
python3 cno_grid/src/run_grid.py cno_grid/config.yaml # 正式跑
```
### 网格运行策略
**桥头堡机制**(手动):在跑完整网格前,先在难收敛区附近算几个"桥头堡"
模型,建立种子库。例如高温区先算:
```bash
# 1. 算 80K He-rich cno-4(冷启动可成功)
python3 cno_grid/src/run_one.py --teff 80000 --logg 6.5 --loghe 2 \
--logc -4 --logn -4 --logo -4
# 2. 用它作种子算 80K He-poor cno-4(种子步进)
python3 cno_grid/src/seed_step.py --teff 80000 --logg 6.5 --loghe -4 \
--logc -4 --logn -4 --logo -4 \
--seed cno_grid/results/t80000_g6.5_he2_c-4_n-4_o-4/t80000_g6.5_he2_c-4_n-4_o-4.7
# 3. 之后跑 run_grid.py 时,find_seed 会自动发现这些已收敛的邻居
```
**run_grid 的种子步进回退流程**
```
对每个网格点 P:
1. 检查 conv.json: 若已 converged → skip
2. find_seed(P): 查找已收敛邻居(同 family 优先,按 CNO 距离)
3. 冷启动 run_one(DEFAULT_CHAIN):
a. 成功 → 完成
b. 失败 + seed_step_fallback=true + 找到种子 →
移动失败结果到 <P>.coldfail/
用 SEED_STEP_CHAIN + seed 重试
4. 写 grid_status.json 汇总(含 seed_step_retries 计数)
```
---
## 6X. ICRSW 修复方案(已实施并测试 —— 2026-07-21
> **状态:源码已修改并重新编译,但修复后的可执行未替换 `tlusty.exe`。**
> 原因:修复在数值上**生效**(SWITCH 确实被调用了,CRSW 不再恒为 1.0),
> 但对最难收敛的 80K + He-poor + 富金属点**没有帮助**(甚至更早发散),
> 所以默认管线仍用未修改的 `tlusty.exe`。修复后的二进制保留在
> `tlusty/tlusty.exe.icrsw_patched`,需要时可以拿来对比试验。
> 备份的原始源码在 `tlusty/tlusty208.f.orig_backup`。
### 背景
`ICRSW`Hummer & Voels 1988 碰撞-辐射开关)在 tlusty208 中曾是**未完成的
集成**——`SUBROUTINE SWITCH``tlusty208.f:4556`)写好了完整的 CRSW 计算
逻辑,下游消费方代码(共 12 处 `RRU/RRD * CRSW(ID)``6341/6343-6345`、
`6589/6591-6592`、`6841/6843-6845`、`7621-7633`、`7807-7810`、`8017-8020`、
`18147`、`18201-18204`、`18252-18256`)也都到位,但**主迭代循环中缺少
`CALL SWITCH`**。结果 `CRSW(ID)` 永远保持默认值 `UN`=1.0line 1812
`CRSW(ID)=UN`),所有乘法都是无效操作。
### 实施的修复
**关键纠正**:旧版本本文档(§6X 步骤 3)建议把 `CALL SWITCH` 插在
`CALL RESOLV` **之前**。这是**错误**的——经源码核查确认:
- `RRU(ITR,ID)`/`RRD(ITR,ID)` 在 `RATES1`line 6179)及其同族子程序
`RATSP1`、`ALIST1`、`ALIST2`、`ALISK1`、`ALISK2`)内部才被零初始化
并累加;这些子程序全部从 `RESOLV`line 3724)调用。
- `COLRAT(ITR,ID)` 在 `INILAM`line 4029)中赋值,`INILAM` 也从
`RESOLV`line 3743)调用。
- **在 iter 1 的首次 `RESOLV` 之前,没有任何 DATA 语句或 START 阶段
初始化过 RRU/RRD/COLRAT**(已 grep 验证)。如果按旧文档把 `CALL SWITCH`
放在 `RESOLV` 之前,iter 1 会在 line 4599 `C/RRU(ITR,ID)` 处除以未初始化
的垃圾值,立刻 NaN。
**正确插入位置:在 `CALL RESOLV` 之后、`INIT=0` 之前**,复用现有的
`INIT` 变量作为 `INITM` 参数(程序启动时 INIT=1 在 line 22RESOLV 之后
被重置为 0 在 line 36):
```fortran
10 ITER=ITER+1
CALL RESOLV
C
C 1a. Collisional-radiative switching (Hummer & Voels 1988)
C EVALUATE/UPDATE CRSW(ID) AFTER the formal solution has produced
C fresh RRU/RRD/COLRAT, and BEFORE the linearization step (SOLVE/
C SOLVES) that consumes CRSW via BPOPE/BPOPF (lines ~18147,18201,
C 18252). On iter 1 INIT is still 1 -> full recompute of CRSW;
C on iter 2..N INIT was reset to 0 -> cheap CRSW*=SWPINC update.
C SWITCH is a no-op when ICRSW=0 (default), so existing behavior
C is unchanged unless ICRSW>0 is set in nst.
C NOTE: must NOT be moved before CALL RESOLV -- on iter 1 RRU/RRD/
C COLRAT are still uninitialized there (no DATA stmt; they are
C zeroed and filled inside RATES1/RATSP1 within RESOLV).
C
CALL SWITCH(INIT)
INIT=0
IF(LFIN) GO TO 20
...
```
这个位置的优点:
1. iter 1 时 RESOLV 已经计算好 COLRAT(来自 INILAM)和 RRU/RRD(来自
RATES1),SWITCH(INIT=1) 能正确执行完整 Hummer-Voels 计算。
2. iter 2..N 时 SWITCH(INIT=0) 仅做 `CRSW *= SWPINC` 的廉价更新。
3. CRSW 在 `SOLVE`/`SOLVES`(通过 MATGEN→BPOP→BPOPE/BPOPF 消费 CRSW
运行之前已经定下来。
4. SWITCH 内部首句 `IF(ICRSW.EQ.0) RETURN`line 4580)保证 ICRSW=0 时
是 no-op,**对现有所有测试零影响**。
**重新编译命令**(关键:必须用 `-mcmodel=large`,否则 x86-64 PIC 重定位
溢出,链接报 `relocation truncated to fit: R_X86_64_PC32 against symbol
curder_`):
```bash
cd tlusty/
cp tlusty.exe tlusty.exe.orig # 备份
cp tlusty208.f tlusty208.f.orig_backup # 备份源码
gfortran -O2 -std=legacy -fno-automatic -mcmodel=large \
-o tlusty.exe tlusty208.f
# 注意:IMPLIC.FOR / BASICS.FOR / 等都是 INCLUDE 文件,不要单独编译;
# 整个程序就在 tlusty208.f 一个文件里(通过 INCLUDE 拉入其它 .FOR)。
```
### 实施验证(2026-07-21
**验证 1:ICRSW=0 时与原版逐字节相同**
在 H+He NLTE20000/5.0/-1)模型上,patched exe 与原 exe 产生的
fort.7(大气)和 fort.9(收敛日志)**完全相同**(`cmp` 通过、md5 相同)。
证明修复对 ICRSW=0 的所有现有运行零影响。
**验证 2ICRSW=1 时 SWITCH 确实被调用**
同样的 H+He 模型,nst 加 `ICRSW=1,SWPFAC=0.001,SWPLIM=1.0,SWPINC=2.0`
- patched exe 在 fort.6 里多出 CRSW 数组 dump`1P8D10.3` 格式,50 个值),
数值序列 `1.438D-12 → 2.875D-12 → 5.750D-12 → 1.150D-11` 精确对应
`SWPINC=2.0` 的逐次翻倍——证明 INITM=0 分支(line 4636)在每次迭代执行。
- 原 exe 在相同 nst 下 fort.6 里**没有** CRSW dump,迭代历史与 ICRSW=0 完全
相同——证明旧代码的 SWITCH 确实从未被调用("死代码"判断成立)。
- iter 1 的 MAXIMUM 列在难收敛深度上明显被压低:
| 深度 | ICRSW=0(原版)| ICRSW=1patched| 压低倍数 |
|------|---------------|-------------------|---------|
| 35 | 1.03E+00 | 2.12E-01 | ~5× |
| 28 | 4.12E+00 | 9.88E-01 | ~4× |
| 25 | 3.72E+00 | 9.84E-01 | ~4× |
这是 Hummer-Voels 开关的预期行为——碰撞速率被人为放大(CRSW<1)以
压制辐射跃迁的非线性。
### 难点测试:80K + He-poor + logCNO=-1(最终未解决)
用 `seed_step` 同款配置(cno-2 种子 → cno-1 目标,热启动),8 次迭代,
测了三组 ICRSW 参数(强阻尼、弱阻尼)对比无 ICRSW 基线:
| iter | ICRSW=0(基线) | ICRSW=1 SWPFAC=1e-4 SWPINC=2.0 | ICRSW=1 SWPFAC=0.1 SWPINC=1.5 |
|------|----------------|-------------------------------|-------------------------------|
| 1 | 1.52e+03 | 2.26e+05 | 2.42e+04 |
| 2 | 1.15e+01 | 1.34e+05 | 9.34e+04 |
| 3 | 7.53e+01 | **NaN(发散)** | 5.98e+06 |
| 4 | 1.52e+01 | NaN | 2.29e+08 |
| 5 | 2.01e+03 | NaN | 5.66e+09 |
| 6 | 1.97e+01 | NaN | 3.13e+21(发散) |
| 7 | 8.24e+00 | NaN | NaN |
| 8 | 6.08e+02 | NaN | NaN |
| 大气 NaN | 0/5210(干净)| 2222/521043% | 0/5210(干净,但解无效) |
(基线 8 次迭代都没崩到 NaN,只是没收敛;两次 ICRSW 都更早爆。)
**结论**:ICRSW 修复在数值层面**完全成功**(SWITCH 跑起来了,CRSW 不再
恒为 1.0,迭代历史明显改变),但**不能解决这个极端难点**——无论是强阻尼
SWPFAC=1e-4)还是弱阻尼(SWPFAC=0.1),开启 ICRSW 都让发散**更早**。
SWPFAC=1e-4 让 iter 1 的初始跳跃从 1.5e3 变成 2.3e5(CRSW 太小导致线性化
过度校正);SWPFAC=0.1 略好但仍单调发散到 1e21。
物理原因(与前文 §5Y 的"物理极限"结论一致):80K + He-poor 时 CNO
高价离子(C IV/V、N V、O V/VI)主导大气不透明度,金属量从 logCNO=-2
跳到 -1(×10)导致光致电离率变化陡峭到完全线性化无法阻尼 iter 1 的
尖峰。Hummer-Voels 开关通过放大碰撞速率来稳定,但当辐射-碰撞比本身
就在极端区间时,开关反而把不稳定提前。
### 40K sanity checkpatched exe 在正常区间仍工作)
为了排除"修复破坏了正常路径"的可能,在已验证的 40K + logg 5.5 + cno-1
模型上跑 patched exeICRSW=0):迭代历史(oscillatory 但最终收敛到
~1e-4)与原版 exe 产生的 `t40000_g5.5_he0_c-1_n-1_o-1.nc_*.9` 文件
**特征一致**(同样的 iter 6 尖峰到 4.69e8、同样的 iter 23-27 收敛到
~1e-4)。证明修复对正常收敛区间无害。
### 实施后的工程决策
**保留源码修改,但不替换 `tlusty.exe`**
1. 修改已通过 sanity checkICRSW=0 时与原版逐字节相同),是安全的。
2. 对网格里 95%+ 的点(20000-40000K 区间),ICRSW 没用也没害。
3. 对剩余难收敛点(80K + He-poor + 富金属),ICRSW 不仅没用反而更糟。
4. 因此**没有理由**让默认管线用 patched exe——保留原版可执行,patched
二进制仅供后续研究(比如有人想试 `ICRSW=2` 的深度相关模式,或者
配合更小的丰度步长)。
**如果未来需要重新启用 patched exe**
```bash
cd tlusty/
# 当前 tlusty.exe 是原版;tlusty208.f 是已修改版
gfortran -O2 -std=legacy -fno-automatic -mcmodel=large \
-o tlusty.exe tlusty208.f
# 想恢复原版:cp tlusty208.f.orig_backup tlusty208.f 后重新编译
```
### 替代方案:网格层面规避(仍是当前推荐)
不改源码也能完成网格:
- 20000-40000K:冷启动全区间可靠(已验证多个点)
- 60000-80000K + He-rich / 低金属:冷启动或一步种子步进可解决
- 60000-80000K + He-poor + 富金属(logCNO=-1):**真实物理极限**
网格如实标记未收敛(这是合理的——观测上这些极端参数组合的 sdB
本就罕见,且本次实测确认 ICRSW 也不能解决)
### 每阶段信息记录
conv.json 记录每阶段的 converged/max_relc/elapsed_sec,以及 synspec_sec。
---
## 7. 下一步建议
### 立即可行(已验证配方 + 种子步进)
- **冷启动跑 20000-40000K + logC/N/O=-4 到 -1**:可靠收敛,无需种子
- **种子步进跑 60000-80000K + He-rich 或低金属**:用冷启动建"桥头堡"
再以它为种子推进到目标点
- **物理极限标注**80K + He-poor + logCNO=-1 是真实物理极限,
网格如实标记未收敛(不强制成功)
### 待完善
- **run_grid.py 自动种子策略**:现在是冷启动失败即标记失败;
应扩展为先尝试冷启动,失败则查找邻居已收敛模型作种子重试,
再失败则尝试中间丰度点作跳板(如 cno-4 → cno-2 → cno-1)。
- **种子库管理**:网格计算时按 Teff/logg 分组,每组先算最容易的点
(He-rich 或低金属),建立种子库,再扩散到难收敛点。
- **synspec 高温区 NaN 问题**80K 大气收敛但 synspec 谱有 74% NaN。
需要单独排查(可能是 gfVIS99.dat 谱线表对 80K 不兼容,或某些 CNO
高价离子模型原子在该温度下数值溢出)。
### 关键教训
1. **先核实物理参数**:之前花了大量时间调 CHMAX/IDLTE/ORELAX/He能级/NFREAD
真正的根因(丰度范围过高 + 冷启动初猜太远)却一直被忽略。
2. **冷启动 ≠ 唯一选择**LTE grey 冷启动在高温 He-poor 模型上必然失败,
但这**不是物理极限**,只是初猜太差。种子步进是 Peter Nemeth 邮件早就
建议的方法("减小模型间步长"),只是之前一直没正确实现。
3. **源码分析的价值**:通过 subagent 读 tlusty208.f 才发现:
- LTGREY 标志的真正含义(T=生成灰大气,F=读 fort.8)—— 种子步进的物理基础
- ICRSW 是**死代码**SWITCH 子程序定义了但从未被 CALL)—— 看似可用的
参数实际无效,必须实测验证
4. **物理极限要承认**80K + He-poor + 金属量 0.1×H 的组合,
即使用尽所有稳定化手段也不收敛。这是物理极限,不是工程问题。
网格应如实记录未收敛而非强行通过。
---
## 8. 邮件往来要点(Peter Nemeth
完整邮件在 `hot_subdwarf/letter/`(已逐条核对原文)。要点:
1. He-rich 模型难收敛属正常(原文:"Helium-rich models struggle a lot ...
That is normal"
2. He 用最复杂模型原子(原文:"I would always use the 24-level He1 and
20-level He2 model atoms")— 但实测在 `.5` 里声明 nlevs=14 对 nc 更稳定,
两者目标不同(Peter 关注光谱精度,我们关注收敛稳定性)
3. ITEK 可调(原文:"you can set it to 3, 15, and 100")— 实测 100 overshoot
且不设(默认 4)最好
4. 模型链:粗→精 CHMAX;减小模型间步长(原文:"You can try decreasing the
steps in between models")← **本次种子步进正是这一条的正确实现**
LTGREY=F 热启动 + 邻居模型作种子)
---
## 9. 参考文件索引
| 文件 | 内容 |
|------|------|
| `/home/dckj/program/tlusty/tests/cno_sdspectrum/GUIDE.md` | 用户原始指南(35000K 验证配方)。注意:在 tl208-s54/ 上一级目录 |
| `cno_grid/src/run_one.py` | 三步链实现(DEFAULT_CHAIN = 正确配方)|
| `cno_grid/src/gen_input5.py` | .5 生成器(He nlevs=14, NFREAD=2000|
| `cno_grid/src/seed_step.py` | **种子步进实现**(高温/难收敛点)|
| `cno_grid/src/run_grid.py` | 6 维网格调度器(冷启动 + 种子步进回退)|
| `cno_grid/src/check_conv.py` | fort.9 解析与收敛判定 |
| `cno_grid/config.yaml` | 网格配置(链、seed_step_fallback 等)|
| `cno_grid/PIPELINE.md` | 计算流程文档(阶段/并行/统计)|
| `cno_grid/results/` | 测试模型结果(含 conv.json|
| `cno_grid/results/bound_*.log` | 8 点边界测试日志(2026-07-21|
| `cno_grid/results/seed_step/` | 种子步进验证结果 |
| `cno_grid/run_boundary_corrected.sh` | 边界测试启动脚本 |
| `hot_subdwarf/letter/` | Peter Nemeth 邮件 |
+461
View File
@@ -0,0 +1,461 @@
# CNO 网格完整计算流程
> 本文档说明完整理论光谱网格的计算流程:每个网格点的计算阶段、每阶段的配置、
> 配置原理、CPU/并行机制、以及如何统计每个阶段的信息(时间、收敛等)。
---
## 1. 总体架构
```
config.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 ← 各阶段大气
```
---
## 2. 每个网格点的计算阶段
每个网格点(一组 Teff/logg/logHe/logC/logN/logO 参数)经过 **4 个阶段**
**默认走"冷启动链"**DEFAULT_CHAIN,适用 20-40K 及大部分易收敛点):
| 阶段 | 程序 | 做什么 | NITER | 典型耗时 |
|------|------|--------|-------|---------|
| 1. LTE 灰大气 | tlusty | `T T` 模式,解析求灰色 T(τ) 结构 | 0 | 1-3 秒 |
| 2. ncNLTE 连续谱)| tlusty | `F F` + `ilvlin=0`,收敛电离平衡(无线跃迁)| **10** | 1-5 分钟 |
| 3. nlNLTE 含线)| tlusty | `F 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 直接热启动)。
### 为什么是这 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)—— 高温区破局(NEW
当冷启动链发散时(典型:60000-80000K + He-poor + 富金属),run_grid 自动
切换到**种子步进链**`seed_step.SEED_STEP_CHAIN`),跳过 LTE grey 冷启动,
直接用邻居已收敛的 `.7` 热启动:
| 阶段 | 做什么 | 关键标志 | NITER |
|------|--------|---------|-------|
| seed_nc | 从种子热启动 NLTE 连续谱 | `LTGRAY=F`(读 fort.8) + `ICHANG=0` | 80 |
| 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=<路径>`
**原理**`tlusty208.f` 源码确认,详见 EXPERIENCE.md §5Y):
- `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`,不强制成功——这些极端参数组合观测上本就罕见。
>
> **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',内容每阶段由 write_nst 生成)
第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 文件(非标准参数,每阶段不同)
**阶段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=10, 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` | **10** | 100 | 最大迭代数 | nc 给 10 次足够(实测最优);nl 给 100 次 |
| `IELCOR` | -1 | -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.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``_worker`):
```python
with Pool(nworkers) as pool:
for res in pool.imap_unordered(_worker, worker_args):
...
```
- `nworkers`config.yaml,当前=**16**):同时运行的 worker 进程数。
- 每个 worker 是一个独立的 Python 子进程,调用 `run_one.py` 跑一个网格点
(在独立的工作目录里,互不干扰)。
- `imap_unordered`:哪个点先完成就先回收,立即分配下一个点(动态负载均衡)。
- 16 核机器跑 16 个 worker = 16 个 tlusty 实例同时跑 = 满载利用。
(按机器核数调整;每个 tlusty 运行是单线程的,调大 nworkers 即可吃更多核。)
**关键:每个 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 分钟(种子步进)| 约同上 | 冷启动失败→种子步进成功 |
| 80000K + He-poor + logCNO=-1 | — | — | **真实物理极限,发散**(如实标注) |
当前 config.yaml 共 432 点(4×2×2×3×3*3);中等参数区冷启动为主,
高温区走种子步进回退,整体约需数小时到一天。
---
## 5. 如何统计每阶段信息
### 5.1 当前已记录的信息(conv.json
每个网格点完成后,`results/<模型名>/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
"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)"}
},
{
"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}
},
{
"label": "nl",
"converged": true,
"final": {"itek":null, "rc":0, "max_relc":0.0069,
"worst_depth":1, "last_iter":17, "n_depths":50}
}
]
}
```
> **走种子步进链时**`seed` 指向邻居 `.7``seed_step_used=true`
> `coldfail_backup` 指向 `<model>.coldfail/``stages` 里没有 `lte`,而是
> `seed_nc`LTGRAY=F 热启动,NITER=80)→ `nl`。
每阶段记录:`converged`(是否收敛)、`max_relc`(最大相对变化)、
`worst_depth`(最差深度点)、`last_iter`(迭代次数)、`n_depths`(深度点数)、
`elapsed_sec`(本阶段耗时)。
### 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']))
"
```
### 5.3 统计整个网格的信息
`run_grid.py` 完成后写 `results/grid_status.json`
```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},
...
]
}
```
汇总统计命令:
```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',''))
"
```
### 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: [-4, -2, -1] # 亚太阳范围(已修正;旧版 -1..1 会发散)
logn: [-4, -2, -1]
logo: [-4, -2, -1]
# 共 4*2*2*3*3*3 = 432 个点
```
### 第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: 432 points total, N already done, M to compute
```
### 第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)。
---
## 7. 注意事项
1. **种子步进回退(核心机制)**`seed_step_fallback: true`(默认开)时,
冷启动失败的点会自动改走种子步进链(`seed_step.SEED_STEP_CHAIN`):
把失败结果备份到 `<model>.coldfail/`,用 `find_seed` 找最近邻已收敛的 `.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 多步跳板)。
2. **断点续算判定**`conv.json``converged=true` 的点会被跳过。假收敛
atmosphere_has_nan=true)的点会被重算。
3. **失败隔离**:单点失败(发散/崩溃)不中断整个网格,记入 grid_status.json
的 error/unfinished 列表。高温难点的失败大多是真实物理极限(见 §2.1),
不强制成功;如确需重试,可用 `seed_step.py` 手动从更近的种子起跳。
4. **磁盘空间**:每个模型约 50-100MB(含中间文件,走种子步进的点还会留
`<model>.coldfail/` 备份)。432 点约需 20-40GB。如不够可定期清理中间文件
(保留 .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, 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 §5Y。
+877
View File
@@ -0,0 +1,877 @@
# DCTS (Distributed Computing TLUSTY/SYNSPEC) API 文档
本文档由源代码自动提取并整理,详细说明了 **DCTS 分布式恒星大气网格计算系统** 服务端 (`dcts_server`) 提供的所有 RESTful API 接口规范、数据结构定义、鉴权机制、错误码及 `curl` 调用示例。
---
## 目录 (Table of Contents)
1. [通用说明与鉴权机制](#1-通用说明与鉴权机制)
2. [数据结构与类型定义 (Rust & TypeScript Schema)](#2-数据结构与类型定义-rust--typescript-schema)
3. [计算节点管理 API (Node Management)](#3-计算节点管理-api-node-management)
4. [任务调度与结果上报 API (Task Processing)](#4-任务调度与结果上报-api-task-processing)
5. [种子文件管理 API (Seed Management)](#5-种子文件管理-api-seed-management)
6. [静态资源与数据下载 API (Data Assets)](#6-静态资源与数据下载-api-data-assets)
7. [系统状态监控 API (System Status)](#7-系统状态监控-api-system-status)
8. [工作流管理 API (Workflow CRUD & Execution)](#8-工作流管理-api-workflow-crud--execution)
9. [错误处理与状态码汇总](#9-错误处理与状态码汇总)
---
## 1. 通用说明与鉴权机制
### 1.1 服务端信息
- **默认服务地址**: `http://127.0.0.1:8090` (端口可通过 `--port` / `DCTS_PORT` 环境变量配置)
- **传输协议**: HTTP / HTTPS
- **默认请求/响应格式**: `application/json` (部分文件下载接口为 `application/octet-stream`,任务上报为 `multipart/form-data`)
### 1.2 鉴权中间件与抗侧信道机制 (`auth_middleware`)
服务端在配置了 `DCTS_AUTH_TOKEN` (或 `AppState.auth_token`) 时,启用全局 Axum 鉴权中间件。客户端请求需附带正确的 Token,支持以下两种 Header 形式:
1. **Bearer Token 方式**:
```http
Authorization: Bearer <your_auth_token>
```
2. **X-API-Key 方式**:
```http
x-api-key: <your_auth_token>
```
> [!TIP]
> **防侧信道保护**:所有鉴权过程底层完全调用经过高定强优化的恒定长位跨运算度等时比较机制(Constant-Time Comparison),规避了一切从请求响应回车微小毫秒间隔判断探测系统敏感密钥或计算出有效前缀长度的侧信道(Side-Channel Attack)攻击。
若未通过鉴权,服务端统一返回 `401 Unauthorized` 响应:
```text
HTTP/1.1 401 Unauthorized
Unauthorized: Invalid or missing authentication token
```
---
## 2. 数据结构与类型定义 (Rust & TypeScript Schema)
### 2.1 6维网格点参数 (`GridPointParams`)
定义恒星大气模型的 6 维大气参数:温度 $T_{\text{eff}}$、重力加速度 $\log g$、以及元素丰度 $\log(N_{\text{He}}/N_{\text{H}})$, $\log(N_{\text{C}}/N_{\text{H}})$, $\log(N_{\text{N}}/N_{\text{H}})$, $\log(N_{\text{O}}/N_{\text{H}})$。
- **Rust 定义** ([models.rs](file:///home/fmq/program/tlusty/tl208-s54/dcts/crates/common/src/models.rs#L6-L14)):
```rust
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct GridPointParams {
pub teff: f64, // 有效温度 (K), e.g. 35000.0
pub logg: f64, // 表面重力加速度对数 (cgs), e.g. 5.5
pub loghe: f64, // 氦丰度对数, e.g. -1.0
pub logc: f64, // 碳丰度对数, e.g. -2.0
pub logn: f64, // 氮丰度对数, e.g. -2.0
pub logo: f64, // 氧丰度对数, e.g. -2.0
}
```
- **TypeScript 类型声明**:
```typescript
export interface GridPointParams {
teff: number;
logg: number;
loghe: number;
logc: number;
logn: number;
logo: number;
}
```
---
### 2.2 任务规格 (`TaskSpec`)
服务端派发给 Worker 节点的单个计算任务定义。
- **Rust 定义** ([models.rs](file:///home/fmq/program/tlusty/tl208-s54/dcts/crates/common/src/models.rs#L98-L106)):
```rust
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TaskSpec {
pub task_id: Uuid,
pub point_name: String,
pub params: GridPointParams,
pub task_type: TaskType, // ColdRun | SeedStep
pub seed_point_name: Option<String>,// 步进种子点名称(如适用)
pub timeout_sec: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum TaskType {
ColdRun,
SeedStep,
}
```
- **TypeScript 类型声明**:
```typescript
export type TaskType = 'cold_run' | 'seed_step';
export interface TaskSpec {
task_id: string;
point_name: string;
params: GridPointParams;
task_type: TaskType;
seed_point_name?: string | null;
timeout_sec: number;
}
```
---
### 2.3 任务上报报告 (`TaskReport`)
Worker 节点向服务端上报的任务计算结果。
- **Rust 定义** ([models.rs](file:///home/fmq/program/tlusty/tl208-s54/dcts/crates/common/src/models.rs#L126-L140)):
```rust
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TaskReport {
pub task_id: Uuid,
pub point_name: String,
#[serde(default)]
pub params: Option<GridPointParams>,
pub node_id: String,
pub status: TaskStatus, // Pending | Running | Completed | Failed | Timeout
pub converged: bool,
pub max_relc: Option<f64>,
pub atmosphere_has_nan: bool,
pub elapsed_sec: f64,
pub error_message: Option<String>,
pub summary_json: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum TaskStatus {
Pending,
Running,
Completed,
Failed,
Timeout,
}
```
- **TypeScript 类型声明**:
```typescript
export type TaskStatus = 'pending' | 'running' | 'completed' | 'failed' | 'timeout';
export interface TaskReport {
task_id: string;
point_name: string;
params?: GridPointParams;
node_id: string;
status: TaskStatus;
converged: boolean;
max_relc?: number | null;
atmosphere_has_nan: boolean;
elapsed_sec: number;
error_message?: string | null;
summary_json: string;
}
```
---
### 2.4 节点信息与请求模型 (`NodeRegisterRequest` / `NodeHeartbeatRequest`)
- **Rust 定义** ([models.rs](file:///home/fmq/program/tlusty/tl208-s54/dcts/crates/common/src/models.rs#L142-L170)):
```rust
pub struct NodeRegisterRequest {
pub node_id: String,
pub host_name: String,
pub max_slots: i32,
}
pub struct NodeHeartbeatRequest {
pub node_id: String,
pub active_slots: i32,
pub cpu_usage: f32,
pub memory_usage: f32,
}
pub struct NodeInfo {
pub node_id: String,
pub host_name: String,
pub max_slots: i32,
pub active_slots: i32,
pub status: String,
pub cpu_usage: f32,
pub memory_usage: f32,
pub last_heartbeat: DateTime<Utc>,
}
```
- **TypeScript 类型声明**:
```typescript
export interface NodeRegisterRequest {
node_id: string;
host_name: string;
max_slots: number;
}
export interface NodeHeartbeatRequest {
node_id: string;
active_slots: number;
cpu_usage: number;
memory_usage: number;
}
export interface NodeInfo {
node_id: string;
host_name: string;
max_slots: number;
active_slots: number;
status: 'online' | 'offline';
cpu_usage: number;
memory_usage: number;
last_heartbeat: string;
}
```
---
### 2.5 工作流响应模型与请求体 (`CreateWorkflowRequest` / `ApiResponse<T>`)
- **Rust 定义** ([workflow.rs](file:///home/fmq/program/tlusty/tl208-s54/dcts/crates/server/src/api/workflow.rs#L12-L24)):
```rust
pub struct CreateWorkflowRequest {
pub name: String,
pub description: Option<String>,
pub config_yaml: String,
}
pub struct ApiResponse<T> {
pub success: bool,
pub message: String,
pub data: Option<T>,
}
```
- **TypeScript 类型声明**:
```typescript
export interface CreateWorkflowRequest {
name: string;
description?: string | null;
config_yaml: string;
}
export interface ApiResponse<T = unknown> {
success: boolean;
message: string;
data?: T | null;
}
export interface WorkflowSummary {
name: string;
description?: string | null;
status: 'idle' | 'running' | 'paused' | 'completed';
created_at: string;
updated_at: string;
}
export interface WorkflowItem {
name: string;
description?: string | null;
config_yaml: string;
status: 'idle' | 'running' | 'paused' | 'completed';
created_at: string;
updated_at: string;
}
```
---
## 3. 计算节点管理 API (Node Management)
处理 Worker 节点的注册登录与定期心跳保活。
### 3.1 注册计算节点 (`POST /api/node/register`)
- **处理函数**: [`register_node`](file:///home/fmq/program/tlusty/tl208-s54/dcts/crates/server/src/api/node.rs#L6-L14)
- **函数签名**:
```rust
pub async fn register_node(
State(state): State<AppState>,
Json(req): Json<NodeRegisterRequest>,
) -> impl IntoResponse
```
- **鉴权**: 是 (若配置 Token)
- **请求 Header**: `Content-Type: application/json`
- **请求 Body**:
```json
{
"node_id": "node-worker-01",
"host_name": "hpc-node-01.local",
"max_slots": 8
}
```
- **响应 Schema**:
- `200 OK` (成功):
```json
{
"status": "ok",
"message": "节点注册成功"
}
```
- `200 OK` (数据库异常):
```json
{
"status": "error",
"message": "数据库错误详情"
}
```
- **curl 示例**:
```bash
curl -X POST http://localhost:8090/api/node/register \
-H "Content-Type: application/json" \
-H "Authorization: Bearer secret_token" \
-d '{
"node_id": "node-worker-01",
"host_name": "hpc-node-01.local",
"max_slots": 8
}'
```
---
### 3.2 节点心跳保活 (`POST /api/node/heartbeat`)
- **处理函数**: [`heartbeat_node`](file:///home/fmq/program/tlusty/tl208-s54/dcts/crates/server/src/api/node.rs#L17-L25)
- **函数签名**:
```rust
pub async fn heartbeat_node(
State(state): State<AppState>,
Json(req): Json<NodeHeartbeatRequest>,
) -> impl IntoResponse
```
- **鉴权**: 是 (若配置 Token)
- **请求 Header**: `Content-Type: application/json`
- **请求 Body**:
```json
{
"node_id": "node-worker-01",
"active_slots": 2,
"cpu_usage": 45.2,
"memory_usage": 30.8
}
```
- **响应 Schema**:
- `200 OK` (成功):
```json
{
"status": "ok"
}
```
- `200 OK` (失败):
```json
{
"status": "error",
"message": "节点未找到或心跳更新失败"
}
```
- **curl 示例**:
```bash
curl -X POST http://localhost:8090/api/node/heartbeat \
-H "Content-Type: application/json" \
-H "Authorization: Bearer secret_token" \
-d '{
"node_id": "node-worker-01",
"active_slots": 2,
"cpu_usage": 45.2,
"memory_usage": 30.8
}'
```
---
## 4. 任务调度与结果上报 API (Task Processing)
支持 Worker 节点抢占式领用任务与计算结果(含种子 `.7` 文件)上传。
### 4.1 领用计算任务 (`POST /api/task/claim`)
- **处理函数**: [`claim_task`](file:///home/fmq/program/tlusty/tl208-s54/dcts/crates/server/src/api/task.rs#L15-L25)
- **函数签名**:
```rust
pub async fn claim_task(State(state): State<AppState>) -> impl IntoResponse
```
- **鉴权**: 是 (若配置 Token)
- **请求 Body**: 无
- **响应 Schema**:
- `200 OK` (有可计算任务):
```json
{
"status": "ok",
"task": {
"task_id": "550e8400-e29b-41d4-a716-446655440000",
"point_name": "t35000_g5.5_he-1_c-2_n-2_o-2",
"params": {
"teff": 35000.0,
"logg": 5.5,
"loghe": -1.0,
"logc": -2.0,
"logn": -2.0,
"logo": -2.0
},
"task_type": "cold_run",
"seed_point_name": null,
"timeout_sec": 7200
}
}
```
- `200 OK` (当前队列为空):
```json
{
"status": "empty",
"task": null
}
```
- `500 Internal Server Error`:
```json
{
"status": "error",
"message": "领用任务失败: <error_details>"
}
```
- **curl 示例**:
```bash
curl -X POST http://localhost:8090/api/task/claim \
-H "Authorization: Bearer secret_token"
```
---
### 4.2 上报任务结果与种子文件 (`POST /api/task/report`)
- **处理函数**: [`report_task`](file:///home/fmq/program/tlusty/tl208-s54/dcts/crates/server/src/api/task.rs#L27-L102)
- **函数签名**:
```rust
pub async fn report_task(
State(state): State<AppState>,
mut multipart: Multipart,
) -> impl IntoResponse
```
- **鉴权**: 是 (若配置 Token)
- **请求格式**: `multipart/form-data`
- Part `report`: JSON 字符串 (映射为 `TaskReport`)
- Part `seed_file` *(可选)*: 二进制数据 (收敛网格点的 `.7` 大气结构种子文件)
- **响应 Schema**:
- `200 OK` (成功):
```json
{
"status": "ok",
"message": "上报成功"
}
```
- `400 Bad Request` (缺少 `report` 字段):
```json
{
"status": "error",
"message": "请求中缺少 report 字段"
}
```
- `400 Bad Request` (参数格式错误):
```json
{
"status": "error",
"message": "无法解析 params 或 summary_json"
}
```
- **说明**: 当 `converged == true` 且 `atmosphere_has_nan == false` 且包含 `seed_file` 时,服务端会将种子保存至 `results_dir/<point_name>/<point_name>.7` 并记入 `seeds` 表。若冷启动任务失败,服务端会自动唤醒 `GridScheduler` 触发针对该网格点的步进回退算法 (Seed-step Fallback)。
- **curl 示例**:
```bash
curl -X POST http://localhost:8090/api/task/report \
-H "Authorization: Bearer secret_token" \
-F 'report={
"task_id": "550e8400-e29b-41d4-a716-446655440000",
"point_name": "t35000_g5.5_he-1_c-2_n-2_o-2",
"node_id": "node-worker-01",
"status": "completed",
"converged": true,
"max_relc": 0.00008,
"atmosphere_has_nan": false,
"elapsed_sec": 142.5,
"error_message": null,
"summary_json": "{\"name\":\"t35000_g5.5_he-1_c-2_n-2_o-2\",\"params\":{\"teff\":35000.0,\"logg\":5.5,\"loghe\":-1.0,\"logc\":-2.0,\"logn\":-2.0,\"logo\":-2.0},\"stages\":[],\"converged\":true,\"elapsed_sec\":142.5,\"atmosphere_has_nan\":false}"
};type=application/json' \
-F 'seed_file=@/path/to/t35000_g5.5_he-1_c-2_n-2_o-2.7'
```
---
## 5. 种子文件管理 API (Seed Management)
提供在网格计算过程中相近网格点间传递与下载 TLUSTY `fort.7` 大气结构二进制种子文件的功能。
### 5.1 下载网格点种子文件 (`GET /api/seed/:name`)
- **处理函数**: [`download_seed`](file:///home/fmq/program/tlusty/tl208-s54/dcts/crates/server/src/api/seed.rs#L12-L58)
- **函数签名**:
```rust
pub async fn download_seed(
State(state): State<AppState>,
AxumPath(name): AxumPath<String>,
) -> Response
```
- **鉴权**: 是 (若配置 Token)
- **路径参数**:
- `name`: 网格点名称 (例如: `t35000_g5.5_he-1_c-2_n-2_o-2`)
- **安全检查**: 防止路径穿越攻击,校验参数中不可包含 `..`、`/` 或 `\`。
- **响应 Header**:
- `Content-Type: application/octet-stream`
- `Content-Disposition: attachment; filename="<name>.7"`
- **状态码与响应体**:
- `200 OK`: 返回文件二进制流
- `400 Bad Request`: `"非法的种子名称参数"`
- `404 Not Found`: `"请求的种子文件不存在"`
- `500 Internal Server Error`: `"无法打开种子文件"`
- **curl 示例**:
```bash
curl -X GET http://localhost:8090/api/seed/t35000_g5.5_he-1_c-2_n-2_o-2 \
-H "Authorization: Bearer secret_token" \
--output t35000_g5.5_he-1_c-2_n-2_o-2.7
```
---
## 6. 静态资源与数据下载 API (Data Assets)
供 Worker 节点下载执行 TLUSTY / SYNSPEC 所需的原子数据文件和线表。
### 6.1 下载任意数据资源文件 (`GET /api/data/file/*filename`)
- **处理函数**: [`download_single_data_file`](file:///home/fmq/program/tlusty/tl208-s54/dcts/crates/server/src/api/data.rs#L11-L24)
- **函数签名**:
```rust
pub async fn download_single_data_file(
AxumPath(filename): AxumPath<String>
) -> axum::response::Response
```
- **鉴权**: 是 (若配置 Token)
- **路径参数**:
- `filename`: 文件相对名称 (例如: `he2.dat`)
- **响应 Header**:
- `Content-Type: application/octet-stream`
- `Content-Disposition: attachment; filename="<filename>"`
- **状态码与响应体**:
- `200 OK`: 返回数据文件二进制流
- `400 Bad Request`: `"无效的数据文件名"`
- `404 Not Found`: `"资源数据文件不存在"`
- `500 Internal Server Error`: `"无法读取资源数据文件"`
- **curl 示例**:
```bash
curl -X GET http://localhost:8090/api/data/file/he2.dat \
-H "Authorization: Bearer secret_token" \
--output he2.dat
```
---
### 6.2 下载主光谱线表文件 (`GET /api/data/linelist`)
- **处理函数**: [`download_linelist`](file:///home/fmq/program/tlusty/tl208-s54/dcts/crates/server/src/api/data.rs#L26-L28)
- **函数签名**:
```rust
pub async fn download_linelist() -> axum::response::Response
```
- **鉴权**: 是 (若配置 Token)
- **响应**: 默认定位并流式返回 `assets/gfVIS99.dat` 文件。
- **状态码**: `200 OK` (或 `404 Not Found` / `500 Internal Server Error`)
- **curl 示例**:
```bash
curl -X GET http://localhost:8090/api/data/linelist \
-H "Authorization: Bearer secret_token" \
--output gfVIS99.dat
```
---
## 7. 系统状态监控 API (System Status)
实时监控分布式计算集群节点活跃度与计算槽位利用率。
### 7.1 获取集群整体状态 (`GET /api/status`)
- **处理函数**: [`get_status`](file:///home/fmq/program/tlusty/tl208-s54/dcts/crates/server/src/api/status.rs#L5-L17)
- **函数签名**:
```rust
pub async fn get_status(State(state): State<AppState>) -> impl IntoResponse
```
- **鉴权**: 是 (若配置 Token)
- **响应 Schema (`200 OK`)**:
```json
{
"status": "online",
"nodes_online": 2,
"total_active_slots": 4,
"total_max_slots": 16,
"nodes": [
{
"node_id": "node-worker-01",
"host_name": "hpc-node-01.local",
"max_slots": 8,
"active_slots": 2,
"status": "online",
"cpu_usage": 45.2,
"memory_usage": 30.8,
"last_heartbeat": "2026-07-27T16:55:00.000Z"
}
],
"grid_stats": {
"total": 512,
"pending": 210,
"running": 32,
"converged": 260,
"failed": 10
}
}
```
- **curl 示例**:
```bash
curl -X GET http://localhost:8090/api/status \
-H "Authorization: Bearer secret_token"
```
---
## 8. 工作流管理 API (Workflow CRUD & Execution)
管理恒星大气网格计算工作流 YAML 配置的增删改查、启动与暂停控制。
### 8.1 获取工作流列表 (`GET /api/workflows`)
- **处理函数**: [`list_workflows`](file:///home/fmq/program/tlusty/tl208-s54/dcts/crates/server/src/api/workflow.rs#L26-L31)
- **函数签名**:
```rust
pub async fn list_workflows(State(state): State<AppState>) -> impl IntoResponse
```
- **鉴权**: 是 (若配置 Token)
- **说明**: 返回工作流轻量级元数据列表(包含 `name`、`description`、`status`、`created_at`、`updated_at`)。如需获取具体工作流的 YAML 配置详情,请调用 `GET /api/workflows/:name`。
- **响应 Schema (`200 OK`)**:
```json
{
"success": true,
"message": "成功获取工作流列表",
"data": [
{
"name": "sdB_cno",
"description": "sdB CNO 6D Stellar Atmosphere Grid",
"status": "idle",
"created_at": "2026-07-27T08:00:00Z",
"updated_at": "2026-07-27T08:00:00Z"
}
]
}
```
- **curl 示例**:
```bash
curl -X GET http://localhost:8090/api/workflows \
-H "Authorization: Bearer secret_token"
```
---
### 8.2 创建或保存工作流 (`POST /api/workflows` / `PUT /api/workflows/:name`)
- **处理函数**: [`save_workflow`](file:///home/fmq/program/tlusty/tl208-s54/dcts/crates/server/src/api/workflow.rs#L44-L81)
- **函数签名**:
```rust
pub async fn save_workflow(
State(state): State<AppState>,
Json(req): Json<CreateWorkflowRequest>,
) -> impl IntoResponse
```
- **鉴权**: 是 (若配置 Token)
- **请求 Body**:
```json
{
"name": "sdB_cno_custom",
"description": "自定义 6维 网格计算工作流",
"config_yaml": "grid:\n teff: [35000, 36000]\n logg: [5.5, 6.0]\n loghe: [-1.0]\n logc: [-2.0]\n logn: [-2.0]\n logo: [-2.0]\nchain:\n - label: LTE_START\n lte: T\n niter: 30\n"
}
```
- **响应 Schema**:
- `200 OK` (成功保存):
```json
{
"success": true,
"message": "工作流 'sdB_cno_custom' 保存成功",
"data": null
}
```
- `400 Bad Request` (YAML 格式不合法):
```json
{
"success": false,
"message": "无效的 YAML 配置: invalid syntax at line 2...",
"data": null
}
```
- **curl 示例**:
```bash
curl -X POST http://localhost:8090/api/workflows \
-H "Content-Type: application/json" \
-H "Authorization: Bearer secret_token" \
-d '{
"name": "sdB_cno_custom",
"description": "自定义 6维 网格计算工作流",
"config_yaml": "grid:\n teff: [35000, 36000]\n logg: [5.5, 6.0]\n loghe: [-1.0]\n logc: [-2.0]\n logn: [-2.0]\n logo: [-2.0]\nchain:\n - label: LTE_START\n lte: T\n niter: 30\n"
}'
```
---
### 8.3 获取特定工作流详情 (`GET /api/workflows/:name`)
- **处理函数**: [`get_workflow`](file:///home/fmq/program/tlusty/tl208-s54/dcts/crates/server/src/api/workflow.rs#L33-L42)
- **函数签名**:
```rust
pub async fn get_workflow(
State(state): State<AppState>,
AxumPath(name): AxumPath<String>,
) -> impl IntoResponse
```
- **鉴权**: 是 (若配置 Token)
- **路径参数**: `name` (工作流唯一名称,如 `sdB_cno`)
- **响应 Schema**:
- `200 OK` (成功):
```json
{
"success": true,
"message": "成功获取工作流详情",
"data": {
"id": 1,
"name": "sdB_cno",
"description": "sdB CNO 6D Stellar Atmosphere Grid",
"config_yaml": "...",
"status": "idle",
"created_at": "2026-07-27T08:00:00Z",
"updated_at": "2026-07-27T08:00:00Z"
}
}
```
- `404 Not Found` (不存在):
```json
{
"success": false,
"message": "工作流 'unknown_wf' 未找到",
"data": null
}
```
- **curl 示例**:
```bash
curl -X GET http://localhost:8090/api/workflows/sdB_cno \
-H "Authorization: Bearer secret_token"
```
---
### 8.4 删除工作流 (`DELETE /api/workflows/:name`)
- **处理函数**: [`delete_workflow`](file:///home/fmq/program/tlusty/tl208-s54/dcts/crates/server/src/api/workflow.rs#L83-L105)
- **函数签名**:
```rust
pub async fn delete_workflow(
State(state): State<AppState>,
AxumPath(name): AxumPath<String>,
) -> impl IntoResponse
```
- **鉴权**: 是 (若配置 Token)
- **响应 Schema (`200 OK`)**:
```json
{
"success": true,
"message": "工作流 'sdB_cno_custom' 已删除",
"data": null
}
```
- **curl 示例**:
```bash
curl -X DELETE http://localhost:8090/api/workflows/sdB_cno_custom \
-H "Authorization: Bearer secret_token"
```
---
### 8.5 启动工作流 (`POST /api/workflows/:name/start`)
- **处理函数**: [`start_workflow`](file:///home/fmq/program/tlusty/tl208-s54/dcts/crates/server/src/api/workflow.rs#L107-L183)
- **函数签名**:
```rust
pub async fn start_workflow(
State(state): State<AppState>,
AxumPath(name): AxumPath<String>,
) -> impl IntoResponse
```
- **说明**: 校验工作流,解析 YAML 中定义的所有 6 维网格坐标点,通过 `GridScheduler::initialize_grid` 展开网格点并计算保序难度 Wave,写入 SQLite 任务队列并开启节点调度。
- **响应 Schema**:
- `200 OK` (成功启动):
```json
{
"success": true,
"message": "工作流 'sdB_cno' 已成功启动并安排计算任务",
"data": null
}
```
- `400 Bad Request` (重复启动):
```json
{
"success": false,
"message": "工作流 'sdB_cno' 已处于运行状态,无需重复启动",
"data": null
}
```
- `404 Not Found`:
```json
{
"success": false,
"message": "工作流 'sdB_cno' 未找到",
"data": null
}
```
- **curl 示例**:
```bash
curl -X POST http://localhost:8090/api/workflows/sdB_cno/start \
-H "Authorization: Bearer secret_token"
```
---
### 8.6 暂停工作流 (`POST /api/workflows/:name/stop`)
- **处理函数**: [`stop_workflow`](file:///home/fmq/program/tlusty/tl208-s54/dcts/crates/server/src/api/workflow.rs#L185-L207)
- **函数签名**:
```rust
pub async fn stop_workflow(
State(state): State<AppState>,
AxumPath(name): AxumPath<String>,
) -> impl IntoResponse
```
- **响应 Schema (`200 OK`)**:
```json
{
"success": true,
"message": "工作流 'sdB_cno' 已暂停",
"data": null
}
```
- **curl 示例**:
```bash
curl -X POST http://localhost:8090/api/workflows/sdB_cno/stop \
-H "Authorization: Bearer secret_token"
```
---
## 9. 错误处理与状态码汇总
| HTTP 状态码 | 触发场景说明 | 响应格式 | 核心原因与解决建议 |
| :--- | :--- | :--- | :--- |
| **`200 OK`** | 请求正常处理 | JSON / Binary Stream | 操作成功执行。 |
| **`400 Bad Request`** | 参数校验失败、缺失关键字段或 YAML 格式错误 | `application/json` / Plain Text | 检查请求 JSON 结构,验证 YAML 配置语法是否正确。 |
| **`401 Unauthorized`** | 鉴权失败或缺失 Authorization Header | Plain Text | 确认环境变量 `DCTS_AUTH_TOKEN` 配置,并在 Request Header 中包含正确的 `Bearer <token>` 或 `x-api-key`。 |
| **`404 Not Found`** | 资源、种子文件或工作流不存在 | `application/json` / Plain Text | 校验请求 URL 中的资源文件名或工作流 `name` 是否拼写无误。 |
| **`500 Internal Server Error`** | 服务端数据库错误、I/O 打开失败或队列异常 | `application/json` / Plain Text | 检查服务端日志以进一步厘清 SQLite 锁冲突、磁盘空间或资源路径问题。 |
---
*文档生成于 2026-07-27 | DCTS Server 0.1.0*
+91
View File
@@ -0,0 +1,91 @@
# DCTS 系统架构 (System Architecture)
> 介绍 DCTS (Distributed Computing TLUSTY/SYNSPEC) 的整体设计架构、Master-Worker 拓扑结构、任务生命周期流转及容错机制。
---
## 1. 架构拓扑 (Topology)
DCTS 采用**中心化控制、分布式离散执行 (Master-Worker)** 的拓扑设计:
```mermaid
flowchart TB
subgraph Master Node ["Master 服务端 (server)"]
API["Axum HTTP REST API"]
DB[(主数据库 dcts.db)]
MQ[(任务队列 dcts_queue.db)]
Scheduler["Grid Scheduler 网格调度器"]
SeedsStorage["本地种子库 results/*.7"]
API <--> DB
API <--> MQ
Scheduler --> MQ
Scheduler --> DB
end
subgraph Compute Nodes ["分布式计算节点集群 (node)"]
Worker1["Worker 节点 1 (Node Daemon)"]
Worker2["Worker 节点 2 (Node Daemon)"]
WorkerN["Worker 节点 N (Node Daemon)"]
end
Worker1 -- "1. 心跳/抢占任务 (Claim)" --> API
Worker1 -- "2. 下载种子/基础数据" --> API
Worker1 -- "3. 汇报结果/上传 .7 大气" --> API
Worker2 -- "HTTP REST / Bearer Auth" --> API
WorkerN -- "HTTP REST / Bearer Auth" --> API
API --> SeedsStorage
```
---
## 2. 核心组件职责
### 2.1 Master 服务端 (`server` & Web `dashboard`)
- **工作流与可视看板调度**:解析 `config.yaml` 生成多维笛卡尔积参数网格点放入 SQLite 数据库;且自带前端服务透射特性(映射 `dashboard/dist`),开局即在后端服务的相同接口同服下发开机即饮用的富监看运维控制桌仪表网页。
- **任务调度与分配**:通过 `mq` 队列管理任务生命周期,响应 Worker 的 Claim 请求分配就绪任务。
- **状态维护与心跳监测**:后台离线检测线程定期标记超时未心跳的节点为 `offline`,并能将因为断线掉电死机僵挂在其身上的坏死大批网格运算占位点清表并原路全方位无漏损地安全刷进重置任务池中(Requeue)避免死锁失联漏计。
- **静态资源与种子分发**:提供原子数据、线列表与 `.7` 大气种子文件的 HTTP 下载和上传接口。
### 2.2 Worker 计算节点 (`node`)
- **环境自适应预热 (Bootstrap)**:启动时核对本地 `./runtime` 运行依赖,缺失时自动向 Master 拉取可执行文件与二进制数据。
- **任务抢占与执行 (Claim & Execute)**:根据并发配置轮询抢占任务,调用 `common` 启动子进程链(tlusty / synspec)。
- **种子检索与回传 (Seed Sync)**:计算成功后将收敛的大气结构文件(`.7`)与状态 JSON 汇报回服务端。
---
## 3. 任务生命周期 (Task Lifecycle)
网格计算点从创建到完成的状态流转如下图所示:
```mermaid
stateDiagram-v2
[*] --> Pending : 工作流注册生成网格点
Pending --> Running : Worker 成功 Claim 抢占
state Running {
[*] --> ExecutingChain
ExecutingChain --> ColdStartChain : 默认冷启动 (lte->nc->nl)
ColdStartChain --> Synspec : 物理收敛
ColdStartChain --> SeedStepChain : 冷启动发散且有可邻近种子
SeedStepChain --> Synspec : 热启动收敛
}
Running --> Completed : 计算成功 & 上传 .7 产物
Running --> Pending : Worker 节点心跳超时/主动释放 (Requeue)
Running --> Failed : 重试次数达到上限 / 彻底发散
Completed --> [*]
Failed --> [*]
```
---
## 4. 容错与高可用设计 (Fault Tolerance)
1. **分布式无状态 Worker & 上报阶梯退避**:Worker 节点不保存持久运行控制状态,异常宕机不会损坏主数据集。计算结果向 Master 上报时,具备多达 8 次指数阶梯容灾回退(最大间隔 60 秒,覆盖超 2 分钟断断连长窗),稳健保障长时间高密物理算单不受瞬时组网闪断或服务端短时上线切换干预。
2. **零文件扫描与连接并发缩流**:底层任务分批取配、排队清洗与邻接优化(Seed-Stepping)全链线依赖常驻内存的 SQlite 主从精算并调优收敛连接池配置(主库=8,队列=4 减免本地排他写冲突并发挂断);去除了历史残存的高损及同步阻塞磁盘遍历 API,在确保零卡死响应的前提下提升查询搜索效力。
3. **任务超时与流控平稳保护 (Stale & Requeue)**:服务端后台定期向已超时死挂的 `Running` (默认 >1800 秒)作业予以强退回转为 `Pending`;此外当操作维护员发起暂停或终止工作流行为时,将仅平滑洗退待调 `Queued` 项,悉心保育在途已投的 Worker 数值演算完整出计算归表,防假命题竞合。
4. **多级退避与种子隔离**:若某点冷启动发散,自动隔离失败现场,依靠数据库记录寻找欧氏空间距离最匹配的热启动合拢 `.7` 气象序列;即便遇到底层强硬大步发散也不产生干扰并留存物理运行根系以便溯源。
+70
View File
@@ -0,0 +1,70 @@
# DCTS 开发者与参与贡献指南 (Contributing Guide)
> 感谢关注并参与 DCTS (Distributed Computing TLUSTY/SYNSPEC) 的开发!本文档为开发者提供本地环境配置、代码库结构说明及测试规范。
---
## 1. 本地开发环境准备
### 必备依赖
- **Rust 工具链**1.75+ (推荐使用 `rustup` 安装)
- **C/Fortran 编译器**(计算节点模拟调试需 `gfortran` 或预编译好的二进制)
- **SQLite 3**开发库(嵌入在 Rust dependencies 中,无需额外安装系统库)
### 克隆与编译
```bash
cd dcts
cargo check --workspace --all-targets
cargo build
```
---
## 2. 代码库结构 (Workspace Layout)
```
dcts/
├── Cargo.toml # Workspace 根配置
├── config.yaml # 示例网格配置文件
├── crates/
│ ├── common/ # [Library] 物理计算引擎、子进程管理、收敛检测与模板
│ ├── server/ # [Binary] Axum HTTP REST 服务端与 Scheduler
│ ├── node/ # [Binary] Worker 节点 Daemon 与任务抢占回路
│ └── mq/ # [Library] SQLite 事务型分布式任务队列
├── tools/
│ └── sync_seeds/ # [Binary] 离线/增量种子同步 CLI 工具
└── docs/ # 分主题架构与规格技术文档
```
---
## 3. 测试与规范 (Testing & Guidelines)
### 3.1 运行单元测试与集成测试
```bash
# 运行 Workspace 内所有单元测试
cargo test --workspace
# 针对核心物理解析模块独立测试
cargo test -p common
```
### 3.2 代码风格与 Linting
提交 PR 前请确保以下命令无 error 和 warning
```bash
# 代码格式化
cargo fmt --all -- --check
# Rust 官方 Linter 静态检查
cargo clippy --workspace --all-targets -- -D warnings
```
---
## 4. 提交 Code Review 规范
1. **分支策略**:从 `main` 切出特性分支,推荐命名如 `feature/seed-optimizer``fix/stale-node-leak`
2. **Commit Message 规范**:格式推荐使用 `module: succinct explanation`,如:
- `common: add tolerance factor check for seed finder`
- `server: fix sqlite connection pool leak under heavy load`
3. **保持文档更新**:若修改了 API 接口定义或数据库 Schema,请同步更新 `docs/api_reference.md``docs/database.md`
+108
View File
@@ -0,0 +1,108 @@
# DCTS 数据库设计 (Database Schema)
> DCTS 采用轻量级、零配置、高并发安全的 **SQLite** 双数据库架构:主状态库 `dcts.db` 存储网格结构与历史记录;队列库 `dcts_queue.db` 由 `mq` 驱动管理原子任务状态机。
---
## 1. 数据库分库架构
```mermaid
erDiagram
WORKFLOWS ||--o{ GRID_POINTS : contains
GRID_POINTS ||--o{ TASK_HISTORY : logs
NODES ||--o{ TASK_QUEUE : executes
subgraph PrimaryDB ["主数据库 (dcts.db)"]
WORKFLOWS {
string name PK
string description
text yaml_config
string status
datetime created_at
}
GRID_POINTS {
string point_id PK
string workflow_name FK
double teff
double logg
double he_abund
double c_abund
double n_abund
double o_abund
string status
datetime updated_at
}
TASK_HISTORY {
string id PK
string point_id FK
string node_id
boolean success
text conv_info_json
datetime duration_sec
}
NODES {
string node_id PK
string hostname
integer cpu_cores
string status
datetime last_heartbeat
}
end
subgraph QueueDB ["队列库 (dcts_queue.db / mq)"]
TASK_QUEUE {
string task_id PK
string workflow_name
string payload_json
string status
string assigned_node
integer retry_count
datetime claimed_at
}
end
```
---
## 2. 表结构定义 (Schema Specification)
### 2.1 `workflows` (工作流配置表)
存储用户定义的计算网格配置及整体状态。
- `name` (`VARCHAR(64) PRIMARY KEY`):工作流唯一标志(如 `sdB_cno`)。
- `description` (`TEXT`):描述信息。
- `yaml_config` (`TEXT`):完整的参数网格定义与物理配置 YAML 内容。
- `status` (`VARCHAR(32)`):状态:`idle` / `running` / `paused` / `completed`
- `created_at` (`DATETIME DEFAULT CURRENT_TIMESTAMP`):创建时间。
### 2.2 `grid_points` (网格点物理参数表)
存储多维笛卡尔积展开后的每一个独立参数点。
- `point_id` (`VARCHAR(128) PRIMARY KEY`):点全局唯一 ID(如 `pt_teff40000_logg600_he-100...`)。
- `workflow_name` (`VARCHAR(64) REFERENCES workflows(name)`):所属工作流。
- `teff`, `logg`, `he_abund`, `c_abund`, `n_abund`, `o_abund` (`REAL`):物理参数。
- `status` (`VARCHAR(32)`)`pending` / `running` / `converged` / `failed`
- `success_method` (`VARCHAR(32)`):收敛时的成功手段 (`cold_run` 冷启动成功 / `seed_step` 种子步进成功)。
### 2.3 `nodes` (计算节点心跳与状态表)
- `node_id` (`VARCHAR(64) PRIMARY KEY`):节点唯一标识。
- `hostname` (`VARCHAR(128)`):节点主机名或 IP。
- `cpu_cores` (`INTEGER`):节点 CPU 核心数。
- `status` (`VARCHAR(32)`)`online` / `offline` / `busy`
- `last_heartbeat` (`DATETIME`):最后一次心跳上报时间。
### 2.4 `task_queue` (分布式任务队列表 - `mq`)
驱动分布式抢占与超时重试的核心表,使用 SQLite `WAL` 模式确保高吞吐并发安全。
- `task_id` (`VARCHAR(128) PRIMARY KEY`):任务 ID。
- `workflow_name` (`VARCHAR(64)`):工作流。
- `payload_json` (`TEXT`):任务所含参数 payload。
- `status` (`VARCHAR(32)`)`pending`(就绪) / `running`(计算中) / `completed`(完成) / `failed`(失败)。
- `assigned_node` (`VARCHAR(64)`):当前抢占该任务的节点 ID。
- `retry_count` (`INTEGER DEFAULT 0`):失败或超时重发次数。
- `claimed_at` (`DATETIME`):抢占时间戳(用于超时释放判定)。
---
## 3. 并发与事务安全设计
1. **WAL (Write-Ahead Logging) 模式**SQLite 连接自动启用 `PRAGMA journal_mode=WAL;``PRAGMA busy_timeout=5000;`,解决多线程/多进程读写锁竞争。
2. **连接池机制**:借助 `r2d2` + `r2d2_sqlite` 维护异步连接池,防止高并发下数据库句柄冲突。
3. **原子 Claim 事务**:任务抢占在单个 SQLite 事务中完成(`UPDATE task_queue SET status='running', assigned_node=? WHERE status='pending' ... LIMIT 1`),保证绝对防重领。
+260
View File
@@ -0,0 +1,260 @@
# DCTS 部署与运维指南 (Deployment & Operations Guide)
> 介绍如何在单机或跨机分布式 Linux 集群环境下编译、配置、部署与运维 DCTS 服务端与 Worker 计算节点。
---
## 1. 部署架构概览
DCTS 计算节点无复杂系统依赖,仅需网络能访问 Master 的 HTTP 端口。
```
[ Master 服务端 ] (拥有固定 IP / 域名, 如 http://192.168.1.100:8090)
├── dcts_server
├── data/dcts.db & data/dcts_queue.db
└── data/results/ 集中种子仓库与计算摘要
│ HTTP / REST (8090)
┌─────┴───────────────┬──────────────────────┐
[ Worker 节点 A ] [ Worker 节点 B ] [ Worker 节点 C ]
(16 Cores) (32 Cores) (64 Cores)
dcts_node dcts_node dcts_node
```
---
## 2. 环境准备与源码编译 (Build & Compilation)
### 2.1 系统依赖准备
编译与运行 DCTS 需要以下 Linux 基础环境:
* **Rust Toolchain**: Rust 1.75+(使用 `rustup` 安装)
* **Fortran 编译器与工具**: `gfortran`, `gcc`, `make`(用于运行 TLUSTY/SYNSPEC 物理引擎底座)
在 Ubuntu/Debian 上安装基础依赖:
```bash
sudo apt-get update
sudo apt-get install -y build-essential gfortran pkg-config libssl-dev
```
### 2.2 源码编译说明
DCTS 采用 Cargo Workspace 组织项目代码,包含 `server``node` 两个核心二进制包。
#### 开发调试编译 (Debug Mode)
编译速度快,包含调优断言与详细日志:
```bash
# 编译整个 Workspace
cargo build
# 仅编译服务端
cargo build -p server
# 仅编译 Worker 计算节点
cargo build -p node
```
编译产物位于 `target/debug/server` (或 `dcts_server`) 和 `target/debug/node` (或 `dcts_node`)。
#### 生产性能编译 (Release Mode - 推荐)
进行全量 LLVM 编译优化,物理计算与网络吞吐效率最高:
```bash
# 编译 Workspace 下所有组件的全量 Release 二进制
cargo build --release
```
编译产物位于 `target/release/server``target/release/node`
### 2.3 Fortran 物理引擎底层二进制编译 (TLUSTY & SYNSPEC)
DCTS 运行时所依赖的物理计算底座二进制文件 `assets/tlusty_static``assets/synspec_static` 是使用 `gfortran` 编译器对 TLUSTY 和 SYNSPEC 的 FORTRAN 原生代码进行优化编译生成的。
#### 1. 编译 TLUSTY 恒星大气结构引擎 (`assets/tlusty_static`)
* **源码位置**: `tlusty/`
- 主程序文件: `tlusty208.f`
- 依赖包含模块: `BASICS.FOR`, `IMPLIC.FOR`, `ITERAT.FOR`, `ALIPAR.FOR`, `ATOMIC.FOR`, `MODELQ.FOR`, `ODFPAR.FOR`, `ARRAY1.FOR`
* **标准编译命令**:
```bash
cd /home/fmq/program/tlusty/tl208-s54/tlusty
gfortran -fno-automatic -O3 -o ../dcts/assets/tlusty_static tlusty208.f
```
* **大内存寻址编译选项 (推荐超大能级网格使用)**:
```bash
gfortran -fno-automatic -mcmodel=large -O3 -o ../dcts/assets/tlusty_static tlusty208.f
```
#### 2. 编译 SYNSPEC 理论光谱合成引擎 (`assets/synspec_static`)
* **源码位置**: `synspec/`
- 主程序文件: `synspec54.f`
- 依赖包含模块: `PARAMS.FOR`, `MODELP.FOR`, `LINDAT.FOR`, `OPTPAR.FOR`, `SYNTHP.FOR`, `WINCOM.FOR`
* **标准编译命令**:
```bash
cd /home/fmq/program/tlusty/tl208-s54/synspec
gfortran -fno-automatic -O3 -o ../dcts/assets/synspec_static synspec54.f
```
* **大内存寻址编译选项**:
```bash
gfortran -fno-automatic -mcmodel=large -O3 -o ../dcts/assets/synspec_static synspec54.f
```
#### 关键编译选项说明:
- `-fno-automatic`: 禁用局部变量的自动栈分配(强制将局部变量保存在静态内存区)。这是保证传统 FORTRAN 77 程序正常运行的关键参数,防止大型局部数组造成栈溢出(Stack Overflow)或段错误(Segmentation Fault)。
- `-O3`: 开启全量 LLVM/GCC 代码优化,极大加快完全线性化/加速 Lambda 迭代(CL/ALI)及辐射转移方程形式解的计算速度。
- `-mcmodel=large`: 当模型数组与数据段超越 2GB 寻址限制时,允许可执行文件使用 64 位大内存寻址模式。
---
## 3. 配置文件与环境变量 (.env)
主程序与计算节点均支持在项目根目录或运行目录下自动加载 `.env` 配置文件。
### 3.1 服务端环境变量表 (`server`)
| 环境变量名 | 默认值 | 说明 |
| :---------------------- | :--------------------- | :--------------------------------------------------------------- |
| `DCTS_PORT` | `8090` | 服务端 HTTP REST API 监听端口 |
| `DCTS_DB_PATH` | `data/dcts.db` | 主 SQLite 数据库文件路径(存放节点、网格点及种子记录) |
| `DCTS_QUEUE_DB_PATH` | `data/dcts_queue.db` | 任务队列 SQLite 数据库文件路径 |
| `DCTS_RESULTS_DIR` | `data/results` | 集中种子仓库与计算总结`conv.json` 保存目录 |
| `DCTS_AUTH_TOKEN` | *空* | 服务端 API 鉴权令牌(可选,若配置则需在请求头携带 Bearer Token |
| `DCTS_STALE_SEC` | `1800` | 任务运行超时重新放回队列的时间上限(秒) |
| `DCTS_NODE_STALE_SEC` | `120` | 判定 Worker 节点离线的心跳超时时间(秒) |
### 3.2 Worker 节点环境变量表 (`node`)
| 环境变量名 | 默认值 | 说明 |
| :--------------------- | :------------------------ | :--------------------------------------------------- |
| `DCTS_SERVER_URL` | `http://127.0.0.1:8090` | 目标 Master 服务端 API 访问地址 |
| `DCTS_NODE_ID` | *自动生成 UUID* | 节点唯一标识(可手动指定固定值如`node-node01` |
| `DCTS_MAX_SLOTS` | `4` | 本地 Worker 节点的并发计算 Slot 槽位数 |
| `DCTS_RUNTIME_DIR` | `data/runtime` | 本地 TLUSTY/SYNSPEC 可执行程序及物理谱线数据存放目录 |
| `DCTS_WORK_DIR` | `data/work` | 本地计算沙盒工作目录 |
| `DCTS_HEARTBEAT_SEC` | `15` | 向服务端发送心跳报告的时间间隔(秒) |
| `DCTS_AUTH_TOKEN` | *空* | 匹配服务端的 API 鉴权令牌 |
---
## 4. 启动与运行方式 (Running Modes)
根据使用场景,支持以下三种运行方式:
### 4.1 方式一:使用 Cargo 直接开发运行 (`cargo run`)
适合本地开发、调试与快速验证。
* **启动 Master 服务端**:
```bash
cargo run -p server
# 或使用 release 模式
cargo run --release -p server
```
* **启动 Worker 计算节点** (在另一终端):
```bash
cargo run -p node
# 或使用 release 模式
cargo run --release -p node
```
### 4.2 方式二:二进制文件直接运行 (Direct Binary Execution)
适合简易命令行部署或手动后台运行。
1. 进入编译好的产物目录或将二进制分发至各节点:
```bash
cd /home/fmq/program/tlusty/tl208-s54/dcts
```
2. **启动 Master 服务端**:
```bash
./target/release/server
```
3. **启动 Worker 计算节点**:
```bash
./target/release/node
```
### 4.3 方式三:一键统一部署自动化控制台脚本 (全栈强烈推荐)
系统整合并提供了覆盖全业务场景的一键全自动化部署与运维治理脚本 [`scripts/deploy.sh`](file:///home/fmq/program/tlusty/tl208-s54/dcts/scripts/deploy.sh)。能够自适应处理本地自部署与异地全自动化编译、推送及拉起的集群管线要求。
该部署管理框架支持**互动式三步精细向导 (3-Step Wizard)** 与 **自动化长命令行快捷免打扰调度**。
您可以根据具体场景灵活运用以下策略组:
| 安装环境 | 技术引擎 | 适用场景说明 | 推荐自动化直呼执行命令 |
|---|---|---|---|
| **本地 (Local)** | **Docker Compose** | 单机联调或微服务生态整装秒启动 | `./scripts/deploy.sh -e local -b compose -r all` |
| **远程 (Remote)** | **Docker Compose** | 面对严苛依赖的机群打散化打包发布与全动态差分更新 | `./scripts/deploy.sh -e remote -b compose -r all` |
| **本地 (Local)** | **Systemd 原生** | 宿主无虚拟化损耗的高并发物理机运行(具备自启提权判定) | `sudo ./scripts/deploy.sh -e local -b systemd -r all` |
| **远程 (Remote)** | **Systemd 原生** | 主端向分站超算节点无界穿梭远抛落地与托管后台注册 | `./scripts/deploy.sh -e remote -b systemd -r node` |
#### 1. 交互式多重导航进站直奔体验
在宿主机或者编译主工作区,以最简洁无参数方式执行即唤醒主线指引,全程遵循人性化三层连贯设计选项:
```bash
./scripts/deploy.sh
```
1. **第一步 (环境定位)**:指明需要作用于**本地主机**还是经 SSH 高速管道传输并管理**远端机房控制端**;
2. **第二步 (底层引擎)**:指明借助 **Docker Compose 容器微服务架构**(绝佳无冲突隔离)或是注入原生主机执行的 **Systemd 系统级常驻服务**(也含双端优雅拆毁卸载/一键强停命令分支);
3. **第三步 (目标角色)**:选定**全部服务 [All: Server + Node]**、**仅运维主控服务端 [Server]** 或 **仅挂扣物理分流算力池计算 Worker 节点 [Node]**。
#### 2. 系统服务与集群清收降解管理 (去除与关停)
无论是系统底层的 Systemd 表项或者是持续处于自运行圈范围内部的 Compose 集群容器套,随时均可指派拆除动作清除干净:
```bash
# 卸载或清除对应部署架构,例如卸载本地所有的 systemd 原生守护任务链
./scripts/deploy.sh remove -e local -b systemd -r all
# 也可随时直接带单 remove 操作前缀命令进行安全降解
./scripts/deploy.sh remove -e remote -b compose -r server
```
#### 3. 守护进程实时勘侦管控技巧 (当直接采用 Systemd 环境时)
```bash
# 检查服务端 / Node 计算分核任务运行常态与生存心跳
sudo systemctl status dcts-server
sudo systemctl status dcts-node
# 精细翻查计算现场时变工作流水轴、迭代误差跟溯及实时运行输出
tail -f data/logs/dcts_server.*.log
tail -f data/logs/dcts_node.*.log
```
### 4.4 方式四:Docker / Docker Compose 标准纯手工控制容器联调(可选方案)
系统由专门精简构筑过的 [Dockerfile.server](file:///home/fmq/program/tlusty/tl208-s54/dcts/Dockerfile.server) 和 [Dockerfile.node](file:///home/fmq/program/tlusty/tl208-s54/dcts/Dockerfile.node) 作为构建基础,默认通过只读载入 `assets` 并以多路复用方式提供极致并发计算生态体系:
```bash
# 快速于本机执行容器冷启聚合与并跑
docker compose up -d --build
```
启动之后访问对口暴露监控 HTTP Dashboard 地址(常规默认指引定位至端口 `8090`),立即获尽实时拓扑状态曲线!
---
## 5. 工作流执行流程示例
1. **检查节点注册状态**:
```bash
curl -X GET http://localhost:8090/api/status
```
2. **启动默认网格工作流**:
```bash
curl -X POST http://localhost:8090/api/workflows/sdB_cno/start
```
3. **查询工作流详情**:
```bash
curl -X GET http://localhost:8090/api/workflows/sdB_cno
```
---
## 6. 日志管理与集群监控
- **轮转日志**: 默认在运行目录的 `data/logs/` 目录下按天自动轮转生成,如:
- 服务端日志: `data/logs/dcts_server.2026-07-27.log`
- 节点端日志: `data/logs/dcts_node.2026-07-27.log`
- **集群状态接口**: 通过 `GET /api/status` 实时监控集群在线节点列表、每个节点的 CPU/内存使用率、活动 Slot 数、在线节点总数及系统资源槽位总数。
+68
View File
@@ -0,0 +1,68 @@
# DCTS 物理计算与流程设计 (Physics Pipeline Design)
> 本文档详细阐述 DCTS 核心物理计算引擎的设计原理、4 阶段 TLUSTY/SYNSPEC 计算链,以及冷启动与种子步进(Seed Stepping)发散回退机制。
---
## 1. 物理计算链原理
TLUSTY 通过**完全线性化 (Complete Linearization)** 方法求解恒星大气结构的 NLTE(非局部热力学平衡)统计平衡方程与辐射转移方程。由于线性化的收敛半径有限,若初始猜想(初猜)偏离真解较远,迭代极易发生数值发散。
DCTS 将单点计算划分为 **4 个渐进物理阶段**
```mermaid
flowchart LR
Stage1["1. LTE 灰大气\n(lte / tlusty)\n解析灰色初猜"] --> Stage2["2. NLTE 连续谱\n(nc / tlusty)\n收敛电离平衡"]
Stage2 --> Stage3["3. NLTE 完整谱线\n(nl / tlusty)\n包含线跃迁求解"]
Stage3 --> Stage4["4. 合成光谱\n(synspec)\n输出 .spec / .cont"]
```
### 各阶段物理配置与功能明细
| 阶段 | 执行程序 | 物理含义 | 关键参数 | 典型迭代/耗时 |
| :--- | :--- | :--- | :--- | :--- |
| **1. LTE 灰大气 (lte)** | `tlusty.exe` | 解析灰色不透明度求解温度结构,无需种子,提供合理起点。 | `T T` 模式 | 0 次迭代 / 1-3 秒 |
| **2. NLTE 连续谱 (nc)** | `tlusty.exe` | 切换到 NLTE,忽略束缚-束缚线跃迁(`ilvlin=0`),收敛基础电离平衡。 | `F F`, `ilvlin=0` | 10 次迭代 / 1-5 分钟 |
| **3. NLTE 完整谱线 (nl)** | `tlusty.exe` | 引入全部线跃迁(`ilvlin=100`),求解包含非平衡辐射场的大气结构。 | `F F`, `ilvlin=100` | 15-30 次迭代 / 5-20 分钟 |
| **4. 合成光谱 (synspec)** | `synspec.exe` | 基于阶段 3 收敛的大气结构(`.7` 文件),计算高分辨率合成光谱。 | `INPOP=35` | 3-10 秒 |
---
## 2. 冷启动链 vs 种子步进链 (Seed-Stepping Fallback)
在极端高有效温度(如 $T_{\text{eff}} \ge 50,000\text{ K}$)、极低氦丰度或强金属线空白区,从 LTE 灰大气直接启动的**冷启动链 (DEFAULT_CHAIN)** 容易发散。
针对这一问题,DCTS 引入了**动态种子步进机制 (Seed Stepping Chain)**
```mermaid
flowchart TD
Start([开始计算指定网格点]) --> ExecCold[执行冷启动链 lte -> nc -> nl]
ExecCold --> CheckCold{nl 阶段物理收敛?}
CheckCold -- "是 (Success)" --> RunSyn1[运行 Synspec 生成光谱] --> Save1[保存结果 & 汇报成功]
CheckCold -- "否 (Diverging)" --> IsolCold[清理并隔离冷启动失败现场]
IsolCold --> FindSeed[在种子库搜索最近邻已收敛 .7 种子]
FindSeed --> HasSeed{找到合规邻居种子?}
HasSeed -- "是" --> ExecSeed[启动种子步进链 seed_nc -> nl\nLTGRAY=F 热启动]
ExecSeed --> CheckSeed{nl 阶段物理收敛?}
CheckSeed -- "是 (Success)" --> RunSyn2[运行 Synspec 生成光谱] --> Save2[标记 seed_step_used=true & 汇报成功]
CheckSeed -- "否 (Failed)" --> MarkFail[标记任务彻底失败]
HasSeed -- "否" --> MarkFail
```
### 种子匹配策略 (Seed Finding Algorithm)
`common::seed_finder` 模块彻底摆脱了早期对文件系统进行同步阻塞遍历式查档的高耗延迟做法,改为经由 Master 服务端 SQLite 内存快照索表直接执行多级筛选,并使用标准化欧氏距离(Euclidean Distance)查找最佳热起邻格起点:
$$d(p_1, p_2) = \sqrt{ \sum_{i} w_i \left( \frac{x_{1,i} - x_{2,i}}{\sigma_i} \right)^2 }$$
优先匹配有效温度 $T_{\text{eff}}$ 和表面重力 $\log g$ 变化最小的已收敛 `.7` 大气结构作为 `fort.8` 热启动输入,跳过容易发散的灰大气阶段。
> [!NOTE]
> **物理现场溯源说明**:为了服务于严谨的天文理论算理复盘,Node 端的沙盒演算文件夹(`data/work/task_{uuid}`)及其中所产生成的全部迭代物理日志与 Fortran 临时数表不会触发自动入侵清除,为发生极端大气参数无解突断时的推导验证提供了长期完整的痕迹。
>
> **严谨声明校验**:在 `GridConfig` 的加载引擎中引入了非合规键位阻断(Denying Unknown Fields),有效杜绝了用户在定义参数和扩展选项(如 `niter`, `itek_fallback`, `template`, `fort55`, `linelist`)由于错拼被静默忽略而导致不可预测迭代的行为。
+57
View File
@@ -0,0 +1,57 @@
# DCTS 故障排查与 FAQ (Troubleshooting & FAQs)
> 汇集 DCTS 计算过程中的常见故障、物理发散问题排查方法及节点恢复指南。
---
## 1. 物理计算发散排查 (TLUSTY Divergence)
### 现象 1`nl` 阶段出现 `DIVD ... BIG` 或 `NITER` 达到上限发散
- **原因**:初猜大气与当前网格点的真实 NLTE 大气物理状态相差过大,线性化半径无法收敛。
- **排查步骤**
1. 查看节点运行目录中的 `fort.6``conv.json`
```bash
cat results/<model_name>/conv.json
```
2. 检查 `coldfail` 现场保存:冷启动失败后,系统会自动保存过程数据到 `<model>.coldfail/`。
3. **解决方案**:确保 Master 的 `results/` 目录下存有相近 $T_{\text{eff}}$ / $\log g$ 的已收敛 `.7` 大气文件。系统将在下次重试时自动触发 **Seed-Stepping** 种子步进算法。
### 现象 2`lte` 阶段报错或瞬间终止
- **原因**:输入的物理参数超出了灰色大气基本假设或基础原子数据(原子能级/光致电离截面)损坏缺失。
- **排查步骤**
1. 验证 `data/` 目录中的 `ATO` / `ISO` 数据文件是否齐全。
2. 检查 `gen_input5` 生成的参数中 `TEFF` 是否小于 10000K 或大于 120000K。
---
## 2. 节点与网络故障 (Node & Network Issues)
### 现象 1Worker 节点提示 `Unauthorized: Invalid or missing authentication token`
- **原因**Master 服务端启用了 API Auth Token,但 Worker 的配置文件或环境变量中未配置匹配的 `auth_token`。
- **解决办法**:在 Worker 的 `.env` 中加入 `DCTS_AUTH_TOKEN=<your_secret_token>`。
### 现象 2:任务长时间处于 `Running` 状态没有进展
- **原因**:Worker 节点在计算中途遭遇断电、内存溢出(OOM)或僵死进程卡死。
- **自动恢复**:Master 服务端后台线程会在超过 `DCTS_STALE_SEC`(默认 30 分钟)后自动将该任务标记为 `pending` 重新放回队列。
- **手动恢复**:若需立即重置挂起任务,可直接重启 `server` 或使用 SQL
```sql
UPDATE task_queue SET status='pending', assigned_node=NULL WHERE status='running';
```
### 现象 3:网络抖动或后端滚动重配下提示 `向服务端上报任务 ... 结果失败`
- **原因**:在较长时效(如1-2小时)的运算完结回传一瞬间,Server 恰遇热更重启或遭遇防火墙短暂会话剔除。
- **容灾机制**:Worker 内置了超强的 8 轮指数级自适应退避长跳上报防护(跨度可自 1s 到 60s 顺次延迟,支撑 2 分钟以上的长时断裂耐受窗口);若由于连天硬件故障真正超出了总重试界限,亦可在本地非清理型数据栈(`data/work/task_{uuid}`)的目录直接调出本套算法终极收敛物并执行手工打标还原。
---
## 3. 日志与现场诊断
日志控制通过 `RUST_LOG` 环境变量配置:
```bash
# 查看服务端调试级别日志
RUST_LOG=info,server=debug ./target/release/server
# 查看节点端详细网络与子进程调用日志
RUST_LOG=info,node=debug,common=trace ./target/release/node
```