diff --git a/CHANGELOG.md b/CHANGELOG.md index 0fca2dc1..c9904bf9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changed ### Fixed +* Zero-padding (`n` larger than the input length along the axis) of input that is both C- and F-contiguous, such as shape `(m, 1)` padded along axis 1, now gives a C-contiguous result as NumPy does instead of a Fortran-contiguous one, so a C-ordered `out` is also written directly without a temporary copy [gh-397](https://github.com/IntelPython/mkl_fft/pull/397) ## [2.4.0] - 2026-10-16 diff --git a/mkl_fft/_pydfti.pyx b/mkl_fft/_pydfti.pyx index 76a30600..de3a0a6f 100644 --- a/mkl_fft/_pydfti.pyx +++ b/mkl_fft/_pydfti.pyx @@ -229,9 +229,9 @@ cdef cnp.ndarray _pad_array( b_shape[axis] = n # allocating temporary buffer - x_arr_is_fortran = cnp.PyArray_CHKFLAGS( - x_arr, cnp.NPY_ARRAY_F_CONTIGUOUS - ) + # F order only if x_arr is not also C-contiguous (e.g. shape (m, 1)), + # as NumPy does, otherwise padding along a unit axis gives an F array + x_arr_is_fortran = cnp.PyArray_ISFORTRAN(x_arr) b_arr = cnp.PyArray_EMPTY( b_ndim, b_shape, b_type, x_arr_is_fortran ) # 0 for C-contiguous diff --git a/mkl_fft/tests/test_fft1d.py b/mkl_fft/tests/test_fft1d.py index 4336aef2..b8b58816 100644 --- a/mkl_fft/tests/test_fft1d.py +++ b/mkl_fft/tests/test_fft1d.py @@ -497,6 +497,28 @@ def test_irfft_dtype(dt): assert_allclose(result, expected, rtol=1e-7, atol=1e-7, strict=True) +@pytest.mark.parametrize( + "shape, axis", [((5, 1), 1), ((1, 5), 0), ((4, 1, 1), 2), ((1, 1, 4), 0)] +) +@pytest.mark.parametrize("func", ["fft", "ifft", "rfft", "irfft"]) +def test_pad_both_contiguous_input(func, shape, axis): + # x is both C- and F-contiguous and is zero-padded along a unit axis, + # the padded copy and so the result are C-contiguous as in NumPy + x = rnd.random(shape) + if func == "irfft": + x = x + 1j * rnd.random(shape) + expected = getattr(np.fft, func)(x, n=8, axis=axis) + + result = getattr(mkl_fft, func)(x, n=8, axis=axis) + assert result.flags.c_contiguous + assert_allclose(result, expected, atol=1e-12) + + out = np.full_like(expected, -1, order="C") + result = getattr(mkl_fft, func)(x, n=8, axis=axis, out=out) + assert result is out + assert_allclose(result, expected, atol=1e-12) + + @pytest.mark.parametrize("step", [2, -1, -3]) @pytest.mark.parametrize("func", ["fft", "ifft"]) def test_fft_real_input_out_strided_1d(func, step):