Example #1
0
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
Example #2
0
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'
    ]
Example #5
0
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
Example #6
0
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