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>
152 lines
4.9 KiB
Rust
152 lines
4.9 KiB
Rust
//! 谱线轮廓波长外推函数。
|
||
//!
|
||
//! 重构自 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);
|
||
}
|
||
}
|