def explore_defect(defect, host, **kwargs): """ Diagnostic tool to determine defect from defect calculation and host. :Parameters: defect : `pylada.vasp.ExtractDFT` Extraction object for the vasp calculation of the defect structure. The defect structure should be a supercell of the host. Its unit cell must be an exact multiple of the host unit cell. Atoms may have moved around, however. host : `pylada.vasp.ExtractDFT` Extraction object for the vasp calculation of the host structure. kwargs Passed on to `reindex_sites`. :return: Dictionary containing three items: - 'vacancy': a list of atoms from the host *missing* in the defect structure. The position of these atoms correspond to the missing atom in the supercell (not the translational equivalent of the unit-cell). - 'substitution': list of indices referring to atoms in the defect structure with substituted types (w.r.t. the host). - 'intersititial': list of indices referring to atoms in the defect structure with no counterpart in the host structure. :note: The results may be incorrect if the defects incur too much relaxation. """ from copy import deepcopy from pylada.crystal.defects import reindex_sites from pylada.crystal import supercell dstr = deepcopy(defect.structure) hstr = host.structure reindex_sites(dstr, hstr, **kwargs) result = {'interstitial': [], 'substitution': [], 'vacancy': []} # looks for intersitials and substitutionals. for i, atom in enumerate(dstr): if atom.site == -1: result['interstitial'].append(i) elif atom.type != hstr[atom.site].type: result['substitution'].append(i) # looks for vacancies. # haowei: supercell, scale filled = supercell(lattice=hstr, supercell=dstr.cell * dstr.scale / hstr.scale) reindex_sites(filled, dstr, **kwargs) for atom in filled: if atom.site != -1: continue result['vacancy'].append(deepcopy(atom)) return result
def explore_defect(defect, host, tol): """ Function to find the position, atom-type etc. of the defect species Parameters defect = defect structure host = host structure tol = tolerance to compare defect and host structure Returns Dictionary containing four items 1. numpy position vectors of defect site 2. Atom type 3. index of defect in defect structure 4. site index with respect to bulk(host) structure Assumptions: 1. user has created only single point defect (may work for more than one point defect, but not thoroughly tested) 2. supercell shape, volume and atom odering in CONTCAR is same in defect and host Note: 1. Adopted from Haowei Peng version in pylada.defects """ result = {'interstitial': [], 'substitution': [], 'vacancy': []} ###look for vacancies hstr = deepcopy(host) dstr = deepcopy(defect) reindex_sites(hstr, dstr, tol) for atom in hstr: if atom.site != -1: continue result['vacancy'].append(deepcopy(atom)) ###look for intersitials and substitutionals hstr = deepcopy(host) dstr = deepcopy(defect) reindex_sites(dstr, hstr, tol) dstr_copy = deepcopy(dstr) for i, atom in enumerate(dstr_copy): atom.index = i ## Is intersitial always last? No, but reindex_sites, always labels interstitial as site=-1 if atom.site == -1: result['interstitial'].append(deepcopy(atom)) elif atom.type != hstr[atom.site].type: result['substitution'].append(deepcopy(atom)) return result
def potential_alignment(defect, host, ngh_shell=False, str_tol=0.4, e_tol=0.2): """ function to compute potential alignment correction Reference: S. Lany and A. Zunger, Phys. Rev. B 78, 235104 (2008) Parameters defect = pylada.vasp.Extract object host = pylada.vasp.Extract object ngh_shell = bool, choice to let pylada decide to remove defect neighbors from pot_align corr str_tol = tolerance to compare host and defect structures e_tol = energy tolerance (same concept to S. Lany Fortran codes) Returns list = str_tol, e_tol, #_atoms_excluded, potal_align_corr *eV format = [str_tol, e_tol, nex, pot_align] Returns average difference of the electrostatic potential of the unperturbed atoms in the defect structure with respect to the host. *Perturbed* atoms are those flagged as defect by `explore_defect`, their first coordination shell if ''first_shell'' is true, and atoms for which the electrostatic potential differ to far from the average electrostatic potential for each lattice site. """ # get defect structure from pylada Extract object d_str = defect.structure # get host structure from pylada Extract object h_str = host.structure # find defect type, its coordinates, atom-type defects = explore_defect(d_str, h_str, str_tol) # re-index defect structure w.r.t host structure, # function in-built in pylada_crystal_defects # CAUTION - may cause problem if defect structure is very highly distorted # Works well for split and reasonably (?) distorted structures reindex_sites(d_str, h_str, str_tol) # list of indicies in defect structure, that are accpetable in pot_align corr acceptable = [True for a in d_str] # make intersitial and substitutional unacceptable for keys in defects: if keys != 'vacancy': for atom in defects[keys]: acceptable[atom.index] = False # Check if not any(acceptable): # there is a problem: possibly defect and host structure cannot be compared using reindex print( "ERROR; Cannot compare defect and host !! Switch to avg_potential_alignment" ) sys.exit() # make neighbors (upto=tol=0.2) to defects unacceptable if ngh_shell: for keys in defects: for atom in defects[keys]: for n in ffirst_shell(d_str, atom.pos, 0.1): acceptable[n[0].index] = False # copy of acceptable before trusting user inputs raw_acceptable = deepcopy(acceptable) # dictionary with average defect elec. pot dict_defecte = avg_electropot(defect) # list to store abs. differnce in the electrostatic potential of defect and host diff_dh = [ (0.0 * eV if not ok else abs(e - dict_defecte[a.type]).rescale(eV)) for e, a, ok in zip(defect.electropot, d_str, acceptable) ] # find max value of difference in electrostatic potential of defect and host maxdiff = max(diff_dh).magnitude # make atoms with diff_dh larger than user energy tolerance(e_tol) unacceptable for ii in range(len(acceptable)): if acceptable[ii] == False: pass elif float(diff_dh[ii].magnitude) >= maxdiff or float( diff_dh[ii].magnitude) >= e_tol: acceptable[ii] = False # Avoid excluding all atoms if not any(acceptable): # if user give e_tol < 0.0001 print( "WARNING; e_tol is too small excluding all atoms !! Switching to defaults" ) # return to default, which accepts all atomic sites expect defect sites (and ngh sites) acceptable = deepcopy(raw_acceptable) # Check if not any(acceptable): # there is a problem: possibly defect and host structure cannot be compared print( "ERROR; Cannot compare defect and host !! Switch to avg_potential_alignment" ) sys.exit() # count for unacceptable number of atoms in defect structure nex = 0 for jj in range(len(acceptable)): if acceptable[jj] == False: nex = nex + 1 # compute the difference in electrostatic of defect and host only for acceptable atoms diff_dh2 = [(e - host.electropot[a.site]).rescale(eV) for e, a, ok in zip(defect.electropot, d_str, acceptable) if ok] # compute alignment_correction align_corr = np.mean(diff_dh2) return [ "{:0.3f}".format(float(str_tol)), "{:0.3f}".format(float(e_tol)), nex, "{:0.4f}".format(float(align_corr)), 'eV' ]
def potential_alignment(defect, host, maxdiff=None, first_shell=False, tolerance=0.25): """ Returns potential alignment correction. :Parameters: defect An output extraction object as returned by the vasp functional when computing the defect of interest. host An output extraction object as returned by the vasp functional when computing the host matrix. maxdiff : float or None, in eV(?) Maximum difference between the electrostatic potential of an atom and the equivalent host electrostatic potential beyond which that atom is considered pertubed by the defect. If None or negative, then differences in electrostatice potentials are not considered. Default 0.5. first_shell : bool If true then removes from potential alignment the first neighbor of defect atoms. Default False. tolerance Passed on to `reindex_sites`. Default 0.25. :return: The potential alignment in eV (without charge factor). Returns average difference of the electrostatic potential of the unperturbed atoms in the defect structure with respect to the host. *Perturbed* atoms are those flagged as defect by `explore_defect`, their first coordination shell if ''first_shell'' is true, and atoms for which the electrostatic potential differ to far from the average electrostatic potential for each lattice site (parameterized by maxdiff). """ from itertools import chain from numpy import abs, array, mean, any from quantities import eV from . import reindex_sites, first_shell as ffirst_shell dstr = defect.structure hstr = host.structure reindex_sites(dstr, hstr, tolerance=tolerance) defects = explore_defect(defect, host, tolerance=tolerance) acceptable = [True for a in dstr] # make interstitials and substitutionals unaceptable. for i in chain(defects['interstitial'], defects['substitution']): acceptable[i] = False if first_shell: for n in ffirst_shell(dstr, dstr[i].pos, tolerance=tolerance): acceptable[n[0].index] = False # makes vacancies unacceptable. if first_shell: for atom in defects['vacancy']: for n in ffirst_shell(dstr, atom.pos, tolerance=tolerance): acceptable[n[0].index] = False # make a deepcopy for backup raw_acceptable = list(acceptable) if maxdiff != None and maxdiff > 0.0: # directly compare the atomic site between the host and defect # cell/supercell diff_dh = [(0.0 * eV if not ok else abs(e - host.electropot[a.site]).rescale(eV)) for e, a, ok in zip(defect.electropot, dstr, acceptable)] for ixx in range(len(acceptable)): if acceptable[ixx] == False: pass elif float(diff_dh[ixx].magnitude) > maxdiff: acceptable[ixx] = False if not any(acceptable): # if some one try to use maxdiff = 0.0000000001, @&#(@&#(#@^@ print("WARNING: maxdiff is too small! Jump to maxdiff=None") # return to the default one, which accept all the atomic sites except the # defect sites acceptable = list(raw_acceptable) iterable = zip(defect.electropot, dstr, acceptable) return mean([(e - host.electropot[a.site]).rescale(eV).magnitude for e, a, ok in iterable if ok]) * eV
def potential_alignment(defect, host, maxdiff=None, first_shell=False, tolerance=0.25): """ Returns potential alignment correction. :Parameters: defect An output extraction object as returned by the vasp functional when computing the defect of interest. host An output extraction object as returned by the vasp functional when computing the host matrix. maxdiff : float or None, in eV(?) Maximum difference between the electrostatic potential of an atom and the equivalent host electrostatic potential beyond which that atom is considered pertubed by the defect. If None or negative, then differences in electrostatice potentials are not considered. Default 0.5. first_shell : bool If true then removes from potential alignment the first neighbor of defect atoms. Default False. tolerance Passed on to `reindex_sites`. Default 0.25. :return: The potential alignment in eV (without charge factor). Returns average difference of the electrostatic potential of the unperturbed atoms in the defect structure with respect to the host. *Perturbed* atoms are those flagged as defect by `explore_defect`, their first coordination shell if ''first_shell'' is true, and atoms for which the electrostatic potential differ to far from the average electrostatic potential for each lattice site (parameterized by maxdiff). """ from itertools import chain from numpy import abs, array, mean, any from quantities import eV from . import reindex_sites, first_shell as ffirst_shell dstr = defect.structure hstr = host.structure reindex_sites(dstr, hstr, tolerance=tolerance) defects = explore_defect(defect, host, tolerance=tolerance) acceptable = [True for a in dstr] # make interstitials and substitutionals unaceptable. for i in chain(defects['interstitial'], defects['substitution']): acceptable[i] = False if first_shell: for n in ffirst_shell(dstr, dstr[i].pos, tolerance=tolerance): acceptable[n[0].index] = False # makes vacancies unacceptable. if first_shell: for atom in defects['vacancy']: for n in ffirst_shell(dstr, atom.pos, tolerance=tolerance): acceptable[n[0].index] = False # make a deepcopy for backup raw_acceptable = list(acceptable) if maxdiff != None and maxdiff > 0.0: # directly compare the atomic site between the host and defect # cell/supercell diff_dh = [ (0.0 * eV if not ok else abs(e - host.electropot[a.site]).rescale(eV)) for e, a, ok in zip(defect.electropot, dstr, acceptable) ] for ixx in range(len(acceptable)): if acceptable[ixx] == False: pass elif float(diff_dh[ixx].magnitude) > maxdiff: acceptable[ixx] = False if not any(acceptable): # if some one try to use maxdiff = 0.0000000001, @&#(@&#(#@^@ print("WARNING: maxdiff is too small! Jump to maxdiff=None") # return to the default one, which accept all the atomic sites except the # defect sites acceptable = list(raw_acceptable) iterable = zip(defect.electropot, dstr, acceptable) return mean([(e - host.electropot[a.site]).rescale(eV).magnitude for e, a, ok in iterable if ok]) * eV