Source code for functions.band_structure

"""Functions for band structure calculations."""

# --- external imports
import numpy as np
import cmath as cm


def _principal(z):
    r"""
    Compute the principal branch of a complex number :math:`z`, such that :math:`-\\\pi<\text{Im}(z)\leq\\\pi`.

    Parameters
    ----------
    z: complex
        The original complex number.

    Returns
    -------
    z: complex
        The principal value of the complex number.
    """

    re = np.real(z)
    im = np.imag(z)
    if im <= -np.pi:
        z = re + 1j * (np.pi - np.abs((im + np.pi)) % (2 * np.pi))
    elif im > np.pi:
        z = re + 1j * (-np.pi + np.abs((im + np.pi)) % (2 * np.pi))

    return z


[docs]def U(var_num, _eigenvectors, _band, _idx_x, _idx_y, _group_size): r"""Compute the link variable. The normalized link variables are defined as .. math:: \tilde{\mathcal{U}}_\gamma(\mathbf{k}_\alpha) = \frac{\det U_\gamma(\mathbf{k}_\alpha)}{|\det U_\gamma(\mathbf{k}_\alpha)|}, \;\;\; \gamma = \{1, 2\}, with link matrices .. math:: U_\gamma(\mathbf{k}_\alpha) = \begin{pmatrix} \braket{u_1(\mathbf{k}_\alpha)|u_1(\mathbf{k}_\alpha+\hat{\mathbf{e}_\gamma})} & \dots & \braket{u_1(\mathbf{k}_\alpha)|u_M(\mathbf{k}_\alpha+\hat{\mathbf{e}_\gamma})} \\ \vdots & \ddots & \vdots \\ \braket{u_M(\mathbf{k}_\alpha)|u_1(\mathbf{k}_\alpha+\hat{\mathbf{e}_\gamma})} & \dots & \braket{u_M(\mathbf{k}_\alpha)|u_M(\mathbf{k}_\alpha+\hat{\mathbf{e}_\gamma})} \end{pmatrix}. Here, :math:`\mathbf{k}_\alpha` is the discretized momentum vector, :math:`\{\hat{\mathbf{e}}_1, \hat{\mathbf{e}}_2\}` are linearly independent unit vectors in the momentum grid, and :math:`\ket{u(\mathbf{k}_\alpha)}` is the eigenvector at momentum :math:`\mathbf{k}_\alpha`. The link variables are constructed for :math:`M` touching bands. :cite:`Fukui05` .. note:: Input eigenvectors are already normalized from :class:`numpy.linalg.eig`. Parameters ---------- var_num: [1, 2] The link variable number. _eigenvectors: ndarray The array of eigenvectors with dimension (num_bands, num_bands, num_samples, num_samples). _band: int The band number. If part of a band group, this must refer to the lowest band of the group. _idx_x: int The x-momentum, with respect to the discretized grid. _idx_y: int The y-momentum, with respect to the discretized grid. _group_size: int The number of bands in the band group. Returns ------- link_var: complex The U(1) link variable. """ _num_samples = np.shape(_eigenvectors)[2] link_matrix = np.zeros((_group_size, _group_size), dtype=complex) for i in range(_group_size): for j in range(_group_size): vec1 = _eigenvectors[:, _band + i, _idx_x, _idx_y] if var_num == 1: vec2 = _eigenvectors[:, _band + j, (_idx_x + 1) % _num_samples, _idx_y] elif var_num == 2: vec2 = _eigenvectors[:, _band + j, _idx_x, (_idx_y + 1) % _num_samples] else: raise ValueError("Link variable number must be in [1, 2].") link_matrix[i, j] = np.conj(vec1).dot(vec2) link_var = np.linalg.det(link_matrix) return link_var
[docs]def berry_curv(_eigenvectors, _band, _idx_x, _idx_y, _group_size=1): r""" Compute the Berry curvature. The Berry curvature around a plaquette is computed using the formula from :cite:`Fukui05` (example applications in :cite:`SoluyanovPhD, AidelsburgerPhD`), such that .. math:: \mathcal{B}_{12}(\mathbf{k}_\alpha) \equiv - \text{Im}\;\log\;(\tilde{\mathcal{U}}_1(\mathbf{k}_\alpha)\tilde{\mathcal{U}}_2(\mathbf{k}_\alpha+\hat{\mathbf{e}}_1)\tilde{\mathcal{U}}_1(\mathbf{k}_\alpha+\hat{\mathbf{e}}_2)^{-1}\tilde{\mathcal{U}}_2(\mathbf{k}_\alpha)^{-1}), where :math:`\tilde{\mathcal{U}}` are the normalized link variables. The Berry curvature at a point :math:`\mathbf{k}` can then be computed by taking the limit of small plaquette size. .. note:: The Berry curvature is defined within the principal branch of the logarithm. For example, the corresponding log sum formula for a single band would be .. math:: \mathcal{B}_{12}(\mathbf{k}_\alpha) = - \text{Im}\;\mathcal{P}\;(\log\tilde{\mathcal{U}}_1(\mathbf{k}_\alpha) +\log\tilde{\mathcal{U}}_2(\mathbf{k}_\alpha+\hat{\mathbf{e}}_1)-\log\tilde{\mathcal{U}}_1(\mathbf{k}_\alpha+\hat{\mathbf{e}}_2)-\log\tilde{\mathcal{U}}_2(\mathbf{k}_\alpha)), where :math:`\mathcal{P}` denotes the principal value of the complex number :math:`z`, such that :math:`-\\\pi<\text{Im}(z)\leq\\\pi`. Parameters ---------- _eigenvectors: ndarray The array of eigenvectors with dimension (num_bands, num_bands, num_samples, num_samples). _band: int The band number. If part of a band group, this must refer to the lowest band of the group. _idx_x: int The x-momentum index, with respect to the discretized grid. _idx_y: int The y-momentum index, with respect to the discretized grid. _group_size: int The number of touching bands a.k.a. number of bands in the band group (default=1). Returns ------- Berry_curv: float The Berry curvature around a square plaquette. """ Berry_curv = - np.imag(np.log(U(1, _eigenvectors, _band, _idx_x, _idx_y, _group_size) * U(2, _eigenvectors, _band, _idx_x+1, _idx_y, _group_size) * U(1, _eigenvectors, _band, _idx_x, _idx_y+1, _group_size)**-1 * U(2, _eigenvectors, _band, _idx_x, _idx_y, _group_size)**-1)) return Berry_curv
[docs]def wilson_loop(_eigenvectors, _band, _idx_y, _group_size=1): r""" Compute the Wilson loop. The Wilson loop term is defined as the product of Berry phases around a cycle of the Brillouin zone, such that .. math:: W = -\Im \log \prod_{\alpha} \tilde{\mathcal{U}}_2(\mathbf{k}_\alpha), where :math:`\tilde{\mathcal{U}}_2` is the normalized link variable, :math:`\mathbf{k}_\alpha` is the discretized momentum vector, and the product is taken on a Brillouin zone cycle in the :math:`\gamma=2` direction. :cite:`Gresch18` Parameters ---------- _eigenvectors: ndarray The array of eigenvectors with dimension (num_bands, num_bands, num_samples, num_samples). _band: int The band number. If part of a band group, this must refer to the lowest band of the group. _idx_y: int The y-momentum index, with respect to the discretized grid. _group_size: int The number of touching bands a.k.a. number of bands in the band group (default=1). Returns ------- Wilson_loop: complex The Wilson loop term. """ # print("np.shape(_eigenvectors) = ", np.shape(_eigenvectors)) numb_kx = np.shape(_eigenvectors)[2] product = 1 for i in range(numb_kx): product *= U(1, _eigenvectors, _band, i, _idx_y, _group_size) Wilson_loop = -np.imag(np.log(product)) return Wilson_loop
[docs]def geom_tensor(_eigenvectors, _eigenvectors_dkx, _eigenvectors_dky, _bvec, _band, _idx_x, _idx_y, _group_size=1): r""" Compute the quantum geometric tensor. The quantum geometric tensor is computed using .. math:: \mathcal{R}_{\mu\nu}(\mathbf{k}) = \mathrm{tr} ( \mathcal{P}_\mathbf{k} \partial_{k_\mu} \mathcal{P}_\mathbf{k} \partial_{k_\nu} \mathcal{P}_\mathbf{k}), where :math:`\mathcal{P}_\mathbf{k} = \sum_n^{N_\mathrm{g}} \ket{u_n(\mathbf{k})}\bra{u_n(\mathbf{k})}` is the band projector and the sum is performed over all bands in the band group :math:`N_\mathrm{g}`. :cite:`Parameswaran13, Hirschmann24` Parameters ---------- _eigenvectors: ndarray The array of eigenvectors with dimension (num_bands, num_bands, num_samples, num_samples). _eigenvectors_dkx: ndarray The array of eigenvectors at a dkx offset with dimension (num_bands, num_bands, num_samples, num_samples). _eigenvectors_dky: ndarray The array of eigenvectors at a dky offset with dimension (num_bands, num_bands, num_samples, num_samples). _bvec: ndarray The array of reciprocal lattice vectors. _band: int The band number. If part of a band group, this must refer to the lowest band of the group. _idx_x: int The x-momentum index, with respect to the discretized grid. _idx_y: int The y-momentum index, with respect to the discretized grid. _group_size: int The number of touching bands a.k.a. number of bands in the band group (default=1). Returns ------- tensor: ndarray The quantum geometric tensor with dimension (2,2). """ numb_samp_x = np.shape(_eigenvectors)[2] numb_samp_y = np.shape(_eigenvectors)[3] tot_proj, tot_proj_dkx, tot_proj_dky = 0, 0, 0 for i in range(_group_size): tot_proj += np.outer(_eigenvectors[:, _band+i, _idx_x, _idx_y], np.conj(_eigenvectors[:, _band+i, _idx_x, _idx_y])) tot_proj_dkx += np.outer(_eigenvectors_dkx[:, _band+i, _idx_x, _idx_y], np.conj(_eigenvectors_dkx[:, _band+i, _idx_x, _idx_y])) tot_proj_dky += np.outer(_eigenvectors_dky[:, _band+i, _idx_x, _idx_y], np.conj(_eigenvectors_dky[:, _band+i, _idx_x, _idx_y])) dkx = np.linalg.norm(_bvec[0]) / (1000 * (numb_samp_x - 1)) dky = np.linalg.norm(_bvec[1]) / (1000 * (numb_samp_y - 1)) grad_kx = np.subtract(tot_proj_dkx, tot_proj) / dkx grad_ky = np.subtract(tot_proj_dky, tot_proj) / dky tensor = np.zeros((2, 2), dtype=np.complex128) tensor[0][0] = np.trace(np.matmul(tot_proj, np.matmul(grad_kx, grad_kx))) tensor[0][1] = np.trace(np.matmul(tot_proj, np.matmul(grad_kx, grad_ky))) tensor[1][0] = np.trace(np.matmul(tot_proj, np.matmul(grad_ky, grad_kx))) tensor[1][1] = np.trace(np.matmul(tot_proj, np.matmul(grad_ky, grad_ky))) return tensor