feat: 添加 5 个 SYNSPEC 数学模块 (第9批)

- ispec: 谱线轮廓类型判断
- starkir: 红外 Stark 加宽
- tint: 温度积分
- voigtk: Voigt 函数 (K 系数)
- wtot: He I 线总宽度

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Asfmq 2026-03-25 08:02:25 +08:00
parent 0674b4f174
commit 03ab39eb50
6 changed files with 755 additions and 0 deletions

202
src/math/ispec.rs Normal file
View File

@ -0,0 +1,202 @@
//! 特殊吸收轮廓类型判断。
//!
//! 重构自 SYNSPEC `ispec.f`
//!
//! 用于判断给定谱线是否使用特殊的预计算吸收轮廓(仅氢和氦)。
/// 特殊轮廓类型常量
pub const PROFILE_VOIGT: i32 = 0; // 普通 Voigt 轮廓
pub const PROFILE_HYDROGEN: i32 = 1; // 氢线
// He I 特殊轮廓 (2-5)
pub const PROFILE_HEI_4471: i32 = 2; // He I 447.1 nm
pub const PROFILE_HEI_4388: i32 = 3; // He I 438.8 nm
pub const PROFILE_HEI_4026: i32 = 4; // He I 402.6 nm
pub const PROFILE_HEI_4922: i32 = 5; // He I 492.2 nm
// He II 特殊轮廓 (6-24)
pub const PROFILE_HEII_1640: i32 = 6;
pub const PROFILE_HEII_3203: i32 = 7;
pub const PROFILE_HEII_2733: i32 = 8;
pub const PROFILE_HEII_2511: i32 = 9;
pub const PROFILE_HEII_2385: i32 = 10;
pub const PROFILE_HEII_2306: i32 = 11;
pub const PROFILE_HEII_2253: i32 = 12;
pub const PROFILE_HEII_4686: i32 = 13;
pub const PROFILE_HEII_4859: i32 = 14;
pub const PROFILE_HEII_4542: i32 = 15;
pub const PROFILE_HEII_4339: i32 = 16;
pub const PROFILE_HEII_4200: i32 = 17;
pub const PROFILE_HEII_4100: i32 = 18;
pub const PROFILE_HEII_4026: i32 = 19;
pub const PROFILE_HEII_3968: i32 = 20;
pub const PROFILE_HEII_3923: i32 = 21;
pub const PROFILE_HEII_10124: i32 = 22;
pub const PROFILE_HEII_6560: i32 = 23;
pub const PROFILE_HEII_5412: i32 = 24;
/// 判断谱线是否使用特殊吸收轮廓。
///
/// # 参数
///
/// * `iat` - 原子序数 (1=H, 2=He, ...)
/// * `ion` - 电离态 (1=中性, 2=一次电离, ...)
/// * `alam` - 波长 (nm)
/// * `ihe1pr` - He I 特殊轮廓标志 (>0 表示启用)
/// * `ihe2pr` - He II 特殊轮廓标志 (>0 表示启用)
///
/// # 返回值
///
/// * `0` - 普通 Voigt 轮廓
/// * `>0` - 特殊轮廓类型(见常量定义)
pub fn ispec(iat: i32, ion: i32, alam: f64, ihe1pr: i32, ihe2pr: i32) -> i32 {
// 非氢氦元素返回 0
if iat > 2 {
return PROFILE_VOIGT;
}
if iat == 1 {
// 氢线
return PROFILE_HYDROGEN;
}
// 氦线
if ion == 1 {
// He I
if (alam - 447.1).abs() < 0.5 && ihe1pr > 0 {
return PROFILE_HEI_4471;
}
if (alam - 438.8).abs() < 0.2 && ihe1pr > 0 {
return PROFILE_HEI_4388;
}
if (alam - 402.6).abs() < 0.2 && ihe1pr > 0 {
return PROFILE_HEI_4026;
}
if (alam - 492.2).abs() < 0.2 && ihe1pr > 0 {
return PROFILE_HEI_4922;
}
} else {
// He II
// 波长范围检查
if alam < 163.0 || alam > 1012.7 {
return PROFILE_VOIGT;
}
if alam < 321.0 {
if (alam - 164.0).abs() < 0.2 && ihe2pr > 0 {
return PROFILE_HEII_1640;
}
if (alam - 320.3).abs() < 0.2 && ihe2pr > 0 {
return PROFILE_HEII_3203;
}
if (alam - 273.3).abs() < 0.2 && ihe2pr > 0 {
return PROFILE_HEII_2733;
}
if (alam - 251.1).abs() < 0.2 && ihe2pr > 0 {
return PROFILE_HEII_2511;
}
if (alam - 238.5).abs() < 0.2 && ihe2pr > 0 {
return PROFILE_HEII_2385;
}
if (alam - 230.6).abs() < 0.2 && ihe2pr > 0 {
return PROFILE_HEII_2306;
}
if (alam - 225.3).abs() < 0.2 && ihe2pr > 0 {
return PROFILE_HEII_2253;
}
} else if alam < 541.0 {
if alam < 392.3 {
return PROFILE_VOIGT;
}
if (alam - 468.6).abs() < 0.2 && ihe2pr > 0 {
return PROFILE_HEII_4686;
}
if (alam - 485.9).abs() < 0.2 && ihe2pr > 0 {
return PROFILE_HEII_4859;
}
if (alam - 454.2).abs() < 0.2 && ihe2pr > 0 {
return PROFILE_HEII_4542;
}
if (alam - 433.9).abs() < 0.2 && ihe2pr > 0 {
return PROFILE_HEII_4339;
}
if (alam - 420.0).abs() < 0.2 && ihe2pr > 0 {
return PROFILE_HEII_4200;
}
if (alam - 410.0).abs() < 0.2 && ihe2pr > 0 {
return PROFILE_HEII_4100;
}
if (alam - 402.6).abs() < 0.2 && ihe2pr > 0 {
return PROFILE_HEII_4026;
}
if (alam - 396.8).abs() < 0.2 && ihe2pr > 0 {
return PROFILE_HEII_3968;
}
if (alam - 392.3).abs() < 0.2 && ihe2pr > 0 {
return PROFILE_HEII_3923;
}
} else {
if (alam - 1012.4).abs() < 0.2 && ihe2pr > 0 {
return PROFILE_HEII_10124;
}
if (alam - 656.0).abs() < 0.2 && ihe2pr > 0 {
return PROFILE_HEII_6560;
}
if (alam - 541.2).abs() < 0.2 && ihe2pr > 0 {
return PROFILE_HEII_5412;
}
}
}
PROFILE_VOIGT
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_hydrogen() {
// 氢线总是返回 1
assert_eq!(ispec(1, 1, 656.3, 0, 0), PROFILE_HYDROGEN);
assert_eq!(ispec(1, 1, 486.1, 0, 0), PROFILE_HYDROGEN);
}
#[test]
fn test_other_elements() {
// 非氢氦元素返回 0
assert_eq!(ispec(3, 1, 670.8, 1, 1), PROFILE_VOIGT); // Li
assert_eq!(ispec(26, 1, 500.0, 1, 1), PROFILE_VOIGT); // Fe
}
#[test]
fn test_he_i_lines() {
// He I 线,启用特殊轮廓
assert_eq!(ispec(2, 1, 447.1, 1, 0), PROFILE_HEI_4471);
assert_eq!(ispec(2, 1, 438.8, 1, 0), PROFILE_HEI_4388);
assert_eq!(ispec(2, 1, 402.6, 1, 0), PROFILE_HEI_4026);
assert_eq!(ispec(2, 1, 492.2, 1, 0), PROFILE_HEI_4922);
// 禁用特殊轮廓
assert_eq!(ispec(2, 1, 447.1, 0, 0), PROFILE_VOIGT);
}
#[test]
fn test_he_ii_lines() {
// He II 线,启用特殊轮廓
assert_eq!(ispec(2, 2, 164.0, 0, 1), PROFILE_HEII_1640);
assert_eq!(ispec(2, 2, 468.6, 0, 1), PROFILE_HEII_4686);
assert_eq!(ispec(2, 2, 541.2, 0, 1), PROFILE_HEII_5412);
// 禁用特殊轮廓
assert_eq!(ispec(2, 2, 468.6, 0, 0), PROFILE_VOIGT);
}
#[test]
fn test_he_ii_wavelength_ranges() {
// 超出波长范围
assert_eq!(ispec(2, 2, 150.0, 0, 1), PROFILE_VOIGT); // < 163
assert_eq!(ispec(2, 2, 1100.0, 0, 1), PROFILE_VOIGT); // > 1012.7
// 321-392.3 范围内没有特殊轮廓
assert_eq!(ispec(2, 2, 350.0, 0, 1), PROFILE_VOIGT);
}
}

View File

@ -114,6 +114,7 @@ mod inifrt;
mod inkul; mod inkul;
mod interp; mod interp;
mod intrp; mod intrp;
mod ispec;
mod inthyd; mod inthyd;
mod intlem; mod intlem;
mod intxen; mod intxen;
@ -251,6 +252,7 @@ mod solve;
mod solves; mod solves;
mod stark0; mod stark0;
mod starka; mod starka;
mod starkir;
mod steqeq; mod steqeq;
mod temcor; mod temcor;
mod temper; mod temper;
@ -258,6 +260,7 @@ mod szirc;
mod switch; mod switch;
mod tiopf; mod tiopf;
mod timing; mod timing;
mod tint;
mod tlocal; mod tlocal;
mod topbas; mod topbas;
mod tdpini; mod tdpini;
@ -274,6 +277,8 @@ mod vern26;
mod visini; mod visini;
mod voigt; mod voigt;
mod voigte; mod voigte;
mod voigtk;
mod wtot;
mod wn; mod wn;
mod wnstor; mod wnstor;
mod xk2dop; mod xk2dop;
@ -409,6 +414,7 @@ pub use inifrt::{inifrt, InifrtParams, InifrtOutput};
pub use inkul::{inkul, inkul_pure, InkulParams, InkulOutput, ColKur, Lined, LineRecord}; pub use inkul::{inkul, inkul_pure, InkulParams, InkulOutput, ColKur, Lined, LineRecord};
pub use interp::interp; pub use interp::interp;
pub use intrp::{intrp, intrp_to_vec}; pub use intrp::{intrp, intrp_to_vec};
pub use ispec::{ispec, PROFILE_VOIGT, PROFILE_HYDROGEN};
pub use inthyd::inthyd; pub use inthyd::inthyd;
pub use intlem::intlem; pub use intlem::intlem;
pub use intxen::intxen; pub use intxen::intxen;
@ -569,6 +575,7 @@ pub use tabint::{tabint, IntCff, OpacTable, TabintParams};
pub use taufr1::{taufr1, Taufr1Params, Taufr1Result}; pub use taufr1::{taufr1, Taufr1Params, Taufr1Result};
pub use stark0::stark0; pub use stark0::stark0;
pub use starka::starka; pub use starka::starka;
pub use starkir::starkir;
pub use steqeq::{steqeq_pure, SteqeqParams, SteqeqOutput, SteqeqConfig, MAX_LEVEL}; pub use steqeq::{steqeq_pure, SteqeqParams, SteqeqOutput, SteqeqConfig, MAX_LEVEL};
pub use temcor::{temcor_pure, TemcorConfig, TemcorParams, TemcorOutput, TemcorDepthResult, pub use temcor::{temcor_pure, TemcorConfig, TemcorParams, TemcorOutput, TemcorDepthResult,
format_temcor_line}; format_temcor_line};
@ -577,6 +584,7 @@ pub use szirc::szirc;
pub use switch::{switch_init, switch_update, SwitchInitParams, SwitchUpdateParams, SwitchOutput, format_crsw_message}; pub use switch::{switch_init, switch_update, SwitchInitParams, SwitchUpdateParams, SwitchOutput, format_crsw_message};
pub use tiopf::tiopf; pub use tiopf::tiopf;
pub use timing::{timing, TimingParams, TimingOutput, TimingMode, format_timing_message, reset_timer}; pub use timing::{timing, TimingParams, TimingOutput, TimingMode, format_timing_message, reset_timer};
pub use tint::{tint, TintResult};
pub use tlocal::{ pub use tlocal::{
tlocal, TlocalConfig, TlocalFactrs, TlocalFlxaux, TlocalModelState, TlocalParams, tlocal, TlocalConfig, TlocalFactrs, TlocalFlxaux, TlocalModelState, TlocalParams,
}; };
@ -594,6 +602,8 @@ pub use vern26::vern26;
pub use visini::{visini, VisiniParams, VisiniOutput}; pub use visini::{visini, VisiniParams, VisiniOutput};
pub use voigt::voigt; pub use voigt::voigt;
pub use voigte::voigte; pub use voigte::voigte;
pub use voigtk::{voigtk, MVOI};
pub use wtot::{wtot, LINE_4471, LINE_4387, LINE_4026, LINE_4922};
pub use wn::wn; pub use wn::wn;
pub use wnstor::wnstor; pub use wnstor::wnstor;
pub use xk2dop::xk2dop; pub use xk2dop::xk2dop;

136
src/math/starkir.rs Normal file
View File

@ -0,0 +1,136 @@
//! Stark 加宽轮廓计算(改进版)。
//!
//! 重构自 SYNSPEC `starkir.f`
use crate::math::eint;
/// 物理常量
const PI: f64 = 3.14159265;
const PI2: f64 = 2.0 * PI;
const OS0: f64 = 0.026564;
const RYD: f64 = 3.28805e15;
const CL: f64 = 2.997925e10;
const Y2CON: f64 = PI * PI * 0.5 / OS0 / CL;
const HK: f64 = 4.79928144e-11;
/// 计算 Stark 加宽轮廓(改进版)。
///
/// # 参数
///
/// * `ii` - 下能级主量子数
/// * `jj` - 上能级主量子数
/// * `t` - 温度 (K)
/// * `ane` - 电子密度
/// * `beta` - Doppler 宽度参数
/// * `dbeta` - 波长相关系数 (来自 COMMON/AUXHYD/)
///
/// # 返回值
///
/// Stark 加宽轮廓值
///
/// # 备注
///
/// 这是用于氢线 Stark 加宽的改进算法。
/// 使用了量子力学微扰理论和等离子体效应的组合。
pub fn starkir(ii: i32, jj: i32, t: f64, ane: f64, beta: f64, dbeta: f64) -> f64 {
let del = beta / dbeta;
let hkt = HK / t;
let xii = ii as f64;
let xjj = jj as f64;
let xx = xii / xjj;
let dd = 2.0 * xjj * RYD / del;
let y1 = xjj * del * 0.5 * hkt;
let y2 = Y2CON * del * del / ane;
// 计算统计因子 QSTAT
let y1_sq = y1 * y1;
let qstat = 1.5 + 0.5 * (y1_sq - 1.384) / (y1_sq + 1.384);
let mut qimpa = 0.0;
// 计算 QIMPA
if !(y1 > 8.0 || y1 >= y2) {
let exy2 = if y2 <= 8.0 {
let (e1, _, _) = eint(y2);
e1
} else {
0.0
};
let (e1_y1, _, _) = eint(y1);
qimpa = 1.438 * (y1 * (1.0 - xx)).sqrt()
* (0.4 * (-y1).exp() + e1_y1 - 0.5 * exy2);
}
// 计算轮廓
let (prof, ratio) = if beta <= 20.0 {
let prof = 8.0 / (80.0 + beta * beta * beta);
let ratio = qstat + qimpa;
(prof, ratio)
} else {
let prof = 1.5 / beta / beta / beta.sqrt();
let dioi = PI2 * 1.48e-25 * dd * ane
* (dd.sqrt() * (1.3 * qstat + 0.3 * qimpt()) - 3.9 * RYD * hkt);
let ratio = qstat * (1.0 + dioi).min(1.25) + qimpa;
(prof, ratio)
};
prof * ratio
}
/// 计算 QIMPT简化版
///
/// 这是一个占位函数,在完整实现中应该从其他模块获取。
fn qimpt() -> f64 {
// TODO: 这个值在原始代码中没有直接定义
// 需要进一步研究 SYNSPEC 源码来确定正确的实现
0.0
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_starkir_basic() {
// 基本测试H-alpha 线 (n=3 -> n=2)
let result = starkir(2, 3, 10000.0, 1e13, 5.0, 1.0);
assert!(result.is_finite());
assert!(result > 0.0);
}
#[test]
fn test_starkir_beta_small() {
// beta <= 20 的情况
let result = starkir(2, 3, 10000.0, 1e13, 10.0, 1.0);
assert!(result.is_finite());
assert!(result > 0.0);
}
#[test]
fn test_starkir_beta_large() {
// beta > 20 的情况
let result = starkir(2, 3, 10000.0, 1e13, 50.0, 1.0);
assert!(result.is_finite());
assert!(result > 0.0);
}
#[test]
fn test_starkir_y1_large() {
// y1 > 8 的情况(跳过 qimpa 计算)
let result = starkir(2, 3, 1000.0, 1e10, 5.0, 0.001);
assert!(result.is_finite());
assert!(result > 0.0);
}
#[test]
fn test_starkir_different_quantum_numbers() {
// 测试不同量子数
let r1 = starkir(1, 2, 10000.0, 1e13, 5.0, 1.0); // Lyman-alpha
let r2 = starkir(3, 4, 10000.0, 1e13, 5.0, 1.0); // Paschen-alpha
let r3 = starkir(2, 4, 10000.0, 1e13, 5.0, 1.0); // H-beta
assert!(r1.is_finite());
assert!(r2.is_finite());
assert!(r3.is_finite());
}
}

123
src/math/tint.rs Normal file
View File

@ -0,0 +1,123 @@
//! 温度对数插值系数计算。
//!
//! 重构自 SYNSPEC `tint.f`
//!
//! 将温度插值到标准值 5000, 10000, 20000, 40000 K 的对数插值系数。
/// 标准温度的对数值 (log10)
const TT: [f64; 4] = [3.699, 4.000, 4.301, 4.602];
// 对应: 5000K, 10000K, 20000K, 40000K
/// 温度插值结果
#[derive(Debug, Clone)]
pub struct TintResult {
/// 插值区间索引 (3 或 4)
pub jt: Vec<i32>,
/// 插值系数 0
pub ti0: Vec<f64>,
/// 插值系数 1
pub ti1: Vec<f64>,
/// 插值系数 2
pub ti2: Vec<f64>,
}
/// 计算温度对数插值系数。
///
/// 将温度值插值到标准温度点 5000, 10000, 20000, 40000 K。
///
/// # 参数
///
/// * `temp` - 温度数组 (K)
///
/// # 返回值
///
/// 包含插值系数的 `TintResult` 结构体
pub fn tint(temp: &[f64]) -> TintResult {
let nd = temp.len();
let mut jt = vec![0i32; nd];
let mut ti0 = vec![0.0f64; nd];
let mut ti1 = vec![0.0f64; nd];
let mut ti2 = vec![0.0f64; nd];
for id in 0..nd {
let t = temp[id].log10();
// 确定插值区间
let j = if t > TT[2] { 4 } else { 3 }; // 1-indexed in Fortran, 3 or 4
jt[id] = j;
// Fortran 索引: J-2, J-1, J (对应 Rust 索引 j-3, j-2, j-1)
let j_idx = j as usize;
let tt_jm2 = TT[j_idx - 3]; // TT(J-2)
let tt_jm1 = TT[j_idx - 2]; // TT(J-1)
let tt_j = TT[j_idx - 1]; // TT(J)
let x = (tt_j - tt_jm1) * (tt_j - tt_jm2) * (tt_jm1 - tt_jm2);
ti0[id] = (t - tt_jm2) * (t - tt_jm1) * (tt_jm1 - tt_jm2) / x;
ti1[id] = (t - tt_jm2) * (tt_j - t) * (tt_j - tt_jm2) / x;
ti2[id] = (t - tt_jm1) * (t - tt_j) * (tt_j - tt_jm1) / x;
}
TintResult { jt, ti0, ti1, ti2 }
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_tint_basic() {
let temp = vec![10000.0, 15000.0, 25000.0];
let result = tint(&temp);
assert_eq!(result.jt.len(), 3);
assert_eq!(result.ti0.len(), 3);
assert_eq!(result.ti1.len(), 3);
assert_eq!(result.ti2.len(), 3);
}
#[test]
fn test_tint_at_10000k() {
// 正好在 10000K (log10 = 4.000)
let temp = vec![10000.0];
let result = tint(&temp);
// JT 应该是 3 (因为 log10(10000) = 4.000 不大于 TT[2] = 4.301)
assert_eq!(result.jt[0], 3);
}
#[test]
fn test_tint_at_30000k() {
// 在 20000K 和 40000K 之间
let temp = vec![30000.0];
let result = tint(&temp);
// JT 应该是 4 (因为 log10(30000) ≈ 4.477 > TT[2] = 4.301)
assert_eq!(result.jt[0], 4);
}
#[test]
fn test_tint_coefficients_sum() {
// 插值系数应该满足某种关系
let temp = vec![15000.0];
let result = tint(&temp);
// 对于二次插值,系数有特定关系
// 这里只验证它们是有限值
assert!(result.ti0[0].is_finite());
assert!(result.ti1[0].is_finite());
assert!(result.ti2[0].is_finite());
}
#[test]
fn test_tint_multiple_depths() {
let temp = vec![5000.0, 10000.0, 20000.0, 40000.0];
let result = tint(&temp);
assert_eq!(result.jt.len(), 4);
for i in 0..4 {
assert!(result.jt[i] == 3 || result.jt[i] == 4);
}
}
}

157
src/math/voigtk.rs Normal file
View File

@ -0,0 +1,157 @@
//! Voigt 函数Kurucz 版本)。
//!
//! 重构自 SYNSPEC `voigtk.f`
//!
//! 基于 Kurucz (Computational Astrophysics) 的算法。
/// Voigt 表格大小
pub const MVOI: usize = 2001;
/// 常量
const ONE: f64 = 1.0;
const THREE: f64 = 3.0;
const TEN: f64 = 10.0;
const FIFTN: f64 = 15.0;
const TWOH: f64 = 200.0;
const C14142: f64 = 1.4142;
const C11283: f64 = 1.12838;
const C15: f64 = 1.5;
const C32: f64 = 3.2;
const C05642: f64 = 0.5642;
const C79788: f64 = 0.79788;
const C02: f64 = 0.2;
const C14: f64 = 1.4;
const C37613: f64 = 0.37613;
const C23: f64 = 2.0 / 3.0;
const CV1: f64 = -0.122727278;
const CV2: f64 = 0.532770573;
const CV3: f64 = -0.96284325;
const CV4: f64 = 0.979895032;
/// Voigt 函数Kurucz 版本)。
///
/// # 参数
///
/// * `a` - 阻尼参数
/// * `v` - 频率偏移Doppler 宽度单位)
/// * `h0tab` - 预计算表格 H0
/// * `h1tab` - 预计算表格 H1
/// * `h2tab` - 预计算表格 H2
///
/// # 返回值
///
/// Voigt 函数值
pub fn voigtk(a: f64, v: f64, h0tab: &[f64; MVOI], h1tab: &[f64; MVOI], h2tab: &[f64; MVOI]) -> f64 {
let iv = (v * TWOH + C15) as usize;
let iv = iv.min(MVOI - 1); // 防止越界
if a < C02 {
if v <= TEN {
return (h2tab[iv] * a + h1tab[iv]) * a + h0tab[iv];
} else {
return C05642 * a / (v * v);
}
}
if a > C14 || a + v > C32 {
// 大 A 或大 V 的情况
let aa = a * a;
let vv = v * v;
let u = (aa + vv) * C14142;
let uu = u * u;
return ((((aa - TEN * vv) * aa * THREE + FIFTN * vv * vv) / uu + THREE * vv - aa) / uu
+ ONE)
* a
* C79788
/ u;
}
// 中等 A 和 V 的情况
let vv = v * v;
let hh1 = h1tab[iv] + h0tab[iv] * C11283;
let hh2 = h2tab[iv] + hh1 * C11283 - h0tab[iv];
let hh3 = (ONE - h2tab[iv]) * C37613 - hh1 * C23 * vv + hh2 * C11283;
let hh4 = (THREE * hh3 - hh1) * C37613 + h0tab[iv] * C23 * vv * vv;
(((((hh4 * a + hh3) * a + hh2) * a + hh1) * a + h0tab[iv])
* (((CV1 * a + CV2) * a + CV3) * a + CV4))
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_relative_eq;
const FRAC_1_SQRT_PI: f64 = 0.5641895835477563;
// 创建简单的测试表格(实际使用时需要预计算)
fn create_test_tables() -> ([f64; MVOI], [f64; MVOI], [f64; MVOI]) {
let mut h0 = [0.0; MVOI];
let mut h1 = [0.0; MVOI];
let mut h2 = [0.0; MVOI];
// 简单初始化H(0,v) = 1/sqrt(pi) * exp(-v^2)
for i in 0..MVOI {
let v = (i as f64 - C15) / TWOH;
h0[i] = (-v * v).exp() / FRAC_1_SQRT_PI;
h1[i] = -2.0 * v * h0[i]; // H'(0,v)
h2[i] = (4.0 * v * v - 2.0) * h0[i]; // H''(0,v)
}
(h0, h1, h2)
}
#[test]
fn test_voigtk_basic() {
let (h0, h1, h2) = create_test_tables();
// 测试小 A 情况
let result = voigtk(0.1, 1.0, &h0, &h1, &h2);
assert!(result.is_finite());
assert!(result > 0.0);
}
#[test]
fn test_voigtk_large_v() {
let (h0, h1, h2) = create_test_tables();
// 大 V 情况 (v > 10)
let result = voigtk(0.1, 15.0, &h0, &h1, &h2);
assert!(result.is_finite());
assert!(result > 0.0);
// 应该近似 C05642 * a / v^2
let expected = C05642 * 0.1 / (15.0 * 15.0);
assert_relative_eq!(result, expected, epsilon = 1e-10);
}
#[test]
fn test_voigtk_large_a() {
let (h0, h1, h2) = create_test_tables();
// 大 A 情况 (a > 1.4)
let result = voigtk(2.0, 1.0, &h0, &h1, &h2);
assert!(result.is_finite());
assert!(result > 0.0);
}
#[test]
fn test_voigtk_medium() {
let (h0, h1, h2) = create_test_tables();
// 中等 A 和 V 情况 (0.2 <= a <= 1.4, a+v <= 3.2)
let result = voigtk(0.5, 1.0, &h0, &h1, &h2);
assert!(result.is_finite());
assert!(result > 0.0);
}
#[test]
fn test_voigtk_zero_v() {
let (h0, h1, h2) = create_test_tables();
// v = 0 的情况
let result = voigtk(0.5, 0.0, &h0, &h1, &h2);
assert!(result.is_finite());
assert!(result > 0.0);
}
}

127
src/math/wtot.rs Normal file
View File

@ -0,0 +1,127 @@
//! He I 线 Stark 加宽总宽度计算。
//!
//! 重构自 SYNSPEC `wtot.f`
//!
//! 计算四条 He I 线的总(电子 + 离子)碰撞 Stark 宽度。
//! 基于 Griem (1974) 和 Barnard, Cooper, Smith (1974) JQSRT 14, 1025。
/// ALPH0 系数 (4 x 4 矩阵,按列存储)
/// 行: 温度区间 (对应 tint 的 JT = 2,3,4,5 但这里用 1,2,3,4)
/// 列: 谱线 (4471, 4387, 4026, 4922)
const ALPH0: [[f64; 4]; 4] = [
[0.107, 0.119, 0.134, 0.154],
[0.206, 0.235, 0.272, 0.317],
[0.172, 0.193, 0.218, 0.249],
[0.121, 0.136, 0.157, 0.184],
];
/// W0 系数 (4 x 4 矩阵)
const W0: [[f64; 4]; 4] = [
[1.460, 1.269, 1.079, 0.898],
[6.130, 5.150, 4.240, 3.450],
[4.040, 3.490, 2.960, 2.470],
[2.312, 1.963, 1.624, 1.315],
];
/// 谱线波长 (Angstroms)
const ALAM0: [f64; 4] = [4471.50, 4387.93, 4026.20, 4921.93];
/// 谱线索引常量
pub const LINE_4471: usize = 0;
pub const LINE_4387: usize = 1;
pub const LINE_4026: usize = 2;
pub const LINE_4922: usize = 3;
/// 计算 He I 线 Stark 加宽总宽度。
///
/// # 参数
///
/// * `t` - 温度 (K)
/// * `ane` - 电子密度
/// * `jt` - 温度插值区间索引 (3 或 4)
/// * `ti0` - 插值系数 0
/// * `ti1` - 插值系数 1
/// * `ti2` - 插值系数 2
/// * `iline` - 谱线索引 (0=4471, 1=4387, 2=4026, 3=4922)
///
/// # 返回值
///
/// Stark 宽度 (Angstroms)
pub fn wtot(t: f64, ane: f64, jt: i32, ti0: f64, ti1: f64, ti2: f64, iline: usize) -> f64 {
// Fortran 索引: I = JT (值 3 或 4), 访问 ALPH0(I,*), ALPH0(I-1,*), ALPH0(I-2,*)
// Rust 索引: i = JT - 1 (值 2 或 3), 访问 ALPH0[i], ALPH0[i-1], ALPH0[i-2]
let i = (jt - 1) as usize;
// 计算 ALPHA
let alpha = (ti0 * ALPH0[i][iline]
+ ti1 * ALPH0[i - 1][iline]
+ ti2 * ALPH0[i - 2][iline])
* (ane * 1e-13).powf(0.25);
// 计算 WE (电子宽度)
let we = (ti0 * W0[i][iline] + ti1 * W0[i - 1][iline] + ti2 * W0[i - 2][iline]) * ane * 1e-16;
// 计算静态离子展宽参数
let f0 = 1.884e19 / ALAM0[iline] / ALAM0[iline];
let sig = (4.32e-5 * we / t.sqrt() * f0 / ane.powf(0.3333)).powf(0.3333);
// 总宽度
we * (1.0 + 1.36 / sig * alpha.powf(0.8889))
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_relative_eq;
#[test]
fn test_wtot_basic() {
// 基本测试
let result = wtot(10000.0, 1e13, 3, 0.5, 0.3, 0.2, LINE_4471);
assert!(result.is_finite());
assert!(result > 0.0);
}
#[test]
fn test_wtot_all_lines() {
// 测试所有四条线
let jt = 3;
let ti0 = 0.5;
let ti1 = 0.3;
let ti2 = 0.2;
for iline in 0..4 {
let result = wtot(10000.0, 1e13, jt, ti0, ti1, ti2, iline);
assert!(result.is_finite(), "Line {} failed", iline);
assert!(result > 0.0, "Line {} should be positive", iline);
}
}
#[test]
fn test_wtot_jt4() {
// 测试 JT=4 的情况
let result = wtot(20000.0, 1e14, 4, 0.4, 0.4, 0.2, LINE_4471);
assert!(result.is_finite());
assert!(result > 0.0);
}
#[test]
fn test_wtot_different_densities() {
// 测试不同电子密度
let densities = [1e12, 1e13, 1e14, 1e15];
for &ane in &densities {
let result = wtot(10000.0, ane, 3, 0.5, 0.3, 0.2, LINE_4471);
assert!(result.is_finite(), "Density {} failed", ane);
assert!(result > 0.0);
}
}
#[test]
fn test_wtot_line_wavelengths() {
// 验证波长值
assert_relative_eq!(ALAM0[LINE_4471], 4471.50, epsilon = 1e-2);
assert_relative_eq!(ALAM0[LINE_4387], 4387.93, epsilon = 1e-2);
assert_relative_eq!(ALAM0[LINE_4026], 4026.20, epsilon = 1e-2);
assert_relative_eq!(ALAM0[LINE_4922], 4921.93, epsilon = 1e-2);
}
}