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>
221 lines
7.2 KiB
Rust
221 lines
7.2 KiB
Rust
//! 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
|
||
);
|
||
}
|
||
}
|