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