1#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
7pub struct Element(u8);
8
9impl Element {
10 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 #[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 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 #[inline]
270 pub fn symbol(self) -> &'static str {
271 SYMBOLS[(self.0 as usize) - 1]
272 }
273
274 #[inline]
276 pub fn atomic_number(self) -> u8 {
277 self.0
278 }
279
280 #[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 #[inline]
290 pub fn vdw_radius(self) -> f32 {
291 VDW_RADII[(self.0 as usize) - 1]
292 }
293
294 #[inline]
297 pub fn covalent_radius(self) -> f32 {
298 COVALENT_RADII[(self.0 as usize) - 1]
299 }
300
301 pub fn atomic_mass(self) -> f64 {
304 ATOMIC_MASSES[(self.0 as usize) - 1]
305 }
306
307 pub fn normal_valences(self) -> &'static [u8] {
310 match self.0 {
311 1 => &[1], 5 => &[3], 6 => &[4], 7 => &[3, 5], 8 => &[2], 9 => &[1], 14 => &[4], 15 => &[3, 5], 16 => &[2, 4, 6], 17 => &[1, 3, 5, 7], 33 => &[3, 5], 34 => &[2, 4, 6], 35 => &[1, 3, 5, 7], 52 => &[2, 4, 6], 53 => &[1, 3, 5, 7], _ => &[],
332 }
333 }
334}
335
336impl core::fmt::Display for Element {
337 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
338 f.write_str(self.symbol())
339 }
340}
341
342static SYMBOLS: [&str; 118] = [
344 "H", "He", "Li", "Be", "B", "C", "N", "O", "F", "Ne", "Na", "Mg", "Al", "Si", "P", "S", "Cl",
345 "Ar", "K", "Ca", "Sc", "Ti", "V", "Cr", "Mn", "Fe", "Co", "Ni", "Cu", "Zn", "Ga", "Ge", "As",
346 "Se", "Br", "Kr", "Rb", "Sr", "Y", "Zr", "Nb", "Mo", "Tc", "Ru", "Rh", "Pd", "Ag", "Cd", "In",
347 "Sn", "Sb", "Te", "I", "Xe", "Cs", "Ba", "La", "Ce", "Pr", "Nd", "Pm", "Sm", "Eu", "Gd", "Tb",
348 "Dy", "Ho", "Er", "Tm", "Yb", "Lu", "Hf", "Ta", "W", "Re", "Os", "Ir", "Pt", "Au", "Hg", "Tl",
349 "Pb", "Bi", "Po", "At", "Rn", "Fr", "Ra", "Ac", "Th", "Pa", "U", "Np", "Pu", "Am", "Cm", "Bk",
350 "Cf", "Es", "Fm", "Md", "No", "Lr", "Rf", "Db", "Sg", "Bh", "Hs", "Mt", "Ds", "Rg", "Cn", "Nh",
351 "Fl", "Mc", "Lv", "Ts", "Og",
352];
353
354#[rustfmt::skip]
358static VDW_RADII: [f32; 118] = [
359 1.20, 1.40, 1.82, 1.53, 1.92, 1.70, 1.55, 1.52, 1.47, 1.54,
361 2.27, 1.73, 1.84, 2.10, 1.80, 1.80, 1.75, 1.88, 2.75, 2.31,
363 2.11, 2.00, 1.92, 1.93, 1.97, 1.96, 2.00, 1.63, 1.40, 1.39,
365 1.87, 2.11, 1.85, 1.90, 1.85, 2.02, 3.03, 2.49, 2.34, 2.23,
367 2.18, 2.17, 2.16, 2.13, 2.10, 1.63, 1.72, 1.58, 1.93, 2.17,
369 2.06, 2.06, 1.98, 2.16, 3.43, 2.68, 2.57, 2.56, 2.54, 2.51,
371 2.50, 2.48, 2.45, 2.43, 2.42, 2.40, 2.38, 2.37, 2.36, 2.35,
373 2.34, 2.23, 2.22, 2.18, 2.16, 2.16, 2.13, 1.75, 1.66, 1.55,
375 1.96, 2.02, 2.07, 1.97, 2.02, 2.20, 3.48, 2.83, 2.64, 2.54,
377 2.52, 2.41, 2.39, 2.43, 2.44, 2.45, 2.44, 2.45, 2.45, 1.70,
379 1.70, 1.70, 1.70, 1.70, 1.70, 1.70, 1.70, 1.70, 1.70, 1.70,
381 1.70, 1.70, 1.70, 1.70, 1.70, 1.70, 1.70, 1.70,
383];
384
385#[rustfmt::skip]
389static COVALENT_RADII: [f32; 118] = [
390 0.31, 0.28, 1.28, 0.96, 0.84, 0.77, 0.71, 0.66, 0.57, 0.58,
392 1.66, 1.41, 1.21, 1.11, 1.07, 1.05, 1.02, 1.06, 2.03, 1.76,
394 1.70, 1.60, 1.53, 1.39, 1.50, 1.52, 1.38, 1.24, 1.32, 1.22,
396 1.22, 1.20, 1.19, 1.20, 1.20, 1.16, 2.20, 1.95, 1.90, 1.75,
398 1.64, 1.54, 1.47, 1.46, 1.42, 1.39, 1.45, 1.44, 1.42, 1.39,
400 1.39, 1.38, 1.39, 1.40, 2.44, 2.15, 2.07, 2.04, 2.03, 2.01,
402 1.99, 1.98, 1.98, 1.96, 1.94, 1.92, 1.92, 1.89, 1.90, 1.87,
404 1.87, 1.75, 1.70, 1.62, 1.51, 1.44, 1.41, 1.36, 1.36, 1.32,
406 1.45, 1.46, 1.48, 1.40, 1.50, 1.50, 2.60, 2.21, 2.15, 2.06,
408 2.00, 1.96, 1.90, 1.87, 1.80, 1.69, 1.68, 1.68, 1.65, 1.67,
410 1.73, 1.76, 1.61, 1.57, 1.49, 1.43, 1.41, 1.34, 1.29, 1.28,
412 1.21, 1.22, 1.36, 1.43, 1.62, 1.75, 1.65, 1.57,
414];
415
416#[rustfmt::skip]
419static ATOMIC_MASSES: [f64; 118] = [
420 1.007825, 4.002603, 7.016003, 9.012182, 11.009305, 12.000000, 14.003074, 15.994915, 18.998403, 19.992440,
422 22.989770, 23.985042, 26.981538, 27.976927, 30.973762, 31.972071, 34.968853, 39.962383, 38.963707, 39.962591,
424 44.955910, 47.947947, 50.943963, 51.940512, 54.938045, 55.934840, 58.933195, 57.935343, 62.929598, 63.929145,
426 68.925581, 73.921218, 74.921596, 79.916521, 78.918337, 83.911507, 84.911789, 87.905614, 88.905848, 89.904704,
428 92.906378, 97.905408, 98.906255, 101.904435, 102.905504, 105.903483, 106.905093, 113.903358, 114.903878, 119.902197,
430 120.903818, 129.906224, 126.904477, 131.904154, 132.905447, 137.905241, 138.906349, 139.905434, 140.907648, 141.907720,
432 145.000000, 151.919729, 152.921227, 157.924103, 158.925343, 163.929171, 164.930319, 165.930292, 168.934211, 173.938858,
434 174.940768, 179.946549, 180.947885, 183.950931, 186.955751, 191.961479, 192.960837, 194.964791, 196.966569, 201.972326,
436 202.972329, 207.982019, 208.980384, 209.000000, 210.000000, 222.000000, 223.000000, 226.000000, 227.000000, 232.038054,
438 231.035884, 238.028915, 237.000000, 244.000000, 243.000000, 247.000000, 247.000000, 251.000000, 252.000000, 257.000000,
440 258.000000, 259.000000, 266.000000, 267.000000, 268.000000, 269.000000, 270.000000, 270.000000, 278.000000, 281.000000,
442 280.000000, 285.000000, 286.000000, 289.000000, 290.000000, 293.000000, 294.000000, 294.000000,
444];
445
446#[cfg(test)]
447mod tests {
448 use super::*;
449
450 #[test]
451 fn test_roundtrip_symbol() {
452 for n in 1u8..=118 {
453 let elem = Element::from_atomic_number(n).unwrap();
454 let sym = elem.symbol();
455 let back =
456 Element::from_symbol(sym).unwrap_or_else(|| panic!("no elem for symbol {sym}"));
457 assert_eq!(elem, back, "roundtrip failed for atomic number {n}");
458 }
459 }
460
461 #[test]
462 fn test_organic_subset() {
463 for sym in &["B", "C", "N", "O", "P", "S", "F", "Cl", "Br", "I"] {
464 assert!(
465 Element::from_symbol(sym).unwrap().is_organic_subset(),
466 "{sym} should be in organic subset"
467 );
468 }
469 assert!(!Element::H.is_organic_subset());
470 assert!(!Element::FE.is_organic_subset());
471 }
472
473 #[test]
474 fn test_vdw_radii() {
475 assert!((Element::H.vdw_radius() - 1.20).abs() < 1e-4);
476 assert!((Element::C.vdw_radius() - 1.70).abs() < 1e-4);
477 assert!((Element::N.vdw_radius() - 1.55).abs() < 1e-4);
478 assert!((Element::O.vdw_radius() - 1.52).abs() < 1e-4);
479 assert!((Element::S.vdw_radius() - 1.80).abs() < 1e-4);
480 assert!((Element::CL.vdw_radius() - 1.75).abs() < 1e-4);
481 assert!((Element::BR.vdw_radius() - 1.85).abs() < 1e-4);
482 assert!((Element::I.vdw_radius() - 1.98).abs() < 1e-4);
483 assert!((Element::OG.vdw_radius() - 1.70).abs() < 1e-4);
485 }
486
487 #[test]
488 fn test_covalent_radii() {
489 assert!((Element::H.covalent_radius() - 0.31).abs() < 1e-4);
490 assert!((Element::C.covalent_radius() - 0.77).abs() < 1e-4);
491 assert!((Element::N.covalent_radius() - 0.71).abs() < 1e-4);
492 assert!((Element::O.covalent_radius() - 0.66).abs() < 1e-4);
493 assert!((Element::CL.covalent_radius() - 1.02).abs() < 1e-4);
494 }
495
496 #[test]
497 fn test_valences() {
498 assert_eq!(Element::C.normal_valences(), &[4]);
499 assert_eq!(Element::N.normal_valences(), &[3, 5]);
500 assert_eq!(Element::S.normal_valences(), &[2, 4, 6]);
501 }
502
503 #[test]
504 fn test_te_valence_source_verified() {
505 assert_eq!(Element::TE.normal_valences(), &[2, 4, 6]);
514 }
515
516 #[test]
517 fn test_s_se_valences_unchanged_regression_guard() {
518 assert_eq!(Element::S.normal_valences(), &[2, 4, 6]);
520 assert_eq!(Element::SE.normal_valences(), &[2, 4, 6]);
521 }
522
523 #[test]
524 fn test_te_not_in_organic_subset() {
525 assert!(!Element::TE.is_organic_subset());
529 }
530}