//! He II line profile data initialization. //! //! Translated from SYNSPEC54.FOR subroutine HE2INI (line 7535). //! //! Initializes necessary arrays for evaluating the He II line //! absorption profiles using data calculated by Schoening and Butler. #![allow(clippy::never_loop)] use std::fs::File; use std::io::{BufRead, BufReader}; /// Constants for He II profile arrays pub const NLINE_HE2: usize = 19; pub const NWL_HE2_MAX: usize = 36; pub const NT_HE2: usize = 6; pub const NE_HE2: usize = 11; /// He II line profile table data #[derive(Debug, Clone)] pub struct He2ProfileTable { /// Lower level index pub il: usize, /// Upper level index pub iu: usize, /// Central wavelength pub wl0: f64, /// Number of wavelength points pub nwl: usize, /// Log10 wavelength displacements [NWL_HE2_MAX] pub wl: [f64; NWL_HE2_MAX], /// Log10 temperature grid [NT_HE2] pub xt: [f64; NT_HE2], /// Log10 electron density grid [NE_HE2] pub xne: [f64; NE_HE2], /// Profile values [NWL_HE2_MAX x NT_HE2 x NE_HE2] pub prf: [[[f64; NE_HE2]; NT_HE2]; NWL_HE2_MAX], /// Asymptotic profile coefficient pub xk: f64, } impl Default for He2ProfileTable { fn default() -> Self { Self { il: 0, iu: 0, wl0: 0.0, nwl: 0, wl: [0.0; NWL_HE2_MAX], xt: [0.0; NT_HE2], xne: [0.0; NE_HE2], prf: [[[0.0; NE_HE2]; NT_HE2]; NWL_HE2_MAX], xk: 0.0, } } } /// He II line initialization result #[derive(Debug, Clone)] pub struct He2InitResult { /// Profile tables for each line pub tables: Vec, /// Number of wavelength points per line [NLINE_HE2] pub nwlhe2: [usize; NLINE_HE2], /// Lower level indices [NLINE_HE2] pub ilhe2: [usize; NLINE_HE2], /// Upper level indices [NLINE_HE2] pub iuhe2: [usize; NLINE_HE2], } impl Default for He2InitResult { fn default() -> Self { Self { tables: Vec::new(), nwlhe2: [0; NLINE_HE2], ilhe2: [0; NLINE_HE2], iuhe2: [0; NLINE_HE2], } } } /// Parameters for HE2INI pub struct He2iniParams { /// Path to data directory pub data_dir: String, /// Model depth points pub nd: usize, /// Temperature array [nd] pub temp: Vec, /// Electron density array [nd] pub elec: Vec, /// Turbulent velocity array [nd] pub vturb: Vec, } /// Initialize He II line profile data. /// /// # Arguments /// * `params` - Initialization parameters /// /// # Returns /// He II line initialization result with profile tables pub fn he2ini(params: &He2iniParams) -> std::io::Result { let filename = format!("{}/he2prf.dat", params.data_dir); let file = File::open(&filename)?; let reader = BufReader::new(file); let mut lines = reader.lines(); let mut result = He2InitResult::default(); for iline in 0..NLINE_HE2 { // Read line indices: FORMAT(//14X,I2,9X,I2/) let header = read_next_nonblank(&mut lines)?; let (il, iu) = parse_he2_header(&header)?; result.ilhe2[iline] = il; result.iuhe2[iline] = iu; // Compute central wavelength let wl00 = if il <= 2 { 227.838 } else { 227.7776 }; let wl0 = wl00 / (1.0 / (il as f64).powi(2) - 1.0 / (iu as f64).powi(2)); let mut table = He2ProfileTable { il, iu, wl0, ..Default::default() }; // Read wavelength points let wl_line = read_next_line(&mut lines)?; let wl_parts = parse_he2_data(&wl_line)?; let nwl = wl_parts[0] as usize; table.nwl = nwl; result.nwlhe2[iline] = nwl; for i in 0..nwl.min(NWL_HE2_MAX) { table.wl[i] = if wl_parts[i + 1] < 1.0e-4 { (1.0e-4_f64).log10() } else { wl_parts[i + 1].log10() }; } // Read temperature points: FORMAT(2X,I4,F10.3,5F12.3) let xt_line = read_next_line(&mut lines)?; let xt_parts = parse_he2_data(&xt_line)?; let nt = xt_parts[0] as usize; for i in 0..nt.min(NT_HE2) { table.xt[i] = xt_parts[i + 1]; } // Read electron density points: FORMAT(2X,I4,F10.2,5F12.2/4X,5F12.2) let xne_line = read_next_line(&mut lines)?; let xne_parts = parse_he2_data(&xne_line)?; let ne = xne_parts[0] as usize; for i in 0..ne.min(NE_HE2) { table.xne[i] = xne_parts[i + 1]; } // Skip blank line lines.next(); // Read profile data: FORMAT(10F8.3) for ie in 0..ne.min(NE_HE2) { for _it in 0..nt.min(NT_HE2) { lines.next(); // Skip blank line let prf_line = read_next_line(&mut lines)?; let prf_parts = parse_he2_data(&prf_line)?; for iwl in 0..nwl.min(NWL_HE2_MAX) { if iwl < prf_parts.len() { table.prf[iwl][_it][ie] = prf_parts[iwl]; } } } } // Compute asymptotic profile coefficient if nwl > 0 && ne > 0 { let xclog = table.prf[nwl - 1][0][0] + 2.5 * table.wl[nwl - 1] + 31.831 - table.xne[0] - 2.0 * wl0.log10(); let xklog = 0.6666667 * (xclog - 0.176); table.xk = (xklog * std::f64::consts::LN_10).exp(); } result.tables.push(table); } Ok(result) } /// Read next non-empty line fn read_next_line(lines: &mut impl Iterator>) -> std::io::Result { loop { match lines.next() { Some(Ok(line)) => return Ok(line), Some(Err(e)) => return Err(e), None => return Err(std::io::Error::new( std::io::ErrorKind::UnexpectedEof, "Unexpected end of file", )), } } } /// Read next non-blank line (skip empty lines) fn read_next_nonblank(lines: &mut impl Iterator>) -> std::io::Result { loop { let line = read_next_line(lines)?; if !line.trim().is_empty() { return Ok(line); } } } /// Parse He II header line: FORMAT(//14X,I2,9X,I2/) fn parse_he2_header(line: &str) -> std::io::Result<(usize, usize)> { let parts: Vec<&str> = line.split_whitespace().collect(); if parts.len() < 2 { return Err(std::io::Error::new( std::io::ErrorKind::InvalidData, format!("Invalid He II header: {}", line), )); } let il = parts[0].parse::().map_err(|e| { std::io::Error::new(std::io::ErrorKind::InvalidData, format!("IL: {}", e)) })?; let iu = parts[1].parse::().map_err(|e| { std::io::Error::new(std::io::ErrorKind::InvalidData, format!("IU: {}", e)) })?; Ok((il, iu)) } /// Parse He II data line (free format) fn parse_he2_data(line: &str) -> std::io::Result> { let values: Vec = line .split_whitespace() .filter_map(|s| s.parse::().ok()) .collect(); Ok(values) } #[cfg(test)] mod tests { use super::*; #[test] fn test_he2ini_default() { let result = He2InitResult::default(); assert_eq!(result.nwlhe2.len(), NLINE_HE2); assert_eq!(result.ilhe2.len(), NLINE_HE2); assert!(result.tables.is_empty()); } #[test] fn test_he2_profile_table_default() { let table = He2ProfileTable::default(); assert_eq!(table.nwl, 0); assert_eq!(table.il, 0); assert_eq!(table.iu, 0); } #[test] fn test_parse_he2_header() { let line = " 1 2"; let result = parse_he2_header(line); assert!(result.is_ok()); let (il, iu) = result.unwrap(); assert_eq!(il, 1); assert_eq!(iu, 2); } #[test] fn test_parse_he2_data() { let line = " 19 0.123 0.456 0.789"; let result = parse_he2_data(line); assert!(result.is_ok()); let values = result.unwrap(); assert_eq!(values.len(), 4); assert!((values[0] - 19.0).abs() < 1e-10); } }