feat(all): NaN 伪收敛否决与 nl_tight 能量回退、emflux 检验 4π 修正、nst 行宽与种子边界修复、gfATO 谱线表接通与网格加密 9216 点、阶段分项统计与跳板机部署

物理修复(400 失败点归因,见 docs/failed400_nan_pseudo_convergence_2026_08_17.md):
- runner: 阶段 converged 后复查 fort.7,含 NaN/Inf 即否决(fort.9 全零伪收敛,
  曾致 261 点误跳过 nl_direct 回退);否决阶段不产出种子,阻断污染传播
- runner: nl_tight 回退——仅能量边际失败时以 CHMAX 收紧 10× 从自身模型续迭代,
  残差降幅 ~10×;execute_tlusty_stage 抽取供主链与回退共用
- conv_check: fort.14 为 Eddington 通量 Hλ,积分需乘 4π 再比 σTeff⁴
  (旧版 ratio 稳定 0.0796=1/4π,全点系统性假阳性)+ 回归测试
- nst_writer: 单行超 80 字符被 TLUSTY 静默截断,IFALI/JALI/TRAD 等从未生效;
  按 75 字符自动换行
- seed_finder: Teff 容忍度改含边界 <=,相邻 5000K 档恢复互为种子 + 回归测试

谱线表与网格:
- 默认线表 gfVIS99 → gfATO(全波段 18-23000Å),TaskSpec.linelist 支持工作流
  级覆盖,节点按需下载(进程互斥锁防并发重复下载 238MB)
- sdB_cno Teff 加密至 5000K 步长,432 → 9216 点;tlusty/synspec 静态二进制更新

统计与部署:
- grid 汇总改按 tlusty_status/synspec_status 分项计数,新增 tlusty_failed/
  synspec_failed/synspec_pending,前端详情页双视图适配
- deploy/fetch_results 支持跳板机 ProxyJump 与 SSH 主连接复用,fetch 新增 --force;
- Docker 构建支持 CARGO_MIRROR/USE_MIRRORS 国内镜像参数;移除 tools/ 拷贝
- 新增 tlusty-synspec-test skill 与 6 篇根因分析/验证文档
This commit is contained in:
fmq
2026-08-17 23:55:26 +08:00
parent 43b82b1ae2
commit b058e66722
54 changed files with 5624 additions and 347 deletions
+47 -1
View File
@@ -8,6 +8,10 @@ use reqwest::Client;
use std::path::{Path, PathBuf};
use tracing::{debug, info, warn};
/// 进程级互斥锁:序列化谱线表按需下载,防止多 Slot 并发首次派发时对同一 238MB 文件
/// 重复发起下载请求(POSIX rename 保证写安全,但冗余下载浪费带宽与 IO)。
static LINELIST_DOWNLOAD_MUTEX: tokio::sync::Mutex<()> = tokio::sync::Mutex::const_new(());
pub async fn execute_task(
client: &Client,
server_url: &str,
@@ -162,7 +166,49 @@ pub async fn execute_task(
.as_ref()
.and_then(|v| serde_json::from_value::<SynspecInput>(v.clone()).ok());
let runner = ExecutionRunner::new(runtime, slot_work_dir.clone());
// 谱线表覆盖:TaskSpec.linelist 指定时(如 YAML 配 linelist: gfATO.dat),
// 按需从服务端下载到 runtime_dir 根目录,并构造覆盖了 linelist 路径的 RuntimePaths。
// None → 用 node 启动时下载的默认线表(ensure_runtime 的 default_linelist 参数)。
let runtime_override: Option<RuntimePaths> = if let Some(ref ll_name) = task.linelist {
// 线表放在 runtime_dir 根目录(与默认线表同级,runner symlink 为 fort.19)。
let ll_path = runtime
.data_dir
.parent()
.unwrap_or(&runtime.data_dir)
.join(ll_name);
// 互斥保护:多 Slot 并发首次派发时,仅第一个进入临界区的 Slot 执行下载,
// 后续 Slot 在获得锁后 double-check 发现文件已存在直接跳过,避免冗余下载。
{
let _guard = LINELIST_DOWNLOAD_MUTEX.lock().await;
if !ll_path.exists() {
info!("任务指定谱线表 {} 本地缺失,从服务端按需下载...", ll_name);
let url = format!("{}/api/data/file/{}", server_url, ll_name);
let resp = client.get(&url).send().await?;
if resp.status().is_success() {
let bytes = resp.bytes().await?;
let ll_dir = ll_path.parent().unwrap_or(std::path::Path::new("."));
let tmp = ll_dir.join(format!("{}.{}.tmp", ll_name, uuid::Uuid::new_v4().simple()));
tokio::fs::write(&tmp, &bytes).await?;
tokio::fs::rename(&tmp, &ll_path).await?;
info!("成功下载谱线表 {} ({} bytes)", ll_name, bytes.len());
} else {
anyhow::bail!(
"下载任务指定谱线表 {} 失败,HTTP {}",
ll_name,
resp.status()
);
}
}
}
let mut rt = runtime.clone();
rt.linelist = ll_path;
Some(rt)
} else {
None
};
let effective_runtime = runtime_override.as_ref().unwrap_or(runtime);
let runner = ExecutionRunner::new(effective_runtime, slot_work_dir.clone());
// 执行链来源(优先级):
// 1. TaskSpec.tlusty_chain_params(用户在 YAML `tlusty_chain:` 配置的多阶段 ChainStep
// 数组,由 scheduler 序列化注入)——非空时优先使用,使用户能细粒度控制 niter/chmax/
+3 -1
View File
@@ -66,7 +66,9 @@ async fn main() -> Result<()> {
let client = build_client_with_token(Some(&node_token));
info!("检查本地运行时二进制与基础数据文件,必要时从服务端拉取...");
let runtime = ensure_runtime(runtime_dir, &node_cfg.server_url, &client)
// 默认谱线表:项目级默认 gfATO.dat(全波段 18-23000Å,230 万线)。
// TaskSpec.linelist 可按工作流覆盖(executor 侧按需下载指定文件)。
let runtime = ensure_runtime(runtime_dir, &node_cfg.server_url, &client, "gfATO.dat")
.await
.context("预热与获取服务端运行时资源失败")?;