Plasmons.jl

Installing

Plasmons.jl can be used either as a Julia package or as a standalone executable. The latter option is provided for people who do not care about Julia and just want to do physics. If you are one of them just download Plasmons.sif executable file from the Releases page and you are good to go.

Otherwise, install Plasmons.jl using Pkg:

import Pkg; Pkg.add(url="https://github.com/twesterhout/Plasmons.jl")

Using the executable

After downloading the container (Plasmons.sif file) from the Releases page one can simply run it. It follows the UNIX philosophy and tries to do one thing and do it well.

The one thing is calculating polarizability $\chi$ (or dielectric function $\varepsilon$). It can be described by the following two functions:

\[\begin{aligned} \left(H, \omega, \mu, T\right) &\mapsto \chi \\ \left(\chi, V\right) &\mapsto \varepsilon \end{aligned}\]

This means that if you provide a Hamiltonian $H$ and frequency $\omega$ (and some information about the environment, namely chemical potential $\mu$ and temperature $T$), Plasmons.sif will compute $\chi(\omega)$ for your system. If you additionally provide unscreened Coulomb interaction $V$, Plasmons.sif will compute $varepsilon(\omega)$ as well.

Algorithm

Within real-space RPA (Random Phase Approximation) the calculation of polarizability matrix $\chi(\omega)$ amounts to evaluation of the following equation

\[\begin{aligned} \chi_{a,b} &= \langle a |\hat\chi| b \rangle = 2\cdot \sum_{i,j} \langle i |\hat G| j \rangle \langle j | a \rangle \langle a | i \rangle \langle i | b \rangle \langle b | j \rangle\; , \\ G_{i,j} &= \langle i | \hat G | j \rangle = \frac{f_i - f_j}{E_i - E_j - (\hbar\omega + i\eta)} \;. \end{aligned}\]

Here $E_i$s are energy eigenvalues, $\langle a | i \rangle = \psi_i (a)$ are energy eigenstates, $f_i = f(E_i)$ is the occupation of the $i$'th state given by the Fermi-Dirac distribution, $\eta$ is Landau damping, and $\omega$ is the frequency.

First of all, for a given temperature $T$ and chemical potential $\mu$, we compute occupation numbers $f(E_i)$.

Plasmons.fermidiracFunction
fermidirac(E; mu, kT) -> f

Return Fermi-Dirac distribution $f$ at energy E, chemical potential mu, and temperature kT. Note that kT is assumed to be temperature multiplied by the Boltzmann constant, i.e. physical dimension of kT is the same as E (e.g. electron-volts).

source

Next, we compute the matrix $G$. This is done by either Plasmons._g or Plasmons._g_blocks functions.

Plasmons._gFunction
_g(ħω, E; mu, kT) -> G
Warning

This is an internal function!

Compute matrix G

\[ G_{ij} = \frac{f(E_i) - f(E_j)}{E_i - E_j - \hbar\omega}\]

where $f$ is Fermi-Dirac distribution at chemical potential mu ($\mu$) and temperature kT ($k_B T$). E is a vector of eigenenergies. ħω is a complex frequency including Landau damping (i.e. $\hbar\omega + i\eta$). All arguments are assumed to be in energy units.

Sometimes one can further exploit the structure of $G$. For $E \ll \mu$ or $E \gg \mu$ Fermi-Dirac distribution is just a constant and $G$ goes to 0 for all $\omega$. Plasmons._g_blocks uses this fact to construct a block-sparse version of $G$. The reason why such a block-sparse version is useful will become apparent later.

source
Plasmons._g_blocksFunction
_g_blocks(ħω, E; mu, kT) -> (Gᵣ, Gᵢ)
Warning

This is an internal function!

Calculate matrix $G$ given a vector of eigenenergies E, chemical potential mu, and temperature kT. See _g for the definition of $G$.

Compared to _g this function applies to tricks:

  • G is split into real and imaginary parts Gᵣ and Gᵢ.
  • We exploit the "block-sparse" structure of G (see Plasmons._ThreeBlockMatrix).
source

Let us now turn to the computation of polarizability matrix $\chi$. The naive approach is to write 4 nested loops. However, this is tremendously slow! A slightly better approach which I used for my Bachelor thesis is to rewrite the computation of each $\chi_{a, b}$ as a combination of GEMV and DOT operations:

\[\begin{aligned} \chi_{a, b} &= \sum_{i, j} \left(\langle a | i \rangle \langle i | b \rangle\right) G_{i, j} \left(\langle j | a \rangle \langle b | j \rangle\right) = A(a,b)^\dagger G A(a,b) \;, \\ &\text{where } A(a,b)_j = \langle j | a \rangle \langle b | j \rangle \;. \end{aligned}\]

We use the following data structure to hold $A$ as well as the temporary $G A$.

Plasmons._WorkspaceType
_Workspace{<: AbstractArray}
Warning

This is an internal data structure!

A workspace which is used by polarizability function to avoid allocating many temporary arrays. Stores two attributes:

  • A vector A which is defined by $A_j = \langle j | a \rangle \langle b | j \rangle$.
  • A vector temp which is the product $G A$.
source

TODO: Finish this.