use super::{Components, HelmholtzEnergy, HelmholtzEnergyDual};
use crate::si::*;
use crate::StateHD;
use crate::{EosError, EosResult};
use ndarray::prelude::*;
use num_dual::*;
use num_traits::{One, Zero};
use std::ops::Div;
pub trait Residual: Components + Send + Sync {
fn compute_max_density(&self, moles: &Array1<f64>) -> f64;
fn contributions(&self) -> &[Box<dyn HelmholtzEnergy>];
fn molar_weight(&self) -> MolarWeight<Array1<f64>>;
fn evaluate_residual<D: DualNum<f64> + Copy>(&self, state: &StateHD<D>) -> D
where
dyn HelmholtzEnergy: HelmholtzEnergyDual<D>,
{
self.contributions()
.iter()
.map(|c| c.helmholtz_energy(state))
.sum()
}
fn evaluate_residual_contributions<D: DualNum<f64> + Copy>(
&self,
state: &StateHD<D>,
) -> Vec<(String, D)>
where
dyn HelmholtzEnergy: HelmholtzEnergyDual<D>,
{
self.contributions()
.iter()
.map(|c| (c.to_string(), c.helmholtz_energy(state)))
.collect()
}
fn validate_moles(&self, moles: Option<&Moles<Array1<f64>>>) -> EosResult<Moles<Array1<f64>>> {
let l = moles.map_or(1, |m| m.len());
if self.components() == l {
match moles {
Some(m) => Ok(m.to_owned()),
None => Ok(Moles::from_reduced(Array::ones(1))),
}
} else {
Err(EosError::IncompatibleComponents(self.components(), l))
}
}
fn max_density(&self, moles: Option<&Moles<Array1<f64>>>) -> EosResult<Density> {
let mr = self.validate_moles(moles)?.to_reduced();
Ok(Density::from_reduced(self.compute_max_density(&mr)))
}
fn second_virial_coefficient(
&self,
temperature: Temperature,
moles: Option<&Moles<Array1<f64>>>,
) -> EosResult<<f64 as Div<Density>>::Output> {
let mr = self.validate_moles(moles)?;
let x = (&mr / mr.sum()).into_value();
let mut rho = HyperDual64::zero();
rho.eps1 = 1.0;
rho.eps2 = 1.0;
let t = HyperDual64::from(temperature.to_reduced());
let s = StateHD::new_virial(t, rho, x);
Ok(Quantity::from_reduced(
self.evaluate_residual(&s).eps1eps2 * 0.5,
))
}
#[allow(clippy::type_complexity)]
fn third_virial_coefficient(
&self,
temperature: Temperature,
moles: Option<&Moles<Array1<f64>>>,
) -> EosResult<<<f64 as Div<Density>>::Output as Div<Density>>::Output> {
let mr = self.validate_moles(moles)?;
let x = (&mr / mr.sum()).into_value();
let rho = Dual3_64::zero().derivative();
let t = Dual3_64::from(temperature.to_reduced());
let s = StateHD::new_virial(t, rho, x);
Ok(Quantity::from_reduced(self.evaluate_residual(&s).v3 / 3.0))
}
#[allow(clippy::type_complexity)]
fn second_virial_coefficient_temperature_derivative(
&self,
temperature: Temperature,
moles: Option<&Moles<Array1<f64>>>,
) -> EosResult<<<f64 as Div<Density>>::Output as Div<Temperature>>::Output> {
let mr = self.validate_moles(moles)?;
let x = (&mr / mr.sum()).into_value();
let mut rho = HyperDual::zero();
rho.eps1 = Dual64::one();
rho.eps2 = Dual64::one();
let t = HyperDual::from_re(Dual64::from(temperature.to_reduced()).derivative());
let s = StateHD::new_virial(t, rho, x);
Ok(Quantity::from_reduced(
self.evaluate_residual(&s).eps1eps2.eps * 0.5,
))
}
#[allow(clippy::type_complexity)]
fn third_virial_coefficient_temperature_derivative(
&self,
temperature: Temperature,
moles: Option<&Moles<Array1<f64>>>,
) -> EosResult<
<<<f64 as Div<Density>>::Output as Div<Density>>::Output as Div<Temperature>>::Output,
> {
let mr = self.validate_moles(moles)?;
let x = (&mr / mr.sum()).into_value();
let rho = Dual3::zero().derivative();
let t = Dual3::from_re(Dual64::from(temperature.to_reduced()).derivative());
let s = StateHD::new_virial(t, rho, x);
Ok(Quantity::from_reduced(
self.evaluate_residual(&s).v3.eps / 3.0,
))
}
}
pub trait EntropyScaling {
fn viscosity_reference(
&self,
temperature: Temperature,
volume: Volume,
moles: &Moles<Array1<f64>>,
) -> EosResult<Viscosity>;
fn viscosity_correlation(&self, s_res: f64, x: &Array1<f64>) -> EosResult<f64>;
fn diffusion_reference(
&self,
temperature: Temperature,
volume: Volume,
moles: &Moles<Array1<f64>>,
) -> EosResult<Diffusivity>;
fn diffusion_correlation(&self, s_res: f64, x: &Array1<f64>) -> EosResult<f64>;
fn thermal_conductivity_reference(
&self,
temperature: Temperature,
volume: Volume,
moles: &Moles<Array1<f64>>,
) -> EosResult<ThermalConductivity>;
fn thermal_conductivity_correlation(&self, s_res: f64, x: &Array1<f64>) -> EosResult<f64>;
}
pub struct NoResidual(pub usize);
impl Components for NoResidual {
fn components(&self) -> usize {
self.0
}
fn subset(&self, component_list: &[usize]) -> Self {
Self(component_list.len())
}
}
impl Residual for NoResidual {
fn compute_max_density(&self, _: &Array1<f64>) -> f64 {
1.0
}
fn contributions(&self) -> &[Box<dyn HelmholtzEnergy>] {
&[]
}
fn molar_weight(&self) -> MolarWeight<Array1<f64>> {
panic!("No mass specific properties are available for this model!")
}
}