Another package (sisl) can do the same thing, but a bit more verbose and dynamic. (disclaimer, I am the author).
One of sisl's main goal is to interact with large TB models, and thus the complexity of the TB models are a bit more verbose, but it allows greater flexibility when one can query stuff on the fly. It is also intrinsically 3D so one has to deal with the extra dimension not used. It may for instance also be used to read in the Hamiltonian from other DFT packages, such as Siesta and Wannier90.
Here is the same TB models shown for the simple cubic, but also for 2x1
and 2x2
times the basic system, for checking band-folding as mentioned by @leopold.talirz
import numpy as np
import sisl
from matplotlib import pyplot as plt
"""s and p orbitals on 2d square lattice.
- nearest neighbor hopping only
- no hopping between orbitals of different type (e.g. s <> pz)
"""
# lattice constant
alat = 1.0 # Ang
# Create a square lattice with lattice constant as defined above
# Each atom have s and p orbitals and their orbital ranges are alat + .01 Ang
# (just slightly above the lattice constant)
s = sisl.AtomicOrbital("s", R=alat + 0.01)
px = sisl.AtomicOrbital("px", R=alat + 0.01)
py = sisl.AtomicOrbital("py", R=alat + 0.01)
pz = sisl.AtomicOrbital("pz", R=alat + 0.01)
# Since each orbital has a specific orbital range, one can also
# use different ranges per orbital (if needed)
# Create the atom, in sisl atoms needs to have a specie associated.
# They aren't internally used in the electronic structure calculation,
# so it doesn't matter which specie you choose here.
atom = sisl.Atom("H", [s, px, py, pz])
geometry = sisl.geom.sc(alat, atom)
# the sc is a simple cubic lattice, but we will truncate one dimension
# to make it 2D, the below code will remove periodicity along the 3rd
# lattice vector
geometry.set_nsc(c=1)
# Now define how the tight-binding model is denoted
# Since there are 2 orbitals per atom, you need to do define a
# simple method that will create the hoppings
def construct(H, ia, atoms, atoms_xyz=None):
# ia: is the atom that we are currently assigning hopping elements to
# atoms: is a subset of all atoms that is ensured to be reachable by ia
# in this small example it isn't needed, but when very large
# TB models are made, it can greatly speed up the construction
# since it reduces the search space.
# convert to the first orbital on the atom
io = H.geometry.a2o(ia)
# this will give a list of:
# jas_onsite: R <= 0.1
# jas_nn: 0.1 < R <= s.R
jas_onsite, jas_nn = H.geometry.close(ia, R=[0.1, s.R], atoms=atoms, atoms_xyz=atoms_xyz)
# we know that there are two orbitals per atom,
# the 1st is s (as defined in the sisl.Atom(...)
# the 2nd is pz
jos_onsite = H.geometry.a2o(jas_onsite)
H[io, jos_onsite] = 0 # s
for i in (1, 2, 3):
H[io+i, jos_onsite+i] = 3 # p
jos_nn = H.geometry.a2o(jas_nn)
H[io, jos_nn] = -0.5 # s
H[io+3, jos_nn+3] = -0.25 # pz
# Now the anisotropic hoppings for px, py
# first we need to get the supercell hoppings
jas_nn_isc = H.geometry.a2isc(jas_nn)
for jo, isc in zip(jos_nn, jas_nn_isc):
if isc[0] != 0:
# this *must* be hopping along x
H[io+1, jo+1] = +0.25 # px
H[io+2, jo+2] = -0.25 # py
elif isc[1] != 0:
# this *must* be hopping along y
H[io+1, jo+1] = -0.25 # px
H[io+2, jo+2] = +0.25 # py
# Define the Hamiltonian
H = sisl.Hamiltonian(geometry)
# Now construct the TB model
H.construct(construct)
def plot_bs(H):
bs = sisl.physics.BandStructure(H,
[[0, 0, 0], [0.5, 0, 0],
[0.5, 0.5, 0], [0, 0, 0]],
100, names=["Gamma", "X", "M", "Gamma"])
# Calculate the eigenvalues
evals = bs.apply.ndarray.eigh()
# plot band structure
fig, ax = plt.subplots()
# retrieve the linear spacings of the k-points
lk, kt, kl = bs.lineark(True)
ax.set_xticks(kt)
for band in evals.T:
ax.plot(lk, band)
ax.set_title("s and p orbitals on 2d square lattice")
ax.set_xlabel("Path in k-space")
ax.set_ylabel("Band energy")
ax.set_xticks(kt)
ax.set_xticklabels(kl)
fig.tight_layout()
plot_bs(H)
# the same bandstructure, twice the size along x
plot_bs(H.tile(2, axis=0))
# the same bandstructure, twice the size along x and y
plot_bs(H.tile(2, axis=0).tile(2, axis=1))
plt.show()
The same plot:

The 2x1 system:

The 2x2 system:

See here for additional details: https://zerothi.github.io/sisl/tutorials/tutorial_es_1.html
There one can also see how one can expand the eigenstates into real-space quantities.
Please remark that this TB model is not efficiently solved in one Hamiltonian. Since all orbitals are disjointed from the each other, there is no need to have 1 Hamiltonian, instead for better efficiency one could do 4 Hamiltonians.
In sisl
extracting sub orbitals is a breeze:
H_s = H.sub_orbital(atom, s)
# or via index
H_py = H.sub_orbital(atom, 2)
if you diagonalize and plot all 4 individually you'll see exactly the same band-structure.
Note that Hubbard is based on sisl
and allows the Hubbard model quite easily.
kwant
andpybinding
$\endgroup$