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

837 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.

//! 流体静力平衡方程求解器。
//!
//! 重构自 TLUSTY `hesolv.f`。
//!
//! 功能:
//! - 求解流体静力平衡方程和 z-m 关系的耦合系统
//! - 使用 Newton-Raphson 迭代方法
//! - 给定声速(总压力/密度),求解压力和深度分布
use crate::tlusty::math::erfcx;
use crate::tlusty::math::matinv;
use crate::tlusty::math::{rhonen_pure, RhonenParams};
use crate::tlusty::math::{steqeq_pure, SteqeqConfig, SteqeqParams};
use crate::tlusty::math::wnstor;
use crate::tlusty::state::constants::{HALF, MDEPTH, TWO, UN};
/// HESOLV 辅助参数PRSAUX COMMON 块)
#[derive(Debug, Clone)]
pub struct HesolvAux {
/// 声速平方 [深度]
pub vsnd2: Vec<f64>,
/// 表面气压标高
pub hg1: f64,
/// 辐射气压标高(未使用但保留)
#[allow(dead_code)]
pub hr1: f64,
/// 辐射/气压标高比
pub rr1: f64,
}
impl Default for HesolvAux {
fn default() -> Self {
Self {
vsnd2: vec![0.0; MDEPTH],
hg1: 0.0,
hr1: 0.0,
rr1: 0.0,
}
}
}
/// HESOLV 模型状态参数
#[derive(Debug, Clone)]
pub struct HesolvModelState {
/// 深度点数
pub nd: usize,
/// 柱质量密度 [深度] (g/cm²)
pub dm: Vec<f64>,
/// 深度变量 [深度] (cm)
pub zd: Vec<f64>,
/// 密度 [深度] (cm⁻³)
pub dens: Vec<f64>,
/// 总压力 [深度]
pub ptotal: Vec<f64>,
/// 气体压力 [深度]
pub pgs: Vec<f64>,
/// 温度 [深度] (K)
pub temp: Vec<f64>,
/// 电子密度 [深度] (cm⁻³)
pub elec: Vec<f64>,
/// 平均分子量 [深度]
pub wmm: Vec<f64>,
/// 底部深度
pub znd: f64,
/// 表面重力
pub qgrav: f64,
}
/// HESOLV 原子/能级参数
#[derive(Debug, Clone)]
pub struct HesolvAtomicParams<'a> {
/// 能级粒子数 [能级]
pub popul: &'a [f64],
/// Saha-Boltzmann 因子 [能级]
pub sbf: &'a [f64],
/// 占据概率 [能级]
pub wop: &'a [f64],
/// SBPSI 因子 [能级]
pub sbpsi: &'a [f64],
/// 能级索引映射 [能级]
pub iifor: &'a [i32],
/// 参考能级索引 [能级]
pub iltref: &'a [i32],
/// 模型标志 [能级]
pub imodl: &'a [i32],
/// 原子索引 [能级]
pub iatm: &'a [i32],
/// 固定标志 [原子]
pub iifix: &'a [i32],
/// 零粒子数标志 [能级]
pub ipzero: &'a [i32],
/// 能级范围起始 [原子]
pub n0a: &'a [i32],
/// 能级范围结束 [原子]
pub nka: &'a [i32],
/// 参考起始能级 [原子]
pub nrefs: &'a [i32],
/// l-量子数链接 [能级]
pub ilk: &'a [i32],
/// l-范围起始 [能级]
pub nfirst: &'a [i32],
/// l-范围结束 [能级]
pub nlast: &'a [i32],
/// 下一电离态能级 [离子]
pub nnext: &'a [i32],
/// 原子丰度 [原子]
pub abund: &'a [f64],
/// 能级量子数 [能级]
pub nquant: &'a [i32],
/// 原子序数 [能级]
pub iz: &'a [i32],
/// 能级权重标志 [能级]
pub ifwop: &'a [i32],
/// XI2 系数 [能级]
pub xi2: &'a [f64],
/// 速率矩阵 A
pub matrix_a: &'a [Vec<f64>],
/// 右端向量 B
pub vector_b: &'a [f64],
/// 粒子数解
pub pop0: &'a [f64],
/// 迭代控制
pub kant: &'a [i32],
}
/// HESOLV 配置参数
#[derive(Debug, Clone)]
pub struct HesolvConfig {
/// 打印标志 (ipring)
pub ipring: i32,
/// H2+ 标志 (ih2p)
pub ih2p: i32,
/// 原子数
pub natom: usize,
/// 能级数
pub nlevel: usize,
/// 离子数
pub nion: usize,
/// 总丰度因子
pub ytot: f64,
/// eldens 参数
pub eldens_qref: f64,
pub eldens_dqnr: f64,
pub eldens_wmy: f64,
/// steqeq 配置
pub steqeq_config: SteqeqConfig,
/// LTE 标志
pub lte: bool,
/// io_ptab 标志
pub io_ptab: i32,
}
impl Default for HesolvConfig {
fn default() -> Self {
Self {
ipring: 0,
ih2p: -1,
natom: 1,
nlevel: 1,
nion: 1,
ytot: 1.0,
eldens_qref: 1.0,
eldens_dqnr: 0.0,
eldens_wmy: 1.0,
steqeq_config: SteqeqConfig::default(),
lte: false,
io_ptab: 0,
}
}
}
/// HESOLV 输入参数
#[derive(Debug, Clone)]
pub struct HesolvParams<'a> {
/// 辅助参数
pub aux: HesolvAux,
/// 模型状态
pub model: HesolvModelState,
/// 原子参数
pub atomic: HesolvAtomicParams<'a>,
/// 配置
pub config: HesolvConfig,
}
/// HESOLV 输出结果
#[derive(Debug, Clone)]
pub struct HesolvOutput {
/// 更新后的压力 [深度]
pub ptotal: Vec<f64>,
/// 更新后的气体压力 [深度]
pub pgs: Vec<f64>,
/// 更新后的深度 [深度]
pub zd: Vec<f64>,
/// 更新后的密度 [深度]
pub dens: Vec<f64>,
/// 更新后的电子密度 [深度]
pub elec: Vec<f64>,
/// 更新后的能级粒子数 [能级][深度]
pub popul: Vec<Vec<f64>>,
/// 迭代次数
pub iterations: i32,
/// 最大相对变化
pub max_change: f64,
/// 是否收敛
pub converged: bool,
}
/// 求解流体静力平衡方程。
///
/// 使用 Newton-Raphson 方法求解耦合的流体静力平衡方程和 z-m 关系。
///
/// # 参数
/// * `params` - 输入参数
///
/// # 返回值
/// 包含更新后的压力、密度、深度等的结果结构体
pub fn hesolv_pure(params: &HesolvParams) -> HesolvOutput {
let nd = params.model.nd;
let mut p = vec![0.0; nd];
let mut vsnd2 = params.aux.vsnd2.clone();
let mut zd = params.model.zd.clone();
let mut dens = params.model.dens.clone();
let mut ptotal = params.model.ptotal.clone();
let mut pgs = params.model.pgs.clone();
let mut elec = params.model.elec.clone();
let mut popul = vec![vec![0.0; nd]; params.config.nlevel];
// 初始化粒子数(仅当有数据时)
let popul_len = params.atomic.popul.len();
for i in 0..params.config.nlevel {
for j in 0..nd {
let idx = i * nd + j;
if idx < popul_len {
popul[i][j] = params.atomic.popul[idx];
}
}
}
// Newton-Raphson 收敛阈值
const ERROR: f64 = 1e-4;
const MAX_ITER: i32 = 10;
// 初始化压力和声速
for id in 0..nd {
p[id] = ptotal[id];
vsnd2[id] = p[id] / dens[id];
}
// 工作数组
let mut b = vec![0.0; 4]; // 2x2 矩阵
let mut c = vec![0.0; 4]; // 2x2 矩阵
let mut vl = vec![0.0; 2];
let mut d = vec![vec![vec![0.0; nd]; 2]; 2]; // 2x2xND
let mut anu = vec![vec![0.0; nd]; 2]; // 2xND
let mut iterh = 0;
// chmaxx 和 converged 会在循环中被赋值,初始值仅用于类型标注
#[allow(unused_assignments)]
let mut chmaxx = 0.0_f64;
#[allow(unused_assignments)]
let mut converged = false;
// Newton-Raphson 迭代
'newton: loop {
iterh += 1;
// =====================
// 前向消除
// =====================
// 上边界条件 (ID = 1)
let id = 0;
let x = zd[id] / params.aux.hg1 - params.aux.rr1;
// 计算 F1 = 1.772453851 * exp(x²) * erfc(x)
let f1 = if x < 3.0 {
let x_clamped = if x < 0.0 { 0.0 } else { x };
1.772453851 * (x_clamped * x_clamped).exp() * erfcx(x_clamped)
} else {
(UN - HALF / x / x) / x
};
let bet0 = HALF / dens[id] / p[id];
let betp = HALF / dens[id + 1] / p[id + 1];
let gama = UN / (params.model.dm[id + 1] - params.model.dm[id]);
// 矩阵 B (2x2, 按行存储)
b[0] = f1;
b[1] = TWO * (x * f1 - UN) * p[0] / params.aux.hg1;
b[2] = bet0;
b[3] = gama;
// 矩阵 C (2x2)
c[0] = 0.0;
c[1] = 0.0;
c[2] = -betp;
c[3] = gama;
// 向量 VL
vl[0] = params.model.dm[id] * 2.0 * vsnd2[id] / params.aux.hg1 - p[id] * f1;
vl[1] = bet0 * p[id] + betp * p[id + 1] - gama * (zd[id] - zd[id + 1]);
anu[0][id] = 0.0;
anu[1][id] = 0.0;
// 矩阵求逆
matinv(&mut b, 2);
// 计算 D 和 ANU
for i in 0..2 {
for j in 0..2 {
let mut s = 0.0;
for k in 0..2 {
s += b[i * 2 + k] * c[k * 2 + j];
}
d[i][j][id] = s;
anu[i][id] += b[i * 2 + j] * vl[j];
}
}
// 中间深度点 1 < ID < ND
let mut betp_current = betp;
for id in 1..nd - 1 {
let bet0_prev = betp_current;
betp_current = HALF / dens[id + 1] / p[id + 1];
let gama_new = UN / (params.model.dm[id + 1] - params.model.dm[id]);
let dmd = HALF * (params.model.dm[id + 1] - params.model.dm[id - 1]);
let aa = UN / (params.model.dm[id] - params.model.dm[id - 1]) / dmd;
let cc = gama_new / dmd;
let bb = aa + cc;
let bq = params.model.qgrav / p[id] / dens[id];
b[0] = bb + bq - aa * d[0][0][id - 1];
b[1] = -aa * d[0][1][id - 1];
b[2] = bet0_prev;
b[3] = gama_new;
c[0] = cc;
c[1] = 0.0;
c[2] = -betp_current;
c[3] = gama_new;
vl[0] = aa * p[id - 1] + cc * p[id + 1] - (bb - bq) * p[id] + aa * anu[0][id - 1];
vl[1] = bet0_prev * p[id] + betp_current * p[id + 1] - gama_new * (zd[id] - zd[id + 1]);
matinv(&mut b, 2);
anu[0][id] = 0.0;
anu[1][id] = 0.0;
for i in 0..2 {
for j in 0..2 {
let mut s = 0.0;
for k in 0..2 {
s += b[i * 2 + k] * c[k * 2 + j];
}
d[i][j][id] = s;
anu[i][id] += b[i * 2 + j] * vl[j];
}
}
}
// 下边界条件 (ID = ND)
let id = nd - 1;
let aa = TWO / (params.model.dm[id] - params.model.dm[id - 1]).powi(2);
let bq = params.model.qgrav / p[id] / dens[id];
b[0] = aa + bq - aa * d[0][0][id - 1];
b[1] = -aa * d[0][1][id - 1];
b[2] = 0.0;
b[3] = UN;
c[0] = 0.0;
c[1] = 0.0;
c[2] = 0.0;
c[3] = 0.0;
vl[0] = params.model.qgrav / dens[id] + aa * (p[id - 1] - p[id] + anu[0][id - 1]);
vl[1] = 0.0;
matinv(&mut b, 2);
anu[0][id] = 0.0;
anu[1][id] = 0.0;
for i in 0..2 {
for j in 0..2 {
let mut s = 0.0;
for k in 0..2 {
s += b[i * 2 + k] * c[k * 2 + j];
}
d[i][j][id] = s;
anu[i][id] += b[i * 2 + j] * vl[j];
}
}
// ============
// 回代
// ============
p[id] = p[id] + anu[0][id];
zd[id] = params.model.znd;
chmaxx = (anu[0][id] / p[id]).abs();
// 从底部向上回代
for iid in 1..nd {
let id = nd - 1 - iid;
for i in 0..2 {
for j in 0..2 {
anu[i][id] = anu[i][id] + d[i][j][id] * anu[j][id + 1];
}
}
let ch1 = anu[0][id] / p[id];
let ch2 = anu[1][id] / zd[id];
if ch1.abs() > chmaxx {
chmaxx = ch1.abs();
}
if ch2.abs() > chmaxx {
chmaxx = ch2.abs();
}
// 限制变化幅度
let ch1_limited = if ch1 < -0.9 {
-0.9
} else if ch1 > 9.0 {
9.0
} else {
ch1
};
p[id] = p[id] * (UN + ch1_limited);
}
// 重新计算密度
for id in 0..nd {
dens[id] = p[id] / vsnd2[id];
}
// 新的深度值
zd[nd - 1] = params.model.znd;
for iid in 1..nd {
let id = nd - 1 - iid;
zd[id] = zd[id + 1]
+ HALF * (params.model.dm[id + 1] - params.model.dm[id])
* (UN / dens[id] + UN / dens[id + 1]);
}
// 收敛检查
if params.config.ipring >= 1 {
eprintln!("\n solution of hydrostatic eq. + z-m relation:iter = {:3} max.rel.chan. ={:.2e}",
iterh, chmaxx);
}
if chmaxx <= ERROR || iterh >= MAX_ITER {
converged = chmaxx <= ERROR;
break 'newton;
}
}
// 更新总压力和气体压力
for id in 0..nd {
let x = pgs[id] / ptotal[id];
ptotal[id] = p[id];
pgs[id] = x * p[id];
}
// 重新计算粒子数(如果需要)
if params.config.ih2p >= 0 {
// 计算电子密度
let mut anerel = if nd > 0 {
elec[0] / (dens[0] / params.model.wmm[0] + elec[0])
} else {
0.0
};
for id in 0..nd {
// 调用 RHONEN 计算电子密度
let rhonen_params = RhonenParams {
id: id + 1, // 1-indexed
t: params.model.temp[id],
rho: dens[id],
wmm: &params.model.wmm,
anerel,
eldens_config: Default::default(),
eldens_ytot: params.config.ytot,
eldens_qref: params.config.eldens_qref,
eldens_dqnr: params.config.eldens_dqnr,
eldens_wmy: params.config.eldens_wmy,
};
let rhonen_output = rhonen_pure(&rhonen_params);
elec[id] = rhonen_output.ane;
anerel = rhonen_output.anerel;
// 调用 WNSTOR
let mut wnhint = vec![vec![0.0; nd]; 80]; // NLMX = 80
let mut wop_local = vec![vec![0.0; nd]; params.config.nlevel];
wnstor(
id,
&params.model.temp,
&elec,
params.atomic.xi2,
&mut wnhint,
&mut wop_local,
params.atomic.ifwop,
params.config.nlevel,
params.atomic.nquant,
params.atomic.iz,
params.config.io_ptab,
params.config.lte,
);
// 调用 STEQEQ 计算粒子数
let mut pop_local = vec![0.0; params.config.nlevel];
for i in 0..params.config.nlevel {
pop_local[i] = popul[i][id];
}
let steqeq_params = SteqeqParams {
id: id + 1, // 1-indexed
temp: params.model.temp[id],
elec: elec[id],
dens: dens[id],
wmm: params.model.wmm[id],
ytot: params.config.ytot,
abund: params.atomic.abund,
popul: &pop_local,
sbf: params.atomic.sbf,
wop: params.atomic.wop,
sbpsi: params.atomic.sbpsi,
iifor: params.atomic.iifor,
iltref: params.atomic.iltref,
imodl: params.atomic.imodl,
iatm: params.atomic.iatm,
iifix: params.atomic.iifix,
ipzero: params.atomic.ipzero,
n0a: params.atomic.n0a,
nka: params.atomic.nka,
nrefs: params.atomic.nrefs,
ilk: params.atomic.ilk,
nfirst: params.atomic.nfirst,
nlast: params.atomic.nlast,
nnext: params.atomic.nnext,
natom: params.config.natom,
nlevel: params.config.nlevel,
nion: params.config.nion,
matrix_a: params.atomic.matrix_a,
vector_b: params.atomic.vector_b,
pop0: params.atomic.pop0,
config: params.config.steqeq_config.clone(),
kant: params.atomic.kant,
};
let steqeq_output = steqeq_pure(&steqeq_params, 1);
// 更新粒子数
for i in 0..params.config.nlevel {
popul[i][id] = steqeq_output.pop1[i];
}
}
}
HesolvOutput {
ptotal,
pgs,
zd,
dens,
elec,
popul,
iterations: iterh,
max_change: chmaxx,
converged,
}
}
#[cfg(test)]
mod tests {
use super::*;
fn create_simple_model(nd: usize) -> HesolvModelState {
let mut dm = vec![0.0; nd];
let mut zd = vec![0.0; nd];
let mut dens = vec![0.0; nd];
let mut ptotal = vec![0.0; nd];
let mut pgs = vec![0.0; nd];
let mut temp = vec![0.0; nd];
let mut elec = vec![0.0; nd];
let mut wmm = vec![0.0; nd];
// 创建简单的指数分布
// zd[0] 是顶部表面zd[nd-1] 是底部
// zd 从顶部到底部增加
for i in 0..nd {
let depth_factor = 10.0_f64.powf(i as f64 / (nd - 1) as f64);
dm[i] = 1e-6 * depth_factor;
// zd 从顶部 (0) 到底部 (znd) 增加
zd[i] = 1e6 * (depth_factor / 10.0);
dens[i] = 1e-7 / depth_factor;
ptotal[i] = 1e3 * depth_factor;
pgs[i] = 0.9 * ptotal[i];
temp[i] = 10000.0;
elec[i] = 1e12 / depth_factor;
wmm[i] = 1.4e-24;
}
HesolvModelState {
nd,
dm,
zd,
dens,
ptotal,
pgs,
temp,
elec,
wmm,
znd: 1e6,
qgrav: 1e4,
}
}
#[test]
fn test_hesolv_basic() {
let nd = 5;
let model = create_simple_model(nd);
// 初始化声速平方
let mut vsnd2 = vec![0.0; nd];
for i in 0..nd {
vsnd2[i] = model.ptotal[i] / model.dens[i];
}
let aux = HesolvAux {
vsnd2,
hg1: 1e8,
hr1: 0.0,
rr1: 0.0,
};
// 创建空的原子参数
let atomic = HesolvAtomicParams {
popul: &[],
sbf: &[],
wop: &[],
sbpsi: &[],
iifor: &[],
iltref: &[],
imodl: &[],
iatm: &[],
iifix: &[],
ipzero: &[],
n0a: &[],
nka: &[],
nrefs: &[],
ilk: &[],
nfirst: &[],
nlast: &[],
nnext: &[],
abund: &[],
nquant: &[],
iz: &[],
ifwop: &[],
xi2: &[],
matrix_a: &[],
vector_b: &[],
pop0: &[],
kant: &[],
};
let config = HesolvConfig {
ih2p: -1, // 跳过粒子数重新计算
..Default::default()
};
let params = HesolvParams {
aux,
model,
atomic,
config,
};
let output = hesolv_pure(&params);
// 验证输出
assert_eq!(output.ptotal.len(), nd);
assert_eq!(output.zd.len(), nd);
assert_eq!(output.dens.len(), nd);
assert!(output.iterations > 0);
}
#[test]
fn test_hesolv_pressure_consistency() {
let nd = 10;
let model = create_simple_model(nd);
let mut vsnd2 = vec![0.0; nd];
for i in 0..nd {
vsnd2[i] = model.ptotal[i] / model.dens[i];
}
let aux = HesolvAux {
vsnd2,
hg1: 1e8,
hr1: 0.0,
rr1: 0.0,
};
let atomic = HesolvAtomicParams {
popul: &[],
sbf: &[],
wop: &[],
sbpsi: &[],
iifor: &[],
iltref: &[],
imodl: &[],
iatm: &[],
iifix: &[],
ipzero: &[],
n0a: &[],
nka: &[],
nrefs: &[],
ilk: &[],
nfirst: &[],
nlast: &[],
nnext: &[],
abund: &[],
nquant: &[],
iz: &[],
ifwop: &[],
xi2: &[],
matrix_a: &[],
vector_b: &[],
pop0: &[],
kant: &[],
};
let config = HesolvConfig {
ih2p: -1,
..Default::default()
};
let params = HesolvParams {
aux,
model,
atomic,
config,
};
let output = hesolv_pure(&params);
// 验证压力随深度增加
for i in 1..nd {
assert!(
output.ptotal[i] > output.ptotal[i - 1],
"Pressure should increase with depth at i={}",
i
);
}
// 验证深度值
// 注意zd 是几何深度底部nd-1应该大于顶部0
// 但经过 Newton-Raphson 迭代后zd 会根据新的密度重新计算
// 这里只验证 zd 是有限值
for (i, &z) in output.zd.iter().enumerate() {
assert!(z.is_finite(), "zd[{}] should be finite: {}", i, z);
}
}
#[test]
fn test_hesolv_density_positive() {
let nd = 5;
let model = create_simple_model(nd);
let mut vsnd2 = vec![0.0; nd];
for i in 0..nd {
vsnd2[i] = model.ptotal[i] / model.dens[i];
}
let aux = HesolvAux {
vsnd2,
hg1: 1e8,
hr1: 0.0,
rr1: 0.0,
};
let atomic = HesolvAtomicParams {
popul: &[],
sbf: &[],
wop: &[],
sbpsi: &[],
iifor: &[],
iltref: &[],
imodl: &[],
iatm: &[],
iifix: &[],
ipzero: &[],
n0a: &[],
nka: &[],
nrefs: &[],
ilk: &[],
nfirst: &[],
nlast: &[],
nnext: &[],
abund: &[],
nquant: &[],
iz: &[],
ifwop: &[],
xi2: &[],
matrix_a: &[],
vector_b: &[],
pop0: &[],
kant: &[],
};
let config = HesolvConfig {
ih2p: -1,
..Default::default()
};
let params = HesolvParams {
aux,
model,
atomic,
config,
};
let output = hesolv_pure(&params);
// 所有密度应为正
for (i, &d) in output.dens.iter().enumerate() {
assert!(d > 0.0, "Density at depth {} should be positive", i);
}
}
}