def _prevent_two_bonds_on_dummy(self, mol: Chem.RWMol): """ The case '*(C)C' is seen legitimately in some warheads... but in most cases these are not. :param mol: :return: """ for atom in mol.GetAtoms(): if atom.GetSymbol() != '*': pass elif len(atom.GetNeighbors()) <= 1: pass elif len(atom.GetNeighbors()) >= 2: self.journal.info( f'Dummy atom (idx={atom.GetIdx()}) has {len(atom.GetNeighbors())} bonds!' ) neighs = atom.GetNeighbors() first = neighs[0] for second in neighs[1:]: rejected = second.GetIdx( ) # that will be absorbed (deleted) keeper = first.GetIdx() # that absorbs (kept) self._copy_bonding(mol, keeper, rejected) self._mark_for_deletion(mol, rejected) self._delete_marked(mol) return self._prevent_two_bonds_on_dummy(mol)
def join_overclose(self, mol: Chem.RWMol, to_check, cutoff=2.2): # was 1.8 """ Cutoff is adapted to element. :param mol: :param to_check: list of atoms indices that need joining (but not to each other) :param cutoff: CC bond :return: """ pt = Chem.GetPeriodicTable() dm = Chem.Get3DDistanceMatrix(mol) for i in to_check: atom_i = mol.GetAtomWithIdx(i) for j, atom_j in enumerate(mol.GetAtoms()): # calculate cutoff if not C-C if atom_i.GetSymbol() == '*' or atom_j.GetSymbol() == '*': ij_cutoff = cutoff elif atom_i.GetSymbol() == 'C' and atom_j.GetSymbol() == 'C': ij_cutoff = cutoff else: ij_cutoff = cutoff - 1.36 + sum([pt.GetRcovalent(atom.GetAtomicNum()) for atom in (atom_i, atom_j)]) # determine if to join if i == j or j in to_check: continue elif dm[i, j] > ij_cutoff: continue else: self._add_bond_if_possible(mol, atom_i, atom_j)
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 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 _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 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 _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