//! H2⁻ 自由-自由吸收系数计算。 //! //! 重构自 SYNSPEC `synspec54.f` 中的 `h2minus` 子程序。 //! //! 数据来源: K L Bell 1980, J. Phys. B: At. Mol. Phys. 13 1859, Table 1 //! 单位: 10^26 cm^4/dyn^-1 use crate::tlusty::math::interpolation::locate; use crate::synspec::math::{CL, BOLK}; // ============================================================================ // 静态数据表 // ============================================================================ /// theta = 5040/T(K) 网格点 (9 个) const FFTHET: [f64; 9] = [0.5, 0.8, 1.0, 1.2, 1.6, 2.0, 2.8, 3.6, 10.0]; /// lambda (Angstroms) 网格点 (18 个) const FFLAMB: [f64; 18] = [ 151883.0, 113913.0, 91130.0, 60753.0, 45565.0, 36452.0, 30377.0, 22783.0, 18226.0, 15188.0, 11391.0, 9113.0, 7594.0, 6509.0, 5696.0, 5063.0, 4142.0, 3505.0, ]; /// kappa 表 (18 x 9),按列优先存储 (Fortran 布局) const NTHET: usize = 9; const NLAMB: usize = 18; /// FFkapp 表,按 Fortran 列优先存储: FFkapp[theta_idx * NLAMB + lamb_idx] /// 即 FFkapp(i,j) = FFKAPP[j * 18 + i],其中 i=lambda, j=theta (0-based) const FFKAPP: [f64; NLAMB * NTHET] = [ // 列 1 (theta=0.5): 18 个 lambda 值 7.16e+01, 4.03e+01, 2.58e+01, 1.15e+01, 6.47e+00, 4.15e+00, 2.89e+00, 1.63e+00, 1.05e+00, 7.36e-01, 4.20e-01, 2.73e-01, 1.92e-01, 1.43e-01, 1.10e-01, 8.70e-02, 5.84e-02, 4.17e-02, // 列 2 (theta=0.8) 9.23e+01, 5.20e+01, 3.33e+01, 1.48e+01, 8.37e+00, 5.38e+00, 3.76e+00, 2.14e+00, 1.39e+00, 9.75e-01, 5.64e-01, 3.71e-01, 2.64e-01, 1.98e-01, 1.54e-01, 1.24e-01, 8.43e-02, 6.10e-02, // 列 3 (theta=1.0) 1.01e+02, 5.70e+01, 3.65e+01, 1.63e+01, 9.20e+00, 5.92e+00, 4.14e+00, 2.36e+00, 1.54e+00, 1.09e+00, 6.35e-01, 4.22e-01, 3.03e-01, 2.30e-01, 1.80e-01, 1.46e-01, 1.01e-01, 7.34e-02, // 列 4 (theta=1.2) 1.08e+02, 6.08e+01, 3.90e+01, 1.74e+01, 9.84e+00, 6.35e+00, 4.44e+00, 2.55e+00, 1.66e+00, 1.18e+00, 6.97e-01, 4.67e-01, 3.39e-01, 2.59e-01, 2.06e-01, 1.67e-01, 1.17e-01, 8.59e-02, // 列 5 (theta=1.6) 1.18e+02, 6.65e+01, 4.27e+01, 1.91e+01, 1.08e+01, 6.99e+00, 4.91e+00, 2.84e+00, 1.87e+00, 1.34e+00, 8.06e-01, 5.52e-01, 4.08e-01, 3.17e-01, 2.55e-01, 2.10e-01, 1.49e-01, 1.11e-01, // 列 6 (theta=2.0) 1.26e+02, 7.08e+01, 4.54e+01, 2.04e+01, 1.16e+01, 7.50e+00, 5.28e+00, 3.07e+00, 2.04e+00, 1.48e+00, 9.09e-01, 6.33e-01, 4.76e-01, 3.75e-01, 3.05e-01, 2.53e-01, 1.82e-01, 1.37e-01, // 列 7 (theta=2.8) 1.38e+02, 7.76e+01, 4.98e+01, 2.24e+01, 1.28e+01, 8.32e+00, 5.90e+00, 3.49e+00, 2.36e+00, 1.74e+00, 1.11e+00, 7.97e-01, 6.13e-01, 4.92e-01, 4.06e-01, 3.39e-01, 2.49e-01, 1.87e-01, // 列 8 (theta=3.6) 1.47e+02, 8.30e+01, 5.33e+01, 2.40e+01, 1.38e+01, 9.02e+00, 6.44e+00, 3.90e+00, 2.68e+00, 2.01e+00, 1.32e+00, 9.63e-01, 7.51e-01, 6.09e-01, 5.07e-01, 4.27e-01, 3.16e-01, 2.40e-01, // 列 9 (theta=10.0) — 线性外推 2.19e+02, 1.26e+02, 8.13e+01, 3.68e+01, 2.18e+01, 1.46e+01, 1.08e+01, 7.18e+00, 5.24e+00, 4.17e+00, 3.00e+00, 2.29e+00, 1.86e+00, 1.55e+00, 1.32e+00, 1.13e+00, 8.52e-01, 6.64e-01, ]; // ============================================================================ // h2minus - H2⁻ 自由-自由吸收 // ============================================================================ /// 计算 H2⁻ 自由-自由吸收系数。 /// /// # 参数 /// /// - `t` - 温度 (K) /// - `anh2` - H2 分子数密度 /// - `ane` - 电子数密度 /// - `fr` - 频率 (Hz) /// /// # 返回 /// /// H2⁻ 自由-自由吸收系数 `oph2m` pub fn h2minus(t: f64, anh2: f64, ane: f64, fr: f64) -> f64 { // theta = 5040 / T let theta = 5040.0 / t; // 在温度数组中定位 (0-indexed) // locate 返回 j 使得 FFTHET[j] <= theta < FFTHET[j+1] let j = locate(&FFTHET, theta); // 波长 (Angstroms): lambda = c / fr * 1e8 let flamb = CL * 1.0e8 / fr; // 在波长数组中定位 (0-indexed) let i = locate(&FFLAMB, flamb); // 双线性插值 // 注意: FFTHET 是递增的,FFLAMB 是递减的 let fkappa = if j >= NTHET - 1 { // theta >= FFTHET[NTHET-1],保持恒定 (高温端) let i_clamped = i.min(NLAMB - 2); let y1 = ffkapp_at(i_clamped, NTHET - 1); let y2 = ffkapp_at(i_clamped + 1, NTHET - 1); let tt = (flamb - FFLAMB[i_clamped]) / (FFLAMB[i_clamped + 1] - FFLAMB[i_clamped]); (1.0 - tt) * y1 + tt * y2 } else if !(FFLAMB[NLAMB - 1]..=FFLAMB[0]).contains(&flamb) { // 超出波长表范围 (FFLAMB 递减: [0] 最大, [NLAMB-1] 最小) 0.0 } else { // 表内双线性插值 let y1 = ffkapp_at(i, j); let y2 = ffkapp_at(i + 1, j); let y3 = ffkapp_at(i + 1, j + 1); let y4 = ffkapp_at(i, j + 1); // tt: 波长方向插值 (FFLAMB 递减) let tt = (flamb - FFLAMB[i]) / (FFLAMB[i + 1] - FFLAMB[i]); // uu: 温度方向插值 (FFTHET 递增) let uu = (theta - FFTHET[j]) / (FFTHET[j + 1] - FFTHET[j]); (1.0 - tt) * (1.0 - uu) * y1 + tt * (1.0 - uu) * y2 + tt * uu * y3 + (1.0 - tt) * uu * y4 }; // 电子压力 let pe = ane * BOLK * t; // 最终吸收系数 anh2 * 1.0e-26 * pe * fkappa } /// 从 FFkapp 表中获取值 (处理边界) /// 索引: FFkapp(i,j) = FFKAPP[j * NLAMB + i],其中 i=lambda, j=theta (0-based) fn ffkapp_at(i: usize, j: usize) -> f64 { let i_clamped = i.min(NLAMB - 1); let j_clamped = j.min(NTHET - 1); FFKAPP[j_clamped * NLAMB + i_clamped] } #[cfg(test)] mod tests { use super::*; #[test] fn test_h2minus_basic() { // 典型恒星大气参数 let t = 5000.0; // K let anh2 = 1.0e15; // H2 数密度 let ane = 1.0e13; // 电子数密度 let fr = 1.0e14; // Hz (红外) let oph2m = h2minus(t, anh2, ane, fr); assert!(oph2m > 0.0, "oph2m 应为正值: {}", oph2m); } #[test] fn test_h2minus_high_temperature() { // 高温情况 let t = 10000.0; let anh2 = 1.0e14; let ane = 1.0e12; let fr = 3.0e14; let oph2m = h2minus(t, anh2, ane, fr); assert!(oph2m >= 0.0, "oph2m 应非负: {}", oph2m); } #[test] fn test_h2minus_low_temperature() { // 低温情况 (theta 大) let t = 3000.0; let anh2 = 1.0e16; let ane = 1.0e14; let fr = 5.0e14; let oph2m = h2minus(t, anh2, ane, fr); assert!(oph2m >= 0.0, "oph2m 应非负: {}", oph2m); } #[test] fn test_h2minus_scaling() { // 吸收系数应与 anh2 和 ane 成正比 let t = 6000.0; let fr = 2.0e14; let oph2m1 = h2minus(t, 1.0e14, 1.0e12, fr); let oph2m2 = h2minus(t, 2.0e14, 1.0e12, fr); let oph2m3 = h2minus(t, 1.0e14, 2.0e12, fr); // 双倍 anh2 → 双倍 opacity assert!( (oph2m2 / oph2m1 - 2.0).abs() < 0.01, "anh2 线性性: {} vs {}", oph2m2, oph2m1 ); // 双倍 ane → 双倍 opacity (pe 线性) assert!( (oph2m3 / oph2m1 - 2.0).abs() < 0.01, "ane 线性性: {} vs {}", oph2m3, oph2m1 ); } }