682 lines
23 KiB
Rust
682 lines
23 KiB
Rust
//! 氢线 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<i32>,ODFFR 需要 &[f64]
|
||
let iz_f64: Vec<f64> = 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<Vec<i32>> (2D),ODFFR 需要 &[i32] (flat, row-major)
|
||
let nlevel = params.levpar.iel.len();
|
||
let itra_flat: Vec<i32> = params.trapar.itra.iter().flatten().copied().collect();
|
||
let odffr_model = OdffrModelData {
|
||
itra: &itra_flat,
|
||
jndodf: ¶ms.odfctr.jndodf,
|
||
};
|
||
// 类型适配: fros/wnus 是 Vec<Vec<f64>> (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<f64>, 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<f64> = (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
|
||
}
|
||
}
|