SpectraRust/src/math/gamhe.rs
Asfmq 0674b4f174 feat: 添加 9 个 SYNSPEC 数学模块 (第8批)
新增模块:
- count_words: 字符串单词计数工具
- divhe2: He II Stark 轮廓除数参数计算
- extprf: 谱线轮廓波长外推 (Cooper 公式)
- feautr: Lyman-α Stark 加宽 (Feautrier 方法)
- gamhe: 中性氦 Stark 加宽参数
- griem: Griem Stark 阻尼参数计算
- intrp: 二分法高效插值程序
- partdv: 配分函数计算 (含压力效应)
- sffhmi_old: H- 自由-自由截面 (Kurucz 公式)

改进:
- 修复 fortran-analyzer 注释行误匹配问题

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-25 06:52:44 +08:00

264 lines
7.7 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.

//! 中性氦 Stark 加宽参数计算。
//!
//! 重构自 SYNSPEC `gamhe.f`
//!
//! 基于 Dimitrijevic 和 Sahal-Brechot (1984, J.Q.S.R.T. 31, 301)
//! 或 Freudenstein 和 Cooper (1978, AP.J. 224, 1079) 的数据。
/// Gamhe 计算所需的模型层参数。
#[derive(Debug, Clone)]
pub struct GamheParams {
/// 温度插值索引 (JT)
pub jt: usize,
/// 温度插值系数 0
pub ti0: f64,
/// 温度插值系数 1
pub ti1: f64,
/// 温度插值系数 2
pub ti2: f64,
}
/// 中性氦 Stark 加宽参数。
///
/// 包含电子和质子贡献的加宽数据。
pub struct GamheData {
/// W 数组: [电子加宽 @ 5000K, @ 10000K, @ 20000K, @ 40000K, 波长(Å)]
/// 按谱线索引 (1-20) 存储
pub w: [[f64; 5]; 20],
/// V 数组: 质子加宽 @ 5000K, @ 10000K, @ 20000K, @ 40000K
pub v: [[f64; 4]; 20],
/// C 数组: 备用系数 (当 W=0 时使用)
pub c: [f64; 20],
}
impl GamheData {
/// 创建默认的 GamheData包含所有谱线数据。
pub fn new() -> Self {
// W 数组: 电子加宽参数 + 波长
// Fortran DATA 是列优先,需要转置
const W_DATA: [[f64; 5]; 20] = [
[5.990, 6.650, 6.610, 6.210, 3819.60],
[2.950, 3.130, 3.230, 3.300, 3867.50],
[0.000, 0.000, 0.000, 0.000, 3871.79],
[0.142, 0.166, 0.182, 0.190, 3888.65],
[0.000, 0.000, 0.000, 0.000, 3926.53],
[1.540, 1.480, 1.400, 1.290, 3964.73],
[41.600, 50.500, 57.400, 65.800, 4009.27],
[1.320, 1.350, 1.380, 1.460, 4120.80],
[7.830, 8.750, 8.690, 8.040, 4143.76],
[5.830, 6.370, 6.820, 6.990, 4168.97],
[0.000, 0.000, 0.000, 0.000, 4437.55],
[1.630, 1.610, 1.490, 1.350, 4471.50],
[0.588, 0.620, 0.641, 0.659, 4713.20],
[2.600, 2.480, 2.240, 1.960, 4921.93],
[0.627, 0.597, 0.568, 0.532, 5015.68],
[1.050, 1.090, 1.110, 1.140, 5047.74],
[0.277, 0.298, 0.296, 0.293, 5875.70],
[0.714, 0.666, 0.602, 0.538, 6678.15],
[3.490, 3.630, 3.470, 3.190, 4026.20],
[4.970, 5.100, 4.810, 4.310, 4387.93],
];
// V 数组: 质子加宽参数
const V_DATA: [[f64; 4]; 20] = [
[1.520, 4.540, 9.140, 10.200],
[0.607, 0.710, 0.802, 0.901],
[0.000, 0.000, 0.000, 0.000],
[0.0396, 0.0434, 0.0476, 0.0526],
[0.000, 0.000, 0.000, 0.000],
[0.507, 0.585, 0.665, 0.762],
[0.930, 1.710, 13.600, 27.200],
[0.288, 0.325, 0.365, 0.410],
[1.330, 6.800, 12.900, 14.300],
[1.100, 1.370, 1.560, 1.760],
[0.000, 0.000, 0.000, 0.000],
[1.340, 1.690, 1.820, 1.630],
[0.128, 0.143, 0.161, 0.181],
[2.040, 2.740, 2.950, 2.740],
[0.187, 0.210, 0.237, 0.270],
[0.231, 0.260, 0.291, 0.327],
[0.0591, 0.0650, 0.0719, 0.0799],
[0.231, 0.260, 0.295, 0.339],
[2.180, 3.760, 4.790, 4.560],
[1.860, 5.320, 7.070, 7.150],
];
// C 数组: 备用系数
// DATA C /2*0.,1.83E-4,0.,1.13E-4,5*0.,1.6E-4,9*0./
const C_DATA: [f64; 20] = [
0.0, 0.0, 1.83e-4, 0.0, 1.13e-4,
0.0, 0.0, 0.0, 0.0, 0.0,
1.6e-4, 0.0, 0.0, 0.0, 0.0,
0.0, 0.0, 0.0, 0.0, 0.0,
];
Self {
w: W_DATA,
v: V_DATA,
c: C_DATA,
}
}
}
impl Default for GamheData {
fn default() -> Self {
Self::new()
}
}
/// 计算中性氦 Stark 加宽参数 GAM。
///
/// # 参数
///
/// * `ind` - 谱线索引 (1-20, 1-indexed)
/// * `t` - 温度 (K)
/// * `ane` - 电子数密度
/// * `anp` - 质子数密度
/// * `params` - 模型层插值参数
/// * `data` - Stark 加宽数据表
///
/// # 返回值
///
/// Stark 加宽参数 GAM (Å)
///
/// # 算法
///
/// 如果 W(1,IND) != 0:
/// GAM = (电子贡献 * ANE + 质子贡献 * ANP) * 1.884e3 / 波长^2
/// 否则:
/// GAM = C(IND) * T^0.16667 * ANE
pub fn gamhe(ind: usize, t: f64, ane: f64, anp: f64, params: &GamheParams, data: &GamheData) -> f64 {
// 转换为 0-indexed
let ind_idx = ind - 1;
// 检查是否有有效的电子加宽数据
if data.w[ind_idx][0] == 0.0 {
// 使用备用公式
let gam = data.c[ind_idx] * t.powf(0.16667) * ane;
return gam.max(0.0);
}
// 温度插值
let jt_idx = params.jt; // 已是 0-indexed
// 电子加宽贡献
let w_electron = params.ti0 * data.w[ind_idx][jt_idx]
+ params.ti1 * data.w[ind_idx][jt_idx - 1]
+ params.ti2 * data.w[ind_idx][jt_idx - 2];
// 质子加宽贡献
let v_proton = params.ti0 * data.v[ind_idx][jt_idx]
+ params.ti1 * data.v[ind_idx][jt_idx - 1]
+ params.ti2 * data.v[ind_idx][jt_idx - 2];
// 波长 (Å)
let wavelength = data.w[ind_idx][4];
// 计算 GAM
let gam = (w_electron * ane + v_proton * anp) * 1.884e3 / (wavelength * wavelength);
gam.max(0.0)
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_relative_eq;
fn make_params(jt: usize) -> GamheParams {
GamheParams {
jt,
ti0: 1.0,
ti1: 0.0,
ti2: 0.0,
}
}
#[test]
fn test_basic() {
let data = GamheData::new();
let params = make_params(2); // jt >= 2 以访问 jt-2
// 使用谱线 1 (3819.60 Å)
let gam = gamhe(1, 10000.0, 1e13, 1e12, &params, &data);
assert!(gam >= 0.0);
assert!(gam.is_finite());
}
#[test]
fn test_zero_w_case() {
let data = GamheData::new();
let params = make_params(2);
// 谱线 3 的 W(1,3) = 0使用备用公式
let gam = gamhe(3, 10000.0, 1e13, 1e12, &params, &data);
// C(3) = 1.83e-4
let expected = 1.83e-4 * 10000.0_f64.powf(0.16667) * 1e13;
assert_relative_eq!(gam, expected, epsilon = 1e-6);
}
#[test]
fn test_electron_contribution() {
let data = GamheData::new();
let params = make_params(2);
// 纯电子贡献 (ANP = 0)
let gam_e = gamhe(1, 10000.0, 1e13, 0.0, &params, &data);
assert!(gam_e > 0.0);
}
#[test]
fn test_proton_contribution() {
let data = GamheData::new();
let params = make_params(2);
// 纯质子贡献 (ANE = 0)
let gam_p = gamhe(1, 10000.0, 0.0, 1e13, &params, &data);
assert!(gam_p > 0.0);
}
#[test]
fn test_density_scaling() {
let data = GamheData::new();
let params = make_params(2);
let gam1 = gamhe(1, 10000.0, 1e13, 1e12, &params, &data);
let gam2 = gamhe(1, 10000.0, 2e13, 2e12, &params, &data);
// 密度翻倍GAM 应该翻倍
assert_relative_eq!(gam2, 2.0 * gam1, epsilon = 1e-10);
}
#[test]
fn test_negative_result_clamped() {
let data = GamheData::new();
let params = GamheParams {
jt: 2,
ti0: -1.0, // 负系数可能导致负 GAM
ti1: 0.0,
ti2: 0.0,
};
let gam = gamhe(1, 10000.0, 1e13, 1e12, &params, &data);
// 负值应该被钳制为 0
assert!(gam >= 0.0);
}
#[test]
fn test_wavelength_scaling() {
let data = GamheData::new();
let params = make_params(2);
// 谱线 1: 3819.60 Å, 谱线 2: 3867.50 Å
let gam1 = gamhe(1, 10000.0, 1e13, 0.0, &params, &data);
let gam2 = gamhe(2, 10000.0, 1e13, 0.0, &params, &data);
// GAM ∝ 1/波长^2较长波长应该有较小的 GAM
// (假设其他参数相似)
assert!(gam1.is_finite() && gam2.is_finite());
}
}