123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375 |
- import operator
- from numpy.testing import assert_raises
- import numpy as np
- import pytest
- from .. import ones, asarray, reshape, result_type, all, equal
- from .._array_object import Array
- from .._dtypes import (
- _all_dtypes,
- _boolean_dtypes,
- _floating_dtypes,
- _integer_dtypes,
- _integer_or_boolean_dtypes,
- _numeric_dtypes,
- int8,
- int16,
- int32,
- int64,
- uint64,
- bool as bool_,
- )
- def test_validate_index():
- # The indexing tests in the official array API test suite test that the
- # array object correctly handles the subset of indices that are required
- # by the spec. But the NumPy array API implementation specifically
- # disallows any index not required by the spec, via Array._validate_index.
- # This test focuses on testing that non-valid indices are correctly
- # rejected. See
- # https://data-apis.org/array-api/latest/API_specification/indexing.html
- # and the docstring of Array._validate_index for the exact indexing
- # behavior that should be allowed. This does not test indices that are
- # already invalid in NumPy itself because Array will generally just pass
- # such indices directly to the underlying np.ndarray.
- a = ones((3, 4))
- # Out of bounds slices are not allowed
- assert_raises(IndexError, lambda: a[:4])
- assert_raises(IndexError, lambda: a[:-4])
- assert_raises(IndexError, lambda: a[:3:-1])
- assert_raises(IndexError, lambda: a[:-5:-1])
- assert_raises(IndexError, lambda: a[4:])
- assert_raises(IndexError, lambda: a[-4:])
- assert_raises(IndexError, lambda: a[4::-1])
- assert_raises(IndexError, lambda: a[-4::-1])
- assert_raises(IndexError, lambda: a[...,:5])
- assert_raises(IndexError, lambda: a[...,:-5])
- assert_raises(IndexError, lambda: a[...,:5:-1])
- assert_raises(IndexError, lambda: a[...,:-6:-1])
- assert_raises(IndexError, lambda: a[...,5:])
- assert_raises(IndexError, lambda: a[...,-5:])
- assert_raises(IndexError, lambda: a[...,5::-1])
- assert_raises(IndexError, lambda: a[...,-5::-1])
- # Boolean indices cannot be part of a larger tuple index
- assert_raises(IndexError, lambda: a[a[:,0]==1,0])
- assert_raises(IndexError, lambda: a[a[:,0]==1,...])
- assert_raises(IndexError, lambda: a[..., a[0]==1])
- assert_raises(IndexError, lambda: a[[True, True, True]])
- assert_raises(IndexError, lambda: a[(True, True, True),])
- # Integer array indices are not allowed (except for 0-D)
- idx = asarray([[0, 1]])
- assert_raises(IndexError, lambda: a[idx])
- assert_raises(IndexError, lambda: a[idx,])
- assert_raises(IndexError, lambda: a[[0, 1]])
- assert_raises(IndexError, lambda: a[(0, 1), (0, 1)])
- assert_raises(IndexError, lambda: a[[0, 1]])
- assert_raises(IndexError, lambda: a[np.array([[0, 1]])])
- # Multiaxis indices must contain exactly as many indices as dimensions
- assert_raises(IndexError, lambda: a[()])
- assert_raises(IndexError, lambda: a[0,])
- assert_raises(IndexError, lambda: a[0])
- assert_raises(IndexError, lambda: a[:])
- def test_operators():
- # For every operator, we test that it works for the required type
- # combinations and raises TypeError otherwise
- binary_op_dtypes = {
- "__add__": "numeric",
- "__and__": "integer_or_boolean",
- "__eq__": "all",
- "__floordiv__": "numeric",
- "__ge__": "numeric",
- "__gt__": "numeric",
- "__le__": "numeric",
- "__lshift__": "integer",
- "__lt__": "numeric",
- "__mod__": "numeric",
- "__mul__": "numeric",
- "__ne__": "all",
- "__or__": "integer_or_boolean",
- "__pow__": "numeric",
- "__rshift__": "integer",
- "__sub__": "numeric",
- "__truediv__": "floating",
- "__xor__": "integer_or_boolean",
- }
- # Recompute each time because of in-place ops
- def _array_vals():
- for d in _integer_dtypes:
- yield asarray(1, dtype=d)
- for d in _boolean_dtypes:
- yield asarray(False, dtype=d)
- for d in _floating_dtypes:
- yield asarray(1.0, dtype=d)
- for op, dtypes in binary_op_dtypes.items():
- ops = [op]
- if op not in ["__eq__", "__ne__", "__le__", "__ge__", "__lt__", "__gt__"]:
- rop = "__r" + op[2:]
- iop = "__i" + op[2:]
- ops += [rop, iop]
- for s in [1, 1.0, False]:
- for _op in ops:
- for a in _array_vals():
- # Test array op scalar. From the spec, the following combinations
- # are supported:
- # - Python bool for a bool array dtype,
- # - a Python int within the bounds of the given dtype for integer array dtypes,
- # - a Python int or float for floating-point array dtypes
- # We do not do bounds checking for int scalars, but rather use the default
- # NumPy behavior for casting in that case.
- if ((dtypes == "all"
- or dtypes == "numeric" and a.dtype in _numeric_dtypes
- or dtypes == "integer" and a.dtype in _integer_dtypes
- or dtypes == "integer_or_boolean" and a.dtype in _integer_or_boolean_dtypes
- or dtypes == "boolean" and a.dtype in _boolean_dtypes
- or dtypes == "floating" and a.dtype in _floating_dtypes
- )
- # bool is a subtype of int, which is why we avoid
- # isinstance here.
- and (a.dtype in _boolean_dtypes and type(s) == bool
- or a.dtype in _integer_dtypes and type(s) == int
- or a.dtype in _floating_dtypes and type(s) in [float, int]
- )):
- # Only test for no error
- getattr(a, _op)(s)
- else:
- assert_raises(TypeError, lambda: getattr(a, _op)(s))
- # Test array op array.
- for _op in ops:
- for x in _array_vals():
- for y in _array_vals():
- # See the promotion table in NEP 47 or the array
- # API spec page on type promotion. Mixed kind
- # promotion is not defined.
- if (x.dtype == uint64 and y.dtype in [int8, int16, int32, int64]
- or y.dtype == uint64 and x.dtype in [int8, int16, int32, int64]
- or x.dtype in _integer_dtypes and y.dtype not in _integer_dtypes
- or y.dtype in _integer_dtypes and x.dtype not in _integer_dtypes
- or x.dtype in _boolean_dtypes and y.dtype not in _boolean_dtypes
- or y.dtype in _boolean_dtypes and x.dtype not in _boolean_dtypes
- or x.dtype in _floating_dtypes and y.dtype not in _floating_dtypes
- or y.dtype in _floating_dtypes and x.dtype not in _floating_dtypes
- ):
- assert_raises(TypeError, lambda: getattr(x, _op)(y))
- # Ensure in-place operators only promote to the same dtype as the left operand.
- elif (
- _op.startswith("__i")
- and result_type(x.dtype, y.dtype) != x.dtype
- ):
- assert_raises(TypeError, lambda: getattr(x, _op)(y))
- # Ensure only those dtypes that are required for every operator are allowed.
- elif (dtypes == "all" and (x.dtype in _boolean_dtypes and y.dtype in _boolean_dtypes
- or x.dtype in _numeric_dtypes and y.dtype in _numeric_dtypes)
- or (dtypes == "numeric" and x.dtype in _numeric_dtypes and y.dtype in _numeric_dtypes)
- or dtypes == "integer" and x.dtype in _integer_dtypes and y.dtype in _numeric_dtypes
- or dtypes == "integer_or_boolean" and (x.dtype in _integer_dtypes and y.dtype in _integer_dtypes
- or x.dtype in _boolean_dtypes and y.dtype in _boolean_dtypes)
- or dtypes == "boolean" and x.dtype in _boolean_dtypes and y.dtype in _boolean_dtypes
- or dtypes == "floating" and x.dtype in _floating_dtypes and y.dtype in _floating_dtypes
- ):
- getattr(x, _op)(y)
- else:
- assert_raises(TypeError, lambda: getattr(x, _op)(y))
- unary_op_dtypes = {
- "__abs__": "numeric",
- "__invert__": "integer_or_boolean",
- "__neg__": "numeric",
- "__pos__": "numeric",
- }
- for op, dtypes in unary_op_dtypes.items():
- for a in _array_vals():
- if (
- dtypes == "numeric"
- and a.dtype in _numeric_dtypes
- or dtypes == "integer_or_boolean"
- and a.dtype in _integer_or_boolean_dtypes
- ):
- # Only test for no error
- getattr(a, op)()
- else:
- assert_raises(TypeError, lambda: getattr(a, op)())
- # Finally, matmul() must be tested separately, because it works a bit
- # different from the other operations.
- def _matmul_array_vals():
- for a in _array_vals():
- yield a
- for d in _all_dtypes:
- yield ones((3, 4), dtype=d)
- yield ones((4, 2), dtype=d)
- yield ones((4, 4), dtype=d)
- # Scalars always error
- for _op in ["__matmul__", "__rmatmul__", "__imatmul__"]:
- for s in [1, 1.0, False]:
- for a in _matmul_array_vals():
- if (type(s) in [float, int] and a.dtype in _floating_dtypes
- or type(s) == int and a.dtype in _integer_dtypes):
- # Type promotion is valid, but @ is not allowed on 0-D
- # inputs, so the error is a ValueError
- assert_raises(ValueError, lambda: getattr(a, _op)(s))
- else:
- assert_raises(TypeError, lambda: getattr(a, _op)(s))
- for x in _matmul_array_vals():
- for y in _matmul_array_vals():
- if (x.dtype == uint64 and y.dtype in [int8, int16, int32, int64]
- or y.dtype == uint64 and x.dtype in [int8, int16, int32, int64]
- or x.dtype in _integer_dtypes and y.dtype not in _integer_dtypes
- or y.dtype in _integer_dtypes and x.dtype not in _integer_dtypes
- or x.dtype in _floating_dtypes and y.dtype not in _floating_dtypes
- or y.dtype in _floating_dtypes and x.dtype not in _floating_dtypes
- or x.dtype in _boolean_dtypes
- or y.dtype in _boolean_dtypes
- ):
- assert_raises(TypeError, lambda: x.__matmul__(y))
- assert_raises(TypeError, lambda: y.__rmatmul__(x))
- assert_raises(TypeError, lambda: x.__imatmul__(y))
- elif x.shape == () or y.shape == () or x.shape[1] != y.shape[0]:
- assert_raises(ValueError, lambda: x.__matmul__(y))
- assert_raises(ValueError, lambda: y.__rmatmul__(x))
- if result_type(x.dtype, y.dtype) != x.dtype:
- assert_raises(TypeError, lambda: x.__imatmul__(y))
- else:
- assert_raises(ValueError, lambda: x.__imatmul__(y))
- else:
- x.__matmul__(y)
- y.__rmatmul__(x)
- if result_type(x.dtype, y.dtype) != x.dtype:
- assert_raises(TypeError, lambda: x.__imatmul__(y))
- elif y.shape[0] != y.shape[1]:
- # This one fails because x @ y has a different shape from x
- assert_raises(ValueError, lambda: x.__imatmul__(y))
- else:
- x.__imatmul__(y)
- def test_python_scalar_construtors():
- b = asarray(False)
- i = asarray(0)
- f = asarray(0.0)
- assert bool(b) == False
- assert int(i) == 0
- assert float(f) == 0.0
- assert operator.index(i) == 0
- # bool/int/float should only be allowed on 0-D arrays.
- assert_raises(TypeError, lambda: bool(asarray([False])))
- assert_raises(TypeError, lambda: int(asarray([0])))
- assert_raises(TypeError, lambda: float(asarray([0.0])))
- assert_raises(TypeError, lambda: operator.index(asarray([0])))
- # bool/int/float should only be allowed on arrays of the corresponding
- # dtype
- assert_raises(ValueError, lambda: bool(i))
- assert_raises(ValueError, lambda: bool(f))
- assert_raises(ValueError, lambda: int(b))
- assert_raises(ValueError, lambda: int(f))
- assert_raises(ValueError, lambda: float(b))
- assert_raises(ValueError, lambda: float(i))
- assert_raises(TypeError, lambda: operator.index(b))
- assert_raises(TypeError, lambda: operator.index(f))
- def test_device_property():
- a = ones((3, 4))
- assert a.device == 'cpu'
- assert all(equal(a.to_device('cpu'), a))
- assert_raises(ValueError, lambda: a.to_device('gpu'))
- assert all(equal(asarray(a, device='cpu'), a))
- assert_raises(ValueError, lambda: asarray(a, device='gpu'))
- def test_array_properties():
- a = ones((1, 2, 3))
- b = ones((2, 3))
- assert_raises(ValueError, lambda: a.T)
- assert isinstance(b.T, Array)
- assert b.T.shape == (3, 2)
- assert isinstance(a.mT, Array)
- assert a.mT.shape == (1, 3, 2)
- assert isinstance(b.mT, Array)
- assert b.mT.shape == (3, 2)
- def test___array__():
- a = ones((2, 3), dtype=int16)
- assert np.asarray(a) is a._array
- b = np.asarray(a, dtype=np.float64)
- assert np.all(np.equal(b, np.ones((2, 3), dtype=np.float64)))
- assert b.dtype == np.float64
- def test_allow_newaxis():
- a = ones(5)
- indexed_a = a[None, :]
- assert indexed_a.shape == (1, 5)
- def test_disallow_flat_indexing_with_newaxis():
- a = ones((3, 3, 3))
- with pytest.raises(IndexError):
- a[None, 0, 0]
- def test_disallow_mask_with_newaxis():
- a = ones((3, 3, 3))
- with pytest.raises(IndexError):
- a[None, asarray(True)]
- @pytest.mark.parametrize("shape", [(), (5,), (3, 3, 3)])
- @pytest.mark.parametrize("index", ["string", False, True])
- def test_error_on_invalid_index(shape, index):
- a = ones(shape)
- with pytest.raises(IndexError):
- a[index]
- def test_mask_0d_array_without_errors():
- a = ones(())
- a[asarray(True)]
- @pytest.mark.parametrize(
- "i", [slice(5), slice(5, 0), asarray(True), asarray([0, 1])]
- )
- def test_error_on_invalid_index_with_ellipsis(i):
- a = ones((3, 3, 3))
- with pytest.raises(IndexError):
- a[..., i]
- with pytest.raises(IndexError):
- a[i, ...]
- def test_array_keys_use_private_array():
- """
- Indexing operations convert array keys before indexing the internal array
- Fails when array_api array keys are not converted into NumPy-proper arrays
- in __getitem__(). This is achieved by passing array_api arrays with 0-sized
- dimensions, which NumPy-proper treats erroneously - not sure why!
- TODO: Find and use appropriate __setitem__() case.
- """
- a = ones((0, 0), dtype=bool_)
- assert a[a].shape == (0,)
- a = ones((0,), dtype=bool_)
- key = ones((0, 0), dtype=bool_)
- with pytest.raises(IndexError):
- a[key]
|