From 1532ec8a4d8933829d01e2877084461a47ed6c39 Mon Sep 17 00:00:00 2001 From: TomNicholas Date: Thu, 22 Aug 2024 17:01:54 -0400 Subject: [PATCH 01/11] add tests for cubed --- xarray_array_testing/tests/test_cubed.py | 77 ++++++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 xarray_array_testing/tests/test_cubed.py diff --git a/xarray_array_testing/tests/test_cubed.py b/xarray_array_testing/tests/test_cubed.py new file mode 100644 index 0000000..80b463f --- /dev/null +++ b/xarray_array_testing/tests/test_cubed.py @@ -0,0 +1,77 @@ +from typing import ContextManager +from contextlib import nullcontext + +import pytest +import hypothesis.strategies as st +from hypothesis import note +import numpy as np +import numpy.testing as npt + +from xarray_array_testing.base import DuckArrayTestMixin +from xarray_array_testing.creation import CreationTests +from xarray_array_testing.reduction import ReductionTests + +import cubed +import cubed.random + + +def cubed_random_array(shape: tuple[int], dtype: np.dtype) -> cubed.Array: + """ + Generates a random cubed array + + Supports integer and float dtypes. + """ + # TODO hypothesis doesn't like us using random inside strategies + rng = np.random.default_rng() + + if np.issubdtype(dtype, np.integer): + arr = rng.integers(low=0, high=+3, size=shape, dtype=dtype) + return cubed.from_array(arr) + else: + # TODO generate general chunking pattern + ca = cubed.random.random(size=shape, chunks=shape) + return cubed.array_api.astype(ca, dtype) + + +def random_cubed_arrays_fn( + *, shape: tuple[int, ...], dtype: np.dtype, +) -> st.SearchStrategy[cubed.Array]: + return st.builds(cubed_random_array, shape=st.just(shape), dtype=st.just(dtype)) + + +class CubedTestMixin(DuckArrayTestMixin): + @property + def xp(self) -> type[cubed.array_api]: + return cubed.array_api + + @property + def array_type(self) -> type[cubed.Array]: + return cubed.Array + + @staticmethod + def array_strategy_fn(*, shape, dtype) -> st.SearchStrategy[cubed.Array]: + return random_cubed_arrays_fn(shape=shape, dtype=dtype) + + @staticmethod + def assert_equal(a: cubed.Array, b: cubed.Array): + npt.assert_equal(a.compute(), b.compute()) + + + +class TestCreationCubed(CreationTests, CubedTestMixin): + pass + + +class TestReductionCubed(ReductionTests, CubedTestMixin): + @staticmethod + def expected_errors(op, **parameters) -> ContextManager: + var = parameters.get('variable') + + note(f"op = {op}") + note(f"dtype = {var.dtype}") + note(f"is_integer = {cubed.array_api.isdtype(var.dtype, 'integral')}") + + if op == 'mean' and cubed.array_api.isdtype(var.dtype, "integral") or var.dtype == np.dtype('float16'): + return pytest.raises(TypeError, match='Only real floating-point dtypes are allowed in mean') + else: + return nullcontext() From 9be6e3c2c94287cf030a8b29f756c4d45d15a616 Mon Sep 17 00:00:00 2001 From: TomNicholas Date: Thu, 22 Aug 2024 17:03:55 -0400 Subject: [PATCH 02/11] silence hypothesis health check warnings --- xarray_array_testing/creation.py | 3 ++- xarray_array_testing/reduction.py | 13 ++++++++++--- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/xarray_array_testing/creation.py b/xarray_array_testing/creation.py index 291e082..32435a5 100644 --- a/xarray_array_testing/creation.py +++ b/xarray_array_testing/creation.py @@ -1,11 +1,12 @@ import hypothesis.strategies as st import xarray.testing.strategies as xrst -from hypothesis import given +from hypothesis import given, settings, HealthCheck from xarray_array_testing.base import DuckArrayTestMixin class CreationTests(DuckArrayTestMixin): + @settings(suppress_health_check=[HealthCheck.differing_executors]) @given(st.data()) def test_create_variable(self, data): variable = data.draw(xrst.variables(array_strategy_fn=self.array_strategy_fn)) diff --git a/xarray_array_testing/reduction.py b/xarray_array_testing/reduction.py index 8e58949..6aa42ef 100644 --- a/xarray_array_testing/reduction.py +++ b/xarray_array_testing/reduction.py @@ -2,7 +2,7 @@ import hypothesis.strategies as st import xarray.testing.strategies as xrst -from hypothesis import given +from hypothesis import given, settings, HealthCheck, note from xarray_array_testing.base import DuckArrayTestMixin @@ -12,16 +12,22 @@ class ReductionTests(DuckArrayTestMixin): def expected_errors(op, **parameters): return nullcontext() + # TODO understand the differing executors health check error + @settings(suppress_health_check=[HealthCheck.differing_executors]) @given(st.data()) def test_variable_mean(self, data): variable = data.draw(xrst.variables(array_strategy_fn=self.array_strategy_fn)) + note(f"note: {variable}") + with self.expected_errors("mean", variable=variable): actual = variable.mean().data expected = self.xp.mean(variable.data) - self.assert_equal(actual, expected) + assert isinstance(actual, self.array_type), type(actual) + self.assert_equal(actual, expected) + @settings(suppress_health_check=[HealthCheck.differing_executors]) @given(st.data()) def test_variable_prod(self, data): variable = data.draw(xrst.variables(array_strategy_fn=self.array_strategy_fn)) @@ -30,4 +36,5 @@ def test_variable_prod(self, data): actual = variable.prod().data expected = self.xp.prod(variable.data) - self.assert_equal(actual, expected) + assert isinstance(actual, self.array_type), type(actual) + self.assert_equal(actual, expected) From df26fc763da78e23e049af3fa21c594effda6224 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 22 Aug 2024 21:12:12 +0000 Subject: [PATCH 03/11] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- xarray_array_testing/creation.py | 2 +- xarray_array_testing/reduction.py | 2 +- xarray_array_testing/tests/test_cubed.py | 32 ++++++++++++++---------- 3 files changed, 21 insertions(+), 15 deletions(-) diff --git a/xarray_array_testing/creation.py b/xarray_array_testing/creation.py index 32435a5..ebcd1be 100644 --- a/xarray_array_testing/creation.py +++ b/xarray_array_testing/creation.py @@ -1,6 +1,6 @@ import hypothesis.strategies as st import xarray.testing.strategies as xrst -from hypothesis import given, settings, HealthCheck +from hypothesis import HealthCheck, given, settings from xarray_array_testing.base import DuckArrayTestMixin diff --git a/xarray_array_testing/reduction.py b/xarray_array_testing/reduction.py index 6aa42ef..9e1b186 100644 --- a/xarray_array_testing/reduction.py +++ b/xarray_array_testing/reduction.py @@ -2,7 +2,7 @@ import hypothesis.strategies as st import xarray.testing.strategies as xrst -from hypothesis import given, settings, HealthCheck, note +from hypothesis import HealthCheck, given, note, settings from xarray_array_testing.base import DuckArrayTestMixin diff --git a/xarray_array_testing/tests/test_cubed.py b/xarray_array_testing/tests/test_cubed.py index 80b463f..b2f9da5 100644 --- a/xarray_array_testing/tests/test_cubed.py +++ b/xarray_array_testing/tests/test_cubed.py @@ -1,29 +1,28 @@ -from typing import ContextManager from contextlib import nullcontext +from typing import ContextManager -import pytest +import cubed +import cubed.random import hypothesis.strategies as st -from hypothesis import note import numpy as np import numpy.testing as npt +import pytest +from hypothesis import note from xarray_array_testing.base import DuckArrayTestMixin from xarray_array_testing.creation import CreationTests from xarray_array_testing.reduction import ReductionTests -import cubed -import cubed.random - def cubed_random_array(shape: tuple[int], dtype: np.dtype) -> cubed.Array: """ Generates a random cubed array - + Supports integer and float dtypes. """ # TODO hypothesis doesn't like us using random inside strategies rng = np.random.default_rng() - + if np.issubdtype(dtype, np.integer): arr = rng.integers(low=0, high=+3, size=shape, dtype=dtype) return cubed.from_array(arr) @@ -34,7 +33,9 @@ def cubed_random_array(shape: tuple[int], dtype: np.dtype) -> cubed.Array: def random_cubed_arrays_fn( - *, shape: tuple[int, ...], dtype: np.dtype, + *, + shape: tuple[int, ...], + dtype: np.dtype, ) -> st.SearchStrategy[cubed.Array]: return st.builds(cubed_random_array, shape=st.just(shape), dtype=st.just(dtype)) @@ -57,7 +58,6 @@ def assert_equal(a: cubed.Array, b: cubed.Array): npt.assert_equal(a.compute(), b.compute()) - class TestCreationCubed(CreationTests, CubedTestMixin): pass @@ -65,13 +65,19 @@ class TestCreationCubed(CreationTests, CubedTestMixin): class TestReductionCubed(ReductionTests, CubedTestMixin): @staticmethod def expected_errors(op, **parameters) -> ContextManager: - var = parameters.get('variable') + var = parameters.get("variable") note(f"op = {op}") note(f"dtype = {var.dtype}") note(f"is_integer = {cubed.array_api.isdtype(var.dtype, 'integral')}") - if op == 'mean' and cubed.array_api.isdtype(var.dtype, "integral") or var.dtype == np.dtype('float16'): - return pytest.raises(TypeError, match='Only real floating-point dtypes are allowed in mean') + if ( + op == "mean" + and cubed.array_api.isdtype(var.dtype, "integral") + or var.dtype == np.dtype("float16") + ): + return pytest.raises( + TypeError, match="Only real floating-point dtypes are allowed in mean" + ) else: return nullcontext() From 72fd4bf3b50dea5efab8d6b83158cb965f632992 Mon Sep 17 00:00:00 2001 From: Justus Magin Date: Fri, 23 Aug 2024 14:36:21 +0200 Subject: [PATCH 04/11] install `cubed` and `cubed-xarray` in ci --- ci/requirements/environment.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ci/requirements/environment.yaml b/ci/requirements/environment.yaml index 51bdd94..6998d45 100644 --- a/ci/requirements/environment.yaml +++ b/ci/requirements/environment.yaml @@ -10,3 +10,5 @@ dependencies: - hypothesis - xarray - numpy + - cubed + - cubed-xarray From 376181c5a50a096c29e91194445bc4ac336cd9bc Mon Sep 17 00:00:00 2001 From: Justus Magin Date: Wed, 28 Aug 2024 11:59:11 +0200 Subject: [PATCH 05/11] actually pass in the right op --- xarray_array_testing/reduction.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xarray_array_testing/reduction.py b/xarray_array_testing/reduction.py index 7528773..2b667bc 100644 --- a/xarray_array_testing/reduction.py +++ b/xarray_array_testing/reduction.py @@ -22,7 +22,7 @@ def test_variable_numerical_reduce(self, op, data): note(f"note: {variable}") - with self.expected_errors("mean", variable=variable): + with self.expected_errors(op, variable=variable): # compute using xr.Variable.() actual = getattr(variable, op)().data # compute using xp.(array) From 4610b309a16b1e2aa7e67d8ff1dc3d01a91e1eeb Mon Sep 17 00:00:00 2001 From: Justus Magin Date: Wed, 28 Aug 2024 11:59:23 +0200 Subject: [PATCH 06/11] create a more informative error message --- xarray_array_testing/reduction.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/xarray_array_testing/reduction.py b/xarray_array_testing/reduction.py index 2b667bc..f3a3fb0 100644 --- a/xarray_array_testing/reduction.py +++ b/xarray_array_testing/reduction.py @@ -28,5 +28,7 @@ def test_variable_numerical_reduce(self, op, data): # compute using xp.(array) expected = getattr(self.xp, op)(variable.data) - assert isinstance(actual, self.array_type), type(actual) + assert isinstance( + actual, self.array_type + ), f"expected {self.array_type} but got {type(actual)}" self.assert_equal(actual, expected) From 77f08c8369a26fd52e135d61dbea78406ab613c5 Mon Sep 17 00:00:00 2001 From: Justus Magin Date: Wed, 28 Aug 2024 12:00:41 +0200 Subject: [PATCH 07/11] use the proper import for `ContextManager` --- xarray_array_testing/tests/test_cubed.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/xarray_array_testing/tests/test_cubed.py b/xarray_array_testing/tests/test_cubed.py index b2f9da5..8247194 100644 --- a/xarray_array_testing/tests/test_cubed.py +++ b/xarray_array_testing/tests/test_cubed.py @@ -1,5 +1,4 @@ -from contextlib import nullcontext -from typing import ContextManager +from contextlib import ContextManager, nullcontext import cubed import cubed.random From 6d77f30e617a7b0b7ae223753320610292e15eb6 Mon Sep 17 00:00:00 2001 From: Justus Magin Date: Wed, 28 Aug 2024 12:01:04 +0200 Subject: [PATCH 08/11] simplify the dtype check --- xarray_array_testing/tests/test_cubed.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/xarray_array_testing/tests/test_cubed.py b/xarray_array_testing/tests/test_cubed.py index 8247194..bf36842 100644 --- a/xarray_array_testing/tests/test_cubed.py +++ b/xarray_array_testing/tests/test_cubed.py @@ -66,14 +66,14 @@ class TestReductionCubed(ReductionTests, CubedTestMixin): def expected_errors(op, **parameters) -> ContextManager: var = parameters.get("variable") + xp = cubed.array_api + note(f"op = {op}") note(f"dtype = {var.dtype}") note(f"is_integer = {cubed.array_api.isdtype(var.dtype, 'integral')}") - if ( - op == "mean" - and cubed.array_api.isdtype(var.dtype, "integral") - or var.dtype == np.dtype("float16") + if op == "mean" and xp.isdtype( + var.dtype, ("integral", "complex floating", np.dtype("float16")) ): return pytest.raises( TypeError, match="Only real floating-point dtypes are allowed in mean" From 3b166943dec7318fad46b0192ef28ef1797741d1 Mon Sep 17 00:00:00 2001 From: Justus Magin Date: Wed, 28 Aug 2024 12:01:20 +0200 Subject: [PATCH 09/11] skip `var` and `std` --- xarray_array_testing/tests/test_cubed.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/xarray_array_testing/tests/test_cubed.py b/xarray_array_testing/tests/test_cubed.py index bf36842..6a7d89f 100644 --- a/xarray_array_testing/tests/test_cubed.py +++ b/xarray_array_testing/tests/test_cubed.py @@ -78,5 +78,7 @@ def expected_errors(op, **parameters) -> ContextManager: return pytest.raises( TypeError, match="Only real floating-point dtypes are allowed in mean" ) + elif op in {"var", "std"}: + pytest.skip(reason=f"cubed does not implement {op} yet") else: return nullcontext() From f97633dc47ce549d955231a7fcbcd63f421aa441 Mon Sep 17 00:00:00 2001 From: Justus Magin Date: Wed, 28 Aug 2024 12:01:36 +0200 Subject: [PATCH 10/11] expect an error for `float16` That dtype is currently not part of the spec. --- xarray_array_testing/tests/test_cubed.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/xarray_array_testing/tests/test_cubed.py b/xarray_array_testing/tests/test_cubed.py index 6a7d89f..a7ab65f 100644 --- a/xarray_array_testing/tests/test_cubed.py +++ b/xarray_array_testing/tests/test_cubed.py @@ -78,6 +78,10 @@ def expected_errors(op, **parameters) -> ContextManager: return pytest.raises( TypeError, match="Only real floating-point dtypes are allowed in mean" ) + elif xp.isdtype(var.dtype, np.dtype("float16")): + return pytest.raises( + TypeError, match="Only numeric dtypes are allowed in isnan" + ) elif op in {"var", "std"}: pytest.skip(reason=f"cubed does not implement {op} yet") else: From 3e66118809eaa74b3b9d287116b06f090e6ebdfc Mon Sep 17 00:00:00 2001 From: Justus Magin Date: Wed, 28 Aug 2024 12:06:52 +0200 Subject: [PATCH 11/11] change the target version to 3.11 --- pyproject.toml | 2 +- xarray_array_testing/tests/test_cubed.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 2d387a6..cb66373 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -28,7 +28,7 @@ exclude = [ "__pycache__", "docs", ] -target-version = "py312" +target-version = "py311" extend-include = ["*.ipynb"] line-length = 100 diff --git a/xarray_array_testing/tests/test_cubed.py b/xarray_array_testing/tests/test_cubed.py index a7ab65f..066f05e 100644 --- a/xarray_array_testing/tests/test_cubed.py +++ b/xarray_array_testing/tests/test_cubed.py @@ -1,4 +1,5 @@ -from contextlib import ContextManager, nullcontext +from contextlib import AbstractContextManager as ContextManager +from contextlib import nullcontext import cubed import cubed.random