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