From 5f4967ccb9a99e99ae8d7ba0f50cfb45c278c5e4 Mon Sep 17 00:00:00 2001 From: Philipp Rehner Date: Thu, 25 Nov 2021 19:29:32 +0100 Subject: [PATCH 1/6] Refactor the handling of binary interaction parameters --- src/cubic.rs | 57 ++++----- src/joback.rs | 10 +- src/parameter/chemical_record.rs | 26 ++--- src/parameter/mod.rs | 193 ++++++++++++++++++++----------- src/parameter/model_record.rs | 40 +++++-- 5 files changed, 195 insertions(+), 131 deletions(-) diff --git a/src/cubic.rs b/src/cubic.rs index 4418618..e12dcc7 100644 --- a/src/cubic.rs +++ b/src/cubic.rs @@ -1,10 +1,10 @@ use crate::equation_of_state::{EquationOfState, HelmholtzEnergy, HelmholtzEnergyDual}; use crate::joback::JobackRecord; -use crate::parameter::{BinaryRecord, Identifier, Parameter, ParameterError, PureRecord}; +use crate::parameter::{Identifier, Parameter, ParameterError, PureRecord}; use crate::si::{GRAM, MOL}; use crate::state::StateHD; use crate::MolarWeight; -use ndarray::{Array, Array1, Array2}; +use ndarray::{Array1, Array2}; use num_dual::DualNum; use quantity::si::{SIArray1, SIUnit}; use serde::{Deserialize, Serialize}; @@ -43,17 +43,6 @@ pub struct PengRobinsonParameters { molarweight: Array1, pure_records: Vec>, joback_records: Option>, - binary_records: Option>>, -} - -impl PengRobinsonParameters { - pub fn subset(&self, component_list: &[usize]) -> Self { - let pure_records = component_list - .iter() - .map(|&i| self.pure_records[i].clone()) - .collect(); - Self::from_records(pure_records, self.binary_records.clone()).unwrap() - } } impl PengRobinsonParameters { @@ -83,7 +72,10 @@ impl PengRobinsonParameters { PureRecord::new(id, molarweight[i], None, Some(record), None) }) .collect(); - PengRobinsonParameters::from_records(records, None) + Ok(PengRobinsonParameters::from_records( + records, + Array2::zeros([pc.len(); 2]), + )) } } @@ -94,8 +86,8 @@ impl Parameter for PengRobinsonParameters { fn from_records( pure_records: Vec>, - binary_records: Option>>, - ) -> Result { + binary_records: Array2, + ) -> Self { let n = pure_records.len(); let mut tc = Array1::zeros(n); @@ -123,35 +115,30 @@ impl Parameter for PengRobinsonParameters { }; } - let mut k_ij = Array::zeros([n, n]); - match &binary_records { - Some(bs) => bs.iter().for_each(|record| { - let i = component_index.get(&record.id1); - let j = component_index.get(&record.id2); - if let (Some(i), Some(j)) = (i, j) { - k_ij[[*i, *j]] = record.model_record; - k_ij[[*j, *i]] = record.model_record - } - }), - None => (), - } - let joback_records = pure_records .iter() .map(|r| r.ideal_gas_record.clone()) .collect(); - Ok(Self { + Self { tc, a, b, - k_ij, + k_ij: binary_records, kappa, molarweight, pure_records, joback_records, - binary_records, - }) + } + } + + fn records( + &self, + ) -> ( + &[PureRecord], + &Array2, + ) { + (&self.pure_records, &self.k_ij) } } @@ -280,8 +267,8 @@ mod tests { let propane = mixture[0].clone(); let tc = propane.model_record.clone().unwrap().tc; let pc = propane.model_record.clone().unwrap().pc; - let parameters = PengRobinsonParameters::from_records(vec![propane.clone()], None) - .expect("Error reading parameters!"); + let parameters = + PengRobinsonParameters::from_records(vec![propane.clone()], vec![vec![0.0]]); let pr = Rc::new(PengRobinson::new(parameters)); let cp = State::critical_point(&pr, None, None, VLEOptions::default())?; println!("{} {}", cp.temperature, cp.pressure(Contributions::Total)); diff --git a/src/joback.rs b/src/joback.rs index 00c15bb..bdf46b2 100644 --- a/src/joback.rs +++ b/src/joback.rs @@ -46,11 +46,7 @@ impl fmt::Display for JobackRecord { /// Implementation of the combining rules as described in /// [Joback and Reid, 1987](https://doi.org/10.1080/00986448708960487). impl FromSegments for JobackRecord { - type Binary = (); - fn from_segments( - segments: &[(Self, f64)], - _binary_records: Option<&[BinaryRecord]>, - ) -> Result { + fn from_segments(segments: &[(Self, f64)]) -> Self { let mut a = -37.93; let mut b = 0.21; let mut c = -3.91e-4; @@ -63,7 +59,7 @@ impl FromSegments for JobackRecord { d += s.d * *n; e += s.e * *n; }); - Ok(Self { a, b, c, d, e }) + Self { a, b, c, d, e } } } @@ -238,7 +234,7 @@ mod tests { .iter() .map(|(s, &n)| (s.ideal_gas_record.clone().unwrap(), n)) .collect(); - let jr = JobackRecord::from_segments(&joback_segments, None).unwrap(); + let jr = JobackRecord::from_segments(&joback_segments); assert_relative_eq!( jr.a, 33.3 * 2.0 - 2.14 * 4.0 - 8.25 * 2.0 - 37.93, diff --git a/src/parameter/chemical_record.rs b/src/parameter/chemical_record.rs index e7a00b8..32a8811 100644 --- a/src/parameter/chemical_record.rs +++ b/src/parameter/chemical_record.rs @@ -56,6 +56,19 @@ impl ChemicalRecord { Ok(counts) } + /// Count the number of occurences of each individual segment identifier in the + /// chemical record. + /// + /// The map contains the segment identifier as key and the count as (float) value. + pub fn segment_id_count(&self) -> HashMap { + let mut counts = HashMap::with_capacity(self.segments.len()); + for si in &self.segments { + let entry = counts.entry(si.clone()).or_insert(0.0); + *entry += 1.0; + } + counts + } + /// Build a HashMap from SegmentRecords for the segments. /// /// The map contains the segment identifier (String) as key @@ -80,19 +93,6 @@ impl ChemicalRecord { .map(|s| segments.remove_entry(s).unwrap()) .collect()) } - - /// Compute the molar weight from SegmentRecords - pub fn molarweight_from_segments( - &self, - segment_records: &[SegmentRecord], - ) -> Result { - let segment_map = self.segment_map(segment_records)?; - Ok(self - .segments - .iter() - .map(|s| segment_map.get(s).unwrap().molarweight) - .sum()) - } } impl std::fmt::Display for ChemicalRecord { diff --git a/src/parameter/mod.rs b/src/parameter/mod.rs index 1bbd6df..2da84ab 100644 --- a/src/parameter/mod.rs +++ b/src/parameter/mod.rs @@ -1,6 +1,7 @@ //! Structures and traits that can be used to build model parameters for equations of state. use indexmap::IndexSet; +use ndarray::Array2; use serde::de::DeserializeOwned; use std::collections::HashMap; use std::fs::File; @@ -16,7 +17,9 @@ mod segment; pub use chemical_record::ChemicalRecord; pub use identifier::{Identifier, IdentifierOption}; -pub use model_record::{BinaryRecord, FromSegments, GroupContributionRecord, NoRecord, PureRecord}; +pub use model_record::{ + BinaryRecord, FromSegments, FromSegmentsBinary, GroupContributionRecord, NoRecord, PureRecord, +}; pub use segment::SegmentRecord; /// Constructor methods for parameters. @@ -30,13 +33,22 @@ where { type Pure: Clone + DeserializeOwned + Default; type IdealGas: Clone + DeserializeOwned + Default; - type Binary: DeserializeOwned + Default; + type Binary: Clone + DeserializeOwned + Default; /// Creates parameters from records for pure substances and possibly binary parameters. fn from_records( pure_records: Vec>, - binary_records: Option>>, - ) -> Result; + binary_records: Array2, + ) -> Self; + + /// Return the original pure and binary records that werde used to construct the parameters. + #[allow(clippy::type_complexity)] + fn records( + &self, + ) -> ( + &[PureRecord], + &Array2, + ); /// Creates parameters from substance information stored in json files. fn from_json

( @@ -77,86 +89,104 @@ where let msg = format!("{:?}", missing); return Err(ParameterError::ComponentsNotFound(msg)); }; - let p = queried + let p: Vec<_> = queried .iter() .filter_map(|identifier| record_map.remove(&identifier.clone())) .collect(); - // Todo: maybe change into buffer - let bp = if let Some(path) = file_binary { + // Read binary records from file if provided + let binary_map = if let Some(path) = file_binary { let file = File::open(path)?; let reader = BufReader::new(file); let binary_records: Vec> = serde_json::from_reader(reader)?; - let brs: Vec> = binary_records + binary_records .into_iter() - .filter(|record| { - let id1 = record.id1.as_string(search_option); - let id2 = record.id2.as_string(search_option); - if let (Some(i1), Some(i2)) = (id1, id2) { - let mut s = IndexSet::with_capacity(2); - s.insert(i1); - s.insert(i2); - s.is_subset(&queried) - } else { - false - } + .filter_map(|br| { + let id1 = br.id1.as_string(search_option); + let id2 = br.id2.as_string(search_option); + id1.and_then(|id1| id2.map(|id2| ((id1, id2), br.model_record))) }) - .collect(); - Some(brs) + .collect() } else { - None + HashMap::with_capacity(0) }; - Self::from_records(p, bp) + + let n = p.len(); + let br = Array2::from_shape_fn([n, n], |(i, j)| { + let id1 = p[i].identifier.as_string(search_option).unwrap(); + let id2 = p[j].identifier.as_string(search_option).unwrap(); + binary_map + .get(&(id1.clone(), id2.clone())) + .or_else(|| binary_map.get(&(id2, id1))) + .cloned() + .unwrap_or_default() + }); + + Ok(Self::from_records(p, br)) } /// Creates parameters from the molecular structure and segment information. /// /// The [FromSegments] trait needs to be implemented for both the model record /// and the ideal gas record. - fn from_segments( - pure_records: Vec>, + fn from_segment_records( + mut pure_records: Vec>, segment_records: Vec>, binary_segment_records: Option>>, ) -> Result where - Self::Pure: FromSegments, + Self::Pure: FromSegments, Self::IdealGas: FromSegments, + Self::Binary: FromSegmentsBinary + Default, { - let mut records: Vec<_> = Vec::with_capacity(pure_records.len()); - for pr in pure_records { - let chemical_record = pr + // update the pure records with model and ideal gas records + // calculated from the gc method + pure_records.iter_mut().try_for_each(|pr| { + let segments = pr .chemical_record .clone() - .ok_or(ParameterError::InsufficientInformation)?; - let molarweight = chemical_record.molarweight_from_segments(&segment_records)?; - let segment_count = chemical_record.segment_count(&segment_records)?; - - let model_segments: Vec<_> = segment_count - .iter() - .map(|(s, &n)| (s.model_record.clone(), n)) - .collect(); - let model_record = - Self::Pure::from_segments(&model_segments, binary_segment_records.as_deref())?; - - let ideal_gas_segments: Option> = segment_count - .iter() - .map(|(s, &n)| s.ideal_gas_record.clone().map(|ig| (ig, n))) - .collect(); - let ideal_gas_record = ideal_gas_segments - .as_ref() - .map(|s| Self::IdealGas::from_segments(s, None)) - .transpose()?; - - records.push(PureRecord::new( - pr.identifier.clone(), - molarweight, - Some(chemical_record), - Some(model_record), - ideal_gas_record, - )) - } - Self::from_records(records, None) + .ok_or(ParameterError::InsufficientInformation)? + .segment_count(&segment_records)?; + pr.update_from_segments(segments); + Ok::<_, ParameterError>(()) + })?; + + // Map: (id1, id2) -> model_record + // empty, if no binary segment records are provided + let binary_map: HashMap<_, _> = binary_segment_records + .into_iter() + .map(|seg| seg.into_iter()) + .flatten() + .map(|br| ((br.id1, br.id2), br.model_record)) + .collect(); + + // For every component: map: id -> count + let segment_id_counts: Vec<_> = pure_records + .iter() + .map(|pr| pr.chemical_record.as_ref().unwrap().segment_id_count()) + .collect(); + + // full matrix of binary records from the gc method. + // If a specific segment-segment interaction is not in the binary map, + // the default value is used. + let n = pure_records.len(); + let binary_records = Array2::from_shape_fn([n, n], |(i, j)| { + let mut vec = Vec::new(); + for (id1, &n1) in segment_id_counts[i].iter() { + for (id2, &n2) in segment_id_counts[j].iter() { + let binary = binary_map + .get(&(id1.clone(), id2.clone())) + .or_else(|| binary_map.get(&(id2.clone(), id1.clone()))) + .cloned() + .unwrap_or_default(); + vec.push((binary, n1, n2)); + } + } + Self::Binary::from_segments_binary(&vec) + }); + + Ok(Self::from_records(pure_records, binary_records)) } /// Creates parameters from segment information stored in json files. @@ -172,8 +202,9 @@ where ) -> Result where P: AsRef, - Self::Pure: FromSegments, + Self::Pure: FromSegments, Self::IdealGas: FromSegments, + Self::Binary: FromSegmentsBinary, { let queried: IndexSet = substances .iter() @@ -227,7 +258,21 @@ where }) .transpose()?; - Self::from_segments(pure_records, segment_records, binary_records) + Self::from_segment_records(pure_records, segment_records, binary_records) + } + + fn subset(&self, component_list: &[usize]) -> Self { + let (pure_records, binary_records) = self.records(); + let pure_records = component_list + .iter() + .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() + }); + + Self::from_records(pure_records, binary_records) } } @@ -267,7 +312,8 @@ mod test { } struct MyParameter { - pure: Vec>, + pure_records: Vec>, + binary_records: Array2, } impl Parameter for MyParameter { @@ -276,12 +322,21 @@ mod test { type Binary = MyBinaryModel; fn from_records( pure_records: Vec>, - _: Option>>, - ) -> Result -where { - Ok(Self { - pure: pure_records.to_vec(), - }) + binary_records: Array2, + ) -> Self { + Self { + pure_records, + binary_records, + } + } + + fn records( + &self, + ) -> ( + &[PureRecord], + &Array2, + ) { + (&self.pure_records, &self.binary_records) } } @@ -302,7 +357,7 @@ where { "#; let records: Vec> = serde_json::from_str(r).expect("Unable to parse json."); - let p = MyParameter::from_records(records, None).unwrap(); - assert_eq!(p.pure[0].identifier.cas, "123-4-5") + let p = MyParameter::from_records(records, vec![]); + assert_eq!(p.pure_records[0].identifier.cas, "123-4-5") } } diff --git a/src/parameter/model_record.rs b/src/parameter/model_record.rs index af77a8f..ef0df9c 100644 --- a/src/parameter/model_record.rs +++ b/src/parameter/model_record.rs @@ -1,6 +1,6 @@ use super::chemical_record::ChemicalRecord; use super::identifier::Identifier; -use super::ParameterError; +use super::segment::SegmentRecord; use serde::{Deserialize, Serialize}; /// A collection of parameters of a pure substance. @@ -36,6 +36,30 @@ impl PureRecord { ideal_gas_record, } } + + /// Update the `PureRecord` from segment counts. + /// + /// The [FromSegments] trait needs to be implemented for both the model record + /// and the ideal gas record. + pub fn update_from_segments(&mut self, segments: S) + where + M: FromSegments, + I: FromSegments, + S: IntoIterator, f64)>, + { + self.molarweight = 0.0; + let mut model_segments = Vec::new(); + let mut ideal_gas_segments = Vec::new(); + for (s, n) in segments { + self.molarweight += s.molarweight * n; + model_segments.push((s.model_record, n)); + ideal_gas_segments.push(s.ideal_gas_record.map(|ig| (ig, n))); + } + self.model_record = Some(M::from_segments(&model_segments)); + + let ideal_gas_segments: Option> = ideal_gas_segments.into_iter().collect(); + self.ideal_gas_record = ideal_gas_segments.as_deref().map(I::from_segments); + } } impl std::fmt::Display for PureRecord @@ -78,15 +102,17 @@ pub type GroupContributionRecord = PureRecord; /// Trait for models that implement a homosegmented group contribution /// method pub trait FromSegments: Clone { - /// Type of the binary interaction parameter(s) used in this model. - type Binary; + /// Constructs the record from a list of segment records with their + /// number of occurences and possibly binary interaction parameters. + fn from_segments(segments: &[(Self, f64)]) -> Self; +} +/// Trait for models that implement a homosegmented group contribution +/// method and have a combining rule for binary interaction parameters. +pub trait FromSegmentsBinary: Clone { /// Constructs the record from a list of segment records with their /// number of occurences and possibly binary interaction parameters. - fn from_segments( - segments: &[(Self, f64)], - binary_records: Option<&[BinaryRecord]>, - ) -> Result; + fn from_segments_binary(segments: &[(Self, f64, f64)]) -> Self; } /// A collection of parameters that model interactions between two From 6528891a60b09d47420d1e00bd277c57fddcc05a Mon Sep 17 00:00:00 2001 From: Philipp Rehner Date: Thu, 25 Nov 2021 19:35:56 +0100 Subject: [PATCH 2/6] Fix tests --- src/cubic.rs | 2 +- src/parameter/mod.rs | 20 +++++--------------- 2 files changed, 6 insertions(+), 16 deletions(-) diff --git a/src/cubic.rs b/src/cubic.rs index e12dcc7..7ac9c7e 100644 --- a/src/cubic.rs +++ b/src/cubic.rs @@ -268,7 +268,7 @@ mod tests { let tc = propane.model_record.clone().unwrap().tc; let pc = propane.model_record.clone().unwrap().pc; let parameters = - PengRobinsonParameters::from_records(vec![propane.clone()], vec![vec![0.0]]); + PengRobinsonParameters::from_records(vec![propane.clone()], Array2::zeros((1, 1))); let pr = Rc::new(PengRobinson::new(parameters)); let cp = State::critical_point(&pr, None, None, VLEOptions::default())?; println!("{} {}", cp.temperature, cp.pressure(Contributions::Total)); diff --git a/src/parameter/mod.rs b/src/parameter/mod.rs index 2da84ab..da03edb 100644 --- a/src/parameter/mod.rs +++ b/src/parameter/mod.rs @@ -306,23 +306,18 @@ mod test { a: f64, } - #[derive(Debug, Clone, Serialize, Deserialize, Default)] - struct MyBinaryModel { - a: f64, - } - struct MyParameter { pure_records: Vec>, - binary_records: Array2, + binary_records: Array2, } impl Parameter for MyParameter { type Pure = MyPureModel; type IdealGas = JobackRecord; - type Binary = MyBinaryModel; + type Binary = f64; fn from_records( pure_records: Vec>, - binary_records: Array2, + binary_records: Array2, ) -> Self { Self { pure_records, @@ -330,12 +325,7 @@ mod test { } } - fn records( - &self, - ) -> ( - &[PureRecord], - &Array2, - ) { + fn records(&self) -> (&[PureRecord], &Array2) { (&self.pure_records, &self.binary_records) } } @@ -357,7 +347,7 @@ mod test { "#; let records: Vec> = serde_json::from_str(r).expect("Unable to parse json."); - let p = MyParameter::from_records(records, vec![]); + let p = MyParameter::from_records(records, Array2::zeros((1, 1))); assert_eq!(p.pure_records[0].identifier.cas, "123-4-5") } } From df328317781c33eff021714f748bd8c200b5bf5f Mon Sep 17 00:00:00 2001 From: Philipp Rehner Date: Fri, 26 Nov 2021 09:29:49 +0100 Subject: [PATCH 3/6] new `new_pure` and `new_binary` routines for `Parameter` to compensate for the now non-optional `binary_records` --- src/parameter/mod.rs | 22 ++++++++++++++++++++ src/python/parameter.rs | 46 ++++++++++++++++++++++++++++++++++------- 2 files changed, 60 insertions(+), 8 deletions(-) diff --git a/src/parameter/mod.rs b/src/parameter/mod.rs index da03edb..3ad9c90 100644 --- a/src/parameter/mod.rs +++ b/src/parameter/mod.rs @@ -41,6 +41,28 @@ where binary_records: Array2, ) -> 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) + } + + /// Creates parameters for a binary system from pure records and an optional + /// binary interaction parameter. + fn new_binary( + 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() + } + }); + Self::from_records(pure_records, binary_record) + } + /// Return the original pure and binary records that werde used to construct the parameters. #[allow(clippy::type_complexity)] fn records( diff --git a/src/python/parameter.rs b/src/python/parameter.rs index df8bcf9..dc80799 100644 --- a/src/python/parameter.rs +++ b/src/python/parameter.rs @@ -524,18 +524,48 @@ macro_rules! impl_parameter { /// ---------- /// pure_records : [PureRecord] /// A list of pure component parameters. - /// binary_records : [BinaryRecord], optional - /// A list of binary interaction parameters. + /// binary_records : numpy.ndarray[float] + /// A matrix of binary interaction parameters. #[staticmethod] - #[pyo3(text_signature = "(pure_records, binary_records=None)")] + #[pyo3(text_signature = "(pure_records, binary_records)")] fn from_records( pure_records: Vec, - binary_records: Option>, - ) -> Result { - Ok(Self(<$parameter>::from_records( + binary_records: &PyArray2, + ) -> Self { + Self(<$parameter>::from_records( pure_records.into_iter().map(|pr| pr.0).collect(), - binary_records.map(|br| br.into_iter().map(|br| br.0).collect()), - )?)) + binary_records.to_owned_array(), + )) + } + + /// Creates parameters for a pure component from a pure record. + /// + /// Parameters + /// ---------- + /// pure_record : PureRecord + /// The pure component parameters. + #[staticmethod] + #[pyo3(text_signature = "(pure_record)")] + fn new_pure(pure_record: PyPureRecord) -> Self { + Self(<$parameter>::new_pure(pure_record.0)) + } + + /// Creates parameters for a binary system from pure records and an optional + /// binary interaction parameter. + /// + /// Parameters + /// ---------- + /// pure_records : [PureRecord] + /// A list of pure component parameters. + /// binary_record : float, optional + /// The binary interaction parameter. + #[staticmethod] + #[pyo3(text_signature = "(pure_records, binary_record)")] + fn new_binary(pure_records: Vec, binary_record: Option) -> Self { + Self(<$parameter>::new_binary( + pure_records.into_iter().map(|pr| pr.0).collect(), + binary_record, + )) } /// Creates parameters from json files. From 185ddfd45dfbb4e84eae0bdf5fe9417840b6b519 Mon Sep 17 00:00:00 2001 From: Philipp Rehner Date: Fri, 26 Nov 2021 11:11:05 +0100 Subject: [PATCH 4/6] store parameters as `Rc` --- src/cubic.rs | 10 +++++----- src/parameter/mod.rs | 4 ++-- src/python/cubic.rs | 2 +- src/python/parameter.rs | 28 ++++++++++++++-------------- 4 files changed, 22 insertions(+), 22 deletions(-) diff --git a/src/cubic.rs b/src/cubic.rs index 7ac9c7e..5491bd6 100644 --- a/src/cubic.rs +++ b/src/cubic.rs @@ -10,6 +10,7 @@ use quantity::si::{SIArray1, SIUnit}; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::f64::consts::SQRT_2; +use std::rc::Rc; const KB_A3: f64 = 13806490.0; @@ -33,7 +34,6 @@ impl std::fmt::Display for PengRobinsonRecord { } /// Peng-Robinson parameters for one ore more substances. -#[derive(Serialize, Deserialize, Debug, Clone, Default)] pub struct PengRobinsonParameters { tc: Array1, a: Array1, @@ -143,11 +143,11 @@ impl Parameter for PengRobinsonParameters { } pub struct PengRobinson { - parameters: PengRobinsonParameters, + parameters: Rc, } impl PengRobinson { - pub fn new(parameters: PengRobinsonParameters) -> Self { + pub fn new(parameters: Rc) -> Self { Self { parameters } } } @@ -158,7 +158,7 @@ impl EquationOfState for PengRobinson { } fn subset(&self, component_list: &[usize]) -> Self { - Self::new(self.parameters.subset(component_list)) + Self::new(Rc::new(self.parameters.subset(component_list))) } fn compute_max_density(&self, moles: &Array1) -> f64 { @@ -269,7 +269,7 @@ mod tests { let pc = propane.model_record.clone().unwrap().pc; let parameters = PengRobinsonParameters::from_records(vec![propane.clone()], Array2::zeros((1, 1))); - let pr = Rc::new(PengRobinson::new(parameters)); + let pr = Rc::new(PengRobinson::new(Rc::new(parameters))); let cp = State::critical_point(&pr, None, None, VLEOptions::default())?; println!("{} {}", cp.temperature, cp.pressure(Contributions::Total)); assert_relative_eq!(cp.temperature, tc * KELVIN, max_relative = 1e-4); diff --git a/src/parameter/mod.rs b/src/parameter/mod.rs index 3ad9c90..f359e41 100644 --- a/src/parameter/mod.rs +++ b/src/parameter/mod.rs @@ -152,7 +152,7 @@ where /// /// The [FromSegments] trait needs to be implemented for both the model record /// and the ideal gas record. - fn from_segment_records( + fn from_segments( mut pure_records: Vec>, segment_records: Vec>, binary_segment_records: Option>>, @@ -280,7 +280,7 @@ where }) .transpose()?; - Self::from_segment_records(pure_records, segment_records, binary_records) + Self::from_segments(pure_records, segment_records, binary_records) } fn subset(&self, component_list: &[usize]) -> Self { diff --git a/src/python/cubic.rs b/src/python/cubic.rs index 31353ba..0fd33f4 100644 --- a/src/python/cubic.rs +++ b/src/python/cubic.rs @@ -53,7 +53,7 @@ impl_pure_record!( text_signature = "(pure_records, binary_records=None, substances=None, search_option='Name')" )] #[derive(Clone)] -pub struct PyPengRobinsonParameters(pub PengRobinsonParameters); +pub struct PyPengRobinsonParameters(pub Rc); impl_parameter!(PengRobinsonParameters, PyPengRobinsonParameters); diff --git a/src/python/parameter.rs b/src/python/parameter.rs index dc80799..1f656f4 100644 --- a/src/python/parameter.rs +++ b/src/python/parameter.rs @@ -532,10 +532,10 @@ macro_rules! impl_parameter { pure_records: Vec, binary_records: &PyArray2, ) -> Self { - Self(<$parameter>::from_records( + Self(Rc::new(<$parameter>::from_records( pure_records.into_iter().map(|pr| pr.0).collect(), - binary_records.to_owned_array(), - )) + binary_records.to_owned_array().mapv(f64::into), + ))) } /// Creates parameters for a pure component from a pure record. @@ -547,7 +547,7 @@ macro_rules! impl_parameter { #[staticmethod] #[pyo3(text_signature = "(pure_record)")] fn new_pure(pure_record: PyPureRecord) -> Self { - Self(<$parameter>::new_pure(pure_record.0)) + Self(Rc::new(<$parameter>::new_pure(pure_record.0))) } /// Creates parameters for a binary system from pure records and an optional @@ -562,10 +562,10 @@ macro_rules! impl_parameter { #[staticmethod] #[pyo3(text_signature = "(pure_records, binary_record)")] fn new_binary(pure_records: Vec, binary_record: Option) -> Self { - Self(<$parameter>::new_binary( + Self(Rc::new(<$parameter>::new_binary( pure_records.into_iter().map(|pr| pr.0).collect(), - binary_record, - )) + binary_record.map(f64::into), + ))) } /// Creates parameters from json files. @@ -595,12 +595,12 @@ macro_rules! impl_parameter { Some(o) => IdentifierOption::try_from(o)?, None => IdentifierOption::Name, }; - Ok(Self(<$parameter>::from_json( + Ok(Self(Rc::new(<$parameter>::from_json( &substances, pure_path, binary_path, io, - )?)) + )?))) } } }; @@ -629,11 +629,11 @@ macro_rules! impl_parameter_from_segments { segment_records: Vec, binary_segment_records: Option>, ) -> Result { - Ok(Self(<$parameter>::from_segments( + Ok(Self(Rc::new(<$parameter>::from_segments( pure_records.into_iter().map(|pr| pr.0).collect(), segment_records.into_iter().map(|sr| sr.0).collect(), - binary_segment_records.map(|r| r.into_iter().map(|r| r.0).collect()), - )?)) + binary_segment_records.map(|r| r.into_iter().map(|r| BinaryRecord{id1:r.0.id1,id2:r.0.id2,model_record:r.0.model_record.into()}).collect()), + )?))) } /// Creates parameters using segments from json file. @@ -666,13 +666,13 @@ macro_rules! impl_parameter_from_segments { Some(o) => IdentifierOption::try_from(o)?, None => IdentifierOption::Name, }; - Ok(Self(<$parameter>::from_json_segments( + Ok(Self(Rc::new(<$parameter>::from_json_segments( &substances, pure_path, segments_path, binary_path, io, - )?)) + )?))) } } }; From d12215cbb1fc169caa1d405c5fbcb96989c2d50d Mon Sep 17 00:00:00 2001 From: Philipp Rehner Date: Fri, 26 Nov 2021 11:26:40 +0100 Subject: [PATCH 5/6] fix tests --- src/state/builder.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/state/builder.rs b/src/state/builder.rs index 37a761c..5e6da59 100644 --- a/src/state/builder.rs +++ b/src/state/builder.rs @@ -18,7 +18,7 @@ use std::rc::Rc; /// # use approx::assert_relative_eq; /// # fn main() -> EosResult<()> { /// // Create a state for given T,V,N -/// let eos = Rc::new(PengRobinson::new(PengRobinsonParameters::new_simple(&[369.8], &[41.9 * 1e5], &[0.15], &[15.0]).unwrap())); +/// let eos = Rc::new(PengRobinson::new(Rc::new(PengRobinsonParameters::new_simple(&[369.8], &[41.9 * 1e5], &[0.15], &[15.0]).unwrap()))); /// let state = StateBuilder::new(&eos) /// .temperature(300.0 * KELVIN) /// .volume(12.5 * METER.powi(3)) @@ -27,7 +27,7 @@ use std::rc::Rc; /// assert_eq!(state.density, 0.2 * MOL / METER.powi(3)); /// /// // For a pure component, the composition does not need to be specified. -/// let eos = Rc::new(PengRobinson::new(PengRobinsonParameters::new_simple(&[369.8], &[41.9 * 1e5], &[0.15], &[15.0]).unwrap())); +/// let eos = Rc::new(PengRobinson::new(Rc::new(PengRobinsonParameters::new_simple(&[369.8], &[41.9 * 1e5], &[0.15], &[15.0]).unwrap()))); /// let state = StateBuilder::new(&eos) /// .temperature(300.0 * KELVIN) /// .volume(12.5 * METER.powi(3)) @@ -37,12 +37,12 @@ use std::rc::Rc; /// /// // The state can be constructed without providing any extensive property. /// let eos = Rc::new(PengRobinson::new( -/// PengRobinsonParameters::new_simple( +/// Rc::new(PengRobinsonParameters::new_simple( /// &[369.8, 305.4], /// &[41.9 * 1e5, 48.2 * 1e5], /// &[0.15, 0.10], /// &[15.0, 30.0] -/// ).unwrap() +/// ).unwrap()) /// )); /// let state = StateBuilder::new(&eos) /// .temperature(300.0 * KELVIN) From 922ecb93da7f00fdeb21cc6a0b26886c4a762f2d Mon Sep 17 00:00:00 2001 From: Philipp Rehner Date: Fri, 26 Nov 2021 14:42:23 +0100 Subject: [PATCH 6/6] remove obsolete error variant --- src/parameter/mod.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/parameter/mod.rs b/src/parameter/mod.rs index f359e41..64e347f 100644 --- a/src/parameter/mod.rs +++ b/src/parameter/mod.rs @@ -311,8 +311,6 @@ pub enum ParameterError { IdentifierNotFound(String), #[error("Information missing.")] InsufficientInformation, - #[error("Building model parameter from homo segments failed: {0}")] - HomoGc(String), #[error("Incompatible parameters: {0}")] IncompatibleParameters(String), }