//! 热力学导数计算。 //! //! 重构自 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 应该是有限的"); } }