Files
DCTS/crates/common/src/seed_finder.rs
T
fmq b058e66722 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 篇根因分析/验证文档
2026-08-17 23:55:26 +08:00

156 lines
8.3 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 档位间隔正好 5000K(如 55000↔60000 相邻档),旧版严格 `< 5000.0`
// 把所有相邻 Teff 档互为种子静默排除出 exact_family——而实测 Teff 方向是最稳的
// 种子方向(55k 点靠 50k Teff 种子收敛;50k→60k 直接 nl 亦收敛),且相邻档
// 同(logg,He,CNO)种子在 exact_family 内有向距离为 0(优于任何同 Teff CNO 邻居)。
// 桶索引(db::SeedBucketKey)本就按 floor/floor+1 双写,5000K 整除边界可命中。
// 2026-08-14 修复,详见 docs/tlusty_coldstart_nc_trace_illcond_2026_08_13.md。
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);
}
/// 回归(2026-08-14):d_teff 恰为 5000K(相邻 Teff 档,如 55000↔60000
/// 应算 exact_family(旧版严格 < 排除了所有相邻档互为种子)。
#[test]
fn test_adjacent_teff_bucket_is_exact_family() {
let seed = params(55000.0, 5.0, -2.0, -4.0, -3.0, -3.0);
let target = params(60000.0, 5.0, -2.0, -4.0, -3.0, -3.0);
let (is_exact, d) = calculate_seed_distance(&seed, &target);
assert!(is_exact, "d_teff=5000 的相邻 Teff 档应属 exact_family");
assert_eq!(d, 0.0, "同 (logg,He,CNO) 的相邻 Teff 种子有向距离应为 0");
}
}