//! Kurucz ATLAS 格式模型读取。 //! //! 重构自 TLUSTY `kurucz.f`。 //! //! 从 unit 8 (fort.8) 读取 Kurucz ATLAS 格式的初始大气模型。 //! 支持 ATLAS9 和 ATLAS12 格式。 //! //! # 功能 //! //! - 解析 Kurucz ATLAS 模型文件格式 //! - 支持标准格式和 IFIXDE 固定格式 //! - 提取温度、密度、电子密度等物理量 use super::{IoError, Result}; use crate::tlusty::state::constants::*; use std::io::BufRead; // f2r_depends: LEVSOL, MOLEQ, QUIT, RATMAT, RHONEN, SABOLF, WNSTOR /// Kurucz ATLAS format model reader wrapper (matches Fortran KURUCZ subroutine signature). pub fn kurucz(ndpth: usize, reader: &mut R) -> Result { let params = KuruczReadParams { max_depth: ndpth, ..Default::default() }; read_kurucz(¶ms, reader) } // ============================================================================ // 常量 // ============================================================================ /// 输入参数数量 const MINPUT: usize = 7; // ============================================================================ // Kurucz 模型头部 // ============================================================================ /// Kurucz 模型头部信息 #[derive(Debug, Clone)] pub struct KuruczHeader { /// 有效温度 (K) pub teff: f64, /// 引力对数 (log g, cgs) pub grav_log: f64, /// 深度点数 pub nd: usize, } // ============================================================================ // Kurucz 深度点数据 // ============================================================================ /// 单个深度点的 Kurucz 数据(标准格式) #[derive(Debug, Clone)] pub struct KuruczDepthPoint { /// 深度变量 (X1) pub depth: f64, /// 温度 (K) pub temp: f64, /// 粒子密度 × 玻尔兹曼常数 × 温度 (X3, 压力相关) pub x3: f64, /// 电子密度 (cm⁻³) pub elec: f64, } /// 单个深度点的 Kurucz 数据(IFIXDE 格式) #[derive(Debug, Clone)] pub struct KuruczIfixdeDepthPoint { /// 质量深度 (g/cm²) pub dm: f64, /// 温度 (K) pub temp: f64, /// 气压 (dyn/cm²) pub pressure: f64, /// 中性氢密度 (cm⁻³) pub ane0: f64, /// 辅助量 1 pub a1: f64, /// 辅助量 2 pub a2: f64, /// 辅助量 3 pub a3: f64, /// 速度场 pub vel: f64, /// 质量密度 (g/cm³) pub rho: f64, } // ============================================================================ // 输入参数 // ============================================================================ /// KURUCZ 读取参数。 pub struct KuruczReadParams { /// 固定深度格式标志 (IFIXDE) /// > 0: 使用固定格式读取 /// = 0: 使用标准 Kurucz 格式 pub ifixde: i32, /// 最大深度点数 (用于数组大小检查) pub max_depth: usize, } impl Default for KuruczReadParams { fn default() -> Self { Self { ifixde: 0, max_depth: MDEPTH, } } } // ============================================================================ // 输出结构 // ============================================================================ /// KURUCZ 模型读取输出。 #[derive(Debug, Clone)] pub struct KuruczModel { /// 深度点数 pub nd: usize, /// 有效温度 (K) pub teff: f64, /// 引力对数 (log g) pub grav_log: f64, /// 深度点数据(标准格式) pub depth_points: Vec, /// 深度点数据(IFIXDE 格式) pub depth_points_ifixde: Vec, /// 插值标志 pub intrpl: i32, /// 是否使用 IFIXDE 格式 pub is_ifixde: bool, } impl KuruczModel { /// 获取温度数组 pub fn temperatures(&self) -> Vec { if self.is_ifixde { self.depth_points_ifixde.iter().map(|d| d.temp).collect() } else { self.depth_points.iter().map(|d| d.temp).collect() } } /// 获取电子密度数组 pub fn electron_densities(&self) -> Vec { if self.is_ifixde { self.depth_points_ifixde.iter().map(|d| d.ane0).collect() } else { self.depth_points.iter().map(|d| d.elec).collect() } } /// 获取质量密度数组 pub fn densities(&self) -> Vec { if self.is_ifixde { self.depth_points_ifixde.iter().map(|d| d.rho).collect() } else { // 标准格式需要从 X3 计算 self.depth_points.iter().map(|d| d.x3).collect() } } } // ============================================================================ // 辅助函数 // ============================================================================ /// 读取 Kurucz 头部信息 (标准格式) /// /// FORMAT: A15, 6X, F8.5 fn read_kurucz_header_standard(reader: &mut R) -> Result<(String, f64)> { let mut line = String::new(); reader.read_line(&mut line)?; if line.len() < 22 { return Err(IoError::ParseError(format!( "Kurucz header line too short: {}", line.len() ))); } // A15: 列 1-15 - KUR 字符串 let kur = line[0..15].trim().to_string(); // F8.5: 列 22-29 (跳过 6 个字符) - GRAVK let gravk: f64 = if line.len() >= 29 { line[21..29].trim().parse().unwrap_or(0.0) } else { 0.0 }; Ok((kur, gravk)) } /// 从 Kurucz 字符串解析 TEFF /// /// FORMAT: 4X, F8.0 fn parse_teff_from_kur(kur: &str) -> Result { // TEFF 在字符串中的位置: 'TEFF' 开头,后面是温度值 if !kur.starts_with("TEFF") { return Err(IoError::ParseError(format!( "Not a Kurucz model: expected 'TEFF', got '{}'", kur ))); } // 4X, F8.0: 从第 5 个字符开始读取温度 if kur.len() < 12 { return Err(IoError::ParseError(format!( "Kurucz TEFF string too short: '{}'", kur ))); } let teff_str = kur[4..12].trim(); teff_str .parse() .map_err(|e| IoError::ParseError(format!("Failed to parse TEFF: {} in '{}'", e, kur))) } /// 跳过直到找到 'READ DECK' 行 fn skip_to_read_deck(reader: &mut R) -> Result { let mut kur = String::new(); loop { kur.clear(); let bytes = reader.read_line(&mut kur)?; if bytes == 0 { return Err(IoError::UnexpectedEof); } if kur.trim().starts_with("READ DECK") { // 解析深度点数: 10X, I3 let ndpth_str = if kur.len() >= 13 { kur[10..13].trim() } else { "0" }; let ndpth: i32 = ndpth_str.parse().unwrap_or(0); return Ok(ndpth); } } } /// 读取固定格式深度数据头部 (IFIXDE > 0 路径) /// /// FORMAT: 4X, F8.0, 9X, F8.5 fn read_ifixde_header(reader: &mut R) -> Result<(f64, f64)> { let mut line = String::new(); reader.read_line(&mut line)?; if line.len() < 26 { return Err(IoError::ParseError(format!( "IFIXDE header line too short: {}", line.len() ))); } // F8.0: 列 5-12 - TEF let tef: f64 = line[4..12].trim().parse().unwrap_or(0.0); // F8.5: 列 22-29 - GRAV let grav: f64 = if line.len() >= 29 { line[21..29].trim().parse().unwrap_or(0.0) } else { 0.0 }; Ok((tef, grav)) } /// 跳过 19 行然后读取深度点数 /// /// FORMAT: ////////////////////10X,I3/ (19 个斜杠) fn read_nd_after_skip(reader: &mut R) -> Result { // 跳过 19 行 for _ in 0..19 { let mut line = String::new(); reader.read_line(&mut line)?; } // 读取深度点数行: 10X, I3 let mut line = String::new(); reader.read_line(&mut line)?; // 解析 I3 let nd: i32 = if line.len() >= 13 { line[10..13].trim().parse().unwrap_or(0) } else { line.trim().parse().unwrap_or(0) }; Ok(nd) } /// 读取自由格式深度数据 (IFIXDE 路径) /// /// 自由格式: DM, TEMP, P, ANE0, A1, A2, A3, VEL, RHO fn read_ifixde_depth(reader: &mut R) -> Result { let mut line = String::new(); reader.read_line(&mut line)?; let values: Vec = line .split_whitespace() .filter_map(|s| s.parse().ok()) .collect(); if values.len() < 9 { return Err(IoError::ParseError(format!( "Not enough values in IFIXDE depth line: expected 9, got {} in '{}'", values.len(), line ))); } Ok(KuruczIfixdeDepthPoint { dm: values[0], temp: values[1], pressure: values[2], ane0: values[3], a1: values[4], a2: values[5], a3: values[6], vel: values[7], rho: values[8], }) } /// 读取标准格式深度数据 /// /// 自由格式: X(1)..X(7) fn read_standard_depth(reader: &mut R) -> Result { let mut line = String::new(); reader.read_line(&mut line)?; let values: Vec = line .split_whitespace() .filter_map(|s| s.parse().ok()) .collect(); if values.len() < MINPUT { return Err(IoError::ParseError(format!( "Not enough values in standard depth line: expected {}, got {} in '{}'", MINPUT, values.len(), line ))); } Ok(KuruczDepthPoint { depth: values[0], temp: values[1], x3: values[2], elec: values[3], }) } // ============================================================================ // 主函数 // ============================================================================ /// 读取 Kurucz ATLAS 格式模型。 /// /// # 参数 /// - `params` - 读取参数 /// - `reader` - BufRead 读取器 /// /// # 返回 /// Kurucz 模型数据或错误 /// /// # Fortran 原始代码 /// ```fortran /// SUBROUTINE KURUCZ(NDPTH) /// ``` pub fn read_kurucz( params: &KuruczReadParams, reader: &mut R, ) -> Result { if params.ifixde > 0 { read_kurucz_ifixde(params, reader) } else { read_kurucz_standard(params, reader) } } /// 读取标准 Kurucz 格式模型。 fn read_kurucz_standard( params: &KuruczReadParams, reader: &mut R, ) -> Result { // 读取头部 let (kur, grav_log) = read_kurucz_header_standard(reader)?; let teff = parse_teff_from_kur(&kur)?; // 跳过直到 'READ DECK' let ndpth = skip_to_read_deck(reader)?; let nd = (ndpth - 1) as usize; if nd > params.max_depth { return Err(IoError::ParseError(format!( "ND {} exceeds max depth {}", nd, params.max_depth ))); } // 跳过一行 (TTT) let mut dummy = String::new(); reader.read_line(&mut dummy)?; // 读取每个深度点 let mut depth_points = Vec::with_capacity(nd); for _ in 0..nd { let dp = read_standard_depth(reader)?; depth_points.push(dp); } // 查找 'BEGIN' 和插值标志 let mut intrpl = 0i32; loop { let mut line = String::new(); let bytes = reader.read_line(&mut line)?; if bytes == 0 { break; } if line.trim().starts_with("BEGIN") { // 尝试读取插值标志 let mut intrpl_line = String::new(); if reader.read_line(&mut intrpl_line).is_ok() { intrpl = intrpl_line.trim().parse().unwrap_or(0); } break; } } Ok(KuruczModel { nd, teff, grav_log, depth_points, depth_points_ifixde: Vec::new(), intrpl, is_ifixde: false, }) } /// 读取 IFIXDE 格式模型。 fn read_kurucz_ifixde( params: &KuruczReadParams, reader: &mut R, ) -> Result { // 读取头部 let (teff, grav_log) = read_ifixde_header(reader)?; // 读取深度点数 let nd_raw = read_nd_after_skip(reader)?; let nd = (nd_raw - 1) as usize; if nd > params.max_depth { return Err(IoError::ParseError(format!( "ND {} exceeds max depth {}", nd, params.max_depth ))); } // 读取每个深度点 let mut depth_points = Vec::with_capacity(nd); for _ in 0..nd { let dp = read_ifixde_depth(reader)?; depth_points.push(dp); } Ok(KuruczModel { nd, teff, grav_log, depth_points: Vec::new(), depth_points_ifixde: depth_points, intrpl: 0, is_ifixde: true, }) } /// 从 FortranReader 读取 Kurucz 模型。 /// /// 注意:此函数会消耗 reader 的缓冲区。 pub fn read_kurucz_from_reader( params: &KuruczReadParams, reader: &mut R, ) -> Result { read_kurucz(params, reader) } // ============================================================================ // 测试 // ============================================================================ #[cfg(test)] mod tests { use super::*; use std::io::BufReader; #[test] fn test_parse_teff_from_kur() { let kur = "TEFF 9750 "; let teff = parse_teff_from_kur(kur).unwrap(); assert!((teff - 9750.0).abs() < 1.0); let kur2 = "TEFF 35000 "; let teff2 = parse_teff_from_kur(kur2).unwrap(); assert!((teff2 - 35000.0).abs() < 1.0); } #[test] fn test_parse_teff_invalid() { let kur = "NOTTEFF 123"; assert!(parse_teff_from_kur(kur).is_err()); } #[test] fn test_kurucz_depth_point() { let dp = KuruczDepthPoint { depth: 1.0e-5, temp: 10000.0, x3: 1.0e5, elec: 1.0e12, }; assert!((dp.depth - 1.0e-5).abs() < 1e-15); assert!((dp.temp - 10000.0).abs() < 1e-10); } #[test] fn test_kurucz_ifixde_depth_point() { let dp = KuruczIfixdeDepthPoint { dm: 1.0e-5, temp: 10000.0, pressure: 1.0e5, ane0: 1.0e12, a1: 0.0, a2: 0.0, a3: 0.0, vel: 0.0, rho: 1.0e-7, }; assert!((dp.dm - 1.0e-5).abs() < 1e-15); assert!((dp.temp - 10000.0).abs() < 1e-10); assert!((dp.rho - 1.0e-7).abs() < 1e-15); } #[test] fn test_read_standard_depth() { let line = " 1.0E-05 10000.0 1.0E+05 1.0E+12 0.0 0.0 0.0\n"; let cursor = std::io::Cursor::new(line.as_bytes()); let mut reader = BufReader::new(cursor); let dp = read_standard_depth(&mut reader).unwrap(); assert!((dp.depth - 1.0e-5).abs() < 1e-15); assert!((dp.temp - 10000.0).abs() < 1e-10); assert!((dp.x3 - 1.0e5).abs() < 1e-10); assert!((dp.elec - 1.0e12).abs() < 1e-10); } #[test] fn test_read_ifixde_depth() { let line = " 1.0E-05 10000.0 1.0E+05 1.0E+12 0.0 0.0 0.0 0.0 1.0E-07\n"; let cursor = std::io::Cursor::new(line.as_bytes()); let mut reader = BufReader::new(cursor); let dp = read_ifixde_depth(&mut reader).unwrap(); assert!((dp.dm - 1.0e-5).abs() < 1e-15); assert!((dp.temp - 10000.0).abs() < 1e-10); assert!((dp.pressure - 1.0e5).abs() < 1e-10); assert!((dp.rho - 1.0e-7).abs() < 1e-15); } #[test] fn test_kurucz_model() { let model = KuruczModel { nd: 3, teff: 9750.0, grav_log: 4.0, depth_points: vec![ KuruczDepthPoint { depth: 1.0e-5, temp: 10000.0, x3: 1.0e5, elec: 1.0e12, }, KuruczDepthPoint { depth: 2.0e-5, temp: 9000.0, x3: 1.2e5, elec: 8.0e11, }, KuruczDepthPoint { depth: 3.0e-5, temp: 8000.0, x3: 1.5e5, elec: 6.0e11, }, ], depth_points_ifixde: Vec::new(), intrpl: 0, is_ifixde: false, }; assert_eq!(model.nd, 3); let temps = model.temperatures(); assert_eq!(temps.len(), 3); assert!((temps[0] - 10000.0).abs() < 1e-10); } #[test] fn test_read_kurucz_header_standard() { // 模拟 Kurucz 头部行 // FORMAT: A15, 6X, F8.5 // A15: 列 1-15, 6X: 列 16-21, F8.5: 列 22-29 // 需要至少 29 个字符 let header_line = "TEFF 9750 4.00000\n"; // 0123456789012345678901234567890 // |A15 |6X |F8.5 | let cursor = std::io::Cursor::new(header_line.as_bytes()); let mut reader = BufReader::new(cursor); let (kur, grav_log) = read_kurucz_header_standard(&mut reader).unwrap(); assert!(kur.starts_with("TEFF")); assert!((grav_log - 4.0).abs() < 0.001); } #[test] fn test_read_ifixde_header() { // 模拟 IFIXDE 头部行 // FORMAT: 4X, F8.0, 9X, F8.5 // 4X: 列 1-4, F8.0: 列 5-12, 9X: 列 13-21, F8.5: 列 22-29 let header_line = " 9750. 4.00000\n"; // 0123456789012345678901234567890 // |4X |F8.0 |9X |F8.5 | let cursor = std::io::Cursor::new(header_line.as_bytes()); let mut reader = BufReader::new(cursor); let (teff, grav_log) = read_ifixde_header(&mut reader).unwrap(); assert!((teff - 9750.0).abs() < 1.0); assert!((grav_log - 4.0).abs() < 0.001); } }