From e21f0ff998723c05d45ac0585edcd982c09e1ff1 Mon Sep 17 00:00:00 2001 From: Philipp Rehner Date: Sun, 24 Apr 2022 18:07:11 +0200 Subject: [PATCH 1/3] Add the gc PC-SAFT eos and functional --- Cargo.toml | 9 ++-- src/dft.rs | 135 +++++++++++++++++++++++++++++++++-------------- src/eos.rs | 68 +++++++++++++++++++++--- src/gc_pcsaft.rs | 21 ++++++++ src/lib.rs | 6 +++ 5 files changed, 188 insertions(+), 51 deletions(-) create mode 100644 src/gc_pcsaft.rs diff --git a/Cargo.toml b/Cargo.toml index 7bdd7f9b1..533723993 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,11 +16,12 @@ categories = ["science"] crate-type = ["cdylib"] [dependencies] -quantity = { version = "0.5", features = ["python"] } -feos-core = { git = "https://github.com/feos-org/feos-core", features = ["python"] } -feos-dft = { git = "https://github.com/feos-org/feos-dft", features = ["python"], branch = "v0.2.0" } +quantity = "0.5" +feos-core = "0.2" +feos-dft = "0.2" feos-pcsaft = { git = "https://github.com/feos-org/feos-pcsaft", features = ["python"] } -feos-pets = { git = "https://github.com/feos-org/feos-pets", features = ["python"], branch = "new_dft_struct" } +feos-gc-pcsaft = { version = "0.1", features = ["python"] } +feos-pets = { git = "https://github.com/feos-org/feos-pets", features = ["python"] } numpy = { version = "0.16" } ndarray = { version = "0.15", features=["approx"] } diff --git a/src/dft.rs b/src/dft.rs index 976a82ef0..819c81948 100644 --- a/src/dft.rs +++ b/src/dft.rs @@ -5,6 +5,8 @@ use feos_dft::interface::*; use feos_dft::python::*; use feos_dft::solvation::*; use feos_dft::*; +use feos_gc_pcsaft::python::PyGcPcSaftFunctionalParameters; +use feos_gc_pcsaft::{GcPcSaftFunctional, GcPcSaftOptions}; use feos_pcsaft::python::PyPcSaftParameters; use feos_pcsaft::{PcSaftFunctional, PcSaftOptions}; use feos_pets::python::PyPetsParameters; @@ -20,75 +22,79 @@ use std::collections::HashMap; use std::rc::Rc; pub enum FunctionalVariant { - PcSaftFunctional(PcSaftFunctional), - PetsFunctional(PetsFunctional), - FMTFunctional(FMTFunctional), + PcSaft(PcSaftFunctional), + GcPcSaft(GcPcSaftFunctional), + Pets(PetsFunctional), + Fmt(FMTFunctional), } impl From for FunctionalVariant { fn from(f: PcSaftFunctional) -> Self { - Self::PcSaftFunctional(f) + Self::PcSaft(f) + } +} + +impl From for FunctionalVariant { + fn from(f: GcPcSaftFunctional) -> Self { + Self::GcPcSaft(f) } } impl From for FunctionalVariant { fn from(f: PetsFunctional) -> Self { - Self::PetsFunctional(f) + Self::Pets(f) } } impl From for FunctionalVariant { fn from(f: FMTFunctional) -> Self { - Self::FMTFunctional(f) + Self::Fmt(f) } } impl HelmholtzEnergyFunctional for FunctionalVariant { fn subset(&self, component_list: &[usize]) -> DFT { match self { - FunctionalVariant::PcSaftFunctional(functional) => { - functional.subset(component_list).into() - } - FunctionalVariant::PetsFunctional(functional) => { - functional.subset(component_list).into() - } - FunctionalVariant::FMTFunctional(functional) => { - functional.subset(component_list).into() - } + FunctionalVariant::PcSaft(functional) => functional.subset(component_list).into(), + FunctionalVariant::GcPcSaft(functional) => functional.subset(component_list).into(), + FunctionalVariant::Pets(functional) => functional.subset(component_list).into(), + FunctionalVariant::Fmt(functional) => functional.subset(component_list).into(), } } fn molecule_shape(&self) -> MoleculeShape { match self { - FunctionalVariant::PcSaftFunctional(functional) => functional.molecule_shape(), - FunctionalVariant::PetsFunctional(functional) => functional.molecule_shape(), - FunctionalVariant::FMTFunctional(functional) => functional.molecule_shape(), + FunctionalVariant::PcSaft(functional) => functional.molecule_shape(), + FunctionalVariant::GcPcSaft(functional) => functional.molecule_shape(), + FunctionalVariant::Pets(functional) => functional.molecule_shape(), + FunctionalVariant::Fmt(functional) => functional.molecule_shape(), } } fn compute_max_density(&self, moles: &Array1) -> f64 { match self { - FunctionalVariant::PcSaftFunctional(functional) => { - functional.compute_max_density(moles) - } - FunctionalVariant::PetsFunctional(functional) => functional.compute_max_density(moles), - FunctionalVariant::FMTFunctional(functional) => functional.compute_max_density(moles), + FunctionalVariant::PcSaft(functional) => functional.compute_max_density(moles), + FunctionalVariant::GcPcSaft(functional) => functional.compute_max_density(moles), + FunctionalVariant::Pets(functional) => functional.compute_max_density(moles), + FunctionalVariant::Fmt(functional) => functional.compute_max_density(moles), } } fn contributions(&self) -> &[Box] { match self { - FunctionalVariant::PcSaftFunctional(functional) => functional.contributions(), - FunctionalVariant::PetsFunctional(functional) => functional.contributions(), - FunctionalVariant::FMTFunctional(functional) => functional.contributions(), + FunctionalVariant::PcSaft(functional) => functional.contributions(), + FunctionalVariant::GcPcSaft(functional) => functional.contributions(), + FunctionalVariant::Pets(functional) => functional.contributions(), + FunctionalVariant::Fmt(functional) => functional.contributions(), } } fn ideal_gas(&self) -> &dyn IdealGasContribution { match self { - FunctionalVariant::PcSaftFunctional(functional) => functional.ideal_gas(), - FunctionalVariant::PetsFunctional(functional) => functional.ideal_gas(), - FunctionalVariant::FMTFunctional(functional) => functional.ideal_gas(), + FunctionalVariant::PcSaft(functional) => functional.ideal_gas(), + FunctionalVariant::GcPcSaft(functional) => functional.ideal_gas(), + FunctionalVariant::Pets(functional) => functional.ideal_gas(), + FunctionalVariant::Fmt(functional) => functional.ideal_gas(), } } } @@ -96,8 +102,9 @@ impl HelmholtzEnergyFunctional for FunctionalVariant { impl MolarWeight for FunctionalVariant { fn molar_weight(&self) -> SIArray1 { match self { - FunctionalVariant::PcSaftFunctional(functional) => functional.molar_weight(), - FunctionalVariant::PetsFunctional(functional) => functional.molar_weight(), + FunctionalVariant::PcSaft(functional) => functional.molar_weight(), + FunctionalVariant::GcPcSaft(functional) => functional.molar_weight(), + FunctionalVariant::Pets(functional) => functional.molar_weight(), _ => unimplemented!(), } } @@ -106,17 +113,19 @@ impl MolarWeight for FunctionalVariant { impl FluidParameters for FunctionalVariant { fn epsilon_k_ff(&self) -> Array1 { match self { - FunctionalVariant::PcSaftFunctional(functional) => functional.epsilon_k_ff(), - FunctionalVariant::PetsFunctional(functional) => functional.epsilon_k_ff(), - FunctionalVariant::FMTFunctional(functional) => functional.epsilon_k_ff(), + FunctionalVariant::PcSaft(functional) => functional.epsilon_k_ff(), + FunctionalVariant::GcPcSaft(functional) => functional.epsilon_k_ff(), + FunctionalVariant::Pets(functional) => functional.epsilon_k_ff(), + FunctionalVariant::Fmt(functional) => functional.epsilon_k_ff(), } } fn sigma_ff(&self) -> &Array1 { match self { - FunctionalVariant::PcSaftFunctional(functional) => functional.sigma_ff(), - FunctionalVariant::PetsFunctional(functional) => functional.sigma_ff(), - FunctionalVariant::FMTFunctional(functional) => functional.sigma_ff(), + FunctionalVariant::PcSaft(functional) => functional.sigma_ff(), + FunctionalVariant::GcPcSaft(functional) => functional.sigma_ff(), + FunctionalVariant::Pets(functional) => functional.sigma_ff(), + FunctionalVariant::Fmt(functional) => functional.sigma_ff(), } } } @@ -124,9 +133,10 @@ impl FluidParameters for FunctionalVariant { impl PairPotential for FunctionalVariant { fn pair_potential(&self, r: &Array1) -> Array2 { match self { - FunctionalVariant::PcSaftFunctional(functional) => functional.pair_potential(r), - FunctionalVariant::PetsFunctional(functional) => functional.pair_potential(r), - FunctionalVariant::FMTFunctional(functional) => functional.pair_potential(r), + FunctionalVariant::PcSaft(functional) => functional.pair_potential(r), + FunctionalVariant::Pets(functional) => functional.pair_potential(r), + FunctionalVariant::Fmt(functional) => functional.pair_potential(r), + _ => unimplemented!(), } } } @@ -187,6 +197,51 @@ impl PyFunctionalVariant { )) } + /// (heterosegmented) group contribution PC-SAFT Helmholtz energy functional. + /// + /// Parameters + /// ---------- + /// parameters: GcPcSaftFunctionalParameters + /// The set of PC-SAFT parameters. + /// fmt_version: FMTVersion, optional + /// The specific variant of the FMT term. Defaults to FMTVersion.WhiteBear + /// max_eta : float, optional + /// Maximum packing fraction. Defaults to 0.5. + /// max_iter_cross_assoc : unsigned integer, optional + /// Maximum number of iterations for cross association. Defaults to 50. + /// tol_cross_assoc : float + /// Tolerance for convergence of cross association. Defaults to 1e-10. + /// + /// Returns + /// ------- + /// Functional + #[args( + fmt_version = "FMTVersion::WhiteBear", + max_eta = "0.5", + max_iter_cross_assoc = "50", + tol_cross_assoc = "1e-10" + )] + #[staticmethod] + #[pyo3( + text_signature = "(parameters, fmt_version, max_eta, max_iter_cross_assoc, tol_cross_assoc)" + )] + fn gc_csaft( + parameters: PyGcPcSaftFunctionalParameters, + fmt_version: FMTVersion, + max_eta: f64, + max_iter_cross_assoc: usize, + tol_cross_assoc: f64, + ) -> Self { + let options = GcPcSaftOptions { + max_eta, + max_iter_cross_assoc, + tol_cross_assoc, + }; + Self(Rc::new( + GcPcSaftFunctional::with_options(parameters.0, fmt_version, options).into(), + )) + } + /// PeTS Helmholtz energy functional without simplifications /// for pure components. /// diff --git a/src/eos.rs b/src/eos.rs index 65199f638..4017528f7 100644 --- a/src/eos.rs +++ b/src/eos.rs @@ -2,6 +2,8 @@ use feos_core::cubic::PengRobinson; use feos_core::python::cubic::PyPengRobinsonParameters; use feos_core::python::user_defined::PyEoSObj; use feos_core::*; +use feos_gc_pcsaft::python::PyGcPcSaftEosParameters; +use feos_gc_pcsaft::{GcPcSaft, GcPcSaftOptions}; use feos_pcsaft::python::PyPcSaftParameters; use feos_pcsaft::{PcSaft, PcSaftOptions}; use feos_pets::python::PyPetsParameters; @@ -18,6 +20,7 @@ use std::rc::Rc; pub enum EosVariant { PcSaft(PcSaft), + GcPcSaft(GcPcSaft), PengRobinson(PengRobinson), Python(PyEoSObj), Pets(Pets), @@ -27,6 +30,7 @@ impl EquationOfState for EosVariant { fn components(&self) -> usize { match self { EosVariant::PcSaft(eos) => eos.components(), + EosVariant::GcPcSaft(eos) => eos.components(), EosVariant::PengRobinson(eos) => eos.components(), EosVariant::Python(eos) => eos.components(), EosVariant::Pets(eos) => eos.components(), @@ -36,6 +40,7 @@ impl EquationOfState for EosVariant { fn compute_max_density(&self, moles: &Array1) -> f64 { match self { EosVariant::PcSaft(eos) => eos.compute_max_density(moles), + EosVariant::GcPcSaft(eos) => eos.compute_max_density(moles), EosVariant::PengRobinson(eos) => eos.compute_max_density(moles), EosVariant::Python(eos) => eos.compute_max_density(moles), EosVariant::Pets(eos) => eos.compute_max_density(moles), @@ -45,6 +50,7 @@ impl EquationOfState for EosVariant { fn subset(&self, component_list: &[usize]) -> Self { match self { EosVariant::PcSaft(eos) => Self::PcSaft(eos.subset(component_list)), + EosVariant::GcPcSaft(eos) => Self::GcPcSaft(eos.subset(component_list)), EosVariant::PengRobinson(eos) => Self::PengRobinson(eos.subset(component_list)), EosVariant::Python(eos) => Self::Python(eos.subset(component_list)), EosVariant::Pets(eos) => Self::Pets(eos.subset(component_list)), @@ -54,6 +60,7 @@ impl EquationOfState for EosVariant { fn residual(&self) -> &[Box] { match self { EosVariant::PcSaft(eos) => eos.residual(), + EosVariant::GcPcSaft(eos) => eos.residual(), EosVariant::PengRobinson(eos) => eos.residual(), EosVariant::Python(eos) => eos.residual(), EosVariant::Pets(eos) => eos.residual(), @@ -65,6 +72,7 @@ impl MolarWeight for EosVariant { fn molar_weight(&self) -> SIArray1 { match self { EosVariant::PcSaft(eos) => eos.molar_weight(), + EosVariant::GcPcSaft(eos) => eos.molar_weight(), EosVariant::PengRobinson(eos) => eos.molar_weight(), EosVariant::Python(eos) => eos.molar_weight(), EosVariant::Pets(eos) => eos.molar_weight(), @@ -118,7 +126,9 @@ impl EntropyScaling for EosVariant { moles: &SIArray1, ) -> EosResult { match self { - EosVariant::PcSaft(eos) => eos.thermal_conductivity_reference(temperature, volume, moles), + EosVariant::PcSaft(eos) => { + eos.thermal_conductivity_reference(temperature, volume, moles) + } _ => unimplemented!(), } } @@ -164,7 +174,9 @@ impl PyEosVariant { dq_variant = "\"dq35\"" )] #[staticmethod] - #[pyo3(text_signature = "(parameters, max_eta, max_iter_cross_assoc, tol_cross_assoc, dq_variant)")] + #[pyo3( + text_signature = "(parameters, max_eta, max_iter_cross_assoc, tol_cross_assoc, dq_variant)" + )] pub fn pcsaft( parameters: PyPcSaftParameters, max_eta: f64, @@ -179,7 +191,49 @@ impl PyEosVariant { dq_variant: dq_variant.into(), }; Self(Rc::new(EosVariant::PcSaft(PcSaft::with_options( - parameters.0.clone(), + parameters.0, + options, + )))) + } + + /// Initialize the (heterosegmented) group contribution PC-SAFT equation of state. + /// + /// Parameters + /// ---------- + /// parameters : GcPcSaftEosParameters + /// The parameters of the PC-Saft equation of state to use. + /// max_eta : float, optional + /// Maximum packing fraction. Defaults to 0.5. + /// max_iter_cross_assoc : unsigned integer, optional + /// Maximum number of iterations for cross association. Defaults to 50. + /// tol_cross_assoc : float + /// Tolerance for convergence of cross association. Defaults to 1e-10. + /// + /// Returns + /// ------- + /// EquationOfState + /// The gc-PC-SAFT equation of state that can be used to compute thermodynamic + /// states. + #[args( + max_eta = "0.5", + max_iter_cross_assoc = "50", + tol_cross_assoc = "1e-10" + )] + #[staticmethod] + #[pyo3(text_signature = "(parameters, max_eta, max_iter_cross_assoc, tol_cross_assoc)")] + pub fn gc_pcsaft( + parameters: PyGcPcSaftEosParameters, + max_eta: f64, + max_iter_cross_assoc: usize, + tol_cross_assoc: f64, + ) -> Self { + let options = GcPcSaftOptions { + max_eta, + max_iter_cross_assoc, + tol_cross_assoc, + }; + Self(Rc::new(EosVariant::GcPcSaft(GcPcSaft::with_options( + parameters.0, options, )))) } @@ -200,18 +254,18 @@ impl PyEosVariant { #[pyo3(text_signature = "(parameters)")] pub fn peng_robinson(parameters: PyPengRobinsonParameters) -> Self { Self(Rc::new(EosVariant::PengRobinson(PengRobinson::new( - parameters.0.clone(), + parameters.0, )))) } /// Equation of state from a Python class. - /// + /// /// Parameters /// ---------- /// obj : Class /// A python class implementing the necessary methods /// to be used as equation of state. - /// + /// /// Returns /// ------- /// EquationOfState @@ -241,7 +295,7 @@ impl PyEosVariant { fn pets(parameters: PyPetsParameters, max_eta: f64) -> Self { let options = PetsOptions { max_eta }; Self(Rc::new(EosVariant::Pets(Pets::with_options( - parameters.0.clone(), + parameters.0, options, )))) } diff --git a/src/gc_pcsaft.rs b/src/gc_pcsaft.rs new file mode 100644 index 000000000..8bfd5cf2b --- /dev/null +++ b/src/gc_pcsaft.rs @@ -0,0 +1,21 @@ +use feos_core::python::joback::PyJobackRecord; +use feos_core::python::parameter::*; +use feos_gc_pcsaft::python::{ + PyGcPcSaftEosParameters, PyGcPcSaftFunctionalParameters, PyGcPcSaftRecord, PySegmentRecord, +}; +use pyo3::prelude::*; + +#[pymodule] +pub fn gc_pcsaft(_py: Python<'_>, m: &PyModule) -> PyResult<()> { + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + m.add_class::()?; + Ok(()) +} diff --git a/src/lib.rs b/src/lib.rs index ccbc8eb7f..7eb776247 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,5 @@ +#![warn(clippy::all)] +#![allow(clippy::too_many_arguments)] use pyo3::prelude::*; use pyo3::wrap_pymodule; use quantity::python::__PYO3_PYMODULE_DEF_QUANTITY; @@ -9,6 +11,8 @@ mod cubic; use cubic::__PYO3_PYMODULE_DEF_CUBIC; mod pcsaft; use pcsaft::__PYO3_PYMODULE_DEF_PCSAFT; +mod gc_pcsaft; +use gc_pcsaft::__PYO3_PYMODULE_DEF_GC_PCSAFT; mod pets; use pets::__PYO3_PYMODULE_DEF_PETS; @@ -19,6 +23,7 @@ pub fn feos(py: Python<'_>, m: &PyModule) -> PyResult<()> { m.add_wrapped(wrap_pymodule!(dft))?; m.add_wrapped(wrap_pymodule!(cubic))?; m.add_wrapped(wrap_pymodule!(pcsaft))?; + m.add_wrapped(wrap_pymodule!(gc_pcsaft))?; m.add_wrapped(wrap_pymodule!(pets))?; py.run( "\ @@ -33,6 +38,7 @@ sys.modules['feos.eos'] = eos sys.modules['feos.dft'] = dft sys.modules['feos.cubic'] = cubic sys.modules['feos.pcsaft'] = pcsaft +sys.modules['feos.gc_pcsaft'] = gc_pcsaft sys.modules['feos.pets'] = pets ", None, From 91c1c07185a324088214e39efa894533a4725fc6 Mon Sep 17 00:00:00 2001 From: Philipp Rehner Date: Sun, 24 Apr 2022 18:09:45 +0200 Subject: [PATCH 2/3] update README --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 0ba026512..ae16cac94 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,7 @@ The following models are currently published as part of the `FeOs` framework |name|description|eos|dft| |-|-|:-:|:-:| |[`feos-pcsaft`](https://github.com/feos-org/feos-pcsaft)|perturbed-chain (polar) statistical associating fluid theory|🗸|🗸| +|[`feos-gc-pcsaft`](https://github.com/feos-org/feos-gc-pcsaft)|(heterosegmented) group contribution PC-SAFT|🗸|🗸| The list is being expanded continuously. Currently under development are implementations of ePC-SAFT, (heterosegmented) group contribution PC-SAFT and equations of state/Helmholtz energy functionals for model fluids like LJ and Mie fluids. From db70c29f6888f8c1226dc32b99563f40e9980071 Mon Sep 17 00:00:00 2001 From: Philipp Rehner Date: Sun, 24 Apr 2022 18:10:33 +0200 Subject: [PATCH 3/3] update README even more --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ae16cac94..2d64c8e33 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ The following models are currently published as part of the `FeOs` framework |[`feos-pcsaft`](https://github.com/feos-org/feos-pcsaft)|perturbed-chain (polar) statistical associating fluid theory|🗸|🗸| |[`feos-gc-pcsaft`](https://github.com/feos-org/feos-gc-pcsaft)|(heterosegmented) group contribution PC-SAFT|🗸|🗸| -The list is being expanded continuously. Currently under development are implementations of ePC-SAFT, (heterosegmented) group contribution PC-SAFT and equations of state/Helmholtz energy functionals for model fluids like LJ and Mie fluids. +The list is being expanded continuously. Currently under development are implementations of ePC-SAFT and equations of state/Helmholtz energy functionals for model fluids like LJ and Mie fluids. Other public repositories that implement models within the `FeOs` framework, but are currently not part of the `feos` Python package, are