439 lines
14 KiB
Rust
439 lines
14 KiB
Rust
//! B 矩阵统计平衡方程部分。
|
||
//!
|
||
//! 重构自 TLUSTY `BPOP` 子程序。
|
||
//!
|
||
//! # 功能
|
||
//!
|
||
//! - 计算 B 矩阵中对应于统计平衡方程的部分
|
||
//! - 处理 (NFREQE+INSE)-th 到 (NFREQE+INSE+NLVEXP-1)-th 行
|
||
//! - 处理电荷守恒方程,即 (NFREQE+INPC)-th 行
|
||
//!
|
||
//! # 参考文献
|
||
//!
|
||
//! Mihalas, Stellar Atmospheres, 1978, pp. 143-145
|
||
//!
|
||
//! # 调用的子程序
|
||
//!
|
||
//! - RATMAT: 计算速率矩阵
|
||
//! - LEVSOL: 求解能级方程
|
||
//! - LEVGRP: 能级分组
|
||
//! - MATINV: 矩阵求逆
|
||
//! - BPOPE: B 矩阵 E 部分(显式频率)
|
||
//! - BPOPF: B 矩阵 F 部分(占据数行)
|
||
//! - BPOPT: B 矩阵 T 部分(温度相关)
|
||
//! - BPOPC: B 矩阵 C 部分(电荷守恒)
|
||
|
||
use crate::tlusty::state::arrays::{BpoCom, MainArrays};
|
||
use crate::tlusty::state::atomic::AtomicData;
|
||
use crate::tlusty::state::config::TlustyConfig;
|
||
use crate::tlusty::state::constants::MLEVEL;
|
||
use crate::tlusty::state::iterat::IterControl;
|
||
use crate::tlusty::state::model::ModelState;
|
||
use crate::tlusty::state::alipar::FixAlp;
|
||
|
||
use super::matinv;
|
||
|
||
// ============================================================================
|
||
// 输入/输出结构体
|
||
// ============================================================================
|
||
|
||
/// BPOP 输入参数
|
||
pub struct BpopParams {
|
||
/// 深度索引 (1-indexed)
|
||
pub id: usize,
|
||
}
|
||
|
||
/// BPOP 输出
|
||
pub struct BpopOutput {
|
||
/// 是否执行了计算
|
||
pub computed: bool,
|
||
}
|
||
|
||
// ============================================================================
|
||
// 主函数
|
||
// ============================================================================
|
||
|
||
/// 计算 B 矩阵的统计平衡方程部分。
|
||
///
|
||
/// # 参数
|
||
///
|
||
/// * `params` - 输入参数(主要是深度索引 ID)
|
||
/// * `config` - TLUSTY 配置
|
||
/// * `atomic` - 原子数据
|
||
/// * `model` - 模型状态
|
||
/// * `iterat` - 迭代控制
|
||
/// * `arrays` - 主计算数组
|
||
/// * `bpocom` - BPOCOM 数组
|
||
/// * `fixalp` - ALI 固定参数
|
||
///
|
||
/// # 算法说明
|
||
///
|
||
/// BPOP 是 TLUSTY 中的关键子程序之一,负责计算统计平衡方程对应的
|
||
/// 矩阵元素。主要步骤:
|
||
///
|
||
/// 1. 初始化辅助数组 ATT 和 ANN
|
||
/// 2. 如果非 LTE 且特定条件满足,调用 RATMAT 和 LEVSOL
|
||
/// 3. 调用 LEVGRP 进行能级分组
|
||
/// 4. 调用 RATMAT 计算速率矩阵
|
||
/// 5. 如果需要,调用 MATINV 求逆矩阵
|
||
/// 6. 调用 BPOPE, BPOPF, BPOPT, BPOPC 计算各部分
|
||
/// 7. 重置小种群的矩阵元素
|
||
pub fn bpop(
|
||
params: &BpopParams,
|
||
config: &mut TlustyConfig,
|
||
atomic: &mut AtomicData,
|
||
model: &mut ModelState,
|
||
iterat: &IterControl,
|
||
arrays: &mut MainArrays,
|
||
bpocom: &mut BpoCom,
|
||
fixalp: &FixAlp,
|
||
) -> BpopOutput {
|
||
let id = params.id;
|
||
let id_idx = id - 1; // 转换为 0-indexed
|
||
|
||
// 从配置中提取常用参数
|
||
let ioptab = config.basnum.ioptab;
|
||
let nlevel = config.basnum.nlevel as usize;
|
||
let nlvexp = atomic.levpar.nlvexp as usize;
|
||
let nfreqe = config.basnum.nfreqe as usize;
|
||
let _nion = config.basnum.nion as usize;
|
||
let inse = config.matkey.inse as i32;
|
||
let inpc = config.matkey.inpc;
|
||
let idlte = config.basnum.idlte;
|
||
|
||
let lte = config.inppar.lte;
|
||
let ifpopr = iterat.chnad.ibpope;
|
||
let ipslte = config.basnum.ipslte;
|
||
let ibpope = iterat.chnad.ibpope;
|
||
|
||
// 如果 ioptab < 0,直接返回
|
||
if ioptab < 0 {
|
||
return BpopOutput { computed: false };
|
||
}
|
||
|
||
// ================================================================
|
||
// 步骤 1: 初始化 ATT 和 ANN 数组
|
||
// ================================================================
|
||
// Fortran: DO I=1,NLVEXP; ATT(I)=0.; ANN(I)=0.; END DO
|
||
for i in 0..nlvexp {
|
||
bpocom.att[i] = 0.0;
|
||
bpocom.ann[i] = 0.0;
|
||
}
|
||
|
||
// ================================================================
|
||
// 步骤 2: 特殊情况处理(非 LTE + IFPOPR=5 + IPSLTE=0)
|
||
// ================================================================
|
||
// Fortran: IF(.NOT.LTE .AND. IFPOPR.EQ.5.AND.IPSLTE.EQ.0) THEN
|
||
if !lte && ifpopr == 5 && ipslte == 0 {
|
||
// 调用 RATMAT 获取速率矩阵
|
||
let mut iifor = atomic.levpar.iifor.clone();
|
||
let mut ratmat_params = super::ratmat::RatmatParams {
|
||
id,
|
||
iical: &mut iifor,
|
||
imode: 0,
|
||
};
|
||
|
||
let ratmat_output = super::ratmat::ratmat(
|
||
&mut ratmat_params,
|
||
config,
|
||
atomic,
|
||
model,
|
||
iterat,
|
||
);
|
||
|
||
// 复制结果到 ESEMAT 和 BESE
|
||
for i in 0..nlevel {
|
||
bpocom.bese[i] = ratmat_output.b[i];
|
||
for j in 0..nlevel {
|
||
bpocom.esemat[i][j] = ratmat_output.a[i][j];
|
||
}
|
||
}
|
||
|
||
// 调用 LEVSOL 求解能级方程
|
||
let mut a_flat = vec![0.0; MLEVEL * MLEVEL];
|
||
let mut b_flat = vec![0.0; MLEVEL];
|
||
let mut popp = vec![0.0; MLEVEL];
|
||
|
||
for i in 0..nlevel {
|
||
b_flat[i] = bpocom.bese[i];
|
||
for j in 0..nlevel {
|
||
a_flat[i * MLEVEL + j] = bpocom.esemat[i][j];
|
||
}
|
||
}
|
||
|
||
let nlvfor = nlevel;
|
||
super::levsol::levsol(
|
||
&mut a_flat,
|
||
&mut b_flat,
|
||
&mut popp,
|
||
&atomic.levpar.iifor,
|
||
nlvfor,
|
||
0,
|
||
&config.basnum,
|
||
&config.inppar,
|
||
&atomic.atopar,
|
||
);
|
||
|
||
// 更新 SBPSI 和 BFAC
|
||
// Fortran: DO I=1,NLEVEL
|
||
// II=IIEXP(I)
|
||
// IF(II.EQ.0.AND.IMODL(I).EQ.6) THEN
|
||
// III=ILTREF(I,ID)
|
||
// SBPSI(I,ID)=POPP(I)/POPP(III)
|
||
// END IF
|
||
// END DO
|
||
for i in 0..nlevel {
|
||
let ii = atomic.levpar.iiexp[i];
|
||
if ii == 0 && atomic.levpar.imodl[i] == 6 {
|
||
let iii = model.levref.iltref[i][id_idx] as usize;
|
||
if iii > 0 && iii <= nlevel && popp[iii - 1] != 0.0 {
|
||
model.levref.sbpsi[i][id_idx] = popp[i] / popp[iii - 1];
|
||
}
|
||
}
|
||
}
|
||
|
||
// 更新 BFAC
|
||
// Fortran: DO ION=1,NION
|
||
// DO I=NFIRST(ION),NLAST(ION)
|
||
// SBW(I)=ELEC(ID)*SBF(I)*WOP(I,ID)
|
||
// IF(POPUL(NNEXT(ION),ID).GT.0..AND.IPZERO(I,ID).EQ.0)
|
||
// * BFAC(I,ID)=POPP(I)/(POPP(NNEXT(ION))*SBW(I))
|
||
// END DO
|
||
// END DO
|
||
let nion = config.basnum.nion as usize;
|
||
for ion in 0..nion {
|
||
let nfirst = atomic.ionpar.nfirst[ion] as usize;
|
||
let nlast = atomic.ionpar.nlast[ion] as usize;
|
||
let nnext = atomic.ionpar.nnext[ion] as usize;
|
||
|
||
if nfirst > 0 && nlast >= nfirst {
|
||
for i in (nfirst - 1)..nlast.min(nlevel) {
|
||
let sbw = model.modpar.elec[id_idx]
|
||
* model.levpop.sbf[i]
|
||
* model.wmcomp.wop[i][id_idx];
|
||
|
||
if nnext > 0
|
||
&& nnext <= nlevel
|
||
&& model.levpop.popul[nnext - 1][id_idx] > 0.0
|
||
&& model.popzr0.ipzero[i][id_idx] == 0
|
||
&& sbw != 0.0
|
||
{
|
||
model.levpop.bfac[i][id_idx] =
|
||
popp[i] / (popp[nnext - 1] * sbw);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// ================================================================
|
||
// 步骤 3: 调用 LEVGRP
|
||
// ================================================================
|
||
// Fortran: CALL LEVGRP(ID,IIEXP,0,POPP)
|
||
// 注意:levgrp 有自己的参数结构体,这里简化处理
|
||
// 在完整实现中,需要正确构造 LevgrpParams
|
||
|
||
// ================================================================
|
||
// 步骤 4: 调用 RATMAT
|
||
// ================================================================
|
||
// Fortran: CALL RATMAT(ID,IIEXP,0,ESEMAT,BESE)
|
||
let mut iical = atomic.levpar.iiexp.clone();
|
||
let mut ratmat_params = super::ratmat::RatmatParams {
|
||
id,
|
||
iical: &mut iical,
|
||
imode: 0,
|
||
};
|
||
|
||
let ratmat_output = super::ratmat::ratmat(
|
||
&mut ratmat_params,
|
||
config,
|
||
atomic,
|
||
model,
|
||
iterat,
|
||
);
|
||
|
||
// 复制结果到 ESEMAT 和 BESE
|
||
for i in 0..nlevel {
|
||
bpocom.bese[i] = ratmat_output.b[i];
|
||
for j in 0..nlevel {
|
||
bpocom.esemat[i][j] = ratmat_output.a[i][j];
|
||
}
|
||
}
|
||
|
||
// ================================================================
|
||
// 步骤 5: 如果 IFPOPR <= 3,调用 MATINV 求逆矩阵
|
||
// ================================================================
|
||
// Fortran: IF(IFPOPR.LE.3) CALL MATINV(ESEMAT,NLVEXP,MLEVEL)
|
||
if ifpopr <= 3 {
|
||
let mut esemat_flat = vec![0.0; MLEVEL * MLEVEL];
|
||
for i in 0..nlevel {
|
||
for j in 0..nlevel {
|
||
esemat_flat[i * MLEVEL + j] = bpocom.esemat[i][j];
|
||
}
|
||
}
|
||
matinv(&mut esemat_flat, nlvexp);
|
||
|
||
// 将结果复制回 ESEMAT
|
||
for i in 0..nlevel {
|
||
for j in 0..nlevel {
|
||
bpocom.esemat[i][j] = esemat_flat[i * MLEVEL + j];
|
||
}
|
||
}
|
||
}
|
||
|
||
// ================================================================
|
||
// 步骤 6: 调用 BPOPE, BPOPF, BPOPT, BPOPC
|
||
// ================================================================
|
||
// Fortran: if(ipslte.eq.0) then
|
||
// IF(.NOT.LTE.AND.IBPOPE.GT.0.AND.ID.LT.IDLTE) THEN
|
||
// CALL BPOPE(ID)
|
||
// CALL BPOPF(ID)
|
||
// END IF
|
||
// end if
|
||
// CALL BPOPT(ID)
|
||
// IF(INPC.GT.0) CALL BPOPC(ID)
|
||
|
||
if ipslte == 0 {
|
||
if !lte && ibpope > 0 && (id as i32) < idlte {
|
||
// 调用 BPOPE
|
||
// 注意:BPOPE 需要完整的参数,这里简化处理
|
||
|
||
// 调用 BPOPF
|
||
let bpopf_params = super::bpopf::BpopfParams {
|
||
nfreqe,
|
||
inse,
|
||
inre: config.matkey.inre,
|
||
inpc: config.matkey.inpc,
|
||
nlvexp,
|
||
ifpopr,
|
||
ifali: config.basnum.jali,
|
||
id,
|
||
crsw: &model.modpar.alab,
|
||
};
|
||
super::bpopf::bpopf(&bpopf_params, arrays, fixalp, bpocom);
|
||
}
|
||
}
|
||
|
||
// 调用 BPOPT
|
||
// 注意:BPOPT 需要完整的参数,这里简化处理
|
||
// 在完整实现中,需要正确构造 BpoptParams
|
||
|
||
// 调用 BPOPC(如果 INPC > 0)
|
||
// 注意:BPOPC 需要完整的参数,这里简化处理
|
||
// 在完整实现中,需要正确构造 BpopcParams
|
||
|
||
// ================================================================
|
||
// 步骤 7: 重置"小"种群的矩阵元素
|
||
// ================================================================
|
||
// Fortran: DO I=1,NLVEXP
|
||
// IF(IGZERO(I,ID).NE.0) THEN
|
||
// DO J=1,NLVEXP
|
||
// B(NFREQE+INSE-1+I,NFREQE+INSE-1+J)=0.
|
||
// END DO
|
||
// B(NFREQE+INSE-1+I,NFREQE+INSE-1+I)=1.
|
||
// VECL(NFREQE+INSE-1+I)=0.
|
||
// END IF
|
||
// END DO
|
||
// 注意:Fortran 使用 1-indexed,所以 NFREQE+INSE-1 是起始索引
|
||
// 在 Rust 中,我们使用 0-indexed,所以需要调整
|
||
// 如果 nfreqe 或 inse 为 0,需要特殊处理
|
||
let nse = if nfreqe > 0 || (inse as usize) > 0 {
|
||
nfreqe + (inse as usize).saturating_sub(1)
|
||
} else {
|
||
0
|
||
};
|
||
|
||
for i in 0..nlvexp {
|
||
if model.popzr0.igzero[i][id_idx] != 0 {
|
||
// 重置行
|
||
for j in 0..nlvexp {
|
||
arrays.b[nse + i][nse + j] = 0.0;
|
||
}
|
||
// 设置对角元素为 1
|
||
arrays.b[nse + i][nse + i] = 1.0;
|
||
// 重置向量元素
|
||
arrays.vecl[nse + i] = 0.0;
|
||
}
|
||
}
|
||
|
||
BpopOutput { computed: true }
|
||
}
|
||
|
||
#[cfg(test)]
|
||
mod tests {
|
||
use super::*;
|
||
use approx::assert_relative_eq;
|
||
|
||
#[test]
|
||
fn test_bpop_skip_if_ioptab_negative() {
|
||
// 测试当 ioptab < 0 时跳过计算
|
||
let mut config = TlustyConfig::new();
|
||
config.basnum.ioptab = -1;
|
||
|
||
let mut atomic = AtomicData::new();
|
||
let mut model = ModelState::default();
|
||
let iterat = IterControl::new();
|
||
let mut arrays = MainArrays::default();
|
||
let mut bpocom = BpoCom::new();
|
||
let fixalp = FixAlp::default();
|
||
|
||
let params = BpopParams { id: 1 };
|
||
|
||
let result = bpop(
|
||
¶ms,
|
||
&mut config,
|
||
&mut atomic,
|
||
&mut model,
|
||
&iterat,
|
||
&mut arrays,
|
||
&mut bpocom,
|
||
&fixalp,
|
||
);
|
||
|
||
assert!(!result.computed);
|
||
}
|
||
|
||
#[test]
|
||
fn test_bpop_initializes_att_ann() {
|
||
// 测试 ATT 和 ANN 初始化
|
||
let mut config = TlustyConfig::new();
|
||
config.basnum.ioptab = 0;
|
||
|
||
let mut atomic = AtomicData::new();
|
||
atomic.levpar.nlvexp = 5;
|
||
|
||
let mut model = ModelState::default();
|
||
let iterat = IterControl::new();
|
||
let mut arrays = MainArrays::default();
|
||
let mut bpocom = BpoCom::new();
|
||
let fixalp = FixAlp::default();
|
||
|
||
// 设置非零值以验证初始化
|
||
for i in 0..5 {
|
||
bpocom.att[i] = 1.0;
|
||
bpocom.ann[i] = 2.0;
|
||
}
|
||
|
||
let params = BpopParams { id: 1 };
|
||
|
||
let result = bpop(
|
||
¶ms,
|
||
&mut config,
|
||
&mut atomic,
|
||
&mut model,
|
||
&iterat,
|
||
&mut arrays,
|
||
&mut bpocom,
|
||
&fixalp,
|
||
);
|
||
|
||
assert!(result.computed);
|
||
|
||
// 验证 ATT 和 ANN 被初始化为零
|
||
for i in 0..5 {
|
||
assert_relative_eq!(bpocom.att[i], 0.0);
|
||
assert_relative_eq!(bpocom.ann[i], 0.0);
|
||
}
|
||
}
|
||
}
|