Skip to content

Commit 209aaf8

Browse files
authored
Merge branch 'master' into compare-shape
2 parents 6b746d6 + 5660da3 commit 209aaf8

File tree

6 files changed

+221
-46
lines changed

6 files changed

+221
-46
lines changed

.github/workflows/array-api-skips.txt

-4
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,5 @@ array_api_tests/test_operators_and_elementwise_functions.py::test_asinh
3434
array_api_tests/test_signatures.py::test_func_signature[std]
3535
array_api_tests/test_signatures.py::test_func_signature[var]
3636

37-
# wrong shape is returned
38-
array_api_tests/test_linalg.py::test_vecdot
39-
array_api_tests/test_linalg.py::test_linalg_vecdot
40-
4137
# arrays have different values
4238
array_api_tests/test_linalg.py::test_linalg_tensordot

dpnp/dpnp_iface_statistics.py

+102-22
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@
3737
3838
"""
3939

40+
import math
41+
4042
import dpctl.tensor as dpt
4143
import dpctl.tensor._tensor_elementwise_impl as ti
4244
import dpctl.utils as dpu
@@ -481,24 +483,66 @@ def _get_padding(a_size, v_size, mode):
481483
r_pad = v_size - l_pad - 1
482484
elif mode == "full":
483485
l_pad, r_pad = v_size - 1, v_size - 1
484-
else:
486+
else: # pragma: no cover
485487
raise ValueError(
486488
f"Unknown mode: {mode}. Only 'valid', 'same', 'full' are supported."
487489
)
488490

489491
return l_pad, r_pad
490492

491493

492-
def _run_native_sliding_dot_product1d(a, v, l_pad, r_pad):
494+
def _choose_conv_method(a, v, rdtype):
495+
assert a.size >= v.size
496+
if rdtype == dpnp.bool:
497+
# to avoid accuracy issues
498+
return "direct"
499+
500+
if v.size < 10**4 or a.size < 10**4:
501+
# direct method is faster for small arrays
502+
return "direct"
503+
504+
if dpnp.issubdtype(rdtype, dpnp.integer):
505+
max_a = int(dpnp.max(dpnp.abs(a)))
506+
sum_v = int(dpnp.sum(dpnp.abs(v)))
507+
max_value = int(max_a * sum_v)
508+
509+
default_float = dpnp.default_float_type(a.sycl_device)
510+
if max_value > 2 ** numpy.finfo(default_float).nmant - 1:
511+
# can't represent the result in the default float type
512+
return "direct" # pragma: no covers
513+
514+
if dpnp.issubdtype(rdtype, dpnp.number):
515+
return "fft"
516+
517+
raise ValueError(f"Unsupported dtype: {rdtype}") # pragma: no cover
518+
519+
520+
def _run_native_sliding_dot_product1d(a, v, l_pad, r_pad, rdtype):
493521
queue = a.sycl_queue
522+
device = a.sycl_device
523+
524+
supported_types = statistics_ext.sliding_dot_product1d_dtypes()
525+
supported_dtype = to_supported_dtypes(rdtype, supported_types, device)
526+
527+
if supported_dtype is None: # pragma: no cover
528+
raise ValueError(
529+
f"function does not support input types "
530+
f"({a.dtype.name}, {v.dtype.name}), "
531+
"and the inputs could not be coerced to any "
532+
f"supported types. List of supported types: "
533+
f"{[st.name for st in supported_types]}"
534+
)
535+
536+
a_casted = dpnp.asarray(a, dtype=supported_dtype, order="C")
537+
v_casted = dpnp.asarray(v, dtype=supported_dtype, order="C")
494538

495-
usm_type = dpu.get_coerced_usm_type([a.usm_type, v.usm_type])
496-
out_size = l_pad + r_pad + a.size - v.size + 1
539+
usm_type = dpu.get_coerced_usm_type([a_casted.usm_type, v_casted.usm_type])
540+
out_size = l_pad + r_pad + a_casted.size - v_casted.size + 1
497541
# out type is the same as input type
498-
out = dpnp.empty_like(a, shape=out_size, usm_type=usm_type)
542+
out = dpnp.empty_like(a_casted, shape=out_size, usm_type=usm_type)
499543

500-
a_usm = dpnp.get_usm_ndarray(a)
501-
v_usm = dpnp.get_usm_ndarray(v)
544+
a_usm = dpnp.get_usm_ndarray(a_casted)
545+
v_usm = dpnp.get_usm_ndarray(v_casted)
502546
out_usm = dpnp.get_usm_ndarray(out)
503547

504548
_manager = dpu.SequentialOrderManager[queue]
@@ -516,7 +560,30 @@ def _run_native_sliding_dot_product1d(a, v, l_pad, r_pad):
516560
return out
517561

518562

519-
def correlate(a, v, mode="valid"):
563+
def _convolve_fft(a, v, l_pad, r_pad, rtype):
564+
assert a.size >= v.size
565+
assert l_pad < v.size
566+
567+
# +1 is needed to avoid circular convolution
568+
padded_size = a.size + r_pad + 1
569+
fft_size = 2 ** int(math.ceil(math.log2(padded_size)))
570+
571+
af = dpnp.fft.fft(a, fft_size) # pylint: disable=no-member
572+
vf = dpnp.fft.fft(v, fft_size) # pylint: disable=no-member
573+
574+
r = dpnp.fft.ifft(af * vf) # pylint: disable=no-member
575+
if dpnp.issubdtype(rtype, dpnp.floating):
576+
r = r.real
577+
elif dpnp.issubdtype(rtype, dpnp.integer) or rtype == dpnp.bool:
578+
r = r.real.round()
579+
580+
start = v.size - 1 - l_pad
581+
end = padded_size - 1
582+
583+
return r[start:end]
584+
585+
586+
def correlate(a, v, mode="valid", method="auto"):
520587
r"""
521588
Cross-correlation of two 1-dimensional sequences.
522589
@@ -541,6 +608,20 @@ def correlate(a, v, mode="valid"):
541608
is ``"valid"``, unlike :obj:`dpnp.convolve`, which uses ``"full"``.
542609
543610
Default: ``"valid"``.
611+
method : {"auto", "direct", "fft"}, optional
612+
Specifies which method to use to calculate the correlation:
613+
614+
- `"direct"` : The correlation is determined directly from sums.
615+
- `"fft"` : The Fourier Transform is used to perform the calculations.
616+
This method is faster for long sequences but can have accuracy issues.
617+
- `"auto"` : Automatically chooses direct or Fourier method based on
618+
an estimate of which is faster.
619+
620+
Note: Use of the FFT convolution on input containing NAN or INF
621+
will lead to the entire output being NAN or INF.
622+
Use method='direct' when your input contains NAN or INF values.
623+
624+
Default: ``"auto"``.
544625
545626
Returns
546627
-------
@@ -608,20 +689,14 @@ def correlate(a, v, mode="valid"):
608689
f"Received shapes: a.shape={a.shape}, v.shape={v.shape}"
609690
)
610691

611-
supported_types = statistics_ext.sliding_dot_product1d_dtypes()
692+
supported_methods = ["auto", "direct", "fft"]
693+
if method not in supported_methods:
694+
raise ValueError(
695+
f"Unknown method: {method}. Supported methods: {supported_methods}"
696+
)
612697

613698
device = a.sycl_device
614699
rdtype = result_type_for_device([a.dtype, v.dtype], device)
615-
supported_dtype = to_supported_dtypes(rdtype, supported_types, device)
616-
617-
if supported_dtype is None: # pragma: no cover
618-
raise ValueError(
619-
f"function does not support input types "
620-
f"({a.dtype.name}, {v.dtype.name}), "
621-
"and the inputs could not be coerced to any "
622-
f"supported types. List of supported types: "
623-
f"{[st.name for st in supported_types]}"
624-
)
625700

626701
if dpnp.issubdtype(v.dtype, dpnp.complexfloating):
627702
v = dpnp.conj(v)
@@ -633,10 +708,15 @@ def correlate(a, v, mode="valid"):
633708

634709
l_pad, r_pad = _get_padding(a.size, v.size, mode)
635710

636-
a_casted = dpnp.asarray(a, dtype=supported_dtype, order="C")
637-
v_casted = dpnp.asarray(v, dtype=supported_dtype, order="C")
711+
if method == "auto":
712+
method = _choose_conv_method(a, v, rdtype)
638713

639-
r = _run_native_sliding_dot_product1d(a_casted, v_casted, l_pad, r_pad)
714+
if method == "direct":
715+
r = _run_native_sliding_dot_product1d(a, v, l_pad, r_pad, rdtype)
716+
elif method == "fft":
717+
r = _convolve_fft(a, v[::-1], l_pad, r_pad, rdtype)
718+
else: # pragma: no cover
719+
raise ValueError(f"Unknown method: {method}")
640720

641721
if revert:
642722
r = r[::-1]

dpnp/dpnp_utils/dpnp_utils_linearalgebra.py

+7-9
Original file line numberDiff line numberDiff line change
@@ -198,13 +198,14 @@ def _define_dim_flags(x, axis):
198198
"""
199199
Define useful flags for the calculations in dpnp_matmul and dpnp_vecdot.
200200
x_is_1D: `x` is 1D array or inherently 1D (all dimensions are equal to one
201-
except for one of them), for instance, if x.shape = (1, 1, 1, 2),
202-
then x_is_1D = True
201+
except for dimension at `axis`), for instance, if x.shape = (1, 1, 1, 2),
202+
and axis=-1, then x_is_1D = True.
203203
x_is_2D: `x` is 2D array or inherently 2D (all dimensions are equal to one
204204
except for the last two of them), for instance, if x.shape = (1, 1, 3, 2),
205-
then x_is_2D = True
205+
then x_is_2D = True.
206206
x_base_is_1D: `x` is 1D considering only its last two dimensions, for instance,
207-
if x.shape = (3, 4, 1, 2), then x_base_is_1D = True
207+
if x.shape = (3, 4, 1, 2), then x_base_is_1D = True.
208+
208209
"""
209210

210211
x_shape = x.shape
@@ -326,14 +327,11 @@ def _get_result_shape_vecdot(x1, x2, x1_ndim, x2_ndim):
326327
if x1_shape[-1] != x2_shape[-1]:
327328
_shape_error(x1_shape[-1], x2_shape[-1], "vecdot", err_msg=0)
328329

329-
_, x1_is_1D, _ = _define_dim_flags(x1, axis=-1)
330-
_, x2_is_1D, _ = _define_dim_flags(x2, axis=-1)
331-
332330
if x1_ndim == 1 and x2_ndim == 1:
333331
result_shape = ()
334-
elif x1_is_1D:
332+
elif x1_ndim == 1:
335333
result_shape = x2_shape[:-1]
336-
elif x2_is_1D:
334+
elif x2_ndim == 1:
337335
result_shape = x1_shape[:-1]
338336
else: # at least 2D
339337
if x1_ndim != x2_ndim:

dpnp/tests/helper.py

+20-3
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ def assert_dtype_allclose(
1313
check_type=True,
1414
check_only_type_kind=False,
1515
factor=8,
16+
relative_factor=None,
1617
):
1718
"""
1819
Assert DPNP and NumPy array based on maximum dtype resolution of input arrays
@@ -189,6 +190,7 @@ def generate_random_numpy_array(
189190
seed_value=None,
190191
low=-10,
191192
high=10,
193+
probability=0.5,
192194
):
193195
"""
194196
Generate a random numpy array with the specified shape and dtype.
@@ -203,23 +205,32 @@ def generate_random_numpy_array(
203205
dtype : str or dtype, optional
204206
Desired data-type for the output array.
205207
If not specified, data type will be determined by numpy.
208+
206209
Default : ``None``
207210
order : {"C", "F"}, optional
208211
Specify the memory layout of the output array.
212+
209213
Default: ``"C"``.
210214
hermitian : bool, optional
211215
If True, generates a Hermitian (symmetric if `dtype` is real) matrix.
216+
212217
Default : ``False``
213218
seed_value : int, optional
214219
The seed value to initialize the random number generator.
220+
215221
Default : ``None``
216222
low : {int, float}, optional
217223
Lower boundary of the generated samples from a uniform distribution.
224+
218225
Default : ``-10``.
219226
high : {int, float}, optional
220227
Upper boundary of the generated samples from a uniform distribution.
228+
221229
Default : ``10``.
230+
probability : float, optional
231+
If dtype is bool, the probability of True. Ignored for other dtypes.
222232
233+
Default : ``0.5``.
223234
Returns
224235
-------
225236
out : numpy.ndarray
@@ -238,9 +249,15 @@ def generate_random_numpy_array(
238249

239250
# dtype=int is needed for 0d arrays
240251
size = numpy.prod(shape, dtype=int)
241-
a = numpy.random.uniform(low, high, size).astype(dtype)
242-
if numpy.issubdtype(a.dtype, numpy.complexfloating):
243-
a += 1j * numpy.random.uniform(low, high, size)
252+
if dtype == dpnp.bool:
253+
a = numpy.random.choice(
254+
[False, True], size, p=[1 - probability, probability]
255+
)
256+
else:
257+
a = numpy.random.uniform(low, high, size).astype(dtype)
258+
259+
if numpy.issubdtype(a.dtype, numpy.complexfloating):
260+
a += 1j * numpy.random.uniform(low, high, size)
244261

245262
a = a.reshape(shape)
246263
if hermitian and a.size > 0:

dpnp/tests/test_product.py

+2
Original file line numberDiff line numberDiff line change
@@ -1000,6 +1000,8 @@ def setup_method(self):
10001000
((1, 4, 5), (3, 1, 5)),
10011001
((1, 1, 4, 5), (3, 1, 5)),
10021002
((1, 4, 5), (1, 3, 1, 5)),
1003+
((2, 1), (1, 1, 1)),
1004+
((1, 1, 3), (3,)),
10031005
],
10041006
)
10051007
def test_basic(self, dtype, shape1, shape2):

0 commit comments

Comments
 (0)