def _generate_conformations_from_mol(self, mol: Chem.Mol, nr_of_conformations: int, enforceChirality: bool = True): """ Helper function - does not need to be called directly. Generates conformations from a rdkit mol object. """ charge = 0 for at in mol.GetAtoms(): if at.GetFormalCharge() != 0: charge += int(at.GetFormalCharge()) if charge != 0: print(Chem.MolToSmiles(mol)) raise NotImplementedError("Charged system") mol.SetProp("smiles", Chem.MolToSmiles(mol)) mol.SetProp("charge", str(charge)) mol.SetProp("name", str(self.name)) # generate numConfs for the smiles string new_confs = Chem.rdDistGeom.EmbedMultipleConfs( mol, numConfs=nr_of_conformations, enforceChirality=enforceChirality, ignoreSmoothingFailures=True, ) # NOTE enforceChirality! # AllChem.AlignMolConformers(mol) assert int(mol.GetNumConformers()) != 0 assert int(mol.GetNumConformers()) == nr_of_conformations return mol
def extract_features(self, ligand: Chem.Mol, conformer_index: int) -> List[PharmacophoricPoint]: """ Extract the pharmacophoric points from a ligand. Parameters ---------- ligand : rdkit.Chem.Mol A ligand conformer_index : int The conformer whose coordinates will be used to obtain the pharmacophoric points. Returns ------- pharmacophoric_points : list of openpharmacophore.PharmacophoricPoint List of pharmacophoric points. """ if ligand.GetNumConformers() == 0: raise NoConformersError if not isinstance(conformer_index, int): raise OpenPharmacophoreTypeError( "conformer_index must be of type int") pharmacophoric_points = [] if isinstance(self.featdef, dict): for smarts_pattern, feat_name in self.featdef.items(): if feat_name not in self.features: continue pattern = Chem.MolFromSmarts(smarts_pattern) atom_indices = ligand.GetSubstructMatch(pattern) if len(atom_indices) == 0: continue pharmacophoric_pnt = self.get_pharmacophoric_point( ligand, feat_name, atom_indices, conformer_index, self.default_radius, self.directionality) insort_right(pharmacophoric_points, pharmacophoric_pnt, key=lambda p: p.short_name) else: # Use rdkit feature factory chemical_features = self.featdef.GetFeaturesForMol(ligand) for feature in chemical_features: feat_name = feature.GetFamily() feat_name = rdkit_to_oph[feat_name] if feat_name not in self.features: continue atom_indices = feature.GetAtomIds() pharmacophoric_pnt = self.get_pharmacophoric_point( ligand, feat_name, atom_indices, conformer_index, self.default_radius, self.directionality) insort_right(pharmacophoric_points, pharmacophoric_pnt, key=lambda p: p.short_name) return pharmacophoric_points
def align_probe_to_target(self, probe: Chem.Mol) -> int: # implace """ :param probe: modified inplace :return: index of best conformer """ ### find what is common common = self._get_common(probe) ### Align them overlap_target = self.target.GetSubstructMatch(common) overlap_probe = probe.GetSubstructMatch(common) atomMap = [ (probe_at, target_at) for probe_at, target_at in zip(overlap_probe, overlap_target) ] rmss = [ rdMolAlign.AlignMol(probe, self.target, prbCid=i, atomMap=atomMap, maxIters=500) for i in range(probe.GetNumConformers()) ] # print(rmss) best_i = rmss.index(min(rmss)) return best_i
def conformer_energy(molecule: Chem.Mol, conformer_id: int = 0, forcefield: str = "UFF") -> float: """ Get the energy of a conformer in a molecule using the universal forcefield (UFF) forcefield or the merck molecular forcefield (MMFF) forcefield. Parameters ---------- molecule : rdkit.Chem.Mol The molecule which energy will be calculated. forcefield : {"UFF", "MMFF"}, optional. The forcefield that will be used to calculate the energy (default="UFF"). Returns ------- float The energy of the molecule. """ if molecule.GetNumConformers() == 0: raise NoConformersError("Molecule must have at least one conformer") if forcefield == "UFF": ff = AllChem.UFFGetMoleculeForceField(molecule, confId=conformer_id) elif forcefield == "MMFF": props = AllChem.MMFFGetMoleculeProperties(molecule) ff = AllChem.MMFFGetMoleculeForceField(molecule, props, confId=conformer_id) return ff.CalcEnergy()
def mol_to_mutliconformer_file(rdkit_mol: Chem.Mol, file_name: str) -> None: """ Write the rdkit molecule to a multi conformer file. Args: rdkit_mol: A complete Chem.Mol instance of a molecule. file_name: Name of the file to be created. """ file_path = Path(file_name) # get the file block writer if file_path.suffix == ".pdb": writer = Chem.MolToPDBBlock elif file_path.suffix == ".mol" or file_path.suffix == ".sdf": writer = Chem.MolToMolBlock elif file_path.suffix == ".xyz": writer = Chem.MolToXYZBlock else: raise FileTypeError( f"The file type {file_path.suffix} is not supported please chose from xyz, pdb, mol or sdf." ) with open(file_name, "w") as out: for i in range(rdkit_mol.GetNumConformers()): out.write(writer(rdkit_mol, confId=i))
def write_conformers_to_xyz(molecule: Mol, path: str, max_num: Optional[int] = None) -> None: num_samples = molecule.GetNumConformers() count = num_samples if max_num is None else min(num_samples, max_num) string = '' for conf_id in range(count): string += conformer_to_xyz(molecule, conf_id=conf_id) string += '\n' with open(path, mode='w') as f: f.write(string)
def MolToPixmap(mol: Mol, size: QSize): if size.isNull() or mol is None: return QPixmap() if not mol.GetNumConformers(): rdDepictor.Compute2DCoords(mol) svg_drawer = rdMolDraw2D.MolDraw2DSVG(size.width(), size.height()) svg_drawer.drawOptions().clearBackground = False svg_drawer.DrawMolecule(mol) svg_drawer.FinishDrawing() drawing_text = svg_drawer.GetDrawingText() return SvgToPixmap(drawing_text, size)
def view_conformers(molecule: Chem.Mol) -> nv.NGLWidget: """ Generate a view of the conformers of a molecule. Parameters ----------- molecule : rdkit.Chem.Mol The molecule which conformers will be visualized. Returns ---------- view : nglview.widget.NGLWidget """ view = nv.NGLWidget() for conformer in range(molecule.GetNumConformers()): mol_string = Chem.MolToMolBlock(molecule, confId=conformer) temp_mol = Chem.MolFromMolBlock(mol_string, removeHs=False) component = view.add_component(temp_mol) component.clear() component.add_ball_and_stick(multipleBond=True) return view
def _from_mol_to_ani_input(self, mol: Chem.Mol, enforceChirality: bool): """ Helper function - does not need to be called directly. Generates ANI input from a rdkit mol object """ # generate atom list atom_list = [] for a in mol.GetAtoms(): atom_list.append(a.GetSymbol()) if ("S" in atom_list or "P" in atom_list or "Cl" in atom_list or "Br" in atom_list or "I" in atom_list): raise NotImplementedError("Atom not yet included in ANI.") # generate conformations mol = self._generate_conformations_from_mol(mol, self.nr_of_conformations, enforceChirality) # generate coord list coord_list = [] # add conformations to coord_list for conf_idx in range(mol.GetNumConformers()): tmp_coord_list = [] for a in mol.GetAtoms(): pos = mol.GetConformer(conf_idx).GetAtomPosition(a.GetIdx()) tmp_coord_list.append([pos.x, pos.y, pos.z]) coord_list.append(tmp_coord_list) coord_list = np.asarray(coord_list) assert coord_list.shape == (mol.GetNumConformers(), mol.GetNumAtoms(), 3) coord_list *= unit.angstrom # generate bond list bond_list = [] for b in mol.GetBonds(): a1 = b.GetBeginAtom() a2 = b.GetEndAtom() bond_list.append((a1.GetIdx(), a2.GetIdx())) # get mdtraj topology n = random.randint(1, 10000000) # TODO: use tmpfile for this https://stackabuse.com/the-python-tempfile-module/ or io.StringIO _ = write_pdb(mol, f"tmp{n}.pdb") topology = md.load(f"tmp{n}.pdb").topology os.remove(f"tmp{n}.pdb") ani_input = { "ligand_atoms": "".join(atom_list), "ligand_coords": coord_list, "ligand_topology": topology, "ligand_bonds": bond_list, } # generate ONE ASE object if thermocorrections are needed ase_atom_list = [] for e, c in zip(ani_input["ligand_atoms"], coord_list[0]): c_list = ( c[0].value_in_unit(unit.angstrom), c[1].value_in_unit(unit.angstrom), c[2].value_in_unit(unit.angstrom), ) ase_atom_list.append(Atom(e, c_list)) mol = Atoms(ase_atom_list) ani_input["ase_mol"] = mol return ani_input