//! 谱线轮廓波长外推函数。 //! //! 重构自 SYNSPEC `extprf.f` //! //! 在 Shamey 或 Barnard, Cooper, Smith 表中进行波长外推。 //! 使用 Cooper 建议的特殊公式。 /// Cooper 外推公式计算谱线轮廓。 /// /// # 参数 /// /// * `dlam` - 波长偏移 /// * `it` - 温度索引 (1-4) /// * `iline` - 谱线索引 (1-4) /// * `anel` - 电子密度对数参数 /// * `dlast` - 最后一个波长点 /// * `plast` - 最后一个轮廓值 /// /// # 返回值 /// /// 外推的谱线轮廓值 /// /// # Panics /// /// 如果 `it` 或 `iline` 不在 1-4 范围内会 panic。 /// /// # 算法 /// /// 使用 Cooper 公式: /// ```text /// WE = W0(it,iline) * exp(anel * ln(10)) * 1e-16 /// F = |dlast|^2.5 * (plast - WE / (pi * dlast^2)) /// EXTPRF = (WE / pi + F / sqrt(|dlam|)) / dlam^2 /// ``` pub fn extprf(dlam: f64, it: usize, iline: usize, anel: f64, dlast: f64, plast: f64) -> f64 { // W0 数组:温度索引 (行) x 谱线索引 (列) // Fortran DATA 语句是列优先存储 const W0: [[f64; 4]; 4] = [ [1.460, 6.130, 4.040, 2.312], // it=1 [1.269, 5.150, 3.490, 1.963], // it=2 [1.079, 4.240, 2.960, 1.624], // it=3 [0.898, 3.450, 2.470, 1.315], // it=4 ]; let it_idx = it - 1; // 转换为 0 索引 let iline_idx = iline - 1; // 获取 W0 值 let w0_val = W0[it_idx][iline_idx]; // WE = W0 * 10^anel * 1e-16 // Fortran: EXP(ANEL*2.3025851) = 10^ANEL (因为 ln(10) ≈ 2.3025851) let we = w0_val * (anel * std::f64::consts::LN_10).exp() * 1e-16; // 使用 PI 的精确值 const PI: f64 = std::f64::consts::PI; let dlasta = dlast.abs(); // D52 = |dlast|^2.5 = |dlast|^2 * sqrt(|dlast|) let d52 = dlasta * dlasta * dlasta.sqrt(); // F = D52 * (plast - WE / (pi * dlast^2)) let f = d52 * (plast - we / (PI * dlast * dlast)); // EXTPRF = (WE / pi + F / sqrt(|dlam|)) / dlam^2 (we / PI + f / dlam.abs().sqrt()) / (dlam * dlam) } #[cfg(test)] mod tests { use super::*; use approx::assert_relative_eq; #[test] fn test_basic() { // 基本测试 let result = extprf(0.1, 1, 1, 0.0, 0.5, 1.0); assert!(result.is_finite()); assert!(result > 0.0); } #[test] fn test_different_it_with_large_anel() { // 使用大的 anel 值使 W0 差异显现 // WE = W0 * 10^anel * 1e-16,当 anel 大时 W0 差异才明显 let anel = 10.0; // 10^10 = 1e10 倍放大 let base = extprf(0.1, 1, 1, anel, 0.5, 1.0); let t2 = extprf(0.1, 2, 1, anel, 0.5, 1.0); let t3 = extprf(0.1, 3, 1, anel, 0.5, 1.0); let t4 = extprf(0.1, 4, 1, anel, 0.5, 1.0); // 不同温度索引应该产生不同结果 assert!((base - t2).abs() > 1e-10, "IT=1 vs IT=2 should differ"); assert!((t2 - t3).abs() > 1e-10, "IT=2 vs IT=3 should differ"); assert!((t3 - t4).abs() > 1e-10, "IT=3 vs IT=4 should differ"); } #[test] fn test_different_iline_with_large_anel() { // 使用大的 anel 值使 W0 差异显现 let anel = 10.0; let l1 = extprf(0.1, 1, 1, anel, 0.5, 1.0); let l2 = extprf(0.1, 1, 2, anel, 0.5, 1.0); let l3 = extprf(0.1, 1, 3, anel, 0.5, 1.0); let l4 = extprf(0.1, 1, 4, anel, 0.5, 1.0); // 不同谱线索引应该产生不同结果 assert!((l1 - l2).abs() > 1e-10, "ILINE=1 vs ILINE=2 should differ"); assert!((l2 - l3).abs() > 1e-10, "ILINE=2 vs ILINE=3 should differ"); assert!((l3 - l4).abs() > 1e-10, "ILINE=3 vs ILINE=4 should differ"); } #[test] fn test_with_anel() { // 测试带电子密度参数 // 注意:anel 增加时 WE 增加但 F 项可能减小,所以结果不一定增加 // 这里测试 anel 确实影响结果 let zero = extprf(0.1, 1, 1, 0.0, 0.5, 1.0); let pos = extprf(0.1, 1, 1, 15.0, 0.5, 1.0); // anel 变化应该改变结果(不验证方向,只验证变化) assert!((pos - zero).abs() > 1e-6, "anel=15 should change the result"); } #[test] fn test_symmetry() { // 测试正负 dlast 的对称性 let pos = extprf(0.1, 1, 1, 0.0, 0.5, 1.0); let neg = extprf(0.1, 1, 1, 0.0, -0.5, 1.0); // 由于 dlast 使用 abs(),结果应该相同 assert_relative_eq!(pos, neg, epsilon = 1e-14); } #[test] fn test_w0_values() { // 验证 W0 数组值正确 // 直接测试:当 anel 很大且 plast=0 时,结果主要由 WE 决定 let anel = 20.0; let plast = 0.0; // 消除 F 项 // W0(1,1) = 1.460, W0(2,1) = 1.269, 比例 = 1.460/1.269 let r1 = extprf(0.1, 1, 1, anel, 0.5, plast); let r2 = extprf(0.1, 2, 1, anel, 0.5, plast); // 比例应该接近 W0 比例 let ratio = r1 / r2; let expected_ratio = 1.460 / 1.269; assert_relative_eq!(ratio, expected_ratio, epsilon = 1e-6); } }