Constructing tight-binding models¶
This guide shows various methods for constructing the non-interacting terms of a model required for RISB.
Constructing h0_k
¶
Warning
The ordering of k
matters for some \(k\)-space integration methods, e.g.,
linear tetrahedron. If that is the case you must permute h0_k
to the
required ordering.
There are many ways to construct the hopping matrices in \(k\)-space. Below
are a few of them that have been useful. Any method can be used as long
as it is possible to return an numpy.ndarray
indexed as
k, orb_i, orb_j
where k
is a \(k\)-point on a meshgrid and orb_i
is one of
the n_orb
orbitals that corresponds to a site, orbital, or spin in a unit
cell. These can possibly be partitioned into symmetry blocks, e.g., by spin.
Explicit method¶
If you have an equation for the matrix representation of \(\hat{H}_0\) at each \(k\)-point, then this is a simple way to code it to have the correct structure for RISB.
First construct a Monkhorst-Pack \(k\)-point mesh in fractional coordinates
import numpy as np
from itertools import product
# The spatial dimensions of the problem
d = ...
# How many points in each spatial dimension
n_k_1 = ...
...
n_k_d = ...
...
n_k_list = [n_k1, ..., n_k_d]
# Total number of k-points
n_k = np.product(n_k_list)
# Shift to apply to each spatial dimension if want to move it off
# high symmetry points. It should be a value between 0 and 1 (usually 0.5)
shift_1 = ...
...
shift_d = ...
shift_list = [shift_1, ..., shift_d]
# Method 1: Using np.meshgrid
# Create linear array in each spatial dimension
k_linear_list = [np.linspace(0, 1, n_k_list[i], endpoint = False) + shift_list[i] / n_k_list[i] for i in range(d)]
# Create a meshgrid
k_mesh = np.meshgrid(*k_linear_list, indexing='ij')
# Make it a list indexed as idx, k1, k2, ...
k_mesh = np.reshape(k_mesh, (d,-1)).T
# Method 2: Using an explicit for loop
# Create empty mesh and populate it
k_mesh = np.empty(shape=(n_k, d))
for idx, coords in enumerate(product( *[range(n_k_list[i]) for i in range(len(n_k_list))] )):
for dim in range(d):
k_mesh[idx,dim] = (coords[dim] + shift_list[dim]) / n_k_list[dim]
If your function for \(\hat{H}_0\) is in a different basis then you will
have to rotate k_mesh
into this basis. For example, to Cartesian
coordinates can be done as
# The unit cell lattice vectors
a_1 = [float_1, ..., float_d]
...
a_d = [float_1, ..., float_d]
...
# A matrix of unit cell lattice vectors where each column is a vector
A = np.array([a_1, a_2, ...]).T
# The reciprocol lattice vectors as a matrix where each column is a vector
G = 2 * np.pi * np.linalg.inv(A).T
# Rotate mesh into this basis as k_cartesian = G @ k_fractional
# Method 1: Using numpy broadcasting
k_mesh = (G @ k_mesh.T).T
# Method 2: Using an explicit for loop
for k in range(k_mesh.shape[0]):
k_mesh[k,:] = G @ k_mesh[k,:]
Next, construct h0_k
as
h0_k = dict()
# For each symmetry block:
# Name of block
bl_name = 'some string'
# Number of orbitals (including sites, orbitals, spin, etc) at each k-point
n_orb = ...
h0_k[bl_name] = np.zeros([n_k, n_orb, n_orb], dtype=complex)
for k in range(n_k):
h0_k[bl_name][k][...] = n_orb by n_orb array that is the function for h0_k in symmetry block bl_name
Using TRIQS lattice tools¶
If you know the positions of the orbitals in real-space then there is a (currently poorly documented) way to do this in TRIQS. Here is some complimentary information on top of what is provided by TRIQS.
First specify the lattice vectors as
units = [a_1, ..., a_d]
The allowed number of spatial dimensions is one of \(d = 1, 2, 3\).
Next give a list of tuples that give the positions of each atom and orbital in
the unit cell. The units are in fractional coordinates of the lattice vectors
that are specified in units
, and there are n_orb
total sites, orbitals,
and maybe spin.
orbital_positions = [
(frac of a_1, ... , frac of a_d), # Position of orbital 1
...
(frac of a_1, ... , frac of a_d), # Position of orbital n_orb
]
Next create a \(k\)-space mesh from this unit cell
from triqs.lattice.tight_binding import BravaisLattice, BrillouinZone, MeshBrZone
# How many points in each spatial dimension
n_k_1 = ...
...
n_k_d = ...
...
n_k_list = [n_k1, ..., n_k_d]
bl = BravaisLattice(units=units)
bz = BrillouinZone(bl)
mk = MeshBrZone(bz, n_k_list)
Next set up a dictionary of hoppings between orbitals, within a unit cell and between unit cells.
Each key in the dictionary is a tuple of the displacement from a reference unit
cell at \(\mathbf{R} = (0, \ldots, 0_d)\).
The coordinates for this displacement is in the basis of lattice
vectors \(\vec{a}_1, \ldots, \vec{a}_d\). For example, in two-dimensions, a
displacement by \(\vec{a}_1\) is the key (1,0)
. A displacement by
\(2\vec{a}_1 + \vec{a}_2\) is the key (2,1)
. Generally, the displacements are
(n,m,p)
where n
, m
, and p
have to be integers.
The value of each key is a matrix of hopping amplitudes from the orbitals
in the reference unit cell to the unit cells defined by the displacement key.
Hopping between orbitals within a unit cell is encoded as a matrix and indexed
with the key (0,...,0_d)
.
Here is an example of how this structure looks like in code
# Number of sites and orbitals (and maybe spin) in each unit cell
n_orb = ...
hoppings = {
(0,...,0_d) : n_orb by n_orb ndarray,
(1,...,0_d) : n_orb by n_orb ndarray,
...
(0,...,1_d) : n_orb by n_orb ndarray,
...
(-1,...,0_d) : n_orb by n_orb ndarray
...
}
Next construct the tight-binding model as
from triqs.lattice.tight_binding import TBLattice
tbl = TBLattice(units=units, hoppings=hoppings, orbital_positions=orbital_positions)
Note
If h0_k
has some block structure you want to encode, the above process
can be done for each block (by definition of being a block matrix structure
there cannot be coupling between orbitals of different blocks).
Each TBLattice
instance must use the same units
and \(k\)-space
meshgrid mk
.
The last and important part is to Fourier transform the tight-binding model
onto the \(k\)-space meshgrid mk
and have it indexed as k, orb_i, orb_j
for
the RISB Solver
class. This is very easily done as
h0_k = dict()
# For each block (a string bl_name) in h0_k
h0_k[bl_name] = tbl.fourier(mk).data
Warning
I have only used this class on hypercubic lattices. It may work poorly for something like kagome, or if the unit cell is very complicated. If this is not an issue then let me know and I will remove this warning.
Using TBmodels¶
Work in progress.
Constructing h0_loc
as a matrix¶
See also
If you want to get \(\hat{H}_i^{\mathrm{loc}}\) for each correlated
space \(\mathcal{C}_i\) calculated from h0_k
then there are some helper
functions.
If there are no projectors and the correlated space is the entire unit cell
from risb.helpers import get_h0_loc_matrix
h0_loc_matrix = dict()
for bl in h0_k.keys():
h0_loc_matrix[bl] = get_h0_loc_matrix(h0_k[bl])
If there are multiple correlated spaces, indexed as i
# Total number of clusters on the lattice
n_clusters = ...
# A list of gf_struct objects in each correlated space
gf_struct = ...
# A list of mappings of the block structure from each correlated
# subspace to the larger space of h0_k
gf_struct_mapping = ...
# A list of projectors into each correlated subspace
projectors =
h0_loc_matrix = [dict() for i in range(n_clusters)]
for i in range(n_clusters):
for bl, bl_size in gf_struct:
bl_full = gf_struct_mapping[bl]
h0_loc_matrix[i][bl] = get_h0_loc_matrix(h0_k[bl_full], projectors[i][bl] )
As a TRIQS operator¶
If you have a block matrix representation of a single-particle operator and you want it as a TRIQS operator
from risb.helpers_triqs get_C_Op
# The block matrix
A = ...
# A gf_struct object of the structure of the space
gf_struct = ...
# A list/vector of operators
C_Op = get_C_Op(gf_struct, dagger=False)
C_dag_Op = get_C_Op(gf_struct, dagger=True)
Op = dict()
for bl, bl_size in gf_struct:
Op[bl] = C_dag_Op[bl] @ A[bl] @ C_Op[bl]
Or you can simply use
from risb.helpers_triqs matrix_to_Op
Op = matrix_to_Op(A, gf_struct)
Constructing h0_kin_k
¶
See also
If you want only the kinetic terms in h0_k
with all of the local terms
h0_loc
from \(\hat{H}_i^{\mathrm{loc}}\) removed then you can use
from risb.helpers import get_h0_kin_k
# A list of gf_struct objects in each correlated space
gf_struct = ...
# A list of mappings of the block structure from each correlated
# subspace to the larger space of h0_k
gf_struct_mapping = ...
# A list of projectors into each correlated subspace
projectors =
h0_kin_k = get_h0_kin_k(h0_k, projectors, gf_struct_mapping)