use crate::system::{Ratio, da, fraction};
use bincode::{Decode, Encode};
use std::{num::NonZeroU16, sync::LazyLock};
use serde::{Deserialize, Serialize};
use crate::system::f64::Mass;
#[derive(
Copy,
Clone,
Eq,
PartialEq,
Ord,
PartialOrd,
Hash,
Debug,
Default,
Serialize,
Deserialize,
Decode,
Encode,
)]
pub enum Element {
Electron = 0,
#[default]
H = 1,
He,
Li,
Be,
B,
C,
N,
O,
F,
Ne,
Na,
Mg,
Al,
Si,
P,
S,
Cl,
Ar,
K,
Ca,
Sc,
Ti,
V,
Cr,
Mn,
Fe,
Co,
Ni,
Cu,
Zn,
Ga,
Ge,
As,
Se,
Br,
Kr,
Rb,
Sr,
Y,
Zr,
Nb,
Mo,
Tc,
Ru,
Rh,
Pd,
Ag,
Cd,
In,
Sn,
Sb,
Te,
I,
Xe,
Cs,
Ba,
La,
Ce,
Pr,
Nd,
Pm,
Sm,
Eu,
Gd,
Tb,
Dy,
Ho,
Er,
Tm,
Yb,
Lu,
Hf,
Ta,
W,
Re,
Os,
Ir,
Pt,
Au,
Hg,
Tl,
Pb,
Bi,
Po,
At,
Rn,
Fr,
Ra,
Ac,
Th,
Pa,
U,
Np,
Pu,
Am,
Cm,
Bk,
Cf,
Es,
Fm,
Md,
No,
Lr,
Rf,
Db,
Sg,
Bh,
Hs,
Mt,
Ds,
Rg,
Cn,
Nh,
Fl,
Mc,
Lv,
Ts,
Og,
}
impl TryFrom<&str> for Element {
type Error = ();
#[expect(clippy::too_many_lines)]
fn try_from(value: &str) -> Result<Self, Self::Error> {
match value.to_ascii_lowercase().as_str() {
"h" => Ok(Self::H),
"he" => Ok(Self::He),
"li" => Ok(Self::Li),
"be" => Ok(Self::Be),
"b" => Ok(Self::B),
"c" => Ok(Self::C),
"n" => Ok(Self::N),
"o" => Ok(Self::O),
"f" => Ok(Self::F),
"ne" => Ok(Self::Ne),
"na" => Ok(Self::Na),
"mg" => Ok(Self::Mg),
"al" => Ok(Self::Al),
"si" => Ok(Self::Si),
"p" => Ok(Self::P),
"s" => Ok(Self::S),
"cl" => Ok(Self::Cl),
"ar" => Ok(Self::Ar),
"k" => Ok(Self::K),
"ca" => Ok(Self::Ca),
"sc" => Ok(Self::Sc),
"ti" => Ok(Self::Ti),
"v" => Ok(Self::V),
"cr" => Ok(Self::Cr),
"mn" => Ok(Self::Mn),
"fe" => Ok(Self::Fe),
"co" => Ok(Self::Co),
"ni" => Ok(Self::Ni),
"cu" => Ok(Self::Cu),
"zn" => Ok(Self::Zn),
"ga" => Ok(Self::Ga),
"ge" => Ok(Self::Ge),
"as" => Ok(Self::As),
"se" => Ok(Self::Se),
"br" => Ok(Self::Br),
"kr" => Ok(Self::Kr),
"rb" => Ok(Self::Rb),
"sr" => Ok(Self::Sr),
"y" => Ok(Self::Y),
"zr" => Ok(Self::Zr),
"nb" => Ok(Self::Nb),
"mo" => Ok(Self::Mo),
"tc" => Ok(Self::Tc),
"ru" => Ok(Self::Ru),
"rh" => Ok(Self::Rh),
"pd" => Ok(Self::Pd),
"ag" => Ok(Self::Ag),
"cd" => Ok(Self::Cd),
"in" => Ok(Self::In),
"sn" => Ok(Self::Sn),
"sb" => Ok(Self::Sb),
"te" => Ok(Self::Te),
"i" => Ok(Self::I),
"xe" => Ok(Self::Xe),
"cs" => Ok(Self::Cs),
"ba" => Ok(Self::Ba),
"la" => Ok(Self::La),
"ce" => Ok(Self::Ce),
"pr" => Ok(Self::Pr),
"nd" => Ok(Self::Nd),
"pm" => Ok(Self::Pm),
"sm" => Ok(Self::Sm),
"eu" => Ok(Self::Eu),
"gd" => Ok(Self::Gd),
"tb" => Ok(Self::Tb),
"dy" => Ok(Self::Dy),
"ho" => Ok(Self::Ho),
"er" => Ok(Self::Er),
"tm" => Ok(Self::Tm),
"yb" => Ok(Self::Yb),
"lu" => Ok(Self::Lu),
"hf" => Ok(Self::Hf),
"ta" => Ok(Self::Ta),
"w" => Ok(Self::W),
"re" => Ok(Self::Re),
"os" => Ok(Self::Os),
"ir" => Ok(Self::Ir),
"pt" => Ok(Self::Pt),
"au" => Ok(Self::Au),
"hg" => Ok(Self::Hg),
"tl" => Ok(Self::Tl),
"pb" => Ok(Self::Pb),
"bi" => Ok(Self::Bi),
"po" => Ok(Self::Po),
"at" => Ok(Self::At),
"rn" => Ok(Self::Rn),
"fr" => Ok(Self::Fr),
"ra" => Ok(Self::Ra),
"ac" => Ok(Self::Ac),
"th" => Ok(Self::Th),
"pa" => Ok(Self::Pa),
"u" => Ok(Self::U),
"np" => Ok(Self::Np),
"pu" => Ok(Self::Pu),
"am" => Ok(Self::Am),
"cm" => Ok(Self::Cm),
"bk" => Ok(Self::Bk),
"cf" => Ok(Self::Cf),
"es" => Ok(Self::Es),
"fm" => Ok(Self::Fm),
"md" => Ok(Self::Md),
"no" => Ok(Self::No),
"lr" => Ok(Self::Lr),
"rf" => Ok(Self::Rf),
"db" => Ok(Self::Db),
"sg" => Ok(Self::Sg),
"bh" => Ok(Self::Bh),
"hs" => Ok(Self::Hs),
"mt" => Ok(Self::Mt),
"ds" => Ok(Self::Ds),
"rg" => Ok(Self::Rg),
"cn" => Ok(Self::Cn),
"nh" => Ok(Self::Nh),
"fl" => Ok(Self::Fl),
"mc" => Ok(Self::Mc),
"lv" => Ok(Self::Lv),
"ts" => Ok(Self::Ts),
"og" => Ok(Self::Og),
_ => Err(()),
}
}
}
impl TryFrom<usize> for Element {
type Error = ();
#[expect(clippy::too_many_lines)]
fn try_from(value: usize) -> Result<Self, Self::Error> {
match value {
0 => Ok(Self::Electron),
1 => Ok(Self::H),
2 => Ok(Self::He),
3 => Ok(Self::Li),
4 => Ok(Self::Be),
5 => Ok(Self::B),
6 => Ok(Self::C),
7 => Ok(Self::N),
8 => Ok(Self::O),
9 => Ok(Self::F),
10 => Ok(Self::Ne),
11 => Ok(Self::Na),
12 => Ok(Self::Mg),
13 => Ok(Self::Al),
14 => Ok(Self::Si),
15 => Ok(Self::P),
16 => Ok(Self::S),
17 => Ok(Self::Cl),
18 => Ok(Self::Ar),
19 => Ok(Self::K),
20 => Ok(Self::Ca),
21 => Ok(Self::Sc),
22 => Ok(Self::Ti),
23 => Ok(Self::V),
24 => Ok(Self::Cr),
25 => Ok(Self::Mn),
26 => Ok(Self::Fe),
27 => Ok(Self::Co),
28 => Ok(Self::Ni),
29 => Ok(Self::Cu),
30 => Ok(Self::Zn),
31 => Ok(Self::Ga),
32 => Ok(Self::Ge),
33 => Ok(Self::As),
34 => Ok(Self::Se),
35 => Ok(Self::Br),
36 => Ok(Self::Kr),
37 => Ok(Self::Rb),
38 => Ok(Self::Sr),
39 => Ok(Self::Y),
40 => Ok(Self::Zr),
41 => Ok(Self::Nb),
42 => Ok(Self::Mo),
43 => Ok(Self::Tc),
44 => Ok(Self::Ru),
45 => Ok(Self::Rh),
46 => Ok(Self::Pd),
47 => Ok(Self::Ag),
48 => Ok(Self::Cd),
49 => Ok(Self::In),
50 => Ok(Self::Sn),
51 => Ok(Self::Sb),
52 => Ok(Self::Te),
53 => Ok(Self::I),
54 => Ok(Self::Xe),
55 => Ok(Self::Cs),
56 => Ok(Self::Ba),
57 => Ok(Self::La),
58 => Ok(Self::Ce),
59 => Ok(Self::Pr),
60 => Ok(Self::Nd),
61 => Ok(Self::Pm),
62 => Ok(Self::Sm),
63 => Ok(Self::Eu),
64 => Ok(Self::Gd),
65 => Ok(Self::Tb),
66 => Ok(Self::Dy),
67 => Ok(Self::Ho),
68 => Ok(Self::Er),
69 => Ok(Self::Tm),
70 => Ok(Self::Yb),
71 => Ok(Self::Lu),
72 => Ok(Self::Hf),
73 => Ok(Self::Ta),
74 => Ok(Self::W),
75 => Ok(Self::Re),
76 => Ok(Self::Os),
77 => Ok(Self::Ir),
78 => Ok(Self::Pt),
79 => Ok(Self::Au),
80 => Ok(Self::Hg),
81 => Ok(Self::Tl),
82 => Ok(Self::Pb),
83 => Ok(Self::Bi),
84 => Ok(Self::Po),
85 => Ok(Self::At),
86 => Ok(Self::Rn),
87 => Ok(Self::Fr),
88 => Ok(Self::Ra),
89 => Ok(Self::Ac),
90 => Ok(Self::Th),
91 => Ok(Self::Pa),
92 => Ok(Self::U),
93 => Ok(Self::Np),
94 => Ok(Self::Pu),
95 => Ok(Self::Am),
96 => Ok(Self::Cm),
97 => Ok(Self::Bk),
98 => Ok(Self::Cf),
99 => Ok(Self::Es),
100 => Ok(Self::Fm),
101 => Ok(Self::Md),
102 => Ok(Self::No),
103 => Ok(Self::Lr),
104 => Ok(Self::Rf),
105 => Ok(Self::Db),
106 => Ok(Self::Sg),
107 => Ok(Self::Bh),
108 => Ok(Self::Hs),
109 => Ok(Self::Mt),
110 => Ok(Self::Ds),
111 => Ok(Self::Rg),
112 => Ok(Self::Cn),
113 => Ok(Self::Nh),
114 => Ok(Self::Fl),
115 => Ok(Self::Mc),
116 => Ok(Self::Lv),
117 => Ok(Self::Ts),
118 => Ok(Self::Og),
_ => Err(()),
}
}
}
impl Element {
pub const fn symbol(self) -> &'static str {
match self {
Self::H => "H",
Self::He => "He",
Self::Li => "Li",
Self::Be => "Be",
Self::B => "B",
Self::C => "C",
Self::N => "N",
Self::O => "O",
Self::F => "F",
Self::Ne => "Ne",
Self::Na => "Na",
Self::Mg => "Mg",
Self::Al => "Al",
Self::Si => "Si",
Self::P => "P",
Self::S => "S",
Self::Cl => "Cl",
Self::Ar => "Ar",
Self::K => "K",
Self::Ca => "Ca",
Self::Sc => "Sc",
Self::Ti => "Ti",
Self::V => "V",
Self::Cr => "Cr",
Self::Mn => "Mn",
Self::Fe => "Fe",
Self::Co => "Co",
Self::Ni => "Ni",
Self::Cu => "Cu",
Self::Zn => "Zn",
Self::Ga => "Ga",
Self::Ge => "Ge",
Self::As => "As",
Self::Se => "Se",
Self::Br => "Br",
Self::Kr => "Kr",
Self::Rb => "Rb",
Self::Sr => "Sr",
Self::Y => "Y",
Self::Zr => "Zr",
Self::Nb => "Nb",
Self::Mo => "Mo",
Self::Tc => "Tc",
Self::Ru => "Ru",
Self::Rh => "Rh",
Self::Pd => "Pd",
Self::Ag => "Ag",
Self::Cd => "Cd",
Self::In => "In",
Self::Sn => "Sn",
Self::Sb => "Sb",
Self::Te => "Te",
Self::I => "I",
Self::Xe => "Xe",
Self::Cs => "Cs",
Self::Ba => "Ba",
Self::La => "La",
Self::Ce => "Ce",
Self::Pr => "Pr",
Self::Nd => "Nd",
Self::Pm => "Pm",
Self::Sm => "Sm",
Self::Eu => "Eu",
Self::Gd => "Gd",
Self::Tb => "Tb",
Self::Dy => "Dy",
Self::Ho => "Ho",
Self::Er => "Er",
Self::Tm => "Tm",
Self::Yb => "Yb",
Self::Lu => "Lu",
Self::Hf => "Hf",
Self::Ta => "Ta",
Self::W => "W",
Self::Re => "Re",
Self::Os => "Os",
Self::Ir => "Ir",
Self::Pt => "Pt",
Self::Au => "Au",
Self::Hg => "Hg",
Self::Tl => "Tl",
Self::Pb => "Pb",
Self::Bi => "Bi",
Self::Po => "Po",
Self::At => "At",
Self::Rn => "Rn",
Self::Fr => "Fr",
Self::Ra => "Ra",
Self::Ac => "Ac",
Self::Th => "Th",
Self::Pa => "Pa",
Self::U => "U",
Self::Np => "Np",
Self::Pu => "Pu",
Self::Am => "Am",
Self::Cm => "Cm",
Self::Bk => "Bk",
Self::Cf => "Cf",
Self::Es => "Es",
Self::Fm => "Fm",
Self::Md => "Md",
Self::No => "No",
Self::Lr => "Lr",
Self::Rf => "Rf",
Self::Db => "Db",
Self::Sg => "Sg",
Self::Bh => "Bh",
Self::Hs => "Hs",
Self::Mt => "Mt",
Self::Ds => "Ds",
Self::Rg => "Rg",
Self::Cn => "Cn",
Self::Nh => "Nh",
Self::Fl => "Fl",
Self::Mc => "Mc",
Self::Lv => "Lv",
Self::Ts => "Ts",
Self::Og => "Og",
Self::Electron => "e",
}
}
}
impl std::fmt::Display for Element {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.symbol())
}
}
pub const ELEMENT_PARSE_LIST: &[(&str, Element)] = &[
("He", Element::He),
("Li", Element::Li),
("Be", Element::Be),
("Ne", Element::Ne),
("Na", Element::Na),
("Mg", Element::Mg),
("Al", Element::Al),
("Si", Element::Si),
("Cl", Element::Cl),
("Ar", Element::Ar),
("Ca", Element::Ca),
("Sc", Element::Sc),
("Ti", Element::Ti),
("Cr", Element::Cr),
("Mn", Element::Mn),
("Fe", Element::Fe),
("Co", Element::Co),
("Ni", Element::Ni),
("Cu", Element::Cu),
("Zn", Element::Zn),
("Ga", Element::Ga),
("Ge", Element::Ge),
("As", Element::As),
("Se", Element::Se),
("Br", Element::Br),
("Kr", Element::Kr),
("Rb", Element::Rb),
("Sr", Element::Sr),
("Zr", Element::Zr),
("Nb", Element::Nb),
("Mo", Element::Mo),
("Tc", Element::Tc),
("Ru", Element::Ru),
("Rh", Element::Rh),
("Pd", Element::Pd),
("Ag", Element::Ag),
("Cd", Element::Cd),
("In", Element::In),
("Sn", Element::Sn),
("Sb", Element::Sb),
("Te", Element::Te),
("Xe", Element::Xe),
("Cs", Element::Cs),
("Ba", Element::Ba),
("La", Element::La),
("Ce", Element::Ce),
("Pr", Element::Pr),
("Nd", Element::Nd),
("Pm", Element::Pm),
("Sm", Element::Sm),
("Eu", Element::Eu),
("Gd", Element::Gd),
("Tb", Element::Tb),
("Dy", Element::Dy),
("Ho", Element::Ho),
("Er", Element::Er),
("Tm", Element::Tm),
("Yb", Element::Yb),
("Lu", Element::Lu),
("Hf", Element::Hf),
("Ta", Element::Ta),
("Re", Element::Re),
("Os", Element::Os),
("Ir", Element::Ir),
("Pt", Element::Pt),
("Au", Element::Au),
("Hg", Element::Hg),
("Tl", Element::Tl),
("Pb", Element::Pb),
("Bi", Element::Bi),
("Po", Element::Po),
("At", Element::At),
("Rn", Element::Rn),
("Fr", Element::Fr),
("Ra", Element::Ra),
("Ac", Element::Ac),
("Th", Element::Th),
("Pa", Element::Pa),
("Np", Element::Np),
("Pu", Element::Pu),
("Am", Element::Am),
("Cm", Element::Cm),
("Bk", Element::Bk),
("Cf", Element::Cf),
("Es", Element::Es),
("Fm", Element::Fm),
("Md", Element::Md),
("No", Element::No),
("Lr", Element::Lr),
("Rf", Element::Rf),
("Db", Element::Db),
("Sg", Element::Sg),
("Bh", Element::Bh),
("Hs", Element::Hs),
("Mt", Element::Mt),
("Ds", Element::Ds),
("Rg", Element::Rg),
("Cn", Element::Cn),
("Nh", Element::Nh),
("Fl", Element::Fl),
("Mc", Element::Mc),
("Lv", Element::Lv),
("Ts", Element::Ts),
("Og", Element::Og),
("U", Element::U),
("W", Element::W),
("I", Element::I),
("Y", Element::Y),
("V", Element::V),
("K", Element::K),
("S", Element::S),
("B", Element::B),
("C", Element::C),
("N", Element::N),
("O", Element::O),
("F", Element::F),
("H", Element::H),
("P", Element::P),
];
pub const COMMON_ELEMENT_PARSE_LIST: &[(&str, Element)] = &[
("He", Element::He),
("Li", Element::Li),
("Na", Element::Na),
("Mg", Element::Mg),
("Al", Element::Al),
("Cl", Element::Cl),
("Ca", Element::Ca),
("Fe", Element::Fe),
("Cu", Element::Cu),
("Zn", Element::Zn),
("Se", Element::Se),
("Ag", Element::Ag),
("Au", Element::Au),
("I", Element::I),
("K", Element::K),
("S", Element::S),
("B", Element::B),
("C", Element::C),
("N", Element::N),
("O", Element::O),
("F", Element::F),
("H", Element::H),
("P", Element::P),
];
#[doc(hidden)]
pub type ElementalData = Vec<(Option<Mass>, Option<Mass>, Vec<(u16, Mass, f64)>)>;
impl Element {
#[allow(unused_variables)]
pub fn is_valid(self, isotope: Option<NonZeroU16>) -> bool {
#[cfg(not(feature = "internal-no-data"))]
{
if self == Self::Electron {
isotope.is_none()
} else {
isotope.map_or_else(
|| ELEMENTAL_DATA[self as usize - 1].0.is_some(),
|isotope| {
ELEMENTAL_DATA[self as usize - 1]
.2
.iter()
.any(|(ii, _, _)| *ii == isotope.get())
},
)
}
}
#[cfg(feature = "internal-no-data")]
{
true
}
}
pub fn isotopes(self) -> &'static [(u16, Mass, f64)] {
&ELEMENTAL_DATA[self as usize - 1].2
}
pub fn mass(self, isotope: Option<NonZeroU16>) -> Option<Mass> {
if self == Self::Electron {
return Some(da(5.485_799_090_65e-4));
}
isotope.map_or_else(
|| ELEMENTAL_DATA[self as usize - 1].0,
|isotope| {
ELEMENTAL_DATA[self as usize - 1]
.2
.iter()
.find(|(ii, _, _)| *ii == isotope.get())
.map(|(_, m, _)| *m)
},
)
}
pub fn average_weight(self, isotope: Option<NonZeroU16>) -> Option<Mass> {
if self == Self::Electron {
return Some(da(5.485_799_090_65e-4));
}
isotope.map_or_else(
|| ELEMENTAL_DATA[self as usize - 1].1,
|isotope| {
ELEMENTAL_DATA[self as usize - 1]
.2
.iter()
.find(|(ii, _, _)| *ii == isotope.get())
.map(|(_, m, _)| *m)
},
)
}
pub fn most_abundant_mass(self, isotope: Option<NonZeroU16>, n: i32) -> Option<Mass> {
if self == Self::Electron {
return Some(da(5.485_799_090_65e-4) * Ratio::new::<fraction>(f64::from(n)));
}
Some(
if let Some(isotope) = isotope {
ELEMENTAL_DATA[self as usize - 1]
.2
.iter()
.find(|(ii, _, _)| *ii == isotope.get())
.map(|(_, m, _)| *m)?
} else {
let mut max = None;
for iso in &ELEMENTAL_DATA[self as usize - 1].2 {
let chance = iso.2 * f64::from(n);
if max.is_none_or(|m: (Mass, f64)| chance > m.1) {
max = Some((iso.1, chance));
}
}
max?.0
} * Ratio::new::<fraction>(f64::from(n)),
)
}
}
pub static ELEMENTAL_DATA: LazyLock<ElementalData> = LazyLock::new(|| {
#[cfg(not(feature = "internal-no-data"))]
{
bincode::serde::decode_from_slice::<ElementalData, bincode::config::Configuration>(
include_bytes!("../databases/elements.dat"),
bincode::config::Configuration::default(),
)
.unwrap()
.0
}
#[cfg(feature = "internal-no-data")]
{
Vec::new()
}
});
#[cfg(test)]
#[expect(clippy::missing_panics_doc)]
mod test {
#[test]
fn hill_notation() {
assert_eq!(
crate::molecular_formula!(C 6 O 5 H 10).hill_notation(),
"C6H10O5".to_string()
);
}
#[test]
fn correct_feature() {
assert!(!cfg!(feature = "internal-no-data"));
}
}