use super::traits::TransportModel;
use crate::thermophysics::eos::EquationOfState;
use crate::thermophysics::imports::*;
use crate::thermophysics::thermo::ThermoModel;
#[derive(Debug, Clone)]
pub struct SutherlandTransport<T: ThermoModel> {
thermo: T,
as_: f64, ts: f64, }
impl<T: ThermoModel> SutherlandTransport<T> {
pub fn new(thermo: T, as_: f64, ts: f64) -> Self {
Self { thermo, as_, ts }
}
pub fn from_two_points(
thermo: T,
mu1: DynamicViscosity,
t1: ThermodynamicTemperature,
mu2: DynamicViscosity,
t2: ThermodynamicTemperature,
) -> Self {
let mu1 = mu1.get::<pascal_second>();
let mu2 = mu2.get::<pascal_second>();
let t1 = t1.get::<kelvin>();
let t2 = t2.get::<kelvin>();
let a = mu1 / t1.powf(1.5) - mu2 / t2.powf(1.5);
let b = mu2 / t2.sqrt() - mu1 / t1.sqrt();
let ts = b / a;
let as_ = mu1 * (1.0 + ts / t1) / t1.sqrt();
Self { thermo, as_, ts }
}
}
impl<T: ThermoModel> EquationOfState for SutherlandTransport<T> {
fn mol_weight(&self) -> MolarMass {
self.thermo.mol_weight()
}
fn r(&self) -> SpecificHeatCapacity {
self.thermo.r()
}
fn rho(&self, p: Pressure, t: ThermodynamicTemperature) -> MassDensity {
self.thermo.rho(p, t)
}
fn psi(&self, p: Pressure, t: ThermodynamicTemperature) -> Compressibility {
self.thermo.psi(p, t)
}
fn z(&self, p: Pressure, t: ThermodynamicTemperature) -> Ratio {
self.thermo.z(p, t)
}
fn cp_m_cv(&self, p: Pressure, t: ThermodynamicTemperature) -> SpecificHeatCapacity {
self.thermo.cp_m_cv(p, t)
}
fn cp_eos(&self, p: Pressure, t: ThermodynamicTemperature) -> SpecificHeatCapacity {
self.thermo.cp_eos(p, t)
}
fn h_eos(&self, p: Pressure, t: ThermodynamicTemperature) -> AvailableEnergy {
self.thermo.h_eos(p, t)
}
fn e_eos(&self, p: Pressure, t: ThermodynamicTemperature) -> AvailableEnergy {
self.thermo.e_eos(p, t)
}
fn s_eos(&self, p: Pressure, t: ThermodynamicTemperature) -> SpecificHeatCapacity {
self.thermo.s_eos(p, t)
}
}
impl<T: ThermoModel> ThermoModel for SutherlandTransport<T> {
fn cp(&self, p: Pressure, t: ThermodynamicTemperature) -> SpecificHeatCapacity {
self.thermo.cp(p, t)
}
fn ha(&self, p: Pressure, t: ThermodynamicTemperature) -> AvailableEnergy {
self.thermo.ha(p, t)
}
fn hs(&self, p: Pressure, t: ThermodynamicTemperature) -> AvailableEnergy {
self.thermo.hs(p, t)
}
fn hc(&self) -> AvailableEnergy {
self.thermo.hc()
}
fn s(&self, p: Pressure, t: ThermodynamicTemperature) -> SpecificHeatCapacity {
self.thermo.s(p, t)
}
}
impl<T: ThermoModel> TransportModel for SutherlandTransport<T> {
fn mu(&self, _p: Pressure, t: ThermodynamicTemperature) -> DynamicViscosity {
let tv = t.get::<kelvin>();
DynamicViscosity::new::<pascal_second>(self.as_ * tv.sqrt() / (1.0 + self.ts / tv))
}
fn kappa(&self, p: Pressure, t: ThermodynamicTemperature) -> ThermalConductivity {
let mu = self.mu(p, t).get::<pascal_second>();
let cv = self.thermo.cv(p, t).get::<joule_per_kilogram_kelvin>();
let r = self.thermo.r().get::<joule_per_kilogram_kelvin>();
ThermalConductivity::new::<watt_per_meter_kelvin>(mu * cv * (1.32 + 1.77 * r / cv))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::thermophysics::eos::PerfectGas;
use crate::thermophysics::thermo::HConstThermo;
use approx::assert_relative_eq;
use uom::si::available_energy::joule_per_kilogram;
use uom::si::molar_mass::gram_per_mole;
use uom::si::pressure::pascal;
use uom::si::specific_heat_capacity::joule_per_kilogram_kelvin;
use uom::si::thermodynamic_temperature::kelvin;
fn air_sutherland() -> SutherlandTransport<HConstThermo<PerfectGas>> {
let eos = PerfectGas::new(MolarMass::new::<gram_per_mole>(28.97));
let thermo = HConstThermo::new(
eos,
SpecificHeatCapacity::new::<joule_per_kilogram_kelvin>(1004.0),
AvailableEnergy::new::<joule_per_kilogram>(0.0),
ThermodynamicTemperature::new::<kelvin>(298.15),
AvailableEnergy::new::<joule_per_kilogram>(0.0),
);
SutherlandTransport::new(thermo, 1.458e-6, 110.4)
}
#[test]
fn mu_at_293k() {
let a = air_sutherland();
let p = Pressure::new::<pascal>(101_325.0);
let t = ThermodynamicTemperature::new::<kelvin>(293.0);
let mu = a.mu(p, t).get::<pascal_second>();
assert_relative_eq!(mu, 1.82e-5, epsilon = 2e-7);
}
#[test]
fn mu_increases_with_temperature() {
let a = air_sutherland();
let p = Pressure::new::<pascal>(101_325.0);
let t1 = ThermodynamicTemperature::new::<kelvin>(300.0);
let t2 = ThermodynamicTemperature::new::<kelvin>(600.0);
assert!(a.mu(p, t1).get::<pascal_second>() < a.mu(p, t2).get::<pascal_second>());
}
#[test]
fn two_point_reconstruction() {
let eos = PerfectGas::new(MolarMass::new::<gram_per_mole>(28.97));
let thermo = HConstThermo::new(
eos,
SpecificHeatCapacity::new::<joule_per_kilogram_kelvin>(1004.0),
AvailableEnergy::new::<joule_per_kilogram>(0.0),
ThermodynamicTemperature::new::<kelvin>(298.15),
AvailableEnergy::new::<joule_per_kilogram>(0.0),
);
let mu1_val = 1.716e-5_f64;
let t1_val = 273.11_f64;
let mu2_val = 1.987e-5_f64;
let t2_val = 373.11_f64;
let s = SutherlandTransport::from_two_points(
thermo,
DynamicViscosity::new::<pascal_second>(mu1_val),
ThermodynamicTemperature::new::<kelvin>(t1_val),
DynamicViscosity::new::<pascal_second>(mu2_val),
ThermodynamicTemperature::new::<kelvin>(t2_val),
);
let p = Pressure::new::<pascal>(101_325.0);
let got1 = s
.mu(p, ThermodynamicTemperature::new::<kelvin>(t1_val))
.get::<pascal_second>();
let got2 = s
.mu(p, ThermodynamicTemperature::new::<kelvin>(t2_val))
.get::<pascal_second>();
assert_relative_eq!(got1, mu1_val, epsilon = 1e-8);
assert_relative_eq!(got2, mu2_val, epsilon = 1e-8);
}
}