Skip to main content

Lattices

Overview

In many-body physics we often want to study systems defined on crystal lattices. In such cases, states, interactions, and correlation functions are most naturally defined in terms of the coordinates of the lattice. Simultaneously, it is optimal for performance reasons to store data in linear arrays. The lattice module provides a convenient and expressive way of defining lattices and lattice coordinates, and transforming those coordinates into linear indices.

Lattice Coordinates

Any crystal lattice can be defined in terms of a Bravais lattice and an atomic basis. A dd-dimensional Bravais lattice is defined in terms of a set of dd primitive vectors, {pj}j=1d\{\vec{p}_j\}_{j=1}^d where pjRD\vec{p}_j \in \mathbb{R}^D and DdD \geq d the embedding dimension for the lattice. Any Bravais lattice point is identified by the vector,

r(n1,...,nd)=j=1dnjpj,\vec{r}(n_1,...,n_d) = \sum_{j=1}^{d} n_j\vec{p}_j,

which is a function of the primitive indices njZn_j \in \mathbb{Z}. We can define a crystal basis of BB additional sites that repeat on each Bravais lattice point, {bk}k=1B\{\vec{b}_k\}_{k=1}^B. The position of an arbitrary lattice site is then,

R(n1,...nd,k)=r(n1,...nd)+bk.R(n_1,...n_d, k) = \vec{r}(n_1,...n_d) + \vec{b}_k.

In theory, a crystal lattice extends infinitely in all directions. For computational purposes all lattices must have finite size. We denote by LjL_j the length of the lattice along dimension j1,,dj \in {1,\ldots, d}. With this notation in place, we can express any point on the crystal lattice in terms of the lattice coordinate,

(n1,...,nd,k)ZL1×ZL2×...×ZLd×ZB (n_1,...,n_d, k) \in \mathbb{Z}_{L_1}\times\mathbb{Z}_{L_2}\times...\times \mathbb{Z}_{L_d} \times \mathbb{Z}_{B}

In aleph, this is represented by the LatticeCoordinate type, which can be constructed using the lattice_coordinate function,

var coord = lattice_coordinate([11,2,7], 2)
coord.primitive_vector() //-> [11, 2, 7]
coord.basis_index() //-> 2

If no basis index is specified the default is zero. By itself, a lattice coordinate has no meaning, since lattice coordinates do not carry information about primitive vectors or the crystal basis. For this, we need Lattice, which we discuss below.

Often we want to get lattice coordinates relative to other coordinates, such as when we define interactions. To this end, latice coordinates support basic arithmetic operators

var x = lattice_coordinate([10,1],1)
var y = lattice_coordinate([2,2], 0)
x + y //-> lattice_coordinate([12,3],1)

Lattice

In aleph, a Lattice is best thought of as a tool for interpreting lattice coordinates. Each instance of Lattice contains four pieces of information:

  • The primitive vectors of the lattice.
  • The positions of each site in the crystal basis.
  • The length of the lattice along each dimension.
  • The boundary conditions of the lattice (either open or periodic) along each dimension.

Just as with a lattice coordinate, Lattice objects are created with the lattice function. There are several way to use lattice. First, one can create a predefined lattice (any of the two dimensional Bravais lattices),

var rectangular_lattice = lattice("rectangular", [10, 14], ["a" : 1.25, "b" : 0.5]) //-> a 10 X 14 rectangular lattice with sides lengths 1.0 and 0.5.
rectangular_lattice.primitive_vectors() //-> [[1.25,0],[0,0.5]].
rectangular_lattice.basis_positions() //->[[0.0,0.0]]

Below are the predefined lattices and their expected parameters,

NameParameters
"square""a" Intersite spacing
"rectangular""a" Intersite spacing along x-axis."b" Intersite spacing along y-axis.
"triangular""a" Intersite spacing.
"oblique""a" Intersite spacing along x-axis."b" Intersite spacing along other axis."theta" angle between primitive vectors.

By default lattices have open boundaries. For more examples of lattices that can be constructed one can refer to the Lattice tutorial page.

Now that we have a Lattice we can use it to translate lattice coordinates into either physical positions or linear indices.

var LC = lattice_coordinate
rectangular_lattice.to_index(LC[10, 0]) //-> 10
rectangular_lattice.to_position(LC[10,2]) //-> [12.5, 1.0]

A Lattice can also be used to validate whether a coordinate is in the lattice.

rectangular_lattice.contains(LC([2,3])) //-> True
rectangular_lattice.contains(LC([100,12])) //-> False

It's useful to be able to loop through all the coordinates in the lattice in a systematic way. This can be achieved with the use of the lattice range method. For example,

for(coordinate : lattice_range(rectangular_lattice)) {
print(coordinate)
}

Neighbourhoods and Neighbourhood Rules

Interactions between degrees of freedom interacting on a lattice are often defined in terms of neighbourhoods (e.g. nearest neighbours, plaquettes, etc...). More precisely, a neighbourhood of NN sites is a collection of displacement coordinates, {δl}l=1N\{\vec{\delta}_l\}_{l=1}^N with δlRD\delta_l \in \mathbb{R}^D. Let L\mathcal{L} denote a lattice and let N\mathcal{N} denote a set of neighbourhoods on the lattice. Any Hamiltonian may be written as,

H^=rLνNh^r(ν)δνh^r+δ \hat{H} = \sum_{r \in \mathcal{L}} \sum_{\nu \in \mathcal{N}} \hat{h}_{\vec{r}}(\nu)\prod_{\vec{\delta}\in\nu} \hat{h}_{\vec{r} + \vec{\delta}}

where h^\hat{h} is an operator acting on a single lattice site that depends on the neighbourhood and gg is an interaction strength that depends on the neighbourhood and the position.

For example, on the lattice above, the nearest neighbour and next nearest neighbours along the xx and yy directions are given by,

var nn_x = neighbourhood_rule("nn_x", LC([0,1]))
var nn_y = neighbourhood_rule("nn_y", LC([1,0]))

We can store all the neighbourhoods associated with a list of neighbourhood rules using the neighbourhood table,

var neighbourhoods = neighbourhood_table([nn_x, nn_y], rectangular_lattice)
neighourhoods("nn_x") //->Pairs of indices for all the x nearest neighbours.

Documentation Contributors

James Lambert