SpectraRust/docs/IO_COMPATIBILITY_LAYER.md
2026-03-23 15:45:52 +08:00

428 lines
12 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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<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)
```rust
/// 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)
```rust
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)
```rust
/// 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
}
}
```
### 使用示例
```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**: 集成测试框架