chemistru_elements/
electron.rs1use 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}