SpectraRust/src/tlusty/math/continuum/opacfa.rs
2026-04-04 09:36:14 +08:00

643 lines
23 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.

//! 所有深度点的吸收、发射和散射系数计算 (含离子贡献)。
//!
//! 重构自 TLUSTY `opacfa.f`
//!
//! 对于给定频率点 IJ计算所有深度点的吸收、发射和散射系数
//! 并保存每个离子的贡献(用于计算冷却和加热率)。
//!
//! # 算法流程
//!
//! 1. 初始化电子散射贡献
//! 2. 计算频率和深度相关的基础量 (XKF, XKFB 等)
//! 3. 计算束缚-自由 (bound-free) 贡献 (DWNFR1, SGMER1)
//! 4. 计算自由-自由 (free-free) 贡献 (GFREE1, SFFHMI, FFCROS)
//! 5. 计算附加不透明度 (OPADD)
//! 6. 保存连续谱系数
//! 7. 计算谱线贡献 (如果 icoolp != 0)
//! 8. 最终不透明度计算
//! 9. PRD 修正
// f2r_depends: DWNFR1, OPADD, PRD, SGMER1
use crate::tlusty::state::constants::UN;
use crate::tlusty::state::{GffPar, DwnPar, InpPar};
use crate::tlusty::math::opacity::dwnfr1;
use crate::tlusty::math::sgmer1_simple;
use crate::tlusty::math::gfree1;
use crate::tlusty::math::sffhmi;
use crate::tlusty::math::ffcros;
// 物理常数
const C14: f64 = 2.99793e14;
// ============================================================================
// 回调接口
// ============================================================================
/// OPADD 回调结果
#[derive(Debug, Clone, Default)]
pub struct OpaddResult {
pub abad: f64,
pub emad: f64,
pub scad: f64,
}
/// OPACFA 回调 trait - 用于 OPADD 和 PRD 外部调用
pub trait OpacfaCallbacks {
fn call_opadd(&mut self, mode: i32, icall: i32, ij: usize, id: usize) -> OpaddResult;
fn call_prd(&mut self, ij: usize);
}
/// 空回调实现
#[derive(Debug, Clone, Default)]
pub struct NoOpCallbacks;
impl OpacfaCallbacks for NoOpCallbacks {
fn call_opadd(&mut self, _mode: i32, _icall: i32, _ij: usize, _id: usize) -> OpaddResult {
OpaddResult::default()
}
fn call_prd(&mut self, _ij: usize) {}
}
// ============================================================================
// 参数结构体
// ============================================================================
/// OPACFA 配置参数
#[derive(Debug, Clone)]
pub struct OpacfaConfig {
pub icompt: i32,
pub icoolp: i32,
pub ispodf: i32,
pub ifdiel: i32,
pub iopadd: i32,
pub ifprd: i32,
pub izscal: i32,
pub frtabm: f64,
}
impl Default for OpacfaConfig {
fn default() -> Self {
Self {
icompt: 0, icoolp: 0, ispodf: 0, ifdiel: 0,
iopadd: 0, ifprd: 0, izscal: 1, frtabm: 1e16,
}
}
}
/// OPACFA 外部依赖上下文
pub struct OpacfaContext<'a> {
pub inppar: &'a InpPar,
pub dwnpar: &'a DwnPar,
pub gffpar: &'a GffPar,
pub frch: &'a [f64],
pub sgmsum: &'a [Vec<Vec<f64>>],
pub nlmx: usize,
}
/// OPACFA 模型数据 (深度相关)
pub struct OpacfaModelData<'a> {
pub nd: usize,
pub nfreq: usize,
pub freq: &'a [f64],
pub bnue: &'a [f64],
pub temp: &'a [f64],
pub elec: &'a [f64],
pub dens1: &'a [f64],
pub popul: &'a [f64],
pub elscat: &'a mut [f64],
pub sigec: &'a [f64],
pub hkt1: &'a mut [f64],
pub xkf: &'a mut [f64],
pub xkf1: &'a mut [f64],
pub xkfb: &'a mut [f64],
}
/// OPACFA 原子数据
pub struct OpacfaAtomicData<'a> {
pub nion: usize,
pub ntranc: usize,
pub itrbf: &'a [i32],
pub ilow: &'a [i32],
pub iup: &'a [i32],
pub ifr0: &'a [i32],
pub ifr1: &'a [i32],
pub mcdw: &'a [i32],
pub fr0: &'a [f64],
pub ifwop: &'a [i32],
pub imrg: &'a [i32],
pub itra: &'a [i32],
pub nnext: &'a [i32],
pub nfirst: &'a [i32],
pub ff: &'a [f64],
pub charg2: &'a [i32],
pub iz: &'a [i32],
pub ielh: i32,
pub iel: &'a [i32],
pub iadop: &'a [i32],
pub iatm: &'a [i32],
pub abtra: &'a [f64],
pub emtra: &'a [f64],
pub sff2: &'a [f64],
pub sff3: &'a [f64],
pub cross_bf: &'a [f64],
pub cross_di: &'a [f64],
pub ijlin: &'a [i32],
pub nlines: &'a [i32],
pub itrlin: &'a [i32],
pub linexp: &'a [bool],
pub prflin: &'a [f64],
pub indexp: &'a [i32],
pub kfr0: &'a [i32],
pub xjid: &'a [f64],
pub jidi: &'a [i32],
pub sigfe: &'a [f64],
pub dwf1: &'a mut [f64],
pub sgmg: &'a mut [f64],
}
impl OpacfaAtomicData<'_> {
pub fn empty<'a>() -> OpacfaAtomicData<'a> {
OpacfaAtomicData {
nion: 0, ntranc: 0,
itrbf: &[], ilow: &[], iup: &[], ifr0: &[], ifr1: &[],
mcdw: &[], fr0: &[], ifwop: &[], imrg: &[], itra: &[],
nnext: &[], nfirst: &[], ff: &[], charg2: &[], iz: &[],
ielh: 0, iel: &[], iadop: &[], iatm: &[],
abtra: &[], emtra: &[], sff2: &[], sff3: &[],
cross_bf: &[], cross_di: &[], ijlin: &[], nlines: &[],
itrlin: &[], linexp: &[], prflin: &[],
indexp: &[], kfr0: &[], xjid: &[], jidi: &[], sigfe: &[],
dwf1: &mut [], sgmg: &mut [],
}
}
}
/// OPACFA 输出
pub struct OpacfaOutput<'a> {
pub abso1: &'a mut [f64],
pub emis1: &'a mut [f64],
pub scat1: &'a mut [f64],
pub absot: &'a mut [f64],
pub absoc1: &'a mut [f64],
pub emisc1: &'a mut [f64],
pub absoti: &'a mut [f64],
pub emisti: &'a mut [f64],
}
// ============================================================================
// 主函数
// ============================================================================
/// 计算给定频率点 IJ 下所有深度点的吸收、发射和散射系数。
pub fn opacfa<C: OpacfaCallbacks>(
ij: usize,
config: &OpacfaConfig,
model: &mut OpacfaModelData,
atomic: &mut OpacfaAtomicData,
output: &mut OpacfaOutput,
context: &OpacfaContext,
callbacks: &mut C,
) {
let ij_idx = ij - 1; // 0-indexed
let nd = model.nd;
let nfreq = model.nfreq;
// ========================================================================
// 1. 初始化
// ========================================================================
if config.icompt > 0 {
for id in 0..nd {
model.elscat[id] = model.elec[id] * model.sigec[ij_idx];
}
}
for id in 0..nd {
output.abso1[id] = model.elscat[id];
output.emis1[id] = 0.0;
output.scat1[id] = model.elscat[id];
output.absoc1[id] = output.abso1[id];
output.emisc1[id] = 0.0;
let nion = output.absoti.len() / nd;
for ion in 0..nion {
output.absoti[ion * nd + id] = 0.0;
output.emisti[ion * nd + id] = 0.0;
}
}
// ========================================================================
// 2. 频率和深度相关的基础量
// ========================================================================
let fr = model.freq[ij_idx];
let frinv = UN / fr;
let fr3inv = frinv * frinv * frinv;
let lfre = fr > config.frtabm;
for id in 0..nd {
model.hkt1[id] = 6.626176e-27 / 1.380662e-16 / model.temp[id];
model.xkf[id] = (-model.hkt1[id] * fr).exp();
model.xkf1[id] = UN - model.xkf[id];
model.xkfb[id] = model.xkf[id] * model.bnue[ij_idx];
}
// ========================================================================
// 3. 束缚-自由贡献
// ========================================================================
if config.ifdiel == 0 {
bound_free_no_diel(ij_idx, nd, nfreq, fr, frinv, fr3inv, lfre, atomic, output, context);
} else {
bound_free_with_diel(ij_idx, nd, nfreq, fr, frinv, fr3inv, lfre, atomic, output, context);
}
// ========================================================================
// 4. 自由-自由贡献
// ========================================================================
free_free(nd, nfreq, fr, fr3inv, lfre, atomic, model, output, context);
// ========================================================================
// 5. 附加不透明度 (OPADD)
// ========================================================================
if config.iopadd != 0 {
let icall: i32 = 1;
for id in 0..nd {
let r = callbacks.call_opadd(0, icall, ij, id + 1);
output.abso1[id] += r.abad;
output.emis1[id] += r.emad;
output.scat1[id] += r.scad;
let ielh = atomic.ielh as usize;
if ielh > 0 && ielh - 1 < output.absoti.len() / nd {
output.absoti[(ielh - 1) * nd + id] += r.abad;
output.emisti[(ielh - 1) * nd + id] += r.emad;
}
}
}
// ========================================================================
// 6. 保存连续谱系数
// ========================================================================
for id in 0..nd {
output.absoc1[id] = output.abso1[id];
output.emisc1[id] = output.emis1[id];
}
if config.icoolp == 0 {
finalize_opacities(config.izscal, model, output, nd);
return;
}
// ========================================================================
// 7. 谱线贡献
// ========================================================================
if config.ispodf == 0 {
line_contributions_standard(ij_idx, nd, nfreq, fr, lfre, atomic, model, output);
} else {
line_contributions_odf(ij_idx, nd, nfreq, lfre, atomic, model, output);
}
// ========================================================================
// 8. 最终不透明度计算
// ========================================================================
finalize_opacities(config.izscal, model, output, nd);
// ========================================================================
// 9. PRD 修正
// ========================================================================
if config.ifprd > 0 {
callbacks.call_prd(ij);
}
}
// ============================================================================
// 束缚-自由 (无双电子复合): SG=CROSS(IBFT,IJ)
// ============================================================================
fn bound_free_no_diel(
ij_idx: usize, nd: usize, nfreq: usize,
fr: f64, frinv: f64, fr3inv: f64, lfre: bool,
atomic: &mut OpacfaAtomicData, output: &mut OpacfaOutput, context: &OpacfaContext,
) {
for ibft in 0..atomic.ntranc {
let itr = (atomic.itrbf[ibft] - 1) as usize;
let sg = atomic.cross_bf[ibft * nfreq + ij_idx];
let ii = (atomic.ilow[itr] - 1) as usize;
let iad = if ii < atomic.iatm.len() {
atomic.iadop[(atomic.iatm[ii] - 1) as usize]
} else { 0 };
if !(sg > 0.0 && (iad == 0 || (iad > 0 && lfre))) { continue; }
let izz = (atomic.iz[ii] - 1) as usize;
let imer = (atomic.imrg[ii] - 1) as usize;
for id in 0..nd {
let mut sgd = sg;
if atomic.mcdw[itr] > 0 {
let mcdw_idx = (atomic.mcdw[itr] - 1) as usize;
let dw1 = dwnfr1(fr, atomic.fr0[itr], id, izz, context.inppar, context.dwnpar);
atomic.dwf1[mcdw_idx * nd + id] = dw1;
sgd = sg * dw1;
}
if atomic.ifwop[ii] < 0 {
let sgme1 = sgmer1_simple(frinv, fr3inv, imer, id,
context.frch, context.sgmsum, context.nlmx);
atomic.sgmg[imer * nd + id] = sgme1;
sgd = sgme1;
}
let emisbf = sgd * atomic.emtra[itr * nd + id];
output.abso1[id] += sgd * atomic.abtra[itr * nd + id];
output.emis1[id] += emisbf;
let ion = (atomic.iel[ii] - 1) as usize;
if ion < output.absoti.len() / nd {
output.absoti[ion * nd + id] += sgd * atomic.abtra[itr * nd + id];
output.emisti[ion * nd + id] += emisbf;
}
}
}
}
// ============================================================================
// 束缚-自由 (有双电子复合): SG=CROSSD(IBFT,IJ,ID)
// ============================================================================
fn bound_free_with_diel(
ij_idx: usize, nd: usize, nfreq: usize,
fr: f64, frinv: f64, fr3inv: f64, lfre: bool,
atomic: &mut OpacfaAtomicData, output: &mut OpacfaOutput, context: &OpacfaContext,
) {
for ibft in 0..atomic.ntranc {
let itr = (atomic.itrbf[ibft] - 1) as usize;
let ii = (atomic.ilow[itr] - 1) as usize;
let iad = if ii < atomic.iatm.len() {
atomic.iadop[(atomic.iatm[ii] - 1) as usize]
} else { 0 };
if !(iad == 0 || (iad > 0 && lfre)) { continue; }
let izz = (atomic.iz[ii] - 1) as usize;
let imer = (atomic.imrg[ii] - 1) as usize;
for id in 0..nd {
let sg = atomic.cross_di[ibft * nfreq * nd + ij_idx * nd + id];
if sg <= 0.0 { continue; }
let mut sgd = sg;
if atomic.mcdw[itr] > 0 {
let mcdw_idx = (atomic.mcdw[itr] - 1) as usize;
let dw1 = dwnfr1(fr, atomic.fr0[itr], id, izz, context.inppar, context.dwnpar);
atomic.dwf1[mcdw_idx * nd + id] = dw1;
sgd = sg * dw1;
}
if atomic.ifwop[ii] < 0 {
let sgme1 = sgmer1_simple(frinv, fr3inv, imer, id,
context.frch, context.sgmsum, context.nlmx);
atomic.sgmg[imer * nd + id] = sgme1;
sgd = sgme1;
}
let emisbf = sgd * atomic.emtra[itr * nd + id];
output.abso1[id] += sgd * atomic.abtra[itr * nd + id];
output.emis1[id] += emisbf;
let ion = (atomic.iel[ii] - 1) as usize;
if ion < output.absoti.len() / nd {
output.absoti[ion * nd + id] += sgd * atomic.abtra[itr * nd + id];
output.emisti[ion * nd + id] += emisbf;
}
}
}
}
// ============================================================================
// 自由-自由贡献
// ============================================================================
fn free_free(
nd: usize, nfreq: usize, fr: f64, fr3inv: f64, lfre: bool,
atomic: &mut OpacfaAtomicData, model: &OpacfaModelData,
output: &mut OpacfaOutput, context: &OpacfaContext,
) {
for ion in 0..atomic.nion {
let nnext_1 = atomic.nnext[ion] as usize; // 1-indexed → usize
let itra_dim = atomic.nion.max(1); // ITRA 维度估计
let it = if nnext_1 > 0 && (nnext_1 - 1) < itra_dim {
atomic.itra[(nnext_1 - 1) * itra_dim + (nnext_1 - 1)]
} else { 0 };
let nnext_0 = nnext_1 - 1; // 0-indexed
let iad = if nnext_0 < atomic.iatm.len() && nnext_0 > 0 {
atomic.iadop[(atomic.iatm[nnext_0] - 1) as usize]
} else { 0 };
if iad > 0 && !lfre { continue; }
match it {
1 => { // 氢型, Gaunt=1
for id in 0..nd {
let sf1 = atomic.sff3[ion * nd + id] * fr3inv;
let mut sf2 = atomic.sff2[ion * nd + id];
if fr < atomic.ff[ion] { sf2 = UN / model.xkf[id]; }
let absoff = sf1 * sf2;
output.abso1[id] += absoff;
output.emis1[id] += absoff;
add_ion(ion, id, nd, absoff, absoff, output);
}
}
2 => { // 氢型, 精确 Gaunt
for id in 0..nd {
let sf1 = atomic.sff3[ion * nd + id] * fr3inv;
let mut sf2 = atomic.sff2[ion * nd + id];
if fr < atomic.ff[ion] { sf2 = UN / model.xkf[id]; }
let x = C14 * (atomic.charg2[ion] as f64) / fr;
sf2 = sf2 - UN + gfree1(id, x, context.gffpar);
let absoff = sf1 * sf2;
output.abso1[id] += absoff;
output.emis1[id] += absoff;
add_ion(ion, id, nd, absoff, absoff, output);
}
}
3 => { // H⁻ 自由-自由
let ielh = atomic.ielh as usize;
if ielh > 0 && ielh - 1 < atomic.nfirst.len() {
let nfirst_elh = (atomic.nfirst[ielh - 1] - 1) as usize;
for id in 0..nd {
let popi = model.popul[nfirst_elh * nd + id];
let absoff = sffhmi(popi, fr, model.temp[id]) * model.elec[id];
output.abso1[id] += absoff;
output.emis1[id] += absoff;
add_ion(ion, id, nd, absoff, absoff, output);
}
}
}
it if it < 0 => { // 特殊截面
for id in 0..nd {
let absoff = ffcros(ion as i32, it, model.temp[id], fr)
* model.popul[nnext_0 * nd + id] * model.elec[id];
output.abso1[id] += absoff;
output.emis1[id] += absoff;
add_ion(ion, id, nd, absoff, absoff, output);
}
}
_ => {}
}
}
}
/// 添加离子贡献 (内联辅助)
#[inline(always)]
fn add_ion(ion: usize, id: usize, nd: usize, abs_val: f64, emis_val: f64, output: &mut OpacfaOutput) {
if ion < output.absoti.len() / nd {
let idx = ion * nd + id;
output.absoti[idx] += abs_val;
output.emisti[idx] += emis_val;
}
}
// ============================================================================
// 谱线贡献 (标准模式, ISPODF=0)
// ============================================================================
fn line_contributions_standard(
ij_idx: usize, nd: usize, nfreq: usize, fr: f64, lfre: bool,
atomic: &mut OpacfaAtomicData, model: &OpacfaModelData, output: &mut OpacfaOutput,
) {
// 主谱线
if atomic.ijlin[ij_idx] > 0 {
let itr = (atomic.ijlin[ij_idx] - 1) as usize;
let ilow_itr = (atomic.ilow[itr] - 1) as usize;
let iad = if ilow_itr < atomic.iatm.len() {
atomic.iadop[(atomic.iatm[ilow_itr] - 1) as usize]
} else { 0 };
if iad == 0 || (lfre && iad > 0) {
let ion = (atomic.iel[ilow_itr] - 1) as usize;
for id in 0..nd {
let sg = atomic.prflin[id * nfreq + ij_idx];
output.abso1[id] += sg * atomic.abtra[itr * nd + id];
output.emis1[id] += sg * atomic.emtra[itr * nd + id];
add_ion(ion, id, nd, sg * atomic.abtra[itr * nd + id],
sg * atomic.emtra[itr * nd + id], output);
}
}
}
if atomic.nlines[ij_idx] <= 0 { return; }
let nlines_ij = atomic.nlines[ij_idx] as usize;
for ilint in 0..nlines_ij {
let itr = (atomic.itrlin[ilint * nfreq + ij_idx] - 1) as usize;
let ilow_itr = (atomic.ilow[itr] - 1) as usize;
let iad = if ilow_itr < atomic.iatm.len() {
atomic.iadop[(atomic.iatm[ilow_itr] - 1) as usize]
} else { 0 };
if iad > 0 && !lfre { continue; }
if atomic.linexp[itr] { continue; }
// 寻找 IJ0: DO IJT=IJ0,IFR1(ITR) IF(FREQ(IJT)<=FR) IJ0=IJT
let ifr0_itr = (atomic.ifr0[itr] - 1) as usize;
let ifr1_itr = (atomic.ifr1[itr] - 1) as usize;
let mut ij0 = ifr0_itr;
for ijt in ifr0_itr..=ifr1_itr {
if model.freq[ijt] <= fr {
ij0 = ijt;
break;
}
}
if ij0 == 0 { continue; }
let ij1 = ij0 - 1;
let a1 = (fr - model.freq[ij0]) / (model.freq[ij1] - model.freq[ij0]);
let a2 = UN - a1;
let ion = (atomic.iel[ilow_itr] - 1) as usize;
for id in 0..nd {
let sg = a1 * atomic.prflin[id * nfreq + ij1]
+ a2 * atomic.prflin[id * nfreq + ij0];
output.abso1[id] += sg * atomic.abtra[itr * nd + id];
output.emis1[id] += sg * atomic.emtra[itr * nd + id];
add_ion(ion, id, nd, sg * atomic.abtra[itr * nd + id],
sg * atomic.emtra[itr * nd + id], output);
}
}
}
// ============================================================================
// 谱线贡献 (ODF 采样模式, ISPODF>0)
// ============================================================================
fn line_contributions_odf(
ij_idx: usize, nd: usize, nfreq: usize, lfre: bool,
atomic: &mut OpacfaAtomicData, model: &OpacfaModelData, output: &mut OpacfaOutput,
) {
if atomic.nlines[ij_idx] <= 0 { return; }
let nlines_ij = atomic.nlines[ij_idx] as usize;
for ilint in 0..nlines_ij {
let itr = (atomic.itrlin[ilint * nfreq + ij_idx] - 1) as usize;
let ilow_itr = (atomic.ilow[itr] - 1) as usize;
let iad = if ilow_itr < atomic.iatm.len() {
atomic.iadop[(atomic.iatm[ilow_itr] - 1) as usize]
} else { 0 };
if iad > 0 && !lfre { continue; }
let ion = (atomic.iel[ilow_itr] - 1) as usize;
let _ij = ij_idx + 1; // 1-indexed for computing KJ
let kj = (_ij as i32 - atomic.ifr0[itr] + atomic.kfr0[itr]) as usize;
let indxpa = atomic.indexp[itr].abs();
if indxpa != 3 && indxpa != 4 {
for id in 0..nd {
let sg = atomic.prflin[id * nfreq + kj];
output.abso1[id] += sg * atomic.abtra[itr * nd + id];
output.emis1[id] += sg * atomic.emtra[itr * nd + id];
add_ion(ion, id, nd, sg * atomic.abtra[itr * nd + id],
sg * atomic.emtra[itr * nd + id], output);
}
} else {
// Fe 不透明度采样
for id in 0..nd {
let kjid = atomic.jidi[id] as usize;
let xjid_val = atomic.xjid[id];
let sg = (xjid_val * atomic.sigfe[kjid * nfreq + kj]
+ (UN - xjid_val) * atomic.sigfe[(kjid + 1) * nfreq + kj]).exp();
output.abso1[id] += sg * atomic.abtra[itr * nd + id];
output.emis1[id] += sg * atomic.emtra[itr * nd + id];
add_ion(ion, id, nd, sg * atomic.abtra[itr * nd + id],
sg * atomic.emtra[itr * nd + id], output);
}
}
}
}
// ============================================================================
// 最终不透明度计算
// ============================================================================
fn finalize_opacities(izscal: i32, model: &OpacfaModelData, output: &mut OpacfaOutput, nd: usize) {
let nion = output.absoti.len() / nd;
for id in 0..nd {
output.abso1[id] -= output.emis1[id] * model.xkf[id];
output.absoc1[id] -= output.emisc1[id] * model.xkf[id];
for ion in 0..nion {
output.absoti[ion * nd + id] -= output.emisti[ion * nd + id] * model.xkf[id];
}
output.emis1[id] *= model.xkfb[id];
output.emisc1[id] *= model.xkfb[id];
for ion in 0..nion {
output.emisti[ion * nd + id] *= model.xkfb[id];
}
output.absot[id] = output.abso1[id];
if izscal == 0 {
output.absot[id] = output.abso1[id] * model.dens1[id];
}
}
}