SpectraRust/src/tlusty/math/temperature/lucy.rs
2026-04-01 16:35:36 +08:00

728 lines
22 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.

//! NLTE Lucy-Unsöld 温度修正方案。
//!
//! 重构自 TLUSTY `lucy.f`。
//!
//! # 功能
//!
//! 实现 Werner & Dreizler 的 NLTE 温度修正方案:
//! 1. 计算辐射加热/冷却率
//! 2. 计算 Eddington 因子
//! 3. 应用温度修正
//! 4. 积分流体静力学平衡
//! 5. 更新粒子数
//!
//! # 算法
//!
//! 基于辐射平衡方程:
//! - HEAT = 加热率(吸收 - 发射)
//! - DELH = 辐射流偏离
//! - DELTAT = 温度修正
use crate::tlusty::state::constants::{BN, BOLK, HALF, HK, MDEPTH, MFREQ, MTRANS, SIG4P, UN};
// f2r_depends: COLIS, CONCOR, ELCOR, ODFMER, OPACFL, OPAINI, SABOLF, STEQEQ, TDPINI, WNSTOR
// ============================================================================
// 常量
// ============================================================================
/// 1/3
const THIRD: f64 = 1.0 / 3.0;
// ============================================================================
// 配置结构体
// ============================================================================
/// LUCY 配置参数。
#[derive(Debug, Clone)]
pub struct LucyConfig {
/// Lucy 迭代次数上限 (ITLUCY)
pub itlucy: i32,
/// 加速开始迭代 (IACLT)
pub iaclt: i32,
/// 加速间隔 (IACLDT)
pub iacldt: i32,
/// 流体静力学修正标志 (IHECOR)
pub ihecor: i32,
/// 是否 LTE (LTE)
pub lte: bool,
/// LCHC 标志
pub lchc: bool,
/// 电子密度修正起始迭代 (IELCOR)
pub ielcor: i32,
/// 当前迭代次数 (ITER)
pub iter: i32,
/// Lucy 跃迁列表 (NTRL)
pub ntrl: i32,
/// Lucy 跃迁标志 (ILUCTR)
pub iluctr: Vec<i32>,
}
impl Default for LucyConfig {
fn default() -> Self {
Self {
itlucy: 0,
iaclt: 10,
iacldt: 1,
ihecor: 1,
lte: false,
lchc: false,
ielcor: 10,
iter: 0,
ntrl: 0,
iluctr: vec![0; MTRANS],
}
}
}
// ============================================================================
// 输入参数结构体
// ============================================================================
/// LUCY 模型参数(只读引用)。
pub struct LucyModelParams<'a> {
/// 深度点数 (ND)
pub nd: usize,
/// 频率点数 (NFREQ)
pub nfreq: usize,
/// 跃迁数 (NTRANS)
pub ntrans: usize,
/// 有效温度 (TEFF)
pub teff: f64,
/// 表面重力加速度 (GRAV)
pub grav: f64,
/// 参考辐射压 (PRD0)
pub prd0: f64,
// 深度相关数组 [nd]
/// 深度 (柱质量密度, g/cm²)
pub dm: &'a [f64],
/// 温度 (K)
pub temp: &'a [f64],
/// 电子密度 (cm⁻³)
pub elec: &'a [f64],
/// 总粒子密度 (cm⁻³)
pub dens: &'a [f64],
/// 密度倒数 (1/DENS)
pub dens1: &'a [f64],
/// 平均分子量
pub wmm: &'a [f64],
/// 深度间隔 [nd-1]
pub deldm: &'a [f64],
/// 湍流速度
pub vturb: &'a [f64],
/// 辐射压力梯度
pub pradt: &'a [f64],
/// 气体压力 [nd]
pub pgs: &'a mut [f64],
// 频率相关数组 [nfreq]
/// 频率 (Hz)
pub freq: &'a [f64],
/// 频率权重
pub w: &'a [f64],
/// H 函数
pub fh: &'a [f64],
/// 外辐射场
pub hextrd: &'a [f64],
// ALI 相关数组
/// LAC2T 标志
pub lac2t: bool,
}
/// LUCY 输出结果。
#[derive(Debug, Clone)]
pub struct LucyOutput {
/// 新温度 [nd]
pub temp: Vec<f64>,
/// 新电子密度 [nd]
pub elec: Vec<f64>,
/// 新总密度 [nd]
pub dens: Vec<f64>,
/// 密度倒数 [nd]
pub dens1: Vec<f64>,
/// 气体压力 [nd]
pub pgs: Vec<f64>,
/// Lucy 迭代计数
pub ilucy: i32,
/// LAC2T 标志
pub lac2t: bool,
/// 最大加热偏差
pub dhhmx1: f64,
}
// ============================================================================
// 辅助结构体 - 迭代状态
// ============================================================================
/// Lucy 迭代内部状态。
struct LucyState {
/// 加热率 [nd]
heat: Vec<f64>,
/// 加热/普朗克比 [nd]
heab: Vec<f64>,
/// 零深度吸收 [nd]
absz: Vec<f64>,
/// 普朗克加权吸收 [nd]
absp: Vec<f64>,
/// 流加权吸收 [nd]
absh: Vec<f64>,
/// 总 J 积分 [nd]
totj: Vec<f64>,
/// 总 H 积分 [nd]
toth: Vec<f64>,
/// 总 B 积分 [nd]
totb: Vec<f64>,
/// Eddington 因子 [nd]
eddf: Vec<f64>,
/// 流偏离 [nd]
delh: Vec<f64>,
/// 光学深度 [nd]
tau: Vec<f64>,
/// 温度修正 [nd]
deltat: Vec<f64>,
/// 温度修正分量1 [nd]
dt1: Vec<f64>,
/// 温度修正分量2 [nd]
dt2: Vec<f64>,
/// 粒子数密度 [nd]
antc: Vec<f64>,
/// 电子分数 [nd]
xe: Vec<f64>,
/// 温度历史 0 [nd]
tem0: Vec<f64>,
/// 温度历史 1 [nd]
tem1: Vec<f64>,
/// 温度历史 2 [nd]
tem2: Vec<f64>,
/// 温度历史 3 [nd]
tem3: Vec<f64>,
/// Eddington H 比值
eddh: f64,
}
impl LucyState {
fn new(nd: usize) -> Self {
Self {
heat: vec![0.0; nd],
heab: vec![0.0; nd],
absz: vec![0.0; nd],
absp: vec![0.0; nd],
absh: vec![0.0; nd],
totj: vec![0.0; nd],
toth: vec![0.0; nd],
totb: vec![0.0; nd],
eddf: vec![0.0; nd],
delh: vec![0.0; nd],
tau: vec![0.0; nd],
deltat: vec![0.0; nd],
dt1: vec![0.0; nd],
dt2: vec![0.0; nd],
antc: vec![0.0; nd],
xe: vec![0.0; nd],
tem0: vec![0.0; nd],
tem1: vec![0.0; nd],
tem2: vec![0.0; nd],
tem3: vec![0.0; nd],
eddh: 0.0,
}
}
}
// ============================================================================
// 纯计算函数
// ============================================================================
/// Lucy 温度修正方案核心计算。
///
/// 这是一个简化版本,仅实现核心温度修正逻辑。
/// 完整版本需要传入完整的模型状态和所有依赖函数。
///
/// # 参数
/// - `config`: 配置参数
/// - `model`: 模型参数
/// - `opacfl_data`: 不透明度数据 (ABSO1, EMIS1L, SCAT1, ABSO1L)
/// - `rad1_data`: 辐射场数据 (RAD1, FAK1)
///
/// # 返回值
/// 温度修正后的模型状态
///
/// # Fortran 原始代码
///
/// ```fortran
/// SUBROUTINE LUCY
/// ! NLTE Lucy-Unsold temperature correction scheme
/// DO IJ=1,NFREQ
/// CALL OPACFL(IJ)
/// CALL RTEFR1(IJ)
/// ! 计算加热率、Eddington 因子等
/// END DO
/// ! 计算温度修正 DELTAT
/// ! 应用加速方案
/// ! 积分流体静力学平衡
/// END
/// ```
pub fn lucy_pure(
config: &LucyConfig,
model: &LucyModelParams,
opacfl_data: &[OpacflPointData],
rad1_data: &[Rad1PointData],
) -> LucyOutput {
let nd = model.nd;
let nfreq = model.nfreq;
// 如果 ITLUCY <= 0直接返回
if config.itlucy <= 0 {
return LucyOutput {
temp: model.temp.to_vec(),
elec: model.elec.to_vec(),
dens: model.dens.to_vec(),
dens1: model.dens1.to_vec(),
pgs: model.pgs.to_vec(),
ilucy: 0,
lac2t: false,
dhhmx1: 0.0,
};
}
// 初始化状态
let mut state = LucyState::new(nd);
let mut lac2t = false;
let iacc0t = config.iaclt - 3;
let mut ilucy = 1;
// 迭代循环
while ilucy <= config.itlucy {
// 重置累积量
for id in 0..nd {
state.heat[id] = 0.0;
state.heab[id] = 0.0;
state.absz[id] = 0.0;
state.absp[id] = 0.0;
state.absh[id] = 0.0;
state.totj[id] = 0.0;
state.toth[id] = 0.0;
state.totb[id] = 0.0;
state.eddf[id] = 0.0;
}
state.eddh = 0.0;
// 频率循环
for ij in 0..nfreq {
let w0 = model.w[ij];
let fr = model.freq[ij];
let fr15 = fr * 1e-15;
let bnu = BN * fr15 * fr15 * fr15;
// 获取预计算的不透明度和辐射数据
let opac = &opacfl_data[ij];
let rad = &rad1_data[ij];
// 计算光学深度 (第一个点)
if ij == 0 {
state.tau[0] = opac.abso1[0] / model.dens[0] * model.dm[0];
}
for id in 0..nd {
// 计算光学深度
if id > 0 {
state.tau[id] = state.tau[id - 1]
+ HALF
* (opac.abso1[id] / model.dens[id]
+ opac.abso1[id - 1] / model.dens[id - 1])
* (model.dm[id] - model.dm[id - 1]);
}
let pland = bnu / ((HK * fr / model.temp[id]).exp() - UN);
let absot0 = opac.abso1l[id] - opac.scat1[id];
// 加热率累积
state.heat[id] += w0 * (absot0 * rad.rad1[id] - opac.emis1l[id]);
state.heab[id] +=
w0 * (absot0 * rad.rad1[id] - opac.emis1l[id]) / (absot0 * pland);
state.absp[id] += w0 * absot0 * pland;
state.totj[id] += w0 * rad.rad1[id];
state.totb[id] += w0 * pland;
state.eddf[id] += w0 * rad.fak1[id] * rad.rad1[id];
}
// 表面 Eddington H
state.eddh += w0 * rad.rad1[0] * model.fh[ij];
state.toth[0] += w0 * (rad.rad1[0] * model.fh[ij] - model.hextrd[ij]);
let absot0 = opac.abso1[0];
state.absh[0] += w0 * absot0 * (rad.rad1[0] * model.fh[ij] - model.hextrd[ij]);
// 深度积分
for id in 1..nd {
let absot0 = HALF * (opac.abso1[id] + opac.abso1[id - 1]);
let dtm = model.deldm[id - 1]
* (opac.abso1[id] * model.dens1[id] + opac.abso1[id - 1] * model.dens1[id - 1]);
let fluz = (rad.rad1[id] * rad.fak1[id] - rad.rad1[id - 1] * rad.fak1[id - 1]) / dtm;
state.toth[id] += w0 * fluz;
state.absh[id] += w0 * absot0 * fluz;
}
}
// 归一化
state.eddh /= state.totj[0];
let tef4 = SIG4P * model.teff.powi(4);
state.toth[nd - 1] = tef4;
state.heat[nd - 1] = 0.0;
state.heat[nd - 2] = 0.0;
// 计算各种吸收系数和流偏离
for id in 0..nd {
state.absz[id] = (state.heat[id] + state.absp[id]) / state.totj[id];
state.absp[id] /= state.totb[id];
state.absh[id] /= state.toth[id];
state.eddf[id] /= state.totj[id];
state.delh[id] = -state.toth[id] + tef4;
}
// 计算最大流偏离
let mut dhhmx1 = 0.0;
for id in 0..(nd - 1) {
let dhh = (state.delh[id] / state.toth[id]).abs();
if dhh > dhhmx1 {
dhhmx1 = dhh;
}
}
// 计算温度修正
let mut xx = 0.0;
let mut xx1 = 0.0;
// 表面点
let tp3 = model.temp[0].powi(3);
xx = state.eddf[0] / state.eddh * state.delh[0];
xx1 = xx;
state.dt1[0] = state.heat[0] / 16.0 / SIG4P * tp3 / state.absp[0];
state.dt2[0] = state.absz[0] / state.eddf[0] * xx / 16.0 / SIG4P * tp3 / state.absp[0];
state.deltat[0] = state.dt1[0] + state.dt2[0];
// 内部点
for id in 1..nd {
let tp3 = model.temp[id].powi(3);
xx += model.deldm[id - 1]
* (state.absh[id - 1] * model.dens1[id - 1] * state.delh[id - 1]
+ state.absh[id] * model.dens1[id] * state.delh[id]);
state.dt1[id] = state.heat[id] / 16.0 / SIG4P * tp3 / state.absp[id];
state.dt2[id] = state.absz[id] / state.eddf[id] * xx / 16.0 / SIG4P * tp3 / state.absp[id];
state.deltat[id] = state.dt1[id] + state.dt2[id];
}
// 应用温度修正
let mut new_temp = model.temp.to_vec();
let mut new_elec = model.elec.to_vec();
let mut new_dens = model.dens.to_vec();
let mut new_dens1 = model.dens1.to_vec();
let mut new_pgs = model.pgs.to_vec();
for id in 0..nd {
new_temp[id] += state.deltat[id];
state.tem0[id] = new_temp[id];
let aold = new_dens[id] / model.wmm[id] + new_elec[id];
state.xe[id] = UN - new_elec[id] / aold;
}
// 加速方案
if ilucy >= config.iaclt && ilucy >= iacc0t {
let ipng = if config.iacldt > 0 {
(ilucy - config.iaclt) % config.iacldt
} else {
0
};
if !lac2t {
let ipt = ilucy % 3;
let ipt0 = config.iaclt % 3;
let ipt1 = (config.iaclt + 1) % 3;
let ipt2 = (config.iaclt + 2) % 3;
if ilucy == iacc0t {
for id in 0..nd {
state.tem3[id] = state.tem0[id];
}
} else if ipt == ipt1 {
for id in 0..nd {
state.tem2[id] = state.tem0[id];
}
} else if ipt == ipt2 {
for id in 0..nd {
state.tem1[id] = state.tem0[id];
}
}
} else if ipng != 0 {
// 滚动温度历史
for id in 0..nd {
state.tem3[id] = state.tem2[id];
state.tem2[id] = state.tem1[id];
state.tem1[id] = state.tem0[id];
}
} else {
// 应用加速度
if ilucy >= config.iaclt {
let (a1, b1, b2, c1, c2) = compute_acceleration(&state, nd);
let ab = b2 * a1 - b1 * b1;
if ab != 0.0 {
let a0 = (b2 * c1 - b1 * c2) / ab;
let b0 = (a1 * c2 - b1 * c1) / ab;
for id in 0..nd {
state.tem0[id] = (1.0 - a0 - b0) * state.tem0[id]
+ a0 * state.tem1[id]
+ b0 * state.tem2[id];
new_temp[id] = state.tem0[id];
}
lac2t = true;
} else {
// 对应 Fortran: WRITE(6,601) ILUCY,AB
// FORMAT(/,' **** ACCELT, ITER=',I4,' AB = ',F7.3,/)
eprintln!("\n **** ACCELT, ITER={:4} AB = {:7.3}\n", ilucy, ab);
}
}
}
}
// 流体静力学平衡积分
if config.ihecor >= 1 {
// 表面边界条件
let ptur = HALF * model.vturb[0] * model.vturb[0] * new_dens[0];
state.antc[0] = (model.dm[0] * model.grav - model.prd0 - ptur) / BOLK / new_temp[0];
if state.antc[0] <= 0.0 {
state.antc[0] = new_dens[0] / model.wmm[0] + new_elec[0];
}
// 向内积分
for id in 1..nd {
let ptur = HALF * model.vturb[id] * model.vturb[id] * new_dens[id];
let pturm = HALF * model.vturb[id - 1] * model.vturb[id - 1] * new_dens[id - 1];
state.antc[id] = (model.grav * (model.dm[id] - model.dm[id - 1])
+ BOLK * new_temp[id - 1] * state.antc[id - 1]
- model.pradt[id]
+ model.pradt[id - 1]
- ptur
+ pturm)
/ BOLK
/ new_temp[id];
}
// 更新密度
for id in 0..nd {
new_elec[id] = (UN - state.xe[id]) * state.antc[id];
new_dens[id] = model.wmm[id] * (state.antc[id] - new_elec[id]);
new_dens1[id] = UN / new_dens[id];
}
}
// 更新气体压力
for id in 0..nd {
new_pgs[id] = (new_dens[id] / model.wmm[id] + new_elec[id]) * BOLK * new_temp[id];
}
ilucy += 1;
// 单次迭代后返回(完整版本会循环)
return LucyOutput {
temp: new_temp,
elec: new_elec,
dens: new_dens,
dens1: new_dens1,
pgs: new_pgs,
ilucy,
lac2t,
dhhmx1,
};
}
// 不应该到达这里
LucyOutput {
temp: model.temp.to_vec(),
elec: model.elec.to_vec(),
dens: model.dens.to_vec(),
dens1: model.dens1.to_vec(),
pgs: model.pgs.to_vec(),
ilucy,
lac2t,
dhhmx1: 0.0,
}
}
// ============================================================================
// 辅助函数
// ============================================================================
/// 计算加速度系数。
fn compute_acceleration(state: &LucyState, nd: usize) -> (f64, f64, f64, f64, f64) {
let mut a1 = 0.0;
let mut b1 = 0.0;
let mut b2 = 0.0;
let mut c1 = 0.0;
let mut c2 = 0.0;
for id in 0..nd {
let mut wt = 0.0;
if state.tem0[id] != 0.0 {
wt = 1.0 / state.tem0[id].abs();
}
let d0 = state.tem0[id] - state.tem1[id];
let d1 = d0 - state.tem1[id] + state.tem2[id];
let d2 = d0 - state.tem2[id] + state.tem3[id];
a1 += wt * d1 * d1;
b1 += wt * d1 * d2;
b2 += wt * d2 * d2;
c1 += wt * d0 * d1;
c2 += wt * d0 * d2;
}
(a1, b1, b2, c1, c2)
}
// ============================================================================
// 数据结构体
// ============================================================================
/// 单个频率点的不透明度数据。
#[derive(Debug, Clone, Default)]
pub struct OpacflPointData {
/// 吸收系数 [nd]
pub abso1: Vec<f64>,
/// 谱线吸收系数 [nd]
pub abso1l: Vec<f64>,
/// 发射系数 [nd]
pub emis1l: Vec<f64>,
/// 散射系数 [nd]
pub scat1: Vec<f64>,
}
/// 单个频率点的辐射场数据。
#[derive(Debug, Clone, Default)]
pub struct Rad1PointData {
/// 辐射强度 [nd]
pub rad1: Vec<f64>,
/// Eddington 因子 [nd]
pub fak1: Vec<f64>,
}
// ============================================================================
// 测试
// ============================================================================
#[cfg(test)]
mod tests {
use super::*;
fn create_test_model(nd: usize, nfreq: usize) -> (LucyConfig, LucyModelParams<'static>) {
let config = LucyConfig {
itlucy: 1,
iaclt: 10,
iacldt: 1,
ihecor: 1,
lte: false,
lchc: false,
ielcor: 10,
iter: 0,
ntrl: 0,
iluctr: vec![0; MTRANS],
};
// 注意:这里需要使用静态生命周期,实际测试中需要用 Box::leak 或其他方式
let dm: &'static [f64] = Box::leak((0..nd).map(|i| 0.01 * (i + 1) as f64).collect::<Vec<_>>().into_boxed_slice());
let temp: &'static [f64] = Box::leak((0..nd).map(|i| 10000.0 - 100.0 * i as f64).collect::<Vec<_>>().into_boxed_slice());
let elec: &'static [f64] = Box::leak(vec![1e12; nd].into_boxed_slice());
let dens: &'static [f64] = Box::leak(vec![1e14; nd].into_boxed_slice());
let dens1: &'static [f64] = Box::leak(vec![1e-14; nd].into_boxed_slice());
let wmm: &'static [f64] = Box::leak(vec![1.0; nd].into_boxed_slice());
let deldm: &'static [f64] = Box::leak(vec![0.005; nd - 1].into_boxed_slice());
let vturb: &'static [f64] = Box::leak(vec![0.0; nd].into_boxed_slice());
let pradt: &'static [f64] = Box::leak(vec![0.0; nd].into_boxed_slice());
let pgs: &'static mut [f64] = Box::leak(vec![0.0; nd].into_boxed_slice());
let freq: &'static [f64] = Box::leak(vec![1e15; nfreq].into_boxed_slice());
let w: &'static [f64] = Box::leak(vec![1.0 / nfreq as f64; nfreq].into_boxed_slice());
let fh: &'static [f64] = Box::leak(vec![0.5; nfreq].into_boxed_slice());
let hextrd: &'static [f64] = Box::leak(vec![0.0; nfreq].into_boxed_slice());
let model = LucyModelParams {
nd,
nfreq,
ntrans: 10,
teff: 10000.0,
grav: 1e4, // 典型表面重力加速度
prd0: 0.0, // 参考辐射压
dm,
temp,
elec,
dens,
dens1,
wmm,
deldm,
vturb,
pradt,
pgs,
freq,
w,
fh,
hextrd,
lac2t: false,
};
(config, model)
}
#[test]
fn test_lucy_config_default() {
let config = LucyConfig::default();
assert_eq!(config.itlucy, 0);
assert_eq!(config.iaclt, 10);
assert_eq!(config.ihecor, 1);
assert!(!config.lte);
}
#[test]
fn test_lucy_state_creation() {
let state = LucyState::new(10);
assert_eq!(state.heat.len(), 10);
assert_eq!(state.tau.len(), 10);
assert_eq!(state.eddh, 0.0);
}
#[test]
fn test_lucy_zero_iterations() {
let (config, model) = create_test_model(5, 3);
let opacfl_data = vec![OpacflPointData::default(); 3];
let rad1_data = vec![Rad1PointData::default(); 3];
// 当 itlucy = 0 时,应该直接返回
let config_zero = LucyConfig {
itlucy: 0,
..config.clone()
};
let output = lucy_pure(&config_zero, &model, &opacfl_data, &rad1_data);
assert_eq!(output.ilucy, 0);
}
#[test]
fn test_compute_acceleration() {
let nd = 5;
let mut state = LucyState::new(nd);
// 设置一些测试值
for i in 0..nd {
state.tem0[i] = 10000.0 + i as f64;
state.tem1[i] = 9900.0 + i as f64;
state.tem2[i] = 9800.0 + i as f64;
state.tem3[i] = 9700.0 + i as f64;
}
let (a1, b1, b2, c1, c2) = compute_acceleration(&state, nd);
// 加速度系数应该是正数
assert!(a1 >= 0.0);
assert!(b2 >= 0.0);
}
}