Entries#

The philosophy of defect thermodynamics is to collectively analyse a collection of individual defects. In defermi the individual defect data is stored in a DefectEntry object, while the collection of entries is stored in the DefectsAnalysis object. This tutorial focuses on DefectEntry.

A single entry stores information regarding the type of defect and the result of a total energy calculation. We explore now its properties. In practise, the class is usually initialized automatically when importing data (see Import defect calculations). The input arguments are:

  • defect : Defect object

  • energy_diff : Energy difference btw defective and pristine cell in eV

  • corrections : Dictionary with correction terms to add to the formation energy

  • data : Dictionary with additional data to store

  • formation_energy_function : Custom function for the formation energy calculation (optional, see section on custom functions)

  • defect_concentration_function: Custom function for the defect concentrations calculation (optional, see section on custom functions)

[1]:
from defermi.defects import Vacancy
from defermi.entries import DefectEntry

entry = DefectEntry(
                    defect=Vacancy(specie='O',charge=2,multiplicity=1,bulk_volume=800),
                    energy_diff=7, #eV
                    corrections={'elastic':0.01}
                    )
entry
[1]:
DefectEntry: Name=Vac_O, Charge=2

Defect object#

Defect and DefectComplex objects collect properties of different defect types. defermi supports:

  • Vacancy

  • Interstitial

  • Substitution (can be used to describe anti-sites as well)

  • Polaron

  • DefectComplex

The main input of the class is the defect species (specie). In case of a Substitution, we include the bulk species that is replaced (bulk_specie). Moreover, we can store information regarding the defect charge and the multiplicity in the simulation cell.

[2]:
defect = Vacancy(specie='O',charge=2,multiplicity=1)
print(defect)

print(f"Defect name: {defect.name}")
print(f"Defect type: {defect.type}")
print(f"Defect species: {defect.specie}")
print(f"Particle number difference btw defect and pristine material: {defect.delta_atoms}")
print(f"Symbol: {defect.symbol_with_charge}")
print(f"Symbol in Kröger-Vink notation: {defect.symbol_with_charge_kv}")
Defect: type=Vacancy, species=O, charge=2
Defect name: Vac_O
Defect type: Vacancy
Defect species: O
Particle number difference btw defect and pristine material: {'O': -1}
Symbol: $V_{O}$$^{+2}$
Symbol in Kröger-Vink notation: $V_{O}$$^{°°}$

Defect objects can be initialized from a string as well. The naming convention is as follows (element = \(A\)):

  • Vacancy: 'Vac_A' (symbol=\(V_{A}\))

  • Interstitial: 'Int_A' (symbol=\(A_{i}\))

  • Substitution: 'Sub_B_on_A' (symbol=\(B_{A}\))

  • Polaron: 'Pol_A' (symbol=\({A}_{A}\))

  • DefectComplex : 'Vac_A;Int_A' (symbol=\(V_A - A_i\))

[ ]:
from defermi.defects import get_defect_from_string

defect = get_defect_from_string('Vac_O')
print(defect)

defect_complex = get_defect_from_string('Vac_O;Int_O')
print(defect_complex)
Defect: type=Vacancy, species=O
DefectComplex: [ Defect: type=Vacancy, species=O, Defect: type=Interstitial, species=O, ]

The object can be intialized with structural data as well, specifically from pymatgen’s Structure and Site objects. Using structures allows to store more information, generate new structures, and compute properties (like multiplicity) automatically. The main inputs in this case are defect_site and bulk_structure.

[4]:
from pymatgen.core.structure import Structure

structure = Structure.from_file('../defermi/tests/test_files/structure_bulk.json')
site = structure[0]
print(site)

defect = Vacancy(defect_site=site,bulk_structure=structure)
defect
[3.87973664 2.74338809 6.71990099] Si
[4]:
Defect: type=Vacancy, species=Si, site= [0.29166667 0.29166667 0.29166667]
[5]:
print(f"Composition: {defect.defect_composition}")
print(f"Bulk volume: {defect.bulk_volume} A°")
print(f"Site index: {defect.defect_site_index}")
print(f"Multiplicity: {defect.get_multiplicity()}")
#defect.generate_defect_structure() # generate structure with defect
Composition: Si53
Bulk volume: 1080.993861120942 A°
Site index: 0
Multiplicity: 54

Import#

DefectEntry objects can also be imported from pymatgen Structure objects or from vasp_directories,as already shown for DefectsAnalysis in Import defect calculations. Defects are automatically determined analysing the defect and bulk structures.

[6]:
# from structures

defect_structure = Structure.from_file('../defermi/tests/test_files/structure_vac.json')
bulk_structure = Structure.from_file('../defermi/tests/test_files/structure_bulk.json')

entry_from_structures = DefectEntry.from_structures(
                                    defect_structure=defect_structure,
                                    bulk_structure=bulk_structure,
                                    energy_diff=5, # made-up number, insert your calculation result
                                    corrections={},
                                    charge=-4)
print("\n",entry_from_structures)
Defect automatically identified for defective structure with composition Si53:
 Defect: type=Vacancy, species=Si, site= [0.29166667 0.29166667 0.29166667]

 DefectEntry
Defect: Defect: type=Vacancy, species=Si, charge=-4, site= [0.29166667 0.29166667 0.29166667]
Energy: 5.0000
Corrections: 0.0000
Charge: -4.0
Multiplicity: 1
Data: []
Name: Vac_Si


[7]:
# from VASP directories

path_defect = '../defermi/tests/test_files/SiO2-defects/Defects/vacancies/Vac_O/q2/2-PBE-OPT'
path_bulk = '../defermi/tests/test_files/SiO2-defects/Bulk-2x2x2-supercell'

entry_from_vasp_dirs = DefectEntry.from_vasp_directories(
                                        path_defect=path_defect,
                                        path_bulk=path_bulk,
                                        corrections={},
                                        initial_structure=True)

print("\n",entry_from_vasp_dirs)
Defect automatically identified for defective structure with composition Si24 O47:
 Defect: type=Vacancy, species=O, site= [0.13461155 0.20669714 0.39244549]

 DefectEntry
Defect: Defect: type=Vacancy, species=O, charge=2.0, site= [0.13461155 0.20669714 0.39244549]
Energy: 4.0104
Corrections: 0.0000
Charge: 2.0
Multiplicity: 1
Data: []
Name: Vac_O


Formation energy and defect concentration#

The formation energy and defect concentration are computed with the formation_energy and the defect_concentration methods, respectively. For more details on the expressions refer to Formation energies and Defect concentrations. IMPORTANT: For calculations of defect concentrations in \(\mathrm{cm}^{-3}\), the volume of the pristine cell in \(\AA^3\) must be provided if the Defect object in DefectEntry was not initialized with structures (either manually or from imported calculations).

[8]:
entry.formation_energy(vbm=0,chemical_potentials={'O':-5},fermi_level=0) # eV
[8]:
2.01
[9]:
conc_in_cm3 = entry.defect_concentration(vbm=0,chemical_potentials={'O':-5},fermi_level=0,temperature=1000)
print(f"Defect concentration in cm^-3: {conc_in_cm3}") # cm^-3

conc_per_unit_cell = entry.defect_concentration(vbm=0,chemical_potentials={'O':-5},fermi_level=0,temperature=1000,per_unit_volume=False)
print(f"Defect concentration per unit cell: {conc_per_unit_cell}") # cm^-3
Defect concentration in cm^-3: 92673562982.4248
Defect concentration per unit cell: 7.413885038593984e-11

These routines can also be modified by assigning custom functions, either when the DefectEntry object is initialized (e.g. formation_energy_function kwarg), or at a later stage using the set_formation_energy_function and set_defect_concentration_function methods. The custom functions must accept the arguments of the original function, with the possibility to add other arguments. If the custom functions are modifications of the default functions, these can be accessed as _default_formation_energy and _default_defect_concentrations.

In the following example, we will include the effects of temperature (\(T\)) and volume (\(V\)) on the formation energy (which are usually neglected as an approximation). We write the formation energy as:

\[\Delta E_f (T) = \Delta E_f (0 K) + E(T) + E(\Delta V)\]

where in this example:

\[E(T) = - 10^{-3} \cdot T\]
\[E(V) = -10^{-2} \cdot \Delta V\]
[10]:
# kwargs of the original function + additional kwargs
def formation_energy_with_temperature(
                                    entry,
                                    vbm=0,
                                    chemical_potentials=None,
                                    fermi_level=0,
                                    temperature=0,
                                    delta_volume=0):

    # we can use _default_formation_energy to call the default function
    default_eform = entry._default_formation_energy(vbm=vbm,chemical_potentials=chemical_potentials,fermi_level=fermi_level)

    return default_eform - 0.001 * temperature - 0.01 * delta_volume


entry.set_formation_energy_function(formation_energy_with_temperature)
entry.formation_energy(
                    vbm=0,
                    chemical_potentials={'O':-5},
                    fermi_level=0,
                    temperature=1000,
                    delta_volume=100)
[10]:
0.009999999999999787

The same philosophy applies for the defect_concentration function. More details can be found in the SECTION ON CUSTOM FUNCTIONS