SpectraRust/src/tlusty/math/hydrogen/hephot.rs
2026-06-03 14:11:10 +08:00

265 lines
12 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.

//! He I 光电离截面。
//!
//! 重构自 TLUSTY `hephot.f`
//!
//! 使用 Seaton-Fernley 三次拟合计算 Opacity Project 截面。
//! 对 L > 2 使用类氢公式。
/// He I 光电离截面。
///
/// 使用 Seaton-Fernley 的三次拟合计算 Opacity Project 截面。
///
/// # 参数
///
/// * `s` - 多重度 (1 或 3)
/// * `l` - 角动量 (0, 1, 2>2 使用类氢公式)
/// * `n` - 主量子数
/// * `freq` - 频率
///
/// # 返回值
///
/// 光电离截面 (cm²)。
///
/// # 备注
///
/// 对于 L > 2 使用类氢公式。
/// 低于阈值频率返回 0。
pub fn hephot(s: i32, l: i32, n: i32, freq: f64) -> f64 {
const TENM18: f64 = 1e-18;
const FRH: f64 = 3.28805e15;
const TENLG: f64 = 2.302585093;
const PHOT0: f64 = 2.815e29;
// IST(3,2) — starting index into COEF/A/B/XFITM arrays
// indexed as IST(LL, SS) where LL=L+1, SS=(S+1)/2
const IST: [[usize; 2]; 3] = [[1, 11], [36, 45], [20, 28]];
// N0(3,2) — minimum principal quantum number for each (L, S) combination
const N0: [[i32; 2]; 3] = [[1, 2], [2, 2], [3, 3]];
// FL0(53) — threshold frequency in log10(freq/FRH)
const FL0: [f64; 53] = [
2.521e-01, -5.381e-01, -9.139e-01, -1.175e00, -1.375e00, -1.537e00,
-1.674e00, -1.792e00, -1.896e00, -1.989e00, -4.555e-01, -8.622e-01,
-1.137e00, -1.345e00, -1.512e00, -1.653e00, -1.774e00, -1.880e00,
-1.974e00, -9.538e-01, -1.204e00, -1.398e00, -1.556e00, -1.690e00,
-1.806e00, -1.909e00, -2.000e00, -9.537e-01, -1.204e00, -1.398e00,
-1.556e00, -1.690e00, -1.806e00, -1.909e00, -2.000e00, -6.065e-01,
-9.578e-01, -1.207e00, -1.400e00, -1.558e00, -1.692e00, -1.808e00,
-1.910e00, -2.002e00, -5.749e-01, -9.352e-01, -1.190e00, -1.386e00,
-1.547e00, -1.682e00, -1.799e00, -1.902e00, -1.995e00,
];
// XFITM(53) — maximum X for cubic fit; beyond this use linear fit A+B*X
const XFITM: [f64; 53] = [
3.262e-01, 6.135e-01, 9.233e-01, 8.438e-01, 1.020e00, 1.169e00,
1.298e00, 1.411e00, 1.512e00, 1.602e00, 7.228e-01, 1.076e00,
1.206e00, 1.404e00, 1.481e00, 1.464e00, 1.581e00, 1.685e00,
1.777e00, 9.586e-01, 1.187e00, 1.371e00, 1.524e00, 1.740e00,
1.854e00, 1.955e00, 2.046e00, 9.585e-01, 1.041e00, 1.371e00,
1.608e00, 1.739e00, 1.768e00, 1.869e00, 1.803e00, 7.360e-01,
1.041e00, 1.272e00, 1.457e00, 1.611e00, 1.741e00, 1.855e00,
1.870e00, 1.804e00, 9.302e-01, 1.144e00, 1.028e00, 1.210e00,
1.362e00, 1.646e00, 1.761e00, 1.863e00, 1.954e00,
];
// A(53) — linear fit coefficient (log sigma intercept)
const A: [f64; 53] = [
6.95319e-01, 1.13101e00, 1.36313e00, 1.51684e00, 1.64767e00,
1.75643e00, 1.84458e00, 1.87243e00, 1.85628e00, 1.90889e00,
9.01802e-01, 1.25389e00, 1.39033e00, 1.55226e00, 1.60658e00,
1.65930e00, 1.68855e00, 1.62477e00, 1.66726e00, 1.83599e00,
2.50403e00, 3.08564e00, 3.56545e00, 4.25922e00, 4.61346e00,
4.91417e00, 5.19211e00, 1.74181e00, 2.25756e00, 2.95625e00,
3.65899e00, 4.04397e00, 4.13410e00, 4.43538e00, 4.19583e00,
1.79027e00, 2.23543e00, 2.63942e00, 3.02461e00, 3.35018e00,
3.62067e00, 3.85218e00, 3.76689e00, 3.49318e00, 1.16294e00,
1.86467e00, 2.02110e00, 2.24231e00, 2.44240e00, 2.76594e00,
2.93230e00, 3.08109e00, 3.21069e00,
];
// B(53) — linear fit coefficient (log sigma slope)
const B: [f64; 53] = [
-1.29000e00, -2.15771e00, -2.13263e00, -2.10272e00, -2.10861e00,
-2.11507e00, -2.11710e00, -2.08531e00, -2.03296e00, -2.03441e00,
-1.85905e00, -2.04057e00, -2.02189e00, -2.05930e00, -2.03403e00,
-2.02071e00, -1.99956e00, -1.92851e00, -1.92905e00, -4.58608e00,
-4.40022e00, -4.39154e00, -4.39676e00, -4.57631e00, -4.57120e00,
-4.56188e00, -4.55915e00, -4.41218e00, -4.12940e00, -4.24401e00,
-4.40783e00, -4.39930e00, -4.25981e00, -4.26804e00, -4.00419e00,
-4.47251e00, -3.87960e00, -3.71668e00, -3.68461e00, -3.67173e00,
-3.65991e00, -3.64968e00, -3.48666e00, -3.23985e00, -2.95758e00,
-3.07110e00, -2.87157e00, -2.83137e00, -2.82132e00, -2.91084e00,
-2.91159e00, -2.91336e00, -2.91296e00,
];
// COEF(4, 53) — cubic fit coefficients: P = C0 + C1*X + C2*X^2 + C3*X^3
// Stored column-major: COEF = [c0_1, c1_1, c2_1, c3_1, c0_2, c1_2, ...]
// We store as flat array indexed by (4*(j-1) + i) for Fortran (i,j)
const COEF: [f64; 212] = [
// J=1..10 (from DATA ((COEF(I,J),I=1,4),J=1,10))
8.734e-01, -1.545e00, -1.093e00, 5.918e-01, // j=1: 1^1S
9.771e-01, -1.567e00, -4.739e-01, -1.302e-01, // j=2: 2^3S
1.174e00, -1.638e00, -2.831e-01, -3.281e-02, // j=3: 2^1S
1.324e00, -1.692e00, -2.916e-01, 9.027e-02, // j=4: 2^3P
1.445e00, -1.761e00, -1.902e-01, 4.401e-02, // j=5: 2^1P
1.546e00, -1.817e00, -1.278e-01, 2.293e-02, // j=6: 3^3S
1.635e00, -1.864e00, -8.252e-02, 9.854e-03, // j=7: 3^1S
1.712e00, -1.903e00, -5.206e-02, 2.892e-03, // j=8: 3^3P
1.782e00, -1.936e00, -2.952e-02, -1.405e-03, // j=9: 3^3D
1.845e00, -1.964e00, -1.152e-02, -4.487e-03, // j=10: 3^1D
// J=11..19 (from DATA ((COEF(I,J),I=1,4),J=11,19))
7.377e-01, -9.327e-01, -1.466e00, 6.891e-01, // j=11: 3^1P
9.031e-01, -1.157e00, -7.151e-01, 1.832e-01, // j=12: 4^3S
1.031e00, -1.313e00, -4.517e-01, 9.207e-02, // j=13: 4^1S
1.135e00, -1.441e00, -2.724e-01, 3.105e-02, // j=14: 4^3P
1.225e00, -1.536e00, -1.725e-01, 7.191e-03, // j=15
1.302e00, -1.602e00, -1.300e-01, 7.345e-03, // j=16
1.372e00, -1.664e00, -8.204e-02, -1.643e-03, // j=17
1.434e00, -1.715e00, -4.646e-02, -7.456e-03, // j=18
1.491e00, -1.760e00, -1.838e-02, -1.152e-02, // j=19
// J=20..27 (from DATA ((COEF(I,J),I=1,4),J=20,27))
1.258e00, -3.442e00, -4.731e-01, -9.522e-02, // j=20: 2^3Po (trip S, l=0, n=2 start)
1.553e00, -2.781e00, -6.841e-01, -4.083e-03, // j=21
1.727e00, -2.494e00, -5.785e-01, -6.015e-02, // j=22
1.853e00, -2.347e00, -4.611e-01, -9.615e-02, // j=23
1.955e00, -2.273e00, -3.457e-01, -1.245e-01, // j=24
2.041e00, -2.226e00, -2.669e-01, -1.344e-01, // j=25
2.115e00, -2.200e00, -1.999e-01, -1.410e-01, // j=26
2.182e00, -2.188e00, -1.405e-01, -1.460e-01, // j=27
// J=28..35 (from DATA ((COEF(I,J),I=1,4),J=28,35))
1.267e00, -3.417e00, -5.038e-01, -1.797e-02, // j=28: 2^1Po (sing S, l=0, n=2 start)
1.565e00, -2.781e00, -6.497e-01, -5.979e-03, // j=29
1.741e00, -2.479e00, -6.099e-01, -2.227e-02, // j=30
1.870e00, -2.336e00, -4.899e-01, -6.616e-02, // j=31
1.973e00, -2.253e00, -3.972e-01, -8.729e-02, // j=32
2.061e00, -2.212e00, -3.072e-01, -1.060e-01, // j=33
2.137e00, -2.189e00, -2.352e-01, -1.171e-01, // j=34
2.205e00, -2.186e00, -1.621e-01, -1.296e-01, // j=35
// J=36..44 (from DATA ((COEF(I,J),I=1,4),J=36,44))
1.129e00, -3.149e00, -1.910e-01, -5.244e-01, // j=36: 2^3Po l=1 (trip P start)
1.431e00, -2.511e00, -3.710e-01, -1.933e-01, // j=37
1.620e00, -2.303e00, -3.045e-01, -1.391e-01, // j=38
1.763e00, -2.235e00, -1.829e-01, -1.491e-01, // j=39
1.879e00, -2.215e00, -9.003e-02, -1.537e-01, // j=40
1.978e00, -2.213e00, -2.066e-02, -1.541e-01, // j=41
2.064e00, -2.220e00, 3.258e-02, -1.527e-01, // j=42
2.140e00, -2.225e00, 6.311e-02, -1.455e-01, // j=43
2.208e00, -2.229e00, 7.977e-02, -1.357e-01, // j=44
// J=45..53 (from DATA ((COEF(I,J),I=1,4),J=45,53))
1.204e00, -2.809e00, -3.094e-01, 1.100e-01, // j=45: 2^1Po l=1 (sing P start)
1.455e00, -2.254e00, -4.795e-01, 6.872e-02, // j=46
1.619e00, -2.109e00, -3.357e-01, -2.532e-02, // j=47
1.747e00, -2.065e00, -2.317e-01, -5.224e-02, // j=48
1.853e00, -2.058e00, -1.517e-01, -6.647e-02, // j=49
1.943e00, -2.055e00, -1.158e-01, -6.081e-02, // j=50
2.023e00, -2.070e00, -6.470e-02, -6.800e-02, // j=51
2.095e00, -2.088e00, -2.357e-02, -7.250e-02, // j=52
2.160e00, -2.107e00, 1.065e-02, -7.542e-02, // j=53
];
// L > 2: 使用类氢公式
if l > 2 {
let gn = 2.0 * (n * n) as f64;
return PHOT0 / freq / freq / freq / (n as f64).powi(5) * (2 * l + 1) as f64 * s as f64 / gn;
}
// SELECT beginning of coefficients
// Fortran: SS=(S+1)/2, LL=L+1, NSL0=N0(LL,SS), I=IST(LL,SS)+N-NSL0
let ss = ((s + 1) / 2 - 1) as usize; // 0-indexed: S=1→0, S=3→1
let ll = (l as usize); // 0-indexed: L=0→0, L=1→1, L=2→2
let nsl0 = N0[ll][ss];
let i = IST[ll][ss] + (n - nsl0) as usize - 1; // 0-indexed
if i >= 53 {
// Beyond tabulated data — fallback to hydrogenic
let gn = 2.0 * (n * n) as f64;
return PHOT0 / freq / freq / freq / (n as f64).powi(5) * (2 * l + 1) as f64 * s as f64 / gn;
}
// EVALUATE cross section
let fl = (freq / FRH).log10();
let x = fl - FL0[i];
if x < -0.001 {
return 0.0;
}
if x < XFITM[i] {
// Cubic fit: P = C0 + C1*X + C2*X^2 + C3*X^3
let c0 = COEF[4 * i];
let c1 = COEF[4 * i + 1];
let c2 = COEF[4 * i + 2];
let c3 = COEF[4 * i + 3];
let p = c0 + x * (c1 + x * (c2 + x * c3));
TENM18 * (TENLG * p).exp()
} else {
// Linear fit: A + B*X
TENM18 * (TENLG * (A[i] + B[i] * x)).exp()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_hephot_l_gt_2() {
// L > 2 使用类氢公式
let result = hephot(1, 3, 3, 1e15);
assert!(result.is_finite());
assert!(result > 0.0);
}
#[test]
fn test_hephot_low_freq() {
// 低频率返回 0 (below threshold)
let result = hephot(1, 0, 1, 1e10);
assert_eq!(result, 0.0);
}
#[test]
fn test_hephot_he1_ground_state() {
// He I 1^1S (s=1, l=0, n=1) at freq above threshold
// Threshold: FL0[0] = 0.2521 → freq = FRH * 10^0.2521 ≈ 5.93e15
let result = hephot(1, 0, 1, 6.0e15);
assert!(result > 0.0);
// Should be around ~1e-18 cm^2 (order of magnitude)
assert!(result < 1e-15 && result > 1e-22, "result = {result:e}");
}
#[test]
fn test_hephot_he1_2triplet_s() {
// He I 2^3S (s=3, l=0, n=2) above threshold
// Threshold: FL0[10] = -0.4555 → freq = FRH * 10^-0.4555 ≈ 1.16e15
let result = hephot(3, 0, 2, 2.0e15);
assert!(result > 0.0);
}
#[test]
fn test_hephot_he1_2triplet_p() {
// He I 2^3P (s=3, l=1, n=2) above threshold
// Index: IST[1][0] = 36, N0[1][0] = 2, n=2 → i = 36+0 = 35 (0-indexed)
let result = hephot(3, 1, 2, 2.0e15);
assert!(result > 0.0);
}
#[test]
fn test_hephot_he1_2singlet_p() {
// He I 2^1P (s=1, l=1, n=2) above threshold
// Index: IST[1][1] = 45, N0[1][1] = 2, n=2 → i = 45+0 = 44 (0-indexed)
let result = hephot(1, 1, 2, 2.0e15);
assert!(result > 0.0);
}
#[test]
fn test_hephot_hydrogenic_high_l() {
// L=3, n=4, S=3 — should use hydrogenic formula
let result = hephot(3, 3, 4, 1e15);
assert!(result > 0.0);
// Hydrogenic: PHOT0 / freq^3 / n^5 * (2*L+1) * S / (2*n^2)
let expected = 2.815e29 / 1e15 / 1e15 / 1e15 / 4.0_f64.powi(5) * 7.0 * 3.0 / (2.0 * 16.0);
assert!((result - expected).abs() / expected < 1e-10);
}
}