//! He II 线不透明度和发射率计算。 //! //! 重构自 SYNSPEC `phe2.f`。 //! //! 使用 Schoening 和 Butler 计算的轮廓系数评估给定 He II 线的不透明度和发射率。 // ============================================================================ // 物理常数 // ============================================================================ /// 普朗克常数 × 光速 (erg·cm) #[allow(dead_code)] const HC: f64 = 1.986477e-16; /// He II 电离能 (cm⁻¹) #[allow(dead_code)] const HE2_IONIZATION_ENERGY: f64 = 438916.146; /// He II 线波长因子 (Å·cm⁻¹) /// WLIN = 227.838 / (1/II - 1/JJ) for n <= 2 /// WLIN = 227.7776 / (1/II - 1/JJ) for n > 2 const WLIN_FACTOR_1: f64 = 227.838; const WLIN_FACTOR_2: f64 = 227.7776; /// 玻尔兹曼常数相关的激发能因子 /// 631479 = HC * HE2_IONIZATION_ENERGY / k (简化形式) const EXCITATION_FACTOR: f64 = 631479.0; /// 萨哈因子常数 /// 4.1412E-16 / (k * sqrt(T)) 的前置因子 const SAHA_PREFACTOR: f64 = 4.1412e-16; /// 转换因子 (2.3025851 = ln(10)) const LN10: f64 = std::f64::consts::LN_10; /// 发射率转换因子 (1.4747E-2) const EMIS_FACTOR: f64 = 1.4747e-2; /// 频率转换因子 (1E-15) const FREQ_SCALE: f64 = 1e-15; /// 频率到波长转换因子 (Å·Hz) #[allow(dead_code)] const FREQ_TO_WAVE: f64 = 2.997925e17; // ============================================================================ // He II 振子强度数据 // ============================================================================ /// He II 线的振子强度 (OSCHE2) /// /// 对应 Fortran DATA 语句中的 19 个值。 /// 这些是 Schoening 和 Butler 计算的振子强度。 const OSCHE2: [f64; 19] = [ 6.407e-1, 1.506e-1, 5.584e-2, 2.768e-2, 1.604e-2, 1.023e-2, 6.980e-3, 8.421e-1, 3.230e-2, 1.870e-2, 1.196e-2, 8.187e-3, 5.886e-3, 4.393e-3, 3.375e-3, 2.656e-3, 1.038, 1.793e-1, 6.549e-2, ]; // ============================================================================ // 参数结构体 // ============================================================================ /// PHE2 输入参数。 /// /// 包含计算 He II 线不透明度所需的所有数据。 #[derive(Debug, Clone)] pub struct Phe2Params<'a> { /// 谱线索引 (ISPEC) /// 定义在 HE2INI 中,He II 线从 ISPEC=6 开始 pub ispec: i32, /// 深度索引 (ID) pub id: i32, /// He II 元素索引 (IELHE2) /// 0 表示不处理 He II NLTE pub ielhe2: i32, /// NLTE 模式标志 (INLTE) /// > 0 表示使用 NLTE 布居数 pub inlte: i32, /// 频率点数 (NFREQ) pub nfreq: i32, /// 频率数组 (FREQ, Hz) pub freq: &'a [f64], /// 波长数组 (WLAM, Å) pub wlam: &'a [f64], /// 温度 (TEMP, K) pub temp: f64, /// 电子密度 (ELEC) pub elec: f64, /// He III 布居数 (来自 POPUL 或 RRR) pub he3_pop: f64, /// He II NLTE 能级数 (NLHE2 = NLAST - NFIRST + 1) pub nlhe2: i32, /// He II 能级起始索引 (NFIRST) pub nfirst_he2: i32, /// 各能级布居数 (POPUL) pub popul: &'a [f64], /// He II 线轮廓数据 (PRFHE2) /// PRFHE2(ILINE, ID, IWL) - 线索引 × 深度 × 波长点 pub prfhe2: &'a [f64], /// He II 线波长表 (WLHE2) /// WLHE2(ILINE, IWL) - log10(|波长 - 线心波长|) pub wlhe2: &'a [f64], /// He II 线波长点数 (NWLHE2) pub nwlhe2: i32, /// He II 线下能级主量子数 (ILHE2) pub ilhe2: i32, /// He II 线上能级主量子数 (IUHE2) pub iuhe2: i32, /// 激光 delta 标志 (lasdel) /// 当 lasdel=true 且 popi <= popj 时跳过该频率点 pub lasdel: bool, } /// PHE2 输出结果。 #[derive(Debug, Clone)] pub struct Phe2Output { /// 吸收系数数组 (ABLIN) pub ablin: Vec, /// 发射系数数组 (EMLIN) pub emlin: Vec, } // ============================================================================ // PHE2 函数 // ============================================================================ /// 计算 He II 线不透明度和发射率。 /// /// 使用 Schoening 和 Butler 预计算的轮廓系数表, /// 通过插值获得给定频率点的不透明度。 /// /// # 参数 /// /// * `params` - 输入参数结构体 /// /// # 返回 /// /// 返回包含吸收系数和发射系数数组的输出结构体 /// /// # Fortran 原始代码 /// /// ```fortran /// SUBROUTINE PHE2(ISPEC,ID,ABLIN,EMLIN) /// ``` pub fn phe2(params: &Phe2Params) -> Phe2Output { let nfreq = params.nfreq as usize; // 初始化输出数组 let mut ablin = vec![0.0; nfreq]; let mut emlin = vec![0.0; nfreq]; // 计算线索引 (He II 线从 ISPEC=6 开始) let iline = (params.ispec - 5) as usize; // 检查线索引有效性 if iline == 0 || iline > 19 { return Phe2Output { ablin, emlin }; } // 提取轮廓数据到局部数组 let nwl = params.nwlhe2 as usize; let mut prf0 = vec![0.0; nwl]; let mut wll = vec![0.0; nwl]; for iwl in 0..nwl { // PRFHE2(ILINE, ID, IWL) 存储为 [iline][id][iwl] 或线性化 // 假设存储顺序为 [iline * nd * nwl + id * nwl + iwl] let id_idx = (params.id - 1) as usize; // Fortran 1-indexed let prf_idx = (iline - 1) * 100 * 36 + id_idx * 36 + iwl; // 假设 MDEPTH=100, 36 wavelength points max let wl_idx = (iline - 1) * 36 + iwl; if prf_idx < params.prfhe2.len() { prf0[iwl] = params.prfhe2[prf_idx]; } if wl_idx < params.wlhe2.len() { wll[iwl] = params.wlhe2[wl_idx]; } } // 计算线心波长 let i = params.ilhe2; let j = params.iuhe2; let ii = (i * i) as f64; let jj = (j * j) as f64; let wlin = if i <= 2 { WLIN_FACTOR_1 / (1.0 / ii - 1.0 / jj) } else { WLIN_FACTOR_2 / (1.0 / ii - 1.0 / jj) }; let t = params.temp; // He III 布居数 (LTE 或 NLTE) let (pp, nlhe2) = if params.ielhe2 > 0 && params.inlte > 0 { (params.he3_pop, params.nlhe2) } else { (params.he3_pop, 0) }; // 计算下能级布居数 let pp_scaled = pp * params.elec * SAHA_PREFACTOR / t / t.sqrt() * ii; let popi = if i <= nlhe2 && params.inlte > 0 { // NLTE 布居数 let level_idx = (params.nfirst_he2 + i - 2) as usize; // 转换为 0-indexed if level_idx < params.popul.len() { params.popul[level_idx] } else { 0.0 } } else { // LTE 布居数 pp_scaled * (EXCITATION_FACTOR / t / ii).exp() }; // 计算上能级布居数 let popj = if j <= nlhe2 { // NLTE 布居数 (归一化到下能级) let level_idx = (params.nfirst_he2 + j - 2) as usize; if level_idx < params.popul.len() { params.popul[level_idx] * ii / jj } else { 0.0 } } else { // LTE 布居数 pp_scaled * (EXCITATION_FACTOR / t / jj).exp() }; // 振子强度因子 let fid = 0.02654 * OSCHE2[iline - 1]; // 遍历频率点 for ij in 2..nfreq { // 计算与线心的波长差 let al = (params.wlam[ij] - wlin).abs(); let al = if al < 1e-4 { 1e-4 } else { al }; let al = al.log10(); // 在轮廓表中插值 let mut iw0 = 0; for iwl in 0..(nwl - 1) { iw0 = iwl; if al <= wll[iwl + 1] { break; } } let iw1 = iw0 + 1; if iw1 >= nwl { continue; } // 线性插值 let denom = wll[iw1] - wll[iw0]; let prh = if denom.abs() > 1e-10 { (prf0[iw0] * (wll[iw1] - al) + prf0[iw1] * (al - wll[iw0])) / denom } else { prf0[iw0] }; // 计算线强度 let sg = (prh * LN10).exp() * fid; // 检查布居数反转 if (popi - popj) <= 0.0 && params.lasdel { continue; } // 累加不透明度和发射率 ablin[ij] += sg * (popi - popj); emlin[ij] += sg * popj * EMIS_FACTOR * (params.freq[ij] * FREQ_SCALE).powi(3); } Phe2Output { ablin, emlin } } // ============================================================================ // 测试 // ============================================================================ #[cfg(test)] mod tests { use super::*; use approx::assert_relative_eq; /// 创建测试用的参数 fn create_test_params() -> Phe2Params<'static> { // 创建简单的测试数据 static FREQ: [f64; 10] = [ 1e15, 1.1e15, 1.2e15, 1.3e15, 1.4e15, 1.5e15, 1.6e15, 1.7e15, 1.8e15, 1.9e15, ]; static WLAM: [f64; 10] = [ 2997.9, 2725.4, 2498.3, 2306.1, 2141.4, 1998.6, 1873.7, 1763.5, 1665.5, 1577.9, ]; static POPUL: [f64; 20] = [ 1e10, 5e9, 2e9, 1e9, 5e8, 2e8, 1e8, 5e7, 2e7, 1e7, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, ]; static PRFHE2: [f64; 100 * 36 * 19] = [0.0; 100 * 36 * 19]; static WLHE2: [f64; 36 * 19] = [0.0; 36 * 19]; Phe2Params { ispec: 6, // 第一条 He II 线 id: 1, ielhe2: 3, inlte: 0, nfreq: 10, freq: &FREQ, wlam: &WLAM, temp: 20000.0, elec: 1e13, he3_pop: 1e10, nlhe2: 0, nfirst_he2: 11, popul: &POPUL, prfhe2: &PRFHE2, wlhe2: &WLHE2, nwlhe2: 10, ilhe2: 2, iuhe2: 3, lasdel: false, } } #[test] fn test_phe2_basic() { let params = create_test_params(); let result = phe2(¶ms); // 验证输出数组长度 assert_eq!(result.ablin.len(), 10); assert_eq!(result.emlin.len(), 10); } #[test] fn test_phe2_invalid_line_index() { let mut params = create_test_params(); params.ispec = 1; // 不是 He II 线 (ISPEC < 6) let result = phe2(¶ms); // 应该返回全零数组 assert!(result.ablin.iter().all(|&x| x == 0.0)); assert!(result.emlin.iter().all(|&x| x == 0.0)); } #[test] fn test_phe2_line_index_calculation() { // 验证线索引计算 // ISPEC=6 -> iline=1 // ISPEC=7 -> iline=2 assert_eq!(6 - 5, 1); assert_eq!(7 - 5, 2); } #[test] fn test_wlin_calculation() { // 测试线心波长计算 // He II Lyman-alpha: n=1 -> n=2 (lower -> upper) // I = lower level, J = upper level let i = 1; // lower level let j = 2; // upper level let ii = (i * i) as f64; let jj = (j * j) as f64; let wlin = WLIN_FACTOR_1 / (1.0 / ii - 1.0 / jj); // He II Lyman-alpha 约为 303.8 Å assert_relative_eq!(wlin, 303.784, epsilon = 0.1); } #[test] fn test_constants() { // 验证常数 assert_relative_eq!(LN10, std::f64::consts::LN_10, epsilon = 1e-7); assert_relative_eq!(EMIS_FACTOR, 1.4747e-2, epsilon = 1e-6); assert_relative_eq!(EXCITATION_FACTOR, 631479.0, epsilon = 1.0); } #[test] fn test_osche2_data() { // 验证振子强度数组 assert_eq!(OSCHE2.len(), 19); assert_relative_eq!(OSCHE2[0], 6.407e-1, epsilon = 1e-4); assert_relative_eq!(OSCHE2[18], 6.549e-2, epsilon = 1e-5); } #[test] fn test_population_inversion_skip() { // 当 lasdel=true 且 popi <= popj 时应该跳过 let mut params = create_test_params(); params.lasdel = true; // 设置使得 popi <= popj 的情况 // 这需要特定的温度和布居数设置 let result = phe2(¶ms); // 由于轮廓数据为零,结果应该很小或为零 // 这是一个边界情况测试 assert!(result.ablin.iter().all(|&x| x >= 0.0)); } #[test] fn test_lte_population() { // 测试 LTE 布居数计算 let params = create_test_params(); // 使用 LTE 模式 (inlte = 0) let t = params.temp; let pp = params.he3_pop; let elec = params.elec; let ii = 4.0; // n=2 let pp_scaled = pp * elec * SAHA_PREFACTOR / t / t.sqrt() * ii; let popi_lte = pp_scaled * (EXCITATION_FACTOR / t / ii).exp(); // 验证 LTE 布居数为正 assert!(popi_lte > 0.0); } #[test] fn test_nlte_population() { // 测试 NLTE 布居数模式 let mut params = create_test_params(); params.inlte = 1; // NLTE 模式 params.nlhe2 = 5; // 有 5 个 NLTE 能级 let result = phe2(¶ms); // 验证输出 assert_eq!(result.ablin.len(), 10); } }