Skip to content

Commit a079815

Browse files
antonwolfyvtavana
andauthored
Implement dpnp.cumprod through dpctl.tensor (#1811)
Co-authored-by: vtavana <[email protected]>
1 parent 4c4b5c5 commit a079815

File tree

8 files changed

+150
-44
lines changed

8 files changed

+150
-44
lines changed

dpnp/dpnp_algo/dpnp_algo_mathematical.pxi

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@ and the rest of the library
3636
# NO IMPORTs here. All imports must be placed into main "dpnp_algo.pyx" file
3737

3838
__all__ += [
39-
"dpnp_cumprod",
4039
"dpnp_ediff1d",
4140
"dpnp_fabs",
4241
"dpnp_fmod",

dpnp/dpnp_array.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -721,7 +721,16 @@ def copy(self, order="C"):
721721
return dpnp.copy(self, order=order)
722722

723723
# 'ctypes',
724-
# 'cumprod',
724+
725+
def cumprod(self, axis=None, dtype=None, out=None):
726+
"""
727+
Return the cumulative product of the elements along the given axis.
728+
729+
Refer to :obj:`dpnp.cumprod` for full documentation.
730+
731+
"""
732+
733+
return dpnp.cumprod(self, axis=axis, dtype=dtype, out=out)
725734

726735
def cumsum(self, axis=None, dtype=None, out=None):
727736
"""

dpnp/dpnp_iface_mathematical.py

Lines changed: 65 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,6 @@
5858

5959
from .backend.extensions.sycl_ext import _sycl_ext_impl
6060
from .dpnp_algo import (
61-
dpnp_cumprod,
6261
dpnp_ediff1d,
6362
dpnp_fabs,
6463
dpnp_fmax,
@@ -806,38 +805,85 @@ def cross(a, b, axisa=-1, axisb=-1, axisc=-1, axis=None):
806805
return dpnp.moveaxis(cp, -1, axisc)
807806

808807

809-
def cumprod(x1, **kwargs):
808+
def cumprod(a, axis=None, dtype=None, out=None):
810809
"""
811810
Return the cumulative product of elements along a given axis.
812811
813812
For full documentation refer to :obj:`numpy.cumprod`.
814813
815-
Limitations
816-
-----------
817-
Parameter `x` is supported as :class:`dpnp.ndarray`.
818-
Keyword argument `kwargs` is currently unsupported.
819-
Otherwise the function will be executed sequentially on CPU.
820-
Input array data types are limited by supported DPNP :ref:`Data types`.
814+
Parameters
815+
----------
816+
a : {dpnp.ndarray, usm_ndarray}
817+
Input array.
818+
axis : {None, int}, optional
819+
Axis along which the cumulative product is computed. It defaults to
820+
compute the cumulative product over the flattened array.
821+
Default: ``None``.
822+
dtype : {None, dtype}, optional
823+
Type of the returned array and of the accumulator in which the elements
824+
are multiplied. If `dtype` is not specified, it defaults to the dtype
825+
of `a`, unless `a` has an integer dtype with a precision less than that
826+
of the default platform integer. In that case, the default platform
827+
integer is used.
828+
Default: ``None``.
829+
out : {None, dpnp.ndarray, usm_ndarray}, optional
830+
Alternative output array in which to place the result. It must have the
831+
same shape and buffer length as the expected output but the type will
832+
be cast if necessary.
833+
Default: ``None``.
834+
835+
Returns
836+
-------
837+
out : dpnp.ndarray
838+
A new array holding the result is returned unless `out` is specified as
839+
:class:`dpnp.ndarray`, in which case a reference to `out` is returned.
840+
The result has the same size as `a`, and the same shape as `a` if `axis`
841+
is not ``None`` or `a` is a 1-d array.
842+
843+
See Also
844+
--------
845+
:obj:`dpnp.prod` : Product array elements.
821846
822847
Examples
823848
--------
824849
>>> import dpnp as np
825850
>>> a = np.array([1, 2, 3])
826-
>>> result = np.cumprod(a)
827-
>>> [x for x in result]
828-
[1, 2, 6]
829-
>>> b = np.array([[1, 2, 3], [4, 5, 6]])
830-
>>> result = np.cumprod(b)
831-
>>> [x for x in result]
832-
[1, 2, 6, 24, 120, 720]
851+
>>> np.cumprod(a) # intermediate results 1, 1*2
852+
... # total product 1*2*3 = 6
853+
array([1, 2, 6])
854+
>>> a = np.array([[1, 2, 3], [4, 5, 6]])
855+
>>> np.cumprod(a, dtype=np.float32) # specify type of output
856+
array([ 1., 2., 6., 24., 120., 720.], dtype=float32)
857+
858+
The cumulative product for each column (i.e., over the rows) of `a`:
859+
860+
>>> np.cumprod(a, axis=0)
861+
array([[ 1, 2, 3],
862+
[ 4, 10, 18]])
863+
864+
The cumulative product for each row (i.e. over the columns) of `a`:
865+
866+
>>> np.cumprod(a, axis=1)
867+
array([[ 1, 2, 6],
868+
[ 4, 20, 120]])
833869
834870
"""
835871

836-
x1_desc = dpnp.get_dpnp_descriptor(x1, copy_when_nondefault_queue=False)
837-
if x1_desc and not kwargs:
838-
return dpnp_cumprod(x1_desc).get_pyobj()
872+
dpnp.check_supported_arrays_type(a)
873+
if a.ndim > 1 and axis is None:
874+
usm_a = dpnp.ravel(a).get_array()
875+
else:
876+
usm_a = dpnp.get_usm_ndarray(a)
839877

840-
return call_origin(numpy.cumprod, x1, **kwargs)
878+
return dpnp_wrap_reduction_call(
879+
a,
880+
out,
881+
dpt.cumulative_prod,
882+
_get_reduction_res_dt,
883+
usm_a,
884+
axis=axis,
885+
dtype=dtype,
886+
)
841887

842888

843889
def cumsum(a, axis=None, dtype=None, out=None):

dpnp/dpnp_iface_nanfunctions.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -265,8 +265,8 @@ def nancumprod(x1, **kwargs):
265265
266266
See Also
267267
--------
268-
:obj:`dpnp.cumprod` : Return the cumulative product of elements
269-
along a given axis.
268+
:obj:`dpnp.cumprod` : Cumulative product across array propagating NaNs.
269+
:obj:`dpnp.isnan` : Show which elements are NaN.
270270
271271
Examples
272272
--------

tests/test_mathematical.py

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,75 @@ def test_not_implemented_kwargs(self, kwargs):
161161
dpnp.clip(a, 1, 5, **kwargs)
162162

163163

164+
class TestCumProd:
165+
@pytest.mark.parametrize(
166+
"arr, axis",
167+
[
168+
pytest.param([1, 2, 10, 11, 6, 5, 4], -1),
169+
pytest.param([[1, 2, 3, 4], [5, 6, 7, 9], [10, 3, 4, 5]], 0),
170+
pytest.param([[1, 2, 3, 4], [5, 6, 7, 9], [10, 3, 4, 5]], -1),
171+
],
172+
)
173+
@pytest.mark.parametrize("dtype", get_all_dtypes())
174+
def test_axis(self, arr, axis, dtype):
175+
a = numpy.array(arr, dtype=dtype)
176+
ia = dpnp.array(a)
177+
178+
result = dpnp.cumprod(ia, axis=axis)
179+
expected = numpy.cumprod(a, axis=axis)
180+
assert_array_equal(expected, result)
181+
182+
@pytest.mark.parametrize("dtype", get_all_dtypes())
183+
def test_ndarray_method(self, dtype):
184+
a = numpy.arange(1, 10).astype(dtype=dtype)
185+
ia = dpnp.array(a)
186+
187+
result = ia.cumprod()
188+
expected = a.cumprod()
189+
assert_array_equal(expected, result)
190+
191+
@pytest.mark.parametrize("sh", [(10,), (2, 5)])
192+
@pytest.mark.parametrize(
193+
"xp_in, xp_out, check",
194+
[
195+
pytest.param(dpt, dpt, False),
196+
pytest.param(dpt, dpnp, True),
197+
pytest.param(dpnp, dpt, False),
198+
],
199+
)
200+
def test_usm_ndarray(self, sh, xp_in, xp_out, check):
201+
a = numpy.arange(-12, -2).reshape(sh)
202+
ia = xp_in.asarray(a)
203+
204+
result = dpnp.cumprod(ia)
205+
expected = numpy.cumprod(a)
206+
assert_array_equal(expected, result)
207+
208+
out = numpy.empty((10,))
209+
iout = xp_out.asarray(out)
210+
211+
result = dpnp.cumprod(ia, out=iout)
212+
expected = numpy.cumprod(a, out=out)
213+
assert_array_equal(expected, result)
214+
assert (result is iout) is check
215+
216+
@pytest.mark.usefixtures("suppress_complex_warning")
217+
@pytest.mark.parametrize("arr_dt", get_all_dtypes(no_none=True))
218+
@pytest.mark.parametrize("out_dt", get_all_dtypes(no_none=True))
219+
@pytest.mark.parametrize("dtype", get_all_dtypes())
220+
def test_out_dtype(self, arr_dt, out_dt, dtype):
221+
a = numpy.arange(5, 10).astype(dtype=arr_dt)
222+
out = numpy.zeros_like(a, dtype=out_dt)
223+
224+
ia = dpnp.array(a)
225+
iout = dpnp.array(out)
226+
227+
result = ia.cumprod(out=iout, dtype=dtype)
228+
expected = a.cumprod(out=out, dtype=dtype)
229+
assert_array_equal(expected, result)
230+
assert result is iout
231+
232+
164233
class TestCumSum:
165234
@pytest.mark.parametrize(
166235
"arr, axis",

tests/test_sycl_queue.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -417,7 +417,7 @@ def test_meshgrid(device_x, device_y):
417417
),
418418
pytest.param("cosh", [-5.0, -3.5, 0.0, 3.5, 5.0]),
419419
pytest.param("count_nonzero", [3, 0, 2, -1.2]),
420-
pytest.param("cumprod", [[1.0, 2.0, 3.0], [4.0, 5.0, 6.0]]),
420+
pytest.param("cumprod", [[1, 2, 3], [4, 5, 6]]),
421421
pytest.param("cumsum", [[1, 2, 3], [4, 5, 6]]),
422422
pytest.param("diff", [1.0, 2.0, 4.0, 7.0, 0.0]),
423423
pytest.param("ediff1d", [1.0, 2.0, 4.0, 7.0, 0.0]),

tests/test_usm_type.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -522,6 +522,7 @@ def test_norm(usm_type, ord, axis):
522522
),
523523
pytest.param("cosh", [-5.0, -3.5, 0.0, 3.5, 5.0]),
524524
pytest.param("count_nonzero", [0, 1, 7, 0]),
525+
pytest.param("cumprod", [[1, 2, 3], [4, 5, 6]]),
525526
pytest.param("cumsum", [[1, 2, 3], [4, 5, 6]]),
526527
pytest.param("diff", [1.0, 2.0, 4.0, 7.0, 0.0]),
527528
pytest.param("exp", [1.0, 2.0, 4.0, 7.0]),

tests/third_party/cupy/math_tests/test_sumprod.py

Lines changed: 2 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -483,13 +483,11 @@ def _cumprod(self, xp, a, *args, **kwargs):
483483
return res
484484

485485
@testing.for_all_dtypes()
486-
# TODO: remove type_check once proper cumprod is implemented
487-
@testing.numpy_cupy_allclose(type_check=(not is_win_platform()))
486+
@testing.numpy_cupy_allclose()
488487
def test_cumprod_1dim(self, xp, dtype):
489488
a = testing.shaped_arange((5,), xp, dtype)
490489
return self._cumprod(xp, a)
491490

492-
@pytest.mark.usefixtures("allow_fall_back_on_numpy")
493491
@testing.for_all_dtypes()
494492
@testing.numpy_cupy_allclose()
495493
def test_cumprod_out(self, xp, dtype):
@@ -498,7 +496,6 @@ def test_cumprod_out(self, xp, dtype):
498496
self._cumprod(xp, a, out=out)
499497
return out
500498

501-
@pytest.mark.usefixtures("allow_fall_back_on_numpy")
502499
@testing.for_all_dtypes()
503500
@testing.numpy_cupy_allclose()
504501
def test_cumprod_out_noncontiguous(self, xp, dtype):
@@ -507,24 +504,18 @@ def test_cumprod_out_noncontiguous(self, xp, dtype):
507504
self._cumprod(xp, a, out=out)
508505
return out
509506

510-
# TODO: remove skip once proper cumprod is implemented
511-
@pytest.mark.skipif(
512-
is_win_platform(), reason="numpy has another default integral dtype"
513-
)
514507
@testing.for_all_dtypes()
515508
@testing.numpy_cupy_allclose(rtol=1e-6)
516509
def test_cumprod_2dim_without_axis(self, xp, dtype):
517510
a = testing.shaped_arange((4, 5), xp, dtype)
518511
return self._cumprod(xp, a)
519512

520-
@pytest.mark.usefixtures("allow_fall_back_on_numpy")
521513
@testing.for_all_dtypes()
522514
@testing.numpy_cupy_allclose()
523515
def test_cumprod_2dim_with_axis(self, xp, dtype):
524516
a = testing.shaped_arange((4, 5), xp, dtype)
525517
return self._cumprod(xp, a, axis=1)
526518

527-
@pytest.mark.skip("ndarray.cumprod() is not implemented yet")
528519
@testing.for_all_dtypes()
529520
@testing.numpy_cupy_allclose()
530521
def test_ndarray_cumprod_2dim_with_axis(self, xp, dtype):
@@ -535,53 +526,44 @@ def test_ndarray_cumprod_2dim_with_axis(self, xp, dtype):
535526
@testing.slow
536527
def test_cumprod_huge_array(self):
537528
size = 2**32
538-
# Free huge memory for slow test
539-
cupy.get_default_memory_pool().free_all_blocks()
540-
a = cupy.ones(size, "b")
529+
a = cupy.ones(size, dtype="b")
541530
result = cupy.cumprod(a, dtype="b")
542531
del a
543532
assert (result == 1).all()
544533
# Free huge memory for slow test
545534
del result
546-
cupy.get_default_memory_pool().free_all_blocks()
547535

548-
@pytest.mark.usefixtures("allow_fall_back_on_numpy")
549536
@testing.for_all_dtypes()
550537
def test_invalid_axis_lower1(self, dtype):
551538
for xp in (numpy, cupy):
552539
a = testing.shaped_arange((4, 5), xp, dtype)
553540
with pytest.raises(numpy.AxisError):
554541
xp.cumprod(a, axis=-a.ndim - 1)
555542

556-
@pytest.mark.usefixtures("allow_fall_back_on_numpy")
557543
@testing.for_all_dtypes()
558544
def test_invalid_axis_lower2(self, dtype):
559545
for xp in (numpy, cupy):
560546
a = testing.shaped_arange((4, 5), xp, dtype)
561547
with pytest.raises(numpy.AxisError):
562548
xp.cumprod(a, axis=-a.ndim - 1)
563549

564-
@pytest.mark.usefixtures("allow_fall_back_on_numpy")
565550
@testing.for_all_dtypes()
566551
def test_invalid_axis_upper1(self, dtype):
567552
for xp in (numpy, cupy):
568553
a = testing.shaped_arange((4, 5), xp, dtype)
569554
with pytest.raises(numpy.AxisError):
570555
return xp.cumprod(a, axis=a.ndim)
571556

572-
@pytest.mark.usefixtures("allow_fall_back_on_numpy")
573557
@testing.for_all_dtypes()
574558
def test_invalid_axis_upper2(self, dtype):
575559
a = testing.shaped_arange((4, 5), cupy, dtype)
576560
with pytest.raises(numpy.AxisError):
577561
return cupy.cumprod(a, axis=a.ndim)
578562

579-
@pytest.mark.skip("no exception is raised by numpy")
580563
def test_cumprod_arraylike(self):
581564
with pytest.raises(TypeError):
582565
return cupy.cumprod((1, 2, 3))
583566

584-
@pytest.mark.skip("no exception is raised by numpy")
585567
@testing.for_float_dtypes()
586568
def test_cumprod_numpy_array(self, dtype):
587569
a_numpy = numpy.arange(1, 6, dtype=dtype)

0 commit comments

Comments
 (0)