feos_core/parameter/
chemical_record.rs1use super::{Identifier, IdentifierOption};
2use crate::{FeosError, FeosResult};
3use indexmap::IndexMap;
4use itertools::Itertools;
5use serde::{Deserialize, Serialize};
6use std::collections::{HashMap, HashSet};
7use std::fs::File;
8use std::io::BufReader;
9use std::ops::Deref;
10use std::path::Path;
11
12#[derive(Serialize, Deserialize)]
14struct ChemicalRecordJSON {
15 identifier: Identifier,
16 segments: Vec<String>,
17 bonds: Option<Vec<[usize; 2]>>,
18}
19
20#[derive(Deserialize, Serialize, Debug, Clone)]
22#[serde(from = "ChemicalRecordJSON")]
23#[serde(into = "ChemicalRecordJSON")]
24pub struct ChemicalRecord {
25 pub identifier: Identifier,
26 pub segments: Vec<String>,
27 pub bonds: Vec<[usize; 2]>,
28}
29
30impl From<ChemicalRecordJSON> for ChemicalRecord {
31 fn from(record: ChemicalRecordJSON) -> Self {
32 Self::new(record.identifier, record.segments, record.bonds)
33 }
34}
35
36impl From<ChemicalRecord> for ChemicalRecordJSON {
37 fn from(record: ChemicalRecord) -> Self {
38 Self {
39 identifier: record.identifier,
40 segments: record.segments,
41 bonds: Some(record.bonds),
42 }
43 }
44}
45
46impl ChemicalRecord {
47 pub fn new(
51 identifier: Identifier,
52 segments: Vec<String>,
53 bonds: Option<Vec<[usize; 2]>>,
54 ) -> ChemicalRecord {
55 let bonds = bonds.unwrap_or_else(|| {
56 (0..segments.len() - 1)
57 .zip(1..segments.len())
58 .map(|x| [x.0, x.1])
59 .collect()
60 });
61 Self {
62 identifier,
63 segments,
64 bonds,
65 }
66 }
67
68 pub fn from_json<P, S>(
70 substances: &[S],
71 file: P,
72 identifier_option: IdentifierOption,
73 ) -> FeosResult<Vec<Self>>
74 where
75 P: AsRef<Path>,
76 S: Deref<Target = str>,
77 {
78 let mut queried: HashSet<&str> = substances.iter().map(|s| s.deref()).collect();
80 if queried.len() != substances.len() {
82 return Err(FeosError::IncompatibleParameters(
83 "A substance was defined more than once.".to_string(),
84 ));
85 }
86
87 let f = File::open(file)?;
88 let reader = BufReader::new(f);
89 let file_records: Vec<Self> = serde_json::from_reader(reader)?;
91 let mut records: HashMap<&str, Self> = HashMap::with_capacity(substances.len());
92
93 for record in file_records {
95 if let Some(id) = record.identifier.as_str(identifier_option) {
96 queried.take(id).map(|id| records.insert(id, record));
97 }
98 if queried.is_empty() {
100 break;
101 }
102 }
103
104 if !queried.is_empty() {
106 return Err(FeosError::ComponentsNotFound(format!("{queried:?}")));
107 };
108
109 Ok(substances
111 .iter()
112 .map(|s| records.remove(s.deref()).unwrap())
113 .collect())
114 }
115}
116
117impl std::fmt::Display for ChemicalRecord {
118 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
119 write!(f, "ChemicalRecord(")?;
120 write!(f, "\n\tidentifier={},", self.identifier)?;
121 write!(f, "\n\tsegments={:?},", self.segments)?;
122 write!(f, "\n\tbonds={:?}\n)", self.bonds)
123 }
124}
125
126pub trait GroupCount: Copy {
131 #[expect(clippy::type_complexity)]
132 fn into_groups(
133 chemical_record: ChemicalRecord,
134 ) -> (Identifier, Vec<(String, Self)>, Vec<([usize; 2], Self)>);
135
136 fn into_f64(self) -> f64;
137}
138
139impl GroupCount for f64 {
140 fn into_groups(
141 chemical_record: ChemicalRecord,
142 ) -> (Identifier, Vec<(String, f64)>, Vec<([usize; 2], f64)>) {
143 let mut group_counts = IndexMap::with_capacity(chemical_record.segments.len());
144 let segment_to_group: Vec<_> = chemical_record
145 .segments
146 .into_iter()
147 .map(|si| {
148 let entry = group_counts.entry(si);
149 let index = entry.index();
150 *entry.or_insert(0.0) += 1.0;
151 index
152 })
153 .collect();
154
155 let mut bond_counts: IndexMap<_, _> = (0..group_counts.len())
156 .array_combinations()
157 .chain((0..group_counts.len()).map(|i| [i, i]))
158 .map(|g| (g, 0.0))
159 .collect();
160 for [i, j] in chemical_record.bonds {
161 let [s1, s2] = [segment_to_group[i], segment_to_group[j]];
162 bond_counts.entry([s1, s2]).and_modify(|x| *x += 1.0);
163 if s1 != s2 {
164 bond_counts.entry([s2, s1]).and_modify(|x| *x += 1.0);
165 }
166 }
167 let group_counts = group_counts.into_iter().collect();
168 let bond_counts = bond_counts.into_iter().filter(|(_, c)| *c > 0.0).collect();
169
170 (chemical_record.identifier, group_counts, bond_counts)
171 }
172
173 fn into_f64(self) -> f64 {
174 self
175 }
176}
177
178impl GroupCount for () {
179 fn into_groups(
180 chemical_record: ChemicalRecord,
181 ) -> (Identifier, Vec<(String, ())>, Vec<([usize; 2], ())>) {
182 let segments = chemical_record
183 .segments
184 .into_iter()
185 .map(|s| (s, ()))
186 .collect();
187 let bonds = chemical_record.bonds.into_iter().map(|b| (b, ())).collect();
188 (chemical_record.identifier, segments, bonds)
189 }
190
191 fn into_f64(self) -> f64 {
192 1.0
193 }
194}