STS simulation

This example shows how to

  • work with Wannier functions in the position representation from Wannier90,

  • simulate scanning-tunnelling microscopy (STM) and spectroscopy (STS) data.

The Wannier functions used to represent the Hamiltonian (seedname_hr.dat) and those written to file (seedname_0000X.xsf) must be identical (including sign). Hence, the following line in plot.F90 of Wannier90 must be commented out:

wann_func(:, :, :, loop_w) = wann_func(:, :, :, loop_w)/wmod

The scanning-tunnelling spectrum is calculated just like the density of states, except that each state is weighted with the square modulus of its overlap with an s orbital located at a chosen position above the sample.

#!/usr/bin/env python3

# Copyright (C) 2017-2025 elphmod Developers
# This program is free software under the terms of the GNU GPLv3 or later.

import elphmod
import matplotlib.pyplot as plt
import numpy as np

comm = elphmod.MPI.comm
info = elphmod.MPI.info

nk = 72

tip = np.array([0.0, 0.0, 3.0]) # tip position
V = 0.1 * elphmod.misc.Ry # sample bias

info('Set up and diagonalize Wannier Hamiltonian')

el = elphmod.el.Model('graphene', read_xsf=True, normalize_wf=True,
    check_ortho=True)

e, U, order = elphmod.dispersion.dispersion_full_nosym(el.H, nk,
    vectors=True, order=True)

e -= elphmod.el.read_Fermi_level('scf.out')

info('Set up Bravais lattice vectors')

pwi = elphmod.bravais.read_pwi('scf.in')
a = elphmod.bravais.primitives(**pwi)

info('Calculate density of states')

w, dw = np.linspace(e.min(), e.max(), 300, retstep=True)

DOS = sum(elphmod.dos.hexDOS(e[:, :, n])(w) for n in range(el.size))

info('Determine indices of unit-cell corners and vertical tip position')

def indices(point):
    return np.unravel_index(np.argmin(np.linalg.norm(el.r - point,
        axis=3)), el.r.shape[:3])

x0, y0, z0 = indices(0.0)
x1, y1, z1 = indices(a.sum(axis=0))
xt, yt, zt = indices(tip)

nx = x1 - x0
ny = y1 - y0

info('Shift Wannier functions and calculate overlap with tip')

if comm.rank == 0:
    cells = range(-3, 4)
    R = np.array([(n1, n2, 0) for n1 in cells for n2 in cells])

    W = np.empty((len(R), *el.W.shape[:3]))
    overlap = np.empty((len(R), el.size))

    norm = np.sqrt(np.pi * elphmod.misc.a0 ** 3)

    for iR in range(len(R)):
        dx = R[iR, 0] * nx
        dy = R[iR, 1] * ny

        shift = np.dot(R[iR], a)
        d = np.linalg.norm(tip - shift - el.r, axis=3)
        s = np.exp(-d / elphmod.misc.a0) / norm

        for n in range(el.size):
            W[iR, n] = el.W[n, :, :, zt]
            W[iR, n] = np.roll(W[iR, n], shift=dx, axis=0)
            W[iR, n] = np.roll(W[iR, n], shift=dy, axis=1)

            if dx > 0: W[iR, n, :dx, :] = 0.0
            if dx < 0: W[iR, n, dx:, :] = 0.0
            if dy > 0: W[iR, n, :, :dy] = 0.0
            if dy < 0: W[iR, n, :, dy:] = 0.0

            overlap[iR, n] = np.sum(s * el.W[n]) * el.dV

info('Calculate scanning-tunneling image and weight electronic eigenstates')

STM = np.zeros((nx, ny))
weight = np.empty(e.shape)

if comm.rank == 0:
    scale = 2 * np.pi / nk

    for k1 in range(nk):
        for k2 in range(nk):
            for n in range(el.size):
                if V < e[k1, k2, n] < 0.0 or 0.0 < e[k1, k2, n] < V:
                    tmp = 0.0
                    for iR in range(len(R)):
                        tmp += np.einsum('nxy,n', W[iR, :, x0:x1, y0:y1],
                            U[k1, k2, :, n]) * np.exp(1j
                            * (k1 * R[iR, 0] + k2 * R[iR, 1]) * scale)
                    STM += abs(tmp) ** 2

            tmp = 0.0
            for iR in range(len(R)):
                tmp += np.dot(overlap[iR], U[k1, k2]) * np.exp(1j
                    * (k1 * R[iR, 0] + k2 * R[iR, 1]) * scale)

            weight[k1, k2] = (abs(tmp) / nk) ** 2

comm.Bcast(STM)
comm.Bcast(weight)

info('Plot scanning-tunnelling image')

plot = elphmod.plot.plot(STM, angle=120)

if comm.rank == 0:
    plt.imshow(plot, cmap='afmhot')
    plt.axis('off')
    plt.savefig('simsts_1.png')
    plt.show()

info('Calculate scanning-tunnelling spectrum')

STS = sum(elphmod.dos.hexa2F(e[:, :, n], weight[:, :, n])(w)
    for n in range(el.size))

STS *= el.size / (np.sum(STS) * dw)

if comm.rank == 0:
    plt.grid()
    plt.plot(w, DOS, label='DOS')
    plt.plot(w, STS, label='STS')
    plt.ylabel('Density of states (1/eV)')
    plt.xlabel('Electron energy (eV)')
    plt.legend()
    plt.savefig('simsts_2.png')
    plt.show()
../_images/simsts_1.png ../_images/simsts_2.png