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 diff --git a/feos-core/src/equation_of_state/mod.rs b/feos-core/src/equation_of_state/mod.rs index 619bc81a6..a1ac42a64 100644 --- a/feos-core/src/equation_of_state/mod.rs +++ b/feos-core/src/equation_of_state/mod.rs @@ -11,7 +11,7 @@ mod residual; pub use helmholtz_energy::{HelmholtzEnergy, HelmholtzEnergyDual}; pub use ideal_gas::{DeBroglieWavelength, DeBroglieWavelengthDual, IdealGas}; -pub use residual::{EntropyScaling, Residual}; +pub use residual::{EntropyScaling, NoResidual, Residual}; /// The number of components that the model is initialized for. pub trait Components { @@ -42,6 +42,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..e8e2819e2 100644 --- a/feos-core/src/equation_of_state/residual.rs +++ b/feos-core/src/equation_of_state/residual.rs @@ -178,3 +178,30 @@ pub trait EntropyScaling { ) -> EosResult; 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 { + 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-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/src/eos.rs b/src/eos.rs index 9820df1e2..66a7242dd 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 { + NoResidual(NoResidual), #[cfg(feature = "pcsaft")] #[implement(entropy_scaling)] PcSaft(PcSaft), diff --git a/src/python/eos.rs b/src/python/eos.rs index 723779e0e..d4bae905f 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::NoResidual(NoResidual(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,21 @@ 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::NoResidual(_) => Arc::new(ResidualModel::NoResidual(NoResidual( + ideal_gas.components(), + ))), + _ => self.0.residual.clone(), + }; Self(Arc::new(EquationOfState::new( - ideal_gas, - self.0.residual.clone(), + Arc::new(ideal_gas), + residual, ))) } }