def attach_capping(mol1, mol2): """it is connecting all Nterminals with the desired capping Arguments: mol1 {rdKit mol object} -- first molecule to be connected mol2 {rdKit mol object} -- second molecule to be connected - chosen N-capping Returns: rdKit mol object -- mol1 updated (connected with mol2, one or more) """ count = 0 # detects all the N terminals in mol1 for atom in mol1.GetAtoms(): atom.SetProp('Cterm', 'False') if atom.GetSmarts() == '[N:2]' or atom.GetSmarts( ) == '[NH2:2]' or atom.GetSmarts() == '[NH:2]': count += 1 atom.SetProp('Nterm', 'True') else: atom.SetProp('Nterm', 'False') # detects all the C terminals in mol2 (it should be one) for atom in mol2.GetAtoms(): atom.SetProp('Nterm', 'False') if atom.GetSmarts() == '[C:1]' or atom.GetSmarts() == '[CH:1]': atom.SetProp('Cterm', 'True') else: atom.SetProp('Cterm', 'False') # mol2 is addes to all the N terminal of mol1 for i in range(count): combo = rdmolops.CombineMols(mol1, mol2) Nterm = [] Cterm = [] # saves in two different lists the index of the atoms which has to be connected for atom in combo.GetAtoms(): if atom.GetProp('Nterm') == 'True': Nterm.append(atom.GetIdx()) if atom.GetProp('Cterm') == 'True': Cterm.append(atom.GetIdx()) # creates the amide bond edcombo = rdchem.EditableMol(combo) edcombo.AddBond(Nterm[0], Cterm[0], order=Chem.rdchem.BondType.SINGLE) clippedMol = edcombo.GetMol() # removes tags and lables form the atoms which reacted clippedMol.GetAtomWithIdx(Nterm[0]).SetProp('Nterm', 'False') clippedMol.GetAtomWithIdx(Cterm[0]).SetProp('Cterm', 'False') clippedMol.GetAtomWithIdx(Nterm[0]).SetAtomMapNum(0) clippedMol.GetAtomWithIdx(Cterm[0]).SetAtomMapNum(0) # uptades the 'core' molecule mol1 = clippedMol return mol1
def simply_merge_hits( self, hits: Optional[List[Chem.Mol]] = None, linked: bool = True, ) -> Chem.Mol: """ Recursively stick the hits together and average the positions. This is the monster of automerging, full-merging mapping and partial merging mapping. The latter however uses `partially_blend_hits` first. The hits are not ring-collapsed and -expanded herein. :param hits: optionally give a hit list, else uses the attribute ``.hits``. :param linked: if true the molecules are joined, else they are placed in the same molecule as disconnected fragments. :return: the rdkit.Chem.Mol object that will fill ``.scaffold`` """ if hits is None: hits = sorted(self.hits, key=lambda h: h.GetNumAtoms(), reverse=True) for hit in hits: BondProvenance.set_all_bonds(hit, 'original') self.journal.debug( f"Merging: {[hit.GetProp('_Name') for hit in hits]}") scaffold = Chem.Mol(hits[0]) # first try save_for_later = [] for fragmentanda in hits[1:]: try: scaffold = self.merge_pair(scaffold, fragmentanda) except ConnectionError: save_for_later.append(fragmentanda) # second try join_later = [] for fragmentanda in save_for_later: try: scaffold = self.merge_pair(scaffold, fragmentanda) except ConnectionError: join_later.append(fragmentanda) # join (last ditch) for fragmentanda in join_later: if linked: try: scaffold = self.join_neighboring_mols( scaffold, fragmentanda) except ConnectionError: self.unmatched.append(fragmentanda.GetProp("_Name")) msg = f'Hit {fragmentanda.GetProp("_Name")} has no connections! Skipping!' if self.throw_on_discard: raise ConnectionError(msg) else: warn(msg) else: new_name = self._get_combined_name(scaffold, fragmentanda) scaffold = rdmolops.CombineMols(scaffold, fragmentanda) scaffold.SetProp('_Name', new_name) return scaffold
def merge(self, scaffold: Chem.Mol, fragmentanda: Chem.Mol, anchor_index: int, attachment_details: List[Dict]) -> Chem.Mol: for detail in attachment_details: attachment_index = detail['idx_F'] # fragmentanda attachment_index scaffold_attachment_index = detail['idx_S'] bond_type = detail['type'] f = Chem.FragmentOnBonds(fragmentanda, [ fragmentanda.GetBondBetweenAtoms(anchor_index, attachment_index).GetIdx() ], addDummies=False) frag_split = [] fragmols = Chem.GetMolFrags(f, asMols=True, fragsMolAtomMapping=frag_split, sanitizeFrags=False) if self._debug_draw: print(frag_split) # Get the fragment of interest. ii = 0 for mol_N, indices in enumerate(frag_split): if anchor_index in indices: break ii += len(indices) else: raise Exception frag = fragmols[mol_N] frag_anchor_index = indices.index(anchor_index) if self._debug_draw: self.draw_nicely(frag) combo = Chem.RWMol(rdmolops.CombineMols(scaffold, frag)) scaffold_anchor_index = frag_anchor_index + scaffold.GetNumAtoms() if self._debug_draw: print(scaffold_anchor_index, scaffold_attachment_index, anchor_index, scaffold.GetNumAtoms()) self.draw_nicely(combo) combo.AddBond(scaffold_anchor_index, scaffold_attachment_index, bond_type) Chem.SanitizeMol( combo, sanitizeOps=Chem.rdmolops.SanitizeFlags.SANITIZE_ADJUSTHS + Chem.rdmolops.SanitizeFlags.SANITIZE_SETAROMATICITY, catchErrors=True) if self._debug_draw: self.draw_nicely(combo) scaffold = combo return scaffold
def merge_molecules(molecules): """Helper method to merge two molecules. Parameters ---------- molecules: list List of rdkit molecules Returns ------- merged: rdkit molecule """ from rdkit.Chem import rdmolops if len(molecules) == 0: return None elif len(molecules) == 1: return molecules[0] else: combined = molecules[0] for nextmol in molecules[1:]: combined = rdmolops.CombineMols(combined, nextmol) return combined
def connect_mol(mol1, mol2): """it is connecting all Nterminals of mol1 with the Cterminal of the maximum possible number of mol2s Arguments: mol1 {rdKit mol object} -- first molecule to be connected mol2 {rdKit mol object} -- second molecule to be connected Returns: rdKit mol object -- mol1 updated (connected with mol2, one or more) """ # used internally to recognize a methylated aa: metbond = False # can be set with exclude or allow methylation, # it refers to the possibility of having methylation in the entire GA: methyl = False count = 0 # detects all the N terminals in mol1 for atom in mol1.GetAtoms(): atom.SetProp('Cterm', 'False') atom.SetProp('methyl', 'False') if atom.GetSmarts() == '[N:2]' or atom.GetSmarts( ) == '[NH2:2]' or atom.GetSmarts() == '[NH:2]': count += 1 atom.SetProp('Nterm', 'True') else: atom.SetProp('Nterm', 'False') # detects all the C terminals in mol2 (it should be one) for atom in mol2.GetAtoms(): atom.SetProp('Nterm', 'False') atom.SetProp('methyl', 'False') if atom.GetSmarts() == '[C:1]' or atom.GetSmarts() == '[CH:1]': atom.SetProp('Cterm', 'True') else: atom.SetProp('Cterm', 'False') # mol2 is addes to all the N terminal of mol1 for i in range(count): combo = rdmolops.CombineMols(mol1, mol2) Nterm = [] Cterm = [] # saves in two different lists the index of the atoms which has to be connected for atom in combo.GetAtoms(): if atom.GetProp('Nterm') == 'True': Nterm.append(atom.GetIdx()) if atom.GetProp('Cterm') == 'True': Cterm.append(atom.GetIdx()) # creates the amide bond edcombo = rdchem.EditableMol(combo) edcombo.AddBond(Nterm[0], Cterm[0], order=Chem.rdchem.BondType.SINGLE) edcombo.RemoveAtom(Cterm[0] + 1) clippedMol = edcombo.GetMol() # removes tags and lables form c term atoms which reacted clippedMol.GetAtomWithIdx(Cterm[0]).SetProp('Cterm', 'False') clippedMol.GetAtomWithIdx(Cterm[0]).SetAtomMapNum(0) # methylates amide bond if metbond == True and methyl == True: Nterm = [] Met = [] methyl = rdmolfiles.MolFromSmiles('[C:4]') for atom in methyl.GetAtoms(): atom.SetProp('methyl', 'True') atom.SetProp('Nterm', 'False') atom.SetProp('Cterm', 'False') metcombo = rdmolops.CombineMols(clippedMol, methyl) for atom in metcombo.GetAtoms(): if atom.GetProp('Nterm') == 'True': Nterm.append(atom.GetIdx()) if atom.GetProp('methyl') == 'True': Met.append(atom.GetIdx()) metedcombo = rdchem.EditableMol(metcombo) metedcombo.AddBond(Nterm[0], Met[0], order=Chem.rdchem.BondType.SINGLE) clippedMol = metedcombo.GetMol() clippedMol.GetAtomWithIdx(Met[0]).SetProp('methyl', 'False') clippedMol.GetAtomWithIdx(Met[0]).SetAtomMapNum(0) # removes tags and lables form the atoms which reacted clippedMol.GetAtomWithIdx(Nterm[0]).SetProp('Nterm', 'False') clippedMol.GetAtomWithIdx(Nterm[0]).SetAtomMapNum(0) # uptades the 'core' molecule mol1 = clippedMol metbond = False return mol1
def _merge_part(self, scaffold: Chem.Mol, fragmentanda: Chem.Mol, anchor_index: int, attachment_details: List[Dict], other_attachments: List[int], other_attachment_details: List[List[Dict]]) -> Chem.Mol: """ This does the messy work for merge_pair. :param scaffold: :param fragmentanda: :param anchor_index: :param attachment_details: :param other_attachments: :param other_attachment_details: :return: """ # get bit to add. bonds_to_frag = [] for detail in attachment_details: attachment_index = detail['idx_F'] # fragmentanda attachment_index bonds_to_frag += [ fragmentanda.GetBondBetweenAtoms(anchor_index, attachment_index).GetIdx() ] bonds_to_frag += [ fragmentanda.GetBondBetweenAtoms(oi, oad[0]['idx_F']).GetIdx() for oi, oad in zip(other_attachments, other_attachment_details) ] if self._debug_draw and other_attachments: print('ring!', other_attachments) print('ring!', other_attachment_details) f = Chem.FragmentOnBonds(fragmentanda, bonds_to_frag, addDummies=False) frag_split = [] fragmols = Chem.GetMolFrags(f, asMols=True, fragsMolAtomMapping=frag_split, sanitizeFrags=False) if self._debug_draw: print('Fragment splits') print(frag_split) # Get the fragment of interest. ii = 0 for mol_N, indices in enumerate(frag_split): if anchor_index in indices: break ii += len(indices) else: raise Exception frag = fragmols[mol_N] frag_anchor_index = indices.index(anchor_index) # pre-emptively fix atom ori_i # offset collapsed to avoid clashes. self._offset_collapsed_ring(frag) self._offset_origins(frag) # Experimental code. # TODO: finish! # frag_atom = frag.GetAtomWithIdx(frag_anchor_index) # old2future = {atom.GetIntProp('_ori_i'): atom.GetIdx() + scaffold.GetNumAtoms() for atom in frag.GetAtoms()} # del old2future[-1] # does nothing but nice to double tap # if frag_atom.GetIntProp('_ori_i') == -1: #damn. # for absent in self._get_mystery_ori_i(frag): # old2future[absent] = scaffold_attachment_index # self._renumber_original_indices(frag, old2future) if self._debug_draw: print('Fragment to add') self.draw_nicely(frag) combo = Chem.RWMol(rdmolops.CombineMols(scaffold, frag)) scaffold_anchor_index = frag_anchor_index + scaffold.GetNumAtoms() if self._debug_draw: print('Pre-merger') print(scaffold_anchor_index, attachment_details, anchor_index, scaffold.GetNumAtoms()) self.draw_nicely(combo) for detail in attachment_details: attachment_index = detail['idx_F'] # fragmentanda attachment_index scaffold_attachment_index = detail['idx_S'] bond_type = detail['type'] combo.AddBond(scaffold_anchor_index, scaffold_attachment_index, bond_type) for oi, oad in zip(other_attachments, other_attachment_details): bond_type = oad[0]['type'] scaffold_attachment_index = oad[0]['idx_S'] scaffold_anchor_index = indices.index(oi) + scaffold.GetNumAtoms() combo.AddBond(scaffold_anchor_index, scaffold_attachment_index, bond_type) if self._debug_draw: print( f"Added additional {bond_type.name} bond between {scaffold_attachment_index} and {scaffold_anchor_index} " + \ f"(formerly {indices.index(oi)})") Chem.SanitizeMol( combo, sanitizeOps=Chem.rdmolops.SanitizeFlags.SANITIZE_ADJUSTHS + Chem.rdmolops.SanitizeFlags.SANITIZE_SETAROMATICITY, catchErrors=True) if self._debug_draw: print('Merged') self.draw_nicely(combo) self._prevent_two_bonds_on_dummy(combo) scaffold = combo.GetMol() return scaffold
def _merge_part(self, scaffold: Chem.Mol, fragmentanda: Chem.Mol, anchor_index: int, attachment_details: List[Dict], other_attachments: List[int], other_attachment_details: List[List[Dict]]) -> Chem.Mol: """ This does the messy work for merge_pair. :param scaffold: the Chem.Mol molecule onto whose copy the fragmentanda Chem.Mol gets added :param fragmentanda: The other Chem.Mol molecule :param anchor_index: the fragment-to-added's internal atom that attaches (hit indexed) :param attachment_details: see `_pre_fragment_pairs` or example below fo an entry :type attachment_details: List[Dict] :param other_attachments: :param other_attachment_details: :return: a new Chem.Mol molecule Details object example: [{'idx': 5, 'type': rdkit.Chem.rdchem.BondType.SINGLE, 'idx_F': 5, # fragmentanda index 'idx_S': 1 # scaffold index }], ...} """ # get bit to add. bonds_to_frag = [] for detail in attachment_details: attachment_index = detail['idx_F'] # fragmentanda attachment_index bonds_to_frag += [ fragmentanda.GetBondBetweenAtoms(anchor_index, attachment_index).GetIdx() ] bonds_to_frag += [ fragmentanda.GetBondBetweenAtoms(oi, oad[0]['idx_F']).GetIdx() for oi, oad in zip(other_attachments, other_attachment_details) ] f = Chem.FragmentOnBonds(fragmentanda, bonds_to_frag, addDummies=False) frag_split = [] fragmols = Chem.GetMolFrags(f, asMols=True, fragsMolAtomMapping=frag_split, sanitizeFrags=False) # Get the fragment of interest. ii = 0 for mol_N, indices in enumerate(frag_split): if anchor_index in indices: break ii += len(indices) else: raise Exception frag = fragmols[mol_N] frag_anchor_index = indices.index(anchor_index) # pre-emptively fix atom ori_i # offset collapsed to avoid clashes. self.offset(frag) # Experimental code. # TODO: finish! # frag_atom = frag.GetAtomWithIdx(frag_anchor_index) # old2future = {atom.GetIntProp('_ori_i'): atom.GetIdx() + scaffold.GetNumAtoms() for atom in frag.GetAtoms()} # del old2future[-1] # does nothing but nice to double tap # if frag_atom.GetIntProp('_ori_i') == -1: #damn. # for absent in self._get_mystery_ori_i(frag): # old2future[absent] = scaffold_attachment_index # self._renumber_original_indices(frag, old2future) combo = Chem.RWMol(rdmolops.CombineMols(scaffold, frag)) scaffold_anchor_index = frag_anchor_index + scaffold.GetNumAtoms() for detail in attachment_details: # scaffold_anchor_index : atom index in scaffold that needs to be added to scaffold_attachment_index # but was originally attached to attachment_index in fragmentanda. # the latter is not kept. attachment_index = detail['idx_F'] # fragmentanda attachment_index scaffold_attachment_index = detail[ 'idx_S'] # scaffold attachment index bond_type = detail['type'] combo.AddBond(scaffold_anchor_index, scaffold_attachment_index, bond_type) new_bond = combo.GetBondBetweenAtoms(scaffold_anchor_index, scaffold_attachment_index) # BondProvenance.set_bond(new_bond, '???') # self.transfer_ring_data(fragmentanda.GetAtomWithIdx(attachment_index), # combo.GetAtomWithIdx(scaffold_anchor_index)) for oi, oad in zip(other_attachments, other_attachment_details): bond_type = oad[0]['type'] scaffold_attachment_index = oad[0]['idx_S'] scaffold_anchor_index = indices.index(oi) + scaffold.GetNumAtoms() combo.AddBond(scaffold_anchor_index, scaffold_attachment_index, bond_type) new_bond = combo.GetBondBetweenAtoms(scaffold_anchor_index, scaffold_attachment_index) # BondProvenance.set_bond(new_bond, '???') Chem.SanitizeMol( combo, sanitizeOps=Chem.rdmolops.SanitizeFlags.SANITIZE_ADJUSTHS + Chem.rdmolops.SanitizeFlags.SANITIZE_SETAROMATICITY, catchErrors=True) self._prevent_two_bonds_on_dummy(combo) scaffold = combo.GetMol() return scaffold
def merge_molecules(ligand, protein): """Helper method to merge ligand and protein molecules.""" from rdkit.Chem import rdmolops return rdmolops.CombineMols(ligand, protein)