//! 准分子不透明度计算(Lyman alpha, beta, gamma 和 Balmer alpha)。 //! //! 重构自 TLUSTY `allard.f` //! //! 计算氢线的准分子不透明度轮廓,基于 Allard 等人的数据表。 use crate::tlusty::state::model::{CalphatD, CallardA, CallardB, CallardC, CallardG, ModelState}; // 归一化常数 // 8.8528e-29 * lambda_0^2 * f_ij const XNORMA: f64 = 8.8528e-29 * 1215.6 * 1215.6 * 0.41618; // Lyman alpha const XNORMB: f64 = 8.8528e-29 * 1025.73 * 1025.7 * 0.0791; // Lyman beta const XNORMG: f64 = 8.8528e-29 * 972.53 * 972.53 * 0.0290; // Lyman gamma const XNORMC: f64 = 8.8528e-29 * 6562.0 * 6562.0 * 0.6407; // Balmer alpha /// 准分子不透明度线轮廓计算。 /// /// 计算 Lyman alpha, beta, gamma 和 Balmer alpha 线的准分子轮廓。 /// /// # 参数 /// /// * `xl` - 波长 (Å) /// * `t` - 温度 (K) /// * `hneutr` - 中性氢粒子密度 (cm⁻³) /// * `hcharg` - 电离氢粒子密度 (cm⁻³) /// * `iq` - 下能级量子数 /// * `jq` - 上能级量子数 /// - iq=1, jq=2: Lyman alpha /// - iq=1, jq=3: Lyman beta /// - iq=1, jq=4: Lyman gamma /// - iq=2, jq=3: Balmer alpha /// * `model` - 模型状态(包含 Allard 数据表) /// /// # 返回值 /// /// 返回归一化的线轮廓值。如果波长超出范围或其他条件不满足,返回 0.0。 pub fn allard( xl: f64, t: f64, hneutr: f64, hcharg: f64, iq: i32, jq: i32, model: &ModelState, ) -> f64 { let mut prof = 0.0; // Lyman alpha if iq == 1 && jq == 2 { if model.quasun.nunalp < 0 { // 使用温度相关计算 return allardt_temp(xl, t, hneutr, hcharg, &model.calphatd); } else { prof = lyman_alpha(xl, hneutr, hcharg, &model.callarda); } } // Lyman beta else if iq == 1 && jq == 3 { if model.callardb.nxbet == 0 { return 0.0; } prof = lyman_beta(xl, hneutr, hcharg, &model.callardb); } // Lyman gamma else if iq == 1 && jq == 4 { if model.callardg.nxgam == 0 { return 0.0; } prof = lyman_gamma(xl, hneutr, hcharg, &model.callardg); } // Balmer alpha else if iq == 2 && jq == 3 { prof = balmer_alpha(xl, hcharg, &model.callardc); } prof } /// Lyman alpha 线轮廓计算。 fn lyman_alpha(xl: f64, hneutr: f64, hcharg: f64, data: &CallardA) -> f64 { let nxalp = data.nxalp as usize; // 检查波长范围 if xl < data.xlalp[0] || xl > data.xlalp[nxalp - 1] { return 0.0; } // 计算归一化密度 let vn1 = hneutr / data.stnnea; let vn2 = hcharg / data.stncha; let vns = vn1 * data.vneua + vn2 * data.vchaa; // 检查密度是否过高(只警告一次) // 注意:原始 Fortran 代码有警告输出,这里保留标志检查但不输出 // if data.iwarna == 0 && (vn1 * data.vneua > 0.3 || vn2 * data.vchaa > 0.3) { // // 可以在这里添加警告逻辑 // } let vn11 = vn1 * vn1; let vn22 = vn2 * vn2; let vn12 = vn1 * vn2; let xnorm = 1.0 / (1.0 + vns + 0.5 * vns * vns); // 二分查找波长索引 let j = binary_search(xl, &data.xlalp, nxalp); // 线性插值 let a1 = (xl - data.xlalp[j - 1]) / (data.xlalp[j] - data.xlalp[j - 1]); let p1 = vn1 * ((1.0 - a1) * data.plalp[j - 1][0] + a1 * data.plalp[j][0]); let p11 = vn11 * ((1.0 - a1) * data.plalp[j - 1][1] + a1 * data.plalp[j][1]); let p2 = vn2 * ((1.0 - a1) * data.plalp[j - 1][2] + a1 * data.plalp[j][2]); let p22 = vn22 * ((1.0 - a1) * data.plalp[j - 1][3] + a1 * data.plalp[j][3]); let p12 = vn12 * ((1.0 - a1) * data.plalp[j - 1][4] + a1 * data.plalp[j][4]); (p1 + p2 + p11 + p22 + p12) * xnorm * XNORMA } /// Lyman beta 线轮廓计算。 fn lyman_beta(xl: f64, hneutr: f64, hcharg: f64, data: &CallardB) -> f64 { let nxbet = data.nxbet as usize; // 检查波长范围 if xl < data.xlbet[0] || xl > data.xlbet[nxbet - 1] { return 0.0; } // 计算归一化密度 let vn1 = hneutr / data.stnneb; let vn2 = hcharg / data.stnchb; let vns = vn1 * data.vneub + vn2 * data.vchab; let vn11 = vn1 * vn1; let vn22 = vn2 * vn2; let vn12 = vn1 * vn2; let xnorm = 1.0 / (1.0 + vns + 0.5 * vns * vns); // 二分查找波长索引 let j = binary_search(xl, &data.xlbet, nxbet); // 线性插值 let a1 = (xl - data.xlbet[j - 1]) / (data.xlbet[j] - data.xlbet[j - 1]); let p1 = vn1 * ((1.0 - a1) * data.plbet[j - 1][0] + a1 * data.plbet[j][0]); let p11 = vn11 * ((1.0 - a1) * data.plbet[j - 1][1] + a1 * data.plbet[j][1]); let p2 = vn2 * ((1.0 - a1) * data.plbet[j - 1][2] + a1 * data.plbet[j][2]); let p22 = vn22 * ((1.0 - a1) * data.plbet[j - 1][3] + a1 * data.plbet[j][3]); let p12 = vn12 * ((1.0 - a1) * data.plbet[j - 1][4] + a1 * data.plbet[j][4]); (p1 + p2 + p11 + p22 + p12) * xnorm * XNORMB } /// Lyman gamma 线轮廓计算。 fn lyman_gamma(xl: f64, hneutr: f64, hcharg: f64, data: &CallardG) -> f64 { let nxgam = data.nxgam as usize; // 检查波长范围 if xl < data.xlgam[0] || xl > data.xlgam[nxgam - 1] { return 0.0; } // 计算归一化密度 let vn1 = hneutr / data.stnneg; let vn2 = hcharg / data.stnchg; let vns = vn1 * data.vneug + vn2 * data.vchag; let vn11 = vn1 * vn1; let vn22 = vn2 * vn2; let vn12 = vn1 * vn2; let xnorm = 1.0 / (1.0 + vns + 0.5 * vns * vns); // 二分查找波长索引 let j = binary_search(xl, &data.xlgam, nxgam); // 线性插值 let a1 = (xl - data.xlgam[j - 1]) / (data.xlgam[j] - data.xlgam[j - 1]); let p1 = vn1 * ((1.0 - a1) * data.plgam[j - 1][0] + a1 * data.plgam[j][0]); let p11 = vn11 * ((1.0 - a1) * data.plgam[j - 1][1] + a1 * data.plgam[j][1]); let p2 = vn2 * ((1.0 - a1) * data.plgam[j - 1][2] + a1 * data.plgam[j][2]); let p22 = vn22 * ((1.0 - a1) * data.plgam[j - 1][3] + a1 * data.plgam[j][3]); let p12 = vn12 * ((1.0 - a1) * data.plgam[j - 1][4] + a1 * data.plgam[j][4]); (p1 + p2 + p11 + p22 + p12) * xnorm * XNORMG } /// Balmer alpha 线轮廓计算。 fn balmer_alpha(xl: f64, hcharg: f64, data: &CallardC) -> f64 { let nxbal = data.nxbal as usize; // 检查波长范围 if xl < data.xlbal[0] || xl > data.xlbal[nxbal - 1] { return 0.0; } // Balmer alpha 只考虑电离氢 let vn1 = 0.0; let vn2 = hcharg / data.stnchc; let vns = vn1 * data.vneuc + vn2 * data.vchac; let vn11 = vn1 * vn1; let vn22 = vn2 * vn2; let vn12 = vn1 * vn2; let xnorm = 1.0 / (1.0 + vns + 0.5 * vns * vns); // 二分查找波长索引 let j = binary_search(xl, &data.xlbal, nxbal); // 线性插值 let a1 = (xl - data.xlbal[j - 1]) / (data.xlbal[j] - data.xlbal[j - 1]); let p1 = vn1 * ((1.0 - a1) * data.plbal[j - 1][0] + a1 * data.plbal[j][0]); let p11 = vn11 * ((1.0 - a1) * data.plbal[j - 1][1] + a1 * data.plbal[j][1]); let p2 = vn2 * ((1.0 - a1) * data.plbal[j - 1][2] + a1 * data.plbal[j][2]); let p22 = vn22 * ((1.0 - a1) * data.plbal[j - 1][3] + a1 * data.plbal[j][3]); let p12 = vn12 * ((1.0 - a1) * data.plbal[j - 1][4] + a1 * data.plbal[j][4]); (p1 + p2 + p11 + p22 + p12) * xnorm * XNORMC } /// 温度相关的 Lyman alpha 线轮廓计算。 /// /// 当 nunalp < 0 时使用此函数。 fn allardt_temp(xl: f64, t: f64, hneutr: f64, hcharg: f64, data: &CalphatD) -> f64 { let ntalpd = data.ntalpd as usize; // 找到接近实际温度的两个部分表 let mut it0 = 0usize; for it in 1..=ntalpd { it0 = it; if t < data.talpd[it - 1] { it0 = it - 1; break; } } // 处理边界情况 if it0 == 0 { it0 = 1; return compute_single_table_temp(xl, hneutr, hcharg, it0 - 1, data); } if it0 >= ntalpd { it0 = ntalpd; return compute_single_table_temp(xl, hneutr, hcharg, it0 - 1, data); } // 在不同温度的表之间进行插值 let prof0 = compute_single_table_temp(xl, hneutr, hcharg, it0 - 1, data); if prof0 == 0.0 { return 0.0; } let prof1 = compute_single_table_temp(xl, hneutr, hcharg, it0, data); if prof1 == 0.0 { return 0.0; } // 温度插值 let dt = data.talpd[it0] - data.talpd[it0 - 1]; if dt.abs() < 1e-10 { prof0 } else { (prof0 * (data.talpd[it0] - t) + prof1 * (t - data.talpd[it0 - 1])) / dt } } /// 计算单个温度表的轮廓值(温度相关版本)。 fn compute_single_table_temp( xl: f64, hneutr: f64, hcharg: f64, it_idx: usize, data: &CalphatD, ) -> f64 { let nx = data.nxalpd[it_idx] as usize; if nx == 0 { return 0.0; } // 检查波长是否在范围内 if xl < data.xlalpd[0][it_idx] || xl > data.xlalpd[nx - 1][it_idx] { return 0.0; } // 计算归一化密度 let vn1 = hneutr / data.stnead[it_idx]; let vn2 = hcharg / data.stnchd[it_idx]; let vns = vn1 * data.vneuad[it_idx] + vn2 * data.vchaad[it_idx]; let vn11 = vn1 * vn1; let vn22 = vn2 * vn2; let vn12 = vn1 * vn2; let xnorm = 1.0 / (1.0 + vns + 0.5 * vns * vns); // 二分查找波长索引 let mut jl = 0usize; let mut ju = nx + 1; while ju - jl > 1 { let jm = (ju + jl) / 2; if xl > data.xlalpd[jm - 1][it_idx] { jl = jm; } else { ju = jm; } } let j = if jl == 0 { 1 } else if jl == nx { nx - 1 } else { jl }; // 线性插值 let a1 = (xl - data.xlalpd[j - 1][it_idx]) / (data.xlalpd[j][it_idx] - data.xlalpd[j - 1][it_idx]); let p1 = vn1 * ((1.0 - a1) * data.plalpd[j - 1][0][it_idx] + a1 * data.plalpd[j][0][it_idx]); let p11 = vn11 * ((1.0 - a1) * data.plalpd[j - 1][1][it_idx] + a1 * data.plalpd[j][1][it_idx]); let p2 = vn2 * ((1.0 - a1) * data.plalpd[j - 1][2][it_idx] + a1 * data.plalpd[j][2][it_idx]); let p22 = vn22 * ((1.0 - a1) * data.plalpd[j - 1][3][it_idx] + a1 * data.plalpd[j][3][it_idx]); let p12 = vn12 * ((1.0 - a1) * data.plalpd[j - 1][4][it_idx] + a1 * data.plalpd[j][4][it_idx]); (p1 + p2 + p11 + p22 + p12) * xnorm * XNORMA } /// 二分查找波长索引。 /// /// 返回索引 j,使得 x[j-1] <= xl < x[j]。 /// Fortran 原始代码使用的是 1-indexed。 fn binary_search(xl: f64, x: &[f64], n: usize) -> usize { let mut jl = 0usize; let mut ju = n + 1; // 判断数组是升序还是降序 let ascending = x[n - 1] > x[0]; while ju - jl > 1 { let jm = (ju + jl) / 2; if ascending == (xl > x[jm - 1]) { jl = jm; } else { ju = jm; } } // 边界检查 if jl == 0 { 1 } else if jl == n { n - 1 } else { jl } } #[cfg(test)] mod tests { use super::*; use approx::assert_relative_eq; fn create_test_callard_a() -> CallardA { let mut data = CallardA::default(); data.nxalp = 3; data.xlalp[0] = 1210.0; data.xlalp[1] = 1215.0; data.xlalp[2] = 1220.0; data.stnnea = 1e14; data.stncha = 1e12; data.vneua = 1.0; data.vchaa = 1.0; data.iwarna = 1; // 禁用警告 for i in 0..3 { for j in 0..5 { data.plalp[i][j] = 1e-20; } } data } fn create_test_callard_b() -> CallardB { let mut data = CallardB::default(); data.nxbet = 3; data.xlbet[0] = 1020.0; data.xlbet[1] = 1025.0; data.xlbet[2] = 1030.0; data.stnneb = 1e14; data.stnchb = 1e12; data.vneub = 1.0; data.vchab = 1.0; data.iwarnb = 1; for i in 0..3 { for j in 0..5 { data.plbet[i][j] = 1e-20; } } data } fn create_test_callard_g() -> CallardG { let mut data = CallardG::default(); data.nxgam = 3; data.xlgam[0] = 970.0; data.xlgam[1] = 972.0; data.xlgam[2] = 975.0; data.stnneg = 1e14; data.stnchg = 1e12; data.vneug = 1.0; data.vchag = 1.0; data.iwarng = 1; for i in 0..3 { for j in 0..5 { data.plgam[i][j] = 1e-20; } } data } fn create_test_callard_c() -> CallardC { let mut data = CallardC::default(); data.nxbal = 3; data.xlbal[0] = 6550.0; data.xlbal[1] = 6562.0; data.xlbal[2] = 6580.0; data.stnchc = 1e12; data.vneuc = 1.0; data.vchac = 1.0; for i in 0..3 { for j in 0..5 { data.plbal[i][j] = 1e-20; } } data } fn create_test_model() -> ModelState { let mut model = ModelState::default(); model.callarda = create_test_callard_a(); model.callardb = create_test_callard_b(); model.callardg = create_test_callard_g(); model.callardc = create_test_callard_c(); model.quasun.nunalp = 0; // 使用非温度相关计算 model } #[test] fn test_binary_search() { let x = vec![1210.0, 1215.0, 1220.0]; // 升序数组 let j = binary_search(1212.0, &x, 3); assert_eq!(j, 1); let j = binary_search(1217.0, &x, 3); assert_eq!(j, 2); } #[test] fn test_allard_lyman_alpha_in_range() { let model = create_test_model(); let prof = allard(1215.0, 10000.0, 1e14, 1e12, 1, 2, &model); assert!(prof > 0.0); } #[test] fn test_allard_lyman_alpha_out_of_range() { let model = create_test_model(); let prof = allard(1000.0, 10000.0, 1e14, 1e12, 1, 2, &model); assert_relative_eq!(prof, 0.0, epsilon = 1e-30); } #[test] fn test_allard_lyman_beta_in_range() { let model = create_test_model(); let prof = allard(1025.0, 10000.0, 1e14, 1e12, 1, 3, &model); assert!(prof > 0.0); } #[test] fn test_allard_lyman_gamma_in_range() { let model = create_test_model(); let prof = allard(972.0, 10000.0, 1e14, 1e12, 1, 4, &model); assert!(prof > 0.0); } #[test] fn test_allard_balmer_alpha_in_range() { let model = create_test_model(); let prof = allard(6562.0, 10000.0, 1e14, 1e12, 2, 3, &model); assert!(prof >= 0.0); // Balmer alpha 只考虑电离氢 } #[test] fn test_allard_invalid_transition() { let model = create_test_model(); // 无效的量子数组合 let prof = allard(6562.0, 10000.0, 1e14, 1e12, 3, 4, &model); assert_relative_eq!(prof, 0.0, epsilon = 1e-30); } }