Esempio n. 1
0
    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")
Esempio n. 2
0
File: io.py Progetto: gipfeli/PyXtal
    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)
Esempio n. 3
0
    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)
Esempio n. 4
0
File: io.py Progetto: Virts/PyXtal
 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
Esempio n. 5
0
 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
Esempio n. 6
0
    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)
Esempio n. 7
0
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
Esempio n. 8
0
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)