use super::parameters::PetsParameters;
#[cfg(feature = "dft")]
use crate::hard_sphere::FMTVersion;
use crate::hard_sphere::{HardSphere, HardSphereProperties, MonomerShape};
use feos_core::{Molarweight, ResidualDyn, Subset};
use nalgebra::{DMatrix, DVector};
use num_dual::DualNum;
use quantity::MolarWeight;
use std::f64::consts::FRAC_PI_6;
pub(crate) mod dispersion;
#[derive(Copy, Clone)]
pub struct PetsOptions {
pub max_eta: f64,
#[cfg(feature = "dft")]
pub fmt_version: FMTVersion,
}
impl Default for PetsOptions {
fn default() -> Self {
Self {
max_eta: 0.5,
#[cfg(feature = "dft")]
fmt_version: FMTVersion::WhiteBear,
}
}
}
pub struct Pets {
pub parameters: PetsParameters,
pub options: PetsOptions,
pub sigma: DVector<f64>,
pub epsilon_k: DVector<f64>,
pub sigma_ij: DMatrix<f64>,
pub epsilon_k_ij: DMatrix<f64>,
}
impl Pets {
pub fn new(parameters: PetsParameters) -> Self {
Self::with_options(parameters, PetsOptions::default())
}
pub fn with_options(parameters: PetsParameters, options: PetsOptions) -> Self {
let [sigma, epsilon_k] = parameters.collate(|pr| [pr.sigma, pr.epsilon_k]);
let n = parameters.pure.len();
let mut sigma_ij = DMatrix::zeros(n, n);
let mut epsilon_k_ij = DMatrix::zeros(n, n);
let [k_ij] = parameters.collate_binary(|b| [b.k_ij]);
for i in 0..n {
for j in 0..n {
epsilon_k_ij[(i, j)] = (epsilon_k[i] * epsilon_k[j]).sqrt() * (1.0 - k_ij[(i, j)]);
sigma_ij[(i, j)] = 0.5 * (sigma[i] + sigma[j]);
}
}
Self {
parameters,
options,
sigma,
epsilon_k,
sigma_ij,
epsilon_k_ij,
}
}
}
impl Subset for Pets {
fn subset(&self, component_list: &[usize]) -> Self {
Self::with_options(self.parameters.subset(component_list), self.options)
}
}
impl ResidualDyn for Pets {
fn components(&self) -> usize {
self.parameters.pure.len()
}
fn compute_max_density<D: num_dual::DualNum<f64> + Copy>(&self, moles: &DVector<D>) -> D {
moles.sum() * self.options.max_eta
/ (self.sigma.map(|v| D::from(v.powi(3))).component_mul(moles)).sum()
/ FRAC_PI_6
}
fn reduced_helmholtz_energy_density_contributions<D: DualNum<f64> + Copy>(
&self,
state: &feos_core::StateHD<D>,
) -> Vec<(&'static str, D)> {
vec![
(
"Hard Sphere",
HardSphere.helmholtz_energy_density(self, state),
),
(
"Dispersion",
self.dispersion_helmholtz_energy_density(state),
),
]
}
}
impl Molarweight for Pets {
fn molar_weight(&self) -> MolarWeight<DVector<f64>> {
self.parameters.molar_weight.clone()
}
}
impl HardSphereProperties for Pets {
fn monomer_shape<N: DualNum<f64>>(&self, _: N) -> MonomerShape<'_, N> {
MonomerShape::Spherical(self.sigma.len())
}
fn hs_diameter<D: DualNum<f64> + Copy>(&self, temperature: D) -> DVector<D> {
let ti = temperature.recip() * -3.052785558;
DVector::from_fn(self.sigma.len(), |i, _| {
-((ti * self.epsilon_k[i]).exp() * 0.127112544 - 1.0) * self.sigma[i]
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::pets::parameters::utils::{
argon_krypton_parameters, argon_parameters, krypton_parameters,
};
use approx::assert_relative_eq;
use feos_core::{Contributions, PhaseEquilibrium, State, StateHD};
use nalgebra::dvector;
use quantity::{BAR, KELVIN, METER, MOL, RGAS};
use typenum::P3;
#[test]
fn ideal_gas_pressure() {
let e = &Pets::new(argon_parameters());
let t = 200.0 * KELVIN;
let v = 1e-3 * METER.powi::<P3>();
let n = dvector![1.0] * MOL;
let s = State::new_nvt(&e, t, v, &n).unwrap();
let p_ig = s.total_moles * RGAS * t / v;
assert_relative_eq!(s.pressure(Contributions::IdealGas), p_ig, epsilon = 1e-10);
assert_relative_eq!(
s.pressure(Contributions::IdealGas) + s.pressure(Contributions::Residual),
s.pressure(Contributions::Total),
epsilon = 1e-10
);
}
#[test]
fn hard_sphere_mix() {
let argon = Pets::new(argon_parameters());
let krypton = Pets::new(krypton_parameters());
let mix = Pets::new(argon_krypton_parameters());
let t = 250.0;
let v = 2.5e28;
let n = 1.0;
let s = StateHD::new(t, v, &dvector![n]);
let a1 = HardSphere.helmholtz_energy_density(&argon, &s);
let a2 = HardSphere.helmholtz_energy_density(&krypton, &s);
let s1m = StateHD::new(t, v, &dvector![n, 0.0]);
let a1m = HardSphere.helmholtz_energy_density(&mix, &s1m);
let s2m = StateHD::new(t, v, &dvector![0.0, n]);
let a2m = HardSphere.helmholtz_energy_density(&mix, &s2m);
assert_relative_eq!(a1, a1m, epsilon = 1e-14);
assert_relative_eq!(a2, a2m, epsilon = 1e-14);
}
#[test]
fn new_tpn() {
let e = &Pets::new(argon_parameters());
let t = 300.0 * KELVIN;
let p = BAR;
let m = dvector![1.0] * MOL;
let s = State::new_npt(&e, t, p, &m, None).unwrap();
let p_calc = s.pressure(Contributions::Total);
assert_relative_eq!(p, p_calc, epsilon = 1e-6);
}
#[test]
fn vle_pure_t() {
let e = &Pets::new(argon_parameters());
let t = 300.0 * KELVIN;
let vle = PhaseEquilibrium::pure(&e, t, None, Default::default());
if let Ok(v) = vle {
assert_relative_eq!(
v.vapor().pressure(Contributions::Total),
v.liquid().pressure(Contributions::Total),
epsilon = 1e-6
)
}
}
#[test]
fn mix_single() {
let e1 = &Pets::new(argon_parameters());
let e2 = &Pets::new(krypton_parameters());
let e12 = &Pets::new(argon_krypton_parameters());
let t = 300.0 * KELVIN;
let v = 0.02456883872966545 * METER.powi::<P3>();
let m1 = dvector![2.0] * MOL;
let m1m = dvector![2.0, 0.0] * MOL;
let m2m = dvector![0.0, 2.0] * MOL;
let s1 = State::new_nvt(&e1, t, v, &m1).unwrap();
let s2 = State::new_nvt(&e2, t, v, &m1).unwrap();
let s1m = State::new_nvt(&e12, t, v, &m1m).unwrap();
let s2m = State::new_nvt(&e12, t, v, &m2m).unwrap();
assert_relative_eq!(
s1.pressure(Contributions::Total),
s1m.pressure(Contributions::Total),
epsilon = 1e-12
);
assert_relative_eq!(
s2.pressure(Contributions::Total),
s2m.pressure(Contributions::Total),
epsilon = 1e-12
)
}
}