392 lines
11 KiB
Rust
392 lines
11 KiB
Rust
//! 对流温度梯度修正模块。
|
||
//!
|
||
//! 重构自 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 会被重新计算
|
||
}
|
||
}
|