From 0e211dbb2b89f3a5ba173a0428b15ff7a192e33a Mon Sep 17 00:00:00 2001 From: Philipp Rehner Date: Tue, 28 Nov 2023 19:46:55 +0100 Subject: [PATCH 1/3] `EquationOfState` without residual model --- feos-core/src/equation_of_state/mod.rs | 13 ++++++++ feos-core/src/equation_of_state/residual.rs | 26 +++++++++++++++ feos-core/src/joback.rs | 25 +++------------ feos-derive/src/residual.rs | 30 ++++++++++++++---- src/eos.rs | 1 + src/python/eos.rs | 35 +++++++++++++++------ 6 files changed, 95 insertions(+), 35 deletions(-) diff --git a/feos-core/src/equation_of_state/mod.rs b/feos-core/src/equation_of_state/mod.rs index 619bc81a6..26aa92a19 100644 --- a/feos-core/src/equation_of_state/mod.rs +++ b/feos-core/src/equation_of_state/mod.rs @@ -11,6 +11,7 @@ mod residual; pub use helmholtz_energy::{HelmholtzEnergy, HelmholtzEnergyDual}; pub use ideal_gas::{DeBroglieWavelength, DeBroglieWavelengthDual, IdealGas}; +use residual::NoResidual; pub use residual::{EntropyScaling, Residual}; /// The number of components that the model is initialized for. @@ -42,6 +43,18 @@ impl EquationOfState { } } +impl EquationOfState { + /// Return a new [EquationOfState] that only consists of + /// an ideal gas models. + pub fn ideal_gas(ideal_gas: Arc) -> Self { + let residual = Arc::new(NoResidual(ideal_gas.components())); + Self { + ideal_gas, + residual, + } + } +} + impl Components for EquationOfState { fn components(&self) -> usize { assert_eq!( diff --git a/feos-core/src/equation_of_state/residual.rs b/feos-core/src/equation_of_state/residual.rs index 344f005eb..17a9686d2 100644 --- a/feos-core/src/equation_of_state/residual.rs +++ b/feos-core/src/equation_of_state/residual.rs @@ -178,3 +178,29 @@ pub trait EntropyScaling { ) -> EosResult; fn thermal_conductivity_correlation(&self, s_res: f64, x: &Array1) -> EosResult; } + +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 { + 1.0 + } + + fn contributions(&self) -> &[Box] { + &[] + } + + fn molar_weight(&self) -> MolarWeight> { + panic!("No mass specific properties are available for this model!") + } +} diff --git a/feos-core/src/joback.rs b/feos-core/src/joback.rs index c0b3c7013..e7d734a9d 100644 --- a/feos-core/src/joback.rs +++ b/feos-core/src/joback.rs @@ -241,7 +241,7 @@ const KB: f64 = 1.38064852e-23; #[cfg(test)] mod tests { use crate::si::*; - use crate::{Contributions, Residual, State, StateBuilder}; + use crate::{Contributions, EquationOfState, State, StateBuilder}; use approx::assert_relative_eq; use ndarray::arr1; use std::sync::Arc; @@ -249,23 +249,6 @@ mod tests { use super::*; - // implement Residual to test Joback as equation of state - impl Residual for Joback { - fn compute_max_density(&self, _moles: &Array1) -> f64 { - 1.0 - } - - fn contributions(&self) -> &[Box] { - &[] - } - - fn molar_weight(&self) -> MolarWeight> { - MolarWeight::from_shape_fn(self.components(), |i| { - self.parameters.pure_records[i].molarweight * GRAM / MOL - }) - } - } - #[test] fn paper_example() -> EosResult<()> { let segments_json = r#"[ @@ -351,7 +334,8 @@ mod tests { assert_relative_eq!(jr.e, 0.0); let pr = PureRecord::new(Identifier::default(), 1.0, jr); - let eos = Arc::new(Joback::new(Arc::new(JobackParameters::new_pure(pr)?))); + let joback = Arc::new(Joback::new(Arc::new(JobackParameters::new_pure(pr)?))); + let eos = Arc::new(EquationOfState::ideal_gas(joback)); let state = State::new_nvt( &eos, 1000.0 * KELVIN, @@ -383,10 +367,11 @@ mod tests { ); let parameters = Arc::new(JobackParameters::new_binary(vec![record1, record2], None)?); let joback = Arc::new(Joback::new(parameters)); + let eos = Arc::new(EquationOfState::ideal_gas(joback.clone())); let temperature = 300.0 * KELVIN; let volume = METER.powi::(); let moles = &arr1(&[1.0, 3.0]) * MOL; - let state = StateBuilder::new(&joback) + let state = StateBuilder::new(&eos) .temperature(temperature) .volume(volume) .moles(&moles) diff --git a/feos-derive/src/residual.rs b/feos-derive/src/residual.rs index f5dbbd133..60bd2c2cf 100644 --- a/feos-derive/src/residual.rs +++ b/feos-derive/src/residual.rs @@ -24,20 +24,38 @@ fn impl_residual( ) -> proc_macro2::TokenStream { let compute_max_density = variants.iter().map(|v| { let name = &v.ident; - quote! { - Self::#name(residual) => residual.compute_max_density(moles) + if name == "NoModel" { + quote! { + Self::#name(_) => 0.0 + } + } else { + quote! { + Self::#name(residual) => residual.compute_max_density(moles) + } } }); let contributions = variants.iter().map(|v| { let name = &v.ident; - quote! { - Self::#name(residual) => residual.contributions() + if name == "NoModel" { + quote! { + Self::#name(_) => &[] + } + } else { + quote! { + Self::#name(residual) => residual.contributions() + } } }); let molar_weight = variants.iter().map(|v| { let name = &v.ident; - quote! { - Self::#name(residual) => residual.molar_weight() + if name == "NoModel" { + quote! { + Self::#name(_) => panic!("OH NO") + } + } else { + quote! { + Self::#name(residual) => residual.molar_weight() + } } }); diff --git a/src/eos.rs b/src/eos.rs index 9820df1e2..1e186ab75 100644 --- a/src/eos.rs +++ b/src/eos.rs @@ -23,6 +23,7 @@ use ndarray::Array1; /// are undesirable (e.g. FFI). #[derive(Components, Residual)] pub enum ResidualModel { + NoModel(usize), #[cfg(feature = "pcsaft")] #[implement(entropy_scaling)] PcSaft(PcSaft), diff --git a/src/python/eos.rs b/src/python/eos.rs index 723779e0e..5e34bdd90 100644 --- a/src/python/eos.rs +++ b/src/python/eos.rs @@ -228,7 +228,7 @@ impl PyEquationOfState { #[cfg(feature = "uvtheory")] #[staticmethod] #[pyo3( - signature = (parameters, max_eta=0.5, perturbation=Perturbation::WeeksChandlerAndersen, virial_order=VirialOrder::Second), + signature = (parameters, max_eta=0.5, perturbation=Perturbation::WeeksChandlerAndersen, virial_order=VirialOrder::Second), text_signature = "(parameters, max_eta=0.5, perturbation, virial_order)" )] fn uvtheory( @@ -285,6 +285,18 @@ impl PyEquationOfState { Self(Arc::new(EquationOfState::new(ideal_gas, residual))) } + /// Equation of state that only contains an ideal gas contribution. + /// + /// Returns + /// ------- + /// EquationOfState + #[staticmethod] + fn ideal_gas() -> Self { + let residual = Arc::new(ResidualModel::NoModel(0)); + let ideal_gas = Arc::new(IdealGasModel::NoModel(0)); + Self(Arc::new(EquationOfState::new(ideal_gas, residual))) + } + /// Ideal gas equation of state from a Python class. /// /// Parameters @@ -297,11 +309,7 @@ impl PyEquationOfState { /// ------- /// EquationOfState fn python_ideal_gas(&self, ideal_gas: Py) -> PyResult { - let ig = Arc::new(IdealGasModel::Python(PyIdealGas::new(ideal_gas)?)); - Ok(Self(Arc::new(EquationOfState::new( - ig, - self.0.residual.clone(), - )))) + Ok(self.add_ideal_gas(IdealGasModel::Python(PyIdealGas::new(ideal_gas)?))) } /// Ideal gas model of Joback and Reid. @@ -315,10 +323,19 @@ impl PyEquationOfState { /// ------- /// EquationOfState fn joback(&self, parameters: PyJobackParameters) -> Self { - let ideal_gas = Arc::new(IdealGasModel::Joback(Joback::new(parameters.0))); + self.add_ideal_gas(IdealGasModel::Joback(Joback::new(parameters.0))) + } +} + +impl PyEquationOfState { + fn add_ideal_gas(&self, ideal_gas: IdealGasModel) -> Self { + let residual = match self.0.residual.as_ref() { + ResidualModel::NoModel(_) => Arc::new(ResidualModel::NoModel(ideal_gas.components())), + _ => self.0.residual.clone(), + }; Self(Arc::new(EquationOfState::new( - ideal_gas, - self.0.residual.clone(), + Arc::new(ideal_gas), + residual, ))) } } From 7d99b12bd4a4da99277d3dd028689428d550c8aa Mon Sep 17 00:00:00 2001 From: Philipp Rehner Date: Tue, 28 Nov 2023 19:54:20 +0100 Subject: [PATCH 2/3] add changelogs --- CHANGELOG.md | 2 ++ feos-core/CHANGELOG.md | 2 ++ 2 files changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 875e6058f..95e931490 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +### Added +- Added `EquationOfState.ideal_gas()` to initialize an equation of state that only consists of an ideal gas contribution. [#204](https://github.com/feos-org/feos/pull/204) ## [0.5.1] - 2023-11-23 - Python only: Release the changes introduced in `feos-core` 0.5.1. diff --git a/feos-core/CHANGELOG.md b/feos-core/CHANGELOG.md index a987dc6c2..3b28c5150 100644 --- a/feos-core/CHANGELOG.md +++ b/feos-core/CHANGELOG.md @@ -5,6 +5,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## Unreleased +### Added +- Added `EquationOfState::ideal_gas` to initialize an equation of state that only consists of an ideal gas contribution. [#204](https://github.com/feos-org/feos/pull/204) ## [0.5.1] - 2023-11-23 ### Fixed From a9849acfc70b80e4f45ffa1b6a4e9705a4b97176 Mon Sep 17 00:00:00 2001 From: Philipp Rehner Date: Tue, 28 Nov 2023 20:08:01 +0100 Subject: [PATCH 3/3] simplify implementation --- feos-core/src/equation_of_state/mod.rs | 3 +-- feos-core/src/equation_of_state/residual.rs | 1 + feos-core/src/lib.rs | 2 +- feos-derive/src/residual.rs | 30 +++++---------------- src/eos.rs | 2 +- src/python/eos.rs | 6 +++-- 6 files changed, 14 insertions(+), 30 deletions(-) diff --git a/feos-core/src/equation_of_state/mod.rs b/feos-core/src/equation_of_state/mod.rs index 26aa92a19..a1ac42a64 100644 --- a/feos-core/src/equation_of_state/mod.rs +++ b/feos-core/src/equation_of_state/mod.rs @@ -11,8 +11,7 @@ mod residual; pub use helmholtz_energy::{HelmholtzEnergy, HelmholtzEnergyDual}; pub use ideal_gas::{DeBroglieWavelength, DeBroglieWavelengthDual, IdealGas}; -use residual::NoResidual; -pub use residual::{EntropyScaling, Residual}; +pub use residual::{EntropyScaling, NoResidual, Residual}; /// The number of components that the model is initialized for. pub trait Components { diff --git a/feos-core/src/equation_of_state/residual.rs b/feos-core/src/equation_of_state/residual.rs index 17a9686d2..e8e2819e2 100644 --- a/feos-core/src/equation_of_state/residual.rs +++ b/feos-core/src/equation_of_state/residual.rs @@ -179,6 +179,7 @@ pub trait EntropyScaling { fn thermal_conductivity_correlation(&self, s_res: f64, x: &Array1) -> EosResult; } +/// Dummy implementation for [EquationOfState](super::EquationOfState)s that only contain an ideal gas contribution. pub struct NoResidual(pub usize); impl Components for NoResidual { diff --git a/feos-core/src/lib.rs b/feos-core/src/lib.rs index 4ce6fff6e..2a8158e19 100644 --- a/feos-core/src/lib.rs +++ b/feos-core/src/lib.rs @@ -34,7 +34,7 @@ pub mod si; mod state; pub use equation_of_state::{ Components, DeBroglieWavelength, DeBroglieWavelengthDual, EntropyScaling, EquationOfState, - HelmholtzEnergy, HelmholtzEnergyDual, IdealGas, Residual, + HelmholtzEnergy, HelmholtzEnergyDual, IdealGas, NoResidual, Residual, }; pub use errors::{EosError, EosResult}; pub use phase_equilibria::{ diff --git a/feos-derive/src/residual.rs b/feos-derive/src/residual.rs index 60bd2c2cf..f5dbbd133 100644 --- a/feos-derive/src/residual.rs +++ b/feos-derive/src/residual.rs @@ -24,38 +24,20 @@ fn impl_residual( ) -> proc_macro2::TokenStream { let compute_max_density = variants.iter().map(|v| { let name = &v.ident; - if name == "NoModel" { - quote! { - Self::#name(_) => 0.0 - } - } else { - quote! { - Self::#name(residual) => residual.compute_max_density(moles) - } + quote! { + Self::#name(residual) => residual.compute_max_density(moles) } }); let contributions = variants.iter().map(|v| { let name = &v.ident; - if name == "NoModel" { - quote! { - Self::#name(_) => &[] - } - } else { - quote! { - Self::#name(residual) => residual.contributions() - } + quote! { + Self::#name(residual) => residual.contributions() } }); let molar_weight = variants.iter().map(|v| { let name = &v.ident; - if name == "NoModel" { - quote! { - Self::#name(_) => panic!("OH NO") - } - } else { - quote! { - Self::#name(residual) => residual.molar_weight() - } + quote! { + Self::#name(residual) => residual.molar_weight() } }); diff --git a/src/eos.rs b/src/eos.rs index 1e186ab75..66a7242dd 100644 --- a/src/eos.rs +++ b/src/eos.rs @@ -23,7 +23,7 @@ use ndarray::Array1; /// are undesirable (e.g. FFI). #[derive(Components, Residual)] pub enum ResidualModel { - NoModel(usize), + NoResidual(NoResidual), #[cfg(feature = "pcsaft")] #[implement(entropy_scaling)] PcSaft(PcSaft), diff --git a/src/python/eos.rs b/src/python/eos.rs index 5e34bdd90..d4bae905f 100644 --- a/src/python/eos.rs +++ b/src/python/eos.rs @@ -292,7 +292,7 @@ impl PyEquationOfState { /// EquationOfState #[staticmethod] fn ideal_gas() -> Self { - let residual = Arc::new(ResidualModel::NoModel(0)); + let residual = Arc::new(ResidualModel::NoResidual(NoResidual(0))); let ideal_gas = Arc::new(IdealGasModel::NoModel(0)); Self(Arc::new(EquationOfState::new(ideal_gas, residual))) } @@ -330,7 +330,9 @@ impl PyEquationOfState { impl PyEquationOfState { fn add_ideal_gas(&self, ideal_gas: IdealGasModel) -> Self { let residual = match self.0.residual.as_ref() { - ResidualModel::NoModel(_) => Arc::new(ResidualModel::NoModel(ideal_gas.components())), + ResidualModel::NoResidual(_) => Arc::new(ResidualModel::NoResidual(NoResidual( + ideal_gas.components(), + ))), _ => self.0.residual.clone(), }; Self(Arc::new(EquationOfState::new(