Skip to content
Snippets Groups Projects
Forked from kwant / kwant
307 commits behind, 51 commits ahead of the upstream repository.
kpm.rst 22.45 KiB

Calculating spectral density with the kernel polynomial method

We have already seen in the ":ref:`closed-systems`" tutorial that we can use Kwant simply to build Hamiltonians, which we can then directly diagonalize using routines from Scipy.

This already allows us to treat systems with a few thousand sites without too many problems. For larger systems one is often not so interested in the exact eigenenergies and eigenstates, but more in the density of states.

The kernel polynomial method (KPM), is an algorithm to obtain a polynomial expansion of the density of states. It can also be used to calculate the spectral density of arbitrary operators. Kwant has an implementation of the KPM method kwant.kpm, that is based on the algorithms presented in Ref. [1].

Introduction

Our aim is to use the kernel polynomial method to obtain the spectral density \rho _A(E), as a function of the energy E, of some Hilbert space operator A. We define

\rho _A(E) = \rho (E) A(E),

where A(E) is the expectation value of A for all the eigenstates of the Hamiltonian with energy E, and the density of states is

\rho (E) = \sum_{k=0}^{D-1} \delta (E-E_k) = \mathrm{Tr}\left(\delta(E-H)\right),

where H is the Hamiltonian of the system, D the Hilbert space dimension, and E_k the eigenvalues.

In the special case when A is the identity, then \rho _A(E) is simply \rho (E), the density of states.

Calculating the density of states

Roughly speaking, KPM approximates the density of states, or any other spectral density, by expanding the action of the Hamiltonian and operator of interest on a small set of random vectors (or local vectors for local density of states), as a sum of Chebyshev polynomials up to some order, and then averaging. The accuracy of the method can be tuned by modifying the order of the Chebyshev expansion and the number of vectors. See notes on accuracy below for details.

The global spectral density \rho _A(E) is approximated by the stochastic trace, the average expectation value of random vectors r

\rho _A(E) = \mathrm{Tr}\left(A\delta(E-H)\right) \sim \frac{1}{R}
\sum_r \langle r \lvert A \delta(E-H) \rvert r \rangle,

while the local spectral density for a site i is

\rho ^i_A(E) = \langle i \lvert A \delta(E-H) \rvert i \rangle,

which is an exact expression.

Global spectral densities using random vectors

In the following example, we will use the KPM implementation in Kwant to obtain the (global) density of states of a graphene disk.

We start by importing kwant and defining our system.

After making a system we can then create a ~kwant.kpm.SpectralDensity object that represents the density of states for this system.

The ~kwant.kpm.SpectralDensity can then be called like a function to obtain a sequence of energies in the spectrum of the Hamiltonian, and the corresponding density of states at these energies.

When called with no arguments, an optimal set of energies is chosen (these are not evenly distributed over the spectrum, see Ref. [1] for details), however it is also possible to provide an explicit sequence of energies at which to evaluate the density of states.

In addition to being called like functions, ~kwant.kpm.SpectralDensity objects also have a method ~kwant.kpm.SpectralDensity.integrate which can be used to integrate the density of states against some distribution function over the whole spectrum. If no distribution function is specified, then the uniform distribution is used:

We see that the integral of the density of states is normalized to the total number of available states in the system. If we wish to calculate, say, the number of states populated in equilibrium, then we should integrate with respect to a Fermi-Dirac distribution:

Local spectral densities using local vectors

The local density of states can be obtained without using random vectors, and using local vectors instead. This approach is best when we want to estimate the local density on a small number of sites of the system. The accuracy of this approach depends only on the number of moments, but the computational cost increases linearly with the number of sites sampled.

To output local densities for each local vector, and not the average, we set the parameter mean=False, and the local vectors will be created with the ~kwant.kpm.LocalVectors generator (see advanced_topics for details).

The spectral density can be restricted to the expectation value inside a region of the system by passing a where function or list of sites to the ~kwant.kpm.RandomVectors or ~kwant.kpm.LocalVectors generators.

In the following example, we compute the local density of states at the center of the graphene disk, and we add a staggering potential between the two sublattices.

Next, we choose one site of each sublattice "A" and "B",

and plot their respective local density of states.

Note that there is no noise comming from the random vectors.

Increasing the accuracy of the approximation

~kwant.kpm.SpectralDensity has two methods for increasing the accuracy of the method, each of which offers different levels of control over what exactly is changed.

The simplest way to obtain a more accurate solution is to use the add_moments method:

This will update the number of calculated moments and also the default number of sampling points such that the maximum distance between successive energy points is energy_resolution (see notes on accuracy).

Alternatively, you can directly increase the number of moments with add_moments, or the number of random vectors with add_vectors. The added vectors will be generated with the vector_factory.

Calculating the spectral density of an operator

Above, we saw how to calculate the density of states by creating a ~kwant.kpm.SpectralDensity and passing it a finalized Kwant system. When instantiating a ~kwant.kpm.SpectralDensity we may optionally supply an operator in addition to the system. In this case it is the spectral density of the given operator that is calculated.

~kwant.kpm.SpectralDensity accepts the operators in a few formats:

  • explicit matrices (numpy array or scipy sparse matrices will work)
  • operators from kwant.operator

If an explicit matrix is provided then it must have the same shape as the system Hamiltonian.

Or, to do the same calculation using kwant.operator.Density:

Spectral density with random vectors

Using operators from kwant.operator allows us to calculate quantities such as the local density of states by telling the operator not to sum over all the sites of the system:

~kwant.kpm.SpectralDensity will properly handle this vector output, and will average the local density obtained with random vectors.

The accuracy of this approximation depends on the number of random vectors used. This allows us to plot an approximate local density of states at different points in the spectrum:

Calculating Kubo conductivity

The Kubo conductivity can be calculated for a closed system with two KPM expansions. In ~kwant.kpm.Conductivity we implemented the Kubo-Bastin formula of the conductivity and any temperature (see Ref. [2]). With the help of ~kwant.kpm.Conductivity, we can calculate any element of the conductivity tensor \sigma _{\alpha \beta }, that relates the applied electric field to the expected current.

j_\alpha  = \sigma _{\alpha , \beta } E_\beta 

In the following example, we will calculate the longitudinal conductivity \sigma _{xx} and the Hall conductivity \sigma _{xy}, for the Haldane model. This model is the first and one of the most simple ones for a topological insulator.

The Haldane model consist of a honeycomb lattice, similar to graphene, with nearest neigbours hoppings. To turn it into a topological insulator we add imaginary second nearest neigbours hoppings, where the sign changes for each sublattice.

To calculate the bulk conductivity, we will select sites in the unit cell in the middle of the sample, and create a vector factory that outputs local vectors

Note that the Kubo conductivity must be normalized with the area covered by the vectors. In this case, each local vector represents a site, and covers an area of half a unit cell, while the sum covers one unit cell. It is possible to use random vectors to get an average expectation value of the conductivity over large parts of the system. In this case, the area that normalizes the result, is the area covered by the random vectors.

Advanced topics

Custom distributions of vectors

By default ~kwant.kpm.SpectralDensity will use random vectors whose components are unit complex numbers with phases drawn from a uniform distribution. The generator is accesible through ~kwant.kpm.RandomVectors.

For large systems, one will generally resort to random vectors to sample the Hilbert space of the system. There are several reasons why you may wish to make a different choice of distribution for your random vectors, for example to enforce certain symmetries or to only use real-valued vectors.

To change how the random vectors are generated, you need only specify a function that takes the dimension of the Hilbert space as a single parameter, and which returns a vector in that Hilbert space:

Aditionally, a ~kwant.kpm.LocalVectors generator is also available, that returns local vectors that correspond to the sites passed. Note that the vectors generated match the sites in order, and will be exhausted after all vectors are drawn from the factory.

Both ~kwant.kpm.RandomVectors and ~kwant.kpm.LocalVectors take the argument where, that restricts the non zero values of the vectors generated to the sites in where.

Reproducible calculations

Because KPM internally uses random vectors, running the same calculation twice will not give bit-for-bit the same result. However, similarly to the funcions in ~kwant.rmt, the random number generator can be directly manipulated by passing a value to the rng parameter of ~kwant.kpm.SpectralDensity. rng can itself be a random number generator, or it may simply be a seed to pass to the numpy random number generator (that is used internally by default).

Defining operators as sesquilinear maps

Above, we showed how ~kwant.kpm.SpectralDensity can calculate the spectral density of operators, and how we can define operators by using kwant.operator. If you need even more flexibility, ~kwant.kpm.SpectralDensity will also accept a function as its operator parameter. This function must itself take two parameters, (bra, ket) and must return either a scalar or a one-dimensional array. In order to be meaningful the function must be a sesquilinear map, i.e. antilinear in its first argument, and linear in its second argument. Below, we compare two methods for computing the local density of states, one using kwant.operator.Density, and the other using a custom function.

References

[1] (1, 2) Rev. Mod. Phys., Vol. 78, No. 1 (2006).
[2] Phys. Rev. Lett. 114, 116602 (2015).