849 lines
26 KiB
Rust
849 lines
26 KiB
Rust
//! 模型参数初始化,用于 RESOLV。
|
||
//!
|
||
//! 重构自 TLUSTY `inilam.f`
|
||
//!
|
||
//! # 功能
|
||
//!
|
||
//! RESOLV 的辅助过程,初始化模型参数以供后续使用。
|
||
//!
|
||
//! # 两个主要分支
|
||
//!
|
||
//! 1. **INIT=1** (第一次完全线性化迭代前):
|
||
//! - 计算所有跃迁、所有深度的碰撞速率
|
||
//! - 初始化 b 因子、固定电荷等
|
||
//!
|
||
//! 2. **INIT≠1** (完全线性化迭代完成后):
|
||
//! - 更新温度、电子密度、密度等
|
||
//! - 计算新的占据数
|
||
//! - 求解辐射转移方程
|
||
|
||
use crate::tlusty::state::constants::{BOLK, HALF, MDEPTH, MFREQ, MLEVEL, MTRANS, PCK, SIG4P, UN};
|
||
// f2r_depends: COLIS, COMSET, CONCOR, DIETOT, ELCOR, ODFMER, OPACF1, OPAINI, OSCCOR, OUTPUT, RATES1, RTECOM, RTEFR1, RYBHEQ, SABOLF, STEQEQ, TDPINI, VISINI, WNSTOR
|
||
|
||
// ============================================================================
|
||
// 配置参数
|
||
// ============================================================================
|
||
|
||
/// INILAM 配置参数(只读)。
|
||
#[derive(Debug, Clone)]
|
||
pub struct InilamConfig {
|
||
/// 初始化标志 (1=第一次迭代前)
|
||
pub init: i32,
|
||
/// 迭代次数
|
||
pub iter: i32,
|
||
/// LTE 模式标志
|
||
pub lte: bool,
|
||
/// IPSLTE 参数
|
||
pub ipslte: i32,
|
||
/// IDLTE 参数
|
||
pub idlte: i32,
|
||
/// IOPABT 参数(选项表)
|
||
pub ioptab: i32,
|
||
/// IDISK 参数(盘模型)
|
||
pub idisk: i32,
|
||
/// 有效温度 (K)
|
||
pub teff: f64,
|
||
/// IFPOPR 参数
|
||
pub ifpopr: i32,
|
||
/// IFRYB 参数
|
||
pub ifryb: i32,
|
||
/// IOSCOR 参数(振荡修正)
|
||
pub ioscor: i32,
|
||
/// IHECOR 参数(流体静力平衡修正)
|
||
pub ihecor: i32,
|
||
/// IFIXDE 参数(固定密度)
|
||
pub ifixde: i32,
|
||
/// ISPLIN 参数
|
||
pub isplin: i32,
|
||
/// IFDIEL 参数(双电子复合)
|
||
pub ifdiel: i32,
|
||
/// ICOMPT 参数(康普顿散射)
|
||
pub icompt: i32,
|
||
/// IPRIND 参数(打印控制)
|
||
pub iprind: i32,
|
||
/// LCHC 参数
|
||
pub lchc: bool,
|
||
/// IELCOR 参数(电子修正迭代)
|
||
pub ielcor: i32,
|
||
/// INRE 参数(辐射平衡)
|
||
pub inre: i32,
|
||
/// INPC 参数(粒子守恒)
|
||
pub inpc: i32,
|
||
/// INHE 参数(流体静力平衡)
|
||
pub inhe: i32,
|
||
/// INMP 参数(大质量粒子)
|
||
pub inmp: i32,
|
||
/// INZD 参数(几何距离)
|
||
pub inzd: i32,
|
||
/// INDL 参数(温度对数梯度)
|
||
pub indl: i32,
|
||
/// NFREQE 参数(线性化频率数)
|
||
pub nfreqe: usize,
|
||
/// DPSILN 参数(密度变化限制)
|
||
pub dpsiln: f64,
|
||
}
|
||
|
||
impl Default for InilamConfig {
|
||
fn default() -> Self {
|
||
Self {
|
||
init: 1,
|
||
iter: 0,
|
||
lte: false,
|
||
ipslte: 0,
|
||
idlte: 100,
|
||
ioptab: 0,
|
||
idisk: 0,
|
||
teff: 10000.0,
|
||
ifpopr: 0,
|
||
ifryb: 0,
|
||
ioscor: 0,
|
||
ihecor: 0,
|
||
ifixde: 0,
|
||
isplin: 0,
|
||
ifdiel: 0,
|
||
icompt: 0,
|
||
iprind: 0,
|
||
lchc: false,
|
||
ielcor: 10,
|
||
inre: 0,
|
||
inpc: 0,
|
||
inhe: 0,
|
||
inmp: 0,
|
||
inzd: 0,
|
||
indl: 0,
|
||
nfreqe: 0,
|
||
dpsiln: 10.0_f64.exp(), // e^10
|
||
}
|
||
}
|
||
}
|
||
|
||
// ============================================================================
|
||
// 模型状态参数
|
||
// ============================================================================
|
||
|
||
/// INILAM 模型状态参数。
|
||
#[derive(Debug)]
|
||
pub struct InilamModelState<'a> {
|
||
/// 深度点数
|
||
pub nd: usize,
|
||
/// 能级数
|
||
pub nlevel: usize,
|
||
/// 离子数
|
||
pub nion: usize,
|
||
/// 跃迁数
|
||
pub ntrans: usize,
|
||
/// 频率点数
|
||
pub nfreq: usize,
|
||
|
||
// 深度相关数组 [nd]
|
||
/// 柱质量密度 (g/cm²)
|
||
pub dm: &'a [f64],
|
||
/// 温度 (K)
|
||
pub temp: &'a mut [f64],
|
||
/// 电子密度 (cm⁻³)
|
||
pub elec: &'a mut [f64],
|
||
/// 总粒子密度 (cm⁻³)
|
||
pub dens: &'a mut [f64],
|
||
/// 总粒子数
|
||
pub totn: &'a mut [f64],
|
||
/// 总原子密度
|
||
pub anto: &'a mut [f64],
|
||
/// 金属原子密度
|
||
pub anma: &'a mut [f64],
|
||
/// 气压
|
||
pub pgs: &'a mut [f64],
|
||
/// 辐射压力导数
|
||
pub pradt: &'a mut [f64],
|
||
/// 湍流速度
|
||
pub vturb: &'a [f64],
|
||
/// 平均分子量
|
||
pub wmm: &'a [f64],
|
||
|
||
// 能级相关数组 [nlevel × nd]
|
||
/// 占据数
|
||
pub popul: &'a mut [f64], // [nlevel * nd]
|
||
/// b 因子
|
||
pub bfac: &'a mut [f64], // [nlevel * nd]
|
||
|
||
// 跃迁相关数组 [ntrans × nd]
|
||
/// 碰撞速率
|
||
pub colrat: &'a mut [f64], // [ntrans * nd]
|
||
/// 碰撞速率目标
|
||
pub coltar: &'a mut [f64], // [ntrans * nd]
|
||
|
||
// 频率相关数组 [nfreq × nd]
|
||
/// 辐射强度
|
||
pub rad: &'a mut [f64], // [nfreq * nd]
|
||
|
||
// 辅助数组
|
||
/// FCOOL 数组 [nd]
|
||
pub fcool: &'a mut [f64],
|
||
/// FPRD 数组 [nd]
|
||
pub fprd: &'a mut [f64],
|
||
/// QFIX 数组 [nd]
|
||
pub qfix: &'a mut [f64],
|
||
|
||
// PSY0 数组 [总变量数 × nd]
|
||
pub psy0: &'a [f64],
|
||
|
||
// 气体压力 (用于 EOS)
|
||
pub ptotal: &'a [f64],
|
||
|
||
// 距离变量
|
||
pub zd: &'a mut [f64],
|
||
|
||
// 温度梯度
|
||
pub delta: &'a mut [f64],
|
||
}
|
||
|
||
/// INILAM 原子参数。
|
||
#[derive(Debug)]
|
||
pub struct InilamAtomicParams<'a> {
|
||
/// 能级原子索引 [nlevel]
|
||
pub iatm: &'a [i32],
|
||
/// 能级离子索引 [nlevel]
|
||
pub iel: &'a [i32],
|
||
/// 能级类型 [nlevel]
|
||
pub ilk: &'a [i32],
|
||
/// 固定丰度标志 [natom]
|
||
pub iifix: &'a [i32],
|
||
/// 能级模型标志 [nlevel]
|
||
pub imodl: &'a [i32],
|
||
/// LTE 能级标志 [nlevel]
|
||
pub iltlev: &'a [i32],
|
||
/// 显式能级索引 [nlevel]
|
||
pub iiexp: &'a [i32],
|
||
/// 非零索引 [nlvexp]
|
||
pub iinonz: &'a [i32],
|
||
/// LTE 参考能级 [nlevel × nd]
|
||
pub iltref: &'a [i32],
|
||
/// SBPSI 数组 [nlevel × nd]
|
||
pub sbpsi: &'a [f64],
|
||
/// 离子电荷 [nion]
|
||
pub iz: &'a [i32],
|
||
/// 离子起始能级 [nion]
|
||
pub nfirst: &'a [i32],
|
||
/// 离子终止能级 [nion]
|
||
pub nlast: &'a [i32],
|
||
/// 下一个离子能级 [nion]
|
||
pub nnext: &'a [i32],
|
||
/// SBF 数组 [nlevel]
|
||
pub sbf: &'a [f64],
|
||
/// WOP 数组 [nlevel × nd]
|
||
pub wop: &'a [f64],
|
||
/// USUM 数组 [nion]
|
||
pub usum: &'a [f64],
|
||
}
|
||
|
||
/// INILAM 频率参数。
|
||
#[derive(Debug)]
|
||
pub struct InilamFreqParams<'a> {
|
||
/// 频率数组 [nfreq]
|
||
pub freq: &'a [f64],
|
||
/// BNUE 数组 [nfreq]
|
||
pub bnue: &'a [f64],
|
||
/// FH 数组 [nfreq]
|
||
pub fh: &'a [f64],
|
||
/// HEXTRD 数组 [nfreq]
|
||
pub hextrd: &'a [f64],
|
||
/// 权重数组 [nfreq]
|
||
pub w: &'a [f64],
|
||
/// h/(kT) 数组 [nd]
|
||
pub hkt1: &'a [f64],
|
||
}
|
||
|
||
/// INILAM 当前频率输出。
|
||
#[derive(Debug, Clone)]
|
||
pub struct InilamFreqOutput {
|
||
/// ABSO1 数组 [nd]
|
||
pub abso1: Vec<f64>,
|
||
/// RAD1 数组 [nd]
|
||
pub rad1: Vec<f64>,
|
||
/// FAK1 数组 [nd]
|
||
pub fak1: Vec<f64>,
|
||
}
|
||
|
||
// ============================================================================
|
||
// 输出结构体
|
||
// ============================================================================
|
||
|
||
/// INILAM 输出结构体。
|
||
#[derive(Debug, Clone)]
|
||
pub struct InilamOutput {
|
||
/// PRAD 参数
|
||
pub prad: f64,
|
||
/// PRD0 参数
|
||
pub prd0: f64,
|
||
/// ANEREL 参数
|
||
pub anerel: f64,
|
||
/// AMUV0 参数
|
||
pub amuv0: f64,
|
||
/// AMUV1 参数
|
||
pub amuv1: f64,
|
||
/// DMTOT 参数
|
||
pub dmtot: f64,
|
||
/// EDISC 参数
|
||
pub edisc: f64,
|
||
/// GRD 数组 [nd]
|
||
pub grd: Vec<f64>,
|
||
/// PRA 数组 [nd]
|
||
pub pra: Vec<f64>,
|
||
}
|
||
|
||
impl Default for InilamOutput {
|
||
fn default() -> Self {
|
||
Self {
|
||
prad: 0.0,
|
||
prd0: 0.0,
|
||
anerel: 0.5,
|
||
amuv0: 0.0,
|
||
amuv1: 1.0,
|
||
dmtot: 0.0,
|
||
edisc: 0.0,
|
||
grd: vec![0.0; MDEPTH],
|
||
pra: vec![0.0; MDEPTH],
|
||
}
|
||
}
|
||
}
|
||
|
||
// ============================================================================
|
||
// 辅助函数
|
||
// ============================================================================
|
||
|
||
/// 获取二维数组的元素(Fortran 列优先顺序)。
|
||
#[inline]
|
||
fn get_2d<T>(arr: &[T], i: usize, j: usize, nrows: usize) -> &T {
|
||
&arr[j * nrows + i]
|
||
}
|
||
|
||
/// 获取二维数组的可变元素(Fortran 列优先顺序)。
|
||
#[inline]
|
||
fn get_2d_mut<T>(arr: &mut [T], i: usize, j: usize, nrows: usize) -> &mut T {
|
||
&mut arr[j * nrows + i]
|
||
}
|
||
|
||
/// 设置二维数组的元素(Fortran 列优先顺序)。
|
||
#[inline]
|
||
fn set_2d<T: Copy>(arr: &mut [T], i: usize, j: usize, nrows: usize, val: T) {
|
||
arr[j * nrows + i] = val;
|
||
}
|
||
|
||
// ============================================================================
|
||
// 主函数
|
||
// ============================================================================
|
||
|
||
/// INILAM 纯计算函数。
|
||
///
|
||
/// # 参数
|
||
/// - `config`: 配置参数
|
||
/// - `model`: 模型状态(会被修改)
|
||
/// - `atomic`: 原子参数
|
||
/// - `freq`: 频率参数
|
||
///
|
||
/// # 返回值
|
||
/// 返回 InilamOutput 结构体
|
||
///
|
||
/// # 注意
|
||
/// 此函数仅实现核心计算逻辑,不包含 I/O 操作。
|
||
/// 外部函数调用(如 TDPINI、WNSTOR 等)需要通过回调实现。
|
||
pub fn inilam_pure(
|
||
config: &InilamConfig,
|
||
model: &mut InilamModelState,
|
||
atomic: &InilamAtomicParams,
|
||
freq: &InilamFreqParams,
|
||
) -> InilamOutput {
|
||
let nd = model.nd;
|
||
let nlevel = model.nlevel;
|
||
let nion = model.nion;
|
||
let ntrans = model.ntrans;
|
||
let nfreq = model.nfreq;
|
||
|
||
let mut output = InilamOutput::default();
|
||
output.grd.truncate(nd);
|
||
output.grd.resize(nd, 0.0);
|
||
output.pra.truncate(nd);
|
||
output.pra.resize(nd, 0.0);
|
||
|
||
// 初始化 ANEREL
|
||
output.anerel = 0.5;
|
||
if config.teff < 8000.0 {
|
||
output.anerel = 0.01;
|
||
}
|
||
|
||
// ================================================================
|
||
// INIT = 1 分支:第一次迭代前
|
||
// ================================================================
|
||
if config.init == 1 {
|
||
// 盘模型参数
|
||
if config.idisk == 1 {
|
||
// AMUV0 = DMVISC**(ZETA0+UN) - 这里需要外部参数
|
||
// AMUV1 = UN - AMUV0
|
||
output.dmtot = model.dm[nd - 1]; // Fortran DM(ND)
|
||
output.edisc = SIG4P * config.teff.powi(4) / output.dmtot;
|
||
}
|
||
|
||
// 对每个深度点进行初始化
|
||
for id in 0..nd {
|
||
// FCOOL 和 FPRD 初始化
|
||
model.fcool[id] = 0.0;
|
||
model.fprd[id] = 0.0;
|
||
|
||
// 初始化 b 因子
|
||
for i in 0..nlevel {
|
||
set_2d(&mut model.bfac, i, id, nlevel, UN);
|
||
|
||
// SBW(I) = ELEC(ID) * SBF(I) * WOP(I,ID)
|
||
let sbw = model.elec[id] * atomic.sbf[i] * *get_2d(atomic.wop, i, id, nlevel);
|
||
|
||
// 如果非 LTE 且 IPSLTE=0 且 id < idlte
|
||
if !config.lte && config.ipslte == 0 && id < config.idlte as usize {
|
||
// 对每个离子
|
||
for ion in 0..nion {
|
||
let nf = atomic.nfirst[ion] as usize;
|
||
let nl = atomic.nlast[ion] as usize;
|
||
let nn = atomic.nnext[ion] as usize;
|
||
|
||
if nn > 0 && nn <= nlevel {
|
||
let pop_next = *get_2d(model.popul, nn, id, nlevel);
|
||
if pop_next > 0.0 && atomic.iltlev[i] == 0 {
|
||
let pop_i = *get_2d(model.popul, i, id, nlevel);
|
||
let bfac_val = pop_i / (pop_next * sbw);
|
||
set_2d(&mut model.bfac, i, id, nlevel, bfac_val);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 固定电荷部分 QFIX
|
||
model.qfix[id] = 0.0;
|
||
for i in 0..nlevel {
|
||
let imodl = atomic.imodl[i];
|
||
let iatm = atomic.iatm[i] as usize;
|
||
if imodl < 0 || (iatm < atomic.iifix.len() && atomic.iifix[iatm] > 0) {
|
||
let mut ch = (atomic.iz[atomic.iel[i] as usize] - 1) as f64;
|
||
let il = atomic.ilk[i];
|
||
if il > 0 {
|
||
let il_usize = il as usize;
|
||
ch = (atomic.iz[il_usize] as f64)
|
||
+ (atomic.iz[il_usize] as f64 - 1.0) * atomic.usum[il_usize] * model.elec[id];
|
||
}
|
||
model.qfix[id] += ch * *get_2d(model.popul, i, id, nlevel);
|
||
}
|
||
}
|
||
|
||
// 碰撞速率(非 LTE 情况)
|
||
if !config.lte {
|
||
// COLIS 调用需要外部实现
|
||
// 这里只初始化数组
|
||
for it in 0..ntrans {
|
||
set_2d(&mut model.colrat, it, id, ntrans, 0.0);
|
||
set_2d(&mut model.coltar, it, id, ntrans, 0.0);
|
||
}
|
||
}
|
||
}
|
||
|
||
// ISPLIN >= 5: 电子散射源函数的 Planck 函数估计
|
||
if config.isplin >= 5 {
|
||
for id in 0..nd {
|
||
for ij in 0..nfreq {
|
||
let rad_val = freq.bnue[ij]
|
||
/ (freq.hkt1[id] * freq.freq[ij]).exp() - UN;
|
||
set_2d(&mut model.rad, ij, id, nfreq, rad_val);
|
||
}
|
||
}
|
||
}
|
||
|
||
return output;
|
||
}
|
||
|
||
// ================================================================
|
||
// INIT ≠ 1 分支:迭代后更新
|
||
// ================================================================
|
||
|
||
// PRAD 初始化
|
||
output.prad = 0.0;
|
||
|
||
// 工作数组
|
||
let mut xe = vec![0.0; nd];
|
||
let mut antc = vec![0.0; nd];
|
||
|
||
// 保存旧量并更新
|
||
for id in 0..nd {
|
||
// AOLD = DENS(ID)/WMM(ID) + ELEC(ID)
|
||
let aold = model.dens[id] / model.wmm[id] + model.elec[id];
|
||
xe[id] = UN - model.elec[id] / aold;
|
||
|
||
// 更新温度(如果求解辐射平衡)
|
||
if config.inre != 0 {
|
||
let psy_idx = (config.nfreqe + config.inre as usize) * nd + id;
|
||
if psy_idx < model.psy0.len() {
|
||
model.temp[id] = model.psy0[psy_idx];
|
||
}
|
||
}
|
||
|
||
// 更新电子密度(如果求解粒子守恒)
|
||
if config.inpc != 0 {
|
||
let psy_idx = (config.nfreqe + config.inpc as usize) * nd + id;
|
||
if psy_idx < model.psy0.len() {
|
||
model.elec[id] = model.psy0[psy_idx];
|
||
}
|
||
}
|
||
|
||
// 更新总粒子数(如果求解流体静力平衡)
|
||
if config.inhe != 0 {
|
||
let psy_idx = (config.nfreqe + config.inhe as usize) * nd + id;
|
||
if psy_idx < model.psy0.len() {
|
||
model.totn[id] = model.psy0[psy_idx];
|
||
}
|
||
}
|
||
|
||
// 密度更新
|
||
if config.ifixde == 0 {
|
||
let dens0 = model.dens[id];
|
||
|
||
if config.inhe != 0 {
|
||
let psy_idx = (config.nfreqe + config.inhe as usize) * nd + id;
|
||
if psy_idx < model.psy0.len() {
|
||
model.dens[id] = model.wmm[id] * (model.psy0[psy_idx] - model.elec[id]);
|
||
}
|
||
let dplp = dens0 * config.dpsiln;
|
||
let dplm = dens0 / config.dpsiln;
|
||
if model.dens[id] > dplp {
|
||
model.dens[id] = dplp;
|
||
}
|
||
if model.dens[id] < dplm {
|
||
model.dens[id] = dplm;
|
||
}
|
||
} else if config.ioptab < -1 {
|
||
model.pgs[id] = model.ptotal[id];
|
||
// DENS(ID) = RHOEOS(TEMP(ID), PGS(ID)) - 需要外部调用
|
||
}
|
||
}
|
||
|
||
// 大质量粒子密度更新
|
||
if config.inmp != 0 {
|
||
let psy_idx = (config.nfreqe + config.inmp as usize) * nd + id;
|
||
if psy_idx < model.psy0.len() {
|
||
model.dens[id] = model.wmm[id] * model.psy0[psy_idx];
|
||
}
|
||
}
|
||
|
||
// 几何距离
|
||
if config.inzd > 0 {
|
||
let psy_idx = (config.nfreqe + config.inzd as usize) * nd + id;
|
||
if psy_idx < model.psy0.len() {
|
||
model.zd[id] = model.psy0[psy_idx];
|
||
}
|
||
}
|
||
|
||
// 温度对数梯度
|
||
if config.indl != 0 {
|
||
let psy_idx = (config.nfreqe + config.indl as usize) * nd + id;
|
||
if psy_idx < model.psy0.len() {
|
||
model.delta[id] = model.psy0[psy_idx];
|
||
}
|
||
}
|
||
|
||
// 更新 ANMA 和 ANTO
|
||
model.anma[id] = model.dens[id] / model.wmm[id];
|
||
model.anto[id] = model.anma[id] + model.elec[id];
|
||
}
|
||
|
||
// 流体静力平衡修正(如果需要)
|
||
if config.ihecor >= 2 {
|
||
// ID = 1
|
||
let ptur = HALF * model.vturb[0] * model.vturb[0] * model.dens[0];
|
||
antc[0] = (model.dm[0] * config.teff * 0.0 - output.prd0 - ptur) // GRAV 需要外部传入
|
||
/ BOLK / model.temp[0];
|
||
if antc[0] <= 0.0 {
|
||
antc[0] = model.dens[0] / model.wmm[0] + model.elec[0];
|
||
}
|
||
|
||
for id in 1..nd {
|
||
let ptur = HALF * model.vturb[id] * model.vturb[id] * model.dens[id];
|
||
let pturm = HALF * model.vturb[id - 1] * model.vturb[id - 1] * model.dens[id - 1];
|
||
// 简化计算,GRAV 需要外部传入
|
||
antc[id] = (model.temp[id - 1] * antc[id - 1] * BOLK - model.pradt[id] + model.pradt[id - 1] - ptur + pturm)
|
||
/ BOLK / model.temp[id];
|
||
}
|
||
|
||
for id in 0..nd {
|
||
model.elec[id] = (UN - xe[id]) * antc[id];
|
||
model.dens[id] = model.wmm[id] * (antc[id] - model.elec[id]);
|
||
model.anma[id] = model.dens[id] / model.wmm[id];
|
||
model.anto[id] = model.anma[id] + model.elec[id];
|
||
}
|
||
}
|
||
|
||
// 更新 PGS
|
||
for id in 0..nd {
|
||
model.pgs[id] = (model.dens[id] / model.wmm[id] + model.elec[id]) * BOLK * model.temp[id];
|
||
}
|
||
|
||
// IOPTAB >= 0 时的处理
|
||
if config.ioptab >= 0 {
|
||
for id in 0..nd {
|
||
// 碰撞速率更新(非 LTE)
|
||
if !config.lte {
|
||
for it in 0..ntrans {
|
||
set_2d(&mut model.colrat, it, id, ntrans, 0.0);
|
||
set_2d(&mut model.coltar, it, id, ntrans, 0.0);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 新占据数计算
|
||
// 分支 1: IFPOPR <= 0 或 LTE 或 IFRYB > 0
|
||
if config.ifpopr <= 0 || config.lte || config.ifryb > 0 {
|
||
// RATES1 调用需要外部实现
|
||
|
||
// 深度遍历求解统计平衡
|
||
for id in 0..nd {
|
||
// STEQEQ 和 ELCOR 需要外部实现
|
||
}
|
||
} else {
|
||
// 分支 2: 从线性化修正获取占据数
|
||
for id in 0..nd {
|
||
for i in 0..nlevel {
|
||
let iatm = atomic.iatm[i] as usize;
|
||
if iatm < atomic.iifix.len() && atomic.iifix[iatm] != 1 {
|
||
let ii = atomic.iiexp[i];
|
||
if ii > 0 {
|
||
let iii = atomic.iinonz[ii as usize];
|
||
if iii > 0 {
|
||
let psy_idx = (config.nfreqe + config.inre as usize + iii as usize - 1) * nd + id;
|
||
if psy_idx < model.psy0.len() {
|
||
set_2d(&mut model.popul, i, id, nlevel, model.psy0[psy_idx]);
|
||
}
|
||
} else {
|
||
set_2d(&mut model.popul, i, id, nlevel, 0.0);
|
||
}
|
||
} else if ii < 0 {
|
||
let iii = atomic.iinonz[(-ii) as usize];
|
||
if iii > 0 {
|
||
let psy_idx = (config.nfreqe + config.inre as usize + iii as usize - 1) * nd + id;
|
||
if psy_idx < model.psy0.len() {
|
||
let pop_val = model.psy0[psy_idx] * *get_2d(atomic.sbpsi, i, id, nlevel);
|
||
set_2d(&mut model.popul, i, id, nlevel, pop_val);
|
||
}
|
||
} else {
|
||
set_2d(&mut model.popul, i, id, nlevel, 0.0);
|
||
}
|
||
} else {
|
||
// II = 0 情况
|
||
let iltref_val = *get_2d(atomic.iltref, i, id, nlevel);
|
||
if iltref_val >= 0 && (iltref_val as usize) < atomic.iiexp.len() {
|
||
let ii_ref = atomic.iiexp[iltref_val as usize];
|
||
if ii_ref > 0 && (ii_ref as usize) < atomic.iinonz.len() {
|
||
let iii = atomic.iinonz[ii_ref as usize];
|
||
if iii > 0 {
|
||
let psy_idx = (config.nfreqe + config.inre as usize + iii as usize - 1) * nd + id;
|
||
if psy_idx < model.psy0.len() {
|
||
let pop_val = model.psy0[psy_idx] * *get_2d(atomic.sbpsi, i, id, nlevel);
|
||
set_2d(&mut model.popul, i, id, nlevel, pop_val);
|
||
}
|
||
} else {
|
||
set_2d(&mut model.popul, i, id, nlevel, 0.0);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 康普顿散射
|
||
if config.icompt > 0 {
|
||
// OPAINI, COMSET, RTECOM 需要外部实现
|
||
}
|
||
|
||
// 辐射转移求解
|
||
// OPAINI(1)
|
||
for id in 0..nd {
|
||
output.grd[id] = 0.0;
|
||
output.pra[id] = 0.0;
|
||
model.pradt[id] = 0.0;
|
||
}
|
||
output.prd0 = 0.0;
|
||
|
||
// 频率循环(完整版本需要 OPACF1 和 RTEFR1 回调)
|
||
for ij in 0..nfreq {
|
||
// OPACF1(IJ) 和 RTEFR1(IJ) 需要外部实现
|
||
// 计算 GRD 和 PRA
|
||
}
|
||
|
||
// GRD(1) = PCK * GRD(1) / DENS(1)
|
||
if nd > 0 && model.dens[0] != 0.0 {
|
||
output.grd[0] = PCK * output.grd[0] / model.dens[0];
|
||
}
|
||
|
||
// PRA 和 PRADT 更新
|
||
for id in 0..nd {
|
||
output.pra[id] *= PCK;
|
||
model.pradt[id] *= PCK;
|
||
}
|
||
|
||
// PGS 更新(非盘模型)
|
||
if config.idisk == 0 {
|
||
// 需要外部 GRAV 参数
|
||
// PGS(1) = DM(1) * (GRAV - GRD(1))
|
||
// PGS(ID) = PGS(ID-1) - PCK*GRD(ID) + GRAV*(DM(ID)-DM(ID-1))
|
||
}
|
||
|
||
output
|
||
}
|
||
|
||
// ============================================================================
|
||
// 测试
|
||
// ============================================================================
|
||
|
||
#[cfg(test)]
|
||
mod tests {
|
||
use super::*;
|
||
use approx::assert_relative_eq;
|
||
|
||
#[test]
|
||
fn test_inilam_config_default() {
|
||
let config = InilamConfig::default();
|
||
assert_eq!(config.init, 1);
|
||
assert!(!config.lte);
|
||
assert_relative_eq!(config.teff, 10000.0);
|
||
}
|
||
|
||
#[test]
|
||
fn test_inilam_output_default() {
|
||
let output = InilamOutput::default();
|
||
assert_relative_eq!(output.prad, 0.0);
|
||
assert_relative_eq!(output.anerel, 0.5);
|
||
}
|
||
|
||
#[test]
|
||
fn test_inilam_init_branch() {
|
||
let config = InilamConfig {
|
||
init: 1,
|
||
teff: 10000.0,
|
||
..Default::default()
|
||
};
|
||
|
||
let nd = 3;
|
||
let nlevel = 2;
|
||
let nion = 1;
|
||
let ntrans = 1;
|
||
let nfreq = 2;
|
||
|
||
let dm = vec![1e-4, 1e-3, 1e-2];
|
||
let mut temp = vec![10000.0, 15000.0, 20000.0];
|
||
let mut elec = vec![1e10, 1e11, 1e12];
|
||
let mut dens = vec![1e14, 1e15, 1e16];
|
||
let mut totn = vec![0.0; 3];
|
||
let mut anto = vec![0.0; 3];
|
||
let mut anma = vec![0.0; 3];
|
||
let mut pgs = vec![0.0; 3];
|
||
let mut pradt = vec![0.0; 3];
|
||
let vturb = vec![0.0; 3];
|
||
let wmm = vec![1.0; 3];
|
||
let mut popul = vec![0.5; nlevel * nd];
|
||
let mut bfac = vec![1.0; nlevel * nd];
|
||
let mut colrat = vec![0.0; ntrans * nd];
|
||
let mut coltar = vec![0.0; ntrans * nd];
|
||
let mut rad = vec![0.0; nfreq * nd];
|
||
let mut fcool = vec![0.0; 3];
|
||
let mut fprd = vec![0.0; 3];
|
||
let mut qfix = vec![0.0; 3];
|
||
let psy0 = vec![0.0; 100];
|
||
let ptotal = vec![0.0; 3];
|
||
let mut zd = vec![0.0; 3];
|
||
let mut delta = vec![0.0; 3];
|
||
|
||
let mut model = InilamModelState {
|
||
nd,
|
||
nlevel,
|
||
nion,
|
||
ntrans,
|
||
nfreq,
|
||
dm: &dm,
|
||
temp: &mut temp,
|
||
elec: &mut elec,
|
||
dens: &mut dens,
|
||
totn: &mut totn,
|
||
anto: &mut anto,
|
||
anma: &mut anma,
|
||
pgs: &mut pgs,
|
||
pradt: &mut pradt,
|
||
vturb: &vturb,
|
||
wmm: &wmm,
|
||
popul: &mut popul,
|
||
bfac: &mut bfac,
|
||
colrat: &mut colrat,
|
||
coltar: &mut coltar,
|
||
rad: &mut rad,
|
||
fcool: &mut fcool,
|
||
fprd: &mut fprd,
|
||
qfix: &mut qfix,
|
||
psy0: &psy0,
|
||
ptotal: &ptotal,
|
||
zd: &mut zd,
|
||
delta: &mut delta,
|
||
};
|
||
|
||
let atomic = InilamAtomicParams {
|
||
iatm: &[0, 0],
|
||
iel: &[0, 0],
|
||
ilk: &[0, 0],
|
||
iifix: &[0],
|
||
imodl: &[0, 0],
|
||
iltlev: &[0, 0],
|
||
iiexp: &[0, 0],
|
||
iinonz: &[0],
|
||
iltref: &[0; 6],
|
||
sbpsi: &[1.0; 6],
|
||
iz: &[1],
|
||
nfirst: &[0],
|
||
nlast: &[1],
|
||
nnext: &[1],
|
||
sbf: &[1.0, 1.0],
|
||
wop: &[1.0; 6],
|
||
usum: &[1.0],
|
||
};
|
||
|
||
let freq_data = vec![1e14, 2e14];
|
||
let bnue = vec![1e-10, 2e-10];
|
||
let fh = vec![1.0, 1.0];
|
||
let hextrd = vec![0.0; 2];
|
||
let w = vec![0.5, 0.5];
|
||
let hkt1 = vec![4.8e-11 / 10000.0, 4.8e-11 / 15000.0, 4.8e-11 / 20000.0];
|
||
|
||
let freq = InilamFreqParams {
|
||
freq: &freq_data,
|
||
bnue: &bnue,
|
||
fh: &fh,
|
||
hextrd: &hextrd,
|
||
w: &w,
|
||
hkt1: &hkt1,
|
||
};
|
||
|
||
let output = inilam_pure(&config, &mut model, &atomic, &freq);
|
||
|
||
// INIT=1 分支应该返回
|
||
assert_relative_eq!(output.anerel, 0.5); // TEFF >= 8000
|
||
}
|
||
|
||
#[test]
|
||
fn test_inilam_cold_star() {
|
||
// 冷星 (TEFF < 8000K)
|
||
let config = InilamConfig {
|
||
init: 1,
|
||
teff: 6000.0,
|
||
..Default::default()
|
||
};
|
||
|
||
assert!(config.teff < 8000.0);
|
||
|
||
let output = InilamOutput::default();
|
||
// 对于冷星,anerel 应该是 0.01(在函数内部设置)
|
||
}
|
||
}
|