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 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 from_1D_dicts(cls, dicts): from pyxtal.molecule import pyxtal_molecule, Orientation mol = pyxtal_molecule(mol=dicts['smile'] + '.smi') rdkit_mol = mol.rdkit_mol(mol.smile) conf = rdkit_mol.GetConformer(0) #print("try") #print(conf.GetPositions()[:3]) #print(dicts["rotor"]) if dicts['reflect']: mol.set_torsion_angles(conf, dicts["rotor"], False) # print(mol.set_torsion_angles(conf, dicts["rotor"], True)) # #import sys; sys.exit() xyz = mol.set_torsion_angles(conf, dicts["rotor"], dicts['reflect']) mol.reset_positions(xyz) g = dicts["number"] index = dicts["index"] dim = dicts["dim"] matrix = R.from_euler('zxy', dicts["orientation"], degrees=True).as_matrix() orientation = Orientation(matrix) #if dicts['reflect']: # print('load'); print(xyz[:3]) # print("aaaaaaaaaaaaaa"); print(xyz[:3].dot(orientation.matrix.T)) # print("matrix"); print(orientation.matrix) wp = Wyckoff_position.from_group_and_index(g, index, dim) diag = dicts["diag"] lattice = Lattice.from_matrix(dicts["lattice"], ltype=dicts["lattice_type"]) position = dicts[ "center"] #np.dot(dicts["center"], lattice.inv_matrix) return cls(mol, position, orientation, wp, lattice, diag)
def make_mol_site(self, ref=False): if ref: mol = self.ref_mol ori = self.ori else: mol = self.molecule ori = Orientation(np.eye(3)) mol = self.add_site_props(mol) pmol = pyxtal_molecule(mol, symmetrize=False) site = mol_site(pmol, self.position, ori, self.wyc, self.lattice, self.diag) return site
def make_mol_sites(self): """ generate the molecular wyckoff sites """ ori = Orientation(np.eye(3)) sites = [] for mol, pos, wp in zip(self.p_mols, self.positions, self.wps): site = mol_site(mol, pos, ori, wp, self.lattice, self.diag) #print(pos) #print(self.lattice.matrix) #print([a.value for a in site.molecule.mol.species]) #print(site.molecule.mol.cart_coords) #print(site._get_coords_and_species(absolute=True)[0][:10]) sites.append(site) return sites
def load_dict(cls, dicts): """ load the sites from a dictionary """ from pyxtal.molecule import pyxtal_molecule, Orientation g = dicts["number"] index = dicts["index"] dim = dicts["dim"] mol = pyxtal_molecule.load_dict(dicts["molecule"]) position = dicts["position"] orientation = Orientation.load_dict(dicts['orientation']) wp = Wyckoff_position.from_group_and_index(g, index, dim) diag = dicts["diag"] lattice = Lattice.from_matrix(dicts["lattice"]) return cls(mol, position, orientation, wp, lattice, diag)
class mol_site: """ Class for storing molecular Wyckoff positions and orientations within the molecular_crystal class. Each mol_site object represenents an entire Wyckoff position, not necessarily a single molecule. This is the molecular version of Wyckoff_site Args: mol: a `pyxtal_molecule <pyxtal.molecule.pyxtal_molecule.html>`_ object position: the 3-vector representing the generating molecule's position orientation: an `Orientation <pyxtal.molecule.Oreintation.html>`_ object wp: a `Wyckoff_position <pyxtal.symmetry.Wyckoff_position.html>`_ object lattice: a `Lattice <pyxtal.lattice.Lattice>`_ object diag: whether or not use the `n` representation """ def __init__(self, mol, position, orientation, wp, lattice=None, diag=False): # describe the molecule self.molecule = mol self.diag = diag self.wp = wp self.position = position # fractional coordinate of molecular center self.orientation = orientation #pyxtal.molecule.orientation object if isinstance(lattice, Lattice): self.lattice = lattice else: self.lattice = Lattice.from_matrix(lattice) self.PBC = self.wp.PBC self.mol = mol.mol # A Pymatgen molecule object self.site_props = mol.props self.symbols = mol.symbols #[site.specie.value for site in self.mol.sites] self.numbers = self.mol.atomic_numbers self.tols_matrix = mol.tols_matrix self.radius = mol.radius if self.diag: self.wp.diagonalize_symops() self.position = project_point(self.position, wp[0]) def __str__(self): if not hasattr(self, "site_symm"): self.site_symm = site_symm(self.wp.symmetry_m[0], self.wp.number, dim=self.wp.dim) self.angles = self.orientation.r.as_euler('zxy', degrees=True) formula = self.mol.formula.replace(" ", "") s = "{:} @ [{:7.4f} {:7.4f} {:7.4f}] ".format(formula, *self.position) s += "WP: {:2d}{:s}, ".format(self.wp.multiplicity, self.wp.letter) s += "Site symmetry {:} ==> Euler: ".format(self.site_symm) s += "{:6.3f} {:6.3f} {:6.3f}".format(*self.angles) return s def _get_dof(self): """ get the number of dof for the given structures: """ freedom = np.trace(self.wp.ops[0].rotation_matrix) > 0 self.dof = len(freedom[freedom == True]) def save_dict(self): dict0 = { "position": self.position, "number": self.wp.number, "dim": self.wp.dim, "index": self.wp.index, "diag": self.diag, "molecule": self.molecule.save_dict(), "orientation": self.orientation.save_dict(), "lattice": self.lattice.matrix, } return dict0 @classmethod def load_dict(cls, dicts): """ load the sites from a dictionary """ from pyxtal.molecule import pyxtal_molecule, Orientation g = dicts["number"] index = dicts["index"] dim = dicts["dim"] mol = pyxtal_molecule.load_dict(dicts["molecule"]) position = dicts["position"] orientation = Orientation.load_dict(dicts['orientation']) wp = Wyckoff_position.from_group_and_index(g, index, dim) diag = dicts["diag"] lattice = Lattice.from_matrix(dicts["lattice"]) return cls(mol, position, orientation, wp, lattice, diag) def show(self, id=None, **kwargs): from pyxtal.viz import display_molecular_site return display_molecular_site(self, id, **kwargs) def _get_coords_and_species(self, absolute=False, add_PBC=False, first=False, unitcell=False): """ Used to generate coords and species for get_coords_and_species Args: absolute: whether or not to return absolute (Euclidean) coordinates. If false, return relative coordinates instead add_PBC: whether or not to add coordinates in neighboring unit cells, used for distance checking first: whether or not to extract the information from only the first site unitcell: whether or not to move the molecular center to the unit cell Returns: atomic coords: a numpy array of fractional coordinates for the atoms in the site species: a list of atomic species for the atomic coords """ coord0 = self.mol.cart_coords.dot(self.orientation.matrix.T) # wp_atomic_sites = [] wp_atomic_coords = None for point_index, op2 in enumerate(self.wp.ops): # Obtain the center in absolute coords center_relative = op2.operate(self.position) if unitcell: center_relative -= np.floor(center_relative) center_absolute = np.dot(center_relative, self.lattice.matrix) # Rotate the molecule (Euclidean metric) op2_m = self.wp.generators_m[point_index] rot = op2_m.affine_matrix[:3, :3].T if self.diag and self.wp.index > 0: tau = op2.translation_vector else: tau = op2_m.affine_matrix[:3, 3] tmp = np.dot(coord0, rot) + tau # Add absolute center to molecule tmp += center_absolute tmp = tmp.dot(self.lattice.inv_matrix) if wp_atomic_coords is None: wp_atomic_coords = tmp else: wp_atomic_coords = np.append(wp_atomic_coords, tmp, axis=0) wp_atomic_sites.extend(self.symbols) if first: break if add_PBC is True: # Filter PBC of wp_atomic_coords wp_atomic_coords = filtered_coords(wp_atomic_coords, PBC=self.PBC) # Add PBC copies of coords m = create_matrix(PBC=self.PBC, omit=True) # Move [0,0,0] PBC vector to first position in array m2 = [[0, 0, 0]] for v in m: m2.append(v) new_coords = np.vstack([wp_atomic_coords + v for v in m2]) wp_atomic_coords = new_coords if absolute: wp_atomic_coords = wp_atomic_coords.dot(self.lattice.matrix) return wp_atomic_coords, wp_atomic_sites def get_coords_and_species(self, absolute=False, add_PBC=False, unitcell=False): """ Lazily generates and returns the atomic coordinate and species for the Wyckoff position. Plugs the molecule into the provided orientation (with angle=0), and calculates the new positions. Args: absolute: whether or not to return absolute (Euclidean) coordinates. If false, return relative coordinates instead add_PBC: whether or not to add coordinates in neighboring unit cells, used for distance checking unitcell: whether or not to move the molecular center to the unit cell Returns: coords: a np array of 3-vectors. species: a list of atomic symbols, e.g. ['H', 'H', 'O', 'H', 'H', 'O'] """ return self._get_coords_and_species(absolute, add_PBC, unitcell=unitcell) def get_centers(self, absolute=False): """ Returns the fractional coordinates for the center of mass for each molecule in the Wyckoff position Returns: A numpy array of fractional 3-vectors """ centers = apply_ops(self.position, self.wp.ops) # centers1 = filtered_coords(centers0, self.PBC) if absolute is False: return centers else: return np.dot(centers, self.lattice.matrix) def get_principle_axes(self, coords, adjust=False): """ compute the principle axis """ coords -= np.mean(coords, axis=0) Inertia = np.zeros([3, 3]) Inertia[0, 0] = np.sum(coords[:, 1]**2 + coords[:, 2]**2) Inertia[1, 1] = np.sum(coords[:, 0]**2 + coords[:, 2]**2) Inertia[2, 2] = np.sum(coords[:, 0]**2 + coords[:, 1]**2) Inertia[0, 1] = Inertia[1, 0] = -np.sum(coords[:, 0] * coords[:, 1]) Inertia[0, 2] = Inertia[2, 0] = -np.sum(coords[:, 0] * coords[:, 2]) Inertia[1, 2] = Inertia[2, 1] = -np.sum(coords[:, 1] * coords[:, 2]) _, matrix = np.linalg.eigh(Inertia) # search for the best direction if adjust: diffs = coords.dot(matrix) - self.mol.cart_coords diffs = np.sqrt(np.sum(diffs**2, axis=0)) / len(coords) for axis in range(3): if diffs[axis] > 0.05: #needs to check matrix[:, axis] *= -1 diffs = coords.dot(matrix) - self.mol.cart_coords tol = np.sqrt(np.sum(diffs**2)) / len(coords) if tol > 0.1: print("warining: molecular geometry changed") print(diffs) return matrix def get_Euler_angle(self): """ To compute the Euler_angle for the given molecule """ coord0 = self.mol.cart_coords.dot(self.orientation.matrix.T) # coord0 -= np.mean(coord0, axis=0) matrix = self.get_principle_axes(coord0, True) return R.from_matrix(matrix).as_euler('zxy', degrees=True) def perturbate(self, lattice, trans=0.1, rot=5): """ Random perturbation of the molecular site Args: lattice: lattice vectors trans: magnitude of tranlation vectors (default: 0.1 A) rot: magnitude of rotation degree (default: 5.0) """ dis = (np.random.sample(3) - 0.5).dot(lattice) dis /= np.linalg.norm(dis) dis *= trans self.translate(dis, True) if rot == 'random': self.orientation.change_orientation() else: self.orientation.change_orientation(angle=rot / 180 * np.pi) def translate(self, disp=np.array([0.0, 0.0, 0.0]), absolute=False): """ To translate the molecule """ disp = np.array(disp) if absolute: disp = disp.dot(self.lattice.inv_matrix) position = self.position + disp self.position = project_point(position, self.wp[0]) def rotate(self, axis=0, angle=180): """ To rotate the molecule """ p = self.orientation.r if type(axis) == int: coord0 = self.mol.cart_coords.dot(self.orientation.r.as_matrix().T) coord0 -= np.mean(coord0, axis=0) ax = self.get_principle_axes(coord0).T[axis] elif len(axis) == 3: ax = axis / np.linalg.norm(axis) q = R.from_rotvec(ax * rad * angle) o = q * p self.orientation.r = o self.orientation.matrix = o.as_matrix() def get_mol_object(self, id=0): """ make the pymatgen molecule object Args: id: the index of molecules in the given site Returns: a molecule object """ coord0 = self.mol.cart_coords.dot(self.orientation.matrix.T) # # Obtain the center in absolute coords if id <= len(self.wp.generators): op = self.wp.generators[id] center_relative = op.operate(self.position) center_relative -= np.floor(center_relative) #print(center_relative) center_absolute = np.dot(center_relative, self.lattice.matrix) # Rotate the molecule (Euclidean metric) op_m = self.wp.generators_m[id] rot = op_m.affine_matrix[0:3][:, 0:3].T tau = op_m.affine_matrix[0:3][:, 3] tmp = np.dot(coord0, rot) + tau # Add absolute center to molecule tmp += center_absolute return Molecule(self.symbols, tmp) else: raise ValueError("id is greater than the number of molecules") 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 pymatgen.core.structure import Structure from pyxtal.io import search_molecule_in_crystal 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)) #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 -= np.floor(position) self.position = 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) #print(mol.to(fmt='xyz')) #print(self.mol.to(fmt='xyz')) raise ValueError("molecular connectivity changes! Exit") #todo check if connectivty changed def _find_gen_wyckoff_in_subgroup(self, groups=None): """ Symmetry transformation group -> subgroup At the moment we only consider for multiplicity 2: P-1, P21, P2, Pm and Pc to add: for multiplicity 4: P21/c, P212121 Permutation is allowed """ from pyxtal.symmetry import Wyckoff_position pos = self.position wp0 = self.wp pos0 = apply_ops(pos, wp0) if len(wp0) == 2: if self.diag: # P21/n -> Pn #print("----------P21n----------") wp1 = Wyckoff_position.from_group_and_index(7, 0) wp1.diagonalize_symops() axes = [[0, 1, 2], [2, 1, 0]] for ax in axes: pos1 = apply_ops(pos[ax], wp1) diff = (pos1[:, ax] - pos0)[1] diff -= np.floor(diff) if len(diff[diff == 0]) >= 2: #return wp1, ax, pos[ax] - 0.5*diff return Wyckoff_position.from_group_and_index( 7, 0), ax, pos[ax] - 0.5 * diff else: if groups is None: groups = [4, 3, 6, 7, 2] if 15 < wp0.number < 71: axes = [[0, 1, 2], [0, 2, 1], [1, 0, 2], [2, 1, 0]] elif wp0.number < 15: axes = [[0, 1, 2], [2, 1, 0]] for group in groups: wp1 = Wyckoff_position.from_group_and_index(group, 0) for ax in axes: pos1 = apply_ops(pos[ax], wp1) diff = (pos1[:, ax] - pos0)[1] diff -= np.floor(diff) if len(diff[diff == 0]) >= 2: return wp1, ax, pos[ax] - 0.5 * diff def make_gen_wyckoff_site(self): if self.wp.index > 0: wp, ax, pos = self._find_gen_wyckoff_in_subgroup() ori = self.orientation.rotate_by_matrix(np.eye(3)[ax]) lat = self.lattice.swap_axis(ids=ax) lat.ltype = Group(wp.number).lattice_type return mol_site(self.molecule, pos, ori, wp, lat, self.diag) else: print("This is already a general position") return self def _create_matrix(self): """ Used for calculating distances in lattices with periodic boundary conditions. When multiplied with a set of points, generates additional points in cells adjacent to and diagonal to the original cell Returns: A numpy array of matrices which can be multiplied by a set of coordinates """ matrix = [] [a, b, c] = np.linalg.norm(self.lattice.matrix, axis=1) if a > 20 and self.radius < 10: i_list = [0] elif a < 6.5: i_list = [-1, 0, 2] else: i_list = [-1, 0, 1] if b > 20 and self.radius < 10: j_list = [0] elif b < 6.5: j_list = [-1, 0, 2] else: j_list = [-1, 0, 1] if c > 20 and self.radius < 10: k_list = [0] elif c < 6.5: k_list = [-1, 0, 2] else: k_list = [-1, 0, 1] if not self.PBC[0]: i_list = [0] if not self.PBC[1]: j_list = [0] if not self.PBC[2]: k_list = [0] for i in i_list: for j in j_list: for k in k_list: if [i, j, k] != [0, 0, 0]: matrix.append([i, j, k]) #In case a,b,c are all greater than 20 if len(matrix) == 0: matrix = [[1, 0, 0]] return np.array(matrix, dtype=float) def compute_distances(self): """ compute if the atoms in the Wyckoff position are too close to each other or not. Does not check distances between atoms in the same molecule. Uses crystal.check_distance as the base code. Returns: minimum distances """ m_length = len(self.symbols) # TODO: Use tm instead of tols lists # Get coords of WP with PBC coords, _ = self._get_coords_and_species() # Get coords of the generating molecule coords_mol = coords[:m_length] # Remove generating molecule's coords from large array coords = coords[m_length:] min_ds = [] # Check periodic images m = self._create_matrix() coords_PBC = np.vstack([coords_mol + v for v in m]) d = distance_matrix(coords_mol, coords_PBC, self.lattice.matrix, [0, 0, 0], True) if d < 0.9: return d else: min_ds.append(d) if self.wp.multiplicity > 1: # Check inter-atomic distances d = distance_matrix(coords_mol, coords, self.lattice.matrix, self.PBC, True) min_ds.append(d) return min(min_ds) def check_distances(self): """ Checks if the atoms in the Wyckoff position are too close to each other or not. Does not check distances between atoms in the same molecule. Uses crystal.check_distance as the base code. Returns: True if the atoms are not too close together, False otherwise """ m_length = len(self.symbols) coords, _ = self._get_coords_and_species() # Get coords of the generating molecule coords_mol = coords[:m_length] # Remove generating molecule's coords from large array coords = coords[m_length:] # Check periodic images m = self._create_matrix() coords_PBC = np.vstack([coords_mol + v for v in m]) d = distance_matrix(coords_PBC, coords_mol, self.lattice.matrix, PBC=[0, 0, 0]) # only check if small distance is detected if np.min(d) < np.max(self.tols_matrix): tols = np.min(d.reshape([len(m), m_length, m_length]), axis=0) if (tols < self.tols_matrix).any(): return False if self.wp.multiplicity > 1: # Check inter-atomic distances d = distance_matrix(coords, coords_mol, self.lattice.matrix, PBC=self.PBC) if np.min(d) < np.max(self.tols_matrix): tols = np.min(d.reshape( [self.wp.multiplicity - 1, m_length, m_length]), axis=0) if (tols < self.tols_matrix).any(): return False return True def check_with_ms2(self, ms2, factor=1.0, tm=Tol_matrix(prototype="molecular")): """ Checks whether or not the molecules of two mol sites overlap. Uses ellipsoid overlapping approximation to check. Takes PBC and lattice into consideration. Args: ms1: a mol_site object ms2: another mol_site object factor: the distance factor to pass to check_distances. (only for inter-atomic distance checking) tm: a Tol_matrix object (or prototype string) for distance checking Returns: False if the Wyckoff positions overlap. True otherwise """ # Get coordinates for both mol_sites c1, _ = self.get_coords_and_species() c2, _ = ms2.get_coords_and_species() # Calculate which distance matrix is smaller/faster m_length1 = len(self.numbers) m_length2 = len(ms2.numbers) wp_length1 = len(c1) wp_length2 = len(c2) size1 = m_length1 * wp_length2 size2 = m_length2 * wp_length1 # Case 1 if size1 <= size2: coords_mol = c1[:m_length1] # Calculate tol matrix for species pairs tols = np.zeros((m_length1, m_length2)) for i1, number1 in enumerate(self.numbers): for i2, number2 in enumerate(ms2.numbers): tols[i1][i2] = tm.get_tol(number1, number2) tols = np.repeat(tols, ms2.wp.multiplicity, axis=1) d = distance_matrix(coords_mol, c2, self.lattice.matrix, PBC=self.PBC) # Case 2 elif size1 > size2: coords_mol = c2[:m_length2] # Calculate tol matrix for species pairs tols = np.zeros((m_length2, m_length1)) for i1, number1 in enumerate(ms2.numbers): for i2, number2 in enumerate(self.numbers): tols[i1][i2] = tm.get_tol(number1, number2) tols = np.repeat(tols, self.wp.multiplicity, axis=1) d = distance_matrix(coords_mol, c1, self.lattice.matrix, PBC=self.PBC) # Check if distances are smaller than tolerances if (d < tols).any(): return False return True
def test_modules(): fprint("====== Testing functionality for pyXtal version 0.1dev ======") global failed_package failed_package = False # Record if errors occur at any level reset() fprint("Importing sys...") try: import sys fprint("Success!") except Exception as e: fail(e) sys.exit(0) fprint("Importing numpy...") try: import numpy as np fprint("Success!") except Exception as e: fail(e) sys.exit(0) fprint("Importing pymatgen...") try: import pymatgen fprint("Success!") except Exception as e: fail(e) sys.exit(0) try: from pymatgen.core.operations import SymmOp except Exception as e: fail(e) sys.exit(0) fprint("Importing pandas...") try: import pandas fprint("Success!") except Exception as e: fail(e) sys.exit(0) fprint("Importing spglib...") try: import spglib fprint("Success!") except Exception as e: fail(e) sys.exit(0) fprint("Importing ase...") try: import ase fprint("Success!") except: fprint("Error: could not import openbabel. Try reinstalling the package.") fprint("=== Testing modules ===") # =====database.element===== fprint("pyxtal.database.element") reset() try: import pyxtal.database.element except Exception as e: fail(e) fprint(" class Element") try: from pyxtal.database.element import Element except Exception as e: fail(e) if passed(): for i in range(1, 95): if passed(): try: ele = Element(i) except: fail("Could not access Element # " + str(i)) try: y = ele.sf y = ele.z y = ele.short_name y = ele.long_name y = ele.valence y = ele.valence_electrons y = ele.covalent_radius y = ele.vdw_radius y = ele.get_all(0) except: fail("Could not access attribute for element # " + str(i)) try: ele.all_z() ele.all_short_names() ele.all_long_names() ele.all_valences() ele.all_valence_electrons() ele.all_covalent_radii() ele.all_vdw_radii() except: fail("Could not access class methods") check() # =====database.hall===== fprint("pyxtal.database.hall") reset() try: import pyxtal.database.hall except Exception as e: fail(e) fprint(" hall_from_hm") try: from pyxtal.database.hall import hall_from_hm except Exception as e: fail(e) if passed(): for i in range(1, 230): if passed(): try: hall_from_hm(i) except: fail("Could not access hm # " + str(i)) check() # =====database.collection===== fprint("pyxtal.database.collection") reset() try: import pyxtal.database.collection except Exception as e: fail(e) fprint(" Collection") try: from pyxtal.database.collection import Collection except Exception as e: fail(e) if passed(): for i in range(1, 230): if passed(): try: molecule_collection = Collection("molecules") except: fail("Could not access hm # " + str(i)) check() # =====operations===== fprint("pyxtal.operations") reset() try: import pyxtal.operations except Exception as e: fail(e) from pyxtal.lattice import random_shear_matrix, random_vector fprint(" angle") try: from pyxtal.operations import angle except Exception as e: fail(e) if passed(): try: for i in range(10): v1 = random_vector() v2 = random_vector() angle(v1, v2) except Exception as e: fail(e) check() fprint(" is_orthogonal") try: from pyxtal.operations import is_orthogonal except Exception as e: fail(e) if passed(): try: a = is_orthogonal([[1, 0, 0], [0, 1, 0], [0, 0, 1]]) b = is_orthogonal([[0, 0, 1], [1, 0, 0], [1, 0, 0]]) if a is True and b is False: pass else: fail() except Exception as e: fail(e) check() fprint(" rotate_vector") try: from pyxtal.operations import rotate_vector except Exception as e: fail(e) if passed(): try: for i in range(10): v1 = random_vector() v2 = random_vector() rotate_vector(v1, v2) except Exception as e: fail(e) check() fprint(" are_equal") try: from pyxtal.operations import are_equal except Exception as e: fail(e) if passed(): try: op1 = SymmOp.from_xyz_string("x,y,z") op2 = SymmOp.from_xyz_string("x,y,z+1") a = are_equal(op1, op2, PBC=[0, 0, 1]) b = are_equal(op1, op2, PBC=[1, 0, 0]) if a is True and b is False: pass else: fail() except Exception as e: fail(e) check() fprint(" class OperationAnalyzer") try: from pyxtal.operations import OperationAnalyzer except Exception as e: fail(e) if passed(): try: m = np.eye(3) t = random_vector() op1 = SymmOp.from_rotation_and_translation(m, t) OperationAnalyzer(op1) except Exception as e: fail(e) check() fprint(" class Orientation") try: from pyxtal.molecule import Orientation except Exception as e: fail(e) if passed(): try: for i in range(10): v1 = random_vector() c1 = random_vector() o = Orientation.from_constraint(v1, c1) except Exception as e: fail(e) check() # =====symmetry===== fprint("pyxtal.symmetry") reset() try: import pyxtal.symmetry except Exception as e: fail(e) fprint(" get_wyckoffs (may take a moment)") try: from pyxtal.symmetry import get_wyckoffs except Exception as e: fail(e) if passed(): try: for i in [1, 2, 229, 230]: get_wyckoffs(i) get_wyckoffs(i, organized=True) except: fail(" Could not access Wyckoff positions for space group # " + str(i)) check() fprint(" get_wyckoff_symmetry (may take a moment)") try: from pyxtal.symmetry import get_wyckoff_symmetry except Exception as e: fail(e) if passed(): try: for i in [1, 2, 229, 230]: get_wyckoff_symmetry(i) get_wyckoff_symmetry(i, molecular=True) except: fail("Could not access Wyckoff symmetry for space group # " + str(i)) check() fprint(" get_wyckoffs_generators (may take a moment)") try: from pyxtal.symmetry import get_wyckoff_generators except Exception as e: fail(e) if passed(): try: for i in [1, 2, 229, 230]: get_wyckoff_generators(i) except: fail("Could not access Wyckoff generators for space group # " + str(i)) check() fprint(" letter_from_index") try: from pyxtal.symmetry import letter_from_index except Exception as e: fail(e) if passed(): try: if letter_from_index(0, get_wyckoffs(47)) == "A": pass else: fail() except Exception as e: fail(e) check() fprint(" index_from_letter") try: from pyxtal.symmetry import index_from_letter except Exception as e: fail(e) if passed(): try: if index_from_letter("A", get_wyckoffs(47)) == 0: pass else: fail() except Exception as e: fail(e) check() fprint(" jk_from_i") try: from pyxtal.symmetry import jk_from_i except Exception as e: fail(e) if passed(): try: w = get_wyckoffs(2, organized=True) j, k = jk_from_i(1, w) if j == 1 and k == 0: pass else: fprint(j, k) fail() except Exception as e: fail(e) check() fprint(" i_from_jk") try: from pyxtal.symmetry import i_from_jk except Exception as e: fail(e) if passed(): try: w = get_wyckoffs(2, organized=True) j, k = jk_from_i(1, w) i = i_from_jk(j, k, w) if i == 1: pass else: fprint(j, k) fail() except Exception as e: fail(e) check() fprint(" ss_string_from_ops") try: from pyxtal.symmetry import ss_string_from_ops except Exception as e: fail(e) if passed(): try: strings = ["1", "4 . .", "2 3 ."] for i, sg in enumerate([1, 75, 195]): ops = get_wyckoffs(sg)[0] ss_string_from_ops(ops, sg, dim=3) except Exception as e: fail(e) check() fprint(" Wyckoff_position") try: from pyxtal.symmetry import Wyckoff_position except Exception as e: fail(e) if passed(): try: wp = Wyckoff_position.from_group_and_index(20, 1) except Exception as e: fail(e) check() fprint(" Group") try: from pyxtal.symmetry import Group except Exception as e: fail(e) if passed(): try: g3 = Group(230) g2 = Group(80, dim=2) g1 = Group(75, dim=1) except Exception as e: fail(e) check() # =====molecule===== fprint("pyxtal.molecule") reset() try: from pyxtal.molecule import pyxtal_molecule except Exception as e: fail(e) if passed(): try: h2o = pyxtal_molecule("H2O").mol ch4 = pyxtal_molecule("CH4").mol except Exception as e: fail(e) check() fprint(" reoriented_molecule") try: from pyxtal.molecule import reoriented_molecule except Exception as e: fail(e) if passed(): try: reoriented_molecule(h2o) reoriented_molecule(ch4) except Exception as e: fail(e) check() fprint(" orientation_in_wyckoff_position") try: from pyxtal.molecule import orientation_in_wyckoff_position except Exception as e: fail(e) if passed(): try: w = get_wyckoffs(20) ws = get_wyckoff_symmetry(20, molecular=True) wp = Wyckoff_position.from_group_and_index(20, 1) orientation_in_wyckoff_position(h2o, wp) orientation_in_wyckoff_position(ch4, wp) except Exception as e: fail(e) check() end(condition=2)