feat(all): 科学计算正确性修复、调度竞态消除、节点优雅退出、安全加固与收敛分析重构

科学计算正确性:
- 修复 Fortran 无-E 科学记数法(指数≥100 时 E 被挤掉,如 -1.35+118)导致
  发散行被静默跳过、误判收敛的 bug;扩展大气无效检测覆盖 Inf 与 *** 溢出标记
- 种子匹配改为 CNO 有向距离(富金属方向重罚 4×、贫金属方向轻罚 1×),
  基于 1191 个真实种子配对回测标定,回测净改善 314 个点
- GridAxisValue 反序列化拒绝非法文本(不再静默 NaN);chmax≤0 显式报错
- ions 行宽列宽对齐真实 fort.5 格式

调度与队列竞态:
- 原子选点(IMMEDIATE 事务 SELECT+UPDATE)消除并发调度重复派发 (#5)
- 调度互斥锁 + 冷启动优先策略(SeedStep 仅作失败后救援,不再正常路径热启动)
- 毒消息 dead_letter 标记防出队死循环;clear_queue 保留 claimed 行 (#6)
- 孤儿 running 点回收兜底;stale_sec 默认 7800→21600s(3 倍超时缓冲)

节点生命周期:
- SIGTERM+SIGINT 双信号监听(修复 Docker stop 发 SIGTERM 不触发优雅退出)
- SlotGuard RAII 防活动 slot 泄漏;子进程超时增加二级 30s wait 防 Fortran hang
- SeedStep 种子下载 fail-fast + 沙盒私有副本解耦 LRU 清理竞争
- reqwest Client 增加连接/请求超时;启动清理残留 task_* 沙盒

安全加固:
- 节点注册 registration_secret 二次凭据 (H8),恒定时间比对防时序旁路
- token 缓存 generation 机制消除 reissue 后旧 token TOCTOU 复活窗口
- 新增 /api/auth/logout 服务端 session 即时撤销;fail-closed 鉴权启动策略
- 前端 token 迁移 sessionStorage;YAML 高亮改 DOM API 消除 XSS 注入面
- 备份文件权限收紧 0600;点表动态值全面 escapeHtml

前端 Dashboard:
- 收敛性分析从热力图重构为 Parallel Sets 平行集合图(6 维+状态轴,手写 SVG 零依赖)
- 进度曲线横轴改为真实时间(服务端 now 锚定,停滞期诚实留白);轮询指数退避
- 移除 imported 收敛途径分类,导入点按实际途径 cold_run/seed_step 归类
- 初始化时 /api/auth/check 校验 token;401 toast 提示替代静默 reload

服务端恢复与工具链:
- 启动恢复 initializing 态工作流;默认工作流 INSERT-only 不覆盖 API 编辑
- body limit 分层(10MB 不再截断 256MB report);multipart 显式错误处理
- 嵌入二进制原子写(tmp+rename)防半写损坏
- import_results 判定收敛途径透传 success_method;conv.json 格式对齐本项目
- push_import_results.sh 退出码修复 + .bat UTF-8 BOM + scp 上传
- Docker USE_MIRRORS 默认关闭;移除无用 assets 挂载;删除 hosts.ini 入库
This commit is contained in:
fmq
2026-08-01 17:01:40 +08:00
parent 1bfa240cb0
commit c8fd24b120
45 changed files with 2821 additions and 831 deletions
+95 -6
View File
@@ -10,29 +10,118 @@ pub struct SeedMatch {
pub const MAX_GLOBAL_SEED_DISTANCE: f64 = 3.0;
/// CNO 有向距离:富金属方向(目标比种子富)重罚,贫金属方向(目标比种子贫)轻罚。
///
/// **数据标定依据**——对历史 1191 个真实 seed_step(种子,目标)配对的成败统计:
/// - 贫金属方向(种子更富、目标往贫走,delta=目标−种子 < 0):成功率 **4254%**
/// - 富金属方向(目标更富、delta > 0):成功率仅 **311%**
/// (每个 loghe 分层该规律独立成立,he=−4 时贫方向 54% vs 富方向 3%,差 18 倍)
///
/// **物理解释**:从高金属丰度的收敛解出发**减少**金属(贫方向)是稳定微扰;
/// 反过来从贫金属种子**增加**金属(富方向),新增的紫外谱线辐射驱动会破坏已建立的
/// 辐射平衡,导致大气发散出现 NaN。这是 NLTE 辐射流体计算的已知特性。
///
/// **回测**:对 871 个失败点的 exact_family 种子池回测,旧版绝对值距离有 55% 选了糟糕的
/// 富方向种子;改用此非对称距离后,81% 选贫方向,净改善 314 个点(旧选富→新选贫)。
fn directed_cno_distance(cand: &GridPointParams, target: &GridPointParams) -> f64 {
const RICH_PENALTY: f64 = 4.0; // 目标比种子富 → 该方向微扰不稳定,重罚
const POOR_PENALTY: f64 = 1.0; // 目标比种子贫 → 该方向微扰稳定,轻罚
// delta = target cand:正 = 目标更富(坏方向),负 = 目标更贫(好方向)
let penalize = |delta: f64| if delta > 0.0 { delta * RICH_PENALTY } else { -delta * POOR_PENALTY };
penalize(target.logc.value() - cand.logc.value())
+ penalize(target.logn.value() - cand.logn.value())
+ penalize(target.logo.value() - cand.logo.value())
}
pub fn calculate_seed_distance(cand: &GridPointParams, target: &GridPointParams) -> (bool, f64) {
let d_teff = (cand.teff.value() - target.teff.value()).abs();
let d_logg = (cand.logg.value() - target.logg.value()).abs();
let d_loghe = (cand.loghe.value() - target.loghe.value()).abs();
let d_cno = (cand.logc.value() - target.logc.value()).abs()
+ (cand.logn.value() - target.logn.value()).abs()
+ (cand.logo.value() - target.logo.value()).abs();
// exact family 判定:Teff/logg/logHe 视为“同物理族”,仅 CNO 丰度不同。
// Teff 容忍度取半步 5000K:实际网格 Teff 档位通常为整数千(20000/30000/.../60000),
// 半步既能覆盖 config_dense 等 10000K 步长的相邻档互作种子,
// 又避免跨过大 Teff 间距导致 sdB 高温模型用低温种子而不收敛(sdB_cno 步长 40000K 仍不命中 exact)。
if d_teff < 5000.0 && d_logg < 0.01 && d_loghe < 0.01 {
(true, d_cno)
// 同物理族内仅 CNO 不同:用有向距离优先匹配贫金属方向的种子(见 directed_cno_distance)。
(true, directed_cno_distance(cand, target))
} else {
// 距离公式物理意义与标定阐释:
// 在恒星非局部热力学平衡(NLTE)辐射流体力学与光谱大气计算中,不同物理自由度对于迭代收敛过程的基本影响层级截然相反:
// 1. Teff (有效温度) 通常达数千至数十万 K,主导连续谱黑体势函数与强激发电离步阶,故除以 5000.0 归一化为基底主控距离量;
// 2. logg (表面重力加速度) 对静力学与辐射光致压差梯度的平衡破坏力极烈,压强差稍高会触发极大激波不平衡,因此乘上 2.0 予以最高维权惩罚;
// 3. loghe (氦丰度) 对自由电子密度与热库贡献次于 H-He 电离梯度,乘 0.5 作为次要控制项;
// 4. CNO 金属元素虽然影响紫外谱线辐射驱动但整体状态基本可作次优微扰微增系数看待,乘 0.1;
// 通过上述尺度正态映射可挑选得到高收敛继承性的初态迭代种子模型
// 4. CNO 金属元素影响紫外谱线辐射驱动,其方向性同样关键(富方向不稳定,见 directed_cno_distance),
// 乘 0.1 归一化后纳入全局距离
let d_cno = directed_cno_distance(cand, target);
let global_d = (d_teff / 5000.0) + (d_logg * 2.0) + (d_loghe * 0.5) + (d_cno * 0.1);
(false, global_d)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::models::GridAxisValue;
fn params(teff: f64, logg: f64, loghe: f64, logc: f64, logn: f64, logo: f64) -> GridPointParams {
GridPointParams {
teff: GridAxisValue::from_value(teff),
logg: GridAxisValue::from_value(logg),
loghe: GridAxisValue::from_value(loghe),
logc: GridAxisValue::from_value(logc),
logn: GridAxisValue::from_value(logn),
logo: GridAxisValue::from_value(logo),
}
}
/// exact_family 内,同 CNO 差幅度下,贫方向距离应远小于富方向距离(4:1)。
#[test]
fn test_directed_cno_distance_favors_poor_metal_direction() {
let seed = params(40000.0, 6.0, -2.0, -2.0, -2.0, -2.0); // 种子 CNO=-6
// 贫方向:目标 CNO=-7(种子更富,目标往贫走),|Δ|=1
let poor_target = params(40000.0, 6.0, -2.0, -3.0, -2.0, -2.0);
// 富方向:目标 CNO=-5(目标更富),|Δ|=1,同一分量、同幅度
let rich_target = params(40000.0, 6.0, -2.0, -1.0, -2.0, -2.0);
let d_poor = directed_cno_distance(&seed, &poor_target);
let d_rich = directed_cno_distance(&seed, &rich_target);
assert!(d_poor < d_rich, "贫方向应更近");
assert!(
(d_rich / d_poor - 4.0).abs() < 1e-9,
"富/贫方向同幅度距离比应为 RICH/POOR=4.0,实际 {} / {} = {}",
d_rich,
d_poor,
d_rich / d_poor
);
}
/// exact_family 选种应在多个候选中优先选贫方向种子,即便其 CNO 绝对差更大。
#[test]
fn test_exact_family_prefers_poor_direction_seed() {
let target = params(40000.0, 6.0, -2.0, -2.0, -2.0, -2.0); // 目标 CNO=-6
// 候选A:富方向种子(目标比种子富),CNO 绝对差=1
let rich_seed = params(40000.0, 6.0, -2.0, -3.0, -2.0, -2.0); // CNO=-7, 目标更富
// 候选B:贫方向种子(目标比种子贫),CNO 绝对差=2(更大)
let poor_seed = params(40000.0, 6.0, -2.0, -1.0, -1.0, -2.0); // CNO=-4, 目标更贫
let (_, d_rich) = calculate_seed_distance(&rich_seed, &target);
let (_, d_poor) = calculate_seed_distance(&poor_seed, &target);
// 贫种子虽 CNO 绝对差更大(2 vs 1),但因方向有利,距离应更小
assert!(
d_poor < d_rich,
"贫方向种子距离 {} 应小于富方向 {}(即便 CNO 绝对差更大)",
d_poor,
d_rich
);
}
/// 跨 teff(非 exact)时仍标记 global,且方向性体现在 global_d 里。
#[test]
fn test_global_branch_marks_non_exact_and_keeps_direction() {
let target = params(40000.0, 6.0, -2.0, -2.0, -2.0, -2.0);
// 跨 teff 20000K(超过 exact 容忍 5000K)→ global
let cand = params(60000.0, 6.0, -2.0, -2.0, -2.0, -2.0);
let (is_exact, d) = calculate_seed_distance(&cand, &target);
assert!(!is_exact, "跨 teff 20000K 应为 global 分支");
assert!(d > 0.0);
}
}