#![allow(dead_code)]
use crate::reference_tables;
use crate::structs::*;
use crate::transformation::*;
use std::fmt;
#[derive(Debug)]
pub struct Atom {
serial_number: usize,
name: [char; 4],
x: f64,
y: f64,
z: f64,
occupancy: f64,
b_factor: f64,
element: [char; 2],
charge: isize,
atf: Option<[[f64; 3]; 2]>,
}
impl Atom {
#[allow(clippy::too_many_arguments)]
pub fn new(
serial_number: usize,
atom_name: [char; 4],
x: f64,
y: f64,
z: f64,
occupancy: f64,
b_factor: f64,
element: [char; 2],
charge: isize,
) -> Option<Atom> {
let atom = Atom {
serial_number,
name: atom_name,
x,
y,
z,
occupancy,
b_factor,
element,
charge,
atf: None,
};
if !check_char4(atom_name) || !check_char2(element) {
None
} else {
Some(atom)
}
}
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) -> String {
self.name
.iter()
.collect::<String>()
.split_whitespace()
.collect::<String>()
}
pub fn set_name(&mut self, new_name: &str) -> Result<(), String> {
let new_name = format!("{:^4}", new_name);
let chars = new_name.to_ascii_uppercase().chars().collect::<Vec<char>>();
if chars.len() < 5 {
if check_chars(new_name.to_string()) {
self.name = [chars[0], chars[1], chars[2], chars[3]];
Ok(())
} else {
Err(format!(
"New name has invalid characters for atom {} name {}",
self.serial_number, new_name
))
}
} else {
Err(format!(
"New name is too long (max 4 chars) for atom {} name {}",
self.serial_number, new_name
))
}
}
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) -> String {
self.element
.iter()
.collect::<String>()
.split_whitespace()
.collect::<String>()
}
pub fn atomic_number(&self) -> Option<usize> {
if self.element != [' ', ' '] {
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 set_element(&mut self, new_element: &str) -> Result<(), String> {
let new_element = format!("{:>2}", new_element);
let chars = new_element
.to_ascii_uppercase()
.chars()
.collect::<Vec<char>>();
if chars.len() <= 2 {
if check_chars(new_element.to_string()) {
self.element = [chars[0], chars[1]];
Ok(())
} else {
Err(format!(
"New element has invalid characters for atom {} name {}",
self.serial_number, new_element
))
}
} else {
Err(format!(
"New element is too long (max 2 chars) for atom {} name {}",
self.serial_number, new_element
))
}
}
pub fn charge(&self) -> isize {
self.charge
}
pub fn pdb_charge(&self) -> String {
if self.charge == 0 {
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) -> Result<(), String> {
if new_charge < -9 || new_charge > 9 {
Err(format!(
"New charge is out of bounds, for Atom {}, with new charge {}",
self.serial_number, new_charge
))
} else {
self.charge = new_charge;
Ok(())
}
}
pub fn anisotropic_temperature_factors(&self) -> Option<[[f64; 3]; 2]> {
self.atf
}
pub fn set_anisotropic_temperature_factors(&mut self, factors: [[f64; 3]; 2]) {
self.atf = Some(factors);
}
pub fn backbone(&self) -> bool {
let backbone_names = vec!["N", "CA", "C", "O"];
backbone_names.contains(&self.name().as_str())
}
pub fn apply_transformation(&mut self, transformation: &TransformationMatrix) {
self.set_pos(transformation.apply(self.pos())).unwrap();
}
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 overlaps(&self, other: &Atom) -> Option<bool> {
if let Some(self_rad) = self.atomic_radius() {
if let Some(other_rad) = other.atomic_radius() {
Some(
self.x() + self_rad > other.x() - other_rad
&& self.x() - self_rad < other.x() + other_rad
&& self.y() + self_rad > other.y() - other_rad
&& self.y() - self_rad < other.y() + other_rad
&& self.z() + self_rad > other.z() - other_rad
&& self.z() - self_rad < other.z() + 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 Clone for Atom {
fn clone(&self) -> Self {
let mut atom = Atom::new(
self.serial_number,
self.name,
self.x,
self.y,
self.z,
self.occupancy,
self.b_factor,
self.element,
self.charge,
)
.unwrap();
atom.atf = self.atf;
atom
}
}
impl PartialEq for Atom {
fn eq(&self, other: &Self) -> bool {
self.serial_number == other.serial_number
&& self.name() == other.name()
&& self.element() == other.element()
&& self.charge() == other.charge()
&& self.atf == other.atf
&& self.pos() == other.pos()
&& self.occupancy == other.occupancy
&& self.b_factor == other.b_factor
}
}
#[cfg(test)]
mod tests {
use super::Atom;
#[test]
fn set_name() {
let mut a = Atom::new(
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_err());
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(
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_err());
a.set_element("RK").unwrap();
a.set_element("R").unwrap();
a.set_element("").unwrap();
}
}