Systems and Hamiltonians
This section describes how to define the Hamiltonian of a system. The system, in this context, is a lattice, a basis describing on-site degrees of freedom, and some additional parameters like temperature or chemical potential. The Hamiltonian, in turn, is just an operator that keeps all the information about the system.
There are several models and Hamiltonians shipped with this package: check out Built-in models section of the API docs for more. But in this section, we will focus on how to create the Hamiltonian from the ground up.
Defining the system
First of all, we need to define the system. This is done by calling System
. For example, if we want to create non-interacting fermions with spin 1/2, zero chemical potential and temperature 1, we can do the following:
using LatticeModels, Plots
l = SquareLattice(4, 4);
sys = System(l, SpinBasis(1//2), μ = 0, T = 1)
H = tightbinding_hamiltonian(sys)
P = densitymatrix(H, info=false)
heatmap(localdensity(P))
In this case we have created a system with defined chemical potential and temperature. Note that you can use mu
instead of μ
if you prefer (or if you are using a non-UTF8 compatible editor). The tightbinding_hamiltonian
function creates a tight-binding Hamiltonian for the given system, and the densitymatrix
function calculates the density matrix.
The first two arguments of the System
constructor are the lattice and the basis of the on-site degrees of freedom. The latter can be any QuantumOptics.Basis
object — refer to the QuantumOptics.jl documentation for more information. Omit the second argument if you don't have any on-site degrees of freedom.
Here are all parameters that can be passed to the System
constructor:
mu
orμ
: the chemical potential of the system.N
: set this instead ofmu
to fix the number of particles — the chemical potential will be calculated automatically. Setting bothN
andmu
will raise an error.statistics
: the statistics of the particles, eitherFermiDirac
orBoseEinstein
. If not set, the statistics isFermiDirac
ifmu
orN
is set, and Gibbs otherwise (i.e. the system consist of one particle).T
: the temperature of the system. The default is 0.
Note that you can pass the very same arguments directly to the tightbinding_hamiltonian
. Also keyword arguments can be passed to the densitymatrix
function — they will be used to evaluate the distribution function. These lines are equivalent to the previous example:
H = tightbinding_hamiltonian(l, SpinBasis(1//2))
P = densitymatrix(H, μ = 0, T = 1, info = false)
For manybody computations you may find interesting NParticles
, if the number of particles is fixed, or you can use System(mb_basis[; T])
for any many-body basis mb_basis
to define the system.
Basic tight-binding Hamiltonian
The tightbinding_hamiltonian
function creates a basic tight-binding Hamiltonian defined by this formula:
\[H = t_1 \sum_{\langle i, j \rangle} c_i^\dagger c_j + \text{h.c.} + t_2 \sum_{\langle\langle i, j \rangle\rangle} c_i^\dagger c_j + \text{h.c.} + t_3 \sum_{\langle\langle\langle i, j \rangle\rangle\rangle} c_i^\dagger c_j + \text{h.c.} + \ldots\]
where $c_i^\dagger$ and $c_i$ are the creation and annihilation operators on site $i$. The $t_1$, $t_2$ and $t_3$ are responsible for the hopping between the nearest, next-nearest, etc. neighbors. Note that they can be factors or operators that act on the on-site degrees of freedom.
By default only the nearest-neighbor hopping is included, all other factors are zero. You can set them to any value you want using the t1
, t2
, t3
, keyword arguments:
sz = sigmaz(SpinBasis(1//2))
H2 = tightbinding_hamiltonian(sys, t1 = sz, t2 = [1 0; 0 -1], t3 = -0.5)
P2 = densitymatrix(H2, info=false)
heatmap(localdensity(P2))
Here sz
is the operator that projects the spin on the $z$ axis. Note that we can use a matrix instead, how we did with t2
: in this case it will be interpreted as the hopping operator acting on the on-site degrees of freedom.
The constructor function
There are many cases where more complex Hamiltonians are needed. In this case, you can use the construct_hamiltonian
function. This function takes a system and several arguments, each describing a term in the Hamiltonian. As an example, let's consider the same Hamiltonian as in the previous example:
using LatticeModels, Plots
l = SquareLattice(4, 4)
spin = SpinBasis(1//2)
sys = System(l, spin, μ = 0, T = 1)
sz = sigmaz(spin)
H = construct_hamiltonian(sys,
sz => NearestNeighbor(1),
[1 0; 0 -1] => NearestNeighbor(2),
-0.5 => NearestNeighbor(3))
P = densitymatrix(H, info=false)
heatmap(localdensity(P))
Basically, every term is a pair of an operator acting on the on-site degrees of freedom and an object describing the lattice part. The lattice part can be one of the following:
- On-site term: $\sum_i t_i c_i^\dagger c_i$. In this case, the operator is applied to every site.
- A single
site
describes a single $t_i c_i^\dagger c_i$ term. A convenient way to create an on-site potential. - A
LatticeValue
object describes thet_i
coefficients for every site.
- A single
- Hopping term: $\sum_{\langle i, j \rangle} t_{ij} c_i^\dagger c_j + h.c.$. Terms like this are used to describe the hopping between different sites.
- A single
site1 => site2
pair describes a single hopping term betweensite1
andsite2
. - An
AbstractBonds
object describes the hopping topology. See the Adjacency and boundary conditions section for more information on them.
- A single
- Arbitrary operator: any
QuantumOptics.Operator
object can be passed as a term, in which case it will be automatically embedded into the Hamiltonian.- Note that a matrix can be passed as an operator, in which case it will be interpreted as one acting on the on-site degrees of freedom. To act on the lattice, use
Operator(l, matrix)
— this will create a lattice operator that acts on the latticel
with the given matrix.
- Note that a matrix can be passed as an operator, in which case it will be interpreted as one acting on the on-site degrees of freedom. To act on the lattice, use
To showcase the flexibility of the construct_hamiltonian
function, let's consider a more complex example. We will create a Hamiltonian of the QWZ model of a topological insulator (on a square lattice).
The QWZ model Hamiltonian is defined by the following formula:
\[\hat{H} = \sum_i^\text{sites} m_i c^\dagger_i \sigma_z c_i + \sum_i^\text{sites} \left( c^\dagger_{i + \hat{x}} \frac{\sigma_z - i \sigma_x}{2} c_i + c^\dagger_{i + \hat{y}} \frac{\sigma_z - i \sigma_y}{2} c_i + h. c. \right)\]
We will take $m_i = 1$ and add some disorder to the on-site term. Also we will add electric field in the $x$ direction, local on-site potential on site at $(2, 2)$ and a new hopping term between the next-nearest neighbors.
To do this, we need to create the Hamiltonian like this:
using LatticeModels, Plots
l = SquareLattice(4, 4)
spin = SpinBasis(1//2)
sys = System(l, spin, μ = 0, T = 1)
sx, sy, sz = sigmax(spin), sigmay(spin), sigmaz(spin)
site = l[!, x = 2, y = 2]
x = LatticeValue(l, :x)
E = 0.1 # Electric field strength
H = construct_hamiltonian(sys,
sz => 1 .+ 0.1 .* randn(l), # Random on-site potential
(sz - im * sx) / 2 => Bravais[1, 0], # x-direction hoppings
(sz - im * sy) / 2 => Bravais[0, 1], # y-direction hoppings
0.5 => site, # Local potential on site (2, 2)
E * x, # Electric field in the x direction
0.1 => NearestNeighbor(2)) # Next-nearest neighbor hopping
P = densitymatrix(H, info=false)
heatmap(localdensity(P))
Note that the qwz
function can be used to create the QWZ Hamiltonian in a more convenient way. The construct_hamiltonian
function is more flexible and can be used to create any Hamiltonian you want.
The construct_hamiltonian
function creates a Hamiltonian
object, which is a QuantumOptics.Operator
object, but with some additional info like particle statistics or chemical potential. You can use it as a regular operator, but if you encounter any problems, you can always convert it to a regular operator using the Operator
function or by calling construct_operator
instead of construct_hamiltonian
.
The operator builder
The construct_hamiltonian
function is a convenient way to create Hamiltonians, but it is sometimes more convenient to set all the individual $c_i^\dagger c_j$ terms manually. This can be done using the OperatorBuilder
class.
Here is an example of how to create the same Hamiltonian as in the previous example using the OperatorBuilder
:
using LatticeModels, Plots
l = SquareLattice(4, 4)
spin = SpinBasis(1//2)
sys = System(l, spin, μ = 0, T = 1)
sx, sy, sz = sigmax(spin), sigmay(spin), sigmaz(spin)
builder = OperatorBuilder(sys, auto_hermitian = true)
sizehint!(builder, length(l) * 5) # You can pre-allocate memory for the builder
for site in l
site_x = site + Bravais[1, 0]
site_y = site + Bravais[0, 1]
builder[site, site] = (1 + 0.1 * randn()) * sz
builder[site, site_x] = (sz - im * sx) / 2
builder[site, site_y] = (sz - im * sy) / 2
builder[site, site] += 0.5 * E * site.x
for site2 in adjacentsites(NearestNeighbor(l, 2), site)
# note that the coefficient is 0.05, not 0.1, because every bond is counted twice
builder[site, site2] += 0.05
end
end
c_site = l[!, x = 2, y = 2]
builder[c_site, c_site] += 0.5 # Added local potential on site (2, 2)
H2 = Hamiltonian(builder)
P = densitymatrix(H, info=false)
heatmap(localdensity(P))
Note that builder[site1, site2]
is not a regular indexing operation. The return value of it is not an matrix or operator, but rather a special object that makes increment/decrement operations possible. Do not use this value in other contexts.
Gauge fields
There is a convenient way to add gauge fields to the Hamiltonian, and it is done by using the GaugeField
objects. It is an interface for different types of gauge fields, like the Landau gauge. To add field to the Hamiltonian, use the field
keyword argument:
using LatticeModels, Plots
l = SquareLattice(4, 4)
H = tightbinding_hamiltonian(l, field=LandauGauge(0.1))
P = densitymatrix(H, info=false)
heatmap(localdensity(P))
This adds a magnetic field in the Landau gauge to the Hamiltonian: $\overrightarrow{\mathcal{A}} = B x \overrightarrow{e_y}$. It adds a phase factor to the hopping terms, which is calculated using Peierls substitution.
Here are all types of gauge fields supported by this package:
LandauGauge(B)
— the Landau gauge, with the magnetic fieldB
in the $z$ direction.SymmetricGauge(B)
— the symmetric gauge, with the magnetic fieldB
in the $z$ direction.PointFlux(Phi, center=(0, 0); gauge=:axial)
— a point flux in the pointcenter
with the fluxPhi
. Thegauge
argument can be either:axial
(default) or:singular
:- The
:axial
gauge stands for the vector potential $\vec{\mathcal{A}} = \frac{\vec{\Phi} \times \vec{r}}{r}$. - The
:singular
gauge is not described by any well-defined vector potential; here a particle acquires a phase factor only when it passes below the point flux.
- The
PointFluxes(fluxes, points; gauge=:axial)
— a collection of point fluxes. Thefluxes
is an array of fluxes (or a single flux for all points), and thepoints
is an array of points described asTuple
s. Go to the type docstring to learn more; see alsoperiodic_fluxes
if you need periodic point fluxes in your system.GaugeField(f; n)
— a general magnetic field. Thef
is a function that takes a coordinate vector and returns the vector potential $\mathcal{A}$ at this point. The line integrals are calculated using then
-point trapezoidal rule. Note that then
must be set explicitly.LineIntegralGaugeField(f)
— a general magnetic field. Thef
is a function that takes two coordinate vectors and returns the $\int_{\vec{r}_1}^{\vec{r}_2} \mathcal{A} \cdot d\vec{r}$ line integral of the vector potential between these points.
Different types of fields can be added together:
julia> using LatticeModels
julia> f1 = LandauGauge(0.1)
Landau gauge uniform field; B = 0.1 flux quanta per 1×1 area
julia> f2 = SymmetricGauge(0.2)
Symmetric gauge uniform field; B = 0.2 flux quanta per 1×1 area
julia> f3 = PointFlux(0.3, (0.5, 0.5))
Point flux field through point (0.5, 0.5), axial gauge; Φ = 0.3 flux quanta
julia> f1 + f2 + f3
LandauGauge(0.1) + SymmetricGauge(0.2) + PointFlux(0.3, (0.5, 0.5); gauge=:axial)
Note that adding gauge fields together by +
operator puts extra burden on the compiler. This allows extra performance when the number of the terms in the sum is small, but it can be very slow and cause compiler issues when it is large. In cases where you need a large sum of gauge fields, use more specific field types like PointFluxes
, or consider creating your own custom field type.
You can pass these objects using the field
keyword argument to the tightbinding_hamiltonian
, construct_hamiltonian
, and OperatorBuilder
/FastOperatorBuilder
functions to add the gauge field to the Hamiltonian:
builder = OperatorBuilder(l, auto_hermitian = true, field = LandauGauge(0.1) + PointFlux(0.3, (0.5, 0.5)))
for site in l
site_x = site + Bravais[1, 0]
site_y = site + Bravais[0, 1]
builder[site, site_x] = 1
builder[site, site_y] = 1
end
H2 = Hamiltonian(builder)
H2 == tightbinding_hamiltonian(l, field=LandauGauge(0.1) + PointFlux(0.3, (0.5, 0.5)))
true
Most of the gauge fields are not directly compatible with periodic boundary conditions. Small tweaks are made to make them work automatically: for example, additional point fluxes are added to ensure hoppings are computed correctly, and the gauge is implicitly switched to :singular
. You can disable this behavior by setting auto_pbc_field=false
keyword argument if you really know what you are doing.
To find out more about operators, diagonalization and observables, proceed to the next section.