SpectraRust/src/synspec/math/extprf.rs
fmq e2c1a4580a feat: F2R 重构全部完成 + 自动化脚本改进
Phase 1 翻译 (完成):
- TLUSTY 350 函数 100% 翻译
- SYNSPEC 168 函数 100% 翻译
- ~495 Rust 模块

Phase 2 集成 (完成):
- TLUSTY RESOLV 7 个 TODO 全部清除
- TLUSTY Runner IJALI 频率选择实现
- OPFRAC ioniz.dat 解析完整实现
- SYNSPEC Runner 编排流程连接完成
- SYNSPEC RESOLV OPAC→RTE→OUTPRI 调用链完整

Phase 3 验证 (完成, 修复 8 处 bug):
- INITIA: compute_hydrogen_level_bounds 索引混合修复
- INILIN: GAMR0/GS0/GW0 展宽公式修复, 经典 VdW 公式修复
- INIBL0: CNM 常数 2.997925e18→e17 修复
- OPAC: Lyman IJ=2 修正缺失修复
- RTE: minv3 矩阵求逆符号错误修复

自动化脚本改进:
- specf2r.sh: 添加 429 限流退避、完成检测、同步等待
- SKILL.md: 三阶段工作流 + 状态文件系统
- references/: Phase 1/2/3 独立参考文档

新增:
- src/bin/synspec.rs: SYNSPEC 可执行文件入口
- .f2r_phase/.f2r_tasks/.f2r_complete: 状态管理文件

编译: 0 错误 | Clippy: 0 错误 | 测试: voigt 28 + eldens 5 通过

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-08 14:54:53 +08:00

152 lines
4.9 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

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

//! 谱线轮廓波长外推函数。
//!
//! 重构自 SYNSPEC `extprf.f`
//!
//! 在 Shamey 或 Barnard, Cooper, Smith 表中进行波长外推。
//! 使用 Cooper 建议的特殊公式。
/// Cooper 外推公式计算谱线轮廓。
///
/// # 参数
///
/// * `dlam` - 波长偏移
/// * `it` - 温度索引 (1-4)
/// * `iline` - 谱线索引 (1-4)
/// * `anel` - 电子密度对数参数
/// * `dlast` - 最后一个波长点
/// * `plast` - 最后一个轮廓值
///
/// # 返回值
///
/// 外推的谱线轮廓值
///
/// # Panics
///
/// 如果 `it` 或 `iline` 不在 1-4 范围内会 panic。
///
/// # 算法
///
/// 使用 Cooper 公式:
/// ```text
/// WE = W0(it,iline) * exp(anel * ln(10)) * 1e-16
/// F = |dlast|^2.5 * (plast - WE / (pi * dlast^2))
/// EXTPRF = (WE / pi + F / sqrt(|dlam|)) / dlam^2
/// ```
pub fn extprf(dlam: f64, it: usize, iline: usize, anel: f64, dlast: f64, plast: f64) -> f64 {
// W0 数组:温度索引 (行) x 谱线索引 (列)
// Fortran DATA 语句是列优先存储
const W0: [[f64; 4]; 4] = [
[1.460, 6.130, 4.040, 2.312], // it=1
[1.269, 5.150, 3.490, 1.963], // it=2
[1.079, 4.240, 2.960, 1.624], // it=3
[0.898, 3.450, 2.470, 1.315], // it=4
];
let it_idx = it - 1; // 转换为 0 索引
let iline_idx = iline - 1;
// 获取 W0 值
let w0_val = W0[it_idx][iline_idx];
// WE = W0 * 10^anel * 1e-16
// Fortran: EXP(ANEL*2.3025851) = 10^ANEL (因为 ln(10) ≈ 2.3025851)
let we = w0_val * (anel * std::f64::consts::LN_10).exp() * 1e-16;
// 使用 PI 的精确值
const PI: f64 = std::f64::consts::PI;
let dlasta = dlast.abs();
// D52 = |dlast|^2.5 = |dlast|^2 * sqrt(|dlast|)
let d52 = dlasta * dlasta * dlasta.sqrt();
// F = D52 * (plast - WE / (pi * dlast^2))
let f = d52 * (plast - we / (PI * dlast * dlast));
// EXTPRF = (WE / pi + F / sqrt(|dlam|)) / dlam^2
(we / PI + f / dlam.abs().sqrt()) / (dlam * dlam)
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_relative_eq;
#[test]
fn test_basic() {
// 基本测试
let result = extprf(0.1, 1, 1, 0.0, 0.5, 1.0);
assert!(result.is_finite());
assert!(result > 0.0);
}
#[test]
fn test_different_it_with_large_anel() {
// 使用大的 anel 值使 W0 差异显现
// WE = W0 * 10^anel * 1e-16当 anel 大时 W0 差异才明显
let anel = 10.0; // 10^10 = 1e10 倍放大
let base = extprf(0.1, 1, 1, anel, 0.5, 1.0);
let t2 = extprf(0.1, 2, 1, anel, 0.5, 1.0);
let t3 = extprf(0.1, 3, 1, anel, 0.5, 1.0);
let t4 = extprf(0.1, 4, 1, anel, 0.5, 1.0);
// 不同温度索引应该产生不同结果
assert!((base - t2).abs() > 1e-10, "IT=1 vs IT=2 should differ");
assert!((t2 - t3).abs() > 1e-10, "IT=2 vs IT=3 should differ");
assert!((t3 - t4).abs() > 1e-10, "IT=3 vs IT=4 should differ");
}
#[test]
fn test_different_iline_with_large_anel() {
// 使用大的 anel 值使 W0 差异显现
let anel = 10.0;
let l1 = extprf(0.1, 1, 1, anel, 0.5, 1.0);
let l2 = extprf(0.1, 1, 2, anel, 0.5, 1.0);
let l3 = extprf(0.1, 1, 3, anel, 0.5, 1.0);
let l4 = extprf(0.1, 1, 4, anel, 0.5, 1.0);
// 不同谱线索引应该产生不同结果
assert!((l1 - l2).abs() > 1e-10, "ILINE=1 vs ILINE=2 should differ");
assert!((l2 - l3).abs() > 1e-10, "ILINE=2 vs ILINE=3 should differ");
assert!((l3 - l4).abs() > 1e-10, "ILINE=3 vs ILINE=4 should differ");
}
#[test]
fn test_with_anel() {
// 测试带电子密度参数
// 注意anel 增加时 WE 增加但 F 项可能减小,所以结果不一定增加
// 这里测试 anel 确实影响结果
let zero = extprf(0.1, 1, 1, 0.0, 0.5, 1.0);
let pos = extprf(0.1, 1, 1, 15.0, 0.5, 1.0);
// anel 变化应该改变结果(不验证方向,只验证变化)
assert!((pos - zero).abs() > 1e-6, "anel=15 should change the result");
}
#[test]
fn test_symmetry() {
// 测试正负 dlast 的对称性
let pos = extprf(0.1, 1, 1, 0.0, 0.5, 1.0);
let neg = extprf(0.1, 1, 1, 0.0, -0.5, 1.0);
// 由于 dlast 使用 abs(),结果应该相同
assert_relative_eq!(pos, neg, epsilon = 1e-14);
}
#[test]
fn test_w0_values() {
// 验证 W0 数组值正确
// 直接测试:当 anel 很大且 plast=0 时,结果主要由 WE 决定
let anel = 20.0;
let plast = 0.0; // 消除 F 项
// W0(1,1) = 1.460, W0(2,1) = 1.269, 比例 = 1.460/1.269
let r1 = extprf(0.1, 1, 1, anel, 0.5, plast);
let r2 = extprf(0.1, 2, 1, anel, 0.5, plast);
// 比例应该接近 W0 比例
let ratio = r1 / r2;
let expected_ratio = 1.460 / 1.269;
assert_relative_eq!(ratio, expected_ratio, epsilon = 1e-6);
}
}