Skip to content

Commit cd1c2b3

Browse files
authored
Merge branch 'master' into compare-shape
2 parents c9dc521 + 7056a0c commit cd1c2b3

File tree

8 files changed

+81
-82
lines changed

8 files changed

+81
-82
lines changed

.pre-commit-config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ repos:
7979
- id: clang-format
8080
args: ["-i"]
8181
- repo: https://github.com/gitleaks/gitleaks
82-
rev: v8.25.1
82+
rev: v8.26.0
8383
hooks:
8484
- id: gitleaks
8585
- repo: https://github.com/jumanjihouse/pre-commit-hooks

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ This release achieves 100% compliance with Python Array API specification (revis
3737
* Bumped oneMKL version up to `0.7` [#2448](https://github.com/IntelPython/dpnp/pull/2448)
3838
* The parameter `axis` in `dpnp.take_along_axis` function has now a default value of `-1` [#2442](https://github.com/IntelPython/dpnp/pull/2442)
3939
* Updates the list of required python versions documented in `Quick Start Guide` [#2449](https://github.com/IntelPython/dpnp/pull/2449)
40+
* Updated FFT module to ensure an input array is Hermitian before calling complex-to-real FFT [#2444](https://github.com/IntelPython/dpnp/pull/2444)
4041

4142
### Fixed
4243

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
@@ -1185,7 +1185,7 @@ def test_complex_fp(self, dtype_x, dtype_y):
11851185
"left, right", [[-40, 40], [dpnp.array(-40), dpnp.array(40)]]
11861186
)
11871187
def test_left_right_args(self, dtype, left, right):
1188-
x = numpy.array([-1, 0, 1, 2, 3, 4, 5, 6], dtype=dtype)
1188+
x = numpy.array([0, 1, 2, 3, 4, 5, 6], dtype=dtype)
11891189
xp = numpy.array([0, 3, 6], dtype=dtype)
11901190
fp = numpy.array([0, 9, 18], dtype=dtype)
11911191

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)