use crate::core::Element;
#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct Zai {
atomic_number: u32,
mass_number: u32,
isomeric_state_number: u32,
}
impl Zai {
pub fn new(atomic_number: u32, mass_number: u32, isomeric_state_number: u32) -> Self {
assert!(atomic_number > 0);
assert!(atomic_number <= Element::MAX_ATOMIC_NUMBER);
assert!(mass_number >= atomic_number);
assert!(mass_number < 1000);
assert!(isomeric_state_number < 10);
Self {
atomic_number,
mass_number,
isomeric_state_number,
}
}
pub fn from_name(name: &str) -> Option<Self> {
if !name.is_ascii() {
return None;
}
let mut ptr = 0;
let mut bytes = name.bytes().peekable();
match bytes.next() {
Some(byte) if (b'A'..=b'Z').contains(&byte) => {
ptr += 1;
}
_ => return None,
}
match bytes.peek() {
Some(byte) if (b'a'..=b'z').contains(byte) => {
ptr += 1;
bytes.next();
}
_ => (),
}
let element = match Element::from_symbol(&name[..ptr]) {
Some(element) => element,
None => return None,
};
let atomic_number = element.atomic_number();
if atomic_number == 0 || atomic_number > Element::MAX_ATOMIC_NUMBER {
return None;
}
let start = ptr;
match bytes.next() {
Some(byte) if (b'1'..=b'9').contains(&byte) => {
ptr += 1;
}
_ => return None,
}
for _ in 0..2 {
match bytes.peek() {
Some(byte) if (b'0'..=b'9').contains(byte) => {
ptr += 1;
bytes.next();
}
_ => break,
}
}
let mass_number = match name[start..ptr].parse() {
Ok(mass_number) => mass_number,
Err(_) => return None,
};
if mass_number < atomic_number {
return None;
}
let isomeric_state_number = match bytes.next() {
None => 0,
Some(b'm') => match bytes.next() {
Some(byte) if (b'1'..=b'9').contains(&byte) => (byte - b'0') as u32,
_ => return None,
},
_ => return None,
};
Some(Self {
atomic_number,
mass_number,
isomeric_state_number,
})
}
pub fn from_id(id: u32) -> Option<Self> {
let atomic_number = id / 10000;
if atomic_number == 0 || atomic_number > Element::MAX_ATOMIC_NUMBER {
return None;
}
let mass_number = id % 10000 / 10;
if mass_number >= 1000 || mass_number < atomic_number {
return None;
}
let isomeric_state_number = id % 10;
Some(Self {
atomic_number,
mass_number,
isomeric_state_number,
})
}
pub fn atomic_number(&self) -> u32 {
self.atomic_number
}
pub fn mass_number(&self) -> u32 {
self.mass_number
}
pub fn isomeric_state_number(&self) -> u32 {
self.isomeric_state_number
}
pub fn id(&self) -> u32 {
self.atomic_number * 10000 + self.mass_number * 10 + self.isomeric_state_number
}
pub fn protons(&self) -> u32 {
self.atomic_number()
}
pub fn neutrons(&self) -> u32 {
assert!(self.mass_number >= self.atomic_number);
self.mass_number() - self.atomic_number()
}
pub fn nucleons(&self) -> u32 {
self.mass_number()
}
pub fn element(&self) -> Element {
assert!(self.atomic_number > 0);
assert!(self.atomic_number <= Element::MAX_ATOMIC_NUMBER);
Element::from_atomic_number(self.atomic_number).unwrap()
}
pub fn as_tuple(&self) -> (u32, u32, u32) {
(
self.atomic_number,
self.mass_number,
self.isomeric_state_number,
)
}
pub fn into_tuple(self) -> (u32, u32, u32) {
(
self.atomic_number,
self.mass_number,
self.isomeric_state_number,
)
}
pub fn is_ground_state(&self) -> bool {
self.isomeric_state_number == 0
}
pub fn is_metastable_state(&self) -> bool {
self.isomeric_state_number != 0
}
pub fn name(&self) -> String {
let element = self.element();
let symbol = element.symbol();
let mass = self.mass_number;
if self.is_ground_state() {
format!("{}{}", symbol, mass)
} else {
let isomer = self.isomeric_state_number;
format!("{}{}m{}", symbol, mass, isomer)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
#[should_panic]
fn new_invalid_atomic_number_min() {
Zai::new(0, 1, 0);
}
#[test]
#[should_panic]
fn new_invalid_atomic_number_max() {
Zai::new(119, 119, 0);
}
#[test]
#[should_panic]
fn new_invalid_mass_number() {
Zai::new(1, 0, 0);
}
#[test]
#[should_panic]
fn new_inconsistent_atomic_mass_numbers() {
Zai::new(2, 1, 0);
}
#[test]
fn from_name_invalid() {
assert!(Zai::from_name("X1").is_none());
assert!(Zai::from_name("Xx1").is_none());
assert!(Zai::from_name("Abc123").is_none());
assert!(Zai::from_name("H0").is_none());
assert!(Zai::from_name("He0").is_none());
assert!(Zai::from_name("He04").is_none());
assert!(Zai::from_name("He004").is_none());
assert!(Zai::from_name("He1234").is_none());
assert!(Zai::from_name("He1").is_none());
assert!(Zai::from_name("H1g").is_none());
assert!(Zai::from_name("H1n1").is_none());
assert!(Zai::from_name("H1mx").is_none());
assert!(Zai::from_name("H1m0").is_none());
}
#[test]
fn from_id_invalid() {
assert!(Zai::from_id(1234).is_none()); assert!(Zai::from_id(12341231).is_none()); assert!(Zai::from_id(11941231).is_none());
assert!(Zai::from_id(10000).is_none()); assert!(Zai::from_id(12312341).is_none()); assert!(Zai::from_id(12310001).is_none()); }
#[test]
fn name() {
assert_eq!(Zai::new(1, 1, 0).name(), "H1");
assert_eq!(Zai::new(1, 2, 0).name(), "H2");
assert_eq!(Zai::new(1, 3, 0).name(), "H3");
assert_eq!(Zai::new(27, 58, 1).name(), "Co58m1");
assert_eq!(Zai::new(72, 178, 2).name(), "Hf178m2");
}
}