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

769 lines
23 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.

//! 辐射传输方程求解器 - 已知源函数时的特定强度计算。
//!
//! 重构自 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());
}
}
}