Skip to content

Commit b694d87

Browse files
authored
Added flipping functions (#1543)
* Added flipping functions * Applied the review comments
1 parent 0fd57d4 commit b694d87

File tree

3 files changed

+622
-2
lines changed

3 files changed

+622
-2
lines changed

dpnp/dpnp_iface_manipulation.py

+183-2
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,9 @@
5858
"concatenate",
5959
"copyto",
6060
"expand_dims",
61+
"flip",
62+
"fliplr",
63+
"flipud",
6164
"hstack",
6265
"moveaxis",
6366
"ravel",
@@ -399,7 +402,7 @@ def expand_dims(a, axis):
399402
400403
Returns
401404
-------
402-
dpnp.ndarray
405+
out : dpnp.ndarray
403406
An array with the number of dimensions increased.
404407
A view is returned whenever possible.
405408
@@ -476,6 +479,184 @@ def expand_dims(a, axis):
476479
)
477480

478481

482+
def flip(m, axis=None):
483+
"""
484+
Reverse the order of elements in an array along the given axis.
485+
486+
The shape of the array is preserved, but the elements are reordered.
487+
488+
For full documentation refer to :obj:`numpy.flip`.
489+
490+
Returns
491+
-------
492+
out : dpnp.ndarray
493+
A view of `m` with the entries of axis reversed.
494+
495+
Limitations
496+
-----------
497+
Parameters `m` is supported either as :class:`dpnp.ndarray`
498+
or :class:`dpctl.tensor.usm_ndarray`.
499+
Input array data types are limited by supported DPNP :ref:`Data types`.
500+
Otherwise ``TypeError`` exception will be raised.
501+
502+
See Also
503+
--------
504+
:obj:`dpnp.flipud` : Flip an array vertically (axis=0).
505+
:obj:`dpnp.fliplr` : Flip an array horizontally (axis=1).
506+
507+
Examples
508+
--------
509+
>>> import dpnp as np
510+
>>> A = np.arange(8).reshape((2, 2, 2))
511+
>>> A
512+
array([[[0, 1],
513+
[2, 3]],
514+
[[4, 5],
515+
[6, 7]]])
516+
>>> np.flip(A, 0)
517+
array([[[4, 5],
518+
[6, 7]],
519+
[[0, 1],
520+
[2, 3]]])
521+
>>> np.flip(A, 1)
522+
array([[[2, 3],
523+
[0, 1]],
524+
[[6, 7],
525+
[4, 5]]])
526+
>>> np.flip(A)
527+
array([[[7, 6],
528+
[5, 4]],
529+
[[3, 2],
530+
[1, 0]]])
531+
>>> np.flip(A, (0, 2))
532+
array([[[5, 4],
533+
[7, 6]],
534+
[[1, 0],
535+
[3, 2]]])
536+
>>> A = np.random.randn(3, 4, 5)
537+
>>> np.all(np.flip(A, 2) == A[:, :, ::-1, ...])
538+
array(True)
539+
540+
"""
541+
542+
m_usm = dpnp.get_usm_ndarray(m)
543+
return dpnp_array._create_from_usm_ndarray(dpt.flip(m_usm, axis=axis))
544+
545+
546+
def fliplr(m):
547+
"""
548+
Reverse the order of elements along axis 1 (left/right).
549+
550+
For a 2-D array, this flips the entries in each row in the left/right
551+
direction. Columns are preserved, but appear in a different order than
552+
before.
553+
554+
For full documentation refer to :obj:`numpy.fliplr`.
555+
556+
Returns
557+
-------
558+
out : dpnp.ndarray
559+
A view of `m` with the columns reversed.
560+
561+
Limitations
562+
-----------
563+
Parameters `m` is supported either as :class:`dpnp.ndarray`
564+
or :class:`dpctl.tensor.usm_ndarray`.
565+
Input array data types are limited by supported DPNP :ref:`Data types`.
566+
Otherwise ``TypeError`` exception will be raised.
567+
568+
See Also
569+
--------
570+
:obj:`dpnp.flipud` : Flip an array vertically (axis=0).
571+
:obj:`dpnp.flip` : Flip array in one or more dimensions.
572+
573+
Examples
574+
--------
575+
>>> import dpnp as np
576+
>>> A = np.diag(np.array([1., 2., 3.]))
577+
>>> A
578+
array([[1., 0., 0.],
579+
[0., 2., 0.],
580+
[0., 0., 3.]])
581+
>>> np.fliplr(A)
582+
array([[0., 0., 1.],
583+
[0., 2., 0.],
584+
[3., 0., 0.]])
585+
586+
>>> A = np.random.randn(2, 3, 5)
587+
>>> np.all(np.fliplr(A) == A[:, ::-1, ...])
588+
array(True)
589+
590+
"""
591+
592+
if not dpnp.is_supported_array_type(m):
593+
raise TypeError(
594+
"An array must be any of supported type, but got {}".format(type(m))
595+
)
596+
597+
if m.ndim < 2:
598+
raise ValueError(f"Input must be >= 2-d, but got {m.ndim}")
599+
return m[:, ::-1]
600+
601+
602+
def flipud(m):
603+
"""
604+
Reverse the order of elements along axis 0 (up/down).
605+
606+
For a 2-D array, this flips the entries in each column in the up/down
607+
direction. Rows are preserved, but appear in a different order than before.
608+
609+
For full documentation refer to :obj:`numpy.flipud`.
610+
611+
Returns
612+
-------
613+
out : dpnp.ndarray
614+
A view of `m` with the rows reversed.
615+
616+
Limitations
617+
-----------
618+
Parameters `m` is supported either as :class:`dpnp.ndarray`
619+
or :class:`dpctl.tensor.usm_ndarray`.
620+
Input array data types are limited by supported DPNP :ref:`Data types`.
621+
Otherwise ``TypeError`` exception will be raised.
622+
623+
See Also
624+
--------
625+
:obj:`dpnp.fliplr` : Flip array in the left/right direction.
626+
:obj:`dpnp.flip` : Flip array in one or more dimensions.
627+
628+
Examples
629+
--------
630+
>>> import dpnp as np
631+
>>> A = np.diag(np.array([1., 2., 3.]))
632+
>>> A
633+
array([[1., 0., 0.],
634+
[0., 2., 0.],
635+
[0., 0., 3.]])
636+
>>> np.flipud(A)
637+
array([[0., 0., 3.],
638+
[0., 2., 0.],
639+
[1., 0., 0.]])
640+
641+
>>> A = np.random.randn(2, 3, 5)
642+
>>> np.all(np.flipud(A) == A[::-1, ...])
643+
array(True)
644+
645+
>>> np.flipud(np.array([1, 2]))
646+
array([2, 1])
647+
648+
"""
649+
650+
if not dpnp.is_supported_array_type(m):
651+
raise TypeError(
652+
"An array must be any of supported type, but got {}".format(type(m))
653+
)
654+
655+
if m.ndim < 1:
656+
raise ValueError(f"Input must be >= 1-d, but got {m.ndim}")
657+
return m[::-1, ...]
658+
659+
479660
def hstack(tup):
480661
"""
481662
Stack arrays in sequence horizontally (column wise).
@@ -949,7 +1130,7 @@ def swapaxes(a, axis1, axis2):
9491130
9501131
Returns
9511132
-------
952-
dpnp.ndarray
1133+
out : dpnp.ndarray
9531134
An array with with swapped axes.
9541135
A view is returned whenever possible.
9551136

tests/test_flipping.py

+169
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
from math import prod
2+
3+
import numpy
4+
import pytest
5+
from numpy.testing import (
6+
assert_equal,
7+
)
8+
9+
import dpnp
10+
11+
from .helper import (
12+
get_all_dtypes,
13+
)
14+
15+
16+
class TestFlip:
17+
@pytest.mark.parametrize("dtype", get_all_dtypes())
18+
def test_arange_2d_default_axis(self, dtype):
19+
sh = (2, 3) if dtype != dpnp.bool else (1, 1)
20+
dp_a = dpnp.arange(prod(sh), dtype=dtype).reshape(sh)
21+
np_a = numpy.arange(prod(sh), dtype=dtype).reshape(sh)
22+
23+
assert_equal(dpnp.flip(dp_a), numpy.flip(np_a))
24+
25+
@pytest.mark.parametrize("axis", list(range(3)))
26+
@pytest.mark.parametrize(
27+
"dtype", get_all_dtypes(no_bool=True, no_none=True)
28+
)
29+
def test_arange_3d(self, axis, dtype):
30+
sh = (2, 2, 2)
31+
dp_a = dpnp.arange(prod(sh), dtype=dtype).reshape(sh)
32+
np_a = numpy.arange(prod(sh), dtype=dtype).reshape(sh)
33+
34+
assert_equal(dpnp.flip(dp_a, axis=axis), numpy.flip(np_a, axis=axis))
35+
36+
@pytest.mark.parametrize("axis", [(), (0, 2), (1, 2)])
37+
@pytest.mark.parametrize(
38+
"dtype", get_all_dtypes(no_bool=True, no_none=True)
39+
)
40+
def test_arange_3d_multiple_axes(self, axis, dtype):
41+
sh = (2, 2, 2)
42+
dp_a = dpnp.arange(prod(sh), dtype=dtype).reshape(sh)
43+
np_a = numpy.arange(prod(sh), dtype=dtype).reshape(sh)
44+
45+
assert_equal(dpnp.flip(dp_a, axis=axis), numpy.flip(np_a, axis=axis))
46+
47+
@pytest.mark.parametrize("axis", list(range(4)))
48+
@pytest.mark.parametrize(
49+
"dtype", get_all_dtypes(no_bool=True, no_none=True)
50+
)
51+
def test_arange_4d(self, axis, dtype):
52+
sh = (2, 3, 4, 5)
53+
dp_a = dpnp.arange(prod(sh), dtype=dtype).reshape(sh)
54+
np_a = numpy.arange(prod(sh), dtype=dtype).reshape(sh)
55+
56+
assert_equal(dpnp.flip(dp_a, axis=axis), numpy.flip(np_a, axis=axis))
57+
58+
@pytest.mark.parametrize(
59+
"dtype", get_all_dtypes(no_bool=True, no_none=True)
60+
)
61+
def test_lr_equivalent(self, dtype):
62+
dp_a = dpnp.arange(4, dtype=dtype)
63+
dp_a = dp_a[:, dpnp.newaxis] + dp_a[dpnp.newaxis, :]
64+
assert_equal(dpnp.flip(dp_a, 1), dpnp.fliplr(dp_a))
65+
66+
np_a = numpy.arange(4, dtype=dtype)
67+
np_a = numpy.add.outer(np_a, np_a)
68+
assert_equal(dpnp.flip(dp_a, 1), numpy.flip(np_a, 1))
69+
70+
@pytest.mark.parametrize(
71+
"dtype", get_all_dtypes(no_bool=True, no_none=True)
72+
)
73+
def test_ud_equivalent(self, dtype):
74+
dp_a = dpnp.arange(4, dtype=dtype)
75+
dp_a = dp_a[:, dpnp.newaxis] + dp_a[dpnp.newaxis, :]
76+
assert_equal(dpnp.flip(dp_a, 0), dpnp.flipud(dp_a))
77+
78+
np_a = numpy.arange(4, dtype=dtype)
79+
np_a = numpy.add.outer(np_a, np_a)
80+
assert_equal(dpnp.flip(dp_a, 0), numpy.flip(np_a, 0))
81+
82+
@pytest.mark.parametrize(
83+
"x, axis",
84+
[
85+
pytest.param(dpnp.ones(4), 1, id="1-d, axis=1"),
86+
pytest.param(dpnp.ones((4, 4)), 2, id="2-d, axis=2"),
87+
pytest.param(dpnp.ones((4, 4)), -3, id="2-d, axis=-3"),
88+
pytest.param(dpnp.ones((4, 4)), (0, 3), id="2-d, axis=(0, 3)"),
89+
],
90+
)
91+
def test_axes(self, x, axis):
92+
with pytest.raises(numpy.AxisError):
93+
dpnp.flip(x, axis=axis)
94+
95+
96+
class TestFliplr:
97+
@pytest.mark.parametrize("dtype", get_all_dtypes())
98+
def test_arange(self, dtype):
99+
sh = (2, 3) if dtype != dpnp.bool else (1, 1)
100+
dp_a = dpnp.arange(prod(sh), dtype=dtype).reshape(sh)
101+
np_a = numpy.arange(prod(sh), dtype=dtype).reshape(sh)
102+
103+
assert_equal(dpnp.fliplr(dp_a), numpy.fliplr(np_a))
104+
105+
@pytest.mark.parametrize(
106+
"dtype", get_all_dtypes(no_bool=True, no_none=True)
107+
)
108+
def test_equivalent(self, dtype):
109+
dp_a = dpnp.arange(4, dtype=dtype)
110+
dp_a = dp_a[:, dpnp.newaxis] + dp_a[dpnp.newaxis, :]
111+
assert_equal(dpnp.fliplr(dp_a), dp_a[:, ::-1])
112+
113+
np_a = numpy.arange(4, dtype=dtype)
114+
np_a = numpy.add.outer(np_a, np_a)
115+
assert_equal(dpnp.fliplr(dp_a), numpy.fliplr(np_a))
116+
117+
@pytest.mark.parametrize(
118+
"val",
119+
[-1.2, numpy.arange(7), [2, 7, 3.6], (-3, 4), range(4)],
120+
ids=["scalar", "numpy.array", "list", "tuple", "range"],
121+
)
122+
def test_raises_array_type(self, val):
123+
with pytest.raises(
124+
TypeError, match="An array must be any of supported type, but got"
125+
):
126+
dpnp.fliplr(val)
127+
128+
def test_raises_1d(self):
129+
a = dpnp.ones(4)
130+
with pytest.raises(ValueError, match="Input must be >= 2-d, but got"):
131+
dpnp.fliplr(a)
132+
133+
134+
class TestFlipud:
135+
@pytest.mark.parametrize("dtype", get_all_dtypes())
136+
def test_arange(self, dtype):
137+
sh = (2, 3) if dtype != dpnp.bool else (1, 1)
138+
dp_a = dpnp.arange(prod(sh), dtype=dtype).reshape(sh)
139+
np_a = numpy.arange(prod(sh), dtype=dtype).reshape(sh)
140+
141+
assert_equal(dpnp.flipud(dp_a), numpy.flipud(np_a))
142+
143+
@pytest.mark.parametrize(
144+
"dtype", get_all_dtypes(no_bool=True, no_none=True)
145+
)
146+
def test_equivalent(self, dtype):
147+
dp_a = dpnp.arange(4, dtype=dtype)
148+
dp_a = dp_a[:, dpnp.newaxis] + dp_a[dpnp.newaxis, :]
149+
assert_equal(dpnp.flipud(dp_a), dp_a[::-1, :])
150+
151+
np_a = numpy.arange(4, dtype=dtype)
152+
np_a = numpy.add.outer(np_a, np_a)
153+
assert_equal(dpnp.flipud(dp_a), numpy.flipud(np_a))
154+
155+
@pytest.mark.parametrize(
156+
"val",
157+
[3.4, numpy.arange(6), [2, -1.7, 6], (-2, 4), range(5)],
158+
ids=["scalar", "numpy.array", "list", "tuple", "range"],
159+
)
160+
def test_raises_array_type(self, val):
161+
with pytest.raises(
162+
TypeError, match="An array must be any of supported type, but got"
163+
):
164+
dpnp.flipud(val)
165+
166+
def test_raises_0d(self):
167+
a = dpnp.array(3)
168+
with pytest.raises(ValueError, match="Input must be >= 1-d, but got"):
169+
dpnp.flipud(a)

0 commit comments

Comments
 (0)