SpectraRust/src/tlusty/math/utils/newdmt.rs
2026-06-06 14:24:50 +08:00

638 lines
19 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 `newdmt.f`
//!
//! # 功能
//!
//! 计算新的 m-scale柱质量密度基于新网格更好地表示温度变化。
//! 使用 GRIDP 函数找到最优网格点分布。
use crate::tlusty::math::gridp;
use crate::tlusty::math::interp;
use crate::tlusty::state::constants::{HALF, MDEPTH, TWO, UN};
// f2r_depends: GRIDP, HESOLV, TEMPER
// ============================================================================
// 常量
// ============================================================================
/// ln(10) = 2.3025851...
const LN10: f64 = 2.30258509299404568402;
// ============================================================================
// 辅助参数结构体
// ============================================================================
/// PRSAUX COMMON 块参数
#[derive(Debug, Clone)]
pub struct NewdmtPrsAux {
/// 声速平方 [深度]
pub vsnd2: Vec<f64>,
/// 表面气压标高
pub hg1: f64,
/// 辐射气压标高
pub hr1: f64,
/// 辐射/气压标高比
pub rr1: f64,
}
impl Default for NewdmtPrsAux {
fn default() -> Self {
Self {
vsnd2: vec![0.0; MDEPTH],
hg1: 0.0,
hr1: 0.0,
rr1: 0.0,
}
}
}
/// FACTRS COMMON 块参数
#[derive(Debug, Clone)]
pub struct NewdmtFactrs {
/// 辐射因子 [深度]
pub gamj: Vec<f64>,
/// 几何稀释因子
pub gamh: f64,
pub fak0: f64,
}
impl Default for NewdmtFactrs {
fn default() -> Self {
Self {
gamj: vec![1.0; MDEPTH],
gamh: 1.0,
fak0: 0.0,
}
}
}
// ============================================================================
// 配置结构体
// ============================================================================
/// NEWDMT 配置参数。
#[derive(Debug, Clone)]
pub struct NewdmtConfig {
/// 深度点数
pub nd: usize,
/// 粘性深度参数
pub dmvisc: f64,
/// ZETA0 参数
pub zeta0: f64,
/// ZETA1 参数
pub zeta1: f64,
/// 粘性分数
pub fractv: f64,
/// 收敛迭代控制
pub nconit: i32,
/// 打印控制
pub ipring: i32,
}
impl Default for NewdmtConfig {
fn default() -> Self {
Self {
nd: 50,
dmvisc: 0.0,
zeta0: 0.0,
zeta1: 0.0,
fractv: 0.0,
nconit: 0,
ipring: 0,
}
}
}
// ============================================================================
// 模型状态结构体
// ============================================================================
/// NEWDMT 模型状态(可变)。
#[derive(Debug, Clone)]
pub struct NewdmtModelState {
/// 深度 (柱质量密度, g/cm²) [深度]
pub dm: Vec<f64>,
/// 温度 (K) [深度]
pub temp: Vec<f64>,
/// 电子密度 (cm⁻³) [深度]
pub elec: Vec<f64>,
/// 总粒子密度 (cm⁻³) [深度]
pub dens: Vec<f64>,
/// 深度变量 (cm) [深度]
pub zd: Vec<f64>,
/// Rosseland 光学深度 [深度]
pub tauros: Vec<f64>,
/// 热光学深度 [深度]
pub tauthe: Vec<f64>,
/// Theta 函数 [深度]
pub theta: Vec<f64>,
/// 粘性系数 [深度]
pub viscd: Vec<f64>,
/// Rosseland 平均不透明度 [深度]
pub abrosd: Vec<f64>,
/// Planck 平均不透明度 [深度]
pub abplad: Vec<f64>,
/// 平均分子量 [深度]
pub wmm: Vec<f64>,
/// 总压力 [深度]
pub ptotal: Vec<f64>,
}
impl Default for NewdmtModelState {
fn default() -> Self {
Self {
dm: vec![0.0; MDEPTH],
temp: vec![0.0; MDEPTH],
elec: vec![0.0; MDEPTH],
dens: vec![0.0; MDEPTH],
zd: vec![0.0; MDEPTH],
tauros: vec![0.0; MDEPTH],
tauthe: vec![0.0; MDEPTH],
theta: vec![0.0; MDEPTH],
viscd: vec![0.0; MDEPTH],
abrosd: vec![0.0; MDEPTH],
abplad: vec![0.0; MDEPTH],
wmm: vec![1.0; MDEPTH],
ptotal: vec![0.0; MDEPTH],
}
}
}
// ============================================================================
// 主计算函数
// ============================================================================
/// NEWDMT 纯计算函数(网格计算部分)。
///
/// 计算新的深度网格,基于温度变化使用 GRIDP。
///
/// # 参数
///
/// * `config` - 配置参数
/// * `state` - 模型状态(输入/输出)
/// * `prs_aux` - PRSAUX 参数(输出)
/// * `factrs` - FACTRS 参数(输出)
///
/// # 注意
///
/// 完整实现需要调用 TEMPER 和 HESOLV这里只实现网格计算部分。
pub fn newdmt(
config: &NewdmtConfig,
state: &mut NewdmtModelState,
prs_aux: &mut NewdmtPrsAux,
factrs: &mut NewdmtFactrs,
) {
let nd = config.nd;
let nd1 = nd - 1;
// 工作数组
let mut dm0 = vec![0.0; MDEPTH]; // log10(dm) 原始
let mut dm11 = vec![0.0; MDEPTH]; // 新 log10(dm)
let mut t0 = vec![0.0; MDEPTH]; // log10(tauros) 原始
let mut t1 = vec![0.0; MDEPTH]; // 新 log10(tauros)
let mut elec0 = vec![0.0; MDEPTH]; // 原始 elec
let mut dens0 = vec![0.0; MDEPTH]; // 原始 dens
let mut zd0 = vec![0.0; MDEPTH]; // 原始 zd
let mut pt0 = vec![0.0; MDEPTH]; // 原始 ptotal
let mut abrs0 = vec![0.0; MDEPTH]; // 原始 abrosd
let mut abpl0 = vec![0.0; MDEPTH]; // 原始 abplad
// ========================================================================
// Step 1: 保存原始值并计算 log10
// ========================================================================
for id in 0..nd {
dm0[id] = if state.dm[id] > 0.0 {
state.dm[id].log10()
} else {
-10.0
};
t0[id] = if state.tauros[id] > 0.0 {
state.tauros[id].log10()
} else {
-10.0
};
elec0[id] = state.elec[id];
dens0[id] = state.dens[id];
pt0[id] = state.ptotal[id];
zd0[id] = state.zd[id];
abrs0[id] = state.abrosd[id];
abpl0[id] = state.abplad[id];
}
// ========================================================================
// Step 2: 使用 GRIDP 计算新网格
// ========================================================================
// GRIDP 需要 nd1 个点(不包括最后一个点)
gridp(&dm0, &t0, &mut dm11, &mut t1, nd1);
// 最后一个点保持不变
dm11[nd - 1] = dm0[nd - 1];
t1[nd - 1] = t0[nd - 1];
// ========================================================================
// Step 3: 转换回线性尺度
// ========================================================================
for id in 0..nd {
state.dm[id] = (LN10 * dm11[id]).exp();
state.tauros[id] = (LN10 * t1[id]).exp();
}
// ========================================================================
// Step 4: 插值各种物理量到新网格
// ========================================================================
// 需要可变数组用于 INTERPINTERP 会修改输入数组)
let mut dm0_mut = dm0.clone();
let mut elec0_mut = elec0.clone();
let mut dm11_mut = dm11.clone();
let mut elec_out = vec![0.0; MDEPTH];
interp(
&mut dm0_mut,
&mut elec0_mut,
&mut dm11_mut,
&mut elec_out,
nd,
nd,
2, // 线性插值
0, // 不对 x 取对数(已经是对数)
1, // 对 y 取对数
);
for id in 0..nd {
state.elec[id] = elec_out[id];
}
// INTERP(DM0, DENS0, DM11, DENS, ND, ND, 2, 0, 1)
let mut dm0_mut = dm0.clone();
let mut dens0_mut = dens0.clone();
let mut dm11_mut = dm11.clone();
let mut dens_out = vec![0.0; MDEPTH];
interp(
&mut dm0_mut,
&mut dens0_mut,
&mut dm11_mut,
&mut dens_out,
nd,
nd,
2,
0,
1,
);
for id in 0..nd {
state.dens[id] = dens_out[id];
}
// INTERP(DM0, PT0, DM11, PTOTAL, ND, ND, 2, 0, 1)
let mut dm0_mut = dm0.clone();
let mut pt0_mut = pt0.clone();
let mut dm11_mut = dm11.clone();
let mut ptotal_out = vec![0.0; MDEPTH];
interp(
&mut dm0_mut,
&mut pt0_mut,
&mut dm11_mut,
&mut ptotal_out,
nd,
nd,
2,
0,
1,
);
for id in 0..nd {
state.ptotal[id] = ptotal_out[id];
}
// INTERP(DM0, ZD0, DM11, ZD, ND, ND, 2, 0, 0)
let mut dm0_mut = dm0.clone();
let mut zd0_mut = zd0.clone();
let mut dm11_mut = dm11.clone();
let mut zd_out = vec![0.0; MDEPTH];
interp(
&mut dm0_mut,
&mut zd0_mut,
&mut dm11_mut,
&mut zd_out,
nd,
nd,
2,
0,
0, // 不对 y 取对数
);
for id in 0..nd {
state.zd[id] = zd_out[id];
}
// INTERP(DM0, ABRS0, DM11, ABROSD, ND, ND, 2, 0, 1)
let mut dm0_mut = dm0.clone();
let mut abrs0_mut = abrs0.clone();
let mut dm11_mut = dm11.clone();
let mut abrosd_out = vec![0.0; MDEPTH];
interp(
&mut dm0_mut,
&mut abrs0_mut,
&mut dm11_mut,
&mut abrosd_out,
nd,
nd,
2,
0,
1,
);
for id in 0..nd {
state.abrosd[id] = abrosd_out[id];
}
// INTERP(DM0, ABPL0, DM11, ABPLAD, ND, ND, 2, 0, 1)
let mut dm0_mut = dm0.clone();
let mut abpl0_mut = abpl0.clone();
let mut dm11_mut = dm11.clone();
let mut abplad_out = vec![0.0; MDEPTH];
interp(
&mut dm0_mut,
&mut abpl0_mut,
&mut dm11_mut,
&mut abplad_out,
nd,
nd,
2,
0,
1,
);
for id in 0..nd {
state.abplad[id] = abplad_out[id];
}
// ========================================================================
// Step 5: 计算声速平方 VSND2 = PTOTAL / DENS
// ========================================================================
for id in 0..nd {
if state.dens[id] > 0.0 {
prs_aux.vsnd2[id] = state.ptotal[id] / state.dens[id];
}
}
// ========================================================================
// Step 6: 计算新的 Rosseland 不透明度和 theta/tauthe 函数
// ========================================================================
let amuv0 = if config.dmvisc > 0.0 && state.dm[nd - 1] > 0.0 {
config.dmvisc.powf(config.zeta0 + UN)
} else {
0.0
};
let amuv1 = UN - amuv0;
for id in 0..nd {
// 计算 viscd 和 theta
if config.dmvisc > 0.0 && state.dm[nd - 1] > 0.0 {
let ratio = state.dm[id] / state.dm[nd - 1];
if ratio <= config.dmvisc {
// 内部区域dm <= dmvisc * dm[nd-1]
state.viscd[id] = (UN - config.fractv) * (config.zeta1 + UN)
/ config.dmvisc.powf(config.zeta1 + UN)
* ratio.powf(config.zeta1);
state.theta[id] =
(UN - config.fractv) * (ratio / config.dmvisc).powf(config.zeta1 + UN);
} else {
// 外部区域dm > dmvisc * dm[nd-1]
state.viscd[id] =
config.fractv * (config.zeta0 + UN) / amuv1 * ratio.powf(config.zeta0);
state.theta[id] = (UN - config.fractv)
+ config.fractv * (ratio.powf(config.zeta0 + UN) - amuv0) / amuv1;
}
} else {
// 当 dmvisc = 0 或 dm[nd-1] = 0 时theta = 1.0(无粘性)
state.theta[id] = UN;
state.viscd[id] = 0.0;
}
factrs.gamj[id] = UN;
// 计算 tauros 和 tauthe
if id == 0 {
state.tauros[id] = state.dm[id] * state.abrosd[id];
state.tauthe[id] = state.tauros[id] * state.theta[id] / (config.zeta1 + TWO);
// 原始代码中计算 ANEREL但不使用
} else {
let ddm = state.dm[id] - state.dm[id - 1];
state.tauros[id] =
state.tauros[id - 1] + ddm * HALF * (state.abrosd[id - 1] + state.abrosd[id]);
let zetad = if config.dmvisc > 0.0
&& state.dm[nd - 1] > 0.0
&& state.dm[id] <= config.dmvisc * state.dm[nd - 1]
{
config.zeta1
} else {
config.zeta0
};
let a0 = (state.abrosd[id - 1] * state.dm[id] - state.abrosd[id] * state.dm[id - 1])
/ ddm
/ (zetad + TWO);
let a1 =
(state.abrosd[id] - state.abrosd[id - 1]) / ddm / (zetad + 3.0);
state.tauthe[id] = state.tauthe[id - 1]
+ a0 * (state.theta[id] * state.dm[id] - state.theta[id - 1] * state.dm[id - 1])
+ a1 * (state.theta[id] * state.dm[id] * state.dm[id]
- state.theta[id - 1] * state.dm[id - 1] * state.dm[id - 1]);
}
// 注意:原始代码中这里调用 TEMPER
// CALL TEMPER(ID, TAUR, 1)
}
// ========================================================================
// Step 7: 调用 HESOLV如果 nconit >= 0
// ========================================================================
// 注意:原始代码中这里调用 HESOLV
// if(nconit.ge.0) CALL HESOLV
// ========================================================================
// Step 8: 再次计算温度和平均不透明度
// ========================================================================
// 注意:原始代码中再次调用 TEMPER 和 HESOLV
// ========================================================================
// Step 9: 打印输出(如果 ipring >= 1
// ========================================================================
if config.ipring >= 1 {
eprintln!("\n NEW DEPTH GRID ESTABLISHED, NEW MODEL:");
eprintln!(" --------------------------------------");
eprintln!(" ID DM TAUROSS TEMP NE P ZD ROSS.MEAN PLANCK");
for id in 0..nd {
eprintln!(
" {:3}{:9.2e}{:9.2e}{:8.0}{:9.2e}{:9.2e}{:9.2e} {:9.2e}{:9.2e}",
id + 1,
state.dm[id],
state.tauros[id],
state.temp[id],
state.elec[id],
state.ptotal[id],
state.zd[id],
state.abrosd[id],
state.abplad[id]
);
}
}
}
// ============================================================================
// 测试
// ============================================================================
#[cfg(test)]
mod tests {
use super::*;
fn create_test_state(nd: usize) -> NewdmtModelState {
let mut state = NewdmtModelState::default();
// 创建简单的测试网格
for i in 0..nd {
let x = (i + 1) as f64 / nd as f64;
state.dm[i] = 1e-4 * x.powf(2.0);
state.dens[i] = 1e13 * (1.0 - 0.5 * x);
state.abrosd[i] = 1e-4 * (1.0 + x);
state.abplad[i] = 2e-4 * (1.0 + x);
state.tauros[i] = state.dm[i] * state.abrosd[i];
state.elec[i] = 1e10 * x;
state.ptotal[i] = 1e4 * (1.0 + x);
state.zd[i] = 1e8 * x;
state.wmm[i] = 1.0;
}
state
}
#[test]
fn test_newdmt_basic() {
let config = NewdmtConfig {
nd: 50,
dmvisc: 0.0,
zeta0: 0.0,
zeta1: 0.0,
fractv: 0.0,
nconit: 0,
ipring: 0,
};
let mut state = create_test_state(config.nd);
let mut prs_aux = NewdmtPrsAux::default();
let mut factrs = NewdmtFactrs::default();
newdmt(&config, &mut state, &mut prs_aux, &mut factrs);
// 验证基本属性
assert!(state.dm[0] > 0.0, "dm[0] should be positive");
assert!(state.tauros[0] >= 0.0, "tauros[0] should be non-negative");
// 验证 dm 是单调递增的
for i in 1..config.nd {
assert!(
state.dm[i] >= state.dm[i - 1],
"dm should be monotonically increasing at i={}",
i
);
}
// 验证 theta = 1.0(无粘性时)
for i in 0..config.nd {
assert!(
(state.theta[i] - 1.0).abs() < 1e-10,
"theta[{}] should be 1.0 when dmvisc=0",
i
);
}
}
#[test]
fn test_newdmt_with_viscosity() {
let config = NewdmtConfig {
nd: 50,
dmvisc: 0.5,
zeta0: 0.5,
zeta1: 1.0,
fractv: 0.1,
nconit: 0,
ipring: 0,
};
let mut state = create_test_state(config.nd);
let mut prs_aux = NewdmtPrsAux::default();
let mut factrs = NewdmtFactrs::default();
newdmt(&config, &mut state, &mut prs_aux, &mut factrs);
// 验证 theta 在有效范围内
for i in 0..config.nd {
assert!(
state.theta[i] >= 0.0,
"theta[{}] = {} is negative",
i,
state.theta[i]
);
assert!(
state.theta[i] <= 2.0,
"theta[{}] = {} is too large",
i,
state.theta[i]
);
}
// 验证 vsnd2 已计算
for i in 0..config.nd {
if state.dens[i] > 0.0 {
assert!(
prs_aux.vsnd2[i] > 0.0,
"vsnd2[{}] should be positive",
i
);
}
}
}
#[test]
fn test_newdmt_tauros_consistency() {
let config = NewdmtConfig {
nd: 50,
dmvisc: 0.0,
zeta0: 0.0,
zeta1: 0.0,
fractv: 0.0,
nconit: 0,
ipring: 0,
};
let mut state = create_test_state(config.nd);
let mut prs_aux = NewdmtPrsAux::default();
let mut factrs = NewdmtFactrs::default();
newdmt(&config, &mut state, &mut prs_aux, &mut factrs);
// 验证 tauros 是单调递增的
for i in 1..config.nd {
assert!(
state.tauros[i] >= state.tauros[i - 1],
"tauros should be monotonically increasing at i={}",
i
);
}
// 验证 tauthe 是单调递增的
for i in 1..config.nd {
assert!(
state.tauthe[i] >= state.tauthe[i - 1],
"tauthe should be monotonically increasing at i={}",
i
);
}
}
}