1use std::{num::NonZeroU16, sync::LazyLock};
2
3use bincode::config::Configuration;
4
5use crate::system::{da, fraction, Ratio};
6
7include!("shared/element.rs");
8
9impl Element {
10 pub fn is_valid(self, isotope: Option<NonZeroU16>) -> bool {
12 if self == Self::Electron {
13 isotope.is_none()
14 } else {
15 isotope.map_or_else(
16 || ELEMENTAL_DATA[self as usize - 1].0.is_some(),
17 |isotope| {
18 ELEMENTAL_DATA[self as usize - 1]
19 .2
20 .iter()
21 .any(|(ii, _, _)| *ii == isotope.get())
22 },
23 )
24 }
25 }
26
27 pub fn isotopes(self) -> &'static [(u16, Mass, f64)] {
29 &ELEMENTAL_DATA[self as usize - 1].2
30 }
31
32 pub fn mass(self, isotope: Option<NonZeroU16>) -> Option<Mass> {
34 if self == Self::Electron {
35 return Some(da(5.485_799_090_65e-4));
36 }
37 isotope.map_or_else(
38 || ELEMENTAL_DATA[self as usize - 1].0,
39 |isotope| {
40 ELEMENTAL_DATA[self as usize - 1]
42 .2
43 .iter()
44 .find(|(ii, _, _)| *ii == isotope.get())
45 .map(|(_, m, _)| *m)
46 },
47 )
48 }
49
50 pub fn average_weight(self, isotope: Option<NonZeroU16>) -> Option<Mass> {
52 if self == Self::Electron {
53 return Some(da(5.485_799_090_65e-4));
54 }
55 isotope.map_or_else(
56 || ELEMENTAL_DATA[self as usize - 1].1,
57 |isotope| {
58 ELEMENTAL_DATA[self as usize - 1]
60 .2
61 .iter()
62 .find(|(ii, _, _)| *ii == isotope.get())
63 .map(|(_, m, _)| *m)
64 },
65 )
66 }
67
68 pub fn most_abundant_mass(self, isotope: Option<NonZeroU16>, n: i32) -> Option<Mass> {
70 if self == Self::Electron {
71 return Some(da(5.485_799_090_65e-4) * Ratio::new::<fraction>(f64::from(n)));
72 }
73 Some(
74 if let Some(isotope) = isotope {
75 ELEMENTAL_DATA[self as usize - 1]
77 .2
78 .iter()
79 .find(|(ii, _, _)| *ii == isotope.get())
80 .map(|(_, m, _)| *m)?
81 } else {
82 let mut max = None;
84 for iso in &ELEMENTAL_DATA[self as usize - 1].2 {
85 let chance = iso.2 * f64::from(n);
86 if max.is_none_or(|m: (Mass, f64)| chance > m.1) {
87 max = Some((iso.1, chance));
88 }
89 }
90 max?.0
91 } * Ratio::new::<fraction>(f64::from(n)),
92 )
93 }
94}
95
96pub static ELEMENTAL_DATA: LazyLock<ElementalData> = LazyLock::new(|| {
100 bincode::serde::decode_from_slice::<ElementalData, Configuration>(
101 include_bytes!("databases/elements.dat"),
102 Configuration::default(),
103 )
104 .unwrap()
105 .0
106});
107
108#[cfg(test)]
109#[expect(clippy::missing_panics_doc)]
110mod test {
111 #[test]
112 fn hill_notation() {
113 assert_eq!(
114 molecular_formula!(C 6 O 5 H 10).hill_notation(),
115 "C6H10O5".to_string()
116 );
117 }
118}