//! 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); } }