chemistru_elements/
electron.rs

1use std::fmt::Display;
2
3use const_panic::concat_panic;
4
5#[derive(Clone, Debug, PartialEq, PartialOrd)]
6pub struct ElectronData {
7    pub(crate) configuration: ElectronConfiguration,
8    pub(crate) affinity: Option<f64>,
9    pub(crate) electronegativity: Option<f64>,
10    pub(crate) ionization_energies: Option<Vec<f64>>,
11    pub(crate) shells: Vec<u8>,
12}
13
14impl ElectronData {
15    #[must_use]
16    pub const fn configuration(&self) -> &ElectronConfiguration {
17        &self.configuration
18    }
19
20    #[must_use]
21    pub const fn electron_affinity(&self) -> Option<f64> {
22        self.affinity
23    }
24
25    #[must_use]
26    pub const fn electronegativity(&self) -> Option<f64> {
27        self.electronegativity
28    }
29
30    #[must_use]
31    pub fn ionization_energies(&self) -> Option<&[f64]> {
32        self.ionization_energies.as_deref()
33    }
34
35    #[must_use]
36    pub fn shells(&self) -> &[u8] {
37        &self.shells
38    }
39
40    #[must_use]
41    pub const fn new(
42        configuration: ElectronConfiguration,
43        affinity: Option<f64>,
44        electronegativity: Option<f64>,
45        ionization_energies: Option<Vec<f64>>,
46        shells: Vec<u8>,
47    ) -> Self {
48        Self {
49            configuration,
50            affinity,
51            electronegativity,
52            ionization_energies,
53            shells,
54        }
55    }
56}
57
58#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
59pub struct ElectronConfiguration(&'static [Suborbital]);
60
61#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
62pub struct Suborbital(u8, u8, u8);
63
64impl From<&str> for ElectronConfiguration {
65    fn from(value: &str) -> Self {
66        let inner = value
67            .split(' ')
68            .map(|suborbital| {
69                let principal_quantum_number =
70                    suborbital.chars().nth(0).expect(
71                        "Suborbital in electron configuration missing principal quantum number",
72                    ) as u8;
73                let azimuthal_letter = suborbital
74                    .chars()
75                    .nth(1)
76                    .expect("Suborbital in electron configuration missing azimuthal letter");
77                let electron_number = suborbital
78                    .get(2..suborbital.len())
79                    .expect("Suborbital in electron configuration missing electron number")
80                    .parse::<u8>()
81                    .expect("Failed to parse electron number from electron configuration as u8");
82
83                let azimuthal_quantum_number = match azimuthal_letter.to_ascii_lowercase() {
84                    's' => 0,
85                    'p' => 1,
86                    'd' => 2,
87                    'f' => 3,
88                    _ => panic!("Expected s, p, d, or f; found {azimuthal_letter}"),
89                };
90
91                Suborbital(
92                    principal_quantum_number,
93                    azimuthal_quantum_number,
94                    electron_number,
95                )
96            })
97            .collect::<Vec<_>>();
98
99        Self(Box::new(inner).leak())
100    }
101}
102
103impl Suborbital {
104    #[must_use]
105    pub const fn principal_quantum_number(&self) -> u8 {
106        self.0
107    }
108
109    #[must_use]
110    pub const fn azimuthal_quantum_number(&self) -> u8 {
111        self.1
112    }
113
114    #[must_use]
115    pub const fn electron_number(&self) -> u8 {
116        self.2
117    }
118
119    #[must_use]
120    pub fn to_string_stylized(&self) -> String {
121        self.to_string()
122    }
123
124    #[must_use]
125    pub fn to_string_nonstylized(&self) -> String {
126        format!("{}{}{}", self.0, azimuthal_number_to_char(self.1), self.2)
127    }
128}
129
130impl Display for Suborbital {
131    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
132        f.write_fmt(format_args!(
133            "{}{}{}",
134            self.0,
135            self.1,
136            to_superscript(self.2)
137        ))
138    }
139}
140
141impl Display for ElectronConfiguration {
142    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
143        let mut suborbitals = self.0.iter();
144
145        if let Some(first) = suborbitals.next() {
146            f.write_fmt(format_args!("{first}"))?;
147
148            for suborbital in suborbitals {
149                f.write_fmt(format_args!(" {suborbital}"))?;
150            }
151        }
152
153        Ok(())
154    }
155}
156
157impl ElectronConfiguration {
158    #[must_use]
159    pub const fn suborbitals(&self) -> &[Suborbital] {
160        self.0
161    }
162
163    #[must_use]
164    pub const fn new(suborbitals: &'static [Suborbital]) -> Self {
165        Self(suborbitals)
166    }
167}
168
169fn to_superscript(n: u8) -> String {
170    const SUPERSCRIPT: [char; 10] = ['⁰', '¹', '²', '³', '⁴', '⁵', '⁶', '⁷', '⁸', '⁹'];
171
172    let units_digit = n & 10;
173    let tens_digit = ((n - units_digit) / 10) % 10;
174    let hundreds_digit = ((n - units_digit - tens_digit) / 100) % 10;
175
176    if hundreds_digit == 0 {
177        if tens_digit == 0 {
178            format!("{}", SUPERSCRIPT[units_digit as usize])
179        } else {
180            format!(
181                "{}{}",
182                SUPERSCRIPT[tens_digit as usize], SUPERSCRIPT[units_digit as usize]
183            )
184        }
185    } else {
186        format!(
187            "{}{}{}",
188            SUPERSCRIPT[hundreds_digit as usize],
189            SUPERSCRIPT[tens_digit as usize],
190            SUPERSCRIPT[units_digit as usize]
191        )
192    }
193}
194
195const fn azimuthal_number_to_char(n: u8) -> char {
196    match n {
197        0 => 's',
198        1 => 'p',
199        2 => 'd',
200        3 => 'f',
201        _ => concat_panic!("Expected s, p, d, or f; found ", n),
202    }
203}