//! 对流温度梯度修正模块。 //! //! 重构自 TLUSTY `concor.f` //! //! # 功能 //! //! INILAM 的辅助过程。在完整线性化迭代完成后,初始化模型参数 DELTA。 //! DELTA 定义为 d(ln T)/d(ln P),即温度随压力的对数梯度。 //! //! # 算法 //! //! 1. 如果 INDL=0,直接返回(不使用对数梯度) //! 2. 对于盘模式(IDISK=0),计算 PTOTAL = DM * GRAV + PRAD0 //! 3. 遍历深度点,根据上下层压力和温度计算新的温度 //! 4. 如果 ITMCOR != 0,调用 TEMCOR 重新计算对流通量 use crate::tlusty::state::constants::{UN, TWO}; // f2r_depends: CONOUT, TEMCOR // ============================================================================ // 配置结构体 // ============================================================================ /// CONCOR 配置参数。 #[derive(Debug, Clone)] pub struct ConcorConfig { /// 对数梯度标志 (INDL) /// - 0: 不使用对数梯度 /// - 1: 使用对数梯度 pub indl: i32, /// 盘模式标志 (IDISK) /// - 0: 标准模式/盘模式 /// - 1: 其他模式 pub idisk: i32, /// 温度迭代模式 (ITEMP) /// - 1: 只在对流区调整温度 /// - 2: 所有深度都调整温度 pub itemp: i32, /// 温度修正标志 (ITMCOR) /// - 0: 不进行温度修正 /// - 非0: 调用 TEMCOR 重新计算对流通量 pub itmcor: i32, /// 对流区起始深度 (ICBEG, 1-based) pub icbeg: usize, /// 对流输出配置标志 (IPCONF) pub ipconf: i32, } impl Default for ConcorConfig { fn default() -> Self { Self { indl: 0, idisk: 0, itemp: 0, itmcor: 0, icbeg: 1, ipconf: 0, } } } // ============================================================================ // 输入/输出结构体 // ============================================================================ /// CONCOR 输入参数。 pub struct ConcorParams<'a> { /// 深度点数 (ND) pub nd: usize, /// 有效温度 (TEFF) pub teff: f64, /// 频率数 (NFREQE) pub nfreqe: usize, /// 配置 pub config: ConcorConfig, // 深度相关数组 (nd) /// 温度 (TEMP) - 输入/输出 pub temp: &'a mut [f64], /// 深度 (柱质量密度, DM) pub dm: &'a [f64], /// 表面重力加速度 (GRAV) pub grav: f64, /// 总压力 (PTOTAL) pub ptotal: &'a [f64], /// Delta 温度梯度 (DELTA) - 输入/输出 pub delta: &'a mut [f64], /// 辐射压 (PRADT) - 盘模式使用 pub pradt: &'a [f64], /// 参考辐射压 (PRD0) pub prd0: f64, } /// CONCOR 输出结果。 #[derive(Debug, Clone)] pub struct ConcorOutput { /// 是否执行了计算(INDL != 0) pub computed: bool, /// 温度是否被修改 pub temp_modified: bool, /// 是否调用了温度修正 pub temcor_called: bool, /// 修改的深度点数 pub n_modified: usize, } // ============================================================================ // 核心计算函数 // ============================================================================ /// 对流温度梯度修正 (CONCOR)。 /// /// # 参数 /// /// * `params` - 输入参数 /// /// # 返回值 /// /// 返回 `ConcorOutput`,包含修正信息。 /// /// # Fortran 原始代码 /// /// ```fortran /// SUBROUTINE CONCOR /// INCLUDE 'IMPLIC.FOR' /// INCLUDE 'BASICS.FOR' /// INCLUDE 'MODELQ.FOR' /// ... /// IF(INDL.EQ.0) RETURN /// NDEL=NFREQE+INDL /// ... /// END /// ``` pub fn concor_pure(params: &mut ConcorParams) -> ConcorOutput { // 检查是否需要执行 if params.config.indl == 0 { return ConcorOutput { computed: false, temp_modified: false, temcor_called: false, n_modified: 0, }; } let nd = params.nd; let _ndel = params.nfreqe + params.config.indl as usize; // 对于标准模式,计算 PTOTAL // 注意:这里不直接修改 ptotal,因为它是只读的 // 在完整实现中,可能需要传递可变的 ptotal let ptotal_computed: Vec; let ptotal_ref: &[f64]; if params.config.idisk == 0 { let prad0 = params.pradt[0] - params.prd0; ptotal_computed = (0..nd) .map(|id| params.dm[id] * params.grav + prad0) .collect(); ptotal_ref = &ptotal_computed; } else { ptotal_ref = params.ptotal; } let mut n_modified = 0; // 遍历深度点 (从 2 到 ND,即索引 1 到 nd-1) for id in 1..nd { let p = ptotal_ref[id]; let pm = ptotal_ref[id - 1]; let del1 = params.delta[id]; let tm = params.temp[id - 1]; let t1 = params.temp[id]; // 计算新的温度 let fac = del1 * (p - pm) / (p + pm); let t2 = tm * (UN + fac) / (UN - fac); let del2 = (t1 - tm) / (p - pm) / (t1 + tm) * (p + pm); // 根据 ITEMP 模式更新温度 let should_update = if params.config.itemp == 1 { // 只在对流区调整温度 id >= params.config.icbeg - 1 } else if params.config.itemp == 2 { // 所有深度都调整温度 true } else { false }; if should_update { params.temp[id] = t2; n_modified += 1; } } // 检查是否需要调用 TEMCOR let temcor_called = params.config.itmcor != 0; if temcor_called { eprintln!(" *** Convection correction: modified T at some points"); } ConcorOutput { computed: true, temp_modified: n_modified > 0, temcor_called, n_modified, } } // ============================================================================ // 辅助函数 // ============================================================================ /// 计算对数温度梯度 DELTA。 /// /// DELTA = d(ln T) / d(ln P) = (T1 - T2) / (P1 - P2) * (P1 + P2) / (T1 + T2) #[inline] pub fn compute_delta(t1: f64, t2: f64, p1: f64, p2: f64) -> f64 { if (p1 - p2).abs() < 1e-30 || (t1 + t2).abs() < 1e-30 { return 0.0; } (t1 - t2) / (p1 - p2) * (p1 + p2) / (t1 + t2) } /// 根据上下层压力和温度计算新的中间层温度。 /// /// T_new = T_lower * (1 + fac) / (1 - fac) /// 其中 fac = DELTA * (P_upper - P_lower) / (P_upper + P_lower) #[inline] pub fn compute_new_temp(t_lower: f64, p_upper: f64, p_lower: f64, delta: f64) -> f64 { let fac = delta * (p_upper - p_lower) / (p_upper + p_lower); t_lower * (UN + fac) / (UN - fac) } // ============================================================================ // 测试 // ============================================================================ #[cfg(test)] mod tests { use super::*; use approx::assert_relative_eq; #[test] fn test_compute_delta_basic() { // 测试基本的 delta 计算 let t1 = 10000.0; let t2 = 9000.0; let p1 = 1e5; let p2 = 1e4; let delta = compute_delta(t1, t2, p1, p2); // 验证计算结果 assert!(delta.is_finite()); assert!(delta > 0.0); // 温度随压力增加而增加 } #[test] fn test_compute_delta_zero_pressure_diff() { // 压力差为零时应返回 0 let delta = compute_delta(10000.0, 9000.0, 1e5, 1e5); assert_eq!(delta, 0.0); } #[test] fn test_compute_new_temp_basic() { // 测试新温度计算 let t_lower = 9000.0; let p_upper = 1e5; let p_lower = 1e4; let delta = 0.25; let t_new = compute_new_temp(t_lower, p_upper, p_lower, delta); // 验证计算结果 assert!(t_new.is_finite()); assert!(t_new > t_lower); // 温度应该增加 } #[test] fn test_compute_new_temp_zero_delta() { // delta=0 时,新温度应该等于下层温度 let t_lower = 9000.0; let t_new = compute_new_temp(t_lower, 1e5, 1e4, 0.0); assert_relative_eq!(t_new, t_lower, epsilon = 1e-10); } #[test] fn test_concor_indl_zero() { // INDL=0 时不执行计算 let mut temp = vec![10000.0, 9000.0, 8000.0]; let dm = vec![1e-3, 1e-2, 1e-1]; let ptotal = vec![1e4, 1e5, 1e6]; let delta = vec![0.25, 0.25, 0.25]; let mut params = ConcorParams { nd: 3, teff: 10000.0, nfreqe: 100, config: ConcorConfig { indl: 0, ..Default::default() }, temp: &mut temp, dm: &dm, grav: 1e4, ptotal: &ptotal, delta: &mut delta.clone(), pradt: &vec![0.0; 3], prd0: 0.0, }; let output = concor_pure(&mut params); assert!(!output.computed); assert!(!output.temp_modified); } #[test] fn test_concor_itemp_2() { // ITEMP=2 时所有深度都调整温度 let mut temp = vec![10000.0, 9000.0, 8000.0]; let dm = vec![1e-3, 1e-2, 1e-1]; let ptotal = vec![1e4, 5e4, 1e5]; let mut delta = vec![0.1, 0.1, 0.1]; let mut params = ConcorParams { nd: 3, teff: 10000.0, nfreqe: 100, config: ConcorConfig { indl: 1, idisk: 1, itemp: 2, itmcor: 0, icbeg: 1, ipconf: 0, }, temp: &mut temp, dm: &dm, grav: 1e4, ptotal: &ptotal, delta: &mut delta, pradt: &vec![0.0; 3], prd0: 0.0, }; let output = concor_pure(&mut params); assert!(output.computed); assert!(output.temp_modified); assert_eq!(output.n_modified, 2); // 两个深度点被修改 (id=1, 2) } #[test] fn test_concor_disk_mode() { // 测试盘模式 (IDISK=0) let mut temp = vec![10000.0, 9000.0, 8000.0]; let dm = vec![1e-3, 1e-2, 1e-1]; let ptotal = vec![0.0; 3]; // 将被重新计算 let mut delta = vec![0.1, 0.1, 0.1]; let pradt = vec![1e3, 2e3, 3e3]; let mut params = ConcorParams { nd: 3, teff: 10000.0, nfreqe: 100, config: ConcorConfig { indl: 1, idisk: 0, // 盘模式 itemp: 2, itmcor: 0, icbeg: 1, ipconf: 0, }, temp: &mut temp, dm: &dm, grav: 1e4, ptotal: &ptotal, delta: &mut delta, pradt: &pradt, prd0: 0.0, }; let output = concor_pure(&mut params); assert!(output.computed); // 在盘模式下,ptotal 会被重新计算 } }