diff --git a/doped/thermodynamics.py b/doped/thermodynamics.py index 10f0a2ab..9f56eb6c 100644 --- a/doped/thermodynamics.py +++ b/doped/thermodynamics.py @@ -51,7 +51,14 @@ def bold_print(string: str) -> None: print("\033[1m" + string + "\033[0m") -def _raise_limit_with_user_chempots_error(no_chempots=True): +def raise_limit_with_user_chempots_error(no_chempots=True): + """ + Raise an error when a user tries to use `limit` with user-specified + chemical potentials (i.e. not in the doped format). + + Args: + no_chempots (bool): Whether the user has not supplied any chemical potentials. + """ problem = ( ( "the supplied chempots are not in the doped format (i.e. with `limits` in the chempots dict), " @@ -65,15 +72,26 @@ def _raise_limit_with_user_chempots_error(no_chempots=True): ) -def _parse_limit(chempots: dict, limit: Optional[str] = None): +def parse_limit(chempots: dict, limit: Optional[str] = None): + """ + Parse the limit input, returning the correct limit name to use in analysis + functions. + + Args: + chempots (dict): The chempots dict, in the doped format. + limit (str): The limit name to use in analysis functions. + + returns: + str: The correct limit name to use in analysis functions. + """ if limit is not None: if limit in chempots["limits"]: return limit # direct match, just return limit name if "limits" not in chempots or "User Chemical Potentials" in chempots["limits"]: # user specified chempots - _raise_limit_with_user_chempots_error(no_chempots=True) + raise_limit_with_user_chempots_error(no_chempots=True) if "No User Chemical Potentials" in chempots["limits"]: - _raise_limit_with_user_chempots_error(no_chempots=False) + raise_limit_with_user_chempots_error(no_chempots=False) if "rich" in limit: limit = get_X_rich_limit(limit.split("-")[0], chempots) elif "poor" in limit: @@ -107,7 +125,19 @@ def get_rich_poor_limit_dict(chempots: dict) -> dict: return limit_dict -def _get_limit_name_from_dict(limit, limit_rich_poor_dict, bracket=False): +def get_limit_name_from_dict(limit, limit_rich_poor_dict, bracket=False): + """ + Get the limit name from a rich/poor limit dictionary, returning the + corresponding rich/poor limit name if it exists. + + Args: + limit (str): The limit name to get the rich/poor limit name for. + limit_rich_poor_dict (dict): The rich/poor limit dictionary. + bracket (bool): Whether to return the limit name with the rich/poor limit in brackets. + + Returns: + str: The limit name with the rich/poor limit in brackets if bracket is True. + """ if limit_rich_poor_dict and limit in limit_rich_poor_dict.values(): # get first key with matching value: x_rich_poor = list(limit_rich_poor_dict.keys())[list(limit_rich_poor_dict.values()).index(limit)] @@ -129,14 +159,18 @@ def _update_old_chempots_dict(chempots: dict) -> dict: return chempots -def _parse_chempots(chempots: Optional[dict] = None, el_refs: Optional[dict] = None): +def parse_chempots(chempots: Optional[dict] = None, el_refs: Optional[dict] = None): """ Parse the chemical potentials input, formatting them in the ``doped`` format for use in analysis functions. Can be either ``doped`` format or user-specified format. - Returns parsed ``chempots`` and ``el_refs`` + Args: + chempots (dict): The chempots dict + el_refs (dict): The elemental reference energies + Returns: + parsed ``chempots`` and ``el_refs`` """ if chempots is not None and "limits_wrt_elt_refs" in chempots: chempots["limits_wrt_el_refs"] = chempots.pop("limits_wrt_elt_refs") @@ -487,7 +521,7 @@ def __init__( defect_entries = list(defect_entries.values()) self._defect_entries = defect_entries - self._chempots, self._el_refs = _parse_chempots(chempots, el_refs) + self._chempots, self._el_refs = parse_chempots(chempots, el_refs) self._dist_tol = dist_tol self.check_compatibility = check_compatibility @@ -645,7 +679,7 @@ def _get_chempots(self, chempots: Optional[dict] = None, el_refs: Optional[dict] Parse chemical potentials, either using input values (after formatting them in the doped format) or using the class attributes if set. """ - return _parse_chempots(chempots or self.chempots, el_refs or self.el_refs) + return parse_chempots(chempots or self.chempots, el_refs or self.el_refs) def _parse_transition_levels(self): r""" @@ -1044,7 +1078,7 @@ def chempots(self, input_chempots): potentials can also be supplied later in each analysis function. (Default: None) """ - self._chempots, self._el_refs = _parse_chempots(input_chempots, self._el_refs) + self._chempots, self._el_refs = parse_chempots(input_chempots, self._el_refs) @property def el_refs(self): @@ -1070,7 +1104,7 @@ def el_refs(self, input_el_refs): Unnecessary if ``chempots`` is provided in format generated by ``doped`` (see tutorials). """ - self._chempots, self._el_refs = _parse_chempots(self._chempots, input_el_refs) + self._chempots, self._el_refs = parse_chempots(self._chempots, input_el_refs) @property def defect_names(self): @@ -1953,7 +1987,7 @@ def get_dopability_limits( "DefectThermodynamics.chempots, so dopability limits cannot be calculated." ) - limit = _parse_limit(chempots, limit) + limit = parse_limit(chempots, limit) limits = [limit] if limit is not None else list(chempots["limits"].keys()) donor_intercepts: list[tuple] = [] @@ -2034,14 +2068,14 @@ def get_dopability_limits( return pd.DataFrame( [ [ - _get_limit_name_from_dict( + get_limit_name_from_dict( limiting_donor_intercept_row["limit"], limit_dict, bracket=True ), limiting_donor_intercept_row["name"], round(limiting_donor_intercept_row["intercept"], 3), ], [ - _get_limit_name_from_dict( + get_limit_name_from_dict( limiting_acceptor_intercept_row["limit"], limit_dict, bracket=True ), limiting_acceptor_intercept_row["name"], @@ -2126,7 +2160,7 @@ def get_doping_windows( "DefectThermodynamics.chempots, so doping windows cannot be calculated." ) - limit = _parse_limit(chempots, limit) + limit = parse_limit(chempots, limit) limits = [limit] if limit is not None else list(chempots["limits"].keys()) vbm_donor_intercepts: list[tuple] = [] @@ -2190,7 +2224,7 @@ def get_doping_windows( limiting_intercept_row = intercepts_df.iloc[intercepts_df[idx]["intercept"].idxmax()] limiting_intercept_rows.append( [ - _get_limit_name_from_dict(limiting_intercept_row["limit"], limit_dict, bracket=True), + get_limit_name_from_dict(limiting_intercept_row["limit"], limit_dict, bracket=True), limiting_intercept_row["name"], round(limiting_intercept_row["intercept"], 3), ] @@ -2337,7 +2371,7 @@ def plot( "limits": {"No User Chemical Potentials": None} } # empty chempots dict to allow plotting, user will be warned - limit = _parse_limit(chempots, limit) + limit = parse_limit(chempots, limit) limits = [limit] if limit is not None else list(chempots["limits"].keys()) if ( @@ -2645,7 +2679,7 @@ def get_formation_energies( "limits_wrt_el_refs": {"No User Chemical Potentials": {}}, # empty dict so is iterable } - limit = _parse_limit(chempots, limit) + limit = parse_limit(chempots, limit) limits = [limit] if limit is not None else list(chempots["limits"].keys()) list_of_dfs = []