feat(all): seed_step_stab 稳定化种子链与同族种子轮换、ladder/自适应 Teff/丰度轴延拓三级回退与梯级种子入库复用、热启动首末比豁免、workflow 完成翻转回退链阻塞修复、大气收敛权威统计与 M14 回填
收敛攻坚(81/400 失败点根因与实测,见 docs/failed81_cno_seed_popzer_dpsilg_2026_08_18.md): - runner: 新增 seed_step_stab 策略链——同物理族(同 Teff/logg/logHe)CNO 邻居种子 + DPSILG=3.0 λ 算子欠松弛 + POPZER=1E-10 微布居置零(联动 POPZR2/RADZER 同值 + NITZER=1),针对 20kK He 富大气 He I/II 电离前沿布居极限环 - runner: 三级链内延拓回退(主链与 nl_direct 全败后自动触发): · 固定 ladder 步进——plan_ladder_steps 按归一化间隔选轴,Δlogg≤0.25/ΔTeff≤2.5kK,≤4 步 · 自适应 Teff 延拓——步长 1250K 起、成功 ×1.5 恢复、失败二分至 25K 折叠墙,≤48 步 · C/N 丰度轴延拓——高温域(Teff>30kK)专用,严格同族种子沿 C(优先)/N 轴 0.2 dex 起步 延拓阶段 NITER 下限提至 300(慢收敛 waypoint 迭代饥饿误判修复) - runner: 稳定化多档回退(DPSILG/POPZER 三档互补,联合回收 41/65); 域门控 Teff≤30kK——高温高金属域实测旋钮致散(17 拍爆至 4e16),域外跳过 - 梯级种子持久化: 收敛中间模型登记 ladder_seeds 随上报落 server seeds 表 (任务失败也上传,合成名 _ladder 与真实网格点零冲突),簇内相邻失败点自动复用 调度与执行: - scheduler: 策略解析新增 seed_step_stab 臂——find_exact_family_seed_from_db 严格 同物理族判定(不做 global 退化,防 ladder 中间种子 ΔTeff≤5000K 误命中), 排除本点历史已用种子实现重试轮换;链在 stab 耗尽时轮换未试过同族邻居重派 - executor: seed_step_stab 补种子下载(漏列曾致 78 任务假失败,seed_nc 无 fort.8 崩溃); Teff>30kK 域外自动降级普通种子链 收敛判据: - conv_check: 热启动豁免——首拍 max_relc<1(种子已近解)时首末比 1e3 判据数学上 不可达,豁免后交五重物理硬门槛裁决(修复 nl_ladder 0.038→6e-4 物理全过被误杀); 冷启动仍受判据门控 workflow 生命周期: - workflows/tasks: 完成 flip 增加「未消费回退链」阻塞子句——failed 点策略链未耗尽 或链尾 seed_step_stab 尚有未试过同族种子时不得置 completed(修复最后活跃点 cold 失败上报抢先 flip、still_running 守卫拦截后续策略永不派发);按 failed_stage 归因 (synspec 失败行只看 synspec 链,防 stale 审计副本永久卡死)+ json_valid 脏行防护 统计与前端: - 统计新增权威口径 tlusty_converged(不按策略拆)与 seed_step_stab_converged 分项, 前端详情页色带/概览卡消费权威总数并新增稳定化青色段(修复 stab 收敛点漏计, 生产 9137/9216 差额);M14 迁移回填历史 completed 点的 tlusty_status 文档: - 新增 failed81 POPZER/DPSILG 制胜配方根因分析、Windows 节点经跳板 RDP 运维手册; failed400 增补 ladder 生产化实现与第二轮 121 残点实测矩阵
This commit is contained in:
@@ -1147,6 +1147,15 @@ pub struct ChainStep {
|
||||
pub idlte: Option<i32>,
|
||||
pub iacc: Option<i32>,
|
||||
pub orelax: Option<f64>,
|
||||
/// DPSILG(λ 算子欠松弛上限,默认不生效)。难收敛角落(如 20kK He 富大气的
|
||||
/// He I/II 电离前沿布居极限环)用 3.0 抑制——2026-08-18 实测,见
|
||||
/// docs/failed81_cno_seed_popzer_dpsilg_2026_08_18.md。
|
||||
#[serde(default)]
|
||||
pub dpsilg: Option<f64>,
|
||||
/// POPZER(微布居置零阈值)。设置时同时写 POPZR2/RADZER=同值 + NITZER=1,
|
||||
/// 把可忽略微布居从方程中剔除(官方标准输入恒设 1E-20;难收敛角落用 1E-10)。
|
||||
#[serde(default)]
|
||||
pub popzer: Option<f64>,
|
||||
}
|
||||
|
||||
fn default_false_str() -> String {
|
||||
@@ -1991,6 +2000,8 @@ mod tests {
|
||||
idlte: None,
|
||||
iacc: None,
|
||||
orelax: None,
|
||||
dpsilg: None,
|
||||
popzer: None,
|
||||
};
|
||||
let input5 = make_input5(&grid, &chain, &yaml_loaded);
|
||||
// 兜底后应含完整 ions 能级数据(而非只有终止行)
|
||||
|
||||
@@ -719,7 +719,14 @@ pub fn check_convergence_trace(
|
||||
.windows(2)
|
||||
.all(|w| w[0] >= w[1] * 0.5); // 容忍 Ng 加速的局部反弹(×2 内)
|
||||
|
||||
let valid = ratio >= min_ratio;
|
||||
// 热启动豁免(2026-08-18 修复):首拍 max_relc < 1 说明种子已接近收敛解,
|
||||
// 首末比在此情形下数学上不可能达到 1e3 量级(首拍被钳在低位),判据失效。
|
||||
// 误杀实例:t55000_g5.0_he-4_c-3_n-4_o-1 的 nl_ladder 阶段(首 0.038→末 6e-4,
|
||||
// 四项物理硬门全过)被否决为未收敛。真伪收敛的最终裁决交给五重物理硬门槛。
|
||||
const HOT_START_FIRST_MAX_RELC: f64 = 1.0;
|
||||
let hot_start_exempt = first < HOT_START_FIRST_MAX_RELC;
|
||||
|
||||
let valid = ratio >= min_ratio || hot_start_exempt;
|
||||
Some(ConvergenceTraceCheckResult {
|
||||
valid,
|
||||
first_max_relc: first,
|
||||
@@ -729,7 +736,13 @@ pub fn check_convergence_trace(
|
||||
n_iters: itek.len(),
|
||||
min_ratio,
|
||||
error: if valid {
|
||||
None
|
||||
if hot_start_exempt && ratio < min_ratio {
|
||||
Some(format!(
|
||||
"热启动豁免:首拍 max_relc {first:.1e} < {HOT_START_FIRST_MAX_RELC},首末比判据不适用"
|
||||
))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
Some(format!(
|
||||
"假收敛排查失败:首末 max_relc 比 {:.1e} < {:.0e}(可能 Ng 加速伪收敛)",
|
||||
@@ -1509,6 +1522,35 @@ mod tests {
|
||||
assert!(res.error.is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_convergence_trace_hot_start_exempt() {
|
||||
// 热启动豁免(2026-08-18):首拍 < 1(种子已接近解)时首末比数学上达不到 1e3,
|
||||
// 旧判据误杀(实例 t55000_g5.0_he-4_c-3_n-4_o-1 nl_ladder:0.038→6e-4,物理硬门全过)。
|
||||
let itek: Vec<IterCheck> = (0..7)
|
||||
.map(|i| IterCheck {
|
||||
iter: i + 1,
|
||||
max_relc: 0.038 / (1.0 + i as f64),
|
||||
n_depths: 50,
|
||||
})
|
||||
.collect();
|
||||
let res = check_convergence_trace(&itek, 1000.0).expect("≥3拍应判定");
|
||||
assert!(res.valid, "热启动首拍 <1 应回退到物理硬门槛裁决,不应判假收敛");
|
||||
assert!(res.ratio < 1000.0);
|
||||
assert!(res.error.is_some(), "豁免时应带豁免说明");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_convergence_trace_cold_start_still_gated() {
|
||||
// 冷启动(首拍 >= 1)不受豁免影响:比值不足仍判假收敛嫌疑。
|
||||
let itek: Vec<IterCheck> = vec![
|
||||
IterCheck { iter: 1, max_relc: 5.0, n_depths: 50 },
|
||||
IterCheck { iter: 2, max_relc: 2.0, n_depths: 50 },
|
||||
IterCheck { iter: 3, max_relc: 0.05, n_depths: 50 },
|
||||
];
|
||||
let res = check_convergence_trace(&itek, 1000.0).expect("≥3拍应判定");
|
||||
assert!(!res.valid, "冷启动首拍 ≥1 且比值 <1000 仍应失败");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_convergence_trace_too_short() {
|
||||
// 2拍:无法判定 → None
|
||||
|
||||
@@ -232,6 +232,8 @@ mod tests {
|
||||
idlte: None,
|
||||
iacc: None,
|
||||
orelax: None,
|
||||
dpsilg: None,
|
||||
popzer: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -223,6 +223,27 @@ impl GridPointParams {
|
||||
)
|
||||
}
|
||||
|
||||
/// 从规范网格点名解析参数:`t60000_g5.5_he-2_c-4_n-4_o-4` → GridPointParams。
|
||||
/// 接受可选的 `_ladder` 等后缀(ladder 合成种子名)。解析失败返回 None。
|
||||
/// 用途:node 端从 seed_point_name 取种子参数(ladder 步进规划)、
|
||||
/// server 端从上传文件名取中间种子参数。
|
||||
pub fn parse_point_name(name: &str) -> Option<Self> {
|
||||
let re = regex::Regex::new(
|
||||
r"^t(\d+(?:\.\d+)?)_g(-?\d+(?:\.\d+)?)_he(-?\d+(?:\.\d+)?)_c(-?\d+(?:\.\d+)?)_n(-?\d+(?:\.\d+)?)_o(-?\d+(?:\.\d+)?)(?:_.*)?$",
|
||||
)
|
||||
.ok()?;
|
||||
let caps = re.captures(name)?;
|
||||
let v = |i: usize| -> Option<f64> { caps.get(i)?.as_str().parse::<f64>().ok() };
|
||||
Some(GridPointParams {
|
||||
teff: GridAxisValue::from_value(v(1)?),
|
||||
logg: GridAxisValue::from_value(v(2)?),
|
||||
loghe: GridAxisValue::from_value(v(3)?),
|
||||
logc: GridAxisValue::from_value(v(4)?),
|
||||
logn: GridAxisValue::from_value(v(5)?),
|
||||
logo: GridAxisValue::from_value(v(6)?),
|
||||
})
|
||||
}
|
||||
|
||||
/// CNO 对数丰度之和 (`logc + logn + logo`)。
|
||||
///
|
||||
/// 注:此数值专门用于网格调度中的 Wave 难度分级与保序分组(对数和越小代表重元素丰度越低,
|
||||
@@ -832,9 +853,33 @@ pub struct ModelSummary {
|
||||
/// `.bfac` b 因子合理性校验结果。None = 未做校验。
|
||||
#[serde(default)]
|
||||
pub bfac_check: Option<BfacCheckResult>,
|
||||
/// ladder 步进链的中间收敛模型(node→server 种子持久化清单)。空 = 未触发 ladder
|
||||
/// 或无合格中间步。中间模型已过收敛 + NaN 否决,可安全入种子库供相邻失败点复用。
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub ladder_seeds: Vec<LadderSeedInfo>,
|
||||
pub note: Option<String>,
|
||||
}
|
||||
|
||||
/// ladder 步进链中间种子信息。
|
||||
///
|
||||
/// `point_name` 是合成种子名(中间参数 model_name + `_ladder` 后缀),与任何真实
|
||||
/// 网格点名不冲突;server 端按其中的参数落 seeds 表(bucket 查找按 teff/logg/loghe
|
||||
/// 数值列匹配,天然覆盖中间参数点)。
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct LadderSeedInfo {
|
||||
/// 产生该模型的阶段标签(如 `ladder_g6.25`)。
|
||||
pub label: String,
|
||||
/// 合成种子名(server 端 seeds.point_name / 磁盘文件名,不含 .7 后缀)。
|
||||
pub point_name: String,
|
||||
/// 中间步大气参数(teff/logg 为中间值,丰度同目标点)。
|
||||
pub teff: f64,
|
||||
pub logg: f64,
|
||||
pub loghe: f64,
|
||||
pub logc: f64,
|
||||
pub logn: f64,
|
||||
pub logo: f64,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
@@ -82,6 +82,21 @@ pub fn generate_nst_content(chain: &ChainStep, global: &TlustyInput) -> String {
|
||||
line2.push(format!("ICHANG={}", ichang));
|
||||
written_keys.insert("ICHANG".to_string());
|
||||
}
|
||||
if let Some(dpsilg) = chain.dpsilg {
|
||||
line2.push(format!("DPSILG={}", dpsilg));
|
||||
written_keys.insert("DPSILG".to_string());
|
||||
}
|
||||
if let Some(popzer) = chain.popzer {
|
||||
// POPZER 联动组:官方标准输入恒 POPZER=POPZR2=RADZER + NITZER=1
|
||||
// (tests/tlusty/*/.6 回显),缺一则置零机制不完整。
|
||||
line2.push(format!("POPZER={:.E}", popzer));
|
||||
line2.push(format!("POPZR2={:.E}", popzer));
|
||||
line2.push(format!("RADZER={:.E}", popzer));
|
||||
line2.push("NITZER=1".to_string());
|
||||
for k in ["POPZER", "POPZR2", "RADZER", "NITZER"] {
|
||||
written_keys.insert(k.to_string());
|
||||
}
|
||||
}
|
||||
line2.push(format!("IELCOR={}", n.physics.ielcor));
|
||||
written_keys.insert("IELCOR".to_string());
|
||||
push_wrapped(&mut out, &line2);
|
||||
@@ -224,6 +239,8 @@ mod tests {
|
||||
idlte: None,
|
||||
iacc: None,
|
||||
orelax: None,
|
||||
dpsilg: None,
|
||||
popzer: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -331,6 +348,35 @@ mod tests {
|
||||
assert!(!content.contains("FRLMIN"), "FRLMIN=0 不应写出");
|
||||
}
|
||||
|
||||
/// DPSILG/POPZER 稳定化旋钮(2026-08-18,seed_step_stab 策略):
|
||||
/// dpsilg → DPSILG=<val>;popzer → POPZER/POPZR2/RADZER 三键同值 + NITZER=1
|
||||
/// (官方标准输入联动组),且行宽仍受 75 字符保护(自动换行)。
|
||||
#[test]
|
||||
fn test_nst_dpsilg_popzer_stabilization() {
|
||||
let mut chain = chain_nc();
|
||||
chain.dpsilg = Some(3.0);
|
||||
chain.popzer = Some(1e-10);
|
||||
let content = generate_nst_content(&chain, &TlustyInput::default());
|
||||
assert!(content.contains("DPSILG=3"), "DPSILG 应写出: {}", content);
|
||||
assert!(content.contains("POPZER=1E-10"), "POPZER 应以 Fortran E 格式写出");
|
||||
assert!(content.contains("POPZR2=1E-10"));
|
||||
assert!(content.contains("RADZER=1E-10"));
|
||||
assert!(content.contains("NITZER=1"), "POPZER 联动 NITZER=1");
|
||||
for (i, line) in content.lines().enumerate() {
|
||||
assert!(
|
||||
line.chars().count() <= 75,
|
||||
"nst 第 {} 行超宽({} 字符): {}",
|
||||
i + 1,
|
||||
line.chars().count(),
|
||||
line
|
||||
);
|
||||
}
|
||||
// 默认(None)不写出,保持字节级兼容
|
||||
let plain = generate_nst_content(&chain_nc(), &TlustyInput::default());
|
||||
assert!(!plain.contains("DPSILG"));
|
||||
assert!(!plain.contains("POPZER"));
|
||||
}
|
||||
|
||||
/// fmt_real 边界:-0.0 归一为 "0."(审查 Nit-1)。
|
||||
#[test]
|
||||
fn test_fmt_real_negative_zero() {
|
||||
|
||||
+904
-2
File diff suppressed because it is too large
Load Diff
@@ -109,6 +109,7 @@ mod tests {
|
||||
temp_check: None,
|
||||
emflux_check: None,
|
||||
bfac_check: None,
|
||||
ladder_seeds: Vec::new(),
|
||||
note: None,
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user