use_molar_mass/
calculation.rs1use use_chemical_formula::ChemicalFormula;
2
3use crate::{
4 AtomicMassLookup, ElementMassContribution, FormulaMolarMass, MassContributionSet,
5 MolarMassValidationError,
6};
7
8#[derive(Clone, Debug, PartialEq)]
10pub struct MolarMassCalculation {
11 formula: ChemicalFormula,
12 lookup: AtomicMassLookup,
13}
14
15impl MolarMassCalculation {
16 #[must_use]
18 pub const fn new(formula: ChemicalFormula, lookup: AtomicMassLookup) -> Self {
19 Self { formula, lookup }
20 }
21
22 pub fn with_standard_atomic_masses(
29 formula: ChemicalFormula,
30 ) -> Result<Self, MolarMassValidationError> {
31 let lookup = AtomicMassLookup::from_formula(&formula)?;
32
33 Ok(Self { formula, lookup })
34 }
35
36 #[must_use]
38 pub const fn formula(&self) -> &ChemicalFormula {
39 &self.formula
40 }
41
42 #[must_use]
44 pub const fn lookup(&self) -> &AtomicMassLookup {
45 &self.lookup
46 }
47
48 pub fn calculate(&self) -> Result<FormulaMolarMass, MolarMassValidationError> {
56 let mut contributions = Vec::new();
57
58 for (symbol, count) in self.formula.element_counts() {
59 if count == 0 {
60 return Err(MolarMassValidationError::ZeroElementCount { symbol });
61 }
62
63 let count = u32::try_from(count).map_err(|_| {
64 MolarMassValidationError::FormulaCountTooLarge {
65 symbol: symbol.clone(),
66 count,
67 }
68 })?;
69 let atomic_mass = self.lookup.atomic_mass(&symbol).ok_or_else(|| {
70 MolarMassValidationError::MissingAtomicMass {
71 symbol: symbol.clone(),
72 }
73 })?;
74
75 contributions.push(ElementMassContribution::new(&symbol, atomic_mass, count)?);
76 }
77
78 FormulaMolarMass::new(
79 self.formula.clone(),
80 MassContributionSet::from_contributions(contributions),
81 )
82 }
83}