# Fortran → Rust 重构问题与解决方案 记录重构过程中遇到的问题,避免重复踩坑。 --- ## 1. Fortran 1-indexed 转 Rust 0-indexed ### 问题 Fortran 数组从 1 开始索引,Rust 从 0 开始。 ### 解决方案 ```rust // 数组访问 arr(i) → arr[i-1] // 循环范围 DO I=1,N → for i in 0..n // 边界条件 (locate, ylintp 等) // Fortran: jl=0 表示"在第一个有效索引之前" // Rust: jl=0 就是第一个有效索引,无需调整 ``` ### 示例 (ylintp.f) ```fortran ! Fortran: jl=0 需要调整 IF (J.EQ.0) J = J+1 ! 调整到 J=1 ``` ```rust // Rust: jl=0 就是第一个有效索引,直接使用 // 删除 IF (J.EQ.0) 的调整逻辑 ``` --- ## 2. Fortran 表达式解析错误 ### 问题 `XL=-LOG(X)` 被误解为 `(-x).ln()` 而不是 `-x.ln()` ### 示例 (erfcin.f) ```fortran XL=-LOG(X) ! 意思是: XL = -ln(X) ``` ```rust // 错误: let xl = (-x).ln(); // ln(-x) = NaN for x>0 // 正确: let xl = -x.ln(); // -ln(x) ``` ### 教训 Fortran 中 `-LOG(X)` 是 `-(LOG(X))`,不是 `LOG(-X)` --- ## 3. powi 类型歧义 ### 问题 `(z1 - z2).powi(2)` 编译错误,类型不明确 ### 解决方案 ```rust // 错误: (z1 - z2).powi(2) // 编译器无法推断类型 // 方案1: 显式乘法 (推荐) (z1 - z2) * (z1 - z2) // 方案2: 显式类型标注 ((z1 - z2): f64).powi(2) ``` --- ## 4. 多项式近似精度 ### 问题 eint 函数 Rust 与 Fortran 结果差异 ~1.3e-8 ### 原因 Abramowitz-Stegun 多项式近似本身精度有限 ### 解决方案 放宽 epsilon 到 1e-7 ```rust // 简单函数: epsilon = 1e-10 // 多项式近似: epsilon = 1e-7 assert_relative_eq!(e1, exp_e1, epsilon = 1e-7); ``` --- ## 5. 数组索引越界 ### 问题 `locate` 函数无限循环 ### 原因 Fortran 中 ju=N+1 (越界值),Rust 直接用导致越界访问 ### 解决方案 使用 i64 允许负值,或调整边界逻辑 ```rust // 使用 i64 支持 jl=-1 let mut jl: i64 = -1; let mut ju: i64 = n as i64; ``` --- ## 6. voigte 索引偏移 ### 问题 voigte 返回负值 ### 原因 Fortran m 值是 1-indexed,Rust 中直接用导致索引偏移 ### 解决方案 ```rust // Fortran: m=6 表示第6个元素 // Rust: 需要用 m-1 let (m, quo) = if v < 2.4 { (5, 1.0) // Fortran m=6 → 0-indexed m=5 } else { (10, 1.0) // Fortran m=11 → 0-indexed m=10 }; ``` --- ## 7. 条件分支遗漏 ### 问题 voigte 某些参数组合返回错误值 ### 原因 漏掉了 Fortran 中的嵌套条件判断 ### 教训 仔细对比 Fortran 的所有分支,特别是嵌套 IF --- ## 8. COMMON 依赖误判 ### 问题 某些标记为"纯函数"的文件实际有 COMMON 依赖 ### 已确认有依赖的文件 ``` gamsp.f - 使用 VOIPAR COMMON sgmer1.f - 使用 COMMON sgmerd.f - 使用 COMMON cross.f - 使用 COMMON gfree1.f - 使用 COMMON gfree0.f - 使用 BASICS, MODELQ COMMON gfreed.f - 使用 BASICS, MODELQ COMMON wn.f - 使用 BASICS COMMON crossd.f - 使用 BASICS, ATOMIC, MODELQ COMMON dopgam.f - 使用 BASICS, ATOMIC, MODELQ COMMON verner.f - 使用 BASICS, ATOMIC COMMON sbfhe1.f - 使用 BASICS, ATOMIC COMMON rayini.f - 有文件 I/O + COMMON ``` ### 解决方案 重构前检查所有 INCLUDE 语句,不仅是 IMPLIC.FOR --- ## 9. Clenshaw 求和整数溢出 ### 问题 collhe 中递减循环溢出 ### 原因 `ir` 和 `jj` 递减到负数时,无符号整数溢出 ### 解决方案 ```rust // 错误: 用 usize let mut ir: usize = ...; ir -= 1; // 当 ir=0 时溢出! // 正确: 用有符号整数 let mut ir: isize = ...; ir -= 1; // 正常变为 -1 ``` --- ## 10. 索引计算中间结果溢出 ### 问题 collhe 的索引计算 `((iu+1)^2 - 3*(iu+1) + 4)/2` 溢出 ### 原因 中间结果超出 usize 范围 ### 解决方案 ```rust // 使用 i32 避免中间结果溢出 let idx = (((iu + 1) * (iu + 1) - 3 * (iu + 1) + 4) / 2 - 1) as usize; ``` --- ## 11. 数组变量命名冲突 ### 问题 不同 Fortran 文件中同名变量冲突 ### 原因 Fortran 文件局部作用域,Rust 需要唯一名称 ### 解决方案 修改 `extract_fortran_data.py`:所有变量添加文件名前缀 ```rust // 旧: ADI, BDENS (冲突) // 新: DIELRC_ADI, DIELRC_BDENS pub const DIELRC_ADI: [f64; 18] = [...]; ``` --- ## 12. 2D 数组存储顺序 ### 问题 Fortran 列优先,Rust 行优先 ### 解决方案 脚本自动转置,访问方式改变 ```rust // Fortran: ARR(j, i) 列优先 // Rust: ARR[i][j] 行优先 (已转置) ``` --- ## 13. DATA 语句数值解析 ### 问题 负数和科学计数法中有空格 ### 示例 ```fortran DATA A / - 14.2, 1.48 D-2 / ``` ### 解决方案 ```python # 修复负数空格 val = re.sub(r'-\s+(\d)', r'-\1', val) # 修复科学计数法空格 val = re.sub(r'(\d)\s+([eEdD])', r'\1\2', val) ``` --- ## 14. BPOPF 测试失败 - 温度列与主循环列重叠 ### 问题 BPOPF 函数测试中,B[20][21] 的值是 -0.12 而不是预期的 -0.02。 ### 原因 测试参数设置导致温度列 (NRE) 与主循环的某一列 (NSE+II) 重叠: - 主循环更新列 NSE+1 到 NSE+NLVEXP (Fortran 1-indexed: 21-23) - 温度列是 NRE (Fortran 1-indexed: 22) - 当 II=2 时,列 NSE+II = 22 = NRE,两者写入同一列 ### 解决方案 测试参数设置时避免列重叠: ```rust let params = BpopfParams { nfreqe: 10, inse: 11, // NSE = 10 + 11 - 1 = 20 inre: 0, // 不更新温度列,避免与主循环列重叠 inpc: 0, // 不更新电子密度列 // ... }; ``` ### Fortran 索引转换注意事项 - `NSE = NFREQE + INSE - 1` (Fortran 起始列索引) - `NRE = NFREQE + INRE` (温度列,1-indexed) - Rust 主循环: `col = nse + ii` (nse 已经是 0-indexed 起始点) - Rust 温度列: `col = (nre - 1) as usize` (需要减 1 转换为 0-indexed) ### 调试技巧 1. 添加 `#[cfg(test)]` 条件编译的调试输出 2. 打印 ESEMAT 矩阵验证是否为单位矩阵 3. 打印每次 B 矩阵更新的行、列、值 4. 检查温度列和电子密度列是否与主循环列重叠 --- ## 15. RAYLEIGH 氦散射截面公式错误 ### 问题 Rayleigh 氦散射截面计算结果不正确。 ### 原因 公式理解错误: - 错误: `5.484e-14 / x2 * (...)` 其中 `x2 = 1/x²` - 正确: `5.484e-14 / (x * x) * (...)` ### 解决方案 ```rust // 错误写法 let x2 = 1.0 / (x * x); raysct.rche[ik] = 5.484e-14 / x2 * (...); // 这等于 5.484e-14 * x² // 正确写法 raysct.rche[ik] = 5.484e-14 / (x * x) * (...); // 这等于 5.484e-14 / x² ``` --- ## 16. ALIFR3 - 大型 COMMON 块函数重构策略 ### 问题 ALIFR3 函数依赖大量 COMMON 块变量(来自 FIXALP, MODELQ, ALIPAR 等)。 ### 解决方案 创建综合的输入结构体,使用生命周期参数引用数据: ```rust /// 输入状态结构体 (使用生命周期引用数据) pub struct Alifr3ModelState<'a> { // 深度相关 (MDEPTH) pub elec: &'a [f64], pub densi: &'a [f64], // ... 其他字段 // 输出累积变量 (可变引用) pub heit: &'a mut [f64], pub hein: &'a mut [f64], // ... 其他字段 } ``` ### 多分支条件处理 Fortran 使用 GOTO 分支,Rust 使用 if-else: ```fortran IF(ILMCOR.NE.3) GO TO 199 ! ... ILMCOR==3 的代码 199 IF(ILASCT.NE.0) GO TO 299 ! ... ILASCT==0 的代码 299 ! ... ILASCT!=0 的代码 ``` ```rust if params.ilmcor == 3 { // ... ILMCOR==3 的代码 return; } if params.ilasct == 0 { // ... ILASCT==0 的代码 return; } // ... ILASCT!=0 的代码 ``` ### 注意事项 - ABST 的计算方式在不同分支中不同: - ILMCOR==3: `ABST = UN/ABSO1(ID)` - ILASCT==0: `ABST = UN/(ABSO1(ID)-ELSCAT(ID))` - ILASCT!=0: `ABST = UN/ABSO1(ID)` - DSFN1 的计算在不同分支也有差异(是否包含 SIGEC 项) --- ## 17. ALIFR6 - COMMON 块结构体命名冲突 ### 问题 添加新的 COMMON 块结构体时,编译器报错 `ambiguous glob re-exports: the name 'Comptn'`。 ### 原因 `Comptn` 结构体同时存在于: - `src/state/config.rs` - Compton 散射角度参数(已有) - `src/state/model.rs` - 新添加的重复定义 两个模块都通过 `pub use *` 重新导出,导致名称冲突。 ### 解决方案 添加新结构体前,先搜索是否已存在: ```bash grep -r "pub struct Comptn" src/state/ ``` 如果已存在,扩展现有结构体而不是创建新的。 --- ## 18. ALIFR6 - 循环中可变变量重新赋值 ### 问题 在循环中重新赋值 `s0p = s0p_new` 报错 `cannot assign twice to immutable variable`。 ### 原因 变量在 if-else 块中首次定义后,无法在循环中重新赋值。 ### 解决方案 ```rust // 错误:不可变变量 let (s0p, dsft1p, dsfn1p) = if ... { ... }; // 正确:声明为可变 let (mut s0p, mut dsft1p, mut dsfn1p) = if ... { ... }; ``` --- ## 19. ALIFR6/ALIFR3 - 大型函数三段式处理模式 ### 模式 ALIFR6 和 ALIFR3 类函数有三个独立处理部分: 1. **第一个深度点 (ID=1)** - 特殊边界条件 - DSFT1M/DSFN1M 初始化为 0 2. **深度循环 (ID=2 到 ND-1)** - 保存前一步值 (DSFTMM, DSFNMM) - 更新当前值 - 计算下一步值 (DSFT1P, DSFN1P) 3. **最深点 (ID=ND)** - IBC 下边界条件处理 - 可能的额外导数 (DSFT1D, DSFN1D) ### IBC 下边界条件 | IBC | 说明 | |-----|------| | 0 | 无特殊处理 | | 1 | 简单 Planck 函数修正 | | 2 | 改进边界条件 | | 3 | 完整边界条件 + DSFT1D/DSFN1D | --- ## 20. ALIFR6 - 三对角 Lambda* 算子 ### 特点 ALIFR6 与 ALIFR3 的主要区别是额外计算三对角算子: - **下对角线**: AREIT, AREIN, AREIP (使用 ALIM1) - **上对角线**: CREIT, CREIN, CREIP (使用 ALIP1) - **对角线**: REIT, REIN, REIP (使用 ALI1) ### IFALI >= 7 额外计算 - HEITP, HEINP, HEIPP (He 相关) - REDTP, REDNP, REDPP (Red 相关) - EHET, EHEN, EHEP (Ehe 相关) - ERET, EREN, EREP (Ere 相关) --- ## 快速检查清单 重构新函数前检查: - [ ] 是否有 INCLUDE 语句 (除 IMPLIC.FOR 外) - [ ] 是否使用 COMMON 块 - [ ] COMMON 块结构体是否已存在于其他模块 (grep 检查) - [ ] 是否有文件 I/O (OPEN, READ, WRITE) - [ ] 数组索引是否需要 -1 调整 - [ ] 循环变量是否可能变负 (用 isize/i32) - [ ] 多项式近似精度是否需要放宽 - [ ] 是否有嵌套 IF 分支遗漏 - [ ] 矩阵列索引是否可能与温度/密度列重叠 - [ ] 公式中 `1/x` 和 `x` 的顺序是否正确 - [ ] 大型 COMMON 块函数是否需要创建综合输入结构体 - [ ] 多分支 GOTO 是否正确转换为 if-else - [ ] 循环中重新赋值的变量是否声明为 mut - [ ] 变量字段在哪个 COMMON 块 → 对应哪个 Rust 结构体 (查 `.FOR` 原文) - [ ] 测试初始化用 `ModelState::new()` 而非 `::default()` - [ ] 二维数组内层维度是否是深度/角度/频率 (不要默认 MDEPTH) - [ ] loop 内部积分变量是否在 loop 外声明以便 break 后使用 --- ## 21. COMMON 块变量所属结构体查找 ### 问题 Fortran COMMON 块变量(如 `IDISK`、`IBC`、`ICHCOO`、`IJORIG`)应放在哪个 Rust 结构体中? ### 解决方案 通过查阅 `.FOR` 中的 COMMON 定义来确认: - `IDISK`、`IBC` → `BASICS.FOR` 的 `COMMON/BASNUM/` → `config.basnum` - `ICHCOO`、`ICOMST`、`ICOMDE` → `BASICS.FOR` 的 `common/compti/` → `config.compti` - `IJORIG` → `BASICS.FOR` 的 `common/comptn/` → `config.comptn.ijorig` - `IWINBL` → `MODELQ.FOR` 的 `COMMON/WINDBL/` → `model.windbl.iwinbl` - `IFZ0` → `BASICS.FOR` 的 `COMMON/CENTRL/` → `config.centrl.ifz0` ```bash # 快速查找变量所在 COMMON 块 grep -i "变量名" tlusty/extracted/*.FOR ``` ### 教训 编译报错 `no field 'xxx' on type 'Accel'` 时,不要盲目添加字段——先查原始 `.FOR` 确认 COMMON 归属。 --- ## 22. 循环外变量对 break 后可见 ### 问题 `ah`、`qq0`、`u0` 在 ALI 循环内定义,循环 `break` 后用于写回结果时编译报 `cannot find value`。 ### 原因 Rust 变量作用域严格,`loop {}` 块内 `let` 声明的变量在块外不可见。 ### 解决方案 ```rust // 正确:循环前提前声明,循环内用 _inner 临时变量 let mut ah = 0.0f64; let mut qq0 = 0.0f64; loop { let mut ah_inner = 0.0f64; let mut qq0_inner = 0.0f64; // 积分计算... if converged { ah = ah_inner; // break 前赋值回外部变量 qq0 = qq0_inner; break; } } model.surfac.flux[ij] = ah; // 此处可用 ``` ### 常见错误(变量被遮蔽) ```rust let mut ah = 0.0f64; loop { let mut ah = 0.0; // ← 遮蔽外层 ah!外层永远是 0 if converged { break; } } // ah 还是 0 ``` --- ## 23. 测试初始化:`ModelState::new()` vs `::default()` ### 问题 测试报错 `index out of bounds: the len is 0 but the index is 0`,即便访问 `model.totrad.rad[0][0]`。 ### 原因 `ModelState` 有 `#[derive(Default)]`,但 `CurRad` 等子结构体**只有 `new()` 方法,没有手动 `Default` 实现**。 `derive(Default)` 对 `Vec` 产生空 Vec,访问任意元素都越界。 ### 解决方案 ```rust // ❌ 错误:CurRad::default() 产生空 Vec let mut model = ModelState::default(); // ✅ 正确:ModelState::new() 调用 CurRad::new(),正确分配数组 let mut model = ModelState::new(); ``` --- ## 24. `extint` 索引:内层是角度维度 ### 问题 `model.totrad.extint[ij][i]` 在 `for i in 0..MDEPTH` 循环中越界(len=6, index=6)。 ### 原因 `extint` 对应 Fortran `EXTINT(MFREQ, MMU)`: - 外层:频率索引(MFREQ) - **内层:角度索引(MMU = 6),不是深度!** ### 解决方案 ```rust // 正确:遍历角度 (MMU=6) use crate::state::constants::MMU; for i in 0..MMU { model.totrad.extint[0][i] = 0.0; } ``` --- ## 25. 常用二维数组维度汇总 | 变量 | 外层维度 | 内层维度 | |------|---------|---------| | `totrad.rad[ij][id]` | 频率 MFREQ | 深度 MDEPTH | | `totrad.extint[ij][i]` | 频率 MFREQ | **角度 MMU** | | `totrad.fak[ij][id]` | 频率 MFREQ | 深度 MDEPTH | | `comptf.delj[iji][id]` | 频率 MFREQ | 深度 MDEPTH | | `expraf.radex[ije][id]` | 显式频率 MFREX | 深度 MDEPTH | | `angles.amu[i]` / `wtmu[i]` | 角度 MMU | — | ### 教训 看到二维数组前,确认两个维度各是什么,不要默认都是 MDEPTH。 --- ## 26. 2026-03-21 新增模块完整性检查 ### SETDRT (密度对温度的导数) - **状态**: ✅ 完整 - 原版 Fortran 调用 RHOEOS 函数 - Rust 版使用泛型函数参数 `rhoeos_fn`,设计更灵活 - 有限差分计算完全一致 ### TAUFR1 (光学深度计算) - **状态**: ✅ 完整 - `ss0` 数组在原版中也计算但未使用,与 Fortran 一致 - `XCON` 常量在原版定义但未使用,已忽略 - 核心逻辑:光学深度计算、参考深度插值、Planck 函数计算 ### TABINT (频率表插值) - **状态**: ⚠️ 部分实现 - **问题**: `interpolate_opacity` 函数中未实际修改 `absopac` 数组 - 代码中有注释 "暂时跳过实际修改" - 二分查找和插值系数计算完整 **待修复**: ```rust // 当前代码 (错误): let opac = rc * (params.freq[ij] / frtab[j - 1]).log10() + absort[j - 1]; let _ = opac; // 计算了但没有写回 // 应该: // 需要设计一个可变引用来修改 absopac ``` ### RYBMAT (Rybicki矩阵) - **状态**: ⚠️ 简化实现 - 原版 ~390 行 → Rust 375 行 - **问题**: 测试失败 `result.rb[0].is_finite()` 返回 NaN **可能原因**: 1. 边界条件 `id=0` 时 `dm[id+1] - dm[id]` 可能为零 2. `abso1` 数组可能包含零值导致除零 3. Hermitian 方法 (`isplin=2`) 被简化 **待修复**: ```rust // 需要添加边界检查 let ddm = (params.dm[id + 1] - params.dm[id]) * HALF; if ddm.abs() < 1e-30 { continue; // 跳过无效深度点 } let dtm = UN / ((params.abso1[id] + params.abso1[id + 1]) * ddm); ``` --- ## 27. 优先级列表注意事项 优先级列表 (`python3 scripts/analyze_fortran.py --priority`) 显示"传递未实现=0"的函数可能有隐藏依赖: - **SETDRT** 调用 RHOEOS(标记为 pending),但通过函数参数传入解决了 - **TABINT** 无外部调用,真正独立 - **TAUFR1** 无外部调用,真正独立 - **RYBMAT** 无外部调用,但依赖大量 COMMON 块变量 **教训**: 优先级列表只检查显式的 SUBROUTINE/FUNCTION 调用,不检查: 1. COMMON 块依赖 2. 通过参数传入的函数指针 3. 隐式的外部函数引用