use indexmap::IndexMap;
use serde::de::{MapAccess, Visitor};
use serde::{de, Deserialize, Deserializer};
use std::fmt;
use std::fs::File;
use std::io::Read;
use std::path::Path;
use crate::errors::ElementError;
use crate::{errors::ParseElementError, select::Select, structures::group::Group, system::System};
#[derive(Debug, Clone)]
pub struct Elements {
pub(crate) elements: IndexMap<String, Element>,
}
impl Default for Elements {
fn default() -> Self {
let yaml = include_str!("../config/elements.yaml");
Elements::new_from_string(yaml)
.expect("FATAL GROAN ERROR | Elements::default | Default `elements.yaml` file could not be read or parsed.")
}
}
impl Elements {
pub fn from_file(filename: impl AsRef<Path>) -> Result<Self, ParseElementError> {
Elements::new_from_string(&Elements::load_yaml_to_string(filename)?)
}
fn new_from_string(yaml: &str) -> Result<Self, ParseElementError> {
let elements: IndexMap<String, Element> = match serde_yaml::from_str(yaml) {
Ok(x) => x,
Err(e) => return Err(ParseElementError::CouldNotParseYaml(e)),
};
Ok(Elements { elements })
}
pub fn update(&mut self, update_elements: Elements) {
for (name, element) in update_elements.elements.into_iter() {
if let Some(old) = self.elements.get_mut(&name) {
old.update(element);
} else {
self.elements.insert(name, element);
}
}
}
fn load_yaml_to_string(filename: impl AsRef<Path>) -> Result<String, ParseElementError> {
let mut yaml_file = match File::open(&filename) {
Ok(x) => x,
Err(_) => {
return Err(ParseElementError::FileNotFound(Box::from(
filename.as_ref(),
)))
}
};
let mut yaml_string = String::new();
match yaml_file.read_to_string(&mut yaml_string) {
Ok(_) => Ok(yaml_string),
Err(_) => {
Err(ParseElementError::FileCouldNotBeRead(Box::from(
filename.as_ref(),
)))
}
}
}
}
#[derive(Debug, Clone)]
pub(crate) struct Element {
pub symbol: Option<String>,
pub select: Option<Select>,
pub mass: Option<f32>,
pub vdw: Option<f32>,
pub expected_max_bonds: Option<u8>,
pub expected_min_bonds: Option<u8>,
}
impl Element {
pub(crate) fn validate_select(&self, system: &System) -> Result<(), ElementError> {
if let Some(select) = &self.select {
match select.validate_groups(system) {
Ok(_) => return Ok(()),
Err(e) => return Err(ElementError::InvalidQuery(e)),
}
}
Ok(())
}
pub(crate) fn matches(&self, atom_index: usize, system: &System) -> Result<bool, ElementError> {
match &self.select {
Some(select) => match Group::matches_select(atom_index, select, system) {
Ok(x) => Ok(x),
Err(e) => Err(ElementError::InvalidQuery(e)),
},
None => Ok(false),
}
}
fn update(&mut self, element: Element) {
if element.symbol.is_some() {
self.symbol = element.symbol;
}
if element.select.is_some() {
self.select = element.select;
}
if element.mass.is_some() {
self.mass = element.mass;
}
if element.vdw.is_some() {
self.vdw = element.vdw;
}
if element.expected_max_bonds.is_some() {
self.expected_max_bonds = element.expected_max_bonds;
}
if element.expected_min_bonds.is_some() {
self.expected_min_bonds = element.expected_min_bonds;
}
}
}
struct ElementVisitor;
impl<'de> Visitor<'de> for ElementVisitor {
type Value = Element;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("struct Element")
}
fn visit_map<V>(self, mut map: V) -> Result<Element, V::Error>
where
V: MapAccess<'de>,
{
let mut symbol = None;
let mut select = None;
let mut mass = None;
let mut vdw = None;
let mut expected_max_bonds = None;
let mut expected_min_bonds = None;
while let Some(key) = map.next_key::<String>()? {
match key.as_str() {
"symbol" => symbol = map.next_value()?,
"query" => {
let query: Option<String> = map.next_value()?;
select = match query {
Some(q) => match Select::parse_query(&q) {
Ok(sel) => Some(*sel),
Err(_) => return Err(de::Error::custom("invalid query")),
},
None => None,
};
}
"mass" => {
let value: Option<f32> = map.next_value()?;
mass = match value {
Some(v) if v < 0.0 => return Err(de::Error::custom("mass is negative")),
_ => value,
};
}
"vdw" => {
let value: Option<f32> = map.next_value()?;
vdw = match value {
Some(v) if v < 0.0 => return Err(de::Error::custom("vdw is negative")),
_ => value,
};
}
"expected_max_bonds" => expected_max_bonds = map.next_value()?,
"expected_min_bonds" => expected_min_bonds = map.next_value()?,
_ => return Err(de::Error::unknown_field(&key, FIELDS)),
}
}
Ok(Element {
symbol,
select,
mass,
vdw,
expected_max_bonds,
expected_min_bonds,
})
}
}
const FIELDS: &[&str] = &[
"symbol",
"query",
"mass",
"vdw",
"expected_max_bonds",
"expected_min_bonds",
];
impl<'de> Deserialize<'de> for Element {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_struct("Element", FIELDS, ElementVisitor)
}
}
#[cfg(test)]
mod tests {
use float_cmp::assert_approx_eq;
use super::*;
#[test]
fn elements_default() {
let elements = Elements::default();
assert_eq!(elements.elements.len(), 39);
let e = elements.elements.get("dummy").unwrap();
assert!(e.symbol.is_none());
assert!(e.select.is_some());
assert_approx_eq!(f32, e.mass.unwrap(), 0.0);
assert!(e.vdw.is_none());
assert!(e.expected_max_bonds.is_none());
assert!(e.expected_min_bonds.is_none());
let e = elements.elements.get("carbon").unwrap();
assert_eq!(e.symbol.as_ref().unwrap(), "C");
assert!(e.select.is_some());
assert_approx_eq!(f32, e.mass.unwrap(), 12.0107);
assert_approx_eq!(f32, e.vdw.unwrap(), 0.17);
assert_eq!(e.expected_max_bonds.unwrap(), 4);
assert_eq!(e.expected_min_bonds.unwrap(), 2);
let e = elements.elements.get("bismuth").unwrap();
assert_eq!(e.symbol.as_ref().unwrap(), "Bi");
assert!(e.select.is_some());
assert_approx_eq!(f32, e.mass.unwrap(), 208.9804);
assert!(e.vdw.is_none());
assert!(e.expected_max_bonds.is_none());
assert!(e.expected_min_bonds.is_none());
}
fn elements_match(el1: &mut Elements, el2: &mut Elements) {
assert_eq!(el1.elements.len(), el2.elements.len());
for (name1, e1) in el1.elements.iter() {
let e2 = el2.elements.get(name1).unwrap();
match (&e1.symbol, &e2.symbol) {
(Some(x), Some(y)) => assert_eq!(x, y),
(None, None) => (),
_ => panic!("Symbols do not match."),
}
match (&e1.select, &e2.select) {
(Some(x), Some(y)) => assert_eq!(x, y),
(None, None) => (),
_ => panic!("Select trees do not match."),
}
match (&e1.mass, &e2.mass) {
(Some(x), Some(y)) => assert_approx_eq!(f32, *x, *y),
(None, None) => (),
_ => panic!("Masses do not match."),
}
match (&e1.vdw, &e2.vdw) {
(Some(x), Some(y)) => assert_approx_eq!(f32, *x, *y),
(None, None) => (),
_ => panic!("Van der Waals radii do not match."),
}
match (&e1.expected_max_bonds, &e2.expected_max_bonds) {
(Some(x), Some(y)) => assert_eq!(x, y),
(None, None) => (),
_ => panic!("Expected max bonds do not match."),
}
}
}
#[test]
fn elements_from_file() {
let mut default_elements = Elements::default();
let mut elements_from_file = Elements::from_file("src/config/elements.yaml").unwrap();
elements_match(&mut default_elements, &mut elements_from_file);
}
#[test]
fn elements_invalid_field() {
match Elements::from_file("test_files/elements_invalid_field.yaml") {
Ok(_) => panic!("Function should have failed but it succeeded."),
Err(ParseElementError::CouldNotParseYaml(e)) => {
let string = e.to_string();
assert!(string.contains("unknown field"));
}
Err(e) => panic!(
"Function failed successfully but incorrect error type `{}` was returned.",
e
),
}
}
#[test]
fn elements_nonexistent_file() {
match Elements::from_file("nonexistent_file.yaml") {
Ok(_) => panic!("Function should have failed but it succeeded."),
Err(ParseElementError::FileNotFound(name)) => {
assert_eq!(name.to_str().unwrap(), "nonexistent_file.yaml")
}
Err(e) => panic!(
"Function failed successfully but incorrect error type `{}` was returned.",
e
),
}
}
#[test]
fn elements_invalid_query() {
match Elements::from_file("test_files/elements_invalid_query.yaml") {
Ok(_) => panic!("Function should have failed but it succeeded."),
Err(ParseElementError::CouldNotParseYaml(e)) => {
let string = e.to_string();
assert!(string.contains("invalid query"));
}
Err(e) => panic!(
"Function failed successfully but incorrect error type `{}` was returned.",
e
),
}
}
#[test]
fn elements_invalid_mass() {
match Elements::from_file("test_files/elements_invalid_mass.yaml") {
Ok(_) => panic!("Function should have failed but it succeeded."),
Err(ParseElementError::CouldNotParseYaml(e)) => {
let string = e.to_string();
assert!(string.contains("mass is negative"));
}
Err(e) => panic!(
"Function failed successfully but incorrect error type `{}` was returned.",
e
),
}
}
#[test]
fn elements_invalid_vdw() {
match Elements::from_file("test_files/elements_invalid_vdw.yaml") {
Ok(_) => panic!("Function should have failed but it succeeded."),
Err(ParseElementError::CouldNotParseYaml(e)) => {
let string = e.to_string();
assert!(string.contains("vdw is negative"));
}
Err(e) => panic!(
"Function failed successfully but incorrect error type `{}` was returned.",
e
),
}
}
#[test]
fn elements_update() {
let mut elements = Elements::default();
elements.update(Elements::from_file("test_files/elements_update.yaml").unwrap());
assert_eq!(elements.elements.len(), 40);
let e = elements.elements.get("dummy").unwrap();
assert!(e.symbol.is_none());
assert!(e.select.is_some());
assert_approx_eq!(f32, e.mass.unwrap(), 0.0);
assert!(e.vdw.is_none());
assert!(e.expected_max_bonds.is_none());
assert!(e.expected_min_bonds.is_none());
let e = elements.elements.get("carbon").unwrap();
assert_eq!(e.symbol.as_ref().unwrap(), "C");
assert!(e.select.is_some());
assert_approx_eq!(f32, e.mass.unwrap(), 12.0107);
assert_approx_eq!(f32, e.vdw.unwrap(), 0.20);
assert_eq!(e.expected_max_bonds.unwrap(), 4);
assert_eq!(e.expected_min_bonds.unwrap(), 2);
let e = elements.elements.get("hydrogen").unwrap();
assert_eq!(e.symbol.as_ref().unwrap(), "HH");
assert!(e.select.is_some());
assert_approx_eq!(f32, e.mass.unwrap(), 2.014);
assert_approx_eq!(f32, e.vdw.unwrap(), 0.10);
assert_eq!(e.expected_max_bonds.unwrap(), 8);
assert_eq!(e.expected_min_bonds.unwrap(), 2);
let e = elements.elements.get("polonium").unwrap();
assert_eq!(e.symbol.as_ref().unwrap(), "Po");
assert!(e.select.is_some());
assert_approx_eq!(f32, e.mass.unwrap(), 209.0);
assert!(e.vdw.is_none());
assert!(e.expected_max_bonds.is_none());
assert!(e.expected_min_bonds.is_none());
let e = elements.elements.get("bismuth").unwrap();
assert_eq!(e.symbol.as_ref().unwrap(), "Bi");
assert!(e.select.is_some());
assert_approx_eq!(f32, e.mass.unwrap(), 208.9804);
assert!(e.vdw.is_none());
assert!(e.expected_max_bonds.is_none());
assert!(e.expected_max_bonds.is_none());
}
}