Skip to content

Commit f13e9a4

Browse files
AlexanderKalistratovvtavanaantonwolfy
authored
Implementation of convolve (#2205)
Add implementation of `dpnp.convolve`. Implementation is mostly based on already existing functionality developed for `dpnp.correlate` Similar to [scipy.signal.convolve](https://docs.scipy.org/doc/scipy/reference/generated/scipy.signal.convolve.html) `method` keyword is introduced, but unlike `scipy.signal.convolve` `dpnp.convolve` works only with 1-d arrays. - [x] Have you provided a meaningful PR description? - [x] Have you added a test, reproducer or referred to issue with a reproducer? - [x] Have you tested your changes locally for CPU and GPU devices? - [x] Have you made sure that new changes do not introduce compiler warnings? - [x] Have you checked performance impact of proposed changes? - [x] If this PR is a work in progress, are you filing the PR as a draft? --------- Co-authored-by: Vahid Tavanashad <[email protected]> Co-authored-by: Anton <[email protected]>
1 parent 1b3da83 commit f13e9a4

File tree

10 files changed

+375
-86
lines changed

10 files changed

+375
-86
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1212
* Added implementation of `dpnp.hanning` [#2358](https://github.com/IntelPython/dpnp/pull/2358)
1313
* Added implementation of `dpnp.blackman` [#2363](https://github.com/IntelPython/dpnp/pull/2363)
1414
* Added implementation of `dpnp.bartlett` [#2366](https://github.com/IntelPython/dpnp/pull/2366)
15+
* Added implementation of `dpnp.convolve` [#2205](https://github.com/IntelPython/dpnp/pull/2205)
1516
* Added implementation of `dpnp.kaiser` [#2387](https://github.com/IntelPython/dpnp/pull/2387)
1617

1718
### Changed

dpnp/dpnp_iface_manipulation.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -958,6 +958,9 @@ def atleast_1d(*arys):
958958
dpnp.check_supported_arrays_type(*arys)
959959
for ary in arys:
960960
if ary.ndim == 0:
961+
# 0-d arrays cannot be empty
962+
# 0-d arrays always have a size of 1, so
963+
# reshape(1) is guaranteed to succeed
961964
result = ary.reshape(1)
962965
else:
963966
result = ary

dpnp/dpnp_iface_mathematical.py

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,6 @@
9191
"clip",
9292
"conj",
9393
"conjugate",
94-
"convolve",
9594
"copysign",
9695
"cross",
9796
"cumprod",
@@ -792,24 +791,6 @@ def clip(a, /, min=None, max=None, *, out=None, order="K", **kwargs):
792791

793792
conj = conjugate
794793

795-
796-
def convolve(a, v, mode="full"):
797-
"""
798-
Returns the discrete, linear convolution of two one-dimensional sequences.
799-
800-
For full documentation refer to :obj:`numpy.convolve`.
801-
802-
Examples
803-
--------
804-
>>> ca = dpnp.convolve([1, 2, 3], [0, 1, 0.5])
805-
>>> print(ca)
806-
[0. , 1. , 2.5, 4. , 1.5]
807-
808-
"""
809-
810-
return call_origin(numpy.convolve, a=a, v=v, mode=mode)
811-
812-
813794
_COPYSIGN_DOCSTRING = """
814795
Composes a floating-point value with the magnitude of `x1_i` and the sign of
815796
`x2_i` for each element of input arrays `x1` and `x2`.

dpnp/dpnp_iface_statistics.py

Lines changed: 142 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@
6262
"amax",
6363
"amin",
6464
"average",
65+
"convolve",
6566
"corrcoef",
6667
"correlate",
6768
"cov",
@@ -357,6 +358,146 @@ def average(a, axis=None, weights=None, returned=False, *, keepdims=False):
357358
return avg
358359

359360

361+
def _convolve_impl(a, v, mode, method, rdtype):
362+
l_pad, r_pad = _get_padding(a.size, v.size, mode)
363+
364+
if method == "auto":
365+
method = _choose_conv_method(a, v, rdtype)
366+
367+
if method == "direct":
368+
r = _run_native_sliding_dot_product1d(a, v[::-1], l_pad, r_pad, rdtype)
369+
elif method == "fft":
370+
r = _convolve_fft(a, v, l_pad, r_pad, rdtype)
371+
else:
372+
raise ValueError(
373+
f"Unknown method: {method}. Supported methods: auto, direct, fft"
374+
)
375+
376+
return r
377+
378+
379+
def convolve(a, v, mode="full", method="auto"):
380+
r"""
381+
Returns the discrete, linear convolution of two one-dimensional sequences.
382+
The convolution operator is often seen in signal processing, where it
383+
models the effect of a linear time-invariant system on a signal [1]_. In
384+
probability theory, the sum of two independent random variables is
385+
distributed according to the convolution of their individual
386+
distributions.
387+
388+
If `v` is longer than `a`, the arrays are swapped before computation.
389+
390+
For full documentation refer to :obj:`numpy.convolve`.
391+
392+
Parameters
393+
----------
394+
a : {dpnp.ndarray, usm_ndarray}
395+
First input array.
396+
v : {dpnp.ndarray, usm_ndarray}
397+
Second input array.
398+
mode : {'full', 'valid', 'same'}, optional
399+
- 'full': This returns the convolution
400+
at each point of overlap, with an output shape of (N+M-1,). At
401+
the end-points of the convolution, the signals do not overlap
402+
completely, and boundary effects may be seen.
403+
- 'same': Mode 'same' returns output of length ``max(M, N)``. Boundary
404+
effects are still visible.
405+
- 'valid': Mode 'valid' returns output of length
406+
``max(M, N) - min(M, N) + 1``. The convolution product is only given
407+
for points where the signals overlap completely. Values outside
408+
the signal boundary have no effect.
409+
410+
Default: ``'full'``.
411+
method : {'auto', 'direct', 'fft'}, optional
412+
- 'direct': The convolution is determined directly from sums.
413+
- 'fft': The Fourier Transform is used to perform the calculations.
414+
This method is faster for long sequences but can have accuracy issues.
415+
- 'auto': Automatically chooses direct or Fourier method based on
416+
an estimate of which is faster.
417+
418+
Note: Use of the FFT convolution on input containing NAN or INF
419+
will lead to the entire output being NAN or INF.
420+
Use ``method='direct'`` when your input contains NAN or INF values.
421+
422+
Default: ``'auto'``.
423+
424+
Returns
425+
-------
426+
out : dpnp.ndarray
427+
Discrete, linear convolution of `a` and `v`.
428+
429+
See Also
430+
--------
431+
:obj:`dpnp.correlate` : Cross-correlation of two 1-dimensional sequences.
432+
433+
Notes
434+
-----
435+
The discrete convolution operation is defined as
436+
437+
.. math:: (a * v)_n = \sum_{m = -\infty}^{\infty} a_m v_{n - m}
438+
439+
It can be shown that a convolution :math:`x(t) * y(t)` in time/space
440+
is equivalent to the multiplication :math:`X(f) Y(f)` in the Fourier
441+
domain, after appropriate padding (padding is necessary to prevent
442+
circular convolution). Since multiplication is more efficient (faster)
443+
than convolution, the function implements two approaches - direct and fft
444+
which are regulated by the keyword `method`.
445+
446+
References
447+
----------
448+
.. [1] Uncyclopedia, "Convolution",
449+
https://en.wikipedia.org/wiki/Convolution
450+
451+
Examples
452+
--------
453+
Note how the convolution operator flips the second array
454+
before "sliding" the two across one another:
455+
456+
>>> import dpnp as np
457+
>>> a = np.array([1, 2, 3], dtype=np.float32)
458+
>>> v = np.array([0, 1, 0.5], dtype=np.float32)
459+
>>> np.convolve(a, v)
460+
array([0. , 1. , 2.5, 4. , 1.5], dtype=float32)
461+
462+
Only return the middle values of the convolution.
463+
Contains boundary effects, where zeros are taken
464+
into account:
465+
466+
>>> np.convolve(a, v, 'same')
467+
array([1. , 2.5, 4. ], dtype=float32)
468+
469+
The two arrays are of the same length, so there
470+
is only one position where they completely overlap:
471+
472+
>>> np.convolve(a, v, 'valid')
473+
array([2.5], dtype=float32)
474+
475+
"""
476+
477+
a, v = dpnp.atleast_1d(a, v)
478+
479+
if a.size == 0 or v.size == 0:
480+
raise ValueError(
481+
f"Array arguments cannot be empty. "
482+
f"Received sizes: a.size={a.size}, v.size={v.size}"
483+
)
484+
if a.ndim > 1 or v.ndim > 1:
485+
raise ValueError(
486+
f"Only 1-dimensional arrays are supported. "
487+
f"Received shapes: a.shape={a.shape}, v.shape={v.shape}"
488+
)
489+
490+
device = a.sycl_device
491+
rdtype = result_type_for_device([a.dtype, v.dtype], device)
492+
493+
if v.size > a.size:
494+
a, v = v, a
495+
496+
r = _convolve_impl(a, v, mode, method, rdtype)
497+
498+
return dpnp.asarray(r, dtype=rdtype, order="C")
499+
500+
360501
def corrcoef(x, y=None, rowvar=True, *, dtype=None):
361502
"""
362503
Return Pearson product-moment correlation coefficients.
@@ -714,17 +855,7 @@ def correlate(a, v, mode="valid", method="auto"):
714855
revert = True
715856
a, v = v, a
716857

717-
l_pad, r_pad = _get_padding(a.size, v.size, mode)
718-
719-
if method == "auto":
720-
method = _choose_conv_method(a, v, rdtype)
721-
722-
if method == "direct":
723-
r = _run_native_sliding_dot_product1d(a, v, l_pad, r_pad, rdtype)
724-
elif method == "fft":
725-
r = _convolve_fft(a, v[::-1], l_pad, r_pad, rdtype)
726-
else: # pragma: no cover
727-
raise ValueError(f"Unknown method: {method}")
858+
r = _convolve_impl(a, v[::-1], mode, method, rdtype)
728859

729860
if revert:
730861
r = r[::-1]

dpnp/tests/helper.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,19 @@ def generate_random_numpy_array(
185185
return a
186186

187187

188+
def factor_to_tol(dtype, factor):
189+
"""
190+
Calculate the tolerance for comparing floating point and complex arrays.
191+
The tolerance is based on the maximum resolution of the input dtype multiplied by the factor.
192+
"""
193+
194+
tol = 0
195+
if numpy.issubdtype(dtype, numpy.inexact):
196+
tol = numpy.finfo(dtype).resolution
197+
198+
return factor * tol
199+
200+
188201
def get_abs_array(data, dtype=None):
189202
if numpy.issubdtype(dtype, numpy.unsignedinteger):
190203
data = numpy.abs(data)

dpnp/tests/test_mathematical.py

Lines changed: 0 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -104,35 +104,6 @@ def test_conj_out(self, dtype):
104104
assert_dtype_allclose(result, expected)
105105

106106

107-
@pytest.mark.usefixtures("allow_fall_back_on_numpy")
108-
class TestConvolve:
109-
def test_object(self):
110-
d = [1.0] * 100
111-
k = [1.0] * 3
112-
assert_array_equal(dpnp.convolve(d, k)[2:-2], dpnp.full(98, 3.0))
113-
114-
def test_no_overwrite(self):
115-
d = dpnp.ones(100)
116-
k = dpnp.ones(3)
117-
dpnp.convolve(d, k)
118-
assert_array_equal(d, dpnp.ones(100))
119-
assert_array_equal(k, dpnp.ones(3))
120-
121-
def test_mode(self):
122-
d = dpnp.ones(100)
123-
k = dpnp.ones(3)
124-
default_mode = dpnp.convolve(d, k)
125-
full_mode = dpnp.convolve(d, k, mode="full")
126-
assert_array_equal(full_mode, default_mode)
127-
# integer mode
128-
with assert_raises(ValueError):
129-
dpnp.convolve(d, k, mode=-1)
130-
assert_array_equal(dpnp.convolve(d, k, mode=2), full_mode)
131-
# illegal arguments
132-
with assert_raises(TypeError):
133-
dpnp.convolve(d, k, mode=None)
134-
135-
136107
class TestClip:
137108
@pytest.mark.parametrize(
138109
"dtype", get_all_dtypes(no_bool=True, no_none=True, no_complex=True)

0 commit comments

Comments
 (0)