1163 lines
32 KiB
Rust
1163 lines
32 KiB
Rust
//! 读取原子能级和跃迁数据。
|
||
//!
|
||
//! 重构自 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<f64>,
|
||
/// FR0PCI 参数
|
||
pub fr0pci: Option<f64>,
|
||
/// 截面参数 (S0, ALF, BET, GAM)
|
||
pub cross_section: Option<[f64; 4]>,
|
||
/// TOPBASE 拟合点数
|
||
pub nfit: Option<i32>,
|
||
/// TOPBASE X 数组
|
||
pub xtop: Option<Vec<f64>>,
|
||
/// TOPBASE C 数组
|
||
pub ctop: Option<Vec<f64>>,
|
||
/// 碰撞数据
|
||
pub collision_data: Option<Vec<CollisionData>>,
|
||
}
|
||
|
||
/// 碰撞数据。
|
||
#[derive(Debug, Clone, Default)]
|
||
pub struct CollisionData {
|
||
/// 类型
|
||
pub itype: i32,
|
||
/// 温度点数
|
||
pub nctemp: i32,
|
||
/// 温度数组
|
||
pub ctemp: Vec<f64>,
|
||
/// 速率数组
|
||
pub colrate: Vec<f64>,
|
||
}
|
||
|
||
/// 谱线跃迁(束缚-束缚)输入数据。
|
||
#[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<f64>,
|
||
/// 轮廓参数
|
||
pub profile: Option<LineProfileData>,
|
||
/// ODF 参数
|
||
pub odf: Option<OdfLineData>,
|
||
/// 碰撞数据
|
||
pub collision_data: Option<Vec<CollisionData>>,
|
||
}
|
||
|
||
/// 谱线轮廓数据。
|
||
#[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<VoigtParams>,
|
||
}
|
||
|
||
/// 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<LevelData>,
|
||
/// 连续跃迁数据
|
||
pub continua: Vec<ContinuumTransition>,
|
||
/// 谱线跃迁数据
|
||
pub lines: Vec<LineTransition>,
|
||
/// 跃迁总数
|
||
pub ntrans: i32,
|
||
/// 连续跃迁数
|
||
pub ntranc: i32,
|
||
/// 最后频率索引
|
||
pub nlaste: i32,
|
||
/// MER 计数器
|
||
pub imer: i32,
|
||
/// MER 能级索引
|
||
pub imrg: Vec<i32>,
|
||
/// IMER 索引
|
||
pub iimer: Vec<i32>,
|
||
/// 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<P: AsRef<Path>>(
|
||
path: P,
|
||
nlevs: usize,
|
||
) -> Result<(Vec<LevelInputData>, Vec<ContinuumInputData>, Vec<LineInputData>), 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::<f64>().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<T: BufRead>(
|
||
lines: &mut std::iter::Peekable<std::io::Lines<T>>,
|
||
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<T: BufRead>(lines: &mut std::io::Lines<T>, 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<LevelInputData, String> {
|
||
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<ContinuumInputData, String> {
|
||
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<LineInputData, String> {
|
||
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<LevelData> {
|
||
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<ContinuumTransition>, 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²
|
||
}
|
||
}
|