matteoferla/Fragmenstein

View on GitHub
fragmenstein/monster/_combine.py

Summary

Maintainability
A
35 mins
Test Coverage
########################################################################################################################

__doc__ = \
    """
Combine = merge/join
    """

########################################################################################################################

from rdkit import Chem
from rdkit.Chem import AllChem, rdmolops

from ._collapse_ring import _MonsterRing
from ._base import _MonsterBase
from ._communal import _MonsterCommunal
from ._merge import _MonsterMerge
from molecular_rectifier import Rectifier
from ..error import DistanceError, RectificationError, FragmensteinError


########################################################################################################################

class _MonsterCombine(_MonsterRing, _MonsterMerge):

    def combine(self, keep_all: bool = True, collapse_rings: bool = True, joining_cutoff: int = 5):
        """
        Merge/links the hits. (Main entrypoint)

        :param keep_all:
        :param collapse_rings:
        :param joining_cutoff:
        :return:
        """
        # blanking
        self.unmatched = []
        self.modifications = {}
        # The following override class declared attributes.
        self.joining_cutoff = joining_cutoff
        self.throw_on_discard = keep_all
        # merge!
        if collapse_rings:
            col_hits = self.collapse_mols(self.hits)
            self.keep_copies(col_hits, 'Collapsed hits')
        else:
            col_hits = self.hits
        self.positioned_mol = self.simply_merge_hits(col_hits, linked=False)
        # remove props
        *map(self.positioned_mol.ClearProp, self.positioned_mol.GetPropNames()),  # noqa
        self.positioned_mol.SetProp('_Name', '-'.join([h.GetProp('_Name') for h in self.hits]))
        self.keep_copy(self.positioned_mol, 'merged template')
        ## Discard can happen for other reasons than disconnect
        if self.throw_on_discard and len(self.unmatched):
            raise DistanceError(hits=self.unmatched, distance=self.joining_cutoff)
        # expand and fix
        self.journal.debug(f'Merged')
        if collapse_rings:
            self.positioned_mol = self.expand_ring(self.positioned_mol)
        # bonded_as_original=False no longer needed.
        self.keep_copy(self.positioned_mol, 'expanded')
        self._join_internally(self.positioned_mol)  # will call `join_neighboring_mols` on any frags
        self.journal.debug(f'Expanded')
        try:
            self.rectify()
        except Chem.AtomValenceException:
            self.journal.info('Ring expansion while trying to appease the bonding caused an issue. Rolling back.')
            mol = Chem.RWMol(
                self.modifications['Rings expanded and original bonding restored'])  # not the novel bonding
            self._delete_collapsed(mol)
            self._detriangulate(mol)
            self.positioned_mol = self._emergency_joining(mol)
            self.rectify()
        except RecursionError:
            self.journal.critical(f'Recursion limit in rectifier')
            raise RectificationError(f'Recursion limit in rectifier', mol=self.positioned_mol)
        self.journal.debug(f'Rectified')
        return self

    def rectify(self):
        Rectifier.log = self.journal
        recto = Rectifier(self.positioned_mol)
        try:
            recto.fix()
        except FragmensteinError:
            self.journal.critical(f'This really odd cornercase: Rectifier broke the mol.')
            mol = self._emergency_joining(recto.mol)
            recto = Rectifier(mol)
            recto.fix()
        frags = AllChem.GetMolFrags(recto.mol, asMols=True)
        if len(frags) == 1:
            # all good
            self.positioned_mol = recto.mol
        else:  # the molecule is still nasty. Getting largest.
            self.positioned_mol = sorted(frags, key=lambda mol: mol.GetNumAtoms(), reverse=True)[0]
        Chem.rdmolops.AssignStereochemistryFrom3D(self.positioned_mol)
        self.keep_copies(recto.modifications, 'rectified')