Phase 1 翻译 (完成): - TLUSTY 350 函数 100% 翻译 - SYNSPEC 168 函数 100% 翻译 - ~495 Rust 模块 Phase 2 集成 (完成): - TLUSTY RESOLV 7 个 TODO 全部清除 - TLUSTY Runner IJALI 频率选择实现 - OPFRAC ioniz.dat 解析完整实现 - SYNSPEC Runner 编排流程连接完成 - SYNSPEC RESOLV OPAC→RTE→OUTPRI 调用链完整 Phase 3 验证 (完成, 修复 8 处 bug): - INITIA: compute_hydrogen_level_bounds 索引混合修复 - INILIN: GAMR0/GS0/GW0 展宽公式修复, 经典 VdW 公式修复 - INIBL0: CNM 常数 2.997925e18→e17 修复 - OPAC: Lyman IJ=2 修正缺失修复 - RTE: minv3 矩阵求逆符号错误修复 自动化脚本改进: - specf2r.sh: 添加 429 限流退避、完成检测、同步等待 - SKILL.md: 三阶段工作流 + 状态文件系统 - references/: Phase 1/2/3 独立参考文档 新增: - src/bin/synspec.rs: SYNSPEC 可执行文件入口 - .f2r_phase/.f2r_tasks/.f2r_complete: 状态管理文件 编译: 0 错误 | Clippy: 0 错误 | 测试: voigt 28 + eldens 5 通过 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
287 lines
8.2 KiB
Rust
287 lines
8.2 KiB
Rust
//! He II line profile data initialization.
|
|
//!
|
|
//! Translated from SYNSPEC54.FOR subroutine HE2INI (line 7535).
|
|
//!
|
|
//! Initializes necessary arrays for evaluating the He II line
|
|
//! absorption profiles using data calculated by Schoening and Butler.
|
|
|
|
#![allow(clippy::never_loop)]
|
|
use std::fs::File;
|
|
use std::io::{BufRead, BufReader};
|
|
|
|
/// Constants for He II profile arrays
|
|
pub const NLINE_HE2: usize = 19;
|
|
pub const NWL_HE2_MAX: usize = 36;
|
|
pub const NT_HE2: usize = 6;
|
|
pub const NE_HE2: usize = 11;
|
|
|
|
/// He II line profile table data
|
|
#[derive(Debug, Clone)]
|
|
pub struct He2ProfileTable {
|
|
/// Lower level index
|
|
pub il: usize,
|
|
/// Upper level index
|
|
pub iu: usize,
|
|
/// Central wavelength
|
|
pub wl0: f64,
|
|
/// Number of wavelength points
|
|
pub nwl: usize,
|
|
/// Log10 wavelength displacements [NWL_HE2_MAX]
|
|
pub wl: [f64; NWL_HE2_MAX],
|
|
/// Log10 temperature grid [NT_HE2]
|
|
pub xt: [f64; NT_HE2],
|
|
/// Log10 electron density grid [NE_HE2]
|
|
pub xne: [f64; NE_HE2],
|
|
/// Profile values [NWL_HE2_MAX x NT_HE2 x NE_HE2]
|
|
pub prf: [[[f64; NE_HE2]; NT_HE2]; NWL_HE2_MAX],
|
|
/// Asymptotic profile coefficient
|
|
pub xk: f64,
|
|
}
|
|
|
|
impl Default for He2ProfileTable {
|
|
fn default() -> Self {
|
|
Self {
|
|
il: 0,
|
|
iu: 0,
|
|
wl0: 0.0,
|
|
nwl: 0,
|
|
wl: [0.0; NWL_HE2_MAX],
|
|
xt: [0.0; NT_HE2],
|
|
xne: [0.0; NE_HE2],
|
|
prf: [[[0.0; NE_HE2]; NT_HE2]; NWL_HE2_MAX],
|
|
xk: 0.0,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// He II line initialization result
|
|
#[derive(Debug, Clone)]
|
|
pub struct He2InitResult {
|
|
/// Profile tables for each line
|
|
pub tables: Vec<He2ProfileTable>,
|
|
/// Number of wavelength points per line [NLINE_HE2]
|
|
pub nwlhe2: [usize; NLINE_HE2],
|
|
/// Lower level indices [NLINE_HE2]
|
|
pub ilhe2: [usize; NLINE_HE2],
|
|
/// Upper level indices [NLINE_HE2]
|
|
pub iuhe2: [usize; NLINE_HE2],
|
|
}
|
|
|
|
impl Default for He2InitResult {
|
|
fn default() -> Self {
|
|
Self {
|
|
tables: Vec::new(),
|
|
nwlhe2: [0; NLINE_HE2],
|
|
ilhe2: [0; NLINE_HE2],
|
|
iuhe2: [0; NLINE_HE2],
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Parameters for HE2INI
|
|
pub struct He2iniParams {
|
|
/// Path to data directory
|
|
pub data_dir: String,
|
|
/// Model depth points
|
|
pub nd: usize,
|
|
/// Temperature array [nd]
|
|
pub temp: Vec<f64>,
|
|
/// Electron density array [nd]
|
|
pub elec: Vec<f64>,
|
|
/// Turbulent velocity array [nd]
|
|
pub vturb: Vec<f64>,
|
|
}
|
|
|
|
/// Initialize He II line profile data.
|
|
///
|
|
/// # Arguments
|
|
/// * `params` - Initialization parameters
|
|
///
|
|
/// # Returns
|
|
/// He II line initialization result with profile tables
|
|
pub fn he2ini(params: &He2iniParams) -> std::io::Result<He2InitResult> {
|
|
let filename = format!("{}/he2prf.dat", params.data_dir);
|
|
let file = File::open(&filename)?;
|
|
let reader = BufReader::new(file);
|
|
let mut lines = reader.lines();
|
|
|
|
let mut result = He2InitResult::default();
|
|
|
|
for iline in 0..NLINE_HE2 {
|
|
// Read line indices: FORMAT(//14X,I2,9X,I2/)
|
|
let header = read_next_nonblank(&mut lines)?;
|
|
let (il, iu) = parse_he2_header(&header)?;
|
|
|
|
result.ilhe2[iline] = il;
|
|
result.iuhe2[iline] = iu;
|
|
|
|
// Compute central wavelength
|
|
let wl00 = if il <= 2 { 227.838 } else { 227.7776 };
|
|
let wl0 = wl00 / (1.0 / (il as f64).powi(2) - 1.0 / (iu as f64).powi(2));
|
|
|
|
let mut table = He2ProfileTable {
|
|
il,
|
|
iu,
|
|
wl0,
|
|
..Default::default()
|
|
};
|
|
|
|
// Read wavelength points
|
|
let wl_line = read_next_line(&mut lines)?;
|
|
let wl_parts = parse_he2_data(&wl_line)?;
|
|
let nwl = wl_parts[0] as usize;
|
|
table.nwl = nwl;
|
|
result.nwlhe2[iline] = nwl;
|
|
for i in 0..nwl.min(NWL_HE2_MAX) {
|
|
table.wl[i] = if wl_parts[i + 1] < 1.0e-4 {
|
|
(1.0e-4_f64).log10()
|
|
} else {
|
|
wl_parts[i + 1].log10()
|
|
};
|
|
}
|
|
|
|
// Read temperature points: FORMAT(2X,I4,F10.3,5F12.3)
|
|
let xt_line = read_next_line(&mut lines)?;
|
|
let xt_parts = parse_he2_data(&xt_line)?;
|
|
let nt = xt_parts[0] as usize;
|
|
for i in 0..nt.min(NT_HE2) {
|
|
table.xt[i] = xt_parts[i + 1];
|
|
}
|
|
|
|
// Read electron density points: FORMAT(2X,I4,F10.2,5F12.2/4X,5F12.2)
|
|
let xne_line = read_next_line(&mut lines)?;
|
|
let xne_parts = parse_he2_data(&xne_line)?;
|
|
let ne = xne_parts[0] as usize;
|
|
for i in 0..ne.min(NE_HE2) {
|
|
table.xne[i] = xne_parts[i + 1];
|
|
}
|
|
|
|
// Skip blank line
|
|
lines.next();
|
|
|
|
// Read profile data: FORMAT(10F8.3)
|
|
for ie in 0..ne.min(NE_HE2) {
|
|
for _it in 0..nt.min(NT_HE2) {
|
|
lines.next(); // Skip blank line
|
|
let prf_line = read_next_line(&mut lines)?;
|
|
let prf_parts = parse_he2_data(&prf_line)?;
|
|
for iwl in 0..nwl.min(NWL_HE2_MAX) {
|
|
if iwl < prf_parts.len() {
|
|
table.prf[iwl][_it][ie] = prf_parts[iwl];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Compute asymptotic profile coefficient
|
|
if nwl > 0 && ne > 0 {
|
|
let xclog = table.prf[nwl - 1][0][0]
|
|
+ 2.5 * table.wl[nwl - 1]
|
|
+ 31.831
|
|
- table.xne[0]
|
|
- 2.0 * wl0.log10();
|
|
let xklog = 0.6666667 * (xclog - 0.176);
|
|
table.xk = (xklog * std::f64::consts::LN_10).exp();
|
|
}
|
|
|
|
result.tables.push(table);
|
|
}
|
|
|
|
Ok(result)
|
|
}
|
|
|
|
/// Read next non-empty line
|
|
fn read_next_line(lines: &mut impl Iterator<Item = std::io::Result<String>>) -> std::io::Result<String> {
|
|
loop {
|
|
match lines.next() {
|
|
Some(Ok(line)) => return Ok(line),
|
|
Some(Err(e)) => return Err(e),
|
|
None => return Err(std::io::Error::new(
|
|
std::io::ErrorKind::UnexpectedEof,
|
|
"Unexpected end of file",
|
|
)),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Read next non-blank line (skip empty lines)
|
|
fn read_next_nonblank(lines: &mut impl Iterator<Item = std::io::Result<String>>) -> std::io::Result<String> {
|
|
loop {
|
|
let line = read_next_line(lines)?;
|
|
if !line.trim().is_empty() {
|
|
return Ok(line);
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Parse He II header line: FORMAT(//14X,I2,9X,I2/)
|
|
fn parse_he2_header(line: &str) -> std::io::Result<(usize, usize)> {
|
|
let parts: Vec<&str> = line.split_whitespace().collect();
|
|
if parts.len() < 2 {
|
|
return Err(std::io::Error::new(
|
|
std::io::ErrorKind::InvalidData,
|
|
format!("Invalid He II header: {}", line),
|
|
));
|
|
}
|
|
|
|
let il = parts[0].parse::<usize>().map_err(|e| {
|
|
std::io::Error::new(std::io::ErrorKind::InvalidData, format!("IL: {}", e))
|
|
})?;
|
|
let iu = parts[1].parse::<usize>().map_err(|e| {
|
|
std::io::Error::new(std::io::ErrorKind::InvalidData, format!("IU: {}", e))
|
|
})?;
|
|
|
|
Ok((il, iu))
|
|
}
|
|
|
|
/// Parse He II data line (free format)
|
|
fn parse_he2_data(line: &str) -> std::io::Result<Vec<f64>> {
|
|
let values: Vec<f64> = line
|
|
.split_whitespace()
|
|
.filter_map(|s| s.parse::<f64>().ok())
|
|
.collect();
|
|
|
|
Ok(values)
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_he2ini_default() {
|
|
let result = He2InitResult::default();
|
|
assert_eq!(result.nwlhe2.len(), NLINE_HE2);
|
|
assert_eq!(result.ilhe2.len(), NLINE_HE2);
|
|
assert!(result.tables.is_empty());
|
|
}
|
|
|
|
#[test]
|
|
fn test_he2_profile_table_default() {
|
|
let table = He2ProfileTable::default();
|
|
assert_eq!(table.nwl, 0);
|
|
assert_eq!(table.il, 0);
|
|
assert_eq!(table.iu, 0);
|
|
}
|
|
|
|
#[test]
|
|
fn test_parse_he2_header() {
|
|
let line = " 1 2";
|
|
let result = parse_he2_header(line);
|
|
assert!(result.is_ok());
|
|
let (il, iu) = result.unwrap();
|
|
assert_eq!(il, 1);
|
|
assert_eq!(iu, 2);
|
|
}
|
|
|
|
#[test]
|
|
fn test_parse_he2_data() {
|
|
let line = " 19 0.123 0.456 0.789";
|
|
let result = parse_he2_data(line);
|
|
assert!(result.is_ok());
|
|
let values = result.unwrap();
|
|
assert_eq!(values.len(), 4);
|
|
assert!((values[0] - 19.0).abs() < 1e-10);
|
|
}
|
|
}
|