def _compute_fragment_join( mol, fragment, mol_atom_count, bond_between_rings=True, asMols=True, ): """List all posibilities of where a fragment can be attached to a mol""" fragment = copy.copy( fragment ) # need to copy the fragment copy is faster than all the other methods with dm.without_rdkit_log(): combined = Chem.CombineMols(mol, fragment) for i1 in range(mol.GetNumAtoms()): a1 = combined.GetAtomWithIdx(i1) if a1.GetImplicitValence() == 0: continue for i2 in range(fragment.GetNumAtoms()): i2 += mol_atom_count a2 = combined.GetAtomWithIdx(i2) if a2.GetImplicitValence() == 0: continue # no bond between atoms already in rings if not bond_between_rings and a1.IsInRing() and a2.IsInRing(): continue # no bond to form large rings else: possibilities = _all_atom_join(combined, a1, a2) for x in possibilities: x = dm.sanitize_mol(x) if x is not None: if not asMols: x = dm.to_smiles(x) yield x
def _all_atom_join(mol, a1, a2): """Join two atoms (a1, a2) in a molecule in all possible valid manner""" new_mols = [] with dm.without_rdkit_log(): try: Chem.Kekulize(mol, clearAromaticFlags=True) except: pass v1, v2 = a1.GetImplicitValence(), a2.GetImplicitValence() bond = mol.GetBondBetweenAtoms(a1.GetIdx(), a2.GetIdx()) if bond is None: if v1 > 0 and v2 > 0: new_mols.append(add_bond_between(mol, a1, a2, dm.SINGLE_BOND)) if v1 > 1 and v2 > 1: new_mols.append(add_bond_between(mol, a1, a2, dm.DOUBLE_BOND)) if v1 > 2 and v2 > 2: new_mols.append(add_bond_between(mol, a1, a2, dm.TRIPLE_BOND)) elif bond.GetBondType() == dm.SINGLE_BOND: if v1 > 0 and v2 > 0: new_mols.append(update_bond(mol, bond, dm.DOUBLE_BOND)) if v1 > 1 and v2 > 1: new_mols.append(update_bond(mol, bond, dm.TRIPLE_BOND)) elif bond.GetBondType() == dm.DOUBLE_BOND: if v1 > 0 and v2 > 0: new_mols.append(update_bond(mol, bond, dm.TRIPLE_BOND)) return [mol for mol in new_mols if mol is not None]
def update_bond(mol, bond, bond_type): """Update bond type between atoms""" new_mol = dm.copy_mol(mol) with dm.without_rdkit_log(): new_bond = new_mol.GetBondWithIdx(bond.GetIdx()) new_bond.SetBondType(bond_type) return dm.sanitize_mol(new_mol)
def test_rdkit_log(capfd): """Test multiple rdkit log scenarios.""" check_logs_are_shown(capfd) with dm.without_rdkit_log(): check_logs_are_not_shown(capfd) check_logs_are_shown(capfd) dm.disable_rdkit_log() check_logs_are_not_shown(capfd) dm.enable_rdkit_log() check_logs_are_shown(capfd) dm.disable_rdkit_log() with dm.without_rdkit_log(): check_logs_are_not_shown(capfd) check_logs_are_not_shown(capfd)
def all_atom_add( mol, atom_types=["C", "N", "O", "F", "Cl", "Br"], asMols=True, max_num_action=float("Inf"), **kwargs, ): """Add a new atom on the mol, by considering all bond type .. warning:: This is computationally expensive Args: mol: <Chem.Mol> Input molecule atom_types: list List of atom symbol to use as replacement (Default: ["C", "N", "O", "F", "Cl", "Br"]) asMols: bool, optional Whether to return output as molecule or smiles max_num_action: float, optional Maximum number of action to reduce complexity Returns: All possible molecules with one additional atom added """ new_mols = [] stop = False with dm.without_rdkit_log(): for atom in mol.GetAtoms(): if stop: break if atom.GetImplicitValence() == 0: continue for atom_symb in atom_types: emol = Chem.RWMol(mol) new_index = emol.AddAtom(Chem.Atom(atom_symb)) emol.UpdatePropertyCache(strict=False) new_mols.extend( _all_atom_join(emol, atom, emol.GetMol().GetAtomWithIdx(new_index))) if len(new_mols) > max_num_action: stop = True break new_mols = [dm.sanitize_mol(mol) for mol in new_mols] new_mols = [mol for mol in new_mols if mol is not None] if not asMols: return [dm.to_smiles(x) for x in new_mols if x] return new_mols
def all_transform_apply( mol, rxns, max_num_action=float("Inf"), asMols=True, **kwargs, ): """ Apply a transformation defined as a reaction from a set of reaction to the input molecule. The reaction need to be one reactant-only Arguments ---------- mol: <Chem.Mol> Input molecule rnxs: list list of reactions/ reaction smarts max_num_action: int, optional Maximum number of result to return (Default: inf) asMols: bool, optional Whether to return smiles or mols Returns ------- Products obtained from applying the chemical reactions """ mols = set([]) with dm.without_rdkit_log(): for rxn in rxns: if len(mols) >= max_num_action: break if isinstance(rxn, str): rxn = AllChem.ReactionFromSmarts(rxn) try: pcdts = [products[0] for products in rxn.RunReactants([mol])] pcdts = [dm.sanitize_mol(x) for x in pcdts] mols.update([dm.to_smiles(x) for x in pcdts if x]) except: pass mols = [x for x in mols if x is not None] if np.isfinite(max_num_action): mols = mols[:max_num_action] mols = [dm.to_mol(x) for x in mols] if not asMols: mols = [dm.to_smiles(x) for x in mols if x is not None] return mols
def all_atom_replace(mol, atom_types=["C", "N", "S", "O"], asMols=True, max_num_action=float("Inf"), **kwargs): """Replace all non-hydrogen atoms by other possibilities. .. warning:: This is computationally expensive Args: mol: <Chem.Mol> Input molecule atom_types: list List of atom symbol to use as replacement (Default: ['C', 'N', 'S', 'O']) asMols: bool, optional Whether to return output as molecule or smiles max_num_action: float, optional Maximum number of action to reduce complexity Returns: All possible molecules with atoms replaced """ new_mols = [] stop = False with dm.without_rdkit_log(): for atom in mol.GetAtoms(): if stop: break if atom.GetAtomicNum() > 1: for atom_symb in atom_types: emol = Chem.RWMol(mol) emol.ReplaceAtom(atom.GetIdx(), Chem.Atom(atom_symb)) new_mols.append(emol) if len(new_mols) > max_num_action: stop = True break # Sanitize and remove bad molecules new_mols = [dm.sanitize_mol(mol) for mol in new_mols] new_mols = [mol for mol in new_mols if mol is not None] if not asMols: # Return SMILES return [dm.to_smiles(x) for x in new_mols] return new_mols