diff --git a/feos-core/CHANGELOG.md b/feos-core/CHANGELOG.md index a7ed73f82..633dcf05f 100644 --- a/feos-core/CHANGELOG.md +++ b/feos-core/CHANGELOG.md @@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added - Added `Components`, `Residual`, `IdealGas` and `DeBroglieWavelength` traits to decouple ideal gas models from residual models. [#158](https://github.com/feos-org/feos/pull/158) - Added `JobackParameters` struct that implements `Parameters` including Python bindings. [#158](https://github.com/feos-org/feos/pull/158) +- Added `Parameter::from_model_records` as a simpler interface to generate parameters. [#169](https://github.com/feos-org/feos/pull/169) ### Changed - Changed `EquationOfState` from a trait to a `struct` that is generic over `Residual` and `IdealGas` and implements all necessary traits to be used as equation of state including the ideal gas contribution. [#158](https://github.com/feos-org/feos/pull/158) @@ -19,6 +20,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Moved `StateVec` into own file and module. [#158](https://github.com/feos-org/feos/pull/158) - Ideal gas and residual Helmholtz energy models can now be separately implemented in Python via the `PyIdealGas` and `PyResidual` structs. [#158](https://github.com/feos-org/feos/pull/158) - Bubble and dew point iterations will not attempt a second iteration if no solution is found for the given initial pressure. [#166](https://github.com/feos-org/feos/pull/166) +- Made the binary records in the constructions and getters of the `Parameter` trait optional. [#169](https://github.com/feos-org/feos/pull/169) +- Changed the second argument of `new_binary` in Python from a `BinaryRecord` to the corresponding binary model record (analogous to the Rust implementation). [#169](https://github.com/feos-org/feos/pull/169) ### Removed - Removed `EquationOfState` trait. [#158](https://github.com/feos-org/feos/pull/158) diff --git a/feos-core/src/cubic.rs b/feos-core/src/cubic.rs index be399d67d..e06ed36e1 100644 --- a/feos-core/src/cubic.rs +++ b/feos-core/src/cubic.rs @@ -100,10 +100,7 @@ impl PengRobinsonParameters { PureRecord::new(id, molarweight[i], record) }) .collect(); - Ok(PengRobinsonParameters::from_records( - records, - Array2::zeros([pc.len(); 2]), - )) + Ok(PengRobinsonParameters::from_records(records, None)) } } @@ -114,7 +111,7 @@ impl Parameter for PengRobinsonParameters { /// Creates parameters from pure component records. fn from_records( pure_records: Vec>, - binary_records: Array2, + binary_records: Option>, ) -> Self { let n = pure_records.len(); @@ -133,19 +130,21 @@ impl Parameter for PengRobinsonParameters { kappa[i] = 0.37464 + (1.54226 - 0.26992 * r.acentric_factor) * r.acentric_factor; } + let k_ij = binary_records.unwrap_or_else(|| Array2::zeros([n; 2])); + Self { tc, a, b, - k_ij: binary_records, + k_ij, kappa, molarweight, pure_records, } } - fn records(&self) -> (&[PureRecord], &Array2) { - (&self.pure_records, &self.k_ij) + fn records(&self) -> (&[PureRecord], Option<&Array2>) { + (&self.pure_records, Some(&self.k_ij)) } } @@ -294,7 +293,7 @@ mod tests { let propane = mixture[0].clone(); let tc = propane.model_record.tc; let pc = propane.model_record.pc; - let parameters = PengRobinsonParameters::from_records(vec![propane], Array2::zeros((1, 1))); + let parameters = PengRobinsonParameters::new_pure(propane); let pr = Arc::new(PengRobinson::new(Arc::new(parameters))); let options = SolverOptions::new().verbosity(Verbosity::Iter); let cp = State::critical_point(&pr, None, None, options)?; diff --git a/feos-core/src/joback.rs b/feos-core/src/joback.rs index d1a6bdb85..6aca1e41f 100644 --- a/feos-core/src/joback.rs +++ b/feos-core/src/joback.rs @@ -72,7 +72,6 @@ pub struct JobackParameters { d: Array1, e: Array1, pure_records: Vec>, - binary_records: Array2, } impl Parameter for JobackParameters { @@ -81,11 +80,10 @@ impl Parameter for JobackParameters { fn from_records( pure_records: Vec>, - _binary_records: Array2, + _binary_records: Option>, ) -> Self { let n = pure_records.len(); - let binary_records = Array::from_elem((n, n), JobackBinaryRecord); let mut a = Array::zeros(n); let mut b = Array::zeros(n); let mut c = Array::zeros(n); @@ -108,12 +106,11 @@ impl Parameter for JobackParameters { d, e, pure_records, - binary_records, } } - fn records(&self) -> (&[PureRecord], &Array2) { - (&self.pure_records, &self.binary_records) + fn records(&self) -> (&[PureRecord], Option<&Array2>) { + (&self.pure_records, None) } } @@ -188,11 +185,7 @@ impl Components for Joback { component_list .iter() .for_each(|&i| records.push(self.parameters.pure_records[i].clone())); - let n = component_list.len(); - Self::new(Arc::new(JobackParameters::from_records( - records, - Array::from_elem((n, n), JobackBinaryRecord), - ))) + Self::new(Arc::new(JobackParameters::from_records(records, None))) } } diff --git a/feos-core/src/lib.rs b/feos-core/src/lib.rs index ddea14018..93ce524f4 100644 --- a/feos-core/src/lib.rs +++ b/feos-core/src/lib.rs @@ -202,7 +202,6 @@ mod tests { use crate::EosResult; use crate::StateBuilder; use approx::*; - use ndarray::Array2; use quantity::si::*; use std::sync::Arc; @@ -248,7 +247,7 @@ mod tests { fn validate_residual_properties() -> EosResult<()> { let mixture = pure_record_vec(); let propane = mixture[0].clone(); - let parameters = PengRobinsonParameters::from_records(vec![propane], Array2::zeros((1, 1))); + let parameters = PengRobinsonParameters::new_pure(propane); let residual = Arc::new(PengRobinson::new(Arc::new(parameters))); let joback_parameters = Arc::new(JobackParameters::new_pure(PureRecord::new( Identifier::default(), diff --git a/feos-core/src/parameter/mod.rs b/feos-core/src/parameter/mod.rs index 5782db00f..67a00bd8c 100644 --- a/feos-core/src/parameter/mod.rs +++ b/feos-core/src/parameter/mod.rs @@ -35,13 +35,12 @@ where /// Creates parameters from records for pure substances and possibly binary parameters. fn from_records( pure_records: Vec>, - binary_records: Array2, + binary_records: Option>, ) -> Self; /// Creates parameters for a pure component from a pure record. fn new_pure(pure_record: PureRecord) -> Self { - let binary_record = Array2::from_elem([1, 1], Self::Binary::default()); - Self::from_records(vec![pure_record], binary_record) + Self::from_records(vec![pure_record], None) } /// Creates parameters for a binary system from pure records and an optional @@ -50,19 +49,31 @@ where pure_records: Vec>, binary_record: Option, ) -> Self { - let binary_record = Array2::from_shape_fn([2, 2], |(i, j)| { - if i == j { - Self::Binary::default() - } else { - binary_record.clone().unwrap_or_default() - } + let binary_record = binary_record.map(|br| { + Array2::from_shape_fn([2, 2], |(i, j)| { + if i == j { + Self::Binary::default() + } else { + br.clone() + } + }) }); Self::from_records(pure_records, binary_record) } + /// Creates parameters from model records with default values for the molar weight, + /// identifiers, and binary interaction parameters. + fn from_model_records(model_records: Vec) -> Self { + let pure_records = model_records + .into_iter() + .map(|r| PureRecord::new(Default::default(), Default::default(), r)) + .collect(); + Self::from_records(pure_records, None) + } + /// Return the original pure and binary records that were used to construct the parameters. #[allow(clippy::type_complexity)] - fn records(&self) -> (&[PureRecord], &Array2); + fn records(&self) -> (&[PureRecord], Option<&Array2>); /// Helper function to build matrix from list of records in correct order. /// @@ -73,7 +84,11 @@ where pure_records: &Vec>, binary_records: &[BinaryRecord], search_option: IdentifierOption, - ) -> Array2 { + ) -> Option> { + if binary_records.is_empty() { + return None; + } + // Build Hashmap (id, id) -> BinaryRecord let binary_map: HashMap<(String, String), Self::Binary> = { binary_records @@ -86,7 +101,7 @@ where .collect() }; let n = pure_records.len(); - Array2::from_shape_fn([n, n], |(i, j)| { + Some(Array2::from_shape_fn([n, n], |(i, j)| { let id1 = pure_records[i] .identifier .as_string(search_option) @@ -106,7 +121,7 @@ where .or_else(|| binary_map.get(&(id2, id1))) .cloned() .unwrap_or_default() - }) + })) } /// Creates parameters from substance information stored in json files. @@ -250,7 +265,7 @@ where } } - Ok(Self::from_records(pure_records, binary_records)) + Ok(Self::from_records(pure_records, Some(binary_records))) } /// Creates parameters from segment information stored in json files. @@ -331,8 +346,10 @@ where .map(|&i| pure_records[i].clone()) .collect(); let n = component_list.len(); - let binary_records = Array2::from_shape_fn([n, n], |(i, j)| { - binary_records[(component_list[i], component_list[j])].clone() + let binary_records = binary_records.map(|br| { + Array2::from_shape_fn([n, n], |(i, j)| { + br[(component_list[i], component_list[j])].clone() + }) }); Self::from_records(pure_records, binary_records) @@ -484,7 +501,7 @@ mod test { struct MyParameter { pure_records: Vec>, - binary_records: Array2, + binary_records: Option>, } impl Parameter for MyParameter { @@ -492,7 +509,7 @@ mod test { type Binary = MyBinaryModel; fn from_records( pure_records: Vec>, - binary_records: Array2, + binary_records: Option>, ) -> Self { Self { pure_records, @@ -500,8 +517,8 @@ mod test { } } - fn records(&self) -> (&[PureRecord], &Array2) { - (&self.pure_records, &self.binary_records) + fn records(&self) -> (&[PureRecord], Option<&Array2>) { + (&self.pure_records, self.binary_records.as_ref()) } } @@ -555,7 +572,7 @@ mod test { assert_eq!(p.pure_records[0].identifier.cas, Some("123-4-5".into())); assert_eq!(p.pure_records[1].identifier.cas, Some("678-9-1".into())); - assert_eq!(p.binary_records[[0, 1]].b, 12.0) + assert_eq!(p.binary_records.unwrap()[[0, 1]].b, 12.0) } #[test] @@ -608,8 +625,9 @@ mod test { assert_eq!(p.pure_records[0].identifier.cas, Some("123-4-5".into())); assert_eq!(p.pure_records[1].identifier.cas, Some("678-9-1".into())); - assert_eq!(p.binary_records[[0, 1]], MyBinaryModel::default()); - assert_eq!(p.binary_records[[0, 1]].b, 0.0) + let br = p.binary_records.as_ref().unwrap(); + assert_eq!(br[[0, 1]], MyBinaryModel::default()); + assert_eq!(br[[0, 1]].b, 0.0) } #[test] @@ -672,11 +690,12 @@ mod test { assert_eq!(p.pure_records[0].identifier.cas, Some("000-0-0".into())); assert_eq!(p.pure_records[1].identifier.cas, Some("123-4-5".into())); assert_eq!(p.pure_records[2].identifier.cas, Some("678-9-1".into())); - assert_eq!(p.binary_records[[0, 1]], MyBinaryModel::default()); - assert_eq!(p.binary_records[[1, 0]], MyBinaryModel::default()); - assert_eq!(p.binary_records[[0, 2]], MyBinaryModel::default()); - assert_eq!(p.binary_records[[2, 0]], MyBinaryModel::default()); - assert_eq!(p.binary_records[[2, 1]].b, 12.0); - assert_eq!(p.binary_records[[1, 2]].b, 12.0); + let br = p.binary_records.as_ref().unwrap(); + assert_eq!(br[[0, 1]], MyBinaryModel::default()); + assert_eq!(br[[1, 0]], MyBinaryModel::default()); + assert_eq!(br[[0, 2]], MyBinaryModel::default()); + assert_eq!(br[[2, 0]], MyBinaryModel::default()); + assert_eq!(br[[2, 1]].b, 12.0); + assert_eq!(br[[1, 2]].b, 12.0); } } diff --git a/feos-core/src/python/cubic.rs b/feos-core/src/python/cubic.rs index 1973a1bf1..a74802750 100644 --- a/feos-core/src/python/cubic.rs +++ b/feos-core/src/python/cubic.rs @@ -4,7 +4,6 @@ use crate::parameter::{ }; use crate::python::parameter::PyIdentifier; use crate::*; -use ndarray::Array2; use numpy::{PyArray2, PyReadonlyArray2, ToPyArray}; use pyo3::exceptions::PyTypeError; use pyo3::prelude::*; @@ -56,7 +55,12 @@ impl_binary_record!(); #[derive(Clone)] pub struct PyPengRobinsonParameters(pub Arc); -impl_parameter!(PengRobinsonParameters, PyPengRobinsonParameters); +impl_parameter!( + PengRobinsonParameters, + PyPengRobinsonParameters, + PyPengRobinsonRecord, + f64 +); #[pymethods] impl PyPengRobinsonParameters { diff --git a/feos-core/src/python/joback.rs b/feos-core/src/python/joback.rs index c54257b6c..de1f05031 100644 --- a/feos-core/src/python/joback.rs +++ b/feos-core/src/python/joback.rs @@ -7,7 +7,6 @@ use crate::{ impl_binary_record, impl_json_handling, impl_parameter, impl_parameter_from_segments, impl_pure_record, impl_segment_record, }; -use ndarray::Array2; use numpy::{PyArray2, PyReadonlyArray2, ToPyArray}; use pyo3::exceptions::PyTypeError; use pyo3::prelude::*; @@ -62,6 +61,7 @@ impl_segment_record!(JobackRecord, PyJobackRecord); pub struct PyJobackBinaryRecord(pub JobackBinaryRecord); impl_binary_record!(JobackBinaryRecord, PyJobackBinaryRecord); + /// Create a set of Joback parameters from records. /// /// Parameters @@ -83,7 +83,12 @@ impl_binary_record!(JobackBinaryRecord, PyJobackBinaryRecord); #[derive(Clone)] pub struct PyJobackParameters(pub Arc); -impl_parameter!(JobackParameters, PyJobackParameters); +impl_parameter!( + JobackParameters, + PyJobackParameters, + PyJobackRecord, + PyJobackBinaryRecord +); impl_parameter_from_segments!(JobackParameters, PyJobackParameters); #[pymethods] diff --git a/feos-core/src/python/parameter.rs b/feos-core/src/python/parameter.rs index 9c0e80e94..8a98b145d 100644 --- a/feos-core/src/python/parameter.rs +++ b/feos-core/src/python/parameter.rs @@ -201,6 +201,12 @@ macro_rules! impl_binary_record { #[derive(Clone)] pub struct PyBinaryRecord(pub BinaryRecord); + impl From<$py_model_record> for $model_record { + fn from(record: $py_model_record) -> Self { + record.0 + } + } + #[pymethods] impl PyBinaryRecord { #[new] @@ -540,7 +546,7 @@ macro_rules! impl_segment_record { #[macro_export] macro_rules! impl_parameter { - ($parameter:ty, $py_parameter:ty) => { + ($parameter:ty, $py_parameter:ty, $py_model_record:ty, $py_binary_model_record:ty) => { #[pymethods] impl $py_parameter { /// Creates parameters from records. @@ -565,32 +571,26 @@ macro_rules! impl_parameter { search_option: IdentifierOption, ) -> PyResult { let prs = pure_records.into_iter().map(|pr| pr.0).collect(); - if let Some(binary_records) = binary_records { - let brs = if let Ok(br) = binary_records.extract::>() { - Ok(br.to_owned_array().mapv(|r| r.try_into().unwrap())) - } else if let Ok(br) = binary_records.extract::>() { - let brs: Vec<_> = br.into_iter().map(|br| br.0).collect(); - Ok(<$parameter>::binary_matrix_from_records( - &prs, - &brs, - search_option, - )) - } else { - Err(PyErr::new::(format!( - "Could not parse binary input!" - ))) - }; - Ok(Self(Arc::new(<$parameter>::from_records( - prs, - brs.unwrap(), - )))) - } else { - let n = prs.len(); - Ok(Self(Arc::new(<$parameter>::from_records( - prs, - Array2::from_elem([n, n], <$parameter as Parameter>::Binary::default()), - )))) - } + let binary_records = binary_records + .map(|binary_records| { + if let Ok(br) = binary_records.extract::>() { + Ok(Some(br.to_owned_array().mapv(|r| r.try_into().unwrap()))) + } else if let Ok(br) = binary_records.extract::>() { + let brs: Vec<_> = br.into_iter().map(|br| br.0).collect(); + Ok(<$parameter>::binary_matrix_from_records( + &prs, + &brs, + search_option, + )) + } else { + Err(PyErr::new::(format!( + "Could not parse binary input!" + ))) + } + }) + .transpose()? + .flatten(); + Ok(Self(Arc::new(Parameter::from_records(prs, binary_records)))) } /// Creates parameters for a pure component from a pure record. @@ -624,8 +624,8 @@ macro_rules! impl_parameter { .map(|br| { if let Ok(r) = br.extract::() { Ok(r.try_into()?) - } else if let Ok(r) = br.extract::() { - Ok(r.0.model_record) + } else if let Ok(r) = br.extract::<$py_binary_model_record>() { + Ok(r.into()) } else { Err(PyErr::new::(format!( "Could not parse binary input!" @@ -636,6 +636,19 @@ macro_rules! impl_parameter { Ok(Self(Arc::new(<$parameter>::new_binary(prs, br)))) } + /// Creates parameters from model records with default values for the molar weight, + /// identifiers, and binary interaction parameters. + /// + /// Parameters + /// ---------- + /// model_records : [ModelRecord] + /// A list of model parameters. + #[staticmethod] + fn from_model_records(model_records: Vec<$py_model_record>) -> PyResult { + let mrs = model_records.into_iter().map(|mr| mr.0).collect(); + Ok(Self(Arc::new(<$parameter>::from_model_records(mrs)))) + } + /// Creates parameters from json files. /// /// Parameters @@ -706,13 +719,11 @@ macro_rules! impl_parameter { } #[getter] - fn get_binary_records<'py>(&self, py: Python<'py>) -> &'py PyArray2 { + fn get_binary_records<'py>(&self, py: Python<'py>) -> Option<&'py PyArray2> { self.0 .records() .1 - .mapv(|r| f64::try_from(r).unwrap()) - .view() - .to_pyarray(py) + .map(|r| r.mapv(|r| f64::try_from(r).unwrap()).view().to_pyarray(py)) } } }; diff --git a/src/pcsaft/parameters.rs b/src/pcsaft/parameters.rs index 95d03c573..2738b61ef 100644 --- a/src/pcsaft/parameters.rs +++ b/src/pcsaft/parameters.rs @@ -267,7 +267,7 @@ impl PcSaftRecord { #[derive(Serialize, Deserialize, Clone, Default)] pub struct PcSaftBinaryRecord { - k_ij: f64, + pub k_ij: f64, association: Option, } @@ -337,7 +337,6 @@ pub struct PcSaftParameters { pub mu2: Array1, pub q2: Array1, pub association: AssociationParameters, - pub k_ij: Array2, pub sigma_ij: Array2, pub epsilon_k_ij: Array2, pub e_k_ij: Array2, @@ -349,7 +348,7 @@ pub struct PcSaftParameters { pub diffusion: Option>, pub thermal_conductivity: Option>, pub pure_records: Vec>, - pub binary_records: Array2, + pub binary_records: Option>, } impl Parameter for PcSaftParameters { @@ -358,7 +357,7 @@ impl Parameter for PcSaftParameters { fn from_records( pure_records: Vec>, - binary_records: Array2, + binary_records: Option>, ) -> Self { let n = pure_records.len(); @@ -410,23 +409,28 @@ impl Parameter for PcSaftParameters { let nquadpole = quadpole_comp.len(); let binary_association: Vec<_> = binary_records - .indexed_iter() - .filter_map(|(i, record)| record.association.map(|r| (i, r))) + .iter() + .flat_map(|r| { + r.indexed_iter() + .filter_map(|(i, record)| record.association.map(|r| (i, r))) + }) .collect(); let association = AssociationParameters::new(&association_records, &sigma, &binary_association, None); - let k_ij = binary_records.map(|br| br.k_ij); - let mut epsilon_k_ij = Array::zeros((n, n)); + let k_ij = binary_records.as_ref().map(|br| br.map(|br| br.k_ij)); let mut sigma_ij = Array::zeros((n, n)); let mut e_k_ij = Array::zeros((n, n)); for i in 0..n { for j in 0..n { e_k_ij[[i, j]] = (epsilon_k[i] * epsilon_k[j]).sqrt(); - epsilon_k_ij[[i, j]] = (1.0 - k_ij[[i, j]]) * e_k_ij[[i, j]]; sigma_ij[[i, j]] = 0.5 * (sigma[i] + sigma[j]); } } + let mut epsilon_k_ij = e_k_ij.clone(); + if let Some(k_ij) = k_ij.as_ref() { + epsilon_k_ij *= &(1.0 - k_ij) + }; let viscosity_coefficients = if viscosity.iter().any(|v| v.is_none()) { None @@ -469,7 +473,6 @@ impl Parameter for PcSaftParameters { mu2, q2, association, - k_ij, sigma_ij, epsilon_k_ij, e_k_ij, @@ -485,8 +488,13 @@ impl Parameter for PcSaftParameters { } } - fn records(&self) -> (&[PureRecord], &Array2) { - (&self.pure_records, &self.binary_records) + fn records( + &self, + ) -> ( + &[PureRecord], + Option<&Array2>, + ) { + (&self.pure_records, self.binary_records.as_ref()) } } @@ -790,7 +798,7 @@ pub mod utils { segment_records, Some(binary_segment_records), )?; - let k_ij = ¶ms.binary_records; + let k_ij = params.binary_records.as_ref().unwrap(); assert_eq!(k_ij[[0, 0]].k_ij, 0.0); assert_eq!(k_ij[[0, 1]].k_ij, -0.5 / 9.); assert_eq!(k_ij[[1, 0]].k_ij, -0.5 / 9.); diff --git a/src/pcsaft/python.rs b/src/pcsaft/python.rs index 5873f48da..90caa218d 100644 --- a/src/pcsaft/python.rs +++ b/src/pcsaft/python.rs @@ -6,7 +6,6 @@ use feos_core::parameter::{ }; use feos_core::python::parameter::*; use feos_core::*; -use ndarray::Array2; use numpy::{PyArray2, PyReadonlyArray2, ToPyArray}; use pyo3::exceptions::PyTypeError; use pyo3::prelude::*; @@ -177,14 +176,22 @@ impl_binary_record!(PcSaftBinaryRecord, PyPcSaftBinaryRecord); #[derive(Clone)] pub struct PyPcSaftParameters(pub Arc); -impl_parameter!(PcSaftParameters, PyPcSaftParameters); +impl_parameter!( + PcSaftParameters, + PyPcSaftParameters, + PyPcSaftRecord, + PyPcSaftBinaryRecord +); impl_parameter_from_segments!(PcSaftParameters, PyPcSaftParameters); #[pymethods] impl PyPcSaftParameters { #[getter] - fn get_k_ij<'py>(&self, py: Python<'py>) -> &'py PyArray2 { - self.0.k_ij.view().to_pyarray(py) + fn get_k_ij<'py>(&self, py: Python<'py>) -> Option<&'py PyArray2> { + self.0 + .binary_records + .as_ref() + .map(|br| br.map(|br| br.k_ij).view().to_pyarray(py)) } fn _repr_markdown_(&self) -> String { diff --git a/src/pets/parameters.rs b/src/pets/parameters.rs index 36391a037..7867402db 100644 --- a/src/pets/parameters.rs +++ b/src/pets/parameters.rs @@ -2,7 +2,6 @@ use crate::hard_sphere::{HardSphereProperties, MonomerShape}; use feos_core::parameter::{Parameter, PureRecord}; use ndarray::{Array, Array1, Array2}; use num_dual::DualNum; -use num_traits::Zero; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::fmt::Write; @@ -104,7 +103,7 @@ pub struct PetsParameters { /// Lennard-Jones energy parameter in Kelvin pub epsilon_k: Array1, /// binary interaction parameter - pub k_ij: Array2, + pub k_ij: Option>, /// diameter matrix pub sigma_ij: Array2, /// energy parameter matrix including k_ij @@ -120,7 +119,7 @@ pub struct PetsParameters { /// records of all pure substances of the system pub pure_records: Vec>, /// records of all binary interaction parameters - pub binary_records: Array2, + pub binary_records: Option>, } impl Parameter for PetsParameters { @@ -129,7 +128,7 @@ impl Parameter for PetsParameters { fn from_records( pure_records: Vec>, - binary_records: Array2, + binary_records: Option>, ) -> Self { let n = pure_records.len(); @@ -153,17 +152,19 @@ impl Parameter for PetsParameters { molarweight[i] = record.molarweight; } - let k_ij = binary_records.map(|br| br.k_ij); - let mut epsilon_k_ij = Array::zeros((n, n)); + let k_ij = binary_records.as_ref().map(|br| br.map(|br| br.k_ij)); let mut sigma_ij = Array::zeros((n, n)); let mut e_k_ij = Array::zeros((n, n)); for i in 0..n { for j in 0..n { e_k_ij[[i, j]] = (epsilon_k[i] * epsilon_k[j]).sqrt(); - epsilon_k_ij[[i, j]] = (1.0 - k_ij[[i, j]]) * e_k_ij[[i, j]]; sigma_ij[[i, j]] = 0.5 * (sigma[i] + sigma[j]); } } + let mut epsilon_k_ij = e_k_ij.clone(); + if let Some(k_ij) = k_ij.as_ref() { + epsilon_k_ij *= &(1.0 - k_ij); + } let viscosity_coefficients = if viscosity.iter().any(|v| v.is_none()) { None @@ -212,8 +213,8 @@ impl Parameter for PetsParameters { } } - fn records(&self) -> (&[PureRecord], &Array2) { - (&self.pure_records, &self.binary_records) + fn records(&self) -> (&[PureRecord], Option<&Array2>) { + (&self.pure_records, self.binary_records.as_ref()) } } @@ -260,8 +261,8 @@ impl std::fmt::Display for PetsParameters { write!(f, "\n\tmolarweight={}", self.molarweight)?; write!(f, "\n\tsigma={}", self.sigma)?; write!(f, "\n\tepsilon_k={}", self.epsilon_k)?; - if !self.k_ij.iter().all(|k| k.is_zero()) { - write!(f, "\n\tk_ij=\n{}", self.k_ij)?; + if let Some(k_ij) = self.k_ij.as_ref() { + write!(f, "\n\tk_ij=\n{}", k_ij)?; } write!(f, "\n)") } diff --git a/src/pets/python.rs b/src/pets/python.rs index 39a78a977..c7cec78e4 100644 --- a/src/pets/python.rs +++ b/src/pets/python.rs @@ -2,7 +2,6 @@ use super::parameters::*; use feos_core::parameter::*; use feos_core::python::parameter::*; use feos_core::{impl_binary_record, impl_json_handling, impl_parameter, impl_pure_record}; -use ndarray::Array2; use numpy::{PyArray2, PyReadonlyArray2, ToPyArray}; use pyo3::exceptions::{PyTypeError, PyValueError}; use pyo3::prelude::*; @@ -183,10 +182,7 @@ impl PyPetsParameters { }) .collect(); - let binary = match k_ij { - Some(v) => v.to_owned_array().mapv(f64::into), - None => Array2::from_shape_fn((n, n), |(_, _)| PetsBinaryRecord::from(0.0)), - }; + let binary = k_ij.map(|v| v.to_owned_array().mapv(f64::into)); Ok(Self(Arc::new(PetsParameters::from_records( pure_records, @@ -241,8 +237,8 @@ impl PyPetsParameters { } #[getter] - fn get_k_ij<'py>(&self, py: Python<'py>) -> &'py PyArray2 { - self.0.k_ij.view().to_pyarray(py) + fn get_k_ij<'py>(&self, py: Python<'py>) -> Option<&'py PyArray2> { + self.0.k_ij.as_ref().map(|k| k.view().to_pyarray(py)) } fn _repr_markdown_(&self) -> String { @@ -254,7 +250,12 @@ impl PyPetsParameters { } } -impl_parameter!(PetsParameters, PyPetsParameters); +impl_parameter!( + PetsParameters, + PyPetsParameters, + PyPetsRecord, + PyPetsBinaryRecord +); #[pymodule] pub fn pets(_py: Python<'_>, m: &PyModule) -> PyResult<()> { diff --git a/src/saftvrqmie/parameters.rs b/src/saftvrqmie/parameters.rs index 13b49eb7e..9f1e9696a 100644 --- a/src/saftvrqmie/parameters.rs +++ b/src/saftvrqmie/parameters.rs @@ -134,7 +134,7 @@ pub struct SaftVRQMieParameters { pub diffusion: Option>, pub thermal_conductivity: Option>, pub pure_records: Vec>, - pub binary_records: Array2, + pub binary_records: Option>, } impl Parameter for SaftVRQMieParameters { @@ -143,7 +143,7 @@ impl Parameter for SaftVRQMieParameters { fn from_records( pure_records: Vec>, - binary_records: Array2, + binary_records: Option>, ) -> Self { let n = pure_records.len(); @@ -173,8 +173,9 @@ impl Parameter for SaftVRQMieParameters { molarweight[i] = record.molarweight; } - let k_ij = binary_records.map(|br| br.k_ij); - let l_ij = binary_records.map(|br| br.l_ij); + let br = binary_records.as_ref(); + let k_ij = br.map_or_else(|| Array2::zeros([n; 2]), |br| br.mapv(|br| br.k_ij)); + let l_ij = br.map_or_else(|| Array2::zeros([n; 2]), |br| br.mapv(|br| br.l_ij)); let mut epsilon_k_ij = Array::zeros((n, n)); let mut sigma_ij = Array::zeros((n, n)); let mut e_k_ij = Array::zeros((n, n)); @@ -260,9 +261,9 @@ impl Parameter for SaftVRQMieParameters { &self, ) -> ( &[PureRecord], - &Array2, + Option<&Array2>, ) { - (&self.pure_records, &self.binary_records) + (&self.pure_records, self.binary_records.as_ref()) } } diff --git a/src/saftvrqmie/python.rs b/src/saftvrqmie/python.rs index 8063e4992..0665dc9ed 100644 --- a/src/saftvrqmie/python.rs +++ b/src/saftvrqmie/python.rs @@ -8,7 +8,6 @@ use feos_core::parameter::{ }; use feos_core::python::parameter::PyIdentifier; use feos_core::*; -use ndarray::Array2; use numpy::{PyArray2, PyReadonlyArray2, ToPyArray}; use pyo3::exceptions::{PyIOError, PyTypeError}; use pyo3::prelude::*; @@ -147,7 +146,12 @@ pub struct PySaftVRQMieParameters(pub Arc); impl_json_handling!(PySaftVRQMieRecord); impl_pure_record!(SaftVRQMieRecord, PySaftVRQMieRecord); impl_binary_record!(SaftVRQMieBinaryRecord, PySaftVRQMieBinaryRecord); -impl_parameter!(SaftVRQMieParameters, PySaftVRQMieParameters); +impl_parameter!( + SaftVRQMieParameters, + PySaftVRQMieParameters, + PySaftVRQMieRecord, + PySaftVRQMieBinaryRecord +); #[pymethods] impl PySaftVRQMieParameters { @@ -206,7 +210,7 @@ impl PySaftVRQMieParameters { ) -> PyResult<()> { self.0 .lammps_tables(temperature.into(), n, r_min.into(), r_max.into()) - .map_err(|e| PyIOError::new_err(e)) + .map_err(PyIOError::new_err) } fn _repr_markdown_(&self) -> String { diff --git a/src/uvtheory/parameters.rs b/src/uvtheory/parameters.rs index e6dbd1fec..901794469 100644 --- a/src/uvtheory/parameters.rs +++ b/src/uvtheory/parameters.rs @@ -111,7 +111,7 @@ pub struct UVParameters { pub sigma: Array1, pub epsilon_k: Array1, pub molarweight: Array1, - pub k_ij: Array2, + pub k_ij: Option>, pub rep_ij: Array2, pub att_ij: Array2, pub sigma_ij: Array2, @@ -119,7 +119,7 @@ pub struct UVParameters { pub cd_bh_pure: Vec>, pub cd_bh_binary: Array2>, pub pure_records: Vec>, - pub binary_records: Array2, + pub binary_records: Option>, } impl Parameter for UVParameters { @@ -128,7 +128,7 @@ impl Parameter for UVParameters { fn from_records( pure_records: Vec>, - binary_records: Array2, + binary_records: Option>, ) -> Self { let n = pure_records.len(); @@ -154,7 +154,7 @@ impl Parameter for UVParameters { let mut att_ij = Array2::zeros((n, n)); let mut sigma_ij = Array2::zeros((n, n)); let mut eps_k_ij = Array2::zeros((n, n)); - let k_ij = binary_records.map(|br| br.k_ij); + let k_ij = binary_records.as_ref().map(|br| br.map(|br| br.k_ij)); for i in 0..n { rep_ij[[i, i]] = rep[i]; @@ -168,7 +168,8 @@ impl Parameter for UVParameters { att_ij[[j, i]] = att_ij[[i, j]]; sigma_ij[[i, j]] = 0.5 * (sigma[i] + sigma[j]); sigma_ij[[j, i]] = sigma_ij[[i, j]]; - eps_k_ij[[i, j]] = (1.0 - k_ij[[i, j]]) * (epsilon_k[i] * epsilon_k[j]).sqrt(); + eps_k_ij[[i, j]] = (1.0 - k_ij.as_ref().map_or(0.0, |k_ij| k_ij[[i, j]])) + * (epsilon_k[i] * epsilon_k[j]).sqrt(); eps_k_ij[[j, i]] = eps_k_ij[[i, j]]; } } @@ -197,8 +198,8 @@ impl Parameter for UVParameters { } } - fn records(&self) -> (&[PureRecord], &Array2) { - (&self.pure_records, &self.binary_records) + fn records(&self) -> (&[PureRecord], Option<&Array2>) { + (&self.pure_records, self.binary_records.as_ref()) } } diff --git a/src/uvtheory/python.rs b/src/uvtheory/python.rs index 04a4425e0..406045782 100644 --- a/src/uvtheory/python.rs +++ b/src/uvtheory/python.rs @@ -5,7 +5,6 @@ use feos_core::parameter::{ }; use feos_core::python::parameter::*; use feos_core::*; -use ndarray::Array2; use numpy::{PyArray2, PyReadonlyArray2, ToPyArray}; use pyo3::exceptions::PyTypeError; use pyo3::prelude::*; @@ -97,8 +96,7 @@ impl PyUVParameters { PureRecord::new(identifier, 1.0, model_record) }) .collect(); - let binary = Array2::from_shape_fn((n, n), |(_, _)| UVBinaryRecord { k_ij: 0.0 }); - Self(Arc::new(UVParameters::from_records(pure_records, binary))) + Self(Arc::new(UVParameters::from_records(pure_records, None))) } /// Create UV Theory parameters for pure substance. @@ -131,7 +129,7 @@ impl PyUVParameters { } impl_pure_record!(UVRecord, PyUVRecord); -impl_parameter!(UVParameters, PyUVParameters); +impl_parameter!(UVParameters, PyUVParameters, PyUVRecord, PyUVBinaryRecord); #[pymodule] pub fn uvtheory(_py: Python<'_>, m: &PyModule) -> PyResult<()> {