//! 读取原子能级和跃迁数据。 //! //! 重构自 TLUSTY `rdata.f`。 //! //! 功能: //! - 读取离子的能级数据(能量、统计权重、量子数等) //! - 读取连续跃迁(束缚-自由)数据 //! - 读取谱线跃迁(束缚-束缚)数据 //! - 处理各种特殊情况(ODF、ALI、碰撞数据等) use crate::tlusty::data::_UNNAMED_OSH; use crate::tlusty::state::atomic::{AtoPar, IonDat, IonFil, IonPar, LevPar, PhoSet, TabCol, TopCs, TraPar, VoiPar}; use crate::tlusty::state::config::{BasNum, InpPar}; use crate::tlusty::state::constants::{EH, H, MCORAT, MCROSS, MFIT, MLEVEL, MTRANS, MVOIGT, MXTCOL}; use crate::tlusty::state::iterat::IterControl; use crate::tlusty::state::model::ModelState; use crate::tlusty::state::odfpar::OdfData; /// 光速 (cm/s) pub const C_LIGHT: f64 = 2.997925e18; /// 1.6018e-12 erg/eV pub const EV_TO_ERG: f64 = 1.6018e-12; /// 1.9857e-16 erg/cm⁻¹ pub const CM1_TO_ERG: f64 = 1.9857e-16; // ============================================================================ // 能量转换 // ============================================================================ /// 将能量值转换为 erg。 /// /// Fortran 逻辑: /// - E = 0: 使用氢原子能级公式 /// - 0 < E < 100: eV /// - 100 < E < 1e7: cm⁻¹ /// - E > 1e7: Hz pub fn convert_energy(e: f64, zz: f64, iq: i32) -> f64 { let x = (iq * iq) as f64; if e == 0.0 { // 氢原子能级公式: E = EH * Z² / n² EH * zz * zz / x } else if e > 1e-7 && e < 100.0 { // eV 转 erg EV_TO_ERG * e } else if e > 100.0 && e < 1e7 { // cm⁻¹ 转 erg CM1_TO_ERG * e } else { // Hz 转 erg (E = h * nu) H * e } } // ============================================================================ // 氢振子强度 // ============================================================================ /// 获取氢振子强度 OSH(n1, n2)。 /// /// 数组存储为 20x20 矩阵,在 _UNNAMED_OSH 中按列优先存储。 pub fn get_osh(n1: i32, n2: i32) -> f64 { if n1 < 1 || n1 > 20 || n2 < 1 || n2 > 20 { return 0.0; } // Fortran 列优先存储: OSH(n1, n2) = _UNNAMED_OSH[(n2-1)*20 + (n1-1)] let idx = ((n2 - 1) * 20 + (n1 - 1)) as usize; _UNNAMED_OSH[idx] } // ============================================================================ // 能级数据 // ============================================================================ /// 单个能级的输入数据。 #[derive(Debug, Clone, Default)] pub struct LevelInputData { /// 电离能(需要转换) pub enion: f64, /// 统计权重 pub g: f64, /// 主量子数 pub nquant: i32, /// 能级类型 pub typlev: String, /// FWOP 标志 pub ifwop: i32, /// ODF 频率 pub frodf: f64, /// 模型能级 pub imodl: i32, } /// 能级处理结果。 #[derive(Debug, Clone, Default)] pub struct LevelData { /// 电离能 (erg) pub enion: f64, /// 统计权重 pub g: f64, /// 主量子数 pub nquant: i32, /// LTE 能级标志 pub iltlev: i32, /// 模型能级 pub imodl: i32, /// FWOP 标志 pub ifwop: i32, /// 引导能级索引 pub iguide: i32, } /// 处理单个能级数据。 pub fn process_level( input: &LevelInputData, level_idx: usize, zz: f64, iq: i32, ispodf: i32, ) -> LevelData { let mut result = LevelData::default(); // 能量转换 let e = input.enion.abs(); let e0 = convert_energy(e, zz, iq); result.enion = if input.enion >= 0.0 { e0 } else { -e0 }; // 统计权重 result.g = if input.g == 0.0 { 2.0 * (iq * iq) as f64 } else { input.g }; // 主量子数 result.nquant = if input.nquant == 0 { iq } else { input.nquant }; // LTE 标志(负量子数表示 LTE) if input.nquant < 0 { result.iltlev = 1; result.nquant = input.nquant.abs(); } // FWOP 处理 result.ifwop = input.ifwop; if ispodf == 0 && input.ifwop >= 2 { result.ifwop = 0; } // 模型能级 result.imodl = input.imodl; if input.imodl > 100 { // imodl > 100 表示引导能级 result.iguide = input.imodl - 100; result.imodl = 6; } result } // ============================================================================ // 跃迁数据 // ============================================================================ /// 连续跃迁(束缚-自由)输入数据。 #[derive(Debug, Clone, Default)] pub struct ContinuumInputData { /// 下能级索引(文件中的) pub ii: i32, /// 上能级索引(文件中的) pub jj: i32, /// 模式 pub mode: i32, /// IFANCY 参数 pub ifancy: i32, /// 碰撞标志 pub icolis: i32, /// 起始频率索引 pub ifrq0: i32, /// 结束频率索引 pub ifrq1: i32, /// 振子强度 pub osc: f64, /// C 参数 pub cparam: f64, /// 碰撞数据点数 pub ncol: i32, /// 额外频率输入 pub fr0inp: Option, /// FR0PCI 参数 pub fr0pci: Option, /// 截面参数 (S0, ALF, BET, GAM) pub cross_section: Option<[f64; 4]>, /// TOPBASE 拟合点数 pub nfit: Option, /// TOPBASE X 数组 pub xtop: Option>, /// TOPBASE C 数组 pub ctop: Option>, /// 碰撞数据 pub collision_data: Option>, } /// 碰撞数据。 #[derive(Debug, Clone, Default)] pub struct CollisionData { /// 类型 pub itype: i32, /// 温度点数 pub nctemp: i32, /// 温度数组 pub ctemp: Vec, /// 速率数组 pub colrate: Vec, } /// 谱线跃迁(束缚-束缚)输入数据。 #[derive(Debug, Clone, Default)] pub struct LineInputData { /// 下能级索引 pub ii: i32, /// 上能级索引 pub jj: i32, /// 模式 pub mode: i32, /// IFANCY 参数 pub ifancy: i32, /// 碰撞标志 pub icolis: i32, /// 起始频率索引 pub ifrq0: i32, /// 结束频率索引 pub ifrq1: i32, /// 振子强度 pub osc: f64, /// C 参数 pub cparam: f64, /// 碰撞数据点数 pub ncol: i32, /// 额外频率输入 pub fr0inp: Option, /// 轮廓参数 pub profile: Option, /// ODF 参数 pub odf: Option, /// 碰撞数据 pub collision_data: Option>, } /// 谱线轮廓数据。 #[derive(Debug, Clone, Default)] pub struct LineProfileData { /// 深度相关轮廓标志 pub lcomp: bool, /// 积分模式 pub intmod: i32, /// 频率点数 pub nf: i32, /// 最大频率偏移 pub xmax: f64, /// 标准温度 pub tstd: f64, /// Voigt 参数(如果 iprof = 1) pub voigt: Option, } /// Voigt 参数。 #[derive(Debug, Clone, Default)] pub struct VoigtParams { /// 辐射阻尼 pub gamar: f64, /// Stark 参数 1 pub stark1: f64, /// Stark 参数 2 pub stark2: f64, /// Stark 参数 3 pub stark3: f64, /// Van der Waals 宽度 pub vdwh: f64, } /// ODF 谱线数据。 #[derive(Debug, Clone, Default)] pub struct OdfLineData { /// KDO 数组 [4] pub kdo: [i32; 4], /// XDO 数组 [3] pub xdo: [f64; 3], } // ============================================================================ // RDATA 参数 // ============================================================================ /// RDATA 输入参数。 #[derive(Debug, Clone)] pub struct RdataParams<'a> { /// 离子索引 (1-based) pub ion: i32, /// 有效温度 pub teff: f64, /// 氢元素索引 pub ielh: i32, /// 氦原子索引 pub iathe: i32, /// ODF 模式 pub ispodf: i32, /// Lyman 截断频率 pub cutlym: f64, /// Balmer 截断频率 pub cutbal: f64, /// 氢轮廓模式 pub ihydpr: i32, /// 频率范围 pub frlmin: f64, pub frlmax: f64, /// IOPTAB 参数 pub ioptab: i32, // 原子数据引用 pub atopar: &'a AtoPar, pub ionpar: &'a IonPar, pub iondat: &'a IonDat, pub ionfil: &'a IonFil, } /// RDATA 输出结构体。 #[derive(Debug, Clone, Default)] pub struct RdataOutput { /// 能级数据 pub levels: Vec, /// 连续跃迁数据 pub continua: Vec, /// 谱线跃迁数据 pub lines: Vec, /// 跃迁总数 pub ntrans: i32, /// 连续跃迁数 pub ntranc: i32, /// 最后频率索引 pub nlaste: i32, /// MER 计数器 pub imer: i32, /// MER 能级索引 pub imrg: Vec, /// IMER 索引 pub iimer: Vec, /// LBPFX 标志 pub lbpfx: bool, /// HOD 计数器 pub nhod: i32, /// LASV 标志 pub lasv: bool, /// 氢轮廓初始化标志 pub ihydp0: i32, } /// 连续跃迁处理结果。 #[derive(Debug, Clone, Default)] pub struct ContinuumTransition { /// 跃迁索引 pub itr: i32, /// 下能级索引 pub ii: i32, /// 上能级索引 pub jj: i32, /// 模式 pub mode: i32, /// 频率 (Hz) pub fr0: f64, /// 振子强度 pub osc0: f64, /// 碰撞标志 pub icol: i32, /// C 参数 pub cpar: f64, /// 频率索引范围 pub ifc0: i32, pub ifc1: i32, /// 连续跃迁索引 pub ic: i32, /// IFANCY 参数 pub ifancy: i32, /// FR0PCI pub fr0pc: f64, } /// 谱线跃迁处理结果。 #[derive(Debug, Clone, Default)] pub struct LineTransition { /// 跃迁索引 pub itr: i32, /// 下能级索引 pub ii: i32, /// 上能级索引 pub jj: i32, /// 模式 pub mode: i32, /// 频率 (Hz) pub fr0: f64, /// 振子强度 pub osc0: f64, /// 碰撞标志 pub icol: i32, /// C 参数 pub cpar: f64, /// 频率索引范围 pub ifr0: i32, pub ifr1: i32, /// 轮廓类型 pub iprof: i32, /// 积分模式 pub intmod: i32, /// 深度相关轮廓 pub lcomp: bool, /// ODF 索引 pub jndodf: i32, /// 是否为谱线 pub is_line: bool, } // ============================================================================ // 原子数据文件读取 // ============================================================================ use std::fs::File; use std::io::{BufRead, BufReader}; use std::path::Path; /// 读取原子数据文件(如 h1.dat, he1.dat 等)。 /// /// # 文件格式 /// /// ```text /// ****** Levels /// ENION G NQUANT TYPLEV IFWOP FRODF IMODL /// ... /// ****** Continuum transitions /// II JJ MODE IFANCY ICOLIS IFRQ0 IFRQ1 OSC CPARAM /// ... /// ****** Line transitions /// II JJ MODE IFANCY ICOLIS IFRQ0 IFRQ1 OSC CPARAM /// LCOMP INTMOD NF XMAX TSTD /// GAMAR STARK1 STARK2 STARK3 VDWH /// ... /// ``` /// /// # 参数 /// * `path` - 文件路径 /// * `nlevs` - 期望读取的能级数 /// /// # 返回值 /// (能级数据, 连续跃迁数据, 谱线跃迁数据) pub fn read_ion_data_file>( path: P, nlevs: usize, ) -> Result<(Vec, Vec, Vec), String> { let file = File::open(&path).map_err(|e| format!("无法打开文件 {:?}: {}", path.as_ref(), e))?; let reader = BufReader::new(file); let mut lines = reader.lines().peekable(); // 跳过第一个 ****** 行 let first_line = lines.next().ok_or("文件为空")?.map_err(|e| e.to_string())?; if !first_line.contains('*') { return Err("文件格式错误:第一行应该是 ****** Levels".to_string()); } // 读取能级数据 let mut levels = Vec::with_capacity(nlevs); for _ in 0..nlevs { let line = lines.next().ok_or("能级数据不完整")?.map_err(|e| e.to_string())?; let level = parse_level_line(&line)?; levels.push(level); } // 跳到连续跃迁部分 skip_to_section_peekable(&mut lines, "Continuum")?; let mut continua = Vec::new(); loop { let line = match lines.next() { Some(Ok(l)) => l, _ => break, }; // 检查是否到达谱线部分 if line.contains("******") { if line.contains("Line") { break; } continue; } if line.trim().is_empty() { continue; } if let Ok(cont) = parse_continuum_line(&line) { // 检查是否结束(ii >= nlevs 表示结束) if cont.ii >= nlevs as i32 { break; } continua.push(cont); } } // 跳到谱线部分(如果还没到) if !lines.peek().map_or(false, |r| r.as_ref().map_or(false, |l| l.contains("Line"))) { skip_to_section_peekable(&mut lines, "Line")?; } let mut line_transitions = Vec::new(); loop { let line = match lines.next() { Some(Ok(l)) => l, _ => break, }; if line.contains("******") { continue; } if line.trim().is_empty() { continue; } // 跳过注释行(以 ! 开头) let trimmed = line.trim(); if trimmed.starts_with('!') { continue; } // 跳过续行(以 T 或 F 开头的是轮廓参数行) if trimmed.starts_with('T') || trimmed.starts_with('F') { continue; } // 跳过纯数字行(可能是额外参数) if trimmed.chars().all(|c| c.is_numeric() || c.is_whitespace() || c == '.') { // 可能是额外的数值行,跳过 if trimmed.split_whitespace().all(|s| s.parse::().is_ok()) { continue; } } if let Ok(line_data) = parse_line_transition(&line) { // 检查是否结束 if line_data.ii >= nlevs as i32 { break; } line_transitions.push(line_data); } } Ok((levels, continua, line_transitions)) } /// 跳到指定部分(peekable 版本) fn skip_to_section_peekable( lines: &mut std::iter::Peekable>, section_name: &str, ) -> Result<(), String> { loop { let line = lines.next().ok_or(format!("未找到 {} 部分", section_name))?.map_err(|e| e.to_string())?; if line.contains("******") && line.contains(section_name) { return Ok(()); } } } /// 跳到指定部分(查找 ****** Section 标记) fn skip_to_section(lines: &mut std::io::Lines, section_name: &str) -> Result<(), String> { loop { let line = lines.next().ok_or(format!("未找到 {} 部分", section_name))?.map_err(|e| e.to_string())?; if line.contains("******") && line.contains(section_name) { return Ok(()); } } } /// 解析能级行 fn parse_level_line(line: &str) -> Result { let parts: Vec<&str> = line.split_whitespace().collect(); if parts.len() < 7 { return Err(format!("能级行字段不足: {}", line)); } let mut level = LevelInputData::default(); // ENION (能量) - 处理 Fortran D 指数格式 let enion_str = parts[0].to_uppercase().replace('D', "E"); level.enion = enion_str.parse().map_err(|_| format!("无法解析能量: {}", parts[0]))?; // G (统计权重) - 同样处理 D 指数 let g_str = parts[1].to_uppercase().replace('D', "E"); level.g = g_str.parse().map_err(|_| format!("无法解析统计权重: {}", parts[1]))?; // NQUANT (主量子数) level.nquant = parts[2].parse().map_err(|_| format!("无法解析量子数: {}", parts[2]))?; // TYPLEV (能级类型,字符串,可能带引号) if parts.len() > 3 { // 处理带引号的字符串 let typlev = parts[3].trim_matches('\'').to_string(); level.typlev = typlev; } // IFWOP if parts.len() > 4 { level.ifwop = parts[4].parse().unwrap_or(0); } // FRODF if parts.len() > 5 { let frodf_str = parts[5].to_uppercase().replace('D', "E"); level.frodf = frodf_str.parse().unwrap_or(0.0); } // IMODL if parts.len() > 6 { level.imodl = parts[6].parse().unwrap_or(0); } Ok(level) } /// 解析连续跃迁行 fn parse_continuum_line(line: &str) -> Result { let parts: Vec<&str> = line.split_whitespace().collect(); if parts.len() < 9 { return Err(format!("连续跃迁行字段不足: {}", line)); } let mut cont = ContinuumInputData::default(); cont.ii = parts[0].parse().map_err(|_| format!("无法解析 ii: {}", parts[0]))?; cont.jj = parts[1].parse().map_err(|_| format!("无法解析 jj: {}", parts[1]))?; cont.mode = parts[2].parse().map_err(|_| format!("无法解析 mode: {}", parts[2]))?; cont.ifancy = parts[3].parse().map_err(|_| format!("无法解析 ifancy: {}", parts[3]))?; cont.icolis = parts[4].parse().map_err(|_| format!("无法解析 icolis: {}", parts[4]))?; cont.ifrq0 = parts[5].parse().map_err(|_| format!("无法解析 ifrq0: {}", parts[5]))?; cont.ifrq1 = parts[6].parse().map_err(|_| format!("无法解析 ifrq1: {}", parts[6]))?; cont.osc = parts[7].parse().map_err(|_| format!("无法解析 osc: {}", parts[7]))?; cont.cparam = parts[8].parse().map_err(|_| format!("无法解析 cparam: {}", parts[8]))?; if parts.len() > 9 { cont.ncol = parts[9].parse().unwrap_or(0); } Ok(cont) } /// 解析谱线跃迁行(简化版,只读取基本信息) fn parse_line_transition(line: &str) -> Result { let parts: Vec<&str> = line.split_whitespace().collect(); if parts.len() < 9 { return Err(format!("谱线跃迁行字段不足: {}", line)); } let mut line_data = LineInputData::default(); line_data.ii = parts[0].parse().map_err(|_| format!("无法解析 ii: {}", parts[0]))?; line_data.jj = parts[1].parse().map_err(|_| format!("无法解析 jj: {}", parts[1]))?; line_data.mode = parts[2].parse().map_err(|_| format!("无法解析 mode: {}", parts[2]))?; line_data.ifancy = parts[3].parse().map_err(|_| format!("无法解析 ifancy: {}", parts[3]))?; line_data.icolis = parts[4].parse().map_err(|_| format!("无法解析 icolis: {}", parts[4]))?; line_data.ifrq0 = parts[5].parse().map_err(|_| format!("无法解析 ifrq0: {}", parts[5]))?; line_data.ifrq1 = parts[6].parse().map_err(|_| format!("无法解析 ifrq1: {}", parts[6]))?; line_data.osc = parts[7].parse().map_err(|_| format!("无法解析 osc: {}", parts[7]))?; line_data.cparam = parts[8].parse().map_err(|_| format!("无法解析 cparam: {}", parts[8]))?; if parts.len() > 9 { line_data.ncol = parts[9].parse().unwrap_or(0); } Ok(line_data) } // ============================================================================ // 纯计算函数 // ============================================================================ /// 计算默认振子强度(用于未指定的连续跃迁)。 /// /// 使用氢原子近似公式。 pub fn compute_default_oscillator_strength( zz: f64, xq: f64, fr0: f64, ) -> f64 { if fr0 <= 0.0 || xq <= 0.0 { return 0.0; } let mut sig0 = 2.815e-20 * zz * zz / (fr0 * 1e-16).powi(3) / xq.powi(5); if zz > 1.9 { sig0 *= 2.0; } if zz > 2.9 { sig0 *= 1.5; } sig0 } /// 计算氢振子强度(用于谱线)。 pub fn compute_hydrogen_oscillator_strength( n1: i32, n2: i32, g: f64, ifwop_jj: i32, nquant_jj_1: i32, nlmx: i32, ) -> f64 { if n1 > 20 || n2 > 20 { // 超出 OSH 表范围,需要外推 return 0.0; } let gh = 2.0 * (n1 * n1) as f64; let mut osc = get_osh(n1, n2) * g / gh; if ifwop_jj < 0 { // 合并能级 osc = 0.0; let jj0 = nquant_jj_1; let j20 = nlmx.min(20); if j20 >= jj0 { for jtr in jj0..=j20 { osc += get_osh(n1, jtr); } } if nlmx > 20 { // 外推到 n > 20 let xii = (n1 * n1) as f64; let mut suf = 0.0; for jtr in 21..=nlmx { let xj = jtr as f64; let xjj = xj * xj; let xjtr = xj / (xjj - xii); suf += xjtr.powi(3); } let xitr = (400.0 - xii) / 20.0; osc += get_osh(n1, 20) * suf * xitr.powi(3); } } osc } // ============================================================================ // RDATA 主处理函数 // ============================================================================ /// 处理能级数据(纯计算部分)。 /// /// # 参数 /// * `params` - 输入参数 /// * `level_inputs` - 从文件读取的能级数据 /// /// # 返回值 /// 处理后的能级数据数组 pub fn process_levels_pure( params: &RdataParams, level_inputs: &[LevelInputData], ) -> Vec { let nlevs = params.iondat.nlevs[params.ion as usize - 1] as usize; let nfirst = params.ionpar.nfirst[params.ion as usize - 1]; let zz = params.ionpar.iz[params.ion as usize - 1] as f64; let mut levels = Vec::with_capacity(nlevs); let mut lbpfx = true; for (il, input) in level_inputs.iter().enumerate().take(nlevs) { let i = (nfirst as usize) + il; let iq = (i + 1 - nfirst as usize) as i32; // 相对于离子的量子数 let mut level = process_level(input, i, zz, iq, params.ispodf); // 检查 LBPFX 条件 let imodl_ok = level.imodl == 0; // 简化:假设 iifix 总是 0 lbpfx = lbpfx && imodl_ok; levels.push(level); } levels } /// 处理连续跃迁数据(纯计算部分)。 pub fn process_continua_pure( params: &RdataParams, inputs: &[ContinuumInputData], enion: &[f64], nfirst_ion: i32, nnext_ion: i32, nlevs: i32, ) -> (Vec, i32, i32, i32, bool) { let ii0 = nfirst_ion - 1; let illim = 0; // 简化 let mut continua = Vec::new(); let mut itr = 0; let mut ic = 0; let mut nhod = 0; let mut lasv = false; for input in inputs { let mut ct = ContinuumTransition::default(); // 索引转换 let (ii, jj) = if input.jj < 1000 { let jcorr = if input.ii == 1 { nlevs + 1 - input.jj } else { 0 }; (input.ii + ii0, input.jj + ii0 + jcorr) } else { // jj >= 1000 表示电离态 (input.ii + ii0, input.jj) }; ct.ii = ii; ct.jj = jj; ct.mode = input.mode; ct.ifancy = input.ifancy; ct.osc0 = input.osc; ct.icol = input.icolis; ct.cpar = input.cparam; ct.ifc0 = input.ifrq0; ct.ifc1 = input.ifrq1; itr += 1; ct.itr = itr; // 计算频率 let enion_ii = enion.get(ii as usize - 1).copied().unwrap_or(0.0); let enion_jj = enion.get(jj as usize - 1).copied().unwrap_or(0.0); let enion_nk = enion.get(nnext_ion as usize - 1).copied().unwrap_or(0.0); ct.fr0 = if let Some(fr0inp) = input.fr0inp { if fr0inp < 1e10 { C_LIGHT / fr0inp } else { fr0inp } } else { (enion_ii - enion_jj + enion_nk) / H }; // FR0PCI 处理 ct.fr0pc = if let Some(fr0pci) = input.fr0pci { if fr0pci < 1e10 { C_LIGHT / fr0pci } else { fr0pci } } else { 0.0 }; // 特殊处理氢 if params.ion == params.ielh { if input.ii == 1 && params.cutlym != 0.0 { ct.fr0pc = params.cutlym; } if input.ii == 2 && params.cutbal != 0.0 { ct.fr0pc = params.cutbal; } } ic += 1; ct.ic = ic; // 检查 LASV 标志 if input.ifancy > 49 && input.ifancy < 100 { lasv = true; } // 检查是否跳过 let should_skip = ii < illim || ct.fr0 <= params.frlmin || ct.fr0 >= params.frlmax; if should_skip { ct.mode = 0; } continua.push(ct); } (continua, itr, ic, nhod, lasv) } // ============================================================================ // 填充 AtomicData 结构体 // ============================================================================ /// 填充原子数据到 AtomicData 结构体。 /// /// 将从文件读取的能级、连续跃迁、谱线跃迁数据填充到全局原子数据结构中。 /// /// # 参数 /// * `atomic` - 原子数据结构体(可变引用) /// * `ion_idx` - 离子索引(0-based) /// * `nfirst` - 离子起始能级索引(1-based) /// * `iat` - 原子序数 /// * `iz` - 电离态 /// * `ff_ion` - 电离势 (Ry) /// * `charg2` - 电荷² /// * `levels` - 能级输入数据 /// * `continua` - 连续跃迁输入数据 /// * `lines` - 谱线跃迁输入数据 /// * `teff` - 有效温度 pub fn populate_atomic_data( atomic: &mut crate::tlusty::state::atomic::AtomicData, ion_idx: usize, nfirst: i32, iat: i32, iz: i32, ff_ion: f64, charg2: f64, levels: &[LevelInputData], continua: &[ContinuumInputData], lines: &[LineInputData], teff: f64, ) -> (i32, i32) { let nlevs = levels.len() as i32; // 填充离子参数 atomic.ionpar.ff[ion_idx] = ff_ion; atomic.ionpar.charg2[ion_idx] = charg2; atomic.ionpar.nfirst[ion_idx] = nfirst; atomic.ionpar.nlast[ion_idx] = nfirst + nlevs - 1; atomic.ionpar.nnext[ion_idx] = nfirst + nlevs; atomic.ionpar.iz[ion_idx] = iat; // 原子序数 Z // 填充离子数据索引 atomic.iondat.iati[ion_idx] = iat; atomic.iondat.izi[ion_idx] = iz; atomic.iondat.nlevs[ion_idx] = nlevs; atomic.iondat.nllim[ion_idx] = nfirst + nlevs - 1; // ZZ: 有效核电荷 = Z - iz + 1(对于类氢离子) let zz = (iat - iz + 1) as f64; // 填充能级参数 let mut ntrans = 0i32; let mut ntranc = 0i32; for (il, input) in levels.iter().enumerate() { let level_idx = (nfirst as usize) + il - 1; // 转换为 0-based // 能量转换 let e = input.enion.abs(); let e0 = convert_energy(e, zz, (il + 1) as i32); let enion_value = if input.enion >= 0.0 { e0 } else { -e0 }; atomic.levpar.enion[level_idx] = enion_value; atomic.levpar.g[level_idx] = if input.g == 0.0 { 2.0 * ((il + 1) as f64).powi(2) } else { input.g }; atomic.levpar.nquant[level_idx] = if input.nquant == 0 { (il + 1) as i32 } else { input.nquant.abs() }; atomic.levpar.iatm[level_idx] = iat; atomic.levpar.iel[level_idx] = (ion_idx + 1) as i32; // 1-based ion index atomic.levpar.indlev[level_idx] = (level_idx + 1) as i32; // LTE 标志(负量子数表示 LTE) if input.nquant < 0 { atomic.levpar.iltlev[level_idx] = 1; } // 模型能级 atomic.levpar.imodl[level_idx] = input.imodl; } // 填充连续跃迁参数 for input in continua { let itr = ntrans as usize; if itr >= MTRANS { break; } // 索引转换 let (ii, jj) = if input.jj < 1000 { (input.ii + nfirst - 1, input.jj + nfirst - 1) } else { (input.ii + nfirst - 1, input.jj) }; // 计算频率 let enion_ii = atomic.levpar.enion.get(ii as usize - 1).copied().unwrap_or(0.0); let enion_jj = if input.jj < 1000 { atomic.levpar.enion.get(jj as usize - 1).copied().unwrap_or(0.0) } else { 0.0 }; let enion_nk = 0.0; // 简化:下一个离子的基态能级 let fr0 = (enion_ii - enion_jj + enion_nk) / H; atomic.trapar.fr0[itr] = fr0; atomic.trapar.osc0[itr] = input.osc; atomic.trapar.cpar[itr] = input.cparam; atomic.trapar.ilow[itr] = ii; atomic.trapar.iup[itr] = jj; atomic.trapar.icol[itr] = input.icolis; atomic.trapar.ifc0[itr] = input.ifrq0; atomic.trapar.ifc1[itr] = input.ifrq1; // 标记为连续跃迁 atomic.trapar.itrcon[itr] = 1; ntrans += 1; ntranc += 1; } // 填充谱线跃迁参数 for input in lines { let itr = ntrans as usize; if itr >= MTRANS { break; } let ii = input.ii + nfirst - 1; let jj = input.jj + nfirst - 1; // 计算频率 let enion_ii = atomic.levpar.enion.get(ii as usize - 1).copied().unwrap_or(0.0); let enion_jj = atomic.levpar.enion.get(jj as usize - 1).copied().unwrap_or(0.0); let fr0 = (enion_jj - enion_ii) / H; atomic.trapar.fr0[itr] = fr0; atomic.trapar.osc0[itr] = input.osc; atomic.trapar.cpar[itr] = input.cparam; atomic.trapar.ilow[itr] = ii; atomic.trapar.iup[itr] = jj; atomic.trapar.icol[itr] = input.icolis; atomic.trapar.ifr0[itr] = input.ifrq0; atomic.trapar.ifr1[itr] = input.ifrq1; // 标记为谱线跃迁 atomic.trapar.itrcon[itr] = 0; ntrans += 1; } (ntrans, ntranc) } // ============================================================================ // 测试 // ============================================================================ #[cfg(test)] mod tests { use super::*; use approx::assert_relative_eq; #[test] fn test_convert_energy_zero() { // E = 0 时使用氢原子公式 let e = convert_energy(0.0, 1.0, 2); // EH * Z² / n² = EH * 1 / 4 assert_relative_eq!(e, EH / 4.0, epsilon = 1e-20); } #[test] fn test_convert_energy_ev() { // eV 转 erg let e = convert_energy(10.0, 1.0, 2); assert_relative_eq!(e, 10.0 * EV_TO_ERG, epsilon = 1e-25); } #[test] fn test_convert_energy_cm1() { // cm⁻¹ 转 erg let e = convert_energy(1000.0, 1.0, 2); assert_relative_eq!(e, 1000.0 * CM1_TO_ERG, epsilon = 1e-28); } #[test] fn test_convert_energy_hz() { // Hz 转 erg let e = convert_energy(1e10, 1.0, 2); assert_relative_eq!(e, H * 1e10, epsilon = 1e-40); } #[test] fn test_get_osh() { // OSH(1,2) = 0.4162 (氢 Lyman-alpha) let osh = get_osh(1, 2); assert_relative_eq!(osh, 0.4162, epsilon = 1e-4); } #[test] fn test_get_osh_out_of_range() { assert_eq!(get_osh(0, 1), 0.0); assert_eq!(get_osh(21, 1), 0.0); assert_eq!(get_osh(1, 21), 0.0); } #[test] fn test_compute_default_oscillator_strength() { let osc = compute_default_oscillator_strength(1.0, 2.0, 1e15); assert!(osc > 0.0); } #[test] fn test_process_level_basic() { let input = LevelInputData { enion: 10.0, // eV g: 4.0, nquant: 2, typlev: "test".to_string(), ifwop: 0, frodf: 0.0, imodl: 0, }; let level = process_level(&input, 0, 1.0, 2, 0); assert_relative_eq!(level.enion, 10.0 * EV_TO_ERG, epsilon = 1e-25); assert_eq!(level.g, 4.0); assert_eq!(level.nquant, 2); assert_eq!(level.iltlev, 0); } #[test] fn test_process_level_negative_quantum() { let input = LevelInputData { enion: 10.0, g: 4.0, nquant: -3, // 负值表示 LTE typlev: "test".to_string(), ifwop: 0, frodf: 0.0, imodl: 0, }; let level = process_level(&input, 0, 1.0, 3, 0); assert_eq!(level.nquant, 3); assert_eq!(level.iltlev, 1); } #[test] fn test_process_level_zero_g() { let input = LevelInputData { enion: 10.0, g: 0.0, // 应使用 2*n² nquant: 3, typlev: "test".to_string(), ifwop: 0, frodf: 0.0, imodl: 0, }; let level = process_level(&input, 0, 1.0, 3, 0); assert_eq!(level.g, 2.0 * 9.0); // 2 * n² } }