def test_corrections(self): entry = DefectEntry(self.substitution, 2.5) self.assertAlmostEqual(entry.energy, 2.5) entry.corrections["pot_corr"] = -0.3 self.assertAlmostEqual(entry.energy, 2.2)
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 # 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"], -1.62202400) 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_defect_concentration(self): entry = DefectEntry(self.substitution, .5, corrections={}) entry.defect._charge = -1 chem_pots = {"Sr": 0., "V": 0., "O": 0.} self.assertAlmostEqual(entry.defect_concentration(chem_pots), 1.2878309944593931e14) # #test temperature dependence self.assertAlmostEqual(entry.defect_concentration(chem_pots, temperature=600), 2.040208007417593e+18) # test fermi level dependence self.assertAlmostEqual(entry.defect_concentration(chem_pots, fermi_level=.3), 1.4113592133771723e+19)
def setUpClass(cls): cls.vbm_val = 2.6682 cls.gap = 1.5 cls.entries = list(loadfn(os.path.join(os.path.dirname(__file__), "GaAs_test_defentries.json")).values()) for entry in cls.entries: entry.parameters.update({"vbm": cls.vbm_val}) cls.pd = DefectPhaseDiagram(cls.entries, cls.vbm_val, cls.gap) cls.mu_elts = {Element("As"): -4.658070555, Element("Ga"): -3.7317319750000006} # make Vac_As (q= -2) only defect test single-stable-charge exceptions cls.extra_entry = DefectEntry(cls.entries[5].defect.copy(), 100.0) sep_entries = [ ent for ent in cls.entries if not (ent.name == "Vac_As_mult4" and ent.charge in [-2, -1, 0, 1, 2]) ] sep_entries.append(cls.extra_entry.copy()) cls.sep_pd = DefectPhaseDiagram(sep_entries, cls.vbm_val, cls.gap) # make Vac_As (q= -2) is incompatible for larger supercell ls_entries = cls.entries[:] for entry in ls_entries: if entry.name == "Vac_As_mult4" and entry.charge == -2.0: entry.parameters["is_compatible"] = False cls.pd_ls_fcTrue = DefectPhaseDiagram(ls_entries, cls.vbm_val, cls.gap, filter_compatible=True) cls.pd_ls_fcFalse = DefectPhaseDiagram(ls_entries, cls.vbm_val, cls.gap, filter_compatible=False) # load complete dos for fermi energy solving with open(os.path.join(PymatgenTest.TEST_FILES_DIR, "complete_dos.json")) as f: dos_dict = json.load(f) cls.dos = CompleteDos.from_dict(dos_dict)
def setUp(self): self.vbm_val = 2.6682 self.gap = 1.5 self.entries = list(loadfn(os.path.join(os.path.dirname(__file__), "GaAs_test_defentries.json")).values()) for entry in self.entries: entry.parameters.update( {'vbm': self.vbm_val}) self.pd = DefectPhaseDiagram(self.entries, self.vbm_val, self.gap) self.mu_elts = {Element("As"): -4.658070555, Element("Ga"): -3.7317319750000006} # make Vac_As (q= -2) only defect test single-stable-charge exceptions self.extra_entry = DefectEntry(self.entries[5].defect.copy(), 100.) sep_entries = [ent for ent in self.entries if not (ent.name == 'Vac_As_mult4' and ent.charge in [-2,-1,0,1,2])] sep_entries.append( self.extra_entry.copy()) self.sep_pd = DefectPhaseDiagram( sep_entries, self.vbm_val, self.gap) # make Vac_As (q= -2) is incompatible for larger supercell ls_entries = self.entries[:] for entry in ls_entries: if entry.name == 'Vac_As_mult4' and entry.charge == -2.: entry.parameters['is_compatible'] = False self.pd_ls_fcTrue = DefectPhaseDiagram(ls_entries, self.vbm_val, self.gap, filter_compatible=True) self.pd_ls_fcFalse = DefectPhaseDiagram(ls_entries, self.vbm_val, self.gap, filter_compatible=False) # load complete dos for fermi energy solving with open(os.path.join(test_dir, "complete_dos.json"), "r") as f: dos_dict = json.load(f) self.dos = CompleteDos.from_dict(dos_dict)
def from_dict(cls, d): """ Reconstitute a DefectPhaseDiagram object from a dict representation created using as_dict(). Args: d (dict): dict representation of DefectPhaseDiagram. Returns: DefectPhaseDiagram object """ entries = [ DefectEntry.from_dict(entry_dict) for entry_dict in d.get("entries") ] vbm = d["vbm"] band_gap = d["band_gap"] filter_compatible = d.get("filter_compatible", True) metadata = d.get("metadata", {}) if 'entry_id' in d.keys() and 'entry_id' not in metadata: metadata['entry_id'] = d['entry_id'] return cls(entries, vbm, band_gap, filter_compatible=filter_compatible, metadata=metadata)
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_formation_energy(self): entry = DefectEntry(self.substitution, 2.5, corrections={"pot_corr": -0.3}) # Test chemical potentials on formation energy self.assertAlmostEqual(entry.formation_energy(), 2.2) self.assertAlmostEqual(entry.formation_energy({"Sr": 0.2}), 2.0) self.assertAlmostEqual(entry.formation_energy({"V": 0.2}), 2.4) self.assertAlmostEqual(entry.formation_energy({"Sr": 0.2, "V": 0.2}), 2.2) self.assertAlmostEqual(entry.formation_energy({"Sr": 0.2, "V": 0.2, "O": 2}), 2.2) # Test Fermi level on formation energy self.assertAlmostEqual(entry.formation_energy({"Sr": 0.2, "V": 0.2}, fermi_level=0.2), 2.2) entry.parameters["vbm"] = 0 self.assertAlmostEqual(entry.formation_energy({"Sr": 0.2, "V": 0.2}, fermi_level=0.2), 2.2) entry.defect._charge = 1 self.assertAlmostEqual(entry.formation_energy({"Sr": 0.2, "V": 0.2}, fermi_level=0.2), 2.4)
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_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_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)
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)
def convert_cd_to_de( cd, b_cse): """ As of pymatgen v2.0, ComputedDefect objects were deprecated in favor of DefectEntry objects in pymatgen.analysis.defects.core This function takes a ComputedDefect (either as a dict or object) and converts it into a DefectEntry object in order to handle legacy PyCDT creation within the current paradigm of PyCDT. :param cd (dict or ComputedDefect object): ComputedDefect as an object or as a dictionary :params b_cse (dict or ComputedStructureEntry object): ComputedStructureEntry of bulk entry associated with the ComputedDefect. :return: de (DefectEntry): Resulting DefectEntry object """ if type(cd) != dict: cd = cd.as_dict() if type(b_cse) != dict: b_cse = b_cse.as_dict() bulk_sc_structure = Structure.from_dict( b_cse["structure"]) #modify defect_site as required for Defect object, confirming site exists in bulk structure site_cls = cd["site"] defect_site = PeriodicSite.from_dict( site_cls) def_nom = cd["name"].lower() if "sub_" in def_nom or "as_" in def_nom: #modify site object for substitution site of Defect object site_cls["species"][0]["element"] = cd["name"].split("_")[2] defect_site = PeriodicSite.from_dict( site_cls) poss_deflist = sorted( bulk_sc_structure.get_sites_in_sphere(defect_site.coords, 0.1, include_index=True), key=lambda x: x[1]) if len(poss_deflist) != 1: raise ValueError("ComputedDefect to DefectEntry conversion failed. " "Could not determine periodic site position in bulk supercell.") # create defect object if "vac_" in def_nom: defect_obj = Vacancy(bulk_sc_structure, defect_site, charge=cd["charge"]) elif "as_" in def_nom or "sub_" in def_nom: defect_obj = Substitution(bulk_sc_structure, defect_site, charge=cd["charge"]) elif "int_" in def_nom: defect_obj = Interstitial(bulk_sc_structure, defect_site, charge=cd["charge"]) else: raise ValueError("Could not recognize defect type for {}".format( cd["name"])) # assign proper energy and parameter metadata uncorrected_energy = cd["entry"]["energy"] - b_cse["energy"] def_path = os.path.split( cd["entry"]["data"]["locpot_path"])[0] bulk_path = os.path.split( b_cse["data"]["locpot_path"])[0] p = {"defect_path": def_path, "bulk_path": bulk_path, "encut": cd["entry"]["data"]["encut"]} de = DefectEntry( defect_obj, uncorrected_energy, parameters = p) return de
def test_defect_concentration(self): entry = DefectEntry(self.substitution, 0.5, corrections={}) entry.defect._charge = -1 chem_pots = {"Sr": 0.0, "V": 0.0, "O": 0.0} self.assertAlmostEqual(entry.defect_concentration(chem_pots) / 1.2878334860092098e14, 1) # #test temperature dependence self.assertAlmostEqual( entry.defect_concentration(chem_pots, temperature=600) / 2.0402099809985405e18, 1, ) # test fermi level dependence self.assertAlmostEqual( entry.defect_concentration(chem_pots, fermi_level=0.3) / 1.411360305591838e19, 1, )
def get_freysoldt_correction(defect_type, defect_specie, path_to_defect_locpot,path_to_pure_locpot,charge, dielectric_constant,defect_site_coordinates,energy_cutoff=500,get_plot=False): ''' Function to perform charge corrections according to the method proposed py Freysoldt If this correction is used, please reference Freysoldt's original paper. doi: 10.1103/PhysRevLett.102.016402 Args: defect_type: 'vacancy' or 'interstitial' defect_specie: string with element occupying the defect site path_to_defect_locpot: path to LOCPOT file of defect structure path_to_pure_locpot: path to LOCPOT file of Pure structure charge: Charge of the defected system dielectric_constant: Dielectric constant defect_site_coordinates: numpy array with fractional coordinates of defect site energy_cutoff: Cut-off of plane wave expansion get_plot: return also Matplotlib object with plot Returns: Freysoldt corrections values as a dictionary ''' # acquiring data from LOCPOT files locpot_pure = Locpot.from_file(path_to_pure_locpot) vol_data_pure = VolumetricData(locpot_pure.structure,locpot_pure.data) locpot_defect = Locpot.from_file(path_to_defect_locpot) vol_data_defect = VolumetricData(locpot_defect.structure,locpot_defect.data) parameters = {} parameters['axis_grid'] = [] parameters['bulk_planar_averages'] = [] parameters['defect_planar_averages'] = [] for i in range(0,3): parameters['axis_grid'].append(vol_data_pure.get_axis_grid(i)) parameters['bulk_planar_averages'].append(vol_data_pure.get_average_along_axis(i)) parameters['defect_planar_averages'].append(vol_data_defect.get_average_along_axis(i)) parameters['initial_defect_structure'] = locpot_defect.structure parameters['defect_frac_sc_coords'] = defect_site_coordinates structure_bulk = locpot_pure.structure defect_site = PeriodicSite(defect_specie, coords=defect_site_coordinates, lattice = locpot_pure.structure.lattice) module = importlib.import_module("pymatgen.analysis.defects.core") defect_class = getattr(module,defect_type) defect = defect_class(structure_bulk, defect_site, charge=charge, multiplicity=None) defect_entry = DefectEntry(defect,None,corrections=None,parameters=parameters) freysoldt_class = FreysoldtCorrection(dielectric_constant,energy_cutoff=energy_cutoff) freysoldt_corrections = freysoldt_class.get_correction(defect_entry) if get_plot: plt = freysoldt_class.plot(1) return freysoldt_corrections , plt else: return freysoldt_corrections
def setUp(self): self.vbm_val = 2.6682 self.gap = 1.5 self.entries = list( loadfn( os.path.join(os.path.dirname(__file__), "GaAs_test_defentries.json")).values()) for entry in self.entries: entry.parameters.update({'vbm': self.vbm_val}) self.pd = DefectPhaseDiagram(self.entries, self.vbm_val, self.gap) self.mu_elts = { Element("As"): -4.658070555, Element("Ga"): -3.7317319750000006 } # make Vac_As (q= -2) only defect test single-stable-charge exceptions self.extra_entry = DefectEntry(self.entries[5].defect.copy(), 100.) sep_entries = [ ent for ent in self.entries if not ( ent.name == 'Vac_As_mult4' and ent.charge in [-2, -1, 0, 1, 2]) ] sep_entries.append(self.extra_entry.copy()) self.sep_pd = DefectPhaseDiagram(sep_entries, self.vbm_val, self.gap) # make Vac_As (q= -2) is incompatible for larger supercell ls_entries = self.entries[:] for entry in ls_entries: if entry.name == 'Vac_As_mult4' and entry.charge == -2.: entry.parameters['is_compatible'] = False self.pd_ls_fcTrue = DefectPhaseDiagram(ls_entries, self.vbm_val, self.gap, filter_compatible=True) self.pd_ls_fcFalse = DefectPhaseDiagram(ls_entries, self.vbm_val, self.gap, filter_compatible=False) # load complete dos for fermi energy solving with open(os.path.join(test_dir, "complete_dos.json"), "r") as f: dos_dict = json.load(f) self.dos = CompleteDos.from_dict(dos_dict)
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_bandedgeshifting(self): struc = PymatgenTest.get_structure("VO2") struc.make_supercell(3) struc = struc vac = Vacancy(struc, struc.sites[0], charge=-3) besc = BandEdgeShiftingCorrection() params = {"hybrid_cbm": 1.0, "hybrid_vbm": -1.0, "vbm": -0.5, "cbm": 0.6} de = DefectEntry(vac, 0.0, corrections={}, parameters=params, entry_id=None) corr = besc.get_correction(de) self.assertEqual(corr["bandedgeshifting_correction"], 1.5)
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_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 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 setUp(self): l = Lattice([[3.52,0.0,2.033], [1.174,3.32,2.033], \ [0.0,0.0,4.066]]) s_bulk = Structure(l, ['Ga', 'As'], \ [[0.0000, 0.0000, 0.0000], \ [0.2500, 0.2500, 0.2500]]) defect_site = PeriodicSite('As', [0.25, 0.25, 0.25], l) defect = Vacancy(s_bulk, defect_site, charge=1.) defect_entry = DefectEntry(defect, 0.) entries = [defect_entry] vbm = 0.2 band_gap = 1. dpd = DefectPhaseDiagram(entries, vbm, band_gap) self.dp = DefectPlotter(dpd)
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.) occu = [[1.457, 0.0833333], [1.5204, 0.0833333], [1.53465, 0.0833333], [1.5498, 0.0416667]] self.assertArrayAlmostEqual( list(sorted(val['occupied_def_levels'], key=lambda x: x[0])), list(sorted(occu, key=lambda x: x[0]))) self.assertAlmostEqual(val['total_occupation_defect_levels'], 0.29166669) self.assertFalse(val['unoccupied_def_levels'])
def from_dict(cls, d): """ Reconstitute a DefectPhaseDiagram object from a dict representation created using as_dict(). Args: d (dict): dict representation of DefectPhaseDiagram. Returns: DefectPhaseDiagram object """ entries = [DefectEntry.from_dict(entry_dict) for entry_dict in d.get("entries")] vbm = d["vbm"] band_gap = d["band_gap"] filter_compatible = d.get("filter_compatible", True) metadata = d.get("metadata", {}) if 'entry_id' in d.keys() and 'entry_id' not in metadata: metadata['entry_id'] = d['entry_id'] return cls(entries, vbm, band_gap, filter_compatible=filter_compatible, metadata=metadata)
def test_bandedgeshifting(self): struc = PymatgenTest.get_structure("VO2") struc.make_supercell(3) struc = struc vac = Vacancy(struc, struc.sites[0], charge=-3) besc = BandEdgeShiftingCorrection() params = { 'hybrid_cbm': 1., 'hybrid_vbm': -1., 'vbm': -0.5, 'cbm': 0.6, 'num_hole_vbm': 0., 'num_elec_cbm': 0. } de = DefectEntry(vac, 0., corrections={}, parameters=params, entry_id=None) #test with no free carriers corr = besc.get_correction(de) self.assertEqual(corr['vbm_shift_correction'], 1.5) self.assertEqual(corr['elec_cbm_shift_correction'], 0.) self.assertEqual(corr['hole_vbm_shift_correction'], 0.) #test with free holes de.parameters.update({'num_hole_vbm': 1.}) corr = besc.get_correction(de) self.assertEqual(corr['vbm_shift_correction'], 1.5) self.assertEqual(corr['elec_cbm_shift_correction'], 0.) self.assertEqual(corr['hole_vbm_shift_correction'], 0.5) #test with free electrons de.parameters.update({'num_hole_vbm': 0., 'num_elec_cbm': 1.}) corr = besc.get_correction(de) self.assertEqual(corr['vbm_shift_correction'], 1.5) self.assertEqual(corr['elec_cbm_shift_correction'], 0.4) self.assertEqual(corr['hole_vbm_shift_correction'], 0.)
def test_formation_energy(self): entry = DefectEntry(self.substitution, 2.5, corrections={"pot_corr": -0.3}) # Test chemical potentials on formation energy self.assertAlmostEqual(entry.formation_energy(), 2.2) self.assertAlmostEqual(entry.formation_energy({"Sr": 0.2}), 2.0) self.assertAlmostEqual(entry.formation_energy({"V": 0.2}), 2.4) self.assertAlmostEqual(entry.formation_energy({ "Sr": 0.2, "V": 0.2 }), 2.2) self.assertAlmostEqual( entry.formation_energy({ "Sr": 0.2, "V": 0.2, "O": 2 }), 2.2) # Test Fermi level on formation energy self.assertAlmostEqual( entry.formation_energy({ "Sr": 0.2, "V": 0.2 }, fermi_level=0.2), 2.2) entry.parameters["vbm"] = 0 self.assertAlmostEqual( entry.formation_energy({ "Sr": 0.2, "V": 0.2 }, fermi_level=0.2), 2.2) entry.defect._charge = 1 self.assertAlmostEqual( entry.formation_energy({ "Sr": 0.2, "V": 0.2 }, fermi_level=0.2), 2.4)
def test_init(self): entry = DefectEntry(self.substitution, 2.5) entry_doc = entry.as_dict() re_entry = DefectEntry.from_dict(entry_doc) self.assertNotEqual(re_entry, None)
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 parse_defect_calculations(self): """ Parses the defect calculations as DefectEntry objects, from a PyCDT root_fldr file structure. Charge correction is missing in the first run. """ logger = logging.getLogger(__name__) parsed_defects = [] subfolders = glob.glob(os.path.join(self._root_fldr, "vac_*")) subfolders += glob.glob(os.path.join(self._root_fldr, "as_*")) subfolders += glob.glob(os.path.join(self._root_fldr, "sub_*")) subfolders += glob.glob(os.path.join(self._root_fldr, "inter_*")) def get_vr_and_check_locpot(fldr): vr_file = os.path.join(fldr,"vasprun.xml") if not os.path.exists(vr_file): logger.warning("{} doesn't exit".format(vr_file)) error_msg = ": Failure, vasprun.xml doesn't exist." return (None, error_msg) #Further processing is not useful try: vr = Vasprun(vr_file, parse_potcar_file=False) except: logger.warning("Couldn't parse {}".format(vr_file)) error_msg = ": Failure, couldn't parse vaprun.xml file." return (None, error_msg) if not vr.converged: logger.warning( "Vasp calculation at {} not converged".format(fldr)) error_msg = ": Failure, Vasp calculation not converged." return (None, error_msg) # Further processing is not useful # Check if locpot exists locpot_file = os.path.join(fldr, "LOCPOT") if not os.path.exists(locpot_file): logger.warning("{} doesn't exit".format(locpot_file)) error_msg = ": Failure, LOCPOT doesn't exist" return (None, error_msg) #Further processing is not useful return (vr, None) def get_encut_from_potcar(fldr): potcar_file = os.path.join(fldr,"POTCAR") if not os.path.exists(potcar_file): logger.warning("Not POTCAR in {} to parse ENCUT".format(fldr)) error_msg = ": Failure, No POTCAR file." return (None, error_msg) #Further processing is not useful try: potcar = Potcar.from_file(potcar_file) except: logger.warning("Couldn't parse {}".format(potcar_file)) error_msg = ": Failure, couldn't read POTCAR file." return (None, error_msg) encut = max(ptcr_sngl.enmax for ptcr_sngl in potcar) return (encut, None) # get bulk entry information first fldr = os.path.join(self._root_fldr, "bulk") vr, error_msg = get_vr_and_check_locpot(fldr) if error_msg: logger.error("Abandoning parsing of the calculations") return {} bulk_energy = vr.final_energy bulk_sc_struct = vr.final_structure try: encut = vr.incar["ENCUT"] except: # ENCUT not specified in INCAR. Read from POTCAR encut, error_msg = get_encut_from_potcar(fldr) if error_msg: logger.error("Abandoning parsing of the calculations") return {} trans_dict = loadfn( os.path.join(fldr, "transformation.json"), cls=MontyDecoder) supercell_size = trans_dict["supercell"] bulk_file_path = fldr bulk_entry = ComputedStructureEntry( bulk_sc_struct, bulk_energy, data={"bulk_path": bulk_file_path, "encut": encut, "supercell_size": supercell_size}) # get defect entry information for fldr in subfolders: fldr_name = os.path.split(fldr)[1] chrg_fldrs = glob.glob(os.path.join(fldr,"charge*")) for chrg_fldr in chrg_fldrs: trans_dict = loadfn( os.path.join(chrg_fldr, "transformation.json"), cls=MontyDecoder) chrg = trans_dict["charge"] vr, error_msg = get_vr_and_check_locpot(chrg_fldr) if error_msg: logger.warning("Parsing the rest of the calculations") continue if "substitution_specie" in trans_dict and \ trans_dict["substitution_specie"] not in bulk_sc_struct.symbol_set: self._substitution_species.add( trans_dict["substitution_specie"]) elif "inter" in trans_dict["defect_type"] and \ trans_dict["defect_site"].specie.symbol not in bulk_sc_struct.symbol_set: # added because extrinsic interstitials don't have # "substitution_specie" character... trans_dict["substitution_specie"] = trans_dict["defect_site"].specie.symbol self._substitution_species.add( trans_dict["defect_site"].specie.symbol) defect_type = trans_dict.get("defect_type", None) energy = vr.final_energy try: encut = vr.incar["ENCUT"] except: # ENCUT not specified in INCAR. Read from POTCAR encut, error_msg = get_encut_from_potcar(chrg_fldr) if error_msg: logger.warning("Not able to determine ENCUT " "in {}".format(fldr_name)) logger.warning("Parsing the rest of the " "calculations") continue comp_data = {"bulk_path": bulk_file_path, "defect_path": chrg_fldr, "encut": encut, "fldr_name": fldr_name, "supercell_size": supercell_size} if "substitution_specie" in trans_dict: comp_data["substitution_specie"] = \ trans_dict["substitution_specie"] # create Defect Object as dict, then load to DefectEntry object defect_dict = {"structure": bulk_sc_struct, "charge": chrg, "@module": "pymatgen.analysis.defects.core" } defect_site = trans_dict["defect_supercell_site"] if "vac_" in defect_type: defect_dict["@class"] = "Vacancy" elif "as_" in defect_type or "sub_" in defect_type: defect_dict["@class"] = "Substitution" substitution_specie = trans_dict["substitution_specie"] defect_site = PeriodicSite( substitution_specie, defect_site.frac_coords, defect_site.lattice, coords_are_cartesian=False) elif "int_" in defect_type: defect_dict["@class"] = "Interstitial" else: raise ValueError("defect type {} not recognized...".format(defect_type)) defect_dict.update( {"defect_site": defect_site}) defect = MontyDecoder().process_decoded( defect_dict) parsed_defects.append( DefectEntry( defect, energy - bulk_energy, parameters=comp_data)) try: parsed_defects_data = {} parsed_defects_data["bulk_entry"] = bulk_entry parsed_defects_data["defects"] = parsed_defects return parsed_defects_data except: return {} # Return Null dict due to failure
class DefectsThermodynamicsTest(PymatgenTest): def setUp(self): self.vbm_val = 2.6682 self.gap = 1.5 self.entries = list(loadfn(os.path.join(os.path.dirname(__file__), "GaAs_test_defentries.json")).values()) for entry in self.entries: entry.parameters.update( {'vbm': self.vbm_val}) self.pd = DefectPhaseDiagram(self.entries, self.vbm_val, self.gap) self.mu_elts = {Element("As"): -4.658070555, Element("Ga"): -3.7317319750000006} # make Vac_As (q= -2) only defect test single-stable-charge exceptions self.extra_entry = DefectEntry(self.entries[5].defect.copy(), 100.) sep_entries = [ent for ent in self.entries if not (ent.name == 'Vac_As_mult4' and ent.charge in [-2,-1,0,1,2])] sep_entries.append( self.extra_entry.copy()) self.sep_pd = DefectPhaseDiagram( sep_entries, self.vbm_val, self.gap) # make Vac_As (q= -2) is incompatible for larger supercell ls_entries = self.entries[:] for entry in ls_entries: if entry.name == 'Vac_As_mult4' and entry.charge == -2.: entry.parameters['is_compatible'] = False self.pd_ls_fcTrue = DefectPhaseDiagram(ls_entries, self.vbm_val, self.gap, filter_compatible=True) self.pd_ls_fcFalse = DefectPhaseDiagram(ls_entries, self.vbm_val, self.gap, filter_compatible=False) # load complete dos for fermi energy solving with open(os.path.join(test_dir, "complete_dos.json"), "r") as f: dos_dict = json.load(f) self.dos = CompleteDos.from_dict(dos_dict) def test_good_test_data(self): self.assertEqual(len(self.entries), 48) def test_suggest_charges(self): suggested_charges = self.pd.suggest_charges() for k in [ "Vac_As_mult4@0-1-2-3-4-5", "Sub_Ga_on_As_mult4@6-7-8-9-10-11", "Vac_Ga_mult4@12-13-14-15", "Sub_As_on_Ga_mult4@16-17-18-19-20-21", "Int_Ga_mult1@22-23-24-25", "Int_As_mult1@26-27-28-29-30-31-32-33-34", "Int_As_mult1@35-36-37-38-39-40-41-42-43", "Int_Ga_mult1@44-45-46-47" ]: self.assertTrue( len(suggested_charges[k]) > 0, "Could not find any suggested charges for {} with band_gap of {}".format( k, self.pd.band_gap)) pd = DefectPhaseDiagram(self.entries, 2.6682, 1.0) suggested_charges = self.pd.suggest_charges() for k in ["Vac_As_mult4@0-1-2-3-4-5", "Vac_Ga_mult4@12-13-14-15"]: self.assertTrue( len(suggested_charges[k]) > 0, "Could not find any suggested charges for {} with band_gap of {}".format( k, pd.band_gap)) #test again but with only one charge state stable for Vac_As suggested_charges = self.sep_pd.suggest_charges() self.assertEqual( set(suggested_charges['Vac_As_mult4@0-43']), set([-4])) def test_suggest_larger_supercells(self): suggested_larger_cells = self.pd_ls_fcFalse.suggest_larger_supercells() self.assertEqual( suggested_larger_cells['Vac_As_mult4@0-1-2-3-4-5'], [-2]) # raise error if filter_compatibile = True self.assertRaises( ValueError, self.pd_ls_fcTrue.suggest_larger_supercells) def test_entries(self): all_stable_entries = self.pd.all_stable_entries self.assertEqual(len(self.pd.defect_types), 8) self.assertEqual(len(all_stable_entries), sum([len(v) for v in self.pd.stable_charges.values()])) #test again but with only one charge state stable for Vac_As self.assertEqual( len(self.sep_pd.transition_level_map['Vac_As_mult4@0-43']), 0) self.assertEqual( len(self.sep_pd.stable_entries['Vac_As_mult4@0-43']), 1) self.assertEqual( len(self.sep_pd.finished_charges['Vac_As_mult4@0-43']), 2) # def test_solve_for_fermi_energy(self): fermi_energy = self.pd.solve_for_fermi_energy( 100., self.mu_elts, self.dos) self.assertAlmostEqual( fermi_energy, 0.57387314) fermi_energy = self.pd.solve_for_fermi_energy( 1000., self.mu_elts, self.dos) self.assertAlmostEqual( fermi_energy, 0.74139553) def test_solve_for_non_equilibrium_fermi_energy(self): fermi_energy = self.pd.solve_for_non_equilibrium_fermi_energy( 300., 1000., self.mu_elts, self.dos) self.assertAlmostEqual( fermi_energy, 0.29500637) fermi_energy = self.pd.solve_for_non_equilibrium_fermi_energy( 1000., 1000., self.mu_elts, self.dos) self.assertAlmostEqual( fermi_energy, 0.74139553) def test_get_dopability_limits(self): lower_lim, upper_lim = self.pd.get_dopability_limits( self.mu_elts) self.assertAlmostEqual( lower_lim, -0.39996272) self.assertAlmostEqual( upper_lim, 1.064193047) # raise error if defects are negative across gap bad_mu_elts = self.mu_elts.copy() bad_mu_elts[Element("Ga")] += 10. lower_lim, upper_lim = self.pd.get_dopability_limits( bad_mu_elts) self.assertIsNone( lower_lim) self.assertIsNone( upper_lim) def test_plot(self): #simple test that plot is produced p = self.pd.plot(saved=False) self.assertTrue( p)
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_bandfilling(self): v = Vasprun(os.path.join(PymatgenTest.TEST_FILES_DIR, "vasprun.xml")) eigenvalues = v.eigenvalues.copy() kptweights = v.actual_kpoints_weights potalign = 0.0 vbm = v.eigenvalue_band_properties[2] cbm = v.eigenvalue_band_properties[1] params = { "eigenvalues": eigenvalues, "kpoint_weights": kptweights, "potalign": potalign, "vbm": vbm, "cbm": cbm, } bfc = BandFillingCorrection() struc = PymatgenTest.get_structure("VO2") struc.make_supercell(3) vac = Vacancy(struc, struc.sites[0], charge=-3) # test trivial performing bandfilling correction bf_corr = bfc.perform_bandfill_corr(eigenvalues, kptweights, potalign, vbm, cbm) self.assertAlmostEqual(bf_corr, 0.0) self.assertFalse(bfc.metadata["num_elec_cbm"]) self.assertFalse(bfc.metadata["num_hole_vbm"]) self.assertFalse(bfc.metadata["potalign"]) # test trivial full entry bandfill evaluation de = DefectEntry(vac, 0.0, corrections={}, parameters=params, entry_id=None) corr = bfc.get_correction(de) self.assertAlmostEqual(corr["bandfilling_correction"], 0.0) # modify the eigenvalue list to have free holes hole_eigenvalues = {} for spinkey, spinset in eigenvalues.items(): hole_eigenvalues[spinkey] = [] for kptset in spinset: hole_eigenvalues[spinkey].append([]) for eig in kptset: if (eig[0] < vbm) and (eig[0] > vbm - 0.8): hole_eigenvalues[spinkey][-1].append([eig[0], 0.5]) else: hole_eigenvalues[spinkey][-1].append(eig) hole_bf_corr = bfc.perform_bandfill_corr( hole_eigenvalues, kptweights, potalign, vbm, cbm ) self.assertAlmostEqual(hole_bf_corr, -0.41138336) self.assertAlmostEqual(bfc.metadata["num_hole_vbm"], 0.8125000649) self.assertFalse(bfc.metadata["num_elec_cbm"]) # test case with only one spin and eigen-occupations are 1. one_spin_eigen = hole_eigenvalues.copy() del one_spin_eigen[list(eigenvalues.keys())[0]] bf_corr = bfc.perform_bandfill_corr( one_spin_eigen, kptweights, potalign, vbm, cbm ) self.assertAlmostEqual(bf_corr, -0.14487501159000005) # test case with only one spin and eigen-occupations are 2. one_spin_eigen_twooccu = one_spin_eigen.copy() for kptset in one_spin_eigen_twooccu.values(): for bandset in kptset: for occuset in bandset: if occuset[1] == 1.0: occuset[1] = 2.0 elif occuset[1] == 0.5: occuset[1] = 1.0 bf_corr = bfc.perform_bandfill_corr( one_spin_eigen_twooccu, kptweights, potalign, vbm, cbm ) self.assertAlmostEqual(bf_corr, -0.14487501159000005)
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"]), )