428 lines
12 KiB
Markdown
428 lines
12 KiB
Markdown
# 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**: 集成测试框架
|