use std::convert::Infallible;
use uom::{
ConstZero,
si::f64::{MassDensity, Pressure, SpecificHeatCapacity, ThermodynamicTemperature},
};
use crate::{
PropertyError, State,
fluid::Stateless,
units::{SpecificEnthalpy, SpecificEntropy, SpecificInternalEnergy, TemperatureDifference},
};
use super::{StateFrom, ThermodynamicProperties};
pub trait IncompressibleFluid {
fn specific_heat(&self) -> SpecificHeatCapacity;
fn reference_temperature(&self) -> ThermodynamicTemperature;
fn reference_density(&self) -> MassDensity;
fn reference_enthalpy(&self) -> SpecificEnthalpy {
SpecificEnthalpy::ZERO
}
fn reference_entropy(&self) -> SpecificEntropy {
SpecificEntropy::ZERO
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct Incompressible;
impl Incompressible {
#[must_use]
pub fn reference_state<F: IncompressibleFluid>(fluid: F) -> State<F> {
let temperature = fluid.reference_temperature();
let density = fluid.reference_density();
State {
temperature,
density,
fluid,
}
}
}
impl<F: IncompressibleFluid> ThermodynamicProperties<F> for Incompressible {
fn pressure(&self, _state: &State<F>) -> Result<Pressure, PropertyError> {
Err(PropertyError::NotImplemented {
property: "pressure",
context: Some(
"pressure is not a thermodynamic property of an incompressible liquid.".into(),
),
})
}
fn internal_energy(&self, state: &State<F>) -> Result<SpecificInternalEnergy, PropertyError> {
self.enthalpy(state)
}
fn enthalpy(&self, state: &State<F>) -> Result<SpecificEnthalpy, PropertyError> {
let c = state.fluid.specific_heat();
let t_ref = state.fluid.reference_temperature();
let h_ref = state.fluid.reference_enthalpy();
Ok(h_ref + c * state.temperature.minus(t_ref))
}
fn entropy(&self, state: &State<F>) -> Result<SpecificEntropy, PropertyError> {
let c = state.fluid.specific_heat();
let t_ref = state.fluid.reference_temperature();
let s_ref = state.fluid.reference_entropy();
Ok(s_ref + c * (state.temperature / t_ref).ln())
}
fn cp(&self, state: &State<F>) -> Result<SpecificHeatCapacity, PropertyError> {
Ok(state.fluid.specific_heat())
}
fn cv(&self, state: &State<F>) -> Result<SpecificHeatCapacity, PropertyError> {
Ok(state.fluid.specific_heat())
}
}
impl<F: IncompressibleFluid + Stateless> StateFrom<F, ThermodynamicTemperature> for Incompressible {
type Error = Infallible;
fn state_from(&self, temperature: ThermodynamicTemperature) -> Result<State<F>, Self::Error> {
let fluid = F::default();
let density = fluid.reference_density();
Ok(State {
temperature,
density,
fluid,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_relative_eq;
use uom::si::{
f64::{MassDensity, ThermodynamicTemperature},
mass_density::kilogram_per_cubic_meter,
specific_heat_capacity::kilojoule_per_kilogram_degree_celsius,
thermodynamic_temperature::degree_celsius,
};
use crate::{fluid::Stateless, units::TemperatureDifference};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
struct MockLiquid;
impl Stateless for MockLiquid {}
impl IncompressibleFluid for MockLiquid {
fn specific_heat(&self) -> SpecificHeatCapacity {
SpecificHeatCapacity::new::<kilojoule_per_kilogram_degree_celsius>(10.0)
}
fn reference_temperature(&self) -> ThermodynamicTemperature {
ThermodynamicTemperature::new::<degree_celsius>(25.0)
}
fn reference_density(&self) -> MassDensity {
MassDensity::new::<kilogram_per_cubic_meter>(1.0)
}
}
#[test]
fn pressure_not_implemented() {
let state = Incompressible::reference_state(MockLiquid);
assert!(Incompressible.pressure(&state).is_err());
}
#[test]
fn internal_energy_equals_enthalpy() -> Result<(), PropertyError> {
let state: State<MockLiquid> = Incompressible
.state_from(ThermodynamicTemperature::new::<degree_celsius>(15.0))
.unwrap();
let u = Incompressible.internal_energy(&state)?;
let h = Incompressible.enthalpy(&state)?;
assert_eq!(u, h);
Ok(())
}
#[test]
fn increase_temperature() -> Result<(), PropertyError> {
let state_a: State<MockLiquid> = Incompressible
.state_from((
ThermodynamicTemperature::new::<degree_celsius>(30.0),
MassDensity::new::<kilogram_per_cubic_meter>(2.0),
))
.unwrap();
let state_b =
state_a.with_temperature(ThermodynamicTemperature::new::<degree_celsius>(60.0));
let h_a = Incompressible.enthalpy(&state_a)?;
let h_b = Incompressible.enthalpy(&state_b)?;
let c = state_a.fluid.specific_heat();
assert_relative_eq!(
(h_b - h_a).value,
(c * state_b.temperature.minus(state_a.temperature)).value,
);
let s_a = Incompressible.entropy(&state_a)?;
let s_b = Incompressible.entropy(&state_b)?;
assert_relative_eq!(
(s_b - s_a).value,
(c * (state_b.temperature / state_a.temperature).ln()).value,
epsilon = 1e-10,
);
Ok(())
}
}