This commit is contained in:
Asfmq 2026-03-19 22:16:23 +08:00
parent 1e30b7bc63
commit 8e21522d2d
41 changed files with 14497 additions and 26 deletions

1
.gitignore vendored
View File

@ -47,4 +47,5 @@ desktop.ini
*.log
*.tmp
__pycache__
.claude/

View File

@ -9,8 +9,71 @@
## 当前状态
- **已完成重构**: 28 个函数
- **测试通过**: 102 个 (单元测试 + Fortran 对比测试 + 文档测试)
- **已完成重构**: 62 个函数 + 8 个状态模块 + 1 个数据文件 (src/data.rs)
- **测试通过**: 281 个
- **最后更新**: 2026-03-19
- **TLUSTY 纯函数重构基本完成** (剩余 4 个有复杂依赖)
### 已完成模块列表
| 模块 | 说明 |
|------|------|
| angset | Compton 散射角度设置 |
| betah | 压力标高 |
| bkhsgo | K/L 壳层光电离截面 |
| butler | H 碰撞激发速率 |
| carbon | C 光电离截面 |
| ceh12 | H I Lyman-α 碰撞速率 |
| cion | 碰撞电离速率 |
| ckoest | Koester He I 光电离拟合 |
| collhe | He 碰撞速率系数 |
| dielrc | 双电子复合速率 |
| erfcx | 余误差函数 |
| expint | 指数积分 E₁ |
| expo | 安全指数函数 |
| ffcros | 自由-自由截面 (桩) |
| gami | γ 函数 |
| gamsp | 用户自定义展宽参数 (占位) |
| gauleg | Gauss-Legendre 积分 |
| gaunt | Gaunt 因子 |
| getwrd | 文本单词提取 |
| gfree | 氢自由-自由 Gaunt 因子 |
| gntk | Kramers-Gaunt 截面 |
| grcor | 广义相对论修正因子 |
| hephot | He I 光电离截面 |
| hidalg | Hidalgo 光电离数据 |
| indexx | 堆排序索引 |
| interpolate | Lagrange/线性插值 |
| irc | 氢原子碰撞电离速率 |
| laguer | Laguerre 根 |
| locate | 二分查找 |
| minv3 | 3x3 矩阵求逆 |
| pffe | Fe IV-IX 配分函数 |
| pfni | Ni IV-IX 配分函数 |
| pfspec | 特殊元素配分函数 |
| quartc | 四次方程求根 |
| quit | 退出处理 |
| raph | Newton-Raphson 迭代 |
| reiman | Reilman-Manson 光电离 |
| sbfch | CH 束缚-自由截面 |
| sbfhe1 | He I 束缚-自由截面 |
| sbfhmi | H⁻ 束缚-自由截面 |
| sbfhmi_old | H⁻ 束缚-自由截面 (旧版) |
| sbfoh | OH 束缚-自由截面 |
| sffhmi | H⁻ 自由-自由截面 |
| sghe12 | He I <n=2> 截面 |
| spsigk | 特殊光电离截面 |
| stark0 | Stark 轮廓参数 |
| szirc | 电子碰撞电离速率 |
| tiopf | Ti 光电离截面 |
| tridag | 三对角矩阵求解 |
| ubeta | U(β) 函数插值 |
| voigt | Voigt 轮廓 |
| voigte | Voigt 轮廓 (替代) |
| verner | Verner 光电离 (有 COMMON 依赖) |
| wn | 占据概率 (有 COMMON 依赖) |
| xk2dop | Doppler 宽度转换 |
| ylintp | 对数插值 |
## 状态说明
@ -30,22 +93,22 @@
| expo.f | 10 | ✅ | 2026-03-19 | 安全指数函数 |
| quit.f | 10 | ✅ | 2026-03-19 | 退出子程序 |
| ffcros.f | 13 | ✅ | 2026-03-19 | 自由-自由截面 (占位) |
| gamsp.f | 14 | ⬜ | | 展宽因子 (有 COMMON) |
| sgmer1.f | 14 | | | Stark展宽 (有 COMMON) |
| sgmerd.f | 15 | | | Stark展宽 (有 COMMON) |
| gamsp.f | 14 | ✅ | 2026-03-19 | 展宽因子 (有 COMMON) |
| sgmer1.f | 14 | | | Stark展宽 (有 COMMON) |
| sgmerd.f | 15 | | | Stark展宽 (有 COMMON) |
| lagran.f | 16 | ✅ | 2026-03-19 | Lagrange插值 |
| gntk.f | 17 | ✅ | 2026-03-19 | Gaunt因子 |
| raph.f | 17 | ✅ | 2026-03-19 | hedif辅助函数 |
| cross.f | 18 | ⬜ | | 截面计算 (有 COMMON) |
| cross.f | 18 | ✅ | 2026-03-19 | 截面计算 (有 COMMON) |
| eint.f | 18 | ✅ | 2026-03-19 | 指数积分 (含 expinx) |
| sghe12.f | 18 | ✅ | 2026-03-19 | He展宽 |
| yint.f | 18 | ✅ | 2026-03-19 | 二次插值 |
| erfcin.f | 20 | ✅ | 2026-03-19 | 误差函数补 |
| erfcx.f | 20 | ✅ | 2026-03-19 | 缩放误差函数 |
| gfree1.f | 21 | ⬜ | | Gaunt自由 (有 COMMON) |
| sbfhmi_old.f | 22 | ⬜ | | H-截面 |
| gfree1.f | 21 | ✅ | 2026-03-19 | Gaunt自由 (有 COMMON) |
| sbfhmi_old.f | 22 | ✅ | 2026-03-19 | H-截面 |
| tridag.f | 22 | ✅ | 2026-03-19 | 三对角矩阵 |
| timing.f | 24 | ⬜ | | 计时 |
| timing.f | 24 | ❌ | | 计时 (有 I/O) |
| expint.f | 30 | ✅ | 2026-03-19 | 指数积分 |
### 优先级 P1 (中等大小)
@ -58,19 +121,19 @@
| gauleg.f | 34 | ✅ | 2026-03-19 | Gauss-Legendre积分 |
| quartc.f | 35 | ✅ | 2026-03-19 | 四次方程求解 |
| minv3.f | 37 | ✅ | 2026-03-19 | 3x3矩阵求逆 |
| crossd.f | 31 | ⬜ | | |
| wn.f | 41 | ⬜ | | |
| sbfhmi.f | 42 | ⬜ | | H-截面 |
| angset.f | 44 | ⬜ | | |
| crossd.f | 31 | ✅ | 2026-03-19 | 截面计算+双电子复合 (有 COMMON) |
| wn.f | 41 | ✅ | 2026-03-19 | 占据概率 (有 COMMON) |
| sbfhmi.f | 42 | ✅ | 2026-03-19 | H-截面 |
| angset.f | 44 | ✅ | 2026-03-19 | 角度设置 (有 COMMON) |
| gami.f | 45 | ✅ | 2026-03-19 | 微扰展宽 |
| gaunt.f | 45 | ⬜ | | Gaunt因子 |
| ubeta.f | 40 | ⬜ | | |
| rayini.f | 42 | ⬜ | | |
| gaunt.f | 45 | ✅ | 2026-03-19 | Gaunt因子 |
| ubeta.f | 40 | ✅ | 2026-03-19 | U(β) 函数 |
| rayini.f | 42 | ❌ | | 射线初始化 (有 I/O) |
| indexx.f | 45 | ✅ | 2026-03-19 | 索引排序 |
| laguer.f | 59 | ✅ | 2026-03-19 | Laguerre多项式求根 |
| sbfhe1.f | 157 | ⬜ | | He截面 |
| hephot.f | 163 | ⬜ | | He光电离 |
| verner.f | 237 | ⬜ | | Verner截面 |
| sbfhe1.f | 157 | ✅ | 2026-03-19 | He截面 (有 COMMON) |
| hephot.f | 163 | ✅ | 2026-03-19 | He光电离 |
| verner.f | 237 | ✅ | 2026-03-19 | Verner截面 (有 COMMON) |
| voigt.f | 64 | ✅ | 2026-03-19 | Voigt线型 |
| voigte.f | 92 | ✅ | 2026-03-19 | Voigt线型 |
| locate.f | 25 | ✅ | 2026-03-19 | 二分查找 |
@ -112,8 +175,30 @@
- 重构 xk2dop.f → src/math/xk2dop.rs
- 重构 minv3.f → src/math/minv3.rs
- 重构 laguer.f → src/math/laguer.rs
- 重构 collhe.f → src/math/collhe.rs (He 碰撞速率, 929 元素大数组)
- 重构 butler.f → src/math/butler.rs (H 碰撞激发)
- 重构 cion.f → src/math/cion.rs (碰撞电离)
- 重构 irc.f → src/math/irc.rs (H 碰撞电离速率)
- 重构 getwrd.f → src/math/getwrd.rs (文本解析)
- 重构 spsigk.f → src/math/spsigk.rs (特殊光电离)
- 重构 tiopf.f → src/math/tiopf.rs (Ti 光电离)
- 重构 sbfhmi_old.f → src/math/sbfhmi_old.rs (H- 束缚-自由旧版)
- 重构 bkhsgo.f → src/math/bkhsgo.rs (K/L 壳层光电离)
- 重构 carbon.f → src/math/carbon.rs (C 光电离)
- 重构 hephot.f → src/math/hephot.rs (He 光电离)
- 重构 hidalg.f → src/math/hidalg.rs (Hidalgo 光电离)
- 重构 reiman.f → src/math/reiman.rs (Reilman-Manson 光电离)
- 重构 sbfhmi.f → src/math/sbfhmi.rs (H- 束缚-自由)
- 重构 sffhmi.f → src/math/sffhmi.rs (H- 自由-自由)
- 重构 grcor.f → src/math/grcor.rs (广义相对论修正)
- 重构 gaunt.f → src/math/gaunt.rs (Gaunt 因子)
- 重构 stark0.f → src/math/stark0.rs (Stark 轮廓)
- 重构 szirc.f → src/math/szirc.rs (电子碰撞电离)
- 重构 ubeta.f → src/math/ubeta.rs (U(β) 函数)
- 重构 dielrc.f → src/math/dielrc.rs (双电子复合速率, 18 个大型数组)
- 重构 pfni.f → src/math/pfni.rs (Ni IV-IX 配分函数, 12 个数组)
- 创建 Fortran 对比测试框架 (tests/fortran_ref/, tests/fortran_comparison.rs)
- **102 个测试通过** (75 单元测试 + 12 Fortran 对比测试 + 4 文档测试)
- **224 个测试通过** (208 单元测试 + 12 Fortran 对比测试 + 4 文档测试)
**规范:**
- 代码注释使用中文
@ -125,3 +210,136 @@
- Fortran 1-indexed 数组转 Rust 0-indexed 时要特别注意边界条件
- `erfcin` 中 `XL=-LOG(X)` 是 `-ln(X)`,不是 `ln(-X)`
- `ylintp` 在 0-indexed 中 jl=0 是有效索引,不需要调整
- `collhe` 中 Clenshaw 求和的 `ir` 和 `jj` 必须用有符号整数 (`isize`),否则递减时会溢出
- `collhe` 的索引计算 `((iu+1)^2 - 3*(iu+1) + 4)/2` 在 Rust 中需用 `i32` 避免中间结果溢出
### 2026-03-19 (续)
**修复:**
- 修复 `pffe.rs` 中的数组引用错误:`P6A/P6B` 应使用 `PFFE_P6A/PFFE_P6B`
**待重构的纯函数分析:**
- `SBFCH` (278行): CH 束缚-自由截面,有 1575 个数据点
- `SBFOH` (327行): OH 束缚-自由截面,有 1950 个数据点
- `TIMING` (24行): 有 I/O 依赖,不适合纯函数
- `CIA_*` (89-90行): 有文件 I/O需要特别处理
**当前模块统计:**
- 49 个 Rust 模块 (不含 mod.rs)
- 218 个单元测试
- 12 个 Fortran 对比测试
- 4 个文档测试
**新增模块:**
- 重构 sbfch.f → src/math/sbfch.rs (CH 束缚-自由截面, 1575 个数据点)
- 重构 sbfoh.f → src/math/sbfoh.rs (OH 束缚-自由截面, 1950 个数据点)
**脚本改进:**
- 修改 `extract_fortran_data.py`: 所有变量都添加文件名前缀 (如 `SBFCH_C1`, `DIELRC_ADI`)
- 避免不同文件中同名变量冲突
### 2026-03-19 (进度总结)
**当前状态:**
- **49 个 Rust 模块**已完成
- **234 个测试通过** (218 单元测试 + 12 Fortran 对比测试 + 4 文档测试)
**纯函数重构完成度分析:**
TLUSTY 原有 195 个无 COMMON 依赖的纯函数,其中大部分已经重构。剩余的函数可分为两类:
1. **有 COMMON/IO 依赖的函数** (暂时不重构):
- `gamsp.f`, `sgmer1.f`, `sgmerd.f` - Stark 展宽
- `cross.f`, `crossd.f` - 截面计算
- `gfree1.f` - Gaunt 自由
- `wn.f` - 占据概率
- `angset.f` - 角度设置
- `rayini.f` - 射线初始化 (有文件 I/O)
- `sbfhe1.f` - He 截面
- `verner.f`, `vern16/18/20/26.f` - Verner 截面
- `timing.f` - 计时 (有 I/O)
- `CIA_*` - 碰撞诱导吸收 (有文件 I/O)
2. **可能遗漏的纯函数**:
- 需要进一步检查 `gfree0.f`, `gfreed.f` 等函数
**下一步计划:**
1. 检查 `gfree0.f`, `gfreed.f` 是否为纯函数
2. 如果是纯函数,继续重构
3. 考虑 SYNSPEC 纯函数的重构
**图例说明:**
- ✅ 已完成
- ❌ 有 COMMON/IO 依赖 (暂不处理)
- ⬜ 待处理
### 2026-03-19 (状态模块重构)
**新增 state 模块** - 将 Fortran COMMON 块转换为 Rust struct
| 文件 | 对应 Fortran | 说明 |
|------|-------------|------|
| src/state/mod.rs | - | 模块入口 |
| src/state/constants.rs | BASICS.FOR | 物理常数和维度参数 |
| src/state/config.rs | BASICS.FOR | 运行时配置 (BASNUM, INPPAR 等) |
| src/state/atomic.rs | ATOMIC.FOR | 原子/离子/能级数据 (ATOPAR, IONPAR 等) |
| src/state/model.rs | MODELQ.FOR | 大气模型状态 (MODPAR, LEVPOP 等) |
| src/state/arrays.rs | ARRAY1.FOR | 大型计算数组 |
**COMMON 块映射:**
| Fortran COMMON | Rust struct | 文件 |
|----------------|-------------|------|
| /BASNUM/ | BasNum | config.rs |
| /INPPAR/ | InpPar | config.rs |
| /MATKEY/ | MatKey | config.rs |
| /RUNKEY/ | RunKey | config.rs |
| /CONKEY/ | ConKey | config.rs |
| /OPCPAR/ | OpcPar | config.rs |
| /ANGLES/ | Angles | config.rs |
| /ATOPAR/ | AtoPar | atomic.rs |
| /IONPAR/ | IonPar | atomic.rs |
| /LEVPAR/ | LevPar | atomic.rs |
| /TRAPAR/ | TraPar | atomic.rs |
| /PHOSET/ | PhoSet | atomic.rs |
| /VOIPAR/ | VoiPar | atomic.rs |
| /MODPAR/ | ModPar | model.rs |
| /LEVPOP/ | LevPop | model.rs |
| /GFFPAR/ | GffPar | model.rs |
| /TOTRAD/ | TotRad | model.rs |
| /CURRAD/ | CurRad | model.rs |
| /FRQALL/ | FrqAll | model.rs |
| 无标签 COMMON | MainArrays | arrays.rs |
| /EXPRAD/ | ExpRad | arrays.rs |
| /BPOCOM/ | BpoCom | arrays.rs |
**综合结构:**
- `TlustyConfig` - 综合配置 (包含所有控制参数)
- `AtomicData` - 综合原子数据
- `ModelState` - 综合模型状态
- `ComputeArrays` - 综合计算数组
**测试更新:**
- 255 个测试通过 (239 单元测试 + 12 Fortran 对比测试 + 4 文档测试)
### 2026-03-19 (完成 .FOR 文件重构)
**完成所有 TLUSTY .FOR 文件重构:**
| 文件 | Rust 模块 | COMMON 块 |
|------|-----------|-----------|
| ITERAT.FOR | iterat.rs | LAMBDA, CHNMAT, ACCEL, ACCLP, ACCLT, CHNAD |
| ALIPAR.FOR | alipar.rs | FIXALP (ALI 数组) |
| ODFPAR.FOR | odfpar.rs | ODFION, ODFCTR, ODFFRQ, ODFMOD, ODFSTK, SPLCOM, OPALIM, OPLIMT, LEVCOM |
**新增常量:**
- MLEVE3, MLVEX3, MTRAN3 (对角/三对角预处理)
- MCROSS, MBF (截面和束缚-自由跃迁)
**内存估算:**
- SplCom (Fe 线数据): ~90 MB
- LevCom (Kurucz 能级): ~0.5 MB
- FixAlp (ALI 数组): ~数百 MB (取决于 MLVEXP)
**所有 TLUSTY .FOR 文件已重构完成!**

403
docs/sp_fortran.md Normal file
View File

@ -0,0 +1,403 @@
---
## TLUSTY208.F 拆分工作 (2026-03-18)
### 任务:将单文件拆分为多个独立模块
**执行流程:**
```bash
cd /home/fmq/program/tlusty/tl208-s54/rust
python3 extract_fortran.py tlusty/tlusty208.f tlusty/extracted/
cp tlusty/*.FOR tlusty/extracted/
```
### 提取结果
| 项目 | 数量 |
|------|------|
| 程序单元 | **304** |
| 子程序 (SUBROUTINE) | 269 |
| 函数 (FUNCTION) | 32 |
| 主程序 (PROGRAM) | 1 |
| BLOCK DATA | **2** (含1个无名) |
| 无 COMMON 依赖的纯函数 | 195 |
> 注意: 2026-03-19 修复了无名 BLOCK DATA 提取问题,单元数从 303 增加到 304
### Include 文件
```
tlusty/extracted/*.FOR
├── IMPLIC.FOR # 隐式类型声明
├── BASICS.FOR # 基本参数和物理常数
├── ITERAT.FOR # 迭代控制参数
├── ALIPAR.FOR # ALI 参数
├── ATOMIC.FOR # 原子数据
├── MODELQ.FOR # 模型物理量
├── ODFPAR.FOR # ODF 参数
└── ARRAY1.FOR # 主工作数组
```
### 编译配置
**关键编译选项**:
```makefile
FFLAGS = -O3 -fno-automatic -mcmodel=large
```
- `-mcmodel=large`: 支持 >2GB 地址空间
- `-fno-automatic`: 所有变量默认为静态存储兼容旧Fortran代码
- **不要使用** `-ffixed-line-length-none`: 会将第73-80列的注释区当作代码解析
### 编译验证
```bash
cd tlusty/extracted && make clean && make
# 生成: tlusty_extracted (1,940,488 bytes)
# 直接编译
gfortran -fno-automatic -mcmodel=large -O3 -o tlusty.exe tlusty208.f
# 生成: tlusty.exe (1,997,416 bytes)
```
**结论**: 功能等价,拆分编译文件更小 (-2.9%)
### 拆分编译 vs 直接编译对比
| 方面 | 直接编译 | 拆分编译 |
|------|---------|---------|
| 文件大小 | 1,997,416 B | 1,940,488 B |
| 功能 | 相同 | 相同 |
| 增量编译 | ❌ | ✅ |
| 代码定位 | 困难 | 简单 |
### 无 COMMON 依赖的纯函数 (198个)
可独立测试和重构的单元:
```
ACCEL2, ALIFR1, ALIFR3, ALIFR6, ALIFRK, ALISK1, ALISK2, ALIST1, ALIST2,
ANGSET, BETAH, BHE, BKHSGO, BPOP, BPOPE, BPOPF, BPOPT, BRE, BREZ,
BRTE, BRTEZ, BUTLER, CARBON, CEH12, CHANGE, CHCKSE, CHEAV, CHEAVJ,
CIA_H2H, CIA_H2H2, CIA_H2HE, CIA_HHE, CION, CKOEST, COLH, COLHE,
COLLHE, CONCOR, CORRWM, CROSS, CROSSD, CSPEC, DIELRC, DIETOT, DIVSTR,
DMEVAL, DOPGAM, DWNFR, DWNFR0, DWNFR1, EINT, EMAT, ENTENE, ERFCIN,
ERFCX, EXPINT, EXPINX, EXPO, FFCROS, GAMI, GAMSP, GAULEG, GAUNT,
GETWRD, GFREE0, GFREE1, GFREED, GNTK, GRCOR, GREYD, GRIDP, H2MINUS,
HEPHOT, HIDALG, IJALI2, IJALIS, INCLDY, INDEXX, INIFRS, INILAM, INTERP,
INTHYD, INTLEM, INTXEN, IRC, LAGRAN, LAGUER, LEMINI, LEVGRP, LEVSET,
LEVSOL, LINEQS, LINSEL, LINSET, LINSPL, LOCATE, LTEGR, LUCY, LYMLIN,
MATGEN, MATINV, MEANOP, MEANOPT, MINV3, NEWPOP, NSTOUT, ODF1, ODFFR,
ODFHST, ODFHYD, ODFHYS, ODFMER, OPACFL, OPADD0, OPAHST, OPAINI, OPCTAB,
OSCCOR, OUTPUT, PFCNO, PFFE, PFHEAV, PFNI, PFSPEC, PRCHAN, PRD, PRDINI,
PRINC, PRNT, PROFSP, PSOLVE, PZERT, QUARTC, QUIT, RADPRE, RAPH, RATES1,
RATMAL, RATMAT, RATSP1, RAYINI, RAYSET, RDATAX, READBF, RECHCK, REFLEV,
REIMAN, RHOEOS, RHONEN, ROSSOP, ROSSTD, RTEDF2, RTEFE2, RTESOL, RTE_SC,
SABOLF, SBFCH, SBFHE1, SBFHMI, SBFHMI_OLD, SBFOH, SFFHMI, SFFHMI_ADD,
SGHE12, SGMER0, SGMER1, SGMERD, SIGAVE, SIGK, SIGMAR, SPSIGK, SRTFRQ,
STARK0, STARKA, SWITCH, SZIRC, TDPINI, TIMING, TIOPF, TLUSTY, TRAINI,
TRIDAG, UBETA, VERN16, VERN18, VERN20, VERN26, VERNER, VISINI, VOIGT,
VOIGTE, WN, WNSTOR, XENINI, XK2DOP, YINT, YLINTP, ZMRHO
```
---
## SYNSPEC54.F 拆分工作 (2026-03-18)
### 任务:将单文件拆分为多个独立模块
**执行流程:**
#### 1. 创建提取脚本 `extract_fortran.py`
```python
# 核心功能:
# - 正则匹配 SUBROUTINE/FUNCTION/PROGRAM/BLOCK DATA
# - 查找对应的 END 语句确定边界
# - 提取到独立 .f 文件
# - 分析 COMMON 块依赖
# - 生成 Makefile
```
#### 2. 运行提取
```bash
cd /home/fmq/program/tlusty/tl208-s54/rust
python3 extract_fortran.py synspec/synspec54.f synspec/extracted/
cp synspec/*.FOR synspec/extracted/
```
#### 3. 提取结果
| 项目 | 数量 |
|------|------|
| 程序单元 | 168 |
| 子程序 (SUBROUTINE) | 134 |
| 函数 (FUNCTION) | 33 |
| 主程序 (PROGRAM) | 1 |
| 总代码行数 | 23,050 |
| 无 COMMON 依赖的纯函数 | 93 |
#### 4. 编译配置
**关键编译选项** (解决大型 COMMON 数组链接问题):
```makefile
FFLAGS = -O3 -fno-automatic -mcmodel=large
```
- `-mcmodel=large`: 支持 >2GB 地址空间
- `-fno-automatic`: 所有变量默认为静态存储兼容旧Fortran代码
- **不要使用** `-ffixed-line-length-none`: 会将第73-80列的注释区当作代码解析
#### 5. 编译验证
```bash
cd synspec/extracted && make clean && make
# 生成: synspec_extracted (1,000,408 bytes)
```
**对比原始编译:**
```bash
gfortran -O3 -fno-automatic -mcmodel=large -o synspec_direct.exe synspec54.f
# 生成: synspec_direct.exe (1,044,928 bytes)
```
**结论**: 功能完全等价,拆分编译更小 (-4.3%)
### 生成的文件结构
```
synspec/extracted/
├── synspec.f # 主程序 (174行)
├── start.f # 子程序 (107行)
├── sbfhmi.f # H⁻ 光电离截面函数 (42行)
├── expint.f # 指数积分函数 (18行)
├── ... # 共168个 .f 文件
├── PARAMS.FOR # 参数定义 (include)
├── MODELP.FOR # 模型参数 (include)
├── LINDAT.FOR # 谱线数据 (include)
├── SYNTHP.FOR # 合成谱参数 (include)
├── WINCOM.FOR # 窗口通信 (include)
├── Makefile # 自动构建
├── _SUMMARY.txt # 提取摘要
├── _COMMON_ANALYSIS.txt # COMMON 依赖分析
└── _PURE_UNITS.txt # 纯函数列表
```
### COMMON 块分析结果
**有 COMMON 依赖的单元**: 75 个
**唯一 COMMON 块**: 68 个
主要 COMMON 块:
- `BLAPAR`, `LIMPAR` - 谱线参数
- `EMFLUX` - 辐射流
- `RTEOPA` - 辐射转移不透明度
- `NLTPOP` - 非LTE布居数
- `lasers` - 激光数据处理
### 拆分编译 vs 直接编译对比
| 方面 | 直接编译 | 拆分编译 |
|------|---------|---------|
| 文件大小 | 1,044,928 B | 1,000,408 B |
| 功能 | 相同 | 相同 |
| 增量编译 | ❌ | ✅ |
| 代码定位 | 困难 | 简单 |
| 模块化重构 | 困难 | 容易 |
### 提取脚本位置
```
extract_fortran.py
```
### 推荐用法
```bash
# 开发/调试/重构
cd synspec/extracted && make
# 生产环境/快速编译
cd synspec && gfortran -O3 -fno-automatic -mcmodel=large -o synspec.exe synspec54.f
```
### 无 COMMON 依赖的纯函数 (93个)
可独立测试和重构的函数:
```
CARBON, CHANGE, CHCKAB, CIA_H2H, CIA_H2H2, CIA_H2HE, CIA_HHE,
COUNT_WORDS, DENSIT, DIVHE2, DIVSTR, DWNFR0, DWNFR1, EPS,
EXOPF, EXPINT, EXTPRF, FEAUTR, GAMHE, GAUNT, GETWRD, GFREE,
GNTK, GRIEM, H2MINUS, H2OPF, HE2SET, HE2SEW, HEPHOT, HESET,
HIDALG, HYDINI, HYDTAB, HYLSET, HYLSEW, INIBLM, INKUR, INPBF,
INTERP, INTHYD, INTRP, INTXEN, IRWPF, ISPEC, LEVSOL, LINEQS,
LOCATE, LYMLIN, MATINV, MOLOP, MPARTF, OPADD, PARTDV, PARTF,
PFFE, PFHEAV, PFNI, PFSPEC, PHTX, QUIT, RATMAT, READBF,
REIMAN, SABOLF, SBFCH, SBFHE1, SBFHMI, SBFHMI_OLD, SBFOH,
SETRAY, SFFHMI, SFFHMI_OLD, SGHE12, SGMERG, SPSIGK, STARK0,
STARKA, STARKIR, STATE0, SYNSPEC, TINT, TRIDAG, VELSET,
VOIGTE, VOPF, WGTJH1, WN, WNSTOR, WTOT, XENINI, XK2DOP, YINT, YLINTP
```
---
## 功能验证测试 (2026-03-19)
### 测试环境变量
```bash
export TL208=/home/fmq/program/tlusty
export TLUSTY=$TL208/tl208-s54
export LINELIST=$TL208/linelist
export IRON=$TL208/irondata
export OPTABLES=$TL208/optables
```
### 测试目录
```
tests/tlusty/hhe/ # H-He 模型测试用例
├── hhe35lt.5 # LTE 模型输入
├── hhe35nc.5 # NLTE continua 模型输入
├── hhe35nl.5 # NLTE with lines 模型输入
└── hhe35*.7,9,14 # 预期输出文件
```
### 测试流程
```bash
cd /home/fmq/program/tlusty/tl208-s54/rust/tests/tlusty
# 创建测试目录
mkdir -p test_extracted
cd test_extracted
cp ../hhe/*.5 .
ln -sf $TLUSTY/data data
# 设置环境变量
export TL208=/home/fmq/program/tlusty
export TLUSTY=$TL208/tl208-s54
export LINELIST=$TL208/linelist
export IRON=$TL208/irondata
export OPTABLES=$TL208/optables
# 可执行文件路径
EXE=../../tlusty/extracted/build/tlusty_extracted
# 测试1: LTE 模型 (从零开始)
$EXE < hhe35lt.5 > hhe35lt.6
cp fort.7 hhe35lt.7; cp fort.9 hhe35lt.9; cp fort.14 hhe35lt.14
# 测试2: NLTE continua (使用 LTE 作为初始模型)
cp hhe35lt.7 fort.8
$EXE < hhe35nc.5 > hhe35nc.6
cp fort.7 hhe35nc.7; cp fort.9 hhe35nc.9; cp fort.14 hhe35nc.14
# 测试3: NLTE with lines (使用 NC 作为初始模型)
cp hhe35nc.7 fort.8
$EXE < hhe35nl.5 > hhe35nl.6
cp fort.7 hhe35nl.7; cp fort.9 hhe35nl.9; cp fort.14 hhe35nl.14
# 验证: 与原始结果比较
diff hhe35lt.7 ../hhe/hhe35lt.7
diff hhe35nc.7 ../hhe/hhe35nc.7
diff hhe35nl.7 ../hhe/hhe35nl.7
```
### 测试结果
| 测试用例 | 拆分编译 | 直接编译 | 原始结果 |
|----------|----------|----------|----------|
| hhe35lt (LTE) | ✓ 通过 | ✓ 通过 | ✓ 相同 |
| hhe35nc (NLTE continua) | ✓ 通过 | ✓ 通过 | ✓ 相同 |
| hhe35nl (NLTE lines) | ✓ 通过 | ✓ 通过 | ✓ 相同 |
**MD5 校验和 (hhe35nl.7)**:
```
01f3169947ca24bf1c989619b83ae8f2
```
**结论**: 拆分编译与直接编译输出完全相同,功能验证通过
---
## SYNSPEC54 功能验证测试 (2026-03-19)
### 测试目录
```
tests/synspec/hhe/
├── hhe35nl.5 # 输入文件
├── hhe35nl.7 # 模型大气 (来自 TLUSTY 测试)
├── fort.55.con # 附加输入文件
├── data # 数据目录路径文件 (内容: $TLUSTY/data)
└── results/ # 预期输出
├── hhe35nl.spec # 合成光谱
├── hhe35nl.cont # 连续谱
└── hhe35nl.iden # 谱线标识
```
### 测试流程
```bash
cd /home/fmq/program/tlusty/tl208-s54/rust/tests/synspec/hhe
# 设置环境变量
export TL208=/home/fmq/program/tlusty
export TLUSTY=$TL208/tl208-s54
export LINELIST=$TL208/linelist
export IRON=$TL208/irondata
export OPTABLES=$TL208/optables
# 准备输入文件
cp hhe35nl.7 fort.8
ln -sf fort.55.con fort.55
ln -sf $TLUSTY/data/gfATO.dat fort.19
# 关键: data 必须是符号链接指向数据目录
rm -f data
ln -sf $TLUSTY/data data
# 可执行文件路径
EXE_ORIG=$TLUSTY/synspec/synspec.exe
EXE_DIRECT=../../synspec/synspec_direct.exe
EXE_EXTRACTED=../../synspec/extracted/build/synspec_extracted
# 测试原始版本
$EXE_ORIG < hhe35nl.5 > hhe35nl_orig.log
cp fort.7 hhe35nl_orig.spec; cp fort.17 hhe35nl_orig.cont
# 测试直接编译版本
rm -f fort.7 fort.17 fort.12
$EXE_DIRECT < hhe35nl.5 > hhe35nl_direct.log
cp fort.7 hhe35nl_direct.spec; cp fort.17 hhe35nl_direct.cont
# 测试拆分编译版本
rm -f fort.7 fort.17 fort.12
$EXE_EXTRACTED < hhe35nl.5 > hhe35nl_extracted.log
cp fort.7 hhe35nl_extracted.spec; cp fort.17 hhe35nl_extracted.cont
# 验证
diff hhe35nl_orig.spec hhe35nl_direct.spec
diff hhe35nl_orig.spec hhe35nl_extracted.spec
# 恢复 data 文件
rm -f data
echo "/home/fmq/program/tlusty/tl208-s54/data" > data
```
### 测试结果
| 测试用例 | 原始程序 | 直接编译 | 拆分编译 |
|----------|----------|----------|----------|
| hhe35nl (NLTE lines) | ✓ 通过 | ✓ 通过 | ✓ 相同 |
**MD5 校验和 (hhe35nl.spec)**:
```
7925533b21b16d6bcdfff40e626cab83
```
**注意事项**:
- `data` 文件/符号链接必须正确设置,否则报错 `Cannot open file './data/h1.dat': Not a directory`
- 拆分编译程序与原始程序输出完全相同,功能验证通过

View File

@ -0,0 +1,438 @@
#!/usr/bin/env python3
"""
Fortran 源文件提取数组数据生成 Rust data.rs
用法:
python3 scripts/extract_fortran_data.py
输出: src/data.rs
"""
import re
from pathlib import Path
def parse_fortran_file(filepath: Path, global_params: dict = None) -> list[dict]:
"""解析单个 Fortran 文件中的数组
Args:
filepath: Fortran 文件路径
global_params: include 文件中提取的全局参数表
"""
if global_params is None:
global_params = {}
with open(filepath, 'r') as f:
content = f.read()
arrays = {}
# 0. 预处理
# 先清理每行的 Fortran 注释 (! 后面的内容)
# 同时移除 Fortran 77 风格的注释行 (以 C 或 * 开头)
lines = content.split('\n')
cleaned_lines = []
for line in lines:
# Fortran 77 注释行 (第1列是 C, c, *, 或完全空行)
if len(line) > 0 and line[0] in 'Cc*':
continue # 跳过整行注释
# Fortran 90 行内注释
if '!' in line:
line = line.split('!')[0]
cleaned_lines.append(line)
# 再合并 Fortran 续行 (第6列是 *, +, &, 数字, 或字母)
# Fortran 允许使用字母作为续行标记 (A, B, C, ... 用于超过9个续行)
merged_lines = []
for line in cleaned_lines:
# 检查是否是续行 (第6列是 *, +, &, 数字, 或非空格字符)
if len(line) >= 6 and line[5] != ' ' and line[5] not in '\n\r\t':
# 续行: 追加到上一行 (去掉前6列)
if merged_lines:
merged_lines[-1] += ' ' + line[6:].strip()
else:
merged_lines.append(line)
content = '\n'.join(merged_lines)
# 1. 首先解析所有 parameter 语句,建立局部常量表
# 合并全局参数和局部参数
param_table = dict(global_params) # 复制全局参数
param_pattern = r'parameter\s*\(([^)]+)\)'
for match in re.finditer(param_pattern, content, re.IGNORECASE):
params_str = match.group(1)
for param in params_str.split(','):
param = param.strip()
if '=' in param:
name, val = param.split('=', 1)
name = name.strip().lower()
val = val.strip().lower()
# 尝试解析为整数或浮点数
try:
# 先尝试整数
if '.' not in val and 'e' not in val and 'd' not in val:
param_table[name] = int(val)
else:
# 浮点数,转换为整数(用于数组维度)
val = val.replace('d', 'e')
param_table[name] = int(float(val))
except ValueError:
pass
# 2. 解析 dimension 语句
# dimension p4a(22), p4b(10,28), adi(nni), ...
# 注意: 使用 [ \t] 代替 \s 避免跨行匹配
dim_pattern = r'dimension[ \t]+([a-z0-9_,() \t]+)'
for match in re.finditer(dim_pattern, content, re.IGNORECASE):
dim_str = match.group(1)
# 清理 dim_str - 移除可能包含的下一个关键字
for keyword in ['\nreal', '\ninteger', '\ncomplex', '\nlogical', '\ncharacter',
'\ndimension', '\ndata', '\nparameter', '\nequivalence']:
if keyword in dim_str.lower():
dim_str = dim_str[:dim_str.lower().find(keyword)]
break
arr_pattern = r'(\w+)\s*\(([^)]+)\)'
for arr_match in re.finditer(arr_pattern, dim_str):
name = arr_match.group(1).lower()
dims_str = arr_match.group(2)
# 解析维度,支持常量和 parameter 变量
dims = []
valid = True
for d in dims_str.split(','):
d = d.strip().lower()
if d in param_table:
dims.append(param_table[d])
else:
try:
dims.append(int(d))
except ValueError:
valid = False
break
if valid and dims:
arrays[name] = {"name": name, "dims": dims, "data": None, "source": filepath.name}
# 2.5 解析类型声明中的数组
# REAL frac(MR), INTEGER arr(10), REAL*4 arr(10), CHARACTER*10 str(5), etc.
# 注意: 使用 [ \t] 代替 \s 避免跨行匹配
type_decl_pattern = r'(real(?:\*[\d]+)?|integer(?:\*[\d]+)?|complex(?:\*[\d]+)?|logical(?:\*[\d]+)?|character(?:\*[\d]+)?)[ \t]+([a-z0-9_,() \t]+)'
for match in re.finditer(type_decl_pattern, content, re.IGNORECASE):
decl_str = match.group(2)
# 清理 decl_str - 移除可能包含的下一个类型声明
for keyword in ['\nreal', '\ninteger', '\ncomplex', '\nlogical', '\ncharacter',
'\ndimension', '\ndata', '\nparameter', '\nequivalence']:
if keyword in decl_str.lower():
decl_str = decl_str[:decl_str.lower().find(keyword)]
break
# 匹配变量名(维度)
arr_pattern = r'(\w+)\s*\(([^)]+)\)'
for arr_match in re.finditer(arr_pattern, decl_str):
name = arr_match.group(1).lower()
if name in arrays:
continue # 已有定义
dims_str = arr_match.group(2)
# 解析维度
dims = []
valid = True
for d in dims_str.split(','):
d = d.strip().lower()
if d in param_table:
dims.append(param_table[d])
else:
try:
dims.append(int(d))
except ValueError:
valid = False
break
if valid and dims:
arrays[name] = {"name": name, "dims": dims, "data": None, "source": filepath.name}
# 3. 解析 data 语句 (支持多行)
# data name / val1, val2, ... /
data_pattern = r'data\s+(\w+)\s*/\s*([^/]+)\s*/'
for match in re.finditer(data_pattern, content, re.IGNORECASE | re.DOTALL):
name = match.group(1).lower()
data_str = match.group(2)
if name not in arrays:
arrays[name] = {"name": name, "dims": [], "data": None, "source": filepath.name}
values = parse_data_values(data_str)
arrays[name]["data"] = values
# 4. 处理 parameter 语句中的标量常量(用于导出)
for match in re.finditer(param_pattern, content, re.IGNORECASE):
params_str = match.group(1)
for param in params_str.split(','):
param = param.strip()
if '=' in param:
name, val = param.split('=', 1)
name = name.strip().lower()
val = val.strip().lower()
if name not in arrays:
try:
val = val.replace('d', 'e')
arrays[name] = {"name": name, "dims": [], "data": [float(val)], "source": filepath.name, "is_param": True}
except ValueError:
pass
return list(arrays.values())
def parse_data_values(data_str: str) -> list[float]:
"""解析 DATA 语句中的数值,处理重复语法如 7*1.387"""
values = []
# 清理
lines = data_str.split('\n')
cleaned_lines = []
for line in lines:
line = line.strip()
if line.startswith('*'):
line = line[1:].strip()
cleaned_lines.append(line)
data_str = ' '.join(cleaned_lines)
for part in data_str.split(','):
part = part.strip()
if not part:
continue
# 移除末尾的 / (DATA 语句结束符)
part = part.rstrip('/')
# 处理重复语法: "7*1.387"
if '*' in part and not part.startswith('-'):
match = re.match(r'(\d+)\s*\*\s*(-?[\d.]+)', part)
if match:
count = int(match.group(1))
val = float(match.group(2))
values.extend([val] * count)
continue
try:
# 处理 Fortran 科学计数法
# 处理 "- 14.2" 这种中间有空格的负数
val = part.replace('d', 'e').replace('D', 'e')
# 移除负号和数字之间的空格
val = re.sub(r'-\s+(\d)', r'-\1', val)
# 移除科学计数法中的多余空格 (如 "1.48 e-2" -> "1.48e-2")
val = re.sub(r'(\d)\s+([eEdD])', r'\1\2', val)
values.append(float(val))
except ValueError:
pass
return values
def generate_data_rs(all_arrays: dict[str, list[dict]]) -> str:
"""生成 src/data.rs 内容"""
lines = []
lines.append("//! Fortran 数据数组自动导出")
lines.append("//!")
lines.append("//! 由 extract_fortran_data.py 自动生成,请勿手动修改")
lines.append("")
# 收集已使用的名称,避免重复
used_names = set()
# 按源文件分组
for source, arrays in sorted(all_arrays.items()):
# 过滤有数据的数组
valid_arrays = [a for a in arrays if a.get("data") and len(a["data"]) > 0]
if not valid_arrays:
continue
lines.append(f"// ========== {source} ==========")
lines.append("")
for arr in valid_arrays:
base_name = arr["name"].upper()
# 清理名称中的特殊字符
base_name = re.sub(r'[^A-Z0-9_]', '', base_name)
# 所有变量都添加文件名前缀,避免命名冲突
prefix = Path(source).stem.upper()[:8] # 取文件名前8个字符
name = f"{prefix}_{base_name}"
# 如果加上前缀后仍有冲突,添加序号
if name in used_names:
counter = 1
while f"{name}_{counter}" in used_names:
counter += 1
name = f"{name}_{counter}"
used_names.add(name)
dims = arr["dims"]
data = arr["data"]
if not data:
continue
total_size = len(data)
# 跳过单值 parameter
if arr.get("is_param") and total_size == 1:
lines.append(f"/// {arr['name']} (from {source})")
lines.append(f"pub const {name}: f64 = {data[0]};")
lines.append("")
continue
if len(dims) == 0:
# 未知维度,用 Vec 格式输出以便检查
lines.append(f"/// {arr['name']} (from {source}, 未知维度,共 {len(data)} 个值)")
lines.append(f"pub const {name}: [f64; {len(data)}] = [")
for i, val in enumerate(data):
if i % 10 == 0:
lines.append(" ")
lines[-1] += f"{val},"
lines.append("];")
lines.append("")
elif len(dims) == 1:
# 1D 数组 - 检查数据量是否匹配
expected_size = dims[0]
if len(data) != expected_size:
print(f"警告: {name} 期望 {expected_size} 个值,实际 {len(data)} 个,跳过")
continue
lines.append(f"/// {arr['name']}({dims[0]}) from {source}")
lines.append(f"pub const {name}: [f64; {dims[0]}] = [")
for i, val in enumerate(data):
if i % 10 == 0:
lines.append(" ")
lines[-1] += f"{val},"
lines.append("];")
lines.append("")
elif len(dims) == 2:
# 2D 数组 - 直接转换为 Rust 行优先格式
nj, ni = dims[0], dims[1]
expected_size = nj * ni
if len(data) < expected_size:
print(f"警告: {name} 期望 {expected_size} 个值,实际 {len(data)} 个,跳过")
continue
lines.append(f"/// {arr['name']}({nj}, {ni}) from {source}")
lines.append(f"/// 已转换为 Rust 行优先格式")
lines.append(f"pub const {name}: [[f64; {ni}]; {nj}] = [")
# 列优先 → 行优先 转换
for j in range(nj):
row = []
for i in range(ni):
idx = j + i * nj # Fortran 列优先索引
row.append(str(data[idx]))
lines.append(f" [{','.join(row)}],")
lines.append("];")
lines.append("")
# 不再需要转换函数和 getter2D 数组直接生成为 const
return '\n'.join(lines)
def parse_include_files(extracted_dir: Path) -> dict:
"""解析 .FOR include 文件中的全局参数"""
global_params = {}
# 扫描 .FOR 文件
for for_file in extracted_dir.glob("*.FOR"):
try:
content = for_file.read_text()
except:
# 也尝试 tlusty/ 根目录
for_file = Path("tlusty") / for_file.name
if for_file.exists():
content = for_file.read_text()
else:
continue
# 清理注释
lines = []
for line in content.split('\n'):
if '!' in line:
line = line.split('!')[0]
lines.append(line)
content = '\n'.join(lines)
# 解析 parameter 语句
param_pattern = r'parameter\s*\(([^)]+)\)'
for match in re.finditer(param_pattern, content, re.IGNORECASE):
params_str = match.group(1)
for param in params_str.split(','):
param = param.strip()
if '=' in param:
name, val = param.split('=', 1)
name = name.strip().lower()
val = val.strip().lower()
try:
if '.' not in val and 'e' not in val and 'd' not in val:
global_params[name] = int(val)
else:
val = val.replace('d', 'e')
global_params[name] = int(float(val))
except ValueError:
pass
return global_params
def main():
# 扫描 tlusty/extracted 目录
extracted_dir = Path("tlusty/extracted")
if not extracted_dir.exists():
print(f"错误: 目录不存在: {extracted_dir}")
return
# 首先解析 include 文件中的全局参数
global_params = parse_include_files(extracted_dir)
print(f"从 .FOR include 文件中提取了 {len(global_params)} 个全局参数")
all_arrays = {}
# 扫描所有 .f 文件
for fortran_file in sorted(extracted_dir.glob("*.f")):
arrays = parse_fortran_file(fortran_file, global_params)
if arrays:
all_arrays[fortran_file.name] = arrays
print(f"解析: {fortran_file.name} -> {len(arrays)} 个数组")
# 统计
total_arrays = sum(len(arrs) for arrs in all_arrays.values())
arrays_with_data = sum(
1 for arrs in all_arrays.values()
for a in arrs if a.get("data") and len(a["data"]) > 0
)
arrays_2d = sum(
1 for arrs in all_arrays.values()
for a in arrs if len(a.get("dims", [])) == 2 and a.get("data")
)
print()
print("=" * 60)
print(f"总计: {total_arrays} 个数组, {arrays_with_data} 个有数据, {arrays_2d} 个 2D 数组")
print("=" * 60)
# 生成 data.rs
output_path = Path("src/data.rs")
rust_code = generate_data_rs(all_arrays)
with open(output_path, 'w') as f:
f.write(rust_code)
print(f"已生成: {output_path}")
print()
print("在 lib.rs 或 main.rs 中添加:")
print(" pub mod data;")
print()
print("使用方法:")
print(" use crate::data::{TT, PN, get_p4b};")
print(" let p4b = get_p4b(); // 自动初始化并返回 2D 数组")
if __name__ == "__main__":
main()

View File

@ -0,0 +1,205 @@
#!/usr/bin/env python3
"""
Fortran DATA 语句转换为 Rust 2D 数组
用法:
python3 scripts/fortran_to_rust_array.py tlusty/extracted/pffe.f
输出: Rust 代码片段可直接复制到 .rs 文件中
"""
import re
import sys
from pathlib import Path
def parse_fortran_arrays(filepath: str) -> list[dict]:
"""
解析 Fortran 文件中的数组定义和 DATA 语句
返回: [{"name": "p4a", "dims": [22], "data": [...]}, ...]
"""
with open(filepath, 'r') as f:
content = f.read()
arrays = {}
# 1. 解析 dimension 语句
# dimension p4a(22), p4b(10,28), ...
dim_pattern = r'dimension\s+([a-z0-9_,()\s]+)'
for match in re.finditer(dim_pattern, content, re.IGNORECASE):
dim_str = match.group(1)
# 解析每个数组
arr_pattern = r'(\w+)\s*\(([^)]+)\)'
for arr_match in re.finditer(arr_pattern, dim_str):
name = arr_match.group(1).lower()
dims = [int(d.strip()) for d in arr_match.group(2).split(',')]
arrays[name] = {"name": name, "dims": dims, "data": None}
# 2. 解析 data 语句
# data p4a / val1, val2, ... /
# 或多行:
# data p4b /
# * val1, val2, ...,
# * val3, ... /
# 先找到所有 data 块
data_pattern = r'data\s+(\w+)\s*/\s*([^/]+)\s*/'
for match in re.finditer(data_pattern, content, re.IGNORECASE | re.DOTALL):
name = match.group(1).lower()
data_str = match.group(2)
if name not in arrays:
arrays[name] = {"name": name, "dims": [], "data": None}
# 解析数值
values = parse_data_values(data_str)
arrays[name]["data"] = values
return list(arrays.values())
def parse_data_values(data_str: str) -> list[float]:
"""解析 DATA 语句中的数值,处理重复语法如 7*1.387"""
values = []
# 清理: 移除注释、换行,但保留 * 用于重复语法
data_str = re.sub(r'[cC]\s*$', '', data_str) # 行尾注释
# 移除 Fortran 续行符 * (行首的 *),但保留数据中的 *
lines = data_str.split('\n')
cleaned_lines = []
for line in lines:
line = line.strip()
if line.startswith('*'):
line = line[1:].strip()
cleaned_lines.append(line)
data_str = ' '.join(cleaned_lines)
for part in data_str.split(','):
part = part.strip()
if not part:
continue
# 处理重复语法: "7*1.387" 或 "7*1.387"
if '*' in part and not part.startswith('-'): # 避免把负数当重复
# 检查是否真的是重复语法
match = re.match(r'(\d+)\s*\*\s*(-?[\d.]+)', part)
if match:
count = int(match.group(1))
val = float(match.group(2))
values.extend([val] * count)
continue
try:
values.append(float(part))
except ValueError:
# 跳过无法解析的部分
pass
return values
def generate_rust_code(arrays: list[dict]) -> str:
"""生成 Rust 代码"""
lines = []
lines.append("// 自动生成的数组数据")
lines.append("")
for arr in arrays:
name = arr["name"].upper()
dims = arr["dims"]
data = arr["data"]
if not data:
continue
total_size = len(data)
if len(dims) == 1:
# 1D 数组
lines.append(f"const {name}_RAW: [f64; {total_size}] = [")
for i, val in enumerate(data):
if i % 10 == 0:
lines.append(" ",)
lines[-1] += f"{val},"
lines.append("];")
lines.append(f"static {name}: OnceLock<[f64; {dims[0]}]> = OnceLock::new();")
lines.append("")
elif len(dims) == 2:
# 2D 数组
nj, ni = dims[0], dims[1]
lines.append(f"// {name}: Fortran {name.lower()}({nj}, {ni})")
lines.append(f"const {name}_RAW: [f64; {total_size}] = [")
for i, val in enumerate(data):
if i % 10 == 0:
lines.append(" ")
lines[-1] += f"{val},"
lines.append("];")
lines.append(f"static {name}: OnceLock<[[f64; {ni}]; {nj}]> = OnceLock::new();")
lines.append("")
# 添加转换函数
lines.append("")
lines.append("/// Fortran 列优先 → Rust 行优先")
lines.append("const fn fortran_to_rust_2d<const NJ: usize, const NI: usize>(")
lines.append(" data: &[f64; NJ * NI],")
lines.append(") -> [[f64; NI]; NJ] {")
lines.append(" let mut result = [[0.0; NI]; NJ];")
lines.append(" let mut i = 0;")
lines.append(" while i < NI {")
lines.append(" let mut j = 0;")
lines.append(" while j < NJ {")
lines.append(" result[j][i] = data[j + i * NJ];")
lines.append(" j += 1;")
lines.append(" }")
lines.append(" i += 1;")
lines.append(" }")
lines.append(" result")
lines.append("}")
lines.append("")
# 添加初始化代码
lines.append("// 初始化函数中调用:")
for arr in arrays:
name = arr["name"].upper()
dims = arr["dims"]
if len(dims) == 2 and arr["data"]:
nj, ni = dims[0], dims[1]
lines.append(f"let {name.lower()} = {name}.get_or_init(|| fortran_to_rust_2d::<{nj}, {ni}>(&{name}_RAW));")
return '\n'.join(lines)
def main():
if len(sys.argv) < 2:
print("用法: python3 scripts/fortran_to_rust_array.py <fortran_file>")
print("示例: python3 scripts/fortran_to_rust_array.py tlusty/extracted/pffe.f")
sys.exit(1)
filepath = sys.argv[1]
if not Path(filepath).exists():
print(f"错误: 文件不存在: {filepath}")
sys.exit(1)
print(f"解析: {filepath}")
print("=" * 60)
arrays = parse_fortran_arrays(filepath)
print(f"找到 {len(arrays)} 个数组:")
for arr in arrays:
dims_str = ', '.join(str(d) for d in arr['dims'])
data_count = len(arr['data']) if arr['data'] else 0
print(f" - {arr['name']}({dims_str}): {data_count} 个值")
print()
print("=" * 60)
print("生成的 Rust 代码:")
print("=" * 60)
print(generate_rust_code(arrays))
if __name__ == "__main__":
main()

5981
src/data.rs Normal file

File diff suppressed because it is too large Load Diff

View File

@ -2,6 +2,20 @@
//!
//! A progressive refactoring of the TLUSTY stellar atmosphere modeling
//! software from Fortran to Rust.
//!
//! # 模块结构
//!
//! - `state`: 状态管理 (COMMON 块转换)
//! - `constants`: 物理常数和维度参数
//! - `config`: 运行时配置
//! - `atomic`: 原子/离子/能级数据
//! - `model`: 大气模型状态
//! - `arrays`: 大型计算数组
//! - `math`: 数学工具函数
//! - `data`: 静态数据数组
//! - `physics`: 物理计算模块
pub mod data;
pub mod math;
pub mod physics;
pub mod state;

168
src/math/angset.rs Normal file
View File

@ -0,0 +1,168 @@
//! Compton 散射角度设置。
//!
//! 重构自 TLUSTY `angset.f`。
//!
//! 设置角度点和角度相关量,用于处理 Compton 散射。
use crate::state::{Comptn, MMUC};
// ============================================================================
// 常量
// ============================================================================
const THREE: f64 = 3.0;
const FIVE: f64 = 5.0;
const ZERO: f64 = 0.0;
const TR16: f64 = 3.0 / 16.0;
// ============================================================================
// ANGSET - 角度设置
// ============================================================================
/// 设置 Compton 散射的角度点和角度相关量。
///
/// 使用 Gauss-Legendre 积分在区间 μ=[0,1] 上设置角度点,
/// 然后扩展到 μ=[-1,1] 区间。
///
/// # 参数
///
/// - `comptn` - Compton 散射参数结构 (会被修改)
/// - `nmuc_init` - 初始角度点数 (在 μ=[0,1] 区间)
///
/// # Fortran 原始代码
///
/// ```fortran
/// subroutine angset
/// call gauleg(zero,un,amu0,wtmu0,nmuc,mmuc)
/// do i=1,nmuc
/// amuc(i)=-amu0(nmuc-i+1)
/// amuc(i+nmuc)=amu0(i)
/// wtmuc(i)=wtmu0(nmuc-i+1)
/// wtmuc(i+nmuc)=wtmu0(i)
/// end do
/// nmuc=2*nmuc
/// ...
/// end
/// ```
pub fn angset(comptn: &mut Comptn, nmuc_init: usize) {
let nmuc0 = nmuc_init;
// 检查数组边界
if 2 * nmuc0 > MMUC {
panic!(
"ANGSET: nmuc_init={} too large, max is {}",
nmuc0,
MMUC / 2
);
}
// 在 [0, 1] 区间上设置 Gauss-Legendre 积分点
let (amu0, wtmu0) = super::gauleg(ZERO, 1.0, nmuc0);
// 扩展到 [-1, 1] 区间
// Fortran: amuc(i) = -amu0(nmuc-i+1), amuc(i+nmuc) = amu0(i)
// 这意味着先放负值(从大到小),再放正值(从小到大)
for i in 0..nmuc0 {
let j = nmuc0 - 1 - i; // Fortran nmuc-i+1 转换为 0-indexed
comptn.amuc[i] = -amu0[j];
comptn.amuc[i + nmuc0] = amu0[i];
comptn.wtmuc[i] = wtmu0[j];
comptn.wtmuc[i + nmuc0] = wtmu0[i];
}
// 更新角度点数
comptn.nmuc = (2 * nmuc0) as i32;
let nmuc = comptn.nmuc as usize;
// 计算角度乘积
for i in 0..nmuc {
let a1 = comptn.amuc[i];
comptn.amuc1[i] = a1 * comptn.wtmuc[i];
comptn.amuc2[i] = a1 * a1 * comptn.wtmuc[i];
comptn.amuc3[i] = a1 * a1 * a1 * comptn.wtmuc[i];
}
// 计算 Compton 散射系数
for i in 0..nmuc {
let a1 = comptn.amuc[i];
let a2 = a1 * a1;
let a3 = a1 * a2;
for i1 in 0..nmuc {
let b1 = comptn.amuc[i1];
let b2 = b1 * b1;
let b3 = b1 * b2;
let trw = TR16 * comptn.wtmuc[i1];
comptn.calph[i][i1] = (THREE * a2 * b2 - a2 - b2 + THREE) * trw;
comptn.cbeta[i][i1] =
(FIVE * (a1 * b1 + a3 * b3) - THREE * (a3 * b1 + a1 * b3)) * trw;
comptn.cgamm[i][i1] = a1 * b1 * trw;
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::state::MFREQ;
fn create_test_comptn() -> Comptn {
Comptn::default()
}
#[test]
fn test_angset_basic() {
let mut comptn = create_test_comptn();
angset(&mut comptn, 1);
// 检查角度点数翻倍
assert_eq!(comptn.nmuc, 2);
// 检查所有数组已填充
for i in 0..comptn.nmuc as usize {
assert!(comptn.amuc[i].abs() <= 1.0);
assert!(comptn.wtmuc[i] > 0.0);
}
}
#[test]
fn test_angset_symmetry() {
let mut comptn = create_test_comptn();
angset(&mut comptn, 1);
let nmuc = comptn.nmuc as usize;
// 检查角度对称性: amuc[0] = -amuc[1]
assert!((comptn.amuc[0] + comptn.amuc[1]).abs() < 1e-10);
// 检查权重对称性
assert!((comptn.wtmuc[0] - comptn.wtmuc[1]).abs() < 1e-10);
}
#[test]
fn test_angset_coefficients() {
let mut comptn = create_test_comptn();
angset(&mut comptn, 1);
let nmuc = comptn.nmuc as usize;
// 检查系数矩阵已填充
for i in 0..nmuc {
for j in 0..nmuc {
// 系数应该是有界值
assert!(comptn.calph[i][j].abs() < 10.0);
assert!(comptn.cbeta[i][j].abs() < 10.0);
assert!(comptn.cgamm[i][j].abs() < 10.0);
}
}
}
#[test]
#[should_panic]
fn test_angset_too_large() {
let mut comptn = create_test_comptn();
// nmuc_init > MMUC/2 应该 panic
angset(&mut comptn, MMUC + 1);
}
}

271
src/math/butler.rs Normal file
View File

@ -0,0 +1,271 @@
//! 氢原子碰撞激发速率。
//!
//! 重构自 TLUSTY `butler.f`
//!
//! 基于 Przybilla & Butler (2004, ApJ) 表3的插值。
use std::sync::OnceLock;
/// Butler 数据。
struct ButlerData {
tref: [f64; 16],
colstr: [[f64; 21]; 16],
}
static BUTLER_DATA: OnceLock<ButlerData> = OnceLock::new();
fn get_butler_data() -> &'static ButlerData {
BUTLER_DATA.get_or_init(|| {
// 参考温度 (K)
let tref = [
2.5e3, 5e3, 7.5e3, 1e4, 1.5e4, 2e4, 2.5e4, 3e4, 4e4, 5e4, 6e4, 8e4, 1e5, 1.5e5, 2e5,
2.5e5,
];
// 碰撞强度数据 (16个温度 x 21个跃迁)
// J=1,21 对应 (NI,NJ)={(1,2),(1,3),...,(1,7),(2,3),...,(6,7)}
// I=1,16 对应 T={2.5e3,...,2.5e5}
let colstr = [
// T = 2500 K
[
6.40e-1, 2.20e-1, 9.93e-2, 4.92e-2, 2.97e-2, 5.03e-2, 2.35e1, 1.07e1, 5.22e0,
2.91e0, 5.25e0, 1.50e2, 7.89e1, 4.13e1, 7.60e1, 5.90e2, 2.94e2, 4.79e2, 1.93e3,
1.95e3, 6.81e3,
],
// T = 5000 K
[
6.98e-1, 2.40e-1, 1.02e-1, 5.84e-2, 4.66e-2, 6.72e-2, 2.78e1, 1.15e1, 5.90e0,
4.53e0, 7.26e0, 1.90e2, 9.01e1, 6.11e1, 1.07e2, 8.17e2, 4.21e2, 7.06e2, 2.91e3,
3.24e3, 1.17e4,
],
// T = 7500 K
[
7.57e-1, 2.50e-1, 1.10e-1, 7.17e-2, 6.28e-2, 7.86e-2, 3.09e1, 1.23e1, 6.96e0,
6.06e0, 8.47e0, 2.28e2, 1.07e2, 8.21e1, 1.25e2, 1.07e3, 5.78e2, 8.56e2, 4.00e3,
4.20e3, 1.50e4,
],
// T = 10000 K
[
8.09e-1, 2.61e-1, 1.22e-1, 8.58e-2, 7.68e-2, 8.74e-2, 3.38e1, 1.34e1, 8.15e0,
7.32e0, 9.27e0, 2.70e2, 1.26e2, 1.01e2, 1.37e2, 1.35e3, 7.36e2, 9.66e2, 5.04e3,
4.95e3, 1.73e4,
],
// T = 15000 K
[
8.97e-1, 2.88e-1, 1.51e-1, 1.12e-1, 9.82e-2, 1.00e-1, 4.01e1, 1.62e1, 1.04e1,
9.17e0, 1.03e1, 3.64e2, 1.66e2, 1.31e2, 1.52e2, 1.93e3, 1.02e3, 1.11e3, 6.81e3,
6.02e3, 2.03e4,
],
// T = 20000 K
[
9.78e-1, 3.22e-1, 1.80e-1, 1.33e-1, 1.14e-1, 1.10e-1, 4.71e1, 1.90e1, 1.23e1,
1.05e1, 1.08e1, 4.66e2, 2.03e2, 1.54e2, 1.61e2, 2.47e3, 1.26e3, 1.21e3, 8.20e3,
6.76e3, 2.21e4,
],
// T = 25000 K
[
1.06e0, 3.59e-1, 2.06e-1, 1.50e-1, 1.25e-1, 1.16e-1, 5.45e1, 2.18e1, 1.39e1,
1.14e1, 1.12e1, 5.70e2, 2.37e2, 1.72e2, 1.68e2, 2.96e3, 1.46e3, 1.29e3, 9.29e3,
7.29e3, 2.33e4,
],
// T = 30000 K
[
1.15e0, 3.96e-1, 2.28e-1, 1.64e-1, 1.33e-1, 1.21e-1, 6.20e1, 2.44e1, 1.52e1,
1.21e1, 1.14e1, 6.72e2, 2.68e2, 1.86e2, 1.72e2, 3.40e3, 1.64e3, 1.34e3, 1.02e4,
7.70e3, 2.41e4,
],
// T = 40000 K
[
1.32e0, 4.64e-1, 2.66e-1, 1.85e-1, 1.45e-1, 1.27e-1, 7.71e1, 2.89e1, 1.74e1,
1.31e1, 1.17e1, 8.66e2, 3.19e2, 2.08e2, 1.78e2, 4.14e3, 1.92e3, 1.41e3, 1.15e4,
8.26e3, 2.52e4,
],
// T = 50000 K
[
1.51e0, 5.26e-1, 2.95e-1, 2.01e-1, 1.53e-1, 1.31e-1, 9.14e1, 3.27e1, 1.90e1,
1.38e1, 1.18e1, 1.04e3, 3.62e2, 2.24e2, 1.81e2, 4.75e3, 2.15e3, 1.46e3, 1.26e4,
8.63e3, 2.60e4,
],
// T = 60000 K
[
1.68e0, 5.79e-1, 3.18e-1, 2.12e-1, 1.58e-1, 1.34e-1, 1.05e2, 3.60e1, 2.03e1,
1.44e1, 1.19e1, 1.19e3, 3.98e2, 2.36e2, 1.83e2, 5.25e3, 2.33e3, 1.50e3, 1.34e4,
8.88e3, 2.69e4,
],
// T = 80000 K
[
2.02e0, 6.70e-1, 3.55e-1, 2.29e-1, 1.65e-1, 1.35e-1, 1.29e2, 4.14e1, 2.23e1,
1.51e1, 1.19e1, 1.46e3, 4.53e2, 2.53e2, 1.85e2, 6.08e3, 2.61e3, 1.55e3, 1.49e4,
9.21e3, 2.90e4,
],
// T = 100000 K
[
2.33e0, 7.43e-1, 3.83e-1, 2.39e-1, 1.70e-1, 1.37e-1, 1.51e2, 4.56e1, 2.37e1,
1.56e1, 1.20e1, 1.67e3, 4.95e2, 2.65e2, 1.86e2, 6.76e3, 2.81e3, 1.57e3, 1.63e4,
9.43e3, 3.17e4,
],
// T = 150000 K
[
2.97e0, 8.80e-1, 4.30e-1, 2.59e-1, 1.77e-1, 1.39e-1, 1.93e2, 5.31e1, 2.61e1,
1.63e1, 1.19e1, 2.08e3, 5.68e2, 2.83e2, 1.87e2, 8.08e3, 3.15e3, 1.61e3, 1.97e4,
9.78e3, 3.94e4,
],
// T = 200000 K
[
3.50e0, 9.79e-1, 4.63e-1, 2.71e-1, 1.82e-1, 1.39e-1, 2.26e2, 5.83e1, 2.78e1,
1.68e1, 1.19e1, 2.39e3, 6.16e2, 2.94e2, 1.86e2, 9.13e3, 3.36e3, 1.62e3, 2.27e4,
1.00e4, 4.73e4,
],
// T = 250000 K (not used, for completeness)
[
3.95e0, 1.06e0, 4.88e-1, 2.81e-1, 1.85e-1, 1.40e-1, 2.52e2, 6.23e1, 2.89e1,
1.71e1, 1.19e1, 2.62e3, 6.51e2, 3.02e2, 1.87e2, 1.00e4, 3.51e3, 1.63e3, 2.54e4,
1.02e4, 5.50e4,
],
];
ButlerData { tref, colstr }
})
}
/// 氢原子碰撞激发速率。
///
/// 基于 Przybilla & Butler (2004, ApJ) 表3的插值计算电子碰撞激发速率。
///
/// # 参数
///
/// * `ni` - 下能级主量子数 (1-6)
/// * `nj` - 上能级主量子数 (2-7)
/// * `t` - 温度 (K)
/// * `u0` - hν/kT
///
/// # 返回值
///
/// `(col, ierr)` 元组:
/// - `col`: 碰撞速率 (cm³/s)
/// - `ierr`: 错误码 (0=正常, 1=温度超出范围, 2=量子数超出范围)
///
/// # 备注
///
/// 基于 Przybilla & Butler (2004, ApJ) 表3。
pub fn butler(ni: i32, nj: i32, t: f64, u0: f64) -> (f64, i32) {
const NL: i32 = 7;
let mut ierr = 0;
let mut col = 0.0;
// 检查温度范围
if t < 2.5e3 || t >= 2.5e5 {
ierr = 1;
}
// 检查量子数范围
if ni < 1 || ni > NL - 1 || nj < 2 || nj > NL {
ierr = 2;
}
if ierr == 0 {
// 计算 J 索引 (跃迁索引)
let mut j: usize = 0;
for _i in 1..ni {
j += (NL - _i) as usize;
}
for _k in (ni + 1)..=nj {
j += 1;
}
let data = get_butler_data();
// 找到最近的温度点
let mut ilow: usize = 0;
while ilow < 15 && t >= data.tref[ilow + 1] {
ilow += 1;
}
let mut ihig: usize = 15;
while ihig > 0 && t < data.tref[ihig - 1] {
ihig -= 1;
}
if ihig == ilow {
ihig += 1;
}
// 对数-对数线性插值碰撞强度
let sl = (data.colstr[ihig][j - 1].log10() - data.colstr[ilow][j - 1].log10())
/ (data.tref[ihig].log10() - data.tref[ilow].log10());
let or = data.colstr[ihig][j - 1].log10() - sl * data.tref[ihig].log10();
col = 10f64.powf(t.log10() * sl + or);
// 计算速率
col = 8.631e-6 / (2.0 * (ni as f64).powi(2)) / t.sqrt() * (-u0).exp() * col;
}
(col, ierr)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_butler_lyman_alpha() {
// Lyman-α: n=1 → n=2
let (col, ierr) = butler(1, 2, 10000.0, 11.87);
assert_eq!(ierr, 0);
assert!(col > 0.0);
}
#[test]
fn test_butler_balmer_alpha() {
// Balmer-α: n=2 → n=3
let (col, ierr) = butler(2, 3, 10000.0, 1.89);
assert_eq!(ierr, 0);
assert!(col > 0.0);
}
#[test]
fn test_butler_low_temp() {
// 低温边界
let (col, ierr) = butler(1, 2, 3000.0, 10.0);
assert_eq!(ierr, 0);
assert!(col > 0.0);
}
#[test]
fn test_butler_high_temp() {
// 高温边界
let (col, ierr) = butler(1, 2, 200000.0, 0.5);
assert_eq!(ierr, 0);
assert!(col > 0.0);
}
#[test]
fn test_butler_temp_out_of_range_low() {
// 温度过低
let (_, ierr) = butler(1, 2, 1000.0, 10.0);
assert_eq!(ierr, 1);
}
#[test]
fn test_butler_temp_out_of_range_high() {
// 温度过高
let (_, ierr) = butler(1, 2, 300000.0, 0.5);
assert_eq!(ierr, 1);
}
#[test]
fn test_butler_quantum_out_of_range() {
// 量子数超出范围
let (_, ierr) = butler(7, 8, 10000.0, 1.0);
assert_eq!(ierr, 2);
}
#[test]
fn test_butler_n6_to_n7() {
// n=6 → n=7 跃迁
let (col, ierr) = butler(6, 7, 10000.0, 0.1);
assert_eq!(ierr, 0);
assert!(col > 0.0);
}
}

231
src/math/cion.rs Normal file
View File

@ -0,0 +1,231 @@
//! 碰撞电离速率。
//!
//! 重构自 TLUSTY `cion.f`
//!
//! 基于 Raymond 的方法,使用 Tim Kallman 的程序。
use crate::math::expo;
/// 碰撞电离速率系数数组。
struct CionData {
a0: [f64; 30],
a1: [f64; 30],
a2: [f64; 30],
a3: [f64; 30],
b0: [f64; 30],
b1: [f64; 30],
b2: [f64; 30],
b3: [f64; 30],
c0: [f64; 30],
c1: [f64; 30],
c2: [f64; 30],
c3: [f64; 30],
d0: [f64; 30],
d1: [f64; 30],
d2: [f64; 30],
d3: [f64; 30],
}
use std::sync::OnceLock;
static CION_DATA: OnceLock<CionData> = OnceLock::new();
fn get_cion_data() -> &'static CionData {
CION_DATA.get_or_init(|| {
// 来自 Summers 等人的拟合系数
// sm younger jqsrt 26, 329; 27, 541; 29, 61 with moores for undone
// a0 for b-like ion has twice 2s plus one 2p as in summers et al
CionData {
a0: [
13.5, 27.0, 9.07, 11.80, 20.2, 28.6, 37.0, 45.4, 53.8, 62.2, 11.7, 38.8, 37.27,
46.7, 57.4, 67.0, 77.8, 90.1, 106.0, 120.8, 135.6, 150.4, 165.2, 180.0, 194.8,
209.6, 224.4, 239.2, 154.0, 268.8,
],
a1: [
-14.2, -60.1, 4.30, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
],
a2: [
40.6, 140.0, 7.69, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
],
a3: [
-17.1, -89.8, -7.53, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
],
b0: [
-4.81, -9.62, -2.47, -3.28, -5.96, -8.64, -11.32, -14.00, -16.68, -19.36, -4.29,
-16.7, -14.58, -16.95, -19.93, -23.05, -26.00, -29.45, -34.25, -38.92, -43.59,
-48.26, -52.93, -57.60, -62.27, -66.94, -71.62, -76.29, -80.96, -85.63,
],
b1: [
9.77, 33.1, -3.78, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
],
b2: [
-28.3, -82.5, -3.59, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
],
b3: [
11.4, 54.6, 3.34, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
],
c0: [
1.85, 3.69, 1.34, 1.64, 2.31, 2.984, 3.656, 4.328, 5.00, 5.672, 1.061, 1.87, 3.26,
5.07, 6.67, 8.10, 9.92, 11.79, 7.953, 8.408, 8.863, 9.318, 9.773, 10.228, 10.683,
11.138, 11.593, 12.048, 12.505, 12.96,
],
c1: [
0.0, 4.32, 0.343, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
],
c2: [
0.0, -2.527, -2.46, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
],
c3: [
0.0, 0.262, 1.38, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
],
d0: [
-10.9, -21.7, -5.37, -7.58, -12.66, -17.74, -22.82, -27.9, -32.98, -38.06, -7.34,
-28.8, -24.87, -30.5, -37.9, -45.3, -53.8, -64.6, -54.54, -61.70, -68.86, -76.02,
-83.18, -90.34, -97.50, -104.66, -111.82, -118.98, -126.14, -133.32,
],
d1: [
8.90, 42.5, -12.4, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
],
d2: [
-35.7, -131.0, -8.09, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
],
d3: [
16.5, 87.4, 1.23, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
],
}
})
}
/// 碰撞电离速率。
///
/// 基于 Raymond 的方法计算碰撞电离速率。
///
/// # 参数
///
/// * `n` - 核电荷数
/// * `j` - 电离级 (1 = 中性)
/// * `e` - 价壳层电离阈值 (eV)
/// * `t` - 温度 (K)
///
/// # 返回值
///
/// 碰撞电离速率 (cm³/s)。
///
/// # 备注
///
/// 使用 Tim Kallman 的程序,基于 Summers 等人的拟合系数。
/// 注意:此程序只考虑价壳层电离。
/// 对于每个电离级应只调用一次,使用最低的价壳层电离阈值。
pub fn cion(n: i32, j: i32, e: f64, t: f64) -> f64 {
let chir = t / (11590.0 * e);
// 低温下返回 0
if chir <= 0.0115 {
return 0.0;
}
let chi = chir;
let ch2 = chi * chi;
let ch3 = ch2 * chi;
// 计算 alpha 和 beta
let alpha = (0.001193 + 0.9764 * chi + 0.6604 * ch2 + 0.02590 * ch3)
/ (1.0 + 1.488 * chi + 0.2972 * ch2 + 0.004925 * ch3);
let beta = (-0.0005725 + 0.01345 * chi + 0.8691 * ch2 + 0.03404 * ch3)
/ (1.0 + 2.197 * chi + 0.2457 * ch2 + 0.002503 * ch3);
let j2 = (j * j) as f64;
let j3 = j2 * j as f64;
let jf = j as f64;
let iso = (n - j + 1) as usize;
// 索引检查
if iso < 1 || iso > 30 {
return 0.0;
}
let iso_idx = iso - 1; // 转换为 0-indexed
let data = get_cion_data();
// 计算系数 a, b, c, d
let (a, b, c, d) = if n == 26 && j == 2 {
// Fe II 实验电离 (Montague et al.), D. Neufeld 拟合
(-13.825, -11.0395, 21.07262, 0.0)
} else {
let a = data.a0[iso_idx] + data.a1[iso_idx] / jf + data.a2[iso_idx] / j2 + data.a3[iso_idx] / j3;
let b = data.b0[iso_idx] + data.b1[iso_idx] / jf + data.b2[iso_idx] / j2 + data.b3[iso_idx] / j3;
let c = data.c0[iso_idx] + data.c1[iso_idx] / jf + data.c2[iso_idx] / j2 + data.c3[iso_idx] / j3;
let d = data.d0[iso_idx] + data.d1[iso_idx] / jf + data.d2[iso_idx] / j2 + data.d3[iso_idx] / j3;
(a, b, c, d)
};
let ch = 1.0 / chi;
let fchi = 0.3 * ch * (a + b * (1.0 + ch) + (c - (a + b * (2.0 + ch)) * ch) * alpha + d * beta * ch);
2.2e-6 * chir.sqrt() * fchi * expo(-1.0 / chir) / (e * e.sqrt())
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_relative_eq;
#[test]
fn test_cion_hydrogen() {
// H I 电离 (n=1, j=1, e=13.6 eV)
let result = cion(1, 1, 13.6, 10000.0);
assert!(result.is_finite());
assert!(result >= 0.0);
}
#[test]
fn test_cion_helium() {
// He I 电离 (n=2, j=1, e=24.6 eV)
let result = cion(2, 1, 24.6, 20000.0);
assert!(result.is_finite());
assert!(result >= 0.0);
}
#[test]
fn test_cion_low_temp() {
// 低温下应返回 0
let result = cion(1, 1, 13.6, 1000.0);
assert_relative_eq!(result, 0.0, epsilon = 1e-30);
}
#[test]
fn test_cion_fe_ii() {
// Fe II 特殊情况 (n=26, j=2)
let result = cion(26, 2, 7.9, 10000.0);
assert!(result.is_finite());
assert!(result >= 0.0);
}
#[test]
fn test_cion_high_temp() {
// 高温
let result = cion(1, 1, 13.6, 50000.0);
assert!(result.is_finite());
assert!(result > 0.0);
}
#[test]
fn test_cion_carbon() {
// C I 电离 (n=6, j=1, e=11.26 eV)
let result = cion(6, 1, 11.26, 15000.0);
assert!(result.is_finite());
assert!(result >= 0.0);
}
}

137
src/math/ckoest.rs Normal file
View File

@ -0,0 +1,137 @@
//! Koester 拟合的 He I 光电离截面。
//!
//! 重构自 TLUSTY `ckoest.f`。
//!
//! 参考Koester (1985, AA 149, 423)
use crate::state::CAS;
/// 基本光子截面常数
const PHOT0: f64 = 2.815e29;
/// Koester 拟合系数表
/// COEF(3, 11) - 每组 3 个系数,共 11 组
const COEF: [[f64; 11]; 3] = [
// COEF(1, *)
[
-58.229, -68.438, -67.310, -92.020, -68.936, -63.408, -63.778, -76.903, -61.027, -83.287,
-83.287,
],
// COEF(2, *)
[
4.3965, 5.7453, 6.1831, 10.313, 5.2666, 3.8797, 4.5102, 6.3639, 3.1833, 7.1751, 7.1751,
],
// COEF(3, *)
[
-0.22134, -0.26277, -0.32244, -0.45090, -0.15812, -0.12479, -0.18213, -0.21565,
-0.043675, -0.20821, -0.20821,
],
];
/// 每个主量子数 n 的起始索引
/// n=1: 索引 1, n=2: 索引 2, n=3: 索引 6, n=4: 索引 10
const IST: [i32; 3] = [1, 2, 6];
/// 计算 He I 光电离截面 (Koester 拟合)。
///
/// # 参数
///
/// - `s` - 多重性 (1 或 3)
/// - `l` - 角动量量子数 (0, 1, 2; L > 2 使用类氢公式)
/// - `n` - 主量子数
/// - `freq` - 频率 (Hz)
/// - `gg` - 统计权重
///
/// # 返回
///
/// 光电离截面 (cm²)
///
/// # Fortran 原始代码
///
/// ```fortran
/// FUNCTION CKOEST(S,L,N,FREQ,GG)
/// ...
/// X=LOG(CAS/FREQ)
/// CKOEST=EXP(COEF(1,NSL)+X*(COEF(2,NSL)+X*COEF(3,NSL)))/GG
/// END
/// ```
pub fn ckoest(s: i32, l: i32, n: i32, freq: f64, gg: f64) -> f64 {
if l > 2 {
// L > 2: 类氢公式
let n_f = n as f64;
let gn = 2.0 * n_f * n_f;
PHOT0 / freq.powi(3) / n_f.powi(5) * (2 * l + 1) as f64 * (s as f64) / gn
} else {
// L <= 2: Koester 拟合
let ss = (s - 1) / 2;
let ll = 2 * l;
// 计算 n 对应的索引偏移
let n_offset = if n <= 1 {
IST[0]
} else if n <= 2 {
IST[1]
} else if n <= 3 {
IST[2]
} else {
// n=4 时索引从 10 开始
10
};
// NSL 是 1-indexed转换为 0-indexed
let nsl = (n_offset + ll + ss - 1) as usize;
// 计算 X = ln(CAS / freq)
let x = (CAS / freq).ln();
// 计算 σ = exp(COEF(1) + X * (COEF(2) + X * COEF(3))) / GG
let log_sigma = COEF[0][nsl] + x * (COEF[1][nsl] + x * COEF[2][nsl]);
log_sigma.exp() / gg
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_ckoest_singlet_s() {
// 1S 态 (s=1, l=0, n=1)
let freq = 1e16; // 高于电离阈值
let result = ckoest(1, 0, 1, freq, 1.0);
assert!(result > 0.0, "Cross section should be positive");
}
#[test]
fn test_ckoest_triplet_s() {
// 3S 态 (s=3, l=0, n=2)
let freq = 1e16;
let result = ckoest(3, 0, 2, freq, 3.0);
assert!(result > 0.0);
}
#[test]
fn test_ckoest_high_l() {
// L > 2 使用类氢公式
let freq = 1e16;
let result = ckoest(1, 3, 4, freq, 1.0);
assert!(result > 0.0);
}
#[test]
fn test_ckoest_frequency_dependence() {
// 频率越高,截面越小
let result_low = ckoest(1, 0, 2, 1e15, 1.0);
let result_high = ckoest(1, 0, 2, 1e17, 1.0);
assert!(result_low > result_high);
}
#[test]
fn test_ckoest_n2_states() {
// n=2 的不同 L 态
let freq = 1e15;
let result_s = ckoest(1, 0, 2, freq, 1.0); // 2S
let result_p = ckoest(1, 1, 2, freq, 3.0); // 2P
assert!(result_s > 0.0 && result_p > 0.0);
}
}

378
src/math/collhe.rs Normal file
View File

@ -0,0 +1,378 @@
//! 氦原子碰撞速率系数。
//!
//! 重构自 TLUSTY `collhe.f`
//!
//! 基于 Berrington & Kingston (J. Phys. B 20, 6631, 1987)。
//!
//! 能级顺序(按能量递增):
//! 1: 1^1S, 2: 2^3S, 3: 2^1S, 4: 2^3P, 5: 2^1P, 6: 3^3S, 7: 3^1S, 8: 3^3P, 9: 3^3D,
//! 10: 3^1D, 11: 3^1P, 12: 4^3S, 13: 4^1S, 14: 4^3P, 15: 4^3D, 16: 4^1D, 17: 4^3F, 18: 4^1F, 19: 4^1P
use std::sync::OnceLock;
/// 氦原子能级能量 (eV)。
static ENER: OnceLock<[f64; 19]> = OnceLock::new();
/// 统计权重。
static STWT: OnceLock<[f64; 19]> = OnceLock::new();
/// 多项式系数起始索引。
static NSTART: OnceLock<[i32; 172]> = OnceLock::new();
/// 多项式系数。
static A_DATA: OnceLock<[f64; 929]> = OnceLock::new();
fn get_ener() -> &'static [f64; 19] {
ENER.get_or_init(|| {
[
0.0,19.8198,20.6160,20.96432,21.2182,
22.7187,22.9206,23.00731,23.0739,23.0743,23.0873,
23.5942,23.6738,23.7081,23.7363,23.7366,23.7373,
23.7373,23.7423,
]
})
}
fn get_stwt() -> &'static [f64; 19] {
STWT.get_or_init(|| {
[
1.0,3.0,1.0,9.0,3.0,3.0,1.0,9.0,
1.5e1,5.0,3.0,3.0,1.0,9.0,1.5e1,5.0,2.1e1,
7.0,3.0,
]
})
}
fn get_nstart() -> &'static [i32; 172] {
NSTART.get_or_init(|| {
[
1, 6, 11, 16, 20, 28, 32, 40, 44, 52, 57, 62, 67, 72, 77, 82,
88, 92, 98,104,110,114,120,125,129,135,139,147,151,157,164,170,
177,183,190,195,202,208,213,220,225,232,236,243,247,251,260,266,
273,278,285,290,300,304,309,316,320,324,329,333,338,343,347,352,
357,362,367,372,376,382,386,391,395,401,405,410,414,421,425,431,
435,440,445,449,454,459,465,470,475,480,487,491,497,503,508,515,
520,525,530,536,542,547,552,559,564,571,576,581,587,592,598,603,
608,613,617,623,630,635,642,646,650,655,660,666,671,677,683,689,
695,702,707,713,718,723,728,732,737,741,745,750,754,759,765,771,
777,782,789,796,801,805,810,815,819,824,831,837,844,850,856,861,
868,873,877,882,890,895,905,909,913,920,925,930,
]
})
}
fn get_a_data() -> &'static [f64; 929] {
A_DATA.get_or_init(|| {
[
1.7339e-07, 2.7997e-08,-1.3812e-08, 2.6639e-09, 1.7776e-09,
2.9820e-07, 7.5210e-08,-3.5975e-09, 3.2270e-09, 1.5245e-09,
1.5601e-05, 1.5340e-06,-2.2122e-06,-1.1073e-07, 1.9249e-07,
2.3682e-08, 1.0638e-08, 2.0959e-09, 2.8381e-10, 3.0497e-05,
1.9252e-05, 6.3109e-06, 6.9098e-07,-2.8039e-07,-2.1128e-07,
-1.2192e-07,-4.4417e-08, 1.3896e-06, 1.5715e-07,-8.4358e-08,
-2.8800e-08, 5.9599e-08, 3.4756e-08, 1.2183e-08, 3.7999e-09,
8.5500e-10,-3.9428e-10,-5.3999e-10,-2.2962e-10, 2.2510e-06,
6.1436e-07,-1.2437e-07,-7.1718e-08, 5.9026e-05, 3.8150e-05,
1.1426e-05, 9.2886e-07,-6.4827e-07,-4.4270e-07,-1.8611e-07,
-5.6403e-08, 5.8752e-06, 2.5167e-06, 2.0787e-07,-2.3353e-07,
-8.9900e-08, 5.6334e-08, 2.9313e-09, 1.7775e-09, 5.5494e-10,
-5.8914e-10, 8.1939e-06, 2.4014e-07, 3.8681e-07, 2.8446e-07,
-6.1936e-08, 1.8173e-06,-4.7530e-07,-6.6432e-08, 5.4898e-08,
-1.2377e-08, 2.0732e-05, 6.5991e-07, 1.5840e-06, 4.9920e-07,
-1.8332e-07, 3.2273e-06,-4.9880e-07,-2.4929e-07, 1.1964e-07,
-2.1996e-08, 9.7096e-08, 1.3557e-08, 7.4404e-09, 1.3858e-09,
-1.3778e-09,-8.4885e-10, 3.5068e-06,-5.0675e-07,-1.2252e-07,
6.0514e-08, 7.0524e-06, 1.4454e-06, 8.3966e-07, 2.7203e-07,
-2.3854e-08,-8.6693e-08, 7.1193e-06, 8.3111e-09,-9.3916e-07,
-2.7944e-08, 1.7803e-07,-3.9216e-08, 9.2760e-06, 2.4761e-06,
1.0095e-06, 3.4039e-07,-8.6900e-08,-1.1156e-07, 2.5036e-05,
-5.7791e-06,-1.8197e-06, 1.0630e-06, 9.8746e-09, 3.1048e-09,
1.2172e-09, 1.8411e-10,-1.5835e-10,-1.4443e-10, 1.9240e-06,
1.5012e-07, 7.9741e-08, 2.9323e-08,-1.2796e-08, 5.0457e-07,
-5.5146e-08,-2.7506e-08, 1.4341e-09, 1.3321e-05, 2.6960e-06,
2.8059e-07, 1.7548e-08,-2.3623e-07,-1.4619e-07, 1.5294e-06,
-9.4925e-08,-1.1347e-07, 8.1980e-09, 1.3324e-04, 7.8068e-05,
2.9238e-05, 4.2718e-06,-1.6556e-06,-1.4529e-06,-8.6046e-07,
-2.1062e-07, 2.8510e-06,-4.7936e-07,-2.4042e-07, 6.2333e-08,
1.3493e-09, 3.5113e-10,-4.7269e-11, 2.4872e-11,-9.7136e-12,
-1.4217e-11, 1.5680e-06, 8.9272e-07, 2.8313e-07, 5.2456e-08,
-2.3404e-08,-2.7304e-08,-8.4539e-09, 1.7967e-07, 6.2133e-08,
-3.2814e-09,-3.7299e-09,-1.9587e-09,-1.6685e-09, 1.2707e-05,
7.5029e-06, 2.8330e-06, 6.7478e-07,-1.5012e-07,-2.3916e-07,
-8.9594e-08, 7.6160e-07, 2.0422e-07,-2.5881e-08,-1.7624e-08,
-8.3518e-09,-4.1743e-09, 4.6044e-05, 2.2425e-05, 3.3079e-06,
3.6752e-07,-8.4476e-08,-4.8455e-07,-2.5391e-07, 7.0824e-07,
1.4917e-07,-1.2005e-07,-1.6983e-08, 1.1545e-08, 7.2866e-04,
3.8907e-04, 9.6365e-05, 2.3153e-05, 1.8462e-07,-9.0627e-06,
-5.4332e-06, 1.0458e-08, 1.6430e-09, 5.6453e-10, 2.0276e-10,
-1.6501e-10,-1.0656e-10, 5.2198e-07, 4.2898e-08,-1.0332e-08,
-5.6754e-09,-7.1960e-09, 3.0390e-06, 1.5385e-06, 6.2110e-07,
1.4111e-07,-3.7415e-08,-4.8395e-08,-1.7219e-08, 2.7227e-06,
1.4381e-07,-5.5030e-08,-2.2192e-10,-2.8583e-08, 1.8417e-05,
9.3860e-06, 3.9999e-06, 1.0424e-06,-2.1337e-07,-3.3271e-07,
-1.2684e-07, 4.6063e-06,-6.7185e-07,-2.5830e-07, 2.9976e-08,
7.8012e-05, 3.4921e-05, 8.3012e-06, 1.5643e-06,-4.3892e-07,
-9.5318e-07,-4.6534e-07, 1.7584e-05,-2.8817e-06,-1.0081e-06,
1.3259e-07, 1.8561e-05, 2.6602e-06,-1.7243e-06,-3.5533e-07,
2.3315e-08, 1.6239e-08, 7.3739e-09, 1.9570e-09,-2.5667e-10,
-5.8101e-10,-2.3947e-10, 4.5821e-12, 4.5024e-11, 3.8796e-07,
1.1239e-07,-2.9033e-08,-5.7237e-09, 2.4186e-09,-2.9670e-09,
1.0887e-06, 4.3737e-07, 8.3753e-08, 3.6771e-08, 1.6545e-09,
-1.3849e-08,-5.8855e-09, 2.1028e-06, 4.3725e-07,-1.7621e-07,
-3.0743e-08, 1.7119e-08, 8.4698e-06, 4.5395e-06, 1.5774e-06,
4.1647e-07,-7.9865e-08,-1.6003e-07,-6.0171e-08, 3.1692e-06,
3.6965e-07,-4.9333e-07,-2.8856e-08, 4.2819e-08, 2.0492e-04,
1.3705e-04, 4.0972e-05, 2.9565e-06,-1.4061e-06,-1.8775e-06,
-1.6306e-06,-3.6380e-07, 3.1213e-07, 2.0912e-07, 1.1965e-05,
9.9088e-07,-1.3565e-06,-2.3128e-07, 1.1379e-05, 2.6632e-06,
-1.6877e-06,-4.0541e-07, 9.8921e-08, 5.9262e-03, 3.2332e-03,
9.2954e-04, 1.6597e-04,-3.7972e-05,-7.1646e-05,-4.0073e-05,
2.5789e-08, 5.2124e-09,-2.2517e-10,-7.9400e-10, 3.3181e-06,
1.9117e-07, 1.0299e-07,-7.7443e-08, 2.2953e-06,-1.0879e-06,
3.4368e-07,-1.1230e-07, 1.6826e-08, 6.9774e-06, 9.2721e-07,
-1.8983e-08,-1.6068e-07, 1.5843e-06,-3.5616e-08,-1.1262e-07,
-4.3051e-08, 8.8140e-09, 3.4244e-05, 8.0163e-06, 2.9703e-06,
-1.6480e-07,-6.3282e-07, 2.3791e-06,-4.9305e-07,-1.7122e-07,
1.2147e-07, 7.2131e-05, 1.7699e-05, 8.4281e-06,-9.3966e-07,
-6.8048e-07, 5.3785e-05, 6.9745e-06,-2.3554e-06,-7.2141e-07,
3.8501e-07, 4.4794e-06,-6.2435e-07,-2.7340e-07, 2.3127e-08,
4.2314e-08, 3.3784e-06,-5.1356e-07,-2.4321e-07,-6.2698e-09,
3.0812e-08, 5.6419e-08, 1.6889e-08, 2.7037e-09,-1.7433e-09,
-9.4507e-10, 1.9714e-06, 4.2743e-08,-7.4114e-08,-2.9304e-08,
2.8973e-06, 1.0079e-06, 2.9561e-07,-6.2977e-09,-4.5782e-08,
-2.4331e-08, 4.3284e-06, 2.8398e-07,-3.2705e-07,-1.5079e-07,
5.2686e-06, 1.6539e-06, 3.6079e-07,-1.1589e-07,-5.4904e-08,
7.0939e-06,-1.9534e-06, 3.2391e-08, 5.5702e-08, 3.9072e-05,
1.7299e-05, 5.1530e-06,-6.0911e-07,-1.2652e-06,-4.6507e-07,
1.1506e-05,-2.0422e-06,-6.1195e-07, 5.8641e-08, 1.0150e-05,
-4.6977e-07,-6.9446e-07, 2.2516e-08, 1.1109e-07, 3.6715e-05,
5.6186e-06,-3.2309e-06,-1.6403e-06, 4.1051e-05, 2.3335e-05,
7.8106e-06, 2.0279e-07,-8.1139e-07,-3.7619e-07,-1.4982e-07,
2.5621e-05,-1.0798e-05, 1.4607e-06, 3.2421e-07, 6.5478e-09,
2.7233e-09, 3.8646e-10,-1.9143e-10,-1.0483e-10,-4.8664e-11,
1.0698e-06, 2.7752e-07,-2.6636e-08,-3.7583e-08, 2.6989e-07,
3.0325e-08,-2.4613e-08,-1.0828e-08, 2.2522e-09, 8.0777e-06,
2.2558e-06, 7.4760e-08,-2.4140e-07,-5.4292e-08, 8.8362e-07,
9.5045e-08,-5.0543e-08,-1.9134e-08, 1.1284e-05, 4.0659e-06,
7.6246e-07,-9.0394e-08,-8.7408e-08, 1.2192e-06,-2.0362e-07,
-1.0700e-07, 2.1990e-08, 1.2519e-08, 5.2758e-05, 2.0175e-05,
3.6690e-06,-4.9014e-07,-7.0474e-07,-3.9931e-07, 4.7638e-05,
1.5546e-05, 6.2294e-07,-1.3428e-06,-1.8177e-07, 4.1203e-06,
-4.9340e-07,-3.0198e-07,-1.5902e-08, 2.8567e-08, 2.2397e-06,
-2.1306e-08,-1.7897e-07, 2.8178e-08, 4.1722e-08, 2.0594e-04,
1.2838e-04, 5.8219e-05, 1.1735e-05,-6.9778e-06,-6.2486e-06,
-1.3800e-06, 2.9170e-06,-7.0833e-07,-1.2673e-07, 4.5069e-08,
1.0974e-09, 4.7908e-10, 3.0294e-11,-7.0815e-11,-1.2039e-11,
5.6357e-12, 8.1274e-07, 4.5158e-07, 1.1655e-07,-2.1481e-08,
-2.2493e-08,-6.5884e-09, 9.8696e-08, 3.6499e-08, 1.4768e-09,
-8.0141e-09,-2.8147e-09, 5.8287e-06, 3.2469e-06, 9.4927e-07,
-7.2550e-08,-1.4231e-07,-5.8408e-08,-1.4468e-08, 4.6070e-07,
1.5956e-07,-3.2311e-09,-3.3487e-08,-7.9013e-09, 4.0092e-06,
1.2195e-06, 1.1192e-07,-1.3870e-07,-6.2194e-08, 5.4918e-07,
-5.1133e-08,-5.4844e-08,-3.1054e-09, 6.1049e-09, 2.4833e-05,
1.1280e-05, 2.2680e-06,-4.4637e-07,-2.8942e-07,-1.2382e-07,
6.8223e-05, 3.6505e-05, 7.8792e-06,-1.7946e-06,-1.4925e-06,
-4.6018e-07, 2.5677e-06,-7.2440e-08,-2.2441e-07,-4.1769e-08,
2.3833e-08, 1.1885e-06, 4.0457e-09,-1.2273e-07,-2.3271e-08,
1.5149e-08, 9.1912e-05, 5.6621e-05, 1.4338e-05,-2.6072e-06,
-2.3688e-06,-6.2490e-07,-1.4386e-07, 1.0821e-06,-1.6304e-07,
-7.9756e-08,-4.9881e-09, 9.9527e-09, 1.5288e-03, 9.8517e-04,
3.4966e-04, 3.4238e-05,-4.3610e-05,-3.2734e-05,-8.3676e-06,
9.6630e-09, 3.2976e-09, 2.1545e-10,-3.3938e-10,-1.4397e-10,
3.4140e-07, 7.6536e-08,-2.2485e-08,-1.8244e-08,-2.0611e-09,
1.3128e-06, 6.4165e-07, 1.4728e-07,-2.7600e-08,-3.0125e-08,
-1.0320e-08, 1.6295e-06, 3.3697e-07,-5.7547e-08,-7.3134e-08,
-1.5301e-08, 8.0375e-06, 4.0695e-06, 1.1441e-06,-5.8423e-08,
-1.6488e-07,-7.5920e-08, 2.1173e-06,-1.9184e-07,-2.4986e-07,
7.2656e-09, 2.7560e-08, 7.9040e-06, 3.3963e-06, 4.8082e-07,
-3.1619e-07,-1.6501e-07, 5.8017e-06,-5.5173e-07,-6.3613e-07,
1.9633e-08, 7.8681e-08, 9.4568e-06, 9.8227e-08,-7.8983e-07,
-2.0343e-07, 7.0351e-05, 3.6017e-05, 8.0504e-06,-1.7276e-06,
-1.5329e-06,-4.7799e-07, 3.5263e-05, 1.8639e-05, 4.2070e-06,
-4.2643e-07,-5.2726e-07,-2.5481e-07,-1.0144e-07, 4.4613e-06,
-8.7802e-07,-3.9633e-07, 6.7953e-08, 4.1539e-08, 1.8780e-04,
1.0547e-04, 2.0983e-05,-3.7076e-06,-3.7090e-06,-1.5934e-06,
-2.6146e-07, 1.6384e-05,-3.3765e-06,-7.8390e-07, 1.2382e-07,
1.5748e-05,-9.4352e-07,-4.8936e-07,-4.9967e-07, 3.8177e-10,
9.6781e-11,-1.9622e-11,-1.7859e-11,-4.4781e-12, 3.0310e-07,
1.1193e-07,-5.4059e-09,-1.4249e-08,-1.9549e-09, 4.7664e-08,
8.5684e-09,-5.0948e-09,-3.1313e-09, 4.9557e-10, 4.2702e-10,
2.3433e-06, 8.3824e-07, 2.6585e-08,-7.5944e-08,-1.5093e-08,
1.8775e-07, 2.7614e-08,-2.1193e-08,-1.0264e-08, 1.9660e-09,
1.1637e-09, 7.7890e-06, 3.2901e-06,-2.9753e-07,-5.3153e-07,
-5.2098e-08, 3.3947e-08, 5.5937e-07, 5.1363e-08,-8.5390e-08,
-2.2580e-08, 1.0857e-08, 3.5157e-09, 4.1303e-05, 2.1947e-05,
3.9480e-06,-1.4234e-06,-9.0220e-07,-2.4485e-07, 1.1031e-04,
6.6726e-05, 1.9581e-05,-4.3637e-07,-2.8911e-06,-1.4332e-06,
-3.2332e-07, 3.0922e-06, 2.0624e-07,-4.0771e-07,-1.0402e-07,
4.8807e-08, 1.2491e-06, 2.1187e-07,-1.5951e-07,-7.2537e-08,
1.4035e-08, 9.6514e-09, 2.5146e-05,-9.4490e-08,-3.4103e-06,
2.4020e-07, 2.9564e-07, 6.6566e-07,-3.0772e-08,-1.0055e-07,
-3.1082e-09, 1.4897e-08, 7.7189e-04, 3.3710e-04, 3.9258e-05,
-1.5271e-05,-6.0652e-06, 2.1986e-04,-3.3135e-05,-8.5682e-06,
-9.2988e-07, 3.8085e-06,-1.3259e-07,-3.8517e-07,-5.7276e-08,
3.7226e-08, 2.0825e-09, 1.6707e-10,-1.2443e-10,-3.9117e-11,
1.0068e-07, 6.2278e-09,-5.9169e-09,-3.9535e-09, 5.3334e-07,
2.2022e-07, 1.4522e-08,-2.8440e-08,-8.1406e-09, 6.0187e-07,
5.8318e-08,-2.7772e-08,-2.6639e-08, 3.0929e-06, 1.0648e-06,
1.2317e-07,-9.9233e-08,-4.0796e-08, 1.5217e-06, 8.3095e-08,
-1.9819e-07,-6.0417e-08, 2.9553e-08, 9.0905e-09, 8.6651e-06,
3.9125e-06,-2.2871e-07,-6.4651e-07,-7.4440e-08, 4.3543e-08,
4.4505e-06, 2.7350e-07,-4.6428e-07,-2.2293e-07, 5.5275e-08,
3.6505e-08, 8.3471e-06, 5.0215e-07,-1.1189e-06,-2.5404e-07,
1.3175e-07, 1.1687e-04, 7.0674e-05, 2.0219e-05,-1.2136e-06,
-3.2299e-06,-1.3981e-06,-2.8271e-07, 3.7298e-05, 2.2054e-05,
5.5510e-06,-9.1515e-07,-1.0832e-06,-3.6657e-07,-4.8581e-08,
2.2506e-06,-2.9469e-07,-2.1641e-07,-3.9630e-08, 5.2879e-08,
2.7894e-05,-3.9312e-06,-2.6413e-06, 5.5848e-07, 8.7829e-06,
-1.4800e-06,-7.0982e-07,-2.1645e-08, 1.3258e-07, 1.2248e-05,
-1.4244e-06,-7.6696e-07,-1.5029e-07, 6.8467e-08, 1.9392e-04,
-2.6676e-05,-8.0536e-06,-6.3609e-07, 2.3074e-05, 7.2856e-07,
-2.3751e-06,-5.8464e-07, 1.3685e-07, 1.9646e-08, 1.2918e-08,
4.6924e-09, 5.1593e-10,-4.4715e-10,-3.4091e-10,-9.7254e-11,
3.4117e-07, 1.2731e-07,-2.2300e-08,-2.5297e-08,-1.1877e-10,
2.3436e-09, 9.4463e-07, 4.9464e-07, 8.9138e-08,-2.2158e-08,
-1.2008e-08,-4.3715e-09,-2.8719e-09, 1.4091e-06, 4.8184e-07,
-9.6135e-08,-1.0945e-07,-9.3174e-09, 9.0166e-09, 4.3747e-06,
2.1053e-06, 1.7702e-07,-3.0956e-07,-1.2902e-07,-8.9418e-09,
1.4297e-06, 7.7612e-08,-1.7188e-07, 6.4348e-10, 1.1572e-08,
7.4828e-06, 4.0177e-06, 1.0890e-06, 8.6393e-08,-5.4701e-08,
-5.7656e-08,-3.3030e-08, 5.0379e-06, 1.2861e-07,-7.1313e-07,
-3.3703e-09, 7.9367e-08, 6.2235e-06, 9.8196e-07,-4.6416e-07,
-2.3278e-07, 2.9519e-05, 1.5275e-05, 3.1448e-06,-6.4571e-07,
-3.4445e-07, 3.6508e-05, 2.0524e-05, 3.1203e-06,-2.3606e-06,
-1.5012e-06,-2.5807e-07, 1.3807e-07, 9.9656e-08, 3.1628e-06,
-5.6361e-08,-3.8861e-07,-1.0385e-09, 2.9930e-08, 3.1351e-04,
2.3774e-04, 1.0342e-04, 1.2429e-05,-1.4107e-05,-9.2597e-06,
-1.7338e-06, 8.4903e-07, 7.3795e-07, 2.2065e-07, 1.2134e-05,
4.6651e-07,-9.3659e-07,-2.7485e-07, 1.2462e-05, 6.2559e-07,
-6.1634e-07,-5.1080e-07, 1.2493e-02, 8.2057e-03, 2.9562e-03,
3.3090e-04,-3.7349e-04,-2.8886e-04,-7.4606e-05, 1.2726e-05,
-1.6480e-07,-1.5006e-06,-1.1181e-07, 1.4846e-07, 8.7160e-03,
4.2652e-03, 5.2455e-04,-2.6363e-04,-7.9836e-05,
]
})
}
/// 氦原子碰撞速率系数。
///
/// 计算氦原子 19 个能级 (n=1,2,3,4) 之间的碰撞跃迁速率。
///
/// # 参数
///
/// * `temp` - 温度 (K)
///
/// # 返回值
///
/// 19x19 矩阵 `colhe1`,其中 `colhe1[i][j]` 是从能级 i+1 到能级 j+1 的碰撞速率 (cm^3/s)。
pub fn collhe(temp: f64) -> [[f64; 19]; 19] {
const C1: f64 = 3.849485;
const C2: f64 = 0.849485002;
const N: usize = 19;
let ener = get_ener();
let stwt = get_stwt();
let nstart = get_nstart();
let a = get_a_data();
let mut colhe1 = [[0.0; 19]; 19];
// 计算 Gamma 系数的变量
let xxx = 2.0 * (temp.log10() - C1) / C2;
let tfac = 1.0 / temp.sqrt();
// 遍历所有能级对
for il in 0..N - 1 {
for iu in il + 1..N {
// 计算跃迁索引 j (Fortran 1-indexed)
// Fortran: J=((IU*IU-3*IU+4)/2)+IL-1
// IU=iu+1, IL=il+1 (转换到 1-indexed)
// 展开简化: (iu^2 - iu + 2) / 2 + il
let iu_i32 = iu as i32;
let j = ((iu_i32 * iu_i32 - iu_i32 + 2) / 2 + il as i32) as usize;
// 边界检查
if j + 1 >= nstart.len() {
continue;
}
let n1 = (nstart[j] - 1) as usize;
let nf = (nstart[j + 1] - 1) as usize;
let nt = nf - n1 + 1;
if nt < 2 || nt > 10 {
continue;
}
let ntm2 = nt - 2;
// 边界检查 a 数组
if nf >= a.len() {
continue;
}
// Clenshaw 求和
let mut b = [0.0; 10];
b[nt - 1] = a[nf];
b[nt - 2] = xxx * b[nt - 1] + a[nf - 1];
let mut ir = (nt as isize) - 3;
let mut jj = (nf as isize) - 3;
for _ in 0..ntm2 {
if ir >= 0 && (ir as usize) < 10 && jj >= 0 && (jj as usize) < a.len() {
let ir_usize = ir as usize;
let jj_usize = jj as usize;
b[ir_usize] = xxx * b[ir_usize + 1] - b[ir_usize + 2] + a[jj_usize];
}
ir -= 1;
jj -= 1;
}
colhe1[iu][il] = (b[0] - b[2]) * tfac;
// 详细平衡计算逆跃迁
let x = (ener[il] - ener[iu]) / 8.62e-5 / temp;
colhe1[il][iu] = colhe1[iu][il] * stwt[iu] / stwt[il] * x.exp();
}
}
colhe1
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_relative_eq;
#[test]
fn test_collhe_10000k() {
let result = collhe(10000.0);
// 检查对角线元素为 0
for i in 0..19 {
assert_relative_eq!(result[i][i], 0.0, epsilon = 1e-30);
}
// 检查非对角线元素有正值
assert!(result[1][0] > 0.0); // 2^3S -> 1^1S
assert!(result[0][1] > 0.0); // 1^1S -> 2^3S
}
#[test]
fn test_collhe_detailed_balance() {
let result = collhe(10000.0);
let ener = get_ener();
let stwt = get_stwt();
for il in 0..5 {
for iu in il + 1..5 {
let x = (ener[il] - ener[iu]) / 8.62e-5 / 10000.0;
let ratio = result[il][iu] / result[iu][il];
let expected = stwt[iu] / stwt[il] * x.exp();
assert_relative_eq!(ratio, expected, epsilon = 1e-6);
}
}
}
#[test]
fn test_collhe_high_temp() {
let result = collhe(50000.0);
assert!(result[1][0] > 0.0);
}
}

307
src/math/cross.rs Normal file
View File

@ -0,0 +1,307 @@
//! 光电离截面计算。
//!
//! 重构自 TLUSTY `cross.f` 和 `crossd.f`。
use crate::state::{AtomicData, BasNum, ModelState};
// ============================================================================
// CROSS - 基本光电离截面
// ============================================================================
/// 计算光电离截面。
///
/// 使用频率插值计算束缚-自由跃迁的光电离截面。
///
/// # 参数
///
/// - `ibft` - 束缚-自由跃迁索引 (0-indexed)
/// - `ij` - 频率索引 (0-indexed)
/// - `model` - 模型状态 (包含 IJBF, AIJBF, BFCS)
///
/// # 返回
///
/// 光电离截面 (cm²)
///
/// # Fortran 原始代码
///
/// ```fortran
/// FUNCTION CROSS(IBFT,IJ)
/// IJ0=IJBF(IJ)
/// A1=AIJBF(IJ)
/// CROSS=A1*BFCS(IBFT,IJ0)+(UN-A1)*BFCS(IBFT,IJ0+1)
/// END
/// ```
pub fn cross(ibft: usize, ij: usize, model: &ModelState) -> f64 {
let ij0 = model.frqall.ijbf[ij] as usize;
let a1 = model.phoexp.aijbf[ij];
// 线性插值
let sig0 = model.phoexp.bfcs[ibft][ij0] as f64;
let sig1 = model.phoexp.bfcs[ibft][ij0 + 1] as f64;
a1 * sig0 + (1.0 - a1) * sig1
}
// ============================================================================
// CROSSD - 含双电子复合的光电离截面
// ============================================================================
/// 计算光电离截面(含双电子复合贡献)。
///
/// 在基本光电离截面的基础上,添加双电子复合的贡献。
/// 双电子复合只在特定条件下添加:
/// 1. ifdiel 标志非零
/// 2. 对应跃迁有双电子复合标志 (idiel > 0)
/// 3. 深度索引有效 (id > 0)
/// 4. 能级满足特定条件 (基态到第一激发态)
/// 5. 频率在阈值频率附近 (fr0 到 1.1*fr0)
///
/// # 参数
///
/// - `ibft` - 束缚-自由跃迁索引 (0-indexed)
/// - `ij` - 频率索引 (0-indexed)
/// - `id` - 深度索引 (1-indexed in Fortran, 0 表示不添加双电子复合)
/// - `model` - 模型状态
/// - `atomic` - 原子数据
/// - `config` - 配置参数
///
/// # 返回
///
/// 光电离截面 (cm²)
///
/// # Fortran 原始代码
///
/// ```fortran
/// FUNCTION CROSSD(IBFT,IJ,ID)
/// IJ0=IJBF(IJ)
/// A1=AIJBF(IJ)
/// CROSSD=A1*BFCS(IBFT,IJ0)+(UN-A1)*BFCS(IBFT,IJ0+1)
/// if(ifdiel.eq.0) return
/// ITR=ITRBF(IBFT)
/// if(idiel(itr).gt.0.and.id.gt.0) then
/// i=ilow(itr)
/// ion=iel(i)
/// if(i.eq.nfirst(ion).and.iup(itr).eq.nnext(ion)) then
/// if(freq(ij).ge.fr0(itr).and.freq(ij).le.fr0(itr)*1.1)
/// * crossd=crossd+diesig(ion,id)
/// end if
/// end if
/// END
/// ```
pub fn crossd(
ibft: usize,
ij: usize,
id: i32,
model: &ModelState,
atomic: &AtomicData,
basnum: &BasNum,
) -> f64 {
// 基本截面
let mut sigma = cross(ibft, ij, model);
// 检查是否启用双电子复合
if basnum.ifdiel == 0 {
return sigma;
}
// 检查深度索引有效性 (Fortran: id > 0)
if id <= 0 {
return sigma;
}
// 获取跃迁索引 (Fortran: ITR = ITRBF(IBFT))
let itr = model.obfpar.itrbf[ibft] as usize;
// 检查跃迁是否有双电子复合标志
if atomic.trapar.idiel[itr] <= 0 {
return sigma;
}
// 获取下能级索引 (Fortran: i = ilow(itr))
let i = atomic.trapar.ilow[itr] as usize;
// 获取该能级所属离子 (Fortran: ion = iel(i))
let ion = atomic.levpar.iel[i] as usize;
// 检查能级条件:
// 下能级是离子的基态 (i == nfirst(ion))
// 上能级是离子的第一激发态 (iup(itr) == nnext(ion))
let nfirst = atomic.ionpar.nfirst[ion];
let nnext = atomic.ionpar.nnext[ion];
let iup = atomic.trapar.iup[itr];
if (i as i32) != nfirst || iup != nnext {
return sigma;
}
// 检查频率范围 (Fortran: freq(ij) >= fr0(itr) .and. freq(ij) <= fr0(itr)*1.1)
let freq_ij = model.frqall.freq[ij];
let fr0 = atomic.trapar.fr0[itr];
let fr0_upper = fr0 * 1.1;
if freq_ij >= fr0 && freq_ij <= fr0_upper {
// 添加双电子复合截面贡献
// Fortran: id 是 1-indexed
let id_idx = (id - 1) as usize;
sigma += model.levadd.diesig[ion][id_idx];
}
sigma
}
#[cfg(test)]
mod tests {
use super::*;
fn create_test_model() -> ModelState {
let mut model = ModelState::new();
// 设置测试数据
model.frqall.ijbf[0] = 10;
model.frqall.ijbf[1] = 20;
model.phoexp.aijbf[0] = 0.5;
model.phoexp.aijbf[1] = 0.3;
// 设置截面数据
model.phoexp.bfcs[0][10] = 1e-18;
model.phoexp.bfcs[0][11] = 2e-18;
model.phoexp.bfcs[0][20] = 3e-18;
model.phoexp.bfcs[0][21] = 4e-18;
// 设置频率数据
model.frqall.freq[0] = 3.0e15; // Hz
// 设置跃迁索引
model.obfpar.itrbf[0] = 5;
model
}
fn create_test_atomic() -> AtomicData {
let mut atomic = AtomicData::new();
// 设置跃迁 5 的参数
atomic.trapar.ilow[5] = 10; // 下能级索引
atomic.trapar.iup[5] = 11; // 上能级索引
atomic.trapar.idiel[5] = 1; // 启用双电子复合
atomic.trapar.fr0[5] = 2.0e15; // 阈值频率
// 设置能级 10 的离子索引
atomic.levpar.iel[10] = 2; // 离子索引 2
// 设置离子 2 的参数
atomic.ionpar.nfirst[2] = 10; // 基态能级
atomic.ionpar.nnext[2] = 11; // 第一激发态
atomic
}
fn create_test_basnum() -> BasNum {
let mut basnum = BasNum::default();
basnum.ifdiel = 1; // 启用双电子复合
basnum
}
#[test]
fn test_cross_basic() {
let model = create_test_model();
// 测试基本插值
let result = cross(0, 0, &model);
// 0.5 * 1e-18 + 0.5 * 2e-18 = 1.5e-18
// 注意bfcs 是 f32精度约为 1e-24
assert!((result - 1.5e-18).abs() < 1e-24);
}
#[test]
fn test_cross_different_freq() {
let model = create_test_model();
let result = cross(0, 1, &model);
// 0.3 * 3e-18 + 0.7 * 4e-18 = 3.7e-18
// 注意bfcs 是 f32精度约为 1e-24
assert!((result - 3.7e-18).abs() < 1e-24);
}
#[test]
fn test_crossd_no_diel_flag() {
let model = create_test_model();
let atomic = create_test_atomic();
let mut basnum = create_test_basnum();
basnum.ifdiel = 0; // 禁用双电子复合
let result = crossd(0, 0, 1, &model, &atomic, &basnum);
let expected = cross(0, 0, &model);
assert!((result - expected).abs() < 1e-24);
}
#[test]
fn test_crossd_negative_id() {
let model = create_test_model();
let atomic = create_test_atomic();
let basnum = create_test_basnum();
// id <= 0 时不添加双电子复合
let result = crossd(0, 0, 0, &model, &atomic, &basnum);
let expected = cross(0, 0, &model);
assert!((result - expected).abs() < 1e-25);
let result = crossd(0, 0, -1, &model, &atomic, &basnum);
assert!((result - expected).abs() < 1e-25);
}
#[test]
fn test_crossd_with_diel_contribution() {
let mut model = create_test_model();
let atomic = create_test_atomic();
let basnum = create_test_basnum();
// 设置双电子复合截面
model.levadd.diesig[2][0] = 5e-19; // 离子 2, 深度 1 (id=1 -> idx=0)
// 频率 2.1e15 在 [2e15, 2.2e15] 范围内
model.frqall.freq[0] = 2.1e15;
let result = crossd(0, 0, 1, &model, &atomic, &basnum);
// 应该是基本截面 + 双电子复合贡献
let base = cross(0, 0, &model); // 1.5e-18
let expected = base + 5e-19; // 2.0e-18
assert!((result - expected).abs() < 1e-24);
}
#[test]
fn test_crossd_freq_outside_range() {
let mut model = create_test_model();
let atomic = create_test_atomic();
let basnum = create_test_basnum();
// 设置双电子复合截面
model.levadd.diesig[2][0] = 5e-19;
// 修改频率到范围外 (fr0*1.1 = 2.2e15)
model.frqall.freq[0] = 3.0e15; // 超过上限
let result = crossd(0, 0, 1, &model, &atomic, &basnum);
// 频率超出范围,不应添加双电子复合
let expected = cross(0, 0, &model);
assert!((result - expected).abs() < 1e-24);
}
#[test]
fn test_crossd_wrong_level_condition() {
let model = create_test_model();
let mut atomic = create_test_atomic();
let basnum = create_test_basnum();
// 修改 nnext 使条件不满足
atomic.ionpar.nnext[2] = 12; // 不是 11
let result = crossd(0, 0, 1, &model, &atomic, &basnum);
// 能级条件不满足,不应添加双电子复合
let expected = cross(0, 0, &model);
assert!((result - expected).abs() < 1e-24);
}
}

228
src/math/dielrc.rs Normal file
View File

@ -0,0 +1,228 @@
//! 双电子复合速率计算。
//!
//! 重构自 TLUSTY `dielrc.f`
//! 参考: Tim Kallman 的 XSTAR 程序,由 Omer Blaes 修改
//!
//! 计算 H, He, C, N, O, Ne, Mg, Si, S, Ar, Ca, Fe, Ni 的双电子复合速率。
use crate::data::{
DIELRC_ADI as ADI, DIELRC_BDI as BDI, DIELRC_CDD as CDD, DIELRC_DCFE as DCFE,
DIELRC_DEFE as DEFE, DIELRC_GFE as GFE, DIELRC_GLI as GLI, DIELRC_GNI as GNI,
DIELRC_INID as INID, DIELRC_ISTOREY as ISTOREY, DIELRC_RSTOREY as RSTOREY, DIELRC_T0 as T0,
DIELRC_T1 as T1, DIELRC_UU as UU,
};
use crate::math::expo;
// 常量
const CONS: f64 = 0.1239529 * 3.28805e15 / 13.595;
const HFRAC: f64 = 1.0;
const ERGSEV: f64 = 1.602192e-12;
/// 计算双电子复合速率和伪截面。
///
/// # 参数
///
/// * `iatom` - 原子序数 (1=H, 2=He, 6=C, 等)
/// * `iont` - 电离态 (1=中性, 2=一次电离, 等)
/// * `temp` - 温度 (K)
/// * `xpx` - 原子核密度 (cm^-3)
///
/// # 返回值
///
/// * `dirt` - 双电子复合速率 (cm^3/s)
/// * `sig0` - 伪截面 σ₀
///
/// # 离子编号
///
/// 离子编号从 1-168:
/// - 1=HI, 2=HeI, 3=HeII
/// - 4-9=C I 到 C VI
/// - 10-16=N I 到 N VII
/// - 17-24=O I 到 O VIII
/// - 25-34=Ne I 到 Ne X
/// - 35-46=Mg I 到 Mg XII
/// - 47-60=Si I 到 Si XIV
/// - 61-76=S I 到 S XVI
/// - 77-94=Ar I 到 Ar XVIII
/// - 95-114=Ca I 到 Ca XX
/// - 115-140=Fe I 到 Fe XXVI
/// - 141-168=Ni I 到 Ni XXVIII
pub fn dielrc(iatom: usize, iont: usize, temp: f64, xpx: f64) -> (f64, f64) {
// 温度单位转换: K -> 10^4 K
let t = temp / 1.0e4;
// 获取离子索引
// Fortran 1-indexed: inid(iont, iatom) -> Rust 0-indexed: INID[iont-1][iatom-1]
// 注意: 提取脚本已将 Fortran 列优先转换为 Rust 行优先
let ini = INID[iont - 1][iatom - 1] as usize;
// 如果没有数据,返回 0
if ini == 0 || ini > 168 {
return (0.0, 0.0);
}
let ekt = t * 0.861707;
let xst = t.sqrt();
let _hconst = HFRAC * ekt * ERGSEV;
let t3s2 = 1.0 / (t * xst);
let tmr = 1.0e-6 * t3s2;
let mut dirt = 0.0;
// Fe 离子的双电子复合 (离子编号 115-139)
if ini >= 115 && ini <= 139 {
// Fe 离子的 kk 索引 (1-25 对应离子 115-139)
let kk = ini - 114; // 1-25
// 对 4 个项求和
for n in 0..4 {
// Fortran: dcfe(kk, n) -> Rust: DCFE[kk-1][n]
// DCFE 是 [[f64; 4]; 26],所以 DCFE[kk-1][n]
dirt += DCFE[kk - 1][n] * expo(-DEFE[kk - 1][n] / ekt);
}
dirt *= tmr;
} else {
// Aldrovandi 和 Pequignot 速率
// 密度修正因子
let enn = xpx.powf(0.2);
// Fortran 1-indexed: cdd(ini) -> Rust: CDD[ini-1]
let ap = 1.0 / (1.0 + CDD[ini - 1] * enn);
// 复合速率公式
// Fortran 1-indexed -> Rust 0-indexed
dirt = ADI[ini - 1] * ap * 1.0e-6 * expo(-T0[ini - 1] / t)
* (1.0 + BDI[ini - 1] * expo(-T1[ini - 1] / t))
/ (t * t.sqrt());
// Storey 修正 (仅对特定离子)
let mut ist = 0;
// 检查是否需要 Storey 修正
// Fortran: istorey(ist) 是离子编号,我们需要检查 ini == istorey(ist) - 1
// 但原代码检查 j.ne.(istorey(ist)-1),所以实际上 ini == istorey(ist) - 1 才会进入
while ist < 13 {
let storey_ion = ISTOREY[ist] as usize;
if storey_ion == 0 {
break;
}
// 原代码: if ((j.ne.(istorey(ist)-1))...
// j = ini, 所以条件是 ini == istorey(ist) - 1
if ini == storey_ion - 1 && t <= 6.0 {
// RSTOREY 是 [[f64; 13]; 5]
let dirtemp = 1.0e-12
* (RSTOREY[0][ist] / t
+ RSTOREY[1][ist]
+ t * (RSTOREY[2][ist] + t * RSTOREY[3][ist]))
* t3s2
* expo(-RSTOREY[4][ist] / t);
dirt += dirtemp;
}
ist += 1;
}
}
// 计算伪截面
let sig0 = if dirt > 0.0 {
compute_sig0(iatom, iont, temp, dirt)
} else {
0.0
};
(dirt, sig0)
}
/// 计算伪截面 σ₀
fn compute_sig0(iatom: usize, iont: usize, temp: f64, dirt: f64) -> f64 {
// 获取统计权重比
let gg = if iatom <= 20 {
// Li 系列统计权重
let gp = if iont < 20 {
let g = GLI[iont]; // iont+1 在 Fortran 中是 iont 索引
if g > 0.0 { g } else { 1.0 }
} else {
1.0
};
let g_curr = GLI[iont - 1];
if g_curr > 0.0 { gp / g_curr } else { gp }
} else if iatom <= 26 {
// Fe 系列统计权重
let gp = if iont < 26 {
let g = GFE[iont];
if g > 0.0 { g } else { 1.0 }
} else {
1.0
};
let g_curr = GFE[iont - 1];
if g_curr > 0.0 { gp / g_curr } else { gp }
} else if iatom <= 28 {
// Ni 系列统计权重
let gp = if iont < 28 {
let g = GNI[iont];
if g > 0.0 { g } else { 1.0 }
} else {
1.0
};
let g_curr = GNI[iont - 1];
if g_curr > 0.0 { gp / g_curr } else { gp }
} else {
1.0
};
// 计算阈值频率
// Fortran 1-indexed: uu(iont, iatom) -> Rust: UU[iont-1][iatom-1]
let frq0 = CONS * UU[iont - 1][iatom - 1];
let frq1 = 1.1 * frq0;
let delfr = frq1 - frq0;
let fra = 0.5 * (frq0 + frq1);
let x = 1.0 - expo(-4.79928e-11 * delfr / temp);
let sig0 = dirt * 8.47272e24 * gg * temp.sqrt() / (fra * fra) / x;
sig0
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_dielrc_he() {
// He+ 双电子复合 (iont=2, iatom=2)
let (dirt, sig0) = dielrc(2, 2, 20000.0, 1e10);
// He+ 应该有有效的双电子复合
assert!(dirt >= 0.0);
assert!(sig0 >= 0.0);
}
#[test]
fn test_dielrc_c() {
// C IV 双电子复合 (iont=4, iatom=6)
let (dirt, sig0) = dielrc(6, 4, 20000.0, 1e10);
assert!(dirt > 0.0);
assert!(sig0 >= 0.0);
}
#[test]
fn test_dielrc_fe() {
// Fe XIV 双电子复合 (iont=14, iatom=26, 离子编号约 128)
let (dirt, sig0) = dielrc(26, 14, 200000.0, 1e10);
assert!(dirt > 0.0);
assert!(sig0 >= 0.0);
}
#[test]
fn test_dielrc_h_zero() {
// H 没有双电子复合
let (dirt, sig0) = dielrc(1, 1, 20000.0, 1e10);
assert_eq!(dirt, 0.0);
assert_eq!(sig0, 0.0);
}
#[test]
fn test_dielrc_o() {
// O VI 双电子复合 (iont=6, iatom=8)
let (dirt, sig0) = dielrc(8, 6, 50000.0, 1e10);
assert!(dirt > 0.0);
assert!(sig0 >= 0.0);
}
}

61
src/math/gamsp.rs Normal file
View File

@ -0,0 +1,61 @@
//! 用户自定义展宽参数。
//!
//! 重构自 TLUSTY `gamsp.f`。
//!
//! 这是一个占位函数,用于用户自定义展宽参数表达式。
/// 计算自定义展宽参数。
///
/// 这是一个占位函数,默认返回零。
/// 用户可以修改此函数来实现自定义的展宽参数表达式。
///
/// # 参数
///
/// - `itr` - 跃迁索引
/// - `t` - 温度
/// - `ane` - 电子密度
///
/// # 返回
///
/// 展宽参数值 (默认为 0)
///
/// # Fortran 原始代码
///
/// ```fortran
/// SUBROUTINE GAMSP(ITR,T,ANE,AGAM)
/// INCLUDE 'BASICS.FOR'
/// AGAM=0.
/// if(itr.le.0) return
/// t1=t
/// ane1=ane
/// RETURN
/// END
/// ```
pub fn gamsp(itr: i32, _t: f64, _ane: f64) -> f64 {
// 默认返回零
if itr <= 0 {
return 0.0;
}
// 原始代码中有 t1=t 和 ane1=ane但没有使用这些值
// 这可能是为用户预留的接口
0.0
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_gamsp_zero_itr() {
// ITR <= 0 应该返回 0
assert_eq!(gamsp(0, 10000.0, 1e14), 0.0);
assert_eq!(gamsp(-1, 10000.0, 1e14), 0.0);
}
#[test]
fn test_gamsp_positive_itr() {
// 默认实现返回 0
assert_eq!(gamsp(1, 10000.0, 1e14), 0.0);
assert_eq!(gamsp(10, 5000.0, 1e12), 0.0);
}
}

110
src/math/getwrd.rs Normal file
View File

@ -0,0 +1,110 @@
//! 文本单词提取。
//!
//! 重构自 TLUSTY `getwrd.f`
//!
//! 来自 MULTI - M. Carlsson (1976)。
/// 分隔符字符列表。
const SEPARATORS: [char; 7] = [' ', '(', ')', '=', '*', '/', ','];
/// 从文本中查找下一个单词。
///
/// 从索引 `k0` 开始查找下一个单词,返回单词的起始和结束索引。
/// 单词从第一个非分隔符字符开始,到最后一个连续非分隔符字符结束。
///
/// # 参数
///
/// * `text` - 输入文本
/// * `k0` - 开始查找的索引 (0-indexed)
///
/// # 返回值
///
/// `Some((k1, k2))` - 单词的起始和结束索引 (包含0-indexed)
/// `None` - 未找到新单词
///
/// # 备注
///
/// 来自 MULTI - M. Carlsson (1976)。
pub fn getwrd(text: &str, k0: usize) -> Option<(usize, usize)> {
let chars: Vec<char> = text.chars().collect();
let len = chars.len();
if k0 >= len {
return None;
}
let mut k1: Option<usize> = None;
for i in k0..len {
if k1.is_none() {
// 查找单词开始
if !SEPARATORS.contains(&chars[i]) {
k1 = Some(i);
}
} else {
// 查找单词结束
if SEPARATORS.contains(&chars[i]) {
// 单词在 i-1 结束
return Some((k1.unwrap(), i - 1));
}
}
}
// 如果找到了开始但没有遇到分隔符,单词到文本末尾
if let Some(start) = k1 {
Some((start, len - 1))
} else {
None
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_getwrd_simple() {
let text = "hello world";
assert_eq!(getwrd(text, 0), Some((0, 4))); // "hello"
}
#[test]
fn test_getwrd_second_word() {
let text = "hello world";
assert_eq!(getwrd(text, 5), Some((6, 10))); // "world"
}
#[test]
fn test_getwrd_no_word() {
let text = " ";
assert_eq!(getwrd(text, 0), None);
}
#[test]
fn test_getwrd_with_separators() {
let text = "a(b)=c";
assert_eq!(getwrd(text, 0), Some((0, 0))); // "a"
assert_eq!(getwrd(text, 1), Some((2, 2))); // "b"
assert_eq!(getwrd(text, 4), Some((5, 5))); // "c"
}
#[test]
fn test_getwrd_empty() {
let text = "";
assert_eq!(getwrd(text, 0), None);
}
#[test]
fn test_getwrd_out_of_bounds() {
let text = "hello";
assert_eq!(getwrd(text, 10), None);
}
#[test]
fn test_getwrd_comma_separator() {
let text = "a,b,c";
assert_eq!(getwrd(text, 0), Some((0, 0))); // "a"
assert_eq!(getwrd(text, 1), Some((2, 2))); // "b"
assert_eq!(getwrd(text, 3), Some((4, 4))); // "c"
}
}

351
src/math/gfree.rs Normal file
View File

@ -0,0 +1,351 @@
//! 氢自由-自由 Gaunt 因子计算。
//!
//! 重构自 TLUSTY `gfree0.f`, `gfree1.f` 和 `gfreed.f`。
// ============================================================================
// 常量参数
// ============================================================================
const THET0: f64 = 5.0404e3;
const A0: f64 = 1.0823;
const B0: f64 = 2.98e-2;
const C0: f64 = 6.70e-3;
const D0: f64 = 1.12e-2;
const A1: f64 = 3.9999187e-3;
const B1: f64 = -7.8622889e-5;
const C1: f64 = 1.070192;
const A2: f64 = 6.4628601e-2;
const B2: f64 = -6.1953813e-4;
const C2: f64 = 2.6061249e-1;
const A3: f64 = 3.7542343e-2;
const B3: f64 = 1.3983474e-5;
const C3: f64 = 5.7917786e-1;
const A4: f64 = 3.4169006e-1;
const B4: f64 = 1.1852264e-2;
const XMIN: f64 = 0.2;
const THMIN: f64 = 4.0e-2;
const C14: f64 = 2.997925e14;
// ============================================================================
// GFREE0 - 深度相关量预计算
// ============================================================================
/// 计算深度相关的氢自由-自由 Gaunt 因子参数。
///
/// 此函数预计算并填充 GffPar 结构中的 GF0-GF6 和 GF0D-GF6D 数组。
///
/// # 参数
///
/// - `id` - 深度索引 (0-indexed)
/// - `temp` - 温度数组
/// - `gffpar` - Gaunt 因子参数结构 (会被修改)
///
/// # Fortran 原始代码
///
/// ```fortran
/// SUBROUTINE GFREE0(ID)
/// INCLUDE 'BASICS.FOR'
/// INCLUDE 'MODELQ.FOR'
/// ...
/// T=TEMP(ID)
/// GF0(ID)=(A0+B0*THET)
/// ...
/// END
/// ```
pub fn gfree0(id: usize, temp: &[f64], gffpar: &mut crate::state::GffPar) {
let t = temp[id];
let thet0_over_t = THET0 / t;
// 计算 θ = 1 / max(θ₀/T, θ_min)
let thet = 1.0 / thet0_over_t.max(THMIN);
// 计算 Gaunt 因子分量
gffpar.gf0[id] = A0 + B0 * thet;
gffpar.gf1[id] = (A1 + B1 * thet) * thet + C1;
gffpar.gf2[id] = (A2 + B2 * thet) * thet + C2;
gffpar.gf3[id] = (A3 + B3 * thet) * thet + C3;
gffpar.gf4[id] = A4 + B4 * thet;
gffpar.gf5[id] = C0 + D0 * thet;
gffpar.gf6[id] = gffpar.gf0[id] + gffpar.gf5[id] / XMIN;
// 辅助量用于导数
let thet1 = 1.0 / THET0;
if thet0_over_t >= THMIN {
// 正常情况:计算导数
gffpar.gf0d[id] = B0 * thet1;
gffpar.gf1d[id] = (A1 + B1 * thet * 2.0) * thet1;
gffpar.gf2d[id] = (A2 + B2 * thet * 2.0) * thet1;
gffpar.gf3d[id] = (A3 + B3 * thet * 2.0) * thet1;
gffpar.gf4d[id] = B4 * thet1;
gffpar.gf5d[id] = D0 * thet1;
gffpar.gf6d[id] = gffpar.gf0d[id] + gffpar.gf5d[id] / XMIN;
} else {
// 饱和情况:导数为零
gffpar.gf0d[id] = 0.0;
gffpar.gf1d[id] = 0.0;
gffpar.gf2d[id] = 0.0;
gffpar.gf3d[id] = 0.0;
gffpar.gf4d[id] = 0.0;
gffpar.gf5d[id] = 0.0;
gffpar.gf6d[id] = 0.0;
}
}
// ============================================================================
// GFREED - Gaunt 因子计算
// ============================================================================
/// 计算氢自由-自由 Gaunt 因子及其导数。
///
/// # 参数
///
/// - `id` - 深度索引 (0-indexed)
/// - `fr` - 频率
/// - `ch` - 电荷
/// - `gffpar` - Gaunt 因子参数结构
///
/// # 返回
///
/// 元组 (GFR, GFRD)Gaunt 因子及其导数
///
/// # Fortran 原始代码
///
/// ```fortran
/// SUBROUTINE GFREED(ID,FR,CH,GFR,GFRD)
/// INCLUDE 'BASICS.FOR'
/// INCLUDE 'MODELQ.FOR'
/// ...
/// X=C14*CH/FR
/// IF(X.LT.UN) THEN
/// GFR=((GF4(ID)*X-GF3(ID))*X+GF2(ID))*X+GF1(ID)
/// ...
/// END IF
/// end
/// ```
pub fn gfreed(id: usize, fr: f64, ch: f64, gffpar: &crate::state::GffPar) -> (f64, f64) {
let x = C14 * ch / fr;
let xmini = 1.0 / XMIN;
let (gfr, gfrd) = if x < 1.0 {
// 低 X 区域:多项式拟合
let gfr = ((gffpar.gf4[id] * x - gffpar.gf3[id]) * x + gffpar.gf2[id]) * x + gffpar.gf1[id];
let gfrd = ((gffpar.gf4d[id] * x - gffpar.gf3d[id]) * x + gffpar.gf2d[id]) * x + gffpar.gf1d[id];
(gfr, gfrd)
} else if x < xmini {
// 中间区域:线性插值
let gfr = gffpar.gf0[id] + gffpar.gf5[id] * x;
let gfrd = gffpar.gf0d[id] + gffpar.gf5d[id] * x;
(gfr, gfrd)
} else {
// 高 X 区域:饱和值
(gffpar.gf6[id], gffpar.gf6d[id])
};
(gfr, gfrd)
}
// ============================================================================
// GFREE1 - Gaunt 因子 (仅值,无导数)
// ============================================================================
/// 计算氢自由-自由 Gaunt 因子 (仅值,无导数)。
///
/// 使用预先计算的系数进行插值。
///
/// # 参数
///
/// - `id` - 深度索引 (0-indexed)
/// - `x` - 无量纲频率参数 (C14 * CH / FR)
/// - `gffpar` - Gaunt 因子参数结构
///
/// # 返回
///
/// Gaunt 因子值
///
/// # Fortran 原始代码
///
/// ```fortran
/// FUNCTION GFREE1(ID,X)
/// IF(X.LT.UN) THEN
/// GFREE1=((GF4(ID)*X-GF3(ID))*X+GF2(ID))*X+GF1(ID)
/// ELSE IF(X.LT.XMINI) THEN
/// GFREE1=GF0(ID)+GF5(ID)*X
/// ELSE
/// GFREE1=GF6(ID)
/// END IF
/// END
/// ```
pub fn gfree1(id: usize, x: f64, gffpar: &crate::state::GffPar) -> f64 {
let xmini = 1.0 / XMIN;
if x < 1.0 {
// 低 X 区域:多项式拟合
((gffpar.gf4[id] * x - gffpar.gf3[id]) * x + gffpar.gf2[id]) * x + gffpar.gf1[id]
} else if x < xmini {
// 中间区域:线性插值
gffpar.gf0[id] + gffpar.gf5[id] * x
} else {
// 高 X 区域:饱和值
gffpar.gf6[id]
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::state::GffPar;
fn create_test_gffpar() -> GffPar {
let mut gffpar = GffPar::new();
// 使用典型温度 10000K
let temp = vec![10000.0; crate::state::MDEPTH];
// 初始化第一个深度点
gfree0(0, &temp, &mut gffpar);
gffpar
}
#[test]
fn test_gfree0_basic() {
let mut gffpar = GffPar::new();
let temp = vec![10000.0; crate::state::MDEPTH];
gfree0(0, &temp, &mut gffpar);
// 检查计算结果为正值
assert!(gffpar.gf0[0] > 0.0);
assert!(gffpar.gf1[0] > 0.0);
assert!(gffpar.gf2[0] > 0.0);
assert!(gffpar.gf3[0] > 0.0);
assert!(gffpar.gf4[0] > 0.0);
assert!(gffpar.gf5[0] > 0.0);
assert!(gffpar.gf6[0] > 0.0);
}
#[test]
fn test_gfree0_low_temperature() {
let mut gffpar = GffPar::new();
// 低温测试T = 100K
// THT = THET0/T = 5040.4/100 = 50.4 > THMIN = 0.04
// 所以进入 IF 分支,计算非零导数
let temp = vec![100.0; crate::state::MDEPTH];
gfree0(0, &temp, &mut gffpar);
// 低温下导数应该非零
assert!(gffpar.gf0d[0].abs() > 0.0);
assert!(gffpar.gf1d[0].abs() > 0.0);
assert!(gffpar.gf2d[0].abs() > 0.0);
}
#[test]
fn test_gfree0_high_temperature() {
let mut gffpar = GffPar::new();
// 高温测试T = 200000K
// THT = THET0/T = 5040.4/200000 = 0.025 < THMIN = 0.04
// 所以进入 ELSE 分支,导数为零
let temp = vec![200000.0; crate::state::MDEPTH];
gfree0(0, &temp, &mut gffpar);
// 高温下导数应该为零(饱和)
assert_eq!(gffpar.gf0d[0], 0.0);
assert_eq!(gffpar.gf1d[0], 0.0);
assert_eq!(gffpar.gf2d[0], 0.0);
}
#[test]
fn test_gfreed_low_x() {
let gffpar = create_test_gffpar();
// 使用高频率产生低 X 值
let fr = 1e16;
let ch = 1.0;
let (gfr, gfrd) = gfreed(0, fr, ch, &gffpar);
// Gaunt 因子应该在合理范围内
assert!(gfr > 0.0);
}
#[test]
fn test_gfreed_mid_x() {
let gffpar = create_test_gffpar();
// 使用中等频率产生中间 X 值
let fr = 1e15;
let ch = 1.0;
let (gfr, gfrd) = gfreed(0, fr, ch, &gffpar);
assert!(gfr > 0.0);
}
#[test]
fn test_gfreed_high_x() {
let gffpar = create_test_gffpar();
// 使用低频率产生高 X 值
let fr = 1e13;
let ch = 1.0;
let (gfr, gfrd) = gfreed(0, fr, ch, &gffpar);
// 高 X 应该使用 GF6 饱和值
assert!((gfr - gffpar.gf6[0]).abs() < 1e-10);
}
#[test]
fn test_gfree_consistency() {
// 测试 gfree0 和 gfreed 的一致性
let mut gffpar = GffPar::new();
let temp = vec![10000.0; crate::state::MDEPTH];
gfree0(0, &temp, &mut gffpar);
// 在边界条件下测试连续性
let ch = 1.0;
// X ≈ 1 边界
let fr1 = C14 * ch * 0.999;
let fr2 = C14 * ch * 1.001;
let (gfr1, _) = gfreed(0, fr1, ch, &gffpar);
let (gfr2, _) = gfreed(0, fr2, ch, &gffpar);
// 在边界附近应该连续
assert!((gfr1 - gfr2).abs() < 1.0);
}
#[test]
fn test_gfree1_basic() {
let gffpar = create_test_gffpar();
// 测试低 X 区域
let x_low = 0.5;
let result_low = gfree1(0, x_low, &gffpar);
assert!(result_low > 0.0);
// 测试中间区域
let x_mid = 2.0;
let result_mid = gfree1(0, x_mid, &gffpar);
assert!(result_mid > 0.0);
// 测试高 X 区域
let x_high = 10.0;
let result_high = gfree1(0, x_high, &gffpar);
assert!(result_high > 0.0);
// 高 X 应该等于 GF6 饱和值
assert!((result_high - gffpar.gf6[0]).abs() < 1e-10);
}
#[test]
fn test_gfree1_vs_gfreed() {
// gfree1 和 gfreed 的 GFR 部分应该一致
let gffpar = create_test_gffpar();
for x in [0.5, 1.0, 2.0, 5.0, 10.0].iter() {
let fr = C14 / x; // X = C14 * 1 / FR
let (gfr, _) = gfreed(0, fr, 1.0, &gffpar);
let gfr1 = gfree1(0, *x, &gffpar);
assert!((gfr - gfr1).abs() < 1e-10, "Mismatch at x={}", x);
}
}
}

117
src/math/irc.rs Normal file
View File

@ -0,0 +1,117 @@
//! 氢原子碰撞电离速率。
//!
//! 重构自 TLUSTY `irc.f`
//!
//! 基于 Johnson (1972)。
use crate::math::{expinx, szirc};
/// 氢原子碰撞电离激发速率。
///
/// 计算从状态 N 由于电子碰撞导致的氢原子电离激发速率。
///
/// # 参数
///
/// * `n` - 主量子数
/// * `t` - 温度 (K)
/// * `ic` - 电荷 (1 = H)
/// * `rno` - 连续谱起始能级
///
/// # 返回值
///
/// 激发速率 SE (cm³/s)。
///
/// # 备注
///
/// 基于 Johnson (1972)。
/// Tim Kallman 的 XSTAR 程序的修改版本。
pub fn irc(n: i32, t: f64, ic: i32, rno: f64) -> f64 {
// 非 H 原子调用 szirc
if ic != 1 {
return szirc(n as usize, t, ic, rno);
}
let nf = n as f64;
let xo = 1.0 - nf * nf / rno / rno;
let yn = xo * 157803.0 / (t * nf * nf);
let (an, bn, rn) = if n <= 1 {
let an = 1.9603 * nf
* (1.133 / 3.0 / xo.powi(3) - 0.4059 / 4.0 / xo.powi(4) + 0.07014 / 5.0 / xo.powi(5));
let bn = 2.0 / 3.0 * nf * nf / xo * (3.0 + 2.0 / xo - 0.603 / (xo * xo));
let rn = 0.45;
(an, bn, rn)
} else if n == 2 {
let an = 1.9603 * nf
* (1.0785 / 3.0 / xo.powi(3) - 0.2319 / 4.0 / xo.powi(4) + 0.02947 / 5.0 / xo.powi(5));
let bn = (4.0 - 18.63 / nf + 36.24 / (nf * nf) - 28.09 / (nf * nf * nf)) / nf;
let bn = 2.0 / 3.0 * nf * nf / xo * (3.0 + 2.0 / xo + bn / (xo * xo));
let rn = 0.653;
(an, bn, rn)
} else {
let g0 = (0.9935 + 0.2328 / nf - 0.1296 / (nf * nf)) / 3.0 / xo.powi(3);
let g1 = -(0.6282 - 0.5598 / nf + 0.5299 / (nf * nf)) / (nf * 4.0) / xo.powi(4);
let g2 = (0.3887 - 1.181 / nf + 1.470 / (nf * nf)) / (nf * nf * 5.0) / xo.powi(5);
let an = 1.9603 * nf * (g0 + g1 + g2);
let bn = (4.0 - 18.63 / nf + 36.24 / (nf * nf) - 28.09 / (nf * nf * nf)) / nf;
let bn = (3.0 + 2.0 / xo + bn / (xo * xo)) * 2.0 * nf * nf / 3.0 / xo;
let rn = 1.94 * nf.powf(-1.57);
(an, bn, rn)
};
let rn = rn * xo;
let zn = rn + yn;
let ey = expinx(yn);
let ez = expinx(zn);
let mut se = an * (ey / (yn * yn) - (-rn).exp() * ez / (zn * zn));
let ey = 1.0 + 1.0 / yn - ey * (2.0 / yn + 1.0);
let ez = (-rn).exp() * (1.0 + 1.0 / zn - ez * (2.0 / zn + 1.0));
se = se + (bn - an * (2.0 * nf * nf / xo).ln()) * (ey - ez);
se = se * t.sqrt() * yn * yn * nf * nf * 1.095e-10 / xo;
se
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_irc_hydrogen_n1() {
// H I 从 n=1 电离rno > n² = 1
let result = irc(1, 10000.0, 1, 2.0);
assert!(result.is_finite());
}
#[test]
fn test_irc_hydrogen_n2() {
// H I 从 n=2 电离rno > n² = 4
let result = irc(2, 10000.0, 1, 5.0);
assert!(result.is_finite());
}
#[test]
fn test_irc_hydrogen_n3() {
// H I 从 n=3 电离rno > n² = 9
let result = irc(3, 10000.0, 1, 10.0);
assert!(result.is_finite());
}
#[test]
fn test_irc_hydrogen_high_temp() {
// 高温
let result = irc(1, 50000.0, 1, 2.0);
assert!(result.is_finite());
}
#[test]
fn test_irc_non_hydrogen() {
// 非 H 原子 (调用 szirc)
let result = irc(1, 10000.0, 2, 2.0);
assert!(result.is_finite());
}
}

View File

@ -1,73 +1,117 @@
//! 数学工具函数,重构自 TLUSTY Fortran。
mod angset;
mod betah;
mod bkhsgo;
mod butler;
mod carbon;
mod ceh12;
mod cion;
mod ckoest;
mod collhe;
mod cross;
mod dielrc;
mod erfcx;
mod expo;
mod expint;
mod ffcros;
mod gauleg;
mod getwrd;
mod gami;
mod gamsp;
mod gfree;
mod gaunt;
mod gntk;
mod grcor;
mod hephot;
mod hidalg;
mod indexx;
mod irc;
mod interpolate;
mod laguer;
mod locate;
mod minv3;
mod quartc;
mod pffe;
mod pfni;
mod pfspec;
mod quit;
mod raph;
mod reiman;
mod sbfch;
mod sbfhe1;
mod sbfhmi;
mod sbfhmi_old;
mod sbfoh;
mod sghe12;
mod sffhmi;
mod spsigk;
mod stark0;
mod szirc;
mod tiopf;
mod tridag;
mod ubeta;
mod verner;
mod voigt;
mod voigte;
mod xk2dop;
mod wn;
mod ylintp;
pub use angset::angset;
pub use betah::betah;
pub use bkhsgo::bkhsgo;
pub use butler::butler;
pub use carbon::carbon;
pub use ceh12::ceh12;
pub use cion::cion;
pub use ckoest::ckoest;
pub use collhe::collhe;
pub use cross::{cross, crossd};
pub use dielrc::dielrc;
pub use erfcx::{erfcin, erfcx};
pub use expo::expo;
pub use expint::{eint, expinx};
pub use ffcros::ffcros;
pub use gauleg::gauleg;
pub use getwrd::getwrd;
pub use gami::gami;
pub use gamsp::gamsp;
pub use gfree::{gfree0, gfreed};
pub use gaunt::gaunt;
pub use gntk::gntk;
pub use grcor::grcor;
pub use hephot::hephot;
pub use hidalg::hidalg;
pub use indexx::indexx;
pub use irc::irc;
pub use interpolate::{lagran, yint};
pub use laguer::laguer;
pub use locate::locate;
pub use minv3::minv3;
pub use quartc::quartc;
pub use pffe::pffe;
pub use pfni::pfni;
pub use pfspec::pfspec;
pub use quit::{quit, quit_error};
pub use raph::raph;
pub use reiman::reiman;
pub use sbfch::sbfch;
pub use sbfhe1::sbfhe1;
pub use sbfhmi::sbfhmi;
pub use sbfhmi_old::sbfhmi_old;
pub use sbfoh::sbfoh;
pub use sghe12::sghe12;
pub use sffhmi::sffhmi;
pub use spsigk::spsigk;
pub use stark0::stark0;
pub use szirc::szirc;
pub use tiopf::tiopf;
pub use tridag::tridag;
pub use ubeta::ubeta;
pub use verner::verner;
pub use voigt::voigt;
pub use voigte::voigte;
pub use wn::wn;
pub use xk2dop::xk2dop;
pub use ylintp::ylintp;

193
src/math/pffe.rs Normal file
View File

@ -0,0 +1,193 @@
//! Fe IV - Fe IX 的配分函数。
//!
//! 重构自 TLUSTY `pffe.f`
//! 参考: Fischel and Sparks, 1971, NASA SP-3066
//!
//! 在温度和电子密度网格上进行二维插值计算配分函数。
use crate::data::{PFFE_NCA as NCA, PFFE_PN as PN, PFFE_P4A as P4A, PFFE_P4B as P4B, PFFE_P5A as P5A, PFFE_P5B as P5B, PFFE_P6A as P6A, PFFE_P6B as P6B, PFFE_P7A as P7A, PFFE_P7B as P7B, PFFE_P8A as P8A, PFFE_P8B as P8B, PFFE_P9A as P9A, PFFE_P9B as P9B, PFFE_TT as TT};
// 常量
const XEN: f64 = 2.302585093; // ln(10)
const XMIL: f64 = 0.001;
const XMILEN: f64 = XMIL * XEN;
const XBTZ: f64 = 1.38054e-16; // 玻尔兹曼常数
/// Fe IV - Fe IX 的配分函数。
///
/// 在温度和电子密度网格上进行二维插值。
///
/// # 参数
///
/// * `ion` - 电离态 (4 = Fe IV, 5 = Fe V, ..., 9 = Fe IX)
/// * `t` - 温度 (K)
/// * `ane` - 电子密度 (cm^-3)
///
/// # 返回值
///
/// * `pf` - 配分函数
/// * `dut` - d(PF)/dT 导数
/// * `dun` - d(PF)/d(ANE) 导数
pub fn pffe(ion: i32, t: f64, ane: f64) -> (f64, f64, f64) {
// Fortran 1-indexed: nca(ion) → Rust 0-indexed: NCA[ion - 1]
// ion: 4=FeIV, 5=FeV, ..., 9=FeIX
let na = NCA[ion as usize - 1] as usize;
let _nb = 50 - na;
let pne = (ane * XBTZ * t).log10();
let t0 = XMIL * t;
// 查找电子密度索引
let (j1, j2) = find_pn_indices(pne);
// 查找温度索引
let (i1, i2) = find_tt_indices(t0);
// 根据温度区间选择插值方式
let (px1, px2, py1, py2) = if i2 < na {
// 在纯 a 区域
get_a_values(ion, i1, i2)
} else if i1 == na - 1 {
// 在 a/b 边界
get_boundary_values(ion, i1, i2, j1, j2, na)
} else {
// 在纯 b 区域
get_b_values(ion, i1, i2, j1, j2, na)
};
// 插值计算
let dlgunx = px2 - px1;
let px = px1 + (pne - PN[j1]) * dlgunx;
let dlguny = py2 - py1;
let py = py1 + (pne - PN[j1]) * dlguny;
let delt = TT[i2] - TT[i1];
let (pf_log, dlgut, dlgun) = if delt != 0.0 {
let dlgut = (py - px) / delt;
let pf_log = px + (t0 - TT[i1]) * dlgut;
let dlgun = dlgunx + (t0 - TT[i1]) / delt * (dlguny - dlgunx);
(pf_log, dlgut, dlgun)
} else {
(px, 0.0, dlgunx)
};
let pf = (XEN * pf_log).exp();
let dut = XMILEN * pf * dlgut;
let dun = (dlgun * pf - t * dut) / ane;
(pf, dut, dun)
}
/// 查找电子密度索引
fn find_pn_indices(pne: f64) -> (usize, usize) {
if pne < PN[0] {
return (0, 0);
}
if pne > PN[9] {
return (9, 9);
}
for j in 0..9 {
if pne >= PN[j] && pne < PN[j + 1] {
return (j, j + 1);
}
}
(0, 1)
}
/// 查找温度索引
fn find_tt_indices(t0: f64) -> (usize, usize) {
for i in 0..49 {
if t0 >= TT[i] && t0 < TT[i + 1] {
return (i, i + 1);
}
}
if t0 > TT[49] {
(49, 49)
} else {
(0, 1)
}
}
/// 从 a 数组获取值 (低密度区)
fn get_a_values(ion: i32, i1: usize, i2: usize) -> (f64, f64, f64, f64) {
match ion {
4 => (P4A[i1], P4A[i1], P4A[i2], P4A[i2]),
5 => (P5A[i1], P5A[i1], P5A[i2], P5A[i2]),
6 => (P6A[i1], P6A[i1], P6A[i2], P6A[i2]),
7 => (P7A[i1], P7A[i1], P7A[i2], P7A[i2]),
8 => (P8A[i1], P8A[i1], P8A[i2], P8A[i2]),
9 => (P9A[i1], P9A[i1], P9A[i2], P9A[i2]),
_ => (0.0, 0.0, 0.0, 0.0),
}
}
/// 从边界区域获取值
fn get_boundary_values(
ion: i32,
i1: usize,
i2: usize,
j1: usize,
j2: usize,
na: usize,
) -> (f64, f64, f64, f64) {
let i_idx = i2 - na;
match ion {
4 => (P4A[i1], P4A[i1], P4B[j1][i_idx], P4B[j2][i_idx]),
5 => (P5A[i1], P5A[i1], P5B[j1][i_idx], P5B[j2][i_idx]),
6 => (P6A[i1], P6A[i1], P6B[j1][i_idx], P6B[j2][i_idx]),
7 => (P7A[i1], P7A[i1], P7B[j1][i_idx], P7B[j2][i_idx]),
8 => (P8A[i1], P8A[i1], P8B[j1][i_idx], P8B[j2][i_idx]),
9 => (P9A[i1], P9A[i1], P9B[j1][i_idx], P9B[j2][i_idx]),
_ => (0.0, 0.0, 0.0, 0.0),
}
}
/// 从 b 数组获取值 (高密度区)
fn get_b_values(
ion: i32,
i1: usize,
i2: usize,
j1: usize,
j2: usize,
na: usize,
) -> (f64, f64, f64, f64) {
let i1_idx = i1 - na;
let i2_idx = i2 - na;
match ion {
4 => (P4B[j1][i1_idx], P4B[j2][i1_idx], P4B[j1][i2_idx], P4B[j2][i2_idx]),
5 => (P5B[j1][i1_idx], P5B[j2][i1_idx], P5B[j1][i2_idx], P5B[j2][i2_idx]),
6 => (P6B[j1][i1_idx], P6B[j2][i1_idx], P6B[j1][i2_idx], P6B[j2][i2_idx]),
7 => (P7B[j1][i1_idx], P7B[j2][i1_idx], P7B[j1][i2_idx], P7B[j2][i2_idx]),
8 => (P8B[j1][i1_idx], P8B[j2][i1_idx], P8B[j1][i2_idx], P8B[j2][i2_idx]),
9 => (P9B[j1][i1_idx], P9B[j2][i1_idx], P9B[j1][i2_idx], P9B[j2][i2_idx]),
_ => (0.0, 0.0, 0.0, 0.0),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_pffe_fe4() {
let (pf, dut, dun) = pffe(4, 20000.0, 1e12);
assert!(pf > 0.0);
assert!(dut.is_finite());
assert!(dun.is_finite());
}
#[test]
fn test_pffe_fe5() {
let (pf, dut, dun) = pffe(5, 25000.0, 1e13);
assert!(pf > 0.0);
assert!(dut.is_finite());
assert!(dun.is_finite());
}
#[test]
fn test_pffe_fe9() {
let (pf, dut, dun) = pffe(9, 50000.0, 1e14);
assert!(pf >= 0.0);
assert!(dut.is_finite());
assert!(dun.is_finite());
}
}

126
src/math/pfni.rs Normal file
View File

@ -0,0 +1,126 @@
//! Ni IV - Ni IX 的配分函数。
//!
//! 重构自 TLUSTY `pfni.f`
//! 参考: Kurucz (1992),超过 12,000 个能级
//!
//! 配分函数只依赖于温度(没有密度依赖)。
use crate::data::{
PFNI_G0, PFNI_P4A, PFNI_P4B, PFNI_P5A, PFNI_P5B, PFNI_P6A, PFNI_P6B, PFNI_P7A, PFNI_P7B,
PFNI_P8A, PFNI_P8B, PFNI_P9A, PFNI_P9B, PFNI_XEN, PFNI_XMIL,
};
/// Ni IV - Ni IX 的配分函数。
///
/// 在温度网格上进行插值。
///
/// # 参数
///
/// * `ion` - 电离态 (4 = Ni IV, 5 = Ni V, ..., 9 = Ni IX)
/// * `t` - 温度 (K)
///
/// # 返回值
///
/// * `pf` - 配分函数
/// * `dut` - d(PF)/dT 导数
/// * `dun` - d(PF)/d(ANE) 导数 (总是 0因为没有密度依赖)
pub fn pfni(ion: i32, t: f64) -> (f64, f64, f64) {
// 温度低于 12000 K 时,使用基态统计权重
if t < 12000.0 {
// Fortran 1-indexed: g0(ion-3) -> Rust: G0[ion-4]
let pf = PFNI_G0[ion as usize - 4];
return (pf, 0.0, 0.0);
}
// 计算温度索引
let it = (t / 1000.0) as i32;
let it = if it >= 350 { 349 } else { it };
let t1 = 1000.0 * it as f64;
// 根据离子和温度选择数组
let (xu1, xu2) = if t <= 200000.0 {
// 使用 "a" 数组 (11K - 200K, 索引 11-200)
let idx1 = (it - 11) as usize;
let idx2 = (it - 10) as usize;
match ion {
4 => (PFNI_P4A[idx1], PFNI_P4A[idx2]),
5 => (PFNI_P5A[idx1], PFNI_P5A[idx2]),
6 => (PFNI_P6A[idx1], PFNI_P6A[idx2]),
7 => (PFNI_P7A[idx1], PFNI_P7A[idx2]),
8 => (PFNI_P8A[idx1], PFNI_P8A[idx2]),
9 => (PFNI_P9A[idx1], PFNI_P9A[idx2]),
_ => return (1.0, 0.0, 0.0),
}
} else {
// 使用 "b" 数组 (181K - 350K, 索引 181-350)
let idx1 = (it - 181) as usize;
let idx2 = (it - 180) as usize;
match ion {
4 => (PFNI_P4B[idx1], PFNI_P4B[idx2]),
5 => (PFNI_P5B[idx1], PFNI_P5B[idx2]),
6 => (PFNI_P6B[idx1], PFNI_P6B[idx2]),
7 => (PFNI_P7B[idx1], PFNI_P7B[idx2]),
8 => (PFNI_P8B[idx1], PFNI_P8B[idx2]),
9 => (PFNI_P9B[idx1], PFNI_P9B[idx2]),
_ => return (1.0, 0.0, 0.0),
}
};
// 插值计算
let dxt = PFNI_XMIL * (xu2 - xu1);
let xu = xu1 + (t - t1) * dxt;
let pf = (PFNI_XEN * xu).exp();
let dut = PFNI_XEN * pf * dxt;
(pf, dut, 0.0)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_pfni_ni4() {
let (pf, dut, dun) = pfni(4, 20000.0);
assert!(pf > 0.0);
assert!(dut.is_finite());
assert_eq!(dun, 0.0);
}
#[test]
fn test_pfni_ni5() {
let (pf, dut, dun) = pfni(5, 50000.0);
assert!(pf > 0.0);
assert!(dut.is_finite());
assert_eq!(dun, 0.0);
}
#[test]
fn test_pfni_ni9() {
let (pf, dut, dun) = pfni(9, 100000.0);
assert!(pf > 0.0);
assert!(dut.is_finite());
assert_eq!(dun, 0.0);
}
#[test]
fn test_pfni_low_temp() {
// 低温使用基态统计权重
let (pf, dut, dun) = pfni(4, 10000.0);
assert_eq!(pf, PFNI_G0[0]); // Ni IV 的 g0
assert_eq!(dut, 0.0);
assert_eq!(dun, 0.0);
}
#[test]
fn test_pfni_high_temp() {
// 高温使用 "b" 数组
let (pf, dut, dun) = pfni(6, 250000.0);
assert!(pf > 0.0);
assert!(dut.is_finite());
assert_eq!(dun, 0.0);
}
}

152
src/math/pfspec.rs Normal file
View File

@ -0,0 +1,152 @@
//! 特殊元素的配分函数。
//!
//! 重构自 TLUSTY `pfspec.f`
//!
//! 为特定原子Ne 和 S的特定电离态返回固定的配分函数值。
//! 这是一个用户自定义过程,用于非标准配分函数计算。
/// 特殊元素的配分函数。
///
/// 为特定原子和电离态返回预设的配分函数值。
/// 目前只支持 Ne (Z=10) 和 S (Z=16)。
///
/// # 参数
///
/// * `iat` - 原子序数 (Z)
/// * `izi` - 电离电荷 (+1 表示中性,+2 表示一次电离,依此类推)
/// * `_t` - 温度 (K),此函数中未使用
/// * `_ane` - 电子密度 (cm^-3),此函数中未使用
///
/// # 返回值
///
/// * `u` - 配分函数
/// * `dut` - dU/dT 导数 (此函数中始终为 0)
/// * `dun` - dU/d(ANE) 导数 (此函数中始终为 0)
///
/// # 备注
///
/// 这是一个占位函数,为特定情况提供固定值。
/// 支持的原子和电离态:
///
/// | 原子 | Z | 电离态 | U |
/// |------|---|--------|---|
/// | Ne | 10 | Ne V | 9 |
/// | Ne | 10 | Ne VI | 6 |
/// | Ne | 10 | Ne VII | 12 |
/// | Ne | 10 | Ne VIII | 8 |
/// | Ne | 10 | Ne IX | 1 |
/// | S | 16 | S V | 1 |
/// | S | 16 | S VI | 2 |
/// | S | 16 | S VII | 1 |
/// | S | 16 | S VIII | 6 |
/// | S | 16 | S IX | 9 |
pub fn pfspec(iat: i32, izi: i32, _t: f64, _ane: f64) -> (f64, f64, f64) {
// 默认值
let mut u = 0.0;
let dut = 0.0;
let dun = 0.0;
// Ne (Z=10) 的配分函数
if iat == 10 {
match izi {
5 => u = 9.0,
6 => u = 6.0,
7 => u = 12.0,
8 => u = 8.0,
9 => u = 1.0,
_ => {}
}
return (u, dut, dun);
}
// S (Z=16) 的配分函数
if iat == 16 {
match izi {
5 => u = 1.0,
6 => u = 2.0,
7 => u = 1.0,
8 => u = 6.0,
9 => u = 9.0,
_ => {}
}
return (u, dut, dun);
}
(u, dut, dun)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_pfspec_neon() {
// Ne V
let (u, dut, dun) = pfspec(10, 5, 10000.0, 1e12);
assert!((u - 9.0).abs() < 1e-15);
assert!((dut - 0.0).abs() < 1e-15);
assert!((dun - 0.0).abs() < 1e-15);
// Ne VI
let (u, _, _) = pfspec(10, 6, 10000.0, 1e12);
assert!((u - 6.0).abs() < 1e-15);
// Ne VII
let (u, _, _) = pfspec(10, 7, 10000.0, 1e12);
assert!((u - 12.0).abs() < 1e-15);
// Ne VIII
let (u, _, _) = pfspec(10, 8, 10000.0, 1e12);
assert!((u - 8.0).abs() < 1e-15);
// Ne IX
let (u, _, _) = pfspec(10, 9, 10000.0, 1e12);
assert!((u - 1.0).abs() < 1e-15);
}
#[test]
fn test_pfspec_sulfur() {
// S V
let (u, dut, dun) = pfspec(16, 5, 10000.0, 1e12);
assert!((u - 1.0).abs() < 1e-15);
assert!((dut - 0.0).abs() < 1e-15);
assert!((dun - 0.0).abs() < 1e-15);
// S VI
let (u, _, _) = pfspec(16, 6, 10000.0, 1e12);
assert!((u - 2.0).abs() < 1e-15);
// S VII
let (u, _, _) = pfspec(16, 7, 10000.0, 1e12);
assert!((u - 1.0).abs() < 1e-15);
// S VIII
let (u, _, _) = pfspec(16, 8, 10000.0, 1e12);
assert!((u - 6.0).abs() < 1e-15);
// S IX
let (u, _, _) = pfspec(16, 9, 10000.0, 1e12);
assert!((u - 9.0).abs() < 1e-15);
}
#[test]
fn test_pfspec_unsupported() {
// 不支持的原子
let (u, _, _) = pfspec(1, 1, 10000.0, 1e12); // H
assert!((u - 0.0).abs() < 1e-15);
// 支持的原子但不支持的电离态
let (u, _, _) = pfspec(10, 1, 10000.0, 1e12); // Ne I
assert!((u - 0.0).abs() < 1e-15);
}
#[test]
fn test_pfspec_temperature_independence() {
// 结果应该与温度无关
let (u1, _, _) = pfspec(10, 5, 5000.0, 1e12);
let (u2, _, _) = pfspec(10, 5, 10000.0, 1e12);
let (u3, _, _) = pfspec(10, 5, 50000.0, 1e12);
assert!((u1 - u2).abs() < 1e-15);
assert!((u2 - u3).abs() < 1e-15);
}
}

215
src/math/sbfch.rs Normal file
View File

@ -0,0 +1,215 @@
//! CH 束缚-自由截面 × 配分函数。
//!
//! 重构自 TLUSTY `sbfch.f`
//! 参考: Kurucz ATLAS9
//!
//! 计算CH分子的光电离截面与配分函数的乘积。
use crate::data::{
SBFCH_C1, SBFCH_C10, SBFCH_C11, SBFCH_C2, SBFCH_C3, SBFCH_C4, SBFCH_C5, SBFCH_C6, SBFCH_C7,
SBFCH_C8, SBFCH_C9, SBFCH_PARTCH as PARTCH,
};
// 常量
const FIHU: f64 = 500.0;
const FIHUI: f64 = 1.0 / FIHU;
const TWHU: f64 = 200.0;
const TWHUI: f64 = 1.0 / TWHU;
const TENL: f64 = 2.30258509299405; // ln(10)
const CAS: f64 = 2.99792458e10; // 光速 (cm/s)
/// CH 束缚-自由截面 × 配分函数。
///
/// # 参数
///
/// * `fr` - 频率 (Hz)
/// * `t` - 温度 (K)
///
/// # 返回值
///
/// 截面 × 配分函数 (cm²)
pub fn sbfch(fr: f64, t: f64) -> f64 {
// 静态变量保存上一次的频率和计算结果
// 在 Rust 中使用 static + Cell 或直接重新计算
// 这里简化处理,每次都重新计算
// 频率转换为波数和电子伏特
let waveno = fr / CAS;
let evolt = waveno / 8065.479;
let n = (evolt * 10.0) as i32;
let en = (n as f64) * 0.1;
// 边界检查
if n < 20 || n >= 105 {
return 0.0;
}
// 温度边界检查
if t >= 9000.0 {
return 0.0;
}
// 插值计算截面 (在能量方向)
let crosscht = compute_crosscht(n, evolt, en);
// 插值计算配分函数
let part = compute_partition(t);
// 插值计算截面 (在温度方向)
compute_opacity(t, &crosscht, part)
}
/// 计算截面在能量方向的插值
///
/// # 参数
/// * `n` - Fortran 1-indexed 能量索引 (20-104)
/// * `evolt` - 电子伏特
/// * `en` - 离散化的能量
fn compute_crosscht(n: i32, evolt: f64, en: f64) -> [f64; 15] {
let mut crosscht = [0.0; 15];
// Fortran 1-indexed: CROSSCH(it, n), it=1-15, n=20-104
// 在 Rust 中直接使用 Fortran 索引计算
for it in 1..=15_i32 {
let val1 = get_crossch(it, n);
let val2 = get_crossch(it, n + 1);
crosscht[(it - 1) as usize] = val1 + (val2 - val1) * (evolt - en) * 10.0;
}
crosscht
}
/// 从 CROSSCH 数组获取值
///
/// Fortran EQUIVALENCE 映射 (列优先存储):
/// - C1: CROSSCH(1:15, 1:10) -> 150 元素
/// - C2: CROSSCH(1:15, 11:20) -> 150 元素
/// - ...
/// - C11: CROSSCH(1:15, 101:105) -> 75 元素
///
/// # 参数
/// * `it` - Fortran 1-indexed 温度索引 (1-15)
/// * `n` - Fortran 1-indexed 能量索引 (20-104)
fn get_crossch(it: i32, n: i32) -> f64 {
// 确定属于哪个 C 数组 (1-11)
let c_index = (n - 1) / 10 + 1; // 1-11
// 在该 C 数组内的局部 n 值 (0-9)
let local_n = (n - 1) % 10; // 0-9
// Fortran 列优先存储: 索引 = local_n * 15 + it
let idx = (local_n * 15 + it - 1) as usize; // 转为 0-indexed
match c_index {
1 => SBFCH_C1[idx],
2 => SBFCH_C2[idx],
3 => SBFCH_C3[idx],
4 => SBFCH_C4[idx],
5 => SBFCH_C5[idx],
6 => SBFCH_C6[idx],
7 => SBFCH_C7[idx],
8 => SBFCH_C8[idx],
9 => SBFCH_C9[idx],
10 => SBFCH_C10[idx],
11 => {
if idx < 75 {
SBFCH_C11[idx]
} else {
0.0
}
}
_ => 0.0,
}
}
/// 计算配分函数插值
fn compute_partition(t: f64) -> f64 {
// Fortran: IT=int((T-1000.)*twhui+1.)
// twhui = 1/200, 所以 IT = (T-1000)/200 + 1
let mut it = ((t - 1000.0) * TWHUI + 1.0) as i32;
if it < 1 {
it = 1;
}
if it > 40 {
it = 40;
}
let tn = (it as f64) * TWHU + 800.0;
// Fortran 1-indexed: PARTCH(IT) -> Rust 0-indexed: PARTCH[it-1]
let it_usize = it as usize;
if it_usize >= 41 {
return PARTCH[40];
}
PARTCH[it_usize - 1] + (PARTCH[it_usize] - PARTCH[it_usize - 1]) * (t - tn) * TWHUI
}
/// 计算最终的截面 × 配分函数
fn compute_opacity(t: f64, crosscht: &[f64; 15], part: f64) -> f64 {
// Fortran: IT=int((T-2000.)*fihui+1.)
// fihui = 1/500, 所以 IT = (T-2000)/500 + 1
let mut it = ((t - 2000.0) * FIHUI + 1.0) as i32;
if it < 1 {
it = 1;
}
if it > 14 {
it = 14;
}
let tn = (it as f64) * FIHU + 1500.0;
let it_usize = it as usize;
// 插值
let log_cross = crosscht[it_usize - 1]
+ (crosscht[it_usize] - crosscht[it_usize - 1]) * (t - tn) * FIHUI;
(log_cross * TENL).exp() * part
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_sbfch_boundary_low_energy() {
// 能量太低 (< 2 eV)
// 2 eV = 2 * 8065.479 cm^-1 = 16130.958 cm^-1
// FR = 16130.958 * CAS = 4.834e14 Hz
let result = sbfch(1e14, 5000.0); // ~0.012 eV
assert_eq!(result, 0.0);
}
#[test]
fn test_sbfch_boundary_high_energy() {
// 能量太高 (>= 10.5 eV)
// 10.5 eV = 10.5 * 8065.479 cm^-1 = 84687.5 cm^-1
// FR = 84687.5 * CAS = 2.54e15 Hz
let result = sbfch(3e15, 5000.0); // ~24.8 eV
assert_eq!(result, 0.0);
}
#[test]
fn test_sbfch_boundary_high_temp() {
// 温度太高 (>= 9000 K)
let result = sbfch(1e15, 10000.0); // ~8.3 eV, 10000 K
assert_eq!(result, 0.0);
}
#[test]
fn test_sbfch_valid() {
// 有效范围内的计算
// 5 eV -> FR ≈ 1.2e15 Hz
let result = sbfch(1.2e15, 5000.0); // ~5 eV, 5000 K
assert!(result > 0.0, "Expected positive result, got {}", result);
assert!(result.is_finite());
}
#[test]
fn test_sbfch_mid_range() {
// 中间范围: 6 eV, 4000 K
let result = sbfch(1.45e15, 4000.0);
assert!(result >= 0.0, "Expected non-negative result, got {}", result);
assert!(result.is_finite());
}
}

213
src/math/sbfhe1.rs Normal file
View File

@ -0,0 +1,213 @@
//! He I 束缚-自由光电离截面。
//!
//! 重构自 TLUSTY `sbfhe1.f`。
//!
//! 计算中性氦 n = 1, 2, 3, 4 态的光电离截面。
//! 使用 Opacity Project 截面的适当平均 (由 HEPHOT 计算) 或 Koester 拟合。
use super::{ckoest, hephot};
/// 计算 He I 光电离截面。
///
/// 对于非平均 (l,s) 态或某些平均态计算截面。
///
/// # 参数
///
/// - `ii` - 下能级索引 (显式能级编号)
/// - `ib` - 光电离开关 IBF
/// - = 10: 从平均能级跃迁
/// - = 11 或 21: 从非平均单态跃迁
/// - = 13 或 23: 从非平均三态跃迁
/// - `fr` - 频率 (Hz)
/// - `nquanti` - 能级 ii 的主量子数 n
/// - `gi` - 能级 ii 的统计权重 g
/// - `gg` - 用于 Koester 拟合的统计权重
///
/// # 返回
///
/// 光电离截面 (cm²)
///
/// # Fortran 原始代码
///
/// ```fortran
/// FUNCTION SBFHE1(II,IB,FR,GG)
/// NI=NQUANT(II)
/// IGI=INT(G(II)+0.01)
/// IS=IB-10
/// IF(IB.GT.20) IS=IB-20
/// ...
/// END
/// ```
pub fn sbfhe1(ib: i32, nquanti: i32, gi: f64, fr: f64, gg: f64) -> f64 {
let ni = nquanti;
let igi = (gi + 0.01) as i32;
let is = if ib > 20 { ib - 20 } else { ib - 10 };
// ----------------------------------------------------------------
// IB=11 或 13 - 从非平均 (l,s) 能级光电离
// ----------------------------------------------------------------
if is == 1 || is == 3 {
let il = (igi / is - 1) / 2;
if ib < 20 {
return hephot(is, il, ni, fr);
} else {
return ckoest(is, il, ni, fr, gg);
}
}
// ----------------------------------------------------------------
// IS=0 - 从平均能级光电离
// ----------------------------------------------------------------
if is == 0 {
if ni == 2 {
// n=2 平均能级
return sbfhe1_n2(igi, fr);
} else if ni == 3 {
// n=3 平均能级
return sbfhe1_n3(igi, fr);
} else if ni == 4 {
// n=4 平均能级
return sbfhe1_n4(igi, fr);
}
}
// 不一致的输入
panic!(
"SBFHE1: inconsistent input - quantum number={}, statistical weight={}, S={}",
ni, igi, is
);
}
/// n=2 平均能级光电离截面
fn sbfhe1_n2(igi: i32, fr: f64) -> f64 {
if igi == 4 {
// a) 平均单态
(hephot(1, 0, 2, fr) + 3.0 * hephot(1, 1, 2, fr)) / 9.0
} else if igi == 12 {
// b) 平均三态
(hephot(3, 0, 2, fr) + 3.0 * hephot(3, 1, 2, fr)) / 9.0
} else if igi == 16 {
// c) 单态和三态的平均
(hephot(1, 0, 2, fr)
+ 3.0 * (hephot(1, 1, 2, fr) + hephot(3, 0, 2, fr))
+ 9.0 * hephot(3, 1, 2, fr))
/ 16.0
} else {
panic!("SBFHE1: inconsistent n=2 level, igi={}", igi);
}
}
/// n=3 平均能级光电离截面
fn sbfhe1_n3(igi: i32, fr: f64) -> f64 {
if igi == 9 {
// a) 平均单态
(hephot(1, 0, 3, fr) + 3.0 * hephot(1, 1, 3, fr) + 5.0 * hephot(1, 2, 3, fr)) / 9.0
} else if igi == 27 {
// b) 平均三态
(hephot(3, 0, 3, fr) + 3.0 * hephot(3, 1, 3, fr) + 5.0 * hephot(3, 2, 3, fr)) / 9.0
} else if igi == 36 {
// c) 单态和三态的平均
(hephot(1, 0, 3, fr)
+ 3.0 * hephot(1, 1, 3, fr)
+ 5.0 * hephot(1, 2, 3, fr)
+ 3.0 * hephot(3, 0, 3, fr)
+ 9.0 * hephot(3, 1, 3, fr)
+ 15.0 * hephot(3, 2, 3, fr))
/ 36.0
} else {
panic!("SBFHE1: inconsistent n=3 level, igi={}", igi);
}
}
/// n=4 平均能级光电离截面
fn sbfhe1_n4(igi: i32, fr: f64) -> f64 {
if igi == 16 {
// a) 平均单态
(hephot(1, 0, 4, fr)
+ 3.0 * hephot(1, 1, 4, fr)
+ 5.0 * hephot(1, 2, 4, fr)
+ 7.0 * hephot(1, 3, 4, fr))
/ 16.0
} else if igi == 48 {
// b) 平均三态
(hephot(3, 0, 4, fr)
+ 3.0 * hephot(3, 1, 4, fr)
+ 5.0 * hephot(3, 2, 4, fr)
+ 7.0 * hephot(3, 3, 4, fr))
/ 16.0
} else if igi == 64 {
// c) 单态和三态的平均
(hephot(1, 0, 4, fr)
+ 3.0 * hephot(1, 1, 4, fr)
+ 5.0 * hephot(1, 2, 4, fr)
+ 7.0 * hephot(1, 3, 4, fr)
+ 3.0 * hephot(3, 0, 4, fr)
+ 9.0 * hephot(3, 1, 4, fr)
+ 15.0 * hephot(3, 2, 4, fr)
+ 21.0 * hephot(3, 3, 4, fr))
/ 64.0
} else {
panic!("SBFHE1: inconsistent n=4 level, igi={}", igi);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_sbfhe1_singlet() {
// IB=11: 非平均单态
let fr = 1e16; // 高于电离阈值
let result = sbfhe1(11, 2, 1.0, fr, 1.0);
assert!(result > 0.0);
}
#[test]
fn test_sbfhe1_triplet() {
// IB=13: 非平均三态
let fr = 1e16;
let result = sbfhe1(13, 2, 3.0, fr, 3.0);
assert!(result > 0.0);
}
#[test]
fn test_sbfhe1_averaged_n2_singlet() {
// IB=10, n=2, igi=4: 平均单态
let fr = 1e15;
let result = sbfhe1(10, 2, 4.0, fr, 1.0);
assert!(result > 0.0);
}
#[test]
fn test_sbfhe1_averaged_n2_triplet() {
// IB=10, n=2, igi=12: 平均三态
let fr = 1e15;
let result = sbfhe1(10, 2, 12.0, fr, 1.0);
assert!(result > 0.0);
}
#[test]
fn test_sbfhe1_averaged_n3() {
// IB=10, n=3
let fr = 1e15;
let result = sbfhe1(10, 3, 9.0, fr, 1.0);
assert!(result > 0.0);
}
#[test]
fn test_sbfhe1_averaged_n4() {
// IB=10, n=4
let fr = 1e15;
let result = sbfhe1(10, 4, 16.0, fr, 1.0);
assert!(result > 0.0);
}
#[test]
fn test_sbfhe1_koester() {
// IB=21 或 23: 使用 Koester 拟合
let fr = 1e16;
let result = sbfhe1(21, 2, 1.0, fr, 1.0);
assert!(result > 0.0);
}
}

109
src/math/sbfhmi_old.rs Normal file
View File

@ -0,0 +1,109 @@
//! H⁻ 束缚-自由截面 (旧版本)。
//!
//! 重构自 TLUSTY `sbfhmi_old.f`
//!
//! 负氢离子的束缚-自由光致电离截面。
/// H⁻ 束缚-自由截面 (旧版本)。
///
/// 计算负氢离子的束缚-自由光致电离截面。
///
/// # 参数
///
/// * `fr` - 频率 (Hz)
///
/// # 返回值
///
/// 截面 (cm²),如果频率低于阈值则返回 0。
///
/// # 备注
///
/// 阈值频率 FR0 = 1.8259×10¹⁴ Hz (对应 H⁻ 的束缚能)。
/// 使用两个不同的多项式拟合:
/// - 低于 2.111×10¹⁴ Hz: 使用 X = c × (1/FR0 - 1/FR) 的多项式
/// - 高于 2.111×10¹⁴ Hz: 使用 X = c/FR 的多项式
pub fn sbfhmi_old(fr: f64) -> f64 {
const FR0: f64 = 1.8259e14;
const C: f64 = 2.997925e15; // 光速 (Å/s)
// 低于阈值
if fr < FR0 {
return 0.0;
}
if fr < 2.111e14 {
// 低于 2.111×10¹⁴ Hz 的多项式
let x = C * (1.0 / FR0 - 1.0 / fr);
let sbfhmi = (2.69818e-1 + x * (2.2019e-1 + x * (-4.11288e-2 + x * 2.73236e-3)))
* x
* 1e-17;
sbfhmi
} else {
// 高于 2.111×10¹⁴ Hz 的多项式
let x = C / fr;
let sbfhmi = (6.80133e-3
+ x * (1.78708e-1 + x * (1.6479e-1 + x * (-2.04842e-2 + x * 5.95244e-4))))
* 1e-17;
sbfhmi
}
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_relative_eq;
#[test]
fn test_sbfhmi_old_below_threshold() {
// 低于阈值
let result = sbfhmi_old(1e14);
assert_relative_eq!(result, 0.0, epsilon = 1e-30);
}
#[test]
fn test_sbfhmi_old_at_threshold() {
// 在阈值处 (x=0结果为 0)
let result = sbfhmi_old(1.8259e14);
assert_relative_eq!(result, 0.0, epsilon = 1e-30);
}
#[test]
fn test_sbfhmi_old_low_frequency() {
// 低频区 (1.8259e14 < fr < 2.111e14)
let result = sbfhmi_old(2.0e14);
assert!(result > 0.0);
assert!(result.is_finite());
}
#[test]
fn test_sbfhmi_old_transition() {
// 在过渡点 (2.111e14)
let result = sbfhmi_old(2.111e14);
assert!(result > 0.0);
assert!(result.is_finite());
}
#[test]
fn test_sbfhmi_old_high_frequency() {
// 高频区 (fr > 2.111e14)
let result = sbfhmi_old(5e14);
assert!(result > 0.0);
assert!(result.is_finite());
}
#[test]
fn test_sbfhmi_old_visible() {
// 可见光范围
let result = sbfhmi_old(5.45e14); // 550 nm
assert!(result > 0.0);
assert!(result.is_finite());
}
#[test]
fn test_sbfhmi_old_uv() {
// 紫外范围
let result = sbfhmi_old(1e15);
assert!(result > 0.0);
assert!(result.is_finite());
}
}

185
src/math/sbfoh.rs Normal file
View File

@ -0,0 +1,185 @@
//! OH 束缚-自由截面 × 配分函数。
//!
//! 重构自 TLUSTY `sbfoh.f`
//! 参考: Kurucz ATLAS9
use crate::data::{
SBFOH_C1, SBFOH_C10, SBFOH_C11, SBFOH_C12, SBFOH_C13, SBFOH_C2, SBFOH_C3, SBFOH_C4,
SBFOH_C5, SBFOH_C6, SBFOH_C7, SBFOH_C8, SBFOH_C9, SBFOH_PARTOH as PARTOH,
};
// 常量
const FIHU: f64 = 500.0;
const FIHUI: f64 = 1.0 / FIHU;
const TWHU: f64 = 200.0;
const TWHUI: f64 = 1.0 / TWHU;
const TENL: f64 = 2.30258509299405; // ln(10)
const CAS: f64 = 2.99792458e10; // 光速 (cm/s)
/// OH 束缚-自由截面 × 配分函数。
///
/// # 参数
///
/// * `fr` - 频率 (Hz)
/// * `t` - 温度 (K)
///
/// # 返回值
///
/// 截面 × 配分函数 (cm²)
pub fn sbfoh(fr: f64, t: f64) -> f64 {
// 频率转换为波数和电子伏特
let waveno = fr / CAS;
let evolt = waveno / 8065.479;
// Fortran: N=int(EVOLT*10.-20.)
// EN = N*0.1 + 2.0
// N 范围: 1-129 (对应 evolt 2.1-14.9 eV)
let n = (evolt * 10.0 - 20.0) as i32;
let en = (n as f64) * 0.1 + 2.0;
// 边界检查
if n <= 0 || n >= 130 {
return 0.0;
}
// 温度边界检查
if t >= 9000.0 {
return 0.0;
}
// 插值计算截面 (在能量方向)
let crossoht = compute_crossoht(n, evolt, en);
// 插值计算配分函数
let part = compute_partition(t);
// 插值计算截面 (在温度方向)
compute_opacity(t, &crossoht, part)
}
/// 计算截面在能量方向的插值
fn compute_crossoht(n: i32, evolt: f64, en: f64) -> [f64; 15] {
let mut crossoht = [0.0; 15];
// Fortran 1-indexed: CROSSOH(it, n), it=1-15, n=1-130
for it in 1..=15_i32 {
let val1 = get_crossoh(it, n);
let val2 = get_crossoh(it, n + 1);
crossoht[(it - 1) as usize] = val1 + (val2 - val1) * (evolt - en) * 10.0;
}
crossoht
}
/// 从 CROSSOH 数组获取值
///
/// Fortran EQUIVALENCE 映射 (列优先存储):
/// - C1: CROSSOH(1:15, 1:10) -> 150 元素
/// - C2: CROSSOH(1:15, 11:20) -> 150 元素
/// - ...
/// - C13: CROSSOH(1:15, 121:130) -> 150 元素
fn get_crossoh(it: i32, n: i32) -> f64 {
// 确定属于哪个 C 数组 (1-13)
let c_index = (n - 1) / 10 + 1;
// 在该 C 数组内的局部 n 值 (0-9)
let local_n = (n - 1) % 10;
// Fortran 列优先存储: 索引 = local_n * 15 + it
let idx = (local_n * 15 + it - 1) as usize;
match c_index {
1 => SBFOH_C1[idx],
2 => SBFOH_C2[idx],
3 => SBFOH_C3[idx],
4 => SBFOH_C4[idx],
5 => SBFOH_C5[idx],
6 => SBFOH_C6[idx],
7 => SBFOH_C7[idx],
8 => SBFOH_C8[idx],
9 => SBFOH_C9[idx],
10 => SBFOH_C10[idx],
11 => SBFOH_C11[idx],
12 => SBFOH_C12[idx],
13 => SBFOH_C13[idx],
_ => 0.0,
}
}
/// 计算配分函数插值
fn compute_partition(t: f64) -> f64 {
let mut it = ((t - 1000.0) * TWHUI + 1.0) as i32;
if it < 1 {
it = 1;
}
if it > 40 {
it = 40;
}
let tn = (it as f64) * TWHU + 800.0;
let it_usize = it as usize;
PARTOH[it_usize - 1] + (PARTOH[it_usize] - PARTOH[it_usize - 1]) * (t - tn) * TWHUI
}
/// 计算最终的截面 × 配分函数
fn compute_opacity(t: f64, crossoht: &[f64; 15], part: f64) -> f64 {
let mut it = ((t - 2000.0) * FIHUI + 1.0) as i32;
if it < 1 {
it = 1;
}
if it > 14 {
it = 14;
}
let tn = (it as f64) * FIHU + 1500.0;
let it_usize = it as usize;
let log_cross = crossoht[it_usize - 1]
+ (crossoht[it_usize] - crossoht[it_usize - 1]) * (t - tn) * FIHUI;
(log_cross * TENL).exp() * part
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_sbfoh_boundary_low_energy() {
// 能量太低 (<= 2 eV)
let result = sbfoh(4e14, 5000.0); // ~3.3 eV 但 n 可能 <= 0
// n = (3.3*10 - 20) = 13, 应该是有效的
assert!(result >= 0.0);
}
#[test]
fn test_sbfoh_boundary_high_energy() {
// 能量太高 (>= 15 eV)
let result = sbfoh(4e15, 5000.0); // ~33 eV
assert_eq!(result, 0.0);
}
#[test]
fn test_sbfoh_boundary_high_temp() {
// 温度太高 (>= 9000 K)
let result = sbfoh(1e15, 10000.0);
assert_eq!(result, 0.0);
}
#[test]
fn test_sbfoh_valid() {
// 有效范围: 5 eV, 5000 K
// 5 eV -> n = 50 - 20 = 30
let result = sbfoh(1.2e15, 5000.0);
assert!(result > 0.0, "Expected positive result, got {}", result);
assert!(result.is_finite());
}
#[test]
fn test_sbfoh_mid_range() {
// 中间范围: 8 eV, 4000 K
let result = sbfoh(1.9e15, 4000.0);
assert!(result >= 0.0);
assert!(result.is_finite());
}
}

114
src/math/spsigk.rs Normal file
View File

@ -0,0 +1,114 @@
//! 特殊光电离截面评估。
//!
//! 重构自 TLUSTY `spsigk.f`
//!
//! 非标准光电离截面评估,用户自定义过程。
use crate::math::{carbon, hidalg, reiman, sghe12};
/// 特殊光电离截面。
///
/// 根据能级索引 IB 选择适当的光电离截面计算方法。
///
/// # 参数
///
/// * `ib` - 能级索引 (负值表示特殊处理)
/// * `fr` - 频率 (Hz)
///
/// # 返回值
///
/// 光电离截面 (cm²)。
///
/// # 备注
///
/// 基本上是用户自定义过程,这里提供一些示例:
/// - IB = -201: He I 基态的特殊公式
/// - IB = -202: He I <n=2> 平均能级
/// - IB = -602, -603: C I 基态组态能级 2p² ¹D 和 ¹S
/// - IB = -101 到 -137: Hidalgo (1968) 数据
/// - IB = -301 到 -337: Reilman & Manson (1979) 数据
pub fn spsigk(ib: i32, fr: f64) -> f64 {
// He I 基态的特殊公式
if ib == -201 {
return 7.3e-18 * (1.373 - 2.311e-16 * fr).exp();
}
// He I <n=2> 平均能级
if ib == -202 {
return sghe12(fr);
}
// C I 基态组态能级 2p² ¹D 和 ¹S
if ib == -602 || ib == -603 {
return carbon(ib, fr);
}
// Hidalgo (Ap.J. 153, 981, 1968) 光电离数据
if ib >= -137 && ib <= -101 {
return hidalg(ib, fr);
}
// Reilman & Manson (Ap.J. Suppl. 40, 815, 1979) 光电离数据
if ib >= -337 && ib <= -301 {
return reiman(ib, fr);
}
// 默认返回 0
0.0
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_relative_eq;
#[test]
fn test_spsigk_he_ground() {
// He I 基态
let result = spsigk(-201, 1e16);
assert!(result.is_finite());
assert!(result > 0.0);
}
#[test]
fn test_spsigk_he_n2() {
// He I <n=2>
let result = spsigk(-202, 1e16);
assert!(result.is_finite());
}
#[test]
fn test_spsigk_carbon_d() {
// C I 2p² ¹D
let result = spsigk(-602, 1e16);
assert!(result.is_finite());
}
#[test]
fn test_spsigk_carbon_s() {
// C I 2p² ¹S
let result = spsigk(-603, 1e16);
assert!(result.is_finite());
}
#[test]
fn test_spsigk_hidalgo() {
// Hidalgo 数据
let result = spsigk(-101, 1e16);
assert!(result.is_finite());
}
#[test]
fn test_spsigk_reiman() {
// Reilman & Manson 数据
let result = spsigk(-301, 1e16);
assert!(result.is_finite());
}
#[test]
fn test_spsigk_unknown() {
// 未知索引返回 0
let result = spsigk(-999, 1e16);
assert_relative_eq!(result, 0.0, epsilon = 1e-30);
}
}

204
src/math/tiopf.rs Normal file
View File

@ -0,0 +1,204 @@
//! TiO 配分函数。
//!
//! 重构自 TLUSTY `tiopf.f`
//!
//! 数据来自 Kurucz 网站。
use std::sync::OnceLock;
/// TiO 配分函数数据 (800个点10K-8000K)。
static PF0: OnceLock<[f64; 800]> = OnceLock::new();
fn get_pf0() -> &'static [f64; 800] {
PF0.get_or_init(|| {
[
29.107, 55.425, 82.417, 111.190, 142.564, 176.916, 214.340, 254.774, 298.065, 344.021,
392.431, 443.089, 495.795, 550.365, 606.632, 664.449, 723.686, 784.230, 845.981,
908.862, 972.800, 1037.739, 1103.636, 1170.451, 1238.155, 1306.723, 1376.144,
1446.403, 1517.492, 1589.409, 1662.152, 1735.724, 1810.122, 1885.352, 1961.428,
2038.351, 2116.119, 2194.758, 2274.260, 2354.633, 2435.907, 2518.063, 2601.125,
2685.096, 2769.992, 2855.809, 2942.560, 3030.257, 3118.897, 3208.496, 3299.067,
3390.598, 3483.106, 3576.598, 3671.095, 3766.569, 3863.048, 3960.522, 4059.035,
4158.545, 4259.074, 4360.642, 4463.259, 4566.905, 4671.582, 4777.321, 4884.105,
4991.937, 5100.852, 5210.813, 5321.838, 5433.972, 5547.154, 5661.417, 5776.789,
5893.211, 6010.774, 6129.422, 6249.173, 6370.026, 6491.973, 6615.042, 6739.240,
6864.542, 6990.959, 7118.533, 7247.214, 7377.053, 7508.012, 7640.121, 7773.370,
7907.764, 8043.309, 8180.032, 8317.835, 8456.861, 8597.055, 8738.396, 8880.926,
9024.672, 9169.570, 9315.610, 9462.927, 9611.339, 9760.963, 9911.798, 10063.900,
10217.148, 10371.572, 10527.253, 10684.109, 10842.173, 11001.469, 11161.970,
11323.751, 11486.758, 11650.978, 11816.415, 11983.159, 12151.134, 12320.243,
12490.668, 12662.333, 12835.234, 13009.470, 13184.926, 13361.601, 13539.660,
13718.891, 13899.456, 14081.252, 14264.326, 14448.643, 14634.341, 14821.225,
15009.476, 15199.021, 15389.829, 15581.955, 15775.377, 15970.188, 16166.239,
16363.513, 16562.006, 16761.930, 16963.301, 17165.906, 17369.881, 17575.236,
17781.814, 17989.816, 18198.996, 18409.707, 18621.680, 18835.068, 19049.715,
19265.768, 19483.375, 19702.006, 19922.209, 20143.668, 20366.555, 20590.742,
20816.402, 21043.338, 21271.672, 21501.369, 21732.563, 21965.119, 22199.068,
22434.432, 22671.266, 22909.307, 23148.898, 23389.893, 23632.322, 23875.969,
24121.160, 24367.707, 24615.848, 24865.471, 25116.320, 25368.604, 25622.342,
25877.512, 26134.055, 26392.404, 26651.764, 26912.826, 27175.250, 27439.197,
27704.539, 27971.287, 28239.572, 28509.373, 28780.707, 29053.516, 29327.602,
29603.338, 29880.539, 30159.105, 30439.322, 30721.055, 31004.254, 31288.818,
31575.061, 31862.693, 32151.781, 32442.586, 32734.619, 33027.777, 33323.023,
33619.535, 33917.707, 34217.711, 34518.996, 34821.676, 35126.195, 35432.141,
35739.602, 36048.926, 36359.488, 36672.023, 36985.633, 37300.863, 37617.965,
37936.469, 38256.309, 38578.074, 38901.668, 39226.461, 39552.969, 39880.852,
40210.785, 40541.852, 40874.691, 41209.359, 41545.535, 41883.602, 42222.715,
42563.895, 42906.508, 43250.656, 43596.902, 43944.355, 44293.695, 44644.504,
44997.621, 45351.590, 45707.242, 46065.008, 46424.367, 46785.605, 47148.023,
47512.496, 47878.418, 48246.426, 48615.895, 48987.336, 49360.082, 49734.758,
50111.004, 50489.383, 50868.996, 51250.250, 51633.691, 52018.945, 52405.715,
52794.090, 53184.340, 53576.375, 53970.605, 54366.176, 54763.148, 55162.430,
55563.215, 55966.391, 56371.000, 56777.176, 57185.570, 57596.074, 58007.617,
58421.418, 58837.172, 59254.539, 59673.418, 60094.066, 60517.410, 60941.844,
61368.660, 61797.395, 62227.590, 62659.789, 63094.238, 63529.695, 63967.488,
64407.887, 64849.496, 65292.867, 65735.922, 66182.000, 66631.266, 67082.055,
67534.391, 67988.992, 68446.117, 68904.789, 69365.180, 69827.914, 70292.781,
70759.352, 71228.500, 71699.375, 72171.672, 72647.086, 73123.984, 73603.023,
74083.516, 74566.359, 75050.555, 75537.758, 76027.258, 76518.125, 77012.008,
77507.063, 78003.813, 78503.977, 79006.125, 79509.320, 80015.375, 80522.461,
81031.938, 81544.164, 82058.313, 82574.352, 83093.914, 83614.367, 84136.820,
84662.211, 85188.867, 85719.375, 86249.977, 86783.781, 87319.219, 87857.180,
88396.797, 88939.805, 89484.266, 90032.023, 90580.930, 91132.563, 91686.148,
92242.742, 92799.406, 93360.016, 93923.453, 94488.313, 95055.211, 95625.297,
96197.477, 96771.531, 97348.156, 97926.922, 98507.453, 99091.563, 99677.938,
100267.234, 100856.438, 101449.828, 102045.750, 102643.094, 103244.117, 103846.969,
104450.313, 105057.641, 105667.188, 106279.516, 106894.937, 107512.789, 108133.117,
108754.758, 109377.687, 110005.039, 110634.602, 111266.141, 111902.133, 112537.984,
113178.891, 113819.766, 114464.312, 115110.969, 115760.687, 116412.469, 117068.055,
117724.547, 118384.383, 119047.469, 119712.469, 120380.187, 121051.336, 121724.102,
122399.250, 123076.266, 123756.977, 124441.195, 125126.406, 125816.453, 126506.766,
127202.367, 127899.086, 128598.266, 129299.969, 130004.969, 130712.016, 131409.266,
132117.719, 132828.969, 133544.016, 134262.750, 134986.344, 135712.891, 136439.937,
137170.969, 137905.562, 138641.578, 139380.266, 140122.937, 140868.641, 141615.484,
142366.703, 143123.078, 143880.000, 144638.484, 145401.594, 146168.125, 146935.359,
147707.484, 148482.641, 149256.578, 150037.281, 150821.953, 151606.750, 152396.094,
153188.766, 153983.391, 154782.141, 155582.203, 156387.234, 157192.719, 158003.156,
158815.125, 159632.437, 160450.766, 161274.750, 162098.172, 162926.000, 163756.609,
164593.141, 165430.859, 166270.937, 167114.750, 167960.797, 168811.562, 169663.906,
170517.203, 171376.531, 172239.469, 173105.891, 173975.250, 174847.203, 175721.453,
176597.250, 177480.984, 178366.094, 179253.828, 180145.734, 181038.000, 181936.031,
182837.969, 183739.922, 184645.937, 185558.281, 186470.844, 187387.422, 188307.234,
189232.281, 190156.000, 191088.234, 192022.062, 192957.250, 193899.328, 194842.984,
195788.391, 196736.156, 197687.828, 198645.719, 199603.422, 200569.234, 201536.437,
202508.641, 203481.000, 204459.016, 205438.750, 206424.312, 207409.953, 208398.734,
209393.234, 210391.047, 211390.984, 212395.516, 213401.547, 214420.141, 215431.812,
216453.453, 217476.734, 218501.266, 219530.219, 220560.719, 221597.891, 222637.875,
223677.750, 224725.500, 225777.406, 226829.297, 227893.125, 228954.547, 230020.969,
231086.453, 232157.469, 233233.047, 234315.406, 235395.625, 236480.953, 237572.125,
238666.484, 239765.125, 240863.281, 241969.750, 243079.250, 244191.719, 245304.812,
246427.937, 247548.234, 248673.562, 249804.984, 250942.781, 252078.953, 253222.812,
254369.641, 255519.359, 256671.406, 257827.906, 258988.859, 260154.734, 261322.281,
262458.781, 263606.437, 264770.625, 265947.750, 267125.156, 268314.125, 269507.687,
270702.344, 271905.156, 273110.156, 274318.937, 275531.687, 276751.344, 277970.781,
279198.531, 280425.750, 281663.250, 282897.469, 284138.906, 285383.594, 286637.031,
287891.156, 289147.625, 290413.312, 291678.719, 292946.031, 294225.875, 295501.344,
296782.656, 298070.094, 299363.875, 300652.250, 301953.750, 303260.062, 304563.781,
305874.375, 307191.437, 308517.031, 309835.750, 311159.375, 312490.937, 313827.469,
315166.781, 316511.031, 317860.406, 319214.969, 320565.875, 321929.344, 323296.906,
324660.219, 326035.687, 327413.844, 328794.406, 330173.156, 331566.156, 332953.469,
334356.187, 335757.625, 337165.562, 338566.094, 339984.750, 341402.937, 342828.125,
344257.562, 345686.750, 347123.125, 348564.250, 350008.906, 351453.219, 352908.062,
354361.469, 355828.000, 357292.500, 358765.719, 360233.687, 361713.562, 363200.187,
364685.656, 366174.500, 367673.594, 369174.906, 370678.969, 372191.125, 373708.937,
375225.281, 376743.719, 378270.406, 379804.500, 381334.250, 382879.125, 384420.812,
385969.531, 387519.812, 389078.937, 390639.781, 392213.875, 393782.437, 395359.156,
396943.625, 398527.625, 400110.937, 401711.750, 403310.344, 404908.937, 406513.875,
408125.781, 409741.906, 411356.875, 412979.500, 414613.125, 416245.500, 417889.094,
419530.000, 421179.906, 422831.531, 424484.344, 426153.187, 427816.406, 429489.094,
431161.312, 432840.656, 434517.000, 436215.281, 437896.000, 439602.594, 441300.625,
443016.156, 444722.906, 446445.437, 448164.812, 449885.937, 451615.094, 453351.594,
455090.125, 456833.281, 458582.719, 460335.344, 462094.844, 463857.094, 465629.906,
467402.781, 469178.406, 470963.750, 472745.906, 474539.594, 476333.312, 478131.125,
479934.000, 481740.750, 483557.844, 485376.625, 487202.937, 489033.562, 490868.031,
492709.281, 494547.375, 496401.094, 498249.594, 500110.250, 501966.594, 503836.062,
505704.437, 507580.687, 509469.187, 511349.781, 513239.000, 515137.187, 517038.812,
518942.906, 520858.156, 522767.094, 524610.625, 526433.812, 528331.062, 530253.437,
532185.500, 534127.875, 536073.937, 538028.312, 539983.375, 541954.687, 543916.312,
545902.500, 547874.812, 549857.125, 551850.937, 553839.937, 555836.625, 557838.500,
559849.937, 561859.375, 563880.625, 565889.875, 567916.000, 569953.625, 571990.375,
574034.937, 576085.062, 578127.375, 580188.937, 582251.000, 584328.812, 586385.562,
588464.062, 590551.875, 592644.625, 594722.250, 596829.937, 598931.375, 601029.687,
603142.812, 605262.812, 607384.625, 609513.125, 611644.000, 613775.875, 615930.375,
618073.750, 620218.437, 622381.937, 624524.312, 626697.500, 628869.000, 631040.937,
633223.562, 635409.187, 637597.562, 639800.187, 642002.125, 644212.562, 646416.250,
648633.562, 650864.187, 653083.687, 655315.312, 657549.687, 659795.500, 662032.250,
664292.875, 666542.312, 668806.250, 671071.312, 673340.937, 675626.938, 677898.750,
]
})
}
/// TiO 配分函数。
///
/// # 参数
///
/// * `t` - 温度 (K)
///
/// # 返回值
///
/// 配分函数值。
///
/// # 备注
///
/// 数据来自 Kurucz 网站,温度范围 10K-8000K。
/// Fortran 原代码: it=int(t/10.), pf=pf0(it)
/// Fortran 1-indexed: t=10 → it=1 → pf0(1)
/// Rust 0-indexed: t=10 → it=0 → pf0[0]
pub fn tiopf(t: f64) -> f64 {
let pf0 = get_pf0();
// 计算索引: Fortran int(t/10.) 是 1-indexed
// Rust 需要 -1 转换为 0-indexed
let mut it = (t / 10.0) as i32 - 1;
// 限制索引范围
if it < 0 {
it = 0;
}
if it >= 800 {
it = 799;
}
pf0[it as usize]
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_tiopf_low_temp() {
// 低温边界 (T=10K → index 1 in Fortran, index 0 in Rust)
let result = tiopf(10.0);
assert!((result - 29.107).abs() < 1e-6);
}
#[test]
fn test_tiopf_mid_temp() {
// 中间温度 (T=5000K → index 500 in Fortran, index 499 in Rust)
let result = tiopf(5000.0);
assert!(result > 0.0);
assert!(result.is_finite());
}
#[test]
fn test_tiopf_high_temp() {
// 高温边界 (T=8000K → index 800, clamped to 799)
let result = tiopf(8000.0);
assert!((result - 677898.750).abs() < 1e-6);
}
#[test]
fn test_tiopf_above_max() {
// 超过最大温度
let result = tiopf(10000.0);
assert!((result - 677898.750).abs() < 1e-6);
}
#[test]
fn test_tiopf_exact_step() {
// 精确步长 (T=100K → index 10 in Fortran, index 9 in Rust)
let result = tiopf(100.0);
assert!((result - 344.021).abs() < 1e-6);
}
}

298
src/math/verner.rs Normal file
View File

@ -0,0 +1,298 @@
//! Verner 光电离截面计算。
//!
//! 重构自 TLUSTY `verner.f`, `vern16.f`, `vern18.f`, `vern20.f`, `vern26.f`。
//!
//! 基态原子和离子的光电离截面解析拟合。
//! 参考Verner D.A. et al. 1996, ApJ 465; Verner & Yakovlev 1995, A&AS 109, 125
use crate::data::{
VERN16_E0, VERN16_E95, VERN16_EMX, VERN16_P95, VERN16_PV, VERN16_S0, VERN16_S95,
VERN16_Y0, VERN16_Y1, VERN16_Y95, VERN16_YA, VERN16_YW, VERN16_YW95, VERN18_E0, VERN18_E95,
VERN18_EMX, VERN18_P95, VERN18_PV, VERN18_S0, VERN18_S95, VERN18_Y0, VERN18_Y1, VERN18_Y95,
VERN18_YA, VERN18_YW, VERN18_YW95, VERN20_E0, VERN20_E95, VERN20_EMX, VERN20_P95,
VERN20_PV, VERN20_S0, VERN20_S95, VERN20_Y0, VERN20_Y1, VERN20_Y95, VERN20_YA, VERN20_YW,
VERN20_YW95, VERN26_E0, VERN26_E95, VERN26_EMX, VERN26_P95, VERN26_PV, VERN26_S0,
VERN26_S95, VERN26_Y0, VERN26_Y1, VERN26_Y95, VERN26_YA, VERN26_YW, VERN26_YW95,
VERNER_E0, VERNER_E95, VERNER_EMX, VERNER_IV0, VERNER_P95, VERNER_PV, VERNER_S0,
VERNER_S95, VERNER_Y0, VERNER_Y1, VERNER_Y95, VERNER_YA, VERNER_YW, VERNER_YW95,
};
use crate::state::H;
// ============================================================================
// 常量
// ============================================================================
/// h / 1.6022e-12 (erg/eV)
const HHEV: f64 = H / 1.6022e-12;
const T18: f64 = 1e-18;
// ============================================================================
// Verner 截面计算
// ============================================================================
/// 计算基态原子和离子的光电离截面。
///
/// 使用 Verner et al. (1996) 的解析拟合公式。
/// 支持 H 到 Si, S, Ar, Ca 和 Fe 的基态。
///
/// # 参数
///
/// - `fr` - 频率 (Hz)
/// - `itr` - 跃迁索引 (0-indexed)
/// - `atomic` - 原子数据
///
/// # 返回
///
/// 光电离截面 (cm²)
///
/// # Fortran 原始代码
///
/// ```fortran
/// FUNCTION VERNER(FR,ITR)
/// INCLUDE 'BASICS.FOR'
/// INCLUDE 'ATOMIC.FOR'
/// ...
/// II=ILOW(ITR)
/// N1=NFIRST(IEL(II))
/// ...
/// END
/// ```
pub fn verner(fr: f64, itr: usize, atomic: &crate::state::AtomicData) -> f64 {
let e = HHEV * fr;
// 获取跃迁的下能级索引 (Fortran 1-indexed → Rust 0-indexed)
let ii = (atomic.trapar.ilow[itr] - 1) as usize;
// 检查是否为基态
let ion_idx = (atomic.levpar.iel[ii] - 1) as usize;
let n1 = atomic.ionpar.nfirst[ion_idx];
if atomic.trapar.ilow[itr] != n1 {
panic!(
"Verner fits only for ground states: ilow={}, nfirst={}",
atomic.trapar.ilow[itr], n1
);
}
// 获取原子序号和电荷
let atm_idx = (atomic.levpar.iatm[ii] - 1) as usize;
let iat = atomic.atopar.numat[atm_idx];
let izz = atomic.ionpar.iz[ion_idx];
// 检查原子序号范围
if iat > 14 {
// 对于更重的元素,调用专门的函数
return verner_heavy(e, izz, iat);
}
// 计算索引
let iver = (VERNER_IV0[(iat - 1) as usize] as usize) + (izz as usize);
// 根据能量选择表达式
if e < VERNER_EMX[iver] {
// 1996 表达式
let xx = e / VERNER_E0[iver] - VERNER_Y0[iver];
let yy = (xx * xx + VERNER_Y1[iver] * VERNER_Y1[iver]).sqrt();
let aa = (xx - 1.0) * (xx - 1.0) + VERNER_YW[iver] * VERNER_YW[iver];
let bb = yy.powf(0.5 * VERNER_PV[iver] - 5.5);
let cc = (1.0 + (yy / VERNER_YA[iver]).sqrt()).powf(VERNER_PV[iver]);
let fy = aa * bb / cc;
VERNER_S0[iver] * T18 * fy
} else {
// 1995 高能表达式 (内壳层电子电离)
let yy = e / VERNER_E95[iver];
let xl = if (iat - izz) >= 10 { 1.0 } else { 0.0 };
let q = 0.5 * VERNER_P95[iver] - 5.5 - xl;
let aa = (yy - 1.0) * (yy - 1.0) + VERNER_YW95[iver] * VERNER_YW95[iver];
let bb = yy.powf(q);
let cc = (1.0 + (yy / VERNER_Y95[iver]).sqrt()).powf(VERNER_P95[iver]);
let fy = aa * bb / cc;
VERNER_S95[iver] * T18 * fy
}
}
/// 重元素 Verner 截面计算。
fn verner_heavy(e: f64, izz: i32, iat: i32) -> f64 {
match iat {
26 => vern26(e, izz),
16 => vern16(e, izz),
18 => vern18(e, izz),
20 => vern20(e, izz),
_ => panic!("VERNER - No data for this element: iat={}, izz={}", iat, izz),
}
}
// ============================================================================
// 重元素截面函数
// ============================================================================
/// S (Z=16) Verner 截面。
///
/// 计算 S I - S XVI 所有硫离子的基态光电离截面。
///
/// # 参数
///
/// - `e` - 光子能量 (eV)
/// - `izz` - 电荷 (1-16, 对应 S I 到 S XVI)
fn vern16(e: f64, izz: i32) -> f64 {
let iver = (izz - 1) as usize;
if e < VERN16_EMX[iver] {
// 1996 表达式
let xx = e / VERN16_E0[iver] - VERN16_Y0[iver];
let yy = (xx * xx + VERN16_Y1[iver] * VERN16_Y1[iver]).sqrt();
let aa = (xx - 1.0) * (xx - 1.0) + VERN16_YW[iver] * VERN16_YW[iver];
let bb = yy.powf(0.5 * VERN16_PV[iver] - 5.5);
let cc = (1.0 + (yy / VERN16_YA[iver]).sqrt()).powf(VERN16_PV[iver]);
let fy = aa * bb / cc;
VERN16_S0[iver] * T18 * fy
} else {
// 1995 高能表达式 (内壳层电子电离)
let yy = e / VERN16_E95[iver];
let xl = if izz <= 6 { 1.0 } else { 0.0 };
let q = 0.5 * VERN16_P95[iver] - 5.5 - xl;
let aa = (yy - 1.0) * (yy - 1.0) + VERN16_YW95[iver] * VERN16_YW95[iver];
let bb = yy.powf(q);
let cc = (1.0 + (yy / VERN16_Y95[iver]).sqrt()).powf(VERN16_P95[iver]);
let fy = aa * bb / cc;
VERN16_S95[iver] * T18 * fy
}
}
/// Ar (Z=18) Verner 截面。
///
/// 计算 Ar I - Ar XVIII 所有氩离子的基态光电离截面。
fn vern18(e: f64, izz: i32) -> f64 {
let iver = (izz - 1) as usize;
if e < VERN18_EMX[iver] {
let xx = e / VERN18_E0[iver] - VERN18_Y0[iver];
let yy = (xx * xx + VERN18_Y1[iver] * VERN18_Y1[iver]).sqrt();
let aa = (xx - 1.0) * (xx - 1.0) + VERN18_YW[iver] * VERN18_YW[iver];
let bb = yy.powf(0.5 * VERN18_PV[iver] - 5.5);
let cc = (1.0 + (yy / VERN18_YA[iver]).sqrt()).powf(VERN18_PV[iver]);
let fy = aa * bb / cc;
VERN18_S0[iver] * T18 * fy
} else {
let yy = e / VERN18_E95[iver];
let xl = if izz <= 8 { 1.0 } else { 0.0 };
let q = 0.5 * VERN18_P95[iver] - 5.5 - xl;
let aa = (yy - 1.0) * (yy - 1.0) + VERN18_YW95[iver] * VERN18_YW95[iver];
let bb = yy.powf(q);
let cc = (1.0 + (yy / VERN18_Y95[iver]).sqrt()).powf(VERN18_P95[iver]);
let fy = aa * bb / cc;
VERN18_S95[iver] * T18 * fy
}
}
/// Ca (Z=20) Verner 截面。
///
/// 计算 Ca I - Ca XX 所有钙离子的基态光电离截面。
fn vern20(e: f64, izz: i32) -> f64 {
let iver = (izz - 1) as usize;
if e < VERN20_EMX[iver] {
let xx = e / VERN20_E0[iver] - VERN20_Y0[iver];
let yy = (xx * xx + VERN20_Y1[iver] * VERN20_Y1[iver]).sqrt();
let aa = (xx - 1.0) * (xx - 1.0) + VERN20_YW[iver] * VERN20_YW[iver];
let bb = yy.powf(0.5 * VERN20_PV[iver] - 5.5);
let cc = (1.0 + (yy / VERN20_YA[iver]).sqrt()).powf(VERN20_PV[iver]);
let fy = aa * bb / cc;
VERN20_S0[iver] * T18 * fy
} else {
let yy = e / VERN20_E95[iver];
let xl = if izz <= 10 { 1.0 } else { 0.0 };
let q = 0.5 * VERN20_P95[iver] - 5.5 - xl;
let aa = (yy - 1.0) * (yy - 1.0) + VERN20_YW95[iver] * VERN20_YW95[iver];
let bb = yy.powf(q);
let cc = (1.0 + (yy / VERN20_Y95[iver]).sqrt()).powf(VERN20_P95[iver]);
let fy = aa * bb / cc;
VERN20_S95[iver] * T18 * fy
}
}
/// Fe (Z=26) Verner 截面。
///
/// 计算 Fe I - Fe XXVI 所有铁离子的基态光电离截面。
fn vern26(e: f64, izz: i32) -> f64 {
let iver = (izz - 1) as usize;
if e < VERN26_EMX[iver] {
// 1996 表达式
let xx = e / VERN26_E0[iver] - VERN26_Y0[iver];
let yy = (xx * xx + VERN26_Y1[iver] * VERN26_Y1[iver]).sqrt();
let aa = (xx - 1.0) * (xx - 1.0) + VERN26_YW[iver] * VERN26_YW[iver];
let bb = yy.powf(0.5 * VERN26_PV[iver] - 5.5);
let cc = (1.0 + (yy / VERN26_YA[iver]).sqrt()).powf(VERN26_PV[iver]);
let fy = aa * bb / cc;
VERN26_S0[iver] * T18 * fy
} else {
// 1995 高能表达式 (内壳层电子电离)
let yy = e / VERN26_E95[iver];
let xl = if izz <= 16 { 1.0 } else { 0.0 };
let q = 0.5 * VERN26_P95[iver] - 5.5 - xl;
let aa = (yy - 1.0) * (yy - 1.0) + VERN26_YW95[iver] * VERN26_YW95[iver];
let bb = yy.powf(q);
let cc = (1.0 + (yy / VERN26_Y95[iver]).sqrt()).powf(VERN26_P95[iver]);
let fy = aa * bb / cc;
VERN26_S95[iver] * T18 * fy
}
}
#[cfg(test)]
mod tests {
use super::*;
// 创建测试用的原子数据
fn create_test_atomic_data() -> crate::state::AtomicData {
let mut atomic = crate::state::AtomicData::default();
// 设置 H I 的数据 (最简单的情况)
// 假设能级 1 是 H I 基态
atomic.levpar.iel[0] = 1; // 离子索引 1
atomic.levpar.iatm[0] = 1; // 原子索引 1
// H I 离子参数
atomic.ionpar.nfirst[0] = 1; // 第一个能级索引
atomic.ionpar.iz[0] = 1; // 电荷 Z=1
// H 原子参数
atomic.atopar.numat[0] = 1; // 原子序数 Z=1
// 跃迁参数 - 跃迁 0 从能级 1 开始
atomic.trapar.ilow[0] = 1;
atomic
}
#[test]
fn test_verner_hydrogen() {
let atomic = create_test_atomic_data();
// 测试 H I 基态光电离
// 频率对应 13.6 eV (电离阈值)
let fr_threshold = 13.6 / HHEV;
let result = verner(fr_threshold, 0, &atomic);
// 在阈值处应该有非零截面
assert!(result > 0.0);
}
#[test]
fn test_verner_high_frequency() {
let atomic = create_test_atomic_data();
// 高频测试
let fr = 100.0 / HHEV; // 100 eV
let result = verner(fr, 0, &atomic);
assert!(result >= 0.0);
}
#[test]
fn test_verner_data_loaded() {
// 验证数据已加载
assert!(VERNER_S0.len() > 0);
assert!(VERNER_E0.len() > 0);
assert!(VERNER_IV0.len() == 14);
}
}

138
src/math/wn.rs Normal file
View File

@ -0,0 +1,138 @@
//! 占据概率计算。
//!
//! 重构自 TLUSTY `wn.f`。
//!
//! 使用 Hummer & Mihalas (1988) 的方法计算氢离子占据概率。
// ============================================================================
// 常量参数
// ============================================================================
const P1: f64 = 0.1402;
const P2: f64 = 0.1285;
const P3: f64 = 1.0;
const P4: f64 = 3.15;
const P5: f64 = 4.0;
const TKN: f64 = 3.01;
const CKN: f64 = 5.33333333;
const CB0: f64 = 8.59e14;
const F23: f64 = -2.0 / 3.0;
// ============================================================================
// 主函数
// ============================================================================
/// 计算氢离子的占据概率。
///
/// 使用 Hummer & Mihalas (1988) Ap.J. 331, 794 的公式 (4.26) 和 (4.39)。
/// 近似计算 Q(β)。
///
/// # 参数
///
/// - `xn` - 对应于量子数 n 的实数
/// - `a` - 相关参数
/// - `ane` - 电子密度
/// - `z` - 离子电荷
/// - `bergfc` - β 引力因子 (来自 INPPAR COMMON)
///
/// # 返回
///
/// 占据概率 w_n
///
/// # Fortran 原始代码
///
/// ```fortran
/// function wn(xn,a,ane,z)
/// INCLUDE 'BASICS.FOR'
/// ...
/// cb=cb0*bergfc
/// ...
/// end
/// ```
pub fn wn(xn: f64, a: f64, ane: f64, z: f64, bergfc: f64) -> f64 {
// 计算 cb = cb0 * bergfc
let cb = CB0 * bergfc;
// 计算 k(n)
let xkn = if xn <= TKN {
1.0
} else {
let xn1 = 1.0 / (xn + 1.0);
CKN * xn * xn1 * xn1
};
// 计算 beta
// beta = cb * z^3 * k(n) / n^4 * ane^(2/3)
let beta = cb * z.powi(3) * xkn / xn.powi(4) * ane.powf(F23);
// 近似表达式计算 Q(beta)
let x = (1.0 + P3 * a).powf(P4);
let c1 = P1 * (x + P5 * (z - 1.0) * a.powi(3));
let c2 = P2 * x;
let f = (c1 * beta.powi(3)) / (1.0 + c2 * beta * beta.sqrt());
// w_n = f / (1 + f)
f / (1.0 + f)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_wn_basic() {
// 基本测试xn=3, z=1, ane=1e14, a=0
let result = wn(3.0, 0.0, 1e14, 1.0, 1.0);
assert!(result > 0.0 && result < 1.0, "wn should be between 0 and 1");
}
#[test]
fn test_wn_low_quantum_number() {
// 低量子数 (xn <= TKN)
let result = wn(2.0, 0.1, 1e14, 1.0, 1.0);
assert!(result > 0.0 && result < 1.0);
}
#[test]
fn test_wn_high_quantum_number() {
// 高量子数 (xn > TKN)
let result = wn(10.0, 0.1, 1e14, 1.0, 1.0);
assert!(result > 0.0 && result < 1.0);
}
#[test]
fn test_wn_different_charge() {
// 不同电荷
let result = wn(5.0, 0.1, 1e14, 2.0, 1.0);
assert!(result > 0.0 && result < 1.0);
}
#[test]
fn test_wn_different_bergfc() {
// 不同 bergfc 值
let result1 = wn(5.0, 0.1, 1e14, 1.0, 1.0);
let result2 = wn(5.0, 0.1, 1e14, 1.0, 2.0);
// bergfc 越大cb 越大beta 越大wn 应该越大
assert!(result2 > result1);
}
#[test]
fn test_wn_electron_density_effect() {
// 电子密度影响
let result1 = wn(5.0, 0.1, 1e12, 1.0, 1.0);
let result2 = wn(5.0, 0.1, 1e16, 1.0, 1.0);
// 电子密度越高wn 应该越小
assert!(result2 < result1);
}
#[test]
fn test_wn_correlation_parameter() {
// 相关参数影响
let result1 = wn(5.0, 0.0, 1e14, 1.0, 1.0);
let result2 = wn(5.0, 1.0, 1e14, 1.0, 1.0);
// a > 0 时结果应该不同
assert!((result1 - result2).abs() > 1e-10);
}
}

250
src/state/alipar.rs Normal file
View File

@ -0,0 +1,250 @@
//! ALI (加速 Lambda 迭代) 相关数组。
//!
//! 重构自 TLUSTY `ALIPAR.FOR`
use super::constants::*;
// ============================================================================
// FIXALP - 固定 ALI 参数
// ============================================================================
/// ALI 固定参数和数组。
/// 对应 COMMON /FIXALP/
///
/// 包含大量辐射转移计算中间变量:
/// - ABSO, EMIS, SCAT: 吸收/发射/散射系数
/// - REIT, REIN, REIM, REIP: 辐射等效项
/// - AREIT, AREIN, AREIM, AREIP: A 相关
/// - HEIT, HEIN, HEIM, HEIP: He 相关
#[derive(Debug, Clone)]
pub struct FixAlp {
// 频率相关 (MFREQ)
/// 吸收系数
pub abso: Vec<f64>,
/// 发射系数
pub emis: Vec<f64>,
/// 散射系数
pub scat: Vec<f64>,
// 深度相关 (MDEPTH)
/// 辐射等效 - T 导数
pub reit: Vec<f64>,
/// 辐射等效 - N 导数
pub rein: Vec<f64>,
/// 辐射等效 - M 导数
pub reim: Vec<f64>,
// 能级导数 (MLVEXP × MDEPTH)
pub reip: Vec<Vec<f64>>,
// A 矩阵相关
pub areit: Vec<f64>,
pub arein: Vec<f64>,
pub areim: Vec<f64>,
pub areip: Vec<Vec<f64>>,
// C 矩阵相关
pub creit: Vec<f64>,
pub crein: Vec<f64>,
pub creim: Vec<f64>,
pub creip: Vec<Vec<f64>>,
// 辐射等效 X
pub reix: Vec<f64>,
pub creix: Vec<f64>,
// Red 相关 - T
pub redx: Vec<f64>,
pub redt: Vec<f64>,
pub redn: Vec<f64>,
pub redm: Vec<f64>,
pub redp: Vec<Vec<f64>>,
// Red 相关 - M
pub redxm: Vec<f64>,
pub redtm: Vec<f64>,
pub rednm: Vec<f64>,
pub redmm: Vec<f64>,
pub redpm: Vec<Vec<f64>>,
// Red 相关 - P
pub redtp: Vec<f64>,
pub rednp: Vec<f64>,
pub redxp: Vec<f64>,
pub redmp: Vec<f64>,
pub redpp: Vec<Vec<f64>>,
// He 相关 - T
pub heit: Vec<f64>,
pub hein: Vec<f64>,
pub heim: Vec<f64>,
pub heip: Vec<Vec<f64>>,
// He 相关 - M
pub heitm: Vec<f64>,
pub heinm: Vec<f64>,
pub heimm: Vec<f64>,
pub heipm: Vec<Vec<f64>>,
// He 相关 - P
pub heitp: Vec<f64>,
pub heinp: Vec<f64>,
pub heimp: Vec<f64>,
pub heipp: Vec<Vec<f64>>,
// Ehe/Ere 相关
pub ehet: Vec<f64>,
pub ehen: Vec<f64>,
pub eret: Vec<f64>,
pub eren: Vec<f64>,
pub ehep: Vec<Vec<f64>>,
pub erep: Vec<Vec<f64>>,
// AP 相关
pub apt: Vec<Vec<f64>>,
pub apn: Vec<Vec<f64>>,
pub aapt: Vec<Vec<f64>>,
pub aapn: Vec<Vec<f64>>,
pub capt: Vec<Vec<f64>>,
pub capn: Vec<Vec<f64>>,
// APP 矩阵 (MLVEXP × MLVEXP × MDEPTH)
pub app: Vec<Vec<Vec<f64>>>,
pub aapp: Vec<Vec<Vec<f64>>>,
pub capp: Vec<Vec<Vec<f64>>>,
// 控制参数
pub qtlas: f64,
pub ifali: i32,
pub ifpopr: i32,
pub irprec: i32,
pub ifprec: i32,
pub itold1: i32,
pub itold2: i32,
pub itlas: i32,
}
impl Default for FixAlp {
fn default() -> Self {
Self {
abso: vec![0.0; MFREQ],
emis: vec![0.0; MFREQ],
scat: vec![0.0; MFREQ],
reit: vec![0.0; MDEPTH],
rein: vec![0.0; MDEPTH],
reim: vec![0.0; MDEPTH],
reip: vec![vec![0.0; MDEPTH]; MLVEXP],
areit: vec![0.0; MDEPTH],
arein: vec![0.0; MDEPTH],
areim: vec![0.0; MDEPTH],
areip: vec![vec![0.0; MDEPTH]; MLVEXP],
creit: vec![0.0; MDEPTH],
crein: vec![0.0; MDEPTH],
creim: vec![0.0; MDEPTH],
creip: vec![vec![0.0; MDEPTH]; MLVEXP],
reix: vec![0.0; MDEPTH],
creix: vec![0.0; MDEPTH],
redx: vec![0.0; MDEPTH],
redt: vec![0.0; MDEPTH],
redn: vec![0.0; MDEPTH],
redm: vec![0.0; MDEPTH],
redp: vec![vec![0.0; MDEPTH]; MLVEXP],
redxm: vec![0.0; MDEPTH],
redtm: vec![0.0; MDEPTH],
rednm: vec![0.0; MDEPTH],
redmm: vec![0.0; MDEPTH],
redpm: vec![vec![0.0; MDEPTH]; MLVEXP],
redtp: vec![0.0; MDEPTH],
rednp: vec![0.0; MDEPTH],
redxp: vec![0.0; MDEPTH],
redmp: vec![0.0; MDEPTH],
redpp: vec![vec![0.0; MDEPTH]; MLVEXP],
heit: vec![0.0; MDEPTH],
hein: vec![0.0; MDEPTH],
heim: vec![0.0; MDEPTH],
heip: vec![vec![0.0; MDEPTH]; MLVEXP],
heitm: vec![0.0; MDEPTH],
heinm: vec![0.0; MDEPTH],
heimm: vec![0.0; MDEPTH],
heipm: vec![vec![0.0; MDEPTH]; MLVEXP],
heitp: vec![0.0; MDEPTH],
heinp: vec![0.0; MDEPTH],
heimp: vec![0.0; MDEPTH],
heipp: vec![vec![0.0; MDEPTH]; MLVEXP],
ehet: vec![0.0; MDEPTH],
ehen: vec![0.0; MDEPTH],
eret: vec![0.0; MDEPTH],
eren: vec![0.0; MDEPTH],
ehep: vec![vec![0.0; MDEPTH]; MLVEX3],
erep: vec![vec![0.0; MDEPTH]; MLVEX3],
apt: vec![vec![0.0; MDEPTH]; MLVEXP],
apn: vec![vec![0.0; MDEPTH]; MLVEXP],
aapt: vec![vec![0.0; MDEPTH]; MLVEX3],
aapn: vec![vec![0.0; MDEPTH]; MLVEX3],
capt: vec![vec![0.0; MDEPTH]; MLVEX3],
capn: vec![vec![0.0; MDEPTH]; MLVEX3],
app: vec![vec![vec![0.0; MDEPTH]; MLVEXP]; MLVEXP],
aapp: vec![vec![vec![0.0; MDEPTH]; MLVEX3]; MLVEX3],
capp: vec![vec![vec![0.0; MDEPTH]; MLVEX3]; MLVEX3],
qtlas: 0.0,
ifali: 0,
ifpopr: 0,
irprec: 0,
ifprec: 0,
itold1: 0,
itold2: 0,
itlas: 0,
}
}
}
impl FixAlp {
/// 估算内存使用 (MB)
pub fn memory_usage_mb(&self) -> f64 {
// 主要数组大小估算
let freq_size = 3 * MFREQ * std::mem::size_of::<f64>();
let depth_1d = 22 * MDEPTH * std::mem::size_of::<f64>();
let depth_2d_lvexp = 17 * MLVEXP * MDEPTH * std::mem::size_of::<f64>();
let depth_2d_lvex3 = 10 * MLVEX3 * MDEPTH * std::mem::size_of::<f64>();
let depth_3d_lvexp = 3 * MLVEXP * MLVEXP * MDEPTH * std::mem::size_of::<f64>();
let depth_3d_lvex3 = 3 * MLVEX3 * MLVEX3 * MDEPTH * std::mem::size_of::<f64>();
(freq_size + depth_1d + depth_2d_lvexp + depth_2d_lvex3 + depth_3d_lvexp + depth_3d_lvex3) as f64
/ (1024.0 * 1024.0)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_fixalp_creation() {
let fixalp = FixAlp::default();
assert_eq!(fixalp.abso.len(), MFREQ);
assert_eq!(fixalp.reit.len(), MDEPTH);
assert_eq!(fixalp.reip.len(), MLVEXP);
}
#[test]
fn test_fixalp_memory() {
let fixalp = FixAlp::default();
let mem = fixalp.memory_usage_mb();
println!("FixAlp memory usage: {:.2} MB", mem);
assert!(mem > 0.0);
}
}

329
src/state/arrays.rs Normal file
View File

@ -0,0 +1,329 @@
//! 大型计算数组。
//!
//! 重构自 TLUSTY `ARRAY1.FOR` 中的 COMMON 块。
//! 包含辐射转移方程求解所需的大型矩阵和向量。
use super::constants::*;
// ============================================================================
// 主矩阵数组 (无标签 COMMON)
// ============================================================================
/// 主计算数组。
/// 对应 ARRAY1.FOR 中的无标签 COMMON 块
#[derive(Debug, Clone)]
pub struct MainArrays {
// 矩阵 (MTOT × MTOT)
/// A 矩阵
pub a: Vec<Vec<f64>>,
/// B 矩阵
pub b: Vec<Vec<f64>>,
/// C 矩阵
pub c: Vec<Vec<f64>>,
/// E 矩阵
pub e: Vec<Vec<f64>>,
// 向量 (MTOT)
/// 左向量
pub vecl: Vec<f64>,
/// Y1 向量
pub y1: Vec<f64>,
/// Y2 向量
pub y2: Vec<f64>,
// Ψ 相关向量 (MTOT)
/// Ψ 在前深度
pub psi0: Vec<f64>,
/// Ψ 在当前深度
pub psim: Vec<f64>,
/// Ψ 在后深度
pub psip: Vec<f64>,
// 辐射相关向量 (MTOT)
pub rad0: Vec<f64>,
pub radm: Vec<f64>,
pub radp: Vec<f64>,
// 频率相关数组 (MFREX)
/// FK 在前深度
pub fkm: Vec<f64>,
/// FK 在当前深度
pub fk0: Vec<f64>,
/// FK 在后深度
pub fkp: Vec<f64>,
// 吸收系数 (MFREX)
pub absom: Vec<f64>,
pub abso0: Vec<f64>,
pub absop: Vec<f64>,
// 发射系数 (MFREX)
pub emism: Vec<f64>,
pub emis0: Vec<f64>,
pub emisp: Vec<f64>,
// 散射系数 (MFREX)
pub scatm: Vec<f64>,
pub scat0: Vec<f64>,
pub scatp: Vec<f64>,
// 温度导数 (MFREX)
pub dabtm: Vec<f64>,
pub dabt0: Vec<f64>,
pub dabtp: Vec<f64>,
pub demtm: Vec<f64>,
pub demt0: Vec<f64>,
pub demtp: Vec<f64>,
// 密度导数 (MFREX)
pub dabnm: Vec<f64>,
pub dabn0: Vec<f64>,
pub dabnp: Vec<f64>,
pub demnm: Vec<f64>,
pub demn0: Vec<f64>,
pub demnp: Vec<f64>,
// 质量导数 (MFREX)
pub dabmm: Vec<f64>,
pub dabm0: Vec<f64>,
pub dabmp: Vec<f64>,
pub demmm: Vec<f64>,
pub demm0: Vec<f64>,
pub demmp: Vec<f64>,
// 深度权重 (MFREX)
pub wdepm: Vec<f64>,
pub wdep0: Vec<f64>,
pub wdepp: Vec<f64>,
// 能级相关 (MLEVEL)
/// 束缚-自由源函数 (前)
pub sbfm: Vec<f64>,
/// 束缚-自由源函数 (当前)
pub sbf0: Vec<f64>,
/// 束缚-自由源函数 (后)
pub sbfp: Vec<f64>,
/// 氦激发速率
pub hex: Vec<f64>,
/// 复合激发速率
pub rex: Vec<f64>,
pub rexa: Vec<f64>,
/// 束缚-自由导数
pub dsbfm: Vec<f64>,
pub dsbf0: Vec<f64>,
pub dsbfp: Vec<f64>,
/// 电荷求和
pub sumdch: Vec<f64>,
// 二维导数数组 (MLEVEL × MFREX)
pub drchm: Vec<Vec<f64>>,
pub dretm: Vec<Vec<f64>>,
pub drch0: Vec<Vec<f64>>,
pub dret0: Vec<Vec<f64>>,
pub drchp: Vec<Vec<f64>>,
pub dretp: Vec<Vec<f64>>,
}
impl Default for MainArrays {
fn default() -> Self {
Self {
a: vec![vec![0.0; MTOT]; MTOT],
b: vec![vec![0.0; MTOT]; MTOT],
c: vec![vec![0.0; MTOT]; MTOT],
e: vec![vec![0.0; MTOT]; MTOT],
vecl: vec![0.0; MTOT],
y1: vec![0.0; MTOT],
y2: vec![0.0; MTOT],
psi0: vec![0.0; MTOT],
psim: vec![0.0; MTOT],
psip: vec![0.0; MTOT],
rad0: vec![0.0; MTOT],
radm: vec![0.0; MTOT],
radp: vec![0.0; MTOT],
fkm: vec![0.0; MFREX],
fk0: vec![0.0; MFREX],
fkp: vec![0.0; MFREX],
absom: vec![0.0; MFREX],
abso0: vec![0.0; MFREX],
absop: vec![0.0; MFREX],
emism: vec![0.0; MFREX],
emis0: vec![0.0; MFREX],
emisp: vec![0.0; MFREX],
scatm: vec![0.0; MFREX],
scat0: vec![0.0; MFREX],
scatp: vec![0.0; MFREX],
dabtm: vec![0.0; MFREX],
dabt0: vec![0.0; MFREX],
dabtp: vec![0.0; MFREX],
demtm: vec![0.0; MFREX],
demt0: vec![0.0; MFREX],
demtp: vec![0.0; MFREX],
dabnm: vec![0.0; MFREX],
dabn0: vec![0.0; MFREX],
dabnp: vec![0.0; MFREX],
demnm: vec![0.0; MFREX],
demn0: vec![0.0; MFREX],
demnp: vec![0.0; MFREX],
dabmm: vec![0.0; MFREX],
dabm0: vec![0.0; MFREX],
dabmp: vec![0.0; MFREX],
demmm: vec![0.0; MFREX],
demm0: vec![0.0; MFREX],
demmp: vec![0.0; MFREX],
wdepm: vec![0.0; MFREX],
wdep0: vec![0.0; MFREX],
wdepp: vec![0.0; MFREX],
sbfm: vec![0.0; MLEVEL],
sbf0: vec![0.0; MLEVEL],
sbfp: vec![0.0; MLEVEL],
hex: vec![0.0; MLEVEL],
rex: vec![0.0; MLEVEL],
rexa: vec![0.0; MLEVEL],
dsbfm: vec![0.0; MLEVEL],
dsbf0: vec![0.0; MLEVEL],
dsbfp: vec![0.0; MLEVEL],
sumdch: vec![0.0; MLEVEL],
drchm: vec![vec![0.0; MFREX]; MLEVEL],
dretm: vec![vec![0.0; MFREX]; MLEVEL],
drch0: vec![vec![0.0; MFREX]; MLEVEL],
dret0: vec![vec![0.0; MFREX]; MLEVEL],
drchp: vec![vec![0.0; MFREX]; MLEVEL],
dretp: vec![vec![0.0; MFREX]; MLEVEL],
}
}
}
// ============================================================================
// EXPRAD - 扩展辐射数组
// ============================================================================
/// 扩展辐射数组。
/// 对应 COMMON /EXPRAD/
#[derive(Debug, Clone)]
pub struct ExpRad {
/// 吸收系数扩展 (频率 × 深度)
pub absoex: Vec<Vec<f64>>,
/// 发射系数扩展
pub emisex: Vec<Vec<f64>>,
/// 散射系数扩展
pub scatex: Vec<Vec<f64>>,
// 导数扩展
pub dabtex: Vec<Vec<f64>>,
pub demtex: Vec<Vec<f64>>,
pub dabcex: Vec<Vec<f64>>,
pub demnex: Vec<Vec<f64>>,
pub dabmex: Vec<Vec<f64>>,
pub demmex: Vec<Vec<f64>>,
// 能级导数扩展 (线性化能级 × 频率 × 深度)
pub drchex: Vec<Vec<Vec<f64>>>,
pub dretex: Vec<Vec<Vec<f64>>>,
}
impl Default for ExpRad {
fn default() -> Self {
Self {
absoex: vec![vec![0.0; MDEPTH]; MFREX],
emisex: vec![vec![0.0; MDEPTH]; MFREX],
scatex: vec![vec![0.0; MDEPTH]; MFREX],
dabtex: vec![vec![0.0; MDEPTH]; MFREX],
demtex: vec![vec![0.0; MDEPTH]; MFREX],
dabcex: vec![vec![0.0; MDEPTH]; MFREX],
demnex: vec![vec![0.0; MDEPTH]; MFREX],
dabmex: vec![vec![0.0; MDEPTH]; MFREX],
demmex: vec![vec![0.0; MDEPTH]; MFREX],
drchex: vec![vec![vec![0.0; MDEPTH]; MFREX]; MLVEXP],
dretex: vec![vec![vec![0.0; MDEPTH]; MFREX]; MLVEXP],
}
}
}
// ============================================================================
// BPOCOM - 束缚-占据矩阵
// ============================================================================
/// 束缚-占据矩阵。
/// 对应 COMMON /BPOCOM/
#[derive(Debug, Clone, Default)]
pub struct BpoCom {
/// 电子散射矩阵
pub esemat: Vec<Vec<f64>>,
/// 束缚-电子散射
pub bese: Vec<f64>,
/// 衰减
pub att: Vec<f64>,
/// Ann 矩阵对角
pub ann: Vec<f64>,
}
impl BpoCom {
pub fn new() -> Self {
Self {
esemat: vec![vec![0.0; MLEVEL]; MLEVEL],
bese: vec![0.0; MLEVEL],
att: vec![0.0; MLEVEL],
ann: vec![0.0; MLEVEL],
}
}
}
// ============================================================================
// 综合数组结构
// ============================================================================
/// TLUSTY 计算数组。
#[derive(Debug, Clone, Default)]
pub struct ComputeArrays {
pub main: MainArrays,
pub exprad: ExpRad,
pub bpocom: BpoCom,
}
impl ComputeArrays {
pub fn new() -> Self {
Self {
bpocom: BpoCom::new(),
..Default::default()
}
}
/// 估算内存使用量 (MB)
pub fn memory_usage_mb(&self) -> f64 {
let main_size = std::mem::size_of::<MainArrays>();
let exprad_size = std::mem::size_of::<ExpRad>();
let bpocom_size = std::mem::size_of::<BpoCom>();
(main_size + exprad_size + bpocom_size) as f64 / (1024.0 * 1024.0)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_main_arrays_creation() {
let arrays = MainArrays::default();
assert_eq!(arrays.a.len(), MTOT);
assert_eq!(arrays.a[0].len(), MTOT);
assert_eq!(arrays.vecl.len(), MTOT);
}
#[test]
fn test_exprad_creation() {
let exprad = ExpRad::default();
assert_eq!(exprad.absoex.len(), MFREX);
assert_eq!(exprad.absoex[0].len(), MDEPTH);
}
#[test]
fn test_compute_arrays() {
let arrays = ComputeArrays::new();
let mem = arrays.memory_usage_mb();
println!("ComputeArrays memory usage: {:.2} MB", mem);
assert!(mem > 0.0);
}
}

422
src/state/atomic.rs Normal file
View File

@ -0,0 +1,422 @@
//! 原子、离子、能级数据。
//!
//! 重构自 TLUSTY `ATOMIC.FOR` 中的 COMMON 块。
use super::constants::*;
// ============================================================================
// ATOPAR - 原子参数
// ============================================================================
/// 原子参数。
/// 对应 COMMON /ATOPAR/
#[derive(Debug, Clone)]
pub struct AtoPar {
/// 原子质量
pub amass: Vec<f64>,
/// 丰度 (原子/深度点)
pub abund: Vec<Vec<f64>>,
/// 原子序数
pub numat: Vec<i32>,
/// 原子起始能级索引
pub n0a: Vec<i32>,
/// 原子终止能级索引
pub nka: Vec<i32>,
/// 参考原子
pub nref: Vec<i32>,
/// 原子提取标志
pub iatex: Vec<i32>,
/// 参考丰度 (深度点)
pub nrefs: Vec<Vec<i32>>,
/// 采用的原子
pub iadop: Vec<i32>,
/// 固定丰度标志
pub iifix: Vec<i32>,
/// 参考原子索引
pub iatref: i32,
/// 参考模型
pub modref: i32,
}
impl Default for AtoPar {
fn default() -> Self {
Self {
amass: vec![0.0; MATOM],
abund: vec![vec![0.0; MDEPTH]; MATOM],
numat: vec![0; MATOM],
n0a: vec![0; MATOM],
nka: vec![0; MATOM],
nref: vec![0; MATOM],
iatex: vec![0; MATOM],
nrefs: vec![vec![0; MDEPTH]; MATOM],
iadop: vec![0; MATOM],
iifix: vec![0; MATOM],
iatref: 0,
modref: 0,
}
}
}
// ============================================================================
// IONPAR - 离子参数
// ============================================================================
/// 离子参数。
/// 对应 COMMON /IONPAR/
#[derive(Debug, Clone)]
pub struct IonPar {
/// 电离势 (Ry)
pub ff: Vec<f64>,
/// 电荷²
pub charg2: Vec<f64>,
/// 离子起始能级索引
pub nfirst: Vec<i32>,
/// 离子终止能级索引
pub nlast: Vec<i32>,
/// 下一个能级索引
pub nnext: Vec<i32>,
/// 原子序数 Z
pub iz: Vec<i32>,
/// 上能级求和索引
pub iupsum: Vec<i32>,
/// 碰撞耦合索引
pub icup: Vec<i32>,
/// LTE 标志
pub ilte: Vec<i32>,
/// LTE 离子标志
pub iltion: Vec<i32>,
}
impl Default for IonPar {
fn default() -> Self {
Self {
ff: vec![0.0; MION],
charg2: vec![0.0; MION],
nfirst: vec![0; MION],
nlast: vec![0; MION],
nnext: vec![0; MION],
iz: vec![0; MION],
iupsum: vec![0; MION],
icup: vec![0; MION],
ilte: vec![0; MION],
iltion: vec![0; MION],
}
}
}
// ============================================================================
// LEVPAR - 能级参数
// ============================================================================
/// 能级参数。
/// 对应 COMMON /LEVPAR/
#[derive(Debug, Clone)]
pub struct LevPar {
/// 电离能 (cm⁻¹)
pub enion: Vec<f64>,
/// 统计权重 g
pub g: Vec<f64>,
/// 主量子数
pub nquant: Vec<i32>,
/// 所属原子索引
pub iatm: Vec<i32>,
/// 所属离子索引
pub iel: Vec<i32>,
/// 能级类型
pub ilk: Vec<i32>,
/// 线性化标志
pub ilin: Vec<i32>,
/// LTE 能级标志
pub iltlev: Vec<i32>,
/// 能级索引
pub indlev: Vec<i32>,
/// 模型能级
pub imodl: Vec<i32>,
/// 显式能级标志
pub iiexp: Vec<i32>,
/// Fortran 能级标志
pub iifor: Vec<i32>,
/// 零占据概率标志
pub ipzert: Vec<i32>,
/// 零 g 标志
pub igzert: Vec<i32>,
/// 零 g 能级索引
pub indlgz: Vec<i32>,
/// 非零能级标志
pub iinonz: Vec<i32>,
/// 显式能级数
pub nlvexp: i32,
/// Fortran 能级数
pub nlvfor: i32,
/// 零能级数
pub nlvexz: i32,
/// 束缚-自由 X 标志
pub lbpfx: i32,
}
impl Default for LevPar {
fn default() -> Self {
Self {
enion: vec![0.0; MLEVEL],
g: vec![0.0; MLEVEL],
nquant: vec![0; MLEVEL],
iatm: vec![0; MLEVEL],
iel: vec![0; MLEVEL],
ilk: vec![0; MLEVEL],
ilin: vec![0; MLEVEL],
iltlev: vec![0; MLEVEL],
indlev: vec![0; MLEVEL],
imodl: vec![0; MLEVEL],
iiexp: vec![0; MLEVEL],
iifor: vec![0; MLEVEL],
ipzert: vec![0; MLEVEL],
igzert: vec![0; MLEVEL],
indlgz: vec![0; MLEVEL],
iinonz: vec![0; MLEVEL],
nlvexp: 0, nlvfor: 0, nlvexz: 0, lbpfx: 0,
}
}
}
// ============================================================================
// TRAPAR - 跃迁参数
// ============================================================================
/// 跃迁参数。
/// 对应 COMMON /TRAPAR/
#[derive(Debug, Clone)]
pub struct TraPar {
/// 跃迁频率 (Hz)
pub fr0: Vec<f64>,
/// 振子强度
pub osc0: Vec<f64>,
/// 线宽参数
pub cpar: Vec<f64>,
/// 最大频率
pub frqmx: Vec<f64>,
/// 跃迁频率 (cm⁻¹)
pub fr0pc: Vec<f64>,
/// 碰撞 Ω 矩阵
pub omecol: Vec<Vec<f64>>,
// 梯度参数
pub xgrad: f64,
pub strl1: f64,
pub strl2: f64,
pub strlx: f64,
// 索引数组
/// 下能级索引
pub ilow: Vec<i32>,
/// 上能级索引
pub iup: Vec<i32>,
/// 指数索引
pub indexp: Vec<i32>,
/// 频率索引
pub kfr0: Vec<i32>,
pub kfr1: Vec<i32>,
/// Luck 跃迁标志
pub iluctr: Vec<i32>,
/// 频率计数
pub ifc0: Vec<i32>,
pub ifc1: Vec<i32>,
pub ifr0: Vec<i32>,
pub ifr1: Vec<i32>,
/// 跃迁矩阵
pub itra: Vec<Vec<i32>>,
/// 轮廓类型
pub iprof: Vec<i32>,
/// 碰撞标志
pub icol: Vec<i32>,
/// 积分模式
pub intmod: Vec<i32>,
/// 连续跃迁标志
pub itrcon: Vec<i32>,
/// 双电子标志
pub idiel: Vec<i32>,
/// 跃迁 J-T 因子
pub ijtf: Vec<i32>,
/// 复合线标志
pub lcomp: Vec<i32>,
/// 谱线索引
pub line: Vec<i32>,
}
impl Default for TraPar {
fn default() -> Self {
Self {
fr0: vec![0.0; MTRANS],
osc0: vec![0.0; MTRANS],
cpar: vec![0.0; MTRANS],
frqmx: vec![0.0; MTRANS],
fr0pc: vec![0.0; MTRANS],
omecol: vec![vec![0.0; MLEVEL]; MLEVEL],
xgrad: 0.0, strl1: 0.0, strl2: 0.0, strlx: 0.0,
ilow: vec![0; MTRANS],
iup: vec![0; MTRANS],
indexp: vec![0; MTRANS],
kfr0: vec![0; MTRANS],
kfr1: vec![0; MTRANS],
iluctr: vec![0; MTRANS],
ifc0: vec![0; MTRANS],
ifc1: vec![0; MTRANS],
ifr0: vec![0; MTRANS],
ifr1: vec![0; MTRANS],
itra: vec![vec![0; MLEVEL]; MLEVEL],
iprof: vec![0; MTRANS],
icol: vec![0; MTRANS],
intmod: vec![0; MTRANS],
itrcon: vec![0; MTRANS],
idiel: vec![0; MTRANS],
ijtf: vec![0; MTRANS],
lcomp: vec![0; MTRANS],
line: vec![0; MTRANS],
}
}
}
// ============================================================================
// PHOSET - 光电离设置
// ============================================================================
/// 光电离设置。
/// 对应 COMMON /PHOSET/
#[derive(Debug, Clone, Default)]
pub struct PhoSet {
/// 截面参数 S₀
pub s0cs: Vec<f64>,
/// 截面参数 α
pub alfcs: Vec<f64>,
/// 截面参数 β
pub betcs: Vec<f64>,
/// 截面参数 γ
pub gamcs: Vec<f64>,
/// 束缚-自由索引
pub ibf: Vec<i32>,
}
impl PhoSet {
pub fn new() -> Self {
Self {
s0cs: vec![0.0; MLEVEL],
alfcs: vec![0.0; MLEVEL],
betcs: vec![0.0; MLEVEL],
gamcs: vec![0.0; MLEVEL],
ibf: vec![0; MLEVEL],
}
}
}
// ============================================================================
// VOIPAR - Voigt 参数
// ============================================================================
/// Voigt 轮廓参数。
/// 对应 COMMON /VOIPAR/
#[derive(Debug, Clone, Default)]
pub struct VoiPar {
/// 辐射宽度 γ
pub gamar: Vec<f64>,
/// Stark 展宽参数 1
pub stark1: Vec<f64>,
/// Stark 展宽参数 2
pub stark2: Vec<f64>,
/// Stark 展宽参数 3
pub stark3: Vec<f64>,
/// van der Waals 宽度
pub vdwh: Vec<f64>,
}
impl VoiPar {
pub fn new() -> Self {
Self {
gamar: vec![0.0; MVOIGT],
stark1: vec![0.0; MVOIGT],
stark2: vec![0.0; MVOIGT],
stark3: vec![0.0; MVOIGT],
vdwh: vec![0.0; MVOIGT],
}
}
}
// ============================================================================
// HECRAT - 氦碰撞速率
// ============================================================================
/// 氦碰撞速率系数。
/// 对应 COMMON /HECRAT/
#[derive(Debug, Clone, Default)]
pub struct HeCrat {
/// 氦碰撞速率矩阵 (19×19)
pub colhe1: [[f64; 19]; 19],
}
// ============================================================================
// 综合原子数据结构
// ============================================================================
/// TLUSTY 原子数据。
/// 包含所有原子、离子、能级、跃迁信息。
#[derive(Debug, Clone, Default)]
pub struct AtomicData {
pub atopar: AtoPar,
pub ionpar: IonPar,
pub levpar: LevPar,
pub trapar: TraPar,
pub phoset: PhoSet,
pub voipar: VoiPar,
pub hecrat: HeCrat,
}
impl AtomicData {
pub fn new() -> Self {
Self {
phoset: PhoSet::new(),
voipar: VoiPar::new(),
..Default::default()
}
}
/// 获取指定能级的原子序数 Z
pub fn get_z(&self, level: usize) -> i32 {
if level < MLEVEL {
let ion_idx = self.levpar.iel[level] as usize;
if ion_idx < MION {
return self.ionpar.iz[ion_idx];
}
}
0
}
/// 获取指定能级的电离电荷
pub fn get_charge(&self, level: usize) -> f64 {
if level < MLEVEL {
let ion_idx = self.levpar.iel[level] as usize;
if ion_idx < MION {
return self.ionpar.charg2[ion_idx].sqrt();
}
}
0.0
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_atomic_data_creation() {
let data = AtomicData::new();
assert_eq!(data.atopar.amass.len(), MATOM);
assert_eq!(data.ionpar.ff.len(), MION);
assert_eq!(data.levpar.g.len(), MLEVEL);
}
#[test]
fn test_get_z() {
let mut data = AtomicData::new();
data.levpar.iel[0] = 1;
data.ionpar.iz[1] = 2; // He
assert_eq!(data.get_z(0), 2);
}
}

428
src/state/config.rs Normal file
View File

@ -0,0 +1,428 @@
//! 运行时配置参数。
//!
//! 重构自 TLUSTY `BASICS.FOR` 中的控制 COMMON 块。
use super::constants::*;
// ============================================================================
// BASNUM - 基本数值计数器
// ============================================================================
/// 基本数值计数器。
/// 对应 COMMON /BASNUM/
#[derive(Debug, Clone)]
pub struct BasNum {
/// 显式原子数
pub natom: i32,
/// 显式离子数
pub nion: i32,
/// 显式能级数
pub nlevel: i32,
/// 跃迁数
pub ntrans: i32,
/// 深度点数
pub nd: i32,
/// 频率点数
pub nfreq: i32,
/// 连续谱频率数
pub nfreqc: i32,
/// 线性化频率数
pub nfreqe: i32,
// 各种选项标志
pub ioptab: i32,
pub idisk: i32,
pub izscal: i32,
pub idmfix: i32,
pub iheso6: i32,
pub ifmol: i32,
pub ifentr: i32,
pub nfreql: i32,
pub nlev0: i32,
pub icolhn: i32,
pub ioscor: i32,
pub ilgder: i32,
pub ifryb: i32,
pub ifrset: i32,
pub nfread: i32,
pub nelsc: i32,
pub ntranc: i32,
pub iover: i32,
pub jali: i32,
pub ibc: i32,
pub iubc: i32,
pub intens: i32,
pub irder: i32,
pub ilmcor: i32,
pub ifdiel: i32,
pub ifalih: i32,
pub iftene: i32,
pub itndre: i32,
pub ilpsct: i32,
pub ilasct: i32,
pub irte: i32,
pub idlte: i32,
pub ibfint: i32,
pub intrpl: i32,
pub ichang: i32,
pub natoms: i32,
pub ipslte: i32,
pub ispodf: i32,
pub itlucy: i32,
pub nretc: i32,
pub ifrayl: i32,
pub ifprad: i32,
}
impl Default for BasNum {
fn default() -> Self {
Self {
natom: 0, nion: 0, nlevel: 0, ntrans: 0,
nd: 0, nfreq: 0, nfreqc: 0, nfreqe: 0,
ioptab: 0, idisk: 0, izscal: 0, idmfix: 0,
iheso6: 0, ifmol: 0, ifentr: 0, nfreql: 0,
nlev0: 0, icolhn: 0, ioscor: 0, ilgder: 0,
ifryb: 0, ifrset: 0, nfread: 0, nelsc: 0,
ntranc: 0, iover: 0, jali: 0, ibc: 0,
iubc: 0, intens: 0, irder: 0, ilmcor: 0,
ifdiel: 0, ifalih: 0, iftene: 0, itndre: 0,
ilpsct: 0, ilasct: 0, irte: 0, idlte: 0,
ibfint: 0, intrpl: 0, ichang: 0, natoms: 0,
ipslte: 0, ispodf: 0, itlucy: 0, nretc: 0,
ifrayl: 0, ifprad: 0,
}
}
}
// ============================================================================
// INPPAR - 输入参数
// ============================================================================
/// 输入参数。
/// 对应 COMMON /INPPAR/
#[derive(Debug, Clone)]
pub struct InpPar {
/// 有效温度 (K)
pub teff: f64,
/// 表面重力 (cm/s²)
pub grav: f64,
/// 总 He 丰度 (质量分数)
pub ytot: Vec<f64>,
/// 平均分子量
pub wmm: Vec<f64>,
/// He 质量分数
pub wmy: Vec<f64>,
/// 分子温度极限
pub tmolim: f64,
// 恒星风参数
pub xmstar: f64,
pub xmdot: f64,
pub rstar: f64,
pub alpha0: f64,
pub reynum: f64,
// 引力相关
pub qgrav: f64,
pub edisc: f64,
pub dzeta: f64,
pub reldst: f64,
// 粘性参数
pub visc: f64,
pub zeta0: f64,
pub zeta1: f64,
pub dmvisc: f64,
pub fractv: f64,
// 自转参数
pub omeg32: f64,
pub wbarm: f64,
pub wbar: f64,
pub alphav: f64,
pub pgas0: f64,
// 控制参数
pub bergfc: f64, // β 引力因子 (wn.f 使用)
pub cutlym: f64,
pub cutbal: f64,
// 选项标志
pub isplin: i32,
pub irsplt: i32,
pub ivisc: i32,
pub ibche: i32,
pub lte: bool,
pub ltgrey: bool,
pub lchc: bool,
pub lresc: bool,
}
impl InpPar {
pub fn new() -> Self {
Self {
teff: 10000.0,
grav: 4.44, // log g = 4.44 (太阳)
ytot: vec![0.0; MDEPTH],
wmm: vec![0.0; MDEPTH],
wmy: vec![0.0; MDEPTH],
tmolim: 0.0,
xmstar: 0.0, xmdot: 0.0, rstar: 0.0, alpha0: 0.0, reynum: 0.0,
qgrav: 0.0, edisc: 0.0, dzeta: 0.0, reldst: 0.0,
visc: 0.0, zeta0: 0.0, zeta1: 0.0, dmvisc: 0.0, fractv: 0.0,
omeg32: 0.0, wbarm: 0.0, wbar: 0.0, alphav: 0.0, pgas0: 0.0,
bergfc: 1.0, // 默认值
cutlym: 0.0, cutbal: 0.0,
isplin: 0, irsplt: 0, ivisc: 0, ibche: 0,
lte: false, ltgrey: false, lchc: false, lresc: false,
}
}
}
impl Default for InpPar {
fn default() -> Self {
Self::new()
}
}
// ============================================================================
// MATKEY - 材料键
// ============================================================================
/// 材料键。
/// 对应 COMMON /MATKEY/
#[derive(Debug, Clone, Default)]
pub struct MatKey {
pub nn: i32,
pub nn0: i32,
pub inhe: i32,
pub inre: i32,
pub inpc: i32,
pub inse: i32,
pub inzd: i32,
pub inmp: i32,
pub ndre: i32,
pub insel: i32,
}
// ============================================================================
// RUNKEY - 运行控制
// ============================================================================
/// 运行控制键。
/// 对应 COMMON /RUNKEY/
#[derive(Debug, Clone, Default)]
pub struct RunKey {
pub chmax: f64,
pub iter: i32,
pub niter: i32,
pub nitzer: i32,
pub init: i32,
pub lac2: i32,
pub lfin: i32,
}
// ============================================================================
// CONKEY - 收敛控制
// ============================================================================
/// 收敛控制键。
/// 对应 COMMON /CONKEY/
#[derive(Debug, Clone, Default)]
pub struct ConKey {
pub hmix0: f64,
pub crflim: f64,
pub nconit: i32,
pub iconv: i32,
pub indl: i32,
pub ipress: i32,
pub itemp: i32,
pub icbeg: i32,
pub itmcor: i32,
pub iconre: i32,
pub ideepc: i32,
pub ndcgap: i32,
pub idconz: i32,
}
// ============================================================================
// OPCPAR - 额外不透明度控制
// ============================================================================
/// 额外不透明度控制。
/// 对应 COMMON /OPCPAR/
#[derive(Debug, Clone, Default)]
pub struct OpcPar {
pub iophmi: i32, // H⁻
pub ioph2p: i32, // H₂⁺
pub iophem: i32, // He⁻
pub iopch: i32, // CH
pub iopoh: i32, // OH
pub ioph2m: i32, // H₂⁻
pub ioh2h2: i32, // H₂-H₂ CIA
pub ioh2he: i32, // H₂-He CIA
pub ioh2h: i32, // H₂-H CIA
pub iohhe: i32, // H-He CIA
pub iophli: i32, // H⁻ 自由-自由
pub irsct: i32, // 汤姆逊散射
pub irsche: i32, // He 散射
pub irsch2: i32, // H₂ 散射
pub keepop: i32,
pub iopold: i32,
}
// ============================================================================
// PRINTS - 打印控制
// ============================================================================
/// 打印控制。
/// 对应 COMMON /PRINTS/
#[derive(Debug, Clone, Default)]
pub struct Prints {
pub iprint: i32,
pub ipring: i32,
pub iprind: i32,
pub iprinp: i32,
pub icoolp: i32,
pub ichckp: i32,
pub ipopac: i32,
pub iprini: i32,
}
// ============================================================================
// ANGLES - 角度设置
// ============================================================================
/// 角度设置。
/// 对应 COMMON /ANGLES/
#[derive(Debug, Clone)]
pub struct Angles {
/// 角度余弦值
pub amu: Vec<f64>,
/// 角度权重
pub wtmu: Vec<f64>,
/// 角度函数
pub fmu: Vec<f64>,
/// 角度点数
pub nmu: i32,
}
impl Default for Angles {
fn default() -> Self {
Self {
amu: vec![0.0; MMU],
wtmu: vec![0.0; MMU],
fmu: vec![0.0; MMU],
nmu: 0,
}
}
}
// ============================================================================
// COMPTN - Compton 散射角度参数
// ============================================================================
/// Compton 散射角度参数。
/// 对应 COMMON /COMPTN/ 和 /ANGNUM/
#[derive(Debug, Clone)]
pub struct Comptn {
/// 角度余弦值
pub amuc: Vec<f64>,
/// 角度权重
pub wtmuc: Vec<f64>,
/// amuc * wtmuc
pub amuc1: Vec<f64>,
/// amuc² * wtmuc
pub amuc2: Vec<f64>,
/// amuc³ * wtmuc
pub amuc3: Vec<f64>,
/// 角度相关量
pub amuj: Vec<f64>,
pub amuk: Vec<f64>,
pub amuh: Vec<f64>,
pub amun: Vec<f64>,
/// Compton 散射 α 系数
pub calph: Vec<Vec<f64>>,
/// Compton 散射 β 系数
pub cbeta: Vec<Vec<f64>>,
/// Compton 散射 γ 系数
pub cgamm: Vec<Vec<f64>>,
/// 辐射零点
pub radzer: f64,
/// Compton 频率
pub frlcom: f64,
/// Compton 截面
pub sigec: Vec<f64>,
/// 原始频率索引
pub ijorig: Vec<i32>,
/// Compton 角度点数
pub nmuc: i32,
}
impl Default for Comptn {
fn default() -> Self {
Self {
amuc: vec![0.0; MMUC],
wtmuc: vec![0.0; MMUC],
amuc1: vec![0.0; MMUC],
amuc2: vec![0.0; MMUC],
amuc3: vec![0.0; MMUC],
amuj: vec![0.0; MMUC],
amuk: vec![0.0; MMUC],
amuh: vec![0.0; MMUC],
amun: vec![0.0; MMUC],
calph: vec![vec![0.0; MMUC]; MMUC],
cbeta: vec![vec![0.0; MMUC]; MMUC],
cgamm: vec![vec![0.0; MMUC]; MMUC],
radzer: 0.0,
frlcom: 0.0,
sigec: vec![0.0; MFREQ],
ijorig: vec![0; MFREQ],
nmuc: 0,
}
}
}
// ============================================================================
// 综合配置结构
// ============================================================================
/// TLUSTY 综合配置。
/// 包含所有控制参数。
#[derive(Debug, Clone, Default)]
pub struct TlustyConfig {
pub basnum: BasNum,
pub inppar: InpPar,
pub matkey: MatKey,
pub runkey: RunKey,
pub conkey: ConKey,
pub opcpar: OpcPar,
pub prints: Prints,
pub angles: Angles,
pub comptn: Comptn,
}
impl TlustyConfig {
pub fn new() -> Self {
Self::default()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_config_creation() {
let config = TlustyConfig::new();
assert_eq!(config.basnum.natom, 0);
assert_eq!(config.inppar.teff, 10000.0);
}
#[test]
fn test_inppar_arrays() {
let inppar = InpPar::new();
assert_eq!(inppar.ytot.len(), MDEPTH);
}
}

164
src/state/constants.rs Normal file
View File

@ -0,0 +1,164 @@
//! 物理常数和维度参数。
//!
//! 重构自 TLUSTY `BASICS.FOR` 中的 PARAMETER 定义。
// ============================================================================
// 维度参数 (数组大小)
// ============================================================================
/// 最大原子数
pub const MATOM: usize = 99;
/// 最大离子数
pub const MION: usize = 170;
/// 最大能级数
pub const MLEVEL: usize = 1134;
/// 最大线性化能级数
pub const MLVEXP: usize = 233;
/// 最大跃迁数
pub const MTRANS: usize = 21000;
/// 最大深度点数
pub const MDEPTH: usize = 100;
/// 最大频率点数
pub const MFREQ: usize = 135000;
/// 工作频率数组大小
pub const MFREQP: usize = 220000;
/// 连续谱频率点数
pub const MFREQC: usize = 125000;
/// 线性化频率数
pub const MFREX: usize = 54;
/// 每条谱线频率数
pub const MFREQL: usize = 25798;
/// 最大线性化参数数
pub const MTOT: usize = 280;
/// 最大角度点数
pub const MMU: usize = 6;
/// Compton 散射最大角度点数
pub const MMUC: usize = 2;
/// 光电离截面拟合点数
pub const MFIT: usize = 357;
/// 重叠跃迁数
pub const MITJ: usize = 380;
/// 伪连续能级数
pub const MMCDW: usize = 26;
/// 合并能级数
pub const MMER: usize = 12;
/// Voigt 轮廓谱线数
pub const MVOIGT: usize = 8080;
/// 占据概率离子最大电荷
pub const MZZ: usize = 10;
/// 最高氢能级
pub const NLMX: usize = 80;
/// 对角预处理能级数
pub const MLEVE3: usize = 1;
/// 对角/三对角操作能级数
pub const MLVEX3: usize = 1;
/// 对角/三对角操作跃迁数
pub const MTRAN3: usize = 1;
/// 光电离截面数
pub const MCROSS: usize = MLEVEL + 5;
/// 束缚-自由跃迁数
pub const MBF: usize = MLEVEL;
// ============================================================================
// 物理常数 (CGS 单位)
// ============================================================================
/// 普朗克常数 h (erg·s)
pub const H: f64 = 6.6256e-27;
/// 玻尔兹曼常数 k (erg/K)
pub const BOLK: f64 = 1.38054e-16;
/// h/k (s·K)
pub const HK: f64 = 4.79928144e-11;
/// 光速 c (Å/s)
pub const CAS: f64 = 2.997925e18;
/// 氢电离能 (erg)
pub const EH: f64 = 2.17853041e-11;
/// 2*h/c³
pub const BN: f64 = 1.4743e-2;
/// 汤姆逊散射截面 (cm²)
pub const SIGE: f64 = 6.6516e-25;
/// 斯特藩-玻尔兹曼常数/4π
pub const SIG4P: f64 = 4.5114062e-6;
/// 4π/h
pub const PI4H: f64 = 1.8966e27;
/// 4π/c
pub const PCK: f64 = 4.19168946e-10;
/// 氢原子质量 (g)
pub const HMASS: f64 = 1.67333e-24;
// ============================================================================
// 数学常数
// ============================================================================
/// 单位 1
pub const UN: f64 = 1.0;
/// 二分之一
pub const HALF: f64 = 0.5;
/// 二
pub const TWO: f64 = 2.0;
/// π
pub const PI: f64 = std::f64::consts::PI;
/// 2π
pub const TWOPI: f64 = 2.0 * std::f64::consts::PI;
/// 4π
pub const FOURPI: f64 = 4.0 * std::f64::consts::PI;
/// ln(10)
pub const LN10: f64 = std::f64::consts::LN_10;
// ============================================================================
// 辅助函数
// ============================================================================
/// 安全指数函数,避免溢出。
/// 重构自 TLUSTY `expo.f`
#[inline]
pub fn expo(x: f64) -> f64 {
const MAX_EXP: f64 = 700.0;
if x > MAX_EXP {
(MAX_EXP).exp()
} else if x < -MAX_EXP {
0.0
} else {
x.exp()
}
}
/// 安全对数函数,避免 log(0)。
#[inline]
pub fn safe_log(x: f64) -> f64 {
if x <= 0.0 {
f64::NEG_INFINITY
} else {
x.ln()
}
}
/// 安全对数10函数。
#[inline]
pub fn safe_log10(x: f64) -> f64 {
if x <= 0.0 {
f64::NEG_INFINITY
} else {
x.log10()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_expo() {
assert!((expo(0.0) - 1.0).abs() < 1e-15);
assert!((expo(1.0) - std::f64::consts::E).abs() < 1e-15);
assert_eq!(expo(-1000.0), 0.0);
assert!(expo(1000.0).is_finite());
}
#[test]
fn test_constants() {
assert!(H > 0.0);
assert!(BOLK > 0.0);
assert!(CAS > 0.0);
}
}

246
src/state/iterat.rs Normal file
View File

@ -0,0 +1,246 @@
//! 迭代控制参数。
//!
//! 重构自 TLUSTY `ITERAT.FOR`
use super::constants::*;
// ============================================================================
// 迭代参数维度
// ============================================================================
/// 最大迭代次数
pub const MITER: usize = 200;
/// 最大 Lambda 点数
pub const MLAMBD: usize = 100;
// ============================================================================
// LAMBDA - Lambda 迭代参数
// ============================================================================
/// Lambda 迭代控制。
/// 对应 COMMON /LAMBDA/
#[derive(Debug, Clone, Default)]
pub struct Lambda {
/// Lambda 点数
pub nlambd: i32,
/// 固定频率标志
pub iffix: Vec<i32>,
/// 显式净跃迁
pub netexp: Vec<i32>,
/// 固定净跃迁
pub netfix: Vec<i32>,
/// 显式能级跃迁
pub ietexp: Vec<Vec<i32>>,
/// 固定能级跃迁
pub ietfix: Vec<Vec<i32>>,
/// Lambda 索引
pub nitlam: Vec<i32>,
/// 能级修正标志
pub ielcor: i32,
}
impl Lambda {
pub fn new() -> Self {
Self {
nlambd: 0,
iffix: vec![0; MITER],
netexp: vec![0; MITER],
netfix: vec![0; MITER],
ietexp: vec![vec![0; MLAMBD]; MITER],
ietfix: vec![vec![0; MLAMBD]; MITER],
nitlam: vec![0; MITER + 1],
ielcor: 0,
}
}
}
// ============================================================================
// CHNMAT - 材料变化控制
// ============================================================================
/// 材料变化控制。
/// 对应 COMMON /CHNMAT/
#[derive(Debug, Clone, Default)]
pub struct ChnMat {
/// He 变化索引
pub inhe0: Vec<i32>,
/// 辐射变化索引
pub inre0: Vec<i32>,
/// PC 变化索引
pub inpc0: Vec<i32>,
/// 深度变化索引
pub indl0: Vec<i32>,
/// SE 变化索引
pub inse0: Vec<i32>,
/// MP 变化索引
pub inmp0: Vec<i32>,
/// NN 变化
pub nn00: Vec<i32>,
/// 深度修正
pub ndre0: Vec<i32>,
/// 材料变化标志
pub lchmat: i32,
/// 辐射存储
pub lirost: i32,
}
impl ChnMat {
pub fn new() -> Self {
Self {
inhe0: vec![0; MITER],
inre0: vec![0; MITER],
inpc0: vec![0; MITER],
indl0: vec![0; MITER],
inse0: vec![0; MITER],
inmp0: vec![0; MITER],
nn00: vec![0; MITER],
ndre0: vec![0; MITER],
lchmat: 0,
lirost: 0,
}
}
}
// ============================================================================
// ACCEL - 加速参数
// ============================================================================
/// 加速收敛参数。
/// 对应 COMMON /ACCEL/
#[derive(Debug, Clone, Default)]
pub struct Accel {
/// 过松弛因子
pub orelax: f64,
/// 迭代计数
pub itek: i32,
/// 加速标志
pub iacc: i32,
/// 初始加速
pub iacc0: i32,
/// 加速方向
pub iacd: i32,
/// Kantorovich 向量
pub kant: Vec<i32>,
/// 奇异点计数
pub ksng: i32,
/// 奇异点位置
pub lsng: Vec<i32>,
/// Aitken 步
pub laso: i32,
/// 重启动标志
pub lres2: i32,
}
impl Accel {
pub fn new() -> Self {
Self {
orelax: 1.0,
itek: 0,
iacc: 0,
iacc0: 0,
iacd: 0,
kant: vec![0; MITER],
ksng: 0,
lsng: vec![0; MTOT],
laso: 0,
lres2: 0,
}
}
}
// ============================================================================
// ACCLP - 加速 Lambda 参数
// ============================================================================
/// 加速 Lambda 迭代参数。
/// 对应 COMMON /ACCLP/
#[derive(Debug, Clone, Default)]
pub struct Acclp {
pub ilam: i32,
pub iacpp: i32,
pub iacc0p: i32,
pub iacdp: i32,
pub lac2p: i32,
}
// ============================================================================
// ACCLT - 温度加速
// ============================================================================
/// 温度加速参数。
/// 对应 COMMON /ACCLT/
#[derive(Debug, Clone, Default)]
pub struct Acclt {
pub iaclt: i32,
pub iacldt: i32,
}
// ============================================================================
// CHNAD - 变化附加
// ============================================================================
/// 变化附加参数。
/// 对应 COMMON /CHNAD/
#[derive(Debug, Clone, Default)]
pub struct ChnAd {
/// 最大变化
pub chmaxt: f64,
/// Lambda 计数
pub nlamt: i32,
/// 导数标志
pubilder: i32,
/// BPOP 标志
pub ibpope: i32,
}
// ============================================================================
// 综合迭代控制
// ============================================================================
/// TLUSTY 迭代控制。
#[derive(Debug, Clone, Default)]
pub struct IterControl {
pub lambda: Lambda,
pub chnmat: ChnMat,
pub accel: Accel,
pub acclp: Acclp,
pub acclt: Acclt,
pub chnad: ChnAd,
}
impl IterControl {
pub fn new() -> Self {
Self {
lambda: Lambda::new(),
chnmat: ChnMat::new(),
accel: Accel::new(),
..Default::default()
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_lambda_creation() {
let lambda = Lambda::new();
assert_eq!(lambda.iffix.len(), MITER);
assert_eq!(lambda.ietexp.len(), MITER);
assert_eq!(lambda.nitlam.len(), MITER + 1);
}
#[test]
fn test_accel_creation() {
let accel = Accel::new();
assert_eq!(accel.kant.len(), MITER);
assert_eq!(accel.lsng.len(), MTOT);
}
#[test]
fn test_iter_control() {
let ctrl = IterControl::new();
assert_eq!(ctrl.lambda.nlambd, 0);
}
}

32
src/state/mod.rs Normal file
View File

@ -0,0 +1,32 @@
//! TLUSTY 状态管理模块。
//!
//! 将 Fortran COMMON 块转换为 Rust struct。
//!
//! # 模块结构
//!
//! - `constants`: 物理和数学常数 (compile-time)
//! - `config`: 运行时配置参数
//! - `atomic`: 原子/离子/能级数据
//! - `model`: 大气模型状态
//! - `arrays`: 大型计算数组
//! - `iterat`: 迭代控制参数
//! - `alipar`: ALI (加速 Lambda 迭代) 数组
//! - `odfpar`: ODF (不透明度分布函数) 数据
pub mod constants;
pub mod config;
pub mod atomic;
pub mod model;
pub mod arrays;
pub mod iterat;
pub mod alipar;
pub mod odfpar;
pub use constants::*;
pub use config::*;
pub use atomic::*;
pub use model::*;
pub use arrays::*;
pub use iterat::*;
pub use alipar::*;
pub use odfpar::*;

524
src/state/model.rs Normal file
View File

@ -0,0 +1,524 @@
//! 大气模型状态。
//!
//! 重构自 TLUSTY `MODELQ.FOR` 中的 COMMON 块。
//! 包含温度、密度、电子密度、占据数等物理状态。
use super::constants::*;
// ============================================================================
// MODPAR - 模型参数
// ============================================================================
/// 模型基本参数。
/// 对应 COMMON /MODPAR/
#[derive(Debug, Clone)]
pub struct ModPar {
/// 深度 (柱质量密度, g/cm²)
pub dm: Vec<f64>,
/// 温度 (K)
pub temp: Vec<f64>,
/// 电子密度 (cm⁻³)
pub elec: Vec<f64>,
/// 总粒子密度 (cm⁻³)
pub dens: Vec<f64>,
/// 总粒子数
pub totn: Vec<f64>,
/// 总原子密度
pub anto: Vec<f64>,
/// 金属原子密度
pub anma: Vec<f64>,
/// 中性氢密度
pub anh1: Vec<f64>,
/// 深度变量
pub zd: Vec<f64>,
// 辅助温度量
/// h/kT
pub hkt1: Vec<f64>,
/// 1/T
pub tk1: Vec<f64>,
/// h/(kT)²
pub hkt21: Vec<f64>,
/// sqrt(T)
pub sqt1: Vec<f64>,
// 迭代中间量
pub temp1: Vec<f64>,
pub elec1: Vec<f64>,
pub dens1: Vec<f64>,
pub densi: Vec<f64>,
pub densim: Vec<f64>,
// 散射相关
/// 电子散射不透明度
pub elscat: Vec<f64>,
/// 线性化辐射参数
pub alab: Vec<f64>,
// 深度差分
pub deldm: Vec<f64>,
pub dedm1: f64,
pub deldmz: Vec<f64>,
// 速度场
pub thetav: Vec<f64>,
pub viscd: Vec<f64>,
pub dalpmx: f64,
pub xhyd: f64,
// 全局量
pub dmtot: f64,
pub rrdil: f64,
pub tempbd: f64,
pub alptav: f64,
pub alpgav: f64,
pub nalp: i32,
pub ibeta: i32,
}
impl Default for ModPar {
fn default() -> Self {
Self {
dm: vec![0.0; MDEPTH],
temp: vec![0.0; MDEPTH],
elec: vec![0.0; MDEPTH],
dens: vec![0.0; MDEPTH],
totn: vec![0.0; MDEPTH],
anto: vec![0.0; MDEPTH],
anma: vec![0.0; MDEPTH],
anh1: vec![0.0; MDEPTH],
zd: vec![0.0; MDEPTH],
hkt1: vec![0.0; MDEPTH],
tk1: vec![0.0; MDEPTH],
hkt21: vec![0.0; MDEPTH],
sqt1: vec![0.0; MDEPTH],
temp1: vec![0.0; MDEPTH],
elec1: vec![0.0; MDEPTH],
dens1: vec![0.0; MDEPTH],
densi: vec![0.0; MDEPTH],
densim: vec![0.0; MDEPTH],
elscat: vec![0.0; MDEPTH],
alab: vec![0.0; MDEPTH],
deldm: vec![0.0; MDEPTH],
dedm1: 0.0,
deldmz: vec![0.0; MDEPTH],
thetav: vec![0.0; MDEPTH],
viscd: vec![0.0; MDEPTH],
dalpmx: 0.0,
xhyd: 0.0,
dmtot: 0.0,
rrdil: 0.0,
tempbd: 0.0,
alptav: 0.0,
alpgav: 0.0,
nalp: 0,
ibeta: 0,
}
}
}
// ============================================================================
// LEVPOP - 能级占据数
// ============================================================================
/// 能级占据数。
/// 对应 COMMON /LEVPOP/
#[derive(Debug, Clone)]
pub struct LevPop {
/// 占据数 (能级 × 深度)
pub popul: Vec<Vec<f64>>,
/// Boltzmann 因子
pub bfac: Vec<Vec<f64>>,
/// 逆占据数
pub popinv: Vec<Vec<f64>>,
/// 当前深度占据数
pub popgrp: Vec<f64>,
pub pop: Vec<f64>,
/// 束缚-自由源函数
pub sbf: Vec<f64>,
pub dsbf: Vec<f64>,
/// 离子配分函数
pub usum: Vec<f64>,
}
impl Default for LevPop {
fn default() -> Self {
Self {
popul: vec![vec![0.0; MDEPTH]; MLEVEL],
bfac: vec![vec![0.0; MDEPTH]; MLEVEL],
popinv: vec![vec![0.0; MDEPTH]; MLEVEL],
popgrp: vec![0.0; MLEVEL],
pop: vec![0.0; MLEVEL],
sbf: vec![0.0; MLEVEL],
dsbf: vec![0.0; MLEVEL],
usum: vec![0.0; MION],
}
}
}
// ============================================================================
// GFFPAR - 自由-自由 Gaunt 因子
// ============================================================================
/// 自由-自由 Gaunt 因子参数。
/// 对应 COMMON /GFFPAR/
#[derive(Debug, Clone, Default)]
pub struct GffPar {
/// Gaunt 因子 (深度)
pub gf0: Vec<f64>,
pub gf1: Vec<f64>,
pub gf2: Vec<f64>,
pub gf3: Vec<f64>,
pub gf4: Vec<f64>,
pub gf5: Vec<f64>,
pub gf6: Vec<f64>,
/// Gaunt 因子导数
pub gf0d: Vec<f64>,
pub gf1d: Vec<f64>,
pub gf2d: Vec<f64>,
pub gf3d: Vec<f64>,
pub gf4d: Vec<f64>,
pub gf5d: Vec<f64>,
pub gf6d: Vec<f64>,
/// 温度步长
pub deltt: Vec<f64>,
}
impl GffPar {
pub fn new() -> Self {
Self {
gf0: vec![0.0; MDEPTH],
gf1: vec![0.0; MDEPTH],
gf2: vec![0.0; MDEPTH],
gf3: vec![0.0; MDEPTH],
gf4: vec![0.0; MDEPTH],
gf5: vec![0.0; MDEPTH],
gf6: vec![0.0; MDEPTH],
gf0d: vec![0.0; MDEPTH],
gf1d: vec![0.0; MDEPTH],
gf2d: vec![0.0; MDEPTH],
gf3d: vec![0.0; MDEPTH],
gf4d: vec![0.0; MDEPTH],
gf5d: vec![0.0; MDEPTH],
gf6d: vec![0.0; MDEPTH],
deltt: vec![0.0; MDEPTH],
}
}
}
// ============================================================================
// TOTRAD - 总辐射场
// ============================================================================
/// 总辐射场。
/// 对应 COMMON /TOTRAD/
#[derive(Debug, Clone)]
pub struct TotRad {
/// 辐射强度 (频率 × 深度)
pub rad: Vec<Vec<f64>>,
/// 频率相关深度
pub fhd: Vec<f64>,
/// 吸收系数 × 辐射
pub fak: Vec<Vec<f64>>,
pub radk: Vec<Vec<f64>>,
/// 外辐射
pub extrad: Vec<f64>,
/// 外辐射强度 (频率 × 角度)
pub extint: Vec<Vec<f64>>,
/// 氦外辐射
pub hextrd: Vec<f64>,
// 全局量
pub trad: f64,
pub wdil: f64,
pub extot: f64,
pub tstar: f64,
}
impl Default for TotRad {
fn default() -> Self {
Self {
rad: vec![vec![0.0; MDEPTH]; MFREQ],
fhd: vec![0.0; MFREQ],
fak: vec![vec![0.0; MDEPTH]; MFREQ],
radk: vec![vec![0.0; MDEPTH]; MFREQ],
extrad: vec![0.0; MFREQ],
extint: vec![vec![0.0; MMU]; MFREQ],
hextrd: vec![0.0; MFREQ],
trad: 0.0,
wdil: 0.0,
extot: 0.0,
tstar: 0.0,
}
}
}
// ============================================================================
// CURRAD - 当前深度辐射
// ============================================================================
/// 当前深度辐射。
/// 对应 COMMON /CURRAD/
#[derive(Debug, Clone, Default)]
pub struct CurRad {
pub rad1: Vec<f64>,
pub ali1: Vec<f64>,
pub fak1: Vec<f64>,
pub radcm: Vec<Vec<f64>>,
pub radl: Vec<Vec<f64>>,
pub absali: Vec<Vec<f64>>,
pub alih1: Vec<f64>,
}
impl CurRad {
pub fn new() -> Self {
Self {
rad1: vec![0.0; MDEPTH],
ali1: vec![0.0; MDEPTH],
fak1: vec![0.0; MDEPTH],
radcm: vec![vec![0.0; MDEPTH]; MFREQ],
radl: vec![vec![0.0; MDEPTH]; MFREQL],
absali: vec![vec![0.0; MDEPTH]; MFREQL],
alih1: vec![0.0; MDEPTH],
}
}
}
// ============================================================================
// FRQALL - 频率相关数组
// ============================================================================
/// 频率相关数组。
/// 对应 COMMON /FRQALL/
#[derive(Debug, Clone)]
pub struct FrqAll {
/// 频率网格 (Hz)
pub freq: Vec<f64>,
/// 频率权重
pub w: Vec<f64>,
/// 谱线轮廓
pub prof: Vec<f64>,
/// 谱线权重
pub wch: Vec<f64>,
// 索引数组
pub jik: Vec<i32>,
pub ijx: Vec<i32>,
pub ijbf: Vec<i32>,
pub ifs0: i32,
pub kij: Vec<i32>,
pub lskip: Vec<Vec<i32>>,
}
impl Default for FrqAll {
fn default() -> Self {
Self {
freq: vec![0.0; MFREQ],
w: vec![0.0; MFREQ],
prof: vec![0.0; MFREQP],
wch: vec![0.0; MFREQ],
jik: vec![0; MFREQ],
ijx: vec![0; MFREQ],
ijbf: vec![0; MFREQ],
ifs0: 0,
kij: vec![0; MFREQ],
lskip: vec![vec![0; MFREQ]; MDEPTH],
}
}
}
// ============================================================================
// TOTPRF - 总谱线轮廓
// ============================================================================
/// 谱线轮廓数组。
/// 对应 COMMON /TOTPRF/
#[derive(Debug, Clone, Default)]
pub struct TotPrf {
/// 谱线轮廓 (深度 × 频率)
pub prflin: Vec<Vec<f32>>,
}
impl TotPrf {
pub fn new() -> Self {
Self {
prflin: vec![vec![0.0; MFREQP]; MDEPTH],
}
}
}
// ============================================================================
// PHOEXP - 光电离截面展开
// ============================================================================
/// 光电离截面展开参数。
/// 对应 COMMON /PHOEXP/
#[derive(Debug, Clone, Default)]
pub struct PhoExp {
/// 频率插值系数
pub aijbf: Vec<f64>,
/// 束缚-自由截面 (MCROSS × MFREQC)
pub bfcs: Vec<Vec<f32>>,
/// 频率索引
pub ifreqb: Vec<i32>,
}
impl PhoExp {
pub fn new() -> Self {
Self {
aijbf: vec![0.0; MFREQ],
bfcs: vec![vec![0.0; MFREQC]; MCROSS],
ifreqb: vec![0; MFREQC],
}
}
}
// ============================================================================
// OBFPAR - 束缚-自由跃迁参数
// ============================================================================
/// 束缚-自由跃迁参数。
/// 对应 COMMON /OBFPAR/
#[derive(Debug, Clone, Default)]
pub struct ObfPar {
/// 束缚-自由跃迁对应的跃迁索引
pub itrbf: Vec<i32>,
}
impl ObfPar {
pub fn new() -> Self {
Self {
itrbf: vec![0; MBF],
}
}
}
// ============================================================================
// LEVADD - 能级附加数组
// ============================================================================
/// 能级附加数组。
/// 对应 COMMON /LEVADD/
#[derive(Debug, Clone, Default)]
pub struct LevAdd {
/// 离子配分函数和 (离子 × 深度)
pub usums: Vec<Vec<f64>>,
/// 配分函数温度导数
pub dusmt: Vec<Vec<f64>>,
/// 配分函数密度导数
pub dusmn: Vec<Vec<f64>>,
/// 双电子复合截面 (离子 × 深度)
pub diesig: Vec<Vec<f64>>,
}
impl LevAdd {
pub fn new() -> Self {
Self {
usums: vec![vec![0.0; MDEPTH]; MION],
dusmt: vec![vec![0.0; MDEPTH]; MION],
dusmn: vec![vec![0.0; MDEPTH]; MION],
diesig: vec![vec![0.0; MDEPTH]; MION],
}
}
}
// ============================================================================
// 综合模型状态
// ============================================================================
/// TLUSTY 大气模型状态。
#[derive(Debug, Clone, Default)]
pub struct ModelState {
pub modpar: ModPar,
pub levpop: LevPop,
pub gffpar: GffPar,
pub totrad: TotRad,
pub currad: CurRad,
pub frqall: FrqAll,
pub totprf: TotPrf,
pub phoexp: PhoExp,
pub obfpar: ObfPar,
pub levadd: LevAdd,
}
impl ModelState {
pub fn new() -> Self {
Self {
gffpar: GffPar::new(),
currad: CurRad::new(),
totprf: TotPrf::new(),
phoexp: PhoExp::new(),
obfpar: ObfPar::new(),
levadd: LevAdd::new(),
..Default::default()
}
}
/// 获取指定深度的温度
pub fn temperature(&self, depth: usize) -> f64 {
if depth < MDEPTH {
self.modpar.temp[depth]
} else {
0.0
}
}
/// 获取指定深度的电子密度
pub fn electron_density(&self, depth: usize) -> f64 {
if depth < MDEPTH {
self.modpar.elec[depth]
} else {
0.0
}
}
/// 获取指定深度的总粒子密度
pub fn total_density(&self, depth: usize) -> f64 {
if depth < MDEPTH {
self.modpar.dens[depth]
} else {
0.0
}
}
/// 获取指定能级在指定深度的占据数
pub fn population(&self, level: usize, depth: usize) -> f64 {
if level < MLEVEL && depth < MDEPTH {
self.levpop.popul[level][depth]
} else {
0.0
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_model_state_creation() {
let state = ModelState::new();
assert_eq!(state.modpar.temp.len(), MDEPTH);
assert_eq!(state.levpop.popul.len(), MLEVEL);
}
#[test]
fn test_temperature_accessor() {
let mut state = ModelState::new();
state.modpar.temp[0] = 10000.0;
assert!((state.temperature(0) - 10000.0).abs() < 1e-10);
assert_eq!(state.temperature(MDEPTH), 0.0); // 越界
}
#[test]
fn test_population_accessor() {
let mut state = ModelState::new();
state.levpop.popul[0][0] = 1e10;
assert!((state.population(0, 0) - 1e10).abs() < 1e-5);
}
}

466
src/state/odfpar.rs Normal file
View File

@ -0,0 +1,466 @@
//! ODF (不透明度分布函数) 相关参数。
//!
//! 重构自 TLUSTY `ODFPAR.FOR`
use super::constants::*;
// ============================================================================
// ODF 维度参数
// ============================================================================
/// ODF 频率点数
pub const MFODF: usize = 180;
/// ODF 热点数
pub const MHOD: usize = 3;
/// ODF 频率范围
pub const MFRO: usize = MFREQL;
/// ODF 深度维度
pub const MDODF: usize = 3;
/// Kurucz 能级数
pub const MKULEV: usize = 7000;
/// 谱线数
pub const MLINE: usize = 1140000;
/// Fe 系数数
pub const MCFE: usize = 7824000;
// ============================================================================
// ODFION - ODF 离子控制
// ============================================================================
/// ODF 离子控制。
/// 对应 COMMON /ODFION/
#[derive(Debug, Clone, Default)]
pub struct OdfIon {
/// ODF 起始索引 1
pub inodf1: Vec<i32>,
/// ODF 起始索引 2
pub inodf2: Vec<i32>,
/// 束缚-自由截面索引
pub inbfcs: Vec<i32>,
/// Kurucz 观测标志
pub ikobs: Vec<i32>,
}
impl OdfIon {
pub fn new() -> Self {
Self {
inodf1: vec![0; MION],
inodf2: vec![0; MION],
inbfcs: vec![0; MION],
ikobs: vec![0; MION],
}
}
}
// ============================================================================
// ODFCTR - ODF 控制
// ============================================================================
/// ODF 控制参数。
/// 对应 COMMON /ODFCTR/
#[derive(Debug, Clone, Default)]
pub struct OdfCtr {
/// ODF 频率
pub frodf: Vec<f64>,
/// ODF 频率计数
pub nfrodf: Vec<i32>,
/// 能级 ODF 索引
pub indodf: Vec<i32>,
/// 跃迁 ODF 索引
pub jndodf: Vec<i32>,
}
impl OdfCtr {
pub fn new() -> Self {
Self {
frodf: vec![0.0; MLEVEL],
nfrodf: vec![0; MHOD],
indodf: vec![0; MLEVEL],
jndodf: vec![0; MTRANS],
}
}
}
// ============================================================================
// ODFFRQ - ODF 频率数据
// ============================================================================
/// ODF 频率数据。
/// 对应 COMMON /ODFFRQ/
#[derive(Debug, Clone, Default)]
pub struct OdfFrq {
/// ODF 频率 (起点)
pub fros: Vec<Vec<f64>>,
/// 波数
pub wnus: Vec<Vec<f64>>,
/// XDO
pub xdo: Vec<Vec<f64>>,
/// KDO
pub kdo: Vec<Vec<i32>>,
}
impl OdfFrq {
pub fn new() -> Self {
Self {
fros: vec![vec![0.0; MHOD]; MFRO],
wnus: vec![vec![0.0; MHOD]; MFRO],
xdo: vec![vec![0.0; 3]; MHOD],
kdo: vec![vec![0; 4]; MHOD],
}
}
}
// ============================================================================
// ODFMOD - ODF 模型数据
// ============================================================================
/// ODF 模型数据。
/// 对应 COMMON /ODFMOD/
#[derive(Debug, Clone, Default)]
pub struct OdfMod {
pub i1odf: Vec<i32>,
pub i2odf: Vec<i32>,
pub nqlodf: Vec<i32>,
}
impl OdfMod {
pub fn new() -> Self {
Self {
i1odf: vec![0; MLEVEL],
i2odf: vec![0; MLEVEL],
nqlodf: vec![0; MLEVEL],
}
}
}
// ============================================================================
// ODFSTK - ODF Stark 数据
// ============================================================================
/// ODF Stark 展宽数据。
/// 对应 COMMON /ODFSTK/
#[derive(Debug, Clone, Default)]
pub struct OdfStk {
/// XKIJ
pub xkij: Vec<Vec<f64>>,
/// 波长
pub wl0: Vec<Vec<f64>>,
/// FIJ
pub fij: Vec<Vec<f64>>,
}
impl OdfStk {
pub fn new(nlmx: usize) -> Self {
Self {
xkij: vec![vec![0.0; nlmx]; MHOD],
wl0: vec![vec![0.0; nlmx]; MHOD],
fij: vec![vec![0.0; nlmx]; MHOD],
}
}
}
// ============================================================================
// SPLCOM - 样条/Fe 线数据
// ============================================================================
/// Fe 线样条数据。
/// 对应 COMMON /SPLCOM/
///
/// 注意SIGFE 是非常大的数组 (3 × 7,824,000 = 23,472,000 个 f32)
#[derive(Debug, Clone)]
pub struct SplCom {
/// Fe 截面 (f32 节省内存)
pub sigfe: Vec<Vec<Vec<f32>>>,
/// 频率起点 1
pub frs1: f64,
/// 频率终点 2
pub frs2: f64,
/// 频率间隔
pub dxnu: f64,
/// J 积分
pub xjid: Vec<f64>,
/// J ID
pub jidi: Vec<i32>,
/// J ID 半径
pub jidr: Vec<i32>,
/// J ID 起始
pub jids: i32,
/// J ID 数量
pub jidn: i32,
/// 频率起始索引
pub nfrs1: i32,
/// 频率表总数
pub nftt: i32,
}
impl Default for SplCom {
fn default() -> Self {
Self {
// 使用较小的测试大小,实际运行时需要调整
sigfe: vec![vec![vec![0.0; 100]; MDODF]; 100],
frs1: 0.0,
frs2: 0.0,
dxnu: 0.0,
xjid: vec![0.0; MDEPTH],
jidi: vec![0; MDEPTH],
jidr: vec![0; MDODF],
jids: 0,
jidn: 0,
nfrs1: 0,
nftt: 0,
}
}
}
impl SplCom {
/// 创建完整大小的数组 (需要大量内存)
pub fn new_full() -> Self {
Self {
sigfe: vec![vec![vec![0.0; MCFE]; MDODF]; 1],
frs1: 0.0,
frs2: 0.0,
dxnu: 0.0,
xjid: vec![0.0; MDEPTH],
jidi: vec![0; MDEPTH],
jidr: vec![0; MDODF],
jids: 0,
jidn: 0,
nfrs1: 0,
nftt: 0,
}
}
/// 估算完整内存使用 (MB)
pub fn full_memory_mb() -> f64 {
// SIGFE: MDODF × MCFE × sizeof(f32)
(MDODF * MCFE * std::mem::size_of::<f32>()) as f64 / (1024.0 * 1024.0)
}
}
// ============================================================================
// OPALIM - 不透明度限制
// ============================================================================
/// 不透明度限制参数。
/// 对应 COMMON /OPALIM/
#[derive(Debug, Clone, Default)]
pub struct OpaLim {
pub m1file: Vec<Vec<i32>>,
pub m2file: Vec<Vec<i32>>,
pub imerg: i32,
}
impl OpaLim {
pub fn new(nlmx: usize) -> Self {
Self {
m1file: vec![vec![0; MHOD]; nlmx],
m2file: vec![vec![0; MHOD]; nlmx],
imerg: 0,
}
}
}
// ============================================================================
// OPLIMT - 不透明度限制 T
// ============================================================================
/// 不透明度限制温度相关。
/// 对应 COMMON /OPLIMT/
#[derive(Debug, Clone, Default)]
pub struct OpLimT {
pub allim1: f64,
pub ablim1: f64,
pub ablim2: f64,
pub ablim3: f64,
}
// ============================================================================
// LEVCOM - 能级 ODF 数据
// ============================================================================
/// 能级 ODF 综合数据。
/// 对应 COMMON /LEVCOM/
#[derive(Debug, Clone)]
pub struct LevCom {
/// EMKU
pub emku: Vec<Vec<f64>>,
/// YMKU
pub ymku: Vec<Vec<f64>>,
/// XEV
pub xev: Vec<Vec<f64>>,
/// XOD
pub xod: Vec<Vec<f64>>,
/// EU
pub eu: Vec<f64>,
/// JEN
pub jen: Vec<i32>,
// Kurucz 能级数据
/// EEV
pub eev: Vec<f64>,
/// AEV
pub aev: Vec<f64>,
/// SEV
pub sev: Vec<f64>,
/// WEV
pub wev: Vec<f64>,
/// EOD
pub eod: Vec<f64>,
/// AOD
pub aod: Vec<f64>,
/// SOD
pub sod: Vec<f64>,
/// WOD
pub wod: Vec<f64>,
/// KSEV
pub ksev: Vec<i32>,
/// KSOD
pub ksod: Vec<i32>,
/// NEVKU
pub nevku: Vec<i32>,
/// NODKU
pub nodku: Vec<i32>,
// 计数器
pub nlevku: i32,
pub nlinku: i32,
pub keve: i32,
pub kodd: i32,
}
impl Default for LevCom {
fn default() -> Self {
// 使用较小的测试大小
Self {
emku: vec![vec![0.0; 2]; MLEVEL],
ymku: vec![vec![0.0; 2]; MLEVEL],
xev: vec![vec![0.0; MION]; MLEVEL],
xod: vec![vec![0.0; MION]; MLEVEL],
eu: vec![0.0; 2 * MLEVEL],
jen: vec![0; 2 * MLEVEL],
// Kurucz 数据使用较小的默认大小
eev: vec![0.0; 1000],
aev: vec![0.0; 1000],
sev: vec![0.0; 1000],
wev: vec![0.0; 1000],
eod: vec![0.0; 1000],
aod: vec![0.0; 1000],
sod: vec![0.0; 1000],
wod: vec![0.0; 1000],
ksev: vec![0; 1000],
ksod: vec![0; 1000],
nevku: vec![0; MION],
nodku: vec![0; MION],
nlevku: 0,
nlinku: 0,
keve: 0,
kodd: 0,
}
}
}
impl LevCom {
/// 创建完整大小的数组
pub fn new_full() -> Self {
Self {
emku: vec![vec![0.0; 2]; MLEVEL],
ymku: vec![vec![0.0; 2]; MLEVEL],
xev: vec![vec![0.0; MION]; MLEVEL],
xod: vec![vec![0.0; MION]; MLEVEL],
eu: vec![0.0; 2 * MLEVEL],
jen: vec![0; 2 * MLEVEL],
eev: vec![0.0; MKULEV],
aev: vec![0.0; MKULEV],
sev: vec![0.0; MKULEV],
wev: vec![0.0; MKULEV],
eod: vec![0.0; MKULEV],
aod: vec![0.0; MKULEV],
sod: vec![0.0; MKULEV],
wod: vec![0.0; MKULEV],
ksev: vec![0; MKULEV],
ksod: vec![0; MKULEV],
nevku: vec![0; MION],
nodku: vec![0; MION],
nlevku: 0,
nlinku: 0,
keve: 0,
kodd: 0,
}
}
/// 估算完整内存使用 (MB)
pub fn full_memory_mb() -> f64 {
let main_size = (4 * MLEVEL * MION + 4 * MLEVEL + 2 * 2 * MLEVEL) * std::mem::size_of::<f64>();
let kurucz_size = 8 * MKULEV * std::mem::size_of::<f64>();
(main_size + kurucz_size) as f64 / (1024.0 * 1024.0)
}
}
// ============================================================================
// 综合 ODF 数据结构
// ============================================================================
/// TLUSTY ODF 综合数据。
#[derive(Debug, Clone, Default)]
pub struct OdfData {
pub odfion: OdfIon,
pub odfctr: OdfCtr,
pub odffrq: OdfFrq,
pub odfmod: OdfMod,
pub splcom: SplCom,
pub opalim: OpaLim,
pub oplimt: OpLimT,
pub levcom: LevCom,
}
impl OdfData {
pub fn new() -> Self {
Self {
odfion: OdfIon::new(),
odfctr: OdfCtr::new(),
odffrq: OdfFrq::new(),
odfmod: OdfMod::new(),
..Default::default()
}
}
/// 估算完整内存使用 (MB)
pub fn full_memory_mb() -> f64 {
SplCom::full_memory_mb() + LevCom::full_memory_mb()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_odfion_creation() {
let odfion = OdfIon::new();
assert_eq!(odfion.inodf1.len(), MION);
}
#[test]
fn test_odfctr_creation() {
let odfctr = OdfCtr::new();
assert_eq!(odfctr.frodf.len(), MLEVEL);
assert_eq!(odfctr.nfrodf.len(), MHOD);
}
#[test]
fn test_memory_estimates() {
println!("SplCom full memory: {:.2} MB", SplCom::full_memory_mb());
println!("LevCom full memory: {:.2} MB", LevCom::full_memory_mb());
println!("Total ODF memory: {:.2} MB", OdfData::full_memory_mb());
}
#[test]
fn test_odf_data_creation() {
let odf = OdfData::new();
assert_eq!(odf.odfion.inodf1.len(), MION);
}
}

View File

@ -202,7 +202,7 @@ C
9-19.184,-19.175,-19.170,-19.168,-19.169,-19.171,-19.174,-19.177,
A -19.977,-19.756,-19.606,-19.501,-19.425,-19.370,-19.330,
A-19.300,-19.278,-19.262,-19.250,-19.241,-19.235,-19.230,-19.227/
DATAC10/-20.445,-20.158,-19.958,-19.815,-19.711,-19.633,-19.574,
DATA C10/-20.445,-20.158,-19.958,-19.815,-19.711,-19.633,-19.574,
1-19.528,-19.493,-19.465,-19.442,-19.425,-19.410,-19.398,-19.389,
2 -20.980,-20.625,-20.391,-20.229,-20.110,-20.020,-19.949,
2-19.892,-19.846,-19.807,-19.775,-19.748,-19.724,-19.704,-19.687,
@ -222,7 +222,7 @@ C
9-21.147,-21.099,-21.061,-21.031,-21.006,-20.985,-20.968,-20.954,
A -22.969,-22.561,-22.288,-22.092,-21.945,-21.832,-21.743,
A-21.672,-21.616,-21.570,-21.533,-21.503,-21.477,-21.457,-21.439/
DATAC11/-24.001,-23.527,-23.199,-22.957,-22.772,-22.629,-22.516,
DATA C11/-24.001,-23.527,-23.199,-22.957,-22.772,-22.629,-22.516,
1-22.427,-22.355,-22.297,-22.250,-22.212,-22.180,-22.153,-22.131,
2 -24.233,-23.774,-23.477,-23.273,-23.128,-23.022,-22.943,
2-22.883,-22.837,-22.802,-22.774,-22.752,-22.735,-22.721,-22.710,

View File

@ -202,7 +202,7 @@ C
9-17.488,-17.493,-17.499,-17.504,-17.509,-17.514,-17.519,-17.524,
A -17.396,-17.403,-17.412,-17.420,-17.429,-17.437,-17.444,
A-17.451,-17.458,-17.464,-17.470,-17.476,-17.481,-17.487,-17.492/
DATAC10/-17.344,-17.355,-17.366,-17.377,-17.387,-17.396,-17.405,
DATA C10/-17.344,-17.355,-17.366,-17.377,-17.387,-17.396,-17.405,
1-17.413,-17.420,-17.427,-17.434,-17.440,-17.446,-17.452,-17.458,
2 -17.295,-17.307,-17.321,-17.333,-17.345,-17.355,-17.365,
2-17.373,-17.382,-17.389,-17.397,-17.404,-17.410,-17.417,-17.423,
@ -222,7 +222,7 @@ C
9-17.260,-17.270,-17.280,-17.289,-17.298,-17.307,-17.316,-17.325,
A -17.193,-17.211,-17.227,-17.241,-17.254,-17.266,-17.277,
A-17.288,-17.298,-17.308,-17.317,-17.327,-17.336,-17.345,-17.353/
DATAC11/-17.239,-17.256,-17.271,-17.284,-17.297,-17.309,-17.320,
DATA C11/-17.239,-17.256,-17.271,-17.284,-17.297,-17.309,-17.320,
1-17.330,-17.340,-17.350,-17.359,-17.369,-17.378,-17.387,-17.395,
2 -17.299,-17.315,-17.329,-17.342,-17.354,-17.365,-17.376,
2-17.386,-17.396,-17.405,-17.415,-17.424,-17.433,-17.442,-17.451,
@ -242,7 +242,7 @@ C
9-18.235,-18.243,-18.252,-18.260,-18.268,-18.277,-18.285,-18.293,
A -18.381,-18.393,-18.403,-18.413,-18.422,-18.430,-18.438,
A-18.447,-18.455,-18.463,-18.471,-18.479,-18.487,-18.495,-18.503/
DATAC12/-18.625,-18.638,-18.650,-18.660,-18.669,-18.678,-18.687,
DATA C12/-18.625,-18.638,-18.650,-18.660,-18.669,-18.678,-18.687,
1-18.695,-18.703,-18.711,-18.719,-18.726,-18.734,-18.742,-18.750,
2 -18.912,-18.929,-18.943,-18.955,-18.966,-18.975,-18.984,
2-18.993,-19.001,-19.008,-19.016,-19.023,-19.031,-19.038,-19.045,
@ -262,7 +262,7 @@ C
9-21.569,-21.585,-21.600,-21.614,-21.627,-21.640,-21.652,-21.663,
A -21.516,-21.549,-21.580,-21.609,-21.635,-21.658,-21.678,
A-21.696,-21.713,-21.728,-21.742,-21.755,-21.767,-21.779,-21.790/
DATAC13/-21.651,-21.681,-21.711,-21.738,-21.763,-21.785,-21.804,
DATA C13/-21.651,-21.681,-21.711,-21.738,-21.763,-21.785,-21.804,
1-21.821,-21.837,-21.851,-21.864,-21.876,-21.887,-21.898,-21.908,
2 -21.810,-21.831,-21.853,-21.874,-21.893,-21.910,-21.925,
2-21.938,-21.950,-21.961,-21.971,-21.980,-21.989,-21.998,-22.006,