Skip to content
This repository was archived by the owner on Jun 14, 2022. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ 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 `pure_records` getter in the `impl_parameter` macro. [#54](https://github.com/feos-org/feos-core/pull/54)

### Changed
- Changed datatype for binary parameters in interfaces of the `from_records` and `new_binary` methods for parameters to take either numpy arrays of `f64` or a list of `BinaryRecord` as input. [#54](https://github.com/feos-org/feos-core/pull/54)

## [0.2.0] - 2022-04-12
### Added
Expand Down
5 changes: 4 additions & 1 deletion build_wheel/src/cubic.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use feos_core::cubic::PengRobinson;
use feos_core::python::cubic::{PyPengRobinsonParameters, PyPengRobinsonRecord, PyPureRecord};
use feos_core::python::cubic::{
PyBinaryRecord, PyPengRobinsonParameters, PyPengRobinsonRecord, PyPureRecord,
};
use feos_core::*;
use numpy::convert::ToPyArray;
use numpy::{PyArray1, PyArray2};
Expand Down Expand Up @@ -46,6 +48,7 @@ pub fn cubic(_py: Python<'_>, m: &PyModule) -> PyResult<()> {
m.add_class::<PyPengRobinsonParameters>()?;
m.add_class::<PyPengRobinsonRecord>()?;
m.add_class::<PyPureRecord>()?;
m.add_class::<PyBinaryRecord>()?;
m.add_class::<PyState>()?;
m.add_class::<PyPhaseDiagram>()?;
m.add_class::<PyPhaseEquilibrium>()?;
Expand Down
3 changes: 1 addition & 2 deletions src/cubic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -316,8 +316,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.clone()], Array2::zeros((1, 1)));
let parameters = PengRobinsonParameters::from_records(vec![propane], Array2::zeros((1, 1)));
let pr = Rc::new(PengRobinson::new(Rc::new(parameters)));
let options = SolverOptions::new().verbosity(Verbosity::Iter);
let cp = State::critical_point(&pr, None, None, options)?;
Expand Down
255 changes: 219 additions & 36 deletions src/parameter/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,38 @@ where
&Array2<Self::Binary>,
);

/// Helper function to build matrix from list of records in correct order.
///
/// If the identifiers in `binary_records` are not a subset of those in
/// `pure_records`, the `Default` implementation of Self::Binary is used.
fn binary_matrix_from_records(
pure_records: &Vec<PureRecord<Self::Pure, Self::IdealGas>>,
binary_records: &[BinaryRecord<Identifier, Self::Binary>],
search_option: IdentifierOption,
) -> Array2<Self::Binary> {
// Build Hashmap (id, id) -> BinaryRecord
let binary_map: HashMap<(String, String), Self::Binary> = {
binary_records
.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.clone())))
})
.collect()
};
let n = pure_records.len();
Array2::from_shape_fn([n, n], |(i, j)| {
let id1 = pure_records[i].identifier.as_string(search_option).unwrap();
let id2 = pure_records[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()
})
}

/// Creates parameters from substance information stored in json files.
fn from_json<P>(
substances: Vec<&str>,
Expand Down Expand Up @@ -135,41 +167,20 @@ where
let msg = format!("{:?}", missing);
return Err(ParameterError::ComponentsNotFound(msg));
};
let p: Vec<_> = queried
let p = 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 binary_records = if let Some(path) = file_binary {
let file = File::open(path)?;
let reader = BufReader::new(file);
let binary_records: Vec<BinaryRecord<Identifier, Self::Binary>> =
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()
serde_json::from_reader(reader)?
} else {
HashMap::with_capacity(0)
Vec::new()
};

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 record_matrix = Self::binary_matrix_from_records(&p, &binary_records, search_option);
Ok(Self::from_records(p, record_matrix))
}

/// Creates parameters from the molecular structure and segment information.
Expand Down Expand Up @@ -340,39 +351,110 @@ mod test {
use super::*;
use crate::joback::JobackRecord;
use serde::{Deserialize, Serialize};
use std::convert::TryFrom;

#[derive(Debug, Clone, Serialize, Deserialize, Default)]
struct MyPureModel {
a: f64,
}

#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq)]
struct MyBinaryModel {
b: f64,
}

impl TryFrom<f64> for MyBinaryModel {
type Error = &'static str;
fn try_from(f: f64) -> Result<Self, Self::Error> {
Ok(Self { b: f })
}
}

struct MyParameter {
pure_records: Vec<PureRecord<MyPureModel, JobackRecord>>,
binary_records: Array2<f64>,
binary_records: Array2<MyBinaryModel>,
}

impl Parameter for MyParameter {
type Pure = MyPureModel;
type IdealGas = JobackRecord;
type Binary = f64;
type Binary = MyBinaryModel;
fn from_records(
pure_records: Vec<PureRecord<MyPureModel, JobackRecord>>,
binary_records: Array2<f64>,
binary_records: Array2<MyBinaryModel>,
) -> Self {
Self {
pure_records,
binary_records,
}
}

fn records(&self) -> (&[PureRecord<MyPureModel, JobackRecord>], &Array2<f64>) {
fn records(
&self,
) -> (
&[PureRecord<MyPureModel, JobackRecord>],
&Array2<MyBinaryModel>,
) {
(&self.pure_records, &self.binary_records)
}
}

#[test]
fn from_records() {
let r = r#"
let pr_json = r#"
[
{
"identifier": {
"cas": "123-4-5"
},
"molarweight": 16.0426,
"model_record": {
"a": 0.1
}
},
{
"identifier": {
"cas": "678-9-1"
},
"molarweight": 32.08412,
"model_record": {
"a": 0.2
}
}
]
"#;
let br_json = r#"
[
{
"id1": {
"cas": "123-4-5"
},
"id2": {
"cas": "678-9-1"
},
"model_record": {
"b": 12.0
}
}
]
"#;
let pure_records = serde_json::from_str(pr_json).expect("Unable to parse json.");
let binary_records: Vec<_> = serde_json::from_str(br_json).expect("Unable to parse json.");
let binary_matrix = MyParameter::binary_matrix_from_records(
&pure_records,
&binary_records,
IdentifierOption::Cas,
);
let p = MyParameter::from_records(pure_records, binary_matrix);

assert_eq!(p.pure_records[0].identifier.cas, "123-4-5");
assert_eq!(p.pure_records[1].identifier.cas, "678-9-1");
assert_eq!(p.binary_records[[0, 1]].b, 12.0)
}

#[test]
fn from_records_missing_binary() {
let pr_json = r#"
[
{
"identifier": {
Expand All @@ -382,12 +464,113 @@ mod test {
"model_record": {
"a": 0.1
}
},
{
"identifier": {
"cas": "678-9-1"
},
"molarweight": 32.08412,
"model_record": {
"a": 0.2
}
}
]
"#;
let br_json = r#"
[
{
"id1": {
"cas": "123-4-5"
},
"id2": {
"cas": "000-00-0"
},
"model_record": {
"b": 12.0
}
}
]
"#;
let pure_records = serde_json::from_str(pr_json).expect("Unable to parse json.");
let binary_records: Vec<_> = serde_json::from_str(br_json).expect("Unable to parse json.");
let binary_matrix = MyParameter::binary_matrix_from_records(
&pure_records,
&binary_records,
IdentifierOption::Cas,
);
let p = MyParameter::from_records(pure_records, binary_matrix);

assert_eq!(p.pure_records[0].identifier.cas, "123-4-5");
assert_eq!(p.pure_records[1].identifier.cas, "678-9-1");
assert_eq!(p.binary_records[[0, 1]], MyBinaryModel::default());
assert_eq!(p.binary_records[[0, 1]].b, 0.0)
}

#[test]
fn from_records_correct_binary_order() {
let pr_json = r#"
[
{
"identifier": {
"cas": "000-0-0"
},
"molarweight": 32.08412,
"model_record": {
"a": 0.2
}
},
{
"identifier": {
"cas": "123-4-5"
},
"molarweight": 16.0426,
"model_record": {
"a": 0.1
}
},
{
"identifier": {
"cas": "678-9-1"
},
"molarweight": 32.08412,
"model_record": {
"a": 0.2
}
}
]
"#;
let br_json = r#"
[
{
"id1": {
"cas": "123-4-5"
},
"id2": {
"cas": "678-9-1"
},
"model_record": {
"b": 12.0
}
}
]
"#;
let records: Vec<PureRecord<MyPureModel, JobackRecord>> =
serde_json::from_str(r).expect("Unable to parse json.");
let p = MyParameter::from_records(records, Array2::zeros((1, 1)));
assert_eq!(p.pure_records[0].identifier.cas, "123-4-5")
let pure_records = serde_json::from_str(pr_json).expect("Unable to parse json.");
let binary_records: Vec<_> = serde_json::from_str(br_json).expect("Unable to parse json.");
let binary_matrix = MyParameter::binary_matrix_from_records(
&pure_records,
&binary_records,
IdentifierOption::Cas,
);
let p = MyParameter::from_records(pure_records, binary_matrix);

assert_eq!(p.pure_records[0].identifier.cas, "000-0-0");
assert_eq!(p.pure_records[1].identifier.cas, "123-4-5");
assert_eq!(p.pure_records[2].identifier.cas, "678-9-1");
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);
}
}
14 changes: 8 additions & 6 deletions src/python/cubic.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
use crate::cubic::{PengRobinsonParameters, PengRobinsonRecord};
use crate::joback::JobackRecord;
use crate::parameter::{IdentifierOption, Parameter, ParameterError, PureRecord};
use crate::parameter::{
BinaryRecord, Identifier, IdentifierOption, Parameter, ParameterError, PureRecord,
};
use crate::python::joback::PyJobackRecord;
use crate::python::parameter::PyIdentifier;
use crate::*;
use numpy::PyArray2;
use numpy::PyReadonlyArray2;
use pyo3::exceptions::PyTypeError;
use pyo3::prelude::*;
use std::convert::TryFrom;
use std::convert::{TryFrom, TryInto};
use std::rc::Rc;

/// A pure substance parameter for the Peng-Robinson equation of state.
Expand Down Expand Up @@ -35,6 +38,8 @@ impl_pure_record!(
PyJobackRecord
);

impl_binary_record!();

/// Create a set of Peng-Robinson parameters from records.
///
/// Parameters
Expand All @@ -54,9 +59,6 @@ impl_pure_record!(
/// -------
/// PengRobinsonParameters
#[pyclass(name = "PengRobinsonParameters", unsendable)]
#[pyo3(
text_signature = "(pure_records, binary_records=None, substances=None, search_option='Name')"
)]
#[derive(Clone)]
pub struct PyPengRobinsonParameters(pub Rc<PengRobinsonParameters>);

Expand Down
Loading