Skip to main content

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}