Skip to content

Commit 29d9f8c

Browse files
committed
Handle association outside of individual models
1 parent d46ad8b commit 29d9f8c

File tree

24 files changed

+1289
-1542
lines changed

24 files changed

+1289
-1542
lines changed

crates/feos-core/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ indexmap = { workspace = true, features = ["serde"] }
2727
rayon = { workspace = true, optional = true }
2828
typenum = { workspace = true }
2929
itertools = { workspace = true }
30+
arrayvec = { workspace = true, features = ["serde"] }
3031

3132
[dev-dependencies]
3233
approx = { workspace = true }

crates/feos-core/src/cubic.rs

Lines changed: 22 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
use crate::FeosError;
88
use crate::equation_of_state::{Components, Molarweight, Residual};
99
use crate::errors::FeosResult;
10-
use crate::parameter::{Identifier, Parameter, PureRecord};
10+
use crate::parameter::{BinaryRecord, Identifier, Parameter, PureRecord};
1111
use crate::state::StateHD;
1212
use ndarray::{Array1, Array2, ScalarOperand};
1313
use num_dual::DualNum;
@@ -61,9 +61,9 @@ pub struct PengRobinsonParameters {
6161
/// Molar weight in units of g/mol
6262
molarweight: Array1<f64>,
6363
/// List of pure component records
64-
pure_records: Vec<PureRecord<PengRobinsonRecord>>,
64+
pure_records: Vec<PureRecord<PengRobinsonRecord, ()>>,
6565
/// List of binary records
66-
binary_records: Vec<([usize; 2], f64)>,
66+
binary_records: Vec<BinaryRecord<usize, f64, ()>>,
6767
}
6868

6969
impl std::fmt::Display for PengRobinsonParameters {
@@ -109,11 +109,12 @@ impl PengRobinsonParameters {
109109
impl Parameter for PengRobinsonParameters {
110110
type Pure = PengRobinsonRecord;
111111
type Binary = f64;
112+
type Association = ();
112113

113114
/// Creates parameters from pure component records.
114115
fn from_records(
115-
pure_records: Vec<PureRecord<Self::Pure>>,
116-
binary_records: Vec<([usize; 2], Self::Binary)>,
116+
pure_records: Vec<PureRecord<Self::Pure, ()>>,
117+
binary_records: Vec<BinaryRecord<usize, Self::Binary, ()>>,
117118
) -> FeosResult<Self> {
118119
let n = pure_records.len();
119120

@@ -133,9 +134,9 @@ impl Parameter for PengRobinsonParameters {
133134
}
134135

135136
let mut k_ij = Array2::zeros([n; 2]);
136-
for &([i, j], r) in &binary_records {
137-
k_ij[[i, j]] = r;
138-
k_ij[[j, i]] = r;
137+
for r in &binary_records {
138+
k_ij[[r.id1, r.id2]] = r.model_record.unwrap_or_default();
139+
k_ij[[r.id2, r.id1]] = r.model_record.unwrap_or_default();
139140
}
140141

141142
Ok(Self {
@@ -150,7 +151,12 @@ impl Parameter for PengRobinsonParameters {
150151
})
151152
}
152153

153-
fn records(&self) -> (&[PureRecord<PengRobinsonRecord>], &[([usize; 2], f64)]) {
154+
fn records(
155+
&self,
156+
) -> (
157+
&[PureRecord<PengRobinsonRecord, ()>],
158+
&[BinaryRecord<usize, f64, ()>],
159+
) {
154160
(&self.pure_records, &self.binary_records)
155161
}
156162
}
@@ -240,7 +246,7 @@ mod tests {
240246
use quantity::{KELVIN, PASCAL};
241247
use std::sync::Arc;
242248

243-
fn pure_record_vec() -> Vec<PureRecord<PengRobinsonRecord>> {
249+
fn pure_record_vec() -> Vec<PureRecord<PengRobinsonRecord, ()>> {
244250
let records = r#"[
245251
{
246252
"identifier": {
@@ -251,11 +257,9 @@ mod tests {
251257
"inchi": "InChI=1/C3H8/c1-3-2/h3H2,1-2H3",
252258
"formula": "C3H8"
253259
},
254-
"model_record": {
255-
"tc": 369.96,
256-
"pc": 4250000.0,
257-
"acentric_factor": 0.153
258-
},
260+
"tc": 369.96,
261+
"pc": 4250000.0,
262+
"acentric_factor": 0.153,
259263
"molarweight": 44.0962
260264
},
261265
{
@@ -267,11 +271,9 @@ mod tests {
267271
"inchi": "InChI=1/C4H10/c1-3-4-2/h3-4H2,1-2H3",
268272
"formula": "C4H10"
269273
},
270-
"model_record": {
271-
"tc": 425.2,
272-
"pc": 3800000.0,
273-
"acentric_factor": 0.199
274-
},
274+
"tc": 425.2,
275+
"pc": 3800000.0,
276+
"acentric_factor": 0.199,
275277
"molarweight": 58.123
276278
}
277279
]"#;

crates/feos-core/src/lib.rs

Lines changed: 12 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,6 @@ pub use state::{
4343
Contributions, DensityInitialization, Derivative, State, StateBuilder, StateHD, StateVec,
4444
};
4545

46-
4746
/// Level of detail in the iteration output.
4847
#[derive(Copy, Clone, PartialOrd, PartialEq, Eq)]
4948
pub enum Verbosity {
@@ -167,16 +166,8 @@ pub trait ReferenceSystem<
167166
}
168167

169168
/// Conversion to and from reduced units
170-
impl<
171-
Inner,
172-
T: Integer,
173-
L: Integer,
174-
M: Integer,
175-
I: Integer,
176-
THETA: Integer,
177-
N: Integer,
178-
J: Integer,
179-
> ReferenceSystem<Inner, T, L, M, I, THETA, N, J>
169+
impl<Inner, T: Integer, L: Integer, M: Integer, I: Integer, THETA: Integer, N: Integer, J: Integer>
170+
ReferenceSystem<Inner, T, L, M, I, THETA, N, J>
180171
for Quantity<Inner, SIUnit<T, L, M, I, THETA, N, J>>
181172
{
182173
fn from_reduced(value: Inner) -> Self
@@ -203,12 +194,12 @@ impl<
203194

204195
#[cfg(test)]
205196
mod tests {
206-
use crate::cubic::*;
207-
use crate::equation_of_state::{Components, EquationOfState, IdealGas};
208-
use crate::parameter::*;
209197
use crate::Contributions;
210198
use crate::FeosResult;
211199
use crate::StateBuilder;
200+
use crate::cubic::*;
201+
use crate::equation_of_state::{Components, EquationOfState, IdealGas};
202+
use crate::parameter::*;
212203
use approx::*;
213204
use ndarray::Array1;
214205
use num_dual::DualNum;
@@ -238,7 +229,7 @@ mod tests {
238229
}
239230
}
240231

241-
fn pure_record_vec() -> Vec<PureRecord<PengRobinsonRecord>> {
232+
fn pure_record_vec() -> Vec<PureRecord<PengRobinsonRecord, ()>> {
242233
let records = r#"[
243234
{
244235
"identifier": {
@@ -249,11 +240,9 @@ mod tests {
249240
"inchi": "InChI=1/C3H8/c1-3-2/h3H2,1-2H3",
250241
"formula": "C3H8"
251242
},
252-
"model_record": {
253-
"tc": 369.96,
254-
"pc": 4250000.0,
255-
"acentric_factor": 0.153
256-
},
243+
"tc": 369.96,
244+
"pc": 4250000.0,
245+
"acentric_factor": 0.153,
257246
"molarweight": 44.0962
258247
},
259248
{
@@ -265,11 +254,9 @@ mod tests {
265254
"inchi": "InChI=1/C4H10/c1-3-4-2/h3-4H2,1-2H3",
266255
"formula": "C4H10"
267256
},
268-
"model_record": {
269-
"tc": 425.2,
270-
"pc": 3800000.0,
271-
"acentric_factor": 0.199
272-
},
257+
"tc": 425.2,
258+
"pc": 3800000.0,
259+
"acentric_factor": 0.199,
273260
"molarweight": 58.123
274261
}
275262
]"#;
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
use arrayvec::ArrayString;
2+
use num_traits::Zero;
3+
use serde::{Deserialize, Serialize};
4+
5+
type SiteId = ArrayString<8>;
6+
7+
/// Pure component association parameters.
8+
#[derive(Serialize, Deserialize, Clone, Copy)]
9+
pub struct AssociationRecord<A> {
10+
#[serde(skip_serializing_if = "SiteId::is_empty")]
11+
#[serde(default)]
12+
pub id: SiteId,
13+
#[serde(flatten)]
14+
pub parameters: Option<A>,
15+
/// \# of association sites of type A
16+
#[serde(skip_serializing_if = "f64::is_zero")]
17+
#[serde(default)]
18+
pub na: f64,
19+
/// \# of association sites of type B
20+
#[serde(skip_serializing_if = "f64::is_zero")]
21+
#[serde(default)]
22+
pub nb: f64,
23+
/// \# of association sites of type C
24+
#[serde(skip_serializing_if = "f64::is_zero")]
25+
#[serde(default)]
26+
pub nc: f64,
27+
}
28+
29+
impl<A> AssociationRecord<A> {
30+
pub fn new(parameters: Option<A>, na: f64, nb: f64, nc: f64) -> Self {
31+
Self::with_id(Default::default(), parameters, na, nb, nc)
32+
}
33+
34+
pub fn with_id(id: SiteId, parameters: Option<A>, na: f64, nb: f64, nc: f64) -> Self {
35+
Self {
36+
id,
37+
parameters,
38+
na,
39+
nb,
40+
nc,
41+
}
42+
}
43+
}
44+
45+
/// Binary association parameters.
46+
#[derive(Serialize, Deserialize, Clone, Copy)]
47+
pub struct BinaryAssociationRecord<A> {
48+
// Identifier of the association site on the first molecule.
49+
#[serde(skip_serializing_if = "SiteId::is_empty")]
50+
#[serde(default)]
51+
pub id1: SiteId,
52+
// Identifier of the association site on the second molecule.
53+
#[serde(skip_serializing_if = "SiteId::is_empty")]
54+
#[serde(default)]
55+
pub id2: SiteId,
56+
// Binary association parameters
57+
#[serde(flatten)]
58+
pub parameters: A,
59+
}
60+
61+
impl<A> BinaryAssociationRecord<A> {
62+
pub fn new(parameters: A) -> Self {
63+
Self::with_id(Default::default(), Default::default(), parameters)
64+
}
65+
66+
pub fn with_id(id1: SiteId, id2: SiteId, parameters: A) -> Self {
67+
Self {
68+
id1,
69+
id2,
70+
parameters,
71+
}
72+
}
73+
}

crates/feos-core/src/parameter/chemical_record.rs

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,9 @@
1-
use super::identifier::Identifier;
2-
use super::segment::SegmentRecord;
1+
use super::{Identifier, SegmentRecord};
32
use crate::{FeosError, FeosResult};
43
use num_traits::NumAssign;
54
use serde::{Deserialize, Serialize};
6-
use std::{
7-
borrow::Cow,
8-
collections::{HashMap, HashSet},
9-
};
5+
use std::borrow::Cow;
6+
use std::collections::{HashMap, HashSet};
107

118
// Auxiliary structure used to deserialize chemical records without explicit bond information.
129
#[derive(Serialize, Deserialize)]
@@ -135,19 +132,20 @@ pub trait SegmentCount {
135132
/// molecule.
136133
///
137134
/// The map contains the segment record as key and the count as value.
138-
fn segment_map<M: Clone>(
135+
#[expect(clippy::type_complexity)]
136+
fn segment_map<M: Clone, A: Clone>(
139137
&self,
140-
segment_records: &[SegmentRecord<M>],
141-
) -> FeosResult<HashMap<SegmentRecord<M>, Self::Count>> {
138+
segment_records: &[SegmentRecord<M, A>],
139+
) -> FeosResult<Vec<(SegmentRecord<M, A>, Self::Count)>> {
142140
let count = self.segment_count();
143-
let queried: HashSet<_> = count.keys().cloned().collect();
144-
let mut segments: HashMap<String, SegmentRecord<M>> = segment_records
141+
let queried: HashSet<_> = count.keys().collect();
142+
let mut segments: HashMap<_, SegmentRecord<M, A>> = segment_records
145143
.iter()
146-
.map(|r| (r.identifier.clone(), r.clone()))
144+
.map(|r| (&r.identifier, r.clone()))
147145
.collect();
148-
let available = segments.keys().cloned().collect();
146+
let available = segments.keys().copied().collect();
149147
if !queried.is_subset(&available) {
150-
let missing: Vec<String> = queried.difference(&available).cloned().collect();
148+
let missing: Vec<_> = queried.difference(&available).collect();
151149
let msg = format!("{:?}", missing);
152150
return Err(FeosError::ComponentsNotFound(msg));
153151
};

0 commit comments

Comments
 (0)