//! TLUSTY 初始化驱动程序。 //! //! 重构自 TLUSTY `initia.f`。 //! 这是 TLUSTY 的主入口点,负责读取输入和初始化所有状态。 //! //! # 功能 //! //! - 读取基本输入参数(TEFF, GRAV, LTE 等) //! - 设置频率网格和权重 //! - 初始化原子数据 //! - 读取和设置模型 //! - 配置各种物理参数 //! //! # 重构策略 //! //! 由于 INITIA 是一个大型驱动程序,大部分逻辑是调用其他子模块。 //! 本模块将纯计算部分提取为独立函数,便于测试。 use super::{Result, FortranReader, FortranWriter}; use crate::tlusty::state::constants::*; // f2r_depends: CHANGE, CHCTAB, CORRWM, DMDER, DOPGAM, GOMINI, INIFRC, INIFRS, INIFRT, INPDIS, INPMOD, INTERP, IROSET, LEVSET, LINSET, LINSPL, LTEGR, LTEGRD, NSTOUT, NSTPAR, ODFHYS, ODFSET, OPADD0, OPAHST, QUIT, RAYINI, RDATA, RDATAX, READBF, RTEANG, SIGAVE, SRTFRQ, STATE, TABINI, TABINT, TRAINI // ============================================================================ // 物理常数 // ============================================================================ /// h/k (Planck 常数 / Boltzmann 常数) [K·s] const HK: f64 = 4.79927e-11; /// h (Planck 常数) [erg·s] const H: f64 = 6.62620e-27; /// 电子电荷 [esu] const ECH: f64 = 4.80298e-10; /// 电子质量 [g] const EMASS: f64 = 9.1091e-28; /// Boltzmann 常数 [erg/K] const BOLK: f64 = 1.38066e-16; /// 氢原子质量 [g] const HMASS: f64 = 1.6733e-24; /// 单位转换常数 (用于 Klein-Nishina) const XCON: f64 = 8.0935e-21; /// Thomson 散射截面 [cm²] const SIGE: f64 = 6.6524e-25; /// π const PI: f64 = std::f64::consts::PI; /// 2π const TWO: f64 = 2.0 * PI; /// 1.0 const UN: f64 = 1.0; /// 0.5 const HALF: f64 = 0.5; /// Stefan-Boltzmann 常数 / 4 const SIG4P: f64 = 1.380835e-2; // ============================================================================ // 辅助数据 - 统计权重 // ============================================================================ /// 统计权重数据数组(来自 DATA 语句) const IGLE: [i32; 18] = [2, 1, 2, 1, 6, 9, 4, 9, 6, 1, 2, 1, 6, 9, 4, 9, 6, 1]; const IGMN: [i32; 25] = [ 2, 1, 2, 1, 6, 9, 4, 9, 6, 1, 2, 1, 6, 9, 4, 9, 6, 1, 10, 21, 28, 25, 6, 7, 6 ]; const IGFE: [i32; 26] = [ 2, 1, 2, 1, 6, 9, 4, 9, 6, 1, 2, 1, 6, 9, 4, 9, 6, 1, 10, 21, 28, 25, 6, 25, 30, 25 ]; const IGNI: [i32; 28] = [ 2, 1, 2, 1, 6, 9, 4, 9, 6, 1, 2, 1, 6, 9, 4, 9, 6, 1, 10, 21, 28, 25, 6, 25, 28, 21, 10, 21 ]; // ============================================================================ // 纯计算函数 // ============================================================================ /// 生成对数均匀频率网格。 /// /// 在频率范围 [frmin, frmax] 内生成 nfreq 个对数均匀分布的频率点, /// 并计算相应的梯形积分权重。 /// /// # 参数 /// /// * `frmin` - 最小频率 (Hz) /// * `frmax` - 最大频率 (Hz) /// * `nfreq` - 频率点数 /// /// # 返回 /// /// (freq, w) 元组: /// - `freq`: 频率数组(降序排列) /// - `w`: 积分权重数组 pub fn generate_log_frequency_grid( frmin: f64, frmax: f64, nfreq: usize, ) -> (Vec, Vec) { if nfreq == 0 { return (Vec::new(), Vec::new()); } if nfreq == 1 { // 单点特殊情况 return (vec![frmin], vec![1.0]); } let log_frmin = frmin.ln(); let log_frmax = frmax.ln(); let delta = (log_frmax - log_frmin) / (nfreq - 1) as f64; // 生成对数均匀网格(升序) let mut freq_ascending: Vec = (0..nfreq) .map(|i| (log_frmin + delta * i as f64).exp()) .collect(); // 反转为降序(Fortran 原始行为) freq_ascending.reverse(); let freq = freq_ascending; // 计算梯形积分权重 let mut w = vec![0.0; nfreq]; w[0] = 0.5 * (freq[0] - freq[1]); w[nfreq - 1] = 0.5 * (freq[nfreq - 2] - freq[nfreq - 1]); for ij in 1..nfreq - 1 { w[ij] = 0.5 * (freq[ij - 1] - freq[ij + 1]); } (freq, w) } /// 计算 Klein-Nishina 散射截面。 /// /// 根据 Rybicki & Lightman (1975) 的公式计算 Compton 散射截面。 /// 对于低能光子(xf << 1),使用泰勒展开; /// 对于高能光子(xf >> 1),使用渐近公式。 /// /// # 参数 /// /// * `freq` - 频率 (Hz) /// * `knish` - 是否使用完整的 Klein-Nishina 公式 /// 0: 一阶近似 /// 1: 完整公式 /// /// # 返回 /// /// 散射截面 (cm²) pub fn klein_nishina_cross_section(freq: f64, knish: i32) -> f64 { if knish == 0 { // 一阶近似 return SIGE * (UN - TWO * freq * XCON); } let xf = XCON * freq; if xf < 1e-1 { // 泰勒展开(低能极限) SIGE * (1.0 - xf * (2.0 - xf * (26.0 / 5.0 - xf * (13.3 - xf * (1144.0 / 35.0 - xf * (544.0 / 7.0 - xf * (3784.0 / 21.0 - xf * (6148.0 / 15.0 - xf * (151552.0 / 165.0 - xf * 111872.0 / 55.0))))))))) } else if xf > 1e3 { // 渐近公式(高能极限) SIGE * 3.0 / 8.0 / xf * (2.0 * xf).ln_1p() + 0.5 } else { // 完整 Klein-Nishina 公式 SIGE * 0.75 * ((1.0 + xf) / xf.powi(3) * (2.0 * xf * (1.0 + xf) / (1.0 + 2.0 * xf) - (1.0 + 2.0 * xf).ln_1p()) + 0.5 * (1.0 + 2.0 * xf).ln_1p() / xf - (1.0 + 3.0 * xf) / (1.0 + 2.0 * xf).powi(2)) } } /// 计算 Planck 函数 B_ν(T)。 /// /// # 参数 /// /// * `freq` - 频率 (Hz) /// * `temp` - 温度 (K) /// /// # 返回 /// /// Planck 函数值 [erg/(s·cm²·Hz·sr)] pub fn planck_function(freq: f64, temp: f64) -> f64 { let x = HK * freq / temp; if x > 700.0 { return 0.0; // 避免溢出 } let hp = H * freq; let exp_x = x.exp(); // CAS = 2.997925e18 Å/s = 2.997925e10 cm/s let cl = 2.997925e10; 2.0 * hp * freq.powi(3) / (cl * cl) / (exp_x - UN) } /// 计算外部辐照强度。 /// /// # 参数 /// /// * `freq` - 频率数组 (Hz) /// * `w` - 频率权重 /// * `trad` - 辐射温度 (K),> 0 使用黑体,< 0 从文件读取,= 0 无辐照 /// * `wdil` - 稀释因子 /// /// # 返回 /// /// (extrad, extot) 元组: /// - `extrad`: 各频率的外部辐射强度 /// - `extot`: 总外部辐射能量 pub fn compute_external_irradiation( freq: &[f64], w: &[f64], trad: f64, wdil: f64, ) -> (Vec, f64) { let nfreq = freq.len(); let mut extrad = vec![0.0; nfreq]; let mut extot = 0.0; if trad == 0.0 { // 无外部辐照 return (extrad, extot); } if trad > 0.0 { // 使用黑体辐射 for ij in 0..nfreq { let bnue = planck_function(freq[ij], trad); extrad[ij] = bnue / ((HK * freq[ij] / trad).exp() - UN) * wdil; extot += w[ij] * extrad[ij]; } } (extrad, extot) } /// 初始化 1/i² 和 1/i³ 数组。 /// /// # 参数 /// /// * `nlmx` - 最大氢能级数 /// /// # 返回 /// /// (xi2, xi3) 元组 pub fn init_reciprocal_powers(nlmx: usize) -> (Vec, Vec) { let mut xi2 = vec![0.0; nlmx + 1]; let mut xi3 = vec![0.0; nlmx + 1]; for i in 1..=nlmx { let x = i as f64; xi2[i] = UN / (x * x); xi3[i] = xi2[i] / x; } (xi2, xi3) } /// 获取元素的统计权重。 /// /// # 参数 /// /// * `iatii` - 原子序数 /// * `izii` - 离子电荷 /// /// # 返回 /// /// 统计权重值 pub fn get_statistical_weight(iatii: i32, izii: i32) -> f64 { if iatii <= izii { return 1.0; } let idx = (iatii - izii) as usize; match iatii { 1..=24 if idx <= 18 => IGLE[idx - 1] as f64, 25 if idx <= 25 => IGMN[idx - 1] as f64, 26 if idx <= 26 => IGFE[idx - 1] as f64, 28 if idx <= 28 => IGNI[idx - 1] as f64, _ => 1.0, } } // ============================================================================ // 参数结构体 // ============================================================================ /// 初始化配置参数。 #[derive(Debug, Clone)] pub struct InitiaConfig { /// 最大频率点数 pub mfreq: usize, /// 最大深度点数 pub mdepth: usize, /// 最大离子数 pub mion: usize, /// 最大能级数 pub mlevel: usize, /// 最大跃迁数 pub mtrans: usize, /// 最大原子数 pub matom: usize, /// 最大连续频率点数 pub mfreqc: usize, /// 最大线性化频率数 pub mfrex: usize, /// 最大线性化能级数 pub mlvexp: usize, /// 最大总参数数 pub mtot: usize, } impl Default for InitiaConfig { fn default() -> Self { Self { mfreq: MFREQ, mdepth: MDEPTH, mion: MION, mlevel: MLEVEL, mtrans: MTRANS, matom: MATOM, mfreqc: MFREQC, mfrex: MFREX, mlvexp: MLVEXP, mtot: MTOT, } } } /// 频率网格参数。 #[derive(Debug, Clone)] pub struct FrequencyGridParams { /// 最小频率 (Hz) pub frmin: f64, /// 最大频率 (Hz) pub frmax: f64, /// 频率点数 pub nfreq: usize, /// 是否使用预设频率 pub ifrset: i32, } /// 频率网格输出。 #[derive(Debug, Clone)] pub struct FrequencyGridOutput { /// 频率数组 (Hz) pub freq: Vec, /// 权重数组 pub w: Vec, /// ALI 索引数组 pub ijali: Vec, } /// INITIA 主参数结构体。 #[derive(Debug, Clone)] pub struct InitiaParams { /// 配置 pub config: InitiaConfig, /// 有效温度 (K) pub teff: f64, /// 表面重力 (cm/s², log10) pub grav: f64, /// 是否 LTE pub lte: bool, /// 是否灰大气 pub ltgrey: bool, /// 湍流速度 (cm/s) pub vtb: f64, /// 是否处理湍流压力 pub ipturb: i32, /// 深度点数 pub nd: usize, } impl Default for InitiaParams { fn default() -> Self { Self { config: InitiaConfig::default(), teff: 10000.0, grav: 4.0, lte: false, ltgrey: false, vtb: 0.0, ipturb: 0, nd: 50, } } } /// INITIA 输出结构体。 #[derive(Debug, Clone)] pub struct InitiaOutput { /// 频率网格 pub freq_grid: FrequencyGridOutput, /// 1/i² 数组 pub xi2: Vec, /// 1/i³ 数组 pub xi3: Vec, /// 湍流速度数组 pub vturb: Vec, /// Compton 散射截面 pub sigec: Vec, /// 外部辐照强度 pub extrad: Vec, } // ============================================================================ // 主初始化函数 // ============================================================================ /// 执行初始化的纯计算部分。 /// /// 这个函数实现 INITIA 中不涉及 I/O 的纯计算逻辑, /// 包括频率网格生成、Klein-Nishina 截面计算等。 /// /// # 参数 /// /// * `params` - 初始化参数 /// * `grid_params` - 频率网格参数 /// * `icompt` - Compton 散射模式 (0: 不变截面, 1: 可变) /// * `knish` - Klein-Nishina 模式 (0: 一阶近似, 1: 完整公式) /// /// # 返回 /// /// 初始化输出结构体 pub fn initia_pure( params: &InitiaParams, grid_params: &FrequencyGridParams, icompt: i32, knish: i32, ) -> InitiaOutput { // 1. 生成频率网格 let (freq, w) = generate_log_frequency_grid( grid_params.frmin, grid_params.frmax, grid_params.nfreq, ); // 初始化 ALI 索引 let ijali = vec![1; grid_params.nfreq]; // 2. 初始化 1/i² 和 1/i³ let (xi2, xi3) = init_reciprocal_powers(NLMX); // 3. 初始化湍流速度 let mut vturb = vec![0.0; params.nd]; let mut vturbs = vec![0.0; params.nd]; let vtb_actual = if params.vtb.abs() < 1e3 { params.vtb * 1e5 // 从 km/s 转换为 cm/s } else { params.vtb }; for id in 0..params.nd { if vtb_actual > 0.0 { vturb[id] = vtb_actual; } if params.ipturb == 0 { vturb[id] = 0.0; } vturbs[id] = vtb_actual.abs(); } // 4. 计算 Compton 散射截面 let mut sigec = vec![SIGE; grid_params.nfreq]; if icompt != 0 { for ij in 0..grid_params.nfreq { sigec[ij] = klein_nishina_cross_section(freq[ij], knish); } } // 5. 初始化外部辐照 let extrad = vec![0.0; grid_params.nfreq]; InitiaOutput { freq_grid: FrequencyGridOutput { freq, w, ijali, }, xi2, xi3, vturb, sigec, extrad, } } // ============================================================================ // 测试 // ============================================================================ #[cfg(test)] mod tests { use super::*; use approx::assert_relative_eq; #[test] fn test_generate_log_frequency_grid() { // 测试单点 let (freq, w) = generate_log_frequency_grid(1e14, 1e15, 1); assert_eq!(freq.len(), 1); assert_relative_eq!(freq[0], 1e14); assert_relative_eq!(w[0], 1.0); // 测试多点 let (freq, w) = generate_log_frequency_grid(1e14, 1e15, 5); assert_eq!(freq.len(), 5); // 频率应该是降序的 for i in 0..freq.len() - 1 { assert!(freq[i] > freq[i + 1]); } // 频率应该在范围内 assert!(freq[0] <= 1e15); assert!(freq[freq.len() - 1] >= 1e14); // 权重总和检查(近似) let total_w: f64 = w.iter().sum(); assert!(total_w > 0.0); } #[test] fn test_klein_nishina_cross_section() { // 低能极限:截面接近 Thomson 截面 let low_energy = klein_nishina_cross_section(1e14, 1); assert_relative_eq!(low_energy / SIGE, 1.0, epsilon = 1e-4); // 一阶近似 let first_order = klein_nishina_cross_section(1e18, 0); assert!(first_order < SIGE); // 高能极限:截面应该更小 let high_energy = klein_nishina_cross_section(1e22, 1); assert!(high_energy < SIGE); assert!(high_energy > 0.0); } #[test] fn test_planck_function() { // 测试 Wien 极限(高频/低温) let bnue = planck_function(1e15, 5000.0); assert!(bnue > 0.0); // 测试 Rayleigh-Jeans 极限(低频/高温) let bnue_rj = planck_function(1e12, 50000.0); assert!(bnue_rj > 0.0); // 测试极高温(避免溢出) let bnue_hot = planck_function(1e15, 100000.0); assert!(bnue_hot > 0.0); } #[test] fn test_init_reciprocal_powers() { let (xi2, xi3) = init_reciprocal_powers(10); // 检查 i=1 assert_relative_eq!(xi2[1], 1.0); assert_relative_eq!(xi3[1], 1.0); // 检查 i=2 assert_relative_eq!(xi2[2], 0.25); assert_relative_eq!(xi3[2], 0.125); // 检查 i=10 assert_relative_eq!(xi2[10], 0.01); assert_relative_eq!(xi3[10], 0.001); } #[test] fn test_get_statistical_weight() { // 测试 H I (Z=1, ion=0) let g_h1 = get_statistical_weight(1, 0); assert_eq!(g_h1, 2.0); // 测试 He I (Z=2, ion=0) let g_he1 = get_statistical_weight(2, 0); assert_eq!(g_he1, 1.0); // 测试 He II (Z=2, ion=1) let g_he2 = get_statistical_weight(2, 1); assert_eq!(g_he2, 2.0); // 测试完全电离(应该返回 1.0) let g_ionized = get_statistical_weight(1, 1); assert_eq!(g_ionized, 1.0); } #[test] fn test_compute_external_irradiation() { let freq = vec![1e14, 5e14, 1e15]; let w = vec![0.5e14, 0.5e14, 0.5e14]; // 无外部辐照 let (extrad, extot) = compute_external_irradiation(&freq, &w, 0.0, 1.0); assert_eq!(extrad, vec![0.0, 0.0, 0.0]); assert_relative_eq!(extot, 0.0); // 有外部辐照 let (extrad, extot) = compute_external_irradiation(&freq, &w, 10000.0, 0.5); assert!(extrad.iter().all(|&x| x >= 0.0)); assert!(extot > 0.0); } #[test] fn test_initia_pure() { let config = InitiaConfig::default(); let params = InitiaParams { config: config.clone(), teff: 35000.0, grav: 4.5, lte: false, ltgrey: false, vtb: 10.0, // 10 km/s ipturb: 0, nd: 50, }; let grid_params = FrequencyGridParams { frmin: 1e14, frmax: 1e16, nfreq: 100, ifrset: 0, }; let output = initia_pure(¶ms, &grid_params, 0, 0); assert_eq!(output.freq_grid.freq.len(), 100); assert_eq!(output.freq_grid.w.len(), 100); assert_eq!(output.freq_grid.ijali.len(), 100); assert_eq!(output.vturb.len(), 50); assert_eq!(output.sigec.len(), 100); // 检查 Compton 截面(不变模式) for &s in &output.sigec { assert_relative_eq!(s, SIGE); } } }