#![allow(dead_code)]
use crate::reference_tables;
use crate::structs::*;
use crate::transformation::*;
use std::cmp::Ordering;
use std::fmt;
#[derive(Debug, Clone, PartialEq)]
pub struct Atom {
hetero: bool,
serial_number: usize,
name: String,
x: f64,
y: f64,
z: f64,
occupancy: f64,
b_factor: f64,
element: String,
charge: isize,
atf: Option<[[f64; 3]; 3]>,
}
impl Atom {
#[allow(clippy::too_many_arguments)]
pub fn new(
hetero: bool,
serial_number: usize,
atom_name: &str,
x: f64,
y: f64,
z: f64,
occupancy: f64,
b_factor: f64,
element: &str,
charge: isize,
) -> Option<Atom> {
if valid_identifier(atom_name) && valid_identifier(element) {
Some(Atom {
hetero,
serial_number,
name: atom_name.trim().to_ascii_uppercase(),
x,
y,
z,
occupancy,
b_factor,
element: element.trim().to_ascii_uppercase(),
charge,
atf: None,
})
} else {
None
}
}
pub fn hetero(&self) -> bool {
self.hetero
}
pub fn set_hetero(&mut self, new_hetero: bool) {
self.hetero = new_hetero
}
pub fn pos(&self) -> (f64, f64, f64) {
(self.x, self.y, self.z)
}
pub fn set_pos(&mut self, new_pos: (f64, f64, f64)) -> Result<(), String> {
if new_pos.0.is_finite() && new_pos.1.is_finite() && new_pos.2.is_finite() {
self.x = new_pos.0;
self.y = new_pos.1;
self.z = new_pos.2;
Ok(())
} else {
Err(format!(
"One (or more) of values of the new position is not finite for atom {} values {:?}",
self.serial_number, new_pos
))
}
}
pub fn x(&self) -> f64 {
self.x
}
pub fn set_x(&mut self, new_pos: f64) -> Result<(), String> {
if new_pos.is_finite() {
self.x = new_pos;
Ok(())
} else {
Err(format!(
"The value of the new x position is not finite for atom {} value {}",
self.serial_number, new_pos
))
}
}
pub fn y(&self) -> f64 {
self.y
}
pub fn set_y(&mut self, new_pos: f64) -> Result<(), String> {
if new_pos.is_finite() {
self.y = new_pos;
Ok(())
} else {
Err(format!(
"The value of the new y position is not finite for atom {} value {}",
self.serial_number, new_pos
))
}
}
pub fn z(&self) -> f64 {
self.z
}
pub fn set_z(&mut self, new_pos: f64) -> Result<(), String> {
if new_pos.is_finite() {
self.z = new_pos;
Ok(())
} else {
Err(format!(
"The value of the new z position is not finite for atom {} value {}",
self.serial_number, new_pos
))
}
}
pub fn serial_number(&self) -> usize {
self.serial_number
}
pub fn set_serial_number(&mut self, new_serial_number: usize) {
self.serial_number = new_serial_number;
}
pub fn name(&self) -> &str {
&self.name
}
pub fn set_name(&mut self, new_name: &str) -> Result<(), String> {
if !valid_identifier(new_name) {
Err(format!(
"New name has invalid characters for atom {} name {}",
self.serial_number, new_name
))
} else {
self.name = new_name.trim().to_ascii_uppercase();
Ok(())
}
}
pub fn occupancy(&self) -> f64 {
self.occupancy
}
pub fn set_occupancy(&mut self, new_occupancy: f64) -> Result<(), String> {
if new_occupancy.is_finite() {
self.occupancy = new_occupancy;
Ok(())
} else {
Err(format!(
"The value of the new occupancy is not finite for atom {} value {}",
self.serial_number, new_occupancy
))
}
}
pub fn b_factor(&self) -> f64 {
self.b_factor
}
pub fn set_b_factor(&mut self, new_b_factor: f64) -> Result<(), String> {
if new_b_factor.is_finite() {
self.b_factor = new_b_factor;
Ok(())
} else {
Err(format!(
"The value of the new b_factor is not finite for atom {} value {}",
self.serial_number, new_b_factor
))
}
}
pub fn element(&self) -> &str {
&self.element
}
pub fn atomic_number(&self) -> Option<usize> {
if !self.element.is_empty() {
reference_tables::get_atomic_number(&self.element())
} else {
reference_tables::get_atomic_number(&self.name())
}
}
pub fn atomic_radius(&self) -> Option<f64> {
if let Some(s) = self.atomic_number() {
reference_tables::get_atomic_radius(s)
} else {
None
}
}
pub fn vanderwaals_radius(&self) -> Option<f64> {
if let Some(s) = self.atomic_number() {
reference_tables::get_vanderwaals_radius(s)
} else {
None
}
}
pub fn covalent_bond_radii(&self) -> Option<(usize, Option<usize>, Option<usize>)> {
if let Some(s) = self.atomic_number() {
Some(reference_tables::get_covalent_bond_radii(s))
} else {
None
}
}
pub fn set_element(&mut self, new_element: &str) -> Result<(), String> {
if !valid_identifier(new_element) {
Err(format!(
"New element has invalid characters for atom {} name {}",
self.serial_number, new_element
))
} else {
self.element = new_element.trim().to_ascii_uppercase();
Ok(())
}
}
pub fn charge(&self) -> isize {
self.charge
}
#[allow(clippy::cast_possible_truncation)]
pub fn pdb_charge(&self) -> String {
if self.charge == 0 || self.charge < -9 || self.charge > 9 {
String::new()
} else {
let mut sign = '+';
let charge = (48 + self.charge.abs() as u8) as char;
if self.charge < 0 {
sign = '-';
}
let mut output = String::new();
output.push(charge);
output.push(sign);
output
}
}
pub fn set_charge(&mut self, new_charge: isize) {
self.charge = new_charge;
}
pub fn anisotropic_temperature_factors(&self) -> Option<[[f64; 3]; 3]> {
self.atf
}
pub fn set_anisotropic_temperature_factors(&mut self, factors: [[f64; 3]; 3]) {
self.atf = Some(factors);
}
pub fn backbone(&self) -> bool {
let backbone_names = vec!["N", "CA", "C", "O"];
backbone_names.contains(&self.name())
}
pub fn apply_transformation(&mut self, transformation: &TransformationMatrix) {
self.set_pos(transformation.apply(self.pos()))
.expect("Some numbers were invalid in applying a transformation");
}
pub fn corresponds(&self, other: &Atom) -> bool {
self.serial_number == other.serial_number
&& self.name() == other.name()
&& self.element() == other.element()
&& self.charge() == other.charge()
&& ((self.atf.is_none() && other.atf.is_none())
|| (self.atf.is_some() && other.atf.is_some()))
}
pub fn distance_wrapping(&self, other: &Atom, cell: &UnitCell) -> f64 {
let mut x = other.x;
if (self.x - other.x).abs() > cell.a() / 2.0 {
if self.x > other.x {
x += cell.a();
} else {
x -= cell.a();
}
}
let mut y = other.y;
if (self.y - other.y).abs() > cell.b() / 2.0 {
if self.y > other.y {
y += cell.b();
} else {
y -= cell.b();
}
}
let mut z = other.z;
if (self.z - other.z).abs() > cell.c() / 2.0 {
if self.z > other.z {
z += cell.c();
} else {
z -= cell.c();
}
}
((x - self.x).powi(2) + (y - self.y).powi(2) + (z - self.z).powi(2)).sqrt()
}
pub fn distance(&self, other: &Atom) -> f64 {
((other.x - self.x).powi(2) + (other.y - self.y).powi(2) + (other.z - self.z).powi(2))
.sqrt()
}
pub fn overlaps(&self, other: &Atom) -> Option<bool> {
if let Some(self_rad) = self.atomic_radius() {
if let Some(other_rad) = other.atomic_radius() {
Some(self.distance(other) <= self_rad + other_rad)
} else {
None
}
} else {
None
}
}
pub fn overlaps_wrapping(&self, other: &Atom, cell: &UnitCell) -> Option<bool> {
if let Some(self_rad) = self.atomic_radius() {
if let Some(other_rad) = other.atomic_radius() {
Some(self.distance_wrapping(other, cell) <= self_rad + other_rad)
} else {
None
}
} else {
None
}
}
}
impl fmt::Display for Atom {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"ATOM ID: {}, Number: {}, Element: {}, X: {}, Y: {}, Z: {}, OCC: {}, B: {}, ANISOU: {}",
self.name(),
self.serial_number(),
self.element(),
self.x(),
self.y(),
self.z(),
self.occupancy(),
self.b_factor(),
self.atf.is_some()
)
}
}
impl Eq for Atom {}
impl PartialOrd for Atom {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.serial_number.cmp(&other.serial_number))
}
}
impl Ord for Atom {
fn cmp(&self, other: &Self) -> Ordering {
self.serial_number.cmp(&other.serial_number)
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
use super::Atom;
use super::UnitCell;
#[test]
fn set_name() {
let mut a = Atom::new(false, 0, "", 0.0, 0.0, 0.0, 0.0, 0.0, "", 0).unwrap();
assert!(a.set_name("Å").is_err());
assert!(a.set_name("ATOMS").is_ok());
a.set_name("ATOM").unwrap();
a.set_name("HOH").unwrap();
a.set_name("RK").unwrap();
a.set_name("R").unwrap();
a.set_name("").unwrap();
}
#[test]
fn set_element() {
let mut a = Atom::new(false, 0, "", 0.0, 0.0, 0.0, 0.0, 0.0, "", 0).unwrap();
assert!(a.set_element("R̈").is_err());
assert!(a.set_element("HOH").is_ok());
a.set_element("RK").unwrap();
a.set_element("R").unwrap();
a.set_element("").unwrap();
}
#[test]
fn distance() {
let a = Atom::new(false, 0, "", 1.0, 0.0, 0.0, 0.0, 0.0, "C", 0).unwrap();
let b = Atom::new(false, 0, "", 9.0, 0.0, 0.0, 0.0, 0.0, "C", 0).unwrap();
let cell = UnitCell::new(10.0, 10.0, 10.0, 90.0, 90.0, 90.0);
assert!(!a.overlaps(&b).unwrap());
assert!(a.overlaps_wrapping(&b, &cell).unwrap());
assert_eq!(a.distance(&b), 8.0);
assert_eq!(a.distance_wrapping(&b, &cell), 2.0);
}
#[test]
fn distance_all_axes() {
let a = Atom::new(false, 0, "", 1.0, 1.0, 1.0, 0.0, 0.0, "C", 0).unwrap();
let b = Atom::new(false, 0, "", 9.0, 9.0, 9.0, 0.0, 0.0, "C", 0).unwrap();
let cell = UnitCell::new(10.0, 10.0, 10.0, 90.0, 90.0, 90.0);
assert!(!a.overlaps(&b).unwrap());
assert!(a.overlaps_wrapping(&b, &cell).unwrap());
}
#[test]
fn check_equality() {
let a = Atom::new(false, 0, "", 1.0, 0.0, 0.0, 0.0, 0.0, "C", 0).unwrap();
let b = Atom::new(false, 0, "", 9.0, 0.0, 0.0, 0.0, 0.0, "C", 0).unwrap();
let c = Atom::new(false, 0, "", 9.0, 0.0, 0.0, 0.0, 0.0, "C", 0).unwrap();
assert_ne!(a, b);
assert_eq!(b, c);
assert_ne!(a, c);
}
#[test]
fn invalid_new_values() {
let mut a = Atom::new(false, 0, "", 1.0, 1.0, 1.0, 0.0, 0.0, "C", 0).unwrap();
assert!(Atom::new(false, 0, "Rͦ", 1.0, 1.0, 1.0, 0.0, 0.0, "C", 0).is_none());
assert!(a.set_x(f64::INFINITY).is_err());
assert!(a.set_x(f64::NAN).is_err());
assert!(a.set_x(f64::NEG_INFINITY).is_err());
assert!(a.set_y(f64::INFINITY).is_err());
assert!(a.set_z(f64::INFINITY).is_err());
assert!(a.set_pos((f64::INFINITY, 0., 0.)).is_err());
assert!(a.set_b_factor(f64::INFINITY).is_err());
assert!(a.set_occupancy(f64::INFINITY).is_err());
}
#[test]
fn check_setters() {
let mut a = Atom::new(false, 0, "C", 1.0, 1.0, 1.0, 0.0, 0.0, "", 0).unwrap();
assert!(Atom::new(false, 0, "Rͦ", 1.0, 1.0, 1.0, 0.0, 0.0, "C", 0).is_none());
assert!(a.set_x(2.0).is_ok());
assert_eq!(a.x(), 2.0);
assert!(a.set_y(2.0).is_ok());
assert_eq!(a.y(), 2.0);
assert!(a.set_z(2.0).is_ok());
assert_eq!(a.z(), 2.0);
assert!(a.set_pos((3.0, 3.0, 3.0)).is_ok());
assert_eq!(a.x(), 3.0);
assert_eq!(a.y(), 3.0);
assert_eq!(a.z(), 3.0);
assert_eq!(a.pos(), (3.0, 3.0, 3.0));
assert!(a.set_b_factor(2.0).is_ok());
assert_eq!(a.b_factor(), 2.0);
assert!(a.set_occupancy(2.0).is_ok());
assert_eq!(a.occupancy(), 2.0);
assert!(a.set_occupancy(0.0).is_ok());
assert!(a.set_b_factor(0.0).is_ok());
a.set_hetero(true);
assert_eq!(a.hetero(), true);
a.set_serial_number(42);
assert_eq!(a.serial_number(), 42);
assert_eq!(a.atomic_number(), Some(6));
assert!(a.set_name("HOH").is_ok());
assert!(a.atomic_radius().is_none());
assert!(a.vanderwaals_radius().is_none());
assert!(a.covalent_bond_radii().is_none());
a.set_charge(-1);
assert_eq!(a.charge(), -1);
assert_eq!(a.pdb_charge(), "1-".to_string());
}
#[test]
fn check_radii() {
let a = Atom::new(false, 0, "H", 1.0, 1.0, 1.0, 0.0, 0.0, "", 0).unwrap();
assert_eq!(a.atomic_radius(), Some(1.54));
assert_eq!(a.vanderwaals_radius(), Some(1.20));
assert_eq!(a.covalent_bond_radii(), Some((32, None, None)));
let a = Atom::new(false, 0, "Cl", 1.0, 1.0, 1.0, 0.0, 0.0, "", 0).unwrap();
assert_eq!(a.atomic_radius(), Some(2.06));
assert_eq!(a.vanderwaals_radius(), Some(1.82));
assert_eq!(a.covalent_bond_radii(), Some((99, Some(95), Some(93))));
}
#[test]
fn check_display() {
let a = Atom::new(false, 0, "C", 1.0, 1.0, 1.0, 0.0, 0.0, "", 0).unwrap();
format!("{:?}", a);
format!("{}", a);
}
}