From ec341adb9c8fb50377d639c1a5c2cd80a2aa8e1e Mon Sep 17 00:00:00 2001 From: Evgeni Burovski Date: Thu, 23 Feb 2023 21:37:52 +0300 Subject: [PATCH 01/16] ENH: add where, searchsorted, inner and outer, diag_indices{_from} --- autogen/numpy_api_dump.py | 61 ------------------- torch_np/_detail/implementations.py | 31 ++++++++++ torch_np/_ndarray.py | 9 +++ torch_np/_wrapper.py | 56 ++++++++++++++--- .../tests/numpy_tests/core/test_numeric.py | 1 - 5 files changed, 86 insertions(+), 72 deletions(-) diff --git a/autogen/numpy_api_dump.py b/autogen/numpy_api_dump.py index 7b39ccd8..7a004dc9 100644 --- a/autogen/numpy_api_dump.py +++ b/autogen/numpy_api_dump.py @@ -216,22 +216,6 @@ def deprecate_with_doc(msg): raise NotImplementedError -def diag_indices(n, ndim=2): - raise NotImplementedError - - -def diag_indices_from(arr): - raise NotImplementedError - - -def diagflat(v, k=0): - raise NotImplementedError - - -def diagonal(a, offset=0, axis1=0, axis2=1): - raise NotImplementedError - - def digitize(x, bins, right=False): raise NotImplementedError @@ -397,10 +381,6 @@ def indices(dimensions, dtype=int, sparse=False): raise NotImplementedError -def inner(a, b, /): - raise NotImplementedError - - def insert(arr, obj, values, axis=None): raise NotImplementedError @@ -417,10 +397,6 @@ def is_busday(dates, weekmask="1111100", holidays=None, busdaycal=None, out=None raise NotImplementedError -def isclose(a, b, rtol=1e-05, atol=1e-08, equal_nan=False): - raise NotImplementedError - - def isfortran(a): raise NotImplementedError @@ -437,14 +413,6 @@ def issctype(rep): raise NotImplementedError -def issubclass_(arg1, arg2): - raise NotImplementedError - - -def issubdtype(arg1, arg2): - raise NotImplementedError - - def issubsctype(arg1, arg2): raise NotImplementedError @@ -624,10 +592,6 @@ def obj2sctype(rep, default=None): raise NotImplementedError -def outer(a, b, out=None): - raise NotImplementedError - - def packbits(a, /, axis=None, bitorder="big"): raise NotImplementedError @@ -696,10 +660,6 @@ def putmask(a, mask, values): raise NotImplementedError -def ravel(a, order="C"): - raise NotImplementedError - - def recfromcsv(fname, **kwargs): raise NotImplementedError @@ -754,10 +714,6 @@ def sctype2char(sctype): raise NotImplementedError -def searchsorted(a, v, side="left", sorter=None): - raise NotImplementedError - - def select(condlist, choicelist, default=0): raise NotImplementedError @@ -810,23 +766,10 @@ def shares_memory(a, b, max_work=None): def show(): raise NotImplementedError - -def sinc(x): - raise NotImplementedError - - -def sometrue(*args, **kwargs): - raise NotImplementedError - - def sort_complex(a): raise NotImplementedError -def swapaxes(a, axis1, axis2): - raise NotImplementedError - - def take(a, indices, axis=None, out=None, mode="raise"): raise NotImplementedError @@ -879,9 +822,5 @@ def vdot(a, b, /): raise NotImplementedError -def where(condition, x, y, /): - raise NotImplementedError - - def who(vardict=None): raise NotImplementedError diff --git a/torch_np/_detail/implementations.py b/torch_np/_detail/implementations.py index e82515a8..5c9c4310 100644 --- a/torch_np/_detail/implementations.py +++ b/torch_np/_detail/implementations.py @@ -127,6 +127,22 @@ def triu_indices(n, k=0, m=None): return result +def diag_indices(n, ndim=2): + idx = torch.arange(n) + return (idx,)*ndim + + +def diag_indices_from(tensor): + if not tensor.ndim >= 2: + raise ValueError("input array must be at least 2-d") + # For more than d=2, the strided formula is only valid for arrays with + # all dimensions equal, so we check first. + s = tensor.shape + if any(s[1:] != s[:-1]): + raise ValueError("All dimensions of input must be of equal length") + return diag_indices(s[0], tensor.ndim) + + # ### splits ### @@ -589,3 +605,18 @@ def argsort(tensor, axis=-1, kind=None, order=None): tensor, axis, stable = _sort_helper(tensor, axis, kind, order) result = torch.argsort(tensor, dim=axis, stable=stable) return result + + +# ### logic and selection ### + + +def where(condition, x, y): + selector = (x is None) == (y is None) + if not selector: + raise ValueError("either both or neither of x and y should be given") + + if x is None and y is None: + result = torch.where(condition) + else: + result = torch.where(condition, x, y) + return result diff --git a/torch_np/_ndarray.py b/torch_np/_ndarray.py index 4df40c4f..3520ea57 100644 --- a/torch_np/_ndarray.py +++ b/torch_np/_ndarray.py @@ -395,6 +395,10 @@ def repeat(self, repeats, axis=None): axis_none_ravel_wrapper(dtype_to_torch(_reductions.cumsum)) ) + def diagonal(self, offset=0, axis1=0, axis2=1): + result = torch.diagonal(self._tensor, offset, axis1, axis2) + return asarray(result) + ### indexing ### @staticmethod def _upcast_int_indices(index): @@ -427,6 +431,11 @@ def argsort(self, axis=-1, kind=None, order=None): result = _impl.argsort(self.tensor, axis, kind, order) return asarray(result) + def searchsorted(self, v, side="left", sorter=None): + v_t, sorter_t = _helpers.to_tensors_or_none(v, sorter) + result = torch.searchsorted(self._tensor, v_t, side=side, sorter=sorter_t) + return asarray(result) + # This is the ideally the only place which talks to ndarray directly. # The rest goes through asarray (preferred) or array. diff --git a/torch_np/_wrapper.py b/torch_np/_wrapper.py index a4488933..cbb01881 100644 --- a/torch_np/_wrapper.py +++ b/torch_np/_wrapper.py @@ -372,6 +372,28 @@ def diag(v, k=0): return asarray(result) +def diagonal(a, offset=0, axis1=0, axis2=1): + arr = asarray(a) + return arr.diagonal(offset, axis1, axis2) + + +def diagflat(v, k=0): + tensor = asarray(v).get() + result = torch.diagflat(tensor, k) + return result + + +def diag_indices(n, ndim=2): + result = _impl.diag_indices(n, ndim) + return tuple(asarray(x) for x in result) + + +def diag_indices_from(arr): + tensor = asarray(arr).get() + result = _impl.diag_indices_from(tensor) + return tuple(asarray(x) for x in result) + + ###### misc/unordered @@ -446,17 +468,19 @@ def bincount(x, /, weights=None, minlength=0): return asarray(result) -# YYY: pattern: sequence of arrays def where(condition, x=None, y=None, /): - selector = (x is None) == (y is None) - if not selector: - raise ValueError("either both or neither of x and y should be given") - condition = asarray(condition).get() - if x is None and y is None: - return tuple(asarray(_) for _ in torch.where(condition)) - x = asarray(condition).get() - y = asarray(condition).get() - return asarray(torch.where(condition, x, y)) + cond_t, x_t, y_t = _helpers.to_tensors_or_none(condition, x, y) + result = _impl.where(cond_t, x_t, y_t) + if isinstance(result, tuple): + # single-argument where(condition) + return tuple(asarray(x) for x in result) + else: + return asarray(result) + + +def searchsorted(a, v, side="left", sorter=None): + arr = asarray(a) + return arr.searchsorted(v, side=side, sorter=sorter) ###### module-level queries of object properties @@ -847,6 +871,18 @@ def median(a, axis=None, out=None, overwrite_input=False, keepdims=False): ) +def inner(a, b, /): + a_t, b_t = _helpers.to_tensors(a, b) + result = torch.inner(a_t, b_t) + return asarray(result) + + +def outer(a, b, out=None): + a_t, b_t = _helpers.to_tensors(a, b) + result = torch.outer(a_t, b_t) + return _helpers.result_or_out(result, out) + + @asarray_replacer() def nanmean(a, axis=None, dtype=None, out=None, keepdims=NoValue, *, where=NoValue): if where is not NoValue: diff --git a/torch_np/tests/numpy_tests/core/test_numeric.py b/torch_np/tests/numpy_tests/core/test_numeric.py index 83fc3d87..827759ba 100644 --- a/torch_np/tests/numpy_tests/core/test_numeric.py +++ b/torch_np/tests/numpy_tests/core/test_numeric.py @@ -125,7 +125,6 @@ def test_cumproduct(self): A = [[1, 2, 3], [4, 5, 6]] assert_(np.all(np.cumproduct(A) == np.array([1, 2, 6, 24, 120, 720]))) - @pytest.mark.xfail(reason="TODO implement diagonal(...)") def test_diagonal(self): a = [[0, 1, 2, 3], [4, 5, 6, 7], From 0dc3a1a88cd8f9584f05d23a348020ce86f95dcd Mon Sep 17 00:00:00 2001 From: Evgeni Burovski Date: Thu, 23 Feb 2023 23:31:27 +0300 Subject: [PATCH 02/16] TST: un-xfail tests of diag_indices --- torch_np/_detail/implementations.py | 2 +- torch_np/tests/numpy_tests/lib/test_index_tricks.py | 12 +++++------- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/torch_np/_detail/implementations.py b/torch_np/_detail/implementations.py index 5c9c4310..e893c07e 100644 --- a/torch_np/_detail/implementations.py +++ b/torch_np/_detail/implementations.py @@ -138,7 +138,7 @@ def diag_indices_from(tensor): # For more than d=2, the strided formula is only valid for arrays with # all dimensions equal, so we check first. s = tensor.shape - if any(s[1:] != s[:-1]): + if s[1:] != s[:-1]: raise ValueError("All dimensions of input must be of equal length") return diag_indices(s[0], tensor.ndim) diff --git a/torch_np/tests/numpy_tests/lib/test_index_tricks.py b/torch_np/tests/numpy_tests/lib/test_index_tricks.py index bff81904..929b680c 100644 --- a/torch_np/tests/numpy_tests/lib/test_index_tricks.py +++ b/torch_np/tests/numpy_tests/lib/test_index_tricks.py @@ -12,7 +12,7 @@ ndindex, r_, s_, ix_ ) -# from torch_np import diag_indices, diag_indices_from +from torch_np import diag_indices, diag_indices_from from torch_np._detail._index_tricks import index_exp @@ -494,7 +494,6 @@ def test_hetero_shape_handling(self): fill_diagonal(a, 2) -@pytest.mark.xfail(reason='diag_indices not implemented') def test_diag_indices(): di = diag_indices(4) a = np.array([[1, 2, 3, 4], @@ -513,7 +512,7 @@ def test_diag_indices(): d3 = diag_indices(2, 3) # And use it to set the diagonal of a zeros array to 1: - a = np.zeros((2, 2, 2), int) + a = np.zeros((2, 2, 2), dtype=int) a[d3] = 1 assert_array_equal( a, np.array([[[1, 0], @@ -523,7 +522,6 @@ def test_diag_indices(): ) -@pytest.mark.xfail(reason='diag_indices_from not implemented') class TestDiagIndicesFrom: def test_diag_indices_from(self): @@ -534,12 +532,12 @@ def test_diag_indices_from(self): def test_error_small_input(self): x = np.ones(7) - with assert_raises_regex(ValueError, "at least 2-d"): + with assert_raises(ValueError): diag_indices_from(x) def test_error_shape_mismatch(self): - x = np.zeros((3, 3, 2, 3), int) - with assert_raises_regex(ValueError, "equal length"): + x = np.zeros((3, 3, 2, 3), dtype=int) + with assert_raises(ValueError): diag_indices_from(x) From f9a695cb6fbfc478d6d30d54c9c6ddce87603d77 Mon Sep 17 00:00:00 2001 From: Evgeni Burovski Date: Thu, 23 Feb 2023 23:55:28 +0300 Subject: [PATCH 03/16] ENH: fill_diagonal, trace, indices, vdot --- autogen/numpy_api_dump.py | 21 +------ torch_np/_detail/implementations.py | 63 ++++++++++++++++++- torch_np/_wrapper.py | 27 ++++++++ .../tests/numpy_tests/core/test_numeric.py | 4 +- .../numpy_tests/lib/test_index_tricks.py | 28 ++++----- 5 files changed, 104 insertions(+), 39 deletions(-) diff --git a/autogen/numpy_api_dump.py b/autogen/numpy_api_dump.py index 7a004dc9..c85c1b1a 100644 --- a/autogen/numpy_api_dump.py +++ b/autogen/numpy_api_dump.py @@ -172,10 +172,6 @@ def choose(a, choices, out=None, mode="raise"): raise NotImplementedError -def clip(a, a_min, a_max, out=None, **kwargs): - raise NotImplementedError - - def common_type(*arrays): raise NotImplementedError @@ -244,10 +240,6 @@ def extract(condition, arr): raise NotImplementedError -def fill_diagonal(a, val, wrap=False): - raise NotImplementedError - - def find_common_type(array_types, scalar_types): raise NotImplementedError @@ -377,10 +369,6 @@ def in1d(ar1, ar2, assume_unique=False, invert=False): raise NotImplementedError -def indices(dimensions, dtype=int, sparse=False): - raise NotImplementedError - - def insert(arr, obj, values, axis=None): raise NotImplementedError @@ -766,6 +754,7 @@ def shares_memory(a, b, max_work=None): def show(): raise NotImplementedError + def sort_complex(a): raise NotImplementedError @@ -778,10 +767,6 @@ def tensordot(a, b, axes=2): raise NotImplementedError -def trace(a, offset=0, axis1=0, axis2=1, dtype=None, out=None): - raise NotImplementedError - - def trapz(y, x=None, dx=1.0, axis=-1): raise NotImplementedError @@ -818,9 +803,5 @@ def unwrap(p, discont=None, axis=-1, *, period=6.283185307179586): raise NotImplementedError -def vdot(a, b, /): - raise NotImplementedError - - def who(vardict=None): raise NotImplementedError diff --git a/torch_np/_detail/implementations.py b/torch_np/_detail/implementations.py index e893c07e..1c15c7d9 100644 --- a/torch_np/_detail/implementations.py +++ b/torch_np/_detail/implementations.py @@ -129,7 +129,7 @@ def triu_indices(n, k=0, m=None): def diag_indices(n, ndim=2): idx = torch.arange(n) - return (idx,)*ndim + return (idx,) * ndim def diag_indices_from(tensor): @@ -143,6 +143,34 @@ def diag_indices_from(tensor): return diag_indices(s[0], tensor.ndim) +def fill_diagonal(tensor, t_val, wrap): + # torch.Tensor.fill_diagonal_ only accepts scalars. Thus vendor the numpy source, + # https://github.com/numpy/numpy/blob/v1.24.0/numpy/lib/index_tricks.py#L786-L917 + + if tensor.ndim < 2: + raise ValueError("array must be at least 2-d") + end = None + if tensor.ndim == 2: + # Explicit, fast formula for the common case. For 2-d arrays, we + # accept rectangular ones. + step = tensor.shape[1] + 1 + # This is needed to don't have tall matrix have the diagonal wrap. + if not wrap: + end = tensor.shape[1] * tensor.shape[1] + else: + # For more than d=2, the strided formula is only valid for arrays with + # all dimensions equal, so we check first. + s = tensor.shape + if s[1:] != s[:-1]: + raise ValueError("All dimensions of input must be of equal length") + sz = torch.as_tensor(tensor.shape[:-1]) + step = 1 + (torch.cumprod(sz, 0)).sum() + + # Write the value out into the diagonal. + tensor.ravel()[:end:step] = t_val + return tensor + + # ### splits ### @@ -396,6 +424,26 @@ def meshgrid(*xi_tensors, copy=True, sparse=False, indexing="xy"): return output +def indices(dimensions, dtype=int, sparse=False): + # https://github.com/numpy/numpy/blob/v1.24.0/numpy/core/numeric.py#L1691-L1791 + dimensions = tuple(dimensions) + N = len(dimensions) + shape = (1,) * N + if sparse: + res = tuple() + else: + res = torch.empty((N,) + dimensions, dtype=dtype) + for i, dim in enumerate(dimensions): + idx = torch.arange(dim, dtype=dtype).reshape( + shape[:i] + (dim,) + shape[i + 1 :] + ) + if sparse: + res = res + (idx,) + else: + res[i] = idx + return res + + def bincount(x_tensor, /, weights_tensor=None, minlength=0): int_dtype = _dtypes_impl.default_int_dtype (x_tensor,) = _util.cast_dont_broadcast((x_tensor,), int_dtype, casting="safe") @@ -620,3 +668,16 @@ def where(condition, x, y): else: result = torch.where(condition, x, y) return result + + +# ### dot and other linalg ### + +def vdot(t_a, t_b, /): + # torch only accepts 1D arrays, numpy ravels + t_a, t_b = torch.atleast_1d(t_a, t_b) + if t_a.ndim > 1: + t_a = t_a.ravel() + if t_b.ndim > 1: + t_b = t_b.ravel() + result = torch.vdot(t_a, t_b) + return result diff --git a/torch_np/_wrapper.py b/torch_np/_wrapper.py index cbb01881..3ceb06d5 100644 --- a/torch_np/_wrapper.py +++ b/torch_np/_wrapper.py @@ -394,6 +394,19 @@ def diag_indices_from(arr): return tuple(asarray(x) for x in result) +def fill_diagonal(a, val, wrap=False): + tensor, t_val = _helpers.to_tensors(a, val) + result = _impl.fill_diagonal(tensor, t_val, wrap) + return asarray(result) + + +@_decorators.dtype_to_torch +def trace(a, offset=0, axis1=0, axis2=1, dtype=None, out=None): + tensor = asarray(a).get() + result = torch.diagonal(tensor, offset, dim1=axis1, dim2=axis2).sum(-1, dtype=dtype) + return asarray(result) + + ###### misc/unordered @@ -483,6 +496,11 @@ def searchsorted(a, v, side="left", sorter=None): return arr.searchsorted(v, side=side, sorter=sorter) +def vdot(a, b, /): + t_a, t_b = _helpers.to_tensors(a, b) + result = _impl.vdot(t_a, t_b) + return result.item() + ###### module-level queries of object properties @@ -615,6 +633,15 @@ def meshgrid(*xi, copy=True, sparse=False, indexing="xy"): return [asarray(t) for t in output] +@_decorators.dtype_to_torch +def indices(dimensions, dtype=int, sparse=False): + result = _impl.indices(dimensions, dtype=dtype, sparse=sparse) + if sparse: + return tuple(asarray(x) for x in result) + else: + return asarray(result) + + def nonzero(a): arr = asarray(a) return arr.nonzero() diff --git a/torch_np/tests/numpy_tests/core/test_numeric.py b/torch_np/tests/numpy_tests/core/test_numeric.py index 827759ba..e8992d27 100644 --- a/torch_np/tests/numpy_tests/core/test_numeric.py +++ b/torch_np/tests/numpy_tests/core/test_numeric.py @@ -291,7 +291,6 @@ def test_take(self): out = np.take(a, indices) assert_equal(out, tgt) - @pytest.mark.xfail(reason="TODO implement trace(...)") def test_trace(self): c = [[1, 2], [3, 4], [5, 6]] assert_equal(np.trace(c), 5) @@ -2627,7 +2626,7 @@ def test_exceptions(self): assert_raises(np.AxisError, np.rollaxis, a, 4, 0) assert_raises(np.AxisError, np.rollaxis, a, 0, 5) - @pytest.mark.xfail(reason="needs np.indices") + @pytest.mark.xfail(reason="needs fancy indexing") def test_results(self): a = np.arange(1*2*3*4).reshape(1, 2, 3, 4).copy() aind = np.indices(a.shape) @@ -2833,7 +2832,6 @@ def test_outer_out_param(): assert_equal(np.outer(arr2, arr3, out2), out2) -@pytest.mark.xfail(reason="TODO") class TestIndices: def test_simple(self): diff --git a/torch_np/tests/numpy_tests/lib/test_index_tricks.py b/torch_np/tests/numpy_tests/lib/test_index_tricks.py index 929b680c..309e51ae 100644 --- a/torch_np/tests/numpy_tests/lib/test_index_tricks.py +++ b/torch_np/tests/numpy_tests/lib/test_index_tricks.py @@ -8,12 +8,12 @@ from pytest import raises as assert_raises #, assert_raises_regex, from numpy.lib.index_tricks import ( - mgrid, ogrid, ndenumerate, fill_diagonal, - ndindex, r_, s_, ix_ + mgrid, ogrid, ndenumerate, + ndindex, r_, ix_ ) -from torch_np import diag_indices, diag_indices_from -from torch_np._detail._index_tricks import index_exp +from torch_np import diag_indices, diag_indices_from, fill_diagonal +from torch_np._detail._index_tricks import index_exp, s_ @pytest.mark.xfail(reason='unravel_index not implemented') @@ -358,7 +358,6 @@ def test_basic(self): [((0, 0), 1), ((0, 1), 2), ((1, 0), 3), ((1, 1), 4)]) -@pytest.mark.xfail(reason='s_ not implemented') class TestIndexExpression: def test_regression_1(self): # ticket #1196 @@ -422,10 +421,9 @@ def test_c_(): assert_equal(a, [[1, 2, 3, 0, 0, 4, 5, 6]]) -@pytest.mark.xfail(reason='fill_diagonal not implemented') class TestFillDiagonal: def test_basic(self): - a = np.zeros((3, 3), int) + a = np.zeros((3, 3), dtype=int) fill_diagonal(a, 5) assert_array_equal( a, np.array([[5, 0, 0], @@ -434,7 +432,7 @@ def test_basic(self): ) def test_tall_matrix(self): - a = np.zeros((10, 3), int) + a = np.zeros((10, 3), dtype=int) fill_diagonal(a, 5) assert_array_equal( a, np.array([[5, 0, 0], @@ -450,7 +448,7 @@ def test_tall_matrix(self): ) def test_tall_matrix_wrap(self): - a = np.zeros((10, 3), int) + a = np.zeros((10, 3), dtype=int) fill_diagonal(a, 5, True) assert_array_equal( a, np.array([[5, 0, 0], @@ -466,7 +464,7 @@ def test_tall_matrix_wrap(self): ) def test_wide_matrix(self): - a = np.zeros((3, 10), int) + a = np.zeros((3, 10), dtype=int) fill_diagonal(a, 5) assert_array_equal( a, np.array([[5, 0, 0, 0, 0, 0, 0, 0, 0, 0], @@ -475,22 +473,22 @@ def test_wide_matrix(self): ) def test_operate_4d_array(self): - a = np.zeros((3, 3, 3, 3), int) + a = np.zeros((3, 3, 3, 3), dtype=int) fill_diagonal(a, 4) i = np.array([0, 1, 2]) assert_equal(np.where(a != 0), (i, i, i, i)) def test_low_dim_handling(self): # raise error with low dimensionality - a = np.zeros(3, int) - with assert_raises_regex(ValueError, "at least 2-d"): + a = np.zeros(3, dtype=int) + with assert_raises(ValueError): fill_diagonal(a, 5) def test_hetero_shape_handling(self): # raise error with high dimensionality and # shape mismatch - a = np.zeros((3,3,7,3), int) - with assert_raises_regex(ValueError, "equal length"): + a = np.zeros((3,3,7,3), dtype=int) + with assert_raises(ValueError): fill_diagonal(a, 2) From 62a7d2c2aa8444fef8032c45709a7c7a7adee3e0 Mon Sep 17 00:00:00 2001 From: Evgeni Burovski Date: Fri, 24 Feb 2023 20:32:22 +0300 Subject: [PATCH 04/16] BUG: un-xfail several tests in test_multiarray::TestMethods, fix found bugs --- torch_np/_detail/_flips.py | 2 + torch_np/_detail/_util.py | 2 +- torch_np/_detail/implementations.py | 47 +- torch_np/_ndarray.py | 30 +- torch_np/_wrapper.py | 11 +- .../tests/numpy_tests/core/test_multiarray.py | 432 ++++-------------- 6 files changed, 169 insertions(+), 355 deletions(-) diff --git a/torch_np/_detail/_flips.py b/torch_np/_detail/_flips.py index 52bc3bbb..76bfd4c4 100644 --- a/torch_np/_detail/_flips.py +++ b/torch_np/_detail/_flips.py @@ -29,6 +29,8 @@ def rot90(m_tensor, k=1, axes=(0, 1)): def swapaxes(tensor, axis1, axis2): + axis1 = _util.normalize_axis_index(axis1, tensor.ndim) + axis2 = _util.normalize_axis_index(axis2, tensor.ndim) return torch.swapaxes(tensor, axis1, axis2) diff --git a/torch_np/_detail/_util.py b/torch_np/_detail/_util.py index ef5f9a7e..550f5492 100644 --- a/torch_np/_detail/_util.py +++ b/torch_np/_detail/_util.py @@ -36,7 +36,7 @@ class UFuncTypeError(TypeError, RuntimeError): def cast_if_needed(tensor, dtype): # NB: no casting if dtype=None - if tensor.dtype != dtype: + if dtype is not None and tensor.dtype != dtype: tensor = tensor.to(dtype) return tensor diff --git a/torch_np/_detail/implementations.py b/torch_np/_detail/implementations.py index 1c15c7d9..462e85fd 100644 --- a/torch_np/_detail/implementations.py +++ b/torch_np/_detail/implementations.py @@ -171,6 +171,21 @@ def fill_diagonal(tensor, t_val, wrap): return tensor +def trace(tensor, offset=0, axis1=0, axis2=1, dtype=None, out=None): + result = torch.diagonal(tensor, offset, dim1=axis1, dim2=axis2).sum(-1, dtype=dtype) + return result + + +def diagonal(tensor, offset=0, axis1=0, axis2=1): + axis1 = _util.normalize_axis_index(axis1, tensor.ndim) + axis2 = _util.normalize_axis_index(axis2, tensor.ndim) + if axis1 == axis2: + raise ValueError("axis1 and axis2 cannot be the same") + result = torch.diagonal(tensor, offset, axis1, axis2) + return result + + + # ### splits ### @@ -560,7 +575,12 @@ def squeeze(tensor, axis=None): elif axis is None: result = tensor.squeeze() else: - result = tensor.squeeze(axis) + if isinstance(axis, tuple): + result = tensor + for ax in axis: + result = result.squeeze(ax) + else: + result = tensor.squeeze(axis) return result @@ -672,6 +692,31 @@ def where(condition, x, y): # ### dot and other linalg ### +def inner(t_a, t_b): + is_half = t_a.dtype == torch.float16 or t_b.dtype == torch.float16 + is_bool = t_a.dtype == torch.bool or t_b.dtype == torch.bool + + dtype = None + if is_half: + # work around torch's "addmm_impl_cpu_" not implemented for 'Half'" + dtype = torch.float32 + if is_bool: + dtype = torch.uint8 + + t_a = _util.cast_if_needed(t_a, dtype) + t_b = _util.cast_if_needed(t_b, dtype) + + result = torch.inner(t_a, t_b) + + if is_half: + result = result.to(torch.float16) + if is_bool: + result = result.to(torch.bool) + + return result + + + def vdot(t_a, t_b, /): # torch only accepts 1D arrays, numpy ravels t_a, t_b = torch.atleast_1d(t_a, t_b) diff --git a/torch_np/_ndarray.py b/torch_np/_ndarray.py index 3520ea57..94a69442 100644 --- a/torch_np/_ndarray.py +++ b/torch_np/_ndarray.py @@ -105,6 +105,10 @@ def strides(self): elsize = self._tensor.element_size() return tuple(stride * elsize for stride in self._tensor.stride()) + @property + def itemsize(self): + return self._tensor.element_size() + @property def base(self): return self._base @@ -225,7 +229,10 @@ def __float__(self): return float(self._tensor) def __complex__(self): - return complex(self._tensor) + try: + return complex(self._tensor) + except ValueError as e: + raise TypeError(*e.args) def __int__(self): return int(self._tensor) @@ -358,6 +365,13 @@ def ravel(self, order="C"): raise NotImplementedError return ndarray._from_tensor_and_base(self._tensor.ravel(), self) + def flatten(self, order="C"): + if order != "C": + raise NotImplementedError + # return a copy + result = self._tensor.ravel().clone() + return asarray(result) + def nonzero(self): tensor = self._tensor return tuple(asarray(_) for _ in tensor.nonzero(as_tuple=True)) @@ -396,9 +410,15 @@ def repeat(self, repeats, axis=None): ) def diagonal(self, offset=0, axis1=0, axis2=1): - result = torch.diagonal(self._tensor, offset, axis1, axis2) + result = _impl.diagonal(self._tensor, offset, axis1, axis2) return asarray(result) + @dtype_to_torch + def trace(self, offset=0, axis1=0, axis2=1, dtype=None, out=None): + tensor = self._tensor + result = _impl.trace(tensor, offset, axis1, axis2, dtype) + return _helpers.result_or_out(result, out) + ### indexing ### @staticmethod def _upcast_int_indices(index): @@ -423,12 +443,12 @@ def __setitem__(self, index, value): ### sorting ### def sort(self, axis=-1, kind=None, order=None): - result = _impl.sort(self.tensor, axis, kind, order) + # ndarray.sort works in-place + result = _impl.sort(self._tensor, axis, kind, order) self._tensor = result - return None def argsort(self, axis=-1, kind=None, order=None): - result = _impl.argsort(self.tensor, axis, kind, order) + result = _impl.argsort(self._tensor, axis, kind, order) return asarray(result) def searchsorted(self, v, side="left", sorter=None): diff --git a/torch_np/_wrapper.py b/torch_np/_wrapper.py index 3ceb06d5..19776bd3 100644 --- a/torch_np/_wrapper.py +++ b/torch_np/_wrapper.py @@ -400,11 +400,10 @@ def fill_diagonal(a, val, wrap=False): return asarray(result) -@_decorators.dtype_to_torch + def trace(a, offset=0, axis1=0, axis2=1, dtype=None, out=None): - tensor = asarray(a).get() - result = torch.diagonal(tensor, offset, dim1=axis1, dim2=axis2).sum(-1, dtype=dtype) - return asarray(result) + arr = asarray(a) + return arr.trace(offset, axis1, axis2, dtype, out=out) ###### misc/unordered @@ -899,8 +898,8 @@ def median(a, axis=None, out=None, overwrite_input=False, keepdims=False): def inner(a, b, /): - a_t, b_t = _helpers.to_tensors(a, b) - result = torch.inner(a_t, b_t) + t_a, t_b = _helpers.to_tensors(a, b) + result = _impl.inner(t_a, t_b) return asarray(result) diff --git a/torch_np/tests/numpy_tests/core/test_multiarray.py b/torch_np/tests/numpy_tests/core/test_multiarray.py index ea6adf5f..79399cdb 100644 --- a/torch_np/tests/numpy_tests/core/test_multiarray.py +++ b/torch_np/tests/numpy_tests/core/test_multiarray.py @@ -1341,11 +1341,12 @@ def test_cast_from_bytes(self): self._test_cast_from_flexible(np.bytes_) -@pytest.mark.xfail(reason='TODO') + class TestMethods: sort_kinds = ['quicksort', 'heapsort', 'stable'] + @pytest.mark.xfail(reason="all(..., where=...)") def test_all_where(self): a = np.array([[True, False, True], [False, False, False], @@ -1367,6 +1368,7 @@ def test_all_where(self): assert_equal(a.all(where=False), True) assert_equal(np.all(a, where=False), True) + @pytest.mark.xfail(reason="any(..., where=...)") def test_any_where(self): a = np.array([[True, False, True], [False, False, False], @@ -1387,6 +1389,7 @@ def test_any_where(self): assert_equal(a.any(where=False), False) assert_equal(np.any(a, where=False), False) + @pytest.mark.xfail(reason="TODO: compress") def test_compress(self): tgt = [[5, 6, 7, 8, 9]] arr = np.arange(10).reshape(2, 5) @@ -1406,6 +1409,7 @@ def test_compress(self): out = arr.compress([0, 1]) assert_equal(out, 1) + @pytest.mark.xfail(reason="TODO: choose") def test_choose(self): x = 2*np.ones((3,), dtype=int) y = 3*np.ones((3,), dtype=int) @@ -1485,6 +1489,7 @@ def test_repeat(self): assert_equal(A, [[1, 1, 2, 2, 3, 3], [4, 4, 5, 5, 6, 6]]) + @pytest.mark.xfail(reason="reshape(..., order='F')") def test_reshape(self): arr = np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9], [10, 11, 12]]) @@ -1521,7 +1526,7 @@ def test_squeeze(self): a = np.array([[[1], [2], [3]]]) assert_equal(a.squeeze(), [1, 2, 3]) assert_equal(a.squeeze(axis=(0,)), [[1], [2], [3]]) - assert_raises(ValueError, a.squeeze, axis=(1,)) + # assert_raises(ValueError, a.squeeze, axis=(1,)) # a noop in pytorch assert_equal(a.squeeze(axis=(2,)), [[1, 2, 3]]) def test_transpose(self): @@ -1541,7 +1546,10 @@ def test_sort(self): msg = "Test real sort order with nans" a = np.array([np.nan, 1, 0]) b = np.sort(a) - assert_equal(b, a[::-1], msg) + assert_equal(b, np.flip(a), msg) + + @pytest.mark.xfail(reason='sort complex') + def test_sort_complex(self): # check complex msg = "Test complex sort order with nans" a = np.zeros(9, dtype=np.complex128) @@ -1560,7 +1568,7 @@ def test_sort(self): np.float16, np.float32, np.float64]) def test_sort_unsigned(self, dtype): a = np.arange(101, dtype=dtype) - b = a[::-1].copy() + b = np.flip(a) for kind in self.sort_kinds: msg = "scalar sort, kind=%s" % kind c = a.copy() @@ -1575,7 +1583,7 @@ def test_sort_unsigned(self, dtype): np.float32, np.float64]) def test_sort_signed(self, dtype): a = np.arange(-50, 51, dtype=dtype) - b = a[::-1].copy() + b = np.flip(a) for kind in self.sort_kinds: msg = "scalar sort, kind=%s" % (kind) c = a.copy() @@ -1585,6 +1593,7 @@ def test_sort_signed(self, dtype): c.sort(kind=kind) assert_equal(c, a, msg) + @pytest.mark.xfail(reason='sort complex') @pytest.mark.parametrize('dtype', [np.float32, np.float64]) @pytest.mark.parametrize('part', ['real', 'imag']) def test_sort_complex(self, part, dtype): @@ -1609,44 +1618,6 @@ def test_sort_complex(self, part, dtype): c.sort(kind=kind) assert_equal(c, ai, msg) - def test_sort_complex_byte_swapping(self): - # test sorting of complex arrays requiring byte-swapping, gh-5441 - for endianness in '<>': - for dt in np.typecodes['Complex']: - arr = np.array([1+3.j, 2+2.j, 3+1.j], dtype=endianness + dt) - c = arr.copy() - c.sort() - msg = 'byte-swapped complex sort, dtype={0}'.format(dt) - assert_equal(c, arr, msg) - - def test_sort_object(self): - # test object array sorts. - a = np.empty((101,), dtype=object) - a[:] = list(range(101)) - b = a[::-1] - for kind in ['q', 'h', 'm']: - msg = "kind=%s" % kind - c = a.copy() - c.sort(kind=kind) - assert_equal(c, a, msg) - c = b.copy() - c.sort(kind=kind) - assert_equal(c, a, msg) - - def test_sort_structured(self): - # test record array sorts. - dt = np.dtype([('f', float), ('i', int)]) - a = np.array([(i, i) for i in range(101)], dtype=dt) - b = a[::-1] - for kind in ['q', 'h', 'm']: - msg = "kind=%s" % kind - c = a.copy() - c.sort(kind=kind) - assert_equal(c, a, msg) - c = b.copy() - c.sort(kind=kind) - assert_equal(c, a, msg) - def test_sort_axis(self): # check axis handling. This should be the same for all type # specific sorts, so we only check it for one type and one kind @@ -1666,55 +1637,13 @@ def test_sort_axis(self): def test_sort_size_0(self): # check axis handling for multidimensional empty arrays a = np.array([]) - a.shape = (3, 2, 1, 0) + a = a.reshape(3, 2, 1, 0) for axis in range(-a.ndim, a.ndim): msg = 'test empty array sort with axis={0}'.format(axis) assert_equal(np.sort(a, axis=axis), a, msg) msg = 'test empty array sort with axis=None' assert_equal(np.sort(a, axis=None), a.ravel(), msg) - def test_sort_bad_ordering(self): - # test generic class with bogus ordering, - # should not segfault. - class Boom: - def __lt__(self, other): - return True - - a = np.array([Boom()] * 100, dtype=object) - for kind in self.sort_kinds: - msg = "kind=%s" % kind - c = a.copy() - c.sort(kind=kind) - assert_equal(c, a, msg) - - def test_void_sort(self): - # gh-8210 - previously segfaulted - for i in range(4): - rand = np.random.randint(256, size=4000, dtype=np.uint8) - arr = rand.view('V4') - arr[::-1].sort() - - dt = np.dtype([('val', 'i4', (1,))]) - for i in range(4): - rand = np.random.randint(256, size=4000, dtype=np.uint8) - arr = rand.view(dt) - arr[::-1].sort() - - def test_sort_raises(self): - #gh-9404 - arr = np.array([0, datetime.now(), 1], dtype=object) - for kind in self.sort_kinds: - assert_raises(TypeError, arr.sort, kind=kind) - #gh-3879 - class Raiser: - def raises_anything(*args, **kwargs): - raise TypeError("SOMETHING ERRORED") - __eq__ = __ne__ = __lt__ = __gt__ = __ge__ = __le__ = raises_anything - arr = np.array([[Raiser(), n] for n in range(10)]).reshape(-1) - np.random.shuffle(arr) - for kind in self.sort_kinds: - assert_raises(TypeError, arr.sort, kind=kind) - @pytest.mark.skip(reason='waaay tooo sloooow') def test_sort_degraded(self): # test degraded dataset would take minutes to run with normal qsort @@ -1731,6 +1660,7 @@ def test_sort_degraded(self): assert_equal(np.sort(d), do) assert_equal(d[np.argsort(d)], do) + @pytest.mark.xfail(reason="order='F'") def test_copy(self): def assert_fortran(arr): assert_(arr.flags.fortran) @@ -1756,7 +1686,8 @@ def assert_c(arr): assert_fortran(a.copy('F')) assert_c(a.copy('A')) - @pytest.mark.parametrize("dtype", ['O', np.int32, 'i,O']) + @pytest.mark.xfail(reason="no .ctypes attribute") + @pytest.mark.parametrize("dtype", [np.int32]) def test__deepcopy__(self, dtype): # Force the entry of NULLs into array a = np.empty(4, dtype=dtype) @@ -1769,52 +1700,6 @@ def test__deepcopy__(self, dtype): with pytest.raises(AssertionError): assert_array_equal(a, b) - def test__deepcopy__catches_failure(self): - class MyObj: - def __deepcopy__(self, *args, **kwargs): - raise RuntimeError - - arr = np.array([1, MyObj(), 3], dtype='O') - with pytest.raises(RuntimeError): - arr.__deepcopy__({}) - - def test_sort_order(self): - # Test sorting an array with fields - x1 = np.array([21, 32, 14]) - x2 = np.array(['my', 'first', 'name']) - x3 = np.array([3.1, 4.5, 6.2]) - r = np.rec.fromarrays([x1, x2, x3], names='id,word,number') - - r.sort(order=['id']) - assert_equal(r.id, np.array([14, 21, 32])) - assert_equal(r.word, np.array(['name', 'my', 'first'])) - assert_equal(r.number, np.array([6.2, 3.1, 4.5])) - - r.sort(order=['word']) - assert_equal(r.id, np.array([32, 21, 14])) - assert_equal(r.word, np.array(['first', 'my', 'name'])) - assert_equal(r.number, np.array([4.5, 3.1, 6.2])) - - r.sort(order=['number']) - assert_equal(r.id, np.array([21, 32, 14])) - assert_equal(r.word, np.array(['my', 'first', 'name'])) - assert_equal(r.number, np.array([3.1, 4.5, 6.2])) - - assert_raises_regex(ValueError, 'duplicate', - lambda: r.sort(order=['id', 'id'])) - - if sys.byteorder == 'little': - strtype = '>i2' - else: - strtype = ' Date: Fri, 24 Feb 2023 21:12:59 +0300 Subject: [PATCH 05/16] TST: un-xfail test_outer --- torch_np/tests/numpy_tests/core/test_numeric.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/torch_np/tests/numpy_tests/core/test_numeric.py b/torch_np/tests/numpy_tests/core/test_numeric.py index e8992d27..9b0e7df4 100644 --- a/torch_np/tests/numpy_tests/core/test_numeric.py +++ b/torch_np/tests/numpy_tests/core/test_numeric.py @@ -2820,13 +2820,12 @@ def test_uint8_int32_mixed_dtypes(self): assert_equal(np.cross(u, v), -z) -@pytest.mark.xfail(reason="TODO") def test_outer_out_param(): arr1 = np.ones((5,)) arr2 = np.ones((2,)) arr3 = np.linspace(-2, 2, 5) - out1 = np.ndarray(shape=(5,5)) - out2 = np.ndarray(shape=(2, 5)) + out1 = np.empty(shape=(5,5)) + out2 = np.empty(shape=(2, 5)) res1 = np.outer(arr1, arr3, out1) assert_equal(res1, out1) assert_equal(np.outer(arr2, arr3, out2), out2) From a0bcdca8d2902136df2c47d12722cbd94285ba77 Mon Sep 17 00:00:00 2001 From: Evgeni Burovski Date: Fri, 24 Feb 2023 21:54:15 +0300 Subject: [PATCH 06/16] TST: inner, vdot --- torch_np/_detail/implementations.py | 30 ++++++++++++++++--- .../tests/numpy_tests/core/test_multiarray.py | 20 +++++++++++-- 2 files changed, 43 insertions(+), 7 deletions(-) diff --git a/torch_np/_detail/implementations.py b/torch_np/_detail/implementations.py index 462e85fd..d68db69c 100644 --- a/torch_np/_detail/implementations.py +++ b/torch_np/_detail/implementations.py @@ -692,11 +692,12 @@ def where(condition, x, y): # ### dot and other linalg ### + def inner(t_a, t_b): - is_half = t_a.dtype == torch.float16 or t_b.dtype == torch.float16 - is_bool = t_a.dtype == torch.bool or t_b.dtype == torch.bool + dtype = _dtypes_impl.result_type_impl((t_a.dtype, t_b.dtype)) + is_half = dtype == torch.float16 + is_bool = dtype == torch.bool - dtype = None if is_half: # work around torch's "addmm_impl_cpu_" not implemented for 'Half'" dtype = torch.float32 @@ -718,11 +719,32 @@ def inner(t_a, t_b): def vdot(t_a, t_b, /): - # torch only accepts 1D arrays, numpy ravels + # 1. torch only accepts 1D arrays, numpy ravels + # 2. torch requires matching dtype, while numpy casts (?) t_a, t_b = torch.atleast_1d(t_a, t_b) if t_a.ndim > 1: t_a = t_a.ravel() if t_b.ndim > 1: t_b = t_b.ravel() + + dtype = _dtypes_impl.result_type_impl((t_a.dtype, t_b.dtype)) + is_half = dtype == torch.float16 + is_bool = dtype == torch.bool + + # work around torch's "dot" not implemented for 'Half', 'Bool' + if is_half: + dtype = torch.float32 + if is_bool: + dtype = torch.uint8 + + t_a = _util.cast_if_needed(t_a, dtype) + t_b = _util.cast_if_needed(t_b, dtype) + result = torch.vdot(t_a, t_b) + + if is_half: + result = result.to(torch.float16) + if is_bool: + result = result.to(torch.bool) + return result diff --git a/torch_np/tests/numpy_tests/core/test_multiarray.py b/torch_np/tests/numpy_tests/core/test_multiarray.py index 79399cdb..86815178 100644 --- a/torch_np/tests/numpy_tests/core/test_multiarray.py +++ b/torch_np/tests/numpy_tests/core/test_multiarray.py @@ -5421,7 +5421,6 @@ def __array_finalize__(self, obj): assert_(res.info == dat.info) -@pytest.mark.xfail(reason='TODO') class TestVdot: def test_basic(self): dt_numeric = np.typecodes['AllFloat'] + np.typecodes['AllInteger'] @@ -5429,7 +5428,7 @@ def test_basic(self): # test real a = np.eye(3) - for dt in dt_numeric + 'O': + for dt in dt_numeric: b = a.astype(dt) res = np.vdot(b, b) assert_(np.isscalar(res)) @@ -5437,7 +5436,7 @@ def test_basic(self): # test complex a = np.eye(3) * 1j - for dt in dt_complex + 'O': + for dt in dt_complex: b = a.astype(dt) res = np.vdot(b, b) assert_(np.isscalar(res)) @@ -5449,6 +5448,7 @@ def test_basic(self): assert_(np.isscalar(res)) assert_equal(np.vdot(b, b), True) + @pytest.mark.xfail(reason="implement order='F'") def test_vdot_array_order(self): a = np.array([[1, 2], [3, 4]], order='C') b = np.array([[1, 2], [3, 4]], order='F') @@ -5476,6 +5476,20 @@ def test_vdot_uncontiguous(self): np.vdot(a.flatten(), b.flatten())) assert_equal(np.vdot(a.copy(), b), np.vdot(a.flatten(), b.flatten())) + + @pytest.mark.xfail(reason="implement order='F'") + def test_vdot_uncontiguous_2(self): + # test order='F' separately + for size in [2, 1000]: + # Different sizes match different branches in vdot. + a = np.zeros((size, 2, 2)) + b = np.zeros((size, 2, 2)) + a[:, 0, 0] = np.arange(size) + b[:, 0, 0] = np.arange(size) + 1 + # Make a and b uncontiguous: + a = a[..., 0] + b = b[..., 0] + assert_equal(np.vdot(a.copy('F'), b), np.vdot(a.flatten(), b.flatten())) assert_equal(np.vdot(a, b.copy('F')), From 7f83c3755ea19d3d8193a3276efec95782fb2c12 Mon Sep 17 00:00:00 2001 From: Evgeni Burovski Date: Fri, 24 Feb 2023 22:06:34 +0300 Subject: [PATCH 07/16] TST: un-xfail where(...) tests --- torch_np/_detail/implementations.py | 8 +++++- torch_np/random.py | 2 +- .../tests/numpy_tests/core/test_multiarray.py | 28 ++++++------------- 3 files changed, 16 insertions(+), 22 deletions(-) diff --git a/torch_np/_detail/implementations.py b/torch_np/_detail/implementations.py index d68db69c..56ff3d92 100644 --- a/torch_np/_detail/implementations.py +++ b/torch_np/_detail/implementations.py @@ -683,10 +683,16 @@ def where(condition, x, y): if not selector: raise ValueError("either both or neither of x and y should be given") + if condition.dtype != torch.bool: + condition = condition.to(torch.bool) + if x is None and y is None: result = torch.where(condition) else: - result = torch.where(condition, x, y) + try: + result = torch.where(condition, x, y) + except RuntimeError as e: + raise ValueError(*e.args) return result diff --git a/torch_np/random.py b/torch_np/random.py index 3b6823d8..7ce365e0 100644 --- a/torch_np/random.py +++ b/torch_np/random.py @@ -38,7 +38,7 @@ def array_or_scalar(values, py_type=float): def seed(seed=None): if seed is not None: - torch.random.manual_seed() + torch.random.manual_seed(seed) def random_sample(size=None): diff --git a/torch_np/tests/numpy_tests/core/test_multiarray.py b/torch_np/tests/numpy_tests/core/test_multiarray.py index 86815178..d55e2c9b 100644 --- a/torch_np/tests/numpy_tests/core/test_multiarray.py +++ b/torch_np/tests/numpy_tests/core/test_multiarray.py @@ -7610,7 +7610,7 @@ def __int__(self): assert_raises(NotImplementedError, int_func, np.array([NotConvertible()])) -@pytest.mark.xfail(reason='TODO') + class TestWhere: def test_basic(self): dts = [bool, np.int16, np.int32, np.int64, np.double, np.complex128] @@ -7633,18 +7633,18 @@ def test_basic(self): assert_equal(np.where(c[1::2], d[1::2], e[1::2]), r[1::2]) assert_equal(np.where(c[::3], d[::3], e[::3]), r[::3]) assert_equal(np.where(c[1::3], d[1::3], e[1::3]), r[1::3]) - assert_equal(np.where(c[::-2], d[::-2], e[::-2]), r[::-2]) - assert_equal(np.where(c[::-3], d[::-3], e[::-3]), r[::-3]) - assert_equal(np.where(c[1::-3], d[1::-3], e[1::-3]), r[1::-3]) + # assert_equal(np.where(c[::-2], d[::-2], e[::-2]), r[::-2]) + # assert_equal(np.where(c[::-3], d[::-3], e[::-3]), r[::-3]) + # assert_equal(np.where(c[1::-3], d[1::-3], e[1::-3]), r[1::-3]) def test_exotic(self): - # object - assert_array_equal(np.where(True, None, None), np.array(None)) # zero sized m = np.array([], dtype=bool).reshape(0, 3) b = np.array([], dtype=np.float64).reshape(0, 3) assert_array_equal(np.where(m, 0, b), np.array([]).reshape(0, 3)) + @pytest.mark.skip(reason='object arrays') + def test_exotic_2(self): # object cast d = np.array([-1.34, -0.16, -0.54, -0.31, -0.08, -0.95, 0.000, 0.313, 0.547, -0.18, 0.876, 0.236, 1.969, 0.310, 0.699, 1.013, @@ -7695,7 +7695,7 @@ def test_ndim(self): def test_dtype_mix(self): c = np.array([False, True, False, False, False, False, True, False, False, False, True, False]) - a = np.uint32(1) + a = np.uint8(1) b = np.array([5., 0., 3., 2., -1., -4., 0., -10., 10., 1., 0., 3.], dtype=np.float64) r = np.array([5., 1., 3., 2., -1., -4., 1., -10., 10., 1., 1., 3.], @@ -7716,6 +7716,7 @@ def test_dtype_mix(self): c[tmpmask] = 0 assert_equal(np.where(c, b, a), r) + @pytest.mark.skip(reason='endianness') def test_foreign(self): c = np.array([False, True, False, False, False, False, True, False, False, False, True, False]) @@ -7742,19 +7743,6 @@ def test_error(self): assert_raises(ValueError, np.where, c, a, a) assert_raises(ValueError, np.where, c[0], a, b) - def test_string(self): - # gh-4778 check strings are properly filled with nulls - a = np.array("abc") - b = np.array("x" * 753) - assert_equal(np.where(True, a, b), "abc") - assert_equal(np.where(False, b, a), "abc") - - # check native datatype sized strings - a = np.array("abcd") - b = np.array("x" * 8) - assert_equal(np.where(True, a, b), "abcd") - assert_equal(np.where(False, b, a), "abcd") - def test_empty_result(self): # pass empty where result through an assignment which reads the data of # empty arrays, error detectable with valgrind, see gh-8922 From 3b5eeea70008f562a4025faf190cb49a9133a277 Mon Sep 17 00:00:00 2001 From: Evgeni Burovski Date: Fri, 24 Feb 2023 22:21:56 +0300 Subject: [PATCH 08/16] TST: un-xfail TestClip --- torch_np/_ndarray.py | 2 +- torch_np/_wrapper.py | 4 +++- torch_np/tests/numpy_tests/core/test_multiarray.py | 11 +++-------- 3 files changed, 7 insertions(+), 10 deletions(-) diff --git a/torch_np/_ndarray.py b/torch_np/_ndarray.py index 94a69442..4fbccc0b 100644 --- a/torch_np/_ndarray.py +++ b/torch_np/_ndarray.py @@ -376,7 +376,7 @@ def nonzero(self): tensor = self._tensor return tuple(asarray(_) for _ in tensor.nonzero(as_tuple=True)) - def clip(self, min, max, out=None): + def clip(self, min=None, max=None, out=None): tensor, t_min, t_max = _helpers.to_tensors_or_none(self, min, max) result = _impl.clip(tensor, t_min, t_max) return _helpers.result_or_out(result, out) diff --git a/torch_np/_wrapper.py b/torch_np/_wrapper.py index 19776bd3..3242defc 100644 --- a/torch_np/_wrapper.py +++ b/torch_np/_wrapper.py @@ -678,7 +678,9 @@ def round_(a, decimals=0, out=None): round = round_ -def clip(a, a_min, a_max, out=None): +def clip(a, a_min=None, a_max=None, out=None): + # np.clip requires both a_min and a_max not None, while ndarray.clip + # allows of then be None. Allow both here. arr = asarray(a) return arr.clip(a_min, a_max, out=out) diff --git a/torch_np/tests/numpy_tests/core/test_multiarray.py b/torch_np/tests/numpy_tests/core/test_multiarray.py index d55e2c9b..74c8a884 100644 --- a/torch_np/tests/numpy_tests/core/test_multiarray.py +++ b/torch_np/tests/numpy_tests/core/test_multiarray.py @@ -4037,14 +4037,14 @@ def test_datetime(self): assert_equal(np.amin(a), a[3]) assert_equal(np.amax(a), a[3]) -@pytest.mark.xfail(reason='TODO') + class TestNewaxis: def test_basic(self): sk = np.array([0, -0.1, 0.1]) res = 250*sk[:, np.newaxis] assert_almost_equal(res.ravel(), 250*sk) -@pytest.mark.xfail(reason='TODO') + class TestClip: def _check_range(self, x, cmin, cmax): assert_(np.all(x >= cmin)) @@ -4084,6 +4084,7 @@ def _clip_type(self, type_group, array_max, self._check_range(x, expected_min, expected_max) return x + @pytest.mark.skip(reason="endianness") def test_basic(self): for inplace in [False, True]: self._clip_type( @@ -4101,12 +4102,6 @@ def test_basic(self): self._clip_type( 'uint', 1024, -120, 100, inplace=inplace, expected_min=0) - def test_record_array(self): - rec = np.array([(-5, 2.0, 3.0), (5.0, 4.0, 3.0)], - dtype=[('x', ' Date: Fri, 24 Feb 2023 23:22:17 +0300 Subject: [PATCH 09/16] add ndarray.conj, conjugate; un-xfail test_multiarray::TestStats --- torch_np/_detail/_reductions.py | 7 ++ torch_np/_ndarray.py | 3 + .../tests/numpy_tests/core/test_multiarray.py | 69 ++++++------------- 3 files changed, 31 insertions(+), 48 deletions(-) diff --git a/torch_np/_detail/_reductions.py b/torch_np/_detail/_reductions.py index ac093699..f6fa9864 100644 --- a/torch_np/_detail/_reductions.py +++ b/torch_np/_detail/_reductions.py @@ -143,11 +143,18 @@ def mean(tensor, axis=None, dtype=None, *, where=NoValue): dtype = _atleast_float(dtype, tensor.dtype) + is_half = dtype == torch.float16 + if is_half: + dtype=torch.float32 + if axis is None: result = tensor.mean(dtype=dtype) else: result = tensor.mean(dtype=dtype, dim=axis) + if is_half: + result = result.to(torch.float16) + return result diff --git a/torch_np/_ndarray.py b/torch_np/_ndarray.py index 4fbccc0b..8b7fac31 100644 --- a/torch_np/_ndarray.py +++ b/torch_np/_ndarray.py @@ -343,6 +343,9 @@ def __irshift__(self, other): __pos__ = _unary_ufuncs.positive __neg__ = _unary_ufuncs.negative + conjugate = _unary_ufuncs.conjugate + conj = conjugate + ### methods to match namespace functions def squeeze(self, axis=None): diff --git a/torch_np/tests/numpy_tests/core/test_multiarray.py b/torch_np/tests/numpy_tests/core/test_multiarray.py index 74c8a884..f31ff9ca 100644 --- a/torch_np/tests/numpy_tests/core/test_multiarray.py +++ b/torch_np/tests/numpy_tests/core/test_multiarray.py @@ -5046,17 +5046,14 @@ def _std(a, **args): return a.std(**args) -@pytest.mark.xfail(reason='TODO') class TestStats: funcs = [_mean, _var, _std] def setup_method(self): - np.random.seed(range(3)) + np.random.seed(3) self.rmat = np.random.random((4, 5)) self.cmat = self.rmat + 1j * self.rmat - self.omat = np.array([Decimal(repr(r)) for r in self.rmat.flat]) - self.omat = self.omat.reshape(4, 5) def test_python_type(self): for x in (np.float16(1.), 1, 1., 1+0j): @@ -5093,16 +5090,6 @@ def test_dtype_from_input(self): icodes = np.typecodes['AllInteger'] fcodes = np.typecodes['AllFloat'] - # object type - for f in self.funcs: - mat = np.array([[Decimal(1)]*3]*3) - tgt = mat.dtype.type - res = f(mat, axis=1).dtype.type - assert_(res is tgt) - # scalar case - res = type(f(mat, axis=None)) - assert_(res is Decimal) - # integer types for f in self.funcs: for c in icodes: @@ -5137,6 +5124,7 @@ def test_dtype_from_input(self): res = f(mat, axis=None).dtype.type assert_(res is tgt) + @pytest.mark.xfail(reason='TODO: dtype in reductions') def test_dtype_from_dtype(self): mat = np.eye(3) @@ -5182,29 +5170,29 @@ def test_ddof_too_big(self): dim = self.rmat.shape[1] for f in [_var, _std]: for ddof in range(dim, dim + 2): - with warnings.catch_warnings(record=True) as w: - warnings.simplefilter('always') + # with warnings.catch_warnings(record=True) as w: + # warnings.simplefilter('always') res = f(self.rmat, axis=1, ddof=ddof) assert_(not (res < 0).any()) - assert_(len(w) > 0) - assert_(issubclass(w[0].category, RuntimeWarning)) + # assert_(len(w) > 0) + # assert_(issubclass(w[0].category, RuntimeWarning)) def test_empty(self): A = np.zeros((0, 3)) for f in self.funcs: for axis in [0, None]: - with warnings.catch_warnings(record=True) as w: - warnings.simplefilter('always') + # with warnings.catch_warnings(record=True) as w: + # warnings.simplefilter('always') assert_(np.isnan(f(A, axis=axis)).all()) - assert_(len(w) > 0) - assert_(issubclass(w[0].category, RuntimeWarning)) + # assert_(len(w) > 0) + # assert_(issubclass(w[0].category, RuntimeWarning)) for axis in [1]: - with warnings.catch_warnings(record=True) as w: - warnings.simplefilter('always') + # with warnings.catch_warnings(record=True) as w: + # warnings.simplefilter('always') assert_equal(f(A, axis=axis), np.zeros([])) def test_mean_values(self): - for mat in [self.rmat, self.cmat, self.omat]: + for mat in [self.rmat, self.cmat]: for axis in [0, 1]: tgt = mat.sum(axis=axis) res = _mean(mat, axis=axis) * mat.shape[axis] @@ -5222,9 +5210,10 @@ def test_mean_float16(self): def test_mean_axis_error(self): # Ensure that AxisError is raised instead of IndexError when axis is # out of bounds, see gh-15817. - with assert_raises(np.exceptions.AxisError): + with assert_raises(np.AxisError): np.arange(10).mean(axis=2) + @pytest.mark.xfail(reason='implement mean(..., where=...)') def test_mean_where(self): a = np.arange(16).reshape((4, 4)) wh_full = np.array([[False, True, False, True], @@ -5262,7 +5251,7 @@ def test_mean_where(self): assert_equal(np.mean(a, where=False), np.nan) def test_var_values(self): - for mat in [self.rmat, self.cmat, self.omat]: + for mat in [self.rmat, self.cmat]: for axis in [0, 1, None]: msqr = _mean(mat * mat.conj(), axis=axis) mean = _mean(mat, axis=axis) @@ -5295,6 +5284,7 @@ def test_var_dimensions(self): res = _var(mat, axis=axis) assert_almost_equal(res, tgt) + @pytest.mark.skip(reason='endianness') def test_var_complex_byteorder(self): # Test that var fast-path does not cause failures for complex arrays # with non-native byteorder @@ -5305,9 +5295,10 @@ def test_var_complex_byteorder(self): def test_var_axis_error(self): # Ensure that AxisError is raised instead of IndexError when axis is # out of bounds, see gh-15817. - with assert_raises(np.exceptions.AxisError): + with assert_raises(np.AxisError): np.arange(10).var(axis=2) + @pytest.mark.xfail(reason="implement var(..., where=...)") def test_var_where(self): a = np.arange(25).reshape((5, 5)) wh_full = np.array([[False, True, False, True, True], @@ -5346,12 +5337,13 @@ def test_var_where(self): assert_equal(np.var(a, where=False), np.nan) def test_std_values(self): - for mat in [self.rmat, self.cmat, self.omat]: + for mat in [self.rmat, self.cmat]: for axis in [0, 1, None]: tgt = np.sqrt(_var(mat, axis=axis)) res = _std(mat, axis=axis) assert_almost_equal(res, tgt) + @pytest.mark.xfail(reason="implement std(..., where=...)") def test_std_where(self): a = np.arange(25).reshape((5,5))[::-1] whf = np.array([[False, True, False, True, True], @@ -5396,25 +5388,6 @@ def test_std_where(self): with pytest.warns(RuntimeWarning) as w: assert_equal(np.std(a, where=False), np.nan) - def test_subclass(self): - class TestArray(np.ndarray): - def __new__(cls, data, info): - result = np.array(data) - result = result.view(cls) - result.info = info - return result - - def __array_finalize__(self, obj): - self.info = getattr(obj, "info", '') - - dat = TestArray([[1, 2, 3, 4], [5, 6, 7, 8]], 'jubba') - res = dat.mean(1) - assert_(res.info == dat.info) - res = dat.std(1) - assert_(res.info == dat.info) - res = dat.var(1) - assert_(res.info == dat.info) - class TestVdot: def test_basic(self): From f135442e2536307866faf0fba269dfdbce67d00c Mon Sep 17 00:00:00 2001 From: Evgeni Burovski Date: Sat, 25 Feb 2023 00:18:45 +0300 Subject: [PATCH 10/16] BUG: dtype is effectively keyword-only --- torch_np/_wrapper.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/torch_np/_wrapper.py b/torch_np/_wrapper.py index 3242defc..4ba6618c 100644 --- a/torch_np/_wrapper.py +++ b/torch_np/_wrapper.py @@ -403,7 +403,7 @@ def fill_diagonal(a, val, wrap=False): def trace(a, offset=0, axis1=0, axis2=1, dtype=None, out=None): arr = asarray(a) - return arr.trace(offset, axis1, axis2, dtype, out=out) + return arr.trace(offset, axis1, axis2, dtype=dtype, out=out) ###### misc/unordered From 12e07704e93224772096e802f140d5a38b36a03f Mon Sep 17 00:00:00 2001 From: Evgeni Burovski Date: Sat, 25 Feb 2023 11:43:14 +0300 Subject: [PATCH 11/16] ENH: dot --- autogen/numpy_api_dump.py | 4 - torch_np/_detail/_reductions.py | 2 +- torch_np/_detail/implementations.py | 16 ++- torch_np/_wrapper.py | 12 ++- .../tests/numpy_tests/core/test_multiarray.py | 99 ++++++++----------- 5 files changed, 64 insertions(+), 69 deletions(-) diff --git a/autogen/numpy_api_dump.py b/autogen/numpy_api_dump.py index c85c1b1a..096a1510 100644 --- a/autogen/numpy_api_dump.py +++ b/autogen/numpy_api_dump.py @@ -220,10 +220,6 @@ def disp(mesg, device=None, linefeed=True): raise NotImplementedError -def dot(a, b, out=None): - raise NotImplementedError - - def ediff1d(ary, to_end=None, to_begin=None): raise NotImplementedError diff --git a/torch_np/_detail/_reductions.py b/torch_np/_detail/_reductions.py index f6fa9864..17891303 100644 --- a/torch_np/_detail/_reductions.py +++ b/torch_np/_detail/_reductions.py @@ -145,7 +145,7 @@ def mean(tensor, axis=None, dtype=None, *, where=NoValue): is_half = dtype == torch.float16 if is_half: - dtype=torch.float32 + dtype = torch.float32 if axis is None: result = tensor.mean(dtype=dtype) diff --git a/torch_np/_detail/implementations.py b/torch_np/_detail/implementations.py index 56ff3d92..8aeb304d 100644 --- a/torch_np/_detail/implementations.py +++ b/torch_np/_detail/implementations.py @@ -185,7 +185,6 @@ def diagonal(tensor, offset=0, axis1=0, axis2=1): return result - # ### splits ### @@ -721,7 +720,6 @@ def inner(t_a, t_b): result = result.to(torch.bool) return result - def vdot(t_a, t_b, /): @@ -754,3 +752,17 @@ def vdot(t_a, t_b, /): result = result.to(torch.bool) return result + + +def dot(t_a, t_b): + if t_a.ndim == 0 or t_b.ndim == 0: + result = t_a * t_b + elif t_a.ndim == 1 and t_b.ndim == 1: + result = torch.dot(t_a, t_b) + elif t_a.ndim == 1: + result = torch.mv(t_b.T, t_a).T + elif t_b.ndim == 1: + result = torch.mv(t_a, t_b) + else: + result = torch.matmul(t_a, t_b) + return result diff --git a/torch_np/_wrapper.py b/torch_np/_wrapper.py index 4ba6618c..f159a974 100644 --- a/torch_np/_wrapper.py +++ b/torch_np/_wrapper.py @@ -400,7 +400,6 @@ def fill_diagonal(a, val, wrap=False): return asarray(result) - def trace(a, offset=0, axis1=0, axis2=1, dtype=None, out=None): arr = asarray(a) return arr.trace(offset, axis1, axis2, dtype=dtype, out=out) @@ -500,6 +499,13 @@ def vdot(a, b, /): result = _impl.vdot(t_a, t_b) return result.item() + +def dot(a, b, out=None): + t_a, t_b = _helpers.to_tensors(a, b) + result = _impl.dot(t_a, t_b) + return _helpers.result_or_out(result, out) + + ###### module-level queries of object properties @@ -1100,10 +1106,6 @@ def array_equiv(a1, a2): return result -def dot(): - raise NotImplementedError - - def common_type(): raise NotImplementedError diff --git a/torch_np/tests/numpy_tests/core/test_multiarray.py b/torch_np/tests/numpy_tests/core/test_multiarray.py index f31ff9ca..3d917e38 100644 --- a/torch_np/tests/numpy_tests/core/test_multiarray.py +++ b/torch_np/tests/numpy_tests/core/test_multiarray.py @@ -5464,15 +5464,30 @@ def test_vdot_uncontiguous_2(self): np.vdot(a.flatten(), b.flatten())) -@pytest.mark.xfail(reason='TODO') class TestDot: def setup_method(self): np.random.seed(128) - self.A = np.random.rand(4, 2) - self.b1 = np.random.rand(2, 1) - self.b2 = np.random.rand(2) - self.b3 = np.random.rand(1, 2) - self.b4 = np.random.rand(4) + + # Numpy guarantees the random stream, and we don't. So inline the + # values from numpy 1.24.1 + # self.A = np.random.rand(4, 2) + self.A = np.array([[0.86663704, 0.26314485], + [0.13140848, 0.04159344], + [0.23892433, 0.6454746 ], + [0.79059935, 0.60144244]]) + + # self.b1 = np.random.rand(2, 1) + self.b1 = np.array([[0.33429937], [0.11942846]]) + + # self.b2 = np.random.rand(2) + self.b2 = np.array([0.30913305, 0.10972379]) + + # self.b3 = np.random.rand(1, 2) + self.b3 = np.array([[0.60211331, 0.25128496]]) + + # self.b4 = np.random.rand(4) + self.b4 = np.array([0.29968129, 0.517116, 0.71520252, 0.9314494]) + self.N = 7 def test_dotmatmat(self): @@ -5541,16 +5556,26 @@ def test_dotcolumnvect2(self): def test_dotvecscalar(self): np.random.seed(100) - b1 = np.random.rand(1, 1) - b2 = np.random.rand(1, 4) + # Numpy guarantees the random stream, and we don't. So inline the + # values from numpy 1.24.1 + # b1 = np.random.rand(1, 1) + b1 = np.array([[0.54340494]]) + + # b2 = np.random.rand(1, 4) + b2 = np.array([[0.27836939, 0.42451759, 0.84477613, 0.00471886]]) + res = np.dot(b1, b2) tgt = np.array([[0.15126730, 0.23068496, 0.45905553, 0.00256425]]) assert_almost_equal(res, tgt, decimal=self.N) def test_dotvecscalar2(self): np.random.seed(100) - b1 = np.random.rand(4, 1) - b2 = np.random.rand(1, 1) + # b1 = np.random.rand(4, 1) + b1 = np.array([[0.54340494], [0.27836939], [0.42451759], [0.84477613]]) + + # b2 = np.random.rand(1, 1) + b2 = np.array([[0.00471886]]) + res = np.dot(b1, b2) tgt = np.array([[0.00256425],[0.00131359],[0.00200324],[ 0.00398638]]) assert_almost_equal(res, tgt, decimal=self.N) @@ -5566,39 +5591,7 @@ def test_all(self): assert_(res.shape == tgt.shape) assert_almost_equal(res, tgt, decimal=self.N) - def test_vecobject(self): - class Vec: - def __init__(self, sequence=None): - if sequence is None: - sequence = [] - self.array = np.array(sequence) - - def __add__(self, other): - out = Vec() - out.array = self.array + other.array - return out - - def __sub__(self, other): - out = Vec() - out.array = self.array - other.array - return out - - def __mul__(self, other): # with scalar - out = Vec(self.array.copy()) - out.array *= other - return out - - def __rmul__(self, other): - return self*other - - U_non_cont = np.transpose([[1., 1.], [1., 2.]]) - U_cont = np.ascontiguousarray(U_non_cont) - x = np.array([Vec([1., 0.]), Vec([0., 1.])]) - zeros = np.array([Vec([0., 0.]), Vec([0., 0.])]) - zeros_test = np.dot(U_cont, x) - np.dot(U_non_cont, x) - assert_equal(zeros[0].array, zeros_test[0].array) - assert_equal(zeros[1].array, zeros_test[1].array) - + @pytest.mark.skip(reason='numpy internals') def test_dot_2args(self): from numpy.core.multiarray import dot @@ -5609,6 +5602,7 @@ def test_dot_2args(self): d = dot(a, b) assert_allclose(c, d) + @pytest.mark.skip(reason='numpy internals') def test_dot_3args(self): from numpy.core.multiarray import dot @@ -5631,6 +5625,7 @@ def test_dot_3args(self): assert_(r is dot(f, v, r)) assert_array_equal(r2, r) + @pytest.mark.skip(reason='numpy internals') def test_dot_3args_errors(self): from numpy.core.multiarray import dot @@ -5661,6 +5656,7 @@ def test_dot_3args_errors(self): r = np.empty((1024, 32), dtype=int) assert_raises(ValueError, dot, f, v, r) + @pytest.mark.skip(reason="TODO order='F'") def test_dot_array_order(self): a = np.array([[1, 2], [3, 4]], order='C') b = np.array([[1, 2], [3, 4]], order='F') @@ -5671,6 +5667,7 @@ def test_dot_array_order(self): assert_equal(np.dot(b, a), res) assert_equal(np.dot(b, b), res) + @pytest.mark.skip(reason='TODO: nbytes, view') def test_accelerate_framework_sgemv_fix(self): def aligned_array(shape, align, dtype, order='C'): @@ -5745,18 +5742,6 @@ def test_huge_vectordot(self, dtype): res = np.dot(data, data) assert res == 2**30+100 - def test_dtype_discovery_fails(self): - # See gh-14247, error checking was missing for failed dtype discovery - class BadObject(object): - def __array__(self): - raise TypeError("just this tiny mint leaf") - - with pytest.raises(TypeError): - np.dot(BadObject(), BadObject()) - - with pytest.raises(TypeError): - np.dot(3.0, BadObject()) - class MatmulCommon: """Common tests for '@' operator and numpy.matmul. @@ -5764,7 +5749,7 @@ class MatmulCommon: """ # Should work with these types. Will want to add # "O" at some point - types = "?bhilqBHILQefdgFDGO" + types = "?bhilqBefdFD" def test_exceptions(self): dims = [ @@ -5975,7 +5960,7 @@ def test_matrix_matrix_values(self): assert_equal(res, tgt12_21) -@pytest.mark.xfail(reason='TODO') +@pytest.mark.xfail(reason='TODO: matmul (ufunc wrapping goes south?)') class TestMatmul(MatmulCommon): matmul = np.matmul From 8f37665bad46fe99c1dcbecc08eaff1f28303063 Mon Sep 17 00:00:00 2001 From: Evgeni Burovski Date: Sat, 25 Feb 2023 12:21:12 +0300 Subject: [PATCH 12/16] BUG: random: size=() returns a 0D array, not scalar --- torch_np/random.py | 14 +++++++------- torch_np/tests/numpy_tests/core/test_multiarray.py | 1 - 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/torch_np/random.py b/torch_np/random.py index 7ce365e0..546def3f 100644 --- a/torch_np/random.py +++ b/torch_np/random.py @@ -29,8 +29,8 @@ ] -def array_or_scalar(values, py_type=float): - if values.numel() == 1: +def array_or_scalar(values, py_type=float, size=None): + if size is None: return py_type(values.item()) else: return asarray(values) @@ -45,7 +45,7 @@ def random_sample(size=None): if size is None: size = () values = torch.empty(size, dtype=_default_dtype).uniform_() - return array_or_scalar(values) + return array_or_scalar(values, size=size) def rand(*size): @@ -60,19 +60,19 @@ def uniform(low=0.0, high=1.0, size=None): if size is None: size = () values = torch.empty(size, dtype=_default_dtype).uniform_(low, high) - return array_or_scalar(values) + return array_or_scalar(values, size=size) def randn(*size): values = torch.randn(size, dtype=_default_dtype) - return array_or_scalar(values) + return array_or_scalar(values, size=size) def normal(loc=0.0, scale=1.0, size=None): if size is None: size = () values = torch.empty(size, dtype=_default_dtype).normal_(loc, scale) - return array_or_scalar(values) + return array_or_scalar(values, size=size) def shuffle(x): @@ -90,7 +90,7 @@ def randint(low, high=None, size=None): if high is None: low, high = 0, low values = torch.randint(low, high, size=size) - return array_or_scalar(values, int) + return array_or_scalar(values, int, size=size) def choice(a, size=None, replace=True, p=None): diff --git a/torch_np/tests/numpy_tests/core/test_multiarray.py b/torch_np/tests/numpy_tests/core/test_multiarray.py index 3d917e38..33fd6e20 100644 --- a/torch_np/tests/numpy_tests/core/test_multiarray.py +++ b/torch_np/tests/numpy_tests/core/test_multiarray.py @@ -3627,7 +3627,6 @@ def test_assign_mask2(self): assert_array_equal(x, np.array([[1, 10, 3, 4], [5, 6, 7, 8]])) -@pytest.mark.xfail(reason='TODO') class TestArgmaxArgminCommon: sizes = [(), (3,), (3, 2), (2, 3), From b612a0ff1eeb533bb3d5c41691f1976ace89bf3b Mon Sep 17 00:00:00 2001 From: Evgeni Burovski Date: Sat, 25 Feb 2023 12:48:30 +0300 Subject: [PATCH 13/16] TST: un-xfail tests for argmin/argmax --- .../tests/numpy_tests/core/test_multiarray.py | 94 +++++++------------ 1 file changed, 32 insertions(+), 62 deletions(-) diff --git a/torch_np/tests/numpy_tests/core/test_multiarray.py b/torch_np/tests/numpy_tests/core/test_multiarray.py index 33fd6e20..e742afcd 100644 --- a/torch_np/tests/numpy_tests/core/test_multiarray.py +++ b/torch_np/tests/numpy_tests/core/test_multiarray.py @@ -3711,6 +3711,7 @@ def test_np_argmin_argmax_keepdims(self, size, axis, method): method(arr.T, axis=axis, out=wrong_outarray, keepdims=True) + @pytest.mark.xfail(reason="TODO: implement choose") @pytest.mark.parametrize('method', ['max', 'min']) def test_all(self, method): a = np.random.normal(0, 1, (4, 5, 6, 7, 8)) @@ -3753,15 +3754,7 @@ def test_ret_is_out(self, ndim, method): ret = arg_method(axis=0, out=out) assert ret is out - @pytest.mark.parametrize('np_array, method, idx, val', - [(np.zeros, 'argmax', 5942, "as"), - (np.ones, 'argmin', 6001, "0")]) - def test_unicode(self, np_array, method, idx, val): - d = np_array(6031, dtype=' Date: Sat, 25 Feb 2023 12:48:57 +0300 Subject: [PATCH 14/16] TST: xfail a test with max(0D array and explicit axis) --- torch_np/tests/numpy_tests/core/test_multiarray.py | 1 + 1 file changed, 1 insertion(+) diff --git a/torch_np/tests/numpy_tests/core/test_multiarray.py b/torch_np/tests/numpy_tests/core/test_multiarray.py index e742afcd..57714da5 100644 --- a/torch_np/tests/numpy_tests/core/test_multiarray.py +++ b/torch_np/tests/numpy_tests/core/test_multiarray.py @@ -3997,6 +3997,7 @@ def test_scalar(self): assert_raises(np.AxisError, np.amax, 1, 1) assert_raises(np.AxisError, np.amin, 1, 1) + pytest.xfail(reason='min/max/argmin/argmax on 0D arrays & axis') assert_equal(np.amax(1, axis=0), 1) assert_equal(np.amin(1, axis=0), 1) assert_equal(np.amax(1, axis=None), 1) From 0e2438e616fe6aaff03982d76ae47fc39eaf60aa Mon Sep 17 00:00:00 2001 From: Evgeni Burovski Date: Sat, 25 Feb 2023 23:07:45 +0300 Subject: [PATCH 15/16] ENH: unique --- autogen/numpy_api_dump.py | 12 -- torch_np/_detail/implementations.py | 37 +++++ torch_np/_wrapper.py | 28 ++++ .../tests/numpy_tests/lib/test_arraysetops.py | 140 ++++++------------ .../numpy_tests/lib/test_function_base.py | 11 +- 5 files changed, 117 insertions(+), 111 deletions(-) diff --git a/autogen/numpy_api_dump.py b/autogen/numpy_api_dump.py index 096a1510..4ac61f12 100644 --- a/autogen/numpy_api_dump.py +++ b/autogen/numpy_api_dump.py @@ -779,18 +779,6 @@ def union1d(ar1, ar2): raise NotImplementedError -def unique( - ar, - return_index=False, - return_inverse=False, - return_counts=False, - axis=None, - *, - equal_nan=True, -): - raise NotImplementedError - - def unpackbits(a, /, axis=None, count=None, bitorder="big"): raise NotImplementedError diff --git a/torch_np/_detail/implementations.py b/torch_np/_detail/implementations.py index 8aeb304d..ef9f9c52 100644 --- a/torch_np/_detail/implementations.py +++ b/torch_np/_detail/implementations.py @@ -766,3 +766,40 @@ def dot(t_a, t_b): else: result = torch.matmul(t_a, t_b) return result + + +# ### unique et al ### + + +def unique( + tensor, + return_index=False, + return_inverse=False, + return_counts=False, + axis=None, + *, + equal_nan=True, +): + if return_index or not equal_nan: + raise NotImplementedError + + if axis is None: + tensor = tensor.ravel() + axis = 0 + axis = _util.normalize_axis_index(axis, tensor.ndim) + + is_half = tensor.dtype == torch.float16 + if is_half: + tensor = tensor.to(torch.float32) + + result = torch.unique( + tensor, return_inverse=return_inverse, return_counts=return_counts, dim=axis + ) + + if is_half: + if isinstance(result, tuple): + result = (result[0].to(torch.float16),) + result[1:] + else: + result = result.to(torch.float16) + + return result diff --git a/torch_np/_wrapper.py b/torch_np/_wrapper.py index f159a974..fcb204c5 100644 --- a/torch_np/_wrapper.py +++ b/torch_np/_wrapper.py @@ -1153,6 +1153,34 @@ def argsort(a, axis=-1, kind=None, order=None): return asarray(result) +# ### unqiue et al ### + + +def unique( + ar, + return_index=False, + return_inverse=False, + return_counts=False, + axis=None, + *, + equal_nan=True, +): + tensor = asarray(ar).get() + result = _impl.unique( + tensor, + return_index=return_index, + return_inverse=return_inverse, + return_counts=return_counts, + axis=axis, + equal_nan=equal_nan, + ) + + if isinstance(result, tuple): + return tuple(asarray(x) for x in result) + else: + return asarray(result) + + ###### mapping from numpy API objects to wrappers from this module ###### # All is in the mapping dict in _mapping.py diff --git a/torch_np/tests/numpy_tests/lib/test_arraysetops.py b/torch_np/tests/numpy_tests/lib/test_arraysetops.py index 06129392..67493abe 100644 --- a/torch_np/tests/numpy_tests/lib/test_arraysetops.py +++ b/torch_np/tests/numpy_tests/lib/test_arraysetops.py @@ -4,8 +4,11 @@ import torch_np as np from torch_np.testing import (assert_array_equal, assert_equal) + +from torch_np import unique + from numpy.lib.arraysetops import ( - ediff1d, intersect1d, setxor1d, union1d, setdiff1d, unique, in1d, isin + ediff1d, intersect1d, setxor1d, union1d, setdiff1d, in1d, isin ) import pytest from pytest import raises as assert_raises @@ -615,7 +618,6 @@ def test_manyways(self): assert_array_equal(c1, c2) -@pytest.mark.xfail(reason='TODO') class TestUnique: def test_unique_1d(self): @@ -627,10 +629,10 @@ def check_all(a, b, i1, i2, c, dt): v = unique(a) assert_array_equal(v, b, msg) - msg = base_msg.format('return_index', dt) - v, j = unique(a, True, False, False) - assert_array_equal(v, b, msg) - assert_array_equal(j, i1, msg) + # msg = base_msg.format('return_index', dt) + # v, j = unique(a, True, False, False) + # assert_array_equal(v, b, msg) + # assert_array_equal(j, i1, msg) msg = base_msg.format('return_inverse', dt) v, j = unique(a, False, True, False) @@ -642,17 +644,17 @@ def check_all(a, b, i1, i2, c, dt): assert_array_equal(v, b, msg) assert_array_equal(j, c, msg) - msg = base_msg.format('return_index and return_inverse', dt) - v, j1, j2 = unique(a, True, True, False) - assert_array_equal(v, b, msg) - assert_array_equal(j1, i1, msg) - assert_array_equal(j2, i2, msg) + # msg = base_msg.format('return_index and return_inverse', dt) + # v, j1, j2 = unique(a, True, True, False) + # assert_array_equal(v, b, msg) + # assert_array_equal(j1, i1, msg) + # assert_array_equal(j2, i2, msg) - msg = base_msg.format('return_index and return_counts', dt) - v, j1, j2 = unique(a, True, False, True) - assert_array_equal(v, b, msg) - assert_array_equal(j1, i1, msg) - assert_array_equal(j2, c, msg) + # msg = base_msg.format('return_index and return_counts', dt) + # v, j1, j2 = unique(a, True, False, True) + # assert_array_equal(v, b, msg) + # assert_array_equal(j1, i1, msg) + # assert_array_equal(j2, c, msg) msg = base_msg.format('return_inverse and return_counts', dt) v, j1, j2 = unique(a, False, True, True) @@ -660,13 +662,13 @@ def check_all(a, b, i1, i2, c, dt): assert_array_equal(j1, i2, msg) assert_array_equal(j2, c, msg) - msg = base_msg.format(('return_index, return_inverse ' - 'and return_counts'), dt) - v, j1, j2, j3 = unique(a, True, True, True) - assert_array_equal(v, b, msg) - assert_array_equal(j1, i1, msg) - assert_array_equal(j2, i2, msg) - assert_array_equal(j3, c, msg) + # msg = base_msg.format(('return_index, return_inverse ' + # 'and return_counts'), dt) + # v, j1, j2, j3 = unique(a, True, True, True) + # assert_array_equal(v, b, msg) + # assert_array_equal(j1, i1, msg) + # assert_array_equal(j2, i2, msg) + # assert_array_equal(j3, c, msg) a = [5, 7, 1, 2, 1, 5, 7]*10 b = [1, 2, 5, 7] @@ -678,30 +680,20 @@ def check_all(a, b, i1, i2, c, dt): types = [] types.extend(np.typecodes['AllInteger']) types.extend(np.typecodes['AllFloat']) - types.append('datetime64[D]') - types.append('timedelta64[D]') for dt in types: + + if dt in 'FD': + # RuntimeError: "unique" not implemented for 'ComplexFloat' + continue + aa = np.array(a, dt) bb = np.array(b, dt) check_all(aa, bb, i1, i2, c, dt) - # test for object arrays - dt = 'O' - aa = np.empty(len(a), dt) - aa[:] = a - bb = np.empty(len(b), dt) - bb[:] = b - check_all(aa, bb, i1, i2, c, dt) - - # test for structured arrays - dt = [('', 'i'), ('', 'i')] - aa = np.array(list(zip(a, a)), dt) - bb = np.array(list(zip(b, b)), dt) - check_all(aa, bb, i1, i2, c, dt) - # test for ticket #2799 - aa = [1. + 0.j, 1 - 1.j, 1] - assert_array_equal(np.unique(aa), [1. - 1.j, 1. + 0.j]) + # RuntimeError: "unique" not implemented for 'ComplexFloat' + # aa = [1. + 0.j, 1 - 1.j, 1] + # assert_array_equal(np.unique(aa), [1. - 1.j, 1. + 0.j]) # test for ticket #4785 a = [(1, 2), (1, 2), (2, 3)] @@ -713,23 +705,21 @@ def check_all(a, b, i1, i2, c, dt): assert_array_equal(a2, unq) assert_array_equal(a2_inv, inv) - # test for chararrays with return_inverse (gh-5099) - a = np.chararray(5) - a[...] = '' - a2, a2_inv = np.unique(a, return_inverse=True) - assert_array_equal(a2_inv, np.zeros(5)) # test for ticket #9137 a = [] - a1_idx = np.unique(a, return_index=True)[1] + # a1_idx = np.unique(a, return_index=True)[1] a2_inv = np.unique(a, return_inverse=True)[1] - a3_idx, a3_inv = np.unique(a, return_index=True, - return_inverse=True)[1:] - assert_equal(a1_idx.dtype, np.intp) + # a3_idx, a3_inv = np.unique(a, return_index=True, + # return_inverse=True)[1:] + # assert_equal(a1_idx.dtype, np.intp) assert_equal(a2_inv.dtype, np.intp) - assert_equal(a3_idx.dtype, np.intp) - assert_equal(a3_inv.dtype, np.intp) + # assert_equal(a3_idx.dtype, np.intp) + # assert_equal(a3_inv.dtype, np.intp) + + @pytest.mark.xfail(reason='unique with nans') + def test_unique_1d_2(self): # test for ticket 2111 - float a = [2.0, np.nan, 1.0, np.nan] ua = [1.0, 2.0, np.nan] @@ -752,30 +742,6 @@ def check_all(a, b, i1, i2, c, dt): assert_equal(np.unique(a, return_inverse=True), (ua, ua_inv)) assert_equal(np.unique(a, return_counts=True), (ua, ua_cnt)) - # test for ticket 2111 - datetime64 - nat = np.datetime64('nat') - a = [np.datetime64('2020-12-26'), nat, np.datetime64('2020-12-24'), nat] - ua = [np.datetime64('2020-12-24'), np.datetime64('2020-12-26'), nat] - ua_idx = [2, 0, 1] - ua_inv = [1, 2, 0, 2] - ua_cnt = [1, 1, 2] - assert_equal(np.unique(a), ua) - assert_equal(np.unique(a, return_index=True), (ua, ua_idx)) - assert_equal(np.unique(a, return_inverse=True), (ua, ua_inv)) - assert_equal(np.unique(a, return_counts=True), (ua, ua_cnt)) - - # test for ticket 2111 - timedelta - nat = np.timedelta64('nat') - a = [np.timedelta64(1, 'D'), nat, np.timedelta64(1, 'h'), nat] - ua = [np.timedelta64(1, 'h'), np.timedelta64(1, 'D'), nat] - ua_idx = [2, 0, 1] - ua_inv = [1, 2, 0, 2] - ua_cnt = [1, 1, 2] - assert_equal(np.unique(a), ua) - assert_equal(np.unique(a, return_index=True), (ua, ua_idx)) - assert_equal(np.unique(a, return_inverse=True), (ua, ua_inv)) - assert_equal(np.unique(a, return_counts=True), (ua, ua_cnt)) - # test for gh-19300 all_nans = [np.nan] * 4 ua = [np.nan] @@ -802,14 +768,11 @@ def test_unique_axis_list(self): assert_array_equal(unique(inp, axis=0), unique(inp_arr, axis=0), msg) assert_array_equal(unique(inp, axis=1), unique(inp_arr, axis=1), msg) + @pytest.mark.xfail(reason='TODO: implement take') def test_unique_axis(self): types = [] types.extend(np.typecodes['AllInteger']) types.extend(np.typecodes['AllFloat']) - types.append('datetime64[D]') - types.append('timedelta64[D]') - types.append([('a', int), ('b', int)]) - types.append([('a', int), ('b', float)]) for dtype in types: self._run_axis_tests(dtype) @@ -830,6 +793,7 @@ def test_unique_1d_with_axis(self, axis): uniq = unique(x, axis=axis) assert_array_equal(uniq, [1, 2, 3, 4]) + @pytest.mark.xfail(reason='unique / return_index') def test_unique_axis_zeros(self): # issue 15559 single_zero = np.empty(shape=(2, 0), dtype=np.int8) @@ -866,24 +830,11 @@ def test_unique_axis_zeros(self): assert_array_equal(unique(multiple_zeros, axis=axis), np.empty(shape=expected_shape)) - def test_unique_masked(self): - # issue 8664 - x = np.array([64, 0, 1, 2, 3, 63, 63, 0, 0, 0, 1, 2, 0, 63, 0], - dtype='uint8') - y = np.ma.masked_equal(x, 0) - - v = np.unique(y) - v2, i, c = np.unique(y, return_index=True, return_counts=True) - - msg = 'Unique returned different results when asked for index' - assert_array_equal(v.data, v2.data, msg) - assert_array_equal(v.mask, v2.mask, msg) - def test_unique_sort_order_with_axis(self): # These tests fail if sorting along axis is done by treating subarrays # as unsigned byte strings. See gh-10495. fmt = "sort order incorrect for integer type '%s'" - for dt in 'bhilq': + for dt in 'bhil': a = np.array([[-1], [0]], dt) b = np.unique(a, axis=0) assert_array_equal(a, b, fmt % dt) @@ -932,6 +883,7 @@ def _run_axis_tests(self, dtype): msg = "Unique's return_counts=True failed with axis=1" assert_array_equal(cnt, np.array([2, 1, 1]), msg) + @pytest.mark.xfail(reason='unique / return_index / nans') def test_unique_nanequals(self): # issue 20326 a = np.array([1, 1, np.nan, np.nan, np.nan]) diff --git a/torch_np/tests/numpy_tests/lib/test_function_base.py b/torch_np/tests/numpy_tests/lib/test_function_base.py index ea1b7ddc..fd7c809d 100644 --- a/torch_np/tests/numpy_tests/lib/test_function_base.py +++ b/torch_np/tests/numpy_tests/lib/test_function_base.py @@ -29,11 +29,11 @@ bartlett, blackman, delete, digitize, extract, gradient, hamming, hanning, insert, interp, kaiser, msort, piecewise, place, - select, setxor1d, trapz, trim_zeros, unique, unwrap, vectorize + select, setxor1d, trapz, trim_zeros, unwrap, vectorize ) from torch_np._detail._util import normalize_axis_tuple -from torch_np import corrcoef, cov, i0, angle, sinc, diff, meshgrid +from torch_np import corrcoef, cov, i0, angle, sinc, diff, meshgrid, unique def get_mat(n): data = np.arange(n) @@ -1864,15 +1864,16 @@ def test_array_like(self): assert_array_equal(y1, y3) -@pytest.mark.xfail(reason='TODO: implement') class TestUnique: def test_simple(self): x = np.array([4, 3, 2, 1, 1, 2, 3, 4, 0]) assert_(np.all(unique(x) == [0, 1, 2, 3, 4])) + assert_(unique(np.array([1, 1, 1, 1, 1])) == np.array([1])) - x = ['widget', 'ham', 'foo', 'bar', 'foo', 'ham'] - assert_(np.all(unique(x) == ['bar', 'foo', 'ham', 'widget'])) + + @pytest.mark.xfail(reason="unique not implemented for 'ComplexDouble'") + def test_simple_complex(self): x = np.array([5 + 6j, 1 + 1j, 1 + 10j, 10, 5 + 6j]) assert_(np.all(unique(x) == [1 + 1j, 1 + 10j, 5 + 6j, 10])) From d70cab6209a19fdaa6d26d545ce1dc500eb3a66b Mon Sep 17 00:00:00 2001 From: Evgeni Burovski Date: Mon, 27 Feb 2023 18:55:50 +0300 Subject: [PATCH 16/16] MAINT: address review comments --- torch_np/_detail/_reductions.py | 1 + torch_np/_detail/implementations.py | 2 -- torch_np/_ndarray.py | 3 +- torch_np/random.py | 14 +++++----- .../tests/numpy_tests/core/test_multiarray.py | 28 ++++++------------- 5 files changed, 18 insertions(+), 30 deletions(-) diff --git a/torch_np/_detail/_reductions.py b/torch_np/_detail/_reductions.py index 17891303..a3d0a0b7 100644 --- a/torch_np/_detail/_reductions.py +++ b/torch_np/_detail/_reductions.py @@ -145,6 +145,7 @@ def mean(tensor, axis=None, dtype=None, *, where=NoValue): is_half = dtype == torch.float16 if is_half: + # XXX revisit when the pytorch version has pytorch/pytorch#95166 dtype = torch.float32 if axis is None: diff --git a/torch_np/_detail/implementations.py b/torch_np/_detail/implementations.py index ef9f9c52..6d66625e 100644 --- a/torch_np/_detail/implementations.py +++ b/torch_np/_detail/implementations.py @@ -179,8 +179,6 @@ def trace(tensor, offset=0, axis1=0, axis2=1, dtype=None, out=None): def diagonal(tensor, offset=0, axis1=0, axis2=1): axis1 = _util.normalize_axis_index(axis1, tensor.ndim) axis2 = _util.normalize_axis_index(axis2, tensor.ndim) - if axis1 == axis2: - raise ValueError("axis1 and axis2 cannot be the same") result = torch.diagonal(tensor, offset, axis1, axis2) return result diff --git a/torch_np/_ndarray.py b/torch_np/_ndarray.py index 8b7fac31..5a0fefbc 100644 --- a/torch_np/_ndarray.py +++ b/torch_np/_ndarray.py @@ -371,8 +371,7 @@ def ravel(self, order="C"): def flatten(self, order="C"): if order != "C": raise NotImplementedError - # return a copy - result = self._tensor.ravel().clone() + result = self._tensor.flatten() return asarray(result) def nonzero(self): diff --git a/torch_np/random.py b/torch_np/random.py index 546def3f..4bc3426c 100644 --- a/torch_np/random.py +++ b/torch_np/random.py @@ -29,8 +29,8 @@ ] -def array_or_scalar(values, py_type=float, size=None): - if size is None: +def array_or_scalar(values, py_type=float, return_scalar=False): + if return_scalar: return py_type(values.item()) else: return asarray(values) @@ -45,7 +45,7 @@ def random_sample(size=None): if size is None: size = () values = torch.empty(size, dtype=_default_dtype).uniform_() - return array_or_scalar(values, size=size) + return array_or_scalar(values, return_scalar=size is None) def rand(*size): @@ -60,19 +60,19 @@ def uniform(low=0.0, high=1.0, size=None): if size is None: size = () values = torch.empty(size, dtype=_default_dtype).uniform_(low, high) - return array_or_scalar(values, size=size) + return array_or_scalar(values, return_scalar=size is None) def randn(*size): values = torch.randn(size, dtype=_default_dtype) - return array_or_scalar(values, size=size) + return array_or_scalar(values, return_scalar=size is None) def normal(loc=0.0, scale=1.0, size=None): if size is None: size = () values = torch.empty(size, dtype=_default_dtype).normal_(loc, scale) - return array_or_scalar(values, size=size) + return array_or_scalar(values, return_scalar=size is None) def shuffle(x): @@ -90,7 +90,7 @@ def randint(low, high=None, size=None): if high is None: low, high = 0, low values = torch.randint(low, high, size=size) - return array_or_scalar(values, int, size=size) + return array_or_scalar(values, int, return_scalar=size is None) def choice(a, size=None, replace=True, p=None): diff --git a/torch_np/tests/numpy_tests/core/test_multiarray.py b/torch_np/tests/numpy_tests/core/test_multiarray.py index 57714da5..cfe17a23 100644 --- a/torch_np/tests/numpy_tests/core/test_multiarray.py +++ b/torch_np/tests/numpy_tests/core/test_multiarray.py @@ -1686,7 +1686,7 @@ def assert_c(arr): assert_fortran(a.copy('F')) assert_c(a.copy('A')) - @pytest.mark.xfail(reason="no .ctypes attribute") + @pytest.mark.skip(reason="no .ctypes attribute") @pytest.mark.parametrize("dtype", [np.int32]) def test__deepcopy__(self, dtype): # Force the entry of NULLs into array @@ -1715,7 +1715,7 @@ def test_argsort(self): assert_equal(a.copy().argsort(kind=kind), a, msg) assert_equal(b.copy().argsort(kind=kind), b, msg) - @pytest.mark.xfail(reason='argsort complex') + @pytest.mark.skip(reason='argsort complex') def test_argsort_complex(self): a = np.arange(101, dtype=np.float32) b = np.flip(a) @@ -2546,7 +2546,7 @@ def test_dot_out_mem_overlap(self): assert_raises(ValueError, np.dot, a, b, out=b[::2]) assert_raises(ValueError, np.dot, a, b, out=b.T) - @pytest.mark.xfail(reason="TODO [::-1]") + @pytest.mark.xfail(reason="TODO: overlapping memor in matmul") def test_matmul_out(self): # overlapping memory a = np.arange(18).reshape(2, 3, 3) @@ -2568,14 +2568,14 @@ def test_diagonal(self): assert_raises(np.AxisError, a.diagonal, axis1=0, axis2=5) assert_raises(np.AxisError, a.diagonal, axis1=5, axis2=0) assert_raises(np.AxisError, a.diagonal, axis1=5, axis2=5) - assert_raises(ValueError, a.diagonal, axis1=1, axis2=1) + assert_raises((ValueError, RuntimeError), a.diagonal, axis1=1, axis2=1) b = np.arange(8).reshape((2, 2, 2)) assert_equal(b.diagonal(), [[0, 6], [1, 7]]) assert_equal(b.diagonal(0), [[0, 6], [1, 7]]) assert_equal(b.diagonal(1), [[2], [3]]) assert_equal(b.diagonal(-1), [[4], [5]]) - assert_raises(ValueError, b.diagonal, axis1=0, axis2=0) + assert_raises((ValueError, RuntimeError), b.diagonal, axis1=0, axis2=0) assert_equal(b.diagonal(0, 1, 2), [[0, 3], [4, 7]]) assert_equal(b.diagonal(0, 0, 1), [[0, 6], [1, 7]]) assert_equal(b.diagonal(offset=1, axis1=0, axis2=2), [[1], [3]]) @@ -2805,7 +2805,6 @@ def test_swapaxes(self): if k == 1: b = c - @pytest.mark.xfail(reason="TODO: ndarray.conjugate") def test_conjugate(self): a = np.array([1-1j, 1+1j, 23+23.0j]) ac = a.conj() @@ -2833,17 +2832,8 @@ def test_conjugate(self): assert_equal(ac, a.conjugate()) assert_equal(ac, np.conjugate(a)) - a = np.array([1-1j, 1+1j, 1, 2.0], object) - ac = a.conj() - assert_equal(ac, [k.conjugate() for k in a]) - assert_equal(ac, a.conjugate()) - assert_equal(ac, np.conjugate(a)) - a = np.array([1-1j, 1, 2.0, 'f'], object) - assert_raises(TypeError, lambda: a.conj()) - assert_raises(TypeError, lambda: a.conjugate()) - - @pytest.mark.xfail(reason="TODO: ndarray.conjugate") + @pytest.mark.xfail(reason="TODO: ndarray.conjugate with out") def test_conjugate_out(self): # Minimal test for the out argument being passed on correctly # NOTE: The ability to pass `out` is currently undocumented! @@ -3754,7 +3744,7 @@ def test_ret_is_out(self, ndim, method): ret = arg_method(axis=0, out=out) assert ret is out - @pytest.mark.xfail(reason='FIXME: keepdims w/ positional args?') + @pytest.mark.xfail(reason='FIXME: out w/ positional args?') @pytest.mark.parametrize('arr_method, np_method', [('argmax', np.argmax), ('argmin', np.argmin)]) @@ -5438,7 +5428,7 @@ class TestDot: def setup_method(self): np.random.seed(128) - # Numpy guarantees the random stream, and we don't. So inline the + # Numpy and pytorch random streams differ, so inline the # values from numpy 1.24.1 # self.A = np.random.rand(4, 2) self.A = np.array([[0.86663704, 0.26314485], @@ -5626,7 +5616,7 @@ def test_dot_3args_errors(self): r = np.empty((1024, 32), dtype=int) assert_raises(ValueError, dot, f, v, r) - @pytest.mark.skip(reason="TODO order='F'") + @pytest.mark.xfail(reason="TODO order='F'") def test_dot_array_order(self): a = np.array([[1, 2], [3, 4]], order='C') b = np.array([[1, 2], [3, 4]], order='F')