//! 线不透明度和发射率计算(风模型变体)。 //! //! 翻译自 SYNSPEC `LINOPW` 子程序 (synspec54.f:10590)。 //! //! 与 LINOP 类似,但针对风模型做了以下扩展: //! - 速度相关的线拒绝 //! - 辐射场处理 (itrad, trad, wdil) //! - 窗口化频率网格的线心索引计算 //! - DOP1 = DOPA1(IAT,ID)/FR0 (与 LINOP 不同) use super::phe1::{phe1, Phe1Params}; use super::phe2::{phe2, Phe2Params}; use super::voigtk::{voigtk, MVOI}; /// 物理常数 const UN: f64 = 1.0; const EXT0: f64 = 3.17; const TEN: f64 = 10.0; const C3: f64 = 1.4387886; const XET: f64 = 8067.6; const XET3: f64 = XET * C3; /// LINOPW 参数结构体。 /// /// 与 LinopParams 类似,但增加了风模型相关参数。 #[derive(Debug)] pub struct LinopwParams<'a> { /// 深度索引 (1-indexed) pub id: usize, /// 温度 (K) pub temp: f64, /// h/k (erg/K) pub hk: f64, /// 频率数 pub nfreq: usize, /// 频率数组 (Hz) pub freq: &'a [f64], /// NOPAC — 不透明度截止频率索引 pub nopac: usize, /// Planck 函数 PLAN(ID) pub plan: f64, /// 受激辐射修正 STIM(ID) — LINOPW 中未使用,保留接口兼容 pub stim: f64, /// 谱线数 (NLIN) pub nlin: usize, /// 谱线数据 pub lines: &'a [LinopwLineData], /// RRR(ID,ION,IAT) pub rrr: &'a [f64], /// RRR 维度: [natom][nion] pub rrr_dims: (usize, usize), /// DOPA1(IAT, ID) — Doppler 宽度 pub dopa1: &'a [f64], /// DOPA1 维度: [natom] pub dopa1_nat: usize, /// G(level) — 能级统计权重 pub g: &'a [f64], /// POPUL(level, ID) — 能级布居数 pub popul: &'a [f64], /// POPUL 维度: [nlevel] pub popul_nlev: usize, /// PNLT(IAT, ION, ID) — NLTE 布居数 pub pnlt: &'a [f64], /// PNLT 维度: [natom][nion] pub pnlt_dims: (usize, usize), /// ENEV(IAT, ION) — 电离能 (cm^-1) pub enev: &'a [f64], /// ENEV 维度: [natom] pub enev_nat: usize, /// ENION(level) — 能级能量 (erg) pub enion: &'a [f64], /// 激光删除标志 (lasdel) pub lasdel: bool, /// He II 特殊线数 (NSP) pub nsp: usize, /// He II 特殊线索引 (ISP0) pub isp0: &'a [usize], /// Voigt 函数表 H0 pub h0tab: &'a [f64; MVOI], /// Voigt 函数表 H1 pub h1tab: &'a [f64; MVOI], /// Voigt 函数表 H2 pub h2tab: &'a [f64; MVOI], /// PHE1 轮廓表数据 pub phe1_data: Option>, /// PHE2 公共数据 pub phe2_common: Option>, // --- 风模型特有参数 --- /// 速度场 VEL(ID) pub vel: f64, /// 最大速度 VELMAX pub velmax: f64, /// 辐射场模式 (ITRAD) pub itrad: i32, /// 辐射温度 TRAD(ipotl, ID) pub trad: &'a [f64], /// TRAD 维度: [npotl] pub trad_npotl: usize, /// IPOTL(line) — 电离势索引 pub ipotl: &'a [usize], /// BNUE(ij) — 频率相关 Planck 函数 pub bnue: &'a [f64], /// NLTE 关闭标志 (NLTOFF) pub nltoff: i32, /// 发射关闭标志 (IEMOFF) pub iemoff: i32, /// 线心 NLTE 关闭标记 ILNE(depth) pub ilne: &'a [usize], /// 线心速度拒绝标记 ILVI(depth) pub ilvi: &'a [usize], /// 线心频率索引 IJCNTR(line) — 输出 pub ijcntr: &'a mut [usize], /// 标准线吸收 ABSTDW(ijcont, ID) pub abstdw: &'a [f64], /// ABSTDW 维度: [nfreq] pub abstdw_nfreq: usize, /// RELOP — 相对不透明度阈值 pub relop: f64, /// IJCONT(line) — 线心频率索引 pub ijcont: &'a [usize], /// 准直函数 XJCON(ID) — 未使用,保留 pub xjcon: f64, /// 稀释因子 WDIL(ID) — 输出 pub wdil_out: &'a mut f64, } /// 单条谱线数据 (LINOPW 版本)。 #[derive(Debug, Clone)] pub struct LinopwLineData { pub il: usize, pub innlt: i32, pub iat: usize, pub ion: usize, pub isprf: usize, pub freq0: f64, pub gf0: f64, pub excl0: f64, pub excu0: f64, pub agam: f64, pub dop1_inv: f64, pub abcent: f64, pub slin: f64, pub ilown: usize, pub iupn: usize, } /// PHE1 轮廓表数据 (LINOPW 版本)。 #[derive(Debug)] pub struct Phe1DataW<'a> { pub vturb: f64, pub elec: f64, pub prf447: &'a [f64], pub dlm447: &'a [f64], pub xne447: &'a [f64], pub nwlam_447: &'a [usize], pub prfhe1: &'a [f64], pub dlmhe1: &'a [f64], pub xnehe1: &'a [f64], pub nwlam_he1: &'a [usize], pub max_wlam_447: usize, pub max_wlam_he1: usize, } /// PHE2 公共数据 (LINOPW 版本)。 #[derive(Debug)] pub struct Phe2CommonW<'a> { pub ielhe2: i32, pub inlte: i32, pub he3_pop: f64, pub nlhe2: i32, pub nfirst_he2: i32, pub wlam: &'a [f64], pub prfhe2: &'a [f64], pub wlhe2: &'a [f64], pub nwlhe2: i32, pub ilhe2: i32, pub iuhe2: i32, pub lasdel: bool, } /// LINOPW 输出结果。 #[derive(Debug)] pub struct LinopwResult { pub ablin: Vec, pub emlin: Vec, } /// 计算线不透明度和发射率(风模型变体)。 pub fn linopw(params: &mut LinopwParams) -> LinopwResult { let nfreq = params.nfreq; let mut ablin = vec![0.0f64; nfreq]; let mut ablinn = vec![0.0f64; nfreq]; let mut emlin = vec![0.0f64; nfreq]; *params.wdil_out = 1.0; let _plw = params.plan * 1.0; // wdil=1 if params.nlin == 0 { return LinopwResult { ablin, emlin }; } let tem1 = UN / params.temp; let hkt = params.hk * tem1; // 计算频率间距因子 let xx = params.freq[nfreq - 1] - params.freq[0]; let dfrcon = if xx.abs() > 1e-30 { -((params.nopac as f64) - 1.0) / xx } else { 0.0 }; for (line_idx, line_data) in params.lines.iter().take(params.nlin).enumerate() { let il = line_data.il; let innlt = line_data.innlt; // 速度拒绝 if params.ilvi[params.id - 1] > 0 { if innlt == 0 { continue; } else if params.nltoff != 0 { continue; } } // 线心频率索引 (仅深度 1) if params.id == 1 { let fr0 = line_data.freq0; let xjc = 3.0 + dfrcon * (params.freq[0] - fr0); let mut ijc = xjc as usize; if ijc > 1 && ijc < params.nopac { // 在频率网格中找到最近的点 if fr0 < params.freq[ijc] { let mut ijc0 = ijc; let mut dfr0 = params.freq[ijc0] - fr0; loop { ijc0 += 1; if ijc0 >= nfreq { break; } let dfr = (params.freq[ijc0] - fr0).abs(); if dfr < dfr0 { ijc = ijc0; dfr0 = dfr; } else { break; } } } else if fr0 > params.freq[ijc] { let mut ijc0 = ijc; let mut dfr0 = fr0 - params.freq[ijc0]; loop { if ijc0 == 0 { break; } ijc0 -= 1; let dfr = (params.freq[ijc0] - fr0).abs(); if dfr < dfr0 { ijc = ijc0; dfr0 = dfr; } else { break; } } } } params.ijcntr[line_idx] = ijc; } let iat = line_data.iat; let ion = line_data.ion; let fr0 = line_data.freq0; let lpr = !(line_data.isprf > 1 && line_data.isprf <= 5); if line_data.isprf >= 6 { continue; } let agam = line_data.agam; let dop1 = 1.0 / line_data.dop1_inv / fr0; // DOPA1(IAT,ID)/FR0 // 计算 ab0 和 sl0 let (ab0, sl0) = if innlt == 0 && params.itrad <= 0 { // LTE 线 (无辐射场) let rrr_idx = params.rrr_dims.0 * params.rrr_dims.1 * (params.id - 1) + ion * params.rrr_dims.0 + iat; let rrr_val = if rrr_idx < params.rrr.len() { params.rrr[rrr_idx] } else { 0.0 }; let ab0 = (line_data.gf0 - line_data.excl0 * tem1).exp() * rrr_val * dop1 * (1.0 - (-hkt * fr0).exp()); (ab0, 0.0) } else if innlt == 0 && params.itrad > 0 { // LTE 线 (有辐射场) let ipotl_idx = if il < params.ipotl.len() { params.ipotl[il] } else { 0 }; let trad_idx = ipotl_idx * params.trad_npotl + (params.id - 1); let trl = if trad_idx < params.trad.len() { params.trad[trad_idx] } else { params.temp }; let xx = (-hkt * fr0).exp(); let rrr_idx = params.rrr_dims.0 * params.rrr_dims.1 * (params.id - 1) + ion * params.rrr_dims.0 + iat; let rrr_val = if rrr_idx < params.rrr.len() { params.rrr[rrr_idx] } else { 0.0 }; let mut ab0 = (line_data.gf0 - line_data.excl0 / trl).exp() * rrr_val * dop1 * (1.0 - xx); if line_data.excl0 > 2000.0 { ab0 *= 1.0; // wdil=1 } let pla = 1.4743e-2 * (fr0 * 1e-15).powi(3) * xx / (1.0 - xx); let sl0 = pla * 1.0; // wdil=1 (ab0, sl0) } else if innlt > 0 { (line_data.abcent, line_data.slin) } else { // NLTE 线 let pnlt_idx = params.pnlt_dims.0 * params.pnlt_dims.1 * (params.id - 1) + ion * params.pnlt_dims.0 + iat; let pp = if pnlt_idx < params.pnlt.len() { params.pnlt[pnlt_idx] } else { 0.0 }; let pi = if line_data.ilown > 0 { let pop_idx = (line_data.ilown - 1) * params.popul_nlev + (params.id - 1); if pop_idx < params.popul.len() { params.popul[pop_idx] / params.g[line_data.ilown - 1] } else { 0.0 } } else { let enev_idx = params.enev_nat * (params.id - 1) + iat; let enev_val = if enev_idx < params.enev.len() { params.enev[enev_idx] } else { 0.0 }; pp * ((enev_val * XET3 - line_data.excl0) * tem1).exp() }; let (pj, cor) = if line_data.iupn > 0 { let pop_idx = (line_data.iupn - 1) * params.popul_nlev + (params.id - 1); let pj = if pop_idx < params.popul.len() { params.popul[pop_idx] / params.g[line_data.iupn - 1] } else { 0.0 }; let cor = if line_data.ilown > 0 && line_data.iupn > 0 { ((line_data.excu0 - line_data.excl0 + (params.enion[line_data.iupn - 1] - params.enion[line_data.ilown - 1]) / 1.38054e-16) * tem1).exp() } else { 1.0 }; (pj, cor) } else { let enev_idx = params.enev_nat * (params.id - 1) + iat; let enev_val = if enev_idx < params.enev.len() { params.enev[enev_idx] } else { 0.0 }; let pj = pp * ((enev_val * XET3 - line_data.excu0) * tem1).exp(); (pj, 1.0) }; let x = if pj > 0.0 { pi / pj * cor } else { UN }; let x = if x == UN { (4.79928e-11 * fr0 * tem1).exp() } else { x }; let sl0 = params.plan / (x - UN); let ab0 = if pi > 0.0 { pi * (UN - UN / x) * line_data.gf0.exp() * dop1 } else { 0.0 }; (ab0, sl0) }; if ab0 <= 0.0 && params.lasdel { continue; } // 频率贡献范围 let ijcont_idx = if il < params.ijcont.len() { params.ijcont[il] } else { 0 }; let abstdw_idx = ijcont_idx * params.abstdw_nfreq + (params.id - 1); let avabw = if abstdw_idx < params.abstdw.len() { params.abstdw[abstdw_idx] * params.relop } else { 0.0 }; let ex0 = if avabw > 0.0 { ab0 / avabw * agam } else { 0.0 }; let ext = if ex0 > TEN { ex0.sqrt() } else { EXT0 }; let ext = ext / dop1; let ijext = (dfrcon * ext + 1.5) as usize; let ijctr = params.ijcntr[line_idx]; let ij1 = ijctr.saturating_sub(ijext).max(1); let ij2 = (ijctr + ijext).min(nfreq); if ij1 >= nfreq || ij2 <= 2 { continue; } if innlt == 0 && params.itrad <= 0 { // LTE 线 if lpr { for ij in ij1..=ij2.min(nfreq - 1) { let xf = (params.freq[ij] - fr0).abs() * dop1; ablin[ij] += ab0 * voigtk(agam, xf, params.h0tab, params.h1tab, params.h2tab); } } else if let Some(ref phe1d) = params.phe1_data { for ij in 0..nfreq { let phe1_p = Phe1Params { id: params.id, freq: params.freq[ij], iline: line_data.isprf - 1, temp: params.temp, elec: phe1d.elec, vturb: phe1d.vturb, prf447: phe1d.prf447, dlm447: phe1d.dlm447, xne447: phe1d.xne447, nwlam_447: phe1d.nwlam_447, prfhe1: phe1d.prfhe1, dlmhe1: phe1d.dlmhe1, xnehe1: phe1d.xnehe1, nwlam_he1: phe1d.nwlam_he1, max_wlam_447: phe1d.max_wlam_447, max_wlam_he1: phe1d.max_wlam_he1, h0tab: params.h0tab, h1tab: params.h1tab, h2tab: params.h2tab, }; ablin[ij] += ab0 * phe1(&phe1_p); } } } else { // NLTE 线 或 有辐射场的 LTE 线 if lpr { for ij in ij1..=ij2.min(nfreq - 1) { let xf = (params.freq[ij] - fr0).abs() * dop1; let abl = ab0 * voigtk(agam, xf, params.h0tab, params.h1tab, params.h2tab); ablinn[ij] += abl; if params.ilne[params.id - 1] == 0 { emlin[ij] += abl * sl0; } } } else if let Some(ref phe1d) = params.phe1_data { for ij in 0..nfreq { let phe1_p = Phe1Params { id: params.id, freq: params.freq[ij], iline: line_data.isprf - 1, temp: params.temp, elec: phe1d.elec, vturb: phe1d.vturb, prf447: phe1d.prf447, dlm447: phe1d.dlm447, xne447: phe1d.xne447, nwlam_447: phe1d.nwlam_447, prfhe1: phe1d.prfhe1, dlmhe1: phe1d.dlmhe1, xnehe1: phe1d.xnehe1, nwlam_he1: phe1d.nwlam_he1, max_wlam_447: phe1d.max_wlam_447, max_wlam_he1: phe1d.max_wlam_he1, h0tab: params.h0tab, h1tab: params.h1tab, h2tab: params.h2tab, }; let abl = ab0 * phe1(&phe1_p); ablinn[ij] += abl; if params.ilne[params.id - 1] == 0 { emlin[ij] += abl * sl0; } } } } } // 连续谱贡献 if params.vel <= params.velmax { for ij in 0..nfreq { let pla = if (hkt * params.freq[ij]).exp() - 1.0 > 1e-30 { params.bnue[ij] / ((hkt * params.freq[ij]).exp() - 1.0) } else { 0.0 }; emlin[ij] += ablin[ij] * pla; // wdil=1 ablin[ij] += ablinn[ij]; } } // He II 特殊线 if params.nsp > 0 && let Some(ref phe2c) = params.phe2_common { for &isp in params.isp0.iter().take(params.nsp) { if (6..=24).contains(&isp) { let phe2_p = Phe2Params { ispec: isp as i32, id: params.id as i32, ielhe2: phe2c.ielhe2, inlte: phe2c.inlte, nfreq: nfreq as i32, freq: params.freq, wlam: phe2c.wlam, temp: params.temp, elec: 0.0, he3_pop: phe2c.he3_pop, nlhe2: phe2c.nlhe2, nfirst_he2: phe2c.nfirst_he2, popul: &[], prfhe2: phe2c.prfhe2, wlhe2: phe2c.wlhe2, nwlhe2: phe2c.nwlhe2, ilhe2: phe2c.ilhe2, iuhe2: phe2c.iuhe2, lasdel: phe2c.lasdel, }; let result = phe2(&phe2_p); for ij in 0..nfreq { ablin[ij] += result.ablin[ij]; emlin[ij] += result.emlin[ij]; } } } } LinopwResult { ablin, emlin } } #[cfg(test)] mod tests { use super::*; #[test] fn test_linopw_zero_lines() { let freq = vec![1e14; 10]; let h0tab = [0.0f64; MVOI]; let h1tab = [0.0f64; MVOI]; let h2tab = [0.0f64; MVOI]; let mut wdil = 0.0f64; let mut ijcntr = vec![0usize; 1]; let mut params = LinopwParams { id: 1, temp: 10000.0, hk: 4.79928e-11, nfreq: 10, freq: &freq, nopac: 10, plan: 1.0, stim: 1.0, nlin: 0, lines: &[], rrr: &[], rrr_dims: (0, 0), dopa1: &[], dopa1_nat: 0, g: &[], popul: &[], popul_nlev: 0, pnlt: &[], pnlt_dims: (0, 0), enev: &[], enev_nat: 0, enion: &[], lasdel: false, nsp: 0, isp0: &[], h0tab: &h0tab, h1tab: &h1tab, h2tab: &h2tab, phe1_data: None, phe2_common: None, vel: 0.0, velmax: 1e10, itrad: 0, trad: &[], trad_npotl: 0, ipotl: &[], bnue: &freq, nltoff: 0, iemoff: 0, ilne: &[0], ilvi: &[0], ijcntr: &mut ijcntr, abstdw: &[], abstdw_nfreq: 0, relop: 1.0, ijcont: &[0], xjcon: 0.0, wdil_out: &mut wdil, }; let result = linopw(&mut params); assert!(result.ablin.iter().all(|&x| x == 0.0)); assert!(result.emlin.iter().all(|&x| x == 0.0)); } }