包含 IO 和 math 模块的实现: - IO: initia, levcd, linset, ltegr, ltegrd, odfset, outpri, resolv, srtfrq, start, tabini, xenini - Math: accel2, alisk1, alisk2, alist1, alist2, concor, conout, conref, contmd, contmp, coolrt, greyd, inilam, linsel, lucy, lymlin, matcon, matgen, moleq, newdm, newdmt, odf1, opacf0, opacf1, opacfa, opacfd, opacfl, opactr, opadd, opahst, pgset, princ, prnt, pzeval, quasim, radpre, radtot, rates1, ratsp1, rdata, rdatax, rechck, rhoeos, rhonen, rhsgen, rossop, rtecf1, rtecmc, rtecmu, rtecom, rtefr1, rteint, russel, rybchn, rybene, rybheq, rybsol, sgmer1, sigave, sigk, solve, solves, state, steqeq, temcor, temper, topbas, trmder, trmdrt Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
603 lines
19 KiB
Rust
603 lines
19 KiB
Rust
//! 特定能级的辐射率和碰撞率平衡计算。
|
|
//!
|
|
//! 重构自 TLUSTY `PRNT` 子程序。
|
|
//!
|
|
//! # 功能
|
|
//!
|
|
//! - 计算指定能级的辐射率和碰撞率流入/流出
|
|
//! - 用于调试和分析能级占据数的速率平衡
|
|
|
|
use crate::state::atomic::AtomicData;
|
|
use crate::state::config::InpPar;
|
|
use crate::state::constants::HK;
|
|
use crate::state::model::{CraTes, LevPop, ModPar, RrRates, WmComp};
|
|
|
|
use super::sabolf::{sabolf_pure, SabolfParams};
|
|
|
|
// ============================================================================
|
|
// 输出结构体
|
|
// ============================================================================
|
|
|
|
/// 单个能级的速率平衡结果。
|
|
#[derive(Debug, Clone)]
|
|
pub struct RateBalance {
|
|
/// 深度索引
|
|
pub id: usize,
|
|
/// 能级索引 (Fortran 1-indexed)
|
|
pub ii: i32,
|
|
/// 流出率 (辐射 + 碰撞)
|
|
pub rou: f64,
|
|
/// 流入率 (辐射 + 碰撞)
|
|
pub rin: f64,
|
|
/// 相对不平衡度 (rou - rin) / rin
|
|
pub imbalance: f64,
|
|
}
|
|
|
|
/// PRNT 输出结果。
|
|
#[derive(Debug, Clone)]
|
|
pub struct PrntOutput {
|
|
/// 各能级的速率平衡
|
|
pub balances: Vec<RateBalance>,
|
|
}
|
|
|
|
// ============================================================================
|
|
// 输入参数结构体
|
|
// ============================================================================
|
|
|
|
/// PRNT 输入参数。
|
|
pub struct PrntParams<'a> {
|
|
/// 模型基本参数
|
|
pub modpar: &'a ModPar,
|
|
/// 能级占据数
|
|
pub levpop: &'a LevPop,
|
|
/// 能级权重和占据概率
|
|
pub wmcomp: &'a WmComp,
|
|
/// 辐射率
|
|
pub rrrates: &'a RrRates,
|
|
/// 碰撞率
|
|
pub crates: &'a CraTes,
|
|
/// 原子数据
|
|
pub atomic: &'a AtomicData,
|
|
/// 配置参数
|
|
pub inppar: &'a InpPar,
|
|
/// 要分析的能级索引列表 (Fortran 1-indexed)
|
|
pub ipop: &'a [i32],
|
|
}
|
|
|
|
// ============================================================================
|
|
// 核心计算函数
|
|
// ============================================================================
|
|
|
|
/// 计算特定能级的速率平衡(纯计算部分)。
|
|
///
|
|
/// # 参数
|
|
/// - `params`: 输入参数
|
|
///
|
|
/// # 返回
|
|
/// 各能级的速率平衡结果
|
|
pub fn prnt_pure(params: &PrntParams) -> PrntOutput {
|
|
let mut balances = Vec::new();
|
|
|
|
let nd = params.modpar.temp.len();
|
|
let atomic = params.atomic;
|
|
let levpop = params.levpop;
|
|
let wmcomp = params.wmcomp;
|
|
let rrrates = params.rrrates;
|
|
let crates = params.crates;
|
|
let inppar = params.inppar;
|
|
|
|
// 遍历深度点,步长 69
|
|
for id in (0..nd).step_by(69) {
|
|
let temp = params.modpar.temp[id];
|
|
let ane = params.modpar.elec[id];
|
|
let hkt = HK / temp;
|
|
|
|
// 调用 sabolf 计算 Saha-Boltzmann 因子
|
|
let sabolf_params = SabolfParams {
|
|
id,
|
|
t: temp,
|
|
ane,
|
|
atomic,
|
|
wnhint: None,
|
|
ioptab: 0,
|
|
};
|
|
let sabolf_result = sabolf_pure(&sabolf_params);
|
|
let sbf = &sabolf_result.sbf;
|
|
let usum = &sabolf_result.usum;
|
|
|
|
// 遍历要分析的能级
|
|
for &ii_1idx in params.ipop {
|
|
// ii_1idx 是 Fortran 1-indexed,转换为 0-indexed
|
|
let ii = (ii_1idx - 3) as usize; // Fortran: ii = ipop(k) - 3
|
|
|
|
// 获取原子和离子索引
|
|
let iat = if ii < atomic.levpar.iatm.len() {
|
|
atomic.levpar.iatm[ii] as usize
|
|
} else {
|
|
continue;
|
|
};
|
|
let ie = if ii < atomic.levpar.iel.len() {
|
|
atomic.levpar.iel[ii] as usize
|
|
} else {
|
|
continue;
|
|
};
|
|
|
|
// 计算该原子所有能级的占据数之和
|
|
let n0a = if iat < atomic.atopar.n0a.len() {
|
|
atomic.atopar.n0a[iat]
|
|
} else {
|
|
continue;
|
|
};
|
|
let nka = if iat < atomic.atopar.nka.len() {
|
|
atomic.atopar.nka[iat]
|
|
} else {
|
|
continue;
|
|
};
|
|
|
|
// 验证 ii 是否在 [n0a, nka] 范围内
|
|
if (ii + 1) < n0a as usize || (ii + 1) > nka as usize {
|
|
continue;
|
|
}
|
|
|
|
let mut psum = 0.0_f64;
|
|
let mut psuu = 0.0_f64;
|
|
|
|
for j in (n0a as usize - 1)..nka as usize {
|
|
// j 是 0-indexed
|
|
psum += levpop.popul[j][id];
|
|
|
|
let ilk_j = if j < atomic.levpar.ilk.len() {
|
|
atomic.levpar.ilk[j]
|
|
} else {
|
|
0
|
|
};
|
|
|
|
if ilk_j > 0 {
|
|
let ilk_idx = (ilk_j - 1) as usize;
|
|
let usum_val = if ilk_idx < usum.len() {
|
|
usum[ilk_idx]
|
|
} else {
|
|
0.0
|
|
};
|
|
psuu += usum_val * ane * levpop.popul[j][id];
|
|
}
|
|
}
|
|
|
|
// 计算 BB = DENS(ID)/WMM(ID)/YTOT(ID)*ABUND(IAT,ID)
|
|
let dens_id = params.modpar.dens[id];
|
|
let wmm_id = inppar.wmm[id];
|
|
let ytot_id = inppar.ytot[id];
|
|
let abund_iat = if iat < atomic.atopar.abund.len() {
|
|
atomic.atopar.abund[iat][id]
|
|
} else {
|
|
0.0
|
|
};
|
|
|
|
let _bb = if wmm_id != 0.0 && ytot_id != 0.0 {
|
|
dens_id / wmm_id / ytot_id * abund_iat
|
|
} else {
|
|
0.0
|
|
};
|
|
|
|
// 获取离子参数
|
|
let nfirst = if ie < atomic.ionpar.nfirst.len() {
|
|
atomic.ionpar.nfirst[ie]
|
|
} else {
|
|
continue;
|
|
};
|
|
let nlast = if ie < atomic.ionpar.nlast.len() {
|
|
atomic.ionpar.nlast[ie]
|
|
} else {
|
|
continue;
|
|
};
|
|
let nnext = if ie < atomic.ionpar.nnext.len() {
|
|
atomic.ionpar.nnext[ie]
|
|
} else {
|
|
continue;
|
|
};
|
|
|
|
let mut rin = 0.0_f64;
|
|
let mut rou = 0.0_f64;
|
|
|
|
// 遍历较低能级 (jj < ii)
|
|
// Fortran: do jj = nfirst(ie), ii-1
|
|
for jj_1idx in nfirst..((ii + 1) as i32) {
|
|
let jj = (jj_1idx - 1) as usize; // 转换为 0-indexed
|
|
|
|
// 获取跃迁索引
|
|
let itr = if jj < atomic.trapar.itra.len()
|
|
&& ii < atomic.trapar.itra[jj].len()
|
|
{
|
|
atomic.trapar.itra[jj][ii]
|
|
} else {
|
|
continue;
|
|
};
|
|
|
|
if itr <= 0 {
|
|
continue;
|
|
}
|
|
|
|
let itr_idx = (itr - 1) as usize;
|
|
|
|
// 获取跃迁率
|
|
let rru_val = if itr_idx < rrrates.rru.len() && id < rrrates.rru[itr_idx].len() {
|
|
rrrates.rru[itr_idx][id]
|
|
} else {
|
|
0.0
|
|
};
|
|
let colrat_val =
|
|
if itr_idx < crates.colrat.len() && id < crates.colrat[itr_idx].len() {
|
|
crates.colrat[itr_idx][id]
|
|
} else {
|
|
0.0
|
|
};
|
|
let rrd_val = if itr_idx < rrrates.rrd.len() && id < rrrates.rrd[itr_idx].len() {
|
|
rrrates.rrd[itr_idx][id]
|
|
} else {
|
|
0.0
|
|
};
|
|
let coltar_val =
|
|
if itr_idx < crates.coltar.len() && id < crates.coltar[itr_idx].len() {
|
|
crates.coltar[itr_idx][id]
|
|
} else {
|
|
0.0
|
|
};
|
|
|
|
let wop_ii = if ii < wmcomp.wop.len() && id < wmcomp.wop[ii].len() {
|
|
wmcomp.wop[ii][id]
|
|
} else {
|
|
1.0
|
|
};
|
|
let wop_jj = if jj < wmcomp.wop.len() && id < wmcomp.wop[jj].len() {
|
|
wmcomp.wop[jj][id]
|
|
} else {
|
|
1.0
|
|
};
|
|
|
|
let g_jj = if jj < atomic.levpar.g.len() {
|
|
atomic.levpar.g[jj]
|
|
} else {
|
|
1.0
|
|
};
|
|
let g_ii = if ii < atomic.levpar.g.len() {
|
|
atomic.levpar.g[ii]
|
|
} else {
|
|
1.0
|
|
};
|
|
|
|
let fr0_val = if itr_idx < atomic.trapar.fr0.len() {
|
|
atomic.trapar.fr0[itr_idx]
|
|
} else {
|
|
0.0
|
|
};
|
|
|
|
// 上跃迁率 (jj -> ii)
|
|
let ru = rru_val * wop_ii;
|
|
let cu = colrat_val * wop_ii;
|
|
|
|
// 下跃迁率 (ii -> jj)
|
|
let (rd, cd) = if (ii + 1) as i32 <= nlast {
|
|
// 束缚-束缚跃迁
|
|
let rd = rrd_val * g_jj / g_ii * (hkt * fr0_val).exp() * wop_jj;
|
|
let cd = coltar_val * wop_jj;
|
|
(rd, cd)
|
|
} else {
|
|
// 束缚-自由跃迁
|
|
let sbf_jj = if jj < sbf.len() { sbf[jj] } else { 1.0 };
|
|
let rd = rrd_val * sbf_jj * ane * wop_jj;
|
|
let cd = coltar_val * wop_jj;
|
|
(rd, cd)
|
|
};
|
|
|
|
let popul_jj = if jj < levpop.popul.len() && id < levpop.popul[jj].len() {
|
|
levpop.popul[jj][id]
|
|
} else {
|
|
0.0
|
|
};
|
|
let popul_ii = if ii < levpop.popul.len() && id < levpop.popul[ii].len() {
|
|
levpop.popul[ii][id]
|
|
} else {
|
|
0.0
|
|
};
|
|
|
|
rin += (ru + cu) * popul_jj;
|
|
rou += (rd + cd) * popul_ii;
|
|
}
|
|
|
|
// 遍历较高能级 (jj > ii)
|
|
// Fortran: do jj = ii+1, nnext(ie)
|
|
for jj_1idx in ((ii + 2) as i32)..=nnext {
|
|
let jj = (jj_1idx - 1) as usize; // 转换为 0-indexed
|
|
|
|
// 获取跃迁索引
|
|
let itr = if ii < atomic.trapar.itra.len()
|
|
&& jj < atomic.trapar.itra[ii].len()
|
|
{
|
|
atomic.trapar.itra[ii][jj]
|
|
} else {
|
|
continue;
|
|
};
|
|
|
|
if itr <= 0 {
|
|
continue;
|
|
}
|
|
|
|
let itr_idx = (itr - 1) as usize;
|
|
|
|
// 获取跃迁率
|
|
let rru_val = if itr_idx < rrrates.rru.len() && id < rrrates.rru[itr_idx].len() {
|
|
rrrates.rru[itr_idx][id]
|
|
} else {
|
|
0.0
|
|
};
|
|
let colrat_val =
|
|
if itr_idx < crates.colrat.len() && id < crates.colrat[itr_idx].len() {
|
|
crates.colrat[itr_idx][id]
|
|
} else {
|
|
0.0
|
|
};
|
|
let rrd_val = if itr_idx < rrrates.rrd.len() && id < rrrates.rrd[itr_idx].len() {
|
|
rrrates.rrd[itr_idx][id]
|
|
} else {
|
|
0.0
|
|
};
|
|
let coltar_val =
|
|
if itr_idx < crates.coltar.len() && id < crates.coltar[itr_idx].len() {
|
|
crates.coltar[itr_idx][id]
|
|
} else {
|
|
0.0
|
|
};
|
|
|
|
let wop_ii = if ii < wmcomp.wop.len() && id < wmcomp.wop[ii].len() {
|
|
wmcomp.wop[ii][id]
|
|
} else {
|
|
1.0
|
|
};
|
|
let wop_jj = if jj < wmcomp.wop.len() && id < wmcomp.wop[jj].len() {
|
|
wmcomp.wop[jj][id]
|
|
} else {
|
|
1.0
|
|
};
|
|
|
|
let g_jj = if jj < atomic.levpar.g.len() {
|
|
atomic.levpar.g[jj]
|
|
} else {
|
|
1.0
|
|
};
|
|
let g_ii = if ii < atomic.levpar.g.len() {
|
|
atomic.levpar.g[ii]
|
|
} else {
|
|
1.0
|
|
};
|
|
|
|
let fr0_val = if itr_idx < atomic.trapar.fr0.len() {
|
|
atomic.trapar.fr0[itr_idx]
|
|
} else {
|
|
0.0
|
|
};
|
|
|
|
// 上跃迁率 (ii -> jj)
|
|
let ru = rru_val * wop_jj;
|
|
let cu = colrat_val * wop_jj;
|
|
|
|
// 下跃迁率 (jj -> ii)
|
|
let (rd, cd) = if jj_1idx <= nlast {
|
|
// 束缚-束缚跃迁
|
|
let rd = rrd_val * g_ii / g_jj * (hkt * fr0_val).exp() * wop_ii;
|
|
let cd = coltar_val * wop_ii;
|
|
(rd, cd)
|
|
} else {
|
|
// 束缚-自由跃迁
|
|
let sbf_ii = if ii < sbf.len() { sbf[ii] } else { 1.0 };
|
|
let rd = rrd_val * sbf_ii * ane * wop_ii;
|
|
let cd = coltar_val * wop_ii;
|
|
(rd, cd)
|
|
};
|
|
|
|
let popul_jj = if jj < levpop.popul.len() && id < levpop.popul[jj].len() {
|
|
levpop.popul[jj][id]
|
|
} else {
|
|
0.0
|
|
};
|
|
let popul_ii = if ii < levpop.popul.len() && id < levpop.popul[ii].len() {
|
|
levpop.popul[ii][id]
|
|
} else {
|
|
0.0
|
|
};
|
|
|
|
rou += (ru + cu) * popul_ii;
|
|
rin += (rd + cd) * popul_jj;
|
|
}
|
|
|
|
// 计算相对不平衡度
|
|
let imbalance = if rin != 0.0 { (rou - rin) / rin } else { 0.0 };
|
|
|
|
balances.push(RateBalance {
|
|
id,
|
|
ii: ii_1idx,
|
|
rou,
|
|
rin,
|
|
imbalance,
|
|
});
|
|
}
|
|
}
|
|
|
|
PrntOutput { balances }
|
|
}
|
|
|
|
// ============================================================================
|
|
// 测试
|
|
// ============================================================================
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use crate::state::atomic::{AtoPar, IonPar, LevPar, TraPar};
|
|
use crate::state::config::InpPar;
|
|
use crate::state::constants::{MDEPTH, MION, MLEVEL, MTRANS};
|
|
use crate::state::model::{CraTes, LevPop, ModPar, RrRates, WmComp};
|
|
|
|
fn create_test_modpar() -> ModPar {
|
|
let mut modpar = ModPar::default();
|
|
modpar.temp[0] = 10000.0;
|
|
modpar.temp[1] = 9000.0;
|
|
modpar.elec[0] = 1.0e12;
|
|
modpar.elec[1] = 1.0e11;
|
|
modpar.dens[0] = 1.0e14;
|
|
modpar.dens[1] = 1.0e13;
|
|
modpar
|
|
}
|
|
|
|
fn create_test_levpop() -> LevPop {
|
|
LevPop::default()
|
|
}
|
|
|
|
fn create_test_atomic() -> AtomicData {
|
|
let mut atomic = AtomicData::default();
|
|
|
|
// 设置能级数据
|
|
for i in 0..100 {
|
|
atomic.levpar.g[i] = 2.0;
|
|
atomic.levpar.iatm[i] = 1;
|
|
atomic.levpar.iel[i] = 1;
|
|
atomic.levpar.ilk[i] = 0;
|
|
atomic.levpar.enion[i] = 10.0 - i as f64 * 0.1;
|
|
}
|
|
|
|
// 设置原子数据
|
|
atomic.atopar.n0a[0] = 1;
|
|
atomic.atopar.nka[0] = 50;
|
|
atomic.atopar.abund[0][0] = 0.1;
|
|
|
|
// 设置离子数据 - 只设置第一个离子,并确保 nfirst >= 1
|
|
atomic.ionpar.nfirst[0] = 1;
|
|
atomic.ionpar.nlast[0] = 50;
|
|
atomic.ionpar.nnext[0] = 51;
|
|
atomic.ionpar.iz[0] = 1;
|
|
|
|
// 清空其他离子的数据,避免 sabolf 处理无效离子
|
|
// sabolf 基于 iz.len() 遍历,而 iz.len() = MION
|
|
// 为了避免处理无效离子,设置 nfirst > nlast 使循环跳过
|
|
// 同时确保 nlast >= 1 避免 nlst = nlast - 1 下溢
|
|
for i in 1..MION {
|
|
atomic.ionpar.nfirst[i] = 2; // nfirst > nlast
|
|
atomic.ionpar.nlast[i] = 1;
|
|
atomic.ionpar.nnext[i] = 0;
|
|
}
|
|
|
|
atomic
|
|
}
|
|
|
|
fn create_test_wmcomp() -> WmComp {
|
|
let mut wmcomp = WmComp::default();
|
|
for i in 0..10 {
|
|
for j in 0..MDEPTH {
|
|
wmcomp.wop[i][j] = 1.0;
|
|
}
|
|
}
|
|
wmcomp
|
|
}
|
|
|
|
fn create_test_rrrates() -> RrRates {
|
|
RrRates::default()
|
|
}
|
|
|
|
fn create_test_crates() -> CraTes {
|
|
CraTes::default()
|
|
}
|
|
|
|
fn create_test_inppar() -> InpPar {
|
|
let mut inppar = InpPar::default();
|
|
inppar.wmm[0] = 1.0;
|
|
inppar.ytot[0] = 1.0;
|
|
inppar
|
|
}
|
|
|
|
#[test]
|
|
fn test_prnt_basic() {
|
|
let modpar = create_test_modpar();
|
|
let levpop = create_test_levpop();
|
|
let atomic = create_test_atomic();
|
|
let wmcomp = create_test_wmcomp();
|
|
let rrrates = create_test_rrrates();
|
|
let crates = create_test_crates();
|
|
let inppar = create_test_inppar();
|
|
|
|
// 测试能级索引 (Fortran 1-indexed)
|
|
let ipop = [98, 99, 100, 115];
|
|
|
|
let params = PrntParams {
|
|
modpar: &modpar,
|
|
levpop: &levpop,
|
|
wmcomp: &wmcomp,
|
|
rrrates: &rrrates,
|
|
crates: &crates,
|
|
atomic: &atomic,
|
|
inppar: &inppar,
|
|
ipop: &ipop,
|
|
};
|
|
|
|
let result = prnt_pure(¶ms);
|
|
|
|
// 由于测试数据是空的,结果应该为空或只有有限的结果
|
|
println!("Number of balances: {}", result.balances.len());
|
|
}
|
|
|
|
#[test]
|
|
fn test_prnt_with_populations() {
|
|
let _modpar = create_test_modpar();
|
|
let mut levpop = create_test_levpop();
|
|
let atomic = create_test_atomic();
|
|
let wmcomp = create_test_wmcomp();
|
|
let rrrates = create_test_rrrates();
|
|
let crates = create_test_crates();
|
|
let inppar = create_test_inppar();
|
|
|
|
// 设置一些非零占据数
|
|
for i in 0..50 {
|
|
levpop.popul[i][0] = 0.01;
|
|
}
|
|
|
|
// 测试能级索引
|
|
let ipop = [100]; // 只测试能级 100
|
|
|
|
let params = PrntParams {
|
|
modpar: &_modpar,
|
|
levpop: &levpop,
|
|
wmcomp: &wmcomp,
|
|
rrrates: &rrrates,
|
|
crates: &crates,
|
|
atomic: &atomic,
|
|
inppar: &inppar,
|
|
ipop: &ipop,
|
|
};
|
|
|
|
let result = prnt_pure(¶ms);
|
|
|
|
// 验证结果
|
|
for balance in &result.balances {
|
|
println!(
|
|
"id={}, ii={}, rou={}, rin={}, imbalance={}",
|
|
balance.id, balance.ii, balance.rou, balance.rin, balance.imbalance
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_rate_balance_structure() {
|
|
let balance = RateBalance {
|
|
id: 0,
|
|
ii: 100,
|
|
rou: 1.0e10,
|
|
rin: 1.0e10,
|
|
imbalance: 0.0,
|
|
};
|
|
|
|
assert_eq!(balance.id, 0);
|
|
assert_eq!(balance.ii, 100);
|
|
assert!((balance.rou - 1.0e10).abs() < 1e5);
|
|
assert!((balance.rin - 1.0e10).abs() < 1e5);
|
|
assert!(balance.imbalance.abs() < 1e-10);
|
|
}
|
|
}
|