-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Duckarray tests for constructors and properties #6903
base: main
Are you sure you want to change the base?
Changes from all commits
21696bf
f14ba29
90f9c41
aa4a457
9fa2eca
7994bad
c4a35f0
0efbbbb
73499b5
532f213
f44aafa
d651438
f84894a
0090db5
ef05c7d
20334d9
f7acc0f
e41a15b
1f095a1
2503af7
b229645
0723418
6c4ccb0
c4aa05a
fc97e90
31e577a
8d80212
7c43e91
b6a90df
a95b5c4
aa7caaa
6415be8
de25594
1b0f372
706ee54
caf6308
cd5aa70
08d72ed
0649d59
c32cb5a
440e0bd
f74a29c
d9346f8
75f584a
b94c84d
7a150f8
a6eecb8
dcb9fc0
0ee096d
53debb2
9b2c0a3
a6efbe1
5d679bf
50db3c3
8114d2a
6afc7c3
2f084d0
14349f2
6a658ef
d595cd6
2b0dcba
6b61900
b9535a1
c675f8d
6e7c538
e0ee7a6
3feef1c
2a70c38
6a18acf
835930c
0a5c487
d1184a4
12b5527
1f95318
9800db5
c43f35e
d1b541e
19d9d96
69e0624
c7f6677
396c2ba
ead706e
3cf9523
cd132c6
1c310b0
7f879b0
ff91be8
cb286ef
438f8a5
01814ff
0f1222e
a38a307
ea3d015
afa33ac
566627a
2e0c6bf
01599b7
259e1d5
527b17c
3437c3d
4866801
8019a20
cc75b46
566470b
b0e94f1
e57cd7b
33f63a7
11d41e3
71a37ba
1d98fec
a826249
f2cd8a1
21a5838
c747733
3f81995
a282686
ede0045
cbf408c
f5b9bdc
ed68dc2
1e4f18e
da2225f
37622c5
86377e6
5af49d8
8d0a8c3
50151a4
707aecb
aa0f9c3
4b51ce4
a984718
8711d46
f40879f
214084c
4c54984
e0dd10d
b8af1e6
2aba7bc
9c38519
8b89911
ab64e5e
f4dd250
626efdf
ff08473
5ab5a74
ec7f726
843217e
9d585ce
7dc832d
32cbdc2
a002d0b
52682bd
bfc3fe7
d23eaec
8e3c655
c07c690
d2b35c5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,145 @@ | ||
from abc import abstractmethod | ||
from typing import TYPE_CHECKING | ||
|
||
import hypothesis.extra.numpy as npst | ||
import hypothesis.strategies as st | ||
import numpy as np | ||
import numpy.testing as npt | ||
import pytest | ||
from hypothesis import given, note | ||
|
||
import xarray as xr | ||
import xarray.testing.strategies as xrst | ||
from xarray.core.types import T_DuckArray | ||
from xarray.testing.assertions import assert_identical | ||
|
||
if TYPE_CHECKING: | ||
from xarray.core.types import _DTypeLikeNested, _ShapeLike | ||
|
||
|
||
__all__ = [ | ||
"ConstructorTests", | ||
"ReduceTests", | ||
] | ||
|
||
|
||
class ArrayConstructorChecksMixin: | ||
"""Mixin for checking results of Variable/DataArray constructors.""" | ||
|
||
def check(self, var, arr): | ||
self.check_types(var, arr) | ||
self.check_values(var, arr) | ||
self.check_attributes(var, arr) | ||
|
||
def check_types(self, var, arr): | ||
# test type of wrapped array | ||
assert isinstance( | ||
var.data, type(arr) | ||
), f"found {type(var.data)}, expected {type(arr)}" | ||
|
||
def check_attributes(self, var, arr): | ||
# test ndarray attributes are exposed correctly | ||
assert var.ndim == arr.ndim | ||
assert var.shape == arr.shape | ||
assert var.dtype == arr.dtype | ||
assert var.size == arr.size | ||
|
||
def check_values(self, var, arr): | ||
# test coercion to numpy | ||
npt.assert_equal(var.to_numpy(), np.asarray(arr)) | ||
|
||
|
||
class ConstructorTests(ArrayConstructorChecksMixin): | ||
shapes = npst.array_shapes() | ||
dtypes = xrst.supported_dtypes() | ||
|
||
@staticmethod | ||
@abstractmethod | ||
def array_strategy_fn( | ||
*, shape: "_ShapeLike", dtype: "_DTypeLikeNested" | ||
) -> st.SearchStrategy[T_DuckArray]: | ||
# TODO can we just make this an attribute? | ||
... | ||
|
||
@given(st.data()) | ||
def test_construct_variable(self, data) -> None: | ||
shape = data.draw(self.shapes) | ||
dtype = data.draw(self.dtypes) | ||
arr = data.draw(self.array_strategy_fn(shape=shape, dtype=dtype)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You can't use |
||
|
||
dim_names = data.draw( | ||
xrst.dimension_names(min_dims=len(shape), max_dims=len(shape)) | ||
) | ||
var = xr.Variable(data=arr, dims=dim_names) | ||
|
||
self.check(var, arr) | ||
|
||
|
||
def is_real_floating(dtype): | ||
return np.issubdtype(dtype, np.number) and np.issubdtype(dtype, np.floating) | ||
|
||
|
||
class ReduceTests: | ||
dtypes = xrst.supported_dtypes() | ||
|
||
@staticmethod | ||
@abstractmethod | ||
def array_strategy_fn( | ||
*, shape: "_ShapeLike", dtype: "_DTypeLikeNested" | ||
) -> st.SearchStrategy[T_DuckArray]: | ||
# TODO can we just make this an attribute? | ||
... | ||
|
||
def check_reduce(self, var, op, dim, *args, **kwargs): | ||
actual = getattr(var, op)(dim=dim, *args, **kwargs) | ||
|
||
data = np.asarray(var.data) | ||
expected = getattr(var.copy(data=data), op)(*args, **kwargs) | ||
|
||
# create expected result (using nanmean because arrays with Nans will be generated) | ||
reduce_axes = tuple(var.get_axis_num(d) for d in dim) | ||
data = np.asarray(var.data) | ||
expected = getattr(var.copy(data=data), op)(*args, axis=reduce_axes, **kwargs) | ||
|
||
note(f"actual:\n{actual}") | ||
note(f"expected:\n{expected}") | ||
|
||
assert_identical(actual, expected) | ||
|
||
@pytest.mark.parametrize( | ||
"method, dtype_assumption", | ||
Check failure on line 110 in xarray/testing/duckarrays.py GitHub Actions / ubuntu-latest py3.10 all-but-dask
Check failure on line 110 in xarray/testing/duckarrays.py GitHub Actions / ubuntu-latest py3.11
Check failure on line 110 in xarray/testing/duckarrays.py GitHub Actions / ubuntu-latest py3.9
Check failure on line 110 in xarray/testing/duckarrays.py GitHub Actions / ubuntu-latest py3.9
Check failure on line 110 in xarray/testing/duckarrays.py GitHub Actions / ubuntu-latest py3.10 flaky
Check failure on line 110 in xarray/testing/duckarrays.py GitHub Actions / macos-latest py3.9
Check failure on line 110 in xarray/testing/duckarrays.py GitHub Actions / macos-latest py3.11
|
||
( | ||
("all", lambda x: True), # should work for any dtype | ||
("any", lambda x: True), # should work for any dtype | ||
# "cumprod", # not in array API | ||
# "cumsum", # not in array API | ||
("max", is_real_floating), # only in array API for real numeric dtypes | ||
# "median", # not in array API | ||
("min", is_real_floating), # only in array API for real numeric dtypes | ||
("prod", is_real_floating), # only in array API for real numeric dtypes | ||
# "std", # TypeError: std() got an unexpected keyword argument 'ddof' | ||
("sum", is_real_floating), # only in array API for real numeric dtypes | ||
# "var", # TypeError: std() got an unexpected keyword argument 'ddof' | ||
), | ||
) | ||
@given(st.data()) | ||
def test_reduce_variable(self, method, dtype_assumption, data): | ||
""" | ||
Test that the reduction applied to an xarray Variable is always equal | ||
to the same reduction applied to the underlying array. | ||
""" | ||
|
||
narrowed_dtypes = self.dtypes.filter(dtype_assumption) | ||
|
||
var = data.draw( | ||
xrst.variables( | ||
array_strategy_fn=self.array_strategy_fn, | ||
dims=xrst.dimension_names(min_dims=1), | ||
dtype=narrowed_dtypes, | ||
) | ||
) | ||
|
||
# specify arbitrary reduction along at least one dimension | ||
reduce_dims = data.draw(xrst.unique_subset_of(var.dims, min_size=1)) | ||
|
||
self.check_reduce(var, method, dim=reduce_dims) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
import warnings | ||
from contextlib import contextmanager | ||
|
||
|
||
@contextmanager | ||
def suppress_warning(category, message=""): | ||
with warnings.catch_warnings(): | ||
warnings.filterwarnings("ignore", category=category, message=message) | ||
|
||
yield |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
from typing import TYPE_CHECKING | ||
|
||
import hypothesis.strategies as st | ||
from hypothesis.extra.array_api import make_strategies_namespace | ||
|
||
from xarray.core.types import T_DuckArray | ||
from xarray.testing import duckarrays | ||
from xarray.testing.utils import suppress_warning | ||
from xarray.tests import _importorskip | ||
|
||
if TYPE_CHECKING: | ||
from xarray.core.types import _DTypeLikeNested, _ShapeLike | ||
|
||
|
||
# ignore the warning that the array_api is experimental raised by numpy | ||
with suppress_warning( | ||
UserWarning, "The numpy.array_api submodule is still experimental. See NEP 47." | ||
): | ||
_importorskip("numpy", "1.26.0") | ||
import numpy.array_api as nxp | ||
|
||
|
||
nxps = make_strategies_namespace(nxp) | ||
|
||
|
||
class TestConstructors(duckarrays.ConstructorTests): | ||
dtypes = nxps.scalar_dtypes() | ||
|
||
@staticmethod | ||
def array_strategy_fn( | ||
shape: "_ShapeLike", | ||
dtype: "_DTypeLikeNested", | ||
) -> st.SearchStrategy[T_DuckArray]: | ||
return nxps.arrays(shape=shape, dtype=dtype) | ||
|
||
|
||
class TestReductions(duckarrays.ReduceTests): | ||
dtypes = nxps.scalar_dtypes() | ||
|
||
@staticmethod | ||
def array_strategy_fn( | ||
shape: "_ShapeLike", | ||
dtype: "_DTypeLikeNested", | ||
) -> st.SearchStrategy[T_DuckArray]: | ||
return nxps.arrays(shape=shape, dtype=dtype) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
from typing import TYPE_CHECKING | ||
|
||
import hypothesis.extra.numpy as npst | ||
import hypothesis.strategies as st | ||
|
||
from xarray.core.types import T_DuckArray | ||
from xarray.testing import duckarrays | ||
from xarray.testing.strategies import supported_dtypes | ||
|
||
if TYPE_CHECKING: | ||
from xarray.core.types import _DTypeLikeNested, _ShapeLike | ||
|
||
|
||
class TestConstructors(duckarrays.ConstructorTests): | ||
dtypes = supported_dtypes() | ||
|
||
@staticmethod | ||
def array_strategy_fn( | ||
shape: "_ShapeLike", | ||
dtype: "_DTypeLikeNested", | ||
) -> st.SearchStrategy[T_DuckArray]: | ||
return npst.arrays(shape=shape, dtype=dtype) | ||
|
||
|
||
class TestReductions(duckarrays.ReduceTests): | ||
dtypes = supported_dtypes() | ||
|
||
@staticmethod | ||
def array_strategy_fn( | ||
shape: "_ShapeLike", | ||
dtype: "_DTypeLikeNested", | ||
) -> st.SearchStrategy[T_DuckArray]: | ||
return npst.arrays(shape=shape, dtype=dtype) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
from typing import TYPE_CHECKING | ||
|
||
import hypothesis.extra.numpy as npst | ||
import hypothesis.strategies as st | ||
import numpy as np | ||
import numpy.testing as npt | ||
import pytest | ||
|
||
import xarray.testing.strategies as xrst | ||
from xarray.testing import duckarrays | ||
from xarray.tests import _importorskip | ||
|
||
if TYPE_CHECKING: | ||
from xarray.core.types import _DTypeLikeNested, _ShapeLike | ||
|
||
|
||
_importorskip("sparse") | ||
import sparse | ||
|
||
|
||
@pytest.fixture(autouse=True) | ||
def disable_bottleneck(): | ||
from xarray import set_options | ||
|
||
with set_options(use_bottleneck=False): | ||
yield | ||
|
||
|
||
# sparse does not support float16 | ||
sparse_dtypes = xrst.supported_dtypes().filter( | ||
lambda dtype: (not np.issubdtype(dtype, np.float16)) | ||
) | ||
|
||
|
||
@st.composite | ||
def sparse_arrays_fn( | ||
draw: st.DrawFn, | ||
*, | ||
shape: "_ShapeLike", | ||
dtype: "_DTypeLikeNested", | ||
) -> sparse.COO: | ||
"""When called generates an arbitrary sparse.COO array of the given shape and dtype.""" | ||
np_arr = draw(npst.arrays(dtype, shape)) | ||
|
||
def to_sparse(arr: np.ndarray) -> sparse.COO: | ||
if arr.ndim == 0: | ||
return arr | ||
|
||
return sparse.COO.from_numpy(arr) | ||
|
||
return to_sparse(np_arr) | ||
|
||
|
||
class TestConstructors(duckarrays.ConstructorTests): | ||
dtypes = sparse_dtypes() | ||
|
||
@staticmethod | ||
def array_strategy_fn( | ||
shape: "_ShapeLike", | ||
dtype: "_DTypeLikeNested", | ||
) -> st.SearchStrategy[sparse.COO]: | ||
return sparse_arrays_fn | ||
|
||
def check_values(self, var, arr): | ||
npt.assert_equal(var.to_numpy(), arr.todense()) | ||
|
||
|
||
class TestReductions(duckarrays.ReduceTests): | ||
dtypes = nxps.scalar_dtypes() | ||
|
||
@staticmethod | ||
def array_strategy_fn( | ||
shape: "_ShapeLike", | ||
dtype: "_DTypeLikeNested", | ||
) -> st.SearchStrategy[T_DuckArray]: | ||
return nxps.arrays(shape=shape, dtype=dtype) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would like to just do this in the subclassed tests:
but then would I need to make
array_strategy_fn
an abstract property?