Example #1
0
    def __init__(self,
                 dynamical_matrix,
                 cell,
                 dimension,
                 phonon_modes,
                 delta_q=None,
                 derivative_order=None,
                 nac_q_direction=None,
                 factor=VaspToTHz):

        """Class describe atomic modulations

        Atomic modulations corresponding to phonon modes are created.
        
        """
        self._dm = dynamical_matrix
        self._cell = cell
        self._phonon_modes = phonon_modes
        self._dimension = dimension
        self._delta_q = delta_q # 1st/2nd order perturbation direction
        self._nac_q_direction = nac_q_direction

        self._ddm = DerivativeOfDynamicalMatrix(dynamical_matrix)

        self._derivative_order = derivative_order

        self._factor = factor
        self._delta_modulations = []
        self._eigvecs = []
        self._eigvals = []
Example #2
0
    def __init__(self,
                 dynamical_matrix,
                 q_length=None,
                 symmetry=None,
                 frequency_factor_to_THz=VaspToTHz,
                 cutoff_frequency=1e-4):
        """
        q_points is a list of sets of q-point and q-direction:
        [[q-point, q-direction], [q-point, q-direction], ...]

        q_length is used such as D(q + q_length) - D(q - q_length).
        """
        self._dynmat = dynamical_matrix
        primitive = dynamical_matrix.get_primitive()
        self._reciprocal_lattice_inv = primitive.get_cell()
        self._reciprocal_lattice = np.linalg.inv(self._reciprocal_lattice_inv)
        self._q_length = q_length
        if q_length is None:
            self._ddm = DerivativeOfDynamicalMatrix(dynamical_matrix)
        else:
            self._ddm = None
        self._symmetry = symmetry
        self._factor = frequency_factor_to_THz
        self._cutoff_frequency = cutoff_frequency

        self._directions = np.array(
            [[1, 2, 3], [1, 0, 0], [0, 1, 0], [0, 0, 1]], dtype='double')
        self._directions[0] /= np.linalg.norm(self._directions[0])

        self._q_points = None
        self._group_velocity = None
        self._perturbation = None
def _assert(ph: Phonopy, ref_vals, show=False):
    dynmat = ph.dynamical_matrix
    ddynmat = DerivativeOfDynamicalMatrix(dynmat)
    ddynmat.run([0, 0.1, 0.1])
    ddm = ddynmat.d_dynamical_matrix
    condition = np.abs(ddm) > 1e-8
    vals = np.extract(condition, ddm).real
    if show:
        _show(vals)
    np.testing.assert_allclose(vals, ref_vals, rtol=0, atol=1e-7)
Example #4
0
    def __init__(
        self,
        dynamical_matrix: Union[DynamicalMatrix, DynamicalMatrixNAC],
        q_length=None,
        symmetry: Optional[Symmetry] = None,
        frequency_factor_to_THz=VaspToTHz,
        cutoff_frequency=1e-4,
    ):
        """Init method.

        dynamical_matrix : DynamicalMatrix or DynamicalMatrixNAC
            Dynamical matrix class instance.
        q_length : float
            This is used such as D(q + q_length) - D(q - q_length) for
            calculating finite difference of dynamical matrix.
            Default is None, which gives 1e-5.
        symmetry : Symmetry
            This is used to symmetrize group velocity at each q-points.
            Default is None, which means no symmetrization.
        frequency_factor_to_THz : float
            Unit conversion factor to convert to THz. Default is VaspToTHz.
        cutoff_frequency : float
            Group velocity is set zero if phonon frequency is below this value.

        """
        self._dynmat = dynamical_matrix
        primitive = dynamical_matrix.primitive
        self._reciprocal_lattice_inv = primitive.cell
        self._reciprocal_lattice = np.linalg.inv(self._reciprocal_lattice_inv)
        self._q_length = q_length
        if self._dynmat.is_nac() and self._dynmat.nac_method == "gonze":
            if self._q_length is None:
                self._q_length = self.Default_q_length

        self._ddm: Optional[DerivativeOfDynamicalMatrix]
        if self._q_length is None:
            self._ddm = DerivativeOfDynamicalMatrix(dynamical_matrix)
        else:
            self._ddm = None

        self._symmetry = symmetry
        self._factor = frequency_factor_to_THz
        self._cutoff_frequency = cutoff_frequency

        self._directions = np.array(
            [[1, 2, 3], [1, 0, 0], [0, 1, 0], [0, 0, 1]], dtype="double"
        )
        self._directions[0] /= np.linalg.norm(self._directions[0])

        self._q_points = None
        self._group_velocities = None
        self._perturbation = None
Example #5
0
    def __init__(self,
                 dynamical_matrix,
                 q_length=None,
                 symmetry=None,
                 frequency_factor_to_THz=VaspToTHz,
                 cutoff_frequency=1e-4,
                 log_level=0):
        """
        q_points is a list of sets of q-point and q-direction:
        [[q-point, q-direction], [q-point, q-direction], ...]

        q_length is used such as D(q + q_length) - D(q - q_length).
        """
        self._dynmat = dynamical_matrix
        primitive = dynamical_matrix.get_primitive()
        self._reciprocal_lattice_inv = primitive.get_cell()
        self._reciprocal_lattice = np.linalg.inv(self._reciprocal_lattice_inv)
        self._q_length = q_length
        if self._dynmat.is_nac() and self._dynmat.get_nac_method() == 'gonze':
            if self._q_length is None:
                self._q_length = 1e-5
                if log_level:
                    print("Group velocity calculation:")
                    text = ("Analytical derivative of dynamical matrix is not "
                            "implemented for NAC by Gonze et al. Instead "
                            "numerical derivative of it is used with dq=1e-5 "
                            "for group velocity calculation.")
                    print(textwrap.fill(text,
                                        initial_indent="  ",
                                        subsequent_indent="  ",
                                        width=70))
        if self._q_length is None:
            self._ddm = DerivativeOfDynamicalMatrix(dynamical_matrix)
        else:
            self._ddm = None
        self._symmetry = symmetry
        self._factor = frequency_factor_to_THz
        self._cutoff_frequency = cutoff_frequency

        self._directions = np.array([[1, 2, 3],
                                     [1, 0, 0],
                                     [0, 1, 0],
                                     [0, 0, 1]], dtype='double')
        self._directions[0] /= np.linalg.norm(self._directions[0])

        self._q_points = None
        self._group_velocity = None
        self._perturbation = None
Example #6
0
    def __init__(
        self,
        dynamical_matrix: Union[DynamicalMatrix, DynamicalMatrixNAC],
        dimension,
        phonon_modes,
        delta_q=None,
        derivative_order=None,
        nac_q_direction=None,
        factor=VaspToTHz,
    ):
        """Init method."""
        self._dm = dynamical_matrix
        self._primitive = dynamical_matrix.primitive
        self._phonon_modes = phonon_modes
        self._dimension = np.array(dimension).ravel()
        self._delta_q = delta_q  # 1st/2nd order perturbation direction
        self._nac_q_direction = nac_q_direction
        self._ddm = DerivativeOfDynamicalMatrix(dynamical_matrix)
        self._derivative_order = derivative_order

        self._factor = factor
        dim = self._get_dimension_3x3()
        self._supercell = get_supercell(self._primitive, dim)
        complex_dtype = "c%d" % (np.dtype("double").itemsize * 2)
        self._u = np.zeros(
            (len(self._phonon_modes), len(self._supercell), 3),
            dtype=complex_dtype,
            order="C",
        )
        self._eigvals = np.zeros(len(self._phonon_modes), dtype="double")
        self._eigvecs = np.zeros(
            (len(self._phonon_modes), len(self._primitive) * 3), dtype=complex_dtype
        )
    def __init__(self,
                 dynamical_matrix,
                 dimension,
                 phonon_modes,
                 delta_q=None,
                 derivative_order=None,
                 nac_q_direction=None,
                 factor=VaspToTHz):
        """Class describe atomic modulations

        Atomic modulations corresponding to phonon modes are created.

        """
        self._dm = dynamical_matrix
        self._primitive = dynamical_matrix.get_primitive()
        self._phonon_modes = phonon_modes
        self._dimension = np.array(dimension).ravel()
        self._delta_q = delta_q  # 1st/2nd order perturbation direction
        self._nac_q_direction = nac_q_direction
        self._ddm = DerivativeOfDynamicalMatrix(dynamical_matrix)
        self._derivative_order = derivative_order

        self._factor = factor
        self._u = []
        self._eigvecs = []
        self._eigvals = []
        self._supercell = None

        dim = self._get_dimension_3x3()
        self._supercell = get_supercell(self._primitive, dim)
Example #8
0
    def __init__(self,
                 dynamical_matrix,
                 q_points=None,
                 symmetry=None,
                 q_length=None,
                 frequency_factor_to_THz=VaspToTHz):
        """
        q_points is a list of sets of q-point and q-direction:
        [[q-point, q-direction], [q-point, q-direction], ...]

        q_length is used such as D(q + q_length) - D(q - q_length).
        """
        self._dynmat = dynamical_matrix
        primitive = dynamical_matrix.get_primitive()
        self._reciprocal_lattice_inv = primitive.get_cell()
        self._reciprocal_lattice = np.linalg.inv(self._reciprocal_lattice_inv)
        self._q_points = q_points
        self._q_length = q_length
        self._symmetry = symmetry
        self._perturation = None
        if q_length is None:
            self._ddm = DerivativeOfDynamicalMatrix(dynamical_matrix)
        else:
            self._ddm = None
        self._factor = frequency_factor_to_THz

        self._directions = np.array([[1, 2, 3],
                                     [1, 0, 0],
                                     [0, 1, 0],
                                     [0, 0, 1]], dtype='double')
        self._directions[0] /= np.linalg.norm(self._directions[0])

        self._group_velocity = None
        if self._q_points is not None:
            self._set_group_velocity()
Example #9
0
    def __init__(self,
                 dynamical_matrix,
                 q,
                 is_little_cogroup=False,
                 nac_q_direction=None,
                 factor=VaspToTHz,
                 symprec=1e-5,
                 degeneracy_tolerance=1e-5,
                 log_level=0):
        self._is_little_cogroup = is_little_cogroup
        self._nac_q_direction = nac_q_direction
        self._factor = factor
        self._log_level = log_level

        self._q = np.array(q)
        self._degeneracy_tolerance = degeneracy_tolerance
        self._symprec = symprec
        self._primitive = dynamical_matrix.get_primitive()
        self._dynamical_matrix = dynamical_matrix
        self._ddm = DerivativeOfDynamicalMatrix(dynamical_matrix)
Example #10
0
def _get_dD(q, ddm: DerivativeOfDynamicalMatrix, perturbation):
    """Return q-vector derivative of dynamical matrix.

    Returns
    -------
    shape=(3, num_band, num_band).

    """
    ddm.run(q)
    ddm_vals = ddm.get_derivative_of_dynamical_matrix()
    dD = np.zeros(ddm_vals.shape[1:], dtype=ddm_vals.dtype, order="C")
    if len(ddm_vals) == 3:
        for i in range(3):
            dD += perturbation[i] * ddm_vals[i]
        return dD / np.linalg.norm(perturbation)
    else:
        dD += perturbation[0] * perturbation[0] * ddm_vals[0]
        dD += perturbation[1] * perturbation[1] * ddm_vals[1]
        dD += perturbation[2] * perturbation[2] * ddm_vals[2]
        dD += 2 * perturbation[0] * perturbation[1] * ddm_vals[5]
        dD += 2 * perturbation[0] * perturbation[2] * ddm_vals[4]
        dD += 2 * perturbation[1] * perturbation[2] * ddm_vals[3]
        return dD / np.linalg.norm(perturbation)**2
Example #11
0
    def __init__(
        self,
        dynamical_matrix: Union[DynamicalMatrix, DynamicalMatrixNAC],
        q,
        is_little_cogroup=False,
        nac_q_direction=None,
        factor=VaspToTHz,
        symprec=1e-5,
        degeneracy_tolerance=None,
        log_level=0,
    ):
        """Init method."""
        self._is_little_cogroup = is_little_cogroup
        self._nac_q_direction = nac_q_direction
        self._factor = factor
        self._log_level = log_level

        self._q = np.array(q)
        if degeneracy_tolerance is None:
            self._degeneracy_tolerance = 1e-5
        else:
            self._degeneracy_tolerance = degeneracy_tolerance
        self._symprec = symprec
        self._primitive = dynamical_matrix.primitive
        self._dynamical_matrix = dynamical_matrix
        self._ddm = DerivativeOfDynamicalMatrix(dynamical_matrix)
        self._character_table = None

        self._symmetry_dataset = Symmetry(
            self._primitive, symprec=self._symprec
        ).dataset

        if not is_primitive_cell(self._symmetry_dataset["rotations"]):
            raise RuntimeError(
                "Non-primitve cell is used. Your unit cell may be transformed to "
                "a primitive cell by PRIMITIVE_AXIS tag."
            )
Example #12
0
def get_eigenvectors(
    q,
    dm: Union[DynamicalMatrix, DynamicalMatrixNAC],
    ddm: DerivativeOfDynamicalMatrix,
    perturbation=None,
    derivative_order=None,
    nac_q_direction=None,
):
    """Return degenerated eigenvalues and rotated eigenvalues."""
    if nac_q_direction is not None and (np.abs(q) < 1e-5).all():
        dm.run(q, q_direction=nac_q_direction)
    else:
        dm.run(q)
    eigvals, eigvecs = np.linalg.eigh(dm.dynamical_matrix)
    eigvals = eigvals.real
    if perturbation is None:
        return eigvals, eigvecs

    if derivative_order is not None:
        ddm.set_derivative_order(derivative_order)
    dD = _get_dD(q, ddm, perturbation)
    rot_eigvecs, _ = rotate_eigenvectors(eigvals, eigvecs, dD)

    return eigvals, rot_eigvecs
Example #13
0
    def __init__(self,
                 dynamical_matrix,
                 q,
                 is_little_cogroup=False,
                 nac_q_direction=None,
                 factor=VaspToTHz,
                 symprec=1e-5,
                 degeneracy_tolerance=1e-5,
                 log_level=0):
        self._is_little_cogroup = is_little_cogroup
        self._nac_q_direction = nac_q_direction
        self._factor = factor
        self._log_level = log_level

        self._q = np.array(q)
        self._degeneracy_tolerance = degeneracy_tolerance
        self._symprec = symprec
        self._primitive = dynamical_matrix.get_primitive()
        self._dynamical_matrix = dynamical_matrix
        self._ddm = DerivativeOfDynamicalMatrix(dynamical_matrix)
Example #14
0
class IrReps:
    def __init__(self,
                 dynamical_matrix,
                 q,
                 is_little_cogroup=False,
                 nac_q_direction=None,
                 factor=VaspToTHz,
                 symprec=1e-5,
                 degeneracy_tolerance=1e-5,
                 log_level=0):
        self._is_little_cogroup = is_little_cogroup
        self._nac_q_direction = nac_q_direction
        self._factor = factor
        self._log_level = log_level

        self._q = np.array(q)
        self._degeneracy_tolerance = degeneracy_tolerance
        self._symprec = symprec
        self._primitive = dynamical_matrix.get_primitive()
        self._dynamical_matrix = dynamical_matrix
        self._ddm = DerivativeOfDynamicalMatrix(dynamical_matrix)

    def run(self):
        self._set_eigenvectors(self._dynamical_matrix)
        self._symmetry_dataset = Symmetry(self._primitive,
                                          self._symprec).get_dataset()

        if not self._is_primitive_cell():
            print('')
            print("Non-primitve cell is used.")
            print("Your unit cell may be transformed to a primitive cell "
                  "by PRIMITIVE_AXIS tag.")
            return False

        (self._rotations_at_q,
         self._translations_at_q) = self._get_rotations_at_q()

        self._g = len(self._rotations_at_q)

        (self._pointgroup_symbol, self._transformation_matrix,
         self._conventional_rotations) = self._get_conventional_rotations()

        self._ground_matrices = self._get_ground_matrix()
        self._degenerate_sets = self._get_degenerate_sets()
        self._irreps = self._get_irreps()
        self._characters, self._irrep_dims = self._get_characters()

        self._ir_labels = None
        if self._pointgroup_symbol in character_table.keys():
            self._rotation_symbols = self._get_rotation_symbols()
            if (abs(self._q) < self._symprec).all() and self._rotation_symbols:
                self._ir_labels = self._get_ir_labels()
            elif (abs(self._q) < self._symprec).all():
                if self._log_level > 0:
                    print("Database for this point group is not preprared.")
            else:
                if self._log_level > 0:
                    print("Database for non-Gamma point is not prepared.")
        else:
            self._rotation_symbols = None

        return True

    def _get_degenerate_sets(self):
        deg_sets = get_degenerate_sets(self._freqs,
                                       cutoff=self._degeneracy_tolerance)
        self._ddm.run(self._q)
        ddm_q = np.sum([
            self._ddm.get_derivative_of_dynamical_matrix()[i] * self._q[i]
            for i in range(3)
        ],
                       axis=0)
        return deg_sets

    def get_band_indices(self):
        return self._degenerate_sets

    def get_characters(self):
        return self._characters

    def get_eigenvectors(self):
        return self._eigvecs

    def get_irreps(self):
        return self._irreps

    def get_ground_matrices(self):
        return self._ground_matrices

    def get_rotation_symbols(self):
        return self._rotation_symbols

    def get_rotations(self):
        return self._conventional_rotations

    def get_projection_operators(self, idx_irrep, i=None, j=None):
        if i is None or j is None:
            return self._get_character_projection_operators(idx_irrep)
        else:
            return self._get_projection_operators(idx_irrep, i, j)

    def show(self, show_irreps=False):
        self._show(show_irreps)

    def write_yaml(self, show_irreps=False):
        self._write_yaml(show_irreps)

    def _set_eigenvectors(self, dm):
        if self._nac_q_direction is not None and (np.abs(self._q) <
                                                  1e-5).all():
            dm.set_dynamical_matrix(self._q, q_direction=self._nac_q_direction)
        else:
            dm.set_dynamical_matrix(self._q)
        eigvals, self._eigvecs = np.linalg.eigh(dm.get_dynamical_matrix())
        self._freqs = np.sqrt(abs(eigvals)) * np.sign(eigvals) * self._factor

    def _get_rotations_at_q(self):
        rotations_at_q = []
        trans_at_q = []
        for r, t in zip(self._symmetry_dataset['rotations'],
                        self._symmetry_dataset['translations']):

            # Using r is used instead of np.linalg.inv(r)
            diff = np.dot(self._q, r) - self._q

            if (abs(diff - np.rint(diff)) < self._symprec).all():
                rotations_at_q.append(r)
                for i in range(3):
                    if np.abs(t[i] - 1) < self._symprec:
                        t[i] = 0.0
                trans_at_q.append(t)

        return np.array(rotations_at_q), np.array(trans_at_q)

    def _get_conventional_rotations(self):
        spacegroup_symbol = self._symmetry_dataset['international'][0]
        spacegroup_number = self._symmetry_dataset['number']
        rotations = self._rotations_at_q.copy()
        pointgroup = get_pointgroup(rotations)
        pointgroup_symbol = pointgroup[0]
        transformation_matrix = pointgroup[1]

        conventional_rotations = self._transform_rotations(
            transformation_matrix, rotations)

        return (pointgroup_symbol, transformation_matrix,
                conventional_rotations)

    def _transform_rotations(self, tmat, rotations):
        trans_rots = []

        for r in rotations:
            r_conv = similarity_transformation(np.linalg.inv(tmat), r)
            trans_rots.append(np.rint(r_conv).astype(int))

        return np.array(trans_rots)

    def _get_ground_matrix(self):
        matrices = []

        for (r, t) in zip(self._rotations_at_q, self._translations_at_q):

            lat = self._primitive.get_cell().T
            r_cart = similarity_transformation(lat, r)

            perm_mat = self._get_modified_permutation_matrix(r, t)
            matrices.append(np.kron(perm_mat, r_cart))

        return np.array(matrices)

    def _get_characters(self):
        characters = []
        irrep_dims = []
        for irrep_Rs in self._irreps:
            characters.append([np.trace(rep) for rep in irrep_Rs])
            irrep_dims.append(len(irrep_Rs[0]))
        return np.array(characters), np.array(irrep_dims)

    def _get_modified_permutation_matrix(
        self,
        r,
        t,
    ):
        num_atom = self._primitive.get_number_of_atoms()
        pos = self._primitive.get_scaled_positions()
        matrix = np.zeros((num_atom, num_atom), dtype=complex)
        for i, p1 in enumerate(pos):
            p_rot = np.dot(r, p1) + t
            for j, p2 in enumerate(pos):
                diff = p_rot - p2
                if (abs(diff - np.rint(diff)) < self._symprec).all():
                    # For this phase factor, see
                    # Dynamics of perfect crystals by G. Venkataraman et al.,
                    # pp132 Eq. (3.22).
                    # It is assumed that dynamical matrix is built without
                    # considering internal atomic positions, so
                    # the phase factors of eigenvectors are shifted in
                    # _get_irreps().
                    phase_factor = np.dot(self._q,
                                          np.dot(np.linalg.inv(r), p2 - p_rot))

                    # This phase factor comes from non-pure-translation of
                    # each symmetry opration.
                    if self._is_little_cogroup:
                        phase_factor += np.dot(t, self._q)

                    matrix[j, i] = np.exp(2j * np.pi * phase_factor)

        return matrix

    def _get_irreps(self):
        eigvecs = []
        phases = np.kron([
            np.exp(2j * np.pi * np.dot(self._q, pos))
            for pos in self._primitive.get_scaled_positions()
        ], [1, 1, 1])
        for vec in self._eigvecs.T:
            eigvecs.append(vec * phases)

        irrep = []
        for band_indices in self._degenerate_sets:
            irrep_Rs = []
            for mat in self._ground_matrices:
                l = len(band_indices)

                if l == 1:
                    vec = eigvecs[band_indices[0]]
                    irrep_Rs.append([[np.vdot(vec, np.dot(mat, vec))]])
                    continue

                irrep_R = np.zeros((l, l), dtype=complex)
                for i, b_i in enumerate(band_indices):
                    vec_i = eigvecs[b_i]
                    for j, b_j in enumerate(band_indices):
                        vec_j = eigvecs[b_j]
                        irrep_R[i, j] = np.vdot(vec_i, np.dot(mat, vec_j))
                irrep_Rs.append(irrep_R)

            irrep.append(irrep_Rs)

        return irrep

    def _get_character_projection_operators(self, idx_irrep):
        dim = self._irrep_dims[idx_irrep]
        chars = self._characters[idx_irrep]
        return np.sum([
            mat * char.conj()
            for mat, char in zip(self._ground_matrices, chars)
        ],
                      axis=0) * dim / self._g

    def _get_projection_operators(self, idx_irrep, i, j):
        dim = self._irrep_dims[idx_irrep]
        return np.sum([
            mat * r[i, j].conj()
            for mat, r in zip(self._ground_matrices, self._irreps[idx_irrep])
        ],
                      axis=0) * dim / self._g

    def _get_rotation_symbols(self):
        ptg = self._pointgroup_symbol
        for mapping_table in character_table[ptg]['mapping_table']:
            rotation_symbols = []
            for r in self._conventional_rotations:
                symbol = get_rotation_symbol(r, mapping_table)
                rotation_symbols.append(symbol)

            if not False in rotation_symbols:
                break

        if False in rotation_symbols:
            return None
        else:
            return rotation_symbols

    def _get_ir_labels(self):
        ir_labels = []
        rot_list = character_table[self._pointgroup_symbol]['rotation_list']
        char_table = character_table[
            self._pointgroup_symbol]['character_table']
        for chars, deg_set in zip(self._characters, self._degenerate_sets):
            chars_ordered = np.zeros(len(rot_list), dtype=complex)
            for rs, ch in zip(self._rotation_symbols, chars):
                chars_ordered[rot_list.index(rs)] += ch

            for i, rl in enumerate(rot_list):
                chars_ordered[i] /= len(character_table[
                    self._pointgroup_symbol]['mapping_table'][0][rl])

            found = False
            for ct_label in char_table.keys():
                if (abs(chars_ordered - np.array(char_table[ct_label])) <
                        self._symprec).all():
                    ir_labels.append(ct_label)
                    found = True
                    break

            if not found:
                ir_labels.append(None)

            if self._log_level > 1:
                text = ""
                for v in chars_ordered:
                    text += "%5.2f " % abs(v)
                if found:
                    print("%s %s" % (text, ct_label))
                else:
                    print("%s Not found" % text)

        return ir_labels

    def _is_primitive_cell(self):
        num_identity = 0
        for r in self._symmetry_dataset['rotations']:
            if (r - np.eye(3, dtype='intc') == 0).all():
                num_identity += 1
                if num_identity > 1:
                    return False
        else:
            return True

    def _show(self, show_irreps):
        print('')
        print("-------------------------------")
        print("  Irreducible representations")
        print("-------------------------------")
        print("q-point: %s" % self._q)
        print("Point group: %s" % self._pointgroup_symbol)
        print('')

        if (np.abs(self._q) < self._symprec).all():
            width = 6
            print("Original rotation matrices:")
            print('')
            print_rotations(self._rotations_at_q, width=width)
        else:
            width = 4
            print("Original symmetry operations:")
            print('')
            print_rotations(self._rotations_at_q,
                            translations=self._translations_at_q,
                            width=width)

        print("Transformation matrix:")
        print('')
        for v in self._transformation_matrix:
            print("%6.3f %6.3f %6.3f" % tuple(v))
        print('')
        print("Rotation matrices by transformation matrix:")
        print('')
        print_rotations(self._conventional_rotations,
                        rotation_symbols=self._rotation_symbols,
                        width=width)
        print("Character table:")
        print('')
        for i, deg_set in enumerate(self._degenerate_sets):
            text = "%3d (%8.3f): " % (deg_set[0] + 1, self._freqs[deg_set[0]])
            if self._ir_labels is None:
                print(text)
            else:
                print(text + self._ir_labels[i])
            print_characters(self._characters[i])
            print('')

        if show_irreps:
            self._show_irreps()

    def _show_irreps(self):
        print("IR representations:")
        print('')

        for i, (deg_set,
                irrep_Rs) in enumerate(zip(self._degenerate_sets,
                                           self._irreps)):
            print("%3d (%8.3f):" % (deg_set[0] + 1, self._freqs[deg_set[0]]))
            print('')
            for j, irrep_R in enumerate(irrep_Rs):
                for k in range(len(irrep_R)):
                    text = "     "
                    for l in range(len(irrep_R)):
                        if irrep_R[k][l].real > 0:
                            sign_r = " "
                        else:
                            sign_r = "-"

                        if irrep_R[k][l].imag > 0:
                            sign_i = "+"
                        else:
                            sign_i = "-"

                        if k == 0:
                            str_index = "%2d" % (j + 1)
                        else:
                            str_index = "  "

                        if l > 0:
                            str_index = ''

                        text += "%s (%s%5.3f %s%5.3fi) " % (
                            str_index, sign_r, abs(irrep_R[k][l].real), sign_i,
                            abs(irrep_R[k][l].imag))
                    print(text)
                if len(irrep_R) > 1:
                    print('')
            if len(irrep_R) == 1:
                print('')

    def _write_yaml(self, show_irreps):
        w = open("irreps.yaml", 'w')
        w.write("q-position: [ %12.7f, %12.7f, %12.7f ]\n" % tuple(self._q))
        w.write("point_group: %s\n" % self._pointgroup_symbol)
        w.write("transformation_matrix:\n")
        for v in self._transformation_matrix:
            w.write("- [ %10.7f, %10.7f, %10.7f ]\n" % tuple(v))
        w.write("rotations:\n")
        for i, r in enumerate(self._conventional_rotations):
            w.write("- matrix:\n")
            for v in r:
                w.write("  - [ %2d, %2d, %2d ]\n" % tuple(v))
            if self._rotation_symbols:
                w.write("  symbol: %s\n" % self._rotation_symbols[i])
        w.write("normal_modes:\n")
        for i, deg_set in enumerate(self._degenerate_sets):
            w.write("- band_indices: [ ")
            w.write("%d" % (deg_set[0] + 1))
            for bi in deg_set[1:]:
                w.write(", %d" % (bi + 1))
            w.write(" ]\n")
            w.write("  frequency: %-15.10f\n" % self._freqs[deg_set[0]])
            if self._ir_labels:
                w.write("  ir_label: %s\n" % self._ir_labels[i])
            w.write("  characters: ")
            chars = np.rint(np.abs(self._characters[i]))
            phase = (np.angle(self._characters[i]) / np.pi * 180) % 360
            if len(chars) > 1:
                w.write("[ [ %2d, %5.1f ]" % (chars[0], phase[0]))
                for chi, theta in zip(chars[1:], phase[1:]):
                    w.write(", [ %2d, %5.1f ]" % (chi, theta))
                w.write(" ]\n")
            else:
                w.write("[ [ %2d, %5.1f ] ]\n" % (chars[0], phase[0]))

        if show_irreps:
            self._write_yaml_irreps(w)

        w.close()

    def _write_yaml_irreps(self, file_pointer):
        w = file_pointer
        if not self._irreps:
            self._irrep = self._get_irreps()

        w.write("\n")
        w.write("irreps:\n")
        for i, (deg_set,
                irrep_Rs) in enumerate(zip(self._degenerate_sets,
                                           self._irreps)):
            w.write("- # %d\n" % (i + 1))
            for j, irrep_R in enumerate(irrep_Rs):
                if self._rotation_symbols:
                    symbol = self._rotation_symbols[j]
                else:
                    symbol = ''
                if len(deg_set) > 1:
                    w.write("  - # %d %s\n" % (j + 1, symbol))
                    for k, v in enumerate(irrep_R):
                        w.write("    - [ ")
                        for x in v[:-1]:
                            w.write("%10.7f, %10.7f,   " % (x.real, x.imag))
                        w.write("%10.7f, %10.7f ] # (" %
                                (v[-1].real, v[-1].imag))

                        w.write(("%5.0f" * len(v)) % tuple(
                            (np.angle(v) / np.pi * 180) % 360))
                        w.write(")\n")
                else:
                    x = irrep_R[0][0]
                    w.write("  - [ [ %10.7f, %10.7f ] ] # (%3.0f) %d %s\n" %
                            (x.real, x.imag,
                             (np.angle(x) / np.pi * 180) % 360, j + 1, symbol))

        pass
Example #15
0
class GroupVelocity(object):
    r"""Class to calculate group velocities of phonons

    d omega   ----
    ------- = \  / omega
    d q        \/q

    Gradient of omega in reciprocal space, which is calculated here by

       1             d D(q)
    ------- <e(q,nu)|------|e(q,nu)>
    2 omega           d q

    Attributes
    ----------
    group_velocity : ndarray
        Group velocities at q-points.
        shape=(q-points, 3), dtype='double', order='C'
    q_length : float
        Distance in reciprocal space used to calculate finite difference of
        dynamcial matrix.

    """
    def __init__(self,
                 dynamical_matrix,
                 q_length=None,
                 symmetry=None,
                 frequency_factor_to_THz=VaspToTHz,
                 cutoff_frequency=1e-4):
        """

        dynamical_matrix : DynamicalMatrix or DynamicalMatrixNAC
            Dynamical matrix class instance.
        q_length : float
            This is used such as D(q + q_length) - D(q - q_length) for
            calculating finite difference of dynamical matrix.
            Default is None, which gives 1e-5.
        symmetry : Symmetry
            This is used to symmetrize group velocity at each q-points.
            Default is None, which means no symmetrization.
        frequency_factor_to_THz : float
            Unit conversion factor to convert to THz. Default is VaspToTHz.
        cutoff_frequency : float
            Group velocity is set zero if phonon frequency is below this value.

        """
        self._dynmat = dynamical_matrix
        primitive = dynamical_matrix.get_primitive()
        self._reciprocal_lattice_inv = primitive.get_cell()
        self._reciprocal_lattice = np.linalg.inv(self._reciprocal_lattice_inv)
        self._q_length = q_length
        if self._dynmat.is_nac() and self._dynmat.get_nac_method() == 'gonze':
            if self._q_length is None:
                self._q_length = 1e-5
        if self._q_length is None:
            self._ddm = DerivativeOfDynamicalMatrix(dynamical_matrix)
        else:
            self._ddm = None
        self._symmetry = symmetry
        self._factor = frequency_factor_to_THz
        self._cutoff_frequency = cutoff_frequency

        self._directions = np.array(
            [[1, 2, 3], [1, 0, 0], [0, 1, 0], [0, 0, 1]], dtype='double')
        self._directions[0] /= np.linalg.norm(self._directions[0])

        self._q_points = None
        self._group_velocities = None
        self._perturbation = None

    def run(self, q_points, perturbation=None):
        """Group velocities are computed at q-points.

        q_points : Array-like
            List of q-points such as [[0, 0, 0], [0.1, 0.2, 0.3], ...].
        perturbation : Array-like
            Direction in fractional coordinates of reciprocal space.

        """

        self._q_points = q_points
        self._perturbation = perturbation
        if perturbation is None:
            # Give an random direction to break symmetry
            self._directions[0] = np.array([1, 2, 3])
        else:
            self._directions[0] = np.dot(self._reciprocal_lattice,
                                         perturbation)
        self._directions[0] /= np.linalg.norm(self._directions[0])

        gv = [self._calculate_group_velocity_at_q(q) for q in self._q_points]
        self._group_velocities = np.array(gv, dtype='double', order='C')

    @property
    def q_length(self):
        return self._q_length

    def get_q_length(self):
        return self.q_length

    @q_length.setter
    def q_length(self, q_length):
        self._q_length = q_length

    def set_q_length(self, q_length):
        self.q_length = q_length

    @property
    def group_velocities(self):
        return self._group_velocities

    def get_group_velocity(self):
        return self.group_velocities

    def _calculate_group_velocity_at_q(self, q):
        self._dynmat.set_dynamical_matrix(q)
        dm = self._dynmat.get_dynamical_matrix()
        eigvals, eigvecs = np.linalg.eigh(dm)
        eigvals = eigvals.real
        freqs = np.sqrt(abs(eigvals)) * np.sign(eigvals) * self._factor
        gv = np.zeros((len(freqs), 3), dtype='double', order='C')
        deg_sets = degenerate_sets(freqs)

        ddms = self._get_dD(np.array(q))
        pos = 0
        for deg in deg_sets:
            gv[pos:pos + len(deg)] = self._perturb_D(ddms, eigvecs[:, deg])
            pos += len(deg)

        for i, f in enumerate(freqs):
            if f > self._cutoff_frequency:
                gv[i, :] *= self._factor**2 / f / 2
            else:
                gv[i, :] = 0

        if self._perturbation is None:
            if self._symmetry is None:
                return gv
            else:
                return self._symmetrize_group_velocity(gv, q)
        else:
            return gv

    def _symmetrize_group_velocity(self, gv, q):
        """Symmetrize obtained group velocities using site symmetries."""

        rotations = []
        for r in self._symmetry.get_reciprocal_operations():
            q_in_BZ = q - np.rint(q)
            diff = q_in_BZ - np.dot(r, q_in_BZ)
            if (np.abs(diff) < self._symmetry.get_symmetry_tolerance()).all():
                rotations.append(r)

        gv_sym = np.zeros_like(gv)
        for r in rotations:
            r_cart = similarity_transformation(self._reciprocal_lattice, r)
            gv_sym += np.dot(r_cart, gv.T).T

        return gv_sym / len(rotations)

    def _get_dD(self, q):
        """Compute derivative or finite difference of dynamcial matrices"""

        if self._q_length is None:
            return self._get_dD_analytical(q)
        else:
            return self._get_dD_FD(q)

    def _get_dD_FD(self, q):
        """Compute finite difference of dynamcial matrices"""

        ddm = []
        for dqc in self._directions * self._q_length:
            dq = np.dot(self._reciprocal_lattice_inv, dqc)
            ddm.append(
                delta_dynamical_matrix(q, dq, self._dynmat) / self._q_length /
                2)
        return np.array(ddm)

    def _get_dD_analytical(self, q):
        """Compute derivative of dynamcial matrices"""

        self._ddm.run(q)
        ddm = self._ddm.get_derivative_of_dynamical_matrix()
        dtype = "c%d" % (np.dtype('double').itemsize * 2)
        ddm_dirs = np.zeros((len(self._directions), ) + ddm.shape[1:],
                            dtype=dtype)
        for i, dq in enumerate(self._directions):
            for j in range(3):
                ddm_dirs[i] += dq[j] * ddm[j]
        return ddm_dirs

    def _perturb_D(self, ddms, eigsets):
        """Treat degeneracy

        Group velocities are calculated using analytical continuation using
        specified directions (self._directions) in reciprocal space.

        ddms : Array-like
            List of delta (derivative or finite difference) of dynamical
            matrices along several q-directions for perturbation.
        eigsets : Array-like
            List of phonon eigenvectors of degenerate bands.

        """

        eigvals, eigvecs = np.linalg.eigh(
            np.dot(eigsets.T.conj(), np.dot(ddms[0], eigsets)))

        gv = []
        rot_eigsets = np.dot(eigsets, eigvecs)
        for ddm in ddms[1:]:
            gv.append(
                np.diag(np.dot(rot_eigsets.T.conj(),
                               np.dot(ddm, rot_eigsets))).real)

        return np.transpose(gv)
Example #16
0
class GroupVelocity:
    """
    d omega   ----
    ------- = \  / omega
    d q        \/q

    Gradient of omega in reciprocal space.

             d D(q)
    <e(q,nu)|------|e(q,nu)>
              d q
    """
    def __init__(self,
                 dynamical_matrix,
                 q_length=None,
                 symmetry=None,
                 frequency_factor_to_THz=VaspToTHz,
                 cutoff_frequency=1e-4):
        """
        q_points is a list of sets of q-point and q-direction:
        [[q-point, q-direction], [q-point, q-direction], ...]

        q_length is used such as D(q + q_length) - D(q - q_length).
        """
        self._dynmat = dynamical_matrix
        primitive = dynamical_matrix.get_primitive()
        self._reciprocal_lattice_inv = primitive.get_cell()
        self._reciprocal_lattice = np.linalg.inv(self._reciprocal_lattice_inv)
        self._q_length = q_length
        if q_length is None:
            self._ddm = DerivativeOfDynamicalMatrix(dynamical_matrix)
        else:
            self._ddm = None
        self._symmetry = symmetry
        self._factor = frequency_factor_to_THz
        self._cutoff_frequency = cutoff_frequency

        self._directions = np.array(
            [[1, 2, 3], [1, 0, 0], [0, 1, 0], [0, 0, 1]], dtype='double')
        self._directions[0] /= np.linalg.norm(self._directions[0])

        self._q_points = None
        self._group_velocity = None
        self._perturbation = None

    def set_q_points(self, q_points, perturbation=None):
        self._q_points = q_points
        self._perturbation = perturbation
        if perturbation is None:
            self._directions[0] = np.array([1, 2, 3])
        else:
            self._directions[0] = np.dot(self._reciprocal_lattice,
                                         perturbation)
        self._directions[0] /= np.linalg.norm(self._directions[0])
        self._set_group_velocity()

    def set_q_length(self, q_length):
        self._q_length = q_length

    def get_group_velocity(self):
        return self._group_velocity

    def _set_group_velocity(self):
        gv = [self._set_group_velocity_at_q(q) for q in self._q_points]
        self._group_velocity = np.array(gv)

    def _set_group_velocity_at_q(self, q):
        self._dynmat.set_dynamical_matrix(q)
        dm = self._dynmat.get_dynamical_matrix()
        eigvals, eigvecs = np.linalg.eigh(dm)
        eigvals = eigvals.real
        freqs = np.sqrt(abs(eigvals)) * np.sign(eigvals) * self._factor
        gv = np.zeros((len(freqs), 3), dtype='double')
        deg_sets = degenerate_sets(freqs)

        ddms = self._get_dD(np.array(q))
        pos = 0
        for deg in deg_sets:
            gv[pos:pos + len(deg)] = self._perturb_D(ddms, eigvecs[:, deg])
            pos += len(deg)

        for i, f in enumerate(freqs):
            if f > self._cutoff_frequency:
                gv[i, :] *= self._factor**2 / f / 2
            else:
                gv[i, :] = 0

        if self._perturbation is None:
            return self._symmetrize_group_velocity(gv, q)
        else:
            return gv

    def _symmetrize_group_velocity(self, gv, q):
        rotations = []
        for r in self._symmetry.get_reciprocal_operations():
            q_in_BZ = q - np.rint(q)
            diff = q_in_BZ - np.dot(r, q_in_BZ)
            if (np.abs(diff) < self._symmetry.get_symmetry_tolerance()).all():
                rotations.append(r)

        gv_sym = np.zeros_like(gv)
        for r in rotations:
            r_cart = similarity_transformation(self._reciprocal_lattice, r)
            gv_sym += np.dot(r_cart, gv.T).T

        return gv_sym / len(rotations)

    def _get_dD(self, q):
        if self._q_length is None:
            return self._get_dD_analytical(q)
        else:
            return self._get_dD_FD(q)

    def _get_dD_FD(self, q):  # finite difference
        ddm = []
        for dqc in self._directions * self._q_length:
            dq = np.dot(self._reciprocal_lattice_inv, dqc)
            ddm.append(
                delta_dynamical_matrix(q, dq, self._dynmat) / self._q_length /
                2)
        return np.array(ddm)

    def _get_dD_analytical(self, q):
        self._ddm.run(q)
        ddm = self._ddm.get_derivative_of_dynamical_matrix()
        ddm_dirs = np.zeros((len(self._directions), ) + ddm.shape[1:],
                            dtype='complex128')
        for i, dq in enumerate(self._directions):
            for j in range(3):
                ddm_dirs[i] += dq[j] * ddm[j]
        return ddm_dirs

    def _perturb_D(self, ddms, eigsets):
        eigvals, eigvecs = np.linalg.eigh(
            np.dot(eigsets.T.conj(), np.dot(ddms[0], eigsets)))

        gv = []
        rot_eigsets = np.dot(eigsets, eigvecs)
        for ddm in ddms[1:]:
            gv.append(
                np.diag(np.dot(rot_eigsets.T.conj(),
                               np.dot(ddm, rot_eigsets))).real)

        return np.transpose(gv)
Example #17
0
class Modulation:
    def __init__(self,
                 dynamical_matrix,
                 cell,
                 dimension,
                 phonon_modes,
                 delta_q=None,
                 derivative_order=None,
                 nac_q_direction=None,
                 factor=VaspToTHz):

        """Class describe atomic modulations

        Atomic modulations corresponding to phonon modes are created.
        
        """
        self._dm = dynamical_matrix
        self._cell = cell
        self._phonon_modes = phonon_modes
        self._dimension = dimension
        self._delta_q = delta_q # 1st/2nd order perturbation direction
        self._nac_q_direction = nac_q_direction

        self._ddm = DerivativeOfDynamicalMatrix(dynamical_matrix)

        self._derivative_order = derivative_order

        self._factor = factor
        self._delta_modulations = []
        self._eigvecs = []
        self._eigvals = []

    def run(self):
        for ph_mode in self._phonon_modes:
            q, band_index, amplitude, argument = ph_mode
            eigvals, eigvecs = self._get_eigenvectors(q)
            u = self._get_delta(eigvecs[:, band_index], q)
            self._eigvecs.append(eigvecs[:, band_index])
            self._eigvals.append(eigvals[band_index])
            # Set phase of modulation so that phase of the element
            # that has maximum absolute value becomes 0.
            self._set_phase_of_modulation(u, argument)
            self._delta_modulations.append(u.reshape(-1,3) * amplitude)

    def write(self, filename="MPOSCAR"):
        deltas = []
        for i, delta_positions in enumerate(self._delta_modulations):
            cell = self._get_cell_with_modulation(delta_positions)
            write_vasp((filename+"-%03d") % (i+1), cell, direct=True)
            deltas.append(delta_positions)
    
        sum_of_deltas = np.sum(deltas, axis=0)
        cell = self._get_cell_with_modulation(sum_of_deltas)
        write_vasp(filename, cell, direct=True)
        no_modulations = np.zeros(sum_of_deltas.shape, dtype=complex)
        cell = self._get_cell_with_modulation(no_modulations)
        write_vasp(filename+"-orig", cell, direct=True)

    def get_modulations(self):
        modulations = []
        for delta_positions in self._delta_modulations:
            modulations.append(self._get_cell_with_modulation(delta_positions))
        return modulations

    def get_delta_modulations(self):
        return self._delta_modulations, self._get_supercell()

    def _get_cell_with_modulation(self, modulation):
        supercell = self._get_supercell()
        lattice = supercell.get_cell()
        positions = supercell.get_positions()
        positions += modulation.real
        scaled_positions = np.dot(positions, np.linalg.inv(lattice))
        for p in scaled_positions:
            p -= np.floor(p)
        supercell.set_scaled_positions(scaled_positions)
    
        return supercell
            
    def _get_delta(self, eigvec, q):
        dim = self._dimension
        m = self._cell.get_masses()
        r = self._cell.get_scaled_positions()
        u = []
        for a, b, c in list(np.ndindex(tuple(dim))):
            for i, e in enumerate(eigvec):
                phase = 2j * np.pi * (
                    np.dot(r[i//3] + np.array([a,b,c]), q))
                u.append(e / np.sqrt(m[i//3]) * np.exp(phase)) 
    
        return np.array(u)

    def _set_phase_of_modulation(self, modulation, argument):
        u = modulation
        index_max_elem = np.argmax(abs(u))
        max_elem = u[index_max_elem]
        phase_for_zero = max_elem / abs(max_elem)
        phase_factor = np.exp(1j * np.pi * argument / 180) / phase_for_zero
        u *= phase_factor

    def _get_supercell(self):
        dim = self._dimension
        scaled_positions = []
        masses = []
        magmoms_prim = self._cell.get_magnetic_moments()
        if magmoms_prim == None:
            magmoms = None
        else:
            magmoms = []
        symbols = []
        for a in range(dim[0]):
            for b in range(dim[1]):
                for c in range(dim[2]):
                    for i in range(self._cell.get_number_of_atoms()):
                        p = self._cell.get_scaled_positions()[i]
                        scaled_positions.append(p + np.array([a,b,c]))
                        masses.append(self._cell.get_masses()[i])
                        symbols.append(self._cell.get_chemical_symbols()[i])
                        if not magmoms_prim == None:
                            magmoms.append(magmoms_prim[i])

        lattice = np.dot(np.diag(dim), self._cell.get_cell())
        positions = np.dot(scaled_positions, self._cell.get_cell())

        return Atoms(cell=lattice,
                     positions=positions,
                     masses=masses,
                     magmoms=magmoms,
                     symbols=symbols,
                     pbc=True)

    def _get_eigenvectors(self, q):
        if self._nac_q_direction is not None and (np.abs(q) < 1e-5).all():
            self._dm.set_dynamical_matrix(q, q_direction=self._nac_q_direction)
        else:        
            self._dm.set_dynamical_matrix(q)
        eigvals, eigvecs = np.linalg.eigh(self._dm.get_dynamical_matrix())
        eigvals = eigvals.real
        if self._delta_q is None:
            return eigvals, eigvecs

        eigvecs_new = np.zeros_like(eigvecs)
        deg_sets = degenerate_sets(eigvals)

        if self._derivative_order is None:
            self._ddm.set_derivative_order(1)
            dD1 = self._get_dD(q)
            self._ddm.set_derivative_order(2)
            dD2 = self._get_dD(q)
        else:
            self._ddm.set_derivative_order(self._derivative_order)
            dD = self._get_dD(q)

        for deg in deg_sets:
            if len(deg) == 1:
                continue
            
            if self._derivative_order is not None:
                eigvecs_new = self._rearrange_eigenvectors(dD, eigvecs[:, deg])
            else:
                eigvecs_new = self._rearrange_eigenvectors(dD1, eigvecs[:, deg])
                if eigvecs_new is None:
                    eigvecs_new = self._rearrange_eigenvectors(
                        dD2, eigvecs[:, deg])

            if eigvecs_new is not None:
                eigvecs[:, deg] = eigvecs_new

        return eigvals, eigvecs

    def _check_eigvecs(self, eigvals, eigvecs, dynmat):
        modified = np.diag(np.dot(eigvecs.conj().T, np.dot(dynmat, eigvecs)))
        print self._eigvals_to_frequencies(eigvals)
        print self._eigvals_to_frequencies(modified)
        print

    def _eigvals_to_frequencies(self, eigvals):
        e = np.array(eigvals).real
        return np.sqrt(np.abs(e)) * np.sign(e) * self._factor
            
    def _get_dD(self, q):
        self._ddm.run(q)
        ddm = self._ddm.get_derivative_of_dynamical_matrix()
        dD = np.zeros(ddm.shape[1:], dtype='complex128')
        for i in range(3):
            dD += self._delta_q[i] * ddm[i]
        return dD / np.linalg.norm(self._delta_q)

    def _rearrange_eigenvectors(self, dD, eigvecs_deg):
        dD_part = np.dot(eigvecs_deg.T.conj(), np.dot(dD, eigvecs_deg))
        p_eigvals, p_eigvecs = np.linalg.eigh(dD_part)
        
        if (np.abs(p_eigvals - p_eigvals[0]) < 1e-5).all():
            return None
        else:
            return np.dot(eigvecs_deg, p_eigvecs)
    
    def write_yaml(self):
        file = open('modulation.yaml', 'w')
        dim = self._dimension
        factor = self._factor
        cell = self._cell
        num_atom = self._cell.get_number_of_atoms()
        modes = self._phonon_modes

        lattice = cell.get_cell()
        positions = cell.get_scaled_positions()
        masses = cell.get_masses()
        symbols = cell.get_chemical_symbols()
        file.write("unitcell:\n")
        file.write("  atom-info:\n")
        for m, s in zip( masses, symbols ):
            file.write("  - { name: %2s, mass: %10.5f }\n" % (s, m))
        
        file.write("  lattice-vectors:\n")
        file.write("  - [ %20.15f, %20.15f, %20.15f ]\n" % (tuple(lattice[0])))
        file.write("  - [ %20.15f, %20.15f, %20.15f ]\n" % (tuple(lattice[1])))
        file.write("  - [ %20.15f, %20.15f, %20.15f ]\n" % (tuple(lattice[2])))
        file.write("  positions:\n")
        for p in positions:
            file.write("  - [ %20.15f, %20.15f, %20.15f ]\n" % (tuple(p)))

        supercell = self._get_supercell()
        lattice = supercell.get_cell()
        positions = supercell.get_scaled_positions()
        masses = supercell.get_masses()
        symbols = supercell.get_chemical_symbols()
        file.write("supercell:\n")
        file.write("  dimension: [ %d, %d, %d ]\n" % tuple(dim))
        file.write("  atom-info:\n")
        for m, s in zip( masses, symbols ):
            file.write("  - { name: %2s, mass: %10.5f }\n" % (s, m))
        
        file.write("  lattice-vectors:\n")
        file.write("  - [ %20.15f, %20.15f, %20.15f ]\n" % (tuple(lattice[0])))
        file.write("  - [ %20.15f, %20.15f, %20.15f ]\n" % (tuple(lattice[1])))
        file.write("  - [ %20.15f, %20.15f, %20.15f ]\n" % (tuple(lattice[2])))
        file.write("  positions:\n")
        for p in positions:
            file.write("  - [ %20.15f, %20.15f, %20.15f ]\n" % (tuple(p)))

        file.write("modulations:\n")
        for deltas, mode in zip(self._delta_modulations,
                                self._phonon_modes):
            q = mode[0]
            file.write("- q-position: [ %12.7f, %12.7f, %12.7f ]\n" %
                       tuple(q))
            file.write("  band: %d\n" % (mode[1] + 1))
            file.write("  amplitude: %f\n" % mode[2])
            file.write("  phase: %f\n" % mode[3])
            file.write("  displacements:\n")
            for i, p in enumerate(deltas):
                file.write("  - [ %20.15f, %20.15f ] # %d x (%f)\n" %
                           (p[0].real, p[0].imag, i + 1, abs(p[0])))
                file.write("  - [ %20.15f, %20.15f ] # %d y (%f)\n" %
                           (p[1].real, p[1].imag, i + 1, abs(p[1])))
                file.write("  - [ %20.15f, %20.15f ] # %d z (%f)\n" %
                           (p[2].real, p[2].imag, i + 1, abs(p[2])))

        file.write("phonon:\n")
        freqs = self._eigvals_to_frequencies(self._eigvals)
        for eigvec, freq, mode in zip(self._eigvecs,
                                      freqs,
                                      self._phonon_modes):
            file.write("- q-position: [ %12.7f, %12.7f, %12.7f ]\n" %
                       tuple(mode[0]))
            file.write("  band: %d\n" % (mode[1] + 1))
            file.write("  amplitude: %f\n" % mode[2])
            file.write("  phase: %f\n" % mode[3])
            file.write("  frequency: %15.10f\n" % freq)
            file.write("  eigenvector:\n")
            for j in range(num_atom):
                file.write("  - # atom %d\n" % (j + 1))
                for k in (0, 1, 2):
                    val = eigvec[j * 3 + k]
                    file.write("    - [ %17.14f, %17.14f ] # %f\n" %
                               (val.real, val.imag, np.angle(val, deg=True)))
Example #18
0
class GroupVelocity(object):
    """
    d omega   ----
    ------- = \  / omega
    d q        \/q

    Gradient of omega in reciprocal space.

             d D(q)
    <e(q,nu)|------|e(q,nu)>
              d q
    """

    def __init__(self,
                 dynamical_matrix,
                 q_length=None,
                 symmetry=None,
                 frequency_factor_to_THz=VaspToTHz,
                 cutoff_frequency=1e-4):
        """
        q_points is a list of sets of q-point and q-direction:
        [[q-point, q-direction], [q-point, q-direction], ...]

        q_length is used such as D(q + q_length) - D(q - q_length).
        """
        self._dynmat = dynamical_matrix
        primitive = dynamical_matrix.get_primitive()
        self._reciprocal_lattice_inv = primitive.get_cell()
        self._reciprocal_lattice = np.linalg.inv(self._reciprocal_lattice_inv)
        self._q_length = q_length
        if self._dynmat.is_nac() and self._dynmat.get_nac_method() == 'gonze':
            if self._q_length is None:
                self._q_length = 1e-5
        if self._q_length is None:
            self._ddm = DerivativeOfDynamicalMatrix(dynamical_matrix)
        else:
            self._ddm = None
        self._symmetry = symmetry
        self._factor = frequency_factor_to_THz
        self._cutoff_frequency = cutoff_frequency

        self._directions = np.array([[1, 2, 3],
                                     [1, 0, 0],
                                     [0, 1, 0],
                                     [0, 0, 1]], dtype='double')
        self._directions[0] /= np.linalg.norm(self._directions[0])

        self._q_points = None
        self._group_velocity = None
        self._perturbation = None

    def set_q_points(self, q_points, perturbation=None):
        self._q_points = q_points
        self._perturbation = perturbation
        if perturbation is None:
            self._directions[0] = np.array([1, 2, 3])
        else:
            self._directions[0] = np.dot(
                self._reciprocal_lattice, perturbation)
        self._directions[0] /= np.linalg.norm(self._directions[0])
        self._set_group_velocity()

    def set_q_length(self, q_length):
        self._q_length = q_length

    def get_q_length(self):
        return self._q_length

    def get_group_velocity(self):
        return self._group_velocity

    def _set_group_velocity(self):
        gv = [self._set_group_velocity_at_q(q) for q in self._q_points]
        self._group_velocity = np.array(gv)

    def _set_group_velocity_at_q(self, q):
        self._dynmat.set_dynamical_matrix(q)
        dm = self._dynmat.get_dynamical_matrix()
        eigvals, eigvecs = np.linalg.eigh(dm)
        eigvals = eigvals.real
        freqs = np.sqrt(abs(eigvals)) * np.sign(eigvals) * self._factor
        gv = np.zeros((len(freqs), 3), dtype='double')
        deg_sets = degenerate_sets(freqs)

        ddms = self._get_dD(np.array(q))
        pos = 0
        for deg in deg_sets:
            gv[pos:pos+len(deg)] = self._perturb_D(ddms, eigvecs[:, deg])
            pos += len(deg)

        for i, f in enumerate(freqs):
            if f > self._cutoff_frequency:
                gv[i, :] *= self._factor ** 2 / f / 2
            else:
                gv[i, :] = 0

        if self._perturbation is None:
            return self._symmetrize_group_velocity(gv, q)
        else:
            return gv

    def _symmetrize_group_velocity(self, gv, q):
        rotations = []
        for r in self._symmetry.get_reciprocal_operations():
            q_in_BZ = q - np.rint(q)
            diff = q_in_BZ - np.dot(r, q_in_BZ)
            if (np.abs(diff) < self._symmetry.get_symmetry_tolerance()).all():
                rotations.append(r)

        gv_sym = np.zeros_like(gv)
        for r in rotations:
            r_cart = similarity_transformation(self._reciprocal_lattice, r)
            gv_sym += np.dot(r_cart, gv.T).T

        return gv_sym / len(rotations)

    def _get_dD(self, q):
        if self._q_length is None:
            return self._get_dD_analytical(q)
        else:
            return self._get_dD_FD(q)

    def _get_dD_FD(self, q): # finite difference
        ddm = []
        for dqc in self._directions * self._q_length:
            dq = np.dot(self._reciprocal_lattice_inv, dqc)
            ddm.append(delta_dynamical_matrix(q, dq, self._dynmat) /
                       self._q_length / 2)
        return np.array(ddm)

    def _get_dD_analytical(self, q):
        self._ddm.run(q)
        ddm = self._ddm.get_derivative_of_dynamical_matrix()
        dtype = "c%d" % (np.dtype('double').itemsize * 2)
        ddm_dirs = np.zeros((len(self._directions),) + ddm.shape[1:],
                            dtype=dtype)
        for i, dq in enumerate(self._directions):
            for j in range(3):
                ddm_dirs[i] += dq[j] * ddm[j]
        return ddm_dirs

    def _perturb_D(self, ddms, eigsets):
        eigvals, eigvecs = np.linalg.eigh(
            np.dot(eigsets.T.conj(), np.dot(ddms[0], eigsets)))

        gv = []
        rot_eigsets = np.dot(eigsets, eigvecs)
        for ddm in ddms[1:]:
            gv.append(
                np.diag(np.dot(rot_eigsets.T.conj(),
                               np.dot(ddm, rot_eigsets))).real)

        return np.transpose(gv)
Example #19
0
class IrReps:
    def __init__(self,
                 dynamical_matrix,
                 q,
                 is_little_cogroup=False,
                 nac_q_direction=None,
                 factor=VaspToTHz,
                 symprec=1e-5,
                 degeneracy_tolerance=1e-5,
                 log_level=0):
        self._is_little_cogroup = is_little_cogroup
        self._nac_q_direction = nac_q_direction
        self._factor = factor
        self._log_level = log_level

        self._q = np.array(q)
        self._degeneracy_tolerance = degeneracy_tolerance
        self._symprec = symprec
        self._primitive = dynamical_matrix.get_primitive()
        self._dynamical_matrix = dynamical_matrix
        self._ddm = DerivativeOfDynamicalMatrix(dynamical_matrix)

    def run(self):
        self._set_eigenvectors(self._dynamical_matrix)
        self._symmetry_dataset = Symmetry(self._primitive,
                                          self._symprec).get_dataset()

        if not self._is_primitive_cell():
            print('')
            print("Non-primitve cell is used.")
            print("Your unit cell may be transformed to a primitive cell "
                  "by PRIMITIVE_AXIS tag.")
            return False
        
        (self._rotations_at_q,
         self._translations_at_q) = self._get_rotations_at_q()

        self._g = len(self._rotations_at_q)

        (self._pointgroup_symbol,
         self._transformation_matrix,
         self._conventional_rotations) = self._get_conventional_rotations()

        self._ground_matrices = self._get_ground_matrix()
        self._degenerate_sets = self._get_degenerate_sets()
        self._irreps = self._get_irreps()
        self._characters, self._irrep_dims = self._get_characters()

        self._ir_labels = None
        if self._pointgroup_symbol in character_table.keys():
            self._rotation_symbols = self._get_rotation_symbols()
            if (abs(self._q) < self._symprec).all() and self._rotation_symbols:
                self._ir_labels = self._get_ir_labels()
            elif (abs(self._q) < self._symprec).all():
                if self._log_level > 0:
                    print("Database for this point group is not preprared.")
            else:
                if self._log_level > 0:
                    print("Database for non-Gamma point is not prepared.")
        else:
            self._rotation_symbols = None

        return True

    def _get_degenerate_sets(self):
        deg_sets = get_degenerate_sets(self._freqs,
                                       cutoff=self._degeneracy_tolerance)
        self._ddm.run(self._q)
        ddm_q = np.sum([self._ddm.get_derivative_of_dynamical_matrix()[i] *
                        self._q[i] for i in range(3)], axis=0)
        return deg_sets
        
    def get_band_indices(self):
        return self._degenerate_sets

    def get_characters(self):
        return self._characters

    def get_eigenvectors(self):
        return self._eigvecs

    def get_irreps(self):
        return self._irreps

    def get_ground_matrices(self):
        return self._ground_matrices

    def get_rotation_symbols(self):
        return self._rotation_symbols

    def get_rotations(self):
        return self._conventional_rotations

    def get_projection_operators(self, idx_irrep, i=None, j=None):
        if i is None or j is None:
            return self._get_character_projection_operators(idx_irrep)
        else:
            return self._get_projection_operators(idx_irrep, i, j)

    def show(self, show_irreps=False):
        self._show(show_irreps)

    def write_yaml(self, show_irreps=False):
        self._write_yaml(show_irreps)

    def _set_eigenvectors(self, dm):
        if self._nac_q_direction is not None and (np.abs(self._q) < 1e-5).all():
            dm.set_dynamical_matrix(self._q, q_direction=self._nac_q_direction)
        else:        
            dm.set_dynamical_matrix(self._q)
        eigvals, self._eigvecs = np.linalg.eigh(dm.get_dynamical_matrix())
        self._freqs = np.sqrt(abs(eigvals)) * np.sign(eigvals) * self._factor

    def _get_rotations_at_q(self):
        rotations_at_q = []
        trans_at_q = []
        for r, t in zip(self._symmetry_dataset['rotations'],
                        self._symmetry_dataset['translations']):

            # Using r is used instead of np.linalg.inv(r)
            diff = np.dot(self._q, r) - self._q 

            if (abs(diff - np.rint(diff)) < self._symprec).all():
                rotations_at_q.append(r)
                for i in range(3):
                    if np.abs(t[i] - 1) < self._symprec:
                        t[i] = 0.0
                trans_at_q.append(t)

        return np.array(rotations_at_q), np.array(trans_at_q)

    def _get_conventional_rotations(self):
        spacegroup_symbol = self._symmetry_dataset['international'][0]
        spacegroup_number = self._symmetry_dataset['number']
        rotations = self._rotations_at_q.copy()
        pointgroup = get_pointgroup(rotations)
        pointgroup_symbol = pointgroup[0]
        transformation_matrix = pointgroup[1]

        conventional_rotations = self._transform_rotations(
            transformation_matrix, rotations)

        return (pointgroup_symbol,
                transformation_matrix,
                conventional_rotations)

    def _transform_rotations(self, tmat, rotations):
        trans_rots = []

        for r in rotations:
            r_conv = similarity_transformation(np.linalg.inv(tmat), r)
            trans_rots.append(np.rint(r_conv).astype(int))

        return np.array(trans_rots)

    def _get_ground_matrix(self):
        matrices = []
        
        for (r, t) in zip(self._rotations_at_q,
                          self._translations_at_q):
    
            lat = self._primitive.get_cell().T
            r_cart = similarity_transformation(lat, r)
    
            perm_mat = self._get_modified_permutation_matrix(r, t)
            matrices.append(np.kron(perm_mat, r_cart))

        return np.array(matrices)

    def _get_characters(self):
        characters = []
        irrep_dims = []
        for irrep_Rs in self._irreps:
            characters.append([np.trace(rep) for rep in irrep_Rs])
            irrep_dims.append(len(irrep_Rs[0]))
        return np.array(characters), np.array(irrep_dims)

    def _get_modified_permutation_matrix(self, r, t, ):
        num_atom = self._primitive.get_number_of_atoms()
        pos = self._primitive.get_scaled_positions()
        matrix = np.zeros((num_atom, num_atom), dtype=complex)
        for i, p1 in enumerate(pos):
            p_rot = np.dot(r, p1) + t
            for j, p2 in enumerate(pos):
                diff = p_rot - p2
                if (abs(diff - np.rint(diff)) < self._symprec).all():
                    # For this phase factor, see
                    # Dynamics of perfect crystals by G. Venkataraman et al.,
                    # pp132 Eq. (3.22).
                    # It is assumed that dynamical matrix is built without
                    # considering internal atomic positions, so
                    # the phase factors of eigenvectors are shifted in
                    # _get_irreps().
                    phase_factor = np.dot(
                        self._q, np.dot(np.linalg.inv(r), p2 - p_rot))
                    
                    # This phase factor comes from non-pure-translation of
                    # each symmetry opration.
                    if self._is_little_cogroup:
                        phase_factor += np.dot(t, self._q)
                        
                    matrix[j, i] = np.exp(2j * np.pi * phase_factor)

        return matrix
    
    def _get_irreps(self):
        eigvecs = []
        phases = np.kron(
            [np.exp(2j * np.pi * np.dot(self._q, pos))
             for pos in self._primitive.get_scaled_positions()], [1, 1, 1])
        for vec in self._eigvecs.T:
            eigvecs.append(vec * phases)

        irrep = []
        for band_indices in self._degenerate_sets:
            irrep_Rs = []
            for mat in self._ground_matrices:
                l = len(band_indices)

                if l == 1:
                    vec = eigvecs[band_indices[0]]
                    irrep_Rs.append([[np.vdot(vec, np.dot(mat, vec))]])
                    continue
                
                irrep_R = np.zeros((l, l), dtype=complex)
                for i, b_i in enumerate(band_indices):
                    vec_i = eigvecs[b_i]
                    for j, b_j in enumerate(band_indices):
                        vec_j = eigvecs[b_j]
                        irrep_R[i, j] = np.vdot(vec_i, np.dot(mat, vec_j))
                irrep_Rs.append(irrep_R)

            irrep.append(irrep_Rs)

        return irrep

    def _get_character_projection_operators(self, idx_irrep):
        dim = self._irrep_dims[idx_irrep]
        chars = self._characters[idx_irrep]
        return np.sum([mat * char.conj()
                       for mat, char in zip(self._ground_matrices, chars)],
                      axis=0) * dim / self._g

    def _get_projection_operators(self, idx_irrep, i, j):
        dim = self._irrep_dims[idx_irrep]
        return np.sum([mat * r[i, j].conj() for mat, r
                       in zip(self._ground_matrices, self._irreps[idx_irrep])],
                      axis=0) * dim / self._g
    
    def _get_rotation_symbols(self):
        ptg = self._pointgroup_symbol
        for mapping_table in character_table[ptg]['mapping_table']:
            rotation_symbols = []
            for r in self._conventional_rotations:
                symbol = get_rotation_symbol(r, mapping_table)
                rotation_symbols.append(symbol)

            if not False in rotation_symbols:
                break

        if False in rotation_symbols:
            return None
        else:
            return rotation_symbols

    def _get_ir_labels(self):
        ir_labels = []
        rot_list = character_table[self._pointgroup_symbol]['rotation_list']
        char_table = character_table[self._pointgroup_symbol]['character_table']
        for chars, deg_set in zip(self._characters, self._degenerate_sets):
            chars_ordered = np.zeros(len(rot_list), dtype=complex)
            for rs, ch in zip(self._rotation_symbols, chars):
                chars_ordered[rot_list.index(rs)] += ch

            for i, rl in enumerate(rot_list):
                chars_ordered[i] /= len(
                    character_table[self._pointgroup_symbol]['mapping_table'][0][rl])

            found = False
            for ct_label in char_table.keys():
                if (abs(chars_ordered - np.array(char_table[ct_label])) <
                    self._symprec).all():
                    ir_labels.append(ct_label)
                    found = True
                    break
                
            if not found:
                ir_labels.append(None)

            if self._log_level > 1:
                text = ""
                for v in chars_ordered:
                    text += "%5.2f " % abs(v)
                if found:
                    print("%s %s" % (text, ct_label))
                else:
                    print("%s Not found" % text)
                
        return ir_labels

    def _is_primitive_cell(self):
        num_identity = 0
        for r in self._symmetry_dataset['rotations']:
            if (r - np.eye(3, dtype='intc') == 0).all():
                num_identity += 1
                if num_identity > 1:
                    return False
        else:
            return True
    
    def _show(self, show_irreps):
        print('')
        print("-------------------------------")
        print("  Irreducible representations")
        print("-------------------------------")
        print("q-point: %s" % self._q)
        print("Point group: %s" % self._pointgroup_symbol)
        print('')
        
        if (np.abs(self._q) < self._symprec).all():
            width = 6
            print("Original rotation matrices:")
            print('')
            print_rotations(self._rotations_at_q, width=width)
        else:
            width = 4
            print("Original symmetry operations:")
            print('')
            print_rotations(self._rotations_at_q,
                            translations=self._translations_at_q,
                            width=width)

        print("Transformation matrix:")
        print('')
        for v in self._transformation_matrix:
            print("%6.3f %6.3f %6.3f" % tuple(v))
        print('')
        print("Rotation matrices by transformation matrix:")
        print('')
        print_rotations(self._conventional_rotations,
                        rotation_symbols=self._rotation_symbols,
                        width=width)
        print("Character table:")
        print('')
        for i, deg_set in enumerate(self._degenerate_sets):
            text = "%3d (%8.3f): " % (deg_set[0] + 1, self._freqs[deg_set[0]])
            if self._ir_labels is None:
                print(text)
            else:
                print(text + self._ir_labels[i])
            print_characters(self._characters[i])
            print('')

        if show_irreps:
            self._show_irreps()
    
    def _show_irreps(self):
        print("IR representations:")
        print('')
        
        for i, (deg_set, irrep_Rs) in enumerate(zip(self._degenerate_sets,
                                                    self._irreps)):
            print("%3d (%8.3f):" % (deg_set[0] + 1, self._freqs[deg_set[0]]))
            print('')
            for j, irrep_R in enumerate(irrep_Rs):
                for k in range(len(irrep_R)):
                    text = "     "
                    for l in range(len(irrep_R)):
                        if irrep_R[k][l].real > 0:
                            sign_r = " "
                        else:
                            sign_r = "-"
                        
                        if irrep_R[k][l].imag > 0:
                            sign_i = "+"
                        else:
                            sign_i = "-"

                        if k == 0:
                            str_index = "%2d" % (j + 1)
                        else:
                            str_index = "  "
                            
                        if l > 0:
                            str_index = ''

                        text += "%s (%s%5.3f %s%5.3fi) " % (
                            str_index,
                            sign_r, abs(irrep_R[k][l].real),
                            sign_i, abs(irrep_R[k][l].imag))
                    print(text)
                if len(irrep_R) > 1:
                    print('')
            if len(irrep_R) == 1:
                print('')

    def _write_yaml(self, show_irreps):
        w = open("irreps.yaml", 'w')
        w.write("q-position: [ %12.7f, %12.7f, %12.7f ]\n" % tuple(self._q))
        w.write("point_group: %s\n" % self._pointgroup_symbol)
        w.write("transformation_matrix:\n")
        for v in self._transformation_matrix:
            w.write("- [ %10.7f, %10.7f, %10.7f ]\n" % tuple(v))
        w.write("rotations:\n")
        for i, r in enumerate(self._conventional_rotations):
            w.write("- matrix:\n")
            for v in r:
                w.write("  - [ %2d, %2d, %2d ]\n" % tuple(v))
            if self._rotation_symbols:
                w.write("  symbol: %s\n" % self._rotation_symbols[i])
        w.write("normal_modes:\n")
        for i, deg_set in enumerate(self._degenerate_sets):
            w.write("- band_indices: [ ")
            w.write("%d" % (deg_set[0] + 1))
            for bi in deg_set[1:]:
                w.write(", %d" % (bi + 1))
            w.write(" ]\n")
            w.write("  frequency: %-15.10f\n" % self._freqs[deg_set[0]])
            if self._ir_labels:
                w.write("  ir_label: %s\n" % self._ir_labels[i])
            w.write("  characters: ")
            chars = np.rint(np.abs(self._characters[i]))
            phase = (np.angle(self._characters[i]) / np.pi * 180) % 360
            if len(chars) > 1:
                w.write("[ [ %2d, %5.1f ]" % (chars[0], phase[0]))
                for chi, theta in zip(chars[1:], phase[1:]):
                    w.write(", [ %2d, %5.1f ]" % (chi, theta))
                w.write(" ]\n")
            else:
                w.write("[ [ %2d, %5.1f ] ]\n" % (chars[0], phase[0]))

        if show_irreps:
            self._write_yaml_irreps(w)
            
        w.close()

    def _write_yaml_irreps(self, file_pointer):
        w = file_pointer
        if not self._irreps:
            self._irrep = self._get_irreps()

        w.write("\n")
        w.write("irreps:\n")
        for i, (deg_set, irrep_Rs) in enumerate(
            zip(self._degenerate_sets, self._irreps)):
            w.write("- # %d\n" % (i + 1))
            for j, irrep_R in enumerate(irrep_Rs):
                if self._rotation_symbols:
                    symbol = self._rotation_symbols[j]
                else:
                    symbol = ''
                if len(deg_set) > 1:
                    w.write("  - # %d %s\n" % (j + 1, symbol))
                    for k, v in enumerate(irrep_R):
                        w.write("    - [ ")
                        for x in v[:-1]:
                            w.write("%10.7f, %10.7f,   " % (x.real, x.imag))
                        w.write("%10.7f, %10.7f ] # (" %
                                (v[-1].real, v[-1].imag))
                        
                        w.write(("%5.0f" * len(v)) %
                                tuple((np.angle(v) / np.pi * 180) % 360))
                        w.write(")\n")
                else:
                    x = irrep_R[0][0]
                    w.write("  - [ [ %10.7f, %10.7f ] ] # (%3.0f) %d %s\n" %
                            (x.real, x.imag,
                             (np.angle(x) / np.pi * 180) % 360, j + 1, symbol))
                
        pass
Example #20
0
class GroupVelocity:
    """
    d omega   ----
    ------- = \  / omega
    d q        \/q

    Gradient of omega in reciprocal space.

             d D(q)
    <e(q,nu)|------|e(q,nu)>
              d q
    """
    
    def __init__(self,
                 dynamical_matrix,
                 q_points=None,
                 symmetry=None,
                 q_length=None,
                 frequency_factor_to_THz=VaspToTHz):
        """
        q_points is a list of sets of q-point and q-direction:
        [[q-point, q-direction], [q-point, q-direction], ...]

        q_length is used such as D(q + q_length) - D(q - q_length).
        """
        self._dynmat = dynamical_matrix
        primitive = dynamical_matrix.get_primitive()
        self._reciprocal_lattice_inv = primitive.get_cell()
        self._reciprocal_lattice = np.linalg.inv(self._reciprocal_lattice_inv)
        self._q_points = q_points
        self._q_length = q_length
        self._symmetry = symmetry
        self._perturation = None
        if q_length is None:
            self._ddm = DerivativeOfDynamicalMatrix(dynamical_matrix)
        else:
            self._ddm = None
        self._factor = frequency_factor_to_THz

        self._directions = np.array([[1, 2, 3],
                                     [1, 0, 0],
                                     [0, 1, 0],
                                     [0, 0, 1]], dtype='double')
        self._directions[0] /= np.linalg.norm(self._directions[0])

        self._group_velocity = None
        if self._q_points is not None:
            self._set_group_velocity()

    def set_q_points(self, q_points, perturbation = None, phonons=None):
        self._q_points = q_points
        self._perturbation = perturbation
        if perturbation is None:
            self._directions[0] = np.array([1, 2, 3])
        else:
            self._directions[0] = np.dot(
                self._reciprocal_lattice, perturbation)
        self._directions[0] /= np.linalg.norm(self._directions[0])
        self._set_group_velocity(phonons=phonons)

    def set_q_length(self, q_length):
        self._q_length = q_length

    def get_group_velocity(self):
        return self._group_velocity

    def _set_group_velocity(self, phonons=None):
        v_g = []
        if phonons is None:
            phonons = [None for q in self._q_points]
        for i, q in enumerate(self._q_points):
            dD_at_q = self._set_group_velocity_at_q(q, phonon=phonons[i])
            v_g.append(dD_at_q)
        self._group_velocity = np.array(v_g)

    def _set_group_velocity_at_q(self, q, phonon=None): # phonon: freq, eigenvector, degenerate
        self._dynmat.set_dynamical_matrix(q)
        dm = self._dynmat.get_dynamical_matrix()
        if phonon is not None:
            freqs, eigvecs, degenerates = phonon
            uniq = np.unique(degenerates)
            deg_sets = [np.where(degenerates == ele)[0] for ele in uniq]
        else:
            eigvals, eigvecs = np.linalg.eigh(dm)
            eigvals = eigvals.real
            freqs = np.sqrt(abs(eigvals)) * np.sign(eigvals) * self._factor
            deg_sets = degenerate_sets(freqs)
        gv = np.zeros((len(freqs), 3), dtype='double')
        ddms = self._get_dD(np.array(q))

        for deg in deg_sets:
            gv[deg] = self._perturb_D(ddms, eigvecs[:, deg])

        for i in range(3):
            gv[:, i] *= self._factor ** 2 / freqs / 2

        if self._perturbation is None:
            return self._symmetrize_group_velocity(gv, q)
        else:
            return gv

    def _get_dD(self, q):
        if self._q_length is None:
            return self._get_dD_analytical(q)
        else:
            return self._get_dD_FD(q)

    def _symmetrize_group_velocity(self, gv, q):
        rotations = []
        for r in self._symmetry.get_reciprocal_operations():
            q_in_BZ = q - np.rint(q)
            diff = q_in_BZ - np.dot(r, q_in_BZ)
            if (np.abs(diff) < self._symmetry.get_symmetry_tolerance()).all():
                rotations.append(r)

        gv_sym = np.zeros_like(gv)
        for r in rotations:
            r_cart = similarity_transformation(self._reciprocal_lattice, r)
            gv_sym += np.dot(r_cart, gv.T).T
        return gv_sym / len(rotations)

    def _get_dD_FD(self, q): # finite difference
        ddm = []
        for dqc in self._directions * self._q_length:
            dq = np.dot(self._reciprocal_lattice_inv, dqc)
            ddm.append(delta_dynamical_matrix(q, dq, self._dynmat) /
                       self._q_length / 2)
        return np.array(ddm)
    
    def _get_dD_analytical(self, q):
        self._ddm.run(q)
        ddm = self._ddm.get_derivative_of_dynamical_matrix()
        ddm_dirs = np.zeros((len(self._directions),) + ddm.shape[1:],
                            dtype='complex128')
        for i, dq in enumerate(self._directions):
            for j in range(3):
                ddm_dirs[i] += dq[j] * ddm[j]
        return ddm_dirs
    
    def _perturb_D(self, ddms, eigsets):
        eigvals, eigvecs = np.linalg.eigh(
            np.dot(eigsets.T.conj(), np.dot(ddms[0], eigsets)))

        gv = []
        rot_eigsets = np.dot(eigsets, eigvecs)
        for ddm in ddms[1:]:
            gv.append(
                np.diag(np.dot(rot_eigsets.T.conj(),
                               np.dot(ddm, rot_eigsets))).real)
        
        return np.transpose(gv)
Example #21
0
class IrReps:
    """Class to calculate irreducible representations from eigenvectors.

    Methods and terminologies used in this class may be easily found
    in textbooks such as
    - Group theory with applications in chemical physics by Patrick Jacobs
    - Symmetry and condensed matter physics by M. El-Batanouny and F. Wooten

    """

    def __init__(
        self,
        dynamical_matrix: Union[DynamicalMatrix, DynamicalMatrixNAC],
        q,
        is_little_cogroup=False,
        nac_q_direction=None,
        factor=VaspToTHz,
        symprec=1e-5,
        degeneracy_tolerance=None,
        log_level=0,
    ):
        """Init method."""
        self._is_little_cogroup = is_little_cogroup
        self._nac_q_direction = nac_q_direction
        self._factor = factor
        self._log_level = log_level

        self._q = np.array(q)
        if degeneracy_tolerance is None:
            self._degeneracy_tolerance = 1e-5
        else:
            self._degeneracy_tolerance = degeneracy_tolerance
        self._symprec = symprec
        self._primitive = dynamical_matrix.primitive
        self._dynamical_matrix = dynamical_matrix
        self._ddm = DerivativeOfDynamicalMatrix(dynamical_matrix)
        self._character_table = None

        self._symmetry_dataset = Symmetry(
            self._primitive, symprec=self._symprec
        ).dataset

        if not is_primitive_cell(self._symmetry_dataset["rotations"]):
            raise RuntimeError(
                "Non-primitve cell is used. Your unit cell may be transformed to "
                "a primitive cell by PRIMITIVE_AXIS tag."
            )

    def run(self):
        """Calculate irreps."""
        self._set_eigenvectors(self._dynamical_matrix)

        (self._rotations_at_q, self._translations_at_q) = self._get_rotations_at_q()

        self._g = len(self._rotations_at_q)

        (
            self._pointgroup_symbol,
            self._transformation_matrix,
            self._conventional_rotations,
        ) = self._get_conventional_rotations()

        self._ground_matrices = self._get_ground_matrix()
        self._degenerate_sets = self._get_degenerate_sets()
        self._irreps = self._get_irreps()
        self._characters, self._irrep_dims = self._get_characters()

        self._ir_labels = None

        if (
            self._pointgroup_symbol in character_table.keys()
            and character_table[self._pointgroup_symbol] is not None
        ):
            self._rotation_symbols = self._get_rotation_symbols()
            if (abs(self._q) < self._symprec).all() and self._rotation_symbols:
                self._ir_labels = self._get_ir_labels()
            elif (abs(self._q) < self._symprec).all():
                if self._log_level > 0:
                    print("Database for this point group is not preprared.")
            else:
                if self._log_level > 0:
                    print("Database for non-Gamma point is not prepared.")
        else:
            self._rotation_symbols = None

        return True

    def _get_degenerate_sets(self):
        deg_sets = get_degenerate_sets(self._freqs, cutoff=self._degeneracy_tolerance)
        self._ddm.run(self._q)
        return deg_sets

    def get_band_indices(self):
        """Return band indices.

        Returns
        -------
        See docstring of ``degenerate_sets``.

        """
        return self._degenerate_sets

    def get_characters(self):
        """Return characters of irreps."""
        return self._characters

    def get_eigenvectors(self):
        """Return eigenvectors."""
        return self._eigvecs

    def get_irreps(self):
        """Return irreps."""
        return self._irreps

    def get_ground_matrices(self):
        """Return ground matrices."""
        return self._ground_matrices

    def get_rotation_symbols(self):
        """Return symbols assigned to rotation matrices."""
        return self._rotation_symbols

    def get_rotations(self):
        """Return rotation matrices."""
        return self._conventional_rotations

    def get_projection_operators(self, idx_irrep, i=None, j=None):
        """Return projection operators."""
        if i is None or j is None:
            return self._get_character_projection_operators(idx_irrep)
        else:
            return self._get_projection_operators(idx_irrep, i, j)

    def show(self, show_irreps=False):
        """Show irreps."""
        self._show(show_irreps)

    def write_yaml(self, show_irreps=False):
        """Write irreps in yaml file."""
        self._write_yaml(show_irreps)

    def _set_eigenvectors(self, dm):
        if self._nac_q_direction is not None and (np.abs(self._q) < 1e-5).all():
            dm.run(self._q, q_direction=self._nac_q_direction)
        else:
            dm.run(self._q)
        eigvals, self._eigvecs = np.linalg.eigh(dm.dynamical_matrix)
        self._freqs = np.sqrt(abs(eigvals)) * np.sign(eigvals) * self._factor

    def _get_rotations_at_q(self):
        rotations_at_q = []
        trans_at_q = []
        for r, t in zip(
            self._symmetry_dataset["rotations"], self._symmetry_dataset["translations"]
        ):

            # Using r is used instead of np.linalg.inv(r)
            diff = np.dot(self._q, r) - self._q

            if (abs(diff - np.rint(diff)) < self._symprec).all():
                rotations_at_q.append(r)
                for i in range(3):
                    if np.abs(t[i] - 1) < self._symprec:
                        t[i] = 0.0
                trans_at_q.append(t)

        return np.array(rotations_at_q), np.array(trans_at_q)

    def _get_conventional_rotations(self):
        rotations = self._rotations_at_q.copy()
        pointgroup_symbol = self._symmetry_dataset["pointgroup"]
        transformation_matrix = self._symmetry_dataset["transformation_matrix"]
        conventional_rotations = self._transform_rotations(
            transformation_matrix, rotations
        )

        return (pointgroup_symbol, transformation_matrix, conventional_rotations)

    def _transform_rotations(self, tmat, rotations):
        trans_rots = []

        for r in rotations:
            r_conv = similarity_transformation(tmat, r)
            trans_rots.append(np.rint(r_conv).astype(int))

        return np.array(trans_rots)

    def _get_ground_matrix(self):
        matrices = []

        for (r, t) in zip(self._rotations_at_q, self._translations_at_q):

            lat = self._primitive.cell.T
            r_cart = similarity_transformation(lat, r)

            perm_mat = self._get_modified_permutation_matrix(r, t)
            matrices.append(np.kron(perm_mat, r_cart))

        return np.array(matrices)

    def _get_characters(self):
        characters = []
        irrep_dims = []
        for irrep_Rs in self._irreps:
            characters.append([np.trace(rep) for rep in irrep_Rs])
            irrep_dims.append(len(irrep_Rs[0]))
        return np.array(characters), np.array(irrep_dims)

    def _get_modified_permutation_matrix(self, r, t):
        num_atom = len(self._primitive)
        pos = self._primitive.scaled_positions
        matrix = np.zeros((num_atom, num_atom), dtype=complex)
        for i, p1 in enumerate(pos):
            p_rot = np.dot(r, p1) + t
            for j, p2 in enumerate(pos):
                diff = p_rot - p2
                if (abs(diff - np.rint(diff)) < self._symprec).all():
                    # For this phase factor, see
                    # Dynamics of perfect crystals by G. Venkataraman et al.,
                    # pp132 Eq. (3.22).
                    # It is assumed that dynamical matrix is built without
                    # considering internal atomic positions, so
                    # the phase factors of eigenvectors are shifted in
                    # _get_irreps().
                    phase_factor = np.dot(self._q, np.dot(np.linalg.inv(r), p2 - p_rot))

                    # This phase factor comes from non-pure-translation of
                    # each symmetry opration.
                    if self._is_little_cogroup:
                        phase_factor += np.dot(t, self._q)

                    matrix[j, i] = np.exp(2j * np.pi * phase_factor)

        return matrix

    def _get_irreps(self):
        eigvecs = []
        phases = np.kron(
            [
                np.exp(2j * np.pi * np.dot(self._q, pos))
                for pos in self._primitive.scaled_positions
            ],
            [1, 1, 1],
        )
        for vec in self._eigvecs.T:
            eigvecs.append(vec * phases)

        irrep = []
        for band_indices in self._degenerate_sets:
            irrep_Rs = []
            for mat in self._ground_matrices:
                n_deg = len(band_indices)

                if n_deg == 1:
                    vec = eigvecs[band_indices[0]]
                    irrep_Rs.append([[np.vdot(vec, np.dot(mat, vec))]])
                    continue

                irrep_R = np.zeros((n_deg, n_deg), dtype=complex)
                for i, b_i in enumerate(band_indices):
                    vec_i = eigvecs[b_i]
                    for j, b_j in enumerate(band_indices):
                        vec_j = eigvecs[b_j]
                        irrep_R[i, j] = np.vdot(vec_i, np.dot(mat, vec_j))
                irrep_Rs.append(irrep_R)

            irrep.append(irrep_Rs)

        return irrep

    def _get_character_projection_operators(self, idx_irrep):
        dim = self._irrep_dims[idx_irrep]
        chars = self._characters[idx_irrep]
        return (
            np.sum(
                [mat * char.conj() for mat, char in zip(self._ground_matrices, chars)],
                axis=0,
            )
            * dim
            / self._g
        )

    def _get_projection_operators(self, idx_irrep, i, j):
        dim = self._irrep_dims[idx_irrep]
        return (
            np.sum(
                [
                    mat * r[i, j].conj()
                    for mat, r in zip(self._ground_matrices, self._irreps[idx_irrep])
                ],
                axis=0,
            )
            * dim
            / self._g
        )

    def _get_rotation_symbols(self):
        ptg_symbol = self._pointgroup_symbol
        for ct in character_table[ptg_symbol]:
            mapping_table = ct["mapping_table"]
            rotation_symbols = []
            for r in self._conventional_rotations:
                rotation_symbols.append(_get_rotation_symbol(r, mapping_table))
            if False in rotation_symbols:
                ret_val = None
            else:
                ret_val = rotation_symbols
            if ret_val is not None:
                self._character_table = ct
                break

        return ret_val

    def _get_ir_labels(self):
        ir_labels = []
        rot_list = self._character_table["rotation_list"]
        char_table = self._character_table["character_table"]
        for chars, deg_set in zip(self._characters, self._degenerate_sets):
            chars_ordered = np.zeros(len(rot_list), dtype=complex)
            for rs, ch in zip(self._rotation_symbols, chars):
                chars_ordered[rot_list.index(rs)] += ch

            for i, rl in enumerate(rot_list):
                chars_ordered[i] /= len(self._character_table["mapping_table"][rl])

            found = False
            for ct_label in char_table.keys():
                if (
                    abs(chars_ordered - np.array(char_table[ct_label])) < self._symprec
                ).all():
                    ir_labels.append(ct_label)
                    found = True
                    break

            if not found:
                ir_labels.append(None)

            if self._log_level > 1:
                text = ""
                for v in chars_ordered:
                    text += "%5.2f " % abs(v)
                if found:
                    print("%s %s" % (text, ct_label))
                else:
                    print("%s Not found" % text)

        return ir_labels

    def _show(self, show_irreps):
        print("")
        print("-------------------------------")
        print("  Irreducible representations")
        print("-------------------------------")
        print("q-point: %s" % self._q)
        print("Point group: %s" % self._pointgroup_symbol)
        print("")

        if (np.abs(self._q) < self._symprec).all():
            width = 6
            print("Original rotation matrices:")
            print("")
            _print_rotations(self._rotations_at_q, width=width)
        else:
            width = 4
            print("Original symmetry operations:")
            print("")
            _print_rotations(
                self._rotations_at_q, translations=self._translations_at_q, width=width
            )

        print("Transformation matrix:")
        print("")
        for v in self._transformation_matrix:
            print("%6.3f %6.3f %6.3f" % tuple(v))
        print("")
        print("Rotation matrices by transformation matrix:")
        print("")
        _print_rotations(
            self._conventional_rotations,
            rotation_symbols=self._rotation_symbols,
            width=width,
        )
        print("Character table:")
        print("")
        for i, deg_set in enumerate(self._degenerate_sets):
            text = "%3d (%8.3f): " % (deg_set[0] + 1, self._freqs[deg_set[0]])
            if self._ir_labels is None:
                print(text)
            elif self._ir_labels[i] is None:
                warning = "Not found. Try adjusting tolerance value in IRREPS."
                print("%s%s" % (text, warning))
            else:
                print("%s%s" % (text, self._ir_labels[i]))
            _print_characters(self._characters[i])
            print("")

        if show_irreps:
            self._show_irreps()

    def _show_irreps(self):
        print("IR representations:")
        print("")

        for i, (deg_set, irrep_Rs) in enumerate(
            zip(self._degenerate_sets, self._irreps)
        ):
            print("%3d (%8.3f):" % (deg_set[0] + 1, self._freqs[deg_set[0]]))
            print("")
            for j, irrep_R in enumerate(irrep_Rs):
                for k, irrep_Rk in enumerate(irrep_R):
                    text = "     "
                    for ll, irrep_Rkl in enumerate(irrep_Rk):
                        if irrep_Rkl.real > 0:
                            sign_r = " "
                        else:
                            sign_r = "-"

                        if irrep_Rkl.imag > 0:
                            sign_i = "+"
                        else:
                            sign_i = "-"

                        if k == 0:
                            str_index = "%2d" % (j + 1)
                        else:
                            str_index = "  "

                        if ll > 0:
                            str_index = ""

                        text += "%s (%s%5.3f %s%5.3fi) " % (
                            str_index,
                            sign_r,
                            abs(irrep_Rkl.real),
                            sign_i,
                            abs(irrep_Rkl.imag),
                        )
                    print(text)
                if len(irrep_R) > 1:
                    print("")
            if len(irrep_R) == 1:
                print("")

    def _write_yaml(self, show_irreps):
        w = open("irreps.yaml", "w")
        w.write("q-position: [ %12.7f, %12.7f, %12.7f ]\n" % tuple(self._q))
        w.write("point_group: %s\n" % self._pointgroup_symbol)
        w.write("transformation_matrix:\n")
        for v in self._transformation_matrix:
            w.write("- [ %10.7f, %10.7f, %10.7f ]\n" % tuple(v))
        w.write("rotations:\n")
        for i, r in enumerate(self._conventional_rotations):
            w.write("- matrix:\n")
            for v in r:
                w.write("  - [ %2d, %2d, %2d ]\n" % tuple(v))
            if self._rotation_symbols:
                w.write("  symbol: %s\n" % self._rotation_symbols[i])
        w.write("normal_modes:\n")
        for i, deg_set in enumerate(self._degenerate_sets):
            w.write("- band_indices: [ ")
            w.write("%d" % (deg_set[0] + 1))
            for bi in deg_set[1:]:
                w.write(", %d" % (bi + 1))
            w.write(" ]\n")
            w.write("  frequency: %-15.10f\n" % self._freqs[deg_set[0]])
            if self._ir_labels:
                w.write("  ir_label: %s\n" % self._ir_labels[i])
            w.write("  characters: ")
            chars = np.rint(np.abs(self._characters[i]))
            phase = (np.angle(self._characters[i]) / np.pi * 180) % 360
            if len(chars) > 1:
                w.write("[ [ %2d, %5.1f ]" % (chars[0], phase[0]))
                for chi, theta in zip(chars[1:], phase[1:]):
                    w.write(", [ %2d, %5.1f ]" % (chi, theta))
                w.write(" ]\n")
            else:
                w.write("[ [ %2d, %5.1f ] ]\n" % (chars[0], phase[0]))

        if show_irreps:
            self._write_yaml_irreps(w)

        w.close()

    def _write_yaml_irreps(self, file_pointer):
        w = file_pointer
        if not self._irreps:
            self._irrep = self._get_irreps()

        w.write("\n")
        w.write("irreps:\n")
        for i, (deg_set, irrep_Rs) in enumerate(
            zip(self._degenerate_sets, self._irreps)
        ):
            w.write("- # %d\n" % (i + 1))
            for j, irrep_R in enumerate(irrep_Rs):
                if self._rotation_symbols:
                    symbol = self._rotation_symbols[j]
                else:
                    symbol = ""
                if len(deg_set) > 1:
                    w.write("  - # %d %s\n" % (j + 1, symbol))
                    for k, v in enumerate(irrep_R):
                        w.write("    - [ ")
                        for x in v[:-1]:
                            w.write("%10.7f, %10.7f,   " % (x.real, x.imag))
                        w.write("%10.7f, %10.7f ] # (" % (v[-1].real, v[-1].imag))

                        w.write(
                            ("%5.0f" * len(v))
                            % tuple((np.angle(v) / np.pi * 180) % 360)
                        )
                        w.write(")\n")
                else:
                    x = irrep_R[0][0]
                    w.write(
                        "  - [ [ %10.7f, %10.7f ] ] # (%3.0f) %d %s\n"
                        % (
                            x.real,
                            x.imag,
                            (np.angle(x) / np.pi * 180) % 360,
                            j + 1,
                            symbol,
                        )
                    )

        pass