Slater-Koster formulation

In this tutorial, we show how to evaluate the hopping integrals utilizing the Slater-Koster formulation as implemented in the SK class taking black phosphorus as the example. The corresponding script can be found at examples/prim_cell/sk_soc.py. To begin with, we import the necessary packages:

from math import exp

import numpy as np
from numpy.linalg import norm

import tbplas as tb

Create cell and add orbitals

The primitive cell of black phosphorus without hopping terms can be created with:

 1# Lattice vectors
 2vectors = np.array([
 3    [4.623311297, 0.000000000, 0.000000000],
 4    [0.000000000, 3.299154095, 0.000000000],
 5    [0.000000000, 0.000000000, 20.478000000],
 6])
 7
 8# Orbital coordinates
 9coord_p = np.array([
10    [0.839001067, 0.250000000, 0.448627136],
11    [0.339001577, 0.750000000, 0.551372864],
12    [0.660998423, 0.250000000, 0.551372864],
13    [0.160998933, 0.750000000, 0.448627136],
14])
15orbital_coord = [row for row in coord_p for _ in range(4)]
16
17# Orbital labels
18orbital_label = ("s", "px", "py", "pz") * 4
19
20# Orbital energies
21orbital_energy = {"s": -8.80, "px": 0.0, "py": 0.0, "pz": 0.0}
22
23# Create primitive cell and add orbitals
24cell = tb.PrimitiveCell(lat_vec=vectors, unit=tb.ANG)
25for i, label in enumerate(orbital_label):
26    coord = orbital_coord[i]
27    energy = orbital_energy[label]
28    cell.add_orbital(coord, energy=energy, label=label)

Here we firstly define the Cartesian coordinates of lattice vectors and orbitals in line 2-14. The primitive cell of black phosphorus has four atoms, each carring one \(3s\) and three \(3p\) states. So we need to replicate the orbital coordinates at line 15. Then we define the orbital labels and on-site energies taking from the reference in line 18-21. Note that the SK class will utilize the orbital labels to evaluate the hopping integral, so they must be correctly spelled. After that, we create the primitive cell from the lattice vectors, and add the orbitals with predefined energies and labels with a loop in line 24-28.

Find and add hopping terms

We then preceed to find and add the hopping terms within given cutoff distance utilizing the Slater-Koster formulation. This is achieved by:

 1# Get hopping terms in the nearest approximation
 2neighbors = tb.find_neighbors(cell, a_max=2, b_max=2, max_distance=1.0)
 3
 4# Add hopping terms
 5sk = tb.SK()
 6for term in neighbors:
 7    i, j = term.pair
 8    label_i = cell.get_orbital(i).label
 9    label_j = cell.get_orbital(j).label
10    hop = calc_hop_bp(sk, term.rij, label_i, label_j)
11    cell.add_hopping(term.rn, i, j, hop)

Here we call the find_neighbors() function to search for orbital pairs within 1.0 nm. The searching range is defined by a_max and b_max, i.e., \([-2, 2]\otimes[-2, 2]\otimes[0, 0]\) in our case, while the cutoff distance is given by max_distance. The returned value neighbors is a list of named tuples, where the pair attribute stands for the orbital indices, rn is the cell index, rij is the displacement vector and distance is the norm of rij. Then we create an SK instance, and do a loop to add the hopping terms in line 6-11. The calc_hop_bp function for evaluating the hopping integral is defined as:

 1def calc_hop_bp(sk: tb.SK, rij: np.ndarray, label_i: str,
 2                label_j: str) -> complex:
 3    """
 4    Evaluate the hopping integral <i,0|H|j,r> for single layer black phosphorus.
 5
 6    Reference:
 7    https://www.sciencedirect.com/science/article/pii/S0927025617306705
 8
 9    :param sk: SK instance
10    :param rij: displacement vector from orbital i to j in nm
11    :param label_i: label of orbital i
12    :param label_j: label of orbital j
13    :return: hopping integral in eV
14    """
15    r = norm(rij)
16    r0 = 0.2224
17    v_sss = -1.59 * exp(-(r - r0) / 0.033)
18    v_sps = 2.39 * exp(-(r - r0) / 0.053)
19    v_pps = 4.03 * exp(-(r - r0) / 0.058)
20    v_ppp = -1.14 * exp(-(r - r0) / 0.053)
21    return sk.eval(r=rij, label_i=label_i, label_j=label_j,
22                   v_sss=v_sss, v_sps=v_sps,
23                   v_pps=v_pps, v_ppp=v_ppp)

Here we firstly evaluate the Slater-Koster parameters \(V_{ss\sigma}\), \(V_{sp\sigma}\), \(V_{pp\sigma}\) and \(V_{pp\pi}\) in line 17-20. Then we call the eval method of SK instance sk to get the hopping integral, taking the displacement vector rij, orbital labels label_i and label_j, and the Slater-Koster parameters as the arguments.

Check the results

We check the primitive cell we have just created by calculating its band structure:

 1# Test band structure
 2k_points = np.array([
 3    [0.0, 0.0, 0.0],
 4    [0.5, 0.0, 0.0],
 5    [0.5, 0.5, 0.0],
 6    [0.0, 0.5, 0.0],
 7    [0.0, 0.0, 0.0]
 8])
 9k_label = ["G", "X", "S", "Y", "G"]
10k_path, k_idx = tb.gen_kpath(k_points, [40, 40, 40, 40])
11k_len, bands = cell.calc_bands(k_path)
12tb.Visualizer().plot_bands(k_len, bands, k_idx, k_label)

The results are shown in the left panel, consistent with the band structure in the right panel taken from the reference.

../../_images/bands1.png

Band structure of black phosphorus (a) created using Slater-Koster formulation and (b) taken from the reference.