diff --git a/doped/analysis.py b/doped/analysis.py index 73fa6ce58..da231101f 100644 --- a/doped/analysis.py +++ b/doped/analysis.py @@ -768,7 +768,7 @@ def __init__( if self.bulk_path is None: # determine bulk_path to use if len(possible_bulk_folders) == 1: self.bulk_path = os.path.join(self.output_path, possible_bulk_folders[0]) - elif len([dir for dir in possible_bulk_folders if dir.endswith("_bulk")]) == 1: + elif len([dir for dir in possible_bulk_folders if str(dir).lower().endswith("_bulk")]) == 1: self.bulk_path = os.path.join( self.output_path, next(iter(dir for dir in possible_bulk_folders if str(dir).lower().endswith("_bulk"))), diff --git a/doped/thermodynamics.py b/doped/thermodynamics.py index d87dd2a9b..1834b7da9 100644 --- a/doped/thermodynamics.py +++ b/doped/thermodynamics.py @@ -3768,12 +3768,12 @@ def _get_constrained_concentrations( def _get_in_gap_fermi_level_stability_window(self, defect_entry: Union[str, DefectEntry]) -> float: """ - Convenience method to calculate the maximum difference between Fermi - levels at which ``defect_entry`` is the. + Convenience method to calculate the maximum difference between a Fermi + level at which ``defect_entry`` is the ground-state charge state, and + the band edges. - ground-state charge state, and the band edges (i.e. the - max of (CBM - lowest TL) and (highest TL - VBM) where - TL is any transition level involving ``defect_entry``). + i.e. taken from the minimum of (CBM - lowest TL) and (highest TL - VBM) + where TL is any transition level involving ``defect_entry``. Args: defect_entry (str or DefectEntry): @@ -3946,7 +3946,7 @@ def _add_effective_dopant_concentration( { "Defect": "Dopant", "Charge": int(np.sign(effective_dopant_concentration)), - "Formation Energy (eV)": "N/A", + "Formation Energy (eV)": np.nan, "Concentration (cm^-3)": np.abs(effective_dopant_concentration), "Charge State Population": "100.0%", }, @@ -3957,7 +3957,11 @@ def _add_effective_dopant_concentration( for col in conc_df.columns: if col not in eff_dopant_df.columns: - eff_dopant_df[col] = "N/A" # e.g. concentration per site, if per_site=True + # if string, set to "N/A": + if isinstance(conc_df[col].iloc[0], str): + eff_dopant_df[col] = "N/A" + else: + eff_dopant_df[col] = np.nan # e.g. concentration per site, if per_site=True columns_to_drop = [col for col in eff_dopant_df.columns if col not in conc_df.columns] eff_dopant_df = eff_dopant_df.drop(columns=columns_to_drop) @@ -3969,7 +3973,10 @@ def _group_defect_charge_state_concentrations( conc_df: pd.DataFrame, per_site: bool = False, skip_formatting: bool = False, lean: bool = False ): summed_df = conc_df.groupby("Defect").sum(numeric_only=True) # auto-reordered by groupby sum - summed_df = summed_df.loc[conc_df["Defect"].unique()] # retain ordering + defects = ( + conc_df["Defect"] if "Defect" in conc_df.columns else conc_df.index.get_level_values("Defect") + ) + summed_df = summed_df.loc[defects.unique()] # retain ordering conc_column = next(k for k in conc_df.columns if k.startswith("Concentration")) raw_concentrations = ( summed_df["Raw Concentration"] @@ -4229,7 +4236,7 @@ def scissor_dos(delta_gap: float, dos: Union[Dos, FermiDos], tol: float = 1e-8, scissored_dos_dict["efermi"] -= np.float64(delta_gap / 2) if verbose: - print(f"Orig gap: {dos.get_gap(tol=tol)}, new gap:{dos.get_gap(tol=tol) + delta_gap}") + print(f"Orig gap: {dos.get_gap(tol=tol):.4f}, new gap:{dos.get_gap(tol=tol) + delta_gap:.4f}") scissored_dos_dict["structure"] = dos.structure.as_dict() if isinstance(dos, FermiDos): return FermiDos.from_dict(scissored_dos_dict) diff --git a/tests/test_analysis.py b/tests/test_analysis.py index 270afb391..63e0bacc2 100644 --- a/tests/test_analysis.py +++ b/tests/test_analysis.py @@ -741,7 +741,7 @@ def test_extrinsic_Sb2Se3(self): # warning about negative corrections when using (fake) isotropic dielectric: Sb2Se3_O_dp, w = _create_dp_and_capture_warnings( output_path=f"{self.Sb2Se3_DATA_DIR}/defect", - bulk_path="bulk", + bulk_path=f"{self.Sb2Se3_DATA_DIR}/bulk", dielectric=40, # fake isotropic dielectric parse_projected_eigen=False, ) diff --git a/tests/test_thermodynamics.py b/tests/test_thermodynamics.py index d156b9d5e..2040b8180 100644 --- a/tests/test_thermodynamics.py +++ b/tests/test_thermodynamics.py @@ -216,7 +216,7 @@ def setUpClass(cls): cls.orig_MgO_defect_thermo = loadfn(os.path.join(cls.MgO_EXAMPLE_DIR, "MgO_thermo.json.gz")) cls.orig_MgO_defect_dict = loadfn(os.path.join(cls.MgO_EXAMPLE_DIR, "MgO_defect_dict.json.gz")) - cls.MgO_chempots = loadfn(os.path.join(cls.EXAMPLE_DIR, "CompetingPhases/MgO_chempots.json")) + cls.MgO_chempots = loadfn(os.path.join(cls.EXAMPLE_DIR, "MgO/CompetingPhases/MgO_chempots.json")) cls.Sb2O5_chempots = loadfn(os.path.join(data_dir, "Sb2O5/Sb2O5_chempots.json")) cls.orig_Sb2O5_defect_thermo = loadfn(os.path.join(data_dir, "Sb2O5/Sb2O5_thermo.json.gz")) @@ -2706,13 +2706,13 @@ def test_prune_to_stable_entries(self): ) == 2 ) - for i in ["sub_1_Te_on_Se_1", "sub_1_S_on_Se_1", "sub_1_O_on_Se_1", "inter_5_S_1", "inter_3_Te_1"]: + for i in ["sub_1_Te_on_Se_1", "sub_1_S_on_Se_1", "sub_1_O_on_Se_1", "inter_5_S_1"]: assert i in self.Se_ext_no_pnict_thermo.defect_entries, f"Checking {i}" pruned_Se_ext_no_pnict_thermo = self.Se_ext_no_pnict_thermo.prune_to_stable_entries() assert len(pruned_Se_ext_no_pnict_thermo) == 70 assert len(pruned_Se_ext_no_pnict_thermo.transition_level_map) == 16 - for i in ["sub_1_Te_on_Se_1", "sub_1_S_on_Se_1", "sub_1_O_on_Se_1", "inter_5_S_1", "inter_3_Te_1"]: + for i in ["sub_1_Te_on_Se_1", "sub_1_S_on_Se_1", "sub_1_O_on_Se_1", "inter_5_S_1"]: assert i not in pruned_Se_ext_no_pnict_thermo.defect_entries, f"Checking {i}" assert ( len( @@ -3085,9 +3085,9 @@ def test_get_fermi_level_and_concentrations(self): assert ("Orig gap:" in output) == kwargs.get("verbose", False) if kwargs.get("delta_gap") == 0.3 and kwargs.get("verbose", False): if kwargs.get("tol") == 1e-1: - assert "Orig gap: 2.7513, new gap:3.0513" in output + assert "Orig gap: 2.7565, new gap:3.0565" in output else: - assert "Orig gap: 1.5126, new gap:1.8126" in output + assert "Orig gap: 1.5178, new gap:1.8178" in output assert np.isclose(fermi_level, 0.35124, atol=1e-3) # different assert not w @@ -3115,7 +3115,7 @@ def test_get_fermi_level_and_concentrations(self): ) # Similar values to CdTe_LZ_Te_rich_concentrations.png, slightly different due to no scissoring assert ( - np.isclose(fermi_level, 0.3396, atol=1e-3) + np.isclose(fermi_level, 0.3374, atol=1e-3) == anneal_at_1000K_quench_at_RT_no_dopants_scissoring ) assert ( @@ -3206,23 +3206,24 @@ def test_get_fermi_level_and_concentrations(self): ) # Note that this check requires ``skip_formatting`` to be True as we don't format the # dopant values here: + expected_df = defect_thermo.get_equilibrium_concentrations( + fermi_level=annealing_fermi_level, + limit="Te-rich", + temperature=kwargs.get("annealing_temperature", 1000), + **{ + k: v + for k, v in kwargs.items() + if k not in ["return_annealing_values", "effective_dopant_concentration"] + }, + ) expected_df = _add_effective_dopant_concentration( - defect_thermo.get_equilibrium_concentrations( - fermi_level=annealing_fermi_level, - limit="Te-rich", - temperature=kwargs.get("annealing_temperature", 1000), - **{ - k: v - for k, v in kwargs.items() - if k not in ["return_annealing_values", "effective_dopant_concentration"] - }, - ), + expected_df, kwargs.get("effective_dopant_concentration"), ) if kwargs.get("per_charge", True): conc_df_to_compare = conc_df.drop(columns=["Total Concentration (cm^-3)"]) - # replace NaNs with "N/A" (Dopant per site conc): - conc_df_to_compare = conc_df_to_compare.fillna("N/A") + else: + conc_df_to_compare = conc_df print(conc_df_to_compare, expected_df) # for debugging print(conc_df_to_compare.columns, expected_df.columns) # for debugging pd.testing.assert_frame_equal(conc_df_to_compare, expected_df) @@ -3394,7 +3395,7 @@ def test_get_fermi_level_and_concentrations(self): "No chemical potentials supplied, so using 0 for all chemical potentials" in str(warn.message) for warn in w ) - assert np.isclose(results[0], 1.1319, atol=1e-3) + assert np.isclose(results[0], 1.2737, atol=1e-3) defect_thermo = deepcopy(self.defect_thermo) defect_thermo.chempots = None @@ -3413,7 +3414,7 @@ def test_get_in_gap_fermi_level_stability_window(self): ("v_Cd_0", 0.35), (self.defect_thermo.defect_entries["v_Cd_0"], 0.35), ("v_Cd_-2", 1.15), - ("Te_Cd_0", 1.374), # this case, of being bounded by other charges on both sides, + ("Te_Cd_0", 1.162), # this case, of being bounded by other charges on both sides, # wasn't tested with CdTe_example_thermo in DefectThermodynamicsTestCase ]: assert np.isclose( @@ -3642,7 +3643,7 @@ def _array_from_conc_df(name): ) ax.plot( self.anneal_temperatures, - _array_from_conc_df("Te_i_Td_Te2.83"), + _array_from_conc_df("Te_i_Td_Te2.83_a"), marker="o", label="$Te_i$", linestyle=":",