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