Ejemplo n.º 1
0
    def _clean_site_offsets(site_offsets, atoms_coord, basis_vectors):
        """Check and convert `site_offsets` init argument."""
        if atoms_coord is not None and site_offsets is not None:
            raise ValueError(
                "atoms_coord is deprecated and replaced by site_offsets, "
                "so both cannot be specified at the same time.")
        if atoms_coord is not None:
            warnings.warn(
                "atoms_coord is deprecated and may be removed in future versions, "
                "please use site_offsets instead",
                FutureWarning,
            )
            site_offsets = atoms_coord

        if site_offsets is None:
            site_offsets = _np.zeros(basis_vectors.shape[0])[None, :]

        site_offsets = _np.asarray(site_offsets)
        fractional_coords = site_offsets @ _np.linalg.inv(basis_vectors)
        fractional_coords_int = comparable_periodic(fractional_coords)
        # Check for duplicates (also across unit cells)
        uniques, idx = _np.unique(fractional_coords_int,
                                  axis=0,
                                  return_index=True)
        if len(site_offsets) != len(uniques):
            site_offsets = site_offsets[idx]
            fractional_coords = fractional_coords[idx]
            fractional_coords_int = fractional_coords_int[idx]
            warnings.warn(
                "Some atom positions are not unique. Duplicates were dropped, and "
                f"now atom positions are {site_offsets}",
                UserWarning,
            )
        # Check if any site is outside primitive cell (may cause KDTree to malfunction)
        if _np.any(fractional_coords_int < comparable(0.0)) or _np.any(
                fractional_coords_int > comparable(1.0)):

            warnings.warn(
                "Some sites were specified outside the primitive unit cell. This may"
                "cause errors in automatic edge finding.",
                UserWarning,
            )
        return site_offsets, fractional_coords
Ejemplo n.º 2
0
def get_naive_edges(positions, cutoff, order):
    """
    Given an array of spatial `positions`, returns a list `es`, so that
    `es[k]` contains all pairs of (k + 1)-nearest neighbors up to `order`.
    Only edges up to distance `cutoff` are considered.
    """
    kdtree = cKDTree(positions)
    dist_matrix = kdtree.sparse_distance_matrix(kdtree, cutoff)
    row, col, dst = find(triu(dist_matrix))
    dst = comparable(dst)
    _, ii = np.unique(dst, return_inverse=True)

    return [sorted(list(zip(row[ii == k], col[ii == k]))) for k in range(order)]
Ejemplo n.º 3
0
    def character_table_by_class(self) -> Array:
        r"""
        Calculates the character table using Burnside's algorithm.

        Each row of the output lists the characters of one irrep in the order the
        conjugacy classes are listed in `self.conjugacy_classes`.

        Assumes that `Identity() == self[0]`, if not, the sign of some characters
        may be flipped. The irreps are sorted by dimension.
        """
        classes, _, _ = self.conjugacy_classes
        class_sizes = classes.sum(axis=1)
        # Construct a random linear combination of the class matrices c_S
        #    (c_S)_{RT} = #{r,s: r \in R, s \in S: rs = t}
        # for conjugacy classes R,S,T, and a fixed t \in T.
        #
        # From our oblique times table it is easier to calculate
        #    (d_S)_{RT} = #{r,t: r \in R, t \in T: rs = t}
        # for a fixed s \in S. This is just `product_table == s`, aggregrated
        # over conjugacy classes. c_S and d_S are related by
        #    c_{RST} = |S| d_{RST} / |T|;
        # since we only want a random linear combination, we forget about the
        # constant |S| and only divide each column through with the appropriate |T|
        class_matrix = (classes @ np.random.uniform(
            size=len(self))[self.product_table] @ classes.T)
        class_matrix /= class_sizes

        # The vectors |R|\chi(r) are (column) eigenvectors of all class matrices
        # the random linear combination ensures (with probability 1) that
        # none of them are degenerate
        _, table = np.linalg.eig(class_matrix)
        table = table.T / class_sizes

        # Normalise the eigenvectors by orthogonality: \sum_g |\chi(g)|^2 = |G|
        norm = np.sum(np.abs(table)**2 * class_sizes, axis=1,
                      keepdims=True)**0.5
        table /= norm
        table /= _cplx_sign(table[:, 0])[:, np.newaxis]  # ensure correct sign
        table *= len(self)**0.5

        # Sort lexicographically, ascending by first column, descending by others
        sorting_table = np.column_stack((table.real, table.imag))
        sorting_table[:, 1:] *= -1
        sorting_table = comparable(sorting_table)
        _, indices = np.unique(sorting_table, axis=0, return_index=True)
        table = table[indices]

        # Get rid of annoying nearly-zero entries
        table = prune_zeros(table)

        return table
Ejemplo n.º 4
0
    def _irrep_matrices(self) -> PyTree:
        random = np.random.standard_normal
        # Generate the eigensystem of a random linear combination of matrices
        # in the group's regular representation (namely, product_table == g)
        # The regular representation contains d copies of each d-dimensional irrep
        # all of which return the same eigenvalues with eigenvectors that
        # correspond to one another in the bases of the several irrep copies
        e, vs = np.linalg.eig(random(len(self))[self.product_table])
        e = np.stack((e.real, e.imag))
        # we only need one eigenvector per eigenvalue
        _, idx = np.unique(comparable(e), return_index=True, axis=1)
        vs = vs[:, idx]
        # We will nedd the true product table as well
        true_product_table = self.product_table[self.inverse]

        irreps = []

        for chi in self.character_table():
            # Check which eigenvectors belong to this irrep
            # the last argument of einsum is the regular projector on this irrep
            proj = np.einsum("gi,hi,gh->i", vs.conj(), vs,
                             chi.conj()[self.product_table])
            proj = np.logical_not(np.isclose(proj, 0.0))
            # Pick the first eigenvector in this irrep
            idx = np.arange(vs.shape[1], dtype=int)[proj][0]
            v = vs[:, idx]
            # Generate an orthonormal basis for the irrep spanned by A_reg(g).v
            # for all matrices in the regular representation
            # A basis follows (with probability 1) as d random linear
            # combinations of these vectors; make them orthonormal using QR
            # NB v[product_table] generates a matrix whose columns are A_reg(h).v
            dim = int(np.rint(chi[0].real))
            w, _ = np.linalg.qr(v[true_product_table] @ random(
                (len(self), dim)))
            # Project the regular representation on this basis
            irreps.append(
                prune_zeros(
                    np.einsum("gi,ghj ->hij", w.conj(),
                              w[true_product_table, :])))

        return irreps
Ejemplo n.º 5
0
 def __eq__(self, other):
     if isinstance(other, PGSymmetry):
         return HashableArray(comparable(self._affine)) == HashableArray(
             comparable(other._affine))
     else:
         return False
Ejemplo n.º 6
0
 def __hash__(self):
     return hash(HashableArray(comparable(self._affine)))