//! 中性氦 Stark 加宽参数计算。 //! //! 重构自 SYNSPEC `gamhe.f` //! //! 基于 Dimitrijevic 和 Sahal-Brechot (1984, J.Q.S.R.T. 31, 301) //! 或 Freudenstein 和 Cooper (1978, AP.J. 224, 1079) 的数据。 /// Gamhe 计算所需的模型层参数。 #[derive(Debug, Clone)] pub struct GamheParams { /// 温度插值索引 (JT) pub jt: usize, /// 温度插值系数 0 pub ti0: f64, /// 温度插值系数 1 pub ti1: f64, /// 温度插值系数 2 pub ti2: f64, } /// 中性氦 Stark 加宽参数。 /// /// 包含电子和质子贡献的加宽数据。 pub struct GamheData { /// W 数组: [电子加宽 @ 5000K, @ 10000K, @ 20000K, @ 40000K, 波长(Å)] /// 按谱线索引 (1-20) 存储 pub w: [[f64; 5]; 20], /// V 数组: 质子加宽 @ 5000K, @ 10000K, @ 20000K, @ 40000K pub v: [[f64; 4]; 20], /// C 数组: 备用系数 (当 W=0 时使用) pub c: [f64; 20], } impl GamheData { /// 创建默认的 GamheData,包含所有谱线数据。 pub fn new() -> Self { // W 数组: 电子加宽参数 + 波长 // Fortran DATA 是列优先,需要转置 const W_DATA: [[f64; 5]; 20] = [ [5.990, 6.650, 6.610, 6.210, 3819.60], [2.950, 3.130, 3.230, 3.300, 3867.50], [0.000, 0.000, 0.000, 0.000, 3871.79], [0.142, 0.166, 0.182, 0.190, 3888.65], [0.000, 0.000, 0.000, 0.000, 3926.53], [1.540, 1.480, 1.400, 1.290, 3964.73], [41.600, 50.500, 57.400, 65.800, 4009.27], [1.320, 1.350, 1.380, 1.460, 4120.80], [7.830, 8.750, 8.690, 8.040, 4143.76], [5.830, 6.370, 6.820, 6.990, 4168.97], [0.000, 0.000, 0.000, 0.000, 4437.55], [1.630, 1.610, 1.490, 1.350, 4471.50], [0.588, 0.620, 0.641, 0.659, 4713.20], [2.600, 2.480, 2.240, 1.960, 4921.93], [0.627, 0.597, 0.568, 0.532, 5015.68], [1.050, 1.090, 1.110, 1.140, 5047.74], [0.277, 0.298, 0.296, 0.293, 5875.70], [0.714, 0.666, 0.602, 0.538, 6678.15], [3.490, 3.630, 3.470, 3.190, 4026.20], [4.970, 5.100, 4.810, 4.310, 4387.93], ]; // V 数组: 质子加宽参数 const V_DATA: [[f64; 4]; 20] = [ [1.520, 4.540, 9.140, 10.200], [0.607, 0.710, 0.802, 0.901], [0.000, 0.000, 0.000, 0.000], [0.0396, 0.0434, 0.0476, 0.0526], [0.000, 0.000, 0.000, 0.000], [0.507, 0.585, 0.665, 0.762], [0.930, 1.710, 13.600, 27.200], [0.288, 0.325, 0.365, 0.410], [1.330, 6.800, 12.900, 14.300], [1.100, 1.370, 1.560, 1.760], [0.000, 0.000, 0.000, 0.000], [1.340, 1.690, 1.820, 1.630], [0.128, 0.143, 0.161, 0.181], [2.040, 2.740, 2.950, 2.740], [0.187, 0.210, 0.237, 0.270], [0.231, 0.260, 0.291, 0.327], [0.0591, 0.0650, 0.0719, 0.0799], [0.231, 0.260, 0.295, 0.339], [2.180, 3.760, 4.790, 4.560], [1.860, 5.320, 7.070, 7.150], ]; // C 数组: 备用系数 // DATA C /2*0.,1.83E-4,0.,1.13E-4,5*0.,1.6E-4,9*0./ const C_DATA: [f64; 20] = [ 0.0, 0.0, 1.83e-4, 0.0, 1.13e-4, 0.0, 0.0, 0.0, 0.0, 0.0, 1.6e-4, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, ]; Self { w: W_DATA, v: V_DATA, c: C_DATA, } } } impl Default for GamheData { fn default() -> Self { Self::new() } } /// 计算中性氦 Stark 加宽参数 GAM。 /// /// # 参数 /// /// * `ind` - 谱线索引 (1-20, 1-indexed) /// * `t` - 温度 (K) /// * `ane` - 电子数密度 /// * `anp` - 质子数密度 /// * `params` - 模型层插值参数 /// * `data` - Stark 加宽数据表 /// /// # 返回值 /// /// Stark 加宽参数 GAM (Å) /// /// # 算法 /// /// 如果 W(1,IND) != 0: /// GAM = (电子贡献 * ANE + 质子贡献 * ANP) * 1.884e3 / 波长^2 /// 否则: /// GAM = C(IND) * T^0.16667 * ANE pub fn gamhe(ind: usize, t: f64, ane: f64, anp: f64, params: &GamheParams, data: &GamheData) -> f64 { // 转换为 0-indexed let ind_idx = ind - 1; // 检查是否有有效的电子加宽数据 if data.w[ind_idx][0] == 0.0 { // 使用备用公式 let gam = data.c[ind_idx] * t.powf(0.16667) * ane; return gam.max(0.0); } // 温度插值 let jt_idx = params.jt; // 已是 0-indexed // 电子加宽贡献 let w_electron = params.ti0 * data.w[ind_idx][jt_idx] + params.ti1 * data.w[ind_idx][jt_idx - 1] + params.ti2 * data.w[ind_idx][jt_idx - 2]; // 质子加宽贡献 let v_proton = params.ti0 * data.v[ind_idx][jt_idx] + params.ti1 * data.v[ind_idx][jt_idx - 1] + params.ti2 * data.v[ind_idx][jt_idx - 2]; // 波长 (Å) let wavelength = data.w[ind_idx][4]; // 计算 GAM let gam = (w_electron * ane + v_proton * anp) * 1.884e3 / (wavelength * wavelength); gam.max(0.0) } #[cfg(test)] mod tests { use super::*; use approx::assert_relative_eq; fn make_params(jt: usize) -> GamheParams { GamheParams { jt, ti0: 1.0, ti1: 0.0, ti2: 0.0, } } #[test] fn test_basic() { let data = GamheData::new(); let params = make_params(2); // jt >= 2 以访问 jt-2 // 使用谱线 1 (3819.60 Å) let gam = gamhe(1, 10000.0, 1e13, 1e12, ¶ms, &data); assert!(gam >= 0.0); assert!(gam.is_finite()); } #[test] fn test_zero_w_case() { let data = GamheData::new(); let params = make_params(2); // 谱线 3 的 W(1,3) = 0,使用备用公式 let gam = gamhe(3, 10000.0, 1e13, 1e12, ¶ms, &data); // C(3) = 1.83e-4 let expected = 1.83e-4 * 10000.0_f64.powf(0.16667) * 1e13; assert_relative_eq!(gam, expected, epsilon = 1e-6); } #[test] fn test_electron_contribution() { let data = GamheData::new(); let params = make_params(2); // 纯电子贡献 (ANP = 0) let gam_e = gamhe(1, 10000.0, 1e13, 0.0, ¶ms, &data); assert!(gam_e > 0.0); } #[test] fn test_proton_contribution() { let data = GamheData::new(); let params = make_params(2); // 纯质子贡献 (ANE = 0) let gam_p = gamhe(1, 10000.0, 0.0, 1e13, ¶ms, &data); assert!(gam_p > 0.0); } #[test] fn test_density_scaling() { let data = GamheData::new(); let params = make_params(2); let gam1 = gamhe(1, 10000.0, 1e13, 1e12, ¶ms, &data); let gam2 = gamhe(1, 10000.0, 2e13, 2e12, ¶ms, &data); // 密度翻倍,GAM 应该翻倍 assert_relative_eq!(gam2, 2.0 * gam1, epsilon = 1e-10); } #[test] fn test_negative_result_clamped() { let data = GamheData::new(); let params = GamheParams { jt: 2, ti0: -1.0, // 负系数可能导致负 GAM ti1: 0.0, ti2: 0.0, }; let gam = gamhe(1, 10000.0, 1e13, 1e12, ¶ms, &data); // 负值应该被钳制为 0 assert!(gam >= 0.0); } #[test] fn test_wavelength_scaling() { let data = GamheData::new(); let params = make_params(2); // 谱线 1: 3819.60 Å, 谱线 2: 3867.50 Å let gam1 = gamhe(1, 10000.0, 1e13, 0.0, ¶ms, &data); let gam2 = gamhe(2, 10000.0, 1e13, 0.0, ¶ms, &data); // GAM ∝ 1/波长^2,较长波长应该有较小的 GAM // (假设其他参数相似) assert!(gam1.is_finite() && gam2.is_finite()); } }