//! 设置包含康普顿散射的辐射转移方程矩阵元素 - 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); } }