SpectraRust/src/math/lemini.rs
2026-03-23 15:45:52 +08:00

554 lines
16 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//! 初始化氢线轮廓表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(&params, &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(&params, &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);
}
}