From 2677b9ef00be094d8ace536f18ddedb1c0e42635 Mon Sep 17 00:00:00 2001 From: Sean Kavanagh Date: Thu, 31 Oct 2024 14:55:30 -0400 Subject: [PATCH] Use `LinearAssignment` in defect site matching for super robust, efficient site matching --- doped/utils/parsing.py | 50 +++--- tests/test_analysis.py | 327 +++++++++++++++++---------------------- tests/test_stenciling.py | 3 + 3 files changed, 168 insertions(+), 212 deletions(-) diff --git a/doped/utils/parsing.py b/doped/utils/parsing.py index 258d717f..74c53a80 100644 --- a/doped/utils/parsing.py +++ b/doped/utils/parsing.py @@ -15,6 +15,7 @@ import numpy as np from monty.io import reverse_readfile from monty.serialization import loadfn +from pymatgen.analysis.structure_matcher import LinearAssignment, pbc_shortest_vectors from pymatgen.core.periodic_table import Element from pymatgen.core.structure import Composition, Lattice, PeriodicSite, Structure from pymatgen.electronic_structure.core import Spin @@ -454,10 +455,11 @@ def process_substitution(bulk_supercell, defect_supercell, composition_diff): bulk_old_species_coords, bulk_old_species_idx = get_coords_and_idx_of_species( bulk_supercell, old_species ) - bulk_site_arg_idx = find_missing_idx( + _bulk_coords, bulk_site_arg_idx = find_nearest_coords( bulk_old_species_coords, - get_coords_and_idx_of_species(defect_supercell, old_species)[0], # defect_old_species + defect_new_species_coords[defect_site_arg_idx], # defect coords bulk_supercell.lattice, + return_idx=True, ) bulk_site_idx = bulk_old_species_idx[bulk_site_arg_idx] unrelaxed_defect_structure = _create_unrelaxed_defect_structure( @@ -618,26 +620,19 @@ def find_missing_idx( lattice (Lattice): The lattice object to use with the fractional coordinates. """ - distance_matrix = lattice.get_all_distances(frac_coords1, frac_coords2) - - # down columns if frac_coords1 is larger (MxN matrix, M>N, axis=0 -> down columns), else across rows - site_matches = distance_matrix.argmin(axis=0 if len(frac_coords1) > len(frac_coords2) else -1) - - # TODO: Trial linear assignment, or matching the other way, if we fail - # TODO: Use linear assignment in stenciling? (for choosing candidate sites) Once tests setup? - # TODO: Depending on speed, could just go to linear assignment from the start. Either way can - # try successive stol increases - if len(np.unique(site_matches)) != len(site_matches): - searched_structure = "bulk" if len(frac_coords1) > len(frac_coords2) else "defect" - raise RuntimeError( - f"Could not uniquely determine defect site in the {searched_structure} supercell. " - f"Remember the bulk and defect supercells should have the same definitions/basis sets for " - f"site-matching (parsing) to be possible." - ) - - return next( - iter(set(np.arange(max(len(frac_coords1), len(frac_coords2)), dtype=int)) - set(site_matches)) + subset, superset = ( # supa-set + (frac_coords1, frac_coords2) + if len(frac_coords1) < len(frac_coords2) + else (frac_coords2, frac_coords1) ) + # in theory this could be made even faster using ``lll_frac_tol`` as in ``_cart_dists()`` in + # ``pymatgen``, with smart choice of initial ``lll_frac_tol`` and scanning upwards if the match is + # below the threshold tolerance (as in ``_scan_sm_stol_till_match()``), but in practice this + # function seems to be incredibly fast as is. Can revisit if it ever becomes a bottleneck + _vecs, d_2 = pbc_shortest_vectors(lattice, subset, superset, return_d2=True) + site_matches = LinearAssignment(d_2).solution + + return next(iter(set(np.arange(len(superset), dtype=int)) - set(site_matches))) def _create_unrelaxed_defect_structure( @@ -795,10 +790,15 @@ def check_atom_mapping_far_from_defect( for site, dist_to_defect in zip(defect, dists_to_defect): if dist_to_defect > wigner_seitz_radius: bulk_frac_coords = find_nearest_coords( # get closest site in bulk to defect site - bulk_species_outside_near_WS_coord_dict[site.specie.symbol], - site.frac_coords, - bulk.lattice, - ) + bulk_species_outside_near_WS_coord_dict.get( + site.specie.symbol, + [ + defect_frac_coords, + ], + ), + site.frac_coords, # if species not in bulk, should be an extrinsic + bulk.lattice, # substitution/interstitial, so use defect coords (also likely means some + ) # issue in defect site parsing... far_from_defect_disps[site.specie.symbol].append( round( site.distance_and_image_from_frac_coords(bulk_frac_coords)[0], diff --git a/tests/test_analysis.py b/tests/test_analysis.py index f4d284fb..16c43282 100644 --- a/tests/test_analysis.py +++ b/tests/test_analysis.py @@ -54,6 +54,17 @@ def if_present_rm(path): shutil.rmtree(path) +def _create_dp_and_capture_warnings(*args, **kwargs): + with warnings.catch_warnings(record=True) as w: + try: + dp = DefectsParser(*args, **kwargs) + except Exception as e: + print([warn.message for warn in w]) # for debugging + raise e + print([warn.message for warn in w]) # for debugging + return dp, w + + class DefectsParsingTestCase(unittest.TestCase): def setUp(self): self.module_path = os.path.dirname(os.path.abspath(__file__)) @@ -339,13 +350,11 @@ def _check_default_CdTe_DefectsParser_outputs( @custom_mpl_image_compare(filename="CdTe_example_defects_plot.png") def test_DefectsParser_CdTe(self): - with warnings.catch_warnings(record=True) as w: - default_dp = DefectsParser( - output_path=self.CdTe_EXAMPLE_DIR, - dielectric=9.13, - json_filename="CdTe_example_defect_dict.json", - ) # for testing in test_thermodynamics.py - print([warn.message for warn in w]) # for debugging + default_dp, w = _create_dp_and_capture_warnings( + output_path=self.CdTe_EXAMPLE_DIR, + dielectric=9.13, + json_filename="CdTe_example_defect_dict.json", + ) # for testing in test_thermodynamics.py self._check_default_CdTe_DefectsParser_outputs(default_dp, w) # saves CdTe_example_thermo.json # test reloading DefectsParser @@ -375,14 +384,12 @@ def test_DefectsParser_CdTe_without_multiprocessing(self): os.path.join(self.CdTe_EXAMPLE_DIR, "orig_CdTe_example_thermo.json"), ) # moved back in tearDown # test same behaviour without multiprocessing: - with warnings.catch_warnings(record=True) as w: - dp = DefectsParser( - output_path=self.CdTe_EXAMPLE_DIR, - dielectric=9.13, - processes=1, - parse_projected_eigen=False, - ) - print([warn.message for warn in w]) # for debugging + dp, w = _create_dp_and_capture_warnings( + output_path=self.CdTe_EXAMPLE_DIR, + dielectric=9.13, + processes=1, + parse_projected_eigen=False, + ) self._check_default_CdTe_DefectsParser_outputs(dp, w) # integration test using parsed CdTe thermo and chempots for plotting: @@ -397,12 +404,10 @@ def test_DefectsParser_CdTe_filterwarnings(self): ) # moved back in tearDown # check using filterwarnings works as expected: warnings.filterwarnings("ignore", "Multiple") - with warnings.catch_warnings(record=True) as w: - dp = DefectsParser( - output_path=self.CdTe_EXAMPLE_DIR, - dielectric=9.13, - ) - print([warn.message for warn in w]) # for debugging + dp, w = _create_dp_and_capture_warnings( + output_path=self.CdTe_EXAMPLE_DIR, + dielectric=9.13, + ) self._check_default_CdTe_DefectsParser_outputs(dp, w, multiple_outcars_warning=False) warnings.filterwarnings("default", "Multiple") @@ -417,22 +422,18 @@ def test_DefectsParser_CdTe_dist_tol(self): ) # moved back in tearDown # test with reduced dist_tol: # Int_Te_3_Unperturbed merged with Int_Te_3 with default dist_tol = 1.5, now no longer merged - with warnings.catch_warnings(record=True) as w: - dp = DefectsParser( - output_path=self.CdTe_EXAMPLE_DIR, dielectric=9.13, parse_projected_eigen=False - ) - print([warn.message for warn in w]) # for debugging + dp, w = _create_dp_and_capture_warnings( + output_path=self.CdTe_EXAMPLE_DIR, dielectric=9.13, parse_projected_eigen=False + ) self._check_default_CdTe_DefectsParser_outputs(dp, w, dist_tol=0.1) @custom_mpl_image_compare(filename="CdTe_Te_Cd_+1_eigenvalue_plot.png") def test_DefectsParser_CdTe_no_dielectric_json(self): # test no dielectric and no JSON: - with warnings.catch_warnings(record=True) as w: - dp = DefectsParser( - output_path=self.CdTe_EXAMPLE_DIR, - json_filename=False, - ) - print([warn.message for warn in w]) # for debugging + dp, w = _create_dp_and_capture_warnings( + output_path=self.CdTe_EXAMPLE_DIR, + json_filename=False, + ) assert any( "The dielectric constant (`dielectric`) is needed to compute finite-size charge " "corrections, but none was provided" in str(warn.message) @@ -456,17 +457,15 @@ def test_DefectsParser_CdTe_custom_settings(self): ) # moved back in tearDown # test custom settings: - with warnings.catch_warnings(record=True) as w: - dp = DefectsParser( - output_path=self.CdTe_EXAMPLE_DIR, - dielectric=[9.13, 9.13, 9.13], - error_tolerance=0.01, - skip_corrections=False, - bulk_band_gap_vr=f"{self.CdTe_BULK_DATA_DIR}/vasprun.xml", - processes=4, - json_filename="test_pop.json", - ) - print([warn.message for warn in w]) # for debugging + dp, w = _create_dp_and_capture_warnings( + output_path=self.CdTe_EXAMPLE_DIR, + dielectric=[9.13, 9.13, 9.13], + error_tolerance=0.01, + skip_corrections=False, + bulk_band_gap_vr=f"{self.CdTe_BULK_DATA_DIR}/vasprun.xml", + processes=4, + json_filename="test_pop.json", + ) assert any( all( i in str(warn.message) @@ -506,7 +505,7 @@ def test_DefectsParser_CdTe_unrecognised_subfolder(self): def test_DefectsParser_CdTe_skip_corrections(self): # skip_corrections: - dp = DefectsParser( + dp, _w = _create_dp_and_capture_warnings( output_path=self.CdTe_EXAMPLE_DIR, skip_corrections=True, parse_projected_eigen=False ) self._check_DefectsParser(dp, skip_corrections=True) @@ -514,12 +513,10 @@ def test_DefectsParser_CdTe_skip_corrections(self): def test_DefectsParser_CdTe_aniso_dielectric(self): # anisotropic dielectric fake_aniso_dielectric = [1, 2, 3] - with warnings.catch_warnings(record=True) as w: - dp = DefectsParser( - output_path=self.CdTe_EXAMPLE_DIR, - dielectric=fake_aniso_dielectric, - ) - print([warn.message for warn in w]) # for debugging + dp, w = _create_dp_and_capture_warnings( + output_path=self.CdTe_EXAMPLE_DIR, + dielectric=fake_aniso_dielectric, + ) assert any( all( i in str(warn.message) @@ -534,11 +531,9 @@ def test_DefectsParser_CdTe_aniso_dielectric(self): ) # correction warning assert any( - "Defects: ['v_Cd_-2', 'v_Cd_-1'] each encountered the same warning:" in str(warn.message) - for warn in w - ) or any( - "Defects: ['v_Cd_-1', 'v_Cd_-2'] each encountered the same warning:" in str(warn.message) + f"Defects: {i} each encountered the same warning:" in str(warn.message) for warn in w + for i in ["['v_Cd_-2', 'v_Cd_-1']", "['v_Cd_-1', 'v_Cd_-2']"] ) for i in [ @@ -575,14 +570,13 @@ def test_DefectsParser_CdTe_aniso_dielectric(self): self._check_DefectsParser(dp) def test_DefectsParser_CdTe_kpoints_mismatch(self): - with warnings.catch_warnings(record=True) as w: - dp = DefectsParser( - output_path=self.CdTe_EXAMPLE_DIR, - bulk_path=f"{self.module_path}/data/CdTe", # vasp_gam bulk vr here - dielectric=9.13, - parse_projected_eigen=False, - ) - print([warn.message for warn in w]) # for debugging + dp, w = _create_dp_and_capture_warnings( + output_path=self.CdTe_EXAMPLE_DIR, + bulk_path=f"{self.module_path}/data/CdTe", # vasp_gam bulk vr here + dielectric=9.13, + parse_projected_eigen=False, + ) + for i in [ "Defects: ", # multiple warnings here so best to do this way: "'Int_Te_3_1'", @@ -608,13 +602,11 @@ def test_DefectsParser_CdTe_kpoints_mismatch(self): dp.get_defect_thermodynamics() # test thermo generation works fine def test_DefectsParser_corrections_errors_warning(self): - with warnings.catch_warnings(record=True) as w: - DefectsParser( - output_path=self.CdTe_EXAMPLE_DIR, - dielectric=9.13, - error_tolerance=0.001, - ) # low error tolerance to force warnings - print([warn.message for warn in w]) # for debugging + _dp, w = _create_dp_and_capture_warnings( + output_path=self.CdTe_EXAMPLE_DIR, + dielectric=9.13, + error_tolerance=0.001, + ) # low error tolerance to force warnings for i in [ "Estimated error in the Freysoldt (FNV) ", @@ -634,13 +626,11 @@ def test_DefectsParser_corrections_errors_warning(self): @custom_mpl_image_compare(filename="YTOS_example_defects_plot.png") def test_DefectsParser_YTOS_default_bulk(self): - with warnings.catch_warnings(record=True) as w: - dp = DefectsParser( - output_path=self.YTOS_EXAMPLE_DIR, - dielectric=self.ytos_dielectric, - json_filename="YTOS_example_defect_dict.json", - ) # for testing in test_thermodynamics.py - print([warn.message for warn in w]) # for debugging + dp, w = _create_dp_and_capture_warnings( + output_path=self.YTOS_EXAMPLE_DIR, + dielectric=self.ytos_dielectric, + json_filename="YTOS_example_defect_dict.json", + ) # for testing in test_thermodynamics.py assert not w self._check_DefectsParser(dp) thermo = dp.get_defect_thermodynamics() @@ -660,13 +650,11 @@ def test_DefectsParser_YTOS_macOS_duplicated_OUTCAR(self): with open(f"{self.YTOS_EXAMPLE_DIR}/F_O_1/.DS_Store", "w") as f: f.write("test pop") - with warnings.catch_warnings(record=True) as w: - dp = DefectsParser( - output_path=self.YTOS_EXAMPLE_DIR, - dielectric=self.ytos_dielectric, - json_filename="YTOS_example_defect_dict.json", - ) # for testing in test_thermodynamics.py - print([warn.message for warn in w]) # for debugging + dp, w = _create_dp_and_capture_warnings( + output_path=self.YTOS_EXAMPLE_DIR, + dielectric=self.ytos_dielectric, + json_filename="YTOS_example_defect_dict.json", + ) # for testing in test_thermodynamics.py assert not w # hidden files ignored self._check_DefectsParser(dp) thermo = dp.get_defect_thermodynamics() @@ -685,14 +673,12 @@ def test_DefectsParser_YTOS_macOS_duplicated_bulk_OUTCAR(self): f.write("test pop") with open(f"{self.YTOS_EXAMPLE_DIR}/F_O_1/.DS_Store", "w") as f: f.write("test pop") - with warnings.catch_warnings(record=True) as w: - dp = DefectsParser( - output_path=self.YTOS_EXAMPLE_DIR, - dielectric=self.ytos_dielectric, - json_filename="YTOS_example_defect_dict.json", - parse_projected_eigen=False, - ) # for testing in test_thermodynamics.py - print([warn.message for warn in w]) # for debugging + dp, w = _create_dp_and_capture_warnings( + output_path=self.YTOS_EXAMPLE_DIR, + dielectric=self.ytos_dielectric, + json_filename="YTOS_example_defect_dict.json", + parse_projected_eigen=False, + ) # for testing in test_thermodynamics.py assert not w # hidden files ignored self._check_DefectsParser(dp) thermo = dp.get_defect_thermodynamics() @@ -703,14 +689,12 @@ def test_DefectsParser_YTOS_macOS_duplicated_bulk_OUTCAR(self): @custom_mpl_image_compare(filename="YTOS_example_defects_plot.png") def test_DefectsParser_YTOS_explicit_bulk(self): - with warnings.catch_warnings(record=True) as w: - dp = DefectsParser( - output_path=self.YTOS_EXAMPLE_DIR, - bulk_path=os.path.join(self.YTOS_EXAMPLE_DIR, "Bulk"), - dielectric=self.ytos_dielectric, - parse_projected_eigen=False, - ) - print([warn.message for warn in w]) # for debugging + dp, w = _create_dp_and_capture_warnings( + output_path=self.YTOS_EXAMPLE_DIR, + bulk_path=os.path.join(self.YTOS_EXAMPLE_DIR, "Bulk"), + dielectric=self.ytos_dielectric, + parse_projected_eigen=False, + ) assert not w self._check_DefectsParser(dp) thermo = dp.get_defect_thermodynamics() @@ -745,15 +729,13 @@ def test_extrinsic_Sb2Se3(self): f"subfolders) and 'bulk' in the folder name" in str(exc.value) ) - with warnings.catch_warnings(record=True) as w: # no warning about negative corrections with - # strong anisotropic dielectric: - Sb2Se3_O_dp = DefectsParser( - output_path=f"{self.Sb2Se3_DATA_DIR}/defect", - bulk_path=f"{self.Sb2Se3_DATA_DIR}/bulk", - dielectric=self.Sb2Se3_dielectric, - json_filename="Sb2Se3_O_example_defect_dict.json", - ) # for testing in test_thermodynamics.py - print([warn.message for warn in w]) # for debugging + # no warning about negative corrections with strong anisotropic dielectric: + Sb2Se3_O_dp, w = _create_dp_and_capture_warnings( + output_path=f"{self.Sb2Se3_DATA_DIR}/defect", + bulk_path=f"{self.Sb2Se3_DATA_DIR}/bulk", + dielectric=self.Sb2Se3_dielectric, + json_filename="Sb2Se3_O_example_defect_dict.json", + ) # for testing in test_thermodynamics.py assert not w # no warnings self._check_DefectsParser(Sb2Se3_O_dp) Sb2Se3_O_thermo = Sb2Se3_O_dp.get_defect_thermodynamics() @@ -762,14 +744,12 @@ def test_extrinsic_Sb2Se3(self): ) # for test_plotting # warning about negative corrections when using (fake) isotropic dielectric: - with warnings.catch_warnings(record=True) as w: - Sb2Se3_O_dp = DefectsParser( - output_path=f"{self.Sb2Se3_DATA_DIR}/defect", - bulk_path=f"{self.Sb2Se3_DATA_DIR}/bulk", - dielectric=40, # fake isotropic dielectric - parse_projected_eigen=False, - ) - print([warn.message for warn in w]) # for debugging + Sb2Se3_O_dp, w = _create_dp_and_capture_warnings( + output_path=f"{self.Sb2Se3_DATA_DIR}/defect", + bulk_path=f"{self.Sb2Se3_DATA_DIR}/bulk", + dielectric=40, # fake isotropic dielectric + parse_projected_eigen=False, + ) assert any( all( i in str(warn.message) @@ -787,14 +767,12 @@ def test_extrinsic_Sb2Se3(self): return Sb2Se3_O_thermo.plot(chempots={"O": -8.9052, "Se": -5}) # example chempots def test_extrinsic_Sb2Se3_parsing_with_single_defect_dir(self): - with warnings.catch_warnings(record=True) as w: # no warning about negative corrections with - # strong anisotropic dielectric: - Sb2Se3_O_dp = DefectsParser( - output_path=f"{self.Sb2Se3_DATA_DIR}/defect/O_-2", - bulk_path=f"{self.Sb2Se3_DATA_DIR}/bulk", - dielectric=self.Sb2Se3_dielectric, - ) - print([warn.message for warn in w]) # for debugging + # no warning about negative corrections with strong anisotropic dielectric: + Sb2Se3_O_dp, w = _create_dp_and_capture_warnings( + output_path=f"{self.Sb2Se3_DATA_DIR}/defect/O_-2", + bulk_path=f"{self.Sb2Se3_DATA_DIR}/bulk", + dielectric=self.Sb2Se3_dielectric, + ) assert not w # no warnings self._check_DefectsParser(Sb2Se3_O_dp) Sb2Se3_O_thermo = Sb2Se3_O_dp.get_defect_thermodynamics() @@ -807,13 +785,11 @@ def test_duplicate_folders_Sb2Se3(self): shutil.copytree(f"{self.Sb2Se3_DATA_DIR}/defect/O_2", f"{self.Sb2Se3_DATA_DIR}/defect/O_b_2") shutil.copytree(f"{self.Sb2Se3_DATA_DIR}/defect/O_2", f"{self.Sb2Se3_DATA_DIR}/defect/O_a_1") shutil.copytree(f"{self.Sb2Se3_DATA_DIR}/defect/O_1", f"{self.Sb2Se3_DATA_DIR}/defect/O_b_1") - with warnings.catch_warnings(record=True) as w: - Sb2Se3_O_dp = DefectsParser( - output_path=f"{self.Sb2Se3_DATA_DIR}/defect", - bulk_path=f"{self.Sb2Se3_DATA_DIR}/bulk", - dielectric=self.Sb2Se3_dielectric, - ) # for testing in test_thermodynamics.py - print([warn.message for warn in w]) # for debugging + Sb2Se3_O_dp, w = _create_dp_and_capture_warnings( + output_path=f"{self.Sb2Se3_DATA_DIR}/defect", + bulk_path=f"{self.Sb2Se3_DATA_DIR}/bulk", + dielectric=self.Sb2Se3_dielectric, + ) # for testing in test_thermodynamics.py assert any( "The following parsed defect entries were found to be duplicates (exact same defect " "supercell energies)" in str(warn.message) @@ -829,14 +805,12 @@ def test_duplicate_folders_Sb2Se3(self): @custom_mpl_image_compare(filename="Sb2Si2Te6_v_Sb_-3_eFNV_plot_no_intralayer.png") def test_sb2si2te6_eFNV(self): - with warnings.catch_warnings(record=True) as w: - dp = DefectsParser( - self.Sb2Si2Te6_DATA_DIR, - dielectric=self.Sb2Si2Te6_dielectric, - json_filename="Sb2Si2Te6_example_defect_dict.json", # testing in test_thermodynamics.py - parse_projected_eigen=False, - ) - print([str(warning.message) for warning in w]) # for debugging + dp, w = _create_dp_and_capture_warnings( + self.Sb2Si2Te6_DATA_DIR, + dielectric=self.Sb2Si2Te6_dielectric, + json_filename="Sb2Si2Te6_example_defect_dict.json", # testing in test_thermodynamics.py + parse_projected_eigen=False, + ) assert any( "Estimated error in the Kumagai (eFNV) charge correction for certain defects" in str(warning.message) @@ -908,13 +882,11 @@ def test_sb2si2te6_eFNV(self): @custom_mpl_image_compare(filename="neutral_v_O_plot.png") def test_V2O5_FNV(self): # only three inequivalent neutral V_O present - with warnings.catch_warnings(record=True) as w: - dp = DefectsParser( - self.V2O5_DATA_DIR, - dielectric=[4.186, 19.33, 17.49], - json_filename="V2O5_example_defect_dict.json", # testing in test_thermodynamics.py - ) - print([str(warning.message) for warning in w]) # for debugging + dp, w = _create_dp_and_capture_warnings( + self.V2O5_DATA_DIR, + dielectric=[4.186, 19.33, 17.49], + json_filename="V2O5_example_defect_dict.json", # testing in test_thermodynamics.py + ) assert not w # no warnings assert len(dp.defect_dict) == 3 # only three inequivalent neutral V_O present @@ -940,9 +912,7 @@ def test_V2O5_same_named_defects(self): if os.path.isdir(f"V2O5_test/{i}") and i.startswith("v_O"): shutil.move(f"V2O5_test/{i}", f"V2O5_test/unrecognised_{i[-1]}") - with warnings.catch_warnings(record=True) as w: - dp = DefectsParser("V2O5_test", dielectric=[4.186, 19.33, 17.49]) - print([str(warning.message) for warning in w]) # for debugging + dp, w = _create_dp_and_capture_warnings("V2O5_test", dielectric=[4.186, 19.33, 17.49]) assert any( "The following parsed defect entries were found to be duplicates" in str(warning.message) for warning in w @@ -990,11 +960,9 @@ def test_SrTiO3_diff_ISYM_bulk_defect_and_concentration_funcs(self): ] ) - with warnings.catch_warnings(record=True) as w: - dp = DefectsParser( - self.SrTiO3_DATA_DIR, dielectric=6.33, parse_projected_eigen=False - ) # wrong dielectric from Kanta - print([str(warning.message) for warning in w]) # for debugging + dp, w = _create_dp_and_capture_warnings( + self.SrTiO3_DATA_DIR, dielectric=6.33, parse_projected_eigen=False + ) # wrong dielectric from Kanta assert len(w) == 1 assert all( i in str(w[0].message) @@ -1175,9 +1143,7 @@ def test_ZnS_non_diagonal_NKRED_mismatch(self): diagonal periodicity-breaking supercell, and with NKRED mismatch from defect and bulk supercells. """ - with warnings.catch_warnings(record=True) as w: - dp = DefectsParser(self.ZnS_DATA_DIR, dielectric=8.9) - print([str(warning.message) for warning in w]) # for debugging + dp, w = _create_dp_and_capture_warnings(self.ZnS_DATA_DIR, dielectric=8.9) assert len(w) == 1 assert all( i in str(w[0].message) @@ -1236,10 +1202,8 @@ def test_solid_solution_oxi_state_handling(self): 'undetermined' by ``doped``, as this property isn't necessary when parsing). """ - with warnings.catch_warnings(record=True) as w: - # no warning with no dielectric/OUTCARs, as is neutral - dp = DefectsParser(self.SOLID_SOLUTION_DATA_DIR, parse_projected_eigen=False) - print([str(warning.message) for warning in w]) # for debugging + # no warning with no dielectric/OUTCARs, as is neutral + dp, w = _create_dp_and_capture_warnings(self.SOLID_SOLUTION_DATA_DIR, parse_projected_eigen=False) assert not w assert len(dp.defect_dict) == 1 self._check_DefectsParser(dp) @@ -1263,13 +1227,10 @@ def test_CaO_symmetry_determination(self): relaxed symmetry determination scheme with old default of ``symprec=0.2``). """ - with warnings.catch_warnings(record=True) as w: - dp = DefectsParser( - output_path=self.CaO_DATA_DIR, - skip_corrections=True, - ) - - print([str(warning.message) for warning in w]) # for debugging + dp, w = _create_dp_and_capture_warnings( + output_path=self.CaO_DATA_DIR, + skip_corrections=True, + ) assert not w assert len(dp.defect_dict) == 4 self._check_DefectsParser(dp, skip_corrections=True) @@ -1296,13 +1257,10 @@ def test_BiOI_v_Bi_symmetry_determination(self): Test parsing v_Bi_+1 from BiOI defect calculations, and confirming the correct point group symmetry of Cs is determined. """ - with warnings.catch_warnings(record=True) as w: - dp = DefectsParser( - output_path=self.BiOI_DATA_DIR, - skip_corrections=True, - ) - - print([str(warning.message) for warning in w]) # for debugging + dp, w = _create_dp_and_capture_warnings( + output_path=self.BiOI_DATA_DIR, + skip_corrections=True, + ) assert not w assert len(dp.defect_dict) == 1 self._check_DefectsParser(dp, skip_corrections=True) @@ -1312,15 +1270,12 @@ def test_BiOI_v_Bi_symmetry_determination(self): assert dp.defect_dict["v_Bi_+1"].calculation_metadata["relaxed point symmetry"] == "Cs" # test setting symprec during parsing - with warnings.catch_warnings(record=True) as w: - dp = DefectsParser( - output_path=self.BiOI_DATA_DIR, - skip_corrections=True, - symprec=0.01, - parse_projected_eigen=False, - ) - - print([str(warning.message) for warning in w]) # for debugging + dp, w = _create_dp_and_capture_warnings( + output_path=self.BiOI_DATA_DIR, + skip_corrections=True, + symprec=0.01, + parse_projected_eigen=False, + ) assert not w assert len(dp.defect_dict) == 1 self._check_DefectsParser(dp, skip_corrections=True) @@ -2691,9 +2646,7 @@ def test_point_symmetry_periodicity_breaking(self): the ``DefectThermodynamics.get_symmetries_and_degeneracies()`` tests in ``test_thermodynamics.py``. """ - with warnings.catch_warnings(record=True) as w: - dp = DefectsParser(self.ZnS_DATA_DIR, dielectric=8.9) - print([str(warning.message) for warning in w]) # for debugging + dp, w = _create_dp_and_capture_warnings(self.ZnS_DATA_DIR, dielectric=8.9) assert len(dp.defect_dict) == 17 with warnings.catch_warnings(record=True) as w: diff --git a/tests/test_stenciling.py b/tests/test_stenciling.py index e3a1b970..d07be65a 100644 --- a/tests/test_stenciling.py +++ b/tests/test_stenciling.py @@ -70,6 +70,9 @@ def setUp(self): self.Se_old_new_names_dict = {"vac_1_Se": "v_Se", "Int_Se_1": "Se_i_C2"} self.tight_sm = StructureMatcher(stol=0.02, comparator=ElementComparator(), primitive_cell=False) # TODO: Do one of the Se extrinsic substitutions/interstitials as a test too + # TODO: Once tests setup, should look at using `LinearAssignment` in + # `_stencil_target_cell_from_big_cell`; more efficient, robust, and then easier to manage with + # consistency with other parts of the code def test_Se_20_Å_supercell(self): """