planewave#
Planewave module.
- jrystal.pw.coeff(pw_param: dict | Array | Tuple, freq_mask: Bool[Array, 'x y z']) Complex[Array, 'spin kpt band x y z'] [source]#
Create the linear coefficients to combine the frequency components.
This function takes a raw parameter of shape
(num_spin, num_kpts, num_gpts, num_bands)
, orthogonalizes for the last two dimensions, so that the resulting tensor satisfies the unitary constrainteinsum('kabc,labc->kl', ret[i, j], ret[i, j]) == eye(num_bands)
.The
pw_param
should be created fromparam_init()
, and the samefreq_mask
used inparam_init()
should be used here. As mentioned inparam_init()
, we use linear combination over 3D Fourier components for creating wave functions. Some extra requirements are:The wave functions that have the same spin and same k component need to be orthogonal to each other.
We only activate some of the frequency components with the
freq_mask
.
As the raw parameter returned from
param_init()
has the shape(num_spin, num_kpts, num_g, num_bands)
, wherenum_g
is the number of activated frequencies flattened from the activated entries in thefreq_mask
, this function first orthogonalizes over the last two dimensions and reorganizes the orthogonalized parameter into a 3D grid the same shape as the frequency mask.Extension reads: 1. Why and how to mask the frequency components. 2. Bloch theorem.
- Parameters:
pw_param (Union[Array, Tuple]) – The raw parameter, maybe created from
param_init()
.freq_mask (Bool[Array, 'x y z']) – A 3D mask to select the frequency components.
- Returns:
Complex array of shape
(num_spin, num_kpts, num_band, x y z)
. It satisfies the unitary constraint that for anyi,j
,einsum('kabc,labc->kl', ret[i, j], ret[i, j])
is an identity matrix.- Return type:
Complex[Array, ‘spin kpt band x y z’]
- jrystal.pw.density_grid(coeff: Complex[Array, 'spin kpt band x y z'], vol: Float, occupation: Float[Array, 'spin kpt band'] | None = None) Float[Array, 'spin kpt band x y z'] | Float[Array, 'spin kpt band'] [source]#
Compute the density at the spatial grid.
In a system with electrons, the density of the electron is the result of multiple wave functions overlapping in space. As mentioned in
wave_grid()
, the wave function is a linear combination of 3D Fourier components. To compute the density, usually we only need to take the absolute square of each wave function and sum them up.\[\rho(r) = \sum_i |\psi_i(r)|^2\]This function evaluates the density \(\rho(r)\) at the spatial grid generated from
jrystal.grid.r_vectors()
.In crystals, this is a little bit more complicated. The form of the wave function is:
\[\psi(r) = \frac{1}{\sqrt{\Omega_\text{cell}}} e^{ikr} \sum_G c_{kG} e^{iGr}\]The \(c_{kG}\) can be computed from
param_init()
andcoeff()
. For calculation of density, we only need the \(c_{kG}\) and the occupation \(o_k\) over \(k\).\[\rho(r) = \frac{1}{\Omega_\text{cell}} e^{ikr} \sum_G c_{kG} e^{iGr}\]- Parameters:
coeff – \(c_{kG}\) part of the parameter. It can have a leading batch dimension which will be summed to get the overall density. Therefore the shape is
(spin, kpt, band, x, y, z)
.vol – Volume of the unit cell, a real scalar.
occupation – The occupation over different k frequencies. The shape is
(spin, kpt, band)
, it should have the same leading dimension ascoeff
. This is an optional argument. Whenoccupation=None
, we compute the density contribution from each \(k\) without summing them. Ifoccupation
is provided, we sum up all the density from each \(k\) weighted by the occupation.
- Returns:
A real-valued tensor that represents the density at the spatial grid computed from
jrystal.grid.r_vectors()
. The shape is(x, y, z)
ifoccupation
is provided, else the shape is(spin, kpt, band, x, y, z)
.- Return type:
Union[Float[Array, “spin kpt band x y z”], Float[Array, “spin kpt band”]]
- jrystal.pw.density_grid_reciprocal(coeff: Complex[Array, 'spin kpt band x y z'], vol: float | Array, occupation: Float[Array, 'spin kpt band'] | None = None) Complex[Array, 'spin kpt band x y z'] | Complex[Array, 'spin kpt band'] [source]#
Fourier transform of the density grid.
In a system with electrons, the density of the electron is the result of multiple wave functions overlapping in the space. As we mention in
wave_grid()
, the wave function is a linear combination of 3D fourier components. To compute the density, usually we only need to take the absolute square of each wave function \(\psi_i\), multiply by the occupation \(f_i\) and sum them up:\[\rho(\vb{r}) = \sum_i f_i |\psi_i(\vb{r})|^2\]density_grid()
computes the density \(\rho(\vb{r})\) at the spatial grid generated fromjrystal.grid.r_vectors()
.The discrete fourier transformation of \(\rho(\vb{r})\) is
\[\tilde{\rho}(\vb{G}) = \frac{1}{\Omega} \int_\Omega \rho(\vb{r}) e^{-\text{i} \vb{G}^\top \vb{r}} \dd{\vb{r}}\]We can also evaluate the \(\tilde{\rho}(G)\) at any \(G\), but this function computes the \(\tilde{\rho}(G)\) evaluated at the grid generated from
jrystal.grid.g_vectors()
. It is equivalent to computing the \(\rho(\vb{r})\) atjrystal.grid.r_vectors()
and then do the discrete fourier transform.Since this function is just composing FFT with
density_grid()
, we refer you todensity_grid()
for more details.- Parameters:
coeff – coefficients of the wave functions.
vol – volume of the unit cell.
occupation – occupation over the different \(k\) components, see more in
density_grid()
.
- Returns:
A complex valued tensor representing \(\tilde{\rho}(G)\) evaluated at \(G\) generated from
jrystal.grid.g_vectors()
.
- jrystal.pw.param_init(key: Array, num_bands: int, num_kpts: int, freq_mask: Bool[Array, 'x y z'], spin_restricted: bool = True) dict [source]#
Initialize the raw parameters.
This function generates a random tensor of shape
(num_spin, num_kpts, num_g, num_bands)
, wherenum_g
is the number ofTrue
items in thefreq_mask
.In planewave-based calculation, a wave function is represented as a linear combination of the Fourier series in 3D. Therefore, to create one wave function we need a 3D shaped tensor to represent the mixing coefficients on each frequency component (denoted as
G
).freq_mask
provides a 3D mask to decide which frequency components are selected, the number of selected components is denoted asnum_g
.The
num_bands
&num_kpts
are a bit hard to explain. Intuitively, the wave functions consist of high frequency components that have a period smaller than the unit cell (denoted \(G\)) and components that have a period larger than the unit cell (denoted \(k\)).The form of wave function under solid state is:
\[\psi(r) = e^{ikr}\sum_G c_{kG} e^{iGr}\]This function generates a raw parameter, which after processing by
coeff()
can be used as the \(c_{kG}\) part of the above equation.Extension reads: 1. Why and how to mask the frequency components. 2. Bloch theorem.
As far as this function is concerned, it simply returns a randomly initialized parameter of shape
(num_spin, num_kpts, num_g, num_bands)
. The input arguments to this function are only used to determine the shape.Note that this function returns the raw parameter that cannot be used directly to weight the frequency components, as in quantum chemistry we require the wave functions to be orthogonal to each other. Check
coeff()
for converting the raw parameter into a unitary tensor.- Parameters:
- Returns:
A complex type raw parameter of shape
(num_spin, num_kpts, num_g, num_bands)
.- Return type:
Complex[Array, ‘spin kpt gpt band’]
- jrystal.pw.wave_grid(coeff: Complex[Array, 'spin kpt band x y z'], vol: Float) Complex[Array, 'spin kpt band x y z'] [source]#
Wave function evaluated at a grid of spatial locations.
This function implements the \(u(r)\) part of the Bloch wave function:
\[u(r)=\frac{1}{\sqrt{\Omega_\text{cell}}} \sum_G c_{G} e^{iG^\top r}\]where \(G\) is the 3D frequency components, \(\Omega_\text{cell}\) is the volume of the crystal unit cell, which is to make sure the wave function is normalized within the cell.
where \(c\) is the linear coefficient. It combines over different \(G\) components that is generated with
jrystal.grid.g_vectors()
. We can evaluate the wave function at any spatial location \(r\) which takes \(O(|G|)\) computation. However, if we evaluate this function on a specific spatial grid of size \(|G|\), we can be faster than \(O(|G|^2)\) by using fourier transform. IFFT gives us an \(O(|G|\log(|G|))\) implementation of the above equation. The \(G\) and \(R\) grid can be obtained fromjrystal.grid.g_vectors()
andjrystal.grid.r_vectors()
correspondingly.G = jrystal.grid.g_vectors(*args) # (x y z, 3) R = jrystal.grid.r_vectors(*args) # (x y z, 3) coefficients = ... # (x y z) vol = ... def wave_function(r): return (coefficients * jnp.exp(1j * G @ r)).sum() / jnp.sqrt(vol) # The following is O(|G|^2) wave_at_R_naive = jax.vmap(jax.vmap(jax.vmap(wave_function)))(R) # The following is O(|G|log|G|) wave_at_R_fft = wave_grid(coefficients, vol)
As IFFT implements
\[x_n = \frac{1}{N} \sum_{k=0}^{N-1} X_k e^{i\frac{2\pi}{N}kn}\]It is a bit different from the definition of the wave function, if you check the code, we do two things to align them,
1. we multiply back the \(N\) to cancel the \(\frac{1}{N}\) factor in the IFFT (in 3D the
np.prod(grid_sizes)
). 2. we divide by the \(\sqrt{\Omega_\text{cell}}\).The
coeff
passed to this function has shape(..., x y z)
, it can have any leading dimension. It can be created usingparam_init()
andcoeff()
.param_init()
creates a raw parameter andcoeff()
converts that parameter into coefficients that are used to linearly weight the 3D fourier components.- Parameters:
coeff (Complex[Array, 'spin kpt band x y z']) – Wave function coefficients, which has a shape of
(spin, kpt, band, x, y, z)
.vol (float) – Volume of the unit cell.
- Returns:
Wave function evaluated at the spatial grid.
- Return type:
Complex[Array, ‘spin kpt band x y z’]
- jrystal.pw.wave_r(r: Float[Array, '3'], coeff: Complex[Array, 'spin kpt band x y z'], cell_vectors: Float[Array, '3 3'], g_vector_grid: Float[Array, 'x y z 3'] | None = None) Complex[Array, 'spin kpt band'] [source]#
Evaluate plane wave functions at location r.
This function computes the \(\psi(r)\) following the equation:
\[\psi(r) = \frac{1}{\sqrt{\Omega_\text{cell}}} \sum_G c_{G} e^{iGr}\]The
coeff
provided is the \(c_{G}\), thecell_vectors
is used to generate the grid of frequency components \(G\) by calling the functionjrystal.grid.g_vectors()
. Ther
is the location where we evaluate the wave function.- Parameters:
r – Spatial location to evaluate the wave function, shape: (3,).
coeff – Wave function coefficients, which has a shape of
(spin, kpt, band, x, y, z)
. It can be created fromparam_init()
followed bycoeff()
.cell_vectors – The cell vectors of the crystal unit cell.
g_vector_grid – The G vectors computed from
jrystal.grid.g_vectors()
. If None, will be computed from cell_vectors.
- Returns:
Complex tensor that represents wave functions evaluated at location r.
- Return type:
Complex[Array, ‘spin kpt band’]