SpectraRust/src/tlusty/math/bpop.rs
2026-03-25 13:31:23 +08:00

439 lines
14 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//! 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(
&params,
&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(
&params,
&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);
}
}
}