//! 所有深度点的吸收、发射和散射系数计算 (含离子贡献)。 //! //! 重构自 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>], 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( 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]; } } }