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}
125pub trait GroupCount: Copy {
126 #[expect(clippy::type_complexity)]
127 fn into_groups(
128 chemical_record: ChemicalRecord,
129 ) -> (Identifier, Vec<(String, Self)>, Vec<([usize; 2], Self)>);
130
131 fn into_f64(self) -> f64;
132}
133
134impl GroupCount for f64 {
135 fn into_groups(
136 chemical_record: ChemicalRecord,
137 ) -> (Identifier, Vec<(String, f64)>, Vec<([usize; 2], f64)>) {
138 let mut group_counts = IndexMap::with_capacity(chemical_record.segments.len());
139 let segment_to_group: Vec<_> = chemical_record
140 .segments
141 .into_iter()
142 .map(|si| {
143 let entry = group_counts.entry(si);
144 let index = entry.index();
145 *entry.or_insert(0.0) += 1.0;
146 index
147 })
148 .collect();
149
150 let mut bond_counts: IndexMap<_, _> = (0..group_counts.len())
151 .array_combinations()
152 .chain((0..group_counts.len()).map(|i| [i, i]))
153 .map(|g| (g, 0.0))
154 .collect();
155 for [i, j] in chemical_record.bonds {
156 let [s1, s2] = [segment_to_group[i], segment_to_group[j]];
157 bond_counts.entry([s1, s2]).and_modify(|x| *x += 1.0);
158 if s1 != s2 {
159 bond_counts.entry([s2, s1]).and_modify(|x| *x += 1.0);
160 }
161 }
162 let group_counts = group_counts.into_iter().collect();
163 let bond_counts = bond_counts.into_iter().filter(|(_, c)| *c > 0.0).collect();
164
165 (chemical_record.identifier, group_counts, bond_counts)
166 }
167
168 fn into_f64(self) -> f64 {
169 self
170 }
171}
172
173impl GroupCount for () {
174 fn into_groups(
175 chemical_record: ChemicalRecord,
176 ) -> (Identifier, Vec<(String, ())>, Vec<([usize; 2], ())>) {
177 let segments = chemical_record
178 .segments
179 .into_iter()
180 .map(|s| (s, ()))
181 .collect();
182 let bonds = chemical_record.bonds.into_iter().map(|b| (b, ())).collect();
183 (chemical_record.identifier, segments, bonds)
184 }
185
186 fn into_f64(self) -> f64 {
187 1.0
188 }
189}