SpectraRust/src/tlusty/io/levcd.rs
2026-03-25 13:31:23 +08:00

694 lines
21 KiB
Rust

//! 超级能级能量和统计权重计算。
//!
//! 重构自 TLUSTY `levcd.f`。
//!
//! 从 Kurucz CD-ROM 文件 (gf*.gam) 读取原子数据,
//! 计算超级能级的平均能量和统计权重。
//!
//! 使用 Eissner-Seaton 公式设置超级能级之间的碰撞强度,
//! 假设 Gamma(T)=0.05, T=Teff。
use super::{FortranReader, IoError, Result};
use crate::tlusty::math::indexx as indexx_func;
use crate::tlusty::math::quit as quit_func;
use crate::tlusty::math::wn as wn_func;
use crate::tlusty::state::atomic::{AtomicData, IonPar, LevPar};
use crate::tlusty::state::constants::*;
use crate::tlusty::state::model::ModPar;
use crate::tlusty::state::odfpar::LevCom;
use std::fs::File;
use std::io::{BufRead, BufReader, BufWriter, Write};
// ============================================================================
// 常量参数
// ============================================================================
/// 玻尔兹曼常数转换因子 (用于能量单位转换)
const BOLCM: f64 = 1.0e8 / HK / CAS;
/// 相关参数
const CCOR: f64 = 0.09;
/// 1/6
const SIXTH: f64 = UN / 6.0;
/// Gamma(T) 值
const GES: f64 = 0.05;
/// Fe 离子的基准能量 (cm⁻¹)
const E0FE: [f64; 10] = [
63480.0, 130563.0, 247220.0, 442000.0, 605000.0, 799000.0, 1008000.0, 1218380.0, 1884000.0,
2114000.0,
];
/// Ni 离子的基准能量 (cm⁻¹)
const E0NI: [f64; 10] = [
61590.0, 146560.0, 283700.0, 443000.0, 613500.0, 871000.0, 1070000.0, 1310000.0, 1560000.0,
1812000.0,
];
/// Cr 离子的基准能量 (cm⁻¹)
const E0CR: [f64; 10] = [
54576.0, 132966.0, 249700.0, 396500.0, 560200.0, 731020.0, 1291900.0, 1490000.0, 1688000.0,
1971000.0,
];
// ============================================================================
// 碰撞强度 COMMON /COLKUR/
// ============================================================================
/// 碰撞强度和 Kurucz 数据。
/// 对应 COMMON /COLKUR/
#[derive(Debug, Clone)]
pub struct ColKur {
/// 碰撞强度矩阵 (100 x 100)
pub omes: Vec<Vec<f64>>,
/// Kurucz 能级能量
pub eku: Vec<f64>,
/// Kurucz 能级统计权重
pub gku: Vec<f64>,
/// GST 常数
pub gst: f64,
/// Kurucz 能级到超级能级的映射
pub kku: Vec<i32>,
}
impl Default for ColKur {
fn default() -> Self {
Self {
omes: vec![vec![0.0; 100]; 100],
eku: vec![0.0; 15000],
gku: vec![0.0; 15000],
gst: 0.0,
kku: vec![0; 15000],
}
}
}
// ============================================================================
// 输入参数
// ============================================================================
/// LEVCD 输入参数。
pub struct LevcdParams<'a> {
/// 离子索引 (1-based)
pub ion: usize,
/// 观测标志 (0=标准, 1=使用观测能级, 2=使用所有能级)
pub iobs: i32,
/// 有效温度 (K)
pub teff: f64,
/// β 引力因子
pub bergfc: f64,
/// 模型参数
pub modpar: &'a mut ModPar,
/// 离子参数
pub ionpar: &'a IonPar,
/// 能级参数
pub levpar: &'a LevPar,
/// 深度点数
pub nd: usize,
/// 占据概率写入选项 (IFWOP)
pub ifwop: &'a [i32],
/// Kurucz 文件路径
pub fiodf1: &'a str,
}
// ============================================================================
// Kurucz 文件格式读取
// ============================================================================
/// Kurucz 文件头部信息
struct KuruczHeader {
/// 链接数
nlinku: i32,
/// 偶宇称能级数
keve: i32,
/// 奇宇称能级数
kodd: i32,
}
/// Kurucz 能级数据
struct KuruczLevel {
/// 角动量量子数 J
yj: f64,
/// 能量 (cm⁻¹)
e: f64,
/// 自动电离宽度
ar: f64,
/// Stark 宽度参数
sr: f64,
/// 辐射宽度
wr: f64,
}
/// 读取 Kurucz 文件头部
///
/// FORMAT 170: I7, 13X, I6, 12X, I6
fn read_kurucz_header<R: BufRead>(reader: &mut R) -> Result<KuruczHeader> {
let mut line = String::new();
reader.read_line(&mut line)?;
if line.len() < 44 {
return Err(IoError::ParseError(format!(
"Kurucz header line too short: {}",
line.len()
)));
}
// I7: 列 1-7
let nlinku: i32 = line[0..7].trim().parse().map_err(|e| {
IoError::ParseError(format!("Failed to parse NLINKU: {} in '{}'", e, line))
})?;
// I6: 列 21-26 (跳过 13 个字符)
let keve: i32 = line[20..26].trim().parse().map_err(|e| {
IoError::ParseError(format!("Failed to parse KEVE: {} in '{}'", e, line))
})?;
// I6: 列 39-44 (跳过 12 个字符)
let kodd: i32 = line[38..44].trim().parse().map_err(|e| {
IoError::ParseError(format!("Failed to parse KODD: {} in '{}'", e, line))
})?;
Ok(KuruczHeader {
nlinku,
keve,
kodd,
})
}
/// 读取 Kurucz 能级数据
///
/// FORMAT 171: 8X, F4.1, 4X, F13.3, 18X, 3E9.2
fn read_kurucz_level<R: BufRead>(reader: &mut R) -> Result<KuruczLevel> {
let mut line = String::new();
reader.read_line(&mut line)?;
if line.len() < 70 {
// 最小长度检查
// 如果行较短但包含有效数据,尝试解析
if line.trim().is_empty() {
return Err(IoError::UnexpectedEof);
}
}
// F4.1: 列 9-12 (跳过 8 个字符) - YJ
let yj: f64 = if line.len() >= 12 {
line[8..12].trim().parse().unwrap_or(0.0)
} else {
0.0
};
// F13.3: 列 17-29 (跳过 4 个字符) - E
let e: f64 = if line.len() >= 29 {
line[16..29].trim().parse().unwrap_or(0.0)
} else {
0.0
};
// 3E9.2: 列 48-74 (跳过 18 个字符) - AR, SR, WR
let ar: f64 = if line.len() >= 56 {
line[47..56].trim().parse().unwrap_or(0.0)
} else {
0.0
};
let sr: f64 = if line.len() >= 65 {
line[56..65].trim().parse().unwrap_or(0.0)
} else {
0.0
};
let wr: f64 = if line.len() >= 74 {
line[65..74].trim().parse().unwrap_or(0.0)
} else {
0.0
};
Ok(KuruczLevel { yj, e, ar, sr, wr })
}
// ============================================================================
// 主函数
// ============================================================================
/// 执行 LEVCD 计算。
///
/// # 参数
/// - `params` - 输入参数
/// - `levcom` - 能级 ODF 数据 (可变)
/// - `colkur` - 碰撞强度数据 (可变)
/// - `wop` - 占据概率数组 (可变)
///
/// # Fortran 原始代码
/// ```fortran
/// SUBROUTINE LEVCD(ION,IOBS)
/// ```
pub fn levcd(
params: &mut LevcdParams,
levcom: &mut LevCom,
colkur: &mut ColKur,
wop: &mut [Vec<f64>],
) -> Result<()> {
let ion = params.ion;
let iobs = if params.iobs != 1 && params.iobs != 2 {
0
} else {
params.iobs
};
let nd = params.nd;
let nevku = levcom.nevku[ion] as usize;
let nodku = levcom.nodku[ion] as usize;
// 临时数组
let mut gwe = vec![vec![vec![0.0f64; 2]; MLEVEL]; MDEPTH];
let mut gwb = vec![vec![vec![0.0f64; 2]; MLEVEL]; MDEPTH];
let mut aa = vec![0.0f64; MDEPTH];
// 初始化
for i in 0..nevku {
levcom.ymku[i][0] = 0.0;
levcom.emku[i][0] = 0.0;
for id in 0..nd {
gwe[id][i][0] = 0.0;
gwb[id][i][0] = 0.0;
}
}
for i in 0..nodku {
levcom.ymku[i][1] = 0.0;
levcom.emku[i][1] = 0.0;
for id in 0..nd {
gwe[id][i][1] = 0.0;
gwb[id][i][1] = 0.0;
}
}
let nevod = nevku + nodku;
if nevod > 100 {
quit_func(
"Too many superlevels in a single Fe ion",
nevku as i32,
nodku as i32,
);
}
// 初始化碰撞矩阵
for i in 0..nevod {
for j in 0..nevod {
colkur.omes[i][j] = 0.0;
}
}
// 检查占据概率计算选项
let iw_sup = params.ifwop[params.ionpar.nfirst[ion] as usize];
if iw_sup >= 2 {
// 预计算温度相关量
for id in 0..nd {
params.modpar.temp1[id] = UN / params.modpar.temp[id];
aa[id] = CCOR * (params.modpar.elec[id].ln() * SIXTH).exp() / params.modpar.temp[id].sqrt();
}
let zz = params.ionpar.iz[ion] as f64;
if params.ionpar.iz[ion] > 10 {
quit_func(
"Too high Fe, Ni or Cr ion: ion,iz",
ion as i32,
params.ionpar.iz[ion],
);
}
let iat = params.levpar.iatm[params.ionpar.nfirst[ion] as usize] as usize;
let mut e0 = E0FE[params.ionpar.iz[ion] as usize - 1];
if params.levpar.iel[params.ionpar.nfirst[ion] as usize] >= 0 {
// 检查原子类型
let numat = params.levpar.iel[params.ionpar.nfirst[ion] as usize]; // 简化处理
if numat == 28 {
e0 = E0NI[params.ionpar.iz[ion] as usize - 1];
} else if numat == 24 {
e0 = E0CR[params.ionpar.iz[ion] as usize - 1];
}
}
// 打开 Kurucz 文件
let file = File::open(params.fiodf1)?;
let mut reader = BufReader::new(file);
// 读取头部
let header = read_kurucz_header(&mut reader)?;
let nlinku = header.nlinku;
let keve = header.keve as usize;
let kodd = header.kodd as usize;
levcom.nlinku = nlinku;
levcom.keve = keve as i32;
levcom.kodd = kodd as i32;
if keve + kodd > 15000 {
quit_func(
"Too many levels in Kurucz file",
keve as i32,
kodd as i32,
);
}
// 读取偶宇称能级
for k in 0..keve {
let level = read_kurucz_level(&mut reader)?;
let yj = level.yj;
let mut e = level.e;
let ar = level.ar;
let sr = level.sr;
let wr = level.wr;
let gev = TWO * yj + UN;
if e < 0.0 {
e = -e;
if iobs == 0 {
continue;
}
}
// 确定超级能级索引
let mut ksl = 0;
if e <= levcom.xev[0][ion] {
ksl = 1;
}
for i in 1..nevku {
if e <= levcom.xev[i][ion] && e > levcom.xev[i - 1][ion] {
ksl = (i + 1) as i32;
}
}
if ksl == 0 {
// WRITE(10,*) 'Error with even levels', E, YJ
eprintln!("Error with even levels: E={}, YJ={}", e, yj);
}
colkur.kku[k] = ksl;
colkur.gku[k] = gev;
colkur.eku[k] = e;
levcom.ymku[(ksl - 1) as usize][0] += gev;
levcom.emku[(ksl - 1) as usize][0] += gev * e;
if iw_sup == 2 {
let ebcm = e / BOLCM;
for id in 0..nd {
let gwx = gev * (-ebcm * params.modpar.temp1[id]).exp();
gwb[id][(ksl - 1) as usize][0] += gwx;
gwe[id][(ksl - 1) as usize][0] += gwx * e;
}
} else if iw_sup == 3 {
let ebcm = e / BOLCM;
if e < e0 {
let xn = (e0 / (e0 - e)).sqrt();
for id in 0..nd {
let wid = wn_func(xn, aa[id], params.modpar.elec[id], zz, params.bergfc);
let gwx = gev * wid * (-ebcm * params.modpar.temp1[id]).exp();
gwb[id][(ksl - 1) as usize][0] += gwx;
gwe[id][(ksl - 1) as usize][0] += gwx * e;
}
} else {
for id in 0..nd {
let wid = UN;
let gwx = gev * wid * (-ebcm * params.modpar.temp1[id]).exp();
gwb[id][(ksl - 1) as usize][0] += gwx;
gwe[id][(ksl - 1) as usize][0] += gwx * e;
}
}
}
// 存储能级数据
if k < levcom.eev.len() {
levcom.eev[k] = e;
levcom.aev[k] = ar;
levcom.sev[k] = sr;
levcom.wev[k] = wr;
levcom.ksev[k] = ksl;
}
}
// 检查偶宇称超级能级
for i in 0..nevku {
if levcom.ymku[i][0] == 0.0 {
quit_func("No levels in even superlevel", (i + 1) as i32, (i + 1) as i32);
}
levcom.emku[i][0] /= levcom.ymku[i][0];
}
// 读取奇宇称能级
for k in 0..kodd {
let level = read_kurucz_level(&mut reader)?;
let yj = level.yj;
let mut e = level.e;
let ar = level.ar;
let sr = level.sr;
let wr = level.wr;
let god = TWO * yj + UN;
if e < 0.0 {
e = -e;
if iobs == 0 {
continue;
}
}
// 确定超级能级索引
let mut ksl = 0;
if e <= levcom.xod[0][ion] {
ksl = 1;
}
for i in 1..nodku {
if e <= levcom.xod[i][ion] && e > levcom.xod[i - 1][ion] {
ksl = (i + 1) as i32;
}
}
if ksl == 0 {
eprintln!("Error with odd levels: E={}, YJ={}", e, yj);
}
let kku_idx = k + keve;
colkur.kku[kku_idx] = ksl + nevku as i32;
colkur.gku[kku_idx] = god;
colkur.eku[kku_idx] = e;
levcom.ymku[(ksl - 1) as usize][1] += god;
levcom.emku[(ksl - 1) as usize][1] += god * e;
if iw_sup == 2 {
let ebcm = e / BOLCM;
for id in 0..nd {
let gwx = god * (-ebcm * params.modpar.temp1[id]).exp();
gwb[id][(ksl - 1) as usize][1] += gwx;
gwe[id][(ksl - 1) as usize][1] += gwx * e;
}
} else if iw_sup == 3 {
let ebcm = e / BOLCM;
if e < e0 {
let xn = (e0 / (e0 - e)).sqrt();
for id in 0..nd {
let wid = wn_func(xn, aa[id], params.modpar.elec[id], zz, params.bergfc);
let gwx = god * wid * (-ebcm * params.modpar.temp1[id]).exp();
gwb[id][(ksl - 1) as usize][1] += gwx;
gwe[id][(ksl - 1) as usize][1] += gwx * e;
}
} else {
for id in 0..nd {
let wid = UN;
let gwx = god * wid * (-ebcm * params.modpar.temp1[id]).exp();
gwb[id][(ksl - 1) as usize][1] += gwx;
gwe[id][(ksl - 1) as usize][1] += gwx * e;
}
}
}
// 存储能级数据
if k < levcom.eod.len() {
levcom.eod[k] = e;
levcom.aod[k] = ar;
levcom.sod[k] = sr;
levcom.wod[k] = wr;
levcom.ksod[k] = ksl;
}
}
// 检查奇宇称超级能级
for i in 0..nodku {
if levcom.ymku[i][1] == 0.0 {
quit_func("No levels in odd superlevel", (i + 1) as i32, (i + 1) as i32);
}
levcom.emku[i][1] /= levcom.ymku[i][1];
}
// 计算碰撞强度
colkur.gst = 8.63e-6 * GES / params.teff.sqrt();
let tk0 = UN / BOLCM / params.teff;
for i in 0..(keve + kodd - 1) {
let ki = colkur.kku[i] as usize;
for j in (i + 1)..(keve + kodd) {
let kj = colkur.kku[j] as usize;
let u0 = (colkur.eku[i] - colkur.eku[j]).abs() * tk0;
colkur.omes[ki][kj] += colkur.gst * (-u0).exp();
colkur.omes[kj][ki] = colkur.omes[ki][kj];
}
}
// 排序超级能级能量
let nlevku = nevku + nodku;
levcom.nlevku = nlevku as i32;
// 复制能量到 EU 数组
for i in 0..nevku {
levcom.eu[i] = levcom.emku[i][0];
}
for i in 0..nodku {
levcom.eu[nevku + i] = levcom.emku[i][1];
}
// 排序
let jen = indexx_func(&levcom.eu[0..nlevku]);
for i in 0..nlevku {
levcom.jen[i] = jen[i] as i32;
}
// 计算超级能级的广义占据概率
for i in 0..nlevku {
let ii = params.ionpar.nfirst[ion] as usize + i;
let jj = levcom.jen[i] as usize;
let mut jk = 1;
let mut jj_adj = jj;
if jj >= nevku {
jj_adj = jj - nevku;
jk = 2;
}
for id in 0..nd {
if gwb[id][jj_adj][jk - 1] != 0.0 {
let esup = gwe[id][jj_adj][jk - 1] / gwb[id][jj_adj][jk - 1];
let wsup = (esup / BOLCM * params.modpar.temp1[id]).exp() / levcom.ymku[jj_adj][jk - 1];
wop[ii][id] = wsup * gwb[id][jj_adj][jk - 1];
}
}
}
}
Ok(())
}
// ============================================================================
// 测试
// ============================================================================
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_constants() {
assert!(BOLCM > 0.0);
assert!(CCOR > 0.0);
assert!(GES > 0.0);
}
#[test]
fn test_e0_arrays() {
// Fe 离子能量应该递增
for i in 1..E0FE.len() {
assert!(E0FE[i] > E0FE[i - 1], "E0FE should be increasing");
}
// Ni 离子能量应该递增
for i in 1..E0NI.len() {
assert!(E0NI[i] > E0NI[i - 1], "E0NI should be increasing");
}
// Cr 离子能量应该递增
for i in 1..E0CR.len() {
assert!(E0CR[i] > E0CR[i - 1], "E0CR should be increasing");
}
}
#[test]
fn test_colkur_default() {
let colkur = ColKur::default();
assert_eq!(colkur.omes.len(), 100);
assert_eq!(colkur.omes[0].len(), 100);
assert_eq!(colkur.eku.len(), 15000);
assert_eq!(colkur.gku.len(), 15000);
assert_eq!(colkur.kku.len(), 15000);
}
#[test]
fn test_read_kurucz_header() {
// 模拟 Kurucz 文件头部行
// FORMAT: I7, 13X, I6, 12X, I6
// 列 1-7: NLINKU (I7)
// 列 8-20: 跳过 (13X)
// 列 21-26: KEVE (I6)
// 列 27-38: 跳过 (12X)
// 列 39-44: KODD (I6)
let header_line = format!(
"{:7}{:13}{:6}{:12}{:6}\n",
"123", // NLINKU at 1-7
"", // 13X at 8-20
"456", // KEVE at 21-26
"", // 12X at 27-38
"789" // KODD at 39-44
);
println!("Header line length: {}", header_line.len());
println!("Header line: {:?}", header_line);
let cursor = std::io::Cursor::new(header_line.as_bytes());
let mut reader = BufReader::new(cursor);
let header = read_kurucz_header(&mut reader).unwrap();
assert_eq!(header.nlinku, 123);
assert_eq!(header.keve, 456);
assert_eq!(header.kodd, 789);
}
#[test]
fn test_read_kurucz_level() {
// 模拟 Kurucz 能级数据行
// FORMAT: 8X, F4.1, 4X, F13.3, 18X, 3E9.2
// 列 1-8: 跳过 (8X)
// 列 9-12: YJ (F4.1)
// 列 13-16: 跳过 (4X)
// 列 17-29: E (F13.3)
// 列 30-47: 跳过 (18X)
// 列 48-56: AR (E9.2)
// 列 57-65: SR (E9.2)
// 列 66-74: WR (E9.2)
let level_line = format!(
"{:8}{:4}{:4}{:13}{:18}{:9}{:9}{:9}\n",
"", // 8X at 1-8
"2.5", // YJ at 9-12
"", // 4X at 13-16
"12345.678", // E at 17-29
"", // 18X at 30-47
"1.23E-03", // AR at 48-56
"4.56E-04", // SR at 57-65
"7.89E-05" // WR at 66-74
);
println!("Level line length: {}", level_line.len());
println!("Level line: {:?}", level_line);
let cursor = std::io::Cursor::new(level_line.as_bytes());
let mut reader = BufReader::new(cursor);
let level = read_kurucz_level(&mut reader).unwrap();
assert!((level.yj - 2.5).abs() < 0.01, "YJ mismatch: got {}", level.yj);
assert!((level.e - 12345.678).abs() < 0.001, "E mismatch: got {}", level.e);
// 使用更宽松的容差
assert!((level.ar - 1.23e-3).abs() < 1e-4, "AR mismatch: got {}", level.ar);
assert!((level.sr - 4.56e-4).abs() < 1e-5, "SR mismatch: got {}", level.sr);
assert!((level.wr - 7.89e-5).abs() < 1e-6, "WR mismatch: got {}", level.wr);
}
#[test]
fn test_bolcm_calculation() {
// 验证 BOLCM 常数
let expected = 1.0e8 / HK / CAS;
assert!((BOLCM - expected).abs() < 1e-10);
}
}