SpectraRust/src/math/butler.rs
2026-03-19 22:16:23 +08:00

272 lines
8.5 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.

//! 氢原子碰撞激发速率。
//!
//! 重构自 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);
}
}