Skip to content

Commit 70e50a4

Browse files
authored
Merge branch 'master' into bump-gitleaks-version
2 parents d49cf35 + c4dab5d commit 70e50a4

File tree

8 files changed

+82
-82
lines changed

8 files changed

+82
-82
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ This release achieves 100% compliance with Python Array API specification (revis
3434
* Clarified details about conda install instructions in `Quick start quide` and `README` [#2446](https://github.com/IntelPython/dpnp/pull/2446)
3535
* Bumped oneMKL version up to `0.7` [#2448](https://github.com/IntelPython/dpnp/pull/2448)
3636
* The parameter `axis` in `dpnp.take_along_axis` function has now a default value of `-1` [#2442](https://github.com/IntelPython/dpnp/pull/2442)
37+
* Updates the list of required python versions documented in `Quick Start Guide` [#2449](https://github.com/IntelPython/dpnp/pull/2449)
38+
* Updated FFT module to ensure an input array is Hermitian before calling complex-to-real FFT [#2444](https://github.com/IntelPython/dpnp/pull/2444)
3739

3840
### Fixed
3941

doc/quick_start_guide.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ Follow device driver installation instructions to complete the step.
2424
Python Interpreter
2525
==================
2626

27-
You will need Python 3.8, 3.9, or 3.10 installed on your system. If you
27+
You will need Python 3.9, 3.10, 3.11 or 3.12 installed on your system. If you
2828
do not have one yet the easiest way to do that is to install
2929
`Intel Distribution for Python*`_. It installs all essential Python numerical
3030
and machine learning packages optimized for the Intel hardware, including

dpnp/fft/dpnp_iface_fft.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -640,11 +640,11 @@ def ifft(a, n=None, axis=-1, norm=None, out=None):
640640
:obj:`dpnp.fft.fft`, i.e.,
641641
642642
* ``a[0]`` should contain the zero frequency term,
643-
* ``a[1:n//2]`` should contain the positive-frequency terms,
643+
* ``a[1:(n+1)//2]`` should contain the positive-frequency terms,
644644
* ``a[n//2 + 1:]`` should contain the negative-frequency terms, in
645645
increasing order starting from the most negative frequency.
646646
647-
For an even number of input points, ``A[n//2]`` represents the sum of
647+
For an even number of input points, ``a[n//2]`` represents the sum of
648648
the values at the positive and negative Nyquist frequencies, as the two
649649
are aliased together.
650650
@@ -1062,7 +1062,7 @@ def irfft(a, n=None, axis=-1, norm=None, out=None):
10621062
10631063
This function computes the inverse of the one-dimensional *n*-point
10641064
discrete Fourier Transform of real input computed by :obj:`dpnp.fft.rfft`.
1065-
In other words, ``irfft(rfft(a), len(a)) == a`` to within numerical
1065+
In other words, ``irfft(rfft(a), n=len(a)) == a`` to within numerical
10661066
accuracy. (See Notes below for why ``len(a)`` is necessary here.)
10671067
10681068
The input is expected to be in the form returned by :obj:`dpnp.fft.rfft`,
@@ -1265,9 +1265,9 @@ def irfftn(a, s=None, axes=None, norm=None, out=None):
12651265
This function computes the inverse of the *N*-dimensional discrete Fourier
12661266
Transform for real input over any number of axes in an *M*-dimensional
12671267
array by means of the Fast Fourier Transform (FFT). In other words,
1268-
``irfftn(rfftn(a), a.shape) == a`` to within numerical accuracy. (The
1269-
``a.shape`` is necessary like ``len(a)`` is for :obj:`dpnp.fft.irfft`,
1270-
and for the same reason.)
1268+
``irfftn(rfftn(a), s=a.shape, axes=range(a.ndim)) == a`` to within
1269+
numerical accuracy. (The ``a.shape`` is necessary like ``len(a)`` is for
1270+
:obj:`dpnp.fft.irfft`, and for the same reason.)
12711271
12721272
The input should be ordered in the same way as is returned by
12731273
:obj:`dpnp.fft.rfftn`, i.e. as for :obj:`dpnp.fft.irfft` for the final

dpnp/fft/dpnp_utils_fft.py

Lines changed: 52 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -285,20 +285,7 @@ def _copy_array(x, complex_input):
285285
dtype = map_dtype_to_device(dpnp.float64, x.sycl_device)
286286

287287
if copy_flag:
288-
x_copy = dpnp.empty_like(x, dtype=dtype, order="C")
289-
290-
exec_q = x.sycl_queue
291-
_manager = dpu.SequentialOrderManager[exec_q]
292-
dep_evs = _manager.submitted_events
293-
294-
ht_copy_ev, copy_ev = ti._copy_usm_ndarray_into_usm_ndarray(
295-
src=dpnp.get_usm_ndarray(x),
296-
dst=x_copy.get_array(),
297-
sycl_queue=exec_q,
298-
depends=dep_evs,
299-
)
300-
_manager.add_event_pair(ht_copy_ev, copy_ev)
301-
x = x_copy
288+
x = x.astype(dtype, order="C", copy=True)
302289

303290
# if copying is done, FFT can be in-place (copy_flag = in_place flag)
304291
return x, copy_flag
@@ -433,6 +420,40 @@ def _fft(a, norm, out, forward, in_place, c2c, axes, batch_fft=True):
433420
return result
434421

435422

423+
def _make_array_hermitian(a, axis, copy_needed):
424+
"""
425+
For complex-to-real FFT, the input array should be Hermitian. If it is not,
426+
the behavior is undefined. This function makes necessary changes to make
427+
sure the given array is Hermitian.
428+
429+
It is assumed that this function is called after `_cook_nd_args` and so
430+
`n` is always ``None``. It is also assumed that it is called after
431+
`_truncate_or_pad`, so the array has enough length.
432+
"""
433+
434+
a = dpnp.moveaxis(a, axis, 0)
435+
n = a.shape[0]
436+
437+
# TODO: if the input array is already Hermitian, the following steps are
438+
# not needed, however, validating the input array is hermitian results in
439+
# synchronization of the SYCL queue, find an alternative.
440+
if copy_needed:
441+
a = a.astype(a.dtype, order="C", copy=True)
442+
443+
a[0].imag = 0
444+
assert n is not None
445+
if n % 2 == 0:
446+
# Nyquist mode (n//2+1 mode) is n//2-th element
447+
f_ny = n // 2
448+
assert a.shape[0] > f_ny
449+
a[f_ny].imag = 0
450+
else:
451+
# No Nyquist mode
452+
pass
453+
454+
return dpnp.moveaxis(a, 0, axis)
455+
456+
436457
def _scale_result(res, a_shape, norm, forward, index):
437458
"""Scale the result of the FFT according to `norm`."""
438459
if res.dtype in [dpnp.float32, dpnp.complex64]:
@@ -559,6 +580,7 @@ def dpnp_fft(a, forward, real, n=None, axis=-1, norm=None, out=None):
559580
"""Calculates 1-D FFT of the input array along axis"""
560581

561582
_check_norm(norm)
583+
a_orig = a
562584
a_ndim = a.ndim
563585
if a_ndim == 0:
564586
raise ValueError("Input array must be at least 1D")
@@ -591,6 +613,12 @@ def dpnp_fft(a, forward, real, n=None, axis=-1, norm=None, out=None):
591613
if a.size == 0:
592614
return dpnp.get_result_array(a, out=out, casting="same_kind")
593615

616+
if c2r:
617+
# input array should be Hermitian for c2r FFT
618+
a = _make_array_hermitian(
619+
a, axis, dpnp.are_same_logical_tensors(a, a_orig)
620+
)
621+
594622
return _fft(
595623
a,
596624
norm=norm,
@@ -607,6 +635,7 @@ def dpnp_fft(a, forward, real, n=None, axis=-1, norm=None, out=None):
607635
def dpnp_fftn(a, forward, real, s=None, axes=None, norm=None, out=None):
608636
"""Calculates N-D FFT of the input array along axes"""
609637

638+
a_orig = a
610639
if isinstance(axes, Sequence) and len(axes) == 0:
611640
if real:
612641
raise IndexError("Empty axes.")
@@ -636,8 +665,12 @@ def dpnp_fftn(a, forward, real, s=None, axes=None, norm=None, out=None):
636665
len_axes = len(axes)
637666
if len_axes == 1:
638667
a = _truncate_or_pad(a, (s[-1],), (axes[-1],))
668+
if c2r:
669+
a = _make_array_hermitian(
670+
a, axes[-1], dpnp.are_same_logical_tensors(a, a_orig)
671+
)
639672
return _fft(
640-
a, norm, out, forward, in_place and c2c, c2c, axes[0], a.ndim != 1
673+
a, norm, out, forward, in_place and c2c, c2c, axes[-1], a.ndim != 1
641674
)
642675

643676
if r2c:
@@ -686,6 +719,10 @@ def dpnp_fftn(a, forward, real, s=None, axes=None, norm=None, out=None):
686719
batch_fft=a.ndim != len_axes - 1,
687720
)
688721
a = _truncate_or_pad(a, (s[-1],), (axes[-1],))
722+
if c2r:
723+
a = _make_array_hermitian(
724+
a, axes[-1], dpnp.are_same_logical_tensors(a, a_orig)
725+
)
689726
return _fft(
690727
a, norm, out, forward, in_place and c2c, c2c, axes[-1], a.ndim != 1
691728
)

dpnp/tests/test_fft.py

Lines changed: 7 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -20,32 +20,6 @@
2020
from .third_party.cupy import testing
2121

2222

23-
def _make_array_Hermitian(a, n):
24-
"""
25-
For `dpnp.fft.irfft` and `dpnp.fft.hfft`, the input array should be Hermitian.
26-
If it is not, the behavior is undefined. This function makes necessary changes
27-
to make sure the given array is Hermitian.
28-
"""
29-
30-
a[0] = a[0].real
31-
if n is None:
32-
# last element is Nyquist mode
33-
a[-1] = a[-1].real
34-
elif n % 2 == 0:
35-
# Nyquist mode (n//2+1 mode) is n//2-th element
36-
f_ny = n // 2
37-
if a.shape[0] > f_ny:
38-
a[f_ny] = a[f_ny].real
39-
a[f_ny + 1 :] = 0 # no data needed after Nyquist mode
40-
else:
41-
# No Nyquist mode
42-
f_ny = n // 2
43-
if a.shape[0] > f_ny:
44-
a[f_ny + 1 :] = 0 # no data needed for the second half
45-
46-
return a
47-
48-
4923
class TestFft:
5024
@pytest.mark.parametrize("dtype", get_all_dtypes(no_none=True))
5125
@pytest.mark.parametrize("n", [None, 5, 20])
@@ -577,13 +551,14 @@ def test_basic(self, func, dtype, axes):
577551

578552

579553
class TestHfft:
580-
@pytest.mark.parametrize("dtype", get_all_dtypes(no_none=True))
554+
# TODO: include boolean dtype when mkl_fft-gh-180 is merged
555+
@pytest.mark.parametrize(
556+
"dtype", get_all_dtypes(no_none=True, no_bool=True)
557+
)
581558
@pytest.mark.parametrize("n", [None, 5, 18])
582559
@pytest.mark.parametrize("norm", [None, "backward", "forward", "ortho"])
583560
def test_basic(self, dtype, n, norm):
584561
a = generate_random_numpy_array(11, dtype)
585-
if numpy.issubdtype(dtype, numpy.complexfloating):
586-
a = _make_array_Hermitian(a, n)
587562
ia = dpnp.array(a)
588563

589564
result = dpnp.fft.hfft(ia, n=n, norm=norm)
@@ -626,8 +601,6 @@ class TestIrfft:
626601
@pytest.mark.parametrize("norm", [None, "backward", "forward", "ortho"])
627602
def test_basic(self, dtype, n, norm):
628603
a = generate_random_numpy_array(11)
629-
if numpy.issubdtype(dtype, numpy.complexfloating):
630-
a = _make_array_Hermitian(a, n)
631604
ia = dpnp.array(a)
632605

633606
result = dpnp.fft.irfft(ia, n=n, norm=norm)
@@ -644,10 +617,6 @@ def test_basic(self, dtype, n, norm):
644617
def test_2d_array(self, dtype, n, axis, norm, order):
645618
a = generate_random_numpy_array((3, 4), dtype=dtype, order=order)
646619
ia = dpnp.array(a)
647-
# For dpnp, each 1-D array of input should be Hermitian
648-
ia = dpnp.moveaxis(ia, axis, 0)
649-
ia = _make_array_Hermitian(ia, n)
650-
ia = dpnp.moveaxis(ia, 0, axis)
651620

652621
result = dpnp.fft.irfft(ia, n=n, axis=axis, norm=norm)
653622
expected = numpy.fft.irfft(a, n=n, axis=axis, norm=norm)
@@ -661,10 +630,6 @@ def test_2d_array(self, dtype, n, axis, norm, order):
661630
def test_3d_array(self, dtype, n, axis, norm, order):
662631
a = generate_random_numpy_array((4, 5, 6), dtype, order)
663632
ia = dpnp.array(a)
664-
# For dpnp, each 1-D array of input should be Hermitian
665-
ia = dpnp.moveaxis(ia, axis, 0)
666-
ia = _make_array_Hermitian(ia, n)
667-
ia = dpnp.moveaxis(ia, 0, axis)
668633

669634
result = dpnp.fft.irfft(ia, n=n, axis=axis, norm=norm)
670635
expected = numpy.fft.irfft(a, n=n, axis=axis, norm=norm)
@@ -674,7 +639,6 @@ def test_3d_array(self, dtype, n, axis, norm, order):
674639
def test_usm_ndarray(self, n):
675640
a = generate_random_numpy_array(11, dtype=numpy.complex64)
676641
a_usm = dpt.asarray(a)
677-
a_usm = _make_array_Hermitian(a_usm, n)
678642

679643
expected = numpy.fft.irfft(a, n=n)
680644
out = dpt.empty(expected.shape, dtype=a_usm.real.dtype)
@@ -688,7 +652,6 @@ def test_usm_ndarray(self, n):
688652
def test_out(self, dtype, n, norm):
689653
a = generate_random_numpy_array(11, dtype=dtype)
690654
ia = dpnp.array(a)
691-
ia = _make_array_Hermitian(ia, n)
692655

693656
expected = numpy.fft.irfft(a, n=n, norm=norm)
694657
out = dpnp.empty(expected.shape, dtype=a.real.dtype)
@@ -704,9 +667,6 @@ def test_out(self, dtype, n, norm):
704667
def test_2d_array_out(self, dtype, n, axis, norm, order):
705668
a = generate_random_numpy_array((3, 4), dtype=dtype, order=order)
706669
ia = dpnp.array(a)
707-
ia = dpnp.moveaxis(ia, axis, 0)
708-
ia = _make_array_Hermitian(ia, n)
709-
ia = dpnp.moveaxis(ia, 0, axis)
710670

711671
expected = numpy.fft.irfft(a, n=n, axis=axis, norm=norm)
712672
out = dpnp.empty(expected.shape, dtype=expected.dtype)
@@ -994,5 +954,7 @@ def test_1d_array(self):
994954

995955
result = dpnp.fft.irfftn(ia)
996956
expected = numpy.fft.irfftn(a)
997-
flag = True if numpy_version() < "2.0.0" else False
957+
# TODO: change to the commented line when mkl_fft-gh-180 is merged
958+
flag = True
959+
# flag = True if numpy_version() < "2.0.0" else False
998960
assert_dtype_allclose(result, expected, check_only_type_kind=flag)

dpnp/tests/test_mathematical.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1188,7 +1188,7 @@ def test_complex_fp(self, dtype_x, dtype_y):
11881188
"left, right", [[-40, 40], [dpnp.array(-40), dpnp.array(40)]]
11891189
)
11901190
def test_left_right_args(self, dtype, left, right):
1191-
x = numpy.array([-1, 0, 1, 2, 3, 4, 5, 6], dtype=dtype)
1191+
x = numpy.array([0, 1, 2, 3, 4, 5, 6], dtype=dtype)
11921192
xp = numpy.array([0, 3, 6], dtype=dtype)
11931193
fp = numpy.array([0, 9, 18], dtype=dtype)
11941194

dpnp/tests/third_party/cupy/creation_tests/test_ranges.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -226,10 +226,13 @@ def test_linspace_mixed_start_stop2(self, xp, dtype_range, dtype_out):
226226
# TODO (ev-br): np 2.0: check if can re-enable float16
227227
# TODO (ev-br): np 2.0: had to bump the default rtol on Windows
228228
# and numpy 1.26+weak promotion from 0 to 5e-6
229-
if xp.dtype(dtype_range).kind in "u":
229+
if xp.dtype(dtype_range).kind == "u":
230230
# to avoid overflow, limit `val` to be smaller
231231
# than xp.iinfo(dtype).max
232-
if dtype_range in [xp.uint8, xp.uint16] or dtype_out == xp.uint8:
232+
if dtype_range in [xp.uint8, xp.uint16] or dtype_out in [
233+
xp.int8,
234+
xp.uint8,
235+
]:
233236
val = 125
234237
else:
235238
val = 160

dpnp/tests/third_party/cupy/fft_tests/test_fft.py

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -912,7 +912,8 @@ def test_rfft(self, xp, dtype):
912912
atol=2e-6,
913913
accept_error=ValueError,
914914
contiguous_check=False,
915-
type_check=has_support_aspect64(),
915+
# TODO: replace with has_support_aspect64() when mkl_fft-gh-180 is merged
916+
type_check=False,
916917
)
917918
def test_irfft(self, xp, dtype):
918919
a = testing.shaped_random(self.shape, xp, dtype)
@@ -1043,14 +1044,11 @@ def test_rfft2(self, xp, dtype, order, enable_nd):
10431044
atol=1e-7,
10441045
accept_error=ValueError,
10451046
contiguous_check=False,
1046-
type_check=has_support_aspect64(),
1047+
# TODO: replace with has_support_aspect64() when mkl_fft-gh-180 is merged
1048+
type_check=False,
10471049
)
10481050
def test_irfft2(self, xp, dtype, order, enable_nd):
10491051
# assert config.enable_nd_planning == enable_nd
1050-
1051-
if self.s is None and self.axes in [None, (-2, -1)]:
1052-
pytest.skip("Input is not Hermitian Symmetric")
1053-
10541052
a = testing.shaped_random(self.shape, xp, dtype)
10551053
if order == "F":
10561054
a = xp.asfortranarray(a)
@@ -1133,14 +1131,11 @@ def test_rfftn(self, xp, dtype, order, enable_nd):
11331131
atol=1e-7,
11341132
accept_error=ValueError,
11351133
contiguous_check=False,
1136-
type_check=has_support_aspect64(),
1134+
# TODO: replace with has_support_aspect64() when mkl_fft-gh-180 is merged
1135+
type_check=False,
11371136
)
11381137
def test_irfftn(self, xp, dtype, order, enable_nd):
11391138
# assert config.enable_nd_planning == enable_nd
1140-
1141-
if self.s is None and self.axes in [None, (-2, -1)]:
1142-
pytest.skip("Input is not Hermitian Symmetric")
1143-
11441139
a = testing.shaped_random(self.shape, xp, dtype)
11451140
if order == "F":
11461141
a = xp.asfortranarray(a)
@@ -1331,7 +1326,8 @@ class TestHfft:
13311326
atol=2e-6,
13321327
accept_error=ValueError,
13331328
contiguous_check=False,
1334-
type_check=has_support_aspect64(),
1329+
# TODO: replace with has_support_aspect64() when mkl_fft-gh-180 is merged
1330+
type_check=False,
13351331
)
13361332
def test_hfft(self, xp, dtype):
13371333
a = testing.shaped_random(self.shape, xp, dtype)

0 commit comments

Comments
 (0)