//! 能级粒子数初始化控制器。 //! //! 重构自 TLUSTY `change.f`。 //! //! 功能: //! - 当显式能级系统与输入模型不一致时,控制初始能级占据数的评估 //! - 支持多种模式来计算新能级的粒子数 //! - 使用 STEQEQ 计算 LTE 粒子数 use crate::tlusty::math::SteqeqConfig; use crate::tlusty::state::constants::{BOLK, MDEPTH}; // f2r_depends: READBF(I/O layer), STEQEQ /// CHANGE 配置参数 #[derive(Debug, Clone)] pub struct ChangeConfig { /// ICHANG 参数 /// < 0: 通用粒子数变化 /// > 0: 简化变化,ICHANG 是旧模型数据文件的单元号 pub ichang: i32, /// 旧模型的能级数 pub nlev0: i32, /// 是否 LTE pub lte: bool, /// Saha 常数 pub saha_const: f64, } impl Default for ChangeConfig { fn default() -> Self { Self { ichang: 0, nlev0: 0, lte: false, saha_const: 2.0706e-16, // S = 2.0706D-16 } } } /// 能级映射信息(用于 MODE 0-2) #[derive(Debug, Clone)] pub struct LevelMapping { /// 当前能级索引 pub ii: usize, /// IOLD: 旧能级索引(0 表示无对应) pub iold: i32, /// MODE: 粒子数计算模式 pub mode: i32, /// NXTOLD: 下一电离态的旧能级索引 pub nxtold: i32, /// ISINEW: 新能级索引(用于 b-因子) pub isinew: i32, /// ISIOLD: 旧能级索引(用于 b-因子) pub isiold: i32, /// NXTSIO: 下一电离态的旧索引 pub nxtsio: i32, /// REL: 粒子数乘数 pub rel: f64, } /// STEQEQ 计算闭包包装 pub struct SteqeqFnWrapper(pub Box Vec>); impl std::fmt::Debug for SteqeqFnWrapper { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "SteqeqFnWrapper") } } /// CHANGE 输入参数 pub struct ChangeParams<'a> { /// 配置参数 pub config: ChangeConfig, /// 深度点数 pub nd: usize, /// 能级数 pub nlevel: usize, /// 离子数 pub nion: usize, /// 原子数 pub natom: usize, /// 温度数组 pub temp: &'a [f64], /// 电子密度数组 pub elec: &'a [f64], /// 当前粒子数 [能级 × 深度] pub popul: &'a [[f64; MDEPTH]], /// 能级权重 g pub g: &'a [f64], /// 电离能 [能级] pub enion: &'a [f64], /// 能级所属元素索引 [能级] pub iel: &'a [i32], /// 下一能级索引 [能级] pub nnext: &'a [i32], /// 离子起始能级 [离子] pub nfirst: &'a [i32], /// 离子结束能级 [离子] pub nlast: &'a [i32], /// 能级所属原子 [能级] pub iatm: &'a [i32], /// 原子编号 [原子] pub numat: &'a [i32], /// 能级电荷 [能级] pub iz: &'a [i32], /// 原子能级范围起始 [原子] pub nka: &'a [i32], /// 能级映射(仅用于 ICHANG < 0) pub level_mappings: Vec, /// STEQEQ 配置 pub steqeq_config: SteqeqConfig, /// 占据概率 [能级 × 深度] pub wop: &'a [[f64; MDEPTH]], /// STEQEQ 计算函数:给定深度点索引(0-based),返回 [nlevel] 个 LTE 粒子数 pub steqeq_fn: Option, } /// CHANGE 输出结果 #[derive(Debug, Clone)] pub struct ChangeOutput { /// 更新后的粒子数 [能级 × 深度] pub popul: Vec>, /// 临时粒子数(LTE) pub popull: Vec>, /// 是否成功 pub success: bool, } /// 处理能级粒子数变化(纯计算函数)。 /// /// # 参数 /// * `params` - 输入参数 /// /// # 返回值 /// 包含更新后的粒子数 pub fn change_pure(params: &ChangeParams) -> ChangeOutput { let nd = params.nd; let nlevel = params.nlevel; let config = ¶ms.config; // 初始化输出数组 let mut popul0 = vec![vec![0.0; nd]; nlevel]; let mut popull = vec![vec![0.0; nd]; nlevel]; let mut popul_new = vec![vec![0.0; nd]; nlevel]; // 复制当前粒子数 for ii in 0..nlevel { for id in 0..nd { popul_new[ii][id] = params.popul[ii][id]; } } if config.ichang < 0 { // 通用粒子数变化 handle_general_change(params, &mut popul0, &mut popull, &mut popul_new); } else if config.ichang > 0 { // 简化变化 handle_simplified_change(params, &mut popul0, &mut popull, &mut popul_new); } ChangeOutput { popul: popul_new, popull, success: true, } } /// 处理通用粒子数变化(ICHANG < 0) fn handle_general_change( params: &ChangeParams, popul0: &mut [Vec], popull: &mut [Vec], popul_new: &mut [Vec], ) { let nd = params.nd; let nlevel = params.nlevel; let config = ¶ms.config; let mut ifese = 0; for mapping in ¶ms.level_mappings { let ii = mapping.ii; let mode = mapping.mode; let iold = mapping.iold; let rel = if mapping.rel == 0.0 { 1.0 } else { mapping.rel }; if mode >= 3 { ifese += 1; } for id in 0..nd { if iold != 0 { // 直接使用旧粒子数 popul0[ii][id] = params.popul[(iold - 1) as usize][id]; } else { match mode { 0 => { // MODE 0: 粒子数 = 旧粒子数 × REL popul0[ii][id] = params.popul[(mapping.isiold - 1) as usize][id] * rel; } 1 | 2 => { // MODE 1/2: 使用 Saha 方程 let t = params.temp[id]; let ane = params.elec[id]; // 计算 Saha 因子 let nxtnew = params.nnext[params.iel[ii] as usize] as usize; let sb = config.saha_const / t / t.sqrt() * params.g[ii] / params.g[nxtnew] * (params.enion[ii] / t / BOLK).exp(); if mode == 1 { // MODE 1: LTE 相对于下一电离态 popul0[ii][id] = sb * ane * params.popul[(mapping.nxtold - 1) as usize][id] * rel; } else { // MODE 2: 使用 b-因子 let kk = mapping.isinew as usize; let knext = params.nnext[params.iel[kk] as usize] as usize; let sbk = config.saha_const / t / t.sqrt() * params.g[kk] / params.g[knext] * (params.enion[kk] / t / BOLK).exp(); popul0[ii][id] = sb / sbk * params.popul[(mapping.nxtold - 1) as usize][id] / params.popul[(mapping.nxtsio - 1) as usize][id] * params.popul[(mapping.isiold - 1) as usize][id] * rel; } } _ => { // MODE >= 3: LTE if ifese == 1 { // 首次遇到 MODE>=3,调用 STEQEQ 计算 LTE 粒子数 if let Some(ref steqeq_fn) = params.steqeq_fn { let popl = (steqeq_fn.0)(id); for iii in 0..nlevel { popull[iii][id] = popl[iii]; } } } popul0[ii][id] = popull[ii][id]; } } } } } // 复制结果到输出 for ii in 0..nlevel { for id in 0..nd { popul_new[ii][id] = popul0[ii][id]; } } } /// 处理简化粒子数变化(ICHANG > 0) fn handle_simplified_change( params: &ChangeParams, popul0: &mut [Vec], _popull: &mut [Vec], popul_new: &mut [Vec], ) { let nd = params.nd; let nlevel = params.nlevel; let config = ¶ms.config; // 设置 LTE 标志并计算所有深度点的 LTE 粒子数 // Fortran: LTE=.TRUE.; DO ID=1,ND; CALL STEQEQ(ID,POPL,0); POPUL0(II,ID)=POPL(II) for id in 0..nd { if let Some(ref steqeq_fn) = params.steqeq_fn { let popl = (steqeq_fn.0)(id); for ii in 0..nlevel { popul0[ii][id] = popl[ii]; } } } // WRITE(6,600) - ' Levels: OLD model -> NEW model' eprintln!(" Levels: OLD model -> NEW model"); eprintln!(" ------------------------------"); if config.ichang == 1 { // 只更新新能级 for ii in config.nlev0 as usize..nlevel { // WRITE(6,601) JL,IL - '**** CHANGE: old, new =',2I5 eprintln!(" {:8} {:8}", ii, ii); for id in 0..nd { popul_new[ii][id] = popul0[ii][id]; } } } // 其他情况(ichang > 1)需要从文件读取旧模型数据 // 这部分在 I/O 层处理 } /// 计算 Saha-Boltzmann 因子 /// /// # 参数 /// * `s` - Saha 常数 /// * `t` - 温度 /// * `g1` - 能级 1 的统计权重 /// * `g2` - 能级 2 的统计权重 /// * `enion` - 电离能 /// /// # 返回值 /// Saha 因子 pub fn saha_factor(s: f64, t: f64, g1: f64, g2: f64, enion: f64) -> f64 { s / t / t.sqrt() * g1 / g2 * (enion / t / BOLK).exp() } #[cfg(test)] mod tests { use super::*; #[test] fn test_saha_factor() { let s = 2.0706e-16; let t = 10000.0; let g1 = 2.0; let g2 = 1.0; let enion = 13.6; let result = saha_factor(s, t, g1, g2, enion); println!("Saha factor: {}", result); // 基本检查:应该是正数 assert!(result > 0.0); } #[test] fn test_change_basic() { // 使用简化的测试参数 let nd = 5; let nlevel = 10; // 创建测试数据 let temp: Vec = (0..nd).map(|i| 10000.0 - i as f64 * 500.0).collect(); let elec: Vec = vec![1e13; nd]; let mut popul_arr = vec![[0.0; MDEPTH]; MLEVEL]; let mut wop_arr = vec![[0.0; MDEPTH]; MLEVEL]; for i in 0..nlevel { for j in 0..nd { popul_arr[i][j] = 1e10; wop_arr[i][j] = 1.0; } } let g = vec![2.0; MLEVEL]; let enion = vec![13.6; MLEVEL]; let iel: Vec = (0..MLEVEL as i32).collect(); let nnext: Vec = (1..=MLEVEL as i32).collect(); let nfirst = vec![0; 200]; let nlast = vec![0; 200]; let iatm = vec![1; MLEVEL]; let numat = vec![0; 100]; let iz = vec![0; MLEVEL]; let nka = vec![0; 100]; // 使用 Box::leak 创建 'static 引用 let temp: &'static [f64] = Box::leak(temp.into_boxed_slice()); let elec: &'static [f64] = Box::leak(elec.into_boxed_slice()); let popul: &'static [[f64; MDEPTH]] = Box::leak(popul_arr.into_boxed_slice().try_into().unwrap()); let g: &'static [f64] = Box::leak(g.into_boxed_slice().try_into().unwrap()); let enion: &'static [f64] = Box::leak(enion.into_boxed_slice().try_into().unwrap()); let iel: &'static [i32] = Box::leak(iel.into_boxed_slice().try_into().unwrap()); let nnext: &'static [i32] = Box::leak(nnext.into_boxed_slice().try_into().unwrap()); let nfirst: &'static [i32] = Box::leak(nfirst.into_boxed_slice().try_into().unwrap()); let nlast: &'static [i32] = Box::leak(nlast.into_boxed_slice().try_into().unwrap()); let iatm: &'static [i32] = Box::leak(iatm.into_boxed_slice().try_into().unwrap()); let numat: &'static [i32] = Box::leak(numat.into_boxed_slice().try_into().unwrap()); let iz: &'static [i32] = Box::leak(iz.into_boxed_slice().try_into().unwrap()); let nka: &'static [i32] = Box::leak(nka.into_boxed_slice().try_into().unwrap()); let wop: &'static [[f64; MDEPTH]] = Box::leak(wop_arr.into_boxed_slice().try_into().unwrap()); let params = ChangeParams { config: ChangeConfig::default(), nd, nlevel, nion: 5, natom: 1, temp, elec, popul, g, enion, iel, nnext, nfirst, nlast, iatm, numat, iz, nka, level_mappings: vec![], steqeq_config: SteqeqConfig::default(), wop, steqeq_fn: None, }; let output = change_pure(¶ms); // 检查输出维度 assert_eq!(output.popul.len(), nlevel); assert!(output.success); } }