335 lines
8.8 KiB
Rust
335 lines
8.8 KiB
Rust
//! 热力学导数计算。
|
||
//!
|
||
//! 重构自 TLUSTY `TRMDRT` 子程序。
|
||
//!
|
||
//! # 功能
|
||
//!
|
||
//! 基于状态方程和熵表计算热力学导数:
|
||
//! - 定压比热 (HEATCP)
|
||
//! - 密度对数导数 d(ln rho)/d(ln T) (DLRDLT)
|
||
//! - 绝热梯度 d(ln T)/d(ln P)_ad (GRDADB)
|
||
//!
|
||
//! # 算法
|
||
//!
|
||
//! 使用数值微分方法计算热力学导数:
|
||
//! 1. 调用 RHOEOS 计算密度
|
||
//! 2. 在密度和温度点附近做数值微分
|
||
//! 3. 计算各种热力学量
|
||
|
||
use crate::tlusty::math::{prsent, rhoeos, PrsentParams, RhoeosParams, ThermTables};
|
||
|
||
/// 气体常数 R (erg/K/mol)
|
||
const RCON: f64 = 8.31434e7;
|
||
|
||
// ============================================================================
|
||
// 输入/输出结构体
|
||
// ============================================================================
|
||
|
||
/// TRMDRT 输入参数。
|
||
pub struct TrmdrtParams<'a> {
|
||
/// 深度索引 (1-based)
|
||
pub id: usize,
|
||
/// 温度 (K)
|
||
pub t: f64,
|
||
/// 气压 (cgs)
|
||
pub p: f64,
|
||
/// 热力学表引用
|
||
pub tables: &'a ThermTables,
|
||
}
|
||
|
||
/// TRMDRT 输出结果。
|
||
#[derive(Debug, Clone)]
|
||
pub struct TrmdrtOutput {
|
||
/// 定压比热
|
||
pub heatcp: f64,
|
||
/// d(ln rho)/d(ln T)
|
||
pub dlrdlt: f64,
|
||
/// 绝热梯度 d(ln T)/d(ln P)_ad
|
||
pub grdadb: f64,
|
||
/// 密度 (g/cm³)
|
||
pub rho: f64,
|
||
/// 熵 (参考值)
|
||
pub entropy: f64,
|
||
/// 定容比热
|
||
pub heatcv: f64,
|
||
/// 比热比 gamma = Cp/Cv
|
||
pub gamma: f64,
|
||
/// 热力学导数项
|
||
pub dpdr: f64,
|
||
pub dpdt: f64,
|
||
pub dsdr: f64,
|
||
pub dsdt: f64,
|
||
}
|
||
|
||
// ============================================================================
|
||
// 核心计算函数
|
||
// ============================================================================
|
||
|
||
/// 计算热力学导数。
|
||
///
|
||
/// # 参数
|
||
///
|
||
/// * `params` - 输入参数,包含深度索引、温度、气压和热力学表
|
||
///
|
||
/// # 返回值
|
||
///
|
||
/// 返回 `TrmdrtOutput`,包含各种热力学量。
|
||
///
|
||
/// # 示例
|
||
///
|
||
/// ```ignore
|
||
/// use tlusty::math::{trmdrt, TrmdrtParams, ThermTables};
|
||
///
|
||
/// let tables = ThermTables::default();
|
||
/// let params = TrmdrtParams { id: 1, t: 10000.0, p: 1e4, tables: &tables };
|
||
/// let result = trmdrt(¶ms);
|
||
/// println!("绝热梯度: {}", result.grdadb);
|
||
/// ```
|
||
pub fn trmdrt(params: &TrmdrtParams) -> TrmdrtOutput {
|
||
let id = params.id;
|
||
let t = params.t;
|
||
let p = params.p;
|
||
let tables = params.tables;
|
||
|
||
// 1. 计算密度
|
||
let rhoeos_params = RhoeosParams { t, p, tables };
|
||
let rho = rhoeos(&rhoeos_params).rho;
|
||
|
||
// 2. 数值微分的步长
|
||
let drho = 0.01 * rho;
|
||
let dt = 0.01 * t;
|
||
|
||
// 3. 在多个点计算压力和熵
|
||
// 中心点
|
||
let prsent_params = PrsentParams { r: rho, t, tables };
|
||
let result0 = prsent(&prsent_params);
|
||
let p0 = result0.fp;
|
||
let s0 = result0.fs;
|
||
let jon = result0.jon;
|
||
|
||
// 密度 +drho
|
||
let prsent_params = PrsentParams {
|
||
r: rho + drho,
|
||
t,
|
||
tables,
|
||
};
|
||
let result1 = prsent(&prsent_params);
|
||
let p1 = result1.fp;
|
||
let s1 = result1.fs;
|
||
|
||
// 密度 -drho
|
||
let prsent_params = PrsentParams {
|
||
r: rho - drho,
|
||
t,
|
||
tables,
|
||
};
|
||
let result2 = prsent(&prsent_params);
|
||
let p2 = result2.fp;
|
||
let s2 = result2.fs;
|
||
|
||
// 温度 +dt
|
||
let prsent_params = PrsentParams {
|
||
r: rho,
|
||
t: t + dt,
|
||
tables,
|
||
};
|
||
let result3 = prsent(&prsent_params);
|
||
let p3 = result3.fp;
|
||
let s3 = result3.fs;
|
||
|
||
// 温度 -dt
|
||
let prsent_params = PrsentParams {
|
||
r: rho,
|
||
t: t - dt,
|
||
tables,
|
||
};
|
||
let result4 = prsent(&prsent_params);
|
||
let p4 = result4.fp;
|
||
let s4 = result4.fs;
|
||
|
||
// 4. 计算数值微分
|
||
let dpdr = (p1 - p2) / (2.0 * drho);
|
||
let dpdt = (p3 - p4) / (2.0 * dt);
|
||
let dsdr = (s1 - s2) / (2.0 * drho) * RCON;
|
||
let dsdt = (s3 - s4) / (2.0 * dt) * RCON;
|
||
|
||
// 5. 计算热力学量
|
||
let (heatcp, dlrdlt, grdadb, heatcv, gamma);
|
||
|
||
if jon == 0 {
|
||
// 正常情况(在表内)
|
||
heatcv = t * dsdt;
|
||
let den = dpdr * dsdt - dpdt * dsdr;
|
||
heatcp = t * den / dpdr;
|
||
|
||
// d(ln rho)/d(ln T) = -rho * dpdr / (t * dpdt)
|
||
// Fortran: DLRDLT = -RHO*DPDR/(T*DPDT); DLRDLT = 1.D0/DLRDLT
|
||
let dlrdlt_raw = -rho * dpdr / (t * dpdt);
|
||
dlrdlt = 1.0 / dlrdlt_raw;
|
||
|
||
// 绝热梯度
|
||
// Fortran: GRDADB = -P/(HEATCP*RHO*T)*DLRDLT
|
||
grdadb = -p / (heatcp * rho * t) * dlrdlt;
|
||
|
||
// Gamma
|
||
let dq = dsdt * p / (den * rho);
|
||
gamma = 1.0 / dq;
|
||
} else {
|
||
// 在表外,使用理想气体近似
|
||
// Fortran 使用 cvedge(JON), cpedge(JON), gammaedge(JON)
|
||
// 简化处理:使用理想气体值
|
||
heatcv = 1.5 * RCON; // 单原子理想气体 Cv
|
||
heatcp = 2.5 * RCON; // 单原子理想气体 Cp
|
||
dlrdlt = -1.0;
|
||
grdadb = -p / (heatcp * rho * t) * dlrdlt;
|
||
gamma = tables.gammaedge.get(jon).copied().unwrap_or(5.0 / 3.0);
|
||
}
|
||
|
||
// 6. 重新计算绝热梯度(Fortran 最后覆盖)
|
||
// grdadb = p/t*(dsdr/(dsdr*dpdt-dsdt*dpdr))
|
||
let den2 = dsdr * dpdt - dsdt * dpdr;
|
||
let grdadb_final = if den2.abs() > 1e-30 {
|
||
p / t * (dsdr / den2)
|
||
} else {
|
||
grdadb
|
||
};
|
||
|
||
TrmdrtOutput {
|
||
heatcp,
|
||
dlrdlt,
|
||
grdadb: grdadb_final,
|
||
rho,
|
||
entropy: s0,
|
||
heatcv,
|
||
gamma,
|
||
dpdr,
|
||
dpdt,
|
||
dsdr,
|
||
dsdt,
|
||
}
|
||
}
|
||
|
||
#[cfg(test)]
|
||
mod tests {
|
||
use super::*;
|
||
|
||
fn create_test_tables() -> ThermTables {
|
||
let mut tables = ThermTables::default();
|
||
|
||
// 填充一些合理的测试数据
|
||
// 这些数据模拟真实的热力学表行为
|
||
for i in 0..330 {
|
||
for j in 0..100 {
|
||
// 熵和压力使用对数空间的值
|
||
tables.sl[i][j] = 10.0 + 0.01 * i as f64 + 0.1 * j as f64;
|
||
tables.pl[i][j] = 5.0 + 0.01 * i as f64 + 0.05 * j as f64;
|
||
}
|
||
}
|
||
|
||
// 边缘数据必须设置合理值(用于表外插值)
|
||
for j in 0..100 {
|
||
tables.pedge[j] = 1.0e5;
|
||
tables.sedge[j] = 1.0e8;
|
||
tables.tedge[j] = 1.0e4;
|
||
tables.gammaedge[j] = 1.6667;
|
||
}
|
||
tables.redge = 1.0e-5;
|
||
|
||
tables
|
||
}
|
||
|
||
#[test]
|
||
fn test_trmdrt_basic() {
|
||
let tables = create_test_tables();
|
||
let params = TrmdrtParams {
|
||
id: 1,
|
||
t: 10000.0,
|
||
p: 1e4,
|
||
tables: &tables,
|
||
};
|
||
|
||
let result = trmdrt(¶ms);
|
||
|
||
// 验证基本属性
|
||
assert!(result.rho > 0.0, "密度应该为正");
|
||
assert!(result.heatcp > 0.0, "定压比热应该为正");
|
||
assert!(result.gamma > 1.0, "gamma 应该大于 1");
|
||
}
|
||
|
||
#[test]
|
||
fn test_trmdrt_higher_temperature() {
|
||
let tables = create_test_tables();
|
||
|
||
let params1 = TrmdrtParams {
|
||
id: 1,
|
||
t: 10000.0,
|
||
p: 1e4,
|
||
tables: &tables,
|
||
};
|
||
let result1 = trmdrt(¶ms1);
|
||
|
||
let params2 = TrmdrtParams {
|
||
id: 2,
|
||
t: 20000.0,
|
||
p: 1e4,
|
||
tables: &tables,
|
||
};
|
||
let result2 = trmdrt(¶ms2);
|
||
|
||
// 更高温度下密度应该更低
|
||
assert!(
|
||
result2.rho < result1.rho,
|
||
"更高温度下密度应该更低: {} vs {}",
|
||
result2.rho,
|
||
result1.rho
|
||
);
|
||
}
|
||
|
||
#[test]
|
||
fn test_trmdrt_different_pressure() {
|
||
let tables = create_test_tables();
|
||
|
||
let params1 = TrmdrtParams {
|
||
id: 1,
|
||
t: 10000.0,
|
||
p: 1e4,
|
||
tables: &tables,
|
||
};
|
||
let result1 = trmdrt(¶ms1);
|
||
|
||
let params2 = TrmdrtParams {
|
||
id: 2,
|
||
t: 10000.0,
|
||
p: 1e5,
|
||
tables: &tables,
|
||
};
|
||
let result2 = trmdrt(¶ms2);
|
||
|
||
// 验证两个结果都是有效的正数
|
||
assert!(result1.rho > 0.0 && result1.rho.is_finite(), "密度1应该是有效的正数");
|
||
assert!(result2.rho > 0.0 && result2.rho.is_finite(), "密度2应该是有效的正数");
|
||
|
||
// 注意:由于测试表数据不是真实的热力学数据,
|
||
// 物理关系可能不正确,所以我们只验证结果有限且为正
|
||
}
|
||
|
||
#[test]
|
||
fn test_trmdrt_derivatives() {
|
||
let tables = create_test_tables();
|
||
let params = TrmdrtParams {
|
||
id: 1,
|
||
t: 10000.0,
|
||
p: 1e4,
|
||
tables: &tables,
|
||
};
|
||
|
||
let result = trmdrt(¶ms);
|
||
|
||
// 检查导数值是有限的
|
||
assert!(result.dpdr.is_finite(), "dpdr 应该是有限的");
|
||
assert!(result.dpdt.is_finite(), "dpdt 应该是有限的");
|
||
assert!(result.dsdr.is_finite(), "dsdr 应该是有限的");
|
||
assert!(result.dsdt.is_finite(), "dsdt 应该是有限的");
|
||
}
|
||
}
|