//! 初始化氢线轮廓表(Lemke/Tremblay 表格)。 //! //! 重构自 TLUSTY `LEMINI` 子程序。 //! //! # 功能 //! //! - 打开并读取 Lemke 或 Tremblay 氢线 Stark 展宽表格 //! - 填充氢线轮廓相关数组 //! - 设置渐近轮廓系数 use std::fs::File; use std::io::{BufRead, BufReader}; use crate::state::constants::*; use crate::state::model::{HydPrf, StrAux}; use crate::io::{FortranReader, Result, IoError}; // ============================================================================ // 常量 // ============================================================================ /// ln(10) 用于对数转换 const LN10: f64 = std::f64::consts::LN_10; // ============================================================================ // 输入参数结构体 // ============================================================================ /// LEMINI 输入参数。 pub struct LeminiParams { /// 氢线表格类型 (21=Lemke, 22=Tremblay) pub ihydpr: i32, } /// 单个谱线块的数据。 #[derive(Debug, Clone)] pub struct LineBlockData { /// 上能级索引 I pub i: i32, /// 下能级索引 J pub j: i32, /// 最小波长对数 pub almin: f64, /// 最小电子密度对数 pub anemin: f64, /// 最小温度对数 pub tmin: f64, /// 波长步长 pub dla: f64, /// 电子密度步长 pub dle: f64, /// 温度步长 pub dlt: f64, /// 波长点数 pub nwl: i32, /// 电子密度点数 pub ne: i32, /// 温度点数 pub nt: i32, } /// LEMINI 输出结果。 #[derive(Debug, Clone)] pub struct LeminiOutput { /// 更新的 ILINH 数组 pub ilinh_updates: Vec<((usize, usize), i32)>, /// 更新的谱线数据 pub line_data: Vec, } /// 单条谱线的完整数据。 #[derive(Debug, Clone)] pub struct LineData { /// 谱线索引 pub iline: usize, /// 波长点数 pub nwl: i32, /// 温度点数 pub nt: i32, /// 电子密度点数 pub ne: i32, /// 波长数组 (对数空间) pub wlh: Vec, /// 波长数组 (线性空间) pub wlhyd: Vec, /// 电子密度网格 (对数空间) pub xnelem: Vec, /// 温度网格 (对数空间) pub xtlem: Vec, /// 轮廓数据 PRFHYD pub prfhyd: Vec, /// 渐近系数 XK0 pub xk0: f64, } // ============================================================================ // 核心计算函数 // ============================================================================ /// 解析谱线块头部信息。 /// /// Fortran 格式: 自由格式读取 10 个值 fn parse_line_block_header(line: &str) -> Option { let parts: Vec<&str> = line.split_whitespace().collect(); if parts.len() < 10 { return None; } Some(LineBlockData { i: parts[0].parse().ok()?, j: parts[1].parse().ok()?, almin: parts[2].parse().ok()?, anemin: parts[3].parse().ok()?, tmin: parts[4].parse().ok()?, dla: parts[5].parse().ok()?, dle: parts[6].parse().ok()?, dlt: parts[7].parse().ok()?, nwl: parts[8].parse().ok()?, ne: parts[9].parse().ok()?, nt: if parts.len() > 10 { parts[10].parse().ok()? } else { 0 }, }) } /// 计算渐近轮廓系数 XK0。 /// /// 从表格最后一列数据计算渐近系数。 fn compute_xk0(prfhyd_last: f64, wlhyd_last: f64) -> f64 { // XCLOG = PRFHYD(...,NWL,1,1) + 2.5*WLHYD(...,NWL) - 0.477121 let xclog = prfhyd_last + 2.5 * wlhyd_last - 0.477121; // XKLOG = 0.6666667 * XCLOG let xklog = 0.6666667 * xclog; // XK0 = EXP(XKLOG * LN(10)) (xklog * LN10).exp() } /// 执行 LEMINI 核心计算(纯计算部分)。 /// /// # 参数 /// - `params`: 输入参数 /// - `table_data`: 从文件读取的表格数据 /// /// # 返回 /// 填充的数组数据 pub fn lemini_pure(params: &LeminiParams, table_data: &LemkeTableData) -> LeminiOutput { let mut ilinh_updates = Vec::new(); let mut line_data = Vec::new(); let mut iline = 0i32; for tab in &table_data.tables { for block in &tab.blocks { iline += 1; let iline_idx = (iline - 1) as usize; // 设置 ILINH(I,J) = ILINE let i_idx = (block.header.i - 1) as usize; let j_idx = (block.header.j - 1) as usize; if i_idx < 4 && j_idx < 22 { ilinh_updates.push(((i_idx, j_idx), iline)); } // 计算波长数组 let nwl = block.header.nwl as usize; let mut wlh = vec![0.0; nwl]; let mut wlhyd = vec![0.0; nwl]; for iwl in 0..nwl { let log_wl = block.header.almin + iwl as f64 * block.header.dla; wlh[iwl] = log_wl; wlhyd[iwl] = (log_wl * LN10).exp(); } // 计算电子密度网格 let ne = block.header.ne as usize; let mut xnelem = vec![0.0; ne]; for ine in 0..ne { xnelem[ine] = block.header.anemin + ine as f64 * block.header.dle; } // 计算温度网格 let nt = block.header.nt as usize; let mut xtlem = vec![0.0; nt]; for it in 0..nt { xtlem[it] = block.header.tmin + it as f64 * block.header.dlt; } // 计算 XK0 let prfhyd_last = if !block.prfhyd.is_empty() && nwl > 0 { block.prfhyd[0 * nwl + nwl - 1] // ine=0, it=0 的最后一个波长点 } else { 0.0 }; let wlhyd_last = if nwl > 0 { wlhyd[nwl - 1] } else { 0.0 }; let xk0 = compute_xk0(prfhyd_last, wlhyd[nwl - 1].log10()); line_data.push(LineData { iline: iline_idx, nwl: block.header.nwl, nt: block.header.nt, ne: block.header.ne, wlh, wlhyd, xnelem, xtlem, prfhyd: block.prfhyd.clone(), xk0, }); } } LeminiOutput { ilinh_updates, line_data, } } /// 将 LeminiOutput 应用到 HydPrf 和 StrAux 结构体。 pub fn apply_lemini_output(hydprf: &mut HydPrf, straux: &mut StrAux, output: &LeminiOutput) { // 应用 ILINH 更新 for ((i, j), value) in &output.ilinh_updates { let idx = j * 4 + i; // ILINH(4,22) -> ilinh[j*4 + i] if idx < hydprf.ilinh.len() { hydprf.ilinh[idx] = *value; } } // 应用谱线数据 for line in &output.line_data { let iline = line.iline; // 设置 NWLHYD, NWLH, NTH, NEH if iline < hydprf.nwlhyd.len() { hydprf.nwlhyd[iline] = line.nwl; hydprf.nwlh[iline] = line.nwl; hydprf.nth[iline] = line.nt; hydprf.neh[iline] = line.ne; } // 设置 WLH 和 WLHYD for (iwl, (&wlh_val, &wlhyd_val)) in line.wlh.iter().zip(line.wlhyd.iter()).enumerate() { if iwl < MHWL { // WLH(MHWL, MLINH) -> wlh[iline * MHWL + iwl] hydprf.wlh[iline * MHWL + iwl] = wlh_val; // WLHYD(MLINH, MHWL) -> wlhyd[iline + MLINH * iwl] hydprf.wlhyd[iline + MLINH * iwl] = wlhyd_val; } } // 设置 XNELEM for (ine, &val) in line.xnelem.iter().enumerate() { if ine < MHE { // XNELEM(MHE, MLINH) -> xnelem[iline * MHE + ine] hydprf.xnelem[iline * MHE + ine] = val; } } // 设置 XTLEM for (it, &val) in line.xtlem.iter().enumerate() { if it < MHT { // XTLEM(MHT, MLINH) -> xtlem[iline * MHT + it] hydprf.xtlem[iline * MHT + it] = val; } } // 设置 PRFHYD let nwl = line.nwl as usize; let nt = line.nt as usize; let ne = line.ne as usize; for ine in 0..ne { for it in 0..nt { for iwl in 0..nwl { let src_idx = ine * nt * nwl + it * nwl + iwl; if src_idx < line.prfhyd.len() { hydprf.set_prfhyd(iline, iwl, it, ine, line.prfhyd[src_idx]); } } } } // 设置 XK0 if iline < straux.xk0.len() { straux.xk0[iline] = line.xk0; } } } // ============================================================================ // 数据结构 // ============================================================================ /// 单个表格块的数据。 #[derive(Debug, Clone)] pub struct TableBlock { /// 头部信息 pub header: LineBlockData, /// 轮廓数据 PRFHYD(NE, NT, NWL) pub prfhyd: Vec, } /// 单个表格的数据。 #[derive(Debug, Clone)] pub struct Table { /// 谱线块数量 pub nlly: i32, /// 谱线块数据 pub blocks: Vec, } /// 完整的 Lemke/Tremblay 表格数据。 #[derive(Debug, Clone, Default)] pub struct LemkeTableData { /// 表格数量 pub ntab: i32, /// 表格数据 pub tables: Vec, } // ============================================================================ // 文件读取函数 // ============================================================================ /// 读取 Lemke/Tremblay 表格文件。 /// /// # 参数 /// - `file_path`: 文件路径 /// /// # 返回 /// 表格数据 pub fn read_lemke_table(file_path: &str) -> Result { let file = File::open(file_path)?; let reader = BufReader::new(file); let mut lines = reader.lines(); // 读取 NTAB let ntab: i32 = lines .next() .ok_or(IoError::UnexpectedEof)?? .trim() .parse() .map_err(|_| IoError::ParseError("Failed to parse NTAB".to_string()))?; let mut tables = Vec::new(); for _ in 0..ntab { // 读取 NLLY let nlly: i32 = lines .next() .ok_or(IoError::UnexpectedEof)?? .trim() .parse() .map_err(|_| IoError::ParseError("Failed to parse NLLY".to_string()))?; let mut blocks = Vec::new(); // 读取头部信息 for _ in 0..nlly { let header_line = lines.next().ok_or(IoError::UnexpectedEof)??; let header = parse_line_block_header(&header_line).ok_or_else(|| { IoError::ParseError(format!("Failed to parse line block header: {}", header_line)) })?; blocks.push(TableBlock { header, prfhyd: Vec::new(), }); } // 读取轮廓数据 for block in &mut blocks { let nwl = block.header.nwl as usize; let ne = block.header.ne as usize; let nt = block.header.nt as usize; // 跳过空行 if let Some(Ok(_)) = lines.next() {} // 读取轮廓数据 for _ine in 0..ne { for _it in 0..nt { let data_line = lines.next().ok_or(IoError::UnexpectedEof)??; let parts: Vec<&str> = data_line.split_whitespace().collect(); // 跳过第一个值 (QLT),读取 NWL 个轮廓值 for iwl in 1..=nwl { if iwl < parts.len() { let val: f64 = parts[iwl] .parse() .map_err(|_| IoError::ParseError(format!("Failed to parse PRFHYD value")))?; block.prfhyd.push(val); } } } } } tables.push(Table { nlly, blocks }); } Ok(LemkeTableData { ntab, tables }) } /// 执行 LEMINI(带 I/O)。 /// /// # 参数 /// - `ihydpr`: 氢线表格类型 (21=Lemke, 22=Tremblay) /// /// # 返回 /// 表格数据 pub fn lemini(ihydpr: i32) -> Result<(LeminiOutput, LemkeTableData)> { let file_path = if ihydpr == 21 { "./data/lemke.dat" } else if ihydpr == 22 { "./data/tremblay.dat" } else { return Err(IoError::FormatError(format!("Unknown IHYDPR value: {}", ihydpr))); }; let table_data = read_lemke_table(file_path)?; let params = LeminiParams { ihydpr }; let output = lemini_pure(¶ms, &table_data); Ok((output, table_data)) } // ============================================================================ // 测试 // ============================================================================ #[cfg(test)] mod tests { use super::*; #[test] fn test_compute_xk0() { // 测试渐近系数计算 let prfhyd_last: f64 = -5.0; let wlhyd_last: f64 = 1000.0_f64; // 1000 Å let wlhyd_log = wlhyd_last.log10(); let xk0 = compute_xk0(prfhyd_last, wlhyd_log); assert!(xk0 > 0.0, "XK0 should be positive"); assert!(xk0.is_finite(), "XK0 should be finite"); } #[test] fn test_parse_line_block_header() { // 模拟 Fortran 自由格式行 let line = "1 2 3.5 10.0 3.8 0.01 0.1 0.05 90 20 7"; let header = parse_line_block_header(line).unwrap(); assert_eq!(header.i, 1); assert_eq!(header.j, 2); assert!((header.almin - 3.5).abs() < 1e-10); assert!((header.anemin - 10.0).abs() < 1e-10); assert_eq!(header.nwl, 90); assert_eq!(header.ne, 20); assert_eq!(header.nt, 7); } #[test] fn test_lemini_pure_basic() { // 创建测试表格数据 let header = LineBlockData { i: 1, j: 2, almin: 3.5, anemin: 10.0, tmin: 3.8, dla: 0.01, dle: 0.1, dlt: 0.05, nwl: 3, ne: 2, nt: 2, }; let block = TableBlock { header: header.clone(), prfhyd: vec![-5.0, -4.9, -4.8, -5.1, -5.0, -4.9, -5.2, -5.1, -5.0, -5.3, -5.2, -5.1], }; let table = Table { nlly: 1, blocks: vec![block], }; let table_data = LemkeTableData { ntab: 1, tables: vec![table], }; let params = LeminiParams { ihydpr: 21 }; let output = lemini_pure(¶ms, &table_data); // 验证 ILINH 更新 assert!(!output.ilinh_updates.is_empty()); // 验证谱线数据 assert_eq!(output.line_data.len(), 1); let line = &output.line_data[0]; assert_eq!(line.nwl, 3); assert_eq!(line.ne, 2); assert_eq!(line.nt, 2); assert_eq!(line.wlh.len(), 3); assert!(line.xk0 > 0.0); } #[test] fn test_apply_lemini_output() { let mut hydprf = HydPrf::default(); let mut straux = StrAux::default(); // 创建测试输出 let output = LeminiOutput { ilinh_updates: vec![((0, 1), 5)], line_data: vec![LineData { iline: 0, nwl: 3, nt: 2, ne: 2, wlh: vec![3.5, 3.51, 3.52], wlhyd: vec![3162.0, 3235.0, 3311.0], xnelem: vec![10.0, 10.1], xtlem: vec![3.8, 3.85], prfhyd: vec![-5.0; 12], xk0: 1.5e-8, }], }; apply_lemini_output(&mut hydprf, &mut straux, &output); // 验证 ILINH assert_eq!(hydprf.ilinh[1 * 4 + 0], 5); // 验证 NWLHYD assert_eq!(hydprf.nwlhyd[0], 3); assert_eq!(hydprf.nth[0], 2); assert_eq!(hydprf.neh[0], 2); // 验证 XK0 assert!((straux.xk0[0] - 1.5e-8).abs() < 1e-15); } #[test] fn test_wavelength_conversion() { // 测试对数到线性波长转换 let log_wl = 3.5; // log10(3162 Å) let linear_wl = (log_wl * LN10).exp(); assert!((linear_wl - 3162.277).abs() < 0.1); } }