//! 超级能级能量和统计权重计算。 //! //! 重构自 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>, /// Kurucz 能级能量 pub eku: Vec, /// Kurucz 能级统计权重 pub gku: Vec, /// GST 常数 pub gst: f64, /// Kurucz 能级到超级能级的映射 pub kku: Vec, } 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(reader: &mut R) -> Result { 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(reader: &mut R) -> Result { 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], ) -> 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); } }