This package provides basic functions for modeling harmonic lattice dynamics in bulk crystals as well as in slab or semi-infinite geometries.
To construct a model, a user inputs two pieces of information:
- Basic crystal information (e.g. lattice vectors, unit cell, surface miller indices, etc.)
- A parameter set which determines the interatomic force constants
With this, the dynamical matrix can be constructed so that the phonon spectrum and normal modes are readily calculated. The motivation for making the package comes from studying topological phonons so there is a mild bias toward functionality that lends itself to that field. The goal is to be able to seemlessly explore bulk and surface modes of a model with clean succinct code.
For a bulk crystal, we need
- Lattice vectors
- Information about the unit cell
- A radius which captures all interacting neighbors
Consider the following schematic example:
using LatticeDynamics
# For lattice vectors a1, a2, a3
latticevectors = [a1, a2, a3]
# For two atoms in the unit cell:
atom1info = ["Element1", frac_coords1] # "Element1" is the element symbol, e.g. "Al", "C", etc.
atom2info = ["Element2", frac_coords2] # note: positions are in fractional coordinates
unitcell = [atom1info, atom2info]
# Neighbor threshold in angstroms <- maximum extent of the short-range interactions
threshold = 5.0
mycrystal = Crystal(unitcell, latticevectors, threshold)
Now mycrystal
contains all the structural information about the crystal including reciprocal lattice information, atomic masses, and nearest neighbors for each atom.
To study phonons at the surface of a crystal, we need a Slab
. To make a slab, we use the same 3 pieces of bulk crystal information along with
- Surface miller indices
- The thickness of the slab.
Here is an example:
surface = "hkl" # This could be "110", "001", etc.
thickness = 40 # Number of bulk unit cells to stack
myslab = Slab(unitcell, latticevectors, surface, thickness, threshold)
Note that the miller indices "hkl"
must be with respect to the reciprocal lattice vectors derived from the given lattice vectors. This can lead to potentially unexpected results. Recall that a surface labeled by "hkl" means that the vector G=h*b1 + k*b2 + l*b3
is normal to the surface where b1
, b2
and b3
are reciprocal lattice vectors derived from the direct lattice vectors in the usual way. Care must be taken to ensure the miller indices "hkl"
given to Slab
match the expected reciprocal lattice vectors. One can always look at myslab.surfaceNormal
to ensure it is pointed in the proper direction.
Right now, LatticeDynamics.jl supports two types of interactions:
- Short-range radial interactions
- Long-range Coulomb interactions
The short-range forces are modeled by a radial potential. So the interaction between atoms at positions ri and rj looks like V(|ri - rj|). With this assumption, the power law fall off of the force constants is fixed and all that must be determined are the first and second derivatives evaluated at equilibrium. These two values are taken to be phenomenological parameters which we might call Aij and Bij (interpreted as radial and tangential force constants resp.). Packaging these as a tuple (Aij, Bij), we organize the short-range interaction parameters for every pair of atom types as
# For two atoms in the unit cell:
shortrange_couplings = [[ (A11, B11) , (A12, B12) ],
[ (A12, B12) , (A22, B22) ]]
So the short-range interaction parameters between atoms i and j is just obtained by shortrange_couplings[i][j]
. To translate this to the couplings for a similar slab, we use the getSlabCouplingArray
function
slabcouplings = getSlabCouplingArray(myslab, shortrange_couplings)
For the long-range Coulomb interactions, the form of the potential is of course 1/r. So we simply assign to each atom in the unit cell an effective charge Zi which is understood to be in units of the fundamental electron charge e. So we write
# Again for two atoms in the unit cell:
charges = [Z1, Z2]
@assert sum(charges) == 0.0
Note that the net charge in the unit cell must be zero.
We can now calculate the phonon spectrum. Given the high symmetry points in the Brillouin zone, we can build a path connecting them with the buildPath
function. For example, if we have high symmetry points Γ, X, W, and K, we can build the path Γ -> X -> W -> K -> Γ, but we have to specify how many points to include. We do this with a pointdensity
. We then build the path with
pointdensity = 35 # sampling rate <- how many points per angstrom should we calculate the energies
bzpath, bzpathparts = buildPath([Γ, X, W, K, Γ], pointdensity)
When studying surfaces it is often convenient to use the projectVector
function which can project a high symmetry point of the 3D BZ to one in the surface BZ
inplaneX, outofplaneX = projectVector(X, myslab.surfaceNormal)
With the Brillouin zone path defined, we can get the dispersion with getDispersion
and plot it with plotDispersion
disp = getDispersion(bzpath, mycrystal, couplings)
labels = ["Γ", "X", "W", "K", "Γ"]
plotDispersion(disp, bzpathparts, labels)
This calculates the phonon dispersion along the given path Γ -> X -> W -> K -> Γ and plots it. The bzpathparts
is just for plotting. It tells where on the plot to show the high symmetry points.
The getDispersion
function can be used to directly diagonalize a slab dynamical matrix, but it is often useful to consider an alternative method to obtaining the surface spectrum; namely the recursion-decimation Green's function algorithm (Sancho 1985). This method has the advantage that in its output, it is easier to distinguish between bulk-projected modes and surface resonances. It is called with the getSpectrum
function. Here is an example:
# construct BZ path and list of energies to calculate the LDOS for
sbzpath, sbzpathparts = buildPath(highsymmpoints, pointdensity)
energylist = range(0., stop=20., length=750)
# calculate the spectral function for the given wavevectors and energies
spec = getSpectrum(sbzpath, energylist, myslab, slabcouplings)
plotSpectrum(spec, energylist, sbzpathparts)
Note that even though a slab is given to this function, it uses information so that it effectively considers a semi-infinite crystal.
Also if there is a single energy of interest (for example the energy of a Weyl point), a plot of the isoenergy surface over some region of the surface BZ can be made with the getEnergySurface
and plotEnergySurface
functions.