from __future__ import annotations
import numpy as np
from .constants import (CYLINDER_LENGTH_FRACTION,
CYLINDER_LENGTH_FRACTION_SPLIT, SPHERE_SCALE)
from .element_data import element_list, vdw_radii
from .geometry import check_3d_vector
[docs]
class Bond:
def __init__(
self,
atom1_index: int,
atom2_index: int,
bond_type: str,
bond_length: float,
interatomic_vector: np.ndarray,
midpoint_position: np.ndarray,
atom1_and_2_pos: np.ndarray,
name: str,
):
self._atom1_index, self._atom2_index = atom1_index, atom2_index
self._bond_type = bond_type
self._bond_length = bond_length
self._interatomic_vector = interatomic_vector
self.set_midpoint(midpoint_position)
self._atom1_pos = atom1_and_2_pos[0]
self._atom2_pos = atom1_and_2_pos[1]
self.set_name(name)
[docs]
def get_atom1_index(self) -> int:
"""Get the index of the first atom that is connected to this bond"""
return self._atom1_index
[docs]
def get_atom2_index(self) -> int:
"""Get the index of the second atom that is connected to this bond"""
return self._atom2_index
[docs]
def get_bond_type(self) -> str:
"""Get the bond type (the two connecting elements in alphabetical order)"""
return self._bond_type
[docs]
def get_length(self) -> float:
"""Get the bond length"""
return self._bond_length
[docs]
def get_interatomic_vector(self) -> np.ndarray[float]:
"""Get the vector connecting the two atoms"""
return self._interatomic_vector
[docs]
def get_midpoint(self) -> np.ndarray[float]:
"""Get the midpoint position of the two atoms"""
return self._midpointPosition
[docs]
def set_midpoint(self, midpoint_position: np.ndarray) -> None:
"""Set the midpoint position of the two atoms"""
midpoint_position = check_3d_vector(midpoint_position)
self._midpointPosition = midpoint_position
[docs]
def get_direction(self) -> np.ndarray[float]:
"""Get the unit vector in the direction of the bond"""
return self._interatomic_vector / self._bond_length
[docs]
def get_atom1_position(self) -> np.ndarray[float]:
"""Get position of atom 1"""
return self._atom1_pos
[docs]
def get_atom2_position(self) -> np.ndarray[float]:
"""Get position of atom 2"""
return self._atom2_pos
[docs]
def get_name(self) -> str:
return self._name
[docs]
def set_name(self, name) -> None:
if not isinstance(name, str):
raise TypeError(f"name was supposed to be string but was type {type(name)}")
self._name = name
[docs]
def get_axis_angle_with_z(self) -> tuple[float, float, float, float]:
"""Get the axis angle such that a created cylinder in the direction of the bond"""
z = np.array([0, 0, 1])
axis = np.cross(z, self._interatomic_vector)
if np.linalg.norm(axis) < 1e-5:
axis = np.array([0, 0, 1])
angle = 0.0
else:
axis /= np.linalg.norm(axis)
angle = np.arccos(np.dot(self._interatomic_vector, z) / self._bond_length)
return angle, axis[0], axis[1], axis[2]
[docs]
def get_vdw_weighted_cylinder_midpoints(
self, element1: str, element2: str
) -> np.ndarray:
"""Get the Van der Waals-radii weighted location where both cylinders need to be placed"""
vdw_midpoint = self.get_vdw_weighted_midpoint(element1, element2)
loc1 = (
vdw_midpoint
- self.get_direction() * self._bond_length * CYLINDER_LENGTH_FRACTION_SPLIT
)
loc2 = (
vdw_midpoint
+ self.get_direction() * self._bond_length * CYLINDER_LENGTH_FRACTION_SPLIT
)
return np.array([loc1, loc2])
[docs]
def get_vdw_weighted_midpoint(self, element1: str, element2: str) -> np.ndarray:
"""Get the Van der Waals-radii weighted bond-midpoints"""
element1_index = element_list.index(element1)
vdw_radius1 = vdw_radii[element1_index]
element2_index = element_list.index(element2)
vdw_radius2 = vdw_radii[element2_index]
# sum_vdw_radii = vdw_radius1 + vdw_radius2
# fraction_vdw_radius1 = vdw_radius1 / sum_vdw_radii
# fraction_vdw_radius2 = vdw_radius2 / sum_vdw_radii
# Starting from the first atom (let's call it atom A),
# the VdW weighed midpoint is (where the two cylinders meet)
# position_A + (r_AB + r_A - rB)/2 * bondDirectionVector
return (
self._atom1_pos
+ (
self._bond_length
+ vdw_radius1 * SPHERE_SCALE
- vdw_radius2 * SPHERE_SCALE
)
/ 2
* -self.get_direction()
)