Source code for schrodinger.structutils.check
"""
Tools for detecting and reporting distortions in structures.
"""
from typing import Optional
from schrodinger import structure
from schrodinger.infra import mm
from schrodinger.structutils import analyze
from schrodinger.structutils import measure
from schrodinger.structutils import rings
[docs]class AngleDistortion(Distortion):
[docs] def __init__(self, atoms, ideal_angle, actual_angle):
self.atoms = atoms
self.ideal_angle = ideal_angle
self.actual_angle = round(actual_angle, 2)
def __str__(self):
return f'Angle Distortion ({self.atoms[0].index}-{self.atoms[1].index}-{self.atoms[2].index}): {self.actual_angle} != {self.ideal_angle}'
[docs]class BondDistortion(Distortion):
[docs] def __init__(self, bond, ideal_length, actual_length):
self.bond = bond
self.ideal_length = round(ideal_length, 3)
self.actual_length = round(actual_length, 2)
def __str__(self):
return f'Bond Distortion ({self.bond.atom1.index}-{self.bond.atom2.index}): {self.actual_length} != {self.ideal_length}'
def _translate_angle_geom(mmideal_geo):
"""
Returns the MMIdeal_Geo enum from the MMAT atom type
"""
if mmideal_geo == mm.MMAT_LINEAR:
return mm.MMIDEAL_LINEAR
elif mmideal_geo == mm.MMAT_TRIGONAL:
return mm.MMIDEAL_TRIGONAL
elif mmideal_geo == mm.MMAT_TETRAHEDRAL:
return mm.MMIDEAL_TETRAHEDRAL
else:
return mm.MMIDEAL_ERROR
def _find_small_ring_distortions(st, tolerance):
"""
Finds angle distortions in 3 and 4 membered rings
"""
distortions = []
small_ring_atoms = set()
# All angles in 3/4-membered rings should be 60/90 degrees
sssr = rings.find_rings(st)
for ring in sssr:
if len(ring) > 4:
continue
ideal_angle = 60 if len(ring) == 3 else 90
full_ring = ring + ring[:2]
all_angles = []
for a1, a2, a3 in zip(full_ring, full_ring[1:], full_ring[2:]):
atoms = [st.atom[a1], st.atom[a2], st.atom[a3]]
all_angles.append(atoms)
small_ring_atoms.add(atoms[1].index)
for angle_atoms in all_angles:
angle = measure.measure_bond_angle(*angle_atoms)
if abs(angle - ideal_angle) > tolerance:
# distortion found
distortions.append(
AngleDistortion(angle_atoms, ideal_angle, angle))
return [distortions, small_ring_atoms]
def _find_angle_distortions(st, tolerance):
"""
Determines if st has any distorted angles. An angle is distorted if it is not
within tolerance of the ideal angle.
"""
ring_angle_distortions = _find_small_ring_distortions(st, tolerance)
distortions = ring_angle_distortions[0]
ring_angle_atoms = ring_angle_distortions[1]
# Check linear/triagonal/tetrahedral angles
for angle_atom_indices in analyze.angle_iterator(st):
# Ignore angles in/attatched to 3 or 4 membered rings
if angle_atom_indices[1] in ring_angle_atoms:
continue
angle_atoms = [st.atom[a] for a in angle_atom_indices]
central_geometry = mm.mmat_get_central_geometry(
angle_atoms[1].atom_type)
mmideal_central_geometry = _translate_angle_geom(central_geometry)
if mmideal_central_geometry == mm.MMIDEAL_ERROR:
# Atom is not LIN/TRI/TET
continue
if mm.mmat_is_wildcard(angle_atoms[1].atom_type) and len(
angle_atoms[1].bond) == 3:
# Atom is automatically labeled as TET but may TRI
mmideal_central_geometry = mm.MMIDEAL_TRIGONAL
ideal_angle = mm.mmideal_get_angle(mmideal_central_geometry)
angle = measure.measure_bond_angle(*angle_atoms)
diff = abs(ideal_angle - angle)
if diff > tolerance:
distortions.append(AngleDistortion(angle_atoms, ideal_angle, angle))
return distortions
def _find_bond_distortions(st, tolerance):
"""
Finds any bond distortions. A bond is distorted if it is not within tolerance
of its ideal length.
"""
distortions = []
for bond in st.bond:
ideal_length = mm.mmideal_get_stretch(st, [bond.atom1, bond.atom2])
bond_length = measure.measure_distance(bond.atom1, bond.atom2)
diff = abs(ideal_length - bond_length)
if diff > tolerance:
distortions.append(BondDistortion(bond, ideal_length, bond_length))
return distortions
[docs]def find_distortions(st: structure.Structure,
angle_tolerance: float = 20.0,
bond_tolerance: float = 0.5) -> Optional[list]:
"""
Determines whether a given structure is distorted or not. Specifically,
this function checks that checks that:
1. All angles in 3-membered rings are within angle_tolerance (in degrees) of 60 deg
2. All angles in 4-membered rings are within angle_tolerance of 90 deg
3. All linear/trigonol/tetrahedral angles are within angle_tolerance of 180/120/109.5
4. All bonds are within bond_tolerance of their ideal length
Returns None or a list of Distortions
"""
angle_distortions = _find_angle_distortions(st, angle_tolerance)
bond_distortions = _find_bond_distortions(st, bond_tolerance)
if not (angle_distortions or bond_distortions):
return None
return angle_distortions + bond_distortions