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_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., corrections={}, parameters={}, entry_id=None) dc = DefectCompatibility() dentry = dc.delocalization_analysis( de) self.assertIsNotNone( dentry)
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)
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 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 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., 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_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.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_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 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.1666667], [1.5204, 0.1666667], [1.53465, 0.1666667], [1.5498, 0.0833333]] 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.58333338) self.assertFalse(val['unoccupied_def_levels'])
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_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 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) #TODO: add correction analysis with Kumagai correction # 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=1.) dentry = dc.process_entry( dentry) self.assertAlmostEqual( dentry.corrections['bandedgeshifting_correction'], 1.85000005) self.assertAlmostEqual( dentry.corrections['bandfilling_correction'], -3.244048) self.assertAlmostEqual( dentry.corrections['charge_correction'], 0.) # turn off band filling and band edge shifting dc = DefectCompatibility( free_chg_cutoff=1., 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_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 __init__(self, defect_entry, compatibility=DefectCompatibility(), defect_vr = None, bulk_vr = None): """ Parse a defect object using features that resemble that of a standard DefectBuilder object (emmet), but without the requirement of atomate. Also allows for use of DefectCompatibility object within pymatgen :param defect_entry (DefectEntry): DefectEntry of interest (using the bulk supercell as bulk_structure) NOTE: to make use of methods within the class, bulk_path and and defect_path must exist within the defect_entry parameters class. :param compatibility (DefectCompatibility): Compatibility class instance for performing compatibility analysis on defect entry. :param defect_vr (Vasprun): :param bulk_vr (Vasprun): """ self.defect_entry = defect_entry self.compatibility = compatibility self.defect_vr = defect_vr self.bulk_vr = bulk_vr
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_is_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 params = { 'initial_defect_structure': initial_defect_structure, 'final_defect_structure': final_defect_structure, 'sampling_radius': sampling_radius, '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.is_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.is_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 params = { 'initial_defect_structure': initial_defect_structure, 'final_defect_structure': final_defect_structure, 'sampling_radius': sampling_radius, 'is_compatible': True } dentry = DefectEntry(inter, 0., corrections={}, parameters=params, entry_id=None) dentry = dc.is_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_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_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_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)
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 from_paths( path_to_defect, path_to_bulk, dielectric, defect_charge, mpid = None, compatibility=DefectCompatibility(), initial_defect_structure = None): """ Identify defect object based on file paths. Minimal parsing performing for instantiating the SingleDefectParser class. :param path_to_defect (str): path to defect file of interest :param path_to_bulk (str): path to bulk file of interest :param dielectric (float or 3x3 matrix): ionic + static contributions to dielectric constant :param defect_charge (int): :param mpid (str): :param compatibility (DefectCompatibility): Compatibility class instance for performing compatibility analysis on defect entry. Return: Instance of the SingleDefectParser class. """ parameters = {"bulk_path": path_to_bulk, "defect_path": path_to_defect, "dielectric": dielectric, "mpid": mpid} # add bulk simple properties bulk_vr = Vasprun( os.path.join(path_to_bulk, "vasprun.xml")) bulk_energy = bulk_vr.final_energy bulk_sc_structure = bulk_vr.initial_structure.copy() # add defect simple properties defect_vr = Vasprun( os.path.join(path_to_defect, "vasprun.xml")) defect_energy = defect_vr.final_energy # Can specify initial defect structure (to help PyCDT find the defect site if # multiple relaxations were required, else use from defect relaxation OUTCAR: if initial_defect_structure: initial_defect_structure = Poscar.from_file(initial_defect_structure).structure.copy() else: initial_defect_structure = defect_vr.initial_structure.copy() # identify defect site, structural information, and create defect object num_ids = len(initial_defect_structure) num_bulk = len(bulk_sc_structure) if num_ids == num_bulk - 1: defect_type = "Vacancy" elif num_ids == num_bulk + 1: defect_type = "Interstitial" elif num_ids == num_bulk: defect_type = "Substitution" else: raise ValueError("Could not identify defect type just from number of sites in structure: " "{} in bulk vs. {} in defect?".format( num_ids, num_bulk )) defect_index_sc_coords = None transformation_path = os.path.join( path_to_defect, "transformation.json") if os.path.exists( transformation_path): tf = loadfn( transformation_path) site = tf["defect_supercell_site"] if defect_type == "Vacancy": poss_deflist = sorted( bulk_sc_structure.get_sites_in_sphere(site.coords, 0.1, include_index=True), key=lambda x: x[1]) else: poss_deflist = sorted( initial_defect_structure.get_sites_in_sphere(site.coords, 0.1, include_index=True), key=lambda x: x[1]) if not len(poss_deflist): raise ValueError("{} specified defect site {}, but could not find it in bulk_supercell." " Abandoning parsing".format( transformation_path, site)) else: defect_index_sc_coords = poss_deflist[0][2] else: print("No transformation file exists at {}.\nCalculating defect index manually" " (proceed with caution)".format( transformation_path)) # IF not transformation file exists, the defect_index_sc_coords will not be identified in previous routine, # proceed by identifying the defect site through a comparison of bulk sites and initial defect structure sites. # WARNING: this can cause issues if intial_defect_structure is slightly different than # bulk_sc_structure (as a result of multiple relaxation steps, for example) if defect_index_sc_coords is None: bulksites = [site.frac_coords for site in bulk_sc_structure] initsites = [site.frac_coords for site in initial_defect_structure] distmatrix = initial_defect_structure.lattice.get_all_distances(bulksites, initsites) min_dist_with_index = [[min(distmatrix[bulk_index]), int(bulk_index), int(distmatrix[bulk_index].argmin())] for bulk_index in range(len(distmatrix))] # list of [min dist, bulk ind, defect ind] site_matching_indices = [] poss_defect = [] if defect_type in ["Vacancy", "Interstitial"]: for mindist, bulk_index, defect_index in min_dist_with_index: if mindist < 0.1: site_matching_indices.append([bulk_index, defect_index]) elif defect_type == "Vacancy": poss_defect.append([bulk_index, bulksites[bulk_index][:]]) if defect_type == "Interstitial": poss_defect = [[ind, fc[:]] for ind, fc in enumerate(initsites) \ if ind not in np.array(site_matching_indices)[:, 1]] elif defect_type == "Substitution": for mindist, bulk_index, defect_index in min_dist_with_index: species_match = bulk_sc_structure[bulk_index].specie == \ initial_defect_structure[defect_index].specie if mindist < 0.1 and species_match: site_matching_indices.append([bulk_index, defect_index]) elif not species_match: poss_defect.append([defect_index, initsites[defect_index][:]]) if len(poss_defect) == 1: defect_index_sc_coords = poss_defect[0][0] else: raise ValueError("Found {} possible defect sites when matching bulk and " "defect structure".format(len(poss_defect))) if len(set(np.array(site_matching_indices)[:, 0])) != len(set(np.array(site_matching_indices)[:, 1])): raise ValueError("Error occured in site_matching routine. Double counting of site matching " "occured:{}\nAbandoning structure parsing.".format(site_matching_indices)) if defect_type == "Vacancy": defect_site = bulk_sc_structure[ defect_index_sc_coords] else: defect_site = initial_defect_structure[ defect_index_sc_coords] for_monty_defect = {"@module": "pymatgen.analysis.defects.core", "@class": defect_type, "charge": defect_charge, "structure": bulk_sc_structure, "defect_site": defect_site} defect = MontyDecoder().process_decoded(for_monty_defect) test_defect_structure = defect.generate_defect_structure() if not StructureMatcher(stol=0.5, primitive_cell=False, scale=False, attempt_supercell=False, allow_subset=False).fit(test_defect_structure, defect_vr.initial_structure): # NOTE: this does not insure that cartesian coordinates or indexing are identical # Note: I've changed stol to 0.5 to fix matching for defects that move the f**k about yo raise ValueError("Error in defect object matching!") defect_entry = DefectEntry(defect, defect_energy - bulk_energy, corrections={}, parameters=parameters) return SingleDefectParser( defect_entry, compatibility=compatibility, defect_vr=defect_vr, bulk_vr=bulk_vr)