SpectraRust/src/tlusty/math/radiative/rtecf0.rs
2026-04-04 09:36:14 +08:00

284 lines
12 KiB
Rust

//! 设置包含康普顿散射的辐射转移方程矩阵元素 - RTECF0。
//!
//! 重构自 TLUSTY `rtecf0.f`
//!
//! 该子程序为特定频率点 IJ 计算矩阵 A, B, C, E, U, V 和 alpha, beta, gamma。
use crate::tlusty::state::atomic::AtomicData;
use crate::tlusty::state::config::TlustyConfig;
use crate::tlusty::state::constants::{HALF, HK, SIGE, TWO, UN, XCON, YCON};
use crate::tlusty::state::iterat::IterControl;
use crate::tlusty::state::model::ModelState;
/// 为频率点 ij 计算 RTE 矩阵元素。
pub fn rtecf0(
ij: usize, // 0-based
config: &TlustyConfig,
_atomic: &AtomicData,
model: &mut ModelState,
_iterat: &IterControl,
) {
let nd = config.basnum.nd as usize;
let nfreq = config.basnum.nfreq as usize;
let iji_1 = (nfreq as i32 - model.frqall.kij[ij] + 1) as usize; // 1-based index in frequency-ordered scale
// Note: ij is 0-based. kij[ij] is 1-based in Fortran.
let fr = model.frqall.freq[ij];
let xcomp = fr * XCON;
// 1. 光学深度尺度 (Optical depth scale)
for id in 0..(nd - 1) {
model.optdpt.dt[id] = model.modpar.deldmz[id] * (model.curopa.absot[id + 1] + model.curopa.absot[id]);
}
// 2. 深度离散化矩阵 (Depth discretization matrices)
// 上边界 (Upper boundary, id = 1)
let id_0 = 0;
let dtp1 = model.optdpt.dt[id_0];
let bb0 = UN / dtp1;
let bb1 = TWO * bb0 * bb0;
model.auxrte.be[id_0] = bb0 * TWO * model.surfac.fh[ij] + bb1 * model.totrad.fak[ij][id_0];
model.auxrte.ga[id_0] = bb1 * model.totrad.fak[ij][id_0 + 1];
let sext = TWO * bb0 * model.totrad.hextrd[ij];
// 标准深度点 (Normal depth points, id = 2..nd-1)
let mut dtp1_current = dtp1;
for id in 1..(nd - 1) {
let dtm1 = dtp1_current;
dtp1_current = model.optdpt.dt[id];
let dt0 = TWO / (dtm1 + dtp1_current);
model.auxrte.al[id] = model.totrad.fak[ij][id - 1] / dtm1 * dt0;
model.auxrte.ga[id] = model.totrad.fak[ij][id + 1] / dtp1_current * dt0;
model.auxrte.be[id] = model.totrad.fak[ij][id] * dt0 * (UN / dtm1 + UN / dtp1_current);
}
// 下边界 (Lower boundary, id = nd)
let id_nd = nd - 1;
if config.basnum.idisk == 0 || config.centrl.ifz0 < 0 {
if config.basnum.ibc == 0 {
model.auxrte.be[id_nd] = model.totrad.fak[ij][id_nd] / dtp1_current + HALF;
model.auxrte.al[id_nd] = model.totrad.fak[ij][id_nd - 1] / dtp1_current;
} else if config.basnum.ibc < 4 {
let b = UN / dtp1_current;
let a = TWO * b * b;
model.auxrte.be[id_nd] = b * TWO * model.totrad.fhd[ij] + a * model.totrad.fak[ij][id_nd];
model.auxrte.al[id_nd] = a * model.totrad.fak[ij][id_nd - 1];
} else {
let b = UN / dtp1_current;
let a = TWO * b * b;
model.auxrte.be[id_nd] = b + a * model.totrad.fak[ij][id_nd];
model.auxrte.al[id_nd] = a * model.totrad.fak[ij][id_nd - 1];
}
} else {
// 吸计盘 - 对称边界
let bb1 = TWO * (UN / dtp1_current) * (UN / dtp1_current);
model.auxrte.be[id_nd] = bb1 * model.totrad.fak[ij][id_nd];
model.auxrte.al[id_nd] = bb1 * model.totrad.fak[ij][id_nd - 1];
}
// 3. 散射矩阵 (Scattering matrices)
for id in 0..nd {
let scat0 = model.modpar.elec[id] * SIGE;
let sa0 = model.curopa.emis1[id] / model.curopa.abso1[id];
let ss0 = scat0 / model.curopa.abso1[id];
let epsnu = (model.curopa.abso1[id] - model.curopa.scat1[id]) / model.curopa.abso1[id];
let mut x0 = ss0;
let temp_id = model.modpar.temp[id];
let e2 = YCON * temp_id + 0.7 * xcomp * xcomp;
let e1 = xcomp - 3.0 * e2 - 0.7 * xcomp * xcomp;
let e0 = 1.0 - xcomp - 4.2 * xcomp * xcomp;
model.auxrte.coma[id] = 0.0;
model.auxrte.comc[id] = 0.0;
model.auxrte.u[id] = 0.0;
model.auxrte.v[id] = 0.0;
model.auxrte.vl[id] = sa0;
if id == 0 && model.windbl.iwinbl < 0 {
model.auxrte.vl[id] = sa0 + sext;
}
model.auxrte.bs[id] = 0.0;
if iji_1 == 1 {
model.auxrte.comc[id] = 0.0;
model.auxrte.comb[id] = x0 * (UN - TWO * xcomp);
} else if iji_1 < nfreq {
let del0 = TWO / (model.comptf.dlnfr[iji_1 - 1] + model.comptf.dlnfr[iji_1 - 2]);
let cder1p_val = (UN - model.comptf.delj[iji_1 - 1][id]) * del0;
let cder1m_val = -model.comptf.delj[iji_1 - 2][id] * del0;
if config.compti.ichcoo == 0 {
let cder10_val = -cder1m_val - cder1p_val;
// Store into arrays for use by other functions
model.comptf.cder1p[iji_1 - 1] = cder1p_val;
model.comptf.cder1m[iji_1 - 1] = cder1m_val;
model.comptf.cder10[iji_1 - 1] = cder10_val;
model.auxrte.coma[id] = x0 * (e1 * cder1m_val + e2 * model.comptf.cder2m[iji_1 - 1]);
model.auxrte.comb[id] = x0 * (e0 + e1 * cder10_val + e2 * model.comptf.cder20[iji_1 - 1]);
model.auxrte.comc[id] = x0 * (e1 * cder1p_val + e2 * model.comptf.cder2p[iji_1 - 1]);
x0 = ss0 * model.comptf.bnus[iji_1 - 1];
if config.compti.icomst == 0 {
x0 = 0.0;
}
model.auxrte.come[id] = x0 * (cder10_val - UN);
model.auxrte.u[id] = x0 * cder1m_val;
model.auxrte.v[id] = x0 * cder1p_val;
model.auxrte.bs[id] = model.auxrte.come[id] * model.totrad.rad[iji_1 - 1][id]
+ model.auxrte.u[id] * model.totrad.rad[iji_1 - 2][id]
+ model.auxrte.v[id] * model.totrad.rad[iji_1][id];
} else {
// ichcoo != 0: store cder10 as in Fortran line 128
model.comptf.cder10[iji_1 - 1] = -del0 * (UN - model.comptf.delj[iji_1 - 2][id] - model.comptf.delj[iji_1 - 1][id]);
let ijom1 = config.comptn.ijorig[iji_1 - 2] as usize - 1; // ijorig(iji-1)
let ijop1 = config.comptn.ijorig[iji_1] as usize - 1; // ijorig(iji+1)
// freq uses ijorig mapping (original frequency order)
let frp = model.frqall.freq[ijop1]; // freq(ijorig(iji+1))
let frm = model.frqall.freq[ijom1]; // freq(ijorig(iji-1))
// rad uses iji directly (frequency-ordered), NOT ijorig
let zxxp = XCON * frp + 0.5 * model.comptf.bnus[iji_1] * model.totrad.rad[iji_1][id] - 3.0 * e2;
let zxx0 = xcomp + 0.5 * model.comptf.bnus[iji_1 - 1] * model.totrad.rad[iji_1 - 1][id] - 3.0 * e2;
let zxxm = XCON * frm + 0.5 * model.comptf.bnus[iji_1 - 2] * model.totrad.rad[iji_1 - 2][id] - 3.0 * e2;
let zxxp12 = ((UN - model.comptf.delj[iji_1 - 1][id]) * zxxp + model.comptf.delj[iji_1 - 1][id] * zxx0) * del0;
let zxxm12 = ((UN - model.comptf.delj[iji_1 - 2][id]) * zxx0 + model.comptf.delj[iji_1 - 2][id] * zxxm) * del0;
model.auxrte.coma[id] = x0 * (-model.comptf.delj[iji_1 - 2][id] * zxxm12 + e2 * model.comptf.cder2m[iji_1 - 1]);
model.auxrte.comc[id] = x0 * ((UN - model.comptf.delj[iji_1 - 1][id]) * zxxp12 + e2 * model.comptf.cder2p[iji_1 - 1]);
model.auxrte.comb[id] = x0 * (model.comptf.delj[iji_1 - 1][id] * zxxp12 - (UN - model.comptf.delj[iji_1 - 2][id]) * zxxm12 + e2 * model.comptf.cder20[iji_1 - 1]) - epsnu + 1.0;
}
} else {
// iji_1 == nfreq
let ijom1 = config.comptn.ijorig[iji_1 - 2] as usize - 1;
let dlt = model.comptf.delj[iji_1 - 2][id];
// Fortran uses freq(ij) and freq(ij+1) directly, NOT ijorig
let zj1 = (-HK * model.frqall.freq[ij] / temp_id).exp();
let zj2 = if ij + 1 < nfreq {
(-HK * model.frqall.freq[ij + 1] / temp_id).exp()
} else {
zj1
};
if config.compti.ichcoo == 0 {
let fr_next = if ij + 1 < nfreq {
model.frqall.freq[ij + 1]
} else {
model.frqall.freq[ij]
};
let zj0 = UN / (HK * (model.frqall.freq[ij] * fr_next).sqrt() / temp_id);
let zxx = UN - 3.0 * zj0 + (UN - dlt) * zj1 + dlt * zj2;
model.auxrte.comb[id] = zj0 / model.comptf.dlnfr[iji_1 - 2] + (UN - dlt) * zxx;
model.auxrte.coma[id] = -zj0 / model.comptf.dlnfr[iji_1 - 2] + dlt * zxx;
} else {
let zxx0 = xcomp * (UN + zj1) - 3.0 * e2;
let frm = model.frqall.freq[ijom1];
let zxxm = XCON * frm * (UN + zj2) - 3.0 * e2;
let zxx = (UN - dlt) * zxx0 + dlt * zxxm;
model.auxrte.comb[id] = e2 / model.comptf.dlnfr[iji_1 - 2] + (UN - dlt) * zxx;
model.auxrte.coma[id] = -e2 / model.comptf.dlnfr[iji_1 - 2] + dlt * zxx;
}
model.auxrte.vl[id] = 0.0;
if config.compti.icomde != 0 {
model.auxrte.al[id] = 0.0;
model.auxrte.be[id] = -UN;
model.auxrte.ga[id] = 0.0;
}
}
if config.compti.icomde == 0 {
model.auxrte.coma[id] = 0.0;
model.auxrte.comc[id] = 0.0;
model.auxrte.comb[id] = x0 * (UN - TWO * xcomp);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::tlusty::state::constants::MDEPTH;
#[test]
fn test_rtecf0_basic() {
let mut config = TlustyConfig::default();
let atomic = AtomicData::default();
let mut model = ModelState::default();
let iterat = IterControl::default();
config.basnum.nd = 5;
config.basnum.nfreq = 10;
model.frqall.kij[0] = 10; // iji_1 = 10 - 10 + 1 = 1
model.frqall.freq[0] = 1.0e15;
for i in 0..MDEPTH {
model.modpar.deldmz[i] = 1.0;
model.curopa.absot[i] = 1.0;
model.modpar.elec[i] = 1.0e12;
model.curopa.abso1[i] = 1.0;
model.curopa.emis1[i] = 1.0;
model.curopa.scat1[i] = 0.0;
model.modpar.temp[i] = 10000.0;
}
model.surfac.fh[0] = 1.0;
model.totrad.fak[0][0] = 1.0;
rtecf0(0, &config, &atomic, &mut model, &iterat);
// 验证基本变量
assert!(model.optdpt.dt[0] > 0.0);
assert!(model.auxrte.be[0] != 0.0);
assert_eq!(model.auxrte.coma[0], 0.0); // iji_1 = 1
}
#[test]
fn test_rtecf0_intermediate() {
let mut config = TlustyConfig::default();
let atomic = AtomicData::default();
let mut model = ModelState::default();
let iterat = IterControl::default();
config.basnum.nd = 5;
config.basnum.nfreq = 10;
model.frqall.kij[0] = 5; // iji_1 = 10 - 5 + 1 = 6
model.frqall.freq[0] = 1.0e15;
for i in 0..10 {
model.comptf.dlnfr[i] = 0.1;
model.comptf.delj[i].resize(MDEPTH, 0.5);
model.comptf.cder2m[i] = 1.0;
model.comptf.cder20[i] = -2.0;
model.comptf.cder2p[i] = 1.0;
model.frqall.freq[i] = 1.0e15 * (1.0 + 0.1 * i as f64);
config.comptn.ijorig[i] = (i + 1) as i32;
}
for i in 0..MDEPTH {
model.modpar.deldmz[i] = 1.0;
model.curopa.absot[i] = 1.0;
model.modpar.elec[i] = 1.0e12;
model.curopa.abso1[i] = 1.0;
model.curopa.emis1[i] = 1.0;
model.curopa.scat1[i] = 0.0;
model.modpar.temp[i] = 10000.0;
}
// 启用康普顿散热逻辑
config.compti.icomde = 1;
config.compti.ichcoo = 0;
rtecf0(0, &config, &atomic, &mut model, &iterat);
// 验证 coma 发生了变化
assert!(model.auxrte.coma[0] != 0.0);
}
}