469 lines
12 KiB
Rust
469 lines
12 KiB
Rust
//! 分子谱线不透明度和发射率计算。
|
||
//!
|
||
//! 重构自 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);
|
||
}
|
||
}
|
||
}
|
||
}
|