From b9471c6f07214e6f2dd4bb40f587073d6c1e7ae9 Mon Sep 17 00:00:00 2001 From: Martin Schlipf Date: Fri, 2 Feb 2024 16:44:09 +0100 Subject: [PATCH] Feat: rename data package to calculation (#139) * Rename the data package to calculation * Implement calculation package that acts like a Calculation object in the current directory * Move Calculation class and all Refinery classes into calculation package * Rename data to calculation in tests * Prepare for Viewer refactoring by moving Viewer3d to third_party * Update documentation configuration for next release * Create automatic summary of documentation * Elaborate on the documentation of each Refinery class * Introduce custom Sphinx role for INCAR tags * Improve error message on incorrect import --- docs/.gitignore | 4 +- docs/Calculation.rst | 5 - docs/_templates/class.rst | 4 + docs/_templates/member.rst | 19 ++++ docs/_templates/module.rst | 13 +++ docs/_templates/package.rst | 13 +++ docs/conf.py | 21 +++- docs/convert.yaml | 9 +- docs/data.rst | 6 - docs/index.rst | 105 +++++++++++------- src/py4vasp/__init__.py | 2 +- src/py4vasp/_combine/base.py | 24 ++-- src/py4vasp/_control/poscar.py | 4 +- .../viewer}/viewer3d.py | 0 src/py4vasp/_util/convert.py | 29 +++++ .../contcar.py => calculation/_CONTCAR.py} | 18 ++- src/py4vasp/calculation/__init__.py | 87 +++++++++++++++ .../{_data/band.py => calculation/_band.py} | 34 +++--- .../bandgap.py => calculation/_bandgap.py} | 48 ++++---- .../{_data/base.py => calculation/_base.py} | 0 .../_born_effective_charge.py} | 17 ++- .../_class.py} | 75 ++++++------- .../density.py => calculation/_density.py} | 34 +++--- .../_dielectric_function.py} | 36 +++--- .../_dielectric_tensor.py} | 16 ++- .../_dispersion.py} | 14 +-- .../{_data/dos.py => calculation/_dos.py} | 52 +++++---- .../_elastic_modulus.py} | 20 ++-- .../energy.py => calculation/_energy.py} | 32 ++++-- .../fatband.py => calculation/_fatband.py} | 24 ++-- .../{_data/force.py => calculation/_force.py} | 29 +++-- .../_force_constant.py} | 20 ++-- .../_internal_strain.py} | 18 +-- .../kpoint.py => calculation/_kpoint.py} | 44 +++++--- .../_magnetism.py} | 52 +++++---- .../_pair_correlation.py} | 35 +++--- .../phonon.py => calculation/_phonon.py} | 8 +- .../_phonon_band.py} | 35 ++++-- .../_phonon_dos.py} | 34 +++--- .../_piezoelectric_tensor.py} | 26 +++-- .../_polarization.py} | 16 ++- .../_potential.py} | 32 ++++-- .../_projector.py} | 36 +++--- .../_selection.py} | 0 .../slice_.py => calculation/_slice.py} | 0 .../stress.py => calculation/_stress.py} | 31 +++--- .../_structure.py} | 74 ++++++------ .../system.py => calculation/_system.py} | 10 +- .../topology.py => calculation/_topology.py} | 37 +++--- .../velocity.py => calculation/_velocity.py} | 29 +++-- .../_workfunction.py} | 28 +++-- src/py4vasp/data.py | 51 --------- src/py4vasp/exception.py | 5 + tests/analysis/test_mlff.py | 29 ++--- .../_data => tests/calculation}/__init__.py | 0 tests/{data => calculation}/conftest.py | 0 tests/{data => calculation}/test_band.py | 35 +++--- tests/{data => calculation}/test_bandgap.py | 10 +- tests/{data => calculation}/test_base.py | 24 ++-- .../test_born_effective_charge.py | 9 +- .../test_class.py} | 27 +---- tests/{data => calculation}/test_contcar.py | 11 +- tests/{data => calculation}/test_density.py | 16 +-- .../test_dielectric_function.py | 15 +-- .../test_dielectric_tensor.py | 9 +- .../{data => calculation}/test_dispersion.py | 8 +- tests/{data => calculation}/test_dos.py | 20 ++-- .../test_elastic_modulus.py | 6 +- tests/{data => calculation}/test_energy.py | 13 +-- tests/{data => calculation}/test_fatband.py | 8 +- tests/{data => calculation}/test_force.py | 15 ++- .../test_force_constant.py | 9 +- .../test_internal_strain.py | 9 +- tests/{data => calculation}/test_kpoint.py | 19 ++-- tests/{data => calculation}/test_magnetism.py | 13 +-- .../test_pair_correlation.py | 12 +- .../{data => calculation}/test_phonon_band.py | 14 +-- .../{data => calculation}/test_phonon_dos.py | 10 +- .../test_piezoelectric_tensor.py | 6 +- .../test_polarization.py | 6 +- tests/{data => calculation}/test_potential.py | 13 +-- tests/{data => calculation}/test_projector.py | 15 ++- tests/calculation/test_repr.py | 16 +++ .../{data => calculation}/test_slice_mixin.py | 8 +- tests/{data => calculation}/test_stress.py | 13 +-- tests/{data => calculation}/test_structure.py | 22 ++-- tests/{data => calculation}/test_system.py | 9 +- tests/{data => calculation}/test_topology.py | 14 +-- tests/{data => calculation}/test_velocity.py | 15 ++- .../test_workfunction.py | 11 +- tests/conftest.py | 1 - tests/control/test_poscar.py | 5 +- tests/data/__init__.py | 0 tests/data/test_repr.py | 39 ------- tests/test_calculations.py | 2 +- tests/{data => third_party}/test_viewer3d.py | 21 ++-- 96 files changed, 1131 insertions(+), 841 deletions(-) delete mode 100644 docs/Calculation.rst create mode 100644 docs/_templates/class.rst create mode 100644 docs/_templates/member.rst create mode 100644 docs/_templates/module.rst create mode 100644 docs/_templates/package.rst delete mode 100644 docs/data.rst rename src/py4vasp/{_data => _third_party/viewer}/viewer3d.py (100%) rename src/py4vasp/{_data/contcar.py => calculation/_CONTCAR.py} (91%) create mode 100644 src/py4vasp/calculation/__init__.py rename src/py4vasp/{_data/band.py => calculation/_band.py} (87%) rename src/py4vasp/{_data/bandgap.py => calculation/_bandgap.py} (83%) rename src/py4vasp/{_data/base.py => calculation/_base.py} (100%) rename src/py4vasp/{_data/born_effective_charge.py => calculation/_born_effective_charge.py} (69%) rename src/py4vasp/{_calculation.py => calculation/_class.py} (66%) rename src/py4vasp/{_data/density.py => calculation/_density.py} (92%) rename src/py4vasp/{_data/dielectric_function.py => calculation/_dielectric_function.py} (80%) rename src/py4vasp/{_data/dielectric_tensor.py => calculation/_dielectric_tensor.py} (78%) rename src/py4vasp/{_data/dispersion.py => calculation/_dispersion.py} (94%) rename src/py4vasp/{_data/dos.py => calculation/_dos.py} (69%) rename src/py4vasp/{_data/elastic_modulus.py => calculation/_elastic_modulus.py} (65%) rename src/py4vasp/{_data/energy.py => calculation/_energy.py} (85%) rename src/py4vasp/{_data/fatband.py => calculation/_fatband.py} (66%) rename src/py4vasp/{_data/force.py => calculation/_force.py} (74%) rename src/py4vasp/{_data/force_constant.py => calculation/_force_constant.py} (64%) rename src/py4vasp/{_data/internal_strain.py => calculation/_internal_strain.py} (69%) rename src/py4vasp/{_data/kpoint.py => calculation/_kpoint.py} (85%) rename src/py4vasp/{_data/magnetism.py => calculation/_magnetism.py} (82%) rename src/py4vasp/{_data/pair_correlation.py => calculation/_pair_correlation.py} (72%) rename src/py4vasp/{_data/phonon.py => calculation/_phonon.py} (92%) rename src/py4vasp/{_data/phonon_band.py => calculation/_phonon_band.py} (62%) rename src/py4vasp/{_data/phonon_dos.py => calculation/_phonon_dos.py} (62%) rename src/py4vasp/{_data/piezoelectric_tensor.py => calculation/_piezoelectric_tensor.py} (59%) rename src/py4vasp/{_data/polarization.py => calculation/_polarization.py} (65%) rename src/py4vasp/{_data/potential.py => calculation/_potential.py} (78%) rename src/py4vasp/{_data/projector.py => calculation/_projector.py} (94%) rename src/py4vasp/{_data/selection.py => calculation/_selection.py} (100%) rename src/py4vasp/{_data/slice_.py => calculation/_slice.py} (100%) rename src/py4vasp/{_data/stress.py => calculation/_stress.py} (69%) rename src/py4vasp/{_data/structure.py => calculation/_structure.py} (83%) rename src/py4vasp/{_data/system.py => calculation/_system.py} (63%) rename src/py4vasp/{_data/topology.py => calculation/_topology.py} (87%) rename src/py4vasp/{_data/velocity.py => calculation/_velocity.py} (69%) rename src/py4vasp/{_data/workfunction.py => calculation/_workfunction.py} (71%) delete mode 100644 src/py4vasp/data.py rename {src/py4vasp/_data => tests/calculation}/__init__.py (100%) rename tests/{data => calculation}/conftest.py (100%) rename tests/{data => calculation}/test_band.py (92%) rename tests/{data => calculation}/test_bandgap.py (97%) rename tests/{data => calculation}/test_base.py (97%) rename tests/{data => calculation}/test_born_effective_charge.py (88%) rename tests/{test_calculation.py => calculation/test_class.py} (77%) rename tests/{data => calculation}/test_contcar.py (93%) rename tests/{data => calculation}/test_density.py (95%) rename tests/{data => calculation}/test_dielectric_function.py (96%) rename tests/{data => calculation}/test_dielectric_tensor.py (94%) rename tests/{data => calculation}/test_dispersion.py (94%) rename tests/{data => calculation}/test_dos.py (95%) rename tests/{data => calculation}/test_elastic_modulus.py (92%) rename tests/{data => calculation}/test_energy.py (94%) rename tests/{data => calculation}/test_fatband.py (88%) rename tests/{data => calculation}/test_force.py (92%) rename tests/{data => calculation}/test_force_constant.py (93%) rename tests/{data => calculation}/test_internal_strain.py (91%) rename tests/{data => calculation}/test_kpoint.py (94%) rename tests/{data => calculation}/test_magnetism.py (95%) rename tests/{data => calculation}/test_pair_correlation.py (90%) rename tests/{data => calculation}/test_phonon_band.py (91%) rename tests/{data => calculation}/test_phonon_dos.py (92%) rename tests/{data => calculation}/test_piezoelectric_tensor.py (91%) rename tests/{data => calculation}/test_polarization.py (87%) rename tests/{data => calculation}/test_potential.py (94%) rename tests/{data => calculation}/test_projector.py (97%) create mode 100644 tests/calculation/test_repr.py rename tests/{data => calculation}/test_slice_mixin.py (95%) rename tests/{data => calculation}/test_stress.py (89%) rename tests/{data => calculation}/test_structure.py (95%) rename tests/{data => calculation}/test_system.py (80%) rename tests/{data => calculation}/test_topology.py (90%) rename tests/{data => calculation}/test_velocity.py (91%) rename tests/{data => calculation}/test_workfunction.py (91%) delete mode 100644 tests/data/__init__.py delete mode 100644 tests/data/test_repr.py rename tests/{data => third_party}/test_viewer3d.py (92%) diff --git a/docs/.gitignore b/docs/.gitignore index 9e5bfb42..59ecbf72 100644 --- a/docs/.gitignore +++ b/docs/.gitignore @@ -1 +1,3 @@ -api \ No newline at end of file +api +_packages +_classes diff --git a/docs/Calculation.rst b/docs/Calculation.rst deleted file mode 100644 index 68609203..00000000 --- a/docs/Calculation.rst +++ /dev/null @@ -1,5 +0,0 @@ -Calculation -=========== - -.. autoclass:: py4vasp.Calculation - :members: diff --git a/docs/_templates/class.rst b/docs/_templates/class.rst new file mode 100644 index 00000000..d5e4433a --- /dev/null +++ b/docs/_templates/class.rst @@ -0,0 +1,4 @@ +{{ name | escape | underline }} + +.. autoclass:: {{ fullname }} + :members: diff --git a/docs/_templates/member.rst b/docs/_templates/member.rst new file mode 100644 index 00000000..e88cf114 --- /dev/null +++ b/docs/_templates/member.rst @@ -0,0 +1,19 @@ +{{ name | escape | underline }} + +.. container:: quantity + + .. currentmodule:: py4vasp.calculation + .. data:: {{ name }} + + .. currentmodule:: py4vasp.calculation._{{name}} + .. autoclass:: {% + if name == "CONTCAR" -%} + CONTCAR + {%- else -%} + {%- for part in name.split("_") -%} + {{ part.capitalize() }} + {%- endfor -%} + {%- endif %} + :members: + :inherited-members: + :exclude-members: from_data, from_file, from_path, path diff --git a/docs/_templates/module.rst b/docs/_templates/module.rst new file mode 100644 index 00000000..336187ee --- /dev/null +++ b/docs/_templates/module.rst @@ -0,0 +1,13 @@ +{{ name | escape | underline }} + +.. automodule:: {{ fullname }} + +.. autosummary:: + + {% for function in functions %} + {{ function }} + {% endfor %} + +{% for function in functions %} +.. autofunction:: {{ function }} +{% endfor %} diff --git a/docs/_templates/package.rst b/docs/_templates/package.rst new file mode 100644 index 00000000..a93e824f --- /dev/null +++ b/docs/_templates/package.rst @@ -0,0 +1,13 @@ +{{ name | escape | underline }} + +.. automodule:: {{ fullname }} + +.. rubric:: Attributes + +.. autosummary:: + :toctree: + :template: member.rst + + {% for member in members %} + {{ member }} + {% endfor %} diff --git a/docs/conf.py b/docs/conf.py index b1498e4f..4f85b52d 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -18,11 +18,11 @@ # -- Project information ----------------------------------------------------- project = "py4vasp" -copyright = "2023, VASP Software GmbH" +copyright = "2024, VASP Software GmbH" author = "VASP Software GmbH" # The full version, including alpha/beta/rc tags -release = "0.7.1" +release = "0.9.0" # -- General configuration --------------------------------------------------- @@ -32,6 +32,7 @@ # ones. extensions = ["sphinx.ext.napoleon", "sphinx_automodapi.automodapi"] automodapi_inheritance_diagram = False +autosummary_ignore_module_all = False # Add any paths that contain templates here, relative to this directory. templates_path = ["_templates"] @@ -64,3 +65,19 @@ # remove common py4vasp prefix from index modindex_common_prefix = ["py4vasp."] + + +# -- Custom extension of Sphinx ---------------------------------------------- +from docutils import nodes + + +# defines an INCAR tag +def tag_role(name, rawtext, text, lineno, inliner, options={}, content=[]): + url = f"https://www.vasp.at/wiki/index.php/{text}" + node = nodes.reference(rawtext, text, refuri=url, **options) + return [node], [] + + +def setup(app): + app.add_role("tag", tag_role) + return diff --git a/docs/convert.yaml b/docs/convert.yaml index 10f114e4..d74267e4 100644 --- a/docs/convert.yaml +++ b/docs/convert.yaml @@ -1,13 +1,12 @@ latest: - dirhtml - - Calculation: - - dirhtml/Calculation + - calculation: + - dirhtml/_packages/py4vasp.calculation + - dirhtml/_classes/py4vasp.Calculation + - dirhtml/_packages/py4vasp.calculation.* - raw: - dirhtml/raw - dirhtml/api/py4vasp.raw.* - - data: - - dirhtml/data - - dirhtml/api/py4vasp.data.* - exception: - dirhtml/exception - dirhtml/api/py4vasp.exception.* diff --git a/docs/data.rst b/docs/data.rst deleted file mode 100644 index 6acd5d2d..00000000 --- a/docs/data.rst +++ /dev/null @@ -1,6 +0,0 @@ -data -==== - -.. automodapi:: py4vasp.data - :inherited-members: - :allowed-package-names: py4vasp._data diff --git a/docs/index.rst b/docs/index.rst index d40d85e4..04d23dbf 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -11,9 +11,9 @@ associated with the XML or OUTCAR files. For these two groups of users, we provide a different level of access. The simple routines used in the tutorials will read the data from the file directly -and then generate the requested plot. For script developers, we provide an -expert interface where the data is lazily loaded as needed with some greater -flexibility when the data file is opened and closed. +and then generate the requested plot. For script developers, we provide interfaces +to convert the data to Python dictionaries for further processing. If I/O access +limits the performance, you can lazily load the data only when needed. Installation ------------ @@ -28,15 +28,27 @@ You can then install *py4vasp* from PyPI_ using the pip package installer pip install py4vasp -This will automatically download *py4vasp* as well as all the required dependencies. -However, we noticed that this approach is not fail-safe, because the installation -of the *mdtraj* dependency does not work on all operating systems. So in case -the simple installation above fails, you may need to use *conda* to install *mdtraj* +This will automatically download *py4vasp* as well as most of the required dependencies. +However, we do not install the *mdtraj* dependency by default because it does not +reliably work with pip. We recommend to install *mdtraj* using conda .. code-block:: bash conda install -c conda-forge mdtraj - pip install py4vasp + +if you need the features that depend on it (plotting a trajectory of structures). + +For a minimalistic setup where you use py4vasp as a library, you can install the +core package + +.. code-block:: bash + + pip install py4vasp-core + +The core package contains the same source code as the main package and does not +impact the usage. However, it does not install any of the dependencies of *py4vasp* +except for *numpy* and *h5py*. Hence, this core package is most suitable for +script developers that do not need all the visualization features of *py4vasp*. Alternatively, you can obtain the code from GitHub and install it. This will give you the most recent version with all bugfixes. However, some features may only work once @@ -47,6 +59,7 @@ the next VASP version is released. git clone https://github.com/vasp-dev/py4vasp.git cd py4vasp pip install . + conda install -c conda-forge mdtraj If these commands succeed, you should be able to use *py4vasp*. You can make a quick test of your installation running the following command @@ -75,28 +88,12 @@ The user interface of *py4vasp* is optimized for usage inside a Jupyter_ environ (Jupyter notebook or Jupyter lab), though it can be used in regular Python scripts as well. To give you an illustrative example of what *py4vasp* can do, we assume that you created a Jupyter notebook inside the directory of your VASP calculation. -Then you access all the results of this calculation with - ->>> from py4vasp import Calculation ->>> calc = Calculation.from_path(".") - -Naturally, if you created the notebook outside of the calculation directory, you -would replace the path ``.`` with the directory of the calculation. - -The attributes of the calculation correspond to different physical quantities that -you could have calculated with VASP. If you have an interactive session you can type -``calc.`` and then hit :kbd:`Tab` to get a list of all possible quantities. However -only the ones that you computed with VASP will give you any meaningful -result. +In the VASP calculation, you computed the density of states (DOS) with orbital +projections (:tag:`LORBIT` = 11). You may now want to read the data from your +VASP calculation to post-process it further with a script. This can be achieved with -.. _LORBIT: https://www.vasp.at/wiki/index.php/LORBIT - -In the following, we will assume that you computed the density of states (DOS) with -orbital projections (LORBIT_ = 11). You may now want to read the data from your -VASP calculation to post-process it further with a script. This can be achieved in -a single line of code - ->>> dos = calc.dos.read() +>>> import py4vasp +>>> dos = py4vasp.calculation.dos.read() Under the hood, this will access the *vaspout.h5* file, because *py4vasp* knows where the output is stored after you ran VASP. It will read the relevant tags from @@ -104,23 +101,40 @@ the file and store them all in a Python dictionary. If you want to access partic orbital projections, let's say the *p* orbitals, you can pass a ``selection = "p"`` as an argument to the routine. More generally, you can check how to use a function with ->>> help(calc.dos.read) +>>> help(py4vasp.calculation.dos.read) The most common use case for the DOS data may be to prepare a plot to get some insight into the system of interest. Because of this, we provide an easy wrapper for this particular functionality ->>> calc.dos.plot() +>>> py4vasp.calculation.dos.plot() This will return an interactive figure that you can use to investigate the DOS. -Note that this requires a browser to work, which means it will open one if you -execute this inside a script instead of a Jupyter notebook. The *plot* command -takes the same arguments as the read command. +The *plot* command takes the same arguments as the read command. Note that this +requires a browser to work; if you execute this from within a interactive +environment, it may open a browser for you or you can enforce it by appending +`.show()` + +>>> py4vasp.calculation.dos.plot().show() The interface for the other quantities is very similar. Every quantity provides a *read* function to get the raw data into Python and where it makes sense a *plot* function visualizes the data. However, note that in particular, all data visualized inside the structure require a Jupyter notebook to work. +All plots can be converted to csv files `to_csv` of pandas dataframes `to_frame` +for further refinement. + +If your calculation is not in the root directory, you can create your own +instance + +>>> from py4vasp import Calculation +>>> calc = Calculation.from_path("/path/to/your/VASP/calcualtion") + +The attributes of the calculation correspond to different physical quantities that +you could have calculated with VASP. If you have an interactive session you can type +``calc.`` and then hit :kbd:`Tab` to get a list of all possible quantities. However +only the ones that you computed with VASP will give you any meaningful +result. .. _tutorials: https://www.vasp.at/tutorials/latest @@ -129,15 +143,24 @@ a look at the tutorials_ for VASP. Many of them use *py4vasp* to plot or analyze the data produced by VASP, so this may give you an excellent starting point to learn how you can apply *py4vasp* in your research. -.. toctree:: - :maxdepth: 1 - :caption: Contents: +.. currentmodule:: py4vasp + +.. rubric:: Packages + +.. autosummary:: + :toctree: _packages + :template: package.rst + :recursive: + + calculation + +.. rubric:: Classes + +.. autosummary:: + :toctree: _classes + :template: class.rst Calculation - control - raw - data - exception ---------------------------------------------------------------------------------------- diff --git a/src/py4vasp/__init__.py b/src/py4vasp/__init__.py index 60643461..4a5df4df 100644 --- a/src/py4vasp/__init__.py +++ b/src/py4vasp/__init__.py @@ -1,10 +1,10 @@ # Copyright © VASP Software GmbH, # Licensed under the Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0) from py4vasp._analysis.mlff import MLFFErrorAnalysis -from py4vasp._calculation import Calculation from py4vasp._calculations import Calculations from py4vasp._third_party.graph import plot from py4vasp._third_party.interactive import set_error_handling +from py4vasp.calculation._class import Calculation __version__ = "0.8.0" set_error_handling("Minimal") diff --git a/src/py4vasp/_combine/base.py b/src/py4vasp/_combine/base.py index 5f228a5e..3672a4b5 100644 --- a/src/py4vasp/_combine/base.py +++ b/src/py4vasp/_combine/base.py @@ -1,26 +1,26 @@ # Copyright © VASP Software GmbH, # Licensed under the Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0) -import importlib import inspect import pathlib from typing import Dict, List -from py4vasp import data, exception +from py4vasp import calculation, exception def _match_combine_with_refinement(combine_name: str): combine_to_refinement_name = { - "Energies": "Energy", - "Forces": "Force", - "Stresses": "Stress", + "Energies": "energy", + "Forces": "force", + "Stresses": "stress", } - for _, class_ in inspect.getmembers(data, inspect.isclass): - if class_.__name__ == combine_to_refinement_name[combine_name]: - return class_ - else: - raise exception.IncorrectUsage( - f"Could not find refinement class for {combine_name}." - ) + return getattr(calculation, combine_to_refinement_name[combine_name]) + # for _, class_ in inspect.getmembers(data_depr, inspect.isclass): + # if class_.__name__ == combine_to_refinement_name[combine_name]: + # return class_ + # else: + # raise exception.IncorrectUsage( + # f"Could not find refinement class for {combine_name}." + # ) class BaseCombine: diff --git a/src/py4vasp/_control/poscar.py b/src/py4vasp/_control/poscar.py index 9dfe4f7c..d3e7a9f7 100644 --- a/src/py4vasp/_control/poscar.py +++ b/src/py4vasp/_control/poscar.py @@ -1,7 +1,7 @@ # Copyright © VASP Software GmbH, # Licensed under the Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0) +from py4vasp import calculation from py4vasp._control import base -from py4vasp.data import Structure class POSCAR(base.InputFile): @@ -28,4 +28,4 @@ def plot(self, *args, **kwargs): Viewer3d Visualize the structure as a 3d figure. """ - return Structure.from_POSCAR(self).plot(*args, **kwargs) + return calculation.structure.from_POSCAR(self).plot(*args, **kwargs) diff --git a/src/py4vasp/_data/viewer3d.py b/src/py4vasp/_third_party/viewer/viewer3d.py similarity index 100% rename from src/py4vasp/_data/viewer3d.py rename to src/py4vasp/_third_party/viewer/viewer3d.py diff --git a/src/py4vasp/_util/convert.py b/src/py4vasp/_util/convert.py index 86c47e6a..5685105f 100644 --- a/src/py4vasp/_util/convert.py +++ b/src/py4vasp/_util/convert.py @@ -46,6 +46,35 @@ def _to_snakecase(word: str) -> str: return word.lower() +# NOTE: to_camelcase is the function camelize from the inflection package +# (Copyright (C) 2012-2020 Janne Vanhala) +def to_camelcase(string: str, uppercase_first_letter: bool = True) -> str: + """ + Convert strings to CamelCase. + + Examples:: + + >>> camelize("device_type") + 'DeviceType' + >>> camelize("device_type", False) + 'deviceType' + + :func:`camelize` can be thought of as a inverse of :func:`underscore`, + although there are some cases where that does not hold:: + + >>> camelize(underscore("IOError")) + 'IoError' + + :param uppercase_first_letter: if set to `True` :func:`camelize` converts + strings to UpperCamelCase. If set to `False` :func:`camelize` produces + lowerCamelCase. Defaults to `True`. + """ + if uppercase_first_letter: + return re.sub(r"(?:^|_)(.)", lambda m: m.group(1).upper(), string) + else: + return string[0].lower() + camelize(string)[1:] + + def to_rgb(hex): "Convert a HEX color code to fractional RGB." hex = hex.lstrip("#") diff --git a/src/py4vasp/_data/contcar.py b/src/py4vasp/calculation/_CONTCAR.py similarity index 91% rename from src/py4vasp/_data/contcar.py rename to src/py4vasp/calculation/_CONTCAR.py index 8cdbc51b..10ce1c6d 100644 --- a/src/py4vasp/_data/contcar.py +++ b/src/py4vasp/calculation/_CONTCAR.py @@ -1,21 +1,19 @@ # Copyright © VASP Software GmbH, # Licensed under the Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0) -import numpy as np - -from py4vasp import data, raw -from py4vasp._data import base, structure +from py4vasp import calculation from py4vasp._util import convert +from py4vasp.calculation import _base, _structure -class CONTCAR(base.Refinery, structure.Mixin): - """Access the final positions after the VASP calculation. +class CONTCAR(_base.Refinery, _structure.Mixin): + """CONTCAR contains structural restart-data after a relaxation or MD simulation. The CONTCAR contains the final structure of the VASP calculation. It can be used as input for the next calculation if desired. Depending on the particular setup the CONTCAR might contain additional information about the system such as the ion and lattice velocities.""" - @base.data_access + @_base.data_access def to_dict(self): """Extract the structural data and the available additional data to a dictionary. @@ -37,7 +35,7 @@ def _read(self, key): data = getattr(self._raw_data, key) return {key: data[:]} if not data.is_none() else {} - @base.data_access + @_base.data_access def plot(self, supercell=None): """Generate a visualization of the final structure. @@ -53,7 +51,7 @@ def plot(self, supercell=None): """ return self._structure.plot(supercell) - @base.data_access + @_base.data_access def __str__(self): return "\n".join(self._line_generator()) @@ -72,7 +70,7 @@ def _line_generator(self): yield from _ion_velocity_lines(self._raw_data.ion_velocities) def _topology(self): - return data.Topology.from_data(self._raw_data.structure.topology) + return calculation.topology.from_data(self._raw_data.structure.topology) def _cell_lines(cell): diff --git a/src/py4vasp/calculation/__init__.py b/src/py4vasp/calculation/__init__.py new file mode 100644 index 00000000..b82f9946 --- /dev/null +++ b/src/py4vasp/calculation/__init__.py @@ -0,0 +1,87 @@ +# Copyright © VASP Software GmbH, +# Licensed under the Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0) +"""Provide refinement functions for a the raw data of a VASP calculation run in the +current directory. + +Usually one is not directly interested in the raw data that is produced but +wants to produce either a figure for a publication or some post-processing of +the data. This package contains multiple modules that enable these kinds of +workflows by extracting the relevant data from the HDF5 file and transforming +them into an accessible format. The modules also provide plotting functionality +to get a quick insight about the data, which can then be refined either within +python or a different tool to obtain publication-quality figures. + +Generally, all modules provide a `read` function that extracts the data from the +HDF5 file and puts it into a Python dictionary. Where it makes sense in addition +a `plot` function is available that converts the data into a figure for Jupyter +notebooks. In addition, data conversion routines `to_X` may be available +transforming the data into another format or file, which may be useful to +generate plots with tools other than Python. For the specifics, please refer to +the documentation of the individual modules. + +The raw data is read from the current directory. The :class:`~py4vasp.Calculation` +class provides a more flexible interface with which you can determine the source +directory or file for the VASP calculation manually. That class exposes functions +of the modules as methods of attributes, i.e., the two following examples are +equivalent: + +.. rubric:: using :mod:`~py4vasp.calculation` module + +>>> from py4vasp import calculation +>>> calculation.dos.read() + +.. rubric:: using :class:`~py4vasp.Calculation` class + +>>> from py4vasp import Calculation +>>> calc = Calculation.from_path(".") +>>> calc.dos.read() + +In the latter example, you can change the path from which the data is extracted. +""" +import importlib + +from py4vasp import exception +from py4vasp._util import convert + +__all__ = ( + "band", + "bandgap", + "born_effective_charge", + "CONTCAR", + "density", + "dielectric_function", + "dielectric_tensor", + "dos", + "elastic_modulus", + "energy", + "fatband", + "force", + "force_constant", + "internal_strain", + "kpoint", + "magnetism", + "pair_correlation", + "phonon_band", + "phonon_dos", + "piezoelectric_tensor", + "polarization", + "potential", + "projector", + "stress", + "structure", + "system", + "topology", + "velocity", + "workfunction", +) +_private = ("dispersion",) + + +def __getattr__(attr): + if attr in (__all__ + _private): + module = importlib.import_module(f"py4vasp.calculation._{attr}") + class_ = getattr(module, convert.to_camelcase(attr)) + return class_.from_path(".") + else: + message = f"Could not find {attr} in the possible attributes, please check the spelling" + raise exception.MissingAttribute(message) diff --git a/src/py4vasp/_data/band.py b/src/py4vasp/calculation/_band.py similarity index 87% rename from src/py4vasp/_data/band.py rename to src/py4vasp/calculation/_band.py index 3b484655..4e18d705 100644 --- a/src/py4vasp/_data/band.py +++ b/src/py4vasp/calculation/_band.py @@ -2,26 +2,26 @@ # Licensed under the Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0) import numpy as np -from py4vasp import data -from py4vasp._data import base, projector +from py4vasp import calculation from py4vasp._third_party import graph from py4vasp._util import check, documentation, import_ +from py4vasp.calculation import _base, _projector pd = import_.optional("pandas") pretty = import_.optional("IPython.lib.pretty") -class Band(base.Refinery, graph.Mixin): - """The electronic band structure. +class Band(_base.Refinery, graph.Mixin): + """The band structure contains the **k** point resolved eigenvalues. The most common use case of this class is to produce the electronic band structure along a path in the Brillouin zone used in a non self consistent - Vasp calculation. In some cases you may want to use the `to_dict` function + VASP calculation. In some cases you may want to use the `to_dict` function just to obtain the eigenvalue and projection data though in that case the **k**-point distances that are calculated are meaningless. """ - @base.data_access + @_base.data_access def __str__(self): return f""" {"spin polarized" if self._spin_polarized() else ""} band data: @@ -30,10 +30,10 @@ def __str__(self): {pretty.pretty(self._projector)} """.strip() - @base.data_access + @_base.data_access @documentation.format( - selection_doc=projector.selection_doc, - examples=projector.selection_examples("band", "to_dict"), + selection_doc=_projector.selection_doc, + examples=_projector.selection_examples("band", "to_dict"), ) def to_dict(self, selection=None): """Read the data into a dictionary. @@ -62,10 +62,10 @@ def to_dict(self, selection=None): "projections": self._read_projections(selection), } - @base.data_access + @_base.data_access @documentation.format( - selection_doc=projector.selection_doc, - examples=projector.selection_examples("band", "to_graph"), + selection_doc=_projector.selection_doc, + examples=_projector.selection_examples("band", "to_graph"), ) def to_graph(self, selection=None, width=0.5): """Read the data and generate a graph. @@ -91,10 +91,10 @@ def to_graph(self, selection=None, width=0.5): graph.ylabel = "Energy (eV)" return graph - @base.data_access + @_base.data_access @documentation.format( - selection_doc=projector.selection_doc, - examples=projector.selection_examples("band", "to_frame"), + selection_doc=_projector.selection_doc, + examples=_projector.selection_examples("band", "to_frame"), ) def to_frame(self, selection=None): """Read the data into a DataFrame. @@ -121,11 +121,11 @@ def _spin_polarized(self): @property def _dispersion(self): - return data.Dispersion.from_data(self._raw_data.dispersion) + return calculation.dispersion.from_data(self._raw_data.dispersion) @property def _projector(self): - return data.Projector.from_data(self._raw_data.projectors) + return calculation.projector.from_data(self._raw_data.projectors) def _projections(self, selection, width): if selection is None: diff --git a/src/py4vasp/_data/bandgap.py b/src/py4vasp/calculation/_bandgap.py similarity index 83% rename from src/py4vasp/_data/bandgap.py rename to src/py4vasp/calculation/_bandgap.py index 4f7cf776..52a0d524 100644 --- a/src/py4vasp/_data/bandgap.py +++ b/src/py4vasp/calculation/_bandgap.py @@ -6,9 +6,9 @@ import numpy as np from py4vasp import exception -from py4vasp._data import base, slice_ from py4vasp._third_party import graph from py4vasp._util import convert, documentation, select +from py4vasp.calculation import _base, _slice class Gap(typing.NamedTuple): @@ -24,17 +24,27 @@ class Gap(typing.NamedTuple): COMPONENTS = ("independent", "up", "down") -@documentation.format(examples=slice_.examples("bandgap")) -class Bandgap(slice_.Mixin, base.Refinery, graph.Mixin): - """Extract information about the band extrema during the relaxation or MD simulation. +@documentation.format(examples=_slice.examples("bandgap")) +class Bandgap(_slice.Mixin, _base.Refinery, graph.Mixin): + """This class describes the band extrema during the relaxation or MD simulation. - Contains utility functions to access the fundamental and direct bandgap as well as - the k-point coordinates at which these are found. + The bandgap represents the energy difference between the highest energy electrons + in the valence band and the lowest energy electrons in the conduction band of a + material. The fundamental gap occurs between the energy states of electrons in the + valence and conduction bands irrespective of the **k** point. In contrast, the + direct gap means that transition from valence to conduction band does not change + the **k** momentum. + + To study bandgap the extrema of the valence and conduction band play an important + role. This class reports the valence band maximum as well as the conduction band + minimum. For collinear calculations (ISPIN = 2) all values are reported separately + for both spins as well as ignoring the spin. This simplifies comparison to + experimental data, where the transitions either conserve the spin or not. {examples} """ - @base.data_access + @_base.data_access def __str__(self): template = """\ Band structure @@ -85,8 +95,8 @@ def _output_kpoint(self, label): to_string = lambda kpoint: " ".join(map("{:8.4f}".format, kpoint)) return " " + " ".join(map(to_string, kpoints)) - @base.data_access - @documentation.format(examples=slice_.examples("bandgap", "to_dict")) + @_base.data_access + @documentation.format(examples=_slice.examples("bandgap", "to_dict")) def to_dict(self): """Read the bandgap data from a VASP relaxation or MD trajectory. @@ -121,8 +131,8 @@ def _kpoint_dict(self, label): def _suffixes(self): return ("", "_up", "_down") if self._spin_polarized() else ("",) - @base.data_access - @documentation.format(examples=slice_.examples("bandgap", "fundamental")) + @_base.data_access + @documentation.format(examples=_slice.examples("bandgap", "fundamental")) def fundamental(self): """Return the fundamental bandgap. @@ -138,8 +148,8 @@ def fundamental(self): """ return self._gap("fundamental", component=0) - @base.data_access - @documentation.format(examples=slice_.examples("bandgap", "direct")) + @_base.data_access + @documentation.format(examples=_slice.examples("bandgap", "direct")) def direct(self): """Return the direct bandgap. @@ -155,8 +165,8 @@ def direct(self): """ return self._gap("direct", component=0) - @base.data_access - @documentation.format(examples=slice_.examples("bandgap", "valence_band_maximum")) + @_base.data_access + @documentation.format(examples=_slice.examples("bandgap", "valence_band_maximum")) def valence_band_maximum(self): """Return the valence band maximum. @@ -169,9 +179,9 @@ def valence_band_maximum(self): """ return self._get(GAPS["fundamental"].bottom, component=0) - @base.data_access + @_base.data_access @documentation.format( - examples=slice_.examples("bandgap", "conduction_band_minimum") + examples=_slice.examples("bandgap", "conduction_band_minimum") ) def conduction_band_minimum(self): """Return the conduction band minimum. @@ -185,8 +195,8 @@ def conduction_band_minimum(self): """ return self._get(GAPS["fundamental"].top, component=0) - @base.data_access - @documentation.format(examples=slice_.examples("bandgap", "to_graph")) + @_base.data_access + @documentation.format(examples=_slice.examples("bandgap", "to_graph")) def to_graph(self, selection="fundamental, direct"): """Plot the direct and fundamental bandgap along the trajectory. diff --git a/src/py4vasp/_data/base.py b/src/py4vasp/calculation/_base.py similarity index 100% rename from src/py4vasp/_data/base.py rename to src/py4vasp/calculation/_base.py diff --git a/src/py4vasp/_data/born_effective_charge.py b/src/py4vasp/calculation/_born_effective_charge.py similarity index 69% rename from src/py4vasp/_data/born_effective_charge.py rename to src/py4vasp/calculation/_born_effective_charge.py index bd38980c..c81d4650 100644 --- a/src/py4vasp/_data/born_effective_charge.py +++ b/src/py4vasp/calculation/_born_effective_charge.py @@ -1,16 +1,21 @@ # Copyright © VASP Software GmbH, # Licensed under the Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0) -from py4vasp._data import base, structure +from py4vasp.calculation import _base, _structure -class BornEffectiveCharge(base.Refinery, structure.Mixin): - """The Born effective charge tensors coupling electric field and atomic displacement. +class BornEffectiveCharge(_base.Refinery, _structure.Mixin): + """The Born effective charge tensors couple electric field and atomic displacement. You can use this class to extract the Born effective charges of a linear - response calculation. + response calculation. The Born effective charges describes the effective charge of + an ion in a crystal lattice when subjected to an external electric field. + These charges account for the displacement of the ion positions in response to the + field, reflecting the distortion of the crystal structure. Born effective charges + help understanding the material's response to external stimuli, such as + piezoelectric and ferroelectric behavior. """ - @base.data_access + @_base.data_access def __str__(self): data = self.to_dict() result = """ @@ -27,7 +32,7 @@ def __str__(self): 3 {vec_to_string(charge_tensor[2])}""" return result - @base.data_access + @_base.data_access def to_dict(self): """Read structure information and Born effective charges into a dictionary. diff --git a/src/py4vasp/_calculation.py b/src/py4vasp/calculation/_class.py similarity index 66% rename from src/py4vasp/_calculation.py rename to src/py4vasp/calculation/_class.py index 6f0baa16..b91765ef 100644 --- a/src/py4vasp/_calculation.py +++ b/src/py4vasp/calculation/_class.py @@ -1,19 +1,28 @@ # Copyright © VASP Software GmbH, # Licensed under the Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0) -import inspect import pathlib -from py4vasp import _data, control, data, exception -from py4vasp._util import convert +from py4vasp import calculation, control, exception class Calculation: - """Manage access to input and output of VASP calculations. + """Manage access to input and output of single VASP calculation. - This is the main user interface if you want to simply investigate the results of VASP - calculations. Create a Calculation object associated with the VASP calculation that you - run. Then you can access the properties of that calculation via the attributes of the - object. + The :mod:`calculation` module always reads the VASP calculation from the current + working directory. This class gives you a more fine grained control so that you + can use a Python script or Jupyter notebook in a different folder or rename the + files that VASP produces. + + To create a new instance, you should use the classmethod :meth:`from_path` or + :meth:`from_file` and *not* the constructor. This will ensure that the path to + your VASP calculation is properly set and all features work as intended. + The two methods allow you to read VASP results from a specific folder other + than the working directory or a nondefault file name. + + With the Calculation instance, you can access the quantities VASP computes via + the attributes of the object. The attributes are the same provided by the + :mod:`calculation` module. You can find links to how to use these quantities + below. Examples -------- @@ -23,14 +32,14 @@ class Calculation: >>> calc.magnetism.read() # to read the magnetic moments >>> calc.structure.print() # to print the structure in a POSCAR format - Notes - ----- - To create new instances, you should use the classmethod :meth:`from_path` or - :meth:`from_file`. This will ensure that the path to your VASP calculation is - properly set and all features work as intended. + .. autosummary:: - Attributes - ---------- + from_file + from_path + path + INCAR + KPOINTS + POSCAR """ def __init__(self, *args, **kwargs): @@ -114,15 +123,15 @@ def POSCAR(self, poscar): def _add_all_refinement_classes(calc, add_single_class): - for _, class_ in inspect.getmembers(data, inspect.isclass): - if issubclass(class_, _data.base.Refinery): - calc = add_single_class(calc, class_) + for name in calculation.__all__: + calc = add_single_class(calc, name) return calc -def _add_attribute_from_path(calc, class_): +def _add_attribute_from_path(calc, name): + class_ = getattr(calculation, name) instance = class_.from_path(calc.path()) - setattr(calc, convert.quantity_name(class_.__name__), instance) + setattr(calc, name, instance) return calc @@ -130,34 +139,18 @@ class _AddAttributeFromFile: def __init__(self, file_name): self._file_name = file_name - def __call__(self, calc, class_): + def __call__(self, calc, name): + class_ = getattr(calculation, name) instance = class_.from_file(self._file_name) - setattr(calc, convert.quantity_name(class_.__name__), instance) + setattr(calc, name, instance) return calc -def _add_to_documentation(calc, class_): - functions = inspect.getmembers(class_, inspect.isfunction) - method_names = [name for name, _ in functions if not name.startswith("_")] - calc.__doc__ += _header_for_class(class_) - for name in method_names: - calc.__doc__ += _link_to_method(class_.__name__, name) +def _add_to_documentation(calc, name): + calc.__doc__ += f" ~py4vasp.calculation.{name}\n " return calc -def _header_for_class(class_): - first_line = class_.__doc__.split("\n")[0] - class_name = class_.__name__ - return f""" - {convert.quantity_name(class_name)} - {first_line} (:class:`py4vasp.data.{class_name}`) - """ - - -def _link_to_method(class_name, method_name): - return f"\n * :meth:`py4vasp.data.{class_name}.{method_name}`" - - Calculation = _add_all_refinement_classes(Calculation, _add_to_documentation) diff --git a/src/py4vasp/_data/density.py b/src/py4vasp/calculation/_density.py similarity index 92% rename from src/py4vasp/_data/density.py rename to src/py4vasp/calculation/_density.py index 069a0ba2..3260d36b 100644 --- a/src/py4vasp/_data/density.py +++ b/src/py4vasp/calculation/_density.py @@ -2,9 +2,9 @@ # Licensed under the Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0) import numpy as np -from py4vasp import _config, data, exception -from py4vasp._data import base, structure +from py4vasp import _config, calculation, exception from py4vasp._util import documentation, import_, index, select +from py4vasp.calculation import _base, _structure pretty = import_.optional("IPython.lib.pretty") @@ -48,18 +48,22 @@ def _join_with_emphasis(data): return ", ".join(emph_data) -class Density(base.Refinery, structure.Mixin): - """The charge and magnetization density. +class Density(_base.Refinery, _structure.Mixin): + """This class accesses various densities (charge, magnetization, ...) of VASP. - You can use this class to extract the density data of the VASP calculation - and to have a quick glance at the resulting density. + The charge density is one key quantity optimized by VASP. With this class you + can extract the final density and visualize it within the structure of the + system. For collinear calculations, one can also consider the magnetization + density. For noncollinear calculations, the magnetization density has three + components. One may also be interested in the kinetic energy density for + metaGGA calculations. """ - @base.data_access + @_base.data_access def __str__(self): _raise_error_if_no_data(self._raw_data.charge) grid = self._raw_data.charge.shape[1:] - topology = data.Topology.from_data(self._raw_data.structure.topology) + topology = calculation.topology.from_data(self._raw_data.structure.topology) if self._selection == "tau": name = "Kinetic energy" elif self.is_nonpolarized(): @@ -78,7 +82,7 @@ def __str__(self): component2=_join_with_emphasis(_COMPONENTS[2]), component3=_join_with_emphasis(_COMPONENTS[3]), ) - @base.data_access + @_base.data_access def selections(self): """Returns possible densities VASP can produce along with all available components. @@ -141,7 +145,7 @@ def selections(self): components = [_COMPONENTS[i][_DEFAULT] for i in range(4)] return {**sources, "component": components} - @base.data_access + @_base.data_access def to_dict(self): """Read the density into a dictionary. @@ -174,7 +178,7 @@ def _read_density(self): elif self.is_noncollinear(): yield "magnetization", density[1:] - @base.data_access + @_base.data_access def to_numpy(self): """Convert the density to a numpy array. @@ -189,7 +193,7 @@ def to_numpy(self): """ return np.moveaxis(self._raw_data.charge, 0, -1).T - @base.data_access + @_base.data_access def plot(self, selection="0", **user_options): """Plot the selected density as a 3d isosurface within the structure. @@ -274,17 +278,17 @@ def _use_symmetric_isosurface(self, label, map_): _raise_is_collinear_error() return component > 0 - @base.data_access + @_base.data_access def is_nonpolarized(self): "Returns whether the density is not spin polarized." return len(self._raw_data.charge) == 1 - @base.data_access + @_base.data_access def is_collinear(self): "Returns whether the density has a collinear magnetization." return len(self._raw_data.charge) == 2 - @base.data_access + @_base.data_access def is_noncollinear(self): "Returns whether the density has a noncollinear magnetization." return len(self._raw_data.charge) == 4 diff --git a/src/py4vasp/_data/dielectric_function.py b/src/py4vasp/calculation/_dielectric_function.py similarity index 80% rename from src/py4vasp/_data/dielectric_function.py rename to src/py4vasp/calculation/_dielectric_function.py index d46f9690..eab984ce 100644 --- a/src/py4vasp/_data/dielectric_function.py +++ b/src/py4vasp/calculation/_dielectric_function.py @@ -1,24 +1,34 @@ # Copyright © VASP Software GmbH, # Licensed under the Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0) -import typing - import numpy as np -from py4vasp._data import base from py4vasp._third_party import graph from py4vasp._util import convert, index, select +from py4vasp.calculation import _base + + +class DielectricFunction(_base.Refinery, graph.Mixin): + """The dielectric function describes the material response to an electric field. + The dielectric function is a fundamental concept that describes how a material + responds to an external electric field. It is a frequency-dependent complex-valued + 3x3 matrix that relates the polarization of a material to the applied electric + field. The dielectric function is essential in understanding optical properties, + such as refractive index and absorption. -class DielectricFunction(base.Refinery, graph.Mixin): - """The dielectric function resulting from electrons and ions. + There are many different ways to compute dielectric functions with VASP. This + class provides a common interface to all of them. You can pass a `selection` + argument to any of the methods of this class to select which dielectric function + you are interested in. Please make sure the INCAR file you use is compatible + with the setup. - You can use this class to extract the dielectric function of a Vasp calculation. - VASP evaluates actually evaluates the (symmetric) dielectric tensor, so all - the returned quantities are 3x3 matrices. For plotting purposes this is reduced - to the 6 independent variables. + The 3x3 matrix is symmetric so for the plotting routines, py4vasp uses only the + six distinct components (xx, yy, zz, xy, xz, yz). The default is the isotropic + dielectric function but you can also select specific components by providing + one of the six components as selection. """ - @base.data_access + @_base.data_access def __str__(self): energies = self._raw_data.energies return f""" @@ -33,7 +43,7 @@ def _components(self): else: return "" - @base.data_access + @_base.data_access def to_dict(self): """Read the data into a dictionary. @@ -59,7 +69,7 @@ def _add_current_current_if_available(self): def _has_current_component(self): return not self._raw_data.current_current.is_none() - @base.data_access + @_base.data_access def to_graph(self, selection=None): """Read the data and generate a figure with the selected directions. @@ -83,7 +93,7 @@ def to_graph(self, selection=None): ylabel="dielectric function ϵ", ) - @base.data_access + @_base.data_access def selections(self): "Returns a dictionary of possible selections for component, direction, and complex value." components = ( diff --git a/src/py4vasp/_data/dielectric_tensor.py b/src/py4vasp/calculation/_dielectric_tensor.py similarity index 78% rename from src/py4vasp/_data/dielectric_tensor.py rename to src/py4vasp/calculation/_dielectric_tensor.py index 05096d4f..a4b734dd 100644 --- a/src/py4vasp/_data/dielectric_tensor.py +++ b/src/py4vasp/calculation/_dielectric_tensor.py @@ -1,14 +1,20 @@ # Copyright © VASP Software GmbH, # Licensed under the Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0) from py4vasp import exception -from py4vasp._data import base from py4vasp._util import convert +from py4vasp.calculation import _base -class DielectricTensor(base.Refinery): - """The static dielectric tensor obtained from linear response.""" +class DielectricTensor(_base.Refinery): + """The dielectric tensor is the static limit of the :attr:`dielectric function`. - @base.data_access + The dielectric tensor represents how a material's response to an external electric + field varies with direction. It is a symmetric 3x3 matrix, encapsulating the + anisotropic nature of a material's dielectric properties. Each element of the + tensor corresponds to the dielectric function along a specific crystallographic + axis.""" + + @_base.data_access def to_dict(self): """Read the dielectric tensor into a dictionary. @@ -25,7 +31,7 @@ def to_dict(self): "method": convert.text_to_string(self._raw_data.method), } - @base.data_access + @_base.data_access def __str__(self): data = self.to_dict() return f""" diff --git a/src/py4vasp/_data/dispersion.py b/src/py4vasp/calculation/_dispersion.py similarity index 94% rename from src/py4vasp/_data/dispersion.py rename to src/py4vasp/calculation/_dispersion.py index 55605c79..e2025f17 100644 --- a/src/py4vasp/_data/dispersion.py +++ b/src/py4vasp/calculation/_dispersion.py @@ -3,23 +3,23 @@ import numpy as np import py4vasp._third_party.graph as _graph -from py4vasp import data -from py4vasp._data import base +from py4vasp import calculation +from py4vasp.calculation import _base -class Dispersion(base.Refinery): +class Dispersion(_base.Refinery): """Generic class for all dispersions (electrons, phonons). Provides some utility functionalities common to all dispersions to avoid duplication of code.""" - @base.data_access + @_base.data_access def __str__(self): return f"""band data: {self._kpoints.number_kpoints()} k-points {self._raw_data.eigenvalues.shape[-1]} bands""" - @base.data_access + @_base.data_access def to_dict(self): """Read the dispersion into a dictionary. @@ -37,9 +37,9 @@ def to_dict(self): @property def _kpoints(self): - return data.Kpoint.from_data(self._raw_data.kpoints) + return calculation.kpoint.from_data(self._raw_data.kpoints) - @base.data_access + @_base.data_access def plot(self, projections=None): """Generate a graph of the dispersion. diff --git a/src/py4vasp/_data/dos.py b/src/py4vasp/calculation/_dos.py similarity index 69% rename from src/py4vasp/_data/dos.py rename to src/py4vasp/calculation/_dos.py index cc7684c5..978eb9ea 100644 --- a/src/py4vasp/_data/dos.py +++ b/src/py4vasp/calculation/_dos.py @@ -1,27 +1,37 @@ # Copyright © VASP Software GmbH, # Licensed under the Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0) -from py4vasp import data -from py4vasp._data import base, projector +from py4vasp import calculation from py4vasp._third_party import graph from py4vasp._util import documentation, import_ +from py4vasp.calculation import _base, _projector pd = import_.optional("pandas") pretty = import_.optional("IPython.lib.pretty") -class Dos(base.Refinery, graph.Mixin): - """The electronic density of states (DOS). - - You can use this class to extract the DOS data of a VASP calculation. - Typically you want to run a non self consistent calculation with a - denser mesh for a smoother DOS, but the class will work independent - of it. If you generated orbital decomposed DOS, you can use this - class to select which subset of these orbitals to read or plot. +class Dos(_base.Refinery, graph.Mixin): + """The density of states (DOS) describes the number of states per energy. + + The DOS quantifies the distribution of electronic states within an energy range + in a material. It provides information about the number of electronic states at + each energy level and offers insights into the material's electronic structure. + On-site projections near the atoms (projected DOS) offer a more detailed view. + This analysis breaks down the DOS contributions by atom, orbital and spin. + Investigating the projected DOS is often a useful step to understand the + electronic properties because it shows how different orbitals and elements + contribute and influence the material's properties. + + VASP writes the DOS after every calculation and the projected DOS if you set + :tag:`LORBIT` in the INCAR file. You can use this class to extract this data. + Typically you want to run a non self consistent calculation with a denser + mesh for a smoother DOS but the class will work independent of it. If you + generated a projected DOS, you can use this class to select which subset of + these orbitals to read or plot. """ _missing_data_message = "No DOS data found, please verify that LORBIT flag is set." - @base.data_access + @_base.data_access def __str__(self): energies = self._raw_data.energies return f""" @@ -30,10 +40,10 @@ def __str__(self): {pretty.pretty(self._projectors())} """.strip() - @base.data_access + @_base.data_access @documentation.format( - selection_doc=projector.selection_doc, - examples=projector.selection_examples("dos", "to_dict"), + selection_doc=_projector.selection_doc, + examples=_projector.selection_examples("dos", "to_dict"), ) def to_dict(self, selection=None): """Read the data into a dictionary. @@ -57,10 +67,10 @@ def to_dict(self, selection=None): "fermi_energy": self._raw_data.fermi_energy, } - @base.data_access + @_base.data_access @documentation.format( - selection_doc=projector.selection_doc, - examples=projector.selection_examples("dos", "to_graph"), + selection_doc=_projector.selection_doc, + examples=_projector.selection_examples("dos", "to_graph"), ) def to_graph(self, selection=None): """Generate a graph of the selected data reading it from the VASP output. @@ -86,10 +96,10 @@ def to_graph(self, selection=None): ylabel="DOS (1/eV)", ) - @base.data_access + @_base.data_access @documentation.format( - selection_doc=projector.selection_doc, - examples=projector.selection_examples("dos", "to_frame"), + selection_doc=_projector.selection_doc, + examples=_projector.selection_examples("dos", "to_frame"), ) def to_frame(self, selection=None): """Read the data into a pandas DataFrame. @@ -116,7 +126,7 @@ def _spin_polarized(self): return self._raw_data.dos.shape[0] == 2 def _projectors(self): - return data.Projector.from_data(self._raw_data.projectors) + return calculation.projector.from_data(self._raw_data.projectors) def _read_data(self, selection): return { diff --git a/src/py4vasp/_data/elastic_modulus.py b/src/py4vasp/calculation/_elastic_modulus.py similarity index 65% rename from src/py4vasp/_data/elastic_modulus.py rename to src/py4vasp/calculation/_elastic_modulus.py index 3bdfbf5a..5d1feab5 100644 --- a/src/py4vasp/_data/elastic_modulus.py +++ b/src/py4vasp/calculation/_elastic_modulus.py @@ -2,17 +2,23 @@ # Licensed under the Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0) import numpy as np -from py4vasp._data import base +from py4vasp.calculation import _base -class ElasticModulus(base.Refinery): - """The elastic modulus (second derivatives w.r.t. strain) +class ElasticModulus(_base.Refinery): + """The elastic modulus is the second derivative of the energy with respect to strain. - You can use this class to extract the elastic modulus of a linear response - calculation. + The elastic modulus, also known as the modulus of elasticity, is a measure of a + material's stiffness and its ability to deform elastically in response to an + applied force. It quantifies the ratio of stress (force per unit area) to strain + (deformation) in a material within its elastic limit. You can use this class to + extract the elastic modulus of a linear response calculation. There are two + variants of the elastic modulus: (i) in the clamped-ion one, the cell is deformed + but the ions are kept in their positions; (ii) in the relaxed-ion one the + atoms are allowed to relax when the cell is deformed. """ - @base.data_access + @_base.data_access def to_dict(self): """Read the clamped-ion and relaxed-ion elastic modulus into a dictionary. @@ -26,7 +32,7 @@ def to_dict(self): "relaxed_ion": self._raw_data.relaxed_ion[:], } - @base.data_access + @_base.data_access def __str__(self): return f"""Elastic modulus (kBar) Direction XX YY ZZ XY YZ ZX diff --git a/src/py4vasp/_data/energy.py b/src/py4vasp/calculation/_energy.py similarity index 85% rename from src/py4vasp/_data/energy.py rename to src/py4vasp/calculation/_energy.py index 30aa691b..88ca3c47 100644 --- a/src/py4vasp/_data/energy.py +++ b/src/py4vasp/calculation/_energy.py @@ -2,9 +2,9 @@ # Licensed under the Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0) import numpy as np -from py4vasp._data import base, slice_ from py4vasp._third_party import graph from py4vasp._util import convert, documentation, index, select +from py4vasp.calculation import _base, _slice def _selection_string(default): @@ -31,17 +31,25 @@ def _selection_string(default): } -@documentation.format(examples=slice_.examples("energy")) -class Energy(slice_.Mixin, base.Refinery, graph.Mixin): +@documentation.format(examples=_slice.examples("energy")) +class Energy(_slice.Mixin, _base.Refinery, graph.Mixin): """The energy data for one or several steps of a relaxation or MD simulation. You can use this class to inspect how the ionic relaxation converges or - during an MD simulation whether the total energy is conserved. + during an MD simulation whether the total energy is conserved. The total + energy of the system is one of the most important results to analyze materials. + Total energy differences of different atom arrangements reveal which structure + is more stable. Even when the number of atoms are different between two + systems, you may be able to compare the total energies by adding a corresponding + amount of single atom energies. In this case, you need to double check the + convergence because some error cancellation does not happen if the number of + atoms is changes. Finally, monitoring the total energy can reveal insights + about the stability of the thermostat. {examples} """ - @base.data_access + @_base.data_access def __str__(self): text = f"Energies at {self._step_string()}:" values = self._raw_data.values[self._last_step_in_slice] @@ -61,10 +69,10 @@ def _step_string(self): else: return f"step {self._steps + 1}" - @base.data_access + @_base.data_access @documentation.format( selection=_selection_string("all energies"), - examples=slice_.examples("energy", "to_dict"), + examples=_slice.examples("energy", "to_dict"), ) def to_dict(self, selection=None): """Read the energy data and store it in a dictionary. @@ -93,10 +101,10 @@ def _default_dict(self): for label, value in zip(self._raw_data.labels, raw_values) } - @base.data_access + @_base.data_access @documentation.format( selection=_selection_string("the total energy"), - examples=slice_.examples("energy", "to_graph"), + examples=_slice.examples("energy", "to_graph"), ) def to_graph(self, selection="TOTEN"): """Read the energy data and generate a figure of the selected components. @@ -121,10 +129,10 @@ def to_graph(self, selection="TOTEN"): y2label=yaxes.y2label, ) - @base.data_access + @_base.data_access @documentation.format( selection=_selection_string("the total energy"), - examples=slice_.examples("energy", "to_numpy"), + examples=_slice.examples("energy", "to_numpy"), ) def to_numpy(self, selection="TOTEN"): """Read the energy of the selected steps. @@ -145,7 +153,7 @@ def to_numpy(self, selection="TOTEN"): tree = select.Tree.from_selection(selection) return np.squeeze([values for _, values in self._read_data(tree, self._steps)]) - @base.data_access + @_base.data_access def selections(self): """Returns all possible selections you can use for the other routines. diff --git a/src/py4vasp/_data/fatband.py b/src/py4vasp/calculation/_fatband.py similarity index 66% rename from src/py4vasp/_data/fatband.py rename to src/py4vasp/calculation/_fatband.py index fd0f2220..0417f730 100644 --- a/src/py4vasp/_data/fatband.py +++ b/src/py4vasp/calculation/_fatband.py @@ -1,14 +1,24 @@ # Copyright © VASP Software GmbH, # Licensed under the Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0) -from py4vasp import data -from py4vasp._data import base +from py4vasp import calculation from py4vasp._util import convert +from py4vasp.calculation import _base -class Fatband(base.Refinery): - "Access data for producing BSE fatband plots." +class Fatband(_base.Refinery): + """BSE fatbands illustrate the excitonic properties of materials. - @base.data_access + The Bethe-Salpeter Equation (BSE) accounts for electron-hole interactions + involved in excitonic processes. For systems, where excitonic excitations + matter the BSE method is an important tool. In the context of electronic + band structure calculations, fatbands represent a visual representation + of the excitonic contributions. Here, the width of the band is adjusted + such that the band is wider the larger the contribution is. This approach + helps understanding of the electronic transitions and excitonic behavior + in materials. + """ + + @_base.data_access def __str__(self): shape = self._raw_data.bse_index.shape return f"""BSE fatband data: @@ -16,7 +26,7 @@ def __str__(self): {shape[3]} valence bands {shape[2]} conduction bands""" - @base.data_access + @_base.data_access def to_dict(self): """Read the data into a dictionary. @@ -45,4 +55,4 @@ def to_dict(self): @property def _dispersion(self): - return data.Dispersion.from_data(self._raw_data.dispersion) + return calculation.dispersion.from_data(self._raw_data.dispersion) diff --git a/src/py4vasp/_data/force.py b/src/py4vasp/calculation/_force.py similarity index 74% rename from src/py4vasp/_data/force.py rename to src/py4vasp/calculation/_force.py index 626d9786..15bda313 100644 --- a/src/py4vasp/_data/force.py +++ b/src/py4vasp/calculation/_force.py @@ -3,17 +3,22 @@ import numpy as np from py4vasp import exception -from py4vasp._data import base, slice_, structure from py4vasp._util import documentation, reader +from py4vasp.calculation import _base, _slice, _structure -@documentation.format(examples=slice_.examples("force")) -class Force(slice_.Mixin, base.Refinery, structure.Mixin): - """The forces acting on the atoms for selected steps of the simulation. +@documentation.format(examples=_slice.examples("force")) +class Force(_slice.Mixin, _base.Refinery, _structure.Mixin): + """The forces determine the path of the atoms in a trajectory. - You can use this class to analyze the forces acting on the atoms. In - particular, you can check whether the forces are small at the end of the - calculation. + You can use this class to analyze the forces acting on the atoms. The forces + are the first derivative of the DFT total energy. The forces being small is + an important criterion for the convergence of a relaxation calculation. The + size of the forces is also related to the maximal time step in MD simulations. + When you choose a too large time step, the forces become large and the atoms + may move too much in a single step leading to an unstable trajectory. You can + use this class to visualize the forces in a trajectory or read the values to + analyze them numerically. {examples} """ @@ -21,7 +26,7 @@ class Force(slice_.Mixin, base.Refinery, structure.Mixin): force_rescale = 1.5 "Scaling constant to convert forces to Å." - @base.data_access + @_base.data_access def __str__(self): "Convert the forces to a format similar to the OUTCAR file." result = """ @@ -36,8 +41,8 @@ def __str__(self): result += f"\n{position_to_string(position)} {force_to_string(force)}" return result - @base.data_access - @documentation.format(examples=slice_.examples("force", "to_dict")) + @_base.data_access + @documentation.format(examples=_slice.examples("force", "to_dict")) def to_dict(self): """Read the forces and associated structural information for one or more selected steps of the trajectory. @@ -55,8 +60,8 @@ def to_dict(self): "forces": self._force[self._steps], } - @base.data_access - @documentation.format(examples=slice_.examples("force", "to_graph")) + @_base.data_access + @documentation.format(examples=_slice.examples("force", "to_graph")) def plot(self): """Visualize the forces showing arrows at the atoms. diff --git a/src/py4vasp/_data/force_constant.py b/src/py4vasp/calculation/_force_constant.py similarity index 64% rename from src/py4vasp/_data/force_constant.py rename to src/py4vasp/calculation/_force_constant.py index 950ba91f..25096579 100644 --- a/src/py4vasp/_data/force_constant.py +++ b/src/py4vasp/calculation/_force_constant.py @@ -2,17 +2,23 @@ # Licensed under the Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0) import itertools -from py4vasp._data import base, structure +from py4vasp.calculation import _base, _structure -class ForceConstant(base.Refinery, structure.Mixin): - """The force constants (second derivatives of atomic displacement). +class ForceConstant(_base.Refinery, _structure.Mixin): + """Force constants are the 2nd derivatives of the energy with respect to displacement. - You can use this class to extract the force constants of a linear - response calculation. + Force constants quantify the strength of interactions between atoms in a crystal + lattice. They describe how the potential energy of the system changes with atomic + displacements. Specifically they are the second derivative of the energy with + respect to a displacement from their equilibrium positions. Force constants are a + key component in determining the vibrational modes of a crystal lattice (phonon + dispersion). Phonon calculations involve the computation of these force constants. + Keep in mind that they are the second derivative at the equilibrium position so + a careful relaxation is required to eliminate the first derivative (i.e. forces). """ - @base.data_access + @_base.data_access def __str__(self): result = """ Force constants (eV/Ų): @@ -29,7 +35,7 @@ def __str__(self): result += f"\n{i + 1:6d} {j + 1:6d} {string_representation}" return result - @base.data_access + @_base.data_access def to_dict(self): """Read structure information and force constants into a dictionary. diff --git a/src/py4vasp/_data/internal_strain.py b/src/py4vasp/calculation/_internal_strain.py similarity index 69% rename from src/py4vasp/_data/internal_strain.py rename to src/py4vasp/calculation/_internal_strain.py index daff71af..d40cc727 100644 --- a/src/py4vasp/_data/internal_strain.py +++ b/src/py4vasp/calculation/_internal_strain.py @@ -1,16 +1,20 @@ # Copyright © VASP Software GmbH, # Licensed under the Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0) -from py4vasp._data import base, structure +from py4vasp.calculation import _base, _structure -class InternalStrain(base.Refinery, structure.Mixin): - """The internal strain +class InternalStrain(_base.Refinery, _structure.Mixin): + """The internal strain is the derivative of energy with respect to displacement and strain. - You can use this class to extract the internal strain of a linear - response calculation. + The internal strain tensor characterizes the deformation within a material at + a microscopic level. It is a symmetric 3 x 3 matrix per displacement and + describes the coupling between the displacement of atoms and the strain on + the system. Specifically, it reveals how atoms would move under strain or which + stress occurs when the atoms are displaced. VASP computes the internal strain + with linear response and this class provides access to the resulting data. """ - @base.data_access + @_base.data_access def __str__(self): result = """ Internal strain tensor (eV/Å): @@ -24,7 +28,7 @@ def __str__(self): ion_string = " " return result.strip() - @base.data_access + @_base.data_access def to_dict(self): """Read the internal strain to a dictionary. diff --git a/src/py4vasp/_data/kpoint.py b/src/py4vasp/calculation/_kpoint.py similarity index 85% rename from src/py4vasp/_data/kpoint.py rename to src/py4vasp/calculation/_kpoint.py index cfaf4f17..ea301810 100644 --- a/src/py4vasp/_data/kpoint.py +++ b/src/py4vasp/calculation/_kpoint.py @@ -6,8 +6,8 @@ import numpy as np from py4vasp import exception -from py4vasp._data import base from py4vasp._util import convert, documentation +from py4vasp.calculation import _base _kpoints_selection = """\ selection : str, optional @@ -16,16 +16,30 @@ """ -class Kpoint(base.Refinery): - """The **k** points used in the Vasp calculation. +class Kpoint(_base.Refinery): + """The **k**-point mesh used in the VASP calculation. - This class provides utility functionality to extract information about the - **k** points used by Vasp. As such it is mostly used as a helper class for + In VASP calculations, **k** points play an important role in discretizing the + Brillouin zone of a crystal. For self-consistent DFT calculations, typically a + regular grid of **k** points is employed to sample the Brillouin zone. A + sufficiently dense **k**-points mesh is critical for the precision of your DFT + calculation, so make sure to test the results for different meshes. Denser + **k** point meshes provide more accurate results but also demand greater + computational resources. + + Another common use case is irregular meshes in non-self-consistent calculations. + In particular in band structure analysis, one employs a mesh along specific lines + in the Brillouin zone. The line mode involves connecting high-symmetry points and + calculating the electronic band structure along these paths. + + This class provides utility functionality to extract information about either of + the aforementioned use cases. As such it is mostly used as a helper class for other postprocessing classes to extract the required information, e.g., to - generate a band structure. + generate a band structure. It may also be used to programmatically analyze the + selected **k** point mesh or take subsets along high symmetry lines. """ - @base.data_access + @_base.data_access def __str__(self): text = f"""k-points {len(self._raw_data.coordinates)} @@ -34,7 +48,7 @@ def __str__(self): text += "\n" + f"{kpoint[0]} {kpoint[1]} {kpoint[2]} {weight}" return text - @base.data_access + @_base.data_access @documentation.format(selection=_kpoints_selection) def to_dict(self): """Read the **k** points data into a dictionary. @@ -61,7 +75,7 @@ def to_dict(self): "labels": self.labels(), } - @base.data_access + @_base.data_access @documentation.format(selection=_kpoints_selection) def line_length(self): """Get the number of points per line in the Brillouin zone. @@ -79,7 +93,7 @@ def line_length(self): return self._raw_data.number return self.number_kpoints() - @base.data_access + @_base.data_access @documentation.format(selection=_kpoints_selection) def number_lines(self): """Get the number of lines in the Brillouin zone. @@ -95,7 +109,7 @@ def number_lines(self): """ return self.number_kpoints() // self.line_length() - @base.data_access + @_base.data_access @documentation.format(selection=_kpoints_selection) def number_kpoints(self): """Get the number of points in the Brillouin zone. @@ -111,7 +125,7 @@ def number_kpoints(self): """ return len(self._raw_data.coordinates) - @base.data_access + @_base.data_access @documentation.format(selection=_kpoints_selection) def distances(self): """Convert the coordinates of the **k** points into a one dimensional array @@ -140,7 +154,7 @@ def distances(self): ) return functools.reduce(concatenate_distances, kpoint_norms) - @base.data_access + @_base.data_access @documentation.format(selection=_kpoints_selection) def mode(self): """Get the **k**-point generation mode specified in the Vasp input file @@ -173,7 +187,7 @@ def mode(self): f"Could not understand the mode '{mode}' when refining the raw kpoints data." ) - @base.data_access + @_base.data_access @documentation.format(selection=_kpoints_selection) def labels(self): """Get any labels given in the input file for specific **k** points. @@ -195,7 +209,7 @@ def labels(self): else: return None - @base.data_access + @_base.data_access @documentation.format(selection=_kpoints_selection) def path_indices(self, start, finish): """Find linear dependent k points between start and finish diff --git a/src/py4vasp/_data/magnetism.py b/src/py4vasp/calculation/_magnetism.py similarity index 82% rename from src/py4vasp/_data/magnetism.py rename to src/py4vasp/calculation/_magnetism.py index c8687926..2e7ecb1e 100644 --- a/src/py4vasp/_data/magnetism.py +++ b/src/py4vasp/calculation/_magnetism.py @@ -3,8 +3,8 @@ import numpy as np from py4vasp import exception -from py4vasp._data import base, slice_, structure from py4vasp._util import documentation +from py4vasp.calculation import _base, _slice, _structure _index_note = """\ Notes @@ -21,12 +21,26 @@ """ -@documentation.format(examples=slice_.examples("magnetism")) -class Magnetism(slice_.Mixin, base.Refinery, structure.Mixin): - """The magnetic moments and localized charges for selected ionic steps. +@documentation.format(examples=_slice.examples("magnetism")) +class Magnetism(_slice.Mixin, _base.Refinery, _structure.Mixin): + """The local moments describe the charge and magnetization near an atom. - This class gives access to the magnetic moments and charges projected on the - different orbitals on every atom. + The projection on local moments is particularly relevant in the context of + magnetic materials. It analyzes the electronic states in the vicinity of an + atom by projecting the electronic orbitals onto the localized projectors of + the PAWs. The local moments help understanding the magnetic ordering, the spin + polarization, and the influence of neighboring atoms on the magnetic behavior. + + This class allows to access the computed moments from a VASP calculation. + Remember that VASP calculates the projections only if you need to set + :tag:`LORBIT` in the INCAR file. If the system is computed without spin + polarization, the resulting moments correspond only to the local charges + resolved by angular momentum. For collinear calculation, additionally the + magnetic moment are computed. In noncollinear calculations, the magnetization + becomes a vector. When comparing the results extracted from VASP to experimental + observation, please be aware that the finite size of the radius in the projection + may influence the observed moments. Hence, there is no one-to-one correspondence + to the experimental moments. {examples} """ @@ -36,7 +50,7 @@ class Magnetism(slice_.Mixin, base.Refinery, structure.Mixin): length_moments = 1.5 "Length in Å how a magnetic moment is displayed relative to the largest moment." - @base.data_access + @_base.data_access def __str__(self): magmom = "MAGMOM = " moments_last_step = self.total_moments() @@ -50,9 +64,9 @@ def __str__(self): generator = (moments_to_string(vec) for vec in moments_last_step) return magmom + separator.join(generator) - @base.data_access + @_base.data_access @documentation.format( - index_note=_index_note, examples=slice_.examples("magnetism", "to_dict") + index_note=_index_note, examples=_slice.examples("magnetism", "to_dict") ) def to_dict(self): """Read the charges and magnetization data into a dictionary. @@ -73,9 +87,9 @@ def to_dict(self): **self._add_spin_and_orbital_moments(), } - @base.data_access + @_base.data_access @documentation.format( - selection=_moment_selection, examples=slice_.examples("magnetism", "to_graph") + selection=_moment_selection, examples=_slice.examples("magnetism", "to_graph") ) def plot(self, selection="total", supercell=None): """Visualize the magnetic moments as arrows inside the structure. @@ -104,8 +118,8 @@ def plot(self, selection="total", supercell=None): viewer.show_arrows_at_atoms(moments) return viewer - @base.data_access - @documentation.format(examples=slice_.examples("magnetism", "charges")) + @_base.data_access + @documentation.format(examples=_slice.examples("magnetism", "charges")) def charges(self): """Read the charges of the selected steps. @@ -119,11 +133,11 @@ def charges(self): self._raise_error_if_steps_out_of_bounds() return self._raw_data.spin_moments[self._steps, 0] - @base.data_access + @_base.data_access @documentation.format( selection=_moment_selection, index_note=_index_note, - examples=slice_.examples("magnetism", "moments"), + examples=_slice.examples("magnetism", "moments"), ) def moments(self, selection="total"): """Read the magnetic moments of the selected steps. @@ -151,8 +165,8 @@ def moments(self, selection="total"): else: return self._noncollinear_moments(selection) - @base.data_access - @documentation.format(examples=slice_.examples("magnetism", "total_charges")) + @_base.data_access + @documentation.format(examples=_slice.examples("magnetism", "total_charges")) def total_charges(self): """Read the total charges of the selected steps. @@ -166,11 +180,11 @@ def total_charges(self): """ return _sum_over_orbitals(self.charges()) - @base.data_access + @_base.data_access @documentation.format( selection=_moment_selection, index_note=_index_note, - examples=slice_.examples("magnetism", "total_moments"), + examples=_slice.examples("magnetism", "total_moments"), ) def total_moments(self, selection="total"): """Read the total magnetic moments of the selected steps. diff --git a/src/py4vasp/_data/pair_correlation.py b/src/py4vasp/calculation/_pair_correlation.py similarity index 72% rename from src/py4vasp/_data/pair_correlation.py rename to src/py4vasp/calculation/_pair_correlation.py index ab6e141e..92fbf296 100644 --- a/src/py4vasp/_data/pair_correlation.py +++ b/src/py4vasp/calculation/_pair_correlation.py @@ -1,8 +1,8 @@ # Copyright © VASP Software GmbH, # Licensed under the Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0) -from py4vasp._data import base, slice_ from py4vasp._third_party import graph from py4vasp._util import convert, documentation, index, select +from py4vasp.calculation import _base, _slice def _selection_string(default): @@ -19,22 +19,31 @@ def _selection_string(default): """ -@documentation.format(examples=slice_.examples("pair_correlation", step="block")) -class PairCorrelation(slice_.Mixin, base.Refinery, graph.Mixin): - """The pair-correlation function for one or several blocks of an MD simulation. +@documentation.format(examples=_slice.examples("pair_correlation", step="block")) +class PairCorrelation(_slice.Mixin, _base.Refinery, graph.Mixin): + """The pair-correlation function measures the distribution of atoms. - Use this class to inspect how the correlation of the position of different - ions types in an MD simulation. The pair-correlation function gives insight - into the structural properties and may help to identify certain orders in - the system. + A pair-correlation function is a statistical measure to describe the spatial + distribution of atoms within a system. Specifically, the pair correlation + function quantifies the probability density of finding two particles at specific + separation distances. This function is helpful in the study of liquids and solids + because it acts as a fingerprint of the system that can be compared to + X-ray or neutron scattering experiments. Another use case is the detection + of specific phases. + + Use this class to inspect the pair-correlation function computed by VASP for + all pairs of ionic types. You can control how often VASP samples the pair + correlation function with the :tag:`NBLOCK` tag. If you want to split your + trajectory into multiple subsets include the tag :tag:`KBLOCK` in your INCAR + file. {examples} """ - @base.data_access + @_base.data_access @documentation.format( selection=_selection_string("all possibilities are read"), - examples=slice_.examples("pair_correlation", "to_dict", "block"), + examples=_slice.examples("pair_correlation", "to_dict", "block"), ) def to_dict(self, selection=None): """Read the pair-correlation function and store it in a dictionary. @@ -59,10 +68,10 @@ def to_dict(self, selection=None): **self._read_data(selection), } - @base.data_access + @_base.data_access @documentation.format( selection=_selection_string("the total pair correlation is used"), - examples=slice_.examples("pair_correlation", "to_graph", "block"), + examples=_slice.examples("pair_correlation", "to_graph", "block"), ) def to_graph(self, selection="total"): """Plot selected pair-correlation functions. @@ -83,7 +92,7 @@ def to_graph(self, selection="total"): series = self._make_series(self.to_dict(selection)) return graph.Graph(series, xlabel="Distance (Å)", ylabel="Pair correlation") - @base.data_access + @_base.data_access def labels(self): "Return all possible labels for the selection string." return tuple(convert.text_to_string(label) for label in self._raw_data.labels) diff --git a/src/py4vasp/_data/phonon.py b/src/py4vasp/calculation/_phonon.py similarity index 92% rename from src/py4vasp/_data/phonon.py rename to src/py4vasp/calculation/_phonon.py index 1f38b111..c65a13bc 100644 --- a/src/py4vasp/_data/phonon.py +++ b/src/py4vasp/calculation/_phonon.py @@ -1,8 +1,8 @@ # Copyright © VASP Software GmbH, # Licensed under the Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0) -from py4vasp import data -from py4vasp._data import base +from py4vasp import calculation from py4vasp._util import select +from py4vasp.calculation import _base selection_doc = """\ selection : str @@ -27,7 +27,7 @@ class Mixin: "Provide functionality common to Phonon classes." - @base.data_access + @_base.data_access def selections(self): "Return a dictionary specifying which atoms and directions can be used as selection." atoms = self._init_atom_dict().keys() @@ -37,7 +37,7 @@ def selections(self): } def _topology(self): - return data.Topology.from_data(self._raw_data.topology) + return calculation.topology.from_data(self._raw_data.topology) def _init_atom_dict(self): return { diff --git a/src/py4vasp/_data/phonon_band.py b/src/py4vasp/calculation/_phonon_band.py similarity index 62% rename from src/py4vasp/_data/phonon_band.py rename to src/py4vasp/calculation/_phonon_band.py index d543f73f..0f02ce8d 100644 --- a/src/py4vasp/_data/phonon_band.py +++ b/src/py4vasp/calculation/_phonon_band.py @@ -2,27 +2,38 @@ # Licensed under the Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0) import numpy as np -from py4vasp import data -from py4vasp._data import base, phonon +from py4vasp import calculation from py4vasp._third_party import graph from py4vasp._util import convert, documentation, index, select +from py4vasp.calculation import _base, _phonon -class PhononBand(phonon.Mixin, base.Refinery, graph.Mixin): - """The phonon band structure. +class PhononBand(_phonon.Mixin, _base.Refinery, graph.Mixin): + """The phonon band structure contains the **q**-resolved phonon eigenvalues. - Use this to examine the phonon band structure along a high-symmetry path in the - Brillouin zone. The `to_dict` function allows to extract the raw data to process - it further.""" + The phonon band structure is a graphical representation of the phonons. It + illustrates the relationship between the frequency of modes and their corresponding + wave vectors in the Brillouin zone. Each line or branch in the band structure + represents a specific phonon, and the slope of these branches provides information + about their velocity. - @base.data_access + The phonon band structure includes the dispersion relations of phonons, which reveal + how vibrational frequencies vary with direction in the crystal lattice. The presence + of band gaps or band crossings indicates the material's ability to conduct or + insulate heat. Additionally, the branches near the high-symmetry points in the + Brillouin zone offer insights into the material's anharmonicity and thermal + conductivity. Furthermore, phonons with imaginary frequencies indicate the presence + of a structural instability. + """ + + @_base.data_access def __str__(self): return f"""phonon band data: {self._raw_data.dispersion.eigenvalues.shape[0]} q-points {self._raw_data.dispersion.eigenvalues.shape[1]} modes {self._topology()}""" - @base.data_access + @_base.data_access def to_dict(self): """Read the phonon band structure into a dictionary. @@ -40,8 +51,8 @@ def to_dict(self): "modes": self._modes(), } - @base.data_access - @documentation.format(selection=phonon.selection_doc) + @_base.data_access + @documentation.format(selection=_phonon.selection_doc) def to_graph(self, selection=None, width=1.0): """Generate a graph of the phonon bands. @@ -64,7 +75,7 @@ def to_graph(self, selection=None, width=1.0): return graph def _dispersion(self): - return data.Dispersion.from_data(self._raw_data.dispersion) + return calculation.dispersion.from_data(self._raw_data.dispersion) def _modes(self): return convert.to_complex(self._raw_data.eigenvectors[:]) diff --git a/src/py4vasp/_data/phonon_dos.py b/src/py4vasp/calculation/_phonon_dos.py similarity index 62% rename from src/py4vasp/_data/phonon_dos.py rename to src/py4vasp/calculation/_phonon_dos.py index b78a0d26..27f08a0a 100644 --- a/src/py4vasp/_data/phonon_dos.py +++ b/src/py4vasp/calculation/_phonon_dos.py @@ -1,21 +1,29 @@ # Copyright © VASP Software GmbH, # Licensed under the Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0) -import numpy as np - -from py4vasp import data -from py4vasp._data import base, phonon from py4vasp._third_party import graph from py4vasp._util import documentation, index, select +from py4vasp.calculation import _base, _phonon + +class PhononDos(_phonon.Mixin, _base.Refinery, graph.Mixin): + """The phonon density of states (DOS) describes the number of modes per energy. -class PhononDos(phonon.Mixin, base.Refinery, graph.Mixin): - """The phonon density of states (DOS). + The phonon density of states (DOS) is a representation of the distribution of + phonons in a material across different frequencies. It provides a histogram of the + number of phonon states per frequency interval. Peaks and features in the DOS reveal + the density of vibrational modes at specific frequencies. One can related these + properties to study e.g. the heat capacity or the thermal conductivity. - You can use this class to extract the phonon DOS data of a VASP - calculation. The DOS can also be resolved by direction and atom. + Projecting the phonon density of states (DOS) onto specific atoms highlights the + contribution of each atomic species to the vibrational spectrum. This analysis helps + to understand the role of individual elements' impact on the material's thermal and + mechanical properties. The projected phonon DOS can guide towards engineering these + properties by substitution of specific atoms. Additionally, the atom-specific + projection allows for the identification of localized modes or vibrations associated + with specific atomic species. """ - @base.data_access + @_base.data_access def __str__(self): energies = self._raw_data.energies topology = self._topology() @@ -24,8 +32,8 @@ def __str__(self): {3 * topology.number_atoms()} modes {topology}""" - @base.data_access - @documentation.format(selection=phonon.selection_doc) + @_base.data_access + @documentation.format(selection=_phonon.selection_doc) def to_dict(self, selection=None): """Read the phonon DOS into a dictionary. @@ -46,8 +54,8 @@ def to_dict(self, selection=None): **self._read_data(selection), } - @base.data_access - @documentation.format(selection=phonon.selection_doc) + @_base.data_access + @documentation.format(selection=_phonon.selection_doc) def to_graph(self, selection=None): """Generate a graph of the selected DOS. diff --git a/src/py4vasp/_data/piezoelectric_tensor.py b/src/py4vasp/calculation/_piezoelectric_tensor.py similarity index 59% rename from src/py4vasp/_data/piezoelectric_tensor.py rename to src/py4vasp/calculation/_piezoelectric_tensor.py index 49ab4eda..b108c8e4 100644 --- a/src/py4vasp/_data/piezoelectric_tensor.py +++ b/src/py4vasp/calculation/_piezoelectric_tensor.py @@ -2,17 +2,29 @@ # Licensed under the Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0) import numpy as np -from py4vasp._data import base +from py4vasp.calculation import _base -class PiezoelectricTensor(base.Refinery): - """The piezoelectric tensor (second derivatives w.r.t. strain and field) +class PiezoelectricTensor(_base.Refinery): + """The piezoelectric tensor is the derivative of the energy with respect to strain and field. - You can use this class to extract the piezoelectric tensor of a linear - response calculation. + The piezoelectric tensor represents the coupling between mechanical stress and + electrical polarization in a material. VASP computes the piezoelectric tensor with + a linear response calculation. The piezoelectric tensor is a 3x3 matrix that relates + the three components of stress to the three components of polarization. + Specifically, it describes how the application of mechanical stress induces an + electric polarization and, conversely, how an applied electric field results in + a deformation. + + The piezoelectric tensor helps to characterize the efficiency and anisotropy of the + piezoelectric response. A large piezoelectric tensor is useful e.g. for sensors + and actuators. Moreover, the tensor's symmetry properties are coupled to the crystal + structure and symmetry. Therefore a mismatch of the symmetry properties between + calculations and experiment can reveal underlying flaws in the characterization of + the crystal structure. """ - @base.data_access + @_base.data_access def __str__(self): data = self.to_dict() return f"""Piezoelectric tensor (C/m²) @@ -21,7 +33,7 @@ def __str__(self): {_tensor_to_string(data["clamped_ion"], "clamped-ion")} {_tensor_to_string(data["relaxed_ion"], "relaxed-ion")}""" - @base.data_access + @_base.data_access def to_dict(self): """Read the ionic and electronic contribution to the piezoelectric tensor into a dictionary. diff --git a/src/py4vasp/_data/polarization.py b/src/py4vasp/calculation/_polarization.py similarity index 65% rename from src/py4vasp/_data/polarization.py rename to src/py4vasp/calculation/_polarization.py index 3933d777..57dfabfc 100644 --- a/src/py4vasp/_data/polarization.py +++ b/src/py4vasp/calculation/_polarization.py @@ -1,10 +1,16 @@ # Copyright © VASP Software GmbH, # Licensed under the Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0) -from py4vasp._data import base +from py4vasp.calculation import _base -class Polarization(base.Refinery): - """The static polarization of the structure obtained from linear response. +class Polarization(_base.Refinery): + """The static polarization describes the electric dipole moment per unit volume. + + Static polarization arises in a material in response to a constant external electric + field. In VASP, we compute the linear response of the system when applying a + :tag:`EFIELD`. Static polarization is a key characteristic of ferroelectric + materials that exhibit a spontaneous electric polarization that persists even in + the absence of an external electric field. Note that the polarization is only well defined relative to a reference system. The absolute value can change by a polarization quantum if some @@ -12,7 +18,7 @@ class Polarization(base.Refinery): side. Therefore you always need to compare changes of polarization. """ - @base.data_access + @_base.data_access def __str__(self): vec_to_string = lambda vec: " ".join(f"{x:11.5f}" for x in vec) return f""" @@ -22,7 +28,7 @@ def __str__(self): electronic dipole moment: {vec_to_string(self._raw_data.electron[:])} """.strip() - @base.data_access + @_base.data_access def to_dict(self): """Read electronic and ionic polarization into a dictionary diff --git a/src/py4vasp/_data/potential.py b/src/py4vasp/calculation/_potential.py similarity index 78% rename from src/py4vasp/_data/potential.py rename to src/py4vasp/calculation/_potential.py index ccb8c678..dbc92b06 100644 --- a/src/py4vasp/_data/potential.py +++ b/src/py4vasp/calculation/_potential.py @@ -4,20 +4,32 @@ import numpy as np -from py4vasp import _config, data, exception -from py4vasp._data import base, structure +from py4vasp import _config, calculation, exception from py4vasp._util import select +from py4vasp.calculation import _base, _structure VALID_KINDS = ("total", "ionic", "xc", "hartree") -class Potential(base.Refinery, structure.Mixin): - """The local potential of the VASP calculation. +class Potential(_base.Refinery, _structure.Mixin): + """The local potential describes the interactions between electrons and ions. - The local potential is defined in real space on the FFT grid. Depending on the setup - of the VASP run, different individual contributions can be accessed.""" + In DFT calculations, the local potential consists of various contributions, each + representing different aspects of the electron-electron and electron-ion + interactions. The ionic potential arises from the attraction between electrons and + the atomic nuclei. The Hartree potential accounts for the repulsion between + electrons resulting from the electron density itself. Additionally, the + exchange-correlation (xc) potential approximates the effects of electron exchange + and correlation. The accuracy of this approximation directly influences the + accuracy of the calculated properties. - @base.data_access + In VASP, the local potential is defined in real space on the FFT grid. You control + which potentials are written with the :tag:`WRT_POTENTIAL` tag. This class provides + the methods to read and visualize the potential. If you are interested in the + average potential, you may also look at the :data:`~py4vasp.calculation.workfunction`. + """ + + @_base.data_access def __str__(self): potential = self._raw_data.total_potential if _is_collinear(potential): @@ -26,7 +38,7 @@ def __str__(self): description = "noncollinear potential:" else: description = "nonpolarized potential:" - topology = data.Topology.from_data(self._raw_data.structure.topology) + topology = calculation.topology.from_data(self._raw_data.structure.topology) structure = f"structure: {topology}" grid = f"grid: {potential.shape[3]}, {potential.shape[2]}, {potential.shape[1]}" available = "available: " + ", ".join( @@ -34,7 +46,7 @@ def __str__(self): ) return "\n ".join([description, structure, grid, available]) - @base.data_access + @_base.data_access def to_dict(self): """Store all available contributions to the potential in a dictionary. @@ -65,7 +77,7 @@ def _generate_items(self, kind): elif _is_noncollinear(potential): yield f"{kind}_magnetization", potential[1:] - @base.data_access + @_base.data_access def plot(self, selection="total", *, isolevel=0): """Plot an isosurface of a selected potential. diff --git a/src/py4vasp/_data/projector.py b/src/py4vasp/calculation/_projector.py similarity index 94% rename from src/py4vasp/_data/projector.py rename to src/py4vasp/calculation/_projector.py index 4f520503..48f782f3 100644 --- a/src/py4vasp/_data/projector.py +++ b/src/py4vasp/calculation/_projector.py @@ -4,12 +4,10 @@ import warnings from typing import NamedTuple, Union -import numpy as np - -from py4vasp import data, exception -from py4vasp._data import base -from py4vasp._data.selection import Selection -from py4vasp._util import convert, documentation, index, reader, select +from py4vasp import calculation, exception +from py4vasp._util import convert, documentation, index, select +from py4vasp.calculation import _base +from py4vasp.calculation._selection import Selection selection_doc = """\ selection : str @@ -68,18 +66,20 @@ def selection_examples(instance_name, function_name): _select_all = select.all -class Projector(base.Refinery): +class Projector(_base.Refinery): """The projectors used for atom and orbital resolved quantities. - This is a common class used by all quantities that contains some projected - quantity, e.g., the electronic band structure and the DOS. It provides - utility functionality to access specific indices of the projected arrays - based on a simple mini language specifying the atom or orbital names. + This is a utility class that facilitates projecting quantities such as the + electronic band structure and the DOS on atoms and orbitals. As a user, you can + investigate the available projections with the :meth:`to_dict` or :meth:`selections` + methods. The former is useful for scripts, when you need to know which array + index corresponds to which orbital or atom. The latter describes the available + selections that you can use in the methods that project on orbitals or atoms. """ _missing_data_message = "No projectors found, please verify the LORBIT tag is set." - @base.data_access + @_base.data_access def __str__(self): if self._raw_data.orbital_types.is_none(): return "no projectors" @@ -87,7 +87,7 @@ def __str__(self): atoms: {", ".join(self._topology().ion_types())} orbitals: {", ".join(self._orbital_types())}""" - @base.data_access + @_base.data_access def to_dict(self, selection=None, projections=None): """Return a map from labels to indices in the arrays produced by VASP. @@ -115,7 +115,7 @@ def to_dict(self, selection=None, projections=None): warnings.warn(message, DeprecationWarning, stacklevel=2) return self.project(selection, projections) - @base.data_access + @_base.data_access @documentation.format(selection_doc=selection_doc) def project(self, selection, projections): """Select a certain subset of the given projections and return them with a @@ -146,7 +146,7 @@ def project(self, selection, projections): for selection in self._parse_selection(selection) } - @base.data_access + @_base.data_access def selections(self): """Return a dictionary describing what options are available to specify the atom, orbital, and spin.""" @@ -191,7 +191,7 @@ def _raise_error_if_orbitals_missing(self): raise exception.IncorrectUsage(message) def _topology(self): - return data.Topology.from_data(self._raw_data.topology) + return calculation.topology.from_data(self._raw_data.topology) def _init_dicts(self): if self._raw_data.orbital_types.is_none(): @@ -261,7 +261,7 @@ class Index(NamedTuple): spin: Union[str, Selection] "Label of the spin component or a Selection object to read the corresponding data." - @base.data_access + @_base.data_access @documentation.format(separator=select.range_separator) def select( self, @@ -307,7 +307,7 @@ def select( spin=dicts["spin"][spin], ) - @base.data_access + @_base.data_access @documentation.format(selection_doc=selection_doc) def parse_selection(self, selection=_select_all): """Generate all possible indices where the projected information is stored. diff --git a/src/py4vasp/_data/selection.py b/src/py4vasp/calculation/_selection.py similarity index 100% rename from src/py4vasp/_data/selection.py rename to src/py4vasp/calculation/_selection.py diff --git a/src/py4vasp/_data/slice_.py b/src/py4vasp/calculation/_slice.py similarity index 100% rename from src/py4vasp/_data/slice_.py rename to src/py4vasp/calculation/_slice.py diff --git a/src/py4vasp/_data/stress.py b/src/py4vasp/calculation/_stress.py similarity index 69% rename from src/py4vasp/_data/stress.py rename to src/py4vasp/calculation/_stress.py index 99de0a43..d2071009 100644 --- a/src/py4vasp/_data/stress.py +++ b/src/py4vasp/calculation/_stress.py @@ -2,23 +2,28 @@ # Licensed under the Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0) import numpy as np -from py4vasp import data -from py4vasp._data import base, slice_ from py4vasp._util import documentation, reader +from py4vasp.calculation import _base, _slice, _structure -@documentation.format(examples=slice_.examples("stress")) -class Stress(slice_.Mixin, base.Refinery): - """The stress acting on the unit cell for selected steps of the simulation. +@documentation.format(examples=_slice.examples("stress")) +class Stress(_slice.Mixin, _base.Refinery, _structure.Mixin): + """The stress describes the force acting on the shape of the unit cell. - You can use this class to analyze the stress on the shape of the cell. In - particular, you can check whether the stress is small at the end of the - calculation. + The stress refers to the force applied to the cell per unit area. Specifically, + VASP computes the stress for a given unit cell and relaxing to vanishing stress + determines the predicted ground-state cell. The stress is 3 x 3 matrix; the trace + indicates changes to the volume and the rest of the matrix changes the shape of + the cell. You can impose an external stress with the tag :tag:`PSTRESS`. + + When you relax the system or in a MD simulation, VASP computes and stores the + stress in every iteration. You can use this class to read the stress for specific + steps along the trajectory. {examples} """ - @base.data_access + @_base.data_access def __str__(self): "Convert the stress to a format similar to the OUTCAR file." step = self._last_step_in_slice @@ -33,8 +38,8 @@ def __str__(self): in kB {stress_to_string(stress)} """.strip() - @base.data_access - @documentation.format(examples=slice_.examples("stress", "to_dict")) + @_base.data_access + @documentation.format(examples=_slice.examples("stress", "to_dict")) def to_dict(self): """Read the stress and associated structural information for one or more selected steps of the trajectory. @@ -52,10 +57,6 @@ def to_dict(self): "structure": self._structure[self._steps].read(), } - @property - def _structure(self): - return data.Structure.from_data(self._raw_data.structure) - @property def _stress(self): return _StressReader(self._raw_data.stress) diff --git a/src/py4vasp/_data/structure.py b/src/py4vasp/calculation/_structure.py similarity index 83% rename from src/py4vasp/_data/structure.py rename to src/py4vasp/calculation/_structure.py index 04d5fad6..8c692023 100644 --- a/src/py4vasp/_data/structure.py +++ b/src/py4vasp/calculation/_structure.py @@ -1,15 +1,14 @@ # Copyright © VASP Software GmbH, # Licensed under the Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0) import io -from collections import Counter from dataclasses import dataclass import numpy as np -from py4vasp import data, exception, raw -from py4vasp._data import base, slice_, topology -from py4vasp._data.viewer3d import Viewer3d +from py4vasp import calculation, exception, raw +from py4vasp._third_party.viewer.viewer3d import Viewer3d from py4vasp._util import documentation, import_, reader +from py4vasp.calculation import _base, _slice, _topology ase = import_.optional("ase") ase_io = import_.optional("ase.io") @@ -48,14 +47,25 @@ def _element_to_string(self, element): return f"{element:21.16f}" -@documentation.format(examples=slice_.examples("structure")) -class Structure(slice_.Mixin, base.Refinery): - """The structure of the crystal for selected steps of the simulation. +@documentation.format(examples=_slice.examples("structure")) +class Structure(_slice.Mixin, _base.Refinery): + """The structure contains the unit cell and the position of all ions within. - You can use this class to process structural information from the Vasp - calculation. Typically you want to do this to inspect the converged structure - after an ionic relaxation or to visualize the changes of the structure along - the simulation. + The crystal structure is the specific arrangement of ions in a three-dimensional + repeating pattern. This spatial arrangement is characterized by the unit cell and + the relative position of the ions. The unit cell is repeated periodically in three + dimensions to form the crystal. The combination of unit cell and ion positions + determines the symmetry of the crystal. This symmetry helps understanding the + material properties because some symmetries do not allow for the presence of some + properties, e.g., you cannot observe a ferroelectric :data:`~py4vasp.calculation.polarization` + in a system with inversion symmetry. Therefore relaxing the crystal structure with + VASP is an important first step in analyzing materials properties. + + When you run a relaxation or MD simulation, this class allows to access all + individual steps of the trajectory. Typically, you would study the converged + structure after an ionic relaxation or to visualize the changes of the structure + along the simulation. Moreover, you could take snapshots along the trajectory + and further process them by computing more properties. {examples} """ @@ -75,18 +85,18 @@ def from_POSCAR(cls, poscar, *, elements=None): def from_ase(cls, structure): """Generate a structure from the ase Atoms class.""" structure = raw.Structure( - topology=topology.raw_topology_from_ase(structure), + topology=_topology.raw_topology_from_ase(structure), cell=_cell_from_ase(structure), positions=structure.get_scaled_positions()[np.newaxis], ) return cls.from_data(structure) - @base.data_access + @_base.data_access def __str__(self): "Generate a string representing the final structure usable as a POSCAR file." return self._create_repr() - @base.data_access + @_base.data_access def _repr_html_(self): format_ = _Format( begin_table="\n
", @@ -109,8 +119,8 @@ def _create_repr(self, format_=_Format()): ) return "\n".join(lines) - @base.data_access - @documentation.format(examples=slice_.examples("structure", "to_dict")) + @_base.data_access + @documentation.format(examples=_slice.examples("structure", "to_dict")) def to_dict(self): """Read the structural information into a dictionary. @@ -130,8 +140,8 @@ def to_dict(self): "names": self._topology().names(), } - @base.data_access - @documentation.format(examples=slice_.examples("structure", "to_graph")) + @_base.data_access + @documentation.format(examples=_slice.examples("structure", "to_graph")) def plot(self, supercell=None): """Generate a 3d representation of the structure(s). @@ -153,8 +163,8 @@ def plot(self, supercell=None): else: return self._viewer_from_structure(supercell) - @base.data_access - @documentation.format(examples=slice_.examples("structure", "to_ase")) + @_base.data_access + @documentation.format(examples=_slice.examples("structure", "to_ase")) def to_ase(self, supercell=None): """Convert the structure to an ase Atoms object. @@ -197,8 +207,8 @@ def to_ase(self, supercell=None): order = sorted(range(num_atoms_super), key=lambda n: n % num_atoms_prim) return structure[order] - @base.data_access - @documentation.format(examples=slice_.examples("structure", "to_mdtraj")) + @_base.data_access + @documentation.format(examples=_slice.examples("structure", "to_mdtraj")) def to_mdtraj(self): """Convert the trajectory to mdtraj.Trajectory @@ -220,8 +230,8 @@ def to_mdtraj(self): trajectory.unitcell_vectors = data["lattice_vectors"] * Structure.A_to_nm return trajectory - @base.data_access - @documentation.format(examples=slice_.examples("structure", "to_POSCAR")) + @_base.data_access + @documentation.format(examples=_slice.examples("structure", "to_POSCAR")) def to_POSCAR(self): """Convert the structure(s) to a POSCAR format @@ -238,8 +248,8 @@ def to_POSCAR(self): message = "Converting multiple structures to a POSCAR is currently not implemented." raise exception.NotImplemented(message) - @base.data_access - @documentation.format(examples=slice_.examples("structure", "cartesian_positions")) + @_base.data_access + @documentation.format(examples=_slice.examples("structure", "cartesian_positions")) def cartesian_positions(self): """Convert the positions from direct coordinates to cartesian ones. @@ -252,8 +262,8 @@ def cartesian_positions(self): """ return self._positions() @ self._lattice_vectors() - @base.data_access - @documentation.format(examples=slice_.examples("structure", "volume")) + @_base.data_access + @documentation.format(examples=_slice.examples("structure", "volume")) def volume(self): """Return the volume of the unit cell for the selected steps. @@ -266,7 +276,7 @@ def volume(self): """ return np.abs(np.linalg.det(self._lattice_vectors())) - @base.data_access + @_base.data_access def number_atoms(self): """Return the total number of atoms in the structure.""" if self._is_trajectory: @@ -274,7 +284,7 @@ def number_atoms(self): else: return self._raw_data.positions.shape[0] - @base.data_access + @_base.data_access def number_steps(self): """Return the number of structures in the trajectory.""" if self._is_trajectory: @@ -284,7 +294,7 @@ def number_steps(self): return 1 def _topology(self): - return data.Topology.from_data(self._raw_data.topology) + return calculation.topology.from_data(self._raw_data.topology) def _lattice_vectors(self): lattice_vectors = _LatticeVectors(self._raw_data.cell.lattice_vectors) @@ -326,7 +336,7 @@ def _step_string(self): else: return f" (step {self._steps + 1})" - @base.data_access + @_base.data_access def __getitem__(self, steps): if not self._is_trajectory: message = "The structure is not a Trajectory so accessing individual elements is not allowed." diff --git a/src/py4vasp/_data/system.py b/src/py4vasp/calculation/_system.py similarity index 63% rename from src/py4vasp/_data/system.py rename to src/py4vasp/calculation/_system.py index 5cc38426..367ee8e6 100644 --- a/src/py4vasp/_data/system.py +++ b/src/py4vasp/calculation/_system.py @@ -1,17 +1,17 @@ # Copyright © VASP Software GmbH, # Licensed under the Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0) -from py4vasp._data import base from py4vasp._util import convert +from py4vasp.calculation import _base -class System(base.Refinery): - "Extract the system tag from the INCAR file." +class System(_base.Refinery): + "The :tag:`SYSTEM` tag in the INCAR file is a title you choose for a VASP calculation." - @base.data_access + @_base.data_access def __str__(self): return convert.text_to_string(self._raw_data.system) - @base.data_access + @_base.data_access def to_dict(self): "Returns a dictionary containing the system tag." return {"system": str(self)} diff --git a/src/py4vasp/_data/topology.py b/src/py4vasp/calculation/_topology.py similarity index 87% rename from src/py4vasp/_data/topology.py rename to src/py4vasp/calculation/_topology.py index 41687cd0..35612096 100644 --- a/src/py4vasp/_data/topology.py +++ b/src/py4vasp/calculation/_topology.py @@ -5,9 +5,9 @@ import numpy as np from py4vasp import raw -from py4vasp._data import base -from py4vasp._data.selection import Selection from py4vasp._util import check, convert, import_, select +from py4vasp.calculation import _base +from py4vasp.calculation._selection import Selection mdtraj = import_.optional("mdtraj") pd = import_.optional("pandas") @@ -15,12 +15,15 @@ _subscript = "_" -class Topology(base.Refinery): - """This class accesses the topology of the crystal. +class Topology(_base.Refinery): + """The topology of the crystal describes the ions of a crystal and their connectivity. - At the current stage this only provides access to the name of the atoms in - the unit cell, but one could extend it to identify logical units like the - octahedra in perovskites. + At the current stage, this class only exposes the name of the atoms in the unit + cell. In the future, we could add functionality for the user to group multiple + atoms. If you are interested in this feature and have a specific use case in mind, + please create an issue on Github_. + + .. _Github: https://github.com/vasp-dev/py4vasp """ @classmethod @@ -28,17 +31,17 @@ def from_ase(cls, structure): """Generate a Topology from the given ase Atoms object.""" return cls.from_data(raw_topology_from_ase(structure)) - @base.data_access + @_base.data_access def __str__(self): number_suffix = lambda number: str(number) if number > 1 else "" return self._create_repr(number_suffix) - @base.data_access + @_base.data_access def _repr_html_(self): number_suffix = lambda number: f"{number}" if number > 1 else "" return self._create_repr(number_suffix) - @base.data_access + @_base.data_access def to_dict(self): """Read the topology and convert it to a dictionary. @@ -54,7 +57,7 @@ def to_dict(self): """ return {**self._default_selection(), **self._specific_selection()} - @base.data_access + @_base.data_access def to_frame(self): """Convert the topology to a DataFrame @@ -65,7 +68,7 @@ def to_frame(self): """ return pd.DataFrame({"name": self.names(), "element": self.elements()}) - @base.data_access + @_base.data_access def to_mdtraj(self): """Convert the topology to a mdtraj.Topology.""" df = self.to_frame() @@ -75,7 +78,7 @@ def to_mdtraj(self): df["chainID"] = 0 return mdtraj.Topology.from_dataframe(df) - @base.data_access + @_base.data_access def to_POSCAR(self, format_newline=""): """Generate the topology lines for the POSCAR file. @@ -97,24 +100,24 @@ def to_POSCAR(self, format_newline=""): number_ion_types = " ".join(str(x) for x in self._raw_data.number_ion_types) return ion_types + format_newline + "\n" + number_ion_types - @base.data_access + @_base.data_access def names(self): """Extract the labels of all atoms.""" atom_dict = self.to_dict() return [val.label for val in atom_dict.values() if _subscript in val.label] - @base.data_access + @_base.data_access def elements(self): """Extract the element of all atoms.""" repeated_types = (itertools.repeat(*x) for x in self._type_numbers()) return list(itertools.chain.from_iterable(repeated_types)) - @base.data_access + @_base.data_access def ion_types(self): "Return the type of all ions in the system as string." return list(dict.fromkeys(self._ion_types)) - @base.data_access + @_base.data_access def number_atoms(self): "Return the number of atoms in the system." return np.sum(self._raw_data.number_ion_types) diff --git a/src/py4vasp/_data/velocity.py b/src/py4vasp/calculation/_velocity.py similarity index 69% rename from src/py4vasp/_data/velocity.py rename to src/py4vasp/calculation/_velocity.py index 4cc40227..1248da69 100644 --- a/src/py4vasp/_data/velocity.py +++ b/src/py4vasp/calculation/_velocity.py @@ -4,23 +4,30 @@ from py4vasp import exception from py4vasp._config import VASP_GRAY -from py4vasp._data import base, slice_, structure from py4vasp._util import convert, documentation, reader +from py4vasp.calculation import _base, _slice, _structure -@documentation.format(examples=slice_.examples("velocity")) -class Velocity(slice_.Mixin, base.Refinery, structure.Mixin): - """The ion velocities for all steps of the calculation. +@documentation.format(examples=_slice.examples("velocity")) +class Velocity(_slice.Mixin, _base.Refinery, _structure.Mixin): + """The velocities describe the ionic motion during an MD simulation. - The velocities are only stored if you set VELOCITY = T in the INCAR file. You can - read velocities of different steps see the examples below. + The velocities of the ions are a metric for the temperature of the system. Most + of the time, it is not necessary to consider them explicitly. VASP will set the + velocities automatically according to the temperature settings (:tag:`TEBEG` and + :tag:`TEEND`) unless you set them explicitly in the POSCAR file. Since the + velocities are not something you typically need, VASP will only store them during + the simulation if you set :tag:`VELOCITY` = T in the INCAR file. In that case you + can read the velocities of each step along the trajectory. If you are only + interested in the final velocities, please consider the :data:'~py4vasp.data.CONTCAR` + class. {examples} """ velocity_rescale = 200 - @base.data_access + @_base.data_access def __str__(self): step = self._last_step_in_slice velocities = self._vectors_to_string(self._velocity[step]) @@ -35,8 +42,8 @@ def _vector_to_string(self, vector): def _element_to_string(self, element): return f"{element:21.16f}" - @base.data_access - @documentation.format(examples=slice_.examples("velocity", "to_dict")) + @_base.data_access + @documentation.format(examples=_slice.examples("velocity", "to_dict")) def to_dict(self): """Return the structure and ion velocities in a dictionary @@ -53,8 +60,8 @@ def to_dict(self): "velocities": self._velocity[self._steps], } - @base.data_access - @documentation.format(examples=slice_.examples("velocity", "plot")) + @_base.data_access + @documentation.format(examples=_slice.examples("velocity", "plot")) def plot(self): """Plot the velocities as vectors in the structure. diff --git a/src/py4vasp/_data/workfunction.py b/src/py4vasp/calculation/_workfunction.py similarity index 71% rename from src/py4vasp/_data/workfunction.py rename to src/py4vasp/calculation/_workfunction.py index 2a0ff335..d6387919 100644 --- a/src/py4vasp/_data/workfunction.py +++ b/src/py4vasp/calculation/_workfunction.py @@ -1,18 +1,24 @@ # Copyright © VASP Software GmbH, # Licensed under the Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0) -from py4vasp import data -from py4vasp._data import base +from py4vasp import calculation from py4vasp._third_party import graph +from py4vasp.calculation import _base -class Workfunction(base.Refinery, graph.Mixin): - """The workfunction of a material describes the energy required to remove an electron - to the vacuum. +class Workfunction(_base.Refinery, graph.Mixin): + """The workfunction describes the energy required to remove an electron to the vacuum. - In VASP you can compute the workfunction by setting the IDIPOL flag in the INCAR file. - This class provides then the functionality to analyze the resulting potential.""" + The workfunction of a material is the minimum energy required to remove an + electron from its most loosely bound state and move it to an energy level just + outside the material's surface. In other words, it represents the energy barrier + that electrons must overcome to escape the material. The workfunction helps + understanding electronic emission phenomena in surface science and materials + engineering. In VASP, you can compute the workfunction by setting the :tag:`IDIPOL` + flag in the INCAR file. This class provides then the functionality to analyze the + resulting potential. + """ - @base.data_access + @_base.data_access def __str__(self): data = self.to_dict() return f"""workfunction along {data["direction"]}: @@ -22,7 +28,7 @@ def __str__(self): # valence band maximum: {data["valence_band_maximum"]:.3f} # conduction band minimum: {data["conduction_band_minimum"]:.3f} - @base.data_access + @_base.data_access def to_dict(self): """Reports useful information about the workfunction as a dictionary. @@ -37,7 +43,7 @@ def to_dict(self): Contains vacuum potential, average potential and relevant reference energies within the surface. """ - bandgap = data.Bandgap.from_data(self._raw_data.reference_potential) + bandgap = calculation.bandgap.from_data(self._raw_data.reference_potential) # vbm and cbm will be uncommented out when the relevant parts of the # code are added to VASP 6.5 return { @@ -50,7 +56,7 @@ def to_dict(self): "fermi_energy": self._raw_data.fermi_energy, } - @base.data_access + @_base.data_access def to_graph(self): """Plot the average potential along the lattice vector selected by IDIPOL. diff --git a/src/py4vasp/data.py b/src/py4vasp/data.py deleted file mode 100644 index 08bd5106..00000000 --- a/src/py4vasp/data.py +++ /dev/null @@ -1,51 +0,0 @@ -# Copyright © VASP Software GmbH, -# Licensed under the Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0) -""" Refine the raw data produced by VASP for plotting or analysis. - -Usually one is not directly interested in the raw data that is produced but -wants to produce either a figure for a publication or some post-processing of -the data. This module contains multiple classes that enable these kinds of -workflows by extracting the relevant data from the HDF5 file and transforming -them into an accessible format. The classes also provide plotting functionality -to get a quick insight about the data, which can then be refined either within -python or a different tool to obtain publication-quality figures. - -Generally, all classes provide a `read` function that extracts the data from the -HDF5 file and puts it into a Python dictionary. Where it makes sense in addition -a `plot` function is available that converts the data into a figure for Jupyter -notebooks. In addition, data conversion routines `to_X` may be available -transforming the data into another format or file, which may be useful to -generate plots with tools other than Python. For the specifics, please refer to -the documentation of the individual classes. -""" - -from py4vasp._data.band import Band -from py4vasp._data.bandgap import Bandgap -from py4vasp._data.born_effective_charge import BornEffectiveCharge -from py4vasp._data.contcar import CONTCAR -from py4vasp._data.density import Density -from py4vasp._data.dielectric_function import DielectricFunction -from py4vasp._data.dielectric_tensor import DielectricTensor -from py4vasp._data.dispersion import Dispersion -from py4vasp._data.dos import Dos -from py4vasp._data.elastic_modulus import ElasticModulus -from py4vasp._data.energy import Energy -from py4vasp._data.fatband import Fatband -from py4vasp._data.force import Force -from py4vasp._data.force_constant import ForceConstant -from py4vasp._data.internal_strain import InternalStrain -from py4vasp._data.kpoint import Kpoint -from py4vasp._data.magnetism import Magnetism -from py4vasp._data.pair_correlation import PairCorrelation -from py4vasp._data.phonon_band import PhononBand -from py4vasp._data.phonon_dos import PhononDos -from py4vasp._data.piezoelectric_tensor import PiezoelectricTensor -from py4vasp._data.polarization import Polarization -from py4vasp._data.potential import Potential -from py4vasp._data.projector import Projector -from py4vasp._data.stress import Stress -from py4vasp._data.structure import Structure -from py4vasp._data.system import System -from py4vasp._data.topology import Topology -from py4vasp._data.velocity import Velocity -from py4vasp._data.workfunction import Workfunction diff --git a/src/py4vasp/exception.py b/src/py4vasp/exception.py index 437d5ae6..d0800864 100644 --- a/src/py4vasp/exception.py +++ b/src/py4vasp/exception.py @@ -36,6 +36,11 @@ class OutdatedVaspVersion(Py4VaspError): used version of Vasp.""" +class MissingAttribute(Py4VaspError, AttributeError): + """Exception raised when py4vasp attribute of Calculation, Batch, ... is used + that does not exist""" + + class ModuleNotInstalled(Py4VaspError): """Exception raised when a functionality is used that relies on an optional dependency of py4vasp but that dependency is not installed.""" diff --git a/tests/analysis/test_mlff.py b/tests/analysis/test_mlff.py index ebe55101..6439de0f 100644 --- a/tests/analysis/test_mlff.py +++ b/tests/analysis/test_mlff.py @@ -1,20 +1,15 @@ # Copyright © VASP Software GmbH, # Licensed under the Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0) from collections import defaultdict -from dataclasses import dataclass from pathlib import Path from typing import Dict from unittest.mock import patch import numpy as np -import numpy.typing as npt import pytest -from py4vasp import exception +from py4vasp import calculation, exception from py4vasp._analysis.mlff import MLFFErrorAnalysis -from py4vasp.data import Energy, Force, Stress - -from ..conftest import raw_data class BaseCalculations: @@ -66,15 +61,15 @@ def mock_calculations(raw_data): data = defaultdict(lambda: defaultdict(list)) for datatype in ["dft_data", "mlff_data"]: raw_energy = raw_data.energy("relax", randomize=True) - energy = Energy.from_data(raw_energy) + energy = calculation.energy.from_data(raw_energy) energy_data = energy.read() data["_energies"][datatype].append(energy_data) raw_force = raw_data.force("Sr2TiO4", randomize=True) - force = Force.from_data(raw_force) + force = calculation.force.from_data(raw_force) force_data = force.read() data["_forces"][datatype].append(force_data) raw_stress = raw_data.stress("Sr2TiO4", randomize=True) - stress = Stress.from_data(raw_stress) + stress = calculation.stress.from_data(raw_stress) stress_data = stress.read() data["_stresses"][datatype].append(stress_data) data["_paths"][datatype].append(Path(__file__) / "calc") @@ -89,15 +84,15 @@ def mock_multiple_calculations(raw_data): for datatype in ["dft_data", "mlff_data"]: for i in range(4): raw_energy = raw_data.energy("relax", randomize=True) - energy = Energy.from_data(raw_energy) + energy = calculation.energy.from_data(raw_energy) energy_data = energy.read() data["_energies"][datatype].append(energy_data) raw_force = raw_data.force("Sr2TiO4", randomize=True) - force = Force.from_data(raw_force) + force = calculation.force.from_data(raw_force) force_data = force.read() data["_forces"][datatype].append(force_data) raw_stress = raw_data.stress("Sr2TiO4", randomize=True) - stress = Stress.from_data(raw_stress) + stress = calculation.stress.from_data(raw_stress) stress_data = stress.read() data["_stresses"][datatype].append(stress_data) data["_paths"][datatype].append(Path(__file__) / "calc") @@ -111,7 +106,7 @@ def mock_calculations_incorrect(raw_data): data = defaultdict(lambda: defaultdict(list)) for datatype in ["dft_data", "mlff_data"]: raw_energy = raw_data.energy("relax", randomize=True) - energy = Energy.from_data(raw_energy) + energy = calculation.energy.from_data(raw_energy) energy_data = energy.read() data["_energies"][datatype].append(energy_data) if datatype == "mlff_data": @@ -119,11 +114,11 @@ def mock_calculations_incorrect(raw_data): else: species = "Fe3O4" raw_force = raw_data.force(species, randomize=True) - force = Force.from_data(raw_force) + force = calculation.force.from_data(raw_force) force_data = force.read() data["_forces"][datatype].append(force_data) raw_stress = raw_data.stress("Sr2TiO4", randomize=True) - stress = Stress.from_data(raw_stress) + stress = calculation.stress.from_data(raw_stress) stress_data = stress.read() data["_stresses"][datatype].append(stress_data) data["_paths"][datatype].append(Path(__file__) / "calc") @@ -132,7 +127,7 @@ def mock_calculations_incorrect(raw_data): return _mock_calculations -@patch("py4vasp._data.base.Refinery.from_path", autospec=True) +@patch("py4vasp.calculation._base.Refinery.from_path", autospec=True) @patch("py4vasp.raw.access", autospec=True) def test_read_inputs_from_path(mock_access, mock_from_path): absolute_path_dft = Path(__file__) / "dft" @@ -156,7 +151,7 @@ def test_read_inputs_from_path(mock_access, mock_from_path): assert isinstance(error_analysis.dft.stresses, np.ndarray) -@patch("py4vasp._data.base.Refinery.from_path", autospec=True) +@patch("py4vasp.calculation._base.Refinery.from_path", autospec=True) @patch("py4vasp.raw.access", autospec=True) def test_read_inputs_from_files(mock_analysis, mock_from_path): absolute_files_dft = Path(__file__) / "dft*.h5" diff --git a/src/py4vasp/_data/__init__.py b/tests/calculation/__init__.py similarity index 100% rename from src/py4vasp/_data/__init__.py rename to tests/calculation/__init__.py diff --git a/tests/data/conftest.py b/tests/calculation/conftest.py similarity index 100% rename from tests/data/conftest.py rename to tests/calculation/conftest.py diff --git a/tests/data/test_band.py b/tests/calculation/test_band.py similarity index 92% rename from tests/data/test_band.py rename to tests/calculation/test_band.py index 03510e17..490656bb 100644 --- a/tests/data/test_band.py +++ b/tests/calculation/test_band.py @@ -6,20 +6,19 @@ import numpy as np import pytest -from py4vasp import exception -from py4vasp.data import Band, Kpoint, Projector +from py4vasp import calculation, exception @pytest.fixture def single_band(raw_data): raw_band = raw_data.band("single") - band = Band.from_data(raw_band) + band = calculation.band.from_data(raw_band) band.ref = types.SimpleNamespace() band.ref.fermi_energy = 0.0 band.ref.bands = raw_band.dispersion.eigenvalues[0] band.ref.occupations = raw_band.occupations[0] raw_kpoints = raw_band.dispersion.kpoints - band.ref.kpoints = Kpoint.from_data(raw_kpoints) + band.ref.kpoints = calculation.kpoint.from_data(raw_kpoints) formatter = {"float": lambda x: f"{x:.2f}"} kpoint_to_string = lambda vec: np.array2string(vec, formatter=formatter) + " 1" band.ref.index = [kpoint_to_string(kpoint) for kpoint in raw_kpoints.coordinates] @@ -29,7 +28,7 @@ def single_band(raw_data): @pytest.fixture def multiple_bands(raw_data): raw_band = raw_data.band("multiple") - band = Band.from_data(raw_band) + band = calculation.band.from_data(raw_band) band.ref = types.SimpleNamespace() band.ref.fermi_energy = raw_band.fermi_energy band.ref.bands = raw_band.dispersion.eigenvalues[0] - raw_band.fermi_energy @@ -40,7 +39,7 @@ def multiple_bands(raw_data): @pytest.fixture def with_projectors(raw_data): raw_band = raw_data.band("multiple with_projectors") - band = Band.from_data(raw_band) + band = calculation.band.from_data(raw_band) band.ref = types.SimpleNamespace() band.ref.bands = raw_band.dispersion.eigenvalues[0] - raw_band.fermi_energy band.ref.Sr = np.sum(raw_band.projections[0, 0:2, :, :, :], axis=(0, 1)) @@ -51,25 +50,25 @@ def with_projectors(raw_data): @pytest.fixture def line_no_labels(raw_data): raw_band = raw_data.band("line no_labels") - band = Band.from_data(raw_band) + band = calculation.band.from_data(raw_band) band.ref = types.SimpleNamespace() - band.ref.kpoints = Kpoint.from_data(raw_band.dispersion.kpoints) + band.ref.kpoints = calculation.kpoint.from_data(raw_band.dispersion.kpoints) return band @pytest.fixture def line_with_labels(raw_data): raw_band = raw_data.band("line with_labels") - band = Band.from_data(raw_band) + band = calculation.band.from_data(raw_band) band.ref = types.SimpleNamespace() - band.ref.kpoints = Kpoint.from_data(raw_band.dispersion.kpoints) + band.ref.kpoints = calculation.kpoint.from_data(raw_band.dispersion.kpoints) return band @pytest.fixture def spin_polarized(raw_data): raw_band = raw_data.band("spin_polarized") - band = Band.from_data(raw_band) + band = calculation.band.from_data(raw_band) band.ref = types.SimpleNamespace() assert raw_band.fermi_energy == 0 band.ref.bands_up = raw_band.dispersion.eigenvalues[0] @@ -82,7 +81,7 @@ def spin_polarized(raw_data): @pytest.fixture def spin_projectors(raw_data): raw_band = raw_data.band("spin_polarized with_projectors") - band = Band.from_data(raw_band) + band = calculation.band.from_data(raw_band) band.ref = types.SimpleNamespace() band.ref.bands_up = raw_band.dispersion.eigenvalues[0] band.ref.bands_down = raw_band.dispersion.eigenvalues[1] @@ -92,7 +91,8 @@ def spin_projectors(raw_data): band.ref.Fe_d_down = np.sum(raw_band.projections[1, 0:3, 2, :, :], axis=0) band.ref.O_up = np.sum(raw_band.projections[0, 3:7, :, :, :], axis=(0, 1)) band.ref.O_down = np.sum(raw_band.projections[1, 3:7, :, :, :], axis=(0, 1)) - band.ref.projectors_string = str(Projector.from_data(raw_band.projectors)) + projector = calculation.projector.from_data(raw_band.projectors) + band.ref.projectors_string = str(projector) return band @@ -153,7 +153,8 @@ def test_more_projections_style(raw_data, Assert): """Vasp 6.1 may store more orbital types then projections available. This test checks that this does not lead to any issues when an available element is used.""" - band = Band.from_data(raw_data.band("spin_polarized excess_orbitals")).read("Fe g") + raw_band = raw_data.band("spin_polarized excess_orbitals") + band = calculation.band.from_data(raw_band).read("Fe g") zero = np.zeros_like(band["projections"]["Fe_up"]) Assert.allclose(band["projections"]["g_up"], zero) Assert.allclose(band["projections"]["g_down"], zero) @@ -292,7 +293,7 @@ def test_plot_incorrect_width(with_projectors): with_projectors.plot("Sr", width="not a number") -@patch("py4vasp._data.band.Band.to_graph") +@patch("py4vasp.calculation._band.Band.to_graph") def test_to_plotly(mock_plot, single_band): fig = single_band.to_plotly("selection", width=0.2) mock_plot.assert_called_once_with("selection", width=0.2) @@ -308,7 +309,7 @@ def test_to_image(single_band): def check_to_image(single_band, filename_argument, expected_filename): - with patch("py4vasp._data.band.Band.to_plotly") as plot: + with patch("py4vasp.calculation._band.Band.to_plotly") as plot: single_band.to_image("args", filename=filename_argument, key="word") plot.assert_called_once_with("args", key="word") fig = plot.return_value @@ -361,4 +362,4 @@ def test_spin_projectors_print(spin_projectors, format_): def test_factory_methods(raw_data, check_factory_methods): data = raw_data.band("multiple") - check_factory_methods(Band, data) + check_factory_methods(calculation.band, data) diff --git a/tests/data/test_bandgap.py b/tests/calculation/test_bandgap.py similarity index 97% rename from tests/data/test_bandgap.py rename to tests/calculation/test_bandgap.py index 8ef1dab4..91b27968 100644 --- a/tests/data/test_bandgap.py +++ b/tests/calculation/test_bandgap.py @@ -6,7 +6,7 @@ import numpy as np import pytest -from py4vasp import data, exception +from py4vasp import calculation, exception VBM = 0 CBM = 1 @@ -31,7 +31,7 @@ def spin_polarized(raw_data): def setup_bandgap(raw_gap): - bandgap = data.Bandgap.from_data(raw_gap) + bandgap = calculation.bandgap.from_data(raw_gap) bandgap.ref = types.SimpleNamespace() bandgap.ref.fundamental = raw_gap.values[..., CBM] - raw_gap.values[..., VBM] bandgap.ref.vbm = raw_gap.values[..., VBM] @@ -164,7 +164,7 @@ def test_plot_incorrect_selection(bandgap, selection): bandgap.plot(selection) -@patch("py4vasp._data.bandgap.Bandgap.to_graph") +@patch("py4vasp.calculation._bandgap.Bandgap.to_graph") def test_energy_to_plotly(mock_plot, bandgap): fig = bandgap.to_plotly() mock_plot.assert_called_once_with() @@ -180,7 +180,7 @@ def test_to_image(bandgap): def check_to_image(bandgap, filename_argument, expected_filename): - with patch("py4vasp._data.bandgap.Bandgap.to_plotly") as plot: + with patch("py4vasp.calculation._bandgap.Bandgap.to_plotly") as plot: bandgap.to_image("args", filename=filename_argument, key="word") plot.assert_called_once_with("args", key="word") fig = plot.return_value @@ -310,4 +310,4 @@ def get_reference_string_spin_polarized(steps): def test_factory_methods(raw_data, check_factory_methods): raw_gap = raw_data.bandgap("default") - check_factory_methods(data.Bandgap, raw_gap) + check_factory_methods(calculation.bandgap, raw_gap) diff --git a/tests/data/test_base.py b/tests/calculation/test_base.py similarity index 97% rename from tests/data/test_base.py rename to tests/calculation/test_base.py index 514c1eb7..543cf3af 100644 --- a/tests/data/test_base.py +++ b/tests/calculation/test_base.py @@ -11,8 +11,8 @@ import pytest from py4vasp import exception, raw -from py4vasp._data import base from py4vasp._util import select +from py4vasp.calculation import _base from .conftest import SELECTION @@ -39,40 +39,40 @@ def mock_behavior(quantity, *, selection=None, path=None, file=None): yield access -class Example(base.Refinery): +class Example(_base.Refinery): def __post_init__(self): self.post_init_called = True - @base.data_access + @_base.data_access def to_dict(self): "to_dict documentation." return self._raw_data.content - @base.data_access + @_base.data_access def wrapper(self): return self.read() - @base.data_access + @_base.data_access def with_arguments(self, mandatory, optional=None): return mandatory, optional - @base.data_access + @_base.data_access def with_variadic_arguments(self, *args, **kwargs): return args, kwargs - @base.data_access + @_base.data_access def with_selection_argument(self, selection=DEFAULT_SELECTION): return self._raw_data.selection, selection - @base.data_access + @_base.data_access def selection_without_default(self, selection): return selection - @base.data_access + @_base.data_access def selection_from_property(self): return self._selection - @base.data_access + @_base.data_access def __str__(self): return self._raw_data.content @@ -255,8 +255,8 @@ def check_mock(example, mock, *args, **kwargs): mock.reset_mock() -class CamelCase(base.Refinery): - @base.data_access +class CamelCase(_base.Refinery): + @_base.data_access def to_dict(self): return "convert CamelCase to snake_case" diff --git a/tests/data/test_born_effective_charge.py b/tests/calculation/test_born_effective_charge.py similarity index 88% rename from tests/data/test_born_effective_charge.py rename to tests/calculation/test_born_effective_charge.py index 7d777a4e..05e3ca4d 100644 --- a/tests/data/test_born_effective_charge.py +++ b/tests/calculation/test_born_effective_charge.py @@ -4,15 +4,16 @@ import pytest -from py4vasp.data import BornEffectiveCharge, Structure +from py4vasp import calculation @pytest.fixture def Sr2TiO4(raw_data): raw_born_charges = raw_data.born_effective_charge("Sr2TiO4") - born_charges = BornEffectiveCharge.from_data(raw_born_charges) + born_charges = calculation.born_effective_charge.from_data(raw_born_charges) born_charges.ref = types.SimpleNamespace() - born_charges.ref.structure = Structure.from_data(raw_born_charges.structure) + structure = calculation.structure.from_data(raw_born_charges.structure) + born_charges.ref.structure = structure born_charges.ref.charge_tensors = raw_born_charges.charge_tensors return born_charges @@ -67,4 +68,4 @@ def test_Sr2TiO4_print(Sr2TiO4, format_): def test_factory_methods(raw_data, check_factory_methods): data = raw_data.born_effective_charge("Sr2TiO4") - check_factory_methods(BornEffectiveCharge, data) + check_factory_methods(calculation.born_effective_charge, data) diff --git a/tests/test_calculation.py b/tests/calculation/test_class.py similarity index 77% rename from tests/test_calculation.py rename to tests/calculation/test_class.py index 6d31b173..501aa89f 100644 --- a/tests/test_calculation.py +++ b/tests/calculation/test_class.py @@ -1,16 +1,15 @@ # Copyright © VASP Software GmbH, # Licensed under the Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0) -import inspect import os from pathlib import Path from unittest.mock import mock_open, patch import pytest -from py4vasp import Calculation, control, data, exception +from py4vasp import Calculation, calculation, control, exception -@patch("py4vasp._data.base.Refinery.from_path", autospec=True) +@patch("py4vasp.calculation._base.Refinery.from_path", autospec=True) @patch("py4vasp.raw.access", autospec=True) def test_creation_from_path(mock_access, mock_from_path): # note: in pytest __file__ defaults to absolute path @@ -26,7 +25,7 @@ def test_creation_from_path(mock_access, mock_from_path): mock_from_path.assert_called() -@patch("py4vasp._data.base.Refinery.from_file", autospec=True) +@patch("py4vasp.calculation._base.Refinery.from_file", autospec=True) @patch("py4vasp.raw.access", autospec=True) def test_creation_from_file(mock_access, mock_from_file): # note: in pytest __file__ defaults to absolute path @@ -45,23 +44,9 @@ def test_creation_from_file(mock_access, mock_from_file): @patch("py4vasp.raw.access", autospec=True) def test_all_attributes(mock_access): - calculation = Calculation.from_path("test_path") - special_cases = { - "BornEffectiveCharge": "born_effective_charge", - "DielectricFunction": "dielectric_function", - "DielectricTensor": "dielectric_tensor", - "CONTCAR": "CONTCAR", - "ElasticModulus": "elastic_modulus", - "ForceConstant": "force_constant", - "InternalStrain": "internal_strain", - "PairCorrelation": "pair_correlation", - "PhononBand": "phonon_band", - "PhononDos": "phonon_dos", - "PiezoelectricTensor": "piezoelectric_tensor", - } - for name, _ in inspect.getmembers(data, inspect.isclass): - attr = special_cases.get(name, name.lower()) - assert hasattr(calculation, attr) + calc = Calculation.from_path("test_path") + for name in calculation.__all__: + assert hasattr(calc, name) mock_access.assert_not_called() mock_access.return_value.__enter__.assert_not_called() diff --git a/tests/data/test_contcar.py b/tests/calculation/test_contcar.py similarity index 93% rename from tests/data/test_contcar.py rename to tests/calculation/test_contcar.py index ddb1cb63..573d2ecd 100644 --- a/tests/data/test_contcar.py +++ b/tests/calculation/test_contcar.py @@ -5,8 +5,8 @@ import pytest -from py4vasp import data -from py4vasp._data import viewer3d +from py4vasp import calculation +from py4vasp._third_party.viewer import viewer3d REF_Sr2TiO4 = """\ Sr2TiO4 @@ -63,9 +63,10 @@ def CONTCAR(raw_data, request): selection = request.param raw_contcar = raw_data.CONTCAR(selection) - contcar = data.CONTCAR.from_data(raw_contcar) + contcar = calculation.CONTCAR.from_data(raw_contcar) contcar.ref = types.SimpleNamespace() - contcar.ref.structure = data.Structure.from_data(raw_data.structure(selection))[-1] + structure = calculation.structure.from_data(raw_data.structure(selection))[-1] + contcar.ref.structure = structure contcar.ref.system = selection contcar.ref.selective_dynamics = raw_contcar.selective_dynamics contcar.ref.lattice_velocities = raw_contcar.lattice_velocities @@ -118,4 +119,4 @@ def test_print(CONTCAR, format_): def test_factory_methods(raw_data, check_factory_methods): raw_contcar = raw_data.CONTCAR("Sr2TiO4") - check_factory_methods(data.CONTCAR, raw_contcar) + check_factory_methods(calculation.CONTCAR, raw_contcar) diff --git a/tests/data/test_density.py b/tests/calculation/test_density.py similarity index 95% rename from tests/data/test_density.py rename to tests/calculation/test_density.py index 296aabb7..2cdf5d80 100644 --- a/tests/data/test_density.py +++ b/tests/calculation/test_density.py @@ -6,9 +6,8 @@ import numpy as np import pytest -from py4vasp import _config, exception, raw -from py4vasp._data import viewer3d -from py4vasp.data import Density, Structure +from py4vasp import _config, calculation, exception, raw +from py4vasp._third_party.viewer import viewer3d @pytest.fixture(params=[None, "tau"]) @@ -39,7 +38,7 @@ def noncollinear_density(raw_data, density_source): @pytest.fixture def empty_density(raw_data): raw_density = raw.Density(raw_data.structure("Sr2TiO4"), charge=raw.VaspData(None)) - return Density.from_data(raw_density) + return calculation.density.from_data(raw_density) @pytest.fixture @@ -54,9 +53,10 @@ def mock_viewer(): def make_reference_density(raw_data, selection, source=None): raw_density = raw_data.density(selection) - density = Density.from_data(raw_density) + density = calculation.density.from_data(raw_density) density.ref = types.SimpleNamespace() - density.ref.structure = Structure.from_data(raw_density.structure).read() + structure = calculation.structure.from_data(raw_density.structure).read() + density.ref.structure = structure density.ref.output = get_expected_dict(raw_density.charge, source) density.ref.string = get_expected_string(selection, source) density.ref.selections = get_expected_selections(raw_density.charge) @@ -266,7 +266,7 @@ def test_color_specified_for_sigma_z(collinear_density, not_core): def test_magnetization_without_component(selection, raw_data, not_core): data = raw_data.density("Fe3O4 noncollinear") with pytest.raises(exception.IncorrectUsage): - Density.from_data(data).plot(selection) + calculation.density.from_data(data).plot(selection) def test_print(reference_density, format_): @@ -276,4 +276,4 @@ def test_print(reference_density, format_): def test_factory_methods(raw_data, check_factory_methods): data = raw_data.density("Fe3O4 collinear") - check_factory_methods(Density, data) + check_factory_methods(calculation.density, data) diff --git a/tests/data/test_dielectric_function.py b/tests/calculation/test_dielectric_function.py similarity index 96% rename from tests/data/test_dielectric_function.py rename to tests/calculation/test_dielectric_function.py index 8c20c8cd..69cd110e 100644 --- a/tests/data/test_dielectric_function.py +++ b/tests/calculation/test_dielectric_function.py @@ -7,14 +7,13 @@ import numpy as np import pytest -from py4vasp import exception -from py4vasp.data import DielectricFunction +from py4vasp import calculation, exception @pytest.fixture def electronic(raw_data): raw_electronic = raw_data.dielectric_function("electron") - electronic = DielectricFunction.from_data(raw_electronic) + electronic = calculation.dielectric_function.from_data(raw_electronic) electronic.ref = types.SimpleNamespace() electronic.ref.energies = raw_electronic.energies to_complex = lambda data: data[..., 0] + 1j * data[..., 1] @@ -26,7 +25,7 @@ def electronic(raw_data): @pytest.fixture def ionic(raw_data): raw_ionic = raw_data.dielectric_function("ion") - ionic = DielectricFunction.from_data(raw_ionic) + ionic = calculation.dielectric_function.from_data(raw_ionic) ionic.ref = types.SimpleNamespace() ionic.ref.energies = raw_ionic.energies to_complex = lambda data: data[..., 0] + 1j * data[..., 1] @@ -300,7 +299,7 @@ def check_figure_contains_plots(fig, references, Assert): assert data.name == ref.name -@patch("py4vasp._data.dielectric_function.DielectricFunction.to_graph") +@patch("py4vasp.calculation._dielectric_function.DielectricFunction.to_graph") def test_electronic_to_plotly(mock_plot, electronic): fig = electronic.to_plotly("selection") mock_plot.assert_called_once_with("selection") @@ -322,7 +321,9 @@ def test_ionic_to_image(ionic): def check_to_image(dielectric_function, filename_argument, expected_filename): - plot_function = "py4vasp._data.dielectric_function.DielectricFunction.to_plotly" + plot_function = ( + "py4vasp.calculation._dielectric_function.DielectricFunction.to_plotly" + ) with patch(plot_function) as plot: dielectric_function.to_image("args", filename=filename_argument, key="word") plot.assert_called_once_with("args", key="word") @@ -370,4 +371,4 @@ def test_ionic_print(ionic, format_): def test_factory_methods(raw_data, check_factory_methods): data = raw_data.dielectric_function("electron") - check_factory_methods(DielectricFunction, data) + check_factory_methods(calculation.dielectric_function, data) diff --git a/tests/data/test_dielectric_tensor.py b/tests/calculation/test_dielectric_tensor.py similarity index 94% rename from tests/data/test_dielectric_tensor.py rename to tests/calculation/test_dielectric_tensor.py index 6ba9081b..169cfad9 100644 --- a/tests/data/test_dielectric_tensor.py +++ b/tests/calculation/test_dielectric_tensor.py @@ -4,8 +4,7 @@ import pytest -from py4vasp import exception -from py4vasp.data import DielectricTensor +from py4vasp import calculation, exception @pytest.fixture @@ -34,7 +33,7 @@ def nscf_tensor(raw_data): def make_reference(raw_data, method, expected_description): raw_tensor = raw_data.dielectric_tensor(method) - tensor = DielectricTensor.from_data(raw_tensor) + tensor = calculation.dielectric_tensor.from_data(raw_tensor) tensor.ref = types.SimpleNamespace() tensor.ref.clamped_ion = raw_tensor.electron if raw_tensor.ion.is_none(): @@ -76,7 +75,7 @@ def check_read_dielectric_tensor(dielectric_tensor, Assert): def test_unknown_method(raw_data): raw_tensor = raw_data.dielectric_tensor("unknown_method with_ion") with pytest.raises(exception.NotImplemented): - DielectricTensor.from_data(raw_tensor).print() + calculation.dielectric_tensor.from_data(raw_tensor).print() def test_print_dft_tensor(dft_tensor, format_): @@ -124,4 +123,4 @@ def check_print_dielectric_tensor(actual, reference): def test_factory_methods(raw_data, check_factory_methods): data = raw_data.dielectric_tensor("dft with_ion") - check_factory_methods(DielectricTensor, data) + check_factory_methods(calculation.dielectric_tensor, data) diff --git a/tests/data/test_dispersion.py b/tests/calculation/test_dispersion.py similarity index 94% rename from tests/data/test_dispersion.py rename to tests/calculation/test_dispersion.py index 422a04c5..1f22ad85 100644 --- a/tests/data/test_dispersion.py +++ b/tests/calculation/test_dispersion.py @@ -5,15 +5,15 @@ import numpy as np import pytest -from py4vasp.data import Dispersion, Kpoint +from py4vasp import calculation @pytest.fixture(params=["single_band", "spin_polarized", "line", "phonon"]) def dispersion(raw_data, request): raw_dispersion = raw_data.dispersion(request.param) - dispersion = Dispersion.from_data(raw_dispersion) + dispersion = calculation.dispersion.from_data(raw_dispersion) dispersion.ref = types.SimpleNamespace() - dispersion.ref.kpoints = Kpoint.from_data(raw_dispersion.kpoints) + dispersion.ref.kpoints = calculation.kpoint.from_data(raw_dispersion.kpoints) dispersion.ref.eigenvalues = raw_dispersion.eigenvalues spin_polarized = request.param == "spin_polarized" dispersion.ref.spin_polarized = spin_polarized @@ -98,4 +98,4 @@ def test_print(dispersion, format_): def test_factory_methods(raw_data, check_factory_methods): data = raw_data.dispersion("single_band") - check_factory_methods(Dispersion, data) + check_factory_methods(calculation.dispersion, data) diff --git a/tests/data/test_dos.py b/tests/calculation/test_dos.py similarity index 95% rename from tests/data/test_dos.py rename to tests/calculation/test_dos.py index 3b396dfe..b6eb69a9 100644 --- a/tests/data/test_dos.py +++ b/tests/calculation/test_dos.py @@ -6,15 +6,13 @@ import numpy as np import pytest -from py4vasp import exception -from py4vasp._util import select -from py4vasp.data import Dos +from py4vasp import calculation, exception @pytest.fixture def Sr2TiO4(raw_data): raw_dos = raw_data.dos("Sr2TiO4") - dos = Dos.from_data(raw_dos) + dos = calculation.dos.from_data(raw_dos) dos.ref = types.SimpleNamespace() dos.ref.energies = raw_dos.energies - raw_dos.fermi_energy dos.ref.dos = raw_dos.dos[0] @@ -25,7 +23,7 @@ def Sr2TiO4(raw_data): @pytest.fixture def Fe3O4(raw_data): raw_dos = raw_data.dos("Fe3O4") - dos = Dos.from_data(raw_dos) + dos = calculation.dos.from_data(raw_dos) dos.ref = types.SimpleNamespace() dos.ref.energies = raw_dos.energies - raw_dos.fermi_energy dos.ref.dos_up = raw_dos.dos[0] @@ -37,7 +35,7 @@ def Fe3O4(raw_data): @pytest.fixture def Sr2TiO4_projectors(raw_data): raw_dos = raw_data.dos("Sr2TiO4 with_projectors") - dos = Dos.from_data(raw_dos) + dos = calculation.dos.from_data(raw_dos) dos.ref = types.SimpleNamespace() dos.ref.s = np.sum(raw_dos.projections[0, :, 0, :], axis=0) dos.ref.Sr_p = np.sum(raw_dos.projections[0, 0:2, 1:4, :], axis=(0, 1)) @@ -54,7 +52,7 @@ def Sr2TiO4_projectors(raw_data): @pytest.fixture def Fe3O4_projectors(raw_data): raw_dos = raw_data.dos("Fe3O4 with_projectors") - dos = Dos.from_data(raw_dos) + dos = calculation.dos.from_data(raw_dos) dos.ref = types.SimpleNamespace() dos.ref.Fe_up = np.sum(raw_dos.projections[0, 0:3, :, :], axis=(0, 1)) dos.ref.Fe_down = np.sum(raw_dos.projections[1, 0:3, :, :], axis=(0, 1)) @@ -111,7 +109,7 @@ def test_read_excess_orbital_types(raw_data, Assert): """Vasp 6.1 may store more orbital types then projections available. This test checks that this does not lead to any issues when an available element is used.""" - dos = Dos.from_data(raw_data.dos("Fe3O4 excess_orbitals")) + dos = calculation.dos.from_data(raw_data.dos("Fe3O4 excess_orbitals")) actual = dos.read("s p g") zero = np.zeros_like(actual["energies"]) Assert.allclose(actual["g_up"], zero) @@ -208,7 +206,7 @@ def test_plot_combine_projectors(Fe3O4_projectors, Assert): Assert.allclose(data[names.index("Fe_down - p_down")].y, -subtraction_down) -@patch("py4vasp._data.dos.Dos.to_graph") +@patch("py4vasp.calculation._dos.Dos.to_graph") def test_Sr2TiO4_to_plotly(mock_plot, Sr2TiO4): fig = Sr2TiO4.to_plotly("selection") mock_plot.assert_called_once_with("selection") @@ -224,7 +222,7 @@ def test_Sr2TiO4_to_image(Sr2TiO4): def check_to_image(Sr2TiO4, filename_argument, expected_filename): - with patch("py4vasp._data.dos.Dos.to_plotly") as plot: + with patch("py4vasp.calculation._dos.Dos.to_plotly") as plot: Sr2TiO4.to_image("args", filename=filename_argument, key="word") plot.assert_called_once_with("args", key="word") fig = plot.return_value @@ -265,4 +263,4 @@ def test_Sr2TiO4_projectors_print(Sr2TiO4_projectors, format_): def test_factory_methods(raw_data, check_factory_methods): data = raw_data.dos("Sr2TiO4") - check_factory_methods(Dos, data) + check_factory_methods(calculation.dos, data) diff --git a/tests/data/test_elastic_modulus.py b/tests/calculation/test_elastic_modulus.py similarity index 92% rename from tests/data/test_elastic_modulus.py rename to tests/calculation/test_elastic_modulus.py index 16dc1b6a..0bae7781 100644 --- a/tests/data/test_elastic_modulus.py +++ b/tests/calculation/test_elastic_modulus.py @@ -4,13 +4,13 @@ import pytest -from py4vasp.data import ElasticModulus +from py4vasp import calculation @pytest.fixture def elastic_modulus(raw_data): raw_elastic_modulus = raw_data.elastic_modulus("dft") - elastic_modulus = ElasticModulus.from_data(raw_elastic_modulus) + elastic_modulus = calculation.elastic_modulus.from_data(raw_elastic_modulus) elastic_modulus.ref = types.SimpleNamespace() elastic_modulus.ref.clamped_ion = raw_elastic_modulus.clamped_ion elastic_modulus.ref.relaxed_ion = raw_elastic_modulus.relaxed_ion @@ -49,4 +49,4 @@ def test_print(elastic_modulus, format_): def test_factory_methods(raw_data, check_factory_methods): data = raw_data.elastic_modulus("dft") - check_factory_methods(ElasticModulus, data) + check_factory_methods(calculation.elastic_modulus, data) diff --git a/tests/data/test_energy.py b/tests/calculation/test_energy.py similarity index 94% rename from tests/data/test_energy.py rename to tests/calculation/test_energy.py index d8ef828c..b301ec6f 100644 --- a/tests/data/test_energy.py +++ b/tests/calculation/test_energy.py @@ -6,15 +6,14 @@ import numpy as np import pytest -from py4vasp import exception +from py4vasp import calculation, exception from py4vasp._util import convert -from py4vasp.data import Energy @pytest.fixture def MD_energy(raw_data): raw_energy = raw_data.energy("MD") - energy = Energy.from_data(raw_energy) + energy = calculation.energy.from_data(raw_energy) energy.ref = types.SimpleNamespace() energy.ref.number_steps = len(raw_energy.values) get_label = lambda x: convert.text_to_string(x).strip() @@ -97,7 +96,7 @@ def test_incorrect_label(MD_energy): MD_energy.plot(number_instead_of_string) -@patch("py4vasp._data.energy.Energy.to_graph") +@patch("py4vasp.calculation._energy.Energy.to_graph") def test_energy_to_plotly(mock_plot, MD_energy): fig = MD_energy.to_plotly("selection") mock_plot.assert_called_once_with("selection") @@ -113,7 +112,7 @@ def test_to_image(MD_energy): def check_to_image(MD_energy, filename_argument, expected_filename): - with patch("py4vasp._data.energy.Energy.to_plotly") as plot: + with patch("py4vasp.calculation._energy.Energy.to_plotly") as plot: MD_energy.to_image("args", filename=filename_argument, key="word") plot.assert_called_once_with("args", key="word") fig = plot.return_value @@ -137,7 +136,7 @@ def test_selections(MD_energy, raw_data): "total_energy", "ETOTAL", ) - assert Energy.from_data(raw_data.energy("relax")).selections() == ( + assert calculation.energy.from_data(raw_data.energy("relax")).selections() == ( "free_energy", "TOTEN", "without_entropy", @@ -172,4 +171,4 @@ def test_print(steps, step_label, MD_energy, format_): def test_factory_methods(raw_data, check_factory_methods): data = raw_data.energy("MD") - check_factory_methods(Energy, data) + check_factory_methods(calculation.energy, data) diff --git a/tests/data/test_fatband.py b/tests/calculation/test_fatband.py similarity index 88% rename from tests/data/test_fatband.py rename to tests/calculation/test_fatband.py index 4cb7bec1..f2a07936 100644 --- a/tests/data/test_fatband.py +++ b/tests/calculation/test_fatband.py @@ -5,15 +5,15 @@ import numpy as np import pytest -from py4vasp.data import Dispersion, Fatband +from py4vasp import calculation @pytest.fixture def fatband(raw_data): raw_fatband = raw_data.fatband("default") - fatband = Fatband.from_data(raw_fatband) + fatband = calculation.fatband.from_data(raw_fatband) fatband.ref = types.SimpleNamespace() - fatband.ref.dispersion = Dispersion.from_data(raw_fatband.dispersion) + fatband.ref.dispersion = calculation.dispersion.from_data(raw_fatband.dispersion) fatbands = raw_fatband.fatbands fatband.ref.fatbands = fatbands[:, :, 0] + fatbands[:, :, 1] * 1j fatband.ref.fermi_energy = raw_fatband.fermi_energy @@ -50,4 +50,4 @@ def test_fatband_print(fatband, format_): def test_factory_methods(raw_data, check_factory_methods): data = raw_data.fatband("default") - check_factory_methods(Fatband, data) + check_factory_methods(calculation.fatband, data) diff --git a/tests/data/test_force.py b/tests/calculation/test_force.py similarity index 92% rename from tests/data/test_force.py rename to tests/calculation/test_force.py index 1f4aaca5..82ba2c3e 100644 --- a/tests/data/test_force.py +++ b/tests/calculation/test_force.py @@ -6,16 +6,15 @@ import numpy as np import pytest -from py4vasp import exception -from py4vasp.data import Force, Structure +from py4vasp import calculation, exception @pytest.fixture def Sr2TiO4(raw_data): raw_forces = raw_data.force("Sr2TiO4") - forces = Force.from_data(raw_forces) + forces = calculation.force.from_data(raw_forces) forces.ref = types.SimpleNamespace() - forces.ref.structure = Structure.from_data(raw_forces.structure) + forces.ref.structure = calculation.structure.from_data(raw_forces.structure) forces.ref.forces = raw_forces.forces return forces @@ -23,9 +22,9 @@ def Sr2TiO4(raw_data): @pytest.fixture def Fe3O4(raw_data): raw_forces = raw_data.force("Fe3O4") - forces = Force.from_data(raw_forces) + forces = calculation.force.from_data(raw_forces) forces.ref = types.SimpleNamespace() - forces.ref.structure = Structure.from_data(raw_forces.structure) + forces.ref.structure = calculation.structure.from_data(raw_forces.structure) forces.ref.forces = raw_forces.forces return forces @@ -66,7 +65,7 @@ def test_plot_Fe3O4(Fe3O4, Assert): def check_plot_forces(forces, step, Assert): - with patch("py4vasp.data.Structure.plot") as plot: + with patch("py4vasp.calculation._structure.Structure.plot") as plot: if step == -1: forces.plot() else: @@ -137,4 +136,4 @@ def test_print_Sr2TiO4(Sr2TiO4, format_): def test_factory_methods(raw_data, check_factory_methods): data = raw_data.force("Fe3O4") - check_factory_methods(Force, data) + check_factory_methods(calculation.force, data) diff --git a/tests/data/test_force_constant.py b/tests/calculation/test_force_constant.py similarity index 93% rename from tests/data/test_force_constant.py rename to tests/calculation/test_force_constant.py index 43f35e62..362a9f0a 100644 --- a/tests/data/test_force_constant.py +++ b/tests/calculation/test_force_constant.py @@ -4,15 +4,16 @@ import pytest -from py4vasp.data import ForceConstant, Structure +from py4vasp import calculation @pytest.fixture def Sr2TiO4(raw_data): raw_force_constants = raw_data.force_constant("Sr2TiO4") - force_constants = ForceConstant.from_data(raw_force_constants) + force_constants = calculation.force_constant.from_data(raw_force_constants) force_constants.ref = types.SimpleNamespace() - force_constants.ref.structure = Structure.from_data(raw_force_constants.structure) + structure = calculation.structure.from_data(raw_force_constants.structure) + force_constants.ref.structure = structure force_constants.ref.force_constants = raw_force_constants.force_constants return force_constants @@ -68,4 +69,4 @@ def test_Sr2TiO4_print(Sr2TiO4, format_): def test_factory_methods(raw_data, check_factory_methods): data = raw_data.force_constant("Sr2TiO4") - check_factory_methods(ForceConstant, data) + check_factory_methods(calculation.force_constant, data) diff --git a/tests/data/test_internal_strain.py b/tests/calculation/test_internal_strain.py similarity index 91% rename from tests/data/test_internal_strain.py rename to tests/calculation/test_internal_strain.py index d593f96d..2a325334 100644 --- a/tests/data/test_internal_strain.py +++ b/tests/calculation/test_internal_strain.py @@ -4,15 +4,16 @@ import pytest -from py4vasp.data import InternalStrain, Structure +from py4vasp import calculation @pytest.fixture def Sr2TiO4(raw_data): raw_internal_strain = raw_data.internal_strain("Sr2TiO4") - internal_strain = InternalStrain.from_data(raw_internal_strain) + internal_strain = calculation.internal_strain.from_data(raw_internal_strain) internal_strain.ref = types.SimpleNamespace() - internal_strain.ref.structure = Structure.from_data(raw_internal_strain.structure) + structure = calculation.structure.from_data(raw_internal_strain.structure) + internal_strain.ref.structure = structure internal_strain.ref.internal_strain = raw_internal_strain.internal_strain return internal_strain @@ -61,4 +62,4 @@ def test_Sr2TiO4_print(Sr2TiO4, format_): def test_factory_methods(raw_data, check_factory_methods): data = raw_data.internal_strain("Sr2TiO4") - check_factory_methods(InternalStrain, data) + check_factory_methods(calculation.internal_strain, data) diff --git a/tests/data/test_kpoint.py b/tests/calculation/test_kpoint.py similarity index 94% rename from tests/data/test_kpoint.py rename to tests/calculation/test_kpoint.py index fd7aba3f..8cf68d12 100644 --- a/tests/data/test_kpoint.py +++ b/tests/calculation/test_kpoint.py @@ -5,14 +5,13 @@ import numpy as np import pytest -from py4vasp import exception -from py4vasp.data import Kpoint +from py4vasp import calculation, exception @pytest.fixture def explicit_kpoints(raw_data): raw_kpoints = raw_data.kpoint("explicit with_labels") - kpoints = Kpoint.from_data(raw_kpoints) + kpoints = calculation.kpoint.from_data(raw_kpoints) kpoints.ref = types.SimpleNamespace() kpoints.ref.mode = "explicit" kpoints.ref.line_length = len(raw_kpoints.coordinates) @@ -31,7 +30,7 @@ def explicit_kpoints(raw_data): @pytest.fixture def grid_kpoints(raw_data): raw_kpoints = raw_data.kpoint("automatic") - kpoints = Kpoint.from_data(raw_kpoints) + kpoints = calculation.kpoint.from_data(raw_kpoints) kpoints.ref = types.SimpleNamespace() kpoints.ref.line_length = len(raw_kpoints.coordinates) return kpoints @@ -40,7 +39,7 @@ def grid_kpoints(raw_data): @pytest.fixture def line_kpoints(raw_data): raw_kpoints = raw_data.kpoint("line with_labels") - kpoints = Kpoint.from_data(raw_kpoints) + kpoints = calculation.kpoint.from_data(raw_kpoints) kpoints.ref = types.SimpleNamespace() kpoints.ref.line_length = raw_kpoints.number kpoints.ref.number_lines = len(raw_kpoints.coordinates) // raw_kpoints.number @@ -58,7 +57,7 @@ def line_kpoints(raw_data): @pytest.fixture def qpoints(raw_data): raw_kpoints = raw_data.kpoint("qpoints") - kpoints = Kpoint.from_data(raw_kpoints) + kpoints = calculation.kpoint.from_data(raw_kpoints) kpoints.ref = types.SimpleNamespace() cartesian = to_cartesian(raw_kpoints.coordinates, raw_kpoints.cell) kpoints.ref.distances = multiple_line_distances(cartesian, raw_kpoints.number) @@ -115,12 +114,12 @@ def test_mode(raw_data): for ref_mode, formats in allowed_mode_formats.items(): for format in formats: raw_kpoints = raw_data.kpoint(format) - actual_mode = Kpoint.from_data(raw_kpoints).mode() + actual_mode = calculation.kpoint.from_data(raw_kpoints).mode() assert actual_mode == ref_mode for unknown_mode in ["x", "y", "z"]: with pytest.raises(exception.RefinementError): raw_kpoints = raw_data.kpoint(unknown_mode) - Kpoint.from_data(raw_kpoints).mode() + calculation.kpoint.from_data(raw_kpoints).mode() def test_explicit_kpoints_number_kpoints(explicit_kpoints): @@ -170,7 +169,7 @@ def test_grid_kpoints_labels_without_data(grid_kpoints): def test_line_kpoints_labels_without_data(raw_data): raw_kpoints = raw_data.kpoint("line") - actual = Kpoint.from_data(raw_kpoints).labels() + actual = calculation.kpoint.from_data(raw_kpoints).labels() ref = [""] * len(raw_kpoints.coordinates) ref[0] = r"$[0 0 0]$" ref[4] = r"$[0 0 \frac{1}{2}]$" @@ -268,4 +267,4 @@ def test_print(explicit_kpoints, format_): def test_factory_methods(raw_data, check_factory_methods): data = raw_data.kpoint("automatic") parameters = {"path_indices": {"start": (0, 0, 0), "finish": (1, 1, 1)}} - check_factory_methods(Kpoint, data, parameters) + check_factory_methods(calculation.kpoint, data, parameters) diff --git a/tests/data/test_magnetism.py b/tests/calculation/test_magnetism.py similarity index 95% rename from tests/data/test_magnetism.py rename to tests/calculation/test_magnetism.py index 47116adf..1be16326 100644 --- a/tests/data/test_magnetism.py +++ b/tests/calculation/test_magnetism.py @@ -7,8 +7,7 @@ import numpy as np import pytest -from py4vasp import exception -from py4vasp.data import Magnetism +from py4vasp import calculation, exception @pytest.fixture(params=[slice(None), slice(1, 3), 0, -1]) @@ -43,7 +42,7 @@ def orbital_moments(raw_data): def setup_magnetism(raw_data, kind): raw_magnetism = raw_data.magnetism(kind) - magnetism = Magnetism.from_data(raw_magnetism) + magnetism = calculation.magnetism.from_data(raw_magnetism) magnetism.ref = types.SimpleNamespace() magnetism.ref.kind = kind magnetism.ref.charges = raw_magnetism.spin_moments[:, 0] @@ -167,7 +166,7 @@ def expect_exception(kind, selection): def get_moments(kind, plot_under_test, selection=None): - with patch("py4vasp.data.Structure.plot") as plot: + with patch("py4vasp.calculation._structure.Structure.plot") as plot: plot_under_test() plot.assert_called_once() viewer = plot.return_value @@ -192,7 +191,7 @@ def expected_moments(reference, selection, steps): if reference.kind == "collinear": moments = np.array([[0, 0, m] for m in moments]) largest_moment = np.max(np.linalg.norm(moments, axis=1)) - rescale_moments = Magnetism.length_moments / largest_moment + rescale_moments = calculation.magnetism.length_moments / largest_moment return rescale_moments * moments @@ -200,7 +199,7 @@ def test_plot_supercell(example_magnetism): if example_magnetism.ref.kind == "charge_only": return supercell = (3, 2, 1) - with patch("py4vasp.data.Structure.plot") as plot: + with patch("py4vasp.calculation._structure.Structure.plot") as plot: example_magnetism.plot(supercell=supercell) plot.assert_called_once_with(supercell) viewer = plot.return_value @@ -248,4 +247,4 @@ def test_incorrect_argument(example_magnetism): def test_factory_methods(raw_data, check_factory_methods): data = raw_data.magnetism("collinear") - check_factory_methods(Magnetism, data) + check_factory_methods(calculation.magnetism, data) diff --git a/tests/data/test_pair_correlation.py b/tests/calculation/test_pair_correlation.py similarity index 90% rename from tests/data/test_pair_correlation.py rename to tests/calculation/test_pair_correlation.py index 0ef45dc0..c3063919 100644 --- a/tests/data/test_pair_correlation.py +++ b/tests/calculation/test_pair_correlation.py @@ -4,14 +4,13 @@ import pytest -from py4vasp import exception -from py4vasp.data import PairCorrelation +from py4vasp import calculation, exception @pytest.fixture def pair_correlation(raw_data): raw_pair_correlation = raw_data.pair_correlation("Sr2TiO4") - pair_correlation = PairCorrelation.from_data(raw_pair_correlation) + pair_correlation = calculation.pair_correlation.from_data(raw_pair_correlation) pair_correlation.ref = raw_pair_correlation return pair_correlation @@ -72,7 +71,7 @@ def test_plot_nonexisting_label(pair_correlation): pair_correlation.plot("label does exist") -@patch("py4vasp._data.pair_correlation.PairCorrelation.to_graph") +@patch("py4vasp.calculation._pair_correlation.PairCorrelation.to_graph") def test_pair_correlation_to_plotly(mock_plot, pair_correlation): fig = pair_correlation.to_plotly("selection") mock_plot.assert_called_once_with("selection") @@ -88,7 +87,8 @@ def test_to_image(pair_correlation): def check_to_image(pair_correlation, filename_argument, expected_filename): - with patch("py4vasp._data.pair_correlation.PairCorrelation.to_plotly") as plot: + function = "py4vasp.calculation._pair_correlation.PairCorrelation.to_plotly" + with patch(function) as plot: pair_correlation.to_image("args", filename=filename_argument, key="word") plot.assert_called_once_with("args", key="word") fig = plot.return_value @@ -98,4 +98,4 @@ def check_to_image(pair_correlation, filename_argument, expected_filename): def test_factory_methods(raw_data, check_factory_methods): data = raw_data.pair_correlation("Sr2TiO4") - check_factory_methods(PairCorrelation, data) + check_factory_methods(calculation.pair_correlation, data) diff --git a/tests/data/test_phonon_band.py b/tests/calculation/test_phonon_band.py similarity index 91% rename from tests/data/test_phonon_band.py rename to tests/calculation/test_phonon_band.py index 1fb69675..fbc13002 100644 --- a/tests/data/test_phonon_band.py +++ b/tests/calculation/test_phonon_band.py @@ -6,20 +6,20 @@ import numpy as np import pytest +from py4vasp import calculation from py4vasp._util import convert -from py4vasp.data import Kpoint, PhononBand, Topology @pytest.fixture def phonon_band(raw_data): raw_band = raw_data.phonon_band("default") - band = PhononBand.from_data(raw_band) + band = calculation.phonon_band.from_data(raw_band) band.ref = types.SimpleNamespace() band.ref.bands = raw_band.dispersion.eigenvalues band.ref.modes = convert.to_complex(raw_band.eigenvectors) raw_qpoints = raw_band.dispersion.kpoints - band.ref.qpoints = Kpoint.from_data(raw_qpoints) - band.ref.topology = Topology.from_data(raw_band.topology) + band.ref.qpoints = calculation.kpoint.from_data(raw_qpoints) + band.ref.topology = calculation.topology.from_data(raw_band.topology) Sr = slice(0, 2) band.ref.Sr = np.sum(np.abs(band.ref.modes[:, :, Sr, :]), axis=(2, 3)) Ti = 2 @@ -88,7 +88,7 @@ def check_series(self, series, projection, label, width): self.Assert.allclose(series.width, width * projection.T) -@patch("py4vasp._data.phonon_band.PhononBand.to_graph") +@patch("py4vasp.calculation._phonon_band.PhononBand.to_graph") def test_to_plotly(mock_plot, phonon_band): fig = phonon_band.to_plotly("selection", width=0.2) mock_plot.assert_called_once_with("selection", width=0.2) @@ -104,7 +104,7 @@ def test_to_image(phonon_band): def check_to_image(phonon_band, filename_argument, expected_filename): - with patch("py4vasp._data.phonon_band.PhononBand.to_plotly") as plot: + with patch("py4vasp.calculation._phonon_band.PhononBand.to_plotly") as plot: phonon_band.to_image("args", filename=filename_argument, key="word") plot.assert_called_once_with("args", key="word") fig = plot.return_value @@ -130,4 +130,4 @@ def test_print(phonon_band, format_): def test_factory_methods(raw_data, check_factory_methods): data = raw_data.phonon_band("default") - check_factory_methods(PhononBand, data) + check_factory_methods(calculation.phonon_band, data) diff --git a/tests/data/test_phonon_dos.py b/tests/calculation/test_phonon_dos.py similarity index 92% rename from tests/data/test_phonon_dos.py rename to tests/calculation/test_phonon_dos.py index 69fcda6d..3cefed00 100644 --- a/tests/data/test_phonon_dos.py +++ b/tests/calculation/test_phonon_dos.py @@ -6,13 +6,13 @@ import numpy as np import pytest -from py4vasp.data import PhononDos +from py4vasp import calculation @pytest.fixture def phonon_dos(raw_data): raw_dos = raw_data.phonon_dos("default") - dos = PhononDos.from_data(raw_dos) + dos = calculation.phonon_dos.from_data(raw_dos) dos.ref = types.SimpleNamespace() dos.ref.energies = raw_dos.energies dos.ref.total_dos = raw_dos.dos @@ -65,7 +65,7 @@ def check_series(series, reference, label, Assert): Assert.allclose(series.y, reference) -@patch("py4vasp._data.phonon_dos.PhononDos.to_graph") +@patch("py4vasp.calculation._phonon_dos.PhononDos.to_graph") def test_phonon_dos_to_plotly(mock_plot, phonon_dos): fig = phonon_dos.to_plotly("selection") mock_plot.assert_called_once_with("selection") @@ -81,7 +81,7 @@ def test_phonon_dos_to_image(phonon_dos): def check_to_image(phonon_dos, filename_argument, expected_filename): - with patch("py4vasp._data.phonon_dos.PhononDos.to_plotly") as plot: + with patch("py4vasp.calculation._phonon_dos.PhononDos.to_plotly") as plot: phonon_dos.to_image("args", filename=filename_argument, key="word") plot.assert_called_once_with("args", key="word") fig = plot.return_value @@ -107,4 +107,4 @@ def test_phonon_dos_print(phonon_dos, format_): def test_factory_methods(raw_data, check_factory_methods): data = raw_data.phonon_dos("default") - check_factory_methods(PhononDos, data) + check_factory_methods(calculation.phonon_dos, data) diff --git a/tests/data/test_piezoelectric_tensor.py b/tests/calculation/test_piezoelectric_tensor.py similarity index 91% rename from tests/data/test_piezoelectric_tensor.py rename to tests/calculation/test_piezoelectric_tensor.py index 5e06965a..ceae47b2 100644 --- a/tests/data/test_piezoelectric_tensor.py +++ b/tests/calculation/test_piezoelectric_tensor.py @@ -4,13 +4,13 @@ import pytest -from py4vasp.data import PiezoelectricTensor +from py4vasp import calculation @pytest.fixture def piezoelectric_tensor(raw_data): raw_tensor = raw_data.piezoelectric_tensor("default") - tensor = PiezoelectricTensor.from_data(raw_tensor) + tensor = calculation.piezoelectric_tensor.from_data(raw_tensor) tensor.ref = types.SimpleNamespace() tensor.ref.clamped_ion = raw_tensor.electron tensor.ref.relaxed_ion = raw_tensor.ion + raw_tensor.electron @@ -43,4 +43,4 @@ def test_print(piezoelectric_tensor, format_): def test_factory_methods(raw_data, check_factory_methods): data = raw_data.piezoelectric_tensor("default") - check_factory_methods(PiezoelectricTensor, data) + check_factory_methods(calculation.piezoelectric_tensor, data) diff --git a/tests/data/test_polarization.py b/tests/calculation/test_polarization.py similarity index 87% rename from tests/data/test_polarization.py rename to tests/calculation/test_polarization.py index 242f84be..65d11895 100644 --- a/tests/data/test_polarization.py +++ b/tests/calculation/test_polarization.py @@ -4,13 +4,13 @@ import pytest -from py4vasp.data import Polarization +from py4vasp import calculation @pytest.fixture def polarization(raw_data): raw_polarization = raw_data.polarization("default") - polarization = Polarization.from_data(raw_polarization) + polarization = calculation.polarization.from_data(raw_polarization) polarization.ref = types.SimpleNamespace() polarization.ref.ion_dipole = raw_polarization.ion polarization.ref.electron_dipole = raw_polarization.electron @@ -36,4 +36,4 @@ def test_print(polarization, format_): def test_factory_methods(raw_data, check_factory_methods): data = raw_data.polarization("default") - check_factory_methods(Polarization, data) + check_factory_methods(calculation.polarization, data) diff --git a/tests/data/test_potential.py b/tests/calculation/test_potential.py similarity index 94% rename from tests/data/test_potential.py rename to tests/calculation/test_potential.py index 78fe1fb1..a3d63ce8 100644 --- a/tests/data/test_potential.py +++ b/tests/calculation/test_potential.py @@ -6,9 +6,8 @@ import numpy as np import pytest -from py4vasp import _config, exception, raw -from py4vasp._data import viewer3d -from py4vasp.data import Potential, Structure +from py4vasp import _config, calculation, exception, raw +from py4vasp._third_party.viewer import viewer3d @pytest.fixture(params=["total", "ionic", "hartree", "xc", "all"]) @@ -24,7 +23,7 @@ def reference_potential(raw_data, request, included_kinds): def make_reference_potential(raw_data, system, included_kinds): selection = f"{system} {included_kinds}" raw_potential = raw_data.potential(selection) - potential = Potential.from_data(raw_potential) + potential = calculation.potential.from_data(raw_potential) potential.ref = types.SimpleNamespace() potential.ref.included_kinds = included_kinds potential.ref.output = get_expected_dict(raw_potential) @@ -34,7 +33,7 @@ def make_reference_potential(raw_data, system, included_kinds): def get_expected_dict(raw_potential): return { - "structure": Structure.from_data(raw_potential.structure).read(), + "structure": calculation.structure.from_data(raw_potential.structure).read(), **separate_potential("total", raw_potential.total_potential), **separate_potential("xc", raw_potential.xc_potential), **separate_potential("hartree", raw_potential.hartree_potential), @@ -171,7 +170,7 @@ def test_incorrect_selection(reference_potential, not_core): def test_empty_potential(raw_data, selection, not_core): raw_potential = raw_data.potential("Sr2TiO4 total") raw_potential.total_potential = raw.VaspData(None) - potential = Potential.from_data(raw_potential) + potential = calculation.potential.from_data(raw_potential) with pytest.raises(exception.NoData): potential.plot(selection) @@ -183,4 +182,4 @@ def test_print(reference_potential, format_): def test_factory_methods(raw_data, check_factory_methods): data = raw_data.potential("Fe3O4 collinear total") - check_factory_methods(Potential, data) + check_factory_methods(calculation.potential, data) diff --git a/tests/data/test_projector.py b/tests/calculation/test_projector.py similarity index 97% rename from tests/data/test_projector.py rename to tests/calculation/test_projector.py index 06ddff94..4e9baee9 100644 --- a/tests/data/test_projector.py +++ b/tests/calculation/test_projector.py @@ -5,25 +5,24 @@ import numpy as np import pytest -from py4vasp import exception -from py4vasp._data.selection import Selection +from py4vasp import calculation, exception from py4vasp._util import select -from py4vasp.data import Projector +from py4vasp.calculation._selection import Selection @pytest.fixture def Sr2TiO4(raw_data): - return Projector.from_data(raw_data.projector("Sr2TiO4")) + return calculation.projector.from_data(raw_data.projector("Sr2TiO4")) @pytest.fixture def Fe3O4(raw_data): - return Projector.from_data(raw_data.projector("Fe3O4")) + return calculation.projector.from_data(raw_data.projector("Fe3O4")) @pytest.fixture def missing_orbitals(raw_data): - return Projector.from_data(raw_data.projector("without_orbitals")) + return calculation.projector.from_data(raw_data.projector("without_orbitals")) @pytest.fixture @@ -199,7 +198,7 @@ def test_missing_orbitals_print(missing_orbitals, format_): def test_factory_methods(raw_data, check_factory_methods, projections): data = raw_data.projector("Sr2TiO4") parameters = {"project": {"selection": "Sr", "projections": projections}} - check_factory_methods(Projector, data, parameters) + check_factory_methods(calculation.projector, data, parameters) # @@ -207,7 +206,7 @@ def test_factory_methods(raw_data, check_factory_methods, projections): # TODO: remove when deprecated methods are removed # -Index = Projector.Index +Index = calculation.projector.Index class SelectionTestCase(NamedTuple): diff --git a/tests/calculation/test_repr.py b/tests/calculation/test_repr.py new file mode 100644 index 00000000..19e24d78 --- /dev/null +++ b/tests/calculation/test_repr.py @@ -0,0 +1,16 @@ +# Copyright © VASP Software GmbH, +# Licensed under the Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0) +import importlib + +from py4vasp import calculation +from py4vasp._util import convert + + +def test_repr(): + for name in calculation.__all__: + instance = getattr(calculation, name) + class_name = convert.to_camelcase(name) + module = importlib.import_module(f"py4vasp.calculation._{name}") + locals()[class_name] = getattr(module, class_name) + copy = eval(repr(instance)) + assert copy.__class__ == instance.__class__ diff --git a/tests/data/test_slice_mixin.py b/tests/calculation/test_slice_mixin.py similarity index 95% rename from tests/data/test_slice_mixin.py rename to tests/calculation/test_slice_mixin.py index 13cf0608..81402d21 100644 --- a/tests/data/test_slice_mixin.py +++ b/tests/calculation/test_slice_mixin.py @@ -5,8 +5,8 @@ import pytest from py4vasp import exception -from py4vasp._data import slice_ from py4vasp._util import documentation +from py4vasp.calculation import _slice class Other: @@ -16,8 +16,8 @@ def __init__(self, *args, **kwargs): self._kwargs = kwargs -@documentation.format(examples=slice_.examples("example")) -class ExampleSlice(slice_.Mixin, Other): +@documentation.format(examples=_slice.examples("example")) +class ExampleSlice(_slice.Mixin, Other): "{examples}" def steps(self): @@ -175,7 +175,7 @@ def test_incorrect_argument(): def test_documentation(single_step, last_step): - reference = slice_.examples("example") + reference = _slice.examples("example") assert inspect.getdoc(single_step) == reference assert inspect.getdoc(last_step) == reference diff --git a/tests/data/test_stress.py b/tests/calculation/test_stress.py similarity index 89% rename from tests/data/test_stress.py rename to tests/calculation/test_stress.py index 8ab01993..bda426cb 100644 --- a/tests/data/test_stress.py +++ b/tests/calculation/test_stress.py @@ -4,16 +4,15 @@ import pytest -from py4vasp import exception -from py4vasp.data import Stress, Structure +from py4vasp import calculation, exception @pytest.fixture def Sr2TiO4(raw_data): raw_stress = raw_data.stress("Sr2TiO4") - stress = Stress.from_data(raw_stress) + stress = calculation.stress.from_data(raw_stress) stress.ref = types.SimpleNamespace() - stress.ref.structure = Structure.from_data(raw_stress.structure) + stress.ref.structure = calculation.structure.from_data(raw_stress.structure) stress.ref.stress = raw_stress.stress return stress @@ -21,9 +20,9 @@ def Sr2TiO4(raw_data): @pytest.fixture def Fe3O4(raw_data): raw_stress = raw_data.stress("Fe3O4") - stress = Stress.from_data(raw_stress) + stress = calculation.stress.from_data(raw_stress) stress.ref = types.SimpleNamespace() - stress.ref.structure = Structure.from_data(raw_stress.structure) + stress.ref.structure = calculation.structure.from_data(raw_stress.structure) stress.ref.stress = raw_stress.stress return stress @@ -92,4 +91,4 @@ def test_print_Sr2TiO4(Sr2TiO4, format_): def test_factory_methods(raw_data, check_factory_methods): data = raw_data.stress("Sr2TiO4") - check_factory_methods(Stress, data) + check_factory_methods(calculation.stress, data) diff --git a/tests/data/test_structure.py b/tests/calculation/test_structure.py similarity index 95% rename from tests/data/test_structure.py rename to tests/calculation/test_structure.py index c16598cc..c485574b 100644 --- a/tests/data/test_structure.py +++ b/tests/calculation/test_structure.py @@ -6,9 +6,8 @@ import numpy as np import pytest -from py4vasp import exception -from py4vasp._data import viewer3d -from py4vasp.data import Structure +from py4vasp import calculation, exception +from py4vasp._third_party.viewer import viewer3d REF_POSCAR = """\ Sr2TiO4 @@ -81,7 +80,7 @@ def Ca3AsBr3(raw_data): def make_structure(raw_structure): - structure = Structure.from_data(raw_structure) + structure = calculation.structure.from_data(raw_structure) structure.ref = types.SimpleNamespace() if not raw_structure.cell.scale.is_none(): scale = raw_structure.cell.scale[()] @@ -137,9 +136,10 @@ def test_to_poscar(Sr2TiO4, Ca3AsBr3): def test_from_poscar(Sr2TiO4, Assert, not_core): - structure = Structure.from_POSCAR(REF_POSCAR) + structure = calculation.structure.from_POSCAR(REF_POSCAR) check_Sr2TiO4_structure(structure.read(), Sr2TiO4.ref, -1, Assert) - structure = Structure.from_POSCAR(REF_POSCAR, elements=["Ba", "Zr", "S"]) + elements = ["Ba", "Zr", "S"] + structure = calculation.structure.from_POSCAR(REF_POSCAR, elements=elements) actual = structure.read() Assert.allclose(actual["lattice_vectors"], Sr2TiO4.ref.lattice_vectors[-1]) Assert.allclose(actual["positions"], Sr2TiO4.ref.positions[-1]) @@ -164,8 +164,8 @@ def test_from_poscar_without_elements(Sr2TiO4, Assert, not_core): 0.5000000000000000 0.0000000000000000 0.5000000000000000 0.0000000000000000 0.5000000000000000 0.5000000000000000""" with pytest.raises(exception.IncorrectUsage): - structure = Structure.from_POSCAR(poscar) - structure = Structure.from_POSCAR(poscar, elements=["Sr", "Ti", "O"]) + structure = calculation.structure.from_POSCAR(poscar) + structure = calculation.structure.from_POSCAR(poscar, elements=["Sr", "Ti", "O"]) check_Sr2TiO4_structure(structure.read(), Sr2TiO4.ref, -1, Assert) @@ -206,7 +206,7 @@ def test_to_ase_Ca3AsBr3(Ca3AsBr3, Assert, not_core): def test_from_ase(Sr2TiO4, Assert, not_core): - structure = Structure.from_ase(Sr2TiO4.to_ase()) + structure = calculation.structure.from_ase(Sr2TiO4.to_ase()) check_Sr2TiO4_structure(structure.read(), Sr2TiO4.ref, -1, Assert) @@ -223,7 +223,7 @@ def test_to_mdtraj(Sr2TiO4, Assert, not_core): def check_Sr2TiO4_mdtraj(trajectory, reference, steps, Assert): assert trajectory.n_frames == len(reference.positions[steps]) assert trajectory.n_atoms == 7 - unitcell_vectors = Structure.A_to_nm * reference.lattice_vectors[steps] + unitcell_vectors = calculation.structure.A_to_nm * reference.lattice_vectors[steps] cartesian_positions = [ pos @ cell for pos, cell in zip(reference.positions[steps], unitcell_vectors) ] @@ -361,4 +361,4 @@ def test_print_Ca3AsBr3(Ca3AsBr3, format_): def test_factory_methods(raw_data, check_factory_methods): data = raw_data.structure("Sr2TiO4") parameters = {"__getitem__": {"steps": slice(None)}} - check_factory_methods(Structure, data, parameters) + check_factory_methods(calculation.structure, data, parameters) diff --git a/tests/data/test_system.py b/tests/calculation/test_system.py similarity index 80% rename from tests/data/test_system.py rename to tests/calculation/test_system.py index c1b54590..1e2c264d 100644 --- a/tests/data/test_system.py +++ b/tests/calculation/test_system.py @@ -4,9 +4,8 @@ import pytest -from py4vasp import raw +from py4vasp import calculation, raw from py4vasp._util.convert import text_to_string -from py4vasp.data import System @pytest.fixture @@ -26,7 +25,7 @@ def test_system_read(string_format, byte_format): def check_system_read(raw_system): expected = {"system": text_to_string(raw_system.system)} - assert System.from_data(raw_system).read() == expected + assert calculation.system.from_data(raw_system).read() == expected def test_system_print(string_format, byte_format, format_): @@ -35,10 +34,10 @@ def test_system_print(string_format, byte_format, format_): def check_system_print(raw_system, format_): - system = System.from_data(raw_system) + system = calculation.system.from_data(raw_system) actual, _ = format_(system) assert actual["text/plain"] == text_to_string(raw_system.system) def test_factory_methods(string_format, check_factory_methods): - check_factory_methods(System, string_format) + check_factory_methods(calculation.system, string_format) diff --git a/tests/data/test_topology.py b/tests/calculation/test_topology.py similarity index 90% rename from tests/data/test_topology.py rename to tests/calculation/test_topology.py index 8efb9493..9019fa92 100644 --- a/tests/data/test_topology.py +++ b/tests/calculation/test_topology.py @@ -2,10 +2,9 @@ # Licensed under the Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0) import pytest -from py4vasp import exception -from py4vasp._data.selection import Selection +from py4vasp import calculation, exception from py4vasp._util import import_, select -from py4vasp.data import Topology +from py4vasp.calculation._selection import Selection ase = import_.optional("ase") pd = import_.optional("pandas") @@ -56,7 +55,7 @@ def test_number_atoms(self): def test_from_ase(self, not_core): structure = ase.Atoms("".join(self.elements)) - topology = Topology.from_ase(structure) + topology = calculation.topology.from_ase(structure) assert topology.elements() == self.elements assert str(topology) == str(self.topology) @@ -72,7 +71,7 @@ def unique_elements(self): class TestSr2TiO4(Base): @pytest.fixture(autouse=True) def _setup(self, raw_data): - self.topology = Topology.from_data(raw_data.topology("Sr2TiO4")) + self.topology = calculation.topology.from_data(raw_data.topology("Sr2TiO4")) self.names = ["Sr_1", "Sr_2", "Ti_1", "O_1", "O_2", "O_3", "O_4"] self.elements = 2 * ["Sr"] + ["Ti"] + 4 * ["O"] @@ -101,7 +100,8 @@ class TestCa3AsBr3(Base): @pytest.fixture(autouse=True) def _setup(self, raw_data): - self.topology = Topology.from_data(raw_data.topology("Ca2AsBr-CaBr2")) + raw_topology = raw_data.topology("Ca2AsBr-CaBr2") + self.topology = calculation.topology.from_data(raw_topology) self.names = ["Ca_1", "Ca_2", "As_1", "Br_1", "Ca_3", "Br_2", "Br_3"] self.elements = ["Ca", "Ca", "As", "Br", "Ca", "Br", "Br"] @@ -124,4 +124,4 @@ def test_print(self, format_): def test_factory_methods(raw_data, check_factory_methods): data = raw_data.topology("Sr2TiO4") - check_factory_methods(Topology, data) + check_factory_methods(calculation.topology, data) diff --git a/tests/data/test_velocity.py b/tests/calculation/test_velocity.py similarity index 91% rename from tests/data/test_velocity.py rename to tests/calculation/test_velocity.py index 67178174..7f589639 100644 --- a/tests/data/test_velocity.py +++ b/tests/calculation/test_velocity.py @@ -5,18 +5,17 @@ import pytest -from py4vasp import exception +from py4vasp import calculation, exception from py4vasp._config import VASP_GRAY from py4vasp._util import convert -from py4vasp.data import Structure, Velocity @pytest.fixture def Sr2TiO4(raw_data): raw_velocity = raw_data.velocity("Sr2TiO4") - velocity = Velocity.from_data(raw_velocity) + velocity = calculation.velocity.from_data(raw_velocity) velocity.ref = types.SimpleNamespace() - velocity.ref.structure = Structure.from_data(raw_velocity.structure) + velocity.ref.structure = calculation.structure.from_data(raw_velocity.structure) velocity.ref.velocities = raw_velocity.velocities return velocity @@ -24,9 +23,9 @@ def Sr2TiO4(raw_data): @pytest.fixture def Fe3O4(raw_data): raw_velocity = raw_data.velocity("Fe3O4") - velocity = Velocity.from_data(raw_velocity) + velocity = calculation.velocity.from_data(raw_velocity) velocity.ref = types.SimpleNamespace() - velocity.ref.structure = Structure.from_data(raw_velocity.structure) + velocity.ref.structure = calculation.structure.from_data(raw_velocity.structure) velocity.ref.velocities = raw_velocity.velocities return velocity @@ -67,7 +66,7 @@ def test_plot_Fe3O4(Fe3O4, Assert): def check_plot_velocity(velocity, step, Assert): - with patch("py4vasp.data.Structure.plot") as plot: + with patch("py4vasp.calculation._structure.Structure.plot") as plot: if step == -1: velocity.plot() else: @@ -132,4 +131,4 @@ def test_print_Sr2TiO4(Sr2TiO4, format_): def test_factory_methods(raw_data, check_factory_methods): data = raw_data.velocity("Fe3O4") - check_factory_methods(Velocity, data) + check_factory_methods(calculation.velocity, data) diff --git a/tests/data/test_workfunction.py b/tests/calculation/test_workfunction.py similarity index 91% rename from tests/data/test_workfunction.py rename to tests/calculation/test_workfunction.py index fdc9f23c..ceacf08f 100644 --- a/tests/data/test_workfunction.py +++ b/tests/calculation/test_workfunction.py @@ -2,10 +2,9 @@ # Licensed under the Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0) from unittest.mock import patch -import numpy as np import pytest -from py4vasp import data +from py4vasp import calculation @pytest.fixture(params=[1, 2, 3]) @@ -15,7 +14,7 @@ def workfunction(raw_data, request): def setup_reference(raw_workfunction): - workfunction = data.Workfunction.from_data(raw_workfunction) + workfunction = calculation.workfunction.from_data(raw_workfunction) workfunction.ref = raw_workfunction raw_gap = raw_workfunction.reference_potential workfunction.ref.lattice_vector = f"lattice vector {raw_workfunction.idipol}" @@ -45,7 +44,7 @@ def test_plot(workfunction, Assert): assert graph.series.name == "potential" -@patch("py4vasp._data.workfunction.Workfunction.to_graph") +@patch("py4vasp.calculation._workfunction.Workfunction.to_graph") def test_to_plotly(mock_plot, workfunction): fig = workfunction.to_plotly() mock_plot.assert_called_once_with() @@ -61,7 +60,7 @@ def test_to_image(workfunction): def check_to_image(workfunction, filename_argument, expected_filename): - with patch("py4vasp._data.workfunction.Workfunction.to_plotly") as plot: + with patch("py4vasp.calculation._workfunction.Workfunction.to_plotly") as plot: workfunction.to_image("args", filename=filename_argument, key="word") plot.assert_called_once_with("args", key="word") fig = plot.return_value @@ -89,4 +88,4 @@ def test_print(workfunction, format_): def test_factory_methods(raw_data, check_factory_methods): raw_workfunction = raw_data.workfunction("1") - check_factory_methods(data.Workfunction, raw_workfunction) + check_factory_methods(calculation.workfunction, raw_workfunction) diff --git a/tests/conftest.py b/tests/conftest.py index f3acc139..f480c25d 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -8,7 +8,6 @@ from numpy.testing import assert_array_almost_equal_nulp from py4vasp import exception, raw -from py4vasp._data.base import _DataWrapper number_steps = 4 number_atoms = 7 diff --git a/tests/control/test_poscar.py b/tests/control/test_poscar.py index 93cc57bf..8ac52b00 100644 --- a/tests/control/test_poscar.py +++ b/tests/control/test_poscar.py @@ -2,8 +2,7 @@ # Licensed under the Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0) from unittest.mock import patch -from py4vasp import data -from py4vasp._data import viewer3d +from py4vasp._third_party.viewer import viewer3d from py4vasp.control import POSCAR from .test_base import AbstractTest @@ -38,6 +37,6 @@ def test_plot_poscar(not_core): def test_plot_argument_forwarding(): text = "! comment line" poscar = POSCAR.from_string(text) - with patch.object(data.Structure, "from_POSCAR") as struct: + with patch("py4vasp.calculation._structure.Structure.from_POSCAR") as struct: poscar.plot("argument", key="value") struct.return_value.plot.assert_called_once_with("argument", key="value") diff --git a/tests/data/__init__.py b/tests/data/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/tests/data/test_repr.py b/tests/data/test_repr.py deleted file mode 100644 index 2b2d2bbd..00000000 --- a/tests/data/test_repr.py +++ /dev/null @@ -1,39 +0,0 @@ -# Copyright © VASP Software GmbH, -# Licensed under the Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0) -from py4vasp.data import * - - -def test_repr(): - tests = [ - Band, - Bandgap, - BornEffectiveCharge, - CONTCAR, - Density, - DielectricFunction, - DielectricTensor, - Dos, - ElasticModulus, - Energy, - Fatband, - ForceConstant, - Force, - InternalStrain, - Kpoint, - Magnetism, - PairCorrelation, - PhononBand, - PhononDos, - PiezoelectricTensor, - Polarization, - Projector, - Stress, - Structure, - System, - Topology, - Workfunction, - ] - for class_ in tests: - instance = class_.from_data("mock_data") - copy = eval(repr(instance)) - assert copy.__class__ == class_ diff --git a/tests/test_calculations.py b/tests/test_calculations.py index 04c048e4..6bfd5af0 100644 --- a/tests/test_calculations.py +++ b/tests/test_calculations.py @@ -129,7 +129,7 @@ def test_create_from_files_with_wildcards(tmp_path): assert output_number_of_calculations["file_2"] == 2 -@patch("py4vasp._data.base.Refinery.from_path", autospec=True) +@patch("py4vasp.calculation._base.Refinery.from_path", autospec=True) @patch("py4vasp.raw.access", autospec=True) def test_has_attributes(mock_access, mock_from_path): calculations = Calculations.from_paths(path_name_1="path_1", path_name_2="path_2") diff --git a/tests/data/test_viewer3d.py b/tests/third_party/test_viewer3d.py similarity index 92% rename from tests/data/test_viewer3d.py rename to tests/third_party/test_viewer3d.py index 3c0686da..e7970c70 100644 --- a/tests/data/test_viewer3d.py +++ b/tests/third_party/test_viewer3d.py @@ -6,10 +6,15 @@ import numpy as np import pytest -from py4vasp import exception -from py4vasp._data.viewer3d import Viewer3d, _Arrow3d, _x_axis, _y_axis, _z_axis +from py4vasp import calculation, exception +from py4vasp._third_party.viewer.viewer3d import ( + Viewer3d, + _Arrow3d, + _x_axis, + _y_axis, + _z_axis, +) from py4vasp._util import import_ -from py4vasp.data import Structure json = import_.optional("ipykernel.jsonutil") nglview = import_.optional("nglview") @@ -33,7 +38,7 @@ def _assert_arrow_message(message, arrow): @pytest.fixture def viewer3d(raw_data, not_core): - structure = Structure.from_data(raw_data.structure("Sr2TiO4")) + structure = calculation.structure.from_data(raw_data.structure("Sr2TiO4")) return make_viewer(structure) @@ -43,7 +48,7 @@ def nonstandard_form(raw_data, not_core): x = np.sqrt(0.5) raw_structure.cell.lattice_vectors = np.array([[[x, x, 0], [-x, x, 0], [0, 0, 1]]]) raw_structure.positions += 0.1 # shift to avoid small comparisons - viewer = make_viewer(Structure.from_data(raw_structure)) + viewer = make_viewer(calculation.structure.from_data(raw_structure)) viewer.ref.transformation = np.array([[x, x, 0], [-x, x, 0], [0, 0, 1]]) return viewer @@ -121,7 +126,7 @@ def test_arrows(viewer3d, assert_arrow_message): def test_supercell(raw_data, not_core): - structure = Structure.from_data(raw_data.structure("Sr2TiO4")) + structure = calculation.structure.from_data(raw_data.structure("Sr2TiO4")) number_atoms = structure.number_atoms() supercell = (1, 2, 3) viewer = make_viewer(structure, supercell) @@ -178,7 +183,7 @@ def rotate(arrow, transformation): def test_isosurface(raw_data, not_core): raw_density = raw_data.density("Fe3O4 collinear") - viewer = make_viewer(Structure.from_data(raw_density.structure)) + viewer = make_viewer(calculation.structure.from_data(raw_density.structure)) viewer.show_isosurface(raw_density.charge) messages = last_messages(viewer, n=1, get_msg_kwargs=True) assert_load_file(messages[0], binary=True, default=True) @@ -191,7 +196,7 @@ def test_isosurface(raw_data, not_core): def test_trajectory(raw_data, not_core): - structure = Structure.from_data(raw_data.structure("Sr2TiO4")) + structure = calculation.structure.from_data(raw_data.structure("Sr2TiO4")) viewer = structure[:].plot() viewer.default_messages = 0 n = count_messages(viewer)