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

469 lines
12 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.

//! 分子谱线不透明度和发射率计算。
//!
//! 重构自 SYNSPEC `molop.f`
//!
//! 计算给定深度点的总分子谱线不透明度 (ABLIN) 和发射率 (EMLIN)。
use crate::synspec::math::voigtk::{voigtk, MVOI};
/// 常量
const UN: f64 = 1.0;
const EXT0: f64 = 3.17;
const TEN: f64 = 10.0;
/// 分子谱线数据参数
///
/// 对应 LINDAT.FOR 中的 MOLTOT COMMON 块
pub struct MolLineData<'a> {
/// 分子谱线频率 [Hz]
pub freqm: &'a [f64],
/// 下能级激发能 [K]
pub exclm: &'a [f64],
/// gf 值 (log10)
pub gfm: &'a [f64],
/// 辐射加宽参数
pub grm: &'a [f64],
/// Stark 加宽参数
pub gsm: &'a [f64],
/// van der Waals 加宽参数 (按深度)
pub gvdw: &'a [f64],
/// 分子数据索引
pub indatm: &'a [i32],
/// 分子线索引
pub inmlin: &'a [i32],
/// 中心频率索引
pub ijcmtr: &'a [i32],
/// 分子谱线数
pub nlinml: i32,
/// 分子列表激活标志
pub inactm: i32,
}
/// 分子状态参数
///
/// 对应 MODELP.FOR 中的 MOLPAR COMMON 块
pub struct MolModelState<'a> {
/// 分子数密度 [cm^-3]
pub rrmol: &'a [f64],
/// 分子多普勒宽度
pub dopmol: &'a [f64],
}
/// 深度点状态参数
///
/// 对应 MODELP.FOR 和 LINDAT.FOR 中的相关变量
pub struct MolopModelState<'a> {
/// 温度 [K]
pub temp: f64,
/// 电子密度 [cm^-3]
pub elec: f64,
/// 分子状态
pub mol: MolModelState<'a>,
/// Planck 函数
pub plan: f64,
/// 受激因子
pub stim: f64,
}
/// 频率网格参数
///
/// 对应 SYNTHP.FOR 中的 FREQSY COMMON 块
pub struct MolopFreqParams<'a> {
/// 频率数组 [Hz]
pub freq: &'a [f64],
/// 频率点数
pub nfreq: usize,
/// 参考频率点数(用于输出范围)
pub nfreqs: usize,
}
/// 配置参数
///
/// 对应 PARAMS.FOR 和 LINDAT.FOR 中的相关变量
pub struct MolopConfig {
/// 分子温度限制 [K]
pub tmolim: f64,
/// 频率转换因子
pub dfrcon: f64,
}
/// 输出结果
pub struct MolopOutput {
/// 不透明度数组 [cm^-1]
pub ablin: Vec<f64>,
/// 发射率数组
pub emlin: Vec<f64>,
}
/// 分子谱线不透明度和发射率计算(纯函数版本)。
///
/// # 参数
///
/// * `config` - 配置参数
/// * `model` - 模型状态
/// * `line_data` - 分子谱线数据
/// * `freq` - 频率网格参数
/// * `avab` - 平均不透明度(用于确定线宽范围)
/// * `voigt_tables` - Voigt 函数预计算表格 (h0, h1, h2)
///
/// # 返回值
///
/// 返回不透明度和发射率数组
pub fn molop_pure(
config: &MolopConfig,
model: &MolopModelState,
line_data: &MolLineData,
freq: &MolopFreqParams,
avab: f64,
voigt_tables: (&[f64; MVOI], &[f64; MVOI], &[f64; MVOI]),
) -> MolopOutput {
let nfreq = freq.nfreq;
let mut ablin = vec![0.0; nfreq];
let mut emlin = vec![0.0; nfreq];
// 检查温度是否超过分子温度限制
if model.temp > config.tmolim {
return MolopOutput { ablin, emlin };
}
// 检查是否有分子谱线
if line_data.nlinml == 0 {
return MolopOutput { ablin, emlin };
}
// 检查分子列表是否激活
if line_data.inactm != 0 {
return MolopOutput { ablin, emlin };
}
let (h0tab, h1tab, h2tab) = voigt_tables;
let tem1 = UN / model.temp;
let ane = model.elec;
// 遍历所有贡献的分子谱线
for i in 0..line_data.nlinml as usize {
let il = line_data.inmlin[i] as usize;
let imol = line_data.indatm[il] as usize;
let dop1 = model.mol.dopmol[imol];
let agam = (line_data.grm[il] + line_data.gsm[il] * ane + line_data.gvdw[il]) * dop1;
let fr0 = line_data.freqm[il];
let ab0 = (line_data.gfm[il] - line_data.exclm[il] * tem1).exp()
* model.mol.rrmol[imol]
* dop1
* model.stim;
// 设置谱线贡献的频率范围限制
let ex0 = ab0 / avab * agam;
let mut ext = EXT0;
if ex0 > TEN {
ext = ex0.sqrt();
}
ext = ext / dop1;
let xijext = config.dfrcon * ext + 1.5;
let ij1 = ((line_data.ijcmtr[i] as f64 - xijext).max(3.0)) as usize;
let ij2 = ((line_data.ijcmtr[i] as f64 + xijext).min(freq.nfreqs as f64)) as usize;
// 只有当范围有效时才计算
if ij1 < nfreq && ij2 > 2 {
for ij in ij1..=ij2.min(nfreq - 1) {
let xf = (freq.freq[ij] - fr0).abs() * dop1;
ablin[ij] = ablin[ij] + ab0 * voigtk(agam, xf, h0tab, h1tab, h2tab);
}
}
}
// 计算发射率从第3个频率点开始
for ij in 3..nfreq {
emlin[ij] = emlin[ij] + ablin[ij] * model.plan;
}
MolopOutput { ablin, emlin }
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_relative_eq;
fn create_test_voigt_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) = exp(-v^2) / sqrt(pi)
let inv_sqrt_pi = 0.5641895835477563;
for i in 0..MVOI {
let v = (i as f64 - 1.5) / 200.0;
h0[i] = (-v * v).exp() * inv_sqrt_pi;
h1[i] = -2.0 * v * h0[i];
h2[i] = (4.0 * v * v - 2.0) * h0[i];
}
(h0, h1, h2)
}
#[test]
fn test_molop_high_temperature() {
let (h0, h1, h2) = create_test_voigt_tables();
let config = MolopConfig {
tmolim: 5000.0,
dfrcon: 1.0,
};
let mol_data = vec![1e10, 1e10, 1e10];
let dop_data = vec![1e-10, 1e-10, 1e-10];
let model = MolopModelState {
temp: 6000.0, // 超过 tmolim
elec: 1e12,
mol: MolModelState {
rrmol: &mol_data,
dopmol: &dop_data,
},
plan: 1.0,
stim: 1.0,
};
let freq_data = vec![0.0; 100];
let freq = MolopFreqParams {
freq: &freq_data,
nfreq: 100,
nfreqs: 100,
};
let gfm = vec![];
let exclm = vec![];
let freqm = vec![];
let grm = vec![];
let gsm = vec![];
let gvdw = vec![];
let indatm = vec![];
let inmlin = vec![];
let ijcmtr = vec![];
let line_data = MolLineData {
freqm: &freqm,
exclm: &exclm,
gfm: &gfm,
grm: &grm,
gsm: &gsm,
gvdw: &gvdw,
indatm: &indatm,
inmlin: &inmlin,
ijcmtr: &ijcmtr,
nlinml: 10,
inactm: 0,
};
let result = molop_pure(&config, &model, &line_data, &freq, 1e-10, (&h0, &h1, &h2));
// 高温时应该返回零
for i in 0..result.ablin.len() {
assert_relative_eq!(result.ablin[i], 0.0);
assert_relative_eq!(result.emlin[i], 0.0);
}
}
#[test]
fn test_molop_no_lines() {
let (h0, h1, h2) = create_test_voigt_tables();
let config = MolopConfig {
tmolim: 10000.0,
dfrcon: 1.0,
};
let mol_data = vec![1e10, 1e10, 1e10];
let dop_data = vec![1e-10, 1e-10, 1e-10];
let model = MolopModelState {
temp: 5000.0,
elec: 1e12,
mol: MolModelState {
rrmol: &mol_data,
dopmol: &dop_data,
},
plan: 1.0,
stim: 1.0,
};
let freq_data = vec![0.0; 100];
let freq = MolopFreqParams {
freq: &freq_data,
nfreq: 100,
nfreqs: 100,
};
let gfm = vec![];
let exclm = vec![];
let freqm = vec![];
let grm = vec![];
let gsm = vec![];
let gvdw = vec![];
let indatm = vec![];
let inmlin = vec![];
let ijcmtr = vec![];
let line_data = MolLineData {
freqm: &freqm,
exclm: &exclm,
gfm: &gfm,
grm: &grm,
gsm: &gsm,
gvdw: &gvdw,
indatm: &indatm,
inmlin: &inmlin,
ijcmtr: &ijcmtr,
nlinml: 0, // 没有谱线
inactm: 0,
};
let result = molop_pure(&config, &model, &line_data, &freq, 1e-10, (&h0, &h1, &h2));
// 没有谱线时应该返回零
for i in 0..result.ablin.len() {
assert_relative_eq!(result.ablin[i], 0.0);
assert_relative_eq!(result.emlin[i], 0.0);
}
}
#[test]
fn test_molop_inactive_list() {
let (h0, h1, h2) = create_test_voigt_tables();
let config = MolopConfig {
tmolim: 10000.0,
dfrcon: 1.0,
};
let mol_data = vec![1e10, 1e10, 1e10];
let dop_data = vec![1e-10, 1e-10, 1e-10];
let model = MolopModelState {
temp: 5000.0,
elec: 1e12,
mol: MolModelState {
rrmol: &mol_data,
dopmol: &dop_data,
},
plan: 1.0,
stim: 1.0,
};
let freq_data = vec![0.0; 100];
let freq = MolopFreqParams {
freq: &freq_data,
nfreq: 100,
nfreqs: 100,
};
let gfm = vec![];
let exclm = vec![];
let freqm = vec![];
let grm = vec![];
let gsm = vec![];
let gvdw = vec![];
let indatm = vec![];
let inmlin = vec![];
let ijcmtr = vec![];
let line_data = MolLineData {
freqm: &freqm,
exclm: &exclm,
gfm: &gfm,
grm: &grm,
gsm: &gsm,
gvdw: &gvdw,
indatm: &indatm,
inmlin: &inmlin,
ijcmtr: &ijcmtr,
nlinml: 10,
inactm: 1, // 不激活
};
let result = molop_pure(&config, &model, &line_data, &freq, 1e-10, (&h0, &h1, &h2));
// 不激活时应该返回零
for i in 0..result.ablin.len() {
assert_relative_eq!(result.ablin[i], 0.0);
assert_relative_eq!(result.emlin[i], 0.0);
}
}
#[test]
fn test_molop_basic_calculation() {
let (h0, h1, h2) = create_test_voigt_tables();
let config = MolopConfig {
tmolim: 10000.0,
dfrcon: 1.0,
};
// 设置一条简单的分子谱线
let mol_data = vec![1e10]; // 分子数密度
let dop_data = vec![0.1]; // 多普勒宽度
let model = MolopModelState {
temp: 5000.0,
elec: 1e12,
mol: MolModelState {
rrmol: &mol_data,
dopmol: &dop_data,
},
plan: 2.0,
stim: 1.0,
};
// 频率网格:中心在 1e15 Hz
let nfreq = 100;
let freq_data: Vec<f64> = (0..nfreq).map(|i| 1e15 + (i as f64 - 50.0) * 1e11).collect();
let freq = MolopFreqParams {
freq: &freq_data,
nfreq,
nfreqs: nfreq,
};
// 一条分子谱线
let gfm = vec![0.0]; // log gf = 0
let exclm = vec![0.0]; // 激发能 = 0
let freqm = vec![1e15]; // 中心频率
let grm = vec![0.1]; // 辐射加宽
let gsm = vec![0.01]; // Stark 加宽
let gvdw = vec![0.05]; // van der Waals 加宽
let indatm = vec![0]; // 分子索引
let inmlin = vec![0]; // 谱线索引
let ijcmtr = vec![50]; // 中心频率索引
let line_data = MolLineData {
freqm: &freqm,
exclm: &exclm,
gfm: &gfm,
grm: &grm,
gsm: &gsm,
gvdw: &gvdw,
indatm: &indatm,
inmlin: &inmlin,
ijcmtr: &ijcmtr,
nlinml: 1,
inactm: 0,
};
let result = molop_pure(&config, &model, &line_data, &freq, 1e-10, (&h0, &h1, &h2));
// 检查中心附近有不透明度
let center_idx = 50;
assert!(result.ablin[center_idx] > 0.0, "中心应该有不透明度");
// 检查发射率与不透明度的关系
for i in 3..nfreq {
if result.ablin[i] > 0.0 {
assert_relative_eq!(result.emlin[i], result.ablin[i] * model.plan, epsilon = 1e-10);
}
}
}
}