Source code for ase2sprkkr.asr.defectformation
"""Defect formation energies."""
from typing import Union
from asr.core import command, option, ASRResult
group = 'property'
creates = [] # what files are created
@command('asr.defectformation',
resources='1:2h',
dependencies=['asr.setup.defects', 'asr.gs'],
creates=None)
# later include 'asr.dielectricconstant' and 'asr.relax')
# and also the files that this recipe creates
@option('--pristine', type=str,
help='Name of the groundstate .gpw file of the pristine system. It '
'always has to be somewhere within a folder that is called '
'"pristine" in order to work correctly.')
@option('--defect', type=str,
help='Name of the groundstate .gpw file of the defect systems. They '
'always have to be within a folder for the specific defect with '
'a subfolder calles "charge_q" for the respective chargestate q '
'in order to work correctly.')
@option('--defect_name', type=str,
help='Runs recipe for all defect folder within your directory when '
'set to None. Set this option to the name of a desired defect '
'folder in order for it to run only for this particular defect.')
def main(pristine: str = 'gs.gpw', defect: str = 'gs.gpw',
defect_name: Union[str, None] = None) -> ASRResult:
"""
Calculate formation energy of defects.
This recipe needs the directory structure that was created with
setup.defects in order to run properly. It has to be launched within the
folder of the initial structure, i.e. the folder where setup.defects was
also executed.
"""
from ase.io import read
from asr.core import write_json, read_json
from gpaw.defects import ElectrostaticCorrections
from pathlib import Path
import numpy as np
q, epsilons, path_gs = check_and_get_general_inputs()
atoms = read('unrelaxed.json')
nd = int(np.sum(atoms.get_pbc()))
gs_dict = read_json('results-asr.gs.json')
defectformation_dict = {}
defectformation_dict['gaps_nosoc'] = gs_dict.get('gaps_nosoc')
defectformation_dict['gaps_soc'] = gs_dict.get('gaps_soc')
sigma = 2 / (2.0 * np.sqrt(2.0 * np.log(2.0)))
if nd == 3:
epsilon = (epsilons[0] + epsilons[1] + epsilons[2]) / 3.
dim = '3d'
elif nd == 2:
epsilon = [(epsilons[0] + epsilons[1]) / 2., epsilons[2]]
dim = '2d'
folder_list = []
p = Path('.')
# Either run for all defect folders or just a specific one
if defect_name is None:
[folder_list.append(x) for x in p.iterdir() if x.is_dir()
and not x.name == 'pristine_sc']
else:
[folder_list.append(x) for x in p.iterdir() if x.is_dir()
and x.name == defect_name]
for folder in folder_list:
s = Path(folder.name)
sub_folder_list = []
[sub_folder_list.append(x) for x in s.iterdir() if x.is_dir()]
e_form = []
# e_fermi = []
charges = []
# e_fermi_calc = []
for sub_folder in sub_folder_list:
sub_folder_path = folder.name + '/' + sub_folder.name
setup_params = read_json(sub_folder_path + '/params.json')
chargestate = setup_params.get('charge')
charged_file = find_file_in_folder('gs.gpw',
sub_folder_path)
elc = ElectrostaticCorrections(pristine=path_gs,
charged=charged_file,
q=chargestate, sigma=sigma,
dimensionality=dim)
elc.set_epsilons(epsilon)
if chargestate == 0:
e_form.append(elc.calculate_uncorrected_formation_energy())
else:
e_form.append(elc.calculate_corrected_formation_energy())
charges.append(chargestate)
# calc = GPAW(find_file_in_folder('gs.gpw', sub_folder_path))
# e_fermi_calc.append(calc.get_fermi_level())
# e_fermi.append(0)
defectformation_dict[folder.name] = {'formation_energies': e_form,
'chargestates': charges}
# 'fermi_energies_c': e_fermi_calc
# 'fermi_energies': e_fermi,
write_json('defectformation.json', defectformation_dict)
return None
[docs]
def check_and_get_general_inputs():
"""Determine whether all necessary files exist.
Checks if all necessary input files and input parameters for
this recipe are acessible.
"""
from asr.core import read_json
# first, get path of 'gs.gpw' file of pristine_sc, as well as the path of
# 'dielectricconstant.json' of the pristine system
path_epsilon = find_file_in_folder('dielectricconstant.json', None)
path_gs = find_file_in_folder('gs.gpw', 'pristine_sc')
path_q = find_file_in_folder('general_parameters.json', None)
# if paths were found correctly, extract epsilon and q
gen_params = read_json(path_q)
params_eps = read_json(path_epsilon)
q = gen_params.get('chargestates')
epsilons = params_eps.get('local_field')
if q is not None and epsilons is not None:
msg = 'INFO: number of chargestates and dielectric constant '
msg += 'extracted: q = {}, eps = {}'.format(q, epsilons)
print(msg)
else:
msg = 'either number of chargestates and/or dielectric '
msg += 'constant of the host material could not be extracted'
raise ValueError(msg)
if path_gs is not None:
print('INFO: check of general inputs successful')
return q, epsilons, path_gs
[docs]
def find_file_in_folder(filename, foldername):
"""Find a specific file in folder.
Finds a specific file within a folder starting from your current
position in the directory tree.
"""
from pathlib import Path
p = Path('.')
find_success = False
# check in current folder directly if no folder specified
if foldername is None:
check_empty = True
tmp_list = list(p.glob(filename))
if len(tmp_list) == 1:
file_path = tmp_list[0]
print('INFO: found {0}: {1}'.format(filename,
file_path.absolute()))
find_success = True
else:
print('ERROR: no unique {} found in this directory'.format(
filename))
else:
tmp_list = list(p.glob('**/' + foldername))
check_empty = False
# check sub_folders
if len(tmp_list) == 1 and not check_empty:
file_list = list(p.glob(foldername + '/**/' + filename))
if len(file_list) == 1:
file_path = file_list[0]
print('INFO: found {0} in {1}: {2}'.format(
filename, foldername, file_path.absolute()))
find_success = True
elif len(file_list) == 0:
print('ERROR: no {} found in this directory'.format(
filename))
else:
print('ERROR: several {0} files in directory tree: {1}'.format(
filename, tmp_list[0].absolute()))
elif len(tmp_list) == 0 and not check_empty:
print('ERROR: no {0} found in this directory tree'.format(
foldername))
elif not check_empty:
print('ERROR: several {0} folders in directory tree: {1}'.format(
foldername, p.absolute()))
if not find_success:
file_path = None
return str(file_path.absolute())
[docs]
def collect_data():
# from ase.io import jsonio
# from pathlib import Path
# if not Path('results_defectformation.json').is_file():
# return {}, {}, {}
# kvp = {}
# data = {}
# key_descriptions = {}
# dct = jsonio.decode(Path('results_defectformation.json').read_text())
return None
# ========================================================================== #
# This function doesn't exist in the new asr version anymore and has to be #
# changed at a later stage, as well as the way the plots are created (db) #
# ========================================================================== #
# def postprocessing():
# from asr.utils import read_json
#
# formation_dict = read_json('defectformation.json')
# transitions_dict = {}
# gap = formation_dict.get('gaps_nosoc').get('gap')
# for element in formation_dict:
# if element != 'gaps_soc' and element != 'gaps_nosoc':
# print(element)
# plotname = element
# defect_dict = formation_dict[element]
# transitions_dict[plotname] = plot_formation_and_transitions(
# defect_dict, plotname, gap)
#
# return transitions_dict
[docs]
def line_intersection(line1, line2):
"""Get intersection between two lines.
Helper function to calculate intersection of two given lines.
"""
xdiff = (line1[0][0] - line1[1][0], line2[0][0] - line2[1][0])
ydiff = (line1[0][1] - line1[1][1], line2[0][1] - line2[1][1])
def det(a, b):
return a[0] * b[1] - a[1] * b[0]
div = det(xdiff, ydiff)
if div == 0:
raise Exception('lines do not intersect')
d = (det(*line1), det(*line2))
x = det(d, xdiff) / div
y = det(d, ydiff) / div
return x, y
[docs]
def line(p1, p2):
"""Define a line between p1 and p2.
Helper function to define a line.
"""
A = (p1[1] - p2[1])
B = (p2[0] - p1[0])
C = (p1[0] * p2[1] - p2[0] * p1[1])
return A, B, -C
[docs]
def intersection(L1, L2):
"""Get intersection between two lines L1 and L2.
Helper function to calculate intersection of two given lines that were
defined with the upper 'line' function.
"""
D = L1[0] * L2[1] - L1[1] * L2[0]
Dx = L1[2] * L2[1] - L1[1] * L2[2]
Dy = L1[0] * L2[2] - L1[2] * L2[0]
if D != 0:
x = Dx / D
y = Dy / D
return x, y
else:
return False
[docs]
def plot_formation_and_transitions(defect_dict, defectname, gap):
"""XXX: Do me.
Function to plot formation energies versus the Fermi energy and to
obtain transition points between most stable charge states of a given
defect
"""
# from asr.utils import write_json
import matplotlib.pyplot as plt
import numpy as np
# initalize and read formation energies and charge states
x = []
y = []
q = []
x = np.array(defect_dict['fermi_energies'])
y = np.array(defect_dict['formation_energies'])
q = np.array(defect_dict['chargestates'])
# set general parameters
x_range = np.array([0, gap])
x_diff = np.array([[-x[0], x_range[1] - x[0]]])
y_edges = np.array(
[[y[0] + x_diff[0][0] * q[0], y[0] + x_diff[0][1] * q[0]]])
# set general plotting parameters
plt.figure()
lw = 1
linestylelist = ['solid', 'dashdot', 'dashed', 'dotted']
colorlist = ['black', 'C0', 'C1']
# plt.ylim(0, max(y_edges[:, 0]) * 1.1)
plt.ylim(0, 20)
plt.xlim(x_range[0] - 0.1 * gap, x_range[1] + 0.1 * gap)
# bbox = {'fc': '0.8', 'pad': 0}
# initialise np array containing all lines
linearray = np.array([[line([x_range[0], y_edges[0][0]],
[x_range[1], y_edges[0][1]]), q[0]]])
# initialise plot
plt.plot(x_range, y_edges[0], color=colorlist[np.sign(q[0])], lw=lw,
linestyle=linestylelist[abs(q[0])], label='q = {}'.format(q[0]))
plt.text((2 * x_range[0] - 0.1 * gap) / 2.,
max(y_edges[:, 0]) * 1.1 / 2., 'valence band',
{'ha': 'center', 'va': 'center'}, rotation=90)
plt.text((2 * x_range[1] + 0.1 * gap) / 2.,
max(y_edges[:, 0]) * 1.1 / 2., 'conduction band',
{'ha': 'center', 'va': 'center'}, rotation=90)
# append other lines in a loop
for i in range(1, len(q)):
x_diff = np.append(x_diff, [[-x[i], x_range[1] - x[i]]], axis=0)
y_edges = np.append(y_edges, [[y[i] + x_diff[i][0] * q[i],
y[i] + x_diff[i][1] * q[i]]], axis=0)
linearray = np.append(linearray, [[line([x_range[0], y_edges[i][0]], [
x_range[1], y_edges[i][1]]), q[i]]], axis=0)
plt.plot(x_range,
y_edges[i],
color=colorlist[np.sign(q[i])],
lw=lw,
linestyle=linestylelist[abs(q[i])],
label='q = {}'.format(q[i]))
# flip arrays in order for them to start with positive slope
linearray_up = np.flip(linearray, axis=0)
x_diff = np.flip(x_diff, axis=0)
y_edges = np.flip(y_edges, axis=0)
q_copy = np.flip(q)
# IMPORTANT: all of the important arrays have now been reversed!
# find minimum line at zero energy and create temporary array with lines
for i in range(len(y_edges)):
if y_edges[i][0] == min(y_edges[:, 0]):
# start_index = i
linearray_up = np.delete(linearray_up, np.s_[0:i], 0)
trans_array = np.array(
[[(0, y_edges[i][0]), q_copy[i], q_copy[i]]])
q_copy = np.delete(q_copy, np.s_[0:i])
# loop over all lines in linearray_up and calculate intersection points
while len(linearray_up) > 1:
linedists = np.array([[intersection(linearray_up[0][0],
linearray_up[1][0]),
q_copy[0], q_copy[1]]])
if len(linearray_up) > 2:
for j in range(2, len(linearray_up)):
linedists = np.append(linedists, [[intersection(
linearray_up[0][0], linearray_up[j][0]), q_copy[0],
q_copy[j]]], axis=0)
linearray_up = np.delete(linearray_up, 0, 0)
q_copy = np.delete(q_copy, 0)
x_list = []
n = 0
for element in linedists:
x_list.append(element[0][0])
if element[0][0] >= 0 and len(x_list) == 1:
if element[0][0] == min(x_list):
trans_array = np.append(trans_array, [element], axis=0)
dropout = n
elif (element[0][0] >= 0 and element[0][0] == min(x_list)
and element[0][0] < trans_array[-1][0][0]):
trans_array = np.append(trans_array, [element], axis=0)
trans_array = np.delete(trans_array, -2, 0)
dropout = n
n = n + 1
for i in range(dropout):
linearray_up = np.delete(linearray_up, np.s_[0:i], 0)
for i in range(len(y_edges)):
if y_edges[i][1] == min(y_edges[:, 1]):
trans_array = np.append(trans_array, [[(x_range[1], y_edges[i][1]),
trans_array[-1][2],
trans_array[-1][2]]],
axis=0)
# plot the results and save the figure
for element in trans_array:
ratio = element[0][1] / max(y_edges[:, 0])
plt.axvline(x=element[0][0], ymax=ratio, color='C3',
linestyle=(0, (5, 10)))
plt.axvspan(x_range[0] - 0.1 * gap, x_range[0], color='lightgrey')
plt.axvspan(x_range[1] + 0.1 * gap, x_range[1], color='lightgrey')
x_val = [x[0] for x in trans_array[:, 0]]
y_val = [x[1] for x in trans_array[:, 0]]
# plt.plot(x_val, y_val, marker='D', linestyle='-', color='C3')
plt.plot(x_val, y_val, marker='D', linestyle='', color='C3')
plt.xlabel(r'$E_{F}$ in eV')
plt.ylabel(r'$E_{formation}$ in eV')
plotname = 'plot_{}.png'.format(defectname)
plt.legend()
plt.savefig(plotname)
return trans_array
# def webpanel(result, row, key_descriptions):
# from asr.database.browser import fig, table
#
# if 'something' not in row.data:
# return None, []
#
# table1 = table(row,
# 'Property',
# ['something'],
# kd=key_descriptions)
# panel = ('Title',
# [[fig('something.png'), table1]])
# things = [(create_plot, ['something.png'])]
# return panel, things
# def create_plot(row, fname):
# import matplotlib.pyplot as plt
#
# data = row.data.something
# fig = plt.figure()
# ax = fig.gca()
# ax.plot(data.things)
# plt.savefig(fname)
if __name__ == '__main__':
main.cli()