//! 分子谱线不透明度和发射率计算。 //! //! 重构自 SYNSPEC `molop.f` //! //! 计算给定深度点的总分子谱线不透明度 (ABLIN) 和发射率 (EMLIN)。 use crate::synspec::math::voigtk::{voigtk, MVOI}; /// 常量 const UN: f64 = 1.0; const EXT0: f64 = 3.17; const TEN: f64 = 10.0; /// 分子谱线数据参数 /// /// 对应 LINDAT.FOR 中的 MOLTOT COMMON 块 pub struct MolLineData<'a> { /// 分子谱线频率 [Hz] pub freqm: &'a [f64], /// 下能级激发能 [K] pub exclm: &'a [f64], /// gf 值 (log10) pub gfm: &'a [f64], /// 辐射加宽参数 pub grm: &'a [f64], /// Stark 加宽参数 pub gsm: &'a [f64], /// van der Waals 加宽参数 (按深度) pub gvdw: &'a [f64], /// 分子数据索引 pub indatm: &'a [i32], /// 分子线索引 pub inmlin: &'a [i32], /// 中心频率索引 pub ijcmtr: &'a [i32], /// 分子谱线数 pub nlinml: i32, /// 分子列表激活标志 pub inactm: i32, } /// 分子状态参数 /// /// 对应 MODELP.FOR 中的 MOLPAR COMMON 块 pub struct MolModelState<'a> { /// 分子数密度 [cm^-3] pub rrmol: &'a [f64], /// 分子多普勒宽度 pub dopmol: &'a [f64], } /// 深度点状态参数 /// /// 对应 MODELP.FOR 和 LINDAT.FOR 中的相关变量 pub struct MolopModelState<'a> { /// 温度 [K] pub temp: f64, /// 电子密度 [cm^-3] pub elec: f64, /// 分子状态 pub mol: MolModelState<'a>, /// Planck 函数 pub plan: f64, /// 受激因子 pub stim: f64, } /// 频率网格参数 /// /// 对应 SYNTHP.FOR 中的 FREQSY COMMON 块 pub struct MolopFreqParams<'a> { /// 频率数组 [Hz] pub freq: &'a [f64], /// 频率点数 pub nfreq: usize, /// 参考频率点数(用于输出范围) pub nfreqs: usize, } /// 配置参数 /// /// 对应 PARAMS.FOR 和 LINDAT.FOR 中的相关变量 pub struct MolopConfig { /// 分子温度限制 [K] pub tmolim: f64, /// 频率转换因子 pub dfrcon: f64, } /// 输出结果 pub struct MolopOutput { /// 不透明度数组 [cm^-1] pub ablin: Vec, /// 发射率数组 pub emlin: Vec, } /// 分子谱线不透明度和发射率计算(纯函数版本)。 /// /// # 参数 /// /// * `config` - 配置参数 /// * `model` - 模型状态 /// * `line_data` - 分子谱线数据 /// * `freq` - 频率网格参数 /// * `avab` - 平均不透明度(用于确定线宽范围) /// * `voigt_tables` - Voigt 函数预计算表格 (h0, h1, h2) /// /// # 返回值 /// /// 返回不透明度和发射率数组 pub fn molop_pure( config: &MolopConfig, model: &MolopModelState, line_data: &MolLineData, freq: &MolopFreqParams, avab: f64, voigt_tables: (&[f64; MVOI], &[f64; MVOI], &[f64; MVOI]), ) -> MolopOutput { let nfreq = freq.nfreq; let mut ablin = vec![0.0; nfreq]; let mut emlin = vec![0.0; nfreq]; // 检查温度是否超过分子温度限制 if model.temp > config.tmolim { return MolopOutput { ablin, emlin }; } // 检查是否有分子谱线 if line_data.nlinml == 0 { return MolopOutput { ablin, emlin }; } // 检查分子列表是否激活 if line_data.inactm != 0 { return MolopOutput { ablin, emlin }; } let (h0tab, h1tab, h2tab) = voigt_tables; let tem1 = UN / model.temp; let ane = model.elec; // 遍历所有贡献的分子谱线 for i in 0..line_data.nlinml as usize { let il = line_data.inmlin[i] as usize; let imol = line_data.indatm[il] as usize; let dop1 = model.mol.dopmol[imol]; let agam = (line_data.grm[il] + line_data.gsm[il] * ane + line_data.gvdw[il]) * dop1; let fr0 = line_data.freqm[il]; let ab0 = (line_data.gfm[il] - line_data.exclm[il] * tem1).exp() * model.mol.rrmol[imol] * dop1 * model.stim; // 设置谱线贡献的频率范围限制 let ex0 = ab0 / avab * agam; let mut ext = EXT0; if ex0 > TEN { ext = ex0.sqrt(); } ext = ext / dop1; let xijext = config.dfrcon * ext + 1.5; let ij1 = ((line_data.ijcmtr[i] as f64 - xijext).max(3.0)) as usize; let ij2 = ((line_data.ijcmtr[i] as f64 + xijext).min(freq.nfreqs as f64)) as usize; // 只有当范围有效时才计算 if ij1 < nfreq && ij2 > 2 { for ij in ij1..=ij2.min(nfreq - 1) { let xf = (freq.freq[ij] - fr0).abs() * dop1; ablin[ij] = ablin[ij] + ab0 * voigtk(agam, xf, h0tab, h1tab, h2tab); } } } // 计算发射率(从第3个频率点开始) for ij in 3..nfreq { emlin[ij] = emlin[ij] + ablin[ij] * model.plan; } MolopOutput { ablin, emlin } } #[cfg(test)] mod tests { use super::*; use approx::assert_relative_eq; fn create_test_voigt_tables() -> ([f64; MVOI], [f64; MVOI], [f64; MVOI]) { let mut h0 = [0.0; MVOI]; let mut h1 = [0.0; MVOI]; let mut h2 = [0.0; MVOI]; // H(0,v) = exp(-v^2) / sqrt(pi) let inv_sqrt_pi = 0.5641895835477563; for i in 0..MVOI { let v = (i as f64 - 1.5) / 200.0; h0[i] = (-v * v).exp() * inv_sqrt_pi; h1[i] = -2.0 * v * h0[i]; h2[i] = (4.0 * v * v - 2.0) * h0[i]; } (h0, h1, h2) } #[test] fn test_molop_high_temperature() { let (h0, h1, h2) = create_test_voigt_tables(); let config = MolopConfig { tmolim: 5000.0, dfrcon: 1.0, }; let mol_data = vec![1e10, 1e10, 1e10]; let dop_data = vec![1e-10, 1e-10, 1e-10]; let model = MolopModelState { temp: 6000.0, // 超过 tmolim elec: 1e12, mol: MolModelState { rrmol: &mol_data, dopmol: &dop_data, }, plan: 1.0, stim: 1.0, }; let freq_data = vec![0.0; 100]; let freq = MolopFreqParams { freq: &freq_data, nfreq: 100, nfreqs: 100, }; let gfm = vec![]; let exclm = vec![]; let freqm = vec![]; let grm = vec![]; let gsm = vec![]; let gvdw = vec![]; let indatm = vec![]; let inmlin = vec![]; let ijcmtr = vec![]; let line_data = MolLineData { freqm: &freqm, exclm: &exclm, gfm: &gfm, grm: &grm, gsm: &gsm, gvdw: &gvdw, indatm: &indatm, inmlin: &inmlin, ijcmtr: &ijcmtr, nlinml: 10, inactm: 0, }; let result = molop_pure(&config, &model, &line_data, &freq, 1e-10, (&h0, &h1, &h2)); // 高温时应该返回零 for i in 0..result.ablin.len() { assert_relative_eq!(result.ablin[i], 0.0); assert_relative_eq!(result.emlin[i], 0.0); } } #[test] fn test_molop_no_lines() { let (h0, h1, h2) = create_test_voigt_tables(); let config = MolopConfig { tmolim: 10000.0, dfrcon: 1.0, }; let mol_data = vec![1e10, 1e10, 1e10]; let dop_data = vec![1e-10, 1e-10, 1e-10]; let model = MolopModelState { temp: 5000.0, elec: 1e12, mol: MolModelState { rrmol: &mol_data, dopmol: &dop_data, }, plan: 1.0, stim: 1.0, }; let freq_data = vec![0.0; 100]; let freq = MolopFreqParams { freq: &freq_data, nfreq: 100, nfreqs: 100, }; let gfm = vec![]; let exclm = vec![]; let freqm = vec![]; let grm = vec![]; let gsm = vec![]; let gvdw = vec![]; let indatm = vec![]; let inmlin = vec![]; let ijcmtr = vec![]; let line_data = MolLineData { freqm: &freqm, exclm: &exclm, gfm: &gfm, grm: &grm, gsm: &gsm, gvdw: &gvdw, indatm: &indatm, inmlin: &inmlin, ijcmtr: &ijcmtr, nlinml: 0, // 没有谱线 inactm: 0, }; let result = molop_pure(&config, &model, &line_data, &freq, 1e-10, (&h0, &h1, &h2)); // 没有谱线时应该返回零 for i in 0..result.ablin.len() { assert_relative_eq!(result.ablin[i], 0.0); assert_relative_eq!(result.emlin[i], 0.0); } } #[test] fn test_molop_inactive_list() { let (h0, h1, h2) = create_test_voigt_tables(); let config = MolopConfig { tmolim: 10000.0, dfrcon: 1.0, }; let mol_data = vec![1e10, 1e10, 1e10]; let dop_data = vec![1e-10, 1e-10, 1e-10]; let model = MolopModelState { temp: 5000.0, elec: 1e12, mol: MolModelState { rrmol: &mol_data, dopmol: &dop_data, }, plan: 1.0, stim: 1.0, }; let freq_data = vec![0.0; 100]; let freq = MolopFreqParams { freq: &freq_data, nfreq: 100, nfreqs: 100, }; let gfm = vec![]; let exclm = vec![]; let freqm = vec![]; let grm = vec![]; let gsm = vec![]; let gvdw = vec![]; let indatm = vec![]; let inmlin = vec![]; let ijcmtr = vec![]; let line_data = MolLineData { freqm: &freqm, exclm: &exclm, gfm: &gfm, grm: &grm, gsm: &gsm, gvdw: &gvdw, indatm: &indatm, inmlin: &inmlin, ijcmtr: &ijcmtr, nlinml: 10, inactm: 1, // 不激活 }; let result = molop_pure(&config, &model, &line_data, &freq, 1e-10, (&h0, &h1, &h2)); // 不激活时应该返回零 for i in 0..result.ablin.len() { assert_relative_eq!(result.ablin[i], 0.0); assert_relative_eq!(result.emlin[i], 0.0); } } #[test] fn test_molop_basic_calculation() { let (h0, h1, h2) = create_test_voigt_tables(); let config = MolopConfig { tmolim: 10000.0, dfrcon: 1.0, }; // 设置一条简单的分子谱线 let mol_data = vec![1e10]; // 分子数密度 let dop_data = vec![0.1]; // 多普勒宽度 let model = MolopModelState { temp: 5000.0, elec: 1e12, mol: MolModelState { rrmol: &mol_data, dopmol: &dop_data, }, plan: 2.0, stim: 1.0, }; // 频率网格:中心在 1e15 Hz let nfreq = 100; let freq_data: Vec = (0..nfreq).map(|i| 1e15 + (i as f64 - 50.0) * 1e11).collect(); let freq = MolopFreqParams { freq: &freq_data, nfreq, nfreqs: nfreq, }; // 一条分子谱线 let gfm = vec![0.0]; // log gf = 0 let exclm = vec![0.0]; // 激发能 = 0 let freqm = vec![1e15]; // 中心频率 let grm = vec![0.1]; // 辐射加宽 let gsm = vec![0.01]; // Stark 加宽 let gvdw = vec![0.05]; // van der Waals 加宽 let indatm = vec![0]; // 分子索引 let inmlin = vec![0]; // 谱线索引 let ijcmtr = vec![50]; // 中心频率索引 let line_data = MolLineData { freqm: &freqm, exclm: &exclm, gfm: &gfm, grm: &grm, gsm: &gsm, gvdw: &gvdw, indatm: &indatm, inmlin: &inmlin, ijcmtr: &ijcmtr, nlinml: 1, inactm: 0, }; let result = molop_pure(&config, &model, &line_data, &freq, 1e-10, (&h0, &h1, &h2)); // 检查中心附近有不透明度 let center_idx = 50; assert!(result.ablin[center_idx] > 0.0, "中心应该有不透明度"); // 检查发射率与不透明度的关系 for i in 3..nfreq { if result.ablin[i] > 0.0 { assert_relative_eq!(result.emlin[i], result.ablin[i] * model.plan, epsilon = 1e-10); } } } }