//! 对流诊断输出模块。 //! //! 重构自 TLUSTY `conout.f` //! //! # 功能 //! //! 计算并输出温度梯度、对流通量及其导数的诊断信息: //! - 计算各深度点的 DELTA (温度梯度参数) //! - 调用 CONVEC 计算对流通量 //! - 确定对流区的起始和结束深度 //! - 根据 ICONV 参数调整 NDRE 和 REDIF/REINT 数组 use crate::state::constants::{HALF, SIG4P, UN}; // ============================================================================ // 配置结构体 // ============================================================================ /// CONOUT 配置参数。 #[derive(Debug, Clone)] pub struct ConoutConfig { /// 混合长度参数 (HMIX0) pub hmix0: f64, /// 对流模式 (ICONV) /// - 2: 在对流区使用 REDIF=1 /// - 3: 在对流区使用 REDIF=1, REINT=0 (差分形式) pub iconv: i32, /// 盘模式标志 (IDISK) pub idisk: i32, /// 不透明度表标志 (IOPTAB) pub ioptab: i32, /// 对数梯度标志 (ILGDER) /// - 0: 线性平均 /// - 1: 对数平均 pub ilgder: i32, /// 表面重力加速度 (GRAV) pub grav: f64, /// 对流常数 A (ACONML) pub aconml: f64, /// 对流常数 B (BCONML) pub bconml: f64, /// 对流常数 C (CCONML) pub cconml: f64, } impl Default for ConoutConfig { fn default() -> Self { Self { hmix0: 1.0, iconv: 0, idisk: 0, ioptab: 0, ilgder: 0, grav: 1e4, aconml: 1.0, bconml: 1.0, cconml: 1.0, } } } // ============================================================================ // 输入/输出结构体 // ============================================================================ /// CONOUT 输入参数。 pub struct ConoutParams<'a> { /// 模式标志 (IMOD) /// - 2: 计算平均不透明度 pub imod: i32, /// 打印标志 (IPRIN) /// - >0: 输出诊断信息 pub iprin: i32, /// 深度点数 (ND) pub nd: usize, /// 有效温度 (TEFF) pub teff: f64, /// 配置 pub config: ConoutConfig, // 深度相关数组 (nd) /// 温度 (TEMP) pub temp: &'a [f64], /// 电子密度 (ELEC) pub elec: &'a [f64], /// 总粒子密度 (DENS) pub dens: &'a [f64], /// 分子质量 (WMM) pub wmm: &'a [f64], /// 深度 (柱质量密度, DM) pub dm: &'a [f64], /// 深度变量 (ZD) - 盘模式使用 pub zd: &'a [f64], /// 总压力 (PTOTAL) pub ptotal: &'a [f64], /// 气压 (PGS) pub pgs: &'a [f64], /// 湍流速度 (VTURB) pub vturb: &'a [f64], /// Rosseland 不透明度/密度 (ABROSD) pub abrosd: &'a mut [f64], /// 辐射通量 (FLRD) pub flrd: &'a [f64], /// 对流通量 (FLXC) - 输出 pub flxc: &'a mut [f64], /// Delta 温度梯度 (DELTA) - 输出 pub delta: &'a mut [f64], /// 辐射等效积分 (REINT) - 输出 pub reint: &'a mut [f64], /// 辐射等效差分 (REDIF) - 输出 pub redif: &'a mut [f64], // 盘模式特定 /// 角速度参数 (THETAV) pub thetav: &'a [f64], /// 引力参数 (QGRAV) pub qgrav: f64, /// 辐射压 (PRADT) - 盘模式 pub pradt: &'a [f64], } /// 单深度点计算结果。 #[derive(Debug, Clone)] pub struct DepthResult { /// 深度索引 (1-based) pub id: usize, /// Rosseland 光学深度 pub tau: f64, /// 温度 pub t: f64, /// Delta 温度梯度 pub delta: f64, /// 绝热梯度 (GRDADB) pub grdadb: f64, /// 对流/总通量比 pub conrel: f64, /// 辐射/总通量比 pub radrel: f64, } /// CONOUT 输出结果。 #[derive(Debug, Clone)] pub struct ConoutOutput { /// 各深度点计算结果 pub depth_results: Vec, /// 对流区起始深度 (ICBEG, 1-based) pub icbeg: usize, /// 对流区结束深度 (ICEND, 1-based) pub icend: usize, /// 更新后的 NDRE pub ndre: usize, } /// CUBCON 通用块数据 (对流计算中间量)。 #[derive(Debug, Clone, Default)] pub struct CubconData { pub a: f64, pub b: f64, pub del: f64, pub grdadb: f64, pub delmde: f64, pub rho: f64, pub flxtot: f64, pub gravd: f64, } // ============================================================================ // 核心计算函数 // ============================================================================ /// 计算对流诊断信息 (CONOUT)。 /// /// # 参数 /// /// * `params` - 输入参数 /// /// # 返回值 /// /// 返回 `ConoutOutput`,包含各深度点的诊断信息和对流区范围。 /// /// # Fortran 原始代码 /// /// ```fortran /// SUBROUTINE CONOUT(IMOD,IPRIN) /// INCLUDE 'IMPLIC.FOR' /// INCLUDE 'BASICS.FOR' /// INCLUDE 'MODELQ.FOR' /// INCLUDE 'ALIPAR.FOR' /// COMMON/CUBCON/A,B,DEL,GRDADB,DELMDE,RHO,FLXTOT,GRAVD /// ... /// END /// ``` pub fn conout_pure(params: &mut ConoutParams) -> ConoutOutput { let nd = params.nd; let mut depth_results = Vec::with_capacity(nd); let mut icbeg: usize = 0; let mut icend: usize = 0; let mut ndre = 0; // 计算总通量 let flxto0 = SIG4P * params.teff.powi(4); // 初始化变量 let mut taum = 0.0; let mut grdadb = 0.0; // 遍历所有深度点 for id in 0..nd { let t = params.temp[id]; let ptot = params.ptotal[id]; let pg = params.pgs[id]; // 计算辐射压 let mut prad = ptot - pg - HALF * params.dens[id] * params.vturb[id].powi(2); if prad < 0.0 { prad = 0.0; } // 计算总通量和引力 let mut flxtot = flxto0; let mut gravd = 0.0; if params.config.idisk == 1 { flxtot = flxto0 * (UN - params.thetav[id]); gravd = params.zd[id] * params.qgrav; prad = params.pradt[id]; } // 第一个深度点特殊处理 let (delta_val, flxcnv) = if id == 0 { let tau = params.dm[0] * params.abrosd[0]; params.delta[0] = 0.0; params.flxc[0] = 0.0; taum = tau; depth_results.push(DepthResult { id: 1, tau, t, delta: 0.0, grdadb: 0.0, conrel: 0.0, radrel: if flxtot > 0.0 { params.flrd[0] / flxtot } else { 1.0 }, }); (0.0, 0.0) } else { // 计算光学深度和温度梯度 let tm = params.temp[id - 1]; let tau = taum + HALF * (params.dm[id] - params.dm[id - 1]) * (params.abrosd[id] + params.abrosd[id - 1]); let ptotm = params.ptotal[id - 1]; let pgm = params.pgs[id - 1]; let mut pradm = ptotm - pgm - HALF * params.dens[id - 1] * params.vturb[id - 1].powi(2); if params.config.idisk == 1 { pradm = params.pradt[id - 1]; } if pradm < 0.0 { pradm = 0.0; } // 计算中间点值 let (t0, pt0, pg0, pr0, ab0, dlt) = if params.config.ilgder == 0 { // 线性平均 let t0 = HALF * (t + tm); let pt0 = HALF * (ptot + ptotm); let pg0 = HALF * (pg + pgm); let pr0 = HALF * (prad + pradm); let ab0 = HALF * (params.abrosd[id] + params.abrosd[id - 1]); let dlt = (t - tm) / (ptot - ptotm) * pt0 / t0; (t0, pt0, pg0, pr0, ab0, dlt) } else { // 对数平均 let t0 = (t * tm).sqrt(); let pt0 = (ptot * ptotm).sqrt(); let pg0 = (pg * pgm).sqrt(); let pr0 = (prad * pradm).sqrt(); let ab0 = (params.abrosd[id] * params.abrosd[id - 1]).sqrt(); let dlt = if t > 0.0 && tm > 0.0 && ptot > 0.0 && ptotm > 0.0 { (t / tm).ln() / (ptot / ptotm).ln() } else { 0.0 }; (t0, pt0, pg0, pr0, ab0, dlt) }; params.delta[id] = dlt; // 计算对流通量 let mut flxcnv = 0.0; let mut vcon = 0.0; if params.config.idisk != 1 || id < nd - 1 { // 调用简化对流计算 let convec_result = compute_convection( id + 1, // 1-based t0, pt0, pg0, pr0, ab0, dlt, ¶ms.config, flxtot, gravd, ); flxcnv = convec_result.0; vcon = convec_result.1; grdadb = convec_result.2; } if params.config.hmix0 > 0.0 { params.flxc[id] = flxcnv; } // 检测对流区起始 if icbeg == 0 && params.flxc[id] > 0.0 && params.flxc[id - 1] == 0.0 && id > 24 { icbeg = id + 1; // 1-based } if icbeg > 0 && params.flxc[id] > 0.0 { icend = id + 1; // 1-based } // 计算通量比 let (conrel, radrel) = if flxtot > 0.0 { (flxcnv / flxtot, params.flrd[id] / flxtot) } else { (0.0, 1.0) }; // 记录结果 depth_results.push(DepthResult { id: id + 1, tau, t, delta: dlt, grdadb, conrel, radrel, }); taum = tau; (dlt, flxcnv) }; } // 根据 ICONV 调整 NDRE 和 REDIF/REINT if icbeg > 3 { if params.config.iconv == 3 { ndre = icbeg - 1; for id in 0..nd { if id >= ndre - 1 { params.reint[id] = 0.0; params.redif[id] = 1.0; } else { params.reint[id] = 1.0; params.redif[id] = 0.0; } } } else if params.config.iconv == 2 { ndre = icbeg - 1; for id in 0..nd { if id >= ndre - 1 { params.redif[id] = 1.0; } } } } ConoutOutput { depth_results, icbeg, icend, ndre, } } /// 简化的对流计算 (内部使用)。 /// /// 返回 (flxcnv, vcon, grdadb) fn compute_convection( _id: usize, t0: f64, pt0: f64, pg0: f64, pr0: f64, ab0: f64, dlt: f64, config: &ConoutConfig, flxtot: f64, gravd: f64, ) -> (f64, f64, f64) { // 如果对流被禁用 if config.hmix0 < 0.0 { return (0.0, 0.0, 0.0); } // 绝热梯度近似 (单原子理想气体 = 0.4) let grdadb = 0.4; // 检查对流不稳定性 let ddel = dlt - grdadb; if ddel < 0.0 { return (0.0, 0.0, grdadb); } // 简化的对流计算 let grav = if config.idisk == 1 { gravd } else { config.grav }; if grav == 0.0 { return (0.0, 0.0, grdadb); } // 粗略估计密度 let rho = if t0 > 0.0 { pt0 / (t0 * 1.38e-16 * grav) } else { 1e-7 }; // 混合长度 let hmix = if config.hmix0 == 0.0 { 1.0 } else { config.hmix0 }; // 压力标高 let hscale = pt0 / rho / grav; // 简化的对流速度 (基于混合长度理论) // vco ~ hmix * sqrt(aconml * pt0 / rho * dlrdlt) // 这里简化处理,假设 dlrdlt ~ 1.0 let vco = hmix * (config.aconml * pt0 / rho).abs().sqrt(); // 简化的对流系数 // flco ~ bconml * rho * heatcp * t0 * hmix / 4pi // 这里假设 heatcp ~ 1.0 let flco = config.bconml * rho * t0 * hmix / 12.5664; // 光学厚度 let taue = hmix * ab0 * rho * hscale; // 辐射耗散因子 let fac = taue / (UN + HALF * taue * taue); // 参数 B (参考 Mihalas) let b = 5.67e-5 * t0.powi(3) / (rho * vco) * fac * config.cconml * HALF; // 参数 D let d = b * b / 2.0; let disc = d / 2.0 + ddel; // 计算有效 DLT let dlt_eff = if disc >= 0.0 { let val = d + ddel - b * disc.sqrt(); if val < 0.0 { 0.0 } else { val } } else { 0.0 }; // 对流速度和通量 let vconv = vco * dlt_eff.sqrt(); let flxcnv = flco * vconv * dlt_eff; (flxcnv, vconv, grdadb) } // ============================================================================ // I/O 函数 // ============================================================================ /// 格式化输出诊断信息表头。 pub fn format_conout_header() -> String { "\n\n ID TAUR TEMP DELTA DELTA(AD) CON/TOT RAD/TOT (C+R)/TOT\n\n".to_string() } /// 格式化单行输出。 pub fn format_depth_line(result: &DepthResult) -> String { format!( "{:4}{:9.2}{:9.1}{:10.2}{:10.2}{:10.2}{:10.2}{:10.2}\n", result.id, result.tau, result.t, result.delta, result.grdadb, result.conrel, result.radrel, result.conrel + result.radrel ) } /// 格式化对流区信息。 pub fn format_convective_zone(icbeg: usize, icend: usize) -> String { format!( "\n convective zone between depths (inclusive) {:4}{:4}\n", icbeg, icend ) } /// 格式化 NDRE 重置信息。 pub fn format_ndre_reset(ndre: usize) -> String { format!( "\n\n NDRE IS RESET IN CONOUT DUE TO THE EXISTENCE OF CONVECTIVE ZONE\n NDRE= {:3}\n", ndre ) } // ============================================================================ // 测试 // ============================================================================ #[cfg(test)] mod tests { use super::*; /// 测试用的参数构建器 struct TestParamsBuilder { nd: usize, imod: i32, iprin: i32, teff: f64, config: ConoutConfig, } impl TestParamsBuilder { fn new(nd: usize) -> Self { Self { nd, imod: 0, iprin: 1, teff: 35000.0, config: ConoutConfig::default(), } } fn config(mut self, config: ConoutConfig) -> Self { self.config = config; self } fn build(self) -> ConoutParams<'static> { let nd = self.nd; let mut temp = vec![0.0; nd]; let mut elec = vec![0.0; nd]; let mut dens = vec![0.0; nd]; let mut wmm = vec![0.0; nd]; let mut dm = vec![0.0; nd]; let mut zd = vec![0.0; nd]; let mut ptotal = vec![0.0; nd]; let mut pgs = vec![0.0; nd]; let mut vturb = vec![0.0; nd]; let mut abrosd = vec![0.0; nd]; let mut flrd = vec![0.0; nd]; let mut flxc = vec![0.0; nd]; let mut delta = vec![0.0; nd]; let mut reint = vec![0.0; nd]; let mut redif = vec![0.0; nd]; let mut thetav = vec![0.0; nd]; let mut pradt = vec![0.0; nd]; for i in 0..nd { temp[i] = 10000.0 - i as f64 * 100.0; elec[i] = 1e12; dens[i] = 1e-7; wmm[i] = 1.0; dm[i] = 1e-2 * (i + 1) as f64; zd[i] = 1e10 * (i + 1) as f64; ptotal[i] = 1e5; pgs[i] = 1e5; vturb[i] = 0.0; abrosd[i] = 0.1; flrd[i] = 1e10; flxc[i] = 0.0; delta[i] = 0.0; reint[i] = 1.0; redif[i] = 0.0; thetav[i] = 0.0; pradt[i] = 0.0; } // 使用 Box::leak 来创建 'static 引用 ConoutParams { imod: self.imod, iprin: self.iprin, nd, teff: self.teff, config: self.config, temp: Box::leak(temp.into_boxed_slice()), elec: Box::leak(elec.into_boxed_slice()), dens: Box::leak(dens.into_boxed_slice()), wmm: Box::leak(wmm.into_boxed_slice()), dm: Box::leak(dm.into_boxed_slice()), zd: Box::leak(zd.into_boxed_slice()), ptotal: Box::leak(ptotal.into_boxed_slice()), pgs: Box::leak(pgs.into_boxed_slice()), vturb: Box::leak(vturb.into_boxed_slice()), abrosd: Box::leak(abrosd.into_boxed_slice()), flrd: Box::leak(flrd.into_boxed_slice()), flxc: Box::leak(flxc.into_boxed_slice()), delta: Box::leak(delta.into_boxed_slice()), reint: Box::leak(reint.into_boxed_slice()), redif: Box::leak(redif.into_boxed_slice()), thetav: Box::leak(thetav.into_boxed_slice()), qgrav: 1e-10, pradt: Box::leak(pradt.into_boxed_slice()), } } } #[test] fn test_conout_basic() { let mut params = TestParamsBuilder::new(50).build(); let output = conout_pure(&mut params); // 验证基本输出 assert_eq!(output.depth_results.len(), 50); } #[test] fn test_format_output() { let header = format_conout_header(); assert!(header.contains("TAUR")); assert!(header.contains("TEMP")); let result = DepthResult { id: 1, tau: 1e-4, t: 10000.0, delta: 0.3, grdadb: 0.4, conrel: 0.1, radrel: 0.9, }; let line = format_depth_line(&result); assert!(line.contains("1")); } #[test] fn test_conout_no_convection() { let config = ConoutConfig { hmix0: -1.0, // 禁用对流 ..Default::default() }; let mut params = TestParamsBuilder::new(50).config(config).build(); let output = conout_pure(&mut params); // 禁用对流时不应该有对流区 assert_eq!(output.icbeg, 0); assert_eq!(output.icend, 0); } #[test] fn test_conout_iconv_mode_2() { let config = ConoutConfig { iconv: 2, hmix0: 1.0, ..Default::default() }; let mut params = TestParamsBuilder::new(50).config(config).build(); let output = conout_pure(&mut params); // 验证基本功能 assert_eq!(output.depth_results.len(), 50); } #[test] fn test_conout_iconv_mode_3() { let config = ConoutConfig { iconv: 3, hmix0: 1.0, ..Default::default() }; let mut params = TestParamsBuilder::new(50).config(config).build(); let output = conout_pure(&mut params); // 验证基本功能 assert_eq!(output.depth_results.len(), 50); } #[test] fn test_conout_disk_mode() { let config = ConoutConfig { idisk: 1, hmix0: 1.0, ..Default::default() }; let mut params = TestParamsBuilder::new(50).config(config).build(); let output = conout_pure(&mut params); // 盘模式应该正常工作 assert_eq!(output.depth_results.len(), 50); } #[test] fn test_compute_convection_disabled() { let config = ConoutConfig { hmix0: -1.0, ..Default::default() }; let (flxcnv, vconv, _) = compute_convection( 1, 10000.0, 1e5, 1e5, 0.0, 0.1, 0.3, &config, 1e10, 0.0 ); assert_eq!(flxcnv, 0.0); assert_eq!(vconv, 0.0); } #[test] fn test_compute_convection_stable() { let config = ConoutConfig::default(); let (flxcnv, vconv, grdadb) = compute_convection( 1, 10000.0, 1e5, 1e5, 0.0, 0.1, 0.1, &config, 1e10, 0.0 ); // dlt < grdadb (0.1 < 0.4),稳定,无对流 assert_eq!(flxcnv, 0.0); assert_eq!(vconv, 0.0); assert!((grdadb - 0.4).abs() < 1e-10); } #[test] fn test_format_convective_zone() { let msg = format_convective_zone(10, 40); assert!(msg.contains("10")); assert!(msg.contains("40")); } #[test] fn test_format_ndre_reset() { let msg = format_ndre_reset(15); assert!(msg.contains("15")); assert!(msg.contains("NDRE")); } }