Skip to content

Commit a2b9cd0

Browse files
committed
Implement binary association parameters (#167)
* Implement binary association parameters * Optional binary records and new parameter constructor (#169)
1 parent debaa1b commit a2b9cd0

File tree

20 files changed

+456
-198
lines changed

20 files changed

+456
-198
lines changed

CHANGELOG.md

Lines changed: 1 addition & 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 `IdealGasModel` enum that collects all implementors of the `IdealGas` trait. [#158](https://github.com/feos-org/feos/pull/158)
1010
- Added `feos.ideal_gas` module in Python from which (currently) `Joback` and `JobackParameters` are available. [#158](https://github.com/feos-org/feos/pull/158)
11+
- Added binary association parameters to PC-SAFT. [#167](https://github.com/feos-org/feos/pull/167)
1112

1213
### Changed
1314
- Changed the internal implementation of the association contribution to accomodate more general association schemes. [#150](https://github.com/feos-org/feos/pull/150)

feos-core/CHANGELOG.md

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

1415
### Changed
1516
- Changed constructors of `Parameter` trait to return `Result`s. [#161](https://github.com/feos-org/feos/pull/161)
@@ -22,6 +23,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2223
- Moved `StateVec` into own file and module. [#158](https://github.com/feos-org/feos/pull/158)
2324
- 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)
2425
- 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)
26+
- Made the binary records in the constructions and getters of the `Parameter` trait optional. [#169](https://github.com/feos-org/feos/pull/169)
27+
- 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)
2528

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

feos-core/src/cubic.rs

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ impl PengRobinsonParameters {
100100
PureRecord::new(id, molarweight[i], record)
101101
})
102102
.collect();
103-
PengRobinsonParameters::from_records(records, Array2::zeros([pc.len(); 2]))
103+
PengRobinsonParameters::from_records(records, None)
104104
}
105105
}
106106

@@ -111,7 +111,7 @@ impl Parameter for PengRobinsonParameters {
111111
/// Creates parameters from pure component records.
112112
fn from_records(
113113
pure_records: Vec<PureRecord<Self::Pure>>,
114-
binary_records: Array2<Self::Binary>,
114+
binary_records: Option<Array2<Self::Binary>>,
115115
) -> Result<Self, ParameterError> {
116116
let n = pure_records.len();
117117

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

133+
let k_ij = binary_records.unwrap_or_else(|| Array2::zeros([n; 2]));
134+
133135
Ok(Self {
134136
tc,
135137
a,
136138
b,
137-
k_ij: binary_records,
139+
k_ij,
138140
kappa,
139141
molarweight,
140142
pure_records,
141143
})
142144
}
143145

144-
fn records(&self) -> (&[PureRecord<PengRobinsonRecord>], &Array2<f64>) {
145-
(&self.pure_records, &self.k_ij)
146+
fn records(&self) -> (&[PureRecord<PengRobinsonRecord>], Option<&Array2<f64>>) {
147+
(&self.pure_records, Some(&self.k_ij))
146148
}
147149
}
148150

@@ -291,8 +293,7 @@ mod tests {
291293
let propane = mixture[0].clone();
292294
let tc = propane.model_record.tc;
293295
let pc = propane.model_record.pc;
294-
let parameters =
295-
PengRobinsonParameters::from_records(vec![propane], Array2::zeros((1, 1)))?;
296+
let parameters = PengRobinsonParameters::new_pure(propane)?;
296297
let pr = Arc::new(PengRobinson::new(Arc::new(parameters)));
297298
let options = SolverOptions::new().verbosity(Verbosity::Iter);
298299
let cp = State::critical_point(&pr, None, None, options)?;

feos-core/src/joback.rs

Lines changed: 4 additions & 9 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
) -> Result<Self, ParameterError> {
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,10 +185,8 @@ 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();
192188
Self::new(Arc::new(
193-
JobackParameters::from_records(records, Array::from_elem((n, n), JobackBinaryRecord))
194-
.unwrap(),
189+
JobackParameters::from_records(records, None).unwrap(),
195190
))
196191
}
197192
}

feos-core/src/lib.rs

Lines changed: 1 addition & 3 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,8 +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 =
252-
PengRobinsonParameters::from_records(vec![propane], Array2::zeros((1, 1)))?;
250+
let parameters = PengRobinsonParameters::new_pure(propane)?;
253251
let residual = Arc::new(PengRobinson::new(Arc::new(parameters)));
254252
let joback_parameters = Arc::new(JobackParameters::new_pure(PureRecord::new(
255253
Identifier::default(),

feos-core/src/parameter/mod.rs

Lines changed: 51 additions & 32 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
) -> Result<Self, ParameterError>;
4040

4141
/// Creates parameters for a pure component from a pure record.
4242
fn new_pure(pure_record: PureRecord<Self::Pure>) -> Result<Self, ParameterError> {
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
) -> Result<Self, ParameterError> {
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>) -> Result<Self, ParameterError> {
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-
) -> Result<Array2<Self::Binary>, ParameterError> {
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-
Ok(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)
@@ -184,7 +199,7 @@ where
184199
} else {
185200
Vec::new()
186201
};
187-
let record_matrix = Self::binary_matrix_from_records(&p, &binary_records, search_option)?;
202+
let record_matrix = Self::binary_matrix_from_records(&p, &binary_records, search_option);
188203
Self::from_records(p, record_matrix)
189204
}
190205

@@ -250,7 +265,7 @@ where
250265
}
251266
}
252267

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

256271
/// Creates parameters from segment information stored in json files.
@@ -336,8 +351,10 @@ where
336351
.map(|&i| pure_records[i].clone())
337352
.collect();
338353
let n = component_list.len();
339-
let binary_records = Array2::from_shape_fn([n, n], |(i, j)| {
340-
binary_records[(component_list[i], component_list[j])].clone()
354+
let binary_records = binary_records.map(|br| {
355+
Array2::from_shape_fn([n, n], |(i, j)| {
356+
br[(component_list[i], component_list[j])].clone()
357+
})
341358
});
342359

343360
Self::from_records(pure_records, binary_records)
@@ -490,24 +507,24 @@ mod test {
490507

491508
struct MyParameter {
492509
pure_records: Vec<PureRecord<MyPureModel>>,
493-
binary_records: Array2<MyBinaryModel>,
510+
binary_records: Option<Array2<MyBinaryModel>>,
494511
}
495512

496513
impl Parameter for MyParameter {
497514
type Pure = MyPureModel;
498515
type Binary = MyBinaryModel;
499516
fn from_records(
500517
pure_records: Vec<PureRecord<MyPureModel>>,
501-
binary_records: Array2<MyBinaryModel>,
518+
binary_records: Option<Array2<MyBinaryModel>>,
502519
) -> Result<Self, ParameterError> {
503520
Ok(Self {
504521
pure_records,
505522
binary_records,
506523
})
507524
}
508525

509-
fn records(&self) -> (&[PureRecord<MyPureModel>], &Array2<MyBinaryModel>) {
510-
(&self.pure_records, &self.binary_records)
526+
fn records(&self) -> (&[PureRecord<MyPureModel>], Option<&Array2<MyBinaryModel>>) {
527+
(&self.pure_records, self.binary_records.as_ref())
511528
}
512529
}
513530

@@ -556,12 +573,12 @@ mod test {
556573
&pure_records,
557574
&binary_records,
558575
IdentifierOption::Cas,
559-
)?;
576+
);
560577
let p = MyParameter::from_records(pure_records, binary_matrix)?;
561578

562579
assert_eq!(p.pure_records[0].identifier.cas, Some("123-4-5".into()));
563580
assert_eq!(p.pure_records[1].identifier.cas, Some("678-9-1".into()));
564-
assert_eq!(p.binary_records[[0, 1]].b, 12.0);
581+
assert_eq!(p.binary_records.unwrap()[[0, 1]].b, 12.0);
565582
Ok(())
566583
}
567584

@@ -610,13 +627,14 @@ mod test {
610627
&pure_records,
611628
&binary_records,
612629
IdentifierOption::Cas,
613-
)?;
630+
);
614631
let p = MyParameter::from_records(pure_records, binary_matrix)?;
615632

616633
assert_eq!(p.pure_records[0].identifier.cas, Some("123-4-5".into()));
617634
assert_eq!(p.pure_records[1].identifier.cas, Some("678-9-1".into()));
618-
assert_eq!(p.binary_records[[0, 1]], MyBinaryModel::default());
619-
assert_eq!(p.binary_records[[0, 1]].b, 0.0);
635+
let br = p.binary_records.as_ref().unwrap();
636+
assert_eq!(br[[0, 1]], MyBinaryModel::default());
637+
assert_eq!(br[[0, 1]].b, 0.0);
620638
Ok(())
621639
}
622640

@@ -674,18 +692,19 @@ mod test {
674692
&pure_records,
675693
&binary_records,
676694
IdentifierOption::Cas,
677-
)?;
695+
);
678696
let p = MyParameter::from_records(pure_records, binary_matrix)?;
679697

680698
assert_eq!(p.pure_records[0].identifier.cas, Some("000-0-0".into()));
681699
assert_eq!(p.pure_records[1].identifier.cas, Some("123-4-5".into()));
682700
assert_eq!(p.pure_records[2].identifier.cas, Some("678-9-1".into()));
683-
assert_eq!(p.binary_records[[0, 1]], MyBinaryModel::default());
684-
assert_eq!(p.binary_records[[1, 0]], MyBinaryModel::default());
685-
assert_eq!(p.binary_records[[0, 2]], MyBinaryModel::default());
686-
assert_eq!(p.binary_records[[2, 0]], MyBinaryModel::default());
687-
assert_eq!(p.binary_records[[2, 1]].b, 12.0);
688-
assert_eq!(p.binary_records[[1, 2]].b, 12.0);
701+
let br = p.binary_records.as_ref().unwrap();
702+
assert_eq!(br[[0, 1]], MyBinaryModel::default());
703+
assert_eq!(br[[1, 0]], MyBinaryModel::default());
704+
assert_eq!(br[[0, 2]], MyBinaryModel::default());
705+
assert_eq!(br[[2, 0]], MyBinaryModel::default());
706+
assert_eq!(br[[2, 1]].b, 12.0);
707+
assert_eq!(br[[1, 2]].b, 12.0);
689708
Ok(())
690709
}
691710
}

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)