The Lattice class#

In version 2.0.0 of pythtb, we introduced the Lattice class to encapsulate all information about the lattice geometry, including lattice vectors, orbital positions, and periodicity. This modular approach allows for a separation of concerns, where the Lattice class handles geometric details, while the TBModel class focuses on the tight-binding model itself. This design enhances code readability, maintainability, and reusability.

from pythtb import Lattice
import numpy as np

Below, we demonstrate how to use the Lattice class to define a lattice and then use it to create a tight-binding model.

We start by defining the lattice vectors and orbital positions for a honeycomb lattice with two orbitals per unit cell. The lattice vectors are given by:

\[ \mathbf{a}_{1} = a \hat{x}, \quad \mathbf{a}_{2} = \frac{a}{2} \hat{x} + \frac{a \sqrt{3}}{2} \hat{y} \]

where \(a\) is the lattice constant, which we set to 1 for simplicity. The orbital positions are given in reduced coordinates as:

\[ \mathbf{\tau}_{1} = 0, \quad \mathbf{\tau}_{2} = \frac{1}{3} \mathbf{a}_{1} + \frac{1}{3} \mathbf{a}_{2} \]

giving us a graphene-like structure.

lat_vecs = [[1, 0], [1 / 2, np.sqrt(3) / 2]]
orb_vecs = [[1 / 3, 1 / 3], [2 / 3, 2 / 3]]

We pass this information to the Lattice class to create a lattice object. Additionally, we specify which directions are periodic. In our case, both directions are periodic.

lat = Lattice(orb_vecs=orb_vecs, lat_vecs=lat_vecs, periodic_dirs=[0, 1])

We can see a report of the lattice object to verify its properties by printing it.

print(lat)
----------------------------------------
       Lattice structure report         
----------------------------------------
r-space dimension           = 2
k-space dimension           = 2
periodic directions         = [0, 1]
number of orbitals          = 2

Lattice vectors (Cartesian):
  # 0 ===> [ 1.000,  0.000]
  # 1 ===> [ 0.500,  0.866]
Volume of unit cell (Cartesian) = 0.866 [A^d]

Reciprocal lattice vectors (Cartesian):
  # 0 ===> [ 6.283, -3.628]
  # 1 ===> [ 0.000,  7.255]
Volume of reciprocal unit cell = 45.586 [A^-d]

Orbital vectors (Cartesian):
  # 0 ===> [ 0.500,  0.289]
  # 1 ===> [ 1.000,  0.577]

Orbital vectors (fractional):
  # 0 ===> [ 0.333,  0.333]
  # 1 ===> [ 0.667,  0.667]
----------------------------------------

If we want to get the positions of the orbitals in Cartesian coordinates, we can use the get_orb_vecs method of the Lattice class and set the cartesian argument to True.

lat.get_orb_vecs(cartesian=True)
array([[0.5       , 0.28867513],
       [1.        , 0.57735027]])

The Lattice class internally generates the reciprocal lattice vectors based on the provided lattice vectors and periodicity. We can access these reciprocal lattice vectors using the get_recip_lat_vecs method or the recip_lat_vecs attribute.

print(lat.get_recip_lat_vecs())
print(lat.recip_lat_vecs)
[[ 6.28318531 -3.62759873]
 [ 0.          7.25519746]]
[[ 6.28318531 -3.62759873]
 [ 0.          7.25519746]]

Let’s verigy that the reciprocal lattice vectors satisfy the orthogonality condition with the real-space lattice vectors.

\[\mathbf{a}_{i} \cdot \mathbf{b}_{j} = 2 \pi \delta_{ij}\]
overlap_mat = lat.lat_vecs @ lat.recip_lat_vecs.T
print(overlap_mat / (2 * np.pi))
[[ 1.00000000e+00  0.00000000e+00]
 [-4.79476621e-17  1.00000000e+00]]

If we like, we can also get the volume of the unit cell in both real and reciprocal space. These are stored as attributes of the Lattice class.

print(lat.recip_volume, lat.cell_volume)
45.58575006211245 0.8660254037844386