SpectraRust/src/tlusty/math/convection/concor.rs
2026-04-01 16:35:36 +08:00

392 lines
11 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 `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<f64>;
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 会被重新计算
}
}