Skip to content

Commit

Permalink
Merge develop
Browse files Browse the repository at this point in the history
  • Loading branch information
kavanase committed Sep 12, 2024
2 parents 4595bc9 + eaf96ce commit 3935e4f
Show file tree
Hide file tree
Showing 93 changed files with 4,458 additions and 2,259 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/pip_install_test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ jobs:
exclude:
- os: macos-14
python-version: '3.9' # Exclude Python 3.9 on macOS, not supported for macOS-14 tests
# macos-latest should be 14 according to link below, but currently doesn't?
# macos-latest should be 14 according to link below, but currently doesn't? Possibly because 3.9 included here? Update when 3.9 cut
# https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners/about-github-hosted-runners#standard-github-hosted-runners-for-public-repositories

runs-on: ${{ matrix.os }}
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ jobs:
exclude:
- os: macos-14
python-version: '3.9' # Exclude Python 3.9 on macOS, not supported for macOS-14 tests
# macos-latest should be 14 according to link below, but currently doesn't?
# macos-latest should be 14 according to link below, but currently doesn't? Possibly because 3.9 included here? Update when 3.9 cut
# https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners/about-github-hosted-runners#standard-github-hosted-runners-for-public-repositories

runs-on: ${{ matrix.os }}
Expand Down
27 changes: 21 additions & 6 deletions docs/Code_Compatibility.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
Code Compatibility
========================

Python Package Compatibility
----------------------------
:code:`doped` is built to natively function using (and being compatible with) the most recent version of
:code:`pymatgen`. If you are receiving :code:`pymatgen`-related errors when using
:code:`doped`, you may need to update :code:`pymatgen` and/or :code:`doped`, which can be done with:
Expand All @@ -15,11 +17,24 @@ July 2022, such that major refactoring was undertaken to make the code compatibl
< ``2022.7.25``).

.. note::
If you run into this ``numpy``-related error, please see the :ref:`Troubleshooting <troubleshooting>`
section on the docs tips page:

.. code:: python
ValueError: numpy.ndarray size changed, may indicate binary incompatibility. Expected 88 from C header, got 80 from PyObject
If you run into any errors when using ``doped``, please check the
:ref:`Troubleshooting <troubleshooting>` docs page, where any known issues with codes (such as ``numpy``,
``scipy`` or ``spglib``) are detailed.

Energy Calculator (DFT/ML) Compatibility
----------------------------------------
The vast majority of the code in :code:`doped` is agnostic to the underlying energy calculator / electronic
structure (i.e. DFT/ML) code used to calculate the raw energies of defect supercells. However, as
demonstrated in the tutorials, direct I/O support is currently provided for the ``VASP`` DFT code, while
structure files for essentially all DFT/ML codes can be easily generated using the
`pymatgen <https://pymatgen.org/pymatgen.core.html#pymatgen.core.structure.IStructure.to>`__ or
`ase <https://wiki.fysik.dtu.dk/ase/ase/io/io.html#ase.io.write>`__ I/O methods, with the ``pymatgen``
``Structure`` objects used for crystal structures in ``doped``.

Direct I/O capabilities for other codes is a goal (such as ``Quantum Espresso``, ``CP2K`` and/or
``FHI-aims``), accompanied by an update publication, so please get in touch with the developers if you
would be interested in contributing to this effort! Input file generation and structure/energy parsing
should be straightforward with the ``doped``/``pymatgen``/``ase`` APIs, while finite-size charge
corrections would be the main challenge for parsing & computing.

Please let us know if you have any issues with compatibility!
8 changes: 5 additions & 3 deletions docs/Tips.rst
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,7 @@ PHS on the transition level diagram with a clear circle is shown on the right.

As mentioned above, the eigenvalue analysis functions use code from ``pydefect``, so please cite the
``pydefect`` paper if using these analyses in your work:

"Insights into oxygen vacancies from high-throughput first-principles calculations"
Yu Kumagai, Naoki Tsunoda, Akira Takahashi, and Fumiyasu Oba
Phys. Rev. Materials 5, 123803 (2021) -- 10.1103/PhysRevMaterials.5.123803
Expand Down Expand Up @@ -434,9 +435,10 @@ for multiple defects.
.. note::

For magnetic competing phases, the spin configuration should also be appropriately set. ``doped`` will
automatically set ``NUPDOWN`` according to the magnetisation output from the ``Materials Project``
calculation of the competing phase, but ``MAGMOM`` may also need to be set to induce a specific spin
configuration.
automatically set ``ISPIN=2`` (allowing spin polarisation) and ``NUPDOWN`` according to the
magnetisation output from the ``Materials Project`` calculation of the competing phase, but ``MAGMOM``
(and possibly ``ISPIN``/``NUPDOWN``) may also need to be set to induce a specific spin configuration in
certain cases.

Symmetry Precision (``symprec``)
--------------------------------
Expand Down
94 changes: 67 additions & 27 deletions doped/analysis.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@
from doped.utils.symmetry import (
_frac_coords_sort_func,
_get_all_equiv_sites,
_get_sga,
get_sga,
point_symmetry_from_defect_entry,
)

Expand Down Expand Up @@ -265,31 +265,16 @@ def defect_from_structures(
if def_type == "interstitial":
# get closest Voronoi site in bulk supercell to final interstitial site as this is likely
# the _initial_ interstitial site
try:
if bulk_voronoi_node_dict is not None and not StructureMatcher(
stol=0.05,
primitive_cell=False,
scale=False,
attempt_supercell=False,
allow_subset=False,
comparator=ElementComparator(),
).fit(bulk_voronoi_node_dict["bulk_supercell"], bulk_supercell):
warnings.warn(
"Previous bulk voronoi_nodes.json detected, but does not match current bulk "
"supercell. Recalculating Voronoi nodes."
)
raise FileNotFoundError

voronoi_frac_coords = bulk_voronoi_node_dict["Voronoi nodes"] # type: ignore[index]

except Exception: # first time parsing
if not bulk_voronoi_node_dict: # first time parsing
from shakenbreak.input import _get_voronoi_nodes

voronoi_frac_coords = [site.frac_coords for site in _get_voronoi_nodes(bulk_supercell)]
bulk_voronoi_node_dict = {
"bulk_supercell": bulk_supercell,
"Voronoi nodes": voronoi_frac_coords,
}
else:
voronoi_frac_coords = bulk_voronoi_node_dict["Voronoi nodes"] # type: ignore[index]

closest_node_frac_coords = min(
voronoi_frac_coords,
Expand Down Expand Up @@ -970,6 +955,40 @@ def _mention_bulk_path_subfolder_for_correction_warnings(warning: str) -> str:
f"expected (see `DefectsParser` docstring)."
)

# check if there are duplicate entries in the parsed defect entries, warn and remove:
energy_entries_dict: dict[float, list[DefectEntry]] = {} # {energy: [defect_entry]}
for defect_entry in parsed_defect_entries: # find duplicates by comparing supercell energies
if defect_entry.sc_entry_energy in energy_entries_dict:
energy_entries_dict[defect_entry.sc_entry_energy].append(defect_entry)
else:
energy_entries_dict[defect_entry.sc_entry_energy] = [defect_entry]

for energy, entries_list in energy_entries_dict.items():
if len(entries_list) > 1: # More than one entry with the same energy
# sort any duplicates by name length, name, folder length, folder (shorter preferred)
energy_entries_dict[energy] = sorted(
entries_list,
key=lambda x: (
len(x.name),
x.name,
len(self._get_defect_folder(x)),
self._get_defect_folder(x),
),
)

if any(len(entries_list) > 1 for entries_list in energy_entries_dict.values()):
duplicate_entry_names_folders_string = "\n".join(
", ".join(f"{entry.name} ({self._get_defect_folder(entry)})" for entry in entries_list)
for entries_list in energy_entries_dict.values()
if len(entries_list) > 1
)
warnings.warn(
f"The following parsed defect entries were found to be duplicates (exact same defect "
f"supercell energies). The first of each duplicate group shown will be kept and the "
f"other duplicate entries omitted:\n{duplicate_entry_names_folders_string}"
)
parsed_defect_entries = [next(iter(entries_list)) for entries_list in energy_entries_dict.values()]

# get any defect entries in parsed_defect_entries that share the same name (without charge):
# first get any entries with duplicate names:
entries_to_rename = [
Expand Down Expand Up @@ -1182,12 +1201,18 @@ def _parse_parsing_warnings(self, warnings_string, defect_folder, defect_path):

return ""

def _get_defect_folder(self, entry):
return (
entry.calculation_metadata["defect_path"]
.replace("/.", "")
.split("/")[-1 if self.subfolder == "." else -2]
)

def _update_pbar_and_return_warnings_from_parsing(self, result, pbar):
pbar.update()

if result[0] is not None:
i = -1 if self.subfolder == "." else -2
defect_folder = result[0].calculation_metadata["defect_path"].replace("/.", "").split("/")[i]
defect_folder = self._get_defect_folder(result[0])
pbar.set_description(f"Parsing {defect_folder}/{self.subfolder}".replace("/.", ""))

if result[1]:
Expand Down Expand Up @@ -1726,9 +1751,23 @@ def _read_bulk_voronoi_node_dict(bulk_path):

if os.path.exists("voronoi_nodes.json.lock"):
with FileLock("voronoi_nodes.json.lock"):
bulk_voronoi_node_dict = _read_bulk_voronoi_node_dict(bulk_path)
prev_bulk_voronoi_node_dict = _read_bulk_voronoi_node_dict(bulk_path)
else:
bulk_voronoi_node_dict = _read_bulk_voronoi_node_dict(bulk_path)
prev_bulk_voronoi_node_dict = _read_bulk_voronoi_node_dict(bulk_path)

if prev_bulk_voronoi_node_dict and not StructureMatcher(
stol=0.05,
primitive_cell=False,
scale=False,
attempt_supercell=False,
allow_subset=False,
comparator=ElementComparator(),
).fit(prev_bulk_voronoi_node_dict["bulk_supercell"], bulk_supercell):
warnings.warn(
"Previous bulk voronoi_nodes.json detected, but does not match current bulk "
"supercell. Recalculating Voronoi nodes."
)
prev_bulk_voronoi_node_dict = {}

# Can specify initial defect structure (to help find the defect site if we have a very distorted
# final structure), but regardless try using the final structure (from defect OUTCAR) first:
Expand All @@ -1747,7 +1786,7 @@ def _read_bulk_voronoi_node_dict(bulk_path):
bulk_supercell,
defect_structure.copy(),
return_all_info=True,
bulk_voronoi_node_dict=bulk_voronoi_node_dict,
bulk_voronoi_node_dict=prev_bulk_voronoi_node_dict,
oxi_state=kwargs.get("oxi_state"),
)

Expand All @@ -1769,7 +1808,7 @@ def _read_bulk_voronoi_node_dict(bulk_path):
bulk_supercell,
defect_structure_for_ID,
return_all_info=True,
bulk_voronoi_node_dict=bulk_voronoi_node_dict,
bulk_voronoi_node_dict=prev_bulk_voronoi_node_dict,
oxi_state=kwargs.get("oxi_state"),
)

Expand Down Expand Up @@ -1822,7 +1861,7 @@ def _read_bulk_voronoi_node_dict(bulk_path):
degeneracy_factors=degeneracy_factors,
)

bulk_supercell_symm_ops = _get_sga(
bulk_supercell_symm_ops = get_sga(
defect_entry.defect.structure, symprec=0.01
).get_symmetry_operations()
if defect.defect_type == core.DefectType.Interstitial:
Expand Down Expand Up @@ -1861,7 +1900,8 @@ def _read_bulk_voronoi_node_dict(bulk_path):
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 and bulk_path: # save to bulk folder for future expedited parsing:
if bulk_voronoi_node_dict and bulk_path and not prev_bulk_voronoi_node_dict:
# save to bulk folder for future expedited parsing:
if os.path.exists("voronoi_nodes.json.lock"):
with FileLock("voronoi_nodes.json.lock"):
dumpfn(bulk_voronoi_node_dict, os.path.join(bulk_path, "voronoi_nodes.json"))
Expand Down
Loading

0 comments on commit 3935e4f

Please sign in to comment.