From 009631f1115750e2256bc3a8e1ebdd8eba128f39 Mon Sep 17 00:00:00 2001 From: Sean Kavanagh Date: Thu, 8 Feb 2024 16:08:16 +0000 Subject: [PATCH] Update docstrings to note degeneracy handling behaviour, and refactor 'unrelaxed symmetry' to 'bulk site symmetry' to be clearer --- doped/analysis.py | 9 +++--- doped/core.py | 4 +-- doped/generation.py | 6 ++-- doped/thermodynamics.py | 64 ++++++++++++++++++++++------------------- doped/utils/parsing.py | 59 +++++++++++++++++++++++-------------- doped/utils/symmetry.py | 32 ++++++++++++--------- 6 files changed, 100 insertions(+), 74 deletions(-) diff --git a/doped/analysis.py b/doped/analysis.py index 905311f0..7a2c61ff 100644 --- a/doped/analysis.py +++ b/doped/analysis.py @@ -111,7 +111,8 @@ def check_and_set_defect_entry_name( If the DefectEntry.name attribute is not defined or does not end with the charge state, then the entry will be renamed with the doped default name - for the `unrelaxed` defect. + for the `unrelaxed` defect (i.e. using the point symmetry of the defect + site in the bulk cell). Args: defect_entry (DefectEntry): DefectEntry object. @@ -1642,19 +1643,19 @@ def _read_bulk_voronoi_node_dict(bulk_path): relaxed_point_group, periodicity_breaking = point_symmetry_from_defect_entry( defect_entry, relaxed=True, verbose=False, return_periodicity_breaking=True ) # relaxed so defect symm_ops - unrelaxed_point_group = point_symmetry_from_defect_entry( + bulk_site_point_group = point_symmetry_from_defect_entry( defect_entry, symm_ops=bulk_supercell_symm_ops, # unrelaxed so bulk symm_ops relaxed=False, symprec=0.01, # same symprec used w/interstitial multiplicity for consistency ) orientational_degeneracy = get_orientational_degeneracy( - relaxed_point_group=relaxed_point_group, unrelaxed_point_group=unrelaxed_point_group + relaxed_point_group=relaxed_point_group, bulk_site_point_group=bulk_site_point_group ) # TODO: Show these properties in tutorials: defect_entry.degeneracy_factors["orientational degeneracy"] = orientational_degeneracy defect_entry.calculation_metadata["relaxed point symmetry"] = relaxed_point_group - defect_entry.calculation_metadata["unrelaxed point symmetry"] = unrelaxed_point_group + defect_entry.calculation_metadata["bulk site symmetry"] = bulk_site_point_group defect_entry.calculation_metadata["periodicity_breaking_supercell"] = periodicity_breaking if bulk_voronoi_node_dict: # save to bulk folder for future expedited parsing: diff --git a/doped/core.py b/doped/core.py index c675e9f3..fa1ad2f6 100644 --- a/doped/core.py +++ b/doped/core.py @@ -28,9 +28,9 @@ "This will not affect defect formation energies / transition levels, but is important for " "concentrations/doping/Fermi level behaviour (see e.g. doi.org/10.1039/D2FD00043A & " "doi.org/10.1039/D3CS00432E).\n" - "You can manually check (and edit) the computed relaxed/unrelaxed point symmetries and " + "You can manually check (and edit) the computed defect/bulk point symmetries and " "corresponding orientational degeneracy factors by inspecting/editing the " - "calculation_metadata['relaxed point symmetry']/['unrelaxed point symmetry'] and " + "calculation_metadata['relaxed point symmetry']/['bulk site symmetry'] and " "degeneracy_factors['orientational degeneracy'] attributes." ) diff --git a/doped/generation.py b/doped/generation.py index d15e8bd8..b9953128 100644 --- a/doped/generation.py +++ b/doped/generation.py @@ -286,9 +286,9 @@ def get_defect_name_from_entry( octahedral distortions etc). relaxed (bool): If False, determines the site symmetry using the defect site `in the - unrelaxed bulk supercell`, otherwise uses the defect supercell to - determine the site symmetry (i.e. try determine the point symmetry - of a relaxed defect in the defect supercell). Default is True. + unrelaxed bulk supercell`, otherwise tries to determine the point + symmetry of the relaxed defect in the defect supercell). + Default is True. Returns: str: Defect name. diff --git a/doped/thermodynamics.py b/doped/thermodynamics.py index 18059e37..51d4ce37 100644 --- a/doped/thermodynamics.py +++ b/doped/thermodynamics.py @@ -232,7 +232,8 @@ def group_defects_by_name(entry_list: List[DefectEntry]) -> Dict[str, List[Defec "{defect_name}_{optional_site_info}_{charge_state}". If the DefectEntry.name attribute is not defined or does not end with the charge state, then the entry will be renamed with the doped default name - for the `unrelaxed` defect. + for the `unrelaxed` defect (i.e. using the point symmetry of the defect + site in the bulk cell). For example, ``v_Cd_C3v_+1``, ``v_Cd_Td_+1`` and ``v_Cd_C3v_+2`` will be grouped as {``v_Cd_C3v``: [``v_Cd_C3v_+1``, ``v_Cd_C3v_+2``], ``v_Cd_Td``: [``v_Cd_Td_+1``]}. @@ -2285,28 +2286,29 @@ def _single_formation_energy_table( def get_symmetries_and_degeneracies(self, skip_formatting: bool = False) -> pd.DataFrame: r""" - Generates a table of the unrelaxed & relaxed point group symmetries, - and spin/orientational/total degeneracies for each defect in the - DefectThermodynamics object. + Generates a table of the bulk-site & relaxed defect point group + symmetries, and spin/orientational/total degeneracies for each + defect in the ``DefectThermodynamics`` object. Table Key: - 'Defect': Defect name (without charge) - 'q': Defect charge state. - - 'Symm_Unrelax': Point group symmetry of the relaxed defect. - - 'Symm_Relax': Point group symmetry of the relaxed defect. + - 'Site_Symm': Point group symmetry of the defect site in the bulk cell. + - 'Defect_Symm': Point group symmetry of the relaxed defect. - 'g_Orient': Orientational degeneracy of the defect. - 'g_Spin': Spin degeneracy of the defect. - 'g_Total': Total degeneracy of the defect. - For interstitials, the 'unrelaxed' point group symmetry - correspond to the point symmetry of the interstitial site - with `no relaxation of the host structure`. For vacancies - and substitutions, this is equivalent to the initial point - symmetry. + For interstitials, the bulk site symmetry corresponds to the + point symmetry of the interstitial site with `no relaxation + of the host structure`, while for vacancies/substitutions it is + simply the symmetry of their corresponding bulk site. + This corresponds to the point symmetry of ``DefectEntry.defect``, + or ``calculation_metadata["bulk_site"]/["unrelaxed_defect_structure"]``. Point group symmetries are taken from the calculation_metadata - ("relaxed point symmetry" and "unrelaxed point symmetry") if + ("relaxed point symmetry" and "bulk site symmetry") if present (should be, if parsed with doped and defect supercell doesn't break host periodicity), otherwise are attempted to be recalculated. @@ -2333,12 +2335,18 @@ def get_symmetries_and_degeneracies(self, skip_formatting: bool = False) -> pd.D - otherwise periodicity-breaking prevents this. If periodicity-breaking prevents auto-symmetry determination, you can manually - determine the relaxed and unrelaxed point symmetries and/or orientational degeneracy - from visualising the structures (e.g. using VESTA)(can use - ``get_orientational_degeneracy`` to obtain the corresponding orientational degeneracy - factor for given initial/relaxed point symmetries) and setting the corresponding - values in the calculation_metadata['relaxed point symmetry']/['unrelaxed point - symmetry'] and/or degeneracy_factors['orientational degeneracy'] attributes. + determine the relaxed defect and bulk-site point symmetries, and/or orientational + degeneracy, from visualising the structures (e.g. using VESTA)(can use + ``get_orientational_degeneracy`` to obtain the corresponding orientational + degeneracy factor for given defect/bulk-site point symmetries) and setting the + corresponding values in the + ``calculation_metadata['relaxed point symmetry']/['bulk site symmetry']`` and/or + ``degeneracy_factors['orientational degeneracy']`` attributes. + Note that the bulk-site point symmetry corresponds to that of ``DefectEntry.defect``, + or equivalently ``calculation_metadata["bulk_site"]/["unrelaxed_defect_structure"]``, + which for vacancies/substitutions is the symmetry of the corresponding bulk site, + while for interstitials it is the point symmetry of the `final relaxed` interstitial + site when placed in the (unrelaxed) bulk structure. The degeneracy factor is used in the calculation of defect/carrier concentrations and Fermi level behaviour (see e.g. doi.org/10.1039/D2FD00043A & doi.org/10.1039/D3CS00432E). @@ -2374,23 +2382,23 @@ def get_symmetries_and_degeneracies(self, skip_formatting: bool = False) -> pd.D f"Unable to determine relaxed point group symmetry for {defect_entry.name}, got " f"error:\n{e!r}" ) - if "unrelaxed point symmetry" not in defect_entry.calculation_metadata: + if "bulk site symmetry" not in defect_entry.calculation_metadata: try: defect_entry.calculation_metadata[ - "unrelaxed point symmetry" + "bulk site symmetry" ] = point_symmetry_from_defect_entry( defect_entry, relaxed=False, symprec=0.01 ) # unrelaxed so bulk symm_ops except Exception as e: warnings.warn( - f"Unable to determine unrelaxed point group symmetry for {defect_entry.name}, got " - f"error:\n{e!r}" + f"Unable to determine bulk site symmetry for {defect_entry.name}, got error:" + f"\n{e!r}" ) if ( all( x in defect_entry.calculation_metadata - for x in ["relaxed point symmetry", "unrelaxed point symmetry"] + for x in ["relaxed point symmetry", "bulk site symmetry"] ) and "orientational degeneracy" not in defect_entry.degeneracy_factors ): @@ -2399,9 +2407,7 @@ def get_symmetries_and_degeneracies(self, skip_formatting: bool = False) -> pd.D "orientational degeneracy" ] = get_orientational_degeneracy( relaxed_point_group=defect_entry.calculation_metadata["relaxed point symmetry"], - unrelaxed_point_group=defect_entry.calculation_metadata[ - "unrelaxed point symmetry" - ], + bulk_site_point_group=defect_entry.calculation_metadata["bulk site symmetry"], ) except Exception as e: warnings.warn( @@ -2413,10 +2419,8 @@ def get_symmetries_and_degeneracies(self, skip_formatting: bool = False) -> pd.D { "Defect": defect_entry.name.rsplit("_", 1)[0], # name without charge "q": defect_entry.charge_state, - "Symm_Unrelax": defect_entry.calculation_metadata.get( - "unrelaxed point symmetry", "N/A" - ), - "Symm_Relax": defect_entry.calculation_metadata.get("relaxed point symmetry", "N/A"), + "Site_Symm": defect_entry.calculation_metadata.get("bulk site symmetry", "N/A"), + "Defect_Symm": defect_entry.calculation_metadata.get("relaxed point symmetry", "N/A"), "g_Orient": defect_entry.degeneracy_factors.get("orientational degeneracy", "N/A"), "g_Spin": defect_entry.degeneracy_factors.get("spin degeneracy", "N/A"), "g_Total": total_degeneracy, diff --git a/doped/utils/parsing.py b/doped/utils/parsing.py index ba083579..4511fcd2 100644 --- a/doped/utils/parsing.py +++ b/doped/utils/parsing.py @@ -800,22 +800,29 @@ def get_interstitial_site_and_orientational_degeneracy( def get_orientational_degeneracy( defect_entry: Optional[DefectEntry] = None, relaxed_point_group: Optional[str] = None, - unrelaxed_point_group: Optional[str] = None, + bulk_site_point_group: Optional[str] = None, bulk_symm_ops: Optional[list] = None, defect_symm_ops: Optional[list] = None, symprec: float = 0.2, ) -> float: r""" Get the orientational degeneracy factor for a given `relaxed` DefectEntry, - by supplying either the DefectEntry object or the relaxed and unrelaxed - point group symbols (e.g. "Td", "C3v" etc). + by supplying either the DefectEntry object or the bulk-site & relaxed + defect point group symbols (e.g. "Td", "C3v" etc). If a DefectEntry is supplied (and the point group symbols are not), - this is computed by determining the `relaxed` point symmetry and the - original/`unrelaxed` defect site symmetry, and then getting the ratio of + this is computed by determining the `relaxed` defect point symmetry and the + (unrelaxed) bulk site symmetry, and then getting the ratio of their point group orders (equivalent to the ratio of partition functions or number of symmetry operations (i.e. degeneracy)). + For interstitials, the bulk site symmetry corresponds to the + point symmetry of the interstitial site with `no relaxation + of the host structure`, while for vacancies/substitutions it is + simply the symmetry of their corresponding bulk site. + This corresponds to the point symmetry of ``DefectEntry.defect``, + or ``calculation_metadata["bulk_site"]/["unrelaxed_defect_structure"]``. + Note: This tries to use the defect_entry.defect_supercell to determine the `relaxed` site symmetry. However, it should be noted that this is not guaranteed to work in all cases; namely for non-diagonal supercell @@ -839,12 +846,18 @@ def get_orientational_degeneracy( prevents this. If periodicity-breaking prevents auto-symmetry determination, you can manually - determine the relaxed and unrelaxed point symmetries and/or orientational degeneracy - from visualising the structures (e.g. using VESTA)(can use - ``get_orientational_degeneracy`` to obtain the corresponding orientational degeneracy - factor for given initial/relaxed point symmetries) and setting the corresponding - values in the calculation_metadata['relaxed point symmetry']/['unrelaxed point - symmetry'] and/or degeneracy_factors['orientational degeneracy'] attributes. + determine the relaxed defect and bulk-site point symmetries, and/or orientational + degeneracy, from visualising the structures (e.g. using VESTA)(can use + ``get_orientational_degeneracy`` to obtain the corresponding orientational + degeneracy factor for given defect/bulk-site point symmetries) and setting the + corresponding values in the + ``calculation_metadata['relaxed point symmetry']/['bulk site symmetry']`` and/or + ``degeneracy_factors['orientational degeneracy']`` attributes. + Note that the bulk-site point symmetry corresponds to that of ``DefectEntry.defect``, + or equivalently ``calculation_metadata["bulk_site"]/["unrelaxed_defect_structure"]``, + which for vacancies/substitutions is the symmetry of the corresponding bulk site, + while for interstitials it is the point symmetry of the `final relaxed` interstitial + site when placed in the (unrelaxed) bulk structure. The degeneracy factor is used in the calculation of defect/carrier concentrations and Fermi level behaviour (see e.g. doi.org/10.1039/D2FD00043A & doi.org/10.1039/D3CS00432E). @@ -854,14 +867,16 @@ def get_orientational_degeneracy( relaxed_point_group (str): Point group symmetry (e.g. "Td", "C3v" etc) of the `relaxed` defect structure, if already calculated / manually determined. Default is None (automatically calculated by doped). - unrelaxed_point_group (str): Point group symmetry (e.g. "Td", "C3v" etc) - of the non-relaxed (initial) defect site, if already calculated / - manually determined. This should match the site symmetry label from - ``doped`` when generating the defect. Default is None (automatically - calculated by doped). + bulk_site_point_group (str): Point group symmetry (e.g. "Td", "C3v" etc) + of the defect site in the bulk, if already calculated / manually + determined. For vacancies/substitutions, this should match the site + symmetry label from ``doped`` when generating the defect, while for + interstitials it should be the point symmetry of the `final relaxed` + interstitial site, when placed in the bulk structure. + Default is None (automatically calculated by doped). bulk_symm_ops (list): List of symmetry operations of the defect_entry.bulk_supercell - structure (used in determining the `unrelaxed` point symmetry), to + structure (used in determining the `unrelaxed` bulk site symmetry), to avoid re-calculating. Default is None (recalculates). defect_symm_ops (list): List of symmetry operations of the defect_entry.defect_supercell @@ -879,9 +894,9 @@ def get_orientational_degeneracy( from doped.utils.symmetry import group_order_from_schoenflies, point_symmetry_from_defect_entry if defect_entry is None: - if relaxed_point_group is None or unrelaxed_point_group is None: + if relaxed_point_group is None or bulk_site_point_group is None: raise ValueError( - "Either the DefectEntry or both relaxed and unrelaxed point group symbols must be " + "Either the DefectEntry or both defect and bulk site point group symbols must be " "provided for doped to determine the orientational degeneracy! " ) @@ -900,15 +915,15 @@ def get_orientational_degeneracy( relaxed=True, # relaxed ) - if unrelaxed_point_group is None: - unrelaxed_point_group = point_symmetry_from_defect_entry( + if bulk_site_point_group is None: + bulk_site_point_group = point_symmetry_from_defect_entry( defect_entry, # type: ignore symm_ops=bulk_symm_ops, # bulk not defect symm_ops symprec=symprec, # same symprec as relaxed_point_group for consistency relaxed=False, # unrelaxed ) - return group_order_from_schoenflies(unrelaxed_point_group) / group_order_from_schoenflies( + return group_order_from_schoenflies(bulk_site_point_group) / group_order_from_schoenflies( relaxed_point_group ) diff --git a/doped/utils/symmetry.py b/doped/utils/symmetry.py index 71893afc..0a3e689b 100644 --- a/doped/utils/symmetry.py +++ b/doped/utils/symmetry.py @@ -983,12 +983,18 @@ def point_symmetry_from_defect_entry( defect symmetry - otherwise periodicity-breaking prevents this. If periodicity-breaking prevents auto-symmetry determination, you can manually - determine the relaxed and unrelaxed point symmetries and/or orientational degeneracy - from visualising the structures (e.g. using VESTA)(can use - ``get_orientational_degeneracy`` to obtain the corresponding orientational degeneracy - factor for given initial/relaxed point symmetries) and setting the corresponding - values in the calculation_metadata['relaxed point symmetry']/['unrelaxed point - symmetry'] and/or degeneracy_factors['orientational degeneracy'] attributes. + determine the relaxed defect and bulk-site point symmetries, and/or orientational + degeneracy, from visualising the structures (e.g. using VESTA)(can use + ``get_orientational_degeneracy`` to obtain the corresponding orientational + degeneracy factor for given defect/bulk-site point symmetries) and setting the + corresponding values in the + ``calculation_metadata['relaxed point symmetry']/['bulk site symmetry']`` and/or + ``degeneracy_factors['orientational degeneracy']`` attributes. + Note that the bulk-site point symmetry corresponds to that of ``DefectEntry.defect``, + or equivalently ``calculation_metadata["bulk_site"]/["unrelaxed_defect_structure"]``, + which for vacancies/substitutions is the symmetry of the corresponding bulk site, + while for interstitials it is the point symmetry of the `final relaxed` interstitial + site when placed in the (unrelaxed) bulk structure. The degeneracy factor is used in the calculation of defect/carrier concentrations and Fermi level behaviour (see e.g. doi.org/10.1039/D2FD00043A & doi.org/10.1039/D3CS00432E). @@ -1006,9 +1012,9 @@ def point_symmetry_from_defect_entry( octahedral distortions etc). relaxed (bool): If False, determines the site symmetry using the defect site `in the - unrelaxed bulk supercell`, otherwise uses the defect supercell to - determine the site symmetry (i.e. try determine the point symmetry - of a relaxed defect in the defect supercell). Default is True. + unrelaxed bulk supercell` (i.e. the bulk site symmetry), otherwise + tries to determine the point symmetry of the relaxed defect in the + defect supercell. Default is True. verbose (bool): If True, prints a warning if the supercell is detected to break the crystal periodicity (and hence not be able to return a reliable @@ -1164,8 +1170,8 @@ def point_symmetry_from_defect_entry( if relaxed: raise RuntimeError( "Site symmetry could not be determined using the defect supercell, and so the relaxed site " - "symmetry cannot be automatically determined (set relaxed=False to obtain the unrelaxed site " - "symmetry)." + "symmetry cannot be automatically determined (set relaxed=False to obtain the (unrelaxed) " + "bulk site symmetry)." ) defect_diagonal_supercell = defect_entry.defect.get_supercell_structure( @@ -1220,10 +1226,10 @@ def _check_relaxed_defect_symmetry_determination( "detected that the defect supercell is likely a non-scalar matrix expansion which " "could be breaking the cell periodicity and possibly preventing the correct _relaxed_ " "point group symmetry from being automatically determined. You can set relaxed=False " - "to instead get the unrelaxed/initial point group symmetry, and/or manually " + "to instead get the (unrelaxed) bulk site symmetry, and/or manually " "check/set/edit the point symmetries and corresponding orientational degeneracy " "factors by inspecting/editing the " - "calculation_metadata['relaxed point symmetry']/['unrelaxed point symmetry'] and " + "calculation_metadata['relaxed point symmetry']/['bulk site symmetry'] and " "degeneracy_factors['orientational degeneracy'] attributes." ) return False