diff --git a/core/poetry.lock b/core/poetry.lock index 1d94d801..a51f3034 100644 --- a/core/poetry.lock +++ b/core/poetry.lock @@ -1,14 +1,14 @@ -# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.5.1 and should not be changed by hand. [[package]] name = "astroid" -version = "3.1.0" +version = "3.2.1" description = "An abstract syntax tree for Python with inference support." optional = false python-versions = ">=3.8.0" files = [ - {file = "astroid-3.1.0-py3-none-any.whl", hash = "sha256:951798f922990137ac090c53af473db7ab4e70c770e6d7fae0cec59f74411819"}, - {file = "astroid-3.1.0.tar.gz", hash = "sha256:ac248253bfa4bd924a0de213707e7ebeeb3138abeb48d798784ead1e56d419d4"}, + {file = "astroid-3.2.1-py3-none-any.whl", hash = "sha256:b452064132234819f023b94f4bd045b250ea0009f372b4377cfcd87f10806ca5"}, + {file = "astroid-3.2.1.tar.gz", hash = "sha256:902564b36796ba1eab3ad2c7a694861fbd926f574d5dbb5fa1d86778a2ba2d91"}, ] [package.dependencies] @@ -235,13 +235,13 @@ numpy = ">=1.17.3" [[package]] name = "hypothesis" -version = "6.101.0" +version = "6.102.4" description = "A library for property-based testing" optional = false python-versions = ">=3.8" files = [ - {file = "hypothesis-6.101.0-py3-none-any.whl", hash = "sha256:3aeeda59bc7878aa2ad066f71925ab9296a148f7fb7d5395a47795035283dec2"}, - {file = "hypothesis-6.101.0.tar.gz", hash = "sha256:b43242fc2b672b26c81688876976c1c2cc44116050ca12f60eb2671b75eacd45"}, + {file = "hypothesis-6.102.4-py3-none-any.whl", hash = "sha256:013df31b04a4daede13756f497e60e451963d86f426395a79f99c5d692919bbd"}, + {file = "hypothesis-6.102.4.tar.gz", hash = "sha256:59b4d144346d5cffb482cc1bafbd21b13ff31608e8c4b3e4630339aee3e87763"}, ] [package.dependencies] @@ -382,13 +382,13 @@ files = [ [[package]] name = "platformdirs" -version = "4.2.1" +version = "4.2.2" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a `user data dir`." optional = false python-versions = ">=3.8" files = [ - {file = "platformdirs-4.2.1-py3-none-any.whl", hash = "sha256:17d5a1161b3fd67b390023cb2d3b026bbd40abde6fdb052dfbd3a29c3ba22ee1"}, - {file = "platformdirs-4.2.1.tar.gz", hash = "sha256:031cd18d4ec63ec53e82dceaac0417d218a6863f7745dfcc9efe7793b7039bdf"}, + {file = "platformdirs-4.2.2-py3-none-any.whl", hash = "sha256:2d7a1657e36a80ea911db832a8a6ece5ee53d8de21edd5cc5879af6530b1bfee"}, + {file = "platformdirs-4.2.2.tar.gz", hash = "sha256:38b7b51f512eed9e84a22788b4bce1de17c0adb134d6becb09836e37d8654cd3"}, ] [package.extras] @@ -413,22 +413,22 @@ testing = ["pytest", "pytest-benchmark"] [[package]] name = "pylint" -version = "3.1.0" +version = "3.2.0" description = "python code static checker" optional = false python-versions = ">=3.8.0" files = [ - {file = "pylint-3.1.0-py3-none-any.whl", hash = "sha256:507a5b60953874766d8a366e8e8c7af63e058b26345cfcb5f91f89d987fd6b74"}, - {file = "pylint-3.1.0.tar.gz", hash = "sha256:6a69beb4a6f63debebaab0a3477ecd0f559aa726af4954fc948c51f7a2549e23"}, + {file = "pylint-3.2.0-py3-none-any.whl", hash = "sha256:9f20c05398520474dac03d7abb21ab93181f91d4c110e1e0b32bc0d016c34fa4"}, + {file = "pylint-3.2.0.tar.gz", hash = "sha256:ad8baf17c8ea5502f23ae38d7c1b7ec78bd865ce34af9a0b986282e2611a8ff2"}, ] [package.dependencies] -astroid = ">=3.1.0,<=3.2.0-dev0" +astroid = ">=3.2.0,<=3.3.0-dev0" colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} dill = [ {version = ">=0.2", markers = "python_version < \"3.11\""}, + {version = ">=0.3.6", markers = "python_version >= \"3.11\""}, {version = ">=0.3.7", markers = "python_version >= \"3.12\""}, - {version = ">=0.3.6", markers = "python_version >= \"3.11\" and python_version < \"3.12\""}, ] isort = ">=4.2.5,<5.13.0 || >5.13.0,<6" mccabe = ">=0.6,<0.8" @@ -528,4 +528,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = ">=3.9" -content-hash = "a19e45d9772b6787259404e7542594e210af50e4ff329eafdc8a2363dfa71dbe" +content-hash = "0e9853afabcd6b1e258bb9c2b31aa7a469829940a2f195fefcd625029743416f" diff --git a/core/pyproject.toml b/core/pyproject.toml index ee78a837..15063423 100644 --- a/core/pyproject.toml +++ b/core/pyproject.toml @@ -25,7 +25,7 @@ repository = "https://github.com/vasp-dev/py4vasp" [tool.poetry.dependencies] python = ">=3.9" -numpy = ">=1.23" +numpy = "^1.23" h5py = ">=3.7.0" [tool.poetry.group.dev.dependencies] diff --git a/poetry.lock b/poetry.lock index 988a6404..19fe5d15 100644 --- a/poetry.lock +++ b/poetry.lock @@ -2124,8 +2124,8 @@ files = [ [package.dependencies] numpy = [ {version = ">=1.22.4", markers = "python_version < \"3.11\""}, - {version = ">=1.26.0", markers = "python_version >= \"3.12\""}, {version = ">=1.23.2", markers = "python_version == \"3.11\""}, + {version = ">=1.26.0", markers = "python_version >= \"3.12\""}, ] python-dateutil = ">=2.8.2" pytz = ">=2020.1" @@ -2479,8 +2479,8 @@ astroid = ">=3.2.0,<=3.3.0-dev0" colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} dill = [ {version = ">=0.2", markers = "python_version < \"3.11\""}, + {version = ">=0.3.6", markers = "python_version >= \"3.11\""}, {version = ">=0.3.7", markers = "python_version >= \"3.12\""}, - {version = ">=0.3.6", markers = "python_version >= \"3.11\" and python_version < \"3.12\""}, ] isort = ">=4.2.5,<5.13.0 || >5.13.0,<6" mccabe = ">=0.6,<0.8" @@ -2646,7 +2646,6 @@ files = [ {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, - {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"}, {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, @@ -3523,4 +3522,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "jaraco.test", "more [metadata] lock-version = "2.0" python-versions = ">=3.9" -content-hash = "1dbe1ec8147ca66d47d5e20b43f33bd2a8941507b9aa3b2a03e6e305968a2dc8" +content-hash = "77a96b6fd125ce33f11c5361874255a95af49da196d543a3de0be1af5b179172" diff --git a/pyproject.toml b/pyproject.toml index f95412e6..7fd1d047 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,7 +24,7 @@ repository = "https://github.com/vasp-dev/py4vasp" [tool.poetry.dependencies] python = ">=3.9" -numpy = ">=1.23" +numpy = "^1.23" h5py = ">=3.7.0" pandas = ">=1.4.3" nglview = ">=3.0.5" diff --git a/src/py4vasp/_third_party/view/view.py b/src/py4vasp/_third_party/view/view.py index 957cee93..2659ea8d 100644 --- a/src/py4vasp/_third_party/view/view.py +++ b/src/py4vasp/_third_party/view/view.py @@ -115,6 +115,9 @@ class View: show_axes_at: Sequence[float] = None """Defines where the axis is shown, defaults to the origin""" + def __post_init__(self): + self._verify() + def _ipython_display_(self): widget = self.to_ngl() widget._ipython_display_() @@ -143,6 +146,8 @@ def to_ngl(self): def _verify(self): self._raise_error_if_present_on_multiple_steps(self.grid_scalars) self._raise_error_if_present_on_multiple_steps(self.ion_arrows) + self._raise_error_if_number_steps_inconsistent() + self._raise_error_if_any_shape_is_incorrect() def _raise_error_if_present_on_multiple_steps(self, attributes): if not attributes: @@ -156,6 +161,34 @@ def _raise_error_if_present_on_multiple_steps(self, attributes): attribute is supplied with its corresponding grid scalar or ion arrow component.""" ) + def _raise_error_if_number_steps_inconsistent(self): + if len(self.elements) == len(self.lattice_vectors) == len(self.positions): + return + raise exception.IncorrectUsage( + "The shape of the arrays is inconsistent. Each of 'elements' (length = " + f"{len(self.elements)}), 'lattice_vectors' (length = " + f"{len(self.lattice_vectors)}), and 'positions' (length = " + f"{len(self.positions)}) should have a leading dimension of the number of" + "steps." + ) + + def _raise_error_if_any_shape_is_incorrect(self): + number_elements = len(self.elements[0]) + _, number_positions, vector_size = np.shape(self.positions) + if number_elements != number_positions: + raise exception.IncorrectUsage( + f"Number of elements ({number_elements}) inconsistent with number of positions ({number_positions})." + ) + if vector_size != 3: + raise exception.IncorrectUsage( + f"Positions must have three components and not {vector_size}." + ) + cell_shape = np.shape(self.lattice_vectors)[1:] + if any(length != 3 for length in cell_shape): + raise exception.IncorrectUsage( + f"Lattice vectors must be a 3x3 unit cell but have the shape {cell_shape}." + ) + def _create_atoms(self, step): symbols = "".join(self.elements[step]) atoms = ase.Atoms(symbols) diff --git a/src/py4vasp/calculation/_structure.py b/src/py4vasp/calculation/_structure.py index 81510c3d..530d6896 100644 --- a/src/py4vasp/calculation/_structure.py +++ b/src/py4vasp/calculation/_structure.py @@ -169,10 +169,12 @@ def to_view(self, supercell=None): {examples} """ make_3d = lambda array: array if array.ndim == 3 else array[np.newaxis] + positions = make_3d(self.positions()) + elements = np.tile(self._topology().elements(), (len(positions), 1)) return view.View( - elements=np.atleast_2d(self._topology().elements()), + elements=elements, lattice_vectors=make_3d(self.lattice_vectors()), - positions=make_3d(self.positions()), + positions=positions, supercell=self._parse_supercell(supercell), ) diff --git a/tests/third_party/view/test_view.py b/tests/third_party/view/test_view.py index cb1ed072..aa2fa5e5 100644 --- a/tests/third_party/view/test_view.py +++ b/tests/third_party/view/test_view.py @@ -1,6 +1,7 @@ # Copyright © VASP Software GmbH, # Licensed under the Apache License 2.0 (http://www.apache.org/licenses/LICENSE-2.0) +import copy import io import itertools from types import SimpleNamespace @@ -103,15 +104,14 @@ def view3d(request, not_core): @pytest.fixture -def view3d_fail(not_core): +def view_multiple_grid_scalars(not_core): inputs = base_input_view(is_structure=False) isosurface = Isosurface(isolevel=0.1, color="#2FB5AB", opacity=0.6) charge_grid_scalar = GridQuantity(np.random.rand(2, 12, 10, 8), "charge") potential_grid_scalar = GridQuantity(np.random.rand(2, 12, 10, 8), "potential") potential_grid_scalar.isosurfaces = [isosurface] grid_scalars = [charge_grid_scalar, potential_grid_scalar] - view = View(grid_scalars=grid_scalars, **inputs) - return view + return View(**inputs), grid_scalars @pytest.fixture(params=[True, False]) @@ -188,9 +188,11 @@ def test_isosurface(view3d): np.allclose(expected_data, output_data) -def test_fail_isosurface(view3d_fail): +def test_fail_isosurface(view_multiple_grid_scalars): + view, grid_scalars = view_multiple_grid_scalars with pytest.raises(exception.NotImplemented): - widget = view3d_fail.to_ngl() + view.grid_scalars = grid_scalars + widget = view.to_ngl() def test_ion_arrows(view_arrow): @@ -292,3 +294,41 @@ def test_showaxes_different_origin(is_structure, not_core): assert msg["args"][1][0][0] == "arrow" expected_origin = np.array([0.2, 0.2, 0.2]) @ transformation.T assert np.allclose(msg["args"][1][0][1], expected_origin) + + +def test_different_number_of_steps_raises_error(view): + too_many_elements = [element for element in view.elements] + [view.elements[0]] + with pytest.raises(exception.IncorrectUsage): + View(too_many_elements, view.lattice_vectors, view.positions) + with pytest.raises(exception.IncorrectUsage): + broken_view = copy.copy(view) + broken_view.elements = too_many_elements + broken_view.to_ngl() + # + too_many_cells = [cell for cell in view.lattice_vectors] + [view.lattice_vectors[0]] + with pytest.raises(exception.IncorrectUsage): + View(view.elements, too_many_cells, view.positions) + with pytest.raises(exception.IncorrectUsage): + broken_view = copy.copy(view) + broken_view.lattice_vectors = too_many_cells + broken_view.to_ngl() + # + too_many_positions = [position for position in view.positions] + [view.positions[0]] + with pytest.raises(exception.IncorrectUsage): + View(view.elements, view.lattice_vectors, too_many_positions) + with pytest.raises(exception.IncorrectUsage): + broken_view = copy.copy(view) + broken_view.positions = too_many_positions + broken_view.to_ngl() + + +def test_incorrect_shape_raises_error(view): + different_number_atoms = np.zeros((len(view.positions), 7, 3)) + with pytest.raises(exception.IncorrectUsage): + View(view.elements, view.lattice_vectors, different_number_atoms) + not_a_three_component_vector = np.array(view.positions)[:, :, :2] + with pytest.raises(exception.IncorrectUsage): + View(view.elements, view.lattice_vectors, not_a_three_component_vector) + incorrect_unit_cell = np.zeros((len(view.lattice_vectors), 2, 4)) + with pytest.raises(exception.IncorrectUsage): + View(view.elements, incorrect_unit_cell, view.positions)