# Fortran I/O 兼容层设计 ## 目标 1. 读取与 Fortran 相同格式的输入文件 2. 生成与 Fortran 相同格式的输出文件 3. 便于 A/B 测试对比 ## TLUSTY I/O 模式分析 ### 单元号映射 | Fortran 单元 | 文件 | 用途 | 格式 | |-------------|------|------|------| | 5 | stdin | 输入命令 | 格式化 | | 6 | stdout | 进度/结果输出 | 格式化 | | 7 | fort.7 | 模型输出 | 格式化 | | 8 | fort.8 | 模型输入 | 格式化 | | 9 | fort.9 | 频率网格 | 格式化 | | 91-93 | 临时 | 内部暂存 | UNFORMATTED | ### 输入格式特点 ```fortran ! 自由格式读取(list-directed) READ(IBUFF,*) TEFF, GRAV ! 35000. 4.0 ! 字符串用单引号 READ(IBUFF,*) TYPION ! ' H 1' ! 行内注释用 ! 35000. 4.0 ! TEFF, GRAV ``` ### 输出格式特点 ```fortran ! 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) ```rust /// Fortran 风格的自由格式读取器 pub struct FortranReader { inner: R, current_line: String, line_number: usize, } impl FortranReader { /// 读取一行,处理注释 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(&mut self) -> io::Result { let line = self.read_line()?; T::from_fortran_str(line) } } /// 从 Fortran 字符串解析的 trait pub trait FromFortran: Sized { fn from_fortran_str(s: &str) -> io::Result; } impl FromFortran for f64 { fn from_fortran_str(s: &str) -> io::Result { // 处理 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 { 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) ```rust /// Fortran 格式输出写入器 pub struct FortranWriter { inner: W, } impl FortranWriter { /// 按 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) ```rust use std::collections::HashMap; use std::fs::File; use std::io::{BufReader, BufWriter, Read, Write}; /// 文件单元管理器(模拟 Fortran 单元号) pub struct FortranUnits { units: HashMap>, } 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); units.insert(6, Box::new(StdoutUnit::new()) as Box); 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 { 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) ```rust /// Fortran FORMAT 规范 #[derive(Debug, Clone)] pub struct FormatSpec { items: Vec, } #[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 { // 移除外层括号 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!("{: output.push(*c), FormatItem::Newline => output.push('\n'), FormatItem::Skip => output.push(' '), } } output } } ``` ### 使用示例 ```rust 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. 单元测试 ```rust #[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. 集成测试 ```rust #[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 的模块: 1. **分离 I/O 和计算** ```rust // ❌ 混合 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) } ``` 2. **保持格式兼容** - 输入解析器必须接受 Fortran 格式 - 输出必须与 Fortran 输出字节级兼容(用于 diff) 3. **渐进式重构** - 先实现纯计算部分 - 后实现 I/O 包装层 - 最后集成测试 ## 实现优先级 1. **Phase 1**: 实现 `FortranReader`(输入解析) 2. **Phase 2**: 实现 `FortranWriter`(输出格式化) 3. **Phase 3**: 实现 `FortranUnits`(文件管理) 4. **Phase 4**: 集成测试框架