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

657 lines
20 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 `eldens.f`。
//!
//! 功能:
//! - 计算电子密度和总氢数密度
//! - 使用 Newton-Raphson 方法求解 Saha 方程组
//! - 计算电荷守恒和粒子守恒
//! - 计算内能和熵
use crate::tlusty::math::lineqs;
use crate::tlusty::math::{moleq_pure, MoleqParams, MoleculeEqData};
use crate::tlusty::math::{mpartf, MpartfResult};
use crate::tlusty::math::{state_pure, StateParams};
use crate::tlusty::math::{entene, EnteneParams, EnteneOutput};
use crate::tlusty::state::constants::{BOLK, HMASS, UN, TWO, HALF};
/// ELDENS 配置参数
#[derive(Debug, Clone)]
pub struct EldensConfig {
/// 是否包含分子 (ifmol)
pub ifmol: i32,
/// 分子温度上限 (tmolim)
pub tmolim: f64,
/// ioptab 标志
pub ioptab: i32,
/// 氢原子索引 (iath)
pub iath: usize,
/// 参考原子索引 (iatref)
pub iatref: usize,
/// 是否处理 H- (ihm)
pub ihm: i32,
/// 是否处理 H2 (ih2)
pub ih2: i32,
/// 是否处理 H2+ (ih2p)
pub ih2p: i32,
/// 氢配分函数 (pfhyd)
pub pfhyd: f64,
}
impl Default for EldensConfig {
fn default() -> Self {
Self {
ifmol: 0,
tmolim: 1e10,
ioptab: 0,
iath: 1,
iatref: 1,
ihm: 1,
ih2: 1,
ih2p: 1,
pfhyd: 2.0,
}
}
}
/// ELDENS 输入参数
pub struct EldensParams<'a> {
/// 深度点索引 (1-indexed)
pub id: usize,
/// 温度 [K]
pub t: f64,
/// 总粒子数密度 [cm⁻³]
pub an: f64,
/// 总氢丰度因子 (ytot)
pub ytot: f64,
/// 参考原子电荷 (qref)
pub qref: f64,
/// 参考原子电荷导数 (dqnr)
pub dqnr: f64,
/// 平均分子量 (wmy)
pub wmy: f64,
/// 配置
pub config: EldensConfig,
/// STATE 函数所需的原子数据(简化)
pub state_params: Option<StateParams<'a>>,
/// 分子数据(可选)
pub molecule_data: Option<&'a MoleculeEqData>,
/// ANATO 计算所需的原子数密度数据
pub anato_data: Option<AnatoData>,
}
/// ANATO 计算所需的输入数据(对应 Fortran lines 245-256
#[derive(Debug, Clone)]
pub struct AnatoData {
/// 氢基态能级索引 n0hn (>0 表示存在)
pub n0hn: i32,
/// 氦基态能级索引 n0a(iathe) (>0 表示存在)
pub n0a_iathe: i32,
/// 氦是否为显式元素 iathe (>0 表示是)
pub iathe: i32,
/// popul(n0hn, id) - 氢基态粒子数(如果 n0hn > 0
pub popul_h: f64,
/// popul(n0a_iathe, id) - 氦基态粒子数(如果 iathe > 0
pub popul_he: f64,
/// dens(id) / wmm(id) / ytot(id) - LTE 估计
pub lte_estimate: f64,
/// abndd(2, id) - 氦丰度
pub abndd_he: f64,
}
/// ELDENS 输出结果
#[derive(Debug, Clone)]
pub struct EldensOutput {
/// 电子密度 [cm⁻³]
pub ane: f64,
/// 质子数密度 [cm⁻³]
pub anp: f64,
/// 总氢数密度 [cm⁻³]
pub ahtot: f64,
/// H2 分子相对数量
pub ahmol: f64,
/// H- 数密度 [cm⁻³]
pub anhm: f64,
/// 内能 [erg]
pub energ: f64,
/// 熵 [erg/K]
pub entt: f64,
/// 平均分子量
pub wm: f64,
/// 质量密度 [g/cm³]
pub rhoter: f64,
/// 电子密度与总粒子数密度之比
pub anerel: f64,
/// ANATO(1,ID) - 氢的原子数密度(可选,仅当 ifmol<=0 或 t>=tmolim
pub anato_h: Option<f64>,
/// ANATO(2,ID) - 氦的原子数密度(可选,仅当 ifmol<=0 或 t>=tmolim
pub anato_he: Option<f64>,
/// WMM 更新值: rhoter/(an-ane),当 ipri>0 时调用者应写入 wmm(id)
pub wmm_update: f64,
}
/// 计算电子密度(纯计算函数)。
///
/// # 参数
/// * `params` - 输入参数
/// * `ipri` - 是否更新全局状态(>0 表示更新)
///
/// # 返回值
/// 包含电子密度、内能、熵等的输出结构体
pub fn eldens_pure(params: &EldensParams, ipri: i32) -> EldensOutput {
// 检查 ioptab 标志
if params.config.ioptab < -1 {
return EldensOutput {
ane: 0.0,
anp: 0.0,
ahtot: 0.0,
ahmol: 0.0,
anhm: 0.0,
energ: 0.0,
entt: 0.0,
wm: 0.0,
rhoter: 0.0,
anerel: 0.0,
anato_h: None,
anato_he: None,
wmm_update: 0.0,
};
}
let t = params.t;
let an = params.an;
// 初始电子密度比例估计
let mut anerel = if t < 4000.0 {
1e-6
} else if t < 5000.0 {
1e-5
} else if t < 5500.0 {
1e-4
} else if t < 6000.0 {
1e-3
} else if t < 7000.0 {
0.01
} else if t < 8000.0 {
0.1
} else if t < 9000.0 {
0.4
} else {
0.5
};
// 如果包含分子且温度低于分子温度上限,调用 MOLEQ
if params.config.ifmol > 0 && t < params.config.tmolim {
let aein = an * anerel;
// 调用 moleq_pure 计算分子平衡
let default_mol_data: MoleculeEqData = Default::default();
let moleq_params = MoleqParams {
id: params.id,
tt: t,
an,
aein,
abundances: &[1.0], // 简化
ionization_energies: &[13.6],
ionization_energies2: &[0.0],
atomic_masses: &[1.0],
nelemx: &[],
nmetal: 0,
molecule_data: params.molecule_data.as_ref()
.map(|d| d as &MoleculeEqData)
.unwrap_or(&default_mol_data),
heh: 0.0,
ipri: 0,
ifmol: params.config.ifmol,
moltab: 0,
};
let moleq_output = moleq_pure(&moleq_params);
let ane = moleq_output.ane;
anerel = ane / an;
return EldensOutput {
ane,
anp: 0.0,
ahtot: an / params.ytot,
ahmol: moleq_output.anhm,
anhm: moleq_output.anhm,
energ: moleq_output.energ,
entt: moleq_output.entt,
wm: moleq_output.wm,
rhoter: params.wmy * (an / params.ytot) * HMASS,
anerel,
anato_h: None,
anato_he: None,
wmm_update: 0.0,
};
}
let tk = BOLK * t;
let thet = 5040.4 / t;
// 初始化系数
let mut qmi = 0.0;
let mut qp = 0.0;
let mut q2 = 0.0;
let mut uh2 = 1.0;
let mut duh2 = 0.0;
// 低温时的分子系数
if t <= 9000.0 {
// H- 复合系数
qmi = 1.0353e-16 / t / t.sqrt() * (8762.9 / t).exp();
// H2 解离系数
qp = tk * ((-11.206998 + thet * (2.7942767 + thet * (0.079196803 - 0.024790744 * thet))) * 2.30258509299405).exp();
// H 配分函数
let MpartfResult { u: uh, .. } = mpartf(1, 1, 0, t);
let uh = uh.max(TWO);
// H2 配分函数
let MpartfResult { u: uh2_val, dulog: duh2_val } = mpartf(0, 0, 2, t);
uh2 = uh2_val;
duh2 = duh2_val;
// H2+ 电离系数
q2 = 1.47e-20 / (t * t.sqrt()) * uh2 / uh / uh * (51951.8 / t).exp();
}
let tkln15 = (BOLK * t).ln() * 1.5;
// H 电离系数
let qh0 = ((15.38287 + 1.5 * t.log10() - 13.595 * thet) * 2.30258509299405).exp() * TWO;
let qh = qh0 / params.config.pfhyd;
// 初始值
let mut ane = an * anerel;
let mut ah = 0.0;
let mut anh = 0.0;
let mut q = 0.0;
let mut dqn = 0.0;
// Newton-Raphson 迭代
for it in 1..=10 {
// 调用 STATE 计算总电荷
// 注意STATE 需要当前的电子密度 ane而不是初始估计值
if let Some(ref state_params) = params.state_params {
// 创建新的 StateParams使用当前迭代的 ane 值
let updated_params = StateParams {
mode: state_params.mode,
id: state_params.id,
t: state_params.t,
ane, // 使用当前迭代的电子密度
natoms: state_params.natoms,
hpop: state_params.hpop,
dens: state_params.dens,
wmm: state_params.wmm,
ytot: state_params.ytot,
abndd: state_params.abndd,
ioniz: state_params.ioniz,
irefa: state_params.irefa,
lgr: state_params.lgr,
ifoppf: state_params.ifoppf,
lrm: state_params.lrm,
};
let state_output = state_pure(&updated_params);
q = state_output.q;
dqn = state_output.dqn;
}
// 判断氢是否为参考原子
if params.config.iatref == params.config.iath || params.config.ioptab >= -1 {
// 氢是参考原子
let g2 = qh / ane;
let g3 = qmi * ane;
let a_val = UN + g2 + g3;
let d_val = g2 - g3;
// 第一次迭代的初始估计
if it == 1 {
if t > 9000.0 {
let f1 = UN / a_val;
let fe = d_val / a_val + q;
ah = ane / fe;
anh = ah * f1;
if params.id == 1 {
eprintln!("DEBUG ELDENS init T>9000: f1={:.6e}, fe={:.6e}, q={:.6e}, ah={:.6e}, anh={:.6e}",
f1, fe, q, ah, anh);
}
} else if t > 4000.0 {
let e_val = g2 * qp / q2;
let b_val = TWO * (UN + e_val);
let gg = ane * q2;
let c1 = b_val * (gg * b_val + a_val * d_val) - e_val * a_val * a_val;
let c2 = a_val * (TWO * e_val + b_val * q) - d_val * b_val;
let c3 = -e_val - b_val * q;
let disc = (c2 * c2 - 4.0 * c1 * c3).sqrt();
let f1 = (disc - c2) * HALF / c1;
let fe = f1 * d_val + e_val * (UN - a_val * f1) / b_val + q;
ah = ane / fe;
anh = ah * f1;
if params.id == 1 {
eprintln!("DEBUG ELDENS init 4000<T<9000: ah={:.6e}, anh={:.6e}", ah, anh);
}
} else {
// 低温情况
let c1 = q2 * (TWO * params.ytot - UN);
let c2 = params.ytot;
let c3 = -an;
let disc = (c2 * c2 - 4.0 * c1 * c3).sqrt();
anh = (disc - c2) * HALF / c1;
ah = anh * (UN + TWO * anh * q2);
let c1 = UN + qmi * anh;
let c2 = -q * ah;
let c3 = -qh * anh;
let disc = (c2 * c2 - 4.0 * c1 * c3).sqrt();
ane = (disc - c2) * HALF / c1;
}
}
let ae = anh / ane;
let gg = ae * qp;
let e_val = anh * q2;
let b_val = anh * qmi;
if params.id == 1 && it <= 2 {
eprintln!("DEBUG ELDENS coeffs iter {}: ae={:.6e}, gg={:.6e}, e_val={:.6e}, b_val={:.6e}",
it, ae, gg, e_val, b_val);
eprintln!("DEBUG ELDENS params iter {}: d_val={:.6e}, g2={:.6e}, a_val={:.6e}",
it, d_val, g2, a_val);
}
// 构建线性方程组
let mut r = [[0.0; 3]; 3];
let mut s = [0.0; 3];
if params.config.ifmol == 0 || t >= params.config.tmolim {
r[0][0] = params.ytot;
r[0][1] = 0.0;
r[0][2] = UN;
s[0] = an - ane - params.ytot * ah;
if params.id == 1 && it <= 2 {
eprintln!("DEBUG ELDENS rhs0: an={:.6e}, ane={:.6e}, ytot={:.6e}, ah={:.6e}, s0={:.6e}",
an, ane, params.ytot, ah, s[0]);
}
} else {
r[0][0] = params.ytot - UN;
r[0][1] = a_val + e_val + gg;
r[0][2] = UN;
s[0] = an - ane - anh * (a_val + e_val + gg) - (params.ytot - UN) * ah;
}
r[1][0] = -q;
r[1][1] = -d_val - TWO * gg;
r[1][2] = UN + b_val + ae * (g2 + gg) - dqn * ah;
r[2][0] = -UN;
r[2][1] = a_val + 4.0 * (e_val + gg);
r[2][2] = b_val - ae * (g2 + TWO * gg);
s[1] = anh * (d_val + gg) + q * ah - ane;
s[2] = ah - anh * (a_val + TWO * (e_val + gg));
if params.id == 1 && it <= 2 {
eprintln!("DEBUG ELDENS rhs12: anh={:.6e}, d_val={:.6e}, gg={:.6e}, q={:.6e}, ah={:.6e}, ane={:.6e}",
anh, d_val, gg, q, ah, ane);
eprintln!("DEBUG ELDENS rhs12: s1={:.6e}, s2={:.6e}", s[1], s[2]);
}
// Debug: print matrix
if params.id == 1 && it <= 2 {
eprintln!("DEBUG ELDENS matrix iter {}: R=[{:.15e},{:.15e},{:.15e}; {:.15e},{:.15e},{:.15e}; {:.15e},{:.15e},{:.15e}]",
it, r[0][0], r[0][1], r[0][2], r[1][0], r[1][1], r[1][2], r[2][0], r[2][1], r[2][2]);
eprintln!("DEBUG ELDENS rhs iter {}: S=[{:.15e},{:.15e},{:.15e}]",
it, s[0], s[1], s[2]);
}
// 求解线性方程组
// 注意LINEQS 需要 Fortran 列优先存储
let mut r_work = vec![0.0; 9];
for j in 0..3 {
for i in 0..3 {
r_work[i + j * 3] = r[i][j]; // 列优先存储
}
}
let mut s_work = s.to_vec();
let mut p = [0.0; 3];
lineqs(&mut r_work, &mut s_work, &mut p, 3);
// 验证解是否正确
if params.id == 1 && it <= 2 {
let check0 = r[0][0]*p[0] + r[0][1]*p[1] + r[0][2]*p[2];
let check1 = r[1][0]*p[0] + r[1][1]*p[1] + r[1][2]*p[2];
let check2 = r[2][0]*p[0] + r[2][1]*p[1] + r[2][2]*p[2];
eprintln!("DEBUG ELDENS verify: R*p=[{:.6e}, {:.6e}, {:.6e}], S=[{:.6e}, {:.6e}, {:.6e}]",
check0, check1, check2, s[0], s[1], s[2]);
}
if params.id == 1 {
eprintln!("DEBUG ELDENS lineqs: p=[{:.6e}, {:.6e}, {:.6e}]", p[0], p[1], p[2]);
}
// 更新值
ah = ah + p[0];
anh = anh + p[1];
let delne = p[2];
ane = ane + delne;
// 收敛检查(与 Fortran 一致)
// 注意Fortran 只检查 ANE <= 0不检查 ANH
if ane <= 0.0 {
ane = 1e-6 * an;
}
if (delne / ane).abs() <= 1e-3 {
break;
}
} else {
// 氢不是参考原子
if it == 1 {
ane = an * HALF;
ah = ane / params.ytot;
}
let mut r = [[0.0; 2]; 2];
let mut s = [0.0; 2];
let mut p = [0.0; 2];
r[0][0] = params.ytot;
r[0][1] = UN;
r[1][0] = -q - params.qref;
r[1][1] = UN - (dqn + params.dqnr) * ah;
s[0] = an - ane - params.ytot * ah;
s[1] = (q + params.qref) * ah - ane;
// 求解 2x2 系统(列优先存储,与 LINEQS 一致)
let mut r_work = vec![0.0; 4];
for j in 0..2 {
for i in 0..2 {
r_work[i + j * 2] = r[i][j]; // 列优先存储
}
}
let mut s_work = s.to_vec();
lineqs(&mut r_work, &mut s_work, &mut p, 2);
ah = ah + p[0];
let delne = p[1];
ane = ane + delne;
// 收敛检查
if (delne / ane).abs() <= 1e-3 {
break;
}
}
}
if params.id == 1 {
eprintln!("DEBUG ELDENS after loop: ane={:.6e}, an={:.6e}", ane, an);
}
// 确保电子密度为正
if ane <= 0.0 {
ane = 1e-6 * an;
}
// 计算最终值
anerel = ane / an;
let ahtot = ah;
let ahmol = anh * anh * q2;
// 计算质子密度 (从 Saha 方程)
// Saha: n_p * n_e / n_H = qh
// 所以: n_p = n_H * qh / n_e
let anp_raw = anh / ane * qh;
// 确保物理一致性:
// 1. 质子密度不能超过总氢密度
// 2. 对于氢主导的气体, ane ≈ anp (电荷守恒)
// 3. 如果 anp > ahtot, 说明计算有问题, 使用 ane 作为估计
let anp = if anp_raw > ahtot || anp_raw < 0.0 {
// Newton-Raphson 可能未收敛, 使用电荷守恒估计
// 对于氢主导气体: ane ≈ anp (电子来自氢电离)
ane.min(ahtot).max(0.0)
} else {
anp_raw
};
let anhm = anh * ane * qmi;
let rhoter = params.wmy * ah * HMASS;
// 调用 ENTENE 计算内能和熵
// f2r_depends: ENTENE, MOLEQ
let rr_dummy = [[0.0f64; 2]; 30];
let enev_dummy = [[0.0f64; 2]; 30];
let amas_dummy = [0.0f64; 30];
let entene_params = EnteneParams {
t,
ah,
anh,
anpr: anp,
ane,
rr: &rr_dummy,
enev: &enev_dummy,
amas: &amas_dummy,
natoms: 1,
bolk: BOLK,
un: UN,
};
let entene_output = entene(&entene_params);
let mut energ = entene_output.energ;
let mut entt = entene_output.entrop;
// H2 的能量和熵修正
if t < 9000.0 && ahmol > 0.0 && uh2 > 0.0 {
energ = energ + (duh2 - 51951.8 / t) * tk * ahmol;
entt = entt + ahmol * (tkln15 - ahmol.ln() + uh2.ln() + 1.0487 + 103.973) * BOLK;
}
let wm = rhoter / an / HMASS;
// ANATO 计算 (Fortran lines 245-256)
// if(ifmol.le.0.or.t.ge.tmolim) then
let (anato_h, anato_he) = if params.config.ifmol <= 0 || t >= params.config.tmolim {
if let Some(ref ad) = params.anato_data {
// anato(1,id)
let ah = if ad.n0hn > 0 {
ad.popul_h
} else {
ad.lte_estimate
};
// anato(2,id)
let ahe = if ad.iathe > 0 {
ad.popul_he
} else {
ad.lte_estimate * ad.abndd_he
};
(Some(ah), Some(ahe))
} else {
(None, None)
}
} else {
(None, None)
};
// WMM 更新值: wmm(id) = dens(id)/(an-ane) = rhoter/(an-ane)
let wmm_update = if an > ane { rhoter / (an - ane) } else { 0.0 };
if params.id == 1 {
eprintln!("DEBUG ELDENS return: id={}, ane={:.6e}, anp={:.6e}, ahtot={:.6e}, anerel={:.6e}",
params.id, ane, anp, ahtot, anerel);
}
EldensOutput {
ane,
anp,
ahtot,
ahmol,
anhm,
energ,
entt,
wm,
rhoter,
anerel,
anato_h,
anato_he,
wmm_update,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_eldens_basic() {
let config = EldensConfig::default();
let params = EldensParams {
id: 1,
t: 10000.0,
an: 1e15,
ytot: 1.0,
qref: 0.0,
dqnr: 0.0,
wmy: 1.0,
config,
state_params: None,
molecule_data: None,
anato_data: None,
};
let output = eldens_pure(&params, 0);
// 验证输出
assert!(output.ane > 0.0);
assert!(output.anerel.is_finite());
}
#[test]
fn test_eldens_low_temp() {
let config = EldensConfig {
ifmol: 0,
tmolim: 5000.0,
..Default::default()
};
let params = EldensParams {
id: 1,
t: 6000.0,
an: 1e15,
ytot: 1.0,
qref: 0.0,
dqnr: 0.0,
wmy: 1.0,
config,
state_params: None,
molecule_data: None,
anato_data: None,
};
let output = eldens_pure(&params, 0);
// 低温时电子密度应该较低
assert!(output.anerel < 0.5);
}
}