#![allow(clippy::excessive_precision)]
#![allow(clippy::needless_range_loop)]
use super::parameters::UVParameters;
use feos_core::{parameter::Parameter, EosError, EosResult, EquationOfState, HelmholtzEnergy};
use ndarray::Array1;
use std::f64::consts::FRAC_PI_6;
use std::sync::Arc;
pub(crate) mod attractive_perturbation_bh;
pub(crate) mod attractive_perturbation_uvb3;
pub(crate) mod attractive_perturbation_wca;
pub(crate) mod hard_sphere_bh;
pub(crate) mod hard_sphere_wca;
pub(crate) mod reference_perturbation_bh;
pub(crate) mod reference_perturbation_uvb3;
pub(crate) mod reference_perturbation_wca;
pub(crate) mod ufraction;
use attractive_perturbation_bh::AttractivePerturbationBH;
use attractive_perturbation_uvb3::AttractivePerturbationUVB3;
use attractive_perturbation_wca::AttractivePerturbationWCA;
use hard_sphere_bh::HardSphereBH;
use hard_sphere_wca::HardSphereWCA;
use reference_perturbation_bh::ReferencePerturbationBH;
use reference_perturbation_uvb3::ReferencePerturbationUVB3;
use reference_perturbation_wca::ReferencePerturbationWCA;
#[derive(Clone)]
#[cfg_attr(feature = "python", pyo3::pyclass)]
pub enum Perturbation {
BarkerHenderson,
WeeksChandlerAndersen,
}
#[derive(Clone)]
#[cfg_attr(feature = "python", pyo3::pyclass)]
pub enum VirialOrder {
Second,
Third,
}
#[derive(Clone)]
pub struct UVTheoryOptions {
pub max_eta: f64,
pub perturbation: Perturbation,
pub virial_order: VirialOrder,
}
impl Default for UVTheoryOptions {
fn default() -> Self {
Self {
max_eta: 0.5,
perturbation: Perturbation::WeeksChandlerAndersen,
virial_order: VirialOrder::Second,
}
}
}
pub struct UVTheory {
parameters: Arc<UVParameters>,
options: UVTheoryOptions,
contributions: Vec<Box<dyn HelmholtzEnergy>>,
}
impl UVTheory {
pub fn new(parameters: Arc<UVParameters>) -> EosResult<Self> {
Self::with_options(parameters, UVTheoryOptions::default())
}
pub fn with_options(
parameters: Arc<UVParameters>,
options: UVTheoryOptions,
) -> EosResult<Self> {
let mut contributions: Vec<Box<dyn HelmholtzEnergy>> = Vec::with_capacity(3);
match options.perturbation {
Perturbation::BarkerHenderson => match options.virial_order {
VirialOrder::Second => {
contributions.push(Box::new(HardSphereBH {
parameters: parameters.clone(),
}));
contributions.push(Box::new(ReferencePerturbationBH {
parameters: parameters.clone(),
}));
contributions.push(Box::new(AttractivePerturbationBH {
parameters: parameters.clone(),
}));
}
VirialOrder::Third => {
return Err(EosError::Error(
"Third virial coefficient is not implemented for Barker-Henderson"
.to_string(),
))
}
},
Perturbation::WeeksChandlerAndersen => {
contributions.push(Box::new(HardSphereWCA {
parameters: parameters.clone(),
}));
match options.virial_order {
VirialOrder::Second => {
contributions.push(Box::new(ReferencePerturbationWCA {
parameters: parameters.clone(),
}));
contributions.push(Box::new(AttractivePerturbationWCA {
parameters: parameters.clone(),
}));
}
VirialOrder::Third => {
if parameters.sigma.len() > 1 {
return Err(EosError::Error(
"Third virial coefficient is not implemented for mixtures!"
.to_string(),
));
}
if parameters.att[0] != 6.0 {
return Err(EosError::Error(
"Third virial coefficient is not implemented for attractive exponents other than 6!"
.to_string(),
));
}
contributions.push(Box::new(ReferencePerturbationUVB3 {
parameters: parameters.clone(),
}));
contributions.push(Box::new(AttractivePerturbationUVB3 {
parameters: parameters.clone(),
}));
}
}
}
}
Ok(Self {
parameters,
options,
contributions,
})
}
}
impl EquationOfState for UVTheory {
fn components(&self) -> usize {
self.parameters.pure_records.len()
}
fn subset(&self, component_list: &[usize]) -> Self {
Self::with_options(
Arc::new(self.parameters.subset(component_list)),
self.options.clone(),
)
.expect("Not defined for mixture")
}
fn compute_max_density(&self, moles: &Array1<f64>) -> f64 {
self.options.max_eta * moles.sum()
/ (FRAC_PI_6 * self.parameters.sigma.mapv(|v| v.powi(3)) * moles).sum()
}
fn residual(&self) -> &[Box<dyn HelmholtzEnergy>] {
&self.contributions
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::uvtheory::parameters::utils::test_parameters_mixture;
use crate::uvtheory::parameters::*;
use approx::assert_relative_eq;
use feos_core::parameter::{Identifier, Parameter, PureRecord};
use feos_core::{Contributions, State};
use ndarray::arr1;
use quantity::si::{ANGSTROM, KELVIN, MOL, NAV, RGAS};
#[test]
fn helmholtz_energy_pure_wca() -> EosResult<()> {
let sig = 3.7039;
let eps_k = 150.03;
let parameters = UVParameters::new_simple(24.0, 6.0, sig, eps_k);
let eos = Arc::new(UVTheory::new(Arc::new(parameters))?);
let reduced_temperature = 4.0;
let reduced_density = 1.0;
let temperature = reduced_temperature * eps_k * KELVIN;
let moles = arr1(&[2.0]) * MOL;
let volume = (sig * ANGSTROM).powi(3) / reduced_density * NAV * 2.0 * MOL;
let s = State::new_nvt(&eos, temperature, volume, &moles).unwrap();
let a = s
.molar_helmholtz_energy(Contributions::ResidualNvt)
.to_reduced(RGAS * temperature)
.unwrap();
assert_relative_eq!(a, 2.972986567516, max_relative = 1e-12); Ok(())
}
#[test]
fn helmholtz_energy_pure_bh() -> EosResult<()> {
let eps_k = 150.03;
let sig = 3.7039;
let rep = 24.0;
let att = 6.0;
let parameters = UVParameters::new_simple(rep, att, sig, eps_k);
let options = UVTheoryOptions {
max_eta: 0.5,
perturbation: Perturbation::BarkerHenderson,
virial_order: VirialOrder::Second,
};
let eos = Arc::new(UVTheory::with_options(Arc::new(parameters), options)?);
let reduced_temperature = 4.0;
let reduced_density = 1.0;
let temperature = reduced_temperature * eps_k * KELVIN;
let moles = arr1(&[2.0]) * MOL;
let volume = (sig * ANGSTROM).powi(3) / reduced_density * NAV * 2.0 * MOL;
let s = State::new_nvt(&eos, temperature, volume, &moles).unwrap();
let a = s
.molar_helmholtz_energy(Contributions::ResidualNvt)
.to_reduced(RGAS * temperature)
.unwrap();
assert_relative_eq!(a, 2.993577305779432, max_relative = 1e-12);
Ok(())
}
#[test]
fn helmholtz_energy_pure_uvb3() -> EosResult<()> {
let eps_k = 150.03;
let sig = 3.7039;
let rep = 12.0;
let att = 6.0;
let parameters = UVParameters::new_simple(rep, att, sig, eps_k);
let options = UVTheoryOptions {
max_eta: 0.5,
perturbation: Perturbation::WeeksChandlerAndersen,
virial_order: VirialOrder::Third,
};
let eos = Arc::new(UVTheory::with_options(Arc::new(parameters), options)?);
let reduced_temperature = 4.0;
let reduced_density = 0.5;
let temperature = reduced_temperature * eps_k * KELVIN;
let moles = arr1(&[2.0]) * MOL;
let volume = (sig * ANGSTROM).powi(3) / reduced_density * NAV * 2.0 * MOL;
let s = State::new_nvt(&eos, temperature, volume, &moles).unwrap();
let a = s
.molar_helmholtz_energy(Contributions::ResidualNvt)
.to_reduced(RGAS * temperature)
.unwrap();
dbg!(a);
assert_relative_eq!(a, 0.37659379124271003, max_relative = 1e-12);
Ok(())
}
#[test]
fn helmholtz_energy_mixtures_bh() -> EosResult<()> {
let rep1 = 24.0;
let eps_k1 = 150.03;
let sig1 = 3.7039;
let r1 = UVRecord::new(rep1, 6.0, sig1, eps_k1);
let i = Identifier::new(None, None, None, None, None, None);
let rep2 = 24.0;
let eps_k2 = 150.03;
let sig2 = 3.7039;
let r2 = UVRecord::new(rep2, 6.0, sig2, eps_k2);
let j = Identifier::new(None, None, None, None, None, None);
let pr1 = PureRecord::new(i, 1.0, r1, None);
let pr2 = PureRecord::new(j, 1.0, r2, None);
let pure_records = vec![pr1, pr2];
let uv_parameters = UVParameters::new_binary(pure_records, None);
let reduced_temperature = 4.0;
let eps_k_x = (eps_k1 + eps_k2) / 2.0; let t_x = reduced_temperature * eps_k_x * KELVIN;
let sig_x = (sig1 + sig2) / 2.0; let reduced_density = 1.0;
let moles = arr1(&[1.7, 0.3]) * MOL;
let total_moles = moles.sum();
let volume = (sig_x * ANGSTROM).powi(3) / reduced_density * NAV * total_moles;
let options = UVTheoryOptions {
max_eta: 0.5,
perturbation: Perturbation::BarkerHenderson,
virial_order: VirialOrder::Second,
};
let eos_bh = Arc::new(UVTheory::with_options(Arc::new(uv_parameters), options)?);
let state_bh = State::new_nvt(&eos_bh, t_x, volume, &moles).unwrap();
let a_bh = state_bh
.molar_helmholtz_energy(Contributions::ResidualNvt)
.to_reduced(RGAS * t_x)
.unwrap();
assert_relative_eq!(a_bh, 2.993577305779432, max_relative = 1e-12);
Ok(())
}
#[test]
fn helmholtz_energy_wca_mixture() -> EosResult<()> {
let p = test_parameters_mixture(
arr1(&[12.0, 12.0]),
arr1(&[6.0, 6.0]),
arr1(&[1.0, 1.0]),
arr1(&[1.0, 0.5]),
);
let reduced_temperature = 1.0;
let t_x = reduced_temperature * p.epsilon_k[0] * KELVIN;
let reduced_density = 0.9;
let moles = arr1(&[0.4, 0.6]) * MOL;
let total_moles = moles.sum();
let volume = (p.sigma[0] * ANGSTROM).powi(3) / reduced_density * NAV * total_moles;
let eos_wca = Arc::new(UVTheory::new(Arc::new(p))?);
let state_wca = State::new_nvt(&eos_wca, t_x, volume, &moles).unwrap();
let a_wca = state_wca
.molar_helmholtz_energy(Contributions::ResidualNvt)
.to_reduced(RGAS * t_x)
.unwrap();
assert_relative_eq!(a_wca, -0.597791038364405, max_relative = 1e-5);
Ok(())
}
#[test]
fn helmholtz_energy_wca_mixture_different_sigma() -> EosResult<()> {
let p = test_parameters_mixture(
arr1(&[12.0, 12.0]),
arr1(&[6.0, 6.0]),
arr1(&[1.0, 2.0]),
arr1(&[1.0, 0.5]),
);
let reduced_temperature = 1.5;
let t_x = reduced_temperature * p.epsilon_k[0] * KELVIN;
let sigma_x_3 = (0.4 + 0.6 * 8.0) * ANGSTROM.powi(3);
let density = 0.52000000000000002 / sigma_x_3;
let moles = arr1(&[0.4, 0.6]) * MOL;
let total_moles = moles.sum();
let volume = NAV * total_moles / density;
let eos_wca = Arc::new(UVTheory::new(Arc::new(p))?);
let state_wca = State::new_nvt(&eos_wca, t_x, volume, &moles).unwrap();
let a_wca = state_wca
.molar_helmholtz_energy(Contributions::ResidualNvt)
.to_reduced(RGAS * t_x)
.unwrap();
assert_relative_eq!(a_wca, -0.034206207363139396, max_relative = 1e-5);
Ok(())
}
}