769 lines
23 KiB
Rust
769 lines
23 KiB
Rust
//! 辐射传输方程求解器 - 已知源函数时的特定强度计算。
|
||
//!
|
||
//! 重构自 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<f64>,
|
||
/// 角度权重 (mma)
|
||
pub wang: Vec<f64>,
|
||
/// 角度点数
|
||
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<F>(
|
||
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<f64> = 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());
|
||
}
|
||
}
|
||
}
|