"""
The Atom class is a useful way of keeping track of properties of an atom, such as its atomic number, charge and position
"""
from __future__ import annotations
import numpy as np
from .constants import BOHR_TO_ANGSTROM
from .element_data import (element_mass, get_atomic_number_from_element,
get_element_from_atomic_number, vdw_radii)
[docs]
class Atom:
def __init__(
self,
atomic_number: int,
element: str,
charge: float,
x: float,
y: float,
z: float,
is_angstrom: bool,
):
"""
Args:
atomicNumber (int): atomic number. For example, for iron atom, 26
element (str): element. For example, for iron atom, Fe
charge (float): charge of atom
x (float): x-coordinate
y (float): y-coordinate
z (float): z-coordinate
isAngstrom (bool): whether coordinates are in Angstrom. If False, assume in Bohr
"""
self._atomic_number = atomic_number
self._element = element
self._charge = charge
self._x = x
self._y = y
self._z = z
self._position = np.array([self._x, self._y, self._z])
self.is_angstrom = is_angstrom
try:
self._mass = element_mass[self._atomic_number - 1]
except ValueError:
msg = f"Could not determine mass of Atom with atomic number {self._atomic_number}"
print(msg)
self._mass = -1
try:
self._vdw_radius = vdw_radii[self._atomic_number - 1]
except ValueError:
msg = f"Could not determine Van der Waals radius of Atom with atomic number {self._atomic_number}"
print(msg)
self._vdw_radius = -1.0
[docs]
@classmethod
def from_cube_string(cls, string: str):
"""Create Atom instance from a line in a CUBE file
Args:
string (str): line in CUBE file"""
split_string = string.split()
atomic_number = int(split_string[0])
element = get_element_from_atomic_number(atomic_number)
charge, x, y, z = (float(field) for field in split_string[1:])
is_angstrom = False # Bohr by default
return cls(atomic_number, element, charge, x, y, z, is_angstrom)
[docs]
@classmethod
def from_xyz_string(cls, string: str):
"""Create Atom instance from a line in an XYZ file
Args:
string (str): line from an XYZ file. Formatted as A x y z ..., where A is either atomic number or element string
"""
split_string = string.split()
element = split_string[0].strip()
try:
atomic_number = int(element)
element = get_element_from_atomic_number(atomic_number)
except ValueError:
atomic_number = get_atomic_number_from_element(element)
x, y, z = (float(field) for field in split_string[1:4])
is_angstrom = True # Angstrom by default
return cls(atomic_number, element, "UNKNOWN", x, y, z, is_angstrom)
[docs]
@classmethod
def from_sdf_string(cls, string: str):
"""Create Atom instance from a line in an SDF file
Args:
string (str): line from an SDF file. Formatted as x y z A, where A is an element string.
"""
split_string = string.split()
element = split_string[3].strip()
atomic_number = get_atomic_number_from_element(element)
x, y, z = (float(field) for field in split_string[:3])
is_angstrom = True # SDF is in Angstrom
return cls(atomic_number, element, "UNKNOWN", x, y, z, is_angstrom)
[docs]
def get_atomic_number(self) -> int:
"""Get the atomic number of the atom
Returns:
int: atomic number"""
return self._atomic_number
[docs]
def get_charge(self) -> float:
"""Get the charge of the Atom (undefined if created from XYZ file)
Returns:
float: charge of Atom"""
return self._charge
[docs]
def get_x(self) -> float:
"""Get the x-coordinate of the atom
Returns:
float: x-coordinate of Atom
"""
return self._x
[docs]
def get_y(self) -> float:
"""Get the y-coordinate of the atom
Returns:
float: y-coordinate of Atom
"""
return self._y
[docs]
def get_z(self) -> float:
"""Get the z-coordinate of the atom
Returns:
float: z-coordinate of Atom
"""
return self._z
[docs]
def get_position(self) -> np.ndarray[float]:
"""Get position of the atom
Returns:
ndarray: array with x, y and z coordinates of Atom"""
return self._position
[docs]
def position_bohr_to_angstrom(self) -> None:
"""Convert the position vector from Bohr to Angstrom"""
if self.is_angstrom:
raise ValueError()
self.is_angstrom = True
self.set_position(self._position * BOHR_TO_ANGSTROM)
[docs]
def set_position(self, new_position) -> None:
"""Set the position of the atom to a new position
Args:
newPosition (ndarray): x, y and z coordinates of new position
"""
self._position = new_position
self._x, self._y, self._z = new_position
[docs]
def get_element(self) -> str:
"""Get the element of the atom
Returns:
str: element of Atom
"""
return self._element
[docs]
def get_mass(self) -> float:
"""Get the mass of the atom
Returns:
float: mass of Atom"""
return self._mass
[docs]
def get_vdw_radius(self) -> float:
"""Get the Van der Waals radius of the atom
Returns:
float: Van der Waals radius of the Atom"""
return self._vdw_radius
def __repr__(self):
return self.__str__()
def __str__(self):
return f"Atom with atomic number {self._atomic_number} at position {self._position}"
# def findBoundAtoms(self, structure: Structure) -> list[int]:
# """Find which Atom indeces are bound to this Atom in the structure"""
# boundAtomIndeces = []
# for i, bond in enumerate(structure.getBonds()):
# atom1Pos = bond.getAtom1Pos()
# atom2Pos = bond.getAtom2Pos()
# if np.all(self._positionVector == atom1Pos):
# boundAtomIndeces.append(bond.getAtom2Index())
# elif np.all(self._positionVector == atom2Pos):
# boundAtomIndeces.append(bond.getAtom1Index())
# return boundAtomIndeces