Spin texture ============ In this tutorial, we demonstrate the usage of :class:`.SpinTexture` class by calculating the spin texture and spin-resolved band structure of graphene with Rashba and Kane-Mele spin-orbital coupling. The spin texture is defined as the the expectation of Pauli operator :math:`\sigma_i`, which can be considered as a function of band index :math:`n` and :math:`\mathbf{k}`-point .. math:: s_i(n, \mathbf{k}) = \langle \psi_{n,\mathbf{k}} | \sigma_i | \psi_{n,\mathbf{k}} \rangle where :math:`i \in {x, y, z}` are components of the Pauli operator. The spin texture can be evaluated for given band index :math:`n` or fixed energy. The script is located at ``examples/prim_cell/spin_texture/spin_texture.py``. As in other tutorials, we begin with importing the ``tbplas`` package: .. code-block:: python from typing import List import numpy as np import matplotlib.pyplot as plt import tbplas as tb Spin texture ------------ We define the following function for calculating the spin texture: .. code-block:: python :linenos: def calc_spin_texture(cell: tb.PrimitiveCell, prefix: str = "kane_mele", ib: int = 0, spin_major: bool = True) -> None: # Sigma_z on a fine grid spin_texture = tb.SpinTexture(cell) spin_texture.config.prefix = f"{prefix}_sigma_z" spin_texture.config.k_points = 2 * (tb.gen_kmesh((240, 240, 1)) - 0.5) spin_texture.config.k_points[:, 2] = 0.0 spin_texture.config.spin_major = spin_major spin_data = spin_texture.calc_spin_texture() plot_sigma_z_band(spin_data, ib) # Sigma_x and sigma_y on a coarse grid spin_texture.config.prefix = f"{prefix}_sigma_xy" spin_texture.config.k_points = 2 * (tb.gen_kmesh((48, 48, 1)) - 0.5) spin_texture.config.k_points[:, 2] = 0.0 spin_data = spin_texture.calc_spin_texture() plot_sigma_xy_band(spin_data, ib) plot_sigma_xy_eng(spin_data) Firstly, we create a :class:`.SpinTexture` object. Then we define the prefix of data files, the k-points and the order of spin-polarized orbitals. To plot the expectation of :math:`\sigma_i` as function of :math:`\mathbf k`-point, we need to generate a uniform mesh grid in the Brillouin zone. As the :func:`.gen_kmesh` function generates k-grid on :math:`[0, 1]`, we need to multiply the result by a factor of 2, then extract 1 from it such that the k-grid will be on :math:`[-1, 1]` which is enough to cover the first Brillouin zone. After that, we call the ``calc_spin_texture`` method to calculate the spin texture. The result is a :class:`.SpinData` object, which contains the Cartesian coordinates of k-points, the energies and the expectation of :math:`\sigma_x`, :math:`\sigma_y` and :math:`\sigma_z`. The expectation of :math:`\sigma_z` can be visualized as scalar field. We achieve this by calling the ``plot_sigma_z_band`` function. The result is shown in the left panel of Fig. 1. The expectation of :math:`\sigma_x` and :math:`\sigma_y` can be visualized simultaneously as vector field. we call the ``plot_sigma_xy_band`` function to visualize :math:`\sigma_x` and :math:`\sigma_y` of specific band, and ``plot_sigma_xy_eng`` to visualize that of specific energy range. The result is shown in the middle and right panels of Fig. 1, where the signature of Rashba spin-orbital coupling is clearly demonstrated. The functions to visualize the results are defined as: .. code-block:: python :linenos: def plot_sigma_z_band(spin_data: tb.SpinData, ib: int = 0) -> None: """ Plot expectation of sigma_z of specific band as scalar field of kx and ky. :param spin_data: spin texture produced of SpinTexture calculator :param ib: band index :return: None """ # Data aliases kpt_cart = spin_data.kpt_cart sigma_z = spin_data.sigma_z[:, ib] # Plot expectation of sigma_z. vis = tb.Visualizer() vis.plot_scalar(x=kpt_cart[:, 0], y=kpt_cart[:, 1], z=sigma_z, scatter=True, num_grid=(480, 480), cmap="jet", with_colorbar=True) def plot_sigma_xy_band(spin_data: tb.SpinData, ib: int = 0) -> None: """ Plot expectation of sigma_x and sigma_y of specific band as vector field of kx and ky. :param spin_data: spin texture produced of SpinTexture calculator :param ib: band index :return: None """ # Data aliases kpt_cart = spin_data.kpt_cart sigma_x = spin_data.sigma_x[:, ib] sigma_y = spin_data.sigma_y[:, ib] # Plot expectation of sigma_z. vis = tb.Visualizer() # Plot expectation of sigma_x and sigma_y. vis.plot_vector(x=kpt_cart[:, 0], y=kpt_cart[:, 1], u=sigma_x, v=sigma_y, cmap="jet", with_colorbar=True) def plot_sigma_xy_eng(spin_data: tb.SpinData, e_min: float = -2, e_max: float = -1.9) -> None: """ Plot expectation of sigma_x and sigma_y of specific energy range as vector field of kx and ky. :param spin_data: spin texture produced of SpinTexture calculator :param e_min: lower bound of energy range in eV :param e_max: upper bound of energy range in eV :return: None """ # Data aliases bands = spin_data.bands kpt_cart = spin_data.kpt_cart sigma_x = spin_data.sigma_x sigma_y = spin_data.sigma_y # Extract spin texture of specific energy range e_min, e_max = -2, -1.9 ik_selected, sigma_x_selected, sigma_y_selected = [], [], [] for i_k in range(bands.shape[0]): idx = np.where((bands[i_k] >= e_min)&(bands[i_k] <= e_max))[0] ik_selected.extend([i_k for _ in idx]) sigma_x_selected.extend(sigma_x[i_k, idx]) sigma_y_selected.extend(sigma_y[i_k, idx]) # Plot expectation of sigma_x and sigma_y. vis = tb.Visualizer() vis.plot_vector(x=kpt_cart[ik_selected, 0], y=kpt_cart[ik_selected, 1], u=sigma_x_selected, v=sigma_y_selected, cmap="jet", with_colorbar=True) .. figure:: images/spin_texture/spin_texture.png :align: center :scale: 30% Spin texture of Kane-Mele model. (Left) Expectation of :math:`\sigma_z` of 0th band. (Middle) Expectation of :math:`\sigma_x` and :math:`\sigma_y` of 0th band. (Right) Expectation of :math:`\sigma_x` and :math:`\sigma_y` of :math:`[-2, -1.9]` eV. Spin-resolved band structure ---------------------------- Spin-resolved band structure can be evaluated in the same approach as spin texture. The only difference is that the k-points should be a path connecting high-symmetric k-points in the first Brillouin zone, rather than a regular mesh grid. We define the following function to calculate the spin-resolved band structure: .. code-block:: python :linenos: def calc_fat_band(cell: tb.PrimitiveCell, prefix: str = "kane_mele", spin_major: bool = True) -> None: # Generate k-path k_points = np.array([ [0.0, 0.0, 0.0], # Gamma [0.5, 0.0, 0.0], # M [2./3, 1./3, 0.0], # K [0.0, 0.0, 0.0], # Gamma [1./3, 2./3, 0.0], # K' [0.5, 0.5, 0.0], # M' [0.0, 0.0, 0.0], # Gamma ]) k_label = [r"$\Gamma$", "M", "K", r"$\Gamma$", r"$K^{\prime}$", r"$M^{\prime}$", r"$\Gamma$"] k_path, k_idx = tb.gen_kpath(k_points, [40, 40, 40, 40, 40, 40]) # Evaluate spin texture (including energies) spin_texture = tb.SpinTexture(cell) spin_texture.config.prefix = f"{prefix}_fat_band" spin_texture.config.k_points = k_path spin_texture.config.spin_major = spin_major spin_data = spin_texture.calc_spin_texture() # Plot fat band plot_fat_band(spin_data, k_idx, k_label, "z") The function for visualizing the results is defined as: .. code-block:: python :linenos: def plot_fat_band(spin_data: tb.SpinData, k_idx: np.ndarray, k_label: List[str], component: str = "z") -> None: """ Plot spin-resolved band structure. :param spin_data: spin texture produced of SpinTexture calculator :param k_idx: indices of high-symmetric k-points :param k_label: labels of high-symmetric k-points :param component: component of sigma :return: None """ # Data aliases bands = spin_data.bands kpt_cart = spin_data.kpt_cart if component == "x": projection = spin_data.sigma_x elif component == "y": projection = spin_data.sigma_y else: projection = spin_data.sigma_z # Evaluate k_len k_len = np.zeros(kpt_cart.shape[0]) for i in range(1, kpt_cart.shape[0]): dk = kpt_cart[i] - kpt_cart[i-1] k_len[i] = k_len[i-1] + np.linalg.norm(dk) # Plot fat band for ib in range(bands.shape[1]): plt.scatter(k_len, bands[:, ib], c=projection[:, ib], s=5.0, cmap="jet") for x in k_len[k_idx]: plt.axvline(x, color="k", linewidth=0.8, linestyle="-") for y in (-2.0, -1.9): plt.axhline(y, color="k", linewidth=1.0, linestyle=":") plt.xlim(k_len[0], k_len[-1]) plt.xticks(k_len[k_idx], k_label) plt.ylabel("Energy (t)") plt.colorbar(label=fr"$\langle\sigma_{component}\rangle$") plt.show() The spin-resolved band structure for :math:`\sigma_z` is shown in Fig. 2, which is consistent with Fig. 1, i.e., zero on :math:`\Gamma`-:math:`M` and non-zero on :math:`M`-:math:`K` paths. .. figure:: images/spin_texture/spin_bands.png :alt: spin_bands :align: center :scale: 100% Spin-resolved band structure of Kane-Mele model. Horizontal dashed lines indicate the energy range for plotting spin texture in the right panel of Fig. 1. Orbital order ------------- The order of spin-polarized orbitals determines how the spin-up and spin-down components are extracted from the eigenstates. There are two popular orders: spin-major and orbital-major. For example, there are two :math:`p_z` orbitals in the two-band model of monolayer graphene in the spin-unpolarized case, which can be denoted as :math:`\{\phi_1, \phi_2\}`. When spin has been taken into consideration, the orbitals become :math:`\{\phi_1^{\uparrow}, \phi_2^{\uparrow}, \phi_1^{\downarrow}, \phi_2^{\downarrow}\}`. In spin-major order they are sorted as :math:`[\phi_1^{\uparrow}, \phi_2^{\uparrow}, \phi_1^{\downarrow}, \phi_2^{\downarrow}]`, while in orbital-major order they are sorted as :math:`[\phi_1^{\uparrow}, \phi_1^{\downarrow}, \phi_2^{\uparrow}, \phi_2^{\downarrow}]`. In the C++ backend of :class:`.SpinTexture` class, the spin-up and spin-down components are extracted as: .. code-block:: c++ :linenos: size_t num_orb_half = num_orbitals / 2; auto seq_up = Eigen::seq(0, num_orb_half - 1, 1); auto seq_down = Eigen::seq(num_orb_half, num_orbitals - 1, 1); if (!spin_major) { seq_up = Eigen::seq(0, num_orbitals - 2, 2); seq_down = Eigen::seq(1, num_orbitals - 1, 2); } // ... Eigen::VectorXcd spin_up(num_orb_half); // half size Eigen::VectorXcd spin_down(num_orb_half); // half size // ... spin_up = eigenstates(seq_up, ib); spin_down = eigenstates(seq_down, ib); If the spin-polarized orbitals follow other user-defined orders, the users should modify the ``SpinTexture::calc_spin_texture`` C++ function to correctly extract the spin-up and spin-down components.