564 lines
17 KiB
Rust
564 lines
17 KiB
Rust
//! 所有深度点的吸收、发射和散射系数计算 (含离子贡献)。
|
||
//!
|
||
//! 重构自 TLUSTY `opacfa.f`
|
||
//!
|
||
//! 对于给定频率点,计算所有深度点的吸收、发射和散射系数,
|
||
//! 并保存每个离子的贡献(用于计算冷却和加热率)。
|
||
//!
|
||
//! # 算法流程
|
||
//!
|
||
//! 1. 初始化电子散射贡献
|
||
//! 2. 计算频率和深度相关的基础量 (XKF, XKFB 等)
|
||
//! 3. 计算束缚-自由 (bound-free) 贡献
|
||
//! 4. 计算自由-自由 (free-free) 贡献
|
||
//! 5. 计算附加不透明度 (OPADD)
|
||
//! 6. 计算谱线贡献 (如果 icoolp != 0)
|
||
//! 7. 最终不透明度计算
|
||
|
||
use crate::tlusty::state::constants::{HK, UN};
|
||
// f2r_depends: DWNFR1, OPADD, PRD, SGMER1
|
||
|
||
// 物理常数
|
||
const C14: f64 = 2.99793e14;
|
||
const CFF1: f64 = 1.3727e-25;
|
||
|
||
// ============================================================================
|
||
// 参数结构体
|
||
// ============================================================================
|
||
|
||
/// OPACFA 输入参数
|
||
#[derive(Debug)]
|
||
pub struct OpacfaParams<'a> {
|
||
/// 频率索引 (1-indexed)
|
||
pub ij: usize,
|
||
|
||
// 控制参数
|
||
/// Compton 散射标志 (>0: 计算)
|
||
pub icompt: i32,
|
||
/// 冷却率标志 (0: 跳过谱线贡献)
|
||
pub icoolp: i32,
|
||
/// ODF 采样标志 (0: 标准模式, >0: ODF 采样)
|
||
pub ispodf: i32,
|
||
/// 双电子复合标志 (0: 无, >0: 有)
|
||
pub ifdiel: i32,
|
||
/// 附加不透明度标志 (0: 无, !=0: 有)
|
||
pub iopadd: i32,
|
||
/// PRD 标志 (>0: 调用 PRD)
|
||
pub ifprd: i32,
|
||
/// 密度缩放标志 (0: 已缩放, >0: 需缩放)
|
||
pub izscal: i32,
|
||
|
||
// 频率数据
|
||
/// 频率数组 (nfreq)
|
||
pub freq: &'a [f64],
|
||
/// Planck 函数 (nfreq)
|
||
pub bnue: &'a [f64],
|
||
|
||
// 深度数据
|
||
/// 深度点数
|
||
pub nd: usize,
|
||
/// 温度 (nd)
|
||
pub temp: &'a [f64],
|
||
/// 电子密度 (nd)
|
||
pub elec: &'a [f64],
|
||
/// 密度倒数 (nd) - 用于 izscal > 0 时
|
||
pub dens1: &'a [f64],
|
||
/// 电子散射系数 (nd) - 输入/输出
|
||
pub elscat: &'a [f64],
|
||
/// 电子散射截面 (nfreq)
|
||
pub sigec: &'a [f64],
|
||
|
||
// 表格频率阈值
|
||
/// 表格最大频率
|
||
pub frtabm: f64,
|
||
|
||
// 工作数组 (输入/输出)
|
||
/// HKT1 (nd) - HK/T
|
||
pub hkt1: &'a mut [f64],
|
||
/// XKF (nd)
|
||
pub xkf: &'a mut [f64],
|
||
/// XKF1 (nd)
|
||
pub xkf1: &'a mut [f64],
|
||
/// XKFB (nd)
|
||
pub xkfb: &'a mut [f64],
|
||
}
|
||
|
||
/// OPACFA 输出状态
|
||
#[derive(Debug)]
|
||
pub struct OpacfaOutput<'a> {
|
||
/// 吸收系数 (nd)
|
||
pub abso1: &'a mut [f64],
|
||
/// 发射系数 (nd)
|
||
pub emis1: &'a mut [f64],
|
||
/// 散射系数 (nd)
|
||
pub scat1: &'a mut [f64],
|
||
/// 累积吸收系数 (nd)
|
||
pub absot: &'a mut [f64],
|
||
/// 连续谱吸收系数 (nd) - 不含谱线
|
||
pub absoc1: &'a mut [f64],
|
||
/// 连续谱发射系数 (nd) - 不含谱线
|
||
pub emisc1: &'a mut [f64],
|
||
|
||
/// 离子吸收贡献 (mion × nd)
|
||
pub absoti: &'a mut [f64],
|
||
/// 离子发射贡献 (mion × nd)
|
||
pub emisti: &'a mut [f64],
|
||
}
|
||
|
||
/// OPACFA 配置结构体 - 包含所有需要的状态数据
|
||
#[derive(Debug)]
|
||
pub struct OpacfaState<'a> {
|
||
/// 离子数
|
||
pub nion: usize,
|
||
/// 束缚-自由跃迁数
|
||
pub ntranc: usize,
|
||
|
||
// 跃迁相关
|
||
/// 束缚-自由跃迁索引 (ntranc), 1-indexed
|
||
pub itrbf: &'a [i32],
|
||
/// 低能级索引 (mtrans), 1-indexed
|
||
pub ilow: &'a [i32],
|
||
/// 高能级索引 (mtrans), 1-indexed
|
||
pub iup: &'a [i32],
|
||
/// 频率阈值索引 (mtrans), 1-indexed
|
||
pub ifr0: &'a [i32],
|
||
/// 频率终点索引 (mtrans), 1-indexed
|
||
pub ifr1: &'a [i32],
|
||
/// Macfarlane 下沉修正索引 (mtrans), <= 0 表示无
|
||
pub mcdw: &'a [i32],
|
||
/// 阈值频率 (mtrans)
|
||
pub fr0: &'a [f64],
|
||
/// Mermerges 处理标志 (mlevel), < 0 表示需要特殊处理
|
||
pub ifwop: &'a [i32],
|
||
/// Mermerges 索引 (mlevel)
|
||
pub imrg: &'a [i32],
|
||
|
||
// 离子相关
|
||
/// 离子对应的下一个能级索引 (mion), 1-indexed
|
||
pub nnext: &'a [i32],
|
||
/// 自由-自由阈值频率 (mion)
|
||
pub ff: &'a [f64],
|
||
/// 电荷² (mion)
|
||
pub charg2: &'a [i32],
|
||
/// 自由-自由系数 SFF2 (mion × nd)
|
||
pub sff2: &'a [f64],
|
||
/// 自由-自由系数 SFF3 (mion × nd)
|
||
pub sff3: &'a [f64],
|
||
/// 自由-自由类型 (mion): 1=氢型(Gaunt=1), 2=精确Gaunt, 3=H⁻, <0=特殊
|
||
pub itype_ff: &'a [i32],
|
||
|
||
// 能级相关
|
||
/// 能级对应的元素索引 (mlevel), 1-indexed
|
||
pub iel: &'a [i32],
|
||
/// 原子操作标志 (matom), 0=正常, >0=特殊
|
||
pub iadop: &'a [i32],
|
||
/// 能级对应的原子索引 (mlevel), 1-indexed
|
||
pub iatm: &'a [i32],
|
||
|
||
// 跃迁吸收/发射系数
|
||
/// 吸收系数 (mtrans × nd)
|
||
pub abtra: &'a [f64],
|
||
/// 发射系数 (mtrans × nd)
|
||
pub emtra: &'a [f64],
|
||
|
||
// 谱线相关
|
||
/// 主谱线索引 (nfreq), 0 表示无, 1-indexed
|
||
pub ijlin: &'a [i32],
|
||
/// 重叠谱线数 (nfreq)
|
||
pub nlines: &'a [i32],
|
||
/// 谱线展开标志 (mtrans), true=展开
|
||
pub linexp: &'a [bool],
|
||
/// 谱线轮廓 (nd × nfreql 或 nd × nfreq)
|
||
pub prflin: &'a [f64],
|
||
|
||
// 截面数据
|
||
/// 束缚-自由截面 (mcross × nfreq)
|
||
pub cross_bf: &'a [f64],
|
||
/// 双电子复合截面 (mcross × nfreq × nd)
|
||
pub cross_di: &'a [f64],
|
||
}
|
||
|
||
// ============================================================================
|
||
// 主函数
|
||
// ============================================================================
|
||
|
||
/// 计算所有深度点的吸收、发射和散射系数。
|
||
///
|
||
/// 这是 OPACFA 的简化版本,只实现核心逻辑框架。
|
||
/// 完整实现需要传入更多状态参数。
|
||
///
|
||
/// # 参数
|
||
///
|
||
/// * `params` - 基本输入参数
|
||
/// * `output` - 输出数组
|
||
pub fn opacfa(params: &mut OpacfaParams, output: &mut OpacfaOutput) {
|
||
let ij = params.ij;
|
||
let ij_idx = ij - 1; // 转换为 0-indexed
|
||
let nd = params.nd;
|
||
|
||
// ========================================================================
|
||
// 1. 初始化
|
||
// ========================================================================
|
||
|
||
// Compton 散射初始化
|
||
if params.icompt > 0 {
|
||
for id in 0..nd {
|
||
let sigec_val = if ij_idx < params.sigec.len() {
|
||
params.sigec[ij_idx]
|
||
} else {
|
||
0.0
|
||
};
|
||
// ELSCAT(ID) = ELEC(ID) * SIGEC(IJ)
|
||
// 注意: elscat 是输入,这里只是使用它
|
||
}
|
||
}
|
||
|
||
// 初始化输出数组
|
||
for id in 0..nd {
|
||
output.abso1[id] = params.elscat[id];
|
||
output.emis1[id] = 0.0;
|
||
output.scat1[id] = params.elscat[id];
|
||
output.absoc1[id] = output.abso1[id];
|
||
output.emisc1[id] = 0.0;
|
||
|
||
// 初始化离子贡献
|
||
for ion in 0..(output.absoti.len() / nd) {
|
||
let idx = ion * nd + id;
|
||
output.absoti[idx] = 0.0;
|
||
output.emisti[idx] = 0.0;
|
||
}
|
||
}
|
||
|
||
// ========================================================================
|
||
// 2. 计算频率和深度相关的基础量
|
||
// ========================================================================
|
||
|
||
let fr = if ij_idx < params.freq.len() {
|
||
params.freq[ij_idx]
|
||
} else {
|
||
return; // 频率索引越界
|
||
};
|
||
|
||
let frinv = UN / fr;
|
||
let fr3inv = frinv * frinv * frinv;
|
||
let lfre = fr > params.frtabm;
|
||
|
||
for id in 0..nd {
|
||
params.hkt1[id] = HK / params.temp[id];
|
||
params.xkf[id] = (-params.hkt1[id] * fr).exp();
|
||
params.xkf1[id] = UN - params.xkf[id];
|
||
params.xkfb[id] = params.xkf[id] * params.bnue[ij_idx];
|
||
}
|
||
|
||
// ========================================================================
|
||
// 3. 束缚-自由贡献 (简化版)
|
||
// ========================================================================
|
||
// 完整实现需要:
|
||
// - 遍历 NTRANC 个束缚-自由跃迁
|
||
// - 调用 CROSS 或 CROSSD 获取截面
|
||
// - 调用 DWNFR1 处理 Macfarlane 下沉修正
|
||
// - 调用 SGMER1 处理 Mermerges 能级
|
||
// 此处留作框架,实际计算在完整版本中实现
|
||
|
||
// ========================================================================
|
||
// 4. 自由-自由贡献 (简化版)
|
||
// ========================================================================
|
||
// 完整实现需要:
|
||
// - 遍历 NION 个离子
|
||
// - 根据 ITYPE_FF 选择计算方式
|
||
// - 调用 SFFHMI (H⁻ 自由-自由)
|
||
// - 调用 FFCROS (特殊截面)
|
||
|
||
// ========================================================================
|
||
// 5. 附加不透明度 (OPADD)
|
||
// ========================================================================
|
||
// 完整实现需要调用 OPADD
|
||
|
||
// ========================================================================
|
||
// 6. 保存连续谱系数
|
||
// ========================================================================
|
||
|
||
for id in 0..nd {
|
||
output.absoc1[id] = output.abso1[id];
|
||
output.emisc1[id] = output.emis1[id];
|
||
}
|
||
|
||
// ========================================================================
|
||
// 7. 谱线贡献 (如果 icoolp != 0)
|
||
// ========================================================================
|
||
// 完整实现需要:
|
||
// - 主谱线处理
|
||
// - 重叠谱线处理
|
||
// - ODF 采样模式处理
|
||
|
||
if params.icoolp == 0 {
|
||
// 跳过谱线贡献
|
||
finalize_opacities(params, output, nd);
|
||
return;
|
||
}
|
||
|
||
// ========================================================================
|
||
// 8. 最终不透明度计算
|
||
// ========================================================================
|
||
|
||
finalize_opacities(params, output, nd);
|
||
}
|
||
|
||
/// 最终不透明度计算
|
||
fn finalize_opacities(
|
||
params: &OpacfaParams,
|
||
output: &mut OpacfaOutput,
|
||
nd: usize,
|
||
) {
|
||
let nion = output.absoti.len() / nd;
|
||
|
||
for id in 0..nd {
|
||
// 总不透明度 = 吸收 - 发射 × 激发因子
|
||
output.abso1[id] = output.abso1[id] - output.emis1[id] * params.xkf[id];
|
||
output.absoc1[id] = output.absoc1[id] - output.emisc1[id] * params.xkf[id];
|
||
|
||
// 离子贡献
|
||
for ion in 0..nion {
|
||
let idx = ion * nd + id;
|
||
output.absoti[idx] = output.absoti[idx] - output.emisti[idx] * params.xkf[id];
|
||
}
|
||
|
||
// 发射系数 × Planck 因子
|
||
output.emis1[id] = output.emis1[id] * params.xkfb[id];
|
||
output.emisc1[id] = output.emisc1[id] * params.xkfb[id];
|
||
|
||
for ion in 0..nion {
|
||
let idx = ion * nd + id;
|
||
output.emisti[idx] = output.emisti[idx] * params.xkfb[id];
|
||
}
|
||
|
||
// 累积吸收系数
|
||
output.absot[id] = output.abso1[id];
|
||
|
||
// 密度缩放
|
||
if params.izscal == 0 {
|
||
output.absot[id] = output.abso1[id] * params.dens1[id];
|
||
}
|
||
}
|
||
}
|
||
|
||
// ============================================================================
|
||
// 测试
|
||
// ============================================================================
|
||
|
||
#[cfg(test)]
|
||
mod tests {
|
||
use super::*;
|
||
use approx::assert_relative_eq;
|
||
|
||
fn create_test_params<'a>(
|
||
freq: &'a [f64],
|
||
bnue: &'a [f64],
|
||
temp: &'a [f64],
|
||
elec: &'a [f64],
|
||
dens1: &'a [f64],
|
||
elscat: &'a [f64],
|
||
sigec: &'a [f64],
|
||
hkt1: &'a mut [f64],
|
||
xkf: &'a mut [f64],
|
||
xkf1: &'a mut [f64],
|
||
xkfb: &'a mut [f64],
|
||
) -> OpacfaParams<'a> {
|
||
OpacfaParams {
|
||
ij: 3,
|
||
icompt: 0,
|
||
icoolp: 0,
|
||
ispodf: 0,
|
||
ifdiel: 0,
|
||
iopadd: 0,
|
||
ifprd: 0,
|
||
izscal: 1,
|
||
freq,
|
||
bnue,
|
||
nd: temp.len(),
|
||
temp,
|
||
elec,
|
||
dens1,
|
||
elscat,
|
||
sigec,
|
||
frtabm: 1e16,
|
||
hkt1,
|
||
xkf,
|
||
xkf1,
|
||
xkfb,
|
||
}
|
||
}
|
||
|
||
fn create_test_output<'a>(
|
||
abso1: &'a mut [f64],
|
||
emis1: &'a mut [f64],
|
||
scat1: &'a mut [f64],
|
||
absot: &'a mut [f64],
|
||
absoc1: &'a mut [f64],
|
||
emisc1: &'a mut [f64],
|
||
absoti: &'a mut [f64],
|
||
emisti: &'a mut [f64],
|
||
) -> OpacfaOutput<'a> {
|
||
OpacfaOutput {
|
||
abso1,
|
||
emis1,
|
||
scat1,
|
||
absot,
|
||
absoc1,
|
||
emisc1,
|
||
absoti,
|
||
emisti,
|
||
}
|
||
}
|
||
|
||
#[test]
|
||
fn test_opacfa_initialization() {
|
||
let nd = 3;
|
||
let nfreq = 5;
|
||
let nion = 2;
|
||
|
||
let freq = vec![1e14, 2e14, 3e14, 4e14, 5e14];
|
||
let bnue = vec![1e-10, 2e-10, 3e-10, 4e-10, 5e-10];
|
||
let temp = vec![5000.0, 6000.0, 7000.0];
|
||
let elec = vec![1e10, 2e10, 3e10];
|
||
let dens1 = vec![1e-15, 1e-15, 1e-15];
|
||
let elscat = vec![1e-20, 2e-20, 3e-20];
|
||
let sigec = vec![1e-24; nfreq];
|
||
|
||
let mut hkt1 = vec![0.0; nd];
|
||
let mut xkf = vec![0.0; nd];
|
||
let mut xkf1 = vec![0.0; nd];
|
||
let mut xkfb = vec![0.0; nd];
|
||
|
||
let mut abso1 = vec![0.0; nd];
|
||
let mut emis1 = vec![0.0; nd];
|
||
let mut scat1 = vec![0.0; nd];
|
||
let mut absot = vec![0.0; nd];
|
||
let mut absoc1 = vec![0.0; nd];
|
||
let mut emisc1 = vec![0.0; nd];
|
||
let mut absoti = vec![0.0; nion * nd];
|
||
let mut emisti = vec![0.0; nion * nd];
|
||
|
||
let mut params = create_test_params(
|
||
&freq, &bnue, &temp, &elec, &dens1, &elscat, &sigec,
|
||
&mut hkt1, &mut xkf, &mut xkf1, &mut xkfb,
|
||
);
|
||
|
||
let mut output = create_test_output(
|
||
&mut abso1, &mut emis1, &mut scat1, &mut absot,
|
||
&mut absoc1, &mut emisc1, &mut absoti, &mut emisti,
|
||
);
|
||
|
||
opacfa(&mut params, &mut output);
|
||
|
||
// 验证初始化
|
||
for id in 0..nd {
|
||
assert_relative_eq!(output.abso1[id], elscat[id], epsilon = 1e-30);
|
||
assert_relative_eq!(output.scat1[id], elscat[id], epsilon = 1e-30);
|
||
}
|
||
}
|
||
|
||
#[test]
|
||
fn test_opacfa_frequency_quantities() {
|
||
let nd = 2;
|
||
let nfreq = 3;
|
||
|
||
let freq = vec![1e15, 2e15, 3e15];
|
||
let bnue = vec![1e-10, 2e-10, 3e-10];
|
||
let temp = vec![5770.0, 6000.0];
|
||
let elec = vec![1e13, 2e13];
|
||
let dens1 = vec![1e-7, 1e-7];
|
||
let elscat = vec![1e-8, 2e-8];
|
||
let sigec = vec![1e-24; nfreq];
|
||
|
||
let mut hkt1 = vec![0.0; nd];
|
||
let mut xkf = vec![0.0; nd];
|
||
let mut xkf1 = vec![0.0; nd];
|
||
let mut xkfb = vec![0.0; nd];
|
||
|
||
let nion = 1;
|
||
let mut abso1 = vec![0.0; nd];
|
||
let mut emis1 = vec![0.0; nd];
|
||
let mut scat1 = vec![0.0; nd];
|
||
let mut absot = vec![0.0; nd];
|
||
let mut absoc1 = vec![0.0; nd];
|
||
let mut emisc1 = vec![0.0; nd];
|
||
let mut absoti = vec![0.0; nion * nd];
|
||
let mut emisti = vec![0.0; nion * nd];
|
||
|
||
let mut params = create_test_params(
|
||
&freq, &bnue, &temp, &elec, &dens1, &elscat, &sigec,
|
||
&mut hkt1, &mut xkf, &mut xkf1, &mut xkfb,
|
||
);
|
||
params.ij = 2; // 使用第二个频率点
|
||
|
||
let mut output = create_test_output(
|
||
&mut abso1, &mut emis1, &mut scat1, &mut absot,
|
||
&mut absoc1, &mut emisc1, &mut absoti, &mut emisti,
|
||
);
|
||
|
||
opacfa(&mut params, &mut output);
|
||
|
||
// 验证 HKT1 = HK / T
|
||
let hk = 6.626176e-27 / 1.380662e-16; // HK constant
|
||
for id in 0..nd {
|
||
let expected_hkt1 = hk / temp[id];
|
||
assert_relative_eq!(params.hkt1[id], expected_hkt1, epsilon = 1e-10);
|
||
}
|
||
|
||
// 验证 XKF1 = 1 - XKF
|
||
for id in 0..nd {
|
||
assert_relative_eq!(params.xkf1[id], 1.0 - params.xkf[id], epsilon = 1e-15);
|
||
}
|
||
}
|
||
|
||
#[test]
|
||
fn test_finalize_opacities() {
|
||
let nd = 2;
|
||
let nfreq = 3; // 需要至少3个频率点因为 ij=3
|
||
let nion = 1;
|
||
|
||
let freq = vec![1e15, 2e15, 3e15];
|
||
let bnue = vec![1e-10, 2e-10, 3e-10];
|
||
let temp = vec![5770.0, 6000.0];
|
||
let elec = vec![1e13, 2e13];
|
||
let dens1 = vec![1e-7, 1e-7];
|
||
let elscat = vec![1e-8, 2e-8];
|
||
let sigec = vec![1e-24; nfreq];
|
||
|
||
let mut hkt1 = vec![0.0; nd];
|
||
let mut xkf = vec![0.0; nd];
|
||
let mut xkf1 = vec![0.0; nd];
|
||
let mut xkfb = vec![0.0; nd];
|
||
|
||
let mut abso1 = vec![0.0; nd];
|
||
let mut emis1 = vec![0.0; nd];
|
||
let mut scat1 = vec![0.0; nd];
|
||
let mut absot = vec![0.0; nd];
|
||
let mut absoc1 = vec![0.0; nd];
|
||
let mut emisc1 = vec![0.0; nd];
|
||
let mut absoti = vec![0.0; nion * nd];
|
||
let mut emisti = vec![0.0; nion * nd];
|
||
|
||
let mut params = create_test_params(
|
||
&freq, &bnue, &temp, &elec, &dens1, &elscat, &sigec,
|
||
&mut hkt1, &mut xkf, &mut xkf1, &mut xkfb,
|
||
);
|
||
params.izscal = 1; // 不进行密度缩放
|
||
|
||
let mut output = create_test_output(
|
||
&mut abso1, &mut emis1, &mut scat1, &mut absot,
|
||
&mut absoc1, &mut emisc1, &mut absoti, &mut emisti,
|
||
);
|
||
|
||
opacfa(&mut params, &mut output);
|
||
|
||
// 验证最终计算: absot = abso1 (izscal = 1)
|
||
// 由于 emis1 = 0,所以 abso1 = abso1 - 0 * xkf = 初始值 = elscat
|
||
for id in 0..nd {
|
||
assert_relative_eq!(output.absot[id], output.abso1[id], epsilon = 1e-30);
|
||
assert_relative_eq!(output.abso1[id], elscat[id], epsilon = 1e-30);
|
||
}
|
||
}
|
||
}
|