Files
DCTS/crates/common/src/seed_finder.rs
T
fmq cd370d88e7 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 笔误
2026-08-04 23:40:52 +08:00

141 lines
7.4 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
use crate::models::GridPointParams;
use std::path::PathBuf;
#[derive(Debug, Clone)]
pub struct SeedMatch {
pub name: String,
pub path: PathBuf,
pub distance: f64,
}
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();
// 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 {
// 同物理族内仅 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 金属元素影响紫外谱线辐射驱动,其方向性同样关键(富方向不稳定,见 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);
}
}