# k-space paths

## Default paths

To generate a k-path for, say, the space group of diamond (space group 227; a cubic face-centered Bravais lattice), we can call irrfbz_path, which will return a minimal path in the irreducible Brillouin zone:

using Brillouin
sgnum = 227
Rs = [[1,0,0], [0,1,0], [0,0,1]] # conventional direct basis
kp = irrfbz_path(sgnum, Rs)
KPath{3} (6 points, 2 paths, 8 points in paths):
points: :U => [0.625, 0.25, 0.625]
:W => [0.5, 0.25, 0.75]
:K => [0.375, 0.375, 0.75]
:Γ => [0.0, 0.0, 0.0]
:L => [0.5, 0.5, 0.5]
:X => [0.5, 0.0, 0.5]
paths: [:Γ, :X, :U]
[:K, :Γ, :L, :W, :X]
basis: [-6.283185, 6.283185, 6.283185]
[6.283185, -6.283185, 6.283185]
[6.283185, 6.283185, -6.283185]

The path data is sourced from the HPKOT paper (or, equivalently, the SeeK-path Python package).

The coordinates of the path are given with respect to the primitive reciprocal basis (here, [[-2π,2π,2π], [2π,-2π,2π], [2π,2π,-2π]]). To convert to a Cartesian basis, we can use cartesianize or cartesianize! (in-place):

cartesianize(kp)
KPath{3} (6 points, 2 paths, 8 points in paths):
points: :U => [1.570796, 6.283185, 1.570796]
:W => [3.141593, 6.283185, 0.0]
:K => [4.712389, 4.712389, 0.0]
:Γ => [0.0, 0.0, 0.0]
:L => [3.141593, 3.141593, 3.141593]
:X => [0.0, 6.283185, 0.0]
paths: [:Γ, :X, :U]
[:K, :Γ, :L, :W, :X]
basis: [-6.283185, 6.283185, 6.283185]
[6.283185, -6.283185, 6.283185]
[6.283185, 6.283185, -6.283185]

We can visualize the k-path using PlotlyJS.jl (conversion to a Cartesian basis for plotting is automatic):

using PlotlyJS
Pᵏ = plot(kp)

Usually, it'll be more helpful to understand the path's geometry in the context of the associated Brillouin zone. To visualize this, we can plot the combination of a Cell (created via wignerseitz) and a KPath:

pGs = basis(kp)      # primitive reciprocal basis associated with k-path
c = wignerseitz(pGs) # associated Brillouin zone
Pᶜ⁺ᵏ = plot(c, kp)

## Interpolation

Interpolation of a KPath structure can be achieved using either interpolate(::KPath, ::Integer) or splice(::KPath, ::Integer), returning a KPathInterpolant. As an example, interpolate(kp, N) returns an interpolation with a target of N interpolation points, distributed as equidistantly as possible (with the distance metric evaluated in Cartesian space):

kpi = interpolate(kp, 100)
99-element KPathInterpolant{3}:
[0.0, 0.0, 0.0]
[0.022727272727272728, 0.0, 0.022727272727272728]
[0.045454545454545456, 0.0, 0.045454545454545456]
[0.06818181818181818, 0.0, 0.06818181818181818]
[0.09090909090909091, 0.0, 0.09090909090909091]
[0.11363636363636363, 0.0, 0.11363636363636363]
[0.13636363636363635, 0.0, 0.13636363636363635]
[0.1590909090909091, 0.0, 0.1590909090909091]
[0.18181818181818182, 0.0, 0.18181818181818182]
[0.20454545454545453, 0.0, 0.20454545454545453]
⋮
[0.5, 0.18181818181818182, 0.6818181818181817]
[0.5, 0.1590909090909091, 0.6590909090909091]
[0.5, 0.13636363636363635, 0.6363636363636364]
[0.5, 0.11363636363636365, 0.6136363636363636]
[0.5, 0.09090909090909093, 0.5909090909090908]
[0.5, 0.06818181818181818, 0.5681818181818181]
[0.5, 0.045454545454545456, 0.5454545454545454]
[0.5, 0.022727272727272735, 0.5227272727272727]
[0.5, 0.0, 0.5]

The returned KPathInterpolant implements the AbstractVector interface, with iterants returning SVector{D, Float64} elements. To get a conventional "flat" vector, we can simply call collect(kpi).

Internally, KPathInterpolant includes additional structure and information: namely, the high-symmetry points and associated labels along the path as well as a partitioning into connected vs. disconnected path segments.

## Band structure

The additional structure of KPathInterpolation enables convenient and clear visualizations of band structure diagrams in combination with PlotlyJS.

To illustrate this, suppose we are considering a tight-binding problem for an s-orbital situated at the 1a Wyckoff position. Such a problem has a single band with dispersion  (assuming a cubic side length $a = 1$):

$$$\epsilon(\mathbf{k}) = 4\gamma\Bigl( \cos \tfrac{1}{2}k_x \cos \tfrac{1}{2}k_y + \cos \tfrac{1}{2}k_y \cos \tfrac{1}{2}k_z + \cos \tfrac{1}{2}k_z \cos \tfrac{1}{2}k_x \Bigr)$$$

with $k_{x,y,z}$ denoting coordinates in a Cartesian basis (which are related to the coordinates $k_{1,2,3}$ in a primitive reciprocal basis by $k_x = 2π(-k_1+k_2+k_3)$, $k_x = 2π(k_1-k_2+k_3)$, and $k_z = 2π(k_1+k_2-k_3)$).

We can calculate the energy band along our k-path using the interpolation object kpi. To do so, we define a function that implements $\epsilon(\mathbf{k})$ and broadcast it over the elements of kpi:

function ϵ(k; γ::Real=1.0)
kx = 2π*(-k+k+k)
ky = 2π*(+k-k+k)
kz = 2π*(+k+k-k)
return 4γ * (cos(kx/2)*cos(ky/2) + cos(ky/2)*cos(kz/2) + cos(kz/2)*cos(kx/2))
end
band = ϵ.(kpi)
99-element Vector{Float64}:
12.0
11.918571535047462
11.67594378891598
11.277055962836148
10.73002826264945
10.045996594834067
9.238885871562282
8.325126539644781
7.3233201040150915
6.253860454731438
⋮
-4.0
-3.9999999999999996
-4.0
-3.9999999999999996
-4.0
-4.0
-4.0
-4.0
-4.0

Finally, we can visualize the associated band using a Brillouin-overloaded PlotlyJS plot-call:

P = plot(kpi, [band])

If we have multiple bands, say $\epsilon_1(\mathbf{k}) = \epsilon(\mathbf{k})$ and $\epsilon_2(\mathbf{k}) = 20 - \tfrac{1}{2}\epsilon(\mathbf{k})$, we can easily plot that by collecting the bands in a single vector (or concatenating into a matrix):

band1 = ϵ.(kpi)
band2 = 20 .- (1/2).*band1
P¹² = plot(kpi, [band1, band2])
PlotlyJS.plotMethod
plot(kpi::KPathInterpolant, bands, [layout]; kwargs...)

Plot a dispersion diagram for provided bands and k-path interpolant kpi.

bands must be an iterable of iterables of <:Reals (e.g., a Vector{Vector{Float64}}), with the first iteration running over distinct energy bands, and the second running over distinct k-points in kpi. Note that the length of each iterant of bands must equal length(kpi).

Alternatively, bands can be an AbstractMatrix{<:Real}, with columns interpreted as distinct energy bands and rows as distinct k-points.

A layout can be supplied to overwrite default layout choices (set by Brillouin.DEFAULT_PLOTLY_LAYOUT_DISPERSION). Alternatively, some simple settings can be set directly via keyword arguments (see below).

Keyword arguments kwargs

• ylims: y-axis limits (default: quasi-tight around bands's range)

• ylabel: y-axis label (default: "Energy")

• title: plot title (default: nothing); can be a String or an attr dictionary of PlotlyJS properties

• band_highlights: dictionary of non-default styling for specified band ranges (default: nothing, indicating all-default styling).

Example: To color bands 2 and 3 black, set band_highlights = Dict(2:3 => attr(color=:black, width=3)). Unlisted band ranges are shown in default band styling.

• annotations: dictionary of hover-text annotations for labeled high-symmetry points in kpi (default: nothing, indicating no annotations). Suitable for labeling of irreps.

Example: Assume bands 1 and 2 touch at X, but not at Γ. To label this, we set: annotations = Dict(:X => [1:2 => "touching!"], :Γ => [1 => "isolated", 2 => "isolated"]). If a band-range is provided, a single annotation is placed at the mean of the energies at these band-ranges. Alternatively, if the first element of each pair is a non-Integer Real number, it is interpreted as referring to the frequency of the annotation.

source

## Treating unit cells in non-standard settings

irrfbz_path(sgnum, Rs) requires Rs to be provided in a standard setting. Often, the setting of Rs may not be standard and it can be a hassle to convert existing calculations to such a setting. To avoid this, we can provide the unit cell information to irrfbz via Spglib's Cell format (this functionality depends on separately loading the Spglib.jl package). If the provided unit cell is a supercell of a smaller primitive cell, irrfbz_path returns the standard k-path of the smaller primitive cell in the basis of the supercell reciprocal lattice vectors.

For example, to construct a k-path for a non-standard trigonal lattice in space group 166 (R-3m):

using Spglib
a = 1.0
c = 8.0
pRs_standard    = [[a*√3/2,  a/2, c/3],
[-a*√3/2, a/2, c/3],
[0, -a, c/3]]
pRs_nonstandard = [[a*√3/2, -a/2, c/3],
[0, a, c/3],
[-a*√3/2, -a/2, c/3]]

cell_standard = Spglib.Cell(pRs_standard, [[0, 0, 0]], )
cell_nonstandard = Spglib.Cell(pRs_nonstandard, [[0, 0, 0]], )

kp_standard = irrfbz_path(cell_standard)
kp_nonstandard = irrfbz_path(cell_nonstandard)
KPath{3} (8 points, 3 paths, 10 points in paths):
points: :T => [0.5, 0.5, 0.5]
:F => [0.5, -0.0, 0.5]
:S₀ => [0.345052, -0.345052, 0.0]
:S₂ => [0.654948, 0.0, 0.345052]
:H₀ => [0.5, -0.190104, 0.190104]
:Γ => [0.0, 0.0, 0.0]
:L => [0.5, -0.0, 0.0]
:H₂ => [0.809896, 0.190104, 0.5]
paths: [:Γ, :T, :H₂]
[:H₀, :L, :Γ, :S₀]
[:S₂, :F, :Γ]
basis: [3.627599, -2.094395, 0.785398]
[0.0, 4.18879, 0.785398]
[-3.627599, -2.094395, 0.785398]

Note that the space group symmetry is inferred by Spglib from the atomic positions and provided basis.

We can check that the generated k-paths for the standard and non-standard lattices are equivalent by plotting and comparing their k-paths and associated Wigner-Seitz cells:

using Bravais # for reciprocalbasis
Pˢ  = plot(wignerseitz(reciprocalbasis(pRs_standard)), kp_standard, Layout(title="standard cell"))
Pⁿˢ = plot(wignerseitz(reciprocalbasis(pRs_nonstandard)), kp_nonstandard, Layout(title="non-standard cell"))