From 3ab271d0d6da02b4d86cdf1b38cfb973ec5f7184 Mon Sep 17 00:00:00 2001 From: Jaewook Lee Date: Wed, 10 Apr 2024 06:41:21 +0200 Subject: [PATCH 01/11] add update --- gustaf/helpers/data.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/gustaf/helpers/data.py b/gustaf/helpers/data.py index 1d01f55b..391f087d 100644 --- a/gustaf/helpers/data.py +++ b/gustaf/helpers/data.py @@ -318,6 +318,20 @@ def items(self): """ return self._saved.items() + def update(self, **kwargs): + """ + Updates given kwargs using __setitem__. + + Parameters + ---------- + kwargs: **kwargs + + Returns + ------- + None + """ + self._saved.update(**kwargs) + class ComputedData(DataHolder): _depends = None From 3c4cde5c6375e42a58e985f546699d5d1c4879d9 Mon Sep 17 00:00:00 2001 From: Jaewook Lee Date: Wed, 10 Apr 2024 06:41:42 +0200 Subject: [PATCH 02/11] bump version to 0.0.25 --- gustaf/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gustaf/_version.py b/gustaf/_version.py index 2a1e88ff..4d3475fb 100644 --- a/gustaf/_version.py +++ b/gustaf/_version.py @@ -3,4 +3,4 @@ Current version. """ -version = "0.0.24" +version = "0.0.25" From d82f4981a0522c1ae5f734f47f31f24b7755bb20 Mon Sep 17 00:00:00 2001 From: Jaewook Lee Date: Wed, 10 Apr 2024 06:48:27 +0200 Subject: [PATCH 03/11] fix doc --- gustaf/helpers/data.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gustaf/helpers/data.py b/gustaf/helpers/data.py index 391f087d..4f64f1b2 100644 --- a/gustaf/helpers/data.py +++ b/gustaf/helpers/data.py @@ -324,7 +324,7 @@ def update(self, **kwargs): Parameters ---------- - kwargs: **kwargs + **kwargs: kwargs Returns ------- From 7854523f8b5727e58a7a62d1688562518880248f Mon Sep 17 00:00:00 2001 From: Jaewook Lee Date: Tue, 23 Apr 2024 12:05:25 +0200 Subject: [PATCH 04/11] add dataholder test --- gustaf/helpers/data.py | 14 +++++ tests/test_helpers/test_dataholder.py | 81 +++++++++++++++++++++++++++ 2 files changed, 95 insertions(+) create mode 100644 tests/test_helpers/test_dataholder.py diff --git a/gustaf/helpers/data.py b/gustaf/helpers/data.py index 4f64f1b2..2b879ddf 100644 --- a/gustaf/helpers/data.py +++ b/gustaf/helpers/data.py @@ -251,6 +251,20 @@ def __contains__(self, key): """ return key in self._saved + def __len__(self): + """ + Returns number of items. + + Parameters + ---------- + None + + Returns + ------- + len: int + """ + return len(self._saved) + def pop(self, key, default=None): """ Applied pop() to saved data diff --git a/tests/test_helpers/test_dataholder.py b/tests/test_helpers/test_dataholder.py new file mode 100644 index 00000000..3c077261 --- /dev/null +++ b/tests/test_helpers/test_dataholder.py @@ -0,0 +1,81 @@ +import pytest + +import gustaf + + +def test_DataHolder(): + """Base class of dataholder types""" + + class Helpee: + pass + + helpee = Helpee() + + dataholder = gustaf.helpers.data.DataHolder(helpee) + + # setitem is pure abstract + with pytest.raises(NotImplementedError): + dataholder["somedata"] = [] + + # test other functions by injecting some keys and values directly to the + # member + dataholder._saved.update(a=1, b=2, c=3) + + # getitem + assert dataholder["a"] == 1 + assert dataholder["b"] == 2 + assert dataholder["c"] == 3 + with pytest.raises(KeyError): + dataholder["d"] + + # contains + assert "a" in dataholder + assert "b" in dataholder + assert "c" in dataholder + assert "d" not in dataholder + assert "e" not in dataholder + + # len + assert len(dataholder) == 3 + + # pop + assert dataholder.pop("c") == 33 + assert "c" not in dataholder + + # get + # 1. key + assert dataholder.get("a") == 1 + # 2. key and default + assert dataholder.get("b", 2) == 2 + # 3. key and wrong default + assert dataholder.get("b", 3) == 2 + # 4. empty key - always None + assert dataholder.get("c") is None + # 5. empty key and default + assert dataholder.get("c", "123") == "123" + + # keys + assert len(set(dataholder.keys()).difference({"a", "b"})) == 0 + + # values + assert len(set(dataholder.values()).difference({1, 2})) == 0 + + # items + for k, v in dataholder.items(): + assert k in dataholder.keys() # noqa SIM118 + assert v in dataholder.values() + + # update + dataholder.update(b=22, c=33, d=44) + assert dataholder["a"] == 1 + assert dataholder["b"] == 22 + assert dataholder["c"] == 33 + assert dataholder["d"] == 44 + + # clear + dataholder.clear() + assert "a" not in dataholder + assert "b" not in dataholder + assert "c" not in dataholder + assert "d" not in dataholder + assert len(dataholder) == 0 From d1e1cfd702e01cb6a4bfa09e1f3e0f9a938c633c Mon Sep 17 00:00:00 2001 From: Jaewook Lee Date: Tue, 23 Apr 2024 12:26:48 +0200 Subject: [PATCH 05/11] add tracked array test --- tests/test_helpers/test_dataholder.py | 105 +++++++++++++++++++++++++- 1 file changed, 104 insertions(+), 1 deletion(-) diff --git a/tests/test_helpers/test_dataholder.py b/tests/test_helpers/test_dataholder.py index 3c077261..31002a71 100644 --- a/tests/test_helpers/test_dataholder.py +++ b/tests/test_helpers/test_dataholder.py @@ -1,8 +1,107 @@ +import numpy as np import pytest import gustaf +def new_tracked_array(dtype=float): + """ + create new tracked array and checks if default flags are set correctly. + Then sets modified to False, to give an easy start for testing + """ + ta = gustaf.helpers.data.make_tracked_array( + [ + [0, 1, 2], + [3, 4, 5], + [6, 7, 8], + ], + dtype=dtype, + ) + + assert ta.modified + assert ta._super_arr + + ta.modified = False + + return ta + + +def test_TrackedArray(): + """test if modified flag is well set""" + # 1. set item + ta = new_tracked_array() + ta[0] = 1 + assert ta.modified + + ta = new_tracked_array() + ta[1, 1] = 2 + assert ta.modified + + # in place + ta = new_tracked_array() + ta += 5 + assert ta.modified + + ta = new_tracked_array() + ta -= 3 + assert ta.modified + + ta = new_tracked_array() + ta *= 1 + assert ta.modified + + ta = new_tracked_array() + ta /= 1.5 + assert ta.modified + + ta = new_tracked_array() + ta @= ta + assert ta.modified + + ta = new_tracked_array() + ta **= 2 + assert ta.modified + + ta = new_tracked_array() + ta %= 3 + assert ta.modified + + ta = new_tracked_array() + ta //= 2 + assert ta.modified + + ta = new_tracked_array(int) + ta <<= 3 + assert ta.modified + + ta = new_tracked_array(int) + ta >>= 1 + assert ta.modified + + ta = new_tracked_array(int) + ta |= 3 + assert ta.modified + + ta = new_tracked_array(int) + ta &= 3 + assert ta.modified + + ta = new_tracked_array(int) + ta ^= 3 + assert ta.modified + + # child array modification + ta = new_tracked_array() + ta_child = ta[0] + assert ta_child.base is ta + ta_child += 5 + assert ta.modified + assert ta_child.modified + + # copy returns normal np.ndarray + assert isinstance(new_tracked_array().copy(), np.ndarray) + + def test_DataHolder(): """Base class of dataholder types""" @@ -39,7 +138,7 @@ class Helpee: assert len(dataholder) == 3 # pop - assert dataholder.pop("c") == 33 + assert dataholder.pop("c") == 3 assert "c" not in dataholder # get @@ -79,3 +178,7 @@ class Helpee: assert "c" not in dataholder assert "d" not in dataholder assert len(dataholder) == 0 + + +def test_ComputedData(): + pass From 26091cdde96084507dcdcf9789461fdb4c5839de Mon Sep 17 00:00:00 2001 From: Jaewook Lee Date: Tue, 23 Apr 2024 14:13:11 +0200 Subject: [PATCH 06/11] add ComputedData test --- tests/test_helpers/test_dataholder.py | 70 ++++++++++++++++++++++++++- 1 file changed, 68 insertions(+), 2 deletions(-) diff --git a/tests/test_helpers/test_dataholder.py b/tests/test_helpers/test_dataholder.py index 31002a71..73b28acc 100644 --- a/tests/test_helpers/test_dataholder.py +++ b/tests/test_helpers/test_dataholder.py @@ -180,5 +180,71 @@ class Helpee: assert len(dataholder) == 0 -def test_ComputedData(): - pass +@pytest.mark.parametrize( + "grid", ("edges", "faces_tri", "faces_quad", "volumes_tet", "volumes_hexa") +) +def test_ComputedData(grid, request): + grid = request.getfixturevalue(grid) + + # vertex related data + v_data = ( + "unique_vertices", + "bounds", + "bounds_diagonal", + "bounds_diagonal_norm", + ) + + # element related data + e_data = ( + "sorted_edges", + "unique_edges", + "single_edges", + "edges", + "sorted_faces", + "unique_faces", + "single_faces", + "faces", + "sorted_volumes", + "unique_volumes", + ) + + # for both + both_data = ("centers", "referenced_vertices") + + # entities before modification + data_dependency = {"vertex": v_data, "element": e_data, "both": both_data} + before = {} + for dependency, attributes in data_dependency.items(): + # init + before[dependency] = {} + for attr in attributes: + func = getattr(grid, attr, None) + if attr is not None and callable(func): + before[dependency][attr] = func() + + # ensure that func is called at least once + assert len(before[dependency]) != 0 + + # loop to check if you get the saved data + for attributes in before.values(): + for attr, value in attributes.items(): + func = getattr(grid, attr, None) + assert value is func() + + # change vertices - assign new vertices + grid.vertices = grid.vertices.copy() + for dependency, attributes in before.items(): + if dependency == "element": + continue + for attr, value in attributes.items(): + func = getattr(grid, attr, None) + assert value is not func() # should be different object + + # change elements - assign new elements + grid.elements = grid.elements.copy() + for dependency, attributes in before.items(): + if dependency == "vertex": + continue + for attr, value in attributes.items(): + func = getattr(grid, attr, None) + assert value is not func() From dfba6e152108279f0ea0ac5a7e134bf8d2b8ee9e Mon Sep 17 00:00:00 2001 From: Jaewook Lee Date: Tue, 23 Apr 2024 15:00:19 +0200 Subject: [PATCH 07/11] add test VertexData --- .../{test_dataholder.py => test_data.py} | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) rename tests/test_helpers/{test_dataholder.py => test_data.py} (77%) diff --git a/tests/test_helpers/test_dataholder.py b/tests/test_helpers/test_data.py similarity index 77% rename from tests/test_helpers/test_dataholder.py rename to tests/test_helpers/test_data.py index 73b28acc..46da9f12 100644 --- a/tests/test_helpers/test_dataholder.py +++ b/tests/test_helpers/test_data.py @@ -248,3 +248,51 @@ def test_ComputedData(grid, request): for attr, value in attributes.items(): func = getattr(grid, attr, None) assert value is not func() + + +@pytest.mark.parametrize( + "grid", ("edges", "faces_tri", "faces_quad", "volumes_tet", "volumes_hexa") +) +def test_VertexData(grid, request): + grid = request.getfixturevalue(grid) + + key = "vertices" + + # set data + grid.vertex_data[key] = grid.vertices + + # get_data - data is viewed as TrackedArray, so check against base + assert grid.vertices is grid.vertex_data[key].base + + # scalar extraction should return a norm + assert np.allclose( + grid.vertex_data.as_scalar(key).ravel(), + np.linalg.norm(grid.vertex_data.get(key), axis=1), + ) + + # norms should be saved, as long as data array isn't changed + assert grid.vertex_data.as_scalar(key) is grid.vertex_data.as_scalar(key) + + before = grid.vertex_data.as_scalar(key) + # trigger modified flag on data - either reset or inplace change + # reset first - with copy, just so that we can try to make inplace changes + # later + grid.vertex_data[key] = grid.vertex_data[key].copy() + assert before is not grid.vertex_data.as_scalar(key) + assert grid.vertex_data.as_scalar(key) is grid.vertex_data.as_scalar(key) + + grid.vertex_data[key][0] = grid.vertex_data[key][0] + assert before is not grid.vertex_data.as_scalar(key) + assert grid.vertex_data.as_scalar(key) is grid.vertex_data.as_scalar(key) + + # check arrow data + assert grid.vertex_data[key] is grid.vertex_data.as_arrow(key) + + # check wrong length assignment + with pytest.raises(ValueError): + grid.vertex_data["bad"] = np.vstack((grid.vertices, grid.vertices)) + + # check wrong arrow data request + with pytest.raises(ValueError): + grid.vertex_data["norm"] = grid.vertex_data.as_scalar(key) + grid.vertex_data.as_arrow("norm") From 3621a793fe399c3729360d8c14011e6c7d4459ca Mon Sep 17 00:00:00 2001 From: Jaewook Lee Date: Tue, 23 Apr 2024 15:13:43 +0200 Subject: [PATCH 08/11] add version check before @= --- tests/test_helpers/test_data.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/tests/test_helpers/test_data.py b/tests/test_helpers/test_data.py index 46da9f12..c1bdb6e5 100644 --- a/tests/test_helpers/test_data.py +++ b/tests/test_helpers/test_data.py @@ -1,3 +1,5 @@ +import sys + import numpy as np import pytest @@ -54,9 +56,11 @@ def test_TrackedArray(): ta /= 1.5 assert ta.modified - ta = new_tracked_array() - ta @= ta - assert ta.modified + # old distributions of numpy does not have this feature + if sys.version_info > (3, 9): + ta = new_tracked_array() + ta @= ta + assert ta.modified ta = new_tracked_array() ta **= 2 From 7090186b5daeeb3f8d03413c4b1619698057c5c3 Mon Sep 17 00:00:00 2001 From: Jaewook Lee Date: Tue, 23 Apr 2024 18:51:18 +0200 Subject: [PATCH 09/11] fname then mesh for io --- gustaf/io/meshio.py | 9 +++++---- gustaf/io/mfem.py | 4 ++-- gustaf/io/mixd.py | 2 +- gustaf/io/nutils.py | 4 ++-- 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/gustaf/io/meshio.py b/gustaf/io/meshio.py index ad66e6eb..c2706ae4 100644 --- a/gustaf/io/meshio.py +++ b/gustaf/io/meshio.py @@ -84,7 +84,7 @@ def load(fname): return meshes[0] if len(meshes) == 1 else meshes -def export(mesh, fname, submeshes=None, **kwargs): +def export(fname, mesh, submeshes=None, **kwargs): """Export mesh elements and vertex data into meshio and use its write function. The definition of submeshes with identical vertex coordinates is possible. In that case vertex numbering and data from the main mesh @@ -132,10 +132,10 @@ def export(mesh, fname, submeshes=None, **kwargs): Parameters ------------ - mesh: Edges, Faces or Volumes - Input mesh fname: Union[str, pathlib.Path] File to save the mesh in. + mesh: Edges, Faces or Volumes + Input mesh submeshes: Iterable Submeshes where the vertices are identical to the main mesh. The element type can be identical to mesh.elements or lower-dimensional (e.g. @@ -164,7 +164,8 @@ def export(mesh, fname, submeshes=None, **kwargs): cells = [] # Merge main mesh and submeshes in one list - meshes = [mesh] + meshes = mesh if isinstance(mesh, list) else [mesh] + if submeshes is not None: meshes.extend(submeshes) diff --git a/gustaf/io/mfem.py b/gustaf/io/mfem.py index 1b38ac71..c4ca9e89 100644 --- a/gustaf/io/mfem.py +++ b/gustaf/io/mfem.py @@ -107,15 +107,15 @@ def extract_values(fname, start_index, n_lines, total_lines, dtype): return mesh -def export(mesh, fname): +def export(fname, mesh): """Export mesh in MFEM format. Supports 2D triangle and quadrilateral meshes. Does not support different element attributes or difference in vertex dimension and mesh dimension. Parameters ------------ - mesh: Faces fname: str + mesh: Faces Returns ------------ diff --git a/gustaf/io/mixd.py b/gustaf/io/mixd.py index 60025d8d..e873e54c 100644 --- a/gustaf/io/mixd.py +++ b/gustaf/io/mixd.py @@ -116,8 +116,8 @@ def load( def export( - mesh, fname, + mesh, space_time=False, dual=False, ): diff --git a/gustaf/io/nutils.py b/gustaf/io/nutils.py index b5915c46..f12d359e 100644 --- a/gustaf/io/nutils.py +++ b/gustaf/io/nutils.py @@ -61,14 +61,14 @@ def load(fname): return mesh -def export(mesh, fname): +def export(fname, mesh): """Export in Nutils format. Files are saved as np.savez(). Supports triangle,and tetrahedron Meshes. Parameters ----------- - mesh: Faces or Volumes fname: str + mesh: Faces or Volumes Returns -------- From eda01336db72b8eb37123d00bbcc4d30920855f2 Mon Sep 17 00:00:00 2001 From: Jaewook Lee Date: Fri, 3 May 2024 09:42:04 +0200 Subject: [PATCH 10/11] separate macos13/14 and rename deploy.yml --- .github/workflows/{build_and_upload_wheels.yml => deploy.yml} | 0 .github/workflows/tests.yml | 4 ++-- 2 files changed, 2 insertions(+), 2 deletions(-) rename .github/workflows/{build_and_upload_wheels.yml => deploy.yml} (100%) diff --git a/.github/workflows/build_and_upload_wheels.yml b/.github/workflows/deploy.yml similarity index 100% rename from .github/workflows/build_and_upload_wheels.yml rename to .github/workflows/deploy.yml diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index e632d054..69b5d5b6 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -10,8 +10,8 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - python-version: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12"] - os: [ubuntu-20.04, macos-latest] + python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] + os: [ubuntu-20.04, macos-13, macos-14, windows-latest] steps: - uses: actions/checkout@v3 From 834036201532e97725342923d6386316bad97e75 Mon Sep 17 00:00:00 2001 From: Jaewook Lee Date: Fri, 3 May 2024 09:42:20 +0200 Subject: [PATCH 11/11] remove py3.7 --- pyproject.toml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 466cffad..6dda0e80 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,13 +10,12 @@ keywords = [ "visualization", "mesh", ] -requires-python = ">=3.7" +requires-python = ">=3.8" license = {file = "LICENSE.txt"} classifiers = [ "Development Status :: 3 - Alpha", "License :: OSI Approved :: MIT License", "Programming Language :: Python", - "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", @@ -81,7 +80,7 @@ version = {attr = "gustaf._version.version"} [tool.ruff] line-length = 79 -target-version = "py37" +target-version = "py38" [tool.ruff.lint] select = [ @@ -107,7 +106,6 @@ ignore = [ "PLR0913", # Too many arguments to function call "PLR0915", # Too many statements "B904", # Within an `except` clause, raise exceptions with ... - # "PLR0911", # Too many return statements ] [tool.ruff.lint.per-file-ignores]