use_molar_mass/
atomic_mass_lookup.rs1use std::collections::BTreeMap;
2
3use use_atomic_mass::atomic_mass_by_symbol;
4use use_chemical_formula::{ChemicalFormula, is_valid_element_symbol};
5
6use crate::{AtomicMassEntry, MolarMassValidationError};
7
8#[derive(Clone, Debug, Default, PartialEq)]
10pub struct AtomicMassLookup {
11 entries: BTreeMap<String, f64>,
12}
13
14impl AtomicMassLookup {
15 #[must_use]
17 pub const fn new() -> Self {
18 Self {
19 entries: BTreeMap::new(),
20 }
21 }
22
23 #[must_use]
25 pub fn from_entries(entries: impl IntoIterator<Item = AtomicMassEntry>) -> Self {
26 let mut lookup = Self::new();
27
28 for entry in entries {
29 lookup.insert(entry);
30 }
31
32 lookup
33 }
34
35 pub fn from_pairs<'a>(
41 entries: impl IntoIterator<Item = (&'a str, f64)>,
42 ) -> Result<Self, MolarMassValidationError> {
43 let mut lookup = Self::new();
44
45 for (symbol, atomic_mass) in entries {
46 lookup.insert_atomic_mass(symbol, atomic_mass)?;
47 }
48
49 Ok(lookup)
50 }
51
52 pub fn from_formula(formula: &ChemicalFormula) -> Result<Self, MolarMassValidationError> {
60 let mut lookup = Self::new();
61
62 for (symbol, count) in formula.element_counts() {
63 if count == 0 {
64 return Err(MolarMassValidationError::ZeroElementCount { symbol });
65 }
66
67 let atomic_mass = atomic_mass_by_symbol(&symbol).ok_or_else(|| {
68 MolarMassValidationError::MissingAtomicMass {
69 symbol: symbol.clone(),
70 }
71 })?;
72 lookup.insert_atomic_mass(&symbol, atomic_mass)?;
73 }
74
75 Ok(lookup)
76 }
77
78 pub fn insert(&mut self, entry: AtomicMassEntry) -> Option<f64> {
80 self.entries
81 .insert(entry.symbol().to_owned(), entry.atomic_mass())
82 }
83
84 pub fn insert_atomic_mass(
90 &mut self,
91 symbol: &str,
92 atomic_mass: f64,
93 ) -> Result<Option<f64>, MolarMassValidationError> {
94 AtomicMassEntry::new(symbol, atomic_mass).map(|entry| self.insert(entry))
95 }
96
97 #[must_use]
99 pub fn atomic_mass(&self, symbol: &str) -> Option<f64> {
100 self.entries.get(symbol).copied()
101 }
102
103 #[must_use]
105 pub fn entry(&self, symbol: &str) -> Option<AtomicMassEntry> {
106 self.entries
107 .get_key_value(symbol)
108 .map(|(stored_symbol, atomic_mass)| {
109 AtomicMassEntry::from_validated(stored_symbol.clone(), *atomic_mass)
110 })
111 }
112
113 #[must_use]
115 pub fn contains_symbol(&self, symbol: &str) -> bool {
116 self.entries.contains_key(symbol)
117 }
118
119 #[must_use]
121 pub fn len(&self) -> usize {
122 self.entries.len()
123 }
124
125 #[must_use]
127 pub fn is_empty(&self) -> bool {
128 self.entries.is_empty()
129 }
130
131 pub fn iter(&self) -> impl Iterator<Item = (&str, f64)> + '_ {
133 self.entries
134 .iter()
135 .map(|(symbol, atomic_mass)| (symbol.as_str(), *atomic_mass))
136 }
137}
138
139pub(crate) fn validate_symbol(symbol: &str) -> Result<&str, MolarMassValidationError> {
140 let symbol = symbol.trim();
141
142 if is_valid_element_symbol(symbol) {
143 Ok(symbol)
144 } else {
145 Err(MolarMassValidationError::InvalidElementSymbol(
146 symbol.to_owned(),
147 ))
148 }
149}