isospec/element.rs
1use std::fmt;
2use std::ops::Range;
3
4use crate::ffi::bridge as sys;
5
6/// A chemical element of IsoSpec's built-in table, with its stable isotopes'
7/// masses and natural abundances.
8///
9/// ```
10/// use isospec::Element;
11/// let carbon = Element::from_symbol("C").unwrap();
12/// assert_eq!(carbon.atomic_number(), 6);
13/// assert_eq!(carbon.isotopes().count(), 2);
14/// assert!(Element::from_symbol("Xx").is_none());
15/// ```
16#[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
17pub struct Element {
18 // The element's first row in the C++ library's flat isotope table; only
19 // ever obtained from the library, so always a valid first row.
20 first: u32,
21}
22
23/// One isotope of an [`Element`].
24#[derive(Clone, Copy, Debug, PartialEq)]
25#[non_exhaustive]
26pub struct Isotope {
27 /// Mass in daltons.
28 pub mass: f64,
29 /// Natural abundance, between 0 and 1.
30 pub abundance: f64,
31 /// Nucleon number.
32 pub mass_number: u32,
33}
34
35impl Element {
36 /// The element with this symbol (`"C"`, `"Se"`, ...), if the table has it.
37 ///
38 /// ```
39 /// use isospec::Element;
40 /// let se = Element::from_symbol("Se").unwrap();
41 /// assert_eq!((se.symbol(), se.name(), se.atomic_number()), ("Se", "selenium", 34));
42 /// assert_eq!(se.isotopes().len(), 6);
43 /// assert!(Element::from_symbol("Xx").is_none());
44 /// ```
45 pub fn from_symbol(symbol: &str) -> Option<Element> {
46 let first = sys::element_find(symbol);
47 Element::from_first_index(first)
48 }
49
50 pub(crate) fn from_first_index(first: i32) -> Option<Element> {
51 if first < 0 || sys::element_isotope_count(first) == 0 {
52 return None;
53 }
54 Some(Element {
55 first: first as u32,
56 })
57 }
58
59 pub(crate) fn first_index(self) -> i32 {
60 self.first as i32
61 }
62
63 fn rows(self) -> Range<usize> {
64 let first = self.first as usize;
65 first..first + sys::element_isotope_count(self.first as i32) as usize
66 }
67
68 /// Every element in the table, in table order (by atomic number).
69 ///
70 /// ```
71 /// use isospec::Element;
72 /// let first = Element::all().next().unwrap();
73 /// assert_eq!(first.symbol(), "H");
74 /// assert!(Element::all().any(|e| e.symbol() == "U"));
75 /// ```
76 pub fn all() -> impl Iterator<Item = Element> {
77 let len = sys::element_table_len();
78 let mut row = 0usize;
79 std::iter::from_fn(move || {
80 if row >= len {
81 return None;
82 }
83 let element = Element { first: row as u32 };
84 row += sys::element_isotope_count(row as i32).max(1) as usize;
85 Some(element)
86 })
87 }
88
89 /// The element's symbol, e.g. `"Se"`.
90 pub fn symbol(self) -> &'static str {
91 sys::element_table_symbol(self.first as usize)
92 }
93
94 /// The element's English name, e.g. `"selenium"`.
95 pub fn name(self) -> &'static str {
96 sys::element_table_name(self.first as usize)
97 }
98
99 /// The atomic number.
100 pub fn atomic_number(self) -> u32 {
101 sys::element_table_atomic_number(self.first as usize) as u32
102 }
103
104 /// The stable isotopes, lightest first.
105 ///
106 /// ```
107 /// use isospec::Element;
108 /// let carbon = Element::from_symbol("C").unwrap();
109 /// let isotopes: Vec<_> = carbon.isotopes().collect();
110 /// assert_eq!(isotopes[0].mass_number, 12);
111 /// assert_eq!(isotopes[0].mass, 12.0);
112 /// assert!((isotopes[1].abundance - 0.0107).abs() < 1e-3);
113 /// ```
114 pub fn isotopes(self) -> impl ExactSizeIterator<Item = Isotope> {
115 self.rows().map(|row| Isotope {
116 mass: sys::element_table_mass(row),
117 abundance: sys::element_table_probability(row),
118 mass_number: sys::element_table_mass_number(row) as u32,
119 })
120 }
121}
122
123impl fmt::Debug for Element {
124 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
125 write!(f, "Element({})", self.symbol())
126 }
127}
128
129impl fmt::Display for Element {
130 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
131 f.write_str(self.symbol())
132 }
133}