将 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 笔误
141 lines
7.4 KiB
Rust
141 lines
7.4 KiB
Rust
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):成功率 **42–54%**
|
||
/// - 富金属方向(目标更富、delta > 0):成功率仅 **3–11%**
|
||
/// (每个 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);
|
||
}
|
||
}
|