12 KiB
12 KiB
Fortran I/O 兼容层设计
目标
- 读取与 Fortran 相同格式的输入文件
- 生成与 Fortran 相同格式的输出文件
- 便于 A/B 测试对比
TLUSTY I/O 模式分析
单元号映射
| Fortran 单元 | 文件 | 用途 | 格式 |
|---|---|---|---|
| 5 | stdin | 输入命令 | 格式化 |
| 6 | stdout | 进度/结果输出 | 格式化 |
| 7 | fort.7 | 模型输出 | 格式化 |
| 8 | fort.8 | 模型输入 | 格式化 |
| 9 | fort.9 | 频率网格 | 格式化 |
| 91-93 | 临时 | 内部暂存 | UNFORMATTED |
输入格式特点
! 自由格式读取(list-directed)
READ(IBUFF,*) TEFF, GRAV ! 35000. 4.0
! 字符串用单引号
READ(IBUFF,*) TYPION ! ' H 1'
! 行内注释用 !
35000. 4.0 ! TEFF, GRAV
输出格式特点
! FORMAT 语句控制
WRITE(6,604) ION,TYPION(ION),N0I,N1I,NKI,IZ(ION)
604 FORMAT(1H ,I3,2X,A4,6I6,1PD15.3)
! 科学计数法
3.289007E+04 3.010526E+08 5.839922E-16
Rust 架构设计
目录结构
src/
├── io/
│ ├── mod.rs # I/O 模块入口
│ ├── reader.rs # Fortran 格式输入解析
│ ├── writer.rs # Fortran 格式输出
│ ├── units.rs # 单元号管理
│ └── format.rs # FORMAT 语句模拟
├── math/ # 纯计算(已有)
└── main.rs # 入口点
核心设计
1. 输入解析器 (reader.rs)
/// Fortran 风格的自由格式读取器
pub struct FortranReader<R: BufRead> {
inner: R,
current_line: String,
line_number: usize,
}
impl<R: BufRead> FortranReader<R> {
/// 读取一行,处理注释
pub fn read_line(&mut self) -> io::Result<&str> {
loop {
self.current_line.clear();
self.inner.read_line(&mut self.current_line)?;
self.line_number += 1;
// 去除行内注释
if let Some(pos) = self.current_line.find('!') {
self.current_line.truncate(pos);
}
let trimmed = self.current_line.trim();
if !trimmed.is_empty() {
return Ok(trimmed);
}
}
}
/// 读取值(模拟 READ(*,*))
pub fn read_values<T: FromFortran>(&mut self) -> io::Result<T> {
let line = self.read_line()?;
T::from_fortran_str(line)
}
}
/// 从 Fortran 字符串解析的 trait
pub trait FromFortran: Sized {
fn from_fortran_str(s: &str) -> io::Result<Self>;
}
impl FromFortran for f64 {
fn from_fortran_str(s: &str) -> io::Result<Self> {
// 处理 Fortran 的 'D' 和 'E' 指数符号
let s = s.to_uppercase().replace('D', "E");
s.trim().parse().map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))
}
}
impl FromFortran for bool {
fn from_fortran_str(s: &str) -> io::Result<Self> {
match s.trim().to_uppercase().as_str() {
"T" | ".TRUE." => Ok(true),
"F" | ".FALSE." => Ok(false),
_ => Err(io::Error::new(io::ErrorKind::InvalidData, "invalid boolean")),
}
}
}
2. 输出格式化器 (writer.rs)
/// Fortran 格式输出写入器
pub struct FortranWriter<W: Write> {
inner: W,
}
impl<W: Write> FortranWriter<W> {
/// 按 FORMAT 规范写入
pub fn write_formatted(&mut self, format: &FormatSpec, values: &[Value]) -> io::Result<()> {
let output = format.apply(values);
write!(self.inner, "{}", output)
}
/// 写入科学计数法(模拟 1PD15.3)
pub fn write_exp(&mut self, val: f64, width: usize, decimals: usize) -> io::Result<()> {
let s = format_exp_fortran(val, width, decimals);
write!(self.inner, "{}", s)
}
}
/// Fortran 风格的科学计数法
fn format_exp_fortran(val: f64, width: usize, decimals: usize) -> String {
if val == 0.0 {
return format!("{:>width$}", "0.0", width = width);
}
let abs_val = val.abs();
let exp = abs_val.log10().floor() as i32;
// Fortran 格式: ±0.XXXXE±YY 或 ±0.XXXXD±YY
let mantissa = val / 10f64.powi(exp);
let sign = if val >= 0.0 { " " } else { "" };
format!("{sign}{mantissa:width$.decimals$}E{exp:+03}")
}
3. 单元号管理 (units.rs)
use std::collections::HashMap;
use std::fs::File;
use std::io::{BufReader, BufWriter, Read, Write};
/// 文件单元管理器(模拟 Fortran 单元号)
pub struct FortranUnits {
units: HashMap<u8, Box<dyn UnitFile>>,
}
trait UnitFile {
fn as_reader(&mut self) -> Option<&mut dyn BufRead>;
fn as_writer(&mut self) -> Option<&mut dyn Write>;
}
impl FortranUnits {
pub fn new() -> Self {
let mut units = HashMap::new();
// 默认单元
units.insert(5, Box::new(StdinUnit::new()) as Box<dyn UnitFile>);
units.insert(6, Box::new(StdoutUnit::new()) as Box<dyn UnitFile>);
Self { units }
}
/// 打开文件到指定单元
pub fn open(&mut self, unit: u8, path: &str, status: OpenStatus) -> io::Result<()> {
let file = File::open(path)?;
let reader = BufReader::new(file);
self.units.insert(unit, Box::new(FileReader { inner: reader }));
Ok(())
}
/// 读取
pub fn read_line(&mut self, unit: u8) -> io::Result<String> {
if let Some(u) = self.units.get_mut(&unit) {
if let Some(reader) = u.as_reader() {
let mut line = String::new();
reader.read_line(&mut line)?;
return Ok(line);
}
}
Err(io::Error::new(io::ErrorKind::NotFound, "unit not found"))
}
/// 写入
pub fn write(&mut self, unit: u8, data: &str) -> io::Result<()> {
if let Some(u) = self.units.get_mut(&unit) {
if let Some(writer) = u.as_writer() {
return writer.write_all(data.as_bytes());
}
}
Err(io::Error::new(io::ErrorKind::NotFound, "unit not found"))
}
}
4. FORMAT 解析器 (format.rs)
/// Fortran FORMAT 规范
#[derive(Debug, Clone)]
pub struct FormatSpec {
items: Vec<FormatItem>,
}
#[derive(Debug, Clone)]
pub enum FormatItem {
Integer { width: usize },
Float { width: usize, decimals: usize, exponential: bool },
String { width: usize },
Char(char),
Newline,
Skip,
}
impl FormatSpec {
/// 解析 FORMAT 字符串
/// 例如: "(I3,2X,A4,6I6,1PD15.3)"
pub fn parse(s: &str) -> Result<Self, FormatError> {
// 移除外层括号
let s = s.trim_start_matches('(').trim_end_matches(')');
let mut items = Vec::new();
let mut chars = s.chars().peekable();
while let Some(&c) = chars.peek() {
match c {
'I' | 'i' => items.push(Self::parse_int(&mut chars)?),
'F' | 'f' => items.push(Self::parse_float(&mut chars)?),
'E' | 'e' | 'D' | 'd' => items.push(Self::parse_exp(&mut chars)?),
'A' | 'a' => items.push(Self::parse_string(&mut chars)?),
'X' | 'x' => { chars.next(); items.push(FormatItem::Skip); }
'/' => { chars.next(); items.push(FormatItem::Newline); }
'1'..='9' => {
let repeat = Self::parse_number(&mut chars)?;
// 处理重复计数
}
_ => { chars.next(); }
}
}
Ok(FormatSpec { items })
}
/// 应用格式到值
pub fn apply(&self, values: &[Value]) -> String {
let mut output = String::new();
let mut val_idx = 0;
for item in &self.items {
match item {
FormatItem::Integer { width } => {
if let Some(Value::Int(v)) = values.get(val_idx) {
output.push_str(&format!("{:>width$}", v));
val_idx += 1;
}
}
FormatItem::Float { width, decimals, exponential } => {
if let Some(Value::Float(v)) = values.get(val_idx) {
if *exponential {
output.push_str(&format_exp_fortran(*v, *width, *decimals));
} else {
output.push_str(&format!("{:>width$.decimals$}", v));
}
val_idx += 1;
}
}
FormatItem::String { width } => {
if let Some(Value::Str(s)) = values.get(val_idx) {
output.push_str(&format!("{:<width$}", s));
val_idx += 1;
}
}
FormatItem::Char(c) => output.push(*c),
FormatItem::Newline => output.push('\n'),
FormatItem::Skip => output.push(' '),
}
}
output
}
}
使用示例
use tlusty::io::{FortranReader, FortranWriter, FortranUnits};
use tlusty::math::some_calculation;
fn main() -> io::Result<()> {
// 初始化单元
let mut units = FortranUnits::new();
units.open(8, "model_input.dat", OpenStatus::Old)?;
units.open(7, "model_output.dat", OpenStatus::Replace)?;
// 读取输入
let reader = FortranReader::new(stdin().lock());
let teff: f64 = reader.read_values()?;
let grav: f64 = reader.read_values()?;
// 调用纯计算函数
let result = some_calculation(teff, grav);
// 写入输出(Fortran 格式)
let mut writer = FortranWriter::new(units.get_writer(7)?);
writer.write_exp(result.temperature, 15, 3)?;
writer.write_exp(result.pressure, 15, 3)?;
Ok(())
}
测试策略
1. 单元测试
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_format_exp() {
assert_eq!(
format_exp_fortran(3.289e4, 15, 3),
" 3.289E+04"
);
}
#[test]
fn test_read_values() {
let input = "35000. 4.0 ! TEFF, GRAV\n";
let mut reader = FortranReader::new(input.as_bytes());
let teff: f64 = reader.read_values().unwrap();
let grav: f64 = reader.read_values().unwrap();
assert_relative_eq!(teff, 35000.0);
assert_relative_eq!(grav, 4.0);
}
}
2. 集成测试
#[test]
fn test_hhe_model() {
// 运行 Rust 版本
let rust_output = run_tlusty_rust("tests/hhe/hhe35lt.5");
// 读取 Fortran 参考输出
let fortran_output = fs::read_to_string("tests/hhe/hhe35lt.7.bak").unwrap();
// 对比
assert_outputs_match(&rust_output, &fortran_output, tolerance=1e-6);
}
重构策略
对于有 I/O 的模块:
-
分离 I/O 和计算
// ❌ 混合 pub fn read_and_calculate() -> Result { let input = read_from_file()?; // I/O let result = calculate(input); // 计算 write_to_file(result)?; // I/O Ok(result) } // ✅ 分离 pub fn calculate(input: Input) -> Output { ... } // 纯函数,可测试 pub fn run() -> Result { let input = read_from_file()?; let result = calculate(input); write_to_file(result)?; Ok(result) } -
保持格式兼容
- 输入解析器必须接受 Fortran 格式
- 输出必须与 Fortran 输出字节级兼容(用于 diff)
-
渐进式重构
- 先实现纯计算部分
- 后实现 I/O 包装层
- 最后集成测试
实现优先级
- Phase 1: 实现
FortranReader(输入解析) - Phase 2: 实现
FortranWriter(输出格式化) - Phase 3: 实现
FortranUnits(文件管理) - Phase 4: 集成测试框架