//! 氢线 ODF 初始化。 //! //! 重构自 TLUSTY `odfhys.f` //! 设置氢线的频率网格、权重和 Stark 展宽参数。 //! //! 注意:此模块是 ODF 处理的核心模块,涉及频率网格设置和 Stark 展宽参数计算。 use crate::tlusty::math::odf::odffr::{self, OdffrAtomicData, OdffrModelData, OdffrOutputState, OdffrParams}; use crate::tlusty::math::ali::IjalisParams; use crate::tlusty::math::stark0; use crate::tlusty::state::atomic::{AtoPar, IonPar, LevPar, TraAli, TraCor, TraPar}; use crate::tlusty::state::config::BasNum; use crate::tlusty::state::constants::{NLMX, MFRO}; use crate::tlusty::state::model::{CompIf, FreAux}; use crate::tlusty::state::odfpar::{OdfCtr, OdfFrq, OdfMod, OdfStk}; // f2r_depends: IJALIS, ODFFR, STARK0 /// Hydrogen line ODF initialization wrapper. /// /// 根据 ISPODF 选择简化模式或完整模式。 pub fn odfhys(dopo: f64, params: &mut OdfhysParams, freq: &mut [f64], weight: &mut [f64]) { if params.basnum.ispodf >= 1 { odfhys_simplified(params); } else { odfhys_full(dopo, params, freq, weight); } } /// ODFHYS 参数结构体 pub struct OdfhysParams<'a> { /// 基本数值 pub basnum: &'a mut BasNum, /// 离子参数(包含 iz) pub ionpar: &'a IonPar, /// 能级参数 pub levpar: &'a LevPar, /// 跃迁参数 pub trapar: &'a mut TraPar, /// ODF 控制(包含 JNDODF) pub odfctr: &'a mut OdfCtr, /// ODF 频率数据 pub odffrq: &'a mut OdfFrq, /// ODF 模型数据 pub odfmod: &'a mut OdfMod, /// ODF Stark 数据 pub odfstk: &'a mut OdfStk, /// 计算标志(包含 LINEXP) pub compif: &'a mut CompIf, /// XI2 数组(0-based: xi2[n-1] = 1/n²) pub xi2: &'a [f64], // --- ODFFR/IJALIS 额外参数 --- /// 有效温度 pub teff: f64, /// 原子参数(IJALIS 需要) pub atopar: &'a AtoPar, /// ALI 跃迁标志(IJALIS 需要) pub traali: &'a TraAli, /// 频率辅助数据(IJALIS 需要) pub freaux: &'a mut FreAux, /// 跃迁修正标志(IJALIS 需要) pub tracor: &'a mut TraCor, } // 常量 const CCM: f64 = 1.0 / 2.997925e10; const THIRD: f64 = 1.0 / 3.0; const FRH: f64 = 3.28805e15; const HALF: f64 = 0.5; /// 初始化氢线 ODF(简化模式:ISPODF >= 1)。 /// /// 设置氢线的 Stark 展宽参数和振子强度。 /// 对应 Fortran 中 ISPODF >= 1 的分支(直接 RETURN)。 pub fn odfhys_simplified(params: &mut OdfhysParams) { let ntrans = params.basnum.ntrans as usize; let izzh: usize = 1; // 氢的原子序数 for itr in 0..ntrans { // Fix: 使用 JNDODF 而非 IJTF,且检查 <= 0(包括负数) let jnd_raw = params.odfctr.jndodf[itr]; if jnd_raw <= 0 { continue; } let jnd = jnd_raw as usize; let mode = params.trapar.indexp[itr].abs(); if mode != 2 { continue; } // Fix: 设置 LINEXP = false(Fortran: LINEXP(ITR)=.FALSE.) params.compif.linexp[itr] = false; params.trapar.lcomp[itr] = 0; params.trapar.intmod[itr] = 6; // I=ILOW(ITR), J=IUP(ITR) — Fortran 1-indexed → Rust 0-indexed let i = (params.trapar.ilow[itr] - 1) as usize; let j = (params.trapar.iup[itr] - 1) as usize; // 设置量子数 params.odfmod.nqlodf[i] = params.trapar.iprof[itr].abs(); if params.odfmod.nqlodf[i] == 0 { params.odfmod.nqlodf[i] = params.levpar.nquant[j]; } // 计算振子强度 params.trapar.osc0[itr] = 0.0; let is_quant = params.levpar.nquant[i] as usize; let j_quant = params.levpar.nquant[j] as usize; // Fix: JNDODF 是 1-based 索引,转为 0-based let jnd_idx = jnd - 1; if jnd_idx >= params.odfstk.xkij.len() { continue; } for k in j_quant..=NLMX { if k < params.odfstk.xkij[jnd_idx].len() { let (xkij_val, wl0_val, fij_val) = stark0(is_quant, k, izzh); params.odfstk.xkij[jnd_idx][k] = xkij_val; params.odfstk.wl0[jnd_idx][k] = wl0_val; params.odfstk.fij[jnd_idx][k] = fij_val; params.trapar.osc0[itr] += fij_val; } } } } /// 初始化氢线 ODF(完整模式)。 /// /// 设置氢线的频率网格、权重和 Stark 展宽参数。 /// 对应 Fortran 中 ISPODF < 1 的完整分支。 /// /// # 参数 /// * `dopo` - 多普勒宽度参数 /// * `params` - 参数结构体 /// * `freq` - 频率数组(输出) /// * `weight` - 权重数组(输出) pub fn odfhys_full( dopo: f64, params: &mut OdfhysParams, freq: &mut [f64], weight: &mut [f64], ) { let ntrans = params.basnum.ntrans as usize; let izzh: usize = 1; let mut nlaste = params.basnum.nfreq as usize; let mut ffro = vec![0.0_f64; MFRO]; for itr in 0..ntrans { // Fix: 使用 JNDODF 而非 IJTF,且检查 <= 0 let jnd_raw = params.odfctr.jndodf[itr]; if jnd_raw <= 0 { continue; } let jnd = jnd_raw as usize; let mode = params.trapar.indexp[itr].abs(); if mode != 2 { continue; } params.trapar.lcomp[itr] = 0; params.trapar.intmod[itr] = 6; // I=ILOW(ITR), J=IUP(ITR) — Fortran 1-indexed → Rust 0-indexed let i = (params.trapar.ilow[itr] - 1) as usize; let j = (params.trapar.iup[itr] - 1) as usize; if i >= params.levpar.nquant.len() || j >= params.levpar.nquant.len() { continue; } // 设置量子数 params.odfmod.nqlodf[i] = params.trapar.iprof[itr].abs(); if params.odfmod.nqlodf[i] == 0 { params.odfmod.nqlodf[i] = params.levpar.nquant[j]; } // Fix: XI2 是 0-based 数组,xi2[n-1] 对应 Fortran XI2(n) // Fortran: XJ2A=HALF*(XI2(NQUANT(J))+XI2(NQUANT(J)-1)) let nquant_j = params.levpar.nquant[j] as usize; if nquant_j == 0 || nquant_j > params.xi2.len() { continue; } let xj2a = HALF * (params.xi2[nquant_j - 1] + params.xi2[nquant_j - 2]); // Fix: JNDODF 是 1-based,转为 0-based 访问 ODF 数组 let jnd_idx = jnd - 1; if jnd_idx >= params.odffrq.kdo.len() { continue; } // kdo[jnd_idx][ifq] 对应 Fortran KDO(ifq+1, jnd) let mut nfro: usize = 0; for ifq in 0..4 { nfro += params.odffrq.kdo[jnd_idx][ifq] as usize; } nfro = nfro.saturating_sub(2); // 计算频率参数 let iel_idx = (params.levpar.iel[i].saturating_sub(1)) as usize; if iel_idx >= params.ionpar.iz.len() { continue; } let nquant_i = params.levpar.nquant[i] as usize; if nquant_i == 0 || nquant_i > params.xi2.len() { continue; } let frion = FRH * (params.ionpar.iz[iel_idx] as f64).powi(2); let fra = frion * (params.xi2[nquant_i - 1] - xj2a); let dopi = dopo * fra * CCM; let frb = 0.99999999 * frion * params.xi2[nquant_i - 1]; let _ifrq0 = params.trapar.ifr0[itr]; let _ifrq1 = params.trapar.ifr1[itr]; params.trapar.ifr0[itr] = (nlaste + 1) as i32; params.trapar.ifr1[itr] = (nlaste + nfro) as i32; params.odfmod.i1odf[i] = params.trapar.ifr0[itr]; params.odfmod.i2odf[i] = (params.trapar.ifr1[itr] - 1) as i32; // Fix: FFRO 使用 0-based 索引 // Fortran: FFRO(1)=..., FFRO(2)=..., IJ00=1 // Rust: ffro[0]=..., ffro[1]=..., ij00=0 (0-based) ffro[0] = 0.99999999 * fra; ffro[1] = fra; let mut ij00: usize = 0; // Fortran IJ00=1 → Rust 0-based = 0 for ik in 0..3 { let kdo_val = params.odffrq.kdo[jnd_idx][ik] as usize; // Fortran: DO IJ=2,KDO(IK,JND) → Rust: DO ij=2..=kdo_val // ijq = ij00 + ij,但因为 ij00 已经是 0-based,所以直接用 for ij in 2..=kdo_val { let ijq = ij00 + ij; if ijq < MFRO { ffro[ijq] = ffro[ijq - 1] + params.odffrq.xdo[jnd_idx][ik] * dopi; } } ij00 += kdo_val.saturating_sub(1); } // 查找 FRB 位置 // Fix: 0-based 搜索范围 0..=ij00 let mut nfrb: usize = ij00; for ij in 0..=ij00 { if ffro[ij] < frb { nfrb = ij; } } if nfrb == ij00 && nfro > 0 && nfro < MFRO { // 扩展频率数组 // Fortran: IJ00=IJ00+1, FFRO(NFRO)=... // Rust 0-based: ij00+=1, ffro[nfro-1] 对应 Fortran FFRO(NFRO) ij00 += 1; ffro[nfro - 1] = 0.99999999 * frion * params.xi2[nquant_i - 1]; while ij00 < MFRO && ffro[ij00] >= ffro[nfro - 1] { // Fix: xdo[jnd_idx][2] 对应 Fortran XDO(3,JND),不是 xdo[2][jnd_idx] params.odffrq.xdo[jnd_idx][2] *= 0.75; let kdo3 = params.odffrq.kdo[jnd_idx][2] as usize; ij00 = ij00.saturating_sub(kdo3); for ij in 2..=kdo3 { let ijq = ij00 + ij; if ijq < MFRO { ffro[ijq] = ffro[ijq - 1] + params.odffrq.xdo[jnd_idx][2] * dopi; } } ij00 += kdo3; } // Fix: kdo[jnd_idx][3] 对应 Fortran KDO(4,JND) let kdo4 = params.odffrq.kdo[jnd_idx][3]; if kdo4 > 1 { let tido = (ffro[nfro - 1] - ffro[ij00]) / (kdo4 - 1) as f64; for ij in 1..=((kdo4 - 2) as usize) { // Fortran: IJQ=NFRO-IJ, FFRO(IJQ)=FFRO(NFRO)-FLOAT(IJ)*TIDO // Rust: ijq = nfro-1-ij (0-based) let ijq = nfro.saturating_sub(1 + ij); if ijq < MFRO { ffro[ijq] = ffro[nfro - 1] - ij as f64 * tido; } } } } else if nfrb + 3 < MFRO { // nfrb + 1/2/3 的相对偏移在 0-based 下不变 let tido = (frb - ffro[nfrb]) * THIRD; ffro[nfrb + 1] = ffro[nfrb] + tido; ffro[nfrb + 2] = frb - tido; ffro[nfrb + 3] = frb; nfro = nfrb + 3; params.trapar.ifr1[itr] = (nlaste + nfro) as i32; params.odfmod.i2odf[i] = (params.trapar.ifr1[itr] - 1) as i32; } // 存储频率(反转) // Fortran: DO IJ=1,NFRO → FREQ(NLASTE+IJ)=FFRO(NFRO-IJ+1) // Rust 0-based: freq[nlaste+ij-1] = ffro[nfro-ij] for ij in 1..=nfro { let dest_idx = nlaste + ij - 1; let src_idx = nfro - ij; if dest_idx < freq.len() && src_idx < MFRO { freq[dest_idx] = ffro[src_idx]; } } // 计算权重 // Fortran: W(NLASTE+NFRO) = HALF*(FREQ(NLASTE+NFRO-1)-FREQ(NLASTE+NFRO)) // Rust 0-based: weight[nlaste+nfro-1] = 0.5*(freq[nlaste+nfro-2]-freq[nlaste+nfro-1]) if nfro >= 2 { let w_idx = nlaste + nfro - 1; if w_idx < weight.len() && w_idx > 0 { weight[w_idx] = HALF * (freq[w_idx - 1] - freq[w_idx]); weight[w_idx - 1] = weight[w_idx]; } // Fortran: DO IJ=2,NFRO-2,2 for ij in (2..=(nfro - 2)).step_by(2) { // FREQ(NLASTE+IJ) → freq[nlaste+ij-1] // FREQ(NLASTE+IJ+1) → freq[nlaste+ij] // W(NLASTE+IJ-1) → weight[nlaste+ij-2] // W(NLASTE+IJ) → weight[nlaste+ij-1] // W(NLASTE+IJ+1) → weight[nlaste+ij] let fi = nlaste + ij - 1; if fi >= 1 && fi + 1 < weight.len() { let tido = (freq[fi] - freq[fi + 1]) * THIRD; weight[fi - 1] += tido; weight[fi] += 4.0 * tido; weight[fi + 1] += tido; } } } nlaste = params.trapar.ifr1[itr] as usize; // CALL ODFFR(I,J) — 设置内部频率 // Fortran: I, J 是 1-based 能级索引 let il_1based = i + 1; // 转回 1-based let iu_1based = j + 1; { let odffr_params = OdffrParams { il: il_1based, iu: iu_1based, teff: params.teff, nlmx: NLMX, }; // 类型适配: iz 是 Vec,ODFFR 需要 &[f64] let iz_f64: Vec = params.ionpar.iz.iter().map(|&x| x as f64).collect(); let odffr_atomic = OdffrAtomicData { iel: ¶ms.levpar.iel, iz: &iz_f64, enion: ¶ms.levpar.enion, nquant: ¶ms.levpar.nquant, }; // 类型适配: itra 是 Vec> (2D),ODFFR 需要 &[i32] (flat, row-major) let nlevel = params.levpar.iel.len(); let itra_flat: Vec = params.trapar.itra.iter().flatten().copied().collect(); let odffr_model = OdffrModelData { itra: &itra_flat, jndodf: ¶ms.odfctr.jndodf, }; // 类型适配: fros/wnus 是 Vec> (2D),ODFFR 需要 &mut [f64] (flat) let num_odf = params.odfctr.nfrodf.len(); let mut fros_flat = vec![0.0_f64; MFRO * num_odf]; let mut wnus_flat = vec![0.0_f64; MFRO * num_odf]; // 复制当前值 for (fi, row) in params.odffrq.fros.iter().enumerate().take(MFRO) { for (ki, &val) in row.iter().enumerate().take(num_odf) { fros_flat[fi * num_odf + ki] = val; } } for (fi, row) in params.odffrq.wnus.iter().enumerate().take(MFRO) { for (ki, &val) in row.iter().enumerate().take(num_odf) { wnus_flat[fi * num_odf + ki] = val; } } { let mut odffr_output = OdffrOutputState { nfrodf: &mut params.odfctr.nfrodf, fros: &mut fros_flat, wnus: &mut wnus_flat, }; odffr::odffr(&odffr_params, &odffr_atomic, &odffr_model, &mut odffr_output); } // 复制回 2D 数组 for (fi, row) in params.odffrq.fros.iter_mut().enumerate().take(MFRO) { for (ki, val) in row.iter_mut().enumerate().take(num_odf) { *val = fros_flat[fi * num_odf + ki]; } } for (fi, row) in params.odffrq.wnus.iter_mut().enumerate().take(MFRO) { for (ki, val) in row.iter_mut().enumerate().take(num_odf) { *val = wnus_flat[fi * num_odf + ki]; } } let _ = nlevel; // 避免未使用警告 } // IF(INDEXP(ITR).NE.0) CALL IJALIS(ITR,IFRQ0,IFRQ1) if params.trapar.indexp[itr] != 0 { let mut ijalis_params = IjalisParams { trapar: params.trapar, levpar: params.levpar, atopar: params.atopar, traali: params.traali, freaux: params.freaux, tracor: params.tracor, }; let _ijalis_out = crate::tlusty::math::ali::ijalis(itr, _ifrq0, _ifrq1, &mut ijalis_params); } params.trapar.osc0[itr] = 0.0; let is_quant = params.levpar.nquant[i] as usize; let j_quant = params.levpar.nquant[j] as usize; if jnd_idx < params.odfstk.xkij.len() { for k in j_quant..=NLMX { if k < params.odfstk.xkij[jnd_idx].len() { let (xkij_val, wl0_val, fij_val) = stark0(is_quant, k, izzh); params.odfstk.xkij[jnd_idx][k] = xkij_val; params.odfstk.wl0[jnd_idx][k] = wl0_val; params.odfstk.fij[jnd_idx][k] = fij_val; params.trapar.osc0[itr] += fij_val; } } } } params.basnum.nfreq = nlaste as i32; } #[cfg(test)] mod tests { use super::*; use crate::tlusty::state::atomic::{AtoPar, IonPar, LevPar, TraAli, TraCor, TraPar}; use crate::tlusty::state::config::BasNum; use crate::tlusty::state::constants::NLMX; use crate::tlusty::state::model::{CompIf, FreAux}; use crate::tlusty::state::odfpar::{OdfCtr, OdfFrq, OdfMod, OdfStk}; fn create_test_state() -> (BasNum, IonPar, LevPar, TraPar, OdfCtr, OdfFrq, OdfMod, OdfStk, CompIf, Vec, AtoPar, TraAli, FreAux, TraCor) { let mut basnum = BasNum::default(); basnum.ntrans = 2; basnum.nfreq = 10; basnum.ispodf = 1; let mut ionpar = IonPar::default(); ionpar.iz[0] = 1; // H let mut levpar = LevPar::default(); levpar.nquant[0] = 1; levpar.nquant[1] = 2; levpar.nquant[2] = 3; levpar.iel[0] = 1; levpar.iel[1] = 1; levpar.iel[2] = 1; let mut trapar = TraPar::default(); trapar.indexp[0] = 2; trapar.ilow[0] = 1; trapar.iup[0] = 2; trapar.iprof[0] = 0; trapar.ifr0[0] = 1; trapar.ifr1[0] = 5; trapar.line[0] = 1; // Fix: 使用 OdfCtr.jndodf 而非 TraPar.ijtf let mut odfctr = OdfCtr::new(); odfctr.jndodf[0] = 1; // 第一个跃迁对应 jnd=1 (1-based) let mut odffrq = OdfFrq::new(); // kdo[jnd_idx][ik] 对应 Fortran KDO(ik+1, jnd) odffrq.kdo[0][0] = 10; odffrq.kdo[0][1] = 10; odffrq.kdo[0][2] = 10; odffrq.kdo[0][3] = 10; odffrq.xdo[0][0] = 0.1; odffrq.xdo[0][1] = 0.1; odffrq.xdo[0][2] = 0.1; let odfmod = OdfMod::new(); let odfstk = OdfStk::new(NLMX); let compif = CompIf::default(); // XI2: 0-based, xi2[n-1] = 1/n² let mut xi2 = vec![0.0; 50]; for n in 1..=10 { xi2[n - 1] = 1.0 / (n as f64).powi(2); } let atopar = AtoPar::default(); let traali = TraAli::default(); let freaux = FreAux::default(); let tracor = TraCor::default(); (basnum, ionpar, levpar, trapar, odfctr, odffrq, odfmod, odfstk, compif, xi2, atopar, traali, freaux, tracor) } macro_rules! make_params { ($basnum:ident, $ionpar:ident, $levpar:ident, $trapar:ident, $odfctr:ident, $odffrq:ident, $odfmod:ident, $odfstk:ident, $compif:ident, $xi2:ident, $atopar:ident, $traali:ident, $freaux:ident, $tracor:ident) => { OdfhysParams { basnum: &mut $basnum, ionpar: &$ionpar, levpar: &$levpar, trapar: &mut $trapar, odfctr: &mut $odfctr, odffrq: &mut $odffrq, odfmod: &mut $odfmod, odfstk: &mut $odfstk, compif: &mut $compif, xi2: &mut $xi2, teff: 35000.0, atopar: &$atopar, traali: &$traali, freaux: &mut $freaux, tracor: &mut $tracor, } }; } #[test] fn test_odfhys_simplified_mode() { let ( mut basnum, ionpar, levpar, mut trapar, mut odfctr, mut odffrq, mut odfmod, mut odfstk, mut compif, mut xi2, atopar, traali, mut freaux, mut tracor, ) = create_test_state(); let mut params = make_params!( basnum, ionpar, levpar, trapar, odfctr, odffrq, odfmod, odfstk, compif, xi2, atopar, traali, freaux, tracor ); odfhys_simplified(&mut params); // 验证振子强度被计算 assert!( params.trapar.osc0[0] > 0.0, "Oscillator strength should be positive, got {}", params.trapar.osc0[0] ); // 验证 INTMOD 被设置 assert_eq!(params.trapar.intmod[0], 6); // 验证 LCOMP 被设置为 false assert_eq!(params.trapar.lcomp[0], 0); // Fix: 验证 LINEXP 被设置为 false assert!(!params.compif.linexp[0]); } #[test] fn test_odfhys_skip_non_mode2() { let ( mut basnum, ionpar, levpar, mut trapar, mut odfctr, mut odffrq, mut odfmod, mut odfstk, mut compif, mut xi2, atopar, traali, mut freaux, mut tracor, ) = create_test_state(); // 设置为非 mode 2 trapar.indexp[0] = 1; let mut params = make_params!( basnum, ionpar, levpar, trapar, odfctr, odffrq, odfmod, odfstk, compif, xi2, atopar, traali, freaux, tracor ); odfhys_simplified(&mut params); // 振子强度应该保持 0(被跳过) assert_eq!(params.trapar.osc0[0], 0.0); } #[test] fn test_odfhys_skip_negative_jndodf() { let ( mut basnum, ionpar, levpar, mut trapar, mut odfctr, mut odffrq, mut odfmod, mut odfstk, mut compif, mut xi2, atopar, traali, mut freaux, mut tracor, ) = create_test_state(); // Fix: 测试负数 JNDODF 被正确跳过 odfctr.jndodf[0] = -1; let mut params = make_params!( basnum, ionpar, levpar, trapar, odfctr, odffrq, odfmod, odfstk, compif, xi2, atopar, traali, freaux, tracor ); odfhys_simplified(&mut params); // 应该被跳过,osc0 保持 0 assert_eq!(params.trapar.osc0[0], 0.0); } #[test] fn test_stark_parameters_computed() { let ( mut basnum, ionpar, levpar, mut trapar, mut odfctr, mut odffrq, mut odfmod, mut odfstk, mut compif, mut xi2, atopar, traali, mut freaux, mut tracor, ) = create_test_state(); let mut params = make_params!( basnum, ionpar, levpar, trapar, odfctr, odffrq, odfmod, odfstk, compif, xi2, atopar, traali, freaux, tracor ); odfhys_simplified(&mut params); // 验证 Stark 参数被计算 // jnd_idx=0 (jnd=1, 0-based), k from j_quant=2 to NLMX assert!(params.odfstk.xkij[0][2] > 0.0); assert!(params.odfstk.wl0[0][2] > 0.0); assert!(params.odfstk.fij[0][2] > 0.0); } #[test] fn test_xi2_0based_indexing() { // 验证 XI2 使用 0-based 索引: xi2[n-1] = 1/n² let xi2: Vec = (1..=10).map(|n| 1.0 / (n as f64).powi(2)).collect(); assert!((xi2[0] - 1.0).abs() < 1e-15); // n=1: xi2[0] = 1.0 assert!((xi2[1] - 0.25).abs() < 1e-15); // n=2: xi2[1] = 0.25 assert!((xi2[2] - 1.0 / 9.0).abs() < 1e-15); // n=3: xi2[2] = 1/9 } }