Skip to content

Commit a7476f4

Browse files
committed
add rot90 and resize
1 parent 37c4305 commit a7476f4

File tree

6 files changed

+369
-16
lines changed

6 files changed

+369
-16
lines changed

dpnp/dpnp_iface_manipulation.py

Lines changed: 202 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -77,9 +77,11 @@
7777
"ravel",
7878
"repeat",
7979
"reshape",
80+
"resize",
8081
"result_type",
8182
"roll",
8283
"rollaxis",
84+
"rot90",
8385
"row_stack",
8486
"shape",
8587
"size",
@@ -1985,7 +1987,8 @@ def reshape(a, /, newshape, order="C", copy=None):
19851987
If ``False``, the result array can never be a copy
19861988
and a ValueError exception will be raised in case the copy is necessary.
19871989
If ``None``, the result array will reuse existing memory buffer of `a`
1988-
if possible and copy otherwise. Default: None.
1990+
if possible and copy otherwise.
1991+
Default: ``None``.
19891992
19901993
Returns
19911994
-------
@@ -2004,14 +2007,14 @@ def reshape(a, /, newshape, order="C", copy=None):
20042007
20052008
Examples
20062009
--------
2007-
>>> import dpnp as dp
2008-
>>> a = dp.array([[1, 2, 3], [4, 5, 6]])
2009-
>>> dp.reshape(a, 6)
2010+
>>> import dpnp as np
2011+
>>> a = np.array([[1, 2, 3], [4, 5, 6]])
2012+
>>> np.reshape(a, 6)
20102013
array([1, 2, 3, 4, 5, 6])
2011-
>>> dp.reshape(a, 6, order='F')
2014+
>>> np.reshape(a, 6, order='F')
20122015
array([1, 4, 2, 5, 3, 6])
20132016
2014-
>>> dp.reshape(a, (3, -1)) # the unspecified value is inferred to be 2
2017+
>>> np.reshape(a, (3, -1)) # the unspecified value is inferred to be 2
20152018
array([[1, 2],
20162019
[3, 4],
20172020
[5, 6]])
@@ -2031,6 +2034,91 @@ def reshape(a, /, newshape, order="C", copy=None):
20312034
return dpnp_array._create_from_usm_ndarray(usm_res)
20322035

20332036

2037+
def resize(a, new_shape):
2038+
"""
2039+
Return a new array with the specified shape.
2040+
2041+
If the new array is larger than the original array, then the new array is
2042+
filled with repeated copies of `a`. Note that this behavior is different
2043+
from ``a.resize(new_shape)`` which fills with zeros instead of repeated
2044+
copies of `a`.
2045+
2046+
For full documentation refer to :obj:`numpy.resize`.
2047+
2048+
Parameters
2049+
----------
2050+
a : {dpnp.ndarray, usm_ndarray}
2051+
Array to be resized.
2052+
new_shape : int or tuple or list of ints
2053+
Shape of resized array.
2054+
2055+
Returns
2056+
-------
2057+
out : dpnp.ndarray
2058+
The new array is formed from the data in the old array, repeated
2059+
if necessary to fill out the required number of elements. The
2060+
data are repeated iterating over the array in C-order.
2061+
2062+
See Also
2063+
--------
2064+
:obj:`dpnp.ndarray.reshape` : Resize an array in-place.
2065+
:obj:`dpnp.reshape` : Reshape an array without changing the total size.
2066+
:obj:`dpnp.pad` : Enlarge and pad an array.
2067+
:obj:`dpnp.repeat` : Repeat elements of an array.
2068+
2069+
Notes
2070+
-----
2071+
When the total size of the array does not change :obj:`dpnp.reshape` should
2072+
be used. In most other cases either indexing (to reduce the size) or
2073+
padding (to increase the size) may be a more appropriate solution.
2074+
2075+
Warning: This functionality does **not** consider axes separately,
2076+
i.e. it does not apply interpolation/extrapolation.
2077+
It fills the return array with the required number of elements, iterating
2078+
over `a` in C-order, disregarding axes (and cycling back from the start if
2079+
the new shape is larger). This functionality is therefore not suitable to
2080+
resize images, or data where each axis represents a separate and distinct
2081+
entity.
2082+
2083+
Examples
2084+
--------
2085+
>>> import dpnp as np
2086+
>>> a=np.array([[0, 1], [2, 3]])
2087+
>>> np.resize(a, (2 ,3))
2088+
array([[0, 1, 2],
2089+
[3, 0, 1]])
2090+
>>> np.resize(a, (1, 4))
2091+
array([[0, 1, 2, 3]])
2092+
>>> np.resize(a, (2, 4))
2093+
array([[0, 1, 2, 3],
2094+
[0, 1, 2, 3]])
2095+
2096+
"""
2097+
2098+
dpnp.check_supported_arrays_type(a)
2099+
if a.ndim == 0:
2100+
return dpnp.full(new_shape, a)
2101+
2102+
if isinstance(new_shape, (int, numpy.integer)):
2103+
new_shape = (new_shape,)
2104+
2105+
a = dpnp.ravel(a)
2106+
new_size = 1
2107+
for dim_length in new_shape:
2108+
if dim_length < 0:
2109+
raise ValueError("all elements of `new_shape` must be non-negative")
2110+
new_size *= dim_length
2111+
2112+
if a.size == 0 or new_size == 0:
2113+
# First case must zero fill. The second would have repeats == 0.
2114+
return dpnp.zeros_like(a, shape=new_shape)
2115+
2116+
repeats = -(-new_size // a.size) # ceil division
2117+
a = dpnp.concatenate((a,) * repeats)[:new_size]
2118+
2119+
return a.reshape(new_shape)
2120+
2121+
20342122
def result_type(*arrays_and_dtypes):
20352123
"""
20362124
result_type(*arrays_and_dtypes)
@@ -2052,16 +2140,16 @@ def result_type(*arrays_and_dtypes):
20522140
20532141
Examples
20542142
--------
2055-
>>> import dpnp as dp
2056-
>>> a = dp.arange(3, dtype=dp.int64)
2057-
>>> b = dp.arange(7, dtype=dp.int32)
2058-
>>> dp.result_type(a, b)
2143+
>>> import dpnp as np
2144+
>>> a = np.arange(3, dtype=np.int64)
2145+
>>> b = np.arange(7, dtype=np.int32)
2146+
>>> np.result_type(a, b)
20592147
dtype('int64')
20602148
2061-
>>> dp.result_type(dp.int64, dp.complex128)
2149+
>>> np.result_type(np.int64, np.complex128)
20622150
dtype('complex128')
20632151
2064-
>>> dp.result_type(dp.ones(10, dtype=dp.float32), dp.float64)
2152+
>>> np.result_type(np.ones(10, dtype=np.float32), np.float64)
20652153
dtype('float64')
20662154
20672155
"""
@@ -2200,6 +2288,108 @@ def rollaxis(x, axis, start=0):
22002288
return dpnp.moveaxis(usm_array, source=axis, destination=start)
22012289

22022290

2291+
def rot90(m, k=1, axes=(0, 1)):
2292+
"""
2293+
Rotate an array by 90 degrees in the plane specified by axes.
2294+
2295+
Rotation direction is from the first towards the second axis.
2296+
This means for a 2D array with the default `k` and `axes`, the
2297+
rotation will be counterclockwise.
2298+
2299+
For full documentation refer to :obj:`numpy.rot90`.
2300+
2301+
Parameters
2302+
----------
2303+
m : {dpnp.ndarray, usm_ndarray}
2304+
Array of two or more dimensions.
2305+
k : integer, optional
2306+
Number of times the array is rotated by 90 degrees.
2307+
Default: ``1``.
2308+
axes : (2,) array_like of ints, optional
2309+
The array is rotated in the plane defined by the axes.
2310+
Axes must be different.
2311+
Default: ``(0, 1)``.
2312+
2313+
Returns
2314+
-------
2315+
out : dpnp.ndarray
2316+
A rotated view of `m`.
2317+
2318+
See Also
2319+
--------
2320+
:obj:`dpnp.flip` : Reverse the order of elements in an array along
2321+
the given axis.
2322+
:obj:`dpnp.fliplr` : Flip an array horizontally.
2323+
:obj:`dpnp.flipud` : Flip an array vertically.
2324+
2325+
Notes
2326+
-----
2327+
``rot90(m, k=1, axes=(1,0))`` is the reverse of
2328+
``rot90(m, k=1, axes=(0,1))``.
2329+
2330+
``rot90(m, k=1, axes=(1,0))`` is equivalent to
2331+
``rot90(m, k=-1, axes=(0,1))``.
2332+
2333+
Examples
2334+
--------
2335+
>>> import dpnp as np
2336+
>>> m = np.array([[1, 2], [3, 4]])
2337+
>>> m
2338+
array([[1, 2],
2339+
[3, 4]])
2340+
>>> np.rot90(m)
2341+
array([[2, 4],
2342+
[1, 3]])
2343+
>>> np.rot90(m, 2)
2344+
array([[4, 3],
2345+
[2, 1]])
2346+
>>> m = np.arange(8).reshape((2, 2, 2))
2347+
>>> np.rot90(m, 1, (1, 2))
2348+
array([[[1, 3],
2349+
[0, 2]],
2350+
[[5, 7],
2351+
[4, 6]]])
2352+
2353+
"""
2354+
2355+
dpnp.check_supported_arrays_type(m)
2356+
if not isinstance(k, (int, dpnp.integer)):
2357+
raise TypeError("k must be an integer.")
2358+
2359+
m_ndim = m.ndim
2360+
if m_ndim < 2:
2361+
raise ValueError("Input must be at least 2-d.")
2362+
2363+
if len(axes) != 2:
2364+
raise ValueError("len(axes) must be 2.")
2365+
2366+
if axes[0] == axes[1] or abs(axes[0] - axes[1]) == m_ndim:
2367+
raise ValueError("Axes must be different.")
2368+
2369+
if not (-m_ndim <= axes[0] < m_ndim and -m_ndim <= axes[1] < m_ndim):
2370+
raise ValueError(
2371+
f"Axes={axes} out of range for array of ndim={m_ndim}."
2372+
)
2373+
2374+
k %= 4
2375+
if k == 0:
2376+
return m[:]
2377+
if k == 2:
2378+
return dpnp.flip(dpnp.flip(m, axes[0]), axes[1])
2379+
2380+
axes_list = list(range(0, m_ndim))
2381+
(axes_list[axes[0]], axes_list[axes[1]]) = (
2382+
axes_list[axes[1]],
2383+
axes_list[axes[0]],
2384+
)
2385+
2386+
if k == 1:
2387+
return dpnp.transpose(flip(m, axes[1]), axes_list)
2388+
2389+
# k == 3
2390+
return dpnp.flip(dpnp.transpose(m, axes_list), axes[1])
2391+
2392+
22032393
def shape(a):
22042394
"""
22052395
Return the shape of an array.

tests/test_manipulation.py

Lines changed: 117 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
import numpy
33
import pytest
44
from dpctl.tensor._numpy_helper import AxisError
5-
from numpy.testing import assert_array_equal, assert_raises
5+
from numpy.testing import assert_array_equal, assert_equal, assert_raises
66

77
import dpnp
88
from tests.third_party.cupy import testing
@@ -665,6 +665,122 @@ def test_minimum_signed_integers(self, data, dtype):
665665
assert_array_equal(result, expected)
666666

667667

668+
class TestResize:
669+
def test_copies(self):
670+
a = numpy.array([[1, 2], [3, 4]])
671+
ia = dpnp.array(a)
672+
assert_equal(dpnp.resize(ia, (2, 4)), numpy.resize(a, (2, 4)))
673+
674+
a = numpy.array([[1, 2], [3, 4], [1, 2], [3, 4]])
675+
ia = dpnp.array(a)
676+
assert_equal(dpnp.resize(ia, (4, 2)), numpy.resize(a, (4, 2)))
677+
678+
a = numpy.array([[1, 2, 3], [4, 1, 2], [3, 4, 1], [2, 3, 4]])
679+
ia = dpnp.array(a)
680+
assert_equal(dpnp.resize(ia, (4, 3)), numpy.resize(a, (4, 3)))
681+
682+
@pytest.mark.parametrize("newshape", [(2, 4), [2, 4], (10,), 10])
683+
def test_newshape_type(self, newshape):
684+
a = numpy.array([[1, 2], [3, 4]])
685+
ia = dpnp.array(a)
686+
assert_equal(dpnp.resize(ia, newshape), numpy.resize(a, newshape))
687+
688+
def test_repeats(self):
689+
a = numpy.array([1, 2, 3])
690+
ia = dpnp.array(a)
691+
assert_equal(dpnp.resize(ia, (2, 4)), numpy.resize(a, (2, 4)))
692+
693+
a = numpy.array([[1, 2], [3, 1], [2, 3], [1, 2]])
694+
ia = dpnp.array(a)
695+
assert_equal(dpnp.resize(ia, (4, 2)), numpy.resize(a, (4, 2)))
696+
697+
a = numpy.array([[1, 2, 3], [1, 2, 3], [1, 2, 3], [1, 2, 3]])
698+
ia = dpnp.array(a)
699+
assert_equal(dpnp.resize(ia, (4, 3)), numpy.resize(a, (4, 3)))
700+
701+
def test_zeroresize(self):
702+
a = numpy.array([[1, 2], [3, 4]])
703+
ia = dpnp.array(a)
704+
assert_array_equal(dpnp.resize(ia, (0,)), numpy.resize(a, (0,)))
705+
assert_equal(a.dtype, ia.dtype)
706+
707+
assert_equal(dpnp.resize(ia, (0, 2)), numpy.resize(a, (0, 2)))
708+
assert_equal(dpnp.resize(ia, (2, 0)), numpy.resize(a, (2, 0)))
709+
710+
def test_reshape_from_zero(self):
711+
a = numpy.zeros(0, dtype=numpy.float32)
712+
ia = dpnp.array(a)
713+
assert_array_equal(dpnp.resize(ia, (2, 1)), numpy.resize(a, (2, 1)))
714+
assert_equal(a.dtype, ia.dtype)
715+
716+
@pytest.mark.parametrize("xp", [numpy, dpnp])
717+
def test_negative_resize(self, xp):
718+
a = xp.arange(0, 10, dtype=xp.float32)
719+
new_shape = (-10, -1)
720+
with pytest.raises(ValueError, match=r"negative"):
721+
xp.resize(a, new_shape=new_shape)
722+
723+
724+
class TestRot90:
725+
@pytest.mark.parametrize("xp", [numpy, dpnp])
726+
def test_error(self, xp):
727+
assert_raises(ValueError, xp.rot90, xp.ones(4))
728+
assert_raises(ValueError, xp.rot90, xp.ones((2, 2, 2)), axes=(0, 1, 2))
729+
assert_raises(ValueError, xp.rot90, xp.ones((2, 2)), axes=(0, 2))
730+
assert_raises(ValueError, xp.rot90, xp.ones((2, 2)), axes=(1, 1))
731+
assert_raises(ValueError, xp.rot90, xp.ones((2, 2, 2)), axes=(-2, 1))
732+
if xp == dpnp:
733+
# NumPy return result of k=3 incorrectly when k is float
734+
assert_raises(TypeError, xp.rot90, xp.ones((2, 2)), k=2.5)
735+
736+
def test_basic(self):
737+
a = numpy.array([[0, 1, 2], [3, 4, 5]])
738+
ia = dpnp.array(a)
739+
740+
for k in range(-3, 13, 4):
741+
assert_equal(dpnp.rot90(ia, k=k), numpy.rot90(a, k=k))
742+
for k in range(-2, 13, 4):
743+
assert_equal(dpnp.rot90(ia, k=k), numpy.rot90(a, k=k))
744+
for k in range(-1, 13, 4):
745+
assert_equal(dpnp.rot90(ia, k=k), numpy.rot90(a, k=k))
746+
for k in range(0, 13, 4):
747+
assert_equal(dpnp.rot90(ia, k=k), numpy.rot90(a, k=k))
748+
749+
assert_equal(dpnp.rot90(dpnp.rot90(ia, axes=(0, 1)), axes=(1, 0)), ia)
750+
assert_equal(
751+
dpnp.rot90(ia, k=1, axes=(1, 0)), dpnp.rot90(ia, k=-1, axes=(0, 1))
752+
)
753+
754+
def test_axes(self):
755+
a = numpy.ones((50, 40, 3))
756+
ia = dpnp.array(a)
757+
assert_equal(dpnp.rot90(ia), numpy.rot90(a))
758+
assert_equal(dpnp.rot90(ia, axes=(0, 2)), dpnp.rot90(ia, axes=(0, -1)))
759+
assert_equal(dpnp.rot90(ia, axes=(1, 2)), dpnp.rot90(ia, axes=(-2, -1)))
760+
761+
@pytest.mark.parametrize(
762+
"axes", [(1, 2), [1, 2], numpy.array([1, 2]), dpnp.array([1, 2])]
763+
)
764+
def test_axes_type(self, axes):
765+
a = numpy.ones((50, 40, 3))
766+
ia = dpnp.array(a)
767+
assert_equal(dpnp.rot90(ia, axes=axes), numpy.rot90(a, axes=axes))
768+
769+
def test_rotation_axes(self):
770+
a = numpy.arange(8).reshape((2, 2, 2))
771+
ia = dpnp.array(a)
772+
773+
assert_equal(dpnp.rot90(ia, axes=(0, 1)), numpy.rot90(a, axes=(0, 1)))
774+
assert_equal(dpnp.rot90(ia, axes=(1, 0)), numpy.rot90(a, axes=(1, 0)))
775+
assert_equal(dpnp.rot90(ia, axes=(1, 2)), numpy.rot90(a, axes=(1, 2)))
776+
777+
for k in range(1, 5):
778+
assert_equal(
779+
dpnp.rot90(ia, k=k, axes=(2, 0)),
780+
numpy.rot90(a, k=k, axes=(2, 0)),
781+
)
782+
783+
668784
class TestTranspose:
669785
@pytest.mark.parametrize("axes", [(0, 1), (1, 0), [0, 1]])
670786
def test_2d_with_axes(self, axes):

0 commit comments

Comments
 (0)