Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add compress #3103

Merged
merged 7 commits into from Feb 25, 2020
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions cupy/__init__.py
Expand Up @@ -435,6 +435,7 @@ def result_type(*arrays_and_dtypes):
from cupy.indexing.generate import unravel_index # NOQA

from cupy.indexing.indexing import choose # NOQA
from cupy.indexing.indexing import compress # NOQA
from cupy.indexing.indexing import diagonal # NOQA
from cupy.indexing.indexing import take # NOQA
from cupy.indexing.indexing import take_along_axis # NOQA
Expand Down
1 change: 1 addition & 0 deletions cupy/core/_routines_indexing.pxd
Expand Up @@ -10,6 +10,7 @@ cdef _ndarray_scatter_min(ndarray self, slices, value)
cdef ndarray _ndarray_take(ndarray self, indices, axis, out)
cdef ndarray _ndarray_put(ndarray self, indices, values, mode)
cdef ndarray _ndarray_choose(ndarray self, choices, out, mode)
cdef ndarray _ndarray_compress(ndarray self, condition, axis, out)
cdef ndarray _ndarray_diagonal(ndarray self, offset, axis1, axis2)

cdef ndarray _simple_getitem(ndarray a, list slice_list)
16 changes: 16 additions & 0 deletions cupy/core/_routines_indexing.pyx
Expand Up @@ -152,6 +152,22 @@ cdef ndarray _ndarray_choose(ndarray self, choices, out, mode):
return out


cdef ndarray _ndarray_compress(ndarray self, condition, axis, out):
a = self

if numpy.isscalar(condition):
raise ValueError('condition must be a 1-d array')

if not isinstance(condition, ndarray):
condition = core.array(condition, dtype=int)
if condition.ndim != 1:
raise ValueError('condition must be a 1-d array')
Comment on lines +161 to +164
Copy link
Member

@niboshi niboshi Feb 21, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We do not accept numpy arrays as argument in general.
I think we can just ensure isinstance(condition, ndarray) and raise TypeError.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure if I understand what you said. Doesn't ndarray mean only cupy ndarrays.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that's correct. ndarray is cupy.ndarray in this context.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we just assert that instance is of ndarray, then we might not be able to take a python list of booleans as input. That's why I thought I might convert it to cupy ndarray (which also has a side-effect of converting numpy arrays also to cupy ndarrays). Otherwise, I would need to check manually for a list of booleans.

Copy link
Member

@niboshi niboshi Feb 25, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We do not accept list of booleans as well.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, as take accepts both lists and NumPy arrays as indices, I think condition can support them as well.


res = _ndarray_nonzero(condition) # synchronize

return _ndarray_take(a, res[0], axis, out)


cdef ndarray _ndarray_diagonal(ndarray self, offset, axis1, axis2):
return _diagonal(self, offset, axis1, axis2)

Expand Down
1 change: 1 addition & 0 deletions cupy/core/core.pxd
Expand Up @@ -43,6 +43,7 @@ cdef class ndarray:
cpdef partition(self, kth, int axis=*)
cpdef ndarray argpartition(self, kth, axis=*)
cpdef tuple nonzero(self)
cpdef ndarray compress(self, condition, axis=*, out=*)
cpdef ndarray diagonal(self, offset=*, axis1=*, axis2=*)
cpdef ndarray max(self, axis=*, out=*, keepdims=*)
cpdef ndarray argmax(self, axis=*, out=*, dtype=*,
Expand Down
14 changes: 13 additions & 1 deletion cupy/core/core.pyx
Expand Up @@ -724,7 +724,19 @@ cdef class ndarray:

return _indexing._ndarray_nonzero(self)

# TODO(okuta): Implement compress
cpdef ndarray compress(self, condition, axis=None, out=None):
"""Returns selected slices of this array along given axis.

.. warning::

This function may synchronize the device.

.. seealso::
:func:`cupy.compress` for full documentation,
:meth:`numpy.ndarray.compress`

"""
return _indexing._ndarray_compress(self, condition, axis, out)

cpdef ndarray diagonal(self, offset=0, axis1=0, axis2=1):
"""Returns a view of the specified diagonals.
Expand Down
28 changes: 27 additions & 1 deletion cupy/indexing/indexing.py
Expand Up @@ -76,7 +76,33 @@ def choose(a, choices, out=None, mode='raise'):
return a.choose(choices, out, mode)


# TODO(okuta): Implement compress
def compress(condition, a, axis=None, out=None):
"""Returns selected slices of an array along given axis.

Args:
condition (1-D array of bools): Array that selects which entries to
return. If len(condition) is less than the size of a along the
given axis, then output is truncated to the length of the condition
array.
a (cupy.ndarray): Array from which to extract a part.
axis (int): Axis along which to take slices. If None (default), work
on the flattened array.
out (cupy.ndarray): Output array. If provided, it should be of
appropriate shape and dtype.

Returns:
cupy.ndarray: A copy of a without the slices along axis for which
condition is false.

.. warning::

This function may synchronize the device.


.. seealso:: :func:`numpy.compress`

"""
return a.compress(condition, axis, out)


def diagonal(a, offset=0, axis1=0, axis2=1):
Expand Down
31 changes: 31 additions & 0 deletions tests/cupy_tests/indexing_tests/test_indexing.py
Expand Up @@ -41,6 +41,37 @@ def test_take_along_axis_none_axis(self, xp):
b = testing.shaped_random((30,), xp, dtype='int64', scale=24)
return xp.take_along_axis(a, b, axis=None)

@testing.numpy_cupy_array_equal()
def test_compress(self, xp):
a = testing.shaped_arange((3, 4, 5), xp)
b = xp.array([True, False, True])
return xp.compress(b, a, axis=1)

@testing.numpy_cupy_array_equal()
def test_compress_no_axis(self, xp):
a = testing.shaped_arange((3, 4, 5), xp)
b = xp.array([True, False, True])
return xp.compress(b, a)

@testing.for_int_dtypes()
@testing.numpy_cupy_array_equal()
def test_compress_no_bool(self, xp, dtype):
a = testing.shaped_arange((3, 4, 5), xp)
b = testing.shaped_arange((3,), xp, dtype)
return xp.compress(b, a, axis=1)

@testing.numpy_cupy_array_equal()
def test_compress_empty_1dim(self, xp):
a = testing.shaped_arange((3, 4, 5), xp)
b = xp.array([])
return xp.compress(b, a, axis=1)

@testing.numpy_cupy_array_equal()
def test_compress_empty_1dim_no_axis(self, xp):
a = testing.shaped_arange((3, 4, 5), xp)
b = xp.array([])
return xp.compress(b, a)

@testing.for_all_dtypes()
@testing.numpy_cupy_array_equal()
def test_diagonal(self, xp, dtype):
Expand Down