Skip to main content

chematic_core/
element.rs

1//! Periodic-table elements indexed by atomic number (1–118).
2
3/// An element represented as its atomic number, wrapped in a newtype.
4///
5/// Zero-copy: implements `Copy`; comparison and hashing are O(1).
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
7pub struct Element(u8);
8
9impl Element {
10    // -- commonly used element constants ----------------------------------
11    pub const H: Element = Element(1);
12    pub const HE: Element = Element(2);
13    pub const LI: Element = Element(3);
14    pub const BE: Element = Element(4);
15    pub const B: Element = Element(5);
16    pub const C: Element = Element(6);
17    pub const N: Element = Element(7);
18    pub const O: Element = Element(8);
19    pub const F: Element = Element(9);
20    pub const NE: Element = Element(10);
21    pub const NA: Element = Element(11);
22    pub const MG: Element = Element(12);
23    pub const AL: Element = Element(13);
24    pub const SI: Element = Element(14);
25    pub const P: Element = Element(15);
26    pub const S: Element = Element(16);
27    pub const CL: Element = Element(17);
28    pub const AR: Element = Element(18);
29    pub const K: Element = Element(19);
30    pub const CA: Element = Element(20);
31    pub const SC: Element = Element(21);
32    pub const TI: Element = Element(22);
33    pub const V: Element = Element(23);
34    pub const CR: Element = Element(24);
35    pub const MN: Element = Element(25);
36    pub const FE: Element = Element(26);
37    pub const CO: Element = Element(27);
38    pub const NI: Element = Element(28);
39    pub const CU: Element = Element(29);
40    pub const ZN: Element = Element(30);
41    pub const GA: Element = Element(31);
42    pub const GE: Element = Element(32);
43    pub const AS: Element = Element(33);
44    pub const SE: Element = Element(34);
45    pub const BR: Element = Element(35);
46    pub const KR: Element = Element(36);
47    pub const RB: Element = Element(37);
48    pub const SR: Element = Element(38);
49    pub const Y: Element = Element(39);
50    pub const ZR: Element = Element(40);
51    pub const NB: Element = Element(41);
52    pub const MO: Element = Element(42);
53    pub const TC: Element = Element(43);
54    pub const RU: Element = Element(44);
55    pub const RH: Element = Element(45);
56    pub const PD: Element = Element(46);
57    pub const AG: Element = Element(47);
58    pub const CD: Element = Element(48);
59    pub const IN: Element = Element(49);
60    pub const SN: Element = Element(50);
61    pub const SB: Element = Element(51);
62    pub const TE: Element = Element(52);
63    pub const I: Element = Element(53);
64    pub const XE: Element = Element(54);
65    pub const CS: Element = Element(55);
66    pub const BA: Element = Element(56);
67    pub const LA: Element = Element(57);
68    pub const CE: Element = Element(58);
69    pub const PR: Element = Element(59);
70    pub const ND: Element = Element(60);
71    pub const PM: Element = Element(61);
72    pub const SM: Element = Element(62);
73    pub const EU: Element = Element(63);
74    pub const GD: Element = Element(64);
75    pub const TB: Element = Element(65);
76    pub const DY: Element = Element(66);
77    pub const HO: Element = Element(67);
78    pub const ER: Element = Element(68);
79    pub const TM: Element = Element(69);
80    pub const YB: Element = Element(70);
81    pub const LU: Element = Element(71);
82    pub const HF: Element = Element(72);
83    pub const TA: Element = Element(73);
84    pub const W: Element = Element(74);
85    pub const RE: Element = Element(75);
86    pub const OS: Element = Element(76);
87    pub const IR: Element = Element(77);
88    pub const PT: Element = Element(78);
89    pub const AU: Element = Element(79);
90    pub const HG: Element = Element(80);
91    pub const TL: Element = Element(81);
92    pub const PB: Element = Element(82);
93    pub const BI: Element = Element(83);
94    pub const PO: Element = Element(84);
95    pub const AT: Element = Element(85);
96    pub const RN: Element = Element(86);
97    pub const FR: Element = Element(87);
98    pub const RA: Element = Element(88);
99    pub const AC: Element = Element(89);
100    pub const TH: Element = Element(90);
101    pub const PA: Element = Element(91);
102    pub const U: Element = Element(92);
103    pub const NP: Element = Element(93);
104    pub const PU: Element = Element(94);
105    pub const AM: Element = Element(95);
106    pub const CM: Element = Element(96);
107    pub const BK: Element = Element(97);
108    pub const CF: Element = Element(98);
109    pub const ES: Element = Element(99);
110    pub const FM: Element = Element(100);
111    pub const MD: Element = Element(101);
112    pub const NO: Element = Element(102);
113    pub const LR: Element = Element(103);
114    pub const RF: Element = Element(104);
115    pub const DB: Element = Element(105);
116    pub const SG: Element = Element(106);
117    pub const BH: Element = Element(107);
118    pub const HS: Element = Element(108);
119    pub const MT: Element = Element(109);
120    pub const DS: Element = Element(110);
121    pub const RG: Element = Element(111);
122    pub const CN: Element = Element(112);
123    pub const NH: Element = Element(113);
124    pub const FL: Element = Element(114);
125    pub const MC: Element = Element(115);
126    pub const LV: Element = Element(116);
127    pub const TS: Element = Element(117);
128    pub const OG: Element = Element(118);
129
130    // -- constructors -------------------------------------------------------
131
132    /// Create an Element from an atomic number. Returns `None` if `n` is outside 1–118.
133    #[inline]
134    pub const fn from_atomic_number(n: u8) -> Option<Self> {
135        if n >= 1 && n <= 118 {
136            Some(Self(n))
137        } else {
138            None
139        }
140    }
141
142    /// Create an Element from its symbol (title-case, e.g. "C", "Cl").
143    /// Case-sensitive: "C" is carbon, "c" is not accepted.
144    pub fn from_symbol(s: &str) -> Option<Self> {
145        match s {
146            "H"  => Some(Self::H),
147            "He" => Some(Self::HE),
148            "Li" => Some(Self::LI),
149            "Be" => Some(Self::BE),
150            "B"  => Some(Self::B),
151            "C"  => Some(Self::C),
152            "N"  => Some(Self::N),
153            "O"  => Some(Self::O),
154            "F"  => Some(Self::F),
155            "Ne" => Some(Self::NE),
156            "Na" => Some(Self::NA),
157            "Mg" => Some(Self::MG),
158            "Al" => Some(Self::AL),
159            "Si" => Some(Self::SI),
160            "P"  => Some(Self::P),
161            "S"  => Some(Self::S),
162            "Cl" => Some(Self::CL),
163            "Ar" => Some(Self::AR),
164            "K"  => Some(Self::K),
165            "Ca" => Some(Self::CA),
166            "Sc" => Some(Self::SC),
167            "Ti" => Some(Self::TI),
168            "V"  => Some(Self::V),
169            "Cr" => Some(Self::CR),
170            "Mn" => Some(Self::MN),
171            "Fe" => Some(Self::FE),
172            "Co" => Some(Self::CO),
173            "Ni" => Some(Self::NI),
174            "Cu" => Some(Self::CU),
175            "Zn" => Some(Self::ZN),
176            "Ga" => Some(Self::GA),
177            "Ge" => Some(Self::GE),
178            "As" => Some(Self::AS),
179            "Se" => Some(Self::SE),
180            "Br" => Some(Self::BR),
181            "Kr" => Some(Self::KR),
182            "Rb" => Some(Self::RB),
183            "Sr" => Some(Self::SR),
184            "Y"  => Some(Self::Y),
185            "Zr" => Some(Self::ZR),
186            "Nb" => Some(Self::NB),
187            "Mo" => Some(Self::MO),
188            "Tc" => Some(Self::TC),
189            "Ru" => Some(Self::RU),
190            "Rh" => Some(Self::RH),
191            "Pd" => Some(Self::PD),
192            "Ag" => Some(Self::AG),
193            "Cd" => Some(Self::CD),
194            "In" => Some(Self::IN),
195            "Sn" => Some(Self::SN),
196            "Sb" => Some(Self::SB),
197            "Te" => Some(Self::TE),
198            "I"  => Some(Self::I),
199            "Xe" => Some(Self::XE),
200            "Cs" => Some(Self::CS),
201            "Ba" => Some(Self::BA),
202            "La" => Some(Self::LA),
203            "Ce" => Some(Self::CE),
204            "Pr" => Some(Self::PR),
205            "Nd" => Some(Self::ND),
206            "Pm" => Some(Self::PM),
207            "Sm" => Some(Self::SM),
208            "Eu" => Some(Self::EU),
209            "Gd" => Some(Self::GD),
210            "Tb" => Some(Self::TB),
211            "Dy" => Some(Self::DY),
212            "Ho" => Some(Self::HO),
213            "Er" => Some(Self::ER),
214            "Tm" => Some(Self::TM),
215            "Yb" => Some(Self::YB),
216            "Lu" => Some(Self::LU),
217            "Hf" => Some(Self::HF),
218            "Ta" => Some(Self::TA),
219            "W"  => Some(Self::W),
220            "Re" => Some(Self::RE),
221            "Os" => Some(Self::OS),
222            "Ir" => Some(Self::IR),
223            "Pt" => Some(Self::PT),
224            "Au" => Some(Self::AU),
225            "Hg" => Some(Self::HG),
226            "Tl" => Some(Self::TL),
227            "Pb" => Some(Self::PB),
228            "Bi" => Some(Self::BI),
229            "Po" => Some(Self::PO),
230            "At" => Some(Self::AT),
231            "Rn" => Some(Self::RN),
232            "Fr" => Some(Self::FR),
233            "Ra" => Some(Self::RA),
234            "Ac" => Some(Self::AC),
235            "Th" => Some(Self::TH),
236            "Pa" => Some(Self::PA),
237            "U"  => Some(Self::U),
238            "Np" => Some(Self::NP),
239            "Pu" => Some(Self::PU),
240            "Am" => Some(Self::AM),
241            "Cm" => Some(Self::CM),
242            "Bk" => Some(Self::BK),
243            "Cf" => Some(Self::CF),
244            "Es" => Some(Self::ES),
245            "Fm" => Some(Self::FM),
246            "Md" => Some(Self::MD),
247            "No" => Some(Self::NO),
248            "Lr" => Some(Self::LR),
249            "Rf" => Some(Self::RF),
250            "Db" => Some(Self::DB),
251            "Sg" => Some(Self::SG),
252            "Bh" => Some(Self::BH),
253            "Hs" => Some(Self::HS),
254            "Mt" => Some(Self::MT),
255            "Ds" => Some(Self::DS),
256            "Rg" => Some(Self::RG),
257            "Cn" => Some(Self::CN),
258            "Nh" => Some(Self::NH),
259            "Fl" => Some(Self::FL),
260            "Mc" => Some(Self::MC),
261            "Lv" => Some(Self::LV),
262            "Ts" => Some(Self::TS),
263            "Og" => Some(Self::OG),
264            _ => None,
265        }
266    }
267
268    /// Return the IUPAC element symbol (title-case).
269    #[inline]
270    pub fn symbol(self) -> &'static str {
271        SYMBOLS[(self.0 as usize) - 1]
272    }
273
274    /// Return the atomic number (1–118).
275    #[inline]
276    pub fn atomic_number(self) -> u8 {
277        self.0
278    }
279
280    /// Returns true if this element is in the OpenSMILES organic subset
281    /// (B, C, N, O, P, S, F, Cl, Br, I) — these may carry implicit H without brackets.
282    #[inline]
283    pub fn is_organic_subset(self) -> bool {
284        matches!(self.0, 5 | 6 | 7 | 8 | 9 | 15 | 16 | 17 | 35 | 53)
285    }
286
287    /// Normal valence list (ascending). Empty = undefined (transition metals, etc.).
288    /// Used for computing implicit H counts in organic-subset atoms.
289    pub fn normal_valences(self) -> &'static [u8] {
290        match self.0 {
291            1  => &[1],           // H
292            5  => &[3],           // B
293            6  => &[4],           // C
294            7  => &[3, 5],        // N
295            8  => &[2],           // O
296            9  => &[1],           // F
297            14 => &[4],           // Si
298            15 => &[3, 5],        // P
299            16 => &[2, 4, 6],     // S
300            17 => &[1, 3, 5, 7],  // Cl
301            33 => &[3, 5],        // As
302            34 => &[2, 4, 6],     // Se
303            35 => &[1, 3, 5, 7],  // Br
304            53 => &[1, 3, 5, 7],  // I
305            _  => &[],
306        }
307    }
308}
309
310impl core::fmt::Display for Element {
311    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
312        f.write_str(self.symbol())
313    }
314}
315
316/// Element symbol table indexed by (atomic_number - 1), covering elements 1–118.
317static SYMBOLS: [&str; 118] = [
318    "H",  "He", "Li", "Be", "B",  "C",  "N",  "O",  "F",  "Ne",
319    "Na", "Mg", "Al", "Si", "P",  "S",  "Cl", "Ar", "K",  "Ca",
320    "Sc", "Ti", "V",  "Cr", "Mn", "Fe", "Co", "Ni", "Cu", "Zn",
321    "Ga", "Ge", "As", "Se", "Br", "Kr", "Rb", "Sr", "Y",  "Zr",
322    "Nb", "Mo", "Tc", "Ru", "Rh", "Pd", "Ag", "Cd", "In", "Sn",
323    "Sb", "Te", "I",  "Xe", "Cs", "Ba", "La", "Ce", "Pr", "Nd",
324    "Pm", "Sm", "Eu", "Gd", "Tb", "Dy", "Ho", "Er", "Tm", "Yb",
325    "Lu", "Hf", "Ta", "W",  "Re", "Os", "Ir", "Pt", "Au", "Hg",
326    "Tl", "Pb", "Bi", "Po", "At", "Rn", "Fr", "Ra", "Ac", "Th",
327    "Pa", "U",  "Np", "Pu", "Am", "Cm", "Bk", "Cf", "Es", "Fm",
328    "Md", "No", "Lr", "Rf", "Db", "Sg", "Bh", "Hs", "Mt", "Ds",
329    "Rg", "Cn", "Nh", "Fl", "Mc", "Lv", "Ts", "Og",
330];
331
332#[cfg(test)]
333mod tests {
334    use super::*;
335
336    #[test]
337    fn test_roundtrip_symbol() {
338        for n in 1u8..=118 {
339            let elem = Element::from_atomic_number(n).unwrap();
340            let sym = elem.symbol();
341            let back = Element::from_symbol(sym).unwrap_or_else(|| panic!("no elem for symbol {sym}"));
342            assert_eq!(elem, back, "roundtrip failed for atomic number {n}");
343        }
344    }
345
346    #[test]
347    fn test_organic_subset() {
348        for sym in &["B", "C", "N", "O", "P", "S", "F", "Cl", "Br", "I"] {
349            assert!(Element::from_symbol(sym).unwrap().is_organic_subset(), "{sym} should be in organic subset");
350        }
351        assert!(!Element::H.is_organic_subset());
352        assert!(!Element::FE.is_organic_subset());
353    }
354
355    #[test]
356    fn test_valences() {
357        assert_eq!(Element::C.normal_valences(), &[4]);
358        assert_eq!(Element::N.normal_valences(), &[3, 5]);
359        assert_eq!(Element::S.normal_valences(), &[2, 4, 6]);
360    }
361}