//! 流体静力平衡方程求解器。 //! //! 重构自 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, /// 表面气压标高 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, /// 深度变量 [深度] (cm) pub zd: Vec, /// 密度 [深度] (cm⁻³) pub dens: Vec, /// 总压力 [深度] pub ptotal: Vec, /// 气体压力 [深度] pub pgs: Vec, /// 温度 [深度] (K) pub temp: Vec, /// 电子密度 [深度] (cm⁻³) pub elec: Vec, /// 平均分子量 [深度] pub wmm: Vec, /// 底部深度 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], /// 右端向量 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, /// 更新后的气体压力 [深度] pub pgs: Vec, /// 更新后的深度 [深度] pub zd: Vec, /// 更新后的密度 [深度] pub dens: Vec, /// 更新后的电子密度 [深度] pub elec: Vec, /// 更新后的能级粒子数 [能级][深度] pub popul: Vec>, /// 迭代次数 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: ¶ms.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, ¶ms.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(¶ms); // 验证输出 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(¶ms); // 验证压力随深度增加 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(¶ms); // 所有密度应为正 for (i, &d) in output.dens.iter().enumerate() { assert!(d > 0.0, "Density at depth {} should be positive", i); } } }