From 9a9b5ce5d5f4c77e4dce290e6ccf90fb0360fc0d Mon Sep 17 00:00:00 2001 From: Anton Volkov Date: Tue, 20 Oct 2026 16:03:15 +0200 Subject: [PATCH] fix: keep C order when zero-padding input that is both C- and F-contiguous `_pad_array` allocated the zero-padded copy in F order whenever the input had the F-contiguous flag. An input with a single non-unit dimension, such as shape (m, 1), has both flags, so padding it along the unit axis gave an F-contiguous copy of shape (m, n), and so an F-contiguous result of `fft`, `ifft`, `rfft` and `irfft`, where NumPy returns a C-contiguous one. It also made a C-ordered `out` incompatible with the copy passed to MKL, so the result was computed into a temporary array and copied into `out`. Use F order only when the input is F- but not C-contiguous, which is what NumPy's PyArray_ISFORTRAN checks. --- CHANGELOG.md | 1 + mkl_fft/_pydfti.pyx | 6 +++--- mkl_fft/tests/test_fft1d.py | 22 ++++++++++++++++++++++ 3 files changed, 26 insertions(+), 3 deletions(-) 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):