Skip to main content

use_molar_mass/
calculation.rs

1use use_chemical_formula::ChemicalFormula;
2
3use crate::{
4    AtomicMassLookup, ElementMassContribution, FormulaMolarMass, MassContributionSet,
5    MolarMassValidationError,
6};
7
8/// A formula and atomic-mass lookup ready for molar-mass calculation.
9#[derive(Clone, Debug, PartialEq)]
10pub struct MolarMassCalculation {
11    formula: ChemicalFormula,
12    lookup: AtomicMassLookup,
13}
14
15impl MolarMassCalculation {
16    /// Creates a calculation from a formula and explicit lookup table.
17    #[must_use]
18    pub const fn new(formula: ChemicalFormula, lookup: AtomicMassLookup) -> Self {
19        Self { formula, lookup }
20    }
21
22    /// Creates a calculation using the RustUse standard atomic-mass table.
23    ///
24    /// # Errors
25    ///
26    /// Returns a molar-mass validation error if any formula element is missing
27    /// from the RustUse atomic-mass table.
28    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    /// Returns the source formula.
37    #[must_use]
38    pub const fn formula(&self) -> &ChemicalFormula {
39        &self.formula
40    }
41
42    /// Returns the atomic-mass lookup table.
43    #[must_use]
44    pub const fn lookup(&self) -> &AtomicMassLookup {
45        &self.lookup
46    }
47
48    /// Calculates the formula molar mass.
49    ///
50    /// # Errors
51    ///
52    /// Returns a structured molar-mass validation error when a formula count is
53    /// invalid, a count cannot be represented, an atomic mass is missing, or the
54    /// calculated molar mass is invalid.
55    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}