Source code for schrodinger.application.scaffold_enumeration.posvarbond
'''
Implements "position variant bond" enumeration (see ENUM-252).
'''
import collections
import itertools
import rdkit.Chem
from schrodinger.utils import log
from . import common
logger = log.get_output_logger(__name__)
#------------------------------------------------------------------------------#
# * "multicenter S-group" (MCG) is a pair of "group" and "center";
#   "group" is a set of atoms, "center" is a (dummy) atom with
#   exactly one bond ("center" cannot be a member of the "group");
#
# * multiple MCGs can be associated with a molecule; "center" of any
#   MCG may not belong to a "group" in any other MCG; "groups" from
#   different MCGs may overlap
#
# * "center" atom may not be bonded to any of the "group" atoms in its
#   MCG or "center" atom of any other MCG
#
# * "position variant bond" (PVB) is defined by "multicenter S-group": in a
#   specific PVB realization, the single bond of the MCG "center" is
#   re-connected from the "center" (dummy) to one of the "group" atoms,
#   and the "center" itself is removed
MulticenterSgroup = collections.namedtuple('MulticenterSgroup',
                                           ['atoms', 'center'])
#------------------------------------------------------------------------------#
def _is_multicenter_sgroup(g):
    '''
    :param g: MRV S-group as dictionary.
    :type g: dict
    '''
    return g.get('role', '') == 'MulticenterSgroup'
#------------------------------------------------------------------------------#
def _drop_multicenter_sgroups(mol):
    '''
    Remove multicenter S-groups from the S-groups associated with `mol`.
    '''
    common.set_sgroups(
        mol,
        [g for g in common.get_sgroups(mol) if not _is_multicenter_sgroup(g)])
#------------------------------------------------------------------------------#
def _collect_posvarbonds(mol):
    '''
    Collects "position variant bond" specs from the molecule.
    :return: List of "multicenter S-groups".
    :rtype: list(MulticenterSgroup)
    '''
    a2idx = common.get_atom_id_map(mol)
    outcome = []
    for g in common.get_sgroups(mol):
        if _is_multicenter_sgroup(g):
            try:
                atom_ids = set(g['atomRefs'].split())
                center_id = g['center']
            except KeyError:
                logger.warning('incomplete multicenter S-group: %s', g)
                continue
            try:
                atoms = sorted(a2idx[a] for a in atom_ids)
                center = a2idx[center_id]
            except KeyError:
                logger.warning(
                    'undefined atom id(s) in multicenter S-group: %s', g)
                continue
            outcome.append(MulticenterSgroup(atoms, center))
    return outcome
#------------------------------------------------------------------------------#
def _validate_posvarbonds(mol, posvarbonds):
    '''
    Validates "position variant bonds" (defined by "multicenter S-groups").
    :param mol: Molecule.
    :type mol: rdkit.Chem.Mol
    :param posvarbonds: List of multicenter S-groups.
    :type posvarbonds: list(MulticenterSgroup)
    :return: Validation success and error message.
    :rtype: (bool, str)
    '''
    centers = set()
    for pvb in posvarbonds:
        if pvb.center in centers:
            return False, 'shared "center" is not allowed'
        else:
            centers.add(pvb.center)
    num_atoms = mol.GetNumAtoms()
    for pvb in posvarbonds:
        if not pvb.atoms:
            return False, 'no atoms'
        if len(set(pvb.atoms)) != len(pvb.atoms):
            return False, 'duplicate atoms'
        for i in itertools.chain(pvb.atoms, (pvb.center,)):
            if not (i >= 0 and i < num_atoms):
                return False, 'invalid atom indices'
        if set(pvb.atoms) & centers:
            return False, 'nested multicenter S-groups are not allowed'
        center_atom = mol.GetAtomWithIdx(pvb.center)
        if center_atom.GetDegree() != 1:
            return False, 'center of a multicenter S-group is not of degree one'
        other_atom = center_atom.GetBonds()[0].GetOtherAtomIdx(pvb.center)
        if other_atom in pvb.atoms:
            return False, 'self-bonded position variant bond'
        if other_atom in centers:
            return (False, 'position variant bond between multicenter '
                    'S-groups is not allowed')
    return True, ''
#------------------------------------------------------------------------------#
def _apply_posvarbond(rwmol, pvbond, i):
    '''
    Creates i-th realization of the `pvbond`. Does not remove the
    "multicenter S-group" center atom.
    :param rwmol: Molecule.
    :type rwmol: rdkit.Chem.RWMol
    :param pvbond: Position variant bond metadata.
    :type pvbond: MulticenterSgroup
    :param i: Index of the desired realization (0 <= i < len(pvbond.atoms)).
    :type i: int
    '''
    center_atom = rwmol.GetAtomWithIdx(pvbond.center)
    center_bond = center_atom.GetBonds()[0]
    atom1_idx = center_bond.GetOtherAtomIdx(pvbond.center)
    atom2_idx = pvbond.atoms[i]
    num_bonds = rwmol.AddBond(atom1_idx, atom2_idx)
    rwmol.ReplaceBond(num_bonds - 1, center_bond)
#------------------------------------------------------------------------------#
[docs]class PosVarBondEnumerable(common.EnumerableMixin):
[docs]    def __init__(self, mol, pvbonds=None):
        '''
        :param mol: RDKit molecule.
        :type mol: rdkit.Chem.Mol
        :param pvbonds: List of position variant bonds.
        :type pvbonds: list(MulticenterSgroup)
        '''
        if pvbonds is None:
            pvbonds = _collect_posvarbonds(mol)
        valid, msg = _validate_posvarbonds(mol, pvbonds)
        if not valid:
            raise ValueError('PosVarBondEnumerable: ' + msg)
        self.pvbonds = sorted(pvbonds, key=lambda pvb: pvb.center, reverse=True)
        self.mol = mol 
[docs]    def getExtents(self):
        return [len(b.atoms) for b in self.pvbonds] 
[docs]    def getRealization(self, idx):
        '''
        :param idx: "Index" of a realization.
        :type idx: iterable over int
        :return: RDKit molecule without "position variant bonds".
        :rtype: rdkit.Chem.Mol
        '''
        if self.pvbonds:
            rwmol = rdkit.Chem.RWMol(self.mol)
            _drop_multicenter_sgroups(rwmol)
            for (i, pvb) in zip(idx, self.pvbonds):
                _apply_posvarbond(rwmol, pvb, i)
            # remove "centers"
            for pvb in self.pvbonds:
                rwmol.RemoveAtom(pvb.center)
            return rwmol.GetMol()
        else:
            return self.mol  
#------------------------------------------------------------------------------#