Exemple #1
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
Exemple #2
0
    def space_group_irreps(self, *k: Array) -> Array:
        """
        Returns the portion of the character table of the full space group corresponding
        to the star of the wave vector *k*.

        Arguments:
            k: the wave vector in Cartesian axes

        Returns:
            An array `CT` listing the characters for a number of irreps of the
            space group.
            `CT[i]` for each `i` gives a distinct irrep, each corresponding to
            `self.little_group(k).character_table[i].
            `CT[i,j]` gives the character of `self.space_group[j]` in the same.
        """
        k = _ensure_iterable(k)
        # Wave vectors
        big_star_Cart = np.tensordot(self.point_group_.matrices(), k, axes=1)
        big_star = self.lattice.to_reciprocal_lattice(big_star_Cart) * (
            2 * pi / self.lattice.extent)
        # Little-group-irrep factors
        # Conjugacy_table[g,p] lists p^{-1}gp, so point_group_factors[i,:,p]
        #     of irrep #i for the little group of p(k) is the equivalent
        # Phase factor for non-symmorphic symmetries is exp(-i w_g . p(k))
        point_group_factors = self._little_group_irreps(
            k, divide=True
        )[:, self.point_group_.conjugacy_table] * np.exp(-1j * np.tensordot(
            self.point_group_.translations(), big_star_Cart, axes=(-1, -1)))
        # Translational factors
        trans_factors = []
        for axis in range(self.lattice.ndim):
            n_trans = self.lattice.extent[axis] if self.lattice.pbc[axis] else 1
            factors = np.exp(-1j *
                             np.outer(np.arange(n_trans), big_star[:, axis]))
            shape = ([1] * axis + [n_trans] + [1] *
                     (self.lattice.ndim - 1 - axis) + [len(self.point_group_)])
            trans_factors.append(factors.reshape(shape))
        trans_factors = reduce(np.multiply,
                               trans_factors).reshape(-1,
                                                      len(self.point_group_))

        # Multiply the factors together and sum over the "p" PGSymmetry axis
        # Translations are more major than point group operations
        result = np.einsum("igp, tp -> itg", point_group_factors,
                           trans_factors).reshape(point_group_factors.shape[0],
                                                  -1)
        return prune_zeros(result)
Exemple #3
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