Exemple #1
0
    def test_vacancy(self):
        struc = PymatgenTest.get_structure("VO2")
        V_index = struc.indices_from_symbol("V")[0]
        vac = Vacancy(struc, struc[V_index])

        # test generation and super cell
        vac_struc = vac.generate_defect_structure(1)
        self.assertEqual(vac_struc.composition.as_dict(), {"V": 1, "O": 4})

        vac_struc = vac.generate_defect_structure(2)
        self.assertEqual(vac_struc.composition.as_dict(), {"V": 15, "O": 32})

        vac_struc = vac.generate_defect_structure(3)
        self.assertEqual(vac_struc.composition.as_dict(), {"V": 53, "O": 108})

        vac_struc = vac.generate_defect_structure([[2.0, 0, 0], [0, 0, -3.0],
                                                   [0, 2.0, 0]])
        self.assertEqual(vac_struc.composition.as_dict(), {"V": 23, "O": 48})

        # test charge
        vac = Vacancy(struc, struc[V_index])
        vac_struc = vac.generate_defect_structure(1)
        self.assertEqual(vac_struc.charge, 0.0)

        vac = Vacancy(struc, struc[V_index], charge=1.0)
        vac_struc = vac.generate_defect_structure(1)
        self.assertEqual(vac_struc.charge, 1.0)

        vac = Vacancy(struc, struc[V_index], charge=-1.0)
        vac_struc = vac.generate_defect_structure(1)
        self.assertEqual(vac_struc.charge, -1.0)

        # test multiplicity
        vac = Vacancy(struc, struc[V_index])
        self.assertEqual(vac.multiplicity, 2)

        O_index = struc.indices_from_symbol("O")[0]
        vac = Vacancy(struc, struc[O_index])
        self.assertEqual(vac.multiplicity, 4)

        # Test composition
        self.assertEqual(dict(vac.defect_composition.as_dict()), {
            "V": 2,
            "O": 3
        })

        # test lattice value error occurs for different lattices
        sc_scaled_struc = struc.copy()
        sc_scaled_struc.make_supercell(2)
        self.assertRaises(ValueError, Vacancy, struc, sc_scaled_struc[V_index])
        self.assertRaises(ValueError, Vacancy, sc_scaled_struc, struc[V_index])

        # test value error raised for site not in the structure
        non_site = PeriodicSite("V",
                                struc[V_index].frac_coords + [0.0, 0.0, 0.1],
                                struc.lattice)
        self.assertRaises(ValueError, Vacancy, struc, non_site)
Exemple #2
0
    def setUp(self):

        self.epsilon = 15.

        struc = PymatgenTest.get_structure("VO2")
        struc.make_supercell(3)
        vac = Vacancy(struc, struc.sites[0], charge=-3)

        # load neccessary parameters for defect_entry to make use
        # of Freysoldt and Kumagai corrections
        p = {}
        ids = vac.generate_defect_structure(1)
        abc = struc.lattice.abc
        axisdata = [np.arange(0., lattval, 0.2) for lattval in abc]
        bldata = [np.array([1. for u in np.arange(0., lattval, 0.2)]) for lattval in abc]
        dldata = [
            np.array([(-1 - np.cos(2 * np.pi * u / lattval)) for u in np.arange(0., lattval, 0.2)]) for lattval in abc
        ]
        p.update({'axis_grid': axisdata, 'bulk_planar_averages': bldata, 'defect_planar_averages': dldata,
                  'initial_defect_structure': ids, 'defect_frac_sc_coords': struc.sites[0].frac_coords,
                  'bulk_sc_structure': struc})

        bulk_atomic_site_averages, defect_atomic_site_averages = [], []
        defect_site_with_sc_lattice = PeriodicSite( struc.sites[0].specie, struc.sites[0].coords,
                                                    struc.lattice, coords_are_cartesian = True)
        max_dist = 9.6
        pert_amnt = 1.
        for site_ind, site in enumerate(struc.sites):
            if site.specie.symbol == "O":
                Oval = -30.6825
                bulk_atomic_site_averages.append( Oval)
                if site_ind:
                    dist_to_defect = site.distance_and_image( defect_site_with_sc_lattice)[0]
                    defect_site_val = Oval - .3 + pert_amnt * ((max_dist - dist_to_defect)/max_dist)**2
                    defect_atomic_site_averages.append( defect_site_val)
            else:
                Vval = -51.6833
                bulk_atomic_site_averages.append( Vval)
                if site_ind:
                    dist_to_defect = site.distance_and_image( defect_site_with_sc_lattice)[0]
                    defect_site_val = Vval - .3 + pert_amnt * ((max_dist - dist_to_defect)/max_dist)**2
                    defect_atomic_site_averages.append( defect_site_val)

        site_matching_indices = [[ind, ind-1] for ind in range(len(struc.sites)) if ind != 0]

        p.update({ "bulk_atomic_site_averages": bulk_atomic_site_averages,
                   "defect_atomic_site_averages": defect_atomic_site_averages,
                   "site_matching_indices": site_matching_indices})
        self.defect_entry = DefectEntry( vac, 0., parameters = p)
    def test_vacancy(self):
        struc = PymatgenTest.get_structure("VO2")
        V_index = struc.indices_from_symbol("V")[0]
        vac = Vacancy(struc, struc[V_index])

        # test generation and super cell
        vac_struc = vac.generate_defect_structure(1)
        self.assertEqual(vac_struc.composition.as_dict(), {"V": 1, "O": 4})

        vac_struc = vac.generate_defect_structure(2)
        self.assertEqual(vac_struc.composition.as_dict(), {"V": 15, "O": 32})

        vac_struc = vac.generate_defect_structure(3)
        self.assertEqual(vac_struc.composition.as_dict(), {"V": 53, "O": 108})

        vac_struc = vac.generate_defect_structure([[2., 0, 0], [0, 0, -3.], [0, 2., 0]])
        self.assertEqual(vac_struc.composition.as_dict(), {"V": 23, "O": 48})

        # test charge
        vac = Vacancy(struc, struc[V_index])
        vac_struc = vac.generate_defect_structure(1)
        self.assertEqual(vac_struc.charge, 0.0)

        vac = Vacancy(struc, struc[V_index], charge=1.0)
        vac_struc = vac.generate_defect_structure(1)
        self.assertEqual(vac_struc.charge, 1.0)

        vac = Vacancy(struc, struc[V_index], charge=-1.0)
        vac_struc = vac.generate_defect_structure(1)
        self.assertEqual(vac_struc.charge, -1.0)

        # test multiplicity
        vac = Vacancy(struc, struc[V_index])
        self.assertEqual(vac.multiplicity, 2)

        O_index = struc.indices_from_symbol("O")[0]
        vac = Vacancy(struc, struc[O_index])
        self.assertEqual(vac.multiplicity, 4)

        # Test composition
        self.assertEqual(dict(vac.defect_composition.as_dict()), {"V": 2, "O": 3})

        # test lattice value error occurs for different lattices
        sc_scaled_struc = struc.copy()
        sc_scaled_struc.make_supercell(2)
        self.assertRaises( ValueError, Vacancy, struc, sc_scaled_struc[V_index])
        self.assertRaises( ValueError, Vacancy, sc_scaled_struc, struc[V_index])

        # test value error raised for site not in the structure
        non_site = PeriodicSite( "V", struc[V_index].frac_coords + [0., 0., .1], struc.lattice)
        self.assertRaises( ValueError, Vacancy, struc, non_site)
Exemple #4
0
    def test_convert_cd_to_de(self):
        #create a ComputedDefect object similar to legacy format
        # Vacancy type first
        struc = PymatgenTest.get_structure("VO2")
        struc.make_supercell(3)
        vac = Vacancy(struc, struc.sites[0], charge=-3)
        ids = vac.generate_defect_structure(1)
        defect_data = {
            "locpot_path": "defect/path/to/files/LOCPOT",
            "encut": 520
        }
        bulk_data = {"locpot_path": "bulk/path/to/files/LOCPOT"}

        cse_defect = ComputedStructureEntry(ids, 100., data=defect_data)
        cd = ComputedDefect(cse_defect,
                            struc.sites[0],
                            charge=-3,
                            name="Vac_1_O")
        b_cse = ComputedStructureEntry(struc, 10., data=bulk_data)

        de = convert_cd_to_de(cd, b_cse)
        self.assertIsInstance(de.defect, Vacancy)
        self.assertIsInstance(de, DefectEntry)
        self.assertEqual(de.parameters["defect_path"], "defect/path/to/files")
        self.assertEqual(de.parameters["bulk_path"], "bulk/path/to/files")
        self.assertEqual(de.parameters["encut"], 520)
        self.assertEqual(de.site.specie.symbol, "O")

        # try again for substitution type
        # (site object had bulk specie for ComputedDefects,
        # while it should have substituional site specie for DefectEntrys...)
        de_site_type = PeriodicSite("Sb", vac.site.frac_coords, struc.lattice)
        sub = Substitution(struc, de_site_type, charge=1)
        ids = sub.generate_defect_structure(1)

        cse_defect = ComputedStructureEntry(ids, 100., data=defect_data)
        cd = ComputedDefect(cse_defect,
                            struc.sites[0],
                            charge=1,
                            name="Sub_1_Sb_on_O")

        de = convert_cd_to_de(cd, b_cse)

        self.assertIsInstance(de.defect, Substitution)
        self.assertIsInstance(de, DefectEntry)
        self.assertEqual(de.site.specie.symbol, "Sb")
Exemple #5
0
    def test_vacancy(self):
        struc = PymatgenTest.get_structure("VO2")
        V_index = struc.indices_from_symbol("V")[0]
        vac = Vacancy(struc, struc[V_index])

        # test generation and super cell
        vac_struc = vac.generate_defect_structure(1)
        self.assertEqual(vac_struc.composition.as_dict(), {"V": 1, "O": 4})

        vac_struc = vac.generate_defect_structure(2)
        self.assertEqual(vac_struc.composition.as_dict(), {"V": 15, "O": 32})

        vac_struc = vac.generate_defect_structure(3)
        self.assertEqual(vac_struc.composition.as_dict(), {"V": 53, "O": 108})

        # test charge
        vac = Vacancy(struc, struc[V_index])
        vac_struc = vac.generate_defect_structure(1)
        self.assertEqual(vac_struc.charge, 0.0)

        vac = Vacancy(struc, struc[V_index], charge=1.0)
        vac_struc = vac.generate_defect_structure(1)
        self.assertEqual(vac_struc.charge, 1.0)

        vac = Vacancy(struc, struc[V_index], charge=-1.0)
        vac_struc = vac.generate_defect_structure(1)
        self.assertEqual(vac_struc.charge, -1.0)

        # test multiplicity
        vac = Vacancy(struc, struc[V_index])
        self.assertEqual(vac.multiplicity, 2)

        O_index = struc.indices_from_symbol("O")[0]
        vac = Vacancy(struc, struc[O_index])
        self.assertEqual(vac.multiplicity, 4)

        # Test composoition
        self.assertEqual(dict(vac.defect_composition.as_dict()), {
            "V": 2,
            "O": 3
        })
Exemple #6
0
def vac_antisite_def_struct_gen(mpid, mapi_key, cellmax, struct_file=None):
    if not mpid and not struct_file:
        print ("============\nERROR: Provide an mpid\n============")
        return

    # Get primitive structure from the Materials Project DB
    if not struct_file:
        if not mapi_key:
            with MPRester() as mp:
                struct = mp.get_structure_by_material_id(mpid)
        else:
            with MPRester(mapi_key) as mp:
                struct = mp.get_structure_by_material_id(mpid)
    else:
        struct = Structure.from_file(struct_file)

    sga = SpacegroupAnalyzer(struct)
    prim_struct = sga.find_primitive()
    #prim_struct_sites = len(prim_struct.sites)
    #conv_struct = sga.get_conventional_standard_structure()
    #conv_struct_sites = len(conv_struct.sites)
    #conv_prim_ratio = int(conv_struct_sites / prim_struct_sites)

    # Default VASP settings
    def_vasp_incar_param = {'ISIF':2, 'EDIFF':1e-6, 'EDIFFG':0.001,}
    kpoint_den = 15000

    # Create bulk structure and associated VASP files
    sc_scale = get_sc_scale(inp_struct=prim_struct, final_site_no=cellmax)
    blk_sc = prim_struct.copy()
    blk_sc.make_supercell(scaling_matrix=sc_scale)
    site_no = blk_sc.num_sites

    # Rescale if needed
    while site_no > cellmax:
        max_sc_dim = max(sc_scale)
        i = sc_scale.index(max_sc_dim)
        sc_scale[i] -= 1
        blk_sc = prim_struct.copy()
        blk_sc.make_supercell(scaling_matrix=sc_scale)
        site_no = blk_sc.num_sites
    
    blk_str_sites = set(blk_sc.sites)
    custom_kpoints = Kpoints.automatic_density(blk_sc, kppa=kpoint_den)
    mpvis = MPMetalRelaxSet(blk_sc, user_incar_settings=def_vasp_incar_param,
                            user_kpoints_settings=custom_kpoints)

    if mpid:
        root_fldr = mpid
    else:
        root_fldr = struct.composition.reduced_formula

    fin_dir = os.path.join(root_fldr, 'bulk')
    mpvis.write_input(fin_dir)
    if not mpid:    # write the input structure if mpid is not used
        struct.to(fmt='poscar', filename=os.path.join(fin_dir, 'POSCAR.uc'))

    # Create each defect structure and associated VASP files
    # First find all unique defect sites
    periodic_struct = sga.get_symmetrized_structure()
    unique_sites = list(set([periodic_struct.find_equivalent_sites(site)[0] \
                             for site in periodic_struct.sites]))
    temp_struct = Structure.from_sites(sorted(unique_sites))
    prim_struct2 = SpacegroupAnalyzer(temp_struct).find_primitive()
    prim_struct2.lattice = prim_struct.lattice  # a little hacky
    for i, site in enumerate(prim_struct2.sites):
        vac = Vacancy(structure=prim_struct, defect_site=site)
        vac_sc = vac.generate_defect_structure(supercell=sc_scale)

        # Get vacancy site information
        vac_str_sites = set(vac_sc.sites)
        vac_sites = blk_str_sites - vac_str_sites
        vac_site = next(iter(vac_sites))
        site_mult = vac.get_multiplicity()
        vac_site_specie = vac_site.specie
        vac_symbol = vac_site_specie.symbol

        custom_kpoints = Kpoints.automatic_density(vac_sc, kppa=kpoint_den)
        mpvis = MPMetalRelaxSet(vac_sc,
                                user_incar_settings=def_vasp_incar_param,
                                user_kpoints_settings=custom_kpoints)
        vac_dir = 'vacancy_{}_mult-{}_sitespecie-{}'.format(
                    str(i+1), site_mult, vac_symbol)
        fin_dir = os.path.join(root_fldr, vac_dir)
        mpvis.write_input(fin_dir)

        # Antisites generation at the vacancy site
        struct_species = blk_sc.species
        for specie in set(struct_species) - set([vac_site_specie]):
            specie_symbol = specie.symbol
            anti_sc = vac_sc.copy()
            anti_sc.append(specie, vac_site.frac_coords)
            mpvis = MPMetalRelaxSet(anti_sc,
                                    user_incar_settings=def_vasp_incar_param,
                                    user_kpoints_settings=custom_kpoints)
            anti_dir = 'antisite_{}_mult-{}_sitespecie-{}_subspecie-{}'.format(
                        str(i+1), site_mult, vac_symbol, specie_symbol)
            fin_dir = os.path.join(root_fldr, anti_dir)
            mpvis.write_input(fin_dir)
class DefectCompatibilityTest(PymatgenTest):
    def setUp(self):
        struc = PymatgenTest.get_structure("VO2")
        struc.make_supercell(3)
        struc = struc
        self.vac = Vacancy(struc, struc.sites[0], charge=-3)

        abc = self.vac.bulk_structure.lattice.abc
        axisdata = [np.arange(0.0, lattval, 0.2) for lattval in abc]
        bldata = [np.array([1.0 for u in np.arange(0.0, lattval, 0.2)]) for lattval in abc]
        dldata = [
            np.array([(-1 - np.cos(2 * np.pi * u / lattval)) for u in np.arange(0.0, lattval, 0.2)]) for lattval in abc
        ]
        self.frey_params = {
            "axis_grid": axisdata,
            "bulk_planar_averages": bldata,
            "defect_planar_averages": dldata,
            "dielectric": 15,
            "initial_defect_structure": struc.copy(),
            "defect_frac_sc_coords": struc.sites[0].frac_coords[:],
        }

        kumagai_bulk_struc = Poscar.from_file(
            os.path.join(PymatgenTest.TEST_FILES_DIR, "defect", "CONTCAR_bulk")
        ).structure
        bulk_out = Outcar(os.path.join(PymatgenTest.TEST_FILES_DIR, "defect", "OUTCAR_bulk.gz"))
        defect_out = Outcar(os.path.join(PymatgenTest.TEST_FILES_DIR, "defect", "OUTCAR_vac_Ga_-3.gz"))
        self.kumagai_vac = Vacancy(kumagai_bulk_struc, kumagai_bulk_struc.sites[0], charge=-3)
        kumagai_defect_structure = self.kumagai_vac.generate_defect_structure()
        self.kumagai_params = {
            "bulk_atomic_site_averages": bulk_out.electrostatic_potential,
            "defect_atomic_site_averages": defect_out.electrostatic_potential,
            "site_matching_indices": [[ind, ind - 1] for ind in range(len(kumagai_bulk_struc))],
            "defect_frac_sc_coords": [0.0, 0.0, 0.0],
            "initial_defect_structure": kumagai_defect_structure,
            "dielectric": 18.118 * np.identity(3),
            "gamma": 0.153156,  # not neccessary to load gamma, but speeds up unit test
        }

        v = Vasprun(os.path.join(PymatgenTest.TEST_FILES_DIR, "vasprun.xml"))
        eigenvalues = v.eigenvalues.copy()
        kptweights = v.actual_kpoints_weights
        potalign = -0.1
        vbm = v.eigenvalue_band_properties[2]
        cbm = v.eigenvalue_band_properties[1]
        defect_incar = v.incar
        self.bandfill_params = {
            "eigenvalues": eigenvalues,
            "kpoint_weights": kptweights,
            "potalign": potalign,
            "vbm": vbm,
            "cbm": cbm,
            "run_metadata": {"defect_incar": defect_incar},
        }

        self.band_edge_params = {
            "hybrid_cbm": 1.0,
            "hybrid_vbm": -1.0,
            "vbm": -0.5,
            "cbm": 0.6,
            "num_hole_vbm": 1.0,
            "num_elec_cbm": 1.0,
        }

    def test_process_entry(self):

        # basic process with no corrections
        dentry = DefectEntry(
            self.vac,
            0.0,
            corrections={},
            parameters={"vbm": 0.0, "cbm": 0.0},
            entry_id=None,
        )
        dc = DefectCompatibility()
        dentry = dc.process_entry(dentry)
        self.assertIsNotNone(dentry)

        # process with corrections from parameters used in other unit tests
        params = self.frey_params.copy()
        params.update(self.bandfill_params)
        params.update(
            {
                "hybrid_cbm": params["cbm"] + 0.2,
                "hybrid_vbm": params["vbm"] - 0.4,
            }
        )
        dentry = DefectEntry(self.vac, 0.0, corrections={}, parameters=params, entry_id=None)
        dc = DefectCompatibility()
        dentry = dc.process_entry(dentry)
        self.assertAlmostEqual(dentry.corrections["bandedgeshifting_correction"], 1.2)
        self.assertAlmostEqual(dentry.corrections["bandfilling_correction"], 0.0)
        self.assertAlmostEqual(dentry.corrections["charge_correction"], 5.44595036)

        # test over delocalized free carriers which forces skipping charge correction
        params = self.bandfill_params.copy()  # No Freysoldt metadata
        params.update(
            {
                "hybrid_cbm": params["cbm"] + 0.2,
                "hybrid_vbm": params["vbm"] - 0.4,
            }
        )
        # modify the eigenvalue list to have free holes
        hole_eigenvalues = {}
        for spinkey, spinset in params["eigenvalues"].items():
            hole_eigenvalues[spinkey] = []
            for kptset in spinset:
                hole_eigenvalues[spinkey].append([])
                for eig in kptset:
                    if (eig[0] < params["vbm"]) and (eig[0] > params["vbm"] - 0.8):
                        hole_eigenvalues[spinkey][-1].append([eig[0], 0.5])
                    else:
                        hole_eigenvalues[spinkey][-1].append(eig)

        params.update({"eigenvalues": hole_eigenvalues})
        dentry = DefectEntry(self.vac, 0.0, corrections={}, parameters=params, entry_id=None)
        dc = DefectCompatibility(free_chg_cutoff=0.8)
        dentry = dc.process_entry(dentry)
        self.assertAlmostEqual(dentry.corrections["bandedgeshifting_correction"], 1.19999999)
        self.assertAlmostEqual(dentry.corrections["bandfilling_correction"], -0.492633372744)
        self.assertAlmostEqual(dentry.corrections["charge_correction"], 0.0)

        # turn off band filling and band edge shifting
        dc = DefectCompatibility(free_chg_cutoff=0.8, use_bandfilling=False, use_bandedgeshift=False)
        dentry = dc.process_entry(dentry)
        self.assertAlmostEqual(dentry.corrections["bandedgeshifting_correction"], 0.0)
        self.assertAlmostEqual(dentry.corrections["bandfilling_correction"], 0.0)
        self.assertAlmostEqual(dentry.corrections["charge_correction"], 0.0)

    def test_perform_all_corrections(self):

        # return entry even if insufficent values are provided
        # for freysoldt, kumagai, bandfilling, or band edge shifting
        de = DefectEntry(self.vac, 0.0, corrections={}, parameters={}, entry_id=None)
        dc = DefectCompatibility()
        dentry = dc.perform_all_corrections(de)
        self.assertIsNotNone(dentry)
        # all other correction applications are tested in unit tests below

    def test_perform_freysoldt(self):
        de = DefectEntry(self.vac, 0.0, corrections={}, parameters=self.frey_params, entry_id=None)
        dc = DefectCompatibility()
        dentry = dc.perform_freysoldt(de)

        val = dentry.parameters["freysoldt_meta"]
        self.assertAlmostEqual(val["freysoldt_electrostatic"], 0.975893)
        self.assertAlmostEqual(val["freysoldt_potential_alignment_correction"], 4.4700574)
        self.assertAlmostEqual(val["freysoldt_potalign"], 1.4900191)
        self.assertTrue("pot_corr_uncertainty_md" in val.keys())
        self.assertTrue("pot_plot_data" in val.keys())

    def test_perform_kumagai(self):
        de = DefectEntry(self.kumagai_vac, 0.0, parameters=self.kumagai_params)
        dc = DefectCompatibility()
        dentry = dc.perform_kumagai(de)

        val = dentry.parameters["kumagai_meta"]
        self.assertAlmostEqual(val["kumagai_electrostatic"], 0.88236299)
        self.assertAlmostEqual(val["kumagai_potential_alignment_correction"], 2.09704862)
        self.assertAlmostEqual(val["kumagai_potalign"], 0.69901620)
        self.assertTrue("pot_corr_uncertainty_md" in val.keys())
        self.assertTrue("pot_plot_data" in val.keys())

    def test_run_bandfilling(self):
        de = DefectEntry(
            self.vac,
            0.0,
            corrections={},
            parameters=self.bandfill_params,
            entry_id=None,
        )
        dc = DefectCompatibility()
        dentry = dc.perform_bandfilling(de)

        val = dentry.parameters["bandfilling_meta"]
        self.assertAlmostEqual(val["num_hole_vbm"], 0.0)
        self.assertAlmostEqual(val["num_elec_cbm"], 0.0)
        self.assertAlmostEqual(val["bandfilling_correction"], 0.0)

    def test_run_band_edge_shifting(self):
        de = DefectEntry(
            self.vac,
            0.0,
            corrections={},
            parameters=self.band_edge_params,
            entry_id=None,
        )

        dc = DefectCompatibility()
        dentry = dc.perform_band_edge_shifting(de)
        val = dentry.parameters["bandshift_meta"]
        self.assertEqual(val["vbmshift"], -0.5)
        self.assertEqual(val["cbmshift"], 0.4)
        self.assertEqual(val["bandedgeshifting_correction"], 1.5)

    def test_delocalization_analysis(self):
        # return entry even if insufficent values are provided
        # for delocalization analysis with freysoldt, kumagai,
        # bandfilling, or band edge shifting
        de = DefectEntry(self.vac, 0.0, corrections={}, parameters={}, entry_id=None)
        dc = DefectCompatibility()
        dentry = dc.delocalization_analysis(de)
        self.assertIsNotNone(dentry)
        # all other correction applications are tested in unit tests below

    def test_check_freysoldt_delocalized(self):
        de = DefectEntry(self.vac, 0.0, corrections={}, parameters=self.frey_params, entry_id=None)
        de.parameters.update({"is_compatible": True})  # needs to be initialized with this here for unittest
        dc = DefectCompatibility(plnr_avg_var_tol=0.1, plnr_avg_minmax_tol=0.5)
        dentry = dc.perform_freysoldt(de)

        # check case which fits under compatibility constraints
        dentry = dc.check_freysoldt_delocalized(dentry)
        frey_delocal = dentry.parameters["delocalization_meta"]["plnr_avg"]
        self.assertTrue(frey_delocal["is_compatible"])
        ans_var = [0.00038993, 0.02119532, 0.02119532]
        ans_window = [0.048331509, 0.36797169, 0.36797169]
        for ax in range(3):
            ax_metadata = frey_delocal["metadata"][ax]
            self.assertTrue(ax_metadata["frey_variance_compatible"])
            self.assertAlmostEqual(ax_metadata["frey_variance"], ans_var[ax])
            self.assertTrue(ax_metadata["frey_minmax_compatible"])
            self.assertAlmostEqual(ax_metadata["frey_minmax_window"], ans_window[ax])

        self.assertTrue(dentry.parameters["is_compatible"])

        # check planar delocalization on 2nd and 3rd axes
        dc = DefectCompatibility(plnr_avg_var_tol=0.1, plnr_avg_minmax_tol=0.2)
        dentry.parameters.update({"is_compatible": True})
        dentry = dc.check_freysoldt_delocalized(dentry)
        frey_delocal = dentry.parameters["delocalization_meta"]["plnr_avg"]
        self.assertFalse(frey_delocal["is_compatible"])
        ax_metadata = frey_delocal["metadata"][0]
        self.assertTrue(ax_metadata["frey_variance_compatible"])
        self.assertTrue(ax_metadata["frey_minmax_compatible"])
        for ax in [1, 2]:
            ax_metadata = frey_delocal["metadata"][ax]
            self.assertTrue(ax_metadata["frey_variance_compatible"])
            self.assertFalse(ax_metadata["frey_minmax_compatible"])

        self.assertFalse(dentry.parameters["is_compatible"])

        # check variance based delocalization on 2nd and 3rd axes
        dc = DefectCompatibility(plnr_avg_var_tol=0.01, plnr_avg_minmax_tol=0.5)
        dentry.parameters.update({"is_compatible": True})
        dentry = dc.check_freysoldt_delocalized(dentry)
        frey_delocal = dentry.parameters["delocalization_meta"]["plnr_avg"]
        self.assertFalse(frey_delocal["is_compatible"])
        ax_metadata = frey_delocal["metadata"][0]
        self.assertTrue(ax_metadata["frey_variance_compatible"])
        self.assertTrue(ax_metadata["frey_minmax_compatible"])
        for ax in [1, 2]:
            ax_metadata = frey_delocal["metadata"][ax]
            self.assertFalse(ax_metadata["frey_variance_compatible"])
            self.assertTrue(ax_metadata["frey_minmax_compatible"])

        self.assertFalse(dentry.parameters["is_compatible"])

    def test_check_kumagai_delocalized(self):
        de = DefectEntry(self.kumagai_vac, 0.0, parameters=self.kumagai_params)
        de.parameters.update({"is_compatible": True})  # needs to be initialized with this here for unittest
        dc = DefectCompatibility(atomic_site_var_tol=13.3, atomic_site_minmax_tol=20.95)
        dentry = dc.perform_kumagai(de)

        # check case which fits under compatibility constraints
        dentry = dc.check_kumagai_delocalized(dentry)
        kumagai_delocal = dentry.parameters["delocalization_meta"]["atomic_site"]
        self.assertTrue(kumagai_delocal["is_compatible"])
        kumagai_md = kumagai_delocal["metadata"]
        true_variance = 13.262304401193997
        true_minmax = 20.9435
        self.assertTrue(kumagai_md["kumagai_variance_compatible"])
        self.assertAlmostEqual(kumagai_md["kumagai_variance"], true_variance)
        self.assertTrue(kumagai_md["kumagai_minmax_compatible"])
        self.assertAlmostEqual(kumagai_md["kumagai_minmax_window"], true_minmax)

        self.assertTrue(dentry.parameters["is_compatible"])

        # break variable compatibility
        dc = DefectCompatibility(atomic_site_var_tol=0.1, atomic_site_minmax_tol=20.95)
        de.parameters.update({"is_compatible": True})
        dentry = dc.perform_kumagai(de)
        dentry = dc.check_kumagai_delocalized(dentry)
        kumagai_delocal = dentry.parameters["delocalization_meta"]["atomic_site"]
        self.assertFalse(kumagai_delocal["is_compatible"])
        kumagai_md = kumagai_delocal["metadata"]
        self.assertFalse(kumagai_md["kumagai_variance_compatible"])
        self.assertAlmostEqual(kumagai_md["kumagai_variance"], true_variance)
        self.assertTrue(kumagai_md["kumagai_minmax_compatible"])
        self.assertAlmostEqual(kumagai_md["kumagai_minmax_window"], true_minmax)

        self.assertFalse(dentry.parameters["is_compatible"])

        # break maxmin compatibility
        dc = DefectCompatibility(atomic_site_var_tol=13.3, atomic_site_minmax_tol=0.5)
        de.parameters.update({"is_compatible": True})
        dentry = dc.perform_kumagai(de)
        dentry = dc.check_kumagai_delocalized(dentry)
        kumagai_delocal = dentry.parameters["delocalization_meta"]["atomic_site"]
        self.assertFalse(kumagai_delocal["is_compatible"])
        kumagai_md = kumagai_delocal["metadata"]
        self.assertTrue(kumagai_md["kumagai_variance_compatible"])
        self.assertAlmostEqual(kumagai_md["kumagai_variance"], true_variance)
        self.assertFalse(kumagai_md["kumagai_minmax_compatible"])
        self.assertAlmostEqual(kumagai_md["kumagai_minmax_window"], true_minmax)

        self.assertFalse(dentry.parameters["is_compatible"])

    def test_check_final_relaxed_structure_delocalized(self):
        # test structure delocalization analysis
        # first test no movement in atoms
        initial_defect_structure = self.vac.generate_defect_structure()
        final_defect_structure = initial_defect_structure.copy()
        sampling_radius = 4.55
        defect_frac_sc_coords = self.vac.site.frac_coords[:]

        params = {
            "initial_defect_structure": initial_defect_structure,
            "final_defect_structure": final_defect_structure,
            "sampling_radius": sampling_radius,
            "defect_frac_sc_coords": defect_frac_sc_coords,
            "is_compatible": True,
        }
        dentry = DefectEntry(self.vac, 0.0, corrections={}, parameters=params, entry_id=None)

        dc = DefectCompatibility(tot_relax_tol=0.1, perc_relax_tol=0.1, defect_tot_relax_tol=0.1)
        dentry = dc.check_final_relaxed_structure_delocalized(dentry)

        struc_delocal = dentry.parameters["delocalization_meta"]["structure_relax"]
        self.assertTrue(dentry.parameters["is_compatible"])
        self.assertTrue(struc_delocal["is_compatible"])
        self.assertTrue(struc_delocal["metadata"]["structure_tot_relax_compatible"])
        self.assertEqual(struc_delocal["metadata"]["tot_relax_outside_rad"], 0.0)
        self.assertTrue(struc_delocal["metadata"]["structure_perc_relax_compatible"])
        self.assertEqual(struc_delocal["metadata"]["perc_relax_outside_rad"], 0.0)
        self.assertEqual(
            len(struc_delocal["metadata"]["full_structure_relax_data"]),
            len(initial_defect_structure),
        )
        self.assertIsNone(struc_delocal["metadata"]["defect_index"])

        defect_delocal = dentry.parameters["delocalization_meta"]["defectsite_relax"]
        self.assertTrue(defect_delocal["is_compatible"])
        self.assertIsNone(defect_delocal["metadata"]["relax_amount"])

        # next test for when structure has delocalized outside of radius from defect
        pert_struct_fin_struct = initial_defect_structure.copy()
        pert_struct_fin_struct.perturb(0.1)
        dentry.parameters.update({"final_defect_structure": pert_struct_fin_struct})
        dentry = dc.check_final_relaxed_structure_delocalized(dentry)

        struc_delocal = dentry.parameters["delocalization_meta"]["structure_relax"]
        self.assertFalse(dentry.parameters["is_compatible"])
        self.assertFalse(struc_delocal["is_compatible"])
        self.assertFalse(struc_delocal["metadata"]["structure_tot_relax_compatible"])
        self.assertAlmostEqual(struc_delocal["metadata"]["tot_relax_outside_rad"], 12.5)
        self.assertFalse(struc_delocal["metadata"]["structure_perc_relax_compatible"])
        self.assertAlmostEqual(struc_delocal["metadata"]["perc_relax_outside_rad"], 77.63975155)

        # now test for when an interstitial defect has migrated too much
        inter_def_site = PeriodicSite(
            "H",
            [7.58857304, 11.70848069, 12.97817518],
            self.vac.bulk_structure.lattice,
            to_unit_cell=True,
            coords_are_cartesian=True,
        )
        inter = Interstitial(self.vac.bulk_structure, inter_def_site, charge=0)

        initial_defect_structure = inter.generate_defect_structure()
        final_defect_structure = initial_defect_structure.copy()
        poss_deflist = sorted(
            final_defect_structure.get_sites_in_sphere(inter.site.coords, 2, include_index=True),
            key=lambda x: x[1],
        )
        def_index = poss_deflist[0][2]
        final_defect_structure.translate_sites(
            indices=[def_index], vector=[0.0, 0.0, 0.008]
        )  # fractional coords translation
        defect_frac_sc_coords = inter_def_site.frac_coords[:]

        params = {
            "initial_defect_structure": initial_defect_structure,
            "final_defect_structure": final_defect_structure,
            "sampling_radius": sampling_radius,
            "defect_frac_sc_coords": defect_frac_sc_coords,
            "is_compatible": True,
        }
        dentry = DefectEntry(inter, 0.0, corrections={}, parameters=params, entry_id=None)

        dentry = dc.check_final_relaxed_structure_delocalized(dentry)

        defect_delocal = dentry.parameters["delocalization_meta"]["defectsite_relax"]
        self.assertFalse(defect_delocal["is_compatible"])
        self.assertAlmostEqual(defect_delocal["metadata"]["relax_amount"], 0.10836054)

    def test_bandfilling_SOC_calc(self):
        v = Vasprun(os.path.join(PymatgenTest.TEST_FILES_DIR, "vasprun.xml.int_Te_SOC.gz"))
        struc = v.structures[0]
        interstitial = Interstitial(struc, struc.sites[-1], charge=-2)
        eigenvalues = v.eigenvalues.copy()
        kptweights = v.actual_kpoints_weights
        potalign = -0.1
        defect_incar = v.incar

        bandfill_params = {
            "eigenvalues": eigenvalues,
            "kpoint_weights": kptweights,
            "potalign": potalign,
            "vbm": 1.6465,  # bulk VBM
            "cbm": 3.1451,  # bulk CBM
            "run_metadata": {"defect_incar": defect_incar},
        }

        soc_dentry = DefectEntry(
            interstitial,
            0.0,
            corrections={},
            parameters=bandfill_params,
            entry_id=None,
        )
        dc = DefectCompatibility()
        soc_dentry = dc.process_entry(soc_dentry)

        self.assertAlmostEqual(soc_dentry.corrections["bandfilling_correction"], -1.9628402187500003)
    def test_freysoldt(self):
        struc = PymatgenTest.get_structure("VO2")
        struc.make_supercell(3)
        struc = struc
        vac = Vacancy(struc, struc.sites[0], charge=-3)
        ids = vac.generate_defect_structure(1)

        abc = struc.lattice.abc
        axisdata = [np.arange(0.0, lattval, 0.2) for lattval in abc]
        bldata = [
            np.array([1.0 for u in np.arange(0.0, lattval, 0.2)]) for lattval in abc
        ]
        dldata = [
            np.array(
                [
                    (-1 - np.cos(2 * np.pi * u / lattval))
                    for u in np.arange(0.0, lattval, 0.2)
                ]
            )
            for lattval in abc
        ]
        params = {
            "axis_grid": axisdata,
            "bulk_planar_averages": bldata,
            "defect_planar_averages": dldata,
            "initial_defect_structure": ids,
            "defect_frac_sc_coords": struc.sites[0].frac_coords,
        }
        fc = FreysoldtCorrection(15)

        # test electrostatic correction
        es_corr = fc.perform_es_corr(struc.lattice, -3)
        self.assertAlmostEqual(es_corr, 0.975893)

        # test potential alignment method
        pot_corr = fc.perform_pot_corr(
            axisdata[0], bldata[0], dldata[0], struc.lattice, -3, vac.site.coords, 0
        )
        self.assertAlmostEqual(pot_corr, 2.836369987722345)

        # test entry full correction method
        de = DefectEntry(vac, 0.0, corrections={}, parameters=params, entry_id=None)
        val = fc.get_correction(de)
        self.assertAlmostEqual(val["freysoldt_electrostatic"], 0.975893)
        self.assertAlmostEqual(val["freysoldt_potential_alignment"], 4.4700574)

        # test the freysoldt plotter
        for ax in range(3):
            fcp = fc.plot(axis=ax)
            self.assertTrue(fcp)

        # check that uncertainty metadata exists
        for ax in range(3):
            self.assertAlmostEqual(
                set(fc.metadata["pot_corr_uncertainty_md"][ax].keys()),
                set(["potcorr", "stats"]),
            )

        # test a specified axis from entry
        fc = FreysoldtCorrection(15, axis=[1])
        val = fc.get_correction(de)
        self.assertAlmostEqual(val["freysoldt_potential_alignment"], 5.2869010593283132)

        # test a different charge
        #   for electrostatic correction
        es_corr = fc.perform_es_corr(struc.lattice, 2)
        self.assertAlmostEqual(es_corr, 0.43373)
        #   for potential alignment method
        pot_corr = fc.perform_pot_corr(
            axisdata[0], bldata[0], dldata[0], struc.lattice, 2, vac.site.coords, 0
        )
        self.assertAlmostEqual(pot_corr, -2.1375685936497768)

        # test an input anisotropic dielectric constant
        fc = FreysoldtCorrection([[1.0, 2.0, 3.0], [0.0, 3.0, 5.0], [4.0, 10.0, 8.0]])
        self.assertAlmostEqual(fc.dielectric, 4.0)
        val = fc.get_correction(de)
        self.assertAlmostEqual(val["freysoldt_electrostatic"], 3.659599)
        self.assertAlmostEqual(val["freysoldt_potential_alignment"], 3.3605255195745087)

        # test potalign being added to defect entry
        self.assertAlmostEqual(de.parameters["potalign"], 1.1201751731915028)

        # test that metadata entries exist in defect entry
        self.assertTrue("freysoldt_meta" in de.parameters.keys())
        self.assertAlmostEqual(
            set(de.parameters["freysoldt_meta"].keys()),
            set(["pot_plot_data", "pot_corr_uncertainty_md"]),
        )

        # test a charge of zero
        vac = Vacancy(struc, struc.sites[0], charge=0)
        de = DefectEntry(vac, 0.0, corrections={}, parameters=params, entry_id=None)
        val = fc.get_correction(de)
        self.assertAlmostEqual(val["freysoldt_electrostatic"], 0.0)
        self.assertAlmostEqual(val["freysoldt_potential_alignment"], 0.0)
    def test_kumagai(self):
        gamma = 0.19357221
        prec = 28
        lattice = Lattice(
            [[4.692882, -8.12831, 0.0], [4.692882, 8.12831, 0.0], [0.0, 0.0, 10.03391]]
        )

        # note that real/recip vector generation is not dependent on epsilon
        g_vecs, _, r_vecs, _ = generate_R_and_G_vecs(
            gamma, prec, lattice, 80.0 * np.identity(3)
        )

        # test real space summation (bigger for large epsilon)
        kc_high_diel = KumagaiCorrection(80.0 * np.identity(3), gamma=gamma)
        real_sum = kc_high_diel.get_real_summation(gamma, r_vecs[0])
        self.assertAlmostEqual(real_sum, 0.00843104)

        # test recip space summation (bigger for small epsilon)
        kc_low_diel = KumagaiCorrection(0.1 * np.identity(3), gamma=gamma)
        recip_sum = kc_low_diel.get_recip_summation(gamma, g_vecs[0], lattice.volume)
        self.assertAlmostEqual(recip_sum, 0.31117099)

        # test self interaction
        si_corr = kc_low_diel.get_self_interaction(gamma)
        self.assertAlmostEqual(si_corr, -0.54965249)

        # test potenital shift interaction correction
        ps_corr = kc_low_diel.get_potential_shift(gamma, lattice.volume)
        self.assertAlmostEqual(ps_corr, -0.00871593)

        # """Test Defect Entry approach to correction """
        bulk_struc = Poscar.from_file(
            os.path.join(PymatgenTest.TEST_FILES_DIR, "defect", "CONTCAR_bulk")
        ).structure
        bulk_out = Outcar(os.path.join(PymatgenTest.TEST_FILES_DIR, "defect", "OUTCAR_bulk.gz"))
        defect_out = Outcar(os.path.join(PymatgenTest.TEST_FILES_DIR, "defect", "OUTCAR_vac_Ga_-3.gz"))
        epsilon = 18.118 * np.identity(3)
        vac = Vacancy(bulk_struc, bulk_struc.sites[0], charge=-3)
        defect_structure = vac.generate_defect_structure()
        defect_frac_coords = [0.0, 0.0, 0.0]

        parameters = {
            "bulk_atomic_site_averages": bulk_out.electrostatic_potential,
            "defect_atomic_site_averages": defect_out.electrostatic_potential,
            "site_matching_indices": [[ind, ind - 1] for ind in range(len(bulk_struc))],
            "initial_defect_structure": defect_structure,
            "defect_frac_sc_coords": defect_frac_coords,
        }
        dentry = DefectEntry(vac, 0.0, parameters=parameters)
        kc = KumagaiCorrection(epsilon)
        kcorr = kc.get_correction(dentry)
        self.assertAlmostEqual(kcorr["kumagai_electrostatic"], 0.88236299)
        self.assertAlmostEqual(kcorr["kumagai_potential_alignment"], 2.09704862)

        # test ES correction
        high_diel_es_corr = kc_high_diel.perform_es_corr(gamma, prec, lattice, -3.0)
        self.assertAlmostEqual(high_diel_es_corr, 0.25176240)

        low_diel_es_corr = kc_low_diel.perform_es_corr(gamma, prec, lattice, -3.0)
        self.assertAlmostEqual(low_diel_es_corr, 201.28810966)

        # test pot correction
        site_list = []
        for bs_ind, ds_ind in dentry.parameters["site_matching_indices"]:
            Vqb = -(
                defect_out.electrostatic_potential[ds_ind]
                - bulk_out.electrostatic_potential[bs_ind]
            )
            site_list.append([defect_structure[ds_ind], Vqb])

        sampling_radius = dentry.parameters["kumagai_meta"]["sampling_radius"]
        gamma = dentry.parameters["kumagai_meta"]["gamma"]
        q = -3
        g_vecs, _, r_vecs, _ = generate_R_and_G_vecs(
            gamma, 28, defect_structure.lattice, np.identity(3)
        )
        high_diel_pot_corr = kc_high_diel.perform_pot_corr(
            defect_structure,
            defect_frac_coords,
            site_list,
            sampling_radius,
            q,
            r_vecs[0],
            g_vecs[0],
            gamma,
        )
        self.assertAlmostEqual(high_diel_pot_corr, 2.35840716)
        low_diel_pot_corr = kc_low_diel.perform_pot_corr(
            defect_structure,
            defect_frac_coords,
            site_list,
            sampling_radius,
            q,
            r_vecs[0],
            g_vecs[0],
            gamma,
        )
        self.assertAlmostEqual(low_diel_pot_corr, -58.83598095)

        # test the kumagai plotter
        kcp = kc.plot()
        self.assertTrue(kcp)

        # check that uncertainty metadata exists
        self.assertAlmostEqual(
            set(kc.metadata["pot_corr_uncertainty_md"].keys()),
            set(["number_sampled", "stats"]),
        )
def substitute_def_struct_gen(mpid, solute, mapi_key, cellmax,
                              struct_file=None):
    if not mpid and not struct_file:
        print ("============\nERROR: Provide an mpid\n============")
        return
    if not solute:
        print ("============\nERROR: Provide solute atom\n============")
        return

    # Get primitive structure from the Materials Project DB
    if not struct_file:
        if not mapi_key:
            with MPRester() as mp:
                struct = mp.get_structure_by_material_id(mpid)
        else:
            with MPRester(mapi_key) as mp:
                struct = mp.get_structure_by_material_id(mpid)
    else:
        struct = Structure.from_file(struct_file)

    if mpid:
        root_fldr = mpid
    else:
        root_fldr = struct.composition.reduced_formula

    sga = SpacegroupAnalyzer(struct)
    prim_struct = sga.find_primitive()
    #prim_struct_sites = len(prim_struct.sites)
    #conv_struct = sga.get_conventional_standard_structure()
    #conv_struct_sites = len(conv_struct.sites)
    #conv_prim_ratio = int(conv_struct_sites / prim_struct_sites)

    # Default VASP settings
    def_vasp_incar_param = {'ISIF':2, 'EDIFF':1e-6, 'EDIFFG':0.001,}
    kpoint_den = 15000

    # Create each substitutional defect structure and associated VASP files
    sc_scale = get_sc_scale(inp_struct=prim_struct, final_site_no=cellmax)
    blk_sc = prim_struct.copy()
    blk_sc.make_supercell(scaling_matrix=sc_scale)
    site_no = blk_sc.num_sites

    # Rescale if needed
    while site_no > cellmax:
        max_sc_dim = max(sc_scale)
        i = sc_scale.index(max_sc_dim)
        sc_scale[i] -= 1
        blk_sc = prim_struct.copy()
        blk_sc.make_supercell(scaling_matrix=sc_scale)
        site_no = blk_sc.num_sites

    # Create solute structures at vacancy sites
    # First find all unique defect sites
    blk_str_sites = set(blk_sc.sites)
    symm_struct = SpacegroupAnalyzer(prim_struct).get_symmetrized_structure()
    unique_sites = sorted([site[0] for site in symm_struct.equivalent_sites], \
                           key=lambda s: s.species_string)
    for i, site in enumerate(unique_sites):
        vac = Vacancy(structure=prim_struct, defect_site=site)
        vac_sc = vac.generate_defect_structure(supercell=sc_scale)

        # Get vacancy site information
        vac_str_sites = set(vac_sc.sites)
        vac_sites = blk_str_sites - vac_str_sites
        vac_site = next(iter(vac_sites))
        vac_specie = vac_site.specie.symbol
        site_mult = vac.get_multiplicity()

        # Solute substitution defect generation at the vacancy site
        solute_struct = vac_sc.copy()
        solute_struct.append(solute, vac_site.frac_coords)
        custom_kpoints = Kpoints.automatic_density(solute_struct,
                                                   kppa=kpoint_den)
        mpvis = MPMetalRelaxSet(solute_struct,
                                user_incar_settings=def_vasp_incar_param,
                                user_kpoints_settings=custom_kpoints)

        # Generate VASP directory
        sub_def_dir ='solute_{}_mult-{}_sitespecie-{}_subspecie-{}'.format(
                str(i+1), site_mult, vac_specie, solute)
        fin_dir = os.path.join(root_fldr, sub_def_dir)
        mpvis.write_input(fin_dir)
Exemple #11
0
class DefectCompatibilityTest(PymatgenTest):
    def setUp(self):
        struc = PymatgenTest.get_structure("VO2")
        struc.make_supercell(3)
        struc = struc
        self.vac = Vacancy(struc, struc.sites[0], charge=-3)

        abc = self.vac.bulk_structure.lattice.abc
        axisdata = [np.arange(0., lattval, 0.2) for lattval in abc]
        bldata = [
            np.array([1. for u in np.arange(0., lattval, 0.2)])
            for lattval in abc
        ]
        dldata = [
            np.array([(-1 - np.cos(2 * np.pi * u / lattval))
                      for u in np.arange(0., lattval, 0.2)]) for lattval in abc
        ]
        self.frey_params = {
            'axis_grid': axisdata,
            'bulk_planar_averages': bldata,
            'defect_planar_averages': dldata,
            'dielectric': 15,
            'initial_defect_structure': struc.copy(),
            'defect_frac_sc_coords': struc.sites[0].frac_coords[:]
        }

        kumagai_bulk_struc = Poscar.from_file(
            os.path.join(test_dir, 'defect', 'CONTCAR_bulk')).structure
        bulk_out = Outcar(os.path.join(test_dir, 'defect', 'OUTCAR_bulk.gz'))
        defect_out = Outcar(
            os.path.join(test_dir, 'defect', 'OUTCAR_vac_Ga_-3.gz'))
        self.kumagai_vac = Vacancy(kumagai_bulk_struc,
                                   kumagai_bulk_struc.sites[0],
                                   charge=-3)
        kumagai_defect_structure = self.kumagai_vac.generate_defect_structure()
        self.kumagai_params = {
            'bulk_atomic_site_averages':
            bulk_out.electrostatic_potential,
            'defect_atomic_site_averages':
            defect_out.electrostatic_potential,
            'site_matching_indices':
            [[ind, ind - 1] for ind in range(len(kumagai_bulk_struc))],
            'defect_frac_sc_coords': [0., 0., 0.],
            'initial_defect_structure':
            kumagai_defect_structure,
            'dielectric':
            18.118 * np.identity(3),
            'gamma':
            0.153156  #not neccessary to load gamma, but speeds up unit test
        }

        v = Vasprun(os.path.join(test_dir, 'vasprun.xml'))
        eigenvalues = v.eigenvalues.copy()
        kptweights = v.actual_kpoints_weights
        potalign = -0.1
        vbm = v.eigenvalue_band_properties[2]
        cbm = v.eigenvalue_band_properties[1]
        self.bandfill_params = {
            'eigenvalues': eigenvalues,
            'kpoint_weights': kptweights,
            'potalign': potalign,
            'vbm': vbm,
            'cbm': cbm
        }

        self.band_edge_params = {
            'hybrid_cbm': 1.,
            'hybrid_vbm': -1.,
            'vbm': -0.5,
            'cbm': 0.6,
            'num_hole_vbm': 1.,
            'num_elec_cbm': 1.
        }

    def test_process_entry(self):

        # basic process with no corrections
        dentry = DefectEntry(self.vac,
                             0.,
                             corrections={},
                             parameters={
                                 'vbm': 0.,
                                 'cbm': 0.
                             },
                             entry_id=None)
        dc = DefectCompatibility()
        dentry = dc.process_entry(dentry)
        self.assertIsNotNone(dentry)

        # process with corrections from parameters used in other unit tests
        params = self.frey_params.copy()
        params.update(self.bandfill_params)
        params.update({
            'hybrid_cbm': params['cbm'] + .2,
            'hybrid_vbm': params['vbm'] - .4,
        })
        dentry = DefectEntry(self.vac,
                             0.,
                             corrections={},
                             parameters=params,
                             entry_id=None)
        dc = DefectCompatibility()
        dentry = dc.process_entry(dentry)
        self.assertAlmostEqual(
            dentry.corrections['bandedgeshifting_correction'], 1.2)
        self.assertAlmostEqual(dentry.corrections['bandfilling_correction'],
                               0.0)
        self.assertAlmostEqual(dentry.corrections['charge_correction'],
                               5.44595036)

        # test over delocalized free carriers which forces skipping charge correction
        # modify the eigenvalue list to have free holes
        hole_eigenvalues = {}
        for spinkey, spinset in params['eigenvalues'].items():
            hole_eigenvalues[spinkey] = []
            for kptset in spinset:
                hole_eigenvalues[spinkey].append([])
                for eig in kptset:
                    if (eig[0] < params['vbm']) and (eig[0] >
                                                     params['vbm'] - .8):
                        hole_eigenvalues[spinkey][-1].append([eig[0], 0.5])
                    else:
                        hole_eigenvalues[spinkey][-1].append(eig)

        params.update({'eigenvalues': hole_eigenvalues})
        dentry = DefectEntry(self.vac,
                             0.,
                             corrections={},
                             parameters=params,
                             entry_id=None)
        dc = DefectCompatibility(free_chg_cutoff=0.8)
        dentry = dc.process_entry(dentry)
        self.assertAlmostEqual(
            dentry.corrections['bandedgeshifting_correction'], 1.19999999)
        self.assertAlmostEqual(dentry.corrections['bandfilling_correction'],
                               -1.62202400)
        self.assertAlmostEqual(dentry.corrections['charge_correction'], 0.)

        # turn off band filling and band edge shifting
        dc = DefectCompatibility(free_chg_cutoff=0.8,
                                 use_bandfilling=False,
                                 use_bandedgeshift=False)
        dentry = dc.process_entry(dentry)
        self.assertAlmostEqual(
            dentry.corrections['bandedgeshifting_correction'], 0.)
        self.assertAlmostEqual(dentry.corrections['bandfilling_correction'],
                               0.)
        self.assertAlmostEqual(dentry.corrections['charge_correction'], 0.)

    def test_perform_all_corrections(self):

        #return entry even if insufficent values are provided
        # for freysoldt, kumagai, bandfilling, or band edge shifting
        de = DefectEntry(self.vac,
                         0.,
                         corrections={},
                         parameters={},
                         entry_id=None)
        dc = DefectCompatibility()
        dentry = dc.perform_all_corrections(de)
        self.assertIsNotNone(dentry)
        #all other correction applications are tested in unit tests below

    def test_perform_freysoldt(self):
        de = DefectEntry(self.vac,
                         0.,
                         corrections={},
                         parameters=self.frey_params,
                         entry_id=None)
        dc = DefectCompatibility()
        dentry = dc.perform_freysoldt(de)

        val = dentry.parameters['freysoldt_meta']
        self.assertAlmostEqual(val['freysoldt_electrostatic'], 0.975893)
        self.assertAlmostEqual(val['freysoldt_potential_alignment_correction'],
                               4.4700574)
        self.assertAlmostEqual(val['freysoldt_potalign'], 1.4900191)
        self.assertTrue('pot_corr_uncertainty_md' in val.keys())
        self.assertTrue('pot_plot_data' in val.keys())

    def test_perform_kumagai(self):
        de = DefectEntry(self.kumagai_vac, 0., parameters=self.kumagai_params)
        dc = DefectCompatibility()
        dentry = dc.perform_kumagai(de)

        val = dentry.parameters['kumagai_meta']
        self.assertAlmostEqual(val['kumagai_electrostatic'], 0.88236299)
        self.assertAlmostEqual(val['kumagai_potential_alignment_correction'],
                               2.09704862)
        self.assertAlmostEqual(val['kumagai_potalign'], 0.69901620)
        self.assertTrue('pot_corr_uncertainty_md' in val.keys())
        self.assertTrue('pot_plot_data' in val.keys())

    def test_run_bandfilling(self):
        de = DefectEntry(self.vac,
                         0.,
                         corrections={},
                         parameters=self.bandfill_params,
                         entry_id=None)
        dc = DefectCompatibility()
        dentry = dc.perform_bandfilling(de)

        val = dentry.parameters['bandfilling_meta']
        self.assertAlmostEqual(val['num_hole_vbm'], 0.)
        self.assertAlmostEqual(val['num_elec_cbm'], 0.)
        self.assertAlmostEqual(val['bandfilling_correction'], 0.)

    def test_run_band_edge_shifting(self):
        de = DefectEntry(self.vac,
                         0.,
                         corrections={},
                         parameters=self.band_edge_params,
                         entry_id=None)

        dc = DefectCompatibility()
        dentry = dc.perform_band_edge_shifting(de)
        val = dentry.parameters['bandshift_meta']
        self.assertEqual(val['vbmshift'], -0.5)
        self.assertEqual(val['cbmshift'], 0.4)
        self.assertEqual(val['bandedgeshifting_correction'], 1.5)

    def test_delocalization_analysis(self):
        #return entry even if insufficent values are provided
        # for delocalization analysis with freysoldt, kumagai,
        # bandfilling, or band edge shifting
        de = DefectEntry(self.vac,
                         0.,
                         corrections={},
                         parameters={},
                         entry_id=None)
        dc = DefectCompatibility()
        dentry = dc.delocalization_analysis(de)
        self.assertIsNotNone(dentry)
        #all other correction applications are tested in unit tests below

    def test_check_freysoldt_delocalized(self):
        de = DefectEntry(self.vac,
                         0.,
                         corrections={},
                         parameters=self.frey_params,
                         entry_id=None)
        de.parameters.update(
            {'is_compatible':
             True})  #needs to be initialized with this here for unittest
        dc = DefectCompatibility(plnr_avg_var_tol=0.1, plnr_avg_minmax_tol=0.5)
        dentry = dc.perform_freysoldt(de)

        # check case which fits under compatibility constraints
        dentry = dc.check_freysoldt_delocalized(dentry)
        frey_delocal = dentry.parameters['delocalization_meta']['plnr_avg']
        self.assertTrue(frey_delocal['is_compatible'])
        ans_var = [0.00038993, 0.02119532, 0.02119532]
        ans_window = [0.048331509, 0.36797169, 0.36797169]
        for ax in range(3):
            ax_metadata = frey_delocal['metadata'][ax]
            self.assertTrue(ax_metadata['frey_variance_compatible'])
            self.assertAlmostEqual(ax_metadata['frey_variance'], ans_var[ax])
            self.assertTrue(ax_metadata['frey_minmax_compatible'])
            self.assertAlmostEqual(ax_metadata['frey_minmax_window'],
                                   ans_window[ax])

        self.assertTrue(dentry.parameters['is_compatible'])

        # check planar delocalization on 2nd and 3rd axes
        dc = DefectCompatibility(plnr_avg_var_tol=0.1, plnr_avg_minmax_tol=0.2)
        dentry.parameters.update({'is_compatible': True})
        dentry = dc.check_freysoldt_delocalized(dentry)
        frey_delocal = dentry.parameters['delocalization_meta']['plnr_avg']
        self.assertFalse(frey_delocal['is_compatible'])
        ax_metadata = frey_delocal['metadata'][0]
        self.assertTrue(ax_metadata['frey_variance_compatible'])
        self.assertTrue(ax_metadata['frey_minmax_compatible'])
        for ax in [1, 2]:
            ax_metadata = frey_delocal['metadata'][ax]
            self.assertTrue(ax_metadata['frey_variance_compatible'])
            self.assertFalse(ax_metadata['frey_minmax_compatible'])

        self.assertFalse(dentry.parameters['is_compatible'])

        # check variance based delocalization on 2nd and 3rd axes
        dc = DefectCompatibility(plnr_avg_var_tol=0.01,
                                 plnr_avg_minmax_tol=0.5)
        dentry.parameters.update({'is_compatible': True})
        dentry = dc.check_freysoldt_delocalized(dentry)
        frey_delocal = dentry.parameters['delocalization_meta']['plnr_avg']
        self.assertFalse(frey_delocal['is_compatible'])
        ax_metadata = frey_delocal['metadata'][0]
        self.assertTrue(ax_metadata['frey_variance_compatible'])
        self.assertTrue(ax_metadata['frey_minmax_compatible'])
        for ax in [1, 2]:
            ax_metadata = frey_delocal['metadata'][ax]
            self.assertFalse(ax_metadata['frey_variance_compatible'])
            self.assertTrue(ax_metadata['frey_minmax_compatible'])

        self.assertFalse(dentry.parameters['is_compatible'])

    def test_check_kumagai_delocalized(self):
        de = DefectEntry(self.kumagai_vac, 0., parameters=self.kumagai_params)
        de.parameters.update(
            {'is_compatible':
             True})  #needs to be initialized with this here for unittest
        dc = DefectCompatibility(atomic_site_var_tol=13.3,
                                 atomic_site_minmax_tol=20.95)
        dentry = dc.perform_kumagai(de)

        # check case which fits under compatibility constraints
        dentry = dc.check_kumagai_delocalized(dentry)
        kumagai_delocal = dentry.parameters['delocalization_meta'][
            'atomic_site']
        self.assertTrue(kumagai_delocal['is_compatible'])
        kumagai_md = kumagai_delocal['metadata']
        true_variance = 13.262304401193997
        true_minmax = 20.9435
        self.assertTrue(kumagai_md['kumagai_variance_compatible'])
        self.assertAlmostEqual(kumagai_md['kumagai_variance'], true_variance)
        self.assertTrue(kumagai_md['kumagai_minmax_compatible'])
        self.assertAlmostEqual(kumagai_md['kumagai_minmax_window'],
                               true_minmax)

        self.assertTrue(dentry.parameters['is_compatible'])

        # break variable compatibility
        dc = DefectCompatibility(atomic_site_var_tol=0.1,
                                 atomic_site_minmax_tol=20.95)
        de.parameters.update({'is_compatible': True})
        dentry = dc.perform_kumagai(de)
        dentry = dc.check_kumagai_delocalized(dentry)
        kumagai_delocal = dentry.parameters['delocalization_meta'][
            'atomic_site']
        self.assertFalse(kumagai_delocal['is_compatible'])
        kumagai_md = kumagai_delocal['metadata']
        self.assertFalse(kumagai_md['kumagai_variance_compatible'])
        self.assertAlmostEqual(kumagai_md['kumagai_variance'], true_variance)
        self.assertTrue(kumagai_md['kumagai_minmax_compatible'])
        self.assertAlmostEqual(kumagai_md['kumagai_minmax_window'],
                               true_minmax)

        self.assertFalse(dentry.parameters['is_compatible'])

        # break maxmin compatibility
        dc = DefectCompatibility(atomic_site_var_tol=13.3,
                                 atomic_site_minmax_tol=0.5)
        de.parameters.update({'is_compatible': True})
        dentry = dc.perform_kumagai(de)
        dentry = dc.check_kumagai_delocalized(dentry)
        kumagai_delocal = dentry.parameters['delocalization_meta'][
            'atomic_site']
        self.assertFalse(kumagai_delocal['is_compatible'])
        kumagai_md = kumagai_delocal['metadata']
        self.assertTrue(kumagai_md['kumagai_variance_compatible'])
        self.assertAlmostEqual(kumagai_md['kumagai_variance'], true_variance)
        self.assertFalse(kumagai_md['kumagai_minmax_compatible'])
        self.assertAlmostEqual(kumagai_md['kumagai_minmax_window'],
                               true_minmax)

        self.assertFalse(dentry.parameters['is_compatible'])

    def test_check_final_relaxed_structure_delocalized(self):
        # test structure delocalization analysis
        # first test no movement in atoms
        initial_defect_structure = self.vac.generate_defect_structure()
        final_defect_structure = initial_defect_structure.copy()
        sampling_radius = 4.55
        defect_frac_sc_coords = self.vac.site.frac_coords[:]

        params = {
            'initial_defect_structure': initial_defect_structure,
            'final_defect_structure': final_defect_structure,
            'sampling_radius': sampling_radius,
            'defect_frac_sc_coords': defect_frac_sc_coords,
            'is_compatible': True
        }
        dentry = DefectEntry(self.vac,
                             0.,
                             corrections={},
                             parameters=params,
                             entry_id=None)

        dc = DefectCompatibility(tot_relax_tol=0.1,
                                 perc_relax_tol=0.1,
                                 defect_tot_relax_tol=0.1)
        dentry = dc.check_final_relaxed_structure_delocalized(dentry)

        struc_delocal = dentry.parameters['delocalization_meta'][
            'structure_relax']
        self.assertTrue(dentry.parameters['is_compatible'])
        self.assertTrue(struc_delocal['is_compatible'])
        self.assertTrue(
            struc_delocal['metadata']['structure_tot_relax_compatible'])
        self.assertEqual(struc_delocal['metadata']['tot_relax_outside_rad'],
                         0.)
        self.assertTrue(
            struc_delocal['metadata']['structure_perc_relax_compatible'])
        self.assertEqual(struc_delocal['metadata']['perc_relax_outside_rad'],
                         0.)
        self.assertEqual(
            len(struc_delocal['metadata']['full_structure_relax_data']),
            len(initial_defect_structure))
        self.assertIsNone(struc_delocal['metadata']['defect_index'])

        defect_delocal = dentry.parameters['delocalization_meta'][
            'defectsite_relax']
        self.assertTrue(defect_delocal['is_compatible'])
        self.assertIsNone(defect_delocal['metadata']['relax_amount'])

        # next test for when structure has delocalized outside of radius from defect
        pert_struct_fin_struct = initial_defect_structure.copy()
        pert_struct_fin_struct.perturb(0.1)
        dentry.parameters.update(
            {'final_defect_structure': pert_struct_fin_struct})
        dentry = dc.check_final_relaxed_structure_delocalized(dentry)

        struc_delocal = dentry.parameters['delocalization_meta'][
            'structure_relax']
        self.assertFalse(dentry.parameters['is_compatible'])
        self.assertFalse(struc_delocal['is_compatible'])
        self.assertFalse(
            struc_delocal['metadata']['structure_tot_relax_compatible'])
        self.assertAlmostEqual(
            struc_delocal['metadata']['tot_relax_outside_rad'], 12.5)
        self.assertFalse(
            struc_delocal['metadata']['structure_perc_relax_compatible'])
        self.assertAlmostEqual(
            struc_delocal['metadata']['perc_relax_outside_rad'], 77.63975155)

        # now test for when an interstitial defect has migrated too much
        inter_def_site = PeriodicSite('H',
                                      [7.58857304, 11.70848069, 12.97817518],
                                      self.vac.bulk_structure.lattice,
                                      to_unit_cell=True,
                                      coords_are_cartesian=True)
        inter = Interstitial(self.vac.bulk_structure, inter_def_site, charge=0)

        initial_defect_structure = inter.generate_defect_structure()
        final_defect_structure = initial_defect_structure.copy()
        poss_deflist = sorted(final_defect_structure.get_sites_in_sphere(
            inter.site.coords, 2, include_index=True),
                              key=lambda x: x[1])
        def_index = poss_deflist[0][2]
        final_defect_structure.translate_sites(
            indices=[def_index],
            vector=[0., 0., 0.008])  #fractional coords translation
        defect_frac_sc_coords = inter_def_site.frac_coords[:]

        params = {
            'initial_defect_structure': initial_defect_structure,
            'final_defect_structure': final_defect_structure,
            'sampling_radius': sampling_radius,
            'defect_frac_sc_coords': defect_frac_sc_coords,
            'is_compatible': True
        }
        dentry = DefectEntry(inter,
                             0.,
                             corrections={},
                             parameters=params,
                             entry_id=None)

        dentry = dc.check_final_relaxed_structure_delocalized(dentry)

        defect_delocal = dentry.parameters['delocalization_meta'][
            'defectsite_relax']
        self.assertFalse(defect_delocal['is_compatible'])
        self.assertAlmostEqual(defect_delocal['metadata']['relax_amount'],
                               0.10836054)
class DefectCompatibilityTest(PymatgenTest):

    def setUp(self):
        struc = PymatgenTest.get_structure("VO2")
        struc.make_supercell(3)
        struc = struc
        self.vac = Vacancy(struc, struc.sites[0], charge=-3)

        abc = self.vac.bulk_structure.lattice.abc
        axisdata = [np.arange(0., lattval, 0.2) for lattval in abc]
        bldata = [np.array([1. for u in np.arange(0., lattval, 0.2)]) for lattval in abc]
        dldata = [
            np.array([(-1 - np.cos(2 * np.pi * u / lattval)) for u in np.arange(0., lattval, 0.2)]) for lattval in abc
        ]
        self.frey_params = {'axis_grid': axisdata, 'bulk_planar_averages': bldata,
                            'defect_planar_averages': dldata, 'dielectric': 15,
                            'initial_defect_structure': struc.copy(),
                            'defect_frac_sc_coords': struc.sites[0].frac_coords[:]}

        kumagai_bulk_struc = Poscar.from_file(os.path.join( test_dir, 'defect', 'CONTCAR_bulk')).structure
        bulk_out = Outcar( os.path.join( test_dir, 'defect', 'OUTCAR_bulk.gz'))
        defect_out = Outcar( os.path.join( test_dir, 'defect', 'OUTCAR_vac_Ga_-3.gz'))
        self.kumagai_vac = Vacancy(kumagai_bulk_struc, kumagai_bulk_struc.sites[0], charge=-3)
        kumagai_defect_structure = self.kumagai_vac.generate_defect_structure()
        self.kumagai_params = {'bulk_atomic_site_averages': bulk_out.electrostatic_potential,
                              'defect_atomic_site_averages': defect_out.electrostatic_potential,
                              'site_matching_indices': [[ind, ind-1] for ind in range(len(kumagai_bulk_struc))],
                              'defect_frac_sc_coords': [0.,0.,0.],
                              'initial_defect_structure': kumagai_defect_structure,
                              'dielectric': 18.118 * np.identity(3),
                               'gamma': 0.153156 #not neccessary to load gamma, but speeds up unit test
                               }

        v = Vasprun(os.path.join(test_dir, 'vasprun.xml'))
        eigenvalues = v.eigenvalues.copy()
        kptweights = v.actual_kpoints_weights
        potalign = -0.1
        vbm = v.eigenvalue_band_properties[2]
        cbm = v.eigenvalue_band_properties[1]
        self.bandfill_params = { 'eigenvalues': eigenvalues,
                                 'kpoint_weights': kptweights,
                                 'potalign': potalign,
                                 'vbm': vbm, 'cbm': cbm }

        self.band_edge_params = {'hybrid_cbm': 1., 'hybrid_vbm': -1., 'vbm': -0.5,
                                 'cbm': 0.6, 'num_hole_vbm': 1., 'num_elec_cbm': 1.}

    def test_process_entry(self):

        # basic process with no corrections
        dentry = DefectEntry(self.vac, 0., corrections={}, parameters={'vbm': 0., 'cbm': 0.}, entry_id=None)
        dc = DefectCompatibility()
        dentry = dc.process_entry( dentry)
        self.assertIsNotNone( dentry)

        # process with corrections from parameters used in other unit tests
        params = self.frey_params.copy()
        params.update(self.bandfill_params)
        params.update({'hybrid_cbm': params['cbm'] + .2, 'hybrid_vbm': params['vbm'] - .4, })
        dentry = DefectEntry(self.vac, 0., corrections={}, parameters=params, entry_id=None)
        dc = DefectCompatibility()
        dentry = dc.process_entry( dentry)
        self.assertAlmostEqual( dentry.corrections['bandedgeshifting_correction'], 1.2)
        self.assertAlmostEqual( dentry.corrections['bandfilling_correction'], 0.0)
        self.assertAlmostEqual( dentry.corrections['charge_correction'], 5.44595036)

        # test over delocalized free carriers which forces skipping charge correction
        # modify the eigenvalue list to have free holes
        hole_eigenvalues = {}
        for spinkey, spinset in params['eigenvalues'].items():
            hole_eigenvalues[spinkey] = []
            for kptset in spinset:
                hole_eigenvalues[spinkey].append([])
                for eig in kptset:
                    if (eig[0] < params['vbm']) and (eig[0] > params['vbm'] - .8):
                        hole_eigenvalues[spinkey][-1].append([eig[0], 0.5])
                    else:
                        hole_eigenvalues[spinkey][-1].append(eig)

        params.update( {'eigenvalues': hole_eigenvalues})
        dentry = DefectEntry(self.vac, 0., corrections={}, parameters=params, entry_id=None)
        dc = DefectCompatibility( free_chg_cutoff=0.8)
        dentry = dc.process_entry( dentry)
        self.assertAlmostEqual( dentry.corrections['bandedgeshifting_correction'], 1.19999999)
        self.assertAlmostEqual( dentry.corrections['bandfilling_correction'], -1.62202400)
        self.assertAlmostEqual( dentry.corrections['charge_correction'], 0.)

        # turn off band filling and band edge shifting
        dc = DefectCompatibility( free_chg_cutoff=0.8, use_bandfilling=False, use_bandedgeshift=False)
        dentry = dc.process_entry( dentry)
        self.assertAlmostEqual( dentry.corrections['bandedgeshifting_correction'], 0.)
        self.assertAlmostEqual( dentry.corrections['bandfilling_correction'], 0.)
        self.assertAlmostEqual( dentry.corrections['charge_correction'], 0.)

    def test_perform_all_corrections(self):

        #return entry even if insufficent values are provided
        # for freysoldt, kumagai, bandfilling, or band edge shifting
        de = DefectEntry(self.vac, 0., corrections={}, parameters={}, entry_id=None)
        dc = DefectCompatibility()
        dentry = dc.perform_all_corrections( de)
        self.assertIsNotNone( dentry)
        #all other correction applications are tested in unit tests below

    def test_perform_freysoldt(self):
        de = DefectEntry(self.vac, 0., corrections={}, parameters=self.frey_params, entry_id=None)
        dc = DefectCompatibility()
        dentry = dc.perform_freysoldt( de)

        val = dentry.parameters['freysoldt_meta']
        self.assertAlmostEqual(val['freysoldt_electrostatic'], 0.975893)
        self.assertAlmostEqual(val['freysoldt_potential_alignment_correction'], 4.4700574)
        self.assertAlmostEqual(val['freysoldt_potalign'], 1.4900191)
        self.assertTrue('pot_corr_uncertainty_md' in val.keys())
        self.assertTrue('pot_plot_data' in val.keys())

    def test_perform_kumagai(self):
        de = DefectEntry( self.kumagai_vac, 0., parameters=self.kumagai_params)
        dc = DefectCompatibility()
        dentry = dc.perform_kumagai( de)

        val = dentry.parameters['kumagai_meta']
        self.assertAlmostEqual(val['kumagai_electrostatic'], 0.88236299)
        self.assertAlmostEqual(val['kumagai_potential_alignment_correction'], 2.09704862)
        self.assertAlmostEqual(val['kumagai_potalign'], 0.69901620)
        self.assertTrue('pot_corr_uncertainty_md' in val.keys())
        self.assertTrue('pot_plot_data' in val.keys())

    def test_run_bandfilling(self):
        de = DefectEntry(self.vac, 0., corrections={}, parameters=self.bandfill_params, entry_id=None)
        dc = DefectCompatibility()
        dentry = dc.perform_bandfilling( de)

        val = dentry.parameters['bandfilling_meta']
        self.assertAlmostEqual(val['num_hole_vbm'], 0.)
        self.assertAlmostEqual(val['num_elec_cbm'], 0.)
        self.assertAlmostEqual(val['bandfilling_correction'], 0.)

    def test_run_band_edge_shifting(self):
        de = DefectEntry(self.vac, 0., corrections={}, parameters=self.band_edge_params, entry_id=None)

        dc = DefectCompatibility()
        dentry = dc.perform_band_edge_shifting( de)
        val = dentry.parameters['bandshift_meta']
        self.assertEqual(val['vbmshift'], -0.5)
        self.assertEqual(val['cbmshift'], 0.4)
        self.assertEqual(val['bandedgeshifting_correction'], 1.5)

    def test_delocalization_analysis(self):
        #return entry even if insufficent values are provided
        # for delocalization analysis with freysoldt, kumagai,
        # bandfilling, or band edge shifting
        de = DefectEntry(self.vac, 0., corrections={}, parameters={}, entry_id=None)
        dc = DefectCompatibility()
        dentry = dc.delocalization_analysis( de)
        self.assertIsNotNone( dentry)
        #all other correction applications are tested in unit tests below

    def test_check_freysoldt_delocalized(self):
        de = DefectEntry(self.vac, 0., corrections={}, parameters=self.frey_params, entry_id=None)
        de.parameters.update( {'is_compatible': True}) #needs to be initialized with this here for unittest
        dc = DefectCompatibility( plnr_avg_var_tol=0.1, plnr_avg_minmax_tol=0.5)
        dentry = dc.perform_freysoldt( de)

        # check case which fits under compatibility constraints
        dentry = dc.check_freysoldt_delocalized( dentry)
        frey_delocal = dentry.parameters['delocalization_meta']['plnr_avg']
        self.assertTrue( frey_delocal['is_compatible'])
        ans_var = [0.00038993, 0.02119532, 0.02119532]
        ans_window = [0.048331509, 0.36797169, 0.36797169]
        for ax in range(3):
            ax_metadata = frey_delocal['metadata'][ax]
            self.assertTrue( ax_metadata['frey_variance_compatible'])
            self.assertAlmostEqual( ax_metadata['frey_variance'], ans_var[ax])
            self.assertTrue( ax_metadata['frey_minmax_compatible'])
            self.assertAlmostEqual( ax_metadata['frey_minmax_window'], ans_window[ax])

        self.assertTrue( dentry.parameters['is_compatible'])

        # check planar delocalization on 2nd and 3rd axes
        dc = DefectCompatibility( plnr_avg_var_tol=0.1, plnr_avg_minmax_tol=0.2)
        dentry.parameters.update( {'is_compatible': True})
        dentry = dc.check_freysoldt_delocalized( dentry)
        frey_delocal = dentry.parameters['delocalization_meta']['plnr_avg']
        self.assertFalse( frey_delocal['is_compatible'])
        ax_metadata = frey_delocal['metadata'][0]
        self.assertTrue( ax_metadata['frey_variance_compatible'])
        self.assertTrue( ax_metadata['frey_minmax_compatible'])
        for ax in [1,2]:
            ax_metadata = frey_delocal['metadata'][ax]
            self.assertTrue( ax_metadata['frey_variance_compatible'])
            self.assertFalse( ax_metadata['frey_minmax_compatible'])

        self.assertFalse( dentry.parameters['is_compatible'])

        # check variance based delocalization on 2nd and 3rd axes
        dc = DefectCompatibility( plnr_avg_var_tol=0.01, plnr_avg_minmax_tol=0.5)
        dentry.parameters.update( {'is_compatible': True})
        dentry = dc.check_freysoldt_delocalized( dentry)
        frey_delocal = dentry.parameters['delocalization_meta']['plnr_avg']
        self.assertFalse( frey_delocal['is_compatible'])
        ax_metadata = frey_delocal['metadata'][0]
        self.assertTrue( ax_metadata['frey_variance_compatible'])
        self.assertTrue( ax_metadata['frey_minmax_compatible'])
        for ax in [1,2]:
            ax_metadata = frey_delocal['metadata'][ax]
            self.assertFalse( ax_metadata['frey_variance_compatible'])
            self.assertTrue( ax_metadata['frey_minmax_compatible'])

        self.assertFalse( dentry.parameters['is_compatible'])

    def test_check_kumagai_delocalized(self):
        de = DefectEntry( self.kumagai_vac, 0., parameters=self.kumagai_params)
        de.parameters.update( {'is_compatible': True}) #needs to be initialized with this here for unittest
        dc = DefectCompatibility( atomic_site_var_tol=13.3, atomic_site_minmax_tol=20.95)
        dentry = dc.perform_kumagai( de)

        # check case which fits under compatibility constraints
        dentry = dc.check_kumagai_delocalized( dentry)
        kumagai_delocal = dentry.parameters['delocalization_meta']['atomic_site']
        self.assertTrue( kumagai_delocal['is_compatible'])
        kumagai_md = kumagai_delocal['metadata']
        true_variance = 13.262304401193997
        true_minmax = 20.9435
        self.assertTrue(kumagai_md['kumagai_variance_compatible'])
        self.assertAlmostEqual(kumagai_md['kumagai_variance'], true_variance)
        self.assertTrue(kumagai_md['kumagai_minmax_compatible'])
        self.assertAlmostEqual(kumagai_md['kumagai_minmax_window'], true_minmax)

        self.assertTrue( dentry.parameters['is_compatible'])

        # break variable compatibility
        dc = DefectCompatibility( atomic_site_var_tol=0.1, atomic_site_minmax_tol=20.95)
        de.parameters.update( {'is_compatible': True})
        dentry = dc.perform_kumagai( de)
        dentry = dc.check_kumagai_delocalized( dentry)
        kumagai_delocal = dentry.parameters['delocalization_meta']['atomic_site']
        self.assertFalse( kumagai_delocal['is_compatible'])
        kumagai_md = kumagai_delocal['metadata']
        self.assertFalse(kumagai_md['kumagai_variance_compatible'])
        self.assertAlmostEqual(kumagai_md['kumagai_variance'], true_variance)
        self.assertTrue(kumagai_md['kumagai_minmax_compatible'])
        self.assertAlmostEqual(kumagai_md['kumagai_minmax_window'], true_minmax)

        self.assertFalse( dentry.parameters['is_compatible'])

        # break maxmin compatibility
        dc = DefectCompatibility(atomic_site_var_tol=13.3, atomic_site_minmax_tol=0.5)
        de.parameters.update({'is_compatible': True})
        dentry = dc.perform_kumagai(de)
        dentry = dc.check_kumagai_delocalized(dentry)
        kumagai_delocal = dentry.parameters['delocalization_meta']['atomic_site']
        self.assertFalse(kumagai_delocal['is_compatible'])
        kumagai_md = kumagai_delocal['metadata']
        self.assertTrue(kumagai_md['kumagai_variance_compatible'])
        self.assertAlmostEqual(kumagai_md['kumagai_variance'], true_variance)
        self.assertFalse(kumagai_md['kumagai_minmax_compatible'])
        self.assertAlmostEqual(kumagai_md['kumagai_minmax_window'], true_minmax)

        self.assertFalse(dentry.parameters['is_compatible'])

    def test_check_final_relaxed_structure_delocalized(self):
        # test structure delocalization analysis
        # first test no movement in atoms
        initial_defect_structure = self.vac.generate_defect_structure()
        final_defect_structure = initial_defect_structure.copy()
        sampling_radius = 4.55
        defect_frac_sc_coords = self.vac.site.frac_coords[:]

        params = {'initial_defect_structure': initial_defect_structure,
                  'final_defect_structure': final_defect_structure,
                  'sampling_radius': sampling_radius,
                  'defect_frac_sc_coords': defect_frac_sc_coords,
                  'is_compatible': True}
        dentry = DefectEntry(self.vac, 0., corrections={}, parameters=params, entry_id=None)

        dc = DefectCompatibility( tot_relax_tol=0.1, perc_relax_tol=0.1, defect_tot_relax_tol=0.1)
        dentry = dc.check_final_relaxed_structure_delocalized( dentry)

        struc_delocal = dentry.parameters['delocalization_meta']['structure_relax']
        self.assertTrue( dentry.parameters['is_compatible'])
        self.assertTrue( struc_delocal['is_compatible'])
        self.assertTrue( struc_delocal['metadata']['structure_tot_relax_compatible'])
        self.assertEqual( struc_delocal['metadata']['tot_relax_outside_rad'], 0.)
        self.assertTrue( struc_delocal['metadata']['structure_perc_relax_compatible'])
        self.assertEqual( struc_delocal['metadata']['perc_relax_outside_rad'], 0.)
        self.assertEqual( len(struc_delocal['metadata']['full_structure_relax_data']), len(initial_defect_structure))
        self.assertIsNone( struc_delocal['metadata']['defect_index'])

        defect_delocal = dentry.parameters['delocalization_meta']['defectsite_relax']
        self.assertTrue( defect_delocal['is_compatible'])
        self.assertIsNone( defect_delocal['metadata']['relax_amount'])


        # next test for when structure has delocalized outside of radius from defect
        pert_struct_fin_struct = initial_defect_structure.copy()
        pert_struct_fin_struct.perturb( 0.1)
        dentry.parameters.update( {'final_defect_structure': pert_struct_fin_struct})
        dentry = dc.check_final_relaxed_structure_delocalized( dentry)

        struc_delocal = dentry.parameters['delocalization_meta']['structure_relax']
        self.assertFalse( dentry.parameters['is_compatible'])
        self.assertFalse( struc_delocal['is_compatible'])
        self.assertFalse( struc_delocal['metadata']['structure_tot_relax_compatible'])
        self.assertAlmostEqual( struc_delocal['metadata']['tot_relax_outside_rad'], 12.5)
        self.assertFalse( struc_delocal['metadata']['structure_perc_relax_compatible'])
        self.assertAlmostEqual( struc_delocal['metadata']['perc_relax_outside_rad'], 77.63975155)


        # now test for when an interstitial defect has migrated too much
        inter_def_site = PeriodicSite('H',  [7.58857304, 11.70848069, 12.97817518],
                                self.vac.bulk_structure.lattice, to_unit_cell=True,
                                coords_are_cartesian=True)
        inter = Interstitial(self.vac.bulk_structure, inter_def_site, charge=0)

        initial_defect_structure = inter.generate_defect_structure()
        final_defect_structure = initial_defect_structure.copy()
        poss_deflist = sorted(
            final_defect_structure.get_sites_in_sphere(inter.site.coords,
                                                       2, include_index=True), key=lambda x: x[1])
        def_index = poss_deflist[0][2]
        final_defect_structure.translate_sites(indices=[def_index],
                                               vector=[0., 0., 0.008]) #fractional coords translation
        defect_frac_sc_coords = inter_def_site.frac_coords[:]

        params = {'initial_defect_structure': initial_defect_structure,
                  'final_defect_structure': final_defect_structure,
                  'sampling_radius': sampling_radius,
                  'defect_frac_sc_coords': defect_frac_sc_coords,
                  'is_compatible': True}
        dentry = DefectEntry(inter, 0., corrections={}, parameters=params, entry_id=None)

        dentry = dc.check_final_relaxed_structure_delocalized( dentry)

        defect_delocal = dentry.parameters['delocalization_meta']['defectsite_relax']
        self.assertFalse( defect_delocal['is_compatible'])
        self.assertAlmostEqual( defect_delocal['metadata']['relax_amount'], 0.10836054)
Exemple #13
0
def vac_antisite_def_struct_gen(mpid, mapi_key, cellmax):
    if not mpid:
        print("============\nERROR: Provide an mpid\n============")
        return

    # Get primitive structure from the Materials Project DB
    if not mapi_key:
        with MPRester() as mp:
            struct = mp.get_structure_by_material_id(mpid)
    else:
        with MPRester(mapi_key) as mp:
            struct = mp.get_structure_by_material_id(mpid)
    sga = SpacegroupAnalyzer(struct)
    prim_struct = sga.find_primitive()
    #prim_struct_sites = len(prim_struct.sites)
    #conv_struct = sga.get_conventional_standard_structure()
    #conv_struct_sites = len(conv_struct.sites)
    #conv_prim_ratio = int(conv_struct_sites / prim_struct_sites)

    # Default VASP settings
    def_vasp_incar_param = {
        'ISIF': 2,
        'EDIFF': 1e-6,
        'EDIFFG': 0.001,
    }
    kpoint_den = 15000

    # Create bulk structure and associated VASP files
    sc_scale = get_sc_scale(inp_struct=prim_struct, final_site_no=cellmax)
    blk_sc = prim_struct.copy()
    blk_sc.make_supercell(scaling_matrix=sc_scale)
    site_no = blk_sc.num_sites

    # Rescale if needed
    if site_no > cellmax:
        max_sc_dim = max(sc_scale)
        i = sc_scale.index(max_sc_dim)
        sc_scale[i] -= 1
        blk_sc = prim_struct.copy()
        blk_sc.make_supercell(scaling_matrix=sc_scale)
    blk_str_sites = set(blk_sc.sites)
    custom_kpoints = Kpoints.automatic_density(blk_sc, kppa=kpoint_den)
    mpvis = MPMetalRelaxSet(blk_sc,
                            user_incar_settings=def_vasp_incar_param,
                            user_kpoints_settings=custom_kpoints)
    ptcr_flag = True
    try:
        potcar = mpvis.potcar
    except:
        print ("VASP POTCAR folder not detected.\n" \
               "Only INCAR, POSCAR, KPOINTS are generated.\n" \
               "If you have VASP installed on this system, \n" \
               "refer to pymatgen documentation for configuring the settings.")
        ptcr_flag = False
    fin_dir = os.path.join(mpid, 'bulk')
    mpvis.write_input(fin_dir)

    # Create each defect structure and associated VASP files
    # First find all unique defect sites
    periodic_struct = sga.get_symmetrized_structure()
    unique_sites = list(set([periodic_struct.find_equivalent_sites(site)[0] \
                             for site in periodic_struct.sites]))
    temp_struct = Structure.from_sites(sorted(unique_sites))
    prim_struct2 = SpacegroupAnalyzer(temp_struct).find_primitive()
    prim_struct2.lattice = prim_struct.lattice  # a little hacky
    for i, site in enumerate(prim_struct2.sites):
        vac = Vacancy(structure=prim_struct, defect_site=site)
        vac_sc = vac.generate_defect_structure(supercell=sc_scale)

        # Get vacancy site information
        vac_str_sites = set(vac_sc.sites)
        vac_sites = blk_str_sites - vac_str_sites
        vac_site = next(iter(vac_sites))
        site_mult = vac.get_multiplicity()
        vac_site_specie = vac_site.specie
        vac_symbol = vac_site_specie.symbol

        custom_kpoints = Kpoints.automatic_density(vac_sc, kppa=kpoint_den)
        mpvis = MPMetalRelaxSet(vac_sc,
                                user_incar_settings=def_vasp_incar_param,
                                user_kpoints_settings=custom_kpoints)
        vac_dir = 'vacancy_{}_mult-{}_sitespecie-{}'.format(
            str(i + 1), site_mult, vac_symbol)
        fin_dir = os.path.join(mpid, vac_dir)
        mpvis.write_input(fin_dir)

        # Antisites generation at the vacancy site
        struct_species = blk_sc.species
        for specie in set(struct_species) - set([vac_site_specie]):
            specie_symbol = specie.symbol
            anti_sc = vac_sc.copy()
            anti_sc.append(specie, vac_site.frac_coords)
            mpvis = MPMetalRelaxSet(anti_sc,
                                    user_incar_settings=def_vasp_incar_param,
                                    user_kpoints_settings=custom_kpoints)
            anti_dir = 'antisite_{}_mult-{}_sitespecie-{}_subspecie-{}'.format(
                str(i + 1), site_mult, vac_symbol, specie_symbol)
            fin_dir = os.path.join(mpid, anti_dir)
            mpvis.write_input(fin_dir)
    def test_kumagai(self):
        gamma = 0.19357221
        prec = 28
        lattice = Lattice( [[ 4.692882, -8.12831 ,  0.],
                            [ 4.692882,  8.12831 ,  0.],
                            [ 0.,  0., 10.03391 ]])

        #note that real/recip vector generation is not dependent on epsilon
        g_vecs, _, r_vecs, _ = generate_R_and_G_vecs( gamma, prec, lattice, 80. * np.identity(3))

        #test real space summation (bigger for large epsilon)
        kc_high_diel = KumagaiCorrection( 80. * np.identity(3), gamma=gamma)
        real_sum = kc_high_diel.get_real_summation( gamma, r_vecs[0])
        self.assertAlmostEqual( real_sum, 0.00843104)

        #test recip space summation (bigger for small epsilon)
        kc_low_diel = KumagaiCorrection( 0.1 * np.identity(3), gamma=gamma)
        recip_sum = kc_low_diel.get_recip_summation( gamma, g_vecs[0], lattice.volume)
        self.assertAlmostEqual( recip_sum, 0.31117099)

        #test self interaction
        si_corr = kc_low_diel.get_self_interaction( gamma)
        self.assertAlmostEqual( si_corr, -0.54965249)

        #test potenital shift interaction correction
        ps_corr = kc_low_diel.get_potential_shift( gamma, lattice.volume)
        self.assertAlmostEqual( ps_corr, -0.00871593)

        # """Test Defect Entry approach to correction """
        bulk_struc = Poscar.from_file(os.path.join( test_dir, 'defect', 'CONTCAR_bulk')).structure
        bulk_out = Outcar( os.path.join( test_dir, 'defect', 'OUTCAR_bulk.gz'))
        defect_out = Outcar( os.path.join( test_dir, 'defect', 'OUTCAR_vac_Ga_-3.gz'))
        epsilon = 18.118 * np.identity(3)
        vac = Vacancy(bulk_struc, bulk_struc.sites[0], charge=-3)
        defect_structure = vac.generate_defect_structure()
        defect_frac_coords = [0.,0.,0.]

        parameters = {'bulk_atomic_site_averages': bulk_out.electrostatic_potential,
                      'defect_atomic_site_averages': defect_out.electrostatic_potential,
                      'site_matching_indices': [[ind, ind-1] for ind in range(len(bulk_struc))],
                      'initial_defect_structure': defect_structure,
                      'defect_frac_sc_coords': defect_frac_coords}
        dentry = DefectEntry( vac, 0., parameters=parameters)
        kc = KumagaiCorrection( epsilon)
        kcorr = kc.get_correction( dentry)
        self.assertAlmostEqual( kcorr['kumagai_electrostatic'], 0.88236299)
        self.assertAlmostEqual( kcorr['kumagai_potential_alignment'], 2.09704862)

        # test ES correction
        high_diel_es_corr = kc_high_diel.perform_es_corr( gamma, prec, lattice, -3.)
        self.assertAlmostEqual( high_diel_es_corr, 0.25176240)

        low_diel_es_corr = kc_low_diel.perform_es_corr( gamma, prec, lattice, -3.)
        self.assertAlmostEqual( low_diel_es_corr, 201.28810966)

        # test pot correction
        site_list = []
        for bs_ind, ds_ind in dentry.parameters['site_matching_indices']:
            Vqb = -(defect_out.electrostatic_potential[ds_ind] - bulk_out.electrostatic_potential[bs_ind])
            site_list.append([defect_structure[ds_ind], Vqb])

        sampling_radius = dentry.parameters["kumagai_meta"]["sampling_radius"]
        gamma = dentry.parameters["kumagai_meta"]["gamma"]
        q = -3
        g_vecs, _, r_vecs, _ = generate_R_and_G_vecs( gamma, 28, defect_structure.lattice, np.identity(3))
        high_diel_pot_corr  = kc_high_diel.perform_pot_corr( defect_structure, defect_frac_coords,
                                                             site_list, sampling_radius, q,
                                                             r_vecs[0], g_vecs[0], gamma)
        self.assertAlmostEqual( high_diel_pot_corr, 2.35840716)
        low_diel_pot_corr  = kc_low_diel.perform_pot_corr( defect_structure, defect_frac_coords,
                                                           site_list, sampling_radius, q,
                                                           r_vecs[0], g_vecs[0], gamma)
        self.assertAlmostEqual( low_diel_pot_corr, -58.83598095)

        #test the kumagai plotter
        kcp = kc.plot()
        self.assertTrue( kcp)

        #check that uncertainty metadata exists
        self.assertAlmostEqual(set(kc.metadata['pot_corr_uncertainty_md'].keys()), set(['number_sampled', 'stats']))
    def test_freysoldt(self):
        struc = PymatgenTest.get_structure("VO2")
        struc.make_supercell(3)
        struc = struc
        vac = Vacancy(struc, struc.sites[0], charge=-3)
        ids = vac.generate_defect_structure(1)

        abc = struc.lattice.abc
        axisdata = [np.arange(0., lattval, 0.2) for lattval in abc]
        bldata = [np.array([1. for u in np.arange(0., lattval, 0.2)]) for lattval in abc]
        dldata = [
            np.array([(-1 - np.cos(2 * np.pi * u / lattval)) for u in np.arange(0., lattval, 0.2)]) for lattval in abc
        ]
        params = {'axis_grid': axisdata, 'bulk_planar_averages': bldata, 'defect_planar_averages': dldata,
                  'initial_defect_structure': ids, 'defect_frac_sc_coords': struc.sites[0].frac_coords}
        fc = FreysoldtCorrection(15)

        #test electrostatic correction
        es_corr = fc.perform_es_corr(struc.lattice, -3)
        self.assertAlmostEqual(es_corr, 0.975893)

        #test potential alignment method
        pot_corr = fc.perform_pot_corr(axisdata[0], bldata[0], dldata[0], struc.lattice, -3, vac.site.coords, 0)
        self.assertAlmostEqual(pot_corr, 2.836369987722345)

        #test entry full correction method
        de = DefectEntry(vac, 0., corrections={}, parameters=params, entry_id=None)
        val = fc.get_correction(de)
        self.assertAlmostEqual(val['freysoldt_electrostatic'], 0.975893)
        self.assertAlmostEqual(val['freysoldt_potential_alignment'], 4.4700574)

        #test the freysoldt plotter
        for ax in range(3):
            fcp = fc.plot(axis=ax)
            self.assertTrue( fcp)

        #check that uncertainty metadata exists
        for ax in range(3):
            self.assertAlmostEqual(set(fc.metadata['pot_corr_uncertainty_md'][ax].keys()), set(['potcorr', 'stats']))

        #test a specified axis from entry
        fc = FreysoldtCorrection(15, axis=[1])
        val = fc.get_correction(de)
        self.assertAlmostEqual(val['freysoldt_potential_alignment'], 5.2869010593283132)

        #test a different charge
        #   for electrostatic correction
        es_corr = fc.perform_es_corr(struc.lattice, 2)
        self.assertAlmostEqual(es_corr, 0.43373)
        #   for potential alignment method
        pot_corr = fc.perform_pot_corr(axisdata[0], bldata[0], dldata[0], struc.lattice, 2, vac.site.coords, 0)
        self.assertAlmostEqual(pot_corr, -2.1375685936497768)

        #test an input anisotropic dielectric constant
        fc = FreysoldtCorrection([[1., 2., 3.], [0., 3., 5.], [4., 10., 8.]])
        self.assertAlmostEqual(fc.dielectric, 4.)
        val = fc.get_correction(de)
        self.assertAlmostEqual(val['freysoldt_electrostatic'], 3.659599)
        self.assertAlmostEqual(val['freysoldt_potential_alignment'], 3.3605255195745087)

        #test potalign being added to defect entry
        self.assertAlmostEqual(de.parameters['potalign'], 1.1201751731915028)

        #test that metadata entries exist in defect entry
        self.assertTrue('freysoldt_meta' in de.parameters.keys())
        self.assertAlmostEqual(
            set(de.parameters['freysoldt_meta'].keys()), set(['pot_plot_data', 'pot_corr_uncertainty_md']))

        #test a charge of zero
        vac = Vacancy(struc, struc.sites[0], charge=0)
        de = DefectEntry(vac, 0., corrections={}, parameters=params, entry_id=None)
        val = fc.get_correction(de)
        self.assertAlmostEqual(val['freysoldt_electrostatic'], 0.)
        self.assertAlmostEqual(val['freysoldt_potential_alignment'], 0.)