SpectraRust/src/synspec/math/h2minus.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

221 lines
7.2 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.

//! H2⁻ 自由-自由吸收系数计算。
//!
//! 重构自 SYNSPEC `synspec54.f` 中的 `h2minus` 子程序。
//!
//! 数据来源: K L Bell 1980, J. Phys. B: At. Mol. Phys. 13 1859, Table 1
//! 单位: 10^26 cm^4/dyn^-1
use crate::tlusty::math::interpolation::locate;
use crate::synspec::math::{CL, BOLK};
// ============================================================================
// 静态数据表
// ============================================================================
/// theta = 5040/T(K) 网格点 (9 个)
const FFTHET: [f64; 9] = [0.5, 0.8, 1.0, 1.2, 1.6, 2.0, 2.8, 3.6, 10.0];
/// lambda (Angstroms) 网格点 (18 个)
const FFLAMB: [f64; 18] = [
151883.0, 113913.0, 91130.0, 60753.0,
45565.0, 36452.0, 30377.0, 22783.0,
18226.0, 15188.0, 11391.0, 9113.0, 7594.0,
6509.0, 5696.0, 5063.0, 4142.0, 3505.0,
];
/// kappa 表 (18 x 9),按列优先存储 (Fortran 布局)
const NTHET: usize = 9;
const NLAMB: usize = 18;
/// FFkapp 表,按 Fortran 列优先存储: FFkapp[theta_idx * NLAMB + lamb_idx]
/// 即 FFkapp(i,j) = FFKAPP[j * 18 + i],其中 i=lambda, j=theta (0-based)
const FFKAPP: [f64; NLAMB * NTHET] = [
// 列 1 (theta=0.5): 18 个 lambda 值
7.16e+01, 4.03e+01, 2.58e+01, 1.15e+01, 6.47e+00,
4.15e+00, 2.89e+00, 1.63e+00, 1.05e+00, 7.36e-01,
4.20e-01, 2.73e-01, 1.92e-01, 1.43e-01, 1.10e-01,
8.70e-02, 5.84e-02, 4.17e-02,
// 列 2 (theta=0.8)
9.23e+01, 5.20e+01, 3.33e+01, 1.48e+01, 8.37e+00,
5.38e+00, 3.76e+00, 2.14e+00, 1.39e+00, 9.75e-01,
5.64e-01, 3.71e-01, 2.64e-01, 1.98e-01, 1.54e-01,
1.24e-01, 8.43e-02, 6.10e-02,
// 列 3 (theta=1.0)
1.01e+02, 5.70e+01, 3.65e+01, 1.63e+01, 9.20e+00,
5.92e+00, 4.14e+00, 2.36e+00, 1.54e+00, 1.09e+00,
6.35e-01, 4.22e-01, 3.03e-01, 2.30e-01, 1.80e-01,
1.46e-01, 1.01e-01, 7.34e-02,
// 列 4 (theta=1.2)
1.08e+02, 6.08e+01, 3.90e+01, 1.74e+01, 9.84e+00,
6.35e+00, 4.44e+00, 2.55e+00, 1.66e+00, 1.18e+00,
6.97e-01, 4.67e-01, 3.39e-01, 2.59e-01, 2.06e-01,
1.67e-01, 1.17e-01, 8.59e-02,
// 列 5 (theta=1.6)
1.18e+02, 6.65e+01, 4.27e+01, 1.91e+01, 1.08e+01,
6.99e+00, 4.91e+00, 2.84e+00, 1.87e+00, 1.34e+00,
8.06e-01, 5.52e-01, 4.08e-01, 3.17e-01, 2.55e-01,
2.10e-01, 1.49e-01, 1.11e-01,
// 列 6 (theta=2.0)
1.26e+02, 7.08e+01, 4.54e+01, 2.04e+01, 1.16e+01,
7.50e+00, 5.28e+00, 3.07e+00, 2.04e+00, 1.48e+00,
9.09e-01, 6.33e-01, 4.76e-01, 3.75e-01, 3.05e-01,
2.53e-01, 1.82e-01, 1.37e-01,
// 列 7 (theta=2.8)
1.38e+02, 7.76e+01, 4.98e+01, 2.24e+01, 1.28e+01,
8.32e+00, 5.90e+00, 3.49e+00, 2.36e+00, 1.74e+00,
1.11e+00, 7.97e-01, 6.13e-01, 4.92e-01, 4.06e-01,
3.39e-01, 2.49e-01, 1.87e-01,
// 列 8 (theta=3.6)
1.47e+02, 8.30e+01, 5.33e+01, 2.40e+01, 1.38e+01,
9.02e+00, 6.44e+00, 3.90e+00, 2.68e+00, 2.01e+00,
1.32e+00, 9.63e-01, 7.51e-01, 6.09e-01, 5.07e-01,
4.27e-01, 3.16e-01, 2.40e-01,
// 列 9 (theta=10.0) — 线性外推
2.19e+02, 1.26e+02, 8.13e+01, 3.68e+01, 2.18e+01,
1.46e+01, 1.08e+01, 7.18e+00, 5.24e+00, 4.17e+00,
3.00e+00, 2.29e+00, 1.86e+00, 1.55e+00, 1.32e+00,
1.13e+00, 8.52e-01, 6.64e-01,
];
// ============================================================================
// h2minus - H2⁻ 自由-自由吸收
// ============================================================================
/// 计算 H2⁻ 自由-自由吸收系数。
///
/// # 参数
///
/// - `t` - 温度 (K)
/// - `anh2` - H2 分子数密度
/// - `ane` - 电子数密度
/// - `fr` - 频率 (Hz)
///
/// # 返回
///
/// H2⁻ 自由-自由吸收系数 `oph2m`
pub fn h2minus(t: f64, anh2: f64, ane: f64, fr: f64) -> f64 {
// theta = 5040 / T
let theta = 5040.0 / t;
// 在温度数组中定位 (0-indexed)
// locate 返回 j 使得 FFTHET[j] <= theta < FFTHET[j+1]
let j = locate(&FFTHET, theta);
// 波长 (Angstroms): lambda = c / fr * 1e8
let flamb = CL * 1.0e8 / fr;
// 在波长数组中定位 (0-indexed)
let i = locate(&FFLAMB, flamb);
// 双线性插值
// 注意: FFTHET 是递增的FFLAMB 是递减的
let fkappa = if j >= NTHET - 1 {
// theta >= FFTHET[NTHET-1],保持恒定 (高温端)
let i_clamped = i.min(NLAMB - 2);
let y1 = ffkapp_at(i_clamped, NTHET - 1);
let y2 = ffkapp_at(i_clamped + 1, NTHET - 1);
let tt = (flamb - FFLAMB[i_clamped]) / (FFLAMB[i_clamped + 1] - FFLAMB[i_clamped]);
(1.0 - tt) * y1 + tt * y2
} else if !(FFLAMB[NLAMB - 1]..=FFLAMB[0]).contains(&flamb) {
// 超出波长表范围 (FFLAMB 递减: [0] 最大, [NLAMB-1] 最小)
0.0
} else {
// 表内双线性插值
let y1 = ffkapp_at(i, j);
let y2 = ffkapp_at(i + 1, j);
let y3 = ffkapp_at(i + 1, j + 1);
let y4 = ffkapp_at(i, j + 1);
// tt: 波长方向插值 (FFLAMB 递减)
let tt = (flamb - FFLAMB[i]) / (FFLAMB[i + 1] - FFLAMB[i]);
// uu: 温度方向插值 (FFTHET 递增)
let uu = (theta - FFTHET[j]) / (FFTHET[j + 1] - FFTHET[j]);
(1.0 - tt) * (1.0 - uu) * y1
+ tt * (1.0 - uu) * y2
+ tt * uu * y3
+ (1.0 - tt) * uu * y4
};
// 电子压力
let pe = ane * BOLK * t;
// 最终吸收系数
anh2 * 1.0e-26 * pe * fkappa
}
/// 从 FFkapp 表中获取值 (处理边界)
/// 索引: FFkapp(i,j) = FFKAPP[j * NLAMB + i],其中 i=lambda, j=theta (0-based)
fn ffkapp_at(i: usize, j: usize) -> f64 {
let i_clamped = i.min(NLAMB - 1);
let j_clamped = j.min(NTHET - 1);
FFKAPP[j_clamped * NLAMB + i_clamped]
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_h2minus_basic() {
// 典型恒星大气参数
let t = 5000.0; // K
let anh2 = 1.0e15; // H2 数密度
let ane = 1.0e13; // 电子数密度
let fr = 1.0e14; // Hz (红外)
let oph2m = h2minus(t, anh2, ane, fr);
assert!(oph2m > 0.0, "oph2m 应为正值: {}", oph2m);
}
#[test]
fn test_h2minus_high_temperature() {
// 高温情况
let t = 10000.0;
let anh2 = 1.0e14;
let ane = 1.0e12;
let fr = 3.0e14;
let oph2m = h2minus(t, anh2, ane, fr);
assert!(oph2m >= 0.0, "oph2m 应非负: {}", oph2m);
}
#[test]
fn test_h2minus_low_temperature() {
// 低温情况 (theta 大)
let t = 3000.0;
let anh2 = 1.0e16;
let ane = 1.0e14;
let fr = 5.0e14;
let oph2m = h2minus(t, anh2, ane, fr);
assert!(oph2m >= 0.0, "oph2m 应非负: {}", oph2m);
}
#[test]
fn test_h2minus_scaling() {
// 吸收系数应与 anh2 和 ane 成正比
let t = 6000.0;
let fr = 2.0e14;
let oph2m1 = h2minus(t, 1.0e14, 1.0e12, fr);
let oph2m2 = h2minus(t, 2.0e14, 1.0e12, fr);
let oph2m3 = h2minus(t, 1.0e14, 2.0e12, fr);
// 双倍 anh2 → 双倍 opacity
assert!(
(oph2m2 / oph2m1 - 2.0).abs() < 0.01,
"anh2 线性性: {} vs {}",
oph2m2,
oph2m1
);
// 双倍 ane → 双倍 opacity (pe 线性)
assert!(
(oph2m3 / oph2m1 - 2.0).abs() < 0.01,
"ane 线性性: {} vs {}",
oph2m3,
oph2m1
);
}
}