Skip to main content

readcon_core/
helpers.rs

1//! Element symbol and atomic number lookup.
2//!
3//! Both lookups cover hydrogen through uranium (Z = 1..=92) plus the
4//! hydrogen isotopes deuterium ("D") and tritium ("T") which both
5//! map to Z = 1 since they share hydrogen's nucleon charge. The
6//! reverse lookup ([`atomic_number_to_symbol`]) returns the standard
7//! "H" for Z = 1; callers that need to distinguish isotopes do so out
8//! of band (e.g. an `isotope` metadata key on the frame, or by
9//! storing "D" / "T" as the per-atom symbol).
10//!
11//! The mapping is *informational, not binding*. The CON spec does not
12//! mandate that an atom's symbol correspond to any periodic-table
13//! element; consumers commonly store ghost atoms (e.g. for QM/MM
14//! link atoms, virtual sites, dummy positions in NEB chains) with
15//! whatever symbol fits their workflow. Unknown inputs return a
16//! stable sentinel: [`symbol_to_atomic_number`] returns 0 for
17//! unknown symbols and [`atomic_number_to_symbol`] returns "X" for
18//! unknown atomic numbers.
19//!
20//! The same lookup is exposed to C/C++ via [`crate::ffi::rkr_symbol_to_z`]
21//! and [`crate::ffi::rkr_z_to_symbol`] so downstream tools can drop
22//! their own copies of the periodic table.
23
24/// Returns the atomic number for a chemical symbol, or 0 if unknown.
25pub fn symbol_to_atomic_number(symbol: &str) -> u64 {
26    match symbol {
27        "H" | "D" | "T" => 1,
28        "He" => 2,
29        "Li" => 3,
30        "Be" => 4,
31        "B" => 5,
32        "C" => 6,
33        "N" => 7,
34        "O" => 8,
35        "F" => 9,
36        "Ne" => 10,
37        "Na" => 11,
38        "Mg" => 12,
39        "Al" => 13,
40        "Si" => 14,
41        "P" => 15,
42        "S" => 16,
43        "Cl" => 17,
44        "Ar" => 18,
45        "K" => 19,
46        "Ca" => 20,
47        "Sc" => 21,
48        "Ti" => 22,
49        "V" => 23,
50        "Cr" => 24,
51        "Mn" => 25,
52        "Fe" => 26,
53        "Co" => 27,
54        "Ni" => 28,
55        "Cu" => 29,
56        "Zn" => 30,
57        "Ga" => 31,
58        "Ge" => 32,
59        "As" => 33,
60        "Se" => 34,
61        "Br" => 35,
62        "Kr" => 36,
63        "Rb" => 37,
64        "Sr" => 38,
65        "Y" => 39,
66        "Zr" => 40,
67        "Nb" => 41,
68        "Mo" => 42,
69        "Tc" => 43,
70        "Ru" => 44,
71        "Rh" => 45,
72        "Pd" => 46,
73        "Ag" => 47,
74        "Cd" => 48,
75        "In" => 49,
76        "Sn" => 50,
77        "Sb" => 51,
78        "Te" => 52,
79        "I" => 53,
80        "Xe" => 54,
81        "Cs" => 55,
82        "Ba" => 56,
83        "La" => 57,
84        "Ce" => 58,
85        "Pr" => 59,
86        "Nd" => 60,
87        "Pm" => 61,
88        "Sm" => 62,
89        "Eu" => 63,
90        "Gd" => 64,
91        "Tb" => 65,
92        "Dy" => 66,
93        "Ho" => 67,
94        "Er" => 68,
95        "Tm" => 69,
96        "Yb" => 70,
97        "Lu" => 71,
98        "Hf" => 72,
99        "Ta" => 73,
100        "W" => 74,
101        "Re" => 75,
102        "Os" => 76,
103        "Ir" => 77,
104        "Pt" => 78,
105        "Au" => 79,
106        "Hg" => 80,
107        "Tl" => 81,
108        "Pb" => 82,
109        "Bi" => 83,
110        "Po" => 84,
111        "At" => 85,
112        "Rn" => 86,
113        "Fr" => 87,
114        "Ra" => 88,
115        "Ac" => 89,
116        "Th" => 90,
117        "Pa" => 91,
118        "U" => 92,
119        _ => 0, // Unknown
120    }
121}
122
123/// Returns the chemical symbol for an atomic number, or "X" if unknown.
124pub fn atomic_number_to_symbol(atomic_number: u64) -> &'static str {
125    match atomic_number {
126        1 => "H",
127        2 => "He",
128        3 => "Li",
129        4 => "Be",
130        5 => "B",
131        6 => "C",
132        7 => "N",
133        8 => "O",
134        9 => "F",
135        10 => "Ne",
136        11 => "Na",
137        12 => "Mg",
138        13 => "Al",
139        14 => "Si",
140        15 => "P",
141        16 => "S",
142        17 => "Cl",
143        18 => "Ar",
144        19 => "K",
145        20 => "Ca",
146        21 => "Sc",
147        22 => "Ti",
148        23 => "V",
149        24 => "Cr",
150        25 => "Mn",
151        26 => "Fe",
152        27 => "Co",
153        28 => "Ni",
154        29 => "Cu",
155        30 => "Zn",
156        31 => "Ga",
157        32 => "Ge",
158        33 => "As",
159        34 => "Se",
160        35 => "Br",
161        36 => "Kr",
162        37 => "Rb",
163        38 => "Sr",
164        39 => "Y",
165        40 => "Zr",
166        41 => "Nb",
167        42 => "Mo",
168        43 => "Tc",
169        44 => "Ru",
170        45 => "Rh",
171        46 => "Pd",
172        47 => "Ag",
173        48 => "Cd",
174        49 => "In",
175        50 => "Sn",
176        51 => "Sb",
177        52 => "Te",
178        53 => "I",
179        54 => "Xe",
180        55 => "Cs",
181        56 => "Ba",
182        57 => "La",
183        58 => "Ce",
184        59 => "Pr",
185        60 => "Nd",
186        61 => "Pm",
187        62 => "Sm",
188        63 => "Eu",
189        64 => "Gd",
190        65 => "Tb",
191        66 => "Dy",
192        67 => "Ho",
193        68 => "Er",
194        69 => "Tm",
195        70 => "Yb",
196        71 => "Lu",
197        72 => "Hf",
198        73 => "Ta",
199        74 => "W",
200        75 => "Re",
201        76 => "Os",
202        77 => "Ir",
203        78 => "Pt",
204        79 => "Au",
205        80 => "Hg",
206        81 => "Tl",
207        82 => "Pb",
208        83 => "Bi",
209        84 => "Po",
210        85 => "At",
211        86 => "Rn",
212        87 => "Fr",
213        88 => "Ra",
214        89 => "Ac",
215        90 => "Th",
216        91 => "Pa",
217        92 => "U",
218        _ => "X", // Represents an unknown element
219    }
220}
221
222#[cfg(test)]
223mod tests {
224    use super::*;
225
226    #[test]
227    fn known_round_trip() {
228        for z in 1u64..=92 {
229            let symbol = atomic_number_to_symbol(z);
230            assert_eq!(
231                symbol_to_atomic_number(symbol),
232                z,
233                "round-trip failed for Z={z} (symbol={symbol})"
234            );
235        }
236    }
237
238    #[test]
239    fn unknown_symbol_returns_zero() {
240        assert_eq!(symbol_to_atomic_number(""), 0);
241        assert_eq!(symbol_to_atomic_number("Xx"), 0);
242        assert_eq!(symbol_to_atomic_number("h"), 0); // case-sensitive
243    }
244
245    #[test]
246    fn hydrogen_isotopes_map_to_z_one() {
247        // Deuterium and tritium share hydrogen's nucleon charge; the
248        // reverse lookup uses canonical "H" so callers that need to
249        // round-trip isotope identity store it out of band.
250        assert_eq!(symbol_to_atomic_number("D"), 1);
251        assert_eq!(symbol_to_atomic_number("T"), 1);
252        // Ghost-atom / dummy-site placeholders (anything outside
253        // H..U plus D/T) stay sentinel-0 so consumers can detect
254        // and route them differently.
255        assert_eq!(symbol_to_atomic_number("Gh"), 0);
256        assert_eq!(symbol_to_atomic_number("Dum"), 0);
257        assert_eq!(symbol_to_atomic_number("M"), 0);
258    }
259
260    #[test]
261    fn unknown_z_returns_x() {
262        assert_eq!(atomic_number_to_symbol(0), "X");
263        assert_eq!(atomic_number_to_symbol(93), "X");
264        assert_eq!(atomic_number_to_symbol(u64::MAX), "X");
265    }
266}