use crate::thermophysics::constants::{T_MAX, T_MIN};
use crate::thermophysics::eos::EquationOfState;
use crate::thermophysics::error::ThermoError;
use crate::thermophysics::imports::*;
pub trait ThermoModel: EquationOfState {
fn cp(&self, p: Pressure, t: ThermodynamicTemperature) -> SpecificHeatCapacity;
fn ha(&self, p: Pressure, t: ThermodynamicTemperature) -> AvailableEnergy;
fn hs(&self, p: Pressure, t: ThermodynamicTemperature) -> AvailableEnergy;
fn hc(&self) -> AvailableEnergy;
fn s(&self, p: Pressure, t: ThermodynamicTemperature) -> SpecificHeatCapacity;
fn cv(&self, p: Pressure, t: ThermodynamicTemperature) -> SpecificHeatCapacity {
self.cp(p, t) - self.cp_m_cv(p, t)
}
fn t_from_ha(
&self,
ha_target: AvailableEnergy,
p: Pressure,
t0: ThermodynamicTemperature,
) -> Result<ThermodynamicTemperature, ThermoError> {
newton_t(
|t| self.ha(p, ThermodynamicTemperature::new::<kelvin>(t)),
|t| self.cp(p, ThermodynamicTemperature::new::<kelvin>(t)),
ha_target.get::<joule_per_kilogram>(),
t0.get::<kelvin>(),
)
}
fn t_from_hs(
&self,
hs_target: AvailableEnergy,
p: Pressure,
t0: ThermodynamicTemperature,
) -> Result<ThermodynamicTemperature, ThermoError> {
let ha_target = hs_target + self.hc();
self.t_from_ha(ha_target, p, t0)
}
fn t_from_e(
&self,
e_target: AvailableEnergy,
p: Pressure,
t0: ThermodynamicTemperature,
) -> Result<ThermodynamicTemperature, ThermoError> {
newton_t(
|t| {
let tt = ThermodynamicTemperature::new::<kelvin>(t);
let rho = self.rho(p, tt);
let ea = self.ha(p, tt) - p / rho;
ea
},
|t| self.cv(p, ThermodynamicTemperature::new::<kelvin>(t)),
e_target.get::<joule_per_kilogram>(),
t0.get::<kelvin>(),
)
}
}
#[allow(non_snake_case)]
fn newton_t(
f: impl Fn(f64) -> AvailableEnergy,
dfdT: impl Fn(f64) -> SpecificHeatCapacity,
target: f64,
t0: f64,
) -> Result<ThermodynamicTemperature, ThermoError> {
const DTMAX: f64 = 500.0;
const MAX_ITER: usize = 50;
let mut t = t0.clamp(T_MIN, T_MAX);
for _ in 0..MAX_ITER {
let f_val = f(t).get::<joule_per_kilogram>();
let cp_val = dfdT(t).get::<joule_per_kilogram_kelvin>();
let dt = (-(f_val - target) / cp_val).clamp(-DTMAX, DTMAX);
t = (t + dt).clamp(T_MIN, T_MAX);
if dt.abs() / t < 1e-6 {
return Ok(ThermodynamicTemperature::new::<kelvin>(t));
}
}
Err(ThermoError::NonConvergent {
max_iter: MAX_ITER,
last_t: t,
})
}