def align(self): """ compute the orientation wrt the reference molecule """ try: from openbabel import pybel, openbabel except: import pybel, openbabel m1 = pybel.readstring('xyz', self.ref_mol.to('xyz')) m2 = pybel.readstring('xyz', self.molecule.to('xyz')) aligner = openbabel.OBAlign(True, False) aligner.SetRefMol(m1.OBMol) aligner.SetTargetMol(m2.OBMol) aligner.Align() print("RMSD: ", aligner.GetRMSD()) rot = np.zeros([3, 3]) for i in range(3): for j in range(3): rot[i, j] = aligner.GetRotMatrix().Get(i, j) coord2 = self.molecule.cart_coords coord2 -= np.mean(coord2, axis=0) coord3 = rot.dot(coord2.T).T + np.mean(self.ref_mol.cart_coords, axis=0) self.mol_aligned = Molecule(self.ref_mol.atomic_numbers, coord3) self.ori = Orientation(rot)
def uniform_labels(self, mol1, mol2): """ Pair the geometrically equivalent atoms of the molecules. Calculate RMSD on all possible isomorphism mappings and return mapping with the least RMSD Args: mol1: First molecule. OpenBabel OBMol or pymatgen Molecule object. mol2: Second molecule. OpenBabel OBMol or pymatgen Molecule object. Returns: (list1, list2) if uniform atom order is found. list1 and list2 are for mol1 and mol2, respectively. Their length equal to the number of atoms. They represents the uniform atom order of the two molecules. The value of each element is the original atom index in mol1 or mol2 of the current atom in uniform atom order. (None, None) if unform atom is not available. """ obmol1 = BabelMolAdaptor(mol1).openbabel_mol obmol2 = BabelMolAdaptor(mol2).openbabel_mol h1 = self.get_molecule_hash(obmol1) h2 = self.get_molecule_hash(obmol2) if h1 != h2: return None, None query = ob.CompileMoleculeQuery(obmol1) isomapper = ob.OBIsomorphismMapper.GetInstance(query) isomorph = ob.vvpairUIntUInt() isomapper.MapAll(obmol2, isomorph) sorted_isomorph = [sorted(x, key=lambda morp: morp[0]) for x in isomorph] label2_list = tuple([tuple([p[1] + 1 for p in x]) for x in sorted_isomorph]) vmol1 = obmol1 aligner = ob.OBAlign(True, False) aligner.SetRefMol(vmol1) least_rmsd = float("Inf") best_label2 = None label1 = list(range(1, obmol1.NumAtoms() + 1)) # noinspection PyProtectedMember elements1 = InchiMolAtomMapper._get_elements(vmol1, label1) for label2 in label2_list: # noinspection PyProtectedMember elements2 = InchiMolAtomMapper._get_elements(obmol2, label2) if elements1 != elements2: continue vmol2 = ob.OBMol() for i in label2: vmol2.AddAtom(obmol2.GetAtom(i)) aligner.SetTargetMol(vmol2) aligner.Align() rmsd = aligner.GetRMSD() if rmsd < least_rmsd: least_rmsd = rmsd best_label2 = copy.copy(label2) return label1, best_label2
def update(self, coords, lattice=None, absolute=False, update_mol=True): """ After the geometry relaxation, the returned atomic coordinates maybe rescaled to [0, 1] bound. In this case, we need to refind the molecular coordinates according to the original neighbor list. If the list does not change, we return the new coordinates otherwise, terminate the calculation. """ from pyxtal.molecule import compare_mol_connectivity, Orientation try: from openbabel import pybel, openbabel except: import pybel, openbabel if lattice is not None: self.lattice = lattice if not absolute: coords = coords.dot(self.lattice.matrix) #mol = Molecule(self.symbols, coords-np.mean(coords, axis=0)) center = self.molecule.get_center(coords) mol = Molecule(self.symbols, coords - center) #match, _ = compare_mol_connectivity(mol, self.mol, True) match, _ = compare_mol_connectivity(mol, self.mol) if match: #position = np.mean(coords, axis=0).dot(self.lattice.inv_matrix) position = center.dot(self.lattice.inv_matrix) #position -= np.floor(position) self.position = position - np.floor(position) if update_mol: self.orientation = Orientation(np.eye(3)) self.mol = mol else: m1 = pybel.readstring('xyz', self.mol.to('xyz')) m2 = pybel.readstring('xyz', mol.to('xyz')) aligner = openbabel.OBAlign(True, False) aligner.SetRefMol(m1.OBMol) aligner.SetTargetMol(m2.OBMol) if aligner.Align(): print("RMSD: ", aligner.GetRMSD()) rot = np.zeros([3, 3]) for i in range(3): for j in range(3): rot[i, j] = aligner.GetRotMatrix().Get(i, j) if abs(np.linalg.det(rot) - 1) < 1e-2: self.orientation.matrix = rot self.orientation.r = R.from_matrix(rot) else: raise ValueError("rotation matrix is wrong") else: import pickle with open('wrong.pkl', "wb") as f: pickle.dump([mol, self.mol], f) mol.to(filename='Wrong.xyz', fmt='xyz') self.mol.to(filename='Ref.xyz', fmt='xyz') raise ValueError("molecular connectivity changes! Exit")
def _calc_rms(mol1, mol2, clabel1, clabel2): """ Calculate the RMSD. Args: mol1: The first molecule. OpenBabel OBMol or pymatgen Molecule object mol2: The second molecule. OpenBabel OBMol or pymatgen Molecule object clabel1: The atom indices that can reorder the first molecule to uniform atom order clabel1: The atom indices that can reorder the second molecule to uniform atom order Returns: The RMSD. """ obmol1 = BabelMolAdaptor(mol1).openbabel_mol obmol2 = BabelMolAdaptor(mol2).openbabel_mol cmol1 = ob.OBMol() for i in clabel1: oa1 = obmol1.GetAtom(i) a1 = cmol1.NewAtom() a1.SetAtomicNum(oa1.GetAtomicNum()) a1.SetVector(oa1.GetVector()) cmol2 = ob.OBMol() for i in clabel2: oa2 = obmol2.GetAtom(i) a2 = cmol2.NewAtom() a2.SetAtomicNum(oa2.GetAtomicNum()) a2.SetVector(oa2.GetVector()) aligner = ob.OBAlign(True, False) aligner.SetRefMol(cmol1) aligner.SetTargetMol(cmol2) aligner.Align() return aligner.GetRMSD()
def _align_hydrogen_atoms(mol1, mol2, heavy_indices1, heavy_indices2): """ Align the label of topologically identical atoms of second molecule towards first molecule Args: mol1: First molecule. OpenBabel OBMol object mol2: Second molecule. OpenBabel OBMol object heavy_indices1: inchi label map of the first molecule heavy_indices2: label map of the second molecule Return: corrected label map of all atoms of the second molecule """ num_atoms = mol2.NumAtoms() all_atom = set(range(1, num_atoms + 1)) hydrogen_atoms1 = all_atom - set(heavy_indices1) hydrogen_atoms2 = all_atom - set(heavy_indices2) label1 = heavy_indices1 + tuple(hydrogen_atoms1) label2 = heavy_indices2 + tuple(hydrogen_atoms2) cmol1 = ob.OBMol() for i in label1: oa1 = mol1.GetAtom(i) a1 = cmol1.NewAtom() a1.SetAtomicNum(oa1.GetAtomicNum()) a1.SetVector(oa1.GetVector()) cmol2 = ob.OBMol() for i in label2: oa2 = mol2.GetAtom(i) a2 = cmol2.NewAtom() a2.SetAtomicNum(oa2.GetAtomicNum()) a2.SetVector(oa2.GetVector()) aligner = ob.OBAlign(False, False) aligner.SetRefMol(cmol1) aligner.SetTargetMol(cmol2) aligner.Align() aligner.UpdateCoords(cmol2) hydrogen_label2 = [] hydrogen_label1 = list(range(len(heavy_indices1) + 1, num_atoms + 1)) for h2 in range(len(heavy_indices2) + 1, num_atoms + 1): distance = 99999.0 idx = hydrogen_label1[0] a2 = cmol2.GetAtom(h2) for h1 in hydrogen_label1: a1 = cmol1.GetAtom(h1) d = a1.GetDistance(a2) if d < distance: distance = d idx = h1 hydrogen_label2.append(idx) hydrogen_label1.remove(idx) hydrogen_orig_idx2 = label2[len(heavy_indices2):] hydrogen_canon_orig_map2 = list( zip(hydrogen_label2, hydrogen_orig_idx2)) hydrogen_canon_orig_map2.sort(key=lambda m: m[0]) hydrogen_canon_indices2 = [x[1] for x in hydrogen_canon_orig_map2] canon_label1 = label1 canon_label2 = heavy_indices2 + tuple(hydrogen_canon_indices2) return canon_label1, canon_label2
def _align_heavy_atoms(mol1, mol2, vmol1, vmol2, ilabel1, ilabel2, eq_atoms): """ Align the label of topologically identical atoms of second molecule towards first molecule Args: mol1: First molecule. OpenBabel OBMol object mol2: Second molecule. OpenBabel OBMol object vmol1: First virtual molecule constructed by centroids. OpenBabel OBMol object vmol2: First virtual molecule constructed by centroids. OpenBabel OBMol object ilabel1: inchi label map of the first molecule ilabel2: inchi label map of the second molecule eq_atoms: equivalent atom labels Return: corrected inchi labels of heavy atoms of the second molecule """ nvirtual = vmol1.NumAtoms() nheavy = len(ilabel1) for i in ilabel2: # add all heavy atoms a1 = vmol1.NewAtom() a1.SetAtomicNum(1) a1.SetVector(0.0, 0.0, 0.0) # useless, just to pair with vmol2 oa2 = mol2.GetAtom(i) a2 = vmol2.NewAtom() a2.SetAtomicNum(1) # align using the virtual atoms, these atoms are not # used to align, but match by positions a2.SetVector(oa2.GetVector()) aligner = ob.OBAlign(False, False) aligner.SetRefMol(vmol1) aligner.SetTargetMol(vmol2) aligner.Align() aligner.UpdateCoords(vmol2) canon_mol1 = ob.OBMol() for i in ilabel1: oa1 = mol1.GetAtom(i) a1 = canon_mol1.NewAtom() a1.SetAtomicNum(oa1.GetAtomicNum()) a1.SetVector(oa1.GetVector()) aligned_mol2 = ob.OBMol() for i in range(nvirtual + 1, nvirtual + nheavy + 1): oa2 = vmol2.GetAtom(i) a2 = aligned_mol2.NewAtom() a2.SetAtomicNum(oa2.GetAtomicNum()) a2.SetVector(oa2.GetVector()) canon_label2 = list(range(1, nheavy + 1)) for symm in eq_atoms: for i in symm: canon_label2[i - 1] = -1 for symm in eq_atoms: candidates1 = list(symm) candidates2 = list(symm) for c2 in candidates2: distance = 99999.0 canon_idx = candidates1[0] a2 = aligned_mol2.GetAtom(c2) for c1 in candidates1: a1 = canon_mol1.GetAtom(c1) d = a1.GetDistance(a2) if d < distance: distance = d canon_idx = c1 canon_label2[c2 - 1] = canon_idx candidates1.remove(canon_idx) canon_inchi_orig_map2 = list( zip(canon_label2, list(range(1, nheavy + 1)), ilabel2)) canon_inchi_orig_map2.sort(key=lambda m: m[0]) heavy_atom_indices2 = tuple(x[2] for x in canon_inchi_orig_map2) return heavy_atom_indices2