def apply(self, mol: RWMol) -> RWMol: num_atoms = mol.GetNumAtoms() if self.detach: for i, a in enumerate(mol.GetAtoms()): m = a.GetAtomMapNum() if m == self.atom_map2: for bond in a.GetBonds(): mol.RemoveBond(bond.GetBeginAtomIdx(), bond.GetEndAtomIdx()) mol.RemoveAtom(i) num_atoms -= 1 break atom_ind = get_atom_ind(mol, self.atom_map1) b_type = rdchem.BondType.values[self.bond_type] b_stereo = rdchem.BondStereo.values[self.bond_stereo] old_atom = mol.GetAtomWithIdx(atom_ind) if old_atom.HasProp('in_reactant'): self.new_a.SetBoolProp('in_reactant', old_atom.GetBoolProp('in_reactant')) if old_atom.HasProp('mol_id'): self.new_a.SetIntProp('mol_id', old_atom.GetIntProp('mol_id')) mol.AddAtom(self.new_a) new_atom_ind = num_atoms bond_ind = mol.AddBond(atom_ind, new_atom_ind, order=b_type) - 1 new_bond = mol.GetBondWithIdx(bond_ind) new_bond.SetStereo(b_stereo) new_bond.SetBoolProp('is_edited', True) return mol
def _delete_marked(self, mol: Chem.RWMol): morituri = list( reversed( mol.GetAtomsMatchingQuery( Chem.rdqueries.HasPropQueryAtom('DELETE')))) for atom in morituri: mol.RemoveAtom(atom.GetIdx())
def remove_exocyclic_attachments(mol): """ Remove exocyclic and exolinker attachments from a molecule. Parameters ---------- mol : rdkit.Chem.rdchem.Mol Returns ------- rdkit.Chem.rdchem.Mol Molecule with exocyclic/exolinker attachments removed. """ edit = RWMol(mol) remove_atoms = set() for atom in edit.GetAtoms(): degree = atom.GetDegree() if degree == 1: bond = atom.GetBonds()[0] if bond.GetBondTypeAsDouble() == 2.0: nbr = bond.GetOtherAtom(atom) hcount = nbr.GetTotalNumHs() nbr.SetNumExplicitHs(hcount + 2) nbr.SetNoImplicit(True) remove_atoms.add(atom.GetIdx()) for aix in sorted(remove_atoms, reverse=True): edit.RemoveAtom(aix) rdmolops.AssignRadicals(edit) GetSymmSSSR(edit) return edit.GetMol()
def _GetSMILES(mol, idxlist): tmol = mol.__copy__() #(t)emporary tmol = RWMol(tmol) for AtomIdx in xrange(tmol.GetNumAtoms() - 1, -1, -1): if AtomIdx not in idxlist: tmol.RemoveAtom(AtomIdx) return Chem.MolToSmiles(tmol)
def depict(self, filename=None, ipython=False): from rdkit.Chem.Draw import IPythonConsole from rdkit.Chem.Draw import MolToImage from rdkit.Chem.Draw import rdMolDraw2D from rdkit.Chem.AllChem import EmbedMolecule from IPython.display import SVG from rdkit.Chem import RWMol, MolFromSmiles, Atom, BondType, ChiralType _ = MolFromSmiles('C') rmol = RWMol(_) dict_old_new_idx = {} n = 1 for a in self.atoms: old_idx = a.GetIdx() rmol.AddAtom(a) dict_old_new_idx[old_idx] = n n += 1 for a in self.enviroments: old_idx = a.GetIdx() a.SetChiralTag(ChiralType.CHI_UNSPECIFIED) a.SetIsAromatic(0) rmol.AddAtom(a) dict_old_new_idx[old_idx] = n n += 1 for b in self.Bonds: rmol.AddBond(dict_old_new_idx[b.GetBeginAtomIdx()], dict_old_new_idx[b.GetEndAtomIdx()], b.GetBondType()) for b in self.bondsenvironments: rmol.AddBond(dict_old_new_idx[b.GetBeginAtomIdx()], dict_old_new_idx[b.GetEndAtomIdx()], b.GetBondType()) rmol.RemoveAtom(0) EmbedMolecule(rmol) drawer = rdMolDraw2D.MolDraw2DSVG(400, 200) drawer.DrawMolecule(rmol) drawer.FinishDrawing() svg = drawer.GetDrawingText() if filename != None: f = open(filename, 'w') f.write(svg) f.close() if ipython: svg = svg.replace('svg:', '') return SVG(svg) else: return None
def _prevent_two_bonds_on_dummy(self, mol: Chem.RWMol): for atom in mol.GetAtoms(): if atom.GetSymbol() != '*': pass elif len(atom.GetNeighbors()) <= 1: pass elif len(atom.GetNeighbors()) >= 2: neighs = atom.GetNeighbors() for second in neighs[1:]: self._absorb(mol, atom.GetIdx(), second.GetIdx()) mol.RemoveAtom(second.GetIdx()) self._prevent_two_bonds_on_dummy(mol) break
def _prevent_bridge_ring(self, mol: Chem.RWMol, examplar: Tuple[int]): ## This is really # examplar is ring ringatoms = self._get_ring_info(mol) #GetRingInfo().AtomRings() ringatoms = [ring for ring in ringatoms if set(ring).intersection(examplar)] ring_idx = list(range(len(ringatoms))) shared_count = {} for ra, rb in itertools.combinations(ring_idx, r=2): shared_count[(ra, rb)] = len(set(ringatoms[ra]).intersection(set(ringatoms[rb]))) if len(shared_count) == 0: return mol ra, rb = list(shared_count.keys())[0] shared = list(set(ringatoms[ra]).intersection(ringatoms[rb])) pairs = [(a, b) for a, b in itertools.combinations(shared, r=2) if mol.GetBondBetweenAtoms(a, b) is not None] c = Counter([i for pair in pairs for i in pair]) ring_A, ring_B = ringatoms[ra], ringatoms[rb] small, big = sorted([ring_A, ring_B], key=lambda ring: len(ring)) inners = [i for i in c if c[i] > 1] x = list(set(shared).difference(inners)) if len(x) != 2: log.critical(f'This is impossible. {ringatoms} share {shared} with {inners} in the inside and {x} on the edge?') return mol a, b = x if len(big) > 6: log.warning(f'Removing {len(inners)} bridging atoms and replacing with fused ring') # bond the vertices bt = Chem.BondType.SINGLE # ??? if mol.GetBondBetweenAtoms(a, b) is None: mol.AddBond(a, b, bt) else: log.warning('This is really odd! Why is there a bond already??') # remove the middle atoms. for i in sorted(inners, reverse=True): mol.RemoveAtom(i) else: log.warning(f'Shriking the smaller ring to change from bridged to fused.') # get the neighbour in the small atom to a vertex. neighs = [neigh for neigh in mol.GetAtomWithIdx(a).GetNeighbors() if neigh.GetIdx() not in shared and neigh.GetIdx() in small] neigh = sorted(neighs, key=lambda atom: atom.GetSymbol() != 'C')[0] bt = mol.GetBondBetweenAtoms(a, neigh.GetIdx()).GetBondType() mol.RemoveBond(a, neigh.GetIdx()) new_neigh = [neigh for neigh in mol.GetAtomWithIdx(a).GetNeighbors() if neigh.GetIdx() in shared][0] mol.AddBond(neigh.GetIdx(), new_neigh.GetIdx(), bt) neigh.SetBoolProp('_Novel', True) new_neigh.SetBoolProp('_Novel', True) mol.GetAtomWithIdx(a).SetBoolProp('_Novel', True) return mol
def complement_reaction(rxn_template): if rxn_template.GetNumProductTemplates() != 1: print("[ERROR] A reaction template has only one product template.") sys.exit(1) pro = rxn_template.GetProductTemplate(0) rw_pro = RWMol(pro) amaps_pro = {a.GetAtomMapNum() for a in pro.GetAtoms()} amaps_rcts = {a.GetAtomMapNum() for rct in rxn_template.GetReactants() for a in rct.GetAtoms()} amaps_not_in_rcts = amaps_pro.intersection(amaps_rcts) for amap in amaps_not_in_rcts: aidx = [a.GetIdx() for a in rw_pro.GetAtoms() if a.GetAtomMapNum() == amap][0] rw_pro.RemoveAtom(aidx) m = rw_pro.GetMol() if '.' in Chem.MolToSmarts(m): return if (m.GetNumAtoms() == 0) or (m.GetNumAtoms() == 1 and m.GetAtomWithIdx(0).GetSymbol() in {"*", None}): return rxn_template.AddReactantTemplate(m)
def fragment(self, scaffold): """Fragment a scaffold into its next set of Murcko fragments. Parameters ---------- scaffold : scaffoldgraph.core.Scaffold Child scaffold to be fragmented. Returns ------- list A list of parent scaffolds representing the next hierarchy. """ parents = [] rings = scaffold.ring_systems # ring system information info = scaffold.rings.info if rings.count == 1: return [] for rix, ring in enumerate(rings): edit = RWMol(scaffold.mol) remove_atoms = set() for index, atom in zip(ring.aix, ring.atoms): if info.NumAtomRings(index) == 1 or any( [not b.IsInRing() for b in atom.GetBonds()]): if atom.GetDegree() > 2: # Evoke linker collection collect_linker_atoms(edit.GetAtomWithIdx(index), remove_atoms) else: remove_atoms.add(index) else: remove_atoms.add(index) for aix in sorted(remove_atoms, reverse=True): edit.RemoveAtom(aix) for parent in get_scaffold_frags(edit): if parent.ring_systems.count == len(rings) - 1: parent.removed_ring_idx = rix parents.append(parent) return parents
def _minimize_rings(mol): """Private: Minimize rings in a scaffold. In this process, all remaining vertices/atoms of degree two are removed by performing an edge merging operation. The only exception being when both vertices neighbours are connected (i.e. we have a triangle), when edge merging would lead to the loss of a cycle. The result is a minimum cycle topological representation of the original molecule. This function is used in the computation of ring topology scaffolds (Oprea). If a ring contains a non-carbon atom, this atom is maintained. Neighbouring ring atoms which are of the same type are merged together into a single atom of the corresponding type. Parameters ---------- mol : rdkit.Chem.rdchem.Mol Returns ------- rdkit.Chem.rdchem.RWMol Minimum cycle topological graph. """ edit = RWMol(mol) remove_atoms = set() for atom in edit.GetAtoms(): if atom.GetDegree() == 2: n1, n2 = atom.GetNeighbors() n1_idx, n2_idx = n1.GetIdx(), n2.GetIdx() connected = edit.GetBondBetweenAtoms(n1_idx, n2_idx) if not connected and (n1.GetAtomicNum() == atom.GetAtomicNum() or n2.GetAtomicNum() == atom.GetAtomicNum()): a_idx = atom.GetIdx() edit.RemoveBond(n1_idx, a_idx) edit.RemoveBond(n2_idx, a_idx) edit.AddBond(n1_idx, n2_idx, BondType.SINGLE) remove_atoms.add(a_idx) for a_idx in sorted(remove_atoms, reverse=True): edit.RemoveAtom(a_idx) return edit
def fragment(self, scaffold): """Fragment a scaffold into its next set of murcko fragments. This fragmenter will not dissect fused ring systems. Parameters ---------- scaffold (sg.core.Scaffold): scaffold to be fragmented. Returns ------- parents (list): a list of the next scaffold parents. """ parents = [] rings = scaffold.ring_systems # ring system information info = scaffold.rings.info if rings.count == 1: return [] for rix, ring in enumerate(rings): edit = RWMol(scaffold.mol) remove_atoms = set() for index, atom in zip(ring.aix, ring.atoms): if info.NumAtomRings(index) == 1: if atom.GetDegree() > 2: # Evoke linker collection collect_linker_atoms(edit.GetAtomWithIdx(index), remove_atoms) else: remove_atoms.add(index) else: remove_atoms.add(index) for aix in sorted(remove_atoms, reverse=True): edit.RemoveAtom(aix) for parent in get_scaffold_frags(edit): if parent.ring_systems.count == len(rings) - 1: parent.removed_ring_idx = rix parents.append(parent) return parents
def _delete_collapsed(self, mol: Chem.RWMol): for a in reversed(range(mol.GetNumAtoms())): if mol.GetAtomWithIdx(a).GetIntProp('_ori_i') == -1: mol.RemoveAtom(a)
def fragment(self, scaffold): """Fragment a scaffold into its next set of Murcko fragments. Parameters ---------- scaffold : scaffoldgraph.core.Scaffold Child scaffold to be fragmented. Returns ------- list A list of parent scaffolds representing the next hierarchy. """ parents = [] # container for parent scaffolds rings = scaffold.rings # ring information for rix, ring in enumerate(rings): # Loop through all rings and remove edit = RWMol(scaffold.mol) # Editable molecule # Collect all removable atoms in the molecule remove_atoms = set() for index, atom in zip(ring.aix, ring.atoms): if rings.info.NumAtomRings(index) == 1: if atom.GetDegree() > 2: # Evoke linker collection collect_linker_atoms(edit.GetAtomWithIdx(index), remove_atoms) else: # Add ring atom to removable set remove_atoms.add(index) else: # Atom is shared between multiple rings correct_atom_props(edit.GetAtomWithIdx(index)) # Collect removable bonds (this needs to be done to prevent the case where when deleting # a ring two atoms belonging to the same bond are also part of separate other rings. # This bond must be broken to prevent an incorrect output) remove_bonds = set() for bix in { x for x in ring.bix if rings.info.NumBondRings(x) == 1 }: bond = edit.GetBondWithIdx(bix) b_x, b_y = bond.GetBeginAtomIdx(), bond.GetEndAtomIdx() if b_x not in remove_atoms and b_y not in remove_atoms: remove_bonds.add((b_x, b_y)) correct_atom_props(edit.GetAtomWithIdx(b_x)) correct_atom_props(edit.GetAtomWithIdx(b_y)) # Scheme 4 (scaffold tree rule) if self.use_scheme_4 is not False and len(ring) == 3: atomic_nums = [a.GetAtomicNum() for a in ring.atoms] if len([a for a in atomic_nums if a != 1 and a != 6]) == 1: shared = { x for x in ring.bix if rings.info.NumBondRings(x) > 1 } if len(shared) == 1: bond = edit.GetBondWithIdx(shared.pop()) bond.SetBondType(BondType.DOUBLE) # Remove collected atoms and bonds for bix in remove_bonds: edit.RemoveBond(*bix) for aix in sorted(remove_atoms, reverse=True): edit.RemoveAtom(aix) # Add new parent scaffolds to parent list for parent in get_scaffold_frags(edit): if parent.rings.count == len(rings) - 1: parent.removed_ring_idx = rix parents.append(parent) return parents