diff --git a/README.md b/README.md
index 0769ebf5..8d12f164 100644
--- a/README.md
+++ b/README.md
@@ -31,14 +31,8 @@ displacements from DFT supercell calculations. See the [JOSS paper](https://doi.
## Installation
```bash
pip install doped # install doped and dependencies
-conda install -c conda-forge spglib # bundle C libraries with spglib
```
-Note that either `conda install -c conda-forge spglib` or
-`pip install git+https://github.com/spglib/spglib --config-settings=cmake.define.SPGLIB_SHARED_LIBS=OFF`
-should be used after `pip install doped`, which ensures that the correct C libraries are bundled with
-`spglib`, to prevent unnecessary warnings.
-
Alternatively if desired, `doped` can also be installed from `conda` with:
```bash
@@ -64,9 +58,10 @@ As shown in the `doped` tutorials, it is highly recommended to use the [`ShakeNB
Not currently cited but should be for chempot plot:
- Quadir et al. **_Low-Temperature Synthesis of Stable CaZn2P2 Zintl Phosphide Thin Films as Candidate Top Absorbers_** [_arXiv_](https://arxiv.org/abs/2406.15637) 2024
-->
+- M. Elgaml et al. **_Controlling the Superconductivity of Nb2PdxS5 via Reversible Li Intercalation_** [_Inorganic Chemistry_](https://pubs.acs.org/doi/full/10.1021/acs.inorgchem.3c03524) 2024
- Z. Yuan & G. Hautier **_First-principles study of defects and doping limits in CaO_** [_Applied Physics Letters_](https://doi.org/10.1063/5.0211707) 2024
- B. E. Murdock et al. **_Li-Site Defects Induce Formation of Li-Rich Impurity Phases: Implications for Charge Distribution and Performance of LiNi0.5-xMxMn1.5O4 Cathodes (M = Fe and Mg; x = 0.05–0.2)_** [_Advanced Materials_](https://doi.org/10.1002/adma.202400343) 2024
-- A. G. Squires et al. **_Oxygen dimerization as a defect-driven process in bulk LiNiO22_** [_ChemRxiv_](https://doi.org/10.26434/chemrxiv-2024-lcmkj) 2024
+- A. G. Squires et al. **_Oxygen dimerization as a defect-driven process in bulk LiNiO22_** [_ACS Energy Letters_](https://pubs.acs.org/doi/10.1021/acsenergylett.4c01307) 2024
- Y. Fu & H. Lohan et al. **_Factors Enabling Delocalized Charge-Carriers in Pnictogen-Based
Solar Absorbers: In-depth Investigation into CuSbSe2_** [_arXiv_](https://doi.org/10.48550/arXiv.2401.02257) 2024
- S. Hachmioune et al. **_Exploring the Thermoelectric Potential of MgB4: Electronic Band Structure, Transport Properties, and Defect Chemistry_** [_Chemistry of Materials_](https://doi.org/10.1021/acs.chemmater.4c00584) 2024
diff --git a/docs/Dev_ToDo.md b/docs/Dev_ToDo.md
index 8eebcc00..514e8606 100644
--- a/docs/Dev_ToDo.md
+++ b/docs/Dev_ToDo.md
@@ -1,6 +1,6 @@
# `doped` Development To-Do List
## Chemical potential
-- Check through chemical potential TO-DOs. Need to recheck validity of approximations used for extrinsic competing phases (and code for this). Proper `vasp_std` setup (with `NKRED` folders like for defect calcs) and `vasp_ncl` generation.
+- Need to recheck validity of approximations used for extrinsic competing phases (and code for this). Proper `vasp_std` setup (with `NKRED` folders like for defect calcs) and `vasp_ncl` generation.
- Efficient generation of competing phases for which there are many polymorphs? See SK notes from CdTe competing phases.
- Publication ready chemical potential diagram plotting tool as in Adam Jackson's `plot-cplap-ternary` (3D) and Sungyhun's `cplapy` (4D) (see `doped_chempot_plotting_example.ipynb`; code there, just needs to be implemented in module functions). `ChemicalPotentialGrid` in `py-sc-fermi` interface could be quite useful for this? (Worth moving that part of code out of `interface` subpackage?) Also can see https://github.com/materialsproject/pymatgen-analysis-defects/blob/main/pymatgen/analysis/defects/plotting/phases.py for 2D chempot plotting.
- Also see `Cs2SnTiI6` notebooks for template code for this.
@@ -15,6 +15,8 @@
- Could also add an optional right-hand-side y-axis for defect concentration (for a chosen anneal temp) to our TLD plotting (e.g. `concentration_T = None`) as done for thesis, noting in docstring that this obvs doesn't account for degeneracy!
- Separate `dist_tol` for interstitials vs (inequivalent) vacancies/substitutions? (See Xinwei chat) Any other options on this front?
- Also see Fig. 6a of the `AiiDA-defects` preprint, want plotting tools like this
+- Kumagai GKFO and CC diagram corrections. Implemented in `pydefect` and relatively easy to port?
+- 2D corrections; like `pydefect_2d`, or recommended to use SCPC in VASP?
- Can we add an option to give the `pydefect` defect-structure-info output (shown here https://kumagai-group.github.io/pydefect/tutorial.html#check-defect-structures) – seems quite useful tbf
## Housekeeping
diff --git a/docs/Future_ToDo.md b/docs/Future_ToDo.md
index 875579b6..cfb1bbfe 100644
--- a/docs/Future_ToDo.md
+++ b/docs/Future_ToDo.md
@@ -84,9 +84,9 @@
- In these cases, will also want to be able to plot these in a smart manner on the defect TLD.
Separate lines to the stoichiometrically-equivalent (unperturbed) point defect, but with the same
colour just different linestyles? (or something similar)
-- `doped` functions should be equally applicable to the base `pymatgen` `Defect`/`DefectEntry` objects (as well as the corresponding `doped` subclasses) as much as possible. Can we add some quick tests for this?
- Implement shallow donor/acceptor binding estimation functions (via effective mass theory)
- Kasamatsu formula for defect concentrations at high concentrations (accounts for lattice site competition), as shown in DefAP paper
+- Eigenvalue corrections for the eigenvalue plots, like shown in 10.1103/PhysRevB.109.054106?
## Docs
- Add LDOS plotting, big selling point for defects and disorder!
diff --git a/docs/Installation.rst b/docs/Installation.rst
index 172f3ea8..00911c61 100644
--- a/docs/Installation.rst
+++ b/docs/Installation.rst
@@ -6,22 +6,6 @@ Installation
.. code-block:: bash
pip install doped # install doped and dependencies
- conda install -c conda-forge spglib # bundle C libraries with spglib
-
-Note that either ``conda install -c conda-forge spglib`` or
-``pip install git+https://github.com/spglib/spglib --config-settings=cmake.define.SPGLIB_SHARED_LIBS=OFF``
-should be used after ``pip install doped``, which ensures that the correct C libraries are bundled with
-``spglib``, to prevent unnecessary warnings. You can check that the correct C libraries have been bundled
-by running the following in Python, and confirming that the same version numbers are printed:
-
-.. code-block:: python
-
- import spglib
- print(spglib.__version__)
- print(spglib.spg_get_version_full())
-
-In some cases, you might need to first uninstall ``spglib`` with both ``conda uninstall spglib`` and
-``pip uninstall spglib`` (to ensure no duplicate installations), before reinstalling.
Alternatively if desired, ``doped`` can also be installed from ``conda`` with:
diff --git a/docs/Troubleshooting.rst b/docs/Troubleshooting.rst
index 18f3b0f0..7adb1703 100644
--- a/docs/Troubleshooting.rst
+++ b/docs/Troubleshooting.rst
@@ -76,11 +76,7 @@ A known issue with ``spglib`` is that it can give unnecessary errors or warnings
spglib: get_bravais_exact_positions_and_lattice failed
spglib: ref_get_exact_structure_and_symmetry failed.
-This can be fixed by reinstalling ``spglib`` with ``conda install -c conda-forge spglib`` or
-``pip install git+https://github.com/spglib/spglib --config-settings=cmake.define.SPGLIB_SHARED_LIBS=OFF``
-as detailed in the `Installation `__ instructions.
-This ensures the correct C libraries are bundled with ``spglib``.
-
+Typically this can be fixed by updating to ``spglib>=2.5`` with `pip install --upgrade spglib``.
.. see doped_spglib_warnings.ipynb
``ShakeNBreak``
diff --git a/docs/index.rst b/docs/index.rst
index 885300a1..83e5c7e6 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -92,9 +92,10 @@ Studies using ``doped``, so far
.. Not currently cited but should be for chempot plot:
Quadir et al. **_Low-Temperature Synthesis of Stable CaZn2P2 Zintl Phosphide Thin Films as Candidate Top Absorbers_** [_arXiv_](https://arxiv.org/abs/2406.15637) 2024
-- Z\. Yuan & G. Hautier **First-principles study of defects and doping limits in CaO** `Applied Physics Letters `_ 2024
+- M\. Elgaml et al. **Controlling the Superconductivity of Nb** :sub:`2` **Pd** :sub:`x` **S** :sub:`5` **via Reversible Li Intercalation** `Inorganic Chemistry `__ 2024
+- Z\. Yuan & G. Hautier **First-principles study of defects and doping limits in CaO** `Applied Physics Letters `__ 2024
- B\. E. Murdock et al. **Li-Site Defects Induce Formation of Li-Rich Impurity Phases: Implications for Charge Distribution and Performance of LiNi** :sub:`0.5-x` **M** :sub:`x` **Mn** :sub:`1.5` **O** :sub:`4` **Cathodes (M = Fe and Mg; x = 0.05–0.2)** `Advanced Materials `_ 2024
-- A\. G. Squires et al. **Oxygen dimerization as a defect-driven process in bulk LiNiO₂** `ChemRxiv `__ 2024
+- A\. G. Squires et al. **Oxygen dimerization as a defect-driven process in bulk LiNiO₂** `ACS Energy Letters `__ 2024
- Y\. Fu & H. Lohan et al. **Factors Enabling Delocalized Charge-Carriers in Pnictogen-Based Solar Absorbers: In-depth Investigation into CuSbSe2** `arXiv `_ 2024
- S\. Hachmioune et al. **Exploring the Thermoelectric Potential of MgB4: Electronic Band Structure, Transport Properties, and Defect Chemistry** `Chemistry of Materials `__ 2024
- J\. Hu et al. **Enabling ionic transport in Li3AlP2 the roles of defects and disorder** `ChemRxiv `__ 2024
diff --git a/doped/__init__.py b/doped/__init__.py
index 3e165202..632c0cd6 100644
--- a/doped/__init__.py
+++ b/doped/__init__.py
@@ -71,9 +71,6 @@ def _ignore_pmg_warnings():
# ignore warning about structure charge that appears when getting Vasprun.as_dict():
warnings.filterwarnings("ignore", message="Structure charge")
- # SpglibDataset warning introduced in v2.4.1
- warnings.filterwarnings("ignore", message="dict interface")
-
_ignore_pmg_warnings()
diff --git a/doped/analysis.py b/doped/analysis.py
index d6d37212..0dcdcf0b 100644
--- a/doped/analysis.py
+++ b/doped/analysis.py
@@ -224,7 +224,6 @@ def defect_from_structures(
Dictionary of bulk supercell Voronoi node information, for
further expedited site-matching.
"""
- warnings.filterwarnings("ignore", "dict interface") # ignore spglib warning from v2.4.1
try:
def_type, comp_diff = get_defect_type_and_composition_diff(bulk_supercell, defect_supercell)
except RuntimeError as exc:
diff --git a/doped/chemical_potentials.py b/doped/chemical_potentials.py
index 5cce5a1a..30ebaaed 100644
--- a/doped/chemical_potentials.py
+++ b/doped/chemical_potentials.py
@@ -9,7 +9,7 @@
import itertools
import os
import warnings
-from collections.abc import Iterable
+from collections.abc import Iterable, Sequence
from copy import deepcopy
from pathlib import Path
from typing import Any, Optional, Union
@@ -18,6 +18,7 @@
import pandas as pd
from monty.serialization import loadfn
from pymatgen.analysis.phase_diagram import PDEntry, PhaseDiagram
+from pymatgen.analysis.structure_matcher import StructureMatcher
from pymatgen.core import SETTINGS, Composition, Element, Structure
from pymatgen.entries.computed_entries import (
ComputedEntry,
@@ -33,6 +34,7 @@
from doped import _ignore_pmg_warnings
from doped.utils.parsing import _get_output_files_and_check_if_multiple, get_vasprun
+from doped.utils.symmetry import get_primitive_structure
from doped.vasp import MODULE_DIR, DopedDictSet, default_HSE_set, default_relax_set
# globally ignore:
@@ -52,13 +54,15 @@
"icsd_id",
"icsd_ids", # some entries have icsd_id and some have icsd_ids
"theoretical",
- "formation_energy_per_atom",
- "energy_per_atom",
- "energy",
+ "formation_energy_per_atom", # uncorrected with legacy MP API, corrected with new API
+ "energy_per_atom", # note that with legacy MP API this is uncorrected, but is corrected with new API
+ "energy", # note that with legacy MP API this is uncorrected, but is corrected with new API
"total_magnetization",
"nelements",
"elements",
-]
+] # note that, because the energy values in the ``data`` dict are uncorrected with legacy MP API and
+# corrected with the new MP API, we should refrain from using these values when possible. The ``energy``
+# and ``energy_per_atom`` attributes are consistent (corrected in both cases)
MP_API_property_keys = {
"legacy": {
@@ -78,6 +82,14 @@
# pymatgen/analysis/defects/plotting/phases.py may be useful
+def _get_pretty_formula(entry_data: dict):
+ return entry_data.get("pretty_formula", entry_data.get("formula_pretty", "N/A"))
+
+
+def _get_e_above_hull(entry_data: dict):
+ return entry_data.get("e_above_hull", entry_data.get("energy_above_hull", 0.0))
+
+
def make_molecule_in_a_box(element: str):
"""
Generate an X2 'molecule-in-a-box' structure for the input element X, (i.e.
@@ -141,7 +153,7 @@ def make_molecular_entry(computed_entry, legacy_MP=False):
"""
property_key_dict = MP_API_property_keys["legacy"] if legacy_MP else MP_API_property_keys["new"]
assert len(computed_entry.composition.elements) == 1 # Elemental!
- formula = computed_entry.data[property_key_dict["pretty_formula"]]
+ formula = _get_pretty_formula(computed_entry.data)
element = formula[0].upper()
struct, total_magnetization = make_molecule_in_a_box(element)
molecular_entry = ComputedStructureEntry(
@@ -430,14 +442,11 @@ def get_entries_in_chemsys(
if e_above_hull is not None:
MP_full_pd_entries = [
- entry
- for entry in MP_full_pd_entries
- if entry.data[property_key_dict["energy_above_hull"]] <= e_above_hull
+ entry for entry in MP_full_pd_entries if _get_e_above_hull(entry.data) <= e_above_hull
]
- MP_full_pd_entries.sort( # sort by energy above hull, num_species, then alphabetically:
- key=lambda x: _entries_sorting_func(x, legacy_MP)
- )
+ # sort by energy above hull, num_species, then alphabetically:
+ MP_full_pd_entries.sort(key=lambda x: _entries_sorting_func(x))
if return_all_info:
return MP_full_pd_entries, property_key_dict, property_data_fields
@@ -491,9 +500,8 @@ def get_entries(
**kwargs,
)
- entries.sort( # sort by energy above hull, num_species, then alphabetically:
- key=lambda x: _entries_sorting_func(x, legacy_MP)
- )
+ # sort by energy above hull, num_species, then alphabetically:
+ entries.sort(key=lambda x: _entries_sorting_func(x))
return entries
@@ -569,15 +577,16 @@ def _parse_MP_API_key(api_key: Optional[str] = None, legacy_MP_info: bool = Fals
def get_MP_summary_docs(
- chemsys: Union[str, list[str]],
- api_key: Optional[str] = None,
entries: Optional[list[ComputedEntry]] = None,
+ chemsys: Optional[Union[str, list[str]]] = None,
+ api_key: Optional[str] = None,
data_fields: Optional[list[str]] = None,
**kwargs,
):
"""
Get the corresponding Materials Project (MP) ``SummaryDoc`` documents for
- computed entries in the input ``chemsys`` chemical system.
+ computed entries in the input ``entries`` list or ``chemsys`` chemical
+ system.
If ``entries`` is provided (which should be a list of ``ComputedEntry``s
from the Materials Project), then only ``SummaryDoc``s in this chemical
@@ -589,16 +598,24 @@ def get_MP_summary_docs(
from the corresponding ``SummaryDoc`` attribute to ``ComputedEntry.data``
for the matching ``ComputedEntry`` in ``entries``
-
Note that this function can only be used with the new Materials Project API,
as the legacy API does not have the ``SummaryDoc`` functionality (but most of
the same data is available through the ``property_data`` arguments for the
legacy-API-compatible functions).
Args:
+ entries (list[ComputedEntry]):
+ Optional input; list of ``ComputedEntry`` objects for the input chemical
+ system. If provided, only ``SummaryDoc``s which match one of these entries
+ (based on the MPIDs given in ``ComputedEntry.entry_id``/
+ ``ComputedEntry.data["material_id"]`` and ``SummaryDoc.material_id``) are
+ returned. Moreover, all data fields listed in ``data_fields`` will be copied
+ from the corresponding ``SummaryDoc`` attribute to ``ComputedEntry.data`` for
+ the matching ``ComputedEntry`` in ``entries``.
chemsys (str, list[str]):
- Chemical system to get entries for, in the format "A-B-C" or
- ["A", "B", "C"]. E.g. "Li-Fe-O" or ["Li", "Fe", "O"].
+ Optional input; chemical system to get entries for, in the format "A-B-C" or
+ ["A", "B", "C"]. E.g. "Li-Fe-O" or ["Li", "Fe", "O"]. Either ``entries`` or
+ ``chemsys`` must be provided!
api_key (str):
Materials Project (MP) API key, needed to access the MP database
to obtain the corresponding ``SummaryDoc`` documents. Must be
@@ -607,14 +624,6 @@ def get_MP_summary_docs(
or ``~/.config/.pmgrc.yaml``) - see the ``doped`` Installation docs page:
https://doped.readthedocs.io/en/latest/Installation.html#setup-potcars-and-materials
-project-api
- entries (list[ComputedEntry]):
- Optional input; list of ``ComputedEntry`` objects for the input chemical
- system. If provided, only ``SummaryDoc``s which match one of these entries
- (based on the MPIDs given in ``ComputedEntry.entry_id``/
- ``ComputedEntry.data["material_id"]`` and ``SummaryDoc.material_id``) are
- returned. Moreover, all data fields listed in ``data_fields`` will be copied
- from the corresponding ``SummaryDoc`` attribute to ``ComputedEntry.data`` for
- the matching ``ComputedEntry`` in ``entries``.
data_fields (list[str]):
List of data fields to copy from the corresponding ``SummaryDoc``
attributes to the ``ComputedEntry.data`` objects, if ``entries`` is supplied.
@@ -633,23 +642,23 @@ def get_MP_summary_docs(
"`get_MP_summary_docs` can only be used with the new Materials Project (MP) API (see "
"https://next-gen.materialsproject.org/api), but a legacy MP API key was supplied!"
)
+ if entries is None and chemsys is None:
+ raise ValueError("Either `entries` or `chemsys` must be provided!")
- with MPRester(api_key) as mpr:
- MP_full_pd_docs = {
- doc.material_id: doc
- for doc in mpr.materials.summary.search(
- chemsys=_get_all_chemsyses("-".join(chemsys)), **kwargs
- )
+ if entries:
+ summary_search_kwargs = {
+ "material_ids": [entry.data["material_id"] for entry in entries],
+ **kwargs,
}
+ else:
+ assert chemsys is not None # typing
+ summary_search_kwargs = {"chemsys": _get_all_chemsyses("-".join(chemsys)), **kwargs}
- if not entries:
- return MP_full_pd_docs
+ with MPRester(api_key) as mpr:
+ MP_docs = {doc.material_id: doc for doc in mpr.materials.summary.search(**summary_search_kwargs)}
- MP_docs = {
- material_id: doc
- for material_id, doc in MP_full_pd_docs.items()
- if material_id in [entry.data["material_id"] for entry in entries]
- }
+ if not entries:
+ return MP_docs
if data_fields is None:
data_fields = ["band_gap", "total_magnetization", "database_IDs"] # ICSD IDs and possibly others
@@ -676,22 +685,16 @@ def get_MP_summary_docs(
return MP_docs
-def _entries_sorting_func(entry: ComputedEntry, legacy_MP=False, use_e_per_atom: bool = False):
+def _entries_sorting_func(entry: ComputedEntry, use_e_per_atom: bool = False):
"""
Function to sort ``ComputedEntry``s by energy above hull, then by the
number of elements in the formula, then alphabetically by formula.
- Usage: entries_list.sort(key=_entries_sorting_func)
+ Usage: ``entries_list.sort(key=_entries_sorting_func)``
Args:
entry (ComputedEntry):
ComputedEntry object to sort.
- legacy_MP (bool):
- If ``True``, use the legacy Materials Project property data fields
- (i.e. ``"e_above_hull"``, ``"pretty_formula"`` etc.), rather than
- the new Materials Project API format (``"energy_above_hull"``,
- ``"formula_pretty"`` etc.).
- Default is ``False``.
use_e_per_atom (bool):
If ``True``, sort by energy per atom rather than energy above hull.
Default is ``False``.
@@ -702,11 +705,7 @@ def _entries_sorting_func(entry: ComputedEntry, legacy_MP=False, use_e_per_atom:
in the formula, and formula name of the entry.
"""
return (
- (
- entry.energy_per_atom
- if use_e_per_atom
- else entry.data[MP_API_property_keys["legacy" if legacy_MP else "new"]["energy_above_hull"]]
- ),
+ entry.energy_per_atom if use_e_per_atom else _get_e_above_hull(entry.data),
len(Composition(entry.name).as_dict()),
entry.name,
)
@@ -716,7 +715,7 @@ def prune_entries_to_border_candidates(
entries: list[ComputedEntry],
bulk_computed_entry: ComputedEntry,
phase_diagram: Optional[PhaseDiagram] = None,
- e_above_hull: float = 0.1,
+ e_above_hull: float = 0.05,
):
"""
Given an input list of ``ComputedEntry``/``ComputedStructureEntry``s
@@ -725,7 +724,7 @@ def prune_entries_to_border_candidates(
border the host on the phase diagram (and therefore be a competing phase
which determines the host chemical potential limits), allowing for an error
tolerance for the semi-local DFT database energies (``e_above_hull``, set
- to ``self.e_above_hull`` 0.1 eV/atom by default).
+ to ``self.e_above_hull`` 0.05 eV/atom by default).
If ``phase_diagram`` is provided then this is used as the reference
phase diagram, otherwise it is generated from ``entries`` and
@@ -752,7 +751,7 @@ def prune_entries_to_border_candidates(
All phases that would border the host material on the phase
diagram, if their relative energy was downshifted by
``e_above_hull``, are included.
- (Default is 0.1 eV/atom).
+ (Default is 0.05 eV/atom).
Returns:
list[ComputedEntry]:
@@ -775,27 +774,116 @@ def prune_entries_to_border_candidates(
bordering_entry.name for bordering_entry in bordering_entries
] # compositions which border the host with EaH=0, according to MP, so we include all phases with
# these compositions up to EaH=e_above_hull (which we've already pruned to)
+ # for determining phases which alter the chemical potential limits when renormalised, only need to
+ # retain the EaH=0 entries from above, so we use this reduced PD to save compute time when looping
+ # below:
+ reduced_pd_entries = {
+ entry
+ for entry in bordering_entries
+ if entry.data.get("energy_above_hull", entry.data.get("e_above_hull", 0)) == 0
+ }
# then add any other phases that would border the host material on the phase diagram, if their
# relative energy was downshifted by ``e_above_hull``:
- for entry in entries: # only check if not already bordering; can just use names for this:
- if entry.name not in bordering_entry_names:
+ # only check if not already bordering; can just use names for this:
+ entries_to_test = [entry for entry in entries if entry.name not in bordering_entry_names]
+ entries_to_test.sort(key=_entries_sorting_func) # sort by energy above hull
+ # to save unnecessary looping, whenever we encounter a phase that is not being added to the border
+ # candidates list, skip all following phases with this composition (because they have higher
+ # energies above hull (because we've sorted by this) and so will also not border the host):
+ compositions_to_skip = []
+ for entry in entries_to_test:
+ if entry.name not in compositions_to_skip:
# decrease entry energy per atom by ``e_above_hull`` eV/atom
renormalised_entry = _renormalise_entry(entry, e_above_hull)
- new_phase_diagram = PhaseDiagram([*phase_diagram.entries, renormalised_entry])
+ new_phase_diagram = PhaseDiagram(
+ [*reduced_pd_entries, bulk_computed_entry, renormalised_entry]
+ )
shifted_MP_chempots = get_chempots_from_phase_diagram(bulk_computed_entry, new_phase_diagram)
+ shifted_MP_bordering_phases = {
+ phase for limit in shifted_MP_chempots for phase in limit.split("-")
+ }
- if shifted_MP_chempots != MP_chempots: # new bordering phase, add to list
+ if shifted_MP_bordering_phases != MP_bordering_phases: # new bordering phase, add to list
bordering_entries.append(entry)
+ else:
+ compositions_to_skip.append(entry.name)
return bordering_entries
+def get_and_set_competing_phase_name(
+ entry: Union[ComputedStructureEntry, ComputedEntry], regenerate=False, ndigits=3
+) -> str:
+ """
+ Get the ``doped`` name for a competing phase entry from the Materials
+ Project (MP) database.
+
+ The default naming convention in ``doped`` for competing phases is:
+ ``"{Chemical Formula}_{Space Group}_EaH_{MP Energy above Hull}"``.
+ This is stored in the ``entry.data["doped_name"]`` key-value pair.
+ If this value is already set, then this function just returns the
+ previously-generated ``doped`` name, unless ``regenerate=True``.
+
+ Args:
+ entry (ComputedStructureEntry, ComputedEntry):
+ ``pymatgen`` ``ComputedStructureEntry`` object for the
+ competing phase.
+ regenerate (bool):
+ Whether to regenerate the ``doped`` name for the competing
+ phase, if ``entry.data["doped_name"]`` already set.
+ Default is False.
+ ndigits (int):
+ Number of digits to round the energy above hull value (in
+ eV/atom) to. Default is 3.
+
+ Returns:
+ doped_name (str):
+ The ``doped`` name for the competing phase, to use as folder
+ name when generating calculation inputs.
+ """
+ if not entry.data.get("doped_name") or regenerate: # not set, so generate
+ rounded_eah = round(_get_e_above_hull(entry.data), ndigits)
+ if np.isclose(rounded_eah, 0):
+ rounded_eah = 0
+ space_group = entry.structure.get_space_group_info()[0] if hasattr(entry, "structure") else "NA"
+ entry.data["doped_name"] = f"{entry.name}_{space_group}_EaH_{rounded_eah}"
+
+ return entry.data.get("doped_name")
+
+
+def _name_entries_and_handle_duplicates(entries: list[ComputedStructureEntry]):
+ """
+ Given an input list of ``ComputedStructureEntry`` objects, sets the
+ ``entry.data["doped_name"]`` values using
+ ``get_and_set_competing_phase_name``, and increases ``ndigits`` (rounding
+ for energy above hull in name) dynamically from 3 -> 4 -> 5 to ensure no
+ duplicate names.
+ """
+ ndigits = 3
+ entry_names = [get_and_set_competing_phase_name(entry, ndigits=ndigits) for entry in entries]
+ while duplicate_entries := [
+ entries[i] for i, name in enumerate(entry_names) if entry_names.count(name) > 1
+ ]:
+ ndigits += 1
+ if ndigits == 5:
+ warnings.warn(
+ f"Duplicate entry names found for generated competing phases: "
+ f"{get_and_set_competing_phase_name(duplicate_entries[0])}!"
+ )
+ break
+ _duplicate_entry_names = [
+ get_and_set_competing_phase_name(entry, regenerate=True, ndigits=ndigits)
+ for entry in duplicate_entries
+ ]
+ entry_names = [get_and_set_competing_phase_name(entry, regenerate=False) for entry in entries]
+
+
class CompetingPhases:
def __init__(
self,
- composition: Union[str, Composition],
- e_above_hull: float = 0.1,
+ composition: Union[str, Composition, Structure],
+ e_above_hull: float = 0.05,
api_key: Optional[str] = None,
full_phase_diagram: bool = False,
):
@@ -807,16 +895,24 @@ def __init__(
For this, the Materials Project (MP) database is queried using the
``MPRester`` API, and any calculated compounds which `could` border
the host material within an error tolerance for the semi-local DFT
- database energies (``e_above_hull``, 0.1 eV/atom by default) are
+ database energies (``e_above_hull``, 0.05 eV/atom by default) are
generated, along with the elemental reference phases.
Diatomic gaseous molecules are generated as molecules-in-a-box as
appropriate (e.g. for O2, F2, H2 etc).
+ Often ``e_above_hull`` can be lowered to reduce the number of
+ calculations while retaining good accuracy relative to the typical
+ error of defect calculations.
+
Args:
- composition (str, ``Composition``):
+ composition (str, ``Composition``, ``Structure``):
Composition of the host material (e.g. ``'LiFePO4'``, or
``Composition('LiFePO4')``, or
``Composition({"Li":1, "Fe":1, "P":1, "O":4})``).
+ Alternatively a ``pymatgen`` ``Structure`` object for the
+ host material can be supplied (recommended), in which case
+ the primitive structure will be used as the only host
+ composition phase, reducing the number of calculations.
e_above_hull (float):
Maximum energy above hull (in eV/atom) of Materials Project
entries to be considered as competing phases. This is an
@@ -826,7 +922,10 @@ def __init__(
All phases that would border the host material on the phase
diagram, if their relative energy was downshifted by
``e_above_hull``, are included.
- (Default is 0.1 eV/atom).
+ Often ``e_above_hull`` can be lowered to reduce the number of
+ calculations while retaining good accuracy relative to the
+ typical error of defect calculations.
+ (Default is 0.05 eV/atom).
api_key (str):
Materials Project (MP) API key, needed to access the MP
database for competing phase generation. If not supplied, will
@@ -845,12 +944,12 @@ def __init__(
"""
self.e_above_hull = e_above_hull # store parameters for reference
self.full_phase_diagram = full_phase_diagram
- # get API key, and print info message if it corresponds to legacy MP -- remove this (and legacy
- # MP API warning filter) in future versions (TODO)
+ # get API key, and print info message if it corresponds to legacy MP -- remove this and legacy
+ # MP API warning filter in future versions (TODO)
self.api_key, self.legacy_MP = _parse_MP_API_key(api_key, legacy_MP_info=True)
warnings.filterwarnings( # Remove in future when users have been given time to transition
"ignore", message="You are using the legacy MPRester"
- ) # currently rely on this so shouldn't show warning, `message` only needs to match start
+ ) # previously relied on this so shouldn't show warning, `message` only needs to match start
# TODO: Should hard code S (solid + S8), P, Te and Se in here too. Common anions with a
# lot of unnecessary polymorphs on MP. Should at least scan over elemental phases and hard code
@@ -879,15 +978,24 @@ def __init__(
# - Could have two optional EaH tolerances, a tight one (0.02 eV/atom?) that applies to all,
# and a looser one (0.1 eV/atom?) that applies to phases with ICSD IDs?
- self.bulk_composition = Composition(composition)
- self.chemsys = list(self.bulk_composition.as_dict().keys())
+ if isinstance(composition, Structure):
+ # if structure is not primitive, reduce to primitive:
+ primitive_structure = get_primitive_structure(composition)
+ if len(primitive_structure) < len(composition):
+ self.bulk_structure = primitive_structure
+ else:
+ self.bulk_structure = composition
+ self.bulk_composition = self.bulk_structure.composition
- # TODO: Update installation pages, docs and tutorials
- # TODO: Add tests with new API keys
+ else:
+ self.bulk_structure = None
+ self.bulk_composition = Composition(composition)
+
+ self.chemsys = list(self.bulk_composition.as_dict().keys())
# get all entries in the chemical system:
self.MP_full_pd_entries, self.property_key_dict, self.property_data_fields = (
- get_entries_in_chemsys(
+ get_entries_in_chemsys( # get all entries in the chemical system, with EaH<``e_above_hull``
self.chemsys,
api_key=self.api_key,
e_above_hull=self.e_above_hull,
@@ -896,22 +1004,28 @@ def __init__(
)
self.MP_full_pd = PhaseDiagram(self.MP_full_pd_entries)
- # convert any gaseous elemental entries to molecules in a box, and prune to a_above_hull range
+ # convert any gaseous elemental entries to molecules in a box
formatted_entries = self._generate_elemental_diatomic_phases(self.MP_full_pd_entries)
# get bulk entry, and warn if not stable or not present on MP database:
- if bulk_entries := [
+ bulk_entries = [
entry
for entry in formatted_entries # sorted by e_above_hull above in get_entries_in_chemsys
if entry.composition.reduced_composition == self.bulk_composition.reduced_composition
- and entry.data[self.property_key_dict["energy_above_hull"]] == 0.0
+ ]
+ if zero_eah_bulk_entries := [
+ entry for entry in bulk_entries if _get_e_above_hull(entry.data) == 0.0
]:
- bulk_computed_entry = bulk_entries[0] # lowest energy entry for bulk (after sorting)
+ self.MP_bulk_computed_entry = bulk_computed_entry = zero_eah_bulk_entries[
+ 0
+ ] # lowest energy entry for bulk (after sorting)
else: # no EaH=0 bulk entries in pruned phase diagram, check first if present (but unstable)
- if MP_bulk_entries := get_entries( # composition present in MP, but not stable
+ if bulk_entries := get_entries( # composition present in MP, but not stable
self.bulk_composition.reduced_formula, api_key=self.api_key
):
- bulk_computed_entry = MP_bulk_entries[0] # already sorted by energy in get_entries()
+ self.MP_bulk_computed_entry = bulk_computed_entry = bulk_entries[
+ 0
+ ] # already sorted by energy in get_entries()
eah = PhaseDiagram(formatted_entries).get_e_above_hull(bulk_computed_entry)
warnings.warn(
f"Note that the Materials Project (MP) database entry for "
@@ -941,7 +1055,7 @@ def __init__(
f"space, and then determine the possible competing phases with the same approach as "
f"usual."
)
- bulk_computed_entry = ComputedEntry(
+ self.MP_bulk_computed_entry = bulk_computed_entry = ComputedEntry(
self.bulk_composition,
self.MP_full_pd.get_hull_energy(self.bulk_composition) - 1e-4,
data={
@@ -955,31 +1069,60 @@ def __init__(
) # TODO: Later need to add handling for file writing for this (POTCAR and INCAR assuming
# non-metallic, non-magnetic, with warning and recommendations
- if bulk_computed_entry not in formatted_entries:
- formatted_entries.append(bulk_computed_entry)
-
- self.MP_bulk_computed_entry = bulk_computed_entry
+ if self.MP_bulk_computed_entry not in formatted_entries:
+ formatted_entries.append(self.MP_bulk_computed_entry)
+
+ if self.bulk_structure: # prune all bulk phases to this structure
+ manual_bulk_entry = None
+
+ if bulk_entries := [
+ entry
+ for entry in formatted_entries # sorted by e_above_hull above in get_entries_in_chemsys
+ if entry.composition.reduced_composition == self.bulk_composition.reduced_composition
+ ]:
+ sm = StructureMatcher()
+ matching_bulk_entries = [
+ entry
+ for entry in bulk_entries
+ if hasattr(entry, "structure") and sm.fit(self.bulk_structure, entry.structure)
+ ]
+ matching_bulk_entries.sort(key=lambda x: sm.get_rms_dist(self.bulk_structure, x.structure))
+ if matching_bulk_entries:
+ matching_bulk_entry = matching_bulk_entries[0]
+ manual_bulk_entry = matching_bulk_entry
+ manual_bulk_entry._structure = self.bulk_structure
+
+ if manual_bulk_entry is None: # take the lowest energy bulk entry
+ manual_bulk_entry_dict = self.MP_bulk_computed_entry.as_dict()
+ manual_bulk_entry_dict["structure"] = self.bulk_structure.as_dict()
+ manual_bulk_entry = ComputedStructureEntry.from_dict(manual_bulk_entry_dict)
+
+ formatted_entries = [ # remove bulk entries from formatted_entries and add the new bulk entry
+ entry
+ for entry in formatted_entries
+ if entry.composition.reduced_composition != self.bulk_composition.reduced_composition
+ ]
+ formatted_entries.append(manual_bulk_entry)
if not self.full_phase_diagram: # default, prune to only phases that would border the host
# material on the phase diagram, if their relative energy was downshifted by ``e_above_hull``:
self.entries: list[ComputedEntry] = prune_entries_to_border_candidates(
entries=formatted_entries,
- bulk_computed_entry=self.MP_bulk_computed_entry,
+ bulk_computed_entry=bulk_computed_entry,
e_above_hull=self.e_above_hull,
)
else: # self.full_phase_diagram = True
self.entries = formatted_entries
- self.entries.sort( # sort by energy above hull, num_species, then alphabetically
- key=lambda x: _entries_sorting_func(x, self.legacy_MP)
- )
+ # sort by energy above hull, num_species, then alphabetically:
+ self.entries.sort(key=lambda x: _entries_sorting_func(x))
+ _name_entries_and_handle_duplicates(self.entries) # set entry names
if not self.legacy_MP: # need to pull ``SummaryDoc``s to get band_gap and magnetization info
self.MP_docs = get_MP_summary_docs(
- self.chemsys,
- api_key=self.api_key,
entries=self.entries, # sets "band_gap", "total_magnetization" and "database_IDs" fields
+ api_key=self.api_key,
)
# TODO: Return dict of DictSet objects for this and vasp_std_setup() functions, as well as
@@ -1060,13 +1203,7 @@ def convergence_setup(
+ ("_" * (dict_set.kpoints.kpts[0][0] // 10))
+ ",".join(str(k) for k in dict_set.kpoints.kpts[0])
)
- fname = f"competing_phases/{self._competing_phase_name(e)}/kpoint_converge/{kname}"
- # TODO: competing_phases folder name should be an optional parameter, and rename default
- # to something that isn't so ugly? CompetingPhases?
- # TODO: Naming should be done in __init__ to ensure consistency and efficiency. Watch
- # out for cases where rounding can give same name (e.g. Te!) - should use
- # {formula}_MP_{mpid}_EaH_{round(e_above_hull,4)} as naming convention, to prevent any
- # rare cases of overwriting -- should use space-group in names!
+ fname = f"CompetingPhases/{get_and_set_competing_phase_name(e)}/kpoint_converge/{kname}"
dict_set.write_input(fname, **kwargs)
for e in self.metals:
@@ -1090,15 +1227,9 @@ def convergence_setup(
+ ("_" * (dict_set.kpoints.kpts[0][0] // 10))
+ ",".join(str(k) for k in dict_set.kpoints.kpts[0])
)
- fname = f"competing_phases/{self._competing_phase_name(e)}/kpoint_converge/{kname}"
+ fname = f"CompetingPhases/{get_and_set_competing_phase_name(e)}/kpoint_converge/{kname}"
dict_set.write_input(fname, **kwargs)
- def _competing_phase_name(self, entry):
- rounded_eah = round(entry.data[self.property_key_dict["energy_above_hull"]], 4)
- if np.isclose(rounded_eah, 0):
- return f"{entry.name}_EaH_0"
- return f"{entry.name}_EaH_{rounded_eah}"
-
# TODO: Add vasp_ncl_setup()
def vasp_std_setup(
self,
@@ -1172,7 +1303,7 @@ def vasp_std_setup(
force_gamma=True,
)
- fname = f"competing_phases/{self._competing_phase_name(e)}/vasp_std"
+ fname = f"CompetingPhases/{get_and_set_competing_phase_name(e)}/vasp_std"
dict_set.write_input(fname, **kwargs)
for e in self.metals:
@@ -1189,7 +1320,7 @@ def vasp_std_setup(
force_gamma=True,
)
- fname = f"competing_phases/{self._competing_phase_name(e)}/vasp_std"
+ fname = f"CompetingPhases/{get_and_set_competing_phase_name(e)}/vasp_std"
dict_set.write_input(fname, **kwargs)
for e in self.molecules: # gamma-only for molecules
@@ -1213,7 +1344,7 @@ def vasp_std_setup(
user_potcar_functional=user_potcar_functional,
force_gamma=True,
)
- fname = f"competing_phases/{self._competing_phase_name(e)}/vasp_std"
+ fname = f"CompetingPhases/{get_and_set_competing_phase_name(e)}/vasp_std"
dict_set.write_input(fname, **kwargs)
def _set_spin_polarisation(self, incar_settings, user_incar_settings, entry):
@@ -1271,104 +1402,127 @@ def _generate_elemental_diatomic_phases(self, entries: list[ComputedEntry]):
for entry in entries.copy():
if (
- entry.data[self.property_key_dict["pretty_formula"]] in elemental_diatomic_gases
- and entry.data[self.property_key_dict["energy_above_hull"]] == 0.0
+ _get_pretty_formula(entry.data) in elemental_diatomic_gases
+ and _get_e_above_hull(entry.data) == 0.0
): # only once for each matching gaseous elemental entry
molecular_entry = make_molecular_entry(entry, legacy_MP=self.legacy_MP)
if not any(
entry.data["molecule"]
- and entry.data[self.property_key_dict["pretty_formula"]]
- == molecular_entry.data[self.property_key_dict["pretty_formula"]]
+ and _get_pretty_formula(entry.data) == _get_pretty_formula(molecular_entry.data)
for entry in formatted_entries
): # first entry only
entries.append(molecular_entry)
formatted_entries.append(molecular_entry)
- elif entry.data[self.property_key_dict["pretty_formula"]] not in elemental_diatomic_gases:
+ elif _get_pretty_formula(entry.data) not in elemental_diatomic_gases:
entry.data["molecule"] = False
formatted_entries.append(entry)
- formatted_entries.sort( # sort by energy above hull, num_species, then alphabetically
- key=lambda x: _entries_sorting_func(x, self.legacy_MP)
- )
+ # sort by energy above hull, num_species, then alphabetically:
+ formatted_entries.sort(key=lambda x: _entries_sorting_func(x))
return formatted_entries
-# TODO: This doesn't need to be a whole extra class right? Better just amalgamated?
class ExtrinsicCompetingPhases(CompetingPhases):
- """
- This class generates the competing phases that need to be calculated to
- obtain the chemical potential limits when doping with extrinsic species /
- impurities.
-
- Ensures that only the necessary additional competing phases are generated.
- """
-
def __init__(
self,
- composition: Union[str, Composition],
+ composition: Union[str, Composition, Structure],
extrinsic_species: Union[str, Iterable],
- e_above_hull: float = 0.1,
+ e_above_hull: float = 0.05,
full_sub_approach: bool = False,
codoping: bool = False,
api_key: Optional[str] = None,
full_phase_diagram: bool = False,
):
"""
- This code uses the Materials Project (MP) phase diagram data along with
- the ``e_above_hull`` error range to generate potential competing
- phases.
+ Class to generate VASP input files for competing phases involving
+ extrinsic (dopant/impurity) elements, which determine the chemical
+ potential limits for those elements in the host compound.
+
+ Only extrinsic competing phases are contained in the
+ ``ExtrinsicCompetingPhases.entries`` list (used for input file
+ generation), while the `intrinsic` competing phases for the host
+ compound are stored in ``ExtrinsicCompetingPhases.intrinsic_entries``.
+
+ For this, the Materials Project (MP) database is queried using the
+ ``MPRester`` API, and any calculated compounds which `could` border
+ the host material within an error tolerance for the semi-local DFT
+ database energies (``e_above_hull``, 0.05 eV/atom by default) are
+ generated, along with the elemental reference phases.
+ Diatomic gaseous molecules are generated as molecules-in-a-box as
+ appropriate (e.g. for O2, F2, H2 etc).
+
+ Often ``e_above_hull`` can be lowered to reduce the number of
+ calculations while retaining good accuracy relative to the typical
+ error of defect calculations.
Args:
- composition (str, Composition):
- Composition of host material (e.g. 'LiFePO4', or Composition('LiFePO4'),
- or Composition({"Li":1, "Fe":1, "P":1, "O":4}))
+ composition (str, ``Composition``, ``Structure``):
+ Composition of the host material (e.g. ``'LiFePO4'``, or
+ ``Composition('LiFePO4')``, or
+ ``Composition({"Li":1, "Fe":1, "P":1, "O":4})``).
+ Alternatively a ``pymatgen`` ``Structure`` object for the
+ host material can be supplied (recommended), in which case
+ the primitive structure will be used as the only host
+ composition phase, reducing the number of calculations.
extrinsic_species (str, Iterable):
- Extrinsic dopant/impurity species to consider, to generate the relevant
- competing phases to additionally determine their chemical potential
- limits within the host. Can be a single element as a string (e.g. "Mg")
- or an iterable of element strings (list, set, tuple, dict) (e.g. ["Mg",
- "Na"]).
+ Extrinsic dopant/impurity species to consider, to generate
+ the relevant competing phases to additionally determine their
+ chemical potential limits within the host. Can be a single
+ element as a string (e.g. "Mg") or an iterable of element
+ strings (list, set, tuple, dict) (e.g. ["Mg", "Na"]).
e_above_hull (float):
Maximum energy-above-hull of Materials Project entries to be
- considered as competing phases. This is an uncertainty range for the
- MP-calculated formation energies, which may not be accurate due to functional
- choice (GGA vs hybrid DFT / GGA+U / RPA etc.), lack of vdW corrections etc.
- Any phases that would border the host material on the phase diagram, if their
- relative energy was downshifted by ``e_above_hull``, are included.
- Default is 0.1 eV/atom.
+ considered as competing phases. This is an uncertainty range
+ for the MP-calculated formation energies, which may not be
+ accurate due to functional choice (GGA vs hybrid DFT / GGA+U
+ / RPA etc.), lack of vdW corrections etc.
+ Any phases that would border the host material on the phase
+ diagram, if their relative energy was downshifted by
+ ``e_above_hull``, are included.
+
+ Often ``e_above_hull`` can be lowered to reduce the number of
+ calculations while retaining good accuracy relative to the
+ typical error of defect calculations.
+
+ Default is 0.05 eV/atom.
full_sub_approach (bool):
- Generate competing phases by considering the full phase diagram, including
- chemical potential limits with multiple extrinsic phases. Only recommended when
- looking at high (non-dilute) doping concentrations.
- Default is ``False``.
- The default approach (``full_sub_approach = False``) for extrinsic elements is to
- only consider chemical potential limits where the host composition borders a maximum
- of 1 extrinsic phase (composition with extrinsic element(s)). This is a valid
- approximation for the case of dilute dopant/impurity concentrations. For high
- (non-dilute) concentrations of extrinsic species, use ``full_sub_approach = True``.
+ Generate competing phases by considering the full phase
+ diagram, including chemical potential limits with multiple
+ extrinsic phases. Only recommended when looking at high
+ (non-dilute) doping concentrations. Default is ``False``.
+
+ The default approach (``full_sub_approach = False``) for
+ extrinsic elements is to only consider chemical potential
+ limits where the host composition borders a maximum of 1
+ extrinsic phase (composition with extrinsic element(s)).
+ This is a valid approximation for the case of dilute
+ dopant/impurity concentrations. For high (non-dilute)
+ concentrations of extrinsic species, use ``full_sub_approach = True``.
codoping (bool):
- Whether to consider extrinsic competing phases containing multiple
- extrinsic species. Only relevant to high (non-dilute) co-doping concentrations.
- If set to True, then ``full_sub_approach`` is also set to ``True``.
+ Whether to consider extrinsic competing phases containing
+ multiple extrinsic species. Only relevant to high (non-dilute)
+ co-doping concentrations. If set to True, then
+ ``full_sub_approach`` is also set to ``True``.
Default is ``False``.
api_key (str):
- Materials Project (MP) API key, needed to access the MP database for
- competing phase generation. If not supplied, will attempt to read from
- environment variable ``PMG_MAPI_KEY`` (in ``~/.pmgrc.yaml``) - see the ``doped``
- Installation docs page: https://doped.readthedocs.io/en/latest/Installation.html
+ Materials Project (MP) API key, needed to access the MP database
+ for competing phase generation. If not supplied, will attempt
+ to read from environment variable ``PMG_MAPI_KEY`` (in
+ ``~/.pmgrc.yaml``) - see the ``doped`` Installation docs page:
+ https://doped.readthedocs.io/en/latest/Installation.html.
MP API key is available at https://next-gen.materialsproject.org/api#api-key
full_phase_diagram (bool):
- If ``True``, include all phases on the MP phase diagram (with energy
- above hull < ``e_above_hull`` eV/atom) for the chemical system of
- the input composition and extrinsic species (not recommended). If ``False``,
- only includes phases that would border the host material on the phase diagram
- (and thus set the chemical potential limits), if their relative energy was
- downshifted by ``e_above_hull`` eV/atom.
- (Default is ``False``).
+ If ``True``, include all phases on the MP phase diagram (with
+ energy above hull < ``e_above_hull`` eV/atom) for the chemical
+ system of the input composition and extrinsic species (not
+ recommended). If ``False``, only includes phases that would border
+ the host material on the phase diagram (and thus set the chemical
+ potential limits), if their relative energy was downshifted by
+ ``e_above_hull`` eV/atom.
+ Default is ``False``.
"""
- # competing phases & entries of the OG system:
- super().__init__(
+ super().__init__( # competing phases & entries of the OG system:
composition=composition,
e_above_hull=e_above_hull,
api_key=api_key,
@@ -1520,14 +1674,83 @@ def __init__(
self.entries += single_bordering_sub_el_entries
- self.MP_full_pd_entries.sort( # sort by energy above hull, num_species, then alphabetically
- key=lambda x: _entries_sorting_func(x, self.legacy_MP)
- )
+ # sort all entries by energy above hull, num_species, then alphabetically:
+ self.MP_full_pd_entries.sort(key=lambda x: _entries_sorting_func(x))
self.MP_full_pd = PhaseDiagram(self.MP_full_pd_entries)
- self.entries.sort( # sort by energy above hull, num_species, then alphabetically
- key=lambda x: _entries_sorting_func(x, self.legacy_MP)
+ self.entries.sort(key=lambda x: _entries_sorting_func(x))
+ _name_entries_and_handle_duplicates(self.entries) # set entry names
+
+
+def get_doped_chempots_from_entries(
+ entries: Sequence[Union[ComputedEntry, ComputedStructureEntry, PDEntry]],
+ composition: Union[str, Composition, ComputedEntry],
+ sort_by: Optional[str] = None,
+ single_chempot_limit: bool = False,
+) -> dict:
+ r"""
+ Given a list of ``ComputedEntry``\s / ``ComputedStructureEntry``\s /
+ ``PDEntry``\s and the bulk ``composition``, returns the chemical potential
+ limits dictionary in the ``doped`` format (i.e. ``{"limits": [{'limit':
+ [chempot_dict]}], ...}``) for the host material.
+
+ Args:
+ entries (list[ComputedEntry]):
+ List of ``ComputedEntry``\s / ``ComputedStructureEntry``\s /
+ ``PDEntry``\s for the chemical system, from which to determine
+ the chemical potential limits for the host material (``composition``).
+ composition (str, Composition, ComputedEntry):
+ Composition of the host material either as a string
+ (e.g. 'LiFePO4') a ``pymatgen`` ``Composition`` object (e.g.
+ ``Composition('LiFePO4')``), or a ``ComputedEntry`` object.
+ sort_by (str):
+ If set, will sort the chemical potential limits in the output
+ ``DataFrame`` according to the chemical potential of the specified
+ element (from element-rich to element-poor conditions).
+ single_chempot_limit (bool):
+ If set to ``True``, only returns the first chemical potential limit
+ in the calculated chemical potentials dictionary. Mainly intended for
+ internal ``doped`` usage when the host material is calculated to be
+ unstable with respect to the competing phases.
+
+ Returns:
+ dict:
+ Dictionary of chemical potential limits in the ``doped`` format.
+ """
+ if isinstance(composition, (str, Composition)):
+ composition = Composition(composition)
+ else:
+ composition = composition.composition
+
+ phase_diagram = PhaseDiagram(
+ entries,
+ list(map(Element, composition.elements)), # preserve bulk comp element ordering
+ )
+ chem_lims = phase_diagram.get_all_chempots(composition.reduced_composition)
+ chem_lims_iterator = list(chem_lims.items())[:1] if single_chempot_limit else chem_lims.items()
+
+ # remove Element to make it JSONable:
+ no_element_chem_lims = {k: {str(kk): vv for kk, vv in v.items()} for k, v in chem_lims_iterator}
+
+ if sort_by is not None:
+ no_element_chem_lims = dict(
+ sorted(no_element_chem_lims.items(), key=lambda x: x[1][sort_by], reverse=True)
)
+ chempots = {
+ "limits": no_element_chem_lims,
+ "elemental_refs": {str(el): ent.energy_per_atom for el, ent in phase_diagram.el_refs.items()},
+ "limits_wrt_el_refs": {},
+ }
+
+ # relate the limits to the elemental energies
+ for limit, chempot_dict in chempots["limits"].items():
+ relative_chempot_dict = copy.deepcopy(chempot_dict)
+ for e in relative_chempot_dict:
+ relative_chempot_dict[e] -= chempots["elemental_refs"][e]
+ chempots["limits_wrt_el_refs"].update({limit: relative_chempot_dict})
+
+ return chempots
+
class CompetingPhasesAnalyzer:
# TODO: Allow parsing using pymatgen ComputedEntries as well, to aid interoperability with
@@ -1565,7 +1788,7 @@ def __init__(self, composition: Union[str, Composition]):
# which auto-parses vaspruns from the subdirectories (or optionally a list of vaspruns,
# or a csv path); see shelved changes for this
# TODO: Could add multiprocessing like DefectsParser to expedite parsing?
- def from_vaspruns(self, path="competing_phases", folder="vasp_std", csv_path=None, verbose=True):
+ def from_vaspruns(self, path="CompetingPhases", folder="vasp_std", csv_path=None, verbose=True):
"""
Parses competing phase energies from ``vasprun.xml(.gz)`` outputs,
computes the formation energies and generates the
@@ -1999,7 +2222,7 @@ def calculate_chempots(
self._intrinsic_phase_diagram = PhaseDiagram(
intrinsic_phase_diagram_entries,
- map(Element, self.bulk_composition.elements),
+ list(map(Element, self.bulk_composition.elements)), # preserve bulk comp element ordering
)
# check if it's stable and if not, warn user and downshift to get _least_ unstable point on convex
@@ -2027,38 +2250,15 @@ def calculate_chempots(
)
self._intrinsic_phase_diagram = PhaseDiagram(
[*intrinsic_phase_diagram_entries, renormalised_bulk_pde],
- map(Element, self.bulk_composition.elements),
+ list(map(Element, self.bulk_composition.elements)), # preserve bulk comp element ordering
)
- chem_lims = self._intrinsic_phase_diagram.get_all_chempots(self.bulk_composition)
-
- # remove Element to make it JSONable:
- no_element_chem_lims = {k: {str(kk): vv for kk, vv in v.items()} for k, v in chem_lims.items()}
-
- if unstable_host:
- no_element_chem_lims = {
- k: {str(kk): vv for kk, vv in v.items()} for k, v in list(chem_lims.items())[:1]
- }
-
- if sort_by is not None:
- no_element_chem_lims = dict(
- sorted(no_element_chem_lims.items(), key=lambda x: x[1][sort_by], reverse=True)
- )
-
- self._intrinsic_chempots = {
- "limits": no_element_chem_lims,
- "elemental_refs": {
- str(el): ent.energy_per_atom for el, ent in self._intrinsic_phase_diagram.el_refs.items()
- },
- "limits_wrt_el_refs": {},
- }
-
- # relate the limits to the elemental energies
- for limit, chempot_dict in self._intrinsic_chempots["limits"].items():
- relative_chempot_dict = copy.deepcopy(chempot_dict)
- for e in relative_chempot_dict:
- relative_chempot_dict[e] -= self._intrinsic_chempots["elemental_refs"][e]
- self._intrinsic_chempots["limits_wrt_el_refs"].update({limit: relative_chempot_dict})
+ self._intrinsic_chempots = get_doped_chempots_from_entries(
+ self._intrinsic_phase_diagram.entries,
+ self.bulk_composition,
+ sort_by=sort_by,
+ single_chempot_limit=unstable_host,
+ )
# get chemical potentials as pandas dataframe
chemical_potentials = []
@@ -2188,7 +2388,7 @@ def intrinsic_chempots(self) -> dict:
return self._intrinsic_chempots
@property
- def intrinsic_phase_diagram(self) -> dict:
+ def intrinsic_phase_diagram(self) -> PhaseDiagram:
"""
Returns the calculated intrinsic phase diagram.
"""
diff --git a/doped/core.py b/doped/core.py
index abbf00bb..66710b70 100644
--- a/doped/core.py
+++ b/doped/core.py
@@ -30,10 +30,6 @@
from easyunfold.procar import Procar as EasyunfoldProcar
-# SpglibDataset warning introduced in v2.4.1, can be ignored for now
-warnings.filterwarnings("ignore", message="dict interface")
-
-
_orientational_degeneracy_warning = (
"The defect supercell has been detected to possibly have a non-scalar matrix expansion, "
"which could be breaking the cell periodicity and possibly preventing the correct _relaxed_ "
@@ -1438,10 +1434,8 @@ def _guess_and_set_struct_oxi_states(structure):
if oxidation states could not be guessed.
"""
bv_analyzer = BVAnalyzer()
- with contextlib.suppress(ValueError), warnings.catch_warnings():
- # ValueError raised if oxi states can't be assigned, and SpglibDataset warning introduced in
- # v2.4.1, can be ignored for now:
- warnings.filterwarnings("ignore", message="dict interface")
+ with contextlib.suppress(ValueError):
+ # ValueError raised if oxi states can't be assigned
oxi_dec_structure = bv_analyzer.get_oxi_state_decorated_structure(structure)
if all(
np.isclose(int(specie.oxi_state), specie.oxi_state) for specie in oxi_dec_structure.species
diff --git a/doped/generation.py b/doped/generation.py
index 7df99fac..e3ffc3ad 100644
--- a/doped/generation.py
+++ b/doped/generation.py
@@ -1197,8 +1197,6 @@ class (such as ``clustering_tol``, ``stol``, ``min_dist`` etc), or to
``DefectsGenerator`` input parameters are also set as attributes.
"""
- # SpglibDataset warning introduced in v2.4.1, can later remove when pymatgen updated to avoid this:
- warnings.filterwarnings("ignore", message="dict interface")
self.defects: dict[str, list[Defect]] = {} # {defect_type: [Defect, ...]}
self.defect_entries: dict[str, DefectEntry] = {} # {defect_species: DefectEntry}
self.structure = structure
diff --git a/doped/thermodynamics.py b/doped/thermodynamics.py
index a89d0349..54f328f9 100644
--- a/doped/thermodynamics.py
+++ b/doped/thermodynamics.py
@@ -266,7 +266,6 @@ def group_defects_by_distance(
# would be independent of this... but challenging to setup without complex clustering approaches (
# for now this works very well as is, and this is a rare case and usually not a problem anyway as
# dist_tol can just be adjusted as needed)
- warnings.filterwarnings("ignore", "dict interface") # ignore spglib warning from v2.4.1
# initial group by Defect.name (same nominal defect), then distance to equiv sites
# first make dictionary of nominal defect name: list of entries with that name
@@ -433,7 +432,7 @@ def __init__(
plots.
Args:
- defect_entries ([DefectEntry] or {str: DefectEntry}):
+ defect_entries (list[DefectEntry] or dict[str, DefectEntry]):
A list or dict of DefectEntry objects. Note that ``DefectEntry.name``
attributes are used for grouping and plotting purposes! These should
be in the format "{defect_name}_{optional_site_info}_{charge_state}".
diff --git a/doped/utils/symmetry.py b/doped/utils/symmetry.py
index a0c77e8c..8f2083d2 100644
--- a/doped/utils/symmetry.py
+++ b/doped/utils/symmetry.py
@@ -5,7 +5,7 @@
import contextlib
import os
import warnings
-from typing import Optional
+from typing import Optional, Union
import numpy as np
from pymatgen.analysis.defects.core import DefectType
@@ -26,8 +26,6 @@
_get_unrelaxed_defect_structure,
)
-warnings.filterwarnings("ignore", "dict interface") # ignore spglib warning from v2.4.1
-
def _set_spglib_warnings_env_var():
"""
@@ -50,13 +48,15 @@ def _check_spglib_version():
warnings.warn(
f"Your spglib Python version (spglib.__version__ = {python_version}) does not match its C "
f"library version (spglib.spg_get_version_full() = {c_version}). This can lead to unnecessary "
- f"spglib warning messages, but can be avoided by:\n"
- f"- First uninstalling spglib with both `conda uninstall spglib` and `pip uninstall spglib` "
- f"(to ensure no duplicate installations).\n"
- f"- Then, install spglib with `conda install -c conda-forge spglib` or "
- f"`pip install git+https://github.com/spglib/spglib "
- f"--config-settings=cmake.define.SPGLIB_SHARED_LIBS=OFF` as detailed in the doped "
- f"installation instructions: https://doped.readthedocs.io/en/latest/Installation.html"
+ f"spglib warning messages, but can be avoided by upgrading spglib with `pip install --upgrade "
+ f"spglib`."
+ # No longer required as of spglib v2.5:
+ # f"- First uninstalling spglib with both `conda uninstall spglib` and `pip uninstall spglib` "
+ # f"(to ensure no duplicate installations).\n"
+ # f"- Then, install spglib with `conda install -c conda-forge spglib` or "
+ # f"`pip install git+https://github.com/spglib/spglib "
+ # f"--config-settings=cmake.define.SPGLIB_SHARED_LIBS=OFF` as detailed in the doped "
+ # f"installation instructions: https://doped.readthedocs.io/en/latest/Installation.html"
)
@@ -551,10 +551,15 @@ def get_clean_structure(structure: Structure, return_T: bool = False):
return new_structure
-def get_primitive_structure(sga, ignored_species: Optional[list] = None, clean: bool = True):
+def get_primitive_structure(
+ sga_or_struct: Union[SpacegroupAnalyzer, Structure],
+ ignored_species: Optional[list] = None,
+ clean: bool = True,
+ **kwargs,
+):
"""
Get a consistent/deterministic primitive structure from a
- SpacegroupAnalyzer object.
+ ``SpacegroupAnalyzer`` object, or ``pymatgen`` ``Structure``.
For some materials (e.g. zinc blende), there are multiple equivalent
primitive cells, so for reproducibility and in line with most structure
@@ -562,10 +567,28 @@ def get_primitive_structure(sga, ignored_species: Optional[list] = None, clean:
fractional coordinates of the sites (i.e. favour Cd (0,0,0) and Te
(0.25,0.25,0.25) over Cd (0,0,0) and Te (0.75,0.75,0.75) for F-43m CdTe).
- If ignored_species is set, then the sorting function used to determine the
+ If ``ignored_species`` is set, then the sorting function used to determine the
ideal primitive structure will ignore sites with species in
- ignored_species.
- """
+ ``ignored_species``.
+
+ Args:
+ sga_or_struct:
+ ``SpacegroupAnalyzer`` object or ``Structure`` object to get the
+ corresponding primitive structure of. If a ``Structure`` object,
+ then additional kwargs are passed to the ``_get_sga`` function
+ which obtains the ``SpacegroupAnalyzer`` object for this structure.
+ ignored_species:
+ List of species to ignore when determining the ideal primitive
+ structure. (Default: None)
+ clean:
+ Whether to return a 'clean' version of the primitive structure,
+ with the lattice matrix in a standardised form. (Default: True)
+ **kwargs:
+ Additional keyword arguments to pass to the ``_get_sga`` function
+ (e.g. ``symprec`` etc).
+ """
+ sga = _get_sga(sga_or_struct, **kwargs) if isinstance(sga_or_struct, Structure) else sga_or_struct
+
possible_prim_structs = []
for _i in range(4):
struct = sga.get_primitive_standard_structure()
@@ -1100,7 +1123,6 @@ def point_symmetry_from_defect(defect, symm_ops=None, symprec=0.01):
Returns:
str: Defect point symmetry.
"""
- warnings.filterwarnings("ignore", "dict interface") # ignore spglib warning from v2.4.1
symm_dataset, _unique_sites = _get_symm_dataset_of_struc_with_all_equiv_sites(
defect.site.frac_coords, defect.structure, symm_ops=symm_ops, symprec=symprec
)
@@ -1200,7 +1222,6 @@ def point_symmetry_from_defect_entry(
a boolean specifying if the supercell has been detected to break the crystal
periodicity).
"""
- warnings.filterwarnings("ignore", "dict interface") # ignore spglib warning from v2.4.1
if symprec is None:
symprec = 0.1 if relaxed else 0.01 # relaxed structures likely have structural noise
# May need to adjust symprec (e.g. for Ag2Se, symprec of 0.2 is too large as we have very
@@ -1506,7 +1527,6 @@ def point_symmetry(
a boolean specifying if the supercell has been detected to break the crystal
periodicity).
"""
- warnings.filterwarnings("ignore", "dict interface") # ignore spglib warning from v2.4.1
if symprec is None:
symprec = 0.1 if relaxed else 0.01 # relaxed structures likely have structural noise
diff --git a/examples/competing_phases/La_ZrO2/La2O3_EaH_0.0/relax/vasprun.xml.gz b/examples/CompetingPhases/La_ZrO2/La2O3_EaH_0.0/relax/vasprun.xml.gz
similarity index 100%
rename from examples/competing_phases/La_ZrO2/La2O3_EaH_0.0/relax/vasprun.xml.gz
rename to examples/CompetingPhases/La_ZrO2/La2O3_EaH_0.0/relax/vasprun.xml.gz
diff --git a/examples/competing_phases/La_ZrO2/La2Zr2O7_EaH_0.0/relax/vasprun.xml.gz b/examples/CompetingPhases/La_ZrO2/La2Zr2O7_EaH_0.0/relax/vasprun.xml.gz
similarity index 100%
rename from examples/competing_phases/La_ZrO2/La2Zr2O7_EaH_0.0/relax/vasprun.xml.gz
rename to examples/CompetingPhases/La_ZrO2/La2Zr2O7_EaH_0.0/relax/vasprun.xml.gz
diff --git a/examples/competing_phases/La_ZrO2/La_EaH_0.0/relax/vasprun.xml.gz b/examples/CompetingPhases/La_ZrO2/La_EaH_0.0/relax/vasprun.xml.gz
similarity index 100%
rename from examples/competing_phases/La_ZrO2/La_EaH_0.0/relax/vasprun.xml.gz
rename to examples/CompetingPhases/La_ZrO2/La_EaH_0.0/relax/vasprun.xml.gz
diff --git a/examples/competing_phases/La_ZrO2/O2_EaH_0.0/relax/vasprun.xml.gz b/examples/CompetingPhases/La_ZrO2/O2_EaH_0.0/relax/vasprun.xml.gz
similarity index 100%
rename from examples/competing_phases/La_ZrO2/O2_EaH_0.0/relax/vasprun.xml.gz
rename to examples/CompetingPhases/La_ZrO2/O2_EaH_0.0/relax/vasprun.xml.gz
diff --git a/examples/competing_phases/La_ZrO2/Zr2O_EaH_0.008/relax/vasprun.xml.gz b/examples/CompetingPhases/La_ZrO2/Zr2O_EaH_0.008/relax/vasprun.xml.gz
similarity index 100%
rename from examples/competing_phases/La_ZrO2/Zr2O_EaH_0.008/relax/vasprun.xml.gz
rename to examples/CompetingPhases/La_ZrO2/Zr2O_EaH_0.008/relax/vasprun.xml.gz
diff --git a/examples/competing_phases/La_ZrO2/Zr3O_EaH_0.0/relax/vasprun.xml.gz b/examples/CompetingPhases/La_ZrO2/Zr3O_EaH_0.0/relax/vasprun.xml.gz
similarity index 100%
rename from examples/competing_phases/La_ZrO2/Zr3O_EaH_0.0/relax/vasprun.xml.gz
rename to examples/CompetingPhases/La_ZrO2/Zr3O_EaH_0.0/relax/vasprun.xml.gz
diff --git a/examples/competing_phases/La_ZrO2/Zr3O_EaH_0.004/relax/vasprun.xml.gz b/examples/CompetingPhases/La_ZrO2/Zr3O_EaH_0.004/relax/vasprun.xml.gz
similarity index 100%
rename from examples/competing_phases/La_ZrO2/Zr3O_EaH_0.004/relax/vasprun.xml.gz
rename to examples/CompetingPhases/La_ZrO2/Zr3O_EaH_0.004/relax/vasprun.xml.gz
diff --git a/examples/competing_phases/La_ZrO2/ZrO2_EaH_0.0/relax/vasprun.xml.gz b/examples/CompetingPhases/La_ZrO2/ZrO2_EaH_0.0/relax/vasprun.xml.gz
similarity index 100%
rename from examples/competing_phases/La_ZrO2/ZrO2_EaH_0.0/relax/vasprun.xml.gz
rename to examples/CompetingPhases/La_ZrO2/ZrO2_EaH_0.0/relax/vasprun.xml.gz
diff --git a/examples/competing_phases/La_ZrO2/ZrO2_EaH_0.009/relax/vasprun.xml.gz b/examples/CompetingPhases/La_ZrO2/ZrO2_EaH_0.009/relax/vasprun.xml.gz
similarity index 100%
rename from examples/competing_phases/La_ZrO2/ZrO2_EaH_0.009/relax/vasprun.xml.gz
rename to examples/CompetingPhases/La_ZrO2/ZrO2_EaH_0.009/relax/vasprun.xml.gz
diff --git a/examples/competing_phases/La_ZrO2/Zr_EaH_0.0/relax/vasprun.xml.gz b/examples/CompetingPhases/La_ZrO2/Zr_EaH_0.0/relax/vasprun.xml.gz
similarity index 100%
rename from examples/competing_phases/La_ZrO2/Zr_EaH_0.0/relax/vasprun.xml.gz
rename to examples/CompetingPhases/La_ZrO2/Zr_EaH_0.0/relax/vasprun.xml.gz
diff --git a/examples/competing_phases/La_ZrO2/Zr_EaH_0.016/relax/vasprun.xml.gz b/examples/CompetingPhases/La_ZrO2/Zr_EaH_0.016/relax/vasprun.xml.gz
similarity index 100%
rename from examples/competing_phases/La_ZrO2/Zr_EaH_0.016/relax/vasprun.xml.gz
rename to examples/CompetingPhases/La_ZrO2/Zr_EaH_0.016/relax/vasprun.xml.gz
diff --git a/examples/competing_phases/MgO/MgO_EaH_0.0/kpoint_converge/k4,4,4/INCAR b/examples/CompetingPhases/MgO/MgO_EaH_0.0/kpoint_converge/k4,4,4/INCAR
similarity index 100%
rename from examples/competing_phases/MgO/MgO_EaH_0.0/kpoint_converge/k4,4,4/INCAR
rename to examples/CompetingPhases/MgO/MgO_EaH_0.0/kpoint_converge/k4,4,4/INCAR
diff --git a/examples/competing_phases/MgO/MgO_EaH_0.0/kpoint_converge/k4,4,4/KPOINTS b/examples/CompetingPhases/MgO/MgO_EaH_0.0/kpoint_converge/k4,4,4/KPOINTS
similarity index 100%
rename from examples/competing_phases/MgO/MgO_EaH_0.0/kpoint_converge/k4,4,4/KPOINTS
rename to examples/CompetingPhases/MgO/MgO_EaH_0.0/kpoint_converge/k4,4,4/KPOINTS
diff --git a/examples/competing_phases/MgO/MgO_EaH_0.0/kpoint_converge/k4,4,4/POSCAR b/examples/CompetingPhases/MgO/MgO_EaH_0.0/kpoint_converge/k4,4,4/POSCAR
similarity index 100%
rename from examples/competing_phases/MgO/MgO_EaH_0.0/kpoint_converge/k4,4,4/POSCAR
rename to examples/CompetingPhases/MgO/MgO_EaH_0.0/kpoint_converge/k4,4,4/POSCAR
diff --git a/examples/competing_phases/MgO/MgO_EaH_0.0/kpoint_converge/k5,5,5/INCAR b/examples/CompetingPhases/MgO/MgO_EaH_0.0/kpoint_converge/k5,5,5/INCAR
similarity index 100%
rename from examples/competing_phases/MgO/MgO_EaH_0.0/kpoint_converge/k5,5,5/INCAR
rename to examples/CompetingPhases/MgO/MgO_EaH_0.0/kpoint_converge/k5,5,5/INCAR
diff --git a/examples/competing_phases/MgO/MgO_EaH_0.0/kpoint_converge/k5,5,5/KPOINTS b/examples/CompetingPhases/MgO/MgO_EaH_0.0/kpoint_converge/k5,5,5/KPOINTS
similarity index 100%
rename from examples/competing_phases/MgO/MgO_EaH_0.0/kpoint_converge/k5,5,5/KPOINTS
rename to examples/CompetingPhases/MgO/MgO_EaH_0.0/kpoint_converge/k5,5,5/KPOINTS
diff --git a/examples/competing_phases/MgO/MgO_EaH_0.0/kpoint_converge/k5,5,5/POSCAR b/examples/CompetingPhases/MgO/MgO_EaH_0.0/kpoint_converge/k5,5,5/POSCAR
similarity index 100%
rename from examples/competing_phases/MgO/MgO_EaH_0.0/kpoint_converge/k5,5,5/POSCAR
rename to examples/CompetingPhases/MgO/MgO_EaH_0.0/kpoint_converge/k5,5,5/POSCAR
diff --git a/examples/competing_phases/MgO/MgO_EaH_0.0/kpoint_converge/k6,6,6/INCAR b/examples/CompetingPhases/MgO/MgO_EaH_0.0/kpoint_converge/k6,6,6/INCAR
similarity index 100%
rename from examples/competing_phases/MgO/MgO_EaH_0.0/kpoint_converge/k6,6,6/INCAR
rename to examples/CompetingPhases/MgO/MgO_EaH_0.0/kpoint_converge/k6,6,6/INCAR
diff --git a/examples/competing_phases/MgO/MgO_EaH_0.0/kpoint_converge/k6,6,6/KPOINTS b/examples/CompetingPhases/MgO/MgO_EaH_0.0/kpoint_converge/k6,6,6/KPOINTS
similarity index 100%
rename from examples/competing_phases/MgO/MgO_EaH_0.0/kpoint_converge/k6,6,6/KPOINTS
rename to examples/CompetingPhases/MgO/MgO_EaH_0.0/kpoint_converge/k6,6,6/KPOINTS
diff --git a/examples/competing_phases/MgO/MgO_EaH_0.0/kpoint_converge/k6,6,6/POSCAR b/examples/CompetingPhases/MgO/MgO_EaH_0.0/kpoint_converge/k6,6,6/POSCAR
similarity index 100%
rename from examples/competing_phases/MgO/MgO_EaH_0.0/kpoint_converge/k6,6,6/POSCAR
rename to examples/CompetingPhases/MgO/MgO_EaH_0.0/kpoint_converge/k6,6,6/POSCAR
diff --git a/examples/competing_phases/MgO/MgO_EaH_0.0/kpoint_converge/k7,7,7/INCAR b/examples/CompetingPhases/MgO/MgO_EaH_0.0/kpoint_converge/k7,7,7/INCAR
similarity index 100%
rename from examples/competing_phases/MgO/MgO_EaH_0.0/kpoint_converge/k7,7,7/INCAR
rename to examples/CompetingPhases/MgO/MgO_EaH_0.0/kpoint_converge/k7,7,7/INCAR
diff --git a/examples/competing_phases/MgO/MgO_EaH_0.0/kpoint_converge/k7,7,7/KPOINTS b/examples/CompetingPhases/MgO/MgO_EaH_0.0/kpoint_converge/k7,7,7/KPOINTS
similarity index 100%
rename from examples/competing_phases/MgO/MgO_EaH_0.0/kpoint_converge/k7,7,7/KPOINTS
rename to examples/CompetingPhases/MgO/MgO_EaH_0.0/kpoint_converge/k7,7,7/KPOINTS
diff --git a/examples/competing_phases/MgO/MgO_EaH_0.0/kpoint_converge/k7,7,7/POSCAR b/examples/CompetingPhases/MgO/MgO_EaH_0.0/kpoint_converge/k7,7,7/POSCAR
similarity index 100%
rename from examples/competing_phases/MgO/MgO_EaH_0.0/kpoint_converge/k7,7,7/POSCAR
rename to examples/CompetingPhases/MgO/MgO_EaH_0.0/kpoint_converge/k7,7,7/POSCAR
diff --git a/examples/competing_phases/MgO/MgO_EaH_0.0/kpoint_converge/k8,8,8/INCAR b/examples/CompetingPhases/MgO/MgO_EaH_0.0/kpoint_converge/k8,8,8/INCAR
similarity index 100%
rename from examples/competing_phases/MgO/MgO_EaH_0.0/kpoint_converge/k8,8,8/INCAR
rename to examples/CompetingPhases/MgO/MgO_EaH_0.0/kpoint_converge/k8,8,8/INCAR
diff --git a/examples/competing_phases/MgO/MgO_EaH_0.0/kpoint_converge/k8,8,8/KPOINTS b/examples/CompetingPhases/MgO/MgO_EaH_0.0/kpoint_converge/k8,8,8/KPOINTS
similarity index 100%
rename from examples/competing_phases/MgO/MgO_EaH_0.0/kpoint_converge/k8,8,8/KPOINTS
rename to examples/CompetingPhases/MgO/MgO_EaH_0.0/kpoint_converge/k8,8,8/KPOINTS
diff --git a/examples/competing_phases/MgO/MgO_EaH_0.0/kpoint_converge/k8,8,8/POSCAR b/examples/CompetingPhases/MgO/MgO_EaH_0.0/kpoint_converge/k8,8,8/POSCAR
similarity index 100%
rename from examples/competing_phases/MgO/MgO_EaH_0.0/kpoint_converge/k8,8,8/POSCAR
rename to examples/CompetingPhases/MgO/MgO_EaH_0.0/kpoint_converge/k8,8,8/POSCAR
diff --git a/examples/competing_phases/MgO/MgO_EaH_0.0/kpoint_converge/k9,9,9/INCAR b/examples/CompetingPhases/MgO/MgO_EaH_0.0/kpoint_converge/k9,9,9/INCAR
similarity index 100%
rename from examples/competing_phases/MgO/MgO_EaH_0.0/kpoint_converge/k9,9,9/INCAR
rename to examples/CompetingPhases/MgO/MgO_EaH_0.0/kpoint_converge/k9,9,9/INCAR
diff --git a/examples/competing_phases/MgO/MgO_EaH_0.0/kpoint_converge/k9,9,9/KPOINTS b/examples/CompetingPhases/MgO/MgO_EaH_0.0/kpoint_converge/k9,9,9/KPOINTS
similarity index 100%
rename from examples/competing_phases/MgO/MgO_EaH_0.0/kpoint_converge/k9,9,9/KPOINTS
rename to examples/CompetingPhases/MgO/MgO_EaH_0.0/kpoint_converge/k9,9,9/KPOINTS
diff --git a/examples/competing_phases/MgO/MgO_EaH_0.0/kpoint_converge/k9,9,9/POSCAR b/examples/CompetingPhases/MgO/MgO_EaH_0.0/kpoint_converge/k9,9,9/POSCAR
similarity index 100%
rename from examples/competing_phases/MgO/MgO_EaH_0.0/kpoint_converge/k9,9,9/POSCAR
rename to examples/CompetingPhases/MgO/MgO_EaH_0.0/kpoint_converge/k9,9,9/POSCAR
diff --git a/examples/competing_phases/MgO/MgO_EaH_0.0/vasp_std/INCAR b/examples/CompetingPhases/MgO/MgO_EaH_0.0/vasp_std/INCAR
similarity index 100%
rename from examples/competing_phases/MgO/MgO_EaH_0.0/vasp_std/INCAR
rename to examples/CompetingPhases/MgO/MgO_EaH_0.0/vasp_std/INCAR
diff --git a/examples/competing_phases/MgO/MgO_EaH_0.0/vasp_std/KPOINTS b/examples/CompetingPhases/MgO/MgO_EaH_0.0/vasp_std/KPOINTS
similarity index 100%
rename from examples/competing_phases/MgO/MgO_EaH_0.0/vasp_std/KPOINTS
rename to examples/CompetingPhases/MgO/MgO_EaH_0.0/vasp_std/KPOINTS
diff --git a/examples/competing_phases/MgO/MgO_EaH_0.0/vasp_std/POSCAR b/examples/CompetingPhases/MgO/MgO_EaH_0.0/vasp_std/POSCAR
similarity index 100%
rename from examples/competing_phases/MgO/MgO_EaH_0.0/vasp_std/POSCAR
rename to examples/CompetingPhases/MgO/MgO_EaH_0.0/vasp_std/POSCAR
diff --git a/examples/competing_phases/MgO/MgO_EaH_0.0/vasp_std/vasprun.xml.gz b/examples/CompetingPhases/MgO/MgO_EaH_0.0/vasp_std/vasprun.xml.gz
similarity index 100%
rename from examples/competing_phases/MgO/MgO_EaH_0.0/vasp_std/vasprun.xml.gz
rename to examples/CompetingPhases/MgO/MgO_EaH_0.0/vasp_std/vasprun.xml.gz
diff --git a/examples/competing_phases/MgO/MgO_EaH_0.0/vasp_std/vasprun.xml_09_04_59on25_01_24.gz b/examples/CompetingPhases/MgO/MgO_EaH_0.0/vasp_std/vasprun.xml_09_04_59on25_01_24.gz
similarity index 100%
rename from examples/competing_phases/MgO/MgO_EaH_0.0/vasp_std/vasprun.xml_09_04_59on25_01_24.gz
rename to examples/CompetingPhases/MgO/MgO_EaH_0.0/vasp_std/vasprun.xml_09_04_59on25_01_24.gz
diff --git a/examples/competing_phases/MgO/Mg_EaH_0.0/kpoint_converge/k5,5,5/INCAR b/examples/CompetingPhases/MgO/Mg_EaH_0.0/kpoint_converge/k5,5,5/INCAR
similarity index 100%
rename from examples/competing_phases/MgO/Mg_EaH_0.0/kpoint_converge/k5,5,5/INCAR
rename to examples/CompetingPhases/MgO/Mg_EaH_0.0/kpoint_converge/k5,5,5/INCAR
diff --git a/examples/competing_phases/MgO/Mg_EaH_0.0/kpoint_converge/k5,5,5/KPOINTS b/examples/CompetingPhases/MgO/Mg_EaH_0.0/kpoint_converge/k5,5,5/KPOINTS
similarity index 100%
rename from examples/competing_phases/MgO/Mg_EaH_0.0/kpoint_converge/k5,5,5/KPOINTS
rename to examples/CompetingPhases/MgO/Mg_EaH_0.0/kpoint_converge/k5,5,5/KPOINTS
diff --git a/examples/competing_phases/MgO/Mg_EaH_0.0/kpoint_converge/k5,5,5/POSCAR b/examples/CompetingPhases/MgO/Mg_EaH_0.0/kpoint_converge/k5,5,5/POSCAR
similarity index 100%
rename from examples/competing_phases/MgO/Mg_EaH_0.0/kpoint_converge/k5,5,5/POSCAR
rename to examples/CompetingPhases/MgO/Mg_EaH_0.0/kpoint_converge/k5,5,5/POSCAR
diff --git a/examples/competing_phases/MgO/Mg_EaH_0.0/kpoint_converge/k6,6,6/INCAR b/examples/CompetingPhases/MgO/Mg_EaH_0.0/kpoint_converge/k6,6,6/INCAR
similarity index 100%
rename from examples/competing_phases/MgO/Mg_EaH_0.0/kpoint_converge/k6,6,6/INCAR
rename to examples/CompetingPhases/MgO/Mg_EaH_0.0/kpoint_converge/k6,6,6/INCAR
diff --git a/examples/competing_phases/MgO/Mg_EaH_0.0/kpoint_converge/k6,6,6/KPOINTS b/examples/CompetingPhases/MgO/Mg_EaH_0.0/kpoint_converge/k6,6,6/KPOINTS
similarity index 100%
rename from examples/competing_phases/MgO/Mg_EaH_0.0/kpoint_converge/k6,6,6/KPOINTS
rename to examples/CompetingPhases/MgO/Mg_EaH_0.0/kpoint_converge/k6,6,6/KPOINTS
diff --git a/examples/competing_phases/MgO/Mg_EaH_0.0/kpoint_converge/k6,6,6/POSCAR b/examples/CompetingPhases/MgO/Mg_EaH_0.0/kpoint_converge/k6,6,6/POSCAR
similarity index 100%
rename from examples/competing_phases/MgO/Mg_EaH_0.0/kpoint_converge/k6,6,6/POSCAR
rename to examples/CompetingPhases/MgO/Mg_EaH_0.0/kpoint_converge/k6,6,6/POSCAR
diff --git a/examples/competing_phases/MgO/Mg_EaH_0.0/kpoint_converge/k7,7,7/INCAR b/examples/CompetingPhases/MgO/Mg_EaH_0.0/kpoint_converge/k7,7,7/INCAR
similarity index 100%
rename from examples/competing_phases/MgO/Mg_EaH_0.0/kpoint_converge/k7,7,7/INCAR
rename to examples/CompetingPhases/MgO/Mg_EaH_0.0/kpoint_converge/k7,7,7/INCAR
diff --git a/examples/competing_phases/MgO/Mg_EaH_0.0/kpoint_converge/k7,7,7/KPOINTS b/examples/CompetingPhases/MgO/Mg_EaH_0.0/kpoint_converge/k7,7,7/KPOINTS
similarity index 100%
rename from examples/competing_phases/MgO/Mg_EaH_0.0/kpoint_converge/k7,7,7/KPOINTS
rename to examples/CompetingPhases/MgO/Mg_EaH_0.0/kpoint_converge/k7,7,7/KPOINTS
diff --git a/examples/competing_phases/MgO/Mg_EaH_0.0/kpoint_converge/k7,7,7/POSCAR b/examples/CompetingPhases/MgO/Mg_EaH_0.0/kpoint_converge/k7,7,7/POSCAR
similarity index 100%
rename from examples/competing_phases/MgO/Mg_EaH_0.0/kpoint_converge/k7,7,7/POSCAR
rename to examples/CompetingPhases/MgO/Mg_EaH_0.0/kpoint_converge/k7,7,7/POSCAR
diff --git a/examples/competing_phases/MgO/Mg_EaH_0.0/vasp_std/INCAR b/examples/CompetingPhases/MgO/Mg_EaH_0.0/vasp_std/INCAR
similarity index 100%
rename from examples/competing_phases/MgO/Mg_EaH_0.0/vasp_std/INCAR
rename to examples/CompetingPhases/MgO/Mg_EaH_0.0/vasp_std/INCAR
diff --git a/examples/competing_phases/MgO/Mg_EaH_0.0/vasp_std/KPOINTS b/examples/CompetingPhases/MgO/Mg_EaH_0.0/vasp_std/KPOINTS
similarity index 100%
rename from examples/competing_phases/MgO/Mg_EaH_0.0/vasp_std/KPOINTS
rename to examples/CompetingPhases/MgO/Mg_EaH_0.0/vasp_std/KPOINTS
diff --git a/examples/competing_phases/MgO/Mg_EaH_0.0/vasp_std/POSCAR b/examples/CompetingPhases/MgO/Mg_EaH_0.0/vasp_std/POSCAR
similarity index 100%
rename from examples/competing_phases/MgO/Mg_EaH_0.0/vasp_std/POSCAR
rename to examples/CompetingPhases/MgO/Mg_EaH_0.0/vasp_std/POSCAR
diff --git a/examples/competing_phases/MgO/Mg_EaH_0.0/vasp_std/vasprun.xml.gz b/examples/CompetingPhases/MgO/Mg_EaH_0.0/vasp_std/vasprun.xml.gz
similarity index 100%
rename from examples/competing_phases/MgO/Mg_EaH_0.0/vasp_std/vasprun.xml.gz
rename to examples/CompetingPhases/MgO/Mg_EaH_0.0/vasp_std/vasprun.xml.gz
diff --git a/examples/competing_phases/MgO/Mg_EaH_0.0/vasp_std/vasprun.xml_09_03_30on25_01_24.gz b/examples/CompetingPhases/MgO/Mg_EaH_0.0/vasp_std/vasprun.xml_09_03_30on25_01_24.gz
similarity index 100%
rename from examples/competing_phases/MgO/Mg_EaH_0.0/vasp_std/vasprun.xml_09_03_30on25_01_24.gz
rename to examples/CompetingPhases/MgO/Mg_EaH_0.0/vasp_std/vasprun.xml_09_03_30on25_01_24.gz
diff --git a/examples/competing_phases/MgO/Mg_EaH_0.0061/kpoint_converge/k10,10,6/INCAR b/examples/CompetingPhases/MgO/Mg_EaH_0.0061/kpoint_converge/k10,10,6/INCAR
similarity index 100%
rename from examples/competing_phases/MgO/Mg_EaH_0.0061/kpoint_converge/k10,10,6/INCAR
rename to examples/CompetingPhases/MgO/Mg_EaH_0.0061/kpoint_converge/k10,10,6/INCAR
diff --git a/examples/competing_phases/MgO/Mg_EaH_0.0061/kpoint_converge/k10,10,6/KPOINTS b/examples/CompetingPhases/MgO/Mg_EaH_0.0061/kpoint_converge/k10,10,6/KPOINTS
similarity index 100%
rename from examples/competing_phases/MgO/Mg_EaH_0.0061/kpoint_converge/k10,10,6/KPOINTS
rename to examples/CompetingPhases/MgO/Mg_EaH_0.0061/kpoint_converge/k10,10,6/KPOINTS
diff --git a/examples/competing_phases/MgO/Mg_EaH_0.0061/kpoint_converge/k10,10,6/POSCAR b/examples/CompetingPhases/MgO/Mg_EaH_0.0061/kpoint_converge/k10,10,6/POSCAR
similarity index 100%
rename from examples/competing_phases/MgO/Mg_EaH_0.0061/kpoint_converge/k10,10,6/POSCAR
rename to examples/CompetingPhases/MgO/Mg_EaH_0.0061/kpoint_converge/k10,10,6/POSCAR
diff --git a/examples/competing_phases/MgO/Mg_EaH_0.0061/kpoint_converge/k7,7,4/INCAR b/examples/CompetingPhases/MgO/Mg_EaH_0.0061/kpoint_converge/k7,7,4/INCAR
similarity index 100%
rename from examples/competing_phases/MgO/Mg_EaH_0.0061/kpoint_converge/k7,7,4/INCAR
rename to examples/CompetingPhases/MgO/Mg_EaH_0.0061/kpoint_converge/k7,7,4/INCAR
diff --git a/examples/competing_phases/MgO/Mg_EaH_0.0061/kpoint_converge/k7,7,4/KPOINTS b/examples/CompetingPhases/MgO/Mg_EaH_0.0061/kpoint_converge/k7,7,4/KPOINTS
similarity index 100%
rename from examples/competing_phases/MgO/Mg_EaH_0.0061/kpoint_converge/k7,7,4/KPOINTS
rename to examples/CompetingPhases/MgO/Mg_EaH_0.0061/kpoint_converge/k7,7,4/KPOINTS
diff --git a/examples/competing_phases/MgO/Mg_EaH_0.0061/kpoint_converge/k7,7,4/POSCAR b/examples/CompetingPhases/MgO/Mg_EaH_0.0061/kpoint_converge/k7,7,4/POSCAR
similarity index 100%
rename from examples/competing_phases/MgO/Mg_EaH_0.0061/kpoint_converge/k7,7,4/POSCAR
rename to examples/CompetingPhases/MgO/Mg_EaH_0.0061/kpoint_converge/k7,7,4/POSCAR
diff --git a/examples/competing_phases/MgO/Mg_EaH_0.0061/kpoint_converge/k8,8,5/INCAR b/examples/CompetingPhases/MgO/Mg_EaH_0.0061/kpoint_converge/k8,8,5/INCAR
similarity index 100%
rename from examples/competing_phases/MgO/Mg_EaH_0.0061/kpoint_converge/k8,8,5/INCAR
rename to examples/CompetingPhases/MgO/Mg_EaH_0.0061/kpoint_converge/k8,8,5/INCAR
diff --git a/examples/competing_phases/MgO/Mg_EaH_0.0061/kpoint_converge/k8,8,5/KPOINTS b/examples/CompetingPhases/MgO/Mg_EaH_0.0061/kpoint_converge/k8,8,5/KPOINTS
similarity index 100%
rename from examples/competing_phases/MgO/Mg_EaH_0.0061/kpoint_converge/k8,8,5/KPOINTS
rename to examples/CompetingPhases/MgO/Mg_EaH_0.0061/kpoint_converge/k8,8,5/KPOINTS
diff --git a/examples/competing_phases/MgO/Mg_EaH_0.0061/kpoint_converge/k8,8,5/POSCAR b/examples/CompetingPhases/MgO/Mg_EaH_0.0061/kpoint_converge/k8,8,5/POSCAR
similarity index 100%
rename from examples/competing_phases/MgO/Mg_EaH_0.0061/kpoint_converge/k8,8,5/POSCAR
rename to examples/CompetingPhases/MgO/Mg_EaH_0.0061/kpoint_converge/k8,8,5/POSCAR
diff --git a/examples/competing_phases/MgO/Mg_EaH_0.0061/kpoint_converge/k9,9,5/INCAR b/examples/CompetingPhases/MgO/Mg_EaH_0.0061/kpoint_converge/k9,9,5/INCAR
similarity index 100%
rename from examples/competing_phases/MgO/Mg_EaH_0.0061/kpoint_converge/k9,9,5/INCAR
rename to examples/CompetingPhases/MgO/Mg_EaH_0.0061/kpoint_converge/k9,9,5/INCAR
diff --git a/examples/competing_phases/MgO/Mg_EaH_0.0061/kpoint_converge/k9,9,5/KPOINTS b/examples/CompetingPhases/MgO/Mg_EaH_0.0061/kpoint_converge/k9,9,5/KPOINTS
similarity index 100%
rename from examples/competing_phases/MgO/Mg_EaH_0.0061/kpoint_converge/k9,9,5/KPOINTS
rename to examples/CompetingPhases/MgO/Mg_EaH_0.0061/kpoint_converge/k9,9,5/KPOINTS
diff --git a/examples/competing_phases/MgO/Mg_EaH_0.0061/kpoint_converge/k9,9,5/POSCAR b/examples/CompetingPhases/MgO/Mg_EaH_0.0061/kpoint_converge/k9,9,5/POSCAR
similarity index 100%
rename from examples/competing_phases/MgO/Mg_EaH_0.0061/kpoint_converge/k9,9,5/POSCAR
rename to examples/CompetingPhases/MgO/Mg_EaH_0.0061/kpoint_converge/k9,9,5/POSCAR
diff --git a/examples/competing_phases/MgO/Mg_EaH_0.0061/kpoint_converge/k9,9,6/INCAR b/examples/CompetingPhases/MgO/Mg_EaH_0.0061/kpoint_converge/k9,9,6/INCAR
similarity index 100%
rename from examples/competing_phases/MgO/Mg_EaH_0.0061/kpoint_converge/k9,9,6/INCAR
rename to examples/CompetingPhases/MgO/Mg_EaH_0.0061/kpoint_converge/k9,9,6/INCAR
diff --git a/examples/competing_phases/MgO/Mg_EaH_0.0061/kpoint_converge/k9,9,6/KPOINTS b/examples/CompetingPhases/MgO/Mg_EaH_0.0061/kpoint_converge/k9,9,6/KPOINTS
similarity index 100%
rename from examples/competing_phases/MgO/Mg_EaH_0.0061/kpoint_converge/k9,9,6/KPOINTS
rename to examples/CompetingPhases/MgO/Mg_EaH_0.0061/kpoint_converge/k9,9,6/KPOINTS
diff --git a/examples/competing_phases/MgO/Mg_EaH_0.0061/kpoint_converge/k9,9,6/POSCAR b/examples/CompetingPhases/MgO/Mg_EaH_0.0061/kpoint_converge/k9,9,6/POSCAR
similarity index 100%
rename from examples/competing_phases/MgO/Mg_EaH_0.0061/kpoint_converge/k9,9,6/POSCAR
rename to examples/CompetingPhases/MgO/Mg_EaH_0.0061/kpoint_converge/k9,9,6/POSCAR
diff --git a/examples/competing_phases/MgO/Mg_EaH_0.0061/vasp_std/INCAR b/examples/CompetingPhases/MgO/Mg_EaH_0.0061/vasp_std/INCAR
similarity index 100%
rename from examples/competing_phases/MgO/Mg_EaH_0.0061/vasp_std/INCAR
rename to examples/CompetingPhases/MgO/Mg_EaH_0.0061/vasp_std/INCAR
diff --git a/examples/competing_phases/MgO/Mg_EaH_0.0061/vasp_std/KPOINTS b/examples/CompetingPhases/MgO/Mg_EaH_0.0061/vasp_std/KPOINTS
similarity index 100%
rename from examples/competing_phases/MgO/Mg_EaH_0.0061/vasp_std/KPOINTS
rename to examples/CompetingPhases/MgO/Mg_EaH_0.0061/vasp_std/KPOINTS
diff --git a/examples/competing_phases/MgO/Mg_EaH_0.0061/vasp_std/POSCAR b/examples/CompetingPhases/MgO/Mg_EaH_0.0061/vasp_std/POSCAR
similarity index 100%
rename from examples/competing_phases/MgO/Mg_EaH_0.0061/vasp_std/POSCAR
rename to examples/CompetingPhases/MgO/Mg_EaH_0.0061/vasp_std/POSCAR
diff --git a/examples/competing_phases/MgO/Mg_EaH_0.0061/vasp_std/vasprun.xml.gz b/examples/CompetingPhases/MgO/Mg_EaH_0.0061/vasp_std/vasprun.xml.gz
similarity index 100%
rename from examples/competing_phases/MgO/Mg_EaH_0.0061/vasp_std/vasprun.xml.gz
rename to examples/CompetingPhases/MgO/Mg_EaH_0.0061/vasp_std/vasprun.xml.gz
diff --git a/examples/competing_phases/MgO/Mg_EaH_0.0061/vasp_std/vasprun.xml_09_04_01on25_01_24.gz b/examples/CompetingPhases/MgO/Mg_EaH_0.0061/vasp_std/vasprun.xml_09_04_01on25_01_24.gz
similarity index 100%
rename from examples/competing_phases/MgO/Mg_EaH_0.0061/vasp_std/vasprun.xml_09_04_01on25_01_24.gz
rename to examples/CompetingPhases/MgO/Mg_EaH_0.0061/vasp_std/vasprun.xml_09_04_01on25_01_24.gz
diff --git a/examples/competing_phases/MgO/Mg_EaH_0.0099/kpoint_converge/k10,10,3/INCAR b/examples/CompetingPhases/MgO/Mg_EaH_0.0099/kpoint_converge/k10,10,3/INCAR
similarity index 100%
rename from examples/competing_phases/MgO/Mg_EaH_0.0099/kpoint_converge/k10,10,3/INCAR
rename to examples/CompetingPhases/MgO/Mg_EaH_0.0099/kpoint_converge/k10,10,3/INCAR
diff --git a/examples/competing_phases/MgO/Mg_EaH_0.0099/kpoint_converge/k10,10,3/KPOINTS b/examples/CompetingPhases/MgO/Mg_EaH_0.0099/kpoint_converge/k10,10,3/KPOINTS
similarity index 100%
rename from examples/competing_phases/MgO/Mg_EaH_0.0099/kpoint_converge/k10,10,3/KPOINTS
rename to examples/CompetingPhases/MgO/Mg_EaH_0.0099/kpoint_converge/k10,10,3/KPOINTS
diff --git a/examples/competing_phases/MgO/Mg_EaH_0.0099/kpoint_converge/k10,10,3/POSCAR b/examples/CompetingPhases/MgO/Mg_EaH_0.0099/kpoint_converge/k10,10,3/POSCAR
similarity index 100%
rename from examples/competing_phases/MgO/Mg_EaH_0.0099/kpoint_converge/k10,10,3/POSCAR
rename to examples/CompetingPhases/MgO/Mg_EaH_0.0099/kpoint_converge/k10,10,3/POSCAR
diff --git a/examples/competing_phases/MgO/Mg_EaH_0.0099/kpoint_converge/k7,7,2/INCAR b/examples/CompetingPhases/MgO/Mg_EaH_0.0099/kpoint_converge/k7,7,2/INCAR
similarity index 100%
rename from examples/competing_phases/MgO/Mg_EaH_0.0099/kpoint_converge/k7,7,2/INCAR
rename to examples/CompetingPhases/MgO/Mg_EaH_0.0099/kpoint_converge/k7,7,2/INCAR
diff --git a/examples/competing_phases/MgO/Mg_EaH_0.0099/kpoint_converge/k7,7,2/KPOINTS b/examples/CompetingPhases/MgO/Mg_EaH_0.0099/kpoint_converge/k7,7,2/KPOINTS
similarity index 100%
rename from examples/competing_phases/MgO/Mg_EaH_0.0099/kpoint_converge/k7,7,2/KPOINTS
rename to examples/CompetingPhases/MgO/Mg_EaH_0.0099/kpoint_converge/k7,7,2/KPOINTS
diff --git a/examples/competing_phases/MgO/Mg_EaH_0.0099/kpoint_converge/k7,7,2/POSCAR b/examples/CompetingPhases/MgO/Mg_EaH_0.0099/kpoint_converge/k7,7,2/POSCAR
similarity index 100%
rename from examples/competing_phases/MgO/Mg_EaH_0.0099/kpoint_converge/k7,7,2/POSCAR
rename to examples/CompetingPhases/MgO/Mg_EaH_0.0099/kpoint_converge/k7,7,2/POSCAR
diff --git a/examples/competing_phases/MgO/Mg_EaH_0.0099/kpoint_converge/k8,8,2/INCAR b/examples/CompetingPhases/MgO/Mg_EaH_0.0099/kpoint_converge/k8,8,2/INCAR
similarity index 100%
rename from examples/competing_phases/MgO/Mg_EaH_0.0099/kpoint_converge/k8,8,2/INCAR
rename to examples/CompetingPhases/MgO/Mg_EaH_0.0099/kpoint_converge/k8,8,2/INCAR
diff --git a/examples/competing_phases/MgO/Mg_EaH_0.0099/kpoint_converge/k8,8,2/KPOINTS b/examples/CompetingPhases/MgO/Mg_EaH_0.0099/kpoint_converge/k8,8,2/KPOINTS
similarity index 100%
rename from examples/competing_phases/MgO/Mg_EaH_0.0099/kpoint_converge/k8,8,2/KPOINTS
rename to examples/CompetingPhases/MgO/Mg_EaH_0.0099/kpoint_converge/k8,8,2/KPOINTS
diff --git a/examples/competing_phases/MgO/Mg_EaH_0.0099/kpoint_converge/k8,8,2/POSCAR b/examples/CompetingPhases/MgO/Mg_EaH_0.0099/kpoint_converge/k8,8,2/POSCAR
similarity index 100%
rename from examples/competing_phases/MgO/Mg_EaH_0.0099/kpoint_converge/k8,8,2/POSCAR
rename to examples/CompetingPhases/MgO/Mg_EaH_0.0099/kpoint_converge/k8,8,2/POSCAR
diff --git a/examples/competing_phases/MgO/Mg_EaH_0.0099/kpoint_converge/k9,9,2/INCAR b/examples/CompetingPhases/MgO/Mg_EaH_0.0099/kpoint_converge/k9,9,2/INCAR
similarity index 100%
rename from examples/competing_phases/MgO/Mg_EaH_0.0099/kpoint_converge/k9,9,2/INCAR
rename to examples/CompetingPhases/MgO/Mg_EaH_0.0099/kpoint_converge/k9,9,2/INCAR
diff --git a/examples/competing_phases/MgO/Mg_EaH_0.0099/kpoint_converge/k9,9,2/KPOINTS b/examples/CompetingPhases/MgO/Mg_EaH_0.0099/kpoint_converge/k9,9,2/KPOINTS
similarity index 100%
rename from examples/competing_phases/MgO/Mg_EaH_0.0099/kpoint_converge/k9,9,2/KPOINTS
rename to examples/CompetingPhases/MgO/Mg_EaH_0.0099/kpoint_converge/k9,9,2/KPOINTS
diff --git a/examples/competing_phases/MgO/Mg_EaH_0.0099/kpoint_converge/k9,9,2/POSCAR b/examples/CompetingPhases/MgO/Mg_EaH_0.0099/kpoint_converge/k9,9,2/POSCAR
similarity index 100%
rename from examples/competing_phases/MgO/Mg_EaH_0.0099/kpoint_converge/k9,9,2/POSCAR
rename to examples/CompetingPhases/MgO/Mg_EaH_0.0099/kpoint_converge/k9,9,2/POSCAR
diff --git a/examples/competing_phases/MgO/Mg_EaH_0.0099/kpoint_converge/k9,9,3/INCAR b/examples/CompetingPhases/MgO/Mg_EaH_0.0099/kpoint_converge/k9,9,3/INCAR
similarity index 100%
rename from examples/competing_phases/MgO/Mg_EaH_0.0099/kpoint_converge/k9,9,3/INCAR
rename to examples/CompetingPhases/MgO/Mg_EaH_0.0099/kpoint_converge/k9,9,3/INCAR
diff --git a/examples/competing_phases/MgO/Mg_EaH_0.0099/kpoint_converge/k9,9,3/KPOINTS b/examples/CompetingPhases/MgO/Mg_EaH_0.0099/kpoint_converge/k9,9,3/KPOINTS
similarity index 100%
rename from examples/competing_phases/MgO/Mg_EaH_0.0099/kpoint_converge/k9,9,3/KPOINTS
rename to examples/CompetingPhases/MgO/Mg_EaH_0.0099/kpoint_converge/k9,9,3/KPOINTS
diff --git a/examples/competing_phases/MgO/Mg_EaH_0.0099/kpoint_converge/k9,9,3/POSCAR b/examples/CompetingPhases/MgO/Mg_EaH_0.0099/kpoint_converge/k9,9,3/POSCAR
similarity index 100%
rename from examples/competing_phases/MgO/Mg_EaH_0.0099/kpoint_converge/k9,9,3/POSCAR
rename to examples/CompetingPhases/MgO/Mg_EaH_0.0099/kpoint_converge/k9,9,3/POSCAR
diff --git a/examples/competing_phases/MgO/Mg_EaH_0.0099/vasp_std/INCAR b/examples/CompetingPhases/MgO/Mg_EaH_0.0099/vasp_std/INCAR
similarity index 100%
rename from examples/competing_phases/MgO/Mg_EaH_0.0099/vasp_std/INCAR
rename to examples/CompetingPhases/MgO/Mg_EaH_0.0099/vasp_std/INCAR
diff --git a/examples/competing_phases/MgO/Mg_EaH_0.0099/vasp_std/KPOINTS b/examples/CompetingPhases/MgO/Mg_EaH_0.0099/vasp_std/KPOINTS
similarity index 100%
rename from examples/competing_phases/MgO/Mg_EaH_0.0099/vasp_std/KPOINTS
rename to examples/CompetingPhases/MgO/Mg_EaH_0.0099/vasp_std/KPOINTS
diff --git a/examples/competing_phases/MgO/Mg_EaH_0.0099/vasp_std/POSCAR b/examples/CompetingPhases/MgO/Mg_EaH_0.0099/vasp_std/POSCAR
similarity index 100%
rename from examples/competing_phases/MgO/Mg_EaH_0.0099/vasp_std/POSCAR
rename to examples/CompetingPhases/MgO/Mg_EaH_0.0099/vasp_std/POSCAR
diff --git a/examples/competing_phases/MgO/Mg_EaH_0.0099/vasp_std/vasprun.xml.gz b/examples/CompetingPhases/MgO/Mg_EaH_0.0099/vasp_std/vasprun.xml.gz
similarity index 100%
rename from examples/competing_phases/MgO/Mg_EaH_0.0099/vasp_std/vasprun.xml.gz
rename to examples/CompetingPhases/MgO/Mg_EaH_0.0099/vasp_std/vasprun.xml.gz
diff --git a/examples/competing_phases/MgO/O2_EaH_0.0/vasp_std/INCAR b/examples/CompetingPhases/MgO/O2_EaH_0.0/vasp_std/INCAR
similarity index 100%
rename from examples/competing_phases/MgO/O2_EaH_0.0/vasp_std/INCAR
rename to examples/CompetingPhases/MgO/O2_EaH_0.0/vasp_std/INCAR
diff --git a/examples/competing_phases/MgO/O2_EaH_0.0/vasp_std/KPOINTS b/examples/CompetingPhases/MgO/O2_EaH_0.0/vasp_std/KPOINTS
similarity index 100%
rename from examples/competing_phases/MgO/O2_EaH_0.0/vasp_std/KPOINTS
rename to examples/CompetingPhases/MgO/O2_EaH_0.0/vasp_std/KPOINTS
diff --git a/examples/competing_phases/MgO/O2_EaH_0.0/vasp_std/POSCAR b/examples/CompetingPhases/MgO/O2_EaH_0.0/vasp_std/POSCAR
similarity index 100%
rename from examples/competing_phases/MgO/O2_EaH_0.0/vasp_std/POSCAR
rename to examples/CompetingPhases/MgO/O2_EaH_0.0/vasp_std/POSCAR
diff --git a/examples/competing_phases/MgO/O2_EaH_0.0/vasp_std/vasprun.xml.gz b/examples/CompetingPhases/MgO/O2_EaH_0.0/vasp_std/vasprun.xml.gz
similarity index 100%
rename from examples/competing_phases/MgO/O2_EaH_0.0/vasp_std/vasprun.xml.gz
rename to examples/CompetingPhases/MgO/O2_EaH_0.0/vasp_std/vasprun.xml.gz
diff --git a/examples/competing_phases/MgO/O2_EaH_0.0/vasp_std/vasprun.xml_09_37_34on25_01_24.gz b/examples/CompetingPhases/MgO/O2_EaH_0.0/vasp_std/vasprun.xml_09_37_34on25_01_24.gz
similarity index 100%
rename from examples/competing_phases/MgO/O2_EaH_0.0/vasp_std/vasprun.xml_09_37_34on25_01_24.gz
rename to examples/CompetingPhases/MgO/O2_EaH_0.0/vasp_std/vasprun.xml_09_37_34on25_01_24.gz
diff --git a/examples/competing_phases/ZrO2/O2_EaH_0.0/relax/vasprun.xml.gz b/examples/CompetingPhases/ZrO2/O2_EaH_0.0/relax/vasprun.xml.gz
similarity index 100%
rename from examples/competing_phases/ZrO2/O2_EaH_0.0/relax/vasprun.xml.gz
rename to examples/CompetingPhases/ZrO2/O2_EaH_0.0/relax/vasprun.xml.gz
diff --git a/examples/competing_phases/ZrO2/Zr2O_EaH_0.008/relax/vasprun.xml.gz b/examples/CompetingPhases/ZrO2/Zr2O_EaH_0.008/relax/vasprun.xml.gz
similarity index 100%
rename from examples/competing_phases/ZrO2/Zr2O_EaH_0.008/relax/vasprun.xml.gz
rename to examples/CompetingPhases/ZrO2/Zr2O_EaH_0.008/relax/vasprun.xml.gz
diff --git a/examples/competing_phases/ZrO2/Zr3O_EaH_0.0/relax/vasprun.xml.gz b/examples/CompetingPhases/ZrO2/Zr3O_EaH_0.0/relax/vasprun.xml.gz
similarity index 100%
rename from examples/competing_phases/ZrO2/Zr3O_EaH_0.0/relax/vasprun.xml.gz
rename to examples/CompetingPhases/ZrO2/Zr3O_EaH_0.0/relax/vasprun.xml.gz
diff --git a/examples/competing_phases/ZrO2/Zr3O_EaH_0.004/relax/vasprun.xml.gz b/examples/CompetingPhases/ZrO2/Zr3O_EaH_0.004/relax/vasprun.xml.gz
similarity index 100%
rename from examples/competing_phases/ZrO2/Zr3O_EaH_0.004/relax/vasprun.xml.gz
rename to examples/CompetingPhases/ZrO2/Zr3O_EaH_0.004/relax/vasprun.xml.gz
diff --git a/examples/competing_phases/ZrO2/ZrO2_EaH_0.0/relax/vasprun.xml.gz b/examples/CompetingPhases/ZrO2/ZrO2_EaH_0.0/relax/vasprun.xml.gz
similarity index 100%
rename from examples/competing_phases/ZrO2/ZrO2_EaH_0.0/relax/vasprun.xml.gz
rename to examples/CompetingPhases/ZrO2/ZrO2_EaH_0.0/relax/vasprun.xml.gz
diff --git a/examples/competing_phases/ZrO2/ZrO2_EaH_0.009/relax/vasprun.xml.gz b/examples/CompetingPhases/ZrO2/ZrO2_EaH_0.009/relax/vasprun.xml.gz
similarity index 100%
rename from examples/competing_phases/ZrO2/ZrO2_EaH_0.009/relax/vasprun.xml.gz
rename to examples/CompetingPhases/ZrO2/ZrO2_EaH_0.009/relax/vasprun.xml.gz
diff --git a/examples/competing_phases/ZrO2/Zr_EaH_0.0/relax/vasprun.xml.gz b/examples/CompetingPhases/ZrO2/Zr_EaH_0.0/relax/vasprun.xml.gz
similarity index 100%
rename from examples/competing_phases/ZrO2/Zr_EaH_0.0/relax/vasprun.xml.gz
rename to examples/CompetingPhases/ZrO2/Zr_EaH_0.0/relax/vasprun.xml.gz
diff --git a/examples/competing_phases/ZrO2/Zr_EaH_0.016/relax/vasprun.xml.gz b/examples/CompetingPhases/ZrO2/Zr_EaH_0.016/relax/vasprun.xml.gz
similarity index 100%
rename from examples/competing_phases/ZrO2/Zr_EaH_0.016/relax/vasprun.xml.gz
rename to examples/CompetingPhases/ZrO2/Zr_EaH_0.016/relax/vasprun.xml.gz
diff --git a/examples/competing_phases/mgo_chempots.csv b/examples/CompetingPhases/mgo_chempots.csv
similarity index 100%
rename from examples/competing_phases/mgo_chempots.csv
rename to examples/CompetingPhases/mgo_chempots.csv
diff --git a/examples/competing_phases/mgo_chempots.json b/examples/CompetingPhases/mgo_chempots.json
similarity index 100%
rename from examples/competing_phases/mgo_chempots.json
rename to examples/CompetingPhases/mgo_chempots.json
diff --git a/examples/competing_phases/mgo_competing_phase_energies.csv b/examples/CompetingPhases/mgo_competing_phase_energies.csv
similarity index 100%
rename from examples/competing_phases/mgo_competing_phase_energies.csv
rename to examples/CompetingPhases/mgo_competing_phase_energies.csv
diff --git a/examples/competing_phases/ytos_phase_diagram.json b/examples/CompetingPhases/ytos_phase_diagram.json
similarity index 100%
rename from examples/competing_phases/ytos_phase_diagram.json
rename to examples/CompetingPhases/ytos_phase_diagram.json
diff --git a/examples/competing_phases/zro2_chempots.csv b/examples/CompetingPhases/zro2_chempots.csv
similarity index 100%
rename from examples/competing_phases/zro2_chempots.csv
rename to examples/CompetingPhases/zro2_chempots.csv
diff --git a/examples/competing_phases/zro2_chempots.json b/examples/CompetingPhases/zro2_chempots.json
similarity index 100%
rename from examples/competing_phases/zro2_chempots.json
rename to examples/CompetingPhases/zro2_chempots.json
diff --git a/examples/competing_phases/zro2_competing_phase_energies.csv b/examples/CompetingPhases/zro2_competing_phase_energies.csv
similarity index 100%
rename from examples/competing_phases/zro2_competing_phase_energies.csv
rename to examples/CompetingPhases/zro2_competing_phase_energies.csv
diff --git a/examples/competing_phases/zro2_la_chempots.csv b/examples/CompetingPhases/zro2_la_chempots.csv
similarity index 100%
rename from examples/competing_phases/zro2_la_chempots.csv
rename to examples/CompetingPhases/zro2_la_chempots.csv
diff --git a/examples/competing_phases/zro2_la_chempots.json b/examples/CompetingPhases/zro2_la_chempots.json
similarity index 100%
rename from examples/competing_phases/zro2_la_chempots.json
rename to examples/CompetingPhases/zro2_la_chempots.json
diff --git a/examples/competing_phases/zro2_la_competing_phase_energies.csv b/examples/CompetingPhases/zro2_la_competing_phase_energies.csv
similarity index 100%
rename from examples/competing_phases/zro2_la_competing_phase_energies.csv
rename to examples/CompetingPhases/zro2_la_competing_phase_energies.csv
diff --git a/examples/competing_phases/zro2_y_chempots.json b/examples/CompetingPhases/zro2_y_chempots.json
similarity index 100%
rename from examples/competing_phases/zro2_y_chempots.json
rename to examples/CompetingPhases/zro2_y_chempots.json
diff --git a/pyproject.toml b/pyproject.toml
index ec7f4a1c..2f320cc0 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -21,12 +21,8 @@ dependencies = [
"tabulate",
"matplotlib>=3.5.2",
"numpy>=1.21.0",
- "pymatgen>=2023.11.12", # limiting factor: POTCAR_STATS_PATH available from pymatgen.io.vasp.inputs
- "spglib>=2.4.0", # previously explicitly set to <=2.0.2 to avoid unnecessary warnings
- # (https://github.com/spglib/spglib/issues/338) but causing dependency issues
- # (https://github.com/SMTG-Bham/doped/issues/73), being worked on in
- # https://github.com/spglib/spglib/issues/462 -- update Troubleshooting.rst and spglib warning in
- # doped.utils.symmetry when finally resolved
+ "pymatgen>=2024.5.31", # limiting factor: breaking changes in `VaspInputSet`;
+ # https://github.com/materialsproject/pymatgen/issues/3860
"pymatgen-analysis-defects>=2023.8.22",
"shakenbreak>=3.3.4", # oxi-state functions renaming
"pandas",
@@ -45,9 +41,10 @@ Repository = "https://github.com/SMTG-Bham/doped"
[project.optional-dependencies]
tests = [
- "pytest<8.2", # with pytest==8.2.0, seemed to cause memory issue on GH Actions mpl tests, python=3.10
+ "pytest>=8.3",
"pytest-mpl>=0.16.1",
- "py-sc-fermi"
+ "mp-api", # not required by default as user can use legacy API, but needed for tests
+ "py-sc-fermi", # not required, but allows `FermiSolver` with `py-sc-fermi` backend to be tested
]
docs = ["sphinx", "sphinx-book-theme", "sphinx_design"]
analysis = [
diff --git a/tests/data/Cu2SiSe3_MP_POSCAR b/tests/data/Cu2SiSe3_MP_POSCAR
new file mode 100644
index 00000000..aeae0e7a
--- /dev/null
+++ b/tests/data/Cu2SiSe3_MP_POSCAR
@@ -0,0 +1,32 @@
+Cu8 Si4 Se12
+1.0
+ 0.0000000000000000 6.6709767058199212 0.0000000000000000
+ 11.7236801335501752 0.0000000000000000 0.0000000000000000
+ 0.0000000000000000 -2.1177252581368129 -6.3413307211506247
+Cu Si Se
+8 4 12
+direct
+ 0.9879925050000000 0.7548038650000000 0.4973765800000000 Cu+
+ 0.4879925050000000 0.7451961350000000 0.9973765800000001 Cu+
+ 0.4970841949999999 0.9130037750000000 0.4998422500000000 Cu+
+ 0.9970841949999998 0.5869962250000000 0.9998422499999999 Cu+
+ 0.4879925050000000 0.2548038650000000 0.4973765800000000 Cu+
+ 0.9879925050000000 0.2451961350000000 0.9973765800000001 Cu+
+ 0.9970841949999998 0.4130037750000000 0.4998422500000000 Cu+
+ 0.4970841949999998 0.0869962250000000 0.9998422499999999 Cu+
+ 0.4706155300000000 0.5848629399999999 0.4756780200000000 Si4+
+ 0.9706155299999999 0.9151370600000001 0.9756780200000001 Si4+
+ 0.9706155299999999 0.0848629399999999 0.4756780200000000 Si4+
+ 0.4706155299999999 0.4151370600000001 0.9756780200000001 Si4+
+ 0.6139464749999999 0.9152577350000000 0.8774259200000000 Se2-
+ 0.1139464749999999 0.5847422650000000 0.3774259200000000 Se2-
+ 0.6090688300000000 0.7419345700000000 0.3731250300000000 Se2-
+ 0.1090688300000000 0.7580654300000000 0.8731250300000000 Se2-
+ 0.6091879750000000 0.5785419649999999 0.8550521899999999 Se2-
+ 0.1091879750000000 0.9214580350000001 0.3550521899999999 Se2-
+ 0.1139464749999999 0.4152577350000000 0.8774259200000000 Se2-
+ 0.6139464749999999 0.0847422650000000 0.3774259200000000 Se2-
+ 0.1090688300000000 0.2419345700000000 0.3731250300000000 Se2-
+ 0.6090688300000000 0.2580654300000000 0.8731250300000000 Se2-
+ 0.1091879750000000 0.0785419649999999 0.8550521899999999 Se2-
+ 0.6091879750000000 0.4214580350000001 0.3550521899999999 Se2-
diff --git a/tests/data/Na2FePO4F_MP_POSCAR b/tests/data/Na2FePO4F_MP_POSCAR
new file mode 100644
index 00000000..d764369c
--- /dev/null
+++ b/tests/data/Na2FePO4F_MP_POSCAR
@@ -0,0 +1,80 @@
+Na16 Fe8 P8 O32 F8
+1.0
+ 5.2809960000000000 0.0000000000000000 0.0000000000000003
+ 0.0000000000000019 11.9541699999999995 0.0000000000000007
+ 0.0000000000000000 0.0000000000000000 14.0416679999999996
+Na Fe P O F
+16 8 8 32 8
+direct
+ 0.2637200000000000 0.3282100000000000 0.2447880000000001 Na+
+ 0.2362800000000000 0.8282100000000000 0.2552119999999999 Na+
+ 0.7637200000000000 0.6717900000000000 0.2552119999999999 Na+
+ 0.7362800000000000 0.1717900000000000 0.2447880000000001 Na+
+ 0.7362800000000000 0.6717900000000000 0.7552119999999999 Na+
+ 0.7637200000000000 0.1717900000000000 0.7447880000000001 Na+
+ 0.2362800000000000 0.3282100000000000 0.7447880000000001 Na+
+ 0.2637200000000000 0.8282100000000000 0.7552119999999999 Na+
+ 0.2391560000000000 0.0834260000000000 0.1247590000000001 Na+
+ 0.2608440000000000 0.5834260000000000 0.3752409999999999 Na+
+ 0.7391560000000000 0.9165740000000000 0.3752409999999999 Na+
+ 0.7608440000000000 0.4165740000000000 0.1247590000000001 Na+
+ 0.7608440000000000 0.9165740000000000 0.8752409999999999 Na+
+ 0.7391560000000000 0.4165740000000000 0.6247590000000001 Na+
+ 0.2608440000000000 0.0834260000000000 0.6247590000000001 Na+
+ 0.2391560000000000 0.5834260000000000 0.8752409999999999 Na+
+ 0.2277400000000000 0.3257140000000000 0.0098220000000001 Fe2+
+ 0.2722600000000000 0.8257140000000001 0.4901779999999999 Fe2+
+ 0.7277400000000001 0.6742859999999999 0.4901779999999999 Fe2+
+ 0.7722599999999999 0.1742860000000000 0.0098220000000001 Fe2+
+ 0.7722599999999999 0.6742859999999999 0.9901779999999999 Fe2+
+ 0.7277400000000001 0.1742860000000000 0.5098220000000001 Fe2+
+ 0.2722600000000000 0.3257140000000000 0.5098220000000001 Fe2+
+ 0.2277400000000000 0.8257140000000001 0.9901779999999999 Fe2+
+ 0.2035070000000000 0.0869920000000000 0.3807690000000000 P5+
+ 0.2964930000000000 0.5869920000000000 0.1192310000000000 P5+
+ 0.7035070000000000 0.9130080000000000 0.1192310000000000 P5+
+ 0.7964930000000000 0.4130080000000000 0.3807690000000000 P5+
+ 0.7964930000000000 0.9130080000000000 0.6192310000000000 P5+
+ 0.7035070000000000 0.4130080000000000 0.8807690000000000 P5+
+ 0.2964930000000000 0.0869920000000000 0.8807690000000000 P5+
+ 0.2035070000000000 0.5869920000000000 0.6192310000000000 P5+
+ 0.2669350000000000 0.9602830000000000 0.3876400000000001 O2-
+ 0.2330650000000000 0.4602830000000000 0.1123599999999999 O2-
+ 0.7669349999999999 0.0397170000000000 0.1123599999999999 O2-
+ 0.7330650000000001 0.5397170000000000 0.3876400000000001 O2-
+ 0.7330650000000001 0.0397170000000000 0.6123599999999999 O2-
+ 0.7669349999999999 0.5397170000000000 0.8876400000000001 O2-
+ 0.2330650000000000 0.9602830000000000 0.8876400000000001 O2-
+ 0.2669350000000000 0.4602830000000000 0.6123599999999999 O2-
+ 0.2851730000000000 0.1334270000000000 0.2834150000000001 O2-
+ 0.2148270000000000 0.6334270000000000 0.2165849999999999 O2-
+ 0.7851730000000000 0.8665730000000000 0.2165849999999999 O2-
+ 0.7148270000000000 0.3665730000000000 0.2834150000000001 O2-
+ 0.7148270000000000 0.8665730000000000 0.7165849999999999 O2-
+ 0.7851730000000000 0.3665730000000000 0.7834150000000001 O2-
+ 0.2148270000000000 0.1334270000000000 0.7834150000000001 O2-
+ 0.2851730000000000 0.6334270000000000 0.7165849999999999 O2-
+ 0.9087130000000000 0.1026060000000000 0.3943770000000001 O2-
+ 0.5912870000000000 0.6026060000000000 0.1056229999999999 O2-
+ 0.4087130000000001 0.8973940000000000 0.1056229999999999 O2-
+ 0.0912870000000000 0.3973940000000000 0.3943770000000001 O2-
+ 0.0912870000000000 0.8973940000000000 0.6056229999999999 O2-
+ 0.4087130000000001 0.3973940000000000 0.8943770000000001 O2-
+ 0.5912870000000000 0.1026060000000000 0.8943770000000001 O2-
+ 0.9087130000000000 0.6026060000000000 0.6056229999999999 O2-
+ 0.3390480000000000 0.1511320000000000 0.4636200000000000 O2-
+ 0.1609520000000000 0.6511320000000000 0.0363800000000000 O2-
+ 0.8390480000000000 0.8488680000000000 0.0363800000000000 O2-
+ 0.6609520000000000 0.3488680000000000 0.4636200000000000 O2-
+ 0.6609520000000000 0.8488680000000000 0.5363800000000000 O2-
+ 0.8390480000000000 0.3488680000000000 0.9636200000000000 O2-
+ 0.1609520000000000 0.1511320000000000 0.9636200000000000 O2-
+ 0.3390480000000000 0.6511320000000000 0.5363800000000000 O2-
+ 0.0000000000000000 0.2500000000000000 0.1241310000000001 F-
+ 0.5000000000000000 0.7500000000000000 0.3758689999999999 F-
+ 0.0000000000000000 0.7500000000000000 0.8758689999999999 F-
+ 0.5000000000000000 0.2500000000000000 0.6241310000000001 F-
+ 0.5000000000000000 0.2500000000000000 0.1012330000000001 F-
+ 0.0000000000000000 0.7500000000000000 0.3987669999999999 F-
+ 0.5000000000000000 0.7500000000000000 0.8987669999999999 F-
+ 0.0000000000000000 0.2500000000000000 0.6012330000000001 F-
diff --git a/tests/test_chemical_potentials.py b/tests/test_chemical_potentials.py
index 675c0fb4..e2387543 100644
--- a/tests/test_chemical_potentials.py
+++ b/tests/test_chemical_potentials.py
@@ -3,10 +3,12 @@
"""
import os
-import shutil
+import sys
import unittest
import warnings
-from pathlib import Path
+from copy import deepcopy
+from functools import wraps
+from io import StringIO
import numpy as np
import pandas as pd
@@ -16,6 +18,7 @@
from test_analysis import if_present_rm
from doped import chemical_potentials
+from doped.utils.symmetry import get_primitive_structure
def _compare_chempot_dicts(dict1, dict2):
@@ -26,45 +29,82 @@ def _compare_chempot_dicts(dict1, dict2):
assert np.isclose(val, dict2[key], atol=1e-5)
-class CompetingPhases(unittest.TestCase):
+def parameterized_subtest(api_key_dict=None):
+ """
+ A test decorator to allow running competing phases tests with both the
+ legacy and new Materials Project API keys.
+ """
+ if api_key_dict is None: # set to SK MP Imperial email (GitHub) A/C keys by default
+ api_key_dict = {"legacy": "c2LiJRMiBeaN5iXsH", "new": "UsPX9Hwut4drZQXPTxk4CwlCstrAAjDv"}
+
+ def decorator(test_func):
+ @wraps(test_func)
+ def wrapper(self, *args, **kwargs):
+ for name, api_key in api_key_dict.items():
+ with self.subTest(api_key=api_key):
+ print(f"Testing with {name} API")
+ test_func(self, api_key, *args, **kwargs)
+
+ return wrapper
+
+ return decorator
+
+
+class CompetingPhasesTestCase(unittest.TestCase):
def setUp(self) -> None:
- self.path = Path(__file__).parents[0]
- self.api_key = "c2LiJRMiBeaN5iXsH" # SK MP Imperial email A/C
+ self.legacy_api_key = "c2LiJRMiBeaN5iXsH" # SK MP Imperial email (GitHub) A/C
+ self.api_key = "UsPX9Hwut4drZQXPTxk4CwlCstrAAjDv" # SK MP Imperial email (GitHub) A/C
+
+ self.cdte = Structure.from_file("../examples/CdTe/relaxed_primitive_POSCAR")
+ self.na2fepo4f = Structure.from_file("data/Na2FePO4F_MP_POSCAR")
+ self.cu2sise3 = Structure.from_file("data/Cu2SiSe3_MP_POSCAR")
+ self.cu2sise4 = self.cu2sise3.get_primitive_structure().copy()
+ self.cu2sise4.append("Se", [0.5, 0.5, 0.5])
+ self.cu2sise4.append("Se", [0.5, 0.75, 0.5])
def tearDown(self) -> None:
- if Path("competing_phases").is_dir():
- shutil.rmtree("competing_phases")
+ if_present_rm("CompetingPhases")
+
+ @parameterized_subtest()
+ def test_init(self, api_key):
+ cp = chemical_potentials.CompetingPhases("ZrO2", e_above_hull=0.03, api_key=api_key)
- def test_init(self):
- cp = chemical_potentials.CompetingPhases("ZrO2", e_above_hull=0.03, api_key=self.api_key)
assert len(cp.entries) == 13
- assert cp.entries[0].name == "O2"
+ assert [entry.name for entry in cp.entries] == [
+ "O2",
+ "Zr",
+ "Zr3O",
+ "ZrO2",
+ "Zr3O",
+ "Zr3O",
+ "Zr2O",
+ "ZrO2",
+ "ZrO2",
+ "Zr",
+ "ZrO2",
+ "ZrO2",
+ "ZrO2",
+ ]
assert cp.entries[0].data["total_magnetization"] == 2
- assert cp.entries[0].data["e_above_hull"] == 0
- assert cp.entries[0].data["molecule"]
+ for i, entry in enumerate(cp.entries):
+ print(entry.name, entry.energy)
+ if i < 4:
+ assert entry.data.get(cp.property_key_dict["energy_above_hull"]) == 0
+ else:
+ assert entry.data.get(cp.property_key_dict["energy_above_hull"]) > 0
+
+ if entry.name == "O2":
+ assert entry.data["molecule"]
+ else:
+ assert not entry.data["molecule"]
+
assert np.isclose(cp.entries[0].data["energy_per_atom"], -4.94795546875)
assert np.isclose(cp.entries[0].data["energy"], -9.8959109375)
- assert cp.entries[1].name == "Zr"
assert np.isclose(cp.entries[1].data["total_magnetization"], 0, atol=1e-3)
- assert cp.entries[1].data["e_above_hull"] == 0
- assert not cp.entries[1].data["molecule"]
- assert cp.entries[2].name == "Zr3O"
- assert cp.entries[2].data["e_above_hull"] == 0
- assert cp.entries[3].name == "ZrO2"
- assert cp.entries[3].data["e_above_hull"] == 0
- assert cp.entries[4].name == "Zr3O"
- assert cp.entries[5].name == "Zr3O"
- assert cp.entries[6].name == "Zr2O"
- assert cp.entries[7].name == "ZrO2"
- assert cp.entries[8].name == "ZrO2"
- assert cp.entries[9].name == "Zr"
- assert cp.entries[10].name == "ZrO2"
- assert cp.entries[11].name == "ZrO2"
- assert cp.entries[12].name == "ZrO2"
-
- assert "Zr4O" not in [e.name for e in cp.entries]
-
- def test_init_ZnSe(self):
+ assert "Zr4O" not in [e.name for e in cp.entries] # not bordering or potentially with EaH
+
+ @parameterized_subtest()
+ def test_init_ZnSe(self, api_key):
"""
As noted by Savya Aggarwal, the legacy MP API code didn't return ZnSe2
as a competing phase despite being on the hull and bordering ZnSe,
@@ -76,56 +116,67 @@ def test_init_ZnSe(self):
Updated code which re-calculates the energy above hull avoids this
issue.
"""
- cp = chemical_potentials.CompetingPhases("ZnSe", api_key=self.api_key)
+ cp = chemical_potentials.CompetingPhases("ZnSe", api_key=api_key)
assert any(e.name == "ZnSe2" for e in cp.entries)
- assert len(cp.entries) == 14 # ZnSe2 now present
+ assert len(cp.entries) in {11, 12} # ZnSe2 present; 2 new Zn entries (mp-264...) with new MP API
znse2_entry = next(e for e in cp.entries if e.name == "ZnSe2")
- assert znse2_entry.data["e_above_hull"] == 0
+ assert znse2_entry.data.get(cp.property_key_dict["energy_above_hull"]) == 0
assert not znse2_entry.data["molecule"]
- assert np.isclose(znse2_entry.data["energy_per_atom"], -3.080017)
- assert np.isclose(znse2_entry.data["energy"], -3.080017 * 12)
+ assert np.isclose(znse2_entry.energy_per_atom, -3.394683861)
+ assert np.isclose(znse2_entry.energy, -3.394683861 * 12)
- def test_init_full_phase_diagram(self):
+ @parameterized_subtest()
+ def test_init_full_phase_diagram(self, api_key):
cp = chemical_potentials.CompetingPhases(
- "ZrO2", e_above_hull=0.03, api_key=self.api_key, full_phase_diagram=True
+ "ZrO2", e_above_hull=0.03, api_key=api_key, full_phase_diagram=True
)
+
assert len(cp.entries) == 14 # Zr4O now present
- assert cp.entries[0].name == "O2"
+ assert [entry.name for entry in cp.entries] == [
+ "O2",
+ "Zr",
+ "Zr3O",
+ "Zr4O",
+ "ZrO2",
+ "Zr3O",
+ "Zr3O",
+ "Zr2O",
+ "ZrO2",
+ "ZrO2",
+ "Zr",
+ "ZrO2",
+ "ZrO2",
+ "ZrO2",
+ ]
assert cp.entries[0].data["total_magnetization"] == 2
- assert cp.entries[0].data["e_above_hull"] == 0
- assert cp.entries[0].data["molecule"]
+ for i, entry in enumerate(cp.entries):
+ print(entry.name, entry.energy)
+ if i < 5:
+ assert entry.data.get(cp.property_key_dict["energy_above_hull"]) == 0
+ else:
+ assert entry.data.get(cp.property_key_dict["energy_above_hull"]) > 0
+
+ if entry.name == "O2":
+ assert entry.data["molecule"]
+ else:
+ assert not entry.data["molecule"]
+
assert np.isclose(cp.entries[0].data["energy_per_atom"], -4.94795546875)
assert np.isclose(cp.entries[0].data["energy"], -9.8959109375)
- assert cp.entries[1].name == "Zr"
assert np.isclose(cp.entries[1].data["total_magnetization"], 0, atol=1e-3)
- assert cp.entries[1].data["e_above_hull"] == 0
- assert not cp.entries[1].data["molecule"]
- assert cp.entries[2].name == "Zr3O"
- assert cp.entries[2].data["e_above_hull"] == 0
- assert cp.entries[3].name == "Zr4O" # new entry!
- assert cp.entries[3].data["e_above_hull"] == 0
- assert cp.entries[4].name == "ZrO2"
- assert cp.entries[4].data["e_above_hull"] == 0
- assert cp.entries[5].name == "Zr3O"
- assert cp.entries[6].name == "Zr3O"
- assert cp.entries[7].name == "Zr2O"
- assert cp.entries[8].name == "ZrO2"
- assert cp.entries[9].name == "ZrO2"
- assert cp.entries[10].name == "Zr"
- assert cp.entries[11].name == "ZrO2"
- assert cp.entries[12].name == "ZrO2"
- assert cp.entries[13].name == "ZrO2"
-
- def test_init_ytos(self):
+
+ @parameterized_subtest()
+ def test_init_YTOS(self, api_key):
# 144 phases on Y-Ti-O-S MP phase diagram
- cp = chemical_potentials.CompetingPhases("Y2Ti2S2O5", e_above_hull=0.1, api_key=self.api_key)
- assert len(cp.entries) == 115 # 115 phases with default algorithm
+ cp = chemical_potentials.CompetingPhases("Y2Ti2S2O5", e_above_hull=0.1, api_key=api_key)
+ assert len(cp.entries) in {109, 113} # legacy and new MP APIs
self.check_O2_entry(cp)
cp = chemical_potentials.CompetingPhases(
- "Y2Ti2S2O5", e_above_hull=0.1, full_phase_diagram=True, api_key=self.api_key
+ "Y2Ti2S2O5", e_above_hull=0.1, full_phase_diagram=True, api_key=api_key
)
- assert len(cp.entries) == 140 # 144 phases on Y-Ti-O-S MP phase diagram, 4 extra O2 phases removed
+ # 144/149 phases on Y-Ti-O-S legacy/new MP phase diagram, 4 extra O2 phases removed
+ assert len(cp.entries) in {140, 145} # legacy and new MP APIs
self.check_O2_entry(cp)
def check_O2_entry(self, cp):
@@ -134,21 +185,75 @@ def check_O2_entry(self, cp):
assert len(result) == 1
assert result[0].name == "O2"
assert result[0].data["total_magnetization"] == 2
- assert result[0].data["e_above_hull"] == 0
+ assert result[0].data[cp.property_key_dict["energy_above_hull"]] == 0
assert result[0].data["molecule"]
assert np.isclose(result[0].data["energy_per_atom"], -4.94795546875)
- def test_unstable_host(self):
+ @parameterized_subtest()
+ def test_entry_naming(self, api_key):
+ """
+ Test the naming functions for competing phase entries in ``doped``,
+ including rounding to "_0" and increasing the number of digits if
+ duplicates are encountered.
+ """
+ cdte_cp = chemical_potentials.CompetingPhases("CdTe", api_key=api_key)
+ if len(api_key) != 32:
+ assert [entry.data["doped_name"] for entry in cdte_cp.entries] == [
+ "Cd_P6_3/mmc_EaH_0",
+ "Te_P3_121_EaH_0",
+ "CdTe_F-43m_EaH_0",
+ "Te_P3_221_EaH_0",
+ "Cd_Fm-3m_EaH_0.001",
+ "Cd_R-3m_EaH_0.001",
+ "CdTe_P6_3mc_EaH_0.003",
+ "CdTe_Cmc2_1_EaH_0.006",
+ "Cd_P6_3/mmc_EaH_0.018",
+ "Te_C2/m_EaH_0.044",
+ "Te_Pm-3m_EaH_0.047",
+ "Te_Pmma_EaH_0.047",
+ "Te_Pmc2_1_EaH_0.049",
+ ]
+ else: # slightly different for new MP API, Te entries the same
+ for i in [
+ "Te_P3_121_EaH_0",
+ "Te_P3_221_EaH_0",
+ "Te_C2/m_EaH_0.044",
+ "Te_Pm-3m_EaH_0.047",
+ "Te_Pmma_EaH_0.047",
+ "Te_Pmc2_1_EaH_0.049",
+ ]:
+ assert i in [entry.data["doped_name"] for entry in cdte_cp.entries]
+
+ # test case when the EaH rounding needs to be dynamically updated:
+ # (this will be quite a rare case, as it requires two phases with the same formula, space group
+ # and energy above hull to 1 meV/atom
+ cds_cp = chemical_potentials.CompetingPhases("CdS", api_key=api_key)
+ assert "S_Pnnm_EaH_0.014" in [entry.data["doped_name"] for entry in cds_cp.entries]
+ new_entry = deepcopy(
+ next(entry for entry in cds_cp.entries if entry.data["doped_name"] == "S_Pnnm_EaH_0.014")
+ ) # duplicate entry to force renaming
+ if len(api_key) != 32:
+ new_entry.data["e_above_hull"] += 2e-4
+ else:
+ new_entry.data["energy_above_hull"] += 2e-4
+ chemical_potentials._name_entries_and_handle_duplicates([*cds_cp.entries, new_entry])
+ entry_names = [entry.data["doped_name"] for entry in [*cds_cp.entries, new_entry]]
+ assert "S_Pnnm_EaH_0.014" not in entry_names
+ assert "S_Pnnm_EaH_0.0141" in entry_names
+ assert "S_Pnnm_EaH_0.0143" in entry_names
+
+ @parameterized_subtest()
+ def test_unstable_host(self, api_key):
"""
Test generating CompetingPhases with a composition that's unstable on
the Materials Project database.
"""
for cp_settings in [
- {"composition": "Na2FePO4F", "e_above_hull": 0.02, "api_key": self.api_key},
+ {"composition": "Na2FePO4F", "e_above_hull": 0.02, "api_key": api_key},
{
"composition": "Na2FePO4F",
"e_above_hull": 0.02,
- "api_key": self.api_key,
+ "api_key": api_key,
"full_phase_diagram": True,
},
]:
@@ -156,21 +261,22 @@ def test_unstable_host(self):
with warnings.catch_warnings(record=True) as w:
cp = chemical_potentials.CompetingPhases(**cp_settings)
print([str(warning.message) for warning in w]) # for debugging
- assert len([warning for warning in w if "You are using" not in str(warning.message)]) == 1
- for sub_message in [
- "Note that the Materials Project (MP) database entry for Na2FePO4F is not stable with "
- "respect to competing phases, having an energy above hull of 0.1701 eV/atom.",
- "Formally, this means that the host material is unstable and so has no chemical "
- "potential limits; though in reality there may be errors in the MP energies",
- "Here we downshift the host compound entry to the convex hull energy, and then "
- "determine the possible competing phases with the same approach as usual",
- ]:
- print(sub_message)
- assert sub_message in str(w[-1].message)
+ if len(api_key) != 32: # recalculated energy for Na2FePO4F on new MP API, now on hull
+ assert len([warning for warning in w if "You are using" not in str(warning.message)]) == 1
+ for sub_message in [
+ "Note that the Materials Project (MP) database entry for Na2FePO4F is not stable with "
+ "respect to competing phases, having an energy above hull of 0.1701 eV/atom.",
+ "Formally, this means that the host material is unstable and so has no chemical "
+ "potential limits; though in reality there may be errors in the MP energies",
+ "Here we downshift the host compound entry to the convex hull energy, and then "
+ "determine the possible competing phases with the same approach as usual",
+ ]:
+ print(sub_message)
+ assert any(sub_message in str(warning.message) for warning in w)
if cp_settings.get("full_phase_diagram"):
- assert len(cp.entries) == 128
+ assert len(cp.entries) in {128, 173} # legacy, new MP APIs
else:
- assert len(cp.entries) == 60
+ assert len(cp.entries) in {50, 68} # legacy, new MP APIs
self.check_O2_entry(cp)
def test_unknown_host(self):
@@ -179,9 +285,9 @@ def test_unknown_host(self):
Materials Project database.
"""
for cp_settings in [
- {"composition": "Cu2SiSe4", "api_key": self.api_key},
- {"composition": "Cu2SiSe4", "api_key": self.api_key, "e_above_hull": 0.0},
- {"composition": "Cu2SiSe4", "api_key": self.api_key, "full_phase_diagram": True},
+ {"composition": "Cu2SiSe4", "api_key": self.legacy_api_key},
+ {"composition": "Cu2SiSe4", "api_key": self.legacy_api_key, "e_above_hull": 0.0},
+ {"composition": "Cu2SiSe4", "api_key": self.legacy_api_key, "full_phase_diagram": True},
]:
print(f"Testing with settings: {cp_settings}")
with warnings.catch_warnings(record=True) as w:
@@ -192,63 +298,47 @@ def test_unknown_host(self):
w[-1].message
)
if cp_settings.get("full_phase_diagram"):
- assert len(cp.entries) == 45
+ assert len(cp.entries) == 29
elif cp_settings.get("e_above_hull") == 0.0:
assert len(cp.entries) == 8
else:
- assert len(cp.entries) == 38
+ assert len(cp.entries) == 26
- def test_api_keys_errors(self):
- api_key_error_start = ValueError(
- "The supplied API key (either ``api_key`` or 'PMG_MAPI_KEY' in ``~/.pmgrc.yaml`` or "
- "``~/.config/.pmgrc.yaml``;"
- )
- with pytest.raises(ValueError) as e:
- chemical_potentials.CompetingPhases(
- "ZrO2",
- api_key="test",
- )
- assert str(api_key_error_start) in str(e.value)
- #
- assert (
- "is not a valid Materials Project API "
- "key, which is required by doped for competing phase generation. See the doped "
- "installation instructions for details:\n"
- "https://doped.readthedocs.io/en/latest/Installation.html#setup-potcars-and-materials"
- "-project-api"
- ) in str(e.value)
+ # check naming of fake entry
+ assert "Cu2SiSe4_NA_EaH_0" in [entry.data["doped_name"] for entry in cp.entries]
- # test all works fine with key from new MP API:
- assert chemical_potentials.CompetingPhases("ZrO2", api_key="UsPX9Hwut4drZQXPTxk4CwlCstrAAjDv")
+ # TODO: Test file generation functions for an unknown host!
- def test_convergence_setup(self):
- cp = chemical_potentials.CompetingPhases("ZrO2", e_above_hull=0.03, api_key=self.api_key)
+ @parameterized_subtest()
+ def test_convergence_setup(self, api_key):
+ cp = chemical_potentials.CompetingPhases("ZrO2", e_above_hull=0.03, api_key=api_key)
# potcar spec doesn't need potcars set up for pmg and it still works
cp.convergence_setup(potcar_spec=True)
assert len(cp.metals) == 6
assert cp.metals[0].data["band_gap"] == 0
assert not cp.nonmetals[0].data["molecule"]
# this shouldn't exist - don't need to convergence test for molecules
- assert not Path("competing_phases/O2_EaH_0.0").is_dir()
+ assert not os.path.exists("CompetingPhases/O2_Pmmm_EaH_0")
# test if it writes out the files correctly
- path1 = "competing_phases/ZrO2_EaH_0.0088/kpoint_converge/k2,1,1/"
- assert Path(path1).is_dir()
- with open(f"{path1}/KPOINTS", encoding="utf-8") as file:
+ Zro2_EaH_0pt009_folder = "CompetingPhases/ZrO2_Pbca_EaH_0.009/kpoint_converge/k2,1,1/"
+ assert os.path.exists(Zro2_EaH_0pt009_folder)
+ with open(f"{Zro2_EaH_0pt009_folder}/KPOINTS", encoding="utf-8") as file:
contents = file.readlines()
assert contents[3] == "2 1 1\n"
- with open(f"{path1}/POTCAR.spec", encoding="utf-8") as file:
+ with open(f"{Zro2_EaH_0pt009_folder}/POTCAR.spec", encoding="utf-8") as file:
contents = file.readlines()
assert contents[0] == "Zr_sv\n"
- with open(f"{path1}/INCAR", encoding="utf-8") as file:
+ with open(f"{Zro2_EaH_0pt009_folder}/INCAR", encoding="utf-8") as file:
contents = file.readlines()
assert any(line == "GGA = Ps\n" for line in contents)
assert any(line == "NSW = 0\n" for line in contents)
- def test_vasp_std_setup(self):
- cp = chemical_potentials.CompetingPhases("ZrO2", e_above_hull=0.03, api_key=self.api_key)
+ @parameterized_subtest()
+ def test_vasp_std_setup(self, api_key):
+ cp = chemical_potentials.CompetingPhases("ZrO2", e_above_hull=0.03, api_key=api_key)
cp.vasp_std_setup(potcar_spec=True)
assert len(cp.nonmetals) == 6
assert len(cp.metals) == 6
@@ -258,95 +348,216 @@ def test_vasp_std_setup(self):
assert cp.molecules[0].data["molecule"]
assert not cp.nonmetals[0].data["molecule"]
- path1 = "competing_phases/ZrO2_EaH_0/vasp_std/"
- assert Path(path1).is_dir()
- with open(f"{path1}/KPOINTS", encoding="utf-8") as file:
+ ZrO2_EaH_0_std_folder = "CompetingPhases/ZrO2_P2_1/c_EaH_0/vasp_std/"
+ assert os.path.exists(ZrO2_EaH_0_std_folder)
+ with open(f"{ZrO2_EaH_0_std_folder}/KPOINTS", encoding="utf-8") as file:
contents = file.readlines()
assert "KPOINTS from doped, with reciprocal_density = 64/Å" in contents[0]
assert contents[3] == "4 4 4\n"
- with open(f"{path1}/POTCAR.spec", encoding="utf-8") as file:
+ with open(f"{ZrO2_EaH_0_std_folder}/POTCAR.spec", encoding="utf-8") as file:
contents = file.readlines()
assert contents == ["Zr_sv\n", "O"]
- with open(f"{path1}/INCAR", encoding="utf-8") as file:
+ with open(f"{ZrO2_EaH_0_std_folder}/INCAR", encoding="utf-8") as file:
contents = file.readlines()
assert all(x in contents for x in ["AEXX = 0.25\n", "ISIF = 3\n", "GGA = Pe\n"])
- path2 = "competing_phases/O2_EaH_0/vasp_std"
- assert Path(path2).is_dir()
- with open(f"{path2}/KPOINTS", encoding="utf-8") as file:
+ O2_EaH_0_std_folder = "CompetingPhases/O2_Pmmm_EaH_0/vasp_std"
+ assert os.path.exists(O2_EaH_0_std_folder)
+ with open(f"{O2_EaH_0_std_folder}/KPOINTS", encoding="utf-8") as file:
contents = file.readlines()
assert contents[3] == "1 1 1\n"
- struct = Structure.from_file(f"{path2}/POSCAR")
+ struct = Structure.from_file(f"{O2_EaH_0_std_folder}/POSCAR")
assert np.isclose(struct.sites[0].frac_coords, [0.49983339, 0.5, 0.50016672]).all()
assert np.isclose(struct.sites[1].frac_coords, [0.49983339, 0.5, 0.5405135]).all()
+ def test_api_keys_errors(self):
+ api_key_error_start = ValueError(
+ "The supplied API key (either ``api_key`` or 'PMG_MAPI_KEY' in ``~/.pmgrc.yaml`` or "
+ "``~/.config/.pmgrc.yaml``;"
+ )
+ with pytest.raises(ValueError) as e:
+ chemical_potentials.CompetingPhases(
+ "ZrO2",
+ api_key="test",
+ )
+ assert str(api_key_error_start) in str(e.value)
+ #
+ assert (
+ "is not a valid Materials Project API "
+ "key, which is required by doped for competing phase generation. See the doped "
+ "installation instructions for details:\n"
+ "https://doped.readthedocs.io/en/latest/Installation.html#setup-potcars-and-materials"
+ "-project-api"
+ ) in str(e.value)
-class ExtrinsicCompetingPhasesTest(unittest.TestCase):
- # TODO: Need to add tests for co-doping, full_sub_approach, full_phase_diagram etc!!
- def setUp(self) -> None:
- self.path = Path(__file__).parents[0]
- self.api_key = "c2LiJRMiBeaN5iXsH" # SK MP Imperial email A/C
- self.ex_cp = chemical_potentials.ExtrinsicCompetingPhases(
- "ZrO2", extrinsic_species="La", e_above_hull=0, api_key=self.api_key
+ # test all works fine with key from new MP API:
+ assert chemical_potentials.CompetingPhases("ZrO2", api_key="UsPX9Hwut4drZQXPTxk4CwlCstrAAjDv")
+
+ def test_legacy_API_message(self):
+ """
+ Quick test to check that the message about doped now supporting the new
+ Materials Project API is printed to stdout as expected.
+ """
+ original_stdout = sys.stdout # Save a reference to the original standard output
+ sys.stdout = StringIO() # Redirect standard output to a stringIO object.
+
+ try:
+ chemical_potentials.CompetingPhases("Si", api_key=self.legacy_api_key)
+ output = sys.stdout.getvalue() # Return a str containing the printed output
+ finally:
+ sys.stdout = original_stdout # Reset standard output to its original value.
+
+ print(output) # for debugging
+ assert (
+ "Note that doped now supports the new Materials Project API, which can be used by updating "
+ "your API key in ~/.pmgrc.yaml or ~/.config/.pmgrc.yaml: "
+ "https://doped.readthedocs.io/en/latest/Installation.html#setup-potcars-and-materials"
+ "-project-api"
+ ) in output
+
+ @parameterized_subtest()
+ def test_structure_input(self, api_key):
+ for struct, name in [
+ (self.cdte, "CdTe_F-43m_EaH_0"),
+ (self.cdte * 2, "CdTe_F-43m_EaH_0"), # supercell
+ (self.na2fepo4f, "Na2FePO4F_Pbcn_EaH_0.17"),
+ (self.cu2sise4, "Cu2SiSe4_P1_EaH_0"),
+ ]:
+ with warnings.catch_warnings(record=True) as w:
+ cp = chemical_potentials.CompetingPhases(
+ struct.composition.reduced_formula, api_key=api_key
+ )
+ cp_struct_input = chemical_potentials.CompetingPhases(struct, api_key=api_key)
+
+ _check_structure_input(cp, cp_struct_input, struct, name, w, api_key)
+
+
+def _check_structure_input(cp, cp_struct_input, struct, name, w, api_key, extrinsic=False):
+ print([str(warning.message) for warning in w]) # for debugging
+ user_warnings = [warning for warning in w if warning.category is UserWarning]
+ if "Na2FePO4F" in name and len(api_key) != 32: # stable in new MP
+ assert len(user_warnings) == 2
+ assert "Note that the Materials Project (MP) database entry for Na2FePO4F is not stable" in str(
+ user_warnings[0].message
+ )
+ elif "Cu2SiSe4" in name:
+ assert len(user_warnings) == 2
+ assert "Note that no Materials Project (MP) database entry exists for Cu2SiSe4" in str(
+ user_warnings[0].message
)
+ else:
+ assert not user_warnings
+
+ struct_entries = cp_struct_input.entries if not extrinsic else cp_struct_input.intrinsic_entries
+ cp_entries = cp.entries if not extrinsic else cp.intrinsic_entries
+ for entry in struct_entries:
+ if entry.name != "Cu2SiSe4": # differs in this case due to doubled formula in unit cell
+ assert entry in cp_entries # structure not compared with ``__eq__`` for entries
+ if entry.name == struct.composition.reduced_formula:
+ if "Na2FePO4F" not in name or len(api_key) != 32:
+ assert entry.data["doped_name"] == name
+ else:
+ assert entry.data["doped_name"] == "Na2FePO4F_Pbcn_EaH_0" # stable in new MP
+ if entry.name != "CdTe" or len(struct) != 16:
+ assert entry.structure == struct
+ else: # with supercell input, structure reduced to the primitive cell
+ assert entry.structure == get_primitive_structure(struct)
- def tearDown(self) -> None:
- if Path("competing_phases").is_dir():
- shutil.rmtree("competing_phases")
+ for entry in cp_entries:
+ if entry.name != struct.composition.reduced_formula:
+ assert entry in struct_entries
- def test_init(self):
- assert len(self.ex_cp.entries) == 2
- assert self.ex_cp.entries[0].name == "La" # definite ordering
- assert self.ex_cp.entries[1].name == "La2Zr2O7" # definite ordering
- assert [(entry.data["e_above_hull"] == 0) for entry in self.ex_cp.entries]
+ assert len(struct_entries) <= len(cp_entries)
+ assert (
+ len([entry for entry in struct_entries if entry.name == struct.composition.reduced_formula]) == 1
+ )
+
+
+class ExtrinsicCompetingPhasesTestCase(unittest.TestCase): # same setUp and tearDown as above
+ # TODO: Need to add tests for co-doping, full_sub_approach, full_phase_diagram etc!!
+ def setUp(self):
+ CompetingPhasesTestCase.setUp(self)
+
+ def tearDown(self):
+ CompetingPhasesTestCase.tearDown(self)
+
+ @parameterized_subtest()
+ def test_init(self, api_key):
+ ex_cp = chemical_potentials.ExtrinsicCompetingPhases(
+ "ZrO2", extrinsic_species="La", e_above_hull=0, api_key=api_key
+ )
+ assert len(ex_cp.entries) == 2
+ assert ex_cp.entries[0].name == "La" # definite ordering
+ assert ex_cp.entries[1].name == "La2Zr2O7" # definite ordering
+ assert [(entry.data["e_above_hull"] == 0) for entry in ex_cp.entries]
# names of intrinsic entries: ['O2', 'Zr', 'Zr3O', 'ZrO2']
- assert len(self.ex_cp.intrinsic_entries) == 4
- assert self.ex_cp.intrinsic_entries[0].name == "O2"
- assert self.ex_cp.intrinsic_entries[1].name == "Zr"
- assert self.ex_cp.intrinsic_entries[2].name == "Zr3O"
- assert self.ex_cp.intrinsic_entries[3].name == "ZrO2"
- assert all(entry.data["e_above_hull"] == 0 for entry in self.ex_cp.intrinsic_entries)
+ assert len(ex_cp.intrinsic_entries) == 4
+ assert ex_cp.intrinsic_entries[0].name == "O2"
+ assert ex_cp.intrinsic_entries[1].name == "Zr"
+ assert ex_cp.intrinsic_entries[2].name == "Zr3O"
+ assert ex_cp.intrinsic_entries[3].name == "ZrO2"
+ assert all(entry.data["e_above_hull"] == 0 for entry in ex_cp.intrinsic_entries)
cp = chemical_potentials.ExtrinsicCompetingPhases(
- "ZrO2", extrinsic_species="La", api_key=self.api_key
- ) # default e_above_hull=0.1
- assert len(cp.entries) == 5
+ "ZrO2", extrinsic_species="La", api_key=api_key
+ ) # default e_above_hull=0.05
+ assert len(cp.entries) == 3
assert cp.entries[2].name == "La" # definite ordering, same 1st & 2nd as before
- assert cp.entries[3].name == "LaZr9O20" # definite ordering
- assert cp.entries[4].name == "LaZr9O20" # definite ordering
assert all(entry.data["e_above_hull"] == 0 for entry in cp.entries[:2])
assert all(entry.data["e_above_hull"] != 0 for entry in cp.entries[2:])
- assert len(cp.intrinsic_entries) == 28
+ assert len(cp.intrinsic_entries) == 18
+
+ @parameterized_subtest()
+ def test_structure_input(self, api_key):
+ for struct, name in [
+ (self.cdte, "CdTe_F-43m_EaH_0"),
+ (self.cdte * 2, "CdTe_F-43m_EaH_0"), # supercell
+ (self.na2fepo4f, "Na2FePO4F_Pbcn_EaH_0.17"),
+ (self.cu2sise4, "Cu2SiSe4_P1_EaH_0"),
+ ]:
+ with warnings.catch_warnings(record=True) as w:
+ cp = chemical_potentials.ExtrinsicCompetingPhases(
+ struct.composition.reduced_formula, api_key=api_key, extrinsic_species={"K"}
+ )
+ cp_struct_input = chemical_potentials.ExtrinsicCompetingPhases(
+ struct, api_key=api_key, extrinsic_species={"K"}
+ )
+
+ _check_structure_input(cp, cp_struct_input, struct, name, w, api_key, extrinsic=True)
+ for entries_list in [cp_struct_input.entries, cp.entries]:
+ assert len(entries_list) >= 1
+ for extrinsic_entry in entries_list:
+ assert "K" in extrinsic_entry.data["doped_name"]
+ assert "K" in extrinsic_entry.name
-class ChemPotAnalyzerTest(unittest.TestCase):
+
+class ChemPotAnalyzerTestCase(unittest.TestCase):
def setUp(self):
- self.path = Path(__file__).parents[1].joinpath("examples/competing_phases")
+ self.examples_path = "../examples/CompetingPhases"
self.stable_system = "ZrO2"
self.unstable_system = "Zr2O"
self.extrinsic_species = "La"
- self.csv_path = self.path / "zro2_competing_phase_energies.csv"
- self.csv_path_ext = self.path / "zro2_la_competing_phase_energies.csv"
- self.parsed_chempots = loadfn(self.path / "zro2_chempots.json")
- self.parsed_ext_chempots = loadfn(self.path / "zro2_la_chempots.json")
-
- def tearDown(self) -> None:
- if Path("chempot_limits.csv").is_file():
- os.remove("chempot_limits.csv")
+ self.csv_path = f"{self.examples_path}/zro2_competing_phase_energies.csv"
+ self.csv_path_ext = f"{self.examples_path}/zro2_la_competing_phase_energies.csv"
+ self.parsed_chempots = loadfn(f"{self.examples_path}/zro2_chempots.json")
+ self.parsed_ext_chempots = loadfn(f"{self.examples_path}/zro2_la_chempots.json")
+ self.parsed_ext_y_chempots = loadfn(f"{self.examples_path}/zro2_la_chempots.json")
- if Path("competing_phases.csv").is_file():
- os.remove("competing_phases.csv")
+ self.zro2_path = os.path.join(self.examples_path, "ZrO2")
+ self.la_zro2_path = os.path.join(self.examples_path, "La_ZrO2")
- if Path("input.dat").is_file():
- os.remove("input.dat")
+ def tearDown(self):
+ for i in ["chempot_limits.csv", "CompetingPhases.csv", "input.dat"]:
+ if_present_rm(i)
- for i in os.listdir(self.path / "ZrO2"):
+ for i in os.listdir(os.path.join(self.examples_path, "ZrO2")):
if i.startswith("."):
- if_present_rm(self.path / "ZrO2" / i)
+ if_present_rm(os.path.join(self.examples_path, "ZrO2", i))
def test_cpa_csv(self):
stable_cpa = chemical_potentials.CompetingPhasesAnalyzer(self.stable_system)
@@ -366,7 +577,6 @@ def test_cpa_csv(self):
-119.619571095,
)
- # test chempots
def test_cpa_chempots(self):
stable_cpa = chemical_potentials.CompetingPhasesAnalyzer(self.stable_system)
stable_cpa.from_csv(self.csv_path)
@@ -444,11 +654,9 @@ def test_sort_by(self):
with pytest.raises(KeyError):
stable_cpa.calculate_chempots(sort_by="M")
- # test vaspruns
def test_vaspruns(self):
cpa = chemical_potentials.CompetingPhasesAnalyzer(self.stable_system)
- path = self.path / "ZrO2"
- cpa.from_vaspruns(path=path, folder="relax", csv_path=self.csv_path)
+ cpa.from_vaspruns(path=self.zro2_path, folder="relax", csv_path=self.csv_path)
assert len(cpa.elements) == 2
assert cpa.data[0]["Formula"] == "O2"
@@ -460,8 +668,7 @@ def test_vaspruns(self):
cpa_no.from_vaspruns(path=0)
ext_cpa = chemical_potentials.CompetingPhasesAnalyzer(self.stable_system)
- path2 = self.path / "La_ZrO2"
- ext_cpa.from_vaspruns(path=path2, folder="relax", csv_path=self.csv_path_ext)
+ ext_cpa.from_vaspruns(path=self.la_zro2_path, folder="relax", csv_path=self.csv_path_ext)
assert len(ext_cpa.elements) == 3
assert len(ext_cpa.extrinsic_elements) == 1
# sorted by num_species, then alphabetically, then by num_atoms_in_fu, then by
@@ -521,48 +728,45 @@ def test_vaspruns(self):
# check if it works from a list
all_paths = []
- for p in path.iterdir():
- if not p.name.startswith("."):
- pp = p / "relax" / "vasprun.xml"
- ppgz = p / "relax" / "vasprun.xml.gz"
- if pp.is_file():
- all_paths.append(pp)
- elif ppgz.is_file():
- all_paths.append(ppgz)
+ for subfolder in os.listdir(self.zro2_path):
+ if os.path.isdir(os.path.join(self.zro2_path, subfolder)) and "relax" in os.listdir(
+ os.path.join(self.zro2_path, subfolder)
+ ):
+ all_paths.extend(
+ os.path.join(self.zro2_path, subfolder, "relax", vr_file)
+ for vr_file in os.listdir(os.path.join(self.zro2_path, subfolder, "relax"))
+ if vr_file.startswith("vasprun.xml")
+ )
lst_cpa = chemical_potentials.CompetingPhasesAnalyzer(self.stable_system)
lst_cpa.from_vaspruns(path=all_paths)
assert len(lst_cpa.elements) == 2
assert len(lst_cpa.vasprun_paths) == 8
- all_fols = [p / "relax" for p in path.iterdir() if not p.name.startswith(".")]
+ all_folders = [path.split("/relax")[0] for path in all_paths]
lst_fols_cpa = chemical_potentials.CompetingPhasesAnalyzer(self.stable_system)
- lst_fols_cpa.from_vaspruns(path=all_fols)
+ lst_fols_cpa.from_vaspruns(path=all_folders)
assert len(lst_fols_cpa.elements) == 2
def test_vaspruns_hidden_files(self):
cpa = chemical_potentials.CompetingPhasesAnalyzer(self.stable_system)
- path = self.path / "ZrO2"
-
- with open(f"{path}/._OUTCAR", "w") as f:
+ with open(f"{self.zro2_path}/._OUTCAR", "w") as f:
f.write("test pop")
- with open(f"{path}/._vasprun.xml", "w") as f:
+ with open(f"{self.zro2_path}/._vasprun.xml", "w") as f:
f.write("test pop")
- with open(f"{path}/._LOCPOT", "w") as f:
+ with open(f"{self.zro2_path}/._LOCPOT", "w") as f:
f.write("test pop")
- with open(f"{path}/.DS_Store", "w") as f:
+ with open(f"{self.zro2_path}/.DS_Store", "w") as f:
f.write("test pop")
with warnings.catch_warnings(record=True) as w:
- cpa.from_vaspruns(path=path, folder="relax", csv_path=self.csv_path)
+ cpa.from_vaspruns(path=self.zro2_path, folder="relax", csv_path=self.csv_path)
print([str(warning.message) for warning in w]) # for debugging
assert not w
def test_vaspruns_none_parsed(self):
cpa = chemical_potentials.CompetingPhasesAnalyzer(self.stable_system)
- path = Path(__file__).parents[1].joinpath("examples/competing_phases")
-
with warnings.catch_warnings(record=True) as w, pytest.raises(FileNotFoundError) as e:
- cpa.from_vaspruns(path=path, folder="relax", csv_path=self.csv_path)
+ cpa.from_vaspruns(path=self.examples_path, folder="relax", csv_path=self.csv_path)
print([str(warning.message) for warning in w]) # for debugging
assert "vasprun.xml files could not be found in the following directories (in" in str(w[0].message)
assert "ZrO2 or ZrO2/relax" in str(w[0].message)
@@ -574,7 +778,7 @@ def test_cplap_input(self):
cpa.from_csv(self.csv_path)
cpa._cplap_input(dependent_variable="O")
- assert Path("input.dat").is_file()
+ assert os.path.exists("input.dat")
with open("input.dat", encoding="utf-8") as file:
contents = file.readlines()
@@ -599,8 +803,7 @@ def test_cplap_input(self):
def test_latex_table(self):
cpa = chemical_potentials.CompetingPhasesAnalyzer(self.stable_system)
- path = self.path / "ZrO2"
- cpa.from_vaspruns(path=path, folder="relax", csv_path=self.csv_path)
+ cpa.from_vaspruns(path=self.zro2_path, folder="relax", csv_path=self.csv_path)
string = cpa.to_LaTeX_table(splits=1)
assert (
@@ -653,9 +856,9 @@ def test_to_csv(self):
stable_cpa.from_csv(self.csv_path)
stable_cpa_data = stable_cpa._get_and_sort_formation_energy_data()
- stable_cpa.to_csv("competing_phases.csv")
+ stable_cpa.to_csv("CompetingPhases.csv")
reloaded_cpa = chemical_potentials.CompetingPhasesAnalyzer(self.stable_system)
- reloaded_cpa.from_csv("competing_phases.csv")
+ reloaded_cpa.from_csv("CompetingPhases.csv")
reloaded_cpa_data = reloaded_cpa._get_and_sort_formation_energy_data()
print(
pd.DataFrame(stable_cpa_data).to_dict(), pd.DataFrame(reloaded_cpa_data).to_dict()
@@ -667,9 +870,9 @@ def test_to_csv(self):
self.ext_cpa = chemical_potentials.CompetingPhasesAnalyzer(self.stable_system)
self.ext_cpa.from_csv(self.csv_path_ext)
- self.ext_cpa.to_csv("competing_phases.csv")
+ self.ext_cpa.to_csv("CompetingPhases.csv")
reloaded_ext_cpa = chemical_potentials.CompetingPhasesAnalyzer(self.stable_system)
- reloaded_ext_cpa.from_csv("competing_phases.csv")
+ reloaded_ext_cpa.from_csv("CompetingPhases.csv")
reloaded_ext_cpa._get_and_sort_formation_energy_data()
assert reloaded_ext_cpa.formation_energy_df.round(4).equals(
@@ -677,16 +880,16 @@ def test_to_csv(self):
)
# test pruning:
- self.ext_cpa.to_csv("competing_phases.csv", prune_polymorphs=True)
- reloaded_ext_cpa.from_csv("competing_phases.csv")
+ self.ext_cpa.to_csv("CompetingPhases.csv", prune_polymorphs=True)
+ reloaded_ext_cpa.from_csv("CompetingPhases.csv")
assert len(reloaded_ext_cpa.data) == 8 # polymorphs pruned
assert len(self.ext_cpa.data) == 11
formulas = [i["Formula"] for i in reloaded_ext_cpa.data]
assert len(formulas) == len(set(formulas)) # no polymorphs
- reloaded_cpa.to_csv("competing_phases.csv", prune_polymorphs=True)
- reloaded_cpa.from_csv("competing_phases.csv")
+ reloaded_cpa.to_csv("CompetingPhases.csv", prune_polymorphs=True)
+ reloaded_cpa.from_csv("CompetingPhases.csv")
# check chem limits the same:
_compare_chempot_dicts(reloaded_cpa.chempots, stable_cpa.chempots)
@@ -704,9 +907,9 @@ def test_to_csv(self):
assert self.ext_cpa.chempots["elemental_refs"] == self.parsed_ext_chempots["elemental_refs"]
# test correct sorting:
- self.ext_cpa.to_csv("competing_phases.csv", prune_polymorphs=True, sort_by_energy=True)
+ self.ext_cpa.to_csv("CompetingPhases.csv", prune_polymorphs=True, sort_by_energy=True)
reloaded_ext_cpa_energy_sorted = chemical_potentials.CompetingPhasesAnalyzer(self.stable_system)
- reloaded_ext_cpa_energy_sorted.from_csv("competing_phases.csv")
+ reloaded_ext_cpa_energy_sorted.from_csv("CompetingPhases.csv")
assert len(reloaded_ext_cpa.data) == 8 # polymorphs pruned
assert reloaded_ext_cpa_energy_sorted.data != reloaded_ext_cpa.data # different order
@@ -724,18 +927,17 @@ def test_from_csv_minimal(self):
"""
self.tearDown() # clear out previous csvs
cpa = chemical_potentials.CompetingPhasesAnalyzer(self.stable_system)
- path = self.path / "ZrO2"
- cpa.from_vaspruns(path=path, folder="relax")
+ cpa.from_vaspruns(path=self.zro2_path, folder="relax")
formation_energy_data = cpa._get_and_sort_formation_energy_data()
formation_energy_df = pd.DataFrame(formation_energy_data)
# drop all but the formula and energy_per_fu columns:
for i in ["DFT Energy (eV/fu)", "DFT Energy (eV/atom)"]:
minimal_formation_energy_df = formation_energy_df[["Formula", i]]
- minimal_formation_energy_df.to_csv("competing_phases.csv", index=False)
+ minimal_formation_energy_df.to_csv("CompetingPhases.csv", index=False)
reloaded_cpa = chemical_potentials.CompetingPhasesAnalyzer(self.stable_system)
- reloaded_cpa.from_csv("competing_phases.csv")
+ reloaded_cpa.from_csv("CompetingPhases.csv")
assert not cpa.formation_energy_df.round(4).equals(
reloaded_cpa.formation_energy_df.round(4)
) # no kpoints or raw energy, but should have formula, energy_per_fu, energy_per_atom,
@@ -765,10 +967,10 @@ def test_from_csv_minimal(self):
# test ValueError without energy_per_fu/energy_per_atom column:
too_minimal_formation_energy_df = formation_energy_df[["Formula"]]
- too_minimal_formation_energy_df.to_csv("competing_phases.csv", index=False)
+ too_minimal_formation_energy_df.to_csv("CompetingPhases.csv", index=False)
reloaded_cpa = chemical_potentials.CompetingPhasesAnalyzer(self.stable_system)
with pytest.raises(ValueError) as exc:
- reloaded_cpa.from_csv("competing_phases.csv")
+ reloaded_cpa.from_csv("CompetingPhases.csv")
assert "Supplied csv does not contain the minimal columns required" in str(exc.value)
def test_elements(self):
@@ -828,18 +1030,10 @@ def test_fe(self):
# lowest energy ZrO2:
assert np.isclose(formation_energy_df["Formation Energy (eV/fu)"][4], -10.975428440000002)
-
-class CombineExtrinsicTestCase(unittest.TestCase):
- def setUp(self) -> None:
- self.path = Path(__file__).parents[1].joinpath("examples/competing_phases")
- first = self.path / "zro2_la_chempots.json"
- second = self.path / "zro2_y_chempots.json"
- self.first = loadfn(first)
- self.second = loadfn(second)
- self.extrinsic_species = "Y"
-
def test_combine_extrinsic(self):
- d = chemical_potentials.combine_extrinsic(self.first, self.second, self.extrinsic_species)
+ d = chemical_potentials.combine_extrinsic(
+ self.parsed_ext_chempots, self.parsed_ext_y_chempots, "Y"
+ )
assert len(d["elemental_refs"].keys()) == 4
limits = list(d["limits"].keys())
assert limits[0].rsplit("-", 1)[1] == "Y2Zr2O7"
@@ -847,10 +1041,12 @@ def test_combine_extrinsic(self):
def test_combine_extrinsic_errors(self):
d = {"a": 1}
with pytest.raises(KeyError):
- chemical_potentials.combine_extrinsic(d, self.second, self.extrinsic_species)
+ chemical_potentials.combine_extrinsic(d, self.parsed_ext_y_chempots, "Y")
with pytest.raises(KeyError):
- chemical_potentials.combine_extrinsic(self.first, d, self.extrinsic_species)
+ chemical_potentials.combine_extrinsic(self.parsed_ext_chempots, d, "Y")
with pytest.raises(ValueError):
- chemical_potentials.combine_extrinsic(self.first, self.second, "R")
+ chemical_potentials.combine_extrinsic(
+ self.parsed_ext_chempots, self.parsed_ext_y_chempots, "R"
+ )
diff --git a/tests/test_thermodynamics.py b/tests/test_thermodynamics.py
index 6793edcd..e542f90b 100644
--- a/tests/test_thermodynamics.py
+++ b/tests/test_thermodynamics.py
@@ -172,7 +172,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, "competing_phases/mgo_chempots.json"))
+ cls.MgO_chempots = loadfn(os.path.join(cls.EXAMPLE_DIR, "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"))
@@ -416,6 +416,14 @@ def _set_and_check_dist_tol(self, dist_tol, defect_thermo, num_grouped_defects=0
defect_thermo.print_transition_levels()
defect_thermo.print_transition_levels(all=True)
+ def _clear_symmetry_degeneracy_info(self, defect_thermo):
+ for defect_entry in defect_thermo.defect_entries:
+ for key in list(defect_entry.calculation_metadata.keys()):
+ if "symmetry" in key:
+ del defect_entry.calculation_metadata[key]
+ for key in list(defect_entry.degeneracy_factors.keys()):
+ del defect_entry.degeneracy_factors[key]
+
def test_initialisation_and_attributes(self):
"""
Test initialisation and attributes of DefectsThermodynamics.
@@ -790,8 +798,37 @@ def test_get_transition_levels_CdTe(self):
assert list(tl_df.iloc[4]) == ["Int_Te_3_a", "None", np.inf, False, 0]
assert list(tl_df.iloc[5]) == ["Int_Te_3_b", "None", np.inf, False, 0]
- def test_get_symmetries_degeneracies_CdTe(self):
- sym_degen_df = self.CdTe_defect_thermo.get_symmetries_and_degeneracies()
+ def test_get_symmetries_degeneracies(self):
+ """
+ Test symmetry and degeneracy functions.
+
+ Also tests that the symmetry and degeneracy functions still work if the
+ symmetry and degeneracy metadata wasn't parsed earlier for any reason
+ (e.g. transferring from old doped versions, from pymatgen-analysis-
+ defects objects etc), with ``reset_thermo=True``.
+ """
+ for reset_thermo in [False, True]:
+ for check_func, thermo in [
+ (self._check_CdTe_symmetries_degeneracies, self.CdTe_defect_thermo),
+ (
+ self._check_MgO_symmetries_degeneracies,
+ loadfn(f"{module_path}/../examples/MgO/MgO_thermo.json.gz"),
+ ),
+ (self._check_YTOS_symmetries_degeneracies, self.YTOS_defect_thermo),
+ (self._check_Sb2O5_symmetries_degeneracies, self.Sb2O5_defect_thermo),
+ ]:
+ print(
+ f"Checking symmetries & degeneracies for {thermo.bulk_formula}, reset_thermo"
+ f"={reset_thermo}"
+ )
+ if reset_thermo:
+ self._clear_symmetry_degeneracy_info(thermo)
+ check_func(thermo)
+
+ self.setUp() # reset thermo objects on 2nd run (with ``reset_thermo=True``)
+
+ def _check_CdTe_symmetries_degeneracies(self, CdTe_defect_thermo: DefectThermodynamics):
+ sym_degen_df = CdTe_defect_thermo.get_symmetries_and_degeneracies()
print(sym_degen_df)
assert sym_degen_df.shape == (7, 8)
assert list(sym_degen_df.columns) == [
@@ -825,12 +862,11 @@ def test_get_symmetries_degeneracies_CdTe(self):
)
print(non_formatted_sym_degen_df) # for debugging
for i, row in enumerate(cdte_sym_degen_lists):
- row[1] = int(row[1])
+ row[1] = int(row[1]) # type: ignore
assert list(non_formatted_sym_degen_df.iloc[i]) == row
- def test_get_symmetries_degeneracies_MgO(self):
- MgO_thermo = loadfn(f"{module_path}/../examples/MgO/MgO_thermo.json.gz")
- sym_degen_df = MgO_thermo.get_symmetries_and_degeneracies()
+ def _check_MgO_symmetries_degeneracies(self, MgO_defect_thermo: DefectThermodynamics):
+ sym_degen_df = MgO_defect_thermo.get_symmetries_and_degeneracies()
# print(sym_degen_df)
assert sym_degen_df.shape == (5, 8)
assert list(sym_degen_df.columns) == [
@@ -857,47 +893,49 @@ def test_get_symmetries_degeneracies_MgO(self):
print(i, row)
assert list(sym_degen_df.iloc[i]) == row
- non_formatted_sym_degen_df = MgO_thermo.get_symmetries_and_degeneracies(skip_formatting=True)
- # print(non_formatted_sym_degen_df) # for debugging
+ non_formatted_sym_degen_df = MgO_defect_thermo.get_symmetries_and_degeneracies(
+ skip_formatting=True
+ )
+ print(non_formatted_sym_degen_df) # for debugging
for i, row in enumerate(mgo_sym_degen_lists):
- row[1] = int(row[1])
+ row[1] = int(row[1]) # type: ignore
assert list(non_formatted_sym_degen_df.iloc[i]) == row
- def test_get_symmetries_degeneracies_YTOS(self):
- sym_degen_df = self.YTOS_defect_thermo.get_symmetries_and_degeneracies()
+ def _check_YTOS_symmetries_degeneracies(self, YTOS_defect_thermo: DefectThermodynamics):
+ sym_degen_df = YTOS_defect_thermo.get_symmetries_and_degeneracies()
# hardcoded tests to ensure symmetry determination working as expected:
assert any(
list(sym_degen_df.iloc[i]) == ["Int_F", "-1", "Cs", "C4v", 0.25, 1, 0.25, 4.0]
for i in range(sym_degen_df.shape[0])
)
- sym_degen_df = self.YTOS_defect_thermo.get_symmetries_and_degeneracies(symprec=0.1)
+ sym_degen_df = YTOS_defect_thermo.get_symmetries_and_degeneracies(symprec=0.1)
assert any(
list(sym_degen_df.iloc[i]) == ["Int_F", "-1", "Cs", "C4v", 0.25, 1, 0.25, 4.0]
for i in range(sym_degen_df.shape[0])
)
- sym_degen_df = self.YTOS_defect_thermo.get_symmetries_and_degeneracies(symprec=0.01)
+ sym_degen_df = YTOS_defect_thermo.get_symmetries_and_degeneracies(symprec=0.01)
assert any(
list(sym_degen_df.iloc[i]) == ["Int_F", "-1", "Cs", "Cs", 1.0, 1, 1.0, 4.0]
for i in range(sym_degen_df.shape[0])
)
- def test_get_symmetries_degeneracies_Sb2O5(self):
- sym_degen_df = self.Sb2O5_defect_thermo.get_symmetries_and_degeneracies()
+ def _check_Sb2O5_symmetries_degeneracies(self, Sb2O5_defect_thermo: DefectThermodynamics):
+ sym_degen_df = Sb2O5_defect_thermo.get_symmetries_and_degeneracies()
# hardcoded tests to ensure symmetry determination working as expected:
assert any(
list(sym_degen_df.iloc[i]) == ["inter_2_Sb", "0", "C1", "C1", 1.0, 2, 2.0, 4.0]
for i in range(sym_degen_df.shape[0])
)
- sym_degen_df = self.Sb2O5_defect_thermo.get_symmetries_and_degeneracies(symprec=0.1)
+ sym_degen_df = Sb2O5_defect_thermo.get_symmetries_and_degeneracies(symprec=0.1)
assert any(
list(sym_degen_df.iloc[i]) == ["inter_2_Sb", "0", "C1", "C1", 1.0, 2, 2.0, 4.0]
for i in range(sym_degen_df.shape[0])
)
- sym_degen_df = self.Sb2O5_defect_thermo.get_symmetries_and_degeneracies(symprec=0.2)
+ sym_degen_df = Sb2O5_defect_thermo.get_symmetries_and_degeneracies(symprec=0.2)
assert any(
list(sym_degen_df.iloc[i]) == ["inter_2_Sb", "0", "C1", "Ci", 0.5, 2, 1.0, 4.0]
for i in range(sym_degen_df.shape[0])
@@ -1763,34 +1801,29 @@ def test_CdTe_all_intrinsic_defects(self):
)
# test adjusting symprec:
- if defect_entry.name == "vac_1_Cd_0":
+ if defect_entry.name in [
+ "vac_1_Cd_0",
+ "as_1_Te_on_Cd_0",
+ "Int_Te_3_unperturbed_0",
+ "as_2_Te_on_Cd_C3v_metastable_1",
+ ]:
assert point_symmetry(defect_entry.defect_supercell, symprec=0.01) == "Cs"
- elif defect_entry.name == "vac_1_Cd_Td_0":
+ elif defect_entry.name in [
+ "vac_1_Cd_Td_0",
+ "as_1_Cd_on_Te_2",
+ ]:
assert point_symmetry(defect_entry.defect_supercell, symprec=0.01) == "D2d"
- elif defect_entry.name == "vac_2_Te_orig_metastable_-1":
- assert point_symmetry(defect_entry.defect_supercell, symprec=0.01) == "C1"
elif defect_entry.name == "vac_2_Te_orig_non_JT_distorted_0":
assert point_symmetry(defect_entry.defect_supercell, symprec=0.01) == "C3v"
- elif defect_entry.name == "vac_2_Te_shaken_-2":
- assert point_symmetry(defect_entry.defect_supercell, symprec=0.01) == "C1"
- elif defect_entry.name == "as_1_Cd_on_Te_2":
- assert point_symmetry(defect_entry.defect_supercell, symprec=0.01) == "D2d"
- elif (
- defect_entry.name == "as_1_Cd_on_Te_1" or defect_entry.name == "as_2_Cd_on_Te_metastable_0"
- ):
- assert point_symmetry(defect_entry.defect_supercell, symprec=0.01) == "C1"
- elif defect_entry.name == "as_1_Te_on_Cd_0":
- assert point_symmetry(defect_entry.defect_supercell, symprec=0.01) == "Cs"
- elif defect_entry.name == "as_1_Te_on_Cd_-1":
- assert point_symmetry(defect_entry.defect_supercell, symprec=0.01) == "C1"
- elif defect_entry.name == "as_2_Te_on_Cd_C3v_metastable_1":
- assert point_symmetry(defect_entry.defect_supercell, symprec=0.01) == "Cs"
- elif (
- defect_entry.name == "Int_Te_3_C3v_meta_2" or defect_entry.name == "Int_Te_3_unperturbed_1"
- ):
+ elif defect_entry.name in [
+ "as_1_Cd_on_Te_1",
+ "as_2_Cd_on_Te_metastable_0",
+ "vac_2_Te_orig_metastable_-1",
+ "vac_2_Te_shaken_-2",
+ "Int_Te_3_C3v_meta_2",
+ "Int_Te_3_unperturbed_1",
+ ]:
assert point_symmetry(defect_entry.defect_supercell, symprec=0.01) == "C1"
- elif defect_entry.name == "Int_Te_3_unperturbed_0":
- assert point_symmetry(defect_entry.defect_supercell, symprec=0.01) == "Cs"
else:
assert (
point_symmetry(defect_entry.defect_supercell, symprec=0.01)
@@ -1817,13 +1850,8 @@ def test_CdTe_all_intrinsic_defects(self):
assert list(sym_degen_df.iloc[i]) == row
# delete symmetry info to force re-parsing, to test symmetry/degeneracy functions
- thermo_wout_symm_meta = deepcopy(cdte_defect_thermo)
- for defect_entry in thermo_wout_symm_meta.defect_entries:
- for key in list(defect_entry.calculation_metadata.keys()):
- if "symmetry" in key or "degeneracy" in key:
- del defect_entry.calculation_metadata[key]
-
- sym_degen_df = thermo_wout_symm_meta.get_symmetries_and_degeneracies()
+ self._clear_symmetry_degeneracy_info(cdte_defect_thermo)
+ sym_degen_df = cdte_defect_thermo.get_symmetries_and_degeneracies()
for i, row in enumerate(cdte_sym_degen_lists):
print(i, row)
if row[0] == "Int_Te_3_C3v_meta":
@@ -1985,15 +2013,6 @@ def test_Sb2O5_formation_energies(self):
os.remove("test.csv")
- def test_symmetry_degeneracy_unparsed(self):
- """
- Test that the symmetry and degeneracy functions still work if the
- symmetry and degeneracy metadata wasn't parsed earlier for any reason
- (e.g. transferring from old doped versions, from pymatgen-analysis-
- defects objects etc).
- """
- # TODO
-
def test_incompatible_chempots_warning(self):
"""
Test that we get the expected warnings when we provide incompatible