Correction schemes for electrostatic finite-size effects¶
Contents
Theoretical introduction¶
The main artifact of the supercell approach for point-defect calculations consists in the introduction of periodic images of the defect located in the simulation cell. Such periodically-repeated array of defects corresponds to very high defect concentrations for commonly used supercell types.
In such case, defect-defect interactions are large and can considerably affect the predicted energy of the point defect.
Among the kinds of defect-defect interactions, electrostatic ones are never negligible for any practical supercell size.
The study of point defects in the dilute limit then requires some scheme to correct for such spurious electrostatic interactions.
The introduction of a point defect will induce a redistribution of the host material electronic charge density. In the ideal case of an isolated point defect, we call this defect-induced charge density \(\rho_{iso}\). On the other hand, in the supercell method, due to the application of periodic boundary conditions, the defect-induced charge density will be different: \(\rho_{per}\). Moreover, for charged point defects, periodic boundary conditions require the introduction of a neutralizing background, usually taken as an homogeneous jellium of density \(-\frac{q}{V}\), where \(q\) is the defect charge state and \(V\) is the supercell volume.
The defect-induced electrostatic potential can be obtained from the induced charge density by solving Poisson equation with the proper boundary conditions. This will yield the potentials \(\phi_{iso}\) and \(\phi_{per}\) for the \(\rho_{iso}\) and \(\rho_{per}\), respectively:
Correction schemes for electrostatic finite-size effects assume that the point defect induces a charge density localized in the supercell. In this case the charge densities induced by periodic and isolated defect can be considered to be the same within the supercell: \(\rho_{iso} = \rho_{per} = \rho\) in \(V\). In addition:
Considering the defect-induced electrostatic energy per supercell, what it is obtained employing periodic boundary conditions is the quantity:
while ideally we would like to obtain:
Correction schemes for electrostatic finite-size effects aim to calculate the corrective term:
Spinney implements two state-of-the-art correction schemes:
The scheme proposed by Freysoldt, Neugebauer and Van de Walle [FNVdW09].
The scheme proposed by Kumagai and Oba [KO14].
The latter is an improvement over the former. In both cases, the correction energy of equation (3) is expressed as:
\(E_{lat}\) takes into account the interaction of \(\rho\) with the host material and the jellium background in periodic boundary conditions, while \(\Delta \phi\) is an alignment term.
\(\rho\) is modelled with a spherically-symmetric charge distribution. In the scheme of Freysoldt, Neugebauer and Van de Walle this is generally taken as a linear combination of a Gaussian function and an exponential one:
where \(N_1\) and \(N_2\) are normalization constants. \(\gamma\) and \(\beta\) are the parameters describing the exponential and Gaussian functions, respectively. In the scheme of Kumagai and Oba a point-charge model is used instead. This allows to easily generalize the scheme to anisotropic materials.
Regarding \(\Delta \phi\), this is obtained from comparing the electrostatic potential of the defective and pristine systems with the one generated by the model \(\rho\) in a region of the crystal far from the defect. The scheme of Freysoldt, Neugebauer and Van de Walle uses the plane-averaged potential in order to compute the alignment term; while the scheme of Kumagai and Oba uses atomic-site potentials. The latter have been show to converge better far from the defect, in particularly when lattice relaxations are important [KO14].
Implementation in Spinney¶
The PointDefect
class can calculate the energy for correcting
electrostatic finite-size effects using both the scheme of Freysoldt, Neugebauer and Van de Walle and
the one of Kumagai and Oba.
Each scheme needs its own set of data in order to compute \(E_{corr}\), this have to fed to a
PointDefect
instance using the method add_correction_scheme_data()
.
This method accepts correction-scheme-specific keywords arguments, which we will explain now.
Warning
each time add_correction_scheme_data()
is called, all the keywords which have not been explicitly given by the user will take
default values. This means that every time the method is called, the PointDefect
object will overwrite the data passed with a former call of add_correction_scheme_data
.
The scheme of Freysoldt, Neugebauer and Van de Walle¶
The keywords arguments for add_correction_scheme_data()
are:
Mandatory arguments:
potential_pristine
: a 3D array containing the electrostatic potential calculated by first-principles on a 3D grid for the pristine supercell.
potential_defective
: a 3D array containing the electrostatic potential calculated by first-principles on a 3D grid for the defective supercell.
axis
: an integer with value0
,1
or2
which specifies the cell vector along which the plane-averaged electrostatic potential will be calculated.Optional arguments:
defect_density
: a 3D array containing a model defect-induced charge density calculated on a 3D grid. For example,defect_density
can be obtained by projecting the electronic charge density onto the defect-induced band.If this argument is present, Spinney will fit the model charge density of equation (4) to
defect_density
.
gamma
: a float. The parameter of the exponential function \(\gamma\) in equation (4). The default value is 1.
beta
: a float. The parameter of the Gaussian function \(\beta\) in equation (4). The default value is 1.
x_comb
: a float between 0 and 1. Weight of the exponential function with respect to the Gaussian function in modeling the defect-induced charge density. Defaultx_comb = 0
. Forx_comb = 0
the charge density is modelled by a pure Gaussian; forx_comb = 1
by a pure exponential function.
e_tol
: a float, break condition for the iterative calculation of the correction energy. Value in Hartree. Default: 1e-6 Ha.
shift_tol
: a float representing the tolerance to be used to locate the defect position alongaxis
. The default value is: \(1e-5 \times a\), where \(a\) is the length of the cell parameter definingaxis
.
Note
Most of the times the default parameters will be enough.
You might need to increase shift_tol
considerarbly if the 3D used to
calculate the electrostatic potential is too coarse.
Example using VASP¶
To illustrate in detail how the correction scheme of Freysoldt, Neugebauer and Van de Walle is implemented in Spinney, we will take the example of a Ga vacancy in the charge state -3 in cubic GaAs, modeled using a \(3 \times 3 \times 3\) supercell of the conventional cubic cell. This system is also studied in the authors’ original paper [FNVdW09].
The electrostatic potential on a 3D grid can be obtained in VASP
by adding to the INCAR
file the following lines (example for VASP 5.2.12, compare with the
documentation for you version):
PREC = High
LVHAR = .TRUE.
NGXF = 200
NGYF = 200
NGZF = 200
These options will write the LOCPOT
file on a fine grid (the grid can be considerably less dense,
depending on the system).
The data for the potential_pristine
and potential_defective
keyword arguments can be obtained
easily using the class VaspChargeDensity
.
from ase.calculators.vasp import VaspChargeDensity
locpot = VaspChargeDensity('LOCPOT')
potential_pristine = -1*locpot.chg[-1]*locpot.get_volume()
We can now calculate the defect formation energy. To do so we assume that the working directory contains the following files:
OUTCAR_prist
andOUTCAR_def
:OUTCAR
files for pristine and defective supercell, respectively.
LOCPOT_prist
andLOCPOT_def
:LOCPOT
files for pristine and defective supercell, respectively.
OUTCAR_Ga
:OUTCAR
file for the reference state of Ga.
import numpy as np
from ase.calculators.vasp import VaspChargeDensity
import ase.io
from spinney.structures.pointdefect import PointDefect
from spinney.defects import fnv
# pristine system
pristine = ase.io.read('OUTCAR_prist')
# Specifications of the defective system: V_Ga -3 in GaAs
q = -3 # charge state
dielectric_constant = 12.4 # from Freysoldt et al. PRL (2009) paper
def_position = np.array([0.33333333333, 0.5, 0.5]) # fractional coordinates defect
axis_average = 2 # axis along where the average potential is calculated
vbm = 4.0513 # valence band maximum
# prepare a numpy 3D array with information about the electrostatic potential
locpot_prist = VaspChargeDensity('LOCPOT_prist') # read using ase
supercell = locpot_prist.atoms[-1]
locpot_def = VaspChargeDensity('LOCPOT_def')
locpot_arr_p = locpot_prist.chg[-1]*supercell.get_volume()*(-1)
locpot_arr_def = locpot_def.chg[-1]*supercell.get_volume()*(-1)
del locpot_prist; del locpot_def
# Prepare the chemical potentials Ga-rich conditions
gallium = ase.io.read('OUTCAR_Ga')
mu_ga = gallium.get_total_energy()/gallium.get_number_of_atoms()
# initialite PointDefect instance
defective = ase.io.read('OUTCAR_def')
pd = PointDefect(ase.io.read(defect))
pd.set_dielectric_tensor(dielectric_constant)
pd.set_defect_position(def_position)
pd.set_defect_charge(q)
pd.set_pristine_system(pristine)
pd.set_vbm(vbm)
# correction scheme of Freysoldt, Neugebauer and Van de Walle
pd.set_finite_size_correction_scheme('fnv')
pd.add_correction_scheme_data(potential_pristine=locpot_arr_p,
potential_defective=locpot_arr_def,
axis=axis_average)
pd.set_chemical_potential_values({'Ga':mu_ga, 'As':None}, force=True)
print('Formation energy Ga-rich conditions, not corrected: ',
pd.get_defect_formation_energy())
print('Formation energy Ga-rich conditions, corrected: ',
pd.get_defect_formation_energy(True))
Example using WIEN2k¶
To illustrate in detail how the correction scheme of Freysoldt, Neugebauer and Van de Walle is implemented in Spinney, we will take the example of a Ga vacancy in the charge state -3 in cubic GaAs, modeled using a \(3 \times 3 \times 3\) supercell of the conventional cubic cell. This system is also studied in the authors’ original paper [FNVdW09].
Obtaining more information on the finite-size corrections¶
More information related to the correction scheme can be accessed through the method
calculate_finite_size_correction()
with the keyword argument verbose=True
.
This returns a tuple, whose first element is the correction energy for finite-size effects and the second element is a dictionary.
The python interface is independent on the employed first-principles code.
We only assume that a PointDefect
instance has been initialized, as shown above.
Such object is assumed to have been stored in the variable pd
.
ecorr, dd = pd.calculate_finite_size_correction(verbose=True)
# 1D grid along which the average potential has been calculated
axis_grid = dd['Potential grid']
# calculated long-range potential
lr_potential = dd['Model potential']
# calculated short-range potential
sr_potential = dd['Alignment potential']
# DFT potential defective system - pristine system
dft_potential = dd['DFT potential']
# Potential alignment part of the correction energy
alignment_term = dd['Alignment term']
# utility class to plot the electrostatic potential between the defect and
# its image
Plott = fnv.FPlotterPot(axis_grid, dft_potential, lr_potential, sr_potential,
-alignment_term, 'z')
# this will save a pdf file called plot_VGa_-3_GaAs.pdf
Plott.plot('VGa_-3_GaAs')
The result of this last block of code is the following picture:
The scheme of Kumagai and Oba¶
The keywords arguments for add_correction_scheme_data()
are:
Mandatory arguments:
potential_pristine
: a 1D array of length equal to the number of atoms in the pristine system. The array elements store the electrostatic potential calculated by first-principles at the atomic sites in the pristine supercell.
potential_defective
: a 1D array of length equal to the number of atoms in the defective system. The array elements store the electrostatic potential calculated by first-principles at the atomic sites in the defective supercell.Optional arguments:
distance_tol
: a float or an array with 3 elements. The rounding tolerance for comparing distances. If a float is insert as input, it will be converted to an array of 3 elements. Each element is the tolerance value in percentage of the length of the corresponding cell parameter. The default value is 1% for each cell parameter.
e_tol
: a float. The condition for breaking the loop in the iterative calculation of the correction energy. Value in eV. The default value is 1e-6 eV.
Example using VASP¶
To illustrate in detail how the correction scheme of Kumagai and Oba is implemented in Spinney, we will take the example of a B vacancy in the charge state -3 in cubic BN, modeled using a \(3 \times 3 \times 3\) supercell of the conventional cubic cell. This system is also studied in the authors’ original paper [KO14].
All the data needed by the Kumagai’s and Oba’s scheme are in the OUTCAR
file.
The following block of code assumes that the working directory contains the files:
OUTCAR_prist
,OUTCAR_def
: theOUTCAR
files of the pristine and defective supercells, respectively.OUTCAR_B
: theOUTCAR
file of the reference state for Boron.
To calculate the defect formation energy with the correction scheme of Kumagai and Oba, you can use this snippet:
import numpy as np
import ase.io
from spinney.io.vasp import extract_potential_at_core_vasp
from spinney.structures.pointdefect import PointDefect
# pristine system
pristine = ase.io.read('OUTCAR_prist')
# Specifications of the defective system: V_B -3 in BN
q = -3 # charge state
e_r = (4.601064 + 2.314707) # electronic and ionic contribution to dielectric
# constant, calculated with VASP
a = 0.41667
def_position = np.ones(3)*a # fractional coordinates defect
vbm = 7.2981 # valence band maximum
# Prepare the chemical potentials B-rich conditions
boron = ase.io.read('OUTCAR_B')
mu_b = boron.get_total_energy()/boron.get_number_of_atoms()
# prepare a numpy array with information about the electrostatic potential
pot_prist = extract_potential_at_core_vasp('OUTCAR_prist')
pot_def = extract_potential_at_core_vasp('OUTCAR_def')
# initialite PointDefect instance
defective = ase.io.read('OUTCAR_def')
pd = PointDefect(defective)
pd.set_dielectric_tensor(e_r)
pd.set_defect_position(def_position)
pd.set_defect_charge(q)
pd.set_pristine_system(pristine)
pd.set_vbm(vbm)
pd.set_chemical_potential_values({'N':None, 'B':mu_b}, force=True)
# correction scheme of Kumagai and Oba
pd.set_finite_size_correction_scheme('ko')
pd.add_correction_scheme_data(potential_pristine=pot_prist, potential_defective=pot_def)
print('Formation energy B-rich, uncorrected: {:.3f}'.format(
pd.get_defect_formation_energy()))
print('Formation energy B-rich, corrected: {:.3f}'.format(
pd.get_defect_formation_energy(True)))
The script will print:
Formation energy B-rich, uncorrected: 11.801
Formation energy B-rich, corrected: 14.275
Example using WIEN2k¶
To illustrate in detail how the correction scheme of Kumagai and Oba is implemented in Spinney, we will take the example of a B vacancy in the charge state -3 in cubic BN, modeled using a \(3 \times 3 \times 3\) supercell of the conventional cubic cell. This system is also studied in the authors’ original paper [KO14].
The data that are needed are the .struct
, .scf
and .vcoul
files for the
pristine and defective supercell.
Note
By default WIEN2k will not write the .vcoul
file. To write it you need to
modify the second line of the .in0
file, by replacing NR2V
with R2V
before running your calculations.
The following block of code assumes that the working directory contains the files:
pristine.struct
anddefective.struct
: the.struct
files for the pristine and defective supercell, respectively.pristine.scf
anddefective.scf
: the.scf
files for the pristine and defective supercell, respectively.pristine.vcoul
anddefective.vcoul
: the.vcoul
files for the pristine and defective supercell, respectively.boron.struct
andboron.scf
: the.struct
and.scf
files for the reference state of Boron.
To calculate the defect formation energy with the correction scheme of Kumagai and Oba, you can use this snippet:
import numpy as np
from spinney.structures.pointdefect import PointDefect
from spinney.constants import conversion_table
from spinney.io.wien2k import prepare_ase_atoms_wien2k
from spinney.io.wien2k import extract_potential_at_core_wien2k
# pristine system
pristine = pristine = prepare_ase_atoms_wien2k('pristine.struct',
'pristine.scf')
# Specifications of the defective system: V_B -3 in BN
q = -3 # charge state
e_r = (4.601064 + 2.314707) # electronic and ionic contribution to dielectric
# constant
def_position = np.zeros(3) # fractional coordinates defect
vbm = 0.7037187925*conversion_table['Ry']['eV'] # valence band maximum
# Prepare the chemical potentials B-rich conditions
boron = prepare_ase_atoms_wien2k('boron.struct', 'boron.scf')
mu_b = boron.get_total_energy()/boron.get_number_of_atoms()
# prepare a numpy array with information about the electrostatic potential
pot_prist = extract_potential_at_core_wien2k('pristine.struct',
'pristine.vcoul')
pot_prist *= conversion_table['Ry']['eV']
pot_def = extract_potential_at_core_wien2k('defective.struct',
'defective.vcoul')
pot_def *= conversion_table['Ry']['eV']
# initialite PointDefect instance
defective = prepare_ase_atoms_wien2k('defective.struct',
'defective.scf')
pd = PointDefect(defective)
pd.set_dielectric_tensor(e_r)
pd.set_defect_position(def_position)
pd.set_defect_charge(q)
pd.set_pristine_system(pristine)
pd.set_vbm(vbm)
pd.set_chemical_potential_values({'N':None, 'B':mu_b}, force=True)
# correction scheme of Kumagai and Oba
pd.set_finite_size_correction_scheme('ko')
pd.add_correction_scheme_data(potential_pristine=pot_prist, potential_defective=pot_def)
print('Formation energy B-rich, uncorrected: {:.3f}'.format(
pd.get_defect_formation_energy()))
print('Formation energy B-rich, corrected: {:.3f}'.format(
pd.get_defect_formation_energy(True)))
The script will print:
Formation energy B-rich, uncorrected: 11.995
Formation energy B-rich, corrected: 14.503
Obtaining more information on the finite-size corrections¶
More information related to the correction scheme can be accessed through the method
calculate_finite_size_correction()
with the keyword argument verbose=True
.
This returns a tuple, whose first element is the correction energy for finite-size effects and the second element is a dictionary.
The dictionary contains the various contributions to \(E_{corr}\),
some information about the sampled atomic-site potential
and the instance of the KumagaiCorr`
class
that has been used to calculate the correction energy term.
Such instance can be accessed from the dictionary with the keyword Corr object
.
From it, we can extract useful information, which allow us, for example to plot the
following picture:
This image can be obtained using this code snippet.
The python interface is independent on the employed first-principles code.
We only assume that a PointDefect
instance has been initialized, as shown above.
Such object is assumed to have been stored in the variable pd
.
For the meaning of the various potentials plotted in the figure, consult the
authors’s paper in reference [KO14].
import matplotlib
import matplotlib.pyplot as plt
matplotlib.rcParams.update({'font.size': 26})
ecorr, dd = pd.calculate_finite_size_correction(verbose=True)
corr_obj = dd['Corr object']
plt.figure(figsize=(8,8))
dist, pot_align_vs_dist = corr_obj.alignment_potential_vs_distance_sampling_region
plt.scatter(dist, pot_align_vs_dist, color='green', marker='^',
label=r'$\Delta V_{PC, q/b}$')
dist, pot = corr_obj.ewald_potential_vs_distance_sampling_region
plt.scatter(dist, pot, label=r'$V_{PC, q}$', color='blue')
dist, pot = corr_obj.difference_potential_vs_distance
plt.scatter(dist, pot, label=r'$V_{q/b}$', marker='x')
# beginning of the sampling region
radius = corr_obj.sphere_radius
plt.axvspan(radius, 9, color='blue', alpha=0.3)
plt.plot(np.linspace(1.5, 9, 100), np.zeros(100), color='black',
linestyle='--')
plt.legend()
plt.xlabel(r'Distance ($\AA$)')
plt.ylabel(r'Potential (V)')
plt.xlim(1.5, 9)
plt.ylim(-3, 0.5)
plt.tight_layout()
plt.savefig('atomic_site_potential_convergence.pdf', format='pdf')
plt.show()