pax_global_header00006660000000000000000000000064143560075220014516gustar00rootroot0000000000000052 comment=965b82f70478e5a2586202243cd645bbb3ba15c9 pyFFTW-0.13.1/000077500000000000000000000000001435600752200127175ustar00rootroot00000000000000pyFFTW-0.13.1/.gitattributes000066400000000000000000000000401435600752200156040ustar00rootroot00000000000000pyfftw/_version.py export-subst pyFFTW-0.13.1/.github/000077500000000000000000000000001435600752200142575ustar00rootroot00000000000000pyFFTW-0.13.1/.github/workflows/000077500000000000000000000000001435600752200163145ustar00rootroot00000000000000pyFFTW-0.13.1/.github/workflows/wheel_tests_and_release.yml000066400000000000000000000213471435600752200237160ustar00rootroot00000000000000name: Build Wheels, Test and Release on: push: branches: - master - 'buildwheels*' tags: - 'v*' - 'buildwheels*' pull_request: jobs: build_linux_wheels: name: Build ${{ matrix.cibw_python }}-${{ matrix.cibw_libc }}_${{ matrix.cibw_arch }} wheel on ${{ matrix.os }} runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: os: [ubuntu-22.04] cibw_python: ["cp38", "cp39", "cp310", "cp311", "pp38"] # SciPy and NumPy don't support musllinux cibw_libc: ["manylinux"] cibw_arch: ["x86_64", "i686", "aarch64"] exclude: # SciPy and NumPy don't support 32-bit Linux from Python 3.10 and onwards - cibw_python: "cp310" cibw_arch: "i686" - cibw_python: "cp311" cibw_arch: "i686" # Numpy only supports pypy38 x86_64 on Linux - cibw_python: "pp38" cibw_arch: "i686" - cibw_python: "pp38" cibw_arch: "aarch64" steps: - uses: actions/checkout@v3 with: fetch-depth: 0 - uses: actions/setup-python@v4 name: Install Python with: python-version: '3.11' - name: Set up QEMU uses: docker/setup-qemu-action@v2 with: platforms: arm64 - name: Install cibuildwheel run: | python -m pip install cibuildwheel - name: Build the wheel run: | python -m cibuildwheel --output-dir dist env: CIBW_BUILD: ${{ matrix.cibw_python }}-${{ matrix.cibw_libc }}_${{ matrix.cibw_arch }} - uses: actions/upload-artifact@v2 name: Upload wheels as artifacts if: ${{ github.event_name != 'pull_request' }} with: name: wheels path: ./dist/*.whl build_macos_wheels: name: Build ${{ matrix.cibw_python }}-macosx_${{ matrix.cibw_arch }} wheel on ${{ matrix.os }} runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: os: [macos-12] cibw_python: ["cp38", "cp39", "cp310", "cp311", "pp38"] # See issue [#352](https://github.com/pyFFTW/pyFFTW/issues/352) # TODO: Add arm64 when we support it # Current problem seems to be that installed libfftw3 does not provide arm64 # symbols cibw_arch: ["x86_64"] exclude: # cibuildwheel only supports pypy38 x86_64 on MacOS - cibw_python: "pp38" cibw_arch: "arm64" - cibw_python: "pp38" cibw_arch: "universal2" env: MACOSX_DEPLOYMENT_TARGET: "10.13" steps: - uses: actions/checkout@v3 with: fetch-depth: 0 - name: Install FFTW3 libraries run: | brew install fftw - uses: actions/setup-python@v4 name: Install Python with: python-version: '3.11' - name: Install cibuildwheel run: | python -m pip install cibuildwheel - name: Build wheels for CPython (MacOS) run: | python -m cibuildwheel --output-dir dist env: CIBW_BUILD: ${{ matrix.cibw_python }}-macosx_${{ matrix.cibw_arch }} - uses: actions/upload-artifact@v2 name: Upload wheels as artifacts if: ${{ github.event_name != 'pull_request' }} with: name: wheels path: ./dist/*.whl build_windows_wheels: # Only works for 64-bit. # 32-bit is likely possible, but probably not worth the time investment to get # working given low usage name: Build ${{ matrix.cibw_python }}-win${{ matrix.cibw_arch }} wheel on ${{ matrix.os }} runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: os: [windows-2022] cibw_python: ["cp38", "cp39", "cp310", "cp311", "pp38"] # # Add arm64 when we support it # # We don't support Windows 32-bit cibw_arch: ["_amd64", "32", "_arm64"] exclude: # windows arm64 support is 3.9+ - cibw_python: "cp38" cibw_arch: "_arm64" # cibuildwheel only supports pypy38 AMD64 on Windows - cibw_python: "pp38" cibw_arch: "32" - cibw_python: "pp38" cibw_arch: "_arm64" steps: - uses: actions/checkout@v3 with: fetch-depth: 0 - uses: actions/setup-python@v4 name: Install Python with: python-version: '3.11' - name: Install cibuildwheel run: | python -m pip install cibuildwheel - name: Download FFTW libraries run: | Write-Output "Downloading FFTW release (64-bit)" Invoke-WebRequest -Uri "https://fftw.org/pub/fftw/fftw-3.3.5-dll64.zip" -OutFile "${{ github.workspace }}\fftw64.zip" Write-Output "Unzipping 64-bit FFTW release" Expand-Archive -Path "${{ github.workspace }}\fftw64.zip" -DestinationPath "${{ github.workspace }}\fftw64" Write-Output "Downloading FFTW release (32-bit)" Invoke-WebRequest -Uri "https://fftw.org/pub/fftw/fftw-3.3.5-dll32.zip" -OutFile "${{ github.workspace }}\fftw32.zip" Write-Output "Unzipping 32-bit FFTW release" Expand-Archive -Path "${{ github.workspace }}\fftw32.zip" -DestinationPath "${{ github.workspace }}\fftw32" shell: powershell # Annoyingly these two next steps are needed as FFTW does not ship with # .lib files which msvc requires for linking - name: Setup Visual Code build tools (32-bit) uses: ilammy/msvc-dev-cmd@v1 with: arch: x86 if: ${{ matrix.cibw_arch == '32' }} - name: Setup Visual Code build tools (64-bit) uses: ilammy/msvc-dev-cmd@v1 with: arch: amd64 if: ${{ matrix.cibw_arch != '32' }} - name: Convert .def files to .lib run: | lib.exe /def:${{ github.workspace }}\fftw64\libfftw3-3.def /out:${{ github.workspace }}\fftw64\libfftw3-3.lib /machine:x64 lib.exe /def:${{ github.workspace }}\fftw64\libfftw3f-3.def /out:${{ github.workspace }}\fftw64\libfftw3f-3.lib /machine:x64 lib.exe /def:${{ github.workspace }}\fftw64\libfftw3l-3.def /out:${{ github.workspace }}\fftw64\libfftw3l-3.lib /machine:x64 lib.exe /def:${{ github.workspace }}\fftw32\libfftw3-3.def /out:${{ github.workspace }}\fftw32\libfftw3-3.lib /machine:x86 lib.exe /def:${{ github.workspace }}\fftw32\libfftw3f-3.def /out:${{ github.workspace }}\fftw32\libfftw3f-3.lib /machine:x86 lib.exe /def:${{ github.workspace }}\fftw32\libfftw3l-3.def /out:${{ github.workspace }}\fftw32\libfftw3l-3.lib /machine:x86 - name: Build Windows wheels env: ARCHITECTURE_BIT: ${{ matrix.cibw_arch == '32' && '32' || '64' }} CIBW_BUILD: ${{ matrix.cibw_python }}-win${{ matrix.cibw_arch }} CIBW_BEFORE_BUILD: >- (robocopy ${{ github.workspace }}\fftw${{ matrix.cibw_arch == '32' && '32' || '64' }} ${{ github.workspace }}\pyfftw /V /S /E) ^& exit 0 run: | python -m cibuildwheel --output-dir dist - uses: actions/upload-artifact@v2 name: Upload wheels as artifacts if: ${{ github.event_name != 'pull_request' }} with: name: wheels path: ./dist/*.whl deploy: name: Release needs: [build_linux_wheels, build_macos_wheels, build_windows_wheels] if: github.repository_owner == 'pyFFTW' && startsWith(github.ref, 'refs/tags/v') && always() runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 with: fetch-depth: 0 - uses: actions/setup-python@v4 name: Install Python with: python-version: '3.11' - name: Install Twine run: | python -m pip install --upgrade pip pip install twine - uses: actions/download-artifact@v2 id: download with: name: wheels path: ./dist - name: Publish the source distribution on PyPI run: | PYFFTW_VERSION=$(git describe --tags) python setup.py sdist ls -la ${{ github.workspace }}/dist # We prefer to release wheels before source because otherwise there is a # small window during which users who pip install pyfftw will require compilation. twine upload ${{ github.workspace }}/dist/*.whl twine upload ${{ github.workspace }}/dist/pyFFTW-${PYFFTW_VERSION:1}.tar.gz env: TWINE_USERNAME: ${{ secrets.TWINE_USERNAME }} TWINE_PASSWORD: ${{ secrets.TWINE_PASSWORD }} - name: Github release uses: softprops/action-gh-release@v1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} GITHUB_REPOSITORY: ${{ github.repository }} pyFFTW-0.13.1/.gitignore000066400000000000000000000004251435600752200147100ustar00rootroot00000000000000*.py[cod] __pycache__ *.so *.c *.swp *.*~ build dist docs/_build MANIFEST CHANGELOG.md pyFFTW.egg-info pyfftw/version.py github_changelog_generator_token .eggs/ # Virtualenv .venv/ venv/ # FFTW *.exe *.dll *.lib *.def *.zip *.f *.f03 *.exp pyfftw/fftw3.h # Editors .vscode pyFFTW-0.13.1/LICENSE.txt000066400000000000000000000040421435600752200145420ustar00rootroot00000000000000IMPORTANT: While all the code in pyfftw (except for fftw3.h) is released under the 3-clause BSD license (set out below), pyfftw requires FFTW3 to function. FFTW3 is available under two licenses, the free GPL and a non-free license that allows it to be used in proprietary programs. If you intend to use the GPLed FFTW3 library, your code must also be GPL licensed. If you do not wish to comply with the terms of the GPL, you have to buy a FFTW3 license from the copyright holder MIT, see http://www.fftw.org/doc/License-and-Copyright.html for more information. fftw3.h is released under the 2-clause BSD license. See each file for the copyright holder. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. pyFFTW-0.13.1/MANIFEST.in000066400000000000000000000010371435600752200144560ustar00rootroot00000000000000include CHANGELOG.md include LICENSE.txt include requirements.txt include README.rst include pyfftw/pyfftw.pyx include pyfftw/pyfftw.pxd include pyfftw/cpu.pxd include pyfftw/utils.pxi include test/*.py include pyproject.toml recursive-include include *.h # All documentation recursive-include docs * # Exclude built docs prune docs/_build # exclude built libraries, swap files, etc. prune build prune */__pycache__ global-exclude *.py[cod] *.so *.dll *.dylib global-exclude *~ *.bak *.swp include versioneer.py include pyfftw/_version.py pyFFTW-0.13.1/README.md000066400000000000000000000322121435600752200141760ustar00rootroot00000000000000### Current Build Status | GitHub Actions | Read the Docs | | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------- | | [![Build Wheels, Test and Release](https://github.com/pyFFTW/pyFFTW/actions/workflows/wheel_tests_and_release.yml/badge.svg)](https://github.com/pyFFTW/pyFFTW/actions/workflows/wheel_tests_and_release.yml) | [![read_the_docs](https://readthedocs.org/projects/pyfftw/badge/?version=latest)](http://pyfftw.readthedocs.io/en/latest/?badge=latest) | ### Conda-forge Status [![Linux](https://img.shields.io/circleci/project/github/conda-forge/pyfftw-feedstock/master.svg?label=Linux)](https://circleci.com/gh/conda-forge/pyfftw-feedstock) [![OSX](https://img.shields.io/travis/conda-forge/pyfftw-feedstock/master.svg?label=macOS)](https://travis-ci.org/conda-forge/pyfftw-feedstock) [![Windows](https://img.shields.io/appveyor/ci/conda-forge/pyfftw-feedstock/master.svg?label=Windows)](https://ci.appveyor.com/project/conda-forge/pyfftw-feedstock/branch/master) ### Conda-forge Info | Name | Downloads | Version | Platforms | | ---------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- | | [![Conda Recipe](https://img.shields.io/badge/recipe-pyfftw-green.svg)](https://anaconda.org/conda-forge/pyfftw) | [![Conda Downloads](https://img.shields.io/conda/dn/conda-forge/pyfftw.svg)](https://anaconda.org/conda-forge/pyfftw) | [![Conda Version](https://img.shields.io/conda/vn/conda-forge/pyfftw.svg)](https://anaconda.org/conda-forge/pyfftw) | [![Conda Platforms](https://img.shields.io/conda/pn/conda-forge/pyfftw.svg)](https://anaconda.org/conda-forge/pyfftw) | # PyFFTW pyFFTW is a pythonic wrapper around [FFTW 3](https://www.fftw.org), the speedy FFT library. The ultimate aim is to present a unified interface for all the possible transforms that FFTW can perform. Both the complex DFT and the real DFT are supported, as well as on arbitrary axes of arbitrary shaped and strided arrays, which makes it almost feature equivalent to standard and real FFT functions of ``numpy.fft`` (indeed, it supports the ``clongdouble`` dtype which ``numpy.fft`` does not). Wisdom import and export now works fairly reliably. Operating FFTW in multithreaded mode is supported. pyFFTW implements the numpy and scipy fft interfaces in order for users to take advantage of the speed of FFTW with minimal code modifications. A comprehensive unittest suite can be found with the source on the [GitHub](https://github.com/PyFFTW/PyFFTW) repository or with the source distribution on [PyPI](https://pypi.org/). The documentation can be found on [Read the Docs](https://pyfftw.readthedocs.io) the source is on [GitHub](https://github.com/PyFFTW/PyFFTW) and the python package index page [PyPI](https://pypi.org/). Issues and questions can be raised at the [GitHub Issues](https://github.com/PyFFTW/PyFFTW/issues) page. ## Requirements (i.e. what it was designed for) - [Python](https://python.org) >= 3.8 (lower versions *may* work) - [Numpy](https://www.numpy.org) >= 1.20 (lower versions *may* work) - [FFTW](https://www.fftw.org) >= 3.3 (lower versions *may* work) libraries for single, double, and long double precision in serial and multithreading (pthreads or openMP) versions. - [Cython](https://cython.org) >= 0.29 (install these as much as possible with your preferred package manager). In practice, pyFFTW *may* work with older versions of these dependencies, but it is not tested against them. We build wheels for PyPy 3.8, but this platform has not been tested. ## Optional Dependencies - [Scipy](https://www.scipy.org) >= 1.8 - [Dask](https://dask.pydata.org) >= 1.0 Scipy and Dask are only required in order to use their respective interfaces. In practice, older versions may work, but they are not tested against. ## Installation We recommend *not* building from github, but using the release on the python package index with tools such as pip: pip install pyfftw Pre-built binary wheels for 64-bit Python on Linux, Mac OS X and Windows are available on the [PyPI](https://pypi.org/) page for all supported Python versions. Note that we only support binaries for 64-bit Python. 32-bit and ARM architectures have prebuilt wheels for some configurations - see below. Note that prior to Python 3.9, the Windows installation defaulted to being 32-bit even on 64-bit Windows, so if you are having problems installing using pip (typically with an error message like `ERROR: Failed building wheel for pyfftw`) then please check your Python version. Installation from PyPI may also work on other systems when the FFTW libraries are available, but other platforms have not been tested. Alternatively, users of the [conda](https://conda.io/docs/) package manager can install from the [conda-forge](https://conda-forge.org/) channel via: conda install -c conda-forge pyfftw Read on if you do want to build from source... ## Wheels Prebuilt wheels are available for the following configurations: | Python version | Windows (32 bit) | Windows (64 bit) | Windows ARM (64 bit) | MacOS | MacOS ARM | Linux (32 bit) | Linux (64 bit) | Linux ARM (64 bit) | | :------------------------------: | :--------------: | :--------------: | :------------------: | :---: | :-------- | :------------: | :------------: | :----------------: | | CPython < 3.8 (unsupported) | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | | CPython 3.8 | ✔ | ✔ | ❌ | ✔ | ❌ | ✔ | ✔ | ✔ | | CPython 3.9 | ✔ | ✔ | ✔ | ✔ | ❌ | ✔ | ✔ | ✔ | | CPython 3.10 | ✔ | ✔ | ✔ | ✔ | ❌ | ❌ | ✔ | ✔ | | CPython 3.11 | ✔ | ✔ | ✔ | ✔ | ❌ | ❌ | ✔ | ✔ | | PyPy < 3.8 (unsupported) | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | | PyPy 3.8 | ❌ | ✔ | ❌ | ✔ | ❌ | ❌ | ✔ | ❌ | | PyPy > 3.8 (unsupported for now) | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ | If your configuration does not match one of these you will have to build `pyfft` from source yourself. See instructions below. ## Building To build in place: pip install -e . -v That cythonizes the python extension and builds it into a shared library which is placed in ``pyfftw/``. The directory can then be treated as a python package. After you've run ``setup.py`` with cython available, you then have a normal C extension in the ``pyfftw`` directory. Further building does not depend on cython (as long as the .c file remains). During configuration the available FFTW libraries are detected, so pay attention to the output when running ``setup.py``. On certain platforms, for example the long double precision is not available. pyFFTW still builds fine but will fail at runtime if asked to perform a transform involving long double precision. To build against FFTW libraries at non standard location, [some compilers are sensitive to the environment variables](https://gcc.gnu.org/onlinedocs/gcc/Environment-Variables.html) `CPATH` and `LIBRARY_PATH`. Moreover, you can also use `PYFFTW_INCLUDE` and `PYFFTW_LIB_DIR`. If the FFTW libraries still cannot be found, you might also need to set the environment variable `CC` to build with the compiler used to compile the libraries. Regarding multithreading, if both posix and openMP FFTW libs are available, the openMP libs are preferred. This preference can be reversed by defining the environment variable ``PYFFTW_USE_PTHREADS`` prior to building. If neither option is available, pyFFTW works in serial mode only. For more ways of building and installing, see the [distutils documentation](http://docs.python.org/distutils/builtdist.html) and [setuptools documentation](https://setuptools.readthedocs.io). ### Platform specific build info #### Windows To build for windows from source, download the fftw dlls for your system and the header file from [here](http://www.fftw.org/install/windows.html) (they're in a zip file) and place them in the pyfftw directory. The files are ``libfftw3-3.dll``, ``libfftw3l-3.dll``, ``libfftw3f-3.dll``. These libs use pthreads for multithreading. If you're using a version of FFTW other than 3.3, it may be necessary to copy ``fftw3.h`` into ``include\win``. The builds on PyPI use mingw for the 32-bit release and the Windows SDK C++ compiler for the 64-bit release. The scripts should handle this automatically. If you want to compile for 64-bit Windows, you have to use the MS Visual C++ compiler. Set up your environment as described [here](https://github.com/cython/cython/wiki/CythonExtensionsOnWindows) and then run ``setup.py`` with the version of python you wish to target and a suitable build command. For using the MS Visual C++ compiler, you'll need to create a set of suitable ``.lib`` files as described on the [FFTW page](http://www.fftw.org/install/windows.html). #### Mac OSX Install FFTW from [homebrew](http://brew.sh): brew install fftw Set temporary environmental variables, such that pyfftw finds fftw: export DYLD_LIBRARY_PATH=/usr/local/lib export LDFLAGS="-L/usr/local/lib" export CFLAGS="-I/usr/local/include" Now install pyfftw from pip: pip install pyfftw It has been suggested that [macports](https://www.macports.org) might also work fine. You should then replace the LD environmental variables above with the right ones. - DYLD - path for libfftw3.dylib etc - ``find /usr -name libfftw3.dylib`` - LDFLAGS - path for fftw3.h - ``find /usr -name fftw3.h`` #### FreeBSD Install FFTW from ports tree or ``pkg``: - math/fftw3 - math/fftw3-float - math/fftw3-long Please install all of them, if possible. ## Testing Tests should be run using `pytest`. Install using: ```sh pip install pytest ``` To run tests against the installed (compiled) `pyFFTW` wheel, use: ```sh pytest --import-mode=append tests/ ``` **Note**: `--import-mode=append` is needed to prevent `pytest` patching `sys.path` in a way that resolves the local installation over the wheel installation. ## Contributions Contributions are always welcome and valued. The primary restriction on accepting contributions is that they are exhaustively tested. The bulk of pyFFTW has been developed in a test-driven way (i.e. the test to be satisfied is written before the code). I strongly encourage potential contributors to adopt such an approach. See some of my philosophy on testing in development [here] (https://hgomersall.wordpress.com/2014/10/03/from-test-driven-development-and-specifications). If you want to argue with the philosophy, there is probably a good place to do it. New contributions should adhere to [PEP 8](https://www.python.org/dev/peps/pep-0008), but this is only weakly enforced (there is loads of legacy stuff that breaks it, and things like a single trailing whitespace is not a big deal). The best place to start with contributing is by raising an issue detailing the specifics of what you wish to achieve (there should be a clear use-case for any new functionality). I tend to respond pretty quickly and am happy to help where I can with any conceptual issues. I suggest reading the issues already open in order that you know where things might be heading, or what others are working on. pyFFTW-0.13.1/bin/000077500000000000000000000000001435600752200134675ustar00rootroot00000000000000pyFFTW-0.13.1/bin/ci/000077500000000000000000000000001435600752200140625ustar00rootroot00000000000000pyFFTW-0.13.1/bin/ci/linux_install_libfftw.sh000077500000000000000000000001501435600752200210170ustar00rootroot00000000000000#!/bin/bash if [ uname -m = "aarch64" ] then apk add fftw-dev else yum install -y fftw-devel fi pyFFTW-0.13.1/docs/000077500000000000000000000000001435600752200136475ustar00rootroot00000000000000pyFFTW-0.13.1/docs/Makefile000066400000000000000000000107541435600752200153160ustar00rootroot00000000000000# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = _build # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " singlehtml to make a single large HTML file" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " devhelp to make HTML files and a Devhelp project" @echo " epub to make an epub" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " latexpdf to make LaTeX files and run them through pdflatex" @echo " text to make text files" @echo " man to make manual pages" @echo " changes to make an overview of all changed/added/deprecated items" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" clean: -rm -rf $(BUILDDIR) html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." singlehtml: $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml @echo @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/pyFFTW.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/pyFFTW.qhc" devhelp: $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp @echo @echo "Build finished." @echo "To view the help file:" @echo "# mkdir -p $$HOME/.local/share/devhelp/pyFFTW" @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/pyFFTW" @echo "# devhelp" epub: $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub @echo @echo "Build finished. The epub file is in $(BUILDDIR)/epub." latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make' in that directory to run these through (pdf)latex" \ "(use \`make latexpdf' here to do that automatically)." latexpdf: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through pdflatex..." make -C $(BUILDDIR)/latex all-pdf @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." text: $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text @echo @echo "Build finished. The text files are in $(BUILDDIR)/text." man: $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man @echo @echo "Build finished. The manual pages are in $(BUILDDIR)/man." changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." pyFFTW-0.13.1/docs/conf.py000066400000000000000000000166111435600752200151530ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # pyFFTW documentation build configuration file, created by # sphinx-quickstart on Mon Jan 30 14:37:35 2012. # # This file is execfile()d with the current directory set to its containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. import sys, os # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. #sys.path.insert(0, os.path.abspath('..')) # -- General configuration ----------------------------------------------------- autoclass_content = "both" autodoc_docstring_signature = True # If your documentation needs a minimal Sphinx version, state it here. #needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be extensions # coming with Sphinx (named 'sphinx.ext.*') or your custom ones. extensions = ['sphinx.ext.autodoc', 'sphinx.ext.doctest', 'sphinx.ext.coverage', 'sphinx.ext.intersphinx', 'sphinx.ext.napoleon'] # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix of source filenames. source_suffix = '.rst' # The encoding of source files. #source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'index' # General information about the project. project = u'pyFFTW' copyright = u'2016, Henry Gomersall' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # import pyfftw # The short X.Y version. version = pyfftw.__version__ # The full version, including alpha/beta/rc tags. release = pyfftw.__version__ # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. #language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: #today = '' # Else, today_fmt is used as the format for a strftime call. #today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = [] # The reST default role (used for this markup: `text`) to use for all documents. #default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. #add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). #add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. #show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. #modindex_common_prefix = [] # -- Options for HTML output --------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. html_theme = 'default' # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. #html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. #html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". #html_title = None # A shorter title for the navigation bar. Default is the same as html_title. #html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. #html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. #html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = [] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. #html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. #html_use_smartypants = True # Custom sidebar templates, maps document names to template names. #html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. #html_additional_pages = {} # If false, no module index is generated. #html_domain_indices = True # If false, no index is generated. #html_use_index = True # If true, the index is split into individual pages for each letter. #html_split_index = False # If true, links to the reST sources are added to the pages. #html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. #html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. #html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. #html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). #html_file_suffix = None # Output file base name for HTML help builder. htmlhelp_basename = 'pyFFTWdoc' # -- Options for LaTeX output -------------------------------------------------- # The paper size ('letter' or 'a4'). #latex_paper_size = 'letter' # The font size ('10pt', '11pt' or '12pt'). #latex_font_size = '10pt' # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ ('index', 'pyFFTW.tex', u'pyFFTW Documentation', u'Henry Gomersall', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. #latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. #latex_use_parts = False # If true, show page references after internal links. #latex_show_pagerefs = False # If true, show URL addresses after external links. #latex_show_urls = False # Additional stuff for the LaTeX preamble. #latex_preamble = '' # Documents to append as an appendix to all manuals. #latex_appendices = [] # If false, no module index is generated. #latex_domain_indices = True # -- Options for manual page output -------------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ ('index', 'pyfftw', u'pyFFTW Documentation', [u'Henry Gomersall'], 1) ] # Intersphinx mappings intersphinx_mapping = {'http://docs.python.org/': None, 'http://docs.scipy.org/doc/numpy': None, 'http://docs.scipy.org/doc/scipy/reference': None} dependencies = {'pyfftw/pyfftw': ['pyfftw/pyfftw.so'], 'pyfftw/builders/builders': ['pyfftw/builders/builders.py'], 'pyfftw/builders/_utils': ['pyfftw/builders/utils.py']} pyFFTW-0.13.1/docs/index.rst000066400000000000000000000036101435600752200155100ustar00rootroot00000000000000.. pyFFTW documentation master file, created by sphinx-quickstart on Mon Jan 30 14:37:35 2012. You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. Welcome to pyFFTW's documentation! ================================== Introduction ------------ pyFFTW is a pythonic wrapper around `FFTW `_, the speedy FFT library. The ultimate aim is to present a unified interface for all the possible transforms that FFTW can perform. Both the complex DFT and the real DFT are supported, as well as on arbitrary axes of abitrary shaped and strided arrays, which makes it almost feature equivalent to standard and real FFT functions of :mod:`numpy.fft` (indeed, it supports the :attr:`~numpy.clongdouble` dtype which :mod:`!numpy.fft` does not). Operating FFTW in multithreaded mode is supported. The core interface is provided by a unified class, :class:`pyfftw.FFTW`. This core interface can be accessed directly, or through a series of helper functions, provided by the :mod:`pyfftw.builders` module. These helper functions provide an interface similar to :mod:`numpy.fft` for ease of use. In addition to using :class:`pyfftw.FFTW`, a convenient series of functions are included through :mod:`pyfftw.interfaces` that make using :mod:`pyfftw` almost equivalent to :mod:`numpy.fft` or :mod:`scipy.fftpack`. The source can be found in `github `_ and its page in the python package index is `here `_. A comprehensive unittest suite is included with the source on the repository. If any aspect of this library is not covered by the test suite, that is a bug (please report it!). Contents -------- .. toctree:: :maxdepth: 2 /source/tutorial /source/license /source/api Indices and tables ================== * :ref:`genindex` * :ref:`modindex` * :ref:`search` pyFFTW-0.13.1/docs/make.bat000066400000000000000000000105561435600752200152630ustar00rootroot00000000000000@ECHO OFF REM Command file for Sphinx documentation if "%SPHINXBUILD%" == "" ( set SPHINXBUILD=sphinx-build ) set BUILDDIR=_build set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . if NOT "%PAPER%" == "" ( set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% ) if "%1" == "" goto help if "%1" == "help" ( :help echo.Please use `make ^` where ^ is one of echo. html to make standalone HTML files echo. dirhtml to make HTML files named index.html in directories echo. singlehtml to make a single large HTML file echo. pickle to make pickle files echo. json to make JSON files echo. htmlhelp to make HTML files and a HTML help project echo. qthelp to make HTML files and a qthelp project echo. devhelp to make HTML files and a Devhelp project echo. epub to make an epub echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter echo. text to make text files echo. man to make manual pages echo. changes to make an overview over all changed/added/deprecated items echo. linkcheck to check all external links for integrity echo. doctest to run all doctests embedded in the documentation if enabled goto end ) if "%1" == "clean" ( rmdir /q /s %BUILDDIR% goto end ) if "%1" == "html" ( %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/html. goto end ) if "%1" == "dirhtml" ( %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. goto end ) if "%1" == "singlehtml" ( %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. goto end ) if "%1" == "pickle" ( %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can process the pickle files. goto end ) if "%1" == "json" ( %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can process the JSON files. goto end ) if "%1" == "htmlhelp" ( %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can run HTML Help Workshop with the ^ .hhp project file in %BUILDDIR%/htmlhelp. goto end ) if "%1" == "qthelp" ( %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can run "qcollectiongenerator" with the ^ .qhcp project file in %BUILDDIR%/qthelp, like this: echo.^> qcollectiongenerator %BUILDDIR%\qthelp\pyFFTW.qhcp echo.To view the help file: echo.^> assistant -collectionFile %BUILDDIR%\qthelp\pyFFTW.ghc goto end ) if "%1" == "devhelp" ( %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp if errorlevel 1 exit /b 1 echo. echo.Build finished. goto end ) if "%1" == "epub" ( %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub if errorlevel 1 exit /b 1 echo. echo.Build finished. The epub file is in %BUILDDIR%/epub. goto end ) if "%1" == "latex" ( %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex if errorlevel 1 exit /b 1 echo. echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. goto end ) if "%1" == "text" ( %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text if errorlevel 1 exit /b 1 echo. echo.Build finished. The text files are in %BUILDDIR%/text. goto end ) if "%1" == "man" ( %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man if errorlevel 1 exit /b 1 echo. echo.Build finished. The manual pages are in %BUILDDIR%/man. goto end ) if "%1" == "changes" ( %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes if errorlevel 1 exit /b 1 echo. echo.The overview file is in %BUILDDIR%/changes. goto end ) if "%1" == "linkcheck" ( %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck if errorlevel 1 exit /b 1 echo. echo.Link check complete; look for any errors in the above output ^ or in %BUILDDIR%/linkcheck/output.txt. goto end ) if "%1" == "doctest" ( %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest if errorlevel 1 exit /b 1 echo. echo.Testing of doctests in the sources finished, look at the ^ results in %BUILDDIR%/doctest/output.txt. goto end ) :end pyFFTW-0.13.1/docs/release/000077500000000000000000000000001435600752200152675ustar00rootroot00000000000000pyFFTW-0.13.1/docs/release/0.11.0.rst000066400000000000000000000305261435600752200165440ustar00rootroot00000000000000============================ pyFFTW v0.11.0 Release Notes ============================ We are happy to announce the release of pyFFTW v0.11.0. This release is the result of more than two years of work by 9 contributors. pyFFTW is a pythonic wrapper around FFTW 3, the speedy FFT library. The ultimate aim is to present a unified interface for all the possible transforms that FFTW can perform. Both the complex DFT and the real DFT are supported, as well as on arbitrary axes of arbitrary shaped and strided arrays. Operating FFTW in multithreaded mode is supported. pyFFTW implements the numpy and scipy fft interfaces in order for users to take advantage of the speed of FFTW with minimal code modifications. A dask fft interface is provided as a drop-in replacement for the equivalent module in dask. New features ============ Dask interface -------------- A complete drop-in replacement of Dask Array's FFT module is provided. It includes all numpy-like FFT functions, which use FFTW under the hood. Other functions from Dask Array's FFT module are imported as a convenience. Fast transform planning utility ------------------------------- A routine ``next_fast_len`` has been added to allow the user to easily determine sizes for which the FFT computation is computationally efficient. Expanded support for norm keyword argument in the numpy interfaces ------------------------------------------------------------------ Support for the ``norm`` keyword argument has been expanded to all numpy FFT interfaces. It was previously present only for the complex-valued routines. Support for norm keyword argument in FFTW builders -------------------------------------------------- A ``norm`` keyword argument has been added to the FFTW builders allowing the normalization of pre-planned FFTs to be chosen in the same manner as for the numpy interfaces. Dynamic library detection at build and run time ----------------------------------------------- setup.py has been overhauled to dynamically detect the variants of FFTW that are present. Previously single, double and long double libraries were all required at build time. Now, compilation will attempt to detect which variants are present and compile based on that. It also now possible to specify an additional search path for the fftw libraries by setting the environment variable ``PYFFTW_LIB_DIR``. One caveat is that dynamic library detection is not used by default on the Windows platform, as the default assumption is that on Windows, a full set of precompiled libraries have been downloaded from fftw.org. Alternatively, if the environment variable ``PYFFTW_WIN_CONDAFORGE`` is defined, dynamic detection of libraries named fftw3.dll, fftw3f.dll and fftw3l.dll is attempted. This is for compatibility with the CMake-based FFTW build used by conda-forge. The builders and interfaces all detect at runtime which variants are available and select the most suitable precision for the input's dtype. For example, if single-precsion FFTW libraries are not available, double-precision FFTs will be used to transform single-precision inputs. The PyFFTW test suite also detects at runtime which variants have been compiled and only runs tests against the available precisions. OpenMP threading support ------------------------ Building with OpenMP-based threading is also now supported. If both OpenMP and posix threaded libraries are found, the OpenMP variant will be preferred. If the user desires to instead link to posix threads by default, the environment variable ``PYFFTW_USE_PTHREADS`` should be defined. Custom Configuration of Planners and Interfaces ----------------------------------------------- The new module pyfftw.config can be used to assign values for the default number of threads (via ``pyfftw.config.NUM_THREADS``) and default FFTW planner effort (via ``pyfftw.config.PLANNER_EFFORT``). It is also now possible to define the environment variables ``PYFFTW_NUM_THREADS`` and ``PYFFTW_PLANNER_EFFORT`` to determine the default values at import time. Bugs Fixed ========== A platform-dependent bug that results in potentially overwriting a previously computed output upon repeated calls to the numpy interfaces was fixed (#199). Fix to potential platform-dependent integer overflow in empty_aligned (#192). rfftfreq is now present in the numpy fft interfaces for numpy >= 1.8 (#207) Other changes ============= float16 inputs are now transformed using single rather than double precision. The default planning for the numpy and scipy interfaces has changed from FFTW_MEASURE to FFTW_ESTIMATE. This results in faster planning. In cases where the same transform is to be repeated many times, it is likely advantageous to manually specify FFTW_MEASURE instead (or use the FFTW builders to pre-plan the FFT). FutureWarnings related to NumPy multiindexing in NumPy 1.15 are avoided by using more modern indexing conventions. version number handling is now automatically handled by versioneer All documentation is now built and hosted at Read the Docs ( http://pyfftw.readthedocs.io). Authors ======= * Frederik Beaujean + * Dennis Brakhane + * Henry Gomersall * John Kirkham + * Antony Lee + * Gregory R. Lee * Iblis Lin + * Matthew D. Scholefield + * Hsiou-Yuan (Herbert) Liu + A total of 9 people contributed to this release. People with a "+" by their names contributed a patch for the first time. This list of names is automatically generated, and may not be fully complete. Issues closed for v0.11 ------------------------ - `#27 `__: missing float and long-double libraries - `#32 `__: Provide the sources of the documentation in the source distribution instead of a build - `#64 `__: Clean install from requirements.txt file - ImportError: No module named 'numpy' - `#70 `__: Support not having all varieties of FFTW installed - `#86 `__: Add the source of the documentation to the release tarball - `#87 `__: Avoid overwriting the version file distributed in the source release - `#125 `__: pip install pyfftw fails on travis-ci - `#132 `__: Citation preferences? - `#134 `__: link on github broken - `#146 `__: README on pypi still points to github.com/hgomersall/pyfftw - `#151 `__: Dask wrappers/interface for pyFFTW - `#152 `__: Missing norm argument in several numpy interface fft methods - `#174 `__: Using OpenMP threads instead of POSIX threads #174 - `#175 `__: Dask 2-D/N-D wrappers for pyFFTW - `#196 `__: Change URL in github description - `#197 `__: Numpy interface corrupts array argument - `#206 `__: numpy_fft should re-export rfftfreq - `#215 `__: AppVeyor status in PRs Pull requests for v0.11 ------------------------ - `#140 `__: ENH: process float16 inputs in single precision - `#148 `__: np/sp interfaces default to FFTW_ESTIMATE - `#149 `__: Add freebsd build support - `#153 `__: Document the NumPy interface's `hfft` and `ihfft` - `#154 `__: Provide a Dask interface to pyFFTW's 1-D FFTs - `#155 `__: Handle install requirements separately - `#156 `__: Use org URLs - `#157 `__: Rename Cache interface tests - `#158 `__: DOC: add next_fast_len to the interfaces docs too - `#159 `__: add next_fast_len as introduced in scipy 0.18 - `#160 `__: ENH: add norm keyword argument to the rfft*, hfft* numpy interfaces - `#161 `__: Configure RTD - `#162 `__: Check system prefix for headers and libraries - `#163 `__: Move docs out of the source code - `#164 `__: Fix sphinx configuration - `#165 `__: Import functions directly from the NumPy FFT interface - `#166 `__: Make sure to reference Dask interface - `#167 `__: extend the norm argument as implemented in the numpy interface to the builders - `#169 `__: Documentation is on Read the Docs - `#170 `__: Redirect to Read the Docs - `#171 `__: DOC: add next_fast_len to the interfaces docs too - `#172 `__: Attempted fix to the problem of the FFTW libs not being downloaded on appveyor - `#173 `__: BLD: no conda package for numpy1.9 on Python3.6 so bump to 1.10.4 - `#176 `__: Automate version number handling via versioneer - `#177 `__: [setup] Detect available FFTW libs - `#178 `__: Disable norm as a keyword argument in Dask test - `#182 `__: include documentation in the source distribution - `#185 `__: fix build-time fftw library detection for compatibility with conda-forge - `#186 `__: update interfaces.scipy_fftpack namespace - `#189 `__: [setup, pyx] Build only the parts for which FFTW libraries were found - `#190 `__: WIP: support building from conda-forge FFTW packages on Windows - `#192 `__: simple fix to overflow in empty_aligned() (on Windows machine) - `#198 `__: Fix broken build with OpenMP - `#199 `__: Fix to #197 in which misaligned arrays in the cache caused overwrites… - `#200 `__: update version import syntax in doc/conf.py for compatibility with versioneer - `#207 `__: add rfftfreq to the numpy interfaces - `#209 `__: Fix dtype bug on systems where longdouble is equivalent to double - `#210 `__: setup.py: make sure install_requires contains numpy - `#211 `__: Fix Dask test interface - `#212 `__: Include Dask 2-D/N-D wrappers for pyFFTW - `#213 `__: Dask Interface Extras - `#216 `__: Tweaked the appeveyor bintray build stuff to properly look up the ver... - `#218 `__: Explicitly add Dask Array as an extra requirement - `#219 `__: Add optional SciPy requirement - `#221 `__: Optionally use Dask if Numpy is 1.10 - `#230 `__: Add clarification to license and add license to docs. - `#233 `__: Fix using non-tuple multidimensional indices - `#240 `__: avoid multi-indexing warnings with numpy >= 1.15 - `#241 `__: Allow run-time choice of the default number of threads and planning effort - `#242 `__: update test suite avoid mkl_fft when it is present pyFFTW-0.13.1/docs/release/0.12.0.rst000066400000000000000000000063741435600752200165510ustar00rootroot00000000000000============================ pyFFTW v0.12.0 Release Notes ============================ We are happy to announce the release of pyFFTW v0.12.0. The highlight of this release is the addition of interfaces for the scipy.fft module that was introduced with SciPy 1.4. This release is now compatibile with SciPy 1.4. This release supports Python 2.7 and 3.5-3.8. pyFFTW is a pythonic wrapper around FFTW 3, the speedy FFT library. The ultimate aim is to present a unified interface for all the possible transforms that FFTW can perform. Both the complex DFT and the real DFT are supported, as well as on arbitrary axes of arbitrary shaped and strided arrays. Operating FFTW in multithreaded mode is supported. pyFFTW implements the numpy and scipy fft interfaces in order for users to take advantage of the speed of FFTW with minimal code modifications. A dask fft interface is provided as a drop-in replacement for the equivalent module in dask. New features ============ scipy.fft interface ------------------- This interface operates like the existing scipy.fftpack interface, but matches the API of the newer scipy.fft module introduced in SciPy 1.4. Bugs Fixed ========== The test suite was updated to be compatible with more recent dask (#278). The Cython variable _N was renamed to avoid a name conflict with a preprocessor token on some platforms (#259). Other changes ============= Python 3.4 support has been dropped. pyFFTW now supports Python 2.7 and 3.5-3.8. The Cython code has been updated to explicitly use `language_level=3str` for compatibility with a future Cython 3.0 release. Authors ======= * Peter Bell + * Gregory R. Lee * Stefan Peterson + * DWesl + A total of 4 people contributed PRs to this release. People with a "+" by their names contributed a patch for the first time. John Kirkham and Henry Gomersall also contributed by helping review PRs. Issues closed for v0.12 ------------------------ - `#268 `__: Add a scipy.fft interface - `#276 `__: futurewarnings in dask 2.8 break test suite Pull requests for v0.12 ----------------------- - `#257 `__: Update install instructions in README - `#259 `__: Avoid using "_N". - `#262 `__: Add reference to third-party planfftw package - `#265 `__: remove import of non-public _fftpack within the scipy interfaces - `#267 `__: DOC: scipy_fftpack does not treat dtypes differently from scipy.fftpack - `#269 `__: scipy.fft interface - `#271 `__: bump conda package versions on Appveyor - `#273 `__: Fix shape argument in scipy_fft interface - `#274 `__: Update scipy.fft interface to fix shape handling and add workers argument - `#278 `__: Update Dask tests for compatibility with recent Dask - `#283 `__: explicitly set the Cython language level pyFFTW-0.13.1/docs/release/0.13.0.rst000066400000000000000000000053511435600752200165440ustar00rootroot00000000000000============================ pyFFTW v0.13.0 release notes ============================ We're happy to announce the release of pyFFTW v0.13.0! pyFFTW is a pythonic wrapper around FFTW 3, the speedy FFT library. The ultimate aim is to present a unified interface for all the possible transforms that FFTW can perform. pyFFTW implements the NumPy and SciPy FFT interfaces in order for users to take advantage of the speed of FFTW with minimal code modifications. A Dask FFT interface is provided as a drop-in replacement for the equivalent module in dask. For more information, examples, and documentation, please see the `documentation `_. A highlight of this release is the addition of real-to-real transforms, specifically the type I-IV discrete sine and cosine transforms. These transforms are also available from the `pyfftw.interfaces.numpy_fft` and `pyfftw.interfaces.scipy_fft` interfaces as well as the legacy `pyfftw.interfaces.scipy_fftpack` interface. The NumPy interfaces have also now been updated to support new normalization options added in NumPy 1.20. The new 'backward' and 'forward' options are described in the `NumPy docs `_. This release supports Python 3.7-3.10 on Windows, MacOS and Linux. For more details on the architectures providing binary wheels on PyPI, see the full `table of prebuilt wheels `_. Binary packages for new Apple M1 processors will be made available via `conda-forge `_. Pull requests for v0.13.0 ************************* - add pyproject.toml (#226) - Real-to-real transforms (#256) - Drop testing on Python 2.7 and 3.5 (#285) - Minor doc changes to README (#305) - Document PYFFTW_INCLUDE and PYFFTW_LIB_DIR (#311) - Build wheels on GitHub Actions via cibuildwheel (#318) - Fixed setup.py and working CI builds (#323) - Update NumPy and SciPy interfaces with new norm options (take 2) (#330) - CI: Added the correct gh actions badge and tweaked the name of the workflow (#331) - CI: Improved badge to link to the actual workflow (#332) - remove use of distutils (#333) - Increase time allowed for cache clearance in test (#334) - Increase timing in cache tests (#336) - Fix miscellaneous typos (#337) 8 authors added to this release [alphabetical by first name or login] --------------------------------------------------------------------- - Chris Val - David Wells - Gregory Lee - Henry Gomersall - Jeppe Klitgaard - Jonathan Essen - Pierre Augier - Tim Gates 3 reviewers added to this release [alphabetical by first name or login] ----------------------------------------------------------------------- - Gregory Lee - Henry Gomersall - Jeppe Klitgaard pyFFTW-0.13.1/docs/source/000077500000000000000000000000001435600752200151475ustar00rootroot00000000000000pyFFTW-0.13.1/docs/source/api.rst000066400000000000000000000002221435600752200164460ustar00rootroot00000000000000API Reference ************* .. toctree:: pyfftw/pyfftw pyfftw/builders/builders pyfftw/builders/_utils pyfftw/interfaces/interfaces pyFFTW-0.13.1/docs/source/license.rst000066400000000000000000000041531435600752200173260ustar00rootroot00000000000000License ======= .. note:: While all the code in pyfftw (except for fftw3.h) is released under the 3-clause BSD license (set out below), pyfftw requires FFTW3 to function. FFTW3 is available under two licenses, the free GPL and a non-free license that allows it to be used in proprietary programs. **If you intend to use the GPLed FFTW3 library, your code must also be GPL licensed.** If you do not wish to comply with the terms of the GPL, you have to buy a FFTW3 license from the copyright holder MIT, `see here for more information `_. fftw3.h is released under the 2-clause BSD license. See each file for the copyright holder. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. pyFFTW-0.13.1/docs/source/pyfftw/000077500000000000000000000000001435600752200164665ustar00rootroot00000000000000pyFFTW-0.13.1/docs/source/pyfftw/builders/000077500000000000000000000000001435600752200202775ustar00rootroot00000000000000pyFFTW-0.13.1/docs/source/pyfftw/builders/_utils.rst000066400000000000000000000005161435600752200223320ustar00rootroot00000000000000``pyfftw.builders._utils`` - Helper functions for :mod:`pyfftw.builders` ======================================================================== .. automodule:: pyfftw.builders._utils :members: :private-members: :exclude-members: _FFTWWrapper .. autoclass:: pyfftw.builders._utils._FFTWWrapper :members: __call__ pyFFTW-0.13.1/docs/source/pyfftw/builders/builders.rst000066400000000000000000000004501435600752200226410ustar00rootroot00000000000000``pyfftw.builders`` - Get :class:`~!pyfftw.FFTW` objects using a :mod:`!numpy.fft` like interface ================================================================================================= .. automodule:: pyfftw.builders :members: :undoc-members: The Functions """"""""""""" pyFFTW-0.13.1/docs/source/pyfftw/interfaces/000077500000000000000000000000001435600752200206115ustar00rootroot00000000000000pyFFTW-0.13.1/docs/source/pyfftw/interfaces/dask_fft.rst000066400000000000000000000003121435600752200231200ustar00rootroot00000000000000:mod:`dask.fft` interface ========================== .. automodule:: pyfftw.interfaces.dask_fft :members: fft, ifft, fft2, ifft2, fftn, ifftn, rfft, irfft, rfft2, irfft2, rfftn, irfftn, hfft, ihfft pyFFTW-0.13.1/docs/source/pyfftw/interfaces/interfaces.rst000066400000000000000000000005221435600752200234650ustar00rootroot00000000000000``pyfftw.interfaces`` - Drop in replacements for other FFT implementations ========================================================================== .. toctree:: :hidden: numpy_fft scipy_fft scipy_fftpack dask_fft .. automodule:: pyfftw.interfaces Caching ------- .. automodule:: pyfftw.interfaces.cache :members: pyFFTW-0.13.1/docs/source/pyfftw/interfaces/numpy_fft.rst000066400000000000000000000003141435600752200233500ustar00rootroot00000000000000:mod:`numpy.fft` interface ========================== .. automodule:: pyfftw.interfaces.numpy_fft :members: fft, ifft, fft2, ifft2, fftn, ifftn, rfft, irfft, rfft2, irfft2, rfftn, irfftn, hfft, ihfft pyFFTW-0.13.1/docs/source/pyfftw/interfaces/scipy_fft.rst000066400000000000000000000003371435600752200233340ustar00rootroot00000000000000:mod:`scipy.fft` interface ============================== .. automodule:: pyfftw.interfaces.scipy_fft :members: fft, ifft, fft2, ifft2, fftn, ifftn, rfft, irfft, rfft2, irfft2, rfftn, irfftn, hfft, ihfft, next_fast_len pyFFTW-0.13.1/docs/source/pyfftw/interfaces/scipy_fftpack.rst000066400000000000000000000003541435600752200241720ustar00rootroot00000000000000:mod:`scipy.fftpack` interface ============================== .. automodule:: pyfftw.interfaces.scipy_fftpack :members: fft, ifft, fftn, ifftn, rfft, irfft, fft2, ifft2, dct, idct, dst, idst, dctn, idctn, dstn, idstn, next_fast_len pyFFTW-0.13.1/docs/source/pyfftw/pyfftw.rst000066400000000000000000000072761435600752200205530ustar00rootroot00000000000000``pyfftw`` - The core ===================== .. automodule:: pyfftw FFTW Class ---------- .. autoclass:: pyfftw.FFTW(input_array, output_array, axes=(-1,), direction='FFTW_FORWARD', flags=('FFTW_MEASURE',), threads=1, planning_timelimit=None) .. autoattribute:: pyfftw.FFTW.N .. autoattribute:: pyfftw.FFTW.simd_aligned .. autoattribute:: pyfftw.FFTW.input_alignment .. autoattribute:: pyfftw.FFTW.output_alignment .. autoattribute:: pyfftw.FFTW.flags .. autoattribute:: pyfftw.FFTW.input_array .. autoattribute:: pyfftw.FFTW.output_array .. autoattribute:: pyfftw.FFTW.input_shape .. autoattribute:: pyfftw.FFTW.output_shape .. autoattribute:: pyfftw.FFTW.input_strides .. autoattribute:: pyfftw.FFTW.output_strides .. autoattribute:: pyfftw.FFTW.input_dtype .. autoattribute:: pyfftw.FFTW.output_dtype .. autoattribute:: pyfftw.FFTW.direction .. autoattribute:: pyfftw.FFTW.axes .. autoattribute:: pyfftw.FFTW.ortho .. autoattribute:: pyfftw.FFTW.normalise_idft .. automethod:: pyfftw.FFTW.__call__ .. automethod:: pyfftw.FFTW.update_arrays .. automethod:: pyfftw.FFTW.execute .. automethod:: pyfftw.FFTW.get_input_array .. automethod:: pyfftw.FFTW.get_output_array .. _wisdom_functions: Wisdom Functions ---------------- Functions for dealing with FFTW's ability to export and restore plans, referred to as *wisdom*. For further information, refer to the `FFTW wisdom documentation `_. .. autofunction:: pyfftw.export_wisdom .. autofunction:: pyfftw.import_wisdom .. autofunction:: pyfftw.forget_wisdom .. _utility_functions: Utility Functions ----------------- .. data:: pyfftw.simd_alignment An integer giving the optimum SIMD alignment in bytes, found by inspecting the CPU (e.g. if AVX is supported, its value will be 32). This can be used as ``n`` in the arguments for :func:`byte_align`, :func:`empty_aligned`, :func:`zeros_aligned`, and :func:`ones_aligned` to create optimally aligned arrays for the running platform. .. autofunction:: pyfftw.byte_align .. autofunction:: pyfftw.empty_aligned .. autofunction:: pyfftw.zeros_aligned .. autofunction:: pyfftw.ones_aligned .. autofunction:: pyfftw.is_byte_aligned .. autofunction:: pyfftw.n_byte_align .. autofunction:: pyfftw.n_byte_align_empty .. autofunction:: pyfftw.is_n_byte_aligned .. autofunction:: pyfftw.next_fast_len .. _configuration_variables: FFTW Configuration ------------------ .. data:: pyfftw.config.NUM_THREADS This variable controls the default number of threads used by the functions in :mod:`pyfftw.builders` and :mod:`pyfftw.interfaces`. The default value is read from the environment variable ``PYFFTW_NUM_THREADS``. If this variable is undefined and the user's underlying FFTW library was built using OpenMP threading, the number of threads will be read from the environment variable ``OMP_NUM_THREADS`` instead. If neither environment variable is defined, the default value is 1. If the specified value is ``<= 0``, the library will use :func:`multiprocessing.cpu_count` to determine the number of threads. The user can modify the value at run time by assigning to this variable. .. data:: pyfftw.config.PLANNER_EFFORT This variable controls the default planning effort used by the functions in :mod:`pyfftw.builders` and :mod:`pyfftw.interfaces`. The default value of is determined by reading from the environment variable ``PYFFTW_PLANNER_EFFORT``. If this environment variable is undefined, it defaults to ``'FFTW_ESTIMATE'``. The user can modify the value at run time by assigning to this variable. pyFFTW-0.13.1/docs/source/tutorial.rst000066400000000000000000000511311435600752200175450ustar00rootroot00000000000000Overview and A Short Tutorial ============================= Before we begin, we assume that you are already familiar with the `discrete Fourier transform `_, and why you want a faster library to perform your FFTs for you. `FFTW `_ is a very fast FFT C library. The way it is designed to work is by planning *in advance* the fastest way to perform a particular transform. It does this by trying lots of different techniques and measuring the fastest way, so called *planning*. One consequence of this is that the user needs to specify in advance exactly what transform is needed, including things like the data type, the array shapes and strides and the precision. This is quite different to how one uses, for example, the :mod:`numpy.fft` module. The purpose of this library is to provide a simple and pythonic way to interact with FFTW, benefiting from the substantial speed-ups it offers. In addition to the method of using FFTW as described above, a convenient series of functions are included through :mod:`pyfftw.interfaces` that make using :mod:`pyfftw` almost equivalent to :mod:`numpy.fft`. This tutorial is split into three parts. A quick introduction to the :mod:`pyfftw.interfaces` module is :ref:`given `, the most simple and direct way to use :mod:`pyfftw`. Secondly an :ref:`overview ` is given of :class:`pyfftw.FFTW`, the core of the library. Finally, the :mod:`pyfftw.builders` helper functions are :ref:`introduced `, which ease the creation of :class:`pyfftw.FFTW` objects. .. _interfaces_tutorial: Quick and easy: the :mod:`pyfftw.interfaces` module --------------------------------------------------- The easiest way to begin using :mod:`pyfftw` is through the :mod:`pyfftw.interfaces` module. This module implements three APIs: :mod:`pyfftw.interfaces.numpy_fft`, :mod:`pyfftw.interfaces.scipy_fftpack`, and :mod:`pyfftw.interfaces.dask_fft`, which are (apart from a small caveat [#caveat]_) drop in replacements for :mod:`numpy.fft`, :mod:`scipy.fftpack`, and :mod:`dask.fft` respectively. .. doctest:: >>> import pyfftw >>> import numpy >>> a = pyfftw.empty_aligned(128, dtype='complex128', n=16) >>> a[:] = numpy.random.randn(128) + 1j*numpy.random.randn(128) >>> b = pyfftw.interfaces.numpy_fft.fft(a) >>> c = numpy.fft.fft(a) >>> numpy.allclose(b, c) True We initially create and fill a complex array, ``a``, of length 128. :func:`pyfftw.empty_aligned` is a helper function that works like :func:`numpy.empty` but returns the array aligned to a particular number of bytes in memory, in this case 16. If the alignment is not specified then the library inspects the CPU for an appropriate alignment value. Having byte aligned arrays allows FFTW to performed vector operations, potentially speeding up the FFT (a similar :func:`pyfftw.byte_align` exists to align a pre-existing array as necessary). Calling :func:`pyfftw.interfaces.numpy_fft.fft` on ``a`` gives the same output (to numerical precision) as calling :func:`numpy.fft.fft` on ``a``. If you wanted to modify existing code that uses :mod:`numpy.fft` to use :mod:`pyfftw.interfaces`, this is done simply by replacing all instances of :mod:`numpy.fft` with :mod:`pyfftw.interfaces.numpy_fft` (similarly for :mod:`scipy.fftpack` and :mod:`pyfftw.interfaces.scipy_fftpack`), and then, optionally, enabling the cache (see below). The first call for a given transform size and shape and dtype and so on may be slow, this is down to FFTW needing to plan the transform for the first time. Once this has been done, subsequent equivalent transforms during the same session are much faster. It's possible to export and save the internal knowledge (the *wisdom*) about how the transform is done. This is described :ref:`below `. Even after the first transform of a given specification has been performed, subsequent transforms are never as fast as using :class:`pyfftw.FFTW` objects directly, and in many cases are substantially slower. This is because of the internal overhead of creating a new :class:`pyfftw.FFTW` object on every call. For this reason, a cache is provided, which is recommended to be used whenever :mod:`pyfftw.interfaces` is used. Turn the cache on using :func:`pyfftw.interfaces.cache.enable`. This function turns the cache on globally. Note that using the cache invokes the threading module. The cache temporarily stores a copy of any interim :class:`pyfftw.FFTW` objects that are created. If they are not used for some period of time, which can be set with :func:`pyfftw.interfaces.cache.set_keepalive_time`, then they are removed from the cache (liberating any associated memory). The default keepalive time is 0.1 seconds. Integration with 3rd party libraries ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ SciPy versions 1.4 and above have support for installing different FFT backends. :mod:`pyfftw.interfaces.scipy_fft` support the use as a backend. Note that the interfaces (and builders) all currently default to a single thread. The number of threads to use can be configured by assigning a positive integer to `pyfftw.config.NUM_THREADS` (see more details under :ref:configuration ). The following code demonstrates using the :mod:`pyfftw` backend to speed up :func:`scipy.signal.fftconvolve`. .. code-block:: python import pyfftw import multiprocessing import scipy.signal import scipy.fft import numpy from timeit import Timer a = pyfftw.empty_aligned((128, 64), dtype='complex128') b = pyfftw.empty_aligned((128, 64), dtype='complex128') a[:] = numpy.random.randn(128, 64) + 1j*numpy.random.randn(128, 64) b[:] = numpy.random.randn(128, 64) + 1j*numpy.random.randn(128, 64) t = Timer(lambda: scipy.signal.fftconvolve(a, b)) print('Time with scipy.fft default backend: %1.3f seconds' % t.timeit(number=100)) # Configure PyFFTW to use all cores (the default is single-threaded) pyfftw.config.NUM_THREADS = multiprocessing.cpu_count() # Use the backend pyfftw.interfaces.scipy_fft with scipy.fft.set_backend(pyfftw.interfaces.scipy_fft): # Turn on the cache for optimum performance pyfftw.interfaces.cache.enable() # We cheat a bit by doing the planning first scipy.signal.fftconvolve(a, b) print('Time with pyfftw backend installed: %1.3f seconds' % t.timeit(number=100)) which outputs something like: .. code-block:: none Time with scipy.fft default backend: 0.267 seconds Time with pyfftw backend installed: 0.162 seconds Prior to SciPy 1.4 it was necessary to monkey patch the libraries directly. :mod:`pyfftw.interfaces.numpy_fft` and :mod:`pyfftw.interfaces.scipy_fftpack` are drop-in replacements for the :mod:`numpy.fft` and :mod:`scipy.fftpack` libraries respectively so it is possible to use them as replacements at run-time through monkey patching. .. code-block:: python # Monkey patch fftpack with pyfftw.interfaces.scipy_fftpack scipy.fftpack = pyfftw.interfaces.scipy_fftpack scipy.signal.fftconvolve(a, b) Note that prior to SciPy 0.16, it was necessary to patch the individual functions in ``scipy.signal.signaltools``. For example: .. code-block:: python scipy.signal.signaltools.ifftn = pyfftw.interfaces.scipy_fftpack.ifftn .. _FFTW_tutorial: The workhorse :class:`pyfftw.FFTW` class ---------------------------------------- The core of this library is provided through the :class:`pyfftw.FFTW` class. FFTW is fully encapsulated within this class. The following gives an overview of the :class:`pyfftw.FFTW` class, but the easiest way to of dealing with it is through the :mod:`pyfftw.builders` helper functions, also :ref:`discussed in this tutorial `. For users that already have some experience of FFTW, there is no interface distinction between any of the supported data types, shapes or transforms, and operating on arbitrarily strided arrays (which are common when using :mod:`numpy`) is fully supported with no copies necessary. In its simplest form, a :class:`pyfftw.FFTW` object is created with a pair of complementary :mod:`numpy` arrays: an input array and an output array. They are complementary insomuch as the data types and the array sizes together define exactly what transform should be performed. We refer to a valid transform as a :ref:`scheme `. Internally, three precisions of FFT are supported. These correspond to single precision floating point, double precision floating point and long double precision floating point, which correspond to :mod:`numpy`'s ``float32``, ``float64`` and ``longdouble`` dtypes respectively (and the corresponding complex types). The precision is decided by the relevant scheme, which is specified by the dtype of the input array. Various schemes are supported by :class:`pyfftw.FFTW`. The scheme that is used depends on the data types of the input array and output arrays, the shape of the arrays and the direction flag. For a full discussion of the schemes available, see the API documentation for :class:`pyfftw.FFTW`. One-Dimensional Transforms ~~~~~~~~~~~~~~~~~~~~~~~~~~ We will first consider creating a simple one-dimensional transform of a one-dimensional complex array: .. testcode:: import pyfftw a = pyfftw.empty_aligned(128, dtype='complex128') b = pyfftw.empty_aligned(128, dtype='complex128') fft_object = pyfftw.FFTW(a, b) In this case, we create 2 complex arrays, ``a`` and ``b`` each of length 128. As before, we use :func:`pyfftw.empty_aligned` to make sure the array is aligned. Given these 2 arrays, the only transform that makes sense is a 1D complex DFT. The direction in this case is the default, which is forward, and so that is the transform that is *planned*. The returned ``fft_object`` represents such a transform. In general, the creation of the :class:`pyfftw.FFTW` object clears the contents of the arrays, so the arrays should be filled or updated after creation. Similarly, to plan the inverse: .. testcode:: c = pyfftw.empty_aligned(128, dtype='complex128') ifft_object = pyfftw.FFTW(b, c, direction='FFTW_BACKWARD') In this case, the direction argument is given as ``'FFTW_BACKWARD'`` (to override the default of ``'FFTW_FORWARD'``). :class:`pyfftw.FFTW` also supports all of the discrete sine and cosine transformations (also called *real to real transformations*) implemented by FFTW: for example .. testcode:: d = pyfftw.empty_aligned(128, dtype='float64') e = pyfftw.empty_aligned(128, dtype='float64') dct_transform = pyfftw.FFTW(d, e, direction='FFTW_REDFT00') creates an instance of :class:`pyfftw.FFTW` which can execute the discrete cosine boundary condition with even boundary conditions on both ends (also known as the DCT-1). The actual FFT is performed by calling the returned objects: .. testcode:: import numpy # Generate some data ar, ai = numpy.random.randn(2, 128) a[:] = ar + 1j*ai fft_a = fft_object() Note that calling the object like this performs the FFT and returns the result in an array. This is the *same* array as ``b``: .. doctest:: >>> fft_a is b True This is particularly useful when using :mod:`pyfftw.builders` to generate the :class:`pyfftw.FFTW` objects. Calling the FFT object followed by the inverse FFT object yields an output that is numerically the same as the original ``a`` (within numerical accuracy). .. doctest:: >>> fft_a = fft_object() >>> ifft_b = ifft_object() >>> ifft_b is c True >>> numpy.allclose(a, c) True >>> a is c False In this case, the normalisation of the DFT is performed automatically by the inverse FFTW object (``ifft_object``). This can be disabled by setting the ``normalise_idft=False`` argument. It is possible to change the data on which a :class:`pyfftw.FFTW` operates. The :meth:`pyfftw.FFTW.__call__` accepts both an ``input_array`` and an ``output_array`` argument to update the arrays. The arrays should be compatible with the arrays with which the :class:`pyfftw.FFTW` object was originally created. Please read the API docs on :meth:`pyfftw.FFTW.__call__` to fully understand the requirements for updating the array. .. doctest:: >>> d = pyfftw.empty_aligned(4, dtype='complex128') >>> e = pyfftw.empty_aligned(4, dtype='complex128') >>> f = pyfftw.empty_aligned(4, dtype='complex128') >>> fft_object = pyfftw.FFTW(d, e) >>> fft_object.input_array is d # get the input array from the object True >>> f[:] = [1, 2, 3, 4] # Add some data to f >>> fft_object(f) array([ 10.+0.j, -2.+2.j, -2.+0.j, -2.-2.j]) >>> fft_object.input_array is d # No longer true! False >>> fft_object.input_array is f # It has been updated with f :) True If the new input array is of the wrong dtype or wrongly strided, :meth:`pyfftw.FFTW.__call__` method will copy the new array into the internal array, if necessary changing it's dtype in the process. It should be made clear that the :meth:`pyfftw.FFTW.__call__` method is simply a helper routine around the other methods of the object. Though it is expected that most of the time :meth:`pyfftw.FFTW.__call__` will be sufficient, all the FFTW functionality can be accessed through other methods at a slightly lower level. Multi-Dimensional Transforms ~~~~~~~~~~~~~~~~~~~~~~~~~~~~ Arrays of more than one dimension are easily supported as well. In this case, the ``axes`` argument specifies over which axes the transform is to be taken. .. testcode:: import pyfftw a = pyfftw.empty_aligned((128, 64), dtype='complex128') b = pyfftw.empty_aligned((128, 64), dtype='complex128') # Plan an fft over the last axis fft_object_a = pyfftw.FFTW(a, b) # Over the first axis fft_object_b = pyfftw.FFTW(a, b, axes=(0,)) # Over the both axes fft_object_c = pyfftw.FFTW(a, b, axes=(0,1)) For further information on all the supported transforms, including real transforms, as well as full documentation on all the instantiation arguments, see the :class:`pyfftw.FFTW` documentation. .. _wisdom_tutorial: Wisdom ~~~~~~ When creating a :class:`pyfftw.FFTW` object, it is possible to instruct FFTW how much effort it should put into finding the fastest possible method for computing the DFT. This is done by specifying a suitable planner flag in ``flags`` argument to :class:`pyfftw.FFTW`. Some of the planner flags can take a very long time to complete which can be problematic. When the a particular transform has been created, distinguished by things like the data type, the shape, the stridings and the flags, FFTW keeps a record of the fastest way to compute such a transform in future. This is referred to as `wisdom `_. When the program is completed, the wisdom that has been accumulated is forgotten. It is possible to output the accumulated wisdom using the :ref:`wisdom output routines `. :func:`pyfftw.export_wisdom` exports and returns the wisdom as a tuple of strings that can be easily written to file. To load the wisdom back in, use the :func:`pyfftw.import_wisdom` function which takes as its argument that same tuple of strings that was returned from :func:`pyfftw.export_wisdom`. If for some reason you wish to forget the accumulated wisdom, call :func:`pyfftw.forget_wisdom`. .. _builders_tutorial: The :mod:`pyfftw.builders` functions ------------------------------------ If you absolutely need the flexibility of dealing with :class:`pyfftw.FFTW` directly, an easier option than constructing valid arrays and so on is to use the convenient :mod:`pyfftw.builders` package. These functions take care of much of the difficulty in specifying the exact size and dtype requirements to produce a valid scheme. The :mod:`pyfftw.builders` functions are a series of helper functions that provide an interface very much like that provided by :mod:`numpy.fft`, only instead of returning the result of the transform, a :class:`pyfftw.FFTW` object (or in some cases a wrapper around :class:`pyfftw.FFTW`) is returned. .. testcode:: import pyfftw a = pyfftw.empty_aligned((128, 64), dtype='complex128') # Generate some data ar, ai = numpy.random.randn(2, 128, 64) a[:] = ar + 1j*ai fft_object = pyfftw.builders.fft(a) b = fft_object() ``fft_object`` is an instance of :class:`pyfftw.FFTW`, ``b`` is the result of the DFT. Note that in this example, unlike creating a :class:`pyfftw.FFTW` object using the direct interface, we can fill the array in advance. This is because by default all the functions in :mod:`pyfftw.builders` keep a copy of the input array during creation (though this can be disabled). The :mod:`pyfftw.builders` functions construct an output array of the correct size and type. In the case of the regular DFTs, this always creates an output array of the same size as the input array. In the case of the real transform, the output array is the right shape to satisfy the scheme requirements. The precision of the transform is determined by the dtype of the input array. If the input array is a floating point array, then the precision of the floating point is used. If the input array is not a floating point array then a double precision transform is used. Any calls made to the resultant object with an array of the same size will then be copied into the internal array of the object, changing the dtype in the process. Like :mod:`numpy.fft`, it is possible to specify a length (in the one-dimensional case) or a shape (in the multi-dimensional case) that may be different to the array that is passed in. In such a case, a wrapper object of type :class:`pyfftw.builders._utils._FFTWWrapper` is returned. From an interface perspective, this is identical to :class:`pyfftw.FFTW`. The difference is in the way calls to the object are handled. With :class:`pyfftw.builders._utils._FFTWWrapper` objects, an array that is passed as an argument when calling the object is *copied* into the internal array. This is done by a suitable slicing of the new passed-in array and the internal array and is done precisely because the shape of the transform is different to the shape of the input array. .. testcode:: a = pyfftw.empty_aligned((128, 64), dtype='complex128') fft_wrapper_object = pyfftw.builders.fftn(a, s=(32, 256)) b = fft_wrapper_object() Inspecting these objects gives us their shapes: .. doctest:: >>> b.shape (32, 256) >>> fft_wrapper_object.input_array.shape (32, 256) >>> a.shape (128, 64) It is only possible to call ``fft_wrapper_object`` with an array that is the same shape as ``a``. In this case, the first axis of ``a`` is sliced to include only the first 32 elements, and the second axis of the internal array is sliced to include only the last 64 elements. This way, shapes are made consistent for copying. Understanding :mod:`numpy.fft`, these functions are largely self-explanatory. We point the reader to the :mod:`API docs ` for more information. If you like the :mod:`pyfftw.builders` functions, but do not need or wish to interact with :class:`pyfftw.FFTW`-instances directly, the third party :mod:`planfftw` package provides helper functions that return planned functions similar to those in :mod:`numpy.fft`, as well as FFTW-powered versions of some functions from :mod:`scipy.signal`. .. _configuration: Configuring FFTW planning effort and number of threads ------------------------------------------------------ The user may set the default number of threads used by the interfaces and builders at run time by assigning to ``pyfftw.config.NUM_THREADS``. Similarly the default `planning effort `_ may be set by assigning a string such as ``'FFTW_ESTIMATE'`` or ``'FFTW_MEASURE'`` to ``pyfftw.config.PLANNER_EFFORT``. For example, to change the effort to ``'FFTW_MEASURE'`` and specify 4 threads: .. testcode:: import pyfftw pyfftw.config.NUM_THREADS = 4 pyfftw.config.PLANNER_EFFORT = 'FFTW_MEASURE' All functions in :mod:`pyfftw.interfaces` and :mod:`pyfftw.builders` use the values from :mod:`pyfftw.config` when determining the default number of threads and planning effort. The initial values in pyfftw.config at import time can be controlled via the environment variables as detailed in the :ref:`configuration ` documentation. .. rubric:: Footnotes .. [#caveat] :mod:`pyfftw.interfaces` deals with repeated values in the ``axes`` argument differently to :mod:`numpy.fft` (and probably to :mod:`scipy.fftpack` to, but that's not documented clearly). Specifically, :mod:`numpy.fft` takes the transform along a given axis as many times as it appears in the ``axes`` argument. :mod:`pyfftw.interfaces` takes the transform only once along each axis that appears, regardless of how many times it appears. This is deemed to be such a fringe corner case that it is ignored. pyFFTW-0.13.1/environment_doc.yml000066400000000000000000000002061435600752200166310ustar00rootroot00000000000000name: pyfftw_doc_env channels: - conda-forge dependencies: - cython - fftw - numpy - python - scipy - dask - sphinx pyFFTW-0.13.1/include/000077500000000000000000000000001435600752200143425ustar00rootroot00000000000000pyFFTW-0.13.1/include/cpu.h000066400000000000000000000062701435600752200153070ustar00rootroot00000000000000/* * Copyright 2016 Knowledge Economy Developments Ltd * * Henry Gomersall * heng@kedevelopments.co.uk * * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * * Neither the name of the copyright holder nor the names of its contributors * may be used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ /* Small utilities for inspecting the CPU */ #ifndef CPU_H #define CPU_H #if __STDC_VERSION__ >= 199901L /* "inline" is a keyword */ #else # define inline #endif #if defined(__amd64__) || defined (_M_X64) || defined(__i386__) || defined(_M_IX86) || defined(_X86_) #define AVX_WORD 2 #define AVX_BIT 28 #define SSE_WORD 3 #define SSE_BIT 25 #ifdef _MSC_VER /* Visual Studio Code */ #include #define cpuid(func, cpuinfo)\ __cpuid(cpuinfo, func); #else /* generic x86 Assembly code (based on wikipedia example) * Firstly it's necessary to move ebx into an interim * register to protect it (cpuid clobbers eax, ebx ecx and edx) * */ #define cpuid(func, cpuinfo)\ cpuinfo[0] = func; /* Load the first entry with the func id */\ __asm__ __volatile__ \ ("mov %%ebx, %%edi;" /* 32bit PIC: don't clobber ebx */ \ "cpuid;" \ "mov %%ebx, %%esi;" \ "mov %%edi, %%ebx;" \ :"+a" (cpuinfo[0]), "=S" (cpuinfo[1]), /* eax rw, esi read */ \ "=c" (cpuinfo[2]), "=d" (cpuinfo[3]) /* ecx read, edx read */\ : :"edi") #endif /* Returns the byte alignment for optimum simd operations */ static inline int simd_alignment(void){ int cpuinfo[4]; /* This gets the cpuinfo (set by 1)*/ cpuid(1, cpuinfo); if (cpuinfo[AVX_WORD] & (1< 1000 #pragma once #endif #if _MSC_VER >= 1600 // [ #include #else // ] _MSC_VER >= 1600 [ #include // For Visual Studio 6 in C++ mode and for many Visual Studio versions when // compiling for ARM we should wrap include with 'extern "C++" {}' // or compiler give many errors like this: // error C2733: second C linkage of overloaded function 'wmemchr' not allowed #ifdef __cplusplus extern "C" { #endif # include #ifdef __cplusplus } #endif // Define _W64 macros to mark types changing their size, like intptr_t. #ifndef _W64 # if !defined(__midl) && (defined(_X86_) || defined(_M_IX86)) && _MSC_VER >= 1300 # define _W64 __w64 # else # define _W64 # endif #endif // 7.18.1 Integer types // 7.18.1.1 Exact-width integer types // Visual Studio 6 and Embedded Visual C++ 4 doesn't // realize that, e.g. char has the same size as __int8 // so we give up on __intX for them. #if (_MSC_VER < 1300) typedef signed char int8_t; typedef signed short int16_t; typedef signed int int32_t; typedef unsigned char uint8_t; typedef unsigned short uint16_t; typedef unsigned int uint32_t; #else typedef signed __int8 int8_t; typedef signed __int16 int16_t; typedef signed __int32 int32_t; typedef unsigned __int8 uint8_t; typedef unsigned __int16 uint16_t; typedef unsigned __int32 uint32_t; #endif typedef signed __int64 int64_t; typedef unsigned __int64 uint64_t; // 7.18.1.2 Minimum-width integer types typedef int8_t int_least8_t; typedef int16_t int_least16_t; typedef int32_t int_least32_t; typedef int64_t int_least64_t; typedef uint8_t uint_least8_t; typedef uint16_t uint_least16_t; typedef uint32_t uint_least32_t; typedef uint64_t uint_least64_t; // 7.18.1.3 Fastest minimum-width integer types typedef int8_t int_fast8_t; typedef int16_t int_fast16_t; typedef int32_t int_fast32_t; typedef int64_t int_fast64_t; typedef uint8_t uint_fast8_t; typedef uint16_t uint_fast16_t; typedef uint32_t uint_fast32_t; typedef uint64_t uint_fast64_t; // 7.18.1.4 Integer types capable of holding object pointers #ifdef _WIN64 // [ typedef signed __int64 intptr_t; typedef unsigned __int64 uintptr_t; #else // _WIN64 ][ typedef _W64 signed int intptr_t; typedef _W64 unsigned int uintptr_t; #endif // _WIN64 ] // 7.18.1.5 Greatest-width integer types typedef int64_t intmax_t; typedef uint64_t uintmax_t; // 7.18.2 Limits of specified-width integer types #if !defined(__cplusplus) || defined(__STDC_LIMIT_MACROS) // [ See footnote 220 at page 257 and footnote 221 at page 259 // 7.18.2.1 Limits of exact-width integer types #define INT8_MIN ((int8_t)_I8_MIN) #define INT8_MAX _I8_MAX #define INT16_MIN ((int16_t)_I16_MIN) #define INT16_MAX _I16_MAX #define INT32_MIN ((int32_t)_I32_MIN) #define INT32_MAX _I32_MAX #define INT64_MIN ((int64_t)_I64_MIN) #define INT64_MAX _I64_MAX #define UINT8_MAX _UI8_MAX #define UINT16_MAX _UI16_MAX #define UINT32_MAX _UI32_MAX #define UINT64_MAX _UI64_MAX // 7.18.2.2 Limits of minimum-width integer types #define INT_LEAST8_MIN INT8_MIN #define INT_LEAST8_MAX INT8_MAX #define INT_LEAST16_MIN INT16_MIN #define INT_LEAST16_MAX INT16_MAX #define INT_LEAST32_MIN INT32_MIN #define INT_LEAST32_MAX INT32_MAX #define INT_LEAST64_MIN INT64_MIN #define INT_LEAST64_MAX INT64_MAX #define UINT_LEAST8_MAX UINT8_MAX #define UINT_LEAST16_MAX UINT16_MAX #define UINT_LEAST32_MAX UINT32_MAX #define UINT_LEAST64_MAX UINT64_MAX // 7.18.2.3 Limits of fastest minimum-width integer types #define INT_FAST8_MIN INT8_MIN #define INT_FAST8_MAX INT8_MAX #define INT_FAST16_MIN INT16_MIN #define INT_FAST16_MAX INT16_MAX #define INT_FAST32_MIN INT32_MIN #define INT_FAST32_MAX INT32_MAX #define INT_FAST64_MIN INT64_MIN #define INT_FAST64_MAX INT64_MAX #define UINT_FAST8_MAX UINT8_MAX #define UINT_FAST16_MAX UINT16_MAX #define UINT_FAST32_MAX UINT32_MAX #define UINT_FAST64_MAX UINT64_MAX // 7.18.2.4 Limits of integer types capable of holding object pointers #ifdef _WIN64 // [ # define INTPTR_MIN INT64_MIN # define INTPTR_MAX INT64_MAX # define UINTPTR_MAX UINT64_MAX #else // _WIN64 ][ # define INTPTR_MIN INT32_MIN # define INTPTR_MAX INT32_MAX # define UINTPTR_MAX UINT32_MAX #endif // _WIN64 ] // 7.18.2.5 Limits of greatest-width integer types #define INTMAX_MIN INT64_MIN #define INTMAX_MAX INT64_MAX #define UINTMAX_MAX UINT64_MAX // 7.18.3 Limits of other integer types #ifdef _WIN64 // [ # define PTRDIFF_MIN _I64_MIN # define PTRDIFF_MAX _I64_MAX #else // _WIN64 ][ # define PTRDIFF_MIN _I32_MIN # define PTRDIFF_MAX _I32_MAX #endif // _WIN64 ] #define SIG_ATOMIC_MIN INT_MIN #define SIG_ATOMIC_MAX INT_MAX #ifndef SIZE_MAX // [ # ifdef _WIN64 // [ # define SIZE_MAX _UI64_MAX # else // _WIN64 ][ # define SIZE_MAX _UI32_MAX # endif // _WIN64 ] #endif // SIZE_MAX ] // WCHAR_MIN and WCHAR_MAX are also defined in #ifndef WCHAR_MIN // [ # define WCHAR_MIN 0 #endif // WCHAR_MIN ] #ifndef WCHAR_MAX // [ # define WCHAR_MAX _UI16_MAX #endif // WCHAR_MAX ] #define WINT_MIN 0 #define WINT_MAX _UI16_MAX #endif // __STDC_LIMIT_MACROS ] // 7.18.4 Limits of other integer types #if !defined(__cplusplus) || defined(__STDC_CONSTANT_MACROS) // [ See footnote 224 at page 260 // 7.18.4.1 Macros for minimum-width integer constants #define INT8_C(val) val##i8 #define INT16_C(val) val##i16 #define INT32_C(val) val##i32 #define INT64_C(val) val##i64 #define UINT8_C(val) val##ui8 #define UINT16_C(val) val##ui16 #define UINT32_C(val) val##ui32 #define UINT64_C(val) val##ui64 // 7.18.4.2 Macros for greatest-width integer constants // These #ifndef's are needed to prevent collisions with . // Check out Issue 9 for the details. #ifndef INTMAX_C // [ # define INTMAX_C INT64_C #endif // INTMAX_C ] #ifndef UINTMAX_C // [ # define UINTMAX_C UINT64_C #endif // UINTMAX_C ] #endif // __STDC_CONSTANT_MACROS ] #endif // _MSC_VER >= 1600 ] #endif // _MSC_STDINT_H_ ] pyFFTW-0.13.1/include/msvc_2010/000077500000000000000000000000001435600752200157545ustar00rootroot00000000000000pyFFTW-0.13.1/include/msvc_2010/stdint.h000066400000000000000000000202441435600752200174340ustar00rootroot00000000000000// ISO C9x compliant stdint.h for Microsoft Visual Studio // Based on ISO/IEC 9899:TC2 Committee draft (May 6, 2005) WG14/N1124 // // Copyright (c) 2006-2013 Alexander Chemeris // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // 1. Redistributions of source code must retain the above copyright notice, // this list of conditions and the following disclaimer. // // 2. Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // // 3. Neither the name of the product nor the names of its contributors may // be used to endorse or promote products derived from this software // without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED // WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF // MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO // EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, // PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; // OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR // OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF // ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // /////////////////////////////////////////////////////////////////////////////// #ifndef _MSC_VER // [ #error "Use this header only with Microsoft Visual C++ compilers!" #endif // _MSC_VER ] #ifndef _MSC_STDINT_H_ // [ #define _MSC_STDINT_H_ #if _MSC_VER > 1000 #pragma once #endif // We comment out the restriction to use the default stdint.h // This fix is to make sure the stdint we define here is used by appveyor // for msvc 2010. This shouldn't need to be done, but it does. It's // probably safe regardless. //#if _MSC_VER >= 1600 // [ //#if _MSC_VER >= 1600 //#include //#else // ] _MSC_VER >= 1600 [ #include // For Visual Studio 6 in C++ mode and for many Visual Studio versions when // compiling for ARM we should wrap include with 'extern "C++" {}' // or compiler give many errors like this: // error C2733: second C linkage of overloaded function 'wmemchr' not allowed #ifdef __cplusplus extern "C" { #endif # include #ifdef __cplusplus } #endif // Define _W64 macros to mark types changing their size, like intptr_t. #ifndef _W64 # if !defined(__midl) && (defined(_X86_) || defined(_M_IX86)) && _MSC_VER >= 1300 # define _W64 __w64 # else # define _W64 # endif #endif // 7.18.1 Integer types // 7.18.1.1 Exact-width integer types // Visual Studio 6 and Embedded Visual C++ 4 doesn't // realize that, e.g. char has the same size as __int8 // so we give up on __intX for them. #if (_MSC_VER < 1300) typedef signed char int8_t; typedef signed short int16_t; typedef signed int int32_t; typedef unsigned char uint8_t; typedef unsigned short uint16_t; typedef unsigned int uint32_t; #else typedef signed __int8 int8_t; typedef signed __int16 int16_t; typedef signed __int32 int32_t; typedef unsigned __int8 uint8_t; typedef unsigned __int16 uint16_t; typedef unsigned __int32 uint32_t; #endif typedef signed __int64 int64_t; typedef unsigned __int64 uint64_t; // 7.18.1.2 Minimum-width integer types typedef int8_t int_least8_t; typedef int16_t int_least16_t; typedef int32_t int_least32_t; typedef int64_t int_least64_t; typedef uint8_t uint_least8_t; typedef uint16_t uint_least16_t; typedef uint32_t uint_least32_t; typedef uint64_t uint_least64_t; // 7.18.1.3 Fastest minimum-width integer types typedef int8_t int_fast8_t; typedef int16_t int_fast16_t; typedef int32_t int_fast32_t; typedef int64_t int_fast64_t; typedef uint8_t uint_fast8_t; typedef uint16_t uint_fast16_t; typedef uint32_t uint_fast32_t; typedef uint64_t uint_fast64_t; // 7.18.1.4 Integer types capable of holding object pointers #ifdef _WIN64 // [ typedef signed __int64 intptr_t; typedef unsigned __int64 uintptr_t; #else // _WIN64 ][ typedef _W64 signed int intptr_t; typedef _W64 unsigned int uintptr_t; #endif // _WIN64 ] // 7.18.1.5 Greatest-width integer types typedef int64_t intmax_t; typedef uint64_t uintmax_t; // 7.18.2 Limits of specified-width integer types #if !defined(__cplusplus) || defined(__STDC_LIMIT_MACROS) // [ See footnote 220 at page 257 and footnote 221 at page 259 // 7.18.2.1 Limits of exact-width integer types #define INT8_MIN ((int8_t)_I8_MIN) #define INT8_MAX _I8_MAX #define INT16_MIN ((int16_t)_I16_MIN) #define INT16_MAX _I16_MAX #define INT32_MIN ((int32_t)_I32_MIN) #define INT32_MAX _I32_MAX #define INT64_MIN ((int64_t)_I64_MIN) #define INT64_MAX _I64_MAX #define UINT8_MAX _UI8_MAX #define UINT16_MAX _UI16_MAX #define UINT32_MAX _UI32_MAX #define UINT64_MAX _UI64_MAX // 7.18.2.2 Limits of minimum-width integer types #define INT_LEAST8_MIN INT8_MIN #define INT_LEAST8_MAX INT8_MAX #define INT_LEAST16_MIN INT16_MIN #define INT_LEAST16_MAX INT16_MAX #define INT_LEAST32_MIN INT32_MIN #define INT_LEAST32_MAX INT32_MAX #define INT_LEAST64_MIN INT64_MIN #define INT_LEAST64_MAX INT64_MAX #define UINT_LEAST8_MAX UINT8_MAX #define UINT_LEAST16_MAX UINT16_MAX #define UINT_LEAST32_MAX UINT32_MAX #define UINT_LEAST64_MAX UINT64_MAX // 7.18.2.3 Limits of fastest minimum-width integer types #define INT_FAST8_MIN INT8_MIN #define INT_FAST8_MAX INT8_MAX #define INT_FAST16_MIN INT16_MIN #define INT_FAST16_MAX INT16_MAX #define INT_FAST32_MIN INT32_MIN #define INT_FAST32_MAX INT32_MAX #define INT_FAST64_MIN INT64_MIN #define INT_FAST64_MAX INT64_MAX #define UINT_FAST8_MAX UINT8_MAX #define UINT_FAST16_MAX UINT16_MAX #define UINT_FAST32_MAX UINT32_MAX #define UINT_FAST64_MAX UINT64_MAX // 7.18.2.4 Limits of integer types capable of holding object pointers #ifdef _WIN64 // [ # define INTPTR_MIN INT64_MIN # define INTPTR_MAX INT64_MAX # define UINTPTR_MAX UINT64_MAX #else // _WIN64 ][ # define INTPTR_MIN INT32_MIN # define INTPTR_MAX INT32_MAX # define UINTPTR_MAX UINT32_MAX #endif // _WIN64 ] // 7.18.2.5 Limits of greatest-width integer types #define INTMAX_MIN INT64_MIN #define INTMAX_MAX INT64_MAX #define UINTMAX_MAX UINT64_MAX // 7.18.3 Limits of other integer types #ifdef _WIN64 // [ # define PTRDIFF_MIN _I64_MIN # define PTRDIFF_MAX _I64_MAX #else // _WIN64 ][ # define PTRDIFF_MIN _I32_MIN # define PTRDIFF_MAX _I32_MAX #endif // _WIN64 ] #define SIG_ATOMIC_MIN INT_MIN #define SIG_ATOMIC_MAX INT_MAX #ifndef SIZE_MAX // [ # ifdef _WIN64 // [ # define SIZE_MAX _UI64_MAX # else // _WIN64 ][ # define SIZE_MAX _UI32_MAX # endif // _WIN64 ] #endif // SIZE_MAX ] // WCHAR_MIN and WCHAR_MAX are also defined in #ifndef WCHAR_MIN // [ # define WCHAR_MIN 0 #endif // WCHAR_MIN ] #ifndef WCHAR_MAX // [ # define WCHAR_MAX _UI16_MAX #endif // WCHAR_MAX ] #define WINT_MIN 0 #define WINT_MAX _UI16_MAX #endif // __STDC_LIMIT_MACROS ] // 7.18.4 Limits of other integer types #if !defined(__cplusplus) || defined(__STDC_CONSTANT_MACROS) // [ See footnote 224 at page 260 // 7.18.4.1 Macros for minimum-width integer constants #define INT8_C(val) val##i8 #define INT16_C(val) val##i16 #define INT32_C(val) val##i32 #define INT64_C(val) val##i64 #define UINT8_C(val) val##ui8 #define UINT16_C(val) val##ui16 #define UINT32_C(val) val##ui32 #define UINT64_C(val) val##ui64 // 7.18.4.2 Macros for greatest-width integer constants // These #ifndef's are needed to prevent collisions with . // Check out Issue 9 for the details. #ifndef INTMAX_C // [ # define INTMAX_C INT64_C #endif // INTMAX_C ] #ifndef UINTMAX_C // [ # define UINTMAX_C UINT64_C #endif // UINTMAX_C ] #endif // __STDC_CONSTANT_MACROS ] //#endif // _MSC_VER >= 1600 ] #endif // _MSC_STDINT_H_ ] pyFFTW-0.13.1/include/pyfftw_complex.h000066400000000000000000000042141435600752200175620ustar00rootroot00000000000000/* * Copyright 2014 Knowledge Economy Developments Ltd * * Henry Gomersall * heng@kedevelopments.co.uk * * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * * Redistributions of source code must retain the above copyright notice, this * list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * * Neither the name of the copyright holder nor the names of its contributors * may be used to endorse or promote products derived from this software without * specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ /* Defines complex types that are bit compatible with C99's complex.h * and (crucially) the same type as expected by fftw3.h. * Note, don't use this with complex.h. fftw3.h checks to see whether * complex.h is included and then uses that to set the interface. * Since MSVC doesn't support C99, by using the following types we * have a cross platform/compiler solution. * * */ #ifndef PYFFTW_COMPLEX_H #define PYFFTW_COMPLEX_H typedef float cfloat[2]; typedef double cdouble[2]; typedef long double clongdouble[2]; #endif /* Header guard */ pyFFTW-0.13.1/include/win/000077500000000000000000000000001435600752200151375ustar00rootroot00000000000000pyFFTW-0.13.1/include/win/fftw3.h000066400000000000000000000433341435600752200163500ustar00rootroot00000000000000/* * Copyright (c) 2003, 2007-11 Matteo Frigo * Copyright (c) 2003, 2007-11 Massachusetts Institute of Technology * * The following statement of license applies *only* to this header file, * and *not* to the other files distributed with FFTW or derived therefrom: * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE * GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ /***************************** NOTE TO USERS ********************************* * * THIS IS A HEADER FILE, NOT A MANUAL * * If you want to know how to use FFTW, please read the manual, * online at http://www.fftw.org/doc/ and also included with FFTW. * For a quick start, see the manual's tutorial section. * * (Reading header files to learn how to use a library is a habit * stemming from code lacking a proper manual. Arguably, it's a * *bad* habit in most cases, because header files can contain * interfaces that are not part of the public, stable API.) * ****************************************************************************/ #ifndef FFTW3_H #define FFTW3_H #include #ifdef __cplusplus extern "C" { #endif /* __cplusplus */ /* If is included, use the C99 complex type. Otherwise define a type bit-compatible with C99 complex */ #if !defined(FFTW_NO_Complex) && defined(_Complex_I) && defined(complex) && defined(I) # define FFTW_DEFINE_COMPLEX(R, C) typedef R _Complex C #else # define FFTW_DEFINE_COMPLEX(R, C) typedef R C[2] #endif #define FFTW_CONCAT(prefix, name) prefix ## name #define FFTW_MANGLE_DOUBLE(name) FFTW_CONCAT(fftw_, name) #define FFTW_MANGLE_FLOAT(name) FFTW_CONCAT(fftwf_, name) #define FFTW_MANGLE_LONG_DOUBLE(name) FFTW_CONCAT(fftwl_, name) #define FFTW_MANGLE_QUAD(name) FFTW_CONCAT(fftwq_, name) /* IMPORTANT: for Windows compilers, you should add a line */ #define FFTW_DLL /* here and in kernel/ifftw.h if you are compiling/using FFTW as a DLL, in order to do the proper importing/exporting, or alternatively compile with -DFFTW_DLL or the equivalent command-line flag. This is not necessary under MinGW/Cygwin, where libtool does the imports/exports automatically. */ #if defined(FFTW_DLL) && (defined(_WIN32) || defined(__WIN32__)) /* annoying Windows syntax for shared-library declarations */ # if defined(COMPILING_FFTW) /* defined in api.h when compiling FFTW */ # define FFTW_EXTERN extern __declspec(dllexport) # else /* user is calling FFTW; import symbol */ # define FFTW_EXTERN extern __declspec(dllimport) # endif #else # define FFTW_EXTERN extern #endif enum fftw_r2r_kind_do_not_use_me { FFTW_R2HC=0, FFTW_HC2R=1, FFTW_DHT=2, FFTW_REDFT00=3, FFTW_REDFT01=4, FFTW_REDFT10=5, FFTW_REDFT11=6, FFTW_RODFT00=7, FFTW_RODFT01=8, FFTW_RODFT10=9, FFTW_RODFT11=10 }; struct fftw_iodim_do_not_use_me { int n; /* dimension size */ int is; /* input stride */ int os; /* output stride */ }; #include /* for ptrdiff_t */ struct fftw_iodim64_do_not_use_me { ptrdiff_t n; /* dimension size */ ptrdiff_t is; /* input stride */ ptrdiff_t os; /* output stride */ }; typedef void (*fftw_write_char_func_do_not_use_me)(char c, void *); typedef int (*fftw_read_char_func_do_not_use_me)(void *); /* huge second-order macro that defines prototypes for all API functions. We expand this macro for each supported precision X: name-mangling macro R: real data type C: complex data type */ #define FFTW_DEFINE_API(X, R, C) \ \ FFTW_DEFINE_COMPLEX(R, C); \ \ typedef struct X(plan_s) *X(plan); \ \ typedef struct fftw_iodim_do_not_use_me X(iodim); \ typedef struct fftw_iodim64_do_not_use_me X(iodim64); \ \ typedef enum fftw_r2r_kind_do_not_use_me X(r2r_kind); \ \ typedef fftw_write_char_func_do_not_use_me X(write_char_func); \ typedef fftw_read_char_func_do_not_use_me X(read_char_func); \ \ FFTW_EXTERN void X(execute)(const X(plan) p); \ \ FFTW_EXTERN X(plan) X(plan_dft)(int rank, const int *n, \ C *in, C *out, int sign, unsigned flags); \ \ FFTW_EXTERN X(plan) X(plan_dft_1d)(int n, C *in, C *out, int sign, \ unsigned flags); \ FFTW_EXTERN X(plan) X(plan_dft_2d)(int n0, int n1, \ C *in, C *out, int sign, unsigned flags); \ FFTW_EXTERN X(plan) X(plan_dft_3d)(int n0, int n1, int n2, \ C *in, C *out, int sign, unsigned flags); \ \ FFTW_EXTERN X(plan) X(plan_many_dft)(int rank, const int *n, \ int howmany, \ C *in, const int *inembed, \ int istride, int idist, \ C *out, const int *onembed, \ int ostride, int odist, \ int sign, unsigned flags); \ \ FFTW_EXTERN X(plan) X(plan_guru_dft)(int rank, const X(iodim) *dims, \ int howmany_rank, \ const X(iodim) *howmany_dims, \ C *in, C *out, \ int sign, unsigned flags); \ FFTW_EXTERN X(plan) X(plan_guru_split_dft)(int rank, const X(iodim) *dims, \ int howmany_rank, \ const X(iodim) *howmany_dims, \ R *ri, R *ii, R *ro, R *io, \ unsigned flags); \ \ FFTW_EXTERN X(plan) X(plan_guru64_dft)(int rank, \ const X(iodim64) *dims, \ int howmany_rank, \ const X(iodim64) *howmany_dims, \ C *in, C *out, \ int sign, unsigned flags); \ FFTW_EXTERN X(plan) X(plan_guru64_split_dft)(int rank, \ const X(iodim64) *dims, \ int howmany_rank, \ const X(iodim64) *howmany_dims, \ R *ri, R *ii, R *ro, R *io, \ unsigned flags); \ \ FFTW_EXTERN void X(execute_dft)(const X(plan) p, C *in, C *out); \ FFTW_EXTERN void X(execute_split_dft)(const X(plan) p, R *ri, R *ii, \ R *ro, R *io); \ \ FFTW_EXTERN X(plan) X(plan_many_dft_r2c)(int rank, const int *n, \ int howmany, \ R *in, const int *inembed, \ int istride, int idist, \ C *out, const int *onembed, \ int ostride, int odist, \ unsigned flags); \ \ FFTW_EXTERN X(plan) X(plan_dft_r2c)(int rank, const int *n, \ R *in, C *out, unsigned flags); \ \ FFTW_EXTERN X(plan) X(plan_dft_r2c_1d)(int n,R *in,C *out,unsigned flags); \ FFTW_EXTERN X(plan) X(plan_dft_r2c_2d)(int n0, int n1, \ R *in, C *out, unsigned flags); \ FFTW_EXTERN X(plan) X(plan_dft_r2c_3d)(int n0, int n1, \ int n2, \ R *in, C *out, unsigned flags); \ \ \ FFTW_EXTERN X(plan) X(plan_many_dft_c2r)(int rank, const int *n, \ int howmany, \ C *in, const int *inembed, \ int istride, int idist, \ R *out, const int *onembed, \ int ostride, int odist, \ unsigned flags); \ \ FFTW_EXTERN X(plan) X(plan_dft_c2r)(int rank, const int *n, \ C *in, R *out, unsigned flags); \ \ FFTW_EXTERN X(plan) X(plan_dft_c2r_1d)(int n,C *in,R *out,unsigned flags); \ FFTW_EXTERN X(plan) X(plan_dft_c2r_2d)(int n0, int n1, \ C *in, R *out, unsigned flags); \ FFTW_EXTERN X(plan) X(plan_dft_c2r_3d)(int n0, int n1, \ int n2, \ C *in, R *out, unsigned flags); \ \ FFTW_EXTERN X(plan) X(plan_guru_dft_r2c)(int rank, const X(iodim) *dims, \ int howmany_rank, \ const X(iodim) *howmany_dims, \ R *in, C *out, \ unsigned flags); \ FFTW_EXTERN X(plan) X(plan_guru_dft_c2r)(int rank, const X(iodim) *dims, \ int howmany_rank, \ const X(iodim) *howmany_dims, \ C *in, R *out, \ unsigned flags); \ \ FFTW_EXTERN X(plan) X(plan_guru_split_dft_r2c)( \ int rank, const X(iodim) *dims, \ int howmany_rank, \ const X(iodim) *howmany_dims, \ R *in, R *ro, R *io, \ unsigned flags); \ FFTW_EXTERN X(plan) X(plan_guru_split_dft_c2r)( \ int rank, const X(iodim) *dims, \ int howmany_rank, \ const X(iodim) *howmany_dims, \ R *ri, R *ii, R *out, \ unsigned flags); \ \ FFTW_EXTERN X(plan) X(plan_guru64_dft_r2c)(int rank, \ const X(iodim64) *dims, \ int howmany_rank, \ const X(iodim64) *howmany_dims, \ R *in, C *out, \ unsigned flags); \ FFTW_EXTERN X(plan) X(plan_guru64_dft_c2r)(int rank, \ const X(iodim64) *dims, \ int howmany_rank, \ const X(iodim64) *howmany_dims, \ C *in, R *out, \ unsigned flags); \ \ FFTW_EXTERN X(plan) X(plan_guru64_split_dft_r2c)( \ int rank, const X(iodim64) *dims, \ int howmany_rank, \ const X(iodim64) *howmany_dims, \ R *in, R *ro, R *io, \ unsigned flags); \ FFTW_EXTERN X(plan) X(plan_guru64_split_dft_c2r)( \ int rank, const X(iodim64) *dims, \ int howmany_rank, \ const X(iodim64) *howmany_dims, \ R *ri, R *ii, R *out, \ unsigned flags); \ \ FFTW_EXTERN void X(execute_dft_r2c)(const X(plan) p, R *in, C *out); \ FFTW_EXTERN void X(execute_dft_c2r)(const X(plan) p, C *in, R *out); \ \ FFTW_EXTERN void X(execute_split_dft_r2c)(const X(plan) p, \ R *in, R *ro, R *io); \ FFTW_EXTERN void X(execute_split_dft_c2r)(const X(plan) p, \ R *ri, R *ii, R *out); \ \ FFTW_EXTERN X(plan) X(plan_many_r2r)(int rank, const int *n, \ int howmany, \ R *in, const int *inembed, \ int istride, int idist, \ R *out, const int *onembed, \ int ostride, int odist, \ const X(r2r_kind) *kind, unsigned flags); \ \ FFTW_EXTERN X(plan) X(plan_r2r)(int rank, const int *n, R *in, R *out, \ const X(r2r_kind) *kind, unsigned flags); \ \ FFTW_EXTERN X(plan) X(plan_r2r_1d)(int n, R *in, R *out, \ X(r2r_kind) kind, unsigned flags); \ FFTW_EXTERN X(plan) X(plan_r2r_2d)(int n0, int n1, R *in, R *out, \ X(r2r_kind) kind0, X(r2r_kind) kind1, \ unsigned flags); \ FFTW_EXTERN X(plan) X(plan_r2r_3d)(int n0, int n1, int n2, \ R *in, R *out, X(r2r_kind) kind0, \ X(r2r_kind) kind1, X(r2r_kind) kind2, \ unsigned flags); \ \ FFTW_EXTERN X(plan) X(plan_guru_r2r)(int rank, const X(iodim) *dims, \ int howmany_rank, \ const X(iodim) *howmany_dims, \ R *in, R *out, \ const X(r2r_kind) *kind, unsigned flags); \ \ FFTW_EXTERN X(plan) X(plan_guru64_r2r)(int rank, const X(iodim64) *dims, \ int howmany_rank, \ const X(iodim64) *howmany_dims, \ R *in, R *out, \ const X(r2r_kind) *kind, unsigned flags); \ \ FFTW_EXTERN void X(execute_r2r)(const X(plan) p, R *in, R *out); \ \ FFTW_EXTERN void X(destroy_plan)(X(plan) p); \ FFTW_EXTERN void X(forget_wisdom)(void); \ FFTW_EXTERN void X(cleanup)(void); \ \ FFTW_EXTERN void X(set_timelimit)(double t); \ \ FFTW_EXTERN void X(plan_with_nthreads)(int nthreads); \ FFTW_EXTERN int X(init_threads)(void); \ FFTW_EXTERN void X(cleanup_threads)(void); \ \ FFTW_EXTERN int X(export_wisdom_to_filename)(const char *filename); \ FFTW_EXTERN void X(export_wisdom_to_file)(FILE *output_file); \ FFTW_EXTERN char *X(export_wisdom_to_string)(void); \ FFTW_EXTERN void X(export_wisdom)(X(write_char_func) write_char, \ void *data); \ FFTW_EXTERN int X(import_system_wisdom)(void); \ FFTW_EXTERN int X(import_wisdom_from_filename)(const char *filename); \ FFTW_EXTERN int X(import_wisdom_from_file)(FILE *input_file); \ FFTW_EXTERN int X(import_wisdom_from_string)(const char *input_string); \ FFTW_EXTERN int X(import_wisdom)(X(read_char_func) read_char, void *data); \ \ FFTW_EXTERN void X(fprint_plan)(const X(plan) p, FILE *output_file); \ FFTW_EXTERN void X(print_plan)(const X(plan) p); \ \ FFTW_EXTERN void *X(malloc)(size_t n); \ FFTW_EXTERN R *X(alloc_real)(size_t n); \ FFTW_EXTERN C *X(alloc_complex)(size_t n); \ FFTW_EXTERN void X(free)(void *p); \ \ FFTW_EXTERN void X(flops)(const X(plan) p, \ double *add, double *mul, double *fmas); \ FFTW_EXTERN double X(estimate_cost)(const X(plan) p); \ FFTW_EXTERN double X(cost)(const X(plan) p); \ \ FFTW_EXTERN const char X(version)[]; \ FFTW_EXTERN const char X(cc)[]; \ FFTW_EXTERN const char X(codelet_optim)[]; /* end of FFTW_DEFINE_API macro */ FFTW_DEFINE_API(FFTW_MANGLE_DOUBLE, double, fftw_complex) FFTW_DEFINE_API(FFTW_MANGLE_FLOAT, float, fftwf_complex) FFTW_DEFINE_API(FFTW_MANGLE_LONG_DOUBLE, long double, fftwl_complex) /* __float128 (quad precision) is a gcc extension on i386, x86_64, and ia64 for gcc >= 4.6 (compiled in FFTW with --enable-quad-precision) */ #if (__GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 6)) \ && !(defined(__ICC) || defined(__INTEL_COMPILER)) \ && (defined(__i386__) || defined(__x86_64__) || defined(__ia64__)) # if !defined(FFTW_NO_Complex) && defined(_Complex_I) && defined(complex) && defined(I) /* note: __float128 is a typedef, which is not supported with the _Complex keyword in gcc, so instead we use this ugly __attribute__ version. However, we can't simply pass the __attribute__ version to FFTW_DEFINE_API because the __attribute__ confuses gcc in pointer types. Hence redefining FFTW_DEFINE_COMPLEX. Ugh. */ # undef FFTW_DEFINE_COMPLEX # define FFTW_DEFINE_COMPLEX(R, C) typedef _Complex float __attribute__((mode(TC))) C # endif FFTW_DEFINE_API(FFTW_MANGLE_QUAD, __float128, fftwq_complex) #endif #define FFTW_FORWARD (-1) #define FFTW_BACKWARD (+1) #define FFTW_NO_TIMELIMIT (-1.0) /* documented flags */ #define FFTW_MEASURE (0U) #define FFTW_DESTROY_INPUT (1U << 0) #define FFTW_UNALIGNED (1U << 1) #define FFTW_CONSERVE_MEMORY (1U << 2) #define FFTW_EXHAUSTIVE (1U << 3) /* NO_EXHAUSTIVE is default */ #define FFTW_PRESERVE_INPUT (1U << 4) /* cancels FFTW_DESTROY_INPUT */ #define FFTW_PATIENT (1U << 5) /* IMPATIENT is default */ #define FFTW_ESTIMATE (1U << 6) #define FFTW_WISDOM_ONLY (1U << 21) /* undocumented beyond-guru flags */ #define FFTW_ESTIMATE_PATIENT (1U << 7) #define FFTW_BELIEVE_PCOST (1U << 8) #define FFTW_NO_DFT_R2HC (1U << 9) #define FFTW_NO_NONTHREADED (1U << 10) #define FFTW_NO_BUFFERING (1U << 11) #define FFTW_NO_INDIRECT_OP (1U << 12) #define FFTW_ALLOW_LARGE_GENERIC (1U << 13) /* NO_LARGE_GENERIC is default */ #define FFTW_NO_RANK_SPLITS (1U << 14) #define FFTW_NO_VRANK_SPLITS (1U << 15) #define FFTW_NO_VRECURSE (1U << 16) #define FFTW_NO_SIMD (1U << 17) #define FFTW_NO_SLOW (1U << 18) #define FFTW_NO_FIXED_RADIX_LARGE_N (1U << 19) #define FFTW_ALLOW_PRUNING (1U << 20) #ifdef __cplusplus } /* extern "C" */ #endif /* __cplusplus */ #endif /* FFTW3_H */ pyFFTW-0.13.1/pyfftw/000077500000000000000000000000001435600752200142365ustar00rootroot00000000000000pyFFTW-0.13.1/pyfftw/__init__.py000066400000000000000000000024501435600752200163500ustar00rootroot00000000000000#!/usr/bin/env python # The pyfftw namespace ''' The core of ``pyfftw`` consists of the :class:`FFTW` class, :ref:`wisdom functions ` and a couple of :ref:`utility functions ` for dealing with aligned arrays. This module represents the full interface to the underlying `FFTW library `_. However, users may find it easier to use the helper routines provided in :mod:`pyfftw.builders`. Default values used by the helper routines can be controlled as via :ref:`configuration variables `. ''' import os from .pyfftw import ( FFTW, export_wisdom, import_wisdom, forget_wisdom, simd_alignment, n_byte_align_empty, n_byte_align, is_n_byte_aligned, byte_align, is_byte_aligned, empty_aligned, ones_aligned, zeros_aligned, next_fast_len, _supported_types, _supported_nptypes_complex, _supported_nptypes_real, _all_types_human_readable, _all_types_np, _threading_type ) from . import config from . import builders from . import interfaces # clean up the namespace del builders.builders from ._version import get_versions __version__ = get_versions()['version'] del get_versions pyFFTW-0.13.1/pyfftw/_version.py000066400000000000000000000441011435600752200164340ustar00rootroot00000000000000 # This file helps to compute a version number in source trees obtained from # git-archive tarball (such as those provided by githubs download-from-tag # feature). Distribution tarballs (built by setup.py sdist) and build # directories (produced by setup.py build) will contain a much shorter file # that just contains the computed version number. # This file is released into the public domain. Generated by # versioneer-0.18 (https://github.com/warner/python-versioneer) """Git implementation of _version.py.""" import errno import os import re import subprocess import sys def get_keywords(): """Get the keywords needed to look up the version information.""" # these strings will be replaced by git during git-archive. # setup.py/versioneer.py will grep for the variable names, so they must # each be defined on a line of their own. _version.py will just call # get_keywords(). git_refnames = " (tag: v0.13.1)" git_full = "965b82f70478e5a2586202243cd645bbb3ba15c9" git_date = "2023-01-06 12:08:50 +0000" keywords = {"refnames": git_refnames, "full": git_full, "date": git_date} return keywords class VersioneerConfig: """Container for Versioneer configuration parameters.""" def get_config(): """Create, populate and return the VersioneerConfig() object.""" # these strings are filled in when 'setup.py versioneer' creates # _version.py cfg = VersioneerConfig() cfg.VCS = "git" cfg.style = "pep440" cfg.tag_prefix = "v" cfg.parentdir_prefix = "pyfftw-" cfg.versionfile_source = "pyfftw/_version.py" cfg.verbose = False return cfg class NotThisMethod(Exception): """Exception raised if a method is not valid for the current scenario.""" LONG_VERSION_PY = {} HANDLERS = {} def register_vcs_handler(vcs, method): # decorator """Decorator to mark a method as the handler for a particular VCS.""" def decorate(f): """Store f in HANDLERS[vcs][method].""" if vcs not in HANDLERS: HANDLERS[vcs] = {} HANDLERS[vcs][method] = f return f return decorate def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False, env=None): """Call the given command(s).""" assert isinstance(commands, list) p = None for c in commands: try: dispcmd = str([c] + args) # remember shell=False, so use git.cmd on windows, not just git p = subprocess.Popen([c] + args, cwd=cwd, env=env, stdout=subprocess.PIPE, stderr=(subprocess.PIPE if hide_stderr else None)) break except EnvironmentError: e = sys.exc_info()[1] if e.errno == errno.ENOENT: continue if verbose: print("unable to run %s" % dispcmd) print(e) return None, None else: if verbose: print("unable to find command, tried %s" % (commands,)) return None, None stdout = p.communicate()[0].strip() if sys.version_info[0] >= 3: stdout = stdout.decode() if p.returncode != 0: if verbose: print("unable to run %s (error)" % dispcmd) print("stdout was %s" % stdout) return None, p.returncode return stdout, p.returncode def versions_from_parentdir(parentdir_prefix, root, verbose): """Try to determine the version from the parent directory name. Source tarballs conventionally unpack into a directory that includes both the project name and a version string. We will also support searching up two directory levels for an appropriately named parent directory """ rootdirs = [] for i in range(3): dirname = os.path.basename(root) if dirname.startswith(parentdir_prefix): return {"version": dirname[len(parentdir_prefix):], "full-revisionid": None, "dirty": False, "error": None, "date": None} else: rootdirs.append(root) root = os.path.dirname(root) # up a level if verbose: print("Tried directories %s but none started with prefix %s" % (str(rootdirs), parentdir_prefix)) raise NotThisMethod("rootdir doesn't start with parentdir_prefix") @register_vcs_handler("git", "get_keywords") def git_get_keywords(versionfile_abs): """Extract version information from the given file.""" # the code embedded in _version.py can just fetch the value of these # keywords. When used from setup.py, we don't want to import _version.py, # so we do it with a regexp instead. This function is not used from # _version.py. keywords = {} try: f = open(versionfile_abs, "r") for line in f.readlines(): if line.strip().startswith("git_refnames ="): mo = re.search(r'=\s*"(.*)"', line) if mo: keywords["refnames"] = mo.group(1) if line.strip().startswith("git_full ="): mo = re.search(r'=\s*"(.*)"', line) if mo: keywords["full"] = mo.group(1) if line.strip().startswith("git_date ="): mo = re.search(r'=\s*"(.*)"', line) if mo: keywords["date"] = mo.group(1) f.close() except EnvironmentError: pass return keywords @register_vcs_handler("git", "keywords") def git_versions_from_keywords(keywords, tag_prefix, verbose): """Get version information from git keywords.""" if not keywords: raise NotThisMethod("no keywords at all, weird") date = keywords.get("date") if date is not None: # git-2.2.0 added "%cI", which expands to an ISO-8601 -compliant # datestamp. However we prefer "%ci" (which expands to an "ISO-8601 # -like" string, which we must then edit to make compliant), because # it's been around since git-1.5.3, and it's too difficult to # discover which version we're using, or to work around using an # older one. date = date.strip().replace(" ", "T", 1).replace(" ", "", 1) refnames = keywords["refnames"].strip() if refnames.startswith("$Format"): if verbose: print("keywords are unexpanded, not using") raise NotThisMethod("unexpanded keywords, not a git-archive tarball") refs = set([r.strip() for r in refnames.strip("()").split(",")]) # starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of # just "foo-1.0". If we see a "tag: " prefix, prefer those. TAG = "tag: " tags = set([r[len(TAG):] for r in refs if r.startswith(TAG)]) if not tags: # Either we're using git < 1.8.3, or there really are no tags. We use # a heuristic: assume all version tags have a digit. The old git %d # expansion behaves like git log --decorate=short and strips out the # refs/heads/ and refs/tags/ prefixes that would let us distinguish # between branches and tags. By ignoring refnames without digits, we # filter out many common branch names like "release" and # "stabilization", as well as "HEAD" and "master". tags = set([r for r in refs if re.search(r'\d', r)]) if verbose: print("discarding '%s', no digits" % ",".join(refs - tags)) if verbose: print("likely tags: %s" % ",".join(sorted(tags))) for ref in sorted(tags): # sorting will prefer e.g. "2.0" over "2.0rc1" if ref.startswith(tag_prefix): r = ref[len(tag_prefix):] if verbose: print("picking %s" % r) return {"version": r, "full-revisionid": keywords["full"].strip(), "dirty": False, "error": None, "date": date} # no suitable tags, so version is "0+unknown", but full hex is still there if verbose: print("no suitable tags, using unknown + full revision id") return {"version": "0+unknown", "full-revisionid": keywords["full"].strip(), "dirty": False, "error": "no suitable tags", "date": None} @register_vcs_handler("git", "pieces_from_vcs") def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): """Get version from 'git describe' in the root of the source tree. This only gets called if the git-archive 'subst' keywords were *not* expanded, and _version.py hasn't already been rewritten with a short version string, meaning we're inside a checked out source tree. """ GITS = ["git"] if sys.platform == "win32": GITS = ["git.cmd", "git.exe"] out, rc = run_command(GITS, ["rev-parse", "--git-dir"], cwd=root, hide_stderr=True) if rc != 0: if verbose: print("Directory %s not under git control" % root) raise NotThisMethod("'git rev-parse --git-dir' returned error") # if there is a tag matching tag_prefix, this yields TAG-NUM-gHEX[-dirty] # if there isn't one, this yields HEX[-dirty] (no NUM) describe_out, rc = run_command(GITS, ["describe", "--tags", "--dirty", "--always", "--long", "--match", "%s*" % tag_prefix], cwd=root) # --long was added in git-1.5.5 if describe_out is None: raise NotThisMethod("'git describe' failed") describe_out = describe_out.strip() full_out, rc = run_command(GITS, ["rev-parse", "HEAD"], cwd=root) if full_out is None: raise NotThisMethod("'git rev-parse' failed") full_out = full_out.strip() pieces = {} pieces["long"] = full_out pieces["short"] = full_out[:7] # maybe improved later pieces["error"] = None # parse describe_out. It will be like TAG-NUM-gHEX[-dirty] or HEX[-dirty] # TAG might have hyphens. git_describe = describe_out # look for -dirty suffix dirty = git_describe.endswith("-dirty") pieces["dirty"] = dirty if dirty: git_describe = git_describe[:git_describe.rindex("-dirty")] # now we have TAG-NUM-gHEX or HEX if "-" in git_describe: # TAG-NUM-gHEX mo = re.search(r'^(.+)-(\d+)-g([0-9a-f]+)$', git_describe) if not mo: # unparseable. Maybe git-describe is misbehaving? pieces["error"] = ("unable to parse git-describe output: '%s'" % describe_out) return pieces # tag full_tag = mo.group(1) if not full_tag.startswith(tag_prefix): if verbose: fmt = "tag '%s' doesn't start with prefix '%s'" print(fmt % (full_tag, tag_prefix)) pieces["error"] = ("tag '%s' doesn't start with prefix '%s'" % (full_tag, tag_prefix)) return pieces pieces["closest-tag"] = full_tag[len(tag_prefix):] # distance: number of commits since tag pieces["distance"] = int(mo.group(2)) # commit: short hex revision ID pieces["short"] = mo.group(3) else: # HEX: no tags pieces["closest-tag"] = None count_out, rc = run_command(GITS, ["rev-list", "HEAD", "--count"], cwd=root) pieces["distance"] = int(count_out) # total number of commits # commit date: see ISO-8601 comment in git_versions_from_keywords() date = run_command(GITS, ["show", "-s", "--format=%ci", "HEAD"], cwd=root)[0].strip() pieces["date"] = date.strip().replace(" ", "T", 1).replace(" ", "", 1) return pieces def plus_or_dot(pieces): """Return a + if we don't already have one, else return a .""" if "+" in pieces.get("closest-tag", ""): return "." return "+" def render_pep440(pieces): """Build up version string, with post-release "local version identifier". Our goal: TAG[+DISTANCE.gHEX[.dirty]] . Note that if you get a tagged build and then dirty it, you'll get TAG+0.gHEX.dirty Exceptions: 1: no tags. git_describe was just HEX. 0+untagged.DISTANCE.gHEX[.dirty] """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"] or pieces["dirty"]: rendered += plus_or_dot(pieces) rendered += "%d.g%s" % (pieces["distance"], pieces["short"]) if pieces["dirty"]: rendered += ".dirty" else: # exception #1 rendered = "0+untagged.%d.g%s" % (pieces["distance"], pieces["short"]) if pieces["dirty"]: rendered += ".dirty" return rendered def render_pep440_pre(pieces): """TAG[.post.devDISTANCE] -- No -dirty. Exceptions: 1: no tags. 0.post.devDISTANCE """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"]: rendered += ".post.dev%d" % pieces["distance"] else: # exception #1 rendered = "0.post.dev%d" % pieces["distance"] return rendered def render_pep440_post(pieces): """TAG[.postDISTANCE[.dev0]+gHEX] . The ".dev0" means dirty. Note that .dev0 sorts backwards (a dirty tree will appear "older" than the corresponding clean one), but you shouldn't be releasing software with -dirty anyways. Exceptions: 1: no tags. 0.postDISTANCE[.dev0] """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"] or pieces["dirty"]: rendered += ".post%d" % pieces["distance"] if pieces["dirty"]: rendered += ".dev0" rendered += plus_or_dot(pieces) rendered += "g%s" % pieces["short"] else: # exception #1 rendered = "0.post%d" % pieces["distance"] if pieces["dirty"]: rendered += ".dev0" rendered += "+g%s" % pieces["short"] return rendered def render_pep440_old(pieces): """TAG[.postDISTANCE[.dev0]] . The ".dev0" means dirty. Eexceptions: 1: no tags. 0.postDISTANCE[.dev0] """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"] or pieces["dirty"]: rendered += ".post%d" % pieces["distance"] if pieces["dirty"]: rendered += ".dev0" else: # exception #1 rendered = "0.post%d" % pieces["distance"] if pieces["dirty"]: rendered += ".dev0" return rendered def render_git_describe(pieces): """TAG[-DISTANCE-gHEX][-dirty]. Like 'git describe --tags --dirty --always'. Exceptions: 1: no tags. HEX[-dirty] (note: no 'g' prefix) """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"]: rendered += "-%d-g%s" % (pieces["distance"], pieces["short"]) else: # exception #1 rendered = pieces["short"] if pieces["dirty"]: rendered += "-dirty" return rendered def render_git_describe_long(pieces): """TAG-DISTANCE-gHEX[-dirty]. Like 'git describe --tags --dirty --always -long'. The distance/hash is unconditional. Exceptions: 1: no tags. HEX[-dirty] (note: no 'g' prefix) """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] rendered += "-%d-g%s" % (pieces["distance"], pieces["short"]) else: # exception #1 rendered = pieces["short"] if pieces["dirty"]: rendered += "-dirty" return rendered def render(pieces, style): """Render the given version pieces into the requested style.""" if pieces["error"]: return {"version": "unknown", "full-revisionid": pieces.get("long"), "dirty": None, "error": pieces["error"], "date": None} if not style or style == "default": style = "pep440" # the default if style == "pep440": rendered = render_pep440(pieces) elif style == "pep440-pre": rendered = render_pep440_pre(pieces) elif style == "pep440-post": rendered = render_pep440_post(pieces) elif style == "pep440-old": rendered = render_pep440_old(pieces) elif style == "git-describe": rendered = render_git_describe(pieces) elif style == "git-describe-long": rendered = render_git_describe_long(pieces) else: raise ValueError("unknown style '%s'" % style) return {"version": rendered, "full-revisionid": pieces["long"], "dirty": pieces["dirty"], "error": None, "date": pieces.get("date")} def get_versions(): """Get version information or return default if unable to do so.""" # I am in _version.py, which lives at ROOT/VERSIONFILE_SOURCE. If we have # __file__, we can work backwards from there to the root. Some # py2exe/bbfreeze/non-CPython implementations don't do __file__, in which # case we can only use expanded keywords. cfg = get_config() verbose = cfg.verbose try: return git_versions_from_keywords(get_keywords(), cfg.tag_prefix, verbose) except NotThisMethod: pass try: root = os.path.realpath(__file__) # versionfile_source is the relative path from the top of the source # tree (where the .git directory might live) to this file. Invert # this to find the root from __file__. for i in cfg.versionfile_source.split('/'): root = os.path.dirname(root) except NameError: return {"version": "0+unknown", "full-revisionid": None, "dirty": None, "error": "unable to find root of source tree", "date": None} try: pieces = git_pieces_from_vcs(cfg.tag_prefix, root, verbose) return render(pieces, cfg.style) except NotThisMethod: pass try: if cfg.parentdir_prefix: return versions_from_parentdir(cfg.parentdir_prefix, root, verbose) except NotThisMethod: pass return {"version": "0+unknown", "full-revisionid": None, "dirty": None, "error": "unable to compute version", "date": None} pyFFTW-0.13.1/pyfftw/builders/000077500000000000000000000000001435600752200160475ustar00rootroot00000000000000pyFFTW-0.13.1/pyfftw/builders/__init__.py000066400000000000000000000001731435600752200201610ustar00rootroot00000000000000#!/usr/bin/env python from .builders import * from . import _utils __doc__ = builders.__doc__ __all__ = builders.__all__ pyFFTW-0.13.1/pyfftw/builders/_utils.py000066400000000000000000000423131435600752200177230ustar00rootroot00000000000000#!/usr/bin/env python # # Copyright 2014 Knowledge Economy Developments Ltd # Copyright 2014 David Wells # # Henry Gomersall # heng@kedevelopments.co.uk # # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, this # list of conditions and the following disclaimer. # # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions and the following disclaimer in the documentation # and/or other materials provided with the distribution. # # * Neither the name of the copyright holder nor the names of its contributors # may be used to endorse or promote products derived from this software without # specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. # ''' A set of utility functions for use with the builders. Users should not need to use the functions directly, but they are included here for completeness and to aid with understanding of what is happening behind the scenes. Certainly, users may encounter instances of :class:`~pyfftw.builders._utils._FFTWWrapper`. Everything documented in this module is *not* part of the public API and may change in future versions. ''' import multiprocessing import pyfftw import numpy import warnings from .. import _threading_type from .. import config __all__ = ['_FFTWWrapper', '_rc_dtype_pairs', '_default_dtype', '_Xfftn', '_setup_input_slicers', '_compute_array_shapes', '_precook_1d_args', '_cook_nd_args'] _valid_efforts = ('FFTW_ESTIMATE', 'FFTW_MEASURE', 'FFTW_PATIENT', 'FFTW_EXHAUSTIVE') _real_to_real_dtypes = [numpy.dtype('float32'), numpy.dtype('float64'), numpy.dtype('longdouble')] # Looking up a real dtype in here returns the complex complement of the same # precision, and vice versa. # It is necessary to use .char as the keys due to MSVC mapping long # double to double and the way that numpy handles this. _rc_dtype_pairs = {} _default_dtype = None # Double precision is the default default precision. Prefer casting to higher precision if # possible. If missing, long double is mapped to double and so we lose less # precision than by converting to single. if '64' in pyfftw._supported_types: _default_dtype = numpy.dtype('float64') _rc_dtype_pairs.update({ numpy.dtype('float64').char: numpy.dtype('complex128'), numpy.dtype('complex128').char: numpy.dtype('float64')}) if 'ld' in pyfftw._supported_types: if _default_dtype is None: _default_dtype = numpy.dtype('longdouble') _rc_dtype_pairs.update({ numpy.dtype('longdouble').char: numpy.dtype('clongdouble'), numpy.dtype('clongdouble').char: numpy.dtype('longdouble')}) if '32' in pyfftw._supported_types: if _default_dtype is None: _default_dtype = numpy.dtype('float32') _rc_dtype_pairs.update({ numpy.dtype('float32').char: numpy.dtype('complex64'), numpy.dtype('complex64').char: numpy.dtype('float32')}) if _default_dtype is None: raise NotImplementedError("No default precision available") def _default_effort(effort): if effort is None: return config.PLANNER_EFFORT else: return effort def _default_threads(threads): if threads is None: if config.NUM_THREADS <= 0: return multiprocessing.cpu_count() return config.NUM_THREADS else: if threads > 1 and _threading_type is None: raise ValueError("threads > 1 requested, but pyFFTW was not built " "with multithreaded FFTW.") elif threads <= 0: return multiprocessing.cpu_count() return threads def _norm_args(norm): """ Returns the proper normalization parameter values. Possible `norm` values are "backward" (alias of None), "ortho", "forward". This function is used by both the builders and the interfaces. """ if norm == "ortho": ortho = True normalise_idft = False elif norm is None or norm == "backward": ortho = False normalise_idft = True elif norm == "forward": ortho = False normalise_idft = False else: raise ValueError(f'Invalid norm value {norm}; should be "ortho", ' '"backward" or "forward".') return dict(normalise_idft=normalise_idft, ortho=ortho) def _Xfftn(a, s, axes, overwrite_input, planner_effort, threads, auto_align_input, auto_contiguous, avoid_copy, inverse, real, normalise_idft=True, ortho=False, real_direction_flag=None): '''Generic transform interface for all the transforms. No defaults exist. The transform must be specified exactly. The argument ``real_direction_flag`` is a slight exception to this rule: for backwards compatibility this function defaults to standard Fourier transforms (and not the specialized real to real variants). If this flag is set to one of the standard real transform types (e.g., 'FFTW_RODFT00') then the arguments ``inverse`` and ``real`` are ignored. ''' a_orig = a invreal = inverse and real if real_direction_flag is not None: direction = real_direction_flag real_to_real = True elif inverse: direction = 'FFTW_BACKWARD' real_to_real = False else: direction = 'FFTW_FORWARD' real_to_real = False if planner_effort not in _valid_efforts: raise ValueError('Invalid planner effort: ', planner_effort) s, axes = _cook_nd_args(a, s, axes, invreal) input_shape, output_shape = _compute_array_shapes( a, s, axes, inverse, real) a_is_complex = numpy.iscomplexobj(a) # Make the input dtype correct by transforming to an available type if real_to_real: if a.dtype not in _real_to_real_dtypes: a = numpy.asarray(a, dtype=_default_dtype) else: if a.dtype.char not in _rc_dtype_pairs: dtype = _default_dtype if a.dtype == numpy.dtype('float16') and '32' in pyfftw._supported_types: # convert half-precision to single precision, if available dtype = numpy.dtype('float32') # warn when losing precision but not when using a higher precision if dtype.itemsize < a.dtype.itemsize: warnings.warn("Narrowing conversion from %s to %s precision" % (a.dtype, dtype)) if not real or inverse: # It's going to be complex dtype = numpy.dtype(_rc_dtype_pairs[dtype.char]) # finally convert the input array a = numpy.asarray(a, dtype=dtype) elif not (real and not inverse) and not a_is_complex: # We need to make it a complex dtype a = numpy.asarray(a, dtype=_rc_dtype_pairs[a.dtype.char]) elif (real and not inverse) and a_is_complex: # It should be real a = numpy.asarray(a, dtype=_rc_dtype_pairs[a.dtype.char]) # Make the output dtype correct if not real: # 'real' implies c2r or r2c; hence 'not real' means r2r or c2c. output_dtype = a.dtype else: output_dtype = _rc_dtype_pairs[a.dtype.char] if not avoid_copy: a_copy = a.copy() output_array = pyfftw.empty_aligned(output_shape, output_dtype) flags = [planner_effort] if not auto_align_input: flags.append('FFTW_UNALIGNED') if overwrite_input: flags.append('FFTW_DESTROY_INPUT') if not a.shape == input_shape: if avoid_copy: raise ValueError('Cannot avoid copy: ' 'The transform shape is not the same as the array size. ' '(from avoid_copy flag)') # This means we need to use an _FFTWWrapper object # and so need to create slicers. update_input_array_slicer, FFTW_array_slicer = ( _setup_input_slicers(a.shape, input_shape)) # Also, the input array will be a different shape to the shape of # `a`, so we need to create a new array. input_array = pyfftw.empty_aligned(input_shape, a.dtype) FFTW_object = _FFTWWrapper(input_array, output_array, axes, direction, flags, threads, input_array_slicer=update_input_array_slicer, FFTW_array_slicer=FFTW_array_slicer, normalise_idft=normalise_idft, ortho=ortho) # We copy the data back into the internal FFTW object array internal_array = FFTW_object.input_array internal_array[:] = 0 internal_array[FFTW_array_slicer] = ( a_copy[update_input_array_slicer]) else: # Otherwise we can use `a` as-is input_array = a if auto_contiguous: # We only need to create a new array if it's not already # contiguous if not (a.flags['C_CONTIGUOUS'] or a.flags['F_CONTIGUOUS']): if avoid_copy: raise ValueError('Cannot avoid copy: ' 'The input array is not contiguous and ' 'auto_contiguous is set. (from avoid_copy flag)') input_array = pyfftw.empty_aligned(a.shape, a.dtype) if (auto_align_input and not pyfftw.is_byte_aligned(input_array)): if avoid_copy: raise ValueError('Cannot avoid copy: ' 'The input array is not aligned and ' 'auto_align is set. (from avoid_copy flag)') input_array = pyfftw.byte_align(input_array) FFTW_object = pyfftw.FFTW(input_array, output_array, axes, direction, flags, threads, normalise_idft=normalise_idft, ortho=ortho) if not avoid_copy: # Copy the data back into the (likely) destroyed array FFTW_object.input_array[:] = a_copy return FFTW_object class _FFTWWrapper(pyfftw.FFTW): ''' A class that wraps :class:`pyfftw.FFTW`, providing a slicer on the input stage during calls to :meth:`~pyfftw.builders._utils._FFTWWrapper.__call__`. ''' def __init__(self, input_array, output_array, axes=[-1], direction='FFTW_FORWARD', flags=['FFTW_MEASURE'], threads=1, input_array_slicer=None, FFTW_array_slicer=None, normalise_idft=True, ortho=False): '''The arguments are as per :class:`pyfftw.FFTW`, but with the addition of 2 keyword arguments: ``input_array_slicer`` and ``FFTW_array_slicer``. These arguments represent 2 slicers: ``input_array_slicer`` slices the input array that is passed in during a call to instances of this class, and ``FFTW_array_slicer`` slices the internal array. The arrays that are returned from both of these slicing operations should be the same size. The data is then copied from the sliced input array into the sliced internal array. ''' self._input_array_slicer = input_array_slicer self._FFTW_array_slicer = FFTW_array_slicer self._normalise_idft = normalise_idft self._ortho = ortho if 'FFTW_DESTROY_INPUT' in flags: self._input_destroyed = True else: self._input_destroyed = False pyfftw.FFTW.__init__(self, input_array, output_array, axes, direction, flags, threads) def __call__(self, input_array=None, output_array=None, normalise_idft=None, ortho=None): '''Wrap :meth:`pyfftw.FFTW.__call__` by firstly slicing the passed-in input array and then copying it into a sliced version of the internal array. These slicers are set at instantiation. When input array is not ``None``, this method always results in a copy. Consequently, the alignment and dtype are maintained in the internal array. ``output_array`` and ``normalise_idft`` are passed through to :meth:`pyfftw.FFTW.__call__` untouched. ''' if input_array is not None: # Do the update here (which is a copy, so it's alignment # safe etc). internal_input_array = self.input_array input_array = numpy.asanyarray(input_array) if self._input_destroyed: internal_input_array[:] = 0 sliced_internal = internal_input_array[self._FFTW_array_slicer] sliced_input = input_array[self._input_array_slicer] if sliced_internal.shape != sliced_input.shape: raise ValueError('Invalid input shape: ' 'The new input array should be the same shape ' 'as the input array used to instantiate the ' 'object.') sliced_internal[:] = sliced_input if normalise_idft is None: normalise_idft = self._normalise_idft if ortho is None: ortho = self._ortho output = super(_FFTWWrapper, self).__call__(input_array=None, output_array=output_array, normalise_idft=normalise_idft, ortho=ortho) return output def _setup_input_slicers(a_shape, input_shape): ''' This function returns two slicers that are to be used to copy the data from the input array to the FFTW object internal array, which can then be passed to _FFTWWrapper: ``(update_input_array_slicer, FFTW_array_slicer)`` On calls to :class:`~pyfftw.builders._utils._FFTWWrapper` objects, the input array is copied in as: ``FFTW_array[FFTW_array_slicer] = input_array[update_input_array_slicer]`` ''' # default the slicers to include everything update_input_array_slicer = ( [slice(None)]*len(a_shape)) FFTW_array_slicer = [slice(None)]*len(a_shape) # iterate over each dimension and modify the slicer and FFTW dimension for axis in range(len(a_shape)): if a_shape[axis] > input_shape[axis]: update_input_array_slicer[axis] = ( slice(0, input_shape[axis])) elif a_shape[axis] < input_shape[axis]: FFTW_array_slicer[axis] = ( slice(0, a_shape[axis])) update_input_array_slicer[axis] = ( slice(0, a_shape[axis])) else: # If neither of these, we use the whole dimension. update_input_array_slicer[axis] = ( slice(0, a_shape[axis])) return tuple(update_input_array_slicer), tuple(FFTW_array_slicer) def _compute_array_shapes(a, s, axes, inverse, real): '''Given a passed in array ``a``, and the rest of the arguments (that have been fleshed out with :func:`~pyfftw.builders._utils._cook_nd_args`), compute the shape the input and output arrays need to be in order to satisfy all the requirements for the transform. The input shape *may* be different to the shape of a. returns: ``(input_shape, output_shape)`` ''' # Start with the shape of a orig_domain_shape = list(a.shape) fft_domain_shape = list(a.shape) try: for n, axis in enumerate(axes): orig_domain_shape[axis] = s[n] fft_domain_shape[axis] = s[n] if real: fft_domain_shape[axes[-1]] = s[-1]//2 + 1 except IndexError: raise IndexError('Invalid axes: ' 'At least one of the passed axes is invalid.') if inverse: input_shape = fft_domain_shape output_shape = orig_domain_shape else: input_shape = orig_domain_shape output_shape = fft_domain_shape return tuple(input_shape), tuple(output_shape) def _precook_1d_args(a, n, axis): '''Turn ``*(n, axis)`` into ``(s, axes)`` ''' if n is not None: s = [int(n)] else: s = None # Force an error with an invalid axis a.shape[axis] return s, (axis,) def _cook_nd_args(a, s=None, axes=None, invreal=False): '''Similar to :func:`numpy.fft.fftpack._cook_nd_args`. ''' if axes is None: if s is None: len_s = len(a.shape) else: len_s = len(s) axes = list(range(-len_s, 0)) if s is None: s = list(numpy.take(a.shape, axes)) if invreal: s[-1] = (a.shape[axes[-1]] - 1) * 2 if len(s) != len(axes): raise ValueError('Shape error: ' 'Shape and axes have different lengths.') if len(s) > len(a.shape): raise ValueError('Shape error: ' 'The length of s or axes cannot exceed the dimensionality ' 'of the input array, a.') return tuple(s), tuple(axes) pyFFTW-0.13.1/pyfftw/builders/builders.py000066400000000000000000000571741435600752200202500ustar00rootroot00000000000000#!/usr/bin/env python # # Copyright 2014 Knowledge Economy Developments Ltd # Copyright 2014 David Wells # # Henry Gomersall # heng@kedevelopments.co.uk # # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, this # list of conditions and the following disclaimer. # # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions and the following disclaimer in the documentation # and/or other materials provided with the distribution. # # * Neither the name of the copyright holder nor the names of its contributors # may be used to endorse or promote products derived from this software without # specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. # ''' Overview """""""" This module contains a set of functions that return :class:`pyfftw.FFTW` objects. The interface to create these objects is mostly the same as :mod:`numpy.fft`, only instead of the call returning the result of the FFT, a :class:`pyfftw.FFTW` object is returned that performs that FFT operation when it is called. Users should be familiar with :mod:`!numpy.fft` before reading on. In the case where the shape argument, ``s`` (or ``n`` in the 1-dimensional case), dictates that the passed-in input array be copied into a different processing array, the returned object is an instance of a child class of :class:`pyfftw.FFTW`, :class:`~pyfftw.builders._utils._FFTWWrapper`, which wraps the call method in order to correctly perform that copying. That is, subsequent calls to the object (i.e. through :meth:`~pyfftw.builders._utils._FFTWWrapper.__call__`) should occur with an input array that can be sliced to the same size as the expected internal array. Note that a side effect of this is that subsequent calls to the object can be made with an array that is *bigger* than the original (but not smaller). Only the call method is wrapped; :meth:`~pyfftw.FFTW.update_arrays` still expects an array with the correct size, alignment, dtype etc for the :class:`pyfftw.FFTW` object. When the internal input array is bigger along any axis than the input array that is passed in (due to ``s`` dictating a larger size), then the extra entries are padded with zeros. This is a one time action. If the internal input array is then extracted using :attr:`pyfftw.FFTW.input_array`, it is possible to persistently fill the padding space with whatever the user desires, so subsequent calls with a new input only overwrite the values that aren't padding (even if the array that is used for the call is bigger than the original - see the point above about bigger arrays being sliced to fit). The precision of the FFT operation is acquired from the input array. If an array is passed in that is not of float type, or is of an unknown float type, an attempt is made to convert the array to a double precision array. This results in a copy being made. If an array of the incorrect complexity is passed in (e.g. a complex array is passed to a real transform routine, or vice-versa), then an attempt is made to convert the array to an array of the correct complexity. This results in a copy being made. Although the array that is internal to the :class:`pyfftw.FFTW` object will be correctly loaded with the values within the input array, it is not necessarily the case that the internal array *is* the input array. The actual internal input array can always be retrieved with :attr:`pyfftw.FFTW.input_array`. The behavior of the ``norm`` parameter in all builder routines matches that of the corresponding ``numpy.fft`` functions. In particular, if ``norm == "backward"`` (alias of ``None``) then the forward/direct FFT is unscaled and the backward/inverse is scaled by 1/N, where N is the length of the input array (for multidimensional FFTs it's the product of the lengths of each dimension). If ``norm == "ortho"`` then both the forward and the backward FFTs are scaled by 1/sqrt(N). Finally, if ``norm == "forward"`` then the forward FFT is scaled by 1/N and the backward is unscaled (exact opposite of the ``"backward"`` case). The default case is ``norm == "backward"``. In all three cases, using the same ``norm`` value for both the forward and the backward FFT ensures *roundtrip equality*, i.e. that applying the forwad and then the backward FFT to an input array returns the original array (up to numerical accuracy). **Example:** .. doctest:: >>> import pyfftw >>> a = pyfftw.empty_aligned(4, dtype='complex128') >>> fft = pyfftw.builders.fft(a) >>> a[:] = [1, 2, 3, 4] >>> fft() # returns the output array([ 10.+0.j, -2.+2.j, -2.+0.j, -2.-2.j]) More examples can be found in the :doc:`tutorial `. Supported Functions and Caveats """"""""""""""""""""""""""""""" The following functions are supported. They can be used with the same calling signature as their respective functions in :mod:`numpy.fft` or (in the case of real-to-real transforms) :mod:`scipy.fftpack`. **Standard FFTs** * :func:`~pyfftw.builders.fft` * :func:`~pyfftw.builders.ifft` * :func:`~pyfftw.builders.fft2` * :func:`~pyfftw.builders.ifft2` * :func:`~pyfftw.builders.fftn` * :func:`~pyfftw.builders.ifftn` **Real FFTs** * :func:`~pyfftw.builders.rfft` * :func:`~pyfftw.builders.irfft` * :func:`~pyfftw.builders.rfft2` * :func:`~pyfftw.builders.irfft2` * :func:`~pyfftw.builders.rfftn` * :func:`~pyfftw.builders.irfftn` **DCTs and DSTs** * :func:`~pyfftw.builders.dct` * :func:`~pyfftw.builders.dst` The first caveat is that the dtype of the input array must match the transform. For example, for ``fft`` and ``ifft``, the dtype must be complex, for ``rfft`` it must be real, and so on. The other point to note from this is that the precision of the transform matches the precision of the input array. So, if a single precision input array is passed in, then a single precision transform will be used. The second caveat is that repeated axes are handled differently; with the returned :class:`pyfftw.FFTW` object, axes that are repeated in the axes argument are considered only once, as compared to :mod:`numpy.fft` in which repeated axes results in the DFT being taken along that axes as many times as the axis occurs (this is down to the underlying library). Note that unless the ``auto_align_input`` argument to the function is set to ``True``, the ``'FFTW_UNALIGNED'`` :ref:`flag ` is set in the returned :class:`pyfftw.FFTW` object. This disables some of the FFTW optimisations that rely on aligned arrays. Also worth noting is that the ``auto_align_input`` flag only results in a copy when calling the resultant :class:`pyfftw.FFTW` object if the input array is not already aligned correctly. .. _builders_args: Additional Arguments """""""""""""""""""" In addition to the arguments that are present with their complementary functions in :mod:`numpy.fft`, each of these functions also offers the following additional keyword arguments: * ``overwrite_input``: Whether or not the input array can be overwritten during the transform. This sometimes results in a faster algorithm being made available. It causes the ``'FFTW_DESTROY_INPUT'`` flag to be passed to the :class:`pyfftw.FFTW` object. This flag is not offered for the multi-dimensional inverse real transforms, as FFTW is unable to not overwrite the input in that case. * ``planner_effort``: A string dictating how much effort is spent in planning the FFTW routines. This is passed to the creation of the :class:`pyfftw.FFTW` object as an entry in the flags list. They correspond to flags passed to the :class:`pyfftw.FFTW` object. The valid strings, in order of their increasing impact on the time to compute are: ``'FFTW_ESTIMATE'``, ``config.PLANNER_EFFORT`` (default), ``'FFTW_PATIENT'`` and ``'FFTW_EXHAUSTIVE'``. The `Wisdom `_ that FFTW has accumulated or has loaded (through :func:`pyfftw.import_wisdom`) is used during the creation of :class:`pyfftw.FFTW` objects. * ``threads``: The number of threads used to perform the FFT. * ``auto_align_input``: Correctly byte align the input array for optimal usage of vector instructions. This can lead to a substantial speedup. Setting this argument to ``True`` makes sure that the input array is correctly aligned. It is possible to correctly byte align the array prior to calling this function (using, for example, :func:`pyfftw.byte_align`). If and only if a realignment is necessary is a new array created. If a new array *is* created, it is up to the calling code to acquire that new input array using :attr:`pyfftw.FFTW.input_array`. The resultant :class:`pyfftw.FFTW` object that is created will be designed to operate on arrays that are aligned. If the object is called with an unaligned array, this would result in a copy. Despite this, it may still be faster to set the ``auto_align_input`` flag and incur a copy with unaligned arrays than to set up an object that uses aligned arrays. It's worth noting that just being aligned may not be sufficient to create the fastest possible transform. For example, if the array is not contiguous (i.e. certain axes have gaps in memory between slices), it may be faster to plan a transform for a contiguous array, and then rely on the array being copied in before the transform (which :class:`pyfftw.FFTW` will handle for you). The ``auto_contiguous`` argument controls whether this function also takes care of making sure the array is contiguous or not. * ``auto_contiguous``: Make sure the input array is contiguous in memory before performing the transform on it. If the array is not contiguous, it is copied into an interim array. This is because it is often faster to copy the data before the transform and then transform a contiguous array than it is to try to take the transform of a non-contiguous array. This is particularly true in conjunction with the ``auto_align_input`` argument which is used to make sure that the transform is taken of an aligned array. Like ``auto_align_input``, If a new array is created, it is up to the calling code to acquire that new input array using :attr:`pyfftw.FFTW.input_array`. * ``avoid_copy``: By default, these functions will always create a copy (and sometimes more than one) of the passed in input array. This is because the creation of the :class:`pyfftw.FFTW` object generally destroys the contents of the input array. Setting this argument to ``True`` will try not to create a copy of the input array, likely resulting in the input array being destroyed. If it is not possible to create the object without a copy being made, a ``ValueError`` is raised. Example situations that require a copy, and so cause the exception to be raised when this flag is set: * The shape of the FFT input as dictated by ``s`` is necessarily different from the shape of the passed-in array. * The dtypes are incompatible with the FFT routine. * The ``auto_contiguous`` or ``auto_align`` flags are True and the input array is not already contiguous or aligned. This argument is distinct from ``overwrite_input`` in that it only influences a copy during the creation of the object. It changes no flags in the :class:`pyfftw.FFTW` object. The exceptions raised by each of these functions are as per their equivalents in :mod:`numpy.fft`, or as documented above. ''' from ._utils import (_precook_1d_args, _Xfftn, _norm_args, _default_effort, _default_threads) __all__ = ['fft','ifft', 'fft2', 'ifft2', 'fftn', 'ifftn', 'rfft', 'irfft', 'rfft2', 'irfft2', 'rfftn', 'irfftn', 'dct', 'dst'] def fft(a, n=None, axis=-1, overwrite_input=False, planner_effort=None, threads=None, auto_align_input=True, auto_contiguous=True, avoid_copy=False, norm=None): '''Return a :class:`pyfftw.FFTW` object representing a 1D FFT. The first three arguments are as per :func:`numpy.fft.fft`; the rest of the arguments are documented :ref:`in the module docs `. ''' inverse = False real = False s, axes = _precook_1d_args(a, n, axis) planner_effort = _default_effort(planner_effort) threads = _default_threads(threads) return _Xfftn(a, s, axes, overwrite_input, planner_effort, threads, auto_align_input, auto_contiguous, avoid_copy, inverse, real, **_norm_args(norm)) def ifft(a, n=None, axis=-1, overwrite_input=False, planner_effort=None, threads=None, auto_align_input=True, auto_contiguous=True, avoid_copy=False, norm=None): '''Return a :class:`pyfftw.FFTW` object representing a 1D inverse FFT. The first three arguments are as per :func:`numpy.fft.ifft`; the rest of the arguments are documented :ref:`in the module docs `. ''' inverse = True real = False s, axes = _precook_1d_args(a, n, axis) planner_effort = _default_effort(planner_effort) threads = _default_threads(threads) return _Xfftn(a, s, axes, overwrite_input, planner_effort, threads, auto_align_input, auto_contiguous, avoid_copy, inverse, real, **_norm_args(norm)) def fft2(a, s=None, axes=(-2,-1), overwrite_input=False, planner_effort=None, threads=None, auto_align_input=True, auto_contiguous=True, avoid_copy=False, norm=None): '''Return a :class:`pyfftw.FFTW` object representing a 2D FFT. The first three arguments are as per :func:`numpy.fft.fft2`; the rest of the arguments are documented :ref:`in the module docs `. ''' inverse = False real = False planner_effort = _default_effort(planner_effort) threads = _default_threads(threads) return _Xfftn(a, s, axes, overwrite_input, planner_effort, threads, auto_align_input, auto_contiguous, avoid_copy, inverse, real, **_norm_args(norm)) def ifft2(a, s=None, axes=(-2,-1), overwrite_input=False, planner_effort=None, threads=None, auto_align_input=True, auto_contiguous=True, avoid_copy=False, norm=None): '''Return a :class:`pyfftw.FFTW` object representing a 2D inverse FFT. The first three arguments are as per :func:`numpy.fft.ifft2`; the rest of the arguments are documented :ref:`in the module docs `. ''' inverse = True real = False planner_effort = _default_effort(planner_effort) threads = _default_threads(threads) return _Xfftn(a, s, axes, overwrite_input, planner_effort, threads, auto_align_input, auto_contiguous, avoid_copy, inverse, real, **_norm_args(norm)) def fftn(a, s=None, axes=None, overwrite_input=False, planner_effort=None, threads=None, auto_align_input=True, auto_contiguous=True, avoid_copy=False, norm=None): '''Return a :class:`pyfftw.FFTW` object representing a n-D FFT. The first three arguments are as per :func:`numpy.fft.fftn`; the rest of the arguments are documented :ref:`in the module docs `. ''' inverse = False real = False planner_effort = _default_effort(planner_effort) threads = _default_threads(threads) return _Xfftn(a, s, axes, overwrite_input, planner_effort, threads, auto_align_input, auto_contiguous, avoid_copy, inverse, real, **_norm_args(norm)) def ifftn(a, s=None, axes=None, overwrite_input=False, planner_effort=None, threads=None, auto_align_input=True, auto_contiguous=True, avoid_copy=False, norm=None): '''Return a :class:`pyfftw.FFTW` object representing an n-D inverse FFT. The first three arguments are as per :func:`numpy.fft.ifftn`; the rest of the arguments are documented :ref:`in the module docs `. ''' inverse = True real = False planner_effort = _default_effort(planner_effort) threads = _default_threads(threads) return _Xfftn(a, s, axes, overwrite_input, planner_effort, threads, auto_align_input, auto_contiguous, avoid_copy, inverse, real, **_norm_args(norm)) def rfft(a, n=None, axis=-1, overwrite_input=False, planner_effort=None, threads=None, auto_align_input=True, auto_contiguous=True, avoid_copy=False, norm=None): '''Return a :class:`pyfftw.FFTW` object representing a 1D real FFT. The first three arguments are as per :func:`numpy.fft.rfft`; the rest of the arguments are documented :ref:`in the module docs `. ''' inverse = False real = True s, axes = _precook_1d_args(a, n, axis) planner_effort = _default_effort(planner_effort) threads = _default_threads(threads) return _Xfftn(a, s, axes, overwrite_input, planner_effort, threads, auto_align_input, auto_contiguous, avoid_copy, inverse, real, **_norm_args(norm)) def irfft(a, n=None, axis=-1, overwrite_input=False, planner_effort=None, threads=None, auto_align_input=True, auto_contiguous=True, avoid_copy=False, norm=None): '''Return a :class:`pyfftw.FFTW` object representing a 1D real inverse FFT. The first three arguments are as per :func:`numpy.fft.irfft`; the rest of the arguments are documented :ref:`in the module docs `. ''' inverse = True real = True s, axes = _precook_1d_args(a, n, axis) planner_effort = _default_effort(planner_effort) threads = _default_threads(threads) return _Xfftn(a, s, axes, overwrite_input, planner_effort, threads, auto_align_input, auto_contiguous, avoid_copy, inverse, real, **_norm_args(norm)) def rfft2(a, s=None, axes=(-2,-1), overwrite_input=False, planner_effort=None, threads=None, auto_align_input=True, auto_contiguous=True, avoid_copy=False, norm=None): '''Return a :class:`pyfftw.FFTW` object representing a 2D real FFT. The first three arguments are as per :func:`numpy.fft.rfft2`; the rest of the arguments are documented :ref:`in the module docs `. ''' inverse = False real = True planner_effort = _default_effort(planner_effort) threads = _default_threads(threads) return _Xfftn(a, s, axes, overwrite_input, planner_effort, threads, auto_align_input, auto_contiguous, avoid_copy, inverse, real, **_norm_args(norm)) def irfft2(a, s=None, axes=(-2,-1), planner_effort=None, threads=None, auto_align_input=True, auto_contiguous=True, avoid_copy=False, norm=None): '''Return a :class:`pyfftw.FFTW` object representing a 2D real inverse FFT. The first three arguments are as per :func:`numpy.fft.irfft2`; the rest of the arguments are documented :ref:`in the module docs `. ''' inverse = True real = True overwrite_input = True planner_effort = _default_effort(planner_effort) threads = _default_threads(threads) return _Xfftn(a, s, axes, overwrite_input, planner_effort, threads, auto_align_input, auto_contiguous, avoid_copy, inverse, real, **_norm_args(norm)) def rfftn(a, s=None, axes=None, overwrite_input=False, planner_effort=None, threads=None, auto_align_input=True, auto_contiguous=True, avoid_copy=False, norm=None): '''Return a :class:`pyfftw.FFTW` object representing an n-D real FFT. The first three arguments are as per :func:`numpy.fft.rfftn`; the rest of the arguments are documented :ref:`in the module docs `. ''' inverse = False real = True planner_effort = _default_effort(planner_effort) threads = _default_threads(threads) return _Xfftn(a, s, axes, overwrite_input, planner_effort, threads, auto_align_input, auto_contiguous, avoid_copy, inverse, real, **_norm_args(norm)) def irfftn(a, s=None, axes=None, planner_effort=None, threads=None, auto_align_input=True, auto_contiguous=True, avoid_copy=False, norm=None): '''Return a :class:`pyfftw.FFTW` object representing an n-D real inverse FFT. The first three arguments are as per :func:`numpy.fft.rfftn`; the rest of the arguments are documented :ref:`in the module docs `. ''' inverse = True real = True overwrite_input = True planner_effort = _default_effort(planner_effort) threads = _default_threads(threads) return _Xfftn(a, s, axes, overwrite_input, planner_effort, threads, auto_align_input, auto_contiguous, avoid_copy, inverse, real, **_norm_args(norm)) def dct(a, n=None, axis=-1, overwrite_input=False, planner_effort=None, threads=None, auto_align_input=True, auto_contiguous=True, avoid_copy=False, type=2): '''Return a :class:`pyfftw.FFTW` object representing a 1D DCT. The first three arguments and 'type' are as per :func:`scipy.fftpack.dct`; the rest of the arguments are documented :ref:`in the module docs `. ''' dct_types = ['FFTW_REDFT00', 'FFTW_REDFT10', 'FFTW_REDFT01', 'FFTW_REDFT11', 1, 2, 3, 4] if type not in dct_types: raise ValueError("Unrecognised DCT type {}".format(type)) if n is not None and n != a.shape[axis]: raise NotImplementedError if isinstance(type, str): direction = type else: direction = dct_types[int(type) - 1] s, axes = _precook_1d_args(a, n, axis) inverse = False real = False planner_effort = _default_effort(planner_effort) threads = _default_threads(threads) return _Xfftn(a, s, axes, overwrite_input, planner_effort, threads, auto_align_input, auto_contiguous, avoid_copy, inverse, real, real_direction_flag=direction) def dst(a, n=None, axis=-1, overwrite_input=False, planner_effort=None, threads=None, auto_align_input=True, auto_contiguous=True, avoid_copy=False, type=2): '''Return a :class:`pyfftw.FFTW` object representing a 1D DST. The first three arguments and 'type' are as per :func:`scipy.fftpack.dst`; the rest of the arguments are documented :ref:`in the module docs `. ''' dst_types = ['FFTW_RODFT00', 'FFTW_RODFT10', 'FFTW_RODFT01', 'FFTW_RODFT11', 1, 2, 3, 4] if type not in dst_types: raise ValueError("Unrecognised DST type {}".format(type)) if n is not None and n != a.shape[axis]: raise NotImplementedError if isinstance(type, str): direction = type else: direction = dst_types[int(type) - 1] s, axes = _precook_1d_args(a, n, axis) inverse = False real = False planner_effort = _default_effort(planner_effort) threads = _default_threads(threads) return _Xfftn(a, s, axes, overwrite_input, planner_effort, threads, auto_align_input, auto_contiguous, avoid_copy, inverse, real, real_direction_flag=direction) pyFFTW-0.13.1/pyfftw/config.py000066400000000000000000000052671435600752200160670ustar00rootroot00000000000000"""Global configuration variables for PyFFTW. The approach taken here was adapated from Numba's config.py. """ from __future__ import print_function, division, absolute_import import os import multiprocessing import warnings from .pyfftw import _threading_type class _EnvReloader(object): def __init__(self): self.reset() def reset(self): self.old_environ = {} self.update(force=True) def update(self, force=False): new_environ = {} # read local env var OMP_NUM_THREADS and any starting with PYFFTW_ for name, value in os.environ.items(): if name.startswith('PYFFTW_') or name == 'OMP_NUM_THREADS': new_environ[name] = value # We update the config variables if at least one PYFFTW environment # variable was modified. This lets the user modify values # directly in the config module without having them when # reload_config() is called by the compiler. if force or self.old_environ != new_environ: self.process_environ(new_environ) # Store a copy self.old_environ = dict(new_environ) def process_environ(self, environ): def _readenv(name, ctor, default): value = environ.get(name) if value is None: return default() if callable(default) else default try: return ctor(value) except Exception: warnings.warn("environ %s defined but failed to parse '%s'" % (name, value), RuntimeWarning) return default def optional_str(x): return str(x) if x is not None else None if _threading_type is None: NUM_THREADS = 1 else: if (_threading_type == "OMP" and "PYFFTW_NUM_THREADS" not in environ): # fallback to OMP_NUM_THREADS if PYFFTW_NUM_THREADS undefined NUM_THREADS = _readenv("OMP_NUM_THREADS", int, 1) else: NUM_THREADS = _readenv("PYFFTW_NUM_THREADS", int, 1) # if user requested <= 0 threads, use the maximum available if NUM_THREADS <= 0: NUM_THREADS = multiprocessing.cpu_count() PLANNER_EFFORT = _readenv( "PYFFTW_PLANNER_EFFORT", str, "FFTW_ESTIMATE") # Inject the configuration values into the module globals for name, value in locals().copy().items(): if name.isupper(): globals()[name] = value _env_reloader = _EnvReloader() def _reload_config(): """ Reload the configuration from environment variables, if necessary. """ _env_reloader.update() pyFFTW-0.13.1/pyfftw/cpu.pxd000066400000000000000000000032221435600752200155410ustar00rootroot00000000000000# cython: language_level=3 # # Copyright 2014 Knowledge Economy Developments Ltd # # Henry Gomersall # heng@kedevelopments.co.uk # # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, this # list of conditions and the following disclaimer. # # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions and the following disclaimer in the documentation # and/or other materials provided with the distribution. # # * Neither the name of the copyright holder nor the names of its contributors # may be used to endorse or promote products derived from this software without # specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. # cdef extern from "cpu.h": int simd_alignment() pyFFTW-0.13.1/pyfftw/interfaces/000077500000000000000000000000001435600752200163615ustar00rootroot00000000000000pyFFTW-0.13.1/pyfftw/interfaces/__init__.py000066400000000000000000000271061435600752200205000ustar00rootroot00000000000000#!/usr/bin/env python '''The :mod:`pyfftw.interfaces` package provides interfaces to :mod:`pyfftw` that implement the API of other, more commonly used FFT libraries; specifically :mod:`numpy.fft`, :mod:`scipy.fft` and :mod:`scipy.fftpack`. The intention is to satisfy two clear use cases: 1. Simple, clean and well established interfaces to using :mod:`pyfftw`, removing the requirement for users to know or understand about creating and using :class:`pyfftw.FFTW` objects, whilst still benefiting from most of the speed benefits of FFTW. 2. A library that can be dropped into code that is already written to use a supported FFT library, with no significant change to the existing code. The power of python allows this to be done at runtime to a third party library, without changing any of that library's code. The :mod:`pyfftw.interfaces` implementation is designed to sacrifice a small amount of the flexibility compared to accessing the :class:`pyfftw.FFTW` object directly, but implements a reasonable set of defaults and optional tweaks that should satisfy most situations. The precision of the transform that is used is selected from the array that is passed in, defaulting to double precision if any type conversion is required. This module works by generating a :class:`pyfftw.FFTW` object behind the scenes using the :mod:`pyfftw.builders` interface, which is then executed. There is therefore a potentially substantial overhead when a new plan needs to be created. This is down to FFTW's internal planner process. After a specific transform has been planned once, subsequent calls in which the input array is equivalent will be much faster, though still not without potentially significant overhead. *This* overhead can be largely alleviated by enabling the :mod:`pyfftw.interfaces.cache` functionality. However, even when the cache is used, very small transforms may suffer a significant relative slow-down not present when accessing :mod:`pyfftw.FFTW` directly (because the transform time can be negligibly small compared to the fixed :mod:`pyfftw.interfaces` overhead). In addition, potentially extra copies of the input array might be made. If speed or memory conservation is of absolutely paramount importance, the suggestion is to use :mod:`pyfftw.FFTW` (which provides better control over copies and so on), either directly or through :mod:`pyfftw.builders`. As always, experimentation is the best guide to optimisation. In practice, this means something like the following (taking :mod:`~pyfftw.interfaces.numpy_fft` as an example): .. doctest:: >>> import pyfftw, numpy >>> a = pyfftw.empty_aligned((128, 64), dtype='complex64', n=16) >>> a[:] = numpy.random.randn(*a.shape) + 1j*numpy.random.randn(*a.shape) >>> fft_a = pyfftw.interfaces.numpy_fft.fft2(a) # Will need to plan .. doctest:: >>> b = pyfftw.empty_aligned((128, 64), dtype='complex64', n=16) >>> b[:] = a >>> fft_b = pyfftw.interfaces.numpy_fft.fft2(b) # Already planned, so faster .. doctest:: >>> c = pyfftw.empty_aligned(132, dtype='complex128', n=16) >>> fft_c = pyfftw.interfaces.numpy_fft.fft(c) # Needs a new plan >>> c[:] = numpy.random.randn(*c.shape) + 1j*numpy.random.randn(*c.shape) .. doctest:: >>> pyfftw.interfaces.cache.enable() >>> fft_a = pyfftw.interfaces.numpy_fft.fft2(a) # still planned >>> fft_b = pyfftw.interfaces.numpy_fft.fft2(b) # much faster, from the cache The usual wisdom import and export functions work well for the case where the initial plan might be prohibitively expensive. Just use :func:`pyfftw.export_wisdom` and :func:`pyfftw.import_wisdom` as needed after having performed the transform once. Implemented Functions --------------------- The implemented functions are listed below. :mod:`numpy.fft` is implemented by :mod:`pyfftw.interfaces.numpy_fft`, :mod:`scipy.fftpack` by :mod:`pyfftw.interfaces.scipy_fftpack` and :mod:`scipy.fft` by :mod:`pyfftw.interfaces.scipy_fft`. All the implemented functions are extended by the use of additional arguments, which are :ref:`documented below`. Not all the functions provided by :mod:`numpy.fft`, :mod:`scipy.fft` and :mod:`scipy.fftpack` are implemented by :mod:`pyfftw.interfaces`. In the case where a function is not implemented, the function is imported into the namespace from the corresponding library. This means that all the documented functionality of the library *is* provided through :mod:`pyfftw.interfaces`. One known caveat is that repeated axes are handled differently. Axes that are repeated in the ``axes`` argument are considered only once and without error; as compared to :mod:`numpy.fft` in which repeated axes results in the DFT being taken along that axes as many times as the axis occurs, or to :mod:`scipy` where an error is raised. :mod:`~pyfftw.interfaces.numpy_fft` """"""""""""""""""""""""""""""""""" * :func:`pyfftw.interfaces.numpy_fft.fft` * :func:`pyfftw.interfaces.numpy_fft.ifft` * :func:`pyfftw.interfaces.numpy_fft.fft2` * :func:`pyfftw.interfaces.numpy_fft.ifft2` * :func:`pyfftw.interfaces.numpy_fft.fftn` * :func:`pyfftw.interfaces.numpy_fft.ifftn` * :func:`pyfftw.interfaces.numpy_fft.rfft` * :func:`pyfftw.interfaces.numpy_fft.irfft` * :func:`pyfftw.interfaces.numpy_fft.rfft2` * :func:`pyfftw.interfaces.numpy_fft.irfft2` * :func:`pyfftw.interfaces.numpy_fft.rfftn` * :func:`pyfftw.interfaces.numpy_fft.irfftn` * :func:`pyfftw.interfaces.numpy_fft.hfft` * :func:`pyfftw.interfaces.numpy_fft.ihfft` :mod:`~pyfftw.interfaces.scipy_fft` """"""""""""""""""""""""""""""""""""""" * :func:`pyfftw.interfaces.scipy_fft.fft` * :func:`pyfftw.interfaces.scipy_fft.ifft` * :func:`pyfftw.interfaces.scipy_fft.fft2` * :func:`pyfftw.interfaces.scipy_fft.ifft2` * :func:`pyfftw.interfaces.scipy_fft.fftn` * :func:`pyfftw.interfaces.scipy_fft.ifftn` * :func:`pyfftw.interfaces.scipy_fft.rfft` * :func:`pyfftw.interfaces.scipy_fft.irfft` * :func:`pyfftw.interfaces.scipy_fft.rfft2` * :func:`pyfftw.interfaces.scipy_fft.irfft2` * :func:`pyfftw.interfaces.scipy_fft.rfftn` * :func:`pyfftw.interfaces.scipy_fft.irfftn` * :func:`pyfftw.interfaces.scipy_fft.hfft` * :func:`pyfftw.interfaces.scipy_fft.ihfft` * :func:`pyfftw.interfaces.scipy_fft.next_fast_len` :mod:`~pyfftw.interfaces.scipy_fftpack` """"""""""""""""""""""""""""""""""""""" * :func:`pyfftw.interfaces.scipy_fftpack.fft` * :func:`pyfftw.interfaces.scipy_fftpack.ifft` * :func:`pyfftw.interfaces.scipy_fftpack.fft2` * :func:`pyfftw.interfaces.scipy_fftpack.ifft2` * :func:`pyfftw.interfaces.scipy_fftpack.fftn` * :func:`pyfftw.interfaces.scipy_fftpack.ifftn` * :func:`pyfftw.interfaces.scipy_fftpack.rfft` * :func:`pyfftw.interfaces.scipy_fftpack.irfft` * :func:`pyfftw.interfaces.scipy_fftpack.next_fast_len` :mod:`~pyfftw.interfaces.dask_fft` """"""""""""""""""""""""""""""""""" * :func:`pyfftw.interfaces.dask_fft.fft` * :func:`pyfftw.interfaces.dask_fft.ifft` * :func:`pyfftw.interfaces.dask_fft.fft2` * :func:`pyfftw.interfaces.dask_fft.ifft2` * :func:`pyfftw.interfaces.dask_fft.fftn` * :func:`pyfftw.interfaces.dask_fft.ifftn` * :func:`pyfftw.interfaces.dask_fft.rfft` * :func:`pyfftw.interfaces.dask_fft.irfft` * :func:`pyfftw.interfaces.dask_fft.rfft2` * :func:`pyfftw.interfaces.dask_fft.irfft2` * :func:`pyfftw.interfaces.dask_fft.rfftn` * :func:`pyfftw.interfaces.dask_fft.irfftn` * :func:`pyfftw.interfaces.dask_fft.hfft` * :func:`pyfftw.interfaces.dask_fft.ihfft` .. _interfaces_additional_args: Additional Arguments -------------------- In addition to the equivalent arguments in :mod:`numpy.fft`, :mod:`scipy.fft` and :mod:`scipy.fftpack`, all these functions also add several additional arguments for finer control over the FFT. These additional arguments are largely a subset of the keyword arguments in :mod:`pyfftw.builders` with a few exceptions and with different defaults. * ``overwrite_input``: Whether or not the input array can be overwritten during the transform. This sometimes results in a faster algorithm being made available. It causes the ``'FFTW_DESTROY_INPUT'`` flag to be passed to the intermediate :class:`pyfftw.FFTW` object. Unlike with :mod:`pyfftw.builders`, this argument is included with *every* function in this package. In :mod:`~pyfftw.interfaces.scipy_fftpack` and :mod:`~pyfftw.interfaces.scipy_fft`, this argument is replaced by ``overwrite_x``, to which it is equivalent (albeit at the same position). The default is ``False`` to be consistent with :mod:`numpy.fft`. * ``planner_effort``: A string dictating how much effort is spent in planning the FFTW routines. This is passed to the creation of the intermediate :class:`pyfftw.FFTW` object as an entry in the flags list. They correspond to flags passed to the :class:`pyfftw.FFTW` object. The valid strings, in order of their increasing impact on the time to compute are: ``'FFTW_ESTIMATE'``, ``'FFTW_MEASURE'`` (default), ``'FFTW_PATIENT'`` and ``'FFTW_EXHAUSTIVE'``. The `Wisdom `_ that FFTW has accumulated or has loaded (through :func:`pyfftw.import_wisdom`) is used during the creation of :class:`pyfftw.FFTW` objects. Note that the first time planning stage can take a substantial amount of time. For this reason, the default is to use ``'FFTW_ESTIMATE'``, which potentially results in a slightly suboptimal plan being used, but with a substantially quicker first-time planner step. * ``threads``: The number of threads used to perform the FFT. In :mod:`~pyfftw.interfaces.scipy_fft`, this argument is replaced by ``workers``, which serves the same purpose, but is also compatible with the :func:`scipy.fft.set_workers` context manager. The default is ``1``. * ``auto_align_input``: Correctly byte align the input array for optimal usage of vector instructions. This can lead to a substantial speedup. This argument being ``True`` makes sure that the input array is correctly aligned. It is possible to correctly byte align the array prior to calling this function (using, for example, :func:`pyfftw.byte_align`). If and only if a realignment is necessary is a new array created. It's worth noting that just being aligned may not be sufficient to create the fastest possible transform. For example, if the array is not contiguous (i.e. certain axes have gaps in memory between slices), it may be faster to plan a transform for a contiguous array, and then rely on the array being copied in before the transform (which :class:`pyfftw.FFTW` will handle for you). The ``auto_contiguous`` argument controls whether this function also takes care of making sure the array is contiguous or not. The default is ``True``. * ``auto_contiguous``: Make sure the input array is contiguous in memory before performing the transform on it. If the array is not contiguous, it is copied into an interim array. This is because it is often faster to copy the data before the transform and then transform a contiguous array than it is to try to take the transform of a non-contiguous array. This is particularly true in conjunction with the ``auto_align_input`` argument which is used to make sure that the transform is taken of an aligned array. The default is ``True``. ''' from . import ( numpy_fft, cache,) try: import scipy.fftpack except ImportError: pass else: from numpy.lib import NumpyVersion has_scipy_fft = NumpyVersion(scipy.__version__) >= NumpyVersion('1.4.0') del NumpyVersion del scipy from . import scipy_fftpack if has_scipy_fft: from . import scipy_fft fft_wrap = None try: from dask.array.fft import fft_wrap except ImportError: pass if fft_wrap: from . import dask_fft del fft_wrap pyFFTW-0.13.1/pyfftw/interfaces/_utils.py000066400000000000000000000132061435600752200202340ustar00rootroot00000000000000#!/usr/bin/env python # # Copyright 2014 Knowledge Economy Developments Ltd # Copyright 2014 David Wells # # Henry Gomersall # heng@kedevelopments.co.uk # # Michael McNeil Forbes # michael.forbes+pyfftw@gmail.com # # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, this # list of conditions and the following disclaimer. # # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions and the following disclaimer in the documentation # and/or other materials provided with the distribution. # # * Neither the name of the copyright holder nor the names of its contributors # may be used to endorse or promote products derived from this software without # specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. # ''' Utility functions for the interfaces routines ''' import pyfftw.builders as builders import pyfftw import numpy from . import cache def _Xfftn(a, s, axes, overwrite_input, planner_effort, threads, auto_align_input, auto_contiguous, calling_func, normalise_idft=True, ortho=False, real_direction_flag=None): work_with_copy = False a = numpy.asanyarray(a) try: s = tuple(s) except TypeError: pass try: axes = tuple(axes) except TypeError: pass if calling_func in ('dct', 'dst'): # real-to-real transforms require passing an additional flag argument avoid_copy = False args = (overwrite_input, planner_effort, threads, auto_align_input, auto_contiguous, avoid_copy, real_direction_flag) elif calling_func in ('irfft2', 'irfftn'): # overwrite_input is not an argument to irfft2 or irfftn args = (planner_effort, threads, auto_align_input, auto_contiguous) if not overwrite_input: # Only irfft2 and irfftn have overwriting the input # as the default (and so require the input array to # be reloaded). work_with_copy = True else: args = (overwrite_input, planner_effort, threads, auto_align_input, auto_contiguous) if not a.flags.writeable: # Special case of a locked array - always work with a # copy. See issue #92. work_with_copy = True if overwrite_input: raise ValueError('overwrite_input cannot be True when the ' + 'input array flags.writeable is False') if work_with_copy: # We make the copy before registering the key so that the # copy's stride information will be cached since this will be # used for planning. Make sure the copy is byte aligned to # prevent further copying a_original = a a = pyfftw.empty_aligned(shape=a.shape, dtype=a.dtype) a[...] = a_original if cache.is_enabled(): alignment = a.ctypes.data % pyfftw.simd_alignment key = (calling_func, a.shape, a.strides, a.dtype, s.__hash__(), axes.__hash__(), alignment, args) try: if key in cache._fftw_cache: FFTW_object = cache._fftw_cache.lookup(key) else: FFTW_object = None except KeyError: # This occurs if the object has fallen out of the cache between # the check and the lookup FFTW_object = None if not cache.is_enabled() or FFTW_object is None: # If we're going to create a new FFTW object and are not # working with a copy, then we need to copy the input array to # preserve it, otherwise we can't actually take the transform # of the input array! (in general, we have to assume that the # input array will be destroyed during planning). if not work_with_copy: a_copy = a.copy() planner_args = (a, s, axes) + args FFTW_object = getattr(builders, calling_func)(*planner_args) # Only copy if the input array is what was actually used # (otherwise it shouldn't be overwritten) if not work_with_copy and FFTW_object.input_array is a: a[:] = a_copy if cache.is_enabled(): cache._fftw_cache.insert(FFTW_object, key) output_array = FFTW_object(normalise_idft=normalise_idft, ortho=ortho) else: orig_output_array = FFTW_object.output_array output_shape = orig_output_array.shape output_dtype = orig_output_array.dtype output_alignment = FFTW_object.output_alignment output_array = pyfftw.empty_aligned( output_shape, output_dtype, n=output_alignment) FFTW_object(input_array=a, output_array=output_array, normalise_idft=normalise_idft, ortho=ortho) return output_array pyFFTW-0.13.1/pyfftw/interfaces/cache.py000066400000000000000000000220661435600752200200040ustar00rootroot00000000000000#!/usr/bin/env python # # Copyright 2015 Knowledge Economy Developments Ltd # # Henry Gomersall # heng@kedevelopments.co.uk # # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, this # list of conditions and the following disclaimer. # # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions and the following disclaimer in the documentation # and/or other materials provided with the distribution. # # * Neither the name of the copyright holder nor the names of its contributors # may be used to endorse or promote products derived from this software without # specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. # ''' During calls to functions implemented in :mod:`pyfftw.interfaces`, a :class:`pyfftw.FFTW` object is necessarily created. Although the time to create a new :class:`pyfftw.FFTW` is short (assuming that the planner possesses the necessary wisdom to create the plan immediately), it may still take longer than a short transform. This module implements a method by which objects that are created through :mod:`pyfftw.interfaces` are temporarily cached. If an equivalent transform is then performed within a short period, the object is acquired from the cache rather than a new one created. The equivalency is quite conservative and in practice means that if any of the arguments change, or if the properties of the array (shape, strides, dtype) change in any way, then the cache lookup will fail. The cache temporarily stores a copy of any interim :class:`pyfftw.FFTW` objects that are created. If they are not used for some period of time, which can be set with :func:`pyfftw.interfaces.cache.set_keepalive_time`, then they are removed from the cache (liberating any associated memory). The default keepalive time is 0.1 seconds. Enable the cache by calling :func:`pyfftw.interfaces.cache.enable`. Disable it by calling :func:`pyfftw.interfaces.cache.disable`. By default, the cache is disabled. Note that even with the cache enabled, there is a fixed overhead associated with lookups. This means that for small transforms, the overhead may exceed the transform. At this point, it's worth looking at using :class:`pyfftw.FFTW` directly. When the cache is enabled, the module spawns a new thread to keep track of the objects. If :mod:`threading` is not available, then the cache is not available and trying to use it will raise an ImportError exception. The actual implementation of the cache is liable to change, but the documented API is stable. ''' try: import threading as _threading _threading_import_error = None except ImportError as e: _threading_import_error = e _threading = None import time import weakref __all__ = ['enable', 'disable', 'set_keepalive_time'] _fftw_cache = None class CacheError(Exception): pass def enable(): '''Enable the cache. ''' global _fftw_cache if _threading is not None: if _fftw_cache is None: _fftw_cache = _Cache() else: raise ImportError(_threading_import_error) def disable(): '''Disable the cache. ''' global _fftw_cache _fftw_cache = None def is_enabled(): '''Return whether the cache is currently enabled. ''' if _fftw_cache is None: return False else: return True def set_keepalive_time(keepalive_time): '''Set the minimum time in seconds for which any :mod:`pyfftw.FFTW` object in the cache is kept alive. When the cache is enabled, the interim objects that are used through a :mod:`pyfftw.interfaces` function are cached for the time set through this function. If the object is not used for the that time, it is removed from the cache. Using the object zeros the timer. The time is not precise, and sets a minimum time to be alive. In practice, it may be quite a bit longer before the object is deleted from the cache (due to implementational details - e.g. contention from other threads). ''' global _fftw_cache if _fftw_cache is None: raise CacheError('Cache is not currently enabled') else: _fftw_cache.set_keepalive_time(keepalive_time) class _Cache(object): @property def keepalive_time(self): return self._keepalive_time def __init__(self, keepalive_time=0.1): self._cache_dict = {} self.set_keepalive_time(keepalive_time) # A set of objects to be kept alive during the next cull self._keepalive_set = set() self._cull_lock = _threading.Lock() self._keepalive_set_lock = _threading.Lock() self.initialised = _threading.Event() self._parent_thread = _threading.current_thread() self._close_thread_now = _threading.Event() self._initialised = _threading.Event() self._initialised.clear() # Explicitly clear it for clarity self._thread_object = _threading.Thread(target=_Cache._run, args=(weakref.proxy(self), ), name='PyFFTWCacheThread') self._thread_object.daemon = True self._thread_object.start() while not self._initialised.is_set(): # This loop is necessary to stop the main thread doing # anything until the exception handler in _run can deal with # the object being deleted. pass def __del__(self): # Wait until the thread object has quit before # exiting (which it will because a reference error will # be raised). try: self._close_thread_now.set() except TypeError: # Not sure what's going on here, but IPython baulks on exit pass def __contains__(self, key): return key in self._cache_dict def _run(self): last_cull_time = time.time() try: self._initialised.set() while True: if (not self._parent_thread.is_alive() or self._close_thread_now.is_set()): break if time.time() - last_cull_time > self._keepalive_time: # Perform a cull last_cull_time = time.time() with self._cull_lock: # Operate on a copy of the cache dict # so lookups continue. new_cache_dict = self._cache_dict.copy() with self._keepalive_set_lock: # Work out which should be culled cull_set = set(new_cache_dict).difference( self._keepalive_set) self._keepalive_set = set() for each_key in cull_set: del new_cache_dict[each_key] # Necessarily atomic, so no problem with # the lookups continuing self._cache_dict = new_cache_dict time.sleep(self._wakeup_time) except ReferenceError: pass def set_keepalive_time(self, keepalive_time=0.1): '''Set the minimum time in seconds for which any object in the cache is kept alive. The time is not precise, and sets a minimum time to be alive. In practice, it may be up to twice as long before the object is deleted from the cache (due to implementational details). ''' self._keepalive_time = float(keepalive_time) if self._keepalive_time/2 > 0.1: self._wakeup_time = 0.1 else: self._wakeup_time = self._keepalive_time/2 def _refresh(self, key): '''Refresh the object referenced by key to stop it being culled on the next round. ''' with self._keepalive_set_lock: self._keepalive_set.add(key) def insert(self, obj, key): '''Insert the passed object into the cache, referenced by key, a hashable. ''' with self._cull_lock: self._cache_dict[key] = obj self._refresh(key) def lookup(self, key): '''Lookup the object referenced by key and return it, refreshing the cache at the same time. ''' self._refresh(key) return self._cache_dict[key] pyFFTW-0.13.1/pyfftw/interfaces/dask_fft.py000066400000000000000000000061451435600752200205220ustar00rootroot00000000000000#!/usr/bin/env python # # Henry Gomersall # heng@kedevelopments.co.uk # # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, this # list of conditions and the following disclaimer. # # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions and the following disclaimer in the documentation # and/or other materials provided with the distribution. # # * Neither the name of the copyright holder nor the names of its contributors # may be used to endorse or promote products derived from this software without # specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. # ''' This module implements those functions that replace aspects of the :mod:`dask.fft` module. This module *provides* the entire documented namespace of :mod:`dask.fft`, but those functions that are not included here are imported directly from :mod:`dask.fft`. It is notable that unlike :mod:`numpy.fftpack`, which :mod:`dask.fft` wraps, these functions will generally return an output array with the same precision as the input array, and the transform that is chosen is chosen based on the precision of the input array. That is, if the input array is 32-bit floating point, then the transform will be 32-bit floating point and so will the returned array. Half precision input will be converted to single precision. Otherwise, if any type conversion is required, the default will be double precision. The exceptions raised by each of these functions are mostly as per their equivalents in :mod:`dask.fft`, though there are some corner cases in which this may not be true. ''' from . import numpy_fft as _numpy_fft from dask.array.fft import ( fft_wrap, fftfreq, rfftfreq, fftshift, ifftshift, ) fft = fft_wrap(_numpy_fft.fft) ifft = fft_wrap(_numpy_fft.ifft) fft2 = fft_wrap(_numpy_fft.fft2) ifft2 = fft_wrap(_numpy_fft.ifft2) fftn = fft_wrap(_numpy_fft.fftn) ifftn = fft_wrap(_numpy_fft.ifftn) rfft = fft_wrap(_numpy_fft.rfft) irfft = fft_wrap(_numpy_fft.irfft) rfft2 = fft_wrap(_numpy_fft.rfft2) irfft2 = fft_wrap(_numpy_fft.irfft2) rfftn = fft_wrap(_numpy_fft.rfftn) irfftn = fft_wrap(_numpy_fft.irfftn) hfft = fft_wrap(_numpy_fft.hfft) ihfft = fft_wrap(_numpy_fft.ihfft) pyFFTW-0.13.1/pyfftw/interfaces/numpy_fft.py000066400000000000000000000335631435600752200207540ustar00rootroot00000000000000#!/usr/bin/env python # # Henry Gomersall # heng@kedevelopments.co.uk # # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, this # list of conditions and the following disclaimer. # # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions and the following disclaimer in the documentation # and/or other materials provided with the distribution. # # * Neither the name of the copyright holder nor the names of its contributors # may be used to endorse or promote products derived from this software without # specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. # ''' This module implements those functions that replace aspects of the :mod:`numpy.fft` module. This module *provides* the entire documented namespace of :mod:`numpy.fft`, but those functions that are not included here are imported directly from :mod:`numpy.fft`. It is notable that unlike :mod:`numpy.fftpack`, these functions will generally return an output array with the same precision as the input array, and the transform that is chosen is chosen based on the precision of the input array. That is, if the input array is 32-bit floating point, then the transform will be 32-bit floating point and so will the returned array. Half precision input will be converted to single precision. Otherwise, if any type conversion is required, the default will be double precision. If pyFFTW was not built with support for double precision, the default is long double precision. If that is not available, it defaults to single precision. One known caveat is that repeated axes are handled differently to :mod:`numpy.fft`; axes that are repeated in the axes argument are considered only once, as compared to :mod:`numpy.fft` in which repeated axes results in the DFT being taken along that axes as many times as the axis occurs. The exceptions raised by each of these functions are mostly as per their equivalents in :mod:`numpy.fft`, though there are some corner cases in which this may not be true. ''' from ._utils import _Xfftn from ..builders._utils import (_norm_args, _default_effort, _default_threads) # Complete the namespace (these are not actually used in this module) from numpy.fft import fftfreq, fftshift, ifftshift import numpy as np __all__ = ['fft', 'ifft', 'fft2', 'ifft2', 'fftn', 'ifftn', 'rfft', 'irfft', 'rfft2', 'irfft2', 'rfftn', 'irfftn', 'hfft', 'ihfft', 'fftfreq', 'fftshift', 'ifftshift'] try: # rfftfreq was added to the namespace in numpy 1.8 from numpy.fft import rfftfreq __all__ += ['rfftfreq', ] except ImportError: pass _swap_direction_dict = {"backward": "forward", None: "forward", "ortho": "ortho", "forward": "backward"} def _swap_direction(norm): try: return _swap_direction_dict[norm] except KeyError: raise ValueError(f'Invalid norm value {norm}; should be "backward", ' '"ortho" or "forward".') def fft(a, n=None, axis=-1, norm=None, overwrite_input=False, planner_effort=None, threads=None, auto_align_input=True, auto_contiguous=True): '''Perform a 1D FFT. The first four arguments are as per :func:`numpy.fft.fft`; the rest of the arguments are documented in the :ref:`additional arguments docs`. ''' calling_func = 'fft' planner_effort = _default_effort(planner_effort) threads = _default_threads(threads) return _Xfftn(a, n, axis, overwrite_input, planner_effort, threads, auto_align_input, auto_contiguous, calling_func, **_norm_args(norm)) def ifft(a, n=None, axis=-1, norm=None, overwrite_input=False, planner_effort=None, threads=None, auto_align_input=True, auto_contiguous=True): '''Perform a 1D inverse FFT. The first four arguments are as per :func:`numpy.fft.ifft`; the rest of the arguments are documented in the :ref:`additional arguments docs`. ''' calling_func = 'ifft' planner_effort = _default_effort(planner_effort) threads = _default_threads(threads) return _Xfftn(a, n, axis, overwrite_input, planner_effort, threads, auto_align_input, auto_contiguous, calling_func, **_norm_args(norm)) def fft2(a, s=None, axes=(-2,-1), norm=None, overwrite_input=False, planner_effort=None, threads=None, auto_align_input=True, auto_contiguous=True): '''Perform a 2D FFT. The first four arguments are as per :func:`numpy.fft.fft2`; the rest of the arguments are documented in the :ref:`additional arguments docs`. ''' calling_func = 'fft2' planner_effort = _default_effort(planner_effort) threads = _default_threads(threads) return _Xfftn(a, s, axes, overwrite_input, planner_effort, threads, auto_align_input, auto_contiguous, calling_func, **_norm_args(norm)) def ifft2(a, s=None, axes=(-2,-1), norm=None, overwrite_input=False, planner_effort=None, threads=None, auto_align_input=True, auto_contiguous=True): '''Perform a 2D inverse FFT. The first four arguments are as per :func:`numpy.fft.ifft2`; the rest of the arguments are documented in the :ref:`additional arguments docs`. ''' calling_func = 'ifft2' planner_effort = _default_effort(planner_effort) threads = _default_threads(threads) return _Xfftn(a, s, axes, overwrite_input, planner_effort, threads, auto_align_input, auto_contiguous, calling_func, **_norm_args(norm)) def fftn(a, s=None, axes=None, norm=None, overwrite_input=False, planner_effort=None, threads=None, auto_align_input=True, auto_contiguous=True): '''Perform an n-D FFT. The first four arguments are as per :func:`numpy.fft.fftn`; the rest of the arguments are documented in the :ref:`additional arguments docs`. ''' calling_func = 'fftn' planner_effort = _default_effort(planner_effort) threads = _default_threads(threads) return _Xfftn(a, s, axes, overwrite_input, planner_effort, threads, auto_align_input, auto_contiguous, calling_func, **_norm_args(norm)) def ifftn(a, s=None, axes=None, norm=None, overwrite_input=False, planner_effort=None, threads=None, auto_align_input=True, auto_contiguous=True): '''Perform an n-D inverse FFT. The first four arguments are as per :func:`numpy.fft.ifftn`; the rest of the arguments are documented in the :ref:`additional arguments docs`. ''' calling_func = 'ifftn' planner_effort = _default_effort(planner_effort) threads = _default_threads(threads) return _Xfftn(a, s, axes, overwrite_input, planner_effort, threads, auto_align_input, auto_contiguous, calling_func, **_norm_args(norm)) def rfft(a, n=None, axis=-1, norm=None, overwrite_input=False, planner_effort=None, threads=None, auto_align_input=True, auto_contiguous=True): '''Perform a 1D real FFT. The first four arguments are as per :func:`numpy.fft.rfft`; the rest of the arguments are documented in the :ref:`additional arguments docs`. ''' calling_func = 'rfft' planner_effort = _default_effort(planner_effort) threads = _default_threads(threads) return _Xfftn(a, n, axis, overwrite_input, planner_effort, threads, auto_align_input, auto_contiguous, calling_func, **_norm_args(norm)) def irfft(a, n=None, axis=-1, norm=None, overwrite_input=False, planner_effort=None, threads=None, auto_align_input=True, auto_contiguous=True): '''Perform a 1D real inverse FFT. The first four arguments are as per :func:`numpy.fft.irfft`; the rest of the arguments are documented in the :ref:`additional arguments docs`. ''' calling_func = 'irfft' planner_effort = _default_effort(planner_effort) threads = _default_threads(threads) return _Xfftn(a, n, axis, overwrite_input, planner_effort, threads, auto_align_input, auto_contiguous, calling_func, **_norm_args(norm)) def rfft2(a, s=None, axes=(-2,-1), norm=None, overwrite_input=False, planner_effort=None, threads=None, auto_align_input=True, auto_contiguous=True): '''Perform a 2D real FFT. The first four arguments are as per :func:`numpy.fft.rfft2`; the rest of the arguments are documented in the :ref:`additional arguments docs`. ''' calling_func = 'rfft2' planner_effort = _default_effort(planner_effort) threads = _default_threads(threads) return _Xfftn(a, s, axes, overwrite_input, planner_effort, threads, auto_align_input, auto_contiguous, calling_func, **_norm_args(norm)) def irfft2(a, s=None, axes=(-2,-1), norm=None, overwrite_input=False, planner_effort=None, threads=None, auto_align_input=True, auto_contiguous=True): '''Perform a 2D real inverse FFT. The first four arguments are as per :func:`numpy.fft.irfft2`; the rest of the arguments are documented in the :ref:`additional arguments docs`. ''' calling_func = 'irfft2' planner_effort = _default_effort(planner_effort) threads = _default_threads(threads) return _Xfftn(a, s, axes, overwrite_input, planner_effort, threads, auto_align_input, auto_contiguous, calling_func, **_norm_args(norm)) def rfftn(a, s=None, axes=None, norm=None, overwrite_input=False, planner_effort=None, threads=None, auto_align_input=True, auto_contiguous=True): '''Perform an n-D real FFT. The first four arguments are as per :func:`numpy.fft.rfftn`; the rest of the arguments are documented in the :ref:`additional arguments docs`. ''' calling_func = 'rfftn' planner_effort = _default_effort(planner_effort) threads = _default_threads(threads) return _Xfftn(a, s, axes, overwrite_input, planner_effort, threads, auto_align_input, auto_contiguous, calling_func, **_norm_args(norm)) def irfftn(a, s=None, axes=None, norm=None, overwrite_input=False, planner_effort=None, threads=None, auto_align_input=True, auto_contiguous=True): '''Perform an n-D real inverse FFT. The first four arguments are as per :func:`numpy.fft.rfftn`; the rest of the arguments are documented in the :ref:`additional arguments docs`. ''' calling_func = 'irfftn' planner_effort = _default_effort(planner_effort) threads = _default_threads(threads) return _Xfftn(a, s, axes, overwrite_input, planner_effort, threads, auto_align_input, auto_contiguous, calling_func, **_norm_args(norm)) def hfft(a, n=None, axis=-1, norm=None, overwrite_input=False, planner_effort=None, threads=None, auto_align_input=True, auto_contiguous=True): '''Perform a 1D FFT of a signal with hermitian symmetry. This yields a real output spectrum. See :func:`numpy.fft.hfft` for more information. The first four arguments are as per :func:`numpy.fft.hfft`; the rest of the arguments are documented in the :ref:`additional arguments docs`. ''' # hfft(a) is equivalent to irfft(conjugate(a)) calling_func = 'irfft' planner_effort = _default_effort(planner_effort) threads = _default_threads(threads) a = np.asarray(a) if a.size < 2: raise ValueError("hfft requires input with length >= 2") if a.dtype == np.float16: a = a.astype(np.float32) if n is None: n = (a.shape[axis] - 1)*2 new_norm = _swap_direction(norm) return _Xfftn(np.conjugate(a), n, axis, overwrite_input, planner_effort, threads, auto_align_input, auto_contiguous, calling_func, **_norm_args(new_norm)) def ihfft(a, n=None, axis=-1, norm=None, overwrite_input=False, planner_effort=None, threads=None, auto_align_input=True, auto_contiguous=True): '''Perform a 1D inverse FFT of a real-spectrum, yielding a signal with hermitian symmetry. See :func:`numpy.fft.ihfft` for more information. The first four arguments are as per :func:`numpy.fft.ihfft`; the rest of the arguments are documented in the :ref:`additional arguments docs`. ''' # ihfft(a) is equivalent to conjugate(rfft(a)) calling_func = 'rfft' planner_effort = _default_effort(planner_effort) threads = _default_threads(threads) a = np.asarray(a) if n is None: n = a.shape[axis] new_norm = _swap_direction(norm) return np.conjugate(_Xfftn(a, n, axis, overwrite_input, planner_effort, threads, auto_align_input, auto_contiguous, calling_func, **_norm_args(new_norm))) pyFFTW-0.13.1/pyfftw/interfaces/scipy_fft.py000066400000000000000000000456711435600752200207360ustar00rootroot00000000000000#!/usr/bin/env python # # Copyright 2019, The pyFFTW developers # # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, this # list of conditions and the following disclaimer. # # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions and the following disclaimer in the documentation # and/or other materials provided with the distribution. # # * Neither the name of the copyright holder nor the names of its contributors # may be used to endorse or promote products derived from this software without # specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. # ''' This module implements those functions that replace aspects of the :mod:`scipy.fft` module. This module *provides* the entire documented namespace of :mod:`scipy.fft`, but those functions that are not included here are imported directly from :mod:`scipy.fft`. The exceptions raised by each of these functions are mostly as per their equivalents in :mod:`scipy.fft`, though there are some corner cases in which this may not be true. Some corner (mis)usages of :mod:`scipy.fft` may not transfer neatly. For example, using :func:`scipy.fft.fft2` with a non 1D array and a 2D `s` argument will return without exception whereas :func:`pyfftw.interfaces.scipy_fft.fft2` will raise a `ValueError`. ''' import os from . import numpy_fft from .scipy_fftpack import (_dct, _idct, _dctn, _idctn, _dst, _idst, _dstn, _idstn) # Complete the namespace (these are not actually used in this module) from scipy.fft import (hfft2, ihfft2, hfftn, ihfftn, fftshift, ifftshift, fftfreq, rfftfreq, get_workers, set_workers) # a next_fast_len specific to pyFFTW is used in place of the scipy.fft one from ..pyfftw import next_fast_len import scipy.fft as _fft import numpy as np __all__ = ['fft', 'ifft', 'fft2', 'ifft2', 'fftn', 'ifftn', 'rfft', 'irfft', 'rfft2', 'irfft2', 'rfftn', 'irfftn', 'hfft', 'ihfft', 'hfft2', 'ihfft2', 'hfftn', 'ihfftn', 'dct', 'idct', 'dst', 'idst', 'dctn', 'idctn', 'dstn', 'idstn', 'fftshift', 'ifftshift', 'fftfreq', 'rfftfreq', 'get_workers', 'set_workers', 'next_fast_len'] # Backend support for scipy.fft _implemented = {} __ua_domain__ = 'numpy.scipy.fft' _cpu_count = os.cpu_count() def __ua_function__(method, args, kwargs): fn = _implemented.get(method, None) if fn is None: return NotImplemented return fn(*args, **kwargs) def _implements(scipy_func): '''Decorator adds function to the dictionary of implemented functions''' def inner(func): _implemented[scipy_func] = func return func return inner def _workers_to_threads(workers): """Handle conversion of workers to a positive number of threads in the same way as scipy.fft.helpers._workers. """ if workers is None: return get_workers() if workers < 0: if workers >= -_cpu_count: workers += 1 + _cpu_count else: raise ValueError("workers value out of range; got {}, must not be" " less than {}".format(workers, -_cpu_count)) elif workers == 0: raise ValueError("workers must not be zero") return workers @_implements(_fft.fft) def fft(x, n=None, axis=-1, norm=None, overwrite_x=False, workers=None, planner_effort=None, auto_align_input=True, auto_contiguous=True): '''Perform a 1D FFT. The first six arguments are as per :func:`scipy.fft.fft`; the rest of the arguments are documented in the :ref:`additional argument docs`. ''' threads = _workers_to_threads(workers) return numpy_fft.fft(x, n, axis, norm, overwrite_x, planner_effort, threads, auto_align_input, auto_contiguous) @_implements(_fft.ifft) def ifft(x, n=None, axis=-1, norm=None, overwrite_x=False, workers=None, planner_effort=None, auto_align_input=True, auto_contiguous=True): '''Perform a 1D inverse FFT. The first six arguments are as per :func:`scipy.fft.ifft`; the rest of the arguments are documented in the :ref:`additional argument docs`. ''' threads = _workers_to_threads(workers) return numpy_fft.ifft(x, n, axis, norm, overwrite_x, planner_effort, threads, auto_align_input, auto_contiguous) @_implements(_fft.fft2) def fft2(x, s=None, axes=(-2, -1), norm=None, overwrite_x=False, workers=None, planner_effort=None, auto_align_input=True, auto_contiguous=True): '''Perform a 2D FFT. The first six arguments are as per :func:`scipy.fft.fft2`; the rest of the arguments are documented in the :ref:`additional argument docs`. ''' threads = _workers_to_threads(workers) return numpy_fft.fft2(x, s, axes, norm, overwrite_x, planner_effort, threads, auto_align_input, auto_contiguous) @_implements(_fft.ifft2) def ifft2(x, s=None, axes=(-2, -1), norm=None, overwrite_x=False, workers=None, planner_effort=None, auto_align_input=True, auto_contiguous=True): '''Perform a 2D inverse FFT. The first six arguments are as per :func:`scipy.fft.ifft2`; the rest of the arguments are documented in the :ref:`additional argument docs `. ''' threads = _workers_to_threads(workers) return numpy_fft.ifft2(x, s, axes, norm, overwrite_x, planner_effort, threads, auto_align_input, auto_contiguous) @_implements(_fft.fftn) def fftn(x, s=None, axes=None, norm=None, overwrite_x=False, workers=None, planner_effort=None, auto_align_input=True, auto_contiguous=True): '''Perform an n-D FFT. The first six arguments are as per :func:`scipy.fft.fftn`; the rest of the arguments are documented in the :ref:`additional argument docs`. ''' threads = _workers_to_threads(workers) return numpy_fft.fftn(x, s, axes, norm, overwrite_x, planner_effort, threads, auto_align_input, auto_contiguous) @_implements(_fft.ifftn) def ifftn(x, s=None, axes=None, norm=None, overwrite_x=False, workers=None, planner_effort=None, auto_align_input=True, auto_contiguous=True): '''Perform an n-D inverse FFT. The first six arguments are as per :func:`scipy.fft.ifftn`; the rest of the arguments are documented in the :ref:`additional argument docs`. ''' threads = _workers_to_threads(workers) return numpy_fft.ifftn(x, s, axes, norm, overwrite_x, planner_effort, threads, auto_align_input, auto_contiguous) @_implements(_fft.rfft) def rfft(x, n=None, axis=-1, norm=None, overwrite_x=False, workers=None, planner_effort=None, auto_align_input=True, auto_contiguous=True): '''Perform a 1D real FFT. The first six arguments are as per :func:`scipy.fft.rfft`; the rest of the arguments are documented in the :ref:`additional argument docs`. ''' x = np.asanyarray(x) if x.dtype.kind == 'c': raise TypeError('x must be a real sequence') threads = _workers_to_threads(workers) return numpy_fft.rfft(x, n, axis, norm, overwrite_x, planner_effort, threads, auto_align_input, auto_contiguous) @_implements(_fft.irfft) def irfft(x, n=None, axis=-1, norm=None, overwrite_x=False, workers=None, planner_effort=None, auto_align_input=True, auto_contiguous=True): '''Perform a 1D real inverse FFT. The first six arguments are as per :func:`scipy.fft.irfft`; the rest of the arguments are documented in the :ref:`additional argument docs`. ''' threads = _workers_to_threads(workers) return numpy_fft.irfft(x, n, axis, norm, overwrite_x, planner_effort, threads, auto_align_input, auto_contiguous) @_implements(_fft.rfft2) def rfft2(x, s=None, axes=(-2, -1), norm=None, overwrite_x=False, workers=None, planner_effort=None, auto_align_input=True, auto_contiguous=True): '''Perform a 2D real FFT. The first six arguments are as per :func:`scipy.fft.rfft2`; the rest of the arguments are documented in the :ref:`additional argument docs`. ''' x = np.asanyarray(x) if x.dtype.kind == 'c': raise TypeError('x must be a real sequence') threads = _workers_to_threads(workers) return numpy_fft.rfft2(x, s, axes, norm, overwrite_x, planner_effort, threads, auto_align_input, auto_contiguous) @_implements(_fft.irfft2) def irfft2(x, s=None, axes=(-2, -1), norm=None, overwrite_x=False, workers=None, planner_effort=None, auto_align_input=True, auto_contiguous=True): '''Perform a 2D real inverse FFT. The first six arguments are as per :func:`scipy.fft.irfft2`; the rest of the arguments are documented in the :ref:`additional argument docs `. ''' threads = _workers_to_threads(workers) return numpy_fft.irfft2(x, s, axes, norm, overwrite_x, planner_effort, threads, auto_align_input, auto_contiguous) @_implements(_fft.rfftn) def rfftn(x, s=None, axes=None, norm=None, overwrite_x=False, workers=None, planner_effort=None, auto_align_input=True, auto_contiguous=True): '''Perform an n-D real FFT. The first six arguments are as per :func:`scipy.fft.rfftn`; the rest of the arguments are documented in the :ref:`additional argument docs`. ''' x = np.asanyarray(x) if x.dtype.kind == 'c': raise TypeError('x must be a real sequence') threads = _workers_to_threads(workers) return numpy_fft.rfftn(x, s, axes, norm, overwrite_x, planner_effort, threads, auto_align_input, auto_contiguous) @_implements(_fft.irfftn) def irfftn(x, s=None, axes=None, norm=None, overwrite_x=False, workers=None, planner_effort=None, auto_align_input=True, auto_contiguous=True): '''Perform an n-D real inverse FFT. The first six arguments are as per :func:`scipy.fft.irfftn`; the rest of the arguments are documented in the :ref:`additional argument docs`. ''' threads = _workers_to_threads(workers) return numpy_fft.irfftn(x, s, axes, norm, overwrite_x, planner_effort, threads, auto_align_input, auto_contiguous) @_implements(_fft.hfft) def hfft(x, n=None, axis=-1, norm=None, overwrite_x=False, workers=None, planner_effort=None, auto_align_input=True, auto_contiguous=True): '''Perform a 1D Hermitian FFT. The first six arguments are as per :func:`scipy.fft.hfft`; the rest of the arguments are documented in the :ref:`additional argument docs`. ''' threads = _workers_to_threads(workers) return numpy_fft.hfft(x, n, axis, norm, overwrite_x, planner_effort, threads, auto_align_input, auto_contiguous) @_implements(_fft.ihfft) def ihfft(x, n=None, axis=-1, norm=None, overwrite_x=False, workers=None, planner_effort=None, auto_align_input=True, auto_contiguous=True): '''Perform a 1D Hermitian inverse FFT. The first six arguments are as per :func:`scipy.fft.ihfft`; the rest of the arguments are documented in the :ref:`additional argument docs`. ''' x = np.asanyarray(x) if x.dtype.kind == 'c': raise TypeError('x must be a real sequence') threads = _workers_to_threads(workers) return numpy_fft.ihfft(x, n, axis, norm, overwrite_x, planner_effort, threads, auto_align_input, auto_contiguous) @_implements(_fft.dct) def dct(x, type=2, n=None, axis=-1, norm=None, overwrite_x=False, workers=None, planner_effort=None, auto_align_input=True, auto_contiguous=True): '''Perform a 1D discrete cosine transform. The first seven arguments are as per :func:`scipy.fft.dct`; the rest of the arguments are documented in the :ref:`additional arguments docs`. ''' threads = _workers_to_threads(workers) if norm is None: norm = 'backward' return _dct(x, type=type, n=n, axis=axis, norm=norm, overwrite_x=overwrite_x, planner_effort=planner_effort, threads=threads, auto_align_input=auto_align_input, auto_contiguous=auto_contiguous) @_implements(_fft.idct) def idct(x, type=2, n=None, axis=-1, norm=None, overwrite_x=False, workers=None, planner_effort=None, auto_align_input=True, auto_contiguous=True): '''Perform an inverse 1D discrete cosine transform. The first seven arguments are as per :func:`scipy.fft.idct`; the rest of the arguments are documented in the :ref:`additional arguments docs`. ''' threads = _workers_to_threads(workers) if norm is None: norm = 'backward' return _idct(x, type=type, n=n, axis=axis, norm=norm, overwrite_x=overwrite_x, planner_effort=planner_effort, threads=threads, auto_align_input=auto_align_input, auto_contiguous=auto_contiguous) @_implements(_fft.dst) def dst(x, type=2, n=None, axis=-1, norm=None, overwrite_x=False, workers=None, planner_effort=None, auto_align_input=True, auto_contiguous=True): '''Perform a 1D discrete sine transform. The first seven arguments are as per :func:`scipy.fft.dst`; the rest of the arguments are documented in the :ref:`additional arguments docs`. ''' threads = _workers_to_threads(workers) if norm is None: norm = 'backward' return _dst(x, type=type, n=n, axis=axis, norm=norm, overwrite_x=overwrite_x, planner_effort=planner_effort, threads=threads, auto_align_input=auto_align_input, auto_contiguous=auto_contiguous) @_implements(_fft.idst) def idst(x, type=2, n=None, axis=-1, norm=None, overwrite_x=False, workers=None, planner_effort=None, auto_align_input=True, auto_contiguous=True): '''Perform an inverse 1D discrete sine transform. The first seven arguments are as per :func:`scipy.fft.idst`; the rest of the arguments are documented in the :ref:`additional arguments docs`. ''' threads = _workers_to_threads(workers) if norm is None: norm = 'backward' return _idst(x, type=type, n=n, axis=axis, norm=norm, overwrite_x=overwrite_x, planner_effort=planner_effort, threads=threads, auto_align_input=auto_align_input, auto_contiguous=auto_contiguous) @_implements(_fft.dctn) def dctn(x, type=2, s=None, axes=None, norm=None, overwrite_x=False, workers=None, planner_effort=None, auto_align_input=True, auto_contiguous=True): """Performan a multidimensional Discrete Cosine Transform. The first seven arguments are as per :func:`scipy.fft.dctn`; the rest of the arguments are documented in the :ref:`additional arguments docs`. """ threads = _workers_to_threads(workers) if norm is None: norm = 'backward' return _dctn(x, type=type, shape=s, axes=axes, norm=norm, overwrite_x=overwrite_x, planner_effort=planner_effort, threads=threads, auto_align_input=auto_align_input, auto_contiguous=auto_contiguous) @_implements(_fft.idctn) def idctn(x, type=2, s=None, axes=None, norm=None, overwrite_x=False, workers=None, planner_effort=None, auto_align_input=True, auto_contiguous=True): """Performan a multidimensional inverse Discrete Cosine Transform. The first seven arguments are as per :func:`scipy.fft.idctn`; the rest of the arguments are documented in the :ref:`additional arguments docs`. """ threads = _workers_to_threads(workers) if norm is None: norm = 'backward' return _idctn(x, type=type, shape=s, axes=axes, norm=norm, overwrite_x=overwrite_x, planner_effort=planner_effort, threads=threads, auto_align_input=auto_align_input, auto_contiguous=auto_contiguous) @_implements(_fft.dstn) def dstn(x, type=2, s=None, axes=None, norm=None, overwrite_x=False, workers=None, planner_effort=None, auto_align_input=True, auto_contiguous=True): """Performan a multidimensional Discrete Sine Transform. The first seven arguments are as per :func:`scipy.fft.dstn`; the rest of the arguments are documented in the :ref:`additional arguments docs`. """ threads = _workers_to_threads(workers) if norm is None: norm = 'backward' return _dstn(x, type=type, shape=s, axes=axes, norm=norm, overwrite_x=overwrite_x, planner_effort=planner_effort, threads=threads, auto_align_input=auto_align_input, auto_contiguous=auto_contiguous) @_implements(_fft.idstn) def idstn(x, type=2, s=None, axes=None, norm=None, overwrite_x=False, workers=None, planner_effort=None, auto_align_input=True, auto_contiguous=True): """Performan a multidimensional inverse Discrete Sine Transform. The first seven arguments are as per :func:`scipy.fft.idstn`; the rest of the arguments are documented in the :ref:`additional arguments docs`. """ threads = _workers_to_threads(workers) if norm is None: norm = 'backward' return _idstn(x, type=type, shape=s, axes=axes, norm=norm, overwrite_x=overwrite_x, planner_effort=planner_effort, threads=threads, auto_align_input=auto_align_input, auto_contiguous=auto_contiguous) pyFFTW-0.13.1/pyfftw/interfaces/scipy_fftpack.py000066400000000000000000000776061435600752200216000ustar00rootroot00000000000000#!/usr/bin/env python # # Copyright 2014 Knowledge Economy Developments Ltd # Copyright 2014 David Wells # # Henry Gomersall # heng@kedevelopments.co.uk # # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, this # list of conditions and the following disclaimer. # # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions and the following disclaimer in the documentation # and/or other materials provided with the distribution. # # * Neither the name of the copyright holder nor the names of its contributors # may be used to endorse or promote products derived from this software without # specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. # ''' This module implements those functions that replace aspects of the :mod:`scipy.fftpack` module. This module *provides* the entire documented namespace of :mod:`scipy.fftpack`, but those functions that are not included here are imported directly from :mod:`scipy.fftpack`. The exceptions raised by each of these functions are mostly as per their equivalents in :mod:`scipy.fftpack`, though there are some corner cases in which this may not be true. Some corner (mis)usages of :mod:`scipy.fftpack` may not transfer neatly. For example, using :func:`scipy.fftpack.fft2` with a non 1D array and a 2D `shape` argument will return without exception whereas :func:`pyfftw.interfaces.scipy_fftpack.fft2` will raise a `ValueError`. ''' import itertools as it import math from numbers import Number import operator from . import numpy_fft from ..builders._utils import _default_effort, _default_threads, _cook_nd_args from ._utils import _Xfftn import numpy # Complete the namespace (these are not actually used in this module) from scipy.fftpack import (diff, tilbert, itilbert, hilbert, ihilbert, cs_diff, sc_diff, ss_diff, cc_diff, shift, fftshift, ifftshift, fftfreq, rfftfreq, convolve) # a next_fast_len specific to pyFFTW is used in place of the scipy.fftpack one from ..pyfftw import next_fast_len __all__ = ['fft', 'ifft', 'fftn', 'ifftn', 'rfft', 'irfft', 'fft2', 'ifft2', 'dct', 'idct', 'dst', 'idst', 'diff', 'tilbert', 'itilbert', 'hilbert', 'ihilbert', 'cs_diff', 'sc_diff', 'ss_diff', 'cc_diff', 'shift', 'fftshift', 'ifftshift', 'fftfreq', 'rfftfreq', 'convolve', 'next_fast_len', 'dctn', 'idctn', 'dstn', 'idstn'] def _iterable_of_int(x, name=None): """Convert ``x`` to an iterable sequence of int vendored from scipy.fft._pocketfft.helper Parameters ---------- x : value, or sequence of values, convertible to int name : str, optional Name of the argument being converted, only used in the error message Returns ------- y : ``List[int]`` """ if isinstance(x, Number): x = (x,) try: x = [operator.index(a) for a in x] except TypeError as e: name = name or "value" raise ValueError("{} must be a scalar or iterable of integers" .format(name)) from e return x def _good_shape(x, shape, axes): """Ensure that shape argument is valid for scipy.fftpack scipy.fftpack does not support len(shape) < x.ndim when axes is not given. """ if shape is not None and axes is None: shape = _iterable_of_int(shape, 'shape') if len(shape) != numpy.ndim(x): raise ValueError("when given, axes and shape arguments" " have to be of the same length") return shape def _init_nd_shape_and_axes(x, shape, axes): """Handles shape and axes arguments for nd transforms vendored from scipy.fft._pocketfft.helper """ noshape = shape is None noaxes = axes is None if not noaxes: axes = _iterable_of_int(axes, 'axes') axes = [a + x.ndim if a < 0 else a for a in axes] if any(a >= x.ndim or a < 0 for a in axes): raise ValueError("Shape error: axes exceeds dimensionality of " "input") if len(set(axes)) != len(axes): raise ValueError("Shape error: all axes must be unique") if not noshape: shape = _iterable_of_int(shape, 'shape') if axes and len(axes) != len(shape): raise ValueError("Shape error: when given, axes and shape " " arguments have to be of the same length") if noaxes: if len(shape) > x.ndim: raise ValueError("Shape error: shape requires more axes than " "are present") axes = range(x.ndim - len(shape), x.ndim) shape = [x.shape[a] if s == -1 else s for s, a in zip(shape, axes)] elif noaxes: shape = list(x.shape) axes = range(x.ndim) else: shape = [x.shape[a] for a in axes] if any(s < 1 for s in shape): raise ValueError( "invalid number of data points ({0}) specified".format(shape)) return shape, axes def fft(x, n=None, axis=-1, overwrite_x=False, planner_effort=None, threads=None, auto_align_input=True, auto_contiguous=True): """ Perform an 1D FFT. The first three arguments are as per :func:`scipy.fftpack.fft`; the rest of the arguments are documented in the :ref:`additional argument docs`. Warning: `scipy.fftpack` is considered legacy, new code should use `scipy.fft` instead. """ planner_effort = _default_effort(planner_effort) threads = _default_threads(threads) return numpy_fft.fft(x, n, axis, None, overwrite_x, planner_effort, threads, auto_align_input, auto_contiguous) def ifft(x, n=None, axis=-1, overwrite_x=False, planner_effort=None, threads=None, auto_align_input=True, auto_contiguous=True): """ Perform an 1D inverse FFT. The first three arguments are as per :func:`scipy.fftpack.ifft`; the rest of the arguments are documented in the :ref:`additional argument docs`. Warning: `scipy.fftpack` is considered legacy, new code should use `scipy.fft` instead. """ planner_effort = _default_effort(planner_effort) threads = _default_threads(threads) return numpy_fft.ifft(x, n, axis, None, overwrite_x, planner_effort, threads, auto_align_input, auto_contiguous) def fft2(x, shape=None, axes=(-2, -1), overwrite_x=False, planner_effort=None, threads=None, auto_align_input=True, auto_contiguous=True): """ Perform a 2D FFT. The first three arguments are as per :func:`scipy.fftpack.fft2`; the rest of the arguments are documented in the :ref:`additional argument docs`. Warning: `scipy.fftpack` is considered legacy, new code should use `scipy.fft` instead. """ planner_effort = _default_effort(planner_effort) threads = _default_threads(threads) shape = _good_shape(x, shape, axes) return numpy_fft.fft2(x, shape, axes, None, overwrite_x, planner_effort, threads, auto_align_input, auto_contiguous) def ifft2(x, shape=None, axes=(-2, -1), overwrite_x=False, planner_effort=None, threads=None, auto_align_input=True, auto_contiguous=True): """ Perform a 2D inverse FFT. The first three arguments are as per :func:`scipy.fftpack.ifft2`; the rest of the arguments are documented in the :ref:`additional argument docs `. Warning: `scipy.fftpack` is considered legacy, new code should use `scipy.fft` instead. """ planner_effort = _default_effort(planner_effort) threads = _default_threads(threads) shape = _good_shape(x, shape, axes) return numpy_fft.ifft2(x, shape, axes, None, overwrite_x, planner_effort, threads, auto_align_input, auto_contiguous) def fftn(x, shape=None, axes=None, overwrite_x=False, planner_effort=None, threads=None, auto_align_input=True, auto_contiguous=True): """ Perform an nD FFT. The first three arguments are as per :func:`scipy.fftpack.fftn`; the rest of the arguments are documented in the :ref:`additional argument docs`. Warning: `scipy.fftpack` is considered legacy, new code should use `scipy.fft` instead. """ shape = _good_shape(x, shape, axes) planner_effort = _default_effort(planner_effort) threads = _default_threads(threads) return numpy_fft.fftn(x, shape, axes, None, overwrite_x, planner_effort, threads, auto_align_input, auto_contiguous) def ifftn(x, shape=None, axes=None, overwrite_x=False, planner_effort=None, threads=None, auto_align_input=True, auto_contiguous=True): """ Perform an nD inverse FFT. The first three arguments are as per :func:`scipy.fftpack.ifftn`; the rest of the arguments are documented in the :ref:`additional argument docs`. Warning: `scipy.fftpack` is considered legacy, new code should use `scipy.fft` instead. """ planner_effort = _default_effort(planner_effort) threads = _default_threads(threads) shape = _good_shape(x, shape, axes) return numpy_fft.ifftn(x, shape, axes, None, overwrite_x, planner_effort, threads, auto_align_input, auto_contiguous) def _complex_to_rfft_output(complex_output, output_shape, axis): '''Convert the complex output from pyfftw to the real output expected from :func:`scipy.fftpack.rfft`. ''' rfft_output = numpy.empty(output_shape, dtype=complex_output.real.dtype) source_slicer = [slice(None)] * complex_output.ndim target_slicer = [slice(None)] * complex_output.ndim # First element source_slicer[axis] = slice(0, 1) target_slicer[axis] = slice(0, 1) rfft_output[tuple(target_slicer)] = complex_output[tuple(source_slicer)].real # Real part source_slicer[axis] = slice(1, None) target_slicer[axis] = slice(1, None, 2) rfft_output[tuple(target_slicer)] = complex_output[tuple(source_slicer)].real # Imaginary part if output_shape[axis] % 2 == 0: end_val = -1 else: end_val = None source_slicer[axis] = slice(1, end_val, None) target_slicer[axis] = slice(2, None, 2) rfft_output[tuple(target_slicer)] = complex_output[tuple(source_slicer)].imag return rfft_output def _irfft_input_to_complex(irfft_input, axis): '''Convert the expected real input to :func:`scipy.fftpack.irfft` to the complex input needed by pyfftw. ''' complex_dtype = numpy.result_type(irfft_input, 1j) input_shape = list(irfft_input.shape) input_shape[axis] = input_shape[axis]//2 + 1 complex_input = numpy.empty(input_shape, dtype=complex_dtype) source_slicer = [slice(None)] * len(input_shape) target_slicer = [slice(None)] * len(input_shape) # First element source_slicer[axis] = slice(0, 1) target_slicer[axis] = slice(0, 1) complex_input[tuple(target_slicer)] = irfft_input[tuple(source_slicer)] # Real part source_slicer[axis] = slice(1, None, 2) target_slicer[axis] = slice(1, None) complex_input[tuple(target_slicer)].real = irfft_input[tuple(source_slicer)] # Imaginary part if irfft_input.shape[axis] % 2 == 0: end_val = -1 target_slicer[axis] = slice(-1, None) complex_input[tuple(target_slicer)].imag = 0.0 else: end_val = None source_slicer[axis] = slice(2, None, 2) target_slicer[axis] = slice(1, end_val) complex_input[tuple(target_slicer)].imag = irfft_input[tuple(source_slicer)] return complex_input def rfft(x, n=None, axis=-1, overwrite_x=False, planner_effort=None, threads=None, auto_align_input=True, auto_contiguous=True): """ Perform an 1D real FFT. The first three arguments are as per :func:`scipy.fftpack.rfft`; the rest of the arguments are documented in the :ref:`additional argument docs`. Warning: `scipy.fftpack` is considered legacy, new code should use `scipy.fft` instead. """ if not numpy.isrealobj(x): raise TypeError('Input array must be real to maintain ' 'compatibility with scipy.fftpack.rfft.') x = numpy.asanyarray(x) planner_effort = _default_effort(planner_effort) threads = _default_threads(threads) complex_output = numpy_fft.rfft(x, n, axis, None, overwrite_x, planner_effort, threads, auto_align_input, auto_contiguous) output_shape = list(x.shape) if n is not None: output_shape[axis] = n return _complex_to_rfft_output(complex_output, output_shape, axis) def irfft(x, n=None, axis=-1, overwrite_x=False, planner_effort=None, threads=None, auto_align_input=True, auto_contiguous=True): """ Perform an 1D inverse real FFT. The first three arguments are as per :func:`scipy.fftpack.irfft`; the rest of the arguments are documented in the :ref:`additional argument docs`. Warning: `scipy.fftpack` is considered legacy, new code should use `scipy.fft` instead. """ if not numpy.isrealobj(x): raise TypeError('Input array must be real to maintain ' 'compatibility with scipy.fftpack.irfft.') x = numpy.asanyarray(x) planner_effort = _default_effort(planner_effort) threads = _default_threads(threads) if n is None: n = x.shape[axis] complex_input = _irfft_input_to_complex(x, axis) return numpy_fft.irfft(complex_input, n, axis, None, overwrite_x, planner_effort, threads, auto_align_input, auto_contiguous) # why map None:None, and not None:"forward"? # if I change it to map None:"forward" some `scipy_interface` tests fail. # see also the if-clauses in the sine/cosine transforms in `scipy_fft.py`; # there's some mishandling of norm=None somewhere. _swap_norm_dictionary = {"backward": "forward", None: None, "ortho": "ortho", "forward": "backward"} _swap_type_dictionary = {1: 1, 2: 3, 3: 2, 4: 4} def _swap_norm_direction(norm): try: return _swap_norm_dictionary[norm] except KeyError: raise ValueError(f'Invalid norm value {norm}; should be "backward", ' '"ortho" or "forward".') def _swap_type_direction(type): try: return _swap_type_dictionary[type] except KeyError: raise ValueError(f'Invalid type value {type}') def _dct(x, type=2, n=None, axis=-1, norm=None, overwrite_x=False, planner_effort=None, threads=None, auto_align_input=True, auto_contiguous=True): """ Private function used for the 1D discrete cosine transforms. It's used by both the `scipy_fftpack` and the `scipy_fft` interfaces, which expose public wrappers of this function. """ if not numpy.isrealobj(x): raise TypeError("1st argument must be real sequence") x = numpy.asanyarray(x) if n is None: n = x.shape[axis] elif n != x.shape[axis]: raise NotImplementedError("Padding/truncating not yet implemented") if norm not in [None, 'forward', 'backward', 'ortho']: raise ValueError(f'Invalid norm value {norm}; should be "backward", ' '"ortho" or "forward".') if norm == 'ortho': if type == 1: x = numpy.copy(x) sp = list(it.repeat(slice(None), len(x.shape))) sp[axis] = 0 x[tuple(sp)] *= math.sqrt(2) sp[axis] = -1 x[tuple(sp)] *= math.sqrt(2) elif type == 3: x = numpy.copy(x) sp = list(it.repeat(slice(None), len(x.shape))) sp[axis] = 0 x[tuple(sp)] /= math.sqrt(x.shape[axis]) sp[axis] = slice(1, None, None) x[tuple(sp)] /= math.sqrt(2*x.shape[axis]) type_flag_lookup = { 1: 'FFTW_REDFT00', 2: 'FFTW_REDFT10', 3: 'FFTW_REDFT01', 4: 'FFTW_REDFT11', } try: type_flag = type_flag_lookup[type] except KeyError: raise ValueError(f"Invalid type value {type}") calling_func = 'dct' planner_effort = _default_effort(planner_effort) threads = _default_threads(threads) result_unnormalized = _Xfftn(x, n, axis, overwrite_x, planner_effort, threads, auto_align_input, auto_contiguous, calling_func, real_direction_flag=type_flag) if norm is None or norm == 'backward': return result_unnormalized result = result_unnormalized if norm == 'ortho': if type == 1: sp = list(it.repeat(slice(None), len(x.shape))) sf_ends = 1 / math.sqrt(2) sp[axis] = 0 result[tuple(sp)] *= sf_ends sp[axis] = -1 result[tuple(sp)] *= sf_ends result *= 1 / math.sqrt(2 * (x.shape[axis] - 1)) elif type == 2: sp = list(it.repeat(slice(None), len(x.shape))) sp[axis] = 0 result[tuple(sp)] /= math.sqrt(2) result *= 1 / math.sqrt(2 * x.shape[axis]) elif type == 4: result *= 1 / math.sqrt(2 * x.shape[axis]) elif norm == 'forward': if type == 1: result *= 1 / (2 * (x.shape[axis] - 1)) else: result *= 1 / (2 * x.shape[axis]) return result def _idct(x, type=2, n=None, axis=-1, norm=None, overwrite_x=False, planner_effort=None, threads=None, auto_align_input=True, auto_contiguous=True): """ Private function used for the 1D inverse discrete cosine transforms. It's used by both the `scipy_fftpack` and the `scipy_fft` interfaces, which expose public wrappers of this function. """ inverse_type = _swap_type_direction(type) new_norm = _swap_norm_direction(norm) planner_effort = _default_effort(planner_effort) threads = _default_threads(threads) return _dct(x, n=n, axis=axis, norm=new_norm, overwrite_x=overwrite_x, type=inverse_type, planner_effort=planner_effort, threads=threads, auto_align_input=auto_align_input, auto_contiguous=auto_contiguous) def _dst(x, type=2, n=None, axis=-1, norm=None, overwrite_x=False, planner_effort=None, threads=None, auto_align_input=True, auto_contiguous=True): """ Private function used for the 1D discrete sine transforms. It's used by both the `scipy_fftpack` and the `scipy_fft` interfaces, which expose public wrappers of this function. """ if not numpy.isrealobj(x): raise TypeError("1st argument must be real sequence") x = numpy.asanyarray(x) if n is None: n = x.shape[axis] elif n != x.shape[axis]: raise NotImplementedError("Padding/truncating not yet implemented") if norm not in [None, 'forward', 'backward', 'ortho']: raise ValueError(f'Invalid norm value {norm}; should be "backward", ' '"ortho" or "forward".') if type == 3 and norm == 'ortho': x = numpy.copy(x) sp = list(it.repeat(Ellipsis, len(x.shape))) sp[axis] = 0 x[tuple(sp)] /= math.sqrt(x.shape[axis]) sp[axis] = slice(1, None, None) x[tuple(sp)] /= math.sqrt(2*x.shape[axis]) type_flag_lookup = { 1: 'FFTW_RODFT00', 2: 'FFTW_RODFT10', 3: 'FFTW_RODFT01', 4: 'FFTW_RODFT11', } try: type_flag = type_flag_lookup[type] except KeyError: raise ValueError(f"Invalid type value {type}") calling_func = 'dst' planner_effort = _default_effort(planner_effort) threads = _default_threads(threads) result_unnormalized = _Xfftn(x, n, axis, overwrite_x, planner_effort, threads, auto_align_input, auto_contiguous, calling_func, real_direction_flag=type_flag) if norm is None or norm == 'backward': return result_unnormalized result = result_unnormalized if norm == 'ortho': if type == 1: result *= 1 / math.sqrt(2 * (x.shape[axis] + 1)) elif type == 2: sp = list(it.repeat(Ellipsis, len(x.shape))) sp[axis] = 0 result[tuple(sp)] *= 1 / math.sqrt(2) result *= 1 / math.sqrt(2 * x.shape[axis]) elif type == 4: result *= 1 / math.sqrt(2 * x.shape[axis]) elif norm == 'forward': if type == 1: result *= 1 / (2 * (x.shape[axis] + 1)) else: result *= 1 / (2 * x.shape[axis]) return result def _idst(x, type=2, n=None, axis=-1, norm=None, overwrite_x=False, planner_effort=None, threads=None, auto_align_input=True, auto_contiguous=True): """ Private function used for the 1D inverse discrete cosine transforms. It's used by both the `scipy_fftpack` and the `scipy_fft` interfaces, which expose public wrappers of this function. """ inverse_type = _swap_type_direction(type) new_norm = _swap_norm_direction(norm) planner_effort = _default_effort(planner_effort) threads = _default_threads(threads) return _dst(x, n=n, axis=axis, norm=new_norm, overwrite_x=overwrite_x, type=inverse_type, planner_effort=planner_effort, threads=threads, auto_align_input=auto_align_input, auto_contiguous=auto_contiguous) def _dctn(x, type=2, shape=None, axes=None, norm=None, overwrite_x=False, planner_effort=None, threads=None, auto_align_input=True, auto_contiguous=True): """ Private function used for the nD discrete cosine transforms. It's used by both the `scipy_fftpack` and the `scipy_fft` interfaces, which expose public wrappers of this function. """ x = numpy.asanyarray(x) shape, axes = _init_nd_shape_and_axes(x, shape, axes) for n, ax in zip(shape, axes): x = _dct(x, type=type, n=n, axis=ax, norm=norm, overwrite_x=overwrite_x, planner_effort=planner_effort, threads=threads, auto_align_input=auto_align_input, auto_contiguous=auto_contiguous) return x def _idctn(x, type=2, shape=None, axes=None, norm=None, overwrite_x=False, planner_effort=None, threads=None, auto_align_input=True, auto_contiguous=True): """ Private function used for the nD inverse discrete cosine transforms. It's used by both the `scipy_fftpack` and the `scipy_fft` interfaces, which expose public wrappers of this function. """ x = numpy.asanyarray(x) shape, axes = _init_nd_shape_and_axes(x, shape, axes) for n, ax in zip(shape, axes): x = _idct(x, type=type, n=n, axis=ax, norm=norm, overwrite_x=overwrite_x, planner_effort=planner_effort, threads=threads, auto_align_input=auto_align_input, auto_contiguous=auto_contiguous) return x def _dstn(x, type=2, shape=None, axes=None, norm=None, overwrite_x=False, planner_effort=None, threads=None, auto_align_input=True, auto_contiguous=True): """ Private function used for the nD discrete sine transforms. It's used by both the `scipy_fftpack` and the `scipy_fft` interfaces, which expose public wrappers of this function. """ x = numpy.asanyarray(x) # if _init_nd_shape_and_axes is not None: shape, axes = _init_nd_shape_and_axes(x, shape, axes) for n, ax in zip(shape, axes): x = _dst(x, type=type, n=n, axis=ax, norm=norm, overwrite_x=overwrite_x, planner_effort=planner_effort, threads=threads, auto_align_input=auto_align_input, auto_contiguous=auto_contiguous) return x def _idstn(x, type=2, shape=None, axes=None, norm=None, overwrite_x=False, planner_effort=None, threads=None, auto_align_input=True, auto_contiguous=True): """ Private function used for the nD inverse discrete sine transforms. It's used by both the `scipy_fftpack` and the `scipy_fft` interfaces, which expose public wrappers of this function. """ x = numpy.asanyarray(x) # if _init_nd_shape_and_axes is not None: shape, axes = _init_nd_shape_and_axes(x, shape, axes) for n, ax in zip(shape, axes): x = _idst(x, type=type, n=n, axis=ax, norm=norm, overwrite_x=overwrite_x, planner_effort=planner_effort, threads=threads, auto_align_input=auto_align_input, auto_contiguous=auto_contiguous) return x def dct(x, type=2, n=None, axis=-1, norm=None, overwrite_x=False, planner_effort=None, threads=None, auto_align_input=True, auto_contiguous=True): """ Perform an 1D discrete cosine transform. The first three arguments are as per :func:`scipy.fftpack.dct`; the rest of the arguments are documented in the :ref:`additional arguments docs`. Warning: `scipy.fftpack` is considered legacy, new code should use `scipy.fft` instead. """ if norm not in [None, 'ortho']: raise ValueError(f'Invalid norm value {norm}; should be None ' 'or "ortho"') return _dct(x, type, n, axis, norm, overwrite_x, planner_effort, threads, auto_align_input, auto_contiguous) def idct(x, type=2, n=None, axis=-1, norm=None, overwrite_x=False, planner_effort=None, threads=None, auto_align_input=True, auto_contiguous=True): """ Perform an 1D inverse discrete cosine transform. The first three arguments are as per :func:`scipy.fftpack.idct`; the rest of the arguments are documented in the :ref:`additional arguments docs`. Warning: `scipy.fftpack` is considered legacy, new code should use `scipy.fft` instead. """ if norm not in [None, 'ortho']: raise ValueError(f'Invalid norm value {norm}; should be None ' 'or "ortho"') return _idct(x, type, n, axis, norm, overwrite_x, planner_effort, threads, auto_align_input, auto_contiguous) def dst(x, type=2, n=None, axis=-1, norm=None, overwrite_x=False, planner_effort=None, threads=None, auto_align_input=True, auto_contiguous=True): """ Perform an 1D discrete sine transform. The first three arguments are as per :func:`scipy.fftpack.dst`; the rest of the arguments are documented in the :ref:`additional arguments docs`. Warning: `scipy.fftpack` is considered legacy, new code should use `scipy.fft` instead. """ if norm not in [None, 'ortho']: raise ValueError(f'Invalid norm value {norm}; should be None ' 'or "ortho"') return _dst(x, type, n, axis, norm, overwrite_x, planner_effort, threads, auto_align_input, auto_contiguous) def idst(x, type=2, n=None, axis=-1, norm=None, overwrite_x=False, planner_effort=None, threads=None, auto_align_input=True, auto_contiguous=True): """ Perform an 1D inverse discrete sine transform. The first three arguments are as per :func:`scipy.fftpack.idst`; the rest of the arguments are documented in the :ref:`additional arguments docs`. Warning: `scipy.fftpack` is considered legacy, new code should use `scipy.fft` instead. """ if norm not in [None, 'ortho']: raise ValueError(f'Invalid norm value {norm}; should be None ' 'or "ortho"') return _idst(x, type, n, axis, norm, overwrite_x, planner_effort, threads, auto_align_input, auto_contiguous) def dctn(x, type=2, shape=None, axes=None, norm=None, overwrite_x=False, planner_effort=None, threads=None, auto_align_input=True, auto_contiguous=True): """ Perform an nD discrete cosine transform. The first six arguments are as per :func:`scipy.fftpack.dctn`; the rest of the arguments are documented in the :ref:`additional arguments docs`. Warning: `scipy.fftpack` is considered legacy, new code should use `scipy.fft` instead. """ shape = _good_shape(x, shape, axes) if norm not in [None, 'ortho']: raise ValueError(f'Invalid norm value {norm}; should be None ' 'or "ortho"') return _dctn(x, type, shape, axes, norm, overwrite_x, planner_effort, threads, auto_align_input, auto_contiguous) def idctn(x, type=2, shape=None, axes=None, norm=None, overwrite_x=False, planner_effort=None, threads=None, auto_align_input=True, auto_contiguous=True): """ Perform an nD inverse discrete cosine transform. The first six arguments are as per :func:`scipy.fftpack.idctn`; the rest of the arguments are documented in the :ref:`additional arguments docs`. Warning: `scipy.fftpack` is considered legacy, new code should use `scipy.fft` instead. """ shape = _good_shape(x, shape, axes) if norm not in [None, 'ortho']: raise ValueError(f'Invalid norm value {norm}; should be None ' 'or "ortho"') return _idctn(x, type, shape, axes, norm, overwrite_x, planner_effort, threads, auto_align_input, auto_contiguous) def dstn(x, type=2, shape=None, axes=None, norm=None, overwrite_x=False, planner_effort=None, threads=None, auto_align_input=True, auto_contiguous=True): """ Perform an nD discrete sine transform. The first six arguments are as per :func:`scipy.fftpack.dstn`; the rest of the arguments are documented in the :ref:`additional arguments docs`. Warning: `scipy.fftpack` is considered legacy, new code should use `scipy.fft` instead. """ shape = _good_shape(x, shape, axes) if norm not in [None, 'ortho']: raise ValueError(f'Invalid norm value {norm}; should be None ' 'or "ortho"') return _dstn(x, type, shape, axes, norm, overwrite_x, planner_effort, threads, auto_align_input, auto_contiguous) def idstn(x, type=2, shape=None, axes=None, norm=None, overwrite_x=False, planner_effort=None, threads=None, auto_align_input=True, auto_contiguous=True): """ Perform an nD inverse discrete sine transform. The first six arguments are as per :func:`scipy.fftpack.idstn`; the rest of the arguments are documented in the :ref:`additional arguments docs`. Warning: `scipy.fftpack` is considered legacy, new code should use `scipy.fft` instead. """ shape = _good_shape(x, shape, axes) if norm not in [None, 'ortho']: raise ValueError(f'Invalid norm value {norm}; should be None ' 'or "ortho"') return _idstn(x, type, shape, axes, norm, overwrite_x, planner_effort, threads, auto_align_input, auto_contiguous) pyFFTW-0.13.1/pyfftw/pyfftw.pxd000066400000000000000000000250111435600752200162710ustar00rootroot00000000000000# Copyright 2014 Knowledge Economy Developments Ltd # # Henry Gomersall # heng@kedevelopments.co.uk # # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, this # list of conditions and the following disclaimer. # # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions and the following disclaimer in the documentation # and/or other materials provided with the distribution. # # * Neither the name of the copyright holder nor the names of its contributors # may be used to endorse or promote products derived from this software without # specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. # cimport numpy as np from libc.stdint cimport int64_t ctypedef struct _fftw_iodim: int _n int _is int _os cdef extern from 'pyfftw_complex.h': ctypedef float cfloat[2] ctypedef double cdouble[2] ctypedef long double clongdouble[2] cdef extern from 'fftw3.h': # Double precision plans ctypedef struct fftw_plan_struct: pass ctypedef fftw_plan_struct *fftw_plan # Single precision plans ctypedef struct fftwf_plan_struct: pass ctypedef fftwf_plan_struct *fftwf_plan # Long double precision plans ctypedef struct fftwl_plan_struct: pass ctypedef fftwl_plan_struct *fftwl_plan # The stride info structure. I think that strictly # speaking, this should be defined with a type suffix # on fftw (ie fftw, fftwf or fftwl), but since the # definition is transparent and is defined as _fftw_iodim, # we ignore the distinction in order to simplify the code. ctypedef struct fftw_iodim: pass # Double precision complex planner fftw_plan fftw_plan_guru_dft( int rank, fftw_iodim *dims, int howmany_rank, fftw_iodim *howmany_dims, cdouble *_in, cdouble *_out, int sign, unsigned flags) nogil # Single precision complex planner fftwf_plan fftwf_plan_guru_dft( int rank, fftw_iodim *dims, int howmany_rank, fftw_iodim *howmany_dims, cfloat *_in, cfloat *_out, int sign, unsigned flags) nogil # Single precision complex planner fftwl_plan fftwl_plan_guru_dft( int rank, fftw_iodim *dims, int howmany_rank, fftw_iodim *howmany_dims, clongdouble *_in, clongdouble *_out, int sign, unsigned flags) nogil # Double precision real to complex planner fftw_plan fftw_plan_guru_dft_r2c( int rank, fftw_iodim *dims, int howmany_rank, fftw_iodim *howmany_dims, double *_in, cdouble *_out, unsigned flags) nogil # Single precision real to complex planner fftwf_plan fftwf_plan_guru_dft_r2c( int rank, fftw_iodim *dims, int howmany_rank, fftw_iodim *howmany_dims, float *_in, cfloat *_out, unsigned flags) nogil # Single precision real to complex planner fftwl_plan fftwl_plan_guru_dft_r2c( int rank, fftw_iodim *dims, int howmany_rank, fftw_iodim *howmany_dims, long double *_in, clongdouble *_out, unsigned flags) nogil # Double precision complex to real planner fftw_plan fftw_plan_guru_dft_c2r( int rank, fftw_iodim *dims, int howmany_rank, fftw_iodim *howmany_dims, cdouble *_in, double *_out, unsigned flags) nogil # Single precision complex to real planner fftwf_plan fftwf_plan_guru_dft_c2r( int rank, fftw_iodim *dims, int howmany_rank, fftw_iodim *howmany_dims, cfloat *_in, float *_out, unsigned flags) nogil # Single precision complex to real planner fftwl_plan fftwl_plan_guru_dft_c2r( int rank, fftw_iodim *dims, int howmany_rank, fftw_iodim *howmany_dims, clongdouble *_in, long double *_out, unsigned flags) nogil # Double precision real planner fftw_plan fftw_plan_guru_r2r( int rank, fftw_iodim *dims, int howmany_rank, fftw_iodim *howmany_dims, double *_in, double *_out, int *kind, unsigned flags) # Single precision real planner fftwf_plan fftwf_plan_guru_r2r( int rank, fftw_iodim *dims, int howmany_rank, fftw_iodim *howmany_dims, float *_in, float *_out, int *kind, unsigned flags) # Long double precision real planner fftwl_plan fftwl_plan_guru_r2r( int rank, fftw_iodim *dims, int howmany_rank, fftw_iodim *howmany_dims, long double *_in, long double *_out, int *kind, unsigned flags) # Double precision complex new array execute void fftw_execute_dft(fftw_plan, cdouble *_in, cdouble *_out) nogil # Single precision complex new array execute void fftwf_execute_dft(fftwf_plan, cfloat *_in, cfloat *_out) nogil # Long double precision complex new array execute void fftwl_execute_dft(fftwl_plan, clongdouble *_in, clongdouble *_out) nogil # Double precision real to complex new array execute void fftw_execute_dft_r2c(fftw_plan, double *_in, cdouble *_out) nogil # Single precision real to complex new array execute void fftwf_execute_dft_r2c(fftwf_plan, float *_in, cfloat *_out) nogil # Long double precision real to complex new array execute void fftwl_execute_dft_r2c(fftwl_plan, long double *_in, clongdouble *_out) nogil # Double precision complex to real new array execute void fftw_execute_dft_c2r(fftw_plan, cdouble *_in, double *_out) nogil # Single precision complex to real new array execute void fftwf_execute_dft_c2r(fftwf_plan, cfloat *_in, float *_out) nogil # Long double precision complex to real new array execute void fftwl_execute_dft_c2r(fftwl_plan, clongdouble *_in, long double *_out) nogil # Double precision real new array execute void fftw_execute_r2r(fftw_plan, double *_in, double *_out) nogil # Single precision real new array execute void fftwf_execute_r2r(fftwf_plan, float *_in, float *_out) nogil # Long double precision real new array execute void fftwl_execute_r2r(fftwl_plan, long double *_in, long double *_out) nogil # Double precision plan destroyer void fftw_destroy_plan(fftw_plan) # Single precision plan destroyer void fftwf_destroy_plan(fftwf_plan) # Long double precision plan destroyer void fftwl_destroy_plan(fftwl_plan) # Double precision set timelimit void fftw_set_timelimit(double seconds) # Single precision set timelimit void fftwf_set_timelimit(double seconds) # Long double precision set timelimit void fftwl_set_timelimit(double seconds) # Threading routines # Double precision void fftw_init_threads() void fftw_plan_with_nthreads(int n) # Single precision void fftwf_init_threads() void fftwf_plan_with_nthreads(int n) # Long double precision void fftwl_init_threads() void fftwl_plan_with_nthreads(int n) # cleanup routines void fftw_cleanup() void fftwf_cleanup() void fftwl_cleanup() void fftw_cleanup_threads() void fftwf_cleanup_threads() void fftwl_cleanup_threads() # wisdom functions void fftw_export_wisdom(void (*write_char)(char c, void *), void *data) void fftwf_export_wisdom(void (*write_char)(char c, void *), void *data) void fftwl_export_wisdom(void (*write_char)(char c, void *), void *data) int fftw_import_wisdom_from_string(char *input_string) int fftwf_import_wisdom_from_string(char *input_string) int fftwl_import_wisdom_from_string(char *input_string) #int fftw_export_wisdom_to_filename(char *filename) #int fftwf_export_wisdom_to_filename(char *filename) #int fftwl_export_wisdom_to_filename(char *filename) # #int fftw_import_wisdom_from_filename(char *filename) #int fftwf_import_wisdom_from_filename(char *filename) #int fftwl_import_wisdom_from_filename(char *filename) void fftw_forget_wisdom() void fftwf_forget_wisdom() void fftwl_forget_wisdom() double FFTW_NO_TIMELIMIT # Define function pointers that can act as a placeholder # for whichever dtype is used (the problem being that fftw # has different function names and signatures for all the # different precisions and dft types). ctypedef void * (*fftw_generic_plan_guru)( int rank, fftw_iodim *dims, int howmany_rank, fftw_iodim *howmany_dims, void *_in, void *_out, int *directions, unsigned flags) nogil ctypedef void (*fftw_generic_execute)(void *_plan, void *_in, void *_out) nogil ctypedef void (*fftw_generic_destroy_plan)(void *_plan) ctypedef void (*fftw_generic_init_threads)() ctypedef void (*fftw_generic_plan_with_nthreads)(int n) ctypedef void (*fftw_generic_set_timelimit)(double seconds) ctypedef bint (*validator)(np.ndarray input_array, np.ndarray output_array, int64_t *axes, int64_t *not_axes, int64_t axes_length) # Direction enum cdef enum: FFTW_FORWARD = -1 FFTW_BACKWARD = 1 # from fftw3.f 3.3.3; may not be valid for different versions of FFTW. FFTW_REDFT00 = 3 FFTW_REDFT01 = 4 FFTW_REDFT10 = 5 FFTW_REDFT11 = 6 FFTW_RODFT00 = 7 FFTW_RODFT01 = 8 FFTW_RODFT10 = 9 FFTW_RODFT11 = 10 # Documented flags cdef enum: FFTW_MEASURE = 0 FFTW_DESTROY_INPUT = 1 FFTW_UNALIGNED = 2 FFTW_CONSERVE_MEMORY = 4 FFTW_EXHAUSTIVE = 8 FFTW_PRESERVE_INPUT = 16 FFTW_PATIENT = 32 FFTW_ESTIMATE = 64 FFTW_WISDOM_ONLY = 2097152 pyFFTW-0.13.1/pyfftw/pyfftw.pyx000066400000000000000000002527521435600752200163340ustar00rootroot00000000000000# cython: language_level=3 # # Copyright 2015 Knowledge Economy Developments Ltd # Copyright 2014 David Wells # Henry Gomersall # heng@kedevelopments.co.uk # # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, this # list of conditions and the following disclaimer. # # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions and the following disclaimer in the documentation # and/or other materials provided with the distribution. # # * Neither the name of the copyright holder nor the names of its contributors # may be used to endorse or promote products derived from this software without # specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. # import numpy as np cimport numpy as np from libc.stdlib cimport calloc, malloc, free from libc.stdint cimport intptr_t, int64_t from libc cimport limits import warnings import threading include 'utils.pxi' cdef extern from *: int Py_AtExit(void (*callback)()) # the total number of types pyfftw can support cdef int _n_types = 3 cdef object _all_types = ['32', '64', 'ld'] _all_types_human_readable = { '32': 'single', '64': 'double', 'ld': 'long double', } _all_types_np = { np.dtype(np.float32): '32', np.dtype(np.float64): '64', } if np.dtype(np.longdouble) != np.dtype(np.float64): _all_types_np[np.dtype(np.longdouble)] = 'ld' # the types supported in this build _supported_types = [] _supported_nptypes_complex = [] _supported_nptypes_real = [] IF HAVE_SINGLE: _supported_types.append('32') _supported_nptypes_complex.append(np.complex64) _supported_nptypes_real.append(np.float32) IF HAVE_DOUBLE: _supported_types.append('64') _supported_nptypes_complex.append(np.complex128) _supported_nptypes_real.append(np.float64) IF HAVE_LONG: _supported_types.append('ld') _supported_nptypes_complex.append(np.clongdouble) _supported_nptypes_real.append(np.longdouble) IF (HAVE_SINGLE_OMP or HAVE_DOUBLE_OMP or HAVE_LONG_OMP): _threading_type = 'OMP' ELIF (HAVE_SINGLE_THREADS or HAVE_DOUBLE_THREADS or HAVE_LONG_THREADS): _threading_type = 'PTHREADS' ELSE: _threading_type = None cdef object directions directions = {'FFTW_FORWARD': FFTW_FORWARD, 'FFTW_BACKWARD': FFTW_BACKWARD, 'FFTW_REDFT00': FFTW_REDFT00, 'FFTW_REDFT10': FFTW_REDFT10, 'FFTW_REDFT01': FFTW_REDFT01, 'FFTW_REDFT11': FFTW_REDFT11, 'FFTW_RODFT00': FFTW_RODFT00, 'FFTW_RODFT10': FFTW_RODFT10, 'FFTW_RODFT01': FFTW_RODFT01, 'FFTW_RODFT11': FFTW_RODFT11} cdef object directions_lookup directions_lookup = {FFTW_FORWARD: 'FFTW_FORWARD', FFTW_BACKWARD: 'FFTW_BACKWARD', FFTW_REDFT00: 'FFTW_REDFT00', FFTW_REDFT10: 'FFTW_REDFT10', FFTW_REDFT01: 'FFTW_REDFT01', FFTW_REDFT11: 'FFTW_REDFT11', FFTW_RODFT00: 'FFTW_RODFT00', FFTW_RODFT10: 'FFTW_RODFT10', FFTW_RODFT01: 'FFTW_RODFT01', FFTW_RODFT11: 'FFTW_RODFT11'} cdef object flag_dict flag_dict = {'FFTW_MEASURE': FFTW_MEASURE, 'FFTW_EXHAUSTIVE': FFTW_EXHAUSTIVE, 'FFTW_PATIENT': FFTW_PATIENT, 'FFTW_ESTIMATE': FFTW_ESTIMATE, 'FFTW_UNALIGNED': FFTW_UNALIGNED, 'FFTW_DESTROY_INPUT': FFTW_DESTROY_INPUT, 'FFTW_WISDOM_ONLY': FFTW_WISDOM_ONLY} _flag_dict = flag_dict.copy() # Need a global lock to protect FFTW planning so that multiple Python threads # do not attempt to plan simultaneously. cdef object plan_lock = threading.Lock() # Function wrappers # ================= # All of these have the same signature as the fftw_generic functions # defined in the .pxd file. The arguments and return values are # cast as required in order to call the actual fftw functions. # # The wrapper function names are simply the fftw names prefixed # with a single underscore. # Planners # ======== # cdef void* _fftw_plan_null( int rank, fftw_iodim *dims, int howmany_rank, fftw_iodim *howmany_dims, void *_in, void *_out, int *direction, unsigned flags): raise RuntimeError("Undefined planner. This is a bug") # Complex double precision IF HAVE_DOUBLE: cdef void* _fftw_plan_guru_dft( int rank, fftw_iodim *dims, int howmany_rank, fftw_iodim *howmany_dims, void *_in, void *_out, int *direction, unsigned flags) nogil: return fftw_plan_guru_dft(rank, dims, howmany_rank, howmany_dims, _in, _out, direction[0], flags) # real to complex double precision cdef void* _fftw_plan_guru_dft_r2c( int rank, fftw_iodim *dims, int howmany_rank, fftw_iodim *howmany_dims, void *_in, void *_out, int *direction, unsigned flags) nogil: return fftw_plan_guru_dft_r2c(rank, dims, howmany_rank, howmany_dims, _in, _out, flags) # complex to real double precision cdef void* _fftw_plan_guru_dft_c2r( int rank, fftw_iodim *dims, int howmany_rank, fftw_iodim *howmany_dims, void *_in, void *_out, int *direction, unsigned flags) nogil: return fftw_plan_guru_dft_c2r(rank, dims, howmany_rank, howmany_dims, _in, _out, flags) # real to real double precision cdef void* _fftw_plan_guru_r2r( int rank, fftw_iodim *dims, int howmany_rank, fftw_iodim *howmany_dims, void *_in, void *_out, int *direction, int flags): return fftw_plan_guru_r2r(rank, dims, howmany_rank, howmany_dims, _in, _out, direction, flags) IF HAVE_SINGLE: # Complex single precision cdef void* _fftwf_plan_guru_dft( int rank, fftw_iodim *dims, int howmany_rank, fftw_iodim *howmany_dims, void *_in, void *_out, int *direction, unsigned flags) nogil: return fftwf_plan_guru_dft(rank, dims, howmany_rank, howmany_dims, _in, _out, direction[0], flags) # real to complex single precision cdef void* _fftwf_plan_guru_dft_r2c( int rank, fftw_iodim *dims, int howmany_rank, fftw_iodim *howmany_dims, void *_in, void *_out, int *direction, unsigned flags) nogil: return fftwf_plan_guru_dft_r2c(rank, dims, howmany_rank, howmany_dims, _in, _out, flags) # complex to real single precision cdef void* _fftwf_plan_guru_dft_c2r( int rank, fftw_iodim *dims, int howmany_rank, fftw_iodim *howmany_dims, void *_in, void *_out, int *direction, unsigned flags) nogil: return fftwf_plan_guru_dft_c2r(rank, dims, howmany_rank, howmany_dims, _in, _out, flags) # real to real single precision cdef void* _fftwf_plan_guru_r2r( int rank, fftw_iodim *dims, int howmany_rank, fftw_iodim *howmany_dims, void *_in, void *_out, int *direction, int flags): return fftwf_plan_guru_r2r(rank, dims, howmany_rank, howmany_dims, _in, _out, direction, flags) IF HAVE_LONG: # Complex long double precision cdef void* _fftwl_plan_guru_dft( int rank, fftw_iodim *dims, int howmany_rank, fftw_iodim *howmany_dims, void *_in, void *_out, int *direction, unsigned flags) nogil: return fftwl_plan_guru_dft(rank, dims, howmany_rank, howmany_dims, _in, _out, direction[0], flags) # real to complex long double precision cdef void* _fftwl_plan_guru_dft_r2c( int rank, fftw_iodim *dims, int howmany_rank, fftw_iodim *howmany_dims, void *_in, void *_out, int *direction, unsigned flags) nogil: return fftwl_plan_guru_dft_r2c(rank, dims, howmany_rank, howmany_dims, _in, _out, flags) # complex to real long double precision cdef void* _fftwl_plan_guru_dft_c2r( int rank, fftw_iodim *dims, int howmany_rank, fftw_iodim *howmany_dims, void *_in, void *_out, int *direction, unsigned flags) nogil: return fftwl_plan_guru_dft_c2r(rank, dims, howmany_rank, howmany_dims, _in, _out, flags) # real to real long double precision cdef void* _fftwl_plan_guru_r2r( int rank, fftw_iodim *dims, int howmany_rank, fftw_iodim *howmany_dims, void *_in, void *_out, int *direction, int flags): return fftwl_plan_guru_r2r(rank, dims, howmany_rank, howmany_dims, _in, _out, direction, flags) # Executors # ========= # cdef void _fftw_execute_null(void *_plan, void *_in, void *_out): raise RuntimeError("Undefined executor. This is a bug") IF HAVE_DOUBLE: # Complex double precision cdef void _fftw_execute_dft(void *_plan, void *_in, void *_out) nogil: fftw_execute_dft(_plan, _in, _out) # real to complex double precision cdef void _fftw_execute_dft_r2c(void *_plan, void *_in, void *_out) nogil: fftw_execute_dft_r2c(_plan, _in, _out) # complex to real double precision cdef void _fftw_execute_dft_c2r(void *_plan, void *_in, void *_out) nogil: fftw_execute_dft_c2r(_plan, _in, _out) IF HAVE_SINGLE: # Complex single precision cdef void _fftwf_execute_dft(void *_plan, void *_in, void *_out) nogil: fftwf_execute_dft(_plan, _in, _out) # real to complex single precision cdef void _fftwf_execute_dft_r2c(void *_plan, void *_in, void *_out) nogil: fftwf_execute_dft_r2c(_plan, _in, _out) # complex to real single precision cdef void _fftwf_execute_dft_c2r(void *_plan, void *_in, void *_out) nogil: fftwf_execute_dft_c2r(_plan, _in, _out) IF HAVE_LONG: # Complex long double precision cdef void _fftwl_execute_dft(void *_plan, void *_in, void *_out) nogil: fftwl_execute_dft(_plan, _in, _out) # real to complex long double precision cdef void _fftwl_execute_dft_r2c(void *_plan, void *_in, void *_out) nogil: fftwl_execute_dft_r2c(_plan, _in, _out) # complex to real long double precision cdef void _fftwl_execute_dft_c2r(void *_plan, void *_in, void *_out) nogil: fftwl_execute_dft_c2r(_plan, _in, _out) # real to real double precision cdef void _fftw_execute_r2r(void *_plan, void *_in, void *_out) nogil: fftw_execute_r2r(_plan, _in, _out) # real to real single precision cdef void _fftwf_execute_r2r(void *_plan, void *_in, void *_out) nogil: fftwf_execute_r2r(_plan, _in, _out) # real to real long double precision cdef void _fftwl_execute_r2r(void *_plan, void *_in, void *_out) nogil: fftwl_execute_r2r(_plan, _in, _out) # Destroyers # ========== # cdef void _fftw_destroy_null(void *plan): raise RuntimeError("Undefined destroy. This is a bug") IF HAVE_DOUBLE: # Double precision cdef void _fftw_destroy_plan(void *_plan): fftw_destroy_plan(_plan) IF HAVE_SINGLE: # Single precision cdef void _fftwf_destroy_plan(void *_plan): fftwf_destroy_plan(_plan) IF HAVE_LONG: # Long double precision cdef void _fftwl_destroy_plan(void *_plan): fftwl_destroy_plan(_plan) # Function lookup tables # ====================== # Planner table (of size the number of planners). cdef fftw_generic_plan_guru planners[12] cdef fftw_generic_plan_guru * _build_planner_list(): for i in range(12): planners[i] = &_fftw_plan_null IF HAVE_DOUBLE: planners[0] = &_fftw_plan_guru_dft planners[3] = &_fftw_plan_guru_dft_r2c planners[6] = &_fftw_plan_guru_dft_c2r planners[9] = &_fftw_plan_guru_r2r IF HAVE_SINGLE: planners[1] = &_fftwf_plan_guru_dft planners[4] = &_fftwf_plan_guru_dft_r2c planners[7] = &_fftwf_plan_guru_dft_c2r planners[10] = &_fftwf_plan_guru_r2r IF HAVE_LONG: planners[2] = &_fftwl_plan_guru_dft planners[5] = &_fftwl_plan_guru_dft_r2c planners[8] = &_fftwl_plan_guru_dft_c2r planners[11] = &_fftwl_plan_guru_r2r # Executor table (of size the number of executors) cdef fftw_generic_execute executors[12] cdef fftw_generic_execute * _build_executor_list(): for i in range(12): executors[i] = &_fftw_execute_null IF HAVE_DOUBLE: executors[0] = &_fftw_execute_dft executors[3] = &_fftw_execute_dft_r2c executors[6] = &_fftw_execute_dft_c2r executors[9] = &_fftw_execute_r2r IF HAVE_SINGLE: executors[1] = &_fftwf_execute_dft executors[4] = &_fftwf_execute_dft_r2c executors[7] = &_fftwf_execute_dft_c2r executors[10] = &_fftwf_execute_r2r IF HAVE_LONG: executors[2] = &_fftwl_execute_dft executors[5] = &_fftwl_execute_dft_r2c executors[8] = &_fftwl_execute_dft_c2r executors[11] = &_fftwl_execute_r2r # Destroyer table (of size the number of destroyers) cdef fftw_generic_destroy_plan destroyers[3] cdef fftw_generic_destroy_plan * _build_destroyer_list(): for i in range(3): destroyers[i] = &_fftw_destroy_null IF HAVE_DOUBLE: destroyers[0] = &_fftw_destroy_plan IF HAVE_SINGLE: destroyers[1] = &_fftwf_destroy_plan IF HAVE_LONG: destroyers[2] = &_fftwl_destroy_plan # nthreads plan setters table cdef fftw_generic_plan_with_nthreads nthreads_plan_setters[3] cdef void _fftw_plan_with_nthreads_null(int n): raise RuntimeError("Undefined plan with nthreads. This is a bug") cdef fftw_generic_plan_with_nthreads * _build_nthreads_plan_setters_list(): for i in range(3): nthreads_plan_setters[i] = ( &_fftw_plan_with_nthreads_null) IF HAVE_DOUBLE_MULTITHREADING: nthreads_plan_setters[0] = ( &fftw_plan_with_nthreads) IF HAVE_SINGLE_MULTITHREADING: nthreads_plan_setters[1] = ( &fftwf_plan_with_nthreads) IF HAVE_LONG_MULTITHREADING: nthreads_plan_setters[2] = ( &fftwl_plan_with_nthreads) # Set planner timelimits cdef fftw_generic_set_timelimit set_timelimit_funcs[3] cdef void _fftw_generic_set_timelimit_null(void *plan): raise RuntimeError("Undefined set timelimit. This is a bug") cdef fftw_generic_set_timelimit * _build_set_timelimit_funcs_list(): for i in range(3): set_timelimit_funcs[i] = ( &_fftw_generic_set_timelimit_null) IF HAVE_DOUBLE: set_timelimit_funcs[0] = ( &fftw_set_timelimit) IF HAVE_SINGLE: set_timelimit_funcs[1] = ( &fftwf_set_timelimit) IF HAVE_LONG: set_timelimit_funcs[2] = ( &fftwl_set_timelimit) # Data validators table cdef validator validators[2] cdef validator * _build_validators_list(): validators[0] = &_validate_r2c_arrays validators[1] = &_validate_c2r_arrays # Validator functions # =================== cdef bint _validate_r2c_arrays(np.ndarray input_array, np.ndarray output_array, int64_t *axes, int64_t *not_axes, int64_t axes_length): ''' Validates the input and output array to check for a valid real to complex transform. ''' # We firstly need to confirm that the dimenions of the arrays # are the same if not (input_array.ndim == output_array.ndim): return False in_shape = input_array.shape out_shape = output_array.shape for n in range(axes_length - 1): if not out_shape[axes[n]] == in_shape[axes[n]]: return False # The critical axis is the last of those over which the # FFT is taken. if not (out_shape[axes[axes_length-1]] == in_shape[axes[axes_length-1]]//2 + 1): return False for n in range(input_array.ndim - axes_length): if not out_shape[not_axes[n]] == in_shape[not_axes[n]]: return False return True cdef bint _validate_c2r_arrays(np.ndarray input_array, np.ndarray output_array, int64_t *axes, int64_t *not_axes, int64_t axes_length): ''' Validates the input and output array to check for a valid complex to real transform. ''' # We firstly need to confirm that the dimenions of the arrays # are the same if not (input_array.ndim == output_array.ndim): return False in_shape = input_array.shape out_shape = output_array.shape for n in range(axes_length - 1): if not in_shape[axes[n]] == out_shape[axes[n]]: return False # The critical axis is the last of those over which the # FFT is taken. if not (in_shape[axes[axes_length-1]] == out_shape[axes[axes_length-1]]//2 + 1): return False for n in range(input_array.ndim - axes_length): if not in_shape[not_axes[n]] == out_shape[not_axes[n]]: return False return True # Shape lookup functions # ====================== def _lookup_shape_r2c_arrays(input_array, output_array): return input_array.shape def _lookup_shape_c2r_arrays(input_array, output_array): return output_array.shape # fftw_schemes is a dictionary with a mapping from a keys, # which are a tuple of the string representation of numpy # dtypes to a scheme name. # # scheme_functions is a dictionary of functions, either # an index to the array of functions in the case of # 'planner', 'executor' and 'generic_precision' or a callable # in the case of 'validator' (generic_precision is a catchall for # functions that only change based on the precision changing - # i.e the prefix fftw, fftwl and fftwf is the only bit that changes). # # The array indices refer to the relevant functions for each scheme, # the tables to which are defined above. # # The 'validator' function is a callable for validating the arrays # that has the following signature: # bool callable(ndarray in_array, ndarray out_array, axes, not_axes) # and checks that the arrays are a valid pair. If it is set to None, # then the default check is applied, which confirms that the arrays # have the same shape. # # The 'fft_shape_lookup' function is a callable for returning the # FFT shape - that is, an array that describes the length of the # fft along each axis. It has the following signature: # fft_shape = fft_shape_lookup(in_array, out_array) # (note that this does not correspond to the lengths of the FFT that is # actually taken, it's the lengths of the FFT that *could* be taken # along each axis. It's necessary because the real FFT has a length # that is different to the length of the input array). cdef object fftw_schemes fftw_schemes = { (np.dtype('complex128'), np.dtype('complex128')): ('c2c', '64'), (np.dtype('complex64'), np.dtype('complex64')): ('c2c', '32'), (np.dtype('float64'), np.dtype('complex128')): ('r2c', '64'), (np.dtype('float32'), np.dtype('complex64')): ('r2c', '32'), (np.dtype('complex128'), np.dtype('float64')): ('c2r', '64'), (np.dtype('complex64'), np.dtype('float32')): ('c2r', '32'), (np.dtype('float32'), np.dtype('float32')): ('r2r', '32'), (np.dtype('float64'), np.dtype('float64')): ('r2r', '64')} cdef object fftw_default_output fftw_default_output = { np.dtype('float32'): np.dtype('complex64'), np.dtype('float64'): np.dtype('complex128'), np.dtype('complex64'): np.dtype('complex64'), np.dtype('complex128'): np.dtype('complex128')} if np.dtype('longdouble') != np.dtype('float64'): fftw_schemes.update({ (np.dtype('clongdouble'), np.dtype('clongdouble')): ('c2c', 'ld'), (np.dtype('longdouble'), np.dtype('clongdouble')): ('r2c', 'ld'), (np.dtype('clongdouble'), np.dtype('longdouble')): ('c2r', 'ld'), (np.dtype('longdouble'), np.dtype('longdouble')): ('r2r', 'ld')}) fftw_default_output.update({ np.dtype('longdouble'): np.dtype('clongdouble'), np.dtype('clongdouble'): np.dtype('clongdouble')}) cdef object scheme_directions scheme_directions = { ('c2c', '64'): ['FFTW_FORWARD', 'FFTW_BACKWARD'], ('c2c', '32'): ['FFTW_FORWARD', 'FFTW_BACKWARD'], ('c2c', 'ld'): ['FFTW_FORWARD', 'FFTW_BACKWARD'], ('r2c', '64'): ['FFTW_FORWARD'], ('r2c', '32'): ['FFTW_FORWARD'], ('r2c', 'ld'): ['FFTW_FORWARD'], ('c2r', '64'): ['FFTW_BACKWARD'], ('c2r', '32'): ['FFTW_BACKWARD'], ('c2r', 'ld'): ['FFTW_BACKWARD'], ('r2r', '64'): ['FFTW_REDFT00', 'FFTW_REDFT10', 'FFTW_REDFT01', 'FFTW_REDFT11', 'FFTW_RODFT00', 'FFTW_RODFT10', 'FFTW_RODFT01', 'FFTW_RODFT11'], ('r2r', '32'): ['FFTW_REDFT00', 'FFTW_REDFT10', 'FFTW_REDFT01', 'FFTW_REDFT11', 'FFTW_RODFT00', 'FFTW_RODFT10', 'FFTW_RODFT01', 'FFTW_RODFT11'], ('r2r', 'ld'): ['FFTW_REDFT00', 'FFTW_REDFT10', 'FFTW_REDFT01', 'FFTW_REDFT11', 'FFTW_RODFT00', 'FFTW_RODFT10', 'FFTW_RODFT01', 'FFTW_RODFT11']} # In the following, -1 denotes using the default. A segfault has been # reported on some systems when this is set to None. It seems # sufficiently trivial to use -1 in place of None, especially given # that scheme_functions is an internal cdef object. cdef object _scheme_functions = {} IF HAVE_DOUBLE: _scheme_functions.update({ ('c2c', '64'): {'planner': 0, 'executor':0, 'generic_precision':0, 'validator': -1, 'fft_shape_lookup': -1}, ('r2c', '64'): {'planner':3, 'executor':3, 'generic_precision':0, 'validator': 0, 'fft_shape_lookup': _lookup_shape_r2c_arrays}, ('c2r', '64'): {'planner':6, 'executor':6, 'generic_precision':0, 'validator': 1, 'fft_shape_lookup': _lookup_shape_c2r_arrays}, ('r2r', '64'): {'planner': 9, 'executor':9, 'generic_precision':0, 'validator': -1, 'fft_shape_lookup': -1}}) IF HAVE_SINGLE: _scheme_functions.update({ ('c2c', '32'): {'planner':1, 'executor':1, 'generic_precision':1, 'validator': -1, 'fft_shape_lookup': -1}, ('r2c', '32'): {'planner':4, 'executor':4, 'generic_precision':1, 'validator': 0, 'fft_shape_lookup': _lookup_shape_r2c_arrays}, ('c2r', '32'): {'planner':7, 'executor':7, 'generic_precision':1, 'validator': 1, 'fft_shape_lookup': _lookup_shape_c2r_arrays}, ('r2r', '32'): {'planner':10, 'executor':10, 'generic_precision':1, 'validator': -1, 'fft_shape_lookup': -1}}) IF HAVE_LONG: _scheme_functions.update({ ('c2c', 'ld'): {'planner':2, 'executor':2, 'generic_precision':2, 'validator': -1, 'fft_shape_lookup': -1}, ('r2c', 'ld'): {'planner':5, 'executor':5, 'generic_precision':2, 'validator': 0, 'fft_shape_lookup': _lookup_shape_r2c_arrays}, ('c2r', 'ld'): {'planner':8, 'executor':8, 'generic_precision':2, 'validator': 1, 'fft_shape_lookup': _lookup_shape_c2r_arrays}, ('r2r', 'ld'): {'planner':11, 'executor':11, 'generic_precision':2, 'validator': -1, 'fft_shape_lookup': -1}}) def scheme_functions(scheme): if scheme in _scheme_functions: return _scheme_functions[scheme] else: msg = "The scheme '%s' is not supported." % str(scheme) if scheme[1] in _all_types: msg += "\nRebuild pyFFTW with support for %s precision!" % \ _all_types_human_readable[scheme[1]] raise NotImplementedError(msg) # Set the cleanup routine cdef void _cleanup(): IF HAVE_DOUBLE: fftw_cleanup() IF HAVE_SINGLE: fftwf_cleanup() IF HAVE_LONG: fftwl_cleanup() IF HAVE_DOUBLE_MULTITHREADING: fftw_cleanup_threads() IF HAVE_SINGLE_MULTITHREADING: fftwf_cleanup_threads() IF HAVE_LONG_MULTITHREADING: fftwl_cleanup_threads() # Initialize the module # Define the functions _build_planner_list() _build_destroyer_list() _build_executor_list() _build_nthreads_plan_setters_list() _build_validators_list() _build_set_timelimit_funcs_list() IF HAVE_DOUBLE_MULTITHREADING: fftw_init_threads() IF HAVE_SINGLE_MULTITHREADING: fftwf_init_threads() IF HAVE_LONG_MULTITHREADING: fftwl_init_threads() Py_AtExit(_cleanup) # Helper functions cdef void make_axes_unique(int64_t *axes, int64_t axes_length, int64_t **unique_axes, int64_t **not_axes, int64_t dimensions, int64_t *unique_axes_length): ''' Takes an array of axes and makes that array unique, returning the unique array in unique_axes. It also creates and fills another array, not_axes, with those axes that are not included in unique_axes. unique_axes_length is updated with the length of unique_axes. dimensions is the number of dimensions to which the axes array might refer. It is the responsibility of the caller to free unique_axes and not_axes. ''' cdef int64_t unique_axes_count = 0 cdef int64_t holding_offset = 0 cdef int64_t *axes_holding = ( calloc(dimensions, sizeof(int64_t))) cdef int64_t *axes_holding_offset = ( calloc(dimensions, sizeof(int64_t))) for n in range(dimensions): axes_holding[n] = -1 # Iterate over all the axes and store each index if it hasn't already # been stored (this keeps one and only one and the first index to axes # i.e. storing the unique set of entries). # # axes_holding_offset holds the shift due to repeated axes for n in range(axes_length): if axes_holding[axes[n]] == -1: axes_holding[axes[n]] = n axes_holding_offset[axes[n]] = holding_offset unique_axes_count += 1 else: holding_offset += 1 unique_axes[0] = malloc( unique_axes_count * sizeof(int64_t)) not_axes[0] = malloc( (dimensions - unique_axes_count) * sizeof(int64_t)) # Now we need to write back the unique axes to a tmp axes cdef int64_t not_axes_count = 0 for n in range(dimensions): if axes_holding[n] != -1: unique_axes[0][axes_holding[n] - axes_holding_offset[n]] = ( axes[axes_holding[n]]) else: not_axes[0][not_axes_count] = n not_axes_count += 1 free(axes_holding) free(axes_holding_offset) unique_axes_length[0] = unique_axes_count return # The External Interface # ====================== # cdef class FFTW: ''' FFTW is a class for computing a variety of discrete Fourier transforms of multidimensional, strided arrays using the FFTW library. The interface is designed to be somewhat pythonic, with the correct transform being inferred from the dtypes of the passed arrays. The exact scheme may be either directly specified with the ``direction`` parameter or inferred from the dtypes and relative shapes of the input arrays. Information on which shapes and dtypes imply which transformations is available in the :ref:`FFTW schemes `. If a match is found, the plan corresponding to that scheme is created, operating on the arrays that are passed in. If no scheme can be created then a ``ValueError`` is raised. The actual transformation is performed by calling the :meth:`~pyfftw.FFTW.execute` method. The arrays can be updated by calling the :meth:`~pyfftw.FFTW.update_arrays` method. The created instance of the class is itself callable, and can perform the execution of the FFT, both with or without array updates, returning the result of the FFT. Unlike calling the :meth:`~pyfftw.FFTW.execute` method, calling the class instance will also optionally normalise the output as necessary. Additionally, calling with an input array update will also coerce that array to be the correct dtype. See the documentation on the :meth:`~pyfftw.FFTW.__call__` method for more information. ''' # Each of these function pointers simply # points to a chosen fftw wrapper function cdef fftw_generic_plan_guru _fftw_planner cdef fftw_generic_execute _fftw_execute cdef fftw_generic_destroy_plan _fftw_destroy cdef fftw_generic_plan_with_nthreads _nthreads_plan_setter # The plan is typecast when it is created or used # within the wrapper functions cdef void *_plan cdef np.ndarray _input_array cdef np.ndarray _output_array cdef int *_direction cdef unsigned _flags cdef bint _simd_allowed cdef int _input_array_alignment cdef int _output_array_alignment cdef bint _use_threads cdef object _input_item_strides cdef object _input_strides cdef object _output_item_strides cdef object _output_strides cdef object _input_shape cdef object _output_shape cdef object _input_dtype cdef object _output_dtype cdef object _flags_used cdef double _normalisation_scaling cdef double _sqrt_normalisation_scaling cdef int _rank cdef _fftw_iodim *_dims cdef int _howmany_rank cdef _fftw_iodim *_howmany_dims cdef int64_t *_axes cdef int64_t *_not_axes cdef int64_t _total_size cdef bint _normalise_idft cdef bint _ortho def _get_N(self): ''' The product of the lengths of the DFT over all DFT axes. 1/N is the normalisation constant. For any input array A, and for any set of axes, 1/N * ifft(fft(A)) = A ''' return self._total_size N = property(_get_N) def _get_simd_aligned(self): ''' Return whether or not this FFTW object requires simd aligned input and output data. ''' return self._simd_allowed simd_aligned = property(_get_simd_aligned) def _get_input_alignment(self): ''' Returns the byte alignment of the input arrays for which the :class:`~pyfftw.FFTW` object was created. Input array updates with arrays that are not aligned on this byte boundary will result in a ValueError being raised, or a copy being made if the :meth:`~pyfftw.FFTW.__call__` interface is used. ''' return self._input_array_alignment input_alignment = property(_get_input_alignment) def _get_output_alignment(self): ''' Returns the byte alignment of the output arrays for which the :class:`~pyfftw.FFTW` object was created. Output array updates with arrays that are not aligned on this byte boundary will result in a ValueError being raised. ''' return self._output_array_alignment output_alignment = property(_get_output_alignment) def _get_flags_used(self): ''' Return which flags were used to construct the FFTW object. This includes flags that were added during initialisation. ''' return tuple(self._flags_used) flags = property(_get_flags_used) def _get_input_array(self): ''' Return the input array that is associated with the FFTW instance. ''' return self._input_array input_array = property(_get_input_array) def _get_output_array(self): ''' Return the output array that is associated with the FFTW instance. ''' return self._output_array output_array = property(_get_output_array) def _get_input_strides(self): ''' Return the strides of the input array for which the FFT is planned. ''' return self._input_strides input_strides = property(_get_input_strides) def _get_output_strides(self): ''' Return the strides of the output array for which the FFT is planned. ''' return self._output_strides output_strides = property(_get_output_strides) def _get_input_shape(self): ''' Return the shape of the input array for which the FFT is planned. ''' return self._input_shape input_shape = property(_get_input_shape) def _get_output_shape(self): ''' Return the shape of the output array for which the FFT is planned. ''' return self._output_shape output_shape = property(_get_output_shape) def _get_input_dtype(self): ''' Return the dtype of the input array for which the FFT is planned. ''' return self._input_dtype input_dtype = property(_get_input_dtype) def _get_output_dtype(self): ''' Return the shape of the output array for which the FFT is planned. ''' return self._output_dtype output_dtype = property(_get_output_dtype) def _get_direction(self): ''' Return the planned FFT direction. Either `'FFTW_FORWARD'`, `'FFTW_BACKWARD'`, or a list of real transform codes of the form `['FFTW_R*DFT**']`. ''' cdef int i transform_directions = list() if self._direction[0] in [FFTW_FORWARD, FFTW_BACKWARD]: # It would be nice to return a length-one list here (so that the # return type is always [str]). This is an annoying type difference, # but is backwards compatible. return directions_lookup[self._direction[0]] else: for i in range(self._rank): transform_directions.append(directions_lookup[ self._direction[i]]) return transform_directions direction = property(_get_direction) def _get_axes(self): ''' Return the axes for the planned FFT in canonical form. That is, as a tuple of positive integers. The order in which they were passed is maintained. ''' axes = [] for i in range(self._rank): axes.append(self._axes[i]) return tuple(axes) axes = property(_get_axes) def _get_normalise_idft(self): ''' If ``normalise_idft=True``, the inverse transform is scaled by 1/N. ''' return self._normalise_idft normalise_idft = property(_get_normalise_idft) def _get_ortho(self): ''' If ``ortho=True`` both the forward and inverse transforms are scaled by 1/sqrt(N). ''' return self._ortho ortho = property(_get_ortho) def __cinit__(self, input_array, output_array, axes=(-1,), direction='FFTW_FORWARD', flags=('FFTW_MEASURE',), unsigned int threads=1, planning_timelimit=None, bint normalise_idft=True, bint ortho=False, *args, **kwargs): if isinstance(direction, str): given_directions = [direction] else: given_directions = list(direction) # Initialise the pointers that need to be freed self._plan = NULL self._dims = NULL self._howmany_dims = NULL self._axes = NULL self._not_axes = NULL self._direction = NULL self._normalise_idft = normalise_idft self._ortho = ortho if self._ortho and self._normalise_idft: raise ValueError('Invalid options: ' 'ortho and normalise_idft cannot both be True.') flags = list(flags) cdef double _planning_timelimit if planning_timelimit is None: _planning_timelimit = FFTW_NO_TIMELIMIT else: try: _planning_timelimit = planning_timelimit except TypeError: raise TypeError('Invalid planning timelimit: ' 'The planning timelimit needs to be a float.') if not isinstance(input_array, np.ndarray): raise ValueError('Invalid input array: ' 'The input array needs to be an instance ' 'of numpy.ndarray') if not isinstance(output_array, np.ndarray): raise ValueError('Invalid output array: ' 'The output array needs to be an instance ' 'of numpy.ndarray') try: input_dtype = input_array.dtype output_dtype = output_array.dtype scheme = fftw_schemes[(input_dtype, output_dtype)] except KeyError: raise ValueError('Invalid scheme: ' 'The output array and input array dtypes ' 'do not correspond to a valid fftw scheme.') self._input_dtype = input_dtype self._output_dtype = output_dtype functions = scheme_functions(scheme) self._fftw_planner = planners[functions['planner']] self._fftw_execute = executors[functions['executor']] self._fftw_destroy = destroyers[functions['generic_precision']] self._nthreads_plan_setter = ( nthreads_plan_setters[functions['generic_precision']]) cdef fftw_generic_set_timelimit set_timelimit_func = ( set_timelimit_funcs[functions['generic_precision']]) # We're interested in the natural alignment on the real type, not # necessarily on the complex type At least one bug was found where # numpy reported an alignment on a complex dtype that was different # to that on the real type. cdef int natural_input_alignment = input_array.real.dtype.alignment cdef int natural_output_alignment = output_array.real.dtype.alignment # If either of the arrays is not aligned on a 16-byte boundary, # we set the FFTW_UNALIGNED flag. This disables SIMD. # (16 bytes is assumed to be the minimal alignment) if 'FFTW_UNALIGNED' in flags: self._simd_allowed = False self._input_array_alignment = natural_input_alignment self._output_array_alignment = natural_output_alignment else: self._input_array_alignment = -1 self._output_array_alignment = -1 for each_alignment in _valid_simd_alignments: if (np.PyArray_DATA(input_array) % each_alignment == 0 and np.PyArray_DATA(output_array) % each_alignment == 0): self._simd_allowed = True self._input_array_alignment = each_alignment self._output_array_alignment = each_alignment break if (self._input_array_alignment == -1 or self._output_array_alignment == -1): self._simd_allowed = False self._input_array_alignment = ( natural_input_alignment) self._output_array_alignment = ( natural_output_alignment) flags.append('FFTW_UNALIGNED') if (not (np.PyArray_DATA(input_array) % self._input_array_alignment == 0)): raise ValueError('Invalid input alignment: ' 'The input array is expected to lie on a %d ' 'byte boundary.' % self._input_array_alignment) if (not (np.PyArray_DATA(output_array) % self._output_array_alignment == 0)): raise ValueError('Invalid output alignment: ' 'The output array is expected to lie on a %d ' 'byte boundary.' % self._output_array_alignment) for direction in given_directions: if direction not in scheme_directions[scheme]: raise ValueError('Invalid direction: ' 'The direction is not valid for the scheme. ' 'Try setting it explicitly if it is not already.') self._direction = malloc(len(axes)*sizeof(int)) real_transforms = True cdef int i if given_directions[0] in ['FFTW_FORWARD', 'FFTW_BACKWARD']: self._direction[0] = directions[given_directions[0]] real_transforms = False else: if len(axes) != len(given_directions): raise ValueError('For real-to-real transforms, there must ' 'be exactly one specified transform for each ' 'transformed axis.') for i in range(len(axes)): if given_directions[0] in ['FFTW_FORWARD', 'FFTW_BACKWARD']: raise ValueError('Heterogeneous transforms cannot be ' 'assigned with \'FFTW_FORWARD\' or ' '\'FFTW_BACKWARD\'.') else: self._direction[i] = directions[given_directions[i]] self._input_shape = input_array.shape self._output_shape = output_array.shape self._input_array = input_array self._output_array = output_array self._axes = malloc(len(axes)*sizeof(int64_t)) for n in range(len(axes)): self._axes[n] = axes[n] # Set the negative entries to their actual index (use the size # of the shape array for this) cdef int64_t array_dimension = len(self._input_shape) for n in range(len(axes)): if self._axes[n] < 0: self._axes[n] = self._axes[n] + array_dimension if self._axes[n] >= array_dimension or self._axes[n] < 0: raise IndexError('Invalid axes: ' 'The axes list cannot contain invalid axes.') cdef int64_t unique_axes_length cdef int64_t *unique_axes cdef int64_t *not_axes make_axes_unique(self._axes, len(axes), &unique_axes, ¬_axes, array_dimension, &unique_axes_length) # and assign axes and not_axes to the filled arrays free(self._axes) self._axes = unique_axes self._not_axes = not_axes total_N = 1 for n in range(unique_axes_length): if self._input_shape[self._axes[n]] == 0: raise ValueError('Zero length array: ' 'The input array should have no zero length' 'axes over which the FFT is to be taken') if real_transforms: if self._direction[n] == FFTW_RODFT00: total_N *= 2*(self._input_shape[self._axes[n]] + 1) elif self._direction[n] == FFTW_REDFT00: if (self._input_shape[self._axes[n]] < 2): raise ValueError('FFTW_REDFT00 (also known as DCT-1) is' ' not defined for inputs of length less than two.') total_N *= 2*(self._input_shape[self._axes[n]] - 1) else: total_N *= 2*self._input_shape[self._axes[n]] else: if self._direction[0] == FFTW_FORWARD: total_N *= self._input_shape[self._axes[n]] else: total_N *= self._output_shape[self._axes[n]] self._total_size = total_N self._normalisation_scaling = 1/float(self.N) self._sqrt_normalisation_scaling = np.sqrt(self._normalisation_scaling) # Now we can validate the array shapes cdef validator _validator if functions['validator'] == -1: if not (output_array.shape == input_array.shape): raise ValueError('Invalid shapes: ' 'The output array should be the same shape as the ' 'input array for the given array dtypes.') else: _validator = validators[functions['validator']] if not _validator(input_array, output_array, self._axes, self._not_axes, unique_axes_length): raise ValueError('Invalid shapes: ' 'The input array and output array are invalid ' 'complementary shapes for their dtypes.') self._rank = unique_axes_length self._howmany_rank = self._input_array.ndim - unique_axes_length self._flags = 0 self._flags_used = [] for each_flag in flags: try: self._flags |= flag_dict[each_flag] self._flags_used.append(each_flag) except KeyError: raise ValueError('Invalid flag: ' + '\'' + each_flag + '\' is not a valid planner flag.') if ('FFTW_DESTROY_INPUT' not in flags) and ( (scheme[0] != 'c2r') or not self._rank > 1): # The default in all possible cases is to preserve the input # This is not possible for r2c arrays with rank > 1 self._flags |= FFTW_PRESERVE_INPUT # Set up the arrays of structs for holding the stride shape # information self._dims = <_fftw_iodim *>malloc( self._rank * sizeof(_fftw_iodim)) self._howmany_dims = <_fftw_iodim *>malloc( self._howmany_rank * sizeof(_fftw_iodim)) if self._dims == NULL or self._howmany_dims == NULL: # Not much else to do than raise an exception raise MemoryError # Find the strides for all the axes of both arrays in terms of the # number of items (as opposed to the number of bytes). self._input_strides = input_array.strides self._input_item_strides = tuple([stride/input_array.itemsize for stride in input_array.strides]) self._output_strides = output_array.strides self._output_item_strides = tuple([stride/output_array.itemsize for stride in output_array.strides]) # Make sure that the arrays are not too big for fftw # This is hard to test, so we cross our fingers and hope for the # best (any suggestions, please get in touch). for i in range(0, len(self._input_shape)): if self._input_shape[i] >= limits.INT_MAX: raise ValueError('Dimensions of the input array must be ' + 'less than ', str(limits.INT_MAX)) if self._input_item_strides[i] >= limits.INT_MAX: raise ValueError('Strides of the input array must be ' + 'less than ', str(limits.INT_MAX)) for i in range(0, len(self._output_shape)): if self._output_shape[i] >= limits.INT_MAX: raise ValueError('Dimensions of the output array must be ' + 'less than ', str(limits.INT_MAX)) if self._output_item_strides[i] >= limits.INT_MAX: raise ValueError('Strides of the output array must be ' + 'less than ', str(limits.INT_MAX)) fft_shape_lookup = functions['fft_shape_lookup'] if fft_shape_lookup == -1: fft_shape = self._input_shape else: fft_shape = fft_shape_lookup(input_array, output_array) # Fill in the stride and shape information input_strides_array = self._input_item_strides output_strides_array = self._output_item_strides for i in range(0, self._rank): self._dims[i]._n = fft_shape[self._axes[i]] self._dims[i]._is = input_strides_array[self._axes[i]] self._dims[i]._os = output_strides_array[self._axes[i]] for i in range(0, self._howmany_rank): self._howmany_dims[i]._n = fft_shape[self._not_axes[i]] self._howmany_dims[i]._is = input_strides_array[self._not_axes[i]] self._howmany_dims[i]._os = output_strides_array[self._not_axes[i]] # parallel execution self._use_threads = (threads > 1) ## Point at which FFTW calls are made ## (and none should be made before this) # noop if threads library not available self._nthreads_plan_setter(threads) # Set the timelimit set_timelimit_func(_planning_timelimit) # Finally, construct the plan, after acquiring the global planner lock # (so that only one python thread can plan at a time, as the FFTW # planning functions are not thread-safe) # no self-lookups allowed in nogil block, so must grab all these first cdef void *plan cdef fftw_generic_plan_guru fftw_planner = self._fftw_planner cdef int rank = self._rank cdef fftw_iodim *dims = self._dims cdef int howmany_rank = self._howmany_rank cdef fftw_iodim *howmany_dims = self._howmany_dims cdef void *_in = np.PyArray_DATA(self._input_array) cdef void *_out = np.PyArray_DATA(self._output_array) cdef unsigned c_flags = self._flags with plan_lock, nogil: plan = fftw_planner(rank, dims, howmany_rank, howmany_dims, _in, _out, self._direction, c_flags) self._plan = plan if self._plan == NULL: if 'FFTW_WISDOM_ONLY' in flags: raise RuntimeError('No FFTW wisdom is known for this plan.') else: raise RuntimeError('The data has an uncaught error that led '+ 'to the planner returning NULL. This is a bug.') def __init__(self, input_array, output_array, axes=(-1,), direction='FFTW_FORWARD', flags=('FFTW_MEASURE',), int threads=1, planning_timelimit=None, normalise_idft=True, ortho=False): ''' **Arguments**: * ``input_array`` and ``output_array`` should be numpy arrays. The contents of these arrays will be destroyed by the planning process during initialisation. Information on supported dtypes for the arrays is :ref:`given below `. * ``axes`` describes along which axes the DFT should be taken. This should be a valid list of axes. Repeated axes are only transformed once. Invalid axes will raise an ``IndexError`` exception. This argument is equivalent to the same argument in :func:`numpy.fft.fftn`, except for the fact that the behaviour of repeated axes is different (``numpy.fft`` will happily take the fft of the same axis if it is repeated in the ``axes`` argument). Rudimentary testing has suggested this is down to the underlying FFTW library and so unlikely to be fixed in these wrappers. * The ``direction`` parameter describes what sort of transformation the object should compute. This parameter is poorly named for historical reasons: older versions of pyFFTW only supported forward and backward transformations, for which this name made sense. Since then pyFFTW has been expanded to support real to real transforms as well and the name is not quite as descriptive. ``direction`` should either be a string, or, in the case of multiple real transforms, a list of strings. The two values corresponding to the DFT are * ``'FFTW_FORWARD'``, which is the forward discrete Fourier transform, and * ``'FFTW_BACKWARD'``, which is the backward discrete Fourier transform. Note that, for the two above options, only the Complex schemes allow a free choice for ``direction``. The direction *must* agree with the the :ref:`table below ` if a Real scheme is used, otherwise a ``ValueError`` is raised. Alternatively, if you are interested in one of the real to real transforms, then pyFFTW supports four different discrete cosine transforms: * ``'FFTW_REDFT00'``, * ``'FFTW_REDFT01'``, * ``'FFTW_REDFT10'``, and * ``'FFTW_REDFT01'``, and four discrete sine transforms: * ``'FFTW_RODFT00'``, * ``'FFTW_RODFT01'``, * ``'FFTW_RODFT10'``, and * ``'FFTW_RODFT01'``. pyFFTW uses the same naming convention for these flags as FFTW: the ``'REDFT'`` part of the name is an acronym for 'real even discrete Fourier transform, and, similarly, ``'RODFT'`` stands for 'real odd discrete Fourier transform'. The trailing ``'0'`` is notation for even data (in terms of symmetry) and the trailing ``'1'`` is for odd data. Unlike the plain discrete Fourier transform, one may specify a different real to real transformation over each axis: for example, .. code-block:: none a = pyfftw.empty_aligned((128,128,128)) b = pyfftw.empty_aligned((128,128,128)) directions = ['FFTW_REDFT00', 'FFTW_RODFT11'] transform = pyfftw.FFTW(a, b, axes=(0, 2), direction=directions) will create a transformation across the first and last axes with a discrete cosine transform over the first and a discrete sine transform over the last. Unfortunately, since this class is ultimately just a wrapper for various transforms implemented in FFTW, one cannot combine real transformations with real to complex transformations in a single object. .. _FFTW_flags: * ``flags`` is a list of strings and is a subset of the flags that FFTW allows for the planners: * ``'FFTW_ESTIMATE'``, ``'FFTW_MEASURE'``, ``'FFTW_PATIENT'`` and ``'FFTW_EXHAUSTIVE'`` are supported. These describe the increasing amount of effort spent during the planning stage to create the fastest possible transform. Usually ``'FFTW_MEASURE'`` is a good compromise. If no flag is passed, the default ``'FFTW_MEASURE'`` is used. * ``'FFTW_UNALIGNED'`` is supported. This tells FFTW not to assume anything about the alignment of the data and disabling any SIMD capability (see below). * ``'FFTW_DESTROY_INPUT'`` is supported. This tells FFTW that the input array can be destroyed during the transform, sometimes allowing a faster algorithm to be used. The default behaviour is, if possible, to preserve the input. In the case of the 1D Backwards Real transform, this may result in a performance hit. In the case of a backwards real transform for greater than one dimension, it is not possible to preserve the input, making this flag implicit in that case. A little more on this is given :ref:`below`. * ``'FFTW_WISDOM_ONLY'`` is supported. This tells FFTW to raise an error if no plan for this transform and data type is already in the wisdom. It thus provides a method to determine whether planning would require additional effort or the cached wisdom can be used. This flag should be combined with the various planning-effort flags (``'FFTW_ESTIMATE'``, ``'FFTW_MEASURE'``, etc.); if so, then an error will be raised if wisdom derived from that level of planning effort (or higher) is not present. If no planning-effort flag is used, the default of ``'FFTW_ESTIMATE'`` is assumed. Note that wisdom is specific to all the parameters, including the data alignment. That is, if wisdom was generated with input/output arrays with one specific alignment, using ``'FFTW_WISDOM_ONLY'`` to create a plan for arrays with any different alignment will cause the ``'FFTW_WISDOM_ONLY'`` planning to fail. Thus it is important to specifically control the data alignment to make the best use of ``'FFTW_WISDOM_ONLY'``. The `FFTW planner flags documentation `_ has more information about the various flags and their impact. Note that only the flags documented here are supported. * ``threads`` tells the wrapper how many threads to use when invoking FFTW, with a default of 1. If the number of threads is greater than 1, then the GIL is released by necessity. * ``planning_timelimit`` is a floating point number that indicates to the underlying FFTW planner the maximum number of seconds it should spend planning the FFT. This is a rough estimate and corresponds to calling of ``fftw_set_timelimit()`` (or an equivalent dependent on type) in the underlying FFTW library. If ``None`` is set, the planner will run indefinitely until all the planning modes allowed by the flags have been tried. See the `FFTW planner flags page `_ for more information on this. .. _fftw_schemes: **Schemes** The currently supported full (so not discrete sine or discrete cosine) DFT schemes are as follows: .. _scheme_table: +----------------+-----------------------+------------------------+-----------+ | Type | ``input_array.dtype`` | ``output_array.dtype`` | Direction | +================+=======================+========================+===========+ | Complex | ``complex64`` | ``complex64`` | Both | +----------------+-----------------------+------------------------+-----------+ | Complex | ``complex128`` | ``complex128`` | Both | +----------------+-----------------------+------------------------+-----------+ | Complex | ``clongdouble`` | ``clongdouble`` | Both | +----------------+-----------------------+------------------------+-----------+ | Real | ``float32`` | ``complex64`` | Forwards | +----------------+-----------------------+------------------------+-----------+ | Real | ``float64`` | ``complex128`` | Forwards | +----------------+-----------------------+------------------------+-----------+ | Real | ``longdouble`` | ``clongdouble`` | Forwards | +----------------+-----------------------+------------------------+-----------+ | Real\ :sup:`1` | ``complex64`` | ``float32`` | Backwards | +----------------+-----------------------+------------------------+-----------+ | Real\ :sup:`1` | ``complex128`` | ``float64`` | Backwards | +----------------+-----------------------+------------------------+-----------+ | Real\ :sup:`1` | ``clongdouble`` | ``longdouble`` | Backwards | +----------------+-----------------------+------------------------+-----------+ \ :sup:`1` Note that the Backwards Real transform for the case in which the dimensionality of the transform is greater than 1 will destroy the input array. This is inherent to FFTW and the only general work-around for this is to copy the array prior to performing the transform. In the case where the dimensionality of the transform is 1, the default is to preserve the input array. This is different from the default in the underlying library, and some speed gain may be achieved by allowing the input array to be destroyed by passing the ``'FFTW_DESTROY_INPUT'`` :ref:`flag `. The discrete sine and discrete cosine transforms are supported for all three real types. ``clongdouble`` typically maps directly to ``complex256`` or ``complex192``, and ``longdouble`` to ``float128`` or ``float96``, dependent on platform. The relative shapes of the arrays should be as follows: * For a Complex transform, ``output_array.shape == input_array.shape`` * For a Real transform in the Forwards direction, both the following should be true: * ``output_array.shape[axes][-1] == input_array.shape[axes][-1]//2 + 1`` * All the other axes should be equal in length. * For a Real transform in the Backwards direction, both the following should be true: * ``input_array.shape[axes][-1] == output_array.shape[axes][-1]//2 + 1`` * All the other axes should be equal in length. In the above expressions for the Real transform, the ``axes`` arguments denotes the unique set of axes on which we are taking the FFT, in the order passed. It is the last of these axes that is subject to the special case shown. The shapes for the real transforms corresponds to those stipulated by the FFTW library. Further information can be found in the FFTW documentation on the `real DFT `_. The actual arrangement in memory is arbitrary and the scheme can be planned for any set of strides on either the input or the output. The user should not have to worry about this and any valid numpy array should work just fine. What is calculated is exactly what FFTW calculates. Notably, this is an unnormalized transform so should be scaled as necessary (fft followed by ifft will scale the input by N, the product of the dimensions along which the DFT is taken). For further information, see the `FFTW documentation `_. The FFTW library benefits greatly from the beginning of each DFT axes being aligned on the correct byte boundary, enabling SIMD instructions. By default, if the data begins on such a boundary, then FFTW will be allowed to try and enable SIMD instructions. This means that all future changes to the data arrays will be checked for similar alignment. SIMD instructions can be explicitly disabled by setting the FFTW_UNALIGNED flags, to allow for updates with unaligned data. :func:`~pyfftw.byte_align` and :func:`~pyfftw.empty_aligned` are two methods included with this module for producing aligned arrays. The optimum alignment for the running platform is provided by :data:`pyfftw.simd_alignment`, though a different alignment may still result in some performance improvement. For example, if the processor supports AVX (requiring 32-byte alignment) as well as SSE (requiring 16-byte alignment), then if the array is 16-byte aligned, SSE will still be used. It's worth noting that just being aligned may not be sufficient to create the fastest possible transform. For example, if the array is not contiguous (i.e. certain axes are displaced in memory), it may be faster to plan a transform for a contiguous array, and then rely on the array being copied in before the transform (which :class:`pyfftw.FFTW` will handle for you when accessed through :meth:`~pyfftw.FFTW.__call__`). ''' def __dealloc__(self): if not self._axes == NULL: free(self._axes) if not self._not_axes == NULL: free(self._not_axes) if not self._plan == NULL: self._fftw_destroy(self._plan) if not self._dims == NULL: free(self._dims) if not self._howmany_dims == NULL: free(self._howmany_dims) if not self._direction == NULL: free(self._direction) def __call__(self, input_array=None, output_array=None, normalise_idft=None, ortho=None): '''__call__(input_array=None, output_array=None, normalise_idft=True, ortho=False) Calling the class instance (optionally) updates the arrays, then calls :meth:`~pyfftw.FFTW.execute`, before optionally normalising the output and returning the output array. It has some built-in helpers to make life simpler for the calling functions (as distinct from manually updating the arrays and calling :meth:`~pyfftw.FFTW.execute`). If ``normalise_idft`` is ``True`` (the default), then the output from an inverse DFT (i.e. when the direction flag is ``'FFTW_BACKWARD'``) is scaled by 1/N, where N is the product of the lengths of input array on which the FFT is taken. If the direction is ``'FFTW_FORWARD'``, this flag makes no difference to the output array. If ``ortho`` is ``True``, then the output of both forward and inverse DFT operations is scaled by 1/sqrt(N), where N is the product of the lengths of input array on which the FFT is taken. This ensures that the DFT is a unitary operation, meaning that it satisfies Parseval's theorem (the sum of the squared values of the transform output is equal to the sum of the squared values of the input). In other words, the energy of the signal is preserved. If either ``normalise_idft`` or ``ortho`` are ``True``, then ifft(fft(A)) = A. When ``input_array`` is something other than None, then the passed in array is coerced to be the same dtype as the input array used when the class was instantiated, the byte-alignment of the passed in array is made consistent with the expected byte-alignment and the striding is made consistent with the expected striding. All this may, but not necessarily, require a copy to be made. As noted in the :ref:`scheme table`, if the FFTW instance describes a backwards real transform of more than one dimension, the contents of the input array will be destroyed. It is up to the calling function to make a copy if it is necessary to maintain the input array. ``output_array`` is always used as-is if possible. If the dtype, the alignment or the striding is incorrect for the FFTW object, then a ``ValueError`` is raised. The coerced input array and the output array (as appropriate) are then passed as arguments to :meth:`~pyfftw.FFTW.update_arrays`, after which :meth:`~pyfftw.FFTW.execute` is called, and then normalisation is applied to the output array if that is desired. Note that it is possible to pass some data structure that can be converted to an array, such as a list, so long as it fits the data requirements of the class instance, such as array shape. Other than the dtype and the alignment of the passed in arrays, the rest of the requirements on the arrays mandated by :meth:`~pyfftw.FFTW.update_arrays` are enforced. A ``None`` argument to either keyword means that that array is not updated. The result of the FFT is returned. This is the same array that is used internally and will be overwritten again on subsequent calls. If you need the data to persist longer than a subsequent call, you should copy the returned array. ''' if ortho is None: ortho = self._ortho if normalise_idft is None: normalise_idft = self._normalise_idft if ortho and normalise_idft: raise ValueError('Invalid options: ortho and normalise_idft cannot' ' both be True.') if input_array is not None or output_array is not None: if input_array is None: input_array = self._input_array if output_array is None: output_array = self._output_array if not isinstance(input_array, np.ndarray): copy_needed = True elif (not input_array.dtype == self._input_dtype): copy_needed = True elif (not input_array.strides == self._input_strides): copy_needed = True elif not (np.PyArray_DATA(input_array) % self.input_alignment == 0): copy_needed = True else: copy_needed = False if copy_needed: if not isinstance(input_array, np.ndarray): input_array = np.asanyarray(input_array) if not input_array.shape == self._input_shape: raise ValueError('Invalid input shape: ' 'The new input array should be the same shape ' 'as the input array used to instantiate the ' 'object.') self._input_array[:] = input_array if output_array is not None: # No point wasting time if no update is necessary # (which the copy above may have avoided) input_array = self._input_array self.update_arrays(input_array, output_array) else: self.update_arrays(input_array, output_array) self.execute() # after executing, optionally normalize output array if ortho: self._output_array *= self._sqrt_normalisation_scaling elif normalise_idft and self._direction[0] == FFTW_BACKWARD: self._output_array *= self._normalisation_scaling elif not normalise_idft and self._direction[0] == FFTW_FORWARD: self._output_array *= self._normalisation_scaling return self._output_array cpdef update_arrays(self, new_input_array, new_output_array): '''update_arrays(new_input_array, new_output_array) Update the arrays upon which the DFT is taken. The new arrays should be of the same dtypes as the originals, the same shapes as the originals and should have the same strides between axes. If the original data was aligned so as to allow SIMD instructions (e.g. by being aligned on a 16-byte boundary), then the new array must also be aligned so as to allow SIMD instructions (assuming, of course, that the ``FFTW_UNALIGNED`` flag was not enabled). The byte alignment requirement extends to requiring natural alignment in the non-SIMD cases as well, but this is much less stringent as it simply means avoiding arrays shifted by, say, a single byte (which invariably takes some effort to achieve!). If all these conditions are not met, a ``ValueError`` will be raised and the data will *not* be updated (though the object will still be in a sane state). ''' if not isinstance(new_input_array, np.ndarray): raise ValueError('Invalid input array: ' 'The new input array needs to be an instance ' 'of numpy.ndarray') if not isinstance(new_output_array, np.ndarray): raise ValueError('Invalid output array ' 'The new output array needs to be an instance ' 'of numpy.ndarray') if not (np.PyArray_DATA(new_input_array) % self.input_alignment == 0): raise ValueError('Invalid input alignment: ' 'The original arrays were %d-byte aligned. It is ' 'necessary that the update input array is similarly ' 'aligned.' % self.input_alignment) if not (np.PyArray_DATA(new_output_array) % self.output_alignment == 0): raise ValueError('Invalid output alignment: ' 'The original arrays were %d-byte aligned. It is ' 'necessary that the update output array is similarly ' 'aligned.' % self.output_alignment) if not new_input_array.dtype == self._input_dtype: raise ValueError('Invalid input dtype: ' 'The new input array is not of the same ' 'dtype as was originally planned for.') if not new_output_array.dtype == self._output_dtype: raise ValueError('Invalid output dtype: ' 'The new output array is not of the same ' 'dtype as was originally planned for.') new_input_shape = new_input_array.shape new_output_shape = new_output_array.shape new_input_strides = new_input_array.strides new_output_strides = new_output_array.strides if not new_input_shape == self._input_shape: raise ValueError('Invalid input shape: ' 'The new input array should be the same shape as ' 'the input array used to instantiate the object.') if not new_output_shape == self._output_shape: raise ValueError('Invalid output shape: ' 'The new output array should be the same shape as ' 'the output array used to instantiate the object.') if not new_input_strides == self._input_strides: raise ValueError('Invalid input striding: ' 'The strides should be identical for the new ' 'input array as for the old.') if not new_output_strides == self._output_strides: raise ValueError('Invalid output striding: ' 'The strides should be identical for the new ' 'output array as for the old.') self._update_arrays(new_input_array, new_output_array) cdef _update_arrays(self, np.ndarray new_input_array, np.ndarray new_output_array): ''' A C interface to the update_arrays method that does not perform any checks on strides being correct and so on. ''' self._input_array = new_input_array self._output_array = new_output_array def get_input_array(self): '''get_input_array() Return the input array that is associated with the FFTW instance. *Deprecated since 0.10. Consider using the* :attr:`FFTW.input_array` *property instead.* ''' warnings.warn('get_input_array is deprecated. ' 'Consider using the input_array property instead.', DeprecationWarning) return self._input_array def get_output_array(self): '''get_output_array() Return the output array that is associated with the FFTW instance. *Deprecated since 0.10. Consider using the* :attr:`FFTW.output_array` *property instead.* ''' warnings.warn('get_output_array is deprecated. ' 'Consider using the output_array property instead.', DeprecationWarning) return self._output_array cpdef execute(self): '''execute() Execute the planned operation, taking the correct kind of FFT of the input array (i.e. :attr:`FFTW.input_array`), and putting the result in the output array (i.e. :attr:`FFTW.output_array`). ''' cdef void *input_pointer = ( np.PyArray_DATA(self._input_array)) cdef void *output_pointer = ( np.PyArray_DATA(self._output_array)) cdef void *plan = self._plan cdef fftw_generic_execute fftw_execute = self._fftw_execute with nogil: fftw_execute(plan, input_pointer, output_pointer) cdef void count_char(char c, void *counter_ptr): ''' On every call, increment the derefenced counter_ptr. ''' (counter_ptr)[0] += 1 cdef void write_char_to_string(char c, void *string_location_ptr): ''' Write the passed character c to the memory location pointed to by the contents of string_location_ptr (i.e. a pointer to a pointer), then increment the contents of string_location_ptr (i.e. move to the next byte in memory). In other words, for every character that is passed, we write that to a string that is referenced by the dereferenced value of string_location_ptr. If the derefenced value of string_location points to an unallocated piece of memory, a segfault will likely occur. ''' cdef char *write_location = ((string_location_ptr)[0]) write_location[0] = c (string_location_ptr)[0] += 1 def export_wisdom(): '''export_wisdom() Return the FFTW wisdom as a tuple of strings. The first string in the tuple is the string for the double precision wisdom, the second is for single precision, and the third for long double precision. If any of the precisions is not supported in the build, the string is empty. The tuple that is returned from this function can be used as the argument to :func:`~pyfftw.import_wisdom`. ''' cdef: # can't directly initialize `bytes` with '' const char* empty = '' bytes py_wisdom = empty bytes py_wisdomf = empty bytes py_wisdoml = empty # default init to zero cdef int counter = 0 cdef int counterf = 0 cdef int counterl = 0 char* c_wisdom = NULL char* c_wisdomf = NULL char* c_wisdoml = NULL # count the length of the string and extract it manually rather than using # `fftw_export_wisdom_to_string` to avoid calling `free` on the string # potentially allocated by a different C library; see #3 IF HAVE_DOUBLE: fftw_export_wisdom(&count_char, &counter) c_wisdom = malloc(sizeof(char)*(counter + 1)) if c_wisdom == NULL: raise MemoryError # Set the pointers to the string pointers cdef intptr_t c_wisdom_ptr = c_wisdom fftw_export_wisdom(&write_char_to_string, &c_wisdom_ptr) # Write the last byte as the null byte c_wisdom[counter] = 0 try: py_wisdom = c_wisdom finally: free(c_wisdom) IF HAVE_SINGLE: fftwf_export_wisdom(&count_char, &counterf) c_wisdomf = malloc(sizeof(char)*(counterf + 1)) if c_wisdomf == NULL: raise MemoryError cdef intptr_t c_wisdomf_ptr = c_wisdomf fftwf_export_wisdom(&write_char_to_string, &c_wisdomf_ptr) c_wisdomf[counterf] = 0 try: py_wisdomf = c_wisdomf finally: free(c_wisdomf) IF HAVE_LONG: fftwl_export_wisdom(&count_char, &counterl) c_wisdoml = malloc(sizeof(char)*(counterl + 1)) if c_wisdoml == NULL: raise MemoryError cdef intptr_t c_wisdoml_ptr = c_wisdoml fftwl_export_wisdom(&write_char_to_string, &c_wisdoml_ptr) c_wisdoml[counterl] = 0 try: py_wisdoml = c_wisdoml finally: free(c_wisdoml) return (py_wisdom, py_wisdomf, py_wisdoml) def import_wisdom(wisdom): '''import_wisdom(wisdom) Function that imports wisdom from the passed tuple of strings. The first string in the tuple is the string for the double precision wisdom. The second string in the tuple is the string for the single precision wisdom. The third string in the tuple is the string for the long double precision wisdom. The tuple that is returned from :func:`~pyfftw.export_wisdom` can be used as the argument to this function. This function returns a tuple of boolean values indicating the success of loading each of the wisdom types (double, float and long double, in that order). ''' cdef: char* c_wisdom = wisdom[0] char* c_wisdomf = wisdom[1] char* c_wisdoml = wisdom[2] bint success = False bint successf = False bint successl = False IF HAVE_DOUBLE: success = fftw_import_wisdom_from_string(c_wisdom) IF HAVE_SINGLE: successf = fftwf_import_wisdom_from_string(c_wisdomf) IF HAVE_LONG: successl = fftwl_import_wisdom_from_string(c_wisdoml) return (success, successf, successl) #def export_wisdom_to_files( # double_wisdom_file=None, # single_wisdom_file=None, # long_double_wisdom_file=None): # '''export_wisdom_to_file(double_wisdom_file=None, single_wisdom_file=None, long_double_wisdom_file=None) # # Export the wisdom to the passed files. # # The double precision wisdom is written to double_wisdom_file. # The single precision wisdom is written to single_wisdom_file. # The long double precision wisdom is written to # long_double_wisdom_file. # # If any of the arguments are None, then nothing is done for that # file. # # This function returns a tuple of boolean values indicating # the success of storing each of the wisdom types (double, float # and long double, in that order). # ''' # cdef bint success = True # cdef bint successf = True # cdef bint successl = True # # cdef char *_double_wisdom_file # cdef char *_single_wisdom_file # cdef char *_long_double_wisdom_file # # # if double_wisdom_file is not None: # _double_wisdom_file = double_wisdom_file # success = fftw_export_wisdom_to_filename(_double_wisdom_file) # # if single_wisdom_file is not None: # _single_wisdom_file = single_wisdom_file # successf = fftwf_export_wisdom_to_filename(_single_wisdom_file) # # if long_double_wisdom_file is not None: # _long_double_wisdom_file = long_double_wisdom_file # successl = fftwl_export_wisdom_to_filename( # _long_double_wisdom_file) # # return (success, successf, successl) # #def import_wisdom_to_files( # double_wisdom_file=None, # single_wisdom_file=None, # long_double_wisdom_file=None): # '''import_wisdom_to_file(double_wisdom_file=None, single_wisdom_file=None, long_double_wisdom_file=None) # # import the wisdom to the passed files. # # The double precision wisdom is imported from double_wisdom_file. # The single precision wisdom is imported from single_wisdom_file. # The long double precision wisdom is imported from # long_double_wisdom_file. # # If any of the arguments are None, then nothing is done for that # file. # # This function returns a tuple of boolean values indicating # the success of loading each of the wisdom types (double, float # and long double, in that order). # ''' # cdef bint success = True # cdef bint successf = True # cdef bint successl = True # # cdef char *_double_wisdom_file # cdef char *_single_wisdom_file # cdef char *_long_double_wisdom_file # # if double_wisdom_file is not None: # _double_wisdom_file = double_wisdom_file # success = fftw_import_wisdom_from_filename(_double_wisdom_file) # # if single_wisdom_file is not None: # _single_wisdom_file = single_wisdom_file # successf = fftwf_import_wisdom_from_filename(_single_wisdom_file) # # if long_double_wisdom_file is not None: # _long_double_wisdom_file = long_double_wisdom_file # successl = fftwl_import_wisdom_from_filename( # _long_double_wisdom_file) # # return (success, successf, successl) def forget_wisdom(): '''forget_wisdom() Forget all the accumulated wisdom. ''' IF HAVE_DOUBLE: fftw_forget_wisdom() IF HAVE_SINGLE: fftwf_forget_wisdom() IF HAVE_LONG: fftwl_forget_wisdom() pyFFTW-0.13.1/pyfftw/utils.pxi000066400000000000000000000411751435600752200161300ustar00rootroot00000000000000# Copyright 2014 Knowledge Economy Developments Ltd # Copyright 2014 David Wells # # Henry Gomersall # heng@kedevelopments.co.uk # David Wells # drwells vt.edu # # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, this # list of conditions and the following disclaimer. # # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions and the following disclaimer in the documentation # and/or other materials provided with the distribution. # # * Neither the name of the copyright holder nor the names of its contributors # may be used to endorse or promote products derived from this software without # specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. # from bisect import bisect_left cimport numpy as np from . cimport cpu from libc.stdint cimport intptr_t import warnings cdef int _simd_alignment = cpu.simd_alignment() #: The optimum SIMD alignment in bytes, found by inspecting the CPU. simd_alignment = _simd_alignment #: A tuple of simd alignments that make sense for this cpu if _simd_alignment == 16: _valid_simd_alignments = (16,) elif _simd_alignment == 32: _valid_simd_alignments = (16, 32) else: _valid_simd_alignments = () cpdef n_byte_align_empty(shape, n, dtype='float64', order='C'): '''n_byte_align_empty(shape, n, dtype='float64', order='C') **This function is deprecated:** ``empty_aligned`` **should be used instead.** Function that returns an empty numpy array that is n-byte aligned. The alignment is given by the first optional argument, ``n``. If ``n`` is not provided then this function will inspect the CPU to determine alignment. The rest of the arguments are as per :func:`numpy.empty`. ''' warnings.warn('This function is deprecated in favour of' '``empty_aligned``.', DeprecationWarning) return empty_aligned(shape, dtype=dtype, order=order, n=n) cpdef n_byte_align(array, n, dtype=None): '''n_byte_align(array, n, dtype=None) **This function is deprecated:** ``byte_align`` **should be used instead.** Function that takes a numpy array and checks it is aligned on an n-byte boundary, where ``n`` is an optional parameter. If ``n`` is not provided then this function will inspect the CPU to determine alignment. If the array is aligned then it is returned without further ado. If it is not aligned then a new array is created and the data copied in, but aligned on the n-byte boundary. ``dtype`` is an optional argument that forces the resultant array to be of that dtype. ''' warnings.warn('This function is deprecated in favour of' '``byte_align``.', DeprecationWarning) return byte_align(array, n=n, dtype=dtype) cpdef byte_align(array, n=None, dtype=None): '''byte_align(array, n=None, dtype=None) Function that takes a numpy array and checks it is aligned on an n-byte boundary, where ``n`` is an optional parameter. If ``n`` is not provided then this function will inspect the CPU to determine alignment. If the array is aligned then it is returned without further ado. If it is not aligned then a new array is created and the data copied in, but aligned on the n-byte boundary. ``dtype`` is an optional argument that forces the resultant array to be of that dtype. ''' if not isinstance(array, np.ndarray): raise TypeError('Invalid array: byte_align requires a subclass ' 'of ndarray') if n is None: n = _simd_alignment if dtype is not None: if not array.dtype == dtype: update_dtype = True else: dtype = array.dtype update_dtype = False # See if we're already n byte aligned. If so, do nothing. offset = np.PyArray_DATA(array) %n if offset is not 0 or update_dtype: _array_aligned = empty_aligned(array.shape, dtype, n=n) _array_aligned[:] = array array = _array_aligned.view(type=array.__class__) return array cpdef is_byte_aligned(array, n=None): ''' is_n_byte_aligned(array, n=None) Function that takes a numpy array and checks it is aligned on an n-byte boundary, where ``n`` is an optional parameter, returning ``True`` if it is, and ``False`` if it is not. If ``n`` is not provided then this function will inspect the CPU to determine alignment. ''' if not isinstance(array, np.ndarray): raise TypeError('Invalid array: is_n_byte_aligned requires a subclass ' 'of ndarray') if n is None: n = _simd_alignment # See if we're n byte aligned. offset = np.PyArray_DATA(array) %n return not bool(offset) cpdef is_n_byte_aligned(array, n): ''' is_n_byte_aligned(array, n) **This function is deprecated:** ``is_byte_aligned`` **should be used instead.** Function that takes a numpy array and checks it is aligned on an n-byte boundary, where ``n`` is a passed parameter, returning ``True`` if it is, and ``False`` if it is not. ''' return is_byte_aligned(array, n=n) cpdef empty_aligned(shape, dtype='float64', order='C', n=None): '''empty_aligned(shape, dtype='float64', order='C', n=None) Function that returns an empty numpy array that is n-byte aligned, where ``n`` is determined by inspecting the CPU if it is not provided. The alignment is given by the final optional argument, ``n``. If ``n`` is not provided then this function will inspect the CPU to determine alignment. The rest of the arguments are as per :func:`numpy.empty`. ''' cdef long long array_length if n is None: n = _simd_alignment itemsize = np.dtype(dtype).itemsize # Apparently there is an issue with numpy.prod wrapping around on 32-bits # on Windows 64-bit. This shouldn't happen, but the following code # alleviates the problem. if not isinstance(shape, (int, np.integer)): array_length = 1 for each_dimension in shape: array_length *= each_dimension else: array_length = shape # Allocate a new array that will contain the aligned data _array_aligned = np.empty(array_length*itemsize+n, dtype='int8') # We now need to know how to offset _array_aligned # so it is correctly aligned _array_aligned_offset = (n-np.PyArray_DATA(_array_aligned))%n array = np.frombuffer( _array_aligned[_array_aligned_offset:_array_aligned_offset-n].data, dtype=dtype).reshape(shape, order=order) return array cpdef zeros_aligned(shape, dtype='float64', order='C', n=None): '''zeros_aligned(shape, dtype='float64', order='C', n=None) Function that returns a numpy array of zeros that is n-byte aligned, where ``n`` is determined by inspecting the CPU if it is not provided. The alignment is given by the final optional argument, ``n``. If ``n`` is not provided then this function will inspect the CPU to determine alignment. The rest of the arguments are as per :func:`numpy.zeros`. ''' array = empty_aligned(shape, dtype=dtype, order=order, n=n) array.fill(0) return array cpdef ones_aligned(shape, dtype='float64', order='C', n=None): '''ones_aligned(shape, dtype='float64', order='C', n=None) Function that returns a numpy array of ones that is n-byte aligned, where ``n`` is determined by inspecting the CPU if it is not provided. The alignment is given by the final optional argument, ``n``. If ``n`` is not provided then this function will inspect the CPU to determine alignment. The rest of the arguments are as per :func:`numpy.ones`. ''' array = empty_aligned(shape, dtype=dtype, order=order, n=n) array.fill(1) return array cpdef next_fast_len(target): '''next_fast_len(target) Find the next fast transform length for FFTW. FFTW has efficient functions for transforms of length 2**a * 3**b * 5**c * 7**d * 11**e * 13**f, where e + f is either 0 or 1. Parameters ---------- target : int Length to start searching from. Must be a positive integer. Returns ------- out : int The first fast length greater than or equal to `target`. Examples -------- On a particular machine, an FFT of prime length takes 2.1 ms: >>> from pyfftw.interfaces import scipy_fftpack >>> min_len = 10007 # prime length is worst case for speed >>> a = numpy.random.randn(min_len) >>> b = scipy_fftpack.fft(a) Zero-padding to the next fast length reduces computation time to 406 us, a speedup of ~5 times: >>> next_fast_len(min_len) 10080 >>> b = scipy_fftpack.fft(a, 10080) Rounding up to the next power of 2 is not optimal, taking 598 us to compute, 1.5 times as long as the size selected by next_fast_len. >>> b = fftpack.fft(a, 16384) Similar speedups will occur for pre-planned FFTs as generated via pyfftw.builders. ''' lpre = (18, 20, 21, 22, 24, 25, 26, 27, 28, 30, 32, 33, 35, 36, 39, 40, 42, 44, 45, 48, 49, 50, 52, 54, 55, 56, 60, 63, 64, 65, 66, 70, 72, 75, 77, 78, 80, 81, 84, 88, 90, 91, 96, 98, 99, 100, 104, 105, 108, 110, 112, 117, 120, 125, 126, 128, 130, 132, 135, 140, 144, 147, 150, 154, 156, 160, 162, 165, 168, 175, 176, 180, 182, 189, 192, 195, 196, 198, 200, 208, 210, 216, 220, 224, 225, 231, 234, 240, 243, 245, 250, 252, 256, 260, 264, 270, 273, 275, 280, 288, 294, 297, 300, 308, 312, 315, 320, 324, 325, 330, 336, 343, 350, 351, 352, 360, 364, 375, 378, 384, 385, 390, 392, 396, 400, 405, 416, 420, 432, 440, 441, 448, 450, 455, 462, 468, 480, 486, 490, 495, 500, 504, 512, 520, 525, 528, 539, 540, 546, 550, 560, 567, 576, 585, 588, 594, 600, 616, 624, 625, 630, 637, 640, 648, 650, 660, 672, 675, 686, 693, 700, 702, 704, 720, 728, 729, 735, 750, 756, 768, 770, 780, 784, 792, 800, 810, 819, 825, 832, 840, 864, 875, 880, 882, 891, 896, 900, 910, 924, 936, 945, 960, 972, 975, 980, 990, 1000, 1008, 1024, 1029, 1040, 1050, 1053, 1056, 1078, 1080, 1092, 1100, 1120, 1125, 1134, 1152, 1155, 1170, 1176, 1188, 1200, 1215, 1225, 1232, 1248, 1250, 1260, 1274, 1280, 1296, 1300, 1320, 1323, 1344, 1350, 1365, 1372, 1375, 1386, 1400, 1404, 1408, 1440, 1456, 1458, 1470, 1485, 1500, 1512, 1536, 1540, 1560, 1568, 1575, 1584, 1600, 1617, 1620, 1625, 1638, 1650, 1664, 1680, 1701, 1715, 1728, 1750, 1755, 1760, 1764, 1782, 1792, 1800, 1820, 1848, 1872, 1875, 1890, 1911, 1920, 1925, 1944, 1950, 1960, 1980, 2000, 2016, 2025, 2048, 2058, 2079, 2080, 2100, 2106, 2112, 2156, 2160, 2184, 2187, 2200, 2205, 2240, 2250, 2268, 2275, 2304, 2310, 2340, 2352, 2376, 2400, 2401, 2430, 2450, 2457, 2464, 2475, 2496, 2500, 2520, 2548, 2560, 2592, 2600, 2625, 2640, 2646, 2673, 2688, 2695, 2700, 2730, 2744, 2750, 2772, 2800, 2808, 2816, 2835, 2880, 2912, 2916, 2925, 2940, 2970, 3000, 3024, 3072, 3080, 3087, 3120, 3125, 3136, 3150, 3159, 3168, 3185, 3200, 3234, 3240, 3250, 3276, 3300, 3328, 3360, 3375, 3402, 3430, 3456, 3465, 3500, 3510, 3520, 3528, 3564, 3584, 3600, 3640, 3645, 3675, 3696, 3744, 3750, 3773, 3780, 3822, 3840, 3850, 3888, 3900, 3920, 3960, 3969, 4000, 4032, 4050, 4095, 4096, 4116, 4125, 4158, 4160, 4200, 4212, 4224, 4312, 4320, 4368, 4374, 4375, 4400, 4410, 4455, 4459, 4480, 4500, 4536, 4550, 4608, 4620, 4680, 4704, 4725, 4752, 4800, 4802, 4851, 4860, 4875, 4900, 4914, 4928, 4950, 4992, 5000, 5040, 5096, 5103, 5120, 5145, 5184, 5200, 5250, 5265, 5280, 5292, 5346, 5376, 5390, 5400, 5460, 5488, 5500, 5544, 5600, 5616, 5625, 5632, 5670, 5733, 5760, 5775, 5824, 5832, 5850, 5880, 5940, 6000, 6048, 6075, 6125, 6144, 6160, 6174, 6237, 6240, 6250, 6272, 6300, 6318, 6336, 6370, 6400, 6468, 6480, 6500, 6552, 6561, 6600, 6615, 6656, 6720, 6750, 6804, 6825, 6860, 6875, 6912, 6930, 7000, 7020, 7040, 7056, 7128, 7168, 7200, 7203, 7280, 7290, 7350, 7371, 7392, 7425, 7488, 7500, 7546, 7560, 7644, 7680, 7700, 7776, 7800, 7840, 7875, 7920, 7938, 8000, 8019, 8064, 8085, 8100, 8125, 8190, 8192, 8232, 8250, 8316, 8320, 8400, 8424, 8448, 8505, 8575, 8624, 8640, 8736, 8748, 8750, 8775, 8800, 8820, 8910, 8918, 8960, 9000, 9072, 9100, 9216, 9240, 9261, 9360, 9375, 9408, 9450, 9477, 9504, 9555, 9600, 9604, 9625, 9702, 9720, 9750, 9800, 9828, 9856, 9900, 9984, 10000) if target <= 16: return target # Quickly check if it's already a power of 2 if not (target & (target-1)): return target # Get result quickly for small sizes, since FFT itself is similarly fast. if target <= lpre[-1]: return lpre[bisect_left(lpre, target)] # check if 13 or 11 is a factor first if target % 13 == 0: p11_13 = 13 e_f_cases = [13, ] # e=0, f=1 elif target % 11 == 0: p11_13 = 11 e_f_cases = [11, ] # e=1, f=0 else: p11_13 = 1 # try all three cases where e + f <= 1 (see docstring) e_f_cases = [13, 11, 1] best_match = float('inf') # Anything found will be smaller # outer loop is for the cases where e + f <= 1 (see docstring) for p11_13 in e_f_cases: match = float('inf') # allow any integer powers of 2, 3, 5 or 7 p7_11_13 = p11_13 while p7_11_13 < target: p5_7_11_13 = p7_11_13 while p5_7_11_13 < target: p3_5_7_11_13 = p5_7_11_13 while p3_5_7_11_13 < target: # Ceiling integer division, avoiding conversion to # float. # (quotient = ceil(target / p35)) quotient = -(-target // p3_5_7_11_13) # Quickly find next power of 2 >= quotient p2 = 2**((quotient - 1).bit_length()) N = p2 * p3_5_7_11_13 if N == target: return N elif N < match: match = N p3_5_7_11_13 *= 3 if p3_5_7_11_13 == target: return p3_5_7_11_13 if p3_5_7_11_13 < match: match = p3_5_7_11_13 p5_7_11_13 *= 5 if p5_7_11_13 == target: return p5_7_11_13 if p5_7_11_13 < match: match = p5_7_11_13 p7_11_13 *= 7 if p7_11_13 == target: return p7_11_13 if p7_11_13 < match: match = p7_11_13 if match < best_match: best_match = match return best_match pyFFTW-0.13.1/pyproject.toml000066400000000000000000000177151435600752200156460ustar00rootroot00000000000000[tool.cibuildwheel] build-verbosity = 2 # Note: Tests are run within a docker container on Linux builds # This will have a different test-requires = "pytest scipy dask" # Import-mode=append to use installed (wheel) version of pyfftw # rather than the local module test-command = "pytest --import-mode=append {project}" [tool.cibuildwheel.environment] PIP_PREFER_BINARY = true [tool.cibuildwheel.windows] archs = [ "AMD64", "x86", "ARM64", ] test-skip = [ # Until SciPy does not support win32 "*win32", # We can't run tests when cross-compiling for ARM64 on AMD64 "*win_arm64", # Until SciPy gives us PyPy wheels "pp*", ] [tool.cibuildwheel.macos] archs = [ "x86_64", # See issue [#352](https://github.com/pyFFTW/pyFFTW/issues/352) # TODO: Add arm64 when we support it # Current problem seems to be that installed libfftw3 does not provide arm64 # symbols # "universal2", # "arm64", ] test-skip = [ # Until SciPy gives us PyPy wheels "pp*", ] [tool.cibuildwheel.macos.environment] CC = "/usr/bin/clang" CXX = "/usr/bin/clang++" CFLAGS = "-Wno-implicit-function-declaration -I/usr/local/opt/fftw/include" LDFLAGS = "-Wl,-S,-rpath,/usr/local/opt/fftw/lib -L/usr/local/opt/fftw/lib" # On Linux we need to use different package managers depending on image used [tool.cibuildwheel.linux] test-skip = [ # See: https://github.com/pyFFTW/pyFFTW/issues/325 "*i686", # See: https://github.com/pyFFTW/pyFFTW/issues/326 "*aarch64", # Until SciPy gives us PyPy wheels "pp*", ] # Fallback, but we specify versions more explicitly below to achieve maximal # compatibility before-all = "apt update; apt install libfftw3-dev" manylinux-x86_64-image = "manylinux_2_28" manylinux-i686-image = "manylinux2014" manylinux-aarch64-image = "manylinux_2_28" manylinux-ppc64le-image = "manylinux_2_28" manylinux-s390x-image = "manylinux_2_28" manylinux-pypy_x86_64-image = "manylinux_2_28" manylinux-pypy_i686-image = "manylinux_2_28" manylinux-pypy_aarch64-image = "manylinux_2_28" archs = [ "x86_64", "i686", "aarch64", ] # Select based on manylinux availability # See: https://github.com/pypa/manylinux # See: https://cibuildwheel.readthedocs.io/en/stable/options/#build-skip # See: https://github.com/pypa/manylinux/issues/994#issuecomment-987835591 # From 3.10+ we can stop doing the madness below and just PEP 600 manylinux_x_y images # Currently the selection of images is conservative and aims for maximal compatibility # The best choice of manylinux is determined by the following: # manylinux version for numpy version for given python version # Use manylinux2010 for Python3.8, as <3.8.4 does not initially work with manylinux2014 [[tool.cibuildwheel.overrides]] select = "{c,p}p38-manylinux*" manylinux-x86_64-image = "manylinux2010" manylinux-i686-image = "manylinux2010" manylinux-aarch64-image = "manylinux2014" manylinux-ppc64le-image = "manylinux2014" manylinux-s390x-image = "manylinux2014" manylinux-pypy_x86_64-image = "manylinux2010" manylinux-pypy_i686-image = "manylinux2010" manylinux-pypy_aarch64-image = "manylinux2014" before-all = "yum install -y fftw-devel" # Use manylinux2014 for Python3.9, as <3.9.5 does not initially work with manylinux_x_y [[tool.cibuildwheel.overrides]] select = "{c,p}p39-manylinux*" manylinux-x86_64-image = "manylinux2014" manylinux-i686-image = "manylinux2014" manylinux-aarch64-image = "manylinux2014" manylinux-ppc64le-image = "manylinux2014" manylinux-s390x-image = "manylinux2014" manylinux-pypy_x86_64-image = "manylinux2014" manylinux-pypy_i686-image = "manylinux2014" manylinux-pypy_aarch64-image = "manylinux2014" before-all = "yum install -y fftw-devel" [[tool.cibuildwheel.overrides]] select = "{c,p}p310-manylinux*" manylinux-x86_64-image = "manylinux2014" manylinux-i686-image = "manylinux2014" manylinux-aarch64-image = "manylinux2014" manylinux-ppc64le-image = "manylinux2014" manylinux-s390x-image = "manylinux2014" manylinux-pypy_x86_64-image = "manylinux2014" manylinux-pypy_i686-image = "manylinux2014" manylinux-pypy_aarch64-image = "manylinux2014" before-all = "yum install -y fftw-devel" [[tool.cibuildwheel.overrides]] select = "{c,p}p311-manylinux*" # Based on https://pypi.org/project/numpy/1.24.0/#files manylinux-x86_64-image = "manylinux2014" manylinux-i686-image = "manylinux2014" manylinux-aarch64-image = "manylinux2014" manylinux-ppc64le-image = "manylinux2014" manylinux-s390x-image = "manylinux2014" manylinux-pypy_x86_64-image = "manylinux2014" manylinux-pypy_i686-image = "manylinux2014" manylinux-pypy_aarch64-image = "manylinux2014" before-all = "yum install -y fftw-devel" # Currently not supported # Even numpy does not support this # This is ignored in the wheel_tests_and_release.yml file # [[tool.cibuildwheel.overrides]] # select = "*-musllinux*" # before-all = "apk add fftw-dev" [build-system] requires = [ "wheel", "setuptools<=59.4.0", "Cython>=0.29.18", # NumPy dependencies - to update these, sync from # https://github.com/scipy/oldest-supported-numpy/ # https://github.com/scipy/scipy/blob/master/pyproject.toml # and then # update minimum version to match our install_requires min version # ---------------------------------------------------------------- # now matches minimum supported version, keep here as separate requirement # to be able to sync more easily with oldest-supported-numpy "numpy==1.19.5; python_version=='3.8' and platform_machine=='aarch64' and platform_python_implementation != 'PyPy'", # arm64 on Darwin supports Python 3.8 and above requires numpy>=1.21.0 # (first version with arm64 wheels available) "numpy==1.21.0; python_version=='3.8' and platform_machine=='arm64' and platform_system=='Darwin'", "numpy==1.21.0; python_version=='3.9' and platform_machine=='arm64' and platform_system=='Darwin'", # loongarch64 requires numpy>=1.22.0 "numpy==1.22.0; platform_machine=='loongarch64'", # On Windows we need to avoid 1.21.6, 1.22.0 and 1.22.1 because they were # built with vc142. 1.22.3 is the first version that has 32-bit Windows # wheels *and* was built with vc141. So use that: "numpy==1.22.3; python_version=='3.10' and platform_system=='Windows' and platform_python_implementation != 'PyPy'", # default numpy requirements "numpy==1.19.5; python_version=='3.8' and (platform_machine!='arm64' or platform_system!='Darwin') and platform_machine!='aarch64' and platform_machine!='loongarch64' and platform_python_implementation != 'PyPy'", "numpy==1.19.5; python_version=='3.9' and (platform_machine!='arm64' or platform_system!='Darwin') and platform_machine!='loongarch64' and platform_python_implementation != 'PyPy'", # Note that 1.21.3 was the first version with a complete set of 3.10 wheels, # however macOS was broken and it's safe C API/ABI-wise to build against 1.21.6 # (see oldest-supported-numpy issues gh-28 and gh-45) "numpy==1.21.6; python_version=='3.10' and (platform_system!='Windows' and platform_machine!='loongarch64') and platform_python_implementation != 'PyPy'", "numpy==1.23.2; python_version=='3.11' and platform_python_implementation != 'PyPy'", # For Python versions which aren't yet officially supported, # we specify an unpinned NumPy which allows source distributions # to be used and allows wheels to be used as soon as they # become available. "numpy; python_version>='3.12'", "numpy; python_version>='3.9' and platform_python_implementation=='PyPy'", ] build-backend = "setuptools.build_meta" pyFFTW-0.13.1/readthedocs.yml000066400000000000000000000001041435600752200157220ustar00rootroot00000000000000conda: file: environment_doc.yml python: setup_py_install: true pyFFTW-0.13.1/requirements.txt000066400000000000000000000000741435600752200162040ustar00rootroot00000000000000cython>=0.29.18 numpy>=1.16 scipy>=1.2.0 dask[array]>=1.0.0 pyFFTW-0.13.1/setup.cfg000066400000000000000000000002371435600752200145420ustar00rootroot00000000000000[versioneer] VCS = git style = pep440 versionfile_source = pyfftw/_version.py versionfile_build = pyfftw/_version.py tag_prefix = v parentdir_prefix = pyfftw- pyFFTW-0.13.1/setup.py000077500000000000000000000752451435600752200144510ustar00rootroot00000000000000# Copyright 2017 Henry Gomersall and the PyFFTW contributors # # Henry Gomersall # heng@kedevelopments.co.uk # # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, this # list of conditions and the following disclaimer. # # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions and the following disclaimer in the documentation # and/or other materials provided with the distribution. # # * Neither the name of the copyright holder nor the names of its contributors # may be used to endorse or promote products derived from this software without # specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. # # import only from standard library so dependencies can be installed from setuptools import setup, Command from setuptools.command.build_ext import build_ext try: from setuptools.errors import CompileError, LinkError from setuptools.extension import Extension except ImportError: # fallback to distutils for older setuptools releases from distutils.errors import CompileError, LinkError from distutils.extension import Extension from pkg_resources import get_platform from contextlib import redirect_stderr, redirect_stdout import os import logging import sys logging.basicConfig(level=logging.DEBUG) log = logging.getLogger(__name__) # ensure the current directory is on sys.path so versioneer can be imported # when pip uses PEP 517/518 build rules. # https://github.com/python-versioneer/python-versioneer/issues/193 sys.path.append(os.path.dirname(__file__)) import versioneer if os.environ.get("READTHEDOCS") == "True": os.environ["CC"] = "x86_64-linux-gnu-gcc" os.environ["LD"] = "x86_64-linux-gnu-ld" os.environ["AR"] = "x86_64-linux-gnu-ar" def get_include_dirs(): import numpy from pkg_resources import get_build_platform include_dirs = [os.path.join(os.getcwd(), 'include'), os.path.join(os.getcwd(), 'pyfftw'), numpy.get_include(), os.path.join(sys.prefix, 'include')] if 'PYFFTW_INCLUDE' in os.environ: include_dirs.append(os.environ['PYFFTW_INCLUDE']) if get_build_platform().startswith("linux"): include_dirs.append('/usr/include') if get_build_platform() in ('win32', 'win-amd64'): include_dirs.append(os.path.join(os.getcwd(), 'include', 'win')) if get_build_platform().startswith('freebsd'): include_dirs.append('/usr/local/include') return include_dirs def get_package_data(): from pkg_resources import get_build_platform package_data = {} if get_build_platform() in ('win32', 'win-amd64'): if 'PYFFTW_WIN_CONDAFORGE' in os.environ: # fftw3.dll, fftw3f.dll will already be on the path (via the # conda environment's \bin subfolder) pass else: # as download from http://www.fftw.org/install/windows.html package_data['pyfftw'] = [ 'libfftw3-3.dll', 'libfftw3l-3.dll', 'libfftw3f-3.dll'] return package_data def get_library_dirs(): from pkg_resources import get_build_platform library_dirs = [] if get_build_platform() in ('win32', 'win-amd64'): library_dirs.append(os.path.join(os.getcwd(), 'pyfftw')) library_dirs.append(os.path.join(sys.prefix, 'bin')) if 'PYFFTW_LIB_DIR' in os.environ: library_dirs.append(os.environ['PYFFTW_LIB_DIR']) library_dirs.append(os.path.join(sys.prefix, 'lib')) if get_build_platform().startswith('freebsd'): library_dirs.append('/usr/local/lib') return library_dirs class EnvironmentSniffer(object): '''Check for availability of headers and libraries of FFTW and MPI. :param compiler: Distutils.ccompiler; The compiler should be the compiler that is used for actual compilation to ensure that include directories, linker flags etc. are identical. ''' def __init__(self, compiler): log.debug("Compiler include_dirs: %s" % compiler.include_dirs) if hasattr(compiler, "initialize"): compiler.initialize() # to set all variables log.debug("Compiler include_dirs after initialize: %s" % compiler.include_dirs) self.compiler = compiler log.debug(sys.version) # contains the compiler used to build this python # members with the info for the outside world self.include_dirs = get_include_dirs() self.objects = [] self.libraries = [] self.library_dirs = get_library_dirs() self.linker_flags = [] self.compile_time_env = {} if self.compiler.compiler_type == 'msvc': if (sys.version_info.major, sys.version_info.minor) < (3, 3): # The check above is a nasty hack. We're using the python # version as a proxy for the MSVC version. 2008 doesn't # have stdint.h, so is needed. 2010 does. # # We need to add the path to msvc includes msvc_2008_path = (os.path.join(os.getcwd(), 'include', 'msvc_2008')) self.include_dirs.append(msvc_2008_path) elif (sys.version_info.major, sys.version_info.minor) < (3, 5): # Actually, it seems that appveyor doesn't have a stdint that # works, so even for 2010 we use our own (hacked) version # of stdint. # This should be pretty safe in whatever case. msvc_2010_path = (os.path.join(os.getcwd(), 'include', 'msvc_2010')) self.include_dirs.append(msvc_2010_path) # To avoid http://bugs.python.org/issue4431 # # C:\Program Files\Microsoft # SDKs\Windows\v7.1\Bin\x64\mt.exe -nologo -manifest # C:\Users\appveyor\AppData\Local\Temp\1\pyfftw-9in6l66u\a.out.exe.manifest # -outputresource:C:\Users\appveyor\AppData\Local\Temp\1\pyfftw-9in6l66u\a.out.exe;1 # C:\Users\appveyor\AppData\Local\Temp\1\pyfftw-9in6l66u\a.out.exe.manifest # : general error c1010070: Failed to load and parse # the manifest. The system cannot find the file # specified. self.compiler.ldflags_shared.append('/MANIFEST') if get_platform().startswith('linux'): # needed at least libm for linker checks to succeed self.libraries.append('m') log.debug("Sniffer include_dirs: %s" % self.include_dirs) # main fftw3 header is required if not self.has_header(['fftw3.h'], include_dirs=self.include_dirs): raise CompileError("Could not find the FFTW header 'fftw3.h'") # mpi is optional # self.support_mpi = self.has_header(['mpi.h', 'fftw3-mpi.h']) # TODO enable check when wrappers are included in Pyfftw self.support_mpi = False if self.support_mpi: try: import mpi4py self.include_dirs.append(mpi4py.get_include()) except ImportError: log.error("Could not import mpi4py. Skipping support for FFTW MPI.") self.support_mpi = False self.search_dependencies() def search_dependencies(self): # lib_checks = {} data_types = ['DOUBLE', 'SINGLE', 'LONG'] data_types_short = ['', 'f', 'l'] lib_types = ['', 'THREADS', 'OMP'] functions = ['plan_dft', 'init_threads', 'init_threads'] if self.support_mpi: lib_types.append('MPI') functions.append('mpi_init') for d, s in zip(data_types, data_types_short): # first check for serial library... basic_lib = self.check('', 'plan_dft', d, s, True) self.add_library(basic_lib) # ...then multithreading: link check with threads requires # the serial library. Both omp and posix define the same # function names. Prefer openmp if linking dynamically, # else fall back to pthreads. pthreads can be prioritized over # openmp by defining the environment variable PYFFTW_USE_PTHREADS if 'PYFFTW_USE_PTHREADS' not in os.environ: # openmp requires special linker treatment self.linker_flags.append(self.openmp_linker_flag()) lib_omp = self.check('OMP', 'init_threads', d, s, basic_lib and not hasattr(self, 'static_fftw_dir')) if lib_omp: self.add_library(lib_omp) # manually set flag because it won't be checked below self.compile_time_env[self.HAVE(d, 'THREADS')] = False else: self.linker_flags.pop() else: lib_omp = False self.compile_time_env[self.HAVE(d, 'OMP')] = False if lib_omp: self.compile_time_env[self.HAVE(d, 'THREADS')] = False if not lib_omp: # -pthread added for gcc/clang when checking for threads self.linker_flags.append(self.pthread_linker_flag()) lib_pthread = self.check('THREADS', 'init_threads', d, s, basic_lib) if lib_pthread: self.add_library(lib_pthread) else: self.linker_flags.pop() # On windows, the serial and posix threading functions are # build into one library as released on fftw.org. openMP # and MPI are not supported in the releases if get_platform() in ('win32', 'win-amd64'): if basic_lib: self.compile_time_env[self.HAVE(d, 'THREADS')] = True # check whatever multithreading is available self.compile_time_env[self.HAVE(d, 'MULTITHREADING')] = self.compile_time_env[self.HAVE(d, 'OMP')] or self.compile_time_env[self.HAVE(d, 'THREADS')] # check MPI only if headers were found self.add_library(self.check('MPI', 'mpi_init', d, s, basic_lib and self.support_mpi)) # compile only if mpi.h *and* one of the fftw mpi libraries are found if self.support_mpi: found_mpi_types = [] for d in data_types: if self.compile_time_env['HAVE_' + d + '_MPI']: found_mpi_types.append(d) else: self.compile_time_env['HAVE_MPI'] = False # Pretend FFTW precision not available, regardless if it was found or # not. Useful for testing that pyfftw still works without requiring all # precisions if 'PYFFTW_IGNORE_SINGLE' in os.environ: self.compile_time_env['HAVE_SINGLE'] = False if 'PYFFTW_IGNORE_DOUBLE' in os.environ: self.compile_time_env['HAVE_DOUBLE'] = False if 'PYFFTW_IGNORE_LONG' in os.environ: self.compile_time_env['HAVE_LONG'] = False log.debug(repr(self.compile_time_env)) # required package: FFTW itself have_fftw = False for d in data_types: have_fftw |= self.compile_time_env['HAVE_' + d] if not have_fftw: raise LinkError("Could not find any of the FFTW libraries") log.info('Build pyFFTW with support for FFTW with') for d in data_types: if not self.compile_time_env[self.HAVE(d)]: continue s = d.lower() + ' precision' if self.compile_time_env[self.HAVE(d, 'OMP')]: s += ' + openMP' elif self.compile_time_env[self.HAVE(d, 'THREADS')]: s += ' + pthreads' if self.compile_time_env[self.HAVE(d, 'MPI')]: s += ' + MPI' log.info(s) def check(self, lib_type, function, data_type, data_type_short, do_check): m = self.HAVE(data_type, lib_type) exists = False lib = '' if do_check: lib = self.lib_root_name( 'fftw3' + data_type_short + ('_' + lib_type.lower() if lib_type else '')) function = 'fftw' + data_type_short + '_' + function exists = self.has_library(lib, function) self.compile_time_env[m] = exists return lib if exists else '' def HAVE(self, data_type, lib_type=''): s = 'HAVE_' + data_type if lib_type: return s + '_' + lib_type else: return s def lib_root_name(self, lib): '''Build the name of the lib to pass to the python compiler interface. Examples: With a unix compiler, `fftw3` is unchanged but the interface passes `-lfftw3` to the linker. On windows, `fftw3l` -> `libfftw3l-3` and the interfaces passes `libfftw3l-3.libf` to the linker. ''' if get_platform() in ('win32', 'win-amd64'): if 'PYFFTW_WIN_CONDAFORGE' in os.environ: return '%s' % lib else: # for download from http://www.fftw.org/install/windows.html return 'lib%s-3' % lib else: return lib def add_library(self, lib): raise NotImplementedError @staticmethod def _log_file(filepath: str, level: int = logging.DEBUG) -> None: with open(filepath) as f: contents = f.read() if contents: log.log(level=level, msg=contents) def has_function(self, function, includes=None, objects=None, libraries=None, include_dirs=None, library_dirs=None, linker_flags=None): ''' Alternative implementation of distutils.ccompiler.has_function that deletes the output and hides calls to the compiler and linker. ''' if includes is None: includes = [] if objects is None: objects = self.objects if libraries is None: libraries = self.libraries if include_dirs is None: include_dirs = self.include_dirs if library_dirs is None: library_dirs = self.library_dirs if linker_flags is None: linker_flags = self.linker_flags msg = "Checking" if function: msg += " for %s" % function if includes: msg += " with includes " + str(includes) msg += "..." status = "no" log.debug("objects: %s" % objects) log.debug("libraries: %s" % libraries) log.debug("include dirs: %s" % include_dirs) import tempfile, shutil tmpdir = tempfile.mkdtemp(prefix='pyfftw-') try: try: fname = os.path.join(tmpdir, '%s.c' % function) f = open(fname, 'w') for inc in includes: f.write('#include <%s>\n' % inc) f.write("""\ int main() { """) if function: f.write('%s();\n' % function) f.write("""\ return 0; }""") finally: f.close() # the root directory file_root = os.path.abspath(os.sep) stdout_path = os.path.join(tmpdir, "compile-stdout") stderr_path = os.path.join(tmpdir, "compile-stderr") try: # output file is stored relative to input file since # the output has the full directory, joining with the # file root gives the right directory with open(stdout_path, "w") as stdout, open(stderr_path, "w") as stderr: with redirect_stdout(stdout), redirect_stderr(stderr): tmp_objects = self.compiler.compile( [fname], output_dir=file_root, include_dirs=include_dirs ) self._log_file(stdout_path) self._log_file(stderr_path) except CompileError as e: log.warning("Compilation error: %s", e) self._log_file(stdout_path) self._log_file(stderr_path) return False except Exception as e: log.error(e) return False stdout_path = os.path.join(tmpdir, "link-stdout") stderr_path = os.path.join(tmpdir, "link-stderr") try: # additional objects should come last to resolve symbols, linker order matters tmp_objects.extend(objects) with open(stdout_path, "w") as stdout, open(stderr_path, "w") as stderr: with redirect_stdout(stdout), redirect_stderr(stderr): # TODO using link_executable, LDFLAGS that the # user can modify are ignored self.compiler.link_executable( tmp_objects, "a.out", output_dir=tmpdir, libraries=libraries, # extra_preargs=linker_flags, library_dirs=library_dirs, ) except (LinkError, TypeError) as e: log.debug("Could not link %s due to %s", function, e) return False except Exception as e: log.error("Failure during linking: %s", e) return False finally: self._log_file(stdout_path) self._log_file(stderr_path) # no error, seems to work status = "ok" return True finally: shutil.rmtree(tmpdir) log.debug(msg + status) def has_header(self, headers, include_dirs=None): '''Check for existence and usability of header files by compiling a test file.''' return self.has_function(None, includes=headers, include_dirs=include_dirs) def has_library(function, lib): raise NotImplementedError def openmp_linker_flag(self): # gcc and newer clang support openmp if self.compiler.compiler_type == 'unix': return '-fopenmp' # TODO support other compilers else: return '' def pthread_linker_flag(self): # gcc and clang if self.compiler.compiler_type == 'unix': return '-pthread' else: # TODO support other compilers return '' class StaticSniffer(EnvironmentSniffer): def __init__(self, compiler): self.static_fftw_dir = os.environ.get('STATIC_FFTW_DIR', None) if not os.path.exists(self.static_fftw_dir): raise LinkError('STATIC_FFTW_DIR="%s" was specified but does not exist' % self.static_fftw_dir) # call parent init super(self.__class__, self).__init__(compiler) def has_library(self, root_name, function): '''Expect library in root form''' # get full name of lib objects = [os.path.join(self.static_fftw_dir, self.lib_full_name(root_name))] objects.extend(self.objects) return self.has_function(function, objects=objects) def lib_full_name(self, root_lib): # TODO use self.compiler.library_filename from pkg_resources import get_build_platform if get_build_platform() in ('win32', 'win-amd64'): lib_pre = '' lib_ext = '.lib' else: lib_pre = 'lib' lib_ext = '.a' return os.path.join(self.static_fftw_dir, lib_pre + root_lib + lib_ext) def add_library(self, lib): full_name = self.lib_full_name(self.lib_root_name(lib)) if lib: self.objects.insert(0, full_name) class DynamicSniffer(EnvironmentSniffer): def __init__(self, compiler): super(self.__class__, self).__init__(compiler) def has_library(self, lib, function): '''Expect lib in root name so it can be passed to compiler''' libraries = [lib] libraries.extend(self.libraries) return self.has_function(function, libraries=libraries) def add_library(self, lib): if lib: self.libraries.insert(0, lib) def make_sniffer(compiler): if os.environ.get('STATIC_FFTW_DIR', None) is None: log.debug("Link FFTW dynamically") return DynamicSniffer(compiler) else: log.debug("Link FFTW statically") return StaticSniffer(compiler) def get_extensions(): ext_modules = [Extension('pyfftw.pyfftw', sources=[os.path.join('pyfftw', 'pyfftw.pyx')])] return ext_modules long_description = ''' pyFFTW is a pythonic wrapper around `FFTW `_, the speedy FFT library. The ultimate aim is to present a unified interface for all the possible transforms that FFTW can perform. Both the complex DFT and the real DFT are supported, as well as arbitrary axes of arbitrary shaped and strided arrays, which makes it almost feature equivalent to standard and real FFT functions of ``numpy.fft`` (indeed, it supports the ``clongdouble`` dtype which ``numpy.fft`` does not). Operating FFTW in multithreaded mode is supported. A comprehensive unittest suite can be found with the source on the github repository. To build for windows from source, download the fftw dlls for your system and the header file from here (they're in a zip file): http://www.fftw.org/install/windows.html and place them in the pyfftw directory. The files are libfftw3-3.dll, libfftw3l-3.dll, libfftw3f-3.dll and libfftw3.h. Under linux, to build from source, the FFTW library must be installed already. This should probably work for OSX, though I've not tried it. Numpy is a dependency for both. The documentation can be found `here `_, and the source is on `github `_. ''' class custom_build_ext(build_ext): def build_extensions(self): '''Check for availability of fftw libraries before building the wrapper. Do it here to make sure we use the exact same compiler for checking includes/linking as for building the libraries.''' sniffer = make_sniffer(self.compiler) # read out information and modify compiler # define macros, that is which part of wrapper is built self.cython_compile_time_env = sniffer.compile_time_env # call `extend()` to keep argument set neither by sniffer nor by # user. On windows there are includes set automatically, we # must not lose them. # prepend automatically generated info to whatever the user specified include_dirs = sniffer.include_dirs or [] if self.include_dirs is not None: include_dirs += self.include_dirs self.compiler.include_dirs.extend(include_dirs) libraries = sniffer.libraries or None if self.libraries is not None: if libraries is None: libraries = self.libraries else: libraries += self.libraries self.compiler.libraries.extend(libraries) library_dirs = sniffer.library_dirs if self.library_dirs is not None: library_dirs += self.library_dirs self.compiler.library_dirs.extend(library_dirs) objects = sniffer.objects if self.link_objects is not None: objects += self.objects self.compiler.set_link_objects(objects) # delegate actual work to standard implementation build_ext.build_extensions(self) class CreateChangelogCommand(Command): '''Depends on the ruby program github_changelog_generator. Install with gem install github_changelog_generator. ''' user_options = [] def initialize_options(self): pass def finalize_options(self): pass def run(self): import subprocess github_token_file = 'github_changelog_generator_token' with open(github_token_file) as f: github_token = f.readline().strip() subprocess.call(['github_changelog_generator', '-t', github_token]) class TestCommand(Command): user_options = [] def initialize_options(self): pass def finalize_options(self): pass def run(self): import subprocess errno = subprocess.call([sys.executable, '-m', 'unittest', 'discover']) raise SystemExit(errno) class QuickTestCommand(Command): '''Runs a set of test cases that covers a limited set of the functionality. It is intended that this class be used as a sanity check that everything is loaded and basically working as expected. It is not meant to replace the comprehensive test suite. ''' user_options = [] def initialize_options(self): pass def finalize_options(self): pass def run(self): quick_test_cases = [ 'test.test_pyfftw_complex.Complex64FFTWTest', 'test.test_pyfftw_complex.Complex128FFTWTest.test_2d', 'test.test_pyfftw_complex.ComplexLongDoubleFFTWTest.test_2d', 'test.test_pyfftw_real_forward.RealForwardSingleFFTWTest', 'test.test_pyfftw_real_forward.RealForwardDoubleFFTWTest.test_2d', 'test.test_pyfftw_real_forward.RealForwardLongDoubleFFTWTest.test_2d', 'test.test_pyfftw_real_backward.RealBackwardSingleFFTWTest', 'test.test_pyfftw_real_backward.RealBackwardDoubleFFTWTest.test_2d', 'test.test_pyfftw_real_backward.RealBackwardLongDoubleFFTWTest.test_2d', 'test.test_pyfftw_wisdom', 'test.test_pyfftw_utils', 'test.test_pyfftw_call', 'test.test_pyfftw_class_misc', 'test.test_pyfftw_nbyte_align', 'test.test_pyfftw_interfaces_cache', 'test.test_pyfftw_multithreaded', 'test.test_pyfftw_numpy_interface.InterfacesNumpyFFTTestModule', 'test.test_pyfftw_numpy_interface.InterfacesNumpyFFTTestFFT2', 'test.test_pyfftw_numpy_interface.InterfacesNumpyFFTTestIFFT2', 'test.test_pyfftw_builders.BuildersTestFFTWWrapper', 'test.test_pyfftw_builders.BuildersTestFFT2', 'test.test_pyfftw_builders.BuildersTestIRFFT2', ] import subprocess subprocess.check_call([sys.executable, '-m', 'unittest'] + quick_test_cases) cmdclass = {'test': TestCommand, 'quick_test': QuickTestCommand, 'build_ext': custom_build_ext, 'create_changelog': CreateChangelogCommand} cmdclass.update(versioneer.get_cmdclass()) def setup_package(): # Figure out whether to add ``*_requires = ['numpy']``. build_requires = [] numpy_requirement = 'numpy>=1.20, <2.0' try: import numpy except ImportError: build_requires = [numpy_requirement] # we require cython because we need to know which part of the wrapper # to build to avoid missing symbols at runtime. But if this script is # called without building pyfftw, for example to install the # dependencies, then we have to hide the cython dependency. try: import cython except ImportError: build_requires.append('cython>=0.29.18, <1.0') install_requires = [numpy_requirement] opt_requires = { 'dask': ['numpy>=1.20, <2.0', 'dask[array]>=1.0'], 'scipy': ['scipy>=1.8.0'] } setup_args = { 'name': 'pyFFTW', 'version': versioneer.get_version(), 'author': 'Henry Gomersall', 'author_email': 'heng@kedevelopments.co.uk', 'description': ( 'A pythonic wrapper around FFTW, the FFT library, presenting a ' 'unified interface for all the supported transforms.'), 'url': 'https://github.com/pyFFTW/pyFFTW', 'long_description': long_description, 'classifiers': [ 'Programming Language :: Python', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.10', 'Programming Language :: Python :: 3.11', 'Development Status :: 4 - Beta', 'License :: OSI Approved :: BSD License', 'Operating System :: OS Independent', 'Intended Audience :: Developers', 'Intended Audience :: Science/Research', 'Topic :: Scientific/Engineering', 'Topic :: Scientific/Engineering :: Mathematics', 'Topic :: Scientific/Engineering :: Image Processing', 'Topic :: Scientific/Engineering :: Physics', 'Topic :: Multimedia :: Sound/Audio :: Analysis' ], 'cmdclass': cmdclass, 'python_requires': ">=3.8", } setup_args['setup_requires'] = build_requires setup_args['install_requires'] = install_requires setup_args['extras_require'] = opt_requires if len(sys.argv) >= 2 and ( '--help' in sys.argv[1:] or sys.argv[1] in ('--help-commands', 'egg_info', '--version', 'clean')): # For these actions, NumPy is not required. pass else: setup_args['packages'] = [ 'pyfftw', 'pyfftw.builders', 'pyfftw.interfaces'] setup_args['ext_modules'] = get_extensions() setup_args['package_data'] = get_package_data() # Do a trial run to determine dependencies # Not ideal, but distutils, setuptools and cython are hard to work with # and several hours of messing around didn't yield a good solution # The problem is getting access to either a good compiler object or # the compile_time_env before calling setup() if sys.argv[1] == "build_ext": from Cython.Build import cythonize trial_distribution = setup(**setup_args) cython_compile_time_env = trial_distribution.get_command_obj("build_ext") setup_args["ext_modules"] = cythonize(get_extensions(), compile_time_env=cython_compile_time_env) setup(**setup_args) if __name__ == '__main__': setup_package() pyFFTW-0.13.1/tests/000077500000000000000000000000001435600752200140615ustar00rootroot00000000000000pyFFTW-0.13.1/tests/__init__.py000066400000000000000000000000001435600752200161600ustar00rootroot00000000000000pyFFTW-0.13.1/tests/_cook_nd_args.py000066400000000000000000000047551435600752200172350ustar00rootroot00000000000000# This file is licensed differently to the rest of pyFFTW. The license # is as set out below. # # Copyright (c) 2005-2011, NumPy Developers. # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are # met: # # * Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # # * Redistributions in binary form must reproduce the above # copyright notice, this list of conditions and the following # disclaimer in the documentation and/or other materials provided # with the distribution. # # * Neither the name of the NumPy Developers nor the names of any # contributors may be used to endorse or promote products derived # from this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # # A module containing the latest version of _cook_nd_args from # numpy.fft.fftpack. It can be used to overwrite an earlier # broken version in the tests. from numpy.core import take def _cook_nd_args(a, s=None, axes=None, invreal=0): if s is None: shapeless = 1 if axes is None: s = list(a.shape) else: s = take(a.shape, axes) else: shapeless = 0 s = list(s) if axes is None: axes = range(-len(s), 0) if len(s) != len(axes): raise ValueError("Shape and axes have different lengths.") if invreal and shapeless: # Here is the fix. The following line is replaced # (see numpy commit 88a02920daf0b408086106439c53bd488e73af29): #s[axes[-1]] = (s[axes[-1]] - 1) * 2 s[-1] = (a.shape[axes[-1]] - 1) * 2 return s, axes pyFFTW-0.13.1/tests/_get_default_args.py000066400000000000000000000044031435600752200200720ustar00rootroot00000000000000# Copyright 2015 Knowledge Economy Developments Ltd # # Henry Gomersall # henry.gomersall@kedevelopments.co.uk # # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, this # list of conditions and the following disclaimer. # # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions and the following disclaimer in the documentation # and/or other materials provided with the distribution. # # * Neither the name of the copyright holder nor the names of its contributors # may be used to endorse or promote products derived from this software without # specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. # import sys import inspect def get_default_args(function): if sys.version_info < (3, 0): # The slightly hacky Python 2.7 way argspec = inspect.getargspec(function) default_args = dict(list(zip( argspec.args[-len(argspec.defaults):], argspec.defaults))) else: # The better Python 3 way sig = inspect.signature(function) default_args = {} for parameter in sig.parameters: default_val = sig.parameters[parameter].default if default_val is not inspect.Parameter.empty: # Add it to the parameter list default_args[parameter] = default_val return default_args pyFFTW-0.13.1/tests/test_pyfftw_base.py000066400000000000000000000176401435600752200200130ustar00rootroot00000000000000# Copyright 2014 Knowledge Economy Developments Ltd # # Henry Gomersall # heng@kedevelopments.co.uk # # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, this # list of conditions and the following disclaimer. # # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions and the following disclaimer in the documentation # and/or other materials provided with the distribution. # # * Neither the name of the copyright holder nor the names of its contributors # may be used to endorse or promote products derived from this software without # specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. # from pyfftw import FFTW, _supported_types, _all_types_human_readable import numpy import struct from timeit import Timer import unittest try: import mkl_fft # mkl_fft monkeypatches numpy.fft # explicitly import from fftpack or pocketfft instead try: # numpy 1.17 replaced fftpack with pocketfft from numpy.fft import pocketfft as np_fft except ImportError: from numpy.fft import fftpack as np_fft except ImportError: from numpy import fft as np_fft def miss(*xs): '''Skip test if the precisions in the iterable `xs` are not available.''' msg = 'Requires %s' % _all_types_human_readable[xs[0]] for x in xs[1:]: msg += ' and %s' % _all_types_human_readable[x] msg += ' precision.' skip = not all(x in _supported_types for x in xs) return (skip, msg) def require(self, *xs): skip, msg = miss(*xs) if skip: self.skipTest(msg) class FFTWBaseTest(unittest.TestCase): def reference_fftn(self, a, axes): return np_fft.fftn(a, axes=axes) def __init__(self, *args, **kwargs): super(FFTWBaseTest, self).__init__(*args, **kwargs) self.make_shapes() if not hasattr(self, 'assertRaisesRegex'): self.assertRaisesRegex = self.assertRaisesRegexp def setUp(self): require(self, '32') self.input_dtype = numpy.complex64 self.output_dtype = numpy.complex64 self.np_fft_comparison = np_fft.fft self.direction = 'FFTW_FORWARD' return def tearDown(self): return def get_input_dtype_alignment(self): return self.input_dtype([]).real.dtype.alignment def get_output_dtype_alignment(self): return self.input_dtype([]).real.dtype.alignment def make_shapes(self): self.input_shapes = { 'small_1d': (16,), '1d': (2048,), '2d': (256, 2048), '3d': (5, 256, 2048)} self.output_shapes = { 'small_1d': (16,), '1d': (2048,), '2d': (256, 2048), '3d': (5, 256, 2048)} def create_test_arrays(self, input_shape, output_shape, axes=None): a = self.input_dtype(numpy.random.randn(*input_shape) +1j*numpy.random.randn(*input_shape)) b = self.output_dtype(numpy.random.randn(*output_shape) +1j*numpy.random.randn(*output_shape)) return a, b def timer_routine(self, pyfftw_callable, numpy_fft_callable, comparison_string='numpy.fft'): N = 100 t = Timer(stmt=pyfftw_callable) t_numpy_fft = Timer(stmt=numpy_fft_callable) t_str = ("%.2f" % (1000.0/N*t.timeit(N)))+' ms' t_numpy_str = ("%.2f" % (1000.0/N*t_numpy_fft.timeit(N)))+' ms' print('One run: '+ t_str + \ ' (versus ' + t_numpy_str + ' for ' + comparison_string + \ ')') def run_validate_fft(self, a, b, axes, fft=None, ifft=None, force_unaligned_data=False, create_array_copies=True, threads=1, flags=('FFTW_ESTIMATE',)): ''' Run a validation of the FFTW routines for the passed pair of arrays, a and b, and the axes argument. a and b are assumed to be the same shape (but not necessarily the same layout in memory). fft and ifft, if passed, should be instantiated FFTW objects. If force_unaligned_data is True, the flag FFTW_UNALIGNED will be passed to the fftw routines. The threads argument runs the validation with multiple threads. flags is passed to the creation of the FFTW object. ''' if create_array_copies: # Don't corrupt the original mutable arrays a = a.copy() b = b.copy() a_orig = a.copy() flags = list(flags) if force_unaligned_data: flags.append('FFTW_UNALIGNED') if fft == None: fft = FFTW(a,b,axes=axes, direction='FFTW_FORWARD', flags=flags, threads=threads) else: fft.update_arrays(a,b) if ifft == None: ifft = FFTW(b, a, axes=axes, direction='FFTW_BACKWARD', flags=flags, threads=threads) else: ifft.update_arrays(b,a) a[:] = a_orig # Test the forward FFT by comparing it to the result from numpy.fft fft.execute() ref_b = self.reference_fftn(a, axes=axes) # This is actually quite a poor relative error, but it still # sometimes fails. I assume that numpy.fft has different internals # to fftw. self.assertTrue(numpy.allclose(b, ref_b, rtol=1e-2, atol=1e-3)) # Test the inverse FFT by comparing the result to the starting # value (which is scaled as per FFTW being unnormalised). ifft.execute() # The scaling is the product of the lengths of the fft along # the axes along which the fft is taken. scaling = numpy.prod(numpy.array(a.shape)[list(axes)]) self.assertEqual(ifft.N, scaling) self.assertEqual(fft.N, scaling) self.assertTrue(numpy.allclose(a/scaling, a_orig, rtol=1e-2, atol=1e-3)) return fft, ifft def run_test_suites(test_suites, run_tests=None): '''From each test case (derived from TestCase) in test_suites, load and run all the test cases within. If run_tests is not None, then it should be a dictionary with keys being the test suite class name, and the values being a list of test methods to run. Alternatively, the key can be 'all' in which case all the test suites will be run with the provided list of test suites. ''' suite = unittest.TestSuite() for test_class in test_suites: tests = unittest.TestLoader().loadTestsFromTestCase(test_class) if run_tests is not None: if test_class.__name__ in run_tests: this_suite_run = set(run_tests[test_class.__name__]) else: this_suite_run = set() if 'all' in run_tests: this_suite_run = this_suite_run.union(run_tests['all']) _tests = [] for each_test in tests: if (each_test.id().split('.')[-1] in this_suite_run): _tests.append(each_test) tests = _tests suite.addTests(tests) unittest.TextTestRunner(verbosity=2).run(suite) pyFFTW-0.13.1/tests/test_pyfftw_builders.py000066400000000000000000001421731435600752200207120ustar00rootroot00000000000000# Copyright 2015 Knowledge Economy Developments Ltd # # Henry Gomersall # heng@kedevelopments.co.uk # # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, this # list of conditions and the following disclaimer. # # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions and the following disclaimer in the documentation # and/or other materials provided with the distribution. # # * Neither the name of the copyright holder nor the names of its contributors # may be used to endorse or promote products derived from this software without # specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. # from pyfftw import builders, empty_aligned, byte_align, FFTW from pyfftw import _supported_nptypes_complex, _supported_nptypes_real from pyfftw.builders import _utils as utils from .test_pyfftw_base import run_test_suites, require from ._get_default_args import get_default_args import unittest import numpy import numpy as np # import the numpy fft routines having the rfft normalization bug fix from .test_pyfftw_numpy_interface import np_fft, _numpy_fft_has_norm_kwarg import copy import warnings warnings.filterwarnings('always') complex_dtypes = _supported_nptypes_complex real_dtypes = _supported_nptypes_real def make_complex_data(shape, dtype): ar, ai = numpy.random.randn(2, *shape).astype(dtype) return ar + 1j*ai def make_real_data(shape, dtype): return numpy.random.randn(*shape).astype(dtype) input_dtypes = { 'complex': (complex_dtypes, make_complex_data), 'r2c': (real_dtypes, make_real_data), 'c2r': (complex_dtypes, make_complex_data)} output_dtypes = { 'complex': complex_dtypes, 'r2c': complex_dtypes, 'c2r': real_dtypes} functions = { 'fft': 'complex', 'ifft': 'complex', 'rfft': 'r2c', 'irfft': 'c2r', 'rfftn': 'r2c', 'irfftn': 'c2r', 'rfft2': 'r2c', 'irfft2': 'c2r', 'fft2': 'complex', 'ifft2': 'complex', 'fftn': 'complex', 'ifftn': 'complex'} class BuildersTestFFT(unittest.TestCase): func = 'fft' axes_kw = 'axis' if numpy.__version__ >= '1.20.0': test_shapes = ( ((100,), {}), ((128, 64), {'axis': 0}), ((128, 32), {'axis': -1}), ((59, 100), {}), ((32, 32, 4), {'axis': 1}), ((32, 32, 4), {'axis': 1, 'norm': 'ortho'}), ((32, 32, 4), {'axis': 1, 'norm': None}), ((32, 32, 4), {'axis': 1, 'norm': 'backward'}), ((32, 32, 4), {'axis': 1, 'norm': 'forward'}), ((64, 128, 16), {}), ) else: test_shapes = ( ((100,), {}), ((128, 64), {'axis': 0}), ((128, 32), {'axis': -1}), ((59, 100), {}), ((32, 32, 4), {'axis': 1}), ((32, 32, 4), {'axis': 1, 'norm': 'ortho'}), ((32, 32, 4), {'axis': 1, 'norm': None}), ((64, 128, 16), {}), ) # invalid_s_shapes is: # (size, invalid_args, error_type, error_string) invalid_args = ( ((100,), ((100, 200),), TypeError, ''), ((100, 200), ((100, 200),), TypeError, ''), ((100,), (100, (-2, -1)), TypeError, ''), ((100,), (100, -20), IndexError, '')) realinv = False has_norm_kwarg = _numpy_fft_has_norm_kwarg() def __init__(self, *args, **kwargs): super(BuildersTestFFT, self).__init__(*args, **kwargs) if not hasattr(self, 'assertRaisesRegex'): self.assertRaisesRegex = self.assertRaisesRegexp @property def test_data(self): for test_shape, kwargs in self.test_shapes: axes = self.axes_from_kwargs(kwargs) s = self.s_from_kwargs(test_shape, kwargs) if not self.has_norm_kwarg and 'norm' in kwargs: kwargs.pop('norm') if self.realinv: test_shape = list(test_shape) test_shape[axes[-1]] = test_shape[axes[-1]]//2 + 1 test_shape = tuple(test_shape) yield test_shape, s, kwargs def validate_pyfftw_object(self, array_type, test_shape, dtype, s, kwargs): input_array = array_type(test_shape, dtype) # Use char because of potential MSVC related bug. if input_array.dtype.char == np.dtype('clongdouble').char: np_input_array = numpy.complex128(input_array) elif input_array.dtype.char == np.dtype('longdouble').char: np_input_array = numpy.float64(input_array) else: np_input_array = input_array with warnings.catch_warnings(record=True) as w: # We catch the warnings so as to pick up on when # a complex array is turned into a real array FFTW_object = getattr(builders, self.func)( input_array.copy(), s, **kwargs) # We run FFT twice to check two operations don't # yield different results (which they might if # the state is buggered up). output_array = FFTW_object(input_array.copy()) output_array_2 = FFTW_object(input_array.copy()) if 'axes' in kwargs: axes = {'axes': kwargs['axes']} elif 'axis' in kwargs: axes = {'axis': kwargs['axis']} else: axes = {} if self.has_norm_kwarg and 'norm' in kwargs: axes['norm'] = kwargs['norm'] test_out_array = getattr(np_fft, self.func)( np_input_array.copy(), s, **axes) if (functions[self.func] == 'r2c'): if numpy.iscomplexobj(input_array): if len(w) > 0: # Make sure a warning is raised self.assertIs( w[-1].category, numpy.ComplexWarning) self.assertTrue( numpy.allclose(output_array, test_out_array, rtol=1e-2, atol=1e-4)) self.assertTrue( numpy.allclose(output_array_2, test_out_array, rtol=1e-2, atol=1e-4)) return FFTW_object def axes_from_kwargs(self, kwargs): default_args = get_default_args(getattr(builders, self.func)) if 'axis' in kwargs: axes = (kwargs['axis'],) elif 'axes' in kwargs: axes = kwargs['axes'] if axes is None: axes = default_args['axes'] else: if 'axis' in default_args: # default 1D axes = (default_args['axis'],) else: # default nD axes = default_args['axes'] if axes is None: axes = (-1,) return axes def s_from_kwargs(self, test_shape, kwargs): ''' Return either a scalar s or a tuple depending on whether axis or axes is specified ''' default_args = get_default_args(getattr(builders, self.func)) if 'axis' in kwargs: s = test_shape[kwargs['axis']] elif 'axes' in kwargs: axes = kwargs['axes'] if axes is not None: s = [] for each_axis in axes: s.append(test_shape[each_axis]) else: # default nD s = [] try: for each_axis in default_args['axes']: s.append(test_shape[each_axis]) except TypeError: s = [test_shape[-1]] else: if 'axis' in default_args: # default 1D s = test_shape[default_args['axis']] else: # default nD s = [] try: for each_axis in default_args['axes']: s.append(test_shape[each_axis]) except TypeError: s = None return s def test_valid(self): dtype_tuple = input_dtypes[functions[self.func]] for dtype in dtype_tuple[0]: for test_shape, s, kwargs in self.test_data: s = None FFTW_object = self.validate_pyfftw_object(dtype_tuple[1], test_shape, dtype, s, kwargs) self.assertTrue(type(FFTW_object) == FFTW) def test_output_dtype_correct(self): '''The output dtype should be correct given the input dtype. It was noted that this is a particular problem on windows 64 due longdouble being mapped to double, but the dtype().char attribute still being different. ''' inp_dtype_tuple = input_dtypes[functions[self.func]] output_dtype_tuple = output_dtypes[functions[self.func]] for input_dtype, output_dtype in zip(inp_dtype_tuple[0], output_dtype_tuple): for test_shape, s, kwargs in self.test_data: s = None FFTW_object = self.validate_pyfftw_object(inp_dtype_tuple[1], test_shape, input_dtype, s, kwargs) self.assertTrue( FFTW_object.output_array.dtype.char == np.dtype(output_dtype).char) def test_fail_on_invalid_s_or_axes(self): dtype_tuple = input_dtypes[functions[self.func]] for dtype in dtype_tuple[0]: for test_shape, args, exception, e_str in self.invalid_args: input_array = dtype_tuple[1](test_shape, dtype) self.assertRaisesRegex(exception, e_str, getattr(builders, self.func), *((input_array,) + args)) def test_same_sized_s(self): dtype_tuple = input_dtypes[functions[self.func]] for dtype in dtype_tuple[0]: for test_shape, s, kwargs in self.test_data: FFTW_object = self.validate_pyfftw_object(dtype_tuple[1], test_shape, dtype, s, kwargs) self.assertTrue(type(FFTW_object) == FFTW) def test_bigger_s_overwrite_input(self): '''Test that FFTWWrapper deals with a destroyed input properly. ''' dtype_tuple = input_dtypes[functions[self.func]] for dtype in dtype_tuple[0]: for test_shape, s, kwargs in self.test_data: try: for each_axis, length in enumerate(s): s[each_axis] += 2 except TypeError: s += 2 _kwargs = kwargs.copy() if self.func not in ('irfft2', 'irfftn'): # They implicitly overwrite the input anyway _kwargs['overwrite_input'] = True FFTW_object = self.validate_pyfftw_object(dtype_tuple[1], test_shape, dtype, s, _kwargs) self.assertTrue( type(FFTW_object) == utils._FFTWWrapper) def test_bigger_s(self): dtype_tuple = input_dtypes[functions[self.func]] for dtype in dtype_tuple[0]: for test_shape, s, kwargs in self.test_data: try: for each_axis, length in enumerate(s): s[each_axis] += 2 except TypeError: s += 2 FFTW_object = self.validate_pyfftw_object(dtype_tuple[1], test_shape, dtype, s, kwargs) self.assertTrue( type(FFTW_object) == utils._FFTWWrapper) def test_smaller_s(self): dtype_tuple = input_dtypes[functions[self.func]] for dtype in dtype_tuple[0]: for test_shape, s, kwargs in self.test_data: try: for each_axis, length in enumerate(s): s[each_axis] -= 2 except TypeError: s -= 2 FFTW_object = self.validate_pyfftw_object(dtype_tuple[1], test_shape, dtype, s, kwargs) self.assertTrue( type(FFTW_object) == utils._FFTWWrapper) def test_bigger_and_smaller_s(self): dtype_tuple = input_dtypes[functions[self.func]] for dtype in dtype_tuple[0]: i = -1 for test_shape, s, kwargs in self.test_data: try: for each_axis, length in enumerate(s): s[each_axis] += i * 2 i *= i except TypeError: s += i * 2 i *= i FFTW_object = self.validate_pyfftw_object(dtype_tuple[1], test_shape, dtype, s, kwargs) self.assertTrue( type(FFTW_object) == utils._FFTWWrapper) def test_auto_contiguous_input(self): dtype_tuple = input_dtypes[functions[self.func]] for dtype in dtype_tuple[0]: for test_shape, s, kwargs in self.test_data: _kwargs = kwargs.copy() s1 = None s2 = copy.copy(s) try: for each_axis, length in enumerate(s): s2[each_axis] += 2 except TypeError: s2 += 2 _test_shape = [] slices = [] for each_dim in test_shape: _test_shape.append(each_dim*2) slices.append(slice(None, None, 2)) slices = tuple(slices) input_array = dtype_tuple[1](_test_shape, dtype)[slices] # check the input is non contiguous self.assertFalse(input_array.flags['C_CONTIGUOUS'] or input_array.flags['F_CONTIGUOUS']) # Firstly check the non-contiguous case (for both # FFTW and _FFTWWrapper) _kwargs['auto_contiguous'] = False # We also need to make sure we're not copying due # to a trivial misalignment _kwargs['auto_align_input'] = False FFTW_object = getattr(builders, self.func)( input_array, s1, **_kwargs) internal_input_array = FFTW_object.input_array flags = internal_input_array.flags self.assertTrue(input_array is internal_input_array) self.assertFalse(flags['C_CONTIGUOUS'] or flags['F_CONTIGUOUS']) FFTW_object = getattr(builders, self.func)( input_array, s2, **_kwargs) internal_input_array = FFTW_object.input_array flags = internal_input_array.flags # We actually expect the _FFTWWrapper to be C_CONTIGUOUS self.assertTrue(flags['C_CONTIGUOUS']) # Now for the contiguous case (for both # FFTW and _FFTWWrapper) _kwargs['auto_contiguous'] = True FFTW_object = getattr(builders, self.func)( input_array, s1, **_kwargs) internal_input_array = FFTW_object.input_array flags = internal_input_array.flags self.assertTrue(flags['C_CONTIGUOUS'] or flags['F_CONTIGUOUS']) FFTW_object = getattr(builders, self.func)( input_array, s2, **_kwargs) internal_input_array = FFTW_object.input_array flags = internal_input_array.flags # as above self.assertTrue(flags['C_CONTIGUOUS']) def test_auto_align_input(self): dtype_tuple = input_dtypes[functions[self.func]] for dtype in dtype_tuple[0]: for test_shape, s, kwargs in self.test_data: _kwargs = kwargs.copy() s1 = None s2 = copy.copy(s) try: for each_axis, length in enumerate(s): s2[each_axis] += 2 except TypeError: s2 += 2 input_array = dtype_tuple[1](test_shape, dtype) # Firstly check the unaligned case (for both # FFTW and _FFTWWrapper) _kwargs['auto_align_input'] = False FFTW_object = getattr(builders, self.func)( input_array.copy(), s1, **_kwargs) self.assertFalse(FFTW_object.simd_aligned) FFTW_object = getattr(builders, self.func)( input_array.copy(), s2, **_kwargs) self.assertFalse(FFTW_object.simd_aligned) # Now for the aligned case (for both # FFTW and _FFTWWrapper) _kwargs['auto_align_input'] = True FFTW_object = getattr(builders, self.func)( input_array.copy(), s1, **_kwargs) self.assertTrue(FFTW_object.simd_aligned) self.assertTrue('FFTW_UNALIGNED' not in FFTW_object.flags) FFTW_object = getattr(builders, self.func)( input_array.copy(), s2, **_kwargs) self.assertTrue(FFTW_object.simd_aligned) self.assertTrue('FFTW_UNALIGNED' not in FFTW_object.flags) def test_dtype_coercian(self): # Make sure we input a dtype that needs to be coerced if functions[self.func] == 'r2c': dtype_tuple = input_dtypes['complex'] else: dtype_tuple = input_dtypes['r2c'] for dtype in dtype_tuple[0]: for test_shape, s, kwargs in self.test_data: s = None FFTW_object = self.validate_pyfftw_object(dtype_tuple[1], test_shape, dtype, s, kwargs) self.assertTrue(type(FFTW_object) == FFTW) def test_persistent_padding(self): '''Test to confirm the padding it not touched after creation. ''' dtype_tuple = input_dtypes[functions[self.func]] for dtype in dtype_tuple[0]: for test_shape, s, kwargs in self.test_data: n_add = 2 # these slicers get the padding # from the internal input array padding_slicer = [slice(None)] * len(test_shape) axes = self.axes_from_kwargs(kwargs) try: for each_axis, length in enumerate(s): s[each_axis] += n_add padding_slicer[axes[each_axis]] = ( slice(s[each_axis], None)) except TypeError: s += n_add padding_slicer[axes[0]] = slice(s, None) padding_slicer = tuple(padding_slicer) # Get a valid object FFTW_object = self.validate_pyfftw_object(dtype_tuple[1], test_shape, dtype, s, kwargs) internal_array = FFTW_object.input_array padding = internal_array[padding_slicer] # Fill the padding with garbage initial_padding = dtype_tuple[1](padding.shape, dtype) padding[:] = initial_padding # Now confirm that nothing is done to the padding FFTW_object() final_padding = FFTW_object.input_array[padding_slicer] self.assertTrue(numpy.all(final_padding == initial_padding)) def test_planner_effort(self): '''Test the planner effort arg ''' dtype_tuple = input_dtypes[functions[self.func]] test_shape = (16,) for dtype in dtype_tuple[0]: s = None if self.axes_kw == 'axis': kwargs = {'axis': -1} else: kwargs = {'axes': (-1,)} for each_effort in ('FFTW_ESTIMATE', 'FFTW_MEASURE', 'FFTW_PATIENT', 'FFTW_EXHAUSTIVE'): kwargs['planner_effort'] = each_effort FFTW_object = self.validate_pyfftw_object( dtype_tuple[1], test_shape, dtype, s, kwargs) self.assertTrue(each_effort in FFTW_object.flags) kwargs['planner_effort'] = 'garbage' self.assertRaisesRegex(ValueError, 'Invalid planner effort', self.validate_pyfftw_object, *(dtype_tuple[1], test_shape, dtype, s, kwargs)) def test_threads_arg(self): '''Test the threads argument ''' dtype_tuple = input_dtypes[functions[self.func]] test_shape = (16,) for dtype in dtype_tuple[0]: s = None if self.axes_kw == 'axis': kwargs = {'axis': -1} else: kwargs = {'axes': (-1,)} kwargs['threads'] = 2 # Should just work FFTW_object = self.validate_pyfftw_object( dtype_tuple[1], test_shape, dtype, s, kwargs) kwargs['threads'] = 'bleh' # Should not work self.assertRaises(TypeError, self.validate_pyfftw_object, *(dtype_tuple[1], test_shape, dtype, s, kwargs)) def test_overwrite_input(self): '''Test the overwrite_input flag ''' dtype_tuple = input_dtypes[functions[self.func]] for dtype in dtype_tuple[0]: for test_shape, s, _kwargs in self.test_data: s = None kwargs = _kwargs.copy() FFTW_object = self.validate_pyfftw_object(dtype_tuple[1], test_shape, dtype, s, kwargs) if self.func not in ('irfft2', 'irfftn'): self.assertTrue( 'FFTW_DESTROY_INPUT' not in FFTW_object.flags) kwargs['overwrite_input'] = True FFTW_object = self.validate_pyfftw_object( dtype_tuple[1], test_shape, dtype, s, kwargs) self.assertTrue('FFTW_DESTROY_INPUT' in FFTW_object.flags) def test_input_maintained(self): '''Test to make sure the input is maintained ''' dtype_tuple = input_dtypes[functions[self.func]] for dtype in dtype_tuple[0]: for test_shape, s, kwargs in self.test_data: input_array = dtype_tuple[1](test_shape, dtype) FFTW_object = getattr( builders, self.func)(input_array, s, **kwargs) final_input_array = FFTW_object.input_array self.assertTrue( numpy.alltrue(input_array == final_input_array)) def test_avoid_copy(self): '''Test the avoid_copy flag ''' dtype_tuple = input_dtypes[functions[self.func]] for dtype in dtype_tuple[0]: for test_shape, s, kwargs in self.test_data: _kwargs = kwargs.copy() _kwargs['avoid_copy'] = True s2 = copy.copy(s) try: for each_axis, length in enumerate(s): s2[each_axis] += 2 except TypeError: s2 += 2 input_array = dtype_tuple[1](test_shape, dtype) self.assertRaisesRegex(ValueError, 'Cannot avoid copy.*transform shape.*', getattr(builders, self.func), input_array, s2, **_kwargs) non_contiguous_shape = [ each_dim * 2 for each_dim in test_shape] non_contiguous_slices = tuple( [slice(None, None, 2)] * len(test_shape)) misaligned_input_array = dtype_tuple[1]( non_contiguous_shape, dtype)[non_contiguous_slices] self.assertRaisesRegex(ValueError, 'Cannot avoid copy.*not contiguous.*', getattr(builders, self.func), misaligned_input_array, s, **_kwargs) # Offset by one from 16 byte aligned to guarantee it's not # 16 byte aligned _input_array = empty_aligned( numpy.prod(test_shape)*input_array.itemsize+1, dtype='int8', n=16) misaligned_input_array = _input_array[1:].view( dtype=input_array.dtype).reshape(*test_shape) self.assertRaisesRegex(ValueError, 'Cannot avoid copy.*not aligned.*', getattr(builders, self.func), misaligned_input_array, s, **_kwargs) _input_array = byte_align(input_array.copy()) FFTW_object = getattr(builders, self.func)( _input_array, s, **_kwargs) # A catch all to make sure the internal array # is not a copy self.assertTrue(FFTW_object.input_array is _input_array) class BuildersTestIFFT(BuildersTestFFT): func = 'ifft' class BuildersTestRFFT(BuildersTestFFT): func = 'rfft' class BuildersTestIRFFT(BuildersTestFFT): func = 'irfft' realinv = True class BuildersTestFFT2(BuildersTestFFT): axes_kw = 'axes' func = 'ifft2' if numpy.__version__ >= '1.20.0': test_shapes = ( ((128, 64), {'axes': None}), ((128, 32), {'axes': None}), ((128, 32, 4), {'axes': (0, 2)}), ((59, 100), {'axes': (-2, -1)}), ((59, 100), {'axes': (-2, -1), 'norm': 'ortho'}), ((59, 100), {'axes': (-2, -1), 'norm': None}), ((59, 100), {'axes': (-2, -1), 'norm': 'backward'}), ((59, 100), {'axes': (-2, -1), 'norm': 'forward'}), ((64, 128, 16), {'axes': (0, 2)}), ((4, 6, 8, 4), {'axes': (0, 3)}), ) else: test_shapes = ( ((128, 64), {'axes': None}), ((128, 32), {'axes': None}), ((128, 32, 4), {'axes': (0, 2)}), ((59, 100), {'axes': (-2, -1)}), ((59, 100), {'axes': (-2, -1), 'norm': 'ortho'}), ((59, 100), {'axes': (-2, -1), 'norm': None}), ((64, 128, 16), {'axes': (0, 2)}), ((4, 6, 8, 4), {'axes': (0, 3)}), ) invalid_args = ( ((100,), ((100, 200),), ValueError, 'Shape error'), ((100, 200), ((100, 200, 100),), ValueError, 'Shape error'), ((100,), ((100, 200), (-3, -2, -1)), ValueError, 'Shape error'), ((100, 200), (100, -1), TypeError, ''), ((100, 200), ((100, 200), (-3, -2)), IndexError, 'Invalid axes'), ((100, 200), ((100,), (-3,)), IndexError, 'Invalid axes')) class BuildersTestIFFT2(BuildersTestFFT2): func = 'ifft2' class BuildersTestRFFT2(BuildersTestFFT2): func = 'rfft2' class BuildersTestIRFFT2(BuildersTestFFT2): func = 'irfft2' realinv = True class BuildersTestFFTN(BuildersTestFFT2): func = 'ifftn' if numpy.__version__ >= '1.20.0': test_shapes = ( ((128, 32, 4), {'axes': None}), ((64, 128, 16), {'axes': (0, 1, 2)}), ((4, 6, 8, 4), {'axes': (0, 3, 1)}), ((4, 6, 8, 4), {'axes': (0, 3, 1), 'norm': 'ortho'}), ((4, 6, 8, 4), {'axes': (0, 3, 1), 'norm': None}), ((4, 6, 8, 4), {'axes': (0, 3, 1), 'norm': 'backward'}), ((4, 6, 8, 4), {'axes': (0, 3, 1), 'norm': 'forward'}), ((4, 6, 8, 4), {'axes': (0, 3, 1, 2)}), ) else: test_shapes = ( ((128, 32, 4), {'axes': None}), ((64, 128, 16), {'axes': (0, 1, 2)}), ((4, 6, 8, 4), {'axes': (0, 3, 1)}), ((4, 6, 8, 4), {'axes': (0, 3, 1), 'norm': 'ortho'}), ((4, 6, 8, 4), {'axes': (0, 3, 1), 'norm': None}), ((4, 6, 8, 4), {'axes': (0, 3, 1, 2)}), ) class BuildersTestIFFTN(BuildersTestFFTN): func = 'ifftn' class BuildersTestRFFTN(BuildersTestFFTN): func = 'rfftn' class BuildersTestIRFFTN(BuildersTestFFTN): func = 'irfftn' realinv = True class BuildersTestFFTWWrapper(unittest.TestCase): '''This basically reimplements the FFTW.__call__ tests, with a few tweaks. ''' def __init__(self, *args, **kwargs): super(BuildersTestFFTWWrapper, self).__init__(*args, **kwargs) if not hasattr(self, 'assertRaisesRegex'): self.assertRaisesRegex = self.assertRaisesRegexp def setUp(self): require(self, '64') self.input_array_slicer = tuple([slice(None), slice(256)]) self.FFTW_array_slicer = tuple([slice(128), slice(None)]) self.input_array = empty_aligned((128, 512), dtype='complex128') self.output_array = empty_aligned((256, 256), dtype='complex128') self.internal_array = empty_aligned((256, 256), dtype='complex128') self.fft = utils._FFTWWrapper(self.internal_array, self.output_array, input_array_slicer=self.input_array_slicer, FFTW_array_slicer=self.FFTW_array_slicer) self.input_array[:] = (numpy.random.randn(*self.input_array.shape) + 1j*numpy.random.randn(*self.input_array.shape)) self.internal_array[:] = 0 self.internal_array[self.FFTW_array_slicer] = ( self.input_array[self.input_array_slicer]) def update_arrays(self, input_array, output_array): '''Does what the internal update arrays does for an FFTW object but with a reslicing. ''' internal_input_array = self.fft.input_array internal_output_array = self.fft.output_array internal_input_array[self.FFTW_array_slicer] = ( input_array[self.input_array_slicer]) self.fft(output_array=output_array) def test_call(self): '''Test a call to an instance of the class. ''' self.input_array[:] = (numpy.random.randn(*self.input_array.shape) + 1j*numpy.random.randn(*self.input_array.shape)) output_array = self.fft() self.assertTrue(numpy.alltrue(output_array == self.output_array)) def test_call_with_positional_input_update(self): '''Test the class call with a positional input update. ''' input_array = byte_align( (numpy.random.randn(*self.input_array.shape) + 1j*numpy.random.randn(*self.input_array.shape))) output_array = self.fft( byte_align(input_array.copy())).copy() self.update_arrays(input_array, self.output_array) self.fft.execute() self.assertTrue(numpy.alltrue(output_array == self.output_array)) def test_call_with_keyword_input_update(self): '''Test the class call with a keyword input update. ''' input_array = byte_align( numpy.random.randn(*self.input_array.shape) + 1j*numpy.random.randn(*self.input_array.shape)) output_array = self.fft( input_array=byte_align(input_array.copy())).copy() self.update_arrays(input_array, self.output_array) self.fft.execute() self.assertTrue(numpy.alltrue(output_array == self.output_array)) def test_call_with_keyword_output_update(self): '''Test the class call with a keyword output update. ''' output_array = byte_align( (numpy.random.randn(*self.output_array.shape) + 1j*numpy.random.randn(*self.output_array.shape))) returned_output_array = self.fft( output_array=byte_align(output_array.copy())).copy() self.update_arrays(self.input_array, output_array) self.fft.execute() self.assertTrue( numpy.alltrue(returned_output_array == output_array)) def test_call_with_positional_updates(self): '''Test the class call with a positional array updates. ''' input_array = byte_align((numpy.random.randn(*self.input_array.shape) + 1j*numpy.random.randn(*self.input_array.shape))) output_array = byte_align((numpy.random.randn(*self.output_array.shape) + 1j*numpy.random.randn(*self.output_array.shape))) returned_output_array = self.fft( byte_align(input_array.copy()), byte_align(output_array.copy())).copy() self.update_arrays(input_array, output_array) self.fft.execute() self.assertTrue(numpy.alltrue(returned_output_array == output_array)) def test_call_with_keyword_updates(self): '''Test the class call with a positional output update. ''' input_array = byte_align( (numpy.random.randn(*self.input_array.shape) + 1j*numpy.random.randn(*self.input_array.shape))) output_array = byte_align( (numpy.random.randn(*self.output_array.shape) + 1j*numpy.random.randn(*self.output_array.shape))) returned_output_array = self.fft( output_array=byte_align(output_array.copy()), input_array=byte_align(input_array.copy())).copy() self.update_arrays(input_array, output_array) self.fft.execute() self.assertTrue(numpy.alltrue(returned_output_array == output_array)) def test_call_with_different_input_dtype(self): '''Test the class call with an array with a different input dtype ''' input_array = byte_align(numpy.complex64( numpy.random.randn(*self.input_array.shape) + 1j*numpy.random.randn(*self.input_array.shape))) output_array = self.fft(byte_align(input_array.copy())).copy() _input_array = numpy.asarray(input_array, dtype=self.input_array.dtype) self.update_arrays(_input_array, self.output_array) self.fft.execute() self.assertTrue(numpy.alltrue(output_array == self.output_array)) def test_call_with_list_input(self): '''Test the class call with a list rather than an array ''' output_array = self.fft().copy() test_output_array = self.fft(self.input_array.tolist()).copy() self.assertTrue(numpy.alltrue(output_array == test_output_array)) def test_call_with_invalid_update(self): '''Test the class call with an invalid update. ''' new_shape = self.input_array.shape + (2, ) invalid_array = (numpy.random.randn(*new_shape) + 1j*numpy.random.randn(*new_shape)) self.assertRaises(ValueError, self.fft, *(), **{'output_array':invalid_array}) self.assertRaises(ValueError, self.fft, *(), **{'input_array':invalid_array}) def test_call_with_invalid_output_striding(self): '''Test the class call with an invalid strided output update. ''' # Add an extra dimension to bugger up the striding new_shape = self.output_array.shape + (2,) output_array = byte_align(numpy.random.randn(*new_shape) + 1j*numpy.random.randn(*new_shape)) self.assertRaisesRegex(ValueError, 'Invalid output striding', self.fft, **{'output_array': output_array[:,:,1]}) def test_call_with_different_striding(self): '''Test the input update with different strides to internal array. ''' input_array_shape = self.input_array.shape + (2,) internal_array_shape = self.internal_array.shape internal_array = byte_align( numpy.random.randn(*internal_array_shape) + 1j*numpy.random.randn(*internal_array_shape)) fft = utils._FFTWWrapper(internal_array, self.output_array, input_array_slicer=self.input_array_slicer, FFTW_array_slicer=self.FFTW_array_slicer) test_output_array = fft().copy() new_input_array = empty_aligned(input_array_shape, dtype=internal_array.dtype) new_input_array[:] = 0 new_input_array[:,:,0][self.input_array_slicer] = ( internal_array[self.FFTW_array_slicer]) new_output = fft(new_input_array[:,:,0]).copy() # Test the test! self.assertTrue( new_input_array[:,:,0].strides != internal_array.strides) self.assertTrue(numpy.alltrue(test_output_array == new_output)) def test_call_with_copy_with_missized_array_error(self): '''Force an input copy with a missized array. ''' shape = list(self.input_array.shape + (2,)) shape[0] += 1 input_array = byte_align(numpy.random.randn(*shape) + 1j*numpy.random.randn(*shape)) self.assertRaisesRegex(ValueError, 'Invalid input shape', self.fft, **{'input_array': input_array[:,:,0]}) def test_call_with_normalisation_on(self): _input_array = empty_aligned(self.internal_array.shape, dtype='complex128') ifft = utils._FFTWWrapper(self.output_array, _input_array, direction='FFTW_BACKWARD', input_array_slicer=slice(None), FFTW_array_slicer=slice(None)) self.fft(normalise_idft=True) # Shouldn't make any difference ifft(normalise_idft=True) self.assertTrue(numpy.allclose( self.input_array[self.input_array_slicer], _input_array[self.FFTW_array_slicer])) def test_call_with_normalisation_off(self): _input_array = empty_aligned(self.internal_array.shape, dtype='complex128') ifft = utils._FFTWWrapper(self.output_array, _input_array, direction='FFTW_BACKWARD', input_array_slicer=slice(None), FFTW_array_slicer=slice(None)) self.fft(normalise_idft=True) # Shouldn't make any difference ifft(normalise_idft=False) _input_array /= ifft.N self.assertTrue(numpy.allclose( self.input_array[self.input_array_slicer], _input_array[self.FFTW_array_slicer])) def test_call_with_normalisation_default(self): _input_array = empty_aligned(self.internal_array.shape, dtype='complex128') ifft = utils._FFTWWrapper(self.output_array, _input_array, direction='FFTW_BACKWARD', input_array_slicer=slice(None), FFTW_array_slicer=slice(None)) self.fft() ifft() # Scaling is performed by default self.assertTrue(numpy.allclose( self.input_array[self.input_array_slicer], _input_array[self.FFTW_array_slicer])) def test_call_norm_ortho(self): _input_array = empty_aligned(self.internal_array.shape, dtype='complex128') normdict = utils._norm_args("ortho") ifft = utils._FFTWWrapper(self.output_array, _input_array, direction='FFTW_BACKWARD', input_array_slicer=slice(None), FFTW_array_slicer=slice(None), normalise_idft=normdict["normalise_idft"], ortho=normdict["ortho"]) self.fft(normalise_idft=normdict["normalise_idft"], ortho=normdict["ortho"]) ifft() self.assertTrue(numpy.allclose( self.input_array[self.input_array_slicer], _input_array[self.FFTW_array_slicer])) def test_call_norm_backward(self): _input_array = empty_aligned(self.internal_array.shape, dtype='complex128') normdict = utils._norm_args("backward") ifft = utils._FFTWWrapper(self.output_array, _input_array, direction='FFTW_BACKWARD', input_array_slicer=slice(None), FFTW_array_slicer=slice(None), normalise_idft=normdict["normalise_idft"], ortho=normdict["ortho"]) self.fft(normalise_idft=normdict["normalise_idft"], ortho=normdict["ortho"]) ifft() self.assertTrue(numpy.allclose( self.input_array[self.input_array_slicer], _input_array[self.FFTW_array_slicer])) def test_call_norm_none(self): _input_array = empty_aligned(self.internal_array.shape, dtype='complex128') normdict = utils._norm_args(None) ifft = utils._FFTWWrapper(self.output_array, _input_array, direction='FFTW_BACKWARD', input_array_slicer=slice(None), FFTW_array_slicer=slice(None), normalise_idft=normdict["normalise_idft"], ortho=normdict["ortho"]) self.fft(normalise_idft=normdict["normalise_idft"], ortho=normdict["ortho"]) ifft() self.assertTrue(numpy.allclose( self.input_array[self.input_array_slicer], _input_array[self.FFTW_array_slicer])) def test_call_norm_forward(self): _input_array = empty_aligned(self.internal_array.shape, dtype='complex128') normdict = utils._norm_args("forward") ifft = utils._FFTWWrapper(self.output_array, _input_array, direction='FFTW_BACKWARD', input_array_slicer=slice(None), FFTW_array_slicer=slice(None), normalise_idft=normdict["normalise_idft"], ortho=normdict["ortho"]) self.fft(normalise_idft=normdict["normalise_idft"], ortho=normdict["ortho"]) ifft() self.assertTrue(numpy.allclose( self.input_array[self.input_array_slicer], _input_array[self.FFTW_array_slicer])) class BuildersTestUtilities(unittest.TestCase): def __init__(self, *args, **kwargs): super(BuildersTestUtilities, self).__init__(*args, **kwargs) if not hasattr(self, 'assertRaisesRegex'): self.assertRaisesRegex = self.assertRaisesRegexp def test_setup_input_slicers(self): inputs = ( ((4, 5), (4, 5)), ((4, 4), (3, 5)), ((4, 5), (3, 5)), ) outputs = ( ((slice(0, 4), slice(0, 5)), (slice(None), slice(None))), ((slice(0, 3), slice(0, 4)), (slice(None), slice(0, 4))), ((slice(0, 3), slice(0, 5)), (slice(None), slice(None))), ) for _input, _output in zip(inputs, outputs): self.assertEqual( utils._setup_input_slicers(*_input), _output) def test_compute_array_shapes(self): # inputs are: # (a.shape, s, axes, inverse, real) inputs = ( ((4, 5), (4, 5), (-2, -1), False, False), ((4, 5), (4, 5), (-1, -2), False, False), ((4, 5), (4, 5), (-1, -2), True, False), ((4, 5), (4, 5), (-1, -2), True, True), ((4, 5), (4, 5), (-2, -1), True, True), ((4, 5), (4, 5), (-2, -1), False, True), ((4, 5), (4, 5), (-1, -2), False, True), ((4, 5, 6), (4, 5), (-2, -1), False, False), ((4, 5, 6), (5, 6), (-2, -1), False, False), ((4, 5, 6), (3, 5), (-3, -1), False, False), ((4, 5, 6), (4, 5), (-2, -1), True, False), ((4, 5, 6), (3, 5), (-3, -1), True, False), ((4, 5, 6), (4, 5), (-2, -1), True, True), ((4, 5, 6), (3, 5), (-3, -1), True, True), ((4, 5, 6), (4, 5), (-2, -1), False, True), ((4, 5, 6), (3, 5), (-3, -1), False, True), ) outputs = ( ((4, 5), (4, 5)), ((5, 4), (5, 4)), ((5, 4), (5, 4)), ((3, 4), (5, 4)), ((4, 3), (4, 5)), ((4, 5), (4, 3)), ((5, 4), (3, 4)), ((4, 4, 5), (4, 4, 5)), ((4, 5, 6), (4, 5, 6)), ((3, 5, 5), (3, 5, 5)), ((4, 4, 5), (4, 4, 5)), ((3, 5, 5), (3, 5, 5)), ((4, 4, 3), (4, 4, 5)), ((3, 5, 3), (3, 5, 5)), ((4, 4, 5), (4, 4, 3)), ((3, 5, 5), (3, 5, 3)), ) for _input, output in zip(inputs, outputs): shape, s, axes, inverse, real = _input a = numpy.empty(shape) self.assertEqual( utils._compute_array_shapes(a, s, axes, inverse, real), output) def test_compute_array_shapes_invalid_axes(self): a = numpy.zeros((3, 4)) s = (3, 4) test_axes = ((1, 2, 3),) for each_axes in test_axes: args = (a, s, each_axes, False, False) self.assertRaisesRegex(IndexError, 'Invalid axes', utils._compute_array_shapes, *args) def _call_cook_nd_args(self, arg_tuple): a = numpy.zeros(arg_tuple[0]) args = ('s', 'axes', 'invreal') arg_dict = {'a': a} for arg_name, arg in zip(args, arg_tuple[1:]): if arg is not None: arg_dict[arg_name] = arg return utils._cook_nd_args(**arg_dict) def test_cook_nd_args_normal(self): # inputs are (a.shape, s, axes, invreal) # None corresponds to no argument inputs = ( ((2, 3), None, (-1,), False), ((2, 3), (5, 6), (-2, -1), False), ((2, 3), (5, 6), (-1, -2), False), ((2, 3), None, (-1, -2), False), ((2, 3, 5), (5, 6), (-1, -2), False), ((2, 3, 5), (5, 6), None, False), ((2, 3, 5), None, (-1, -2), False), ((2, 3, 5), None, (-1, -3), False)) outputs = ( ((3,), (-1,)), ((5, 6), (-2, -1)), ((5, 6), (-1, -2)), ((3, 2), (-1, -2)), ((5, 6), (-1, -2)), ((5, 6), (-2, -1)), ((5, 3), (-1, -2)), ((5, 2), (-1, -3)) ) for each_input, each_output in zip(inputs, outputs): self.assertEqual(self._call_cook_nd_args(each_input), each_output) def test_cook_nd_args_invreal(self): # inputs are (a.shape, s, axes, invreal) # None corresponds to no argument inputs = ( ((2, 3), None, (-1,), True), ((2, 3), (5, 6), (-2, -1), True), ((2, 3), (5, 6), (-1, -2), True), ((2, 3), None, (-1, -2), True), ((2, 3, 5), (5, 6), (-1, -2), True), ((2, 3, 5), (5, 6), None, True), ((2, 3, 5), None, (-1, -2), True), ((2, 3, 5), None, (-1, -3), True)) outputs = ( ((4,), (-1,)), ((5, 6), (-2, -1)), ((5, 6), (-1, -2)), ((3, 2), (-1, -2)), ((5, 6), (-1, -2)), ((5, 6), (-2, -1)), ((5, 4), (-1, -2)), ((5, 2), (-1, -3)) ) for each_input, each_output in zip(inputs, outputs): self.assertEqual(self._call_cook_nd_args(each_input), each_output) def test_cook_nd_args_invalid_inputs(self): # inputs are (a.shape, s, axes, invreal) # None corresponds to no argument inputs = ( ((2, 3), (1,), (-1, -2), None), ((2, 3), (2, 3, 4), (-3, -2, -1), None), ) # all the inputs should yield an error for each_input in inputs: self.assertRaisesRegex(ValueError, 'Shape error', self._call_cook_nd_args, *(each_input,)) test_cases = ( BuildersTestFFTWWrapper, BuildersTestUtilities, BuildersTestFFT, BuildersTestIFFT, BuildersTestRFFT, BuildersTestIRFFT, BuildersTestFFT2, BuildersTestIFFT2, BuildersTestRFFT2, BuildersTestIRFFT2, BuildersTestFFTN, BuildersTestIFFTN, BuildersTestRFFTN, BuildersTestIRFFTN) #test_set = {'BuildersTestRFFTN': ['test_dtype_coercian']} test_set = None if __name__ == '__main__': run_test_suites(test_cases, test_set) pyFFTW-0.13.1/tests/test_pyfftw_call.py000066400000000000000000000413051435600752200200070ustar00rootroot00000000000000# Copyright 2014 Knowledge Economy Developments Ltd # # Henry Gomersall # heng@kedevelopments.co.uk # # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, this # list of conditions and the following disclaimer. # # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions and the following disclaimer in the documentation # and/or other materials provided with the distribution. # # * Neither the name of the copyright holder nor the names of its contributors # may be used to endorse or promote products derived from this software without # specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. # from pyfftw import ( FFTW, empty_aligned, byte_align) from .test_pyfftw_base import run_test_suites, miss, require import numpy import unittest @unittest.skipIf(*miss('64')) class FFTWCallTest(unittest.TestCase): def __init__(self, *args, **kwargs): super(FFTWCallTest, self).__init__(*args, **kwargs) if not hasattr(self, 'assertRaisesRegex'): self.assertRaisesRegex = self.assertRaisesRegexp def setUp(self): self.input_array = empty_aligned((256, 512), dtype='complex128', n=16) self.output_array = empty_aligned((256, 512), dtype='complex128', n=16) self.fft = FFTW(self.input_array, self.output_array) self.input_array[:] = (numpy.random.randn(*self.input_array.shape) + 1j*numpy.random.randn(*self.input_array.shape)) def test_call(self): '''Test a call to an instance of the class. ''' self.input_array[:] = (numpy.random.randn(*self.input_array.shape) + 1j*numpy.random.randn(*self.input_array.shape)) output_array = self.fft() self.assertTrue(numpy.alltrue(output_array == self.output_array)) def test_call_with_positional_input_update(self): '''Test the class call with a positional input update. ''' input_array = byte_align( (numpy.random.randn(*self.input_array.shape) + 1j*numpy.random.randn(*self.input_array.shape)), n=16) output_array = self.fft(byte_align(input_array.copy(), n=16)).copy() self.fft.update_arrays(input_array, self.output_array) self.fft.execute() self.assertTrue(numpy.alltrue(output_array == self.output_array)) def test_call_with_keyword_input_update(self): '''Test the class call with a keyword input update. ''' input_array = byte_align( numpy.random.randn(*self.input_array.shape) + 1j*numpy.random.randn(*self.input_array.shape), n=16) output_array = self.fft( input_array=byte_align(input_array.copy(), n=16)).copy() self.fft.update_arrays(input_array, self.output_array) self.fft.execute() self.assertTrue(numpy.alltrue(output_array == self.output_array)) def test_call_with_keyword_output_update(self): '''Test the class call with a keyword output update. ''' output_array = byte_align( (numpy.random.randn(*self.output_array.shape) + 1j*numpy.random.randn(*self.output_array.shape)), n=16) returned_output_array = self.fft( output_array=byte_align(output_array.copy(), n=16)).copy() self.fft.update_arrays(self.input_array, output_array) self.fft.execute() self.assertTrue( numpy.alltrue(returned_output_array == output_array)) def test_call_with_positional_updates(self): '''Test the class call with a positional array updates. ''' input_array = byte_align((numpy.random.randn(*self.input_array.shape) + 1j*numpy.random.randn(*self.input_array.shape)), n=16) output_array = byte_align((numpy.random.randn(*self.output_array.shape) + 1j*numpy.random.randn(*self.output_array.shape)), n=16) returned_output_array = self.fft( byte_align(input_array.copy(), n=16), byte_align(output_array.copy(), n=16)).copy() self.fft.update_arrays(input_array, output_array) self.fft.execute() self.assertTrue(numpy.alltrue(returned_output_array == output_array)) def test_call_with_keyword_updates(self): '''Test the class call with a positional output update. ''' input_array = byte_align( (numpy.random.randn(*self.input_array.shape) + 1j*numpy.random.randn(*self.input_array.shape)), n=16) output_array = byte_align( (numpy.random.randn(*self.output_array.shape) + 1j*numpy.random.randn(*self.output_array.shape)), n=16) returned_output_array = self.fft( output_array=byte_align(output_array.copy(), n=16), input_array=byte_align(input_array.copy(), n=16)).copy() self.fft.update_arrays(input_array, output_array) self.fft.execute() self.assertTrue(numpy.alltrue(returned_output_array == output_array)) def test_call_with_different_input_dtype(self): '''Test the class call with an array with a different input dtype ''' input_array = byte_align(numpy.complex64( numpy.random.randn(*self.input_array.shape) + 1j*numpy.random.randn(*self.input_array.shape)), n=16) output_array = self.fft(byte_align(input_array.copy(), n=16)).copy() _input_array = byte_align(numpy.asarray(input_array, dtype=self.input_array.dtype), n=16) self.assertTrue(_input_array.dtype != input_array.dtype) self.fft.update_arrays(_input_array, self.output_array) self.fft.execute() self.assertTrue(numpy.alltrue(output_array == self.output_array)) def test_call_with_list_input(self): '''Test the class call with a list rather than an array ''' output_array = self.fft().copy() test_output_array = self.fft(self.input_array.tolist()).copy() self.assertTrue(numpy.alltrue(output_array == test_output_array)) def test_call_with_invalid_update(self): '''Test the class call with an invalid update. ''' new_shape = self.input_array.shape + (2, ) invalid_array = (numpy.random.randn(*new_shape) + 1j*numpy.random.randn(*new_shape)) self.assertRaises(ValueError, self.fft, *(), **{'output_array':invalid_array}) self.assertRaises(ValueError, self.fft, *(), **{'input_array':invalid_array}) @unittest.skipIf(*miss('32')) def test_call_with_auto_input_alignment(self): '''Test the class call with a keyword input update. ''' input_array = (numpy.random.randn(*self.input_array.shape) + 1j*numpy.random.randn(*self.input_array.shape)) output_array = self.fft( input_array=byte_align(input_array.copy(), n=16)).copy() # Offset by one from 16 byte aligned to guarantee it's not # 16 byte aligned a = input_array a__ = empty_aligned(numpy.prod(a.shape)*a.itemsize+1, dtype='int8', n=16) a_ = a__[1:].view(dtype=a.dtype).reshape(*a.shape) a_[:] = a # Just confirm that a usual update will fail self.assertRaisesRegex(ValueError, 'Invalid input alignment', self.fft.update_arrays, *(a_, self.output_array)) self.fft(a_, self.output_array) self.assertTrue(numpy.alltrue(output_array == self.output_array)) # now try with a single byte offset and SIMD off ar, ai = numpy.float32(numpy.random.randn(2, 257)) a = ar[1:] + 1j*ai[1:] b = a.copy() a_size = len(a.ravel())*a.itemsize update_array = numpy.frombuffer( numpy.zeros(a_size + 1, dtype='int8')[1:].data, dtype=a.dtype).reshape(a.shape) fft = FFTW(a, b, flags=('FFTW_UNALIGNED',)) # Confirm that a usual update will fail (it's not on the # byte boundary) self.assertRaisesRegex(ValueError, 'Invalid input alignment', fft.update_arrays, *(update_array, b)) fft(update_array, b) def test_call_with_invalid_output_striding(self): '''Test the class call with an invalid strided output update. ''' # Add an extra dimension to bugger up the striding new_shape = self.output_array.shape + (2,) output_array = byte_align(numpy.random.randn(*new_shape) + 1j*numpy.random.randn(*new_shape), n=16) self.assertRaisesRegex(ValueError, 'Invalid output striding', self.fft, **{'output_array': output_array[:,:,1]}) def test_call_with_different_striding(self): '''Test the input update with different strides to internal array. ''' shape = self.input_array.shape + (2,) input_array = byte_align(numpy.random.randn(*shape) + 1j*numpy.random.randn(*shape), n=16) fft = FFTW(input_array[:,:,0], self.output_array) test_output_array = fft().copy() new_input_array = byte_align( input_array[:, :, 0].copy(), n=16) new_output = fft(new_input_array).copy() # Test the test! self.assertTrue(new_input_array.strides != input_array[:,:,0].strides) self.assertTrue(numpy.alltrue(test_output_array == new_output)) def test_call_with_copy_with_missized_array_error(self): '''Force an input copy with a missized array. ''' shape = list(self.input_array.shape + (2,)) shape[0] += 1 input_array = byte_align(numpy.random.randn(*shape) + 1j*numpy.random.randn(*shape), n=16) fft = FFTW(self.input_array, self.output_array) self.assertRaisesRegex(ValueError, 'Invalid input shape', self.fft, **{'input_array': input_array[:,:,0]}) def test_call_with_unaligned(self): '''Make sure the right thing happens with unaligned data. ''' input_array = (numpy.random.randn(*self.input_array.shape) + 1j*numpy.random.randn(*self.input_array.shape)) output_array = self.fft( input_array=byte_align(input_array.copy(), n=16)).copy() input_array = byte_align(input_array, n=16) output_array = byte_align(output_array, n=16) # Offset by one from 16 byte aligned to guarantee it's not # 16 byte aligned a = byte_align(input_array.copy(), n=16) a__ = empty_aligned(numpy.prod(a.shape)*a.itemsize+1, dtype='int8', n=16) a_ = a__[1:].view(dtype=a.dtype).reshape(*a.shape) a_[:] = a # Create a different second array the same way b = byte_align(output_array.copy(), n=16) b__ = empty_aligned(numpy.prod(b.shape)*a.itemsize+1, dtype='int8', n=16) b_ = b__[1:].view(dtype=b.dtype).reshape(*b.shape) b_[:] = a # Set up for the first array fft = FFTW(input_array, output_array) a_[:] = a output_array = fft().copy() # Check a_ is not aligned... self.assertRaisesRegex(ValueError, 'Invalid input alignment', self.fft.update_arrays, *(a_, output_array)) # and b_ too self.assertRaisesRegex(ValueError, 'Invalid output alignment', self.fft.update_arrays, *(input_array, b_)) # But it should still work with the a_ fft(a_) # However, trying to update the output will raise an error self.assertRaisesRegex(ValueError, 'Invalid output alignment', self.fft.update_arrays, *(input_array, b_)) # Same with SIMD off fft = FFTW(input_array, output_array, flags=('FFTW_UNALIGNED',)) fft(a_) self.assertRaisesRegex(ValueError, 'Invalid output alignment', self.fft.update_arrays, *(input_array, b_)) def test_call_with_normalisation_on(self): _input_array = empty_aligned((256, 512), dtype='complex128', n=16) ifft = FFTW(self.output_array, _input_array, direction='FFTW_BACKWARD') self.fft(normalise_idft=True) # Shouldn't make any difference ifft(normalise_idft=True) self.assertTrue(numpy.allclose(self.input_array, _input_array)) def test_call_with_normalisation_off(self): _input_array = empty_aligned((256, 512), dtype='complex128', n=16) ifft = FFTW(self.output_array, _input_array, direction='FFTW_BACKWARD') self.fft(normalise_idft=True) # Shouldn't make any difference ifft(normalise_idft=False) _input_array /= ifft.N self.assertTrue(numpy.allclose(self.input_array, _input_array)) def test_call_with_normalisation_default(self): _input_array = empty_aligned((256, 512), dtype='complex128', n=16) ifft = FFTW(self.output_array, _input_array, direction='FFTW_BACKWARD') self.fft() ifft() # Scaling is performed by default self.assertTrue(numpy.allclose(self.input_array, _input_array)) @unittest.skipIf(*miss('32', '64')) def test_call_with_normalisation_precision(self): '''The normalisation should use a double precision scaling. ''' # Should be the case for double inputs... _input_array = empty_aligned((256, 512), dtype='complex128', n=16) self.fft() ifft = FFTW(self.output_array, _input_array, direction='FFTW_BACKWARD') ref_output = ifft(normalise_idft=False).copy()/numpy.float64(ifft.N) test_output = ifft(normalise_idft=True).copy() self.assertTrue(numpy.alltrue(ref_output == test_output)) # ... and single inputs. _input_array = empty_aligned((256, 512), dtype='complex64', n=16) ifft = FFTW(numpy.array(self.output_array, _input_array.dtype), _input_array, direction='FFTW_BACKWARD') ref_output = ifft(normalise_idft=False).copy()/numpy.float64(ifft.N) test_output = ifft(normalise_idft=True).copy() self.assertTrue(numpy.alltrue(ref_output == test_output)) def test_call_with_ortho_on(self): _input_array = empty_aligned((256, 512), dtype='complex128', n=16) ifft = FFTW(self.output_array, _input_array, direction='FFTW_BACKWARD') self.fft(ortho=True, normalise_idft=False) # ortho case preserves the norm in forward direction self.assertTrue( numpy.allclose(numpy.linalg.norm(self.input_array), numpy.linalg.norm(self.output_array))) ifft(ortho=True, normalise_idft=False) # ortho case preserves the norm in backward direction self.assertTrue( numpy.allclose(numpy.linalg.norm(_input_array), numpy.linalg.norm(self.output_array))) self.assertTrue(numpy.allclose(self.input_array, _input_array)) # cant select both ortho and normalise_idft self.assertRaisesRegex(ValueError, 'Invalid options', self.fft, normalise_idft=True, ortho=True) # cant specify orth=True with default normalise_idft=True self.assertRaisesRegex(ValueError, 'Invalid options', self.fft, ortho=True) def test_call_with_ortho_off(self): _input_array = empty_aligned((256, 512), dtype='complex128', n=16) ifft = FFTW(self.output_array, _input_array, direction='FFTW_BACKWARD') self.fft(ortho=False) ifft(ortho=False) # Scaling by normalise_idft is performed by default self.assertTrue(numpy.allclose(self.input_array, _input_array)) test_cases = ( FFTWCallTest,) test_set = None if __name__ == '__main__': run_test_suites(test_cases, test_set) pyFFTW-0.13.1/tests/test_pyfftw_class_misc.py000066400000000000000000000346171435600752200212240ustar00rootroot00000000000000# Copyright 2014 Knowledge Economy Developments Ltd # # Henry Gomersall # heng@kedevelopments.co.uk # # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, this # list of conditions and the following disclaimer. # # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions and the following disclaimer in the documentation # and/or other materials provided with the distribution. # # * Neither the name of the copyright holder nor the names of its contributors # may be used to endorse or promote products derived from this software without # specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. # from pyfftw import ( FFTW, empty_aligned, is_byte_aligned, simd_alignment) import pyfftw from .test_pyfftw_base import run_test_suites, miss, require import unittest import numpy import warnings # FFTW tests that don't seem to fit anywhere else class FFTWMiscTest(unittest.TestCase): def __init__(self, *args, **kwargs): super(FFTWMiscTest, self).__init__(*args, **kwargs) # Assume python 3, but keep backwards compatibility if not hasattr(self, 'assertRaisesRegex'): self.assertRaisesRegex = self.assertRaisesRegexp def setUp(self): require(self, '64') self.input_array = empty_aligned((256, 512), dtype='complex128', n=16) self.output_array = empty_aligned((256, 512), dtype='complex128', n=16) self.fft = FFTW(self.input_array, self.output_array) self.output_array[:] = (numpy.random.randn(*self.output_array.shape) + 1j*numpy.random.randn(*self.output_array.shape)) def test_aligned_flag(self): '''Test to see if the aligned flag is correct ''' fft = FFTW(self.input_array, self.output_array) self.assertTrue(fft.simd_aligned) fft = FFTW(self.input_array, self.output_array, flags=('FFTW_UNALIGNED',)) self.assertFalse(fft.simd_aligned) @unittest.skipIf(*miss('32')) def test_flags(self): '''Test to see if the flags are correct ''' fft = FFTW(self.input_array, self.output_array) self.assertEqual(fft.flags, ('FFTW_MEASURE',)) fft = FFTW(self.input_array, self.output_array, flags=('FFTW_DESTROY_INPUT', 'FFTW_UNALIGNED')) self.assertEqual(fft.flags, ('FFTW_DESTROY_INPUT', 'FFTW_UNALIGNED')) # Test an implicit flag _input_array = empty_aligned(256, dtype='complex64', n=16) _output_array = empty_aligned(256, dtype='complex64', n=16) # These are guaranteed to be misaligned (due to dtype size == 8) input_array = _input_array[:-1] output_array = _output_array[:-1] u_input_array = _input_array[1:] u_output_array = _output_array[1:] fft = FFTW(input_array, u_output_array) self.assertEqual(fft.flags, ('FFTW_MEASURE', 'FFTW_UNALIGNED')) fft = FFTW(u_input_array, output_array) self.assertEqual(fft.flags, ('FFTW_MEASURE', 'FFTW_UNALIGNED')) fft = FFTW(u_input_array, u_output_array) self.assertEqual(fft.flags, ('FFTW_MEASURE', 'FFTW_UNALIGNED')) @unittest.skipIf(*miss('32')) def test_differing_aligned_arrays_update(self): '''Test to see if the alignment code is working as expected ''' # Start by creating arrays that are only on various byte # alignments (4, 16 and 32) _input_array = empty_aligned(len(self.input_array.ravel())*2+5, dtype='float32', n=32) _output_array = empty_aligned(len(self.output_array.ravel())*2+5, dtype='float32', n=32) _input_array[:] = 0 _output_array[:] = 0 input_array_4 = ( numpy.frombuffer(_input_array[1:-4].data, dtype='complex64') .reshape(self.input_array.shape)) output_array_4 = ( numpy.frombuffer(_output_array[1:-4].data, dtype='complex64') .reshape(self.output_array.shape)) input_array_16 = ( numpy.frombuffer(_input_array[4:-1].data, dtype='complex64') .reshape(self.input_array.shape)) output_array_16 = ( numpy.frombuffer(_output_array[4:-1].data, dtype='complex64') .reshape(self.output_array.shape)) input_array_32 = ( numpy.frombuffer(_input_array[:-5].data, dtype='complex64') .reshape(self.input_array.shape)) output_array_32 = ( numpy.frombuffer(_output_array[:-5].data, dtype='complex64') .reshape(self.output_array.shape)) input_arrays = {4: input_array_4, 16: input_array_16, 32: input_array_32} output_arrays = {4: output_array_4, 16: output_array_16, 32: output_array_32} alignments = (4, 16, 32) # Test the arrays are aligned on 4 bytes... self.assertTrue(is_byte_aligned(input_arrays[4], n=4)) self.assertTrue(is_byte_aligned(output_arrays[4], n=4)) # ...and on 16... self.assertFalse(is_byte_aligned(input_arrays[4], n=16)) self.assertFalse(is_byte_aligned(output_arrays[4], n=16)) self.assertTrue(is_byte_aligned(input_arrays[16], n=16)) self.assertTrue(is_byte_aligned(output_arrays[16], n=16)) # ...and on 32... self.assertFalse(is_byte_aligned(input_arrays[16], n=32)) self.assertFalse(is_byte_aligned(output_arrays[16], n=32)) self.assertTrue(is_byte_aligned(input_arrays[32], n=32)) self.assertTrue(is_byte_aligned(output_arrays[32], n=32)) if len(pyfftw.pyfftw._valid_simd_alignments) > 0: max_align = pyfftw.pyfftw._valid_simd_alignments[0] else: max_align = simd_alignment for in_align in alignments: for out_align in alignments: expected_align = min(in_align, out_align, max_align) fft = FFTW(input_arrays[in_align], output_arrays[out_align]) self.assertTrue(fft.input_alignment == expected_align) self.assertTrue(fft.output_alignment == expected_align) for update_align in alignments: if update_align < expected_align: # This should fail (not aligned properly) self.assertRaisesRegex(ValueError, 'Invalid input alignment', fft.update_arrays, input_arrays[update_align], output_arrays[out_align]) self.assertRaisesRegex(ValueError, 'Invalid output alignment', fft.update_arrays, input_arrays[in_align], output_arrays[update_align]) else: # This should work (and not segfault!) fft.update_arrays(input_arrays[update_align], output_arrays[out_align]) fft.update_arrays(input_arrays[in_align], output_arrays[update_align]) fft.execute() def test_get_input_array(self): '''Test to see the get_input_array method returns the correct thing ''' with warnings.catch_warnings(record=True) as w: # This method is deprecated, so check the deprecation warning # is raised. warnings.simplefilter("always") input_array = self.fft.get_input_array() self.assertEqual(len(w), 1) self.assertTrue(issubclass(w[-1].category, DeprecationWarning)) self.assertIs(self.input_array, input_array) def test_get_output_array(self): '''Test to see the get_output_array method returns the correct thing ''' with warnings.catch_warnings(record=True) as w: # This method is deprecated, so check the deprecation warning # is raised. warnings.simplefilter("always") output_array = self.fft.get_output_array() self.assertEqual(len(w), 1) self.assertTrue(issubclass(w[-1].category, DeprecationWarning)) self.assertIs(self.output_array, output_array) def test_input_array(self): '''Test to see the input_array property returns the correct thing ''' self.assertIs(self.input_array, self.fft.input_array) def test_output_array(self): '''Test to see the output_array property returns the correct thing ''' self.assertIs(self.output_array, self.fft.output_array) def test_input_strides(self): '''Test to see if the input_strides property returns the correct thing ''' self.assertEqual(self.fft.input_strides, self.input_array.strides) new_input_array = self.input_array[::2, ::4] new_output_array = self.output_array[::2, ::4] new_fft = FFTW(new_input_array, new_output_array) self.assertEqual(new_fft.input_strides, new_input_array.strides) def test_output_strides(self): '''Test to see if the output_strides property returns the correct thing ''' self.assertEqual(self.fft.output_strides, self.output_array.strides) new_input_array = self.output_array[::2, ::4] new_output_array = self.output_array[::2, ::4] new_fft = FFTW(new_input_array, new_output_array) self.assertEqual(new_fft.output_strides, new_output_array.strides) def test_input_shape(self): '''Test to see if the input_shape property returns the correct thing ''' self.assertEqual(self.fft.input_shape, self.input_array.shape) new_input_array = self.input_array[::2, ::4] new_output_array = self.output_array[::2, ::4] new_fft = FFTW(new_input_array, new_output_array) self.assertEqual(new_fft.input_shape, new_input_array.shape) def test_output_strides(self): '''Test to see if the output_shape property returns the correct thing ''' self.assertEqual(self.fft.output_shape, self.output_array.shape) new_input_array = self.output_array[::2, ::4] new_output_array = self.output_array[::2, ::4] new_fft = FFTW(new_input_array, new_output_array) self.assertEqual(new_fft.output_shape, new_output_array.shape) @unittest.skipIf(*miss('32')) def test_input_dtype(self): '''Test to see if the input_dtype property returns the correct thing ''' self.assertEqual(self.fft.input_dtype, self.input_array.dtype) new_input_array = numpy.complex64(self.input_array) new_output_array = numpy.complex64(self.output_array) new_fft = FFTW(new_input_array, new_output_array) self.assertEqual(new_fft.input_dtype, new_input_array.dtype) @unittest.skipIf(*miss('32')) def test_output_dtype(self): '''Test to see if the output_dtype property returns the correct thing ''' self.assertEqual(self.fft.output_dtype, self.output_array.dtype) new_input_array = numpy.complex64(self.input_array) new_output_array = numpy.complex64(self.output_array) new_fft = FFTW(new_input_array, new_output_array) self.assertEqual(new_fft.output_dtype, new_output_array.dtype) def test_direction_property(self): '''Test to see if the direction property returns the correct thing ''' self.assertEqual(self.fft.direction, 'FFTW_FORWARD') new_fft = FFTW(self.input_array, self.output_array, direction='FFTW_BACKWARD') self.assertEqual(new_fft.direction, 'FFTW_BACKWARD') def test_axes_property(self): '''Test to see if the axes property returns the correct thing ''' self.assertEqual(self.fft.axes, (1,)) new_fft = FFTW(self.input_array, self.output_array, axes=(-1, -2)) self.assertEqual(new_fft.axes, (1, 0)) new_fft = FFTW(self.input_array, self.output_array, axes=(-2, -1)) self.assertEqual(new_fft.axes, (0, 1)) new_fft = FFTW(self.input_array, self.output_array, axes=(1, 0)) self.assertEqual(new_fft.axes, (1, 0)) new_fft = FFTW(self.input_array, self.output_array, axes=(1,)) self.assertEqual(new_fft.axes, (1,)) new_fft = FFTW(self.input_array, self.output_array, axes=(0,)) self.assertEqual(new_fft.axes, (0,)) def test_ortho_property(self): '''ortho property defaults to False ''' self.assertEqual(self.fft.ortho, False) newfft = FFTW(self.input_array, self.output_array, ortho=True, normalise_idft=False) self.assertEqual(newfft.ortho, True) def test_normalise_idft_property(self): '''normalise_idft property defaults to True ''' self.assertEqual(self.fft.normalise_idft, True) newfft = FFTW(self.input_array, self.output_array, normalise_idft=False) self.assertEqual(newfft.normalise_idft, False) def test_invalid_normalisation(self): # both ortho and normalise_idft cannot be True self.assertRaisesRegex( ValueError, 'Invalid options: ortho', FFTW, self.input_array, self.output_array, direction='FFTW_BACKWARD', ortho=True, normalise_idft=True) test_cases = ( FFTWMiscTest,) test_set = None if __name__ == '__main__': run_test_suites(test_cases, test_set) pyFFTW-0.13.1/tests/test_pyfftw_complex.py000066400000000000000000000635501435600752200205510ustar00rootroot00000000000000# # Copyright 2014 Knowledge Economy Developments Ltd # # Henry Gomersall # heng@kedevelopments.co.uk # # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, this # list of conditions and the following disclaimer. # # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions and the following disclaimer in the documentation # and/or other materials provided with the distribution. # # * Neither the name of the copyright holder nor the names of its contributors # may be used to endorse or promote products derived from this software without # specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. # from pyfftw import FFTW, byte_align, empty_aligned, forget_wisdom import pyfftw import numpy from timeit import Timer import time import unittest from .test_pyfftw_base import FFTWBaseTest, run_test_suites, miss, np_fft # We make this 1D case not inherit from FFTWBaseTest. # It needs to be combined with FFTWBaseTest to work. # This allows us to separate out tests that are use # in multiple locations. class Complex64FFTW1DTest(object): def test_time(self): in_shape = self.input_shapes['2d'] out_shape = self.output_shapes['2d'] axes=(-1,) a, b = self.create_test_arrays(in_shape, out_shape) fft, ifft = self.run_validate_fft(a, b, axes) self.timer_routine(fft.execute, lambda: self.np_fft_comparison(a)) self.assertTrue(True) def test_invalid_args_raise(self): in_shape = self.input_shapes['1d'] out_shape = self.output_shapes['1d'] axes=(-1,) a, b = self.create_test_arrays(in_shape, out_shape) # Note "thread" is incorrect, it should be "threads" self.assertRaises(TypeError, FFTW, a, b, axes, thread=4) def test_1d(self): in_shape = self.input_shapes['1d'] out_shape = self.output_shapes['1d'] axes=(0,) a, b = self.create_test_arrays(in_shape, out_shape) self.run_validate_fft(a, b, axes) def test_multiple_1d(self): in_shape = self.input_shapes['2d'] out_shape = self.output_shapes['2d'] axes=(-1,) a, b = self.create_test_arrays(in_shape, out_shape) self.run_validate_fft(a, b, axes) def test_default_args(self): in_shape = self.input_shapes['2d'] out_shape = self.output_shapes['2d'] a, b = self.create_test_arrays(in_shape, out_shape) fft = FFTW(a,b) fft.execute() ref_b = self.reference_fftn(a, axes=(-1,)) self.assertTrue(numpy.allclose(b, ref_b, rtol=1e-2, atol=1e-3)) def test_time_with_array_update(self): in_shape = self.input_shapes['2d'] out_shape = self.output_shapes['2d'] axes=(-1,) a, b = self.create_test_arrays(in_shape, out_shape) fft, ifft = self.run_validate_fft(a, b, axes) def fftw_callable(): fft.update_arrays(a,b) fft.execute() self.timer_routine(fftw_callable, lambda: self.np_fft_comparison(a)) self.assertTrue(True) def test_planning_time_limit(self): in_shape = self.input_shapes['1d'] out_shape = self.output_shapes['1d'] axes=(0,) a, b = self.create_test_arrays(in_shape, out_shape) # run this a few times runs = 10 t1 = time.time() for n in range(runs): forget_wisdom() fft = FFTW(a, b, axes=axes) unlimited_time = (time.time() - t1)/runs time_limit = (unlimited_time)/8 # Now do it again but with an upper limit on the time t1 = time.time() for n in range(runs): forget_wisdom() fft = FFTW(a, b, axes=axes, planning_timelimit=time_limit) limited_time = (time.time() - t1)/runs import sys if sys.platform == 'win32': # Give a 6x margin on windows. The timers are low # precision and FFTW seems to take longer anyway. # Also, we need to allow for processor contention which # Appveyor seems prone to. self.assertTrue(limited_time < time_limit*6) else: # Otherwise have a 2x margin self.assertTrue(limited_time < time_limit*2) def test_invalid_planning_time_limit(self): in_shape = self.input_shapes['1d'] out_shape = self.output_shapes['1d'] axes=(0,) a, b = self.create_test_arrays(in_shape, out_shape) self.assertRaisesRegex(TypeError, 'Invalid planning timelimit', FFTW, *(a,b, axes), **{'planning_timelimit': 'foo'}) def test_planner_flags(self): '''Test all the planner flags on a small array ''' in_shape = self.input_shapes['small_1d'] out_shape = self.output_shapes['small_1d'] axes=(0,) a, b = self.create_test_arrays(in_shape, out_shape) for each_flag in pyfftw.pyfftw._flag_dict: if each_flag == 'FFTW_WISDOM_ONLY': continue fft, ifft = self.run_validate_fft(a, b, axes, flags=(each_flag,)) self.assertTrue(each_flag in fft.flags) self.assertTrue(each_flag in ifft.flags) # also, test no flags (which should still work) fft, ifft = self.run_validate_fft(a, b, axes, flags=()) def test_wisdom_only(self): in_shape = self.input_shapes['small_1d'] out_shape = self.output_shapes['small_1d'] axes=(0,) a, b = self.create_test_arrays(in_shape, out_shape) forget_wisdom() # with no wisdom, an error should be raised with FFTW_WISDOM_ONLY # # NB: wisdom is specific to aligned/unaligned distinction, so we must # ensure that the arrays don't get copied (and potentially # switched between aligned and unaligned) by run_validate_fft()... self.assertRaisesRegex(RuntimeError, 'No FFTW wisdom', self.run_validate_fft, *(a, b, axes), **{'flags':('FFTW_ESTIMATE', 'FFTW_WISDOM_ONLY'), 'create_array_copies': False}) # now plan the FFT self.run_validate_fft(a, b, axes, flags=('FFTW_ESTIMATE',), create_array_copies=False) # now FFTW_WISDOM_ONLY should not raise an error because the plan should # be in the wisdom self.run_validate_fft(a, b, axes, flags=('FFTW_ESTIMATE', 'FFTW_WISDOM_ONLY'), create_array_copies=False) def test_destroy_input(self): '''Test the destroy input flag ''' # We can't really test it actually destroys the input, as it might # not (plus it's not exactly something we want). # It's enough just to check it runs ok with that flag. in_shape = self.input_shapes['1d'] out_shape = self.output_shapes['1d'] axes=(0,) a, b = self.create_test_arrays(in_shape, out_shape) self.run_validate_fft(a, b, axes, flags=('FFTW_ESTIMATE','FFTW_DESTROY_INPUT')) def test_invalid_flag_fail(self): '''Test passing a garbage flag fails ''' in_shape = self.input_shapes['1d'] out_shape = self.output_shapes['1d'] axes=(0,) a, b = self.create_test_arrays(in_shape, out_shape) self.assertRaisesRegex(ValueError, 'Invalid flag', self.run_validate_fft, *(a, b, axes), **{'flags':('garbage',)}) def test_alignment(self): '''Test to see if the alignment is returned correctly ''' in_shape = self.input_shapes['2d'] out_shape = self.output_shapes['2d'] input_dtype_alignment = self.get_input_dtype_alignment() output_dtype_alignment = self.get_output_dtype_alignment() axes=(-1,) a, b = self.create_test_arrays(in_shape, out_shape) a = byte_align(a, n=16) b = byte_align(b, n=16) fft, ifft = self.run_validate_fft(a, b, axes, force_unaligned_data=True) a, b = self.create_test_arrays(in_shape, out_shape) a = byte_align(a, n=16) b = byte_align(b, n=16) a_orig = a.copy() b_orig = b.copy() # Offset from 16 byte aligned to guarantee it's not # 16 byte aligned a__ = empty_aligned( numpy.prod(in_shape)*a.itemsize + input_dtype_alignment, dtype='int8', n=16) a_ = (a__[input_dtype_alignment:] .view(dtype=self.input_dtype).reshape(*in_shape)) a_[:] = a b__ = empty_aligned( numpy.prod(out_shape)*b.itemsize + input_dtype_alignment, dtype='int8', n=16) b_ = (b__[input_dtype_alignment:] .view(dtype=self.output_dtype).reshape(*out_shape)) b_[:] = b a[:] = a_orig fft, ifft = self.run_validate_fft(a, b, axes, create_array_copies=False) self.assertTrue(fft.input_alignment == 16) self.assertTrue(fft.output_alignment == 16) a[:] = a_orig fft, ifft = self.run_validate_fft(a, b_, axes, create_array_copies=False) self.assertTrue(fft.input_alignment == input_dtype_alignment) self.assertTrue(fft.output_alignment == output_dtype_alignment) a_[:] = a_orig fft, ifft = self.run_validate_fft(a_, b, axes, create_array_copies=False) self.assertTrue(fft.input_alignment == input_dtype_alignment) self.assertTrue(fft.output_alignment == output_dtype_alignment) a_[:] = a_orig fft, ifft = self.run_validate_fft(a_, b_, axes, create_array_copies=False) self.assertTrue(fft.input_alignment == input_dtype_alignment) self.assertTrue(fft.output_alignment == output_dtype_alignment) a[:] = a_orig fft, ifft = self.run_validate_fft(a, b, axes, create_array_copies=False, force_unaligned_data=True) self.assertTrue(fft.input_alignment == input_dtype_alignment) self.assertTrue(fft.output_alignment == output_dtype_alignment) def test_incorrect_byte_alignment_fails(self): in_shape = self.input_shapes['2d'] out_shape = self.output_shapes['2d'] input_dtype_alignment = self.get_input_dtype_alignment() axes=(-1,) a, b = self.create_test_arrays(in_shape, out_shape) a = byte_align(a, n=16) b = byte_align(b, n=16) fft, ifft = self.run_validate_fft(a, b, axes, force_unaligned_data=True) a, b = self.create_test_arrays(in_shape, out_shape) # Offset from 16 byte aligned to guarantee it's not # 16 byte aligned a__ = empty_aligned( numpy.prod(in_shape)*a.itemsize + 1, dtype='int8', n=16) a_ = a__[1:].view(dtype=self.input_dtype).reshape(*in_shape) a_[:] = a b__ = empty_aligned( numpy.prod(out_shape)*b.itemsize + 1, dtype='int8', n=16) b_ = b__[1:].view(dtype=self.output_dtype).reshape(*out_shape) b_[:] = b self.assertRaisesRegex(ValueError, 'Invalid output alignment', FFTW, *(a, b_)) self.assertRaisesRegex(ValueError, 'Invalid input alignment', FFTW, *(a_, b)) self.assertRaisesRegex(ValueError, 'Invalid input alignment', FFTW, *(a_, b_)) def test_zero_length_fft_axis_fail(self): in_shape = (1024, 0) out_shape = in_shape axes = (-1,) a, b = self.create_test_arrays(in_shape, out_shape) self.assertRaisesRegex(ValueError, 'Zero length array', self.run_validate_fft, *(a,b, axes)) def test_missized_fail(self): in_shape = self.input_shapes['2d'] _out_shape = self.output_shapes['2d'] out_shape = (_out_shape[0]+1, _out_shape[1]) axes=(0,) a, b = self.create_test_arrays(in_shape, out_shape) with self.assertRaisesRegex(ValueError, 'Invalid shapes'): FFTW(a, b, axes, direction=self.direction) def test_missized_nonfft_axes_fail(self): in_shape = self.input_shapes['3d'] _out_shape = self.output_shapes['3d'] out_shape = (_out_shape[0], _out_shape[1]+1, _out_shape[2]) axes=(2,) a, b = self.create_test_arrays(in_shape, out_shape) with self.assertRaisesRegex(ValueError, 'Invalid shapes'): FFTW(a, b, direction=self.direction) def test_extra_dimension_fail(self): in_shape = self.input_shapes['2d'] _out_shape = self.output_shapes['2d'] out_shape = (2, _out_shape[0], _out_shape[1]) axes=(1,) a, b = self.create_test_arrays(in_shape, out_shape) with self.assertRaisesRegex(ValueError, 'Invalid shapes'): FFTW(a, b, direction=self.direction) def test_f_contiguous_1d(self): in_shape = self.input_shapes['2d'] out_shape = self.output_shapes['2d'] axes=(0,) a, b = self.create_test_arrays(in_shape, out_shape) # Taking the transpose just makes the array F contiguous a = a.transpose() b = b.transpose() self.run_validate_fft(a, b, axes, create_array_copies=False) def test_different_dtypes_fail(self): in_shape = self.input_shapes['2d'] out_shape = self.output_shapes['2d'] axes=(-1,) a, b = self.create_test_arrays(in_shape, out_shape) a_ = numpy.complex64(a) b_ = numpy.complex128(b) self.assertRaisesRegex(ValueError, 'Invalid scheme', FFTW, *(a_,b_)) a_ = numpy.complex128(a) b_ = numpy.complex64(b) self.assertRaisesRegex(ValueError, 'Invalid scheme', FFTW, *(a_,b_)) def test_update_data(self): in_shape = self.input_shapes['2d'] out_shape = self.output_shapes['2d'] axes=(-1,) a, b = self.create_test_arrays(in_shape, out_shape) fft, ifft = self.run_validate_fft(a, b, axes) a, b = self.create_test_arrays(in_shape, out_shape) self.run_validate_fft(a, b, axes, fft=fft, ifft=ifft) def test_with_not_ndarray_error(self): in_shape = self.input_shapes['2d'] out_shape = self.output_shapes['2d'] a, b = self.create_test_arrays(in_shape, out_shape) self.assertRaisesRegex(ValueError, 'Invalid output array', FFTW, *(a,10)) self.assertRaisesRegex(ValueError, 'Invalid input array', FFTW, *(10,b)) def test_update_data_with_not_ndarray_error(self): in_shape = self.input_shapes['2d'] out_shape = self.output_shapes['2d'] axes=(-1,) a, b = self.create_test_arrays(in_shape, out_shape) fft, ifft = self.run_validate_fft(a, b, axes, create_array_copies=False) self.assertRaisesRegex(ValueError, 'Invalid output array', fft.update_arrays, *(a,10)) self.assertRaisesRegex(ValueError, 'Invalid input array', fft.update_arrays, *(10,b)) def test_update_data_with_stride_error(self): in_shape = self.input_shapes['2d'] out_shape = self.output_shapes['2d'] axes=(-1,) a, b = self.create_test_arrays(in_shape, out_shape) fft, ifft = self.run_validate_fft(a, b, axes, create_array_copies=False) # We offset by 16 to make sure the byte alignment is still correct. in_shape = (in_shape[0]+16, in_shape[1]+16) out_shape = (out_shape[0]+16, out_shape[1]+16) a_, b_ = self.create_test_arrays(in_shape, out_shape) a_ = a_[16:,16:] b_ = b_[16:,16:] with self.assertRaisesRegex(ValueError, 'Invalid input striding'): self.run_validate_fft(a_, b, axes, fft=fft, ifft=ifft, create_array_copies=False) with self.assertRaisesRegex(ValueError, 'Invalid output striding'): self.run_validate_fft(a, b_, axes, fft=fft, ifft=ifft, create_array_copies=False) def test_update_data_with_shape_error(self): in_shape = self.input_shapes['2d'] out_shape = self.output_shapes['2d'] axes=(-1,) a, b = self.create_test_arrays(in_shape, out_shape) fft, ifft = self.run_validate_fft(a, b, axes) in_shape = (in_shape[0]-10, in_shape[1]) out_shape = (out_shape[0], out_shape[1]+5) a_, b_ = self.create_test_arrays(in_shape, out_shape) with self.assertRaisesRegex(ValueError, 'Invalid input shape'): self.run_validate_fft(a_, b, axes, fft=fft, ifft=ifft, create_array_copies=False) with self.assertRaisesRegex(ValueError, 'Invalid output shape'): self.run_validate_fft(a, b_, axes, fft=fft, ifft=ifft, create_array_copies=False) def test_update_unaligned_data_with_FFTW_UNALIGNED(self): in_shape = self.input_shapes['2d'] out_shape = self.output_shapes['2d'] input_dtype_alignment = self.get_input_dtype_alignment() axes=(-1,) a, b = self.create_test_arrays(in_shape, out_shape) a = byte_align(a, n=16) b = byte_align(b, n=16) fft, ifft = self.run_validate_fft(a, b, axes, force_unaligned_data=True) a, b = self.create_test_arrays(in_shape, out_shape) # Offset from 16 byte aligned to guarantee it's not # 16 byte aligned a__ = empty_aligned( numpy.prod(in_shape)*a.itemsize + input_dtype_alignment, dtype='int8', n=16) a_ = (a__[input_dtype_alignment:] .view(dtype=self.input_dtype).reshape(*in_shape)) a_[:] = a b__ = empty_aligned( numpy.prod(out_shape)*b.itemsize + input_dtype_alignment, dtype='int8', n=16) b_ = (b__[input_dtype_alignment:] .view(dtype=self.output_dtype).reshape(*out_shape)) b_[:] = b self.run_validate_fft(a, b_, axes, fft=fft, ifft=ifft) self.run_validate_fft(a_, b, axes, fft=fft, ifft=ifft) self.run_validate_fft(a_, b_, axes, fft=fft, ifft=ifft) def test_update_data_with_unaligned_original(self): in_shape = self.input_shapes['2d'] out_shape = self.output_shapes['2d'] input_dtype_alignment = self.get_input_dtype_alignment() axes=(-1,) a, b = self.create_test_arrays(in_shape, out_shape) # Offset from 16 byte aligned to guarantee it's not # 16 byte aligned a__ = empty_aligned( numpy.prod(in_shape)*a.itemsize + input_dtype_alignment, dtype='int8', n=16) a_ = a__[input_dtype_alignment:].view(dtype=self.input_dtype).reshape(*in_shape) a_[:] = a b__ = empty_aligned( numpy.prod(out_shape)*b.itemsize + input_dtype_alignment, dtype='int8', n=16) b_ = b__[input_dtype_alignment:].view(dtype=self.output_dtype).reshape(*out_shape) b_[:] = b fft, ifft = self.run_validate_fft(a_, b_, axes, force_unaligned_data=True) self.run_validate_fft(a, b_, axes, fft=fft, ifft=ifft) self.run_validate_fft(a_, b, axes, fft=fft, ifft=ifft) self.run_validate_fft(a_, b_, axes, fft=fft, ifft=ifft) def test_update_data_with_alignment_error(self): in_shape = self.input_shapes['2d'] out_shape = self.output_shapes['2d'] byte_error = 1 axes=(-1,) a, b = self.create_test_arrays(in_shape, out_shape) a = byte_align(a, n=16) b = byte_align(b, n=16) fft, ifft = self.run_validate_fft(a, b, axes) a, b = self.create_test_arrays(in_shape, out_shape) # Offset from 16 byte aligned to guarantee it's not # 16 byte aligned a__ = empty_aligned( numpy.prod(in_shape)*a.itemsize+byte_error, dtype='int8', n=16) a_ = (a__[byte_error:] .view(dtype=self.input_dtype).reshape(*in_shape)) a_[:] = a b__ = empty_aligned( numpy.prod(out_shape)*b.itemsize+byte_error, dtype='int8', n=16) b_ = (b__[byte_error:] .view(dtype=self.output_dtype).reshape(*out_shape)) b_[:] = b with self.assertRaisesRegex(ValueError, 'Invalid output alignment'): self.run_validate_fft(a, b_, axes, fft=fft, ifft=ifft, create_array_copies=False) with self.assertRaisesRegex(ValueError, 'Invalid input alignment'): self.run_validate_fft(a_, b, axes, fft=fft, ifft=ifft, create_array_copies=False) # Should also be true for the unaligned case fft, ifft = self.run_validate_fft(a, b, axes, force_unaligned_data=True) with self.assertRaisesRegex(ValueError, 'Invalid output alignment'): self.run_validate_fft(a, b_, axes, fft=fft, ifft=ifft, create_array_copies=False) with self.assertRaisesRegex(ValueError, 'Invalid input alignment'): self.run_validate_fft(a_, b, axes, fft=fft, ifft=ifft, create_array_copies=False) def test_invalid_axes(self): in_shape = self.input_shapes['2d'] out_shape = self.output_shapes['2d'] axes=(-3,) a, b = self.create_test_arrays(in_shape, out_shape) with self.assertRaisesRegex(IndexError, 'Invalid axes'): FFTW(a, b, axes, direction=self.direction) axes=(10,) with self.assertRaisesRegex(IndexError, 'Invalid axes'): FFTW(a, b, axes, direction=self.direction) class Complex64FFTWTest(Complex64FFTW1DTest, FFTWBaseTest): def test_2d(self): in_shape = self.input_shapes['2d'] out_shape = self.output_shapes['2d'] axes=(-2,-1) a, b = self.create_test_arrays(in_shape, out_shape) self.run_validate_fft(a, b, axes, create_array_copies=False) def test_multiple_2d(self): in_shape = self.input_shapes['3d'] out_shape = self.output_shapes['3d'] axes=(-2,-1) a, b = self.create_test_arrays(in_shape, out_shape) self.run_validate_fft(a, b, axes, create_array_copies=False) def test_3d(self): in_shape = self.input_shapes['3d'] out_shape = self.output_shapes['3d'] axes=(0, 1, 2) a, b = self.create_test_arrays(in_shape, out_shape) self.run_validate_fft(a, b, axes, create_array_copies=False) def test_non_monotonic_increasing_axes(self): '''Test the case where the axes arg does not monotonically increase. ''' axes=(1, 0) # We still need the shapes to work! in_shape = numpy.asarray(self.input_shapes['2d'])[list(axes)] out_shape = numpy.asarray(self.output_shapes['2d'])[list(axes)] a, b = self.create_test_arrays(in_shape, out_shape, axes=axes) self.run_validate_fft(a, b, axes, create_array_copies=False) def test_non_contiguous_2d(self): in_shape = self.input_shapes['2d'] out_shape = self.output_shapes['2d'] axes=(-2,-1) a, b = self.create_test_arrays(in_shape, out_shape) # Some arbitrary and crazy slicing a_sliced = a[12:200:3, 300:2041:9] # b needs to be the same size b_sliced = b[20:146:2, 100:1458:7] self.run_validate_fft(a_sliced, b_sliced, axes, create_array_copies=False) def test_non_contiguous_2d_in_3d(self): in_shape = (256, 4, 2048) out_shape = in_shape axes=(0,2) a, b = self.create_test_arrays(in_shape, out_shape) # Some arbitrary and crazy slicing a_sliced = a[12:200:3, :, 300:2041:9] # b needs to be the same size b_sliced = b[20:146:2, :, 100:1458:7] self.run_validate_fft(a_sliced, b_sliced, axes, create_array_copies=False) @unittest.skipIf(*miss('64')) class Complex128FFTWTest(Complex64FFTWTest): def setUp(self): self.input_dtype = numpy.complex128 self.output_dtype = numpy.complex128 self.np_fft_comparison = np_fft.fft self.direction = 'FFTW_FORWARD' return @unittest.skipIf(*miss('ld')) class ComplexLongDoubleFFTWTest(Complex64FFTWTest): def setUp(self): self.input_dtype = numpy.clongdouble self.output_dtype = numpy.clongdouble self.np_fft_comparison = self.reference_fftn self.direction = 'FFTW_FORWARD' return def reference_fftn(self, a, axes): # numpy.fft.fftn doesn't support complex256 type, # so we need to compare to a lower precision type. a = numpy.complex128(a) return np_fft.fftn(a, axes=axes) @unittest.skip('numpy.fft has issues with this dtype.') def test_time(self): pass @unittest.skip('numpy.fft has issues with this dtype.') def test_time_with_array_update(self): pass test_cases = ( Complex64FFTWTest, Complex128FFTWTest, ComplexLongDoubleFFTWTest,) test_set = None #test_set = {'all':['test_alignment', 'test_incorrect_byte_alignment_fails']} if __name__ == '__main__': run_test_suites(test_cases, test_set) pyFFTW-0.13.1/tests/test_pyfftw_config.py000066400000000000000000000061001435600752200203330ustar00rootroot00000000000000 from pyfftw import config, _threading_type from .test_pyfftw_base import run_test_suites import unittest import os from numpy.testing import assert_equal class ConfigTest(unittest.TestCase): env_keys = ['PYFFTW_NUM_THREADS', 'OMP_NUM_THREADS', 'PYFFTW_PLANNER_EFFORT'] orig_env = {} def setUp(self): # store environment variables prior to testing for key in self.env_keys: self.orig_env[key] = os.environ.get(key, None) return def tearDown(self): # restore original environment variables values for key in self.env_keys: val = self.orig_env[key] if val is None: os.environ.pop(key, None) else: os.environ[key] = val return def test_default_config(self): # unset environment variables if they were defined os.environ.pop('PYFFTW_NUM_THREADS', None) os.environ.pop('OMP_NUM_THREADS', None) os.environ.pop('PYFFTW_PLANNER_EFFORT', None) # defaults to single-threaded and FFTW_ESTIMATE config._reload_config() assert_equal(config.NUM_THREADS, 1) assert_equal(config.PLANNER_EFFORT, 'FFTW_ESTIMATE') @unittest.skipIf(_threading_type != 'OMP', reason='non-OpenMP build') def test_default_threads_OpenMP(self): # unset environment variables if they were defined os.environ.pop('PYFFTW_NUM_THREADS', None) os.environ.pop('OMP_NUM_THREADS', None) # defaults to single-threaded if neither variable is defined config._reload_config() assert_equal(config.NUM_THREADS, 1) # load default from OMP_NUM_THREADS environment variable os.environ['OMP_NUM_THREADS'] = '2' config._reload_config() assert_equal(config.NUM_THREADS, 2) # PYFFTW_NUM_THREADS overrides OMP_NUM_THREADS when both are defined os.environ['PYFFTW_NUM_THREADS'] = '4' config._reload_config() assert_equal(config.NUM_THREADS, 4) def test_non_default_config(self): # set environment variables to non-default values if _threading_type is None: os.environ['PYFFTW_NUM_THREADS'] = '1' else: os.environ['PYFFTW_NUM_THREADS'] = '4' os.environ['PYFFTW_PLANNER_EFFORT'] = 'FFTW_MEASURE' config._reload_config() assert_equal(config.NUM_THREADS, 4) assert_equal(config.PLANNER_EFFORT, 'FFTW_MEASURE') # set values to something else config.NUM_THREADS = 6 config.PLANNER_EFFORT = 'FFTW_ESTIMATE' # _reload_config preserves the user-defined values config._reload_config() assert_equal(config.NUM_THREADS, 6) assert_equal(config.PLANNER_EFFORT, 'FFTW_ESTIMATE') # can reset back to the values from the environment variables config._env_reloader.reset() assert_equal(config.NUM_THREADS, 4) assert_equal(config.PLANNER_EFFORT, 'FFTW_MEASURE') test_cases = (ConfigTest, ) test_set = None if __name__ == '__main__': run_test_suites(test_cases, test_set) pyFFTW-0.13.1/tests/test_pyfftw_dask_interface.py000066400000000000000000000473131435600752200220430ustar00rootroot00000000000000# Copyright 2015 Knowledge Economy Developments Ltd # # Henry Gomersall # heng@kedevelopments.co.uk # # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, this # list of conditions and the following disclaimer. # # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions and the following disclaimer in the documentation # and/or other materials provided with the distribution. # # * Neither the name of the copyright holder nor the names of its contributors # may be used to endorse or promote products derived from this software without # specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. # from pyfftw import interfaces try: interfaces.dask_fft except AttributeError: interfaces.dask_fft = None try: # Kludge to skip dask tests when mkl_fft is present import mkl_fft interfaces.dask_fft = None except ImportError: pass from .test_pyfftw_base import run_test_suites from .test_pyfftw_numpy_interface import complex_dtypes, real_dtypes from ._get_default_args import get_default_args from distutils.version import LooseVersion import unittest import numpy if interfaces.dask_fft: import dask.array as da from dask.array import fft as da_fft from dask.array.fft import fft_wrap else: da = None da_fft = None fft_wrap = None import warnings import copy warnings.filterwarnings('always') def make_complex_data(shape, dtype): ar, ai = dtype(numpy.random.randn(2, *shape)) ac = ar + 1j*ai return da.from_array(ac, chunks=shape) def make_real_data(shape, dtype): ar = dtype(numpy.random.randn(*shape)) return da.from_array(ar, chunks=shape) def _dask_array_fft_has_norm_kwarg(): """returns True if dask.array's fft supports the norm keyword argument """ return False functions = { 'fft': 'complex', 'fft2': 'complex', 'fftn': 'complex', 'ifft': 'complex', 'ifft2': 'complex', 'ifftn': 'complex', 'rfft': 'r2c', 'rfft2': 'r2c', 'rfftn': 'r2c', 'irfft': 'c2r', 'irfft2': 'c2r', 'irfftn': 'c2r', 'hfft': 'c2r', 'ihfft': 'r2c'} acquired_names = ('fft_wrap', 'fftfreq', 'rfftfreq', 'fftshift', 'ifftshift') @unittest.skipIf( not interfaces.dask_fft, "dask interface is not available, so skipping tests." ) class InterfacesDaskFFTTestModule(unittest.TestCase): ''' A really simple test suite to check the module works as expected. ''' def test_acquired_names(self): for each_name in acquired_names: da_fft_attr = getattr(da_fft, each_name) acquired_attr = getattr(interfaces.dask_fft, each_name) self.assertIs(da_fft_attr, acquired_attr) @unittest.skipIf( not interfaces.dask_fft, "dask interface is not available, so skipping tests." ) class InterfacesDaskFFTTestFFT(unittest.TestCase): io_dtypes = { 'complex': (complex_dtypes, make_complex_data), 'r2c': (real_dtypes, make_real_data), 'c2r': (complex_dtypes, make_complex_data)} validator_module = da_fft test_wrapped_interface = interfaces.numpy_fft test_interface = interfaces.dask_fft func = 'fft' axes_kw = 'axis' default_s_from_shape_slicer = slice(-1, None) test_shapes = ( ((100,), {}), ((128, 64), {'axis': 0}), ((128, 32), {'axis': -1}), ((59, 100), {}), ((59, 99), {'axis': -1}), ((59, 99), {'axis': 0}), ((32, 32, 4), {'axis': 1}), ((32, 32, 2), {'axis': 1, 'norm': 'ortho'}), ((32, 32, 2), {'axis': 1, 'norm': None}), ((32, 32, 2), {'axis': 1, 'norm': 'backward'}), ((32, 32, 2), {'axis': 1, 'norm': 'forward'}), ((64, 128, 16), {}), ) realinv = False has_norm_kwarg = _dask_array_fft_has_norm_kwarg() @property def test_data(self): for test_shape, kwargs in self.test_shapes: axes = self.axes_from_kwargs(kwargs) s = self.s_from_kwargs(test_shape, kwargs) if not self.has_norm_kwarg and 'norm' in kwargs: kwargs.pop('norm') if self.realinv: test_shape = list(test_shape) test_shape[axes[-1]] = test_shape[axes[-1]]//2 + 1 test_shape = tuple(test_shape) yield test_shape, s, kwargs def __init__(self, *args, **kwargs): super(InterfacesDaskFFTTestFFT, self).__init__(*args, **kwargs) # Assume python 3, but keep backwards compatibility if not hasattr(self, 'assertRaisesRegex'): self.assertRaisesRegex = self.assertRaisesRegexp def validate(self, array_type, test_shape, dtype, s, kwargs): # Do it without the cache # without: interfaces.cache.disable() self._validate(array_type, test_shape, dtype, s, kwargs) def munge_input_array(self, array, kwargs): return array def _validate(self, array_type, test_shape, dtype, s, kwargs): input_array = self.munge_input_array( array_type(test_shape, dtype), kwargs) orig_input_array = input_array np_input_array = numpy.asarray(input_array) if np_input_array.dtype == 'clongdouble': np_input_array = numpy.complex128(input_array) elif np_input_array.dtype == 'longdouble': np_input_array = numpy.float64(input_array) da_input_array = da.from_array(np_input_array, chunks=np_input_array.shape) with warnings.catch_warnings(record=True) as w: # We catch the warnings so as to pick up on when # a complex array is turned into a real array if 'axes' in kwargs: validator_kwargs = {'axes': kwargs['axes']} elif 'axis' in kwargs: validator_kwargs = {'axis': kwargs['axis']} else: validator_kwargs = {} if self.has_norm_kwarg and 'norm' in kwargs: validator_kwargs['norm'] = kwargs['norm'] try: test_out_array = getattr(self.validator_module, self.func)( da_input_array, s, **validator_kwargs) except Exception as e: interface_exception = None try: getattr(self.test_interface, self.func)( input_array, s, **kwargs) except Exception as _interface_exception: # It's necessary to assign the exception to the # already defined variable in Python 3. # See http://www.python.org/dev/peps/pep-3110/#semantic-changes interface_exception = _interface_exception # If the test interface raised, so must this. self.assertEqual(type(interface_exception), type(e), msg='Interface exception raised. ' + 'Testing for: ' + repr(e)) return output_array = getattr(self.test_interface, self.func)( input_array, s, **kwargs) if (functions[self.func] == 'r2c'): if input_array.dtype.kind == 'c': if len(w) > 0: # Make sure a warning is raised self.assertIs( w[-1].category, numpy.ComplexWarning) # convert dask arrays to NumPy ones prior to calling allclose output_np = output_array.compute() input_np = input_array.compute() test_out_array = test_out_array.compute() self.assertTrue( numpy.allclose(output_np, test_out_array, rtol=1e-2, atol=1e-4)) if input_np.real.dtype == numpy.float16: # FFTW output will never be single precision for half precision # inputs as there is no half-precision FFTW routine input_precision_dtype = numpy.float32 else: input_precision_dtype = input_np.real.dtype self.assertEqual(input_precision_dtype, output_array.real.dtype) self.assertTrue(numpy.allclose(input_array, orig_input_array)) return output_array def axes_from_kwargs(self, kwargs): default_args = get_default_args( getattr(self.test_wrapped_interface, self.func)) if 'axis' in kwargs: axes = (kwargs['axis'],) elif 'axes' in kwargs: axes = kwargs['axes'] if axes is None: axes = default_args['axes'] else: if 'axis' in default_args: # default 1D axes = (default_args['axis'],) else: # default nD axes = default_args['axes'] if axes is None: axes = (-1,) return axes def s_from_kwargs(self, test_shape, kwargs): ''' Return either a scalar s or a tuple depending on whether axis or axes is specified ''' default_args = get_default_args( getattr(self.test_wrapped_interface, self.func)) if 'axis' in kwargs: s = test_shape[kwargs['axis']] elif 'axes' in kwargs: axes = kwargs['axes'] if axes is not None: s = [] for each_axis in axes: s.append(test_shape[each_axis]) else: # default nD s = [] try: for each_axis in default_args['axes']: s.append(test_shape[each_axis]) except TypeError: try: s = list(test_shape[ self.default_s_from_shape_slicer]) except TypeError: # We had an integer as the default, so force # it to be a list s = [test_shape[self.default_s_from_shape_slicer]] else: if 'axis' in default_args: # default 1D s = test_shape[default_args['axis']] else: # default nD s = [] try: for each_axis in default_args['axes']: s.append(test_shape[each_axis]) except TypeError: s = None return s def test_valid(self): dtype_tuple = self.io_dtypes[functions[self.func]] for dtype in dtype_tuple[0]: for test_shape, s, kwargs in self.test_data: s = None self.validate(dtype_tuple[1], test_shape, dtype, s, kwargs) def test_same_sized_s(self): dtype_tuple = self.io_dtypes[functions[self.func]] for dtype in dtype_tuple[0]: for test_shape, s, kwargs in self.test_data: self.validate(dtype_tuple[1], test_shape, dtype, s, kwargs) def test_bigger_s(self): dtype_tuple = self.io_dtypes[functions[self.func]] for dtype in dtype_tuple[0]: for test_shape, s, kwargs in self.test_data: try: for each_axis, length in enumerate(s): s[each_axis] += 2 except TypeError: s += 2 self.validate(dtype_tuple[1], test_shape, dtype, s, kwargs) def test_smaller_s(self): dtype_tuple = self.io_dtypes[functions[self.func]] for dtype in dtype_tuple[0]: for test_shape, s, kwargs in self.test_data: try: for each_axis, length in enumerate(s): s[each_axis] -= 2 except TypeError: s -= 2 self.validate(dtype_tuple[1], test_shape, dtype, s, kwargs) def check_arg(self, arg, arg_test_values, array_type, test_shape, dtype, s, kwargs): '''Check that the correct arg is passed to the builder''' # We trust the builders to work as expected when passed # the correct arg (the builders have their own unittests). return_values = [] input_array = array_type(test_shape, dtype) def fake_fft(*args, **kwargs): return_values.append((args, kwargs)) return (args, kwargs) try: # Replace the function that is to be used real_fft = getattr(self.test_interface, self.func) setattr(self.test_interface, self.func, fake_fft) _kwargs = kwargs.copy() for each_value in arg_test_values: _kwargs[arg] = each_value builder_args = getattr(self.test_interface, self.func)( input_array.copy(), s, **_kwargs) self.assertTrue(builder_args[1][arg] == each_value) # make sure it was called self.assertTrue(len(return_values) > 0) except: raise finally: # Make sure we set it back setattr(self.test_interface, self.func, real_fft) # Validate it aswell for each_value in arg_test_values: _kwargs[arg] = each_value builder_args = getattr(self.test_interface, self.func)( input_array.copy(), s, **_kwargs) self.validate(array_type, test_shape, dtype, s, _kwargs) def test_bigger_and_smaller_s(self): dtype_tuple = self.io_dtypes[functions[self.func]] for dtype in dtype_tuple[0]: i = -1 for test_shape, s, kwargs in self.test_data: try: for each_axis, length in enumerate(s): s[each_axis] += i * 2 i *= i except TypeError: s += i * 2 i *= i self.validate(dtype_tuple[1], test_shape, dtype, s, kwargs) def test_dtype_coercion(self): # Make sure we input a dtype that needs to be coerced if functions[self.func] == 'r2c': dtype_tuple = self.io_dtypes['complex'] else: dtype_tuple = self.io_dtypes['r2c'] for dtype in dtype_tuple[0]: for test_shape, s, kwargs in self.test_data: s = None self.validate(dtype_tuple[1], test_shape, dtype, s, kwargs) def test_input_maintained(self): '''Test to make sure the input is maintained by default. ''' dtype_tuple = self.io_dtypes[functions[self.func]] for dtype in dtype_tuple[0]: for test_shape, s, kwargs in self.test_data: input_array = dtype_tuple[1](test_shape, dtype) orig_input_array = input_array.copy() getattr(self.test_interface, self.func)( input_array, s, **kwargs) self.assertTrue( numpy.alltrue(input_array == orig_input_array)) class InterfacesDaskFFTTestFFT2(InterfacesDaskFFTTestFFT): axes_kw = 'axes' func = 'ifft2' has_norm_kwarg = False test_shapes = ( ((128, 64), {'axes': None}), ((128, 32), {'axes': None}), ((128, 32, 4), {'axes': (0, 2)}), ((59, 100), {'axes': (-2, -1)}), ((32, 32), {'axes': (-2, -1), 'norm': 'ortho'}), ((32, 32), {'axes': (-2, -1), 'norm': None}), ((32, 32), {'axes': (-2, -1), 'norm': 'backward'}), ((32, 32), {'axes': (-2, -1), 'norm': 'forward'}), ((64, 128, 16), {'axes': (0, 2)}), ((4, 6, 8, 4), {'axes': (0, 3)}), ) invalid_args = () def test_shape_and_s_different_lengths(self): dtype_tuple = self.io_dtypes[functions[self.func]] for dtype in dtype_tuple[0]: for test_shape, s, _kwargs in self.test_data: kwargs = copy.copy(_kwargs) try: s = s[1:] except TypeError: self.skipTest('Not meaningful test on 1d arrays.') # Convert empty tuples to None s = s if s else None del kwargs['axes'] self.validate(dtype_tuple[1], test_shape, dtype, s, kwargs) class InterfacesDaskFFTTestFFTN(InterfacesDaskFFTTestFFT2): func = 'ifftn' has_norm_kwarg = False test_shapes = ( ((128, 32, 4), {'axes': None}), ((64, 128, 16), {'axes': (0, 1, 2)}), ((4, 6, 8, 4), {'axes': (0, 3, 1)}), ((4, 6, 4, 4), {'axes': (0, 3, 1), 'norm': 'ortho'}), ((4, 6, 4, 4), {'axes': (0, 3, 1), 'norm': None}), ((4, 6, 4, 4), {'axes': (0, 3, 1), 'norm': 'backward'}), ((4, 6, 4, 4), {'axes': (0, 3, 1), 'norm': 'forward'}), ((4, 6, 8, 4), {'axes': (0, 3, 1, 2)}), ) class InterfacesDaskFFTTestIFFT(InterfacesDaskFFTTestFFT): func = 'ifft' has_norm_kwarg = False class InterfacesDaskFFTTestIFFT2(InterfacesDaskFFTTestFFT2): func = 'ifft2' has_norm_kwarg = False class InterfacesDaskFFTTestIFFTN(InterfacesDaskFFTTestFFTN): func = 'ifftn' has_norm_kwarg = False class InterfacesDaskFFTTestRFFT(InterfacesDaskFFTTestFFT): func = 'rfft' has_norm_kwarg = False class InterfacesDaskFFTTestRFFT2(InterfacesDaskFFTTestFFT2): func = 'rfft2' has_norm_kwarg = False class InterfacesDaskFFTTestRFFTN(InterfacesDaskFFTTestFFTN): func = 'rfftn' has_norm_kwarg = False class InterfacesDaskFFTTestIRFFT(InterfacesDaskFFTTestFFT): func = 'irfft' realinv = True has_norm_kwarg = False class InterfacesDaskFFTTestIRFFT2(InterfacesDaskFFTTestFFT2): func = 'irfft2' has_norm_kwarg = False realinv = True class InterfacesDaskFFTTestIRFFTN(InterfacesDaskFFTTestFFTN): func = 'irfftn' has_norm_kwarg = False realinv = True class InterfacesDaskFFTTestHFFT(InterfacesDaskFFTTestFFT): func = 'hfft' realinv = True has_norm_kwarg = False class InterfacesDaskFFTTestIHFFT(InterfacesDaskFFTTestFFT): func = 'ihfft' has_norm_kwarg = False test_cases = ( InterfacesDaskFFTTestModule, InterfacesDaskFFTTestFFT, InterfacesDaskFFTTestFFT2, InterfacesDaskFFTTestFFTN, InterfacesDaskFFTTestIFFT, InterfacesDaskFFTTestIFFT2, InterfacesDaskFFTTestIFFTN, InterfacesDaskFFTTestRFFT, InterfacesDaskFFTTestRFFT2, InterfacesDaskFFTTestRFFTN, InterfacesDaskFFTTestIRFFT, InterfacesDaskFFTTestIRFFT2, InterfacesDaskFFTTestIRFFTN, InterfacesDaskFFTTestHFFT, InterfacesDaskFFTTestIHFFT) test_set = None if __name__ == '__main__': run_test_suites(test_cases, test_set) pyFFTW-0.13.1/tests/test_pyfftw_interfaces_cache.py000066400000000000000000000332461435600752200223470ustar00rootroot00000000000000# # Copyright 2017 Knowledge Economy Developments Ltd # # Henry Gomersall # heng@kedevelopments.co.uk # # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, this # list of conditions and the following disclaimer. # # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions and the following disclaimer in the documentation # and/or other materials provided with the distribution. # # * Neither the name of the copyright holder nor the names of its contributors # may be used to endorse or promote products derived from this software without # specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. # import copy import sys from pyfftw import interfaces, builders import pyfftw import numpy import numpy as np import unittest from .test_pyfftw_base import run_test_suites, miss from .test_pyfftw_numpy_interface import InterfacesNumpyFFTTestFFT import threading import time import os import hashlib '''Test the caching functionality of the interfaces package. ''' def _check_n_cache_threads_running(): '''Return how many threads have the name 'PyFFTWCacheThread. Obviously, this isn't production quality, but it should suffice for the tests here. ''' cache_threads = 0 for each_thread in threading.enumerate(): if each_thread.name == 'PyFFTWCacheThread': cache_threads += 1 return cache_threads @unittest.skipIf(*miss('64')) class InterfacesNumpyFFTCacheTestFFT(InterfacesNumpyFFTTestFFT): test_shapes = ( ((100,), {}), ((128, 64), {'axis': 0}), ((128, 32), {'axis': -1}), ((32, 64), {}), ) def validate(self, array_type, test_shape, dtype, s, kwargs, copy_func=copy.copy): # Do it with the cache interfaces.cache.enable() output = self._validate(array_type, test_shape, dtype, s, kwargs, copy_func=copy_func) output2 = self._validate(array_type, test_shape, dtype, s, kwargs, copy_func=copy_func) self.assertIsNot(output, output2) # Turn it off to finish interfaces.cache.disable() @unittest.skipIf(*miss('64')) class CacheSpecificInterfacesUtils(unittest.TestCase): def test_slow_lookup_no_race_condition(self): '''Checks that lookups in _utils longer than the keepalive time are ok. ''' # Any old size, it doesn't matter data_shape = (128,) # Monkey patch the module with a custom _Cache object _Cache_class = interfaces.cache._Cache class _SlowLookupCache(_Cache_class): def _lookup(self, key): return _Cache_class.lookup(self, key) def lookup(self, key): time.sleep(0.1) return self._lookup(key) try: interfaces.cache._Cache = _SlowLookupCache interfaces.cache.enable() # something shortish interfaces.cache.set_keepalive_time(0.001) ar, ai = numpy.random.randn(*(2,) + data_shape) a = ar + 1j*ai # Both the following should work without exception # (even if it fails to get from the cache) interfaces.numpy_fft.fft(a) interfaces.numpy_fft.fft(a) interfaces.cache.disable() finally: # Revert the monkey patching interfaces.cache._Cache = _Cache_class class InterfacesCacheTest(unittest.TestCase): def test_missing_threading(self): self.assertIs(interfaces.cache._fftw_cache, None) mod_threading = interfaces.cache._threading interfaces.cache._threading = None with self.assertRaises(ImportError): interfaces.cache.enable() interfaces.cache._threading = mod_threading def test_is_enabled(self): self.assertIs(interfaces.cache._fftw_cache, None) interfaces.cache.enable() self.assertTrue(interfaces.cache.is_enabled()) interfaces.cache.disable() self.assertFalse(interfaces.cache.is_enabled()) def test_cache_enable_disable(self): self.assertIs(interfaces.cache._fftw_cache, None) interfaces.cache.enable() self.assertIsInstance( interfaces.cache._fftw_cache, interfaces.cache._Cache) interfaces.cache.disable() self.assertIs(interfaces.cache._fftw_cache, None) def test_set_keepalive_time(self): with self.assertRaises(interfaces.cache.CacheError): interfaces.cache.set_keepalive_time(10) interfaces.cache.enable() interfaces.cache.set_keepalive_time(10) self.assertTrue( interfaces.cache._fftw_cache.keepalive_time == 10.0) interfaces.cache.disable() class CacheTest(unittest.TestCase): def test_cache_parent_thread_ended(self): '''Test ending cache parent thread ends cache thread. ''' # Firstly make sure we've exited any lingering threads from other # tests. time.sleep(0.25) self.assertTrue(_check_n_cache_threads_running() == 0) def cache_parent_thread(): cache = interfaces.cache._Cache() time.sleep(0.5) # We give the parent thread the same name as a Cache thread so # it is picked up by the cache_threads_running function parent_t = threading.Thread( target=cache_parent_thread, name='PyFFTWCacheThread') parent_t.start() time.sleep(0.25) # Check it's running self.assertTrue(_check_n_cache_threads_running() == 2) parent_t.join() time.sleep(0.25) # Check both threads have exited properly self.assertTrue(_check_n_cache_threads_running() == 0) def test_delete_cache_object(self): '''Test deleting a cache object ends cache thread. ''' # Firstly make sure we've exited any lingering threads from other # tests. time.sleep(0.25) self.assertTrue(_check_n_cache_threads_running() == 0) _cache = interfaces.cache._Cache() time.sleep(0.25) self.assertTrue(_check_n_cache_threads_running() == 1) del _cache time.sleep(0.25) self.assertTrue(_check_n_cache_threads_running() == 0) @unittest.skipIf(*miss('64')) def test_insert_and_lookup_item(self): _cache = interfaces.cache._Cache() key = 'the key' test_array = numpy.random.randn(16) obj = builders.fft(test_array) _cache.insert(obj, key) self.assertIs(_cache.lookup(key), obj) @unittest.skipIf(*miss('64')) def test_invalid_lookup(self): _cache = interfaces.cache._Cache() key = 'the key' test_array = numpy.random.randn(16) obj = builders.fft(test_array) _cache.insert(obj, key) self.assertRaises(KeyError, _cache.lookup, 'wrong_key') def test_keepalive_time_update(self): _cache = interfaces.cache._Cache() # The default self.assertEqual(_cache.keepalive_time, 0.1) _cache.set_keepalive_time(0.3) self.assertEqual(_cache.keepalive_time, 0.3) _cache.set_keepalive_time(10.0) self.assertEqual(_cache.keepalive_time, 10.0) _cache.set_keepalive_time('0.2') self.assertEqual(_cache.keepalive_time, 0.2) with self.assertRaises(ValueError): _cache.set_keepalive_time('foo') with self.assertRaises(TypeError): _cache.set_keepalive_time([]) @unittest.skipIf(*miss('64')) def test_contains(self): _cache = interfaces.cache._Cache() key = 'the key' test_array = numpy.random.randn(16) obj = builders.fft(test_array) _cache.insert(obj, key) self.assertTrue(key in _cache) self.assertFalse('Not a key' in _cache) @unittest.skipIf(*miss('64')) def test_objects_removed_after_keepalive(self): _cache = interfaces.cache._Cache() key = 'the key' test_array = numpy.random.randn(16) obj = builders.fft(test_array) _cache.insert(obj, key) self.assertIs(_cache.lookup(key), obj) keepalive_time = _cache.keepalive_time if os.name == 'nt' or sys.platform == 'darwin': # increase sleep time to address random test failures on CI time.sleep(keepalive_time * 8) else: # Relax a bit more otherwise time.sleep(keepalive_time * 4) self.assertRaises(KeyError, _cache.lookup, key) _cache.insert(obj, key) old_keepalive_time = _cache.keepalive_time _cache.set_keepalive_time(old_keepalive_time * 4) self.assertIs(_cache.lookup(key), obj) time.sleep(old_keepalive_time * 3.5) # still should be there self.assertIs(_cache.lookup(key), obj) time.sleep(old_keepalive_time * 16) self.assertRaises(KeyError, _cache.lookup, key) def test_misaligned_data_doesnt_clobber_cache(self): '''A bug was highlighted in #197 in which misaligned data causes an overwrite of an FFTW internal array which is also the same as an output array. The correct behaviour is for the cache to have alignment as a key to stop this happening. ''' interfaces.cache.enable() N = 64 pyfftw.interfaces.cache.enable() np.random.seed(12345) Um = pyfftw.empty_aligned((N, N+1), dtype=np.float32, order='C') Vm = pyfftw.empty_aligned((N, N+1), dtype=np.float32, order='C') U = np.ndarray((N, N), dtype=Um.dtype, buffer=Um.data, offset=0) V = np.ndarray( (N, N), dtype=Vm.dtype, buffer=Vm.data, offset=Vm.itemsize) U[:] = np.random.randn(N, N).astype(np.float32) V[:] = np.random.randn(N, N).astype(np.float32) uh = hashlib.md5(U).hexdigest() vh = hashlib.md5(V).hexdigest() x = interfaces.numpy_fft.rfftn( U, None, axes=(0, 1), overwrite_input=False) y = interfaces.numpy_fft.rfftn( V, None, axes=(0, 1), overwrite_input=False) self.assertTrue(uh == hashlib.md5(U).hexdigest()) self.assertTrue(vh == hashlib.md5(V).hexdigest()) interfaces.cache.disable() class InterfacesNumpyFFTCacheTestIFFT(InterfacesNumpyFFTCacheTestFFT): func = 'ifft' class InterfacesNumpyFFTCacheTestRFFT(InterfacesNumpyFFTCacheTestFFT): func = 'rfft' class InterfacesNumpyFFTCacheTestIRFFT(InterfacesNumpyFFTCacheTestFFT): func = 'irfft' realinv = True class InterfacesNumpyFFTCacheTestFFT2(InterfacesNumpyFFTCacheTestFFT): axes_kw = 'axes' func = 'ifft2' test_shapes = ( ((128, 64), {'axes': None}), ((128, 32), {'axes': None}), ((32, 64), {'axes': (-2, -1)}), ((4, 6, 8, 4), {'axes': (0, 3)}), ) invalid_args = ( ((100,), ((100, 200),), ValueError, 'Shape error'), ((100, 200), ((100, 200, 100),), ValueError, 'Shape error'), ((100,), ((100, 200), (-3, -2, -1)), ValueError, 'Shape error'), ((100, 200), (100, -1), TypeError, ''), ((100, 200), ((100, 200), (-3, -2)), IndexError, 'Invalid axes'), ((100, 200), ((100,), (-3,)), IndexError, 'Invalid axes')) class InterfacesNumpyFFTCacheTestIFFT2(InterfacesNumpyFFTCacheTestFFT2): func = 'ifft2' class InterfacesNumpyFFTCacheTestRFFT2(InterfacesNumpyFFTCacheTestFFT2): func = 'rfft2' class InterfacesNumpyFFTCacheTestIRFFT2(InterfacesNumpyFFTCacheTestFFT2): func = 'irfft2' realinv = True class InterfacesNumpyFFTCacheTestFFTN(InterfacesNumpyFFTCacheTestFFT2): func = 'ifftn' test_shapes = ( ((128, 32, 4), {'axes': None}), ((64, 128, 16), {'axes': (0, 1, 2)}), ((4, 6, 8, 4), {'axes': (0, 3, 1)}), ((4, 6, 8, 4), {'axes': (0, 3, 1, 2)}), ) class InterfacesNumpyFFTCacheTestIFFTN(InterfacesNumpyFFTCacheTestFFTN): func = 'ifftn' class InterfacesNumpyFFTCacheTestRFFTN(InterfacesNumpyFFTCacheTestFFTN): func = 'rfftn' class InterfacesNumpyFFTCacheTestIRFFTN(InterfacesNumpyFFTCacheTestFFTN): func = 'irfftn' realinv = True test_cases = ( CacheTest, InterfacesCacheTest, CacheSpecificInterfacesUtils, InterfacesNumpyFFTCacheTestFFT, InterfacesNumpyFFTCacheTestIFFT, InterfacesNumpyFFTCacheTestRFFT, InterfacesNumpyFFTCacheTestIRFFT, InterfacesNumpyFFTCacheTestFFT2, InterfacesNumpyFFTCacheTestIFFT2, InterfacesNumpyFFTCacheTestRFFT2, InterfacesNumpyFFTCacheTestIRFFT2, InterfacesNumpyFFTCacheTestFFTN, InterfacesNumpyFFTCacheTestIFFTN, InterfacesNumpyFFTCacheTestRFFTN, InterfacesNumpyFFTCacheTestIRFFTN,) test_set = None if __name__ == '__main__': run_test_suites(test_cases, test_set) pyFFTW-0.13.1/tests/test_pyfftw_multithreaded.py000066400000000000000000000070361435600752200217320ustar00rootroot00000000000000# Copyright 2014 Knowledge Economy Developments Ltd # # Henry Gomersall # heng@kedevelopments.co.uk # # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, this # list of conditions and the following disclaimer. # # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions and the following disclaimer in the documentation # and/or other materials provided with the distribution. # # * Neither the name of the copyright holder nor the names of its contributors # may be used to endorse or promote products derived from this software without # specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. # from pyfftw import FFTW import numpy from timeit import Timer from .test_pyfftw_base import run_test_suites, miss, np_fft import unittest from .test_pyfftw_base import FFTWBaseTest class Complex64MultiThreadedTest(FFTWBaseTest): def run_multithreaded_test(self, threads): in_shape = self.input_shapes['2d']; out_shape = self.output_shapes['2d'] axes=(-1,) a, b = self.create_test_arrays(in_shape, out_shape) fft, ifft = self.run_validate_fft(a, b, axes, threads=threads) fft_, ifft_ = self.run_validate_fft(a, b, axes, threads=1) self.timer_routine(fft.execute, fft_.execute, comparison_string='singled threaded') self.assertTrue(True) def test_2_threads(self): self.run_multithreaded_test(2) def test_4_threads(self): self.run_multithreaded_test(4) def test_7_threads(self): self.run_multithreaded_test(7) def test_25_threads(self): self.run_multithreaded_test(25) @unittest.skipIf(*miss('64')) class Complex128MultiThreadedTest(Complex64MultiThreadedTest): def setUp(self): self.input_dtype = numpy.complex128 self.output_dtype = numpy.complex128 self.np_fft_comparison = np_fft.fft return @unittest.skipIf(*miss('ld')) class ComplexLongDoubleMultiThreadedTest(Complex64MultiThreadedTest): def setUp(self): self.input_dtype = numpy.clongdouble self.output_dtype = numpy.clongdouble self.np_fft_comparison = self.reference_fftn return def reference_fftn(self, a, axes): # numpy.fft.fftn doesn't support complex256 type, # so we need to compare to a lower precision type. a = numpy.complex128(a) return np_fft.fftn(a, axes=axes) test_cases = ( Complex64MultiThreadedTest, Complex128MultiThreadedTest, ComplexLongDoubleMultiThreadedTest,) test_set = None if __name__ == '__main__': run_test_suites(test_cases, test_set) pyFFTW-0.13.1/tests/test_pyfftw_nbyte_align.py000066400000000000000000000310101435600752200213570ustar00rootroot00000000000000# Copyright 2014 Knowledge Economy Developments Ltd # Copyright 2014 David Wells # # Henry Gomersall # heng@kedevelopments.co.uk # David Wells # drwells vt.edu # # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, this # list of conditions and the following disclaimer. # # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions and the following disclaimer in the documentation # and/or other materials provided with the distribution. # # * Neither the name of the copyright holder nor the names of its contributors # may be used to endorse or promote products derived from this software without # specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. # from pyfftw import (byte_align, is_byte_aligned, ones_aligned, empty_aligned, zeros_aligned, simd_alignment,) # Test the deprecated functions. from pyfftw import n_byte_align, n_byte_align_empty, is_n_byte_aligned import numpy from timeit import Timer from .test_pyfftw_base import run_test_suites import unittest import warnings def ignore_deprecation_warning(function): def new_function(*args, **kwargs): with warnings.catch_warnings(): warnings.filterwarnings("ignore", category=DeprecationWarning) return function(*args, **kwargs) return new_function class ByteAlignTest(unittest.TestCase): def __init__(self, *args, **kwargs): super(ByteAlignTest, self).__init__(*args, **kwargs) if not hasattr(self, 'assertRaisesRegex'): self.assertRaisesRegex = self.assertRaisesRegexp def setUp(self): return def tearDown(self): return def test_ones_aligned(self): shape = (10,10) # Test a few alignments and dtypes for each in [(3, 'float64'), (7, 'float64'), (9, 'float32'), (16, 'int64'), (24, 'bool'), (23, 'complex64'), (63, 'complex128'), (64, 'int8')]: n = each[0] a = numpy.ones(shape, dtype=each[1]) b = ones_aligned(shape, dtype=each[1], n=n) self.assertTrue(b.ctypes.data%n == 0) self.assertTrue(b.dtype == each[1]) self.assertTrue(numpy.array_equal(a, b)) def test_zeros_aligned(self): shape = (10,10) # Test a few alignments and dtypes for each in [(3, 'float64'), (7, 'float64'), (9, 'float32'), (16, 'int64'), (24, 'bool'), (23, 'complex64'), (63, 'complex128'), (64, 'int8')]: n = each[0] a = numpy.zeros(shape, dtype=each[1]) b = zeros_aligned(shape, dtype=each[1], n=n) self.assertTrue(b.ctypes.data%n == 0) self.assertTrue(b.dtype == each[1]) self.assertTrue(numpy.array_equal(a, b)) def test_empty_aligned(self): shape = (10,10) # Test a few alignments and dtypes for each in [(3, 'float64'), (7, 'float64'), (9, 'float32'), (16, 'int64'), (24, 'bool'), (23, 'complex64'), (63, 'complex128'), (64, 'int8')]: n = each[0] b = empty_aligned(shape, dtype=each[1], n=n) self.assertTrue(b.ctypes.data%n == 0) self.assertTrue(b.dtype == each[1]) @ignore_deprecation_warning def test_n_byte_align_empty(self): shape = (10,10) # Test a few alignments and dtypes for each in [(3, 'float64'), (7, 'float64'), (9, 'float32'), (16, 'int64'), (24, 'bool'), (23, 'complex64'), (63, 'complex128'), (64, 'int8')]: n = each[0] b = n_byte_align_empty(shape, n, dtype=each[1]) self.assertTrue(b.ctypes.data%n == 0) self.assertTrue(b.dtype == each[1]) def test_byte_align(self): shape = (10,10) a = numpy.random.randn(*shape) # Test a few alignments for n in [None, 3, 7, 9, 16, 24, 23, 63, 64]: expected_alignment = get_expected_alignment(n) b = byte_align(a, n=n) self.assertTrue(b.ctypes.data % expected_alignment == 0) @ignore_deprecation_warning def test_n_byte_align(self): shape = (10,10) a = numpy.random.randn(*shape) # Test a few alignments for n in [3, 7, 9, 16, 24, 23, 63, 64]: b = n_byte_align(a, n) self.assertTrue(b.ctypes.data%n == 0) def test_byte_align_integer_shape(self): shape = 100 a = numpy.random.randn(shape) # Test a few alignments for n in [None, 3, 7, 9, 16, 24, 23, 63, 64]: expected_alignment = get_expected_alignment(n) b = byte_align(a, n=n) self.assertTrue(b.ctypes.data % expected_alignment == 0) @ignore_deprecation_warning def test_n_byte_align_integer_shape(self): shape = 100 a = numpy.random.randn(shape) # Test a few alignments for n in [3, 7, 9, 16, 24, 23, 63, 64]: b = n_byte_align(a, n) self.assertTrue(b.ctypes.data%n == 0) def test_is_byte_aligned(self): a = empty_aligned(100) self.assertTrue(is_byte_aligned(a, get_expected_alignment(None))) a = empty_aligned(100, n=16) self.assertTrue(is_byte_aligned(a, n=16)) a = empty_aligned(100, n=5) self.assertTrue(is_byte_aligned(a, n=5)) a = empty_aligned(100, dtype='float32', n=16)[1:] self.assertFalse(is_byte_aligned(a, n=16)) self.assertTrue(is_byte_aligned(a, n=4)) @ignore_deprecation_warning def test_is_n_byte_aligned(self): a = n_byte_align_empty(100, 16) self.assertTrue(is_n_byte_aligned(a, 16)) a = n_byte_align_empty(100, 5) self.assertTrue(is_n_byte_aligned(a, 5)) a = n_byte_align_empty(100, 16, dtype='float32')[1:] self.assertFalse(is_n_byte_aligned(a, 16)) self.assertTrue(is_n_byte_aligned(a, 4)) def test_is_byte_aligned_fail_with_non_array(self): a = [1, 2, 3, 4] self.assertRaisesRegex(TypeError, 'Invalid array', is_byte_aligned, a, n=16) @ignore_deprecation_warning def test_is_n_byte_aligned_fail_with_non_array(self): a = [1, 2, 3, 4] self.assertRaisesRegex(TypeError, 'Invalid array', is_n_byte_aligned, a, 16) def test_byte_align_fail_with_non_array(self): a = [1, 2, 3, 4] self.assertRaisesRegex(TypeError, 'Invalid array', byte_align, a, n=16) @ignore_deprecation_warning def test_n_byte_align_fail_with_non_array(self): a = [1, 2, 3, 4] self.assertRaisesRegex(TypeError, 'Invalid array', n_byte_align, a, 16) def test_byte_align_consistent_data(self): shape = (10,10) a = numpy.int16(numpy.random.randn(*shape)*16000) b = numpy.float64(numpy.random.randn(*shape)) c = numpy.int8(numpy.random.randn(*shape)*255) # Test a few alignments for n in [None, 3, 7, 9, 16, 24, 23, 63, 64]: d = byte_align(a, n=n) self.assertTrue(numpy.array_equal(a, d)) d = byte_align(b, n=n) self.assertTrue(numpy.array_equal(b, d)) d = byte_align(c, n=n) self.assertTrue(numpy.array_equal(c, d)) @ignore_deprecation_warning def test_n_byte_align_consistent_data(self): shape = (10,10) a = numpy.int16(numpy.random.randn(*shape)*16000) b = numpy.float64(numpy.random.randn(*shape)) c = numpy.int8(numpy.random.randn(*shape)*255) # Test a few alignments for n in [3, 7, 9, 16, 24, 23, 63, 64]: d = n_byte_align(a, n) self.assertTrue(numpy.array_equal(a, d)) d = n_byte_align(b, n) self.assertTrue(numpy.array_equal(b, d)) d = n_byte_align(c, n) self.assertTrue(numpy.array_equal(c, d)) def test_byte_align_different_dtypes(self): shape = (10,10) a = numpy.int16(numpy.random.randn(*shape)*16000) b = numpy.float64(numpy.random.randn(*shape)) c = numpy.int8(numpy.random.randn(*shape)*255) # Test a few alignments for n in [None, 3, 7, 9, 16, 24, 23, 63, 64]: expected_alignment = get_expected_alignment(n) d = byte_align(a, n=n) self.assertTrue(d.ctypes.data % expected_alignment == 0) self.assertTrue(d.__class__ == a.__class__) d = byte_align(b, n=n) self.assertTrue(d.ctypes.data % expected_alignment == 0) self.assertTrue(d.__class__ == b.__class__) d = byte_align(c, n=n) self.assertTrue(d.ctypes.data % expected_alignment == 0) self.assertTrue(d.__class__ == c.__class__) @ignore_deprecation_warning def test_n_byte_align_different_dtypes(self): shape = (10,10) a = numpy.int16(numpy.random.randn(*shape)*16000) b = numpy.float64(numpy.random.randn(*shape)) c = numpy.int8(numpy.random.randn(*shape)*255) # Test a few alignments for n in [3, 7, 9, 16, 24, 23, 63, 64]: d = n_byte_align(a, n) self.assertTrue(d.ctypes.data%n == 0) self.assertTrue(d.__class__ == a.__class__) d = n_byte_align(b, n) self.assertTrue(d.ctypes.data%n == 0) self.assertTrue(d.__class__ == b.__class__) d = n_byte_align(c, n) self.assertTrue(d.ctypes.data%n == 0) self.assertTrue(d.__class__ == c.__class__) def test_byte_align_set_dtype(self): shape = (10,10) a = numpy.int16(numpy.random.randn(*shape)*16000) b = numpy.float64(numpy.random.randn(*shape)) c = numpy.int8(numpy.random.randn(*shape)*255) # Test a few alignments for n in [None, 3, 7, 9, 16, 24, 23, 63, 64]: expected_alignment = get_expected_alignment(n) d = byte_align(a, dtype='float32', n=n) self.assertTrue(d.ctypes.data % expected_alignment == 0) self.assertTrue(d.dtype == 'float32') d = byte_align(b, dtype='float32', n=n) self.assertTrue(d.ctypes.data % expected_alignment == 0) self.assertTrue(d.dtype == 'float32') d = byte_align(c, dtype='float64', n=n) self.assertTrue(d.ctypes.data % expected_alignment == 0) self.assertTrue(d.dtype == 'float64') @ignore_deprecation_warning def test_n_byte_align_set_dtype(self): shape = (10,10) a = numpy.int16(numpy.random.randn(*shape)*16000) b = numpy.float64(numpy.random.randn(*shape)) c = numpy.int8(numpy.random.randn(*shape)*255) # Test a few alignments for n in [3, 7, 9, 16, 24, 23, 63, 64]: d = n_byte_align(a, n, dtype='float32') self.assertTrue(d.ctypes.data%n == 0) self.assertTrue(d.dtype == 'float32') d = n_byte_align(b, n, dtype='float32') self.assertTrue(d.ctypes.data%n == 0) self.assertTrue(d.dtype == 'float32') d = n_byte_align(c, n, dtype='float64') self.assertTrue(d.ctypes.data%n == 0) self.assertTrue(d.dtype == 'float64') def get_expected_alignment(n): if n is None: return simd_alignment else: return n test_cases = ( ByteAlignTest,) test_set = None if __name__ == '__main__': run_test_suites(test_cases, test_set) pyFFTW-0.13.1/tests/test_pyfftw_numpy_interface.py000066400000000000000000000705301435600752200222660ustar00rootroot00000000000000# Copyright 2015 Knowledge Economy Developments Ltd # # Henry Gomersall # heng@kedevelopments.co.uk # # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, this # list of conditions and the following disclaimer. # # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions and the following disclaimer in the documentation # and/or other materials provided with the distribution. # # * Neither the name of the copyright holder nor the names of its contributors # may be used to endorse or promote products derived from this software without # specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. # from pyfftw import interfaces, _supported_types, _all_types_np from .test_pyfftw_base import run_test_suites, np_fft from ._get_default_args import get_default_args from distutils.version import LooseVersion import unittest import numpy import warnings import copy warnings.filterwarnings('always') if LooseVersion(numpy.version.version) <= LooseVersion('1.6.2'): # We overwrite the broken _cook_nd_args with a fixed version. from ._cook_nd_args import _cook_nd_args numpy.fft.fftpack._cook_nd_args = _cook_nd_args complex_dtypes = [] real_dtypes = [] if '32' in _supported_types: complex_dtypes.extend([numpy.complex64]*2) real_dtypes.extend([numpy.float16, numpy.float32]) if '64' in _supported_types: complex_dtypes.append(numpy.complex128) real_dtypes.append(numpy.float64) if 'ld' in _supported_types: complex_dtypes.append(numpy.clongdouble) real_dtypes.append(numpy.longdouble) def make_complex_data(shape, dtype): ar, ai = dtype(numpy.random.randn(2, *shape)) return ar + 1j*ai def make_real_data(shape, dtype): return dtype(numpy.random.randn(*shape)) def _numpy_fft_has_norm_kwarg(): """returns True if numpy's fft supports the norm keyword argument This should be true for numpy >= 1.10 """ # return LooseVersion(numpy.version.version) >= LooseVersion('1.10') try: np_fft.fft(numpy.ones(4), norm=None) return True except TypeError: return False if _numpy_fft_has_norm_kwarg() and numpy.__version__ < '1.13': # use version of numpy.fft.rfft* with normalisation bug fixed # The patched version here, corresponds to the following bugfix PR: # https://github.com/numpy/numpy/pull/8445 from numpy.fft import fftpack as fftpk def rfft_fix(a, n=None, axis=-1, norm=None): # from numpy.fft import fftpack_lite as fftpack # from numpy.fft.fftpack import _raw_fft, _unitary, _real_fft_cache a = numpy.array(a, copy=True, dtype=float) output = fftpk._raw_fft(a, n, axis, fftpk.fftpack.rffti, fftpk.fftpack.rfftf, fftpk._real_fft_cache) if fftpk._unitary(norm): if n is None: n = a.shape[axis] output *= 1 / numpy.sqrt(n) return output def rfftn_fix(a, s=None, axes=None, norm=None): a = numpy.array(a, copy=True, dtype=float) s, axes = fftpk._cook_nd_args(a, s, axes) a = rfft_fix(a, s[-1], axes[-1], norm) for ii in range(len(axes)-1): a = fftpk.fft(a, s[ii], axes[ii], norm) return a def rfft2_fix(a, s=None, axes=(-2, -1), norm=None): return rfftn_fix(a, s, axes, norm) np_fft.rfft = rfft_fix np_fft.rfft2 = rfft2_fix np_fft.rfftn = rfftn_fix functions = { 'fft': 'complex', 'ifft': 'complex', 'rfft': 'r2c', 'irfft': 'c2r', 'rfftn': 'r2c', 'hfft': 'c2r', 'ihfft': 'r2c', 'irfftn': 'c2r', 'rfft2': 'r2c', 'irfft2': 'c2r', 'fft2': 'complex', 'ifft2': 'complex', 'fftn': 'complex', 'ifftn': 'complex'} acquired_names = ('fftfreq', 'fftshift', 'ifftshift') if LooseVersion(numpy.version.version) >= LooseVersion('1.8'): acquired_names += ('rfftfreq', ) class InterfacesNumpyFFTTestModule(unittest.TestCase): ''' A really simple test suite to check the module works as expected. ''' def test_acquired_names(self): for each_name in acquired_names: numpy_fft_attr = getattr(numpy.fft, each_name) acquired_attr = getattr(interfaces.numpy_fft, each_name) self.assertIs(numpy_fft_attr, acquired_attr) class InterfacesNumpyFFTTestFFT(unittest.TestCase): io_dtypes = { 'complex': (complex_dtypes, make_complex_data), 'r2c': (real_dtypes, make_real_data), 'c2r': (complex_dtypes, make_complex_data)} validator_module = np_fft test_interface = interfaces.numpy_fft func = 'fft' axes_kw = 'axis' threads_arg_name = 'threads' overwrite_input_flag = 'overwrite_input' default_s_from_shape_slicer = slice(-1, None) if numpy.__version__ >= '1.20.0': test_shapes = ( ((100,), {}), ((128, 64), {'axis': 0}), ((128, 32), {'axis': -1}), ((59, 100), {}), ((59, 99), {'axis': -1}), ((59, 99), {'axis': 0}), ((32, 32, 4), {'axis': 1}), ((32, 32, 2), {'axis': 1, 'norm': 'ortho'}), ((32, 32, 2), {'axis': 1, 'norm': None}), ((32, 32, 2), {'axis': 1, 'norm': 'backward'}), ((32, 32, 2), {'axis': 1, 'norm': 'forward'}), ((64, 128, 16), {}), ) else: test_shapes = ( ((100,), {}), ((128, 64), {'axis': 0}), ((128, 32), {'axis': -1}), ((59, 100), {}), ((59, 99), {'axis': -1}), ((59, 99), {'axis': 0}), ((32, 32, 4), {'axis': 1}), ((32, 32, 2), {'axis': 1, 'norm': 'ortho'}), ((32, 32, 2), {'axis': 1, 'norm': None}), ((64, 128, 16), {}), ) # invalid_s_shapes is: # (size, invalid_args, error_type, error_string) invalid_args = ( ((100,), ((100, 200),), TypeError, ''), ((100, 200), ((100, 200),), TypeError, ''), ((100,), (100, (-2, -1)), TypeError, ''), ((100,), (100, -20), IndexError, '')) realinv = False has_norm_kwarg = _numpy_fft_has_norm_kwarg() @property def test_data(self): for test_shape, kwargs in self.test_shapes: axes = self.axes_from_kwargs(kwargs) s = self.s_from_kwargs(test_shape, kwargs) if not self.has_norm_kwarg and 'norm' in kwargs: kwargs.pop('norm') if self.realinv: test_shape = list(test_shape) test_shape[axes[-1]] = test_shape[axes[-1]]//2 + 1 test_shape = tuple(test_shape) yield test_shape, s, kwargs def __init__(self, *args, **kwargs): super(InterfacesNumpyFFTTestFFT, self).__init__(*args, **kwargs) # Assume python 3, but keep backwards compatibility if not hasattr(self, 'assertRaisesRegex'): self.assertRaisesRegex = self.assertRaisesRegexp def validate(self, array_type, test_shape, dtype, s, kwargs, copy_func=copy.copy): # Do it without the cache # without: interfaces.cache.disable() self._validate(array_type, test_shape, dtype, s, kwargs, copy_func=copy_func) def munge_input_array(self, array, kwargs): return array def _validate(self, array_type, test_shape, dtype, s, kwargs, copy_func=copy.copy): input_array = self.munge_input_array( array_type(test_shape, dtype), kwargs) orig_input_array = copy_func(input_array) np_input_array = numpy.asarray(input_array) # Why are long double inputs copied to double precision? It's what # numpy silently does anyways as of v1.10 but helps with backward # compatibility and scipy. # https://github.com/pyFFTW/pyFFTW/pull/189#issuecomment-356449731 if np_input_array.dtype == 'clongdouble': np_input_array = numpy.complex128(input_array) elif np_input_array.dtype == 'longdouble': np_input_array = numpy.float64(input_array) with warnings.catch_warnings(record=True) as w: # We catch the warnings so as to pick up on when # a complex array is turned into a real array if 'axes' in kwargs: validator_kwargs = {'axes': kwargs['axes']} elif 'axis' in kwargs: validator_kwargs = {'axis': kwargs['axis']} else: validator_kwargs = {} if self.has_norm_kwarg and 'norm' in kwargs: validator_kwargs['norm'] = kwargs['norm'] try: test_out_array = getattr(self.validator_module, self.func)( copy_func(np_input_array), s, **validator_kwargs) except Exception as e: interface_exception = None try: getattr(self.test_interface, self.func)( copy_func(input_array), s, **kwargs) except Exception as _interface_exception: # It's necessary to assign the exception to the # already defined variable in Python 3. # See http://www.python.org/dev/peps/pep-3110/#semantic-changes interface_exception = _interface_exception # If the test interface raised, so must this. self.assertEqual(type(interface_exception), type(e), msg='Interface exception raised. ' + 'Testing for: ' + repr(e)) return try: output_array = getattr(self.test_interface, self.func)( copy_func(np_input_array), s, **kwargs) except NotImplementedError as e: # check if exception due to missing precision msg = repr(e) if 'Rebuild pyFFTW with support for' in msg: self.skipTest(msg) else: raise if (functions[self.func] == 'r2c'): if numpy.iscomplexobj(input_array): if len(w) > 0: # Make sure a warning is raised self.assertIs( w[-1].category, numpy.ComplexWarning) self.assertTrue( numpy.allclose(output_array, test_out_array, rtol=1e-2, atol=1e-4)) if _all_types_np.get(np_input_array.real.dtype, "") in _supported_types: # supported precisions should not be converted self.assertEqual(np_input_array.real.dtype, output_array.real.dtype) if (not self.overwrite_input_flag in kwargs or not kwargs[self.overwrite_input_flag]): self.assertTrue(numpy.allclose(input_array, orig_input_array)) return output_array def axes_from_kwargs(self, kwargs): default_args = get_default_args( getattr(self.test_interface, self.func)) if 'axis' in kwargs: axes = (kwargs['axis'],) elif 'axes' in kwargs: axes = kwargs['axes'] if axes is None: axes = default_args['axes'] else: if 'axis' in default_args: # default 1D axes = (default_args['axis'],) else: # default nD axes = default_args['axes'] if axes is None: axes = (-1,) return axes def s_from_kwargs(self, test_shape, kwargs): ''' Return either a scalar s or a tuple depending on whether axis or axes is specified ''' default_args = get_default_args( getattr(self.test_interface, self.func)) if 'axis' in kwargs: s = test_shape[kwargs['axis']] elif 'axes' in kwargs: axes = kwargs['axes'] if axes is not None: s = [] for each_axis in axes: s.append(test_shape[each_axis]) else: # default nD s = [] try: for each_axis in default_args['axes']: s.append(test_shape[each_axis]) except TypeError: try: s = list(test_shape[ self.default_s_from_shape_slicer]) except TypeError: # We had an integer as the default, so force # it to be a list s = [test_shape[self.default_s_from_shape_slicer]] else: if 'axis' in default_args: # default 1D s = test_shape[default_args['axis']] else: # default nD s = [] try: for each_axis in default_args['axes']: s.append(test_shape[each_axis]) except TypeError: s = None return s def test_valid(self): dtype_tuple = self.io_dtypes[functions[self.func]] for dtype in dtype_tuple[0]: for test_shape, s, kwargs in self.test_data: s = None self.validate(dtype_tuple[1], test_shape, dtype, s, kwargs) def test_on_non_numpy_array(self): dtype_tuple = self.io_dtypes[functions[self.func]] array_type = (lambda test_shape, dtype: dtype_tuple[1](test_shape, dtype).tolist()) for dtype in dtype_tuple[0]: for test_shape, s, kwargs in self.test_data: s = None self.validate(array_type, test_shape, dtype, s, kwargs) def test_fail_on_invalid_s_or_axes_or_norm(self): dtype_tuple = self.io_dtypes[functions[self.func]] for dtype in dtype_tuple[0]: for test_shape, args, exception, e_str in self.invalid_args: input_array = dtype_tuple[1](test_shape, dtype) if len(args) > 2 and not self.has_norm_kwarg: # skip tests involving norm argument if it isn't available continue self.assertRaisesRegex(exception, e_str, getattr(self.test_interface, self.func), *((input_array,) + args)) def test_same_sized_s(self): dtype_tuple = self.io_dtypes[functions[self.func]] for dtype in dtype_tuple[0]: for test_shape, s, kwargs in self.test_data: self.validate(dtype_tuple[1], test_shape, dtype, s, kwargs) def test_bigger_s(self): dtype_tuple = self.io_dtypes[functions[self.func]] for dtype in dtype_tuple[0]: for test_shape, s, kwargs in self.test_data: try: for each_axis, length in enumerate(s): s[each_axis] += 2 except TypeError: s += 2 self.validate(dtype_tuple[1], test_shape, dtype, s, kwargs) def test_smaller_s(self): dtype_tuple = self.io_dtypes[functions[self.func]] for dtype in dtype_tuple[0]: for test_shape, s, kwargs in self.test_data: try: for each_axis, length in enumerate(s): s[each_axis] -= 2 except TypeError: s -= 2 self.validate(dtype_tuple[1], test_shape, dtype, s, kwargs) def check_arg(self, arg, arg_test_values, array_type, test_shape, dtype, s, kwargs): '''Check that the correct arg is passed to the builder''' # We trust the builders to work as expected when passed # the correct arg (the builders have their own unittests). return_values = [] input_array = array_type(test_shape, dtype) def fake_fft(*args, **kwargs): return_values.append((args, kwargs)) return (args, kwargs) try: # Replace the function that is to be used real_fft = getattr(self.test_interface, self.func) setattr(self.test_interface, self.func, fake_fft) _kwargs = kwargs.copy() for each_value in arg_test_values: _kwargs[arg] = each_value builder_args = getattr(self.test_interface, self.func)( input_array.copy(), s, **_kwargs) self.assertTrue(builder_args[1][arg] == each_value) # make sure it was called self.assertTrue(len(return_values) > 0) except: raise finally: # Make sure we set it back setattr(self.test_interface, self.func, real_fft) # Validate it aswell for each_value in arg_test_values: _kwargs[arg] = each_value builder_args = getattr(self.test_interface, self.func)( input_array.copy(), s, **_kwargs) self.validate(array_type, test_shape, dtype, s, _kwargs) def test_auto_align_input(self): dtype_tuple = self.io_dtypes[functions[self.func]] for dtype in dtype_tuple[0]: for test_shape, s, kwargs in self.test_data: self.check_arg('auto_align_input', (True, False), dtype_tuple[1], test_shape, dtype, s, kwargs) def test_auto_contiguous_input(self): dtype_tuple = self.io_dtypes[functions[self.func]] for dtype in dtype_tuple[0]: for test_shape, s, kwargs in self.test_data: self.check_arg('auto_contiguous', (True, False), dtype_tuple[1], test_shape, dtype, s, kwargs) def test_bigger_and_smaller_s(self): dtype_tuple = self.io_dtypes[functions[self.func]] for dtype in dtype_tuple[0]: i = -1 for test_shape, s, kwargs in self.test_data: try: for each_axis, length in enumerate(s): s[each_axis] += i * 2 i *= i except TypeError: s += i * 2 i *= i self.validate(dtype_tuple[1], test_shape, dtype, s, kwargs) def test_dtype_coercian(self): # Make sure we input a dtype that needs to be coerced if functions[self.func] == 'r2c': dtype_tuple = self.io_dtypes['complex'] else: dtype_tuple = self.io_dtypes['r2c'] for dtype in dtype_tuple[0]: for test_shape, s, kwargs in self.test_data: s = None self.validate(dtype_tuple[1], test_shape, dtype, s, kwargs) def test_planner_effort(self): '''Test the planner effort arg ''' dtype_tuple = self.io_dtypes[functions[self.func]] test_shape = (16,) for dtype in dtype_tuple[0]: s = None if self.axes_kw == 'axis': kwargs = {'axis': -1} else: kwargs = {'axes': (-1,)} for each_effort in ('FFTW_ESTIMATE', 'FFTW_MEASURE', 'FFTW_PATIENT', 'FFTW_EXHAUSTIVE'): kwargs['planner_effort'] = each_effort self.validate( dtype_tuple[1], test_shape, dtype, s, kwargs) kwargs['planner_effort'] = 'garbage' self.assertRaisesRegex(ValueError, 'Invalid planner effort', self.validate, *(dtype_tuple[1], test_shape, dtype, s, kwargs)) def test_threads_arg(self): '''Test the threads argument ''' dtype_tuple = self.io_dtypes[functions[self.func]] test_shape = (16,) for dtype in dtype_tuple[0]: s = None if self.axes_kw == 'axis': kwargs = {'axis': -1} else: kwargs = {'axes': (-1,)} self.check_arg(self.threads_arg_name, (1, 2, 5, 10), dtype_tuple[1], test_shape, dtype, s, kwargs) kwargs[self.threads_arg_name] = 'bleh' # Should not work self.assertRaises(TypeError, self.validate, *(dtype_tuple[1], test_shape, dtype, s, kwargs)) def test_overwrite_input(self): '''Test the overwrite_input flag ''' dtype_tuple = self.io_dtypes[functions[self.func]] for dtype in dtype_tuple[0]: for test_shape, s, _kwargs in self.test_data: s = None kwargs = _kwargs.copy() self.validate(dtype_tuple[1], test_shape, dtype, s, kwargs) self.check_arg(self.overwrite_input_flag, (True, False), dtype_tuple[1], test_shape, dtype, s, kwargs) def test_input_maintained(self): '''Test to make sure the input is maintained by default. ''' dtype_tuple = self.io_dtypes[functions[self.func]] for dtype in dtype_tuple[0]: for test_shape, s, kwargs in self.test_data: input_array = dtype_tuple[1](test_shape, dtype) orig_input_array = input_array.copy() getattr(self.test_interface, self.func)( input_array, s, **kwargs) self.assertTrue( numpy.alltrue(input_array == orig_input_array)) def test_on_non_writeable_array_issue_92(self): '''Test to make sure that locked arrays work. Regression test for issue 92. ''' def copy_with_writeable(array_to_copy): array_copy = array_to_copy.copy() array_copy.flags.writeable = array_to_copy.flags.writeable return array_copy dtype_tuple = self.io_dtypes[functions[self.func]] def array_type(test_shape, dtype): a = dtype_tuple[1](test_shape, dtype) a.flags.writeable = False return a for dtype in dtype_tuple[0]: for test_shape, s, kwargs in self.test_data: s = None self.validate(array_type, test_shape, dtype, s, kwargs, copy_func=copy_with_writeable) def test_overwrite_input_for_issue_92(self): '''Tests that trying to overwrite a locked array fails. ''' a = numpy.zeros((4,)) a.flags.writeable = False self.assertRaisesRegex( ValueError, 'overwrite_input cannot be True when the ' + 'input array flags.writeable is False', interfaces.numpy_fft.fft, a, overwrite_input=True) class InterfacesNumpyFFTTestIFFT(InterfacesNumpyFFTTestFFT): func = 'ifft' class InterfacesNumpyFFTTestRFFT(InterfacesNumpyFFTTestFFT): func = 'rfft' class InterfacesNumpyFFTTestIRFFT(InterfacesNumpyFFTTestFFT): func = 'irfft' realinv = True class InterfacesNumpyFFTTestHFFT(InterfacesNumpyFFTTestFFT): func = 'hfft' realinv = True class InterfacesNumpyFFTTestIHFFT(InterfacesNumpyFFTTestFFT): func = 'ihfft' class InterfacesNumpyFFTTestFFT2(InterfacesNumpyFFTTestFFT): axes_kw = 'axes' func = 'ifft2' if numpy.__version__ >= '1.20.0': test_shapes = ( ((128, 64), {'axes': None}), ((128, 32), {'axes': None}), ((128, 32, 4), {'axes': (0, 2)}), ((59, 100), {'axes': (-2, -1)}), ((32, 32), {'axes': (-2, -1), 'norm': 'ortho'}), ((32, 32), {'axes': (-2, -1), 'norm': None}), ((32, 32), {'axes': (-2, -1), 'norm': 'backward'}), ((32, 32), {'axes': (-2, -1), 'norm': 'forward'}), ((64, 128, 16), {'axes': (0, 2)}), ((4, 6, 8, 4), {'axes': (0, 3)}), ) else: test_shapes = ( ((128, 64), {'axes': None}), ((128, 32), {'axes': None}), ((128, 32, 4), {'axes': (0, 2)}), ((59, 100), {'axes': (-2, -1)}), ((32, 32), {'axes': (-2, -1), 'norm': 'ortho'}), ((32, 32), {'axes': (-2, -1), 'norm': None}), ((64, 128, 16), {'axes': (0, 2)}), ((4, 6, 8, 4), {'axes': (0, 3)}), ) invalid_args = ( ((100,), ((100, 200),), ValueError, ''), ((100, 200), ((100, 200, 100),), ValueError, ''), ((100,), ((100, 200), (-3, -2, -1)), ValueError, ''), ((100, 200), (100, -1), TypeError, ''), ((100, 200), ((100, 200), (-3, -2)), IndexError, 'Invalid axes'), ((100, 200), ((100,), (-3,)), IndexError, 'Invalid axes'), # pass invalid normalisation string ((100, 200), ((100,), (-3,), 'invalid_norm'), ValueError, '')) def test_shape_and_s_different_lengths(self): dtype_tuple = self.io_dtypes[functions[self.func]] for dtype in dtype_tuple[0]: for test_shape, s, _kwargs in self.test_data: kwargs = copy.copy(_kwargs) try: s = s[1:] except TypeError: self.skipTest('Not meaningful test on 1d arrays.') del kwargs['axes'] self.validate(dtype_tuple[1], test_shape, dtype, s, kwargs) class InterfacesNumpyFFTTestIFFT2(InterfacesNumpyFFTTestFFT2): func = 'ifft2' class InterfacesNumpyFFTTestRFFT2(InterfacesNumpyFFTTestFFT2): func = 'rfft2' class InterfacesNumpyFFTTestIRFFT2(InterfacesNumpyFFTTestFFT2): func = 'irfft2' realinv = True class InterfacesNumpyFFTTestFFTN(InterfacesNumpyFFTTestFFT2): func = 'ifftn' if numpy.__version__ >= '1.20.0': test_shapes = ( ((128, 32, 4), {'axes': None}), ((64, 128, 16), {'axes': (0, 1, 2)}), ((4, 6, 8, 4), {'axes': (0, 3, 1)}), ((4, 6, 4, 4), {'axes': (0, 3, 1), 'norm': 'ortho'}), ((4, 6, 4, 4), {'axes': (0, 3, 1), 'norm': None}), ((4, 6, 4, 4), {'axes': (0, 3, 1), 'norm': 'backward'}), ((4, 6, 4, 4), {'axes': (0, 3, 1), 'norm': 'forward'}), ((4, 6, 8, 4), {'axes': (0, 3, 1, 2)}), ) else: test_shapes = ( ((128, 32, 4), {'axes': None}), ((64, 128, 16), {'axes': (0, 1, 2)}), ((4, 6, 8, 4), {'axes': (0, 3, 1)}), ((4, 6, 4, 4), {'axes': (0, 3, 1), 'norm': 'ortho'}), ((4, 6, 4, 4), {'axes': (0, 3, 1), 'norm': None}), ((4, 6, 8, 4), {'axes': (0, 3, 1, 2)}), ) class InterfacesNumpyFFTTestIFFTN(InterfacesNumpyFFTTestFFTN): func = 'ifftn' class InterfacesNumpyFFTTestRFFTN(InterfacesNumpyFFTTestFFTN): func = 'rfftn' class InterfacesNumpyFFTTestIRFFTN(InterfacesNumpyFFTTestFFTN): func = 'irfftn' realinv = True test_cases = ( InterfacesNumpyFFTTestModule, InterfacesNumpyFFTTestFFT, InterfacesNumpyFFTTestIFFT, InterfacesNumpyFFTTestRFFT, InterfacesNumpyFFTTestIRFFT, InterfacesNumpyFFTTestHFFT, InterfacesNumpyFFTTestIHFFT, InterfacesNumpyFFTTestFFT2, InterfacesNumpyFFTTestIFFT2, InterfacesNumpyFFTTestRFFT2, InterfacesNumpyFFTTestIRFFT2, InterfacesNumpyFFTTestFFTN, InterfacesNumpyFFTTestIFFTN, InterfacesNumpyFFTTestRFFTN, InterfacesNumpyFFTTestIRFFTN,) #test_set = {'InterfacesNumpyFFTTestHFFT': ('test_valid',)} test_set = None if __name__ == '__main__': run_test_suites(test_cases, test_set) pyFFTW-0.13.1/tests/test_pyfftw_partial.py000066400000000000000000000107701435600752200205320ustar00rootroot00000000000000# Copyright 2017 Knowledge Economy Developments Ltd # # Henry Gomersall # heng@kedevelopments.co.uk # Frederik Beaujean # Frederik.Beaujean@lmu.de # # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, this # list of conditions and the following disclaimer. # # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions and the following disclaimer in the documentation # and/or other materials provided with the distribution. # # * Neither the name of the copyright holder nor the names of its contributors # may be used to endorse or promote products derived from this software without # specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. # from pyfftw import ( FFTW, empty_aligned, interfaces, _all_types_np, _all_types_human_readable, _supported_types ) from pyfftw.builders._utils import _rc_dtype_pairs import numpy as np import unittest import warnings @unittest.skipIf(len(_all_types_human_readable) == len(_supported_types), "All data types available") class FFTWPartialTest(unittest.TestCase): def __init__(self, *args, **kwargs): super(FFTWPartialTest, self).__init__(*args, **kwargs) if not hasattr(self, 'assertRaisesRegex'): self.assertRaisesRegex = self.assertRaisesRegexp def test_failure(self): for dtype, npdtype in zip(['32', '64', 'ld'], [np.complex64, np.complex128, np.clongdouble]): if dtype == 'ld' and np.dtype(np.clongdouble) == np.dtype(np.complex128): # skip this test on systems where clongdouble is complex128 continue if dtype not in _supported_types: a = empty_aligned((1,1024), npdtype, n=16) b = empty_aligned(a.shape, dtype=a.dtype, n=16) msg = "Rebuild pyFFTW with support for %s precision!" % _all_types_human_readable[dtype] with self.assertRaisesRegex(NotImplementedError, msg): FFTW(a,b) def conversion(self, missing, alt1, alt2): '''If the ``missing`` precision is not available, the builder should convert to ``alt1`` precision. If that isn't available either, it should fall back to ``alt2``. If input precision is lost, a warning should be emitted. ''' missing, alt1, alt2 = [np.dtype(x) for x in (missing, alt1, alt2)] if _all_types_np[missing] in _supported_types: return with warnings.catch_warnings(record=True) as w: warnings.simplefilter("always") itemsize = alt1.itemsize a = empty_aligned((1, 512), dtype=missing) b = interfaces.numpy_fft.fft(a) res = _rc_dtype_pairs.get(alt1.char, None) if res is not None: self.assertEqual(b.dtype, res) else: itemsize = alt2.itemsize self.assertEqual(b.dtype, _rc_dtype_pairs[alt2.char]) if itemsize < missing.itemsize: print(itemsize, missing.itemsize) assert len(w) == 1 assert "Narrowing conversion" in str(w[-1].message) print("Found narrowing conversion from %d to %d bytes" % (missing.itemsize, itemsize)) else: assert len(w) == 0 def test_conversion(self): self.conversion('float32', 'float64', 'longdouble') self.conversion('float64', 'longdouble', 'single') self.conversion('longdouble', 'float64', 'float32') test_cases = ( FFTWPartialTest,) test_set = None if __name__ == '__main__': run_test_suites(test_cases, test_set) pyFFTW-0.13.1/tests/test_pyfftw_real_backward.py000066400000000000000000000264721435600752200216650ustar00rootroot00000000000000# Copyright 2014 Knowledge Economy Developments Ltd # # Henry Gomersall # heng@kedevelopments.co.uk # # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, this # list of conditions and the following disclaimer. # # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions and the following disclaimer in the documentation # and/or other materials provided with the distribution. # # * Neither the name of the copyright holder nor the names of its contributors # may be used to endorse or promote products derived from this software without # specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. # from pyfftw import FFTW, forget_wisdom import numpy from timeit import Timer import time from .test_pyfftw_base import run_test_suites, miss, require, np_fft import unittest from .test_pyfftw_complex import Complex64FFTWTest class RealBackwardDoubleFFTWTest(Complex64FFTWTest): def setUp(self): require(self, '64') self.input_dtype = numpy.complex128 self.output_dtype = numpy.float64 self.np_fft_comparison = np_fft.irfft self.direction = 'FFTW_BACKWARD' def make_shapes(self): self.input_shapes = { 'small_1d': (9,), '1d': (1025,), '2d': (256, 1025), '3d': (5, 256, 1025)} self.output_shapes = { 'small_1d': (16,), '1d': (2048,), '2d': (256, 2048), '3d': (5, 256, 2048)} def test_invalid_args_raise(self): in_shape = self.input_shapes['1d'] out_shape = self.output_shapes['1d'] axes=(-1,) a, b = self.create_test_arrays(in_shape, out_shape) # Note "thread" is incorrect, it should be "threads" self.assertRaises(TypeError, FFTW, a, b, axes, direction='FFTW_BACKWARD', thread=4) def create_test_arrays(self, input_shape, output_shape, axes=None): a = self.input_dtype(numpy.random.randn(*input_shape) +1j*numpy.random.randn(*input_shape)) b = self.output_dtype(numpy.random.randn(*output_shape)) # We fill a by doing the forward FFT from b. # This means that the relevant bits that should be purely # real will be (for example the zero freq component). # This is easier than writing a complicate system to work it out. try: if axes == None: fft = FFTW(b,a,direction='FFTW_FORWARD') else: fft = FFTW(b,a,direction='FFTW_FORWARD', axes=axes) b[:] = self.output_dtype(numpy.random.randn(*output_shape)) fft.execute() scaling = numpy.prod(numpy.array(a.shape)) a = self.input_dtype(a/scaling) except ValueError: # In this case, we assume that it was meant to error, # so we can return what we want. pass b = self.output_dtype(numpy.random.randn(*output_shape)) return a, b def run_validate_fft(self, a, b, axes, fft=None, ifft=None, force_unaligned_data=False, create_array_copies=True, threads=1, flags=('FFTW_ESTIMATE',)): ''' *** EVERYTHING IS FLIPPED AROUND BECAUSE WE ARE VALIDATING AN INVERSE FFT *** Run a validation of the FFTW routines for the passed pair of arrays, a and b, and the axes argument. a and b are assumed to be the same shape (but not necessarily the same layout in memory). fft and ifft, if passed, should be instantiated FFTW objects. If force_unaligned_data is True, the flag FFTW_UNALIGNED will be passed to the fftw routines. ''' if create_array_copies: # Don't corrupt the original mutable arrays a = a.copy() b = b.copy() a_orig = a.copy() flags = list(flags) if force_unaligned_data: flags.append('FFTW_UNALIGNED') if ifft == None: ifft = FFTW(a, b, axes=axes, direction='FFTW_BACKWARD', flags=flags, threads=threads) else: ifft.update_arrays(a,b) if fft == None: fft = FFTW(b, a, axes=axes, direction='FFTW_FORWARD', flags=flags, threads=threads) else: fft.update_arrays(b,a) a[:] = a_orig # Test the inverse FFT by comparing it to the result from numpy.fft ifft.execute() a[:] = a_orig ref_b = self.reference_fftn(a, axes=axes) # The scaling is the product of the lengths of the fft along # the axes along which the fft is taken. scaling = numpy.prod(numpy.array(b.shape)[list(axes)]) self.assertEqual(ifft.N, scaling) self.assertEqual(fft.N, scaling) # This is actually quite a poor relative error, but it still # sometimes fails. I assume that numpy.fft has different internals # to fftw. self.assertTrue(numpy.allclose(b/scaling, ref_b, rtol=1e-2, atol=1e-3)) # Test the FFT by comparing the result to the starting # value (which is scaled as per FFTW being unnormalised). fft.execute() self.assertTrue(numpy.allclose(a/scaling, a_orig, rtol=1e-2, atol=1e-3)) return fft, ifft def test_time_with_array_update(self): in_shape = self.input_shapes['2d'] out_shape = self.output_shapes['2d'] axes=(-1,) a, b = self.create_test_arrays(in_shape, out_shape) fft, ifft = self.run_validate_fft(a, b, axes) def fftw_callable(): fft.update_arrays(b,a) fft.execute() self.timer_routine(fftw_callable, lambda: self.np_fft_comparison(a)) self.assertTrue(True) def reference_fftn(self, a, axes): # This needs to be an inverse return np_fft.irfftn(a, axes=axes) def test_wrong_direction_fail(self): in_shape = self.input_shapes['2d'] out_shape = self.output_shapes['2d'] axes=(-1,) a, b = self.create_test_arrays(in_shape, out_shape) with self.assertRaisesRegex(ValueError, 'Invalid direction'): FFTW(a, b, direction='FFTW_FORWARD') def test_planning_time_limit(self): in_shape = self.input_shapes['1d'] out_shape = self.output_shapes['1d'] axes=(0,) a, b = self.create_test_arrays(in_shape, out_shape) # run this a few times runs = 10 t1 = time.time() for n in range(runs): forget_wisdom() fft = FFTW(b, a, axes=axes) unlimited_time = (time.time() - t1)/runs time_limit = (unlimited_time)/8 # Now do it again but with an upper limit on the time t1 = time.time() for n in range(runs): forget_wisdom() fft = FFTW(b, a, axes=axes, planning_timelimit=time_limit) limited_time = (time.time() - t1)/runs import sys if sys.platform == 'win32': # Give a 4x margin on windows. The timers are low # precision and FFTW seems to take longer anyway self.assertTrue(limited_time < time_limit*4) else: # Otherwise have a 2x margin self.assertTrue(limited_time < time_limit*2) def test_invalid_planning_time_limit(self): in_shape = self.input_shapes['1d'] out_shape = self.output_shapes['1d'] axes=(0,) a, b = self.create_test_arrays(in_shape, out_shape) self.assertRaisesRegex(TypeError, 'Invalid planning timelimit', FFTW, *(b, a, axes), **{'planning_timelimit': 'foo'}) def test_default_args(self): in_shape = self.input_shapes['2d'] out_shape = self.output_shapes['2d'] a, b = self.create_test_arrays(in_shape, out_shape) # default args should fail for backwards transforms # (as the default is FFTW_FORWARD) with self.assertRaisesRegex(ValueError, 'Invalid direction'): FFTW(a, b) def test_non_contiguous_2d(self): in_shape = self.input_shapes['2d'] out_shape = self.output_shapes['2d'] axes=(-2,-1) a, b = self.create_test_arrays(in_shape, out_shape) # Some arbitrary and crazy slicing a_sliced = a[20:146:2, 100:786:7] # b needs to be compatible b_sliced = b[12:200:3, 300:2041:9] self.run_validate_fft(a_sliced, b_sliced, axes, create_array_copies=False) def test_non_contiguous_2d_in_3d(self): in_shape = (256, 4, 1025) out_shape = (256, 4, 2048) axes=(0,2) a, b = self.create_test_arrays(in_shape, out_shape) # Some arbitrary and crazy slicing a_sliced = a[20:146:2, :, 100:786:7] # b needs to be compatible b_sliced = b[12:200:3, :, 300:2041:9] # The data doesn't work, so we need to generate it for the # correct size a_, b_ = self.create_test_arrays(a_sliced.shape, b_sliced.shape, axes=axes) # And then copy it into the non contiguous array a_sliced[:] = a_ b_sliced[:] = b_ self.run_validate_fft(a_sliced, b_sliced, axes, create_array_copies=False) def test_non_monotonic_increasing_axes(self): super(RealBackwardDoubleFFTWTest, self).test_non_monotonic_increasing_axes() @unittest.skipIf(*miss('32')) class RealBackwardSingleFFTWTest(RealBackwardDoubleFFTWTest): def setUp(self): self.input_dtype = numpy.complex64 self.output_dtype = numpy.float32 self.np_fft_comparison = np_fft.irfft self.direction = 'FFTW_BACKWARD' @unittest.skipIf(*miss('ld')) class RealBackwardLongDoubleFFTWTest(RealBackwardDoubleFFTWTest): def setUp(self): self.input_dtype = numpy.clongdouble self.output_dtype = numpy.longdouble self.np_fft_comparison = np_fft.irfft self.direction = 'FFTW_BACKWARD' def reference_fftn(self, a, axes): a = numpy.complex128(a) return np_fft.irfftn(a, axes=axes) @unittest.skip('numpy.fft has issues with this dtype.') def test_time(self): pass @unittest.skip('numpy.fft has issues with this dtype.') def test_time_with_array_update(self): pass test_cases = ( RealBackwardDoubleFFTWTest, RealBackwardSingleFFTWTest, RealBackwardLongDoubleFFTWTest,) test_set = None if __name__ == '__main__': run_test_suites(test_cases, test_set) del Complex64FFTWTest pyFFTW-0.13.1/tests/test_pyfftw_real_forward.py000066400000000000000000000120721435600752200215420ustar00rootroot00000000000000# Copyright 2014 Knowledge Economy Developments Ltd # # Henry Gomersall # heng@kedevelopments.co.uk # # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, this # list of conditions and the following disclaimer. # # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions and the following disclaimer in the documentation # and/or other materials provided with the distribution. # # * Neither the name of the copyright holder nor the names of its contributors # may be used to endorse or promote products derived from this software without # specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. # from pyfftw import FFTW import numpy from timeit import Timer from .test_pyfftw_base import run_test_suites, miss, require, np_fft import unittest from .test_pyfftw_complex import Complex64FFTWTest class RealForwardDoubleFFTWTest(Complex64FFTWTest): def setUp(self): require(self, '64') self.input_dtype = numpy.float64 self.output_dtype = numpy.complex128 self.np_fft_comparison = np_fft.rfft self.direction = 'FFTW_FORWARD' def make_shapes(self): self.input_shapes = { 'small_1d': (16,), '1d': (2048,), '2d': (256, 2048), '3d': (5, 256, 2048)} self.output_shapes = { 'small_1d': (9,), '1d': (1025,), '2d': (256, 1025), '3d': (5, 256, 1025)} def create_test_arrays(self, input_shape, output_shape, axes=None): a = self.input_dtype(numpy.random.randn(*input_shape)) b = self.output_dtype(numpy.random.randn(*output_shape) +1j*numpy.random.randn(*output_shape)) return a, b def reference_fftn(self, a, axes): return np_fft.rfftn(a, axes=axes) def test_wrong_direction_fail(self): in_shape = self.input_shapes['2d'] out_shape = self.output_shapes['2d'] axes=(-1,) a, b = self.create_test_arrays(in_shape, out_shape) with self.assertRaisesRegex(ValueError, 'Invalid direction'): FFTW(a, b, direction='FFTW_BACKWARD') def test_non_contiguous_2d(self): in_shape = self.input_shapes['2d'] out_shape = self.output_shapes['2d'] axes=(-2,-1) a, b = self.create_test_arrays(in_shape, out_shape) # Some arbitrary and crazy slicing a_sliced = a[12:200:3, 300:2041:9] # b needs to be compatible b_sliced = b[20:146:2, 100:786:7] self.run_validate_fft(a_sliced, b_sliced, axes, create_array_copies=False) def test_non_contiguous_2d_in_3d(self): in_shape = (256, 4, 2048) out_shape = in_shape axes=(0,2) a, b = self.create_test_arrays(in_shape, out_shape) # Some arbitrary and crazy slicing a_sliced = a[12:200:3, :, 300:2041:9] # b needs to be compatible b_sliced = b[20:146:2, :, 100:786:7] self.run_validate_fft(a_sliced, b_sliced, axes, create_array_copies=False) @unittest.skipIf(*miss('32')) class RealForwardSingleFFTWTest(RealForwardDoubleFFTWTest): def setUp(self): self.input_dtype = numpy.float32 self.output_dtype = numpy.complex64 self.np_fft_comparison = np_fft.rfft self.direction = 'FFTW_FORWARD' @unittest.skipIf(*miss('ld')) class RealForwardLongDoubleFFTWTest(RealForwardDoubleFFTWTest): def setUp(self): self.input_dtype = numpy.longdouble self.output_dtype = numpy.clongdouble self.np_fft_comparison = np_fft.rfft self.direction = 'FFTW_FORWARD' @unittest.skip('numpy.fft has issues with this dtype.') def test_time(self): pass @unittest.skip('numpy.fft has issues with this dtype.') def test_time_with_array_update(self): pass def reference_fftn(self, a, axes): a = numpy.float64(a) return np_fft.rfftn(a, axes=axes) test_cases = ( RealForwardDoubleFFTWTest, RealForwardSingleFFTWTest, RealForwardLongDoubleFFTWTest,) test_set = None if __name__ == '__main__': run_test_suites(test_cases, test_set) del Complex64FFTWTest pyFFTW-0.13.1/tests/test_pyfftw_scipy_fft.py000066400000000000000000000324761435600752200210730ustar00rootroot00000000000000# Copyright 2019, The pyFFTW developers # # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, this # list of conditions and the following disclaimer. # # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions and the following disclaimer in the documentation # and/or other materials provided with the distribution. # # * Neither the name of the copyright holder nor the names of its contributors # may be used to endorse or promote products derived from this software without # specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. # import pyfftw import numpy try: import scipy scipy_version = scipy.__version__ except ImportError: scipy_version = '0.0.0' from distutils.version import LooseVersion has_scipy_fft = LooseVersion(scipy_version) >= '1.4.0' if has_scipy_fft: import scipy.fft import scipy.signal from pyfftw.interfaces import scipy_fft import unittest from pyfftw import _supported_types from .test_pyfftw_base import run_test_suites, miss from . import test_pyfftw_numpy_interface '''pyfftw.interfaces.scipy_fft just wraps pyfftw.interfaces.numpy_fft. All the tests here just check that the call is made correctly. ''' funcs = ('fft', 'ifft', 'fft2', 'ifft2', 'fftn', 'ifftn', 'rfft', 'irfft', 'rfft2', 'irfft2', 'rfftn', 'irfftn', 'hfft', 'ihfft') acquired_names = ('hfft2', 'ihfft2', 'hfftn', 'ihfftn', 'fftshift', 'ifftshift', 'fftfreq', 'rfftfreq') def make_complex_data(shape, dtype): ar, ai = dtype(numpy.random.randn(2, *shape)) return ar + 1j*ai def make_r2c_real_data(shape, dtype): return dtype(numpy.random.randn(*shape)) def make_c2r_real_data(shape, dtype): return dtype(numpy.random.randn(*shape)) # reuse from numpy tests make_complex_data = test_pyfftw_numpy_interface.make_complex_data complex_dtypes = test_pyfftw_numpy_interface.complex_dtypes real_dtypes = test_pyfftw_numpy_interface.real_dtypes io_dtypes = { 'complex': (complex_dtypes, make_complex_data), 'r2c': (real_dtypes, make_r2c_real_data), 'c2r': (real_dtypes, make_c2r_real_data)} @unittest.skipIf(not has_scipy_fft, 'scipy.fft is unavailable') class InterfacesScipyFFTTestSimple(unittest.TestCase): ''' A simple test suite for a simple implementation. ''' @unittest.skipIf(*miss('64')) def test_scipy_backend(self): a = pyfftw.empty_aligned((128, 64), dtype='complex128', n=16) b = pyfftw.empty_aligned((128, 64), dtype='complex128', n=16) a[:] = (numpy.random.randn(*a.shape) + 1j*numpy.random.randn(*a.shape)) b[:] = (numpy.random.randn(*b.shape) + 1j*numpy.random.randn(*b.shape)) scipy_c = scipy.signal.fftconvolve(a, b) with scipy.fft.set_backend(scipy_fft, only=True): scipy_replaced_c = scipy.signal.fftconvolve(a, b) self.assertTrue(numpy.allclose(scipy_c, scipy_replaced_c)) def test_acquired_names(self): for each_name in acquired_names: fft_attr = getattr(scipy.fft, each_name) acquired_attr = getattr(scipy_fft, each_name) self.assertIs(fft_attr, acquired_attr) # InterfacesScipyR2RFFTTest is mostly the same as the ones defined in # test_pyfftw_scipy_interface.py, but call the functions from scipy.fft instead # of scipy.fftpack and test additional normalization modes. if '64' in _supported_types: default_floating_type = numpy.float64 elif '32' in _supported_types: default_floating_type = numpy.float32 elif 'ld' in _supported_types: default_floating_type = numpy.longdouble atol_dict = dict(f=1e-5, d=1e-7, g=1e-7) rtol_dict = dict(f=1e-4, d=1e-5, g=1e-5) transform_types = [1, 2, 3, 4] if LooseVersion(scipy_version) >= '1.6.0': # all norm options aside from None scipy_norms = [None, 'ortho', 'forward', 'backward'] else: scipy_norms = [None, 'ortho'] @unittest.skipIf(not has_scipy_fft, 'scipy.fft is unavailable') class InterfacesScipyR2RFFTTest(unittest.TestCase): ''' Class template for building the scipy real to real tests. ''' # unittest is not very smart and will always turn this class into a test, # even though it is not on the list. Hence mark test-dependent values as # constants (so this particular test ends up being run twice). func_name = 'dct' float_type = default_floating_type atol = atol_dict['f'] rtol = rtol_dict['f'] def setUp(self): self.scipy_func = getattr(scipy.fft, self.func_name) self.pyfftw_func = getattr(scipy_fft, self.func_name) self.ndims = numpy.random.randint(1, high=3) self.axis = numpy.random.randint(0, high=self.ndims) self.shape = numpy.random.randint(2, high=10, size=self.ndims) self.data = numpy.random.rand(*self.shape).astype(self.float_type) self.data_copy = self.data.copy() if self.func_name in ['dctn', 'idctn', 'dstn', 'idstn']: self.kwargs = dict(axes=(self.axis, )) else: self.kwargs = dict(axis=self.axis) def test_unnormalized(self): '''Test unnormalized pyfftw transformations against their scipy equivalents. ''' for transform_type in transform_types: data_hat_p = self.pyfftw_func(self.data, type=transform_type, overwrite_x=False, **self.kwargs) self.assertEqual(numpy.linalg.norm(self.data - self.data_copy), 0.0) data_hat_s = self.scipy_func(self.data, type=transform_type, overwrite_x=False, **self.kwargs) self.assertTrue(numpy.allclose(data_hat_p, data_hat_s, atol=self.atol, rtol=self.rtol)) def test_normalized(self): '''Test normalized against scipy results. Note that scipy does not support normalization for all transformations. ''' for norm in scipy_norms: for transform_type in transform_types: data_hat_p = self.pyfftw_func(self.data, type=transform_type, norm=norm, overwrite_x=False, **self.kwargs) if norm == 'ortho': self.assertEqual( numpy.linalg.norm(self.data - self.data_copy), 0.0 ) data_hat_s = self.scipy_func(self.data, type=transform_type, norm=norm, overwrite_x=False, **self.kwargs) self.assertTrue(numpy.allclose(data_hat_p, data_hat_s, atol=self.atol, rtol=self.rtol)) def test_normalization_inverses(self): '''Test normalization in all of the pyfftw scipy wrappers. ''' for transform_type in transform_types: inverse_type = {1: 1, 2: 3, 3: 2, 4: 4}[transform_type] forward = self.pyfftw_func(self.data, type=transform_type, norm='ortho', overwrite_x=False, **self.kwargs) result = self.pyfftw_func(forward, type=inverse_type, norm='ortho', overwrite_x=False, **self.kwargs) self.assertTrue(numpy.allclose(self.data, result, atol=self.atol, rtol=self.rtol)) @unittest.skipIf(not has_scipy_fft, 'scipy.fft is unavailable') class InterfacesScipyR2RFFTNTest(InterfacesScipyR2RFFTTest): ''' Class template for building the scipy real to real tests. ''' # unittest is not very smart and will always turn this class into a test, # even though it is not on the list. Hence mark test-dependent values as # constants (so this particular test ends up being run twice). func_name = 'dctn' float_type = default_floating_type atol = atol_dict['f'] rtol = rtol_dict['f'] def setUp(self): self.scipy_func = getattr(scipy.fft, self.func_name) self.pyfftw_func = getattr(scipy_fft, self.func_name) self.ndims = numpy.random.randint(1, high=3) self.shape = numpy.random.randint(2, high=10, size=self.ndims) self.data = numpy.random.rand(*self.shape).astype(self.float_type) self.data_copy = self.data.copy() # random subset of axes self.axes = tuple(range(0, numpy.random.randint(0, high=self.ndims))) self.kwargs = dict(axes=self.axes) def test_axes_none(self): '''Test transformation over all axes. ''' for transform_type in transform_types: data_hat_p = self.pyfftw_func(self.data, type=transform_type, overwrite_x=False, axes=None) self.assertEqual(numpy.linalg.norm(self.data - self.data_copy), 0.0) data_hat_s = self.scipy_func(self.data, type=transform_type, overwrite_x=False, axes=None) self.assertTrue(numpy.allclose(data_hat_p, data_hat_s, atol=self.atol, rtol=self.rtol)) def test_axes_scalar(self): '''Test transformation over a single, scalar axis. ''' for transform_type in transform_types: data_hat_p = self.pyfftw_func(self.data, type=transform_type, overwrite_x=False, axes=-1) self.assertEqual(numpy.linalg.norm(self.data - self.data_copy), 0.0) data_hat_s = self.scipy_func(self.data, type=transform_type, overwrite_x=False, axes=-1) self.assertTrue(numpy.allclose(data_hat_p, data_hat_s, atol=self.atol, rtol=self.rtol)) # Construct all the test classes automatically. test_cases = [] # Construct the r2r test classes. for floating_type, floating_name in [[numpy.float32, 'Float32'], [numpy.float64, 'Float64']]: if floating_type == numpy.float32 and '32' not in _supported_types: # skip single precision tests if library is unavailable continue elif floating_type == numpy.float64 and '64' not in _supported_types: # skip double precision tests if library is unavailable continue real_transforms = ('dct', 'idct', 'dst', 'idst') real_transforms_nd = ('dctn', 'idctn', 'dstn', 'idstn') real_transforms += real_transforms_nd dt_char = numpy.dtype(floating_type).char atol = atol_dict[dt_char] rtol = rtol_dict[dt_char] # test-cases where only one axis is transformed for transform_name in real_transforms: class_name = ('InterfacesScipyR2RFFTTest' + transform_name.upper() + floating_name) globals()[class_name] = type( class_name, (InterfacesScipyR2RFFTTest,), {'func_name': transform_name, 'float_type': floating_type, 'atol': atol, 'rtol': rtol}) test_cases.append(globals()[class_name]) # n-dimensional test-cases for transform_name in real_transforms_nd: class_name = ('InterfacesScipyR2RFFTNTest' + transform_name.upper() + floating_name) globals()[class_name] = type( class_name, (InterfacesScipyR2RFFTNTest,), {'func_name': transform_name, 'float_type': floating_type, 'atol': atol, 'rtol': rtol}) test_cases.append(globals()[class_name]) for each_func in funcs: class_name = 'InterfacesScipyFFTTest' + each_func.upper() parent_class_name = 'InterfacesNumpyFFTTest' + each_func.upper() parent_class = getattr(test_pyfftw_numpy_interface, parent_class_name) class_dict = {'validator_module': scipy.fft if has_scipy_fft else None, 'test_interface': scipy_fft if has_scipy_fft else None, 'io_dtypes': io_dtypes, 'overwrite_input_flag': 'overwrite_x', 'default_s_from_shape_slicer': slice(None), 'threads_arg_name': 'workers'} cls = type(class_name, (parent_class,), class_dict) cls = unittest.skipIf(not has_scipy_fft, "scipy.fft is not available")(cls) globals()[class_name] = cls test_cases.append(cls) test_cases.append(InterfacesScipyFFTTestSimple) test_set = None if __name__ == '__main__': run_test_suites(test_cases, test_set) pyFFTW-0.13.1/tests/test_pyfftw_scipy_interface.py000066400000000000000000000400641435600752200222440ustar00rootroot00000000000000# Copyright 2014 Knowledge Economy Developments Ltd # Copyright 2014 David Wells # # Henry Gomersall # heng@kedevelopments.co.uk # # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, this # list of conditions and the following disclaimer. # # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions and the following disclaimer in the documentation # and/or other materials provided with the distribution. # # * Neither the name of the copyright holder nor the names of its contributors # may be used to endorse or promote products derived from this software without # specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. # from pyfftw.interfaces import scipy_fftpack from distutils.version import LooseVersion import pyfftw from pyfftw import _supported_types import numpy try: import scipy import scipy.fftpack import scipy.signal except ImportError: scipy_missing = True else: scipy_missing = False import unittest from .test_pyfftw_base import run_test_suites, miss from . import test_pyfftw_numpy_interface '''pyfftw.interfaces.scipy_fftpack wraps pyfftw.interfaces.numpy_fft and implements the dct and dst functions. All the tests here just check that the call is made correctly. ''' funcs = ('fft', 'ifft', 'fft2', 'ifft2', 'fftn', 'ifftn', 'rfft', 'irfft') acquired_names = ('diff', 'tilbert', 'itilbert', 'hilbert', 'ihilbert', 'cs_diff', 'sc_diff', 'ss_diff', 'cc_diff', 'shift', 'fftshift', 'ifftshift', 'fftfreq', 'rfftfreq', 'convolve') def make_complex_data(shape, dtype): ar, ai = dtype(numpy.random.randn(2, *shape)) return ar + 1j*ai def make_r2c_real_data(shape, dtype): return dtype(numpy.random.randn(*shape)) def make_c2r_real_data(shape, dtype): return dtype(numpy.random.randn(*shape)) make_complex_data = test_pyfftw_numpy_interface.make_complex_data if scipy.__version__ < '0.19': # Older scipy will raise an error for inputs of type float16, so we # cannot validate transforms with float16 input vs. scipy.fftpack complex_dtypes = pyfftw._supported_nptypes_complex real_dtypes = pyfftw._supported_nptypes_real else: # reuse all dtypes from numpy tests (including float16) complex_dtypes = test_pyfftw_numpy_interface.complex_dtypes real_dtypes = test_pyfftw_numpy_interface.real_dtypes # Remove long double because scipy explicitly doesn't support it complex_dtypes = [x for x in complex_dtypes if x != numpy.clongdouble] real_dtypes = [x for x in real_dtypes if x != numpy.longdouble] def numpy_fft_replacement(a, s, axes, overwrite_input, planner_effort, threads, auto_align_input, auto_contiguous): return (a, s, axes, overwrite_input, planner_effort, threads, auto_align_input, auto_contiguous) io_dtypes = { 'complex': (complex_dtypes, make_complex_data), 'r2c': (real_dtypes, make_r2c_real_data), 'c2r': (real_dtypes, make_c2r_real_data)} if '64' in _supported_types: default_floating_type = numpy.float64 elif '32' in _supported_types: default_floating_type = numpy.float32 elif 'ld' in _supported_types: default_floating_type = numpy.longdouble atol_dict = dict(f=1e-5, d=1e-7, g=1e-7) rtol_dict = dict(f=1e-4, d=1e-5, g=1e-5) @unittest.skipIf(scipy_missing, 'scipy is not installed, so this feature is' 'unavailable') class InterfacesScipyR2RFFTPackTestSimple(unittest.TestCase): ''' A really simple test suite to check simple implementation. ''' @unittest.skipIf(*miss('64')) def test_scipy_overwrite(self): new_style_scipy_fftn = False try: scipy_fftn = scipy.signal.signaltools.fftn scipy_ifftn = scipy.signal.signaltools.ifftn except AttributeError: scipy_fftn = scipy.fftpack.fftn scipy_ifftn = scipy.fftpack.ifftn new_style_scipy_fftn = True a = pyfftw.empty_aligned((128, 64), dtype='complex128', n=16) b = pyfftw.empty_aligned((128, 64), dtype='complex128', n=16) a[:] = (numpy.random.randn(*a.shape) + 1j*numpy.random.randn(*a.shape)) b[:] = (numpy.random.randn(*b.shape) + 1j*numpy.random.randn(*b.shape)) scipy_c = scipy.signal.fftconvolve(a, b) if new_style_scipy_fftn: scipy.fftpack.fftn = scipy_fftpack.fftn scipy.fftpack.ifftn = scipy_fftpack.ifftn else: scipy.signal.signaltools.fftn = scipy_fftpack.fftn scipy.signal.signaltools.ifftn = scipy_fftpack.ifftn scipy_replaced_c = scipy.signal.fftconvolve(a, b) self.assertTrue(numpy.allclose(scipy_c, scipy_replaced_c)) if new_style_scipy_fftn: scipy.fftpack.fftn = scipy_fftn scipy.fftpack.ifftn = scipy_ifftn else: scipy.signal.signaltools.fftn = scipy_fftn scipy.signal.signaltools.ifftn = scipy_ifftn def test_funcs(self): for each_func in funcs: func_being_replaced = getattr(scipy_fftpack, each_func) #create args (8 of them) args = [] for n in range(8): args.append(object()) args = tuple(args) try: setattr(scipy_fftpack, each_func, numpy_fft_replacement) return_args = getattr(scipy_fftpack, each_func)(*args) for n, each_arg in enumerate(args): # Check that what comes back is what is sent # (which it should be) self.assertIs(each_arg, return_args[n]) except: raise finally: setattr(scipy_fftpack, each_func, func_being_replaced) def test_acquired_names(self): for each_name in acquired_names: fftpack_attr = getattr(scipy.fftpack, each_name) acquired_attr = getattr(scipy_fftpack, each_name) self.assertIs(fftpack_attr, acquired_attr) transform_types = [1, 2, 3, 4] @unittest.skipIf(scipy_missing or (LooseVersion(scipy.__version__) <= LooseVersion('1.2.0')), 'SciPy >= 1.2.0 is not installed') class InterfacesScipyR2RFFTTest(unittest.TestCase): ''' Class template for building the scipy real to real tests. ''' # unittest is not very smart and will always turn this class into a test, # even though it is not on the list. Hence mark test-dependent values as # constants (so this particular test ends up being run twice). func_name = 'dct' float_type = default_floating_type atol = atol_dict['f'] rtol = rtol_dict['f'] def setUp(self): self.scipy_func = getattr(scipy.fftpack, self.func_name) self.pyfftw_func = getattr(scipy_fftpack, self.func_name) self.ndims = numpy.random.randint(1, high=3) self.axis = numpy.random.randint(0, high=self.ndims) self.shape = numpy.random.randint(2, high=10, size=self.ndims) self.data = numpy.random.rand(*self.shape).astype(self.float_type) self.data_copy = self.data.copy() if self.func_name in ['dctn', 'idctn', 'dstn', 'idstn']: self.kwargs = dict(axes=(self.axis, )) else: self.kwargs = dict(axis=self.axis) def test_unnormalized(self): '''Test unnormalized pyfftw transformations against their scipy equivalents. ''' for transform_type in transform_types: data_hat_p = self.pyfftw_func(self.data, type=transform_type, overwrite_x=False, **self.kwargs) self.assertEqual(numpy.linalg.norm(self.data - self.data_copy), 0.0) data_hat_s = self.scipy_func(self.data, type=transform_type, overwrite_x=False, **self.kwargs) self.assertTrue(numpy.allclose(data_hat_p, data_hat_s, atol=self.atol, rtol=self.rtol)) def test_normalized(self): '''Test normalized against scipy results. Note that scipy does not support normalization for all transformations. ''' for transform_type in transform_types: data_hat_p = self.pyfftw_func(self.data, type=transform_type, norm='ortho', overwrite_x=False, **self.kwargs) self.assertEqual(numpy.linalg.norm(self.data - self.data_copy), 0.0) data_hat_s = self.scipy_func(self.data, type=transform_type, norm='ortho', overwrite_x=False, **self.kwargs) self.assertTrue(numpy.allclose(data_hat_p, data_hat_s, atol=self.atol, rtol=self.rtol)) def test_normalization_inverses(self): '''Test normalization in all of the pyfftw scipy wrappers. ''' for transform_type in transform_types: inverse_type = {1: 1, 2: 3, 3: 2, 4: 4}[transform_type] forward = self.pyfftw_func(self.data, type=transform_type, norm='ortho', overwrite_x=False, **self.kwargs) result = self.pyfftw_func(forward, type=inverse_type, norm='ortho', overwrite_x=False, **self.kwargs) self.assertTrue(numpy.allclose(self.data, result, atol=self.atol, rtol=self.rtol)) @unittest.skipIf(scipy_missing or (LooseVersion(scipy.__version__) <= LooseVersion('1.2.0')), 'SciPy >= 1.2.0 is not installed') class InterfacesScipyR2RFFTNTest(InterfacesScipyR2RFFTTest): ''' Class template for building the scipy real to real tests. ''' # unittest is not very smart and will always turn this class into a test, # even though it is not on the list. Hence mark test-dependent values as # constants (so this particular test ends up being run twice). func_name = 'dctn' float_type = default_floating_type atol = atol_dict['f'] rtol = rtol_dict['f'] def setUp(self): self.scipy_func = getattr(scipy.fftpack, self.func_name) self.pyfftw_func = getattr(scipy_fftpack, self.func_name) self.ndims = numpy.random.randint(1, high=3) self.shape = numpy.random.randint(2, high=10, size=self.ndims) self.data = numpy.random.rand(*self.shape).astype(self.float_type) self.data_copy = self.data.copy() # random subset of axes self.axes = tuple(range(0, numpy.random.randint(0, high=self.ndims))) self.kwargs = dict(axes=self.axes) def test_axes_none(self): '''Test transformation over all axes. ''' for transform_type in transform_types: data_hat_p = self.pyfftw_func(self.data, type=transform_type, overwrite_x=False, axes=None) self.assertEqual(numpy.linalg.norm(self.data - self.data_copy), 0.0) data_hat_s = self.scipy_func(self.data, type=transform_type, overwrite_x=False, axes=None) self.assertTrue(numpy.allclose(data_hat_p, data_hat_s, atol=self.atol, rtol=self.rtol)) @unittest.skipIf(LooseVersion(scipy.__version__) <= LooseVersion('1.2.0'), 'scipy version not new enough') def test_axes_scalar(self): '''Test transformation over a single, scalar axis. ''' for transform_type in transform_types: if scipy.__version__ < '1.2': # scalar axes not supported in older SciPy continue data_hat_p = self.pyfftw_func(self.data, type=transform_type, overwrite_x=False, axes=-1) self.assertEqual(numpy.linalg.norm(self.data - self.data_copy), 0.0) data_hat_s = self.scipy_func(self.data, type=transform_type, overwrite_x=False, axes=-1) self.assertTrue(numpy.allclose(data_hat_p, data_hat_s, atol=self.atol, rtol=self.rtol)) built_classes = [] # Construct the r2r test classes. for floating_type, floating_name in [[numpy.float32, 'Float32'], [numpy.float64, 'Float64']]: if floating_type == numpy.float32 and '32' not in _supported_types: # skip single precision tests if library is unavailable continue elif floating_type == numpy.float64 and '64' not in _supported_types: # skip double precision tests if library is unavailable continue real_transforms = ('dct', 'idct', 'dst', 'idst') try: # additional n-dimensional real transforms in scipy 1.0+ from scipy.fftpack import dctn real_transforms_nd = ('dctn', 'idctn', 'dstn', 'idstn') real_transforms += real_transforms_nd except ImportError: real_transforms_nd = () dt_char = numpy.dtype(floating_type).char atol = atol_dict[dt_char] rtol = rtol_dict[dt_char] # test-cases where only one axis is transformed for transform_name in real_transforms: class_name = ('InterfacesScipyR2RFFTTest' + transform_name.upper() + floating_name) globals()[class_name] = type( class_name, (InterfacesScipyR2RFFTTest,), {'func_name': transform_name, 'float_type': floating_type, 'atol': atol, 'rtol': rtol}) built_classes.append(globals()[class_name]) # n-dimensional test-cases for transform_name in real_transforms_nd: class_name = ('InterfacesScipyR2RFFTNTest' + transform_name.upper() + floating_name) globals()[class_name] = type( class_name, (InterfacesScipyR2RFFTNTest,), {'func_name': transform_name, 'float_type': floating_type, 'atol': atol, 'rtol': rtol}) built_classes.append(globals()[class_name]) # Construct the test classes derived from the numpy tests. for each_func in funcs: class_name = 'InterfacesScipyR2RFFTPackTest' + each_func.upper() parent_class_name = 'InterfacesNumpyFFTTest' + each_func.upper() parent_class = getattr(test_pyfftw_numpy_interface, parent_class_name) class_dict = {'validator_module': scipy.fftpack, 'test_interface': scipy_fftpack, 'io_dtypes': io_dtypes, 'overwrite_input_flag': 'overwrite_x', 'default_s_from_shape_slicer': slice(None)} globals()[class_name] = type(class_name, (parent_class,), class_dict) # unlike numpy, none of the scipy functions support the norm kwarg globals()[class_name].has_norm_kwarg = False built_classes.append(globals()[class_name]) built_classes = tuple(built_classes) test_cases = ( InterfacesScipyR2RFFTPackTestSimple,) + built_classes test_set = None #test_set = {'InterfacesScipyR2RFFTPackTestIFFTN': ['test_auto_align_input']} if __name__ == '__main__': run_test_suites(test_cases, test_set) pyFFTW-0.13.1/tests/test_pyfftw_utils.py000066400000000000000000000126531435600752200202400ustar00rootroot00000000000000# Copyright 2014 Knowledge Economy Developments Ltd # # Henry Gomersall # heng@kedevelopments.co.uk # # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, this # list of conditions and the following disclaimer. # # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions and the following disclaimer in the documentation # and/or other materials provided with the distribution. # # * Neither the name of the copyright holder nor the names of its contributors # may be used to endorse or promote products derived from this software without # specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. # from .test_pyfftw_base import run_test_suites import unittest import pyfftw import platform import os from numpy.testing import assert_, assert_equal def get_cpus_info(): if 'Linux' in platform.system(): # A simple /proc/cpuinfo parser with open(os.path.join('/', 'proc','cpuinfo'), 'r') as f: cpus_info = [] idx = 0 for line in f.readlines(): if line.find(':') < 0: idx += 1 continue key, values = [each.strip() for each in line.split(':')] try: cpus_info[idx][key] = values except IndexError: cpus_info.append({key: values}) else: cpus_info = None return cpus_info class UtilsTest(unittest.TestCase): def setUp(self): return def tearDown(self): return @unittest.skipIf('Linux' not in platform.system(), 'Skipping as we only have it set up for Linux at present.') def test_get_alignment(self): cpus_info = get_cpus_info() for each_cpu in cpus_info: if 'avx' in each_cpu['flags']: self.assertTrue(pyfftw.simd_alignment == 32) elif 'sse' in each_cpu['flags']: self.assertTrue(pyfftw.simd_alignment == 16) else: self.assertTrue(pyfftw.simd_alignment == 1) class NextFastLenTest(unittest.TestCase): def __init__(self, *args, **kwargs): super(NextFastLenTest, self).__init__(*args, **kwargs) if not hasattr(self, 'assertRaisesRegex'): self.assertRaisesRegex = self.assertRaisesRegexp def test_next_fast_len(self): def nums(): for j in range(1, 1000): yield j yield 2**5 * 3**5 * 4**5 + 1 for n in nums(): m = pyfftw.next_fast_len(n) msg = "n=%d, m=%d" % (n, m) assert_(m >= n, msg) # check regularity k = m num11 = num13 = 0 # These factors come from the description in the FFTW3 docs: # http://fftw.org/fftw3_doc/Complex-DFTs.html#Complex-DFTs for d in [2, 3, 5, 7, 11, 13]: while True: a, b = divmod(k, d) if b == 0: k = a if d in [11, 13]: # only allowed to match 11 or 13 once if num11 > 0 or num13 > 0: break if d == 11: num11 += 1 else: num13 += 1 else: break assert_equal(k, 1, err_msg=msg) def test_next_fast_len_strict(self): strict_test_cases = { 1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 6: 6, 7: 7, 8: 8, 11: 11, 13: 13, 14: 14, 15: 15, 16: 16, 17: 18, 1021: 1024, # 2 * 3 * 5 * 7 * 11 2310: 2310, 2310 - 1: 2310, # 2 * 3 * 5 * 7 * 13 2730: 2730, 2730 - 1: 2730, # 2**2 * 3**2 * 5**2 * 7**2 * 11 485100: 485100, 485100-1: 485100, # 2**2 * 3**2 * 5**2 * 7**2 * 13 573300: 573300, 573300-1: 573300, # more than one multiple of 11 or 13 is not accepted # 2 * 3 * 5 * 7 * 11**2 25410: 25872, # 2 * 3 * 5 * 7 * 13**2 35490: 35672, # 2 * 3 * 5 * 7 * 11 * 13 30030: 30576, } for x, y in strict_test_cases.items(): assert_equal(pyfftw.next_fast_len(x), y) test_cases = ( UtilsTest, NextFastLenTest) test_set = None if __name__ == '__main__': run_test_suites(test_cases, test_set) pyFFTW-0.13.1/tests/test_pyfftw_wisdom.py000066400000000000000000000070511435600752200203760ustar00rootroot00000000000000# Copyright 2014 Knowledge Economy Developments Ltd # # Henry Gomersall # heng@kedevelopments.co.uk # # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, this # list of conditions and the following disclaimer. # # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions and the following disclaimer in the documentation # and/or other materials provided with the distribution. # # * Neither the name of the copyright holder nor the names of its contributors # may be used to endorse or promote products derived from this software without # specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. # from pyfftw import ( FFTW, empty_aligned, export_wisdom, import_wisdom, forget_wisdom, _supported_types, _supported_nptypes_complex) from .test_pyfftw_base import run_test_suites import numpy import pickle import sys import unittest class FFTWWisdomTest(unittest.TestCase): def generate_wisdom(self): for each_dtype in _supported_nptypes_complex: n = 16 a = empty_aligned((1,1024), each_dtype, n=n) b = empty_aligned(a.shape, dtype=a.dtype, n=n) fft = FFTW(a,b) def compare_single(self, prec, before, after): # skip over unsupported data types where wisdom is the empty string if prec in _supported_types: # wisdom not updated for ld, at least on appveyor; e.g. # https://ci.appveyor.com/project/hgomersall/pyfftw/build/job/vweyed25jx8oxxcb if prec == 'ld' and sys.platform.startswith("win"): pass else: self.assertNotEqual(before, after) else: self.assertEqual(before, b'') self.assertEqual(before, after) def compare(self, before, after): for prec, ind in zip(['64', '32', 'ld'], [0,1,2]): self.compare_single(prec, before[ind], after[ind]) def test_export(self): forget_wisdom() before_wisdom = export_wisdom() self.generate_wisdom() after_wisdom = export_wisdom() self.compare(before_wisdom, after_wisdom) def test_import(self): forget_wisdom() self.generate_wisdom() after_wisdom = export_wisdom() forget_wisdom() before_wisdom = export_wisdom() success = import_wisdom(after_wisdom) self.compare(before_wisdom, after_wisdom) self.assertEqual(success, tuple([x in _supported_types for x in ['64', '32', 'ld']])) test_cases = ( FFTWWisdomTest,) test_set = None if __name__ == '__main__': run_test_suites(test_cases, test_set) pyFFTW-0.13.1/tests/test_r2r.py000066400000000000000000000332621435600752200162050ustar00rootroot00000000000000#!/usr/bin/env python # # Copyright 2014 Knowledge Economy Developments Ltd # Copyright 2014 - 2016 David Wells # # Henry Gomersall # heng@kedevelopments.co.uk # # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, this # list of conditions and the following disclaimer. # # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions and the following disclaimer in the documentation # and/or other materials provided with the distribution. # # * Neither the name of the copyright holder nor the names of its contributors # may be used to endorse or promote products derived from this software without # specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. # from __future__ import division import itertools as it import random as rand import unittest import numpy import pyfftw from pyfftw import _supported_types from .test_pyfftw_base import run_test_suites discrete_sine_directions = ['FFTW_RODFT00', 'FFTW_RODFT01', 'FFTW_RODFT10', 'FFTW_RODFT11'] discrete_cosine_directions = ['FFTW_REDFT00', 'FFTW_REDFT01', 'FFTW_REDFT10', 'FFTW_REDFT11'] real_transforms = discrete_sine_directions + discrete_cosine_directions normalisation_lookup = { 'FFTW_RODFT00': lambda n: 2*(n + 1), 'FFTW_RODFT01': lambda n: 2*n, 'FFTW_RODFT10': lambda n: 2*n, 'FFTW_RODFT11': lambda n: 2*n, 'FFTW_REDFT00': lambda n: 2*(n - 1), 'FFTW_REDFT01': lambda n: 2*n, 'FFTW_REDFT10': lambda n: 2*n, 'FFTW_REDFT11': lambda n: 2*n, } inverse_lookup = { 'FFTW_RODFT00': 'FFTW_RODFT00', 'FFTW_RODFT01': 'FFTW_RODFT10', 'FFTW_RODFT10': 'FFTW_RODFT01', 'FFTW_RODFT11': 'FFTW_RODFT11', 'FFTW_REDFT00': 'FFTW_REDFT00', 'FFTW_REDFT01': 'FFTW_REDFT10', 'FFTW_REDFT10': 'FFTW_REDFT01', 'FFTW_REDFT11': 'FFTW_REDFT11', } interpolated_function_lookup = { 'FFTW_RODFT00': lambda k, x: numpy.sin(numpy.pi*(k + 1)*x), 'FFTW_RODFT01': lambda k, x: numpy.sin(numpy.pi*(k + 0.5)*x), 'FFTW_RODFT10': lambda k, x: numpy.sin(numpy.pi*(k + 1)*x), 'FFTW_RODFT11': lambda k, x: numpy.sin(numpy.pi*(k + 0.5)*x), 'FFTW_REDFT00': lambda k, x: numpy.cos(numpy.pi*k*x), 'FFTW_REDFT10': lambda k, x: numpy.cos(numpy.pi*k*x), 'FFTW_REDFT01': lambda k, x: numpy.cos(numpy.pi*(k + 0.5)*x), 'FFTW_REDFT11': lambda k, x: numpy.cos(numpy.pi*(k + 0.5)*x), } nodes_lookup = { 'FFTW_RODFT00': lambda n: numpy.arange(n + 2)[1:-1]/(n + 1), 'FFTW_RODFT01': lambda n: numpy.arange(1, n + 1)/n, 'FFTW_RODFT10': lambda n: (numpy.arange(n) + 0.5)/n, 'FFTW_RODFT11': lambda n: (numpy.arange(n) + 0.5)/n, 'FFTW_REDFT00': lambda n: numpy.arange(n)/(n - 1), 'FFTW_REDFT10': lambda n: (numpy.arange(n) + 0.5)/n, 'FFTW_REDFT01': lambda n: numpy.arange(n)/n, 'FFTW_REDFT11': lambda n: (numpy.arange(n) + 0.5)/n, } @unittest.skipIf('64' not in _supported_types, 'double precision unavailable') class TestRealToRealLookups(unittest.TestCase): '''Test that the lookup tables correctly pair node choices and function choices for using the DCT/DST as interpolators. ''' def test_lookups(self): n = rand.randint(10, 20) j = rand.randint(5, n) - 3 for transform in real_transforms: nodes = nodes_lookup[transform](n) data = interpolated_function_lookup[transform](j, nodes) output = numpy.empty_like(data) plan = pyfftw.FFTW(data, output, direction=[transform]) data[:] = interpolated_function_lookup[transform](j, nodes) plan.execute() tol = 4*j*n*1e-16 if transform == 'FFTW_RODFT00': self.assertTrue(abs(output[j] - n - 1) < tol) elif transform == 'FFTW_REDFT00': self.assertTrue(abs(output[j] - n + 1) < tol) else: self.assertTrue(abs(output[j] - n) < tol) class TestRealTransform(object): '''Common set of functionality for performing tests on the real to real transforms. This is not implemented as a distinct test class (inheriting from unittest.TestCase) because its `__init__` method takes multiple arguments as input which set up the size and directions of the transform. ''' def __init__(self, directions=['FFTW_REDFT00'], dims=(16, ), axes=None, noncontiguous=True, dtype=None): """ Arguments: - `directions`: List of abbreviated directions, like 'O11' or 'E01'. - `dims`: Shape of the data. - `axes`: Axes on which to take the transformation. Defaults to the number of directions. """ if axes is None: self.axes = tuple(range(len(directions))) else: self.axes = axes for dim in dims: if dim < 3: raise NotImplementedError("Due to complications with the DCT1, " "arrays must be of length at least " "three.") if len(self.axes) != len(directions): raise ValueError("There must be exactly one axis per direction.") self.directions = directions self.inverse_directions = [inverse_lookup[direction] for direction in directions] self.dims = dims self._normalisation_factor = 1.0 for index, axis in enumerate(self.axes): dim = self.dims[axis] direction = self.directions[index] self._normalisation_factor *= normalisation_lookup[direction](dim) if noncontiguous: self._input_array = empty_noncontiguous(dims) self._output_array = empty_noncontiguous(dims) else: self._input_array = numpy.zeros(dims) self._output_array = numpy.zeros(dims) self.plan = pyfftw.FFTW(self._input_array, self._output_array, axes=self.axes, direction=self.directions) self.inverse_plan = pyfftw.FFTW(self._input_array, self._output_array, axes=self.axes, direction=self.inverse_directions) self.tol = 1e-10 if dtype is None: if '64' in _supported_types: dtype = numpy.float64 elif '32' in _supported_types: dtype = numpy.float32 self.tol = 1e-5 elif 'ld' in _supported_types: dtype = numpy.longdouble self.tol = 1e-14 self.dtype = dtype def test_normalisation(self): return self._normalisation_factor == float(self.plan._get_N()) def test_against_random_data(self): data = numpy.random.rand(*self.dims).astype(self.dtype, copy=False) self._input_array[:] = data self.plan.execute() self._input_array[:] = self._output_array[:] self.inverse_plan.execute() data *= self._normalisation_factor err = numpy.mean(numpy.abs(data - self._output_array))/self._normalisation_factor return err < self.tol def test_against_exact_data(self): points = grid(self.dims, self.axes, self.directions) data = numpy.ones_like(points[0], dtype=self.dtype) wavenumbers = list() factors = list() for index, axis in enumerate(self.axes): # Simplification: don't test constant terms. They are weird. if self.directions[index] in discrete_cosine_directions: wavenumber_min = 1 wavenumber_max = self.dims[axis] - 2 else: wavenumber_min = 0 wavenumber_max = self.dims[axis] - 2 _wavenumbers = sorted({rand.randint(wavenumber_min, wavenumber_max) for _ in range(self.dims[axis])}) _factors = [rand.randint(1, 8) for _ in _wavenumbers] interpolated_function = interpolated_function_lookup[ self.directions[index]] data *= sum((factor*interpolated_function(wavenumber, points[axis]) for factor, wavenumber in zip(_factors, _wavenumbers))) wavenumbers.append(numpy.array(_wavenumbers)) factors.append(numpy.array(_factors)) self._input_array[:] = data self.plan.execute() # zero all of the entries that do not correspond to a wavenumber. exact_coefficients = numpy.ones(data.shape) for index, axis in enumerate(self.axes): dim = self.dims[axis] sp = list(it.repeat(slice(None), len(data.shape))) zero_indicies = (numpy.array(list(set(numpy.arange(0, dim)) - set(wavenumbers[index])))) if len(zero_indicies) == 0: pass else: sp[axis] = zero_indicies mask = numpy.ones(data.shape) mask[tuple(sp)] = 0.0 exact_coefficients *= mask # create the 'known' array of interpolation coefficients. normalisation = self.plan.N/(2**len(self.axes)) for index, axis in enumerate(self.axes): for factor, wavenumber in zip(factors[index], wavenumbers[index]): sp = list(it.repeat(slice(None), len(data.shape))) sp[axis] = wavenumber exact_coefficients[tuple(sp)] *= factor error = numpy.mean(numpy.abs(self._output_array/normalisation - exact_coefficients)) return error < self.tol def meshgrid(*x): if len(x) == 1: # necessary for one-dimensional case to work correctly. x is a # tuple due to the * operator. return x else: args = numpy.atleast_1d(*x) s0 = (1,)*len(args) return list(map(numpy.squeeze, numpy.broadcast_arrays(*[x.reshape(s0[:i] + (-1,) + s0[i + 1::]) for i, x in enumerate(args)]))) def grid(shape, axes, directions, aspect_ratio=None): grids = [numpy.linspace(1, 2, dim) for dim in shape] for index, (axis, direction) in enumerate(zip(axes, directions)): grids[axis] = nodes_lookup[direction](shape[axes[index]]) return numpy.array(meshgrid(*grids)) def empty_noncontiguous(shape): '''Create a non-contiguous empty array with shape `shape`. ''' offsets = lambda s: [rand.randint(0, 3) for _ in s] strides = lambda s: [rand.randint(1, 3) for _ in s] parent_left_offsets = offsets(shape) parent_right_offsets = offsets(shape) parent_strides = strides(shape) parent_shape = list() child_slice = list() for index, length in enumerate(shape): left_offset = parent_left_offsets[index] right_offset = parent_right_offsets[index] stride = parent_strides[index] parent_shape.append(left_offset + stride*length + right_offset) if right_offset == 0: child_slice.append(slice(left_offset, None, stride)) else: child_slice.append(slice(left_offset, -1*right_offset, stride)) child = numpy.empty(parent_shape)[tuple(child_slice)] if list(child.shape) != list(shape): raise ValueError("The shape of the noncontiguous array is incorrect." " This is a bug.") return child def random_testcase(): ndims = rand.randint(1, 5) axes = list() directions = list() dims = list() for dim in range(ndims): if ndims > 3: dims.append(rand.randint(3, 10)) else: dims.append(rand.randint(3, 100)) # throw out some dimensions randomly if rand.choice([True, True, False]): directions.append(rand.choice(real_transforms)) axes.append(dim) if len(axes) == 0: # reroll. return random_testcase() else: return TestRealTransform(directions, dims, axes=axes) @unittest.skipIf('64' not in _supported_types, 'double precision unavailable') class RealToRealNormalisation(unittest.TestCase): def test_normalisation(self): for _ in range(50): testcase = random_testcase() self.assertTrue(testcase.test_normalisation()) @unittest.skipIf('64' not in _supported_types, 'double precision unavailable') class RealToRealExactData(unittest.TestCase): def test_exact_data(self): for _ in range(50): testcase = random_testcase() self.assertTrue(testcase.test_against_exact_data()) @unittest.skipIf('64' not in _supported_types, 'double precision unavailable') class RealToRealRandomData(unittest.TestCase): def test_random_data(self): for _ in range(50): testcase = random_testcase() self.assertTrue(testcase.test_against_random_data()) test_cases = (TestRealToRealLookups, RealToRealNormalisation, RealToRealExactData, RealToRealRandomData,) if __name__ == '__main__': run_test_suites(test_cases) pyFFTW-0.13.1/versioneer.py000066400000000000000000002060031435600752200154530ustar00rootroot00000000000000 # Version: 0.18 """The Versioneer - like a rocketeer, but for versions. The Versioneer ============== * like a rocketeer, but for versions! * https://github.com/warner/python-versioneer * Brian Warner * License: Public Domain * Compatible With: python2.6, 2.7, 3.2, 3.3, 3.4, 3.5, 3.6, and pypy * [![Latest Version] (https://pypip.in/version/versioneer/badge.svg?style=flat) ](https://pypi.python.org/pypi/versioneer/) * [![Build Status] (https://travis-ci.org/warner/python-versioneer.png?branch=master) ](https://travis-ci.org/warner/python-versioneer) This is a tool for managing a recorded version number in distutils-based python projects. The goal is to remove the tedious and error-prone "update the embedded version string" step from your release process. Making a new release should be as easy as recording a new tag in your version-control system, and maybe making new tarballs. ## Quick Install * `pip install versioneer` to somewhere to your $PATH * add a `[versioneer]` section to your setup.cfg (see below) * run `versioneer install` in your source tree, commit the results ## Version Identifiers Source trees come from a variety of places: * a version-control system checkout (mostly used by developers) * a nightly tarball, produced by build automation * a snapshot tarball, produced by a web-based VCS browser, like github's "tarball from tag" feature * a release tarball, produced by "setup.py sdist", distributed through PyPI Within each source tree, the version identifier (either a string or a number, this tool is format-agnostic) can come from a variety of places: * ask the VCS tool itself, e.g. "git describe" (for checkouts), which knows about recent "tags" and an absolute revision-id * the name of the directory into which the tarball was unpacked * an expanded VCS keyword ($Id$, etc) * a `_version.py` created by some earlier build step For released software, the version identifier is closely related to a VCS tag. Some projects use tag names that include more than just the version string (e.g. "myproject-1.2" instead of just "1.2"), in which case the tool needs to strip the tag prefix to extract the version identifier. For unreleased software (between tags), the version identifier should provide enough information to help developers recreate the same tree, while also giving them an idea of roughly how old the tree is (after version 1.2, before version 1.3). Many VCS systems can report a description that captures this, for example `git describe --tags --dirty --always` reports things like "0.7-1-g574ab98-dirty" to indicate that the checkout is one revision past the 0.7 tag, has a unique revision id of "574ab98", and is "dirty" (it has uncommitted changes. The version identifier is used for multiple purposes: * to allow the module to self-identify its version: `myproject.__version__` * to choose a name and prefix for a 'setup.py sdist' tarball ## Theory of Operation Versioneer works by adding a special `_version.py` file into your source tree, where your `__init__.py` can import it. This `_version.py` knows how to dynamically ask the VCS tool for version information at import time. `_version.py` also contains `$Revision$` markers, and the installation process marks `_version.py` to have this marker rewritten with a tag name during the `git archive` command. As a result, generated tarballs will contain enough information to get the proper version. To allow `setup.py` to compute a version too, a `versioneer.py` is added to the top level of your source tree, next to `setup.py` and the `setup.cfg` that configures it. This overrides several distutils/setuptools commands to compute the version when invoked, and changes `setup.py build` and `setup.py sdist` to replace `_version.py` with a small static file that contains just the generated version data. ## Installation See [INSTALL.md](./INSTALL.md) for detailed installation instructions. ## Version-String Flavors Code which uses Versioneer can learn about its version string at runtime by importing `_version` from your main `__init__.py` file and running the `get_versions()` function. From the "outside" (e.g. in `setup.py`), you can import the top-level `versioneer.py` and run `get_versions()`. Both functions return a dictionary with different flavors of version information: * `['version']`: A condensed version string, rendered using the selected style. This is the most commonly used value for the project's version string. The default "pep440" style yields strings like `0.11`, `0.11+2.g1076c97`, or `0.11+2.g1076c97.dirty`. See the "Styles" section below for alternative styles. * `['full-revisionid']`: detailed revision identifier. For Git, this is the full SHA1 commit id, e.g. "1076c978a8d3cfc70f408fe5974aa6c092c949ac". * `['date']`: Date and time of the latest `HEAD` commit. For Git, it is the commit date in ISO 8601 format. This will be None if the date is not available. * `['dirty']`: a boolean, True if the tree has uncommitted changes. Note that this is only accurate if run in a VCS checkout, otherwise it is likely to be False or None * `['error']`: if the version string could not be computed, this will be set to a string describing the problem, otherwise it will be None. It may be useful to throw an exception in setup.py if this is set, to avoid e.g. creating tarballs with a version string of "unknown". Some variants are more useful than others. Including `full-revisionid` in a bug report should allow developers to reconstruct the exact code being tested (or indicate the presence of local changes that should be shared with the developers). `version` is suitable for display in an "about" box or a CLI `--version` output: it can be easily compared against release notes and lists of bugs fixed in various releases. The installer adds the following text to your `__init__.py` to place a basic version in `YOURPROJECT.__version__`: from ._version import get_versions __version__ = get_versions()['version'] del get_versions ## Styles The setup.cfg `style=` configuration controls how the VCS information is rendered into a version string. The default style, "pep440", produces a PEP440-compliant string, equal to the un-prefixed tag name for actual releases, and containing an additional "local version" section with more detail for in-between builds. For Git, this is TAG[+DISTANCE.gHEX[.dirty]] , using information from `git describe --tags --dirty --always`. For example "0.11+2.g1076c97.dirty" indicates that the tree is like the "1076c97" commit but has uncommitted changes (".dirty"), and that this commit is two revisions ("+2") beyond the "0.11" tag. For released software (exactly equal to a known tag), the identifier will only contain the stripped tag, e.g. "0.11". Other styles are available. See [details.md](details.md) in the Versioneer source tree for descriptions. ## Debugging Versioneer tries to avoid fatal errors: if something goes wrong, it will tend to return a version of "0+unknown". To investigate the problem, run `setup.py version`, which will run the version-lookup code in a verbose mode, and will display the full contents of `get_versions()` (including the `error` string, which may help identify what went wrong). ## Known Limitations Some situations are known to cause problems for Versioneer. This details the most significant ones. More can be found on Github [issues page](https://github.com/warner/python-versioneer/issues). ### Subprojects Versioneer has limited support for source trees in which `setup.py` is not in the root directory (e.g. `setup.py` and `.git/` are *not* siblings). The are two common reasons why `setup.py` might not be in the root: * Source trees which contain multiple subprojects, such as [Buildbot](https://github.com/buildbot/buildbot), which contains both "master" and "slave" subprojects, each with their own `setup.py`, `setup.cfg`, and `tox.ini`. Projects like these produce multiple PyPI distributions (and upload multiple independently-installable tarballs). * Source trees whose main purpose is to contain a C library, but which also provide bindings to Python (and perhaps other langauges) in subdirectories. Versioneer will look for `.git` in parent directories, and most operations should get the right version string. However `pip` and `setuptools` have bugs and implementation details which frequently cause `pip install .` from a subproject directory to fail to find a correct version string (so it usually defaults to `0+unknown`). `pip install --editable .` should work correctly. `setup.py install` might work too. Pip-8.1.1 is known to have this problem, but hopefully it will get fixed in some later version. [Bug #38](https://github.com/warner/python-versioneer/issues/38) is tracking this issue. The discussion in [PR #61](https://github.com/warner/python-versioneer/pull/61) describes the issue from the Versioneer side in more detail. [pip PR#3176](https://github.com/pypa/pip/pull/3176) and [pip PR#3615](https://github.com/pypa/pip/pull/3615) contain work to improve pip to let Versioneer work correctly. Versioneer-0.16 and earlier only looked for a `.git` directory next to the `setup.cfg`, so subprojects were completely unsupported with those releases. ### Editable installs with setuptools <= 18.5 `setup.py develop` and `pip install --editable .` allow you to install a project into a virtualenv once, then continue editing the source code (and test) without re-installing after every change. "Entry-point scripts" (`setup(entry_points={"console_scripts": ..})`) are a convenient way to specify executable scripts that should be installed along with the python package. These both work as expected when using modern setuptools. When using setuptools-18.5 or earlier, however, certain operations will cause `pkg_resources.DistributionNotFound` errors when running the entrypoint script, which must be resolved by re-installing the package. This happens when the install happens with one version, then the egg_info data is regenerated while a different version is checked out. Many setup.py commands cause egg_info to be rebuilt (including `sdist`, `wheel`, and installing into a different virtualenv), so this can be surprising. [Bug #83](https://github.com/warner/python-versioneer/issues/83) describes this one, but upgrading to a newer version of setuptools should probably resolve it. ### Unicode version strings While Versioneer works (and is continually tested) with both Python 2 and Python 3, it is not entirely consistent with bytes-vs-unicode distinctions. Newer releases probably generate unicode version strings on py2. It's not clear that this is wrong, but it may be surprising for applications when then write these strings to a network connection or include them in bytes-oriented APIs like cryptographic checksums. [Bug #71](https://github.com/warner/python-versioneer/issues/71) investigates this question. ## Updating Versioneer To upgrade your project to a new release of Versioneer, do the following: * install the new Versioneer (`pip install -U versioneer` or equivalent) * edit `setup.cfg`, if necessary, to include any new configuration settings indicated by the release notes. See [UPGRADING](./UPGRADING.md) for details. * re-run `versioneer install` in your source tree, to replace `SRC/_version.py` * commit any changed files ## Future Directions This tool is designed to make it easily extended to other version-control systems: all VCS-specific components are in separate directories like src/git/ . The top-level `versioneer.py` script is assembled from these components by running make-versioneer.py . In the future, make-versioneer.py will take a VCS name as an argument, and will construct a version of `versioneer.py` that is specific to the given VCS. It might also take the configuration arguments that are currently provided manually during installation by editing setup.py . Alternatively, it might go the other direction and include code from all supported VCS systems, reducing the number of intermediate scripts. ## License To make Versioneer easier to embed, all its code is dedicated to the public domain. The `_version.py` that it creates is also in the public domain. Specifically, both are released under the Creative Commons "Public Domain Dedication" license (CC0-1.0), as described in https://creativecommons.org/publicdomain/zero/1.0/ . """ from __future__ import print_function try: import configparser except ImportError: import ConfigParser as configparser import errno import json import os import re import subprocess import sys class VersioneerConfig: """Container for Versioneer configuration parameters.""" def get_root(): """Get the project root directory. We require that all commands are run from the project root, i.e. the directory that contains setup.py, setup.cfg, and versioneer.py . """ root = os.path.realpath(os.path.abspath(os.getcwd())) setup_py = os.path.join(root, "setup.py") versioneer_py = os.path.join(root, "versioneer.py") if not (os.path.exists(setup_py) or os.path.exists(versioneer_py)): # allow 'python path/to/setup.py COMMAND' root = os.path.dirname(os.path.realpath(os.path.abspath(sys.argv[0]))) setup_py = os.path.join(root, "setup.py") versioneer_py = os.path.join(root, "versioneer.py") if not (os.path.exists(setup_py) or os.path.exists(versioneer_py)): err = ("Versioneer was unable to run the project root directory. " "Versioneer requires setup.py to be executed from " "its immediate directory (like 'python setup.py COMMAND'), " "or in a way that lets it use sys.argv[0] to find the root " "(like 'python path/to/setup.py COMMAND').") raise VersioneerBadRootError(err) try: # Certain runtime workflows (setup.py install/develop in a setuptools # tree) execute all dependencies in a single python process, so # "versioneer" may be imported multiple times, and python's shared # module-import table will cache the first one. So we can't use # os.path.dirname(__file__), as that will find whichever # versioneer.py was first imported, even in later projects. me = os.path.realpath(os.path.abspath(__file__)) me_dir = os.path.normcase(os.path.splitext(me)[0]) vsr_dir = os.path.normcase(os.path.splitext(versioneer_py)[0]) if me_dir != vsr_dir: print("Warning: build in %s is using versioneer.py from %s" % (os.path.dirname(me), versioneer_py)) except NameError: pass return root def get_config_from_root(root): """Read the project setup.cfg file to determine Versioneer config.""" # This might raise EnvironmentError (if setup.cfg is missing), or # configparser.NoSectionError (if it lacks a [versioneer] section), or # configparser.NoOptionError (if it lacks "VCS="). See the docstring at # the top of versioneer.py for instructions on writing your setup.cfg . setup_cfg = os.path.join(root, "setup.cfg") parser = configparser.SafeConfigParser() with open(setup_cfg, "r") as f: parser.readfp(f) VCS = parser.get("versioneer", "VCS") # mandatory def get(parser, name): if parser.has_option("versioneer", name): return parser.get("versioneer", name) return None cfg = VersioneerConfig() cfg.VCS = VCS cfg.style = get(parser, "style") or "" cfg.versionfile_source = get(parser, "versionfile_source") cfg.versionfile_build = get(parser, "versionfile_build") cfg.tag_prefix = get(parser, "tag_prefix") if cfg.tag_prefix in ("''", '""'): cfg.tag_prefix = "" cfg.parentdir_prefix = get(parser, "parentdir_prefix") cfg.verbose = get(parser, "verbose") return cfg class NotThisMethod(Exception): """Exception raised if a method is not valid for the current scenario.""" # these dictionaries contain VCS-specific tools LONG_VERSION_PY = {} HANDLERS = {} def register_vcs_handler(vcs, method): # decorator """Decorator to mark a method as the handler for a particular VCS.""" def decorate(f): """Store f in HANDLERS[vcs][method].""" if vcs not in HANDLERS: HANDLERS[vcs] = {} HANDLERS[vcs][method] = f return f return decorate def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False, env=None): """Call the given command(s).""" assert isinstance(commands, list) p = None for c in commands: try: dispcmd = str([c] + args) # remember shell=False, so use git.cmd on windows, not just git p = subprocess.Popen([c] + args, cwd=cwd, env=env, stdout=subprocess.PIPE, stderr=(subprocess.PIPE if hide_stderr else None)) break except EnvironmentError: e = sys.exc_info()[1] if e.errno == errno.ENOENT: continue if verbose: print("unable to run %s" % dispcmd) print(e) return None, None else: if verbose: print("unable to find command, tried %s" % (commands,)) return None, None stdout = p.communicate()[0].strip() if sys.version_info[0] >= 3: stdout = stdout.decode() if p.returncode != 0: if verbose: print("unable to run %s (error)" % dispcmd) print("stdout was %s" % stdout) return None, p.returncode return stdout, p.returncode LONG_VERSION_PY['git'] = ''' # This file helps to compute a version number in source trees obtained from # git-archive tarball (such as those provided by githubs download-from-tag # feature). Distribution tarballs (built by setup.py sdist) and build # directories (produced by setup.py build) will contain a much shorter file # that just contains the computed version number. # This file is released into the public domain. Generated by # versioneer-0.18 (https://github.com/warner/python-versioneer) """Git implementation of _version.py.""" import errno import os import re import subprocess import sys def get_keywords(): """Get the keywords needed to look up the version information.""" # these strings will be replaced by git during git-archive. # setup.py/versioneer.py will grep for the variable names, so they must # each be defined on a line of their own. _version.py will just call # get_keywords(). git_refnames = "%(DOLLAR)sFormat:%%d%(DOLLAR)s" git_full = "%(DOLLAR)sFormat:%%H%(DOLLAR)s" git_date = "%(DOLLAR)sFormat:%%ci%(DOLLAR)s" keywords = {"refnames": git_refnames, "full": git_full, "date": git_date} return keywords class VersioneerConfig: """Container for Versioneer configuration parameters.""" def get_config(): """Create, populate and return the VersioneerConfig() object.""" # these strings are filled in when 'setup.py versioneer' creates # _version.py cfg = VersioneerConfig() cfg.VCS = "git" cfg.style = "%(STYLE)s" cfg.tag_prefix = "%(TAG_PREFIX)s" cfg.parentdir_prefix = "%(PARENTDIR_PREFIX)s" cfg.versionfile_source = "%(VERSIONFILE_SOURCE)s" cfg.verbose = False return cfg class NotThisMethod(Exception): """Exception raised if a method is not valid for the current scenario.""" LONG_VERSION_PY = {} HANDLERS = {} def register_vcs_handler(vcs, method): # decorator """Decorator to mark a method as the handler for a particular VCS.""" def decorate(f): """Store f in HANDLERS[vcs][method].""" if vcs not in HANDLERS: HANDLERS[vcs] = {} HANDLERS[vcs][method] = f return f return decorate def run_command(commands, args, cwd=None, verbose=False, hide_stderr=False, env=None): """Call the given command(s).""" assert isinstance(commands, list) p = None for c in commands: try: dispcmd = str([c] + args) # remember shell=False, so use git.cmd on windows, not just git p = subprocess.Popen([c] + args, cwd=cwd, env=env, stdout=subprocess.PIPE, stderr=(subprocess.PIPE if hide_stderr else None)) break except EnvironmentError: e = sys.exc_info()[1] if e.errno == errno.ENOENT: continue if verbose: print("unable to run %%s" %% dispcmd) print(e) return None, None else: if verbose: print("unable to find command, tried %%s" %% (commands,)) return None, None stdout = p.communicate()[0].strip() if sys.version_info[0] >= 3: stdout = stdout.decode() if p.returncode != 0: if verbose: print("unable to run %%s (error)" %% dispcmd) print("stdout was %%s" %% stdout) return None, p.returncode return stdout, p.returncode def versions_from_parentdir(parentdir_prefix, root, verbose): """Try to determine the version from the parent directory name. Source tarballs conventionally unpack into a directory that includes both the project name and a version string. We will also support searching up two directory levels for an appropriately named parent directory """ rootdirs = [] for i in range(3): dirname = os.path.basename(root) if dirname.startswith(parentdir_prefix): return {"version": dirname[len(parentdir_prefix):], "full-revisionid": None, "dirty": False, "error": None, "date": None} else: rootdirs.append(root) root = os.path.dirname(root) # up a level if verbose: print("Tried directories %%s but none started with prefix %%s" %% (str(rootdirs), parentdir_prefix)) raise NotThisMethod("rootdir doesn't start with parentdir_prefix") @register_vcs_handler("git", "get_keywords") def git_get_keywords(versionfile_abs): """Extract version information from the given file.""" # the code embedded in _version.py can just fetch the value of these # keywords. When used from setup.py, we don't want to import _version.py, # so we do it with a regexp instead. This function is not used from # _version.py. keywords = {} try: f = open(versionfile_abs, "r") for line in f.readlines(): if line.strip().startswith("git_refnames ="): mo = re.search(r'=\s*"(.*)"', line) if mo: keywords["refnames"] = mo.group(1) if line.strip().startswith("git_full ="): mo = re.search(r'=\s*"(.*)"', line) if mo: keywords["full"] = mo.group(1) if line.strip().startswith("git_date ="): mo = re.search(r'=\s*"(.*)"', line) if mo: keywords["date"] = mo.group(1) f.close() except EnvironmentError: pass return keywords @register_vcs_handler("git", "keywords") def git_versions_from_keywords(keywords, tag_prefix, verbose): """Get version information from git keywords.""" if not keywords: raise NotThisMethod("no keywords at all, weird") date = keywords.get("date") if date is not None: # git-2.2.0 added "%%cI", which expands to an ISO-8601 -compliant # datestamp. However we prefer "%%ci" (which expands to an "ISO-8601 # -like" string, which we must then edit to make compliant), because # it's been around since git-1.5.3, and it's too difficult to # discover which version we're using, or to work around using an # older one. date = date.strip().replace(" ", "T", 1).replace(" ", "", 1) refnames = keywords["refnames"].strip() if refnames.startswith("$Format"): if verbose: print("keywords are unexpanded, not using") raise NotThisMethod("unexpanded keywords, not a git-archive tarball") refs = set([r.strip() for r in refnames.strip("()").split(",")]) # starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of # just "foo-1.0". If we see a "tag: " prefix, prefer those. TAG = "tag: " tags = set([r[len(TAG):] for r in refs if r.startswith(TAG)]) if not tags: # Either we're using git < 1.8.3, or there really are no tags. We use # a heuristic: assume all version tags have a digit. The old git %%d # expansion behaves like git log --decorate=short and strips out the # refs/heads/ and refs/tags/ prefixes that would let us distinguish # between branches and tags. By ignoring refnames without digits, we # filter out many common branch names like "release" and # "stabilization", as well as "HEAD" and "master". tags = set([r for r in refs if re.search(r'\d', r)]) if verbose: print("discarding '%%s', no digits" %% ",".join(refs - tags)) if verbose: print("likely tags: %%s" %% ",".join(sorted(tags))) for ref in sorted(tags): # sorting will prefer e.g. "2.0" over "2.0rc1" if ref.startswith(tag_prefix): r = ref[len(tag_prefix):] if verbose: print("picking %%s" %% r) return {"version": r, "full-revisionid": keywords["full"].strip(), "dirty": False, "error": None, "date": date} # no suitable tags, so version is "0+unknown", but full hex is still there if verbose: print("no suitable tags, using unknown + full revision id") return {"version": "0+unknown", "full-revisionid": keywords["full"].strip(), "dirty": False, "error": "no suitable tags", "date": None} @register_vcs_handler("git", "pieces_from_vcs") def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): """Get version from 'git describe' in the root of the source tree. This only gets called if the git-archive 'subst' keywords were *not* expanded, and _version.py hasn't already been rewritten with a short version string, meaning we're inside a checked out source tree. """ GITS = ["git"] if sys.platform == "win32": GITS = ["git.cmd", "git.exe"] out, rc = run_command(GITS, ["rev-parse", "--git-dir"], cwd=root, hide_stderr=True) if rc != 0: if verbose: print("Directory %%s not under git control" %% root) raise NotThisMethod("'git rev-parse --git-dir' returned error") # if there is a tag matching tag_prefix, this yields TAG-NUM-gHEX[-dirty] # if there isn't one, this yields HEX[-dirty] (no NUM) describe_out, rc = run_command(GITS, ["describe", "--tags", "--dirty", "--always", "--long", "--match", "%%s*" %% tag_prefix], cwd=root) # --long was added in git-1.5.5 if describe_out is None: raise NotThisMethod("'git describe' failed") describe_out = describe_out.strip() full_out, rc = run_command(GITS, ["rev-parse", "HEAD"], cwd=root) if full_out is None: raise NotThisMethod("'git rev-parse' failed") full_out = full_out.strip() pieces = {} pieces["long"] = full_out pieces["short"] = full_out[:7] # maybe improved later pieces["error"] = None # parse describe_out. It will be like TAG-NUM-gHEX[-dirty] or HEX[-dirty] # TAG might have hyphens. git_describe = describe_out # look for -dirty suffix dirty = git_describe.endswith("-dirty") pieces["dirty"] = dirty if dirty: git_describe = git_describe[:git_describe.rindex("-dirty")] # now we have TAG-NUM-gHEX or HEX if "-" in git_describe: # TAG-NUM-gHEX mo = re.search(r'^(.+)-(\d+)-g([0-9a-f]+)$', git_describe) if not mo: # unparseable. Maybe git-describe is misbehaving? pieces["error"] = ("unable to parse git-describe output: '%%s'" %% describe_out) return pieces # tag full_tag = mo.group(1) if not full_tag.startswith(tag_prefix): if verbose: fmt = "tag '%%s' doesn't start with prefix '%%s'" print(fmt %% (full_tag, tag_prefix)) pieces["error"] = ("tag '%%s' doesn't start with prefix '%%s'" %% (full_tag, tag_prefix)) return pieces pieces["closest-tag"] = full_tag[len(tag_prefix):] # distance: number of commits since tag pieces["distance"] = int(mo.group(2)) # commit: short hex revision ID pieces["short"] = mo.group(3) else: # HEX: no tags pieces["closest-tag"] = None count_out, rc = run_command(GITS, ["rev-list", "HEAD", "--count"], cwd=root) pieces["distance"] = int(count_out) # total number of commits # commit date: see ISO-8601 comment in git_versions_from_keywords() date = run_command(GITS, ["show", "-s", "--format=%%ci", "HEAD"], cwd=root)[0].strip() pieces["date"] = date.strip().replace(" ", "T", 1).replace(" ", "", 1) return pieces def plus_or_dot(pieces): """Return a + if we don't already have one, else return a .""" if "+" in pieces.get("closest-tag", ""): return "." return "+" def render_pep440(pieces): """Build up version string, with post-release "local version identifier". Our goal: TAG[+DISTANCE.gHEX[.dirty]] . Note that if you get a tagged build and then dirty it, you'll get TAG+0.gHEX.dirty Exceptions: 1: no tags. git_describe was just HEX. 0+untagged.DISTANCE.gHEX[.dirty] """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"] or pieces["dirty"]: rendered += plus_or_dot(pieces) rendered += "%%d.g%%s" %% (pieces["distance"], pieces["short"]) if pieces["dirty"]: rendered += ".dirty" else: # exception #1 rendered = "0+untagged.%%d.g%%s" %% (pieces["distance"], pieces["short"]) if pieces["dirty"]: rendered += ".dirty" return rendered def render_pep440_pre(pieces): """TAG[.post.devDISTANCE] -- No -dirty. Exceptions: 1: no tags. 0.post.devDISTANCE """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"]: rendered += ".post.dev%%d" %% pieces["distance"] else: # exception #1 rendered = "0.post.dev%%d" %% pieces["distance"] return rendered def render_pep440_post(pieces): """TAG[.postDISTANCE[.dev0]+gHEX] . The ".dev0" means dirty. Note that .dev0 sorts backwards (a dirty tree will appear "older" than the corresponding clean one), but you shouldn't be releasing software with -dirty anyways. Exceptions: 1: no tags. 0.postDISTANCE[.dev0] """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"] or pieces["dirty"]: rendered += ".post%%d" %% pieces["distance"] if pieces["dirty"]: rendered += ".dev0" rendered += plus_or_dot(pieces) rendered += "g%%s" %% pieces["short"] else: # exception #1 rendered = "0.post%%d" %% pieces["distance"] if pieces["dirty"]: rendered += ".dev0" rendered += "+g%%s" %% pieces["short"] return rendered def render_pep440_old(pieces): """TAG[.postDISTANCE[.dev0]] . The ".dev0" means dirty. Eexceptions: 1: no tags. 0.postDISTANCE[.dev0] """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"] or pieces["dirty"]: rendered += ".post%%d" %% pieces["distance"] if pieces["dirty"]: rendered += ".dev0" else: # exception #1 rendered = "0.post%%d" %% pieces["distance"] if pieces["dirty"]: rendered += ".dev0" return rendered def render_git_describe(pieces): """TAG[-DISTANCE-gHEX][-dirty]. Like 'git describe --tags --dirty --always'. Exceptions: 1: no tags. HEX[-dirty] (note: no 'g' prefix) """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"]: rendered += "-%%d-g%%s" %% (pieces["distance"], pieces["short"]) else: # exception #1 rendered = pieces["short"] if pieces["dirty"]: rendered += "-dirty" return rendered def render_git_describe_long(pieces): """TAG-DISTANCE-gHEX[-dirty]. Like 'git describe --tags --dirty --always -long'. The distance/hash is unconditional. Exceptions: 1: no tags. HEX[-dirty] (note: no 'g' prefix) """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] rendered += "-%%d-g%%s" %% (pieces["distance"], pieces["short"]) else: # exception #1 rendered = pieces["short"] if pieces["dirty"]: rendered += "-dirty" return rendered def render(pieces, style): """Render the given version pieces into the requested style.""" if pieces["error"]: return {"version": "unknown", "full-revisionid": pieces.get("long"), "dirty": None, "error": pieces["error"], "date": None} if not style or style == "default": style = "pep440" # the default if style == "pep440": rendered = render_pep440(pieces) elif style == "pep440-pre": rendered = render_pep440_pre(pieces) elif style == "pep440-post": rendered = render_pep440_post(pieces) elif style == "pep440-old": rendered = render_pep440_old(pieces) elif style == "git-describe": rendered = render_git_describe(pieces) elif style == "git-describe-long": rendered = render_git_describe_long(pieces) else: raise ValueError("unknown style '%%s'" %% style) return {"version": rendered, "full-revisionid": pieces["long"], "dirty": pieces["dirty"], "error": None, "date": pieces.get("date")} def get_versions(): """Get version information or return default if unable to do so.""" # I am in _version.py, which lives at ROOT/VERSIONFILE_SOURCE. If we have # __file__, we can work backwards from there to the root. Some # py2exe/bbfreeze/non-CPython implementations don't do __file__, in which # case we can only use expanded keywords. cfg = get_config() verbose = cfg.verbose try: return git_versions_from_keywords(get_keywords(), cfg.tag_prefix, verbose) except NotThisMethod: pass try: root = os.path.realpath(__file__) # versionfile_source is the relative path from the top of the source # tree (where the .git directory might live) to this file. Invert # this to find the root from __file__. for i in cfg.versionfile_source.split('/'): root = os.path.dirname(root) except NameError: return {"version": "0+unknown", "full-revisionid": None, "dirty": None, "error": "unable to find root of source tree", "date": None} try: pieces = git_pieces_from_vcs(cfg.tag_prefix, root, verbose) return render(pieces, cfg.style) except NotThisMethod: pass try: if cfg.parentdir_prefix: return versions_from_parentdir(cfg.parentdir_prefix, root, verbose) except NotThisMethod: pass return {"version": "0+unknown", "full-revisionid": None, "dirty": None, "error": "unable to compute version", "date": None} ''' @register_vcs_handler("git", "get_keywords") def git_get_keywords(versionfile_abs): """Extract version information from the given file.""" # the code embedded in _version.py can just fetch the value of these # keywords. When used from setup.py, we don't want to import _version.py, # so we do it with a regexp instead. This function is not used from # _version.py. keywords = {} try: f = open(versionfile_abs, "r") for line in f.readlines(): if line.strip().startswith("git_refnames ="): mo = re.search(r'=\s*"(.*)"', line) if mo: keywords["refnames"] = mo.group(1) if line.strip().startswith("git_full ="): mo = re.search(r'=\s*"(.*)"', line) if mo: keywords["full"] = mo.group(1) if line.strip().startswith("git_date ="): mo = re.search(r'=\s*"(.*)"', line) if mo: keywords["date"] = mo.group(1) f.close() except EnvironmentError: pass return keywords @register_vcs_handler("git", "keywords") def git_versions_from_keywords(keywords, tag_prefix, verbose): """Get version information from git keywords.""" if not keywords: raise NotThisMethod("no keywords at all, weird") date = keywords.get("date") if date is not None: # git-2.2.0 added "%cI", which expands to an ISO-8601 -compliant # datestamp. However we prefer "%ci" (which expands to an "ISO-8601 # -like" string, which we must then edit to make compliant), because # it's been around since git-1.5.3, and it's too difficult to # discover which version we're using, or to work around using an # older one. date = date.strip().replace(" ", "T", 1).replace(" ", "", 1) refnames = keywords["refnames"].strip() if refnames.startswith("$Format"): if verbose: print("keywords are unexpanded, not using") raise NotThisMethod("unexpanded keywords, not a git-archive tarball") refs = set([r.strip() for r in refnames.strip("()").split(",")]) # starting in git-1.8.3, tags are listed as "tag: foo-1.0" instead of # just "foo-1.0". If we see a "tag: " prefix, prefer those. TAG = "tag: " tags = set([r[len(TAG):] for r in refs if r.startswith(TAG)]) if not tags: # Either we're using git < 1.8.3, or there really are no tags. We use # a heuristic: assume all version tags have a digit. The old git %d # expansion behaves like git log --decorate=short and strips out the # refs/heads/ and refs/tags/ prefixes that would let us distinguish # between branches and tags. By ignoring refnames without digits, we # filter out many common branch names like "release" and # "stabilization", as well as "HEAD" and "master". tags = set([r for r in refs if re.search(r'\d', r)]) if verbose: print("discarding '%s', no digits" % ",".join(refs - tags)) if verbose: print("likely tags: %s" % ",".join(sorted(tags))) for ref in sorted(tags): # sorting will prefer e.g. "2.0" over "2.0rc1" if ref.startswith(tag_prefix): r = ref[len(tag_prefix):] if verbose: print("picking %s" % r) return {"version": r, "full-revisionid": keywords["full"].strip(), "dirty": False, "error": None, "date": date} # no suitable tags, so version is "0+unknown", but full hex is still there if verbose: print("no suitable tags, using unknown + full revision id") return {"version": "0+unknown", "full-revisionid": keywords["full"].strip(), "dirty": False, "error": "no suitable tags", "date": None} @register_vcs_handler("git", "pieces_from_vcs") def git_pieces_from_vcs(tag_prefix, root, verbose, run_command=run_command): """Get version from 'git describe' in the root of the source tree. This only gets called if the git-archive 'subst' keywords were *not* expanded, and _version.py hasn't already been rewritten with a short version string, meaning we're inside a checked out source tree. """ GITS = ["git"] if sys.platform == "win32": GITS = ["git.cmd", "git.exe"] out, rc = run_command(GITS, ["rev-parse", "--git-dir"], cwd=root, hide_stderr=True) if rc != 0: if verbose: print("Directory %s not under git control" % root) raise NotThisMethod("'git rev-parse --git-dir' returned error") # if there is a tag matching tag_prefix, this yields TAG-NUM-gHEX[-dirty] # if there isn't one, this yields HEX[-dirty] (no NUM) describe_out, rc = run_command(GITS, ["describe", "--tags", "--dirty", "--always", "--long", "--match", "%s*" % tag_prefix], cwd=root) # --long was added in git-1.5.5 if describe_out is None: raise NotThisMethod("'git describe' failed") describe_out = describe_out.strip() full_out, rc = run_command(GITS, ["rev-parse", "HEAD"], cwd=root) if full_out is None: raise NotThisMethod("'git rev-parse' failed") full_out = full_out.strip() pieces = {} pieces["long"] = full_out pieces["short"] = full_out[:7] # maybe improved later pieces["error"] = None # parse describe_out. It will be like TAG-NUM-gHEX[-dirty] or HEX[-dirty] # TAG might have hyphens. git_describe = describe_out # look for -dirty suffix dirty = git_describe.endswith("-dirty") pieces["dirty"] = dirty if dirty: git_describe = git_describe[:git_describe.rindex("-dirty")] # now we have TAG-NUM-gHEX or HEX if "-" in git_describe: # TAG-NUM-gHEX mo = re.search(r'^(.+)-(\d+)-g([0-9a-f]+)$', git_describe) if not mo: # unparseable. Maybe git-describe is misbehaving? pieces["error"] = ("unable to parse git-describe output: '%s'" % describe_out) return pieces # tag full_tag = mo.group(1) if not full_tag.startswith(tag_prefix): if verbose: fmt = "tag '%s' doesn't start with prefix '%s'" print(fmt % (full_tag, tag_prefix)) pieces["error"] = ("tag '%s' doesn't start with prefix '%s'" % (full_tag, tag_prefix)) return pieces pieces["closest-tag"] = full_tag[len(tag_prefix):] # distance: number of commits since tag pieces["distance"] = int(mo.group(2)) # commit: short hex revision ID pieces["short"] = mo.group(3) else: # HEX: no tags pieces["closest-tag"] = None count_out, rc = run_command(GITS, ["rev-list", "HEAD", "--count"], cwd=root) pieces["distance"] = int(count_out) # total number of commits # commit date: see ISO-8601 comment in git_versions_from_keywords() date = run_command(GITS, ["show", "-s", "--format=%ci", "HEAD"], cwd=root)[0].strip() pieces["date"] = date.strip().replace(" ", "T", 1).replace(" ", "", 1) return pieces def do_vcs_install(manifest_in, versionfile_source, ipy): """Git-specific installation logic for Versioneer. For Git, this means creating/changing .gitattributes to mark _version.py for export-subst keyword substitution. """ GITS = ["git"] if sys.platform == "win32": GITS = ["git.cmd", "git.exe"] files = [manifest_in, versionfile_source] if ipy: files.append(ipy) try: me = __file__ if me.endswith(".pyc") or me.endswith(".pyo"): me = os.path.splitext(me)[0] + ".py" versioneer_file = os.path.relpath(me) except NameError: versioneer_file = "versioneer.py" files.append(versioneer_file) present = False try: f = open(".gitattributes", "r") for line in f.readlines(): if line.strip().startswith(versionfile_source): if "export-subst" in line.strip().split()[1:]: present = True f.close() except EnvironmentError: pass if not present: f = open(".gitattributes", "a+") f.write("%s export-subst\n" % versionfile_source) f.close() files.append(".gitattributes") run_command(GITS, ["add", "--"] + files) def versions_from_parentdir(parentdir_prefix, root, verbose): """Try to determine the version from the parent directory name. Source tarballs conventionally unpack into a directory that includes both the project name and a version string. We will also support searching up two directory levels for an appropriately named parent directory """ rootdirs = [] for i in range(3): dirname = os.path.basename(root) if dirname.startswith(parentdir_prefix): return {"version": dirname[len(parentdir_prefix):], "full-revisionid": None, "dirty": False, "error": None, "date": None} else: rootdirs.append(root) root = os.path.dirname(root) # up a level if verbose: print("Tried directories %s but none started with prefix %s" % (str(rootdirs), parentdir_prefix)) raise NotThisMethod("rootdir doesn't start with parentdir_prefix") SHORT_VERSION_PY = """ # This file was generated by 'versioneer.py' (0.18) from # revision-control system data, or from the parent directory name of an # unpacked source archive. Distribution tarballs contain a pre-generated copy # of this file. import json version_json = ''' %s ''' # END VERSION_JSON def get_versions(): return json.loads(version_json) """ def versions_from_file(filename): """Try to determine the version from _version.py if present.""" try: with open(filename) as f: contents = f.read() except EnvironmentError: raise NotThisMethod("unable to read _version.py") mo = re.search(r"version_json = '''\n(.*)''' # END VERSION_JSON", contents, re.M | re.S) if not mo: mo = re.search(r"version_json = '''\r\n(.*)''' # END VERSION_JSON", contents, re.M | re.S) if not mo: raise NotThisMethod("no version_json in _version.py") return json.loads(mo.group(1)) def write_to_version_file(filename, versions): """Write the given version number to the given _version.py file.""" os.unlink(filename) contents = json.dumps(versions, sort_keys=True, indent=1, separators=(",", ": ")) with open(filename, "w") as f: f.write(SHORT_VERSION_PY % contents) print("set %s to '%s'" % (filename, versions["version"])) def plus_or_dot(pieces): """Return a + if we don't already have one, else return a .""" if "+" in pieces.get("closest-tag", ""): return "." return "+" def render_pep440(pieces): """Build up version string, with post-release "local version identifier". Our goal: TAG[+DISTANCE.gHEX[.dirty]] . Note that if you get a tagged build and then dirty it, you'll get TAG+0.gHEX.dirty Exceptions: 1: no tags. git_describe was just HEX. 0+untagged.DISTANCE.gHEX[.dirty] """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"] or pieces["dirty"]: rendered += plus_or_dot(pieces) rendered += "%d.g%s" % (pieces["distance"], pieces["short"]) if pieces["dirty"]: rendered += ".dirty" else: # exception #1 rendered = "0+untagged.%d.g%s" % (pieces["distance"], pieces["short"]) if pieces["dirty"]: rendered += ".dirty" return rendered def render_pep440_pre(pieces): """TAG[.post.devDISTANCE] -- No -dirty. Exceptions: 1: no tags. 0.post.devDISTANCE """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"]: rendered += ".post.dev%d" % pieces["distance"] else: # exception #1 rendered = "0.post.dev%d" % pieces["distance"] return rendered def render_pep440_post(pieces): """TAG[.postDISTANCE[.dev0]+gHEX] . The ".dev0" means dirty. Note that .dev0 sorts backwards (a dirty tree will appear "older" than the corresponding clean one), but you shouldn't be releasing software with -dirty anyways. Exceptions: 1: no tags. 0.postDISTANCE[.dev0] """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"] or pieces["dirty"]: rendered += ".post%d" % pieces["distance"] if pieces["dirty"]: rendered += ".dev0" rendered += plus_or_dot(pieces) rendered += "g%s" % pieces["short"] else: # exception #1 rendered = "0.post%d" % pieces["distance"] if pieces["dirty"]: rendered += ".dev0" rendered += "+g%s" % pieces["short"] return rendered def render_pep440_old(pieces): """TAG[.postDISTANCE[.dev0]] . The ".dev0" means dirty. Eexceptions: 1: no tags. 0.postDISTANCE[.dev0] """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"] or pieces["dirty"]: rendered += ".post%d" % pieces["distance"] if pieces["dirty"]: rendered += ".dev0" else: # exception #1 rendered = "0.post%d" % pieces["distance"] if pieces["dirty"]: rendered += ".dev0" return rendered def render_git_describe(pieces): """TAG[-DISTANCE-gHEX][-dirty]. Like 'git describe --tags --dirty --always'. Exceptions: 1: no tags. HEX[-dirty] (note: no 'g' prefix) """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] if pieces["distance"]: rendered += "-%d-g%s" % (pieces["distance"], pieces["short"]) else: # exception #1 rendered = pieces["short"] if pieces["dirty"]: rendered += "-dirty" return rendered def render_git_describe_long(pieces): """TAG-DISTANCE-gHEX[-dirty]. Like 'git describe --tags --dirty --always -long'. The distance/hash is unconditional. Exceptions: 1: no tags. HEX[-dirty] (note: no 'g' prefix) """ if pieces["closest-tag"]: rendered = pieces["closest-tag"] rendered += "-%d-g%s" % (pieces["distance"], pieces["short"]) else: # exception #1 rendered = pieces["short"] if pieces["dirty"]: rendered += "-dirty" return rendered def render(pieces, style): """Render the given version pieces into the requested style.""" if pieces["error"]: return {"version": "unknown", "full-revisionid": pieces.get("long"), "dirty": None, "error": pieces["error"], "date": None} if not style or style == "default": style = "pep440" # the default if style == "pep440": rendered = render_pep440(pieces) elif style == "pep440-pre": rendered = render_pep440_pre(pieces) elif style == "pep440-post": rendered = render_pep440_post(pieces) elif style == "pep440-old": rendered = render_pep440_old(pieces) elif style == "git-describe": rendered = render_git_describe(pieces) elif style == "git-describe-long": rendered = render_git_describe_long(pieces) else: raise ValueError("unknown style '%s'" % style) return {"version": rendered, "full-revisionid": pieces["long"], "dirty": pieces["dirty"], "error": None, "date": pieces.get("date")} class VersioneerBadRootError(Exception): """The project root directory is unknown or missing key files.""" def get_versions(verbose=False): """Get the project version from whatever source is available. Returns dict with two keys: 'version' and 'full'. """ if "versioneer" in sys.modules: # see the discussion in cmdclass.py:get_cmdclass() del sys.modules["versioneer"] root = get_root() cfg = get_config_from_root(root) assert cfg.VCS is not None, "please set [versioneer]VCS= in setup.cfg" handlers = HANDLERS.get(cfg.VCS) assert handlers, "unrecognized VCS '%s'" % cfg.VCS verbose = verbose or cfg.verbose assert cfg.versionfile_source is not None, \ "please set versioneer.versionfile_source" assert cfg.tag_prefix is not None, "please set versioneer.tag_prefix" versionfile_abs = os.path.join(root, cfg.versionfile_source) # extract version from first of: _version.py, VCS command (e.g. 'git # describe'), parentdir. This is meant to work for developers using a # source checkout, for users of a tarball created by 'setup.py sdist', # and for users of a tarball/zipball created by 'git archive' or github's # download-from-tag feature or the equivalent in other VCSes. get_keywords_f = handlers.get("get_keywords") from_keywords_f = handlers.get("keywords") if get_keywords_f and from_keywords_f: try: keywords = get_keywords_f(versionfile_abs) ver = from_keywords_f(keywords, cfg.tag_prefix, verbose) if verbose: print("got version from expanded keyword %s" % ver) return ver except NotThisMethod: pass try: ver = versions_from_file(versionfile_abs) if verbose: print("got version from file %s %s" % (versionfile_abs, ver)) return ver except NotThisMethod: pass from_vcs_f = handlers.get("pieces_from_vcs") if from_vcs_f: try: pieces = from_vcs_f(cfg.tag_prefix, root, verbose) ver = render(pieces, cfg.style) if verbose: print("got version from VCS %s" % ver) return ver except NotThisMethod: pass try: if cfg.parentdir_prefix: ver = versions_from_parentdir(cfg.parentdir_prefix, root, verbose) if verbose: print("got version from parentdir %s" % ver) return ver except NotThisMethod: pass if verbose: print("unable to compute version") return {"version": "0+unknown", "full-revisionid": None, "dirty": None, "error": "unable to compute version", "date": None} def get_version(): """Get the short version string for this project.""" return get_versions()["version"] def get_cmdclass(): """Get the custom setuptools/distutils subclasses used by Versioneer.""" if "versioneer" in sys.modules: del sys.modules["versioneer"] # this fixes the "python setup.py develop" case (also 'install' and # 'easy_install .'), in which subdependencies of the main project are # built (using setup.py bdist_egg) in the same python process. Assume # a main project A and a dependency B, which use different versions # of Versioneer. A's setup.py imports A's Versioneer, leaving it in # sys.modules by the time B's setup.py is executed, causing B to run # with the wrong versioneer. Setuptools wraps the sub-dep builds in a # sandbox that restores sys.modules to it's pre-build state, so the # parent is protected against the child's "import versioneer". By # removing ourselves from sys.modules here, before the child build # happens, we protect the child from the parent's versioneer too. # Also see https://github.com/warner/python-versioneer/issues/52 cmds = {} # we add "version" to both distutils and setuptools from distutils.core import Command class cmd_version(Command): description = "report generated version string" user_options = [] boolean_options = [] def initialize_options(self): pass def finalize_options(self): pass def run(self): vers = get_versions(verbose=True) print("Version: %s" % vers["version"]) print(" full-revisionid: %s" % vers.get("full-revisionid")) print(" dirty: %s" % vers.get("dirty")) print(" date: %s" % vers.get("date")) if vers["error"]: print(" error: %s" % vers["error"]) cmds["version"] = cmd_version # we override "build_py" in both distutils and setuptools # # most invocation pathways end up running build_py: # distutils/build -> build_py # distutils/install -> distutils/build ->.. # setuptools/bdist_wheel -> distutils/install ->.. # setuptools/bdist_egg -> distutils/install_lib -> build_py # setuptools/install -> bdist_egg ->.. # setuptools/develop -> ? # pip install: # copies source tree to a tempdir before running egg_info/etc # if .git isn't copied too, 'git describe' will fail # then does setup.py bdist_wheel, or sometimes setup.py install # setup.py egg_info -> ? # we override different "build_py" commands for both environments if "setuptools" in sys.modules: from setuptools.command.build_py import build_py as _build_py else: from distutils.command.build_py import build_py as _build_py class cmd_build_py(_build_py): def run(self): root = get_root() cfg = get_config_from_root(root) versions = get_versions() _build_py.run(self) # now locate _version.py in the new build/ directory and replace # it with an updated value if cfg.versionfile_build: target_versionfile = os.path.join(self.build_lib, cfg.versionfile_build) print("UPDATING %s" % target_versionfile) write_to_version_file(target_versionfile, versions) cmds["build_py"] = cmd_build_py if "cx_Freeze" in sys.modules: # cx_freeze enabled? from cx_Freeze.dist import build_exe as _build_exe # nczeczulin reports that py2exe won't like the pep440-style string # as FILEVERSION, but it can be used for PRODUCTVERSION, e.g. # setup(console=[{ # "version": versioneer.get_version().split("+", 1)[0], # FILEVERSION # "product_version": versioneer.get_version(), # ... class cmd_build_exe(_build_exe): def run(self): root = get_root() cfg = get_config_from_root(root) versions = get_versions() target_versionfile = cfg.versionfile_source print("UPDATING %s" % target_versionfile) write_to_version_file(target_versionfile, versions) _build_exe.run(self) os.unlink(target_versionfile) with open(cfg.versionfile_source, "w") as f: LONG = LONG_VERSION_PY[cfg.VCS] f.write(LONG % {"DOLLAR": "$", "STYLE": cfg.style, "TAG_PREFIX": cfg.tag_prefix, "PARENTDIR_PREFIX": cfg.parentdir_prefix, "VERSIONFILE_SOURCE": cfg.versionfile_source, }) cmds["build_exe"] = cmd_build_exe del cmds["build_py"] if 'py2exe' in sys.modules: # py2exe enabled? try: from py2exe.distutils_buildexe import py2exe as _py2exe # py3 except ImportError: from py2exe.build_exe import py2exe as _py2exe # py2 class cmd_py2exe(_py2exe): def run(self): root = get_root() cfg = get_config_from_root(root) versions = get_versions() target_versionfile = cfg.versionfile_source print("UPDATING %s" % target_versionfile) write_to_version_file(target_versionfile, versions) _py2exe.run(self) os.unlink(target_versionfile) with open(cfg.versionfile_source, "w") as f: LONG = LONG_VERSION_PY[cfg.VCS] f.write(LONG % {"DOLLAR": "$", "STYLE": cfg.style, "TAG_PREFIX": cfg.tag_prefix, "PARENTDIR_PREFIX": cfg.parentdir_prefix, "VERSIONFILE_SOURCE": cfg.versionfile_source, }) cmds["py2exe"] = cmd_py2exe # we override different "sdist" commands for both environments if "setuptools" in sys.modules: from setuptools.command.sdist import sdist as _sdist else: from distutils.command.sdist import sdist as _sdist class cmd_sdist(_sdist): def run(self): versions = get_versions() self._versioneer_generated_versions = versions # unless we update this, the command will keep using the old # version self.distribution.metadata.version = versions["version"] return _sdist.run(self) def make_release_tree(self, base_dir, files): root = get_root() cfg = get_config_from_root(root) _sdist.make_release_tree(self, base_dir, files) # now locate _version.py in the new base_dir directory # (remembering that it may be a hardlink) and replace it with an # updated value target_versionfile = os.path.join(base_dir, cfg.versionfile_source) print("UPDATING %s" % target_versionfile) write_to_version_file(target_versionfile, self._versioneer_generated_versions) cmds["sdist"] = cmd_sdist return cmds CONFIG_ERROR = """ setup.cfg is missing the necessary Versioneer configuration. You need a section like: [versioneer] VCS = git style = pep440 versionfile_source = src/myproject/_version.py versionfile_build = myproject/_version.py tag_prefix = parentdir_prefix = myproject- You will also need to edit your setup.py to use the results: import versioneer setup(version=versioneer.get_version(), cmdclass=versioneer.get_cmdclass(), ...) Please read the docstring in ./versioneer.py for configuration instructions, edit setup.cfg, and re-run the installer or 'python versioneer.py setup'. """ SAMPLE_CONFIG = """ # See the docstring in versioneer.py for instructions. Note that you must # re-run 'versioneer.py setup' after changing this section, and commit the # resulting files. [versioneer] #VCS = git #style = pep440 #versionfile_source = #versionfile_build = #tag_prefix = #parentdir_prefix = """ INIT_PY_SNIPPET = """ from ._version import get_versions __version__ = get_versions()['version'] del get_versions """ def do_setup(): """Main VCS-independent setup function for installing Versioneer.""" root = get_root() try: cfg = get_config_from_root(root) except (EnvironmentError, configparser.NoSectionError, configparser.NoOptionError) as e: if isinstance(e, (EnvironmentError, configparser.NoSectionError)): print("Adding sample versioneer config to setup.cfg", file=sys.stderr) with open(os.path.join(root, "setup.cfg"), "a") as f: f.write(SAMPLE_CONFIG) print(CONFIG_ERROR, file=sys.stderr) return 1 print(" creating %s" % cfg.versionfile_source) with open(cfg.versionfile_source, "w") as f: LONG = LONG_VERSION_PY[cfg.VCS] f.write(LONG % {"DOLLAR": "$", "STYLE": cfg.style, "TAG_PREFIX": cfg.tag_prefix, "PARENTDIR_PREFIX": cfg.parentdir_prefix, "VERSIONFILE_SOURCE": cfg.versionfile_source, }) ipy = os.path.join(os.path.dirname(cfg.versionfile_source), "__init__.py") if os.path.exists(ipy): try: with open(ipy, "r") as f: old = f.read() except EnvironmentError: old = "" if INIT_PY_SNIPPET not in old: print(" appending to %s" % ipy) with open(ipy, "a") as f: f.write(INIT_PY_SNIPPET) else: print(" %s unmodified" % ipy) else: print(" %s doesn't exist, ok" % ipy) ipy = None # Make sure both the top-level "versioneer.py" and versionfile_source # (PKG/_version.py, used by runtime code) are in MANIFEST.in, so # they'll be copied into source distributions. Pip won't be able to # install the package without this. manifest_in = os.path.join(root, "MANIFEST.in") simple_includes = set() try: with open(manifest_in, "r") as f: for line in f: if line.startswith("include "): for include in line.split()[1:]: simple_includes.add(include) except EnvironmentError: pass # That doesn't cover everything MANIFEST.in can do # (http://docs.python.org/2/distutils/sourcedist.html#commands), so # it might give some false negatives. Appending redundant 'include' # lines is safe, though. if "versioneer.py" not in simple_includes: print(" appending 'versioneer.py' to MANIFEST.in") with open(manifest_in, "a") as f: f.write("include versioneer.py\n") else: print(" 'versioneer.py' already in MANIFEST.in") if cfg.versionfile_source not in simple_includes: print(" appending versionfile_source ('%s') to MANIFEST.in" % cfg.versionfile_source) with open(manifest_in, "a") as f: f.write("include %s\n" % cfg.versionfile_source) else: print(" versionfile_source already in MANIFEST.in") # Make VCS-specific changes. For git, this means creating/changing # .gitattributes to mark _version.py for export-subst keyword # substitution. do_vcs_install(manifest_in, cfg.versionfile_source, ipy) return 0 def scan_setup_py(): """Validate the contents of setup.py against Versioneer's expectations.""" found = set() setters = False errors = 0 with open("setup.py", "r") as f: for line in f.readlines(): if "import versioneer" in line: found.add("import") if "versioneer.get_cmdclass()" in line: found.add("cmdclass") if "versioneer.get_version()" in line: found.add("get_version") if "versioneer.VCS" in line: setters = True if "versioneer.versionfile_source" in line: setters = True if len(found) != 3: print("") print("Your setup.py appears to be missing some important items") print("(but I might be wrong). Please make sure it has something") print("roughly like the following:") print("") print(" import versioneer") print(" setup( version=versioneer.get_version(),") print(" cmdclass=versioneer.get_cmdclass(), ...)") print("") errors += 1 if setters: print("You should remove lines like 'versioneer.VCS = ' and") print("'versioneer.versionfile_source = ' . This configuration") print("now lives in setup.cfg, and should be removed from setup.py") print("") errors += 1 return errors if __name__ == "__main__": cmd = sys.argv[1] if cmd == "setup": errors = do_setup() errors += scan_setup_py() if errors: sys.exit(1)