554 lines
16 KiB
Rust
554 lines
16 KiB
Rust
//! 初始化氢线轮廓表(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<LineData>,
|
||
}
|
||
|
||
/// 单条谱线的完整数据。
|
||
#[derive(Debug, Clone)]
|
||
pub struct LineData {
|
||
/// 谱线索引
|
||
pub iline: usize,
|
||
/// 波长点数
|
||
pub nwl: i32,
|
||
/// 温度点数
|
||
pub nt: i32,
|
||
/// 电子密度点数
|
||
pub ne: i32,
|
||
/// 波长数组 (对数空间)
|
||
pub wlh: Vec<f64>,
|
||
/// 波长数组 (线性空间)
|
||
pub wlhyd: Vec<f64>,
|
||
/// 电子密度网格 (对数空间)
|
||
pub xnelem: Vec<f64>,
|
||
/// 温度网格 (对数空间)
|
||
pub xtlem: Vec<f64>,
|
||
/// 轮廓数据 PRFHYD
|
||
pub prfhyd: Vec<f64>,
|
||
/// 渐近系数 XK0
|
||
pub xk0: f64,
|
||
}
|
||
|
||
// ============================================================================
|
||
// 核心计算函数
|
||
// ============================================================================
|
||
|
||
/// 解析谱线块头部信息。
|
||
///
|
||
/// Fortran 格式: 自由格式读取 10 个值
|
||
fn parse_line_block_header(line: &str) -> Option<LineBlockData> {
|
||
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<f64>,
|
||
}
|
||
|
||
/// 单个表格的数据。
|
||
#[derive(Debug, Clone)]
|
||
pub struct Table {
|
||
/// 谱线块数量
|
||
pub nlly: i32,
|
||
/// 谱线块数据
|
||
pub blocks: Vec<TableBlock>,
|
||
}
|
||
|
||
/// 完整的 Lemke/Tremblay 表格数据。
|
||
#[derive(Debug, Clone, Default)]
|
||
pub struct LemkeTableData {
|
||
/// 表格数量
|
||
pub ntab: i32,
|
||
/// 表格数据
|
||
pub tables: Vec<Table>,
|
||
}
|
||
|
||
// ============================================================================
|
||
// 文件读取函数
|
||
// ============================================================================
|
||
|
||
/// 读取 Lemke/Tremblay 表格文件。
|
||
///
|
||
/// # 参数
|
||
/// - `file_path`: 文件路径
|
||
///
|
||
/// # 返回
|
||
/// 表格数据
|
||
pub fn read_lemke_table(file_path: &str) -> Result<LemkeTableData> {
|
||
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);
|
||
}
|
||
}
|