use crate::element_data::{symbol_to_atomic_num, ELEMENTS, IS_EARLY_ATOM};
#[derive(Debug, Clone, Copy)]
pub struct Element {
pub atomic_num: u8,
pub symbol: &'static str,
pub period: u8,
pub rcov: f32,
pub rvdw: f32,
pub mass: f32,
pub outer_electrons: u8,
pub common_isotope: u16,
pub common_isotope_mass: f64,
pub valences: &'static [i8],
}
impl Element {
#[must_use]
pub fn has_valence_constraint(&self) -> bool {
!self.valences.is_empty() && self.valences[0] != -1
}
#[must_use]
pub fn default_valence_for(&self, used: i8) -> Option<i8> {
if !self.has_valence_constraint() {
return None;
}
self.valences.iter().copied().find(|&v| v >= used)
}
}
#[must_use]
pub fn by_atomic_num(atomic_num: u8) -> Option<&'static Element> {
ELEMENTS.get(atomic_num as usize)
}
#[must_use]
pub fn by_symbol(symbol: &str) -> Option<&'static Element> {
symbol_to_atomic_num(symbol).and_then(by_atomic_num)
}
#[must_use]
pub fn atomic_num_of(symbol: &str) -> Option<u8> {
symbol_to_atomic_num(symbol)
}
#[must_use]
pub fn count() -> usize {
ELEMENTS.len()
}
#[must_use]
pub fn is_early_atom(atomic_num: u8) -> bool {
IS_EARLY_ATOM
.get(atomic_num as usize)
.copied()
.unwrap_or(false)
}
#[must_use]
pub fn is_organic_subset(atomic_num: u8) -> bool {
matches!(
atomic_num,
5 | 6 | 7 | 8 | 15 | 16 | 9 | 17 | 35 | 53 )
}
#[must_use]
pub fn can_be_aromatic_lowercase(atomic_num: u8) -> bool {
matches!(atomic_num, 5 | 6 | 7 | 8 | 15 | 16 | 33 | 34 | 52) }
#[must_use]
pub fn has_stereogenic_lone_pair(atomic_num: u8, formal_charge: i8) -> bool {
formal_charge <= 0 && matches!(atomic_num, 15 | 16 | 33 | 34 | 52) }
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn 孤对立体中心只认那五个元素() {
for (z, chg, want) in [
(16u8, 0i8, true), (15, 0, true), (33, 0, true), (34, 0, true), (52, 0, true), (16, 1, false), (15, 1, false), (7, 0, false), (6, 0, false), (8, 0, false), ] {
assert_eq!(
has_stereogenic_lone_pair(z, chg),
want,
"元素 {z} 电荷 {chg}"
);
}
}
#[test]
fn table_is_indexed_by_atomic_number() {
for (i, e) in ELEMENTS.iter().enumerate() {
assert_eq!(e.atomic_num as usize, i, "第 {i} 项的原子序数错位");
}
}
#[test]
fn covers_through_oganesson() {
assert_eq!(count(), 119, "元素表应覆盖 0..=118");
assert_eq!(by_atomic_num(118).unwrap().symbol, "Og");
assert_eq!(by_atomic_num(0).unwrap().symbol, "*");
}
#[test]
fn symbol_roundtrip() {
for e in ELEMENTS.iter() {
assert_eq!(
atomic_num_of(e.symbol),
Some(e.atomic_num),
"符号 {} 无法反查",
e.symbol
);
}
}
#[test]
fn organic_subset_default_valences() {
assert_eq!(by_symbol("C").unwrap().valences, &[4]);
assert_eq!(by_symbol("N").unwrap().valences, &[3]);
assert_eq!(by_symbol("O").unwrap().valences, &[2]);
assert_eq!(by_symbol("F").unwrap().valences, &[1]);
assert_eq!(by_symbol("B").unwrap().valences, &[3]);
assert_eq!(by_symbol("H").unwrap().valences, &[1]);
}
#[test]
fn default_valence_selection() {
let s = by_symbol("S").unwrap();
assert!(
s.valences.len() > 1,
"S 应有多个默认价,实际 {:?}",
s.valences
);
assert_eq!(s.default_valence_for(2), Some(2));
assert_eq!(s.default_valence_for(3), Some(s.valences[1]));
let c = by_symbol("C").unwrap();
assert_eq!(c.default_valence_for(4), Some(4));
assert_eq!(c.default_valence_for(5), None);
}
#[test]
fn unknown_symbol_is_none() {
assert!(by_symbol("Xx").is_none());
assert!(
by_symbol("c").is_none(),
"小写芳香符号应由调用方归一化后再查表"
);
}
#[test]
fn organic_subset_membership() {
for sym in ["B", "C", "N", "O", "P", "S", "F", "Cl", "Br", "I"] {
let a = atomic_num_of(sym).unwrap();
assert!(is_organic_subset(a), "{sym} 应属于有机子集");
}
for sym in ["Si", "Se", "Na", "Fe"] {
let a = atomic_num_of(sym).unwrap();
assert!(!is_organic_subset(a), "{sym} 不应属于有机子集");
}
}
}