663 lines
18 KiB
Rust
663 lines
18 KiB
Rust
//! Kurucz ATLAS 格式模型读取。
|
||
//!
|
||
//! 重构自 TLUSTY `kurucz.f`。
|
||
//!
|
||
//! 从 unit 8 (fort.8) 读取 Kurucz ATLAS 格式的初始大气模型。
|
||
//! 支持 ATLAS9 和 ATLAS12 格式。
|
||
//!
|
||
//! # 功能
|
||
//!
|
||
//! - 解析 Kurucz ATLAS 模型文件格式
|
||
//! - 支持标准格式和 IFIXDE 固定格式
|
||
//! - 提取温度、密度、电子密度等物理量
|
||
|
||
use super::{IoError, Result};
|
||
use crate::tlusty::state::constants::*;
|
||
use std::io::BufRead;
|
||
|
||
// f2r_depends: CALLER_HANDLED
|
||
// Fortran KURUCZ performs I/O + physics initialization in one subroutine.
|
||
// Rust architecture separates these:
|
||
// - I/O: read_kurucz() / kurucz() (this file)
|
||
// - Physics: caller must invoke for each depth point:
|
||
// 1. RHONEN (IFIXDE path) or compute AN from pressure (standard path)
|
||
// 2. WNSTOR(id, ...)
|
||
// 3. SABOLF(id, ...) via sabolf_pure()
|
||
// 4. Set IIFOR0 = [1, 2, ..., NLEV0]
|
||
// 5. RATMAT(id, iifor0, -1, a, b)
|
||
// 6. LEVSOL(a, b, poplte, iifor0, nlev0, 1)
|
||
// 7. Store POPUL0(i, id) = POPLTE(i) or POPUL(i, id) = POPLTE(i)
|
||
// 8. Optional: MOLEQ if ifmol > 0 and T < tmolim
|
||
|
||
/// Kurucz ATLAS format model reader wrapper (matches Fortran KURUCZ subroutine signature).
|
||
pub fn kurucz<R: BufRead>(ndpth: usize, reader: &mut R) -> Result<KuruczModel> {
|
||
let params = KuruczReadParams {
|
||
max_depth: ndpth,
|
||
..Default::default()
|
||
};
|
||
read_kurucz(¶ms, reader)
|
||
}
|
||
|
||
// ============================================================================
|
||
// 常量
|
||
// ============================================================================
|
||
|
||
/// 输入参数数量
|
||
const MINPUT: usize = 7;
|
||
|
||
// ============================================================================
|
||
// Kurucz 模型头部
|
||
// ============================================================================
|
||
|
||
/// Kurucz 模型头部信息
|
||
#[derive(Debug, Clone)]
|
||
pub struct KuruczHeader {
|
||
/// 有效温度 (K)
|
||
pub teff: f64,
|
||
/// 引力对数 (log g, cgs)
|
||
pub grav_log: f64,
|
||
/// 深度点数
|
||
pub nd: usize,
|
||
}
|
||
|
||
// ============================================================================
|
||
// Kurucz 深度点数据
|
||
// ============================================================================
|
||
|
||
/// 单个深度点的 Kurucz 数据(标准格式)
|
||
#[derive(Debug, Clone)]
|
||
pub struct KuruczDepthPoint {
|
||
/// 深度变量 (X1)
|
||
pub depth: f64,
|
||
/// 温度 (K)
|
||
pub temp: f64,
|
||
/// 粒子密度 × 玻尔兹曼常数 × 温度 (X3, 压力相关)
|
||
pub x3: f64,
|
||
/// 电子密度 (cm⁻³)
|
||
pub elec: f64,
|
||
}
|
||
|
||
/// 单个深度点的 Kurucz 数据(IFIXDE 格式)
|
||
#[derive(Debug, Clone)]
|
||
pub struct KuruczIfixdeDepthPoint {
|
||
/// 质量深度 (g/cm²)
|
||
pub dm: f64,
|
||
/// 温度 (K)
|
||
pub temp: f64,
|
||
/// 气压 (dyn/cm²)
|
||
pub pressure: f64,
|
||
/// 中性氢密度 (cm⁻³)
|
||
pub ane0: f64,
|
||
/// 辅助量 1
|
||
pub a1: f64,
|
||
/// 辅助量 2
|
||
pub a2: f64,
|
||
/// 辅助量 3
|
||
pub a3: f64,
|
||
/// 速度场
|
||
pub vel: f64,
|
||
/// 质量密度 (g/cm³)
|
||
pub rho: f64,
|
||
}
|
||
|
||
// ============================================================================
|
||
// 输入参数
|
||
// ============================================================================
|
||
|
||
/// KURUCZ 读取参数。
|
||
pub struct KuruczReadParams {
|
||
/// 固定深度格式标志 (IFIXDE)
|
||
/// > 0: 使用固定格式读取
|
||
/// = 0: 使用标准 Kurucz 格式
|
||
pub ifixde: i32,
|
||
/// 最大深度点数 (用于数组大小检查)
|
||
pub max_depth: usize,
|
||
}
|
||
|
||
impl Default for KuruczReadParams {
|
||
fn default() -> Self {
|
||
Self {
|
||
ifixde: 0,
|
||
max_depth: MDEPTH,
|
||
}
|
||
}
|
||
}
|
||
|
||
// ============================================================================
|
||
// 输出结构
|
||
// ============================================================================
|
||
|
||
/// KURUCZ 模型读取输出。
|
||
#[derive(Debug, Clone)]
|
||
pub struct KuruczModel {
|
||
/// 深度点数
|
||
pub nd: usize,
|
||
/// 有效温度 (K)
|
||
pub teff: f64,
|
||
/// 引力对数 (log g)
|
||
pub grav_log: f64,
|
||
/// 深度点数据(标准格式)
|
||
pub depth_points: Vec<KuruczDepthPoint>,
|
||
/// 深度点数据(IFIXDE 格式)
|
||
pub depth_points_ifixde: Vec<KuruczIfixdeDepthPoint>,
|
||
/// 插值标志
|
||
pub intrpl: i32,
|
||
/// 是否使用 IFIXDE 格式
|
||
pub is_ifixde: bool,
|
||
}
|
||
|
||
impl KuruczModel {
|
||
/// 获取温度数组
|
||
pub fn temperatures(&self) -> Vec<f64> {
|
||
if self.is_ifixde {
|
||
self.depth_points_ifixde.iter().map(|d| d.temp).collect()
|
||
} else {
|
||
self.depth_points.iter().map(|d| d.temp).collect()
|
||
}
|
||
}
|
||
|
||
/// 获取电子密度数组
|
||
pub fn electron_densities(&self) -> Vec<f64> {
|
||
if self.is_ifixde {
|
||
self.depth_points_ifixde.iter().map(|d| d.ane0).collect()
|
||
} else {
|
||
self.depth_points.iter().map(|d| d.elec).collect()
|
||
}
|
||
}
|
||
|
||
/// 获取质量密度数组
|
||
pub fn densities(&self) -> Vec<f64> {
|
||
if self.is_ifixde {
|
||
self.depth_points_ifixde.iter().map(|d| d.rho).collect()
|
||
} else {
|
||
// 标准格式需要从 X3 计算
|
||
self.depth_points.iter().map(|d| d.x3).collect()
|
||
}
|
||
}
|
||
}
|
||
|
||
// ============================================================================
|
||
// 辅助函数
|
||
// ============================================================================
|
||
|
||
/// 读取 Kurucz 头部信息 (标准格式)
|
||
///
|
||
/// FORMAT: A15, 6X, F8.5
|
||
fn read_kurucz_header_standard<R: BufRead>(reader: &mut R) -> Result<(String, f64)> {
|
||
let mut line = String::new();
|
||
reader.read_line(&mut line)?;
|
||
|
||
if line.len() < 22 {
|
||
return Err(IoError::ParseError(format!(
|
||
"Kurucz header line too short: {}",
|
||
line.len()
|
||
)));
|
||
}
|
||
|
||
// A15: 列 1-15 - KUR 字符串
|
||
let kur = line[0..15].trim().to_string();
|
||
|
||
// F8.5: 列 22-29 (跳过 6 个字符) - GRAVK
|
||
let gravk: f64 = if line.len() >= 29 {
|
||
line[21..29].trim().parse().unwrap_or(0.0)
|
||
} else {
|
||
0.0
|
||
};
|
||
|
||
Ok((kur, gravk))
|
||
}
|
||
|
||
/// 从 Kurucz 字符串解析 TEFF
|
||
///
|
||
/// FORMAT: 4X, F8.0
|
||
fn parse_teff_from_kur(kur: &str) -> Result<f64> {
|
||
// TEFF 在字符串中的位置: 'TEFF' 开头,后面是温度值
|
||
if !kur.starts_with("TEFF") {
|
||
return Err(IoError::ParseError(format!(
|
||
"Not a Kurucz model: expected 'TEFF', got '{}'",
|
||
kur
|
||
)));
|
||
}
|
||
|
||
// 4X, F8.0: 从第 5 个字符开始读取温度
|
||
if kur.len() < 12 {
|
||
return Err(IoError::ParseError(format!(
|
||
"Kurucz TEFF string too short: '{}'",
|
||
kur
|
||
)));
|
||
}
|
||
|
||
let teff_str = kur[4..12].trim();
|
||
teff_str
|
||
.parse()
|
||
.map_err(|e| IoError::ParseError(format!("Failed to parse TEFF: {} in '{}'", e, kur)))
|
||
}
|
||
|
||
/// 跳过直到找到 'READ DECK' 行
|
||
fn skip_to_read_deck<R: BufRead>(reader: &mut R) -> Result<i32> {
|
||
let mut kur = String::new();
|
||
|
||
loop {
|
||
kur.clear();
|
||
let bytes = reader.read_line(&mut kur)?;
|
||
if bytes == 0 {
|
||
return Err(IoError::UnexpectedEof);
|
||
}
|
||
|
||
if kur.trim().starts_with("READ DECK") {
|
||
// 解析深度点数: 10X, I3
|
||
let ndpth_str = if kur.len() >= 13 {
|
||
kur[10..13].trim()
|
||
} else {
|
||
"0"
|
||
};
|
||
let ndpth: i32 = ndpth_str.parse().unwrap_or(0);
|
||
return Ok(ndpth);
|
||
}
|
||
}
|
||
}
|
||
|
||
/// 读取固定格式深度数据头部 (IFIXDE > 0 路径)
|
||
///
|
||
/// FORMAT: 4X, F8.0, 9X, F8.5
|
||
fn read_ifixde_header<R: BufRead>(reader: &mut R) -> Result<(f64, f64)> {
|
||
let mut line = String::new();
|
||
reader.read_line(&mut line)?;
|
||
|
||
if line.len() < 26 {
|
||
return Err(IoError::ParseError(format!(
|
||
"IFIXDE header line too short: {}",
|
||
line.len()
|
||
)));
|
||
}
|
||
|
||
// F8.0: 列 5-12 - TEF
|
||
let tef: f64 = line[4..12].trim().parse().unwrap_or(0.0);
|
||
|
||
// F8.5: 列 22-29 - GRAV
|
||
let grav: f64 = if line.len() >= 29 {
|
||
line[21..29].trim().parse().unwrap_or(0.0)
|
||
} else {
|
||
0.0
|
||
};
|
||
|
||
Ok((tef, grav))
|
||
}
|
||
|
||
/// 跳过 19 行然后读取深度点数
|
||
///
|
||
/// FORMAT: ////////////////////10X,I3/ (19 个斜杠)
|
||
fn read_nd_after_skip<R: BufRead>(reader: &mut R) -> Result<i32> {
|
||
// 跳过 19 行
|
||
for _ in 0..19 {
|
||
let mut line = String::new();
|
||
reader.read_line(&mut line)?;
|
||
}
|
||
|
||
// 读取深度点数行: 10X, I3
|
||
let mut line = String::new();
|
||
reader.read_line(&mut line)?;
|
||
|
||
// 解析 I3
|
||
let nd: i32 = if line.len() >= 13 {
|
||
line[10..13].trim().parse().unwrap_or(0)
|
||
} else {
|
||
line.trim().parse().unwrap_or(0)
|
||
};
|
||
|
||
Ok(nd)
|
||
}
|
||
|
||
/// 读取自由格式深度数据 (IFIXDE 路径)
|
||
///
|
||
/// 自由格式: DM, TEMP, P, ANE0, A1, A2, A3, VEL, RHO
|
||
fn read_ifixde_depth<R: BufRead>(reader: &mut R) -> Result<KuruczIfixdeDepthPoint> {
|
||
let mut line = String::new();
|
||
reader.read_line(&mut line)?;
|
||
|
||
let values: Vec<f64> = line
|
||
.split_whitespace()
|
||
.filter_map(|s| s.parse().ok())
|
||
.collect();
|
||
|
||
if values.len() < 9 {
|
||
return Err(IoError::ParseError(format!(
|
||
"Not enough values in IFIXDE depth line: expected 9, got {} in '{}'",
|
||
values.len(),
|
||
line
|
||
)));
|
||
}
|
||
|
||
Ok(KuruczIfixdeDepthPoint {
|
||
dm: values[0],
|
||
temp: values[1],
|
||
pressure: values[2],
|
||
ane0: values[3],
|
||
a1: values[4],
|
||
a2: values[5],
|
||
a3: values[6],
|
||
vel: values[7],
|
||
rho: values[8],
|
||
})
|
||
}
|
||
|
||
/// 读取标准格式深度数据
|
||
///
|
||
/// 自由格式: X(1)..X(7)
|
||
fn read_standard_depth<R: BufRead>(reader: &mut R) -> Result<KuruczDepthPoint> {
|
||
let mut line = String::new();
|
||
reader.read_line(&mut line)?;
|
||
|
||
let values: Vec<f64> = line
|
||
.split_whitespace()
|
||
.filter_map(|s| s.parse().ok())
|
||
.collect();
|
||
|
||
if values.len() < MINPUT {
|
||
return Err(IoError::ParseError(format!(
|
||
"Not enough values in standard depth line: expected {}, got {} in '{}'",
|
||
MINPUT,
|
||
values.len(),
|
||
line
|
||
)));
|
||
}
|
||
|
||
Ok(KuruczDepthPoint {
|
||
depth: values[0],
|
||
temp: values[1],
|
||
x3: values[2],
|
||
elec: values[3],
|
||
})
|
||
}
|
||
|
||
// ============================================================================
|
||
// 主函数
|
||
// ============================================================================
|
||
|
||
/// 读取 Kurucz ATLAS 格式模型。
|
||
///
|
||
/// # 参数
|
||
/// - `params` - 读取参数
|
||
/// - `reader` - BufRead 读取器
|
||
///
|
||
/// # 返回
|
||
/// Kurucz 模型数据或错误
|
||
///
|
||
/// # Fortran 原始代码
|
||
/// ```fortran
|
||
/// SUBROUTINE KURUCZ(NDPTH)
|
||
/// ```
|
||
pub fn read_kurucz<R: BufRead>(
|
||
params: &KuruczReadParams,
|
||
reader: &mut R,
|
||
) -> Result<KuruczModel> {
|
||
if params.ifixde > 0 {
|
||
read_kurucz_ifixde(params, reader)
|
||
} else {
|
||
read_kurucz_standard(params, reader)
|
||
}
|
||
}
|
||
|
||
/// 读取标准 Kurucz 格式模型。
|
||
fn read_kurucz_standard<R: BufRead>(
|
||
params: &KuruczReadParams,
|
||
reader: &mut R,
|
||
) -> Result<KuruczModel> {
|
||
// 读取头部
|
||
let (kur, grav_log) = read_kurucz_header_standard(reader)?;
|
||
let teff = parse_teff_from_kur(&kur)?;
|
||
|
||
// 跳过直到 'READ DECK'
|
||
let ndpth = skip_to_read_deck(reader)?;
|
||
let nd = (ndpth - 1) as usize;
|
||
|
||
if nd > params.max_depth {
|
||
return Err(IoError::ParseError(format!(
|
||
"ND {} exceeds max depth {}",
|
||
nd, params.max_depth
|
||
)));
|
||
}
|
||
|
||
// 跳过一行 (TTT)
|
||
let mut dummy = String::new();
|
||
reader.read_line(&mut dummy)?;
|
||
|
||
// 读取每个深度点
|
||
let mut depth_points = Vec::with_capacity(nd);
|
||
for _ in 0..nd {
|
||
let dp = read_standard_depth(reader)?;
|
||
depth_points.push(dp);
|
||
}
|
||
|
||
// 查找 'BEGIN' 和插值标志
|
||
let mut intrpl = 0i32;
|
||
loop {
|
||
let mut line = String::new();
|
||
let bytes = reader.read_line(&mut line)?;
|
||
if bytes == 0 {
|
||
break;
|
||
}
|
||
|
||
if line.trim().starts_with("BEGIN") {
|
||
// 尝试读取插值标志
|
||
let mut intrpl_line = String::new();
|
||
if reader.read_line(&mut intrpl_line).is_ok() {
|
||
intrpl = intrpl_line.trim().parse().unwrap_or(0);
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
|
||
Ok(KuruczModel {
|
||
nd,
|
||
teff,
|
||
grav_log,
|
||
depth_points,
|
||
depth_points_ifixde: Vec::new(),
|
||
intrpl,
|
||
is_ifixde: false,
|
||
})
|
||
}
|
||
|
||
/// 读取 IFIXDE 格式模型。
|
||
fn read_kurucz_ifixde<R: BufRead>(
|
||
params: &KuruczReadParams,
|
||
reader: &mut R,
|
||
) -> Result<KuruczModel> {
|
||
// 读取头部
|
||
let (teff, grav_log) = read_ifixde_header(reader)?;
|
||
|
||
// 读取深度点数
|
||
let nd_raw = read_nd_after_skip(reader)?;
|
||
let nd = (nd_raw - 1) as usize;
|
||
|
||
if nd > params.max_depth {
|
||
return Err(IoError::ParseError(format!(
|
||
"ND {} exceeds max depth {}",
|
||
nd, params.max_depth
|
||
)));
|
||
}
|
||
|
||
// 读取每个深度点
|
||
let mut depth_points = Vec::with_capacity(nd);
|
||
for _ in 0..nd {
|
||
let dp = read_ifixde_depth(reader)?;
|
||
depth_points.push(dp);
|
||
}
|
||
|
||
Ok(KuruczModel {
|
||
nd,
|
||
teff,
|
||
grav_log,
|
||
depth_points: Vec::new(),
|
||
depth_points_ifixde: depth_points,
|
||
intrpl: 0,
|
||
is_ifixde: true,
|
||
})
|
||
}
|
||
|
||
/// 从 FortranReader 读取 Kurucz 模型。
|
||
///
|
||
/// 注意:此函数会消耗 reader 的缓冲区。
|
||
pub fn read_kurucz_from_reader<R: BufRead>(
|
||
params: &KuruczReadParams,
|
||
reader: &mut R,
|
||
) -> Result<KuruczModel> {
|
||
read_kurucz(params, reader)
|
||
}
|
||
|
||
// ============================================================================
|
||
// 测试
|
||
// ============================================================================
|
||
|
||
#[cfg(test)]
|
||
mod tests {
|
||
use super::*;
|
||
use std::io::BufReader;
|
||
|
||
#[test]
|
||
fn test_parse_teff_from_kur() {
|
||
let kur = "TEFF 9750 ";
|
||
let teff = parse_teff_from_kur(kur).unwrap();
|
||
assert!((teff - 9750.0).abs() < 1.0);
|
||
|
||
let kur2 = "TEFF 35000 ";
|
||
let teff2 = parse_teff_from_kur(kur2).unwrap();
|
||
assert!((teff2 - 35000.0).abs() < 1.0);
|
||
}
|
||
|
||
#[test]
|
||
fn test_parse_teff_invalid() {
|
||
let kur = "NOTTEFF 123";
|
||
assert!(parse_teff_from_kur(kur).is_err());
|
||
}
|
||
|
||
#[test]
|
||
fn test_kurucz_depth_point() {
|
||
let dp = KuruczDepthPoint {
|
||
depth: 1.0e-5,
|
||
temp: 10000.0,
|
||
x3: 1.0e5,
|
||
elec: 1.0e12,
|
||
};
|
||
|
||
assert!((dp.depth - 1.0e-5).abs() < 1e-15);
|
||
assert!((dp.temp - 10000.0).abs() < 1e-10);
|
||
}
|
||
|
||
#[test]
|
||
fn test_kurucz_ifixde_depth_point() {
|
||
let dp = KuruczIfixdeDepthPoint {
|
||
dm: 1.0e-5,
|
||
temp: 10000.0,
|
||
pressure: 1.0e5,
|
||
ane0: 1.0e12,
|
||
a1: 0.0,
|
||
a2: 0.0,
|
||
a3: 0.0,
|
||
vel: 0.0,
|
||
rho: 1.0e-7,
|
||
};
|
||
|
||
assert!((dp.dm - 1.0e-5).abs() < 1e-15);
|
||
assert!((dp.temp - 10000.0).abs() < 1e-10);
|
||
assert!((dp.rho - 1.0e-7).abs() < 1e-15);
|
||
}
|
||
|
||
#[test]
|
||
fn test_read_standard_depth() {
|
||
let line = " 1.0E-05 10000.0 1.0E+05 1.0E+12 0.0 0.0 0.0\n";
|
||
let cursor = std::io::Cursor::new(line.as_bytes());
|
||
let mut reader = BufReader::new(cursor);
|
||
|
||
let dp = read_standard_depth(&mut reader).unwrap();
|
||
assert!((dp.depth - 1.0e-5).abs() < 1e-15);
|
||
assert!((dp.temp - 10000.0).abs() < 1e-10);
|
||
assert!((dp.x3 - 1.0e5).abs() < 1e-10);
|
||
assert!((dp.elec - 1.0e12).abs() < 1e-10);
|
||
}
|
||
|
||
#[test]
|
||
fn test_read_ifixde_depth() {
|
||
let line = " 1.0E-05 10000.0 1.0E+05 1.0E+12 0.0 0.0 0.0 0.0 1.0E-07\n";
|
||
let cursor = std::io::Cursor::new(line.as_bytes());
|
||
let mut reader = BufReader::new(cursor);
|
||
|
||
let dp = read_ifixde_depth(&mut reader).unwrap();
|
||
assert!((dp.dm - 1.0e-5).abs() < 1e-15);
|
||
assert!((dp.temp - 10000.0).abs() < 1e-10);
|
||
assert!((dp.pressure - 1.0e5).abs() < 1e-10);
|
||
assert!((dp.rho - 1.0e-7).abs() < 1e-15);
|
||
}
|
||
|
||
#[test]
|
||
fn test_kurucz_model() {
|
||
let model = KuruczModel {
|
||
nd: 3,
|
||
teff: 9750.0,
|
||
grav_log: 4.0,
|
||
depth_points: vec![
|
||
KuruczDepthPoint {
|
||
depth: 1.0e-5,
|
||
temp: 10000.0,
|
||
x3: 1.0e5,
|
||
elec: 1.0e12,
|
||
},
|
||
KuruczDepthPoint {
|
||
depth: 2.0e-5,
|
||
temp: 9000.0,
|
||
x3: 1.2e5,
|
||
elec: 8.0e11,
|
||
},
|
||
KuruczDepthPoint {
|
||
depth: 3.0e-5,
|
||
temp: 8000.0,
|
||
x3: 1.5e5,
|
||
elec: 6.0e11,
|
||
},
|
||
],
|
||
depth_points_ifixde: Vec::new(),
|
||
intrpl: 0,
|
||
is_ifixde: false,
|
||
};
|
||
|
||
assert_eq!(model.nd, 3);
|
||
let temps = model.temperatures();
|
||
assert_eq!(temps.len(), 3);
|
||
assert!((temps[0] - 10000.0).abs() < 1e-10);
|
||
}
|
||
|
||
#[test]
|
||
fn test_read_kurucz_header_standard() {
|
||
// 模拟 Kurucz 头部行
|
||
// FORMAT: A15, 6X, F8.5
|
||
// A15: 列 1-15, 6X: 列 16-21, F8.5: 列 22-29
|
||
// 需要至少 29 个字符
|
||
let header_line = "TEFF 9750 4.00000\n";
|
||
// 0123456789012345678901234567890
|
||
// |A15 |6X |F8.5 |
|
||
let cursor = std::io::Cursor::new(header_line.as_bytes());
|
||
let mut reader = BufReader::new(cursor);
|
||
|
||
let (kur, grav_log) = read_kurucz_header_standard(&mut reader).unwrap();
|
||
assert!(kur.starts_with("TEFF"));
|
||
assert!((grav_log - 4.0).abs() < 0.001);
|
||
}
|
||
|
||
#[test]
|
||
fn test_read_ifixde_header() {
|
||
// 模拟 IFIXDE 头部行
|
||
// FORMAT: 4X, F8.0, 9X, F8.5
|
||
// 4X: 列 1-4, F8.0: 列 5-12, 9X: 列 13-21, F8.5: 列 22-29
|
||
let header_line = " 9750. 4.00000\n";
|
||
// 0123456789012345678901234567890
|
||
// |4X |F8.0 |9X |F8.5 |
|
||
let cursor = std::io::Cursor::new(header_line.as_bytes());
|
||
let mut reader = BufReader::new(cursor);
|
||
|
||
let (teff, grav_log) = read_ifixde_header(&mut reader).unwrap();
|
||
assert!((teff - 9750.0).abs() < 1.0);
|
||
assert!((grav_log - 4.0).abs() < 0.001);
|
||
}
|
||
}
|