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