Source code for schrodinger.application.matsci.etatoggle

"""
Utilities for toggle eta style.

Copyright Schrodinger, LLC. All rights reserved.
"""

from schrodinger import structure
from schrodinger.application.matsci import buildcomplex
from schrodinger.application.matsci import msutils
from schrodinger.application.matsci import parserutils
from schrodinger.infra import mm
from schrodinger.structutils import analyze
from schrodinger.structutils import transform
from schrodinger.test import ioredirect

DUMMY_ATOMIC_NUMBERS = msutils.DUMMY_ATOMIC_NUMBERS


[docs]class Maker(object): """ Base class for classes that change Eta complex bonding """
[docs] def __init__(self, struct): """ Create a Maker instance :type struct: `schrodinger.structure.Structure` :param struct: The structure to operate on """ self.struct = struct self.metals = { self.struct.atom[x] for x in analyze.evaluate_asl(struct, 'metals') }
[docs]class PerAtomMaker(Maker): """ Class that changes Eta bonding to have one bond per atom """
[docs] def __init__(self, dummies, *args, **kwargs): """ Create a PerAtomMaker instance :type dummies: list :param dummies: The atom objects of each dummy atom in the structure """ super().__init__(*args, **kwargs) self.dummies = dummies
[docs] def convert(self): """ Convert the complex to have one bond per atom :raise ValueError: Too many metal-to-ligand bonds """ for dummy in self.dummies: metal = None others = [] # Find the metal atom the dummy is bonded to and all its other # neighbors for atom in dummy.bonded_atoms: if atom in self.metals: metal = atom else: others.append(atom) if not metal: continue # Add a bond from each real atom to the metal. We'll fix bond orders # later for atom in others: if metal.bond_total == mm.MMCT_MAXBOND: msg = ( f'The maximum number of allowed bonds, {mm.MMCT_MAXBOND}, ' 'to the metal has been exceeded.') raise ValueError(msg) self.struct.addBond(atom, metal, 0) self.struct.deleteAtoms([dummy]) # Fix the bond orders for metal in self.metals: buildcomplex.fix_metal_bond_orders(self.struct, metal.index)
[docs]class DummyMaker(buildcomplex.EtaFindingMixin, Maker): """ Class that changes Eta bonding to have one dummy bond per Eta ligand """
[docs] def createDummies(self): """ Create the dummy atoms and fix bonding """ bonds_to_make = [] for group in self.eta_groups: alist = [x.index for x in group.atoms] # Put a dummy atom at the center of the haptic ligand centroid = transform.get_centroid(self.struct, atom_list=alist)[:3] dummy = self.struct.addAtom('DU', *centroid) bonds_to_make.append( [dummy, group.metal, structure.BondType.Single]) # Bond all atoms to the dummy and delete their bond to the metal for atom in group.atoms: bonds_to_make.append([dummy, atom, structure.BondType.Zero]) try: # Redirect any failure messsages because we catch the # exception they raise with ioredirect.IOSilence(): self.struct.deleteBond(atom, group.metal) except Exception as err: if 'are not bound' in str(err): # This is part of a second eta group that already # deleted the bond raise ValueError('This structure contains a haptic ' 'system that is too complicated for a ' 'simple style toggle - no changes ' 'have been made.') self.struct.addBonds(bonds_to_make)
[docs] def convert(self): """ Convert Eta bonds in the structure to dummy-style """ self.findEtaGroups(dummy_style=False) self.createDummies()
[docs]def get_dummy_atoms(struct): """ Find all dummy atoms in the structure :type struct: `schrodinger.structure.Structure` :param struct: The structure containing the dummy atoms :rtype: list :return: The atom objects of the dummy atoms in the structure """ return [x for x in struct.atom if x.atomic_number in DUMMY_ATOMIC_NUMBERS]
[docs]def toggle_structure(struct, out_rep=None): """ Toggle the Eta bonding between dummy style and per atom style :type struct: `schrodinger.structure.Structure` :param struct: The structure to toggle the style of :type out_rep: str or None :param out_rep: if None then the conversion is to the opposite of the given representation, eta to centroid or centroid to eta, if a string then must be either module constant parserutils.CENTROID or parserutils.ETA in which case the conversion will always provide an output representation of the given type :rtype: bool :return: True if the style has changed False otherwise """ assert out_rep in [None, parserutils.CENTROID, parserutils.ETA] dummies = get_dummy_atoms(struct) if dummies: if out_rep == parserutils.CENTROID: return False maker = PerAtomMaker(dummies, struct) else: if out_rep == parserutils.ETA: return False maker = DummyMaker(struct) maker.convert() return True