Introduction

GRABNES (GRAphene-BN-Electronic Structure) allows you to calculate the electronic properties of several types of layered materials, including the obvious combinations of graphene and h-BN, hence the name that was given to this package.

One can build the systems for several basis systems using built-in methods which is useful as a first approximation when considering the rigid configurations. In reality, we often want to include lattice relaxation effects where we read in the structure given as output from LAMMPS (for example) as input for the GRABNES code. The go-to method in that case is ReadXYZ.

In this tutorial, we are going to first use the build-in methods to build the systam and calculate some observables for them while after we are going to focus on the ReadXYZ approach.

A few useful post-processing code-snippets are given at the end of the notebook.

[ ]:

Built-in methods

Graphene

Gendata_graphene.in allows you to build graphene systems. Run it using the grabnes executable in bin to make sure it runs correctly. - Modify it to build systems of different sizes, using CellSize. Check in the source code what this flag means. Get familiar with grep -r "SuperCell ."-style commands to find all the occurences of a certain flag inside the source code. - Modify the output files generate.pos or pos to your needs to visualize it using VESTA, VMD, xcrysden or any other of your favorite tools.

Graphene with hBN, using effective model

Let’s switch our attention to graphene on hBN using the effective model introduced in [Jeil Jung, PRB 89, 205414 (2014)]. For this, we can use (some of) the following input flags: - MoirePotential .true. - MoireJeil .true. - MoireOffDiag .true. - MoireOnlyH0 .false. - MoireOnlyHZ .false. - MoireNoH0AndHZ .false. - MoireH0AndHZ .true. - MoirePotC0   0-0.01013 - MoirePotCz   0-0.00901 - MoirePotCab   0.01134 - MoirePotPhi0  1.510233401750693 - MoirePotPhiz  0.147131255943122 - MoirePotPhiab  0.342084533390889 - Latticepercent  0-0.018181818181818

Tasks: - The value for the last flag works for a 55x55-sized system. Why is that? - Modify the value for a 56x56-sized system. - Why is there a negative sign in front of this value? - Plot the onsite energies for each of the atoms using a scatter plot where the color matches those energies. Confirm that the resulting moire pattern is periodic. - Calculate the DOS for a single moire unit cell using Lanczos recursion. Plot out the data in the resulting generate.DOS. Why does the plot look spiky? - Run a convergence study on this DOS by increasing the SuperCell value. - Calculate the DOS using exact diagonalization. - Calculate the electronic bandstructure of this system. - Add a magnetic field to this system and repeat the previous steps. Why can’t you use exact diagonalization for this? Hint: read up on the Peierls phase substitution, see for instance Ref. [A. Cresti, Phys. Rev. B 103, 045402 (2021)].

Twisted bilayer graphene

A common way to describe a moire system is using the 4 indices defined by Hermann.

Tasks: - Create a twisted bilayer system using the 31 30 30 31 indices and visualize the structure. - Calculate the electronic bandstructure and visualize the flat band. - Calculate the DOS and optimize the supercell size to get clear signatures.

ReadXYZ

Twisted bilayer graphene

  • Repeat the twisted bilayer graphene calculations from the previous section using the relaxed structure given for download here.

  • Compare the results for the previous rigid configuration with the relaxed configuration observables.

Graphene on h-BN

  • Calculate the bandstructure for this system using the input file and structure given in the shared folder.

[ ]:

Code snippets

[4]:
import numpy as np
import matplotlib.pyplot as plt
import os

def latexify(fig_width=None, fig_height=None, columns=1):
    """Set up matplotlib's RC params for LaTeX plotting.
    Call this before plotting a figure.

    Parameters
    ----------
    fig_width : float, optional, inches
    fig_height : float,  optional, inches
    columns : {1, 2}
    """

    # code adapted from http://www.scipy.org/Cookbook/Matplotlib/LaTeX_Examples

    # Width and max height in inches for IEEE journals taken from
    # computer.org/cms/Computer.org/Journal%20templates/transactions_art_guide.pdf

    assert(columns in [1,2])

    if fig_width is None:
        fig_width = 3.39 if columns==1 else 6.9 # width in inches

    if fig_height is None:
        golden_mean = (sqrt(5)-1.0)/2.0    # Aesthetic ratio
        fig_height = fig_width*golden_mean # height in inches

    #MAX_HEIGHT_INCHES = 7.0
    #if fig_height > MAX_HEIGHT_INCHES:
    #    print("WARNING: fig_height too large:" + fig_height +
    #          "so will reduce to" + MAX_HEIGHT_INCHES + "inches.")
    #    fig_height = MAX_HEIGHT_INCHES

    params = {'backend': 'ps',
              'text.latex.preamble': [r'\usepackage{gensymb}'],
              'axes.labelsize': 8, # fontsize for x and y labels (was 10)
              'axes.titlesize': 8,
              'font.size': 8, # was 10
              'legend.fontsize': 8, # was 10
              'xtick.labelsize': 8,
              'ytick.labelsize': 8,
              'text.usetex': True,
              'figure.figsize': [fig_width,fig_height],
              'font.family': 'serif',
              'figure.autolayout': True,
              'lines.linewidth' : 1.0
    }

    matplotlib.rcParams.update(params)


def format_axes(ax):

    SPINE_COLOR = 'gray'

    for spine in ['top', 'right']:
        ax.spines[spine].set_visible(False)

    for spine in ['left', 'bottom']:
        ax.spines[spine].set_color(SPINE_COLOR)
        ax.spines[spine].set_linewidth(0.5)

    ax.xaxis.set_ticks_position('bottom')
    ax.yaxis.set_ticks_position('left')

    for axis in [ax.xaxis, ax.yaxis]:
        axis.set_tick_params(direction='out', color=SPINE_COLOR)

#q_sigma = c0 * 22.2  # [nm] hopping fitting parameter
#q_pi = a_cc * 22.2  # [nm] hopping fitting parameter
#r0 = 0.184 * a  # [nm] hopping fitting parameter




from mpl_toolkits.axes_grid1 import make_axes_locatable


def getBandstructure(dir1, nPlotBands, EShift):

    f = open(dir1+'/generate.bands.dat','r')
    print(f)


    i = 0
    j = 0
    l = 0

    k = []
    E = []

    kVec = []
    EVec = []

    counterPlotBands = 0

    for line in f:
        i = i+1
        a = line.split()
        if i==10:
            nbands = int(a[5])
            print(nbands)
            nk = int(a[7])
            print(nbands,nk)
        if i>16:
            if len(a)==0:
                j = j+0.5
                #print(j)
            else:
                if (int(j)==int(nbands/2-nPlotBands/2 + counterPlotBands) and int(j) < int(nbands/2+nPlotBands/2)):
                    if l<nk:
                        k.append(float(a[0]))
                        E.append((float(a[1])-EShift))
                        l = l+1
                    if l==nk:
                        kVec.append(k)
                        EVec.append(E)
                        l = 0
                        counterPlotBands += 1
                        k = []
                        E = []

    return kVec, EVec

def getSingleBand3(dir1, EShift, bandIndex):

    f = open(dir1+'/generate.bands.dat','r')


    i = 0
    j = 0
    l = 0

    k = []
    E = []

    kVec = []
    EVec = []

    counterPlotBands = 0

    for line in f:
        i = i+1
        a = line.split()
        if i==10:
            nbands = int(a[5])
            nk = int(a[7])
            #print(nbands,nk)
        if i>14:
            if len(a)==0:
                j = j+0.5
                #print(j)
            else:
                #print(nbands)
                if (int(j)==int(nbands/2) + bandIndex):
                    if l<nk:
                        k.append(float(a[0]))
                        E.append(float(a[1])-EShift)
                        l = l+1
                    if l==nk:
                        kVec.append(k)
                        EVec.append(E)
                        l = 0
                        counterPlotBands += 1
                        k = []
                        E = []

    return kVec, EVec

def getGap(dir1, EShift):
    Emin = 10000000
    Emax = -10000000
    bandIndexVec = [-1]
    for bandIndex in bandIndexVec:
        kVec, EVec = getSingleBand3(dir1, EShift, bandIndex)
        i = 0
        if (np.max(EVec)>Emax):
            Emax = np.max(EVec)
        #if (np.min(EVec)<Emin):
        #    Emin= np.min(EVec)
    bandIndexVec = [0]
    for bandIndex in bandIndexVec:
        kVec, EVec = getSingleBand3(dir1, EShift, bandIndex)
        i = 0
        if (np.min(EVec)<Emin):
            Emin= np.min(EVec)
    gap = (Emin - Emax)*1000
    return gap

def getGap2(dir1, EShift):
    Emin = 10000000
    Emax = -10000000
    bandIndexVec = [-3]
    for bandIndex in bandIndexVec:
        kVec, EVec = getSingleBand3(dir1, EShift, bandIndex)
        i = 0
        if (np.max(EVec)>Emax):
            Emax = np.max(EVec)
        #if (np.min(EVec)<Emin):
        #    Emin= np.min(EVec)
    bandIndexVec = [-2]
    for bandIndex in bandIndexVec:
        kVec, EVec = getSingleBand3(dir1, EShift, bandIndex)
        i = 0
        if (np.min(EVec)<Emin):
            Emin= np.min(EVec)
    gap = (Emin - Emax)*1000
    return gap

def getEnergyCutPlot(dir1, chosenEnergy, Nx, Ny, cutoffMin, cutoffMax, nEnergy):

    os.chdir(dir1)


    data = np.genfromtxt("generate.spectral",skip_header=4)
    kx = data[:,0]
    ky = data[:,1]
    kz = data[:,2]
    energy = (data[:,3]*3.5)-0.31
    spectral = data[:,4]

    cutoffMin = cutoff
    cutoffMax = cutoff

    i = 0
    j = 0
    eigVec = []
    spectralVec = []

    eig = np.zeros(nEnergy)
    spect = np.zeros(nEnergy)
    k = np.zeros(shape=(Nx*Ny,2))

    for el1, el2, el3, el4 in zip(kx,ky,energy,spectral):
        i = i+1
        if (i==1):
            k[j,0] = el1
            k[j,1] = el2
        if (i<=nEnergy):
            eig[i-1] = el3
            spect[i-1] = el4
        if (i==nEnergy):
            eigVec.append(eig)
            spectralVec.append(spect)
            i = 0
            j = j+1
            eig = np.zeros(nEnergy)
            spect = np.zeros(nEnergy)


    eigTrueVec = []
    for i, kki in enumerate(k):
        eigTrue = np.zeros(nEnergy)
        eig = eigVec[i]
        spec = spectralVec[i]
        eigTrue[np.logical_and(np.logical_and(eig>chosenEnergy-cutoffMin,eig<chosenEnergy+cutoffMax),spec>0.0)] = 1.0
        specSum = np.sum(spec[np.logical_and(np.logical_and(eig>chosenEnergy-cutoffMin,eig<chosenEnergy+cutoffMax),spec>0.0)])
        if (eigTrue.any()==1):
            eigTrueVec.append(specSum)
        else:
            eigTrueVec.append(0)


    return k, eigTrueVec

K1 = np.array([1.70276024584813, 0.0000])
K1p = np.array([1.70243417150001, 3.332186294066186E-002])


def getEnergyCutPlot2(dir1, chosenEnergy, Nx, Ny, cutoffMin, cutoffMax, nEnergy):

    os.chdir(dir1)


    data = np.genfromtxt("generate.spectral")
    kx = data[:,0]
    ky = data[:,1]
    kz = data[:,2]
    energy = (data[:,3]*3.5)-0.31
    spectral = data[:,4]

    cutoffMin = cutoff
    cutoffMax = cutoff

    i = 0
    j = 0
    eigVec = []
    spectralVec = []

    eig = np.zeros(nEnergy)
    spect = np.zeros(nEnergy)
    k = np.zeros(shape=(Nx*Ny,2))

    for el1, el2, el3, el4 in zip(kx,ky,energy,spectral):
        i = i+1
        if (i==1):
            k[j,0] = el1
            k[j,1] = el2
        if (i<=nEnergy):
            eig[i-1] = el3
            spect[i-1] = el4
        if (i==nEnergy):
            eigVec.append(eig)
            spectralVec.append(spect)
            i = 0
            j = j+1
            eig = np.zeros(nEnergy)
            spect = np.zeros(nEnergy)


    eigTrueVec = []
    for i, kki in enumerate(k):
        eigTrue = np.zeros(nEnergy)
        eig = eigVec[i]
        spec = spectralVec[i]
        eigTrue[np.logical_and(np.logical_and(eig>chosenEnergy-cutoffMin,eig<chosenEnergy+cutoffMax),spec>0.0)] = 1.0
        specSum = np.sum(spec[np.logical_and(np.logical_and(eig>chosenEnergy-cutoffMin,eig<chosenEnergy+cutoffMax),spec>0.0)])
        if (eigTrue.any()==1):
            eigTrueVec.append(specSum)
        else:
            eigTrueVec.append(0)


    return k, eigTrueVec

def getDOS(fileName):
    data = np.genfromtxt(fileName)
    xdata = data[:,0]
    ydata = data[:,1]
    return xdata, ydata

K1 = np.array([1.70276024584813, 0.0000])
K1p = np.array([1.70243417150001, 3.332186294066186E-002])
[ ]:

[ ]:

[ ]: