//! 额外不透明度计算。 //! //! 重构自 TLUSTY `OPADD.f` //! //! 计算各种额外的非标准不透明度来源: //! - Rayleigh 散射 (HI, HeI, H2) //! - H⁻ 束缚-自由和自由-自由不透明度 //! - H₂⁺ 束缚-自由和自由-自由不透明度 //! - He⁻ 自由-自由不透明度 //! - H₂⁻ 自由-自由不透明度 //! - CH 和 OH 连续不透明度 //! - CIA (碰撞诱导吸收) 不透明度 use crate::tlusty::math::{cia_h2h, cia_h2h2, cia_h2he, cia_hhe, h2minus, sbfch, sbfoh, CiaH2h2Data, CiaH2heData, CiaH2hData, CiaHheData}; use crate::tlusty::math::sffhmi; // ============================================================================ // 常量 // ============================================================================ /// HI Rayleigh 散射阈值频率 (Hz) const FRAY: f64 = 2.463e15; /// HeI Rayleigh 散射阈值频率 (Hz) const FRAYHE: f64 = 5.150e15; /// H2 Rayleigh 散射阈值频率 (Hz) const FRAYH2: f64 = 2.922e15; /// 光速 (Å/s) const CLS: f64 = 2.997925e18; /// 光速 (cm/s) const C18: f64 = 2.997925e18; /// H⁻ 束缚-自由常数 const CR0: f64 = 5.799e-13; const CR1: f64 = 1.422e-6; const CR2: f64 = 2.784; const TENM4: f64 = 1.0e-4; /// H⁻ 束缚-自由阈值 const THM0: f64 = 8.7629e3; /// Saha 常数 const SBHM: f64 = 1.0353e-16; /// H₂⁺ 阈值比率 const TRHA: f64 = 1.5; /// H⁻ 自由-自由常数 const SFF0: f64 = 1.3727e-25; const SFF1: f64 = 4.3748e-10; const SFFM2: f64 = -2.5993e-7; /// HeI 阈值频率 const F0HE1: f64 = 3.29e15; /// HeII 阈值频率 const F0HE2: f64 = 1.316e16; /// Saha 常数 const SBH0: f64 = 4.1412e-16; const SG01: f64 = 2.815e-16; const SG02: f64 = 4.504e-15; /// 普朗克常数 / 玻尔兹曼常数 (erg/K) const HK: f64 = 6.626176e-27 / 1.380662e-16; // ============================================================================ // OPADD 输入参数 // ============================================================================ /// OPADD 输入参数。 #[derive(Debug, Clone)] pub struct OpaddInput { /// 计算模式 /// - -1: 初始化(计算深度相关量) /// - 0: 计算不透明度 /// - 1: 计算不透明度和导数 pub mode: i32, /// 调用标志 (>0 表示需要初始化温度相关量) pub icall: i32, /// 频率索引 (0-indexed) pub ij: usize, /// 深度索引 (0-indexed) pub id: usize, } /// OPADD 开关参数(来自 COMMON/OPCKEY)。 #[derive(Debug, Clone, Default)] pub struct OpaddSwitches { /// HI Rayleigh 散射开关 pub irsct: i32, /// HeI Rayleigh 散射开关 pub irsche: i32, /// H2 Rayleigh 散射开关 pub irsch2: i32, /// H⁻ 不透明度开关 pub iophmi: i32, /// H₂⁺ 不透明度开关 pub ioph2p: i32, /// He⁻ 不透明度开关 pub iophem: i32, /// H₂⁻ 不透明度开关 pub ioph2m: i32, /// CH 不透明度开关 pub iopch: i32, /// OH 不透明度开关 pub iopoh: i32, /// CIA H2-H2 不透明度开关 pub ioh2h2: i32, /// CIA H2-He 不透明度开关 pub ioh2he: i32, /// CIA H2-H 不透明度开关 pub ioh2h: i32, /// CIA H-He 不透明度开关 pub iohhe: i32, /// 分子开关 pub ifmol: i32, /// 分子温度极限 pub tmolim: f64, } /// OPADD 模型状态(所需变量)。 #[derive(Debug, Clone)] pub struct OpaddModel<'a> { /// 温度数组 pub temp: &'a [f64], /// 电子密度数组 pub elec: &'a [f64], /// 频率数组 pub freq: &'a [f64], /// 深度数 pub nd: usize, /// 能级数 pub nlevel: usize, /// H 离子索引 pub ielh: i32, /// He 离子索引 pub iathe: i32, /// H 第一能级索引 (1-indexed in Fortran) pub n0hn: i32, /// H⁺ 索引 pub nkh: i32, /// He 第一能级索引 pub n0ahe: i32, /// 原子数密度数组 [物种][深度] pub anato: &'a [Vec], /// 离子数密度数组 [物种][深度] pub anion: &'a [Vec], /// 分子数密度数组 [物种][深度] pub anmol: &'a [Vec], /// 占据数数组 [能级][深度] pub popul: &'a [Vec], /// 光电离截面表 [跃迁][频率] pub cross: &'a [Vec], /// 连续跃迁计数 pub ncon: usize, /// CIA H2-H2 数据 pub cia_h2h2_data: &'a CiaH2h2Data, /// CIA H2-He 数据 pub cia_h2he_data: &'a CiaH2heData, /// CIA H2-H 数据 pub cia_h2h_data: &'a CiaH2hData, /// CIA H-He 数据 pub cia_hhe_data: &'a CiaHheData, } /// OPADD 输出结果。 #[derive(Debug, Clone, Default)] pub struct OpaddOutput { /// 吸收系数 pub abad: f64, /// 发射系数 pub emad: f64, /// 散射系数 pub scad: f64, /// 对温度的吸收导数 pub dat: f64, /// 对电子密度的吸收导数 pub dan: f64, /// 对温度的发射导数 pub det: f64, /// 对电子密度的发射导数 pub den: f64, /// 对温度的散射导数 pub dst: f64, /// 对电子密度的散射导数 pub dsn: f64, /// 对能级占据数的导数 pub ddn: Vec, } // ============================================================================ // 缓存状态(对应 Fortran SAVE 变量) // ============================================================================ /// 缓存的温度相关量。 #[derive(Debug, Clone, Default)] pub struct OpaddCache { /// 温度 pub t: f64, /// 0.0001 * T pub deltat: f64, /// 电子密度 pub ane: f64, /// h/kT pub hkt: f64, /// 1/(T*sqrt(T)) pub t32: f64, /// THM0/T pub xhm: f64, /// 中性氢占据数 pub popi: f64, /// H⁻ Saha 因子 pub sb00: f64, } // ============================================================================ // OPADD 主函数 // ============================================================================ /// 计算额外不透明度。 /// /// # 参数 /// /// * `input` - 输入参数 /// * `switches` - 开关配置 /// * `model` - 模型状态 /// * `cache` - 缓存的温度相关量(可变引用) /// /// # 返回值 /// /// 计算结果包含吸收、发射、散射系数及其导数。 /// /// # Fortran 原始代码 /// /// ```fortran /// SUBROUTINE OPADD(MODE,ICALL,IJ,ID) /// ``` pub fn opadd( input: &OpaddInput, switches: &OpaddSwitches, model: &OpaddModel, cache: &mut OpaddCache, ) -> OpaddOutput { let mut output = OpaddOutput { ddn: vec![0.0; model.nlevel], ..Default::default() }; let mut ab0 = 0.0_f64; let mut ab1 = 0.0_f64; let mut dab1 = 0.0_f64; let fr = model.freq[input.ij]; let _al = CLS / fr; // 波长 (Å) // 获取 H 和 He 相关变量 let (n0hn, nkh, n0ahe): (i32, i32, i32) = if model.ielh > 0 { ( model.n0hn, model.nkh, if model.iathe > 0 { model.n0ahe } else { 0 }, ) } else { (0, 0, 0) }; // 初始化温度相关量 if input.icall > 0 { let id = input.id; cache.t = model.temp[id]; cache.deltat = TENM4 * cache.t; cache.ane = model.elec[id]; cache.hkt = HK / cache.t; cache.t32 = 1.0 / cache.t / cache.t.sqrt(); cache.xhm = THM0 / cache.t; // 获取 H 和 H⁺ 数密度 let (ah, ahp): (f64, f64) = if model.ielh > 0 { let ah = model.popul[(n0hn - 1) as usize][id]; let ahp = model.popul[(nkh - 1) as usize][id]; (ah, ahp) } else { (model.anato[0][id], model.anion[0][id]) }; cache.popi = ah; // H⁻ Saha 因子 cache.sb00 = SBHM * cache.t32 * (cache.xhm.exp()) * cache.popi * cache.ane; } // 获取 He 数密度 let ahe: f64 = if model.iathe > 0 { model.popul[(n0ahe - 1) as usize][input.id] } else { model.anato[1][input.id] }; // 获取 H 数密度(用于散射) let ah: f64 = if model.ielh > 0 { model.popul[(n0hn - 1) as usize][input.id] } else { model.anato[0][input.id] }; let t = cache.t; let ane = cache.ane; let mut it = model.ncon as i32; // ----------------------------------------------------------------------- // HI Rayleigh 散射 // ----------------------------------------------------------------------- if switches.irsct != 0 { it += 1; output.scad = ah * model.cross[it as usize][input.ij]; } // ----------------------------------------------------------------------- // HeI Rayleigh 散射 // ----------------------------------------------------------------------- if switches.irsche != 0 && input.mode >= 0 { it += 1; output.scad += ahe * model.cross[it as usize][input.ij]; } // ----------------------------------------------------------------------- // H2 Rayleigh 散射 // ----------------------------------------------------------------------- if switches.irsch2 != 0 && input.mode >= 0 && switches.ifmol > 0 { it += 1; let sg = model.cross[it as usize][input.ij] * model.anmol[2][input.id]; if t < switches.tmolim { output.scad += sg; } } // ----------------------------------------------------------------------- // H⁻ 束缚-自由和自由-自由 // ----------------------------------------------------------------------- if switches.iophmi > 0 { it += 1; if t < 20000.0 { let sb = cache.sb00 * model.cross[it as usize][input.ij]; let sf = sffhmi(ah, fr, t) * ane; ab0 = sb + sf; } } // ----------------------------------------------------------------------- // H₂⁺ 束缚-自由和自由-自由 // ----------------------------------------------------------------------- if switches.ioph2p > 0 { it += 1; let x2 = -model.cross[it as usize][input.ij] / t + model.cross[it as usize + 1][input.ij]; it += 1; let mut sb = 0.0; if x2 > -150.0 && fr < 3.28e15 && t <= 9000.0 { let ahp = if model.ielh > 0 { model.popul[(nkh - 1) as usize][input.id] } else { model.anion[0][input.id] }; sb = ah * x2.exp() * ahp; } ab0 += sb; dab1 = sb * model.cross[it as usize - 1][input.ij] / t / t; if n0hn > 0 { output.ddn[(n0hn - 1) as usize] += sb / cache.popi; } if nkh > 0 { output.ddn[(nkh - 1) as usize] += sb / model.popul[(nkh - 1) as usize][input.id]; } } // ----------------------------------------------------------------------- // He⁻ 自由-自由 // ----------------------------------------------------------------------- if switches.iophem > 0 { it += 1; let sg = model.cross[it as usize][input.ij] * t + model.cross[it as usize + 1][input.ij] + model.cross[it as usize + 2][input.ij] / t; ab0 += sg * ane * ahe; } // ----------------------------------------------------------------------- // H₂⁻ 自由-自由 // ----------------------------------------------------------------------- if switches.ioph2m != 0 && input.mode >= 0 && switches.ifmol > 0 && t < switches.tmolim { let oph2 = h2minus(t, model.anmol[2][input.id], ane, fr); ab1 += oph2; } // ----------------------------------------------------------------------- // CH 和 OH 连续不透明度 // ----------------------------------------------------------------------- if input.mode >= 0 && switches.ifmol > 0 && t < switches.tmolim { if switches.iopch > 0 { ab0 += sbfch(fr, t) * model.anmol[5][input.id]; } if switches.iopoh > 0 { ab0 += sbfoh(fr, t) * model.anmol[4][input.id]; } // ------------------------------------------------------------------- // CIA H2-H2 不透明度 // ------------------------------------------------------------------- if switches.ioh2h2 > 0 { let oph2 = cia_h2h2(t, model.anmol[2][input.id], fr, model.cia_h2h2_data); ab1 += oph2; } // ------------------------------------------------------------------- // CIA H2-He 不透明度 // ------------------------------------------------------------------- if switches.ioh2he > 0 { let oph2 = cia_h2he(t, model.anmol[2][input.id], ahe, fr, model.cia_h2he_data); ab1 += oph2; } // ------------------------------------------------------------------- // CIA H2-H 不透明度 // ------------------------------------------------------------------- if switches.ioh2h > 0 { let oph2 = cia_h2h(t, model.anmol[2][input.id], ah, fr, model.cia_h2h_data); ab1 += oph2; } // ------------------------------------------------------------------- // CIA H-He 不透明度 // ------------------------------------------------------------------- if switches.iohhe > 0 { let oph2 = cia_hhe(t, ah, ahe, fr, model.cia_hhe_data); ab1 += oph2; } } // ----------------------------------------------------------------------- // 最终计算吸收和发射系数及其导数 // ----------------------------------------------------------------------- if input.mode < 0 { return output; } let x = (-cache.hkt * fr).exp(); let x1 = 1.0 - x; let fr15 = fr * 1.0e-15; let _bnx = 1.0e-22 * fr15 * fr15 * fr15 * x; // BN 常数(未使用) ab1 /= x1; output.abad = ab0 + ab1; output.emad = ab0 + ab1; if input.mode == 1 { let hkft = cache.hkt * fr / t; let _db = hkft * ab0; // 未使用 output.dat = dab1; output.det = dab1; output.dan = ab0 / ane; output.den = ab0 / ane; } output } // ============================================================================ // 测试 // ============================================================================ #[cfg(test)] mod tests { use super::*; use approx::assert_relative_eq; use std::sync::LazyLock; /// 测试数据 static TEST_DATA: LazyLock = LazyLock::new(|| { TestData { temp: vec![10000.0, 8000.0, 6000.0], elec: vec![1.0e13, 1.0e12, 1.0e11], freq: vec![1.0e15, 2.0e15, 3.0e15], anato: vec![ vec![1.0e14, 1.0e14, 1.0e14], // H vec![1.0e13, 1.0e13, 1.0e13], // He ], anion: vec![ vec![1.0e12, 1.0e12, 1.0e12], // H+ ], anmol: vec![ vec![0.0; 3], // H2 vec![0.0; 3], // 索引 1 vec![0.0; 3], // 索引 2 (H2) vec![0.0; 3], // OH (索引 3) vec![0.0; 3], // 索引 4 (OH) vec![0.0; 3], // CH (索引 5) vec![0.0; 3], // 额外 ], popul: vec![vec![0.0; 3]; 10], cross: vec![vec![0.0; 3]; 20], cia_h2h2_data: cia_h2h2::CiaH2h2Data::default(), cia_h2he_data: cia_h2he::CiaH2heData::default(), cia_h2h_data: cia_h2h::CiaH2hData::default(), cia_hhe_data: cia_hhe::CiaHheData::default(), } }); struct TestData { temp: Vec, elec: Vec, freq: Vec, anato: Vec>, anion: Vec>, anmol: Vec>, popul: Vec>, cross: Vec>, cia_h2h2_data: cia_h2h2::CiaH2h2Data, cia_h2he_data: cia_h2he::CiaH2heData, cia_h2h_data: cia_h2h::CiaH2hData, cia_hhe_data: cia_hhe::CiaHheData, } /// 创建测试用的模型状态 fn create_test_model() -> (OpaddModel<'static>, OpaddSwitches) { let data = &*TEST_DATA; let model = OpaddModel { temp: &data.temp, elec: &data.elec, freq: &data.freq, nd: 3, nlevel: 10, ielh: 0, iathe: 0, n0hn: 0, nkh: 0, n0ahe: 0, anato: &data.anato, anion: &data.anion, anmol: &data.anmol, popul: &data.popul, cross: &data.cross, ncon: 5, cia_h2h2_data: &data.cia_h2h2_data, cia_h2he_data: &data.cia_h2he_data, cia_h2h_data: &data.cia_h2h_data, cia_hhe_data: &data.cia_hhe_data, }; let switches = OpaddSwitches { irsct: 0, irsche: 0, irsch2: 0, iophmi: 0, ioph2p: 0, iophem: 0, ioph2m: 0, iopch: 0, iopoh: 0, ioh2h2: 0, ioh2he: 0, ioh2h: 0, iohhe: 0, ifmol: 0, tmolim: 5000.0, }; (model, switches) } #[test] fn test_opadd_basic() { let (model, switches) = create_test_model(); let mut cache = OpaddCache::default(); let input = OpaddInput { mode: 0, icall: 1, ij: 0, id: 0, }; let result = opadd(&input, &switches, &model, &mut cache); // 当所有开关关闭时,结果应为 0 assert_relative_eq!(result.abad, 0.0, epsilon = 1e-20); assert_relative_eq!(result.emad, 0.0, epsilon = 1e-20); assert_relative_eq!(result.scad, 0.0, epsilon = 1e-20); } #[test] fn test_opadd_mode_negative() { let (model, switches) = create_test_model(); let mut cache = OpaddCache::default(); let input = OpaddInput { mode: -1, icall: 1, ij: 0, id: 0, }; let result = opadd(&input, &switches, &model, &mut cache); // mode < 0 应立即返回 assert_relative_eq!(result.abad, 0.0, epsilon = 1e-20); } #[test] fn test_opadd_cache_initialization() { let (model, switches) = create_test_model(); let mut cache = OpaddCache::default(); let input = OpaddInput { mode: 0, icall: 1, ij: 0, id: 0, }; let _ = opadd(&input, &switches, &model, &mut cache); // 验证缓存已初始化 assert_relative_eq!(cache.t, 10000.0, epsilon = 1e-10); assert_relative_eq!(cache.ane, 1.0e13, epsilon = 1e-10); assert!(cache.hkt > 0.0); } }