Charge Transition Levels

In section Thermodynamic limits for the chemical potentials we have discussed how Spinney can be used for obtaining the values of the chemical potentials in given conditions.

The defect formation energy, equation (2), depends also on the chemical potential of the electron, usually expressed as \(\epsilon_{VMB} + E_F\).

For this reason, the defect formation energy is often plotted as a function of the Fermi level \(E_F\). Spinney offers the class Diagram for this purpose.

Such diagrams are also useful for visualizing the defect thermodynamic charge transition levels, which define the value of \(E_F\) for which two defect charge states have the same formation energy.

We can take again the defect chemistry of intrinsic GaN as an illustrative example. In particular, the electronic energy of the intrinsic defects of GaN in several charge states was calculated using a supercell containing 96 atoms and the PBE exchange-correlation functional using a 4x2x2 reciprocal-point mesh. The formation energies will be calculated in the Ga-rich limit.

Using the class Diagram

Step 1: Calculate the Defect Formation Energies

The easiest way to calculate defect formation energies, including electrostatic finite-size effect corrections, is through the PointDefect class. Since many defects in several charge states have to be considered, a straightforward way for processing the DFT data is collecting the results in a proper directory hierarchy. For example, using output from VASP, if we decide to use the correction scheme of Kumagai and Oba, which only requires the OUTCAR files, the directory tree might look like this (see also Manage a defective system with the class DefectiveSystem):

data_path
├── data_defects
│   ├── Ga_int
│   │   ├── 0
│   │   │   ├── OUTCAR
│   │   │   └── position.txt
│   │   ├── 1
│   │   │   ├── OUTCAR
│   │   │   └── position.txt
│   │   ├── 2
│   │   │   ├── OUTCAR
│   │   │   └── position.txt
│   │   └── 3
│   │       ├── OUTCAR
│   │       └── position.txt
│   ├── Ga_N
│   │   ├── 0
│   │   │   ├── OUTCAR
│   │   │   └── position.txt
│   │   ├── 1
│   │   │   ├── OUTCAR
│   │   │   └── position.txt
│   │   ├── -1
│   │   │   ├── OUTCAR
│   │   │   └── position.txt
│   │   ├── 2
│   │   │   ├── OUTCAR
│   │   │   └── position.txt
│   │   └── 3
│   │       ├── OUTCAR
│   │       └── position.txt
...
├── Ga
│   └── OUTCAR
├── N2
│   └── OUTCAR
└── pristine
    └── OUTCAR

where the calculations result are collected for each charge state of any specific defect, as well for the pristine system and parent compounds.

Now the defect formation energy can be calculated for each defect by walking the directory tree. This code snippet also writes a file containing the formation energies for each defect in each charge state.

import os

 import ase.io
 from spinney.structures.pointdefect import PointDefect
 from spinney.io.vasp import extract_potential_at_core_vasp
 from spinney.tools.formulas import count_elements

 path_defects = os.path.join('data', 'data_defects')
 path_pristine = os.path.join('data', 'pristine', 'OUTCAR')
 path_ga = os.path.join('data', 'Ga', 'OUTCAR')

 # prepare preliminary data
 ase_pristine = ase.io.read(path_pristine, format='vasp-out')
 pot_pristine = extract_potential_at_core_vasp(path_pristine)
 ase_ga = ase.io.read(path_ga, format='vasp-out')

 vbm = 5.009256 # valence band maximum pristine system
 e_rx = 5.888338 + 4.544304
 e_rz = 6.074446 + 5.501630
 # dielectric tensor
 e_r = [[e_rx, 0, 0], [0, e_rx, 0], [0, 0, e_rz]]

 # store defect positions in  dictionary for access convenience
 defect_positions = {'Ga_int' : (0.25, 0.55, 0.55),
                     'N_int'  : (0.5, 0.41667, 0.50156),
                     'Vac_Ga' : (0.5, 0.6666666666, 0.50156),
                     'Vac_N'  : (0.5, 0.3333333333, 0.46045),
                     'Ga_N'   : (0.5, 0.3333333333, 0.46045),
                     'N_Ga'   : (0.5, 0.6666666666, 0.50156)
                     }

 # get the chemical potential of Ga
 chem_pot_ga = ase_ga.get_total_energy()/ase_ga.get_number_of_atoms()
 # get the chemical potential of N in the Ga-rich conditions
 elements = count_elements(ase_pristine.get_chemical_formula())
 chem_pot_n = ase_pristine.get_total_energy() - elements['Ga']*chem_pot_ga
 chem_pot_n /= elements['N']

 # write the formation energy for Fermi level = 0 to a file
 energy_file = open('formation_energies_GaN_Ga_rich.txt', 'w')
 energy_file.write('#{:<8} {:8} {:>30}\n'.format('system',
                                                 'charge',
                                                 'formation energy (eV)'))
 for root, dirs, files in os.walk(path_defects):
     path = root.split(os.sep)
     if 'OUTCAR' in files:
         def_path = os.path.join(root, 'OUTCAR')
         ase_outcar = ase.io.read(def_path)
         pot_defective = extract_potential_at_core_vasp(def_path)
         defect_name = path[-2]
         charge_state = int(path[-1])
         print('Processing defect {} in charge state {}'.format(defect_name,
                                                                charge_state))
         print('Data in: {}'.format(path))
         # prepare PointDefect object
         pdf = PointDefect(ase_outcar)
         pdf.set_pristine_system(ase_pristine)
         pdf.set_chemical_potential_values({'N':chem_pot_n, 'Ga':chem_pot_ga})
         pdf.set_vbm(vbm)
         pdf.set_defect_charge(charge_state)
         pdf.set_defect_position(defect_positions[defect_name])
         pdf.set_dielectric_tensor(e_r)
         pdf.set_finite_size_correction_scheme('ko')
         pdf.add_correction_scheme_data(potential_pristine=pot_pristine,
                                        potential_defective=pot_defective)
         corrected_energy = pdf.get_defect_formation_energy(True)
         energy_file.write('{:<8} {:>8} {:30.10f}\n'.format(defect_name,
                                                            charge_state,
                                                            corrected_energy))
         print('Done.\n------------------------------------------------------')

 energy_file.close()

A completely similar approach can be used for WIEN2k or any other first-principle code.

Step 2: Get Thermodynamic Charge Transition Levels and plot the Diagram

The file formation_energies_GaN_Ga_rich.txt, containing the formation energies calculated for each point defect, has the general format of a text file, with optional headers starting with a #:

# description or other comments
defect_name    charge_state     formation_energy_at_vbm

formation_energy_at_vbm is the calculated value of the formation energy for an electron chemical potential equal to the valence band maximum, i.e. for \(E_F = 0\).

Thermodynamic charge transition levels can be easily calculated by initializing a Diagram object.

The class constructor takes two mandatory parameters: a dictionary containing information about point defects formation energies (at \(E_F = 0\)) and their charge state and a 2-ple indicating the system valence band maximum and conduction band minimum. The latter argument will give the range within which possible transition levels are considered. Absolute values are intended for \(\mu_e\) as independent variables; setting the valence band maximum to 0, will give \(E_F\) as independent variable.

The former argument, might be obtained from a file analogous to formation_energies_GaN_Ga_rich.txt using the function extract_formation_energies_from_file().

The following code snipper will calculate the charge transition levels and print them in the text file transition_levels.txt.

from spinney.defects.diagrams import extract_formation_energies_from_file, Diagram

data_file = 'formation_energies_GaN_Ga_rich.txt'
defect_dictionary = extract_formation_energies_from_file(data_file)
dgm = Diagram(defect_dictionary, (0, 1.713)) # E_F from 0 to PBE band gap value
dgm.write_transition_levels('transition_levels.txt')

The content of transition_levels.txt will be:

#Defect type  q/q'
Ga_N          2/3      0.441121
              1/2      0.731836
              0/1      1.250109
Ga_int        2/3      0.892796
N_Ga          1/2      0.192495
              0/1      0.806926
...

Transition level values can also be accessed in a Python session using the attribute transition_levels:

dgm.transition_levels

returns a Pandas Series object with the transition levels.

Extending the band gap

It is well known that standard local and semilocal functionals severely underestimate the band gap of semiconductor materials. For this purpose the valence band maxima are often aligned to those obtained from more accurate functionals, like hybrids. Transition levels can be calculated in this case by specifying an extended_gap parameter in the constructor of Diagram.

For example, the paper if reference [LVdW17] finds that the valence band maximum of GaN calculated with HSE with the 31% of Hartree-Fock exchange lies 0.85 eV below the valence band maximum found by PBE and predicts a band gap of 3.51 eV.

We can calculate the transition levels in this extended gap by using the following snippet:

# Fermi level will range in the HSE range
dgm = Diagram(defect_dictionary, (0, 1.713), (-0.85, - 0.85 + 3.51))
dgm.write_transition_levels('transition_levels_extended.txt')

The file transition_levels_extended.txt will contain also transition levels located outside the PBE gap:

#Defect type  q/q'
Ga_N          2/3      0.441121
              1/2      0.731836
              0/1      1.250109
              -1/0     1.825432
Ga_int        2/3      0.892796
              1/2      2.008318
              0/1      2.374206
...

A plot is very useful to visualize these results. The following snippet shows how to make the plot with Spinney.

from spinney.defects.diagrams import extract_formation_energies_from_file, Diagram

data_file = 'formation_energies_GaN_Ga_rich.txt'
defect_dictionary = extract_formation_energies_from_file(data_file)

dgm = Diagram(defect_dictionary, (0, 1.713), (-0.85, - 0.85 + 3.51))
# use some prettier labels in the plot
dgm.labels = {'Ga_int' : r'$Ga_i$',
              'N_int'  : r'$N_i$',
              'Vac_Ga' : r'$Vac_{Ga}$',
              'Vac_N'  : r'$Vac_N$',
              'Ga_N'   : r'$Ga_N$',
              'N_Ga'   : r'$N_{Ga}$'}
# personalize colors to use in the plot
colors = {'Ga_int' : 'red',
          'N_int'  : 'blue',
          'Vac_Ga' : 'orange',
          'Vac_N'  : 'gray',
          'Ga_N'   : 'magenta',
          'N_Ga'   : 'green'}
# save the plot
dgm.plot(save_flag=True, save_title='diagram',
         title='Intrinsic GaN, Ga-rich conditions', legend=True,
         colors_dict=colors, x_label=r'$E_F$ (eV)')

This code will save the file diagram.pdf with the plot:

../_images/diagram.png

Using the class DefectiveSystem

The directories structure used in the previous section is compatible with the one required by an instance of DefectiveSystem.

Such object has a Diagram instance accessible through the attribute diagram. In order to create this Diagram object, one needs to specify the parameters gap_range and eventually extended_gap_range needed by Diagram. These values can be added using the attributes gap_range and extended_gap_range of a DefectiveSystem.

For example, suppose that a DefectiveSystem instance as been initialized as defective_system (see Manage a defective system with the class DefectiveSystem). Then for our example, one can type:

defective_system.gap_range = (0, 1.713)
defective_system.extended_gap_range = (-0.85, - 0.85 + 3.51)
dgm = defective_system.diagram

dgm is an initialized Diagram object, which we can use as shown in the previous sections.