feos_core/parameter/
model_record.rs

1use super::identifier::Identifier;
2use super::segment::SegmentRecord;
3use super::{IdentifierOption, ParameterError};
4use conv::ValueInto;
5use serde::de::DeserializeOwned;
6use serde::{Deserialize, Serialize};
7use std::collections::{HashMap, HashSet};
8use std::fs::File;
9use std::io::BufReader;
10use std::path::Path;
11
12/// A collection of parameters of a pure substance.
13#[derive(Serialize, Deserialize, Debug, Clone)]
14pub struct PureRecord<M> {
15    pub identifier: Identifier,
16    #[serde(default)]
17    pub molarweight: f64,
18    pub model_record: M,
19}
20
21impl<M> PureRecord<M> {
22    /// Create a new `PureRecord`.
23    pub fn new(identifier: Identifier, molarweight: f64, model_record: M) -> Self {
24        Self {
25            identifier,
26            molarweight,
27            model_record,
28        }
29    }
30
31    /// Update the `PureRecord` from segment counts.
32    ///
33    /// The [FromSegments] trait needs to be implemented for both the model record
34    /// and the ideal gas record.
35    pub fn from_segments<S, T>(identifier: Identifier, segments: S) -> Result<Self, ParameterError>
36    where
37        T: Copy + ValueInto<f64>,
38        M: FromSegments<T>,
39        S: IntoIterator<Item = (SegmentRecord<M>, T)>,
40    {
41        let mut molarweight = 0.0;
42        let mut model_segments = Vec::new();
43        for (s, n) in segments {
44            molarweight += s.molarweight * n.value_into().unwrap();
45            model_segments.push((s.model_record, n));
46        }
47        let model_record = M::from_segments(&model_segments)?;
48
49        Ok(Self::new(identifier, molarweight, model_record))
50    }
51
52    /// Create pure substance parameters from a json file.
53    pub fn from_json<P>(
54        substances: &[&str],
55        file: P,
56        identifier_option: IdentifierOption,
57    ) -> Result<Vec<Self>, ParameterError>
58    where
59        P: AsRef<Path>,
60        M: Clone + DeserializeOwned,
61    {
62        // create list of substances
63        let mut queried: HashSet<String> = substances.iter().map(|s| s.to_string()).collect();
64        // raise error on duplicate detection
65        if queried.len() != substances.len() {
66            return Err(ParameterError::IncompatibleParameters(
67                "A substance was defined more than once.".to_string(),
68            ));
69        }
70
71        let f = File::open(file)?;
72        let reader = BufReader::new(f);
73        // use stream in the future
74        let file_records: Vec<Self> = serde_json::from_reader(reader)?;
75        let mut records: HashMap<String, Self> = HashMap::with_capacity(substances.len());
76
77        // build map, draining list of queried substances in the process
78        for record in file_records {
79            if let Some(id) = record.identifier.as_string(identifier_option) {
80                queried.take(&id).map(|id| records.insert(id, record));
81            }
82            // all parameters parsed
83            if queried.is_empty() {
84                break;
85            }
86        }
87
88        // report missing parameters
89        if !queried.is_empty() {
90            return Err(ParameterError::ComponentsNotFound(format!("{:?}", queried)));
91        };
92
93        // collect into vec in correct order
94        Ok(substances
95            .iter()
96            .map(|&s| records.get(s).unwrap().clone())
97            .collect())
98    }
99}
100
101impl<M> std::fmt::Display for PureRecord<M>
102where
103    M: std::fmt::Display,
104{
105    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
106        write!(f, "PureRecord(")?;
107        write!(f, "\n\tidentifier={},", self.identifier)?;
108        write!(f, "\n\tmolarweight={},", self.molarweight)?;
109        write!(f, "\n\tmodel_record={},", self.model_record)?;
110        write!(f, "\n)")
111    }
112}
113
114/// Trait for models that implement a homosegmented group contribution
115/// method
116pub trait FromSegments<T>: Clone {
117    /// Constructs the record from a list of segment records with their
118    /// number of occurences.
119    fn from_segments(segments: &[(Self, T)]) -> Result<Self, ParameterError>;
120}
121
122/// Trait for models that implement a homosegmented group contribution
123/// method and have a combining rule for binary interaction parameters.
124pub trait FromSegmentsBinary<T>: Clone {
125    /// Constructs the binary record from a list of segment records with
126    /// their number of occurences.
127    fn from_segments_binary(segments: &[(f64, T, T)]) -> Result<Self, ParameterError>;
128}
129
130/// A collection of parameters that model interactions between two
131/// substances or segments.
132#[derive(Serialize, Deserialize, Debug, Clone)]
133pub struct BinaryRecord<I, B> {
134    /// Identifier of the first component
135    pub id1: I,
136    /// Identifier of the second component
137    pub id2: I,
138    /// Binary interaction parameter(s)
139    pub model_record: B,
140}
141
142impl<I, B> BinaryRecord<I, B> {
143    /// Crates a new `BinaryRecord`.
144    pub fn new(id1: I, id2: I, model_record: B) -> Self {
145        Self {
146            id1,
147            id2,
148            model_record,
149        }
150    }
151
152    /// Read a list of `BinaryRecord`s from a JSON file.
153    pub fn from_json<P: AsRef<Path>>(file: P) -> Result<Vec<Self>, ParameterError>
154    where
155        I: DeserializeOwned,
156        B: DeserializeOwned,
157    {
158        Ok(serde_json::from_reader(BufReader::new(File::open(file)?))?)
159    }
160}
161
162impl<I, B> std::fmt::Display for BinaryRecord<I, B>
163where
164    I: std::fmt::Display,
165    B: std::fmt::Display,
166{
167    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
168        write!(f, "BinaryRecord(")?;
169        write!(f, "\n\tid1={},", self.id1)?;
170        write!(f, "\n\tid2={},", self.id2)?;
171        write!(f, "\n\tmodel_record={},", self.model_record)?;
172        write!(f, "\n)")
173    }
174}
175
176#[cfg(test)]
177mod test {
178    use super::*;
179
180    #[derive(Serialize, Deserialize, Debug, Default, Clone)]
181    struct TestModelRecordSegments {
182        a: f64,
183    }
184
185    #[test]
186    fn deserialize() {
187        let r = r#"
188        {
189            "identifier": {
190                "cas": "123-4-5"
191            },
192            "molarweight": 16.0426,
193            "model_record": {
194                "a": 0.1
195            }
196        }
197        "#;
198        let record: PureRecord<TestModelRecordSegments> =
199            serde_json::from_str(r).expect("Unable to parse json.");
200        assert_eq!(record.identifier.cas, Some("123-4-5".into()))
201    }
202
203    #[test]
204    fn deserialize_list() {
205        let r = r#"
206        [
207            {
208                "identifier": {
209                    "cas": "1"
210                },
211                "molarweight": 1.0,
212                "model_record": {
213                    "a": 1.0
214                }
215            },
216            {
217                "identifier": {
218                    "cas": "2"
219                },
220                "molarweight": 2.0,
221                "model_record": {
222                    "a": 2.0
223                }
224            }
225        ]"#;
226        let records: Vec<PureRecord<TestModelRecordSegments>> =
227            serde_json::from_str(r).expect("Unable to parse json.");
228        assert_eq!(records[0].identifier.cas, Some("1".into()));
229        assert_eq!(records[1].identifier.cas, Some("2".into()))
230    }
231}