Skip to content

Commit c5667fb

Browse files
authored
Optional binary records and new parameter constructor (#169)
1 parent af8e361 commit c5667fb

File tree

16 files changed

+198
-144
lines changed

16 files changed

+198
-144
lines changed

feos-core/CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
88
### Added
99
- Added `Components`, `Residual`, `IdealGas` and `DeBroglieWavelength` traits to decouple ideal gas models from residual models. [#158](https://github.com/feos-org/feos/pull/158)
1010
- Added `JobackParameters` struct that implements `Parameters` including Python bindings. [#158](https://github.com/feos-org/feos/pull/158)
11+
- Added `Parameter::from_model_records` as a simpler interface to generate parameters. [#169](https://github.com/feos-org/feos/pull/169)
1112

1213
### Changed
1314
- 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
1920
- Moved `StateVec` into own file and module. [#158](https://github.com/feos-org/feos/pull/158)
2021
- 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)
2122
- 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)
23+
- Made the binary records in the constructions and getters of the `Parameter` trait optional. [#169](https://github.com/feos-org/feos/pull/169)
24+
- 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)
2225

2326
### Removed
2427
- Removed `EquationOfState` trait. [#158](https://github.com/feos-org/feos/pull/158)

feos-core/src/cubic.rs

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -100,10 +100,7 @@ impl PengRobinsonParameters {
100100
PureRecord::new(id, molarweight[i], record)
101101
})
102102
.collect();
103-
Ok(PengRobinsonParameters::from_records(
104-
records,
105-
Array2::zeros([pc.len(); 2]),
106-
))
103+
Ok(PengRobinsonParameters::from_records(records, None))
107104
}
108105
}
109106

@@ -114,7 +111,7 @@ impl Parameter for PengRobinsonParameters {
114111
/// Creates parameters from pure component records.
115112
fn from_records(
116113
pure_records: Vec<PureRecord<Self::Pure>>,
117-
binary_records: Array2<Self::Binary>,
114+
binary_records: Option<Array2<Self::Binary>>,
118115
) -> Self {
119116
let n = pure_records.len();
120117

@@ -133,19 +130,21 @@ impl Parameter for PengRobinsonParameters {
133130
kappa[i] = 0.37464 + (1.54226 - 0.26992 * r.acentric_factor) * r.acentric_factor;
134131
}
135132

133+
let k_ij = binary_records.unwrap_or_else(|| Array2::zeros([n; 2]));
134+
136135
Self {
137136
tc,
138137
a,
139138
b,
140-
k_ij: binary_records,
139+
k_ij,
141140
kappa,
142141
molarweight,
143142
pure_records,
144143
}
145144
}
146145

147-
fn records(&self) -> (&[PureRecord<PengRobinsonRecord>], &Array2<f64>) {
148-
(&self.pure_records, &self.k_ij)
146+
fn records(&self) -> (&[PureRecord<PengRobinsonRecord>], Option<&Array2<f64>>) {
147+
(&self.pure_records, Some(&self.k_ij))
149148
}
150149
}
151150

@@ -294,7 +293,7 @@ mod tests {
294293
let propane = mixture[0].clone();
295294
let tc = propane.model_record.tc;
296295
let pc = propane.model_record.pc;
297-
let parameters = PengRobinsonParameters::from_records(vec![propane], Array2::zeros((1, 1)));
296+
let parameters = PengRobinsonParameters::new_pure(propane);
298297
let pr = Arc::new(PengRobinson::new(Arc::new(parameters)));
299298
let options = SolverOptions::new().verbosity(Verbosity::Iter);
300299
let cp = State::critical_point(&pr, None, None, options)?;

feos-core/src/joback.rs

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,6 @@ pub struct JobackParameters {
7272
d: Array1<f64>,
7373
e: Array1<f64>,
7474
pure_records: Vec<PureRecord<JobackRecord>>,
75-
binary_records: Array2<JobackBinaryRecord>,
7675
}
7776

7877
impl Parameter for JobackParameters {
@@ -81,11 +80,10 @@ impl Parameter for JobackParameters {
8180

8281
fn from_records(
8382
pure_records: Vec<PureRecord<Self::Pure>>,
84-
_binary_records: Array2<Self::Binary>,
83+
_binary_records: Option<Array2<Self::Binary>>,
8584
) -> Self {
8685
let n = pure_records.len();
8786

88-
let binary_records = Array::from_elem((n, n), JobackBinaryRecord);
8987
let mut a = Array::zeros(n);
9088
let mut b = Array::zeros(n);
9189
let mut c = Array::zeros(n);
@@ -108,12 +106,11 @@ impl Parameter for JobackParameters {
108106
d,
109107
e,
110108
pure_records,
111-
binary_records,
112109
}
113110
}
114111

115-
fn records(&self) -> (&[PureRecord<Self::Pure>], &Array2<Self::Binary>) {
116-
(&self.pure_records, &self.binary_records)
112+
fn records(&self) -> (&[PureRecord<Self::Pure>], Option<&Array2<Self::Binary>>) {
113+
(&self.pure_records, None)
117114
}
118115
}
119116

@@ -188,11 +185,7 @@ impl Components for Joback {
188185
component_list
189186
.iter()
190187
.for_each(|&i| records.push(self.parameters.pure_records[i].clone()));
191-
let n = component_list.len();
192-
Self::new(Arc::new(JobackParameters::from_records(
193-
records,
194-
Array::from_elem((n, n), JobackBinaryRecord),
195-
)))
188+
Self::new(Arc::new(JobackParameters::from_records(records, None)))
196189
}
197190
}
198191

feos-core/src/lib.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,6 @@ mod tests {
202202
use crate::EosResult;
203203
use crate::StateBuilder;
204204
use approx::*;
205-
use ndarray::Array2;
206205
use quantity::si::*;
207206
use std::sync::Arc;
208207

@@ -248,7 +247,7 @@ mod tests {
248247
fn validate_residual_properties() -> EosResult<()> {
249248
let mixture = pure_record_vec();
250249
let propane = mixture[0].clone();
251-
let parameters = PengRobinsonParameters::from_records(vec![propane], Array2::zeros((1, 1)));
250+
let parameters = PengRobinsonParameters::new_pure(propane);
252251
let residual = Arc::new(PengRobinson::new(Arc::new(parameters)));
253252
let joback_parameters = Arc::new(JobackParameters::new_pure(PureRecord::new(
254253
Identifier::default(),

feos-core/src/parameter/mod.rs

Lines changed: 48 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -35,13 +35,12 @@ where
3535
/// Creates parameters from records for pure substances and possibly binary parameters.
3636
fn from_records(
3737
pure_records: Vec<PureRecord<Self::Pure>>,
38-
binary_records: Array2<Self::Binary>,
38+
binary_records: Option<Array2<Self::Binary>>,
3939
) -> Self;
4040

4141
/// Creates parameters for a pure component from a pure record.
4242
fn new_pure(pure_record: PureRecord<Self::Pure>) -> Self {
43-
let binary_record = Array2::from_elem([1, 1], Self::Binary::default());
44-
Self::from_records(vec![pure_record], binary_record)
43+
Self::from_records(vec![pure_record], None)
4544
}
4645

4746
/// Creates parameters for a binary system from pure records and an optional
@@ -50,19 +49,31 @@ where
5049
pure_records: Vec<PureRecord<Self::Pure>>,
5150
binary_record: Option<Self::Binary>,
5251
) -> Self {
53-
let binary_record = Array2::from_shape_fn([2, 2], |(i, j)| {
54-
if i == j {
55-
Self::Binary::default()
56-
} else {
57-
binary_record.clone().unwrap_or_default()
58-
}
52+
let binary_record = binary_record.map(|br| {
53+
Array2::from_shape_fn([2, 2], |(i, j)| {
54+
if i == j {
55+
Self::Binary::default()
56+
} else {
57+
br.clone()
58+
}
59+
})
5960
});
6061
Self::from_records(pure_records, binary_record)
6162
}
6263

64+
/// Creates parameters from model records with default values for the molar weight,
65+
/// identifiers, and binary interaction parameters.
66+
fn from_model_records(model_records: Vec<Self::Pure>) -> Self {
67+
let pure_records = model_records
68+
.into_iter()
69+
.map(|r| PureRecord::new(Default::default(), Default::default(), r))
70+
.collect();
71+
Self::from_records(pure_records, None)
72+
}
73+
6374
/// Return the original pure and binary records that were used to construct the parameters.
6475
#[allow(clippy::type_complexity)]
65-
fn records(&self) -> (&[PureRecord<Self::Pure>], &Array2<Self::Binary>);
76+
fn records(&self) -> (&[PureRecord<Self::Pure>], Option<&Array2<Self::Binary>>);
6677

6778
/// Helper function to build matrix from list of records in correct order.
6879
///
@@ -73,7 +84,11 @@ where
7384
pure_records: &Vec<PureRecord<Self::Pure>>,
7485
binary_records: &[BinaryRecord<Identifier, Self::Binary>],
7586
search_option: IdentifierOption,
76-
) -> Array2<Self::Binary> {
87+
) -> Option<Array2<Self::Binary>> {
88+
if binary_records.is_empty() {
89+
return None;
90+
}
91+
7792
// Build Hashmap (id, id) -> BinaryRecord
7893
let binary_map: HashMap<(String, String), Self::Binary> = {
7994
binary_records
@@ -86,7 +101,7 @@ where
86101
.collect()
87102
};
88103
let n = pure_records.len();
89-
Array2::from_shape_fn([n, n], |(i, j)| {
104+
Some(Array2::from_shape_fn([n, n], |(i, j)| {
90105
let id1 = pure_records[i]
91106
.identifier
92107
.as_string(search_option)
@@ -106,7 +121,7 @@ where
106121
.or_else(|| binary_map.get(&(id2, id1)))
107122
.cloned()
108123
.unwrap_or_default()
109-
})
124+
}))
110125
}
111126

112127
/// Creates parameters from substance information stored in json files.
@@ -250,7 +265,7 @@ where
250265
}
251266
}
252267

253-
Ok(Self::from_records(pure_records, binary_records))
268+
Ok(Self::from_records(pure_records, Some(binary_records)))
254269
}
255270

256271
/// Creates parameters from segment information stored in json files.
@@ -331,8 +346,10 @@ where
331346
.map(|&i| pure_records[i].clone())
332347
.collect();
333348
let n = component_list.len();
334-
let binary_records = Array2::from_shape_fn([n, n], |(i, j)| {
335-
binary_records[(component_list[i], component_list[j])].clone()
349+
let binary_records = binary_records.map(|br| {
350+
Array2::from_shape_fn([n, n], |(i, j)| {
351+
br[(component_list[i], component_list[j])].clone()
352+
})
336353
});
337354

338355
Self::from_records(pure_records, binary_records)
@@ -484,24 +501,24 @@ mod test {
484501

485502
struct MyParameter {
486503
pure_records: Vec<PureRecord<MyPureModel>>,
487-
binary_records: Array2<MyBinaryModel>,
504+
binary_records: Option<Array2<MyBinaryModel>>,
488505
}
489506

490507
impl Parameter for MyParameter {
491508
type Pure = MyPureModel;
492509
type Binary = MyBinaryModel;
493510
fn from_records(
494511
pure_records: Vec<PureRecord<MyPureModel>>,
495-
binary_records: Array2<MyBinaryModel>,
512+
binary_records: Option<Array2<MyBinaryModel>>,
496513
) -> Self {
497514
Self {
498515
pure_records,
499516
binary_records,
500517
}
501518
}
502519

503-
fn records(&self) -> (&[PureRecord<MyPureModel>], &Array2<MyBinaryModel>) {
504-
(&self.pure_records, &self.binary_records)
520+
fn records(&self) -> (&[PureRecord<MyPureModel>], Option<&Array2<MyBinaryModel>>) {
521+
(&self.pure_records, self.binary_records.as_ref())
505522
}
506523
}
507524

@@ -555,7 +572,7 @@ mod test {
555572

556573
assert_eq!(p.pure_records[0].identifier.cas, Some("123-4-5".into()));
557574
assert_eq!(p.pure_records[1].identifier.cas, Some("678-9-1".into()));
558-
assert_eq!(p.binary_records[[0, 1]].b, 12.0)
575+
assert_eq!(p.binary_records.unwrap()[[0, 1]].b, 12.0)
559576
}
560577

561578
#[test]
@@ -608,8 +625,9 @@ mod test {
608625

609626
assert_eq!(p.pure_records[0].identifier.cas, Some("123-4-5".into()));
610627
assert_eq!(p.pure_records[1].identifier.cas, Some("678-9-1".into()));
611-
assert_eq!(p.binary_records[[0, 1]], MyBinaryModel::default());
612-
assert_eq!(p.binary_records[[0, 1]].b, 0.0)
628+
let br = p.binary_records.as_ref().unwrap();
629+
assert_eq!(br[[0, 1]], MyBinaryModel::default());
630+
assert_eq!(br[[0, 1]].b, 0.0)
613631
}
614632

615633
#[test]
@@ -672,11 +690,12 @@ mod test {
672690
assert_eq!(p.pure_records[0].identifier.cas, Some("000-0-0".into()));
673691
assert_eq!(p.pure_records[1].identifier.cas, Some("123-4-5".into()));
674692
assert_eq!(p.pure_records[2].identifier.cas, Some("678-9-1".into()));
675-
assert_eq!(p.binary_records[[0, 1]], MyBinaryModel::default());
676-
assert_eq!(p.binary_records[[1, 0]], MyBinaryModel::default());
677-
assert_eq!(p.binary_records[[0, 2]], MyBinaryModel::default());
678-
assert_eq!(p.binary_records[[2, 0]], MyBinaryModel::default());
679-
assert_eq!(p.binary_records[[2, 1]].b, 12.0);
680-
assert_eq!(p.binary_records[[1, 2]].b, 12.0);
693+
let br = p.binary_records.as_ref().unwrap();
694+
assert_eq!(br[[0, 1]], MyBinaryModel::default());
695+
assert_eq!(br[[1, 0]], MyBinaryModel::default());
696+
assert_eq!(br[[0, 2]], MyBinaryModel::default());
697+
assert_eq!(br[[2, 0]], MyBinaryModel::default());
698+
assert_eq!(br[[2, 1]].b, 12.0);
699+
assert_eq!(br[[1, 2]].b, 12.0);
681700
}
682701
}

feos-core/src/python/cubic.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ use crate::parameter::{
44
};
55
use crate::python::parameter::PyIdentifier;
66
use crate::*;
7-
use ndarray::Array2;
87
use numpy::{PyArray2, PyReadonlyArray2, ToPyArray};
98
use pyo3::exceptions::PyTypeError;
109
use pyo3::prelude::*;
@@ -56,7 +55,12 @@ impl_binary_record!();
5655
#[derive(Clone)]
5756
pub struct PyPengRobinsonParameters(pub Arc<PengRobinsonParameters>);
5857

59-
impl_parameter!(PengRobinsonParameters, PyPengRobinsonParameters);
58+
impl_parameter!(
59+
PengRobinsonParameters,
60+
PyPengRobinsonParameters,
61+
PyPengRobinsonRecord,
62+
f64
63+
);
6064

6165
#[pymethods]
6266
impl PyPengRobinsonParameters {

feos-core/src/python/joback.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ use crate::{
77
impl_binary_record, impl_json_handling, impl_parameter, impl_parameter_from_segments,
88
impl_pure_record, impl_segment_record,
99
};
10-
use ndarray::Array2;
1110
use numpy::{PyArray2, PyReadonlyArray2, ToPyArray};
1211
use pyo3::exceptions::PyTypeError;
1312
use pyo3::prelude::*;
@@ -62,6 +61,7 @@ impl_segment_record!(JobackRecord, PyJobackRecord);
6261
pub struct PyJobackBinaryRecord(pub JobackBinaryRecord);
6362

6463
impl_binary_record!(JobackBinaryRecord, PyJobackBinaryRecord);
64+
6565
/// Create a set of Joback parameters from records.
6666
///
6767
/// Parameters
@@ -83,7 +83,12 @@ impl_binary_record!(JobackBinaryRecord, PyJobackBinaryRecord);
8383
#[derive(Clone)]
8484
pub struct PyJobackParameters(pub Arc<JobackParameters>);
8585

86-
impl_parameter!(JobackParameters, PyJobackParameters);
86+
impl_parameter!(
87+
JobackParameters,
88+
PyJobackParameters,
89+
PyJobackRecord,
90+
PyJobackBinaryRecord
91+
);
8792
impl_parameter_from_segments!(JobackParameters, PyJobackParameters);
8893

8994
#[pymethods]

0 commit comments

Comments
 (0)