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)
Example #2
0
def check_mol_sites(ms1,
                    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, _ = ms1.get_coords_and_species()
    c2, _ = ms2.get_coords_and_species()

    # Calculate which distance matrix is smaller/faster
    m_length1 = len(ms1.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(ms1.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, ms1.lattice.matrix, PBC=ms1.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(ms1.numbers):
                tols[i1][i2] = tm.get_tol(number1, number2)
        tols = np.repeat(tols, ms1.wp.multiplicity, axis=1)
        d = distance_matrix(coords_mol, c1, ms1.lattice.matrix, PBC=ms1.PBC)

    # Check if distances are smaller than tolerances
    if (d < tols).any():
        return False
    return True
Example #3
0
def check_atom_sites(ws1, ws2, lattice, tm, same_group=True):
    """
    Given two Wyckoff sites, checks the inter-atomic distances between them.

    Args:
        ws1: a Wyckoff_site object
        ws2: a different Wyckoff_site object (will always return False if
            two identical WS's are provided)
        lattice: a 3x3 cell matrix
        same_group: whether or not the two WS's are in the same structure.
            Default value True reduces the calculation cost

    Returns:
        True if all distances are greater than the allowed tolerances.
        False if any distance is smaller than the allowed tolerance
    """
    # Ensure the PBC values are valid
    if ws1.PBC != ws2.PBC:
        printx("Error: PBC values do not match between Wyckoff sites")
        return
    # Get tolerance
    tol = tm.get_tol(ws1.specie, ws2.specie)
    # Symmetry shortcut method: check only some atoms
    if same_group is True:
        # We can either check one atom in WS1 against all WS2, or vice-versa
        # Check which option is faster
        if ws1.multiplicity > ws2.multiplicity:
            coords1 = [ws1.coords[0]]
            coords2 = ws2.coords
        else:
            coords1 = [ws2.coords[0]]
            coords2 = ws1.coords
        # Calculate distances
        dm = distance_matrix(coords1, coords2, lattice, PBC=ws1.PBC)
        # Check if any distances are less than the tolerance
        if (dm < tol).any():
            return False
        else:
            return True
    # No symmetry method: check all atomic pairs
    else:
        dm = distance_matrix(ws1.coords, ws2.coords, lattice, PBC=ws1.PBC)
        # Check if any distances are less than the tolerance
        if (dm < tol).any():
            return False
        else:
            return True
Example #4
0
    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
Example #5
0
def WP_merge(pt, lattice, wp, tol, orientations=None):
    """
    Given a list of fractional coordinates, merges them within a given
    tolerance, and checks if the merged coordinates satisfy a Wyckoff
    position. Used for merging general Wyckoff positions into special Wyckoff
    positions within the random_crystal (and its derivative) classes.

    Args:
        pt: the originl point (3-vector)
        lattice: a 3x3 matrix representing the unit cell
        wp: a `Wyckoff_position <pyxtal.symmetry.Wyckoff_position.html> object after merge
        tol: the cutoff distance for merging coordinates
        orientations: the valid orientations for a given molecule. Obtained
            from get_sg_orientations, which is called within molecular_crystal

    Returns:
        pt: 3-vector after merge
        wp: a `pyxtal.symmetry.Wyckoff_position` object, If no matching WP, returns False. 
        valid_ori: the valid orientations after merge

    """
    index = wp.index
    PBC = wp.PBC
    group = Group(wp.number, wp.dim)
    pt = project_point(pt, wp[0], lattice, PBC)
    coor = apply_ops(pt, wp)
    if orientations is None:
        valid_ori = None
    else:
        j, k = jk_from_i(index, orientations)
        valid_ori = orientations[j][k]

    # Main loop for merging multiple times

    while True:
        # Check distances of current WP. If too small, merge
        dm = distance_matrix([coor[0]], coor, lattice, PBC=PBC)
        passed_distance_check = True
        x = np.argwhere(dm < tol)
        for y in x:
            # Ignore distance from atom to itself
            if y[0] == 0 and y[1] == 0:
                pass
            else:
                passed_distance_check = False
                break

        # for molecular crystal, one more check
        if check_images([coor[0]], [6], lattice, PBC=PBC, tol=tol) is False:
            passed_distance_check = False

        if not passed_distance_check:
            mult1 = group[index].multiplicity
            # Find possible wp's to merge into
            possible = []
            for i, wp0 in enumerate(group):
                mult2 = wp0.multiplicity
                # Check that a valid orientation exists
                if orientations is not None:
                    j, k = jk_from_i(i, orientations)
                    if orientations[j][k] == []:
                        continue
                    else:
                        valid_ori = orientations[j][k]
                # factor = mult2 / mult1

                if (mult2 < mult1) and (mult1 % mult2 == 0):
                    possible.append(i)
            if possible == []:
                return None, False, valid_ori

            # Calculate minimum separation for each WP
            distances = []
            for i in possible:
                wp = group[i]
                projected_point = project_point(pt,
                                                wp[0],
                                                lattice=lattice,
                                                PBC=PBC)
                d = distance(pt - projected_point, lattice, PBC=PBC)
                distances.append(np.min(d))
            # Choose wp with shortest translation for generating point
            tmpindex = np.argmin(distances)
            index = possible[tmpindex]
            wp = group[index]
            pt = project_point(pt, wp[0], lattice=lattice, PBC=PBC)
            coor = apply_ops(pt, wp)
        # Distances were not too small; return True
        else:
            return pt, wp, valid_ori