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 Distortion: pass
[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