//! 辐射传输方程求解器 - 已知源函数时的特定强度计算。 //! //! 重构自 TLUSTY `rteint.f` //! //! 支持的数值方法 (ISPLIN): //! - 0: 普通 Feautrier 方案 //! - 1: 样条配点法 //! - 2: Hermite 四阶方法 //! - 3: 改进的 Feautrier 方案 (Rybicki & Hummer 1991, A&A 245, 171) //! //! 所有方法使用标准高斯消元求解矩阵系统。 use crate::tlusty::state::constants::{MDEPTH, MFREQ, UN, HALF, TWO}; // f2r_depends: OPACF1 // ============================================================================ // 常量 // ============================================================================ /// 六分之一 const SIXTH: f64 = UN / 6.0; /// 三分之一 const THIRD: f64 = UN / 3.0; /// 三分之二 const TWOTHR: f64 = TWO / 3.0; /// 最大角度数 const MMA: usize = 20; /// 光速 × 1e18 (用于波长计算) const C18: f64 = 2.997925e18; // ============================================================================ // 参数结构体 // ============================================================================ /// RTEINT 配置参数 #[derive(Debug, Clone)] pub struct RteIntConfig { /// 数值方法选择 (0-3) pub isplin: i32, /// 盘状/球状模型标志 (0: 球状, 1: 盘状) pub idisk: i32, /// 边界条件类型 pub ibc: i32, /// 中心对称标志 (>=0: 对称) pub ifz0: i32, /// 窗口黑体标志 (<0: 使用额外辐射) pub iwinbl: i32, /// ODF 采样标志 (0: 标准模式) pub ispodf: i32, /// 强度计算角度数 pub intens: i32, } impl Default for RteIntConfig { fn default() -> Self { Self { isplin: 0, idisk: 1, ibc: 0, ifz0: 0, iwinbl: 0, ispodf: 0, intens: 4, } } } /// RTEINT 模型状态参数 #[derive(Debug)] pub struct RteIntModelState<'a> { /// 深度点数 pub nd: usize, /// 温度 (nd) pub temp: &'a [f64], /// 深度 (柱质量密度) (nd) pub dm: &'a [f64], /// 深度差分 (nd-1) pub deldmz: &'a [f64], /// 边界温度 pub tempbd: f64, } /// RTEINT 频率数据参数 #[derive(Debug)] pub struct RteIntFreqParams<'a> { /// 频率数组 (nfreq) pub freq: &'a [f64], /// ODF 频率索引 (nfreq), 1-indexed pub jik: &'a [i32], /// 频率标志 (nfreq), -1 表示跳过 pub ijx: &'a [i32], /// 额外辐射 (nfreq) pub extrad: &'a [f64], /// 频率点数 pub nfreq: usize, } /// RTEINT 物理常量 #[derive(Debug, Clone)] pub struct RteIntPhysics { /// Planck 常数 pub hk: f64, /// 稀释因子 pub rrdil: f64, /// Planck 函数归一化常数 pub bn: f64, } impl Default for RteIntPhysics { fn default() -> Self { Self { hk: 4.7994e-11, // h/k rrdil: 0.5, // 稀释因子 bn: 1.0, // Planck 归一化 } } } /// RTEINT 不透明度参数 (来自 OPACF1) #[derive(Debug)] pub struct RteIntOpacity<'a> { /// 吸收系数 (nd) pub abso1: &'a [f64], /// 发射系数 (nd) pub emis1: &'a [f64], /// 散射系数 (nd) pub scat1: &'a [f64], /// 总吸收系数 (nd) pub absot: &'a [f64], } /// RTEINT 通量数据 #[derive(Debug)] pub struct RteIntFlux<'a> { /// 通量数组 (nfreq) pub flux: &'a [f64], } /// RTEINT 输出 #[derive(Debug)] pub struct RteIntOutput<'a> { /// 辐射强度 (nd) pub rad1: &'a mut [f64], /// 光学深度增量 (nd-1), 局部 COMMON /OPTDPT/ pub dt: &'a mut [f64], } /// RTEINT 角度数据 #[derive(Debug, Clone)] pub struct RteIntAngles { /// 角度余弦值 (mma) pub angl: Vec, /// 角度权重 (mma) pub wang: Vec, /// 角度点数 pub nmu: usize, } impl Default for RteIntAngles { fn default() -> Self { Self { angl: vec![0.0; MMA], wang: vec![0.0; MMA], nmu: 4, } } } // ============================================================================ // 辅助函数 // ============================================================================ /// 初始化角度网格 /// /// 设置等间距的角度点和权重 fn init_angles(nmu: usize) -> RteIntAngles { let mut angles = RteIntAngles { angl: vec![0.0; MMA], wang: vec![0.0; MMA], nmu, }; for imu in 0..nmu { // 角度余弦从 0.1 到 1.0 均匀分布 angles.angl[imu] = 0.1 + (imu as f64) * 0.9 / ((nmu - 1) as f64); angles.wang[imu] = 0.9 / ((nmu - 1) as f64); } // 首尾权重减半 (梯形法则) angles.wang[0] *= 0.5; angles.wang[nmu - 1] *= 0.5; angles } /// 矩阵求逆 (简化版, 调用 matinv 模块) /// /// 对 n×n 矩阵 a 进行原地求逆 fn matinv(a: &mut [f64], n: usize, _mmax: usize) { // 调用已实现的 matinv 模块 // 将 1D slice 转换为 2D 矩阵表示 crate::tlusty::math::matinv(a, n); } /// 3D 数组 D(I,J,ID) 的 Fortran 列主序索引 /// Fortran: DIMENSION D(MMA,MMA,MDEPTH), D(I,J,ID) offset = I + J*MMA + ID*MMA*MMA #[inline] fn d_idx(i: usize, j: usize, id: usize) -> usize { i + j * MMA + id * MMA * MMA } /// 2D 数组 ANU(I,ID) 的 Fortran 列主序索引 /// Fortran: DIMENSION ANU(MMA,MDEPTH), ANU(I,ID) offset = I + ID*MMA #[inline] fn anu_idx(i: usize, id: usize) -> usize { i + id * MMA } /// 2D 矩阵 A(I,J) 的行主序索引 (Rust 本地数组) #[inline] fn m2_idx(i: usize, j: usize) -> usize { i * MMA + j } // ============================================================================ // 主函数 // ============================================================================ /// 求解辐射传输方程 - 计算特定强度。 /// /// 对于已知源函数,使用 Feautrier 或相关方法求解辐射传输方程。 /// /// # 参数 /// /// * `config` - 配置参数 /// * `model` - 模型状态 /// * `freq_params` - 频率数据 /// * `physics` - 物理常量 /// * `opacity` - 不透明度 (来自 OPACF1) /// * `flux_data` - 通量数据 /// * `output` - 输出数组 /// * `opacf1_fn` - 不透明度计算函数 /// /// # 注意 /// /// 原始 Fortran 代码写入 fort.18 进行调试输出。 /// Rust 版本暂时省略 I/O 操作。 #[allow(clippy::too_many_arguments)] pub fn rteint( config: &RteIntConfig, model: &RteIntModelState, freq_params: &RteIntFreqParams, physics: &RteIntPhysics, opacity: &mut RteIntOpacity, flux_data: &RteIntFlux, output: &mut RteIntOutput, mut opacf1_fn: F, ) where F: FnMut(usize), { let nd = model.nd; // 保存原始 nmu let nmuf = config.intens as usize; let nmu = nmuf; // 初始化角度网格 let angles = init_angles(nmu); // 工作数组 let mut st0 = vec![0.0; MDEPTH]; let mut ss0 = vec![0.0; MDEPTH]; let mut ab0 = vec![0.0; MDEPTH]; let mut tau = vec![0.0; MDEPTH]; // 矩阵数组 let mut aa = vec![0.0; MMA * MMA]; let mut bb = vec![0.0; MMA * MMA]; let mut cc = vec![0.0; MMA * MMA]; let mut vl = vec![0.0; MMA]; let mut ffd = vec![0.0; MMA * MMA]; let mut ff0d = vec![0.0; MMA * MMA]; let mut ffpd = vec![0.0; MMA * MMA]; // 三维数组 D(MMA,MMA,MDEPTH) - Fortran 列主序: D(I,J,ID) = I + J*MMA + ID*MMA*MMA let mut d = vec![0.0; MMA * MMA * MDEPTH]; // 二维数组 ANU(MMA,MDEPTH) - Fortran 列主序: ANU(I,ID) = I + ID*MMA let mut anu = vec![0.0; MMA * MDEPTH]; // ======================================================================== // 遍历所有频率 // ======================================================================== for ijo in 0..freq_params.nfreq { // Fortran: IJ=IJO; IF(ispodf.eq.0) IJ=JIK(IJO) let ij = if config.ispodf == 0 { // 标准模式: 通过 JIK 映射到原始频率索引 let jik_val = freq_params.jik[ijo]; if jik_val <= 0 { continue; } (jik_val - 1) as usize } else { // ODF 模式: 直接使用频率索引 ijo }; // 检查频率标志 if freq_params.ijx[ij] == -1 { continue; } // 调用 OPACF1 计算不透明度 opacf1_fn(ij); let fr = freq_params.freq[ij]; // ==================================================================== // 计算总源函数 // ==================================================================== let ah = 0.0; for id in 0..nd { ab0[id] = opacity.abso1[id]; st0[id] = opacity.emis1[id] / ab0[id]; ss0[id] = -opacity.scat1[id] / ab0[id]; output.rad1[id] = 0.0; } // ==================================================================== // 计算光学深度标度 // ==================================================================== tau[0] = opacity.absot[0] * model.dm[0]; for id in 0..(nd - 1) { output.dt[id] = model.deldmz[id] * (opacity.absot[id + 1] + opacity.absot[id]); tau[id + 1] = tau[id] + output.dt[id]; } let u0 = 0.0; let qq0 = 0.0; let us0 = 0.0; let taumin = opacity.absot[0] * model.dm[0] / 2.0; let alb1 = 0.0; // ==================================================================== // 前向消元 // ==================================================================== // ---------------------------------------------------------------- // 上边界条件 (ID = 1) // ---------------------------------------------------------------- let id = 0; let dtp1 = output.dt[0]; let mut p0 = 0.0; let mut ex = UN; let (b_coef, c_coef) = if config.isplin % 3 == 0 { // 普通 Feautrier (dtp1 * HALF, 0.0) } else { // 高阶方法 let b = dtp1 * THIRD; (b, b * HALF) }; let qq0 = 0.0; let us0 = 0.0; // 初始化边界矩阵 for i in 0..nmu { if config.idisk == 0 { // 非零光学深度修正 let tamm = taumin / angles.angl[i]; ex = (-tamm).exp(); p0 = UN - ex; } let bi = b_coef / angles.angl[i]; let ci = c_coef / angles.angl[i]; vl[i] = (bi + p0) * st0[id] + ci * st0[id + 1]; if config.iwinbl < 0 { vl[i] += freq_params.extrad[ij]; } for j in 0..nmu { bb[m2_idx(i, j)] = ss0[id] * angles.wang[j] * (bi + p0) - alb1 * angles.wang[j]; cc[m2_idx(i, j)] = -ci * ss0[id + 1] * angles.wang[j]; } bb[m2_idx(i, i)] += angles.angl[i] / dtp1 + UN + bi; cc[m2_idx(i, i)] += angles.angl[i] / dtp1 - ci; anu[anu_idx(i, id)] = 0.0; } if config.isplin <= 2 { // 标准方法 matinv(&mut bb, nmu, MMA); for i in 0..nmu { for j in 0..nmu { d[d_idx(i, j, id)] = 0.0; for k in 0..nmu { d[d_idx(i, j, id)] += bb[m2_idx(i, k)] * cc[m2_idx(k, j)]; } anu[anu_idx(i, id)] += bb[m2_idx(i, j)] * vl[j]; } } } else { // 改进的 Feautrier (ISPLIN = 3) for i in 0..nmu { for j in 0..nmu { ff0d[m2_idx(i, j)] = bb[m2_idx(i, j)] / cc[m2_idx(i, i)]; } ff0d[m2_idx(i, i)] -= UN; } matinv(&mut bb, nmu, MMA); for i in 0..nmu { anu[anu_idx(i, id)] = 0.0; for j in 0..nmu { d[d_idx(i, j, id)] = bb[m2_idx(i, j)] * cc[m2_idx(j, j)]; anu[anu_idx(i, id)] += bb[m2_idx(i, j)] * vl[j]; } } } // ---------------------------------------------------------------- // 内部深度点 (1 < ID < ND) // ---------------------------------------------------------------- for id in 1..(nd - 1) { let dtm1 = dtp1; let dtp1 = output.dt[id]; let dt0 = TWO / (dtm1 + dtp1); let al = UN / dtm1 * dt0; let ga = UN / dtp1 * dt0; let be = al + ga; let (a_coef, c_coef) = if config.isplin % 3 == 0 { (0.0, 0.0) } else if config.isplin == 1 { let a = dtm1 * dt0 * SIXTH; let c = dtp1 * dt0 * SIXTH; (a, c) } else { let a = (UN - HALF * al * dtp1 * dtp1) * SIXTH; let c = (UN - HALF * ga * dtm1 * dtm1) * SIXTH; (a, c) }; let b_coef = UN - a_coef - c_coef; let vl0 = a_coef * st0[id - 1] + b_coef * st0[id] + c_coef * st0[id + 1]; // 填充矩阵 for i in 0..nmu { for j in 0..nmu { aa[m2_idx(i, j)] = -a_coef * ss0[id - 1] * angles.wang[j]; cc[m2_idx(i, j)] = -c_coef * ss0[id + 1] * angles.wang[j]; bb[m2_idx(i, j)] = b_coef * ss0[id] * angles.wang[j]; } } for i in 0..nmu { vl[i] = vl0; let div = angles.angl[i] * angles.angl[i]; aa[m2_idx(i, i)] += div * al - a_coef; cc[m2_idx(i, i)] += div * ga - c_coef; bb[m2_idx(i, i)] += div * be + b_coef; } for i in 0..nmu { for j in 0..nmu { vl[i] += aa[m2_idx(i, j)] * anu[anu_idx(j, id - 1)]; } } if config.isplin <= 2 { // 标准方法 for i in 0..nmu { for j in 0..nmu { let mut s = 0.0; for k in 0..nmu { s += aa[m2_idx(i, k)] * d[d_idx(k, j, id - 1)]; } bb[m2_idx(i, j)] -= s; } } matinv(&mut bb, nmu, MMA); for i in 0..nmu { for j in 0..nmu { d[d_idx(i, j, id)] = 0.0; for k in 0..nmu { d[d_idx(i, j, id)] += bb[m2_idx(i, k)] * cc[m2_idx(k, j)]; } } } } else { // 改进的 Feautrier for i in 0..nmu { bb[m2_idx(i, i)] = -aa[m2_idx(i, i)] + bb[m2_idx(i, i)] - cc[m2_idx(i, i)]; for j in 0..nmu { ffpd[m2_idx(i, j)] = aa[m2_idx(i, i)] * ff0d[m2_idx(i, j)]; } } for i in 0..nmu { for j in 0..nmu { let mut s = 0.0; for k in 0..nmu { s += ffpd[m2_idx(i, k)] * d[d_idx(k, j, id - 1)]; } ffd[m2_idx(i, j)] = (bb[m2_idx(i, j)] + s) / cc[m2_idx(i, i)]; } } for i in 0..nmu { for j in 0..nmu { ff0d[m2_idx(i, j)] = ffd[m2_idx(i, j)]; } ffd[m2_idx(i, i)] += UN; } matinv(&mut ffd, nmu, MMA); for i in 0..nmu { for j in 0..nmu { d[d_idx(i, j, id)] = ffd[m2_idx(i, j)]; bb[m2_idx(i, j)] = ffd[m2_idx(i, j)] / cc[m2_idx(j, j)]; } } } for i in 0..nmu { anu[anu_idx(i, id)] = 0.0; for j in 0..nmu { anu[anu_idx(i, id)] += bb[m2_idx(i, j)] * vl[j]; } } } // ---------------------------------------------------------------- // 下边界条件 (ID = ND) // ---------------------------------------------------------------- let id = nd - 1; if config.ifz0 >= 0 && config.idisk == 1 { // 第一种边界条件: 中心平面对称 // I(taumax,-mu,nu) = I(taumax,+mu,nu) let b_coef = dtp1 * HALF; let a_coef = 0.0; for i in 0..nmu { let bi = b_coef / angles.angl[i]; let ai = a_coef / angles.angl[i]; vl[i] = st0[id] * bi + st0[id - 1] * ai; for j in 0..nmu { aa[m2_idx(i, j)] = -ai * ss0[id - 1] * angles.wang[j]; bb[m2_idx(i, j)] = bi * ss0[id] * angles.wang[j]; } aa[m2_idx(i, i)] += angles.angl[i] / dtp1 - ai; bb[m2_idx(i, i)] += angles.angl[i] / dtp1 + bi; } for i in 0..nmu { let mut s1 = 0.0; for j in 0..nmu { let mut s = 0.0; s1 += aa[m2_idx(i, j)] * anu[anu_idx(j, id - 1)]; for k in 0..nmu { s += aa[m2_idx(i, k)] * d[d_idx(k, j, id - 1)]; } bb[m2_idx(i, j)] -= s; } vl[i] += s1; } } else { // 第二种边界条件: 恒星大气标准边界条件 let fr15 = fr * 1e-15; let bnu = physics.bn * fr15 * fr15 * fr15; let mut pland = bnu / ((physics.hk * fr / model.temp[id]).exp() - UN) * physics.rrdil; let mut dplan = bnu / ((physics.hk * fr / model.temp[id - 1]).exp() - UN) * physics.rrdil; if model.tempbd > 0.0 { pland = bnu / ((physics.hk * fr / model.tempbd).exp() - UN) * physics.rrdil; dplan = pland; } dplan = (pland - dplan) / output.dt[nd - 2]; if config.ibc == 0 || config.ibc == 4 { for i in 0..nmu { aa[m2_idx(i, i)] = angles.angl[i] / dtp1; vl[i] = pland + angles.angl[i] * dplan + aa[m2_idx(i, i)] * anu[anu_idx(i, id - 1)]; for j in 0..nmu { bb[m2_idx(i, j)] = -aa[m2_idx(i, i)] * d[d_idx(i, j, id - 1)]; } bb[m2_idx(i, i)] += aa[m2_idx(i, i)] + UN; } } else { for i in 0..nmu { let a = angles.angl[i] / dtp1; let b = HALF / a; aa[m2_idx(i, i)] = a; vl[i] = b * st0[id] + pland + angles.angl[i] * dplan + aa[m2_idx(i, i)] * anu[anu_idx(i, id - 1)]; for j in 0..nmu { bb[m2_idx(i, j)] = b * ss0[id] * angles.wang[j] - aa[m2_idx(i, i)] * d[d_idx(i, j, id - 1)]; } bb[m2_idx(i, i)] += a + b + UN; } } } matinv(&mut bb, nmu, MMA); for i in 0..nmu { anu[anu_idx(i, id)] = 0.0; for j in 0..nmu { d[d_idx(i, j, id)] = 0.0; anu[anu_idx(i, id)] += bb[m2_idx(i, j)] * vl[j]; } } // ==================================================================== // 回代 // ==================================================================== for id in (0..(nd - 1)).rev() { for i in 0..nmu { for j in 0..nmu { anu[anu_idx(i, id)] += d[d_idx(i, j, id)] * anu[anu_idx(j, id + 1)]; } } } // ==================================================================== // 计算积分通量 // ==================================================================== let sum: f64 = (0..nmu) .map(|imu| anu[anu_idx(imu, 0)] * angles.angl[imu] * angles.wang[imu]) .sum(); let sua: f64 = (0..nmu) .map(|imu| angles.angl[imu] * angles.wang[imu]) .sum(); // 原始代码写入 fort.18 // WRITE(18,641) WLAM,flux(ij),sum,sua,(2.*ANU(IMU,1),IMU=1,NMU) // format: f11.3,(1p13e11.3) let wlam = C18 / fr; let flux_ij = flux_data.flux[ij]; let mut anu_vals: Vec = Vec::new(); for imu in 0..nmu { anu_vals.push(2.0 * anu[anu_idx(imu, 0)]); } // Fortran FORMAT 641: f11.3,(1p13e11.3) let mut output = format!("{:11.3}", wlam); output.push_str(&format!("{:11.3e}", flux_ij)); output.push_str(&format!("{:11.3e}", sum)); output.push_str(&format!("{:11.3e}", sua)); for val in &anu_vals { output.push_str(&format!("{:11.3e}", val)); } eprintln!("{}", output); } } // ============================================================================ // 测试 // ============================================================================ #[cfg(test)] mod tests { use super::*; #[test] fn test_init_angles() { let nmu = 4; let angles = init_angles(nmu); assert_eq!(angles.nmu, nmu); assert!((angles.angl[0] - 0.1).abs() < 1e-10); assert!((angles.angl[nmu - 1] - 1.0).abs() < 1e-10); // 检查权重归一化 (近似 1.0) let sum: f64 = angles.wang.iter().sum(); assert!((sum - 0.9).abs() < 0.1); } #[test] fn test_rteint_config_default() { let config = RteIntConfig::default(); assert_eq!(config.isplin, 0); assert_eq!(config.idisk, 1); assert_eq!(config.intens, 4); } #[test] fn test_rteint_minimal() { let config = RteIntConfig::default(); let nd = 5; let temp = vec![10000.0, 12000.0, 15000.0, 18000.0, 20000.0]; let dm = vec![0.01, 0.1, 1.0, 10.0, 100.0]; let deldmz = vec![0.09, 0.9, 9.0, 90.0]; let model = RteIntModelState { nd, temp: &temp, dm: &dm, deldmz: &deldmz, tempbd: 0.0, }; let freq = vec![1e15; 10]; let jik = vec![0; 10]; let ijx = vec![0; 10]; let extrad = vec![0.0; 10]; let freq_params = RteIntFreqParams { freq: &freq, jik: &jik, ijx: &ijx, extrad: &extrad, nfreq: 1, }; let physics = RteIntPhysics::default(); let mut abso1 = vec![1e-10; nd]; let mut emis1 = vec![1e-20; nd]; let mut scat1 = vec![1e-12; nd]; let mut absot = vec![1e-10; nd]; let mut opacity = RteIntOpacity { abso1: &abso1, emis1: &emis1, scat1: &scat1, absot: &absot, }; let flux = vec![1e10; 10]; let flux_data = RteIntFlux { flux: &flux }; let mut rad1 = vec![0.0; nd]; let mut dt = vec![0.0; nd]; let mut output = RteIntOutput { rad1: &mut rad1, dt: &mut dt, }; // 运行 RTEINT rteint( &config, &model, &freq_params, &physics, &mut opacity, &flux_data, &mut output, |_ij| { // 空的 OPACF1 回调 }, ); // 验证输出不为 NaN for id in 0..nd { assert!(!output.rad1[id].is_nan()); } } }