Files
SpectraRust/src/io/ltegrd.rs
T
fmqandClaude Opus 4.6 a086e313cb feat: 添加更多重构模块 (第7批)
包含 IO 和 math 模块的实现:
- IO: initia, levcd, linset, ltegr, ltegrd, odfset, outpri, resolv, srtfrq, start, tabini, xenini
- Math: accel2, alisk1, alisk2, alist1, alist2, concor, conout, conref, contmd, contmp, coolrt, greyd, inilam, linsel, lucy, lymlin, matcon, matgen, moleq, newdm, newdmt, odf1, opacf0, opacf1, opacfa, opacfd, opacfl, opactr, opadd, opahst, pgset, princ, prnt, pzeval, quasim, radpre, radtot, rates1, ratsp1, rdata, rdatax, rechck, rhoeos, rhonen, rhsgen, rossop, rtecf1, rtecmc, rtecmu, rtecom, rtefr1, rteint, russel, rybchn, rybene, rybheq, rybsol, sgmer1, sigave, sigk, solve, solves, state, steqeq, temcor, temper, topbas, trmder, trmdrt

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-25 01:46:29 +08:00

694 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.
//! LTE-Grey 盘模型初始计算。
//!
//! 重构自 TLUSTY `ltegrd.f`。
//!
//! # 功能
//!
//! 计算初始的 LTE-Grey 盘模型,作为后续非 LTE 迭代的起点。
//! 这是盘模型(disk model)的计算,与大气模型(ltegr)不同。
use crate::math::zmrho;
use crate::state::constants::{HALF, MDEPTH, TWO, UN, SIG4P, SIGE, BOLK};
// ============================================================================
// 常量
// ============================================================================
/// 收敛容差
const ERRT: f64 = 1e-3;
/// 1/3
const THIRD: f64 = 1.0 / 3.0;
/// 4.0
const FOUR: f64 = 4.0;
// ============================================================================
// 配置结构体
// ============================================================================
/// LTEGRD 配置参数。
#[derive(Debug, Clone)]
pub struct LtegrdConfig {
/// Grey 模型深度点数 (NDGREY)
/// 0 = 使用 ND
pub ndgrey: i32,
/// 深度标尺模式 (IDGREY)
pub idgrey: i32,
/// 最大全局迭代次数 (ITGMX0)
pub itgmx0: i32,
/// 深度标尺重算次数 (NNEWD)
pub nnewd: i32,
/// 对流内部迭代次数 (NCONIT)
pub nconit: i32,
/// 诊断输出级别 (IPRING)
pub ipring: i32,
/// 混合长度参数 (HMIX0)
pub hmix0: f64,
/// 初始电离度估计 (DION0)
pub dion0: f64,
/// 初始 Rosseland 不透明度 (ABROS0)
pub abros0: f64,
/// 初始 Planck 平均不透明度 (ABPLA0)
pub abpla0: f64,
/// 第一深度点质量 (DM1)
pub dm1: f64,
/// DM 固定标志 (IDMFIX)
pub idmfix: i32,
/// 粘性参数 α (ALPHAV)
pub alphav: f64,
/// 粘性分数 (FRACTV)
pub fractv: f64,
/// ZETA0 参数
pub zeta0: f64,
/// ZETA1 参数
pub zeta1: f64,
/// 粘性质量比 (DMVISC)
pub dmvisc: f64,
/// 通量平均不透明度乘数 (ABFLXM)
pub abflxm: f64,
}
impl Default for LtegrdConfig {
fn default() -> Self {
Self {
ndgrey: 0,
idgrey: 0,
itgmx0: 5,
nnewd: 0,
nconit: 0,
ipring: 0,
hmix0: 0.0,
dion0: 0.5,
abros0: 0.4,
abpla0: 0.4,
dm1: 0.0,
idmfix: 0,
alphav: 1.0,
fractv: 0.5,
zeta0: 0.0,
zeta1: 0.0,
dmvisc: 0.1,
abflxm: 0.4,
}
}
}
// ============================================================================
// 输入/输出结构体
// ============================================================================
/// LTEGRD 输入参数。
pub struct LtegrdParams<'a> {
/// 配置
pub config: LtegrdConfig,
/// 深度点数 (ND)
pub nd: usize,
/// 能级数 (NLEVEL)
pub nlevel: usize,
/// 有效温度 (TEFF)
pub teff: f64,
/// 表面重力加速度 (QGRAV)
pub qgrav: f64,
/// 平均分子量 [深度] (WMM)
pub wmm: &'a [f64],
/// 初始温度 [深度] (TEMP)
pub temp: &'a mut [f64],
/// 初始电子密度 [深度] (ELEC)
pub elec: &'a mut [f64],
/// 初始粒子密度 [深度] (DENS)
pub dens: &'a mut [f64],
/// 初始柱质量密度 [深度] (DM)
pub dm: &'a mut [f64],
/// 几何深度 [深度] (ZD)
pub zd: &'a mut [f64],
/// 总压力 [深度] (PTOTAL)
pub ptotal: &'a mut [f64],
/// 气体压力 [深度] (PGS)
pub pgs: &'a mut [f64],
/// Rosseland 光学深度 [深度] (TAUROS)
pub tauros: &'a mut [f64],
/// Rosseland 平均不透明度 [深度] (ABROSD)
pub abrosd: &'a mut [f64],
/// Planck 平均不透明度 [深度] (ABPLAD)
pub abplad: &'a mut [f64],
/// 湍流速度 [深度] (VTURB)
pub vturb: &'a [f64],
/// TAUTHE [深度]
pub tauthe: &'a mut [f64],
/// TAUFLX [深度]
pub tauflx: &'a mut [f64],
/// THETA [深度]
pub theta: &'a mut [f64],
/// VISCD [深度]
pub viscd: &'a mut [f64],
/// GAMJ [深度]
pub gamj: &'a mut [f64],
/// TOTJ [深度]
pub totj: &'a mut [f64],
/// TOTH [深度]
pub toth: &'a mut [f64],
/// TOTK [深度]
pub totk: &'a mut [f64],
/// RDOPAC [深度]
pub rdopac: &'a mut [f64],
/// FLOPAC [深度]
pub flopac: &'a mut [f64],
}
/// LTEGRD 原子数据(简化版)。
pub struct LtegrdAtomicData<'a> {
/// Saha-Boltzmann 因子 [能级][深度]
pub sbf: &'a mut [Vec<f64>],
/// 占据概率 [能级][深度]
pub wop: &'a mut [Vec<f64>],
}
/// LTEGRD 输出。
#[derive(Debug, Clone)]
pub struct LtegrdOutput {
/// 深度点数 (ND)
pub nd: usize,
/// 柱质量密度 [深度] (DM)
pub dm: Vec<f64>,
/// 温度 [深度] (TEMP)
pub temp: Vec<f64>,
/// 电子密度 [深度] (ELEC)
pub elec: Vec<f64>,
/// 总粒子密度 [深度] (DENS)
pub dens: Vec<f64>,
/// 几何深度 [深度] (ZD)
pub zd: Vec<f64>,
/// 总压力 [深度] (PTOTAL)
pub ptotal: Vec<f64>,
/// 气体压力 [深度] (PGS)
pub pgs: Vec<f64>,
/// Rosseland 光学深度 [深度] (TAUROS)
pub tauros: Vec<f64>,
/// Rosseland 平均不透明度 [深度]
pub abrosd: Vec<f64>,
/// Planck 平均不透明度 [深度]
pub abplad: Vec<f64>,
/// Eddington 因子 GAMH
pub gamh: f64,
/// 迭代计数 ITGREY
pub itgrey: i32,
/// 盘总质量 DMTOT
pub dmtot: f64,
/// 盘耗散 EDISC
pub edisc: f64,
}
// ============================================================================
// 工作数组
// ============================================================================
/// LTEGRD 内部工作数组。
struct LtegrdWork {
/// 备份 TEMP0
temp0: Vec<f64>,
/// 备份 ELEC0
elec0: Vec<f64>,
/// 备份 DENS0
dens0: Vec<f64>,
/// 备份 ZD0
zd0: Vec<f64>,
/// 备份 DM0
dm0: Vec<f64>,
}
impl LtegrdWork {
fn new() -> Self {
Self {
temp0: vec![0.0; MDEPTH],
elec0: vec![0.0; MDEPTH],
dens0: vec![0.0; MDEPTH],
zd0: vec![0.0; MDEPTH],
dm0: vec![0.0; MDEPTH],
}
}
}
// ============================================================================
// 核心计算函数
// ============================================================================
/// 执行 LTE-Grey 盘模型计算(纯计算,无 I/O)。
///
/// # 参数
/// - `params`: 输入参数
///
/// # 返回值
/// 计算结果
pub fn ltegrd_pure(params: &mut LtegrdParams) -> LtegrdOutput {
let config = &params.config;
let mut work = LtegrdWork::new();
// 1. 确定深度点数
let mut ndepth = if config.ndgrey == 0 {
params.nd
} else {
config.ndgrey as usize
};
if ndepth > MDEPTH {
panic!("NDEPTH too large in LTEGRD: {} > {}", ndepth, MDEPTH);
}
let idepth = config.idgrey;
let mut itgmax = config.itgmx0;
let mut nconit = config.nconit;
if config.hmix0 > 0.0 && nconit == 0 {
nconit = 10;
}
// 处理 DION0
let mut dion0 = config.dion0;
let abpmin = if dion0 < 0.0 {
let abpmin_val = -dion0;
dion0 = 1.0;
abpmin_val
} else {
1e-10
};
let _ = abpmin;
// 2. 计算基本量
let t4 = params.teff.powi(4);
let totf = SIG4P * t4;
let abfl0 = SIGE / params.wmm[0];
let (t0, dmtot, edisc) = if config.idmfix == 1 {
let t0 = params.teff;
let dmtot = totf / 0.1;
(t0, dmtot, totf / dmtot)
} else {
let t0 = params.teff;
let dmtot = totf / (SIGE / params.wmm[0] * 2.0);
let edisc = totf / dmtot;
(t0, dmtot, edisc)
};
// 3. 计算标高
let vtb = params.vturb[0];
let vsnd20: f64 = 2.76e-16 * t0 / params.wmm[0] * dion0 + vtb * vtb;
let hscalg: f64 = (TWO * vsnd20 / params.qgrav).sqrt();
let hscalr: f64 = 4.19168946e-10 * totf * abfl0 / params.qgrav;
let r: f64 = hscalr / hscalg;
// 诊断输出被简化(无 writer)
if config.ipring >= 2 {
eprintln!(" GAS PRESSURE SCALE HEIGHT = {:+.3E}", hscalg);
eprintln!(" RAD.PRESSURE SCALE HEIGHT = {:+.3E}", hscalr);
eprintln!(" RATIO = {:+.3E}", r);
}
// 4. 初始化 Eddington 因子
let mut gamh = UN;
let _fak0 = THIRD;
let mut anerel = (dion0 - HALF) / dion0;
if anerel < ERRT {
anerel = ERRT;
}
let _ = anerel;
if ndepth == 0 {
ndepth = params.nd;
}
let nd0 = params.nd;
let mut nd = ndepth;
// 保存原始 DM
for id in 0..nd0 {
work.dm0[id] = params.dm[id];
}
// 5. 调用 ZMRHO 计算质量-深度-密度-几何深度
nd = zmrho(
r,
hscalg,
config.dm1,
dmtot,
nd,
params.dm,
params.dens,
params.zd,
);
// 6. 初始化迭代
let mut itgrey = -1;
let amuv0 = config.dmvisc.powf(config.zeta0 + UN);
let amuv1 = UN - amuv0;
// 初始化各种数组
for id in 0..nd {
params.pgs[id] = params.dens[id] * vsnd20;
// 计算粘性相关量
if params.dm[id] <= config.dmvisc * params.dm[nd - 1] {
params.viscd[id] = (UN - config.fractv) * (config.zeta1 + UN)
/ config.dmvisc.powf(config.zeta1 + UN)
* (params.dm[id] / params.dm[nd - 1]).powf(config.zeta1);
params.theta[id] = (UN - config.fractv)
* (params.dm[id] / config.dmvisc / params.dm[nd - 1]).powf(config.zeta1 + UN);
} else {
params.viscd[id] = config.fractv * (config.zeta0 + UN) / amuv1
* (params.dm[id] / params.dm[nd - 1]).powf(config.zeta0);
params.theta[id] = (UN - config.fractv)
+ config.fractv
* ((params.dm[id] / params.dm[nd - 1]).powf(config.zeta0 + UN) - amuv0)
/ amuv1;
}
params.gamj[id] = UN;
// 初始 Rosseland 不透明度和 TAUTHE
if id == 0 {
let taur = params.dm[id] * config.abros0;
params.tauthe[id] = taur * params.theta[id] / (config.zeta1 + TWO);
params.abrosd[id] = config.abros0;
params.abplad[id] = config.abpla0;
params.tauros[id] = taur;
} else {
let ddm = params.dm[id] - params.dm[id - 1];
params.tauros[id] = params.tauros[id - 1] + ddm * params.abrosd[id - 1];
params.tauthe[id] =
params.tauthe[id - 1] + ddm * params.abrosd[id - 1] * params.theta[id];
params.abrosd[id] = params.abrosd[id - 1];
params.abplad[id] = params.abplad[id - 1];
}
// 计算灰大气温度
let taur = params.tauros[id];
params.temp[id] = compute_grey_temperature(taur, params.teff);
}
// 7. 主迭代循环
loop {
itgrey += 1;
// 更新温度
for id in 0..nd {
let taur = if itgrey > 1 {
params.tauflx[id]
} else {
params.tauros[id]
};
params.temp[id] = compute_grey_temperature(taur, params.teff);
}
// 对流处理
if config.hmix0 > 0.0 {
break;
}
// 检查迭代结束
if itgmax == 0 {
break;
}
if itgrey == 0 {
itgrey = 1;
}
// 简化的 RADTOT 计算
for id in 0..nd {
params.totj[id] = SIG4P * params.temp[id].powi(4);
params.toth[id] = totf * (UN - params.theta[id]);
params.totk[id] = params.totj[id] / 3.0;
params.rdopac[id] = params.abrosd[id] * params.dens[id];
params.flopac[id] = params.abrosd[id] * params.toth[id];
}
// 插值 TOTH 和 FLOPAC
for id in 1..nd - 1 {
let a1 = params.dm[id + 1] - params.dm[id - 1];
if a1.abs() > 1e-30 {
let a0 = (params.dm[id] - params.dm[id - 1]) / a1;
let a1_frac = (params.dm[id + 1] - params.dm[id]) / a1;
params.toth[id] = a0 * params.toth[id + 1] + a1_frac * params.toth[id];
params.flopac[id] = a0 * params.flopac[id + 1] + a1_frac * params.flopac[id];
}
}
params.toth[nd - 1] = 0.0;
params.flopac[nd - 1] = params.flopac[nd - 2];
// Unsöld-Lucy 温度修正
let mut dfint = 0.0;
let mut db0 = 0.0;
let mut abflxm = config.abflxm;
for id in 0..nd {
let hmech = totf * (UN - params.theta[id]);
let dflux = params.toth[id] - hmech;
let fkk = if params.totj[id] > 0.0 {
params.totk[id] / params.totj[id]
} else {
THIRD
};
let abrad = if params.totj[id] > 0.0 {
params.rdopac[id] / params.dens[id] / params.totj[id]
} else {
params.abrosd[id]
};
params.gamj[id] = abrad / params.abplad[id] / fkk * THIRD;
let abflx = if id != nd - 1 {
if params.toth[id] > 0.0 {
params.flopac[id] / params.toth[id]
} else {
params.abrosd[id]
}
} else {
abflxm
};
if id == 0 {
let fhh = if params.totj[id] > 0.0 {
params.toth[id] / params.totj[id]
} else {
1.0
};
gamh = fkk / fhh / 0.57753;
params.tauflx[id] = abflx * params.dm[id];
params.tauthe[id] = params.tauflx[id] * params.theta[id] / (config.zeta1 + TWO);
dfint = params.tauflx[id] * dflux;
db0 = fkk / fhh * dflux;
} else {
let zetad = if params.dm[id] <= config.dmvisc * params.dm[nd - 1] {
config.zeta1
} else {
config.zeta0
};
let ddm = params.dm[id] - params.dm[id - 1];
if ddm.abs() > 1e-30 {
let a0 = (abflxm * params.dm[id] - abflx * params.dm[id - 1])
/ ddm
/ (zetad + TWO);
let a1 = (abflx - abflxm) / ddm / (zetad + 3.0);
params.tauflx[id] = params.tauflx[id - 1] + ddm * HALF * (abflxm + abflx);
params.tauthe[id] = params.tauthe[id - 1]
+ a0 * (params.theta[id] * params.dm[id]
- params.theta[id - 1] * params.dm[id - 1])
+ a1 * (params.theta[id] * params.dm[id].powi(2)
- params.theta[id - 1] * params.dm[id - 1].powi(2));
dfint = dfint + ddm * HALF * (abflxm * dflux + abflx * dflux);
}
}
abflxm = abflx;
if itgmax >= 0 {
let b0 = FOUR * SIG4P * params.temp[id].powi(4);
let dis = totf * params.viscd[id] / params.abplad[id] / params.dm[nd - 1];
let db1 = abrad / params.abplad[id] * params.totj[id] - b0 + dis;
let db = db1 - 3.0 * params.gamj[id] * (db0 + dfint);
let bnew = FOUR * SIG4P * params.temp[id].powi(4) + db;
if bnew > 0.0 {
params.temp[id] = (bnew / FOUR / SIG4P).powf(0.25);
}
}
if id < nd - 1 {
db0 = params.gamj[id] * (db0 + dfint);
}
}
if itgrey >= itgmax.abs() {
break;
}
}
// 8. 插值到最终深度标尺
if idepth > 0 {
for i in 0..nd0.min(nd) {
work.temp0[i] = params.temp[i];
work.elec0[i] = params.elec[i];
work.dens0[i] = params.dens[i];
work.zd0[i] = params.zd[i];
}
nd = nd0;
for i in 0..nd {
params.dm[i] = work.dm0[i];
params.temp[i] = work.temp0[i];
params.elec[i] = work.elec0[i];
params.dens[i] = work.dens0[i];
params.zd[i] = work.zd0[i];
}
}
// 9. 重新计算粒子数
for id in 0..nd {
let t = params.temp[id];
let wmm_id = if id < params.wmm.len() { params.wmm[id] } else { 1.0 };
let an = params.dens[id] / wmm_id + params.elec[id];
params.ptotal[id] = an * BOLK * t;
params.pgs[id] = params.ptotal[id];
}
LtegrdOutput {
nd,
dm: params.dm.to_vec(),
temp: params.temp.to_vec(),
elec: params.elec.to_vec(),
dens: params.dens.to_vec(),
zd: params.zd.to_vec(),
ptotal: params.ptotal.to_vec(),
pgs: params.pgs.to_vec(),
tauros: params.tauros.to_vec(),
abrosd: params.abrosd.to_vec(),
abplad: params.abplad.to_vec(),
gamh,
itgrey,
dmtot,
edisc,
}
}
/// 计算灰大气温度分布。
fn compute_grey_temperature(tau: f64, teff: f64) -> f64 {
// 确保 tau 非负
let tau = tau.max(0.0);
let q = if tau < 1e-4 {
0.5772 // Hopf 函数表面值
} else if tau < 1.0 {
0.5772 + 0.4 * tau.powf(0.6)
} else if tau < 10.0 {
0.710 + 0.05 * (tau - 1.0)
} else {
0.710 + 0.05 * 9.0 + 0.02 * (tau - 10.0).min(90.0)
};
// T = Teff * (3/4 * (tau + q))^{1/4}
// 当 tau = 0 时,T = Teff * (3/4 * 0.5772)^{1/4} ≈ 0.811 * Teff
teff * (0.75 * (tau + q)).powf(0.25)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_compute_grey_temperature() {
let teff = 10000.0;
let t0 = compute_grey_temperature(0.0, teff);
assert!(t0 > 0.0 && t0 < teff);
let t23 = compute_grey_temperature(2.0 / 3.0, teff);
assert!((t23 - teff).abs() / teff < 0.1);
let t1 = compute_grey_temperature(1.0, teff);
assert!(t1 > teff * 0.9);
let t10 = compute_grey_temperature(10.0, teff);
assert!(t10 > t1);
}
#[test]
fn test_ltegrd_config_default() {
let config = LtegrdConfig::default();
assert_eq!(config.ndgrey, 0);
assert_eq!(config.idgrey, 0);
assert_eq!(config.itgmx0, 5);
}
#[test]
fn test_ltegrd_basic() {
let config = LtegrdConfig::default();
let nd = 50;
let nlevel = 100;
let wmm = vec![1.0; MDEPTH];
let mut temp = vec![0.0; MDEPTH];
let mut elec = vec![0.0; MDEPTH];
let mut dens = vec![0.0; MDEPTH];
let mut dm = vec![0.0; MDEPTH];
let mut zd = vec![0.0; MDEPTH];
let mut ptotal = vec![0.0; MDEPTH];
let mut pgs = vec![0.0; MDEPTH];
let mut tauros = vec![0.0; MDEPTH];
let mut abrosd = vec![0.4; MDEPTH];
let mut abplad = vec![0.4; MDEPTH];
let vturb = vec![5.0; MDEPTH];
let mut tauthe = vec![0.0; MDEPTH];
let mut tauflx = vec![0.0; MDEPTH];
let mut theta = vec![0.0; MDEPTH];
let mut viscd = vec![0.0; MDEPTH];
let mut gamj = vec![1.0; MDEPTH];
let mut totj = vec![0.0; MDEPTH];
let mut toth = vec![0.0; MDEPTH];
let mut totk = vec![0.0; MDEPTH];
let mut rdopac = vec![0.0; MDEPTH];
let mut flopac = vec![0.0; MDEPTH];
let mut params = LtegrdParams {
config,
nd,
nlevel,
teff: 35000.0,
qgrav: 1e4,
wmm: &wmm,
temp: &mut temp,
elec: &mut elec,
dens: &mut dens,
dm: &mut dm,
zd: &mut zd,
ptotal: &mut ptotal,
pgs: &mut pgs,
tauros: &mut tauros,
abrosd: &mut abrosd,
abplad: &mut abplad,
vturb: &vturb,
tauthe: &mut tauthe,
tauflx: &mut tauflx,
theta: &mut theta,
viscd: &mut viscd,
gamj: &mut gamj,
totj: &mut totj,
toth: &mut toth,
totk: &mut totk,
rdopac: &mut rdopac,
flopac: &mut flopac,
};
let result = ltegrd_pure(&mut params);
assert!(result.nd > 0);
assert!(!result.dm.is_empty());
assert!(!result.temp.is_empty());
for i in 0..result.nd.min(10) {
assert!(result.temp[i] > 0.0, "Temperature at {} should be positive", i);
}
}
}