Non-orthogonal basis set

In this tutorial, we show how to deal with non-orthogonal basis set in diagonalization-based calculations taking monolayer graphene as the example. The script can be found in examples/advanced/non_ortho.py. First of all, we import the necessary packages:

import numpy as np

import tbplas as tb

Then we define the following function to generate the overlap for graphene:

def make_overlap(prim_cell: tb.PrimitiveCell,
                 on_site: float = 1.0,
                 hop: float = 0.0) -> tb.PrimitiveCell:
    """
    Make an overlap for given primitive cell.

    :param prim_cell: primitive cell from which the overlap will be generated
    :param on_site: on-site term of the overlap
    :param hop: hopping terms of the overlap
    :return: overlap with the same numbers of orbitals and hopping terms as the model
    """
    overlap = tb.PrimitiveCell(prim_cell.lat_vec, prim_cell.origin, 1.0)
    for i in range(prim_cell.num_orb):
        orbital = prim_cell.orbitals[i]
        overlap.add_orbital(orbital.position, on_site)

    overlap.add_hopping((0, 0), 0, 1, hop)
    overlap.add_hopping((1, 0), 1, 0, hop)
    overlap.add_hopping((0, 1), 1, 0, hop)
    return overlap

The overlap is actually an instance of the PrimitiveCell class sharing the same lattice vectors, lattice origin and orbital positions. The on-site and hopping terms represent the overlap of the basis functions themselves, and the overlap between different basis functions, respectively. In the orthogonal case, the on-site overlap exactly 1, while the hopping overlap is exactly 0. In the non-orthogonal case, the on-site` overlap is never 1.0, and the hopping overlap is non-zero.

We check the effects of orthogonality of basis functions by calculating the band structure:

def main():
    prim_cell = tb.make_graphene_diamond()
    overlap = make_overlap(prim_cell, on_site=1.0, hop=0.0)
    #overlap = make_overlap(prim_cell, on_site=0.8, hop=0.1)
    solver = tb.DiagSolver(prim_cell, overlap)

    k_points = np.array([
        [0.0, 0.0, 0.0],
        [2./3, 1./3, 0.0],
        [0.5, 0.0, 0.0],
        [0.0, 0.0, 0.0]
    ])
    k_path, k_idx = tb.gen_kpath(k_points, (1000, 1000, 1000))
    solver.config.prefix = "graphene"
    solver.config.k_points = k_path

    timer = tb.Timer()
    timer.tic("bands")
    k_len, bands = solver.calc_bands()
    timer.toc("bands")

    if solver.is_master:
        timer.report_total_time()
        vis = tb.Visualizer()
        vis.plot_bands(k_len, bands, k_idx, ["G", "K", "M", "G"])


if __name__ == "__main__":
    main()

We firstly set on-site to 1.0 and hop to 0.0, i.e., the basis set is orthogonal. Then we set on-site to 0.8 and hop to 0.1 to mimic a non-orthogonal basis set. The overlap instance is passed as the second argument when constructing the solver. Subsequential steps are the same as the orthogonal case. The output is shown in the figure below. It is clear that the non-orthogonal basis set significantly reshapes the band structure, including both the energy range and the dispersion.

../../_images/bands4.png

Band structure of monolayer graphene: (a) orthogonal basis set; (b) non-orthogonal basis set.