SpectraRust/src/tlusty/math/allard.rs
2026-03-25 13:31:23 +08:00

498 lines
14 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.

//! 准分子不透明度计算Lyman alpha, beta, gamma 和 Balmer alpha
//!
//! 重构自 TLUSTY `allard.f`
//!
//! 计算氢线的准分子不透明度轮廓,基于 Allard 等人的数据表。
use crate::tlusty::state::model::{CalphatD, CallardA, CallardB, CallardC, CallardG, ModelState};
// 归一化常数
// 8.8528e-29 * lambda_0^2 * f_ij
const XNORMA: f64 = 8.8528e-29 * 1215.6 * 1215.6 * 0.41618; // Lyman alpha
const XNORMB: f64 = 8.8528e-29 * 1025.73 * 1025.7 * 0.0791; // Lyman beta
const XNORMG: f64 = 8.8528e-29 * 972.53 * 972.53 * 0.0290; // Lyman gamma
const XNORMC: f64 = 8.8528e-29 * 6562.0 * 6562.0 * 0.6407; // Balmer alpha
/// 准分子不透明度线轮廓计算。
///
/// 计算 Lyman alpha, beta, gamma 和 Balmer alpha 线的准分子轮廓。
///
/// # 参数
///
/// * `xl` - 波长 (Å)
/// * `t` - 温度 (K)
/// * `hneutr` - 中性氢粒子密度 (cm⁻³)
/// * `hcharg` - 电离氢粒子密度 (cm⁻³)
/// * `iq` - 下能级量子数
/// * `jq` - 上能级量子数
/// - iq=1, jq=2: Lyman alpha
/// - iq=1, jq=3: Lyman beta
/// - iq=1, jq=4: Lyman gamma
/// - iq=2, jq=3: Balmer alpha
/// * `model` - 模型状态(包含 Allard 数据表)
///
/// # 返回值
///
/// 返回归一化的线轮廓值。如果波长超出范围或其他条件不满足,返回 0.0。
pub fn allard(
xl: f64,
t: f64,
hneutr: f64,
hcharg: f64,
iq: i32,
jq: i32,
model: &ModelState,
) -> f64 {
let mut prof = 0.0;
// Lyman alpha
if iq == 1 && jq == 2 {
if model.quasun.nunalp < 0 {
// 使用温度相关计算
return allardt_temp(xl, t, hneutr, hcharg, &model.calphatd);
} else {
prof = lyman_alpha(xl, hneutr, hcharg, &model.callarda);
}
}
// Lyman beta
else if iq == 1 && jq == 3 {
if model.callardb.nxbet == 0 {
return 0.0;
}
prof = lyman_beta(xl, hneutr, hcharg, &model.callardb);
}
// Lyman gamma
else if iq == 1 && jq == 4 {
if model.callardg.nxgam == 0 {
return 0.0;
}
prof = lyman_gamma(xl, hneutr, hcharg, &model.callardg);
}
// Balmer alpha
else if iq == 2 && jq == 3 {
prof = balmer_alpha(xl, hcharg, &model.callardc);
}
prof
}
/// Lyman alpha 线轮廓计算。
fn lyman_alpha(xl: f64, hneutr: f64, hcharg: f64, data: &CallardA) -> f64 {
let nxalp = data.nxalp as usize;
// 检查波长范围
if xl < data.xlalp[0] || xl > data.xlalp[nxalp - 1] {
return 0.0;
}
// 计算归一化密度
let vn1 = hneutr / data.stnnea;
let vn2 = hcharg / data.stncha;
let vns = vn1 * data.vneua + vn2 * data.vchaa;
// 检查密度是否过高(只警告一次)
// 注意:原始 Fortran 代码有警告输出,这里保留标志检查但不输出
// if data.iwarna == 0 && (vn1 * data.vneua > 0.3 || vn2 * data.vchaa > 0.3) {
// // 可以在这里添加警告逻辑
// }
let vn11 = vn1 * vn1;
let vn22 = vn2 * vn2;
let vn12 = vn1 * vn2;
let xnorm = 1.0 / (1.0 + vns + 0.5 * vns * vns);
// 二分查找波长索引
let j = binary_search(xl, &data.xlalp, nxalp);
// 线性插值
let a1 = (xl - data.xlalp[j - 1]) / (data.xlalp[j] - data.xlalp[j - 1]);
let p1 = vn1 * ((1.0 - a1) * data.plalp[j - 1][0] + a1 * data.plalp[j][0]);
let p11 = vn11 * ((1.0 - a1) * data.plalp[j - 1][1] + a1 * data.plalp[j][1]);
let p2 = vn2 * ((1.0 - a1) * data.plalp[j - 1][2] + a1 * data.plalp[j][2]);
let p22 = vn22 * ((1.0 - a1) * data.plalp[j - 1][3] + a1 * data.plalp[j][3]);
let p12 = vn12 * ((1.0 - a1) * data.plalp[j - 1][4] + a1 * data.plalp[j][4]);
(p1 + p2 + p11 + p22 + p12) * xnorm * XNORMA
}
/// Lyman beta 线轮廓计算。
fn lyman_beta(xl: f64, hneutr: f64, hcharg: f64, data: &CallardB) -> f64 {
let nxbet = data.nxbet as usize;
// 检查波长范围
if xl < data.xlbet[0] || xl > data.xlbet[nxbet - 1] {
return 0.0;
}
// 计算归一化密度
let vn1 = hneutr / data.stnneb;
let vn2 = hcharg / data.stnchb;
let vns = vn1 * data.vneub + vn2 * data.vchab;
let vn11 = vn1 * vn1;
let vn22 = vn2 * vn2;
let vn12 = vn1 * vn2;
let xnorm = 1.0 / (1.0 + vns + 0.5 * vns * vns);
// 二分查找波长索引
let j = binary_search(xl, &data.xlbet, nxbet);
// 线性插值
let a1 = (xl - data.xlbet[j - 1]) / (data.xlbet[j] - data.xlbet[j - 1]);
let p1 = vn1 * ((1.0 - a1) * data.plbet[j - 1][0] + a1 * data.plbet[j][0]);
let p11 = vn11 * ((1.0 - a1) * data.plbet[j - 1][1] + a1 * data.plbet[j][1]);
let p2 = vn2 * ((1.0 - a1) * data.plbet[j - 1][2] + a1 * data.plbet[j][2]);
let p22 = vn22 * ((1.0 - a1) * data.plbet[j - 1][3] + a1 * data.plbet[j][3]);
let p12 = vn12 * ((1.0 - a1) * data.plbet[j - 1][4] + a1 * data.plbet[j][4]);
(p1 + p2 + p11 + p22 + p12) * xnorm * XNORMB
}
/// Lyman gamma 线轮廓计算。
fn lyman_gamma(xl: f64, hneutr: f64, hcharg: f64, data: &CallardG) -> f64 {
let nxgam = data.nxgam as usize;
// 检查波长范围
if xl < data.xlgam[0] || xl > data.xlgam[nxgam - 1] {
return 0.0;
}
// 计算归一化密度
let vn1 = hneutr / data.stnneg;
let vn2 = hcharg / data.stnchg;
let vns = vn1 * data.vneug + vn2 * data.vchag;
let vn11 = vn1 * vn1;
let vn22 = vn2 * vn2;
let vn12 = vn1 * vn2;
let xnorm = 1.0 / (1.0 + vns + 0.5 * vns * vns);
// 二分查找波长索引
let j = binary_search(xl, &data.xlgam, nxgam);
// 线性插值
let a1 = (xl - data.xlgam[j - 1]) / (data.xlgam[j] - data.xlgam[j - 1]);
let p1 = vn1 * ((1.0 - a1) * data.plgam[j - 1][0] + a1 * data.plgam[j][0]);
let p11 = vn11 * ((1.0 - a1) * data.plgam[j - 1][1] + a1 * data.plgam[j][1]);
let p2 = vn2 * ((1.0 - a1) * data.plgam[j - 1][2] + a1 * data.plgam[j][2]);
let p22 = vn22 * ((1.0 - a1) * data.plgam[j - 1][3] + a1 * data.plgam[j][3]);
let p12 = vn12 * ((1.0 - a1) * data.plgam[j - 1][4] + a1 * data.plgam[j][4]);
(p1 + p2 + p11 + p22 + p12) * xnorm * XNORMG
}
/// Balmer alpha 线轮廓计算。
fn balmer_alpha(xl: f64, hcharg: f64, data: &CallardC) -> f64 {
let nxbal = data.nxbal as usize;
// 检查波长范围
if xl < data.xlbal[0] || xl > data.xlbal[nxbal - 1] {
return 0.0;
}
// Balmer alpha 只考虑电离氢
let vn1 = 0.0;
let vn2 = hcharg / data.stnchc;
let vns = vn1 * data.vneuc + vn2 * data.vchac;
let vn11 = vn1 * vn1;
let vn22 = vn2 * vn2;
let vn12 = vn1 * vn2;
let xnorm = 1.0 / (1.0 + vns + 0.5 * vns * vns);
// 二分查找波长索引
let j = binary_search(xl, &data.xlbal, nxbal);
// 线性插值
let a1 = (xl - data.xlbal[j - 1]) / (data.xlbal[j] - data.xlbal[j - 1]);
let p1 = vn1 * ((1.0 - a1) * data.plbal[j - 1][0] + a1 * data.plbal[j][0]);
let p11 = vn11 * ((1.0 - a1) * data.plbal[j - 1][1] + a1 * data.plbal[j][1]);
let p2 = vn2 * ((1.0 - a1) * data.plbal[j - 1][2] + a1 * data.plbal[j][2]);
let p22 = vn22 * ((1.0 - a1) * data.plbal[j - 1][3] + a1 * data.plbal[j][3]);
let p12 = vn12 * ((1.0 - a1) * data.plbal[j - 1][4] + a1 * data.plbal[j][4]);
(p1 + p2 + p11 + p22 + p12) * xnorm * XNORMC
}
/// 温度相关的 Lyman alpha 线轮廓计算。
///
/// 当 nunalp < 0 时使用此函数。
fn allardt_temp(xl: f64, t: f64, hneutr: f64, hcharg: f64, data: &CalphatD) -> f64 {
let ntalpd = data.ntalpd as usize;
// 找到接近实际温度的两个部分表
let mut it0 = 0usize;
for it in 1..=ntalpd {
it0 = it;
if t < data.talpd[it - 1] {
it0 = it - 1;
break;
}
}
// 处理边界情况
if it0 == 0 {
it0 = 1;
return compute_single_table_temp(xl, hneutr, hcharg, it0 - 1, data);
}
if it0 >= ntalpd {
it0 = ntalpd;
return compute_single_table_temp(xl, hneutr, hcharg, it0 - 1, data);
}
// 在不同温度的表之间进行插值
let prof0 = compute_single_table_temp(xl, hneutr, hcharg, it0 - 1, data);
if prof0 == 0.0 {
return 0.0;
}
let prof1 = compute_single_table_temp(xl, hneutr, hcharg, it0, data);
if prof1 == 0.0 {
return 0.0;
}
// 温度插值
let dt = data.talpd[it0] - data.talpd[it0 - 1];
if dt.abs() < 1e-10 {
prof0
} else {
(prof0 * (data.talpd[it0] - t) + prof1 * (t - data.talpd[it0 - 1])) / dt
}
}
/// 计算单个温度表的轮廓值(温度相关版本)。
fn compute_single_table_temp(
xl: f64,
hneutr: f64,
hcharg: f64,
it_idx: usize,
data: &CalphatD,
) -> f64 {
let nx = data.nxalpd[it_idx] as usize;
if nx == 0 {
return 0.0;
}
// 检查波长是否在范围内
if xl < data.xlalpd[0][it_idx] || xl > data.xlalpd[nx - 1][it_idx] {
return 0.0;
}
// 计算归一化密度
let vn1 = hneutr / data.stnead[it_idx];
let vn2 = hcharg / data.stnchd[it_idx];
let vns = vn1 * data.vneuad[it_idx] + vn2 * data.vchaad[it_idx];
let vn11 = vn1 * vn1;
let vn22 = vn2 * vn2;
let vn12 = vn1 * vn2;
let xnorm = 1.0 / (1.0 + vns + 0.5 * vns * vns);
// 二分查找波长索引
let mut jl = 0usize;
let mut ju = nx + 1;
while ju - jl > 1 {
let jm = (ju + jl) / 2;
if xl > data.xlalpd[jm - 1][it_idx] {
jl = jm;
} else {
ju = jm;
}
}
let j = if jl == 0 { 1 } else if jl == nx { nx - 1 } else { jl };
// 线性插值
let a1 = (xl - data.xlalpd[j - 1][it_idx])
/ (data.xlalpd[j][it_idx] - data.xlalpd[j - 1][it_idx]);
let p1 = vn1 * ((1.0 - a1) * data.plalpd[j - 1][0][it_idx] + a1 * data.plalpd[j][0][it_idx]);
let p11 = vn11 * ((1.0 - a1) * data.plalpd[j - 1][1][it_idx] + a1 * data.plalpd[j][1][it_idx]);
let p2 = vn2 * ((1.0 - a1) * data.plalpd[j - 1][2][it_idx] + a1 * data.plalpd[j][2][it_idx]);
let p22 = vn22 * ((1.0 - a1) * data.plalpd[j - 1][3][it_idx] + a1 * data.plalpd[j][3][it_idx]);
let p12 = vn12 * ((1.0 - a1) * data.plalpd[j - 1][4][it_idx] + a1 * data.plalpd[j][4][it_idx]);
(p1 + p2 + p11 + p22 + p12) * xnorm * XNORMA
}
/// 二分查找波长索引。
///
/// 返回索引 j使得 x[j-1] <= xl < x[j]。
/// Fortran 原始代码使用的是 1-indexed。
fn binary_search(xl: f64, x: &[f64], n: usize) -> usize {
let mut jl = 0usize;
let mut ju = n + 1;
// 判断数组是升序还是降序
let ascending = x[n - 1] > x[0];
while ju - jl > 1 {
let jm = (ju + jl) / 2;
if ascending == (xl > x[jm - 1]) {
jl = jm;
} else {
ju = jm;
}
}
// 边界检查
if jl == 0 {
1
} else if jl == n {
n - 1
} else {
jl
}
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_relative_eq;
fn create_test_callard_a() -> CallardA {
let mut data = CallardA::default();
data.nxalp = 3;
data.xlalp[0] = 1210.0;
data.xlalp[1] = 1215.0;
data.xlalp[2] = 1220.0;
data.stnnea = 1e14;
data.stncha = 1e12;
data.vneua = 1.0;
data.vchaa = 1.0;
data.iwarna = 1; // 禁用警告
for i in 0..3 {
for j in 0..5 {
data.plalp[i][j] = 1e-20;
}
}
data
}
fn create_test_callard_b() -> CallardB {
let mut data = CallardB::default();
data.nxbet = 3;
data.xlbet[0] = 1020.0;
data.xlbet[1] = 1025.0;
data.xlbet[2] = 1030.0;
data.stnneb = 1e14;
data.stnchb = 1e12;
data.vneub = 1.0;
data.vchab = 1.0;
data.iwarnb = 1;
for i in 0..3 {
for j in 0..5 {
data.plbet[i][j] = 1e-20;
}
}
data
}
fn create_test_callard_g() -> CallardG {
let mut data = CallardG::default();
data.nxgam = 3;
data.xlgam[0] = 970.0;
data.xlgam[1] = 972.0;
data.xlgam[2] = 975.0;
data.stnneg = 1e14;
data.stnchg = 1e12;
data.vneug = 1.0;
data.vchag = 1.0;
data.iwarng = 1;
for i in 0..3 {
for j in 0..5 {
data.plgam[i][j] = 1e-20;
}
}
data
}
fn create_test_callard_c() -> CallardC {
let mut data = CallardC::default();
data.nxbal = 3;
data.xlbal[0] = 6550.0;
data.xlbal[1] = 6562.0;
data.xlbal[2] = 6580.0;
data.stnchc = 1e12;
data.vneuc = 1.0;
data.vchac = 1.0;
for i in 0..3 {
for j in 0..5 {
data.plbal[i][j] = 1e-20;
}
}
data
}
fn create_test_model() -> ModelState {
let mut model = ModelState::default();
model.callarda = create_test_callard_a();
model.callardb = create_test_callard_b();
model.callardg = create_test_callard_g();
model.callardc = create_test_callard_c();
model.quasun.nunalp = 0; // 使用非温度相关计算
model
}
#[test]
fn test_binary_search() {
let x = vec![1210.0, 1215.0, 1220.0];
// 升序数组
let j = binary_search(1212.0, &x, 3);
assert_eq!(j, 1);
let j = binary_search(1217.0, &x, 3);
assert_eq!(j, 2);
}
#[test]
fn test_allard_lyman_alpha_in_range() {
let model = create_test_model();
let prof = allard(1215.0, 10000.0, 1e14, 1e12, 1, 2, &model);
assert!(prof > 0.0);
}
#[test]
fn test_allard_lyman_alpha_out_of_range() {
let model = create_test_model();
let prof = allard(1000.0, 10000.0, 1e14, 1e12, 1, 2, &model);
assert_relative_eq!(prof, 0.0, epsilon = 1e-30);
}
#[test]
fn test_allard_lyman_beta_in_range() {
let model = create_test_model();
let prof = allard(1025.0, 10000.0, 1e14, 1e12, 1, 3, &model);
assert!(prof > 0.0);
}
#[test]
fn test_allard_lyman_gamma_in_range() {
let model = create_test_model();
let prof = allard(972.0, 10000.0, 1e14, 1e12, 1, 4, &model);
assert!(prof > 0.0);
}
#[test]
fn test_allard_balmer_alpha_in_range() {
let model = create_test_model();
let prof = allard(6562.0, 10000.0, 1e14, 1e12, 2, 3, &model);
assert!(prof >= 0.0); // Balmer alpha 只考虑电离氢
}
#[test]
fn test_allard_invalid_transition() {
let model = create_test_model();
// 无效的量子数组合
let prof = allard(6562.0, 10000.0, 1e14, 1e12, 3, 4, &model);
assert_relative_eq!(prof, 0.0, epsilon = 1e-30);
}
}