From b138673712abb4950a987c90a0de6443d97edd8d Mon Sep 17 00:00:00 2001 From: Gernot Bauer Date: Mon, 20 Dec 2021 18:29:13 +0100 Subject: [PATCH 1/3] add function to read parameters from multiple different json files --- example/pr-alcohols.json | 18 ++++++ example/pr-alkanes.json | 34 +++++++++++ src/cubic.rs | 18 ++++++ src/parameter/mod.rs | 120 +++++++++++++++++++++++++++++++++------ src/python/cubic.rs | 12 ++-- src/python/parameter.rs | 32 ++++++++++- 6 files changed, 211 insertions(+), 23 deletions(-) create mode 100644 example/pr-alcohols.json create mode 100644 example/pr-alkanes.json diff --git a/example/pr-alcohols.json b/example/pr-alcohols.json new file mode 100644 index 0000000..2e16376 --- /dev/null +++ b/example/pr-alcohols.json @@ -0,0 +1,18 @@ +[ + { + "identifier": { + "cas": "67-56-1", + "name": "methanol", + "iupac_name": "methanol", + "smiles": "CO", + "inchi": "InChI=1/CH4O/c1-2/h2H,1H3", + "formula": "CH4O" + }, + "model_record": { + "tc": 512.60, + "pc": 8096000.0, + "acentric_factor": 0.559 + }, + "molarweight": 32.04 + } +] diff --git a/example/pr-alkanes.json b/example/pr-alkanes.json new file mode 100644 index 0000000..9aec553 --- /dev/null +++ b/example/pr-alkanes.json @@ -0,0 +1,34 @@ +[ + { + "identifier": { + "cas": "74-98-6", + "name": "propane", + "iupac_name": "propane", + "smiles": "CCC", + "inchi": "InChI=1/C3H8/c1-3-2/h3H2,1-2H3", + "formula": "C3H8" + }, + "model_record": { + "tc": 369.96, + "pc": 4250000.0, + "acentric_factor": 0.153 + }, + "molarweight": 44.0962 + }, + { + "identifier": { + "cas": "106-97-8", + "name": "butane", + "iupac_name": "butane", + "smiles": "CCCC", + "inchi": "InChI=1/C4H10/c1-3-4-2/h3-4H2,1-2H3", + "formula": "C4H10" + }, + "model_record": { + "tc": 425.2, + "pc": 3800000.0, + "acentric_factor": 0.199 + }, + "molarweight": 58.123 + } +] \ No newline at end of file diff --git a/src/cubic.rs b/src/cubic.rs index 6b67a2e..e2c5a54 100644 --- a/src/cubic.rs +++ b/src/cubic.rs @@ -69,6 +69,24 @@ pub struct PengRobinsonParameters { joback_records: Option>, } +impl std::fmt::Display for PengRobinsonParameters { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.pure_records + .iter() + .zip(self.molarweight.iter()) + .try_for_each(|(r, mw)| { + let cas = r.identifier.cas.clone(); + let mr = r.model_record.clone().unwrap(); + writeln!( + f, + "Cas: {}, mw: {} g/mol, tc: {} K, pc: {} Pa, acentric factor: {}", + cas, mw, mr.tc, mr.pc, mr.acentric_factor + ) + })?; + writeln!(f, "\nkappa: {}", self.kappa) + } +} + impl PengRobinsonParameters { /// Build a simple parameter set without binary interaction parameters. pub fn new_simple( diff --git a/src/parameter/mod.rs b/src/parameter/mod.rs index 64e347f..f21f695 100644 --- a/src/parameter/mod.rs +++ b/src/parameter/mod.rs @@ -74,7 +74,7 @@ where /// Creates parameters from substance information stored in json files. fn from_json

( - substances: &[&str], + substances: Vec<&str>, file_pure: P, file_binary: Option

, search_option: IdentifierOption, @@ -82,24 +82,108 @@ where where P: AsRef, { - let queried: IndexSet = substances - .iter() - .map(|identifier| identifier.to_string()) - .collect(); - let file = File::open(file_pure)?; - let reader = BufReader::new(file); + Self::from_multiple_json(&[(substances, file_pure)], file_binary, search_option) + // let queried: IndexSet = substances + // .iter() + // .map(|identifier| identifier.to_string()) + // .collect(); + // let file = File::open(file_pure)?; + // let reader = BufReader::new(file); + + // let pure_records: Vec> = + // serde_json::from_reader(reader)?; + // let mut record_map: HashMap<_, _> = pure_records + // .into_iter() + // .filter_map(|record| { + // record + // .identifier + // .as_string(search_option) + // .map(|i| (i, record)) + // }) + // .collect(); + + // // Compare queried components and available components + // let available: IndexSet = record_map + // .keys() + // .map(|identifier| identifier.to_string()) + // .collect(); + // if !queried.is_subset(&available) { + // let missing: Vec = queried.difference(&available).cloned().collect(); + // let msg = format!("{:?}", missing); + // return Err(ParameterError::ComponentsNotFound(msg)); + // }; + // let p: Vec<_> = queried + // .iter() + // .filter_map(|identifier| record_map.remove(&identifier.clone())) + // .collect(); + + // // 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)?; + // binary_records + // .into_iter() + // .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() + // } else { + // HashMap::with_capacity(0) + // }; + + // 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)) + } - let pure_records: Vec> = - serde_json::from_reader(reader)?; - let mut record_map: HashMap<_, _> = pure_records - .into_iter() - .filter_map(|record| { - record - .identifier - .as_string(search_option) - .map(|i| (i, record)) - }) - .collect(); + /// Creates parameters from substance information stored in multiple json files. + fn from_multiple_json

( + input: &[(Vec<&str>, P)], + file_binary: Option

, + search_option: IdentifierOption, + ) -> Result + where + P: AsRef, + { + let mut queried: IndexSet = IndexSet::new(); + let mut record_map: HashMap> = + HashMap::new(); + + for (substances, file) in input { + substances.iter().for_each(|identifier| { + let _ = queried.insert(identifier.to_string()); + }); + let f = File::open(file)?; + let reader = BufReader::new(f); + + let pure_records: Vec> = + serde_json::from_reader(reader)?; + + pure_records + .into_iter() + .filter_map(|record| { + record + .identifier + .as_string(search_option) + .map(|i| (i, record)) + }) + .for_each(|(i, r)| { + let _ = record_map.insert(i, r); + }); + } // Compare queried components and available components let available: IndexSet = record_map diff --git a/src/python/cubic.rs b/src/python/cubic.rs index 762ac20..de51648 100644 --- a/src/python/cubic.rs +++ b/src/python/cubic.rs @@ -14,7 +14,6 @@ use std::collections::HashMap; use std::convert::TryFrom; use std::rc::Rc; - /// A pure substance parameter for the Peng-Robinson equation of state. #[pyclass(name = "PengRobinsonRecord", unsendable)] #[derive(Clone)] @@ -63,6 +62,13 @@ pub struct PyPengRobinsonParameters(pub Rc); impl_parameter!(PengRobinsonParameters, PyPengRobinsonParameters); +#[pymethods] +impl PyPengRobinsonParameters { + fn __repr__(&self) -> PyResult { + Ok(self.0.to_string()) + } +} + /* EQUATION OF STATE */ /// A simple version of the Peng-Robinson equation of state. @@ -76,9 +82,7 @@ impl_parameter!(PengRobinsonParameters, PyPengRobinsonParameters); /// ------- /// PengRobinson #[pyclass(name = "PengRobinson", unsendable)] -#[pyo3( - text_signature = "(parameters)" -)] +#[pyo3(text_signature = "(parameters)")] #[derive(Clone)] pub struct PyPengRobinson(pub Rc); diff --git a/src/python/parameter.rs b/src/python/parameter.rs index 30181ad..08554dd 100644 --- a/src/python/parameter.rs +++ b/src/python/parameter.rs @@ -596,12 +596,42 @@ macro_rules! impl_parameter { None => IdentifierOption::Name, }; Ok(Self(Rc::new(<$parameter>::from_json( - &substances, + substances, pure_path, binary_path, io, )?))) } + + /// Creates parameters from json files. + /// + /// Parameters + /// ---------- + /// input : List[Tuple[List[str], str]] + /// The substances to search and their respective parameter files. + /// E.g. [(["methane", "propane"], "parameters/alkanes.json"), (["methanol"], "parameters/alcohols.json")] + /// binary_path : str, optional + /// Path to file containing binary substance parameters. + /// search_option : str, optional, defaults to "Name" + /// Identifier that is used to search substance. + /// One of 'Name', 'Cas', 'Inchi', 'IupacName', 'Formula', 'Smiles' + #[staticmethod] + #[pyo3(text_signature = "(input, binary_path=None, search_option='Name')")] + fn from_multiple_json( + input: Vec<(Vec<&str>, &str)>, + binary_path: Option<&str>, + search_option: Option<&str>, + ) -> Result { + let io = match search_option { + Some(o) => IdentifierOption::try_from(o)?, + None => IdentifierOption::Name, + }; + Ok(Self(Rc::new(<$parameter>::from_multiple_json( + &input, + binary_path, + io, + )?))) + } } }; } From 252724083c9221debf99f0feea1f9ed25c83981e Mon Sep 17 00:00:00 2001 From: Gernot Bauer Date: Tue, 21 Dec 2021 14:45:39 +0100 Subject: [PATCH 2/3] cleanup --- src/cubic.rs | 13 ++------- src/parameter/mod.rs | 64 -------------------------------------------- src/python/mod.rs | 1 - 3 files changed, 2 insertions(+), 76 deletions(-) diff --git a/src/cubic.rs b/src/cubic.rs index e2c5a54..c9863c4 100644 --- a/src/cubic.rs +++ b/src/cubic.rs @@ -73,17 +73,8 @@ impl std::fmt::Display for PengRobinsonParameters { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { self.pure_records .iter() - .zip(self.molarweight.iter()) - .try_for_each(|(r, mw)| { - let cas = r.identifier.cas.clone(); - let mr = r.model_record.clone().unwrap(); - writeln!( - f, - "Cas: {}, mw: {} g/mol, tc: {} K, pc: {} Pa, acentric factor: {}", - cas, mw, mr.tc, mr.pc, mr.acentric_factor - ) - })?; - writeln!(f, "\nkappa: {}", self.kappa) + .try_for_each(|pr| writeln!(f, "{}", pr.to_string()))?; + writeln!(f, "\nk_ij:\n{}", self.k_ij) } } diff --git a/src/parameter/mod.rs b/src/parameter/mod.rs index f21f695..f51f3f6 100644 --- a/src/parameter/mod.rs +++ b/src/parameter/mod.rs @@ -83,70 +83,6 @@ where P: AsRef, { Self::from_multiple_json(&[(substances, file_pure)], file_binary, search_option) - // let queried: IndexSet = substances - // .iter() - // .map(|identifier| identifier.to_string()) - // .collect(); - // let file = File::open(file_pure)?; - // let reader = BufReader::new(file); - - // let pure_records: Vec> = - // serde_json::from_reader(reader)?; - // let mut record_map: HashMap<_, _> = pure_records - // .into_iter() - // .filter_map(|record| { - // record - // .identifier - // .as_string(search_option) - // .map(|i| (i, record)) - // }) - // .collect(); - - // // Compare queried components and available components - // let available: IndexSet = record_map - // .keys() - // .map(|identifier| identifier.to_string()) - // .collect(); - // if !queried.is_subset(&available) { - // let missing: Vec = queried.difference(&available).cloned().collect(); - // let msg = format!("{:?}", missing); - // return Err(ParameterError::ComponentsNotFound(msg)); - // }; - // let p: Vec<_> = queried - // .iter() - // .filter_map(|identifier| record_map.remove(&identifier.clone())) - // .collect(); - - // // 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)?; - // binary_records - // .into_iter() - // .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() - // } else { - // HashMap::with_capacity(0) - // }; - - // 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 substance information stored in multiple json files. diff --git a/src/python/mod.rs b/src/python/mod.rs index 18ed0e2..d9482c0 100644 --- a/src/python/mod.rs +++ b/src/python/mod.rs @@ -108,7 +108,6 @@ pub fn feos_core(py: Python<'_>, m: &PyModule) -> PyResult<()> { import sys sys.modules['feos_core.si'] = quantity sys.modules['feos_core.user_defined'] = user_defined -sys.modules['feos_core.user_defined.num_dual'] = user_defined.num_dual sys.modules['feos_core.cubic'] = cubic ", None, From 4ab70c7853e3b129d757bd9e706e05ba553b5500 Mon Sep 17 00:00:00 2001 From: Gernot Bauer Date: Tue, 21 Dec 2021 14:57:59 +0100 Subject: [PATCH 3/3] added error when two components with same identifier are added --- src/parameter/mod.rs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/parameter/mod.rs b/src/parameter/mod.rs index f51f3f6..db16c06 100644 --- a/src/parameter/mod.rs +++ b/src/parameter/mod.rs @@ -99,9 +99,17 @@ where HashMap::new(); for (substances, file) in input { - substances.iter().for_each(|identifier| { - let _ = queried.insert(identifier.to_string()); - }); + substances.iter().try_for_each(|identifier| { + match queried.insert(identifier.to_string()) { + true => Ok(()), + false => Err(ParameterError::IncompatibleParameters(String::from( + format!( + "tried to add substance '{}' to system but it is already present.", + identifier.to_string() + ), + ))), + } + })?; let f = File::open(file)?; let reader = BufReader::new(f);