diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index f0a2973e..72da65e7 100755 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -16,7 +16,7 @@ jobs: max-parallel: 1 matrix: - python-version: ['3.11'] + python-version: ['3.12'] name: Python ${{ matrix.python-version }} Test Pop diff --git a/.github/workflows/pip_install_test.yml b/.github/workflows/pip_install_test.yml index 795e827b..1344ceeb 100755 --- a/.github/workflows/pip_install_test.yml +++ b/.github/workflows/pip_install_test.yml @@ -18,12 +18,7 @@ jobs: matrix: os: [ ubuntu-latest, macos-14 ] - python-version: [ '3.9', '3.10', '3.11' ] - exclude: - - os: macos-14 - python-version: '3.9' # Exclude Python 3.9 on macOS, not supported for macOS-14 tests - # macos-latest should be 14 according to link below, but currently doesn't? - # https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners/about-github-hosted-runners#standard-github-hosted-runners-for-public-repositories + python-version: [ '3.10', '3.11', '3.12' ] runs-on: ${{matrix.os}} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b6e8b6f2..e3a00393 100755 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -20,7 +20,7 @@ jobs: - uses: actions/setup-python@v5 with: - python-version: "3.10" + python-version: "3.12" - name: Install dependencies run: | diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index f8c64d4d..94553a35 100755 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -14,12 +14,7 @@ jobs: matrix: os: [ ubuntu-latest, macos-14 ] - python-version: [ '3.9', '3.10', '3.11' ] - exclude: - - os: macos-14 - python-version: '3.9' # Exclude Python 3.9 on macOS, not supported for macOS-14 tests - # macos-latest should be 14 according to link below, but currently doesn't? - # https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners/about-github-hosted-runners#standard-github-hosted-runners-for-public-repositories + python-version: [ '3.10', '3.11', '3.12' ] runs-on: ${{matrix.os}} steps: diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 19af9179..dcef1afb 100755 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,6 +1,6 @@ # See https://pre-commit.com for more information # See https://pre-commit.com/hooks.html for more hooks -exclude: ^(docs|tests|SnB_input_files|.github|shakenbreak/scripts|CITATION*|MANIFEST*) +exclude: ^(docs|tests|shakenbreak/SnB_input_files|.github|CITATION*|MANIFEST*) repos: # Lint and format, isort, docstrings... - repo: https://github.com/charliermarsh/ruff-pre-commit diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 90eeb662..94bdacbc 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,10 +1,32 @@ Change Log ========== +v3.4.0 +---------- +- Major efficiency updates: + - Uses ``_scan_sm_stol_till_match`` and turbo-charged ``StructureMatcher`` methods from ``doped`` + ``v3``, speeding up structure matching (e.g. in ``snb-regenerate`` for identifying distinct defect + geometries) by >~3 orders of magnitude. + - Uses caching in atomic displacement calculations (for ``"disp"``/``"max_dist"`` metrics) + - Use efficient Voronoi analyzer from ``doped`` ``v3`` for multiplicity determination. + - More efficient (and cleaner) plotting with many defects/distortions +- Add ``Dimer`` to default distortions grid output for vacancies, following discussions and testing for + cation vacancies in oxides. +- Miscellaneous: + - All ``snb-xxx`` functions now auto-detect if running within a defect folder or in a top-level + directory (i.e. auto ``--all`` behaviour). + - Handling of gzipped ``OUTCAR.gz`` files for energy parsing. + - For (rare) cases of degenerate choices of NNs to distort in terms of distance, but non-degenerate + `combinations` (e.g. distorting 2 NNs of a square coordination, could be cis or trans choices), + the choice is made deterministically; choosing the combination with the shortest distances between + distorted NNs (i.e. the cis choice). + - Greater verbosity control + - Some code cleanup and formatting, and robustness updates + v3.3.6 ---------- -- Add `py.typed` to properly detect type hints by @Andrew-S-Rosen -- `snb-run` updates to improve efficiency +- Add ``py.typed`` to properly detect type hints by @Andrew-S-Rosen +- ``snb-run`` updates to improve efficiency v3.3.5 ---------- @@ -36,9 +58,9 @@ v3.3.1 ---------- - ``distortion_metadata.json`` for each defect now saved to the individual defect folders (as well as the combined total distortion metadata in the top level folder) – more likely to be retained by the user - when ``scp``ing around etc. + when ``scp``\ing around etc. - Minor updates: - - Refactor ``_format_defect_name`` to ``format_defect_name`` from `doped` (now a public function) + - Refactor ``_format_defect_name`` to ``format_defect_name`` from ``doped`` (now a public function) - Update ``snb-run`` to avoid possible 'file exists' warning - Update tutorials/notebooks to specify ``vasp_nkred_std`` to streamline workflow - Remove unnecessary ``tutorials`` folder with duplicate tutorial notebook (to reduce workload). @@ -56,26 +78,26 @@ v3.3.1 v3.3.0 ---------- - Add Dimer distortion as a targeted distortion for dimer reconstructions. It pushes two of the defect NN - to a distance of 2 A. -- Add option `distorted_atoms` to the `Distortion` class to allow users to specify the indexes of the + to a distance of 2 Å. +- Add option ``distorted_atoms`` to the ``Distortion`` class to allow users to specify the indexes of the atoms to distort. - Update tests to check the new functionality. -- Update `get_homoionic_bonds` to detect homoionic bonds between different cations/anions (rather than +- Update ``get_homoionic_bonds`` to detect homoionic bonds between different cations/anions (rather than just bonds between the same element) -- Fix issue with `snb-generate` when no defect name was specified (by adding `unrelaxed=True` when - calling `get_defect_name_from_entry`) -- Update functions that read `OUTCARs` to be able to read `OUTCAR.gz` files too +- Fix issue with ``snb-generate`` when no defect name was specified (by adding ``unrelaxed=True`` when + calling ``get_defect_name_from_entry``) +- Update functions that read ``OUTCARs`` to be able to read ``OUTCAR.gz`` files too - Update energies parsing to still work when all distortions are high energy, but warn - the user about this (i.e. only `Unperturbed`) -- Update `snb-run` to add early-on detection of distortions that are stuck in high energy basins and + the user about this (i.e. only ``Unperturbed``) +- Update ``snb-run`` to add early-on detection of distortions that are stuck in high energy basins and rename them to "High_Energy" to avoid continuing their relaxation - Miscellaneous efficiency improvements and bug fixes v3.2.3 ---------- -- Ensure the sorted `pymatgen` `Structure` is created for the VASP input (fixes a rare bug in `v3.2.1` - and `v3.2.2` where for certain structures the order of elements in the POSCAR was not properly sorted, - which is usually fine, but messed with the `ROPT` `INCAR` setting). +- Ensure the sorted ``pymatgen`` ``Structure`` is created for the VASP input (fixes a rare bug in ``v3.2.1`` + and ``v3.2.2`` where for certain structures the order of elements in the POSCAR was not properly sorted, + which is usually fine, but messed with the ``ROPT`` ``INCAR`` setting). - Plotting format updates (make legend frame more transparent to make datapoints behind it easier to see). - Update tests - Update docs (note about handling AFM systems) @@ -89,78 +111,78 @@ v3.2.2 v3.2.1 ---------- - Update CLI config handling. -- Remove `shakenbreak.vasp` module and use `doped` VASP file writing functions directly. -- Add INCAR/KPOINTS/POTCAR file writing tests. `test_local.py` now deleted as these tests are now - automatically run in `test_input.py`/`test_cli.py` if `POTCAR`s available. +- Remove ``shakenbreak.vasp`` module and use ``doped`` VASP file writing functions directly. +- Add INCAR/KPOINTS/POTCAR file writing tests. ``test_local.py`` now deleted as these tests are now + automatically run in ``test_input.py``/``test_cli.py`` if ``POTCAR``\s available. v3.2.0 ---------- -- Following the major release of `doped` `v2.0`, now compatible with the new `pymatgen` - defects code (`pymatgen>2022.7.25`), this update: - - Allows input of `doped` `DefectsGenerator` object to `Distortions` +- Following the major release of ``doped`` ``v2.0``, now compatible with the new ``pymatgen`` + defects code (``pymatgen>2022.7.25``), this update: + - Allows input of ``doped`` ``DefectsGenerator`` object to ``Distortions`` - Updates the tutorials to reflect the current recommended workflow of generating defects - with `doped` and then applying `ShakeNBreak`, no longer requiring separate virtual environments 🎉 + with ``doped`` and then applying ``ShakeNBreak``, no longer requiring separate virtual environments 🎉 v3.1.0 ---------- -- Update dependencies, as `hiphive=1.2` has been released, making `ShakeNBreak` compatible with - `python=3.11`🎉 +- Update dependencies, as ``hiphive=1.2`` has been released, making ``ShakeNBreak`` compatible with + ``python=3.11`` 🎉 v3.0.0 ---------- - Switch to semantic versioning - Update rattling functions to handle primitive bulk materials as well as supercells. -- Add check to `snb-run` if there are multiple `OUTCAR`s present with one or less ionic steps, and if +- Add check to ``snb-run`` if there are multiple ``OUTCAR``\s present with one or less ionic steps, and if this is also the case for the current run -> warn the user. - Small fixes, formatting and docs updates. v23.06.23 ---------- -- Add `snb-mag` function, and automatically check the magnetisation from `ISPIN = 2` `OUTCAR` files when continuing - relaxations with `snb-run` (and change to `ISPIN = 1` if magnetisation is negligible). +- Add ``snb-mag`` function, and automatically check the magnetisation from ``ISPIN = 2`` ``OUTCAR`` files when continuing + relaxations with ``snb-run`` (and change to ``ISPIN = 1`` if magnetisation is negligible). - Update handling of minimum distances and oxidation states, to deal with single-atom primitive unit cells and - systems where `pymatgen` cannot guess the oxidation state (e.g. single-elements, intermetallics etc). + systems where ``pymatgen`` cannot guess the oxidation state (e.g. single-elements, intermetallics etc). - Docs updates v23.06.03 ---------- -- Make parsing of `DefectEntry`s more robust. -- Update dependencies (now supporting `python=3.10` due to `numba` updates) -- Refactor `CITATION.cff` to `CITATIONS.md` +- Make parsing of ``DefectEntry``\s more robust. +- Update dependencies (now supporting ``python=3.10`` due to ``numba`` updates) +- Refactor ``CITATION.cff`` to ``CITATIONS.md`` - Update docs, formatting and cleanup. v23.04.27 ---------- -- Update `numpy` requirement to `numpy>=1.21.2` to fix `numpy.typing.NDArray` import error. +- Update ``numpy`` requirement to ``numpy>=1.21.2`` to fix ``numpy.typing.NDArray`` import error. - Add News & Views free-to-read link to docs v23.04.26 ---------- -- Updates to `snb-run` (copy `job` from parent directory if present, switch to `ALGO = All` if poor electronic convergence...) -- Make `format_defect_name()` more robust -- Update docs and `README.md` with published article links +- Updates to ``snb-run`` (copy ``job`` from parent directory if present, switch to ``ALGO = All`` if poor electronic convergence...) +- Make ``format_defect_name()`` more robust +- Update docs and ``README.md`` with published article links - Formatting and cleanup - Make oxidation state guessing more efficient (previously was causing bottleneck with large cells) - Fix oxidation state guessing for rare elements -- Add note to `Tips` docs page about bulk phase transformation behaviour -- Refactor to `json` rather than `pickle` +- Add note to ``Tips`` docs page about bulk phase transformation behaviour +- Refactor to ``json`` rather than ``pickle`` v23.02.08 ---------- -- Change `numpy` version requirement in `docs/requirements.txt` to `numpy>=1.21` to work with `numpy.typing.NDArray`. +- Change ``numpy`` version requirement in ``docs/requirements.txt`` to ``numpy>=1.21`` to work with ``numpy.typing.NDArray``. v23.02.02 ---------- - Refactor Distortions() class to take in DefectEntry objects as input, rather than Defect objects, to be -compatible with `pymatgen-analysis-defects`. + compatible with ``pymatgen-analysis-defects``. - Fix ticks and ticklabels in plots v23.01.25 -------- -- Specify `pandas` version in requirements.txt to equal or higher than 1.1.0 -- Refactor `snb-regenerate` to execute when no arguments are specified (rather than showing help message) +- Specify ``pandas`` version in requirements.txt to equal or higher than 1.1.0 +- Refactor ``snb-regenerate`` to execute when no arguments are specified (rather than showing help message) v23.01.7 -------- @@ -183,7 +205,6 @@ v22.12.1 v22.11.29 -------- -Main changes: - Add example notebook showing how to generate interstitials and apply SnB to them. - Fix typo in example notebook and docs. - Add comment about font installation to Installation guide. @@ -205,16 +226,14 @@ Docs tutorial update. v22.11.17 -------- -Main changes: - -- Refactor :code:`Distortions()` to a list or simple-format dict of :code:`Defect` objects as input. - Same for :code:`Distortions.from_structures()` -- Update defect naming to :code:`{Defect.name}_s{Defect.defect_site_index}` for vacancies/substitutions and - :code:`{Defect.name}_m{Defect.multiplicity}` for interstitials. Append "a", "b", "c" etc in cases of inequivalent +- Refactor ``Distortions()`` to a list or simple-format dict of ``Defect`` objects as input. + Same for ``Distortions.from_structures()`` +- Update defect naming to ``{Defect.name}_s{Defect.defect_site_index}`` for vacancies/substitutions and + ``{Defect.name}_m{Defect.multiplicity}`` for interstitials. Append "a", "b", "c" etc in cases of inequivalent defects -- Make :code:`ShakeNBreak` compatible with most recent :code:`pymatgen` and :code:`pymatgen-analysis-defects` packages. +- Make ``ShakeNBreak`` compatible with most recent ``pymatgen`` and ``pymatgen-analysis-defects`` packages. - Update legend format in plots and site index/multiplicity labelling, make default format png. -- Update default charge state setting to match :code:`pymatgen-analysis-defects` oxi state + padding approach. +- Update default charge state setting to match ``pymatgen-analysis-defects`` oxi state + padding approach. - A lot of additional warning and error catches. - Miscellaneous warnings and docs updates. @@ -222,61 +241,51 @@ Main changes: v22.11.7 -------- -Main changes: - -- Refactor ShakeNBreak to make it compatible with `pymatgen>=2022.8.23`. Now `Distortions` takes in - `pymatgen.analysis.defects.core.Defect` objects. -- Add `Distortions.from_dict()` and `Distortions.from_structures()` to generate defect distortions from a +- Refactor ShakeNBreak to make it compatible with ``pymatgen>=2022.8.23``. Now ``Distortions`` takes in + ``pymatgen.analysis.defects.core.Defect`` objects. +- Add ``Distortions.from_dict()`` and ``Distortions.from_structures()`` to generate defect distortions from a dictionary of defects (in doped format) or from a list of defect structures, respectively. v22.11.1 -------- -Main changes: - -- Update rattling procedure; :code:`stdev` be automatically set to 10% bulk bond length and :code:`seed` alternated for different +- Update rattling procedure; ``stdev`` be automatically set to 10% bulk bond length and ``seed`` alternated for different distortions (set to 100*distortion_factor) to avoid rare 'stuck rattle' occurrences. -- Refactor :code:`pickle` usages to :code:`JSON` serialisation to be more robust to package (i.e. pymatgen) updates. -- Update :code:`snb-regenerate` to be more robust, can be continually rerun without generating duplicate calculations. -- Update :code:`snb-run` to consider calculations with >50 ionic steps and <2 meV energy change as converged. +- Refactor ``pickle`` usages to ``JSON`` serialisation to be more robust to package (i.e. pymatgen) updates. +- Update ``snb-regenerate`` to be more robust, can be continually rerun without generating duplicate calculations. +- Update ``snb-run`` to consider calculations with >50 ionic steps and <2 meV energy change as converged. - Minor changes, efficiency improvements and bug fixes. v22.10.14 -------- -Just bumping version number to test updated GH Actions pip-install-test workflow. +Just bumping version number to test updated GH Actions ``pip-install-test`` workflow. v22.10.13 -------- -Main changes: - - Updated defect name handling to work for all conventions -- More robust `snb-generate` and plotting behaviour +- More robust ``snb-generate`` and plotting behaviour - Add CLI summary GIF to docs and README -- Updated `snb-run` behaviour to catch high-energies and forces error to improve efficiency +- Updated ``snb-run`` behaviour to catch high-energies and forces error to improve efficiency - Many miscellaneous tests and fixes - Docs updates v22.9.21 -------- -Main changes: - -- Fonts now included in `package_data` so can be installed with `pip` from `PyPI` -- Refactoring `distortion_plots` plot saving to saving to defect directories, and preventing overwriting of previous plots +- Fonts now included in ``package_data`` so can be installed with ``pip`` from ``PyPI`` +- Refactoring ``distortion_plots`` plot saving to saving to defect directories, and preventing overwriting of previous plots - Miscellaneous tests and fixes - Add summary GIF to docs and README - Handling for partial oxidation state input -- Setting `EDIFFG = -0.01` and `local_rattle = False` as default +- Setting ``EDIFFG = -0.01`` and ``local_rattle = False`` as default v22.9.2 -------- -Main changes: - - Update CLI commands (snb-parse, analyse, plot and groundstate can all now be run with no arguments within a defect folder) - Update custom font - Update groundstate() tests @@ -286,8 +295,6 @@ Main changes: v22.9.1 -------- -Main changes: - - Test for pip install - Automatic release and upload to pypi - Add ShakeNBreak custom font, and automatise its installation @@ -297,8 +304,6 @@ Main changes: v1.0.1 ------ -Main changes: - - Docs formatting - Update pymatgen version to v2022.7.25, while refactoring to be compatible with v2022.8.23 takes place. diff --git a/README.md b/README.md index db69515e..c64ebbc2 100644 --- a/README.md +++ b/README.md @@ -31,19 +31,20 @@ The code currently supports `VASP`, `CP2K`, `Quantum-Espresso`, `CASTEP` & `FHI- - News & Views: Mannodi-Kanakkithodi, A. [The Devil is in the Defects](https://doi.org/10.1038/s41567-023-02049-9), _Nature Physics_ **2023** ([Free-to-read link](https://t.co/EetpnRgjzh)) ## Installation -`ShakeNBreak` can be installed using `pip`: +`ShakeNBreak` can be installed using `conda`: ```bash - pip install shakenbreak +conda install -c conda-forge shakenbreak ``` - -Alternatively if needed, it can also be installed from `conda` with: +or `pip`: ```bash - conda install -c conda-forge shakenbreak +pip install shakenbreak ``` +See the [Installation docs](https://shakenbreak.readthedocs.io/en/latest/Installation.html) if you encounter any issues (e.g. known issue with `phonopy` `CMake` build). + If using `VASP`, in order for `ShakeNBreak` to automatically generate the pseudopotential input files (`POTCAR`s), your local `VASP` pseudopotential directory must be set in the `pymatgen` configuration file `$HOME/.pmgrc.yaml` as follows: ```bash - PMG_VASP_PSP_DIR: +PMG_VASP_PSP_DIR: ``` Within your `VASP` pseudopotential top directory, you should have a folder named `POT_GGA_PAW_PBE` which contains the `POTCAR.X(.gz)` files (in this case for PBE `POTCAR`s). Please refer to the [`doped` Installation docs](https://doped.readthedocs.io/en/latest/Installation.html) if you have @@ -56,15 +57,15 @@ For development work, ShakeNBreak can also be installed from a copy of the sourc 1. Download `ShakeNBreak` source code using the command: ```bash - git clone https://github.com/SMTG-Bham/ShakeNBreak +git clone https://github.com/SMTG-Bham/ShakeNBreak ``` 2. Navigate to root directory: ```bash - cd ShakeNBreak +cd ShakeNBreak ``` 3. Install the code, using the command: ```bash - pip install -e . +pip install -e . ``` This command tries to obtain the required packages and their dependencies and install them automatically. @@ -128,17 +129,23 @@ Automatic testing is run on the master and develop branches using Github Actions ## Studies using `ShakeNBreak` +- Y. Fu & H. Lohan et al. **_Factors Enabling Delocalized Charge-Carriers in Pnictogen-Based +Solar Absorbers: In-depth Investigation into CuSbSe2_** [_Nature Communications_](https://doi.org/10.1038/s41467-024-55254-2) 2025 +- S. R. Kavanagh **_Identifying Split Vacancies with Foundation Models and Electrostatics_** [_arXiv_](https://doi.org/10.48550/arXiv.2412.19330) 2025 +- S. R. Kavanagh et al. **_Intrinsic point defect tolerance in selenium for indoor and tandem photovoltaics_** [_ChemRxiv_](https://doi.org/10.26434/chemrxiv-2024-91h02) 2025 +- J. Hu et al. **_Enabling ionic transport in Li3AlP2 the roles of defects and disorder_** [_Journal of Materials Chemistry A_](https://doi.org/10.1039/D4TA04347B) 2025 +- X. Zhao et al. **_Trace Yb doping-induced cationic vacancy clusters enhance thermoelectrics in p-type PbTe_** [_Applied Physics Letters_](https://doi.org/10.1063/5.0249058) 2025 +- Z. Cai & C. Ma **_Origin of oxygen partial pressure-dependent conductivity in SrTiO3_** [_Applied Physics Letters_](doi.org/10.1063/5.0245820) 2025 +- W. D. Neilson et al. **_Oxygen Potential, Uranium Diffusion, and Defect Chemistry in UO2±x: A Density Functional Theory Study_** [_Journal of Physical Chemistry C_](https://doi.org/10.1021/acs.jpcc.4c06580) 2024 +- X. Wang et al. **_Sulfur Vacancies Limit the Open-circuit Voltage of Sb2S3 Solar Cells_** [_ACS Energy Letters_](https://doi.org/10.1021/acsenergylett.4c02722) 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 -- 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 -- J. Hu et al. **_Enabling ionic transport in Li3AlP2 the roles of defects and disorder_** [_ChemRxiv_](https://doi.org/10.26434/chemrxiv-2024-3s0kh) 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 - X. Wang et al. **_Upper efficiency limit of Sb2Se3 solar cells_** [_Joule_](https://doi.org/10.1016/j.joule.2024.05.004) 2024 - I. Mosquera-Lois et al. **_Machine-learning structural reconstructions for accelerated point defect calculations_** [_npj Computational Materials_](https://doi.org/10.1038/s41524-024-01303-9) 2024 - S. R. Kavanagh et al. **_doped: Python toolkit for robust and repeatable charged defect supercell calculations_** [_Journal of Open Source Software_](https://doi.org/10.21105/joss.06433) 2024 - K. Li et al. **_Computational Prediction of an Antimony-based n-type Transparent Conducting Oxide: F-doped Sb2O5_** [_Chemistry of Materials_](https://doi.org/10.1021/acs.chemmater.3c03257) 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 - X. Wang et al. **_Four-electron negative-U vacancy defects in antimony selenide_** [_Physical Review B_](https://journals.aps.org/prb/abstract/10.1103/PhysRevB.108.134102) 2023 - Y. Kumagai et al. **_Alkali Mono-Pnictides: A New Class of Photovoltaic Materials by Element Mutation_** [_PRX Energy_](http://dx.doi.org/10.1103/PRXEnergy.2.043002) 2023 - A. T. J. Nicolson et al. **_Cu2SiSe3 as a promising solar absorber: harnessing cation dissimilarity to avoid killer antisites_** [_Journal of Materials Chemistry A_](https://doi.org/10.1039/D3TA02429F) 2023 diff --git a/docs/Analysis.rst b/docs/Analysis.rst index 3bcdd048..b3021995 100644 --- a/docs/Analysis.rst +++ b/docs/Analysis.rst @@ -24,15 +24,11 @@ Where ``defects_folder`` is the path to the top level directory containing the d different from the current directory. Instead of a single defect, we can parse the results for **all** defects present -in a given/current directory using the ``-a``/``--all`` flag: - -.. code:: bash - - $ snb-parse -a - -This generates a ``yaml`` file for each defect, mapping each distortion to the -final energy of the relaxed structures (in eV). These files are saved to the -corresponding defect directory (e.g. ``defects_folder/v_Cd_0/v_Cd_0.yaml``). +in a given/current directory by running ``snb-parse`` from the top-level directory +containing our defect folders. This generates a ``yaml`` file for each defect, +mapping each distortion to the final energy of the relaxed structures (in eV). +These files are saved to the corresponding defect directory +(e.g. ``defects_folder/v_Cd_0/v_Cd_0.yaml``). .. code:: yaml @@ -63,12 +59,8 @@ was used (if not :code:`VASP`) and which reference structure to use (default = ` $ snb-analyse --defect v_Cd_0 --code FHI-aims --path defects_folder --ref_struct -0.4 --verbose -Again if we want to analyse the results for **all** defects present in a given/current directory, we can use the -``-a``/``--all`` flag: - -.. code:: bash - - $ snb-analyse -a +Again if we want to analyse the results for **all** defects present in a given/current directory, +we can just run ``snb-analyse`` from the top-level directory containing the defect folders. .. NOTE:: Further analysis tools are provided through the python API. These are documented in @@ -114,14 +106,10 @@ was used (if not :code:`VASP`) and other options (what ``metric`` to use for col .. code:: bash - $ snb-plot --defect v_Cd_0 --code FHI-aims --path defects_folder --colorbar -0.4 --metric disp --units meV --verbose - -Again if we want to plot the results for **all** defects present in a given/current directory, we can use the -``-a``/``--all`` flag: - -.. code:: bash + $ snb-plot --defect v_Cd_0 --code FHI-aims --path defects_folder --colorbar --ref_struct -0.4 --metric disp --units meV --verbose - $ snb-plot -a +Again if we want to plot the results for **all** defects present in a given/current directory, we can +just run ``snb-plot`` from the top-level directory containing the defect folders. .. TIP:: See ``snb-plot -h`` or `the CLI docs `_ diff --git a/docs/Generation.rst b/docs/Generation.rst index 3e0943e8..dce0e049 100644 --- a/docs/Generation.rst +++ b/docs/Generation.rst @@ -32,7 +32,7 @@ we'll get a warning and we'll need to specify the defect site with the ``--defec .. NOTE:: To specify additional distortion parameters, we can use a - `config.yaml `_ + `config.yaml `_ file like the one below and use the ``--config`` flag to specify its path (i.e. ``snb-generate --config ./my_config.yaml``). A detailed description of all the parameters is available in the Python API section (:ref:`shakenbreak.input.Distortions class `). @@ -122,7 +122,7 @@ the following directory structures will be parsed correctly: .. NOTE:: To specify the charge state range for each defect, as well as other optional arguments, we can use a - `config.yaml `_ file + `config.yaml `_ file like the one below. A detailed description of all the parameters is available in the Python API section (:ref:`shakenbreak.input.Distortions class `). @@ -198,12 +198,16 @@ Submitting the geometry optimisations ======================================= Once the input files have been generated, we can submit the geometry optimisations -for a single or all defects using the ``snb-run`` command. -To submit all defects present in the current directory: +for a single or all defects using the ``snb-run`` command: .. code:: bash - $ snb-run -a + $ snb-run + +If ``snb-run`` is run in the top-level directory (i.e. the directory containing the defect folders), +it will loop through all defect folders present and attempt to submit the distortion calculations for +each. Alternatively, it can be run within a single defect folder to just submit the calculations for +that defect. This assumes the ``SGE`` queuing system (i.e. ``qsub`` = job submission command) for the HPC and a job script name of ``job`` by default, but again can be controlled with the ``--submit-command`` and ``--job-script`` flags @@ -212,14 +216,7 @@ script file name of ``my_job_script.sh``, we would use: .. code:: bash - $ snb-run --submit-command sbatch --job-script my_job_script.sh --all - - -To submit a single defect, we can simply run the command :code:`snb-run` within the defect folder: - -.. code:: bash - - $ snb-run + $ snb-run --submit-command sbatch --job-script my_job_script.sh ``snb-run`` can be used to submit the initial geometry optimisation calculations, as well as automatically continuing and resubmitting calculations that have not yet converged (and handle calculations which have failed) as discussed in diff --git a/docs/Installation.rst b/docs/Installation.rst index 15bdaa51..6b4df682 100644 --- a/docs/Installation.rst +++ b/docs/Installation.rst @@ -1,17 +1,27 @@ Installation ===================== -ShakeNBreak can be installed using ``pip``: +``ShakeNBreak`` can be installed using ``conda``: .. code:: bash - pip install --user shakenbreak + conda install -c conda-forge shakenbreak -Alternatively if needed, it can also be installed from ``conda`` with: +or ``pip``: .. code:: bash - conda install -c conda-forge shakenbreak + pip install shakenbreak + +.. NOTE:: + Due to a recent change in the python build procedure for ``phonopy`` (an indirect dependency of + ``ShakeNBreak``), in version ``2.26``, ``pip install shakenbreak`` can fail on some older systems (with + older versions of ``gcc``). This can be resolved by either (1) installing ``ShakeNBreak`` from ``conda`` + (as above), (2) installing ``phonopy`` from ``conda`` (see + `here `__) and then ``ShakeNBreak`` with ``pip``, + (3) installing ``phonopy<=2.25`` (``pip install phonopy<=2.25``) and then ``ShakeNBreak`` with ``pip``, + or (4) upgrading your system's ``gcc`` to a more recent version if possible. + If using ``VASP``, in order for ``ShakeNBreak`` to automatically generate the pseudopotential input files (``POTCARs``), your local ``VASP`` pseudopotential directory must be set in the ``pymatgen`` diff --git a/docs/Tips.rst b/docs/Tips.rst index 50627446..f42b8c62 100644 --- a/docs/Tips.rst +++ b/docs/Tips.rst @@ -218,9 +218,14 @@ message about the origin of the problem, it is likely to be an issue with your v pip install pymatgen pymatgen-analysis-defects monty --upgrade pip install ShakeNBreak doped --upgrade -If this does not solve your issue, please check the specific cases noted below. If your issue still isn't -solved, then please contact the developers through the ``GitHub`` -`Issues `_ page. +If this does not solve your issue, please check the specific cases noted below. +The next recommended step is to search through the ``ShakeNBreak`` +`GitHub Issues `_ (use the GitHub search bar on the top +right) to see if your issue/question has been asked before. If your problem is still not solved, then +please contact the developers through the +`GitHub Issues `_ page. + +- For any issues relating to installation, please see the `Installation`_ page. - A current known issue with ``numpy``/``pymatgen`` is that it might give an error similar to this: @@ -238,3 +243,6 @@ solved, then please contact the developers through the ``GitHub`` Have any tips for users from using `ShakeNBreak`? Please share it with the developers and we'll add them here! + + +.. _Installation: https://shakenbreak.readthedocs.io/en/latest/Installation.html \ No newline at end of file diff --git a/docs/conf.py b/docs/conf.py index 1057987c..27209365 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -15,50 +15,50 @@ from recommonmark.transform import AutoStructify -sys.path.insert(0, os.path.abspath('..')) +sys.path.insert(0, os.path.abspath("..")) # -- Project information ----------------------------------------------------- -project = 'shakenbreak' -copyright = '2022, Irea Mosquera-Lois, Seán R. Kavanagh' -author = 'Irea Mosquera-Lois, Seán R. Kavanagh' +project = "shakenbreak" +copyright = "2022, Irea Mosquera-Lois, Seán R. Kavanagh" +author = "Irea Mosquera-Lois, Seán R. Kavanagh" # The full version, including alpha/beta/rc tags -release = '3.3.6' +release = "3.4.0" # -- General configuration --------------------------------------------------- # Add any Sphinx extension module names here, as strings. They can be -# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# extensions coming with Sphinx (named "sphinx.ext.*") or your custom # ones. extensions = [ - 'sphinx.ext.autodoc', - 'sphinx.ext.coverage', - 'sphinx.ext.napoleon', - 'sphinx.ext.mathjax', - 'sphinx.ext.viewcode', - 'sphinx.ext.autosectionlabel', - 'sphinx_click', - 'sphinx_design', - # 'sphinx_mdinclude', - 'myst_nb', # for jupyter notebooks - # 'myst_parser', + "sphinx.ext.autodoc", + "sphinx.ext.coverage", + "sphinx.ext.napoleon", + "sphinx.ext.mathjax", + "sphinx.ext.viewcode", + "sphinx.ext.autosectionlabel", + "sphinx_click", + "sphinx_design", + # "sphinx_mdinclude", + "myst_nb", # for jupyter notebooks + # "myst_parser", ] source_suffix = { - '.rst': 'restructuredtext', - '.ipynb': 'myst-nb', + ".rst": "restructuredtext", + ".ipynb": "myst-nb", } # Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] +templates_path = ["_templates"] # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This pattern also affects html_static_path and html_extra_path. -exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] +exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] myst_enable_extensions = [ "html_admonition", @@ -69,7 +69,7 @@ # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # -html_theme = 'sphinx_book_theme' # 'sphinx_rtd_theme' +html_theme = "sphinx_book_theme" # "sphinx_rtd_theme" # The name of an image file (relative to this directory) to place at the top # of the sidebar. @@ -79,7 +79,7 @@ # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = ['_static'] +html_static_path = ["_static"] # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. @@ -127,10 +127,10 @@ nb_render_image_options = {"height": "300",} # Reduce plots size #myst_render_markdown_format = "gfm" myst_heading_anchors = 2 -github_doc_root = 'https://github.com/executablebooks/MyST-Parser/tree/master/docs/' +github_doc_root = "https://github.com/executablebooks/MyST-Parser/tree/master/docs/" def setup(app): - app.add_config_value('myst_parser_config', { - 'url_resolver': lambda url: github_doc_root + url, - 'auto_toc_tree_section': 'Contents', + app.add_config_value("myst_parser_config", { + "url_resolver": lambda url: github_doc_root + url, + "auto_toc_tree_section": "Contents", }, True) app.add_transform(AutoStructify) diff --git a/docs/index.rst b/docs/index.rst index a318a60f..0d0c3a0f 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -71,18 +71,20 @@ Literature Installation ======================== -``ShakeNBreak`` can be installed using ``pip``: +``ShakeNBreak`` can be installed using ``conda``: .. code:: bash - pip install shakenbreak + conda install -c conda-forge shakenbreak -Alternatively if needed, it can also be installed from ``conda`` with: +or ``pip``: .. code:: bash - conda install -c conda-forge shakenbreak + pip install shakenbreak +See the `Installation docs `__ if you +encounter any issues (e.g. known issue with ``phonopy`` ``CMake`` build). If using ``VASP``, in order for ``ShakeNBreak`` to automatically generate the pseudopotential input files (``POTCARs``), your local ``VASP`` pseudopotential directory must be set in the ``pymatgen`` @@ -211,30 +213,36 @@ run tests and add new tests for any new features whenever submitting pull reques Studies using ``ShakeNBreak`` ============================= -- 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 +- Y\. Fu & H. Lohan et al. **Factors Enabling Delocalized Charge-Carriers in Pnictogen-Based Solar Absorbers: In-depth Investigation into CuSbSe₂** `Nature Communications `__ 2025 +- S\. R. Kavanagh **Identifying Split Vacancies with Foundation Models and Electrostatics** `arXiv `__ 2025 +- S\. R. Kavanagh et al. **Intrinsic point defect tolerance in selenium for indoor and tandem photovoltaics** `ChemRxiv `__ 2025 +- J\. Hu et al. **Enabling ionic transport in Li₃AlP₂ the roles of defects and disorder** `Journal of Materials Chemistry A `__ 2025 +- X\. Zhao et al. **Trace Yb doping-induced cationic vacancy clusters enhance thermoelectrics in p-type PbTe** `Applied Physics Letters `__ 2025 +- Z\. Cai & C. Ma **Origin of oxygen partial pressure-dependent conductivity in SrTiO** :sub:`3` `Applied Physics Letters `__ 2025 +- W\. D. Neilson et al. **Oxygen Potential, Uranium Diffusion, and Defect Chemistry in UO** :sub:`2±x` **: A Density Functional Theory Study** `Journal of Physical Chemistry C `__ 2024 +- X\. Wang et al. **Sulfur vacancies limit the open-circuit voltage of Sb₂S₃ solar cells** `ACS Energy Letters `__ 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₄ 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₂** `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 -- X\. Wang et al. **Upper efficiency limit of Sb₂Se₃ solar cells** `Joule `_ 2024 -- I\. Mosquera-Lois et al. **Machine-learning structural reconstructions for accelerated point defect calculations** `npj Computational Materials `_ 2024 -- S\. R. Kavanagh et al. **doped: Python toolkit for robust and repeatable charged defect supercell calculations** `Journal of Open Source Software `_ 2024 -- K\. Li et al. **Computational Prediction of an Antimony-based n-type Transparent Conducting Oxide: F-doped Sb₂O₅** `Chemistry of Materials `_ 2024 -- X\. Wang et al. **Four-electron negative-U vacancy defects in antimony selenide** `Physical Review B `_ 2023 +- X\. Wang et al. **Upper efficiency limit of Sb₂Se₃ solar cells** `Joule `__ 2024 +- I\. Mosquera-Lois et al. **Machine-learning structural reconstructions for accelerated point defect calculations** `npj Computational Materials `__ 2024 +- S\. R. Kavanagh et al. **doped: Python toolkit for robust and repeatable charged defect supercell calculations** `Journal of Open Source Software `__ 2024 +- K\. Li et al. **Computational Prediction of an Antimony-based n-type Transparent Conducting Oxide: F-doped Sb₂O₅** `Chemistry of Materials `__ 2024 +- S\. Hachmioune et al. **Exploring the Thermoelectric Potential of MgB₄: Electronic Band Structure, Transport Properties, and Defect Chemistry** `Chemistry of Materials `__ 2024 +- X\. Wang et al. **Four-electron negative-U vacancy defects in antimony selenide** `Physical Review B `__ 2023 - Y\. Kumagai et al. **Alkali Mono-Pnictides: A New Class of Photovoltaic Materials by Element Mutation** `PRX Energy `__ 2023 - J\. Willis, K. B. Spooner, D. O. Scanlon. **On the possibility of p-type doping in barium stannate** `Applied Physics Letters `__ 2023 - A\. T. J. Nicolson et al. **Cu₂SiSe₃ as a promising solar absorber: harnessing cation dissimilarity to avoid killer antisites** `Journal of Materials Chemistry A `__ 2023 -- J\. Cen et al. **Cation disorder dominates the defect chemistry of high-voltage LiMn** :sub:`1.5` **Ni** :sub:`0.5` **O₄ (LMNO) spinel cathodes** `Journal of Materials Chemistry A`_ 2023 +- J\. Cen et al. **Cation disorder dominates the defect chemistry of high-voltage LiMn** :sub:`1.5` **Ni** :sub:`0.5` **O₄ (LMNO) spinel cathodes** `Journal of Materials Chemistry A `__ 2023 - J\. Willis & R. Claes et al. **Limits to Hole Mobility and Doping in Copper Iodide** `Chemistry of Materials `__ 2023 -- I\. Mosquera-Lois & S. R. Kavanagh, A. Walsh, D. O. Scanlon **Identifying the ground state structures of point defects in solids** `npj Computational Materials`_ 2023 +- I\. Mosquera-Lois & S. R. Kavanagh, A. Walsh, D. O. Scanlon **Identifying the ground state structures of point defects in solids** `npj Computational Materials `__ 2023 - B\. Peng et al. **Advancing understanding of structural, electronic, and magnetic properties in 3d-transition-metal TM-doped α-Ga₂O₃ (TM = V, Cr, Mn, and Fe)** `Journal of Applied Physics `__ 2023 -- Y\. T. Huang & S. R. Kavanagh et al. **Strong absorption and ultrafast localisation in NaBiS₂ nanocrystals with slow charge-carrier recombination** `Nature Communications`_ 2022 -- S\. R. Kavanagh, D. O. Scanlon, A. Walsh, C. Freysoldt **Impact of metastable defect structures on carrier recombination in solar cells** `Faraday Discussions`_ 2022 +- Y\. T. Huang & S. R. Kavanagh et al. **Strong absorption and ultrafast localisation in NaBiS₂ nanocrystals with slow charge-carrier recombination** `Nature Communications `__ 2022 +- S\. R. Kavanagh, D. O. Scanlon, A. Walsh, C. Freysoldt **Impact of metastable defect structures on carrier recombination in solar cells** `Faraday Discussions `__ 2022 - Y-S\. Choi et al. **Intrinsic Defects and Their Role in the Phase Transition of Na-Ion Anode Na₂Ti₃O₇** `ACS Applied Energy Materials `__ 2022 (Early version) - S\. R. Kavanagh, D. O. Scanlon, A. Walsh **Rapid Recombination by Cadmium Vacancies in CdTe** `ACS Energy Letters `__ 2021 -- C\. J. Krajewska et al. **Enhanced visible light absorption in layered Cs₃Bi₂Br₉ through mixed-valence Sn(II)/Sn(IV) doping** `Chemical Science`_ 2021 (Early version) -- (News & Views): A. Mannodi-Kanakkithodi **The devil is in the defects** `Nature Physics`_ 2023 (`Free-to-read link `__) +- C\. J. Krajewska et al. **Enhanced visible light absorption in layered Cs₃Bi₂Br₉ through mixed-valence Sn(II)/Sn(IV) doping** `Chemical Science `__ 2021 (Early version) +- (News & Views): A. Mannodi-Kanakkithodi **The devil is in the defects** `Nature Physics `__ 2023 (`Free-to-read link `__) .. Se .. Wenzhen paper @@ -246,14 +254,6 @@ Studies using ``ShakeNBreak`` .. Kat YTOS .. Squires (and mention benchmark test against AIRSS? See Slack message) -.. _Journal of Materials Chemistry A: https://doi.org/10.1039/D3TA00532A -.. _npj Computational Materials: https://www.nature.com/articles/s41524-023-00973-1 -.. _Nature Communications: https://www.nature.com/articles/s41467-022-32669-3 -.. _Faraday Discussions: https://doi.org/10.1039/D2FD00043A -.. _ACS Energy Letters: https://pubs.acs.org/doi/full/10.1021/acsenergylett.1c00380 -.. _Nature Physics: https://doi.org/10.1038/s41567-023-02049-9 -.. _Chemical Science: https://doi.org/10.1039/D1SC03775G - License and Citation ======================== diff --git a/setup.py b/setup.py index 3e0ba5ab..e0053931 100644 --- a/setup.py +++ b/setup.py @@ -114,24 +114,13 @@ def run(self): _install_custom_font() -# https://stackoverflow.com/questions/27664504/how-to-add-package-data-recursively-in-python-setup-py -def package_files(directory): - """Include package data.""" - paths = [] - for path, _dir, filenames in os.walk(directory): - paths.extend(os.path.join("..", path, filename) for filename in filenames) - return paths - - -input_files = package_files("SnB_input_files/") - with open("README.md", encoding="utf-8") as file: long_description = file.read() setup( name="shakenbreak", - version="3.3.6", + version="3.4.0", description="Package to generate and analyse distorted defect structures, in order to " "identify ground-state and metastable defect configurations.", long_description=long_description, @@ -160,11 +149,11 @@ def package_files(directory): ], keywords="chemistry pymatgen dft defects structure-searching distortions symmetry-breaking", packages=find_packages(), - python_requires=">=3.8", + python_requires=">=3.10", # dictated by "pymatgen>=2024.9.10" requirement in doped install_requires=[ "numpy", # >=1.21.2" needed for numpy.typing.NDArray? - "pymatgen>=2022.10.22", - "pymatgen-analysis-defects>=2022.10.28", + "pymatgen", # requirement set by doped + "pymatgen-analysis-defects", # requirement set by doped "matplotlib>=3.6", "ase", "pandas>=1.1.0", @@ -173,7 +162,7 @@ def package_files(directory): "monty", "click>8.0", "importlib_metadata", - "doped>=2.4.4", + "doped>=3.0.0", # for _scan_sm_stol_till_match, for super-fast structure matching ], extras_require={ "tests": [ @@ -192,7 +181,7 @@ def package_files(directory): }, # Specify any non-python files to be distributed with the package package_data={ - "shakenbreak": ["shakenbreak/*", *input_files], + "shakenbreak": ["shakenbreak/*"], }, include_package_data=True, # Specify the custom installation class diff --git a/SnB_input_files/castep.param b/shakenbreak/SnB_input_files/castep.param similarity index 100% rename from SnB_input_files/castep.param rename to shakenbreak/SnB_input_files/castep.param diff --git a/SnB_input_files/cp2k_input.inp b/shakenbreak/SnB_input_files/cp2k_input.inp similarity index 100% rename from SnB_input_files/cp2k_input.inp rename to shakenbreak/SnB_input_files/cp2k_input.inp diff --git a/SnB_input_files/default_POTCARs.yaml b/shakenbreak/SnB_input_files/default_POTCARs.yaml similarity index 100% rename from SnB_input_files/default_POTCARs.yaml rename to shakenbreak/SnB_input_files/default_POTCARs.yaml diff --git a/SnB_input_files/example_generate_all_config.yaml b/shakenbreak/SnB_input_files/example_generate_all_config.yaml similarity index 96% rename from SnB_input_files/example_generate_all_config.yaml rename to shakenbreak/SnB_input_files/example_generate_all_config.yaml index 57e3f118..4c02b8a6 100644 --- a/SnB_input_files/example_generate_all_config.yaml +++ b/shakenbreak/SnB_input_files/example_generate_all_config.yaml @@ -20,7 +20,7 @@ n_iter: 1 # Number of Monte Carlo cycles to perform. (Default: 1) active_atoms: None # Atoms to apply rattle displacement to. (Default = all atoms) nbr_cutoff: 5 # The radial cutoff distance (in Angstroms) used to construct the list of atomic neighbours for checking interatomic distances. (Default: 5) width: 0.1 # Width of the Monte Carlo rattling error function, in Angstroms. (Default: 0.1) -max_attempts: 5000 # Limit for how many attempted rattle moves are allowed a single atom; if this limit is reached an `Exception` is raised +max_attempts: 5000 # Limit for how many attempted rattle moves are allowed a single atom; if this limit is reached an ``Exception`` is raised max_disp: 2.0 # Rattle moves that yields a displacement larger than max_disp will always be rejected. Rarely occurs, mostly used as a safety net seed: 42 # Seed from which rattle random displacements are generated (Default = 100*distortion_factor, e.g. 40 for -60% distortion, 100 for 0% Distortion/Rattled etc) local_rattle: False # If True, rattle displacements will tail-off as we move away from the defect site. Not recommended as typically worsens performance. diff --git a/SnB_input_files/example_generate_config.yaml b/shakenbreak/SnB_input_files/example_generate_config.yaml similarity index 96% rename from SnB_input_files/example_generate_config.yaml rename to shakenbreak/SnB_input_files/example_generate_config.yaml index c76bb118..8c855e78 100644 --- a/SnB_input_files/example_generate_config.yaml +++ b/shakenbreak/SnB_input_files/example_generate_config.yaml @@ -20,7 +20,7 @@ # active_atoms: None # Atoms to apply rattle displacement to. (Default = all atoms) # nbr_cutoff: 5 # The radial cutoff distance (in Angstroms) used to construct the list of atomic neighbours for checking interatomic distances. (Default: 5) # width: 0.1 # Width of the Monte Carlo rattling error function, in Angstroms. (Default: 0.1) -# max_attempts: 5000 # Limit for how many attempted rattle moves are allowed a single atom; if this limit is reached an `Exception` is raised +# max_attempts: 5000 # Limit for how many attempted rattle moves are allowed a single atom; if this limit is reached an ``Exception`` is raised # max_disp: 2.0 # Rattle moves that yields a displacement larger than max_disp will always be rejected. Rarely occurs, mostly used as a safety net # seed: 42 # Seed from which rattle random displacements are generated (Default = 100*distortion_factor, e.g. 40 for -60% distortion, 100 for 0% Distortion/Rattled etc) # local_rattle: False # If True, rattle displacements will tail-off as we move away from the defect site. Not recommended as typically worsens performance. diff --git a/SnB_input_files/incar.yaml b/shakenbreak/SnB_input_files/incar.yaml similarity index 95% rename from SnB_input_files/incar.yaml rename to shakenbreak/SnB_input_files/incar.yaml index 98f8e57a..9bbc4f82 100644 --- a/SnB_input_files/incar.yaml +++ b/shakenbreak/SnB_input_files/incar.yaml @@ -3,6 +3,7 @@ # Changed from doped default: LCHARG: false # reduce file sizes LREAL: Auto # real-space force projection, runtime speedup with acceptable loss in force accuracy +# Note that ROPT is also set to 1e-3 * num_elements LWAVE: false # reduce file sizes NELM: 40 # slightly reduced NELM to speedup with incomplete SCF convergence far from minimum NSW: 300 # increased NSW from doped default to allow relaxation from initially distorted structure diff --git a/SnB_input_files/qe_input.yaml b/shakenbreak/SnB_input_files/qe_input.yaml similarity index 100% rename from SnB_input_files/qe_input.yaml rename to shakenbreak/SnB_input_files/qe_input.yaml diff --git a/shakenbreak/SnB_run.sh b/shakenbreak/SnB_run.sh index eecb0adf..ba2d6a34 100755 --- a/shakenbreak/SnB_run.sh +++ b/shakenbreak/SnB_run.sh @@ -85,6 +85,10 @@ SnB_run_loop() { if [[ "$i" == *"_High_Energy"* ]]; then continue fi + if [ -f "${i}"/OUTCAR.gz ]; then + echo "Unzipping OUTCAR for ${i%/}, needed for checking relaxation" + gzip -d "${i}"/OUTCAR.gz + fi if [ ! -f "${i}"/OUTCAR ] || { ! grep -q "required accuracy" "${i}"/OUTCAR && ! grep -q "considering this converged" "${i}"/OUTCAR; }; then # check calculation not converged builtin cd "$i" || return if [ ! -f "${job_filepath}" ] && [ ! "$job_in_cwd" = false ]; then @@ -93,7 +97,7 @@ SnB_run_loop() { if [ -f "../Unperturbed/OUTCAR" ] && (($(grep -c entropy= ../Unperturbed/OUTCAR) > 0)); then unperturbed_energy=$(grep entropy= ../Unperturbed/OUTCAR | awk '{print $NF}' | tail -1) else - unperturbed_energy=-10000 + unperturbed_energy=10000 fi if [ -f OUTCAR ]; then # if OUTCAR exists so rerunning rather than 1st run # count number of ionic steps with positive energies, after the first 5 ionic steps @@ -104,8 +108,7 @@ SnB_run_loop() { if ((errors > 0)) || { ((pos_energies > 0)) && { (($(echo "$energy_diff_to_unperturbed > 1" | bc -l))) || [[ "$i" == *"Unperturbed"* ]]; }; }; then # if there are positive energies or errors in OUTCAR, and at least 1 eV higher than Unperturbed if [[ "$i" == *"Unperturbed"* ]]; then # positive energies / errors for Unperturbed structure, indicates pathological defect structure - echo "Positive energies or forces error encountered for ${i%/}. " - echo "This typically indicates the initial defect structure supplied to ShakeNBreak is highly unstable, often with bond lengths smaller than the ionic radii." + echo "Positive energies or forces error encountered for ${i%/}. This typically indicates the initial defect structure supplied to ShakeNBreak is highly unstable, often with bond lengths smaller than the ionic radii." echo "Please check this defect structure and/or the relaxation output files." builtin cd .. || return continue @@ -150,36 +153,30 @@ SnB_run_loop() { # check if multiple <=single-step OUTCARs present, and CONTCAR empty/less than 9 lines or same as POSCAR if check_multiple_single_step_outcars && { [[ ! -f "CONTCAR" ]] || { [[ -f "CONTCAR" ]] && { [[ $(wc -l < "CONTCAR") -le 9 ]] || diff -q "POSCAR" "CONTCAR" >/dev/null; }; }; }; then - echo "Previous run for ${i%?} did not yield more than one ionic step, and multiple OUTCARs with <=1 ionic " - echo "steps present, suggesting poor convergence. Recommended to manually check the VASP output files for this!" + echo "Previous run for ${i%?} did not yield more than one ionic step, and multiple OUTCARs with <=1 ionic steps present, suggesting poor convergence. Recommended to manually check the VASP output files for this!" fi # check if more than 2 OUTCARs present - might indicate tricky relaxation if check_many_outcars; then - # echo "More than 2 OUTCARs present for ${i%?}, suggesting tricky relaxation. " #sed -i.bak 's/IBRION.*/IBRION = 1/g' INCAR && rm -f INCAR.bak # sometimes helps to change IBRION if relaxation taking long # Check total number of ionic steps in all OUTCARs num_ionic_steps=$(grep entropy= OUTCAR* | wc -l) # use wc -l rather than grep -c because multiple files if [ -f ../Unperturbed/OUTCAR ]; then # only compare if Unperturbed folder present # If equal or higher than 150, compare to final energy in Unperturbed OUTCAR - if ((num_ionic_steps >= 150)); then + if ((num_ionic_steps >= 150)) && (($(echo "$energy_diff_to_unperturbed > 2" | bc -l))); then # If energy difference to Unperturbed is higher than 2 eV, rename to _High_Energy and continue - if (($(echo "$energy_diff_to_unperturbed > 1" | bc -l))); then - echo "More than 150 ionic steps present for ${i%?}. The energy difference to last structure in Unperturbed relaxation" - echo "is higher than 2 eV, indicating that ${i%?} is stuck in a high energy basin. " - echo "Renaming to ${i%?}_High_Energy and continuing." - builtin cd .. || return - mv "${i%/}" "${i%/}_High_Energy" - continue - # If higher than 500 and energy similar to Unperturbed, rename to _High_Energy and continue - elif ((num_ionic_steps > 500)) && (($(echo "${energy_diff#-} > -0.002" | bc -l))); then - echo "More than 500 ionic steps present for ${i%?} and energy higher than Unperturbed, " - echo "indicating that ${i%?} won't lead to an energy-lowering structure. " - echo "Renaming to ${i%?}_High_Energy and continuing." - builtin cd .. || return - mv "${i%/}" "${i%/}_High_Energy" - continue - fi + echo "More than 150 ionic steps present for ${i%?}. The energy difference to last structure in Unperturbed relaxation is higher than 2 eV, indicating that ${i%?} is stuck in a high energy basin. " + echo "Renaming to ${i%?}_High_Energy and continuing." + builtin cd .. || return + mv "${i%/}" "${i%/}_High_Energy" + continue + # If higher than 500 ionic steps, energy not changing significantly and higher energy than Unperturbed, rename to _High_Energy and continue + elif ((num_ionic_steps > 500)) && (($(echo "${energy_diff#-} > -0.002" | bc -l))) && (($(echo "$energy_diff_to_unperturbed > 0.5" | bc -l))); then + echo "More than 500 ionic steps present for ${i%?} and energy higher than Unperturbed, indicating that ${i%?} won't lead to an energy-lowering structure. " + echo "Renaming to ${i%?}_High_Energy and continuing." + builtin cd .. || return + mv "${i%/}" "${i%/}_High_Energy" + continue fi fi # else if more than 300 ionic steps taken (and not moved to High_Energy), warn user to check this manually diff --git a/shakenbreak/analysis.py b/shakenbreak/analysis.py index 52339645..eb7d2741 100644 --- a/shakenbreak/analysis.py +++ b/shakenbreak/analysis.py @@ -3,20 +3,22 @@ structure relaxations. """ +import contextlib import json import os import warnings from copy import deepcopy +from functools import lru_cache from typing import Optional, Union import numpy as np import pandas as pd +from doped.utils.configurations import _scan_sm_stol_till_match from doped.utils.parsing import get_outcar from monty.serialization import loadfn from pymatgen.analysis.local_env import CrystalNN -from pymatgen.analysis.structure_matcher import StructureMatcher -from pymatgen.core.periodic_table import Element -from pymatgen.core.structure import Structure +from pymatgen.core.composition import Composition, Element +from pymatgen.core.structure import IStructure, PeriodicSite, Structure from pymatgen.io.vasp.outputs import Outcar from shakenbreak import input, io @@ -61,7 +63,7 @@ def _read_distortion_metadata(output_path: str) -> dict: Args: output_path (:obj:`str`): - Path to directory containing `distortion_metadata.json` + Path to directory containing ``distortion_metadata.json`` (Default: '.', current directory) Returns: @@ -80,7 +82,7 @@ def _read_distortion_metadata(output_path: str) -> dict: def _get_distortion_filename(distortion) -> str: """ Format distortion names for file naming (e.g. from 0.5 to - 'Bond_Distortion_50.0%'). + ``"Bond_Distortion_50.0%"``). Args: distortion (float or str): @@ -91,38 +93,24 @@ def _get_distortion_filename(distortion) -> str: distortion (:obj:`str`): distortion label used for file names. """ - if isinstance(distortion, (float, int)): - if distortion != 0: - distortion_label = f"Bond_Distortion_{round(distortion * 100, 1)+0}%" - # as percentage with 1 decimal place (e.g. 50.0%) - else: - distortion_label = f"Bond_Distortion_{distortion:.1f}%" - elif isinstance(distortion, str): - if "_from_" in distortion and ("Rattled" not in distortion and "Dimer" not in distortion): - distortion_label = f"Bond_Distortion_{distortion}" - # runs from other charge states - elif ( - "Rattled_from_" in distortion - or "Dimer_from" in distortion - or distortion - in [ - "Unperturbed", - "Rattled", - "Dimer", - ] - ): - distortion_label = distortion - elif distortion == "Unperturbed" or distortion == "Rattled" or distortion == "Dimer": - distortion_label = distortion # e.g. "Unperturbed"/"Rattled"/"Dimer" - else: - try: # try converting to float, in case user entered '0.5' - distortion = float(distortion) - distortion_label = f"Bond_Distortion_{round(distortion * 100, 1)+0}%" - except Exception: - distortion_label = "Distortion_not_recognized" - else: - distortion_label = "Distortion_not_recognized" - return distortion_label + if isinstance(distortion, (int, float)): + if distortion != 0: # as percentage with 1 decimal place (e.g. 50.0%) + return f"Bond_Distortion_{round(distortion * 100, 1)+0}%" + + return f"Bond_Distortion_{distortion:.1f}%" + + # otherwise is string: + if "_from_" in distortion and ("Rattled" not in distortion and "Dimer" not in distortion): + return f"Bond_Distortion_{distortion}" # runs from other charge states + + if any(distortion.startswith(i) for i in ["Unperturbed", "Rattled", "Dimer"]): + return distortion + + with contextlib.suppress(Exception): # try converting to float, in case user entered '0.5' + distortion = float(distortion) + return f"Bond_Distortion_{round(distortion * 100, 1)+0}%" + + return "Distortion_not_recognized" def _format_distortion_names( @@ -130,14 +118,14 @@ def _format_distortion_names( ) -> str: """ Formats the distortion filename to the names used internally and for - analysis (i.e. 'Bond_Distortion_-50.0%' -> -0.5). + analysis (i.e. ``"Bond_Distortion_-50.0%"`` -> -0.5). Args: distortion_label (:obj:`str`): distortion label used for file names. Returns: - distortion (:obj:`float` or :obj:`float`): + distortion (:obj:`float`` or :obj:`float`): distortion factor (e.g. -0.6, 0.0, +0.6) or string (e.g. "Unperturbed"/"Rattled"/"-60.0%_from_+2"/"Rattled_from_-1") """ @@ -165,16 +153,16 @@ def _format_distortion_names( def get_gs_distortion(defect_energies_dict: dict) -> tuple: """ - Calculate energy difference between `Unperturbed` structure and + Calculate energy difference between ``Unperturbed`` structure and lowest energy distortion. Returns the energy (in eV) and bond - distortion of the ground-state relative to `Unperturbed`. If - `Unperturbed` not present, returns (None, ground-state bond + distortion of the ground-state relative to ``Unperturbed``. If + ``Unperturbed`` not present, returns (None, ground-state bond distortion). Args: defect_energies_dict (:obj:`dict`): Dictionary matching distortion to final energy, as - produced by `get_energies()` or `_sort_data`. + produced by ``get_energies()`` or ``_sort_data``. Returns: :obj:`tuple`: @@ -214,34 +202,34 @@ def get_gs_distortion(defect_energies_dict: dict) -> tuple: def _sort_data(energies_file: str, verbose: bool = True, min_e_diff: float = 0.05) -> tuple: """ Organize bond distortion results in a dictionary, calculate energy - of ground-state defect structure relative to `Unperturbed` structure + of ground-state defect structure relative to ``Unperturbed`` structure (in eV) and its corresponding bond distortion, and return all three - as a tuple. If `Unperturbed` not present, returns (defect_energies_dict, + as a tuple. If ``Unperturbed`` not present, returns (defect_energies_dict, None, ground-state distortion). Args: energies_file (:obj:`str`): - Path to `yaml` file with bond distortions and final energies - (in eV), obtained using the CLI command `snb-parse` or the - function `parse_energies()`. + Path to ``yaml`` file with bond distortions and final energies + (in eV), obtained using the CLI command ``snb-parse`` or the + function ``parse_energies()``. verbose (:obj:`bool`): Whether to print information about energy lowering distortions, if found. (Default: True) - min_e_diff (:obj: `float`): + min_e_diff (:obj:`float`): Minimum energy difference (in eV) between the ground-state - defect structure, relative to the `Unperturbed` structure, + defect structure, relative to the ``Unperturbed`` structure, to consider it as having found a new energy-lowering distortion. Default is 0.05 eV. Returns: defect_energies_dict (:obj:`dict`): Dictionary matching distortion to final energy, as - produced by `_organize_data()` + produced by ``_organize_data()`` energy_diff (:obj:`float`): Energy difference between minimum energy structure and - `Unperturbed` (in eV). - None if `Unperturbed` not present. + ``Unperturbed`` (in eV). + None if ``Unperturbed`` not present. gs_distortion (:obj:`float`): Distortion corresponding to the minimum energy structure """ @@ -283,13 +271,13 @@ def analyse_defect_site( site_num: Optional[int] = None, vac_site: Optional[list] = None, ) -> tuple: - """ + r""" Analyse coordination environment and bond distances to nearest neighbours of defect site. Args: structure (:obj:`Structure`): - `pymatgen` Structure object to analyse + ``pymatgen`` Structure object to analyse name (:obj:`str`): Defect name for printing. (Default: None) site_num (:obj:`int`): @@ -302,7 +290,7 @@ def analyse_defect_site( Returns: :obj:`tuple`: - Tuple of coordination analysis and bond length DataFrames, + Tuple of coordination analysis and bond length ``DataFrame``\s, respectively. """ # get defect site @@ -350,11 +338,11 @@ def analyse_structure( structure: Structure, output_path: str = ".", ) -> tuple: - """ + r""" Analyse the local distortion of the input defect structure. Requires - access to the `distortion_metadata.json` file generated with + access to the ``distortion_metadata.json`` file generated with ShakeNBreak to read info about defect site. If lacking this, - can alternatively use `analyse_defect_site()`. + can alternatively use ``analyse_defect_site()``. Args: defect_species (:obj:`str`): @@ -362,12 +350,12 @@ def analyse_structure( structure (:obj:`~pymatgen.core.structure.Structure`): Defect structure to analyse output_path (:obj:`str`): - Path to directory containing `distortion_metadata.json` + Path to directory containing ``distortion_metadata.json`` (Default: '.', current directory) Returns: :obj:`tuple`: - Tuple of coordination analysis and bond length DataFrames, + Tuple of coordination analysis and bond length ``DataFrame``\s, respectively. """ defect_name_without_charge = defect_species.rsplit("_", 1)[0] @@ -403,14 +391,14 @@ def get_structures( store them in a dictionary matching the bond distortion to the final structure. By default, will read the structures from the distortion subdirectories present in each defect folder. If only certain - distortions should be parsed, use the argument `bond_distortions` + distortions should be parsed, use the argument ``bond_distortions`` to specify them. Args: defect_species (:obj:`str`): Defect name including charge (e.g. 'vac_1_Cd_0') output_path (:obj:`str`): - Path to top-level directory containing `defect_species` + Path to top-level directory containing ``defect_species`` subdirectories. (Default: current directory. bond_distortions (:obj:`list`, optional): @@ -501,7 +489,7 @@ def get_energies( defect_species (:obj:`str`): Defect name including charge (e.g. 'vac_1_Cd_0') output_path (:obj:`str`): - Path to top-level directory containing `defect_species` + Path to top-level directory containing ``defect_species`` subdirectories. (Default: current directory) distortion_increment (:obj:`float`): @@ -543,68 +531,59 @@ def get_energies( return defect_energies_dict -def _calculate_atomic_disp( +@lru_cache(maxsize=int(1e4)) +def _cached_calculate_atomic_disp( struct1: Structure, struct2: Structure, - stol: float = 0.5, - ltol: float = 0.3, - angle_tol: float = 5, + **sm_kwargs, ) -> tuple: """ - Calculate root mean square displacement and atomic displacements, + Calculate root-mean-square displacement and atomic displacements, normalized by the free length per atom ((Vol/Nsites)^(1/3)) between two structures. + Should only really be used internally in ``ShakeNBreak`` + as the caching in this function relies on the ``Structure`` hashes, + which in ``pymatgen`` is just the composition which is unusable here, + but this is monkey-patched in ``calculate_struct_comparison`` in + ``shakenbreak.analysis`` for fast internal usage. + Args: struct1 (:obj:`Structure`): Structure to compare to struct2. struct2 (:obj:`Structure`): Structure to compare to struct1. - stol (:obj:`float`): - Site tolerance used for structural comparison (via - `pymatgen`'s `StructureMatcher`), as a fraction of the - average free length per atom := ( V / Nsites ) ** (1/3). If - output contains too many 'NaN' values, this likely needs to - be increased. - (Default: 0.5) - ltol (:obj:`float`): - Length tolerance used for structural comparison (via - `pymatgen`'s `StructureMatcher`). - (Default: 0.3) - angle_tol (:obj:`float`): - Angle tolerance used for structural comparison (via - `pymatgen`'s `StructureMatcher`). - (Default: 5) + **sm_kwargs: + Additional keyword arguments to pass to ``_scan_sm_stol_till_match`` + in ``doped`` (used for ultra-fast structure matching), such as + ``min_stol``, ``max_stol``, ``stol_factor`` etc. Returns: :obj:`tuple`: Tuple of normalized root mean squared displacements and normalized displacements between the two structures. """ - sm = StructureMatcher(ltol=ltol, stol=stol, angle_tol=angle_tol, primitive_cell=False, scale=True) - struct1, struct2 = sm._process_species([struct1, struct2]) - struct1, struct2, fu, s1_supercell = sm._preprocess(struct1, struct2) - match = sm._match(struct1, struct2, fu, s1_supercell, use_rms=True, break_on_match=False) - - return None if match is None else (match[0], match[1]) + # ``StructureMatcher._cart_dists()`` is the performance bottleneck for large supercells here. It could + # likely be made faster using Cython/numba (see + # https://github.com/materialsproject/pymatgen/issues/2593), caching and/or multiprocessing, but + # ``doped`` efficiency improvements have made it multiple orders of magnitude faster: + return _scan_sm_stol_till_match(struct1, struct2, func_name="_get_atomic_disps", **sm_kwargs) def calculate_struct_comparison( defect_structures_dict: dict, metric: str = "max_dist", ref_structure: Union[str, float, Structure] = "Unperturbed", - stol: float = 0.5, - ltol: float = 0.3, - angle_tol: float = 5, min_dist: float = 0.1, verbose: bool = False, + **sm_kwargs, ) -> dict: """ Calculate either the summed atomic displacement, with metric = "disp", or the maximum distance between matched atoms, with metric = "max_dist", - (default) between each distorted structure in `defect_struct_dict`, - and either 'Unperturbed' or a specified structure (`ref_structure`). - For metric = "disp", atomic displacements below `min_dist` (in Å, + (default) between each distorted structure in ``defect_struct_dict``, + and either 'Unperturbed' or a specified structure (``ref_structure``). + For metric = "disp", atomic displacements below ``min_dist`` (in Å, default 0.1 Å) are considered noise and omitted from the sum, to reduce supercell size dependence. @@ -618,38 +597,27 @@ def calculate_struct_comparison( ('disp') or the maximum distance between matched atoms ('max_dist', default). (Default: "max_dist") - ref_structure (:obj:`str` or :obj:`float` or :obj:`Structure`): + ref_structure (:obj:`str`` or :obj:`float`` or :obj:`Structure`): Structure to use as a reference for comparison (to compute atomic displacements). Either as a key from - `defect_structures_dict` (e.g. '-0.4' for "Bond_Distortion_-40.0%") - or a pymatgen Structure object (to compare with a specific external + ``defect_structures_dict`` (e.g. '-0.4' for ``"Bond_Distortion_-40.0%"``) + or a ``pymatgen`` ``Structure`` object (to compare with a specific external structure). (Default: "Unperturbed") - stol (:obj:`float`): - Site tolerance used for structural comparison (via - `pymatgen`'s `StructureMatcher`), as a fraction of the - average free length per atom := ( V / Nsites ) ** (1/3). If - output contains too many 'NaN' values, this likely needs to - be increased. - (Default: 0.5) - ltol (:obj:`float`): - Length tolerance used for structural comparison (via - `pymatgen`'s `StructureMatcher`). - (Default: 0.3) - angle_tol (:obj:`float`): - Angle tolerance used for structural comparison (via - `pymatgen`'s `StructureMatcher`). - (Default: 5) min_dist (:obj:`float`): Minimum atomic displacement threshold to include in atomic displacements sum (in Å, default 0.1 Å). verbose (:obj:`bool`): Whether to print information message about structures being compared. + **sm_kwargs: + Additional keyword arguments to pass to ``_scan_sm_stol_till_match`` + in ``doped`` (used for ultra-fast structure matching), such as + ``min_stol``, ``max_stol``, ``stol_factor`` etc. Returns: :obj:`dict`: Dictionary matching bond distortions to structure - comparison metric (disp or max_dist). + comparison metric (``"disp"`` or ``"max_dist"``). """ # Check reference structure if isinstance(ref_structure, (str, float)): @@ -682,17 +650,28 @@ def calculate_struct_comparison( disp_dict = {} normalization = (len(ref_structure) / ref_structure.volume) ** (1 / 3) + + # use doped efficiency functions for speed (speeds up structure matching dramatically): + from doped.utils.efficiency import Composition as doped_Composition + from doped.utils.efficiency import IStructure as doped_IStructure + from doped.utils.efficiency import PeriodicSite as doped_PeriodicSite + + Composition.__instances__ = {} + Composition.__eq__ = doped_Composition.__eq__ + PeriodicSite.__eq__ = doped_PeriodicSite.__eq__ + PeriodicSite.__hash__ = doped_PeriodicSite.__hash__ + IStructure.__instances__ = {} + IStructure.__eq__ = doped_IStructure.__eq__ + for distortion in list(defect_structures_dict.keys()): if defect_structures_dict[distortion] == "Not converged": disp_dict[distortion] = "Not converged" # Structure not converged else: try: - _, norm_dist = _calculate_atomic_disp( + _, norm_dist = _cached_calculate_atomic_disp( struct1=ref_structure, struct2=defect_structures_dict[distortion], - stol=stol, - ltol=ltol, - angle_tol=angle_tol, + **sm_kwargs, ) if metric == "disp": disp_dict[distortion] = ( @@ -704,8 +683,7 @@ def calculate_struct_comparison( else: raise ValueError(f"Invalid metric '{metric}'. Must be one of 'disp' or 'max_dist'.") except TypeError: - disp_dict[distortion] = None # algorithm couldn't match lattices. Set comparison - # metric to None + disp_dict[distortion] = None # algorithm couldn't match lattices. Set metric to None # warnings.warn( # f"pymatgen StructureMatcher could not match lattices between " # f"{ref_name} and {distortion} structures." @@ -718,15 +696,15 @@ def compare_structures( defect_structures_dict: dict, defect_energies_dict: dict, ref_structure: Union[str, float, Structure] = "Unperturbed", - stol: float = 0.5, units: str = "eV", min_dist: float = 0.1, display_df: bool = True, verbose: bool = True, + **sm_kwargs, ) -> Union[None, pd.DataFrame]: """ Compare final bond-distorted structures with either 'Unperturbed' or - a specified structure (`ref_structure`), and calculate the summed + a specified structure (``ref_structure``), and calculate the summed atomic displacement (in Å), and maximum distance between matched atomic sites (in Å). @@ -735,38 +713,35 @@ def compare_structures( Dictionary mapping bond distortion to (relaxed) structure defect_energies_dict (:obj:`dict`): Dictionary matching distortion to final energy (eV), as - produced by `_organize_data()`. - ref_structure (:obj:`str` or :obj:`float` or :obj:`Structure`): + produced by ``_organize_data()``. + ref_structure (:obj:`str`` or :obj:`float`` or :obj:`Structure`): Structure to use as a reference for comparison (to compute atomic displacements). Either as a key from - `defect_structures_dict` (e.g. '-0.4' for 'Bond_Distortion_-40.0%') - or a pymatgen Structure object (to compare with a specific external + ``defect_structures_dict`` (e.g. '-0.4' for ``"Bond_Distortion_-40.0%"``) + or a ``pymatgen`` ``Structure`` object (to compare with a specific external structure). (Default: "Unperturbed") - stol (:obj:`float`): - Site tolerance used for structural comparison (via - `pymatgen`'s `StructureMatcher`), as a fraction of the - average free length per atom := ( V / Nsites ) ** (1/3). If - structure comparison output contains too many 'NaN' values, - this likely needs to be increased. - (Default: 0.5) units (:obj:`str`): Energy units label for outputs (either 'eV' or 'meV'). - Should be the same as the units in `defect_energies_dict`, + Should be the same as the units in ``defect_energies_dict``, as this does not modify the supplied values. (Default: "eV") min_dist (:obj:`float`): Minimum atomic displacement threshold to include in atomic displacements sum (in Å, default 0.1 Å). display_df (:obj:`bool`): - Whether to display the structure comparison DataFrame + Whether to display the structure comparison ``DataFrame`` interactively in Jupyter/Ipython (Default: True). verbose (:obj:`bool`): Whether to print information message about structures being compared. + **sm_kwargs: + Additional keyword arguments to pass to ``_scan_sm_stol_till_match`` + in ``doped`` (used for ultra-fast structure matching), such as + ``min_stol``, ``max_stol``, ``stol_factor`` etc. Returns: :obj:`pd.DataFrame`: - DataFrame containing structural comparison results (summed + ``DataFrame`` containing structural comparison results (summed normalised atomic displacement and maximum distance between matched atomic sites), and relative energies. """ @@ -774,44 +749,17 @@ def compare_structures( warnings.warn("All structures in defect_structures_dict are not converged. Returning None.") return None df_list = [] - disp_dict = calculate_struct_comparison( - defect_structures_dict, - metric="disp", - ref_structure=ref_structure, - stol=stol, - min_dist=min_dist, - verbose=verbose, - ) - max_dist_dict = calculate_struct_comparison( - defect_structures_dict, - metric="max_dist", - ref_structure=ref_structure, - stol=stol, - verbose=False, # only print "Comparing to..." once - ) - # Check if too many 'NaN' values in disp_dict, if so, try with higher stol - number_of_nan = len([value for value in disp_dict.values() if value is None]) - if number_of_nan > len(disp_dict.values()) // 3: - warnings.warn( - f"The specified tolerance {stol} seems to be too tight as" - " too many lattices could not be matched. Will retry with" - f" larger tolerance ({stol+0.4})." - ) - max_dist_dict = calculate_struct_comparison( - defect_structures_dict, - metric="max_dist", - ref_structure=ref_structure, - stol=stol + 0.4, - verbose=False, - ) - disp_dict = calculate_struct_comparison( + disp_dict, max_dist_dict = ( + calculate_struct_comparison( # cached to avoid redundant calculation time defect_structures_dict, - metric="disp", + metric=i, ref_structure=ref_structure, - stol=stol + 0.4, min_dist=min_dist, - verbose=False, + verbose=verbose if i == "disp" else False, # only print "Comparing to..." once + **sm_kwargs, ) + for i in ["disp", "max_dist"] + ) for distortion in defect_energies_dict["distortions"]: try: @@ -891,26 +839,27 @@ def compare_structures( def get_homoionic_bonds( structure: Structure, - elements: list, + elements: Union[list[str], str], radius: Optional[float] = 3.3, verbose: bool = True, ) -> dict: """ - Returns a list of homoionic bonds for the given element. These bonds - are often formed by the defect neighbouts to accomodate charge + Returns a list of homo-ionic bonds for the given element. These bonds + are often formed by the defect neighbours to accommodate charge deficiency. Args: structure (:obj:`~pymatgen.core.structure.Structure`): - `pymatgen` Structure object to analyse + ``pymatgen`` Structure object to analyse elements (:obj:`list`): - List of element symbols (wihout oxidation state) for which - to find the homoionic bonds (e.g. ["Te", "Se"]). + List or single string of element symbols (wihout + oxidation state) for which to find the homoionic + bonds (e.g. ["Te", "Se"]). radius (:obj:`float`, optional): Distance cutoff to look for homoionic bonds. Defaults to 3.3 A. verbose (:obj:`bool`, optional): - Whether or not to print the list of homoionic bonds. + Whether to print the list of homoionic bonds. Returns: :obj:`dict`: @@ -1004,7 +953,7 @@ def _site_magnetizations( Returns: :obj:`pandas.DataFrame`: - pandas.Dataframe with sites with magnetization above threshold. + ``Dataframe`` with sites with magnetization above threshold. """ # Site magnetizations mag = outcar.magnetization @@ -1056,13 +1005,13 @@ def get_site_magnetizations( List of distortions to analyse (e.g. ['Unperturbed', 0.1, -0.2]) output_path (:obj:`str`): - Path to top-level directory containing `defect_species` + Path to top-level directory containing ``defect_species`` subdirectories. (Default: current directory) threshold (:obj:`float`, optional): Magnetization threshold to consider site. (Default: 0.1) - defect_site (:obj:`int` or :obj:`list`, optional): + defect_site (:obj:`int`` or :obj:`list`, optional): Site index or fractional coordinates of the defect. If not specified, will try to read from distortion_metadata.json. If this file is not present, will not include the distance @@ -1077,7 +1026,7 @@ def get_site_magnetizations( Returns: :obj:`dict`: - Dictionary matching distortion to DataFrame containing + Dictionary matching distortion to ``DataFrame`` containing magnetization info. """ magnetizations = {} diff --git a/shakenbreak/cli.py b/shakenbreak/cli.py index 17e16d17..2684b083 100644 --- a/shakenbreak/cli.py +++ b/shakenbreak/cli.py @@ -110,9 +110,9 @@ def snb(): @click.option( "--padding", "-p", - help="If `--charge` or `--min-charge` & `--max-charge` are not set, " + help="If ``--charge`` or ``--min-charge`` & ``--max-charge`` are not set, " "defect charges will be set to the range: 0 - {Defect oxidation state}, " - "with a `--padding` on either side of this range.", + "with a ``--padding`` on either side of this range.", default=1, type=int, ) @@ -150,8 +150,8 @@ def snb(): @click.option( "--config", "-conf", - help="Config file for advanced distortion settings. See example in" - " SnB_input_files/example_generate_config.yaml", + help="Config file for advanced distortion settings. See example in " + "``shakenbreak/SnB_input_files/example_generate_config.yaml``", default=None, type=click.Path(exists=True, dir_okay=False), show_default=True, @@ -159,19 +159,18 @@ def snb(): @click.option( "--input_file", "-inp", - help="Input file for the code specified with `--code`, " - "with relaxation parameters to override defaults (e.g. `INCAR` for `VASP`).", + help="Input file for the code specified with ``--code``, " + "with relaxation parameters to override defaults (e.g. ``INCAR`` for ``VASP``).", default=None, type=click.Path(exists=True, dir_okay=False, file_okay=True), show_default=True, ) @click.option( - "--verbose", - "-v", - help="Print information about identified defects and generated distortions", - default=False, - is_flag=True, - show_default=True, + "--verbose/--non-verbose", + "-v/-nv", + help="Print information about identified defects and generated distortions. " + "Default (None) gives medium level verbosity.", + default=None, ) def generate( defect, @@ -243,15 +242,15 @@ def generate( defect_struct = Structure.from_file(defect) bulk_struct = Structure.from_file(bulk) - # Note that here the Defect.defect_structure is the defect `supercell` - # structure, not the defect `primitive` structure. + # Note that here the Defect.defect_structure is the defect ``supercell`` + # structure, not the defect ``primitive`` structure. defect_object = input.identify_defect( defect_structure=defect_struct, bulk_structure=bulk_struct, defect_index=defect_index, defect_coords=defect_coords, ) - if verbose and defect_index is None and defect_coords is None: + if verbose is not False and defect_index is None and defect_coords is None: # medium level verbosity site = defect_object.site site_info = ( f"{site.species_string} at [{site._frac_coords[0]:.3f}," @@ -297,9 +296,8 @@ def generate( if name is None: name = get_defect_name_from_entry(defect_entries[0], relaxed=False) - # if user_charges not set for all defects, print info about how charge states will be - # determined - if not defect_object.user_charges: + # if user_charges not set for all defects, print info about how charge states will be determined + if not defect_object.user_charges and verbose is not False: # medium level verbosity print( "Defect charge states will be set to the range: 0 - {Defect oxidation state}, " f"with a `padding = {padding}` on either side of this range." @@ -410,9 +408,9 @@ def generate( @click.option( "--padding", "-p", - help="For any defects where `charge` is not set in the --config file, " + help="For any defects where ``charge`` is not set in the --config file, " "charges will be set to the range: 0 - {Defect oxidation state}, " - "with a `--padding` on either side of this range.", + "with a ``--padding`` on either side of this range.", default=1, type=int, ) @@ -428,7 +426,7 @@ def generate( "--config", "-conf", help="Config file for advanced distortion settings. See example in " - "/SnB_input_files/example_generate_all_config.yaml", + "``shakenbreak/SnB_input_files/example_generate_all_config.yaml``", default=None, type=click.Path(exists=True, dir_okay=False, file_okay=True), show_default=True, @@ -436,19 +434,18 @@ def generate( @click.option( "--input_file", "-inp", - help="Input file for the code specified with `--code`, " - "with relaxation parameters to override defaults (e.g. `INCAR` for `VASP`).", + help="Input file for the code specified with ``--code``, " + "with relaxation parameters to override defaults (e.g. ``INCAR`` for ``VASP``).", default=None, type=click.Path(exists=True, dir_okay=False, file_okay=True), show_default=True, ) @click.option( - "--verbose", - "-v", - help="Print information about identified defects and generated distortions", - default=False, - is_flag=True, - show_default=True, + "--verbose/--non-verbose", + "-v/-nv", + help="Print information about identified defects and generated distortions. " + "Default (None) gives medium level verbosity.", + default=None, ) def generate_all( defects, @@ -633,7 +630,7 @@ def parse_defect_position(defect_name, defect_settings): None if _bulk_oxi_states else "Undetermined" ), # guess if bulk_oxi, else "Undetermined" ) - if verbose: + if verbose is not False: # medium level verbosity site = defect_object.site site_info = ( f"{site.species_string} at [{site._frac_coords[0]:.3f}," @@ -761,15 +758,6 @@ def parse_defect_position(defect_name, defect_settings): type=str, default=None, ) -@click.option( - "--all", - "-a", - help="Loop through all defect folders (then through their distortion subfolders) in the " - "current directory", - default=False, - is_flag=True, - show_default=True, -) @click.option( "--verbose", "-v", @@ -778,18 +766,22 @@ def parse_defect_position(defect_name, defect_settings): is_flag=True, show_default=True, ) -def run(submit_command, job_script, job_name_option, all, verbose): +def run(submit_command, job_script, job_name_option, verbose): """ Loop through distortion subfolders for a defect, when run within a defect folder, or for all - defect folders in the current (top-level) directory if the --all (-a) flag is set, and submit - jobs to the HPC scheduler. + defect folders in the current (top-level) directory, and submit jobs to the HPC scheduler. As well as submitting the initial geometry optimisations, can automatically continue and resubmit calculations that have not yet converged (and handle those which have failed), see: https://shakenbreak.readthedocs.io/en/latest/Generation.html#submitting-the-geometry-optimisations """ optional_flags = "-" - if all: + # determine if running from within a defect directory or from the top level directory: + if not _running_in_defect_dir( + path=".", + warning_substring="calculations will only be submitted for the distortion folders in this " + "directory.", + ): optional_flags += "a" if verbose: optional_flags += "v" @@ -822,14 +814,6 @@ def run(submit_command, job_script, job_name_option, all, verbose): type=str, default=None, ) -@click.option( - "--all", - "-a", - help="Parse energies for all defects present in the specified/current directory", - default=False, - is_flag=True, - show_default=True, -) @click.option( "--path", "-p", @@ -854,24 +838,26 @@ def run(submit_command, job_script, job_name_option, all, verbose): is_flag=True, show_default=True, ) -def parse(defect, all, path, code, verbose): +def parse(defect, path, code, verbose): """ Parse final energies of defect structures from relaxation output files. - Parsed energies are written to a `yaml` file in the corresponding defect directory. + Parsed energies are written to a ``yaml`` file in the corresponding defect directory. + + Can be run within a single defect folder, or in the top-level directory (either + specifying ``defect`` or looping through all defect folders). """ if defect: _ = io.parse_energies(defect, path, code, verbose=verbose) - elif all: - defect_dirs = _parse_defect_dirs(path) - _ = [io.parse_energies(defect, path, code, verbose=verbose) for defect in defect_dirs] - else: + elif ( + _running_in_defect_dir( + path=path, + warning_substring="calculations will only be parsed for the distortion folders in this " + "directory.", + ) + and path == "." + ): # assume current directory is the defect folder try: - if path != ".": - warnings.warn( - "`--path` option ignored when running from within defect folder (i.e. " - "when `--defect` is not specified." - ) cwd = os.getcwd() defect = cwd.split("/")[-1] path = cwd.rsplit("/", 1)[0] @@ -880,10 +866,14 @@ def parse(defect, all, path, code, verbose): raise Exception( f"Could not parse defect '{defect}' in directory '{path}'. Please either specify " f"a defect to parse (with option --defect), run from within a single defect " - f"directory (without setting --defect) or use the --all flag to parse all " - f"defects in the specified/current directory." + f"directory (without setting --defect) or run from the top-level directory to " + f"analyse all defects in the specified/current directory." ) from exc + else: + defect_dirs = _parse_defect_dirs(path) + _ = [io.parse_energies(defect, path, code, verbose=verbose) for defect in defect_dirs] + @snb.command( name="analyse", @@ -899,14 +889,6 @@ def parse(defect, all, path, code, verbose): type=str, default=None, ) -@click.option( - "--all", - "-a", - help="Analyse all defects present in specified directory", - default=False, - is_flag=True, - show_default=True, -) @click.option( "--path", "-p", @@ -928,7 +910,7 @@ def parse(defect, all, path, code, verbose): "-ref", help="Structure to use as a reference for comparison " "(to compute atomic displacements). Given as a key from " - "`defect_structures_dict` (e.g. '-0.4' for 'Bond_Distortion_-40.0%').", + "``defect_structures_dict`` (e.g. '-0.4' for ``'Bond_Distortion_-40.0%'``).", type=str, default="Unperturbed", show_default=True, @@ -941,10 +923,13 @@ def parse(defect, all, path, code, verbose): is_flag=True, show_default=True, ) -def analyse(defect, all, path, code, ref_struct, verbose): +def analyse(defect, path, code, ref_struct, verbose): """ - Generate `csv` file mapping each distortion to its final energy (in eV) and its - mean displacement (in Angstrom and relative to `ref_struct`). + Generate ``csv`` file mapping each distortion to its final energy (in eV) and its + mean displacement (in Angstrom and relative to ``ref_struct``). + + Can be run within a single defect folder, or in the top-level directory (either + specifying ``defect`` or looping through all defect folders). """ def analyse_single_defect(defect, path, code, ref_struct, verbose): @@ -970,23 +955,26 @@ def analyse_single_defect(defect, path, code, ref_struct, verbose): dataframe.to_csv(f"{path}/{defect}/{defect}.csv") # change name to results.csv? print(f"Saved results to {path}/{defect}/{defect}.csv") - if all: - defect_dirs = _parse_defect_dirs(path) - for defect in defect_dirs: - print(f"\nAnalysing {defect}...") - analyse_single_defect(defect, path, code, ref_struct, verbose) - - elif defect is None: + if ( + defect is None + and _running_in_defect_dir( + path=path, + warning_substring="calculations will only be analysed for the distortion folders in this " + "directory.", + ) + and path == "." + ): # assume current directory is the defect folder - if path != ".": - warnings.warn( - "`--path` option ignored when running from within defect folder (i.e. when `--defect` is " - "not specified." - ) cwd = os.getcwd() defect = cwd.split("/")[-1] path = cwd.rsplit("/", 1)[0] + if defect is None: # then all + defect_dirs = _parse_defect_dirs(path) + for defect in defect_dirs: + print(f"\nAnalysing {defect}...") + analyse_single_defect(defect, path, code, ref_struct, verbose) + defect = defect.strip("/") # Remove trailing slash if present # Check if defect present in path: if path == ".": @@ -1004,9 +992,9 @@ def analyse_single_defect(defect, path, code, ref_struct, verbose): except Exception as exc: raise Exception( f"Could not analyse defect '{defect}' in directory '{path}'. Please either specify a " - f"defect to analyse (with option --defect), run from within a single defect directory (" - f"without setting --defect) or use the --all flag to analyse all defects in the " - f"specified/current directory." + f"defect to analyse (with option --defect), run from within a single defect directory " + f"(without setting --defect) or run from the top-level directory to " + f"analyse all defects in the specified/current directory." ) from exc @@ -1024,20 +1012,12 @@ def analyse_single_defect(defect, path, code, ref_struct, verbose): type=str, default=None, ) -@click.option( - "--all", - "-a", - help="Analyse all defects present in current/specified directory", - default=False, - is_flag=True, - show_default=True, -) @click.option( "--min_energy", "-min", help="Minimum energy difference (in eV) between the ground-state " - "distortion and the `Unperturbed` structure to generate the " - "distortion plot, when `--all` is set.", + "distortion and the ``Unperturbed`` structure to generate the " + "distortion plot, when running from the top-level folder.", default=0.05, type=float, show_default=True, @@ -1071,7 +1051,7 @@ def analyse_single_defect(defect, path, code, ref_struct, verbose): @click.option( "--metric", "-m", - help="If the option `--colorbar` is specified, determines the criteria used" + help="If the option ``--colorbar`` is specified, determines the criteria used" " for the structural comparison. Can choose between the summed of atomic" " displacements ('disp') or the maximum distance between" " matched sites ('max_dist', default).", @@ -1098,7 +1078,7 @@ def analyse_single_defect(defect, path, code, ref_struct, verbose): @click.option( "--max_energy", "-max", - help="Maximum energy (in chosen `units`), relative to the " + help="Maximum energy (in chosen ``units``), relative to the " "unperturbed structure, to show on the plot.", type=float, default=0.5, @@ -1121,9 +1101,15 @@ def analyse_single_defect(defect, path, code, ref_struct, verbose): is_flag=True, show_default=True, ) +@click.option( + "--style-file", + "-s", + help="Path to a mplstyle file to use for the plot(s). Default is 'shakenbreak.mplstyle'.", + default=None, + type=click.Path(exists=True, dir_okay=False), +) def plot( defect, - all, min_energy, path, code, @@ -1134,17 +1120,33 @@ def plot( max_energy, no_title, verbose, + style_file, ): """ Generate energy vs distortion plots. Optionally, the structural similarity between configurations can be illustrated with a colorbar. + + Can be run within a single defect folder, or in the top-level directory + (either specifying ``defect`` or looping through all defect folders). """ - if all: - if defect is not None: - warnings.warn( - "The option `--defect` is ignored when using the `--all` flag. (All defects in " - f"`--path` = {path} will be plotted)." - ) + if style_file is None: + style_file = f"{os.path.dirname(os.path.abspath(__file__))}/shakenbreak.mplstyle" + + if ( + defect is None + and _running_in_defect_dir( + path=path, + warning_substring="calculations will only be analysed and plotted for the distortion folders " + "in this directory.", + ) + and path == "." + ): + # assume current directory is the defect folder + cwd = os.getcwd() + defect = cwd.split("/")[-1] + path = cwd.rsplit("/", 1)[0] + + if defect is None: # then all defect_dirs = _parse_defect_dirs(path) for defect in defect_dirs: if verbose: @@ -1166,20 +1168,10 @@ def plot( add_title=not no_title, max_energy_above_unperturbed=max_energy, verbose=verbose, + style_file=style_file, close_figures=True, # reduce memory usage with snb-plot with many defects at once ) - if defect is None: - # assume current directory is the defect folder - if path != ".": - warnings.warn( - "`--path` option ignored when running from within defect folder (" - "i.e. when `--defect` is not specified." - ) - cwd = os.getcwd() - defect = cwd.split("/")[-1] - path = cwd.rsplit("/", 1)[0] - defect = defect.strip("/") # Remove trailing slash if present # Check if defect present in path: if path == ".": @@ -1207,6 +1199,7 @@ def plot( units=units, add_title=not no_title, max_energy_above_unperturbed=max_energy, + style_file=style_file, verbose=verbose, ) except Exception: @@ -1228,14 +1221,15 @@ def plot( units=units, add_title=not no_title, max_energy_above_unperturbed=max_energy, + style_file=style_file, verbose=verbose, ) except Exception as exc: raise Exception( f"Could not analyse & plot defect '{defect}' in directory '{path}'. Please either " f"specify a defect to analyse (with option --defect), run from within a single " - f"defect directory (without setting --defect) or use the --all flag to analyse all " - f"defects in the specified/current directory." + f"defect directory (without setting --defect) or run from the top-level directory to " + f"analyse all defects in the specified/current directory." ) from exc @@ -1272,7 +1266,7 @@ def plot( "--min_energy", "-min", help="Minimum energy difference (in eV) between the ground-state" - " defect structure, relative to the `Unperturbed` structure," + " defect structure, relative to the ``Unperturbed`` structure," " to consider it as having found a new energy-lowering" " distortion.", type=float, @@ -1304,7 +1298,7 @@ def regenerate(path, code, filename, min_energy, metastable, verbose): Considers all identified energy-lowering distortions for each defect in each charge state, and screens out duplicate distorted structures found for multiple charge states. Defect folder names should end with - charge state after an underscore (e.g. `vac_1_Cd_0` or `Va_Cd_0` etc). + charge state after an underscore (e.g. ``vac_1_Cd_0`` or ``Va_Cd_0`` etc). """ if path == ".": path = os.getcwd() # more verbose error if no defect folders found in path @@ -1327,6 +1321,37 @@ def regenerate(path, code, filename, min_energy, metastable, verbose): ) +def _running_in_defect_dir(path: str = ".", warning_substring: str = ""): + warning_substring = warning_substring or ( + "the groundstate structure from the distortion folders in this directory will be generated." + ) + if any( + dir + for dir in os.listdir() + if os.path.isdir(dir) + and any(substring in dir for substring in ["Bond_Distortion", "Rattled", "Unperturbed", "Dimer"]) + ): # distortion subfolders in cwd + # check if defect folders also in cwd + for dir in [dir for dir in os.listdir() if os.path.isdir(dir)]: + defect_name = None + try: + defect_name = format_defect_name(dir, include_site_info_in_name=False) + except Exception: + with contextlib.suppress(Exception): + defect_name = format_defect_name(f"{dir}_0", include_site_info_in_name=False) + if defect_name: # recognised defect folder found in cwd, warn user and proceed + # assuming they want to just parse the distortion folders in cwd + warnings.warn( + f"Both distortion folders and defect folders (i.e. {dir}) were found in the current " + f"directory. The defect folders will be ignored and {warning_substring}" + ) + break + + return True # current directory is the defect folder + + return False + + @snb.command( name="groundstate", context_settings=CONTEXT_SETTINGS, @@ -1335,8 +1360,8 @@ def regenerate(path, code, filename, min_energy, metastable, verbose): @click.option( "--directory", "-d", - help="Folder name where the ground state structure will be written to. If using with `doped`, then " - "typically recommended to set to `vasp_nkred_std` or `vasp_std`.", + help="Folder name where the ground state structure will be written to. If using with ``doped``, then " + "typically recommended to set to ``vasp_nkred_std`` or ``vasp_std``.", type=str, default="Groundstate", show_default=True, @@ -1381,45 +1406,24 @@ def groundstate( ): """ Generate folders with the identified ground state structures. A folder (named - `directory`) is created with the ground state structure (named - `groundstate_filename`) for each defect present in the specified path (if `path` is + ``directory``) is created with the ground state structure (named + ``groundstate_filename``) for each defect present in the specified path (if ``path`` is the top-level directory) or for the current defect if run within a defect folder. - If the name of the structure/output files is not specified, the code assumes `CONTCAR` + If the name of the structure/output files is not specified, the code assumes ``CONTCAR`` (e.g. geometry optimisations performed with VASP). If using a different code, please specify the name of the structure/output files. """ # determine if running from within a defect directory or from the top level directory - if any( - dir - for dir in os.listdir() - if os.path.isdir(dir) - and any(substring in dir for substring in ["Bond_Distortion", "Rattled", "Unperturbed", "Dimer"]) - ): # distortion subfolders in cwd - # check if defect folders also in cwd - for dir in [dir for dir in os.listdir() if os.path.isdir(dir)]: - defect_name = None - try: - defect_name = format_defect_name(dir, include_site_info_in_name=False) - except Exception: - with contextlib.suppress(Exception): - defect_name = format_defect_name(f"{dir}_0", include_site_info_in_name=False) - if defect_name: # recognised defect folder found in cwd, warn user and proceed - # assuming they want to just parse the distortion folders in cwd - warnings.warn( - f"Both distortion folders and defect folders (i.e. {dir}) were " - f"found in the current directory. The defect folders will be " - f"ignored and the groundstate structure from the distortion folders " - f"in this directory will be generated." - ) - break - - # assume current directory is the defect folder - if path != ".": - warnings.warn( - "`--path` option ignored when running from within defect folder (assumed to be " - "the case here as distortion folders found in current directory)." - ) - + if ( + _running_in_defect_dir( + path, + warning_substring=( + "the groundstate structure from the distortion folders in this directory will be " + "generated." + ), + ) + and path == "." + ): energy_lowering_distortions.write_groundstate_structure( all=False, output_path=os.getcwd(), diff --git a/shakenbreak/distortions.py b/shakenbreak/distortions.py index e2c40163..599a4b67 100644 --- a/shakenbreak/distortions.py +++ b/shakenbreak/distortions.py @@ -1,19 +1,14 @@ """Module containing functions for applying distortions to defect structures.""" import os -import sys import warnings -from typing import Optional +from typing import Optional, Union import numpy as np from ase.neighborlist import NeighborList from hiphive.structure_generation.rattle import _probability_mc_rattle, generate_mc_rattled_structures from pymatgen.analysis.local_env import CrystalNN, MinimumDistanceNN from pymatgen.core.structure import Structure -from pymatgen.io.ase import AseAtomsAdaptor # could be removed to use the Structure.to/from_ase_atoms() - -# methods added in pymatgen 2024.5.31, but then not backwards compatible. Will refactor to this if/when -# pymatgen>=2024.5.31 is a necessary requirement. def _warning_on_one_line(message, category, filename, lineno, file=None, line=None): @@ -24,20 +19,214 @@ def _warning_on_one_line(message, category, filename, lineno, file=None, line=No warnings.formatwarning = _warning_on_one_line +def _get_ase_defect_structure( + structure: Structure, + site_index: Optional[int] = None, # starting from 1 + frac_coords: Optional[np.array] = None, # use frac coords for vacancies +): + """ + Convenience function to get an ASE Atoms object of the input structure + and the defect site index (0-indexed). + + If the defect is a vacancy (i.e. ``frac_coords`` is provided), a fake + "V" atom is appended to the structure for consistent behaviour with + substitutions and interstitials. + + Returns a tuple of: + + - the ASE Atoms object of the input structure, with the defect site appended as + a fake atom for consistent behaviour with substitutions and interstitials as + for vacancies + - the defect site index (0-indexed) + + Args: + structure (:obj:`~pymatgen.core.structure.Structure`): + Defect structure as a pymatgen object + site_index (:obj:`int`, optional): + Index of defect site in structure (for substitutions or + interstitials), counting from 1. + frac_coords (:obj:`numpy.ndarray`, optional): + Fractional coordinates of the defect site in the structure (for + vacancies). + + Returns: + :obj:`tuple`: + - the ASE Atoms object of the input structure, with the defect site appended as + a fake atom for consistent behaviour with substitutions and interstitials as + for vacancies + - the defect site index (0-indexed) + """ + input_structure_ase = structure.to_ase_atoms() + + if site_index is not None: # site_index can be 0 + defect_site_index = site_index - 1 # Align atom number with python 0-indexing + elif isinstance(frac_coords, np.ndarray): # Only for vacancies! + input_structure_ase.append("V") # fake "V" at vacancy, for consistent behaviour with subs/ints + input_structure_ase.positions[-1] = np.dot(frac_coords, input_structure_ase.cell) + defect_site_index = len(input_structure_ase) - 1 + else: + raise ValueError( + "Insufficient information to apply bond distortions, no `site_index` or `frac_coords` " + "provided." + ) + + return input_structure_ase, defect_site_index + + +def _get_nns_to_distort( + structure: Structure, + num_nearest_neighbours: int, + site_index: Optional[int] = None, # starting from 1 + frac_coords: Optional[np.array] = None, # use frac coords for vacancies + distorted_element: Optional[Union[str, list]] = None, + distorted_atoms: Optional[list] = None, +): + """ + Convenience function to get the nearest neighbours to distort, based on the input + parameters. + + The nearest neighbours to distort are chosen by taking all sites (or those + matching ``distorted_element`` / ``distorted_atoms``, if provided), then sorting + by distance to the defect site (rounded to 2 decimal places) and site index, and + then taking the first ``num_nearest_neighbours`` of these. If there are multiple + non-degenerate combinations of (nearly) equidistant NNs to distort (e.g. cis vs + trans when distorting 2 NNs in a 4 NN square coordination), then the combination + with distorted NNs closest to each other is chosen. + + Returns a tuple of: + + - a list of the nearest neighbours to distort in the form of a list of tuples + containing the distance to the defect site, the site index (1-indexed), and the + element symbol + - the defect site index (0-indexed) + - the ASE Atoms object of the input structure, with the defect site appended as + a fake atom for consistent behaviour with substitutions and interstitials as + for vacancies + + Args: + structure (:obj:`~pymatgen.core.structure.Structure`): + Defect structure as a pymatgen object + num_nearest_neighbours (:obj:`int`): + Number of defect nearest neighbours to apply bond distortions to + site_index (:obj:`int`, optional): + Index of defect site in structure (for substitutions or + interstitials), counting from 1. + frac_coords (:obj:`numpy.ndarray`, optional): + Fractional coordinates of the defect site in the structure (for + vacancies). + distorted_element (:obj:`str`, optional): + Neighbouring element(s) to distort, as a string or list of strings. + If None, the closest neighbours to the defect will be chosen. + (Default: None) + distorted_atoms (:obj:`list`, optional): + List of atom indices to distort. If None, the closest neighbours to + the defect will be chosen. (Default: None) + + Returns: + :obj:`tuple`: + - a list of the nearest neighbours to distort in the form of a list of tuples + containing the distance to the defect site, the site index (1-indexed), and the + element symbol + - the defect site index (0-indexed) + - the ASE Atoms object of the input structure, with the defect site appended as + a fake atom for consistent behaviour with substitutions and interstitials as + for vacancies + """ + input_structure_ase, defect_site_index = _get_ase_defect_structure(structure, site_index, frac_coords) + + if distorted_atoms and len(distorted_atoms) < num_nearest_neighbours: + warnings.warn( + f"Only {len(distorted_atoms)} atoms were specified to distort in `distorted_atoms`, " + f"but `num_nearest_neighbours` was set to {num_nearest_neighbours}. " + f"Will overide the indices specified in `distorted_atoms` and distort the " + f"{num_nearest_neighbours} closest neighbours to the defect site." + ) + distorted_atoms = None + + if distorted_atoms is None: + distorted_atoms = list(range(len(input_structure_ase))) + + if distorted_element: + if isinstance(distorted_element, str): + distorted_element = [distorted_element] + distorted_atoms = [ + i + for i in distorted_atoms + if input_structure_ase.get_chemical_symbols()[i] in distorted_element + ] + if not distorted_atoms: + raise ValueError( + f"No atoms of `distorted_element` = {distorted_element} found in the defect structure, " + f"cannot apply bond distortions." + ) + + nearest_neighbours = sorted( + [ + ( + input_structure_ase.get_distance(defect_site_index, index, mic=True), + index + 1, + input_structure_ase.get_chemical_symbols()[index], + ) + for index in distorted_atoms + if index != defect_site_index # ignore defect itself + ], + key=lambda tup: (round(tup[0], 2), tup[1]), # sort by distance (to 2 dp), then by index + ) + furthest_nn_to_distort = nearest_neighbours[num_nearest_neighbours - 1] + + # check if there are non-degenerate combinations of (nearly) equidistant NNs to distort: + # non-degenerate combinations only possible when more than one NN to distort, and more than one + # equidistant NN _not_ being distorted (e.g. distorting 3 of 4 equidistant NNs does not have + # non-degenerate combinations): + if ( + num_nearest_neighbours > 1 + and len([nn_tup for nn_tup in nearest_neighbours if nn_tup[0] <= furthest_nn_to_distort[0] * 1.05]) + < num_nearest_neighbours - 1 + ): + nearest_neighbour = nearest_neighbours[0] + # now re-sort by NN distance (to 2 dp), and then distance to first distorted NN (to 2 dp) + # this is to ensure a deterministic choice in NNs to distort, in cases of degenerate choices of NNs + # in terms of distance, but non-degenerate in terms of combination of NNs to distort + # e.g. square coordination, indexed clockwise 1-4, then distorting 1 & 2 is different to 1 & 3 ( + # i.e. cis vs trans essentially); ShakeNBreak default is to favour NNs which are closer to each + # other (i.e. favouring cis distortions, and thus dimer/trimer formation or other cluster-type + # rebonding) + nearest_neighbours = sorted( + nearest_neighbours, + key=lambda tup: ( + round(tup[0], 2), + round(input_structure_ase.get_distance(nearest_neighbour[1] - 1, tup[1] - 1, mic=True), 2), + tup[1], + ), + ) + + nns_to_distort = nearest_neighbours[:num_nearest_neighbours] # defect site itself already cut + + return nns_to_distort, defect_site_index, input_structure_ase + + def distort( structure: Structure, num_nearest_neighbours: int, distortion_factor: float, site_index: Optional[int] = None, # starting from 1 frac_coords: Optional[np.array] = None, # use frac coords for vacancies - distorted_element: Optional[str] = None, + distorted_element: Optional[Union[str, list]] = None, distorted_atoms: Optional[list] = None, verbose: Optional[bool] = False, ) -> dict: """ - Applies bond distortions to `num_nearest_neighbours` of the defect (specified - by `site_index` (for substitutions or interstitials) or `frac_coords` - (for vacancies)). + Applies bond distortions to ``num_nearest_neighbours`` of the defect (specified + by ``site_index`` (for substitutions or interstitials, counting from 1) or + ``frac_coords`` (for vacancies)). + + The nearest neighbours to distort are chosen by taking all sites (or those + matching ``distorted_element`` / ``distorted_atoms``, if provided), then sorting + by distance to the defect site (rounded to 2 decimal places) and site index, and + then taking the first ``num_nearest_neighbours`` of these. If there are multiple + non-degenerate combinations of (nearly) equidistant NNs to distort (e.g. cis vs + trans when distorting 2 NNs in a 4 NN square coordination), then the combination + with distorted NNs closest to each other is chosen. Args: structure (:obj:`~pymatgen.core.structure.Structure`): @@ -55,8 +244,9 @@ def distort( Fractional coordinates of the defect site in the structure (for vacancies). distorted_element (:obj:`str`, optional): - Neighbouring element to distort. If None, the closest neighbours to - the defect will be chosen. (Default: None) + Neighbouring element(s) to distort, as a string or list of strings. + If None, the closest neighbours to the defect will be chosen. + (Default: None) distorted_atoms (:obj:`list`, optional): List of atom indices to distort. If None, the closest neighbours to the defect will be chosen. (Default: None) @@ -67,92 +257,28 @@ def distort( :obj:`dict`: Dictionary with distorted defect structure and the distortion parameters. """ - aaa = AseAtomsAdaptor() - input_structure_ase = aaa.get_atoms(structure) - - if site_index is not None: # site_index can be 0 - atom_number = site_index - 1 # Align atom number with python 0-indexing - elif isinstance(frac_coords, np.ndarray): # Only for vacancies! - input_structure_ase.append("V") # fake "V" at vacancy - input_structure_ase.positions[-1] = np.dot(frac_coords, input_structure_ase.cell) - atom_number = len(input_structure_ase) - 1 - else: - raise ValueError( - "Insufficient information to apply bond distortions, no `site_index`" - " or `frac_coords` provided." - ) - - neighbours = num_nearest_neighbours + 1 # Prevent self-counting of the defect atom itself - if distorted_atoms and len(distorted_atoms) >= num_nearest_neighbours: - nearest = [ - ( - round(input_structure_ase.get_distance(atom_number, index, mic=True), 4), - index + 1, - input_structure_ase.get_chemical_symbols()[index], - ) - for index in distorted_atoms - ] - nearest = sorted(nearest, key=lambda tup: tup[0])[0:num_nearest_neighbours] - else: - if distorted_atoms: - warnings.warn( - f"Only {len(distorted_atoms)} atoms were specified to distort in `distorted_atoms`, " - f"but `num_nearest_neighbours` was set to {num_nearest_neighbours}. " - f"Will overide the indices specified in `distorted_atoms` and distort the " - f"{num_nearest_neighbours} closest neighbours to the defect site." - ) - distances = [ # Get all distances between the selected atom and all other atoms - ( - round(input_structure_ase.get_distance(atom_number, index, mic=True), 4), - index + 1, # Indices start from 1 - symbol, - ) - for index, symbol in zip( - list(range(len(input_structure_ase))), - input_structure_ase.get_chemical_symbols(), - ) - ] - distances = sorted(distances, key=lambda tup: tup[0]) # Sort the distances shortest->longest - - if distorted_element: # filter the neighbours that match the element criteria and are - # closer than 4.5 Angstroms - nearest = [] # list of nearest neighbours - for dist, index, element in distances[1:]: # starting from 1 to exclude defect atom - if element == distorted_element and dist < 4.5 and len(nearest) < num_nearest_neighbours: - nearest.append((dist, index, element)) - - # if the number of nearest neighbours not reached, add other neighbouring - # elements - if len(nearest) < num_nearest_neighbours: - for i in distances[1:]: - if len(nearest) < num_nearest_neighbours and i not in nearest and i[0] < 4.5: - nearest.append(i) - warnings.warn( - f"{distorted_element} was specified as the nearest neighbour " - f"element to distort, with `distortion_factor` {distortion_factor} " - f"but did not find `num_nearest_neighbours` " - f"({num_nearest_neighbours}) of these elements within 4.5 \u212B " - f"of the defect site. For the remaining neighbours to distort, " - f"we ignore the elemental identity. The final distortion information is:" - ) - sys.stderr.flush() # ensure warning message printed before distortion info - verbose = True - else: - nearest = distances[1:neighbours] # Extract the nearest neighbours according to distance + nns_to_distort, defect_site_index, input_structure_ase = _get_nns_to_distort( + structure, + num_nearest_neighbours, + site_index, + frac_coords, + distorted_element, + distorted_atoms, + ) distorted = [ - (i[0] * distortion_factor, i[1], i[2]) for i in nearest + (i[0] * distortion_factor, i[1], i[2]) for i in nns_to_distort ] # Distort the nearest neighbour distances according to the distortion factor for i in distorted: input_structure_ase.set_distance( - atom_number, i[1] - 1, i[0], fix=0, mic=True + defect_site_index, i[1] - 1, i[0], fix=0, mic=True ) # Set the distorted distances in the ASE Atoms object if isinstance(frac_coords, np.ndarray): input_structure_ase.pop(-1) # remove fake V from vacancy structure - distorted_structure = aaa.get_structure(input_structure_ase) - distorted_atoms = [[i[1], i[2]] for i in nearest] # element and site number + distorted_structure = Structure.from_ase_atoms(input_structure_ase) + distorted_atoms = [[i[1], i[2]] for i in nns_to_distort] # element and site number # Create dictionary with distortion info & distorted structure bond_distorted_defect = { @@ -167,13 +293,12 @@ def distort( bond_distorted_defect["defect_frac_coords"] = frac_coords if verbose: - distorted = [(round(i[0], 2), i[1], i[2]) for i in distorted] - nearest = [(round(i[0], 2), i[1], i[2]) for i in nearest] # round numbers + distorted_info = [(round(i[0], 2), i[1], i[2]) for i in distorted] + nearest_info = [(round(i[0], 2), i[1], i[2]) for i in nns_to_distort] # round numbers print( - f"""\tDefect Site Index / Frac Coords: { - site_index or np.around(frac_coords, decimals=3)} - Original Neighbour Distances: {nearest} - Distorted Neighbour Distances:\n\t{distorted}""" + f"""\tDefect Site Index / Frac Coords: {site_index or np.around(frac_coords, decimals=3)} + Original Neighbour Distances: {nearest_info} + Distorted Neighbour Distances:\n\t{distorted_info}""" ) return bond_distorted_defect @@ -183,12 +308,14 @@ def apply_dimer_distortion( structure: Structure, site_index: Optional[int] = None, frac_coords: Optional[np.array] = None, # use frac coords for vacancies + verbose: Optional[bool] = False, ) -> dict: """ Apply a dimer distortion to a defect structure. - The defect nearest neighbours are determined, from them the two closest - in distance are selected, which are pushed towards each other so that - their distance is 2.0 A. + + The defect nearest neighbours are determined (using ``CrystalNN`` with + default settings), from them the two closest in distance are selected, + which are pushed towards each other so that their distance is 2.0 Å. Args: structure (Structure): @@ -200,40 +327,31 @@ def apply_dimer_distortion( Fractional coordinates of the defect site in the structure (for vacancies). Defaults to None. + verbose (Optional[bool], optional): + Print information about the dimer distortion. + Defaults to False. Returns: obj:`Structure`: Distorted dimer structure """ - # Get ase atoms object - aaa = AseAtomsAdaptor() - input_structure_ase = aaa.get_atoms(structure) - - if site_index is not None: # site_index can be 0 - atom_number = site_index - 1 # Align atom number with python 0-indexing - elif type(frac_coords) in [list, tuple, np.ndarray]: # Only for vacancies! - input_structure_ase.append("V") # fake "V" at vacancy - input_structure_ase.positions[-1] = np.dot(frac_coords, input_structure_ase.cell) - atom_number = len(input_structure_ase) - 1 - else: - raise ValueError( - "Insufficient information to apply bond distortions, no `site_index`" - " or `frac_coords` provided." - ) + # Note: Future work could extend this to allow the use of ``distorted_element`` and ``distorted_atoms`` + input_structure_ase, defect_site_index = _get_ase_defect_structure(structure, site_index, frac_coords) # Get defect nn - struct = aaa.get_structure(input_structure_ase) + input_structure = Structure.from_ase_atoms(input_structure_ase) cnn = CrystalNN() - sites = [d["site"] for d in cnn.get_nn_info(struct, atom_number)] + sites = [d["site"] for d in cnn.get_nn_info(input_structure, defect_site_index)] # Get distances between NN distances = {} + sites = sorted(sites, key=lambda x: x.index) # sort by site index for deterministic behaviour for i, site in enumerate(sites): for other_site in sites[i + 1 :]: distances[(site.index, other_site.index)] = site.distance(other_site) - # Get defect NN with smallest distance and lowest indices: + # Get defect NN with the smallest distance and lowest indices: site_indexes = tuple( - sorted(min(distances, key=lambda k: (round(distances.get(k, 10), 3), k[0], k[1]))) + sorted(min(distances, key=lambda k: (round(distances.get(k, 10), 2), k[0], k[1]))) ) # Set their distance to 2 A input_structure_ase.set_distance( @@ -242,7 +360,7 @@ def apply_dimer_distortion( if type(frac_coords) in [list, tuple, np.ndarray]: input_structure_ase.pop(-1) # remove fake V from vacancy structure - distorted_structure = aaa.get_structure(input_structure_ase) + distorted_structure = Structure.from_ase_atoms(input_structure_ase) # Create dictionary with distortion info & distorted structure # Get distorted atoms distorted_structure_wout_oxi = distorted_structure.copy() @@ -261,7 +379,14 @@ def apply_dimer_distortion( bond_distorted_defect["defect_site_index"] = site_index elif type(frac_coords) in [np.ndarray, list]: bond_distorted_defect["defect_frac_coords"] = frac_coords - + if verbose: + original_distance = round(structure.get_distance(site_indexes[0], site_indexes[1]), 2) + print( + f"""\tDefect Site Index / Frac Coords: {site_index or np.around(frac_coords, decimals=3)} + Dimer Distorted Neighbours: {distorted_atoms} + Original Distance: {original_distance} + Distorted Neighbour Distances: {2.0}""" + ) return bond_distorted_defect @@ -281,7 +406,7 @@ def rattle( """ Given a pymatgen Structure object, apply random displacements to all atomic positions, with the displacement distances randomly drawn from a Gaussian - distribution of standard deviation `stdev`. + distribution of standard deviation ``stdev``. Args: structure (:obj:`~pymatgen.core.structure.Structure`): @@ -296,7 +421,7 @@ def rattle( structure. Monte Carlo rattle moves that put atoms at distances less than this will be heavily penalised. Default is to set this to 80% of the nearest neighbour - distance in the defect supercell (ignoring interstitials). + distance in the defect supercell. verbose (:obj:`bool`): Whether to print information about the rattling process. n_iter (:obj:`int`): @@ -319,7 +444,7 @@ def rattle( (Default: 2.0) max_attempts (:obj:`int`): Maximum Monte Carlo rattle move attempts allowed for a single atom; - if this limit is reached an `Exception` is raised. + if this limit is reached an ``Exception`` is raised. (Default: 5000) seed (:obj:`int`): Seed for NumPy random state from which random rattle displacements @@ -327,10 +452,9 @@ def rattle( Returns: :obj:`Structure`: - Rattled pymatgen Structure object + Rattled ``pymatgen`` ``Structure`` object """ - aaa = AseAtomsAdaptor() - ase_struct = aaa.get_atoms(structure) + ase_struct = structure.to_ase_atoms() if active_atoms is not None: # select only the distances involving active_atoms distance_matrix = structure.distance_matrix[active_atoms, :][:, active_atoms] @@ -410,7 +534,7 @@ def rattle( else: raise ex - return aaa.get_structure(rattled_ase_struct) + return Structure.from_ase_atoms(rattled_ase_struct) def _local_mc_rattle_displacements( @@ -451,7 +575,7 @@ def _local_mc_rattle_displacements( for not generating structures where two or more have swapped positions. max_attempts (:obj:`int`): limit for how many attempted rattle moves are allowed a single atom; - if this limit is reached an `Exception` is raised. + if this limit is reached an ``Exception`` is raised. active_atoms (:obj:`list`): list of which atomic indices should undergo Monte Carlo rattling nbr_cutoff (:obj:`float`): @@ -463,7 +587,7 @@ def _local_mc_rattle_displacements( Returns: :obj:`numpy.ndarray`: - atomic displacements (`Nx3`) + atomic displacements (Nx3) """ def scale_stdev(disp, r_min, r): @@ -476,9 +600,7 @@ def scale_stdev(disp, r_min, r): r = r_min return disp * r_min / r - # Transform to pymatgen structure - aaa = AseAtomsAdaptor() - structure = aaa.get_structure(atoms) + structure = Structure.from_ase_atoms(atoms) # transform to pymatgen structure dist_defect_to_nn = max( structure[site_index].distance(_["site"]) for _ in MinimumDistanceNN().get_nn_info(structure, site_index) @@ -555,15 +677,15 @@ def _generate_local_mc_rattled_structures( Compared to the standard Monte Carlo rattle, here the displacements tail off as we move away from the defect site. - Rattling atom `i` is carried out as a Monte Carlo move that is + Rattling atom ``i`` is carried out as a Monte Carlo move that is accepted with a probability determined from the minimum - interatomic distance :math:`d_{ij}`. If :math:`\\min(d_{ij})` is - smaller than :math:`d_{min}` the move is only accepted with a low + interatomic distance :math:`d_{ij}`. If :math:`\\min(d_{ij})`` is + smaller than :math:`d_{min}`` the move is only accepted with a low probability. This process is repeated for each atom a number of times meaning the magnitude of the final displacements is not *directly* - connected to `rattle_std`. + connected to ``rattle_std``. This function has been adapted from https://gitlab.com/materials-modeling/hiphive @@ -575,8 +697,8 @@ def _generate_local_mc_rattled_structures( Notes: The procedure implemented here might not generate a symmetric - distribution for the displacements `kwargs` will be forwarded to - `mc_rattle` (see user guide for a detailed explanation) + distribution for the displacements ``kwargs`` will be forwarded to + ``mc_rattle`` (see user guide for a detailed explanation) Args: atoms (:obj:`ase.Atoms`): @@ -599,7 +721,7 @@ def _generate_local_mc_rattled_structures( n_iter (:obj:`int`): Number of Monte Carlo cycles **kwargs: - Additional keyword arguments to be passed to `mc_rattle` + Additional keyword arguments to be passed to ``mc_rattle`` Returns: :obj:`list`: @@ -636,7 +758,7 @@ def local_mc_rattle( """ Given a pymatgen Structure object, apply random displacements to all atomic positions, with the displacement distances randomly drawn from a Gaussian - distribution of standard deviation `stdev`. The random displacements + distribution of standard deviation ``stdev``. The random displacements tail off as we move away from the defect site. Args: @@ -680,7 +802,7 @@ def local_mc_rattle( (Default: 2.0) max_attempts (:obj:`int`): Maximum Monte Carlo rattle move attempts allowed for a single atom; - if this limit is reached an `Exception` is raised. + if this limit is reached an ``Exception`` is raised. (Default: 5000) seed (:obj:`int`): Seed for NumPy random state from which random rattle displacements @@ -690,8 +812,7 @@ def local_mc_rattle( :obj:`Structure`: Rattled pymatgen Structure object """ - aaa = AseAtomsAdaptor() - ase_struct = aaa.get_atoms(structure) + ase_struct = structure.to_ase_atoms() if active_atoms is not None: # select only the distances involving active_atoms distance_matrix = structure.distance_matrix[active_atoms, :][:, active_atoms] @@ -701,11 +822,11 @@ def local_mc_rattle( sorted_distances = np.sort(distance_matrix[distance_matrix > 0.5].flatten()) if isinstance(site_index, int): - atom_number = site_index - 1 # Align atom number with python 0-indexing + defect_site_index = site_index - 1 # Align atom number with python 0-indexing elif isinstance(frac_coords, np.ndarray): # Only for vacancies! ase_struct.append("V") # fake "V" at vacancy ase_struct.positions[-1] = np.dot(frac_coords, ase_struct.cell) - atom_number = len(ase_struct) - 1 + defect_site_index = len(ase_struct) - 1 else: raise ValueError( "Insufficient information to apply local rattle, no `site_index` or `frac_coords` provided." @@ -736,7 +857,7 @@ def local_mc_rattle( try: local_rattled_ase_struct = _generate_local_mc_rattled_structures( ase_struct, - site_index=atom_number, + site_index=defect_site_index, n_configs=1, rattle_std=stdev, d_min=d_min, @@ -754,7 +875,7 @@ def local_mc_rattle( reduced_d_min = sorted_distances[0] + stdev local_rattled_ase_struct = _generate_local_mc_rattled_structures( ase_struct, - site_index=atom_number, + site_index=defect_site_index, n_configs=1, rattle_std=stdev, d_min=reduced_d_min, @@ -779,4 +900,4 @@ def local_mc_rattle( if isinstance(frac_coords, np.ndarray): local_rattled_ase_struct.pop(-1) # remove fake V from vacancy structure - return aaa.get_structure(local_rattled_ase_struct) + return Structure.from_ase_atoms(local_rattled_ase_struct) diff --git a/shakenbreak/energy_lowering_distortions.py b/shakenbreak/energy_lowering_distortions.py index c857347e..ddad2145 100644 --- a/shakenbreak/energy_lowering_distortions.py +++ b/shakenbreak/energy_lowering_distortions.py @@ -51,7 +51,7 @@ def _format_distortion_directory_name( def read_defects_directories(output_path: str = "./") -> dict: """ - Reads all defect folders in the `output_path` directory and stores defect + Reads all defect folders in the ``output_path`` directory and stores defect names and charge states in a dictionary. Args: @@ -99,14 +99,14 @@ def _compare_distortion( gs_distortion: float, gs_struct: Structure, low_energy_defects: dict, - stol: float = 0.5, min_dist: float = 0.2, verbose: bool = False, + **sm_kwargs, ) -> dict: """ - Compare the ground state distortion (`gs_distortion`) to the other - favourable distortions stored in `low_energy_defects`. If different, - add distortion as separate entry to `low_energy_defects`. + Compare the ground state distortion (``gs_distortion``) to the other + favourable distortions stored in ``low_energy_defects``. If different, + add distortion as separate entry to ``low_energy_defects``. If same, store together with the other similar distortions. Args: @@ -125,17 +125,16 @@ def _compare_distortion( pymatgen Structure object of the ground state configuration. low_energy_defects (:obj:`dict): Dictionary storing all unique, energy-lowering distortions. - stol (:obj:`float`): - Site-matching tolerance for structure matching. Site - tolerance defined as the fraction of the average free length - per atom := ( V / Nsites ) ** (1/3). - (Default: 0.5) min_dist (:obj:`float`): Minimum atomic displacement threshold between structures, in order to consider them not matching (in Å, default = 0.2 Å). verbose (:obj:`bool`): Whether to print information message about structures being compared. (Default: False) + **sm_kwargs: + Additional keyword arguments to pass to ``_scan_sm_stol_till_match`` + in ``doped`` (used for ultra-fast structure matching), such as + ``min_stol``, ``max_stol``, ``stol_factor`` etc. Returns: :obj:`dict` @@ -153,9 +152,9 @@ def _compare_distortion( ], # just select the first structure in # each list as these structures have already been # found to match - stol=stol, min_dist=min_dist, verbose=verbose, + **sm_kwargs, ) comparison_dicts_dict[i] = struct_comparison_dict @@ -210,9 +209,9 @@ def _prune_dict_across_charges( code: str = "VASP", structure_filename: str = "CONTCAR", output_path: str = ".", - stol: float = 0.5, min_dist: float = 0.2, verbose: bool = False, + **sm_kwargs, ) -> dict: """ Screen through defects to check if any lower-energy distorted structures @@ -237,11 +236,6 @@ def _prune_dict_across_charges( structure_filename (:obj:`str`, optional): Name of the file containing the structure. (Default: CONTCAR) - stol (:obj:`float`): - Site-matching tolerance for structure matching. Site - tolerance defined as the fraction of the average free length - per atom := ( V / Nsites ) ** (1/3). - (Default: 0.5) min_dist (:obj:`float`): Minimum atomic displacement threshold between structures, in order to consider them not matching (in Å, default = 0.2 Å). @@ -249,6 +243,10 @@ def _prune_dict_across_charges( Whether to print verbose information about parsed defect structures for energy-lowering distortions, if found. (Default: False) + **sm_kwargs: + Additional keyword arguments to pass to ``_scan_sm_stol_till_match`` + in ``doped`` (used for ultra-fast structure matching), such as + ``min_stol``, ``max_stol``, ``stol_factor`` etc. Returns: :obj:`dict` @@ -286,9 +284,9 @@ def _prune_dict_across_charges( output_path, code=code, structure_filename=structure_filename, - stol=stol, min_dist=min_dist, verbose=verbose, + **sm_kwargs, ) if comparison_results[0] is not None: break @@ -329,11 +327,11 @@ def get_energy_lowering_distortions( code: str = "vasp", structure_filename: str = "CONTCAR", min_e_diff: float = 0.05, - stol: float = 0.5, min_dist: float = 0.2, verbose: bool = False, write_input_files: bool = False, metastable: bool = False, + **sm_kwargs, ) -> dict: """ Convenience function to identify defect species undergoing @@ -347,7 +345,7 @@ def get_energy_lowering_distortions( defect_charges_dict (:obj:`dict`, optional): Dictionary matching defect name(s) to list(s) of their charge states. (e.g {"Int_Sb_1":[0,+1,+2]} etc). If not - specified, all defects present in `output_path` will be + specified, all defects present in ``output_path`` will be parsed. (Default: None) output_path (:obj:`str`): @@ -362,16 +360,11 @@ def get_energy_lowering_distortions( structure_filename (:obj:`str`, optional): Name of the file containing the structure. (Default: CONTCAR) - min_e_diff (:obj: `float`): + min_e_diff (:obj:`float`): Minimum energy difference (in eV) between the ground-state - defect structure, relative to the `Unperturbed` structure, + defect structure, relative to the ``Unperturbed`` structure, to consider it as having found a new energy-lowering distortion. Default is 0.05 eV. - stol (:obj:`float`): - Site-matching tolerance for structure matching. Site - tolerance defined as the fraction of the average free length - per atom := ( V / Nsites ) ** (1/3). - (Default: 0.5) min_dist (:obj:`float`): Minimum atomic displacement threshold between structures, in order to consider them not matching (in Å, default = 0.2 Å). @@ -387,6 +380,10 @@ def get_energy_lowering_distortions( energy-lowering distortions, as these can become ground-state distortions for other charge states. (Default: False) + **sm_kwargs: + Additional keyword arguments to pass to ``_scan_sm_stol_till_match`` + in ``doped`` (used for ultra-fast structure matching), such as + ``min_stol``, ``max_stol``, ``stol_factor`` etc. Returns: :obj:`dict`: @@ -493,9 +490,9 @@ def get_energy_lowering_distortions( gs_distortion=gs_distortion, gs_struct=gs_struct, low_energy_defects=low_energy_defects, - stol=stol, min_dist=min_dist, verbose=verbose, + **sm_kwargs, ) else: # if defect not in dict, add it @@ -565,9 +562,9 @@ def get_energy_lowering_distortions( gs_distortion=distortion, gs_struct=struct, low_energy_defects=low_energy_defects, - stol=stol, min_dist=min_dist, verbose=verbose, + **sm_kwargs, ) else: # if defect not in dict, add it @@ -621,8 +618,8 @@ def get_energy_lowering_distortions( code=code, structure_filename=structure_filename, output_path=output_path, - stol=stol, min_dist=min_dist, + **sm_kwargs, ) # Write input files for the identified distortions @@ -642,13 +639,13 @@ def compare_struct_to_distortions( output_path: str = ".", code: str = "vasp", structure_filename: str = "CONTCAR", - stol: float = 0.5, min_dist: float = 0.2, verbose: bool = False, + **sm_kwargs, ) -> tuple: """ Compares the ground-state structure found for a certain defect charge - state with all relaxed bond-distorted structures for `defect_species`, + state with all relaxed bond-distorted structures for ``defect_species``, to avoid redundant work (testing this distorted structure for other charge states when it has already been found for them). @@ -669,25 +666,24 @@ def compare_struct_to_distortions( structure_filename (:obj:`str`, optional): Name of the file containing the structure. (Default: CONTCAR) - stol (:obj:`float`): - Site-matching tolerance for structure matching. Site - tolerance defined as thefraction of the average free length - per atom := ( V / Nsites ) ** (1/3). - (Default: 0.5) min_dist (:obj:`float`): Minimum atomic displacement threshold between structures, in orderto consider them not matching (in Å, default = 0.2 Å). verbose (:obj:`bool`): Whether to print information message about structures being compared. (Default: False) + **sm_kwargs: + Additional keyword arguments to pass to ``_scan_sm_stol_till_match`` + in ``doped`` (used for ultra-fast structure matching), such as + ``min_stol``, ``max_stol``, ``stol_factor`` etc. Returns: :obj:`tuple`: - (True/False/None, matching structure, energy difference of the - matching structure compared to its unperturbed reference, bond - distortion of the matching structure). True if a match is found + (``True``/``False``/``None``, matching structure, energy difference + of the matching structure compared to its unperturbed reference, bond + distortion of the matching structure). ``True`` if a match is found between the input structure and the relaxed bond-distorted - structures for `defect_species`, False if no match, None if no + structures for ``defect_species``, ``False`` if no match, ``None`` if no converged structures found for defect_species. """ try: @@ -697,7 +693,7 @@ def compare_struct_to_distortions( code=code, structure_filename=structure_filename, ) - except FileNotFoundError: # catch exception raised by `analysis.get_structures()` + except FileNotFoundError: # catch exception raised by ``analysis.get_structures()`` return None, None, None, None defect_energies_dict = analysis.get_energies( defect_species=defect_species, output_path=output_path, verbose=False @@ -708,10 +704,10 @@ def compare_struct_to_distortions( defect_structures_dict=defect_structures_dict, defect_energies_dict=defect_energies_dict, ref_structure=distorted_struct, - stol=stol, min_dist=min_dist, display_df=False, verbose=verbose, + **sm_kwargs, ) if struct_comparison_df is None: # no converged structures found for # defect_species @@ -802,13 +798,8 @@ def compare_struct_to_distortions( struc_key, ) - # no matches - return ( - False, - None, - None, - None, - ) # T/F, matching structure, energy_diff, distortion factor + # no matches; T/F, matching structure, energy_diff, distortion factor + return (False, None, None, None) def write_retest_inputs( @@ -820,14 +811,14 @@ def write_retest_inputs( """ Create folders with relaxation input files for testing the low-energy distorted defect structures found for other charge states of that - defect, as identified with `get_energy_lowering_distortions()`. + defect, as identified with ``get_energy_lowering_distortions()``. Args: low_energy_defects (:obj:`dict`): Dictionary of defects for which bond distortion found an energy-lowering distortion which is missed with normal unperturbed relaxation), generated by - `get_energy_lowering_distortions()`. Has the form + ``get_energy_lowering_distortions()``. Has the form {defect: [list of distortion dictionaries (with corresponding charge states, energy lowering, distortion factors, structures and charge states for which these @@ -842,9 +833,9 @@ def write_retest_inputs( (case-insensitive). (Default: "vasp") input_filename (:obj:`str`): - Name of the code input file if different from `ShakeNBreak` + Name of the code input file if different from ``ShakeNBreak`` default. Only applies to CP2K, Quantum Espresso, CASTEP and - FHI-aims. If not specified, `ShakeNBreak` default name is + FHI-aims. If not specified, ``ShakeNBreak`` default name is assumed, that is: for Quantum Espresso: "espresso.pwi", CP2K: "cp2k_input.inp", CASTEP: "castep.param", FHI-aims: "control.in" @@ -975,9 +966,9 @@ def _copy_vasp_files( if not all(file_dict.values()): warnings.warn( - f"Subfolders with VASP input files ({[k for k,v in file_dict.items() if not v]} not found in " + f"Subfolders with VASP input files ({[k for k, v in file_dict.items() if not v]} not found in " f"{output_path}/{defect_species}, so just writing distorted POSCAR file" - f"{f' and {[k for k,v in file_dict.items() if v]}' if any(file_dict.values()) else ''} to " + f"{f' and {[k for k, v in file_dict.items() if v]}' if any(file_dict.values()) else ''} to " f"{distorted_dir} directory." ) @@ -1204,19 +1195,19 @@ def write_groundstate_structure( verbose: bool = False, ) -> None: """ - Writes the groundstate structure of each defect (if `all=True`, default) + Writes the groundstate structure of each defect (if ``all=True``, default) to the corresponding defect folder, with an optional name (default "groundstate_POSCAR"), to then run continuation calculations. Args: all (:obj:`bool`): Write groundstate structures for all defect folders in the - (top-level) directory, specified by `output_path`. If False, - `output_path` should be a single defect folder, for which the + (top-level) directory, specified by ``output_path``. If False, + ``output_path`` should be a single defect folder, for which the groundstate structure will be written. output_path (:obj:`str`): Path to top-level directory with your distorted defect - calculation folders (if `all=True`, else path to single defect + calculation folders (if ``all=True``, else path to single defect folder)(need CONTCAR files for structure matching) and distortion_metadata.json. (Default: current directory = "./") diff --git a/shakenbreak/input.py b/shakenbreak/input.py index 86fc8051..83a823b6 100644 --- a/shakenbreak/input.py +++ b/shakenbreak/input.py @@ -1,19 +1,18 @@ """ Module containing functions to generate rattled and bond-distorted structures, -as well as input files to run Gamma point relaxations with `VASP`, `CP2K`, -`Quantum-Espresso`, `FHI-aims` and `CASTEP`. +as well as input files to run Gamma point relaxations with ``VASP``, ``CP2K``, +``Quantum-Espresso``, ``FHI-aims`` and ``CASTEP``. """ import contextlib import copy import datetime -import itertools import os import shutil import warnings from importlib.metadata import version from pathlib import Path -from typing import Optional, Tuple, Union +from typing import Optional, Union import ase import numpy as np @@ -22,31 +21,28 @@ from doped.core import Defect, DefectEntry, guess_and_set_oxi_states_with_timeout from doped.generation import DefectsGenerator, name_defect_entries from doped.utils.parsing import ( - get_defect_site_idxs_and_unrelaxed_structure, get_defect_type_and_composition_diff, + get_defect_type_site_idxs_and_unrelaxed_structure, ) from doped.vasp import DefectDictSet from monty.json import MontyDecoder from monty.serialization import dumpfn, loadfn from pymatgen.analysis.defects import thermo from pymatgen.analysis.defects.supercells import get_sc_fromstruct -from pymatgen.analysis.structure_matcher import StructureMatcher +from pymatgen.analysis.structure_matcher import ElementComparator, StructureMatcher from pymatgen.core.structure import Composition, Element, PeriodicSite, Structure from pymatgen.entries.computed_entries import ComputedStructureEntry from pymatgen.io.ase import AseAtomsAdaptor from pymatgen.io.cp2k.inputs import Cp2kInput from pymatgen.io.vasp.inputs import Kpoints from pymatgen.io.vasp.sets import BadInputSetWarning -from scipy.cluster.hierarchy import fcluster, linkage -from scipy.spatial import Voronoi -from scipy.spatial.distance import squareform from shakenbreak import analysis, distortions, io MODULE_DIR = os.path.dirname(os.path.abspath(__file__)) -default_potcar_dict = loadfn(f"{MODULE_DIR}/../SnB_input_files/default_POTCARs.yaml") +default_potcar_dict = loadfn(f"{MODULE_DIR}/SnB_input_files/default_POTCARs.yaml") # Load default INCAR settings for the ShakeNBreak geometry relaxations -default_incar_settings = loadfn(os.path.join(MODULE_DIR, "../SnB_input_files/incar.yaml")) +default_incar_settings = loadfn(os.path.join(MODULE_DIR, "SnB_input_files/incar.yaml")) _ignore_pmg_warnings() # Ignore pymatgen POTCAR warnings @@ -54,8 +50,8 @@ def _warning_on_one_line(message, category, filename, lineno, file=None, line=None) -> str: """Output warning messages on one line.""" - # To set this as warnings.formatwarning, we need to be able to take in `file` - # and `line`, but don't want to print them, so unused arguments here + # To set this as warnings.formatwarning, we need to be able to take in ``file`` + # and ``line``, but don't want to print them, so unused arguments here return f"{os.path.split(filename)[-1]}:{lineno}: {category.__name__}: {message}\n" @@ -69,7 +65,7 @@ def _bold_print(string: str) -> None: def _create_folder(folder_name: str) -> None: - """Creates a folder at `./folder_name` if it doesn't already exist.""" + """Creates a folder at ``./folder_name`` if it doesn't already exist.""" path = os.getcwd() if not os.path.isdir(f"{path}/{folder_name}"): try: @@ -290,8 +286,8 @@ def _create_vasp_input( user_potcar_settings: Optional[dict] = None, output_path: str = ".", **kwargs, -) -> None: - """ +) -> str: + r""" Creates folders for storing VASP ShakeNBreak files. Args: @@ -301,29 +297,31 @@ def _create_vasp_input( Dictionary with the distorted structures of charged defect user_incar_settings (:obj:`dict`): Dictionary of user VASP INCAR settings, to overwrite/update the - `doped` defaults. + ``doped`` defaults. user_potcar_functional (str): POTCAR functional to use. Default is "PBE" and if this fails, tries "PBE_52", then "PBE_54". user_potcar_settings (:obj:`dict`): Dictionary of user VASP POTCAR settings, to overwrite/update - the `doped` defaults (e.g. {'Fe': 'Fe_pv', 'O': 'O'}}). Highly - recommended to look at output `POTCAR`s, or `shakenbreak` - `SnB_input_files/default_POTCARs.yaml`, to see what the default - `POTCAR` settings are. (Default: None) + the ``doped`` defaults (e.g. {'Fe': 'Fe_pv', 'O': 'O'}}). Highly + recommended to look at output ``POTCAR``\s, or + ``shakenbreak/SnB_input_files/default_POTCARs.yaml``, to see what + the default ``POTCAR`` settings are. (Default: None) output_path (:obj:`str`): Path to directory in which to write distorted defect structures and calculation inputs. (Default is current directory = "./") **kwargs: - Keyword arguments to pass to `DefectDictSet.write_input()` (e.g. - `potcar_spec`). + Keyword arguments to pass to ``DefectDictSet.write_input()`` (e.g. + ``potcar_spec``). Returns: - None + :obj:`str`: + The final defect folder name (in case it was renamed due to duplicate + folder names etc). """ # create folder for defect - defect_name_wout_charge, charge_state = defect_name.rsplit("_", 1) # `defect_name` includes charge + defect_name_wout_charge, charge_state = defect_name.rsplit("_", 1) # ``defect_name`` includes charge charge_state = int(charge_state) test_letters = [ "h", @@ -374,9 +372,15 @@ def _create_vasp_input( current_unperturbed_struct = distorted_defect_dict["Unperturbed"][ "Defect Structure" ].copy() - for i in [prev_unperturbed_struct, current_unperturbed_struct]: - i.remove_oxidation_states() - if prev_unperturbed_struct == current_unperturbed_struct: + tight_sm = StructureMatcher( + stol=0.02, + ltol=0.02, + angle_tol=0.5, + primitive_cell=False, + attempt_supercell=False, + comparator=ElementComparator(), + ) + if tight_sm.fit(prev_unperturbed_struct, current_unperturbed_struct): warnings.warn( f"The previously-generated defect distortions folder {dir} in " f"{os.path.basename(os.path.abspath(output_path))} " @@ -472,11 +476,14 @@ def _create_vasp_input( dds.write_input( f"{output_path}/{defect_name}/{distortion}", - unperturbed_poscar=True, + poscar=True, + rattle=False, # way ahead of you pal snb=True, **kwargs, ) + return defect_name + def _get_bulk_comp(defect_object) -> Composition: """ @@ -552,7 +559,7 @@ def _get_defect_entry_from_defect( charge_state: int = 0, ): """ - Generate a DefectEntry from a Defect object, whose defect structure + Generate a ``DefectEntry`` from a ``Defect`` object, whose defect structure corresponds to the defect supercell (rather than unit cell). This is the case when initialising Distortions() from an old doped/pycdt defect dict or from structures specified by the user. @@ -621,7 +628,7 @@ def _calc_number_electrons( ) -> int: """ Calculates the number of extra/missing electrons of the neutral - defect species (in `defect_object`) based on `oxidation_states`. + defect species (in ``defect_object``) based on ``oxidation_states``. Args: defect_entry (:obj:`DefectEntry`): @@ -701,95 +708,11 @@ def _calc_number_neighbours(num_electrons: int) -> int: return abs(8 - abs(num_electrons)) if abs(num_electrons) > 4 else abs(num_electrons) -def _get_voronoi_nodes( - structure, -): - """ - Get the Voronoi nodes of a structure. - Templated from the TopographyAnalyzer class, added to - pymatgen.analysis.defects.utils by Yiming Chen, but now deleted. - Modified to map down to primitive, do Voronoi analysis, then map - back to original supercell; much more efficient. - - Args: - structure (:obj:`Structure`): - pymatgen `Structure` object. - """ - # map all sites to the unit cell; 0 ≤ xyz < 1. - structure = Structure.from_sites(structure, to_unit_cell=True) - # get Voronoi nodes in primitive structure and then map back to the - # supercell - prim_structure = structure.get_primitive_structure() - - # get all atom coords in a supercell of the structure because - # Voronoi polyhedra can extend beyond the standard unit cell. - coords = [] - cell_range = list(range(-1, 2)) - for shift in itertools.product(cell_range, cell_range, cell_range): - for site in prim_structure.sites: - shifted = site.frac_coords + shift - coords.append(prim_structure.lattice.get_cartesian_coords(shifted)) - - # Voronoi tessellation. - voro = Voronoi(coords) - - # Only include voronoi polyhedra within the unit cell. - vnodes = [] - tol = 1e-3 - for vertex in voro.vertices: - frac_coords = prim_structure.lattice.get_fractional_coords(vertex) - vnode = PeriodicSite("V-", frac_coords, prim_structure.lattice) - if np.all([-tol <= coord < 1 + tol for coord in frac_coords]) and all( - p.distance(vnode) >= tol for p in vnodes - ): - vnodes.append(vnode) - - # cluster nodes that are within a certain distance of each other - voronoi_coords = [v.frac_coords for v in vnodes] - dist_matrix = np.array(prim_structure.lattice.get_all_distances(voronoi_coords, voronoi_coords)) - dist_matrix = (dist_matrix + dist_matrix.T) / 2 - condensed_m = squareform(dist_matrix) - z = linkage(condensed_m) - cn = fcluster(z, 0.2, criterion="distance") # cluster nodes with 0.2 Å - merged_vnodes = [] - for n in set(cn): - frac_coords = [] - for i, j in enumerate(np.where(cn == n)[0]): - if i == 0: - frac_coords.append(vnodes[j].frac_coords) - else: - fcoords = vnodes[j].frac_coords - d, image = prim_structure.lattice.get_distance_and_image(frac_coords[0], fcoords) - frac_coords.append(fcoords + image) - merged_vnodes.append(PeriodicSite("V-", np.average(frac_coords, axis=0), prim_structure.lattice)) - vnodes = merged_vnodes - - # remove nodes less than 0.5 Å from sites in the structure - vfcoords = [v.frac_coords for v in vnodes] - sfcoords = prim_structure.frac_coords - dist_matrix = prim_structure.lattice.get_all_distances(vfcoords, sfcoords) - all_dist = np.min(dist_matrix, axis=1) - vnodes = [v for i, v in enumerate(vnodes) if all_dist[i] > 0.5] - - # map back to the supercell - sm = StructureMatcher(primitive_cell=False, attempt_supercell=True) - mapping = sm.get_supercell_matrix(structure, prim_structure) - voronoi_struct = Structure.from_sites(vnodes) # Structure object with Voronoi nodes as sites - voronoi_struct.make_supercell(mapping) # Map back to the supercell - - # check if there was an origin shift between primitive and supercell - regenerated_supercell = prim_structure.copy() - regenerated_supercell.make_supercell(mapping) - fractional_shift = sm.get_transformation(structure, regenerated_supercell)[1] - if not np.allclose(fractional_shift, 0): - voronoi_struct.translate_sites(range(len(voronoi_struct)), fractional_shift, frac_coords=True) - - return voronoi_struct.sites - - def _get_voronoi_multiplicity(site, structure): """Get the multiplicity of a Voronoi site in structure.""" - vnodes = _get_voronoi_nodes(structure) + from doped.utils.efficiency import get_voronoi_nodes + + vnodes = get_voronoi_nodes(structure) distances_and_species_list = [] for vnode in vnodes: @@ -1036,12 +959,11 @@ def _remove_matching_sites(bulk_site_list, defect_site_list): try: ( + _defect_type, auto_matching_bulk_site_index, auto_matching_defect_site_index, unrelaxed_defect_structure, - ) = get_defect_site_idxs_and_unrelaxed_structure( - bulk_structure, defect_structure, defect_type, comp_diff - ) + ) = get_defect_type_site_idxs_and_unrelaxed_structure(bulk_structure, defect_structure) except Exception as exc: # failed auto-site matching, rely on user input or raise error if no user input @@ -1171,6 +1093,7 @@ def generate_defect_object( List of charge states for the defect. verbose (:obj:`bool`): Whether to print information about the defect object being parsed. + (Default is False). Returns: :obj:`Defect` """ @@ -1284,14 +1207,16 @@ def _find_sc_defect_coords(defect_entry): """ Find defect fractional coordinates in defect supercell. - Targets cases where user generated DefectEntry manually and - didn't set the `sc_defect_frac_coords` attribute. + Targets cases where user generated ``DefectEntry`` manually and + didn't set the ``sc_defect_frac_coords`` attribute. Args: - defect_entry (DefectEntry): DefectEntry object + defect_entry (:obj:`DefectEntry`): + ``DefectEntry`` object. Returns: - frac_coords (list): Fractional coordinates of defect in defect supercell + frac_coords (:obj:`list`): + Fractional coordinates of defect in defect supercell. """ frac_coords = defect_entry.sc_defect_frac_coords if frac_coords is None: @@ -1334,13 +1259,13 @@ def _apply_rattle_bond_distortions( ) -> dict: """ Applies rattle and bond distortions to the unperturbed defect structure - of `defect_entry` by calling `distortion.distort` with either: + of ``defect_entry`` by calling ``distortion.distort`` with either: - fractional coordinates (for vacancies) or - defect site index (other defect types). Args: defect_entry (:obj:`DefectEntry`): - The defect to distort, as a pymatgen `DefectEntry` object. + The defect to distort, as a pymatgen ``DefectEntry`` object. num_nearest_neighbours (:obj:`int`): Number of defect nearest neighbours to apply bond distortions to. distortion_factor (:obj:`float`): @@ -1381,8 +1306,8 @@ def _apply_rattle_bond_distortions( Whether to print distortion information. (Default: False) **mc_rattle_kwargs: - Additional keyword arguments to pass to `hiphive`'s - `mc_rattle` function. These include: + Additional keyword arguments to pass to ``hiphive``'s + ``mc_rattle`` function. These include: - max_disp (:obj:`float`): Maximum atomic displacement (in Angstroms) during Monte Carlo rattling. Rarely occurs and is used primarily as a safety net. @@ -1419,6 +1344,7 @@ def _apply_rattle_bond_distortions( structure=defect_structure, site_index=defect_site_index, frac_coords=frac_coords, + verbose=verbose, ) else: bond_distorted_defect = distortions.distort( @@ -1444,6 +1370,7 @@ def _apply_rattle_bond_distortions( structure=defect_structure, site_index=defect_site_index, frac_coords=frac_coords, + verbose=verbose, ) else: bond_distorted_defect = distortions.distort( @@ -1505,12 +1432,12 @@ def apply_snb_distortions( **mc_rattle_kwargs, ) -> dict: """ - Applies rattle and bond distortions to `num_nearest_neighbours` of the - unperturbed defect structure of `defect_entry`. + Applies rattle and bond distortions to ``num_nearest_neighbours`` of the + unperturbed defect structure of ``defect_entry``. Args: defect_entry (:obj:`DefectEntry`): - The defect to distort, as a pymatgen `DefectEntry` object. + The defect to distort, as a pymatgen ``DefectEntry`` object. num_nearest_neighbours (:obj:`int`): Number of defect nearest neighbours to apply bond distortions to bond_distortions (:obj:`list`): @@ -1546,8 +1473,8 @@ def apply_snb_distortions( Whether to print distortion information. (Default: False) **mc_rattle_kwargs: - Additional keyword arguments to pass to `hiphive`'s - `mc_rattle` function. These include: + Additional keyword arguments to pass to ``hiphive``'s + ``mc_rattle`` function. These include: - max_disp (:obj:`float`): Maximum atomic displacement (in Angstroms) during Monte Carlo rattling. Rarely occurs and is used primarily as a safety net. @@ -1585,8 +1512,13 @@ def apply_snb_distortions( seed = mc_rattle_kwargs.pop("seed", None) if num_nearest_neighbours != 0: + # If vacancy, add "Dimer" to bond_distortions to ensure dimer reconstruction + # is found. + if defect_type == "vacancy" and ("dimer" not in (str(item).lower() for item in bond_distortions)): + bond_distortions = list(bond_distortions) # in case provided as array + bond_distortions.append("Dimer") for raw_distortion in bond_distortions: - if isinstance(raw_distortion, float): + if not isinstance(raw_distortion, str): distortion = round(raw_distortion, ndigits=3) + 0 # ensure positive zero (not "-0.0%") if verbose: print(f"--Distortion {distortion:.1%}") @@ -1624,6 +1556,8 @@ def apply_snb_distortions( elif isinstance(raw_distortion, str) and raw_distortion.lower() == "dimer": # Apply dimer distortion, with rattling + if verbose: + print(f"--Distortion {raw_distortion}") bond_distorted_defect = _apply_rattle_bond_distortions( defect_entry=defect_entry, num_nearest_neighbours=2, @@ -1697,6 +1631,7 @@ def apply_snb_distortions( structure=defect_structure, site_index=defect_site_index, frac_coords=frac_coords, + verbose=verbose, ) distorted_defect_dict["distortions"]["Dimer"] = bond_distorted_defect["distorted_structure"] distorted_defect_dict["distortion_parameters"].update( @@ -1711,8 +1646,8 @@ def apply_snb_distortions( class Distortions: """ - Class to apply rattle and bond distortion to all defects in `defect_entries` - (each defect as a `doped` or `pymatgen` DefectEntry object). + Class to apply rattle and bond distortion to all defects in ``defect_entries`` + (each defect as a ``doped`` or ``pymatgen`` ``DefectEntry`` object). """ def __init__( @@ -1727,28 +1662,28 @@ def __init__( distorted_atoms: Optional[list] = None, **mc_rattle_kwargs, ): - """ + r""" Args: defect_entries (Union[DefectsGenerator, list, dict, DefectEntry]): - Either a `DefectsGenerator` object from `doped`, or a list/dictionary - of, or single, DefectEntry object(s). - E.g.: [DefectEntry(), DefectEntry(), ...], or single DefectEntry. - If a `DefectsGenerator` object or a dictionary (-> - {defect_species: DefectEntry}), the defect folder names will be - set equal to `defect_species` (with charge states included). If - a list or single `DefectEntry` object is provided, the defect - folder names will be set equal to `DefectEntry.name` if the `name` - attribute is set for all input `DefectEntry`s, otherwise generated - according to the `doped` convention + Either a ``DefectsGenerator`` object from ``doped``, or a list/dictionary + of, or single, ``DefectEntry`` object(s). + E.g.: ``[DefectEntry(), DefectEntry(), ...]``, or single ``DefectEntry``. + If a ``DefectsGenerator`` object or a dictionary (-> + ``{defect_species: DefectEntry}``), the defect folder names will be + set equal to ``defect_species`` (with charge states included). If + a list or single ``DefectEntry`` object is provided, the defect + folder names will be set equal to ``DefectEntry.name`` if the ``name`` + attribute is set for all input ``DefectEntry``\s, otherwise generated + according to the ``doped`` convention (see: https://doped.readthedocs.io/en/latest/generation_tutorial.html). Defect charge states (from which bond distortions are determined) are - taken from the `DefectEntry.charge_state` property. + taken from the ``DefectEntry.charge_state`` property. - Alternatively, a defects dict generated by `ChargedDefectStructures` - from `PyCDT`/`doped<2.0` can also be used as input, and the defect names + Alternatively, a defects dict generated by ``ChargedDefectStructures`` + from ``PyCDT``/``doped<2.0`` can also be used as input, and the defect names and charge states generated by these codes will be used - E.g.: {"bulk": {..}, "vacancies": [{...}, {...},], ...} + E.g.: ``{"bulk": {..}, "vacancies": [{...}, {...},], ...}`` oxidation_states (:obj:`dict`): Dictionary of oxidation states for species in your material, used to determine the number of defect neighbours to distort @@ -1764,7 +1699,7 @@ def __init__( (Default: None) distortion_increment (:obj:`float`): Bond distortion increment. Distortion factors will range from - 0 to +/-0.6, in increments of `distortion_increment`. + 0 to +/-0.6, in increments of ``distortion_increment``. Recommended values: 0.1-0.3 (Default: 0.1) bond_distortions (:obj:`list`): @@ -1792,8 +1727,8 @@ def __init__( (e.g {'vac_1_Cd': [0, 2]}). If None, the closest neighbours to the defect are chosen. **mc_rattle_kwargs: - Additional keyword arguments to pass to `hiphive`'s - `mc_rattle` function. These include: + Additional keyword arguments to pass to ``hiphive``'s + ``mc_rattle`` function. These include: - stdev (:obj:`float`): Standard deviation (in Angstroms) of the Gaussian distribution from which random atomic displacement distances are drawn during @@ -1827,7 +1762,7 @@ def __init__( self.dict_number_electrons_user = dict_number_electrons_user self.local_rattle = local_rattle - # To allow user to specify defect names (with CLI), `defect_entries` can be either + # To allow user to specify defect names (with CLI), ``defect_entries`` can be either # a dict or list of DefectEntry's, or a single DefectEntry if isinstance(defect_entries, (DefectEntry, thermo.DefectEntry)): defect_entries = [ @@ -2043,7 +1978,7 @@ def _parse_distorted_element( self, defect_name, distorted_elements: Optional[dict], - ) -> str: + ) -> Union[str, None, list[str]]: """ Parse the user-defined distorted elements for a given defect (if given). @@ -2055,6 +1990,11 @@ def _parse_distorted_element( Dictionary of distorted elements for each defect, in the form of {'defect_name': ['element1', 'element2', ...]} (e.g {'vac_1_Cd': ['Te']}). + + Returns: + Union[str, None, list[str]]: + Neighbouring element(s) to distort, or None if no element is + specified. """ # Specific elements to distort if distorted_elements: @@ -2067,24 +2007,10 @@ def _parse_distorted_element( "neighbour elements to distort.", ) distorted_element = None - else: - # Check distorted element is a valid element symbol - try: - if isinstance(distorted_elements[defect_name], list): - for element in distorted_elements[defect_name]: - Element(element) - elif isinstance(distorted_elements[defect_name], str): - Element(distorted_elements[defect_name]) - except ValueError: - warnings.warn( - "Problem reading the keys in distorted_elements. Are they correct element " - "symbols? Proceeding without discriminating which neighbour elements to " - "distort.", - ) - distorted_element = None + else: distorted_element = None - return distorted_element + return distorted_element # may be str or list def _parse_number_electrons( self, @@ -2092,6 +2018,7 @@ def _parse_number_electrons( oxidation_states: dict, dict_number_electrons_user: dict, defect_entry: DefectEntry, + verbose: bool = True, ) -> int: """ Parse or calculate the number of extra/missing electrons @@ -2111,8 +2038,12 @@ def _parse_number_electrons( where charge_change is the negative of the number of extra/missing electrons. defect_entry (:obj:`DefectEntry`): - DefectEntry in dictionary of defect_entries. Must be a - `doped` or `pymatgen` DefectEntry object. + ``DefectEntry`` in dictionary of defect_entries. Must be a + ``doped`` or ``pymatgen`` ``DefectEntry`` object. + verbose (:obj:`bool`): + Whether to print the number of extra/missing electrons for + the defect. + (Default: True) Returns: :obj:`int`: @@ -2124,11 +2055,12 @@ def _parse_number_electrons( else: number_electrons = _calc_number_electrons(defect_entry, defect_name, oxidation_states) - _bold_print(f"\nDefect: {defect_name}") - if number_electrons < 0: - _bold_print(f"Number of extra electrons in neutral state: {abs(number_electrons)}") - else: - _bold_print(f"Number of missing electrons in neutral state: {number_electrons}") + if verbose: + _bold_print(f"\nDefect: {defect_name}") + if number_electrons < 0: + _bold_print(f"Number of extra electrons in neutral state: {abs(number_electrons)}") + else: + _bold_print(f"Number of missing electrons in neutral state: {number_electrons}") return number_electrons def _get_number_distorted_neighbours( @@ -2136,6 +2068,7 @@ def _get_number_distorted_neighbours( defect_name: str, number_electrons: int, charge: int, + verbose: bool = True, ) -> int: """ Calculate extra/missing electrons accounting for the charge state of @@ -2147,10 +2080,11 @@ def _get_number_distorted_neighbours( num_nearest_neighbours = _calc_number_neighbours( num_electrons_charged_defect ) # Number of distorted neighbours for each charge state - print( - f"\nDefect {defect_name} in charge state: {'+' if charge > 0 else ''}{charge}. " - f"Number of distorted neighbours: {num_nearest_neighbours}" - ) + if verbose: + print( + f"\nDefect {defect_name} in charge state: {'+' if charge > 0 else ''}{charge}. " + f"Number of distorted neighbours: {num_nearest_neighbours}" + ) return num_nearest_neighbours def _print_distortion_info( @@ -2159,7 +2093,7 @@ def _print_distortion_info( stdev: float, ) -> None: """Print applied bond distortions and rattle standard deviation.""" - rounded_distortions = [f"{round(i,3)+0}" if isinstance(i, float) else i for i in bond_distortions] + rounded_distortions = [f"{round(i, 3)+0}" if isinstance(i, float) else i for i in bond_distortions] print( "Applying ShakeNBreak...", "Will apply the following bond distortions:", @@ -2175,6 +2109,7 @@ def _update_distortion_metadata( defect_site_index: int, num_nearest_neighbours: int, distorted_atoms: list, + defect_type: str = "", ) -> dict: """ Update distortion_metadata with distortion information for each @@ -2202,6 +2137,11 @@ def _update_distortion_metadata( } } ) + # If vacancy, add "Dimer" to bond_distortions + if defect_type == "vacancy": + distortion_metadata["defects"][defect_name]["charges"][int(charge)]["distortion_parameters"][ + "bond_distortions" + ] = [*self.bond_distortions, "Dimer"] return distortion_metadata def _generate_structure_comment( @@ -2232,16 +2172,16 @@ def _setup_distorted_defect_dict( defect_entry: DefectEntry, ) -> dict: """ - Setup `distorted_defect_dict` with info for `defect` in - `DefectEntry`. + Setup ``distorted_defect_dict`` with info for ``defect`` in + ``DefectEntry``. Args: defect_entry (:obj:`doped.core.DefectEntry`): - DefectEntry object to generate `distorted_defect_dict` from. + ``DefectEntry`` object to generate ``distorted_defect_dict`` from. Returns: :obj:`dict` - Dictionary with information for `defect`. + Dictionary with information for ``defect``. """ defect = defect_entry.defect defect_type = defect.defect_type.name.lower() @@ -2331,19 +2271,19 @@ def write_distortion_metadata( def apply_distortions( self, - verbose: bool = False, - ) -> Tuple[dict, dict]: + verbose: Optional[bool] = None, + ) -> tuple[dict, dict]: """ - Applies rattle and bond distortion to all defects in `defect_entries`. + Applies rattle and bond distortion to all defects in ``defect_entries``. Returns a dictionary with the distorted (and undistorted) structures for each charge state of each defect. - If file generation is desired, instead use the methods `write__files()`. + If file generation is desired, instead use the methods ``write__files()``. Args: verbose (:obj:`bool`): Whether to print distortion information (bond atoms and distances) for each charged defect. - (Default: False) + (Default: None -- medium level verbosity) Returns: :obj:`tuple`: @@ -2356,10 +2296,11 @@ def apply_distortions( 'structures': {...}, }, }, - } + }} and dictionary with distortion parameters for each defect. """ - self._print_distortion_info(bond_distortions=self.bond_distortions, stdev=self.stdev) + if verbose is not False: # medium level verbosity + self._print_distortion_info(bond_distortions=self.bond_distortions, stdev=self.stdev) distorted_defects_dict = {} # Store distorted & undistorted structures @@ -2390,6 +2331,7 @@ def apply_distortions( oxidation_states=self.oxidation_states, dict_number_electrons_user=self.dict_number_electrons_user, defect_entry=defect_entry, + verbose=verbose is not False, # medium level verbosity, ) self.distortion_metadata["defects"][defect_name] = { @@ -2408,6 +2350,7 @@ def apply_distortions( defect_name=defect_name, number_electrons=number_electrons, charge=charge, + verbose=verbose is not False, # medium level verbosity, ) # Generate distorted structures defect_distorted_structures = apply_snb_distortions( @@ -2418,7 +2361,7 @@ def apply_distortions( stdev=self.stdev, distorted_element=distorted_element, distorted_atoms=self.distorted_atoms, - verbose=verbose, + verbose=verbose is True, # high level verbosity, oxidation_states=self.oxidation_states, **self._mc_rattle_kwargs, ) @@ -2431,7 +2374,7 @@ def apply_distortions( if shortest_interatomic_distance < 1.0 and all( el.symbol != "H" for el in struct.composition.elements ): - if verbose: + if verbose is True: # high level verbosity warnings.warn( f"{dist} for defect {defect_name} gives an interatomic distance " f"less than 1.0 Å ({shortest_interatomic_distance:.2} Å), " @@ -2460,6 +2403,7 @@ def apply_distortions( defect_site_index=defect_site_index, num_nearest_neighbours=num_nearest_neighbours, distorted_atoms=distorted_atoms, + defect_type=defect_entry.defect.defect_type.name.lower(), ) return distorted_defects_dict, self.distortion_metadata @@ -2519,31 +2463,31 @@ def write_vasp_files( user_potcar_functional: Optional[str] = "PBE", user_potcar_settings: Optional[dict] = None, output_path: str = ".", - verbose: bool = False, + verbose: Optional[bool] = None, **kwargs, - ) -> Tuple[dict, dict]: - """ - Generates the input files for `vasp_gam` relaxations of all output + ) -> tuple[dict, dict]: + r""" + Generates the input files for ``vasp_gam`` relaxations of all output structures. Args: user_incar_settings (:obj:`dict`): Dictionary of user VASP INCAR settings (e.g. - {"ENCUT": 300, ...}), to overwrite the `ShakenBreak` defaults - for those tags. Highly recommended to look at output `INCAR`s, - or `SnB_input_files/incar.yaml` to see what the default `INCAR` - settings are. Note that any flags that aren't numbers or + {"ENCUT": 300, ...}), to overwrite the ``ShakenBreak`` defaults + for those tags. Highly recommended to look at output ``INCAR``\s, + or ``shakenbreak/SnB_input_files/incar.yaml`` to see what the default + ``INCAR`` settings are. Note that any flags that aren't numbers or True/False need to be input as strings with quotation marks - (e.g. `{"ALGO": "All"}`). (Default: None) + (e.g. ``{"ALGO": "All"}``). (Default: None) user_potcar_functional (str): POTCAR functional to use. Default is "PBE" and if this fails, tries "PBE_52", then "PBE_54". user_potcar_settings (:obj:`dict`): Dictionary of user VASP POTCAR settings, to overwrite/update - the `doped` defaults (e.g. {'Fe': 'Fe_pv', 'O': 'O'}}). Highly - recommended to look at output `POTCAR`s, or `shakenbreak` - `SnB_input_files/default_POTCARs.yaml`, to see what the default - `POTCAR` settings are. (Default: None) + the ``doped`` defaults (e.g. {'Fe': 'Fe_pv', 'O': 'O'}}). Highly + recommended to look at output ``POTCAR``\s, or + ``shakenbreak/SnB_input_files/default_POTCARs.yaml``, to see what + the default ``POTCAR`` settings are. (Default: None) write_files (:obj:`bool`): Whether to write output files (Default: True) output_path (:obj:`str`): @@ -2552,9 +2496,9 @@ def write_vasp_files( (Default is current directory = ".") verbose (:obj:`bool`): Whether to print distortion information (bond atoms and - distances). (Default: False) + distances). (Default: None -- medium level verbosity) kwargs: - Additional keyword arguments to pass to `_create_vasp_input()` + Additional keyword arguments to pass to ``_create_vasp_input()`` (Mainly for testing purposes). Returns: @@ -2601,7 +2545,7 @@ def write_vasp_files( } defect_species = f"{defect_name}_{'+' if charge_state > 0 else ''}{charge_state}" - _create_vasp_input( + defect_folder_name = _create_vasp_input( # folder name may change if any duplicates defect_name=defect_species, distorted_defect_dict=charged_defect_dict, user_incar_settings=user_incar_settings, @@ -2611,7 +2555,7 @@ def write_vasp_files( **kwargs, ) self.write_distortion_metadata( - output_path=f"{output_path}/{defect_species}", + output_path=f"{output_path}/{defect_folder_name}", defect=defect_name, charge=charge_state, ) @@ -2626,9 +2570,9 @@ def write_espresso_files( input_file: Optional[str] = None, write_structures_only: Optional[bool] = False, output_path: str = ".", - verbose: Optional[bool] = False, + verbose: Optional[bool] = None, profile=None, - ) -> Tuple[dict, dict]: + ) -> tuple[dict, dict]: """ Generates input files for Quantum Espresso relaxations of all output structures. @@ -2639,13 +2583,14 @@ def write_espresso_files( (Defaults: None) input_parameters (:obj:`dict`, optional): Dictionary of user Quantum Espresso input parameters, to - overwrite/update `shakenbreak` default ones (see - `SnB_input_files/qe_input.yaml`). + overwrite/update ShakeNBreak defaults (see + ``shakenbreak/SnB_input_files/qe_input.yaml``). (Default: None) input_file (:obj:`str`, optional): Path to Quantum Espresso input file, to overwrite/update - `shakenbreak` default ones (see `SnB_input_files/qe_input.yaml`). - If both `input_parameters` and `input_file` are provided, + ShakeNBreak defaults (see + ``shakenbreak/SnB_input_files/qe_input.yaml``). + If both ``input_parameters`` and ``input_file`` are provided, the input_parameters will be used. (Default: None) write_structures_only (:obj:`bool`, optional): @@ -2659,7 +2604,7 @@ def write_espresso_files( verbose (:obj:`bool`): Whether to print distortion information (bond atoms and distances). - (Default: False) + (Default: None -- medium level verbosity) profile (:obj:`BaseProfile`, optional): ASE profile object to use for the ``Espresso()`` calculator class, if using ase>=3.23. If ``None`` (default), set to @@ -2684,7 +2629,7 @@ def write_espresso_files( # Update default parameters with user defined values if pseudopotentials and not write_structures_only: - default_input_parameters = loadfn(os.path.join(MODULE_DIR, "../SnB_input_files/qe_input.yaml")) + default_input_parameters = loadfn(os.path.join(MODULE_DIR, "SnB_input_files/qe_input.yaml")) if input_file and not input_parameters: input_parameters = io.parse_qe_input(input_file) if input_parameters: @@ -2758,18 +2703,18 @@ def write_espresso_files( def write_cp2k_files( self, - input_file: Optional[str] = f"{MODULE_DIR}/../SnB_input_files/cp2k_input.inp", + input_file: Optional[str] = f"{MODULE_DIR}/SnB_input_files/cp2k_input.inp", write_structures_only: Optional[bool] = False, output_path: str = ".", - verbose: Optional[bool] = False, - ) -> Tuple[dict, dict]: + verbose: Optional[bool] = None, + ) -> tuple[dict, dict]: """ Generates input files for CP2K relaxations of all output structures. Args: input_file (:obj:`str`, optional): Path to CP2K input file. If not set, default input file will be - used (see `shakenbreak/SnB_input_files/cp2k_input.inp`). + used (see ``shakenbreak/SnB_input_files/cp2k_input.inp``). write_structures_only (:obj:`bool`, optional): Whether to only write the structure files (in CIF format) (without calculation inputs). @@ -2781,7 +2726,7 @@ def write_cp2k_files( verbose (:obj:`bool`, optional): Whether to print distortion information (bond atoms and distances). - (Default: False) + (Default: None -- medium level verbosity) Returns: :obj:`tuple`: @@ -2790,15 +2735,13 @@ def write_cp2k_files( """ if os.path.exists(input_file) and not write_structures_only: cp2k_input = Cp2kInput.from_file(input_file) - elif ( - os.path.exists(f"{MODULE_DIR}/../SnB_input_files/cp2k_input.inp") and not write_structures_only - ): + elif os.path.exists(f"{MODULE_DIR}/SnB_input_files/cp2k_input.inp") and not write_structures_only: warnings.warn( f"Specified input file {input_file} does not exist! Using" " default CP2K input file " "(see shakenbreak/shakenbreak/cp2k_input.inp)" ) - cp2k_input = Cp2kInput.from_file(f"{MODULE_DIR}/../SnB_input_files/cp2k_input.inp") + cp2k_input = Cp2kInput.from_file(f"{MODULE_DIR}/SnB_input_files/cp2k_input.inp") distorted_defects_dict, self.distortion_metadata = self.apply_distortions( verbose=verbose, @@ -2822,19 +2765,19 @@ def write_cp2k_files( def write_castep_files( self, - input_file: Optional[str] = f"{MODULE_DIR}/../SnB_input_files/castep.param", + input_file: Optional[str] = f"{MODULE_DIR}/SnB_input_files/castep.param", write_structures_only: Optional[bool] = False, output_path: str = ".", - verbose: Optional[bool] = False, - ) -> Tuple[dict, dict]: + verbose: Optional[bool] = None, + ) -> tuple[dict, dict]: """ - Generates input `.cell` and `.param` files for CASTEP relaxations of + Generates input ``.cell`` and ``.param`` files for CASTEP relaxations of all output structures. Args: input_file (:obj:`str`, optional): - Path to CASTEP input (`.param`) file. If not set, default input - file will be used (see `shakenbreak/SnB_input_files/castep.param`). + Path to CASTEP input (``.param``) file. If not set, default input + file will be used (see ``shakenbreak/SnB_input_files/castep.param``). write_structures_only (:obj:`bool`, optional): Whether to only write the structure files (in CIF format) (without calculation inputs). @@ -2846,7 +2789,7 @@ def write_castep_files( verbose (:obj:`bool`, optional): Whether to print distortion information (bond atoms and distances). - (Default: False) + (Default: None -- medium level verbosity) Returns: :obj:`tuple`: @@ -2901,9 +2844,9 @@ def write_fhi_aims_files( ase_calculator=None, # Aims or AimsTemplate write_structures_only: Optional[bool] = False, output_path: str = ".", - verbose: Optional[bool] = False, + verbose: Optional[bool] = None, profile=None, - ) -> Tuple[dict, dict]: + ) -> tuple[dict, dict]: """ Generates input geometry and control files for FHI-aims relaxations of all output structures. @@ -2914,8 +2857,8 @@ def write_fhi_aims_files( Args: input_file (:obj:`str`, optional): Path to FHI-aims input file, to overwrite/update - `shakenbreak` default ones. - If both `input_file` and `ase_calculator` are provided, + ``shakenbreak`` default ones. + If both ``input_file`` and ``ase_calculator`` are provided, the ase_calculator will be used. ase_calculator (:obj:`Aims`, :obj:`AimsTemplate`, optional): Either an ``Aims`` (ASE calculator) or ``AimsTemplate`` object @@ -2933,7 +2876,7 @@ def write_fhi_aims_files( verbose (:obj:`bool`, optional): Whether to print distortion information (bond atoms and distances). - (Default: False) + (Default: None -- medium level verbosity) profile (:obj:`BaseProfile`, optional): ASE profile object to use for the ``Aims()`` calculator class, if using ase>=3.23. If ``None`` (default), set to @@ -3055,7 +2998,7 @@ def from_structures( [(defect Structure, frac_coords/index), ...] to aid site-matching. Defect charge states (from which bond distortions are determined) are - set to the range: 0 - {Defect oxidation state}, with a `padding` + set to the range: 0 - {Defect oxidation state}, with a ``padding`` (default = 1) on either side of this range. bulk (:obj:`pymatgen.core.structure.Structure`): Bulk supercell structure, matching defect supercells. @@ -3067,7 +3010,7 @@ def from_structures( common oxidation states of any extrinsic species. padding (:obj:`int`): Defect charge states are set to the range: - 0 - {Defect oxidation state}, with a `padding` (default = 1) + 0 - {Defect oxidation state}, with a ``padding`` (default = 1) on either side of this range. dict_number_electrons_user (:obj:`dict`): Optional argument to set the number of extra/missing charge @@ -3078,7 +3021,7 @@ def from_structures( (Default: None) distortion_increment (:obj:`float`): Bond distortion increment. Distortion factors will range from - 0 to +/-0.6, in increments of `distortion_increment`. + 0 to +/-0.6, in increments of ``distortion_increment``. Recommended values: 0.1-0.3 (Default: 0.1) bond_distortions (:obj:`list`): @@ -3105,8 +3048,8 @@ def from_structures( If None, the closest neighbours to the defect are chosen. (Default: None) **mc_rattle_kwargs: - Additional keyword arguments to pass to `hiphive`'s - `mc_rattle` function. These include: + Additional keyword arguments to pass to ``hiphive``'s + ``mc_rattle`` function. These include: - stdev (:obj:`float`): Standard deviation (in Angstroms) of the Gaussian distribution from which random atomic displacement distances are drawn during diff --git a/shakenbreak/io.py b/shakenbreak/io.py index 263fd09f..8039c985 100644 --- a/shakenbreak/io.py +++ b/shakenbreak/io.py @@ -31,14 +31,14 @@ def parse_energies( ) -> None: """ Parse final energy for all distortions present in the given defect - directory and write them to a `yaml` file in the defect directory. - Returns the energies_file path. + directory and write them to a ``yaml`` file in the defect directory. + Returns the ``energies_file`` path. Args: defect (:obj:`str`): Name of defect to parse, including charge state. Should match the name of the defect folder. - path (:obj: `str`): + path (:obj:`str`): Path to the top-level directory containing the defect folder. Defaults to current directory ("."). code (:obj:`str`): @@ -50,12 +50,14 @@ def parse_energies( that are defined in the default input files: (i.e. vasp: "OUTCAR", cp2k: "relax.out", espresso: "espresso.out", castep: "*.castep", fhi-aims: "aims.out") - Default to the ShakeNBreak default filenames. + Defaults to the ``ShakeNBreak`` default filenames. verbose (:obj:`bool`): If True, print information about renamed/saved-over files. Defaults to False. - Returns: energies_file path. + Returns: + :obj:`str`: + Path to the ``energies_file``. """ def _match(filename, grep_string): @@ -112,7 +114,7 @@ def save_file(energies, defect, path, verbose=False): dumpfn(energies, filename) def parse_vasp_energy(defect_dir, dist, energy, outcar): - """Parse VASP energy from OUTCAR file.""" + """Parse VASP energy from ``OUTCAR`` file.""" converged = False if os.path.exists(os.path.join(defect_dir, dist, "OUTCAR")): outcar = os.path.join(defect_dir, dist, "OUTCAR") @@ -127,7 +129,7 @@ def parse_vasp_energy(defect_dir, dist, energy, outcar): return converged, energy, outcar def parse_espresso_energy(defect_dir, dist, energy, espresso_out): - """Parse Quantum Espresso energy from espresso.out file.""" + """Parse Quantum Espresso energy from ``espresso.out`` file.""" if os.path.join(defect_dir, dist, "espresso.out"): # Default SnB output filename espresso_out = os.path.join(defect_dir, dist, "espresso.out") elif os.path.exists(os.path.join(defect_dir, dist, filename)): @@ -199,7 +201,7 @@ def parse_fhi_aims_energy(defect_dir, dist, energy, aims_out): if defect == os.path.basename(os.path.normpath(path)) and not [ dir for dir in path if (os.path.isdir(dir) and os.path.basename(os.path.normpath(dir)) == defect) - ]: # if `defect` is in end of `path` and `path` doesn't have a subdirectory called `defect` + ]: # if ``defect`` is in end of ``path`` and ``path`` doesn't have a subdirectory called ``defect`` # then remove defect from end of path path = os.path.dirname(path) @@ -283,7 +285,7 @@ def parse_fhi_aims_energy(defect_dir, dist, energy, aims_out): energies[dist_name] = prev_energies_dict[dist_name] elif "distortions" in prev_energies_dict and dist_name in prev_energies_dict["distortions"]: energies["distortions"][dist_name] = prev_energies_dict["distortions"][dist_name] - else: + elif f"{dist}_High_Energy" not in dist_dirs: # don't warn if was renamed to High_Energy warnings.warn(f"No output file in {dist} directory") if energies["distortions"]: @@ -383,42 +385,43 @@ def read_vasp_structure( file_path: str, ) -> Union[Structure, str]: """ - Read VASP structure from `file_path` and convert to `pymatgen` Structure + Read VASP structure from ``file_path`` and convert to ``pymatgen`` Structure object. Args: file_path (:obj:`str`): - Path to VASP `CONTCAR` file + Path to VASP ``CONTCAR`` file Returns: :obj:`Structure`: - `pymatgen` Structure object + ``pymatgen`` ``Structure`` object """ abs_path_formatted = file_path.replace("\\", "/") # for Windows compatibility if not os.path.isfile(abs_path_formatted): - warnings.warn( - f"{abs_path_formatted} file doesn't exist, storing as " - f"'Not converged'. Check path & relaxation" - ) - struct = "Not converged" - else: - try: - struct = Structure.from_file(abs_path_formatted) - except Exception: + # check if there's an equivalent high-energy folder, and if so don't warn + if not os.path.exists(abs_path_formatted.rsplit("/", 1)[0] + "_High_Energy"): warnings.warn( - f"Problem obtaining structure from: {abs_path_formatted}, " - f"storing as 'Not converged'. Check file & relaxation" + f"{abs_path_formatted} file doesn't exist, storing as " + f"'Not converged'. Check path & relaxation" ) - struct = "Not converged" - return struct + return "Not converged" + + try: + return Structure.from_file(abs_path_formatted) + except Exception: + warnings.warn( + f"Problem obtaining structure from: {abs_path_formatted}, " + f"storing as 'Not converged'. Check file & relaxation" + ) + return "Not converged" def read_espresso_structure( filename: str, ) -> Union[Structure, str]: """ - Reads a structure from Quantum Espresso output and returns it as a pymatgen - Structure. + Reads a structure from Quantum Espresso output and returns it as a + ``pymatgen`` ``Structure``. Args: filename (:obj:`str`): @@ -426,7 +429,7 @@ def read_espresso_structure( Returns: :obj:`Structure`: - `pymatgen` Structure object + ``pymatgen`` ``Structure`` object """ # ase.io.espresso functions seem a bit buggy, so we use the following implementation if os.path.exists(filename): @@ -485,8 +488,8 @@ def read_espresso_structure( def read_fhi_aims_structure(filename: str, format="aims") -> Union[Structure, str]: """ - Reads a structure from FHI-aims output and returns it as a pymatgen - Structure. + Reads a structure from FHI-aims output and returns it as a + ``pymatgen`` ``Structure``. Args: filename (:obj:`str`): @@ -496,7 +499,7 @@ def read_fhi_aims_structure(filename: str, format="aims") -> Union[Structure, st Returns: :obj:`Structure`: - `pymatgen` Structure object + ``pymatgen`` ``Structure`` object """ if not os.path.exists(filename): raise FileNotFoundError(f"File {filename} does not exist!") @@ -519,8 +522,8 @@ def read_cp2k_structure( filename: str, ) -> Union[Structure, str]: """ - Reads a structure from CP2K restart file and returns it as a pymatgen - Structure. + Reads a structure from CP2K restart file and returns it as a + ``pymatgen`` ``Structure``. Args: filename (:obj:`str`): @@ -528,7 +531,7 @@ def read_cp2k_structure( Returns: :obj:`Structure`: - `pymatgen` Structure object + ``pymatgen`` ``Structure`` object """ if not os.path.exists(filename): raise FileNotFoundError(f"File {filename} does not exist!") @@ -554,16 +557,16 @@ def read_castep_structure( filename: str, ) -> Union[Structure, str]: """ - Reads a structure from CASTEP output (`.castep`) file and returns it as a - pymatgen Structure. + Reads a structure from ``CASTEP`` output (``.castep``) file and + returns it as a ``pymatgen`` ``Structure``. Args: filename (:obj:`str`): - Path to the CASTEP output file. + Path to the ``CASTEP`` output file. Returns: :obj:`Structure`: - `pymatgen` Structure object + ``pymatgen`` ``Structure`` object """ if not os.path.exists(filename): raise FileNotFoundError(f"File {filename} does not exist!") @@ -592,7 +595,7 @@ def parse_structure( ) -> Union[Structure, str]: """ Parses the output structure from different codes (VASP, CP2K, Quantum Espresso, - CATSEP, FHI-aims) and converts it to a pymatgen Structure object. + CATSEP, FHI-aims) and converts it to a ``pymatgen`` ``Structure`` object. Args: code (:obj:`str`): @@ -604,15 +607,16 @@ def parse_structure( Name of the structure file or the output file containing the optimized structure. If not set, the following values will be used for each code: - vasp: "CONTCAR", - cp2k: "cp2k.restart" (The restart file is used), - Quantum espresso: "espresso.out", - castep: "castep.castep" (castep output file is used) - fhi-aims: geometry.in.next_step + + - ``VASP``: "CONTCAR", + - ``cp2k``: "cp2k.restart" (The restart file is used), + - ``Quantum Espresso``: "espresso.out", + - ``CASTEP``: "castep.castep" (castep output file is used) + - ``Fhi-AIMS``: "geometry.in.next_step" Returns: :obj:`Structure`: - `pymatgen` Structure object + ``pymatgen`` ``Structure`` object """ if code.lower() == "vasp": if not structure_filename: diff --git a/shakenbreak/plotting.py b/shakenbreak/plotting.py index 14775f0d..ec91dade 100644 --- a/shakenbreak/plotting.py +++ b/shakenbreak/plotting.py @@ -8,7 +8,7 @@ import os import shutil import warnings -from typing import Optional, Tuple +from typing import Optional import matplotlib as mpl import matplotlib.pyplot as plt @@ -17,6 +17,8 @@ from doped.utils.plotting import format_defect_name from matplotlib import font_manager from matplotlib.figure import Figure +from matplotlib.legend_handler import HandlerTuple +from pymatgen.util.typing import PathLike from shakenbreak import analysis @@ -97,7 +99,7 @@ def _verify_data_directories_exist( output_path: str, defect_species: str, ) -> None: - """Check top-level directory (e.g. `output_path`) and defect folders exist.""" + """Check top-level directory (e.g. ``output_path``) and defect folders exist.""" # Check directories and input if not os.path.isdir(output_path): # if output_path does not exist, raise error raise FileNotFoundError(f"Path {output_path} does not exist! Skipping {defect_species}.") @@ -147,13 +149,13 @@ def _cast_energies_to_floats( defect_species: str, ) -> dict: """ - If values of the `energies_dict` are not floats, convert them to floats. + If values of the ``energies_dict`` are not floats, convert them to floats. If any problem encountered during conversion, raise ValueError. Args: energies_dict (:obj:`dict`): Dictionary matching distortion to final energy (eV), as produced by - `_organize_data()` or `analysis.get_energies()`).. + ``_organize_data()`` or ``analysis.get_energies()``).. defect_species (:obj:`str`): Defect name including charge (e.g. 'vac_1_Cd_0') @@ -180,7 +182,7 @@ def _change_energy_units_to_meV( energies_dict: dict, max_energy_above_unperturbed: float, y_label: str, -) -> Tuple[dict, float, str]: +) -> tuple[dict, float, str]: """ Converts energy values from eV to meV and format y label accordingly. @@ -209,7 +211,7 @@ def _change_energy_units_to_meV( def _purge_data_dicts( disp_dict: dict, energies_dict: dict, -) -> Tuple[dict, dict]: +) -> tuple[dict, dict]: """ Purges dictionaries of displacements and energies so that they are consistent (i.e. contain data for same distortions). @@ -220,10 +222,10 @@ def _purge_data_dicts( disp_dict (dict): dictionary with displacements (for each structure relative to Unperturbed), in the output format of - `analysis.calculate_struct_comparison()` + ``analysis.calculate_struct_comparison()`` energies_dict (dict): dictionary with final energies (for each structure relative to ¡ - Unperturbed), in the output format of `analysis.get_energies()` or + Unperturbed), in the output format of ``analysis.get_energies()`` or analysis.organize_data() Returns: @@ -247,21 +249,21 @@ def _remove_high_energy_points( energies_dict: dict, max_energy_above_unperturbed: float, disp_dict: Optional[dict] = None, -) -> Tuple[dict, dict]: +) -> tuple[dict, dict]: """ Remove points whose energy is higher than the reference (Unperturbed) by - more than `max_energy_above_unperturbed`. + more than ``max_energy_above_unperturbed``. Args: energies_dict (:obj:`dict`): Dictionary matching distortion to final energy (eV), as produced by - `analysis._organize_data()` or `analysis.get_energies()` + ``analysis._organize_data()`` or ``analysis.get_energies()`` max_energy_above_unperturbed (:obj:`float`): - Maximum energy (in chosen `units`), relative to the unperturbed + Maximum energy (in chosen ``units``), relative to the unperturbed structure, to show on plot disp_dict (:obj:`dict`): Dictionary matching distortion to sum of atomic displacements, as - produced by `analysis.calculate_struct_comparison()` + produced by ``analysis.calculate_struct_comparison()`` (Default: None) Returns: @@ -271,7 +273,7 @@ def _remove_high_energy_points( # remove high energy points if energies_dict["distortions"][key] - energies_dict["Unperturbed"] > max_energy_above_unperturbed: energies_dict["distortions"].pop(key) - if disp_dict and key in disp_dict: # only exists if user selected `add_colorbar=True` + if disp_dict and key in disp_dict: # only exists if user selected ``add_colorbar=True`` disp_dict.pop(key) return energies_dict, disp_dict @@ -283,15 +285,15 @@ def _get_displacement_dict( energies_dict: dict, add_colorbar: bool, code: Optional[str] = "vasp", -) -> Tuple[bool, dict, dict]: +) -> tuple[bool, dict, dict]: """ - Parses structures of `defect_species` to calculate displacements between each + Parses structures of ``defect_species`` to calculate displacements between each of them and the reference configuration (Unperturbed). These displacements are stored in a dictionary matching distortion key to displacement value. - Then, ensures `energies_dict` and `disp_dict` are consistent (same keys), + Then, ensures ``energies_dict`` and ``disp_dict`` are consistent (same keys), and makes them consistent otherwise. If any problems encountered when parsing or calculating structural - similarity, warning will be raised and `add_colorbar` will be set to False. + similarity, warning will be raised and ``add_colorbar`` will be set to False. Args: defect_species (:obj:`str`): @@ -308,7 +310,7 @@ def _get_displacement_dict( (Default: 'max_dist') energies_dict (:obj:`dict`): Dictionary matching distortion to final energy (eV), as produced by - `_organize_data()` or `analysis.get_energies()`) + ``_organize_data()`` or ``analysis.get_energies()``) add_colorbar (:obj:`bool`): Whether to add a colorbar indicating structural similarity between each structure and the unperturbed one. @@ -318,8 +320,8 @@ def _get_displacement_dict( (Default: "vasp") Returns: - Tuple[bool, dict, dict]: tuple of `add_colorbar`, `energies_dict` and - `disp_dict` + Tuple[bool, dict, dict]: tuple of ``add_colorbar``, ``energies_dict`` and + ``disp_dict`` """ try: defect_structs = analysis.get_structures( @@ -456,7 +458,7 @@ def _save_plot( Args: fig (:obj:`mpl.figure.Figure`): - mpl.figure.Figure object to save. + ``Figure`` object to save. defect_name (:obj:`str`): Defect name that will be used as file name and for identifying defect folder. output_path (:obj:`str`): @@ -519,7 +521,7 @@ def _format_ticks( Args: ax (obj:`mpl.axes.Axes`): - matplotlib.axes.Axes of figure to format + ``Axes`` of figure to format energies_list (:obj:`list`): List of y (energy) values @@ -576,18 +578,18 @@ def _format_axis( Args: ax (:obj:`mpl.axes.Axes`): - current matplotlib.axes.Axes + Current ``matplotlib.axes.Axes`` defect_name (:obj:`str`): - name of defect (e.g. 'vac_1_Cd_0') + Name of defect (e.g. 'vac_1_Cd_0') y_label (:obj:`str`): - label for y axis + Label for y axis num_nearest_neighbours (:obj:`int`): - number of distorted nearest neighbours + Number of distorted nearest neighbours neighbour_atom (:obj:`str`): - element symbol of distorted nearest neighbour + Element symbol of distorted nearest neighbour Returns: - mpl.axes.Axes: axes with formatted labels + mpl.axes.Axes: Axes with formatted labels """ if num_nearest_neighbours and neighbour_atom and defect_name: x_label = ( @@ -618,30 +620,28 @@ def _get_line_colors(number_of_colors: int) -> list: Returns: list """ - if 11 > number_of_colors > 1: - # If user didnt specify colors and more than one color needed, - # use deep color palette - colors = sns.color_palette("deep", 10) - elif number_of_colors > 11: # otherwise use colormap - colors = list( - mpl.cm.get_cmap("viridis", number_of_colors + 1).colors - ) # +1 to avoid yellow color (which is at the end of the colormap) - else: - colors = [ - "#59a590", - ] # Turquoise by default - return colors + default_colors = sns.color_palette("deep", 10) + if number_of_colors <= 1: + return ["#59a590", *default_colors] # Turquoise by default + if number_of_colors <= 10: + # If user didn't specify colors and more than one color needed, use deep color palette + return default_colors[:number_of_colors] + + # otherwise use colormap + return list( + mpl.cm.get_cmap("viridis", number_of_colors + 1).colors + ) # +1 to avoid yellow color (which is at the end of the colormap) def _setup_colormap( disp_dict: dict, -) -> Tuple[mpl.colors.Colormap, float, float, float, mpl.colors.Normalize]: +) -> tuple[mpl.colors.Colormap, float, float, float, mpl.colors.Normalize]: """ Setup colormap to measure structural similarity between structures. Args: - disp_dict (:obj: `dict`): - dictionary mapping distortion key to structural similarity between + disp_dict (:obj:`dict`): + Dictionary mapping distortion key to structural similarity between the associated structure and the reference structure. Returns: @@ -675,21 +675,21 @@ def _format_colorbar( Args: fig (:obj:`mpl.figure.Figure`): - matplotlib.figure.Figure object + ``Figure`` object ax (:obj:`mpl.axes.Axes`): - current matplotlib.axes.Axes object + Current ``Axes`` object metric (:obj:`str`): - metric to be plotted: "disp" or "max_dist" + Metric to be plotted: "disp" or "max_dist" vmin (:obj:`float`): tick label for the colorbar vmax (:obj:`float`): - tick label for the colorbar + Tick label for the colorbar vmedium (:obj:`float`): - tick label for the colorbar + Tick label for the colorbar norm (:obj:`mpl.colors.Normalize`): - normalization for the colorbar + Normalization for the colorbar cmap (:obj:`mpl.colors.Colormap`): - colormap for the colorbar + Colormap for the colorbar Returns: cbar (:obj:`mpl.colorbar.Colorbar`) @@ -706,29 +706,92 @@ def _format_colorbar( ) cbar.ax.tick_params(size=0) cbar.outline.set_visible(False) - if metric == "disp": - cmap_label = r"$\Sigma$ Disp $(\AA)$" - elif metric == "max_dist": - cmap_label = r"$d_{max}$ $(\AA)$" + cmap_label = r"$\Sigma$ Disp $(\AA)$" if metric == "disp" else r"$d_{max}$ $(\AA)$" # else max_dist cbar.ax.set_title(cmap_label, size="medium", loc="center", ha="center", va="center", pad=20.5) - if vmin != vmax: - cbar.set_ticks([vmin, vmedium, vmax]) - cbar.set_ticklabels([vmin, vmedium, vmax]) - else: - cbar.set_ticks([vmedium]) - cbar.set_ticklabels([vmedium]) + ticks = [vmin, vmedium, vmax] if vmin != vmax else [vmedium] + cbar.set_ticks(ticks) + cbar.set_ticklabels([str(i) for i in ticks]) return cbar -def _parse_other_charge_state_label(distortion_key: str) -> tuple: +def _parse_other_charge_state_label(distortion_key: str) -> str: try: other_charge_state = int(distortion_key.split("_")[-1]) - label = f"From {'+' if other_charge_state > 0 else ''}{other_charge_state} charge state" + return f"From {'+' if other_charge_state > 0 else ''}{other_charge_state} charge state" except ValueError: other_charge_state = distortion_key.split("_")[-1] - label = f"From {other_charge_state}" + return f"From {other_charge_state}" + + +def _format_legend( + ax: mpl.axes.Axes, + line: Optional[mpl.lines.Line2D] = None, + path_col: Optional[mpl.collections.PathCollection] = None, + legend_label: str = "", +) -> None: + """ + Formats the legend of a SnB distortions plot. + + If line and path_col are provided, then line and path_col are merged + for the legend entry with label 'legend_label'. + + If there are any duplicate legend keys (e.g. from multiple imported + structures from the same charge state, when the ``meta`` option was + used), then these are merged into a single legend entry, with the + handles grouped together in the legend key. + """ + # reformat 'line' legend handle to include 'path_col' datapoint handle + handles, labels = ax.get_legend_handles_labels() + # get handle and label that corresponds to line, if line present: + if line and path_col: + line_handle, line_label = next( + (handle, label) for handle, label in zip(handles, labels) if label == legend_label + ) + # remove line handle and label from handles and labels + handles = [handle for handle in handles if handle != line_handle] + labels = [label for label in labels if label != line_label] + # add line handle and label to handles and labels, with datapoint handle + handles = [(path_col, line_handle), *handles] + labels = [line_label, *labels] + + # merge any duplicate labels (multiple imported charge states perhaps): + unique_labels = {} + for handle, label in zip(handles, labels): + if label not in unique_labels: + unique_labels[label] = (handle,) + else: + unique_labels[label] += (handle,) - return label + # set handlelength to max length of unique_labels value, divided by 1.5 (so enough spacing for all + # handles that are included in the legend): + handlelength = max(max(len(handle) for handle in unique_labels.values()) / 1.5, 1) + + # Prepare the legend entries, creating a handler_map excluding 'legend_label' + handler_map = {} + final_handles = [] + final_labels = [] + + for label, handle_tuple in unique_labels.items(): + if label == legend_label: + final_handles.append(handle_tuple[0]) + else: + if isinstance(handle_tuple, tuple) and len(handle_tuple) > 1: + handler_map[handle_tuple] = HandlerTuple(ndivide=None) + final_handles.append(handle_tuple if len(handle_tuple) > 1 else handle_tuple[0]) + final_labels.append(label) + + ax.legend( + final_handles, + final_labels, + numpoints=1, + handlelength=handlelength, + handler_map=handler_map, + scatteryoffsets=[0.5], + frameon=True, + framealpha=0.3, + ).set_zorder( + 100 + ) # make sure it's on top of the other points # Main plotting functions: @@ -740,6 +803,7 @@ def plot_all_defects( max_energy_above_unperturbed: float = 0.5, units: str = "eV", min_e_diff: float = 0.05, + style_file: Optional[PathLike] = None, line_color: Optional[str] = None, add_title: Optional[bool] = True, save_plot: bool = True, @@ -770,7 +834,7 @@ def plot_all_defects( matched sites ('max_dist', default). (Default: "max_dist") max_energy_above_unperturbed (:obj:`float`): - Maximum energy (in chosen `units`), relative to the unperturbed structure, + Maximum energy (in chosen ``units``), relative to the unperturbed structure, to show on the plot. (Default: 0.5 eV) units (:obj:`str`): @@ -778,8 +842,11 @@ def plot_all_defects( (Default: "eV") min_e_diff (:obj:`float`): Minimum energy difference (in eV) between the ground-state defect - structure and the `Unperturbed` structure to generate the distortion plot. + structure and the ``Unperturbed`` structure to generate the distortion plot. (Default: 0.05 eV) + style_file (PathLike): + Path to a mplstyle file to use for the plot. If None (default), uses + the default ShakeNBreak style (``shakenbreak.mplstyle``). line_color (:obj:`str`): Color of the line connecting points. (Default: ShakeNBreak base style) @@ -788,7 +855,7 @@ def plot_all_defects( formatted defect name (i.e. V$_{Cd}^{0}$). (Default: True) save_plot (:obj:`bool`): - Whether to plot the results or not. + Whether to save the plot(s) to disk. (Default: True) save_format (:obj:`str`): Format to save the plot as. @@ -856,7 +923,7 @@ def plot_all_defects( f"Path {energies_file} does not exist. Skipping {defect_species}." ) # skip defect continue - energies_dict, energy_diff, gs_distortion = analysis._sort_data(energies_file, verbose=False) + energies_dict, energy_diff, _gs_distortion = analysis._sort_data(energies_file, verbose=False) if not energy_diff: # if Unperturbed calc is not converged, warn user warnings.warn( @@ -872,6 +939,8 @@ def plot_all_defects( f"Energy lowering distortion found for {defect} with " f"charge {'+' if charge > 0 else ''}{charge}. Generating distortion plot..." ) + num_nearest_neighbours = None + neighbour_atom = None if distortion_metadata and isinstance(distortion_metadata, list): # try load directly from defect folder first: with contextlib.suppress(FileNotFoundError): @@ -895,9 +964,6 @@ def plot_all_defects( num_nearest_neighbours, neighbour_atom = _parse_distortion_metadata( distortion_metadata, defect, charge ) - elif distortion_metadata is None: - num_nearest_neighbours = None - neighbour_atom = None defects_to_plot[defect_species] = { "energies_dict": energies_dict, @@ -916,6 +982,7 @@ def plot_all_defects( metric=metric, units=units, max_energy_above_unperturbed=max_energy_above_unperturbed, + style_file=style_file, line_color=line_color, add_title=add_title, save_plot=save_plot, @@ -937,6 +1004,7 @@ def plot_defect( metric: Optional[str] = "max_dist", max_energy_above_unperturbed: Optional[float] = 0.5, include_site_info_in_name: Optional[bool] = False, + style_file: Optional[PathLike] = None, y_label: Optional[str] = "Energy (eV)", add_title: Optional[bool] = True, line_color: Optional[str] = None, @@ -955,7 +1023,7 @@ def plot_defect( Defect name including charge (e.g. 'vac_1_Cd_0') energies_dict (:obj:`dict`): Dictionary matching distortion to final energy (eV), as produced by - `_organize_data()` or `analysis.get_energies()`) + ``_organize_data()`` or ``analysis.get_energies()``) output_path (:obj:`str`): Path to top-level directory with your distorted defect calculations (to calculate structure comparisons and save plots) @@ -979,7 +1047,7 @@ def plot_defect( matched sites ('max_dist', default). (Default: "max_dist") max_energy_above_unperturbed (:obj:`float`): - Maximum energy (in chosen `units`), relative to the unperturbed + Maximum energy (in chosen ``units``), relative to the unperturbed structure, to show on the plot. (Default: 0.5 eV) units (:obj:`str`): @@ -990,6 +1058,9 @@ def plot_defect( nearest neighbour info, as generated by doped) in the defect name. Useful for materials with many symmetry-inequivalent sites. (Default: False) + style_file (PathLike): + Path to a mplstyle file to use for the plot. If None (default), uses + the default ShakeNBreak style (``shakenbreak.mplstyle``). y_label (:obj:`str`): Y axis label (Default: "Energy (eV)") @@ -1001,7 +1072,7 @@ def plot_defect( Color of the line connecting points. (Default: ShakeNBreak base style) save_plot (:obj:`bool`): - Whether to save the plot as an SVG file. + Whether to save the plot to disk. (Default: True) save_format (:obj:`str`): Format to save the plot as. @@ -1016,7 +1087,7 @@ def plot_defect( Returns: :obj:`mpl.figure.Figure`: - Energy vs distortion plot, as a mpl.figure.Figure object + Energy vs distortion plot, as a ``Figure`` object. """ # Ensure necessary directories exist, and raise error if not try: @@ -1111,7 +1182,7 @@ def plot_defect( else: legend_label = "Distortions" - with plt.style.context(f"{MODULE_DIR}/shakenbreak.mplstyle"): + with plt.style.context(style_file or f"{MODULE_DIR}/shakenbreak.mplstyle"): if add_colorbar: fig = plot_colorbar( energies_dict=energies_dict, @@ -1123,6 +1194,7 @@ def plot_defect( neighbour_atom=neighbour_atom, legend_label=legend_label, metric=metric, + style_file=style_file, y_label=y_label, max_energy_above_unperturbed=max_energy_above_unperturbed, line_color=line_color, @@ -1140,6 +1212,7 @@ def plot_defect( num_nearest_neighbours=num_nearest_neighbours, neighbour_atom=neighbour_atom, dataset_labels=[legend_label], + style_file=style_file, y_label=y_label, max_energy_above_unperturbed=max_energy_above_unperturbed, save_plot=save_plot, @@ -1153,6 +1226,188 @@ def plot_defect( return fig +def _setup_plot( + defect_species: str, + include_site_info_in_name: bool, + y_label: str, + title: Optional[str], + num_nearest_neighbours: Optional[int], + neighbour_atom: Optional[str], + **fig_kwargs, +) -> tuple[plt.Figure, plt.Axes]: + _install_custom_font() + fig, ax = plt.subplots(1, 1, **fig_kwargs) + + if title: + ax.set_title(title) + + try: + formatted_defect_name = format_defect_name( + defect_species, include_site_info_in_name=include_site_info_in_name + ) + except Exception: + formatted_defect_name = "defect" + + ax = _format_axis( + ax=ax, + y_label=y_label, + defect_name=formatted_defect_name, + num_nearest_neighbours=num_nearest_neighbours, + neighbour_atom=neighbour_atom, + ) + + return fig, ax + + +def _plot_unperturbed( + ax: plt.Axes, unperturbed_energy: float, color, label: Optional[str] = "Unperturbed", **kwargs +) -> None: + ax.scatter(0, unperturbed_energy, color=color, ls="None", marker="d", label=label, **kwargs) + + +def _plot_distortions( + ax: plt.Axes, + energies_dict: dict, + imported_indices, + sorted_distortions, + sorted_energies, + keys, + disp_dict: Optional[dict], + colors: list[str] = "k", # colors[dataset_number], + colormap=None, + norm=None, + style_settings: Optional[dict] = None, + sorted_disp: Optional[list] = None, + label: str = "", # dataset_labels[dataset_number] + line_color: Optional[str] = None, + legend_label: Optional[str] = "SnB", +): + path_col = line = None # to later check if line was plotted, for legend formatting + disp_dict = disp_dict or {} + style_settings = style_settings or {} + line_color = line_color or "#59a590" # turquoise by default + + def _get_color_from_colormap(disp, colormap=None, norm=None, default=colors[0]): + if isinstance(disp, float) and colormap and norm: + return colormap(norm(disp)) + if isinstance(disp, str): + return disp + return default + + for special_entry in ["Rattled", "Dimer"]: + if special_entry in energies_dict["distortions"]: + path_col = ax.scatter( + 0.0, + energies_dict["distortions"][special_entry], + color=_get_color_from_colormap(disp_dict.get(special_entry, colors[0]), colormap, norm), + label=special_entry, + s=50, + marker=style_settings.get("marker", "s" if special_entry == "Dimer" else "o"), + alpha=1, + ) + + if len(sorted_distortions) > 0 and [ + key for key in energies_dict["distortions"] if key not in ["Rattled", "Dimer"] + ]: # more than just Rattled or Dimer + if imported_indices: # Exclude datapoints from other charge states + non_imported_sorted_indices = [ + i for i in range(len(sorted_distortions)) if i not in imported_indices.values() + ] + else: + non_imported_sorted_indices = range(len(sorted_distortions)) + + if len(non_imported_sorted_indices) > 1 and not disp_dict: # multiple points, plotting dataset + (line,) = ax.plot( # plot non-imported distortions + [sorted_distortions[i] for i in non_imported_sorted_indices], + [sorted_energies[i] for i in non_imported_sorted_indices], + c=colors[0], + markersize=style_settings.get("markersize"), + marker=style_settings.get("marker", "o"), + linestyle=style_settings.get("linestyle", "-"), + label=label, + linewidth=style_settings.get("linewidth"), + ) + + elif len(non_imported_sorted_indices) > 1 and disp_dict: + for with_disp in [True, False]: + indices_list = [ + i + for i in non_imported_sorted_indices + if isinstance(sorted_disp[i], float) == with_disp + ] + if indices_list: + path_col = ax.scatter( # plot any datapoints with undetermined disp as black + [sorted_distortions[i] for i in indices_list], + [sorted_energies[i] for i in indices_list], + c=[sorted_disp[i] for i in indices_list] if with_disp else "k", + ls="-", + s=50, + marker="o", + cmap=colormap if with_disp else None, + norm=norm if with_disp else None, + alpha=1, + ) + if len(non_imported_sorted_indices) > 1: # more than one point + (line,) = ax.plot( # Plot line connecting points + [sorted_distortions[i] for i in non_imported_sorted_indices], + [sorted_energies[i] for i in non_imported_sorted_indices], + ls="-", + markersize=1, + marker="o", + color=line_color, + label=legend_label, + ) + + if imported_indices: + num_other_charges = len( + [ + list(energies_dict["distortions"].keys())[i].split("_")[-1] for i in imported_indices + ] # number of other charge states whose distortions have been imported + ) + for i, j in zip(imported_indices, range(num_other_charges)): + sorted_i = imported_indices[i] # index for the sorted dicts + if sorted_disp: + colors = [ + _get_color_from_colormap(sorted_disp[sorted_i], colormap, norm), + ] + ax.scatter( # distortions from other charge states + np.array(keys)[i], + sorted_energies[sorted_i], + color=(colors * 100)[ # repeat colours in case many imported charge states + j + 1 + ], # different colors for different imported charge states, if only one dataset + edgecolors="k", + ls="-", + s=50, + zorder=10, # make sure it's on top of the other lines + marker=( + ["s", "v", "<", ">", "^", "p", "X"] * 10 + )[ # repeat markers in case many imported charge states + j + ], # different markers for different imported charge states + alpha=1, + label=_parse_other_charge_state_label(list(energies_dict["distortions"].keys())[i]), + ) + + return path_col, line + + +def _set_xlim(ax, sorted_distortions): + # distortion_range is sorted_distortions range, including 0 if above/below this range + distortion_range = ( + min((*sorted_distortions, 0)), + max((*sorted_distortions, 0)), + ) + + # set xlim to distortion_range + 5% (matplotlib default padding), if distortion_range is + # not zero (only rattled and unperturbed) + if distortion_range[1] - distortion_range[0] > 0: + ax.set_xlim( + distortion_range[0] - 0.05 * (distortion_range[1] - distortion_range[0]), + distortion_range[1] + 0.05 * (distortion_range[1] - distortion_range[0]), + ) + + def plot_colorbar( energies_dict: dict, disp_dict: dict, @@ -1166,6 +1421,7 @@ def plot_colorbar( max_energy_above_unperturbed: Optional[float] = 0.5, save_plot: Optional[bool] = False, output_path: Optional[str] = ".", + style_file: Optional[PathLike] = None, y_label: Optional[str] = "Energy (eV)", line_color: Optional[str] = None, save_format: Optional[str] = "png", @@ -1178,14 +1434,14 @@ def plot_colorbar( Args: energies_dict (:obj:`dict`): Dictionary matching distortion to final energy (eV), as produced by - `analysis.get_energies()` or `analysis._organize_data()`. + ``analysis.get_energies()`` or ``analysis._organize_data()``. disp_dict (:obj:`dict`): Dictionary matching bond distortions to structure comparison metric (metric = 'disp' or 'max_dist'), as produced by - `analysis.calculate_struct_comparison()`. + ``analysis.calculate_struct_comparison()``. defect_species (:obj:`str`): Specific defect name that will appear in plot labels (in LaTeX form) - and file names (e.g 'vac_1_Cd_0') + and file names (e.g 'vac_1_Cd_0') include_site_info_in_name (:obj:`bool`): Whether to include the site info (i.e. point group symbol and possibly nearest neighbour info, as generated by doped) in the defect name. @@ -1209,12 +1465,9 @@ def plot_colorbar( or the maximum distance between matched sites ('max_dist', default). (Default: "max_dist") max_energy_above_unperturbed (:obj:`float`): - Maximum energy (in chosen `units`), relative to the unperturbed + Maximum energy (in chosen ``units``), relative to the unperturbed structure, to show on the plot. (Default: 0.5 eV) - line_color (:obj:`str`): - Color of the line conneting points. - (Default: ShakeNBreak base style) save_plot (:obj:`bool`): Whether to save the plot as an SVG file. (Default: True) @@ -1222,9 +1475,15 @@ def plot_colorbar( Path to top-level directory containing the defect directory (in which to save the plot). (Default: ".") + style_file (PathLike): + Path to a mplstyle file to use for the plot. If None (default), uses + the default ShakeNBreak style (``shakenbreak.mplstyle``). y_label (:obj:`str`): Y axis label (Default: 'Energy (eV)') + line_color (:obj:`str`): + Color of the line connecting points. + (Default: ShakeNBreak base style) save_format (:obj:`str`): Format to save the plot as. (Default: 'png') @@ -1234,200 +1493,69 @@ def plot_colorbar( Returns: :obj:`mpl.figure.Figure`: Energy vs distortion plot with colorbar for structural similarity, - as a mpl.figure.Figure object + as a ``Figure`` object """ - _install_custom_font() - with plt.style.context(f"{MODULE_DIR}/shakenbreak.mplstyle"): - fig, ax = plt.subplots(1, 1, figsize=(6.5, 5)) - - # Title and format axis labels and locators - if title: - ax.set_title(title) - - try: - formatted_defect_name = format_defect_name( - defect_species, include_site_info_in_name=include_site_info_in_name - ) - except Exception: - formatted_defect_name = "defect" - - ax = _format_axis( - ax=ax, + with plt.style.context(style_file or f"{MODULE_DIR}/shakenbreak.mplstyle"): + fig, ax = _setup_plot( + defect_species=defect_species, + include_site_info_in_name=include_site_info_in_name, y_label=y_label, - defect_name=formatted_defect_name, + title=title, num_nearest_neighbours=num_nearest_neighbours, neighbour_atom=neighbour_atom, + figsize=(6.5, 5), ) - # All energies relative to unperturbed one - for key, i in energies_dict["distortions"].items(): - energies_dict["distortions"][key] = i - energies_dict["Unperturbed"] - energies_dict["Unperturbed"] = 0.0 - - energies_dict, disp_dict = _remove_high_energy_points( - energies_dict=energies_dict, - disp_dict=disp_dict, - max_energy_above_unperturbed=max_energy_above_unperturbed, - ) # Remove high energy points - if not energies_dict["distortions"]: - warnings.warn( - f"No distortion energies within {max_energy_above_unperturbed} eV above " - f"unperturbed structure for {defect_species}. Skipping plot." - ) - return None + # All energies relative to unperturbed one + for key, i in energies_dict["distortions"].items(): + energies_dict["distortions"][key] = i - energies_dict["Unperturbed"] + energies_dict["Unperturbed"] = 0.0 - # Setting line color and colorbar - if not line_color: - line_color = "#59a590" # By default turquoise - # colormap to measure structural similarity - colormap, vmin, vmedium, vmax, norm = _setup_colormap(disp_dict) - - # Format distortion keys from other charge states - ( - imported_indices, - keys, - sorted_distortions, - sorted_energies, - sorted_disp, - ) = _format_datapoints_from_other_chargestates(energies_dict=energies_dict, disp_dict=disp_dict) - - # Plotting - line = None # to later check if line was plotted, for legend formatting - with plt.style.context(f"{MODULE_DIR}/shakenbreak.mplstyle"): - if "Rattled" in energies_dict["distortions"] and "Rattled" in disp_dict: - # Plot Rattled energy - path_col = ax.scatter( - 0.0, - energies_dict["distortions"]["Rattled"], - c=(disp_dict["Rattled"] if isinstance(disp_dict["Rattled"], float) else "k"), - label="Rattled", - s=50, - marker="o", - cmap=colormap if isinstance(disp_dict["Rattled"], float) else None, - norm=norm if isinstance(disp_dict["Rattled"], float) else None, - alpha=1, - ) - # Plot Dimer - if "Dimer" in energies_dict["distortions"] and "Dimer" in disp_dict: - path_col = ax.scatter( - 0.0, - energies_dict["distortions"]["Dimer"], - c=disp_dict["Dimer"] if isinstance(disp_dict["Dimer"], float) else "k", - s=50, - marker="s", # default_style_settings["marker"], - label="Dimer", - cmap=colormap if isinstance(disp_dict["Dimer"], float) else None, - norm=norm if isinstance(disp_dict["Dimer"], float) else None, - alpha=1, + energies_dict, disp_dict = _remove_high_energy_points( + energies_dict=energies_dict, + disp_dict=disp_dict, + max_energy_above_unperturbed=max_energy_above_unperturbed, + ) # Remove high energy points + if not energies_dict["distortions"]: + warnings.warn( + f"No distortion energies within {max_energy_above_unperturbed} eV above " + f"unperturbed structure for {defect_species}. Skipping plot." ) + return None - if len(sorted_distortions) > 0 and [ - key for key in energies_dict["distortions"] if key != "Rattled" - ]: # more than just Rattled - if imported_indices: # Exclude datapoints from other charge states - non_imported_sorted_indices = [ - i for i in range(len(sorted_distortions)) if i not in imported_indices.values() - ] - else: - non_imported_sorted_indices = range(len(sorted_distortions)) + colormap, vmin, vmedium, vmax, norm = _setup_colormap(disp_dict) # colormap for struct similarity - # Plot non-imported distortions - non_imported_distortion_indices_with_disp = [ - i for i in non_imported_sorted_indices if isinstance(sorted_disp[i], float) - ] - non_imported_distortion_indices_without_disp = [ - i for i in non_imported_sorted_indices if not isinstance(sorted_disp[i], float) - ] - for indices_list, color_map in [ - (non_imported_distortion_indices_without_disp, False), - (non_imported_distortion_indices_with_disp, True), - ]: - if indices_list: - path_col = ax.scatter( # plot any datapoints with undetermined disp as black - [sorted_distortions[i] for i in indices_list], - [sorted_energies[i] for i in indices_list], - c=[sorted_disp[i] for i in indices_list] if color_map else "k", - ls="-", - s=50, - marker="o", - cmap=colormap if color_map else None, - norm=norm if color_map else None, - alpha=1, - ) - if len(non_imported_sorted_indices) > 1: # more than one point - # Plot line connecting points - (line,) = ax.plot( - [sorted_distortions[i] for i in non_imported_sorted_indices], - [sorted_energies[i] for i in non_imported_sorted_indices], - ls="-", - markersize=1, - marker="o", - color=line_color, - label=legend_label, - ) - - # Datapoints from other charge states - if imported_indices: - other_charges = len( - [ # number of other charge states whose distortions have been imported - list(energies_dict["distortions"].keys())[i].split("_")[-1] - for i in imported_indices - ] - ) - for i, j in zip(imported_indices.keys(), range(other_charges)): - sorted_i = imported_indices[i] # index for the sorted dicts - ax.scatter( # plot any datapoints where disp could not be determined as black - np.array(keys)[i], - sorted_energies[sorted_i], - c=(sorted_disp[sorted_i] if isinstance(sorted_disp[sorted_i], float) else "k"), - edgecolors="k", - ls="-", - s=50, - marker=( - ["s", "v", "<", ">", "^", "p", "X"] * 3 - )[ # repeat markers in case many imported charge states - j - ], # different markers for different charge states - zorder=10, # make sure it's on top of the other points - cmap=(colormap if isinstance(sorted_disp[sorted_i], float) else None), - norm=norm if isinstance(sorted_disp[sorted_i], float) else None, - alpha=1, - label=_parse_other_charge_state_label( - list(energies_dict["distortions"].keys())[i] - ), - ) + ( # Format distortion keys from other charge states + imported_indices, + keys, + sorted_distortions, + sorted_energies, + sorted_disp, + ) = _format_datapoints_from_other_chargestates(energies_dict=energies_dict, disp_dict=disp_dict) - # Plot reference energy - unperturbed_color = colormap( - 0 - ) # get color of unperturbed structure (corresponding to 0 as disp is calculated with - # respect to this structure) - ax.scatter( - 0, - energies_dict["Unperturbed"], - color=unperturbed_color, - ls="None", - s=120, - marker="d", - label="Unperturbed", + path_col, line = _plot_distortions( + ax=ax, + energies_dict=energies_dict, + imported_indices=imported_indices, + sorted_distortions=sorted_distortions, + sorted_energies=sorted_energies, + keys=keys, + disp_dict=disp_dict, + colormap=colormap, + norm=norm, + sorted_disp=sorted_disp, + legend_label=legend_label, + line_color=line_color, ) - # distortion_range is sorted_distortions range, including 0 if above/below this range - distortion_range = ( - min((*sorted_distortions, 0)), - max((*sorted_distortions, 0)), - ) - # set xlim to distortion_range + 5% (matplotlib default padding), if distortion_range is - # not zero (only rattled and unperturbed) - if distortion_range[1] - distortion_range[0] > 0: - ax.set_xlim( - distortion_range[0] - 0.05 * (distortion_range[1] - distortion_range[0]), - distortion_range[1] + 0.05 * (distortion_range[1] - distortion_range[0]), - ) + # Plot reference energy; color corresponds to 0 as disp is calculated wrt this structure + _plot_unperturbed(ax=ax, unperturbed_energy=energies_dict["Unperturbed"], color=colormap(0), s=120) + + _set_xlim(ax, sorted_distortions) - # Formatting of tick labels. + # Formatting of tick labels: # For yaxis (i.e. energies): 1 decimal point if deltaE = (max E - min E) > 0.4 eV, - # 2 if deltaE > 0.1 eV, otherwise 3. + # 2 if deltaE > 0.1 eV, otherwise 3: ax = _format_ticks( ax=ax, energies_list=[ @@ -1436,25 +1564,9 @@ def plot_colorbar( ], ) - # reformat 'line' legend handle to include 'path_col' datapoint handle - handles, labels = ax.get_legend_handles_labels() - # get handle and label that corresponds to line, if line present: - if line: - line_handle, line_label = next( - (handle, label) for handle, label in zip(handles, labels) if label == legend_label - ) - # remove line handle and label from handles and labels - handles = [handle for handle in handles if handle != line_handle] - labels = [label for label in labels if label != line_label] - # add line handle and label to handles and labels, with datapoint handle - handles = [(path_col, line_handle), *handles] - labels = [line_label, *labels] - - plt.legend(handles, labels, scatteryoffsets=[0.5], frameon=True, framealpha=0.3).set_zorder( - 100 - ) # make sure it's on top of the other points - - _ = _format_colorbar( + _format_legend(ax=ax, line=line, path_col=path_col, legend_label=legend_label) + + _format_colorbar( fig=fig, ax=ax, metric=metric, @@ -1465,15 +1577,15 @@ def plot_colorbar( cmap=colormap, ) # Colorbar formatting - # Save plot? - if save_plot: - _save_plot( - fig=fig, - defect_name=defect_species, - output_path=output_path, - save_format=save_format, - verbose=verbose, - ) + # Save plot? + if save_plot: + _save_plot( + fig=fig, + defect_name=defect_species, + output_path=output_path, + save_format=save_format, + verbose=verbose, + ) return fig @@ -1486,6 +1598,7 @@ def plot_datasets( neighbour_atom: Optional[str] = None, num_nearest_neighbours: Optional[int] = None, max_energy_above_unperturbed: Optional[float] = 0.5, + style_file: Optional[PathLike] = None, y_label: str = r"Energy (eV)", markers: Optional[list] = None, linestyles: Optional[list] = None, @@ -1504,13 +1617,13 @@ def plot_datasets( datasets (:obj:`list`): List of {distortion: energy} dictionaries to plot (each dictionary matching distortion to final energy (eV), as produced by - `analysis._organize_data()` or `analysis.get_energies()`) + ``analysis._organize_data()`` or ``analysis.get_energies()``) dataset_labels (:obj:`list`): Labels for each dataset plot legend. If None, defaults to ["Distortions"]*len(datasets). defect_species (:obj:`str`): Specific defect name that will appear in plot labels (in LaTeX form) - and file names (e.g 'vac_1_Cd_0'). Defaults to 'defect'. + and file names (e.g 'vac_1_Cd_0'). Defaults to 'defect'. include_site_info_in_name (:obj:`bool`): Whether to include the site info (i.e. point group symbol and possibly nearest neighbour info, as generated by doped) in the defect name. @@ -1524,9 +1637,12 @@ def plot_datasets( num_nearest_neighbours (:obj:`int`): Number of distorted neighbour atoms (e.g. 2) max_energy_above_unperturbed (:obj:`float`): - Maximum energy (in chosen `units`), relative to the unperturbed + Maximum energy (in chosen ``units``), relative to the unperturbed structure, to show on the plot. (Default: 0.5 eV) + style_file (PathLike): + Path to a mplstyle file to use for the plot. If None (default), uses + the default ShakeNBreak style (``shakenbreak.mplstyle``). y_label (:obj:`str`): Y axis label (Default: 'Energy (eV)') colors (:obj:`list`): @@ -1562,244 +1678,147 @@ def plot_datasets( Returns: :obj:`mpl.figure.Figure`: Energy vs distortion plot for multiple datasets, - as a mpl.figure.Figure object + as a ``Figure`` object """ - # Validate input - if dataset_labels is None: - dataset_labels = ["Distortions"] * len(datasets) - - elif len(datasets) != len(dataset_labels): - raise ValueError( - f"Number of datasets and labels must match! " - f"You gave me {len(datasets)} datasets and" - f" {len(dataset_labels)} labels." + with plt.style.context(style_file or f"{MODULE_DIR}/shakenbreak.mplstyle"): + fig, ax = _setup_plot( + defect_species=defect_species, + include_site_info_in_name=include_site_info_in_name, + y_label=y_label, + title=title, + num_nearest_neighbours=num_nearest_neighbours, + neighbour_atom=neighbour_atom, ) - _install_custom_font() - # Set up figure - with plt.style.context(f"{MODULE_DIR}/shakenbreak.mplstyle"): - fig, ax = plt.subplots(1, 1) + # Line colors - if not colors: - colors = _get_line_colors(number_of_colors=len(datasets)) # get list of - # colors to use for each dataset - elif len(colors) < len(datasets): - if verbose: + if not colors or len(colors) < len(datasets): + if verbose and colors: warnings.warn( f"Insufficient colors provided for {len(datasets)} datasets. Using default colors." ) colors = _get_line_colors(number_of_colors=len(datasets)) - # Title and labels of axis - if title: - ax.set_title(title) - try: - formatted_defect_name = format_defect_name( - defect_species, include_site_info_in_name=include_site_info_in_name - ) - except Exception: - formatted_defect_name = "defect" + # Validate input + if dataset_labels is None: + dataset_labels = ["Distortions"] * len(datasets) - ax = _format_axis( - ax=ax, - y_label=y_label, - defect_name=formatted_defect_name, - num_nearest_neighbours=num_nearest_neighbours, - neighbour_atom=neighbour_atom, - ) + elif len(datasets) != len(dataset_labels): + raise ValueError( + f"Number of datasets and labels must match! " + f"You gave me {len(datasets)} datasets and" + f" {len(dataset_labels)} labels." + ) - # Plot data points for each dataset - unperturbed_energies = {} # energies for unperturbed structure obtained with different methods - - # all energies relative to the unperturbed energy of first dataset - for dataset_number, dataset in enumerate(datasets): - for key, energy in dataset["distortions"].items(): - dataset["distortions"][key] = ( - energy - datasets[0]["Unperturbed"] - ) # Energies relative to unperturbed E of dataset 1 - - if dataset_number >= 1: - dataset["Unperturbed"] = dataset["Unperturbed"] - datasets[0]["Unperturbed"] - unperturbed_energies[dataset_number] = dataset["Unperturbed"] - - for key in list(dataset["distortions"].keys()): - # remove high E points (relative to reference) - if dataset["distortions"][key] > max_energy_above_unperturbed: - dataset["distortions"].pop(key) - - default_style_settings = { - "marker": "o", - "linestyle": "solid", - "linewidth": 1.0, - "markersize": 6, - } - for key, optional_style_settings in { - "marker": markers, - "linestyle": linestyles, - "linewidth": linewidth, - "markersize": markersize, - }.items(): - if optional_style_settings: # if set by user - if isinstance(optional_style_settings, list): - try: - default_style_settings[key] = optional_style_settings[dataset_number] - except IndexError: - default_style_settings[key] = optional_style_settings[ - 0 - ] # in case not enough for each dataset - else: - default_style_settings[key] = optional_style_settings + # Plot data points for each dataset + unperturbed_energies = {} # energies for unperturbed structure obtained with different methods + + # all energies relative to the unperturbed energy of first dataset + min_max_distortions = [] + for dataset_number, dataset in enumerate(datasets): + for key, energy in dataset["distortions"].items(): + dataset["distortions"][key] = ( + energy - datasets[0]["Unperturbed"] + ) # Energies relative to unperturbed E of dataset 1 + + if dataset_number >= 1: + dataset["Unperturbed"] = dataset["Unperturbed"] - datasets[0]["Unperturbed"] + unperturbed_energies[dataset_number] = dataset["Unperturbed"] + + for key in list(dataset["distortions"].keys()): + # remove high E points (relative to reference) + if dataset["distortions"][key] > max_energy_above_unperturbed: + dataset["distortions"].pop(key) + + default_style_settings = { + "marker": "o", + "linestyle": "solid", + "linewidth": 1.0, + "markersize": 6, + } + for key, optional_style_settings in { + "marker": markers, + "linestyle": linestyles, + "linewidth": linewidth, + "markersize": markersize, + }.items(): + if optional_style_settings: # if set by user + if isinstance(optional_style_settings, list): + try: + default_style_settings[key] = optional_style_settings[dataset_number] + except IndexError: # in case not enough for each dataset + default_style_settings[key] = optional_style_settings[0] + else: + default_style_settings[key] = optional_style_settings + + ( # Format distortion keys of the distortions imported from other charge states + imported_indices, + keys, + sorted_distortions, + sorted_energies, + ) = _format_datapoints_from_other_chargestates(energies_dict=dataset, disp_dict=None) + min_max_distortions.extend([min(sorted_distortions), max(sorted_distortions)]) + + _path_col, _line = _plot_distortions( + ax=ax, + energies_dict=dataset, + imported_indices=imported_indices, + sorted_distortions=sorted_distortions, + sorted_energies=sorted_energies, + keys=keys, + disp_dict=None, + colors=colors if len(datasets) == 1 else [colors[dataset_number]] * 1000, + style_settings=default_style_settings, + label=dataset_labels[dataset_number], + ) - # Format distortion keys of the distortions imported from other charge states - ( - imported_indices, - keys, - sorted_distortions, - sorted_energies, - ) = _format_datapoints_from_other_chargestates(energies_dict=dataset, disp_dict=None) - with plt.style.context(f"{MODULE_DIR}/shakenbreak.mplstyle"): - if "Rattled" in dataset["distortions"]: - ax.scatter( # Scatter plot for Rattled (1 datapoint) - 0.0, - dataset["distortions"]["Rattled"], - c=colors[dataset_number], - s=50, - marker=default_style_settings["marker"], - label="Rattled", - ) + datasets[0]["Unperturbed"] = 0.0 # unperturbed energy of first dataset (our reference energy) - # Plot Dimer - if "Dimer" in dataset["distortions"]: - ax.scatter( # Scatter plot for Rattled (1 datapoint) - 0.0, - dataset["distortions"]["Dimer"], - c=colors[dataset_number], - s=50, - marker="s", # default_style_settings["marker"], - label="Dimer", + # Plot Unperturbed point for every dataset, relative to the unperturbed energy of first dataset + for key, value in unperturbed_energies.items(): + if abs(value) > 0.1: # Only plot if different energy from the reference Unperturbed + print( + f"Energies for unperturbed structures obtained with different methods " + f"({dataset_labels[key]}) differ by {value:.2f}. If testing different " + "magnetic states (FM, AFM) this is normal, otherwise you may want to check this!" ) - - if len(sorted_distortions) > 0 and [ - key for key in dataset["distortions"] if key not in ["Rattled", "Dimer"] - ]: # more than just Rattled - if imported_indices: # Exclude datapoints from other charge states - non_imported_sorted_indices = [ - i for i in range(len(sorted_distortions)) if i not in imported_indices.values() - ] - else: - non_imported_sorted_indices = range(len(sorted_distortions)) - - if len(non_imported_sorted_indices) > 1: # more than one point - # Plot non-imported distortions - ax.plot( # plot bond distortions - [sorted_distortions[i] for i in non_imported_sorted_indices], - [sorted_energies[i] for i in non_imported_sorted_indices], - c=colors[dataset_number], - markersize=default_style_settings["markersize"], - marker=default_style_settings["marker"], - linestyle=default_style_settings["linestyle"], - label=dataset_labels[dataset_number], - linewidth=default_style_settings["linewidth"], - ) - - if imported_indices: - other_charges = len( - [ - list(dataset["distortions"].keys())[i].split("_")[-1] for i in imported_indices - ] # number of other charge states whose distortions have been imported + _plot_unperturbed( + ax=ax, + unperturbed_energy=datasets[key]["Unperturbed"], + color=colors[key], + label=None, + s=80, ) - for i, j in zip(imported_indices, range(other_charges)): - ax.scatter( # distortions from other charge states - np.array(keys)[i], - list(dataset["distortions"].values())[i], - c=colors[dataset_number], - edgecolors="k", - ls="-", - s=50, - zorder=10, # make sure it's on top of the other lines - marker=( - ["s", "v", "<", ">", "^", "p", "X"] * 3 - )[ # repeat markers in case many imported charge states - j - ], # different markers for different charge states - alpha=1, - label=_parse_other_charge_state_label(list(dataset["distortions"].keys())[i]), - ) - - datasets[0]["Unperturbed"] = 0.0 # unperturbed energy of first dataset (our reference energy) - # Plot Unperturbed point for every dataset, relative to the unperturbed energy of first dataset - for key, value in unperturbed_energies.items(): - if abs(value) > 0.1: # Only plot if different energy from the reference Unperturbed - print( - f"Energies for unperturbed structures obtained with different methods " - f"({dataset_labels[key]}) differ by {value:.2f}. If testing different " - "magnetic states (FM, AFM) this is normal, otherwise you may want to check this!" - ) - with plt.style.context(f"{MODULE_DIR}/shakenbreak.mplstyle"): - ax.plot( - 0, - datasets[key]["Unperturbed"], - ls="None", - marker="d", - markersize=9, - c=colors[key], - ) + _plot_unperturbed(ax=ax, unperturbed_energy=datasets[0]["Unperturbed"], color=colors[0], s=80) - with plt.style.context(f"{MODULE_DIR}/shakenbreak.mplstyle"): - ax.plot( # plot our reference energy - 0, - datasets[0]["Unperturbed"], - ls="None", - marker="d", - markersize=9, - c=colors[0], - label="Unperturbed", - ) + _set_xlim(ax, min_max_distortions) # just needs min/max vals for determining x-limits - # distortion_range is sorted_distortions range, including 0 if above/below this range - distortion_range = ( - min((*sorted_distortions, 0)), - max( - ( - *sorted_distortions, - 0, + # If several datasets, check min & max energy are included + if len(datasets) > 1: + min_energy = min(min(list(dataset["distortions"].values())) for dataset in datasets) + max_energy = max(max(list(dataset["distortions"].values())) for dataset in datasets) + ax.set_ylim( + min_energy - 0.1 * (max_energy - min_energy), + max_energy + 0.1 * (max_energy - min_energy), ) - ), - ) - # set xlim to distortion_range + 5% (matplotlib default padding) - if distortion_range[1] - distortion_range[0] > 0: - ax.set_xlim( - distortion_range[0] - 0.05 * (distortion_range[1] - distortion_range[0]), - distortion_range[1] + 0.05 * (distortion_range[1] - distortion_range[0]), - ) - # Format tick labels: - # For yaxis, 1 decimal point if energy difference between max E and min E - # > 0.4 eV, 3 if E < 0.1 eV, 2 otherwise - ax = _format_ticks( - ax=ax, - energies_list=[ - *list(datasets[0]["distortions"].values()), - datasets[0]["Unperturbed"], - ], - ) - # If several datasets, check min & max energy are included - if len(datasets) > 1: - min_energy = min(min(list(dataset["distortions"].values())) for dataset in datasets) - max_energy = max(max(list(dataset["distortions"].values())) for dataset in datasets) - ax.set_ylim( - min_energy - 0.1 * (max_energy - min_energy), - max_energy + 0.1 * (max_energy - min_energy), + ax = _format_ticks( + ax=ax, + energies_list=[ + energy + for dataset in datasets + for energy in [*dataset["distortions"].values(), datasets[0]["Unperturbed"]] + ], ) - ax.legend(frameon=True, framealpha=0.3).set_zorder(100) # show legend on top of all other datapoints + _format_legend(ax=ax) - if save_plot: # Save plot? - _save_plot( - fig=fig, - defect_name=defect_species, - output_path=output_path, - save_format=save_format, - verbose=verbose, - ) + if save_plot: # Save plot? + _save_plot( + fig=fig, + defect_name=defect_species, + output_path=output_path, + save_format=save_format, + verbose=verbose, + ) return fig diff --git a/shakenbreak/py.typed b/shakenbreak/py.typed index 8b137891..e69de29b 100644 --- a/shakenbreak/py.typed +++ b/shakenbreak/py.typed @@ -1 +0,0 @@ - diff --git a/shakenbreak/shakenbreak.mplstyle b/shakenbreak/shakenbreak.mplstyle index 98379e4a..48e9f0de 100644 --- a/shakenbreak/shakenbreak.mplstyle +++ b/shakenbreak/shakenbreak.mplstyle @@ -50,6 +50,5 @@ ytick.left : True ytick.labelsize : 11.5 # Legend -legend.handlelength : 1.2 -legend.fontsize : 13 +legend.fontsize : 12 legend.frameon : False diff --git a/tests/data/castep/vac_1_Cd_0/Bond_Distortion_30.0%/castep.cell b/tests/data/castep/vac_1_Cd_0/Bond_Distortion_30.0%/castep.cell index f4936f44..56ca1914 100644 --- a/tests/data/castep/vac_1_Cd_0/Bond_Distortion_30.0%/castep.cell +++ b/tests/data/castep/vac_1_Cd_0/Bond_Distortion_30.0%/castep.cell @@ -39,7 +39,7 @@ Cd 10.043575 3.185236 6.991714 Cd 10.024212 9.936445 0.110487 Cd 9.751072 9.845503 6.461401 Te 1.554143 1.973402 4.923374 -Te 2.126624 2.126624 10.960144 +Te 2.126600 2.126600 10.960168 Te 1.717454 8.282029 5.499843 Te 1.477344 8.091485 11.088470 Te 8.265338 1.604358 4.842549 @@ -48,7 +48,7 @@ Te 8.154670 8.417128 4.977731 Te 8.025655 8.432566 11.679300 Te 1.491028 4.818344 1.986594 Te 1.663899 4.799020 7.888536 -Te 2.126624 10.960144 2.126624 +Te 2.126600 10.960168 2.126600 Te 1.666898 11.672231 8.427836 Te 8.037659 4.861865 1.475221 Te 8.079000 5.065492 7.435791 diff --git a/tests/data/cp2k/vac_1_Cd_0/Bond_Distortion_30.0%/cp2k_input.inp b/tests/data/cp2k/vac_1_Cd_0/Bond_Distortion_30.0%/cp2k_input.inp index 113b42dc..ebdcba17 100644 --- a/tests/data/cp2k/vac_1_Cd_0/Bond_Distortion_30.0%/cp2k_input.inp +++ b/tests/data/cp2k/vac_1_Cd_0/Bond_Distortion_30.0%/cp2k_input.inp @@ -1,120 +1,83 @@ -&GLOBAL - PROJECT relax ! files generated will be named relax.out etc - RUN_TYPE GEO_OPT ! geometry optimization - IOLEVEL MEDIUM ! reduce amount of IO +&GLOBAL + PROJECT relax + RUN_TYPE GEO_OPT + IOLEVEL MEDIUM &END GLOBAL -&FORCE_EVAL +&FORCE_EVAL METHOD Quickstep - - ! the electronic structure part - &DFT + &DFT BASIS_SET_FILE_NAME HFX_BASIS POTENTIAL_FILE_NAME GTH_POTENTIALS CHARGE 0 SPIN_POLARIZED .TRUE. - &MGRID - CUTOFF [eV] 500 ! PW cutoff + &MGRID + CUTOFF [eV] 500 &END MGRID - &QS + &QS METHOD GPW EPS_DEFAULT 1e-10 EXTRAPOLATION ASPC &END QS - - ! use the GPW method (i.e. pseudopotential - ! basedcalculations with the Gaussian and Plane - ! Wavesscheme) - &DFT - &KPOINTS - SCHEME GAMMA 1 1 1 ! Gamma point only + &DFT + &KPOINTS + SCHEME GAMMA 1 1 1 &END KPOINTS &END DFT - &POISSON - PERIODIC XYZ ! the default + &POISSON + PERIODIC XYZ &END POISSON - &PRINT - - ! at the end of the SCF procedure generate - ! cubefiles of the density + &PRINT &E_DENSITY_CUBE OFF &END E_DENSITY_CUBE &END PRINT - - ! use the OT METHOD for robust and efficientSCF, - ! suitable for all non-metallic systems. - &SCF - SCF_GUESS RESTART ! can be used to RESTART an interrupted calculation + &SCF + SCF_GUESS RESTART MAX_SCF 80 - EPS_SCF 1e-06 ! accuracy of the SCF procedure typically 1.0E-6 - 1.0E-7 - &OT + EPS_SCF 1e-06 + &OT PRECONDITIONER FULL_SINGLE_INVERSE MINIMIZER DIIS &END OT - - ! an accurate preconditioner suitable also - ! forlarger systems, the most robust choice - ! (DIISmight sometimes be faster, but not - ! asstable). &OUTER_SCF ! repeat the inner SCF cycle 10 times MAX_SCF 10 - EPS_SCF 1e-06 ! must match the above + EPS_SCF 1e-06 &END OUTER_SCF - - ! do not store the wfn - &PRINT - &RESTART + &PRINT + &RESTART &END RESTART &END PRINT &END SCF - - ! specify the exchange and correlation treatment - &XC - - ! use a PBE0 functional - &XC_FUNCTIONAL - &PBE + &XC + &XC_FUNCTIONAL + &PBE SCALE_X 0.75 SCALE_C 1.0 &END PBE &END XC_FUNCTIONAL - - ! 75% GGA exchange 100% GGA correlation - &HF + &HF FRACTION 0.25 - - ! 25 % HFX exchange - &SCREENING + &SCREENING EPS_SCHWARZ 1e-06 SCREEN_ON_INITIAL_P True &END SCREENING - - ! important parameter to get stable - ! HFXcalcsneeds a good (GGA) initial guess - &INTERACTION_POTENTIAL + &INTERACTION_POTENTIAL POTENTIAL_TYPE TRUNCATED CUTOFF_RADIUS 6.0 T_C_G_DATA ./t_c_g.dat &END INTERACTION_POTENTIAL - - ! for condensed phase systemsshould be - ! lessthan halve the celldata file needed with - ! thetruncated operator - &MEMORY + &MEMORY MAX_MEMORY 4000 EPS_STORAGE_SCALING 0.1 &END MEMORY &END HF &END XC &END DFT - - ! Description of the systemStructure will be read - ! from external file - &SUBSYS - &CELL + &SUBSYS + &CELL CELL_FILE_FORMAT CIF CELL_FILE_NAME structure.cif &END CELL - &TOPOLOGY + &TOPOLOGY COORD_FILE_NAME structure.cif COORD_FILE_FORMAT CIF &END TOPOLOGY diff --git a/tests/data/cp2k/vac_1_Cd_0/Bond_Distortion_30.0%/cp2k_input_user_parameters.inp b/tests/data/cp2k/vac_1_Cd_0/Bond_Distortion_30.0%/cp2k_input_user_parameters.inp index 7f9c8795..d6b77f84 100644 --- a/tests/data/cp2k/vac_1_Cd_0/Bond_Distortion_30.0%/cp2k_input_user_parameters.inp +++ b/tests/data/cp2k/vac_1_Cd_0/Bond_Distortion_30.0%/cp2k_input_user_parameters.inp @@ -1,120 +1,83 @@ -&GLOBAL - PROJECT relax ! files generated will be named relax.out etc - RUN_TYPE GEO_OPT ! geometry optimization - IOLEVEL MEDIUM ! reduce amount of IO +&GLOBAL + PROJECT relax + RUN_TYPE GEO_OPT + IOLEVEL MEDIUM &END GLOBAL -&FORCE_EVAL +&FORCE_EVAL METHOD Quickstep - - ! the electronic structure part - &DFT + &DFT BASIS_SET_FILE_NAME HFX_BASIS POTENTIAL_FILE_NAME GTH_POTENTIALS CHARGE 0 SPIN_POLARIZED .FALSE. - &MGRID - CUTOFF [eV] 800 ! PW cutoff + &MGRID + CUTOFF [eV] 800 &END MGRID - &QS + &QS METHOD GPW EPS_DEFAULT 1e-10 EXTRAPOLATION ASPC &END QS - - ! use the GPW method (i.e. pseudopotential - ! basedcalculations with the Gaussian and Plane - ! Wavesscheme) - &DFT - &KPOINTS - SCHEME GAMMA 1 1 1 ! Gamma point only + &DFT + &KPOINTS + SCHEME GAMMA 1 1 1 &END KPOINTS &END DFT - &POISSON - PERIODIC XYZ ! the default + &POISSON + PERIODIC XYZ &END POISSON - &PRINT - - ! at the end of the SCF procedure generate - ! cubefiles of the density + &PRINT &E_DENSITY_CUBE OFF &END E_DENSITY_CUBE &END PRINT - - ! use the OT METHOD for robust and efficientSCF, - ! suitable for all non-metallic systems. - &SCF - SCF_GUESS RESTART ! can be used to RESTART an interrupted calculation + &SCF + SCF_GUESS RESTART MAX_SCF 30 - EPS_SCF 1e-07 ! accuracy of the SCF procedure typically 1.0E-6 - 1.0E-7 - &OT + EPS_SCF 1e-07 + &OT PRECONDITIONER FULL_SINGLE_INVERSE MINIMIZER DIIS &END OT - - ! an accurate preconditioner suitable also - ! forlarger systems, the most robust choice - ! (DIISmight sometimes be faster, but not - ! asstable). &OUTER_SCF ! repeat the inner SCF cycle 10 times MAX_SCF 10 - EPS_SCF 1e-07 ! must match the above + EPS_SCF 1e-07 &END OUTER_SCF - - ! do not store the wfn - &PRINT - &RESTART + &PRINT + &RESTART &END RESTART &END PRINT &END SCF - - ! specify the exchange and correlation treatment - &XC - - ! use a PBE0 functional - &XC_FUNCTIONAL - &PBE + &XC + &XC_FUNCTIONAL + &PBE SCALE_X 0.75 SCALE_C 1.0 &END PBE &END XC_FUNCTIONAL - - ! 75% GGA exchange 100% GGA correlation - &HF + &HF FRACTION 0.35 - - ! 35 % HFX exchange - &SCREENING + &SCREENING EPS_SCHWARZ 1e-06 SCREEN_ON_INITIAL_P True &END SCREENING - - ! important parameter to get stable - ! HFXcalcsneeds a good (GGA) initial guess - &INTERACTION_POTENTIAL + &INTERACTION_POTENTIAL POTENTIAL_TYPE TRUNCATED CUTOFF_RADIUS 6.0 T_C_G_DATA ./t_c_g.dat &END INTERACTION_POTENTIAL - - ! for condensed phase systemsshould be - ! lessthan halve the celldata file needed with - ! thetruncated operator - &MEMORY + &MEMORY MAX_MEMORY 4000 EPS_STORAGE_SCALING 0.1 &END MEMORY &END HF &END XC &END DFT - - ! Description of the systemStructure will be read - ! from external file - &SUBSYS - &CELL + &SUBSYS + &CELL CELL_FILE_FORMAT CIF CELL_FILE_NAME structure.cif &END CELL - &TOPOLOGY + &TOPOLOGY COORD_FILE_NAME structure.cif COORD_FILE_FORMAT CIF &END TOPOLOGY diff --git a/tests/data/cp2k/vac_1_Cd_0/test_vac_1_Cd_0.yaml b/tests/data/cp2k/vac_1_Cd_0/test_vac_1_Cd_0.yaml index e76a6166..36c4e6ca 100644 --- a/tests/data/cp2k/vac_1_Cd_0/test_vac_1_Cd_0.yaml +++ b/tests/data/cp2k/vac_1_Cd_0/test_vac_1_Cd_0.yaml @@ -1,2 +1,2 @@ distortions: - 0.3: -467.07350652793633 + 0.3: -467.0735065278093 diff --git a/tests/data/quantum_espresso/vac_1_Cd_0/Bond_Distortion_30.0%/espresso.pwi b/tests/data/quantum_espresso/vac_1_Cd_0/Bond_Distortion_30.0%/espresso.pwi index 9f2d1585..df648b19 100644 --- a/tests/data/quantum_espresso/vac_1_Cd_0/Bond_Distortion_30.0%/espresso.pwi +++ b/tests/data/quantum_espresso/vac_1_Cd_0/Bond_Distortion_30.0%/espresso.pwi @@ -77,7 +77,7 @@ Cd 10.0435753234 3.1852362330 6.9917138830 Cd 10.0242116925 9.9364450730 0.1104867397 Cd 9.7510717928 9.8455033094 6.4614014274 Te 1.5541433257 1.9734023807 4.9233741105 -Te 2.1266235285 2.1266235285 10.9601444715 +Te 2.1265998000 2.1265998000 10.9601682000 Te 1.7174543391 8.2820291303 5.4998426799 Te 1.4773444045 8.0914853904 11.0884702149 Te 8.2653380643 1.6043581688 4.8425490397 @@ -86,7 +86,7 @@ Te 8.1546704364 8.4171284306 4.9777313383 Te 8.0256553870 8.4325657640 11.6792998138 Te 1.4910276874 4.8183441414 1.9865939846 Te 1.6638992716 4.7990204335 7.8885361462 -Te 2.1266235285 10.9601444715 2.1266235285 +Te 2.1265998000 10.9601682000 2.1265998000 Te 1.6668981429 11.6722305551 8.4278363781 Te 8.0376589517 4.8618654142 1.4752211561 Te 8.0790002588 5.0654924940 7.4357907467 diff --git a/tests/data/quantum_espresso/vac_1_Cd_0/Bond_Distortion_30.0%/espresso_structure.pwi b/tests/data/quantum_espresso/vac_1_Cd_0/Bond_Distortion_30.0%/espresso_structure.pwi index 0dadcd21..b7e86bde 100644 --- a/tests/data/quantum_espresso/vac_1_Cd_0/Bond_Distortion_30.0%/espresso_structure.pwi +++ b/tests/data/quantum_espresso/vac_1_Cd_0/Bond_Distortion_30.0%/espresso_structure.pwi @@ -59,7 +59,7 @@ Cd 10.0435753234 3.1852362330 6.9917138830 Cd 10.0242116925 9.9364450730 0.1104867397 Cd 9.7510717928 9.8455033094 6.4614014274 Te 1.5541433257 1.9734023807 4.9233741105 -Te 2.1266235285 2.1266235285 10.9601444715 +Te 2.1265998000 2.1265998000 10.9601682000 Te 1.7174543391 8.2820291303 5.4998426799 Te 1.4773444045 8.0914853904 11.0884702149 Te 8.2653380643 1.6043581688 4.8425490397 @@ -68,7 +68,7 @@ Te 8.1546704364 8.4171284306 4.9777313383 Te 8.0256553870 8.4325657640 11.6792998138 Te 1.4910276874 4.8183441414 1.9865939846 Te 1.6638992716 4.7990204335 7.8885361462 -Te 2.1266235285 10.9601444715 2.1266235285 +Te 2.1265998000 10.9601682000 2.1265998000 Te 1.6668981429 11.6722305551 8.4278363781 Te 8.0376589517 4.8618654142 1.4752211561 Te 8.0790002588 5.0654924940 7.4357907467 diff --git a/tests/data/quantum_espresso/vac_1_Cd_0/Bond_Distortion_30.0%/espresso_user_parameters.pwi b/tests/data/quantum_espresso/vac_1_Cd_0/Bond_Distortion_30.0%/espresso_user_parameters.pwi index aaff0b34..1725223d 100644 --- a/tests/data/quantum_espresso/vac_1_Cd_0/Bond_Distortion_30.0%/espresso_user_parameters.pwi +++ b/tests/data/quantum_espresso/vac_1_Cd_0/Bond_Distortion_30.0%/espresso_user_parameters.pwi @@ -75,7 +75,7 @@ Cd 10.0435753234 3.1852362330 6.9917138830 Cd 10.0242116925 9.9364450730 0.1104867397 Cd 9.7510717928 9.8455033094 6.4614014274 Te 1.5541433257 1.9734023807 4.9233741105 -Te 2.1266235285 2.1266235285 10.9601444715 +Te 2.1265998000 2.1265998000 10.9601682000 Te 1.7174543391 8.2820291303 5.4998426799 Te 1.4773444045 8.0914853904 11.0884702149 Te 8.2653380643 1.6043581688 4.8425490397 @@ -84,7 +84,7 @@ Te 8.1546704364 8.4171284306 4.9777313383 Te 8.0256553870 8.4325657640 11.6792998138 Te 1.4910276874 4.8183441414 1.9865939846 Te 1.6638992716 4.7990204335 7.8885361462 -Te 2.1266235285 10.9601444715 2.1266235285 +Te 2.1265998000 10.9601682000 2.1265998000 Te 1.6668981429 11.6722305551 8.4278363781 Te 8.0376589517 4.8618654142 1.4752211561 Te 8.0790002588 5.0654924940 7.4357907467 diff --git a/tests/data/quantum_espresso/vac_1_Cd_0/test_vac_1_Cd_0.yaml b/tests/data/quantum_espresso/vac_1_Cd_0/test_vac_1_Cd_0.yaml index 6ec5254d..8816df6b 100644 --- a/tests/data/quantum_espresso/vac_1_Cd_0/test_vac_1_Cd_0.yaml +++ b/tests/data/quantum_espresso/vac_1_Cd_0/test_vac_1_Cd_0.yaml @@ -1,2 +1,2 @@ distortions: - 0.3: -18317.384460112215 + 0.3: -18317.38446010723 diff --git a/tests/data/remote_baseline_plots/Cd_Te_s32c_2_displacement.png b/tests/data/remote_baseline_plots/Cd_Te_s32c_2_displacement.png index f92253e8..423ea86d 100644 Binary files a/tests/data/remote_baseline_plots/Cd_Te_s32c_2_displacement.png and b/tests/data/remote_baseline_plots/Cd_Te_s32c_2_displacement.png differ diff --git a/tests/data/remote_baseline_plots/Int_Se_1_6.png b/tests/data/remote_baseline_plots/Int_Se_1_6.png index d16cc15a..e50348f8 100644 Binary files a/tests/data/remote_baseline_plots/Int_Se_1_6.png and b/tests/data/remote_baseline_plots/Int_Se_1_6.png differ diff --git a/tests/data/remote_baseline_plots/Te_i_Td_Te2.83_+2.png b/tests/data/remote_baseline_plots/Te_i_Td_Te2.83_+2.png index 719db264..8aabe355 100644 Binary files a/tests/data/remote_baseline_plots/Te_i_Td_Te2.83_+2.png and b/tests/data/remote_baseline_plots/Te_i_Td_Te2.83_+2.png differ diff --git a/tests/data/remote_baseline_plots/Te_i_Td_Te2.83_+2_include_site_info_in_name.png b/tests/data/remote_baseline_plots/Te_i_Td_Te2.83_+2_include_site_info_in_name.png index c974b72f..f8fa0504 100644 Binary files a/tests/data/remote_baseline_plots/Te_i_Td_Te2.83_+2_include_site_info_in_name.png and b/tests/data/remote_baseline_plots/Te_i_Td_Te2.83_+2_include_site_info_in_name.png differ diff --git a/tests/data/remote_baseline_plots/Va_O1_1_plot_defect_with_unrecognised_distortion.png b/tests/data/remote_baseline_plots/Va_O1_1_plot_defect_with_unrecognised_distortion.png index 5b041534..5fe73af7 100644 Binary files a/tests/data/remote_baseline_plots/Va_O1_1_plot_defect_with_unrecognised_distortion.png and b/tests/data/remote_baseline_plots/Va_O1_1_plot_defect_with_unrecognised_distortion.png differ diff --git a/tests/data/remote_baseline_plots/as_2_O_on_I_1.png b/tests/data/remote_baseline_plots/as_2_O_on_I_1.png index 19ee474c..363d3113 100644 Binary files a/tests/data/remote_baseline_plots/as_2_O_on_I_1.png and b/tests/data/remote_baseline_plots/as_2_O_on_I_1.png differ diff --git a/tests/data/remote_baseline_plots/v_Ca_s0_0_plot_defect_without_colorbar.png b/tests/data/remote_baseline_plots/v_Ca_s0_0_plot_defect_without_colorbar.png index e2e2a324..9b6e986b 100644 Binary files a/tests/data/remote_baseline_plots/v_Ca_s0_0_plot_defect_without_colorbar.png and b/tests/data/remote_baseline_plots/v_Ca_s0_0_plot_defect_without_colorbar.png differ diff --git a/tests/data/remote_baseline_plots/vac_1_Cd_-1.png b/tests/data/remote_baseline_plots/vac_1_Cd_-1.png index f5d3e108..29f717d8 100644 Binary files a/tests/data/remote_baseline_plots/vac_1_Cd_-1.png and b/tests/data/remote_baseline_plots/vac_1_Cd_-1.png differ diff --git a/tests/data/remote_baseline_plots/vac_1_Cd_-2.png b/tests/data/remote_baseline_plots/vac_1_Cd_-2.png index df525f3f..6ff42c69 100644 Binary files a/tests/data/remote_baseline_plots/vac_1_Cd_-2.png and b/tests/data/remote_baseline_plots/vac_1_Cd_-2.png differ diff --git a/tests/data/remote_baseline_plots/vac_1_Cd_-2_only_rattled.png b/tests/data/remote_baseline_plots/vac_1_Cd_-2_only_rattled.png index 9bc83313..1e0cbc46 100644 Binary files a/tests/data/remote_baseline_plots/vac_1_Cd_-2_only_rattled.png and b/tests/data/remote_baseline_plots/vac_1_Cd_-2_only_rattled.png differ diff --git a/tests/data/remote_baseline_plots/vac_1_Cd_-2_only_rattled_and_rattled_dist_from_other_charges_states.png b/tests/data/remote_baseline_plots/vac_1_Cd_-2_only_rattled_and_rattled_dist_from_other_charges_states.png index 0db743fc..9c2dca69 100644 Binary files a/tests/data/remote_baseline_plots/vac_1_Cd_-2_only_rattled_and_rattled_dist_from_other_charges_states.png and b/tests/data/remote_baseline_plots/vac_1_Cd_-2_only_rattled_and_rattled_dist_from_other_charges_states.png differ diff --git a/tests/data/remote_baseline_plots/vac_1_Cd_-2_rattled_other_charge_states.png b/tests/data/remote_baseline_plots/vac_1_Cd_-2_rattled_other_charge_states.png index f9f7d02d..89f76fbc 100644 Binary files a/tests/data/remote_baseline_plots/vac_1_Cd_-2_rattled_other_charge_states.png and b/tests/data/remote_baseline_plots/vac_1_Cd_-2_rattled_other_charge_states.png differ diff --git a/tests/data/remote_baseline_plots/vac_1_Cd_0.png b/tests/data/remote_baseline_plots/vac_1_Cd_0.png index 5b4354df..4b3a287f 100644 Binary files a/tests/data/remote_baseline_plots/vac_1_Cd_0.png and b/tests/data/remote_baseline_plots/vac_1_Cd_0.png differ diff --git a/tests/data/remote_baseline_plots/vac_1_Cd_0_colors.png b/tests/data/remote_baseline_plots/vac_1_Cd_0_colors.png index 60ed2edb..4c82cf8f 100644 Binary files a/tests/data/remote_baseline_plots/vac_1_Cd_0_colors.png and b/tests/data/remote_baseline_plots/vac_1_Cd_0_colors.png differ diff --git a/tests/data/remote_baseline_plots/vac_1_Cd_0_displacement.png b/tests/data/remote_baseline_plots/vac_1_Cd_0_displacement.png index 18a03787..6a788d4f 100644 Binary files a/tests/data/remote_baseline_plots/vac_1_Cd_0_displacement.png and b/tests/data/remote_baseline_plots/vac_1_Cd_0_displacement.png differ diff --git a/tests/data/remote_baseline_plots/vac_1_Cd_0_fake_defect_name.png b/tests/data/remote_baseline_plots/vac_1_Cd_0_fake_defect_name.png index 88e78d46..24df8bc4 100644 Binary files a/tests/data/remote_baseline_plots/vac_1_Cd_0_fake_defect_name.png and b/tests/data/remote_baseline_plots/vac_1_Cd_0_fake_defect_name.png differ diff --git a/tests/data/remote_baseline_plots/vac_1_Cd_0_include_site_info_in_name.png b/tests/data/remote_baseline_plots/vac_1_Cd_0_include_site_info_in_name.png index 5adbc178..84c445f8 100644 Binary files a/tests/data/remote_baseline_plots/vac_1_Cd_0_include_site_info_in_name.png and b/tests/data/remote_baseline_plots/vac_1_Cd_0_include_site_info_in_name.png differ diff --git a/tests/data/remote_baseline_plots/vac_1_Cd_0_max_dist.png b/tests/data/remote_baseline_plots/vac_1_Cd_0_max_dist.png index 54d37b50..b2daa2c2 100644 Binary files a/tests/data/remote_baseline_plots/vac_1_Cd_0_max_dist.png and b/tests/data/remote_baseline_plots/vac_1_Cd_0_max_dist.png differ diff --git a/tests/data/remote_baseline_plots/vac_1_Cd_0_maxdist_title_linecolor_label.png b/tests/data/remote_baseline_plots/vac_1_Cd_0_maxdist_title_linecolor_label.png index ece35f43..adc10fc8 100644 Binary files a/tests/data/remote_baseline_plots/vac_1_Cd_0_maxdist_title_linecolor_label.png and b/tests/data/remote_baseline_plots/vac_1_Cd_0_maxdist_title_linecolor_label.png differ diff --git a/tests/data/remote_baseline_plots/vac_1_Cd_0_not_enough_markers.png b/tests/data/remote_baseline_plots/vac_1_Cd_0_not_enough_markers.png index 365bb168..4fbbd336 100644 Binary files a/tests/data/remote_baseline_plots/vac_1_Cd_0_not_enough_markers.png and b/tests/data/remote_baseline_plots/vac_1_Cd_0_not_enough_markers.png differ diff --git a/tests/data/remote_baseline_plots/vac_1_Cd_0_notitle.png b/tests/data/remote_baseline_plots/vac_1_Cd_0_notitle.png index d5209dd2..ff98cd0c 100644 Binary files a/tests/data/remote_baseline_plots/vac_1_Cd_0_notitle.png and b/tests/data/remote_baseline_plots/vac_1_Cd_0_notitle.png differ diff --git a/tests/data/remote_baseline_plots/vac_1_Cd_0_other_charge_states.png b/tests/data/remote_baseline_plots/vac_1_Cd_0_other_charge_states.png index 5a78dc07..cf409f44 100644 Binary files a/tests/data/remote_baseline_plots/vac_1_Cd_0_other_charge_states.png and b/tests/data/remote_baseline_plots/vac_1_Cd_0_other_charge_states.png differ diff --git a/tests/data/remote_baseline_plots/vac_1_Cd_0_plot_defect_add_colorbar_max_dist.png b/tests/data/remote_baseline_plots/vac_1_Cd_0_plot_defect_add_colorbar_max_dist.png index 7a924978..ad8beed1 100644 Binary files a/tests/data/remote_baseline_plots/vac_1_Cd_0_plot_defect_add_colorbar_max_dist.png and b/tests/data/remote_baseline_plots/vac_1_Cd_0_plot_defect_add_colorbar_max_dist.png differ diff --git a/tests/data/remote_baseline_plots/vac_1_Cd_0_plot_defect_with_unrecognised_name.png b/tests/data/remote_baseline_plots/vac_1_Cd_0_plot_defect_with_unrecognised_name.png index d6c7ccc0..c38082eb 100644 Binary files a/tests/data/remote_baseline_plots/vac_1_Cd_0_plot_defect_with_unrecognised_name.png and b/tests/data/remote_baseline_plots/vac_1_Cd_0_plot_defect_with_unrecognised_name.png differ diff --git a/tests/data/remote_baseline_plots/vac_1_Cd_0_plot_defect_without_colorbar.png b/tests/data/remote_baseline_plots/vac_1_Cd_0_plot_defect_without_colorbar.png index d2ecc1b8..8e7f4dda 100644 Binary files a/tests/data/remote_baseline_plots/vac_1_Cd_0_plot_defect_without_colorbar.png and b/tests/data/remote_baseline_plots/vac_1_Cd_0_plot_defect_without_colorbar.png differ diff --git a/tests/data/remote_baseline_plots/vac_1_Cd_0_plot_defect_without_title_units_meV.png b/tests/data/remote_baseline_plots/vac_1_Cd_0_plot_defect_without_title_units_meV.png index 8aa3e2dc..f48da0b8 100644 Binary files a/tests/data/remote_baseline_plots/vac_1_Cd_0_plot_defect_without_title_units_meV.png and b/tests/data/remote_baseline_plots/vac_1_Cd_0_plot_defect_without_title_units_meV.png differ diff --git a/tests/data/remote_baseline_plots/vac_1_Cd_0_unparsed_disp.png b/tests/data/remote_baseline_plots/vac_1_Cd_0_unparsed_disp.png index a5d6f774..476ef036 100644 Binary files a/tests/data/remote_baseline_plots/vac_1_Cd_0_unparsed_disp.png and b/tests/data/remote_baseline_plots/vac_1_Cd_0_unparsed_disp.png differ diff --git a/tests/data/vasp/CdTe/CdTe_Int_Cd_2_-60%_Distortion_NN_10_POSCAR b/tests/data/vasp/CdTe/CdTe_Int_Cd_2_-60%_Distortion_NN_10_POSCAR index 470aa78a..dcb12b6f 100644 --- a/tests/data/vasp/CdTe/CdTe_Int_Cd_2_-60%_Distortion_NN_10_POSCAR +++ b/tests/data/vasp/CdTe/CdTe_Int_Cd_2_-60%_Distortion_NN_10_POSCAR @@ -1,73 +1,73 @@ Cd33 Te32 1.0 -13.086768 0.000000 0.000000 -0.000000 13.086768 0.000000 -0.000000 0.000000 13.086768 + 13.0867679999999993 0.0000000000000000 0.0000000000000000 + 0.0000000000000000 13.0867679999999993 0.0000000000000000 + 0.0000000000000000 0.0000000000000000 13.0867679999999993 Cd Te Cd 32 32 1 direct --0.112499 0.112499 -0.112499 Cd -0.000000 0.000000 0.500000 Cd -0.000000 0.500000 0.000000 Cd -0.000000 0.500000 0.500000 Cd -0.500000 0.000000 0.000000 Cd -0.500000 0.000000 0.500000 Cd -0.500000 0.500000 0.000000 Cd -0.500000 0.500000 0.500000 Cd -0.000000 0.250000 0.250000 Cd --0.112501 0.212500 0.787500 Cd -0.000000 0.750000 0.250000 Cd -0.000000 0.750000 0.750000 Cd -0.500000 0.250000 0.250000 Cd -0.687499 0.212500 0.787500 Cd -0.500000 0.750000 0.250000 Cd -0.500000 0.750000 0.750000 Cd -0.250000 0.000000 0.250000 Cd -0.250000 0.000000 0.750000 Cd -0.250000 0.500000 0.250000 Cd -0.250000 0.500000 0.750000 Cd -0.750000 0.000000 0.250000 Cd -0.787500 0.112501 0.787500 Cd -0.750000 0.500000 0.250000 Cd -0.787500 0.312501 0.787500 Cd -0.250000 0.250000 0.000000 Cd -0.250000 0.250000 0.500000 Cd -0.250000 0.750000 0.000000 Cd -0.250000 0.750000 0.500000 Cd -0.787500 0.212500 -0.112501 Cd -0.787500 0.212500 0.687499 Cd -0.750000 0.750000 0.000000 Cd -0.750000 0.750000 0.500000 Cd -0.125000 0.125000 0.375000 Te -0.125000 0.125000 0.875000 Te -0.125000 0.625000 0.375000 Te -0.125000 0.625000 0.875000 Te -0.625000 0.125000 0.375000 Te -0.737501 0.162500 0.837500 Te -0.625000 0.625000 0.375000 Te -0.625000 0.625000 0.875000 Te -0.125000 0.375000 0.125000 Te -0.125000 0.375000 0.625000 Te -0.125000 0.875000 0.125000 Te -0.125000 0.875000 0.625000 Te -0.625000 0.375000 0.125000 Te -0.625000 0.375000 0.625000 Te -0.625000 0.875000 0.125000 Te -0.625000 0.875000 0.625000 Te -0.375000 0.125000 0.125000 Te -0.375000 0.125000 0.625000 Te -0.375000 0.625000 0.125000 Te -0.375000 0.625000 0.625000 Te -0.875000 0.125000 0.125000 Te -0.837500 0.162500 0.737501 Te -0.875000 0.625000 0.125000 Te -0.875000 0.625000 0.625000 Te -0.375000 0.375000 0.375000 Te -0.375000 0.375000 0.875000 Te -0.375000 0.875000 0.375000 Te -0.375000 0.875000 0.875000 Te -0.875000 0.375000 0.375000 Te -0.837500 0.262499 0.837500 Te -0.875000 0.875000 0.375000 Te -0.875000 0.875000 0.875000 Te -0.812500 0.187500 0.812500 Cd + -0.1125000000000000 0.1125000000000000 -0.1125000000000000 Cd + -0.1125000000000000 0.1125000000000000 0.6874999999999999 Cd + -0.1125000000000000 0.3125000000000000 -0.1125000000000000 Cd + 0.0000000000000000 0.5000000000000000 0.5000000000000000 Cd + 0.6874999999999999 0.1125000000000000 -0.1125000000000000 Cd + 0.5000000000000000 0.0000000000000000 0.5000000000000000 Cd + 0.5000000000000000 0.5000000000000000 0.0000000000000000 Cd + 0.5000000000000000 0.5000000000000000 0.5000000000000000 Cd + 0.0000000000000000 0.2500000000000000 0.2500000000000000 Cd + -0.1125000000000000 0.2125000000000000 0.7875000000000000 Cd + 0.0000000000000000 0.7500000000000000 0.2500000000000000 Cd + 0.0000000000000000 0.7500000000000000 0.7500000000000000 Cd + 0.5000000000000000 0.2500000000000000 0.2500000000000000 Cd + 0.6874999999999999 0.2125000000000000 0.7875000000000000 Cd + 0.5000000000000000 0.7500000000000000 0.2500000000000000 Cd + 0.5000000000000000 0.7500000000000000 0.7500000000000000 Cd + 0.2500000000000000 0.0000000000000000 0.2500000000000000 Cd + 0.2500000000000000 0.0000000000000000 0.7500000000000000 Cd + 0.2500000000000000 0.5000000000000000 0.2500000000000000 Cd + 0.2500000000000000 0.5000000000000000 0.7500000000000000 Cd + 0.7500000000000000 0.0000000000000000 0.2500000000000000 Cd + 0.7875000000000000 0.1125000000000000 0.7875000000000000 Cd + 0.7500000000000000 0.5000000000000000 0.2500000000000000 Cd + 0.7875000000000000 0.3125000000000000 0.7875000000000000 Cd + 0.2500000000000000 0.2500000000000000 0.0000000000000000 Cd + 0.2500000000000000 0.2500000000000000 0.5000000000000000 Cd + 0.2500000000000000 0.7500000000000000 0.0000000000000000 Cd + 0.2500000000000000 0.7500000000000000 0.5000000000000000 Cd + 0.7875000000000000 0.2125000000000000 -0.1125000000000000 Cd + 0.7875000000000000 0.2125000000000000 0.6874999999999999 Cd + 0.7500000000000000 0.7500000000000000 0.0000000000000000 Cd + 0.7500000000000000 0.7500000000000000 0.5000000000000000 Cd + 0.1250000000000000 0.1250000000000000 0.3750000000000000 Te + 0.1250000000000000 0.1250000000000000 0.8749999999999999 Te + 0.1250000000000000 0.6250000000000000 0.3750000000000000 Te + 0.1250000000000000 0.6250000000000000 0.8749999999999999 Te + 0.6250000000000000 0.1250000000000000 0.3750000000000000 Te + 0.6250000000000000 0.1250000000000000 0.8749999999999999 Te + 0.6250000000000000 0.6250000000000000 0.3750000000000000 Te + 0.6250000000000000 0.6250000000000000 0.8749999999999999 Te + 0.1250000000000000 0.3750000000000000 0.1250000000000000 Te + 0.1250000000000000 0.3750000000000000 0.6250000000000000 Te + 0.1250000000000000 0.8749999999999999 0.1250000000000000 Te + 0.1250000000000000 0.8749999999999999 0.6250000000000000 Te + 0.6250000000000000 0.3750000000000000 0.1250000000000000 Te + 0.6250000000000000 0.3750000000000000 0.6250000000000000 Te + 0.6250000000000000 0.8749999999999999 0.1250000000000000 Te + 0.6250000000000000 0.8749999999999999 0.6250000000000000 Te + 0.3750000000000000 0.1250000000000000 0.1250000000000000 Te + 0.3750000000000000 0.1250000000000000 0.6250000000000000 Te + 0.3750000000000000 0.6250000000000000 0.1250000000000000 Te + 0.3750000000000000 0.6250000000000000 0.6250000000000000 Te + 0.8749999999999999 0.1250000000000000 0.1250000000000000 Te + 0.8749999999999999 0.1250000000000000 0.6250000000000000 Te + 0.8749999999999999 0.6250000000000000 0.1250000000000000 Te + 0.8749999999999999 0.6250000000000000 0.6250000000000000 Te + 0.3750000000000000 0.3750000000000000 0.3750000000000000 Te + 0.3750000000000000 0.3750000000000000 0.8749999999999999 Te + 0.3750000000000000 0.8749999999999999 0.3750000000000000 Te + 0.3750000000000000 0.8749999999999999 0.8749999999999999 Te + 0.8749999999999999 0.3750000000000000 0.3750000000000000 Te + 0.8749999999999999 0.3750000000000000 0.8749999999999999 Te + 0.8749999999999999 0.8749999999999999 0.3750000000000000 Te + 0.8749999999999999 0.8749999999999999 0.8749999999999999 Te + 0.8125000000000000 0.1875000000000000 0.8125000000000000 Cd diff --git a/tests/data/vasp/CdTe/CdTe_V_Cd_Dimer_0p15_kwarged_POSCAR b/tests/data/vasp/CdTe/CdTe_V_Cd_Dimer_0p15_kwarged_POSCAR new file mode 100644 index 00000000..a9feef87 --- /dev/null +++ b/tests/data/vasp/CdTe/CdTe_V_Cd_Dimer_0p15_kwarged_POSCAR @@ -0,0 +1,71 @@ +Cd31 Te32 +1.0 + 13.0867679999999993 0.0000000000000000 0.0000000000000000 + 0.0000000000000000 13.0867679999999993 0.0000000000000000 + 0.0000000000000000 0.0000000000000000 13.0867679999999993 +Cd Te +31 32 +direct + 0.0076508734572868 -0.0111160800783185 0.4989191331006381 Cd2+ + -0.0278008070674646 0.4853039301126391 -0.0482569391334262 Cd2+ + 0.0056094846267911 0.5266794371003551 0.4949954492756701 Cd2+ + 0.5058729660326475 -0.0103976501765008 0.0061791979158810 Cd2+ + 0.5196124441975841 0.0048015714804666 0.5180523052023848 Cd2+ + 0.5074475927086642 0.4652225102685832 0.0076146146102819 Cd2+ + 0.4959321026354098 0.4909762524898729 0.5016386884274029 Cd2+ + 0.0498837557106085 0.2254428377342082 0.2226057562207752 Cd2+ + -0.0010348721011234 0.2307491652124881 0.7307022678107895 Cd2+ + 0.0015640806065654 0.7929164815479313 0.2448496665046756 Cd2+ + -0.0101733433615836 0.7163172474075324 0.7428707384258456 Cd2+ + 0.4805393877426376 0.2755566484041924 0.2607482687172096 Cd2+ + 0.4812093002675777 0.2332452227025487 0.7481809671641206 Cd2+ + 0.4861285343805795 0.7287897905961499 0.2779939389082122 Cd2+ + 0.4894390914192516 0.7705817000622570 0.7418850976598311 Cd2+ + 0.3004498748498597 0.0444733360420171 0.2718031239002768 Cd2+ + 0.2450393421735408 0.0134477913607735 0.7364195082693271 Cd2+ + 0.2395782973289355 0.4526853108585976 0.2368028764339562 Cd2+ + 0.2437737660004711 0.5166809553735984 0.7110339008209652 Cd2+ + 0.7478899398728867 -0.0021092316084060 0.2539025685425276 Cd2+ + 0.7717375657131700 0.0015952823242325 0.7812445578368915 Cd2+ + 0.7508557113410675 0.5057216659962478 0.2130356721790438 Cd2+ + 0.7494073922354219 0.5002230816200611 0.7275491442156435 Cd2+ + 0.3019406140628218 0.2634147063403596 -0.0139241255905550 Cd2+ + 0.2503904405546442 0.2294487281618451 0.4906439376739768 Cd2+ + 0.2794496359080834 0.7407082869729572 0.0348474640544172 Cd2+ + 0.2194928398181931 0.7303534264061050 0.5119262343250054 Cd2+ + 0.7925336624037438 0.2571388068510573 -0.0021607920147257 Cd2+ + 0.7603926767630633 0.2573835328110093 0.5175829804486854 Cd2+ + 0.7577902536424233 0.7446842965979577 -0.0088044548103033 Cd2+ + 0.7788481757811281 0.7357716039280140 0.4991781815227044 Cd2+ + 0.1250000000000000 0.1250000000000000 0.3750000000000000 Te2- + 0.1250000000000000 0.0540321935245239 0.9459678064754760 Te2- + 0.1250000000000000 0.6250000000000000 0.3750000000000000 Te2- + 0.1250000000000000 0.6250000000000000 0.8749999999999999 Te2- + 0.6250000000000000 0.1250000000000000 0.3750000000000000 Te2- + 0.6250000000000000 0.1250000000000000 0.8749999999999999 Te2- + 0.6250000000000000 0.6250000000000000 0.3750000000000000 Te2- + 0.6250000000000000 0.6250000000000000 0.8749999999999999 Te2- + 0.1250000000000000 0.3750000000000000 0.1250000000000000 Te2- + 0.1250000000000000 0.3750000000000000 0.6250000000000000 Te2- + 0.1250000000000000 0.9459678064754760 0.0540321935245239 Te2- + 0.1250000000000000 0.8749999999999999 0.6250000000000000 Te2- + 0.6250000000000000 0.3750000000000000 0.1250000000000000 Te2- + 0.6250000000000000 0.3750000000000000 0.6250000000000000 Te2- + 0.6250000000000000 0.8749999999999999 0.1250000000000000 Te2- + 0.6250000000000000 0.8749999999999999 0.6250000000000000 Te2- + 0.3750000000000000 0.1250000000000000 0.1250000000000000 Te2- + 0.3750000000000000 0.1250000000000000 0.6250000000000000 Te2- + 0.3750000000000000 0.6250000000000000 0.1250000000000000 Te2- + 0.3750000000000000 0.6250000000000000 0.6250000000000000 Te2- + 0.8749999999999999 0.1250000000000000 0.1250000000000000 Te2- + 0.8749999999999999 0.1250000000000000 0.6250000000000000 Te2- + 0.8749999999999999 0.6250000000000000 0.1250000000000000 Te2- + 0.8749999999999999 0.6250000000000000 0.6250000000000000 Te2- + 0.3750000000000000 0.3750000000000000 0.3750000000000000 Te2- + 0.3750000000000000 0.3750000000000000 0.8749999999999999 Te2- + 0.3750000000000000 0.8749999999999999 0.3750000000000000 Te2- + 0.3750000000000000 0.8749999999999999 0.8749999999999999 Te2- + 0.8749999999999999 0.3750000000000000 0.3750000000000000 Te2- + 0.8749999999999999 0.3750000000000000 0.8749999999999999 Te2- + 0.8749999999999999 0.8749999999999999 0.3750000000000000 Te2- + 0.8749999999999999 0.8749999999999999 0.8749999999999999 Te2- diff --git a/tests/data/vasp/CdTe/CdTe_V_Cd_Dimer_Rattled_0pt1_POSCAR b/tests/data/vasp/CdTe/CdTe_V_Cd_Dimer_Rattled_0pt1_POSCAR new file mode 100644 index 00000000..e90d58f0 --- /dev/null +++ b/tests/data/vasp/CdTe/CdTe_V_Cd_Dimer_Rattled_0pt1_POSCAR @@ -0,0 +1,71 @@ +Cd31 Te32 +1.0 + 13.0867679999999993 0.0000000000000000 0.0000000000000000 + 0.0000000000000000 13.0867679999999993 0.0000000000000000 + 0.0000000000000000 0.0000000000000000 13.0867679999999993 +Cd Te +31 32 +direct + 0.0062748477936141 0.0067399668944426 0.5005463842628175 Cd2+ + 0.0108600046267474 0.4982904898437306 0.0040089905471629 Cd2+ + -0.0005798281488839 0.4992292609740860 0.4912476867184196 Cd2+ + 0.4888837693392256 0.0080202517745635 -0.0073076892189527 Cd2+ + 0.4954234112560248 -0.0021551491495530 0.4927069201857676 Cd2+ + 0.5022512829396093 0.5074511194199661 -0.0107898446343443 Cd2+ + 0.4920675400874676 0.5102897584264418 0.5099222151115034 Cd2+ + -0.0139326547580313 0.2591577726685924 0.2454984190024501 Cd2+ + -0.0042298189011236 0.2566221329107055 0.7399881732952480 Cd2+ + -0.0030276291992162 0.7329293158275093 0.2516120847270399 Cd2+ + -0.0020364158951041 0.7442169262304004 0.7571817636223048 Cd2+ + 0.5002326546793547 0.2628214552991536 0.2434161163756805 Cd2+ + 0.5065804550601825 0.2516980087795269 0.7429129486536834 Cd2+ + 0.5028331434021956 0.7510585103820943 0.2657906328076021 Cd2+ + 0.5019833997599519 0.7572668788779322 0.7536776894373064 Cd2+ + 0.2425775603045213 -0.0055876632130815 0.2584359455036643 Cd2+ + 0.2450547652157574 0.0021475177696298 0.7452661204691099 Cd2+ + 0.2587349618325078 0.4969156897266309 0.2480713690626920 Cd2+ + 0.2549178988891245 0.5069419878865083 0.7404085356026594 Cd2+ + 0.7468405562457381 -0.0147757701672056 0.2540440704723057 Cd2+ + 0.7557204102916126 0.0033023889547702 0.7450057870308784 Cd2+ + 0.7395326710563223 0.5121387359072169 0.2565967564129916 Cd2+ + 0.7608919455016635 0.5024695151156592 0.7467553770292750 Cd2+ + 0.2345167809724359 0.2456727212517074 -0.0018855117443169 Cd2+ + 0.2539050414825705 0.2561900143754324 0.4944728501503702 Cd2+ + 0.2642764555893630 0.7567042967624692 -0.0070248222905559 Cd2+ + 0.2521323396930947 0.7429648631638198 0.4891665510097931 Cd2+ + 0.7651149712494228 0.2384385098848358 -0.0015761426271305 Cd2+ + 0.7602436818720776 0.2578696503052150 0.4962153965172211 Cd2+ + 0.7569841330859556 0.7473574600835342 0.0137033034578395 Cd2+ + 0.7563922793620920 0.7537096729450755 0.5033770519874864 Cd2+ + 0.1250000000000000 0.1250000000000000 0.3750000000000000 Te2- + 0.1118083995437553 0.0594641009822785 0.9573525000419821 Te2- + 0.1230436970471231 0.6259300175381510 0.3724941842766248 Te2- + 0.1225027394309718 0.6353174865070533 0.8754840342701389 Te2- + 0.6274943771934689 0.1281420784816622 0.3931039254288170 Te2- + 0.6201553631738506 0.1223180663216272 0.8639215798712504 Te2- + 0.6276319123047446 0.6240375673737454 0.3730135978466817 Te2- + 0.6263539051282582 0.6191729082770522 0.8634838133126178 Te2- + 0.1242493314278758 0.3822714189035653 0.1271454751322477 Te2- + 0.1250000000000000 0.3750000000000000 0.6250000000000000 Te2- + 0.1203059574998047 0.9537110709395499 0.0610126126410727 Te2- + 0.1205735957834545 0.8722737697013426 0.6357206908419375 Te2- + 0.6258574545398634 0.3716831362331378 0.1161148771395134 Te2- + 0.6259491157156534 0.3817643456389506 0.6325987097216799 Te2- + 0.6206728491469607 0.8736040071681819 0.1200904656101602 Te2- + 0.6219364554719776 0.8798279145461867 0.6022766124297928 Te2- + 0.3880252030458048 0.1157852598732673 0.1115459615800778 Te2- + 0.3682898162389527 0.1285786844031821 0.6201831604604865 Te2- + 0.3946821850493004 0.6393274557991967 0.1175167488001660 Te2- + 0.3692553302103380 0.6260836728493149 0.6395900794789832 Te2- + 0.8751008169699941 0.1205774148271531 0.1123558521549460 Te2- + 0.8874376642755274 0.1181635597295581 0.6195638766764527 Te2- + 0.8804486075869701 0.6263198575097928 0.1223366690475819 Te2- + 0.8938755723776842 0.6123451567664192 0.6250147045546504 Te2- + 0.3726663698056140 0.3710187142073223 0.3857445075649882 Te2- + 0.3681322897921753 0.3835976622004865 0.8600436244388805 Te2- + 0.3771251424762890 0.8820497432175181 0.3808353131387208 Te2- + 0.3828971499403986 0.8809528826208155 0.8638461582418080 Te2- + 0.8756430505988116 0.3800898192099253 0.3780966942526203 Te2- + 0.8870085536984263 0.3760587078291046 0.8629235880213116 Te2- + 0.8840206931911493 0.8725498839485679 0.3905011198839049 Te2- + 0.8833461377043587 0.8747572765396386 0.8671082398817888 Te2- diff --git a/tests/data/vasp/CdTe/v_Cd_entry.pkl b/tests/data/vasp/CdTe/v_Cd_entry.pkl new file mode 100644 index 00000000..5aeae382 Binary files /dev/null and b/tests/data/vasp/CdTe/v_Cd_entry.pkl differ diff --git a/tests/data/vasp/CdTe/vac_1_Cd_0/default_INCAR b/tests/data/vasp/CdTe/vac_1_Cd_0/default_INCAR index 90321236..3c586c56 100644 --- a/tests/data/vasp/CdTe/vac_1_Cd_0/default_INCAR +++ b/tests/data/vasp/CdTe/vac_1_Cd_0/default_INCAR @@ -23,7 +23,7 @@ LREAL = Auto LVHAR = True LWAVE = False NCORE = 16 -NEDOS = 2000 +NEDOS = 3000 NELECT = 564.0 NELM = 40 NSW = 300 diff --git a/tests/data/vasp/CdTe/vacancies_dist_defect_dict.json b/tests/data/vasp/CdTe/vacancies_dist_defect_dict.json index d3cbdb1f..04d7cabe 100644 --- a/tests/data/vasp/CdTe/vacancies_dist_defect_dict.json +++ b/tests/data/vasp/CdTe/vacancies_dist_defect_dict.json @@ -1 +1 @@ -{"v_Cd_Td_Te2.83": {"defect_type": "vacancy", "defect_site": {"species": [{"element": "Cd", "occu": 1}], "abc": [0.0, 0.0, 0.0], "lattice": {"@module": "pymatgen.core.lattice", "@class": "Lattice", "matrix": [[13.086768, 0.0, 0.0], [0.0, 13.086768, 0.0], [0.0, 0.0, 13.086768]], "pbc": [true, true, true]}, "@module": "pymatgen.core.sites", "@class": "PeriodicSite", "properties": {}, "label": "Cd", "@version": null}, "defect_supercell_site": {"species": [{"element": "Cd", "occu": 1}], "abc": [0.0, 0.0, 0.0], "lattice": {"@module": "pymatgen.core.lattice", "@class": "Lattice", "matrix": [[13.086768, 0.0, 0.0], [0.0, 13.086768, 0.0], [0.0, 0.0, 13.086768]], "pbc": [true, true, true]}, "@module": "pymatgen.core.sites", "@class": "PeriodicSite", "properties": {}, "label": "Cd", "@version": null}, "charges": {"0": {"structures": {"Unperturbed": {"@module": "pymatgen.core.structure", "@class": "Structure", "charge": -2.0, "lattice": {"matrix": [[13.086768, 0.0, 0.0], [0.0, 13.086768, 0.0], [0.0, 0.0, 13.086768]], "pbc": [true, true, true], "a": 13.086768, "b": 13.086768, "c": 13.086768, "alpha": 90.0, "beta": 90.0, "gamma": 90.0, "volume": 2241.2856479961474}, "properties": {}, "sites": [{"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.0, 0.0, 0.5], "xyz": [0.0, 0.0, 6.543384], "properties": {}, "label": "Cd2+"}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.0, 0.5, 0.0], "xyz": [0.0, 6.543384, 0.0], "properties": {}, "label": "Cd2+"}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.0, 0.5, 0.5], "xyz": [0.0, 6.543384, 6.543384], "properties": {}, "label": "Cd2+"}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.5, 0.0, 0.0], "xyz": [6.543384, 0.0, 0.0], "properties": {}, "label": "Cd2+"}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.5, 0.0, 0.5], "xyz": [6.543384, 0.0, 6.543384], "properties": {}, "label": "Cd2+"}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.5, 0.5, 0.0], "xyz": [6.543384, 6.543384, 0.0], "properties": {}, "label": "Cd2+"}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.5, 0.5, 0.5], "xyz": [6.543384, 6.543384, 6.543384], "properties": {}, "label": "Cd2+"}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.0, 0.25, 0.25], "xyz": [0.0, 3.271692, 3.271692], "properties": {}, "label": "Cd2+"}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.0, 0.25, 0.75], "xyz": [0.0, 3.271692, 9.815076], "properties": {}, "label": "Cd2+"}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.0, 0.75, 0.25], "xyz": [0.0, 9.815076, 3.271692], "properties": {}, "label": "Cd2+"}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.0, 0.75, 0.75], "xyz": [0.0, 9.815076, 9.815076], "properties": {}, "label": "Cd2+"}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.5, 0.25, 0.25], "xyz": [6.543384, 3.271692, 3.271692], "properties": {}, "label": "Cd2+"}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.5, 0.25, 0.75], "xyz": [6.543384, 3.271692, 9.815076], "properties": {}, "label": "Cd2+"}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.5, 0.75, 0.25], "xyz": [6.543384, 9.815076, 3.271692], "properties": {}, "label": "Cd2+"}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.5, 0.75, 0.75], "xyz": [6.543384, 9.815076, 9.815076], "properties": {}, "label": "Cd2+"}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.25, 0.0, 0.25], "xyz": [3.271692, 0.0, 3.271692], "properties": {}, "label": "Cd2+"}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.25, 0.0, 0.75], "xyz": [3.271692, 0.0, 9.815076], "properties": {}, "label": "Cd2+"}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.25, 0.5, 0.25], "xyz": [3.271692, 6.543384, 3.271692], "properties": {}, "label": "Cd2+"}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.25, 0.5, 0.75], "xyz": [3.271692, 6.543384, 9.815076], "properties": {}, "label": "Cd2+"}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.75, 0.0, 0.25], "xyz": [9.815076, 0.0, 3.271692], "properties": {}, "label": "Cd2+"}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.75, 0.0, 0.75], "xyz": [9.815076, 0.0, 9.815076], "properties": {}, "label": "Cd2+"}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.75, 0.5, 0.25], "xyz": [9.815076, 6.543384, 3.271692], "properties": {}, "label": "Cd2+"}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.75, 0.5, 0.75], "xyz": [9.815076, 6.543384, 9.815076], "properties": {}, "label": "Cd2+"}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.25, 0.25, 0.0], "xyz": [3.271692, 3.271692, 0.0], "properties": {}, "label": "Cd2+"}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.25, 0.25, 0.5], "xyz": [3.271692, 3.271692, 6.543384], "properties": {}, "label": "Cd2+"}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.25, 0.75, 0.0], "xyz": [3.271692, 9.815076, 0.0], "properties": {}, "label": "Cd2+"}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.25, 0.75, 0.5], "xyz": [3.271692, 9.815076, 6.543384], "properties": {}, "label": "Cd2+"}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.75, 0.25, 0.0], "xyz": [9.815076, 3.271692, 0.0], "properties": {}, "label": "Cd2+"}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.75, 0.25, 0.5], "xyz": [9.815076, 3.271692, 6.543384], "properties": {}, "label": "Cd2+"}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.75, 0.75, 0.0], "xyz": [9.815076, 9.815076, 0.0], "properties": {}, "label": "Cd2+"}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.75, 0.75, 0.5], "xyz": [9.815076, 9.815076, 6.543384], "properties": {}, "label": "Cd2+"}, {"species": [{"element": "Te", "oxidation_state": -2.0, "spin": null, "occu": 1}], "abc": [0.125, 0.125, 0.375], "xyz": [1.635846, 1.635846, 4.907538], "properties": {}, "label": "Te2-"}, {"species": [{"element": "Te", "oxidation_state": -2.0, "spin": null, "occu": 1}], "abc": [0.125, 0.125, 0.8749999999999999], "xyz": [1.635846, 1.635846, 11.450921999999998], "properties": {}, "label": "Te2-"}, {"species": [{"element": "Te", "oxidation_state": -2.0, "spin": null, "occu": 1}], "abc": [0.125, 0.625, 0.375], "xyz": [1.635846, 8.17923, 4.907538], "properties": {}, "label": "Te2-"}, {"species": [{"element": "Te", "oxidation_state": -2.0, "spin": null, "occu": 1}], "abc": [0.125, 0.625, 0.8749999999999999], "xyz": [1.635846, 8.17923, 11.450921999999998], "properties": {}, "label": "Te2-"}, {"species": [{"element": "Te", "oxidation_state": -2.0, "spin": null, "occu": 1}], "abc": [0.625, 0.125, 0.375], "xyz": [8.17923, 1.635846, 4.907538], "properties": {}, "label": "Te2-"}, {"species": [{"element": "Te", "oxidation_state": -2.0, "spin": null, "occu": 1}], "abc": [0.625, 0.125, 0.8749999999999999], "xyz": [8.17923, 1.635846, 11.450921999999998], "properties": {}, "label": "Te2-"}, {"species": [{"element": "Te", "oxidation_state": -2.0, "spin": null, "occu": 1}], "abc": [0.625, 0.625, 0.375], "xyz": [8.17923, 8.17923, 4.907538], "properties": {}, "label": "Te2-"}, {"species": [{"element": "Te", "oxidation_state": -2.0, "spin": null, "occu": 1}], "abc": [0.625, 0.625, 0.8749999999999999], "xyz": [8.17923, 8.17923, 11.450921999999998], "properties": {}, "label": "Te2-"}, {"species": [{"element": "Te", "oxidation_state": -2.0, "spin": null, "occu": 1}], "abc": [0.125, 0.375, 0.125], "xyz": [1.635846, 4.907538, 1.635846], "properties": {}, "label": "Te2-"}, {"species": [{"element": "Te", "oxidation_state": -2.0, "spin": null, "occu": 1}], "abc": [0.125, 0.375, 0.625], "xyz": [1.635846, 4.907538, 8.17923], "properties": {}, "label": "Te2-"}, {"species": [{"element": "Te", "oxidation_state": -2.0, "spin": null, "occu": 1}], "abc": [0.125, 0.8749999999999999, 0.125], "xyz": [1.635846, 11.450921999999998, 1.635846], "properties": {}, "label": "Te2-"}, {"species": [{"element": "Te", "oxidation_state": -2.0, "spin": null, "occu": 1}], "abc": [0.125, 0.8749999999999999, 0.625], "xyz": [1.635846, 11.450921999999998, 8.17923], "properties": {}, "label": "Te2-"}, {"species": [{"element": "Te", "oxidation_state": -2.0, "spin": null, "occu": 1}], "abc": [0.625, 0.375, 0.125], "xyz": [8.17923, 4.907538, 1.635846], "properties": {}, "label": "Te2-"}, {"species": [{"element": "Te", "oxidation_state": -2.0, "spin": null, "occu": 1}], "abc": [0.625, 0.375, 0.625], "xyz": [8.17923, 4.907538, 8.17923], "properties": {}, "label": "Te2-"}, {"species": [{"element": "Te", "oxidation_state": -2.0, "spin": null, "occu": 1}], "abc": [0.625, 0.8749999999999999, 0.125], "xyz": [8.17923, 11.450921999999998, 1.635846], "properties": {}, "label": "Te2-"}, {"species": [{"element": "Te", "oxidation_state": -2.0, "spin": null, "occu": 1}], "abc": [0.625, 0.8749999999999999, 0.625], "xyz": [8.17923, 11.450921999999998, 8.17923], "properties": {}, "label": "Te2-"}, {"species": [{"element": "Te", "oxidation_state": -2.0, "spin": null, "occu": 1}], "abc": [0.375, 0.125, 0.125], "xyz": [4.907538, 1.635846, 1.635846], "properties": {}, "label": "Te2-"}, {"species": [{"element": "Te", "oxidation_state": -2.0, "spin": null, "occu": 1}], "abc": [0.375, 0.125, 0.625], "xyz": [4.907538, 1.635846, 8.17923], "properties": {}, "label": "Te2-"}, {"species": [{"element": "Te", "oxidation_state": -2.0, "spin": null, "occu": 1}], "abc": [0.375, 0.625, 0.125], "xyz": [4.907538, 8.17923, 1.635846], "properties": {}, "label": "Te2-"}, {"species": [{"element": "Te", "oxidation_state": -2.0, "spin": null, "occu": 1}], "abc": [0.375, 0.625, 0.625], "xyz": [4.907538, 8.17923, 8.17923], "properties": {}, "label": "Te2-"}, {"species": [{"element": "Te", "oxidation_state": -2.0, "spin": null, "occu": 1}], "abc": [0.8749999999999999, 0.125, 0.125], "xyz": [11.450921999999998, 1.635846, 1.635846], "properties": {}, "label": "Te2-"}, {"species": [{"element": "Te", "oxidation_state": -2.0, "spin": null, "occu": 1}], "abc": [0.8749999999999999, 0.125, 0.625], "xyz": [11.450921999999998, 1.635846, 8.17923], "properties": {}, "label": "Te2-"}, {"species": [{"element": "Te", "oxidation_state": -2.0, "spin": null, "occu": 1}], "abc": [0.8749999999999999, 0.625, 0.125], "xyz": [11.450921999999998, 8.17923, 1.635846], "properties": {}, "label": "Te2-"}, {"species": [{"element": "Te", "oxidation_state": -2.0, "spin": null, "occu": 1}], "abc": [0.8749999999999999, 0.625, 0.625], "xyz": [11.450921999999998, 8.17923, 8.17923], "properties": {}, "label": "Te2-"}, {"species": [{"element": "Te", "oxidation_state": -2.0, "spin": null, "occu": 1}], "abc": [0.375, 0.375, 0.375], "xyz": [4.907538, 4.907538, 4.907538], "properties": {}, "label": "Te2-"}, {"species": [{"element": "Te", "oxidation_state": -2.0, "spin": null, "occu": 1}], "abc": [0.375, 0.375, 0.8749999999999999], "xyz": [4.907538, 4.907538, 11.450921999999998], "properties": {}, "label": "Te2-"}, {"species": [{"element": "Te", "oxidation_state": -2.0, "spin": null, "occu": 1}], "abc": [0.375, 0.8749999999999999, 0.375], "xyz": [4.907538, 11.450921999999998, 4.907538], "properties": {}, "label": "Te2-"}, {"species": [{"element": "Te", "oxidation_state": -2.0, "spin": null, "occu": 1}], "abc": [0.375, 0.8749999999999999, 0.8749999999999999], "xyz": [4.907538, 11.450921999999998, 11.450921999999998], "properties": {}, "label": "Te2-"}, {"species": [{"element": "Te", "oxidation_state": -2.0, "spin": null, "occu": 1}], "abc": [0.8749999999999999, 0.375, 0.375], "xyz": [11.450921999999998, 4.907538, 4.907538], "properties": {}, "label": "Te2-"}, {"species": [{"element": "Te", "oxidation_state": -2.0, "spin": null, "occu": 1}], "abc": [0.8749999999999999, 0.375, 0.8749999999999999], "xyz": [11.450921999999998, 4.907538, 11.450921999999998], "properties": {}, "label": "Te2-"}, {"species": [{"element": "Te", "oxidation_state": -2.0, "spin": null, "occu": 1}], "abc": [0.8749999999999999, 0.8749999999999999, 0.375], "xyz": [11.450921999999998, 11.450921999999998, 4.907538], "properties": {}, "label": "Te2-"}, {"species": [{"element": "Te", "oxidation_state": -2.0, "spin": null, "occu": 1}], "abc": [0.8749999999999999, 0.8749999999999999, 0.8749999999999999], "xyz": [11.450921999999998, 11.450921999999998, 11.450921999999998], "properties": {}, "label": "Te2-"}], "@version": null}, "distortions": {"Bond_Distortion_-30.0%": {"@module": "pymatgen.core.structure", "@class": "Structure", "charge": -2.0, "lattice": {"matrix": [[13.086768, 0.0, 0.0], [0.0, 13.086768, 0.0], [0.0, 0.0, 13.086768]], "pbc": [true, true, true], "a": 13.086768, "b": 13.086768, "c": 13.086768, "alpha": 90.0, "beta": 90.0, "gamma": 90.0, "volume": 2241.2856479961474}, "properties": {}, "sites": [{"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.01568711948403533, 0.016849917236106522, 0.5013659606570436], "xyz": [0.20529369327585004, 0.22051095768812726, 6.561260010215857], "properties": {}, "label": "Cd2+"}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.027150011566868578, 0.4957262246093266, 0.010022476367907264], "xyz": [0.35530590257292555, 6.4874540929781475, 0.13116182301228502], "properties": {}, "label": "Cd2+"}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [-0.0014495703722098626, 0.498073152435215, 0.47811921679604913], "xyz": [-0.01897019116078412, 6.518167792948294, 6.257035266551598], "properties": {}, "label": "Cd2+"}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.4722094233480639, 0.020050629436408757, -0.018269223047381645], "xyz": [6.179695170769895, 0.26239793568825215, -0.2390850835613366], "properties": {}, "label": "Cd2+"}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.4885585281400621, -0.005387872873882472, 0.481767300464419], "xyz": [6.393652112190464, -0.07050984231399317, 6.304776891164144], "properties": {}, "label": "Cd2+"}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.5056282073490231, 0.5186277985499153, -0.02697461158586072], "xyz": [6.617039043832561, 6.787161677973477, -0.3530104837142713], "properties": {}, "label": "Cd2+"}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.4801688502186689, 0.5257243960661042, 0.5248055377787585], "xyz": [6.283858343638469, 6.880033203257218, 6.868008318025847], "properties": {}, "label": "Cd2+"}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [-0.034831636895078275, 0.27289443167148103, 0.23874604750612521], "xyz": [-0.4558335511061297, 3.5713061157765242, 3.1244141346296392], "properties": {}, "label": "Cd2+"}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [-0.010574547252808927, 0.2665553322767636, 0.72497043323812], "xyz": [-0.13838664660254776, 3.488347792668917, 9.487519866646766], "properties": {}, "label": "Cd2+"}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [-0.007569072998040621, 0.7073232895687733, 0.2540302118175998], "xyz": [-0.09905470230042206, 9.256575791583357, 3.324434447047787], "properties": {}, "label": "Cd2+"}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [-0.005091039737760129, 0.735542315576001, 0.7679544090557621], "xyz": [-0.06662525592684765, 9.625871638125911, 10.050041185889857], "properties": {}, "label": "Cd2+"}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.500581636698387, 0.282053638247884, 0.23354029093920137], "xyz": [6.5509957445320754, 3.691170527305984, 3.0562876061738304], "properties": {}, "label": "Cd2+"}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.5164511376504562, 0.2542450219488173, 0.7322823716342084], "xyz": [6.758676221767586, 3.32724561739908, 9.583209508066666], "properties": {}, "label": "Cd2+"}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.5070828585054892, 0.7526462759552357, 0.2894765820190052], "xyz": [6.636075726038163, 9.849707199490148, 3.7883128703156927], "properties": {}, "label": "Cd2+"}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.5049584993998797, 0.7681671971948304, 0.759194223593266], "xyz": [6.608274731274364, 10.052825894898996, 9.9353986711052], "properties": {}, "label": "Cd2+"}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.23144390076130325, -0.013969158032703688, 0.27108986375916067], "xyz": [3.028852634278199, -0.18281113032932955, 3.5476901541677432], "properties": {}, "label": "Cd2+"}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.23763691303939355, 0.005368794424074448, 0.738165301172775], "xyz": [3.109899149182718, 0.07026016706755592, 9.660198042098234], "properties": {}, "label": "Cd2+"}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.27183740458126954, 0.4922892243165773, 0.2451784226567301], "xyz": [3.5574730474772114, 6.442474867531005, 3.2085931359145703], "properties": {}, "label": "Cd2+"}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.26229474722281126, 0.517354969716271, 0.7260213390066484], "xyz": [3.432590504523575, 6.770504462323864, 9.501272826629359], "properties": {}, "label": "Cd2+"}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.7421013906143455, -0.03693942541801388, 0.2601101761807642], "xyz": [9.711708731447317, -0.48341769049885064, 3.404001530116787], "properties": {}, "label": "Cd2+"}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.7643010257290317, 0.008255972386925511, 0.7375144675771959], "xyz": [10.002230205877868, 0.10804399524210039, 9.651680733826284], "properties": {}, "label": "Cd2+"}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.7772298637541588, 0.506173787789148, 0.24188844257318742], "xyz": [10.171426909622285, 6.6241789284778125, 3.165537929836627], "properties": {}, "label": "Cd2+"}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.7112919524310897, 0.48918180312926857, 0.7452862206392076], "xyz": [9.308512761732706, 6.401808767374411, 9.753387863102121], "properties": {}, "label": "Cd2+"}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.2597626037064264, 0.2654750359385809, -0.013817874624074463], "xyz": [3.3994529297819422, 3.47421020511987, -0.1808313194583497], "properties": {}, "label": "Cd2+"}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.2856911389734074, 0.26676074190617316, 0.4824379442736103], "xyz": [3.738773655400741, 3.4910359408339655, 6.313553451105666], "properties": {}, "label": "Cd2+"}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.25533084923273663, 0.7324121579095495, -0.027083622475517065], "xyz": [3.341455587151802, 9.58490799094164, -0.3544370839366775], "properties": {}, "label": "Cd2+"}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.28778742812355695, 0.7210962747120893, 0.49605964343217374], "xyz": [3.766207305169665, 9.436819652821379, 6.4918174677595815], "properties": {}, "label": "Cd2+"}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.7756092046801939, 0.26967412576303745, -0.009461508706947064], "xyz": [10.150217720314211, 3.529162719463694, -0.1238205693777962], "properties": {}, "label": "Cd2+"}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.767460332714889, 0.2433936502088358, 0.5342582586445987], "xyz": [10.043575323442562, 3.1852362329561856, 6.991713882965858], "properties": {}, "label": "Cd2+"}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.7659806984052299, 0.759274182362689, 0.008442629968716076], "xyz": [10.024211692507214, 9.936445072970201, 0.11048673971043455], "properties": {}, "label": "Cd2+"}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.7451092426178079, 0.7523250438453772, 0.49373546069156204], "xyz": [9.751071792794963, 9.84550330939428, 6.461401427443592], "properties": {}, "label": "Cd2+"}, {"species": [{"element": "Te", "oxidation_state": -2.0, "spin": null, "occu": 1}], "abc": [0.11875684857742955, 0.1507937162676332, 0.3762100856753475], "xyz": [1.5541433257439505, 1.9734023806523413, 4.923374110493397], "properties": {}, "label": "Te2-"}, {"species": [{"element": "Te", "oxidation_state": -2.0, "spin": null, "occu": 1}], "abc": [0.08750097632244412, 0.08750097632244412, 0.9124990236775559], "xyz": [1.1451049769053194, 1.1451049769053194, 11.94166302309468], "properties": {}, "label": "Te2-"}, {"species": [{"element": "Te", "oxidation_state": -2.0, "spin": null, "occu": 1}], "abc": [0.13123594298367203, 0.6328551962041555, 0.42025981357204256], "xyz": [1.7174543390885435, 8.282029130318264, 5.499842679940572], "properties": {}, "label": "Te2-"}, {"species": [{"element": "Te", "oxidation_state": -2.0, "spin": null, "occu": 1}], "abc": [0.11288840793462648, 0.618295165804068, 0.8473039496781265], "xyz": [1.4773444045298159, 8.09148539039937, 11.088470214921315], "properties": {}, "label": "Te2-"}, {"species": [{"element": "Te", "oxidation_state": -2.0, "spin": null, "occu": 1}], "abc": [0.6315797807618615, 0.12259391843436342, 0.3700339946167043], "xyz": [8.265338064321345, 1.6043581687614372, 4.842549039662058], "properties": {}, "label": "Te2-"}, {"species": [{"element": "Te", "oxidation_state": -2.0, "spin": null, "occu": 1}], "abc": [0.6283847628206453, 0.11043227069263029, 0.8462095332815449], "xyz": [8.22352560576881, 1.4452015062676518, 11.074147841443857], "properties": {}, "label": "Te2-"}, {"species": [{"element": "Te", "oxidation_state": -2.0, "spin": null, "occu": 1}], "abc": [0.6231233285696897, 0.6431785472589133, 0.3803636878306193], "xyz": [8.1546704363793, 8.417128430554435, 4.977731338263738], "properties": {}, "label": "Te2-"}, {"species": [{"element": "Te", "oxidation_state": -2.0, "spin": null, "occu": 1}], "abc": [0.6132648937495118, 0.644358161160185, 0.8924510477913717], "xyz": [8.02565538704451, 8.43256576400995, 11.679299813802594], "properties": {}, "label": "Te2-"}, {"species": [{"element": "Te", "oxidation_state": -2.0, "spin": null, "occu": 1}], "abc": [0.11393398945863639, 0.36818442425335673, 0.1518017271048436], "xyz": [1.49102768735962, 4.818344141417253, 1.9865939846203997], "properties": {}, "label": "Te2-"}, {"species": [{"element": "Te", "oxidation_state": -2.0, "spin": null, "occu": 1}], "abc": [0.12714363634965842, 0.36670784058284467, 0.6027871928487836], "xyz": [1.6638992715843466, 4.799020433488673, 7.888536146183289], "properties": {}, "label": "Te2-"}, {"species": [{"element": "Te", "oxidation_state": -2.0, "spin": null, "occu": 1}], "abc": [0.08750097632244412, 0.9124990236775559, 0.08750097632244412], "xyz": [1.1451049769053194, 11.94166302309468, 1.1451049769053194], "properties": {}, "label": "Te2-"}, {"species": [{"element": "Te", "oxidation_state": -2.0, "spin": null, "occu": 1}], "abc": [0.12737278928913331, 0.8919108640973763, 0.6439967743041993], "xyz": [1.6668981429397725, 11.672230555121892, 8.427836378067418], "properties": {}, "label": "Te2-"}, {"species": [{"element": "Te", "oxidation_state": -2.0, "spin": null, "occu": 1}], "abc": [0.6141821228674019, 0.3715100179204548, 0.11272616402540049], "xyz": [8.037658951713183, 4.861865414200834, 1.4752211561303623], "properties": {}, "label": "Te2-"}, {"species": [{"element": "Te", "oxidation_state": -2.0, "spin": null, "occu": 1}], "abc": [0.6173411386799441, 0.387069786365467, 0.5681915310744818], "xyz": [8.079000258760255, 5.06549249397443, 7.435790746736534], "properties": {}, "label": "Te2-"}, {"species": [{"element": "Te", "oxidation_state": -2.0, "spin": null, "occu": 1}], "abc": [0.608224540597382, 0.8839467110079551, 0.11295790115121591], "xyz": [7.959693454704519, 11.568005531324154, 1.4782538461328956], "properties": {}, "label": "Te2-"}, {"species": [{"element": "Te", "oxidation_state": -2.0, "spin": null, "occu": 1}], "abc": [0.6742054626232512, 0.9108186394979915, 0.6062918720004151], "xyz": [8.82317047368316, 11.91967222518585, 7.934401069155128], "properties": {}, "label": "Te2-"}, {"species": [{"element": "Te", "oxidation_state": -2.0, "spin": null, "occu": 1}], "abc": [0.36063832552584496, 0.12770918212328716, 0.16147519869745774], "xyz": [4.71959009806521, 1.6713004379172065, 2.1131884631075315], "properties": {}, "label": "Te2-"}, {"species": [{"element": "Te", "oxidation_state": -2.0, "spin": null, "occu": 1}], "abc": [0.37525204242498533, 0.11394353706788272, 0.5933896303873651], "xyz": [4.910836420741941, 1.4911526347067814, 7.765552426485197], "properties": {}, "label": "Te2-"}, {"species": [{"element": "Te", "oxidation_state": -2.0, "spin": null, "occu": 1}], "abc": [0.4060941606888188, 0.6079088993238952, 0.11140969169113184], "xyz": [5.314460067089292, 7.9555627305871734, 1.4579927881133699], "properties": {}, "label": "Te2-"}, {"species": [{"element": "Te", "oxidation_state": -2.0, "spin": null, "occu": 1}], "abc": [0.3803128561907225, 0.6426243580437953, 0.6395882828468022], "xyz": [4.977066116385349, 8.409875884868082, 8.37014347313448], "properties": {}, "label": "Te2-"}, {"species": [{"element": "Te", "oxidation_state": -2.0, "spin": null, "occu": 1}], "abc": [0.8947428748509965, 0.13988220655203906, 0.0971153956045202], "xyz": [11.709292422828025, 1.830605984474615, 1.2709266515045756], "properties": {}, "label": "Te2-"}, {"species": [{"element": "Te", "oxidation_state": -2.0, "spin": null, "occu": 1}], "abc": [0.9050213842460662, 0.12764676957276155, 0.5948089700532792], "xyz": [11.843804890667123, 1.6704836593481895, 7.784126995406211], "properties": {}, "label": "Te2-"}, {"species": [{"element": "Te", "oxidation_state": -2.0, "spin": null, "occu": 1}], "abc": [0.895865344260897, 0.6243931913490971, 0.10527059970447229], "xyz": [11.72398191958249, 8.17128883596524, 1.3776519155532974], "properties": {}, "label": "Te2-"}, {"species": [{"element": "Te", "oxidation_state": -2.0, "spin": null, "occu": 1}], "abc": [0.8997301454086939, 0.6260280502017525, 0.6286909690625229], "xyz": [11.774559675569842, 8.192683854482688, 8.227532855816413], "properties": {}, "label": "Te2-"}, {"species": [{"element": "Te", "oxidation_state": -2.0, "spin": null, "occu": 1}], "abc": [0.39036318842662027, 0.3750040440301268, 0.36936334792189346], "xyz": [5.108592482679464, 4.907590923284054, 4.833772441957102], "properties": {}, "label": "Te2-"}, {"species": [{"element": "Te", "oxidation_state": -2.0, "spin": null, "occu": 1}], "abc": [0.3840986630527822, 0.3765797318809899, 0.8701975112891386], "xyz": [5.026610092481932, 4.928211584628718, 11.388072944418337], "properties": {}, "label": "Te2-"}, {"species": [{"element": "Te", "oxidation_state": -2.0, "spin": null, "occu": 1}], "abc": [0.3794827130771702, 0.8854032160195859, 0.36748368646509866], "xyz": [4.966202226051492, 11.587066474502205, 4.809173748553486], "properties": {}, "label": "Te2-"}, {"species": [{"element": "Te", "oxidation_state": -2.0, "spin": null, "occu": 1}], "abc": [0.3917242540183349, 0.8543409397301263, 0.878435252439452], "xyz": [5.126404432311016, 11.180561671150144, 11.495878351696541], "properties": {}, "label": "Te2-"}, {"species": [{"element": "Te", "oxidation_state": -2.0, "spin": null, "occu": 1}], "abc": [0.8817775378020919, 0.3813543713889261, 0.40317439575351394], "xyz": [11.539618064827206, 4.990696184152713, 5.276249780766422], "properties": {}, "label": "Te2-"}, {"species": [{"element": "Te", "oxidation_state": -2.0, "spin": null, "occu": 1}], "abc": [0.8794108581844571, 0.3946822747640026, 0.87570067228848], "xyz": [11.508645877740891, 5.1651153635487566, 11.460091535683366], "properties": {}, "label": "Te2-"}, {"species": [{"element": "Te", "oxidation_state": -2.0, "spin": null, "occu": 1}], "abc": [0.8832990018766742, 0.9089615994988091, 0.36272692412143825], "xyz": [11.5595291121916, 11.89536957354983, 4.746923103330866], "properties": {}, "label": "Te2-"}, {"species": [{"element": "Te", "oxidation_state": -2.0, "spin": null, "occu": 1}], "abc": [0.8460654184237592, 0.8901079778386413, 0.8694308400251983], "xyz": [11.072261843734662, 11.648636600923439, 11.378039695454884], "properties": {}, "label": "Te2-"}], "@version": null}}}}}}, "v_Te_Td_Cd2.83": {"defect_type": "vacancy", "defect_site": {"species": [{"element": "Te", "occu": 1}], "abc": [0.125, 0.125, 0.375], "lattice": {"@module": "pymatgen.core.lattice", "@class": "Lattice", "matrix": [[13.086768, 0.0, 0.0], [0.0, 13.086768, 0.0], [0.0, 0.0, 13.086768]], "pbc": [true, true, true]}, "@module": "pymatgen.core.sites", "@class": "PeriodicSite", "properties": {}, "label": "Te", "@version": null}, "defect_supercell_site": {"species": [{"element": "Te", "occu": 1}], "abc": [0.125, 0.125, 0.375], "lattice": {"@module": "pymatgen.core.lattice", "@class": "Lattice", "matrix": [[13.086768, 0.0, 0.0], [0.0, 13.086768, 0.0], [0.0, 0.0, 13.086768]], "pbc": [true, true, true]}, "@module": "pymatgen.core.sites", "@class": "PeriodicSite", "properties": {}, "label": "Te", "@version": null}, "charges": {"0": {"structures": {"Unperturbed": {"@module": "pymatgen.core.structure", "@class": "Structure", "charge": 2.0, "lattice": {"matrix": [[13.086768, 0.0, 0.0], [0.0, 13.086768, 0.0], [0.0, 0.0, 13.086768]], "pbc": [true, true, true], "a": 13.086768, "b": 13.086768, "c": 13.086768, "alpha": 90.0, "beta": 90.0, "gamma": 90.0, "volume": 2241.2856479961474}, "properties": {}, "sites": [{"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.0, 0.0, 0.0], "xyz": [0.0, 0.0, 0.0], "properties": {}, "label": "Cd2+"}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.0, 0.0, 0.5], "xyz": [0.0, 0.0, 6.543384], "properties": {}, "label": "Cd2+"}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.0, 0.5, 0.0], "xyz": [0.0, 6.543384, 0.0], "properties": {}, "label": "Cd2+"}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.0, 0.5, 0.5], "xyz": [0.0, 6.543384, 6.543384], "properties": {}, "label": "Cd2+"}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.5, 0.0, 0.0], "xyz": [6.543384, 0.0, 0.0], "properties": {}, "label": "Cd2+"}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.5, 0.0, 0.5], "xyz": [6.543384, 0.0, 6.543384], "properties": {}, "label": "Cd2+"}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.5, 0.5, 0.0], "xyz": [6.543384, 6.543384, 0.0], "properties": {}, "label": "Cd2+"}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.5, 0.5, 0.5], "xyz": [6.543384, 6.543384, 6.543384], "properties": {}, "label": "Cd2+"}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.0, 0.25, 0.25], "xyz": [0.0, 3.271692, 3.271692], "properties": {}, "label": "Cd2+"}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.0, 0.25, 0.75], "xyz": [0.0, 3.271692, 9.815076], "properties": {}, "label": "Cd2+"}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.0, 0.75, 0.25], "xyz": [0.0, 9.815076, 3.271692], "properties": {}, "label": "Cd2+"}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.0, 0.75, 0.75], "xyz": [0.0, 9.815076, 9.815076], "properties": {}, "label": "Cd2+"}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.5, 0.25, 0.25], "xyz": [6.543384, 3.271692, 3.271692], "properties": {}, "label": "Cd2+"}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.5, 0.25, 0.75], "xyz": [6.543384, 3.271692, 9.815076], "properties": {}, "label": "Cd2+"}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.5, 0.75, 0.25], "xyz": [6.543384, 9.815076, 3.271692], "properties": {}, "label": "Cd2+"}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.5, 0.75, 0.75], "xyz": [6.543384, 9.815076, 9.815076], "properties": {}, "label": "Cd2+"}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.25, 0.0, 0.25], "xyz": [3.271692, 0.0, 3.271692], "properties": {}, "label": "Cd2+"}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.25, 0.0, 0.75], "xyz": [3.271692, 0.0, 9.815076], "properties": {}, "label": "Cd2+"}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.25, 0.5, 0.25], "xyz": [3.271692, 6.543384, 3.271692], "properties": {}, "label": "Cd2+"}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.25, 0.5, 0.75], "xyz": [3.271692, 6.543384, 9.815076], "properties": {}, "label": "Cd2+"}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.75, 0.0, 0.25], "xyz": [9.815076, 0.0, 3.271692], "properties": {}, "label": "Cd2+"}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.75, 0.0, 0.75], "xyz": [9.815076, 0.0, 9.815076], "properties": {}, "label": "Cd2+"}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.75, 0.5, 0.25], "xyz": [9.815076, 6.543384, 3.271692], "properties": {}, "label": "Cd2+"}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.75, 0.5, 0.75], "xyz": [9.815076, 6.543384, 9.815076], "properties": {}, "label": "Cd2+"}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.25, 0.25, 0.0], "xyz": [3.271692, 3.271692, 0.0], "properties": {}, "label": "Cd2+"}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.25, 0.25, 0.5], "xyz": [3.271692, 3.271692, 6.543384], "properties": {}, "label": "Cd2+"}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.25, 0.75, 0.0], "xyz": [3.271692, 9.815076, 0.0], "properties": {}, "label": "Cd2+"}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.25, 0.75, 0.5], "xyz": [3.271692, 9.815076, 6.543384], "properties": {}, "label": "Cd2+"}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.75, 0.25, 0.0], "xyz": [9.815076, 3.271692, 0.0], "properties": {}, "label": "Cd2+"}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.75, 0.25, 0.5], "xyz": [9.815076, 3.271692, 6.543384], "properties": {}, "label": "Cd2+"}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.75, 0.75, 0.0], "xyz": [9.815076, 9.815076, 0.0], "properties": {}, "label": "Cd2+"}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.75, 0.75, 0.5], "xyz": [9.815076, 9.815076, 6.543384], "properties": {}, "label": "Cd2+"}, {"species": [{"element": "Te", "oxidation_state": -2.0, "spin": null, "occu": 1}], "abc": [0.125, 0.125, 0.8749999999999999], "xyz": [1.635846, 1.635846, 11.450921999999998], "properties": {}, "label": "Te2-"}, {"species": [{"element": "Te", "oxidation_state": -2.0, "spin": null, "occu": 1}], "abc": [0.125, 0.625, 0.375], "xyz": [1.635846, 8.17923, 4.907538], "properties": {}, "label": "Te2-"}, {"species": [{"element": "Te", "oxidation_state": -2.0, "spin": null, "occu": 1}], "abc": [0.125, 0.625, 0.8749999999999999], "xyz": [1.635846, 8.17923, 11.450921999999998], "properties": {}, "label": "Te2-"}, {"species": [{"element": "Te", "oxidation_state": -2.0, "spin": null, "occu": 1}], "abc": [0.625, 0.125, 0.375], "xyz": [8.17923, 1.635846, 4.907538], "properties": {}, "label": "Te2-"}, {"species": [{"element": "Te", "oxidation_state": -2.0, "spin": null, "occu": 1}], "abc": [0.625, 0.125, 0.8749999999999999], "xyz": [8.17923, 1.635846, 11.450921999999998], "properties": {}, "label": "Te2-"}, {"species": [{"element": "Te", "oxidation_state": -2.0, "spin": null, "occu": 1}], "abc": [0.625, 0.625, 0.375], "xyz": [8.17923, 8.17923, 4.907538], "properties": {}, "label": "Te2-"}, {"species": [{"element": "Te", "oxidation_state": -2.0, "spin": null, "occu": 1}], "abc": [0.625, 0.625, 0.8749999999999999], "xyz": [8.17923, 8.17923, 11.450921999999998], "properties": {}, "label": "Te2-"}, {"species": [{"element": "Te", "oxidation_state": -2.0, "spin": null, "occu": 1}], "abc": [0.125, 0.375, 0.125], "xyz": [1.635846, 4.907538, 1.635846], "properties": {}, "label": "Te2-"}, {"species": [{"element": "Te", "oxidation_state": -2.0, "spin": null, "occu": 1}], "abc": [0.125, 0.375, 0.625], "xyz": [1.635846, 4.907538, 8.17923], "properties": {}, "label": "Te2-"}, {"species": [{"element": "Te", "oxidation_state": -2.0, "spin": null, "occu": 1}], "abc": [0.125, 0.8749999999999999, 0.125], "xyz": [1.635846, 11.450921999999998, 1.635846], "properties": {}, "label": "Te2-"}, {"species": [{"element": "Te", "oxidation_state": -2.0, "spin": null, "occu": 1}], "abc": [0.125, 0.8749999999999999, 0.625], "xyz": [1.635846, 11.450921999999998, 8.17923], "properties": {}, "label": "Te2-"}, {"species": [{"element": "Te", "oxidation_state": -2.0, "spin": null, "occu": 1}], "abc": [0.625, 0.375, 0.125], "xyz": [8.17923, 4.907538, 1.635846], "properties": {}, "label": "Te2-"}, {"species": [{"element": "Te", "oxidation_state": -2.0, "spin": null, "occu": 1}], "abc": [0.625, 0.375, 0.625], "xyz": [8.17923, 4.907538, 8.17923], "properties": {}, "label": "Te2-"}, {"species": [{"element": "Te", "oxidation_state": -2.0, "spin": null, "occu": 1}], "abc": [0.625, 0.8749999999999999, 0.125], "xyz": [8.17923, 11.450921999999998, 1.635846], "properties": {}, "label": "Te2-"}, {"species": [{"element": "Te", "oxidation_state": -2.0, "spin": null, "occu": 1}], "abc": [0.625, 0.8749999999999999, 0.625], "xyz": [8.17923, 11.450921999999998, 8.17923], "properties": {}, "label": "Te2-"}, {"species": [{"element": "Te", "oxidation_state": -2.0, "spin": null, "occu": 1}], "abc": [0.375, 0.125, 0.125], "xyz": [4.907538, 1.635846, 1.635846], "properties": {}, "label": "Te2-"}, {"species": [{"element": "Te", "oxidation_state": -2.0, "spin": null, "occu": 1}], "abc": [0.375, 0.125, 0.625], "xyz": [4.907538, 1.635846, 8.17923], "properties": {}, "label": "Te2-"}, {"species": [{"element": "Te", "oxidation_state": -2.0, "spin": null, "occu": 1}], "abc": [0.375, 0.625, 0.125], "xyz": [4.907538, 8.17923, 1.635846], "properties": {}, "label": "Te2-"}, {"species": [{"element": "Te", "oxidation_state": -2.0, "spin": null, "occu": 1}], "abc": [0.375, 0.625, 0.625], "xyz": [4.907538, 8.17923, 8.17923], "properties": {}, "label": "Te2-"}, {"species": [{"element": "Te", "oxidation_state": -2.0, "spin": null, "occu": 1}], "abc": [0.8749999999999999, 0.125, 0.125], "xyz": [11.450921999999998, 1.635846, 1.635846], "properties": {}, "label": "Te2-"}, {"species": [{"element": "Te", "oxidation_state": -2.0, "spin": null, "occu": 1}], "abc": [0.8749999999999999, 0.125, 0.625], "xyz": [11.450921999999998, 1.635846, 8.17923], "properties": {}, "label": "Te2-"}, {"species": [{"element": "Te", "oxidation_state": -2.0, "spin": null, "occu": 1}], "abc": [0.8749999999999999, 0.625, 0.125], "xyz": [11.450921999999998, 8.17923, 1.635846], "properties": {}, "label": "Te2-"}, {"species": [{"element": "Te", "oxidation_state": -2.0, "spin": null, "occu": 1}], "abc": [0.8749999999999999, 0.625, 0.625], "xyz": [11.450921999999998, 8.17923, 8.17923], "properties": {}, "label": "Te2-"}, {"species": [{"element": "Te", "oxidation_state": -2.0, "spin": null, "occu": 1}], "abc": [0.375, 0.375, 0.375], "xyz": [4.907538, 4.907538, 4.907538], "properties": {}, "label": "Te2-"}, {"species": [{"element": "Te", "oxidation_state": -2.0, "spin": null, "occu": 1}], "abc": [0.375, 0.375, 0.8749999999999999], "xyz": [4.907538, 4.907538, 11.450921999999998], "properties": {}, "label": "Te2-"}, {"species": [{"element": "Te", "oxidation_state": -2.0, "spin": null, "occu": 1}], "abc": [0.375, 0.8749999999999999, 0.375], "xyz": [4.907538, 11.450921999999998, 4.907538], "properties": {}, "label": "Te2-"}, {"species": [{"element": "Te", "oxidation_state": -2.0, "spin": null, "occu": 1}], "abc": [0.375, 0.8749999999999999, 0.8749999999999999], "xyz": [4.907538, 11.450921999999998, 11.450921999999998], "properties": {}, "label": "Te2-"}, {"species": [{"element": "Te", "oxidation_state": -2.0, "spin": null, "occu": 1}], "abc": [0.8749999999999999, 0.375, 0.375], "xyz": [11.450921999999998, 4.907538, 4.907538], "properties": {}, "label": "Te2-"}, {"species": [{"element": "Te", "oxidation_state": -2.0, "spin": null, "occu": 1}], "abc": [0.8749999999999999, 0.375, 0.8749999999999999], "xyz": [11.450921999999998, 4.907538, 11.450921999999998], "properties": {}, "label": "Te2-"}, {"species": [{"element": "Te", "oxidation_state": -2.0, "spin": null, "occu": 1}], "abc": [0.8749999999999999, 0.8749999999999999, 0.375], "xyz": [11.450921999999998, 11.450921999999998, 4.907538], "properties": {}, "label": "Te2-"}, {"species": [{"element": "Te", "oxidation_state": -2.0, "spin": null, "occu": 1}], "abc": [0.8749999999999999, 0.8749999999999999, 0.8749999999999999], "xyz": [11.450921999999998, 11.450921999999998, 11.450921999999998], "properties": {}, "label": "Te2-"}], "@version": null}, "distortions": {"Bond_Distortion_-30.0%": {"@module": "pymatgen.core.structure", "@class": "Structure", "charge": 2.0, "lattice": {"matrix": [[13.086768, 0.0, 0.0], [0.0, 13.086768, 0.0], [0.0, 0.0, 13.086768]], "pbc": [true, true, true], "a": 13.086768, "b": 13.086768, "c": 13.086768, "alpha": 90.0, "beta": 90.0, "gamma": 90.0, "volume": 2241.2856479961474}, "properties": {}, "sites": [{"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.01568711948403533, 0.016849917236106522, 0.0013659606570437218], "xyz": [0.20529369327585004, 0.22051095768812726, 0.017876010215858752], "properties": {}, "label": "Cd2+"}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.03749902367755584, 0.03749902367755584, 0.4625009763224441], "xyz": [0.49074102309468004, 0.49074102309468004, 6.052642976905319], "properties": {}, "label": "Cd2+"}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.027150011566868578, 0.4957262246093266, 0.010022476367907264], "xyz": [0.35530590257292555, 6.4874540929781475, 0.13116182301228502], "properties": {}, "label": "Cd2+"}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [-0.0014495703722098626, 0.498073152435215, 0.47811921679604913], "xyz": [-0.01897019116078412, 6.518167792948294, 6.257035266551598], "properties": {}, "label": "Cd2+"}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.4722094233480639, 0.020050629436408757, -0.018269223047381645], "xyz": [6.179695170769895, 0.26239793568825215, -0.2390850835613366], "properties": {}, "label": "Cd2+"}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.4885585281400621, -0.005387872873882472, 0.481767300464419], "xyz": [6.393652112190464, -0.07050984231399317, 6.304776891164144], "properties": {}, "label": "Cd2+"}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.5056282073490231, 0.5186277985499153, -0.02697461158586072], "xyz": [6.617039043832561, 6.787161677973477, -0.3530104837142713], "properties": {}, "label": "Cd2+"}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.4801688502186689, 0.5257243960661042, 0.5248055377787585], "xyz": [6.283858343638469, 6.880033203257218, 6.868008318025847], "properties": {}, "label": "Cd2+"}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.03749902367755584, 0.21250097632244416, 0.2874990236775558], "xyz": [0.49074102309468004, 2.78095097690532, 3.7624330230946796], "properties": {}, "label": "Cd2+"}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [-0.034831636895078275, 0.27289443167148103, 0.7387460475061252], "xyz": [-0.4558335511061297, 3.5713061157765242, 9.667798134629638], "properties": {}, "label": "Cd2+"}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [-0.010574547252808927, 0.7665553322767635, 0.22497043323812005], "xyz": [-0.13838664660254776, 10.031731792668916, 2.9441358666467656], "properties": {}, "label": "Cd2+"}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [-0.007569072998040621, 0.7073232895687733, 0.7540302118175998], "xyz": [-0.09905470230042206, 9.256575791583357, 9.867818447047785], "properties": {}, "label": "Cd2+"}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.49490896026223985, 0.23554231557600105, 0.267954409055762], "xyz": [6.476758744073152, 3.082487638125912, 3.506657185889856], "properties": {}, "label": "Cd2+"}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.500581636698387, 0.282053638247884, 0.7335402909392014], "xyz": [6.5509957445320754, 3.691170527305984, 9.59967160617383], "properties": {}, "label": "Cd2+"}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.5164511376504562, 0.7542450219488174, 0.23228237163420845], "xyz": [6.758676221767586, 9.87062961739908, 3.0398255080666665], "properties": {}, "label": "Cd2+"}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.5070828585054892, 0.7526462759552357, 0.7894765820190052], "xyz": [6.636075726038163, 9.849707199490148, 10.331696870315692], "properties": {}, "label": "Cd2+"}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.2549584993998797, 0.018167197194830374, 0.25919422359326616], "xyz": [3.3365827312743646, 0.2377498948989959, 3.3920146711052004], "properties": {}, "label": "Cd2+"}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.23144390076130325, -0.013969158032703688, 0.7710898637591608], "xyz": [3.028852634278199, -0.18281113032932955, 10.091074154167744], "properties": {}, "label": "Cd2+"}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.23763691303939355, 0.5053687944240745, 0.23816530117277496], "xyz": [3.109899149182718, 6.613644167067555, 3.1168140420982335], "properties": {}, "label": "Cd2+"}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.27183740458126954, 0.4922892243165773, 0.7451784226567301], "xyz": [3.5574730474772114, 6.442474867531005, 9.751977135914569], "properties": {}, "label": "Cd2+"}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.7622947472228112, 0.017354969716271013, 0.22602133900664856], "xyz": [9.975974504523574, 0.22712046232386454, 2.95788882662936], "properties": {}, "label": "Cd2+"}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.7421013906143455, -0.03693942541801388, 0.7601101761807642], "xyz": [9.711708731447317, -0.48341769049885064, 9.947385530116787], "properties": {}, "label": "Cd2+"}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.7643010257290317, 0.5082559723869255, 0.23751446757719594], "xyz": [10.002230205877868, 6.6514279952421, 3.1082967338262852], "properties": {}, "label": "Cd2+"}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.7772298637541588, 0.506173787789148, 0.7418884425731875], "xyz": [10.171426909622285, 6.6241789284778125, 9.708921929836627], "properties": {}, "label": "Cd2+"}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.21129195243108964, 0.23918180312926857, -0.004713779360792357], "xyz": [2.765128761732706, 3.1301167673744117, -0.061688136897877874], "properties": {}, "label": "Cd2+"}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.2597626037064264, 0.2654750359385809, 0.4861821253759255], "xyz": [3.3994529297819422, 3.47421020511987, 6.362552680541649], "properties": {}, "label": "Cd2+"}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.2856911389734074, 0.7667607419061732, -0.0175620557263897], "xyz": [3.738773655400741, 10.034419940833965, -0.22983054889433344], "properties": {}, "label": "Cd2+"}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.25533084923273663, 0.7324121579095495, 0.47291637752448296], "xyz": [3.341455587151802, 9.58490799094164, 6.188946916063323], "properties": {}, "label": "Cd2+"}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.787787428123557, 0.22109627471208937, -0.003940356567826238], "xyz": [10.309591305169665, 2.8934356528213803, -0.05156653224041824], "properties": {}, "label": "Cd2+"}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.7756092046801939, 0.26967412576303745, 0.49053849129305294], "xyz": [10.150217720314211, 3.529162719463694, 6.419563430622204], "properties": {}, "label": "Cd2+"}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.767460332714889, 0.7433936502088357, 0.03425825864459876], "xyz": [10.043575323442562, 9.728620232956185, 0.44832988296585846], "properties": {}, "label": "Cd2+"}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.7659806984052299, 0.759274182362689, 0.5084426299687161], "xyz": [10.024211692507214, 9.936445072970201, 6.653870739710435], "properties": {}, "label": "Cd2+"}, {"species": [{"element": "Te", "oxidation_state": -2.0, "spin": null, "occu": 1}], "abc": [0.12010924261780784, 0.12732504384537732, 0.8687354606915619], "xyz": [1.5718417927949637, 1.6662733093942808, 11.368939427443589], "properties": {}, "label": "Te2-"}, {"species": [{"element": "Te", "oxidation_state": -2.0, "spin": null, "occu": 1}], "abc": [0.11875684857742955, 0.6507937162676332, 0.3762100856753475], "xyz": [1.5541433257439505, 8.516786380652341, 4.923374110493397], "properties": {}, "label": "Te2-"}, {"species": [{"element": "Te", "oxidation_state": -2.0, "spin": null, "occu": 1}], "abc": [0.13123594298367203, 0.6328551962041555, 0.9202598135720425], "xyz": [1.7174543390885435, 8.282029130318264, 12.04322667994057], "properties": {}, "label": "Te2-"}, {"species": [{"element": "Te", "oxidation_state": -2.0, "spin": null, "occu": 1}], "abc": [0.6128884079346265, 0.11829516580406792, 0.3473039496781265], "xyz": [8.020728404529816, 1.5481013903993701, 4.545086214921316], "properties": {}, "label": "Te2-"}, {"species": [{"element": "Te", "oxidation_state": -2.0, "spin": null, "occu": 1}], "abc": [0.6315797807618615, 0.12259391843436342, 0.8700339946167042], "xyz": [8.265338064321345, 1.6043581687614372, 11.385933039662056], "properties": {}, "label": "Te2-"}, {"species": [{"element": "Te", "oxidation_state": -2.0, "spin": null, "occu": 1}], "abc": [0.6283847628206453, 0.6104322706926303, 0.3462095332815449], "xyz": [8.22352560576881, 7.988585506267651, 4.530763841443857], "properties": {}, "label": "Te2-"}, {"species": [{"element": "Te", "oxidation_state": -2.0, "spin": null, "occu": 1}], "abc": [0.6231233285696897, 0.6431785472589133, 0.8803636878306192], "xyz": [8.1546704363793, 8.417128430554435, 11.521115338263737], "properties": {}, "label": "Te2-"}, {"species": [{"element": "Te", "oxidation_state": -2.0, "spin": null, "occu": 1}], "abc": [0.11326489374951178, 0.3943581611601848, 0.14245104779137183], "xyz": [1.4822713870445106, 5.160873764009949, 1.8642238138025955], "properties": {}, "label": "Te2-"}, {"species": [{"element": "Te", "oxidation_state": -2.0, "spin": null, "occu": 1}], "abc": [0.11393398945863639, 0.36818442425335673, 0.6518017271048436], "xyz": [1.49102768735962, 4.818344141417253, 8.5299779846204], "properties": {}, "label": "Te2-"}, {"species": [{"element": "Te", "oxidation_state": -2.0, "spin": null, "occu": 1}], "abc": [0.12714363634965842, 0.8667078405828446, 0.10278719284878346], "xyz": [1.6638992715843466, 11.342404433488671, 1.345152146183288], "properties": {}, "label": "Te2-"}, {"species": [{"element": "Te", "oxidation_state": -2.0, "spin": null, "occu": 1}], "abc": [0.12737278928913331, 0.8919108640973763, 0.6439967743041993], "xyz": [1.6668981429397725, 11.672230555121892, 8.427836378067418], "properties": {}, "label": "Te2-"}, {"species": [{"element": "Te", "oxidation_state": -2.0, "spin": null, "occu": 1}], "abc": [0.6141821228674019, 0.3715100179204548, 0.11272616402540049], "xyz": [8.037658951713183, 4.861865414200834, 1.4752211561303623], "properties": {}, "label": "Te2-"}, {"species": [{"element": "Te", "oxidation_state": -2.0, "spin": null, "occu": 1}], "abc": [0.6173411386799441, 0.387069786365467, 0.5681915310744818], "xyz": [8.079000258760255, 5.06549249397443, 7.435790746736534], "properties": {}, "label": "Te2-"}, {"species": [{"element": "Te", "oxidation_state": -2.0, "spin": null, "occu": 1}], "abc": [0.608224540597382, 0.8839467110079551, 0.11295790115121591], "xyz": [7.959693454704519, 11.568005531324154, 1.4782538461328956], "properties": {}, "label": "Te2-"}, {"species": [{"element": "Te", "oxidation_state": -2.0, "spin": null, "occu": 1}], "abc": [0.6742054626232512, 0.9108186394979915, 0.6062918720004151], "xyz": [8.82317047368316, 11.91967222518585, 7.934401069155128], "properties": {}, "label": "Te2-"}, {"species": [{"element": "Te", "oxidation_state": -2.0, "spin": null, "occu": 1}], "abc": [0.36063832552584496, 0.12770918212328716, 0.16147519869745774], "xyz": [4.71959009806521, 1.6713004379172065, 2.1131884631075315], "properties": {}, "label": "Te2-"}, {"species": [{"element": "Te", "oxidation_state": -2.0, "spin": null, "occu": 1}], "abc": [0.37525204242498533, 0.11394353706788272, 0.5933896303873651], "xyz": [4.910836420741941, 1.4911526347067814, 7.765552426485197], "properties": {}, "label": "Te2-"}, {"species": [{"element": "Te", "oxidation_state": -2.0, "spin": null, "occu": 1}], "abc": [0.4060941606888188, 0.6079088993238952, 0.11140969169113184], "xyz": [5.314460067089292, 7.9555627305871734, 1.4579927881133699], "properties": {}, "label": "Te2-"}, {"species": [{"element": "Te", "oxidation_state": -2.0, "spin": null, "occu": 1}], "abc": [0.369165924514035, 0.6150467855183058, 0.6518612689124705], "xyz": [4.831188807620689, 8.048974591223828, 8.530757194443114], "properties": {}, "label": "Te2-"}, {"species": [{"element": "Te", "oxidation_state": -2.0, "spin": null, "occu": 1}], "abc": [0.8803128561907224, 0.14262435804379522, 0.13958828284680208], "xyz": [11.520450116385348, 1.8664918848680818, 1.8267594731344783], "properties": {}, "label": "Te2-"}, {"species": [{"element": "Te", "oxidation_state": -2.0, "spin": null, "occu": 1}], "abc": [0.8947428748509965, 0.13988220655203906, 0.5971153956045203], "xyz": [11.709292422828025, 1.830605984474615, 7.814310651504576], "properties": {}, "label": "Te2-"}, {"species": [{"element": "Te", "oxidation_state": -2.0, "spin": null, "occu": 1}], "abc": [0.9050213842460662, 0.6276467695727617, 0.09480897005327915], "xyz": [11.843804890667123, 8.21386765934819, 1.240742995406212], "properties": {}, "label": "Te2-"}, {"species": [{"element": "Te", "oxidation_state": -2.0, "spin": null, "occu": 1}], "abc": [0.895865344260897, 0.6243931913490971, 0.6052705997044724], "xyz": [11.72398191958249, 8.17128883596524, 7.921035915553298], "properties": {}, "label": "Te2-"}, {"species": [{"element": "Te", "oxidation_state": -2.0, "spin": null, "occu": 1}], "abc": [0.39973014540869395, 0.37602805020175234, 0.3786909690625228], "xyz": [5.231175675569842, 4.920991854482685, 4.9558408558164135], "properties": {}, "label": "Te2-"}, {"species": [{"element": "Te", "oxidation_state": -2.0, "spin": null, "occu": 1}], "abc": [0.36627096956773875, 0.37647047428422986, 0.8799247251675606], "xyz": [4.793303203868057, 4.926781755807682, 11.515370735731626], "properties": {}, "label": "Te2-"}, {"species": [{"element": "Te", "oxidation_state": -2.0, "spin": null, "occu": 1}], "abc": [0.3694061068288607, 0.8514102855379397, 0.36722890002230013], "xyz": [4.834332017852516, 11.142208879648772, 4.805839417487037], "properties": {}, "label": "Te2-"}, {"species": [{"element": "Te", "oxidation_state": -2.0, "spin": null, "occu": 1}], "abc": [0.39036318842662027, 0.8750040440301268, 0.8693633479218933], "xyz": [5.108592482679464, 11.450974923284054, 11.3771564419571], "properties": {}, "label": "Te2-"}, {"species": [{"element": "Te", "oxidation_state": -2.0, "spin": null, "occu": 1}], "abc": [0.8840986630527821, 0.3765797318809899, 0.3701975112891385], "xyz": [11.56999409248193, 4.928211584628718, 4.844688944418337], "properties": {}, "label": "Te2-"}, {"species": [{"element": "Te", "oxidation_state": -2.0, "spin": null, "occu": 1}], "abc": [0.8878406141383686, 0.39518376205924294, 0.8849706124942863], "xyz": [11.61896413820635, 5.1716782114365145, 11.581405092530625], "properties": {}, "label": "Te2-"}, {"species": [{"element": "Te", "oxidation_state": -2.0, "spin": null, "occu": 1}], "abc": [0.8794827130771701, 0.8854032160195859, 0.36748368646509866], "xyz": [11.509586226051491, 11.587066474502205, 4.809173748553486], "properties": {}, "label": "Te2-"}, {"species": [{"element": "Te", "oxidation_state": -2.0, "spin": null, "occu": 1}], "abc": [0.8917242540183349, 0.8543409397301263, 0.878435252439452], "xyz": [11.669788432311016, 11.180561671150144, 11.495878351696541], "properties": {}, "label": "Te2-"}], "@version": null}}}}}}} \ No newline at end of file +{"v_Cd_Td_Te2.83": {"defect_type": "vacancy", "defect_site": {"species": [{"element": "Cd", "occu": 1}], "abc": [0.0, 0.0, 0.0], "lattice": {"@module": "pymatgen.core.lattice", "@class": "Lattice", "matrix": [[13.086768, 0.0, 0.0], [0.0, 13.086768, 0.0], [0.0, 0.0, 13.086768]], "pbc": [true, true, true]}, "@module": "pymatgen.core.sites", "@class": "PeriodicSite", "properties": {}, "label": "Cd", "@version": null}, "defect_supercell_site": {"species": [{"element": "Cd", "occu": 1}], "abc": [0.0, 0.0, 0.0], "lattice": {"@module": "pymatgen.core.lattice", "@class": "Lattice", "matrix": [[13.086768, 0.0, 0.0], [0.0, 13.086768, 0.0], [0.0, 0.0, 13.086768]], "pbc": [true, true, true]}, "@module": "pymatgen.core.sites", "@class": "PeriodicSite", "properties": {}, "label": "Cd", "@version": null}, "charges": {"0": {"structures": {"Unperturbed": {"@module": "pymatgen.core.structure", "@class": "Structure", "charge": 0, "lattice": {"matrix": [[13.086768, 0.0, 0.0], [0.0, 13.086768, 0.0], [0.0, 0.0, 13.086768]], "pbc": [true, true, true], "a": 13.086768, "b": 13.086768, "c": 13.086768, "alpha": 90.0, "beta": 90.0, "gamma": 90.0, "volume": 2241.2856479961474}, "properties": {}, "sites": [{"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.0, 0.0, 0.5], "properties": {}, "label": "Cd", "xyz": [0.0, 0.0, 6.543384]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.0, 0.5, 0.0], "properties": {}, "label": "Cd", "xyz": [0.0, 6.543384, 0.0]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.0, 0.5, 0.5], "properties": {}, "label": "Cd", "xyz": [0.0, 6.543384, 6.543384]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.5, 0.0, 0.0], "properties": {}, "label": "Cd", "xyz": [6.543384, 0.0, 0.0]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.5, 0.0, 0.5], "properties": {}, "label": "Cd", "xyz": [6.543384, 0.0, 6.543384]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.5, 0.5, 0.0], "properties": {}, "label": "Cd", "xyz": [6.543384, 6.543384, 0.0]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.5, 0.5, 0.5], "properties": {}, "label": "Cd", "xyz": [6.543384, 6.543384, 6.543384]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.0, 0.25, 0.25], "properties": {}, "label": "Cd", "xyz": [0.0, 3.271692, 3.271692]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.0, 0.25, 0.75], "properties": {}, "label": "Cd", "xyz": [0.0, 3.271692, 9.815076]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.0, 0.75, 0.25], "properties": {}, "label": "Cd", "xyz": [0.0, 9.815076, 3.271692]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.0, 0.75, 0.75], "properties": {}, "label": "Cd", "xyz": [0.0, 9.815076, 9.815076]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.5, 0.25, 0.25], "properties": {}, "label": "Cd", "xyz": [6.543384, 3.271692, 3.271692]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.5, 0.25, 0.75], "properties": {}, "label": "Cd", "xyz": [6.543384, 3.271692, 9.815076]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.5, 0.75, 0.25], "properties": {}, "label": "Cd", "xyz": [6.543384, 9.815076, 3.271692]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.5, 0.75, 0.75], "properties": {}, "label": "Cd", "xyz": [6.543384, 9.815076, 9.815076]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.25, 0.0, 0.25], "properties": {}, "label": "Cd", "xyz": [3.271692, 0.0, 3.271692]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.25, 0.0, 0.75], "properties": {}, "label": "Cd", "xyz": [3.271692, 0.0, 9.815076]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.25, 0.5, 0.25], "properties": {}, "label": "Cd", "xyz": [3.271692, 6.543384, 3.271692]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.25, 0.5, 0.75], "properties": {}, "label": "Cd", "xyz": [3.271692, 6.543384, 9.815076]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.75, 0.0, 0.25], "properties": {}, "label": "Cd", "xyz": [9.815076, 0.0, 3.271692]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.75, 0.0, 0.75], "properties": {}, "label": "Cd", "xyz": [9.815076, 0.0, 9.815076]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.75, 0.5, 0.25], "properties": {}, "label": "Cd", "xyz": [9.815076, 6.543384, 3.271692]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.75, 0.5, 0.75], "properties": {}, "label": "Cd", "xyz": [9.815076, 6.543384, 9.815076]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.25, 0.25, 0.0], "properties": {}, "label": "Cd", "xyz": [3.271692, 3.271692, 0.0]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.25, 0.25, 0.5], "properties": {}, "label": "Cd", "xyz": [3.271692, 3.271692, 6.543384]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.25, 0.75, 0.0], "properties": {}, "label": "Cd", "xyz": [3.271692, 9.815076, 0.0]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.25, 0.75, 0.5], "properties": {}, "label": "Cd", "xyz": [3.271692, 9.815076, 6.543384]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.75, 0.25, 0.0], "properties": {}, "label": "Cd", "xyz": [9.815076, 3.271692, 0.0]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.75, 0.25, 0.5], "properties": {}, "label": "Cd", "xyz": [9.815076, 3.271692, 6.543384]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.75, 0.75, 0.0], "properties": {}, "label": "Cd", "xyz": [9.815076, 9.815076, 0.0]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.75, 0.75, 0.5], "properties": {}, "label": "Cd", "xyz": [9.815076, 9.815076, 6.543384]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.125, 0.125, 0.375], "properties": {}, "label": "Te", "xyz": [1.635846, 1.635846, 4.907538]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.125, 0.125, 0.8749999999999999], "properties": {}, "label": "Te", "xyz": [1.635846, 1.635846, 11.450921999999998]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.125, 0.625, 0.375], "properties": {}, "label": "Te", "xyz": [1.635846, 8.17923, 4.907538]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.125, 0.625, 0.8749999999999999], "properties": {}, "label": "Te", "xyz": [1.635846, 8.17923, 11.450921999999998]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.625, 0.125, 0.375], "properties": {}, "label": "Te", "xyz": [8.17923, 1.635846, 4.907538]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.625, 0.125, 0.8749999999999999], "properties": {}, "label": "Te", "xyz": [8.17923, 1.635846, 11.450921999999998]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.625, 0.625, 0.375], "properties": {}, "label": "Te", "xyz": [8.17923, 8.17923, 4.907538]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.625, 0.625, 0.8749999999999999], "properties": {}, "label": "Te", "xyz": [8.17923, 8.17923, 11.450921999999998]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.125, 0.375, 0.125], "properties": {}, "label": "Te", "xyz": [1.635846, 4.907538, 1.635846]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.125, 0.375, 0.625], "properties": {}, "label": "Te", "xyz": [1.635846, 4.907538, 8.17923]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.125, 0.8749999999999999, 0.125], "properties": {}, "label": "Te", "xyz": [1.635846, 11.450921999999998, 1.635846]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.125, 0.8749999999999999, 0.625], "properties": {}, "label": "Te", "xyz": [1.635846, 11.450921999999998, 8.17923]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.625, 0.375, 0.125], "properties": {}, "label": "Te", "xyz": [8.17923, 4.907538, 1.635846]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.625, 0.375, 0.625], "properties": {}, "label": "Te", "xyz": [8.17923, 4.907538, 8.17923]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.625, 0.8749999999999999, 0.125], "properties": {}, "label": "Te", "xyz": [8.17923, 11.450921999999998, 1.635846]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.625, 0.8749999999999999, 0.625], "properties": {}, "label": "Te", "xyz": [8.17923, 11.450921999999998, 8.17923]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.375, 0.125, 0.125], "properties": {}, "label": "Te", "xyz": [4.907538, 1.635846, 1.635846]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.375, 0.125, 0.625], "properties": {}, "label": "Te", "xyz": [4.907538, 1.635846, 8.17923]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.375, 0.625, 0.125], "properties": {}, "label": "Te", "xyz": [4.907538, 8.17923, 1.635846]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.375, 0.625, 0.625], "properties": {}, "label": "Te", "xyz": [4.907538, 8.17923, 8.17923]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.8749999999999999, 0.125, 0.125], "properties": {}, "label": "Te", "xyz": [11.450921999999998, 1.635846, 1.635846]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.8749999999999999, 0.125, 0.625], "properties": {}, "label": "Te", "xyz": [11.450921999999998, 1.635846, 8.17923]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.8749999999999999, 0.625, 0.125], "properties": {}, "label": "Te", "xyz": [11.450921999999998, 8.17923, 1.635846]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.8749999999999999, 0.625, 0.625], "properties": {}, "label": "Te", "xyz": [11.450921999999998, 8.17923, 8.17923]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.375, 0.375, 0.375], "properties": {}, "label": "Te", "xyz": [4.907538, 4.907538, 4.907538]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.375, 0.375, 0.8749999999999999], "properties": {}, "label": "Te", "xyz": [4.907538, 4.907538, 11.450921999999998]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.375, 0.8749999999999999, 0.375], "properties": {}, "label": "Te", "xyz": [4.907538, 11.450921999999998, 4.907538]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.375, 0.8749999999999999, 0.8749999999999999], "properties": {}, "label": "Te", "xyz": [4.907538, 11.450921999999998, 11.450921999999998]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.8749999999999999, 0.375, 0.375], "properties": {}, "label": "Te", "xyz": [11.450921999999998, 4.907538, 4.907538]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.8749999999999999, 0.375, 0.8749999999999999], "properties": {}, "label": "Te", "xyz": [11.450921999999998, 4.907538, 11.450921999999998]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.8749999999999999, 0.8749999999999999, 0.375], "properties": {}, "label": "Te", "xyz": [11.450921999999998, 11.450921999999998, 4.907538]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.8749999999999999, 0.8749999999999999, 0.8749999999999999], "properties": {}, "label": "Te", "xyz": [11.450921999999998, 11.450921999999998, 11.450921999999998]}], "@version": null}, "distortions": {"Bond_Distortion_-30.0%": {"@module": "pymatgen.core.structure", "@class": "Structure", "charge": -2, "lattice": {"matrix": [[13.086768, 0.0, 0.0], [0.0, 13.086768, 0.0], [0.0, 0.0, 13.086768]], "pbc": [true, true, true], "a": 13.086768, "b": 13.086768, "c": 13.086768, "alpha": 90.0, "beta": 90.0, "gamma": 90.0, "volume": 2241.2856479961474}, "properties": {}, "sites": [{"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.01568711948403533, 0.016849917236106522, 0.5013659606570436], "properties": {}, "label": "Cd2+", "xyz": [0.20529369327585004, 0.22051095768812726, 6.561260010215857]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.027150011566868578, 0.4957262246093266, 0.010022476367907264], "properties": {}, "label": "Cd2+", "xyz": [0.35530590257292555, 6.4874540929781475, 0.13116182301228502]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [-0.0014495703722098626, 0.498073152435215, 0.47811921679604913], "properties": {}, "label": "Cd2+", "xyz": [-0.01897019116078412, 6.518167792948294, 6.257035266551598]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.4722094233480639, 0.020050629436408757, -0.018269223047381645], "properties": {}, "label": "Cd2+", "xyz": [6.179695170769895, 0.26239793568825215, -0.2390850835613366]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.4885585281400621, -0.005387872873882472, 0.481767300464419], "properties": {}, "label": "Cd2+", "xyz": [6.393652112190464, -0.07050984231399317, 6.304776891164144]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.5056282073490231, 0.5186277985499153, -0.02697461158586072], "properties": {}, "label": "Cd2+", "xyz": [6.617039043832561, 6.787161677973477, -0.3530104837142713]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.4801688502186689, 0.5257243960661042, 0.5248055377787585], "properties": {}, "label": "Cd2+", "xyz": [6.283858343638469, 6.880033203257218, 6.868008318025847]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [-0.034831636895078275, 0.27289443167148103, 0.23874604750612521], "properties": {}, "label": "Cd2+", "xyz": [-0.4558335511061297, 3.5713061157765242, 3.1244141346296392]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [-0.010574547252808927, 0.2665553322767636, 0.72497043323812], "properties": {}, "label": "Cd2+", "xyz": [-0.13838664660254776, 3.488347792668917, 9.487519866646766]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [-0.007569072998040621, 0.7073232895687733, 0.2540302118175998], "properties": {}, "label": "Cd2+", "xyz": [-0.09905470230042206, 9.256575791583357, 3.324434447047787]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [-0.005091039737760129, 0.735542315576001, 0.7679544090557621], "properties": {}, "label": "Cd2+", "xyz": [-0.06662525592684765, 9.625871638125911, 10.050041185889857]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.500581636698387, 0.282053638247884, 0.23354029093920137], "properties": {}, "label": "Cd2+", "xyz": [6.5509957445320754, 3.691170527305984, 3.0562876061738304]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.5164511376504562, 0.2542450219488173, 0.7322823716342084], "properties": {}, "label": "Cd2+", "xyz": [6.758676221767586, 3.32724561739908, 9.583209508066666]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.5070828585054892, 0.7526462759552357, 0.2894765820190052], "properties": {}, "label": "Cd2+", "xyz": [6.636075726038163, 9.849707199490148, 3.7883128703156927]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.5049584993998797, 0.7681671971948304, 0.759194223593266], "properties": {}, "label": "Cd2+", "xyz": [6.608274731274364, 10.052825894898996, 9.9353986711052]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.23144390076130325, -0.013969158032703688, 0.27108986375916067], "properties": {}, "label": "Cd2+", "xyz": [3.028852634278199, -0.18281113032932955, 3.5476901541677432]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.23763691303939355, 0.005368794424074448, 0.738165301172775], "properties": {}, "label": "Cd2+", "xyz": [3.109899149182718, 0.07026016706755592, 9.660198042098234]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.27183740458126954, 0.4922892243165773, 0.2451784226567301], "properties": {}, "label": "Cd2+", "xyz": [3.5574730474772114, 6.442474867531005, 3.2085931359145703]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.26229474722281126, 0.517354969716271, 0.7260213390066484], "properties": {}, "label": "Cd2+", "xyz": [3.432590504523575, 6.770504462323864, 9.501272826629359]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.7421013906143455, -0.03693942541801388, 0.2601101761807642], "properties": {}, "label": "Cd2+", "xyz": [9.711708731447317, -0.48341769049885064, 3.404001530116787]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.7643010257290317, 0.008255972386925511, 0.7375144675771959], "properties": {}, "label": "Cd2+", "xyz": [10.002230205877868, 0.10804399524210039, 9.651680733826284]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.7772298637541588, 0.506173787789148, 0.24188844257318742], "properties": {}, "label": "Cd2+", "xyz": [10.171426909622285, 6.6241789284778125, 3.165537929836627]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.7112919524310897, 0.48918180312926857, 0.7452862206392076], "properties": {}, "label": "Cd2+", "xyz": [9.308512761732706, 6.401808767374411, 9.753387863102121]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.2597626037064264, 0.2654750359385809, -0.013817874624074463], "properties": {}, "label": "Cd2+", "xyz": [3.3994529297819422, 3.47421020511987, -0.1808313194583497]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.2856911389734074, 0.26676074190617316, 0.4824379442736103], "properties": {}, "label": "Cd2+", "xyz": [3.738773655400741, 3.4910359408339655, 6.313553451105666]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.25533084923273663, 0.7324121579095495, -0.027083622475517065], "properties": {}, "label": "Cd2+", "xyz": [3.341455587151802, 9.58490799094164, -0.3544370839366775]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.28778742812355695, 0.7210962747120893, 0.49605964343217374], "properties": {}, "label": "Cd2+", "xyz": [3.766207305169665, 9.436819652821379, 6.4918174677595815]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.7756092046801939, 0.26967412576303745, -0.009461508706947064], "properties": {}, "label": "Cd2+", "xyz": [10.150217720314211, 3.529162719463694, -0.1238205693777962]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.767460332714889, 0.2433936502088358, 0.5342582586445987], "properties": {}, "label": "Cd2+", "xyz": [10.043575323442562, 3.1852362329561856, 6.991713882965858]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.7659806984052299, 0.759274182362689, 0.008442629968716076], "properties": {}, "label": "Cd2+", "xyz": [10.024211692507214, 9.936445072970201, 0.11048673971043455]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.7451092426178079, 0.7523250438453772, 0.49373546069156204], "properties": {}, "label": "Cd2+", "xyz": [9.751071792794963, 9.84550330939428, 6.461401427443592]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.11875684857742955, 0.1507937162676332, 0.3762100856753475], "properties": {}, "label": "Te2-", "xyz": [1.5541433257439505, 1.9734023806523413, 4.923374110493397]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.08750097632244412, 0.08750097632244412, 0.9124990236775559], "properties": {}, "label": "Te2-", "xyz": [1.1451049769053194, 1.1451049769053194, 11.94166302309468]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.13123594298367203, 0.6328551962041555, 0.42025981357204256], "properties": {}, "label": "Te2-", "xyz": [1.7174543390885435, 8.282029130318264, 5.499842679940572]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.11288840793462648, 0.618295165804068, 0.8473039496781265], "properties": {}, "label": "Te2-", "xyz": [1.4773444045298159, 8.09148539039937, 11.088470214921315]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.6315797807618615, 0.12259391843436342, 0.3700339946167043], "properties": {}, "label": "Te2-", "xyz": [8.265338064321345, 1.6043581687614372, 4.842549039662058]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.6283847628206453, 0.11043227069263029, 0.8462095332815449], "properties": {}, "label": "Te2-", "xyz": [8.22352560576881, 1.4452015062676518, 11.074147841443857]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.6231233285696897, 0.6431785472589133, 0.3803636878306193], "properties": {}, "label": "Te2-", "xyz": [8.1546704363793, 8.417128430554435, 4.977731338263738]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.6132648937495118, 0.644358161160185, 0.8924510477913717], "properties": {}, "label": "Te2-", "xyz": [8.02565538704451, 8.43256576400995, 11.679299813802594]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.11393398945863639, 0.36818442425335673, 0.1518017271048436], "properties": {}, "label": "Te2-", "xyz": [1.49102768735962, 4.818344141417253, 1.9865939846203997]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.12714363634965842, 0.36670784058284467, 0.6027871928487836], "properties": {}, "label": "Te2-", "xyz": [1.6638992715843466, 4.799020433488673, 7.888536146183289]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.08750097632244412, 0.9124990236775559, 0.08750097632244412], "properties": {}, "label": "Te2-", "xyz": [1.1451049769053194, 11.94166302309468, 1.1451049769053194]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.12737278928913331, 0.8919108640973763, 0.6439967743041993], "properties": {}, "label": "Te2-", "xyz": [1.6668981429397725, 11.672230555121892, 8.427836378067418]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.6141821228674019, 0.3715100179204548, 0.11272616402540049], "properties": {}, "label": "Te2-", "xyz": [8.037658951713183, 4.861865414200834, 1.4752211561303623]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.6173411386799441, 0.387069786365467, 0.5681915310744818], "properties": {}, "label": "Te2-", "xyz": [8.079000258760255, 5.06549249397443, 7.435790746736534]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.608224540597382, 0.8839467110079551, 0.11295790115121591], "properties": {}, "label": "Te2-", "xyz": [7.959693454704519, 11.568005531324154, 1.4782538461328956]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.6742054626232512, 0.9108186394979915, 0.6062918720004151], "properties": {}, "label": "Te2-", "xyz": [8.82317047368316, 11.91967222518585, 7.934401069155128]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.36063832552584496, 0.12770918212328716, 0.16147519869745774], "properties": {}, "label": "Te2-", "xyz": [4.71959009806521, 1.6713004379172065, 2.1131884631075315]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.37525204242498533, 0.11394353706788272, 0.5933896303873651], "properties": {}, "label": "Te2-", "xyz": [4.910836420741941, 1.4911526347067814, 7.765552426485197]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.4060941606888188, 0.6079088993238952, 0.11140969169113184], "properties": {}, "label": "Te2-", "xyz": [5.314460067089292, 7.9555627305871734, 1.4579927881133699]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.3803128561907225, 0.6426243580437953, 0.6395882828468022], "properties": {}, "label": "Te2-", "xyz": [4.977066116385349, 8.409875884868082, 8.37014347313448]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.8947428748509965, 0.13988220655203906, 0.0971153956045202], "properties": {}, "label": "Te2-", "xyz": [11.709292422828025, 1.830605984474615, 1.2709266515045756]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.9050213842460662, 0.12764676957276155, 0.5948089700532792], "properties": {}, "label": "Te2-", "xyz": [11.843804890667123, 1.6704836593481895, 7.784126995406211]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.895865344260897, 0.6243931913490971, 0.10527059970447229], "properties": {}, "label": "Te2-", "xyz": [11.72398191958249, 8.17128883596524, 1.3776519155532974]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.8997301454086939, 0.6260280502017525, 0.6286909690625229], "properties": {}, "label": "Te2-", "xyz": [11.774559675569842, 8.192683854482688, 8.227532855816413]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.39036318842662027, 0.3750040440301268, 0.36936334792189346], "properties": {}, "label": "Te2-", "xyz": [5.108592482679464, 4.907590923284054, 4.833772441957102]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.3840986630527822, 0.3765797318809899, 0.8701975112891386], "properties": {}, "label": "Te2-", "xyz": [5.026610092481932, 4.928211584628718, 11.388072944418337]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.3794827130771702, 0.8854032160195859, 0.36748368646509866], "properties": {}, "label": "Te2-", "xyz": [4.966202226051492, 11.587066474502205, 4.809173748553486]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.3917242540183349, 0.8543409397301263, 0.878435252439452], "properties": {}, "label": "Te2-", "xyz": [5.126404432311016, 11.180561671150144, 11.495878351696541]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.8817775378020919, 0.3813543713889261, 0.40317439575351394], "properties": {}, "label": "Te2-", "xyz": [11.539618064827206, 4.990696184152713, 5.276249780766422]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.8794108581844571, 0.3946822747640026, 0.87570067228848], "properties": {}, "label": "Te2-", "xyz": [11.508645877740891, 5.1651153635487566, 11.460091535683366]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.8832990018766742, 0.9089615994988091, 0.36272692412143825], "properties": {}, "label": "Te2-", "xyz": [11.5595291121916, 11.89536957354983, 4.746923103330866]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.8460654184237592, 0.8901079778386413, 0.8694308400251983], "properties": {}, "label": "Te2-", "xyz": [11.072261843734662, 11.648636600923439, 11.378039695454884]}], "@version": null}, "Dimer": {"@module": "pymatgen.core.structure", "@class": "Structure", "charge": -2, "lattice": {"matrix": [[13.086768, 0.0, 0.0], [0.0, 13.086768, 0.0], [0.0, 0.0, 13.086768]], "pbc": [true, true, true], "a": 13.086768, "b": 13.086768, "c": 13.086768, "alpha": 90.0, "beta": 90.0, "gamma": 90.0, "volume": 2241.2856479961474}, "properties": {}, "sites": [{"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.01568711948403533, 0.016849917236106522, 0.5013659606570436], "properties": {}, "label": "Cd2+", "xyz": [0.20529369327585004, 0.22051095768812726, 6.561260010215857]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.027150011566868578, 0.4957262246093266, 0.010022476367907264], "properties": {}, "label": "Cd2+", "xyz": [0.35530590257292555, 6.4874540929781475, 0.13116182301228502]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [-0.0014495703722098626, 0.498073152435215, 0.47811921679604913], "properties": {}, "label": "Cd2+", "xyz": [-0.01897019116078412, 6.518167792948294, 6.257035266551598]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.4722094233480639, 0.020050629436408757, -0.018269223047381645], "properties": {}, "label": "Cd2+", "xyz": [6.179695170769895, 0.26239793568825215, -0.2390850835613366]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.4885585281400621, -0.005387872873882472, 0.481767300464419], "properties": {}, "label": "Cd2+", "xyz": [6.393652112190464, -0.07050984231399317, 6.304776891164144]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.5056282073490231, 0.5186277985499153, -0.02697461158586072], "properties": {}, "label": "Cd2+", "xyz": [6.617039043832561, 6.787161677973477, -0.3530104837142713]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.4801688502186689, 0.5257243960661042, 0.5248055377787585], "properties": {}, "label": "Cd2+", "xyz": [6.283858343638469, 6.880033203257218, 6.868008318025847]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [-0.034831636895078275, 0.27289443167148103, 0.23874604750612521], "properties": {}, "label": "Cd2+", "xyz": [-0.4558335511061297, 3.5713061157765242, 3.1244141346296392]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [-0.010574547252808927, 0.2665553322767636, 0.72497043323812], "properties": {}, "label": "Cd2+", "xyz": [-0.13838664660254776, 3.488347792668917, 9.487519866646766]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [-0.007569072998040621, 0.7073232895687733, 0.2540302118175998], "properties": {}, "label": "Cd2+", "xyz": [-0.09905470230042206, 9.256575791583357, 3.324434447047787]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [-0.005091039737760129, 0.735542315576001, 0.7679544090557621], "properties": {}, "label": "Cd2+", "xyz": [-0.06662525592684765, 9.625871638125911, 10.050041185889857]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.500581636698387, 0.282053638247884, 0.23354029093920137], "properties": {}, "label": "Cd2+", "xyz": [6.5509957445320754, 3.691170527305984, 3.0562876061738304]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.5164511376504562, 0.2542450219488173, 0.7322823716342084], "properties": {}, "label": "Cd2+", "xyz": [6.758676221767586, 3.32724561739908, 9.583209508066666]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.5070828585054892, 0.7526462759552357, 0.2894765820190052], "properties": {}, "label": "Cd2+", "xyz": [6.636075726038163, 9.849707199490148, 3.7883128703156927]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.5049584993998797, 0.7681671971948304, 0.759194223593266], "properties": {}, "label": "Cd2+", "xyz": [6.608274731274364, 10.052825894898996, 9.9353986711052]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.23144390076130325, -0.013969158032703688, 0.27108986375916067], "properties": {}, "label": "Cd2+", "xyz": [3.028852634278199, -0.18281113032932955, 3.5476901541677432]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.23763691303939355, 0.005368794424074448, 0.738165301172775], "properties": {}, "label": "Cd2+", "xyz": [3.109899149182718, 0.07026016706755592, 9.660198042098234]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.27183740458126954, 0.4922892243165773, 0.2451784226567301], "properties": {}, "label": "Cd2+", "xyz": [3.5574730474772114, 6.442474867531005, 3.2085931359145703]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.26229474722281126, 0.517354969716271, 0.7260213390066484], "properties": {}, "label": "Cd2+", "xyz": [3.432590504523575, 6.770504462323864, 9.501272826629359]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.7421013906143455, -0.03693942541801388, 0.2601101761807642], "properties": {}, "label": "Cd2+", "xyz": [9.711708731447317, -0.48341769049885064, 3.404001530116787]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.7643010257290317, 0.008255972386925511, 0.7375144675771959], "properties": {}, "label": "Cd2+", "xyz": [10.002230205877868, 0.10804399524210039, 9.651680733826284]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.7238316776408056, 0.5303468397680425, 0.26649189103247894], "properties": {}, "label": "Cd2+", "xyz": [9.47261723633601, 6.940526051577546, 3.4875175518233323]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.7772298637541588, 0.506173787789148, 0.7418884425731875], "properties": {}, "label": "Cd2+", "xyz": [10.171426909622285, 6.6241789284778125, 9.708921929836627]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.21129195243108964, 0.23918180312926857, -0.004713779360792357], "properties": {}, "label": "Cd2+", "xyz": [2.765128761732706, 3.1301167673744117, -0.061688136897877874]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.2597626037064264, 0.2654750359385809, 0.4861821253759255], "properties": {}, "label": "Cd2+", "xyz": [3.3994529297819422, 3.47421020511987, 6.362552680541649]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.2856911389734074, 0.7667607419061732, -0.0175620557263897], "properties": {}, "label": "Cd2+", "xyz": [3.738773655400741, 10.034419940833965, -0.22983054889433344]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.25533084923273663, 0.7324121579095495, 0.47291637752448296], "properties": {}, "label": "Cd2+", "xyz": [3.341455587151802, 9.58490799094164, 6.188946916063323]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.787787428123557, 0.22109627471208937, -0.003940356567826238], "properties": {}, "label": "Cd2+", "xyz": [10.309591305169665, 2.8934356528213803, -0.05156653224041824]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.7756092046801939, 0.26967412576303745, 0.49053849129305294], "properties": {}, "label": "Cd2+", "xyz": [10.150217720314211, 3.529162719463694, 6.419563430622204]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.767460332714889, 0.7433936502088357, 0.03425825864459876], "properties": {}, "label": "Cd2+", "xyz": [10.043575323442562, 9.728620232956185, 0.44832988296585846]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.7659806984052299, 0.759274182362689, 0.5084426299687161], "properties": {}, "label": "Cd2+", "xyz": [10.024211692507214, 9.936445072970201, 6.653870739710435]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.125, 0.125, 0.375], "properties": {}, "label": "Te2-", "xyz": [1.635846, 1.635846, 4.907538]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.09202099885938815, 0.06761196216891037, 0.9744295403917416], "properties": {}, "label": "Te2-", "xyz": [1.2042574632010772, 0.8848220629293068, 12.75213332745335]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.12010924261780784, 0.6273250438453774, 0.36873546069156204], "properties": {}, "label": "Te2-", "xyz": [1.5718417927949637, 8.20965730939428, 4.825555427443592]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.11875684857742955, 0.6507937162676332, 0.8762100856753475], "properties": {}, "label": "Te2-", "xyz": [1.5541433257439505, 8.516786380652341, 11.466758110493396]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.631235942983672, 0.13285519620415553, 0.42025981357204256], "properties": {}, "label": "Te2-", "xyz": [8.260838339088544, 1.738645130318264, 5.499842679940572]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.6128884079346265, 0.11829516580406792, 0.8473039496781265], "properties": {}, "label": "Te2-", "xyz": [8.020728404529816, 1.5481013903993701, 11.088470214921315]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.6315797807618615, 0.6225939184343635, 0.3700339946167043], "properties": {}, "label": "Te2-", "xyz": [8.265338064321345, 8.147742168761438, 4.842549039662058]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.6283847628206453, 0.6104322706926303, 0.8462095332815449], "properties": {}, "label": "Te2-", "xyz": [8.22352560576881, 7.988585506267651, 11.074147841443857]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.12312332856968954, 0.39317854725891327, 0.1303636878306193], "properties": {}, "label": "Te2-", "xyz": [1.6112864363792987, 5.145436430554434, 1.706039338263738]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.125, 0.375, 0.625], "properties": {}, "label": "Te2-", "xyz": [1.635846, 4.907538, 8.17923]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.11326489374951178, 0.9653259676356608, 0.07148324131589577], "properties": {}, "label": "Te2-", "xyz": [1.4822713870445106, 12.632996982823402, 0.9354845949891426]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.11393398945863639, 0.8681844242533566, 0.6518017271048436], "properties": {}, "label": "Te2-", "xyz": [1.49102768735962, 11.36172814141725, 8.5299779846204]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.6271436363496584, 0.36670784058284467, 0.10278719284878346], "properties": {}, "label": "Te2-", "xyz": [8.207283271584346, 4.799020433488673, 1.345152146183288]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.6273727892891334, 0.3919108640973764, 0.6439967743041993], "properties": {}, "label": "Te2-", "xyz": [8.210282142939773, 5.1288465551218945, 8.427836378067418]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.6141821228674019, 0.8715100179204547, 0.11272616402540049], "properties": {}, "label": "Te2-", "xyz": [8.037658951713183, 11.405249414200831, 1.4752211561303623]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.6173411386799441, 0.8870697863654669, 0.5681915310744818], "properties": {}, "label": "Te2-", "xyz": [8.079000258760255, 11.608876493974428, 7.435790746736534]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.4075630076145119, 0.10196314968316818, 0.09136490395019445], "properties": {}, "label": "Te2-", "xyz": [5.333682526033351, 1.3343680844528953, 1.1956713013384783]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.3582245405973819, 0.1339467110079552, 0.612957901151216], "properties": {}, "label": "Te2-", "xyz": [4.688001454704518, 1.7529295313241555, 8.021637846132895]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.42420546262325104, 0.6608186394979916, 0.10629187200041514], "properties": {}, "label": "Te2-", "xyz": [5.551478473683157, 8.647980225185853, 1.3910170691551287]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.36063832552584496, 0.6277091821232872, 0.6614751986974577], "properties": {}, "label": "Te2-", "xyz": [4.71959009806521, 8.214684437917207, 8.656572463107532]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.8752520424249852, 0.11394353706788272, 0.09338963038736504], "properties": {}, "label": "Te2-", "xyz": [11.454220420741938, 1.4911526347067814, 1.2221684264851964]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.9060941606888188, 0.10790889932389516, 0.6114096916911319], "properties": {}, "label": "Te2-", "xyz": [11.85784406708929, 1.4121787305871727, 8.00137678811337]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.8886215189674253, 0.6282996437744819, 0.11834167261895481], "properties": {}, "label": "Te2-", "xyz": [11.629183658534293, 8.222411672559288, 1.548710014296214]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.9221889309442108, 0.593362891916048, 0.6250367613866259], "properties": {}, "label": "Te2-", "xyz": [12.068472591434908, 7.765202506314394, 8.17971108773813]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.36916592451403507, 0.3650467855183059, 0.4018612689124704], "properties": {}, "label": "Te2-", "xyz": [4.8311888076206895, 4.777282591223829, 5.259065194443113]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.35783072448043823, 0.3964941555012161, 0.8376090610972016], "properties": {}, "label": "Te2-", "xyz": [4.682847674547415, 5.188827026400339, 10.961595457276902]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.3803128561907226, 0.8926243580437951, 0.3895882828468021], "properties": {}, "label": "Te2-", "xyz": [4.97706611638535, 11.68156788486808, 5.098451473134478]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.3947428748509966, 0.889882206552039, 0.8471153956045201], "properties": {}, "label": "Te2-", "xyz": [5.165908422828027, 11.645681984474614, 11.086002651504574]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.8766076264970295, 0.38772454802481315, 0.38274173563155073], "properties": {}, "label": "Te2-", "xyz": [11.471960634997277, 5.074061207905587, 5.0088522981274375]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.9050213842460662, 0.37764676957276155, 0.8448089700532792], "properties": {}, "label": "Te2-", "xyz": [11.843804890667123, 4.942175659348189, 11.055818995406211]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.8975517329778733, 0.86887470987142, 0.4137527997097622], "properties": {}, "label": "Te2-", "xyz": [11.746051297479376, 11.370761749154584, 5.414686899152125]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.895865344260897, 0.874393191349097, 0.8552705997044722], "properties": {}, "label": "Te2-", "xyz": [11.72398191958249, 11.442980835965239, 11.192727915553295]}], "@version": null}}}}}}, "v_Te_Td_Cd2.83": {"defect_type": "vacancy", "defect_site": {"species": [{"element": "Te", "occu": 1}], "abc": [0.125, 0.125, 0.375], "lattice": {"@module": "pymatgen.core.lattice", "@class": "Lattice", "matrix": [[13.086768, 0.0, 0.0], [0.0, 13.086768, 0.0], [0.0, 0.0, 13.086768]], "pbc": [true, true, true]}, "@module": "pymatgen.core.sites", "@class": "PeriodicSite", "properties": {}, "label": "Te", "@version": null}, "defect_supercell_site": {"species": [{"element": "Te", "occu": 1}], "abc": [0.125, 0.125, 0.375], "lattice": {"@module": "pymatgen.core.lattice", "@class": "Lattice", "matrix": [[13.086768, 0.0, 0.0], [0.0, 13.086768, 0.0], [0.0, 0.0, 13.086768]], "pbc": [true, true, true]}, "@module": "pymatgen.core.sites", "@class": "PeriodicSite", "properties": {}, "label": "Te", "@version": null}, "charges": {"0": {"structures": {"Unperturbed": {"@module": "pymatgen.core.structure", "@class": "Structure", "charge": 0, "lattice": {"matrix": [[13.086768, 0.0, 0.0], [0.0, 13.086768, 0.0], [0.0, 0.0, 13.086768]], "pbc": [true, true, true], "a": 13.086768, "b": 13.086768, "c": 13.086768, "alpha": 90.0, "beta": 90.0, "gamma": 90.0, "volume": 2241.2856479961474}, "properties": {}, "sites": [{"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.0, 0.0, 0.0], "properties": {}, "label": "Cd", "xyz": [0.0, 0.0, 0.0]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.0, 0.0, 0.5], "properties": {}, "label": "Cd", "xyz": [0.0, 0.0, 6.543384]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.0, 0.5, 0.0], "properties": {}, "label": "Cd", "xyz": [0.0, 6.543384, 0.0]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.0, 0.5, 0.5], "properties": {}, "label": "Cd", "xyz": [0.0, 6.543384, 6.543384]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.5, 0.0, 0.0], "properties": {}, "label": "Cd", "xyz": [6.543384, 0.0, 0.0]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.5, 0.0, 0.5], "properties": {}, "label": "Cd", "xyz": [6.543384, 0.0, 6.543384]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.5, 0.5, 0.0], "properties": {}, "label": "Cd", "xyz": [6.543384, 6.543384, 0.0]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.5, 0.5, 0.5], "properties": {}, "label": "Cd", "xyz": [6.543384, 6.543384, 6.543384]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.0, 0.25, 0.25], "properties": {}, "label": "Cd", "xyz": [0.0, 3.271692, 3.271692]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.0, 0.25, 0.75], "properties": {}, "label": "Cd", "xyz": [0.0, 3.271692, 9.815076]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.0, 0.75, 0.25], "properties": {}, "label": "Cd", "xyz": [0.0, 9.815076, 3.271692]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.0, 0.75, 0.75], "properties": {}, "label": "Cd", "xyz": [0.0, 9.815076, 9.815076]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.5, 0.25, 0.25], "properties": {}, "label": "Cd", "xyz": [6.543384, 3.271692, 3.271692]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.5, 0.25, 0.75], "properties": {}, "label": "Cd", "xyz": [6.543384, 3.271692, 9.815076]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.5, 0.75, 0.25], "properties": {}, "label": "Cd", "xyz": [6.543384, 9.815076, 3.271692]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.5, 0.75, 0.75], "properties": {}, "label": "Cd", "xyz": [6.543384, 9.815076, 9.815076]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.25, 0.0, 0.25], "properties": {}, "label": "Cd", "xyz": [3.271692, 0.0, 3.271692]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.25, 0.0, 0.75], "properties": {}, "label": "Cd", "xyz": [3.271692, 0.0, 9.815076]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.25, 0.5, 0.25], "properties": {}, "label": "Cd", "xyz": [3.271692, 6.543384, 3.271692]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.25, 0.5, 0.75], "properties": {}, "label": "Cd", "xyz": [3.271692, 6.543384, 9.815076]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.75, 0.0, 0.25], "properties": {}, "label": "Cd", "xyz": [9.815076, 0.0, 3.271692]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.75, 0.0, 0.75], "properties": {}, "label": "Cd", "xyz": [9.815076, 0.0, 9.815076]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.75, 0.5, 0.25], "properties": {}, "label": "Cd", "xyz": [9.815076, 6.543384, 3.271692]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.75, 0.5, 0.75], "properties": {}, "label": "Cd", "xyz": [9.815076, 6.543384, 9.815076]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.25, 0.25, 0.0], "properties": {}, "label": "Cd", "xyz": [3.271692, 3.271692, 0.0]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.25, 0.25, 0.5], "properties": {}, "label": "Cd", "xyz": [3.271692, 3.271692, 6.543384]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.25, 0.75, 0.0], "properties": {}, "label": "Cd", "xyz": [3.271692, 9.815076, 0.0]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.25, 0.75, 0.5], "properties": {}, "label": "Cd", "xyz": [3.271692, 9.815076, 6.543384]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.75, 0.25, 0.0], "properties": {}, "label": "Cd", "xyz": [9.815076, 3.271692, 0.0]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.75, 0.25, 0.5], "properties": {}, "label": "Cd", "xyz": [9.815076, 3.271692, 6.543384]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.75, 0.75, 0.0], "properties": {}, "label": "Cd", "xyz": [9.815076, 9.815076, 0.0]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.75, 0.75, 0.5], "properties": {}, "label": "Cd", "xyz": [9.815076, 9.815076, 6.543384]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.125, 0.125, 0.8749999999999999], "properties": {}, "label": "Te", "xyz": [1.635846, 1.635846, 11.450921999999998]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.125, 0.625, 0.375], "properties": {}, "label": "Te", "xyz": [1.635846, 8.17923, 4.907538]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.125, 0.625, 0.8749999999999999], "properties": {}, "label": "Te", "xyz": [1.635846, 8.17923, 11.450921999999998]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.625, 0.125, 0.375], "properties": {}, "label": "Te", "xyz": [8.17923, 1.635846, 4.907538]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.625, 0.125, 0.8749999999999999], "properties": {}, "label": "Te", "xyz": [8.17923, 1.635846, 11.450921999999998]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.625, 0.625, 0.375], "properties": {}, "label": "Te", "xyz": [8.17923, 8.17923, 4.907538]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.625, 0.625, 0.8749999999999999], "properties": {}, "label": "Te", "xyz": [8.17923, 8.17923, 11.450921999999998]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.125, 0.375, 0.125], "properties": {}, "label": "Te", "xyz": [1.635846, 4.907538, 1.635846]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.125, 0.375, 0.625], "properties": {}, "label": "Te", "xyz": [1.635846, 4.907538, 8.17923]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.125, 0.8749999999999999, 0.125], "properties": {}, "label": "Te", "xyz": [1.635846, 11.450921999999998, 1.635846]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.125, 0.8749999999999999, 0.625], "properties": {}, "label": "Te", "xyz": [1.635846, 11.450921999999998, 8.17923]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.625, 0.375, 0.125], "properties": {}, "label": "Te", "xyz": [8.17923, 4.907538, 1.635846]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.625, 0.375, 0.625], "properties": {}, "label": "Te", "xyz": [8.17923, 4.907538, 8.17923]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.625, 0.8749999999999999, 0.125], "properties": {}, "label": "Te", "xyz": [8.17923, 11.450921999999998, 1.635846]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.625, 0.8749999999999999, 0.625], "properties": {}, "label": "Te", "xyz": [8.17923, 11.450921999999998, 8.17923]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.375, 0.125, 0.125], "properties": {}, "label": "Te", "xyz": [4.907538, 1.635846, 1.635846]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.375, 0.125, 0.625], "properties": {}, "label": "Te", "xyz": [4.907538, 1.635846, 8.17923]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.375, 0.625, 0.125], "properties": {}, "label": "Te", "xyz": [4.907538, 8.17923, 1.635846]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.375, 0.625, 0.625], "properties": {}, "label": "Te", "xyz": [4.907538, 8.17923, 8.17923]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.8749999999999999, 0.125, 0.125], "properties": {}, "label": "Te", "xyz": [11.450921999999998, 1.635846, 1.635846]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.8749999999999999, 0.125, 0.625], "properties": {}, "label": "Te", "xyz": [11.450921999999998, 1.635846, 8.17923]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.8749999999999999, 0.625, 0.125], "properties": {}, "label": "Te", "xyz": [11.450921999999998, 8.17923, 1.635846]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.8749999999999999, 0.625, 0.625], "properties": {}, "label": "Te", "xyz": [11.450921999999998, 8.17923, 8.17923]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.375, 0.375, 0.375], "properties": {}, "label": "Te", "xyz": [4.907538, 4.907538, 4.907538]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.375, 0.375, 0.8749999999999999], "properties": {}, "label": "Te", "xyz": [4.907538, 4.907538, 11.450921999999998]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.375, 0.8749999999999999, 0.375], "properties": {}, "label": "Te", "xyz": [4.907538, 11.450921999999998, 4.907538]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.375, 0.8749999999999999, 0.8749999999999999], "properties": {}, "label": "Te", "xyz": [4.907538, 11.450921999999998, 11.450921999999998]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.8749999999999999, 0.375, 0.375], "properties": {}, "label": "Te", "xyz": [11.450921999999998, 4.907538, 4.907538]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.8749999999999999, 0.375, 0.8749999999999999], "properties": {}, "label": "Te", "xyz": [11.450921999999998, 4.907538, 11.450921999999998]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.8749999999999999, 0.8749999999999999, 0.375], "properties": {}, "label": "Te", "xyz": [11.450921999999998, 11.450921999999998, 4.907538]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.8749999999999999, 0.8749999999999999, 0.8749999999999999], "properties": {}, "label": "Te", "xyz": [11.450921999999998, 11.450921999999998, 11.450921999999998]}], "@version": null}, "distortions": {"Bond_Distortion_-30.0%": {"@module": "pymatgen.core.structure", "@class": "Structure", "charge": 2, "lattice": {"matrix": [[13.086768, 0.0, 0.0], [0.0, 13.086768, 0.0], [0.0, 0.0, 13.086768]], "pbc": [true, true, true], "a": 13.086768, "b": 13.086768, "c": 13.086768, "alpha": 90.0, "beta": 90.0, "gamma": 90.0, "volume": 2241.2856479961474}, "properties": {}, "sites": [{"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.01568711948403533, 0.016849917236106522, 0.0013659606570437218], "properties": {}, "label": "Cd2+", "xyz": [0.20529369327585004, 0.22051095768812726, 0.017876010215858752]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.03749902367755584, 0.03749902367755584, 0.4625009763224441], "properties": {}, "label": "Cd2+", "xyz": [0.49074102309468004, 0.49074102309468004, 6.052642976905319]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.027150011566868578, 0.4957262246093266, 0.010022476367907264], "properties": {}, "label": "Cd2+", "xyz": [0.35530590257292555, 6.4874540929781475, 0.13116182301228502]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [-0.0014495703722098626, 0.498073152435215, 0.47811921679604913], "properties": {}, "label": "Cd2+", "xyz": [-0.01897019116078412, 6.518167792948294, 6.257035266551598]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.4722094233480639, 0.020050629436408757, -0.018269223047381645], "properties": {}, "label": "Cd2+", "xyz": [6.179695170769895, 0.26239793568825215, -0.2390850835613366]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.4885585281400621, -0.005387872873882472, 0.481767300464419], "properties": {}, "label": "Cd2+", "xyz": [6.393652112190464, -0.07050984231399317, 6.304776891164144]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.5056282073490231, 0.5186277985499153, -0.02697461158586072], "properties": {}, "label": "Cd2+", "xyz": [6.617039043832561, 6.787161677973477, -0.3530104837142713]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.4801688502186689, 0.5257243960661042, 0.5248055377787585], "properties": {}, "label": "Cd2+", "xyz": [6.283858343638469, 6.880033203257218, 6.868008318025847]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.03749902367755584, 0.21250097632244416, 0.2874990236775558], "properties": {}, "label": "Cd2+", "xyz": [0.49074102309468004, 2.78095097690532, 3.7624330230946796]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [-0.034831636895078275, 0.27289443167148103, 0.7387460475061252], "properties": {}, "label": "Cd2+", "xyz": [-0.4558335511061297, 3.5713061157765242, 9.667798134629638]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [-0.010574547252808927, 0.7665553322767635, 0.22497043323812005], "properties": {}, "label": "Cd2+", "xyz": [-0.13838664660254776, 10.031731792668916, 2.9441358666467656]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [-0.007569072998040621, 0.7073232895687733, 0.7540302118175998], "properties": {}, "label": "Cd2+", "xyz": [-0.09905470230042206, 9.256575791583357, 9.867818447047785]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.49490896026223985, 0.23554231557600105, 0.267954409055762], "properties": {}, "label": "Cd2+", "xyz": [6.476758744073152, 3.082487638125912, 3.506657185889856]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.500581636698387, 0.282053638247884, 0.7335402909392014], "properties": {}, "label": "Cd2+", "xyz": [6.5509957445320754, 3.691170527305984, 9.59967160617383]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.5164511376504562, 0.7542450219488174, 0.23228237163420845], "properties": {}, "label": "Cd2+", "xyz": [6.758676221767586, 9.87062961739908, 3.0398255080666665]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.5070828585054892, 0.7526462759552357, 0.7894765820190052], "properties": {}, "label": "Cd2+", "xyz": [6.636075726038163, 9.849707199490148, 10.331696870315692]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.2549584993998797, 0.018167197194830374, 0.25919422359326616], "properties": {}, "label": "Cd2+", "xyz": [3.3365827312743646, 0.2377498948989959, 3.3920146711052004]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.23144390076130325, -0.013969158032703688, 0.7710898637591608], "properties": {}, "label": "Cd2+", "xyz": [3.028852634278199, -0.18281113032932955, 10.091074154167744]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.23763691303939355, 0.5053687944240745, 0.23816530117277496], "properties": {}, "label": "Cd2+", "xyz": [3.109899149182718, 6.613644167067555, 3.1168140420982335]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.27183740458126954, 0.4922892243165773, 0.7451784226567301], "properties": {}, "label": "Cd2+", "xyz": [3.5574730474772114, 6.442474867531005, 9.751977135914569]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.7622947472228112, 0.017354969716271013, 0.22602133900664856], "properties": {}, "label": "Cd2+", "xyz": [9.975974504523574, 0.22712046232386454, 2.95788882662936]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.7421013906143455, -0.03693942541801388, 0.7601101761807642], "properties": {}, "label": "Cd2+", "xyz": [9.711708731447317, -0.48341769049885064, 9.947385530116787]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.7643010257290317, 0.5082559723869255, 0.23751446757719594], "properties": {}, "label": "Cd2+", "xyz": [10.002230205877868, 6.6514279952421, 3.1082967338262852]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.7772298637541588, 0.506173787789148, 0.7418884425731875], "properties": {}, "label": "Cd2+", "xyz": [10.171426909622285, 6.6241789284778125, 9.708921929836627]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.21129195243108964, 0.23918180312926857, -0.004713779360792357], "properties": {}, "label": "Cd2+", "xyz": [2.765128761732706, 3.1301167673744117, -0.061688136897877874]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.2597626037064264, 0.2654750359385809, 0.4861821253759255], "properties": {}, "label": "Cd2+", "xyz": [3.3994529297819422, 3.47421020511987, 6.362552680541649]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.2856911389734074, 0.7667607419061732, -0.0175620557263897], "properties": {}, "label": "Cd2+", "xyz": [3.738773655400741, 10.034419940833965, -0.22983054889433344]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.25533084923273663, 0.7324121579095495, 0.47291637752448296], "properties": {}, "label": "Cd2+", "xyz": [3.341455587151802, 9.58490799094164, 6.188946916063323]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.787787428123557, 0.22109627471208937, -0.003940356567826238], "properties": {}, "label": "Cd2+", "xyz": [10.309591305169665, 2.8934356528213803, -0.05156653224041824]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.7756092046801939, 0.26967412576303745, 0.49053849129305294], "properties": {}, "label": "Cd2+", "xyz": [10.150217720314211, 3.529162719463694, 6.419563430622204]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.767460332714889, 0.7433936502088357, 0.03425825864459876], "properties": {}, "label": "Cd2+", "xyz": [10.043575323442562, 9.728620232956185, 0.44832988296585846]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.7659806984052299, 0.759274182362689, 0.5084426299687161], "properties": {}, "label": "Cd2+", "xyz": [10.024211692507214, 9.936445072970201, 6.653870739710435]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.12010924261780784, 0.12732504384537732, 0.8687354606915619], "properties": {}, "label": "Te2-", "xyz": [1.5718417927949637, 1.6662733093942808, 11.368939427443589]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.11875684857742955, 0.6507937162676332, 0.3762100856753475], "properties": {}, "label": "Te2-", "xyz": [1.5541433257439505, 8.516786380652341, 4.923374110493397]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.13123594298367203, 0.6328551962041555, 0.9202598135720425], "properties": {}, "label": "Te2-", "xyz": [1.7174543390885435, 8.282029130318264, 12.04322667994057]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.6128884079346265, 0.11829516580406792, 0.3473039496781265], "properties": {}, "label": "Te2-", "xyz": [8.020728404529816, 1.5481013903993701, 4.545086214921316]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.6315797807618615, 0.12259391843436342, 0.8700339946167042], "properties": {}, "label": "Te2-", "xyz": [8.265338064321345, 1.6043581687614372, 11.385933039662056]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.6283847628206453, 0.6104322706926303, 0.3462095332815449], "properties": {}, "label": "Te2-", "xyz": [8.22352560576881, 7.988585506267651, 4.530763841443857]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.6231233285696897, 0.6431785472589133, 0.8803636878306192], "properties": {}, "label": "Te2-", "xyz": [8.1546704363793, 8.417128430554435, 11.521115338263737]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.11326489374951178, 0.3943581611601848, 0.14245104779137183], "properties": {}, "label": "Te2-", "xyz": [1.4822713870445106, 5.160873764009949, 1.8642238138025955]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.11393398945863639, 0.36818442425335673, 0.6518017271048436], "properties": {}, "label": "Te2-", "xyz": [1.49102768735962, 4.818344141417253, 8.5299779846204]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.12714363634965842, 0.8667078405828446, 0.10278719284878346], "properties": {}, "label": "Te2-", "xyz": [1.6638992715843466, 11.342404433488671, 1.345152146183288]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.12737278928913331, 0.8919108640973763, 0.6439967743041993], "properties": {}, "label": "Te2-", "xyz": [1.6668981429397725, 11.672230555121892, 8.427836378067418]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.6141821228674019, 0.3715100179204548, 0.11272616402540049], "properties": {}, "label": "Te2-", "xyz": [8.037658951713183, 4.861865414200834, 1.4752211561303623]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.6173411386799441, 0.387069786365467, 0.5681915310744818], "properties": {}, "label": "Te2-", "xyz": [8.079000258760255, 5.06549249397443, 7.435790746736534]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.608224540597382, 0.8839467110079551, 0.11295790115121591], "properties": {}, "label": "Te2-", "xyz": [7.959693454704519, 11.568005531324154, 1.4782538461328956]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.6742054626232512, 0.9108186394979915, 0.6062918720004151], "properties": {}, "label": "Te2-", "xyz": [8.82317047368316, 11.91967222518585, 7.934401069155128]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.36063832552584496, 0.12770918212328716, 0.16147519869745774], "properties": {}, "label": "Te2-", "xyz": [4.71959009806521, 1.6713004379172065, 2.1131884631075315]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.37525204242498533, 0.11394353706788272, 0.5933896303873651], "properties": {}, "label": "Te2-", "xyz": [4.910836420741941, 1.4911526347067814, 7.765552426485197]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.4060941606888188, 0.6079088993238952, 0.11140969169113184], "properties": {}, "label": "Te2-", "xyz": [5.314460067089292, 7.9555627305871734, 1.4579927881133699]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.369165924514035, 0.6150467855183058, 0.6518612689124705], "properties": {}, "label": "Te2-", "xyz": [4.831188807620689, 8.048974591223828, 8.530757194443114]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.8803128561907224, 0.14262435804379522, 0.13958828284680208], "properties": {}, "label": "Te2-", "xyz": [11.520450116385348, 1.8664918848680818, 1.8267594731344783]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.8947428748509965, 0.13988220655203906, 0.5971153956045203], "properties": {}, "label": "Te2-", "xyz": [11.709292422828025, 1.830605984474615, 7.814310651504576]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.9050213842460662, 0.6276467695727617, 0.09480897005327915], "properties": {}, "label": "Te2-", "xyz": [11.843804890667123, 8.21386765934819, 1.240742995406212]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.895865344260897, 0.6243931913490971, 0.6052705997044724], "properties": {}, "label": "Te2-", "xyz": [11.72398191958249, 8.17128883596524, 7.921035915553298]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.39973014540869395, 0.37602805020175234, 0.3786909690625228], "properties": {}, "label": "Te2-", "xyz": [5.231175675569842, 4.920991854482685, 4.9558408558164135]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.36627096956773875, 0.37647047428422986, 0.8799247251675606], "properties": {}, "label": "Te2-", "xyz": [4.793303203868057, 4.926781755807682, 11.515370735731626]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.3694061068288607, 0.8514102855379397, 0.36722890002230013], "properties": {}, "label": "Te2-", "xyz": [4.834332017852516, 11.142208879648772, 4.805839417487037]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.39036318842662027, 0.8750040440301268, 0.8693633479218933], "properties": {}, "label": "Te2-", "xyz": [5.108592482679464, 11.450974923284054, 11.3771564419571]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.8840986630527821, 0.3765797318809899, 0.3701975112891385], "properties": {}, "label": "Te2-", "xyz": [11.56999409248193, 4.928211584628718, 4.844688944418337]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.8878406141383686, 0.39518376205924294, 0.8849706124942863], "properties": {}, "label": "Te2-", "xyz": [11.61896413820635, 5.1716782114365145, 11.581405092530625]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.8794827130771701, 0.8854032160195859, 0.36748368646509866], "properties": {}, "label": "Te2-", "xyz": [11.509586226051491, 11.587066474502205, 4.809173748553486]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.8917242540183349, 0.8543409397301263, 0.878435252439452], "properties": {}, "label": "Te2-", "xyz": [11.669788432311016, 11.180561671150144, 11.495878351696541]}], "@version": null}, "Dimer": {"@module": "pymatgen.core.structure", "@class": "Structure", "charge": 2, "lattice": {"matrix": [[13.086768, 0.0, 0.0], [0.0, 13.086768, 0.0], [0.0, 0.0, 13.086768]], "pbc": [true, true, true], "a": 13.086768, "b": 13.086768, "c": 13.086768, "alpha": 90.0, "beta": 90.0, "gamma": 90.0, "volume": 2241.2856479961474}, "properties": {}, "sites": [{"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.0, 0.0, 0.0], "properties": {}, "label": "Cd2+", "xyz": [0.0, 0.0, 0.0]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.01568711948403533, 0.08781772371158253, 0.4303981541815677], "properties": {}, "label": "Cd2+", "xyz": [0.20529369327585004, 1.1492501765015795, 5.632520791402406]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.027150011566868578, 0.4957262246093266, 0.010022476367907264], "properties": {}, "label": "Cd2+", "xyz": [0.35530590257292555, 6.4874540929781475, 0.13116182301228502]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [-0.0014495703722098626, 0.498073152435215, 0.47811921679604913], "properties": {}, "label": "Cd2+", "xyz": [-0.01897019116078412, 6.518167792948294, 6.257035266551598]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.4722094233480639, 0.020050629436408757, -0.018269223047381645], "properties": {}, "label": "Cd2+", "xyz": [6.179695170769895, 0.26239793568825215, -0.2390850835613366]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.4885585281400621, -0.005387872873882472, 0.481767300464419], "properties": {}, "label": "Cd2+", "xyz": [6.393652112190464, -0.07050984231399317, 6.304776891164144]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.5056282073490231, 0.5186277985499153, -0.02697461158586072], "properties": {}, "label": "Cd2+", "xyz": [6.617039043832561, 6.787161677973477, -0.3530104837142713]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.5, 0.5, 0.5], "properties": {}, "label": "Cd2+", "xyz": [6.543384, 6.543384, 6.543384]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [-0.019831149781331055, 0.20475658959062829, 0.3457733442542344], "properties": {}, "label": "Cd2+", "xyz": [-0.25952565636153024, 2.6796019844437673, 4.525055536839298]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [-0.034831636895078275, 0.27289443167148103, 0.7387460475061252], "properties": {}, "label": "Cd2+", "xyz": [-0.4558335511061297, 3.5713061157765242, 9.667798134629638]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [-0.010574547252808927, 0.7665553322767635, 0.22497043323812005], "properties": {}, "label": "Cd2+", "xyz": [-0.13838664660254776, 10.031731792668916, 2.9441358666467656]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [-0.007569072998040621, 0.7073232895687733, 0.7540302118175998], "properties": {}, "label": "Cd2+", "xyz": [-0.09905470230042206, 9.256575791583357, 9.867818447047785]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.49490896026223985, 0.23554231557600105, 0.267954409055762], "properties": {}, "label": "Cd2+", "xyz": [6.476758744073152, 3.082487638125912, 3.506657185889856]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.500581636698387, 0.282053638247884, 0.7335402909392014], "properties": {}, "label": "Cd2+", "xyz": [6.5509957445320754, 3.691170527305984, 9.59967160617383]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.5164511376504562, 0.7542450219488174, 0.23228237163420845], "properties": {}, "label": "Cd2+", "xyz": [6.758676221767586, 9.87062961739908, 3.0398255080666665]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.5070828585054892, 0.7526462759552357, 0.7894765820190052], "properties": {}, "label": "Cd2+", "xyz": [6.636075726038163, 9.849707199490148, 10.331696870315692]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.2549584993998797, 0.018167197194830374, 0.25919422359326616], "properties": {}, "label": "Cd2+", "xyz": [3.3365827312743646, 0.2377498948989959, 3.3920146711052004]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.23144390076130325, -0.013969158032703688, 0.7710898637591608], "properties": {}, "label": "Cd2+", "xyz": [3.028852634278199, -0.18281113032932955, 10.091074154167744]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.23763691303939355, 0.5053687944240745, 0.23816530117277496], "properties": {}, "label": "Cd2+", "xyz": [3.109899149182718, 6.613644167067555, 3.1168140420982335]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.27183740458126954, 0.4922892243165773, 0.7451784226567301], "properties": {}, "label": "Cd2+", "xyz": [3.5574730474772114, 6.442474867531005, 9.751977135914569]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.7622947472228112, 0.017354969716271013, 0.22602133900664856], "properties": {}, "label": "Cd2+", "xyz": [9.975974504523574, 0.22712046232386454, 2.95788882662936]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.7421013906143455, -0.03693942541801388, 0.7601101761807642], "properties": {}, "label": "Cd2+", "xyz": [9.711708731447317, -0.48341769049885064, 9.947385530116787]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.7643010257290317, 0.5082559723869255, 0.23751446757719594], "properties": {}, "label": "Cd2+", "xyz": [10.002230205877868, 6.6514279952421, 3.1082967338262852]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.7238316776408056, 0.5303468397680425, 0.766491891032479], "properties": {}, "label": "Cd2+", "xyz": [9.47261723633601, 6.940526051577546, 10.030901551823332]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.2772298637541588, 0.256173787789148, -0.008111557426812552], "properties": {}, "label": "Cd2+", "xyz": [3.6280429096222853, 3.3524869284778127, -0.10615407016337285]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.21129195243108964, 0.23918180312926857, 0.49528622063920763], "properties": {}, "label": "Cd2+", "xyz": [2.765128761732706, 3.1301167673744117, 6.4816958631021215]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.2597626037064264, 0.7654750359385809, -0.013817874624074463], "properties": {}, "label": "Cd2+", "xyz": [3.3994529297819422, 10.01759420511987, -0.1808313194583497]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.2856911389734074, 0.7667607419061732, 0.4824379442736103], "properties": {}, "label": "Cd2+", "xyz": [3.738773655400741, 10.034419940833965, 6.313553451105666]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.7553308492327366, 0.23241215790954953, -0.027083622475517065], "properties": {}, "label": "Cd2+", "xyz": [9.884839587151802, 3.0415239909416396, -0.3544370839366775]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.787787428123557, 0.22109627471208937, 0.49605964343217374], "properties": {}, "label": "Cd2+", "xyz": [10.309591305169665, 2.8934356528213803, 6.4918174677595815]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.7756092046801939, 0.7696741257630374, -0.009461508706947064], "properties": {}, "label": "Cd2+", "xyz": [10.150217720314211, 10.072546719463693, -0.1238205693777962]}, {"species": [{"element": "Cd", "oxidation_state": 2, "spin": null, "occu": 1}], "abc": [0.767460332714889, 0.7433936502088357, 0.5342582586445987], "properties": {}, "label": "Cd2+", "xyz": [10.043575323442562, 9.728620232956185, 6.991713882965858]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.14098069840522992, 0.13427418236268895, 0.883442629968716], "properties": {}, "label": "Te2-", "xyz": [1.8449816925072138, 1.757215072970202, 11.561408739710433]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.09202099885938815, 0.6385797686443865, 0.4034617339162655], "properties": {}, "label": "Te2-", "xyz": [1.2042574632010772, 8.35694528174276, 5.280010108639898]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.12010924261780784, 0.6273250438453774, 0.8687354606915619], "properties": {}, "label": "Te2-", "xyz": [1.5718417927949637, 8.20965730939428, 11.368939427443589]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.6187568485774295, 0.1507937162676332, 0.3762100856753475], "properties": {}, "label": "Te2-", "xyz": [8.09752732574395, 1.9734023806523413, 4.923374110493397]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.631235942983672, 0.13285519620415553, 0.9202598135720425], "properties": {}, "label": "Te2-", "xyz": [8.260838339088544, 1.738645130318264, 12.04322667994057]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.6128884079346265, 0.618295165804068, 0.3473039496781265], "properties": {}, "label": "Te2-", "xyz": [8.020728404529816, 8.09148539039937, 4.545086214921316]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.6315797807618615, 0.6225939184343635, 0.8700339946167042], "properties": {}, "label": "Te2-", "xyz": [8.265338064321345, 8.147742168761438, 11.385933039662056]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.12838476282064515, 0.3604322706926303, 0.09620953328154498], "properties": {}, "label": "Te2-", "xyz": [1.6801416057688086, 4.716893506267652, 1.2590718414438578]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.12312332856968954, 0.39317854725891327, 0.6303636878306194], "properties": {}, "label": "Te2-", "xyz": [1.6112864363792987, 5.145436430554434, 8.249423338263739]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.11326489374951178, 0.8943581611601847, 0.14245104779137183], "properties": {}, "label": "Te2-", "xyz": [1.4822713870445106, 11.704257764009949, 1.8642238138025955]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.11393398945863639, 0.8681844242533566, 0.6518017271048436], "properties": {}, "label": "Te2-", "xyz": [1.49102768735962, 11.36172814141725, 8.5299779846204]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.6271436363496584, 0.36670784058284467, 0.10278719284878346], "properties": {}, "label": "Te2-", "xyz": [8.207283271584346, 4.799020433488673, 1.345152146183288]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.6273727892891334, 0.3919108640973764, 0.6439967743041993], "properties": {}, "label": "Te2-", "xyz": [8.210282142939773, 5.1288465551218945, 8.427836378067418]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.6141821228674019, 0.8715100179204547, 0.11272616402540049], "properties": {}, "label": "Te2-", "xyz": [8.037658951713183, 11.405249414200831, 1.4752211561303623]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.6173411386799441, 0.8870697863654669, 0.5681915310744818], "properties": {}, "label": "Te2-", "xyz": [8.079000258760255, 11.608876493974428, 7.435790746736534]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.4075630076145119, 0.10196314968316818, 0.09136490395019445], "properties": {}, "label": "Te2-", "xyz": [5.333682526033351, 1.3343680844528953, 1.1956713013384783]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.3582245405973819, 0.1339467110079552, 0.612957901151216], "properties": {}, "label": "Te2-", "xyz": [4.688001454704518, 1.7529295313241555, 8.021637846132895]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.42420546262325104, 0.6608186394979916, 0.10629187200041514], "properties": {}, "label": "Te2-", "xyz": [5.551478473683157, 8.647980225185853, 1.3910170691551287]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.36063832552584496, 0.6277091821232872, 0.6614751986974577], "properties": {}, "label": "Te2-", "xyz": [4.71959009806521, 8.214684437917207, 8.656572463107532]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.8752520424249852, 0.11394353706788272, 0.09338963038736504], "properties": {}, "label": "Te2-", "xyz": [11.454220420741938, 1.4911526347067814, 1.2221684264851964]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.9060941606888188, 0.10790889932389516, 0.6114096916911319], "properties": {}, "label": "Te2-", "xyz": [11.85784406708929, 1.4121787305871727, 8.00137678811337]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.8886215189674253, 0.6282996437744819, 0.11834167261895481], "properties": {}, "label": "Te2-", "xyz": [11.629183658534293, 8.222411672559288, 1.548710014296214]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.9221889309442108, 0.593362891916048, 0.6250367613866259], "properties": {}, "label": "Te2-", "xyz": [12.068472591434908, 7.765202506314394, 8.17971108773813]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.36916592451403507, 0.3650467855183059, 0.4018612689124704], "properties": {}, "label": "Te2-", "xyz": [4.8311888076206895, 4.777282591223829, 5.259065194443113]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.35783072448043823, 0.3964941555012161, 0.8376090610972016], "properties": {}, "label": "Te2-", "xyz": [4.682847674547415, 5.188827026400339, 10.961595457276902]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.3803128561907226, 0.8926243580437951, 0.3895882828468021], "properties": {}, "label": "Te2-", "xyz": [4.97706611638535, 11.68156788486808, 5.098451473134478]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.3947428748509966, 0.889882206552039, 0.8471153956045201], "properties": {}, "label": "Te2-", "xyz": [5.165908422828027, 11.645681984474614, 11.086002651504574]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.8766076264970295, 0.38772454802481315, 0.38274173563155073], "properties": {}, "label": "Te2-", "xyz": [11.471960634997277, 5.074061207905587, 5.0088522981274375]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.9050213842460662, 0.37764676957276155, 0.8448089700532792], "properties": {}, "label": "Te2-", "xyz": [11.843804890667123, 4.942175659348189, 11.055818995406211]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.8975517329778733, 0.86887470987142, 0.4137527997097622], "properties": {}, "label": "Te2-", "xyz": [11.746051297479376, 11.370761749154584, 5.414686899152125]}, {"species": [{"element": "Te", "oxidation_state": -2, "spin": null, "occu": 1}], "abc": [0.895865344260897, 0.874393191349097, 0.8552705997044722], "properties": {}, "label": "Te2-", "xyz": [11.72398191958249, 11.442980835965239, 11.192727915553295]}], "@version": null}}}}}}} \ No newline at end of file diff --git a/tests/data/vasp/CdTe/vacancies_dist_metadata.json b/tests/data/vasp/CdTe/vacancies_dist_metadata.json index e679fe42..a8cfd303 100644 --- a/tests/data/vasp/CdTe/vacancies_dist_metadata.json +++ b/tests/data/vasp/CdTe/vacancies_dist_metadata.json @@ -1,81 +1 @@ -{ - "distortion_parameters": { - "distortion_increment": null, - "bond_distortions": [ - -0.3 - ], - "local_rattle": false, - "mc_rattle_parameters": { - "seed": 42, - "stdev": 0.25 - } - }, - "defects": { - "v_Cd_Td_Te2.83": { - "unique_site": [ - 0.0, - 0.0, - 0.0 - ], - "charges": { - "0": { - "num_nearest_neighbours": 2, - "distorted_atoms": [ - [ - 33, - "Te" - ], - [ - 42, - "Te" - ] - ], - "distortion_parameters": { - "distortion_increment": null, - "bond_distortions": [ - -0.3 - ], - "local_rattle": false, - "mc_rattle_parameters": { - "seed": 42, - "stdev": 0.25 - } - } - } - } - }, - "v_Te_Td_Cd2.83": { - "unique_site": [ - 0.125, - 0.125, - 0.375 - ], - "charges": { - "0": { - "num_nearest_neighbours": 2, - "distorted_atoms": [ - [ - 2, - "Cd" - ], - [ - 9, - "Cd" - ] - ], - "distortion_parameters": { - "distortion_increment": null, - "bond_distortions": [ - -0.3 - ], - "local_rattle": false, - "mc_rattle_parameters": { - "seed": 42, - "stdev": 0.25 - } - } - } - } - } - } -} +{"distortion_parameters": {"distortion_increment": null, "bond_distortions": [-0.3], "local_rattle": false, "mc_rattle_parameters": {"seed": 42, "stdev": 0.25}}, "defects": {"v_Cd_Td_Te2.83": {"unique_site": [0.0, 0.0, 0.0], "charges": {"0": {"num_nearest_neighbours": 2, "distorted_atoms": [[33, "Te"], [42, "Te"]], "distortion_parameters": {"distortion_increment": null, "bond_distortions": [-0.3, "Dimer"], "local_rattle": false, "mc_rattle_parameters": {"seed": 42, "stdev": 0.25}}}}}, "v_Te_Td_Cd2.83": {"unique_site": [0.125, 0.125, 0.375], "charges": {"0": {"num_nearest_neighbours": 2, "distorted_atoms": [[2, "Cd"], [9, "Cd"]], "distortion_parameters": {"distortion_increment": null, "bond_distortions": [-0.3, "Dimer"], "local_rattle": false, "mc_rattle_parameters": {"seed": 42, "stdev": 0.25}}}}, "defect_site_index": 32}}} \ No newline at end of file diff --git a/tests/test_analysis.py b/tests/test_analysis.py index 95583495..3b446661 100644 --- a/tests/test_analysis.py +++ b/tests/test_analysis.py @@ -59,6 +59,7 @@ def setUp(self): self.V_Cd_unperturbed = Structure.from_file( os.path.join(self.VASP_CDTE_DATA_DIR, "vac_1_Cd_0/Unperturbed/CONTCAR") ) + self.dense_bond_distortions = list(np.around(np.arange(-0.6, 0.001, 0.025), 3)) def tearDown(self): # restore the original file (after 'no unperturbed' tests): @@ -75,6 +76,11 @@ def tearDown(self): if_present_rm(f"{self.VASP_TIO2_DATA_DIR}/Unperturbed/OUTCAR") if_present_rm(f"{self.VASP_TIO2_DATA_DIR}/Bond_Distortion_-40.0%/OUTCAR") if_present_rm("v_Ca_s0_0.png") + if_present_rm(f"{self.VASP_TIO2_DATA_DIR}/Bond_Distortion_20.0%") + + for i in os.listdir(os.path.join(self.VASP_DIR, "v_Ca_s0_0")): + if os.path.isdir(os.path.join(self.VASP_DIR, "v_Ca_s0_0", i)): + shutil.rmtree(os.path.join(self.VASP_DIR, "v_Ca_s0_0", i)) def copy_v_Ti_OUTCARs(self): """ @@ -300,14 +306,14 @@ def test_analyse_defect_site(self): expected_Int_Cd_2_NN_10_crystalNN_coord_df = pd.DataFrame( { "Coordination": { - 0: "hexagonal planar", - 1: "octahedral", - 2: "pentagonal pyramidal", + 0: "trigonal planar", + 1: "trigonal non-coplanar", + 2: "T-shaped", }, "Factor": { - 0: round(0.778391113850372, 2), - 1: round(0.014891251252011014, 2), - 2: round(0.058350214482398306, 2), + 0: 0.81, + 1: 0.59, + 2: 0.06, }, } ) @@ -316,14 +322,11 @@ def test_analyse_defect_site(self): ) expected_Int_Cd_2_NN_10_crystalNN_bonding_df = pd.DataFrame( { - "Element": {0: "Cd", 1: "Cd", 2: "Cd", 3: "Te", 4: "Te", 5: "Te"}, + "Element": {0: "Cd", 1: "Cd", 2: "Cd"}, "Distance (\u212B)": { 0: "1.09", 1: "1.09", 2: "1.09", - 3: "1.09", - 4: "1.09", - 5: "1.09", }, } ) @@ -347,7 +350,7 @@ def test_get_structures(self): defect_species="vac_1_Cd_0", output_path=self.VASP_CDTE_DATA_DIR ) self.assertEqual(len(defect_structures_dict), 26) - bond_distortions = list(np.around(np.arange(-0.6, 0.001, 0.025), 3)) + bond_distortions = self.dense_bond_distortions self.assertEqual( set(defect_structures_dict.keys()), set(bond_distortions + ["Unperturbed"]) ) @@ -408,7 +411,7 @@ def test_get_structures(self): defect_species="vac_1_Cd_0", output_path=self.VASP_CDTE_DATA_DIR ) self.assertEqual(len(defect_structures_dict), 26) - bond_distortions = list(np.around(np.arange(-0.6, 0.001, 0.025), 3)) + bond_distortions = self.dense_bond_distortions self.assertEqual( set(defect_structures_dict.keys()), set(bond_distortions + ["Unperturbed"]) ) @@ -433,7 +436,7 @@ def test_get_energies(self): ) energies_dict_keys_dict = {"distortions": None, "Unperturbed": None} self.assertEqual(defect_energies_dict.keys(), energies_dict_keys_dict.keys()) - bond_distortions = list(np.around(np.arange(-0.6, 0.001, 0.025), 3)) + bond_distortions = self.dense_bond_distortions self.assertEqual( list(defect_energies_dict["distortions"].keys()), bond_distortions ) @@ -459,7 +462,7 @@ def test_get_energies(self): self.assertEqual( defect_energies_dict.keys(), energies_dict_keys_dict.keys() ) - bond_distortions = list(np.around(np.arange(-0.6, 0.001, 0.025), 3)) + bond_distortions = self.dense_bond_distortions self.assertEqual( list(defect_energies_dict["distortions"].keys()), bond_distortions ) @@ -516,7 +519,7 @@ def test_get_energies(self): self.assertEqual( defect_energies_dict.keys(), energies_dict_keys_dict.keys() ) - bond_distortions = list(np.around(np.arange(-0.6, 0.001, 0.025), 3)) + bond_distortions = self.dense_bond_distortions self.assertEqual( list(defect_energies_dict["distortions"].keys()), bond_distortions ) @@ -551,11 +554,9 @@ def test_calculate_struct_comparison(self): self.assertEqual( max_dist_dict.keys(), defect_structures_dict.keys() ) # one for each - np.testing.assert_almost_equal(max_dist_dict[-0.4], 0.8082011457587672) - np.testing.assert_almost_equal(max_dist_dict[-0.2], 0.02518600797944396) - np.testing.assert_almost_equal( - max_dist_dict["Unperturbed"], 1.7500286730158273e-15 - ) + assert np.isclose(max_dist_dict[-0.4], 0.8082011457587672, atol=1e-2) + assert np.isclose(max_dist_dict[-0.2], 0.024, atol=1e-2) + assert np.isclose(max_dist_dict["Unperturbed"], 0) # V_Cd_0 with 'disp' (reading from `vac_1_Cd_0` and `distortion_metadata.json`): disp_dict = analysis.calculate_struct_comparison(defect_structures_dict, "disp") @@ -604,7 +605,6 @@ def test_calculate_struct_comparison(self): ) # spot check: self.assertEqual(round(max_dist_dict[-0.2], 3), 0.025) - self.assertIsNone(max_dist_dict[-0.4]) np.testing.assert_almost_equal(max_dist_dict["Unperturbed"], 0) disp_dict = analysis.calculate_struct_comparison( @@ -612,7 +612,6 @@ def test_calculate_struct_comparison(self): ) # spot check: self.assertEqual(round(disp_dict[-0.2], 3), 0.121) - self.assertIsNone(disp_dict[-0.4]) self.assertTrue(np.isclose(disp_dict["Unperturbed"], 0)) # test error catching: @@ -650,7 +649,7 @@ def test_calculate_struct_comparison(self): with self.assertRaises(ValueError) as e: wrong_metric_error = ValueError( - f"Invalid metric 'metwhat'. Must be one of 'disp' or 'max_dist'." + "Invalid metric 'metwhat'. Must be one of 'disp' or 'max_dist'." ) # https://youtu.be/DmH1prySUpA analysis.calculate_struct_comparison( defect_structures_dict, metric="metwhat" @@ -688,7 +687,7 @@ def test_compare_structures(self): ) # spot check: self.assertEqual( - struct_comparison_df.iloc[16].to_list(), [-0.2, 0.0, 0.025, 0.0] + struct_comparison_df.iloc[16].to_list(), [-0.2, 0.0, 0.024, 0.0] ) self.assertEqual( struct_comparison_df.iloc[8].to_list(), [-0.4, 5.760, 0.808, -0.75] @@ -750,16 +749,6 @@ def test_compare_structures(self): self.assertEqual( struct_comparison_df.iloc[16].to_list(), [-0.2, 0.121, 0.025, 0.0] ) - # When a too tight stol is used, check the code retries with larger stol - warning_message = ( - f"The specified tolerance {0.01} seems to be too tight as" - " too many lattices could not be matched. Will retry with" - f" larger tolerance ({0.01+0.4})." - ) - # self.assertEqual(w[-1].category, UserWarning) - self.assertTrue( - any([warning_message in str(warning.message) for warning in w]) - ) self.assertEqual( struct_comparison_df.iloc[8].to_list(), [-0.4, 8.31, 0.808, -0.75] ) @@ -773,7 +762,7 @@ def test_compare_structures(self): "Bond Distortion", "\u03A3{Displacements} (\u212B)", # Sigma and Angstrom "Max Distance (\u212B)", # Angstrom - f"\u0394 Energy (meV)", # Delta + "\u0394 Energy (meV)", # Delta ], ) @@ -1121,7 +1110,7 @@ def test_get_site_magnetizations(self): with open(f"{self.DATA_DIR}/vasp/vac_1_Ti_0/Bond_Distortion_20.0%/OUTCAR") as f: outcar_string = f.read() ispin1_outcar_string = re.sub( - "ISPIN = 2 spin polarized calculation?", "ISPIN = 1", outcar_string + r"ISPIN\s*=\s*2", "ISPIN = 1", outcar_string ) with open( f"{self.DATA_DIR}/vasp/vac_1_Ti_0/Bond_Distortion_20.0%/OUTCAR", "w" @@ -1140,8 +1129,7 @@ def test_get_site_magnetizations(self): self.assertTrue( any( f"{self.DATA_DIR}/vasp/vac_1_Ti_0/Bond_Distortion_20.0%/OUTCAR is " - f"from a " - "non-spin-polarised calculation (ISPIN = 1), so magnetization analysis " + f"from a non-spin-polarised calculation (ISPIN = 1), so magnetization analysis " "is not possible. Skipping." in str(warning.message) for warning in w ) diff --git a/tests/test_cli.py b/tests/test_cli.py index df7f78af..1c2787dd 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -74,9 +74,7 @@ def setUp(self): "CdTe_V_Cd_-50%_Distortion_local_rattle_POSCAR", ) ) # Local rattled - self.CdTe_distortion_config = os.path.join( - self.VASP_CDTE_DATA_DIR, "distortion_config.yml" - ) + self.CdTe_distortion_config = os.path.join(self.VASP_CDTE_DATA_DIR, "distortion_config.yml") self.V_Cd_minus0pt5_struc_kwarged = Structure.from_file( os.path.join(self.VASP_CDTE_DATA_DIR, "CdTe_V_Cd_-50%_Kwarged_POSCAR") ) @@ -91,9 +89,7 @@ def setUp(self): ) self.Int_Cd_2_dict = self.cdte_doped_defect_dict["interstitials"][1] self.Int_Cd_2_minus0pt6_struc_rattled = Structure.from_file( - os.path.join( - self.VASP_CDTE_DATA_DIR, "CdTe_Int_Cd_2_-60%_Distortion_Rattled_POSCAR" - ) + os.path.join(self.VASP_CDTE_DATA_DIR, "CdTe_Int_Cd_2_-60%_Distortion_Rattled_POSCAR") ) previous_default_rattle_settings = {"stdev": 0.25, "seed": 42} with open("previous_default_rattle_settings.yaml", "w") as fp: @@ -105,9 +101,7 @@ def setUp(self): warnings.filterwarnings("ignore", category=UnknownPotcarWarning) # get example INCAR: - self.V_Cd_INCAR_file = os.path.join( - self.VASP_CDTE_DATA_DIR, "vac_1_Cd_0/default_INCAR" - ) + self.V_Cd_INCAR_file = os.path.join(self.VASP_CDTE_DATA_DIR, "vac_1_Cd_0/default_INCAR") self.V_Cd_INCAR = Incar.from_file(self.V_Cd_INCAR_file) def tearDown(self): @@ -148,7 +142,7 @@ def tearDown(self): [ shutil.rmtree(f"{self.EXAMPLE_RESULTS}/{defect}/{dir}") for dir in os.listdir(f"{self.EXAMPLE_RESULTS}/{defect}") - if "_from_" in dir + if "_from_" in dir or "_residual_" in dir ] [ os.remove(f"{self.EXAMPLE_RESULTS}/{defect}/{file}") @@ -157,6 +151,7 @@ def tearDown(self): ] elif os.path.isfile(f"{self.EXAMPLE_RESULTS}/{defect}"): os.remove(f"{self.EXAMPLE_RESULTS}/{defect}") + if_present_rm(f"{self.EXAMPLE_RESULTS}/{defect}/{defect}.csv") for i in os.listdir(f"{self.EXAMPLE_RESULTS}"): if any( @@ -176,30 +171,16 @@ def tearDown(self): # Remove re-generated files folder = "Bond_Distortion_-60.0%_from_0" for charge in [-1, -2]: - if os.path.exists( - os.path.join(self.EXAMPLE_RESULTS, f"v_Cd_{charge}", folder) - ): - shutil.rmtree( - os.path.join(self.EXAMPLE_RESULTS, f"v_Cd_{charge}", folder) - ) + if os.path.exists(os.path.join(self.EXAMPLE_RESULTS, f"v_Cd_{charge}", folder)): + shutil.rmtree(os.path.join(self.EXAMPLE_RESULTS, f"v_Cd_{charge}", folder)) folder = "Bond_Distortion_20.0%_from_-1" for charge in [0, -2]: - if os.path.exists( - os.path.join(self.EXAMPLE_RESULTS, f"v_Cd_{charge}", folder) - ): - shutil.rmtree( - os.path.join(self.EXAMPLE_RESULTS, f"v_Cd_{charge}", folder) - ) + if os.path.exists(os.path.join(self.EXAMPLE_RESULTS, f"v_Cd_{charge}", folder)): + shutil.rmtree(os.path.join(self.EXAMPLE_RESULTS, f"v_Cd_{charge}", folder)) if_present_rm( - os.path.join( - self.VASP_CDTE_DATA_DIR, "vac_1_Cd_0/Bond_Distortion_-48.0%_High_Energy" - ) - ) - if_present_rm( - os.path.join( - self.EXAMPLE_RESULTS, "vac_1_Cd_0/Bond_Distortion_-48.0%_High_Energy" - ) + os.path.join(self.VASP_CDTE_DATA_DIR, "vac_1_Cd_0/Bond_Distortion_-48.0%_High_Energy") ) + if_present_rm(os.path.join(self.EXAMPLE_RESULTS, "vac_1_Cd_0/Bond_Distortion_-48.0%_High_Energy")) if_present_rm("Rattled_Bulk_CdTe_POSCAR") # Remove parsed vac_1_Ti_0 energies file @@ -224,6 +205,22 @@ def tearDown(self): for i in ["castep", "cp2k", "fhi_aims", "quantum_espresso"]: if_present_rm(f"{self.DATA_DIR}/{i}/vac_1_Cd_0/default_INCAR") + v_Ti_0_distortion_10pct_dir = os.path.join(self.VASP_TIO2_DATA_DIR, "Bond_Distortion_10.0%") + if os.path.exists(f"{v_Ti_0_distortion_10pct_dir}_High_Energy"): + os.rename(f"{v_Ti_0_distortion_10pct_dir}_High_Energy", v_Ti_0_distortion_10pct_dir) + + if_present_rm(f"{self.VASP_DIR}/vac_1_Ti_1") + if os.path.exists(f"{self.VASP_DIR}/v_Ge_s16_0/Bond_Distortion_-60.0%/prev_otcr"): + shutil.move( + f"{self.VASP_DIR}/v_Ge_s16_0/Bond_Distortion_-60.0%/prev_otcr", + f"{self.VASP_DIR}/v_Ge_s16_0/Bond_Distortion_-60.0%/OUTCAR", + ) + if os.path.exists(f"{self.VASP_DIR}/v_Ge_s16_0/Bond_Distortion_50.0%_High_Energy"): + os.rename( + f"{self.VASP_DIR}/v_Ge_s16_0/Bond_Distortion_50.0%_High_Energy", + f"{self.VASP_DIR}/v_Ge_s16_0/Bond_Distortion_50.0%", + ) + def copy_v_Ti_OUTCARs(self): """ Copy the OUTCAR files from the `v_Ti_0` `example_results` directory to the `vac_1_Ti_0` `vasp` @@ -261,9 +258,7 @@ def test_snb_generate(self): catch_exceptions=False, ) print([str(warning.message) for warning in w]) # for debugging - non_potcar_warnings = [ - warning for warning in w if "POTCAR" not in str(warning.message) - ] + non_potcar_warnings = [warning for warning in w if "POTCAR" not in str(warning.message)] assert not non_potcar_warnings # no warnings other than POTCAR warnings self.assertEqual(result.exit_code, 0) self.assertIn( @@ -308,9 +303,7 @@ def test_snb_generate(self): # check if correct files were created: V_Cd_Bond_Distortion_folder = f"{defect_name}_0/Bond_Distortion_-50.0%" self.assertTrue(os.path.exists(V_Cd_Bond_Distortion_folder)) - V_Cd_minus0pt5_rattled_POSCAR = Poscar.from_file( - f"{V_Cd_Bond_Distortion_folder}/POSCAR" - ) + V_Cd_minus0pt5_rattled_POSCAR = Poscar.from_file(f"{V_Cd_Bond_Distortion_folder}/POSCAR") self.assertEqual( V_Cd_minus0pt5_rattled_POSCAR.comment, "-50.0% N(Distort)=2 ~[0.0,0.0,0.0]", @@ -332,9 +325,7 @@ def test_snb_generate(self): assert set(potcar.as_dict()["symbols"]) == {"Cd", "Te"} # Test recognises distortion_metadata.json: - if_present_rm( - f"{defect_name}_0" - ) # but distortion_metadata.json still present, but different + if_present_rm(f"{defect_name}_0") # but distortion_metadata.json still present, but different with warnings.catch_warnings(record=True) as w: result = runner.invoke( snb, @@ -350,7 +341,7 @@ def test_snb_generate(self): catch_exceptions=False, ) # non-verbose this time self.assertEqual(result.exit_code, 0) - self.assertNotIn( + self.assertIn( # printed with medium-level verbosity "Auto site-matching identified " f"{self.VASP_CDTE_DATA_DIR}/CdTe_V_Cd_POSCAR " f"to be type Vacancy with site Cd at [0.000, 0.000, 0.000]", @@ -391,15 +382,10 @@ def test_snb_generate(self): # skipping minutes comparison in case this changes between test and check result.output, ) - self.assertIn( - "Combining old and new metadata in distortion_metadata.json", result.output - ) + self.assertIn("Combining old and new metadata in distortion_metadata.json", result.output) self.assertTrue(os.path.exists("distortion_metadata.json")) self.assertTrue( - any( - f"distortion_metadata_{current_datetime_wo_minutes}" in i - for i in os.listdir(".") - ) + any(f"distortion_metadata_{current_datetime_wo_minutes}" in i for i in os.listdir(".")) ) self.assertIn( "Previous and new metadata show different distortion parameters for v_Cd_Td_Te2.83 in " @@ -410,9 +396,7 @@ def test_snb_generate(self): new_distortion_metadata = loadfn("distortion_metadata.json") self.assertTrue( np.isclose( - new_distortion_metadata["distortion_parameters"][ - "mc_rattle_parameters" - ]["stdev"], + new_distortion_metadata["distortion_parameters"]["mc_rattle_parameters"]["stdev"], 0.28333683853583164, ) ) @@ -441,13 +425,9 @@ def test_snb_generate(self): catch_exceptions=False, ) print([str(warning.message) for warning in w]) # for debugging - non_potcar_warnings = [ - warning for warning in w if "POTCAR" not in str(warning.message) - ] + non_potcar_warnings = [warning for warning in w if "POTCAR" not in str(warning.message)] assert len(non_potcar_warnings) == 1 # only overwriting structures warning - assert "has the same Unperturbed defect structure" in str( - non_potcar_warnings[0].message - ) + assert "has the same Unperturbed defect structure" in str(non_potcar_warnings[0].message) self.assertEqual(result.exit_code, 0) self.assertIn(f"Defect: {defect_name}", result.output) self.assertIn("Number of missing electrons in neutral state: 2", result.output) @@ -463,23 +443,17 @@ def test_snb_generate(self): # test distortion_metadata file: reloaded_distortion_metadata = loadfn("distortion_metadata.json") self.assertEqual(len(reloaded_distortion_metadata["defects"]), 1) - self.assertEqual( - len(reloaded_distortion_metadata["defects"]["v_Cd_Td_Te2.83"]["charges"]), 1 - ) + self.assertEqual(len(reloaded_distortion_metadata["defects"]["v_Cd_Td_Te2.83"]["charges"]), 1) self.assertEqual( len( - reloaded_distortion_metadata["defects"]["v_Cd_Td_Te2.83"]["charges"][ - "0" - ]["distortion_parameters"]["bond_distortions"] + reloaded_distortion_metadata["defects"]["v_Cd_Td_Te2.83"]["charges"]["0"][ + "distortion_parameters" + ]["bond_distortions"] ), - 13, + 14, ) # no duplication of bond distortions - defect_folder_distortion_metadata = loadfn( - "v_Cd_Td_Te2.83_0/distortion_metadata.json" - ) - self.assertDictEqual( - reloaded_distortion_metadata, defect_folder_distortion_metadata - ) + defect_folder_distortion_metadata = loadfn("v_Cd_Td_Te2.83_0/distortion_metadata.json") + self.assertDictEqual(reloaded_distortion_metadata, defect_folder_distortion_metadata) # test rerunning with same settings but with more charge states, so previous distortion_metadata # is a subset of current one, so no warnings / info messages (about distortion_metadata): @@ -496,13 +470,9 @@ def test_snb_generate(self): catch_exceptions=False, ) print([str(warning.message) for warning in w]) # for debugging - non_potcar_warnings = [ - warning for warning in w if "POTCAR" not in str(warning.message) - ] + non_potcar_warnings = [warning for warning in w if "POTCAR" not in str(warning.message)] assert len(non_potcar_warnings) == 1 # only overwriting structures warning - assert "has the same Unperturbed defect structure" in str( - non_potcar_warnings[0].message - ) + assert "has the same Unperturbed defect structure" in str(non_potcar_warnings[0].message) self.assertEqual(result.exit_code, 0) self.assertIn(f"Defect: {defect_name}", result.output) self.assertIn("Number of missing electrons in neutral state: 2", result.output) @@ -524,23 +494,17 @@ def test_snb_generate(self): ) self.assertEqual( len( - reloaded_distortion_metadata["defects"]["v_Cd_Td_Te2.83"]["charges"][ - "0" - ]["distortion_parameters"]["bond_distortions"] + reloaded_distortion_metadata["defects"]["v_Cd_Td_Te2.83"]["charges"]["0"][ + "distortion_parameters" + ]["bond_distortions"] ), - 13, + 14, ) # no duplication of bond distortions - defect_folder_distortion_metadata = loadfn( - "v_Cd_Td_Te2.83_0/distortion_metadata.json" - ) - self.assertNotEqual( - reloaded_distortion_metadata, defect_folder_distortion_metadata - ) + defect_folder_distortion_metadata = loadfn("v_Cd_Td_Te2.83_0/distortion_metadata.json") + self.assertNotEqual(reloaded_distortion_metadata, defect_folder_distortion_metadata) self.assertDictEqual( reloaded_distortion_metadata["defects"]["v_Cd_Td_Te2.83"]["charges"]["0"], - defect_folder_distortion_metadata["defects"]["v_Cd_Td_Te2.83"]["charges"][ - "0" - ], + defect_folder_distortion_metadata["defects"]["v_Cd_Td_Te2.83"]["charges"]["0"], ) # test rerunning with same settings but with less charge states, so new distortion_metadata @@ -560,13 +524,9 @@ def test_snb_generate(self): catch_exceptions=False, ) print([str(warning.message) for warning in w]) # for debugging - non_potcar_warnings = [ - warning for warning in w if "POTCAR" not in str(warning.message) - ] + non_potcar_warnings = [warning for warning in w if "POTCAR" not in str(warning.message)] assert len(non_potcar_warnings) == 1 # only overwriting structures warning - assert "has the same Unperturbed defect structure" in str( - non_potcar_warnings[0].message - ) + assert "has the same Unperturbed defect structure" in str(non_potcar_warnings[0].message) self.assertEqual(result.exit_code, 0) self.assertIn(f"Defect: {defect_name}", result.output) self.assertIn("Number of missing electrons in neutral state: 2", result.output) @@ -587,15 +547,13 @@ def test_snb_generate(self): ) # combined metadata so still has 5 charges self.assertEqual( len( - reloaded_distortion_metadata["defects"]["v_Cd_Td_Te2.83"]["charges"][ - "0" - ]["distortion_parameters"]["bond_distortions"] + reloaded_distortion_metadata["defects"]["v_Cd_Td_Te2.83"]["charges"]["0"][ + "distortion_parameters" + ]["bond_distortions"] ), - 13, + 14, ) # no duplication of bond distortions - defect_folder_distortion_metadata = loadfn( - "v_Cd_Td_Te2.83_0/distortion_metadata.json" - ) + defect_folder_distortion_metadata = loadfn("v_Cd_Td_Te2.83_0/distortion_metadata.json") self.assertNotEqual( # combined metadata so has 5 charges, rather than just neutral reloaded_distortion_metadata, defect_folder_distortion_metadata ) @@ -620,13 +578,9 @@ def test_snb_generate(self): catch_exceptions=False, ) print([str(warning.message) for warning in w]) # for debugging - non_potcar_warnings = [ - warning for warning in w if "POTCAR" not in str(warning.message) - ] + non_potcar_warnings = [warning for warning in w if "POTCAR" not in str(warning.message)] assert len(non_potcar_warnings) == 1 # only overwriting structures warning - assert "has the same Unperturbed defect structure" in str( - non_potcar_warnings[0].message - ) + assert "has the same Unperturbed defect structure" in str(non_potcar_warnings[0].message) self.assertEqual(result.exit_code, 0) self.assertIn(f"Defect: {defect_name}", result.output) self.assertIn("Number of missing electrons in neutral state: 2", result.output) @@ -642,23 +596,17 @@ def test_snb_generate(self): # test distortion_metadata file: reloaded_distortion_metadata = loadfn("distortion_metadata.json") self.assertEqual(len(reloaded_distortion_metadata["defects"]), 1) - self.assertEqual( - len(reloaded_distortion_metadata["defects"]["v_Cd_Td_Te2.83"]["charges"]), 5 - ) + self.assertEqual(len(reloaded_distortion_metadata["defects"]["v_Cd_Td_Te2.83"]["charges"]), 5) self.assertEqual( len( - reloaded_distortion_metadata["defects"]["v_Cd_Td_Te2.83"]["charges"][ - "0" - ]["distortion_parameters"]["bond_distortions"] + reloaded_distortion_metadata["defects"]["v_Cd_Td_Te2.83"]["charges"]["0"][ + "distortion_parameters" + ]["bond_distortions"] ), - 25, + 26, ) # no duplication of bond distortions - defect_folder_distortion_metadata = loadfn( - "v_Cd_Td_Te2.83_0/distortion_metadata.json" - ) - self.assertNotEqual( - reloaded_distortion_metadata, defect_folder_distortion_metadata - ) + defect_folder_distortion_metadata = loadfn("v_Cd_Td_Te2.83_0/distortion_metadata.json") + self.assertNotEqual(reloaded_distortion_metadata, defect_folder_distortion_metadata) # test defect_index option: self.tearDown() @@ -735,6 +683,7 @@ def test_snb_generate(self): 0.4, 0.5, 0.6, + "Dimer", ], "local_rattle": False, "mc_rattle_parameters": { @@ -743,12 +692,13 @@ def test_snb_generate(self): }, }, }, - } + 'defect_site_index': 4,} }, } # check defects from old metadata file are in new metadata file with open("distortion_metadata.json", "r") as metadata_file: metadata = json.load(metadata_file) + print(metadata, wrong_site_V_Cd_dict) # for debugging np.testing.assert_equal(metadata, wrong_site_V_Cd_dict) # test warning with defect_coords option but wrong site: (matches Cd site in bulk) @@ -821,12 +771,8 @@ def test_snb_generate(self): self.assertEqual(result.exit_code, 0) if w: # Check no problems in identifying the defect site - self.assertFalse( - any(str(warning.message) == warning_message for warning in w) - ) - self.assertFalse( - any("Coordinates" in str(warning.message) for warning in w) - ) + self.assertFalse(any(str(warning.message) == warning_message for warning in w)) + self.assertFalse(any("Coordinates" in str(warning.message) for warning in w)) self.assertNotIn("Auto site-matching", result.output) self.assertIn("--Distortion -60.0%", result.output) @@ -845,9 +791,7 @@ def test_snb_generate(self): # with rattled bulk self.tearDown() with warnings.catch_warnings(record=True) as w: - rattled_bulk = rattle( - self.CdTe_bulk_struc, stdev=0.25, d_min=2.25 - ) # previous default + rattled_bulk = rattle(self.CdTe_bulk_struc, stdev=0.25, d_min=2.25) # previous default rattled_bulk.to(filename="./Rattled_Bulk_CdTe_POSCAR", fmt="POSCAR") result = runner.invoke( snb, @@ -874,9 +818,7 @@ def test_snb_generate(self): # Bond_Distortion_-60.0% for defect Int_Cd_mult1 gives an interatomic # distance less than 1.0 Å (1.0 Å), which is likely to give explosive forces. # Omitting this distortion. - self.assertFalse( - any("Coordinates" in str(warning.message) for warning in w) - ) + self.assertFalse(any("Coordinates" in str(warning.message) for warning in w)) self.assertNotIn("Auto site-matching", result.output) self.assertIn("--Distortion -60.0%", result.output) @@ -891,9 +833,7 @@ def test_snb_generate(self): # test defect_coords working even when slightly off correct site with V_Cd and rattled bulk self.tearDown() with warnings.catch_warnings(record=True) as w: - rattled_bulk = rattle( - self.CdTe_bulk_struc, stdev=0.25, d_min=2.25 - ) # previous default + rattled_bulk = rattle(self.CdTe_bulk_struc, stdev=0.25, d_min=2.25) # previous default rattled_bulk.to(filename="./Rattled_Bulk_CdTe_POSCAR", fmt="POSCAR") result = runner.invoke( snb, @@ -1012,6 +952,7 @@ def test_snb_generate(self): 0.4, 0.5, 0.6, + "Dimer", ], "local_rattle": False, "mc_rattle_parameters": {"stdev": 0.28333683853583164}, @@ -1146,6 +1087,30 @@ def test_snb_generate(self): self.assertFalse(os.path.exists(f"{defect_name}_+5")) self.assertFalse(os.path.exists(f"{defect_name}_-7")) + # test no printed output when verbose=False (need to provide oxidation states for no output) + test_yml = """oxidation_states: + Cd: 2 + Te: -2 + """ + with open("test_config.yml", "w+") as fp: + fp.write(test_yml) + result = runner.invoke( + snb, + [ + "generate", + "-d", + f"{self.VASP_CDTE_DATA_DIR}/CdTe_V_Cd_POSCAR", + "-b", + f"{self.VASP_CDTE_DATA_DIR}/CdTe_Bulk_Supercell_POSCAR", + "-nv", # non-verbose + "--config", + "test_config.yml", # to give oxi states for no output + ], + catch_exceptions=False, + ) + print(result.output) # for debugging + assert not result.output # no printed output + def test_snb_generate_config(self): # test config file: test_yml = """ @@ -1183,18 +1148,14 @@ def test_snb_generate_config(self): ) defect_name = "v_Cd_Td_Te2.83" self.assertEqual(result.exit_code, 0) - V_Cd_kwarged_POSCAR = Poscar.from_file( - f"{defect_name}_0/Bond_Distortion_-50.0%/POSCAR" - ) - self.assertEqual( - V_Cd_kwarged_POSCAR.structure, self.V_Cd_minus0pt5_struc_kwarged - ) + V_Cd_kwarged_POSCAR = Poscar.from_file(f"{defect_name}_0/Bond_Distortion_-50.0%/POSCAR") + self.assertEqual(V_Cd_kwarged_POSCAR.structure, self.V_Cd_minus0pt5_struc_kwarged) kpoints = Kpoints.from_file(f"{defect_name}_0/Bond_Distortion_-50.0%/KPOINTS") self.assertEqual(kpoints.kpts, [(1, 1, 1)]) if _potcars_available(): - assert filecmp.cmp( - f"{defect_name}_0/Bond_Distortion_-50.0%/INCAR", self.V_Cd_INCAR_file + assert Incar.from_file(f"{defect_name}_0/Bond_Distortion_-50.0%/INCAR") == Incar.from_file( + self.V_Cd_INCAR_file ) # check if POTCARs have been written: @@ -1222,7 +1183,7 @@ def test_snb_generate_config(self): ], ) self.assertEqual(result.exit_code, 0) - self.assertNotIn("Auto site-matching identified", result.output) + self.assertIn("Auto site-matching identified", result.output) # medium verbosity self.assertNotIn("Oxidation states were not explicitly set", result.output) self.assertIn( "Applying ShakeNBreak... Will apply the following bond distortions: [" @@ -1237,12 +1198,8 @@ def test_snb_generate_config(self): f"Defect {defect_name} in charge state: 0. Number of distorted neighbours: 3", result.output, ) - V_Cd_ox3_POSCAR = Poscar.from_file( - f"{defect_name}_0/Bond_Distortion_-50.0%/POSCAR" - ) - self.assertNotEqual( - V_Cd_ox3_POSCAR.structure, self.V_Cd_minus0pt5_struc_local_rattled - ) + V_Cd_ox3_POSCAR = Poscar.from_file(f"{defect_name}_0/Bond_Distortion_-50.0%/POSCAR") + self.assertNotEqual(V_Cd_ox3_POSCAR.structure, self.V_Cd_minus0pt5_struc_local_rattled) self.tearDown() test_yml = """ @@ -1275,7 +1232,7 @@ def test_snb_generate_config(self): ) defect_name = "Int_Cd_2" self.assertEqual(result.exit_code, 0) - self.assertNotIn("Auto site-matching identified", result.output) + self.assertIn("Auto site-matching identified", result.output) # medium verbosity self.assertIn("Oxidation states were not explicitly set", result.output) self.assertIn( "Applying ShakeNBreak... Will apply the following bond distortions: ['-0.5', " @@ -1363,7 +1320,7 @@ def test_snb_generate_config(self): ], ) self.assertEqual(result.exit_code, 0) - self.assertNotIn("Auto site-matching identified", result.output) + self.assertIn("Auto site-matching identified", result.output) # medium verbosity self.assertIn("Oxidation states were not explicitly set", result.output) self.assertIn( "Applying ShakeNBreak... Will apply the following bond distortions: ['-0.5', '-0.25', " @@ -1380,9 +1337,7 @@ def test_snb_generate_config(self): V_Cd_Bond_Distortion_folder = "Wally_McDoodle_0/Bond_Distortion_-50.0%" self.assertTrue(os.path.exists(V_Cd_Bond_Distortion_folder)) self.assertTrue(os.path.exists("Wally_McDoodle_0/Unperturbed")) - V_Cd_minus0pt5_rattled_POSCAR = Poscar.from_file( - V_Cd_Bond_Distortion_folder + "/POSCAR" - ) + V_Cd_minus0pt5_rattled_POSCAR = Poscar.from_file(V_Cd_Bond_Distortion_folder + "/POSCAR") self.assertEqual( V_Cd_minus0pt5_rattled_POSCAR.comment, "-50.0% N(Distort)=2 ~[0.0,0.0,0.0]", @@ -1613,17 +1568,13 @@ def test_snb_generate_all(self): + " Distorted Neighbour Distances:\n\t[(3.68, 33, 'Te'), (3.68, 42, 'Te'), (3.68, 52, 'Te')]", result.output, ) - self.assertNotIn( - f"Defect {defect_name} in charge state: +2.", result.output - ) # old default + self.assertNotIn(f"Defect {defect_name} in charge state: +2.", result.output) # old default for charge in [ 1, ] + list(range(-1, 2)): for dist in ["Unperturbed", "Bond_Distortion_30.0%"]: self.assertTrue( - os.path.exists( - f"{defect_name}_{'+' if charge > 0 else ''}{charge}/{dist}/POSCAR" - ) + os.path.exists(f"{defect_name}_{'+' if charge > 0 else ''}{charge}/{dist}/POSCAR") ) for dist in ["Unperturbed", "Rattled"]: # -2 has 0 electron change -> only Unperturbed & rattled folders @@ -1947,6 +1898,7 @@ def test_snb_generate_all(self): "4", ], ) + print(result.output) # check print info message: self.assertIn( "Defect charge states will be set to the range: 0 - {Defect oxidation " @@ -1970,9 +1922,7 @@ def test_run(self): """Test snb-run function""" os.chdir(self.VASP_TIO2_DATA_DIR) self.copy_v_Ti_OUTCARs() - proc = subprocess.Popen( - ["snb-run", "-v"], stdout=subprocess.PIPE, stderr=subprocess.PIPE - ) + proc = subprocess.Popen(["snb-run", "-v"], stdout=subprocess.PIPE, stderr=subprocess.PIPE) out = str(proc.communicate()[0]) self.assertIn( "Job file 'job' not found, so will only save files and submit jobs in folders with 'job' " @@ -2000,9 +1950,7 @@ def test_run(self): ) self.assertIn("Bond_Distortion_-40.0% fully relaxed", out) self.assertIn("Unperturbed fully relaxed", out) - self.assertNotIn( - "Bond_Distortion_10.0% fully relaxed", out - ) # also present but no OUTCAR + self.assertNotIn("Bond_Distortion_10.0% fully relaxed", out) # also present but no OUTCAR self.assertIn("Running job for Bond_Distortion_10.0%", out) self.assertIn("this vac_1_Ti_0_10.0% job_file", out) # job submit command self.assertTrue(os.path.exists("Bond_Distortion_10.0%/job_file")) @@ -2026,9 +1974,7 @@ def test_run(self): ) self.assertIn("Bond_Distortion_-40.0% fully relaxed", out) self.assertIn("Unperturbed fully relaxed", out) - self.assertNotIn( - "Bond_Distortion_10.0% fully relaxed", out - ) # also present but no OUTCAR + self.assertNotIn("Bond_Distortion_10.0% fully relaxed", out) # also present but no OUTCAR self.assertIn("Running job for Bond_Distortion_10.0%", out) self.assertIn("this vac_1_Ti_0_10.0% job_file", out) # job submit command self.assertTrue(os.path.exists("Bond_Distortion_10.0%/job_file")) @@ -2038,49 +1984,31 @@ def test_run(self): # test save_vasp_files: car_files = [ - file - for file in os.listdir("Bond_Distortion_10.0%") - if "CAR_" in file and "on" in file + file for file in os.listdir("Bond_Distortion_10.0%") if "CAR_" in file and "on" in file ] for file in car_files: # remove os.remove(f"Bond_Distortion_10.0%/{file}") with open("Bond_Distortion_10.0%/OUTCAR", "w") as fp: fp.write("Test pop") - proc = subprocess.Popen( - ["snb-run", "-v"], stdout=subprocess.PIPE, stderr=subprocess.PIPE - ) + proc = subprocess.Popen(["snb-run", "-v"], stdout=subprocess.PIPE, stderr=subprocess.PIPE) out = str(proc.communicate()[0]) - self.assertIn( - "Bond_Distortion_10.0% not (fully) relaxed, saving files and rerunning", out - ) + self.assertIn("Bond_Distortion_10.0% not (fully) relaxed, saving files and rerunning", out) saved_files = [ - file - for file in os.listdir("Bond_Distortion_10.0%") - if "on" in file and "CAR_" in file + file for file in os.listdir("Bond_Distortion_10.0%") if "on" in file and "CAR_" in file ] # contcar and outcar - self.assertEqual( - len(saved_files), 0 - ) # no saved files, because submit command will fail + self.assertEqual(len(saved_files), 0) # no saved files, because submit command will fail # with job file present, but failing job submit command (no file saving) with open("job", "w") as fp: fp.write("Test pop") - proc = subprocess.Popen( - ["snb-run", "-v"], stdout=subprocess.PIPE, stderr=subprocess.PIPE - ) + proc = subprocess.Popen(["snb-run", "-v"], stdout=subprocess.PIPE, stderr=subprocess.PIPE) out = str(proc.communicate()[0]) - self.assertIn( - "Bond_Distortion_10.0% not (fully) relaxed, saving files and rerunning", out - ) + self.assertIn("Bond_Distortion_10.0% not (fully) relaxed, saving files and rerunning", out) saved_files = [ - file - for file in os.listdir("Bond_Distortion_10.0%") - if "on" in file and "CAR_" in file + file for file in os.listdir("Bond_Distortion_10.0%") if "on" in file and "CAR_" in file ] - self.assertEqual( - len(saved_files), 0 - ) # no saved files, because submit command fails + self.assertEqual(len(saved_files), 0) # no saved files, because submit command fails # with working (fake) job submit command: proc = subprocess.Popen( @@ -2089,13 +2017,9 @@ def test_run(self): stderr=subprocess.PIPE, ) out = str(proc.communicate()[0]) - self.assertIn( - "Bond_Distortion_10.0% not (fully) relaxed, saving files and rerunning", out - ) + self.assertIn("Bond_Distortion_10.0% not (fully) relaxed, saving files and rerunning", out) saved_files = [ - file - for file in os.listdir("Bond_Distortion_10.0%") - if "on" in file and "CAR_" in file + file for file in os.listdir("Bond_Distortion_10.0%") if "on" in file and "CAR_" in file ] # poscar, contcar and outcar self.assertEqual(len(saved_files), 3) # POSCAR, CONTCAR, OUTCAR self.assertEqual(len([i for i in saved_files if "POSCAR" in i]), 1) @@ -2108,16 +2032,14 @@ def test_run(self): if_present_rm("Bond_Distortion_10.0%/job") if_present_rm("job") - # test "--all" option + # test "all" behaviour os.chdir("..") shutil.copytree("vac_1_Ti_0", "vac_1_Ti_1") shutil.copyfile( "v_Ge_s16_0/Bond_Distortion_-60.0%/OUTCAR", "v_Ge_s16_0/Bond_Distortion_-60.0%/prev_otcr", ) - proc = subprocess.Popen( - ["snb-run", "-v", "-a"], stdout=subprocess.PIPE, stderr=subprocess.PIPE - ) + proc = subprocess.Popen(["snb-run", "-v"], stdout=subprocess.PIPE, stderr=subprocess.PIPE) out = str(proc.communicate()[0]) self.assertIn("Looping through distortion folders for v_Ca_s0_0", out) self.assertIn( @@ -2128,27 +2050,15 @@ def test_run(self): self.assertIn("Looping through distortion folders for v_Ge_s16_0", out) # Check it filters distortions stuck in high energy basins for v_Ge (50%, while -60% is handled # separately as the energy doesn't change for the last 50 steps) - self.assertIn( - "More than 150 ionic steps present for Bond_Distortion_50.0%.", out - ) - self.assertIn( - "Bond_Distortion_40.0% not (fully) relaxed, saving files and rerunning", out - ) - self.assertFalse( - os.path.exists("v_Ge_s16_0/Bond_Distortion_-60.0%_High_Energy") - ) + self.assertIn("More than 150 ionic steps present for Bond_Distortion_50.0%.", out) + self.assertIn("Bond_Distortion_40.0% not (fully) relaxed, saving files and rerunning", out) + self.assertFalse(os.path.exists("v_Ge_s16_0/Bond_Distortion_-60.0%_High_Energy")) self.assertTrue(os.path.exists("v_Ge_s16_0/Bond_Distortion_50.0%_High_Energy")) self.assertFalse(os.path.exists("v_Ge_s16_0/Bond_Distortion_50.0%")) # Check number of OUTCARs in Bond_Distortion_50.0%_High_Energy - outcars = [ - f - for f in os.listdir("v_Ge_s16_0/Bond_Distortion_50.0%_High_Energy") - if "OUTCAR" in f - ] + outcars = [f for f in os.listdir("v_Ge_s16_0/Bond_Distortion_50.0%_High_Energy") if "OUTCAR" in f] assert len(outcars) == 6 # last OUTCAR not saved cause high energy distortion - outcars = [ - f for f in os.listdir("v_Ge_s16_0/Bond_Distortion_40.0%") if "OUTCAR" in f - ] + outcars = [f for f in os.listdir("v_Ge_s16_0/Bond_Distortion_40.0%") if "OUTCAR" in f] assert len(outcars) == 3 # last OUTCAR saved # Clean up; rename high energy directories os.rename( @@ -2170,19 +2080,10 @@ def test_run(self): ) os.chdir("..") - proc = subprocess.Popen( - ["snb-run", "-v"], stdout=subprocess.PIPE, stderr=subprocess.PIPE - ) - out = str(proc.communicate()[0]) - self.assertIn("No distortion folders found in current directory", out) - - proc = subprocess.Popen( - ["snb-run", "-v", "-a"], stdout=subprocess.PIPE, stderr=subprocess.PIPE - ) + proc = subprocess.Popen(["snb-run", "-v"], stdout=subprocess.PIPE, stderr=subprocess.PIPE) out = str(proc.communicate()[0]) self.assertIn( - "No defect folders (with names ending in a number (charge state)) found in " - "current directory", + "No defect folders (with names ending in a number (charge state)) found in current directory", out, ) @@ -2262,9 +2163,7 @@ def test_run(self): with open("Bond_Distortion_10.0%/OUTCAR", "w") as fp: fp.write(positive_energies_outcar_string) car_files = [ - file - for file in os.listdir("Bond_Distortion_10.0%") - if "on" in file and "CAR_" in file + file for file in os.listdir("Bond_Distortion_10.0%") if "on" in file and "CAR_" in file ] for i in car_files: # remove os.remove(f"Bond_Distortion_10.0%/{i}") @@ -2286,13 +2185,9 @@ def test_run(self): ) self.assertFalse(os.path.exists("Bond_Distortion_10.0%_High_Energy")) self.assertTrue(os.path.exists("Bond_Distortion_10.0%")) - self.assertIn( - "Bond_Distortion_10.0% not (fully) relaxed, saving files and rerunning", out - ) + self.assertIn("Bond_Distortion_10.0% not (fully) relaxed, saving files and rerunning", out) saved_files = [ - file - for file in os.listdir("Bond_Distortion_10.0%") - if "on" in file and "CAR_" in file + file for file in os.listdir("Bond_Distortion_10.0%") if "on" in file and "CAR_" in file ] self.assertEqual(len(saved_files), 2) # CONTCAR, OUTCAR self.assertEqual(len([i for i in saved_files if "CONTCAR" in i]), 1) @@ -2326,9 +2221,7 @@ def _test_OUTCAR_error(error_string): self.assertIn("Unperturbed fully relaxed", out) self.assertNotIn("Bond_Distortion_10.0% fully relaxed", out) # also present self.assertNotIn("Running job for Bond_Distortion_10.0%", out) - self.assertNotIn( - "this vac_1_Ti_0_10.0% job_file", out - ) # job submit command + self.assertNotIn("this vac_1_Ti_0_10.0% job_file", out) # job submit command self.assertIn( "Positive energies or forces error encountered for Bond_Distortion_10.0%, " "ignoring and renaming to Bond_Distortion_10.0%_High_Energy", @@ -2370,9 +2263,7 @@ def _test_OUTCAR_error(error_string): self.assertIn("Bond_Distortion_-40.0% fully relaxed", out) self.assertIn("Unperturbed fully relaxed", out) self.assertNotIn("Bond_Distortion_10.0% fully relaxed", out) - self.assertNotIn( - "Running job for Bond_Distortion_10.0%", out - ) # not running job though! + self.assertNotIn("Running job for Bond_Distortion_10.0%", out) # not running job though! self.assertNotIn("this vac_1_Ti_0_10.0% job_file", out) # job submit command self.assertNotIn( "Positive energies or forces error encountered for Bond_Distortion_10.0%, " @@ -2386,9 +2277,7 @@ def _test_OUTCAR_error(error_string): ) self.assertFalse(os.path.exists("Bond_Distortion_10.0%_High_Energy")) self.assertTrue(os.path.exists("Bond_Distortion_10.0%")) - self.assertNotIn( - "Bond_Distortion_10.0% not (fully) relaxed, saving files and rerunning", out - ) + self.assertNotIn("Bond_Distortion_10.0% not (fully) relaxed, saving files and rerunning", out) files = os.listdir("Bond_Distortion_10.0%") saved_files = [file for file in files if "on" in file and "CAR_" in file] self.assertEqual(len(saved_files), 0) @@ -2423,9 +2312,7 @@ def _test_OUTCAR_error(error_string): self.assertIn("Bond_Distortion_-40.0% fully relaxed", out) self.assertIn("Unperturbed fully relaxed", out) self.assertNotIn("Bond_Distortion_10.0% fully relaxed", out) - self.assertNotIn( - "Running job for Bond_Distortion_10.0%", out - ) # not running job though! + self.assertNotIn("Running job for Bond_Distortion_10.0%", out) # not running job though! self.assertNotIn("this vac_1_Ti_0_10.0% job_file", out) # job submit command self.assertNotIn( "Positive energies or forces error encountered for Bond_Distortion_10.0%, " @@ -2439,9 +2326,7 @@ def _test_OUTCAR_error(error_string): ) self.assertFalse(os.path.exists("Bond_Distortion_10.0%_High_Energy")) self.assertTrue(os.path.exists("Bond_Distortion_10.0%")) - self.assertNotIn( - "Bond_Distortion_10.0% not (fully) relaxed, saving files and rerunning", out - ) + self.assertNotIn("Bond_Distortion_10.0% not (fully) relaxed, saving files and rerunning", out) files = os.listdir("Bond_Distortion_10.0%") saved_files = [file for file in files if "on" in file and "CAR_" in file] self.assertEqual(len(saved_files), 0) @@ -2513,9 +2398,7 @@ def _test_OUTCAR_error(error_string): self.assertNotIn("Unperturbed fully relaxed", out) self.assertNotIn("Running job for Unperturbed", out) self.assertIn("this vac_1_Ti_0_10.0% job_file", out) # job submit command - self.assertIn( - "Positive energies or forces error encountered for Unperturbed.", out - ) + self.assertIn("Positive energies or forces error encountered for Unperturbed.", out) self.assertIn( "This typically indicates the initial defect structure supplied to " "ShakeNBreak is highly unstable, often with bond lengths smaller than the " @@ -2562,9 +2445,7 @@ def _test_OUTCAR_error(error_string): ) self.assertIn("Running job for Unperturbed", out) self.assertIn("this vac_1_Ti_0_10.0% job_file", out) # job submit command - self.assertNotIn( - "Positive energies or forces error encountered for Unperturbed.", out - ) + self.assertNotIn("Positive energies or forces error encountered for Unperturbed.", out) self.assertTrue(os.path.exists("Unperturbed")) # not renamed shutil.move("Unperturbed/OUTCAR_backup", "Unperturbed/OUTCAR") if_present_rm("Unperturbed/job_file") @@ -2598,9 +2479,7 @@ def _test_OUTCAR_error(error_string): self.assertNotIn("Previous run for Unperturbed", out) self.assertIn("Running job for Unperturbed", out) self.assertIn("this vac_1_Ti_0_10.0% job_file", out) # job submit command - self.assertNotIn( - "Positive energies or forces error encountered for Unperturbed.", out - ) + self.assertNotIn("Positive energies or forces error encountered for Unperturbed.", out) self.assertTrue(os.path.exists("Unperturbed")) # not renamed shutil.move("Unperturbed/OUTCAR_backup", "Unperturbed/OUTCAR") if_present_rm("Unperturbed/job_file") @@ -2622,9 +2501,7 @@ def _test_OUTCAR_error(error_string): fp.write("ALGO = Fast") car_files = [ - file - for file in os.listdir("Bond_Distortion_10.0%") - if "CAR_" in file and "on" in file + file for file in os.listdir("Bond_Distortion_10.0%") if "CAR_" in file and "on" in file ] for file in car_files: os.remove(f"Bond_Distortion_10.0%/{file}") @@ -2643,13 +2520,10 @@ def _test_OUTCAR_error(error_string): self.assertFalse(os.path.exists("Bond_Distortion_10.0%_High_Energy")) self.assertTrue(os.path.exists("Bond_Distortion_10.0%")) self.assertIn( - "Bond_Distortion_10.0% is showing poor electronic convergence, changing ALGO " - "to All.", + "Bond_Distortion_10.0% is showing poor electronic convergence, changing ALGO " "to All.", out, ) - self.assertIn( - "Bond_Distortion_10.0% not (fully) relaxed, saving files and rerunning", out - ) + self.assertIn("Bond_Distortion_10.0% not (fully) relaxed, saving files and rerunning", out) self.assertIn("ALGO = All", open("Bond_Distortion_10.0%/INCAR").read()) files = os.listdir("Bond_Distortion_10.0%") saved_files = [file for file in files if "on" in file and "CAR_" in file] @@ -2663,10 +2537,7 @@ def _test_OUTCAR_error(error_string): # test changing no message with poor electronic convergence when ALGO already = All with open("Bond_Distortion_10.0%/OUTCAR", "w") as fp: - fp.write( - poor_electronic_convergence_outcar_string - + "\nIALGO = 58 # i.e. ALGO = All" - ) + fp.write(poor_electronic_convergence_outcar_string + "\nIALGO = 58 # i.e. ALGO = All") proc = subprocess.Popen( ["snb-run", "-v", "-s echo", "-n this", "-j job_file"], @@ -2698,14 +2569,10 @@ def _test_OUTCAR_error(error_string): with open("Unperturbed/OUTCAR", "r") as f: outcar_string = f.read() final_mag_string = outcar_string[-5870:] - edited_final_mag_string = re.sub( - "[0-9]\.\d+", "0.000", final_mag_string - ) # zero magnetisation + edited_final_mag_string = re.sub(r"[0-9]\.\d+", "0.000", final_mag_string) # zero magnetisation with open("Unperturbed/OUTCAR", "w") as f: - f.write( - outcar_string[:-5870] + edited_final_mag_string - ) # zero magnetisation + f.write(outcar_string[:-5870] + edited_final_mag_string) # zero magnetisation proc = subprocess.Popen( ["snb-run", "-v", "-s echo", "-n this", "-j job_file"], @@ -2717,9 +2584,7 @@ def _test_OUTCAR_error(error_string): self.assertIn("Unperturbed fully relaxed", out) with open(f"Bond_Distortion_-40.0%/INCAR", "r") as fp: incar_string = fp.read() - self.assertIn( - "ISPIN = 2", incar_string - ) # no change in INCAR as run was fully converged + self.assertIn("ISPIN = 2", incar_string) # no change in INCAR as run was fully converged self.assertEqual(len(os.listdir(f"Bond_Distortion_-40.0%")), 3) # no new files with open(f"Unperturbed/INCAR", "r") as fp: @@ -2749,15 +2614,11 @@ def _test_OUTCAR_error(error_string): self.assertNotIn("Unperturbed fully relaxed", out) self.assertIn("Running job for Unperturbed", out) self.assertIn("this vac_1_Ti_0_Unperturbed job_file", out) # job submit command - self.assertIn( - "Unperturbed not (fully) relaxed, saving files and rerunning", out - ) + self.assertIn("Unperturbed not (fully) relaxed, saving files and rerunning", out) with open(f"Bond_Distortion_-40.0%/INCAR", "r") as fp: incar_string = fp.read() - self.assertIn( - "ISPIN = 2", incar_string - ) # no change in INCAR as run was fully converged + self.assertIn("ISPIN = 2", incar_string) # no change in INCAR as run was fully converged self.assertEqual(len(os.listdir("Bond_Distortion_-40.0%")), 3) # no new files with open(f"Unperturbed/INCAR", "r") as fp: @@ -2775,9 +2636,7 @@ def _test_OUTCAR_error(error_string): old_incar = [i for i in saved_files if "INCAR" in i][0] with open(f"Unperturbed/{old_incar}", "r") as fp: old_incar_string = fp.read() - self.assertIn( - "ISPIN = 2", old_incar_string - ) # Old INCAR unchanged from ISPIN = 2 + self.assertIn("ISPIN = 2", old_incar_string) # Old INCAR unchanged from ISPIN = 2 self.assertEqual(len([i for i in saved_files if "CONTCAR" in i]), 1) self.assertEqual(len([i for i in saved_files if "OUTCAR" in i]), 1) for i in saved_files: @@ -2804,9 +2663,7 @@ def _test_OUTCAR_error(error_string): self.assertNotIn("Unperturbed fully relaxed", out) with open(f"Bond_Distortion_-40.0%/INCAR", "r") as fp: incar_string = fp.read() - self.assertIn( - "ISPIN = 2", incar_string - ) # no change in INCAR as run was fully converged + self.assertIn("ISPIN = 2", incar_string) # no change in INCAR as run was fully converged self.assertEqual(len(os.listdir(f"Bond_Distortion_-40.0%")), 3) # no new files with open(f"Unperturbed/INCAR", "r") as fp: @@ -2829,9 +2686,7 @@ def test_parse(self): def _parse_v_Ti_and_check_output(verbose=False): with open(f"{self.EXAMPLE_RESULTS}/{defect}/{defect}.yaml", "w") as f: - f.write( - "" - ) # write empty energies file, otherwise no verbose print because detects + f.write("") # write empty energies file, otherwise no verbose print because detects # that it already exists with the same energies args = ["parse", "-d", defect, "-p", self.EXAMPLE_RESULTS] if verbose: @@ -2898,7 +2753,7 @@ def _parse_v_Ti_and_check_output(verbose=False): self.assertEqual(test_energies, energies) os.remove(f"{self.VASP_DIR}/{defect}/{defect}.yaml") - # Test --all option + # Test "all" behaviour self.tearDown() os.mkdir(f"{self.EXAMPLE_RESULTS}/pesky_defects") defect = "v_Ti" @@ -2914,22 +2769,15 @@ def _parse_v_Ti_and_check_output(verbose=False): snb, [ "parse", - "--all", "-p", f"{self.EXAMPLE_RESULTS}/pesky_defects", ], catch_exceptions=False, ) self.assertTrue( - os.path.exists( - f"{self.EXAMPLE_RESULTS}/pesky_defects/{defect}_-1/{defect}_-1.yaml" - ) - ) - self.assertTrue( - os.path.exists( - f"{self.EXAMPLE_RESULTS}/pesky_defects/{defect}_0/{defect}_0.yaml" - ) + os.path.exists(f"{self.EXAMPLE_RESULTS}/pesky_defects/{defect}_-1/{defect}_-1.yaml") ) + self.assertTrue(os.path.exists(f"{self.EXAMPLE_RESULTS}/pesky_defects/{defect}_0/{defect}_0.yaml")) # Test parsing from inside the defect folder defect = "v_Ti" @@ -2943,42 +2791,7 @@ def _parse_v_Ti_and_check_output(verbose=False): catch_exceptions=False, ) self.assertTrue( - os.path.exists( - f"{self.EXAMPLE_RESULTS}/pesky_defects/{defect}_-1/{defect}_-1.yaml" - ) - ) - os.chdir(file_path) - - # Test warning when setting path and parsing from inside the defect folder - defect = "v_Ti" - defect_name = "v_Ti_-1" - os.remove(f"{self.EXAMPLE_RESULTS}/pesky_defects/{defect}_-1/{defect}_-1.yaml") - os.chdir(f"{self.EXAMPLE_RESULTS}/pesky_defects/{defect}_-1") - with warnings.catch_warnings(record=True) as w: - result = runner.invoke( - snb, - [ - "parse", - "-p", - self.EXAMPLE_RESULTS, - ], - catch_exceptions=False, - ) - self.assertTrue(any([warning.category == UserWarning for warning in w])) - self.assertTrue( - any( - [ - str(warning.message) - == "`--path` option ignored when running from within defect folder (i.e. " - "when `--defect` is not specified." - for warning in w - ] - ) - ) - self.assertTrue( - os.path.exists( - f"{self.EXAMPLE_RESULTS}/pesky_defects/{defect}_-1/{defect}_-1.yaml" - ) + os.path.exists(f"{self.EXAMPLE_RESULTS}/pesky_defects/{defect}_-1/{defect}_-1.yaml") ) os.chdir(file_path) shutil.rmtree(f"{self.EXAMPLE_RESULTS}/pesky_defects/") @@ -3003,17 +2816,14 @@ def _parse_v_Ti_and_check_output(verbose=False): ) self.assertTrue( os.path.exists( - f"{self.EXAMPLE_RESULTS}/{defect_name}_defect_folder/{defect_name}" - f"/{defect_name}.yaml" + f"{self.EXAMPLE_RESULTS}/{defect_name}_defect_folder/{defect_name}" f"/{defect_name}.yaml" ) ) # test warning when nothing parsed because defect folder not recognised os.chdir(self.EXAMPLE_RESULTS) with warnings.catch_warnings(record=True) as w: - result = runner.invoke( - snb, ["parse", "-d", "defect"], catch_exceptions=True - ) + result = runner.invoke(snb, ["parse", "-d", "defect"], catch_exceptions=True) self.assertTrue(any([warning.category == UserWarning for warning in w])) self.assertTrue( any( @@ -3022,31 +2832,17 @@ def _parse_v_Ti_and_check_output(verbose=False): for warning in w ) ) - self.assertFalse( - any(os.path.exists(i) for i in os.listdir() if i.endswith(".yaml")) - ) + self.assertFalse(any(os.path.exists(i) for i in os.listdir() if i.endswith(".yaml"))) os.chdir(file_path) # Test warning when run with no arguments in top-level folder os.chdir(self.EXAMPLE_RESULTS) with warnings.catch_warnings(record=True) as w: result = runner.invoke(snb, ["parse"], catch_exceptions=True) - self.assertTrue(any([warning.category == UserWarning for warning in w])) - self.assertTrue( - any( - [ - str(warning.message) - == "Energies could not be parsed for defect 'example_results' in" - f" '{self.DATA_DIR}'. If these directories are correct, " - "check calculations have converged, and that distortion subfolders match " - "ShakeNBreak naming (e.g. Bond_Distortion_xxx, Rattled, Unperturbed)" - for warning in w - ] - ) - ) - self.assertFalse( - any(os.path.exists(i) for i in os.listdir() if i.endswith(".yaml")) - ) + print([str(warning.message) for warning in w]) # for debugging + self.assertFalse(any([warning.category == UserWarning for warning in w])) + self.assertFalse(any(os.path.exists(i) for i in os.listdir() if i.endswith(".yaml"))) + self.assertTrue(os.path.exists("v_Ti_0/v_Ti_0.yaml")) # parsed successfully os.chdir(file_path) # test ignoring "*High_Energy*" folder(s) @@ -3073,9 +2869,7 @@ def _parse_v_Ti_and_check_output(verbose=False): for file in os.listdir(f"{self.EXAMPLE_RESULTS}/{defect}") if os.path.isfile(f"{self.EXAMPLE_RESULTS}/{defect}/{file}") ] - shutil.rmtree( - f"{self.EXAMPLE_RESULTS}/{defect}/Bond_Distortion_-20.0%_High_Energy" - ) + shutil.rmtree(f"{self.EXAMPLE_RESULTS}/{defect}/Bond_Distortion_-20.0%_High_Energy") # test parsing energies of calculations that still haven't converged defect = "v_Ti_0" @@ -3108,9 +2902,7 @@ def _parse_v_Ti_and_check_output(verbose=False): catch_exceptions=False, ) energies = loadfn(f"{self.EXAMPLE_RESULTS}/{defect}/{defect}.yaml") - self.assertNotEqual( - test_energies, energies - ) # Bond_Distortion_-20.0%_not_converged now included + self.assertNotEqual(test_energies, energies) # Bond_Distortion_-20.0%_not_converged now included not_converged_energies = copy.deepcopy(test_energies) not_converged_energies["distortions"].update( {"Bond_Distortion_-20.0%_not_converged": -1110.37833497} @@ -3128,9 +2920,7 @@ def _parse_v_Ti_and_check_output(verbose=False): for file in os.listdir(f"{self.EXAMPLE_RESULTS}/{defect}") if os.path.isfile(f"{self.EXAMPLE_RESULTS}/{defect}/{file}") ] - shutil.rmtree( - f"{self.EXAMPLE_RESULTS}/{defect}/Bond_Distortion_-20.0%_not_converged" - ) + shutil.rmtree(f"{self.EXAMPLE_RESULTS}/{defect}/Bond_Distortion_-20.0%_not_converged") # test parsing energies of residual-forces calculations defect = "v_Ti_0" @@ -3145,8 +2935,8 @@ def _parse_v_Ti_and_check_output(verbose=False): """ * 70 ) - + """ShakeNBreak: At least 50 ionic steps and energy change < 2 meV for this - defect, considering this converged.""" + + "\nShakeNBreak: At least 50 ionic steps and energy change < 2 meV for this defect, " + "considering this converged.\n" ) with open( f"{self.EXAMPLE_RESULTS}/{defect}/Bond_Distortion_-20.0%_residual_forces/OUTCAR", @@ -3166,9 +2956,7 @@ def _parse_v_Ti_and_check_output(verbose=False): catch_exceptions=False, ) energies = loadfn(f"{self.EXAMPLE_RESULTS}/{defect}/{defect}.yaml") - self.assertNotEqual( - test_energies, energies - ) # Bond_Distortion_-20.0%_residual_forces now included + self.assertNotEqual(test_energies, energies) # Bond_Distortion_-20.0%_residual_forces now included residual_forces_energies = copy.deepcopy(test_energies) residual_forces_energies["distortions"].update( {"Bond_Distortion_-20.0%_residual_forces": -675.21988502} @@ -3177,15 +2965,19 @@ def _parse_v_Ti_and_check_output(verbose=False): residual_forces_energies, energies ) # Bond_Distortion_-20.0%_residual_forces now included # test no print statement about not being fully relaxed + print(result.output) + with open( + f"{self.EXAMPLE_RESULTS}/{defect}/Bond_Distortion_-20.0%_residual_forces/OUTCAR", + "r+", + ) as f: + print(f.readlines()) self.assertNotIn("not fully relaxed", result.output) [ os.remove(f"{self.EXAMPLE_RESULTS}/{defect}/{file}") for file in os.listdir(f"{self.EXAMPLE_RESULTS}/{defect}") if os.path.isfile(f"{self.EXAMPLE_RESULTS}/{defect}/{file}") ] - shutil.rmtree( - f"{self.EXAMPLE_RESULTS}/{defect}/Bond_Distortion_-20.0%_residual_forces" - ) + shutil.rmtree(f"{self.EXAMPLE_RESULTS}/{defect}/Bond_Distortion_-20.0%_residual_forces") # test warning when all parsed distortions are >0.1 eV higher energy than unperturbed defect = "v_Ti_+3" @@ -3219,13 +3011,9 @@ def _parse_v_Ti_and_check_output(verbose=False): "distortions": {-0.4: -675.21988502}, "Unperturbed": -1173.02056574, } # energies still parsed despite being high and not converged (like myself) - self.assertEqual( - high_energies_dict, energies - ) # energies still parsed, but all high energy + self.assertEqual(high_energies_dict, energies) # energies still parsed, but all high energy # test print statement about not being fully relaxed - self.assertIn( - "Bond_Distortion_-40.0% for v_Ti_+3 is not fully relaxed", result.output - ) + self.assertIn("Bond_Distortion_-40.0% for v_Ti_+3 is not fully relaxed", result.output) self.assertTrue(len([i for i in w if i.category == UserWarning]) == 1) self.assertTrue( any( @@ -3282,12 +3070,8 @@ def _parse_v_Ti_and_check_output(verbose=False): } # energies still parsed despite being odd self.assertEqual(low_energies_dict, energies) # energies still parsed # test print statement about not being fully relaxed - self.assertIn( - "Bond_Distortion_-40.0% for v_Ti_+3 is not fully relaxed", result.output - ) - self.assertTrue( - len([i for i in w if i.category == UserWarning]) == 0 - ) # no warning when + self.assertIn("Bond_Distortion_-40.0% for v_Ti_+3 is not fully relaxed", result.output) + self.assertTrue(len([i for i in w if i.category == UserWarning]) == 0) # no warning when # <2 parsed distortions shutil.copytree( @@ -3318,9 +3102,7 @@ def _parse_v_Ti_and_check_output(verbose=False): } # energies still parsed despite being odd self.assertEqual(low_energies_dict, energies) # energies still parsed # test print statement about not being fully relaxed - self.assertIn( - "Bond_Distortion_-40.0% for v_Ti_+3 is not fully relaxed", result.output - ) + self.assertIn("Bond_Distortion_-40.0% for v_Ti_+3 is not fully relaxed", result.output) self.assertTrue( any( [ @@ -3329,8 +3111,7 @@ def _parse_v_Ti_and_check_output(verbose=False): f"it can indicate that a bulk phase transformation is occurring within your " f"defect supercell. If so, see " f"https://shakenbreak.readthedocs.io/en/latest/Tips.html#bulk-phase" - f"-transformations for advice on dealing with this phenomenon." - == str(i.message) + f"-transformations for advice on dealing with this phenomenon." == str(i.message) for i in w if i.category == UserWarning ] @@ -3407,14 +3188,17 @@ def test_parse_codes(self): ], catch_exceptions=False, ) - self.assertTrue( - os.path.exists(f"{self.DATA_DIR}/{code}/{defect}/{defect}.yaml") - ) - with open( - f"{self.DATA_DIR}/{code}/{defect}/test_{defect}.yaml", "r" - ) as test, open(f"{self.DATA_DIR}/{code}/{defect}/{defect}.yaml", "r") as new: + self.assertTrue(os.path.exists(f"{self.DATA_DIR}/{code}/{defect}/{defect}.yaml")) + with open(f"{self.DATA_DIR}/{code}/{defect}/test_{defect}.yaml", "r") as test, open( + f"{self.DATA_DIR}/{code}/{defect}/{defect}.yaml", "r" + ) as new: test_yaml = yaml.safe_load(test) new_yaml = yaml.safe_load(new) + # for updating file, due to small numerical updates etc: + # shutil.copyfile( + # f"{self.DATA_DIR}/{code}/{defect}/{defect}.yaml", + # f"{self.DATA_DIR}/{code}/{defect}/test_{defect}.yaml", + # ) self.assertDictEqual(test_yaml, new_yaml) os.remove(f"{self.DATA_DIR}/{code}/{defect}/{defect}.yaml") @@ -3433,12 +3217,10 @@ def test_parse_codes(self): ], catch_exceptions=False, ) - self.assertTrue( - os.path.exists(f"{self.DATA_DIR}/{code}/{defect}/{defect}.yaml") - ) - with open( - f"{self.DATA_DIR}/{code}/{defect}/test_{defect}.yaml", "r" - ) as test, open(f"{self.DATA_DIR}/{code}/{defect}/{defect}.yaml", "r") as new: + self.assertTrue(os.path.exists(f"{self.DATA_DIR}/{code}/{defect}/{defect}.yaml")) + with open(f"{self.DATA_DIR}/{code}/{defect}/test_{defect}.yaml", "r") as test, open( + f"{self.DATA_DIR}/{code}/{defect}/{defect}.yaml", "r" + ) as new: test_yaml = yaml.safe_load(test) new_yaml = yaml.safe_load(new) self.assertDictEqual(test_yaml, new_yaml) @@ -3459,14 +3241,17 @@ def test_parse_codes(self): ], catch_exceptions=False, ) - self.assertTrue( - os.path.exists(f"{self.DATA_DIR}/{code}/{defect}/{defect}.yaml") - ) - with open( - f"{self.DATA_DIR}/{code}/{defect}/test_{defect}.yaml", "r" - ) as test, open(f"{self.DATA_DIR}/{code}/{defect}/{defect}.yaml", "r") as new: + self.assertTrue(os.path.exists(f"{self.DATA_DIR}/{code}/{defect}/{defect}.yaml")) + with open(f"{self.DATA_DIR}/{code}/{defect}/test_{defect}.yaml", "r") as test, open( + f"{self.DATA_DIR}/{code}/{defect}/{defect}.yaml", "r" + ) as new: test_yaml = yaml.safe_load(test) new_yaml = yaml.safe_load(new) + # for updating file, due to small numerical updates etc: + # shutil.copyfile( + # f"{self.DATA_DIR}/{code}/{defect}/{defect}.yaml", + # f"{self.DATA_DIR}/{code}/{defect}/test_{defect}.yaml", + # ) self.assertDictEqual(test_yaml, new_yaml) os.remove(f"{self.DATA_DIR}/{code}/{defect}/{defect}.yaml") @@ -3485,12 +3270,10 @@ def test_parse_codes(self): ], catch_exceptions=False, ) - self.assertTrue( - os.path.exists(f"{self.DATA_DIR}/{code}/{defect}/{defect}.yaml") - ) - with open( - f"{self.DATA_DIR}/{code}/{defect}/test_{defect}.yaml", "r" - ) as test, open(f"{self.DATA_DIR}/{code}/{defect}/{defect}.yaml", "r") as new: + self.assertTrue(os.path.exists(f"{self.DATA_DIR}/{code}/{defect}/{defect}.yaml")) + with open(f"{self.DATA_DIR}/{code}/{defect}/test_{defect}.yaml", "r") as test, open( + f"{self.DATA_DIR}/{code}/{defect}/{defect}.yaml", "r" + ) as new: test_yaml = yaml.safe_load(test) new_yaml = yaml.safe_load(new) self.assertDictEqual(test_yaml, new_yaml) @@ -3533,7 +3316,7 @@ def test_analyse(self): if os.path.isfile(f"{self.EXAMPLE_RESULTS}/{defect}/{file}") ] - # Test --all flag + # Test 'all' behaviour os.mkdir(f"{self.EXAMPLE_RESULTS}/pesky_defects") defect_name = "v_Ti_-1" shutil.copytree( @@ -3548,20 +3331,15 @@ def test_analyse(self): snb, [ "analyse", - "--all", "-p", f"{self.EXAMPLE_RESULTS}/pesky_defects", ], catch_exceptions=False, ) self.assertTrue( - os.path.exists( - f"{self.EXAMPLE_RESULTS}/pesky_defects/{defect_name}/{defect_name}.csv" - ) - ) - self.assertTrue( - os.path.exists(f"{self.EXAMPLE_RESULTS}/pesky_defects/v_Ti_0/v_Ti_0.csv") + os.path.exists(f"{self.EXAMPLE_RESULTS}/pesky_defects/{defect_name}/{defect_name}.csv") ) + self.assertTrue(os.path.exists(f"{self.EXAMPLE_RESULTS}/pesky_defects/v_Ti_0/v_Ti_0.csv")) shutil.rmtree(f"{self.EXAMPLE_RESULTS}/pesky_defects/") # Test non-existent defect name = "v_Ti_-2" @@ -3579,8 +3357,8 @@ def test_analyse(self): self.assertIn( f"Could not analyse defect '{name}' in directory '{self.EXAMPLE_RESULTS}'. Please " "either specify a defect to analyse (with option --defect), run from within a single " - "defect directory (without setting --defect) or use the --all flag to analyse all " - "defects in the specified/current directory.", + "defect directory (without setting --defect) or run from the top-level directory to " + f"analyse all defects in the specified/current directory.", str(result.exception), ) @@ -3592,9 +3370,7 @@ def test_analyse(self): f"{self.EXAMPLE_RESULTS}/{defect}", f"{self.EXAMPLE_RESULTS}/{defect}_defect_folder/{defect}", ) - with open( - f"{self.EXAMPLE_RESULTS}/{defect}_defect_folder/{defect}/{defect}.yaml", "w" - ) as f: + with open(f"{self.EXAMPLE_RESULTS}/{defect}_defect_folder/{defect}/{defect}.yaml", "w") as f: f.write("") runner = CliRunner() result = runner.invoke( @@ -3613,9 +3389,7 @@ def test_analyse(self): f"Saved results to {self.EXAMPLE_RESULTS}/{defect}_defect_folder/{defect}/{defect}.csv", result.output, ) - with open( - f"{self.EXAMPLE_RESULTS}/{defect}_defect_folder/{defect}/{defect}.csv" - ) as f: + with open(f"{self.EXAMPLE_RESULTS}/{defect}_defect_folder/{defect}/{defect}.csv") as f: file = f.read() csv_content = ( ",Bond Distortion,Σ{Displacements} (Å),Max Distance (Å),Δ Energy (eV)\n" @@ -3642,42 +3416,7 @@ def test_analyse(self): self.assertTrue( any( [ - str(warning.message) == "No output file in Bond_Distortion_10.0% " - "directory" - for warning in w - ] - ) - ) - self.assertIn("Comparing structures to Unperturbed...", result.output) - self.assertIn( - f"Saved results to {os.path.join(os.getcwd(), defect_name)}.csv", - result.output, - ) - self.assertTrue(os.path.exists(f"{defect_name}.csv")) - self.assertTrue(os.path.exists(f"{defect_name}.yaml")) - os.remove(f"{defect_name}.csv") - os.remove(f"{defect_name}.yaml") - - # Test warning when setting path and analysing from inside the defect folder - # we are in self.VASP_TIO2_DATA_DIR - defect_name = "vac_1_Ti_0" - with warnings.catch_warnings(record=True) as w: - result = runner.invoke( - snb, - [ - "analyse", - "-p", - self.EXAMPLE_RESULTS, - ], - catch_exceptions=True, - ) - self.assertTrue(any([warning.category == UserWarning for warning in w])) - self.assertTrue( - any( - [ - str(warning.message) - == "`--path` option ignored when running from within defect folder (i.e. " - "when `--defect` is not specified." + str(warning.message) == "No output file in Bond_Distortion_10.0% " "directory" for warning in w ] ) @@ -3693,24 +3432,13 @@ def test_analyse(self): os.remove(f"{defect_name}.yaml") os.chdir(file_path) - # Test exception when run with no arguments in top-level folder + # Test no exception when run with no arguments in top-level folder os.chdir(self.EXAMPLE_RESULTS) - result = runner.invoke(snb, ["analyse"], catch_exceptions=True) - self.assertIn( - f"Could not analyse defect 'example_results' in directory '{self.DATA_DIR}'. Please " - "either specify a defect to analyse (with option --defect), run from within a single " - "defect directory (without setting --defect) or use the --all flag to analyse all " - "defects in the specified/current directory.", - str(result.exception), - ) - self.assertNotIn("Saved results to", result.output) + result = runner.invoke(snb, ["analyse"]) self.assertFalse( - any( - os.path.exists(i) - for i in os.listdir() - if (i.endswith(".csv") or i.endswith(".yaml")) - ) + any(os.path.exists(i) for i in os.listdir() if (i.endswith(".csv") or i.endswith(".yaml"))) ) + self.assertTrue(os.path.exists("v_Ti_0/v_Ti_0.csv")) # parsed successfully os.chdir(file_path) def test_plot(self): @@ -3746,9 +3474,7 @@ def test_plot(self): "and unperturbed: -3.26 eV.", result.output, ) # verbose output - self.assertIn( - f"Plot saved to {defect_name}_0/{defect_name}_0.png", result.output - ) + self.assertIn(f"Plot saved to {defect_name}_0/{defect_name}_0.png", result.output) self.assertEqual(w[0].category, UserWarning) self.assertEqual( f"Path {self.EXAMPLE_RESULTS}/distortion_metadata.json or {self.EXAMPLE_RESULTS}/" @@ -3756,9 +3482,7 @@ def test_plot(self): "its contents (to specify which neighbour atoms were distorted in plot text).", str(w[0].message), ) - self.assertTrue( - os.path.exists(os.path.join(self.EXAMPLE_RESULTS, defect, defect + ".png")) - ) + self.assertTrue(os.path.exists(os.path.join(self.EXAMPLE_RESULTS, defect, defect + ".png"))) # Figures are compared in the local test since on Github Actions images are saved # with a different size (raising error when comparing). self.tearDown() @@ -3770,9 +3494,7 @@ def test_plot(self): # test with new doped naming defect = "Te_i_Td_Te2.83_+2" - shutil.copytree( - f"{self.EXAMPLE_RESULTS}/v_Ti_0", f"{self.EXAMPLE_RESULTS}/{defect}" - ) + shutil.copytree(f"{self.EXAMPLE_RESULTS}/v_Ti_0", f"{self.EXAMPLE_RESULTS}/{defect}") with warnings.catch_warnings(record=True) as w: result = runner.invoke( snb, @@ -3798,12 +3520,10 @@ def test_plot(self): "its contents (to specify which neighbour atoms were distorted in plot text).", str(w[0].message), ) - self.assertTrue( - os.path.exists(os.path.join(self.EXAMPLE_RESULTS, defect, defect + ".png")) - ) + self.assertTrue(os.path.exists(os.path.join(self.EXAMPLE_RESULTS, defect, defect + ".png"))) self.tearDown() - # Test --all option, with the distortion_metadata.json file present to parse number of + # Test 'all' behaviour, with the distortion_metadata.json file present to parse number of # distorted neighbours and their identities defect = "v_Ti_0" defect_name = "v_Ti" @@ -3840,7 +3560,6 @@ def test_plot(self): snb, [ "plot", - "--all", "-p", self.EXAMPLE_RESULTS, "-f", @@ -3849,18 +3568,10 @@ def test_plot(self): catch_exceptions=False, ) self.assertTrue( - os.path.exists( - os.path.join( - self.EXAMPLE_RESULTS, f"{defect_name}_0/{defect_name}_0.png" - ) - ) - ) - self.assertTrue( - os.path.exists(os.path.join(self.EXAMPLE_RESULTS, "v_Cd_0/v_Cd_0.png")) - ) - self.assertTrue( - os.path.exists(os.path.join(self.EXAMPLE_RESULTS, "v_Cd_-1/v_Cd_-1.png")) + os.path.exists(os.path.join(self.EXAMPLE_RESULTS, f"{defect_name}_0/{defect_name}_0.png")) ) + self.assertTrue(os.path.exists(os.path.join(self.EXAMPLE_RESULTS, "v_Cd_0/v_Cd_0.png"))) + self.assertTrue(os.path.exists(os.path.join(self.EXAMPLE_RESULTS, "v_Cd_-1/v_Cd_-1.png"))) if w: [ self.assertNotIn( # no distortion_metadata.json warning @@ -3893,9 +3604,7 @@ def test_plot(self): "and unperturbed: -3.26 eV.", result.output, ) # non-verbose output - self.assertNotIn( - "Plot saved to v_Ti_0/v_Ti_0.png", result.output - ) # non-verbose + self.assertNotIn("Plot saved to v_Ti_0/v_Ti_0.png", result.output) # non-verbose self.assertTrue( len([warning for warning in w if warning.category == UserWarning]) == 0 ) # non-verbose @@ -3941,9 +3650,7 @@ def test_plot(self): result.output, ) # verbose output self.assertIn("Plot saved to v_Ti_0/v_Ti_0.png", result.output) # verbose - self.assertTrue( - len([warning for warning in w if warning.category == UserWarning]) == 1 - ) # verbose + self.assertTrue(len([warning for warning in w if warning.category == UserWarning]) == 1) # verbose self.assertTrue( any( [ # verbose @@ -3959,15 +3666,12 @@ def test_plot(self): ) self.assertTrue( os.path.exists( - f"{self.EXAMPLE_RESULTS}/{defect_name}_defect_folder" - f"/{defect_name}/v_Ti_0.png" + f"{self.EXAMPLE_RESULTS}/{defect_name}_defect_folder" f"/{defect_name}/v_Ti_0.png" ) ) self.assertTrue( os.path.exists( - f"{self.EXAMPLE_RESULTS}/" - f"{defect_name}_defect_folder/" - f"{defect_name}/v_Ti_0.yaml" + f"{self.EXAMPLE_RESULTS}/" f"{defect_name}_defect_folder/" f"{defect_name}/v_Ti_0.yaml" ) ) @@ -3996,82 +3700,23 @@ def test_plot(self): len([warning for warning in w if warning.category == UserWarning]) == 0 ) # verbose, but no distortion_metadata warning self.assertTrue( - os.path.exists( - f"{self.EXAMPLE_RESULTS}/{defect_name}_defect_folder/{defect_name}/v_Ti_0.png" - ) + os.path.exists(f"{self.EXAMPLE_RESULTS}/{defect_name}_defect_folder/{defect_name}/v_Ti_0.png") ) self.tearDown() - # Test warning when setting path and plotting from inside the defect folder - os.chdir(f"{self.EXAMPLE_RESULTS}/{defect}") # vac_1_Ti_0 - with warnings.catch_warnings(record=True) as w: - result = runner.invoke( - snb, - [ - "plot", - "-p", - self.EXAMPLE_RESULTS, - "-v", - ], - catch_exceptions=True, - ) - self.assertTrue(any([warning.category == UserWarning for warning in w])) - self.assertTrue( - any( - [ - str(warning.message) - == "`--path` option ignored when running from within defect folder (i.e. " - "when `--defect` is not specified." - for warning in w - ] - ) - ) - self.assertIn( - f"{defect}: Energy difference between minimum, found with -0.4 bond distortion, " - "and unperturbed: -3.26 eV.", - result.output, - ) # non-verbose output - self.assertIn("Plot saved to v_Ti_0/v_Ti_0.png", result.output) - self.assertTrue( - any( - [ - f"Path {self.EXAMPLE_RESULTS}/distortion_metadata.json or " - f"{self.EXAMPLE_RESULTS}/" - "v_Ti_0/distortion_metadata.json not found. Will not parse " - "its contents (to specify which neighbour atoms were distorted in plot " - "text)." == str(warning.message) - for warning in w - ] - ) - ) - self.assertTrue(os.path.exists("./v_Ti_0.png")) - self.assertTrue(os.path.exists(os.getcwd() + "/v_Ti_0.yaml")) - if_present_rm(os.getcwd() + "/v_Ti_0.yaml") - self.tearDown() - - # Test exception when run with no arguments in top-level folder + # Test no exception when run with no arguments in top-level folder os.chdir(self.EXAMPLE_RESULTS) - result = runner.invoke(snb, ["plot"], catch_exceptions=True) - self.assertIn( - f"Could not analyse & plot defect 'example_results' in directory '{self.DATA_DIR}'. " - "Please either specify a defect to analyse (with option --defect), run from within a " - "single defect directory (without setting --defect) or use the --all flag to analyse " - "all defects in the specified/current directory.", - str(result.exception), - ) - self.assertNotIn(f"Plot saved to v_Ti_0/v_Ti_0.png", result.output) - self.assertFalse( - any(os.path.exists(i) for i in os.listdir() if i.endswith(".yaml")) - ) + result = runner.invoke(snb, ["plot", "-v"]) + self.assertIn("Plot saved to v_Ti_0/v_Ti_0.png", result.output) + self.assertTrue(os.path.exists("v_Ti_0/v_Ti_0.png")) # parsed successfully self.tearDown() - # Test --all option, with --min_energy option + # Test 'all' behaviour, with --min_energy option result = runner.invoke( snb, [ "plot", - "--all", "-min", "1", "-p", @@ -4112,17 +3757,9 @@ def test_regenerate(self): catch_exceptions=False, ) defect = "v_Cd" # in example results - non_ignored_warnings = [ - warning for warning in w if "Subfolders with" not in str(warning.message) - ] + non_ignored_warnings = [warning for warning in w if "Subfolders with" not in str(warning.message)] self.assertEqual( - len( - [ - warning - for warning in non_ignored_warnings - if warning.category == UserWarning - ] - ), + len([warning for warning in non_ignored_warnings if warning.category == UserWarning]), 0, ) @@ -4181,14 +3818,10 @@ def test_regenerate(self): # test "*High_Energy*" ignored and doesn't cause errors if not os.path.exists( - os.path.join( - self.EXAMPLE_RESULTS, f"{defect}_0/Bond_Distortion_-48.0%_High_Energy" - ) + os.path.join(self.EXAMPLE_RESULTS, f"{defect}_0/Bond_Distortion_-48.0%_High_Energy") ): shutil.copytree( - os.path.join( - self.EXAMPLE_RESULTS, f"{defect}_0/Bond_Distortion_-60.0%" - ), + os.path.join(self.EXAMPLE_RESULTS, f"{defect}_0/Bond_Distortion_-60.0%"), os.path.join( self.EXAMPLE_RESULTS, f"{defect}_0/Bond_Distortion_-48.0%_High_Energy", @@ -4205,17 +3838,9 @@ def test_regenerate(self): ], catch_exceptions=False, ) - non_ignored_warnings = [ - warning for warning in w if "Subfolders with" not in str(warning.message) - ] + non_ignored_warnings = [warning for warning in w if "Subfolders with" not in str(warning.message)] self.assertEqual( - len( - [ - warning - for warning in non_ignored_warnings - if warning.category == UserWarning - ] - ), + len([warning for warning in non_ignored_warnings if warning.category == UserWarning]), 0, ) assert any( @@ -4274,17 +3899,13 @@ def test_groundstate(self): ], catch_exceptions=False, ) - self.assertTrue( - os.path.exists(f"{self.VASP_CDTE_DATA_DIR}/{defect}/Groundstate/POSCAR") - ) + self.assertTrue(os.path.exists(f"{self.VASP_CDTE_DATA_DIR}/{defect}/Groundstate/POSCAR")) self.assertIn( f"{defect}: Ground state structure (found with -0.55 distortion) saved to" f" {self.VASP_CDTE_DATA_DIR}/{defect}/Groundstate/POSCAR", result.output, ) - gs_structure = Structure.from_file( - f"{self.VASP_CDTE_DATA_DIR}/{defect}/Groundstate/POSCAR" - ) + gs_structure = Structure.from_file(f"{self.VASP_CDTE_DATA_DIR}/{defect}/Groundstate/POSCAR") self.assertEqual(gs_structure, self.V_Cd_minus0pt55_CONTCAR_struc) if_present_rm(f"{self.VASP_CDTE_DATA_DIR}/{defect}/Groundstate") @@ -4304,9 +3925,7 @@ def test_groundstate(self): catch_exceptions=False, ) self.assertTrue( - os.path.exists( - f"{self.VASP_CDTE_DATA_DIR}/{defect}/My_Groundstate/Groundstate_CONTCAR" - ) + os.path.exists(f"{self.VASP_CDTE_DATA_DIR}/{defect}/My_Groundstate/Groundstate_CONTCAR") ) self.assertIn( f"{defect}: Ground state structure (found with -0.55 distortion) saved to" @@ -4332,9 +3951,7 @@ def test_groundstate(self): ], catch_exceptions=True, ) - self.assertFalse( - os.path.exists(f"{self.VASP_CDTE_DATA_DIR}/{defect}/Groundstate") - ) + self.assertFalse(os.path.exists(f"{self.VASP_CDTE_DATA_DIR}/{defect}/Groundstate")) self.assertIsInstance(result.exception, FileNotFoundError) self.assertIn( "The structure file Fake_structure is not present in the directory" @@ -4351,43 +3968,7 @@ def test_groundstate(self): f" {self.VASP_CDTE_DATA_DIR}/{defect}/Groundstate/POSCAR", result.output, ) - gs_structure = Structure.from_file( - f"{self.VASP_CDTE_DATA_DIR}/{defect}/Groundstate/POSCAR" - ) - self.assertEqual(gs_structure, self.V_Cd_minus0pt55_CONTCAR_struc) - if_present_rm(f"{self.VASP_CDTE_DATA_DIR}/{defect}/Groundstate") - - # Test warning when setting path and parsing from inside the defect folder - with warnings.catch_warnings(record=True) as w: - result = runner.invoke( - snb, - [ - "groundstate", - "-p", - self.EXAMPLE_RESULTS, - ], - catch_exceptions=False, - ) - self.assertTrue(any([warning.category == UserWarning for warning in w])) - self.assertTrue( - any( - [ - str(warning.message) - == "`--path` option ignored when running from within defect folder (assumed " - "to be the case here as distortion folders found in current directory)." - for warning in w - ] - ) - ) - self.assertTrue(os.path.exists("Groundstate/POSCAR")) - self.assertIn( - f"{defect}: Ground state structure (found with -0.55 distortion) saved to" - f" {self.VASP_CDTE_DATA_DIR}/{defect}/Groundstate/POSCAR", - result.output, - ) - gs_structure = Structure.from_file( - f"{self.VASP_CDTE_DATA_DIR}/{defect}/Groundstate/POSCAR" - ) + gs_structure = Structure.from_file(f"{self.VASP_CDTE_DATA_DIR}/{defect}/Groundstate/POSCAR") self.assertEqual(gs_structure, self.V_Cd_minus0pt55_CONTCAR_struc) if_present_rm(f"{self.VASP_CDTE_DATA_DIR}/{defect}/Groundstate") @@ -4409,9 +3990,7 @@ def test_groundstate(self): # test "*High_Energy*" ignored and doesn't cause errors defect = "vac_1_Cd_0" # in self.VASP_CDTE_DATA_DIR if not os.path.exists( - os.path.join( - self.EXAMPLE_RESULTS, f"v_Cd_0/Bond_Distortion_-48.0%_High_Energy" - ) + os.path.join(self.EXAMPLE_RESULTS, f"v_Cd_0/Bond_Distortion_-48.0%_High_Energy") ): shutil.copytree( os.path.join(self.EXAMPLE_RESULTS, f"v_Cd_0/Bond_Distortion_-60.0%"), @@ -4429,12 +4008,8 @@ def test_groundstate(self): ], catch_exceptions=False, ) - self.assertTrue( - os.path.exists(f"{self.VASP_CDTE_DATA_DIR}/{defect}/Groundstate/POSCAR") - ) - gs_structure = Structure.from_file( - f"{self.VASP_CDTE_DATA_DIR}/{defect}/Groundstate/POSCAR" - ) + self.assertTrue(os.path.exists(f"{self.VASP_CDTE_DATA_DIR}/{defect}/Groundstate/POSCAR")) + gs_structure = Structure.from_file(f"{self.VASP_CDTE_DATA_DIR}/{defect}/Groundstate/POSCAR") self.assertEqual(gs_structure, self.V_Cd_minus0pt55_CONTCAR_struc) if_present_rm(f"{self.VASP_CDTE_DATA_DIR}/{defect}/Groundstate") self.assertFalse("High_Energy" in result.output) @@ -4449,17 +4024,13 @@ def test_groundstate(self): ], catch_exceptions=False, ) - self.assertTrue( - os.path.exists(f"{self.EXAMPLE_RESULTS}/{defect}/Groundstate/POSCAR") - ) + self.assertTrue(os.path.exists(f"{self.EXAMPLE_RESULTS}/{defect}/Groundstate/POSCAR")) self.assertIn( f"{defect}: Ground state structure (found with -0.4 distortion) saved to" f" {self.EXAMPLE_RESULTS}/{defect}/Groundstate/POSCAR", result.output, ) - gs_structure = Structure.from_file( - f"{self.EXAMPLE_RESULTS}/{defect}/Groundstate/POSCAR" - ) + gs_structure = Structure.from_file(f"{self.EXAMPLE_RESULTS}/{defect}/Groundstate/POSCAR") V_Ti_minus0pt4_structure = Structure.from_file( f"{self.EXAMPLE_RESULTS}/{defect}/Bond_Distortion_-40.0%/CONTCAR" ) @@ -4479,16 +4050,10 @@ def test_groundstate(self): ], catch_exceptions=False, ) - self.assertTrue( - os.path.exists(f"{self.VASP_CDTE_DATA_DIR}/{defect}/Groundstate/POSCAR") - ) - self.assertFalse( - result.output - ) # no output (No "Parsing..." or "Groundstate structure + self.assertTrue(os.path.exists(f"{self.VASP_CDTE_DATA_DIR}/{defect}/Groundstate/POSCAR")) + self.assertFalse(result.output) # no output (No "Parsing..." or "Groundstate structure # saved to...") - gs_structure = Structure.from_file( - f"{self.VASP_CDTE_DATA_DIR}/{defect}/Groundstate/POSCAR" - ) + gs_structure = Structure.from_file(f"{self.VASP_CDTE_DATA_DIR}/{defect}/Groundstate/POSCAR") self.assertEqual(gs_structure, self.V_Cd_minus0pt55_CONTCAR_struc) if_present_rm(f"{self.VASP_CDTE_DATA_DIR}/{defect}/Groundstate") @@ -4512,9 +4077,7 @@ def test_groundstate(self): result.output, ) gs_structure = Structure.from_file(f"{defect}/Groundstate/POSCAR") - V_Ti_minus0pt4_structure = Structure.from_file( - f"{defect}/Bond_Distortion_-40.0%/CONTCAR" - ) + V_Ti_minus0pt4_structure = Structure.from_file(f"{defect}/Bond_Distortion_-40.0%/CONTCAR") self.assertEqual(gs_structure, V_Ti_minus0pt4_structure) # test groundstate with only Unperturbed not high energy (should still write groundstate fine) @@ -4526,9 +4089,7 @@ def test_groundstate(self): os.chdir("v_Ti_0") os.remove("Bond_Distortion_-40.0%/OUTCAR") os.remove("Unperturbed/OUTCAR") - dumpfn( - {"distortions": {}, "Unperturbed": -822.66563022}, "v_Ti_0.yaml" - ) # only Unperturbed + dumpfn({"distortions": {}, "Unperturbed": -822.66563022}, "v_Ti_0.yaml") # only Unperturbed # parsed as all high energy result = runner.invoke( diff --git a/tests/test_distortions.py b/tests/test_distortions.py index eaa819e1..62a73d55 100644 --- a/tests/test_distortions.py +++ b/tests/test_distortions.py @@ -5,7 +5,9 @@ import numpy as np from monty.serialization import loadfn -from pymatgen.core.structure import Structure +from pymatgen.core.structure import Structure, Lattice +from pymatgen.core.sites import PeriodicSite +from pymatgen.core.periodic_table import Element from shakenbreak import distortions, analysis @@ -122,6 +124,46 @@ def test_distort_V_Cd(self, mock_print): + " Distorted Neighbour Distances:\n\t[]" ) + @patch("builtins.print") + def test_distort_degenerate_case(self, mock_print): + """ + Test bond distortion function for case of degenerate distances (but non-degenerate + NN distortion combinations). + """ + # create a pymatgen structure with octahedral coordination of vacant site + # (this is perovskite-like) + lattice = Lattice.cubic(3.0) + sites = [ + PeriodicSite(Element("H"), [0, 0, 0], lattice), + PeriodicSite(Element("H"), [0, 0.5, 0.5], lattice), + PeriodicSite(Element("H"), [0.5, 0, 0.5], lattice), + PeriodicSite(Element("H"), [0.5, 0.5, 0], lattice), + # PeriodicSite(Element("V"), [0.5, 0.5, 0.5], lattice), # vacancy site + ] + structure = Structure.from_sites(sites) + vac_coords = np.array([0.5, 0.5, 0.5]) # centre of octahedral coordination + output = distortions.distort( + structure, 2, + 0.5, frac_coords=vac_coords, verbose=True) + self.assertEqual(output["undistorted_structure"], structure) + self.assertEqual(output["num_distorted_neighbours"], 2) + np.testing.assert_array_equal(output["defect_frac_coords"], vac_coords) + self.assertEqual(output.get("defect_site_index"), None) + self.assertEqual(output["distorted_atoms"], [[2, "H"], [3, "H"]]) + + # get min distance in distorted structure: + # (would be 1.5 Å if trans NN combo distorted, but shorter for cis distortion combo) + dist_matrix = output["distorted_structure"].distance_matrix.flatten() + min_dist = np.min(dist_matrix[dist_matrix > 0]) + self.assertAlmostEqual(min_dist, 1.06, places=2) + + mock_print.assert_called_with( + f"\tDefect Site Index / Frac Coords: {vac_coords}\n" + " Original Neighbour Distances: [(1.5, 2, 'H'), (1.5, 3, 'H')]\n" + " Distorted Neighbour Distances:\n\t[(0.75, 2, 'H'), (0.75, 3, 'H')]" + ) + + @patch("builtins.print") def test_distort_Int_Cd_2(self, mock_print): """Test bond distortion function for Int_Cd_2""" @@ -144,18 +186,16 @@ def test_distort_Int_Cd_2(self, mock_print): + " Distorted Neighbour Distances:\n\t[(1.09, 10, 'Cd'), (1.09, 22, 'Cd')]" ) - # test correct behaviour with `num_nearest_neighbours` is greater than number of - # `distorted_element` atoms withing 4.5 Å of the defect site + # test correct behaviour with odd parameters input: output = distortions.distort( self.Int_Cd_2_struc, num_nearest_neighbours=10, distortion_factor=0.4, distorted_element="Cd", site_index=site_index, + verbose=True, ) - self.assertEqual( - output["distorted_structure"], self.Int_Cd_2_minus0pt6_NN_10_struc_rattled - ) + self.assertEqual(output["distorted_structure"], self.Int_Cd_2_minus0pt6_NN_10_struc_rattled) self.assertEqual(output["undistorted_structure"], self.Int_Cd_2_struc) self.assertEqual(output["num_distorted_neighbours"], 10) self.assertEqual(output["defect_site_index"], 65) @@ -170,99 +210,41 @@ def test_distort_Int_Cd_2(self, mock_print): [14, "Cd"], [24, "Cd"], [30, "Cd"], - [38, "Te"], - [54, "Te"], - [62, "Te"], + [2, "Cd"], + [3, "Cd"], + [5, "Cd"], ], ) - distortions.distort( - self.Int_Cd_2_struc, - num_nearest_neighbours=10, - distortion_factor=0.4, - site_index=site_index, - distorted_element="Cd", - verbose=True, - ) mock_print.assert_called_with( - f"\tDefect Site Index / Frac Coords: {site_index}\n" - + " Original Neighbour Distances: [(2.71, 10, 'Cd'), (2.71, 22, 'Cd'), " - + "(2.71, 29, 'Cd'), (4.25, 1, 'Cd'), (4.25, 14, 'Cd'), (4.25, 24, 'Cd'), (4.25, 30, " - + "'Cd'), (2.71, 38, 'Te'), (2.71, 54, 'Te'), (2.71, 62, 'Te')]\n" - + " Distorted Neighbour Distances:\n\t[(1.09, 10, 'Cd'), (1.09, 22, 'Cd'), " - + "(1.09, 29, 'Cd'), (1.7, 1, 'Cd'), (1.7, 14, 'Cd'), (1.7, 24, 'Cd'), " - + "(1.7, 30, 'Cd'), (1.09, 38, 'Te'), (1.09, 54, 'Te'), (1.09, 62, 'Te')]" + "\tDefect Site Index / Frac Coords: 65\n" + " Original Neighbour Distances: [(2.71, 10, 'Cd'), (2.71, 22, 'Cd'), " + "(2.71, 29, 'Cd'), (4.25, 1, 'Cd'), (4.25, 14, 'Cd'), (4.25, 24, 'Cd'), (4.25, 30, 'Cd'), " + "(5.36, 2, 'Cd'), (5.36, 3, 'Cd'), (5.36, 5, 'Cd')]\n" + " Distorted Neighbour Distances:\n\t[(1.09, 10, 'Cd'), (1.09, 22, 'Cd'), " + "(1.09, 29, 'Cd'), (1.7, 1, 'Cd'), (1.7, 14, 'Cd'), (1.7, 24, 'Cd'), (1.7, 30, 'Cd'), " + "(2.15, 2, 'Cd'), (2.15, 3, 'Cd'), (2.15, 5, 'Cd')]" ) - def test_distort_warnings(self): - """Test warning messages for bond distortion function""" + def test_distorted_element_error(self): + """Test error message when non-existent element symbol provided""" site_index = 65 # Cd interstitial site index (VASP indexing) for missing_element in ["C", "O", "H", "N", "S", "P", "X"]: for num_neighbours in range(8): for distortion_factor in np.arange(-0.6, 0.61, 0.1): - with warnings.catch_warnings(record=True) as w: + with self.assertRaises(ValueError) as e: distortions.distort( - self.Int_Cd_2_struc, # cause warning for no `missing_element` - # neighbours + self.Int_Cd_2_struc, # cause error for no `missing_element` neighbours num_nearest_neighbours=num_neighbours, distortion_factor=distortion_factor, site_index=site_index, distorted_element=missing_element, ) - warning_message = ( - f"{missing_element} was specified as the nearest neighbour element to " - f"distort, with `distortion_factor` {distortion_factor} but did not " - f"find `num_nearest_neighbours` ({num_neighbours}) of these elements " - f"within 4.5 Å of the defect site. For the remaining neighbours to " - f"distort, we ignore the elemental identity. The final distortion " - f"information is:" + + missing_elt_error = ValueError( + f"No atoms of `distorted_element` = {missing_element} found in the defect " + f"structure, cannot apply bond distortions." ) - user_warnings = [ - warning for warning in w if warning.category == UserWarning - ] - if num_neighbours > 0: - self.assertEqual(len(user_warnings), 1) - self.assertIn( - warning_message, str(user_warnings[0].message) - ) - else: - self.assertEqual( - len(user_warnings), 0 - ) # No warning if we distort none - - # test the case where we do have some of the `distorted_element` neighbours, but less than - # `num_nearest_neighbours` of them with 4.5 Å of the defect site - for num_neighbours in range(12): - for distortion_factor in np.arange(-0.6, 0.61, 0.1): - with warnings.catch_warnings(record=True) as w: - distortions.distort( - self.Int_Cd_2_struc, # we have 3 Cd at 2.71 Å, 4 Cd at 4.25 Å from the - # defect site - num_nearest_neighbours=num_neighbours, - distortion_factor=distortion_factor, - site_index=site_index, - distorted_element="Cd", - ) - warning_message = ( - f"Cd was specified as the nearest neighbour element to " - f"distort, with `distortion_factor` {distortion_factor} but did not " - f"find `num_nearest_neighbours` ({num_neighbours}) of these elements " - f"within 4.5 Å of the defect site. For the remaining neighbours to " - f"distort, we ignore the elemental identity. The final distortion " - f"information is:" - ) - user_warnings = [ - warning for warning in w if warning.category == UserWarning - ] - if ( - num_neighbours > 7 - ): # should only give warning when more than 7 distorted - # neighbours requested - self.assertEqual(len(user_warnings), 1) - self.assertIn(warning_message, str(user_warnings[0].message)) - else: - self.assertEqual( - len(user_warnings), 0 - ) # No warning if we distort none + self.assertIn(missing_elt_error, e.exception) def test_rattle_V_Cd(self): """Test structure rattle function for V_Cd""" diff --git a/tests/test_energy_lowering_distortions.py b/tests/test_energy_lowering_distortions.py index 5c50570e..9add39fd 100644 --- a/tests/test_energy_lowering_distortions.py +++ b/tests/test_energy_lowering_distortions.py @@ -7,10 +7,22 @@ import ase import numpy as np from monty.serialization import dumpfn, loadfn -from pymatgen.core.structure import Structure +from pymatgen.core.structure import Structure, Composition, IStructure, PeriodicSite from shakenbreak import analysis, distortions, energy_lowering_distortions, io +# use doped efficiency functions for speed (speeds up structure matching dramatically): +from doped.utils.efficiency import Composition as doped_Composition +from doped.utils.efficiency import IStructure as doped_IStructure +from doped.utils.efficiency import PeriodicSite as doped_PeriodicSite + +Composition.__instances__ = {} +Composition.__eq__ = doped_Composition.__eq__ +PeriodicSite.__eq__ = doped_PeriodicSite.__eq__ +PeriodicSite.__hash__ = doped_PeriodicSite.__hash__ +IStructure.__instances__ = {} +IStructure.__eq__ = doped_IStructure.__eq__ + def if_present_rm(path): if os.path.exists(path): @@ -109,6 +121,7 @@ def tearDown(self): ]: # everything but VASP data dir if_present_rm(os.path.join(data_dir, "vac_1_Cd_0", "fake_vac_1_Cd_0.yaml")) if_present_rm(os.path.join(data_dir, "vac_1_Cd_0", "vac_1_Cd_0.yaml")) + if_present_rm(os.path.join(data_dir, "vac_1_Cd_0", "default_INCAR")) for defect_dir in os.listdir(os.path.join(data_dir, "vac_1_Cd_0")): if "Bond_Distortion_30.0%" not in defect_dir and os.path.isdir( os.path.join(data_dir, "vac_1_Cd_0", defect_dir) @@ -119,6 +132,12 @@ def tearDown(self): if file not in orig_files: if_present_rm(os.path.join(data_dir, "vac_1_Cd_0", "Bond_Distortion_30.0%", file)) + if os.path.exists(f"{self.VASP_CDTE_DATA_DIR}/vac_1_Cd_0/Bond_Distortion_-10.0%/CONTCAR_original"): + shutil.move( + f"{self.VASP_CDTE_DATA_DIR}/vac_1_Cd_0/Bond_Distortion_-10.0%/CONTCAR_original", + f"{self.VASP_CDTE_DATA_DIR}/vac_1_Cd_0/Bond_Distortion_-10.0%/CONTCAR", + ) + def test__format_distortion_directory_name(self): self.assertEqual( "my_output_path/my_defect_species/Unperturbed_from_+2", @@ -1008,7 +1027,7 @@ def test_write_retest_inputs(self): "vac_1_Cd_-1/Bond_Distortion_-55.0%_from_0/structure.cif", ) ) - self.assertTrue(analysis._calculate_atomic_disp(struct, self.V_Cd_minus_0pt55_structure)[0] < 0.01) + self.assertTrue(analysis._cached_calculate_atomic_disp(struct, self.V_Cd_minus_0pt55_structure)[0] < 0.01) # Test copying over Quantum Espresso input files shutil.move( # avoid overwriting yaml file @@ -1058,7 +1077,7 @@ def test_write_retest_inputs(self): ) as f: atoms = ase.io.espresso.read_espresso_in(f) struct = Structure.from_ase_atoms(atoms) - self.assertTrue(analysis._calculate_atomic_disp(struct, self.V_Cd_minus_0pt55_structure)[0] < 0.01) + self.assertTrue(analysis._cached_calculate_atomic_disp(struct, self.V_Cd_minus_0pt55_structure)[0] < 0.01) # Test copying over FHI-aims input files when the input files are only # present in one distortion directory (different from Unperturbed) @@ -1107,7 +1126,7 @@ def test_write_retest_inputs(self): "vac_1_Cd_-1/Bond_Distortion_-55.0%_from_0/geometry.in", ) ) - self.assertTrue(analysis._calculate_atomic_disp(struct, self.V_Cd_minus_0pt55_structure)[0] < 0.01) + self.assertTrue(analysis._cached_calculate_atomic_disp(struct, self.V_Cd_minus_0pt55_structure)[0] < 0.01) # Test CASTEP input files shutil.move( # avoid overwriting yaml file @@ -1161,7 +1180,7 @@ def test_write_retest_inputs(self): ) ) ) - self.assertTrue(analysis._calculate_atomic_disp(struct, self.V_Cd_minus_0pt55_structure)[0] < 0.01) + self.assertTrue(analysis._cached_calculate_atomic_disp(struct, self.V_Cd_minus_0pt55_structure)[0] < 0.01) if __name__ == "__main__": diff --git a/tests/test_input.py b/tests/test_input.py index 1480fb87..6b608072 100644 --- a/tests/test_input.py +++ b/tests/test_input.py @@ -11,7 +11,6 @@ import numpy as np from ase.build import bulk, make_supercell -from ase.calculators.aims import Aims from ase.io import read from doped.generation import get_defect_name_from_entry from doped.vasp import _test_potcar_functional_choice, DefectRelaxSet @@ -46,9 +45,7 @@ def _potcars_available() -> bool: return False -def _update_struct_defect_dict( - defect_dict: dict, structure: Structure, poscar_comment: str -) -> dict: +def _update_struct_defect_dict(defect_dict: dict, structure: Structure, poscar_comment: str) -> dict: """ Given a Structure object and POSCAR comment, update the folders dictionary (generated with `doped.vasp_input.prepare_vasp_defect_inputs()`) with the given values. @@ -80,9 +77,7 @@ def _get_defect_entry_from_defect( # from example notebook # Dummy species (used to keep track of the defect coords in the supercell) # Find its fractional coordinates & remove it from supercell dummy_site = [ - site - for site in defect_supercell - if site.species.elements[0].symbol == dummy_species.symbol + site for site in defect_supercell if site.species.elements[0].symbol == dummy_species.symbol ][0] sc_defect_frac_coords = dummy_site.frac_coords defect_supercell.remove(dummy_site) @@ -165,51 +160,36 @@ def setUpClass(cls): # Refactor doped defect dict to list of list of DefectEntrys() objects # (there's a DefectEntry for each charge state) cls.cdte_defect_list = sum(list(cls.cdte_defects.values()), []) - cls.CdTe_extrinsic_defect_list = sum( - list(cls.cdte_extrinsic_defects.values()), [] - ) + cls.CdTe_extrinsic_defect_list = sum(list(cls.cdte_extrinsic_defects.values()), []) cls.V_Cd_dict = cls.cdte_doped_defect_dict["vacancies"][0] cls.Int_Cd_2_dict = cls.cdte_doped_defect_dict["interstitials"][1] # Refactor to Defect() objects - cls.V_Cd = input.generate_defect_object( - cls.V_Cd_dict, cls.cdte_doped_defect_dict["bulk"] - ) + cls.V_Cd = input.generate_defect_object(cls.V_Cd_dict, cls.cdte_doped_defect_dict["bulk"]) cls.V_Cd.user_charges = cls.V_Cd_dict["charges"] - cls.V_Cd_entry = input._get_defect_entry_from_defect( - cls.V_Cd, cls.V_Cd.user_charges[0] - ) - cls.V_Cd_entry_neutral = input._get_defect_entry_from_defect( - cls.V_Cd, 0 - ) + cls.V_Cd_entry = input._get_defect_entry_from_defect(cls.V_Cd, cls.V_Cd.user_charges[0]) + cls.V_Cd_entry_neutral = input._get_defect_entry_from_defect(cls.V_Cd, 0) cls.V_Cd_entries = [ - input._get_defect_entry_from_defect(cls.V_Cd, c) - for c in cls.V_Cd.user_charges + input._get_defect_entry_from_defect(cls.V_Cd, c) for c in cls.V_Cd.user_charges ] - cls.Int_Cd_2 = input.generate_defect_object( - cls.Int_Cd_2_dict, cls.cdte_doped_defect_dict["bulk"] - ) + cls.Int_Cd_2 = input.generate_defect_object(cls.Int_Cd_2_dict, cls.cdte_doped_defect_dict["bulk"]) cls.Int_Cd_2.user_charges = cls.Int_Cd_2.user_charges cls.Int_Cd_2_entry = input._get_defect_entry_from_defect( cls.Int_Cd_2, cls.Int_Cd_2.user_charges[0] ) cls.Int_Cd_2_entries = [ - input._get_defect_entry_from_defect(cls.Int_Cd_2, c) - for c in cls.Int_Cd_2.user_charges + input._get_defect_entry_from_defect(cls.Int_Cd_2, c) for c in cls.Int_Cd_2.user_charges ] # Setup structures and add oxidation states (as pymatgen-analysis-defects does it) - cls.V_Cd_struc = Structure.from_file( - os.path.join(cls.VASP_CDTE_DATA_DIR, "CdTe_V_Cd_POSCAR") - ) + cls.V_Cd_struc = Structure.from_file(os.path.join(cls.VASP_CDTE_DATA_DIR, "CdTe_V_Cd_POSCAR")) cls.V_Cd_minus0pt5_struc_rattled = Structure.from_file( - os.path.join( - cls.VASP_CDTE_DATA_DIR, "CdTe_V_Cd_-50%_Distortion_Rattled_POSCAR" - ) + os.path.join(cls.VASP_CDTE_DATA_DIR, "CdTe_V_Cd_-50%_Distortion_Rattled_POSCAR") + ) + cls.V_Cd_dimer_struc_0pt1_rattled = Structure.from_file( + os.path.join(cls.VASP_CDTE_DATA_DIR, "CdTe_V_Cd_Dimer_Rattled_0pt1_POSCAR") ) cls.V_Cd_dimer_struc_0pt25_rattled = Structure.from_file( - os.path.join( - cls.VASP_CDTE_DATA_DIR, "CdTe_V_Cd_Dimer_Rattled_0pt25_POSCAR" - ) + os.path.join(cls.VASP_CDTE_DATA_DIR, "CdTe_V_Cd_Dimer_Rattled_0pt25_POSCAR") ) cls.V_Cd_minus0pt5_struc_0pt1_rattled = Structure.from_file( os.path.join( @@ -220,24 +200,21 @@ def setUpClass(cls): cls.V_Cd_minus0pt5_struc_kwarged = Structure.from_file( os.path.join(cls.VASP_CDTE_DATA_DIR, "CdTe_V_Cd_-50%_Kwarged_POSCAR") ) + cls.V_Cd_dimer_struc_kwarged = Structure.from_file( + os.path.join(cls.VASP_CDTE_DATA_DIR, "CdTe_V_Cd_Dimer_0p15_kwarged_POSCAR") + ) cls.Int_Cd_2_struc = Structure.from_file( os.path.join(cls.VASP_CDTE_DATA_DIR, "CdTe_Int_Cd_2_POSCAR") ) cls.Int_Cd_2_minus0pt6_struc_rattled = Structure.from_file( - os.path.join( - cls.VASP_CDTE_DATA_DIR, "CdTe_Int_Cd_2_-60%_Distortion_Rattled_POSCAR" - ) + os.path.join(cls.VASP_CDTE_DATA_DIR, "CdTe_Int_Cd_2_-60%_Distortion_Rattled_POSCAR") ) cls.Int_Cd_2_minus0pt6_NN_10_struc_unrattled = Structure.from_file( - os.path.join( - cls.VASP_CDTE_DATA_DIR, "CdTe_Int_Cd_2_-60%_Distortion_NN_10_POSCAR" - ) + os.path.join(cls.VASP_CDTE_DATA_DIR, "CdTe_Int_Cd_2_-60%_Distortion_NN_10_POSCAR") ) # get example INCAR: - cls.V_Cd_INCAR_file = os.path.join( - cls.VASP_CDTE_DATA_DIR, "vac_1_Cd_0/default_INCAR" - ) + cls.V_Cd_INCAR_file = os.path.join(cls.VASP_CDTE_DATA_DIR, "vac_1_Cd_0/default_INCAR") cls.V_Cd_INCAR = Incar.from_file(cls.V_Cd_INCAR_file) # Setup distortion parameters @@ -245,6 +222,9 @@ def setUpClass(cls): "unique_site": np.array([0.0, 0.0, 0.0]), "num_distorted_neighbours": 2, "distorted_atoms": [(33, "Te"), (42, "Te")], + # Add Dimer-related info: + "num_distorted_neighbours_in_dimer": 2, + "distorted_atoms_in_dimer": [[32, "Te"], [41, "Te"]], } cls.Int_Cd_2_normal_distortion_parameters = { "unique_site": cls.Int_Cd_2_dict["unique_site"].frac_coords, @@ -264,9 +244,9 @@ def setUpClass(cls): (14 + 1, "Cd"), (24 + 1, "Cd"), (30 + 1, "Cd"), - (38 + 1, "Te"), - (54 + 1, "Te"), - (62 + 1, "Te"), + (2 + 1, "Cd"), + (3 + 1, "Cd"), + (5 + 1, "Cd"), # +1 because interstitial is added at the beginning of the structure ], "defect_site_index": 1, @@ -432,13 +412,9 @@ def setUpClass(cls): # Get the current locale setting cls.original_locale = locale.getlocale(locale.LC_CTYPE) # should be UTF-8 - cls.Ag_Sb_AgSbTe2_m2_defect_entry = loadfn( - f"{cls.DATA_DIR}/Ag_Sb_Cs_Te2.90_-2.json" - ) + cls.Ag_Sb_AgSbTe2_m2_defect_entry = loadfn(f"{cls.DATA_DIR}/Ag_Sb_Cs_Te2.90_-2.json") # Generate defect entry for V_Cd in CdSeTe - defect_structure = Structure.from_file( - os.path.join(cls.VASP_DIR, "CdSeTe_v_Cd.POSCAR") - ) + defect_structure = Structure.from_file(os.path.join(cls.VASP_DIR, "CdSeTe_v_Cd.POSCAR")) coords = [0.986350003237154, 0.4992578370461876, 0.9995065238765345] bulk = defect_structure.copy() bulk.append("Cd", coords, coords_are_cartesian=False) @@ -447,21 +423,20 @@ def setUpClass(cls): bulk_structure=bulk, ) # Generate a defect entry for each charge state - cls.V_Cd_in_CdSeTe_entry = input._get_defect_entry_from_defect( - defect=defect, charge_state=0 - ) + cls.V_Cd_in_CdSeTe_entry = input._get_defect_entry_from_defect(defect=defect, charge_state=0) def tearDown(self) -> None: # reset locale: - locale.setlocale(locale.LC_CTYPE, self.original_locale) # should be UTF-8 + try: + locale.setlocale(locale.LC_CTYPE, self.original_locale) # should be UTF-8 + except locale.Error: + locale.setlocale(locale.LC_CTYPE, "C") # Fallback to a safe default # remove test-generated defect folders if present for i in self.cdte_defect_folders_old_names + self.cdte_defect_folders: if_present_rm(i) for i in os.listdir(): - if os.path.isdir(i) and any( - x in i for x in ["v_Te", "v_Cd", "vac_1_Cd", "Ag_Sb"] - ): + if os.path.isdir(i) and any(x in i for x in ["v_Te", "v_Cd", "vac_1_Cd", "Ag_Sb"]): if_present_rm(i) for fname in os.listdir("./"): if fname.endswith("json") or fname.endswith("png"): @@ -470,7 +445,8 @@ def tearDown(self) -> None: regen_defect_folder_names = [ get_defect_name_from_entry(self.cdte_defects[old_key][0], relaxed=False) - for old_key in self.new_names_old_names_CdTe.values()] + for old_key in self.new_names_old_names_CdTe.values() + ] for i in os.listdir(): if os.path.isdir(i) and any(x in i for x in regen_defect_folder_names): if_present_rm(i) @@ -601,9 +577,7 @@ def test_apply_rattle_bond_distortions_V_Cd(self): rattling_atom_indices = np.arange(0, 63) idx = np.in1d(rattling_atom_indices, [i - 1 for i in [33, 42]]) - rattling_atom_indices = rattling_atom_indices[ - ~idx - ] # removed distorted Te indices + rattling_atom_indices = rattling_atom_indices[~idx] # removed distorted Te indices output["distorted_structure"] = rattle( # overwrite with distorted and rattle # structure output["distorted_structure"], @@ -652,13 +626,9 @@ def test_apply_rattle_bond_distortions_Int_Cd_2(self): output, ) # Shouldn't match because rattling not done yet - rattling_atom_indices = np.arange( - 0, 64 - ) # not including index 64 which is Int_Cd_2 + rattling_atom_indices = np.arange(0, 64) # not including index 64 which is Int_Cd_2 idx = np.in1d(rattling_atom_indices, [i - 1 for i in [10, 22]]) - rattling_atom_indices = rattling_atom_indices[ - ~idx - ] # removed distorted Cd indices + rattling_atom_indices = rattling_atom_indices[~idx] # removed distorted Cd indices output["distorted_structure"] = rattle( # overwrite with distorted and rattle output["distorted_structure"], d_min=d_min, @@ -702,9 +672,7 @@ def test_apply_rattle_bond_distortions_kwargs(self, mock_print): Int_Cd_2_distorted_dict["distorted_structure"], self.Int_Cd_2_minus0pt6_NN_10_struc_unrattled, ) - self.assertEqual( - Int_Cd_2_distorted_dict["undistorted_structure"], self.Int_Cd_2_struc - ) + self.assertEqual(Int_Cd_2_distorted_dict["undistorted_structure"], self.Int_Cd_2_struc) self.assertEqual(Int_Cd_2_distorted_dict["num_distorted_neighbours"], 10) self.assertEqual(Int_Cd_2_distorted_dict["defect_site_index"], 1) self.assertEqual(Int_Cd_2_distorted_dict.get("defect_frac_coords"), None) @@ -718,20 +686,20 @@ def test_apply_rattle_bond_distortions_kwargs(self, mock_print): [14 + 1, "Cd"], [24 + 1, "Cd"], [30 + 1, "Cd"], - [38 + 1, "Te"], - [54 + 1, "Te"], - [62 + 1, "Te"], + [2 + 1, "Cd"], + [3 + 1, "Cd"], + [5 + 1, "Cd"], ], ) # Interstitial is added at the beginning - shift all indexes + 1 mock_print.assert_called_with( - f"\tDefect Site Index / Frac Coords: 1\n" - + " Original Neighbour Distances: [(2.71, 11, 'Cd'), (2.71, 23, 'Cd'), " - + "(2.71, 30, 'Cd'), (4.25, 2, 'Cd'), (4.25, 15, 'Cd'), (4.25, 25, 'Cd'), (4.25, 31, " - + "'Cd'), (2.71, 39, 'Te'), (2.71, 55, 'Te'), (2.71, 63, 'Te')]\n" - + " Distorted Neighbour Distances:\n\t[(1.09, 11, 'Cd'), (1.09, 23, 'Cd'), " - + "(1.09, 30, 'Cd'), (1.7, 2, 'Cd'), (1.7, 15, 'Cd'), (1.7, 25, 'Cd'), " - + "(1.7, 31, 'Cd'), (1.09, 39, 'Te'), (1.09, 55, 'Te'), (1.09, 63, 'Te')]" + "\tDefect Site Index / Frac Coords: 1\n" + " Original Neighbour Distances: [(2.71, 11, 'Cd'), (2.71, 23, 'Cd'), " + "(2.71, 30, 'Cd'), (4.25, 2, 'Cd'), (4.25, 15, 'Cd'), (4.25, 25, 'Cd'), (4.25, 31, " + "'Cd'), (5.36, 3, 'Cd'), (5.36, 4, 'Cd'), (5.36, 6, 'Cd')]\n" + " Distorted Neighbour Distances:\n\t[(1.09, 11, 'Cd'), (1.09, 23, 'Cd'), " + "(1.09, 30, 'Cd'), (1.7, 2, 'Cd'), (1.7, 15, 'Cd'), (1.7, 25, 'Cd'), " + "(1.7, 31, 'Cd'), (2.15, 3, 'Cd'), (2.15, 4, 'Cd'), (2.15, 6, 'Cd')]" ) # test all possible rattling kwargs with V_Cd @@ -759,15 +727,11 @@ def test_apply_rattle_bond_distortions_kwargs(self, mock_print): V_Cd_kwarg_distorted_dict["distorted_structure"], self.V_Cd_minus0pt5_struc_kwarged, ) - self.assertEqual( - V_Cd_kwarg_distorted_dict["undistorted_structure"], self.V_Cd_struc - ) + self.assertEqual(V_Cd_kwarg_distorted_dict["undistorted_structure"], self.V_Cd_struc) self.assertEqual(V_Cd_kwarg_distorted_dict["num_distorted_neighbours"], 2) self.assertEqual(V_Cd_kwarg_distorted_dict.get("defect_site_index"), None) vac_coords = np.array([0, 0, 0]) # Cd vacancy fractional coordinates - np.testing.assert_array_equal( - V_Cd_kwarg_distorted_dict.get("defect_frac_coords"), vac_coords - ) + np.testing.assert_array_equal(V_Cd_kwarg_distorted_dict.get("defect_frac_coords"), vac_coords) def test_apply_rattle_bond_distortions_V_Cd_dimer(self): """Test _apply_rattle_bond_distortions function with dimer distortion @@ -789,9 +753,7 @@ def test_apply_rattle_bond_distortions_V_Cd_dimer(self): rattling_atom_indices = np.arange(0, 63) idx = np.in1d(rattling_atom_indices, [i - 1 for i in [41, 32]]) - rattling_atom_indices = rattling_atom_indices[ - ~idx - ] # removed distorted Te indices + rattling_atom_indices = rattling_atom_indices[~idx] # removed distorted Te indices output["distorted_structure"] = rattle( # overwrite with distorted and rattle # structure output["distorted_structure"], @@ -828,9 +790,7 @@ def test_apply_snb_distortions_V_Cd(self): ) self.assertEqual(self.V_Cd_entry, V_Cd_distorted_dict["Unperturbed"]) - distorted_V_Cd_struc = V_Cd_distorted_dict["distortions"][ - "Bond_Distortion_-50.0%" - ] + distorted_V_Cd_struc = V_Cd_distorted_dict["distortions"]["Bond_Distortion_-50.0%"] distorted_V_Cd_struc.remove_oxidation_states() # pymatgen-analysis-defects add ox. states self.assertNotEqual(self.V_Cd_struc, distorted_V_Cd_struc) self.assertEqual(self.V_Cd_minus0pt5_struc_rattled, distorted_V_Cd_struc) @@ -838,17 +798,20 @@ def test_apply_snb_distortions_V_Cd(self): V_Cd_0pt1_distorted_dict = input.apply_snb_distortions( self.V_Cd_entry, num_nearest_neighbours=2, - bond_distortions=[-0.5], + bond_distortions=[ + -0.5, + ], stdev=0.1, verbose=True, seed=42, # old default ) - distorted_V_Cd_struc = V_Cd_0pt1_distorted_dict["distortions"][ - "Bond_Distortion_-50.0%" - ] + distorted_V_Cd_struc = V_Cd_0pt1_distorted_dict["distortions"]["Bond_Distortion_-50.0%"] distorted_V_Cd_struc.remove_oxidation_states() self.assertNotEqual(self.V_Cd_struc, distorted_V_Cd_struc) self.assertEqual(self.V_Cd_minus0pt5_struc_0pt1_rattled, distorted_V_Cd_struc) + distorted_V_Cd_dimer_struc = V_Cd_0pt1_distorted_dict["distortions"]["Dimer"] + distorted_V_Cd_dimer_struc.remove_oxidation_states() + self.assertEqual(self.V_Cd_dimer_struc_0pt1_rattled, distorted_V_Cd_dimer_struc) np.testing.assert_equal( V_Cd_distorted_dict["distortion_parameters"], @@ -884,17 +847,39 @@ def test_apply_snb_distortions_V_Cd(self): key = f"Bond_Distortion_{round(distortion,3)+0:.1%}" self.assertIn(key, V_Cd_distorted_dict["distortions"]) self.assertNotEqual(prev_struc, V_Cd_distorted_dict["distortions"][key]) - prev_struc = V_Cd_distorted_dict["distortions"][ - key - ] # different structure for each + prev_struc = V_Cd_distorted_dict["distortions"][key] # different structure for each # distortion mock_print.assert_any_call(f"--Distortion {round(distortion,3)+0:.1%}") + # Check Dimer added for vacancies + mock_print.assert_any_call(f"--Distortion Dimer") # plus some hard-coded checks self.assertIn("Bond_Distortion_-60.0%", V_Cd_distorted_dict["distortions"]) self.assertIn("Bond_Distortion_60.0%", V_Cd_distorted_dict["distortions"]) # test zero distortion is written as positive zero (not "-0.0%") self.assertIn("Bond_Distortion_0.0%", V_Cd_distorted_dict["distortions"]) + # Check Dimer added for vacancies + self.assertIn("Dimer", V_Cd_distorted_dict["distortions"]) + + def test_apply_snb_distortions_distorted_elements(self): + """ + Test apply_snb_distortions function for V_Cd with distorted_element + kwargs set to ["Cd", "Te"], giving same behaviour. + """ + V_Cd_distorted_dict = input.apply_snb_distortions( + self.V_Cd_entry, + num_nearest_neighbours=2, + bond_distortions=[-0.5], + stdev=0.25, + verbose=True, + seed=42, # old default + distorted_element=["Cd", "Te"], + ) + self.assertEqual(self.V_Cd_entry, V_Cd_distorted_dict["Unperturbed"]) + distorted_V_Cd_struc = V_Cd_distorted_dict["distortions"]["Bond_Distortion_-50.0%"] + distorted_V_Cd_struc.remove_oxidation_states() # pymatgen-analysis-defects add ox. states + self.assertNotEqual(self.V_Cd_struc, distorted_V_Cd_struc) + self.assertEqual(self.V_Cd_minus0pt5_struc_rattled, distorted_V_Cd_struc) def test_apply_snb_distortions_Int_Cd_2(self): """Test apply_distortions function for Int_Cd_2""" @@ -907,18 +892,16 @@ def test_apply_snb_distortions_Int_Cd_2(self): verbose=True, ) self.assertEqual(self.Int_Cd_2_entry, Int_Cd_2_distorted_dict["Unperturbed"]) - distorted_Int_Cd_2_struc = Int_Cd_2_distorted_dict["distortions"][ - "Bond_Distortion_-60.0%" - ] + distorted_Int_Cd_2_struc = Int_Cd_2_distorted_dict["distortions"]["Bond_Distortion_-60.0%"] distorted_Int_Cd_2_struc.remove_oxidation_states() self.assertNotEqual(self.Int_Cd_2_struc, distorted_Int_Cd_2_struc) - self.assertEqual( - self.Int_Cd_2_minus0pt6_struc_rattled, distorted_Int_Cd_2_struc - ) + self.assertEqual(self.Int_Cd_2_minus0pt6_struc_rattled, distorted_Int_Cd_2_struc) np.testing.assert_equal( Int_Cd_2_distorted_dict["distortion_parameters"], self.Int_Cd_2_normal_distortion_parameters, ) + # Check Dimer distortion only added to vacancies by default + self.assertNotIn("Dimer", Int_Cd_2_distorted_dict["distortions"].keys()) @patch("builtins.print") def test_apply_snb_distortions_kwargs(self, mock_print): @@ -940,26 +923,22 @@ def test_apply_snb_distortions_kwargs(self, mock_print): self.Int_Cd_2_entry.defect, Int_Cd_2_distorted_dict["Unperturbed"].defect, ) - distorted_Int_Cd_2_struc = Int_Cd_2_distorted_dict["distortions"][ - "Bond_Distortion_-60.0%" - ] + distorted_Int_Cd_2_struc = Int_Cd_2_distorted_dict["distortions"]["Bond_Distortion_-60.0%"] distorted_Int_Cd_2_struc.remove_oxidation_states() self.assertNotEqual(self.Int_Cd_2_struc, distorted_Int_Cd_2_struc) - self.assertEqual( - self.Int_Cd_2_minus0pt6_NN_10_struc_unrattled, distorted_Int_Cd_2_struc - ) + self.assertEqual(self.Int_Cd_2_minus0pt6_NN_10_struc_unrattled, distorted_Int_Cd_2_struc) np.testing.assert_equal( Int_Cd_2_distorted_dict["distortion_parameters"], self.Int_Cd_2_NN_10_distortion_parameters, ) mock_print.assert_called_with( f"\tDefect Site Index / Frac Coords: 1\n" - + " Original Neighbour Distances: [(2.71, 11, 'Cd'), (2.71, 23, 'Cd'), " - + "(2.71, 30, 'Cd'), (4.25, 2, 'Cd'), (4.25, 15, 'Cd'), (4.25, 25, 'Cd'), (4.25, 31, " - + "'Cd'), (2.71, 39, 'Te'), (2.71, 55, 'Te'), (2.71, 63, 'Te')]\n" - + " Distorted Neighbour Distances:\n\t[(1.09, 11, 'Cd'), (1.09, 23, 'Cd'), " - + "(1.09, 30, 'Cd'), (1.7, 2, 'Cd'), (1.7, 15, 'Cd'), (1.7, 25, 'Cd'), " - + "(1.7, 31, 'Cd'), (1.09, 39, 'Te'), (1.09, 55, 'Te'), (1.09, 63, 'Te')]" + " Original Neighbour Distances: [(2.71, 11, 'Cd'), (2.71, 23, 'Cd'), " + "(2.71, 30, 'Cd'), (4.25, 2, 'Cd'), (4.25, 15, 'Cd'), (4.25, 25, 'Cd'), (4.25, 31, " + "'Cd'), (5.36, 3, 'Cd'), (5.36, 4, 'Cd'), (5.36, 6, 'Cd')]\n" + " Distorted Neighbour Distances:\n\t[(1.09, 11, 'Cd'), (1.09, 23, 'Cd'), " + "(1.09, 30, 'Cd'), (1.7, 2, 'Cd'), (1.7, 15, 'Cd'), (1.7, 25, 'Cd'), " + "(1.7, 31, 'Cd'), (2.15, 3, 'Cd'), (2.15, 4, 'Cd'), (2.15, 6, 'Cd')]" ) # test all possible rattling kwargs with V_Cd @@ -988,12 +967,13 @@ def test_apply_snb_distortions_kwargs(self, mock_print): self.V_Cd_entry.defect, V_Cd_kwarg_distorted_dict["Unperturbed"].defect, ) - distorted_V_Cd_struc = V_Cd_kwarg_distorted_dict["distortions"][ - "Bond_Distortion_-50.0%" - ] + distorted_V_Cd_struc = V_Cd_kwarg_distorted_dict["distortions"]["Bond_Distortion_-50.0%"] distorted_V_Cd_struc.remove_oxidation_states() self.assertNotEqual(self.V_Cd_struc, distorted_V_Cd_struc) self.assertEqual(self.V_Cd_minus0pt5_struc_kwarged, distorted_V_Cd_struc) + distorted_V_Cd_dimer_struc = V_Cd_kwarg_distorted_dict["distortions"]["Dimer"] + distorted_V_Cd_dimer_struc.remove_oxidation_states() + self.assertEqual(self.V_Cd_dimer_struc_kwarged, distorted_V_Cd_dimer_struc) np.testing.assert_equal( V_Cd_kwarg_distorted_dict["distortion_parameters"], self.V_Cd_distortion_parameters, @@ -1006,23 +986,23 @@ def test_apply_snb_distortions_V_Cd_dimer(self): V_Cd_distorted_dict = input.apply_snb_distortions( self.V_Cd_entry, num_nearest_neighbours=2, - bond_distortions=["dimer",], + bond_distortions=[ + "dimer", + ], d_min=d_min, stdev=0.25, verbose=True, seed=42, # old default ) self.assertEqual(self.V_Cd_entry, V_Cd_distorted_dict["Unperturbed"]) - distorted_V_Cd_struc = V_Cd_distorted_dict["distortions"][ - "Dimer" - ] + distorted_V_Cd_struc = V_Cd_distorted_dict["distortions"]["Dimer"] distorted_V_Cd_struc.remove_oxidation_states() # pymatgen-analysis-defects add ox. states self.assertNotEqual(self.V_Cd_struc, distorted_V_Cd_struc) distortion_parameters_dict = V_Cd_distorted_dict["distortion_parameters"] self.assertEqual(distortion_parameters_dict["num_distorted_neighbours_in_dimer"], 2) self.assertEqual( distortion_parameters_dict["distorted_atoms_in_dimer"], - [[32, "Te"], [41, "Te"]] # order of elements not important + [[32, "Te"], [41, "Te"]], # order of elements not important ) self.assertEqual(self.V_Cd_dimer_struc_0pt25_rattled, distorted_V_Cd_struc) @@ -1030,14 +1010,16 @@ def test_apply_snb_distortions_indexes(self): """Test selecting indices of atoms to distort""" dist_dict = input.apply_snb_distortions( defect_entry=self.V_Cd_in_CdSeTe_entry, - distorted_atoms=[33, 57], # Te, Se + distorted_atoms=[33, 57], # Te, Se num_nearest_neighbours=2, - bond_distortions=[0.1,], + bond_distortions=[ + 0.1, + ], verbose=True, ) self.assertEqual( dist_dict["distortion_parameters"]["distorted_atoms"], - [[57+1, 'Se'], [33+1, 'Te']] # indices start at 1 + [[57 + 1, "Se"], [33 + 1, "Te"]], # indices start at 1 ) # test create_folder and create_vasp_input simultaneously: @@ -1064,18 +1046,14 @@ def test_create_vasp_input(self): self.V_Cd_minus0pt5_struc_rattled, "V_Cd Rattled", ) - V_Cd_charged_defect_dict = { - "Bond_Distortion_-50.0%": V_Cd_updated_charged_defect_dict - } + V_Cd_charged_defect_dict = {"Bond_Distortion_-50.0%": V_Cd_updated_charged_defect_dict} self.assertFalse(os.path.exists("vac_1_Cd_0")) with warnings.catch_warnings(record=True) as w: input._create_vasp_input( "vac_1_Cd_0", distorted_defect_dict=V_Cd_charged_defect_dict, ) - V_Cd_POSCAR = self._check_V_Cd_rattled_poscar( - "vac_1_Cd_0/Bond_Distortion_-50.0%" - ) + V_Cd_POSCAR = self._check_V_Cd_rattled_poscar("vac_1_Cd_0/Bond_Distortion_-50.0%") kpoints = Kpoints.from_file("vac_1_Cd_0/Bond_Distortion_-50.0%/KPOINTS") self.assertEqual(kpoints.kpts, [(1, 1, 1)]) @@ -1091,9 +1069,7 @@ def test_create_vasp_input(self): } else: # test POTCAR warning print([str(warning.message) for warning in w]) - assert ( - len(w) == 2 - ) # general POTCAR warning and NELECT/NUPDOWN INCAR warning + assert len(w) == 2 # general POTCAR warning and NELECT/NUPDOWN INCAR warning assert any( "POTCAR directory not set up with pymatgen" in str(warning.message) for warning in w ) @@ -1151,28 +1127,18 @@ def test_create_vasp_input(self): user_incar_settings=kwarg_incar_settings, output_path="test_path", ) - V_Cd_POSCAR = self._check_V_Cd_rattled_poscar( - "test_path/vac_1_Cd_0/Bond_Distortion_-50.0%" - ) - kpoints = Kpoints.from_file( - "test_path/vac_1_Cd_0/Bond_Distortion_-50.0%/KPOINTS" - ) + V_Cd_POSCAR = self._check_V_Cd_rattled_poscar("test_path/vac_1_Cd_0/Bond_Distortion_-50.0%") + kpoints = Kpoints.from_file("test_path/vac_1_Cd_0/Bond_Distortion_-50.0%/KPOINTS") self.assertEqual(kpoints.kpts, [(1, 1, 1)]) if _potcars_available(): - assert self.V_Cd_INCAR != Incar.from_file( - "test_path/vac_1_Cd_0/Bond_Distortion_-50.0%/INCAR" - ) + assert self.V_Cd_INCAR != Incar.from_file("test_path/vac_1_Cd_0/Bond_Distortion_-50.0%/INCAR") kwarged_INCAR = self.V_Cd_INCAR.copy() kwarged_INCAR.update(kwarg_incar_settings) - assert kwarged_INCAR == Incar.from_file( - "test_path/vac_1_Cd_0/Bond_Distortion_-50.0%/INCAR" - ) + assert kwarged_INCAR == Incar.from_file("test_path/vac_1_Cd_0/Bond_Distortion_-50.0%/INCAR") # check if POTCARs have been written: - potcar = Potcar.from_file( - "test_path/vac_1_Cd_0/Bond_Distortion_-50.0%/POTCAR" - ) + potcar = Potcar.from_file("test_path/vac_1_Cd_0/Bond_Distortion_-50.0%/POTCAR") assert set(potcar.as_dict()["symbols"]) == { input.default_potcar_dict["POTCAR"][el_symbol] for el_symbol in V_Cd_POSCAR.structure.symbol_set @@ -1187,9 +1153,7 @@ def test_create_vasp_input(self): # then the previous folder will be overwritten: os.mkdir("vac_1_Cdb_0/Unperturbed") unperturbed_poscar = Poscar(self.V_Cd_struc) - unperturbed_poscar.comment = ( - "V_Cd Original" # will later check that this is overwritten - ) + unperturbed_poscar.comment = "V_Cd Original" # will later check that this is overwritten unperturbed_poscar.write_file("vac_1_Cdb_0/Unperturbed/POSCAR") # make unperturbed defect entry: V_Cd_charged_defect_dict["Unperturbed"] = _update_struct_defect_dict( @@ -1264,9 +1228,7 @@ def test_with_non_UTF_8_encoding(self): def _check_V_Cd_rattled_poscar(self, defect_dir): poscar = Poscar.from_file(f"{defect_dir}/POSCAR") - self.assertEqual( - len(poscar.site_symbols), len(set(poscar.site_symbols)) - ) # no duplicates + self.assertEqual(len(poscar.site_symbols), len(set(poscar.site_symbols))) # no duplicates self.assertEqual(poscar.comment, "V_Cd Rattled") self.assertEqual(poscar.structure, self.V_Cd_minus0pt5_struc_rattled) return poscar @@ -1274,8 +1236,7 @@ def _check_V_Cd_rattled_poscar(self, defect_dir): def _check_V_Cd_folder_renaming(self, w, top_dir, defect_dir): self.assertTrue( any( - f"{top_dir}{os.path.basename(os.path.abspath('.'))}{defect_dir}" - in str(warning.message) + f"{top_dir}{os.path.basename(os.path.abspath('.'))}{defect_dir}" in str(warning.message) for warning in w ) ) @@ -1295,9 +1256,7 @@ def test_generate_defect_object(self): list(defect.site.frac_coords), list(self.Int_Cd_2_dict["bulk_supercell_site"].frac_coords), ) - self.assertEqual( - str(defect.as_dict()["@class"].lower()), self.Int_Cd_2_dict["defect_type"] - ) + self.assertEqual(str(defect.as_dict()["@class"].lower()), self.Int_Cd_2_dict["defect_type"]) # Test vacancy vacancy = self.cdte_doped_defect_dict["vacancies"][0] defect = input.generate_defect_object( @@ -1309,9 +1268,7 @@ def test_generate_defect_object(self): list(defect.site.frac_coords), list(vacancy["bulk_supercell_site"].frac_coords), ) - self.assertEqual( - str(defect.as_dict()["@class"].lower()), vacancy["defect_type"] - ) + self.assertEqual(str(defect.as_dict()["@class"].lower()), vacancy["defect_type"]) # Test substitution subs = self.cdte_doped_defect_dict["substitutions"][0] defect = input.generate_defect_object( @@ -1319,9 +1276,7 @@ def test_generate_defect_object(self): bulk_dict=self.cdte_doped_defect_dict["bulk"], ) self.assertEqual(defect.user_charges, subs["charges"]) - self.assertEqual( - list(defect.site.frac_coords), list(subs["bulk_supercell_site"].frac_coords) - ) + self.assertEqual(list(defect.site.frac_coords), list(subs["bulk_supercell_site"].frac_coords)) self.assertEqual(str(defect.as_dict()["@class"].lower()), "substitution") def test_Distortions_initialisation(self): @@ -1408,24 +1363,18 @@ def test_Distortions_initialisation(self): mock_print.assert_not_called() # test extrinsic interstitial defect: - fake_extrinsic_interstitial_subdict = self.cdte_doped_defect_dict[ - "interstitials" - ][0].copy() + fake_extrinsic_interstitial_subdict = self.cdte_doped_defect_dict["interstitials"][0].copy() fake_extrinsic_interstitial_subdict["site_specie"] = "Li" - fake_extrinsic_interstitial_site = fake_extrinsic_interstitial_subdict[ - "supercell" - ]["structure"][-1] + fake_extrinsic_interstitial_site = fake_extrinsic_interstitial_subdict["supercell"]["structure"][ + -1 + ] fake_extrinsic_interstitial_site = PeriodicSite( "Li", fake_extrinsic_interstitial_site.coords, fake_extrinsic_interstitial_site.lattice, ) - fake_extrinsic_interstitial_subdict[ - "bulk_supercell_site" - ] = fake_extrinsic_interstitial_site - fake_extrinsic_interstitial_subdict[ - "unique_site" - ] = fake_extrinsic_interstitial_site + fake_extrinsic_interstitial_subdict["bulk_supercell_site"] = fake_extrinsic_interstitial_site + fake_extrinsic_interstitial_subdict["unique_site"] = fake_extrinsic_interstitial_site fake_extrinsic_interstitial_subdict["name"] = "Int_Li_1" fake_extrinsic_interstitial_list = self.cdte_defect_list.copy() [ @@ -1493,9 +1442,7 @@ def test_Distortions_single_atom_primitive(self): try: mock_print.assert_called_once_with(oxi_state_warning_message) except AssertionError: - mock_print.assert_any_call( - oxi_state_warning_message.replace("0.0", "0") - ) + mock_print.assert_any_call(oxi_state_warning_message.replace("0.0", "0")) self.assertEqual(dist.oxidation_states, {"Cu": 0}) self.assertAlmostEqual(dist.stdev, 0.2529625487091717) @@ -1551,7 +1498,9 @@ def test_Distortions_dimer(self): """Test initialising Distortions with a dimer distortion""" dist = input.Distortions( defect_entries=[self.V_Cd_entry_neutral], - bond_distortions=["Dimer",] + bond_distortions=[ + "Dimer", + ], ) self.assertEqual(dist.bond_distortions, ["Dimer"]) @@ -1573,13 +1522,10 @@ def test_write_vasp_files(self): with warnings.catch_warnings(record=True) as w: _, distortion_metadata = dist.write_vasp_files( user_incar_settings={"ENCUT": 212, "IBRION": 0, "EDIFF": 1e-4}, - verbose=False, ) # check if expected folders were created: - self.assertTrue( - set(self.cdte_defect_folders_old_names).issubset(set(os.listdir())) - ) + self.assertTrue(set(self.cdte_defect_folders_old_names).issubset(set(os.listdir()))) # check expected info printing: mock_print.assert_any_call( "Applying ShakeNBreak...", @@ -1590,42 +1536,34 @@ def test_write_vasp_files(self): "'0.5', '0.55', '0.6'].", "Then, will rattle with a std dev of 0.25 Å \n", ) - mock_print.assert_any_call( - "\033[1m" + "\nDefect: vac_1_Cd" + "\033[0m" - ) # bold print + mock_print.assert_any_call("\033[1m" + "\nDefect: vac_1_Cd" + "\033[0m") # bold print mock_print.assert_any_call( "\033[1m" + "Number of missing electrons in neutral state: 2" + "\033[0m" ) mock_print.assert_any_call( - "\nDefect vac_1_Cd in charge state: -2. Number of distorted " - "neighbours: 0" + "\nDefect vac_1_Cd in charge state: -2. Number of distorted " "neighbours: 0" ) mock_print.assert_any_call( - "\nDefect vac_1_Cd in charge state: -1. Number of distorted " - "neighbours: 1" + "\nDefect vac_1_Cd in charge state: -1. Number of distorted " "neighbours: 1" ) mock_print.assert_any_call( "\nDefect vac_1_Cd in charge state: 0. Number of distorted " "neighbours: 2" ) # test correct distorted neighbours based on oxidation states: mock_print.assert_any_call( - "\nDefect vac_2_Te in charge state: -2. Number of distorted " - "neighbours: 4" + "\nDefect vac_2_Te in charge state: -2. Number of distorted " "neighbours: 4" ) mock_print.assert_any_call( - "\nDefect as_1_Cd_on_Te in charge state: -2. Number of " - "distorted neighbours: 2" + "\nDefect as_1_Cd_on_Te in charge state: -2. Number of " "distorted neighbours: 2" ) mock_print.assert_any_call( - "\nDefect as_1_Te_on_Cd in charge state: -2. Number of " - "distorted neighbours: 2" + "\nDefect as_1_Te_on_Cd in charge state: -2. Number of " "distorted neighbours: 2" ) mock_print.assert_any_call( "\nDefect Int_Cd_1 in charge state: 0. Number of distorted " "neighbours: 2" ) mock_print.assert_any_call( - "\nDefect Int_Te_1 in charge state: -2. Number of distorted " - "neighbours: 0" + "\nDefect Int_Te_1 in charge state: -2. Number of distorted " "neighbours: 0" ) # check if correct files were created: @@ -1644,14 +1582,10 @@ def test_write_vasp_files(self): self.assertEqual(kpoints.kpts, [(1, 1, 1)]) if _potcars_available(): - assert self.V_Cd_INCAR != Incar.from_file( - f"{V_Cd_Bond_Distortion_folder}/INCAR" - ) + assert self.V_Cd_INCAR != Incar.from_file(f"{V_Cd_Bond_Distortion_folder}/INCAR") kwarged_INCAR = self.V_Cd_INCAR.copy() kwarged_INCAR.update({"ENCUT": 212, "IBRION": 0, "EDIFF": 1e-4}) - assert kwarged_INCAR == Incar.from_file( - f"{V_Cd_Bond_Distortion_folder}/INCAR" - ) + assert kwarged_INCAR == Incar.from_file(f"{V_Cd_Bond_Distortion_folder}/INCAR") # check if POTCARs have been written: potcar = Potcar.from_file(f"{V_Cd_Bond_Distortion_folder}/POTCAR") @@ -1697,17 +1631,20 @@ def test_write_vasp_files(self): for el_symbol in V_Cd_POSCAR.structure.symbol_set } + # check quiet output with verbose=False + self.tearDown() + with patch("builtins.print") as mock_print: + dist.write_vasp_files(verbose=False) + mock_print.assert_not_called() + # Test `Rattled` folder not generated for non-fully-ionised defects, # and only `Rattled` and `Unperturbed` folders generated for fully-ionised defects self.tearDown() - self.assertFalse( - set(self.cdte_defect_folders_old_names).issubset(set(os.listdir())) - ) + self.assertFalse(set(self.cdte_defect_folders_old_names).issubset(set(os.listdir()))) reduced_V_Cd = copy.copy(self.V_Cd) reduced_V_Cd.user_charges = [0, -2] reduced_V_Cd_entries = [ - input._get_defect_entry_from_defect(reduced_V_Cd, c) - for c in reduced_V_Cd.user_charges + input._get_defect_entry_from_defect(reduced_V_Cd, c) for c in reduced_V_Cd.user_charges ] dist = input.Distortions( {"vac_1_Cd": reduced_V_Cd_entries}, @@ -1719,16 +1656,12 @@ def test_write_vasp_files(self): verbose=False, ) # check if expected folders were created - V_Cd_minus0pt5_POSCAR = Poscar.from_file( - "vac_1_Cd_0/Bond_Distortion_-50.0%/POSCAR" - ) + V_Cd_minus0pt5_POSCAR = Poscar.from_file("vac_1_Cd_0/Bond_Distortion_-50.0%/POSCAR") self.assertEqual( len(V_Cd_minus0pt5_POSCAR.site_symbols), len(set(V_Cd_minus0pt5_POSCAR.site_symbols)), ) # no duplicates - self.assertEqual( - V_Cd_minus0pt5_POSCAR.structure, self.V_Cd_minus0pt5_struc_rattled - ) + self.assertEqual(V_Cd_minus0pt5_POSCAR.structure, self.V_Cd_minus0pt5_struc_rattled) self.assertEqual( V_Cd_minus0pt5_POSCAR.comment, "-50.0% N(Distort)=2 ~[0.0,0.0,0.0]", @@ -1771,16 +1704,14 @@ def test_write_vasp_files(self): _, distortion_metadata = dist.write_vasp_files( verbose=False, ) - V_Cd_kwarged_POSCAR = Poscar.from_file( - "vac_1_Cd_0/Bond_Distortion_-50.0%/POSCAR" - ) + V_Cd_kwarged_POSCAR = Poscar.from_file("vac_1_Cd_0/Bond_Distortion_-50.0%/POSCAR") self.assertEqual( len(V_Cd_kwarged_POSCAR.site_symbols), len(set(V_Cd_kwarged_POSCAR.site_symbols)), ) # no duplicates self.assertEqual( V_Cd_kwarged_POSCAR.structure.get_sorted_structure(), - self.V_Cd_minus0pt5_struc_kwarged.get_sorted_structure() + self.V_Cd_minus0pt5_struc_kwarged.get_sorted_structure(), ) rounded_bond_distortions = np.around(bond_distortions, 3) np.testing.assert_equal( @@ -1794,8 +1725,7 @@ def test_write_vasp_files(self): 1, ] reduced_Int_Cd_2_entries = [ - input._get_defect_entry_from_defect(reduced_Int_Cd_2, c) - for c in reduced_Int_Cd_2.user_charges + input._get_defect_entry_from_defect(reduced_Int_Cd_2, c) for c in reduced_Int_Cd_2.user_charges ] with patch("builtins.print") as mock_Int_Cd_2_print: @@ -1825,9 +1755,7 @@ def test_write_vasp_files(self): }, "defects": { "Int_Cd_2": { - "unique_site": self.Int_Cd_2_dict[ - "bulk_supercell_site" - ].frac_coords, + "unique_site": self.Int_Cd_2_dict["bulk_supercell_site"].frac_coords, "charges": { 1: { "num_nearest_neighbours": 4, @@ -1835,7 +1763,7 @@ def test_write_vasp_files(self): (11, "Cd"), (23, "Cd"), (30, "Cd"), - (39, "Te"), + (2, "Cd"), ], # Defect added at index 0 "distortion_parameters": { "distortion_increment": 0.25, @@ -1887,6 +1815,7 @@ def test_write_vasp_files(self): 0.5, 0.55, 0.6, + "Dimer", # now included by default ], "local_rattle": False, "mc_rattle_parameters": { @@ -1955,9 +1884,9 @@ def test_write_vasp_files(self): for defect in kwarged_Int_Cd_2_dict["defects"]: for charge in kwarged_Int_Cd_2_dict["defects"][defect]["charges"]: np.testing.assert_equal( - metadata["defects"][defect]["charges"][charge], # check defect in distortion_defect_dict - kwarged_Int_Cd_2_dict["defects"][defect]["charges"][charge], - ) + metadata["defects"][defect]["charges"][charge], # defect in distortion_defect_dict + kwarged_Int_Cd_2_dict["defects"][defect]["charges"][charge], + ) # check expected info printing: mock_Int_Cd_2_print.assert_any_call( @@ -1966,9 +1895,7 @@ def test_write_vasp_files(self): "['-0.5', '-0.25', '0.0', '0.25', '0.5'].", "Then, will rattle with a std dev of 0.25 Å \n", ) - mock_Int_Cd_2_print.assert_any_call( - "\033[1m" + "\nDefect: Int_Cd_2" + "\033[0m" - ) + mock_Int_Cd_2_print.assert_any_call("\033[1m" + "\nDefect: Int_Cd_2" + "\033[0m") mock_Int_Cd_2_print.assert_any_call( "\033[1m" + "Number of missing electrons in neutral state: 3" + "\033[0m" ) @@ -1979,15 +1906,13 @@ def test_write_vasp_files(self): mock_Int_Cd_2_print.assert_any_call( f"\tDefect Site Index / Frac Coords: 1\n" + " Original Neighbour Distances: [(2.71, 11, 'Cd'), (2.71, 23, 'Cd'), " - + "(2.71, 30, 'Cd'), (2.71, 39, 'Te')]\n" + + "(2.71, 30, 'Cd'), (4.25, 2, 'Cd')]\n" + " Distorted Neighbour Distances:\n\t[(1.36, 11, 'Cd'), (1.36, 23, 'Cd'), " - + "(1.36, 30, 'Cd'), (1.36, 39, 'Te')]" + + "(1.36, 30, 'Cd'), (2.13, 2, 'Cd')]" ) # Defect added at index 0, so atom indexing + 1 wrt original structure # check correct folder was created: self.assertTrue(os.path.exists("Int_Cd_2_+1/Unperturbed")) - _int_Cd_2_POSCAR = Poscar.from_file( - "Int_Cd_2_+1/Unperturbed/POSCAR" - ) # test POSCAR loaded fine + _int_Cd_2_POSCAR = Poscar.from_file("Int_Cd_2_+1/Unperturbed/POSCAR") # test POSCAR loaded fine self.assertEqual( len(_int_Cd_2_POSCAR.site_symbols), len(set(_int_Cd_2_POSCAR.site_symbols)) ) # no duplicates @@ -2027,9 +1952,7 @@ def test_write_vasp_files(self): ) self.assertTrue(os.path.exists("distortion_metadata.json")) # check expected info printing: - mock_Int_Cd_2_print.assert_any_call( - "\033[1m" + "\nDefect: Int_Cd_2" + "\033[0m" - ) + mock_Int_Cd_2_print.assert_any_call("\033[1m" + "\nDefect: Int_Cd_2" + "\033[0m") mock_Int_Cd_2_print.assert_any_call( "\033[1m" + "Number of extra electrons in neutral state: 2" + "\033[0m" ) @@ -2043,14 +1966,12 @@ def test_write_vasp_files(self): _, distortion_metadata = dist.write_vasp_files() self.assertTrue(os.path.exists("distortion_metadata.json")) current_datetime = datetime.datetime.now().strftime("%Y-%m-%d-%H-%M") - current_datetime_minus1min = ( - datetime.datetime.now() - datetime.timedelta(minutes=1) - ).strftime("%Y-%m-%d-%H-%M") + current_datetime_minus1min = (datetime.datetime.now() - datetime.timedelta(minutes=1)).strftime( + "%Y-%m-%d-%H-%M" + ) self.assertTrue( os.path.exists(f"./distortion_metadata_{current_datetime}.json") - or os.path.exists( - f"./distortion_metadata_{current_datetime_minus1min}.json" - ) + or os.path.exists(f"./distortion_metadata_{current_datetime_minus1min}.json") ) self.assertFalse( # no distortion_metadata warning with smooth merging any( @@ -2060,8 +1981,7 @@ def test_write_vasp_files(self): ) or any( f"There is a previous version of distortion_metadata.json. Will rename old " - f"metadata to distortion_metadata_{current_datetime_minus1min}.json" - in call[0][0] + f"metadata to distortion_metadata_{current_datetime_minus1min}.json" in call[0][0] for call in mock_Int_Cd_2_print.call_args_list ) ) @@ -2090,16 +2010,14 @@ def test_write_vasp_files(self): ) self.assertTrue(os.path.exists("test_path/vac_1_Cd_0/Bond_Distortion_-50.0%")) self.assertTrue(os.path.exists("test_path/distortion_metadata.json")) - V_Cd_kwarged_POSCAR = Poscar.from_file( - "test_path/vac_1_Cd_0/Bond_Distortion_-50.0%/POSCAR" - ) + V_Cd_kwarged_POSCAR = Poscar.from_file("test_path/vac_1_Cd_0/Bond_Distortion_-50.0%/POSCAR") self.assertEqual( len(V_Cd_kwarged_POSCAR.site_symbols), len(set(V_Cd_kwarged_POSCAR.site_symbols)), ) # no duplicates self.assertEqual( V_Cd_kwarged_POSCAR.structure.get_sorted_structure(), - self.V_Cd_minus0pt5_struc_kwarged.get_sorted_structure() + self.V_Cd_minus0pt5_struc_kwarged.get_sorted_structure(), ) def test_write_vasp_files_dimer_distortion(self): @@ -2108,7 +2026,9 @@ def test_write_vasp_files_dimer_distortion(self): d_min = 0.8 * sorted_distances[len(self.V_Cd_struc) + 20] dist = input.Distortions( defect_entries=[self.V_Cd_entry_neutral], - bond_distortions=["Dimer",], + bond_distortions=[ + "Dimer", + ], seed=42, stdev=0.25, d_min=d_min, @@ -2122,12 +2042,8 @@ def test_write_vasp_files_dimer_distortion(self): "['Dimer'].", "Then, will rattle with a std dev of 0.25 Å \n", ) - V_Cd_dimer_POSCAR = Structure.from_file( - "v_Cd_Td_Te2.83_0/Dimer/POSCAR" - ) - self.assertEqual( - V_Cd_dimer_POSCAR, self.V_Cd_dimer_struc_0pt25_rattled - ) + V_Cd_dimer_POSCAR = Structure.from_file("v_Cd_Td_Te2.83_0/Dimer/POSCAR") + self.assertEqual(V_Cd_dimer_POSCAR, self.V_Cd_dimer_struc_0pt25_rattled) def test_write_vasp_files_from_doped_defect_gen(self): """Test Distortions() class with (new) doped DefectsGenerator input""" @@ -2138,10 +2054,7 @@ def test_write_vasp_files_from_doped_defect_gen(self): ) with patch("builtins.print") as mock_print: with warnings.catch_warnings(record=True) as w: - _, distortion_metadata = dist.write_vasp_files( - user_incar_settings={"IVDW": 12}, - verbose=False, - ) + _, distortion_metadata = dist.write_vasp_files(user_incar_settings={"IVDW": 12}) # check if expected folders were created: for key in self.cdte_doped_reduced_defect_gen.keys(): @@ -2161,31 +2074,17 @@ def test_write_vasp_files_from_doped_defect_gen(self): "'0.6'].", "Then, will rattle with a std dev of 0.25 Å \n", ) - mock_print.assert_any_call( - "\033[1m" + "\nDefect: v_Cd" + "\033[0m" - ) # bold print + mock_print.assert_any_call("\033[1m" + "\nDefect: v_Cd" + "\033[0m") # bold print mock_print.assert_any_call( "\033[1m" + "Number of missing electrons in neutral state: 2" + "\033[0m" ) - mock_print.assert_any_call( - "\nDefect v_Cd in charge state: -2. Number of distorted neighbours: 0" - ) - mock_print.assert_any_call( - "\nDefect v_Cd in charge state: -1. Number of distorted neighbours: 1" - ) - mock_print.assert_any_call( - "\nDefect v_Cd in charge state: 0. Number of distorted neighbours: 2" - ) + mock_print.assert_any_call("\nDefect v_Cd in charge state: -2. Number of distorted neighbours: 0") + mock_print.assert_any_call("\nDefect v_Cd in charge state: -1. Number of distorted neighbours: 1") + mock_print.assert_any_call("\nDefect v_Cd in charge state: 0. Number of distorted neighbours: 2") # test correct distorted neighbours based on oxidation states: - mock_print.assert_any_call( - "\nDefect v_Te in charge state: -1. Number of distorted neighbours: 3" - ) - mock_print.assert_any_call( - "\nDefect Cd_Te in charge state: +4. Number of distorted neighbours: 0" - ) - mock_print.assert_any_call( - "\nDefect Te_Cd in charge state: +1. Number of distorted neighbours: 3" - ) + mock_print.assert_any_call("\nDefect v_Te in charge state: -1. Number of distorted neighbours: 3") + mock_print.assert_any_call("\nDefect Cd_Te in charge state: +4. Number of distorted neighbours: 0") + mock_print.assert_any_call("\nDefect Te_Cd in charge state: +1. Number of distorted neighbours: 3") mock_print.assert_any_call( "\nDefect Cd_i_C3v in charge state: 0. Number of distorted neighbours: 2" ) @@ -2208,14 +2107,10 @@ def test_write_vasp_files_from_doped_defect_gen(self): self.assertEqual(kpoints.kpts, [(1, 1, 1)]) if _potcars_available(): - assert self.V_Cd_INCAR != Incar.from_file( - f"{V_Cd_Bond_Distortion_folder}/INCAR" - ) + assert self.V_Cd_INCAR != Incar.from_file(f"{V_Cd_Bond_Distortion_folder}/INCAR") kwarged_INCAR = self.V_Cd_INCAR.copy() kwarged_INCAR.update({"IVDW": 12}) - assert kwarged_INCAR == Incar.from_file( - f"{V_Cd_Bond_Distortion_folder}/INCAR" - ) + assert kwarged_INCAR == Incar.from_file(f"{V_Cd_Bond_Distortion_folder}/INCAR") # check if POTCARs have been written: potcar = Potcar.from_file(f"{V_Cd_Bond_Distortion_folder}/POTCAR") @@ -2288,9 +2183,7 @@ def _check_agsbte2_files(self, folder_name, mock_print, w, charge_state=-2): "Then, will rattle with a std dev of 0.29 Å \n", ) defect_wout_charge = folder_name.rsplit("_", 1)[0] - mock_print.assert_any_call( - "\033[1m" + f"\nDefect: {defect_wout_charge}" + "\033[0m" - ) # bold print + mock_print.assert_any_call("\033[1m" + f"\nDefect: {defect_wout_charge}" + "\033[0m") # bold print mock_print.assert_any_call( "\033[1m" + "Number of missing electrons in neutral state: 2" + "\033[0m" ) @@ -2308,9 +2201,7 @@ def _check_agsbte2_files(self, folder_name, mock_print, w, charge_state=-2): ) # closest to middle default # check correct element ordering in POSCAR: - self.assertEqual( - len(poscar.site_symbols), len(set(poscar.site_symbols)) - ) # no duplicates + self.assertEqual(len(poscar.site_symbols), len(set(poscar.site_symbols))) # no duplicates kpoints = Kpoints.from_file(f"{folder_name}/{distortion_folder}/KPOINTS") self.assertEqual(kpoints.kpts, [(1, 1, 1)]) @@ -2319,8 +2210,7 @@ def _check_agsbte2_files(self, folder_name, mock_print, w, charge_state=-2): # check if POTCARs have been written: potcar = Potcar.from_file(f"{folder_name}/{distortion_folder}/POTCAR") assert set(potcar.as_dict()["symbols"]) == { - input.default_potcar_dict["POTCAR"][el_symbol] - for el_symbol in poscar.structure.symbol_set + input.default_potcar_dict["POTCAR"][el_symbol] for el_symbol in poscar.structure.symbol_set } else: # test POTCAR warning assert any( @@ -2343,9 +2233,7 @@ def test_write_vasp_files_from_doped_defect_entry(self): verbose=True, ) - self._check_agsbte2_files( - self.Ag_Sb_AgSbTe2_m2_defect_entry.name, mock_print, w - ) + self._check_agsbte2_files(self.Ag_Sb_AgSbTe2_m2_defect_entry.name, mock_print, w) def test_write_vasp_files_from_doped_defect_entry_wout_name(self): """ @@ -2376,9 +2264,7 @@ def test_write_vasp_files_from_doped_defect_entry_list(self): name attributes set, and then `test_write_vasp_files_from_list` tests no name attributes set. """ - Ag_Sb_AgSbTe2_neutral_defect_entry = copy.deepcopy( - self.Ag_Sb_AgSbTe2_m2_defect_entry - ) + Ag_Sb_AgSbTe2_neutral_defect_entry = copy.deepcopy(self.Ag_Sb_AgSbTe2_m2_defect_entry) Ag_Sb_AgSbTe2_neutral_defect_entry.charge_state = 0 Ag_Sb_AgSbTe2_neutral_defect_entry.name = ( Ag_Sb_AgSbTe2_neutral_defect_entry.name.rsplit("_", 1)[0] + "_0" @@ -2394,12 +2280,8 @@ def test_write_vasp_files_from_doped_defect_entry_list(self): verbose=True, ) - self._check_agsbte2_files( - self.Ag_Sb_AgSbTe2_m2_defect_entry.name, mock_print, w, charge_state=-2 - ) - self._check_agsbte2_files( - Ag_Sb_AgSbTe2_neutral_defect_entry.name, mock_print, w, charge_state=0 - ) + self._check_agsbte2_files(self.Ag_Sb_AgSbTe2_m2_defect_entry.name, mock_print, w, charge_state=-2) + self._check_agsbte2_files(Ag_Sb_AgSbTe2_neutral_defect_entry.name, mock_print, w, charge_state=0) def test_write_vasp_files_from_doped_defect_entry_list_w_incomplete_names(self): """ @@ -2407,9 +2289,7 @@ def test_write_vasp_files_from_doped_defect_entry_list_w_incomplete_names(self): Here the DefectEntry.name attributes are not set in all cases, so the doped defect names are regenerated and used as folder names. """ - Ag_Sb_AgSbTe2_neutral_defect_entry = copy.deepcopy( - self.Ag_Sb_AgSbTe2_m2_defect_entry - ) + Ag_Sb_AgSbTe2_neutral_defect_entry = copy.deepcopy(self.Ag_Sb_AgSbTe2_m2_defect_entry) Ag_Sb_AgSbTe2_neutral_defect_entry.charge_state = 0 delattr(Ag_Sb_AgSbTe2_neutral_defect_entry, "name") @@ -2434,26 +2314,14 @@ def test_write_vasp_files_overwriting(self): (i.e. if doped defect folder already generated). """ drs = DefectRelaxSet(self.Ag_Sb_AgSbTe2_m2_defect_entry) - drs.write_all(unperturbed_poscar=True) + drs.write_all(poscar=True, rattle=False) self.assertTrue(os.path.exists(f"{self.Ag_Sb_AgSbTe2_m2_defect_entry.name}")) - self.assertTrue( - os.path.exists(f"{self.Ag_Sb_AgSbTe2_m2_defect_entry.name}/vasp_std") - ) - self.assertTrue( - os.path.exists(f"{self.Ag_Sb_AgSbTe2_m2_defect_entry.name}/vasp_std/POSCAR") - ) - self.assertTrue( - os.path.exists( - f"{self.Ag_Sb_AgSbTe2_m2_defect_entry.name}/vasp_std/KPOINTS" - ) - ) - self.assertTrue( - os.path.exists(f"{self.Ag_Sb_AgSbTe2_m2_defect_entry.name}/vasp_ncl") - ) - self.assertTrue( - os.path.exists(f"{self.Ag_Sb_AgSbTe2_m2_defect_entry.name}/vasp_nkred_std") - ) + self.assertTrue(os.path.exists(f"{self.Ag_Sb_AgSbTe2_m2_defect_entry.name}/vasp_std")) + self.assertTrue(os.path.exists(f"{self.Ag_Sb_AgSbTe2_m2_defect_entry.name}/vasp_std/POSCAR")) + self.assertTrue(os.path.exists(f"{self.Ag_Sb_AgSbTe2_m2_defect_entry.name}/vasp_std/KPOINTS")) + self.assertTrue(os.path.exists(f"{self.Ag_Sb_AgSbTe2_m2_defect_entry.name}/vasp_ncl")) + self.assertTrue(os.path.exists(f"{self.Ag_Sb_AgSbTe2_m2_defect_entry.name}/vasp_nkred_std")) dist = input.Distortions(self.Ag_Sb_AgSbTe2_m2_defect_entry) with patch("builtins.print") as mock_print: @@ -2464,18 +2332,35 @@ def test_write_vasp_files_overwriting(self): ) self.assertEqual( - len( - [ - warning - for warning in w - if "reviously-generated" in str(warning.message) - ] - ), + len([warning for warning in w if "reviously-generated" in str(warning.message)]), 0, ) - self._check_agsbte2_files( - self.Ag_Sb_AgSbTe2_m2_defect_entry.name, mock_print, w, charge_state=-2 - ) + self._check_agsbte2_files(self.Ag_Sb_AgSbTe2_m2_defect_entry.name, mock_print, w, charge_state=-2) + + def _compare_dist_dicts(self, generated_dist_dict, test_dist_dict, defect_name, snb_name): + """ + Convenience test function to compare two distortion dictionaries. + + Avoids using ``assertDictEqual`` as this can be sensitive to tiny + rounding differences between Linux and MacOS. + """ + print(f"Comparing {defect_name}, {snb_name}") + for k, v in generated_dist_dict[defect_name].items(): + if k != "charges": + print(f"Comparing {k}") + self.assertEqual(v, test_dist_dict[snb_name][k]) + else: + for charge, charge_dict in v.items(): + print(f"Comparing charge {charge}") + structures = charge_dict["structures"] + self.assertEqual(structures["Unperturbed"], + test_dist_dict[snb_name]["charges"][charge]["structures"][ + "Unperturbed"]) + + for distortion, structure in structures["distortions"].items(): + self.assertEqual(structure, + test_dist_dict[snb_name]["charges"][charge]["structures"][ + "distortions"][distortion]) def test_write_vasp_files_from_doped_dict(self): """Test Distortions() class with doped dict input""" @@ -2506,8 +2391,7 @@ def test_write_vasp_files_from_doped_dict(self): for i in range(len(pmg_defects[defect_entry_key])) ) assert all( - dist.defects_dict[defect_entry_key][i].defect - == pmg_defects[defect_entry_key][i].defect + dist.defects_dict[defect_entry_key][i].defect == pmg_defects[defect_entry_key][i].defect for i in range(len(pmg_defects[defect_entry_key])) ) assert all( @@ -2537,40 +2421,26 @@ def test_write_vasp_files_from_doped_dict(self): "['-0.3'].", "Then, will rattle with a std dev of 0.25 Å \n", ) - mock_print.assert_any_call( - "\033[1m" + "\nDefect: vac_1_Cd" + "\033[0m" - ) # bold print + mock_print.assert_any_call("\033[1m" + "\nDefect: vac_1_Cd" + "\033[0m") # bold print mock_print.assert_any_call( "\033[1m" + "Number of missing electrons in neutral state: 2" + "\033[0m" ) - mock_print.assert_any_call( - "\033[1m" + "\nDefect: vac_2_Te" + "\033[0m" - ) # bold print - mock_print.assert_any_call( - "\033[1m" + "Number of extra electrons in neutral state: 2" + "\033[0m" - ) - vacancies_dist_metadata = loadfn( - f"{self.VASP_CDTE_DATA_DIR}/vacancies_dist_metadata.json" - ) + mock_print.assert_any_call("\033[1m" + "\nDefect: vac_2_Te" + "\033[0m") # bold print + mock_print.assert_any_call("\033[1m" + "Number of extra electrons in neutral state: 2" + "\033[0m") + vacancies_dist_metadata = loadfn(f"{self.VASP_CDTE_DATA_DIR}/vacancies_dist_metadata.json") doped_dict_metadata = loadfn("distortion_metadata.json") - self.assertNotEqual( - doped_dict_metadata, vacancies_dist_metadata - ) # new vs old names + self.assertNotEqual(doped_dict_metadata, vacancies_dist_metadata) # new vs old names self.assertDictEqual( doped_dict_metadata["distortion_parameters"], vacancies_dist_metadata["distortion_parameters"], ) dumpfn(dist_defects_dict, "distorted_defects_dict.json") - test_dist_dict = loadfn( - f"{self.VASP_CDTE_DATA_DIR}/vacancies_dist_defect_dict.json" - ) + test_dist_dict = loadfn(f"{self.VASP_CDTE_DATA_DIR}/vacancies_dist_defect_dict.json") doped_dist_defects_dict = loadfn("distorted_defects_dict.json") for defect_name in ["vac_1_Cd", "vac_2_Te"]: - self.assertTrue( - os.path.exists(f"{defect_name}_0/Bond_Distortion_-30.0%/POSCAR") - ) + self.assertTrue(os.path.exists(f"{defect_name}_0/Bond_Distortion_-30.0%/POSCAR")) # get key for value = defect_name in self.new_names_dict snb_name = list(self.new_full_names_old_names_CdTe.keys())[ list(self.new_full_names_old_names_CdTe.values()).index(defect_name) @@ -2579,9 +2449,8 @@ def test_write_vasp_files_from_doped_dict(self): doped_dict_metadata["defects"][defect_name], vacancies_dist_metadata["defects"][snb_name], ) - self.assertDictEqual( - doped_dist_defects_dict[defect_name], test_dist_dict[snb_name] - ) + self._compare_dist_dicts(doped_dist_defects_dict, test_dist_dict, defect_name, snb_name) + # Test error if missing bulk entry vacancies = { @@ -2614,17 +2483,15 @@ def test_write_vasp_files_from_list(self): "oxidation_states" ) pmg_defects = { - new_key: self.cdte_defects[old_key] for new_key, old_key in - self.new_full_names_old_names_CdTe.items() + new_key: self.cdte_defects[old_key] + for new_key, old_key in self.new_full_names_old_names_CdTe.items() } # self.assertDictEqual(dist.defects_dict, pmg_defects) # order of list of DefectEntries varies # so we compare each DefectEntry (with same charge) for key, defect_list in pmg_defects.items(): for c in defect_list[0].defect.get_charge_states(): self.assertEqual( - [i.defect for i in dist.defects_dict[key] if i.charge_state == c][ - 0 - ], + [i.defect for i in dist.defects_dict[key] if i.charge_state == c][0], [i.defect for i in pmg_defects[key] if i.charge_state == c][0], ) @@ -2670,23 +2537,19 @@ def test_write_vasp_files_from_list(self): "\nDefect Cd_i_C3v_Cd2.71 in charge state: 0. Number of distorted " "neighbours: 2" ) mock_print.assert_any_call( - "\nDefect Cd_i_Td_Cd2.83 in charge state: 0. Number of distorted " - "neighbours: 2" + "\nDefect Cd_i_Td_Cd2.83 in charge state: 0. Number of distorted " "neighbours: 2" ) mock_print.assert_any_call( - "\nDefect Cd_i_Td_Te2.83 in charge state: 0. Number of distorted " - "neighbours: 2" + "\nDefect Cd_i_Td_Te2.83 in charge state: 0. Number of distorted " "neighbours: 2" ) mock_print.assert_any_call( "\nDefect Te_i_C3v_Cd2.71 in charge state: 0. Number of distorted " "neighbours: 2" ) mock_print.assert_any_call( - "\nDefect Te_i_Td_Cd2.83 in charge state: 0. Number of distorted " - "neighbours: 2" + "\nDefect Te_i_Td_Cd2.83 in charge state: 0. Number of distorted " "neighbours: 2" ) mock_print.assert_any_call( - "\nDefect Te_i_Td_Te2.83 in charge state: 0. Number of distorted " - "neighbours: 2" + "\nDefect Te_i_Td_Te2.83 in charge state: 0. Number of distorted " "neighbours: 2" ) # check if correct files were created: @@ -2743,30 +2606,22 @@ def test_write_vasp_files_from_list(self): "['-0.3'].", "Then, will rattle with a std dev of 0.25 Å \n", ) - mock_print.assert_any_call( - "\033[1m" + "\nDefect: v_Cd_Td_Te2.83" + "\033[0m" - ) # bold print + mock_print.assert_any_call("\033[1m" + "\nDefect: v_Cd_Td_Te2.83" + "\033[0m") # bold print mock_print.assert_any_call( "\033[1m" + "Number of missing electrons in neutral state: 2" + "\033[0m" ) - mock_print.assert_any_call( - "\033[1m" + "\nDefect: v_Te_Td_Cd2.83" + "\033[0m" - ) # bold print - mock_print.assert_any_call( - "\033[1m" + "Number of extra electrons in neutral state: 2" + "\033[0m" - ) + mock_print.assert_any_call("\033[1m" + "\nDefect: v_Te_Td_Cd2.83" + "\033[0m") # bold print + mock_print.assert_any_call("\033[1m" + "Number of extra electrons in neutral state: 2" + "\033[0m") for defect_name in ["v_Cd_Td_Te2.83", "v_Te_Td_Cd2.83"]: - self.assertTrue( - os.path.exists(f"{defect_name}_0/Bond_Distortion_-30.0%/POSCAR") - ) + self.assertTrue(os.path.exists(f"{defect_name}_0/Bond_Distortion_-30.0%/POSCAR")) self.assertFalse(os.path.exists(f"{defect_name}_+1")) metadata = loadfn(f"{self.VASP_CDTE_DATA_DIR}/vacancies_dist_metadata.json") self.assertDictEqual(loadfn("distortion_metadata.json"), metadata) dumpfn(dist_defects_dict, "distorted_defects_dict.json") - test_dist_dict = loadfn( - f"{self.VASP_CDTE_DATA_DIR}/vacancies_dist_defect_dict.json" + test_dist_dict = loadfn(f"{self.VASP_CDTE_DATA_DIR}/vacancies_dist_defect_dict.json") + self._compare_dist_dicts( + loadfn("distorted_defects_dict.json"), test_dist_dict, "v_Cd_Td_Te2.83", "v_Cd_Td_Te2.83" ) - self.assertDictEqual(test_dist_dict, loadfn("distorted_defects_dict.json")) # Test error if missing bulk entry vacancies = { @@ -2864,10 +2719,15 @@ def test_write_espresso_files(self): "vac_1_Cd_0/Bond_Distortion_30.0%/espresso.pwi", ) ).read_text() - generated_input = pathlib.Path( - "vac_1_Cd_0/Bond_Distortion_30.0%/espresso.pwi" - ).read_text() - self.assertEqual(test_input, generated_input) + generated_input = pathlib.Path("vac_1_Cd_0/Bond_Distortion_30.0%/espresso.pwi").read_text() + # shutil.copyfile( # to update test input files + # "vac_1_Cd_0/Bond_Distortion_30.0%/espresso.pwi", + # os.path.join( + # self.ESPRESSO_DATA_DIR, + # "vac_1_Cd_0/Bond_Distortion_30.0%/espresso.pwi", + # ), + # ) # last change was due to removed rounding (to 4 dp) of distorted atom distances + self.assertEqual(test_input, generated_input) # Test parameter file is not written if write_structures_only = True for i in self.cdte_defect_folders_old_names: @@ -2879,10 +2739,15 @@ def test_write_espresso_files(self): "vac_1_Cd_0/Bond_Distortion_30.0%/espresso_structure.pwi", ) ).read_text() - generated_input = pathlib.Path( - "vac_1_Cd_0/Bond_Distortion_30.0%/espresso.pwi" - ).read_text() - self.assertEqual(test_input, generated_input) + generated_input = pathlib.Path("vac_1_Cd_0/Bond_Distortion_30.0%/espresso.pwi").read_text() + # shutil.copyfile( # to update test input files + # "vac_1_Cd_0/Bond_Distortion_30.0%/espresso.pwi", + # os.path.join( + # self.ESPRESSO_DATA_DIR, + # "vac_1_Cd_0/Bond_Distortion_30.0%/espresso_structure.pwi", + # ), + # ) # last change was due to removed rounding (to 4 dp) of distorted atom distances + self.assertEqual(test_input, generated_input) # Test user defined parameters _, _ = Dist.write_espresso_files( @@ -2903,9 +2768,14 @@ def test_write_espresso_files(self): "vac_1_Cd_0/Bond_Distortion_30.0%/espresso_user_parameters.pwi", ) ).read_text() - generated_input = pathlib.Path( - "vac_1_Cd_0/Bond_Distortion_30.0%/espresso.pwi" - ).read_text() + generated_input = pathlib.Path("vac_1_Cd_0/Bond_Distortion_30.0%/espresso.pwi").read_text() + # shutil.copyfile( # to update test input files + # "vac_1_Cd_0/Bond_Distortion_30.0%/espresso.pwi", + # os.path.join( + # self.ESPRESSO_DATA_DIR, + # "vac_1_Cd_0/Bond_Distortion_30.0%/espresso_user_parameters.pwi", + # ), + # ) # last change was due to removed rounding (to 4 dp) of distorted atom distances self.assertEqual(test_input, generated_input) def test_write_cp2k_files(self): @@ -2938,11 +2808,16 @@ def test_write_cp2k_files(self): test_input = f.read() with open("vac_1_Cd_0/Bond_Distortion_30.0%/cp2k_input.inp") as f: generated_input = f.read() + # shutil.copyfile( # to update test input files (when pymatgen updates Cp2K input formats) + # "vac_1_Cd_0/Bond_Distortion_30.0%/cp2k_input.inp", + # os.path.join( + # self.CP2K_DATA_DIR, + # "vac_1_Cd_0/Bond_Distortion_30.0%/cp2k_input.inp", + # ) + # ) # most recent change was switch to lean cp2k_input.inp output, with no comments self.assertEqual(test_input, generated_input) # Test input structure file - generated_input_struct = Structure.from_file( - "vac_1_Cd_0/Bond_Distortion_30.0%/structure.cif" - ) + generated_input_struct = Structure.from_file("vac_1_Cd_0/Bond_Distortion_30.0%/structure.cif") test_input_struct = Structure.from_file( os.path.join( self.CP2K_DATA_DIR, @@ -2956,12 +2831,8 @@ def test_write_cp2k_files(self): for i in self.cdte_defect_folders_old_names: if_present_rm(i) # remove test-generated defect folders _, _ = Dist.write_cp2k_files(write_structures_only=True) - self.assertFalse( - os.path.exists("vac_1_Cd_0/Bond_Distortion_30.0%/cp2k_input.inp") - ) - self.assertTrue( - os.path.exists("vac_1_Cd_0/Bond_Distortion_30.0%/structure.cif") - ) + self.assertFalse(os.path.exists("vac_1_Cd_0/Bond_Distortion_30.0%/cp2k_input.inp")) + self.assertTrue(os.path.exists("vac_1_Cd_0/Bond_Distortion_30.0%/structure.cif")) # Test user defined parameters for i in self.cdte_defect_folders_old_names: @@ -2978,6 +2849,13 @@ def test_write_cp2k_files(self): test_input = f.read() with open("vac_1_Cd_0/Bond_Distortion_30.0%/cp2k_input.inp") as f: generated_input = f.read() + # shutil.copyfile( # to update test input files (when pymatgen updates Cp2K input formats) + # "vac_1_Cd_0/Bond_Distortion_30.0%/cp2k_input.inp", + # os.path.join( + # self.CP2K_DATA_DIR, + # "vac_1_Cd_0/Bond_Distortion_30.0%/cp2k_input_user_parameters.inp", + # ) + # ) # most recent change was switch to lean cp2k_input.inp output, with no comments self.assertEqual(test_input, generated_input) # The input_file option is tested through the test for `generate_all()` # (in `test_cli.py`) @@ -3008,9 +2886,7 @@ def test_write_castep_files(self): "vac_1_Cd_0/Bond_Distortion_30.0%/castep.param", ) ) as f: - test_input = f.readlines()[ - 28: - ] # only last line contains parameter (charge) + test_input = f.readlines()[28:] # only last line contains parameter (charge) with open("vac_1_Cd_0/Bond_Distortion_30.0%/castep.param") as f: generated_input = f.readlines()[28:] self.assertEqual(test_input, generated_input) @@ -3024,15 +2900,20 @@ def test_write_castep_files(self): test_input_struct = f.readlines()[6:-3] # avoid comment with file path etc with open("vac_1_Cd_0/Bond_Distortion_30.0%/castep.cell") as f: generated_input_struct = f.readlines()[6:-3] + # shutil.copyfile( # to update test input files + # "vac_1_Cd_0/Bond_Distortion_30.0%/castep.cell", + # os.path.join( + # self.CASTEP_DATA_DIR, + # "vac_1_Cd_0/Bond_Distortion_30.0%/castep.cell", + # ), + # ) # last change was due to removed rounding (to 4 dp) of distorted atom distances self.assertEqual(test_input_struct, generated_input_struct) # Test only structure files are written if write_structures_only = True for i in self.cdte_defect_folders_old_names: if_present_rm(i) # remove test-generated defect folders _, _ = Dist.write_castep_files(write_structures_only=True) - self.assertFalse( - os.path.exists("vac_1_Cd_0/Bond_Distortion_30.0%/castep.param") - ) + self.assertFalse(os.path.exists("vac_1_Cd_0/Bond_Distortion_30.0%/castep.param")) self.assertTrue(os.path.exists("vac_1_Cd_0/Bond_Distortion_30.0%/castep.cell")) # Test user defined parameters @@ -3057,9 +2938,7 @@ def test_write_castep_files(self): def test_write_fhi_aims_files(self): """Test method write_fhi_aims_files""" oxidation_states = {"Cd": +2, "Te": -2} - bond_distortions = [ - 0.3, 0.7 - ] + bond_distortions = [0.3, 0.7] Dist = input.Distortions( {"vac_1_Cd": self.V_Cd_entries}, @@ -3076,10 +2955,12 @@ def test_write_fhi_aims_files(self): self.assertTrue(os.path.exists("vac_1_Cd_0/Unperturbed")) # Test input structure file - test_atoms = read(os.path.join( + test_atoms = read( + os.path.join( self.FHI_AIMS_DATA_DIR, "vac_1_Cd_0/Bond_Distortion_30.0%/geometry.in", - )) + ) + ) generated_atoms = read("vac_1_Cd_0/Bond_Distortion_30.0%/geometry.in") for array_tuple in zip(test_atoms.get_positions(), generated_atoms.get_positions()): np.testing.assert_array_almost_equal(array_tuple[0], array_tuple[1], decimal=3) @@ -3108,6 +2989,7 @@ def test_write_fhi_aims_files(self): # # User defined parameters # for i in self.cdte_defect_folders_old_names: # if_present_rm(i) # remove test-generated defect folders + # from ase.calculators.aims import Aims # ase_calculator = Aims( # k_grid=(1, 1, 1), # relax_geometry=("bfgs", 5e-4), @@ -3140,8 +3022,7 @@ def test_apply_distortions(self): 0, ] int_Cd_2_entries = [ - input._get_defect_entry_from_defect(int_Cd_2, c) - for c in int_Cd_2.user_charges + input._get_defect_entry_from_defect(int_Cd_2, c) for c in int_Cd_2.user_charges ] dist = input.Distortions( # don't set `stdev` or `seed`, in order to test default behaviour {"Int_Cd_2": int_Cd_2_entries}, @@ -3152,9 +3033,9 @@ def test_apply_distortions(self): with patch("builtins.print") as mock_print: defects_dict, metadata_dict = dist.apply_distortions() # Check structure - gen_struct = defects_dict["Int_Cd_2"]["charges"][0]["structures"][ - "distortions" - ]["Bond_Distortion_-60.0%"] + gen_struct = defects_dict["Int_Cd_2"]["charges"][0]["structures"]["distortions"][ + "Bond_Distortion_-60.0%" + ] test_struct = self.Int_Cd_2_minus0pt6_struc_rattled for struct in [test_struct, gen_struct]: struct.remove_oxidation_states() @@ -3175,8 +3056,7 @@ def test_apply_distortions(self): reduced_V_Cd = copy.copy(self.V_Cd) reduced_V_Cd.user_charges = [0] reduced_V_Cd_entries = [ - input._get_defect_entry_from_defect(reduced_V_Cd, c) - for c in reduced_V_Cd.user_charges + input._get_defect_entry_from_defect(reduced_V_Cd, c) for c in reduced_V_Cd.user_charges ] oxidation_states = {"Cd": +2, "Te": -2} bond_distortions = list(np.arange(-0.6, 0.601, 0.05)) @@ -3198,14 +3078,10 @@ def test_apply_distortions(self): {"vac_1_Cd": reduced_V_Cd_entries}, bond_distortions=bond_distortions, ) - distortion_defect_dict, distortion_metadata = dist.apply_distortions( - verbose=True - ) + distortion_defect_dict, distortion_metadata = dist.apply_distortions(verbose=True) self.assertFalse(os.path.exists("vac_1_Cd_0")) - self.assertEqual( - len([warning for warning in w if warning.category == UserWarning]), 5 - ) + self.assertEqual(len([warning for warning in w if warning.category == UserWarning]), 5) message_1 = ( "Bond_Distortion_-100.0% for defect vac_1_Cd gives an interatomic " "distance less than 1.0 Å (0.0 Å), which is likely to give explosive " @@ -3245,10 +3121,10 @@ def test_apply_distortions(self): ] ) ) - V_Cd_distortions_dict = distortion_defect_dict["vac_1_Cd"]["charges"][0][ - "structures" - ]["distortions"] - self.assertEqual(len(V_Cd_distortions_dict), 16) + V_Cd_distortions_dict = distortion_defect_dict["vac_1_Cd"]["charges"][0]["structures"][ + "distortions" + ] + self.assertEqual(len(V_Cd_distortions_dict), 17) # 16 distortions and Dimer self.assertFalse("Bond_Distortion_-80.0%" in V_Cd_distortions_dict) self.assertTrue("Bond_Distortion_-75.0%" in V_Cd_distortions_dict) @@ -3274,10 +3150,10 @@ def test_apply_distortions(self): ] ) ) - V_Cd_distortions_dict = distortion_defect_dict["vac_1_Cd"]["charges"][0][ - "structures" - ]["distortions"] - self.assertEqual(len(V_Cd_distortions_dict), 16) + V_Cd_distortions_dict = distortion_defect_dict["vac_1_Cd"]["charges"][0]["structures"][ + "distortions" + ] + self.assertEqual(len(V_Cd_distortions_dict), 17) self.assertFalse("Bond_Distortion_-80.0%" in V_Cd_distortions_dict) self.assertTrue("Bond_Distortion_-75.0%" in V_Cd_distortions_dict) @@ -3301,9 +3177,7 @@ def test_apply_distortions(self): {"vac_1_Cd": fake_hydrogen_V_Cd_entries}, bond_distortions=bond_distortions, ) - distortion_defect_dict, distortion_metadata = dist.apply_distortions( - verbose=True - ) + distortion_defect_dict, distortion_metadata = dist.apply_distortions(verbose=True) self.assertEqual( len([warning for warning in w if warning.category == UserWarning]), 0, # no warnings @@ -3322,30 +3196,40 @@ def test_apply_distortions(self): ] ) ) - V_Cd_distortions_dict = distortion_defect_dict["vac_1_Cd"]["charges"][0][ - "structures" - ]["distortions"] - self.assertEqual(len(V_Cd_distortions_dict), 21) # 21 total distortions + V_Cd_distortions_dict = distortion_defect_dict["vac_1_Cd"]["charges"][0]["structures"][ + "distortions" + ] + self.assertEqual(len(V_Cd_distortions_dict), 22) # 21 total distortions + Dimer self.assertTrue("Bond_Distortion_-80.0%" in V_Cd_distortions_dict) self.assertTrue("Bond_Distortion_-75.0%" in V_Cd_distortions_dict) def test_apply_distortions_indices(self): """Test apply_distortions() method when specifying indices of atoms to distort""" dist = input.Distortions( - defect_entries=[self.V_Cd_in_CdSeTe_entry,], - distorted_atoms=[33, 57], # Te, Se - bond_distortions=[-0.2,], + defect_entries=[ + self.V_Cd_in_CdSeTe_entry, + ], + distorted_atoms=[33, 57], # Te, Se + bond_distortions=[ + -0.2, + ], ) output = dist.apply_distortions() self.assertEqual( - output[1]['defects']['v_Cd_C1_Se2.68']["charges"][0]['distorted_atoms'], - [[58, 'Se'], [34, 'Te']] + output[1]["defects"]["v_Cd_C1_Se2.68"]["charges"][0]["distorted_atoms"], + [[58, "Se"], [34, "Te"]], ) # Test when user doesn't specify enough neighbours to distort dist = input.Distortions( - defect_entries=[self.V_Cd_in_CdSeTe_entry,], - distorted_atoms=[33,], # Te, Se - bond_distortions=[-0.2,], + defect_entries=[ + self.V_Cd_in_CdSeTe_entry, + ], + distorted_atoms=[ + 33, + ], # Te, Se + bond_distortions=[ + -0.2, + ], ) with warnings.catch_warnings(record=True) as w: output = dist.apply_distortions() @@ -3359,8 +3243,8 @@ def test_apply_distortions_indices(self): ), ) self.assertEqual( - output[1]['defects']['v_Cd_C1_Se2.68']["charges"][0]['distorted_atoms'], - [[58, 'Se'], [63, 'Se']] + output[1]["defects"]["v_Cd_C1_Se2.68"]["charges"][0]["distorted_atoms"], + [[58, "Se"], [63, "Se"]], ) def test_local_rattle( @@ -3370,8 +3254,7 @@ def test_local_rattle( reduced_V_Cd = copy.copy(self.V_Cd) reduced_V_Cd.user_charges = [0] reduced_V_Cd_entries = [ - input._get_defect_entry_from_defect(reduced_V_Cd, c) - for c in reduced_V_Cd.user_charges + input._get_defect_entry_from_defect(reduced_V_Cd, c) for c in reduced_V_Cd.user_charges ] oxidation_states = {"Cd": +2, "Te": -2} dist = input.Distortions( @@ -3400,9 +3283,7 @@ def test_local_rattle( Structure.from_file( f"{self.VASP_CDTE_DATA_DIR}/vac_1_Cd_0_-30.0%_Distortion_tailed_off_rattle_POSCAR" ), - defects_dict["vac_1_Cd"]["charges"][0]["structures"]["distortions"][ - "Bond_Distortion_-30.0%" - ], + defects_dict["vac_1_Cd"]["charges"][0]["structures"]["distortions"]["Bond_Distortion_-30.0%"], ) # Check if option written to metadata file self.assertTrue(metadata_dict["distortion_parameters"]["local_rattle"]) @@ -3413,8 +3294,7 @@ def test_local_rattle( +2, ] int_Cd_2_entries = [ - input._get_defect_entry_from_defect(int_Cd_2, c) - for c in int_Cd_2.user_charges + input._get_defect_entry_from_defect(int_Cd_2, c) for c in int_Cd_2.user_charges ] oxidation_states = {"Cd": +2, "Te": -2} dist = input.Distortions( @@ -3436,9 +3316,7 @@ def test_local_rattle( "['-0.3'].", "Then, will rattle with a std dev of 0.28 \u212B \n", ) - gen_struct = defects_dict["Int_Cd_2"]["charges"][2]["structures"][ - "distortions" - ]["Rattled"] + gen_struct = defects_dict["Int_Cd_2"]["charges"][2]["structures"]["distortions"]["Rattled"] gen_struct.remove_oxidation_states() test_struct = Structure.from_file( f"{self.VASP_CDTE_DATA_DIR}/Int_Cd_2_+2_tailed_off_rattle_seed_0_stdev_0.28_POSCAR" @@ -3455,8 +3333,7 @@ def test_default_rattle_stdev_and_seed( reduced_V_Cd = copy.copy(self.V_Cd) reduced_V_Cd.user_charges = [0] reduced_V_Cd_entries = [ - input._get_defect_entry_from_defect(reduced_V_Cd, c) - for c in reduced_V_Cd.user_charges + input._get_defect_entry_from_defect(reduced_V_Cd, c) for c in reduced_V_Cd.user_charges ] oxidation_states = {"Cd": +2, "Te": -2} dist = input.Distortions( @@ -3470,9 +3347,9 @@ def test_default_rattle_stdev_and_seed( self.assertTrue(dist.local_rattle) defects_dict, metadata_dict = dist.apply_distortions() # Check structure - generated_struct = defects_dict["vac_1_Cd"]["charges"][0]["structures"][ - "distortions" - ]["Bond_Distortion_-30.0%"] + generated_struct = defects_dict["vac_1_Cd"]["charges"][0]["structures"]["distortions"][ + "Bond_Distortion_-30.0%" + ] generated_struct.remove_oxidation_states() self.assertEqual( Structure.from_file( @@ -3487,8 +3364,7 @@ def test_default_rattle_stdev_and_seed( int_Cd_2 = self.Int_Cd_2 int_Cd_2.user_charges = [+2] int_Cd_2_entries = [ - input._get_defect_entry_from_defect(int_Cd_2, c) - for c in int_Cd_2.user_charges + input._get_defect_entry_from_defect(int_Cd_2, c) for c in int_Cd_2.user_charges ] oxidation_states = {"Cd": +2, "Te": -2} dist = input.Distortions( @@ -3502,9 +3378,7 @@ def test_default_rattle_stdev_and_seed( seed=0, # distortion_factor * 100, default ) defects_dict, metadata_dict = dist.apply_distortions() - generated_struct = defects_dict["Int_Cd_2"]["charges"][2]["structures"][ - "distortions" - ]["Rattled"] + generated_struct = defects_dict["Int_Cd_2"]["charges"][2]["structures"]["distortions"]["Rattled"] generated_struct.remove_oxidation_states() test_struct = Structure.from_file( f"{self.VASP_CDTE_DATA_DIR}/Int_Cd_2_+2_tailed_off_rattle_seed_0_stdev_0.28_POSCAR" @@ -3521,22 +3395,12 @@ def test_from_structures(self): # Test normal behaviour (no defect_index or defect_coords), with `defect_entries` as a single # structure with patch("builtins.print") as mock_print: - dist = input.Distortions.from_structures( - self.V_Cd_struc, self.CdTe_bulk_struc - ) + dist = input.Distortions.from_structures(self.V_Cd_struc, self.CdTe_bulk_struc) dist.write_vasp_files() for charge in [0, -1, -2]: self.assertEqual( - [ - i.defect - for i in dist.defects_dict["v_Cd_Td_Te2.83"] - if i.charge_state == charge - ][0], - [ - i.defect - for i in self.cdte_defects["vac_1_Cd"] - if i.charge_state == charge - ][0], + [i.defect for i in dist.defects_dict["v_Cd_Td_Te2.83"] if i.charge_state == charge][0], + [i.defect for i in self.cdte_defects["vac_1_Cd"] if i.charge_state == charge][0], ) # check expected info printing: @@ -3547,9 +3411,7 @@ def test_from_structures(self): "'0.5', '0.6'].", "Then, will rattle with a std dev of 0.28 Å \n", # default stdev ) - mock_print.assert_any_call( - "\033[1m" + "\nDefect: v_Cd_Td_Te2.83" + "\033[0m" - ) # bold print + mock_print.assert_any_call("\033[1m" + "\nDefect: v_Cd_Td_Te2.83" + "\033[0m") # bold print mock_print.assert_any_call( "\033[1m" + "Number of missing electrons in neutral state: 2" + "\033[0m" ) @@ -3592,29 +3454,13 @@ def test_from_structures(self): # ) for charge in [0, -1, -2]: self.assertEqual( - [ - i.defect - for i in dist.defects_dict["v_Cd_Td_Te2.83"] - if i.charge_state == charge - ][0], - [ - i.defect - for i in self.cdte_defects["vac_1_Cd"] - if i.charge_state == charge - ][0], + [i.defect for i in dist.defects_dict["v_Cd_Td_Te2.83"] if i.charge_state == charge][0], + [i.defect for i in self.cdte_defects["vac_1_Cd"] if i.charge_state == charge][0], ) for charge in [0, 1, 2]: self.assertEqual( - [ - i.defect - for i in dist.defects_dict["Cd_i_C3v_Cd2.71"] - if i.charge_state == charge - ][0], - [ - i.defect - for i in self.cdte_defects["Int_Cd_2"] - if i.charge_state == charge - ][0], + [i.defect for i in dist.defects_dict["Cd_i_C3v_Cd2.71"] if i.charge_state == charge][0], + [i.defect for i in self.cdte_defects["Int_Cd_2"] if i.charge_state == charge][0], ) # check if correct files were created: @@ -3640,9 +3486,7 @@ def test_from_structures(self): dist = input.Distortions.from_structures( [ ( - self.cdte_doped_defect_dict["vacancies"][0]["supercell"][ - "structure" - ], + self.cdte_doped_defect_dict["vacancies"][0]["supercell"]["structure"], [0, 0, 0], ) ], @@ -3663,38 +3507,20 @@ def test_from_structures(self): # ) for charge in [0, -1, -2]: self.assertEqual( - [ - i.defect - for i in dist.defects_dict["v_Cd_Td_Te2.83"] - if i.charge_state == charge - ][0], - [ - i.defect - for i in self.cdte_defects["vac_1_Cd"] - if i.charge_state == charge - ][0], + [i.defect for i in dist.defects_dict["v_Cd_Td_Te2.83"] if i.charge_state == charge][0], + [i.defect for i in self.cdte_defects["vac_1_Cd"] if i.charge_state == charge][0], ) # Test defect position given with `defect_index` with patch("builtins.print") as mock_print: - dist = input.Distortions.from_structures( - [(self.V_Cd_struc, 0)], bulk=self.CdTe_bulk_struc - ) + dist = input.Distortions.from_structures([(self.V_Cd_struc, 0)], bulk=self.CdTe_bulk_struc) # self.assertDictEqual( # dist.defects_dict, {"v_Cd": self.cdte_defects["vac_1_Cd"]} # ) for charge in [0, -1, -2]: self.assertEqual( - [ - i.defect - for i in dist.defects_dict["v_Cd_Td_Te2.83"] - if i.charge_state == charge - ][0], - [ - i.defect - for i in self.cdte_defects["vac_1_Cd"] - if i.charge_state == charge - ][0], + [i.defect for i in dist.defects_dict["v_Cd_Td_Te2.83"] if i.charge_state == charge][0], + [i.defect for i in self.cdte_defects["vac_1_Cd"] if i.charge_state == charge][0], ) # Most cases already tested in test_cli.py for `snb-generate`` (which uses @@ -3717,33 +3543,10 @@ def test_from_structures(self): ) self.assertEqual(dist.defects_dict["Cd_i_C3v_Cd2.71"][0].defect.defect_site_index, 0) self.assertEqual( - list( - dist.defects_dict["Cd_i_C3v_Cd2.71"][0].defect.defect_structure[0].frac_coords - ), + list(dist.defects_dict["Cd_i_C3v_Cd2.71"][0].defect.defect_structure[0].frac_coords), list([0.8125, 0.1875, 0.8125]), ) - # test defect_coords working even when significantly off (~2.2 Å) correct site, - # with rattled bulk - rattled_bulk = rattle(self.CdTe_bulk_struc) - with patch("builtins.print") as mock_print: - dist = input.Distortions.from_structures( - [(self.V_Cd_struc, [0, 0, 0])], bulk=rattled_bulk - ) - for charge in [0, -1, -2]: - self.assertEqual( - [ - i.defect - for i in dist.defects_dict[list(dist.defects_dict.keys())[0]] - if i.charge_state == charge - ][0], - [ - i.defect - for i in self.cdte_defects["vac_1_Cd"] - if i.charge_state == charge - ][0], - ) - # Test wrong type for defect index/coords with warnings.catch_warnings(record=True) as w: dist = input.Distortions.from_structures( @@ -3764,16 +3567,8 @@ def test_from_structures(self): # ) for charge in [0, -1, -2]: self.assertEqual( - [ - i.defect - for i in dist.defects_dict["v_Cd_Td_Te2.83"] - if i.charge_state == charge - ][0], - [ - i.defect - for i in self.cdte_defects["vac_1_Cd"] - if i.charge_state == charge - ][0], + [i.defect for i in dist.defects_dict["v_Cd_Td_Te2.83"] if i.charge_state == charge][0], + [i.defect for i in self.cdte_defects["vac_1_Cd"] if i.charge_state == charge][0], ) # Test wrong type for `defect_entries` @@ -3794,21 +3589,15 @@ def test_from_structures(self): dist = input.Distortions.from_structures(self.V_Cd_struc, self.CdTe_bulk_struc) dist.write_vasp_files() defect_name = "v_Cd_Td_Te2.83" - self.assertTrue( - os.path.exists(f"{defect_name}_+1/Bond_Distortion_-30.0%/POSCAR") - ) + self.assertTrue(os.path.exists(f"{defect_name}_+1/Bond_Distortion_-30.0%/POSCAR")) self.assertFalse(os.path.exists(f"{defect_name}_+2")) self.assertTrue(os.path.exists(f"{defect_name}_-3")) self.tearDown() # test explicitly set - dist = input.Distortions.from_structures( - self.V_Cd_struc, self.CdTe_bulk_struc, padding=4 - ) + dist = input.Distortions.from_structures(self.V_Cd_struc, self.CdTe_bulk_struc, padding=4) dist.write_vasp_files() - self.assertTrue( - os.path.exists(f"{defect_name}_+4/Bond_Distortion_-30.0%/POSCAR") - ) + self.assertTrue(os.path.exists(f"{defect_name}_+4/Bond_Distortion_-30.0%/POSCAR")) self.assertFalse(os.path.exists(f"{defect_name}_+5")) self.assertTrue(os.path.exists(f"{defect_name}_-6")) self.assertFalse(os.path.exists(f"{defect_name}_-7")) diff --git a/tests/test_io.py b/tests/test_io.py index 2483b28b..0aeacef9 100644 --- a/tests/test_io.py +++ b/tests/test_io.py @@ -2,9 +2,9 @@ import unittest import warnings -from pymatgen.core.structure import Structure +from pymatgen.core.structure import Structure, Composition, IStructure, PeriodicSite -from shakenbreak.analysis import _calculate_atomic_disp +from shakenbreak.analysis import _cached_calculate_atomic_disp from shakenbreak.io import ( parse_fhi_aims_input, parse_qe_input, @@ -15,9 +15,21 @@ read_vasp_structure, ) +# use doped efficiency functions for speed (speeds up structure matching dramatically): +from doped.utils.efficiency import Composition as doped_Composition +from doped.utils.efficiency import IStructure as doped_IStructure +from doped.utils.efficiency import PeriodicSite as doped_PeriodicSite + +Composition.__instances__ = {} +Composition.__eq__ = doped_Composition.__eq__ +PeriodicSite.__eq__ = doped_PeriodicSite.__eq__ +PeriodicSite.__hash__ = doped_PeriodicSite.__hash__ +IStructure.__instances__ = {} +IStructure.__eq__ = doped_IStructure.__eq__ + class IoTestCase(unittest.TestCase): - """ "Test functions in shakenbreak.io. + """Test functions in shakenbreak.io. Note that io.parse_energies is tested via test_cli.py""" def setUp(self): @@ -80,7 +92,7 @@ def test_read_espresso_structure(self): ) ) self.assertTrue( - _calculate_atomic_disp(structure_from_cif, structure_from_espresso_output)[ + _cached_calculate_atomic_disp(structure_from_cif, structure_from_espresso_output)[ 0 ] < 0.01 @@ -100,7 +112,7 @@ def test_read_castep_structure(self): test_structure = Structure.from_file( os.path.join(self.DATA_DIR, "castep/POSCAR") ) - self.assertTrue(_calculate_atomic_disp(test_structure, structure)[0] < 0.01) + self.assertTrue(_cached_calculate_atomic_disp(test_structure, structure)[0] < 0.01) def test_read_fhi_aims_structure(self): "Test read_cp2k_structure() function." @@ -110,14 +122,14 @@ def test_read_fhi_aims_structure(self): test_structure = Structure.from_file( os.path.join(self.DATA_DIR, "fhi_aims/POSCAR") ) - self.assertTrue(_calculate_atomic_disp(test_structure, structure)[0] < 0.01) + self.assertTrue(_cached_calculate_atomic_disp(test_structure, structure)[0] < 0.01) # Test parsing geometry.in file path = os.path.join(self.DATA_DIR, "fhi_aims/geometry.in") structure = read_fhi_aims_structure(path, format="aims") test_structure = Structure.from_file( os.path.join(self.DATA_DIR, "fhi_aims/POSCAR_geom") ) - self.assertTrue(_calculate_atomic_disp(test_structure, structure)[0] < 0.01) + self.assertTrue(_cached_calculate_atomic_disp(test_structure, structure)[0] < 0.01) def test_parse_qe_input(self): "Test parse_qe_input() function." diff --git a/tests/test_plotting.py b/tests/test_plotting.py index 8d1427b4..8c1d14ec 100644 --- a/tests/test_plotting.py +++ b/tests/test_plotting.py @@ -117,6 +117,10 @@ def tearDown(self): if_present_rm("Int_Se_1_6.png") if_present_rm("as_2_O_on_I_1.png") if_present_rm("vac_1_Cd_0.png") + if_present_rm("v_Ca_s0_0.png") + for file in os.listdir((f"{self.VASP_DIR}/Va_O1_1")): + if file.endswith(".png"): + os.remove(f"{self.VASP_DIR}/Va_O1_1/{file}") def test_verify_data_directories_exist(self): """Test _verify_data_directories_exist() function""" @@ -702,7 +706,8 @@ def test_plot_with_multiple_imported_distortions_from_same_charge_state(self): "-30.0%_from_+5": -624.65113207, "0.0%_from_+4": -624.65313207, "-15.0%_from_+7": -624.06113207, - "30.0%_from_+5": -624.65513207, + "35.0%_from_+5": -624.65513207, + "40.0%_from_+5": -624.75513207, "25.0%_from_+7": -624.06513207, "10.0%_from_+2": -624.66110949, "20.0%_from_+2": -624.65991904,