def get_conformer_rmsd(mol: RDKitMol) -> np.ndarray: """ Calculate conformer-conformer RMSD. Parameters ---------- mol: rdkit.Chem.rdchem.Mol RDKit Mol object Returns ------- rmsd: np.ndarray A conformer-conformer RMSD value. The shape is `(NumConformers, NumConformers)` """ try: from rdkit.Chem import AllChem except ModuleNotFoundError: raise ValueError("This function requires RDKit to be installed.") rmsd = np.zeros((mol.GetNumConformers(), mol.GetNumConformers()), dtype=float) for i, ref_conf in enumerate(mol.GetConformers()): for j, fit_conf in enumerate(mol.GetConformers()): if i >= j: continue rmsd[i, j] = AllChem.GetBestRMS(mol, mol, ref_conf.GetId(), fit_conf.GetId()) rmsd[j, i] = rmsd[i, j] return rmsd
def generate_conformers(self, mol: RDKitMol) -> RDKitMol: """ Generate conformers for a molecule. This function returns a copy of the original molecule with embedded conformers. Parameters ---------- mol: rdkit.Chem.rdchem.Mol RDKit Mol object Returns ------- mol: rdkit.Chem.rdchem.Mol A new RDKit Mol object containing the chosen conformers, sorted by increasing energy. """ # initial embedding mol = self.embed_molecule(mol) if not mol.GetNumConformers(): msg = 'No conformers generated for molecule' if mol.HasProp('_Name'): name = mol.GetProp('_Name') msg += ' "{}".'.format(name) else: msg += '.' raise RuntimeError(msg) # minimization and pruning self.minimize_conformers(mol) mol = self.prune_conformers(mol) return mol
def prune_conformers(self, mol: RDKitMol) -> RDKitMol: """ Prune conformers from a molecule using an RMSD threshold, starting with the lowest energy conformer. Parameters ---------- mol: rdkit.Chem.rdchem.Mol RDKit Mol object Returns ------- new_mol: rdkit.Chem.rdchem.Mol A new rdkit.Chem.rdchem.Mol containing the chosen conformers, sorted by increasing energy. """ try: from rdkit import Chem except ModuleNotFoundError: raise ValueError("This function requires RDKit to be installed.") if self.rmsd_threshold < 0 or mol.GetNumConformers() <= 1: return mol energies = self.get_conformer_energies(mol) rmsd = self.get_conformer_rmsd(mol) sort = np.argsort(energies) # sort by increasing energy keep: List[float] = [] # always keep lowest-energy conformer discard = [] for i in sort: # always keep lowest-energy conformer if len(keep) == 0: keep.append(i) continue # discard conformers after max_conformers is reached if len(keep) >= self.max_conformers: discard.append(i) continue # get RMSD to selected conformers this_rmsd = rmsd[i][np.asarray(keep, dtype=int)] # discard conformers within the RMSD threshold if np.all(this_rmsd >= self.rmsd_threshold): keep.append(i) else: discard.append(i) # create a new molecule to hold the chosen conformers # this ensures proper conformer IDs and energy-based ordering new_mol = Chem.Mol(mol) new_mol.RemoveAllConformers() conf_ids = [conf.GetId() for conf in mol.GetConformers()] for i in keep: conf = mol.GetConformer(conf_ids[i]) new_mol.AddConformer(conf, assignId=True) return new_mol