├── mkl_fft
├── tests
│ ├── __init__.py
│ ├── helper.py
│ ├── third_party
│ │ └── scipy
│ │ │ └── test_multithreading.py
│ ├── test_interfaces.py
│ ├── test_fftnd.py
│ └── test_fft1d.py
├── _version.py
├── interfaces
│ ├── __init__.py
│ ├── numpy_fft.py
│ ├── scipy_fft.py
│ ├── _float_utils.py
│ ├── _numpy_helper.py
│ ├── README.md
│ ├── _numpy_fft.py
│ └── _scipy_fft.py
├── _init_helper.py
├── __init__.py
├── _mkl_fft.py
├── src
│ ├── mklfft.h
│ └── multi_iter.h
└── _fft_utils.py
├── _vendored
├── __init__.py
├── README.md
└── conv_template.py
├── .github
├── CODEOWNERS
├── dependabot.yml
└── workflows
│ ├── pre-commit.yml
│ ├── build_pip.yaml
│ ├── build-with-clang.yml
│ ├── openssf-scorecard.yml
│ ├── conda-package-cf.yml
│ └── conda-package.yml
├── conda-recipe-cf
├── bld.bat
├── build.sh
└── meta.yaml
├── .git-blame-ignore-revs
├── .gitignore
├── SECURITY.md
├── conda-recipe
├── bld.bat
├── conda_build_config.yaml
├── build.sh
└── meta.yaml
├── LICENSE.txt
├── .flake8
├── .pre-commit-config.yaml
├── pyproject.toml
├── setup.py
├── README.md
└── CHANGELOG.md
/mkl_fft/tests/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/_vendored/__init__.py:
--------------------------------------------------------------------------------
1 | # empty file
2 |
--------------------------------------------------------------------------------
/mkl_fft/_version.py:
--------------------------------------------------------------------------------
1 | __version__ = "2.2.0dev0"
2 |
--------------------------------------------------------------------------------
/.github/CODEOWNERS:
--------------------------------------------------------------------------------
1 | * @ndgrigorian @antonwolfy @xaleryb @jharlow-intel
2 |
--------------------------------------------------------------------------------
/conda-recipe-cf/bld.bat:
--------------------------------------------------------------------------------
1 | set MKLROOT=%PREFIX%
2 | %PYTHON% -m pip install --no-build-isolation --no-deps .
3 | if errorlevel 1 exit 1
4 |
--------------------------------------------------------------------------------
/conda-recipe-cf/build.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash -x
2 |
3 | export MKLROOT=$PREFIX
4 | export CFLAGS="-I$PREFIX/include $CFLAGS"
5 | $PYTHON -m pip install --no-build-isolation --no-deps .
6 |
--------------------------------------------------------------------------------
/_vendored/README.md:
--------------------------------------------------------------------------------
1 | ## Vendored files
2 |
3 | File `conv_template.py` is copied from NumPy's numpy/distutils folder, since
4 | `numpy.distutils` is absent from the installation layout starting with
5 | Python 3.12
6 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: "github-actions"
4 | directory: "/"
5 | schedule:
6 | interval: "weekly"
7 | day: "saturday"
8 | rebase-strategy: "disabled"
9 |
--------------------------------------------------------------------------------
/.git-blame-ignore-revs:
--------------------------------------------------------------------------------
1 | # $ git config blame.ignoreRevsFile .git-blame-ignore-revs
2 |
3 | # Add pre-commit hooks
4 | 2e1b33fcf6b7f0c7c9d7d5d7f55d2d0ba35f393a
5 |
6 | # Add cython-lint to pre-commit config
7 | 5df98187dda56f6d340f0824570fe5080d0b4f05
8 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # CMake build and local install directory
2 | build/
3 | mkl_fft.egg-info/
4 |
5 | # Byte-compiled / optimized / DLL files
6 | __pycache__/
7 |
8 | mkl_fft/_pydfti.c
9 | mkl_fft/_pydfti.cpython*.so
10 | mkl_fft/_pydfti.*-win_amd64.pyd
11 | mkl_fft/src/mklfft.c
12 |
--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------
1 | # Security Policy
2 | Intel is committed to rapidly addressing security vulnerabilities affecting our customers and providing clear guidance on the solution, impact, severity and mitigation.
3 |
4 | ## Reporting a Vulnerability
5 | Please report any security vulnerabilities in this project utilizing the guidelines [here](https://www.intel.com/content/www/us/en/security-center/vulnerability-handling-guidelines.html).
6 |
--------------------------------------------------------------------------------
/conda-recipe/bld.bat:
--------------------------------------------------------------------------------
1 | set MKLROOT=%PREFIX%
2 |
3 | rem Build wheel package
4 | if NOT "%WHEELS_OUTPUT_FOLDER%"=="" (
5 | %PYTHON% -m pip wheel --no-build-isolation --no-deps .
6 | if errorlevel 1 exit 1
7 | copy mkl_fft*.whl %WHEELS_OUTPUT_FOLDER%
8 | if errorlevel 1 exit 1
9 | ) ELSE (
10 | rem Build conda package
11 | %PYTHON% -m pip install --no-build-isolation --no-deps .
12 | if errorlevel 1 exit 1
13 | )
14 |
--------------------------------------------------------------------------------
/conda-recipe/conda_build_config.yaml:
--------------------------------------------------------------------------------
1 | c_compiler: # [linux]
2 | - gcc # [linux]
3 | cxx_compiler: # [linux]
4 | - gxx # [linux]
5 | cxx_compiler_version: # [linux]
6 | - '14' # [linux]
7 | c_stdlib: # [linux]
8 | - sysroot # [linux]
9 | c_stdlib_version: # [linux]
10 | - '2.28' # [linux]
11 | c_stdlib: # [win]
12 | - vs # [win]
13 | cxx_compiler: # [win]
14 | - vs2017 # [win]
15 | c_compiler: # [win]
16 | - vs2017 # [win]
17 |
--------------------------------------------------------------------------------
/conda-recipe/build.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash -x
2 |
3 | export MKLROOT=$PREFIX
4 | export CFLAGS="-I$PREFIX/include $CFLAGS"
5 | export LDFLAGS="-Wl,-rpath,\$ORIGIN/../.. -Wl,-rpath,\$ORIGIN/../../.. -L${PREFIX}/lib ${LDFLAGS}"
6 |
7 | read -r GLIBC_MAJOR GLIBC_MINOR <<<"$(conda list '^sysroot_linux-64$' \
8 | | tail -n 1 | awk '{print $2}' | grep -oP '\d+' | head -n 2 | tr '\n' ' ')"
9 |
10 | # Build wheel package
11 | if [ -n "${WHEELS_OUTPUT_FOLDER}" ]; then
12 | $PYTHON -m pip wheel --no-build-isolation --no-deps .
13 | ${PYTHON} -m wheel tags --remove --platform-tag "manylinux_${GLIBC_MAJOR}_${GLIBC_MINOR}_x86_64" mkl_fft*.whl
14 | cp mkl_fft*.whl "${WHEELS_OUTPUT_FOLDER}"
15 | else
16 | # Build conda package
17 | $PYTHON -m pip install --no-build-isolation --no-deps .
18 | fi
19 |
--------------------------------------------------------------------------------
/conda-recipe-cf/meta.yaml:
--------------------------------------------------------------------------------
1 | {% set version = "2.2.0dev0" %}
2 | {% set buildnumber = 0 %}
3 |
4 | package:
5 | name: mkl_fft
6 | version: {{ version }}
7 |
8 | source:
9 | path: ../
10 |
11 | build:
12 | number: {{ buildnumber }}
13 | ignore_run_exports:
14 | - blas
15 |
16 | requirements:
17 | build:
18 | - {{ compiler('c') }}
19 | host:
20 | - python
21 | - setuptools >=77
22 | - mkl-devel
23 | - cython
24 | - numpy
25 | run:
26 | - python
27 | - mkl-service
28 | - numpy
29 |
30 | test:
31 | commands:
32 | - pytest -v --pyargs mkl_fft
33 | requires:
34 | - pytest
35 | - scipy >=1.10
36 | imports:
37 | - mkl_fft
38 | - mkl_fft.interfaces
39 | - mkl_fft.interfaces.numpy_fft
40 | - mkl_fft.interfaces.scipy_fft
41 |
42 | about:
43 | home: http://github.com/IntelPython/mkl_fft
44 | license: BSD-3-Clause
45 | license_file: LICENSE.txt
46 | summary: NumPy-based implementation of Fast Fourier Transform using Intel® oneAPI Math Kernel Library (OneMKL)
47 |
--------------------------------------------------------------------------------
/.github/workflows/pre-commit.yml:
--------------------------------------------------------------------------------
1 | name: pre-commit
2 |
3 | on:
4 | pull_request:
5 | push:
6 | branches: [master]
7 |
8 | permissions: read-all
9 |
10 | jobs:
11 | pre-commit:
12 | runs-on: ubuntu-latest
13 | timeout-minutes: 30
14 | steps:
15 | - name: Checkout repo
16 | uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
17 |
18 | - name: Set up python
19 | uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0
20 | with:
21 | python-version: '3.13'
22 |
23 | - name: Set up pip packages
24 | uses: BSFishy/pip-action@8f2d471d809dc20b6ada98c91910b6ae6243f318 # v1
25 | with:
26 | packages: |
27 | codespell
28 | pylint
29 |
30 | - name: Set up clang-format
31 | run: |
32 | sudo apt-get install -y clang-format-14
33 | sudo unlink /usr/bin/clang-format
34 | sudo ln -s /usr/bin/clang-format-14 /usr/bin/clang-format
35 | clang-format --version
36 |
37 | - name: Run pre-commit checks
38 | uses: pre-commit/action@2c7b3805fd2a0fd8c1884dcaebf91fc102a13ecd # v3.0.1
39 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | Copyright (c) 2017, Intel Corporation
2 |
3 | Redistribution and use in source and binary forms, with or without
4 | modification, are permitted provided that the following conditions are met:
5 |
6 | * Redistributions of source code must retain the above copyright notice,
7 | this list of conditions and the following disclaimer.
8 | * Redistributions in binary form must reproduce the above copyright
9 | notice, this list of conditions and the following disclaimer in the
10 | documentation and/or other materials provided with the distribution.
11 | * Neither the name of Intel Corporation nor the names of its contributors
12 | may be used to endorse or promote products derived from this software
13 | without specific prior written permission.
14 |
15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
16 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
19 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
21 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
22 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
23 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 |
--------------------------------------------------------------------------------
/.flake8:
--------------------------------------------------------------------------------
1 | [flake8]
2 | extend-ignore =
3 | # whitespace before ':' (currently conflicts with black formatting):
4 | E203,
5 | # line too long (in docstrings):
6 | E501,
7 | # ‘from module import *’ used; unable to detect undefined names:
8 | F403,
9 | # doc line too long (105 > 80 characters):
10 | W505,
11 | # missing docstring in public module:
12 | D100,
13 | # missing docstring in public class:
14 | D101,
15 | # missing docstring in public method:
16 | D102,
17 | # missing docstring in public function:
18 | D103,
19 | # missing docstring in public package:
20 | D104,
21 | # missing docstring in magic method:
22 | D105,
23 | # missing docstring in __init__:
24 | D107,
25 | # no blank lines allowed after function docstring:
26 | D202,
27 | # 1 blank line required between summary line and description:
28 | D205,
29 | # first line should end with a period:
30 | D400,
31 | # first line should be in imperative mood:
32 | D401,
33 | # first line should not be the function's "signature":
34 | D402,
35 | # first word of the first line should be properly capitalized
36 | D403,
37 |
38 | per-file-ignores =
39 | mkl_fft/__init__.py: E402, F401
40 | mkl_fft/interfaces/__init__.py: F401
41 | mkl_fft/interfaces/scipy_fft.py: F401
42 | mkl_fft/interfaces/numpy_fft.py: F401
43 |
44 | exclude = _vendored/conv_template.py
45 |
46 | filename = *.py, *.pyx, *.pxi, *.pxd
47 | max_line_length = 80
48 | max-doc-length = 80
49 | show-source = True
50 |
51 | # Print detailed statistic if any issue detected
52 | count = True
53 | statistics = True
54 |
--------------------------------------------------------------------------------
/conda-recipe/meta.yaml:
--------------------------------------------------------------------------------
1 | package:
2 | name: mkl_fft
3 | version: {{ GIT_DESCRIBE_TAG }}
4 |
5 | source:
6 | path: ../
7 |
8 | build:
9 | number: {{ GIT_DESCRIBE_NUMBER }}
10 | script_env:
11 | - WHEELS_OUTPUT_FOLDER
12 | ignore_run_exports:
13 | - blas
14 |
15 | requirements:
16 | build:
17 | - {{ compiler('c') }}
18 | - {{ stdlib('c') }}
19 | host:
20 | - python
21 | - python-gil # [py>=314]
22 | - pip
23 | - setuptools >=77
24 | - mkl-devel
25 | - cython
26 | - numpy-base
27 | - wheel >=0.41.3
28 | run:
29 | - python
30 | - python-gil # [py>=314]
31 | - mkl-service
32 | - {{ pin_compatible('numpy-base') }}
33 |
34 | test:
35 | commands:
36 | - pytest -v --pyargs mkl_fft
37 | requires:
38 | - pytest
39 | # This is a temporary python restriction
40 | - scipy >=1.10 # [py<314]
41 | imports:
42 | - mkl_fft
43 | - mkl_fft.interfaces
44 |
45 | about:
46 | home: http://github.com/IntelPython/mkl_fft
47 | license: BSD-3-Clause
48 | license_file: LICENSE.txt
49 | summary: NumPy-based implementation of Fast Fourier Transform using Intel® oneAPI Math Kernel Library (oneMKL)
50 | description: |
51 | LEGAL NOTICE: Use of this software package is subject to the
52 | software license agreement (as set forth above, in the license section of
53 | the installed Conda package and/or the README file) and all notices,
54 | disclaimers or license terms for third party or open source software
55 | included in or with the software.
56 |
57 | EULA: BSD-3-Clause
58 |
59 |
--------------------------------------------------------------------------------
/mkl_fft/interfaces/__init__.py:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2017, Intel Corporation
2 | #
3 | # Redistribution and use in source and binary forms, with or without
4 | # modification, are permitted provided that the following conditions are met:
5 | #
6 | # * Redistributions of source code must retain the above copyright notice,
7 | # this list of conditions and the following disclaimer.
8 | # * Redistributions in binary form must reproduce the above copyright
9 | # notice, this list of conditions and the following disclaimer in the
10 | # documentation and/or other materials provided with the distribution.
11 | # * Neither the name of Intel Corporation nor the names of its contributors
12 | # may be used to endorse or promote products derived from this software
13 | # without specific prior written permission.
14 | #
15 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
16 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
19 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
21 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
22 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
23 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 |
26 | from . import numpy_fft
27 |
28 | # find scipy, not scipy.fft, to avoid circular dependency
29 | try:
30 | import scipy
31 | except ImportError:
32 | pass
33 | else:
34 | from . import scipy_fft
35 |
--------------------------------------------------------------------------------
/mkl_fft/tests/helper.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # Copyright (c) 2025, Intel Corporation
3 | #
4 | # Redistribution and use in source and binary forms, with or without
5 | # modification, are permitted provided that the following conditions are met:
6 | #
7 | # * Redistributions of source code must retain the above copyright notice,
8 | # this list of conditions and the following disclaimer.
9 | # * Redistributions in binary form must reproduce the above copyright
10 | # notice, this list of conditions and the following disclaimer in the
11 | # documentation and/or other materials provided with the distribution.
12 | # * Neither the name of Intel Corporation nor the names of its contributors
13 | # may be used to endorse or promote products derived from this software
14 | # without specific prior written permission.
15 | #
16 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS """AS IS"
17 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
20 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
22 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
23 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
24 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 |
27 |
28 | import numpy as np
29 | import pytest
30 |
31 | requires_numpy_2 = pytest.mark.skipif(
32 | np.lib.NumpyVersion(np.__version__) < "2.0.0",
33 | reason="Requires NumPy >= 2.0.0",
34 | )
35 |
--------------------------------------------------------------------------------
/.github/workflows/build_pip.yaml:
--------------------------------------------------------------------------------
1 | name: Editable build using pip and pre-release NumPy
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 | pull_request:
8 |
9 | permissions: read-all
10 |
11 | env:
12 | PACKAGE_NAME: mkl_fft
13 | MODULE_NAME: mkl_fft
14 | TEST_ENV_NAME: test_mkl_fft
15 |
16 | jobs:
17 | build:
18 | runs-on: ubuntu-latest
19 | defaults:
20 | run:
21 | shell: bash -el {0}
22 |
23 | strategy:
24 | matrix:
25 | python: ['3.10', '3.11', '3.12', '3.13']
26 | use_pre: ["", "--pre"]
27 |
28 | steps:
29 | - name: Install jq
30 | shell: bash -l {0}
31 | run: |
32 | sudo apt-get install jq
33 |
34 | - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
35 | with:
36 | fetch-depth: 0
37 |
38 | - uses: conda-incubator/setup-miniconda@835234971496cad1653abb28a638a281cf32541f # v3.2.0
39 | with:
40 | use-mamba: true
41 | miniforge-version: latest
42 | channels: conda-forge
43 | conda-remove-defaults: true
44 | activate-environment: test
45 | python-version: ${{ matrix.python }}
46 |
47 | - name: Install MKL
48 | run: |
49 | conda install mkl-devel mkl-service
50 | python -c "import sys; print(sys.executable)"
51 | which python
52 | python -c "import mkl; print(mkl.__file__)"
53 |
54 | - name: Build conda package
55 | run: |
56 | pip install --no-cache-dir cython setuptools
57 | pip install --no-cache-dir numpy ${{ matrix.use_pre }}
58 | echo "CONDA_PREFFIX is '${CONDA_PREFIX}'"
59 | export MKLROOT=${CONDA_PREFIX}
60 | pip install -e .[test] --no-build-isolation --verbose
61 | pip list
62 | python -m pytest -v mkl_fft/tests
63 |
--------------------------------------------------------------------------------
/mkl_fft/_init_helper.py:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2025, Intel Corporation
2 | #
3 | # Redistribution and use in source and binary forms, with or without
4 | # modification, are permitted provided that the following conditions are met:
5 | #
6 | # * Redistributions of source code must retain the above copyright notice,
7 | # this list of conditions and the following disclaimer.
8 | # * Redistributions in binary form must reproduce the above copyright
9 | # notice, this list of conditions and the following disclaimer in the
10 | # documentation and/or other materials provided with the distribution.
11 | # * Neither the name of Intel Corporation nor the names of its contributors
12 | # may be used to endorse or promote products derived from this software
13 | # without specific prior written permission.
14 | #
15 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
16 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
19 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
21 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
22 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
23 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 |
26 | import os
27 | import os.path
28 | import sys
29 |
30 | is_venv_win32 = (
31 | sys.platform == "win32"
32 | and sys.base_exec_prefix != sys.exec_prefix
33 | and os.path.isfile(os.path.join(sys.exec_prefix, "pyvenv.cfg"))
34 | )
35 |
36 | if is_venv_win32:
37 | # In Windows venv: add Library/bin to PATH for proper DLL loading
38 | dll_dir = os.path.join(sys.exec_prefix, "Library", "bin")
39 | if os.path.isdir(dll_dir):
40 | os.add_dll_directory(dll_dir)
41 |
42 | del is_venv_win32
43 |
--------------------------------------------------------------------------------
/mkl_fft/__init__.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # Copyright (c) 2017, Intel Corporation
3 | #
4 | # Redistribution and use in source and binary forms, with or without
5 | # modification, are permitted provided that the following conditions are met:
6 | #
7 | # * Redistributions of source code must retain the above copyright notice,
8 | # this list of conditions and the following disclaimer.
9 | # * Redistributions in binary form must reproduce the above copyright
10 | # notice, this list of conditions and the following disclaimer in the
11 | # documentation and/or other materials provided with the distribution.
12 | # * Neither the name of Intel Corporation nor the names of its contributors
13 | # may be used to endorse or promote products derived from this software
14 | # without specific prior written permission.
15 | #
16 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
20 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
22 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
23 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
24 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 |
27 | from . import _init_helper
28 | from ._mkl_fft import (
29 | fft,
30 | fft2,
31 | fftn,
32 | ifft,
33 | ifft2,
34 | ifftn,
35 | irfft,
36 | irfft2,
37 | irfftn,
38 | rfft,
39 | rfft2,
40 | rfftn,
41 | )
42 | from ._version import __version__
43 |
44 | import mkl_fft.interfaces # isort: skip
45 |
46 | __all__ = [
47 | "fft",
48 | "ifft",
49 | "fft2",
50 | "ifft2",
51 | "fftn",
52 | "ifftn",
53 | "rfft",
54 | "irfft",
55 | "rfft2",
56 | "irfft2",
57 | "rfftn",
58 | "irfftn",
59 | "interfaces",
60 | ]
61 |
62 | del _init_helper
63 |
--------------------------------------------------------------------------------
/mkl_fft/interfaces/numpy_fft.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # Copyright (c) 2017, Intel Corporation
3 | #
4 | # Redistribution and use in source and binary forms, with or without
5 | # modification, are permitted provided that the following conditions are met:
6 | #
7 | # * Redistributions of source code must retain the above copyright notice,
8 | # this list of conditions and the following disclaimer.
9 | # * Redistributions in binary form must reproduce the above copyright
10 | # notice, this list of conditions and the following disclaimer in the
11 | # documentation and/or other materials provided with the distribution.
12 | # * Neither the name of Intel Corporation nor the names of its contributors
13 | # may be used to endorse or promote products derived from this software
14 | # without specific prior written permission.
15 | #
16 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
20 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
22 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
23 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
24 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 |
27 | # pylint: disable=no-name-in-module
28 | from ._numpy_fft import (
29 | fft,
30 | fft2,
31 | fftn,
32 | hfft,
33 | ifft,
34 | ifft2,
35 | ifftn,
36 | ihfft,
37 | irfft,
38 | irfft2,
39 | irfftn,
40 | rfft,
41 | rfft2,
42 | rfftn,
43 | )
44 | from ._numpy_helper import fftfreq, fftshift, ifftshift, rfftfreq
45 |
46 | __all__ = [
47 | "fft",
48 | "ifft",
49 | "fft2",
50 | "ifft2",
51 | "fftn",
52 | "ifftn",
53 | "rfft",
54 | "irfft",
55 | "rfft2",
56 | "irfft2",
57 | "rfftn",
58 | "irfftn",
59 | "hfft",
60 | "ihfft",
61 | "fftshift",
62 | "fftfreq",
63 | "rfftfreq",
64 | "ifftshift",
65 | ]
66 |
--------------------------------------------------------------------------------
/.github/workflows/build-with-clang.yml:
--------------------------------------------------------------------------------
1 | name: Build project with IntelLLVM clang compiler
2 |
3 | on:
4 | pull_request:
5 | push:
6 | branches: [master]
7 |
8 | permissions: read-all
9 |
10 | jobs:
11 | build-with-clang:
12 | runs-on: ubuntu-latest
13 |
14 | strategy:
15 | matrix:
16 | python: ["3.10", "3.11", "3.12", "3.13"]
17 | numpy_version: ["numpy'>=2'"]
18 |
19 | env:
20 | ONEAPI_ROOT: /opt/intel/oneapi
21 |
22 | defaults:
23 | run:
24 | shell: bash -el {0}
25 |
26 | steps:
27 | - name: Cancel Previous Runs
28 | uses: styfle/cancel-workflow-action@85880fa0301c86cca9da44039ee3bb12d3bedbfa # 0.12.1
29 | with:
30 | access_token: ${{ github.token }}
31 |
32 | - name: Add Intel repository
33 | run: |
34 | wget https://apt.repos.intel.com/intel-gpg-keys/GPG-PUB-KEY-INTEL-SW-PRODUCTS-2023.PUB
35 | sudo apt-key add GPG-PUB-KEY-INTEL-SW-PRODUCTS-2023.PUB
36 | rm GPG-PUB-KEY-INTEL-SW-PRODUCTS-2023.PUB
37 | sudo add-apt-repository "deb https://apt.repos.intel.com/oneapi all main"
38 | sudo apt-get update
39 |
40 | - name: Install Intel OneAPI
41 | run: |
42 | sudo apt-get install intel-oneapi-compiler-dpcpp-cpp
43 | sudo apt-get install intel-oneapi-tbb
44 | sudo apt-get install intel-oneapi-mkl-devel
45 |
46 | - name: Setup Python
47 | uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0
48 | with:
49 | python-version: ${{ matrix.python }}
50 | architecture: x64
51 |
52 | - name: Checkout repo
53 | uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
54 | with:
55 | fetch-depth: 0
56 |
57 | - name: Install mkl_fft dependencies
58 | run: |
59 | pip install cython setuptools">=77"
60 | pip install ${{ matrix.numpy_version }}
61 |
62 | - name: List oneAPI folder content
63 | run: ls ${{ env.ONEAPI_ROOT }}/compiler
64 |
65 | - name: Build mkl_fft
66 | run: |
67 | source ${{ env.ONEAPI_ROOT }}/setvars.sh
68 | echo $CMPLR_ROOT
69 | export CC=$CMPLR_ROOT/bin/icx
70 | export CFLAGS="${CFLAGS} -fno-fast-math -O2"
71 | pip install -e . --no-build-isolation --no-deps --verbose
72 |
73 | - name: Run mkl_fft tests
74 | run: |
75 | source ${{ env.ONEAPI_ROOT }}/setvars.sh
76 | pip install scipy mkl-service pytest
77 | pytest -s -v --pyargs mkl_fft
78 |
--------------------------------------------------------------------------------
/mkl_fft/interfaces/scipy_fft.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # Copyright (c) 2017, Intel Corporation
3 | #
4 | # Redistribution and use in source and binary forms, with or without
5 | # modification, are permitted provided that the following conditions are met:
6 | #
7 | # * Redistributions of source code must retain the above copyright notice,
8 | # this list of conditions and the following disclaimer.
9 | # * Redistributions in binary form must reproduce the above copyright
10 | # notice, this list of conditions and the following disclaimer in the
11 | # documentation and/or other materials provided with the distribution.
12 | # * Neither the name of Intel Corporation nor the names of its contributors
13 | # may be used to endorse or promote products derived from this software
14 | # without specific prior written permission.
15 | #
16 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
20 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
22 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
23 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
24 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 |
27 | # pylint: disable=no-name-in-module
28 | from ._scipy_fft import (
29 | fft,
30 | fft2,
31 | fftfreq,
32 | fftn,
33 | fftshift,
34 | get_workers,
35 | hfft,
36 | hfft2,
37 | hfftn,
38 | ifft,
39 | ifft2,
40 | ifftn,
41 | ifftshift,
42 | ihfft,
43 | ihfft2,
44 | ihfftn,
45 | irfft,
46 | irfft2,
47 | irfftn,
48 | rfft,
49 | rfft2,
50 | rfftfreq,
51 | rfftn,
52 | set_workers,
53 | )
54 |
55 | __all__ = [
56 | "fft",
57 | "ifft",
58 | "fft2",
59 | "ifft2",
60 | "fftn",
61 | "ifftn",
62 | "rfft",
63 | "irfft",
64 | "rfft2",
65 | "irfft2",
66 | "rfftn",
67 | "irfftn",
68 | "hfft",
69 | "ihfft",
70 | "hfft2",
71 | "ihfft2",
72 | "hfftn",
73 | "ihfftn",
74 | "fftshift",
75 | "ifftshift",
76 | "fftfreq",
77 | "rfftfreq",
78 | "get_workers",
79 | "set_workers",
80 | ]
81 |
82 |
83 | __ua_domain__ = "numpy.scipy.fft"
84 |
85 |
86 | def __ua_function__(method, args, kwargs):
87 | """Fetch registered UA function."""
88 | fn = globals().get(method.__name__, None)
89 | if fn is None:
90 | return NotImplemented
91 | return fn(*args, **kwargs)
92 |
--------------------------------------------------------------------------------
/.pre-commit-config.yaml:
--------------------------------------------------------------------------------
1 | repos:
2 | - repo: https://github.com/pre-commit/pre-commit-hooks
3 | rev: v5.0.0
4 | hooks:
5 | - id: check-ast
6 | - id: check-builtin-literals
7 | - id: check-case-conflict
8 | - id: check-executables-have-shebangs
9 | - id: check-merge-conflict
10 | - id: check-toml
11 | - id: debug-statements
12 | - id: destroyed-symlinks
13 | - id: end-of-file-fixer
14 | - id: fix-byte-order-marker
15 | - id: mixed-line-ending
16 | - id: trailing-whitespace
17 |
18 | - repo: https://github.com/pre-commit/pygrep-hooks
19 | rev: v1.10.0
20 | hooks:
21 | - id: python-check-blanket-noqa
22 | - id: python-check-blanket-type-ignore
23 | - id: python-check-mock-methods
24 | - id: python-no-eval
25 | - id: python-no-log-warn
26 | - id: python-use-type-annotations
27 | - id: rst-backticks
28 | - id: rst-directive-colons
29 | - id: rst-inline-touching-normal
30 | - id: text-unicode-replacement-char
31 |
32 | - repo: https://github.com/codespell-project/codespell
33 | rev: v2.4.1
34 | hooks:
35 | - id: codespell
36 | args: ["-L", "nd"] # ignore "nd" used for n-dimensional
37 | additional_dependencies:
38 | - tomli
39 |
40 | - repo: https://github.com/psf/black
41 | rev: 25.1.0
42 | hooks:
43 | - id: black
44 | exclude: "_vendored/conv_template.py"
45 |
46 | - repo: https://github.com/pocc/pre-commit-hooks
47 | rev: v1.3.5
48 | hooks:
49 | - id: clang-format
50 | args: ["-i"]
51 |
52 | - repo: https://github.com/MarcoGorelli/cython-lint
53 | rev: v0.16.6
54 | hooks:
55 | - id: cython-lint
56 | - id: double-quote-cython-strings
57 |
58 | - repo: https://github.com/pycqa/flake8
59 | rev: 7.1.2
60 | hooks:
61 | - id: flake8
62 | args: ["--config=.flake8"]
63 | additional_dependencies:
64 | - flake8-docstrings==1.7.0
65 | - flake8-bugbear==24.4.26
66 |
67 | - repo: https://github.com/pycqa/isort
68 | rev: 6.0.1
69 | hooks:
70 | - id: isort
71 | name: isort (python)
72 | - id: isort
73 | name: isort (cython)
74 | types: [cython]
75 | - id: isort
76 | name: isort (pyi)
77 | types: [pyi]
78 |
79 | - repo: https://github.com/macisamuele/language-formatters-pre-commit-hooks
80 | rev: v2.14.0
81 | hooks:
82 | - id: pretty-format-toml
83 | args: [--autofix]
84 |
85 | - repo: local
86 | hooks:
87 | - id: pylint
88 | name: pylint
89 | entry: pylint
90 | language: system
91 | types: [python]
92 | require_serial: true
93 | args:
94 | [
95 | "-rn", # Only display messages
96 | "-sn", # Don't display the score
97 | "--errors-only",
98 | "--disable=import-error",
99 | ]
100 |
101 | - repo: https://github.com/jumanjihouse/pre-commit-hooks
102 | rev: 3.0.0
103 | hooks:
104 | - id: shellcheck
105 |
--------------------------------------------------------------------------------
/.github/workflows/openssf-scorecard.yml:
--------------------------------------------------------------------------------
1 | # This workflow uses actions that are not certified by GitHub. They are provided
2 | # by a third-party and are governed by separate terms of service, privacy
3 | # policy, and support documentation.
4 |
5 | name: Scorecard supply-chain security
6 | on:
7 | # For Branch-Protection check. Only the default branch is supported. See
8 | # https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection
9 | branch_protection_rule:
10 | # To guarantee Maintained check is occasionally updated. See
11 | # https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained
12 | schedule:
13 | - cron: '28 2 * * 1'
14 | - cron: '28 2 * * 4'
15 | push:
16 | branches: [ "master" ]
17 |
18 | # Declare default permissions as read only.
19 | permissions: read-all
20 |
21 | jobs:
22 | analysis:
23 | name: Scorecard analysis
24 | runs-on: ubuntu-latest
25 | timeout-minutes: 30
26 | permissions:
27 | # Needed to upload the results to code-scanning dashboard.
28 | security-events: write
29 | # Needed to publish results and get a badge (see publish_results below).
30 | id-token: write
31 | # Uncomment the permissions below if installing in a private repository.
32 | # contents: read
33 | # actions: read
34 |
35 | steps:
36 | - name: "Checkout code"
37 | uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
38 | with:
39 | persist-credentials: false
40 |
41 | - name: "Run analysis"
42 | uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3
43 | with:
44 | results_file: results.sarif
45 | results_format: sarif
46 | # (Optional) "write" PAT token. Uncomment the `repo_token` line below if:
47 | # - you want to enable the Branch-Protection check on a *public* repository, or
48 | # - you are installing Scorecard on a *private* repository
49 | # To create the PAT, follow the steps in https://github.com/ossf/scorecard-action#authentication-with-pat.
50 | # repo_token: ${{ secrets.SCORECARD_TOKEN }}
51 |
52 | # Public repositories:
53 | # - Publish results to OpenSSF REST API for easy access by consumers
54 | # - Allows the repository to include the Scorecard badge.
55 | # - See https://github.com/ossf/scorecard-action#publishing-results.
56 | # For private repositories:
57 | # - `publish_results` will always be set to `false`, regardless
58 | # of the value entered here.
59 | publish_results: true
60 |
61 | # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
62 | # format to the repository Actions tab.
63 | - name: "Upload artifact"
64 | uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
65 | with:
66 | name: SARIF file
67 | path: results.sarif
68 | retention-days: 14
69 |
70 | # Upload the results to GitHub's code scanning dashboard.
71 | - name: "Upload to code-scanning"
72 | uses: github/codeql-action/upload-sarif@fdbfb4d2750291e159f0156def62b853c2798ca2 # v4.31.5
73 | with:
74 | sarif_file: results.sarif
75 |
--------------------------------------------------------------------------------
/mkl_fft/tests/third_party/scipy/test_multithreading.py:
--------------------------------------------------------------------------------
1 | # This file includes tests from scipy.fft module:
2 | # https://github.com/scipy/scipy/blob/main/scipy/fft/tests/test_multithreading.py.py
3 |
4 | import multiprocessing
5 |
6 | import mkl
7 | import numpy as np
8 | import pytest
9 | from numpy.testing import assert_allclose
10 |
11 | try:
12 | import mkl_fft.interfaces.scipy_fft as fft
13 | except ImportError:
14 | pytest.skip("This test file needs scipy", allow_module_level=True)
15 |
16 |
17 | @pytest.fixture(scope="module")
18 | def x():
19 | return np.random.randn(512, 128) # Must be large enough to qualify for mt
20 |
21 |
22 | @pytest.mark.parametrize(
23 | "func",
24 | [
25 | fft.fft,
26 | fft.ifft,
27 | fft.fft2,
28 | fft.ifft2,
29 | fft.fftn,
30 | fft.ifftn,
31 | fft.rfft,
32 | fft.irfft,
33 | fft.rfft2,
34 | fft.irfft2,
35 | fft.rfftn,
36 | fft.irfftn,
37 | fft.hfft,
38 | fft.ihfft,
39 | fft.hfft2,
40 | fft.ihfft2,
41 | fft.hfftn,
42 | fft.ihfftn,
43 | # TODO: fft.dct, fft.idct, fft.dctn, fft.idctn,
44 | # TODO: fft.dst, fft.idst, fft.dstn, fft.idstn,
45 | ],
46 | )
47 | @pytest.mark.parametrize("workers", [2, -1])
48 | def test_threaded_same(x, func, workers):
49 | expected = func(x, workers=1)
50 | actual = func(x, workers=workers)
51 | assert_allclose(actual, expected)
52 |
53 |
54 | def _mt_fft(x):
55 | return fft.fft(x, workers=2)
56 |
57 |
58 | # @pytest.mark.slow
59 | def test_mixed_threads_processes(x):
60 | # Test that the fft threadpool is safe to use before & after fork
61 |
62 | expect = fft.fft(x, workers=2)
63 |
64 | with multiprocessing.Pool(2) as p:
65 | res = p.map(_mt_fft, [x for _ in range(4)])
66 |
67 | for r in res:
68 | assert_allclose(r, expect)
69 |
70 | fft.fft(x, workers=2)
71 |
72 |
73 | def test_invalid_workers(x):
74 | # cpus = os.cpu_count()
75 | threads = mkl.get_max_threads() # pylint: disable=no-member
76 | # cpus and threads are usually the same but in CI, cpus = 4 and threads = 2
77 | # SciPy uses `os.cpu_count()` to get the number of workers, while
78 | # `mkl_fft.interfaces.scipy_fft` uses `mkl.get_max_threads()`
79 |
80 | fft.ifft([1], workers=-threads)
81 |
82 | with pytest.raises(ValueError, match="workers must not be zero"):
83 | fft.fft(x, workers=0)
84 |
85 | with pytest.raises(ValueError, match="workers value out of range"):
86 | fft.ifft(x, workers=-threads - 1)
87 |
88 |
89 | def test_set_get_workers():
90 | # cpus = os.cpu_count()
91 | threads = mkl.get_max_threads() # pylint: disable=no-member
92 |
93 | # default value is max number of threads unlike stock SciPy
94 | assert fft.get_workers() == threads
95 | with fft.set_workers(4):
96 | assert fft.get_workers() == 4
97 |
98 | with fft.set_workers(-1):
99 | assert fft.get_workers() == threads
100 |
101 | assert fft.get_workers() == 4
102 |
103 | # default value is max number of threads unlike stock SciPy
104 | assert fft.get_workers() == threads
105 |
106 | with fft.set_workers(-threads):
107 | assert fft.get_workers() == 1
108 |
109 |
110 | def test_set_workers_invalid():
111 |
112 | with pytest.raises(ValueError, match="workers must not be zero"):
113 | with fft.set_workers(0):
114 | pass
115 |
116 | with pytest.raises(ValueError, match="workers value out of range"):
117 | # pylint: disable=no-member
118 | with fft.set_workers(-mkl.get_max_threads() - 1):
119 | pass
120 |
--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2025, Intel Corporation
2 | #
3 | # Redistribution and use in source and binary forms, with or without
4 | # modification, are permitted provided that the following conditions are met:
5 | #
6 | # * Redistributions of source code must retain the above copyright notice,
7 | # this list of conditions and the following disclaimer.
8 | # * Redistributions in binary form must reproduce the above copyright
9 | # notice, this list of conditions and the following disclaimer in the
10 | # documentation and/or other materials provided with the distribution.
11 | # * Neither the name of Intel Corporation nor the names of its contributors
12 | # may be used to endorse or promote products derived from this software
13 | # without specific prior written permission.
14 | #
15 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
16 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
19 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
21 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
22 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
23 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 |
26 | [build-system]
27 | build-backend = "setuptools.build_meta"
28 | requires = ["setuptools>=77", "Cython", "numpy", "mkl-devel"]
29 |
30 | [project]
31 | authors = [
32 | {name = "Intel Corporation", email = "scripting@intel.com"}
33 | ]
34 | classifiers = [
35 | "Development Status :: 5 - Production/Stable",
36 | "Intended Audience :: Science/Research",
37 | "Intended Audience :: Developers",
38 | "Programming Language :: C",
39 | "Programming Language :: Python",
40 | "Programming Language :: Python :: 3",
41 | "Programming Language :: Python :: 3.10",
42 | "Programming Language :: Python :: 3.11",
43 | "Programming Language :: Python :: 3.12",
44 | "Programming Language :: Python :: 3.13",
45 | "Programming Language :: Python :: 3.14",
46 | "Programming Language :: Python :: Implementation :: CPython",
47 | "Topic :: Software Development",
48 | "Topic :: Scientific/Engineering",
49 | "Operating System :: Microsoft :: Windows",
50 | "Operating System :: POSIX",
51 | "Operating System :: Unix"
52 | ]
53 | dependencies = ["numpy>=1.26.4", "mkl-service"]
54 | description = "MKL-based FFT transforms for NumPy arrays"
55 | dynamic = ["version"]
56 | keywords = ["DFTI", "FFT", "Fourier", "MKL"]
57 | license = "BSD-3-Clause"
58 | name = "mkl_fft"
59 | readme = {file = "README.md", content-type = "text/markdown"}
60 | requires-python = ">=3.10,<3.15"
61 |
62 | [project.optional-dependencies]
63 | scipy_interface = ["scipy>=1.10"]
64 | test = ["pytest", "scipy>=1.10"]
65 |
66 | [project.urls]
67 | Download = "http://github.com/IntelPython/mkl_fft"
68 | Homepage = "http://github.com/IntelPython/mkl_fft"
69 |
70 | [tool.black]
71 | exclude = "_vendored/conv_template.py"
72 | line-length = 80
73 |
74 | [tool.cython-lint]
75 | ignore = ['E722'] # do not use bare 'except'
76 | max-line-length = 80
77 |
78 | [tool.isort]
79 | ensure_newline_before_comments = true
80 | force_grid_wrap = 0
81 | include_trailing_comma = true
82 | line_length = 80
83 | multi_line_output = 3
84 | skip = ["_vendored/conv_template.py"]
85 | use_parentheses = true
86 |
87 | [tool.setuptools]
88 | include-package-data = true
89 | packages = ["mkl_fft", "mkl_fft.interfaces"]
90 |
91 | [tool.setuptools.dynamic]
92 | version = {attr = "mkl_fft._version.__version__"}
93 |
94 | [tool.setuptools.package-data]
95 | "mkl_fft" = ["tests/**/*.py"]
96 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2017, Intel Corporation
2 | #
3 | # Redistribution and use in source and binary forms, with or without
4 | # modification, are permitted provided that the following conditions are met:
5 | #
6 | # * Redistributions of source code must retain the above copyright notice,
7 | # this list of conditions and the following disclaimer.
8 | # * Redistributions in binary form must reproduce the above copyright
9 | # notice, this list of conditions and the following disclaimer in the
10 | # documentation and/or other materials provided with the distribution.
11 | # * Neither the name of Intel Corporation nor the names of its contributors
12 | # may be used to endorse or promote products derived from this software
13 | # without specific prior written permission.
14 | #
15 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
16 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
19 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
21 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
22 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
23 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 |
26 | import os
27 | import sys
28 | from os.path import join
29 |
30 | import Cython.Build
31 | import numpy
32 | from setuptools import Extension, setup
33 |
34 | sys.path.insert(0, os.path.dirname(__file__)) # Ensures local imports work
35 | from _vendored.conv_template import process_file as process_c_file # noqa: E402
36 |
37 |
38 | def extensions():
39 | mkl_root = os.environ.get("MKLROOT", None)
40 | if mkl_root:
41 | mkl_info = {
42 | "include_dirs": [join(mkl_root, "include")],
43 | "library_dirs": [
44 | join(mkl_root, "lib"),
45 | join(mkl_root, "lib", "intel64"),
46 | ],
47 | "libraries": ["mkl_rt"],
48 | }
49 | else:
50 | raise ValueError("MKLROOT environment variable not set.")
51 |
52 | mkl_include_dirs = mkl_info.get("include_dirs", [])
53 | mkl_library_dirs = mkl_info.get("library_dirs", [])
54 | mkl_libraries = mkl_info.get("libraries", ["mkl_rt"])
55 |
56 | mklfft_templ = join("mkl_fft", "src", "mklfft.c.src")
57 | processed_mklfft_fn = join("mkl_fft", "src", "mklfft.c")
58 | src_processed = process_c_file(mklfft_templ)
59 |
60 | with open(processed_mklfft_fn, "w") as fid:
61 | fid.write(src_processed)
62 |
63 | return [
64 | Extension(
65 | "mkl_fft._pydfti",
66 | sources=[
67 | join("mkl_fft", "_pydfti.pyx"),
68 | join("mkl_fft", "src", "mklfft.c"),
69 | ],
70 | depends=[
71 | join("mkl_fft", "src", "mklfft.h"),
72 | join("mkl_fft", "src", "multi_iter.h"),
73 | ],
74 | include_dirs=[join("mkl_fft", "src"), numpy.get_include()]
75 | + mkl_include_dirs,
76 | libraries=mkl_libraries,
77 | library_dirs=mkl_library_dirs,
78 | extra_compile_args=[
79 | "-DNDEBUG",
80 | # '-ggdb', '-O0', '-Wall', '-Wextra', '-DDEBUG',
81 | ],
82 | define_macros=[
83 | ("NPY_NO_DEPRECATED_API", None),
84 | ("PY_ARRAY_UNIQUE_SYMBOL", "mkl_fft_ext"),
85 | ],
86 | )
87 | ]
88 |
89 |
90 | setup(
91 | cmdclass={"build_ext": Cython.Build.build_ext},
92 | ext_modules=extensions(),
93 | zip_safe=False,
94 | )
95 |
--------------------------------------------------------------------------------
/mkl_fft/interfaces/_float_utils.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # Copyright (c) 2017, Intel Corporation
3 | #
4 | # Redistribution and use in source and binary forms, with or without
5 | # modification, are permitted provided that the following conditions are met:
6 | #
7 | # * Redistributions of source code must retain the above copyright notice,
8 | # this list of conditions and the following disclaimer.
9 | # * Redistributions in binary form must reproduce the above copyright
10 | # notice, this list of conditions and the following disclaimer in the
11 | # documentation and/or other materials provided with the distribution.
12 | # * Neither the name of Intel Corporation nor the names of its contributors
13 | # may be used to endorse or promote products derived from this software
14 | # without specific prior written permission.
15 | #
16 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
20 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
22 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
23 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
24 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 |
27 | import numpy as np
28 |
29 |
30 | def _upcast_float16_array(x):
31 | """
32 | Used in _scipy_fftpack to upcast float16 to float32,
33 | instead of float64, as mkl_fft would do
34 | """
35 | if hasattr(x, "dtype"):
36 | xdt = x.dtype
37 | if xdt == np.half:
38 | # no half-precision routines, so convert to single precision
39 | return np.asarray(x, dtype=np.float32)
40 | if xdt == np.longdouble and not xdt == np.float64:
41 | raise ValueError("type %s is not supported" % xdt)
42 | if not isinstance(x, np.ndarray):
43 | _x = np.asarray(x)
44 | xdt = _x.dtype
45 | if xdt == np.half:
46 | # no half-precision routines, so convert to single precision
47 | return np.asarray(_x, dtype=np.float32)
48 | if xdt == np.longdouble and not xdt == np.float64:
49 | raise ValueError("type %s is not supported" % xdt)
50 | return _x
51 | return x
52 |
53 |
54 | def _downcast_float128_array(x):
55 | """
56 | Used in _numpy_fft to unsafely downcast float128/complex256 to
57 | complex128, instead of raising an error
58 | """
59 | if hasattr(x, "dtype"):
60 | xdt = x.dtype
61 | if xdt == np.longdouble and not xdt == np.float64:
62 | return np.asarray(x, dtype=np.float64)
63 | elif xdt == np.clongdouble and not xdt == np.complex128:
64 | return np.asarray(x, dtype=np.complex128)
65 | if not isinstance(x, np.ndarray):
66 | _x = np.asarray(x)
67 | xdt = _x.dtype
68 | if xdt == np.longdouble and not xdt == np.float64:
69 | return np.asarray(x, dtype=np.float64)
70 | elif xdt == np.clongdouble and not xdt == np.complex128:
71 | return np.asarray(x, dtype=np.complex128)
72 | return _x
73 | return x
74 |
75 |
76 | def _supported_array_or_not_implemented(x):
77 | """
78 | Used in _scipy_fft to convert array to float32, float64, complex64,
79 | or complex128 type or raise NotImplementedError
80 | """
81 | _x = np.asarray(x)
82 | black_list = [np.half]
83 | if hasattr(np, "float128"):
84 | black_list.append(np.float128)
85 | if hasattr(np, "complex256"):
86 | black_list.append(np.complex256)
87 | if _x.dtype in black_list:
88 | raise NotImplementedError
89 | return _x
90 |
--------------------------------------------------------------------------------
/mkl_fft/_mkl_fft.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # Copyright (c) 2025, Intel Corporation
3 | #
4 | # Redistribution and use in source and binary forms, with or without
5 | # modification, are permitted provided that the following conditions are met:
6 | #
7 | # * Redistributions of source code must retain the above copyright notice,
8 | # this list of conditions and the following disclaimer.
9 | # * Redistributions in binary form must reproduce the above copyright
10 | # notice, this list of conditions and the following disclaimer in the
11 | # documentation and/or other materials provided with the distribution.
12 | # * Neither the name of Intel Corporation nor the names of its contributors
13 | # may be used to endorse or promote products derived from this software
14 | # without specific prior written permission.
15 | #
16 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
20 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
22 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
23 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
24 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 |
27 | from ._fft_utils import (
28 | _c2c_fftnd_impl,
29 | _c2r_fftnd_impl,
30 | _compute_fwd_scale,
31 | _r2c_fftnd_impl,
32 | )
33 |
34 | # pylint: disable=no-name-in-module
35 | from ._pydfti import _c2c_fft1d_impl, _c2r_fft1d_impl, _r2c_fft1d_impl
36 |
37 | __all__ = [
38 | "fft",
39 | "ifft",
40 | "fft2",
41 | "ifft2",
42 | "fftn",
43 | "ifftn",
44 | "rfft",
45 | "irfft",
46 | "rfft2",
47 | "irfft2",
48 | "rfftn",
49 | "irfftn",
50 | ]
51 |
52 |
53 | def fft(x, n=None, axis=-1, norm=None, out=None):
54 | fsc = _compute_fwd_scale(norm, n, x.shape[axis])
55 | return _c2c_fft1d_impl(x, n=n, axis=axis, out=out, direction=+1, fsc=fsc)
56 |
57 |
58 | def ifft(x, n=None, axis=-1, norm=None, out=None):
59 | fsc = _compute_fwd_scale(norm, n, x.shape[axis])
60 | return _c2c_fft1d_impl(x, n=n, axis=axis, out=out, direction=-1, fsc=fsc)
61 |
62 |
63 | def fft2(x, s=None, axes=(-2, -1), norm=None, out=None):
64 | return fftn(x, s=s, axes=axes, norm=norm, out=out)
65 |
66 |
67 | def ifft2(x, s=None, axes=(-2, -1), norm=None, out=None):
68 | return ifftn(x, s=s, axes=axes, norm=norm, out=out)
69 |
70 |
71 | def fftn(x, s=None, axes=None, norm=None, out=None):
72 | fsc = _compute_fwd_scale(norm, s, x.shape)
73 | return _c2c_fftnd_impl(x, s=s, axes=axes, out=out, direction=+1, fsc=fsc)
74 |
75 |
76 | def ifftn(x, s=None, axes=None, norm=None, out=None):
77 | fsc = _compute_fwd_scale(norm, s, x.shape)
78 | return _c2c_fftnd_impl(x, s=s, axes=axes, out=out, direction=-1, fsc=fsc)
79 |
80 |
81 | def rfft(x, n=None, axis=-1, norm=None, out=None):
82 | fsc = _compute_fwd_scale(norm, n, x.shape[axis])
83 | return _r2c_fft1d_impl(x, n=n, axis=axis, out=out, fsc=fsc)
84 |
85 |
86 | def irfft(x, n=None, axis=-1, norm=None, out=None):
87 | fsc = _compute_fwd_scale(norm, n, 2 * (x.shape[axis] - 1))
88 | return _c2r_fft1d_impl(x, n=n, axis=axis, out=out, fsc=fsc)
89 |
90 |
91 | def rfft2(x, s=None, axes=(-2, -1), norm=None, out=None):
92 | return rfftn(x, s=s, axes=axes, norm=norm, out=out)
93 |
94 |
95 | def irfft2(x, s=None, axes=(-2, -1), norm=None, out=None):
96 | return irfftn(x, s=s, axes=axes, norm=norm, out=out)
97 |
98 |
99 | def rfftn(x, s=None, axes=None, norm=None, out=None):
100 | fsc = _compute_fwd_scale(norm, s, x.shape)
101 | return _r2c_fftnd_impl(x, s=s, axes=axes, out=out, fsc=fsc)
102 |
103 |
104 | def irfftn(x, s=None, axes=None, norm=None, out=None):
105 | fsc = _compute_fwd_scale(norm, s, x.shape)
106 | return _c2r_fftnd_impl(x, s=s, axes=axes, out=out, fsc=fsc)
107 |
--------------------------------------------------------------------------------
/mkl_fft/interfaces/_numpy_helper.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # Copyright (c) 2017, Intel Corporation
3 | #
4 | # Redistribution and use in source and binary forms, with or without
5 | # modification, are permitted provided that the following conditions are met:
6 | #
7 | # * Redistributions of source code must retain the above copyright notice,
8 | # this list of conditions and the following disclaimer.
9 | # * Redistributions in binary form must reproduce the above copyright
10 | # notice, this list of conditions and the following disclaimer in the
11 | # documentation and/or other materials provided with the distribution.
12 | # * Neither the name of Intel Corporation nor the names of its contributors
13 | # may be used to endorse or promote products derived from this software
14 | # without specific prior written permission.
15 | #
16 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
20 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
22 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
23 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
24 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 |
27 | """
28 | FFT helper functions copied from `numpy.fft` (with some modification) to
29 | prevent circular dependencies when patching NumPy.
30 | """
31 |
32 | import numpy as np
33 |
34 | __all__ = ["fftshift", "ifftshift", "fftfreq", "rfftfreq"]
35 |
36 |
37 | def fftshift(x, axes=None):
38 | """
39 | Shift the zero-frequency component to the center of the spectrum.
40 |
41 | For full documentation refer to `numpy.fft.fftshift`.
42 |
43 | """
44 | x = np.asarray(x)
45 | if axes is None:
46 | axes = tuple(range(x.ndim))
47 | shift = [dim // 2 for dim in x.shape]
48 | elif isinstance(axes, (int, np.integer)):
49 | shift = x.shape[axes] // 2
50 | else:
51 | shift = [x.shape[ax] // 2 for ax in axes]
52 |
53 | return np.roll(x, shift, axes)
54 |
55 |
56 | def ifftshift(x, axes=None):
57 | """
58 | The inverse of `fftshift`. Although identical for even-length `x`, the
59 | functions differ by one sample for odd-length `x`.
60 |
61 | For full documentation refer to `numpy.fft.ifftshift`.
62 |
63 | """
64 | x = np.asarray(x)
65 | if axes is None:
66 | axes = tuple(range(x.ndim))
67 | shift = [-(dim // 2) for dim in x.shape]
68 | elif isinstance(axes, (int, np.integer)):
69 | shift = -(x.shape[axes] // 2)
70 | else:
71 | shift = [-(x.shape[ax] // 2) for ax in axes]
72 |
73 | return np.roll(x, shift, axes)
74 |
75 |
76 | def fftfreq(n, d=1.0, device=None):
77 | """
78 | Return the Discrete Fourier Transform sample frequencies.
79 |
80 | For full documentation refer to `numpy.fft.fftfreq`.
81 |
82 | """
83 | if not isinstance(n, (int, np.integer)):
84 | raise ValueError("n should be an integer")
85 | val = 1.0 / (n * d)
86 | # pylint: disable=unexpected-keyword-arg
87 | results = np.empty(n, int, device=device)
88 | # pylint: enable=unexpected-keyword-arg
89 | N = (n - 1) // 2 + 1
90 | # pylint: disable=unexpected-keyword-arg
91 | p1 = np.arange(0, N, dtype=int, device=device)
92 | # pylint: enable=unexpected-keyword-arg
93 | results[:N] = p1
94 | # pylint: disable=unexpected-keyword-arg
95 | p2 = np.arange(-(n // 2), 0, dtype=int, device=device)
96 | # pylint: enable=unexpected-keyword-arg
97 | results[N:] = p2
98 | return results * val
99 |
100 |
101 | def rfftfreq(n, d=1.0, device=None):
102 | """
103 | Return the Discrete Fourier Transform sample frequencies (for usage with
104 | `rfft`, `irfft`).
105 |
106 | For full documentation refer to `numpy.fft.rfftfreq`.
107 |
108 | """
109 | if not isinstance(n, (int, np.integer)):
110 | raise ValueError("n should be an integer")
111 | val = 1.0 / (n * d)
112 | N = n // 2 + 1
113 | # pylint: disable=unexpected-keyword-arg
114 | results = np.arange(0, N, dtype=int, device=device)
115 | # pylint: enable=unexpected-keyword-arg
116 | return results * val
117 |
--------------------------------------------------------------------------------
/mkl_fft/interfaces/README.md:
--------------------------------------------------------------------------------
1 | # Interfaces
2 | The `mkl_fft` package provides interfaces that serve as drop-in replacements for equivalent functions in NumPy and SciPy.
3 |
4 | ---
5 |
6 | ## NumPy interface - `mkl_fft.interfaces.numpy_fft`
7 |
8 | This interface is a drop-in replacement for the [`numpy.fft`](https://numpy.org/devdocs/reference/routines.fft.html) module and includes **all** the functions available there:
9 |
10 | * complex-to-complex FFTs: `fft`, `ifft`, `fft2`, `ifft2`, `fftn`, `ifftn`.
11 |
12 | * real-to-complex and complex-to-real FFTs: `rfft`, `irfft`, `rfft2`, `irfft2`, `rfftn`, `irfftn`.
13 |
14 | * Hermitian FFTs: `hfft`, `ihfft`.
15 |
16 | * Helper routines: `fftfreq`, `rfftfreq`, `fftshift`, `ifftshift`. These routines serve as a fallback to the NumPy implementation and are included for completeness.
17 |
18 | The following example shows how to use this interface for calculating a 1D FFT.
19 |
20 | ```python
21 | import numpy
22 | import mkl_fft.interfaces.numpy_fft as numpy_fft
23 |
24 | a = numpy.random.randn(10) + 1j*numpy.random.randn(10)
25 |
26 | mkl_res = numpy_fft.fft(a)
27 | np_res = numpy.fft.fft(a)
28 | numpy.allclose(mkl_res, np_res)
29 | # True
30 | ```
31 |
32 | ---
33 |
34 | ## SciPy interface - `mkl_fft.interfaces.scipy_fft`
35 | This interface is a drop-in replacement for the [`scipy.fft`](https://scipy.github.io/devdocs/reference/fft.html) module and includes **subset** of the functions available there:
36 |
37 | * complex-to-complex FFTs: `fft`, `ifft`, `fft2`, `ifft2`, `fftn`, `ifftn`.
38 |
39 | * real-to-complex and complex-to-real FFTs: `rfft`, `irfft`, `rfft2`, `irfft2`, `rfftn`, `irfftn`.
40 |
41 | * Hermitian FFTs: `hfft`, `ihfft`, `hfft2`, `ihfft2`, `hfftn`, `ihfftn`.
42 |
43 | * Helper functions: `fftshift`, `ifftshift`, `fftfreq`, `rfftfreq`, `set_workers`, `get_workers`. All of these functions, except for `set_workers` and `get_workers`, serve as a fallback to the SciPy implementation and are included for completeness.
44 |
45 | Note that in computing FFTs, the default value of `workers` parameter is the maximum number of threads available unlike the default behavior of SciPy where only one thread is used.
46 |
47 | The following example shows how to use this interface for calculating a 1D FFT.
48 |
49 | ```python
50 | import numpy, scipy
51 | import mkl_fft.interfaces.scipy_fft as scipy_fft
52 |
53 | a = numpy.random.randn(10) + 1j * numpy.random.randn(10)
54 |
55 | mkl_res = scipy_fft.fft(a)
56 | sp_res = scipy.fft.fft(a)
57 | numpy.allclose(mkl_res, sp_res)
58 | # True
59 | ```
60 |
61 | ---
62 |
63 | ### Registering `mkl_fft` as the FFT backend for SciPy
64 |
65 | `mkl_fft.interfaces.scipy_fft` can be registered as a backend for SciPy. To learn more about how to control the backend [see the SciPy documentation](https://docs.scipy.org/doc/scipy/reference/fft.html#backend-control). The following example shows how to set `mkl_fft` as the FFT backend for SciPy using a context manager.
66 |
67 | ```python
68 | import numpy, scipy, mkl
69 | import mkl_fft.interfaces.scipy_fft as mkl_backend
70 | x = numpy.random.randn(56).reshape(7, 8)
71 |
72 | # turning on verbosity to show `mkl_fft` is used as the SciPy backend
73 | mkl.verbose(1)
74 | # True
75 |
76 | with scipy.fft.set_backend(mkl_backend, only=True):
77 | mkl_res = scipy.fft.fft2(x, workers=4) # Calls `mkl_fft` backend
78 | # MKL_VERBOSE oneMKL 2024.0 Update 2 Patch 2 Product build 20240823 for Intel(R) 64 architecture Intel(R) Advanced Vector Extensions 512 (Intel(R) AVX-512) with support for INT8, BF16, FP16 (limited) instructions, and Intel(R) Advanced Matrix Extensions (Intel(R) AMX) with INT8 and BF16, Lnx 2.00GHz intel_thread
79 | # MKL_VERBOSE FFT(drfo7:8:8x8:1:1,input_strides:{0,8,1},output_strides:{0,8,1},bScale:0.0178571,tLim:1,unaligned_output,desc:0x557affb60d40) 36.11us CNR:OFF Dyn:1 FastMM:1 TID:0 NThr:4
80 |
81 | sp_res = scipy.fft.fft2(x, workers=4) # Calls default SciPy backend
82 | numpy.allclose(mkl_res, sp_res)
83 | # True
84 | ```
85 |
86 | The previous example was only for illustration purposes. In practice, there is no added benefit to defining a backend and calculating FFT, since this functionality is already accessible through the scipy interface, as shown earlier.
87 | To demonstrate the advantages of using `mkl_fft` as a backend, the following example compares the timing of `scipy.signal.fftconvolve` using the default SciPy backend versus the `mkl_fft` backend on an Intel® Xeon® CPU.
88 |
89 | ```python
90 | import numpy, scipy
91 | import mkl_fft.interfaces.scipy_fft as mkl_backend
92 | import timeit
93 | shape = (4096, 2048)
94 | a = numpy.random.randn(*shape) + 1j*numpy.random.randn(*shape)
95 | b = numpy.random.randn(*shape) + 1j*numpy.random.randn(*shape)
96 |
97 | t1 = timeit.timeit(lambda: scipy.signal.fftconvolve(a, b), number=10)
98 | print(f"Time with scipy.fft default backend: {t1:.1f} seconds")
99 | # Time with scipy.fft default backend: 51.9 seconds
100 |
101 | with scipy.fft.set_backend(mkl_backend, only=True):
102 | t2 = timeit.timeit(lambda: scipy.signal.fftconvolve(a, b), number=10)
103 |
104 | print(f"Time with oneMKL FFT backend installed: {t2:.1f} seconds")
105 | # Time with MKL FFT backend installed: 9.1 seconds
106 | ```
107 |
108 | In the following example, we use `set_worker` to control the number of threads when `mkl_fft` is being used as a backend for SciPy.
109 |
110 | ```python
111 | import numpy, mkl, scipy
112 | import mkl_fft.interfaces.scipy_fft as mkl_fft
113 | a = numpy.random.randn(128, 64) + 1j*numpy.random.randn(128, 64)
114 | scipy.fft.set_global_backend(mkl_fft) # set mkl_fft as global backend
115 |
116 | mkl.verbose(1)
117 | # True
118 | mkl.get_max_threads()
119 | # 112
120 | y = scipy.signal.fftconvolve(a, a) # Note that Nthr:112
121 | # MKL_VERBOSE FFT(dcbo256x128,input_strides:{0,128,1},output_strides:{0,128,1},bScale:3.05176e-05,tLim:56,unaligned_input,unaligned_output,desc:0x563aefe86180) 165.02us CNR:OFF Dyn:1 FastMM:1 TID:0 NThr:112
122 |
123 | with mkl_fft.set_workers(4):
124 | y = scipy.signal.fftconvolve(a, a) # Note that Nthr:4
125 | # MKL_VERBOSE FFT(dcbo256x128,input_strides:{0,128,1},output_strides:{0,128,1},bScale:3.05176e-05,tLim:4,unaligned_output,desc:0x563aefe86180) 187.37us CNR:OFF Dyn:1 FastMM:1 TID:0 NThr:4
126 | ```
127 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://github.com/IntelPython/mkl_fft/actions/workflows/conda-package.yml)
2 | [](https://github.com/IntelPython/mkl_fft/actions/workflows/build_pip.yaml)
3 | [](https://github.com/IntelPython/mkl_fft/actions/workflows/conda-package-cf.yml)
4 | [](https://securityscorecards.dev/viewer/?uri=github.com/IntelPython/mkl_fft)
5 |
6 | ## `mkl_fft` -- a NumPy-based Python interface to Intel® oneAPI Math Kernel Library (oneMKL) Fourier Transform Functions
7 |
8 | # Introduction
9 | `mkl_fft` is part of [Intel® Distribution for Python*](https://www.intel.com/content/www/us/en/developer/tools/oneapi/distribution-for-python.html) optimizations to NumPy.
10 | It offers a thin layered python interface to the [Intel® oneAPI Math Kernel Library (oneMKL) Fourier Transform Functions](https://www.intel.com/content/www/us/en/docs/onemkl/developer-reference-c/2025-2/fourier-transform-functions.html) that allows efficient access to computing a discrete Fourier transform through the fast Fourier transform (FFT) algorithm. As a result, its performance is close to the performance of native C/Intel® oneMKL. The optimizations are provided for real and complex data types in both single and double precisions for in-place and out-of-place modes of operation. For analyzing the performance use [FFT benchmarks](https://github.com/intelpython/fft_benchmark).
11 |
12 | Thanks to Intel® oneMKL’s flexibility in its supports for arbitrarily strided input and output arrays both one-dimensional and multi-dimensional FFTs along distinct axes can be performed directly, without the need to copy the input into a contiguous array first. Furthermore, input strides can be arbitrary, including negative or zero, as long as strides remain an integer multiple of array’s item size, otherwise a copy will be made.
13 |
14 | More details can be found in ["Accelerating Scientific Python with Intel Optimizations"](https://proceedings.scipy.org/articles/shinma-7f4c6e7-00f) from Proceedings of the 16th Python in Science Conference (SciPy 2017).
15 |
16 | ---
17 | # Installation
18 | `mkl_fft` can be installed into conda environment from Intel's channel using:
19 |
20 | ```
21 | conda install -c https://software.repos.intel.com/python/conda mkl_fft
22 | ```
23 |
24 | or from conda-forge channel:
25 |
26 | ```
27 | conda install -c conda-forge mkl_fft
28 | ```
29 |
30 | To install `mkl_fft` PyPI package please use the following command:
31 |
32 | ```
33 | python -m pip install --index-url https://software.repos.intel.com/python/pypi --extra-index-url https://pypi.org/simple mkl_fft
34 | ```
35 |
36 | If command above installs NumPy package from the PyPI, please use following command to install Intel optimized NumPy wheel package from Intel PyPI Cloud:
37 |
38 | ```
39 | python -m pip install --index-url https://software.repos.intel.com/python/pypi --extra-index-url https://pypi.org/simple mkl_fft numpy==
40 | ```
41 |
42 | where `` should be the latest version from https://software.repos.intel.com/python/conda/.
43 |
44 | ---
45 | # How to use?
46 | ## `mkl_fft.interfaces` module
47 | The recommended way to use `mkl_fft` package is through `mkl_fft.interfaces` module. These interfaces act as drop-in replacements for equivalent functions in NumPy and SciPy. Learn more about these interfaces [here](https://github.com/IntelPython/mkl_fft/blob/master/mkl_fft/interfaces/README.md).
48 |
49 | ## `mkl_fft` package
50 | While using the interfaces module is the recommended way to leverage `mk_fft`, one can also use `mkl_fft` directly with the following FFT functions:
51 |
52 | ### complex-to-complex (c2c) transforms:
53 |
54 | `fft(x, n=None, axis=-1, norm=None, out=None)` - 1D FFT, similar to `scipy.fft.fft`
55 |
56 | `fft2(x, s=None, axes=(-2, -1), norm=None, out=None)` - 2D FFT, similar to `scipy.fft.fft2`
57 |
58 | `fftn(x, s=None, axes=None, norm=None, out=None)` - ND FFT, similar to `scipy.fft.fftn`
59 |
60 | and similar inverse FFT (`ifft*`) functions.
61 |
62 | ### real-to-complex (r2c) and complex-to-real (c2r) transforms:
63 |
64 | `rfft(x, n=None, axis=-1, norm=None, out=None)` - r2c 1D FFT, similar to `numpy.fft.rfft`
65 |
66 | `rfft2(x, s=None, axes=(-2, -1), norm=None, out=None)` - r2c 2D FFT, similar to `numpy.fft.rfft2`
67 |
68 | `rfftn(x, s=None, axes=None, norm=None, out=None)` - r2c ND FFT, similar to `numpy.fft.rfftn`
69 |
70 | and similar inverse c2r FFT (`irfft*`) functions.
71 |
72 | The following example shows how to use `mkl_fft` for calculating a 1D FFT.
73 |
74 | ```python
75 | import numpy, mkl_fft
76 | a = numpy.random.randn(10) + 1j*numpy.random.randn(10)
77 |
78 | mkl_res = mkl_fft.fft(a)
79 | np_res = numpy.fft.fft(a)
80 | numpy.allclose(mkl_res, np_res)
81 | # True
82 | ```
83 |
84 | ---
85 | # Building from source
86 |
87 | To build `mkl_fft` from sources on Linux with Intel® oneMKL:
88 | - create a virtual environment: `python3 -m venv fft_env`
89 | - activate the environment: `source fft_env/bin/activate`
90 | - install a recent version of oneMKL, if necessary
91 | - execute `source /path_to_oneapi/mkl/latest/env/vars.sh`
92 | - `git clone https://github.com/IntelPython/mkl_fft.git mkl_fft`
93 | - `cd mkl_fft`
94 | - `python -m pip install .`
95 | - `pip install scipy` (optional: for using `mkl_fft.interface.scipy_fft` module)
96 | - `cd ..`
97 | - `python -c "import mkl_fft"`
98 |
99 | To build `mkl_fft` from sources on Linux with conda follow these steps:
100 | - `conda create -n fft_env python=3.12 mkl-devel`
101 | - `conda activate fft_env`
102 | - `export MKLROOT=$CONDA_PREFIX`
103 | - `git clone https://github.com/IntelPython/mkl_fft.git mkl_fft`
104 | - `cd mkl_fft`
105 | - `python -m pip install .`
106 | - `conda install scipy` (optional: for using `mkl_fft.interface.scipy_fft` module)
107 | - `cd ..`
108 | - `python -c "import mkl_fft"`
109 |
--------------------------------------------------------------------------------
/mkl_fft/src/mklfft.h:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) 2017, Intel Corporation
3 |
4 | Redistribution and use in source and binary forms, with or without
5 | modification, are permitted provided that the following conditions are met:
6 |
7 | * Redistributions of source code must retain the above copyright notice,
8 | this list of conditions and the following disclaimer.
9 | * Redistributions in binary form must reproduce the above copyright
10 | notice, this list of conditions and the following disclaimer in the
11 | documentation and/or other materials provided with the distribution.
12 | * Neither the name of Intel Corporation nor the names of its contributors
13 | may be used to endorse or promote products derived from this software
14 | without specific prior written permission.
15 |
16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 | */
27 | #include "mkl.h"
28 | #include "numpy/arrayobject.h"
29 |
30 | typedef struct DftiCache {
31 | DFTI_DESCRIPTOR_HANDLE hand;
32 | int initialized;
33 | } DftiCache;
34 |
35 | extern int _free_dfti_cache(DftiCache *);
36 |
37 | /* Complex input, in-place */
38 | extern int cdouble_mkl_fft1d_in(PyArrayObject *, npy_intp, int, double,
39 | DftiCache *);
40 | extern int cfloat_mkl_fft1d_in(PyArrayObject *, npy_intp, int, double,
41 | DftiCache *);
42 | extern int cdouble_mkl_ifft1d_in(PyArrayObject *, npy_intp, int, double,
43 | DftiCache *);
44 | extern int cfloat_mkl_ifft1d_in(PyArrayObject *, npy_intp, int, double,
45 | DftiCache *);
46 |
47 | /* Complex input/output, out-of-place */
48 | extern int cfloat_cfloat_mkl_fft1d_out(PyArrayObject *, npy_intp, int,
49 | PyArrayObject *, double, DftiCache *);
50 | extern int cdouble_cdouble_mkl_fft1d_out(PyArrayObject *, npy_intp, int,
51 | PyArrayObject *, double, DftiCache *);
52 | extern int cfloat_cfloat_mkl_ifft1d_out(PyArrayObject *, npy_intp, int,
53 | PyArrayObject *, double, DftiCache *);
54 | extern int cdouble_cdouble_mkl_ifft1d_out(PyArrayObject *, npy_intp, int,
55 | PyArrayObject *, double, DftiCache *);
56 |
57 | /* Real input, complex output, out-of-place */
58 | extern int float_cfloat_mkl_fft1d_out(PyArrayObject *, npy_intp, int,
59 | PyArrayObject *, int, double,
60 | DftiCache *);
61 | extern int double_cdouble_mkl_fft1d_out(PyArrayObject *, npy_intp, int,
62 | PyArrayObject *, int, double,
63 | DftiCache *);
64 | extern int float_cfloat_mkl_ifft1d_out(PyArrayObject *, npy_intp, int,
65 | PyArrayObject *, int, double,
66 | DftiCache *);
67 | extern int double_cdouble_mkl_ifft1d_out(PyArrayObject *, npy_intp, int,
68 | PyArrayObject *, int, double,
69 | DftiCache *);
70 |
71 | /* Real input, real output, in-place */
72 | extern int float_mkl_rfft_in(PyArrayObject *, npy_intp, int, double,
73 | DftiCache *);
74 | extern int float_mkl_irfft_in(PyArrayObject *, npy_intp, int, double,
75 | DftiCache *);
76 |
77 | extern int double_mkl_rfft_in(PyArrayObject *, npy_intp, int, double,
78 | DftiCache *);
79 | extern int double_mkl_irfft_in(PyArrayObject *, npy_intp, int, double,
80 | DftiCache *);
81 |
82 | /* Real input, real output, out-of-place */
83 | extern int float_float_mkl_rfft_out(PyArrayObject *, npy_intp, int,
84 | PyArrayObject *, double, DftiCache *);
85 | extern int float_float_mkl_irfft_out(PyArrayObject *, npy_intp, int,
86 | PyArrayObject *, double, DftiCache *);
87 |
88 | extern int double_double_mkl_rfft_out(PyArrayObject *, npy_intp, int,
89 | PyArrayObject *, double, DftiCache *);
90 | extern int double_double_mkl_irfft_out(PyArrayObject *, npy_intp, int,
91 | PyArrayObject *, double, DftiCache *);
92 |
93 | /* Complex input. real output, out-of-place */
94 | extern int cdouble_double_mkl_irfft_out(PyArrayObject *, npy_intp, int,
95 | PyArrayObject *, double, DftiCache *);
96 | extern int cfloat_float_mkl_irfft_out(PyArrayObject *, npy_intp, int,
97 | PyArrayObject *, double, DftiCache *);
98 |
99 | /* Complex, ND, in-place */
100 | extern int cdouble_cdouble_mkl_fftnd_in(PyArrayObject *, double);
101 | extern int cdouble_cdouble_mkl_ifftnd_in(PyArrayObject *, double);
102 | extern int cfloat_cfloat_mkl_fftnd_in(PyArrayObject *, double);
103 | extern int cfloat_cfloat_mkl_ifftnd_in(PyArrayObject *, double);
104 |
105 | extern int cdouble_cdouble_mkl_fftnd_out(PyArrayObject *, PyArrayObject *,
106 | double);
107 | extern int cdouble_cdouble_mkl_ifftnd_out(PyArrayObject *, PyArrayObject *,
108 | double);
109 | extern int cfloat_cfloat_mkl_fftnd_out(PyArrayObject *, PyArrayObject *,
110 | double);
111 | extern int cfloat_cfloat_mkl_ifftnd_out(PyArrayObject *, PyArrayObject *,
112 | double);
113 |
114 | extern int float_cfloat_mkl_fftnd_out(PyArrayObject *, PyArrayObject *, double);
115 | extern int float_cfloat_mkl_ifftnd_out(PyArrayObject *, PyArrayObject *,
116 | double);
117 | extern int double_cdouble_mkl_fftnd_out(PyArrayObject *, PyArrayObject *,
118 | double);
119 | extern int double_cdouble_mkl_ifftnd_out(PyArrayObject *, PyArrayObject *,
120 | double);
121 |
122 | extern char *mkl_dfti_error(MKL_LONG);
123 |
--------------------------------------------------------------------------------
/mkl_fft/src/multi_iter.h:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) 2017, Intel Corporation
3 |
4 | Redistribution and use in source and binary forms, with or without
5 | modification, are permitted provided that the following conditions are met:
6 |
7 | * Redistributions of source code must retain the above copyright notice,
8 | this list of conditions and the following disclaimer.
9 | * Redistributions in binary form must reproduce the above copyright
10 | notice, this list of conditions and the following disclaimer in the
11 | documentation and/or other materials provided with the distribution.
12 | * Neither the name of Intel Corporation nor the names of its contributors
13 | may be used to endorse or promote products derived from this software
14 | without specific prior written permission.
15 |
16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 | */
27 |
28 | #ifndef MULTI_ITER_H
29 | #define MULTI_ITER_H
30 |
31 | #include "mkl.h"
32 | #include "numpy/arrayobject.h"
33 | #include
34 | #include
35 |
36 | typedef struct multi_iter_t {
37 | npy_intp *shape; // shape of the tensor
38 | npy_intp *ind; // multi-index
39 | int rank; // tensor rank, length of shape and of ind
40 | char done; // boolean variable: True when end of iterator has been reached
41 | } multi_iter_t;
42 |
43 | typedef struct multi_iter_masked_t {
44 | npy_intp *shape; // shape of the tensor
45 | npy_intp *ind; // multi-index
46 | int *mask; // list of indexes to iterate over
47 | int rank; // tensor rank, length of shape and of ind
48 | int mask_len;
49 | char done; // boolean variable: True when end of iterator has been reached
50 | } multi_iter_masked_t;
51 |
52 | #define MultiIter_Index(mit) ((mit).ind)
53 | #define MultiIter_Shape(mit) ((mit).shape)
54 | #define MultiIter_Done(mit) ((mit).done)
55 | #define MultiIter_Rank(mit) ((mit).rank)
56 | #define MultiIter_Mask(mit) ((mit).mask)
57 | #define MultiIter_MaskLength(mit) ((mit).mask_len)
58 |
59 | #define MultiIter_IndexElem(mit, i) ((mit).ind)[(i)]
60 | #define MultiIter_ShapeElem(mit, i) ((mit).shape)[(i)]
61 | #define MultiIter_MaskElem(mit, i) ((mit).mask)[(i)]
62 |
63 | /*
64 | void multi_iter_new(multi_iter_t*, npy_intp*, int);
65 | void multi_iter_masked_new(multi_iter_masked_t*, npy_intp*, int, int*, int);
66 |
67 | void multi_iter_free(multi_iter_t*);
68 | int multi_iter_next(multi_iter_t*);
69 |
70 | void multi_iter_masked_free(multi_iter_masked_t*);
71 | int multi_iter_masked_next(multi_iter_masked_t*);
72 | */
73 |
74 | static NPY_INLINE void multi_iter_new(multi_iter_t *mi, npy_intp shape[],
75 | int rank) {
76 | int i;
77 | char d = 0;
78 |
79 | assert(rank > 0);
80 |
81 | MultiIter_Index(*mi) = (npy_intp *)mkl_calloc(rank, sizeof(npy_intp), 64);
82 | MultiIter_Shape(*mi) = (npy_intp *)mkl_malloc(rank * sizeof(npy_intp), 64);
83 | memcpy(MultiIter_Shape(*mi), shape, rank * sizeof(npy_intp));
84 | MultiIter_Rank(*mi) = rank;
85 |
86 | for (i = 0; i < rank; i++) {
87 | d |= MultiIter_IndexElem(*mi, i) >= MultiIter_ShapeElem(*mi, i);
88 | if (d)
89 | break;
90 | }
91 |
92 | MultiIter_Done(*mi) = d;
93 |
94 | return;
95 | }
96 |
97 | static NPY_INLINE void multi_iter_masked_new(multi_iter_masked_t *mi,
98 | npy_intp shape[], int rank,
99 | int mask[], int mask_len) {
100 | int i;
101 | char d = 0;
102 |
103 | assert(rank > 0);
104 |
105 | MultiIter_Index(*mi) = (npy_intp *)mkl_calloc(rank, sizeof(npy_intp), 64);
106 | MultiIter_Shape(*mi) = (npy_intp *)mkl_malloc(rank * sizeof(npy_intp), 64);
107 | memcpy(MultiIter_Shape(*mi), shape, rank * sizeof(npy_intp));
108 | MultiIter_Rank(*mi) = rank;
109 |
110 | for (i = 0; i < rank; i++) {
111 | d |= MultiIter_IndexElem(*mi, i) >= MultiIter_ShapeElem(*mi, i);
112 | if (d)
113 | break;
114 | }
115 |
116 | MultiIter_Done(*mi) = d;
117 |
118 | assert(mask_len > 0);
119 | MultiIter_MaskLength(*mi) = mask_len;
120 | MultiIter_Mask(*mi) = (int *)mkl_malloc(mask_len * sizeof(int), 64);
121 | memcpy(MultiIter_Mask(*mi), mask, mask_len * sizeof(int));
122 |
123 | return;
124 | }
125 |
126 | static NPY_INLINE void multi_iter_masked_free(multi_iter_masked_t *mi) {
127 | if (mi) {
128 | if (MultiIter_Index(*mi))
129 | mkl_free(MultiIter_Index(*mi));
130 |
131 | if (MultiIter_Shape(*mi))
132 | mkl_free(MultiIter_Shape(*mi));
133 |
134 | if (MultiIter_Mask(*mi))
135 | mkl_free(MultiIter_Mask(*mi));
136 | }
137 |
138 | return;
139 | }
140 |
141 | static NPY_INLINE void multi_iter_free(multi_iter_t *mi) {
142 | if (mi) {
143 | if (MultiIter_Index(*mi))
144 | mkl_free(MultiIter_Index(*mi));
145 |
146 | if (MultiIter_Shape(*mi))
147 | mkl_free(MultiIter_Shape(*mi));
148 | }
149 |
150 | return;
151 | }
152 |
153 | /* Modifies iterator in-place, returns 1 when iterator is empty, 0 otherwise */
154 | static NPY_INLINE int multi_iter_next(multi_iter_t *mi) {
155 | int j, k;
156 |
157 | if (MultiIter_Done(*mi))
158 | return 1;
159 |
160 | for (k = MultiIter_Rank(*mi); k > 0; k--) {
161 | j = k - 1;
162 | if (++(MultiIter_IndexElem(*mi, j)) < MultiIter_ShapeElem(*mi, j))
163 | return 0;
164 | else {
165 | MultiIter_IndexElem(*mi, j) = 0;
166 | if (!j) {
167 | MultiIter_Done(*mi) = 1;
168 | }
169 | }
170 | }
171 |
172 | return 1;
173 | }
174 |
175 | /* Modifies iterator in-place, returns 1 when iterator is empty, 0 otherwise */
176 | static NPY_INLINE int multi_iter_masked_next(multi_iter_masked_t *mi) {
177 | int j, k;
178 |
179 | if (MultiIter_Done(*mi))
180 | return 1;
181 |
182 | for (k = MultiIter_MaskLength(*mi); k > 0; k--) {
183 | j = MultiIter_MaskElem(*mi, k - 1);
184 | if (++(MultiIter_IndexElem(*mi, j)) < MultiIter_ShapeElem(*mi, j))
185 | return 0;
186 | else {
187 | MultiIter_IndexElem(*mi, j) = 0;
188 | if (!k) {
189 | MultiIter_Done(*mi) = 1;
190 | }
191 | }
192 | }
193 |
194 | return 1;
195 | }
196 |
197 | #endif
198 |
--------------------------------------------------------------------------------
/mkl_fft/tests/test_interfaces.py:
--------------------------------------------------------------------------------
1 | # Copyright (c) 2017, Intel Corporation
2 | #
3 | # Redistribution and use in source and binary forms, with or without
4 | # modification, are permitted provided that the following conditions are met:
5 | #
6 | # * Redistributions of source code must retain the above copyright notice,
7 | # this list of conditions and the following disclaimer.
8 | # * Redistributions in binary form must reproduce the above copyright
9 | # notice, this list of conditions and the following disclaimer in the
10 | # documentation and/or other materials provided with the distribution.
11 | # * Neither the name of Intel Corporation nor the names of its contributors
12 | # may be used to endorse or promote products derived from this software
13 | # without specific prior written permission.
14 | #
15 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
16 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
19 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
21 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
22 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
23 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 |
26 | import numpy as np
27 | import pytest
28 | from numpy.testing import assert_raises
29 |
30 | import mkl_fft.interfaces as mfi
31 |
32 | try:
33 | scipy_fft = mfi.scipy_fft
34 | except AttributeError:
35 | scipy_fft = None
36 |
37 | interfaces = []
38 | ids = []
39 | if scipy_fft is not None:
40 | interfaces.append(scipy_fft)
41 | ids.append("scipy")
42 | interfaces.append(mfi.numpy_fft)
43 | ids.append("numpy")
44 |
45 |
46 | @pytest.mark.parametrize("norm", [None, "forward", "backward", "ortho"])
47 | @pytest.mark.parametrize(
48 | "dtype", [np.float32, np.float64, np.complex64, np.complex128]
49 | )
50 | def test_scipy_fft(norm, dtype):
51 | pytest.importorskip("scipy", reason="requires scipy")
52 | x = np.ones(511, dtype=dtype)
53 | w = mfi.scipy_fft.fft(x, norm=norm, workers=None, plan=None)
54 | xx = mfi.scipy_fft.ifft(w, norm=norm, workers=None, plan=None)
55 | tol = 128 * np.finfo(np.dtype(dtype)).eps
56 | np.testing.assert_allclose(x, xx, atol=tol, rtol=tol)
57 |
58 |
59 | @pytest.mark.parametrize("norm", [None, "forward", "backward", "ortho"])
60 | @pytest.mark.parametrize(
61 | "dtype", [np.float32, np.float64, np.complex64, np.complex128]
62 | )
63 | def test_numpy_fft(norm, dtype):
64 | x = np.ones(511, dtype=dtype)
65 | w = mfi.numpy_fft.fft(x, norm=norm)
66 | xx = mfi.numpy_fft.ifft(w, norm=norm)
67 | tol = 128 * np.finfo(np.dtype(dtype)).eps
68 | np.testing.assert_allclose(x, xx, atol=tol, rtol=tol)
69 |
70 |
71 | @pytest.mark.parametrize("norm", [None, "forward", "backward", "ortho"])
72 | @pytest.mark.parametrize("dtype", [np.float32, np.float64])
73 | def test_scipy_rfft(norm, dtype):
74 | pytest.importorskip("scipy", reason="requires scipy")
75 | x = np.ones(511, dtype=dtype)
76 | w = mfi.scipy_fft.rfft(x, norm=norm, workers=None, plan=None)
77 | xx = mfi.scipy_fft.irfft(
78 | w, n=x.shape[0], norm=norm, workers=None, plan=None
79 | )
80 | tol = 64 * np.finfo(np.dtype(dtype)).eps
81 | assert np.allclose(x, xx, atol=tol, rtol=tol)
82 |
83 | x = np.ones(510, dtype=dtype)
84 | w = mfi.scipy_fft.rfft(x, norm=norm, workers=None, plan=None)
85 | xx = mfi.scipy_fft.irfft(w, norm=norm, workers=None, plan=None)
86 | tol = 64 * np.finfo(np.dtype(dtype)).eps
87 | assert np.allclose(x, xx, atol=tol, rtol=tol)
88 |
89 |
90 | @pytest.mark.parametrize("norm", [None, "forward", "backward", "ortho"])
91 | @pytest.mark.parametrize("dtype", [np.float32, np.float64])
92 | def test_numpy_rfft(norm, dtype):
93 | x = np.ones(511, dtype=dtype)
94 | w = mfi.numpy_fft.rfft(x, norm=norm)
95 | xx = mfi.numpy_fft.irfft(w, n=x.shape[0], norm=norm)
96 | tol = 64 * np.finfo(np.dtype(dtype)).eps
97 | assert np.allclose(x, xx, atol=tol, rtol=tol)
98 |
99 |
100 | @pytest.mark.parametrize("norm", [None, "forward", "backward", "ortho"])
101 | @pytest.mark.parametrize(
102 | "dtype", [np.float32, np.float64, np.complex64, np.complex128]
103 | )
104 | def test_scipy_fftn(norm, dtype):
105 | pytest.importorskip("scipy", reason="requires scipy")
106 | x = np.ones((37, 83), dtype=dtype)
107 | w = mfi.scipy_fft.fftn(x, norm=norm, workers=None, plan=None)
108 | xx = mfi.scipy_fft.ifftn(w, norm=norm, workers=None, plan=None)
109 | tol = 64 * np.finfo(np.dtype(dtype)).eps
110 | assert np.allclose(x, xx, atol=tol, rtol=tol)
111 |
112 |
113 | @pytest.mark.parametrize("norm", [None, "forward", "backward", "ortho"])
114 | @pytest.mark.parametrize(
115 | "dtype", [np.float32, np.float64, np.complex64, np.complex128]
116 | )
117 | def test_numpy_fftn(norm, dtype):
118 | x = np.ones((37, 83), dtype=dtype)
119 | w = mfi.numpy_fft.fftn(x, norm=norm)
120 | xx = mfi.numpy_fft.ifftn(w, norm=norm)
121 | tol = 64 * np.finfo(np.dtype(dtype)).eps
122 | assert np.allclose(x, xx, atol=tol, rtol=tol)
123 |
124 |
125 | @pytest.mark.parametrize("norm", [None, "forward", "backward", "ortho"])
126 | @pytest.mark.parametrize("dtype", [np.float32, np.float64])
127 | def test_scipy_rfftn(norm, dtype):
128 | pytest.importorskip("scipy", reason="requires scipy")
129 | x = np.ones((37, 83), dtype=dtype)
130 | w = mfi.scipy_fft.rfftn(x, norm=norm, workers=None, plan=None)
131 | xx = mfi.scipy_fft.irfftn(w, s=x.shape, norm=norm, workers=None, plan=None)
132 | tol = 64 * np.finfo(np.dtype(dtype)).eps
133 | assert np.allclose(x, xx, atol=tol, rtol=tol)
134 |
135 | x = np.ones((36, 82), dtype=dtype)
136 | w = mfi.scipy_fft.rfftn(x, norm=norm, workers=None, plan=None)
137 | xx = mfi.scipy_fft.irfftn(w, norm=norm, workers=None, plan=None)
138 | tol = 64 * np.finfo(np.dtype(dtype)).eps
139 | assert np.allclose(x, xx, atol=tol, rtol=tol)
140 |
141 |
142 | @pytest.mark.filterwarnings("ignore::DeprecationWarning")
143 | @pytest.mark.parametrize("norm", [None, "forward", "backward", "ortho"])
144 | @pytest.mark.parametrize("dtype", [np.float32, np.float64])
145 | def test_numpy_rfftn(norm, dtype):
146 | x = np.ones((37, 83), dtype=dtype)
147 | w = mfi.numpy_fft.rfftn(x, norm=norm)
148 | xx = mfi.numpy_fft.irfftn(w, s=x.shape, norm=norm)
149 | tol = 64 * np.finfo(np.dtype(dtype)).eps
150 | assert np.allclose(x, xx, atol=tol, rtol=tol)
151 |
152 |
153 | def _get_blacklisted_dtypes():
154 | bl_list = []
155 | for dt in ["float16", "float128", "complex256"]:
156 | if hasattr(np, dt):
157 | bl_list.append(getattr(np, dt))
158 | return bl_list
159 |
160 |
161 | @pytest.mark.parametrize("dtype", _get_blacklisted_dtypes())
162 | def test_scipy_no_support_for(dtype):
163 | pytest.importorskip("scipy", reason="requires scipy")
164 | x = np.ones(16, dtype=dtype)
165 | assert_raises(NotImplementedError, mfi.scipy_fft.ifft, x)
166 |
167 |
168 | def test_scipy_fft_arg_validate():
169 | pytest.importorskip("scipy", reason="requires scipy")
170 | with pytest.raises(ValueError):
171 | mfi.scipy_fft.fft([1, 2, 3, 4], norm=b"invalid")
172 |
173 | with pytest.raises(NotImplementedError):
174 | mfi.scipy_fft.fft([1, 2, 3, 4], plan="magic")
175 |
176 |
177 | @pytest.mark.parametrize("interface", interfaces, ids=ids)
178 | def test_axes(interface):
179 | x = np.arange(24.0).reshape(2, 3, 4)
180 | res = interface.rfft2(x, axes=(1, 2))
181 | exp = np.fft.rfft2(x, axes=(1, 2))
182 | tol = 64 * np.finfo(np.float64).eps
183 | assert np.allclose(res, exp, atol=tol, rtol=tol)
184 |
185 |
186 | @pytest.mark.parametrize("interface", interfaces, ids=ids)
187 | @pytest.mark.parametrize(
188 | "func", ["fftshift", "ifftshift", "fftfreq", "rfftfreq"]
189 | )
190 | def test_interface_helper_functions(interface, func):
191 | assert hasattr(interface, func)
192 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # changelog
2 | All notable changes to this project will be documented in this file.
3 |
4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6 |
7 | ## [dev] - YYYY-MM-DD
8 |
9 | ### Added
10 | * Enabled support of Python 3.14 [gh-234](https://github.com/IntelPython/mkl_fft/pull/234)
11 |
12 | ### Removed
13 | * Dropped support for Python 3.9 [gh-243](https://github.com/IntelPython/mkl_fft/pull/243)
14 |
15 | ## [2.1.1] - 2025-10-09
16 |
17 | ### Fixed
18 | * Fix a circular dependency in Intel NumPy: revert vendoring of `scipy.fft` functions, instead using thin wrappers to call from `scipy` directly [gh-233](https://github.com/IntelPython/mkl_fft/pull/233)
19 |
20 | ## [2.1.0] - 2025-10-06
21 |
22 | ### Added
23 | * Enabled support of Python 3.13 [gh-164](https://github.com/IntelPython/mkl_fft/pull/164)
24 |
25 | ### Changed
26 | * Dropped support for `scipy.fftpack` interface [gh-185](https://github.com/IntelPython/mkl_fft/pull/185)
27 | * Dropped support for `overwrite_x` parameter in `mkl_fft` [gh-185](https://github.com/IntelPython/mkl_fft/pull/185)
28 | * Replaced `fwd_scale` parameter with `norm` in `mkl_fft` [gh-189](https://github.com/IntelPython/mkl_fft/pull/189)
29 | * Conditionally import `scipy_fft` only if `scipy` is installed [gh-195](https://github.com/IntelPython/mkl_fft/pull/195)
30 | * Vendor `fftfreq`, `rfftfreq`, `fftshift`, and `ifftshift` to `scipy_fft` and `numpy_fft` interfaces [gh-226](https://github.com/IntelPython/mkl_fft/pull/226), [gh=229](https://github.com/IntelPython/mkl_fft/pull/229)
31 |
32 | ### Fixed
33 | * Fixed a bug for N-D FFTs when both `s` and `out` are given [gh-185](https://github.com/IntelPython/mkl_fft/pull/185)
34 |
35 | ## [2.0.0] - 2025-06-03
36 |
37 | ### Added
38 | * Added Hermitian FFT functions to SciPy interface `mkl_fft.interfaces.scipy_fft`: `hfft`, `ihfft`, `hfftn`, `ihfftn`, `hfft2`, and `ihfft2` [gh-161](https://github.com/IntelPython/mkl_fft/pull/161)
39 | * Added support for `out` kwarg to all FFT functions in `mkl_fft` and `mkl_fft.interfaces.numpy_fft` [gh-157](https://github.com/IntelPython/mkl_fft/pull/157)
40 | * Added `fftfreq`, `fftshift`, `ifftshift`, and `rfftfreq` to both NumPy and SciPy interfaces [gh-179](https://github.com/IntelPython/mkl_fft/pull/179)
41 |
42 | ### Changed
43 | * NumPy interface `mkl_fft.interfaces.numpy_fft` is aligned with numpy-2.x.x [gh-139](https://github.com/IntelPython/mkl_fft/pull/139), [gh-157](https://github.com/IntelPython/mkl_fft/pull/157)
44 | * To set `mkl_fft` as the backend for SciPy is only possible through `mkl_fft.interfaces.scipy_fft` [gh-179](https://github.com/IntelPython/mkl_fft/pull/179)
45 | * SciPy interface `mkl_fft.interfaces.scipy_fft` uses the same function from SciPy for handling `s` and `axes` for N-D FFTs [gh-181](https://github.com/IntelPython/mkl_fft/pull/181)
46 |
47 | ### Fixed
48 | * Fixed a bug in `mkl_fft.interfaces.numpy.fftn` when an empty tuple is passed for `axes` [gh-139](https://github.com/IntelPython/mkl_fft/pull/139)
49 | * Fixed a bug for a case when a zero-size array is passed to `mkl_fft.interfaces.numpy.fftn` [gh-139](https://github.com/IntelPython/mkl_fft/pull/139)
50 | * Fixed inconsistency of input and output arrays dtype for `irfft` function [gh-180](https://github.com/IntelPython/mkl_fft/pull/180)
51 | * Fixed issues with `set_workers` function in SciPy interface `mkl_fft.interfaces.scipy_fft` [gh-183](https://github.com/IntelPython/mkl_fft/pull/183)
52 |
53 | ## [1.3.14] - 2025-04-10
54 |
55 | resolves gh-152 by adding an explicit `mkl-service` dependency to `mkl-fft` when building the wheel
56 | to ensure the `mkl` Python module is always available at runtime
57 |
58 | resolves gh-115, gh-116, gh-119, gh-135
59 |
60 | ## [1.3.13] - 2025-03-25
61 |
62 | Supported python versions are 3.9, 3.10, 3.11, 3.12
63 |
64 | migrate from `setup.py` to `pyproject.toml`
65 |
66 | includes support in virtual environment out of the box
67 |
68 | the original `mkl_fft.rfft` and `mkl_fft.irfft` are renamed to `mkl_fft.rfftpack` and `mkl_fft.irfftpack`,
69 | since they replicate the behavior from the deprecated `scipy.fftpack` module.
70 |
71 | `mkl_fft.rfft_numpy`, `mkl_fft.irfft_numpy`, `mkl_fft.rfft2_numpy`, `mkl_fft.irfft2_numpy`,
72 | `mkl_fft.rfftn_numpy`, and `mkl_fft.irfftn_numpy` are renamed to `mkl_fft.rfft`, `mkl_fft.irfft`,
73 | `mkl_fft.rfft2`, `mkl_fft.irfft2`, `mkl_fft.rfftn`, and `mkl_fft.irfftn`, respectively.
74 | (consistent with `numpy.fft` and `scipy.fft` modules)
75 |
76 | file `_scipy_fft_backend.py` is renamed to `_scipy_fft.py` since it replicates `scipy.fft` module
77 | (similar to file `_numpy_fft.py` which replicates `numpy.fft` module)
78 |
79 | ## [1.3.11]
80 |
81 | Bugfix release, resolving gh-109 and updating installation instructions
82 |
83 | ## [1.3.10]
84 |
85 | Bugfix release, resolving leftover uses of NumPy attributes removed in NumPy 2.0 that break
86 | test suite run on Windows.
87 |
88 | ## [1.3.9]
89 |
90 | Updated code and build system to support NumPy 2.0
91 |
92 | ## [1.3.8]
93 |
94 | Added vendored `conv_template.py` from NumPy's distutils submodule to enable building of `mkl_fft` with
95 | NumPy >=1.25 and Python 3.12
96 |
97 | ## [1.3.7]
98 |
99 | Updated build system away from removed in NumPy 1.25 numpy.distutils module.
100 |
101 | Transitioned to Cython 3.0.
102 |
103 | ## [1.3.0]
104 |
105 | Updated numpy interface to support new in NumPy 1.20 supported values of norm keyword, such as "forward" and "backward".
106 | To enable this, `mkl_fft` functions now support `forward_scale` parameter that defaults to 1.
107 |
108 | Fixed issue #48.
109 |
110 | ## [1.2.1]
111 |
112 | Includes bug fix #54
113 |
114 | ## [1.2.0]
115 |
116 | Due to removal of deprecated real-to-real FFT with `DFTI_CONJUGATE_EVEN_STORAGE=DFTI_COMPLEX_REAL` and
117 | `DFTI_PACKED_FORMAT=DFTI_PACK` from Intel(R) Math Kernel Library, reimplemented `mkl_fft.rfft` and
118 | `mkl_fft.irfft` to use real-to-complex functionality with subsequent copying to rearange the transform as expected
119 | of `mkl_fft.rfft`, with the associated performance penalty. The use of the real-to-complex
120 | transform improves multi-core utilization which may offset the performance loss incurred due to copying.
121 |
122 | ## [1.1.0]
123 |
124 | Added `scipy.fft` backend, see #42. Fixed #46.
125 |
126 | ## [1.0.15]
127 |
128 | Changed tests to not compare against numpy fft, as this broke due to renaming of `np.fft.pocketfft` to
129 | `np.fft._pocketfft`. Instead compare against naive realization of 1D FFT as a sum.
130 |
131 | Setup script is now aware of `MKLROOT` environment variable. If unset, NumPy's mkl_info will be queried.
132 |
133 | ## [1.0.14]
134 |
135 | Fixed unreferenced bug in `irfftn_numpy`, and adjusted NumPy interfaces to change to pocketfft in NumPy 1.17
136 |
137 | ## [1.0.13]
138 |
139 | Issue #39 fixed (memory leak with complex FFT on real arrays)
140 |
141 | ## [1.0.12]
142 |
143 | Issue #37 fixed.
144 |
145 | Inhibited vectorization of short loops computing pointer to memory referenced by a multi-iterator by Intel (R) C Compiler,
146 | improving performance of ND `fft` and `ifft` on real input arrays.
147 |
148 | ## [1.0.11]
149 |
150 | Improvement for performance of ND `fft` on real input arrays by inlining multi-iterators.
151 | This particularly benefits performance of mkl_fft built with Intel (R) C Compiler.
152 |
153 | ## [1.0.10]
154 |
155 | Fix for issue #29.
156 |
157 | ## [1.0.7]
158 |
159 | Improved exception message raised if MKL is signalling an error. The message now includes MKL's own description of the exception.
160 | This partially improves #24.
161 |
162 | Improved argument validation for ND transforms aligning with scipy 1.2.0
163 |
164 | ## [1.0.6]
165 |
166 | Fixed issues #21, and addressed NumPy 1.15 deprecation warnings from using lists instead of tuples to specify multiple slices.
167 |
168 | ## [1.0.5]
169 |
170 | Fixed issues #7, #17, #18.
171 | Consolidated version specification into a single file `mkl_fft/_version.py`.
172 |
173 | ## [1.0.4]
174 |
175 | Added CHANGES.rst. Fixed issue #11 by using lock around calls to 1D FFT routines.
176 |
177 | ## [1.0.3]
178 |
179 | This is a bug fix release.
180 |
181 | It fixes issues #9, and #13.
182 |
183 | As part of fixing issue #13, out-of-place 1D FFT calls such as `fft`, `ifft`, `rfft_numpy`
184 | and `irfftn_numpy` will allocate Fortran layout array for the output is the input is a Fotran array.
185 |
186 | ## [1.0.2]
187 |
188 | Minor update of `mkl_fft`, reflecting renaming of `numpy.core.multiarray_tests` module to
189 | `numpy.core._multiarray_tests` as well as fixing #4.
190 |
191 | ## [1.0.1]
192 |
193 | Bug fix release.
194 |
195 | ## [1.0.0]
196 |
197 | Initial release of `mkl_fft`.
198 |
--------------------------------------------------------------------------------
/mkl_fft/interfaces/_numpy_fft.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # Copyright (c) 2017, Intel Corporation
3 | #
4 | # Redistribution and use in source and binary forms, with or without
5 | # modification, are permitted provided that the following conditions are met:
6 | #
7 | # * Redistributions of source code must retain the above copyright notice,
8 | # this list of conditions and the following disclaimer.
9 | # * Redistributions in binary form must reproduce the above copyright
10 | # notice, this list of conditions and the following disclaimer in the
11 | # documentation and/or other materials provided with the distribution.
12 | # * Neither the name of Intel Corporation nor the names of its contributors
13 | # may be used to endorse or promote products derived from this software
14 | # without specific prior written permission.
15 | #
16 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
20 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
22 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
23 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
24 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 |
27 | """
28 | An interface for FFT module of NumPy (`numpy.fft`) that uses OneMKL FFT
29 | in the backend.
30 | """
31 |
32 | import re
33 | import warnings
34 |
35 | import numpy as np
36 |
37 | import mkl_fft
38 |
39 | from .._fft_utils import _swap_direction
40 | from ._float_utils import _downcast_float128_array
41 |
42 | __all__ = [
43 | "fft",
44 | "ifft",
45 | "fft2",
46 | "ifft2",
47 | "fftn",
48 | "ifftn",
49 | "rfft",
50 | "irfft",
51 | "rfft2",
52 | "irfft2",
53 | "rfftn",
54 | "irfftn",
55 | "hfft",
56 | "ihfft",
57 | ]
58 |
59 |
60 | # copied with modifications from:
61 | # https://github.com/numpy/numpy/blob/main/numpy/fft/_pocketfft.py
62 | def _cook_nd_args(a, s=None, axes=None, invreal=False):
63 | if s is None:
64 | shapeless = True
65 | if axes is None:
66 | s = list(a.shape)
67 | else:
68 | s = np.take(a.shape, axes)
69 | else:
70 | shapeless = False
71 | s = list(s)
72 | if axes is None:
73 | if not shapeless and np.lib.NumpyVersion(np.__version__) >= "2.0.0":
74 | msg = (
75 | "`axes` should not be `None` if `s` is not `None` "
76 | "(Deprecated in NumPy 2.0). In a future version of NumPy, "
77 | "this will raise an error and `s[i]` will correspond to "
78 | "the size along the transformed axis specified by "
79 | "`axes[i]`. To retain current behaviour, pass a sequence "
80 | "[0, ..., k-1] to `axes` for an array of dimension k."
81 | )
82 | warnings.warn(msg, DeprecationWarning, stacklevel=3)
83 | axes = list(range(-len(s), 0))
84 | if len(s) != len(axes):
85 | raise ValueError("Shape and axes have different lengths.")
86 | if invreal and shapeless:
87 | s[-1] = (a.shape[axes[-1]] - 1) * 2
88 | if None in s and np.lib.NumpyVersion(np.__version__) >= "2.0.0":
89 | msg = (
90 | "Passing an array containing `None` values to `s` is "
91 | "deprecated in NumPy 2.0 and will raise an error in "
92 | "a future version of NumPy. To use the default behaviour "
93 | "of the corresponding 1-D transform, pass the value matching "
94 | "the default for its `n` parameter. To use the default "
95 | "behaviour for every axis, the `s` argument can be omitted."
96 | )
97 | warnings.warn(msg, DeprecationWarning, stacklevel=3)
98 | # use the whole input array along axis `i` if `s[i] == -1 or None`
99 | s = [a.shape[_a] if _s in [-1, None] else _s for _s, _a in zip(s, axes)]
100 |
101 | return s, axes
102 |
103 |
104 | def _trycall(func, args, kwrds):
105 | try:
106 | res = func(*args, **kwrds)
107 | except ValueError as ve:
108 | if len(ve.args) == 1:
109 | if re.match("^Dimension n", ve.args[0]):
110 | raise ValueError("Invalid number of FFT data points")
111 | raise ve
112 | return res
113 |
114 |
115 | def fft(a, n=None, axis=-1, norm=None, out=None):
116 | """
117 | Compute the one-dimensional discrete Fourier Transform.
118 |
119 | For full documentation refer to `numpy.fft.fft`.
120 |
121 | """
122 | x = _downcast_float128_array(a)
123 | return _trycall(
124 | mkl_fft.fft, (x,), {"n": n, "axis": axis, "norm": norm, "out": out}
125 | )
126 |
127 |
128 | def ifft(a, n=None, axis=-1, norm=None, out=None):
129 | """
130 | Compute the one-dimensional inverse discrete Fourier Transform.
131 |
132 | For full documentation refer to `numpy.fft.ifft`.
133 |
134 | """
135 | x = _downcast_float128_array(a)
136 | return _trycall(
137 | mkl_fft.ifft, (x,), {"n": n, "axis": axis, "norm": norm, "out": out}
138 | )
139 |
140 |
141 | def fft2(a, s=None, axes=(-2, -1), norm=None, out=None):
142 | """
143 | Compute the 2-dimensional discrete Fourier Transform
144 |
145 | For full documentation refer to `numpy.fft.fft2`.
146 |
147 | """
148 | return fftn(a, s=s, axes=axes, norm=norm, out=out)
149 |
150 |
151 | def ifft2(a, s=None, axes=(-2, -1), norm=None, out=None):
152 | """
153 | Compute the 2-dimensional inverse discrete Fourier Transform.
154 |
155 | For full documentation refer to `numpy.fft.ifft2`.
156 |
157 | """
158 | return ifftn(a, s=s, axes=axes, norm=norm, out=out)
159 |
160 |
161 | def fftn(a, s=None, axes=None, norm=None, out=None):
162 | """
163 | Compute the N-dimensional discrete Fourier Transform.
164 |
165 | For full documentation refer to `numpy.fft.fftn`.
166 |
167 | """
168 | x = _downcast_float128_array(a)
169 | s, axes = _cook_nd_args(x, s, axes)
170 |
171 | return _trycall(
172 | mkl_fft.fftn, (x,), {"s": s, "axes": axes, "norm": norm, "out": out}
173 | )
174 |
175 |
176 | def ifftn(a, s=None, axes=None, norm=None, out=None):
177 | """
178 | Compute the N-dimensional inverse discrete Fourier Transform.
179 |
180 | For full documentation refer to `numpy.fft.ifftn`.
181 |
182 | """
183 | x = _downcast_float128_array(a)
184 | s, axes = _cook_nd_args(x, s, axes)
185 |
186 | return _trycall(
187 | mkl_fft.ifftn,
188 | (x,),
189 | {"s": s, "axes": axes, "norm": norm, "out": out},
190 | )
191 |
192 |
193 | def rfft(a, n=None, axis=-1, norm=None, out=None):
194 | """
195 | Compute the one-dimensional discrete Fourier Transform for real input.
196 |
197 | For full documentation refer to `numpy.fft.rfft`.
198 |
199 | """
200 | x = _downcast_float128_array(a)
201 |
202 | return _trycall(
203 | mkl_fft.rfft, (x,), {"n": n, "axis": axis, "norm": norm, "out": out}
204 | )
205 |
206 |
207 | def irfft(a, n=None, axis=-1, norm=None, out=None):
208 | """
209 | Compute the inverse of `rfft`.
210 |
211 | For full documentation refer to `numpy.fft.irfft`.
212 |
213 | """
214 | x = _downcast_float128_array(a)
215 |
216 | return _trycall(
217 | mkl_fft.irfft,
218 | (x,),
219 | {"n": n, "axis": axis, "norm": norm, "out": out},
220 | )
221 |
222 |
223 | def rfft2(a, s=None, axes=(-2, -1), norm=None, out=None):
224 | """
225 | Compute the 2-dimensional FFT of a real array.
226 |
227 | For full documentation refer to `numpy.fft.rfft2`.
228 |
229 | """
230 | return rfftn(a, s=s, axes=axes, norm=norm, out=out)
231 |
232 |
233 | def irfft2(a, s=None, axes=(-2, -1), norm=None, out=None):
234 | """
235 | Compute the inverse FFT of `rfft2`.
236 |
237 | For full documentation refer to `numpy.fft.irfft2`.
238 |
239 | """
240 | return irfftn(a, s=s, axes=axes, norm=norm, out=out)
241 |
242 |
243 | def rfftn(a, s=None, axes=None, norm=None, out=None):
244 | """
245 | Compute the N-dimensional discrete Fourier Transform for real input.
246 |
247 | For full documentation refer to `numpy.fft.rfftn`.
248 |
249 | """
250 | x = _downcast_float128_array(a)
251 | s, axes = _cook_nd_args(x, s, axes)
252 |
253 | return _trycall(
254 | mkl_fft.rfftn,
255 | (x,),
256 | {"s": s, "axes": axes, "norm": norm, "out": out},
257 | )
258 |
259 |
260 | def irfftn(a, s=None, axes=None, norm=None, out=None):
261 | """
262 | Compute the inverse of `rfftn`.
263 |
264 | For full documentation refer to `numpy.fft.irfftn`.
265 |
266 | """
267 |
268 | x = _downcast_float128_array(a)
269 | s, axes = _cook_nd_args(x, s, axes, invreal=True)
270 |
271 | return _trycall(
272 | mkl_fft.irfftn,
273 | (x,),
274 | {"s": s, "axes": axes, "norm": norm, "out": out},
275 | )
276 |
277 |
278 | def hfft(a, n=None, axis=-1, norm=None, out=None):
279 | """
280 | Compute the FFT of a signal which has Hermitian symmetry,
281 | i.e., a real spectrum..
282 |
283 | For full documentation refer to `numpy.fft.hfft`.
284 |
285 | """
286 | norm = _swap_direction(norm)
287 | x = _downcast_float128_array(a)
288 | return _trycall(
289 | mkl_fft.irfft,
290 | (np.conjugate(x),),
291 | {"n": n, "axis": axis, "norm": norm, "out": out},
292 | )
293 |
294 |
295 | def ihfft(a, n=None, axis=-1, norm=None, out=None):
296 | """
297 | Compute the inverse FFT of a signal which has Hermitian symmetry.
298 |
299 | For full documentation refer to `numpy.fft.ihfft`.
300 |
301 | """
302 | norm = _swap_direction(norm)
303 | x = _downcast_float128_array(a)
304 |
305 | result = _trycall(
306 | mkl_fft.rfft, (x,), {"n": n, "axis": axis, "norm": norm, "out": out}
307 | )
308 |
309 | np.conjugate(result, out=result)
310 | return result
311 |
--------------------------------------------------------------------------------
/_vendored/conv_template.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python3
2 | """
3 | takes templated file .xxx.src and produces .xxx file where .xxx is
4 | .i or .c or .h, using the following template rules
5 |
6 | /**begin repeat -- on a line by itself marks the start of a repeated code
7 | segment
8 | /**end repeat**/ -- on a line by itself marks it's end
9 |
10 | After the /**begin repeat and before the */, all the named templates are placed
11 | these should all have the same number of replacements
12 |
13 | Repeat blocks can be nested, with each nested block labeled with its depth,
14 | i.e.
15 | /**begin repeat1
16 | *....
17 | */
18 | /**end repeat1**/
19 |
20 | When using nested loops, you can optionally exclude particular
21 | combinations of the variables using (inside the comment portion of the inner loop):
22 |
23 | :exclude: var1=value1, var2=value2, ...
24 |
25 | This will exclude the pattern where var1 is value1 and var2 is value2 when
26 | the result is being generated.
27 |
28 |
29 | In the main body each replace will use one entry from the list of named replacements
30 |
31 | Note that all #..# forms in a block must have the same number of
32 | comma-separated entries.
33 |
34 | Example:
35 |
36 | An input file containing
37 |
38 | /**begin repeat
39 | * #a = 1,2,3#
40 | * #b = 1,2,3#
41 | */
42 |
43 | /**begin repeat1
44 | * #c = ted, jim#
45 | */
46 | @a@, @b@, @c@
47 | /**end repeat1**/
48 |
49 | /**end repeat**/
50 |
51 | produces
52 |
53 | line 1 "template.c.src"
54 |
55 | /*
56 | *********************************************************************
57 | ** This file was autogenerated from a template DO NOT EDIT!!**
58 | ** Changes should be made to the original source (.src) file **
59 | *********************************************************************
60 | */
61 |
62 | #line 9
63 | 1, 1, ted
64 |
65 | #line 9
66 | 1, 1, jim
67 |
68 | #line 9
69 | 2, 2, ted
70 |
71 | #line 9
72 | 2, 2, jim
73 |
74 | #line 9
75 | 3, 3, ted
76 |
77 | #line 9
78 | 3, 3, jim
79 |
80 | """
81 |
82 | __all__ = ['process_str', 'process_file']
83 |
84 | import os
85 | import sys
86 | import re
87 |
88 | # names for replacement that are already global.
89 | global_names = {}
90 |
91 | # header placed at the front of head processed file
92 | header =\
93 | """
94 | /*
95 | *****************************************************************************
96 | ** This file was autogenerated from a template DO NOT EDIT!!!! **
97 | ** Changes should be made to the original source (.src) file **
98 | *****************************************************************************
99 | */
100 |
101 | """
102 | # Parse string for repeat loops
103 | def parse_structure(astr, level):
104 | """
105 | The returned line number is from the beginning of the string, starting
106 | at zero. Returns an empty list if no loops found.
107 |
108 | """
109 | if level == 0 :
110 | loopbeg = "/**begin repeat"
111 | loopend = "/**end repeat**/"
112 | else :
113 | loopbeg = "/**begin repeat%d" % level
114 | loopend = "/**end repeat%d**/" % level
115 |
116 | ind = 0
117 | line = 0
118 | spanlist = []
119 | while True:
120 | start = astr.find(loopbeg, ind)
121 | if start == -1:
122 | break
123 | start2 = astr.find("*/", start)
124 | start2 = astr.find("\n", start2)
125 | fini1 = astr.find(loopend, start2)
126 | fini2 = astr.find("\n", fini1)
127 | line += astr.count("\n", ind, start2+1)
128 | spanlist.append((start, start2+1, fini1, fini2+1, line))
129 | line += astr.count("\n", start2+1, fini2)
130 | ind = fini2
131 | spanlist.sort()
132 | return spanlist
133 |
134 |
135 | def paren_repl(obj):
136 | torep = obj.group(1)
137 | numrep = obj.group(2)
138 | return ','.join([torep]*int(numrep))
139 |
140 | parenrep = re.compile(r"\(([^)]*)\)\*(\d+)")
141 | plainrep = re.compile(r"([^*]+)\*(\d+)")
142 | def parse_values(astr):
143 | # replaces all occurrences of '(a,b,c)*4' in astr
144 | # with 'a,b,c,a,b,c,a,b,c,a,b,c'. Empty braces generate
145 | # empty values, i.e., ()*4 yields ',,,'. The result is
146 | # split at ',' and a list of values returned.
147 | astr = parenrep.sub(paren_repl, astr)
148 | # replaces occurrences of xxx*3 with xxx, xxx, xxx
149 | astr = ','.join([plainrep.sub(paren_repl, x.strip())
150 | for x in astr.split(',')])
151 | return astr.split(',')
152 |
153 |
154 | stripast = re.compile(r"\n\s*\*?")
155 | named_re = re.compile(r"#\s*(\w*)\s*=([^#]*)#")
156 | exclude_vars_re = re.compile(r"(\w*)=(\w*)")
157 | exclude_re = re.compile(":exclude:")
158 | def parse_loop_header(loophead) :
159 | """Find all named replacements in the header
160 |
161 | Returns a list of dictionaries, one for each loop iteration,
162 | where each key is a name to be substituted and the corresponding
163 | value is the replacement string.
164 |
165 | Also return a list of exclusions. The exclusions are dictionaries
166 | of key value pairs. There can be more than one exclusion.
167 | [{'var1':'value1', 'var2', 'value2'[,...]}, ...]
168 |
169 | """
170 | # Strip out '\n' and leading '*', if any, in continuation lines.
171 | # This should not effect code previous to this change as
172 | # continuation lines were not allowed.
173 | loophead = stripast.sub("", loophead)
174 | # parse out the names and lists of values
175 | names = []
176 | reps = named_re.findall(loophead)
177 | nsub = None
178 | for rep in reps:
179 | name = rep[0]
180 | vals = parse_values(rep[1])
181 | size = len(vals)
182 | if nsub is None :
183 | nsub = size
184 | elif nsub != size :
185 | msg = "Mismatch in number of values, %d != %d\n%s = %s"
186 | raise ValueError(msg % (nsub, size, name, vals))
187 | names.append((name, vals))
188 |
189 |
190 | # Find any exclude variables
191 | excludes = []
192 |
193 | for obj in exclude_re.finditer(loophead):
194 | span = obj.span()
195 | # find next newline
196 | endline = loophead.find('\n', span[1])
197 | substr = loophead[span[1]:endline]
198 | ex_names = exclude_vars_re.findall(substr)
199 | excludes.append(dict(ex_names))
200 |
201 | # generate list of dictionaries, one for each template iteration
202 | dlist = []
203 | if nsub is None :
204 | raise ValueError("No substitution variables found")
205 | for i in range(nsub):
206 | tmp = {name: vals[i] for name, vals in names}
207 | dlist.append(tmp)
208 | return dlist
209 |
210 | replace_re = re.compile(r"@(\w+)@")
211 | def parse_string(astr, env, level, line) :
212 | lineno = "#line %d\n" % line
213 |
214 | # local function for string replacement, uses env
215 | def replace(match):
216 | name = match.group(1)
217 | try :
218 | val = env[name]
219 | except KeyError:
220 | msg = 'line %d: no definition of key "%s"'%(line, name)
221 | raise ValueError(msg) from None
222 | return val
223 |
224 | code = [lineno]
225 | struct = parse_structure(astr, level)
226 | if struct :
227 | # recurse over inner loops
228 | oldend = 0
229 | newlevel = level + 1
230 | for sub in struct:
231 | pref = astr[oldend:sub[0]]
232 | head = astr[sub[0]:sub[1]]
233 | text = astr[sub[1]:sub[2]]
234 | oldend = sub[3]
235 | newline = line + sub[4]
236 | code.append(replace_re.sub(replace, pref))
237 | try :
238 | envlist = parse_loop_header(head)
239 | except ValueError as e:
240 | msg = "line %d: %s" % (newline, e)
241 | raise ValueError(msg)
242 | for newenv in envlist :
243 | newenv.update(env)
244 | newcode = parse_string(text, newenv, newlevel, newline)
245 | code.extend(newcode)
246 | suff = astr[oldend:]
247 | code.append(replace_re.sub(replace, suff))
248 | else :
249 | # replace keys
250 | code.append(replace_re.sub(replace, astr))
251 | code.append('\n')
252 | return ''.join(code)
253 |
254 | def process_str(astr):
255 | code = [header]
256 | code.extend(parse_string(astr, global_names, 0, 1))
257 | return ''.join(code)
258 |
259 |
260 | include_src_re = re.compile(r"(\n|\A)#include\s*['\"]"
261 | r"(?P[\w\d./\\]+[.]src)['\"]", re.I)
262 |
263 | def resolve_includes(source):
264 | d = os.path.dirname(source)
265 | with open(source) as fid:
266 | lines = []
267 | for line in fid:
268 | m = include_src_re.match(line)
269 | if m:
270 | fn = m.group('name')
271 | if not os.path.isabs(fn):
272 | fn = os.path.join(d, fn)
273 | if os.path.isfile(fn):
274 | lines.extend(resolve_includes(fn))
275 | else:
276 | lines.append(line)
277 | else:
278 | lines.append(line)
279 | return lines
280 |
281 | def process_file(source):
282 | lines = resolve_includes(source)
283 | sourcefile = os.path.normcase(source).replace("\\", "\\\\")
284 | try:
285 | code = process_str(''.join(lines))
286 | except ValueError as e:
287 | raise ValueError('In "%s" loop at %s' % (sourcefile, e)) from None
288 | return '#line 1 "%s"\n%s' % (sourcefile, code)
289 |
290 |
291 | def unique_key(adict):
292 | # this obtains a unique key given a dictionary
293 | # currently it works by appending together n of the letters of the
294 | # current keys and increasing n until a unique key is found
295 | # -- not particularly quick
296 | allkeys = list(adict.keys())
297 | done = False
298 | n = 1
299 | while not done:
300 | newkey = "".join([x[:n] for x in allkeys])
301 | if newkey in allkeys:
302 | n += 1
303 | else:
304 | done = True
305 | return newkey
306 |
307 |
308 | def main():
309 | try:
310 | file = sys.argv[1]
311 | except IndexError:
312 | fid = sys.stdin
313 | outfile = sys.stdout
314 | else:
315 | fid = open(file, 'r')
316 | (base, ext) = os.path.splitext(file)
317 | newname = base
318 | outfile = open(newname, 'w')
319 |
320 | allstr = fid.read()
321 | try:
322 | writestr = process_str(allstr)
323 | except ValueError as e:
324 | raise ValueError("In %s loop at %s" % (file, e)) from None
325 |
326 | outfile.write(writestr)
327 |
328 | if __name__ == "__main__":
329 | main()
330 |
--------------------------------------------------------------------------------
/mkl_fft/tests/test_fftnd.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # Copyright (c) 2017, Intel Corporation
3 | #
4 | # Redistribution and use in source and binary forms, with or without
5 | # modification, are permitted provided that the following conditions are met:
6 | #
7 | # * Redistributions of source code must retain the above copyright notice,
8 | # this list of conditions and the following disclaimer.
9 | # * Redistributions in binary form must reproduce the above copyright
10 | # notice, this list of conditions and the following disclaimer in the
11 | # documentation and/or other materials provided with the distribution.
12 | # * Neither the name of Intel Corporation nor the names of its contributors
13 | # may be used to endorse or promote products derived from this software
14 | # without specific prior written permission.
15 | #
16 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
20 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
22 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
23 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
24 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 |
27 | import numpy as np
28 | import pytest
29 | from numpy import random as rnd
30 | from numpy.testing import TestCase, assert_allclose
31 |
32 | import mkl_fft
33 |
34 | from .helper import requires_numpy_2
35 |
36 | reps_64 = (2**11) * np.finfo(np.float64).eps
37 | reps_32 = (2**11) * np.finfo(np.float32).eps
38 | atol_64 = (2**9) * np.finfo(np.float64).eps
39 | atol_32 = (2**9) * np.finfo(np.float32).eps
40 |
41 |
42 | def _get_rtol_atol(x):
43 | dt = x.dtype
44 | if dt == np.float64 or dt == np.complex128:
45 | return reps_64, atol_64
46 | elif dt == np.float32 or dt == np.complex64:
47 | return reps_32, atol_32
48 | else:
49 | assert (
50 | dt == np.float64
51 | or dt == np.complex128
52 | or dt == np.float32
53 | or dt == np.complex64
54 | ), "Unexpected dtype {}".format(dt)
55 | return reps_64, atol_64
56 |
57 |
58 | class Test_mklfft_matrix(TestCase):
59 | def setUp(self):
60 | rnd.seed(123456)
61 | self.md = rnd.randn(256, 256)
62 | self.mf = self.md.astype(np.float32)
63 | self.mz = rnd.randn(256, 256 * 2).view(np.complex128)
64 | self.mc = self.mz.astype(np.complex64)
65 |
66 | def test_matrix1(self):
67 | """fftn equals repeated fft"""
68 | for ar in [self.md, self.mz, self.mf, self.mc]:
69 | r_tol, a_tol = _get_rtol_atol(ar)
70 | d = ar.copy()
71 | t1 = mkl_fft.fftn(d)
72 | t2 = mkl_fft.fft(mkl_fft.fft(d, axis=0), axis=1)
73 | t3 = mkl_fft.fft(mkl_fft.fft(d, axis=1), axis=0)
74 | assert_allclose(
75 | t1,
76 | t2,
77 | rtol=r_tol,
78 | atol=a_tol,
79 | err_msg="failed test for dtype {}, max abs diff: {}".format(
80 | d.dtype, np.max(np.abs(t1 - t2))
81 | ),
82 | )
83 | assert_allclose(
84 | t1,
85 | t3,
86 | rtol=r_tol,
87 | atol=a_tol,
88 | err_msg="failed test for dtype {}, max abs diff: {}".format(
89 | d.dtype, np.max(np.abs(t1 - t3))
90 | ),
91 | )
92 |
93 | def test_matrix2(self):
94 | """ifftn(fftn(x)) is x"""
95 | for ar in [self.md, self.mz, self.mf, self.mc]:
96 | d = ar.copy()
97 | r_tol, a_tol = _get_rtol_atol(d)
98 | t = mkl_fft.ifftn(mkl_fft.fftn(d))
99 | assert_allclose(
100 | d,
101 | t,
102 | rtol=r_tol,
103 | atol=a_tol,
104 | err_msg="failed test for dtype {}, max abs diff: {}".format(
105 | d.dtype, np.max(np.abs(d - t))
106 | ),
107 | )
108 |
109 | def test_matrix3(self):
110 | """fftn(ifftn(x)) is x"""
111 | for ar in [self.md, self.mz, self.mf, self.mc]:
112 | d = ar.copy()
113 | r_tol, a_tol = _get_rtol_atol(d)
114 | t = mkl_fft.fftn(mkl_fft.ifftn(d))
115 | assert_allclose(
116 | d,
117 | t,
118 | rtol=r_tol,
119 | atol=a_tol,
120 | err_msg="failed test for dtype {}, max abs diff: {}".format(
121 | d.dtype, np.max(np.abs(d - t))
122 | ),
123 | )
124 |
125 | def test_matrix4(self):
126 | """fftn of strided array is same as fftn of a contiguous copy"""
127 | for ar in [self.md, self.mz, self.mf, self.mc]:
128 | r_tol, a_tol = _get_rtol_atol(ar)
129 | d_strided = ar[::2, ::2]
130 | d_contig = d_strided.copy()
131 | t_strided = mkl_fft.fftn(d_strided)
132 | t_contig = mkl_fft.fftn(d_contig)
133 | assert_allclose(t_strided, t_contig, rtol=r_tol, atol=a_tol)
134 |
135 | def test_matrix5(self):
136 | """fftn of strided array is same as fftn of a contiguous copy"""
137 | np.random.seed(1234)
138 | x = np.random.randn(6, 11, 12, 13)
139 | y = x[::-2, :, :, ::3]
140 | r_tol, a_tol = _get_rtol_atol(y)
141 | f = mkl_fft.fftn(y, axes=(1, 2))
142 | for i0 in range(y.shape[0]):
143 | for i3 in range(y.shape[3]):
144 | assert_allclose(
145 | f[i0, :, :, i3],
146 | mkl_fft.fftn(y[i0, :, :, i3]),
147 | rtol=r_tol,
148 | atol=a_tol,
149 | )
150 |
151 | def test_matrix6(self):
152 | """fftn with tuple, list and ndarray axes and s"""
153 | for ar in [self.md, self.mz, self.mf, self.mc]:
154 | d = ar.copy()
155 | for norm in [None, "forward", "backward", "ortho"]:
156 | for container in [tuple, list, np.array]:
157 | axes = container(range(d.ndim))
158 | s = container(d.shape)
159 | kwargs = dict(s=s, axes=axes, norm=norm)
160 | r_tol, a_tol = _get_rtol_atol(d)
161 | t = mkl_fft.fftn(mkl_fft.ifftn(d, **kwargs), **kwargs)
162 | assert_allclose(
163 | d,
164 | t,
165 | rtol=r_tol,
166 | atol=a_tol,
167 | err_msg="failed test for dtype {}, max abs diff: {}".format(
168 | d.dtype, np.max(np.abs(d - t))
169 | ),
170 | )
171 |
172 |
173 | class Test_Regressions(TestCase):
174 |
175 | def setUp(self):
176 | rnd.seed(123456)
177 | self.ad = rnd.randn(32, 17, 23)
178 | self.af = self.ad.astype(np.float32)
179 | self.az = rnd.randn(32, 17, 23 * 2).view(np.complex128)
180 | self.ac = self.az.astype(np.complex64)
181 |
182 | def test_cf_contig(self):
183 | """fft of F-contiguous array is the same as of C-contiguous with same data"""
184 | for ar in [self.ad, self.af, self.az, self.ac]:
185 | r_tol, a_tol = _get_rtol_atol(ar)
186 | d_ccont = ar.copy()
187 | d_fcont = np.asfortranarray(d_ccont)
188 | for a in range(ar.ndim):
189 | f1 = mkl_fft.fft(d_ccont, axis=a)
190 | f2 = mkl_fft.fft(d_fcont, axis=a)
191 | assert_allclose(f1, f2, rtol=r_tol, atol=a_tol)
192 |
193 | def test_rfftn(self):
194 | axes = [
195 | (0, 1, 2),
196 | (0, 2, 1),
197 | (1, 0, 2),
198 | (1, 2, 0),
199 | (2, 0, 1),
200 | (2, 1, 0),
201 | ]
202 | for x in [self.ad, self.af]:
203 | for a in axes:
204 | r_tol, a_tol = _get_rtol_atol(x)
205 | rfft_tr = mkl_fft.rfftn(np.transpose(x, a))
206 | tr_rfft = np.transpose(mkl_fft.rfftn(x, axes=a), a)
207 | assert_allclose(rfft_tr, tr_rfft, rtol=r_tol, atol=a_tol)
208 |
209 | def test_gh64(self):
210 | a = np.arange(12).reshape((3, 4))
211 | x = a.astype(np.cdouble)
212 | r1 = mkl_fft.fftn(a, s=None, axes=(-2, -1))
213 | r2 = mkl_fft.fftn(x)
214 | r_tol, a_tol = _get_rtol_atol(x)
215 | assert_allclose(r1, r2, rtol=r_tol, atol=a_tol)
216 |
217 |
218 | def test_gh109():
219 | b_int = np.array([[5, 7, 6, 5], [4, 6, 4, 8], [9, 3, 7, 5]], dtype=np.int64)
220 | b = np.asarray(b_int, dtype=np.float32)
221 |
222 | r1 = mkl_fft.fftn(b, s=None, axes=(0,), norm="ortho")
223 | r2 = mkl_fft.fftn(b_int, s=None, axes=(0,), norm="ortho")
224 |
225 | rtol, atol = _get_rtol_atol(b)
226 | assert_allclose(r1, r2, rtol=rtol, atol=atol)
227 |
228 |
229 | @pytest.mark.parametrize("dtype", [complex, float])
230 | @pytest.mark.parametrize("s", [(15, 24, 10), [35, 25, 15], [25, 15, 5]])
231 | @pytest.mark.parametrize("axes", [(0, 1, 2), (-1, -2, -3), [1, 0, 2]])
232 | @pytest.mark.parametrize("func", ["fftn", "ifftn", "rfftn", "irfftn"])
233 | def test_s_axes(dtype, s, axes, func):
234 | shape = (30, 20, 10)
235 | if dtype is complex and func != "rfftn":
236 | x = np.random.random(shape) + 1j * np.random.random(shape)
237 | else:
238 | x = np.random.random(shape)
239 |
240 | r1 = getattr(np.fft, func)(x, s=s, axes=axes)
241 | r2 = getattr(mkl_fft, func)(x, s=s, axes=axes)
242 |
243 | rtol, atol = _get_rtol_atol(x)
244 | assert_allclose(r1, r2, rtol=rtol, atol=atol)
245 |
246 |
247 | @requires_numpy_2
248 | @pytest.mark.parametrize("dtype", [complex, float])
249 | @pytest.mark.parametrize("s", [(15, 24, 10), [35, 25, 15], [25, 15, 5]])
250 | @pytest.mark.parametrize("axes", [(0, 1, 2), (-1, -2, -3), [1, 0, 2]])
251 | @pytest.mark.parametrize("func", ["fftn", "ifftn", "rfftn", "irfftn"])
252 | def test_s_axes_out(dtype, s, axes, func):
253 | shape = (30, 20, 10)
254 | if dtype is complex and func != "rfftn":
255 | x = np.random.random(shape) + 1j * np.random.random(shape)
256 | else:
257 | x = np.random.random(shape)
258 |
259 | r1 = getattr(np.fft, func)(x, s=s, axes=axes)
260 | out = np.empty_like(r1)
261 | r2 = getattr(mkl_fft, func)(x, s=s, axes=axes, out=out)
262 | assert r2 is out
263 |
264 | rtol, atol = _get_rtol_atol(x)
265 | assert_allclose(r1, r2, rtol=rtol, atol=atol)
266 |
267 |
268 | @pytest.mark.parametrize("dtype", [complex, float])
269 | @pytest.mark.parametrize("axes", [(2, 0, 2, 0), (0, 1, 1), (2, 0, 1, 3, 2, 1)])
270 | @pytest.mark.parametrize("func", ["rfftn", "irfftn"])
271 | def test_repeated_axes(dtype, axes, func):
272 | shape = (2, 3, 4, 5)
273 | if dtype is complex and func != "rfftn":
274 | x = np.random.random(shape) + 1j * np.random.random(shape)
275 | else:
276 | x = np.random.random(shape)
277 |
278 | r1 = getattr(np.fft, func)(x, axes=axes)
279 | r2 = getattr(mkl_fft, func)(x, axes=axes)
280 |
281 | rtol, atol = _get_rtol_atol(x)
282 | assert_allclose(r1, r2, rtol=rtol, atol=atol)
283 |
284 |
285 | @requires_numpy_2
286 | @pytest.mark.parametrize("axes", [None, (0, 1), (0, 2), (1, 2)])
287 | @pytest.mark.parametrize("func", ["fftn", "ifftn"])
288 | def test_out_strided(axes, func):
289 | shape = (20, 30, 40)
290 | x = rnd.random(shape) + 1j * rnd.random(shape)
291 | out = np.empty(shape, dtype=x.dtype)
292 |
293 | x = x[::2, ::3, ::4]
294 | out = out[::2, ::3, ::4]
295 | result = getattr(mkl_fft, func)(x, axes=axes, out=out)
296 | expected = getattr(np.fft, func)(x, axes=axes, out=out)
297 |
298 | assert_allclose(result, expected, strict=True)
299 |
--------------------------------------------------------------------------------
/.github/workflows/conda-package-cf.yml:
--------------------------------------------------------------------------------
1 | name: Conda package with conda-forge channel only
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 | pull_request:
8 |
9 | permissions: read-all
10 |
11 | env:
12 | PACKAGE_NAME: mkl_fft
13 | MODULE_NAME: mkl_fft
14 | TEST_ENV_NAME: test_mkl_fft
15 | VER_SCRIPT1: "import json; f = open('ver.json', 'r'); j = json.load(f); f.close(); d = j['mkl_fft'][0];"
16 | VER_SCRIPT2: "print('='.join((d[s] for s in ('version', 'build'))))"
17 |
18 | jobs:
19 | build_linux:
20 | runs-on: ubuntu-latest
21 | strategy:
22 | matrix:
23 | include:
24 | - python: '3.10'
25 | numpy: '2.2'
26 | - python: '3.11'
27 | numpy: '2.3'
28 | - python: '3.12'
29 | numpy: '2.3'
30 | - python: '3.13'
31 | numpy: '2.3'
32 | steps:
33 | - name: Cancel Previous Runs
34 | uses: styfle/cancel-workflow-action@85880fa0301c86cca9da44039ee3bb12d3bedbfa # 0.12.1
35 | with:
36 | access_token: ${{ github.token }}
37 |
38 | - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
39 | with:
40 | fetch-depth: 0
41 |
42 | - name: Set pkgs_dirs
43 | run: |
44 | echo "pkgs_dirs: [~/.conda/pkgs]" >> ~/.condarc
45 |
46 | - name: Cache conda packages
47 | uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
48 | env:
49 | CACHE_NUMBER: 0 # Increase to reset cache
50 | with:
51 | path: ~/.conda/pkgs
52 | key:
53 | ${{ runner.os }}-conda-${{ env.CACHE_NUMBER }}-python-${{ matrix.python }}-${{hashFiles('**/meta.yaml') }}
54 | restore-keys: |
55 | ${{ runner.os }}-conda-${{ env.CACHE_NUMBER }}-python-${{ matrix.python }}-
56 | ${{ runner.os }}-conda-${{ env.CACHE_NUMBER }}-
57 |
58 | - name: Add conda to system path
59 | run: echo $CONDA/bin >> $GITHUB_PATH
60 |
61 | - name: Install conda-build
62 | run: conda install conda-build
63 |
64 | - name: Build conda package with NumPy 2.x
65 | run: |
66 | CHANNELS="-c conda-forge --override-channels"
67 | VERSIONS="--python ${{ matrix.python }} --numpy ${{ matrix.numpy }}"
68 | TEST="--no-test"
69 |
70 | conda build \
71 | $TEST \
72 | $VERSIONS \
73 | $CHANNELS \
74 | conda-recipe-cf
75 |
76 | - name: Upload artifact
77 | uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
78 | with:
79 | name: ${{ env.PACKAGE_NAME }} ${{ runner.os }} Python ${{ matrix.python }}
80 | path: /usr/share/miniconda/conda-bld/linux-64/${{ env.PACKAGE_NAME }}-*.conda
81 |
82 | test_linux:
83 | needs: build_linux
84 | runs-on: ${{ matrix.runner }}
85 |
86 | strategy:
87 | matrix:
88 | python_ver: ['3.10', '3.11', '3.12', '3.13']
89 | numpy: ['numpy">=2"']
90 | experimental: [false]
91 | runner: [ubuntu-latest]
92 | continue-on-error: ${{ matrix.experimental }}
93 | env:
94 | CHANNELS: -c conda-forge --override-channels
95 |
96 | steps:
97 | - name: Download artifact
98 | uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
99 | with:
100 | name: ${{ env.PACKAGE_NAME }} ${{ runner.os }} Python ${{ matrix.python_ver }}
101 |
102 | - name: Add conda to system path
103 | run: echo $CONDA/bin >> $GITHUB_PATH
104 |
105 | - name: Install conda-build
106 | run: conda install conda-build
107 |
108 | - name: Create conda channel
109 | run: |
110 | mkdir -p $GITHUB_WORKSPACE/channel/linux-64
111 | mv ${PACKAGE_NAME}-*.conda $GITHUB_WORKSPACE/channel/linux-64
112 | conda index $GITHUB_WORKSPACE/channel
113 | # Test channel
114 | conda search $PACKAGE_NAME -c $GITHUB_WORKSPACE/channel --override-channels
115 |
116 | - name: Collect dependencies
117 | run: |
118 | CHANNELS="-c $GITHUB_WORKSPACE/channel ${{ env.CHANNELS }}"
119 | conda create -n ${{ env.TEST_ENV_NAME }} $PACKAGE_NAME python=${{ matrix.python_ver }} ${{ matrix.numpy }} $CHANNELS --only-deps --dry-run > lockfile
120 |
121 | - name: Display lockfile
122 | run: cat lockfile
123 |
124 | - name: Set pkgs_dirs
125 | run: |
126 | echo "pkgs_dirs: [~/.conda/pkgs]" >> ~/.condarc
127 |
128 | - name: Cache conda packages
129 | uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
130 | env:
131 | CACHE_NUMBER: 0 # Increase to reset cache
132 | with:
133 | path: ~/.conda/pkgs
134 | key:
135 | ${{ runner.os }}-conda-${{ env.CACHE_NUMBER }}-python-${{ matrix.python_ver }}-${{hashFiles('lockfile') }}
136 | restore-keys: |
137 | ${{ runner.os }}-conda-${{ env.CACHE_NUMBER }}-python-${{ matrix.python_ver }}-
138 | ${{ runner.os }}-conda-${{ env.CACHE_NUMBER }}-
139 |
140 | - name: Install mkl_fft
141 | run: |
142 | CHANNELS="-c $GITHUB_WORKSPACE/channel ${{ env.CHANNELS }}"
143 | conda create -n ${{ env.TEST_ENV_NAME }} python=${{ matrix.python_ver }} ${{ matrix.numpy }} $PACKAGE_NAME pytest scipy $CHANNELS
144 | # Test installed packages
145 | conda list -n ${{ env.TEST_ENV_NAME }}
146 |
147 | - name: Run tests
148 | run: |
149 | source $CONDA/etc/profile.d/conda.sh
150 | conda activate ${{ env.TEST_ENV_NAME }}
151 | pytest -v --pyargs $MODULE_NAME
152 |
153 | build_windows:
154 | runs-on: windows-latest
155 |
156 | strategy:
157 | matrix:
158 | include:
159 | - python: '3.10'
160 | numpy: '2.2'
161 | - python: '3.11'
162 | numpy: '2.3'
163 | - python: '3.12'
164 | numpy: '2.3'
165 | - python: '3.13'
166 | numpy: '2.3'
167 | steps:
168 | - name: Cancel Previous Runs
169 | uses: styfle/cancel-workflow-action@85880fa0301c86cca9da44039ee3bb12d3bedbfa # 0.12.1
170 | with:
171 | access_token: ${{ github.token }}
172 |
173 | - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
174 | with:
175 | fetch-depth: 0
176 |
177 | - uses: conda-incubator/setup-miniconda@835234971496cad1653abb28a638a281cf32541f # v3.2.0
178 | with:
179 | miniforge-version: latest
180 | activate-environment: build
181 | python-version: ${{ matrix.python }}
182 | channels: conda-forge
183 | conda-remove-defaults: 'true'
184 |
185 | - name: Install conda-build
186 | run: conda install -n base conda-build
187 |
188 | - name: Cache conda packages
189 | uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
190 | env:
191 | CACHE_NUMBER: 3 # Increase to reset cache
192 | with:
193 | path: /home/runner/conda_pkgs_dir
194 | key:
195 | ${{ runner.os }}-conda-${{ env.CACHE_NUMBER }}-python-${{ matrix.python }}-${{hashFiles('**/meta.yaml') }}
196 | restore-keys: |
197 | ${{ runner.os }}-conda-${{ env.CACHE_NUMBER }}-python-${{ matrix.python }}-
198 | ${{ runner.os }}-conda-${{ env.CACHE_NUMBER }}-
199 |
200 | - name: Setup MSVC
201 | uses: ilammy/msvc-dev-cmd@0b201ec74fa43914dc39ae48a89fd1d8cb592756 # v1.13.0
202 |
203 | - name: Build conda package with NumPy 2.x
204 | run: |
205 | conda activate
206 | conda build --no-test --python ${{ matrix.python }} --numpy ${{ matrix.numpy }} -c conda-forge --override-channels conda-recipe-cf
207 |
208 | - name: Store conda paths as envs
209 | shell: bash -l {0}
210 | run: |
211 | echo "CONDA_BLD=$CONDA/conda-bld/win-64/" | tr "\\\\" '/' >> $GITHUB_ENV
212 |
213 | - name: Upload artifact
214 | uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
215 | with:
216 | name: ${{ env.PACKAGE_NAME }} ${{ runner.os }} Python ${{ matrix.python }}
217 | path: ${{ env.CONDA_BLD }}${{ env.PACKAGE_NAME }}-*.conda
218 |
219 | test_windows:
220 | needs: build_windows
221 | runs-on: ${{ matrix.runner }}
222 | defaults:
223 | run:
224 | shell: cmd /C CALL {0}
225 |
226 | strategy:
227 | matrix:
228 | python_ver: ['3.10', '3.11', '3.12', '3.13']
229 | numpy: ['numpy">=2"']
230 | experimental: [false]
231 | runner: [windows-latest]
232 | continue-on-error: ${{ matrix.experimental }}
233 | env:
234 | workdir: '${{ github.workspace }}'
235 | CHANNELS: -c conda-forge --override-channels
236 |
237 | steps:
238 | - name: Download artifact
239 | uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
240 | with:
241 | name: ${{ env.PACKAGE_NAME }} ${{ runner.os }} Python ${{ matrix.python_ver }}
242 |
243 | - uses: conda-incubator/setup-miniconda@835234971496cad1653abb28a638a281cf32541f # v3.2.0
244 | with:
245 | miniforge-version: latest
246 | activate-environment: ${{ env.TEST_ENV_NAME }}
247 | python-version: ${{ matrix.python }}
248 | channels: conda-forge
249 | conda-remove-defaults: 'true'
250 |
251 | - name: Create conda channel with the artifact bit
252 | shell: cmd /C CALL {0}
253 | run: |
254 | echo ${{ env.workdir }}
255 | mkdir ${{ env.workdir }}\channel\
256 | mkdir ${{ env.workdir }}\channel\win-64
257 | move ${{ env.PACKAGE_NAME }}-*.conda ${{ env.workdir }}\channel\win-64
258 | dir ${{ env.workdir }}\channel\win-64
259 |
260 | - name: Install conda index
261 | shell: cmd /C CALL {0}
262 | run: conda install -n base conda-index
263 |
264 | - name: Index the channel
265 | shell: cmd /C CALL {0}
266 | run: conda index ${{ env.workdir }}\channel
267 |
268 | - name: Dump mkl_fft version info from created channel into ver.json
269 | shell: cmd /C CALL {0}
270 | run: |
271 | conda search ${{ env.PACKAGE_NAME }} -c ${{ env.workdir }}/channel --override-channels --info --json > ${{ env.workdir }}\ver.json
272 |
273 | - name: Output content of produced ver.json
274 | shell: pwsh
275 | run: Get-Content -Path ${{ env.workdir }}\ver.json
276 |
277 | - name: Collect dependencies
278 | shell: cmd /C CALL {0}
279 | run: |
280 | @ECHO ON
281 | IF NOT EXIST ver.json (
282 | copy /Y ${{ env.workdir }}\ver.json .
283 | )
284 | SET "SCRIPT=%VER_SCRIPT1% %VER_SCRIPT2%"
285 | FOR /F "tokens=* USEBACKQ" %%F IN (`python -c "%SCRIPT%"`) DO (
286 | SET PACKAGE_VERSION=%%F
287 | )
288 | conda install -n ${{ env.TEST_ENV_NAME }} ${{ env.PACKAGE_NAME }}=%PACKAGE_VERSION% python=${{ matrix.python_ver }} ${{ matrix.numpy }} -c ${{ env.workdir }}/channel ${{ env.CHANNELS }} --only-deps --dry-run > lockfile
289 |
290 | - name: Display lockfile content
291 | shell: pwsh
292 | run: Get-Content -Path .\lockfile
293 |
294 | - name: Cache conda packages
295 | uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
296 | env:
297 | CACHE_NUMBER: 0 # Increase to reset cache
298 | with:
299 | path: /home/runner/conda_pkgs_dir
300 | key:
301 | ${{ runner.os }}-conda-${{ env.CACHE_NUMBER }}-python-${{ matrix.python_ver }}-${{hashFiles('lockfile') }}
302 | restore-keys: |
303 | ${{ runner.os }}-conda-${{ env.CACHE_NUMBER }}-python-${{ matrix.python_ver }}-
304 | ${{ runner.os }}-conda-${{ env.CACHE_NUMBER }}-
305 |
306 | - name: Install mkl_fft
307 | shell: cmd /C CALL {0}
308 | run: |
309 | @ECHO ON
310 | IF NOT EXIST ver.json (
311 | copy /Y ${{ env.workdir }}\ver.json .
312 | )
313 | set "SCRIPT=%VER_SCRIPT1% %VER_SCRIPT2%"
314 | FOR /F "tokens=* USEBACKQ" %%F IN (`python -c "%SCRIPT%"`) DO (
315 | SET PACKAGE_VERSION=%%F
316 | )
317 | SET "TEST_DEPENDENCIES=pytest scipy"
318 | 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 }}
319 |
320 | - name: Report content of test environment
321 | shell: cmd /C CALL {0}
322 | run: |
323 | echo "Value of CONDA environment variable was: " %CONDA%
324 | echo "Value of CONDA_PREFIX environment variable was: " %CONDA_PREFIX%
325 | conda info && conda list -n ${{ env.TEST_ENV_NAME }}
326 |
327 | - name: Run tests
328 | shell: cmd /C CALL {0}
329 | run: >-
330 | conda activate ${{ env.TEST_ENV_NAME }} && python -m pytest -v -s --pyargs ${{ env.MODULE_NAME }}
331 |
--------------------------------------------------------------------------------
/.github/workflows/conda-package.yml:
--------------------------------------------------------------------------------
1 | name: Conda package
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 | pull_request:
8 |
9 | permissions: read-all
10 |
11 | env:
12 | PACKAGE_NAME: mkl_fft
13 | MODULE_NAME: mkl_fft
14 | TEST_ENV_NAME: test_mkl_fft
15 | VER_SCRIPT1: "import json; f = open('ver.json', 'r'); j = json.load(f); f.close(); d = j['mkl_fft'][0];"
16 | VER_SCRIPT2: "print('='.join((d[s] for s in ('version', 'build'))))"
17 |
18 | jobs:
19 | build_linux:
20 | runs-on: ubuntu-latest
21 | strategy:
22 | matrix:
23 | python: ['3.10', '3.11', '3.12', '3.13']
24 | steps:
25 | - name: Cancel Previous Runs
26 | uses: styfle/cancel-workflow-action@85880fa0301c86cca9da44039ee3bb12d3bedbfa # 0.12.1
27 | with:
28 | access_token: ${{ github.token }}
29 |
30 | - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
31 | with:
32 | fetch-depth: 0
33 |
34 | - name: Set pkgs_dirs
35 | run: |
36 | echo "pkgs_dirs: [~/.conda/pkgs]" >> ~/.condarc
37 |
38 | - name: Cache conda packages
39 | uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
40 | env:
41 | CACHE_NUMBER: 0 # Increase to reset cache
42 | with:
43 | path: ~/.conda/pkgs
44 | key:
45 | ${{ runner.os }}-conda-${{ env.CACHE_NUMBER }}-python-${{ matrix.python }}-${{hashFiles('**/meta.yaml') }}
46 | restore-keys: |
47 | ${{ runner.os }}-conda-${{ env.CACHE_NUMBER }}-python-${{ matrix.python }}-
48 | ${{ runner.os }}-conda-${{ env.CACHE_NUMBER }}-
49 |
50 | - name: Add conda to system path
51 | run: echo $CONDA/bin >> $GITHUB_PATH
52 |
53 | - name: Install conda-build
54 | run: conda install conda-build
55 |
56 | - name: Build conda package
57 | run: |
58 | CHANNELS="-c https://software.repos.intel.com/python/conda -c conda-forge --override-channels"
59 | VERSIONS="--python ${{ matrix.python }}"
60 | TEST="--no-test"
61 |
62 | conda build \
63 | $TEST \
64 | $VERSIONS \
65 | $CHANNELS \
66 | conda-recipe
67 |
68 | - name: Upload artifact
69 | uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
70 | with:
71 | name: ${{ env.PACKAGE_NAME }} ${{ runner.os }} Python ${{ matrix.python }}
72 | path: /usr/share/miniconda/conda-bld/linux-64/${{ env.PACKAGE_NAME }}-*.conda
73 |
74 | test_linux:
75 | needs: build_linux
76 | runs-on: ${{ matrix.runner }}
77 |
78 | strategy:
79 | matrix:
80 | python: ['3.10', '3.11', '3.12', '3.13']
81 | experimental: [false]
82 | runner: [ubuntu-latest]
83 | continue-on-error: ${{ matrix.experimental }}
84 | env:
85 | CHANNELS: -c https://software.repos.intel.com/python/conda -c conda-forge --override-channels
86 |
87 | steps:
88 | - name: Download artifact
89 | uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
90 | with:
91 | name: ${{ env.PACKAGE_NAME }} ${{ runner.os }} Python ${{ matrix.python }}
92 |
93 | - name: Add conda to system path
94 | run: echo $CONDA/bin >> $GITHUB_PATH
95 |
96 | - name: Install conda-build
97 | run: conda install conda-build
98 |
99 | - name: Create conda channel
100 | run: |
101 | mkdir -p $GITHUB_WORKSPACE/channel/linux-64
102 | mv ${PACKAGE_NAME}-*.conda $GITHUB_WORKSPACE/channel/linux-64
103 | conda index $GITHUB_WORKSPACE/channel
104 |
105 | - name: Test conda channel
106 | run: |
107 | conda search $PACKAGE_NAME -c $GITHUB_WORKSPACE/channel --override-channels --info --json > $GITHUB_WORKSPACE/ver.json
108 | cat $GITHUB_WORKSPACE/ver.json
109 |
110 | - name: Get package version
111 | run: |
112 | export PACKAGE_VERSION=$(python -c "${{ env.VER_SCRIPT1 }} ${{ env.VER_SCRIPT2 }}")
113 | echo PACKAGE_VERSION=${PACKAGE_VERSION}
114 | echo "PACKAGE_VERSION=$PACKAGE_VERSION" >> $GITHUB_ENV
115 |
116 | - name: Collect dependencies
117 | run: |
118 | CHANNELS="-c $GITHUB_WORKSPACE/channel ${{ env.CHANNELS }}"
119 | conda create -n ${{ env.TEST_ENV_NAME }} $PACKAGE_NAME python=${{ matrix.python }} $CHANNELS --only-deps --dry-run > lockfile
120 |
121 | - name: Display lockfile
122 | run: cat lockfile
123 |
124 | - name: Set pkgs_dirs
125 | run: |
126 | echo "pkgs_dirs: [~/.conda/pkgs]" >> ~/.condarc
127 |
128 | - name: Cache conda packages
129 | uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
130 | env:
131 | CACHE_NUMBER: 0 # Increase to reset cache
132 | with:
133 | path: ~/.conda/pkgs
134 | key:
135 | ${{ runner.os }}-conda-${{ env.CACHE_NUMBER }}-python-${{ matrix.python }}-${{hashFiles('lockfile') }}
136 | restore-keys: |
137 | ${{ runner.os }}-conda-${{ env.CACHE_NUMBER }}-python-${{ matrix.python }}-
138 | ${{ runner.os }}-conda-${{ env.CACHE_NUMBER }}-
139 |
140 | - name: Install mkl_fft
141 | run: |
142 | CHANNELS="-c $GITHUB_WORKSPACE/channel ${{ env.CHANNELS }}"
143 | conda create -n ${{ env.TEST_ENV_NAME }} $PACKAGE_NAME=${{ env.PACKAGE_VERSION }} python=${{ matrix.python }} pytest $CHANNELS
144 | conda install -n ${{ env.TEST_ENV_NAME }} "scipy>=1.10" $CHANNELS
145 | # Test installed packages
146 | conda list -n ${{ env.TEST_ENV_NAME }}
147 |
148 | - name: Run tests
149 | run: |
150 | source $CONDA/etc/profile.d/conda.sh
151 | conda activate ${{ env.TEST_ENV_NAME }}
152 | pytest -v --pyargs $MODULE_NAME
153 |
154 | build_windows:
155 | runs-on: windows-latest
156 |
157 | strategy:
158 | matrix:
159 | python: ['3.10', '3.11', '3.12', '3.13']
160 | steps:
161 | - name: Cancel Previous Runs
162 | uses: styfle/cancel-workflow-action@85880fa0301c86cca9da44039ee3bb12d3bedbfa # 0.12.1
163 | with:
164 | access_token: ${{ github.token }}
165 |
166 | - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 # v6.0.0
167 | with:
168 | fetch-depth: 0
169 |
170 | - uses: conda-incubator/setup-miniconda@835234971496cad1653abb28a638a281cf32541f # v3.2.0
171 | with:
172 | miniforge-version: latest
173 | activate-environment: build
174 | python-version: ${{ matrix.python }}
175 | channels: conda-forge
176 | conda-remove-defaults: 'true'
177 |
178 | - name: Install conda-build
179 | run: |
180 | conda activate
181 | conda install -n base conda-build
182 |
183 | - name: Cache conda packages
184 | uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
185 | env:
186 | CACHE_NUMBER: 3 # Increase to reset cache
187 | with:
188 | path: /home/runner/conda_pkgs_dir
189 | key:
190 | ${{ runner.os }}-conda-${{ env.CACHE_NUMBER }}-python-${{ matrix.python }}-${{hashFiles('**/meta.yaml') }}
191 | restore-keys: |
192 | ${{ runner.os }}-conda-${{ env.CACHE_NUMBER }}-python-${{ matrix.python }}-
193 | ${{ runner.os }}-conda-${{ env.CACHE_NUMBER }}-
194 |
195 | - name: Setup MSVC
196 | uses: ilammy/msvc-dev-cmd@0b201ec74fa43914dc39ae48a89fd1d8cb592756 # v1.13.0
197 |
198 | - name: Build conda package
199 | run: |
200 | conda activate
201 | conda build --no-test --python ${{ matrix.python }} -c https://software.repos.intel.com/python/conda -c conda-forge --override-channels conda-recipe
202 |
203 | - name: Store conda paths as envs
204 | shell: bash -l {0}
205 | run: |
206 | echo "CONDA_BLD=$CONDA/conda-bld/win-64/" | tr "\\\\" '/' >> $GITHUB_ENV
207 |
208 | - name: Upload artifact
209 | uses: actions/upload-artifact@330a01c490aca151604b8cf639adc76d48f6c5d4 # v5.0.0
210 | with:
211 | name: ${{ env.PACKAGE_NAME }} ${{ runner.os }} Python ${{ matrix.python }}
212 | path: ${{ env.CONDA_BLD }}${{ env.PACKAGE_NAME }}-*.conda
213 |
214 | test_windows:
215 | needs: build_windows
216 | runs-on: ${{ matrix.runner }}
217 | defaults:
218 | run:
219 | shell: cmd /C CALL {0}
220 |
221 | strategy:
222 | matrix:
223 | python: ['3.10', '3.11', '3.12', '3.13']
224 | experimental: [false]
225 | runner: [windows-latest]
226 | continue-on-error: ${{ matrix.experimental }}
227 | env:
228 | workdir: '${{ github.workspace }}'
229 | CHANNELS: -c https://software.repos.intel.com/python/conda -c conda-forge --override-channels
230 |
231 | steps:
232 | - name: Download artifact
233 | uses: actions/download-artifact@018cc2cf5baa6db3ef3c5f8a56943fffe632ef53 # v6.0.0
234 | with:
235 | name: ${{ env.PACKAGE_NAME }} ${{ runner.os }} Python ${{ matrix.python }}
236 |
237 | - uses: conda-incubator/setup-miniconda@835234971496cad1653abb28a638a281cf32541f # v3.2.0
238 | with:
239 | miniforge-version: latest
240 | activate-environment: ${{ env.TEST_ENV_NAME }}
241 | python-version: ${{ matrix.python }}
242 | channels: conda-forge
243 | conda-remove-defaults: 'true'
244 |
245 | - name: Create conda channel with the artifact bit
246 | shell: cmd /C CALL {0}
247 | run: |
248 | echo ${{ env.workdir }}
249 | mkdir ${{ env.workdir }}\channel\
250 | mkdir ${{ env.workdir }}\channel\win-64
251 | move ${{ env.PACKAGE_NAME }}-*.conda ${{ env.workdir }}\channel\win-64
252 | dir ${{ env.workdir }}\channel\win-64
253 |
254 | - name: Install conda index
255 | shell: cmd /C CALL {0}
256 | run: conda install -n base conda-index
257 |
258 | - name: Index the channel
259 | shell: cmd /C CALL {0}
260 | run: conda index ${{ env.workdir }}\channel
261 |
262 | - name: Dump mkl_fft version info from created channel into ver.json
263 | shell: cmd /C CALL {0}
264 | run: |
265 | conda search ${{ env.PACKAGE_NAME }} -c ${{ env.workdir }}/channel --override-channels --info --json > ${{ env.workdir }}\ver.json
266 |
267 | - name: Output content of produced ver.json
268 | shell: pwsh
269 | run: Get-Content -Path ${{ env.workdir }}\ver.json
270 |
271 | - name: Collect dependencies
272 | shell: cmd /C CALL {0}
273 | run: |
274 | @ECHO ON
275 | IF NOT EXIST ver.json (
276 | copy /Y ${{ env.workdir }}\ver.json .
277 | )
278 | SET "SCRIPT=%VER_SCRIPT1% %VER_SCRIPT2%"
279 | FOR /F "tokens=* USEBACKQ" %%F IN (`python -c "%SCRIPT%"`) DO (
280 | SET PACKAGE_VERSION=%%F
281 | )
282 | conda install -n ${{ env.TEST_ENV_NAME }} ${{ env.PACKAGE_NAME }}=%PACKAGE_VERSION% python=${{ matrix.python }} -c ${{ env.workdir }}/channel ${{ env.CHANNELS }} --only-deps --dry-run > lockfile
283 |
284 | - name: Display lockfile content
285 | shell: pwsh
286 | run: Get-Content -Path .\lockfile
287 |
288 | - name: Cache conda packages
289 | uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0
290 | env:
291 | CACHE_NUMBER: 0 # Increase to reset cache
292 | with:
293 | path: /home/runner/conda_pkgs_dir
294 | key:
295 | ${{ runner.os }}-conda-${{ env.CACHE_NUMBER }}-python-${{ matrix.python }}-${{hashFiles('lockfile') }}
296 | restore-keys: |
297 | ${{ runner.os }}-conda-${{ env.CACHE_NUMBER }}-python-${{ matrix.python }}-
298 | ${{ runner.os }}-conda-${{ env.CACHE_NUMBER }}-
299 |
300 | - name: Install mkl_fft
301 | shell: cmd /C CALL {0}
302 | run: |
303 | @ECHO ON
304 | IF NOT EXIST ver.json (
305 | copy /Y ${{ env.workdir }}\ver.json .
306 | )
307 | set "SCRIPT=%VER_SCRIPT1% %VER_SCRIPT2%"
308 | FOR /F "tokens=* USEBACKQ" %%F IN (`python -c "%SCRIPT%"`) DO (
309 | SET PACKAGE_VERSION=%%F
310 | )
311 | SET "TEST_DEPENDENCIES=pytest"
312 | conda install -n ${{ env.TEST_ENV_NAME }} ${{ env.PACKAGE_NAME }}=%PACKAGE_VERSION% %TEST_DEPENDENCIES% python=${{ matrix.python }} -c ${{ env.workdir }}/channel ${{ env.CHANNELS }}
313 | conda install -n ${{ env.TEST_ENV_NAME }} scipy -c ${{ env.workdir }}/channel ${{ env.CHANNELS }}
314 | }
315 | - name: Report content of test environment
316 | shell: cmd /C CALL {0}
317 | run: |
318 | echo "Value of CONDA environment variable was: " %CONDA%
319 | echo "Value of CONDA_PREFIX environment variable was: " %CONDA_PREFIX%
320 | conda info && conda list -n ${{ env.TEST_ENV_NAME }}
321 |
322 | - name: Run tests
323 | shell: cmd /C CALL {0}
324 | run: >-
325 | conda activate ${{ env.TEST_ENV_NAME }} && python -m pytest -v -s --pyargs ${{ env.MODULE_NAME }}
326 |
--------------------------------------------------------------------------------
/mkl_fft/tests/test_fft1d.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # Copyright (c) 2017, Intel Corporation
3 | #
4 | # Redistribution and use in source and binary forms, with or without
5 | # modification, are permitted provided that the following conditions are met:
6 | #
7 | # * Redistributions of source code must retain the above copyright notice,
8 | # this list of conditions and the following disclaimer.
9 | # * Redistributions in binary form must reproduce the above copyright
10 | # notice, this list of conditions and the following disclaimer in the
11 | # documentation and/or other materials provided with the distribution.
12 | # * Neither the name of Intel Corporation nor the names of its contributors
13 | # may be used to endorse or promote products derived from this software
14 | # without specific prior written permission.
15 | #
16 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS """AS IS"
17 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
20 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
22 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
23 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
24 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 |
27 | import numpy as np
28 | import pytest
29 | from numpy import random as rnd
30 | from numpy.testing import TestCase, assert_, assert_allclose
31 |
32 | import mkl_fft
33 |
34 | from .helper import requires_numpy_2
35 |
36 |
37 | def naive_fft1d(vec):
38 | L = len(vec)
39 | phase = -2j * np.pi * (np.arange(L) / float(L))
40 | phase = np.arange(L).reshape(-1, 1) * phase
41 | return np.sum(vec * np.exp(phase), axis=1)
42 |
43 |
44 | def _datacopied(arr, original):
45 | """
46 | Strict check for `arr` not sharing any data with `original`,
47 | under the assumption that arr = asarray(original)
48 | """
49 | if arr is original:
50 | return False
51 | if not isinstance(original, np.ndarray) and hasattr(original, "__array__"):
52 | return False
53 | return arr.base is None
54 |
55 |
56 | class Test_mklfft_vector(TestCase):
57 | def setUp(self):
58 | rnd.seed(1234567)
59 | self.xd1 = rnd.standard_normal(128)
60 | self.xf1 = self.xd1.astype(np.float32)
61 | self.xz1 = (
62 | rnd.standard_normal((128, 2)).view(dtype=np.complex128).squeeze()
63 | )
64 | self.xc1 = self.xz1.astype(np.complex64)
65 |
66 | def test_vector1(self):
67 | """check that mkl_fft gives the same result of numpy.fft"""
68 | f1 = mkl_fft.fft(self.xz1)
69 | f2 = naive_fft1d(self.xz1)
70 | assert_allclose(f1, f2, rtol=1e-7, atol=2e-12)
71 |
72 | f1 = mkl_fft.fft(self.xc1)
73 | f2 = naive_fft1d(self.xc1)
74 | assert_allclose(f1, f2, rtol=2e-6, atol=2e-6)
75 |
76 | def test_vector2(self):
77 | """ifft(fft(x)) is identity"""
78 | f1 = mkl_fft.fft(self.xz1)
79 | f2 = mkl_fft.ifft(f1)
80 | assert_(np.allclose(self.xz1, f2))
81 |
82 | f1 = mkl_fft.fft(self.xc1)
83 | f2 = mkl_fft.ifft(f1)
84 | assert_(np.allclose(self.xc1, f2))
85 |
86 | f1 = mkl_fft.fft(self.xd1)
87 | f2 = mkl_fft.ifft(f1)
88 | assert_(np.allclose(self.xd1, f2))
89 |
90 | f1 = mkl_fft.fft(self.xf1)
91 | f2 = mkl_fft.ifft(f1)
92 | assert_(np.allclose(self.xf1, f2, atol=2.0e-7))
93 |
94 | def test_vector3(self):
95 | """fft(ifft(x)) is identity"""
96 | f1 = mkl_fft.ifft(self.xz1)
97 | f2 = mkl_fft.fft(f1)
98 | assert_(np.allclose(self.xz1, f2))
99 |
100 | f1 = mkl_fft.ifft(self.xc1)
101 | f2 = mkl_fft.fft(f1)
102 | assert_(np.allclose(self.xc1, f2))
103 |
104 | f1 = mkl_fft.ifft(self.xd1)
105 | f2 = mkl_fft.fft(f1)
106 | assert_(np.allclose(self.xd1, f2))
107 |
108 | f1 = mkl_fft.ifft(self.xf1)
109 | f2 = mkl_fft.fft(f1)
110 | assert_(np.allclose(self.xf1, f2, atol=2.0e-7))
111 |
112 | def test_vector4(self):
113 | """fft of strided is same as fft of contiguous copy"""
114 | x = self.xz1[::2]
115 | f1 = mkl_fft.fft(x)
116 | f2 = mkl_fft.fft(x.copy())
117 | assert_(np.allclose(f1, f2))
118 |
119 | x = self.xz1[::-1]
120 | f1 = mkl_fft.fft(x)
121 | f2 = mkl_fft.fft(x.copy())
122 | assert_(np.allclose(f1, f2))
123 |
124 | def test_vector5(self):
125 | """fft in-place is the same as fft out-of-place"""
126 | x = self.xz1.copy()[::-2]
127 | f1 = mkl_fft.fft(x, out=x)
128 | f2 = mkl_fft.fft(self.xz1[::-2])
129 | assert_(np.allclose(f1, f2))
130 |
131 | def test_vector6(self):
132 | """fft in place"""
133 | x = self.xz1.copy()
134 | f1 = mkl_fft.fft(x, out=x)
135 | assert_(not _datacopied(f1, x)) # this is in-place
136 |
137 | x = self.xz1.copy()
138 | f1 = mkl_fft.fft(x[::-2], out=x[::-2])
139 | assert_(not np.allclose(x, self.xz1)) # this is also in-place
140 | assert_(np.allclose(x[-2::-2], self.xz1[-2::-2]))
141 | assert_(np.allclose(x[-1::-2], f1))
142 |
143 | def test_vector7(self):
144 | """fft of real array is the same as fft of its complex cast"""
145 | x = self.xd1[3:17:2]
146 | f1 = mkl_fft.fft(x)
147 | f2 = mkl_fft.fft(x.astype(np.complex128))
148 | assert_(np.allclose(f1, f2))
149 |
150 | def test_vector8(self):
151 | """ifft of real array is the same as fft of its complex cast"""
152 | x = self.xd1[3:17:2]
153 | f1 = mkl_fft.ifft(x)
154 | f2 = mkl_fft.ifft(x.astype(np.complex128))
155 | assert_(np.allclose(f1, f2))
156 |
157 | def test_vector9(self):
158 | """works on subtypes of ndarray"""
159 | mask = np.zeros(self.xd1.shape, dtype="int")
160 | mask[1] = 1
161 | mask[-2] = 1
162 | x = np.ma.masked_array(self.xd1, mask=mask)
163 | f1 = mkl_fft.fft(x)
164 | f2 = mkl_fft.fft(self.xd1)
165 | assert_allclose(f1, f2)
166 |
167 | def test_vector10(self):
168 | """check n for real arrays"""
169 | x = self.xd1[:8].copy()
170 | f1 = mkl_fft.fft(x, n=7)
171 | f2 = mkl_fft.fft(self.xd1[:7])
172 | assert_allclose(f1, f2)
173 |
174 | f1 = mkl_fft.fft(x, n=9)
175 | y = self.xd1[:9].copy()
176 | y[-1] = 0.0
177 | f2 = mkl_fft.fft(y)
178 | assert_allclose(f1, f2)
179 |
180 | def test_vector11(self):
181 | """check n for complex arrays"""
182 | x = self.xz1[:8].copy()
183 | f1 = mkl_fft.fft(x, n=7)
184 | f2 = mkl_fft.fft(self.xz1[:7])
185 | assert_allclose(f1, f2)
186 |
187 | f1 = mkl_fft.fft(x, n=9)
188 | y = self.xz1[:9].copy()
189 | y[-1] = 0.0 + 0.0j
190 | f2 = mkl_fft.fft(y)
191 | assert_allclose(f1, f2)
192 |
193 | def test_vector12(self):
194 | """check fft of float-valued array"""
195 | x = np.arange(20)
196 | f1 = mkl_fft.fft(x)
197 | f2 = mkl_fft.fft(x.astype(np.float64))
198 | assert_allclose(f1, f2)
199 |
200 |
201 | class DuckArray(np.ndarray):
202 | pass
203 |
204 |
205 | class Test_mklfft_matrix(TestCase):
206 | def setUp(self):
207 | rnd.seed(1234567)
208 | self.ad2 = rnd.standard_normal((4, 3))
209 | self.af2 = self.ad2.astype(np.float32)
210 | self.az2 = np.dot(
211 | rnd.standard_normal((17, 15, 2)),
212 | np.array([1.0 + 0.0j, 0.0 + 1.0j], dtype=np.complex128),
213 | )
214 | self.ac2 = self.az2.astype(np.complex64)
215 | self.mat = self.az2.view(DuckArray)
216 | self.xd1 = rnd.standard_normal(128)
217 |
218 | def test_matrix1(self):
219 | x = self.az2.copy()
220 | f1 = mkl_fft.fft(x)
221 | f2 = np.array([mkl_fft.fft(x[i]) for i in range(x.shape[0])])
222 | assert_allclose(f1, f2)
223 |
224 | f1 = mkl_fft.fft(x, axis=0)
225 | f2 = np.array([mkl_fft.fft(x[:, i]) for i in range(x.shape[1])]).T
226 | assert_allclose(f1, f2)
227 |
228 | def test_matrix2(self):
229 | f1 = mkl_fft.fft(self.az2)
230 | f2 = mkl_fft.fft(self.mat)
231 | assert_allclose(f1, f2)
232 |
233 | def test_matrix3(self):
234 | x = self.az2.copy()
235 | f1 = mkl_fft.fft(x[::3, ::-1])
236 | f2 = mkl_fft.fft(x[::3, ::-1].copy())
237 | assert_allclose(f1, f2)
238 |
239 | def test_matrix4(self):
240 | x = self.az2.copy()
241 | f1 = mkl_fft.fft(x[::3, ::-1])
242 | f2 = mkl_fft.fft(x[::3, ::-1], out=x[::3, ::-1])
243 | assert_allclose(f1, f2)
244 |
245 | def test_matrix5(self):
246 | x = self.ad2
247 | f1 = mkl_fft.fft(x)
248 | f2 = mkl_fft.ifft(f1)
249 | assert_allclose(x, f2, atol=1e-10)
250 |
251 | def test_matrix6(self):
252 | x = self.ad2
253 | f1 = mkl_fft.ifft(x)
254 | f2 = mkl_fft.fft(f1)
255 | assert_allclose(x, f2, atol=1e-10)
256 |
257 | def test_matrix7(self):
258 | x = self.ad2.copy()
259 | f1 = mkl_fft.fft(x)
260 | f2 = np.array([mkl_fft.fft(x[i]) for i in range(x.shape[0])])
261 | assert_allclose(f1, f2)
262 |
263 | f1 = mkl_fft.fft(x, axis=0)
264 | f2 = np.array([mkl_fft.fft(x[:, i]) for i in range(x.shape[1])]).T
265 | assert_allclose(f1, f2)
266 |
267 | def test_matrix8(self):
268 | from numpy.lib.stride_tricks import as_strided
269 |
270 | x = self.xd1[:10].copy()
271 | y = as_strided(
272 | x,
273 | shape=(
274 | 4,
275 | 4,
276 | ),
277 | strides=(2 * x.itemsize, x.itemsize),
278 | )
279 | f1 = mkl_fft.fft(y)
280 | f2 = mkl_fft.fft(y.copy())
281 | assert_allclose(f1, f2, atol=1e-15, rtol=1e-7)
282 |
283 |
284 | class Test_mklfft_rank3(TestCase):
285 | def setUp(self):
286 | rnd.seed(1234567)
287 | self.ad3 = rnd.standard_normal((7, 11, 19))
288 | self.af3 = self.ad3.astype(np.float32)
289 | self.az3 = np.dot(
290 | rnd.standard_normal((17, 13, 15, 2)),
291 | np.array([1.0 + 0.0j, 0.0 + 1.0j], dtype=np.complex128),
292 | )
293 | self.ac3 = self.az3.astype(np.complex64)
294 |
295 | def test_array1(self):
296 | x = self.az3
297 | for ax in range(x.ndim):
298 | f1 = mkl_fft.fft(x, axis=ax)
299 | f2 = mkl_fft.ifft(f1, axis=ax)
300 | assert_allclose(f2, x, atol=2e-15)
301 |
302 | def test_array2(self):
303 | x = self.ad3
304 | for ax in range(x.ndim):
305 | f1 = mkl_fft.fft(x, axis=ax)
306 | f2 = mkl_fft.ifft(f1, axis=ax)
307 | assert_allclose(f2, x, atol=2e-15)
308 |
309 | def test_array3(self):
310 | x = self.az3
311 | for ax in range(x.ndim):
312 | f1 = mkl_fft.ifft(x, axis=ax)
313 | f2 = mkl_fft.fft(f1, axis=ax)
314 | assert_allclose(f2, x, atol=2e-15)
315 |
316 | def test_array4(self):
317 | x = self.ad3
318 | for ax in range(x.ndim):
319 | f1 = mkl_fft.ifft(x, axis=ax)
320 | f2 = mkl_fft.fft(f1, axis=ax)
321 | assert_allclose(f2, x, atol=2e-15)
322 |
323 | def test_array5(self):
324 | """Inputs with zero strides are handled correctly"""
325 | z = self.az3
326 | z1 = z[np.newaxis]
327 | f1 = mkl_fft.fft(z1, axis=-1)
328 | f2 = mkl_fft.fft(z1.reshape(z1.shape), axis=-1)
329 | assert_allclose(f1, f2, atol=2e-15)
330 | z1 = z[:, np.newaxis]
331 | f1 = mkl_fft.fft(z1, axis=-1)
332 | f2 = mkl_fft.fft(z1.reshape(z1.shape), axis=-1)
333 | assert_allclose(f1, f2, atol=2e-15)
334 | z1 = z[:, :, np.newaxis]
335 | f1 = mkl_fft.fft(z1, axis=-1)
336 | f2 = mkl_fft.fft(z1.reshape(z1.shape), axis=-1)
337 | assert_allclose(f1, f2, atol=2e-15)
338 | z1 = z[:, :, :, np.newaxis]
339 | f1 = mkl_fft.fft(z1, axis=-1)
340 | f2 = mkl_fft.fft(z1.reshape(z1.shape), axis=-1)
341 | assert_allclose(f1, f2, atol=2e-15)
342 |
343 | def test_array6(self):
344 | """Inputs with Fortran layout are handled correctly, issue 29"""
345 | z = self.az3
346 | z = z.astype(z.dtype, order="F")
347 | y1 = mkl_fft.fft(z, axis=0)
348 | y2 = mkl_fft.fft(self.az3, axis=0)
349 | assert_allclose(y1, y2, atol=2e-15)
350 | y1 = mkl_fft.fft(z, axis=-1)
351 | y2 = mkl_fft.fft(self.az3, axis=-1)
352 | assert_allclose(y1, y2, atol=2e-15)
353 |
354 |
355 | @requires_numpy_2
356 | @pytest.mark.parametrize("axis", [0, 1, 2])
357 | @pytest.mark.parametrize("func", ["fft", "ifft"])
358 | def test_fft_out_strided(axis, func):
359 | shape = (20, 33, 54)
360 | x = rnd.random(shape) + 1j * rnd.random(shape)
361 | out = np.empty(shape, dtype=x.dtype)
362 |
363 | x = x[::2, ::3, ::4]
364 | out = np.empty(x.shape, dtype=x.dtype)
365 | result = getattr(mkl_fft, func)(x, axis=axis, out=out)
366 | expected = getattr(np.fft, func)(x, axis=axis, out=out)
367 |
368 | assert_allclose(result, expected)
369 |
370 |
371 | @requires_numpy_2
372 | @pytest.mark.parametrize("axis", [0, 1, 2])
373 | def test_rfft_out_strided(axis):
374 | shape = (20, 33, 54)
375 | x = rnd.random(shape)
376 | if axis == 0:
377 | out_sh = (12, 33, 54)
378 | elif axis == 1:
379 | out_sh = (20, 18, 54)
380 | else: # axis == 2
381 | out_sh = (20, 33, 32)
382 | out = np.empty(out_sh, dtype=np.complex128)
383 |
384 | x = x[::2, ::3, ::4]
385 | out = out[::2, ::3, ::4]
386 | result = mkl_fft.rfft(x, axis=axis, out=out)
387 | expected = np.fft.rfft(x, axis=axis, out=out)
388 |
389 | assert_allclose(result, expected)
390 |
391 |
392 | @requires_numpy_2
393 | @pytest.mark.parametrize("axis", [0, 1, 2])
394 | def test_irfft_out_strided(axis):
395 | shape = (20, 33, 54)
396 | x = rnd.random(shape) + 1j * rnd.random(shape)
397 | if axis == 0:
398 | out_sh = (36, 33, 54)
399 | elif axis == 1:
400 | out_sh = (20, 60, 54)
401 | else: # axis == 2
402 | out_sh = (20, 33, 104)
403 | out = np.empty(out_sh, dtype=np.float64)
404 |
405 | x = x[::2, ::3, ::4]
406 | out = out[::2, ::3, ::4]
407 | result = mkl_fft.irfft(x, axis=axis, out=out)
408 | expected = np.fft.irfft(x, axis=axis, out=out)
409 |
410 | assert_allclose(result, expected)
411 |
412 |
413 | @requires_numpy_2
414 | @pytest.mark.parametrize("dt", ["i4", "f4", "f8", "c8", "c16"])
415 | def test_irfft_dtype(dt):
416 | x = np.array(rnd.random((20, 20)), dtype=dt)
417 | result = mkl_fft.irfft(x)
418 | expected = np.fft.irfft(x)
419 | assert_allclose(result, expected, rtol=1e-7, atol=1e-7, strict=True)
420 |
--------------------------------------------------------------------------------
/mkl_fft/interfaces/_scipy_fft.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # Copyright (c) 2017, Intel Corporation
3 | #
4 | # Redistribution and use in source and binary forms, with or without
5 | # modification, are permitted provided that the following conditions are met:
6 | #
7 | # * Redistributions of source code must retain the above copyright notice,
8 | # this list of conditions and the following disclaimer.
9 | # * Redistributions in binary form must reproduce the above copyright
10 | # notice, this list of conditions and the following disclaimer in the
11 | # documentation and/or other materials provided with the distribution.
12 | # * Neither the name of Intel Corporation nor the names of its contributors
13 | # may be used to endorse or promote products derived from this software
14 | # without specific prior written permission.
15 | #
16 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
20 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
22 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
23 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
24 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 |
27 | """
28 | An interface for FFT module of SciPy (`scipy.fft`) that uses OneMKL FFT
29 | in the backend.
30 | """
31 |
32 | import contextlib
33 | import contextvars
34 | import operator
35 | from numbers import Number
36 |
37 | import mkl
38 | import numpy as np
39 | import scipy
40 |
41 | import mkl_fft
42 |
43 | from .._fft_utils import _swap_direction
44 | from ._float_utils import _supported_array_or_not_implemented
45 |
46 | __all__ = [
47 | "fft",
48 | "ifft",
49 | "fft2",
50 | "ifft2",
51 | "fftn",
52 | "ifftn",
53 | "rfft",
54 | "irfft",
55 | "rfft2",
56 | "irfft2",
57 | "rfftn",
58 | "irfftn",
59 | "hfft",
60 | "ihfft",
61 | "hfft2",
62 | "ihfft2",
63 | "hfftn",
64 | "ihfftn",
65 | "fftshift",
66 | "ifftshift",
67 | "fftfreq",
68 | "rfftfreq",
69 | "get_workers",
70 | "set_workers",
71 | ]
72 |
73 |
74 | class _workers_data:
75 | def __init__(self, workers=None):
76 | if workers is not None: # workers = 0 should be handled
77 | self.workers_ = _workers_to_num_threads(workers)
78 | else:
79 | # Unlike SciPy, the default value is maximum number of threads
80 | self.workers_ = mkl.get_max_threads() # pylint: disable=no-member
81 | self.workers_ = operator.index(self.workers_)
82 |
83 | @property
84 | def workers(self):
85 | return self.workers_
86 |
87 | @workers.setter
88 | def workers(self, workers_val):
89 | self.workerks_ = operator.index(workers_val)
90 |
91 |
92 | _workers_global_settings = contextvars.ContextVar(
93 | "scipy_backend_workers", default=_workers_data()
94 | )
95 |
96 |
97 | def _workers_to_num_threads(w):
98 | """
99 | Handle conversion of workers to a positive number of threads in the
100 | same way as scipy.fft._pocketfft.helpers._workers.
101 | """
102 | if w is None:
103 | return _workers_global_settings.get().workers
104 | _w = operator.index(w)
105 | if _w == 0:
106 | raise ValueError("Number of workers must not be zero")
107 | if _w < 0:
108 | # SciPy uses os.cpu_count()
109 | _cpu_count = mkl.get_max_threads() # pylint: disable=no-member
110 | _w += _cpu_count + 1
111 | if _w <= 0:
112 | raise ValueError(
113 | f"workers value out of range; got {w}, must not be less "
114 | f"than {-_cpu_count}"
115 | )
116 | return _w
117 |
118 |
119 | class _Workers:
120 | def __init__(self, workers):
121 | self.workers = workers
122 | self.n_threads = _workers_to_num_threads(workers)
123 |
124 | def __enter__(self):
125 | try:
126 | # mkl.set_num_threads_local sets the number of threads to the
127 | # given input number, and returns the previous number of threads
128 | # pylint: disable=no-member
129 | self.prev_num_threads = mkl.set_num_threads_local(self.n_threads)
130 | except Exception as e:
131 | raise ValueError(
132 | f"Class argument {self.workers} results in invalid number of "
133 | f"threads {self.n_threads}"
134 | ) from e
135 | return self
136 |
137 | def __exit__(self, *args):
138 | # restore old value
139 | # pylint: disable=no-member
140 | mkl.set_num_threads_local(self.prev_num_threads)
141 |
142 |
143 | def _check_plan(plan):
144 | if plan is not None:
145 | raise NotImplementedError(
146 | f"Passing a precomputed plan with value={plan} is currently not supported"
147 | )
148 |
149 |
150 | # copied from scipy.fft._pocketfft.helper
151 | # https://github.com/scipy/scipy/blob/main/scipy/fft/_pocketfft/helper.py
152 | def _iterable_of_int(x, name=None):
153 | if isinstance(x, Number):
154 | x = (x,)
155 |
156 | try:
157 | x = [operator.index(a) for a in x]
158 | except TypeError as e:
159 | name = name or "value"
160 | raise ValueError(
161 | f"{name} must be a scalar or iterable of integers"
162 | ) from e
163 |
164 | return x
165 |
166 |
167 | # copied and modified from scipy.fft._pocketfft.helper
168 | # https://github.com/scipy/scipy/blob/main/scipy/fft/_pocketfft/helper.py
169 | def _init_nd_shape_and_axes(x, shape, axes, invreal=False):
170 | noshape = shape is None
171 | noaxes = axes is None
172 |
173 | if not noaxes:
174 | axes = _iterable_of_int(axes, "axes")
175 | axes = [a + x.ndim if a < 0 else a for a in axes]
176 |
177 | if any(a >= x.ndim or a < 0 for a in axes):
178 | raise ValueError("axes exceeds dimensionality of input")
179 | if len(set(axes)) != len(axes):
180 | raise ValueError("all axes must be unique")
181 |
182 | if not noshape:
183 | shape = _iterable_of_int(shape, "shape")
184 |
185 | if axes and len(axes) != len(shape):
186 | raise ValueError(
187 | "when given, axes and shape arguments"
188 | " have to be of the same length"
189 | )
190 | if noaxes:
191 | if len(shape) > x.ndim:
192 | raise ValueError("shape requires more axes than are present")
193 | axes = range(x.ndim - len(shape), x.ndim)
194 |
195 | shape = [x.shape[a] if s == -1 else s for s, a in zip(shape, axes)]
196 | elif noaxes:
197 | shape = list(x.shape)
198 | axes = range(x.ndim)
199 | else:
200 | shape = [x.shape[a] for a in axes]
201 |
202 | if noshape and invreal:
203 | shape[-1] = (x.shape[axes[-1]] - 1) * 2
204 |
205 | if any(s < 1 for s in shape):
206 | raise ValueError(f"invalid number of data points ({shape}) specified")
207 |
208 | return tuple(shape), list(axes)
209 |
210 |
211 | def _use_input_as_out(x, overwrite_x):
212 | """Check if the input can be used as output."""
213 | if overwrite_x and np.issubdtype(x.dtype, np.complexfloating):
214 | # pass input as out to overwrite it
215 | return x
216 | return None
217 |
218 |
219 | def _validate_input(x):
220 | try:
221 | x = _supported_array_or_not_implemented(x)
222 | except ValueError:
223 | raise NotImplementedError
224 |
225 | return x
226 |
227 |
228 | def fft(
229 | x, n=None, axis=-1, norm=None, overwrite_x=False, workers=None, *, plan=None
230 | ):
231 | """
232 | Compute the 1-D discrete Fourier Transform.
233 |
234 | For full documentation refer to `scipy.fft.fft`.
235 |
236 | """
237 | _check_plan(plan)
238 | x = _validate_input(x)
239 | out = _use_input_as_out(x, overwrite_x)
240 |
241 | with _Workers(workers):
242 | return mkl_fft.fft(x, n=n, axis=axis, norm=norm, out=out)
243 |
244 |
245 | def ifft(
246 | x, n=None, axis=-1, norm=None, overwrite_x=False, workers=None, *, plan=None
247 | ):
248 | """
249 | Compute the 1-D inverse discrete Fourier Transform.
250 |
251 | For full documentation refer to `scipy.fft.ifft`.
252 |
253 | """
254 | _check_plan(plan)
255 | x = _validate_input(x)
256 | out = _use_input_as_out(x, overwrite_x)
257 |
258 | with _Workers(workers):
259 | return mkl_fft.ifft(x, n=n, axis=axis, norm=norm, out=out)
260 |
261 |
262 | def fft2(
263 | x,
264 | s=None,
265 | axes=(-2, -1),
266 | norm=None,
267 | overwrite_x=False,
268 | workers=None,
269 | *,
270 | plan=None,
271 | ):
272 | """
273 | Compute the 2-D discrete Fourier Transform.
274 |
275 | For full documentation refer to `scipy.fft.fft2`.
276 |
277 | """
278 | return fftn(
279 | x,
280 | s=s,
281 | axes=axes,
282 | norm=norm,
283 | overwrite_x=overwrite_x,
284 | workers=workers,
285 | plan=plan,
286 | )
287 |
288 |
289 | def ifft2(
290 | x,
291 | s=None,
292 | axes=(-2, -1),
293 | norm=None,
294 | overwrite_x=False,
295 | workers=None,
296 | *,
297 | plan=None,
298 | ):
299 | """
300 | Compute the 2-D inverse discrete Fourier Transform.
301 |
302 | For full documentation refer to `scipy.fft.ifft2`.
303 |
304 | """
305 | return ifftn(
306 | x,
307 | s=s,
308 | axes=axes,
309 | norm=norm,
310 | overwrite_x=overwrite_x,
311 | workers=workers,
312 | plan=plan,
313 | )
314 |
315 |
316 | def fftn(
317 | x,
318 | s=None,
319 | axes=None,
320 | norm=None,
321 | overwrite_x=False,
322 | workers=None,
323 | *,
324 | plan=None,
325 | ):
326 | """
327 | Compute the N-D discrete Fourier Transform.
328 |
329 | For full documentation refer to `scipy.fft.fftn`.
330 |
331 | """
332 | _check_plan(plan)
333 | x = _validate_input(x)
334 | out = _use_input_as_out(x, overwrite_x)
335 | s, axes = _init_nd_shape_and_axes(x, s, axes)
336 |
337 | with _Workers(workers):
338 | return mkl_fft.fftn(x, s=s, axes=axes, norm=norm, out=out)
339 |
340 |
341 | def ifftn(
342 | x,
343 | s=None,
344 | axes=None,
345 | norm=None,
346 | overwrite_x=False,
347 | workers=None,
348 | *,
349 | plan=None,
350 | ):
351 | """
352 | Compute the N-D inverse discrete Fourier Transform.
353 |
354 | For full documentation refer to `scipy.fft.ifftn`.
355 |
356 | """
357 | _check_plan(plan)
358 | x = _validate_input(x)
359 | out = _use_input_as_out(x, overwrite_x)
360 | s, axes = _init_nd_shape_and_axes(x, s, axes)
361 |
362 | with _Workers(workers):
363 | return mkl_fft.ifftn(x, s=s, axes=axes, norm=norm, out=out)
364 |
365 |
366 | def rfft(
367 | x, n=None, axis=-1, norm=None, overwrite_x=False, workers=None, *, plan=None
368 | ):
369 | """
370 | Compute the 1-D discrete Fourier Transform for real input..
371 |
372 | For full documentation refer to `scipy.fft.rfft`.
373 |
374 | """
375 | _check_plan(plan)
376 | x = _validate_input(x)
377 |
378 | with _Workers(workers):
379 | # Note: overwrite_x is not utilized
380 | return mkl_fft.rfft(x, n=n, axis=axis, norm=norm)
381 |
382 |
383 | def irfft(
384 | x, n=None, axis=-1, norm=None, overwrite_x=False, workers=None, *, plan=None
385 | ):
386 | """
387 | Compute the inverse of `rfft`.
388 |
389 | For full documentation refer to `scipy.fft.irfft`.
390 |
391 | """
392 | _check_plan(plan)
393 | x = _validate_input(x)
394 |
395 | with _Workers(workers):
396 | # Note: overwrite_x is not utilized
397 | return mkl_fft.irfft(x, n=n, axis=axis, norm=norm)
398 |
399 |
400 | def rfft2(
401 | x,
402 | s=None,
403 | axes=(-2, -1),
404 | overwrite_x=False,
405 | norm=None,
406 | workers=None,
407 | *,
408 | plan=None,
409 | ):
410 | """
411 | Compute the 2-D discrete Fourier Transform for real input.
412 |
413 | For full documentation refer to `scipy.fft.rfft2`.
414 |
415 | """
416 | return rfftn(
417 | x,
418 | s=s,
419 | axes=axes,
420 | norm=norm,
421 | overwrite_x=overwrite_x,
422 | workers=workers,
423 | plan=plan,
424 | )
425 |
426 |
427 | def irfft2(
428 | x,
429 | s=None,
430 | axes=(-2, -1),
431 | norm=None,
432 | overwrite_x=False,
433 | workers=None,
434 | *,
435 | plan=None,
436 | ):
437 | """
438 | Compute the inverse of `rfft2`.
439 |
440 | For full documentation refer to `scipy.fft.irfft2`.
441 |
442 | """
443 | return irfftn(
444 | x,
445 | s=s,
446 | axes=axes,
447 | norm=norm,
448 | overwrite_x=overwrite_x,
449 | workers=workers,
450 | plan=plan,
451 | )
452 |
453 |
454 | def rfftn(
455 | x,
456 | s=None,
457 | axes=None,
458 | norm=None,
459 | overwrite_x=False,
460 | workers=None,
461 | *,
462 | plan=None,
463 | ):
464 | """
465 | Compute the N-D discrete Fourier Transform for real input.
466 |
467 | For full documentation refer to `scipy.fft.rfftn`.
468 |
469 | """
470 | _check_plan(plan)
471 | x = _validate_input(x)
472 | s, axes = _init_nd_shape_and_axes(x, s, axes)
473 |
474 | with _Workers(workers):
475 | # Note: overwrite_x is not utilized
476 | return mkl_fft.rfftn(x, s, axes, norm=norm)
477 |
478 |
479 | def irfftn(
480 | x,
481 | s=None,
482 | axes=None,
483 | norm=None,
484 | overwrite_x=False,
485 | workers=None,
486 | *,
487 | plan=None,
488 | ):
489 | """
490 | Compute the inverse of `rfftn`.
491 |
492 | For full documentation refer to `scipy.fft.irfftn`.
493 |
494 | """
495 | _check_plan(plan)
496 | x = _validate_input(x)
497 | s, axes = _init_nd_shape_and_axes(x, s, axes, invreal=True)
498 |
499 | with _Workers(workers):
500 | # Note: overwrite_x is not utilized
501 | return mkl_fft.irfftn(x, s, axes, norm=norm)
502 |
503 |
504 | def hfft(
505 | x, n=None, axis=-1, norm=None, overwrite_x=False, workers=None, *, plan=None
506 | ):
507 | """
508 | Compute the FFT of a signal that has Hermitian symmetry,
509 | i.e., a real spectrum.
510 |
511 | For full documentation refer to `scipy.fft.hfft`.
512 |
513 | """
514 | _check_plan(plan)
515 | x = _validate_input(x)
516 | norm = _swap_direction(norm)
517 | x = np.array(x, copy=True)
518 | np.conjugate(x, out=x)
519 |
520 | with _Workers(workers):
521 | # Note: overwrite_x is not utilized
522 | return mkl_fft.irfft(x, n=n, axis=axis, norm=norm)
523 |
524 |
525 | def ihfft(
526 | x, n=None, axis=-1, norm=None, overwrite_x=False, workers=None, *, plan=None
527 | ):
528 | """
529 | Compute the inverse FFT of a signal that has Hermitian symmetry.
530 |
531 | For full documentation refer to `scipy.fft.ihfft`.
532 |
533 | """
534 | _check_plan(plan)
535 | x = _validate_input(x)
536 | norm = _swap_direction(norm)
537 |
538 | with _Workers(workers):
539 | # Note: overwrite_x is not utilized
540 | result = mkl_fft.rfft(x, n=n, axis=axis, norm=norm)
541 |
542 | np.conjugate(result, out=result)
543 | return result
544 |
545 |
546 | def hfft2(
547 | x,
548 | s=None,
549 | axes=(-2, -1),
550 | norm=None,
551 | overwrite_x=False,
552 | workers=None,
553 | *,
554 | plan=None,
555 | ):
556 | """
557 | Compute the 2-D FFT of a Hermitian complex array.
558 |
559 | For full documentation refer to `scipy.fft.hfft2`.
560 |
561 | """
562 | return hfftn(
563 | x,
564 | s=s,
565 | axes=axes,
566 | norm=norm,
567 | overwrite_x=overwrite_x,
568 | workers=workers,
569 | plan=plan,
570 | )
571 |
572 |
573 | def ihfft2(
574 | x,
575 | s=None,
576 | axes=(-2, -1),
577 | norm=None,
578 | overwrite_x=False,
579 | workers=None,
580 | *,
581 | plan=None,
582 | ):
583 | """
584 | Compute the 2-D inverse FFT of a real spectrum.
585 |
586 | For full documentation refer to `scipy.fft.ihfft2`.
587 |
588 | """
589 | return ihfftn(
590 | x,
591 | s=s,
592 | axes=axes,
593 | norm=norm,
594 | overwrite_x=overwrite_x,
595 | workers=workers,
596 | plan=plan,
597 | )
598 |
599 |
600 | def hfftn(
601 | x,
602 | s=None,
603 | axes=None,
604 | norm=None,
605 | overwrite_x=False,
606 | workers=None,
607 | *,
608 | plan=None,
609 | ):
610 | """
611 | Compute the N-D FFT of Hermitian symmetric complex input,
612 | i.e., a signal with a real spectrum.
613 |
614 | For full documentation refer to `scipy.fft.hfftn`.
615 |
616 | """
617 | _check_plan(plan)
618 | x = _validate_input(x)
619 | norm = _swap_direction(norm)
620 | x = np.array(x, copy=True)
621 | np.conjugate(x, out=x)
622 | s, axes = _init_nd_shape_and_axes(x, s, axes, invreal=True)
623 |
624 | with _Workers(workers):
625 | # Note: overwrite_x is not utilized
626 | return mkl_fft.irfftn(x, s, axes, norm=norm)
627 |
628 |
629 | def ihfftn(
630 | x,
631 | s=None,
632 | axes=None,
633 | norm=None,
634 | overwrite_x=False,
635 | workers=None,
636 | *,
637 | plan=None,
638 | ):
639 | """
640 | Compute the N-D inverse discrete Fourier Transform for a real spectrum.
641 |
642 | For full documentation refer to `scipy.fft.ihfftn`.
643 |
644 | """
645 | _check_plan(plan)
646 | x = _validate_input(x)
647 | norm = _swap_direction(norm)
648 | s, axes = _init_nd_shape_and_axes(x, s, axes)
649 |
650 | with _Workers(workers):
651 | # Note: overwrite_x is not utilized
652 | result = mkl_fft.rfftn(x, s, axes, norm=norm)
653 |
654 | np.conjugate(result, out=result)
655 | return result
656 |
657 |
658 | # define thin wrappers for scipy functions to avoid circular dependencies
659 | def fftfreq(n, d=1.0, *, xp=None, device=None):
660 | """
661 | Return the Discrete Fourier Transform sample frequencies.
662 |
663 | For full documentation refer to `scipy.fft.fftfreq`.
664 |
665 | """
666 | return scipy.fft.fftfreq(n, d=d, xp=xp, device=device)
667 |
668 |
669 | def rfftfreq(n, d=1.0, *, xp=None, device=None):
670 | """
671 | Return the Discrete Fourier Transform sample frequencies (for usage with
672 | `rfft`, `irfft`).
673 |
674 | For full documentation refer to `scipy.fft.rfftfreq`.
675 |
676 | """
677 | return scipy.fft.rfftfreq(n, d=d, xp=xp, device=device)
678 |
679 |
680 | def fftshift(x, axes=None):
681 | """
682 | Shift the zero-frequency component to the center of the spectrum.
683 |
684 | For full documentation refer to `scipy.fft.fftshift`.
685 |
686 | """
687 | from scipy.fft import fftshift
688 |
689 | return fftshift(x, axes=axes)
690 |
691 |
692 | def ifftshift(x, axes=None):
693 | """
694 | The inverse of `fftshift`. Although identical for even-length `x`, the
695 | functions differ by one sample for odd-length `x`.
696 |
697 | For full documentation refer to `scipy.fft.ifftshift`.
698 |
699 | """
700 | return scipy.fft.ifftshift(x, axes=axes)
701 |
702 |
703 | def get_workers():
704 | """
705 | Gets the number of workers used by mkl_fft by default.
706 |
707 | For full documentation refer to `scipy.fft.get_workers`.
708 |
709 | """
710 | return _workers_global_settings.get().workers
711 |
712 |
713 | @contextlib.contextmanager
714 | def set_workers(workers):
715 | """
716 | Set the value of workers used by default, returns the previous value.
717 |
718 | For full documentation refer to `scipy.fft.set_workers`.
719 |
720 | """
721 | nw = operator.index(workers)
722 | token = None
723 | try:
724 | new_wd = _workers_data(nw)
725 | token = _workers_global_settings.set(new_wd)
726 | yield
727 | finally:
728 | if token is not None:
729 | _workers_global_settings.reset(token)
730 |
--------------------------------------------------------------------------------
/mkl_fft/_fft_utils.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # Copyright (c) 2025, Intel Corporation
3 | #
4 | # Redistribution and use in source and binary forms, with or without
5 | # modification, are permitted provided that the following conditions are met:
6 | #
7 | # * Redistributions of source code must retain the above copyright notice,
8 | # this list of conditions and the following disclaimer.
9 | # * Redistributions in binary form must reproduce the above copyright
10 | # notice, this list of conditions and the following disclaimer in the
11 | # documentation and/or other materials provided with the distribution.
12 | # * Neither the name of Intel Corporation nor the names of its contributors
13 | # may be used to endorse or promote products derived from this software
14 | # without specific prior written permission.
15 | #
16 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
17 | # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 | # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19 | # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
20 | # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21 | # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
22 | # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
23 | # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
24 | # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25 | # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 |
27 | import numpy as np
28 |
29 | # pylint: disable=no-name-in-module
30 | from ._pydfti import (
31 | _c2c_fft1d_impl,
32 | _c2r_fft1d_impl,
33 | _direct_fftnd,
34 | _r2c_fft1d_impl,
35 | _validate_out_array,
36 | )
37 |
38 |
39 | def _check_norm(norm):
40 | if norm not in (None, "ortho", "forward", "backward"):
41 | raise ValueError(
42 | f"Invalid norm value {norm} should be None, 'ortho', 'forward', "
43 | "or 'backward'."
44 | )
45 |
46 |
47 | def _check_shapes_for_direct(xs, shape, axes):
48 | if len(axes) > 7: # Intel MKL supports up to 7D
49 | return False
50 | if not (len(xs) == len(shape)):
51 | # full-dimensional transform
52 | return False
53 | if not (len(set(axes)) == len(axes)):
54 | # repeated axes
55 | return False
56 | for xsi, ai in zip(xs, axes):
57 | try:
58 | sh_ai = shape[ai]
59 | except IndexError:
60 | raise ValueError("Invalid axis (%d) specified" % ai)
61 |
62 | if not (xsi == sh_ai):
63 | return False
64 | return True
65 |
66 |
67 | def _compute_fwd_scale(norm, n, shape):
68 | _check_norm(norm)
69 | if norm in (None, "backward"):
70 | return 1.0
71 |
72 | ss = n if n is not None else shape
73 | nn = np.prod(ss)
74 | fsc = 1 / nn if nn != 0 else 1
75 | if norm == "forward":
76 | return fsc
77 | else: # norm == "ortho"
78 | return np.sqrt(fsc)
79 |
80 |
81 | def _cook_nd_args(a, s=None, axes=None, invreal=False):
82 | if s is None:
83 | shapeless = True
84 | if axes is None:
85 | s = list(a.shape)
86 | else:
87 | try:
88 | s = [a.shape[i] for i in axes]
89 | except IndexError:
90 | # fake s designed to trip the ValueError further down
91 | s = range(len(axes) + 1)
92 | pass
93 | else:
94 | shapeless = False
95 | s = list(s)
96 | if axes is None:
97 | axes = list(range(-len(s), 0))
98 | if len(s) != len(axes):
99 | raise ValueError("Shape and axes have different lengths.")
100 | if invreal and shapeless:
101 | s[-1] = (a.shape[axes[-1]] - 1) * 2
102 | return s, axes
103 |
104 |
105 | # copied from scipy.fft module
106 | # https://github.com/scipy/scipy/blob/main/scipy/fft/_pocketfft/helper.py
107 | def _datacopied(arr, original):
108 | """
109 | Strict check for `arr` not sharing any data with `original`,
110 | under the assumption that arr = np.asarray(original).
111 | """
112 | if arr is original:
113 | return False
114 | if not isinstance(original, np.ndarray) and hasattr(original, "__array__"):
115 | return False
116 | return arr.base is None
117 |
118 |
119 | def _flat_to_multi(ind, shape):
120 | nd = len(shape)
121 | m_ind = [-1] * nd
122 | j = ind
123 | for i in range(nd):
124 | si = shape[nd - 1 - i]
125 | q = j // si
126 | r = j - si * q
127 | m_ind[nd - 1 - i] = r
128 | j = q
129 | return m_ind
130 |
131 |
132 | # copied from scipy.fftpack.helper
133 | def _init_nd_shape_and_axes(x, shape, axes):
134 | """Handle shape and axes arguments for n-dimensional transforms.
135 | Returns the shape and axes in a standard form, taking into account negative
136 | values and checking for various potential errors.
137 | Parameters
138 | ----------
139 | x : array_like
140 | The input array.
141 | shape : int or array_like of ints or None
142 | The shape of the result. If both `shape` and `axes` (see below) are
143 | None, `shape` is ``x.shape``; if `shape` is None but `axes` is
144 | not None, then `shape` is ``scipy.take(x.shape, axes, axis=0)``.
145 | If `shape` is -1, the size of the corresponding dimension of `x` is
146 | used.
147 | axes : int or array_like of ints or None
148 | Axes along which the calculation is computed.
149 | The default is over all axes.
150 | Negative indices are automatically converted to their positive
151 | counterpart.
152 | Returns
153 | -------
154 | shape : array
155 | The shape of the result. It is a 1D integer array.
156 | axes : array
157 | The shape of the result. It is a 1D integer array.
158 | """
159 | x = np.asarray(x)
160 | noshape = shape is None
161 | noaxes = axes is None
162 |
163 | if noaxes:
164 | axes = np.arange(x.ndim, dtype=np.intc)
165 | else:
166 | axes = np.atleast_1d(axes)
167 |
168 | if axes.size == 0:
169 | axes = axes.astype(np.intc)
170 |
171 | if not axes.ndim == 1:
172 | raise ValueError("when given, axes values must be a scalar or vector")
173 | if not np.issubdtype(axes.dtype, np.integer):
174 | raise ValueError("when given, axes values must be integers")
175 |
176 | axes = np.where(axes < 0, axes + x.ndim, axes)
177 |
178 | if axes.size != 0 and (axes.max() >= x.ndim or axes.min() < 0):
179 | raise ValueError("axes exceeds dimensionality of input")
180 | if axes.size != 0 and np.unique(axes).shape != axes.shape:
181 | raise ValueError("all axes must be unique")
182 |
183 | if not noshape:
184 | shape = np.atleast_1d(shape)
185 | elif np.isscalar(x):
186 | shape = np.array([], dtype=np.intc)
187 | elif noaxes:
188 | shape = np.array(x.shape, dtype=np.intc)
189 | else:
190 | shape = np.take(x.shape, axes)
191 |
192 | if shape.size == 0:
193 | shape = shape.astype(np.intc)
194 |
195 | if shape.ndim != 1:
196 | raise ValueError("when given, shape values must be a scalar or vector")
197 | if not np.issubdtype(shape.dtype, np.integer):
198 | raise ValueError("when given, shape values must be integers")
199 | if axes.shape != shape.shape:
200 | raise ValueError(
201 | "when given, axes and shape arguments have to be of the same length"
202 | )
203 |
204 | shape = np.where(shape == -1, np.array(x.shape)[axes], shape)
205 | if shape.size != 0 and (shape < 1).any():
206 | raise ValueError(f"invalid number of data points ({shape}) specified")
207 |
208 | return shape, axes
209 |
210 |
211 | def _iter_complementary(x, axes, func, kwargs, result):
212 | if axes is None:
213 | # s and axes are None, direct N-D FFT
214 | return func(x, **kwargs, out=result)
215 | x_shape = x.shape
216 | nd = x.ndim
217 | r = list(range(nd))
218 | sl = [slice(None, None, None)] * nd
219 | if not np.iterable(axes):
220 | axes = (axes,)
221 | for ai in axes:
222 | r[ai] = None
223 | size = 1
224 | sub_shape = []
225 | dual_ind = []
226 | for ri in r:
227 | if ri is not None:
228 | size *= x_shape[ri]
229 | sub_shape.append(x_shape[ri])
230 | dual_ind.append(ri)
231 |
232 | for ind in range(size):
233 | m_ind = _flat_to_multi(ind, sub_shape)
234 | for k1, k2 in zip(dual_ind, m_ind):
235 | sl[k1] = k2
236 | if np.issubdtype(x.dtype, np.complexfloating):
237 | func(x[tuple(sl)], **kwargs, out=result[tuple(sl)])
238 | else:
239 | # For c2c FFT, if the input is real, half of the output is the
240 | # complex conjugate of the other half. Instead of upcasting the
241 | # input to complex and performing c2c FFT, we perform rfft and then
242 | # construct the other half using the first half.
243 | # However, when using the `out` keyword here I encountered a result
244 | # in tests/third_party/scipy/test_basic.py::test_fft_with_order
245 | # that was correct but the elements were not necessarily similar to
246 | # NumPy. For example, an element in the first half of mkl_fft output
247 | # array appeared in the second half of the NumPy output array,
248 | # while the equivalent element in the NumPy array was the conjugate
249 | # of the mkl_fft output array.
250 | np.copyto(result[tuple(sl)], func(x[tuple(sl)], **kwargs))
251 |
252 | return result
253 |
254 |
255 | def _iter_fftnd(
256 | a,
257 | s=None,
258 | axes=None,
259 | out=None,
260 | direction=+1,
261 | scale_function=lambda ind: 1.0,
262 | ):
263 | a = np.asarray(a)
264 | s, axes = _init_nd_shape_and_axes(a, s, axes)
265 |
266 | # Combine the two, but in reverse, to end with the first axis given.
267 | axes_and_s = list(zip(axes, s))[::-1]
268 | # We try to use in-place calculations where possible, which is
269 | # everywhere except when the size changes after the first FFT.
270 | size_changes = [axis for axis, n in axes_and_s[1:] if a.shape[axis] != n]
271 |
272 | # If there are any size changes, we cannot use out
273 | res = None if size_changes else out
274 | for ind, (axis, n) in enumerate(axes_and_s):
275 | if axis in size_changes:
276 | if axis == size_changes[-1]:
277 | # Last size change, so any output should now be OK
278 | # (an error will be raised if not), and if no output is
279 | # required, we want a freshly allocated array of the right size.
280 | res = out
281 | elif res is not None and n < res.shape[axis]:
282 | # For an intermediate step where we return fewer elements, we
283 | # can use a smaller view of the previous array.
284 | res = res[(slice(None),) * axis + (slice(n),)]
285 | else:
286 | # If we need more elements, we cannot use res.
287 | res = None
288 | a = _c2c_fft1d_impl(
289 | a,
290 | n=n,
291 | axis=axis,
292 | direction=direction,
293 | fsc=scale_function(ind),
294 | out=res,
295 | )
296 | # Default output for next iteration.
297 | res = a
298 | return a
299 |
300 |
301 | def _output_dtype(dt):
302 | if dt == np.float64:
303 | return np.complex128
304 | if dt == np.float32:
305 | return np.complex64
306 | return dt
307 |
308 |
309 | def _pad_array(arr, s, axes):
310 | """Pads array arr with zeros to attain shape s associated with axes"""
311 | arr_shape = arr.shape
312 | no_padding = True
313 | pad_widths = [(0, 0)] * len(arr_shape)
314 | for si, ai in zip(s, axes):
315 | try:
316 | shp_i = arr_shape[ai]
317 | except IndexError:
318 | raise ValueError(f"Invalid axis {ai} specified")
319 | if si > shp_i:
320 | no_padding = False
321 | pad_widths[ai] = (0, si - shp_i)
322 | if no_padding:
323 | return arr
324 | return np.pad(arr, tuple(pad_widths), "constant")
325 |
326 |
327 | def _remove_axis(s, axes, axis_to_remove):
328 | lens = len(s)
329 | axes_normalized = tuple(lens + ai if ai < 0 else ai for ai in axes)
330 | a2r = lens + axis_to_remove if axis_to_remove < 0 else axis_to_remove
331 |
332 | ss = s[:a2r] + s[a2r + 1 :]
333 | pivot = axes_normalized[a2r]
334 | aa = tuple(
335 | ai if ai < pivot else ai - 1 for ai in axes_normalized[:a2r]
336 | ) + tuple(ai if ai < pivot else ai - 1 for ai in axes_normalized[a2r + 1 :])
337 | return ss, aa
338 |
339 |
340 | def _trim_array(arr, s, axes):
341 | """
342 | Forms a view into subarray of arr if any element of shape parameter s is
343 | smaller than the corresponding element of the shape of the input array arr,
344 | otherwise returns the input array.
345 | """
346 |
347 | arr_shape = arr.shape
348 | no_trim = True
349 | ind = [slice(None, None, None)] * len(arr_shape)
350 | for si, ai in zip(s, axes):
351 | try:
352 | shp_i = arr_shape[ai]
353 | except IndexError:
354 | raise ValueError(f"Invalid axis {ai} specified")
355 | if si < shp_i:
356 | no_trim = False
357 | ind[ai] = slice(None, si, None)
358 | if no_trim:
359 | return arr
360 | return arr[tuple(ind)]
361 |
362 |
363 | def _swap_direction(norm):
364 | _check_norm(norm)
365 | _swap_direction_map = {
366 | "backward": "forward",
367 | None: "forward",
368 | "ortho": "ortho",
369 | "forward": "backward",
370 | }
371 |
372 | return _swap_direction_map[norm]
373 |
374 |
375 | def _c2c_fftnd_impl(
376 | x,
377 | s=None,
378 | axes=None,
379 | direction=+1,
380 | fsc=1.0,
381 | out=None,
382 | ):
383 | if direction not in [-1, +1]:
384 | raise ValueError("Direction of FFT should +1 or -1")
385 |
386 | valid_dtypes = [np.complex64, np.complex128, np.float32, np.float64]
387 | # _direct_fftnd requires complex type, and full-dimensional transform
388 | if isinstance(x, np.ndarray) and x.size != 0 and x.ndim > 1:
389 | _direct = s is None and axes is None
390 | if _direct:
391 | _direct = x.ndim <= 7 # Intel MKL only supports FFT up to 7D
392 | if not _direct:
393 | xs, xa = _cook_nd_args(x, s, axes)
394 | if _check_shapes_for_direct(xs, x.shape, xa):
395 | _direct = True
396 | _direct = _direct and x.dtype in valid_dtypes
397 | else:
398 | _direct = False
399 |
400 | if _direct:
401 | return _direct_fftnd(
402 | x,
403 | direction=direction,
404 | fsc=fsc,
405 | out=out,
406 | )
407 | else:
408 | if s is None and x.dtype in valid_dtypes:
409 | x = np.asarray(x)
410 | if out is None:
411 | res = np.empty_like(x, dtype=_output_dtype(x.dtype))
412 | else:
413 | _validate_out_array(out, x, _output_dtype(x.dtype))
414 | res = out
415 |
416 | return _iter_complementary(
417 | x,
418 | axes,
419 | _direct_fftnd,
420 | {"direction": direction, "fsc": fsc},
421 | res,
422 | )
423 | else:
424 | # perform N-D FFT as a series of 1D FFTs
425 | return _iter_fftnd(
426 | x,
427 | s=s,
428 | axes=axes,
429 | out=out,
430 | direction=direction,
431 | scale_function=lambda i: fsc if i == 0 else 1.0,
432 | )
433 |
434 |
435 | def _r2c_fftnd_impl(x, s=None, axes=None, fsc=1.0, out=None):
436 | a = np.asarray(x)
437 | no_trim = (s is None) and (axes is None)
438 | s, axes = _cook_nd_args(a, s, axes)
439 | axes = [ax + a.ndim if ax < 0 else ax for ax in axes]
440 | la = axes[-1]
441 |
442 | # trim array, so that rfft avoids doing unnecessary computations
443 | if not no_trim:
444 | a = _trim_array(a, s, axes)
445 |
446 | # last axis is not included since we calculate r2c FFT separately
447 | # and not in the loop
448 | axes_and_s = list(zip(axes, s))[-2::-1]
449 | size_changes = [axis for axis, n in axes_and_s if a.shape[axis] != n]
450 | res = None if size_changes else out
451 |
452 | # r2c along last axis
453 | a = _r2c_fft1d_impl(a, n=s[-1], axis=la, fsc=fsc, out=res)
454 | res = a
455 | if len(s) > 1:
456 |
457 | len_axes = len(axes)
458 | if len(set(axes)) == len_axes and len_axes == a.ndim and len_axes > 2:
459 | if not no_trim:
460 | ss = list(s)
461 | ss[-1] = a.shape[la]
462 | a = _pad_array(a, tuple(ss), axes)
463 | # a series of ND c2c FFTs along last axis
464 | ss, aa = _remove_axis(s, axes, -1)
465 | ind = [slice(None, None, 1)] * len(s)
466 | for ii in range(a.shape[la]):
467 | ind[la] = ii
468 | tind = tuple(ind)
469 | a_inp = a[tind]
470 | res = out[tind] if out is not None else a_inp
471 | _ = _c2c_fftnd_impl(a_inp, s=ss, axes=aa, direction=1, out=res)
472 | if out is not None:
473 | a = out
474 | else:
475 | # another size_changes check is needed if there are repeated axes
476 | # of last axis, since since FFT changes the shape along last axis
477 | size_changes = [
478 | axis for axis, n in axes_and_s if a.shape[axis] != n
479 | ]
480 |
481 | # a series of 1D c2c FFTs along all axes except last
482 | for axis, n in axes_and_s:
483 | if axis in size_changes:
484 | if axis == size_changes[-1]:
485 | res = out
486 | elif res is not None and n < res.shape[axis]:
487 | res = res[(slice(None),) * axis + (slice(n),)]
488 | else:
489 | res = None
490 | a = _c2c_fft1d_impl(a, n, axis, out=res)
491 | res = a
492 | return a
493 |
494 |
495 | def _c2r_fftnd_impl(x, s=None, axes=None, fsc=1.0, out=None):
496 | a = np.asarray(x)
497 | no_trim = (s is None) and (axes is None)
498 | s, axes = _cook_nd_args(a, s, axes, invreal=True)
499 | axes = [ax + a.ndim if ax < 0 else ax for ax in axes]
500 | la = axes[-1]
501 | if not no_trim:
502 | a = _trim_array(a, s, axes)
503 | if len(s) > 1:
504 | len_axes = len(axes)
505 | if len(set(axes)) == len_axes and len_axes == a.ndim and len_axes > 2:
506 | if not no_trim:
507 | a = _pad_array(a, s, axes)
508 | # a series of ND c2c FFTs along last axis
509 | # due to need to write into a, we must copy
510 | a = a if _datacopied(a, x) else a.copy()
511 | if not np.issubdtype(a.dtype, np.complexfloating):
512 | # complex output will be copied to input, copy is needed
513 | if a.dtype == np.float32:
514 | a = a.astype(np.complex64)
515 | else:
516 | a = a.astype(np.complex128)
517 | ss, aa = _remove_axis(s, axes, -1)
518 | ind = [slice(None, None, 1)] * len(s)
519 | for ii in range(a.shape[la]):
520 | ind[la] = ii
521 | tind = tuple(ind)
522 | a_inp = a[tind]
523 | # out has real dtype and cannot be used in intermediate steps
524 | # ss and aa are reversed since np.irfftn uses forward order but
525 | # np.ifftn uses reverse order see numpy-gh-28950
526 | _ = _c2c_fftnd_impl(
527 | a_inp, s=ss[::-1], axes=aa[::-1], out=a_inp, direction=-1
528 | )
529 | else:
530 | # a series of 1D c2c FFTs along all axes except last
531 | # forward order, see numpy-gh-28950
532 | axes_and_s = list(zip(axes, s))[:-1]
533 | size_changes = [
534 | axis for axis, n in axes_and_s[1:] if a.shape[axis] != n
535 | ]
536 | # out has real dtype cannot be used for intermediate steps
537 | res = None
538 | for axis, n in axes_and_s:
539 | if axis in size_changes:
540 | if res is not None and n < res.shape[axis]:
541 | # pylint: disable=unsubscriptable-object
542 | res = res[(slice(None),) * axis + (slice(n),)]
543 | else:
544 | res = None
545 | a = _c2c_fft1d_impl(a, n, axis, out=res, direction=-1)
546 | res = a
547 | # c2r along last axis
548 | a = _c2r_fft1d_impl(a, n=s[-1], axis=la, fsc=fsc, out=out)
549 | return a
550 |
--------------------------------------------------------------------------------