423 lines
11 KiB
Rust
423 lines
11 KiB
Rust
//! 线列表初始化驱动程序。
|
||
//!
|
||
//! 重构自 SYNSPEC `inibla.f`
|
||
//!
|
||
//! 处理当前波长范围的部分线列表,设置 Doppler 宽度和 Van der Waals 宽度等参数。
|
||
|
||
// ============================================================================
|
||
// 物理常数
|
||
// ============================================================================
|
||
|
||
/// 光速 (cm/s)
|
||
pub const CL: f64 = 2.997925e10;
|
||
|
||
/// 普朗克常数 (erg·s)
|
||
pub const H: f64 = 6.6256e-27;
|
||
|
||
/// 玻尔兹曼常数 (erg/K)
|
||
pub const BOLK: f64 = 1.38054e-16;
|
||
|
||
/// Planck 函数常数 BN = 2*h*c²/c³ = 2*h/c²
|
||
pub const BN: f64 = 1.4743e-2;
|
||
|
||
/// h/k (s·K)
|
||
pub const HK: f64 = 4.79928144e-11;
|
||
|
||
/// Doppler 宽度常数 DP0 = 1/c
|
||
pub const DP0: f64 = 3.33564e-11;
|
||
|
||
/// Doppler 宽度常数 DP1 = 2*k/m_H * 1e8
|
||
pub const DP1: f64 = 1.651e8;
|
||
|
||
/// Van der Waals 宽度常数 VW1
|
||
pub const VW1: f64 = 0.42;
|
||
|
||
/// Van der Waals 宽度常数 VW2
|
||
pub const VW2: f64 = 0.45;
|
||
|
||
/// 温度归一化因子
|
||
pub const TENM4: f64 = 1e-4;
|
||
|
||
// ============================================================================
|
||
// 参数结构体
|
||
// ============================================================================
|
||
|
||
/// INIBLA 输入参数。
|
||
#[derive(Debug, Clone)]
|
||
pub struct IniblaParams<'a> {
|
||
/// 线数量 (如果为 0,直接返回)
|
||
pub nlin: i32,
|
||
|
||
/// 频率数组 (Hz)
|
||
pub freq: &'a [f64],
|
||
|
||
/// 频率点数
|
||
pub nfreq: i32,
|
||
|
||
/// 频率窗口标志 (>0 表示使用窗口)
|
||
pub ifwin: i32,
|
||
|
||
/// 中心频率数组 (Hz)
|
||
pub freqc: &'a [f64],
|
||
|
||
/// 中心频率点数
|
||
pub nfreqc: i32,
|
||
|
||
/// 深度点数
|
||
pub nd: usize,
|
||
|
||
/// 温度数组 (K)
|
||
pub temp: &'a [f64],
|
||
|
||
/// 电子密度数组
|
||
pub elec: &'a [f64],
|
||
|
||
/// 氢密度数组 (中性氢)
|
||
pub ah: &'a [f64],
|
||
|
||
/// 氦密度数组
|
||
pub ahe: &'a [f64],
|
||
|
||
/// H2 分子密度数组
|
||
pub anh2: &'a [f64],
|
||
|
||
/// 原子质量数组
|
||
pub amas: &'a [f64],
|
||
|
||
/// 湍流速度数组
|
||
pub vturb: &'a [f64],
|
||
|
||
/// 氢原子处理标志 (>0 表示处理氢)
|
||
pub iath: i32,
|
||
}
|
||
|
||
/// INIBLA 输出结果。
|
||
#[derive(Debug, Clone)]
|
||
pub struct IniblaOutput {
|
||
/// h*k*频率 / 温度 (用于激发) - EXHK
|
||
pub exhk: Vec<f64>,
|
||
|
||
/// Planck 函数值 - PLAN
|
||
pub plan: Vec<f64>,
|
||
|
||
/// 受激因子 (1 - e^(-hν/kT)) - STIM
|
||
pub stim: Vec<f64>,
|
||
|
||
/// Van der Waals 宽度 - VDWC
|
||
pub vdwc: Vec<f64>,
|
||
|
||
/// Doppler 宽度数组 (原子数 × 深度点数) - DOPA1
|
||
pub dopa1: Vec<f64>,
|
||
}
|
||
|
||
// ============================================================================
|
||
// INIBLA 函数
|
||
// ============================================================================
|
||
|
||
/// 初始化线列表参数。
|
||
///
|
||
/// 设置 Doppler 宽度、Van der Waals 宽度、Planck 函数等参数,
|
||
/// 用于后续的线不透明度计算。
|
||
///
|
||
/// # 参数
|
||
///
|
||
/// * `params` - 输入参数结构体
|
||
///
|
||
/// # 返回
|
||
///
|
||
/// 包含各种预计算值的输出结构体
|
||
///
|
||
/// # Fortran 原始代码
|
||
///
|
||
/// ```fortran
|
||
/// SUBROUTINE INIBLA
|
||
/// ```
|
||
pub fn inibla(params: &IniblaParams) -> IniblaOutput {
|
||
let nd = params.nd;
|
||
let n_atoms = params.amas.len();
|
||
|
||
// 初始化输出数组
|
||
let mut output = IniblaOutput {
|
||
exhk: vec![0.0; nd],
|
||
plan: vec![0.0; nd],
|
||
stim: vec![0.0; nd],
|
||
vdwc: vec![0.0; nd],
|
||
dopa1: vec![0.0; nd * n_atoms],
|
||
};
|
||
|
||
// 如果没有线,直接返回
|
||
if params.nlin == 0 {
|
||
return output;
|
||
}
|
||
|
||
// 计算参考频率 XX
|
||
let xx = if params.nfreq >= 2 {
|
||
0.5 * (params.freq[0] + params.freq[1])
|
||
} else {
|
||
params.freq[0]
|
||
};
|
||
|
||
// 如果使用频率窗口,使用窗口中心频率
|
||
let mut xx = if params.ifwin > 0 && params.nfreqc > 0 {
|
||
0.5 * (params.freqc[0] + params.freqc[params.nfreqc as usize - 1])
|
||
} else {
|
||
xx
|
||
};
|
||
|
||
// 计算 Planck 函数常数
|
||
let bnu = BN * (xx * 1e-15).powi(3);
|
||
let hkf = HK * xx;
|
||
|
||
// 如果使用频率窗口,归一化 xx
|
||
if params.ifwin > 0 {
|
||
xx = 1.0;
|
||
}
|
||
|
||
// 遍历所有深度点
|
||
for id in 0..nd {
|
||
let t = params.temp[id];
|
||
let _ane = params.elec[id];
|
||
|
||
// 计算激发因子
|
||
let exh = (hkf / t).exp();
|
||
output.exhk[id] = 1.0 / exh;
|
||
|
||
// 计算 Planck 函数
|
||
output.plan[id] = bnu / (exh - 1.0);
|
||
|
||
// 计算受激因子
|
||
output.stim[id] = 1.0 - output.exhk[id];
|
||
|
||
// 计算 Van der Waals 宽度
|
||
let ah = params.ah[id];
|
||
let ahe = params.ahe[id];
|
||
let anh2 = params.anh2[id];
|
||
output.vdwc[id] = (ah + VW1 * ahe + 0.85 * anh2) * (t * TENM4).powf(VW2);
|
||
|
||
// 计算每个原子的 Doppler 宽度
|
||
for (iat, &amas) in params.amas.iter().enumerate() {
|
||
if amas > 0.0 {
|
||
let vturb_sq = params.vturb[id] * params.vturb[id];
|
||
let dopa1_val = 1.0 / (xx * DP0 * (DP1 * t / amas + vturb_sq).sqrt());
|
||
output.dopa1[iat * nd + id] = dopa1_val;
|
||
}
|
||
}
|
||
}
|
||
|
||
output
|
||
}
|
||
|
||
// ============================================================================
|
||
// 辅助函数
|
||
// ============================================================================
|
||
|
||
/// 计算单个深度点的 Doppler 宽度。
|
||
///
|
||
/// # 参数
|
||
///
|
||
/// * `xx` - 参考频率 (Hz)
|
||
/// * `temp` - 温度 (K)
|
||
/// * `amas` - 原子质量 (原子质量单位)
|
||
/// * `vturb` - 湍流速度 (cm/s)
|
||
///
|
||
/// # 返回
|
||
///
|
||
/// Doppler 宽度参数
|
||
pub fn compute_doppler_width(xx: f64, temp: f64, amas: f64, vturb: f64) -> f64 {
|
||
1.0 / (xx * DP0 * (DP1 * temp / amas + vturb * vturb).sqrt())
|
||
}
|
||
|
||
/// 计算单个深度点的 Van der Waals 宽度。
|
||
///
|
||
/// # 参数
|
||
///
|
||
/// * `ah` - 中性氢密度
|
||
/// * `ahe` - 氦密度
|
||
/// * `anh2` - H2 分子密度
|
||
/// * `temp` - 温度 (K)
|
||
///
|
||
/// # 返回
|
||
///
|
||
/// Van der Waals 宽度参数
|
||
pub fn compute_vdw_width(ah: f64, ahe: f64, anh2: f64, temp: f64) -> f64 {
|
||
(ah + VW1 * ahe + 0.85 * anh2) * (temp * TENM4).powf(VW2)
|
||
}
|
||
|
||
/// 计算 Planck 函数值。
|
||
///
|
||
/// # 参数
|
||
///
|
||
/// * `freq` - 频率 (Hz)
|
||
/// * `temp` - 温度 (K)
|
||
///
|
||
/// # 返回
|
||
///
|
||
/// Planck 函数值 (erg/s/cm²/Hz/sr)
|
||
pub fn compute_planck(freq: f64, temp: f64) -> f64 {
|
||
let hkf = HK * freq;
|
||
let exh = (hkf / temp).exp();
|
||
let bnu = BN * (freq * 1e-15).powi(3);
|
||
bnu / (exh - 1.0)
|
||
}
|
||
|
||
// ============================================================================
|
||
// 测试
|
||
// ============================================================================
|
||
|
||
#[cfg(test)]
|
||
mod tests {
|
||
use super::*;
|
||
use approx::assert_relative_eq;
|
||
|
||
#[test]
|
||
fn test_inibla_no_lines() {
|
||
// 当 nlin = 0 时应返回零数组
|
||
let freq = [1e15];
|
||
let temp = [10000.0];
|
||
let elec = [1e12];
|
||
let ah = [1e10];
|
||
let ahe = [1e9];
|
||
let anh2 = [1e8];
|
||
let amas = [1.0, 4.0];
|
||
let vturb = [1e5];
|
||
|
||
let params = IniblaParams {
|
||
nlin: 0,
|
||
freq: &freq,
|
||
nfreq: 1,
|
||
ifwin: 0,
|
||
freqc: &[],
|
||
nfreqc: 0,
|
||
nd: 1,
|
||
temp: &temp,
|
||
elec: &elec,
|
||
ah: &ah,
|
||
ahe: &ahe,
|
||
anh2: &anh2,
|
||
amas: &amas,
|
||
vturb: &vturb,
|
||
iath: 1,
|
||
};
|
||
|
||
let result = inibla(¶ms);
|
||
assert_eq!(result.exhk.len(), 1);
|
||
assert_eq!(result.plan.len(), 1);
|
||
assert_eq!(result.stim.len(), 1);
|
||
assert_eq!(result.vdwc.len(), 1);
|
||
}
|
||
|
||
#[test]
|
||
fn test_inibla_with_lines() {
|
||
// 当 nlin > 0 时应计算正确的值
|
||
let freq = [1e15, 1.1e15];
|
||
let temp = [10000.0, 9000.0];
|
||
let elec = [1e12, 9e11];
|
||
let ah = [1e10, 9e9];
|
||
let ahe = [1e9, 9e8];
|
||
let anh2 = [1e8, 9e7];
|
||
let amas = [1.0, 4.0];
|
||
let vturb = [1e5, 9e4];
|
||
|
||
let params = IniblaParams {
|
||
nlin: 100,
|
||
freq: &freq,
|
||
nfreq: 2,
|
||
ifwin: 0,
|
||
freqc: &[],
|
||
nfreqc: 0,
|
||
nd: 2,
|
||
temp: &temp,
|
||
elec: &elec,
|
||
ah: &ah,
|
||
ahe: &ahe,
|
||
anh2: &anh2,
|
||
amas: &amas,
|
||
vturb: &vturb,
|
||
iath: 1,
|
||
};
|
||
|
||
let result = inibla(¶ms);
|
||
|
||
// 验证输出数组长度
|
||
assert_eq!(result.exhk.len(), 2);
|
||
assert_eq!(result.plan.len(), 2);
|
||
assert_eq!(result.stim.len(), 2);
|
||
assert_eq!(result.vdwc.len(), 2);
|
||
assert_eq!(result.dopa1.len(), 4); // 2 atoms * 2 depth points
|
||
|
||
// 验证所有值都是正数
|
||
for &v in &result.exhk {
|
||
assert!(v > 0.0 && v < 1.0);
|
||
}
|
||
for &v in &result.plan {
|
||
assert!(v > 0.0);
|
||
}
|
||
for &v in &result.stim {
|
||
assert!(v > 0.0 && v < 1.0);
|
||
}
|
||
for &v in &result.vdwc {
|
||
assert!(v > 0.0);
|
||
}
|
||
}
|
||
|
||
#[test]
|
||
fn test_compute_doppler_width() {
|
||
let xx = 1e15; // Hz
|
||
let temp = 10000.0; // K
|
||
let amas = 1.0; // 氢原子质量
|
||
let vturb = 1e5; // cm/s
|
||
|
||
let result = compute_doppler_width(xx, temp, amas, vturb);
|
||
|
||
// 验证结果为正数
|
||
assert!(result > 0.0);
|
||
|
||
// 手动计算
|
||
let expected = 1.0 / (xx * DP0 * (DP1 * temp / amas + vturb * vturb).sqrt());
|
||
assert_relative_eq!(result, expected, epsilon = 1e-20);
|
||
}
|
||
|
||
#[test]
|
||
fn test_compute_vdw_width() {
|
||
let ah = 1e10;
|
||
let ahe = 1e9;
|
||
let anh2 = 1e8;
|
||
let temp = 10000.0;
|
||
|
||
let result = compute_vdw_width(ah, ahe, anh2, temp);
|
||
|
||
// 验证结果为正数
|
||
assert!(result > 0.0);
|
||
|
||
// 手动计算
|
||
let expected = (ah + VW1 * ahe + 0.85 * anh2) * (temp * TENM4).powf(VW2);
|
||
assert_relative_eq!(result, expected, epsilon = 1e-20);
|
||
}
|
||
|
||
#[test]
|
||
fn test_compute_planck() {
|
||
let freq = 1e15; // Hz
|
||
let temp = 10000.0; // K
|
||
|
||
let result = compute_planck(freq, temp);
|
||
|
||
// 验证结果为正数
|
||
assert!(result > 0.0);
|
||
|
||
// 验证 Planck 函数值在合理范围内
|
||
// 对于 T=10000K, ν=1e15 Hz,Planck 函数约为 1e-5 erg/s/cm²/Hz/sr
|
||
assert!(result > 1e-10 && result < 1e10);
|
||
}
|
||
|
||
#[test]
|
||
fn test_constants() {
|
||
// 验证常数与 Fortran 一致
|
||
assert_relative_eq!(DP0, 3.33564e-11, epsilon = 1e-16);
|
||
assert_relative_eq!(DP1, 1.651e8, epsilon = 1e3);
|
||
assert_relative_eq!(VW1, 0.42, epsilon = 1e-10);
|
||
assert_relative_eq!(VW2, 0.45, epsilon = 1e-10);
|
||
assert_relative_eq!(BN, 1.4743e-2, epsilon = 1e-6);
|
||
assert_relative_eq!(HK, 4.79928144e-11, epsilon = 1e-18);
|
||
}
|
||
}
|