1use crate::errors::*;
3use indexmap::{IndexMap, IndexSet};
4use itertools::Itertools;
5use nalgebra::{DMatrix, DVector, Scalar};
6use quantity::{GRAM, MOL, MolarWeight};
7use serde::Serialize;
8use serde::de::DeserializeOwned;
9use std::array;
10use std::collections::HashMap;
11use std::fs::File;
12use std::io::BufReader;
13use std::ops::Deref;
14use std::path::Path;
15
16mod association;
17mod chemical_record;
18mod identifier;
19mod model_record;
20
21pub use association::{
22 AssociationParameters, AssociationRecord, AssociationSite, BinaryAssociationRecord,
23 CombiningRule,
24};
25pub use chemical_record::{ChemicalRecord, GroupCount};
26pub use identifier::{Identifier, IdentifierOption};
27pub use model_record::{
28 BinaryRecord, BinarySegmentRecord, FromSegments, FromSegmentsBinary, PureRecord, Record,
29 SegmentRecord,
30};
31
32#[derive(Clone)]
33pub struct PureParameters<M, C> {
34 pub identifier: String,
35 pub model_record: M,
36 pub count: C,
37 pub component_index: usize,
38}
39
40impl<M: Clone> PureParameters<M, ()> {
41 fn from_pure_record(model_record: M, component_index: usize) -> Self {
42 Self {
43 identifier: "".into(),
44 model_record,
45 count: (),
46 component_index,
47 }
48 }
49}
50
51impl<M: Clone, C> PureParameters<M, C> {
52 fn from_segment_record<A>(
53 segment: &SegmentRecord<M, A>,
54 count: C,
55 component_index: usize,
56 ) -> Self {
57 Self {
58 identifier: segment.identifier.clone(),
59 model_record: segment.model_record.clone(),
60 count,
61 component_index,
62 }
63 }
64}
65
66#[derive(Clone, Copy)]
67pub struct BinaryParameters<B, C> {
68 pub id1: usize,
69 pub id2: usize,
70 pub model_record: B,
71 pub count: C,
72}
73
74impl<B, C> BinaryParameters<B, C> {
75 pub fn new(id1: usize, id2: usize, model_record: B, count: C) -> Self {
76 Self {
77 id1,
78 id2,
79 model_record,
80 count,
81 }
82 }
83}
84
85pub struct GenericParameters<P, B, A, Bo, C, Data> {
86 pub pure: Vec<PureParameters<P, C>>,
87 pub binary: Vec<BinaryParameters<B, ()>>,
88 pub bonds: Vec<BinaryParameters<Bo, C>>,
89 pub association: AssociationParameters<A>,
90 pub molar_weight: MolarWeight<DVector<f64>>,
91 data: Data,
92}
93
94pub type Parameters<P, B, A> =
95 GenericParameters<P, B, A, (), (), (Vec<PureRecord<P, A>>, Vec<BinaryRecord<usize, B, A>>)>;
96pub type GcParameters<P, B, A, Bo, C> = GenericParameters<
97 P,
98 B,
99 A,
100 Bo,
101 C,
102 (
103 Vec<ChemicalRecord>,
104 Vec<SegmentRecord<P, A>>,
105 Option<Vec<BinarySegmentRecord<B, A>>>,
106 Vec<BinarySegmentRecord<Bo, ()>>,
107 ),
108>;
109pub type IdealGasParameters<I> = Parameters<I, (), ()>;
110
111impl<P, B, A, Bo, C, Data> GenericParameters<P, B, A, Bo, C, Data> {
112 pub fn collate<F, T: Scalar + Copy, const N: usize>(&self, f: F) -> [DVector<T>; N]
113 where
114 F: Fn(&P) -> [T; N],
115 {
116 array::from_fn(|i| {
117 DVector::from_vec(self.pure.iter().map(|pr| f(&pr.model_record)[i]).collect())
118 })
119 }
120
121 pub fn collate_binary<F, T: Scalar + Default + Copy, const N: usize>(
122 &self,
123 f: F,
124 ) -> [DMatrix<T>; N]
125 where
126 F: Fn(&B) -> [T; N],
127 {
128 array::from_fn(|i| {
129 let mut b_mat = DMatrix::from_element(self.pure.len(), self.pure.len(), T::default());
130 for br in &self.binary {
131 let b = f(&br.model_record)[i];
132 b_mat[(br.id1, br.id2)] = b;
133 b_mat[(br.id2, br.id1)] = b;
134 }
135 b_mat
136 })
137 }
138
139 pub fn collate_ab<F, T: Scalar + Copy, const N: usize>(&self, f: F) -> [DMatrix<Option<T>>; N]
140 where
141 F: Fn(&A) -> [T; N],
142 {
143 let a = &self.association;
144 array::from_fn(|i| {
145 let mut b_mat = DMatrix::from_element(a.sites_a.len(), a.sites_b.len(), None);
146 for br in &a.binary_ab {
147 b_mat[(br.id1, br.id2)] = Some(f(&br.model_record)[i]);
148 }
149 b_mat
150 })
151 }
152
153 pub fn collate_cc<F, T: Scalar + Copy, const N: usize>(&self, f: F) -> [DMatrix<Option<T>>; N]
154 where
155 F: Fn(&A) -> [T; N],
156 {
157 let a = &self.association;
158 array::from_fn(|i| {
159 let mut b_mat = DMatrix::from_element(a.sites_c.len(), a.sites_c.len(), None);
160 for br in &a.binary_cc {
161 b_mat[(br.id1, br.id2)] = Some(f(&br.model_record)[i]);
162 }
163 b_mat
164 })
165 }
166}
167
168impl<P: Clone, B: Clone, A: CombiningRule<P> + Clone> Parameters<P, B, A> {
169 pub fn new(
170 pure_records: Vec<PureRecord<P, A>>,
171 binary_records: Vec<BinaryRecord<usize, B, A>>,
172 ) -> FeosResult<Self> {
173 let association_parameters = AssociationParameters::new(&pure_records, &binary_records)?;
174 let (molar_weight, pure) = pure_records
175 .iter()
176 .enumerate()
177 .map(|(i, pr)| {
178 (
179 pr.molarweight,
180 PureParameters::from_pure_record(pr.model_record.clone(), i),
181 )
182 })
183 .unzip();
184 let binary = binary_records
185 .iter()
186 .filter_map(|br| {
187 br.model_record
188 .clone()
189 .map(|m| BinaryParameters::new(br.id1, br.id2, m, ()))
190 })
191 .collect();
192
193 Ok(Self {
194 pure,
195 binary,
196 bonds: vec![],
197 association: association_parameters,
198 molar_weight: DVector::from_vec(molar_weight) * (GRAM / MOL),
199 data: (pure_records, binary_records),
200 })
201 }
202
203 pub fn new_pure(pure_record: PureRecord<P, A>) -> FeosResult<Self> {
205 Self::new(vec![pure_record], vec![])
206 }
207
208 pub fn new_binary(
211 pure_records: [PureRecord<P, A>; 2],
212 binary_record: Option<B>,
213 binary_association_records: Vec<BinaryAssociationRecord<A>>,
214 ) -> FeosResult<Self> {
215 let binary_record = vec![BinaryRecord::with_association(
216 0,
217 1,
218 binary_record,
219 binary_association_records,
220 )];
221 Self::new(pure_records.to_vec(), binary_record)
222 }
223
224 pub fn from_records(
226 pure_records: Vec<PureRecord<P, A>>,
227 binary_records: Vec<BinaryRecord<Identifier, B, A>>,
228 identifier_option: IdentifierOption,
229 ) -> FeosResult<Self> {
230 let binary_records =
231 Self::binary_matrix_from_records(&pure_records, &binary_records, identifier_option)?;
232 Self::new(pure_records, binary_records)
233 }
234
235 pub fn from_model_records(model_records: Vec<P>) -> FeosResult<Self> {
238 let pure_records = model_records
239 .into_iter()
240 .map(|r| PureRecord::new(Default::default(), Default::default(), r))
241 .collect();
242 Self::new(pure_records, vec![])
243 }
244}
245
246impl<P: Clone, B: Clone, A: Clone> Parameters<P, B, A> {
247 pub fn binary_matrix_from_records(
249 pure_records: &[PureRecord<P, A>],
250 binary_records: &[BinaryRecord<Identifier, B, A>],
251 identifier_option: IdentifierOption,
252 ) -> FeosResult<Vec<BinaryRecord<usize, B, A>>> {
253 let binary_map: HashMap<_, _> = {
255 binary_records
256 .iter()
257 .filter_map(|br| {
258 let id1 = br.id1.as_str(identifier_option);
259 let id2 = br.id2.as_str(identifier_option);
260 id1.and_then(|id1| {
261 id2.map(|id2| ((id1, id2), (&br.model_record, &br.association_sites)))
262 })
263 })
264 .collect()
265 };
266
267 pure_records
269 .iter()
270 .enumerate()
271 .array_combinations()
272 .chain(
273 pure_records
277 .iter()
278 .enumerate()
279 .map(|(i, p)| [(i, p), (i, p)]),
280 )
281 .map(|[(i1, p1), (i2, p2)]| {
282 let Some(id1) = p1.identifier.as_str(identifier_option) else {
283 return Err(FeosError::MissingParameters(format!(
284 "No {} for pure record {} ({}).",
285 identifier_option, i1, p1.identifier
286 )));
287 };
288 let Some(id2) = p2.identifier.as_str(identifier_option) else {
289 return Err(FeosError::MissingParameters(format!(
290 "No {} for pure record {} ({}).",
291 identifier_option, i2, p2.identifier
292 )));
293 };
294 Ok([(i1, id1), (i2, id2)])
295 })
296 .filter_map(|x| {
297 x.map(|[(i1, id1), (i2, id2)]| {
298 let records = if let Some(&(b, a)) = binary_map.get(&(id1, id2)) {
299 Some((b, a.clone()))
300 } else if let Some(&(b, a)) = binary_map.get(&(id2, id1)) {
301 let a = a
302 .iter()
303 .cloned()
304 .map(|a| BinaryAssociationRecord::with_id(a.id2, a.id1, a.parameters))
305 .collect();
306 Some((b, a))
307 } else {
308 None
309 };
310 records.map(|(b, a)| BinaryRecord::with_association(i1, i2, b.clone(), a))
311 })
312 .transpose()
313 })
314 .collect()
315 }
316}
317
318impl<P: Clone, B: Clone, A: CombiningRule<P> + Clone> Parameters<P, B, A> {
319 pub fn from_json<F, S>(
321 substances: Vec<S>,
322 file_pure: F,
323 file_binary: Option<F>,
324 identifier_option: IdentifierOption,
325 ) -> FeosResult<Self>
326 where
327 F: AsRef<Path>,
328 S: Deref<Target = str>,
329 P: DeserializeOwned,
330 B: DeserializeOwned,
331 A: DeserializeOwned,
332 {
333 Self::from_multiple_json(&[(substances, file_pure)], file_binary, identifier_option)
334 }
335
336 pub fn from_multiple_json<F, S>(
338 input: &[(Vec<S>, F)],
339 file_binary: Option<F>,
340 identifier_option: IdentifierOption,
341 ) -> FeosResult<Self>
342 where
343 F: AsRef<Path>,
344 S: Deref<Target = str>,
345 P: DeserializeOwned,
346 B: DeserializeOwned,
347 A: DeserializeOwned,
348 {
349 let records = PureRecord::from_multiple_json(input, identifier_option)?;
350
351 let binary_records = if let Some(path) = file_binary {
352 let file = File::open(path)?;
353 let reader = BufReader::new(file);
354 serde_json::from_reader(reader)?
355 } else {
356 Vec::new()
357 };
358
359 Self::from_records(records, binary_records, identifier_option)
360 }
361
362 pub fn from_segments(
367 chemical_records: Vec<ChemicalRecord>,
368 segment_records: &[SegmentRecord<P, A>],
369 binary_segment_records: Option<&[BinarySegmentRecord<B, A>]>,
370 ) -> FeosResult<Self>
371 where
372 P: FromSegments,
373 B: FromSegmentsBinary + Default,
374 {
375 let segment_map: HashMap<_, _> =
376 segment_records.iter().map(|s| (&s.identifier, s)).collect();
377
378 let (group_counts, pure_records): (Vec<_>, _) = chemical_records
380 .into_iter()
381 .map(|cr| {
382 let (identifier, group_counts, _) = GroupCount::into_groups(cr);
383 let groups = group_counts
384 .iter()
385 .map(|(s, c)| {
386 segment_map.get(s).map(|&x| (x.clone(), *c)).ok_or_else(|| {
387 FeosError::MissingParameters(format!("No segment record found for {s}"))
388 })
389 })
390 .collect::<FeosResult<Vec<_>>>()?;
391 let pure_record = PureRecord::from_segments(identifier, groups)?;
392 Ok::<_, FeosError>((group_counts, pure_record))
393 })
394 .collect::<Result<Vec<_>, _>>()?
395 .into_iter()
396 .unzip();
397
398 let binary_map: HashMap<_, _> = binary_segment_records
401 .into_iter()
402 .flat_map(|seg| seg.iter())
403 .filter_map(|br| br.model_record.as_ref().map(|b| ((&br.id1, &br.id2), b)))
404 .collect();
405
406 let binary_records = group_counts
410 .iter()
411 .enumerate()
412 .array_combinations()
413 .map(|[(i, sc1), (j, sc2)]| {
414 let mut vec = Vec::new();
415 for (id1, n1) in sc1.iter() {
416 for (id2, n2) in sc2.iter() {
417 let binary = binary_map
418 .get(&(id1, id2))
419 .or_else(|| binary_map.get(&(id2, id1)))
420 .copied()
421 .cloned()
422 .unwrap_or_default();
423 vec.push((binary, *n1, *n2));
424 }
425 }
426 B::from_segments_binary(&vec).map(|br| BinaryRecord::new(i, j, Some(br)))
427 })
428 .collect::<Result<_, _>>()?;
429
430 Self::new(pure_records, binary_records)
431 }
432
433 pub fn from_json_segments<F>(
438 substances: &[&str],
439 file_pure: F,
440 file_segments: F,
441 file_binary: Option<F>,
442 identifier_option: IdentifierOption,
443 ) -> FeosResult<Self>
444 where
445 F: AsRef<Path>,
446 P: FromSegments + DeserializeOwned,
447 B: FromSegmentsBinary + DeserializeOwned + Default,
448 A: DeserializeOwned,
449 {
450 let queried: IndexSet<_> = substances.iter().copied().collect();
451
452 let file = File::open(file_pure)?;
453 let reader = BufReader::new(file);
454 let chemical_records: Vec<ChemicalRecord> = serde_json::from_reader(reader)?;
455 let mut record_map: HashMap<_, _> = chemical_records
456 .into_iter()
457 .filter_map(|record| {
458 record
459 .identifier
460 .as_str(identifier_option)
461 .map(|i| i.to_owned())
462 .map(|i| (i, record))
463 })
464 .collect();
465
466 let available: IndexSet<_> = record_map
468 .keys()
469 .map(|identifier| identifier as &str)
470 .collect();
471 if !queried.is_subset(&available) {
472 let missing: Vec<_> = queried.difference(&available).cloned().collect();
473 let msg = format!("{missing:?}");
474 return Err(FeosError::ComponentsNotFound(msg));
475 };
476
477 let chemical_records: Vec<_> = queried
479 .into_iter()
480 .filter_map(|identifier| record_map.remove(identifier))
481 .collect();
482
483 let segment_records: Vec<SegmentRecord<P, A>> = SegmentRecord::from_json(file_segments)?;
485
486 let binary_records = file_binary
488 .map(|file_binary| {
489 let reader = BufReader::new(File::open(file_binary)?);
490 let binary_records: FeosResult<Vec<BinarySegmentRecord<B, A>>> =
491 Ok(serde_json::from_reader(reader)?);
492 binary_records
493 })
494 .transpose()?;
495
496 Self::from_segments(
497 chemical_records,
498 &segment_records,
499 binary_records.as_deref(),
500 )
501 }
502
503 pub fn identifiers(&self) -> Vec<&Identifier> {
504 self.data.0.iter().map(|pr| &pr.identifier).collect()
505 }
506
507 pub fn subset(&self, component_list: &[usize]) -> Self {
513 let (pure_records, binary_records) = &self.data;
514 let pure_records = component_list
515 .iter()
516 .map(|&i| pure_records[i].clone())
517 .collect();
518 let comp_map: HashMap<_, _> = component_list
519 .iter()
520 .enumerate()
521 .map(|(i, &c)| (c, i))
522 .collect();
523 let binary_records = binary_records
524 .iter()
525 .filter_map(|br| {
526 let id1 = comp_map.get(&br.id1);
527 let id2 = comp_map.get(&br.id2);
528 id1.and_then(|&id1| {
529 id2.map(|&id2| BinaryRecord::new(id1, id2, br.model_record.clone()))
530 })
531 })
532 .collect();
533
534 Self::new(pure_records, binary_records).unwrap()
535 }
536
537 pub fn pure_parameters(&self) -> IndexMap<String, DVector<f64>>
542 where
543 P: Serialize,
544 {
545 let na: Vec<_> = self.association.sites_a.iter().map(|s| s.n).collect();
546 let nb: Vec<_> = self.association.sites_b.iter().map(|s| s.n).collect();
547 let nc: Vec<_> = self.association.sites_c.iter().map(|s| s.n).collect();
548 let pars: IndexSet<_> = self
549 .pure
550 .iter()
551 .flat_map(|p| to_map(&p.model_record).into_keys())
552 .collect();
553 pars.into_iter()
554 .map(|p| {
555 let [pars] = self.collate(|r| [to_map(r).get(&p).copied().unwrap_or_default()]);
556 (p, pars)
557 })
558 .chain(
559 na.iter()
560 .any(|&n| n > 0.0)
561 .then(|| ("na".into(), DVector::from(na))),
562 )
563 .chain(
564 nb.iter()
565 .any(|&n| n > 0.0)
566 .then(|| ("nb".into(), DVector::from(nb))),
567 )
568 .chain(
569 nc.iter()
570 .any(|&n| n > 0.0)
571 .then(|| ("nc".into(), DVector::from(nc))),
572 )
573 .collect()
574 }
575
576 pub fn binary_parameters(&self) -> IndexMap<String, DMatrix<f64>>
581 where
582 B: Serialize,
583 {
584 let pars: IndexSet<String> = self
585 .binary
586 .iter()
587 .flat_map(|b| to_map(&b.model_record).into_keys())
588 .collect();
589 pars.into_iter()
590 .map(|p| {
591 let [pars] = self.collate_binary(|b| [to_map(b)[&p]]);
592 (p, pars)
593 })
594 .collect()
595 }
596
597 pub fn association_parameters_ab(&self) -> IndexMap<String, DMatrix<f64>>
602 where
603 A: Serialize,
604 {
605 let pars: IndexSet<String> = self
606 .association
607 .binary_ab
608 .iter()
609 .flat_map(|a| to_map(&a.model_record).into_keys())
610 .collect();
611 pars.into_iter()
612 .map(|p| {
613 let [pars] = self.collate_ab(|a| [to_map(a).get(&p).copied().unwrap_or_default()]);
614 let pars = pars.map(|p| p.unwrap_or(f64::NAN));
615 (p, pars)
616 })
617 .collect()
618 }
619
620 pub fn association_parameters_cc(&self) -> IndexMap<String, DMatrix<f64>>
625 where
626 A: Serialize,
627 {
628 let pars: IndexSet<String> = self
629 .association
630 .binary_cc
631 .iter()
632 .flat_map(|a| to_map(&a.model_record).into_keys())
633 .collect();
634 pars.into_iter()
635 .map(|mut p| {
636 let [pars] = self.collate_cc(|a| [to_map(a)[&p]]);
637 let pars = pars.map(|p| p.unwrap_or(f64::NAN));
638 if p.ends_with("ab") {
639 let _ = p.split_off(p.len() - 2);
640 }
641 p.push_str("cc");
642 (p, pars)
643 })
644 .collect()
645 }
646}
647
648fn to_map<R: Serialize>(record: R) -> IndexMap<String, f64> {
649 serde_json::from_value(serde_json::to_value(&record).unwrap()).unwrap()
650}
651
652impl<P, B, A, Bo> GcParameters<P, B, A, Bo, f64> {
653 pub fn segment_counts(&self) -> DVector<f64> {
654 DVector::from_vec(self.pure.iter().map(|pr| pr.count).collect())
655 }
656}
657
658impl<P: Clone, B: Clone, A: CombiningRule<P> + Clone, Bo: Clone, C: GroupCount + Default>
659 GcParameters<P, B, A, Bo, C>
660{
661 pub fn from_segments_hetero(
662 chemical_records: Vec<ChemicalRecord>,
663 segment_records: &[SegmentRecord<P, A>],
664 binary_segment_records: Option<&[BinarySegmentRecord<B, A>]>,
665 ) -> FeosResult<Self>
666 where
667 Bo: Default,
668 {
669 let mut bond_records = Vec::new();
670 for s1 in segment_records.iter() {
671 for s2 in segment_records.iter() {
672 bond_records.push(BinarySegmentRecord::new(
673 s1.identifier.clone(),
674 s2.identifier.clone(),
675 Some(Bo::default()),
676 ));
677 }
678 }
679 Self::from_segments_with_bonds(
680 chemical_records,
681 segment_records,
682 binary_segment_records,
683 &bond_records,
684 )
685 }
686
687 pub fn from_segments_with_bonds(
688 chemical_records: Vec<ChemicalRecord>,
689 segment_records: &[SegmentRecord<P, A>],
690 binary_segment_records: Option<&[BinarySegmentRecord<B, A>]>,
691 bond_records: &[BinarySegmentRecord<Bo, ()>],
692 ) -> FeosResult<Self> {
693 let segment_map: HashMap<_, _> =
694 segment_records.iter().map(|s| (&s.identifier, s)).collect();
695
696 let mut bond_records_map = HashMap::new();
697 for bond_record in bond_records {
698 bond_records_map.insert((&bond_record.id1, &bond_record.id2), bond_record);
699 bond_records_map.insert((&bond_record.id2, &bond_record.id1), bond_record);
700 }
701
702 let mut groups = Vec::new();
703 let mut association_sites = Vec::new();
704 let mut bonds = Vec::new();
705 let mut molar_weight: DVector<f64> = DVector::zeros(chemical_records.len());
706 for (i, cr) in chemical_records.iter().enumerate() {
707 let (_, group_counts, bond_counts) = C::into_groups(cr.clone());
708 let n = groups.len();
709 for (s, c) in &group_counts {
710 let Some(&segment) = segment_map.get(s) else {
711 return Err(FeosError::MissingParameters(format!(
712 "No segment record found for {s}"
713 )));
714 };
715 molar_weight[i] += segment.molarweight * c.into_f64();
716 groups.push(PureParameters::from_segment_record(segment, *c, i));
717 association_sites.push(segment.association_sites.clone());
718 }
719 for ([a, b], c) in bond_counts {
720 let id1 = &group_counts[a].0;
721 let id2 = &group_counts[b].0;
722 let Some(&bond) = bond_records_map.get(&(id1, id2)) else {
723 return Err(FeosError::MissingParameters(format!(
724 "No bond record found for {id1}-{id2}"
725 )));
726 };
727 let Some(bond) = bond.model_record.as_ref() else {
728 return Err(FeosError::MissingParameters(format!(
729 "No bond record found for {id1}-{id2}"
730 )));
731 };
732 bonds.push(BinaryParameters::new(a + n, b + n, bond.clone(), c));
733 }
734 }
735
736 let mut binary_records = Vec::new();
737 let mut binary_association_records = Vec::new();
738 if let Some(binary_segment_records) = binary_segment_records {
739 let mut binary_segment_records_map = HashMap::new();
740 for binary_record in binary_segment_records {
741 binary_segment_records_map
742 .insert((&binary_record.id1, &binary_record.id2), binary_record);
743 binary_segment_records_map
744 .insert((&binary_record.id2, &binary_record.id1), binary_record);
745 }
746
747 for [(i1, s1), (i2, s2)] in groups.iter().enumerate().array_combinations() {
748 if s1.component_index != s2.component_index {
749 let id1 = &s1.identifier;
750 let id2 = &s2.identifier;
751 if let Some(&br) = binary_segment_records_map.get(&(id1, id2)) {
752 if let Some(br) = &br.model_record {
753 binary_records.push(BinaryParameters::new(i1, i2, br.clone(), ()));
754 }
755 if !br.association_sites.is_empty() {
756 binary_association_records.push(BinaryParameters::new(
757 i1,
758 i2,
759 br.association_sites.clone(),
760 (),
761 ))
762 }
763 }
764 }
765 }
766 }
767
768 let association_parameters = AssociationParameters::new_hetero(
769 &groups,
770 &association_sites,
771 &binary_association_records,
772 )?;
773
774 Ok(Self {
775 pure: groups,
776 binary: binary_records,
777 bonds,
778 association: association_parameters,
779 molar_weight: molar_weight * (GRAM / MOL),
780 data: (
781 chemical_records,
782 segment_records.to_vec(),
783 binary_segment_records.map(|b| b.to_vec()),
784 bond_records.to_vec(),
785 ),
786 })
787 }
788
789 pub fn from_json_segments_hetero<F>(
794 substances: &[&str],
795 file_pure: F,
796 file_segments: F,
797 file_binary: Option<F>,
798 identifier_option: IdentifierOption,
799 ) -> FeosResult<Self>
800 where
801 F: AsRef<Path>,
802 P: DeserializeOwned,
803 B: DeserializeOwned + Default,
804 A: DeserializeOwned,
805 Bo: Default,
806 {
807 let (chemical_records, segment_records, binary_records) = Self::read_json(
808 substances,
809 file_pure,
810 file_segments,
811 file_binary,
812 identifier_option,
813 )?;
814
815 Self::from_segments_hetero(
816 chemical_records,
817 &segment_records,
818 binary_records.as_deref(),
819 )
820 }
821
822 pub fn from_json_segments_with_bonds<F>(
827 substances: &[&str],
828 file_pure: F,
829 file_segments: F,
830 file_binary: Option<F>,
831 file_bonds: F,
832 identifier_option: IdentifierOption,
833 ) -> FeosResult<Self>
834 where
835 F: AsRef<Path>,
836 P: DeserializeOwned,
837 B: DeserializeOwned + Default,
838 A: DeserializeOwned,
839 Bo: DeserializeOwned,
840 {
841 let (chemical_records, segment_records, binary_records) = Self::read_json(
842 substances,
843 file_pure,
844 file_segments,
845 file_binary,
846 identifier_option,
847 )?;
848
849 let bond_records: Vec<_> = BinaryRecord::from_json(file_bonds)?;
851
852 Self::from_segments_with_bonds(
853 chemical_records,
854 &segment_records,
855 binary_records.as_deref(),
856 &bond_records,
857 )
858 }
859
860 #[expect(clippy::type_complexity)]
861 fn read_json<F>(
862 substances: &[&str],
863 file_pure: F,
864 file_segments: F,
865 file_binary: Option<F>,
866 identifier_option: IdentifierOption,
867 ) -> FeosResult<(
868 Vec<ChemicalRecord>,
869 Vec<SegmentRecord<P, A>>,
870 Option<Vec<BinarySegmentRecord<B, A>>>,
871 )>
872 where
873 F: AsRef<Path>,
874 P: DeserializeOwned,
875 B: DeserializeOwned + Default,
876 A: DeserializeOwned,
877 {
878 let queried: IndexSet<_> = substances.iter().copied().collect();
879
880 let file = File::open(file_pure)?;
881 let reader = BufReader::new(file);
882 let chemical_records: Vec<ChemicalRecord> = serde_json::from_reader(reader)?;
883 let mut record_map: HashMap<_, _> = chemical_records
884 .into_iter()
885 .filter_map(|record| {
886 record
887 .identifier
888 .as_str(identifier_option)
889 .map(|i| i.to_owned())
890 .map(|i| (i, record))
891 })
892 .collect();
893
894 let available: IndexSet<_> = record_map
896 .keys()
897 .map(|identifier| identifier as &str)
898 .collect();
899 if !queried.is_subset(&available) {
900 let missing: Vec<_> = queried.difference(&available).cloned().collect();
901 let msg = format!("{missing:?}");
902 return Err(FeosError::ComponentsNotFound(msg));
903 };
904
905 let chemical_records = queried
907 .into_iter()
908 .filter_map(|identifier| record_map.remove(identifier))
909 .collect();
910
911 let segment_records = SegmentRecord::from_json(file_segments)?;
913
914 let binary_records = file_binary
916 .map(|file_binary| BinaryRecord::from_json(file_binary))
917 .transpose()?;
918
919 Ok((chemical_records, segment_records, binary_records))
920 }
921
922 pub fn component_index(&self) -> Vec<usize> {
923 self.pure.iter().map(|pr| pr.component_index).collect()
924 }
925
926 pub fn identifiers(&self) -> Vec<&Identifier> {
927 self.data.0.iter().map(|cr| &cr.identifier).collect()
928 }
929
930 pub fn subset(&self, component_list: &[usize]) -> Self {
936 let (chemical_records, segment_records, binary_segment_records, bond_records) = &self.data;
937 let chemical_records = component_list
938 .iter()
939 .map(|&i| chemical_records[i].clone())
940 .collect();
941 Self::from_segments_with_bonds(
942 chemical_records,
943 segment_records,
944 binary_segment_records.as_deref(),
945 bond_records,
946 )
947 .unwrap()
948 }
949}