InvInt 的 xi2 和 xi3 数组应该预计算为 1/I² 和 1/I³, 与 Fortran INITIA 中的初始化逻辑一致。 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
11 KiB
11 KiB
| name | description |
|---|---|
| fortran-to-rust | Fortran 到 Rust 的重构指南和工作流。触发条件:(1) 刚使用过fortran-analyzer skills 获得需要重构的模块(2)用户提到重构 Fortran 到 Rust;(3) 开始新的 Fortran 函数重构。(4) 继续重构下一个fortran模块。提供完整重构流程、常见陷阱、测试规范。 |
Fortran → Rust 重构指南
将 TLUSTY/SYNSPEC 的 Fortran 函数重构为 Rust。
重构流程
Step 1: 选择目标函数
注意:使用 fortran-analyzer skills 获取需要重构的模块。
选择原则:
- 必须选择fortran-analyzer skills推荐的第一个模块
- 不要因为行数多、复杂或者 COMMON 依赖就回避它们,不要回避复杂函数,它们往往是重构重点。
Step 2: 分析 Fortran 源码
cat tlusty/extracted/TARGET.f
检查清单:
[ ] INCLUDE 文件(COMMON 块 → src/state/)
[ ] 函数参数和返回值
[ ] 调用的其他函数(是否已实现?)
[ ] 是否有 I/O 操作(READ/WRITE/OPEN)
[ ] DATA 语句(已预提取到 src/data.rs)
重要:不要删减任何逻辑,完整实现 Fortran 功能。
Step 3: 创建 Rust 模块
touch src/math/TARGET.rs
注意:所有重构的rust代码都暂时放到src/math/文件夹下
Step 4: 实现函数
命名映射:
| Fortran | Rust |
|---|---|
FUNCTION |
pub fn |
SUBROUTINE |
pub fn(返回值用元组或结构体) |
| COMMON 块 | 结构体参数 |
Step 5: 添加到 mod.rs
// src/math/mod.rs 或 src/io/mod.rs
mod target;
pub use target::target;
Step 6: 编写测试
注意: 需要编写完整的真实的函数测试,而不只是测试变量
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_relative_eq;
#[test]
fn test_basic() {
// 基本功能测试
}
}
Step 7: 运行测试
# 推荐:静默警告
RUSTFLAGS="-A warnings" cargo test target 2>&1 | tail -10
# 或简洁输出
cargo test target -- --quiet 2>&1 | grep -E "^test|^running|^test result"
I/O 模块重构
何时使用 I/O 兼容层
当模块包含以下操作时:
READ(unit, ...)- 从文件/标准输入读取WRITE(unit, ...)- 写入文件/标准输出OPEN(...)- 打开文件
I/O 模块分类
| 类别 | 策略 | 示例 |
|---|---|---|
| 调试输出 | 删除或用 log::debug! |
fort.10, fort.14, fort.18 |
| 进度输出 | 保留,用 FortranWriter |
fort.6, fort.9 |
| 模型 I/O | 必须兼容,用 model.rs |
fort.7, fort.8 |
| 参数文件 | 用 FortranReader |
fort.5, fort.55 |
I/O 重构模式
模式 1: 分离计算与 I/O
// ❌ 混合(难以测试)
pub fn read_and_calculate() -> Result<f64> {
let input = read_from_file()?; // I/O
let result = calculate(input); // 计算
write_to_file(result)?; // I/O
Ok(result)
}
// ✅ 分离(可独立测试)
pub fn calculate(params: &CalcParams) -> CalcResult {
// 纯计算,无 I/O
}
pub fn run(input_path: &str, output_path: &str) -> Result<()> {
let reader = FortranReader::from_file(input_path)?;
let params = parse_input(reader)?;
let result = calculate(¶ms);
let mut writer = FortranWriter::to_file(output_path)?;
write_result(&mut writer, &result)?;
Ok(())
}
模式 2: 使用 I/O 兼容层
use crate::io::{FortranReader, FortranWriter, ModelFile};
// 读取输入
let mut reader = FortranReader::from_file("fort.5")?;
let teff: f64 = reader.read_value()?;
let grav: f64 = reader.read_value()?;
// 读取模型
let model = ModelFile::read(FortranReader::from_file("fort.8")?)?;
// 写入模型(与 Fortran 字节级兼容)
ModelFile::write(&model, BufWriter::new(File::create("fort.7")?))?;
Fortran I/O 特性处理
| Fortran 特性 | Rust 处理 |
|---|---|
READ(*,*) 自由格式 |
FortranReader::read_value() |
READ(IBUFF,*) 缓冲区 |
FortranReader 从字符串创建 |
WRITE(6,601) FORMAT |
FortranWriter + format_exp_fortran |
! 行内注释 |
FortranReader::read_line() 自动跳过 |
* 固定格式注释 |
FortranReader::read_line() 自动跳过 |
'string' 引号字符串 |
FortranReader::read_string() |
D 指数符号 |
FromFortran::from_fortran_str() 自动转换 |
单元号映射
// src/io/mod.rs
pub mod units {
pub const STDIN: u8 = 5;
pub const STDOUT: u8 = 6;
pub const MODEL_OUT: u8 = 7;
pub const MODEL_IN: u8 = 8;
pub const CONV_HIST: u8 = 9;
pub const SCRATCH: [u8; 3] = [91, 92, 93];
}
有 I/O 的模块列表
| 模块 | 主要 I/O | 重构策略 |
|---|---|---|
| INITIA | 打开所有输入文件 | 高:入口点 |
| OUTPUT | fort.7, fort.12, fort.22 | 高:模型输出 |
| READBF | 原子数据文件 | 中:用 FortranReader |
| KURUCZ | Kurucz 格式模型 | 中:格式转换 |
| NSTPAR | 非标准参数 | 低:可选 |
| TIMING | fort.69 | 低:调试 |
常见陷阱
索引和表达式
| 问题 | Fortran | Rust |
|---|---|---|
| 数组索引 | arr(i) 1-indexed |
arr[i-1] 0-indexed |
| 负对数 | -LOG(X) |
-ln(X) 不是 ln(-X) |
| 幂运算 | x**2 |
(x)*(x) 避免类型歧义 |
| 递减循环 | DO I=N,1,-1 |
用 isize 不用 usize |
矩阵存储
// Fortran 列优先: A(j,i) = a[(i-1)*N + (j-1)]
// 读取时:
for i in 0..n {
for j in 0..m {
a[i * m + j] = reader.read_value()?;
}
}
类型推断
// ❌ 编译错误
let result = (z1 - z2).powi(2);
// ✅ 显式乘法
let result = (z1 - z2) * (z1 - z2);
I/O 生命周期
// ❌ 返回引用,但修改了源
fn extract_token(&mut self) -> Result<&str> {
let token = &self.remaining[..];
self.remaining = "...".to_string(); // 借用冲突!
Ok(token)
}
// ✅ 返回 String
fn extract_token(&mut self) -> Result<String> {
let token = self.remaining[..end].to_string();
self.remaining = self.remaining[end..].to_string();
Ok(token)
}
测试规范
精度要求
| 函数类型 | 容差 | 示例 |
|---|---|---|
| 简单数学运算 | 1e-10 |
exp, log |
| 多项式近似 | 1e-7 |
Abramowitz-Stegun |
| f32 数组 | 1e-24 |
Voigt 轮廓 |
| I/O 读写 | 1e-6 |
模型文件往返 |
use approx::assert_relative_eq;
#[test]
fn test_calculation() {
let result = calculate(35000.0);
assert_relative_eq!(result, expected, epsilon = 1e-7);
}
I/O 测试模式
#[test]
fn test_model_roundtrip() {
// 创建测试模型
let mut model = ModelState::new(3, 3);
model.temp = vec![10000.0, 8000.0, 5000.0];
// 写入内存
let mut buffer = Vec::new();
ModelFile::write(&model, BufWriter::new(&mut buffer)).unwrap();
// 读回
let cursor = Cursor::new(buffer);
let reader = FortranReader::new(BufReader::new(cursor));
let model2 = ModelFile::read(reader).unwrap();
// 验证
for i in 0..model.nd {
assert_relative_eq!(model2.temp[i], model.temp[i], epsilon = 1e-6);
}
}
测试命令
# 单模块测试(推荐)
RUSTFLAGS="-A warnings" cargo test target 2>&1 | tail -10
# I/O 模块测试
cargo test io:: 2>&1 | grep -E "^test |^test result"
# 全量测试(谨慎!可能卡死)
# cargo test
代码规范
文件头注释
//! 模块简要说明。
//!
//! 重构自 TLUSTY `filename.f`
函数注释
/// 函数功能说明。
///
/// # 参数
/// * `x` - 参数说明
///
/// # 返回值
/// 返回值说明
pub fn func(x: f64) -> f64 { ... }
I/O 模块结构
// src/io/TARGET.rs
//! 模块说明
use super::{FortranReader, FortranWriter, Result};
/// 参数结构体
pub struct TargetParams<'a> {
pub input: &'a [f64],
}
/// 输出结构体
pub struct TargetOutput {
pub result: f64,
}
/// 纯计算函数(可测试)
pub fn target_pure(params: &TargetParams) -> TargetOutput {
// 实现
}
/// 带 I/O 的入口(调用纯计算)
pub fn target(input_path: &str, output_path: &str) -> Result<TargetOutput> {
let reader = FortranReader::from_file(input_path)?;
let params = parse_params(reader)?;
let result = target_pure(¶ms);
// 可选:写输出
Ok(result)
}
快速命令参考
# === 分析 ===
# 查看重构进度
python3 .claude/skills/fortran-analyzer/scripts/analyze_fortran.py --priority | head -20
# 查看函数依赖树
python3 .claude/skills/fortran-analyzer/scripts/analyze_fortran.py --tree FUNCTION_NAME
# === 编译 ===
# 编译检查
cargo build 2>&1 | grep error
# 编译检查(静默警告)
RUSTFLAGS="-A warnings" cargo build 2>&1 | tail -5
# === 测试 ===
# 单模块测试(推荐)
RUSTFLAGS="-A warnings" cargo test target 2>&1 | tail -10
# I/O 模块测试
cargo test io:: 2>&1 | grep -E "^test |^test result"
项目结构
rust/src/
├── io/ # I/O 兼容层
│ ├── mod.rs # 模块入口,单元号常量
│ ├── reader.rs # FortranReader(自由格式)
│ ├── writer.rs # FortranWriter(格式化输出)
│ ├── model.rs # fort.7/8 模型文件
│ ├── input.rs # fort.5 主输入
│ └── format.rs # FORMAT 解析
├── math/ # 纯计算函数 (120+ 个 .rs 文件)
├── state/ # COMMON 块 (8 个模块)
│ ├── constants.rs # BASICS.FOR
│ ├── atomic.rs # ATOMIC.FOR
│ ├── model.rs # MODELQ.FOR
│ ├── arrays.rs # ARRAY1.FOR
│ ├── iterat.rs # ITERAT.FOR
│ ├── alipar.rs # ALIPAR.FOR
│ └── odfpar.rs # ODFPAR.FOR
└── data.rs # 静态数据(DATA 语句)
相关文档
| 文档 | 内容 |
|---|---|
.learnings/LEARNINGS.md |
14 个实际案例(索引、表达式、COMMON 等) |
docs/TLUSTY_IO_FILES.md |
完整 I/O 文件映射 |
docs/SYNSPEC_IO_FILES.md |
SYNSPEC I/O 文件 |
docs/IO_COMPATIBILITY_LAYER.md |
I/O 兼容层设计 |
相关 Skills
| Skill | 用途 | 状态 |
|---|---|---|
fortran-extractor |
提取 Fortran 文件 | ✅ 已完成 |
fortran-analyzer |
分析依赖关系 | 活跃使用 |
fortran-to-rust |
执行重构 | 本 skill |
self-improving-agent |
记录经验教训 | 建议配合使用 |
经验记录
重构过程中遇到问题或发现新模式时,使用 self-improving-agent 记录:
# 记录到 .learnings/LEARNINGS.md
# 格式:
## [LRN-YYYYMMDD-XXX] category
**Priority**: high | medium | low
**Status**: pending | resolved | promoted
### Summary
一句话总结
### Details
详细说明
### Metadata
- Source: 模块名
- Tags: 关键词