Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/conda-package-cf.yml
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ jobs:
- name: Install mkl_fft
run: |
CHANNELS="-c $GITHUB_WORKSPACE/channel ${{ env.CHANNELS }}"
conda create -n ${{ env.TEST_ENV_NAME }} python=${{ matrix.python_ver }} ${{ matrix.numpy }} $PACKAGE_NAME pytest $CHANNELS
conda create -n ${{ env.TEST_ENV_NAME }} python=${{ matrix.python_ver }} ${{ matrix.numpy }} $PACKAGE_NAME pytest scipy $CHANNELS
# Test installed packages
conda list -n ${{ env.TEST_ENV_NAME }}
- name: Run tests
Expand Down Expand Up @@ -268,7 +268,7 @@ jobs:
SET PACKAGE_VERSION=%%F
)
SET "TEST_DEPENDENCIES=pytest pytest-cov"
conda install -n ${{ env.TEST_ENV_NAME }} ${{ env.PACKAGE_NAME }}=%PACKAGE_VERSION% %TEST_DEPENDENCIES% python=${{ matrix.python }} ${{ matrix.numpy }} -c ${{ env.workdir }}/channel ${{ env.CHANNELS }}
conda install -n ${{ env.TEST_ENV_NAME }} ${{ env.PACKAGE_NAME }}=%PACKAGE_VERSION% %TEST_DEPENDENCIES% python=${{ matrix.python }} ${{ matrix.numpy }} scipy -c ${{ env.workdir }}/channel ${{ env.CHANNELS }}
- name: Report content of test environment
shell: cmd /C CALL {0}
run: |
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/conda-package.yml
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ jobs:
- name: Install mkl_fft
run: |
CHANNELS="-c $GITHUB_WORKSPACE/channel ${{ env.CHANNELS }}"
conda create -n ${{ env.TEST_ENV_NAME }} python=${{ matrix.python }} $PACKAGE_NAME pytest $CHANNELS
conda create -n ${{ env.TEST_ENV_NAME }} python=${{ matrix.python }} $PACKAGE_NAME pytest scipy $CHANNELS
# Test installed packages
conda list -n ${{ env.TEST_ENV_NAME }}
- name: Run tests
Expand Down Expand Up @@ -267,7 +267,7 @@ jobs:
SET PACKAGE_VERSION=%%F
)
SET "TEST_DEPENDENCIES=pytest pytest-cov"
conda install -n ${{ env.TEST_ENV_NAME }} ${{ env.PACKAGE_NAME }}=%PACKAGE_VERSION% %TEST_DEPENDENCIES% python=${{ matrix.python }} -c ${{ env.workdir }}/channel ${{ env.CHANNELS }}
conda install -n ${{ env.TEST_ENV_NAME }} ${{ env.PACKAGE_NAME }}=%PACKAGE_VERSION% %TEST_DEPENDENCIES% python=${{ matrix.python }} scipy -c ${{ env.workdir }}/channel ${{ env.CHANNELS }}
- name: Report content of test environment
shell: cmd /C CALL {0}
run: |
Expand Down
11 changes: 6 additions & 5 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
_vendored/__pycache__/
# CMake build and local install directory
build/
mkl_fft.egg-info/
mkl_fft/__pycache__/

# Byte-compiled / optimized / DLL files
__pycache__/

mkl_fft/_pydfti.c
mkl_fft/_pydfti.cpython-310-x86_64-linux-gnu.so
mkl_fft/interfaces/__pycache__/
mkl_fft/_pydfti.cpython*.so
mkl_fft/src/mklfft.c
mkl_fft/tests/__pycache__/
1 change: 1 addition & 0 deletions conda-recipe-cf/meta.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ test:
- pytest -v --pyargs mkl_fft
requires:
- pytest
- scipy
imports:
- mkl_fft
- mkl_fft.interfaces
Expand Down
1 change: 1 addition & 0 deletions conda-recipe/meta.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ test:
- pytest -v --pyargs mkl_fft
requires:
- pytest
- scipy
imports:
- mkl_fft
- mkl_fft.interfaces
Expand Down
51 changes: 51 additions & 0 deletions mkl_fft/_fft_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
#!/usr/bin/env python
# Copyright (c) 2025, Intel Corporation
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# * Neither the name of Intel Corporation nor the names of its contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

import numpy as np

__all__ = ["_check_norm", "_compute_fwd_scale"]


def _check_norm(norm):
if norm not in (None, "ortho", "forward", "backward"):
raise ValueError(
f"Invalid norm value {norm} should be None, 'ortho', 'forward', "
"or 'backward'."
)


def _compute_fwd_scale(norm, n, shape):
_check_norm(norm)
if norm in (None, "backward"):
return 1.0

ss = n if n is not None else shape
nn = np.prod(ss)
fsc = 1 / nn if nn != 0 else 1
if norm == "forward":
return fsc
else: # norm == "ortho"
return np.sqrt(fsc)
4 changes: 2 additions & 2 deletions mkl_fft/_float_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ def __downcast_float128_array(x):
def __supported_array_or_not_implemented(x):
"""
Used in _scipy_fft to convert array to float32,
float64, complex64, or complex128 type or return NotImplemented
float64, complex64, or complex128 type or raise NotImplementedError
"""
__x = np.asarray(x)
black_list = [np.half]
Expand All @@ -91,5 +91,5 @@ def __supported_array_or_not_implemented(x):
if hasattr(np, "complex256"):
black_list.append(np.complex256)
if __x.dtype in black_list:
return NotImplemented
raise NotImplementedError
return __x
135 changes: 57 additions & 78 deletions mkl_fft/_numpy_fft.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,32 +74,54 @@
import warnings

import numpy as np
from numpy import array, conjugate, prod, sqrt, take

from . import _float_utils
from . import _pydfti as mkl_fft # pylint: disable=no-name-in-module
from ._fft_utils import _check_norm, _compute_fwd_scale
from ._float_utils import __downcast_float128_array


def _compute_fwd_scale(norm, n, shape):
_check_norm(norm)
if norm in (None, "backward"):
return 1.0

ss = n if n is not None else shape
nn = prod(ss)
fsc = 1 / nn if nn != 0 else 1
if norm == "forward":
return fsc
else: # norm == "ortho"
return sqrt(fsc)


def _check_norm(norm):
if norm not in (None, "ortho", "forward", "backward"):
raise ValueError(
f"Invalid norm value {norm} should be None, 'ortho', 'forward', "
"or 'backward'."
# copied with modifications from:
# https://github.com/numpy/numpy/blob/main/numpy/fft/_pocketfft.py
def _cook_nd_args(a, s=None, axes=None, invreal=False):
if s is None:
shapeless = True
if axes is None:
s = list(a.shape)
else:
s = np.take(a.shape, axes)
else:
shapeless = False
s = list(s)
if axes is None:
if not shapeless and np.__version__ >= "2.0":
msg = (
"`axes` should not be `None` if `s` is not `None` "
"(Deprecated in NumPy 2.0). In a future version of NumPy, "
"this will raise an error and `s[i]` will correspond to "
"the size along the transformed axis specified by "
"`axes[i]`. To retain current behaviour, pass a sequence "
"[0, ..., k-1] to `axes` for an array of dimension k."
)
warnings.warn(msg, DeprecationWarning, stacklevel=3)
axes = list(range(-len(s), 0))
if len(s) != len(axes):
raise ValueError("Shape and axes have different lengths.")
if invreal and shapeless:
s[-1] = (a.shape[axes[-1]] - 1) * 2
if None in s and np.__version__ >= "2.0":
msg = (
"Passing an array containing `None` values to `s` is "
"deprecated in NumPy 2.0 and will raise an error in "
"a future version of NumPy. To use the default behaviour "
"of the corresponding 1-D transform, pass the value matching "
"the default for its `n` parameter. To use the default "
"behaviour for every axis, the `s` argument can be omitted."
)
warnings.warn(msg, DeprecationWarning, stacklevel=3)
# use the whole input array along axis `i` if `s[i] == -1 or None`
s = [a.shape[_a] if _s in [-1, None] else _s for _s, _a in zip(s, axes)]

return s, axes


def _swap_direction(norm):
Expand Down Expand Up @@ -218,7 +240,7 @@ def fft(a, n=None, axis=-1, norm=None):

"""

x = _float_utils.__downcast_float128_array(a)
x = __downcast_float128_array(a)
fsc = _compute_fwd_scale(norm, n, x.shape[axis])

return trycall(mkl_fft.fft, (x,), {"n": n, "axis": axis, "fwd_scale": fsc})
Expand Down Expand Up @@ -311,7 +333,7 @@ def ifft(a, n=None, axis=-1, norm=None):

"""

x = _float_utils.__downcast_float128_array(a)
x = __downcast_float128_array(a)
fsc = _compute_fwd_scale(norm, n, x.shape[axis])

return trycall(mkl_fft.ifft, (x,), {"n": n, "axis": axis, "fwd_scale": fsc})
Expand Down Expand Up @@ -402,7 +424,7 @@ def rfft(a, n=None, axis=-1, norm=None):

"""

x = _float_utils.__downcast_float128_array(a)
x = __downcast_float128_array(a)
fsc = _compute_fwd_scale(norm, n, x.shape[axis])

return trycall(mkl_fft.rfft, (x,), {"n": n, "axis": axis, "fwd_scale": fsc})
Expand Down Expand Up @@ -495,7 +517,7 @@ def irfft(a, n=None, axis=-1, norm=None):

"""

x = _float_utils.__downcast_float128_array(a)
x = __downcast_float128_array(a)
fsc = _compute_fwd_scale(norm, n, 2 * (x.shape[axis] - 1))

return trycall(
Expand Down Expand Up @@ -581,9 +603,9 @@ def hfft(a, n=None, axis=-1, norm=None):
"""

norm = _swap_direction(norm)
x = _float_utils.__downcast_float128_array(a)
x = array(x, copy=True, dtype=complex)
conjugate(x, out=x)
x = __downcast_float128_array(a)
x = np.array(x, copy=True, dtype=complex)
np.conjugate(x, out=x)
fsc = _compute_fwd_scale(norm, n, 2 * (x.shape[axis] - 1))

return trycall(
Expand Down Expand Up @@ -651,61 +673,18 @@ def ihfft(a, n=None, axis=-1, norm=None):

# The copy may be required for multithreading.
norm = _swap_direction(norm)
x = _float_utils.__downcast_float128_array(a)
x = array(x, copy=True, dtype=float)
x = __downcast_float128_array(a)
x = np.array(x, copy=True, dtype=float)
fsc = _compute_fwd_scale(norm, n, x.shape[axis])

output = trycall(
mkl_fft.rfft, (x,), {"n": n, "axis": axis, "fwd_scale": fsc}
)

conjugate(output, out=output)
np.conjugate(output, out=output)
return output


# copied from: https://github.com/numpy/numpy/blob/main/numpy/fft/_pocketfft.py
def _cook_nd_args(a, s=None, axes=None, invreal=False):
if s is None:
shapeless = True
if axes is None:
s = list(a.shape)
else:
s = take(a.shape, axes)
else:
shapeless = False
s = list(s)
if axes is None:
if not shapeless and np.__version__ >= "2.0":
msg = (
"`axes` should not be `None` if `s` is not `None` "
"(Deprecated in NumPy 2.0). In a future version of NumPy, "
"this will raise an error and `s[i]` will correspond to "
"the size along the transformed axis specified by "
"`axes[i]`. To retain current behaviour, pass a sequence "
"[0, ..., k-1] to `axes` for an array of dimension k."
)
warnings.warn(msg, DeprecationWarning, stacklevel=3)
axes = list(range(-len(s), 0))
if len(s) != len(axes):
raise ValueError("Shape and axes have different lengths.")
if invreal and shapeless:
s[-1] = (a.shape[axes[-1]] - 1) * 2
if None in s and np.__version__ >= "2.0":
msg = (
"Passing an array containing `None` values to `s` is "
"deprecated in NumPy 2.0 and will raise an error in "
"a future version of NumPy. To use the default behaviour "
"of the corresponding 1-D transform, pass the value matching "
"the default for its `n` parameter. To use the default "
"behaviour for every axis, the `s` argument can be omitted."
)
warnings.warn(msg, DeprecationWarning, stacklevel=3)
# use the whole input array along axis `i` if `s[i] == -1 or None`
s = [a.shape[_a] if _s in [-1, None] else _s for _s, _a in zip(s, axes)]

return s, axes


def fftn(a, s=None, axes=None, norm=None):
"""
Compute the N-dimensional discrete Fourier Transform.
Expand Down Expand Up @@ -806,7 +785,7 @@ def fftn(a, s=None, axes=None, norm=None):

"""

x = _float_utils.__downcast_float128_array(a)
x = __downcast_float128_array(a)
s, axes = _cook_nd_args(x, s, axes)
fsc = _compute_fwd_scale(norm, s, x.shape)

Expand Down Expand Up @@ -913,7 +892,7 @@ def ifftn(a, s=None, axes=None, norm=None):

"""

x = _float_utils.__downcast_float128_array(a)
x = __downcast_float128_array(a)
s, axes = _cook_nd_args(x, s, axes)
fsc = _compute_fwd_scale(norm, s, x.shape)

Expand Down Expand Up @@ -1201,7 +1180,7 @@ def rfftn(a, s=None, axes=None, norm=None):

"""

x = _float_utils.__downcast_float128_array(a)
x = __downcast_float128_array(a)
s, axes = _cook_nd_args(x, s, axes)
fsc = _compute_fwd_scale(norm, s, x.shape)

Expand Down Expand Up @@ -1345,7 +1324,7 @@ def irfftn(a, s=None, axes=None, norm=None):

"""

x = _float_utils.__downcast_float128_array(a)
x = __downcast_float128_array(a)
s, axes = _cook_nd_args(x, s, axes, invreal=True)
fsc = _compute_fwd_scale(norm, s, x.shape)

Expand Down
4 changes: 2 additions & 2 deletions mkl_fft/_pydfti.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -1335,11 +1335,11 @@ def ifftn(x, s=None, axes=None, overwrite_x=False, fwd_scale=1.0):


def rfft2(x, s=None, axes=(-2, -1), fwd_scale=1.0):
return rfftn(x, s=s, axes=axes, fsc=fwd_scale)
return rfftn(x, s=s, axes=axes, fwd_scale=fwd_scale)


def irfft2(x, s=None, axes=(-2, -1), fwd_scale=1.0):
return irfftn(x, s=s, axes=axes, fsc=fwd_scale)
return irfftn(x, s=s, axes=axes, fwd_scale=fwd_scale)


def _remove_axis(s, axes, axis_to_remove):
Expand Down
Loading
Loading