From 741a2ca7268257521717617ca0a8ff94b5e27c2e Mon Sep 17 00:00:00 2001 From: Jackson McClintock Date: Thu, 22 Feb 2024 15:47:00 -0500 Subject: [PATCH 01/15] fix: properly handle edge cases in `tile` for paddle backend --- .../backends/paddle/manipulation.py | 64 ++++++++++--------- 1 file changed, 34 insertions(+), 30 deletions(-) diff --git a/ivy/functional/backends/paddle/manipulation.py b/ivy/functional/backends/paddle/manipulation.py index 900f684a6e85c..799e9d9750493 100644 --- a/ivy/functional/backends/paddle/manipulation.py +++ b/ivy/functional/backends/paddle/manipulation.py @@ -363,38 +363,42 @@ def repeat( def tile( x: paddle.Tensor, /, repeats: Sequence[int], *, out: Optional[paddle.Tensor] = None ) -> paddle.Tensor: - if x.ndim >= 7: - repeats = ( - repeats.numpy().tolist() if isinstance(repeats, paddle.Tensor) else repeats - ) - new_shape = [*x.shape[:5], -1] - reshaped_tensor = paddle.reshape(x, new_shape) - new_repeats = repeats[:5] + [math.prod(repeats[5:])] - tiled_reshaped_tensor = tile(reshaped_tensor, new_repeats) - tiled_shape = tuple(s * r for s, r in zip(x.shape, repeats)) - result = paddle.reshape(tiled_reshaped_tensor, tiled_shape) - return result - if ivy.min(repeats) == 0: - # This logic is to mimic other backends behaviour when a 0 in repeat - # is received since paddle doesn't natively support it - if len(repeats) < x.ndim: + repeats = repeats.tolist() if isinstance(repeats, paddle.Tensor) else list(repeats) + # Paddle doesn't natively support repeats containing zeros + if len(repeats) > 0 and min(repeats) == 0: + if x.ndim == 0: + shape = repeats + elif len(repeats) <= x.ndim: shape = x.shape - shape[-len(repeats) :] = paddle_backend.multiply( - shape[-len(repeats) :], repeats - ).tolist() - elif len(repeats) > x.ndim: - shape = ( - repeats.tolist() - if isinstance(repeats, paddle.Tensor) - else list(repeats) - ) - shape[-x.ndim - 1 :] = paddle_backend.multiply( - shape[-x.ndim - 1 :], repeats - ).tolist() + shape[-len(repeats) :] = [ + s * r for s, r in zip(shape[-len(repeats) :], repeats) + ] else: - shape = paddle_backend.multiply(x.shape, repeats).tolist() - return paddle.zeros(shape).cast(x.dtype) - + shape = repeats.copy() + shape[-x.ndim :] = [s * r for r, s in zip(shape[-x.ndim :], x.shape)] + return paddle.empty(shape, dtype=x.dtype) + # Paddle doesn't natively support tensors containing more than 6 dimensions + if x.ndim > 6 or len(repeats) > 6: + if len(repeats) < x.ndim: + repeats = [1] * (x.ndim - len(repeats)) + repeats + elif len(repeats) > x.ndim: + shape = [1] * (len(repeats) - x.ndim) + x.shape + x = paddle.reshape(x, shape) + cur_shape = x.shape + cur_tensor = x + for i in range(0, x.ndim, 5): + size = 5 if i <= x.ndim - 5 else x.ndim - i + red_shape = [*cur_shape[:size], -1] + red_tensor = paddle.reshape(cur_tensor, red_shape) + red_repeats = [*repeats[i : i + size], 1] + tiled_red_tensor = paddle.tile(red_tensor, red_repeats) + perm = [size, *list(range(size))] + tiled_red_tensor = paddle.transpose(tiled_red_tensor, perm) + cur_shape = cur_shape[size:] + [ + s * r for s, r in zip(cur_shape[:size], repeats[i : i + size]) + ] + cur_tensor = paddle.reshape(tiled_red_tensor, cur_shape) + return cur_tensor return paddle.tile(x, repeats) From 378571bfa24cb70ffbe37451ddb2663bc3dd90f7 Mon Sep 17 00:00:00 2001 From: Jackson McClintock Date: Thu, 22 Feb 2024 16:03:21 -0500 Subject: [PATCH 02/15] fix: fix edge case handling in `tile` for tensorflow backend --- .../backends/tensorflow/manipulation.py | 25 ++++++++----------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/ivy/functional/backends/tensorflow/manipulation.py b/ivy/functional/backends/tensorflow/manipulation.py index ed13db5d98afa..44ea4856381d0 100644 --- a/ivy/functional/backends/tensorflow/manipulation.py +++ b/ivy/functional/backends/tensorflow/manipulation.py @@ -312,22 +312,19 @@ def tile( *, out: Optional[Union[tf.Tensor, tf.Variable]] = None, ) -> Union[tf.Tensor, tf.Variable]: - if x.shape == (): - x = tf.reshape(x, (-1,)) - if isinstance(repeats, Number): - repeats = [repeats] - if isinstance(repeats, tf.Tensor) and repeats.shape == (): - repeats = tf.reshape(repeats, (-1,)) - # code to unify behaviour with numpy and torch - if len(x.shape) < len(repeats): - while len(x.shape) != len(repeats): - x = tf.expand_dims(x, 0) - elif len(x.shape) > len(repeats): - repeats = list(repeats) - while len(x.shape) != len(repeats): - repeats = [1] + repeats + # Unify behaviour with numpy and torch # TODO remove the unifying behaviour code if tensorflow handles this # https://github.com/tensorflow/tensorflow/issues/58002 + if len(repeats) < len(x.shape): + repeats = ( + repeats.numpy().tolist() + if isinstance(repeats, (tf.Tensor, tf.Variable)) + else list(repeats) + ) + repeats = [1] * (len(x.shape) - len(repeats)) + repeats + elif len(repeats) > len(x.shape): + shape = [1] * (len(repeats) - len(x.shape)) + x.shape + x = tf.reshape(x, shape) return tf.tile(x, repeats) From a47df413f5c09c5b80299ae7ffa8f06430d25b7f Mon Sep 17 00:00:00 2001 From: Jackson McClintock Date: Thu, 22 Feb 2024 16:09:04 -0500 Subject: [PATCH 03/15] fix: call correct function in `tile` for torch backend --- ivy/functional/backends/torch/manipulation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ivy/functional/backends/torch/manipulation.py b/ivy/functional/backends/torch/manipulation.py index 8c4bdbb9eda22..bf84f6e76be28 100644 --- a/ivy/functional/backends/torch/manipulation.py +++ b/ivy/functional/backends/torch/manipulation.py @@ -266,7 +266,7 @@ def tile( ) -> torch.Tensor: if isinstance(repeats, torch.Tensor): repeats = repeats.detach().cpu().numpy().tolist() - return x.repeat(repeats) + return torch.tile(x, repeats) def constant_pad( From 81908588e18b612d85872733451468b47c5d0bb8 Mon Sep 17 00:00:00 2001 From: Jackson McClintock Date: Thu, 22 Feb 2024 16:22:27 -0500 Subject: [PATCH 04/15] fix: improve ivy test for `tile` --- .../test_core/test_manipulation.py | 27 ++++++++----------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/ivy_tests/test_ivy/test_functional/test_core/test_manipulation.py b/ivy_tests/test_ivy/test_functional/test_core/test_manipulation.py index 6745c947a6d5e..e722305250bd1 100644 --- a/ivy_tests/test_ivy/test_functional/test_core/test_manipulation.py +++ b/ivy_tests/test_ivy/test_functional/test_core/test_manipulation.py @@ -152,9 +152,8 @@ def _get_splits( allow_array_indices=True, is_mod_split=False, ): - """Generate valid splits, either by generating an integer that evenly - divides the axis or a list of splits that sum to the length of the axis - being split.""" + """Generate valid splits, either by generating an integer that evenly divides the + axis or a list of splits that sum to the length of the axis being split.""" shape = draw( st.shared(helpers.get_shape(min_num_dims=min_num_dims), key="value_shape") ) @@ -701,29 +700,25 @@ def test_swapaxes( @handle_test( fn_tree="functional.ivy.tile", dtype_value=helpers.dtype_and_values( - available_dtypes=helpers.get_dtypes("valid", full=True), - shape=st.shared(helpers.get_shape(min_num_dims=1), key="value_shape"), - ), - repeat=helpers.dtype_and_values( - available_dtypes=helpers.get_dtypes("signed_integer"), - shape=st.shared(helpers.get_shape(min_num_dims=1), key="value_shape").map( - lambda rep: (len(rep),) + available_dtypes=helpers.get_dtypes("valid"), + shape=st.shared( + helpers.get_shape(min_dim_size=0, max_num_dims=8), key="value_shape" ), - min_value=0, - max_value=10, ), + repeats=st.lists(st.integers(min_value=0, max_value=5), max_size=8), ) -def test_tile(*, dtype_value, repeat, test_flags, backend_fw, fn_name, on_device): +def test_tile(*, dtype_value, repeats, test_flags, backend_fw, fn_name, on_device): dtype, value = dtype_value - repeat_dtype, repeat_list = repeat + # Empty tensors do not copy correctly in paddle + assume(backend_fw != "paddle" or 0 not in value[0].shape) helpers.test_function( - input_dtypes=dtype + repeat_dtype, + input_dtypes=dtype, test_flags=test_flags, backend_to_test=backend_fw, fn_name=fn_name, on_device=on_device, x=value[0], - repeats=repeat_list[0], + repeats=repeats, rtol_=1e-2, atol_=1e-2, xs_grad_idxs=[[0, 0]], From 0fa393eee9a769e03b8914fd6671440fb2976b45 Mon Sep 17 00:00:00 2001 From: Jackson McClintock Date: Thu, 22 Feb 2024 16:59:40 -0500 Subject: [PATCH 05/15] fix: handle case for empty array in `_remove_np_bfloat16` helper function --- ivy/functional/ivy/creation.py | 136 +++++++++++++++++++-------------- 1 file changed, 79 insertions(+), 57 deletions(-) diff --git a/ivy/functional/ivy/creation.py b/ivy/functional/ivy/creation.py index 5a837dcd93bcc..ace9b74ccc613 100644 --- a/ivy/functional/ivy/creation.py +++ b/ivy/functional/ivy/creation.py @@ -44,9 +44,10 @@ def _asarray_handle_nestable(fn: Callable) -> Callable: @functools.wraps(fn) def _asarray_handle_nestable_wrapper(*args, **kwargs): - """Call `fn` with the *nestable* property of the function correctly - handled. This means mapping the function to the container leaves if any - containers are passed in the input. + """ + Call `fn` with the *nestable* property of the function correctly handled. This + means mapping the function to the container leaves if any containers are passed + in the input. Parameters ---------- @@ -128,6 +129,9 @@ def _remove_np_bfloat16(obj): # from numpy arrays that have bfloat16 dtype using any extension because # bfloat16 in not supported natively by numpy (as of version <=1.25) if isinstance(obj, np.ndarray) and obj.dtype.name == "bfloat16": + # change dtype of empty array instead so that it doesn't lose its shape + if 0 in obj.shape: + return obj.astype(np.float32) return obj.tolist() return obj @@ -135,9 +139,9 @@ def _remove_np_bfloat16(obj): def _asarray_to_native_arrays_and_back(fn: Callable) -> Callable: @functools.wraps(fn) def _asarray_to_native_arrays_and_back_wrapper(*args, dtype=None, **kwargs): - """Wrap `fn` so that input arrays are all converted to - `ivy.NativeArray` instances and return arrays are all converted to - `ivy.Array` instances. + """ + Wrap `fn` so that input arrays are all converted to `ivy.NativeArray` instances + and return arrays are all converted to `ivy.Array` instances. This wrapper is specifically for the backend implementations of asarray. @@ -160,9 +164,10 @@ def _asarray_to_native_arrays_and_back_wrapper(*args, dtype=None, **kwargs): def _asarray_infer_dtype(fn: Callable) -> Callable: @functools.wraps(fn) def _asarray_infer_dtype_wrapper(*args, dtype=None, **kwargs): - """Determine the correct `dtype`, and then calls the function with the - `dtype` passed explicitly. This wrapper is specifically for the backend - implementations of asarray. + """ + Determine the correct `dtype`, and then calls the function with the `dtype` + passed explicitly. This wrapper is specifically for the backend implementations + of asarray. Parameters ---------- @@ -217,9 +222,10 @@ def _infer_dtype(obj): def _asarray_infer_device(fn: Callable) -> Callable: @functools.wraps(fn) def _asarray_infer_device_wrapper(*args, device=None, **kwargs): - """Determine the correct `device`, and then calls the function with the - `device` passed explicitly. This wrapper is specifically for the - backend implementations of asarray. + """ + Determine the correct `device`, and then calls the function with the `device` + passed explicitly. This wrapper is specifically for the backend implementations + of asarray. Parameters ---------- @@ -297,8 +303,9 @@ def arange( device: Optional[Union[ivy.Device, ivy.NativeDevice]] = None, out: Optional[ivy.Array] = None, ) -> ivy.Array: - """Return evenly spaced values within a given interval, with the spacing - being specified. + """ + Return evenly spaced values within a given interval, with the spacing being + specified. Values are generated within the half-open interval [start, stop) (in other words, the interval including start but excluding stop). For integer arguments the function @@ -408,7 +415,8 @@ def asarray( device: Optional[Union[ivy.Device, ivy.NativeDevice]] = None, out: Optional[ivy.Array] = None, ) -> ivy.Array: - """Convert the input to an array. + """ + Convert the input to an array. Parameters ---------- @@ -493,7 +501,8 @@ def zeros( device: Optional[Union[ivy.Device, ivy.NativeDevice]] = None, out: Optional[ivy.Array] = None, ) -> ivy.Array: - """Return a new array having a specified ``shape`` and filled with zeros. + """ + Return a new array having a specified ``shape`` and filled with zeros. Parameters ---------- @@ -557,7 +566,8 @@ def ones( device: Optional[Union[ivy.Device, ivy.NativeDevice]] = None, out: Optional[ivy.Array] = None, ) -> ivy.Array: - """Return a new array having a specified ``shape`` and filled with ones. + """ + Return a new array having a specified ``shape`` and filled with ones. .. note:: @@ -655,8 +665,9 @@ def full_like( device: Optional[Union[ivy.Device, ivy.NativeDevice]] = None, out: Optional[ivy.Array] = None, ) -> ivy.Array: - """Return a new array filled with ``fill_value`` and having the same - ``shape`` as an input array ``x`` . + """ + Return a new array filled with ``fill_value`` and having the same ``shape`` as an + input array ``x`` . Parameters ---------- @@ -762,8 +773,9 @@ def ones_like( device: Optional[Union[ivy.Device, ivy.NativeDevice]] = None, out: Optional[ivy.Array] = None, ) -> ivy.Array: - """Return a new array filled with ones and having the same shape as an - input array ``x``. + """ + Return a new array filled with ones and having the same shape as an input array + ``x``. .. note:: @@ -881,8 +893,9 @@ def zeros_like( device: Optional[Union[ivy.Device, ivy.NativeDevice]] = None, out: Optional[ivy.Array] = None, ) -> ivy.Array: - """Return a new array filled with zeros and having the same ``shape`` as an - input array ``x``. + """ + Return a new array filled with zeros and having the same ``shape`` as an input array + ``x``. Parameters ---------- @@ -993,8 +1006,8 @@ def tril( k: int = 0, out: Optional[ivy.Array] = None, ) -> ivy.Array: - """Return the lower triangular part of a matrix (or a stack of matrices) - ``x``. + """ + Return the lower triangular part of a matrix (or a stack of matrices) ``x``. .. note:: @@ -1049,8 +1062,8 @@ def triu( k: int = 0, out: Optional[ivy.Array] = None, ) -> ivy.Array: - """Return the upper triangular part of a matrix (or a stack of matrices) - ``x``. + """ + Return the upper triangular part of a matrix (or a stack of matrices) ``x``. .. note:: @@ -1107,7 +1120,8 @@ def empty( device: Optional[Union[ivy.Device, ivy.NativeDevice]] = None, out: Optional[ivy.Array] = None, ) -> ivy.Array: - """Return a new array of given shape and type, filled with zeros. + """ + Return a new array of given shape and type, filled with zeros. Parameters ---------- @@ -1157,7 +1171,8 @@ def empty_like( device: Optional[Union[ivy.Device, ivy.NativeDevice]] = None, out: Optional[ivy.Array] = None, ) -> ivy.Array: - """Return an uninitialized array with the same shape as an input array x. + """ + Return an uninitialized array with the same shape as an input array x. Parameters ---------- @@ -1211,8 +1226,8 @@ def eye( device: Optional[Union[ivy.Device, ivy.NativeDevice]] = None, out: Optional[ivy.Array] = None, ) -> ivy.Array: - """Return a two-dimensional array with ones on the k diagonal and zeros - elsewhere. + """ + Return a two-dimensional array with ones on the k diagonal and zeros elsewhere. Parameters ---------- @@ -1354,8 +1369,8 @@ def linspace( device: Optional[Union[ivy.Device, ivy.NativeDevice]] = None, out: Optional[ivy.Array] = None, ) -> ivy.Array: - """Generate a certain number of evenly-spaced values in an interval along a - given axis. + """ + Generate a certain number of evenly-spaced values in an interval along a given axis. See :math:`arange` that allows to specify the step size of evenly spaced values in an interval. @@ -1457,7 +1472,8 @@ def meshgrid( indexing: str = "xy", out: Optional[ivy.Array] = None, ) -> List[ivy.Array]: - """Return coordinate matrices from coordinate vectors. + """ + Return coordinate matrices from coordinate vectors. Parameters ---------- @@ -1581,8 +1597,8 @@ def full( device: Optional[Union[ivy.Device, ivy.NativeDevice]] = None, out: Optional[ivy.Array] = None, ) -> ivy.Array: - """Return a new array having a specified ``shape`` and filled with - ``fill_value``. + """ + Return a new array having a specified ``shape`` and filled with ``fill_value``. Parameters ---------- @@ -1684,7 +1700,8 @@ def full( def to_dlpack( x: Union[ivy.Array, ivy.NativeArray], /, *, out: Optional[ivy.Array] = None ): - """Return PyCapsule Object. + """ + Return PyCapsule Object. Parameters ---------- @@ -1723,8 +1740,9 @@ def to_dlpack( def from_dlpack( x: Union[ivy.Array, ivy.NativeArray], /, *, out: Optional[ivy.Array] = None ) -> ivy.Array: - """Return a new array containing the data from another (array) object with - a ``__dlpack__`` method or PyCapsule Object. + """ + Return a new array containing the data from another (array) object with a + ``__dlpack__`` method or PyCapsule Object. Parameters ---------- @@ -1780,7 +1798,8 @@ def copy_array( to_ivy_array: bool = True, out: Optional[ivy.Array] = None, ) -> ivy.Array: - """Copy an array. + """ + Copy an array. Parameters ---------- @@ -1885,7 +1904,8 @@ def native_array( dtype: Optional[Union[ivy.Dtype, ivy.NativeDtype]] = None, device: Optional[Union[ivy.Device, ivy.NativeDevice]] = None, ) -> ivy.NativeArray: - """Convert the input to a native array. + """ + Convert the input to a native array. Parameters ---------- @@ -1949,9 +1969,9 @@ def one_hot( device: Union[ivy.Device, ivy.NativeDevice] = None, out: Optional[ivy.Array] = None, ) -> ivy.Array: - """Return a one-hot array. The locations represented by indices in the - parameter indices take value on_value, while all other locations take value - off_value. + """ + Return a one-hot array. The locations represented by indices in the parameter + indices take value on_value, while all other locations take value off_value. Parameters ---------- @@ -2065,8 +2085,9 @@ def logspace( device: Optional[Union[ivy.Device, ivy.NativeDevice]] = None, out: Optional[ivy.Array] = None, ) -> ivy.Array: - """Generate a certain number of evenly-spaced values in log space, in an - interval along a given axis. + """ + Generate a certain number of evenly-spaced values in log space, in an interval along + a given axis. Parameters ---------- @@ -2169,7 +2190,8 @@ def frombuffer( count: Optional[int] = -1, offset: Optional[int] = 0, ) -> ivy.Array: - r"""Interpret a buffer as a 1-dimensional array. + r""" + Interpret a buffer as a 1-dimensional array. .. note:: Note that either of the following must be true: @@ -2234,16 +2256,16 @@ def triu_indices( *, device: Optional[Union[ivy.Device, ivy.NativeDevice]] = None, ) -> Tuple[ivy.Array]: - """Return the indices of the upper triangular part of a row by col matrix - in a 2-by-N shape (tuple of two N dimensional arrays), where the first row - contains row coordinates of all indices and the second row contains column - coordinates. Indices are ordered based on rows and then columns. The upper - triangular part of the matrix is defined as the elements on and above the - diagonal. The argument k controls which diagonal to consider. If k = 0, - all elements on and above the main diagonal are retained. A positive value - excludes just as many diagonals above the main diagonal, and similarly a - negative value includes just as many diagonals below the main diagonal. The - main diagonal are the set of indices {(i,i)} for i∈[0,min{n_rows, + """ + Return the indices of the upper triangular part of a row by col matrix in a 2-by-N + shape (tuple of two N dimensional arrays), where the first row contains row + coordinates of all indices and the second row contains column coordinates. Indices + are ordered based on rows and then columns. The upper triangular part of the matrix + is defined as the elements on and above the diagonal. The argument k controls which + diagonal to consider. If k = 0, all elements on and above the main diagonal are + retained. A positive value excludes just as many diagonals above the main diagonal, + and similarly a negative value includes just as many diagonals below the main + diagonal. The main diagonal are the set of indices {(i,i)} for i∈[0,min{n_rows, n_cols}−1]. Notes From 24a27053bbeb3c8ed4a8036ac81ae54e522d935a Mon Sep 17 00:00:00 2001 From: Jackson McClintock Date: Thu, 22 Feb 2024 16:46:48 -0500 Subject: [PATCH 06/15] fix: add bound on repeats size in `repeat` torch tensor frontend test --- ivy_tests/test_ivy/test_frontends/test_torch/test_tensor.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/ivy_tests/test_ivy/test_frontends/test_torch/test_tensor.py b/ivy_tests/test_ivy/test_frontends/test_torch/test_tensor.py index 10a8b3385fbf3..f88060672aaff 100644 --- a/ivy_tests/test_ivy/test_frontends/test_torch/test_tensor.py +++ b/ivy_tests/test_ivy/test_frontends/test_torch/test_tensor.py @@ -347,15 +347,13 @@ def _repeat_helper(draw): ) ) - MAX_NUMPY_DIMS = 32 repeats = draw( st.lists( st.integers(min_value=1, max_value=5), min_size=len(shape), - max_size=MAX_NUMPY_DIMS, + max_size=5, ) ) - assume(np.prod(repeats) * np.prod(shape) <= 2**28) return input_dtype, x, repeats From d1143bb1c0ceea9e504f820d065992d4bf2be7e4 Mon Sep 17 00:00:00 2001 From: Jackson McClintock Date: Thu, 22 Feb 2024 16:50:45 -0500 Subject: [PATCH 07/15] fix: fix "unpack_repeats" in `repeat` torch tensor frontend test --- .../test_frontends/test_torch/test_tensor.py | 24 +++++++------------ 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/ivy_tests/test_ivy/test_frontends/test_torch/test_tensor.py b/ivy_tests/test_ivy/test_frontends/test_torch/test_tensor.py index f88060672aaff..2fc2e29088e22 100644 --- a/ivy_tests/test_ivy/test_frontends/test_torch/test_tensor.py +++ b/ivy_tests/test_ivy/test_frontends/test_torch/test_tensor.py @@ -11331,11 +11331,11 @@ def test_torch_remainder_( init_tree="torch.tensor", method_name="repeat", dtype_x_repeats=_repeat_helper(), - unpack_repeat=st.booleans(), + unpack_repeats=st.booleans(), ) def test_torch_repeat( dtype_x_repeats, - unpack_repeat, + unpack_repeats, frontend_method_data, init_flags, method_flags, @@ -11344,19 +11344,11 @@ def test_torch_repeat( backend_fw, ): input_dtype, x, repeats = dtype_x_repeats - - if backend_fw == "paddle": - # paddle only supports size of the shape of repeats - # to be less than or equal to 6 - assume(len(repeats) <= 6) - - repeat = { - "repeats": repeats, - } - if unpack_repeat: - method_flags.num_positional_args = len(repeat["repeats"]) + 1 - for i, x_ in enumerate(repeat["repeats"]): - repeat[f"x{i}"] = x_ + if unpack_repeats: + method_flags.num_positional_args = len(repeats) + method_kwargs = {f"x{i}": x_ for i, x_ in enumerate(repeats)} + else: + method_kwargs = {"repeats": repeats} helpers.test_frontend_method( init_input_dtypes=input_dtype, backend_to_test=backend_fw, @@ -11364,7 +11356,7 @@ def test_torch_repeat( "data": x[0], }, method_input_dtypes=input_dtype, - method_all_as_kwargs_np=repeat, + method_all_as_kwargs_np=method_kwargs, frontend_method_data=frontend_method_data, init_flags=init_flags, method_flags=method_flags, From 0050df8c7e10d0ae90cf84cfaae7479b22868607 Mon Sep 17 00:00:00 2001 From: Jackson McClintock Date: Mon, 12 Feb 2024 17:53:56 -0500 Subject: [PATCH 08/15] fix: check for empty tensor in `astype` for paddle backend --- ivy/functional/backends/paddle/data_type.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/ivy/functional/backends/paddle/data_type.py b/ivy/functional/backends/paddle/data_type.py index aa009c9f7533c..aa17a4c718178 100644 --- a/ivy/functional/backends/paddle/data_type.py +++ b/ivy/functional/backends/paddle/data_type.py @@ -118,13 +118,18 @@ def astype( out: Optional[paddle.Tensor] = None, ) -> paddle.Tensor: dtype = ivy.as_native_dtype(dtype) - - if copy and 0 in x.shape: - return paddle.empty(x.shape, dtype=dtype) - - if x.dtype == dtype: - return x.clone() if copy else x - return x.clone().cast(dtype) if copy else x.cast(dtype) + if copy: + # Checking if the tensor is not empty + # As clone is not supported for empty tensors + if 0 in x.shape: + return paddle.to_tensor( + x, + dtype=dtype, + place=x.place, + stop_gradient=x.stop_gradient, + ) + return x.clone() if x.dtype == dtype else x.clone().cast(dtype) + return x if x.dtype == dtype else x.cast(dtype) def broadcast_arrays(*arrays: paddle.Tensor) -> List[paddle.Tensor]: From 4fc40a096a650e2d860eee2f15ce8b25fd92db34 Mon Sep 17 00:00:00 2001 From: Jackson McClintock Date: Mon, 12 Feb 2024 18:00:10 -0500 Subject: [PATCH 09/15] fix: check for empty tensor in `mean` for paddle backend --- ivy/functional/backends/paddle/statistical.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/ivy/functional/backends/paddle/statistical.py b/ivy/functional/backends/paddle/statistical.py index 6b231f3161d26..7d34dfe4477bc 100644 --- a/ivy/functional/backends/paddle/statistical.py +++ b/ivy/functional/backends/paddle/statistical.py @@ -160,6 +160,16 @@ def axis_condition(axis): return ret.astype(ret_dtype) +def _calculate_reduced_shape(x, axis, keepdims): + if axis is None: + axis = tuple(range(len(x.shape))) + elif type(axis) not in (tuple, list): + axis = (axis,) + if keepdims: + return [1 if i in axis else x.shape[i] for i in range(len(x.shape))] + return [x.shape[i] for i in range(len(x.shape)) if i not in axis] + + @with_supported_dtypes( {"2.6.0 and below": ("bool", "complex", "float32", "float64")}, backend_version ) @@ -174,6 +184,9 @@ def mean( ) -> paddle.Tensor: if dtype is not None: x = ivy.astype(x, dtype).to_native() + if 0 in x.shape: + shape = _calculate_reduced_shape(x, axis, keepdims) + ret = paddle.empty(shape) if paddle.is_complex(x): ret = paddle.complex( paddle.mean(x.real(), axis=axis, keepdim=keepdims), From c5323d9b806778077e748ab04d0f531f23279c8f Mon Sep 17 00:00:00 2001 From: Jackson McClintock Date: Thu, 22 Feb 2024 16:33:44 -0500 Subject: [PATCH 10/15] fix: minor improvement in `mean` for paddle backend --- ivy/functional/backends/paddle/statistical.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ivy/functional/backends/paddle/statistical.py b/ivy/functional/backends/paddle/statistical.py index 7d34dfe4477bc..ce12b15aabcd7 100644 --- a/ivy/functional/backends/paddle/statistical.py +++ b/ivy/functional/backends/paddle/statistical.py @@ -187,7 +187,7 @@ def mean( if 0 in x.shape: shape = _calculate_reduced_shape(x, axis, keepdims) ret = paddle.empty(shape) - if paddle.is_complex(x): + elif paddle.is_complex(x): ret = paddle.complex( paddle.mean(x.real(), axis=axis, keepdim=keepdims), paddle.mean(x.imag(), axis=axis, keepdim=keepdims), From 5098f04b402cf3b1bb313050ba36ffd7613fcbd2 Mon Sep 17 00:00:00 2001 From: ivy-branch Date: Thu, 22 Feb 2024 22:42:58 +0000 Subject: [PATCH 11/15] =?UTF-8?q?=F0=9F=A4=96=20Lint=20code?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ivy/functional/ivy/creation.py | 133 ++++++++---------- .../test_core/test_manipulation.py | 5 +- 2 files changed, 60 insertions(+), 78 deletions(-) diff --git a/ivy/functional/ivy/creation.py b/ivy/functional/ivy/creation.py index ace9b74ccc613..f0077024e3a2b 100644 --- a/ivy/functional/ivy/creation.py +++ b/ivy/functional/ivy/creation.py @@ -44,10 +44,9 @@ def _asarray_handle_nestable(fn: Callable) -> Callable: @functools.wraps(fn) def _asarray_handle_nestable_wrapper(*args, **kwargs): - """ - Call `fn` with the *nestable* property of the function correctly handled. This - means mapping the function to the container leaves if any containers are passed - in the input. + """Call `fn` with the *nestable* property of the function correctly + handled. This means mapping the function to the container leaves if any + containers are passed in the input. Parameters ---------- @@ -139,9 +138,9 @@ def _remove_np_bfloat16(obj): def _asarray_to_native_arrays_and_back(fn: Callable) -> Callable: @functools.wraps(fn) def _asarray_to_native_arrays_and_back_wrapper(*args, dtype=None, **kwargs): - """ - Wrap `fn` so that input arrays are all converted to `ivy.NativeArray` instances - and return arrays are all converted to `ivy.Array` instances. + """Wrap `fn` so that input arrays are all converted to + `ivy.NativeArray` instances and return arrays are all converted to + `ivy.Array` instances. This wrapper is specifically for the backend implementations of asarray. @@ -164,10 +163,9 @@ def _asarray_to_native_arrays_and_back_wrapper(*args, dtype=None, **kwargs): def _asarray_infer_dtype(fn: Callable) -> Callable: @functools.wraps(fn) def _asarray_infer_dtype_wrapper(*args, dtype=None, **kwargs): - """ - Determine the correct `dtype`, and then calls the function with the `dtype` - passed explicitly. This wrapper is specifically for the backend implementations - of asarray. + """Determine the correct `dtype`, and then calls the function with the + `dtype` passed explicitly. This wrapper is specifically for the backend + implementations of asarray. Parameters ---------- @@ -222,10 +220,9 @@ def _infer_dtype(obj): def _asarray_infer_device(fn: Callable) -> Callable: @functools.wraps(fn) def _asarray_infer_device_wrapper(*args, device=None, **kwargs): - """ - Determine the correct `device`, and then calls the function with the `device` - passed explicitly. This wrapper is specifically for the backend implementations - of asarray. + """Determine the correct `device`, and then calls the function with the + `device` passed explicitly. This wrapper is specifically for the + backend implementations of asarray. Parameters ---------- @@ -303,9 +300,8 @@ def arange( device: Optional[Union[ivy.Device, ivy.NativeDevice]] = None, out: Optional[ivy.Array] = None, ) -> ivy.Array: - """ - Return evenly spaced values within a given interval, with the spacing being - specified. + """Return evenly spaced values within a given interval, with the spacing + being specified. Values are generated within the half-open interval [start, stop) (in other words, the interval including start but excluding stop). For integer arguments the function @@ -415,8 +411,7 @@ def asarray( device: Optional[Union[ivy.Device, ivy.NativeDevice]] = None, out: Optional[ivy.Array] = None, ) -> ivy.Array: - """ - Convert the input to an array. + """Convert the input to an array. Parameters ---------- @@ -501,8 +496,7 @@ def zeros( device: Optional[Union[ivy.Device, ivy.NativeDevice]] = None, out: Optional[ivy.Array] = None, ) -> ivy.Array: - """ - Return a new array having a specified ``shape`` and filled with zeros. + """Return a new array having a specified ``shape`` and filled with zeros. Parameters ---------- @@ -566,8 +560,7 @@ def ones( device: Optional[Union[ivy.Device, ivy.NativeDevice]] = None, out: Optional[ivy.Array] = None, ) -> ivy.Array: - """ - Return a new array having a specified ``shape`` and filled with ones. + """Return a new array having a specified ``shape`` and filled with ones. .. note:: @@ -665,9 +658,8 @@ def full_like( device: Optional[Union[ivy.Device, ivy.NativeDevice]] = None, out: Optional[ivy.Array] = None, ) -> ivy.Array: - """ - Return a new array filled with ``fill_value`` and having the same ``shape`` as an - input array ``x`` . + """Return a new array filled with ``fill_value`` and having the same + ``shape`` as an input array ``x`` . Parameters ---------- @@ -773,9 +765,8 @@ def ones_like( device: Optional[Union[ivy.Device, ivy.NativeDevice]] = None, out: Optional[ivy.Array] = None, ) -> ivy.Array: - """ - Return a new array filled with ones and having the same shape as an input array - ``x``. + """Return a new array filled with ones and having the same shape as an + input array ``x``. .. note:: @@ -893,9 +884,8 @@ def zeros_like( device: Optional[Union[ivy.Device, ivy.NativeDevice]] = None, out: Optional[ivy.Array] = None, ) -> ivy.Array: - """ - Return a new array filled with zeros and having the same ``shape`` as an input array - ``x``. + """Return a new array filled with zeros and having the same ``shape`` as an + input array ``x``. Parameters ---------- @@ -1006,8 +996,8 @@ def tril( k: int = 0, out: Optional[ivy.Array] = None, ) -> ivy.Array: - """ - Return the lower triangular part of a matrix (or a stack of matrices) ``x``. + """Return the lower triangular part of a matrix (or a stack of matrices) + ``x``. .. note:: @@ -1062,8 +1052,8 @@ def triu( k: int = 0, out: Optional[ivy.Array] = None, ) -> ivy.Array: - """ - Return the upper triangular part of a matrix (or a stack of matrices) ``x``. + """Return the upper triangular part of a matrix (or a stack of matrices) + ``x``. .. note:: @@ -1120,8 +1110,7 @@ def empty( device: Optional[Union[ivy.Device, ivy.NativeDevice]] = None, out: Optional[ivy.Array] = None, ) -> ivy.Array: - """ - Return a new array of given shape and type, filled with zeros. + """Return a new array of given shape and type, filled with zeros. Parameters ---------- @@ -1171,8 +1160,7 @@ def empty_like( device: Optional[Union[ivy.Device, ivy.NativeDevice]] = None, out: Optional[ivy.Array] = None, ) -> ivy.Array: - """ - Return an uninitialized array with the same shape as an input array x. + """Return an uninitialized array with the same shape as an input array x. Parameters ---------- @@ -1226,8 +1214,8 @@ def eye( device: Optional[Union[ivy.Device, ivy.NativeDevice]] = None, out: Optional[ivy.Array] = None, ) -> ivy.Array: - """ - Return a two-dimensional array with ones on the k diagonal and zeros elsewhere. + """Return a two-dimensional array with ones on the k diagonal and zeros + elsewhere. Parameters ---------- @@ -1369,8 +1357,8 @@ def linspace( device: Optional[Union[ivy.Device, ivy.NativeDevice]] = None, out: Optional[ivy.Array] = None, ) -> ivy.Array: - """ - Generate a certain number of evenly-spaced values in an interval along a given axis. + """Generate a certain number of evenly-spaced values in an interval along a + given axis. See :math:`arange` that allows to specify the step size of evenly spaced values in an interval. @@ -1472,8 +1460,7 @@ def meshgrid( indexing: str = "xy", out: Optional[ivy.Array] = None, ) -> List[ivy.Array]: - """ - Return coordinate matrices from coordinate vectors. + """Return coordinate matrices from coordinate vectors. Parameters ---------- @@ -1597,8 +1584,8 @@ def full( device: Optional[Union[ivy.Device, ivy.NativeDevice]] = None, out: Optional[ivy.Array] = None, ) -> ivy.Array: - """ - Return a new array having a specified ``shape`` and filled with ``fill_value``. + """Return a new array having a specified ``shape`` and filled with + ``fill_value``. Parameters ---------- @@ -1700,8 +1687,7 @@ def full( def to_dlpack( x: Union[ivy.Array, ivy.NativeArray], /, *, out: Optional[ivy.Array] = None ): - """ - Return PyCapsule Object. + """Return PyCapsule Object. Parameters ---------- @@ -1740,9 +1726,8 @@ def to_dlpack( def from_dlpack( x: Union[ivy.Array, ivy.NativeArray], /, *, out: Optional[ivy.Array] = None ) -> ivy.Array: - """ - Return a new array containing the data from another (array) object with a - ``__dlpack__`` method or PyCapsule Object. + """Return a new array containing the data from another (array) object with + a ``__dlpack__`` method or PyCapsule Object. Parameters ---------- @@ -1798,8 +1783,7 @@ def copy_array( to_ivy_array: bool = True, out: Optional[ivy.Array] = None, ) -> ivy.Array: - """ - Copy an array. + """Copy an array. Parameters ---------- @@ -1904,8 +1888,7 @@ def native_array( dtype: Optional[Union[ivy.Dtype, ivy.NativeDtype]] = None, device: Optional[Union[ivy.Device, ivy.NativeDevice]] = None, ) -> ivy.NativeArray: - """ - Convert the input to a native array. + """Convert the input to a native array. Parameters ---------- @@ -1969,9 +1952,9 @@ def one_hot( device: Union[ivy.Device, ivy.NativeDevice] = None, out: Optional[ivy.Array] = None, ) -> ivy.Array: - """ - Return a one-hot array. The locations represented by indices in the parameter - indices take value on_value, while all other locations take value off_value. + """Return a one-hot array. The locations represented by indices in the + parameter indices take value on_value, while all other locations take value + off_value. Parameters ---------- @@ -2085,9 +2068,8 @@ def logspace( device: Optional[Union[ivy.Device, ivy.NativeDevice]] = None, out: Optional[ivy.Array] = None, ) -> ivy.Array: - """ - Generate a certain number of evenly-spaced values in log space, in an interval along - a given axis. + """Generate a certain number of evenly-spaced values in log space, in an + interval along a given axis. Parameters ---------- @@ -2190,8 +2172,7 @@ def frombuffer( count: Optional[int] = -1, offset: Optional[int] = 0, ) -> ivy.Array: - r""" - Interpret a buffer as a 1-dimensional array. + r"""Interpret a buffer as a 1-dimensional array. .. note:: Note that either of the following must be true: @@ -2256,16 +2237,16 @@ def triu_indices( *, device: Optional[Union[ivy.Device, ivy.NativeDevice]] = None, ) -> Tuple[ivy.Array]: - """ - Return the indices of the upper triangular part of a row by col matrix in a 2-by-N - shape (tuple of two N dimensional arrays), where the first row contains row - coordinates of all indices and the second row contains column coordinates. Indices - are ordered based on rows and then columns. The upper triangular part of the matrix - is defined as the elements on and above the diagonal. The argument k controls which - diagonal to consider. If k = 0, all elements on and above the main diagonal are - retained. A positive value excludes just as many diagonals above the main diagonal, - and similarly a negative value includes just as many diagonals below the main - diagonal. The main diagonal are the set of indices {(i,i)} for i∈[0,min{n_rows, + """Return the indices of the upper triangular part of a row by col matrix + in a 2-by-N shape (tuple of two N dimensional arrays), where the first row + contains row coordinates of all indices and the second row contains column + coordinates. Indices are ordered based on rows and then columns. The upper + triangular part of the matrix is defined as the elements on and above the + diagonal. The argument k controls which diagonal to consider. If k = 0, + all elements on and above the main diagonal are retained. A positive value + excludes just as many diagonals above the main diagonal, and similarly a + negative value includes just as many diagonals below the main diagonal. The + main diagonal are the set of indices {(i,i)} for i∈[0,min{n_rows, n_cols}−1]. Notes diff --git a/ivy_tests/test_ivy/test_functional/test_core/test_manipulation.py b/ivy_tests/test_ivy/test_functional/test_core/test_manipulation.py index e722305250bd1..3bdc8866c7790 100644 --- a/ivy_tests/test_ivy/test_functional/test_core/test_manipulation.py +++ b/ivy_tests/test_ivy/test_functional/test_core/test_manipulation.py @@ -152,8 +152,9 @@ def _get_splits( allow_array_indices=True, is_mod_split=False, ): - """Generate valid splits, either by generating an integer that evenly divides the - axis or a list of splits that sum to the length of the axis being split.""" + """Generate valid splits, either by generating an integer that evenly + divides the axis or a list of splits that sum to the length of the axis + being split.""" shape = draw( st.shared(helpers.get_shape(min_num_dims=min_num_dims), key="value_shape") ) From b7fd7bfacb6cdcfde0bde3906c74c640e0bb71de Mon Sep 17 00:00:00 2001 From: Jackson McClintock Date: Thu, 22 Feb 2024 21:49:53 -0500 Subject: [PATCH 12/15] fix: fix empty tensor functionality in `mean` for paddle backend --- ivy/functional/backends/paddle/statistical.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ivy/functional/backends/paddle/statistical.py b/ivy/functional/backends/paddle/statistical.py index ce12b15aabcd7..225169f6561e5 100644 --- a/ivy/functional/backends/paddle/statistical.py +++ b/ivy/functional/backends/paddle/statistical.py @@ -163,7 +163,7 @@ def axis_condition(axis): def _calculate_reduced_shape(x, axis, keepdims): if axis is None: axis = tuple(range(len(x.shape))) - elif type(axis) not in (tuple, list): + elif isinstance(axis, int): axis = (axis,) if keepdims: return [1 if i in axis else x.shape[i] for i in range(len(x.shape))] @@ -186,7 +186,7 @@ def mean( x = ivy.astype(x, dtype).to_native() if 0 in x.shape: shape = _calculate_reduced_shape(x, axis, keepdims) - ret = paddle.empty(shape) + ret = paddle.full(shape, float("nan")) elif paddle.is_complex(x): ret = paddle.complex( paddle.mean(x.real(), axis=axis, keepdim=keepdims), From 8b96cc65e668cdb894489f7a0fae3d7823b8a366 Mon Sep 17 00:00:00 2001 From: Jackson McClintock Date: Fri, 29 Mar 2024 17:16:36 -0400 Subject: [PATCH 13/15] fix: handle empty tensors correctly in paddle.manipulation `tile` --- ivy/functional/backends/paddle/manipulation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ivy/functional/backends/paddle/manipulation.py b/ivy/functional/backends/paddle/manipulation.py index 799e9d9750493..6ae7a7304f8e7 100644 --- a/ivy/functional/backends/paddle/manipulation.py +++ b/ivy/functional/backends/paddle/manipulation.py @@ -365,7 +365,7 @@ def tile( ) -> paddle.Tensor: repeats = repeats.tolist() if isinstance(repeats, paddle.Tensor) else list(repeats) # Paddle doesn't natively support repeats containing zeros - if len(repeats) > 0 and min(repeats) == 0: + if 0 in x.shape or (len(repeats) > 0 and min(repeats) == 0): if x.ndim == 0: shape = repeats elif len(repeats) <= x.ndim: From c91b9d7f91820ca602cf3d21a219463de87f4ff0 Mon Sep 17 00:00:00 2001 From: Jackson McClintock Date: Fri, 29 Mar 2024 17:23:06 -0400 Subject: [PATCH 14/15] fix: improve frontend test for torch.tensor `repeat` --- .../test_frontends/test_torch/test_tensor.py | 33 +++++++++---------- 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/ivy_tests/test_ivy/test_frontends/test_torch/test_tensor.py b/ivy_tests/test_ivy/test_frontends/test_torch/test_tensor.py index 2fc2e29088e22..2cf190bd2ac3d 100644 --- a/ivy_tests/test_ivy/test_frontends/test_torch/test_tensor.py +++ b/ivy_tests/test_ivy/test_frontends/test_torch/test_tensor.py @@ -335,26 +335,16 @@ def _masked_fill_helper(draw): @st.composite def _repeat_helper(draw): shape = draw( - helpers.get_shape( - min_num_dims=1, max_num_dims=5, min_dim_size=2, max_dim_size=10 - ) - ) - - input_dtype, x = draw( - helpers.dtype_and_values( - available_dtypes=helpers.get_dtypes("valid"), - shape=shape, - ) + st.shared(helpers.get_shape(min_dim_size=0, max_num_dims=8), key="value_shape") ) - repeats = draw( st.lists( - st.integers(min_value=1, max_value=5), + st.integers(min_value=0, max_value=5), min_size=len(shape), - max_size=5, + max_size=8, ) ) - return input_dtype, x, repeats + return repeats @st.composite @@ -11330,11 +11320,18 @@ def test_torch_remainder_( class_tree=CLASS_TREE, init_tree="torch.tensor", method_name="repeat", - dtype_x_repeats=_repeat_helper(), + dtype_and_x=helpers.dtype_and_values( + available_dtypes=helpers.get_dtypes("valid"), + shape=st.shared( + helpers.get_shape(min_dim_size=0, max_num_dims=8), key="value_shape" + ), + ), + repeats=_repeat_helper(), unpack_repeats=st.booleans(), ) def test_torch_repeat( - dtype_x_repeats, + dtype_and_x, + repeats, unpack_repeats, frontend_method_data, init_flags, @@ -11343,8 +11340,8 @@ def test_torch_repeat( on_device, backend_fw, ): - input_dtype, x, repeats = dtype_x_repeats - if unpack_repeats: + input_dtype, x = dtype_and_x + if unpack_repeats and len(repeats) > 0: method_flags.num_positional_args = len(repeats) method_kwargs = {f"x{i}": x_ for i, x_ in enumerate(repeats)} else: From 41228de24dbe127c97da88f54c41535c2d8ef58d Mon Sep 17 00:00:00 2001 From: Jackson McClintock Date: Wed, 10 Apr 2024 16:52:53 -0400 Subject: [PATCH 15/15] fix: correct type in ivy.experimental.manipulation `fill_diagonal` --- ivy/functional/ivy/experimental/manipulation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ivy/functional/ivy/experimental/manipulation.py b/ivy/functional/ivy/experimental/manipulation.py index a1467eddf7271..0aa7400b30856 100644 --- a/ivy/functional/ivy/experimental/manipulation.py +++ b/ivy/functional/ivy/experimental/manipulation.py @@ -2203,7 +2203,7 @@ def fill_diagonal( steps = ivy.arange(0, end, step) if isinstance(v, (ivy.Array, ivy.NativeArray)): v = ivy.reshape(v, (-1,)).astype(a.dtype) - v = ivy.tile(v, int(ivy.ceil(len(steps) / v.shape[0])))[: len(steps)] + v = ivy.tile(v, (int(ivy.ceil(len(steps) / v.shape[0])),))[: len(steps)] else: v = ivy.repeat(v, len(steps)) ivy.scatter_flat(steps, v, size=a.shape[0], reduction="replace", out=a)