//! Continuous opacity, emissivity, and scattering coefficients. //! //! Translated from SYNSPEC54.FOR subroutine OPACON(ID,CROSS,ABSOC,EMISC,SCATC) //! at line 12661. //! //! Computes absorption, emission, and scattering coefficients at a given //! depth point for continuum frequencies. use super::dwnfr1::{dwnfr1, Dwnfr1Params}; use super::gfree::gfree; use super::sffhmi::sffhmi; /// Parameters for continuous opacity calculation. pub struct OpaconParams<'a> { /// Depth index pub id: usize, /// Temperature at each depth (K) pub temp: &'a [f64], /// Electron density at each depth (cm^-3) pub elec: &'a [f64], /// Continuum frequencies pub freqc: &'a [f64], /// Number of continuum frequencies pub nfreqc: usize, /// Number of ions pub nion: usize, /// First level index for each ion pub nfirst: &'a [usize], /// Last level index for each ion pub nlast: &'a [usize], /// Next ion index for each ion pub nnext: &'a [usize], /// Free-free flag for each ion pub ifree: &'a [i32], /// IELHM index (H- element) pub ielhm: usize, /// Ionic charge for each element pub iz: &'a [i32], /// Element index for each level pub iel: &'a [usize], /// Occupation probability flag for each level pub ifwop: &'a [i32], /// Population of each level at current depth pub popul_lev: &'a [f64], /// Statistical weight of each level pub g: &'a [f64], /// Ionization energy of each level (erg) pub enion: &'a [f64], /// Occupation probability for each level pub wop: &'a [f64], /// Dissolved fraction frequency for each level pub fropc: &'a [f64], /// Dissolved fraction index for each level pub indexp: &'a [i32], /// Z3 array for dissolved fraction pub z3: &'a [f64], /// ELEC23 array for dissolved fraction pub elec23: &'a [f64], /// DWC1 array for dissolved fraction pub dwc1: &'a [f64], /// DWC2 array for dissolved fraction pub dwc2: &'a [f64], /// DWC1 number of depth points pub ndepth: usize, /// Bergmann factor pub bergfc: f64, /// Planck function constant pub bn: f64, /// h/k constant pub hk: f64, /// Electron scattering cross-section pub sige: f64, /// Stimulated emission correction at current depth pub stim: f64, /// Planck function at current depth pub plan: f64, /// Photoionization cross-sections (nion x nfreqc) pub cross: &'a [&'a [f64]], /// Saha-Boltzmann factors for merged levels pub sgmerg_fn: Option f64>>, /// Additional opacity function pub opadd_fn: Option (f64, f64, f64)>>, /// Lyman line function pub lymlin_fn: Option (f64, f64, f64)>>, /// Photoionization function pub phtion_fn: Option>, /// Photoionization function (extended) pub phtx_fn: Option>, } /// Result of continuous opacity calculation. pub struct OpaconResult { /// Absorption coefficient array pub absoc: Vec, /// Emission coefficient array pub emisc: Vec, /// Scattering coefficient array pub scatc: Vec, } /// Continuous opacity, emissivity, and scattering coefficients. /// /// Computes absorption, emission, and scattering coefficients at a given /// depth point for continuum frequencies. Includes bound-free, free-free, /// and additional opacity contributions. /// /// # Arguments /// * `params` - Calculation parameters /// /// # Returns /// Arrays of absorption, emission, and scattering coefficients. pub fn opacon(params: &OpaconParams) -> OpaconResult { let id = params.id; let t = params.temp[id]; let ane = params.elec[id]; let t1 = 1.0 / t; let hkt = params.hk * t1; let tk = hkt / params.bn; let srt = 1.0 / t.sqrt(); let sgff = 3.694e8 * srt; let con = 2.0706e-16 * t1 * srt; let sce = ane * params.sige; let mut absoc = vec![0.0; params.nfreqc]; let mut emisc = vec![0.0; params.nfreqc]; let mut scatc = vec![0.0; params.nfreqc]; for ij in 0..params.nfreqc { let fr = params.freqc[ij]; let fr15 = fr * 1e-15; let bnu = params.bn * fr15 * fr15 * fr15; let hkf = hkt * fr; let mut abf = 0.0; let mut ebf = 0.0; let mut aff = 0.0; for il in 0..params.nion { let n0i = params.nfirst[il]; let n1i = params.nlast[il]; let nke = params.nnext[il]; let xn = params.popul_lev[nke]; // Bound-free contribution for ii in n0i..=n1i { let mut sg = 0.0; if params.ifwop[ii] < 0 { // Merged level if let Some(ref f) = params.sgmerg_fn { sg = f(ii, id, fr); } } else { sg = params.cross[il][ij]; if sg <= 0.0 { continue; } // Dissolved fraction correction if params.indexp[ii] == 5 { let izz = params.iz[params.iel[ii]] as usize; let fr0 = params.enion[ii] / 6.6256e-27; let dw1 = dwnfr1(&Dwnfr1Params { fr, fr0, id, izz, z3: params.z3, elec23: params.elec23, dwc1: params.dwc1, ndepth: params.ndepth, dwc2: params.dwc2, bergfc: params.bergfc, }); sg *= dw1; } } if params.popul_lev[ii] < 1e-20 || xn < 1e-20 { continue; } abf += sg * params.popul_lev[ii]; let xx = sg * xn * (params.enion[ii] * tk - hkf).exp() * params.wop[ii]; let _ee = (params.enion[ii] * tk - hkf).exp(); ebf += xx * con * params.g[ii] / params.g[nke]; } let it = params.ifree[il]; if it == 0 { continue; } // Free-free contribution let ie = il; if ie == params.ielhm { let sff = sffhmi(xn, fr, t); aff += sff; } else { let ch = params.iz[il] as f64 * params.iz[il] as f64; let sf1 = ch * xn * sgff / (fr * fr * fr); let sff = if it == 2 { let sg = gfree(t, fr / ch); sf1 * sg } else { sf1 }; aff += sff; } } // Additional opacities let (abad, emad, scad) = if let Some(ref f) = params.opadd_fn { f(id, fr) } else { (0.0, 0.0, 0.0) }; // Lyman lines let (ably, emly, scly) = if let Some(ref f) = params.lymlin_fn { f(id, fr) } else { (0.0, 0.0, 0.0) }; // Total opacity and emissivity let x = (-hkf).exp(); let x1 = 1.0 - x; let bne = bnu * x * ane; absoc[ij] = abf + ane * (x1 * aff - ebf) + abad + ably; emisc[ij] = bne * aff + bnu * ane * ebf + emad + emly; scatc[ij] = scad + scly + sce; } // Photoionization contributions if let Some(ref f) = params.phtion_fn { f(id, &mut absoc, &mut emisc, params.freqc, params.nfreqc); } if let Some(ref f) = params.phtx_fn { f(id, &mut absoc, &mut emisc, params.freqc, 1); } OpaconResult { absoc, emisc, scatc, } } #[cfg(test)] mod tests { use super::*; #[test] fn test_opacon_basic() { let temp = [10000.0]; let elec = [1e13]; let freqc = [1e14, 2e14, 3e14]; let nfirst = [0]; let nlast = [0]; let nnext = [1]; let ifree = [1]; let iz = [1]; let iel = [0]; let ifwop = [1]; let popul_lev = [1e12, 1e10]; let g = [1.0, 2.0]; let enion = [0.0, 1e-11]; let wop = [1.0, 1.0]; let fropc = [0.0, 0.0]; let indexp = [0, 0]; let z3 = [1.0]; let elec23 = [2.15e-9]; let dwc1 = [0.0]; let dwc2 = [0.0]; let cross_row: &[f64] = &[1e-18, 2e-18, 3e-18]; let cross: &[&[f64]] = &[cross_row]; let params = OpaconParams { id: 0, temp: &temp, elec: &elec, freqc: &freqc, nfreqc: 3, nion: 1, nfirst: &nfirst, nlast: &nlast, nnext: &nnext, ifree: &ifree, ielhm: 99, iz: &iz, iel: &iel, ifwop: &ifwop, popul_lev: &popul_lev, g: &g, enion: &enion, wop: &wop, fropc: &fropc, indexp: &indexp, z3: &z3, elec23: &elec23, dwc1: &dwc1, dwc2: &dwc2, ndepth: 1, bergfc: 1.0, bn: 1.0, hk: 4.79928144e-11, sige: 6.65e-25, stim: 1.0, plan: 1.0, cross, sgmerg_fn: None, opadd_fn: None, lymlin_fn: None, phtion_fn: None, phtx_fn: None, }; let result = opacon(¶ms); assert_eq!(result.absoc.len(), 3); assert_eq!(result.emisc.len(), 3); assert_eq!(result.scatc.len(), 3); assert!(result.absoc[0].is_finite()); assert!(result.scatc[0].is_finite()); } }