Fixed database typo and removed unnecessary class identifier.

This commit is contained in:
Batuhan Berk Başoğlu 2020-10-14 10:10:37 -04:00
parent 00ad49a143
commit 45fb349a7d
5098 changed files with 952558 additions and 85 deletions

View file

@ -0,0 +1,115 @@
"""
==============================================
Discrete Fourier transforms (:mod:`scipy.fft`)
==============================================
.. currentmodule:: scipy.fft
Fast Fourier Transforms (FFTs)
==============================
.. autosummary::
:toctree: generated/
fft - Fast (discrete) Fourier Transform (FFT)
ifft - Inverse FFT
fft2 - 2-D FFT
ifft2 - 2-D inverse FFT
fftn - N-D FFT
ifftn - N-D inverse FFT
rfft - FFT of strictly real-valued sequence
irfft - Inverse of rfft
rfft2 - 2-D FFT of real sequence
irfft2 - Inverse of rfft2
rfftn - N-D FFT of real sequence
irfftn - Inverse of rfftn
hfft - FFT of a Hermitian sequence (real spectrum)
ihfft - Inverse of hfft
hfft2 - 2-D FFT of a Hermitian sequence
ihfft2 - Inverse of hfft2
hfftn - N-D FFT of a Hermitian sequence
ihfftn - Inverse of hfftn
Discrete Sin and Cosine Transforms (DST and DCT)
================================================
.. autosummary::
:toctree: generated/
dct - Discrete cosine transform
idct - Inverse discrete cosine transform
dctn - N-D Discrete cosine transform
idctn - N-D Inverse discrete cosine transform
dst - Discrete sine transform
idst - Inverse discrete sine transform
dstn - N-D Discrete sine transform
idstn - N-D Inverse discrete sine transform
Helper functions
================
.. autosummary::
:toctree: generated/
fftshift - Shift the zero-frequency component to the center of the spectrum
ifftshift - The inverse of `fftshift`
fftfreq - Return the Discrete Fourier Transform sample frequencies
rfftfreq - DFT sample frequencies (for usage with rfft, irfft)
next_fast_len - Find the optimal length to zero-pad an FFT for speed
set_workers - Context manager to set default number of workers
get_workers - Get the current default number of workers
Backend control
===============
.. autosummary::
:toctree: generated/
set_backend - Context manager to set the backend within a fixed scope
skip_backend - Context manager to skip a backend within a fixed scope
set_global_backend - Sets the global fft backend
register_backend - Register a backend for permanent use
"""
from ._basic import (
fft, ifft, fft2, ifft2, fftn, ifftn,
rfft, irfft, rfft2, irfft2, rfftn, irfftn,
hfft, ihfft, hfft2, ihfft2, hfftn, ihfftn)
from ._realtransforms import dct, idct, dst, idst, dctn, idctn, dstn, idstn
from ._helper import next_fast_len
from ._backend import (set_backend, skip_backend, set_global_backend,
register_backend)
from numpy.fft import fftfreq, rfftfreq, fftshift, ifftshift
from ._pocketfft.helper import set_workers, get_workers
__all__ = [
'fft', 'ifft', 'fft2','ifft2', 'fftn', 'ifftn',
'rfft', 'irfft', 'rfft2', 'irfft2', 'rfftn', 'irfftn',
'hfft', 'ihfft', 'hfft2', 'ihfft2', 'hfftn', 'ihfftn',
'fftfreq', 'rfftfreq', 'fftshift', 'ifftshift',
'next_fast_len',
'dct', 'idct', 'dst', 'idst', 'dctn', 'idctn', 'dstn', 'idstn',
'set_backend', 'skip_backend', 'set_global_backend', 'register_backend',
'get_workers', 'set_workers']
from scipy._lib._testutils import PytestTester
test = PytestTester(__name__)
del PytestTester
# Hack to allow numpy.fft.fft to be called as scipy.fft
import sys
class _FFTModule(sys.modules[__name__].__class__):
@staticmethod
def __call__(*args, **kwargs):
from scipy import _dep_fft
return _dep_fft(*args, **kwargs)
import os
if os.environ.get('_SCIPY_BUILDING_DOC') != 'True':
sys.modules[__name__].__class__ = _FFTModule
del os
del _FFTModule
del sys

View file

@ -0,0 +1,180 @@
import scipy._lib.uarray as ua
from . import _pocketfft
class _ScipyBackend:
"""The default backend for fft calculations
Notes
-----
We use the domain ``numpy.scipy`` rather than ``scipy`` because in the
future, ``uarray`` will treat the domain as a hierarchy. This means the user
can install a single backend for ``numpy`` and have it implement
``numpy.scipy.fft`` as well.
"""
__ua_domain__ = "numpy.scipy.fft"
@staticmethod
def __ua_function__(method, args, kwargs):
fn = getattr(_pocketfft, method.__name__, None)
if fn is None:
return NotImplemented
return fn(*args, **kwargs)
_named_backends = {
'scipy': _ScipyBackend,
}
def _backend_from_arg(backend):
"""Maps strings to known backends and validates the backend"""
if isinstance(backend, str):
try:
backend = _named_backends[backend]
except KeyError:
raise ValueError('Unknown backend {}'.format(backend))
if backend.__ua_domain__ != 'numpy.scipy.fft':
raise ValueError('Backend does not implement "numpy.scipy.fft"')
return backend
def set_global_backend(backend):
"""Sets the global fft backend
The global backend has higher priority than registered backends, but lower
priority than context-specific backends set with `set_backend`.
Parameters
----------
backend: {object, 'scipy'}
The backend to use.
Can either be a ``str`` containing the name of a known backend
{'scipy'} or an object that implements the uarray protocol.
Raises
------
ValueError: If the backend does not implement ``numpy.scipy.fft``.
Notes
-----
This will overwrite the previously set global backend, which, by default, is
the SciPy implementation.
Examples
--------
We can set the global fft backend:
>>> from scipy.fft import fft, set_global_backend
>>> set_global_backend("scipy") # Sets global backend. "scipy" is the default backend.
>>> fft([1]) # Calls the global backend
array([1.+0.j])
"""
backend = _backend_from_arg(backend)
ua.set_global_backend(backend)
def register_backend(backend):
"""
Register a backend for permanent use.
Registered backends have the lowest priority and will be tried after the
global backend.
Parameters
----------
backend: {object, 'scipy'}
The backend to use.
Can either be a ``str`` containing the name of a known backend
{'scipy'} or an object that implements the uarray protocol.
Raises
------
ValueError: If the backend does not implement ``numpy.scipy.fft``.
Examples
--------
We can register a new fft backend:
>>> from scipy.fft import fft, register_backend, set_global_backend
>>> class NoopBackend: # Define an invalid Backend
... __ua_domain__ = "numpy.scipy.fft"
... def __ua_function__(self, func, args, kwargs):
... return NotImplemented
>>> set_global_backend(NoopBackend()) # Set the invalid backend as global
>>> register_backend("scipy") # Register a new backend
>>> fft([1]) # The registered backend is called because the global backend returns `NotImplemented`
array([1.+0.j])
>>> set_global_backend("scipy") # Restore global backend to default
"""
backend = _backend_from_arg(backend)
ua.register_backend(backend)
def set_backend(backend, coerce=False, only=False):
"""Context manager to set the backend within a fixed scope.
Upon entering the ``with`` statement, the given backend will be added to
the list of available backends with the highest priority. Upon exit, the
backend is reset to the state before entering the scope.
Parameters
----------
backend: {object, 'scipy'}
The backend to use.
Can either be a ``str`` containing the name of a known backend
{'scipy'} or an object that implements the uarray protocol.
coerce: bool, optional
Whether to allow expensive conversions for the ``x`` parameter. e.g.,
copying a NumPy array to the GPU for a CuPy backend. Implies ``only``.
only: bool, optional
If only is ``True`` and this backend returns ``NotImplemented``, then a
BackendNotImplemented error will be raised immediately. Ignoring any
lower priority backends.
Examples
--------
>>> import scipy.fft as fft
>>> with fft.set_backend('scipy', only=True):
... fft.fft([1]) # Always calls the scipy implementation
array([1.+0.j])
"""
backend = _backend_from_arg(backend)
return ua.set_backend(backend, coerce=coerce, only=only)
def skip_backend(backend):
"""Context manager to skip a backend within a fixed scope.
Within the context of a ``with`` statement, the given backend will not be
called. This covers backends registered both locally and globally. Upon
exit, the backend will again be considered.
Parameters
----------
backend: {object, 'scipy'}
The backend to skip.
Can either be a ``str`` containing the name of a known backend
{'scipy'} or an object that implements the uarray protocol.
Examples
--------
>>> import scipy.fft as fft
>>> fft.fft([1]) # Calls default SciPy backend
array([1.+0.j])
>>> with fft.skip_backend('scipy'): # We explicitly skip the SciPy backend
... fft.fft([1]) # leaving no implementation available
Traceback (most recent call last):
...
BackendNotImplementedError: No selected backends had an implementation ...
"""
backend = _backend_from_arg(backend)
return ua.skip_backend(backend)
set_global_backend('scipy')

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,22 @@
import numpy as np
class NumPyBackend:
"""Backend that uses numpy.fft"""
__ua_domain__ = "numpy.scipy.fft"
@staticmethod
def __ua_function__(method, args, kwargs):
kwargs.pop("overwrite_x", None)
fn = getattr(np.fft, method.__name__, None)
return (NotImplemented if fn is None
else fn(*args, **kwargs))
class EchoBackend:
"""Backend that just prints the __ua_function__ arguments"""
__ua_domain__ = "numpy.scipy.fft"
@staticmethod
def __ua_function__(method, args, kwargs):
print(method, args, kwargs, sep='\n')

View file

@ -0,0 +1,99 @@
from functools import update_wrapper, lru_cache
from ._pocketfft import helper as _helper
def next_fast_len(target, real=False):
"""Find the next fast size of input data to ``fft``, for zero-padding, etc.
SciPy's FFT algorithms gain their speed by a recursive divide and conquer
strategy. This relies on efficient functions for small prime factors of the
input length. Thus, the transforms are fastest when using composites of the
prime factors handled by the fft implementation. If there are efficient
functions for all radices <= `n`, then the result will be a number `x`
>= ``target`` with only prime factors < `n`. (Also known as `n`-smooth
numbers)
Parameters
----------
target : int
Length to start searching from. Must be a positive integer.
real : bool, optional
True if the FFT involves real input or output (e.g., `rfft` or `hfft` but
not `fft`). Defaults to False.
Returns
-------
out : int
The smallest fast length greater than or equal to ``target``.
Notes
-----
The result of this function may change in future as performance
considerations change, for example, if new prime factors are added.
Calling `fft` or `ifft` with real input data performs an ``'R2C'``
transform internally.
Examples
--------
On a particular machine, an FFT of prime length takes 17 ms:
>>> from scipy import fft
>>> min_len = 93059 # prime length is worst case for speed
>>> a = np.random.randn(min_len)
>>> b = fft.fft(a)
Zero-padding to the next regular length reduces computation time to
1.3 ms, a speedup of 13 times:
>>> fft.next_fast_len(min_len)
93312
>>> b = fft.fft(a, 93312)
Rounding up to the next power of 2 is not optimal, taking 1.9 ms to
compute; 1.3 times longer than the size given by ``next_fast_len``:
>>> b = fft.fft(a, 131072)
"""
pass
# Directly wrap the c-function good_size but take the docstring etc., from the
# next_fast_len function above
next_fast_len = update_wrapper(lru_cache()(_helper.good_size), next_fast_len)
next_fast_len.__wrapped__ = _helper.good_size
def _init_nd_shape_and_axes(x, shape, axes):
"""Handle shape and axes arguments for N-D transforms.
Returns the shape and axes in a standard form, taking into account negative
values and checking for various potential errors.
Parameters
----------
x : array_like
The input array.
shape : int or array_like of ints or None
The shape of the result. If both `shape` and `axes` (see below) are
None, `shape` is ``x.shape``; if `shape` is None but `axes` is
not None, then `shape` is ``scipy.take(x.shape, axes, axis=0)``.
If `shape` is -1, the size of the corresponding dimension of `x` is
used.
axes : int or array_like of ints or None
Axes along which the calculation is computed.
The default is over all axes.
Negative indices are automatically converted to their positive
counterparts.
Returns
-------
shape : array
The shape of the result. It is a 1-D integer array.
axes : array
The shape of the result. It is a 1-D integer array.
"""
return _helper._init_nd_shape_and_axes(x, shape, axes)

View file

@ -0,0 +1,25 @@
Copyright (C) 2010-2019 Max-Planck-Society
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.

View file

@ -0,0 +1,9 @@
""" FFT backend using pypocketfft """
from .basic import *
from .realtransforms import *
from .helper import *
from scipy._lib._testutils import PytestTester
test = PytestTester(__name__)
del PytestTester

View file

@ -0,0 +1,297 @@
"""
Discrete Fourier Transforms - basic.py
"""
import numpy as np
import functools
from . import pypocketfft as pfft
from .helper import (_asfarray, _init_nd_shape_and_axes, _datacopied,
_fix_shape, _fix_shape_1d, _normalization,
_workers)
def c2c(forward, x, n=None, axis=-1, norm=None, overwrite_x=False,
workers=None, *, plan=None):
""" Return discrete Fourier transform of real or complex sequence. """
if plan is not None:
raise NotImplementedError('Passing a precomputed plan is not yet '
'supported by scipy.fft functions')
tmp = _asfarray(x)
overwrite_x = overwrite_x or _datacopied(tmp, x)
norm = _normalization(norm, forward)
workers = _workers(workers)
if n is not None:
tmp, copied = _fix_shape_1d(tmp, n, axis)
overwrite_x = overwrite_x or copied
elif tmp.shape[axis] < 1:
raise ValueError("invalid number of data points ({0}) specified"
.format(tmp.shape[axis]))
out = (tmp if overwrite_x and tmp.dtype.kind == 'c' else None)
return pfft.c2c(tmp, (axis,), forward, norm, out, workers)
fft = functools.partial(c2c, True)
fft.__name__ = 'fft'
ifft = functools.partial(c2c, False)
ifft.__name__ = 'ifft'
def r2c(forward, x, n=None, axis=-1, norm=None, overwrite_x=False,
workers=None, *, plan=None):
"""
Discrete Fourier transform of a real sequence.
"""
if plan is not None:
raise NotImplementedError('Passing a precomputed plan is not yet '
'supported by scipy.fft functions')
tmp = _asfarray(x)
norm = _normalization(norm, forward)
workers = _workers(workers)
if not np.isrealobj(tmp):
raise TypeError("x must be a real sequence")
if n is not None:
tmp, _ = _fix_shape_1d(tmp, n, axis)
elif tmp.shape[axis] < 1:
raise ValueError("invalid number of data points ({0}) specified"
.format(tmp.shape[axis]))
# Note: overwrite_x is not utilised
return pfft.r2c(tmp, (axis,), forward, norm, None, workers)
rfft = functools.partial(r2c, True)
rfft.__name__ = 'rfft'
ihfft = functools.partial(r2c, False)
ihfft.__name__ = 'ihfft'
def c2r(forward, x, n=None, axis=-1, norm=None, overwrite_x=False,
workers=None, *, plan=None):
"""
Return inverse discrete Fourier transform of real sequence x.
"""
if plan is not None:
raise NotImplementedError('Passing a precomputed plan is not yet '
'supported by scipy.fft functions')
tmp = _asfarray(x)
norm = _normalization(norm, forward)
workers = _workers(workers)
# TODO: Optimize for hermitian and real?
if np.isrealobj(tmp):
tmp = tmp + 0.j
# Last axis utilizes hermitian symmetry
if n is None:
n = (tmp.shape[axis] - 1) * 2
if n < 1:
raise ValueError("Invalid number of data points ({0}) specified"
.format(n))
else:
tmp, _ = _fix_shape_1d(tmp, (n//2) + 1, axis)
# Note: overwrite_x is not utilized
return pfft.c2r(tmp, (axis,), n, forward, norm, None, workers)
hfft = functools.partial(c2r, True)
hfft.__name__ = 'hfft'
irfft = functools.partial(c2r, False)
irfft.__name__ = 'irfft'
def fft2(x, s=None, axes=(-2,-1), norm=None, overwrite_x=False, workers=None,
*, plan=None):
"""
2-D discrete Fourier transform.
"""
if plan is not None:
raise NotImplementedError('Passing a precomputed plan is not yet '
'supported by scipy.fft functions')
return fftn(x, s, axes, norm, overwrite_x, workers)
def ifft2(x, s=None, axes=(-2,-1), norm=None, overwrite_x=False, workers=None,
*, plan=None):
"""
2-D discrete inverse Fourier transform of real or complex sequence.
"""
if plan is not None:
raise NotImplementedError('Passing a precomputed plan is not yet '
'supported by scipy.fft functions')
return ifftn(x, s, axes, norm, overwrite_x, workers)
def rfft2(x, s=None, axes=(-2,-1), norm=None, overwrite_x=False, workers=None,
*, plan=None):
"""
2-D discrete Fourier transform of a real sequence
"""
if plan is not None:
raise NotImplementedError('Passing a precomputed plan is not yet '
'supported by scipy.fft functions')
return rfftn(x, s, axes, norm, overwrite_x, workers)
def irfft2(x, s=None, axes=(-2,-1), norm=None, overwrite_x=False, workers=None,
*, plan=None):
"""
2-D discrete inverse Fourier transform of a real sequence
"""
if plan is not None:
raise NotImplementedError('Passing a precomputed plan is not yet '
'supported by scipy.fft functions')
return irfftn(x, s, axes, norm, overwrite_x, workers)
def hfft2(x, s=None, axes=(-2,-1), norm=None, overwrite_x=False, workers=None,
*, plan=None):
"""
2-D discrete Fourier transform of a Hermitian sequence
"""
if plan is not None:
raise NotImplementedError('Passing a precomputed plan is not yet '
'supported by scipy.fft functions')
return hfftn(x, s, axes, norm, overwrite_x, workers)
def ihfft2(x, s=None, axes=(-2,-1), norm=None, overwrite_x=False, workers=None,
*, plan=None):
"""
2-D discrete inverse Fourier transform of a Hermitian sequence
"""
if plan is not None:
raise NotImplementedError('Passing a precomputed plan is not yet '
'supported by scipy.fft functions')
return ihfftn(x, s, axes, norm, overwrite_x, workers)
def c2cn(forward, x, s=None, axes=None, norm=None, overwrite_x=False,
workers=None, *, plan=None):
"""
Return multidimensional discrete Fourier transform.
"""
if plan is not None:
raise NotImplementedError('Passing a precomputed plan is not yet '
'supported by scipy.fft functions')
tmp = _asfarray(x)
shape, axes = _init_nd_shape_and_axes(tmp, s, axes)
overwrite_x = overwrite_x or _datacopied(tmp, x)
workers = _workers(workers)
if len(axes) == 0:
return x
tmp, copied = _fix_shape(tmp, shape, axes)
overwrite_x = overwrite_x or copied
norm = _normalization(norm, forward)
out = (tmp if overwrite_x and tmp.dtype.kind == 'c' else None)
return pfft.c2c(tmp, axes, forward, norm, out, workers)
fftn = functools.partial(c2cn, True)
fftn.__name__ = 'fftn'
ifftn = functools.partial(c2cn, False)
ifftn.__name__ = 'ifftn'
def r2cn(forward, x, s=None, axes=None, norm=None, overwrite_x=False,
workers=None, *, plan=None):
"""Return multidimensional discrete Fourier transform of real input"""
if plan is not None:
raise NotImplementedError('Passing a precomputed plan is not yet '
'supported by scipy.fft functions')
tmp = _asfarray(x)
if not np.isrealobj(tmp):
raise TypeError("x must be a real sequence")
shape, axes = _init_nd_shape_and_axes(tmp, s, axes)
tmp, _ = _fix_shape(tmp, shape, axes)
norm = _normalization(norm, forward)
workers = _workers(workers)
if len(axes) == 0:
raise ValueError("at least 1 axis must be transformed")
# Note: overwrite_x is not utilized
return pfft.r2c(tmp, axes, forward, norm, None, workers)
rfftn = functools.partial(r2cn, True)
rfftn.__name__ = 'rfftn'
ihfftn = functools.partial(r2cn, False)
ihfftn.__name__ = 'ihfftn'
def c2rn(forward, x, s=None, axes=None, norm=None, overwrite_x=False,
workers=None, *, plan=None):
"""Multidimensional inverse discrete fourier transform with real output"""
if plan is not None:
raise NotImplementedError('Passing a precomputed plan is not yet '
'supported by scipy.fft functions')
tmp = _asfarray(x)
# TODO: Optimize for hermitian and real?
if np.isrealobj(tmp):
tmp = tmp + 0.j
noshape = s is None
shape, axes = _init_nd_shape_and_axes(tmp, s, axes)
if len(axes) == 0:
raise ValueError("at least 1 axis must be transformed")
if noshape:
shape[-1] = (x.shape[axes[-1]] - 1) * 2
norm = _normalization(norm, forward)
workers = _workers(workers)
# Last axis utilizes hermitian symmetry
lastsize = shape[-1]
shape[-1] = (shape[-1] // 2) + 1
tmp, _ = _fix_shape(tmp, shape, axes)
# Note: overwrite_x is not utilized
return pfft.c2r(tmp, axes, lastsize, forward, norm, None, workers)
hfftn = functools.partial(c2rn, True)
hfftn.__name__ = 'hfftn'
irfftn = functools.partial(c2rn, False)
irfftn.__name__ = 'irfftn'
def r2r_fftpack(forward, x, n=None, axis=-1, norm=None, overwrite_x=False):
"""FFT of a real sequence, returning fftpack half complex format"""
tmp = _asfarray(x)
overwrite_x = overwrite_x or _datacopied(tmp, x)
norm = _normalization(norm, forward)
workers = _workers(None)
if tmp.dtype.kind == 'c':
raise TypeError('x must be a real sequence')
if n is not None:
tmp, copied = _fix_shape_1d(tmp, n, axis)
overwrite_x = overwrite_x or copied
elif tmp.shape[axis] < 1:
raise ValueError("invalid number of data points ({0}) specified"
.format(tmp.shape[axis]))
out = (tmp if overwrite_x else None)
return pfft.r2r_fftpack(tmp, (axis,), forward, forward, norm, out, workers)
rfft_fftpack = functools.partial(r2r_fftpack, True)
rfft_fftpack.__name__ = 'rfft_fftpack'
irfft_fftpack = functools.partial(r2r_fftpack, False)
irfft_fftpack.__name__ = 'irfft_fftpack'

View file

@ -0,0 +1,213 @@
from numbers import Number
import operator
import os
import threading
import contextlib
import numpy as np
# good_size is exposed (and used) from this import
from .pypocketfft import good_size
_config = threading.local()
_cpu_count = os.cpu_count()
def _iterable_of_int(x, name=None):
"""Convert ``x`` to an iterable sequence of int
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 _init_nd_shape_and_axes(x, shape, axes):
"""Handles shape and axes arguments for nd transforms"""
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("axes exceeds dimensionality of input")
if len(set(axes)) != len(axes):
raise ValueError("all axes must be unique")
if not noshape:
shape = _iterable_of_int(shape, 'shape')
if axes and len(axes) != len(shape):
raise ValueError("when given, axes and shape arguments"
" have to be of the same length")
if noaxes:
if len(shape) > x.ndim:
raise ValueError("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 _asfarray(x):
"""
Convert to array with floating or complex dtype.
float16 values are also promoted to float32.
"""
if not hasattr(x, "dtype"):
x = np.asarray(x)
if x.dtype == np.float16:
return np.asarray(x, np.float32)
elif x.dtype.kind not in 'fc':
return np.asarray(x, np.float64)
# Require native byte order
dtype = x.dtype.newbyteorder('=')
# Always align input
copy = not x.flags['ALIGNED']
return np.array(x, dtype=dtype, copy=copy)
def _datacopied(arr, original):
"""
Strict check for `arr` not sharing any data with `original`,
under the assumption that arr = asarray(original)
"""
if arr is original:
return False
if not isinstance(original, np.ndarray) and hasattr(original, '__array__'):
return False
return arr.base is None
def _fix_shape(x, shape, axes):
"""Internal auxiliary function for _raw_fft, _raw_fftnd."""
must_copy = False
# Build an nd slice with the dimensions to be read from x
index = [slice(None)]*x.ndim
for n, ax in zip(shape, axes):
if x.shape[ax] >= n:
index[ax] = slice(0, n)
else:
index[ax] = slice(0, x.shape[ax])
must_copy = True
index = tuple(index)
if not must_copy:
return x[index], False
s = list(x.shape)
for n, axis in zip(shape, axes):
s[axis] = n
z = np.zeros(s, x.dtype)
z[index] = x[index]
return z, True
def _fix_shape_1d(x, n, axis):
if n < 1:
raise ValueError(
"invalid number of data points ({0}) specified".format(n))
return _fix_shape(x, (n,), (axis,))
def _normalization(norm, forward):
"""Returns the pypocketfft normalization mode from the norm argument"""
if norm is None:
return 0 if forward else 2
if norm == 'ortho':
return 1
raise ValueError(
"Invalid norm value {}, should be None or \"ortho\".".format(norm))
def _workers(workers):
if workers is None:
return getattr(_config, 'default_workers', 1)
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
@contextlib.contextmanager
def set_workers(workers):
"""Context manager for the default number of workers used in `scipy.fft`
Parameters
----------
workers : int
The default number of workers to use
Examples
--------
>>> from scipy import fft, signal
>>> x = np.random.randn(128, 64)
>>> with fft.set_workers(4):
... y = signal.fftconvolve(x, x)
"""
old_workers = get_workers()
_config.default_workers = _workers(operator.index(workers))
try:
yield
finally:
_config.default_workers = old_workers
def get_workers():
"""Returns the default number of workers within the current context
Examples
--------
>>> from scipy import fft
>>> fft.get_workers()
1
>>> with fft.set_workers(4):
... fft.get_workers()
4
"""
return getattr(_config, 'default_workers', 1)

View file

@ -0,0 +1,110 @@
import numpy as np
from . import pypocketfft as pfft
from .helper import (_asfarray, _init_nd_shape_and_axes, _datacopied,
_fix_shape, _fix_shape_1d, _normalization, _workers)
import functools
def _r2r(forward, transform, x, type=2, n=None, axis=-1, norm=None,
overwrite_x=False, workers=None):
"""Forward or backward 1-D DCT/DST
Parameters
----------
forward: bool
Transform direction (determines type and normalisation)
transform: {pypocketfft.dct, pypocketfft.dst}
The transform to perform
"""
tmp = _asfarray(x)
overwrite_x = overwrite_x or _datacopied(tmp, x)
norm = _normalization(norm, forward)
workers = _workers(workers)
if not forward:
if type == 2:
type = 3
elif type == 3:
type = 2
if n is not None:
tmp, copied = _fix_shape_1d(tmp, n, axis)
overwrite_x = overwrite_x or copied
elif tmp.shape[axis] < 1:
raise ValueError("invalid number of data points ({0}) specified"
.format(tmp.shape[axis]))
out = (tmp if overwrite_x else None)
# For complex input, transform real and imaginary components separably
if np.iscomplexobj(x):
out = np.empty_like(tmp) if out is None else out
transform(tmp.real, type, (axis,), norm, out.real, workers)
transform(tmp.imag, type, (axis,), norm, out.imag, workers)
return out
return transform(tmp, type, (axis,), norm, out, workers)
dct = functools.partial(_r2r, True, pfft.dct)
dct.__name__ = 'dct'
idct = functools.partial(_r2r, False, pfft.dct)
idct.__name__ = 'idct'
dst = functools.partial(_r2r, True, pfft.dst)
dst.__name__ = 'dst'
idst = functools.partial(_r2r, False, pfft.dst)
idst.__name__ = 'idst'
def _r2rn(forward, transform, x, type=2, s=None, axes=None, norm=None,
overwrite_x=False, workers=None):
"""Forward or backward nd DCT/DST
Parameters
----------
forward: bool
Transform direction (determines type and normalisation)
transform: {pypocketfft.dct, pypocketfft.dst}
The transform to perform
"""
tmp = _asfarray(x)
shape, axes = _init_nd_shape_and_axes(tmp, s, axes)
overwrite_x = overwrite_x or _datacopied(tmp, x)
if len(axes) == 0:
return x
tmp, copied = _fix_shape(tmp, shape, axes)
overwrite_x = overwrite_x or copied
if not forward:
if type == 2:
type = 3
elif type == 3:
type = 2
norm = _normalization(norm, forward)
workers = _workers(workers)
out = (tmp if overwrite_x else None)
# For complex input, transform real and imaginary components separably
if np.iscomplexobj(x):
out = np.empty_like(tmp) if out is None else out
transform(tmp.real, type, axes, norm, out.real, workers)
transform(tmp.imag, type, axes, norm, out.imag, workers)
return out
return transform(tmp, type, axes, norm, out, workers)
dctn = functools.partial(_r2rn, True, pfft.dct)
dctn.__name__ = 'dctn'
idctn = functools.partial(_r2rn, False, pfft.dct)
idctn.__name__ = 'idctn'
dstn = functools.partial(_r2rn, True, pfft.dst)
dstn.__name__ = 'dstn'
idstn = functools.partial(_r2rn, False, pfft.dst)
idstn.__name__ = 'idstn'

View file

@ -0,0 +1,49 @@
def pre_build_hook(build_ext, ext):
from scipy._build_utils.compiler_helper import (
set_cxx_flags_hook, try_add_flag, try_compile, has_flag)
cc = build_ext._cxx_compiler
args = ext.extra_compile_args
set_cxx_flags_hook(build_ext, ext)
if cc.compiler_type == 'msvc':
args.append('/EHsc')
else:
# Use pthreads if available
has_pthreads = try_compile(cc, code='#include <pthread.h>\n'
'int main(int argc, char **argv) {}')
if has_pthreads:
ext.define_macros.append(('POCKETFFT_PTHREADS', None))
if has_flag(cc, '-pthread'):
args.append('-pthread')
ext.extra_link_args.append('-pthread')
else:
raise RuntimeError("Build failed: System has pthreads header "
"but could not compile with -pthread option")
# Don't export library symbols
try_add_flag(args, cc, '-fvisibility=hidden')
def configuration(parent_package='', top_path=None):
from numpy.distutils.misc_util import Configuration
import pybind11
include_dirs = [pybind11.get_include(True), pybind11.get_include(False)]
config = Configuration('_pocketfft', parent_package, top_path)
ext = config.add_extension('pypocketfft',
sources=['pypocketfft.cxx'],
depends=['pocketfft_hdronly.h'],
include_dirs=include_dirs,
language='c++')
ext._pre_build_hook = pre_build_hook
config.add_data_files('LICENSE.md')
config.add_data_dir('tests')
return config
if __name__ == '__main__':
from numpy.distutils.core import setup
setup(**configuration(top_path='').todict())

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,487 @@
from os.path import join, dirname
import numpy as np
from numpy.testing import (
assert_array_almost_equal, assert_equal, assert_allclose)
import pytest
from pytest import raises as assert_raises
from scipy.fft._pocketfft.realtransforms import (
dct, idct, dst, idst, dctn, idctn, dstn, idstn)
fftpack_test_dir = join(dirname(__file__), '..', '..', '..', 'fftpack', 'tests')
MDATA_COUNT = 8
FFTWDATA_COUNT = 14
def is_longdouble_binary_compatible():
try:
one = np.frombuffer(
b'\x00\x00\x00\x00\x00\x00\x00\x80\xff\x3f\x00\x00\x00\x00\x00\x00',
dtype='<f16')
return one == np.longfloat(1.)
except TypeError:
return False
def get_reference_data():
ref = getattr(globals(), '__reference_data', None)
if ref is not None:
return ref
# Matlab reference data
MDATA = np.load(join(fftpack_test_dir, 'test.npz'))
X = [MDATA['x%d' % i] for i in range(MDATA_COUNT)]
Y = [MDATA['y%d' % i] for i in range(MDATA_COUNT)]
# FFTW reference data: the data are organized as follows:
# * SIZES is an array containing all available sizes
# * for every type (1, 2, 3, 4) and every size, the array dct_type_size
# contains the output of the DCT applied to the input np.linspace(0, size-1,
# size)
FFTWDATA_DOUBLE = np.load(join(fftpack_test_dir, 'fftw_double_ref.npz'))
FFTWDATA_SINGLE = np.load(join(fftpack_test_dir, 'fftw_single_ref.npz'))
FFTWDATA_SIZES = FFTWDATA_DOUBLE['sizes']
assert len(FFTWDATA_SIZES) == FFTWDATA_COUNT
if is_longdouble_binary_compatible():
FFTWDATA_LONGDOUBLE = np.load(
join(fftpack_test_dir, 'fftw_longdouble_ref.npz'))
else:
FFTWDATA_LONGDOUBLE = {k: v.astype(np.longfloat)
for k,v in FFTWDATA_DOUBLE.items()}
ref = {
'FFTWDATA_LONGDOUBLE': FFTWDATA_LONGDOUBLE,
'FFTWDATA_DOUBLE': FFTWDATA_DOUBLE,
'FFTWDATA_SINGLE': FFTWDATA_SINGLE,
'FFTWDATA_SIZES': FFTWDATA_SIZES,
'X': X,
'Y': Y
}
globals()['__reference_data'] = ref
return ref
@pytest.fixture(params=range(FFTWDATA_COUNT))
def fftwdata_size(request):
return get_reference_data()['FFTWDATA_SIZES'][request.param]
@pytest.fixture(params=range(MDATA_COUNT))
def mdata_x(request):
return get_reference_data()['X'][request.param]
@pytest.fixture(params=range(MDATA_COUNT))
def mdata_xy(request):
ref = get_reference_data()
y = ref['Y'][request.param]
x = ref['X'][request.param]
return x, y
def fftw_dct_ref(type, size, dt):
x = np.linspace(0, size-1, size).astype(dt)
dt = np.result_type(np.float32, dt)
if dt == np.double:
data = get_reference_data()['FFTWDATA_DOUBLE']
elif dt == np.float32:
data = get_reference_data()['FFTWDATA_SINGLE']
elif dt == np.longfloat:
data = get_reference_data()['FFTWDATA_LONGDOUBLE']
else:
raise ValueError()
y = (data['dct_%d_%d' % (type, size)]).astype(dt)
return x, y, dt
def fftw_dst_ref(type, size, dt):
x = np.linspace(0, size-1, size).astype(dt)
dt = np.result_type(np.float32, dt)
if dt == np.double:
data = get_reference_data()['FFTWDATA_DOUBLE']
elif dt == np.float32:
data = get_reference_data()['FFTWDATA_SINGLE']
elif dt == np.longfloat:
data = get_reference_data()['FFTWDATA_LONGDOUBLE']
else:
raise ValueError()
y = (data['dst_%d_%d' % (type, size)]).astype(dt)
return x, y, dt
def ref_2d(func, x, **kwargs):
"""Calculate 2-D reference data from a 1d transform"""
x = np.array(x, copy=True)
for row in range(x.shape[0]):
x[row, :] = func(x[row, :], **kwargs)
for col in range(x.shape[1]):
x[:, col] = func(x[:, col], **kwargs)
return x
def naive_dct1(x, norm=None):
"""Calculate textbook definition version of DCT-I."""
x = np.array(x, copy=True)
N = len(x)
M = N-1
y = np.zeros(N)
m0, m = 1, 2
if norm == 'ortho':
m0 = np.sqrt(1.0/M)
m = np.sqrt(2.0/M)
for k in range(N):
for n in range(1, N-1):
y[k] += m*x[n]*np.cos(np.pi*n*k/M)
y[k] += m0 * x[0]
y[k] += m0 * x[N-1] * (1 if k % 2 == 0 else -1)
if norm == 'ortho':
y[0] *= 1/np.sqrt(2)
y[N-1] *= 1/np.sqrt(2)
return y
def naive_dst1(x, norm=None):
"""Calculate textbook definition version of DST-I."""
x = np.array(x, copy=True)
N = len(x)
M = N+1
y = np.zeros(N)
for k in range(N):
for n in range(N):
y[k] += 2*x[n]*np.sin(np.pi*(n+1.0)*(k+1.0)/M)
if norm == 'ortho':
y *= np.sqrt(0.5/M)
return y
def naive_dct4(x, norm=None):
"""Calculate textbook definition version of DCT-IV."""
x = np.array(x, copy=True)
N = len(x)
y = np.zeros(N)
for k in range(N):
for n in range(N):
y[k] += x[n]*np.cos(np.pi*(n+0.5)*(k+0.5)/(N))
if norm == 'ortho':
y *= np.sqrt(2.0/N)
else:
y *= 2
return y
def naive_dst4(x, norm=None):
"""Calculate textbook definition version of DST-IV."""
x = np.array(x, copy=True)
N = len(x)
y = np.zeros(N)
for k in range(N):
for n in range(N):
y[k] += x[n]*np.sin(np.pi*(n+0.5)*(k+0.5)/(N))
if norm == 'ortho':
y *= np.sqrt(2.0/N)
else:
y *= 2
return y
@pytest.mark.parametrize('dtype', [np.complex64, np.complex128, np.longcomplex])
@pytest.mark.parametrize('transform', [dct, dst, idct, idst])
def test_complex(transform, dtype):
y = transform(1j*np.arange(5, dtype=dtype))
x = 1j*transform(np.arange(5))
assert_array_almost_equal(x, y)
# map (tranform, dtype, type) -> decimal
dec_map = {
# DCT
(dct, np.double, 1): 13,
(dct, np.float32, 1): 6,
(dct, np.double, 2): 14,
(dct, np.float32, 2): 5,
(dct, np.double, 3): 14,
(dct, np.float32, 3): 5,
(dct, np.double, 4): 13,
(dct, np.float32, 4): 6,
# IDCT
(idct, np.double, 1): 14,
(idct, np.float32, 1): 6,
(idct, np.double, 2): 14,
(idct, np.float32, 2): 5,
(idct, np.double, 3): 14,
(idct, np.float32, 3): 5,
(idct, np.double, 4): 14,
(idct, np.float32, 4): 6,
# DST
(dst, np.double, 1): 13,
(dst, np.float32, 1): 6,
(dst, np.double, 2): 14,
(dst, np.float32, 2): 6,
(dst, np.double, 3): 14,
(dst, np.float32, 3): 7,
(dst, np.double, 4): 13,
(dst, np.float32, 4): 6,
# IDST
(idst, np.double, 1): 14,
(idst, np.float32, 1): 6,
(idst, np.double, 2): 14,
(idst, np.float32, 2): 6,
(idst, np.double, 3): 14,
(idst, np.float32, 3): 6,
(idst, np.double, 4): 14,
(idst, np.float32, 4): 6,
}
for k,v in dec_map.copy().items():
if k[1] == np.double:
dec_map[(k[0], np.longdouble, k[2])] = v
elif k[1] == np.float32:
dec_map[(k[0], int, k[2])] = v
@pytest.mark.parametrize('rdt', [np.longfloat, np.double, np.float32, int])
@pytest.mark.parametrize('type', [1, 2, 3, 4])
class TestDCT:
def test_definition(self, rdt, type, fftwdata_size):
x, yr, dt = fftw_dct_ref(type, fftwdata_size, rdt)
y = dct(x, type=type)
assert_equal(y.dtype, dt)
dec = dec_map[(dct, rdt, type)]
assert_allclose(y, yr, rtol=0., atol=np.max(yr)*10**(-dec))
@pytest.mark.parametrize('size', [7, 8, 9, 16, 32, 64])
def test_axis(self, rdt, type, size):
nt = 2
dec = dec_map[(dct, rdt, type)]
x = np.random.randn(nt, size)
y = dct(x, type=type)
for j in range(nt):
assert_array_almost_equal(y[j], dct(x[j], type=type),
decimal=dec)
x = x.T
y = dct(x, axis=0, type=type)
for j in range(nt):
assert_array_almost_equal(y[:,j], dct(x[:,j], type=type),
decimal=dec)
@pytest.mark.parametrize('rdt', [np.longfloat, np.double, np.float32, int])
def test_dct1_definition_ortho(rdt, mdata_x):
# Test orthornomal mode.
dec = dec_map[(dct, rdt, 1)]
x = np.array(mdata_x, dtype=rdt)
dt = np.result_type(np.float32, rdt)
y = dct(x, norm='ortho', type=1)
y2 = naive_dct1(x, norm='ortho')
assert_equal(y.dtype, dt)
assert_allclose(y, y2, rtol=0., atol=np.max(y2)*10**(-dec))
@pytest.mark.parametrize('rdt', [np.longfloat, np.double, np.float32, int])
def test_dct2_definition_matlab(mdata_xy, rdt):
# Test correspondence with matlab (orthornomal mode).
dt = np.result_type(np.float32, rdt)
x = np.array(mdata_xy[0], dtype=dt)
yr = mdata_xy[1]
y = dct(x, norm="ortho", type=2)
dec = dec_map[(dct, rdt, 2)]
assert_equal(y.dtype, dt)
assert_array_almost_equal(y, yr, decimal=dec)
@pytest.mark.parametrize('rdt', [np.longfloat, np.double, np.float32, int])
def test_dct3_definition_ortho(mdata_x, rdt):
# Test orthornomal mode.
x = np.array(mdata_x, dtype=rdt)
dt = np.result_type(np.float32, rdt)
y = dct(x, norm='ortho', type=2)
xi = dct(y, norm="ortho", type=3)
dec = dec_map[(dct, rdt, 3)]
assert_equal(xi.dtype, dt)
assert_array_almost_equal(xi, x, decimal=dec)
@pytest.mark.parametrize('rdt', [np.longfloat, np.double, np.float32, int])
def test_dct4_definition_ortho(mdata_x, rdt):
# Test orthornomal mode.
x = np.array(mdata_x, dtype=rdt)
dt = np.result_type(np.float32, rdt)
y = dct(x, norm='ortho', type=4)
y2 = naive_dct4(x, norm='ortho')
dec = dec_map[(dct, rdt, 4)]
assert_equal(y.dtype, dt)
assert_allclose(y, y2, rtol=0., atol=np.max(y2)*10**(-dec))
@pytest.mark.parametrize('rdt', [np.longfloat, np.double, np.float32, int])
@pytest.mark.parametrize('type', [1, 2, 3, 4])
def test_idct_definition(fftwdata_size, rdt, type):
xr, yr, dt = fftw_dct_ref(type, fftwdata_size, rdt)
x = idct(yr, type=type)
dec = dec_map[(idct, rdt, type)]
assert_equal(x.dtype, dt)
assert_allclose(x, xr, rtol=0., atol=np.max(xr)*10**(-dec))
@pytest.mark.parametrize('rdt', [np.longfloat, np.double, np.float32, int])
@pytest.mark.parametrize('type', [1, 2, 3, 4])
def test_definition(fftwdata_size, rdt, type):
xr, yr, dt = fftw_dst_ref(type, fftwdata_size, rdt)
y = dst(xr, type=type)
dec = dec_map[(dst, rdt, type)]
assert_equal(y.dtype, dt)
assert_allclose(y, yr, rtol=0., atol=np.max(yr)*10**(-dec))
@pytest.mark.parametrize('rdt', [np.longfloat, np.double, np.float32, int])
def test_dst1_definition_ortho(rdt, mdata_x):
# Test orthornomal mode.
dec = dec_map[(dst, rdt, 1)]
x = np.array(mdata_x, dtype=rdt)
dt = np.result_type(np.float32, rdt)
y = dst(x, norm='ortho', type=1)
y2 = naive_dst1(x, norm='ortho')
assert_equal(y.dtype, dt)
assert_allclose(y, y2, rtol=0., atol=np.max(y2)*10**(-dec))
@pytest.mark.parametrize('rdt', [np.longfloat, np.double, np.float32, int])
def test_dst4_definition_ortho(rdt, mdata_x):
# Test orthornomal mode.
dec = dec_map[(dst, rdt, 4)]
x = np.array(mdata_x, dtype=rdt)
dt = np.result_type(np.float32, rdt)
y = dst(x, norm='ortho', type=4)
y2 = naive_dst4(x, norm='ortho')
assert_equal(y.dtype, dt)
assert_array_almost_equal(y, y2, decimal=dec)
@pytest.mark.parametrize('rdt', [np.longfloat, np.double, np.float32, int])
@pytest.mark.parametrize('type', [1, 2, 3, 4])
def test_idst_definition(fftwdata_size, rdt, type):
xr, yr, dt = fftw_dst_ref(type, fftwdata_size, rdt)
x = idst(yr, type=type)
dec = dec_map[(idst, rdt, type)]
assert_equal(x.dtype, dt)
assert_allclose(x, xr, rtol=0., atol=np.max(xr)*10**(-dec))
@pytest.mark.parametrize('routine', [dct, dst, idct, idst])
@pytest.mark.parametrize('dtype', [np.float32, np.float64, np.longfloat])
@pytest.mark.parametrize('shape, axis', [
((16,), -1), ((16, 2), 0), ((2, 16), 1)
])
@pytest.mark.parametrize('type', [1, 2, 3, 4])
@pytest.mark.parametrize('overwrite_x', [True, False])
@pytest.mark.parametrize('norm', [None, 'ortho'])
def test_overwrite(routine, dtype, shape, axis, type, norm, overwrite_x):
# Check input overwrite behavior
np.random.seed(1234)
if np.issubdtype(dtype, np.complexfloating):
x = np.random.randn(*shape) + 1j*np.random.randn(*shape)
else:
x = np.random.randn(*shape)
x = x.astype(dtype)
x2 = x.copy()
routine(x2, type, None, axis, norm, overwrite_x=overwrite_x)
sig = "%s(%s%r, %r, axis=%r, overwrite_x=%r)" % (
routine.__name__, x.dtype, x.shape, None, axis, overwrite_x)
if not overwrite_x:
assert_equal(x2, x, err_msg="spurious overwrite in %s" % sig)
class Test_DCTN_IDCTN(object):
dec = 14
dct_type = [1, 2, 3, 4]
norms = [None, 'ortho']
rstate = np.random.RandomState(1234)
shape = (32, 16)
data = rstate.randn(*shape)
@pytest.mark.parametrize('fforward,finverse', [(dctn, idctn),
(dstn, idstn)])
@pytest.mark.parametrize('axes', [None,
1, (1,), [1],
0, (0,), [0],
(0, 1), [0, 1],
(-2, -1), [-2, -1]])
@pytest.mark.parametrize('dct_type', dct_type)
@pytest.mark.parametrize('norm', ['ortho'])
def test_axes_round_trip(self, fforward, finverse, axes, dct_type, norm):
tmp = fforward(self.data, type=dct_type, axes=axes, norm=norm)
tmp = finverse(tmp, type=dct_type, axes=axes, norm=norm)
assert_array_almost_equal(self.data, tmp, decimal=12)
@pytest.mark.parametrize('funcn,func', [(dctn, dct), (dstn, dst)])
@pytest.mark.parametrize('dct_type', dct_type)
@pytest.mark.parametrize('norm', norms)
def test_dctn_vs_2d_reference(self, funcn, func, dct_type, norm):
y1 = funcn(self.data, type=dct_type, axes=None, norm=norm)
y2 = ref_2d(func, self.data, type=dct_type, norm=norm)
assert_array_almost_equal(y1, y2, decimal=11)
@pytest.mark.parametrize('funcn,func', [(idctn, idct), (idstn, idst)])
@pytest.mark.parametrize('dct_type', dct_type)
@pytest.mark.parametrize('norm', [None, 'ortho'])
def test_idctn_vs_2d_reference(self, funcn, func, dct_type, norm):
fdata = dctn(self.data, type=dct_type, norm=norm)
y1 = funcn(fdata, type=dct_type, norm=norm)
y2 = ref_2d(func, fdata, type=dct_type, norm=norm)
assert_array_almost_equal(y1, y2, decimal=11)
@pytest.mark.parametrize('fforward,finverse', [(dctn, idctn),
(dstn, idstn)])
def test_axes_and_shape(self, fforward, finverse):
with assert_raises(ValueError,
match="when given, axes and shape arguments"
" have to be of the same length"):
fforward(self.data, s=self.data.shape[0], axes=(0, 1))
with assert_raises(ValueError,
match="when given, axes and shape arguments"
" have to be of the same length"):
fforward(self.data, s=self.data.shape, axes=0)
@pytest.mark.parametrize('fforward', [dctn, dstn])
def test_shape(self, fforward):
tmp = fforward(self.data, s=(128, 128), axes=None)
assert_equal(tmp.shape, (128, 128))
@pytest.mark.parametrize('fforward,finverse', [(dctn, idctn),
(dstn, idstn)])
@pytest.mark.parametrize('axes', [1, (1,), [1],
0, (0,), [0]])
def test_shape_is_none_with_axes(self, fforward, finverse, axes):
tmp = fforward(self.data, s=None, axes=axes, norm='ortho')
tmp = finverse(tmp, s=None, axes=axes, norm='ortho')
assert_array_almost_equal(self.data, tmp, decimal=self.dec)
@pytest.mark.parametrize('func', [dct, dctn, idct, idctn,
dst, dstn, idst, idstn])
def test_swapped_byte_order(func):
rng = np.random.RandomState(1234)
x = rng.rand(10)
swapped_dt = x.dtype.newbyteorder('S')
assert_allclose(func(x.astype(swapped_dt)), func(x))

View file

@ -0,0 +1,618 @@
from ._basic import _dispatch
from scipy._lib.uarray import Dispatchable
import numpy as np
__all__ = ['dct', 'idct', 'dst', 'idst', 'dctn', 'idctn', 'dstn', 'idstn']
@_dispatch
def dctn(x, type=2, s=None, axes=None, norm=None, overwrite_x=False,
workers=None):
"""
Return multidimensional Discrete Cosine Transform along the specified axes.
Parameters
----------
x : array_like
The input array.
type : {1, 2, 3, 4}, optional
Type of the DCT (see Notes). Default type is 2.
s : int or array_like of ints or None, optional
The shape of the result. If both `s` and `axes` (see below) are None,
`s` is ``x.shape``; if `s` is None but `axes` is not None, then `s` is
``scipy.take(x.shape, axes, axis=0)``.
If ``s[i] > x.shape[i]``, the ith dimension is padded with zeros.
If ``s[i] < x.shape[i]``, the ith dimension is truncated to length
``s[i]``.
If any element of `s` is -1, the size of the corresponding dimension of
`x` is used.
axes : int or array_like of ints or None, optional
Axes over which the DCT is computed. If not given, the last ``len(s)``
axes are used, or all axes if `s` is also not specified.
norm : {None, 'ortho'}, optional
Normalization mode (see Notes). Default is None.
overwrite_x : bool, optional
If True, the contents of `x` can be destroyed; the default is False.
workers : int, optional
Maximum number of workers to use for parallel computation. If negative,
the value wraps around from ``os.cpu_count()``.
See :func:`~scipy.fft.fft` for more details.
Returns
-------
y : ndarray of real
The transformed input array.
See Also
--------
idctn : Inverse multidimensional DCT
Notes
-----
For full details of the DCT types and normalization modes, as well as
references, see `dct`.
Examples
--------
>>> from scipy.fft import dctn, idctn
>>> y = np.random.randn(16, 16)
>>> np.allclose(y, idctn(dctn(y)))
True
"""
return (Dispatchable(x, np.ndarray),)
@_dispatch
def idctn(x, type=2, s=None, axes=None, norm=None, overwrite_x=False,
workers=None):
"""
Return multidimensional Discrete Cosine Transform along the specified axes.
Parameters
----------
x : array_like
The input array.
type : {1, 2, 3, 4}, optional
Type of the DCT (see Notes). Default type is 2.
s : int or array_like of ints or None, optional
The shape of the result. If both `s` and `axes` (see below) are
None, `s` is ``x.shape``; if `s` is None but `axes` is
not None, then `s` is ``scipy.take(x.shape, axes, axis=0)``.
If ``s[i] > x.shape[i]``, the ith dimension is padded with zeros.
If ``s[i] < x.shape[i]``, the ith dimension is truncated to length
``s[i]``.
If any element of `s` is -1, the size of the corresponding dimension of
`x` is used.
axes : int or array_like of ints or None, optional
Axes over which the IDCT is computed. If not given, the last ``len(s)``
axes are used, or all axes if `s` is also not specified.
norm : {None, 'ortho'}, optional
Normalization mode (see Notes). Default is None.
overwrite_x : bool, optional
If True, the contents of `x` can be destroyed; the default is False.
workers : int, optional
Maximum number of workers to use for parallel computation. If negative,
the value wraps around from ``os.cpu_count()``.
See :func:`~scipy.fft.fft` for more details.
Returns
-------
y : ndarray of real
The transformed input array.
See Also
--------
dctn : multidimensional DCT
Notes
-----
For full details of the IDCT types and normalization modes, as well as
references, see `idct`.
Examples
--------
>>> from scipy.fft import dctn, idctn
>>> y = np.random.randn(16, 16)
>>> np.allclose(y, idctn(dctn(y)))
True
"""
return (Dispatchable(x, np.ndarray),)
@_dispatch
def dstn(x, type=2, s=None, axes=None, norm=None, overwrite_x=False,
workers=None):
"""
Return multidimensional Discrete Sine Transform along the specified axes.
Parameters
----------
x : array_like
The input array.
type : {1, 2, 3, 4}, optional
Type of the DST (see Notes). Default type is 2.
s : int or array_like of ints or None, optional
The shape of the result. If both `s` and `axes` (see below) are None,
`s` is ``x.shape``; if `s` is None but `axes` is not None, then `s` is
``scipy.take(x.shape, axes, axis=0)``.
If ``s[i] > x.shape[i]``, the ith dimension is padded with zeros.
If ``s[i] < x.shape[i]``, the ith dimension is truncated to length
``s[i]``.
If any element of `shape` is -1, the size of the corresponding dimension
of `x` is used.
axes : int or array_like of ints or None, optional
Axes over which the DST is computed. If not given, the last ``len(s)``
axes are used, or all axes if `s` is also not specified.
norm : {None, 'ortho'}, optional
Normalization mode (see Notes). Default is None.
overwrite_x : bool, optional
If True, the contents of `x` can be destroyed; the default is False.
workers : int, optional
Maximum number of workers to use for parallel computation. If negative,
the value wraps around from ``os.cpu_count()``.
See :func:`~scipy.fft.fft` for more details.
Returns
-------
y : ndarray of real
The transformed input array.
See Also
--------
idstn : Inverse multidimensional DST
Notes
-----
For full details of the DST types and normalization modes, as well as
references, see `dst`.
Examples
--------
>>> from scipy.fft import dstn, idstn
>>> y = np.random.randn(16, 16)
>>> np.allclose(y, idstn(dstn(y)))
True
"""
return (Dispatchable(x, np.ndarray),)
@_dispatch
def idstn(x, type=2, s=None, axes=None, norm=None, overwrite_x=False,
workers=None):
"""
Return multidimensional Discrete Sine Transform along the specified axes.
Parameters
----------
x : array_like
The input array.
type : {1, 2, 3, 4}, optional
Type of the DST (see Notes). Default type is 2.
s : int or array_like of ints or None, optional
The shape of the result. If both `s` and `axes` (see below) are None,
`s` is ``x.shape``; if `s` is None but `axes` is not None, then `s` is
``scipy.take(x.shape, axes, axis=0)``.
If ``s[i] > x.shape[i]``, the ith dimension is padded with zeros.
If ``s[i] < x.shape[i]``, the ith dimension is truncated to length
``s[i]``.
If any element of `s` is -1, the size of the corresponding dimension of
`x` is used.
axes : int or array_like of ints or None, optional
Axes over which the IDST is computed. If not given, the last ``len(s)``
axes are used, or all axes if `s` is also not specified.
norm : {None, 'ortho'}, optional
Normalization mode (see Notes). Default is None.
overwrite_x : bool, optional
If True, the contents of `x` can be destroyed; the default is False.
workers : int, optional
Maximum number of workers to use for parallel computation. If negative,
the value wraps around from ``os.cpu_count()``.
See :func:`~scipy.fft.fft` for more details.
Returns
-------
y : ndarray of real
The transformed input array.
See Also
--------
dstn : multidimensional DST
Notes
-----
For full details of the IDST types and normalization modes, as well as
references, see `idst`.
Examples
--------
>>> from scipy.fft import dstn, idstn
>>> y = np.random.randn(16, 16)
>>> np.allclose(y, idstn(dstn(y)))
True
"""
return (Dispatchable(x, np.ndarray),)
@_dispatch
def dct(x, type=2, n=None, axis=-1, norm=None, overwrite_x=False, workers=None):
r"""
Return the Discrete Cosine Transform of arbitrary type sequence x.
Parameters
----------
x : array_like
The input array.
type : {1, 2, 3, 4}, optional
Type of the DCT (see Notes). Default type is 2.
n : int, optional
Length of the transform. If ``n < x.shape[axis]``, `x` is
truncated. If ``n > x.shape[axis]``, `x` is zero-padded. The
default results in ``n = x.shape[axis]``.
axis : int, optional
Axis along which the dct is computed; the default is over the
last axis (i.e., ``axis=-1``).
norm : {None, 'ortho'}, optional
Normalization mode (see Notes). Default is None.
overwrite_x : bool, optional
If True, the contents of `x` can be destroyed; the default is False.
workers : int, optional
Maximum number of workers to use for parallel computation. If negative,
the value wraps around from ``os.cpu_count()``.
See :func:`~scipy.fft.fft` for more details.
Returns
-------
y : ndarray of real
The transformed input array.
See Also
--------
idct : Inverse DCT
Notes
-----
For a single dimension array ``x``, ``dct(x, norm='ortho')`` is equal to
MATLAB ``dct(x)``.
For ``norm=None``, there is no scaling on `dct` and the `idct` is scaled by
``1/N`` where ``N`` is the "logical" size of the DCT. For ``norm='ortho'``
both directions are scaled by the same factor ``1/sqrt(N)``.
There are, theoretically, 8 types of the DCT, only the first 4 types are
implemented in SciPy.'The' DCT generally refers to DCT type 2, and 'the'
Inverse DCT generally refers to DCT type 3.
**Type I**
There are several definitions of the DCT-I; we use the following
(for ``norm=None``)
.. math::
y_k = x_0 + (-1)^k x_{N-1} + 2 \sum_{n=1}^{N-2} x_n \cos\left(
\frac{\pi k n}{N-1} \right)
If ``norm='ortho'``, ``x[0]`` and ``x[N-1]`` are multiplied by a scaling
factor of :math:`\sqrt{2}`, and ``y[k]`` is multiplied by a scaling factor
``f``
.. math::
f = \begin{cases}
\frac{1}{2}\sqrt{\frac{1}{N-1}} & \text{if }k=0\text{ or }N-1, \\
\frac{1}{2}\sqrt{\frac{2}{N-1}} & \text{otherwise} \end{cases}
.. note::
The DCT-I is only supported for input size > 1.
**Type II**
There are several definitions of the DCT-II; we use the following
(for ``norm=None``)
.. math::
y_k = 2 \sum_{n=0}^{N-1} x_n \cos\left(\frac{\pi k(2n+1)}{2N} \right)
If ``norm='ortho'``, ``y[k]`` is multiplied by a scaling factor ``f``
.. math::
f = \begin{cases}
\sqrt{\frac{1}{4N}} & \text{if }k=0, \\
\sqrt{\frac{1}{2N}} & \text{otherwise} \end{cases}
which makes the corresponding matrix of coefficients orthonormal
(``O @ O.T = np.eye(N)``).
**Type III**
There are several definitions, we use the following (for ``norm=None``)
.. math::
y_k = x_0 + 2 \sum_{n=1}^{N-1} x_n \cos\left(\frac{\pi(2k+1)n}{2N}\right)
or, for ``norm='ortho'``
.. math::
y_k = \frac{x_0}{\sqrt{N}} + \sqrt{\frac{2}{N}} \sum_{n=1}^{N-1} x_n
\cos\left(\frac{\pi(2k+1)n}{2N}\right)
The (unnormalized) DCT-III is the inverse of the (unnormalized) DCT-II, up
to a factor `2N`. The orthonormalized DCT-III is exactly the inverse of
the orthonormalized DCT-II.
**Type IV**
There are several definitions of the DCT-IV; we use the following
(for ``norm=None``)
.. math::
y_k = 2 \sum_{n=0}^{N-1} x_n \cos\left(\frac{\pi(2k+1)(2n+1)}{4N} \right)
If ``norm='ortho'``, ``y[k]`` is multiplied by a scaling factor ``f``
.. math::
f = \frac{1}{\sqrt{2N}}
References
----------
.. [1] 'A Fast Cosine Transform in One and Two Dimensions', by J.
Makhoul, `IEEE Transactions on acoustics, speech and signal
processing` vol. 28(1), pp. 27-34,
:doi:`10.1109/TASSP.1980.1163351` (1980).
.. [2] Wikipedia, "Discrete cosine transform",
https://en.wikipedia.org/wiki/Discrete_cosine_transform
Examples
--------
The Type 1 DCT is equivalent to the FFT (though faster) for real,
even-symmetrical inputs. The output is also real and even-symmetrical.
Half of the FFT input is used to generate half of the FFT output:
>>> from scipy.fft import fft, dct
>>> fft(np.array([4., 3., 5., 10., 5., 3.])).real
array([ 30., -8., 6., -2., 6., -8.])
>>> dct(np.array([4., 3., 5., 10.]), 1)
array([ 30., -8., 6., -2.])
"""
return (Dispatchable(x, np.ndarray),)
@_dispatch
def idct(x, type=2, n=None, axis=-1, norm=None, overwrite_x=False,
workers=None):
"""
Return the Inverse Discrete Cosine Transform of an arbitrary type sequence.
Parameters
----------
x : array_like
The input array.
type : {1, 2, 3, 4}, optional
Type of the DCT (see Notes). Default type is 2.
n : int, optional
Length of the transform. If ``n < x.shape[axis]``, `x` is
truncated. If ``n > x.shape[axis]``, `x` is zero-padded. The
default results in ``n = x.shape[axis]``.
axis : int, optional
Axis along which the idct is computed; the default is over the
last axis (i.e., ``axis=-1``).
norm : {None, 'ortho'}, optional
Normalization mode (see Notes). Default is None.
overwrite_x : bool, optional
If True, the contents of `x` can be destroyed; the default is False.
workers : int, optional
Maximum number of workers to use for parallel computation. If negative,
the value wraps around from ``os.cpu_count()``.
See :func:`~scipy.fft.fft` for more details.
Returns
-------
idct : ndarray of real
The transformed input array.
See Also
--------
dct : Forward DCT
Notes
-----
For a single dimension array `x`, ``idct(x, norm='ortho')`` is equal to
MATLAB ``idct(x)``.
'The' IDCT is the IDCT-II, which is the same as the normalized DCT-III.
The IDCT is equivalent to a normal DCT except for the normalization and
type. DCT type 1 and 4 are their own inverse and DCTs 2 and 3 are each
other's inverses.
Examples
--------
The Type 1 DCT is equivalent to the DFT for real, even-symmetrical
inputs. The output is also real and even-symmetrical. Half of the IFFT
input is used to generate half of the IFFT output:
>>> from scipy.fft import ifft, idct
>>> ifft(np.array([ 30., -8., 6., -2., 6., -8.])).real
array([ 4., 3., 5., 10., 5., 3.])
>>> idct(np.array([ 30., -8., 6., -2.]), 1)
array([ 4., 3., 5., 10.])
"""
return (Dispatchable(x, np.ndarray),)
@_dispatch
def dst(x, type=2, n=None, axis=-1, norm=None, overwrite_x=False, workers=None):
r"""
Return the Discrete Sine Transform of arbitrary type sequence x.
Parameters
----------
x : array_like
The input array.
type : {1, 2, 3, 4}, optional
Type of the DST (see Notes). Default type is 2.
n : int, optional
Length of the transform. If ``n < x.shape[axis]``, `x` is
truncated. If ``n > x.shape[axis]``, `x` is zero-padded. The
default results in ``n = x.shape[axis]``.
axis : int, optional
Axis along which the dst is computed; the default is over the
last axis (i.e., ``axis=-1``).
norm : {None, 'ortho'}, optional
Normalization mode (see Notes). Default is None.
overwrite_x : bool, optional
If True, the contents of `x` can be destroyed; the default is False.
workers : int, optional
Maximum number of workers to use for parallel computation. If negative,
the value wraps around from ``os.cpu_count()``.
See :func:`~scipy.fft.fft` for more details.
Returns
-------
dst : ndarray of reals
The transformed input array.
See Also
--------
idst : Inverse DST
Notes
-----
For a single dimension array ``x``.
For ``norm=None``, there is no scaling on the `dst` and the `idst` is
scaled by ``1/N`` where ``N`` is the "logical" size of the DST. For
``norm='ortho'`` both directions are scaled by the same factor
``1/sqrt(N)``.
There are, theoretically, 8 types of the DST for different combinations of
even/odd boundary conditions and boundary off sets [1]_, only the first
4 types are implemented in SciPy.
**Type I**
There are several definitions of the DST-I; we use the following
for ``norm=None``. DST-I assumes the input is odd around `n=-1` and `n=N`.
.. math::
y_k = 2 \sum_{n=0}^{N-1} x_n \sin\left(\frac{\pi(k+1)(n+1)}{N+1}\right)
Note that the DST-I is only supported for input size > 1.
The (unnormalized) DST-I is its own inverse, up to a factor `2(N+1)`.
The orthonormalized DST-I is exactly its own inverse.
**Type II**
There are several definitions of the DST-II; we use the following for
``norm=None``. DST-II assumes the input is odd around `n=-1/2` and
`n=N-1/2`; the output is odd around :math:`k=-1` and even around `k=N-1`
.. math::
y_k = 2 \sum_{n=0}^{N-1} x_n \sin\left(\frac{\pi(k+1)(2n+1)}{2N}\right)
if ``norm='ortho'``, ``y[k]`` is multiplied by a scaling factor ``f``
.. math::
f = \begin{cases}
\sqrt{\frac{1}{4N}} & \text{if }k = 0, \\
\sqrt{\frac{1}{2N}} & \text{otherwise} \end{cases}
**Type III**
There are several definitions of the DST-III, we use the following (for
``norm=None``). DST-III assumes the input is odd around `n=-1` and even
around `n=N-1`
.. math::
y_k = (-1)^k x_{N-1} + 2 \sum_{n=0}^{N-2} x_n \sin\left(
\frac{\pi(2k+1)(n+1)}{2N}\right)
The (unnormalized) DST-III is the inverse of the (unnormalized) DST-II, up
to a factor `2N`. The orthonormalized DST-III is exactly the inverse of the
orthonormalized DST-II.
**Type IV**
There are several definitions of the DST-IV, we use the following (for
``norm=None``). DST-IV assumes the input is odd around `n=-0.5` and even
around `n=N-0.5`
.. math::
y_k = 2 \sum_{n=0}^{N-1} x_n \sin\left(\frac{\pi(2k+1)(2n+1)}{4N}\right)
The (unnormalized) DST-IV is its own inverse, up to a factor `2N`. The
orthonormalized DST-IV is exactly its own inverse.
References
----------
.. [1] Wikipedia, "Discrete sine transform",
https://en.wikipedia.org/wiki/Discrete_sine_transform
"""
return (Dispatchable(x, np.ndarray),)
@_dispatch
def idst(x, type=2, n=None, axis=-1, norm=None, overwrite_x=False,
workers=None):
"""
Return the Inverse Discrete Sine Transform of an arbitrary type sequence.
Parameters
----------
x : array_like
The input array.
type : {1, 2, 3, 4}, optional
Type of the DST (see Notes). Default type is 2.
n : int, optional
Length of the transform. If ``n < x.shape[axis]``, `x` is
truncated. If ``n > x.shape[axis]``, `x` is zero-padded. The
default results in ``n = x.shape[axis]``.
axis : int, optional
Axis along which the idst is computed; the default is over the
last axis (i.e., ``axis=-1``).
norm : {None, 'ortho'}, optional
Normalization mode (see Notes). Default is None.
overwrite_x : bool, optional
If True, the contents of `x` can be destroyed; the default is False.
workers : int, optional
Maximum number of workers to use for parallel computation. If negative,
the value wraps around from ``os.cpu_count()``.
See :func:`~scipy.fft.fft` for more details.
Returns
-------
idst : ndarray of real
The transformed input array.
See Also
--------
dst : Forward DST
Notes
-----
'The' IDST is the IDST-II, which is the same as the normalized DST-III.
The IDST is equivalent to a normal DST except for the normalization and
type. DST type 1 and 4 are their own inverse and DSTs 2 and 3 are each
other's inverses.
"""
return (Dispatchable(x, np.ndarray),)

View file

@ -0,0 +1,12 @@
def configuration(parent_package='', top_path=None):
from numpy.distutils.misc_util import Configuration
config = Configuration('fft', parent_package, top_path)
config.add_subpackage('_pocketfft')
config.add_data_dir('tests')
return config
if __name__ == '__main__':
from numpy.distutils.core import setup
setup(**configuration(top_path='').todict())

View file

@ -0,0 +1,56 @@
import numpy as np
class _MockFunction:
def __init__(self, return_value = None):
self.number_calls = 0
self.return_value = return_value
self.last_args = ([], {})
def __call__(self, *args, **kwargs):
self.number_calls += 1
self.last_args = (args, kwargs)
return self.return_value
fft = _MockFunction(np.random.random(10))
fft2 = _MockFunction(np.random.random(10))
fftn = _MockFunction(np.random.random(10))
ifft = _MockFunction(np.random.random(10))
ifft2 = _MockFunction(np.random.random(10))
ifftn = _MockFunction(np.random.random(10))
rfft = _MockFunction(np.random.random(10))
rfft2 = _MockFunction(np.random.random(10))
rfftn = _MockFunction(np.random.random(10))
irfft = _MockFunction(np.random.random(10))
irfft2 = _MockFunction(np.random.random(10))
irfftn = _MockFunction(np.random.random(10))
hfft = _MockFunction(np.random.random(10))
hfft2 = _MockFunction(np.random.random(10))
hfftn = _MockFunction(np.random.random(10))
ihfft = _MockFunction(np.random.random(10))
ihfft2 = _MockFunction(np.random.random(10))
ihfftn = _MockFunction(np.random.random(10))
dct = _MockFunction(np.random.random(10))
idct = _MockFunction(np.random.random(10))
dctn = _MockFunction(np.random.random(10))
idctn = _MockFunction(np.random.random(10))
dst = _MockFunction(np.random.random(10))
idst = _MockFunction(np.random.random(10))
dstn = _MockFunction(np.random.random(10))
idstn = _MockFunction(np.random.random(10))
__ua_domain__ = "numpy.scipy.fft"
def __ua_function__(method, args, kwargs):
fn = globals().get(method.__name__)
return (fn(*args, **kwargs) if fn is not None
else NotImplemented)

View file

@ -0,0 +1,87 @@
import numpy as np
import scipy.fft
from scipy.fft import set_backend
from scipy.fft import _pocketfft
from scipy.fft.tests import mock_backend
from numpy.testing import assert_allclose, assert_equal
import pytest
fnames = ('fft', 'fft2', 'fftn',
'ifft', 'ifft2', 'ifftn',
'rfft', 'rfft2', 'rfftn',
'irfft', 'irfft2', 'irfftn',
'dct', 'idct', 'dctn', 'idctn',
'dst', 'idst', 'dstn', 'idstn')
np_funcs = (np.fft.fft, np.fft.fft2, np.fft.fftn,
np.fft.ifft, np.fft.ifft2, np.fft.ifftn,
np.fft.rfft, np.fft.rfft2, np.fft.rfftn,
np.fft.irfft, np.fft.irfft2, np.fft.irfftn,
np.fft.hfft, _pocketfft.hfft2, _pocketfft.hfftn, # np has no hfftn
np.fft.ihfft, _pocketfft.ihfft2, _pocketfft.ihfftn,
_pocketfft.dct, _pocketfft.idct, _pocketfft.dctn, _pocketfft.idctn,
_pocketfft.dst, _pocketfft.idst, _pocketfft.dstn, _pocketfft.idstn)
funcs = (scipy.fft.fft, scipy.fft.fft2, scipy.fft.fftn,
scipy.fft.ifft, scipy.fft.ifft2, scipy.fft.ifftn,
scipy.fft.rfft, scipy.fft.rfft2, scipy.fft.rfftn,
scipy.fft.irfft, scipy.fft.irfft2, scipy.fft.irfftn,
scipy.fft.hfft, scipy.fft.hfft2, scipy.fft.hfftn,
scipy.fft.ihfft, scipy.fft.ihfft2, scipy.fft.ihfftn,
scipy.fft.dct, scipy.fft.idct, scipy.fft.dctn, scipy.fft.idctn,
scipy.fft.dst, scipy.fft.idst, scipy.fft.dstn, scipy.fft.idstn)
mocks = (mock_backend.fft, mock_backend.fft2, mock_backend.fftn,
mock_backend.ifft, mock_backend.ifft2, mock_backend.ifftn,
mock_backend.rfft, mock_backend.rfft2, mock_backend.rfftn,
mock_backend.irfft, mock_backend.irfft2, mock_backend.irfftn,
mock_backend.hfft, mock_backend.hfft2, mock_backend.hfftn,
mock_backend.ihfft, mock_backend.ihfft2, mock_backend.ihfftn,
mock_backend.dct, mock_backend.idct, mock_backend.dctn, mock_backend.idctn,
mock_backend.dst, mock_backend.idst, mock_backend.dstn, mock_backend.idstn)
@pytest.mark.parametrize("func, np_func, mock", zip(funcs, np_funcs, mocks))
def test_backend_call(func, np_func, mock):
x = np.arange(20).reshape((10,2))
answer = np_func(x)
assert_allclose(func(x), answer, atol=1e-10)
with set_backend(mock_backend, only=True):
mock.number_calls = 0
y = func(x)
assert_equal(y, mock.return_value)
assert_equal(mock.number_calls, 1)
assert_allclose(func(x), answer, atol=1e-10)
plan_funcs = (scipy.fft.fft, scipy.fft.fft2, scipy.fft.fftn,
scipy.fft.ifft, scipy.fft.ifft2, scipy.fft.ifftn,
scipy.fft.rfft, scipy.fft.rfft2, scipy.fft.rfftn,
scipy.fft.irfft, scipy.fft.irfft2, scipy.fft.irfftn,
scipy.fft.hfft, scipy.fft.hfft2, scipy.fft.hfftn,
scipy.fft.ihfft, scipy.fft.ihfft2, scipy.fft.ihfftn)
plan_mocks = (mock_backend.fft, mock_backend.fft2, mock_backend.fftn,
mock_backend.ifft, mock_backend.ifft2, mock_backend.ifftn,
mock_backend.rfft, mock_backend.rfft2, mock_backend.rfftn,
mock_backend.irfft, mock_backend.irfft2, mock_backend.irfftn,
mock_backend.hfft, mock_backend.hfft2, mock_backend.hfftn,
mock_backend.ihfft, mock_backend.ihfft2, mock_backend.ihfftn)
@pytest.mark.parametrize("func, mock", zip(plan_funcs, plan_mocks))
def test_backend_plan(func, mock):
x = np.arange(20).reshape((10, 2))
with pytest.raises(NotImplementedError, match='precomputed plan'):
func(x, plan='foo')
with set_backend(mock_backend, only=True):
mock.number_calls = 0
y = func(x, plan='foo')
assert_equal(y, mock.return_value)
assert_equal(mock.number_calls, 1)
assert_equal(mock.last_args[1]['plan'], 'foo')

View file

@ -0,0 +1,36 @@
import pytest
import numpy as np
from numpy.testing import assert_allclose
def test_fft_function():
# Many NumPy symbols are imported into the scipy namespace, including
# numpy.fft.fft as scipy.fft, conflicting with this module (gh-10253)
np.random.seed(1234)
# Callable before scipy.fft is imported
import scipy
x = np.random.randn(10) + 1j * np.random.randn(10)
with pytest.deprecated_call(match=r'1\.5\.0'):
X = scipy.fft(x)
with pytest.deprecated_call(match=r'2\.0\.0'):
y = scipy.ifft(X)
assert_allclose(y, x)
# Callable after scipy.fft is imported
import scipy.fft
assert_allclose(X, scipy.fft.fft(x))
with pytest.deprecated_call(match=r'1\.5\.0'):
X = scipy.fft(x)
assert_allclose(X, scipy.fft.fft(x))
with pytest.deprecated_call(match=r'2\.0\.0'):
y = scipy.ifft(X)
assert_allclose(y, x)
# Callable when imported using from
from scipy import fft
with pytest.deprecated_call(match=r'1\.5\.0'):
X = fft(x)
with pytest.deprecated_call(match=r'2\.0\.0'):
y = scipy.ifft(X)
assert_allclose(y, x)

View file

@ -0,0 +1,297 @@
from scipy.fft._helper import next_fast_len, _init_nd_shape_and_axes
from numpy.testing import assert_equal, assert_array_equal
from pytest import raises as assert_raises
import pytest
import numpy as np
import sys
_5_smooth_numbers = [
2, 3, 4, 5, 6, 8, 9, 10,
2 * 3 * 5,
2**3 * 3**5,
2**3 * 3**3 * 5**2,
]
def test_next_fast_len():
for n in _5_smooth_numbers:
assert_equal(next_fast_len(n), n)
def _assert_n_smooth(x, n):
x_orig = x
if n < 2:
assert False
while True:
q, r = divmod(x, 2)
if r != 0:
break
x = q
for d in range(3, n+1, 2):
while True:
q, r = divmod(x, d)
if r != 0:
break
x = q
assert x == 1, \
'x={} is not {}-smooth, remainder={}'.format(x_orig, n, x)
class TestNextFastLen(object):
def test_next_fast_len(self):
np.random.seed(1234)
def nums():
for j in range(1, 1000):
yield j
yield 2**5 * 3**5 * 4**5 + 1
for n in nums():
m = next_fast_len(n)
_assert_n_smooth(m, 11)
assert m == next_fast_len(n, False)
m = next_fast_len(n, True)
_assert_n_smooth(m, 5)
def test_np_integers(self):
ITYPES = [np.int16, np.int32, np.int64, np.uint16, np.uint32, np.uint64]
for ityp in ITYPES:
x = ityp(12345)
testN = next_fast_len(x)
assert_equal(testN, next_fast_len(int(x)))
def testnext_fast_len_small(self):
hams = {
1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 6: 6, 7: 8, 8: 8, 14: 15, 15: 15,
16: 16, 17: 18, 1021: 1024, 1536: 1536, 51200000: 51200000
}
for x, y in hams.items():
assert_equal(next_fast_len(x, True), y)
@pytest.mark.xfail(sys.maxsize < 2**32,
reason="Hamming Numbers too large for 32-bit",
raises=ValueError, strict=True)
def testnext_fast_len_big(self):
hams = {
510183360: 510183360, 510183360 + 1: 512000000,
511000000: 512000000,
854296875: 854296875, 854296875 + 1: 859963392,
196608000000: 196608000000, 196608000000 + 1: 196830000000,
8789062500000: 8789062500000, 8789062500000 + 1: 8796093022208,
206391214080000: 206391214080000,
206391214080000 + 1: 206624260800000,
470184984576000: 470184984576000,
470184984576000 + 1: 470715894135000,
7222041363087360: 7222041363087360,
7222041363087360 + 1: 7230196133913600,
# power of 5 5**23
11920928955078125: 11920928955078125,
11920928955078125 - 1: 11920928955078125,
# power of 3 3**34
16677181699666569: 16677181699666569,
16677181699666569 - 1: 16677181699666569,
# power of 2 2**54
18014398509481984: 18014398509481984,
18014398509481984 - 1: 18014398509481984,
# above this, int(ceil(n)) == int(ceil(n+1))
19200000000000000: 19200000000000000,
19200000000000000 + 1: 19221679687500000,
288230376151711744: 288230376151711744,
288230376151711744 + 1: 288325195312500000,
288325195312500000 - 1: 288325195312500000,
288325195312500000: 288325195312500000,
288325195312500000 + 1: 288555831593533440,
}
for x, y in hams.items():
assert_equal(next_fast_len(x, True), y)
class Test_init_nd_shape_and_axes(object):
def test_py_0d_defaults(self):
x = np.array(4)
shape = None
axes = None
shape_expected = np.array([])
axes_expected = np.array([])
shape_res, axes_res = _init_nd_shape_and_axes(x, shape, axes)
assert_equal(shape_res, shape_expected)
assert_equal(axes_res, axes_expected)
def test_np_0d_defaults(self):
x = np.array(7.)
shape = None
axes = None
shape_expected = np.array([])
axes_expected = np.array([])
shape_res, axes_res = _init_nd_shape_and_axes(x, shape, axes)
assert_equal(shape_res, shape_expected)
assert_equal(axes_res, axes_expected)
def test_py_1d_defaults(self):
x = np.array([1, 2, 3])
shape = None
axes = None
shape_expected = np.array([3])
axes_expected = np.array([0])
shape_res, axes_res = _init_nd_shape_and_axes(x, shape, axes)
assert_equal(shape_res, shape_expected)
assert_equal(axes_res, axes_expected)
def test_np_1d_defaults(self):
x = np.arange(0, 1, .1)
shape = None
axes = None
shape_expected = np.array([10])
axes_expected = np.array([0])
shape_res, axes_res = _init_nd_shape_and_axes(x, shape, axes)
assert_equal(shape_res, shape_expected)
assert_equal(axes_res, axes_expected)
def test_py_2d_defaults(self):
x = np.array([[1, 2, 3, 4],
[5, 6, 7, 8]])
shape = None
axes = None
shape_expected = np.array([2, 4])
axes_expected = np.array([0, 1])
shape_res, axes_res = _init_nd_shape_and_axes(x, shape, axes)
assert_equal(shape_res, shape_expected)
assert_equal(axes_res, axes_expected)
def test_np_2d_defaults(self):
x = np.arange(0, 1, .1).reshape(5, 2)
shape = None
axes = None
shape_expected = np.array([5, 2])
axes_expected = np.array([0, 1])
shape_res, axes_res = _init_nd_shape_and_axes(x, shape, axes)
assert_equal(shape_res, shape_expected)
assert_equal(axes_res, axes_expected)
def test_np_5d_defaults(self):
x = np.zeros([6, 2, 5, 3, 4])
shape = None
axes = None
shape_expected = np.array([6, 2, 5, 3, 4])
axes_expected = np.array([0, 1, 2, 3, 4])
shape_res, axes_res = _init_nd_shape_and_axes(x, shape, axes)
assert_equal(shape_res, shape_expected)
assert_equal(axes_res, axes_expected)
def test_np_5d_set_shape(self):
x = np.zeros([6, 2, 5, 3, 4])
shape = [10, -1, -1, 1, 4]
axes = None
shape_expected = np.array([10, 2, 5, 1, 4])
axes_expected = np.array([0, 1, 2, 3, 4])
shape_res, axes_res = _init_nd_shape_and_axes(x, shape, axes)
assert_equal(shape_res, shape_expected)
assert_equal(axes_res, axes_expected)
def test_np_5d_set_axes(self):
x = np.zeros([6, 2, 5, 3, 4])
shape = None
axes = [4, 1, 2]
shape_expected = np.array([4, 2, 5])
axes_expected = np.array([4, 1, 2])
shape_res, axes_res = _init_nd_shape_and_axes(x, shape, axes)
assert_equal(shape_res, shape_expected)
assert_equal(axes_res, axes_expected)
def test_np_5d_set_shape_axes(self):
x = np.zeros([6, 2, 5, 3, 4])
shape = [10, -1, 2]
axes = [1, 0, 3]
shape_expected = np.array([10, 6, 2])
axes_expected = np.array([1, 0, 3])
shape_res, axes_res = _init_nd_shape_and_axes(x, shape, axes)
assert_equal(shape_res, shape_expected)
assert_equal(axes_res, axes_expected)
def test_shape_axes_subset(self):
x = np.zeros((2, 3, 4, 5))
shape, axes = _init_nd_shape_and_axes(x, shape=(5, 5, 5), axes=None)
assert_array_equal(shape, [5, 5, 5])
assert_array_equal(axes, [1, 2, 3])
def test_errors(self):
x = np.zeros(1)
with assert_raises(ValueError, match="axes must be a scalar or "
"iterable of integers"):
_init_nd_shape_and_axes(x, shape=None, axes=[[1, 2], [3, 4]])
with assert_raises(ValueError, match="axes must be a scalar or "
"iterable of integers"):
_init_nd_shape_and_axes(x, shape=None, axes=[1., 2., 3., 4.])
with assert_raises(ValueError,
match="axes exceeds dimensionality of input"):
_init_nd_shape_and_axes(x, shape=None, axes=[1])
with assert_raises(ValueError,
match="axes exceeds dimensionality of input"):
_init_nd_shape_and_axes(x, shape=None, axes=[-2])
with assert_raises(ValueError,
match="all axes must be unique"):
_init_nd_shape_and_axes(x, shape=None, axes=[0, 0])
with assert_raises(ValueError, match="shape must be a scalar or "
"iterable of integers"):
_init_nd_shape_and_axes(x, shape=[[1, 2], [3, 4]], axes=None)
with assert_raises(ValueError, match="shape must be a scalar or "
"iterable of integers"):
_init_nd_shape_and_axes(x, shape=[1., 2., 3., 4.], axes=None)
with assert_raises(ValueError,
match="when given, axes and shape arguments"
" have to be of the same length"):
_init_nd_shape_and_axes(np.zeros([1, 1, 1, 1]),
shape=[1, 2, 3], axes=[1])
with assert_raises(ValueError,
match="invalid number of data points"
r" \(\[0\]\) specified"):
_init_nd_shape_and_axes(x, shape=[0], axes=None)
with assert_raises(ValueError,
match="invalid number of data points"
r" \(\[-2\]\) specified"):
_init_nd_shape_and_axes(x, shape=-2, axes=None)

View file

@ -0,0 +1,83 @@
from scipy import fft
import numpy as np
import pytest
from numpy.testing import assert_allclose
import multiprocessing
import os
@pytest.fixture(scope='module')
def x():
return np.random.randn(512, 128) # Must be large enough to qualify for mt
@pytest.mark.parametrize("func", [
fft.fft, fft.ifft, fft.fft2, fft.ifft2, fft.fftn, fft.ifftn,
fft.rfft, fft.irfft, fft.rfft2, fft.irfft2, fft.rfftn, fft.irfftn,
fft.hfft, fft.ihfft, fft.hfft2, fft.ihfft2, fft.hfftn, fft.ihfftn,
fft.dct, fft.idct, fft.dctn, fft.idctn,
fft.dst, fft.idst, fft.dstn, fft.idstn,
])
@pytest.mark.parametrize("workers", [2, -1])
def test_threaded_same(x, func, workers):
expected = func(x, workers=1)
actual = func(x, workers=workers)
assert_allclose(actual, expected)
def _mt_fft(x):
return fft.fft(x, workers=2)
def test_mixed_threads_processes(x):
# Test that the fft threadpool is safe to use before & after fork
expect = fft.fft(x, workers=2)
with multiprocessing.Pool(2) as p:
res = p.map(_mt_fft, [x for _ in range(4)])
for r in res:
assert_allclose(r, expect)
fft.fft(x, workers=2)
def test_invalid_workers(x):
cpus = os.cpu_count()
fft.ifft([1], workers=-cpus)
with pytest.raises(ValueError, match='workers must not be zero'):
fft.fft(x, workers=0)
with pytest.raises(ValueError, match='workers value out of range'):
fft.ifft(x, workers=-cpus-1)
def test_set_get_workers():
cpus = os.cpu_count()
assert fft.get_workers() == 1
with fft.set_workers(4):
assert fft.get_workers() == 4
with fft.set_workers(-1):
assert fft.get_workers() == cpus
assert fft.get_workers() == 4
assert fft.get_workers() == 1
with fft.set_workers(-cpus):
assert fft.get_workers() == 1
def test_set_workers_invalid():
with pytest.raises(ValueError, match='workers must not be zero'):
with fft.set_workers(0):
pass
with pytest.raises(ValueError, match='workers value out of range'):
with fft.set_workers(-os.cpu_count()-1):
pass

View file

@ -0,0 +1,325 @@
import queue
import threading
import multiprocessing
import numpy as np
import pytest
from numpy.random import random
from numpy.testing import (
assert_array_almost_equal, assert_array_equal, assert_allclose
)
from pytest import raises as assert_raises
import scipy.fft as fft
def fft1(x):
L = len(x)
phase = -2j*np.pi*(np.arange(L)/float(L))
phase = np.arange(L).reshape(-1, 1) * phase
return np.sum(x*np.exp(phase), axis=1)
class TestFFTShift(object):
def test_fft_n(self):
assert_raises(ValueError, fft.fft, [1, 2, 3], 0)
class TestFFT1D(object):
def test_identity(self):
maxlen = 512
x = random(maxlen) + 1j*random(maxlen)
xr = random(maxlen)
for i in range(1,maxlen):
assert_array_almost_equal(fft.ifft(fft.fft(x[0:i])), x[0:i],
decimal=12)
assert_array_almost_equal(fft.irfft(fft.rfft(xr[0:i]),i),
xr[0:i], decimal=12)
def test_fft(self):
x = random(30) + 1j*random(30)
assert_array_almost_equal(fft1(x), fft.fft(x))
assert_array_almost_equal(fft1(x) / np.sqrt(30),
fft.fft(x, norm="ortho"))
def test_ifft(self):
x = random(30) + 1j*random(30)
assert_array_almost_equal(x, fft.ifft(fft.fft(x)))
assert_array_almost_equal(
x, fft.ifft(fft.fft(x, norm="ortho"), norm="ortho"))
def test_fft2(self):
x = random((30, 20)) + 1j*random((30, 20))
assert_array_almost_equal(fft.fft(fft.fft(x, axis=1), axis=0),
fft.fft2(x))
assert_array_almost_equal(fft.fft2(x) / np.sqrt(30 * 20),
fft.fft2(x, norm="ortho"))
def test_ifft2(self):
x = random((30, 20)) + 1j*random((30, 20))
assert_array_almost_equal(fft.ifft(fft.ifft(x, axis=1), axis=0),
fft.ifft2(x))
assert_array_almost_equal(fft.ifft2(x) * np.sqrt(30 * 20),
fft.ifft2(x, norm="ortho"))
def test_fftn(self):
x = random((30, 20, 10)) + 1j*random((30, 20, 10))
assert_array_almost_equal(
fft.fft(fft.fft(fft.fft(x, axis=2), axis=1), axis=0),
fft.fftn(x))
assert_array_almost_equal(fft.fftn(x) / np.sqrt(30 * 20 * 10),
fft.fftn(x, norm="ortho"))
def test_ifftn(self):
x = random((30, 20, 10)) + 1j*random((30, 20, 10))
assert_array_almost_equal(
fft.ifft(fft.ifft(fft.ifft(x, axis=2), axis=1), axis=0),
fft.ifftn(x))
assert_array_almost_equal(fft.ifftn(x) * np.sqrt(30 * 20 * 10),
fft.ifftn(x, norm="ortho"))
def test_rfft(self):
x = random(30)
for n in [x.size, 2*x.size]:
for norm in [None, 'ortho']:
assert_array_almost_equal(
fft.fft(x, n=n, norm=norm)[:(n//2 + 1)],
fft.rfft(x, n=n, norm=norm))
assert_array_almost_equal(fft.rfft(x, n=n) / np.sqrt(n),
fft.rfft(x, n=n, norm="ortho"))
def test_irfft(self):
x = random(30)
assert_array_almost_equal(x, fft.irfft(fft.rfft(x)))
assert_array_almost_equal(
x, fft.irfft(fft.rfft(x, norm="ortho"), norm="ortho"))
def test_rfft2(self):
x = random((30, 20))
assert_array_almost_equal(fft.fft2(x)[:, :11], fft.rfft2(x))
assert_array_almost_equal(fft.rfft2(x) / np.sqrt(30 * 20),
fft.rfft2(x, norm="ortho"))
def test_irfft2(self):
x = random((30, 20))
assert_array_almost_equal(x, fft.irfft2(fft.rfft2(x)))
assert_array_almost_equal(
x, fft.irfft2(fft.rfft2(x, norm="ortho"), norm="ortho"))
def test_rfftn(self):
x = random((30, 20, 10))
assert_array_almost_equal(fft.fftn(x)[:, :, :6], fft.rfftn(x))
assert_array_almost_equal(fft.rfftn(x) / np.sqrt(30 * 20 * 10),
fft.rfftn(x, norm="ortho"))
def test_irfftn(self):
x = random((30, 20, 10))
assert_array_almost_equal(x, fft.irfftn(fft.rfftn(x)))
assert_array_almost_equal(
x, fft.irfftn(fft.rfftn(x, norm="ortho"), norm="ortho"))
def test_hfft(self):
x = random(14) + 1j*random(14)
x_herm = np.concatenate((random(1), x, random(1)))
x = np.concatenate((x_herm, x[::-1].conj()))
assert_array_almost_equal(fft.fft(x), fft.hfft(x_herm))
assert_array_almost_equal(fft.hfft(x_herm) / np.sqrt(30),
fft.hfft(x_herm, norm="ortho"))
def test_ihfft(self):
x = random(14) + 1j*random(14)
x_herm = np.concatenate((random(1), x, random(1)))
x = np.concatenate((x_herm, x[::-1].conj()))
assert_array_almost_equal(x_herm, fft.ihfft(fft.hfft(x_herm)))
assert_array_almost_equal(
x_herm, fft.ihfft(fft.hfft(x_herm, norm="ortho"),
norm="ortho"))
def test_hfft2(self):
x = random((30, 20))
assert_array_almost_equal(x, fft.hfft2(fft.ihfft2(x)))
assert_array_almost_equal(
x, fft.hfft2(fft.ihfft2(x, norm="ortho"), norm="ortho"))
def test_ihfft2(self):
x = random((30, 20))
assert_array_almost_equal(fft.ifft2(x)[:, :11], fft.ihfft2(x))
assert_array_almost_equal(fft.ihfft2(x) * np.sqrt(30 * 20),
fft.ihfft2(x, norm="ortho"))
def test_hfftn(self):
x = random((30, 20, 10))
assert_array_almost_equal(x, fft.hfftn(fft.ihfftn(x)))
assert_array_almost_equal(
x, fft.hfftn(fft.ihfftn(x, norm="ortho"), norm="ortho"))
def test_ihfftn(self):
x = random((30, 20, 10))
assert_array_almost_equal(fft.ifftn(x)[:, :, :6], fft.ihfftn(x))
assert_array_almost_equal(fft.ihfftn(x) * np.sqrt(30 * 20 * 10),
fft.ihfftn(x, norm="ortho"))
@pytest.mark.parametrize("op", [fft.fftn, fft.ifftn,
fft.rfftn, fft.irfftn,
fft.hfftn, fft.ihfftn])
def test_axes(self, op):
x = random((30, 20, 10))
axes = [(0, 1, 2), (0, 2, 1), (1, 0, 2), (1, 2, 0), (2, 0, 1), (2, 1, 0)]
for a in axes:
op_tr = op(np.transpose(x, a))
tr_op = np.transpose(op(x, axes=a), a)
assert_array_almost_equal(op_tr, tr_op)
@pytest.mark.parametrize("op", [fft.fft2, fft.ifft2,
fft.rfft2, fft.irfft2,
fft.hfft2, fft.ihfft2,
fft.fftn, fft.ifftn,
fft.rfftn, fft.irfftn,
fft.hfftn, fft.ihfftn])
def test_axes_subset_with_shape(self, op):
x = random((16, 8, 4))
axes = [(0, 1, 2), (0, 2, 1), (1, 2, 0)]
for a in axes:
# different shape on the first two axes
shape = tuple([2*x.shape[ax] if ax in a[:2] else x.shape[ax]
for ax in range(x.ndim)])
# transform only the first two axes
op_tr = op(np.transpose(x, a), s=shape[:2], axes=(0, 1))
tr_op = np.transpose(op(x, s=shape[:2], axes=a[:2]), a)
assert_array_almost_equal(op_tr, tr_op)
def test_all_1d_norm_preserving(self):
# verify that round-trip transforms are norm-preserving
x = random(30)
x_norm = np.linalg.norm(x)
n = x.size * 2
func_pairs = [(fft.fft, fft.ifft),
(fft.rfft, fft.irfft),
# hfft: order so the first function takes x.size samples
# (necessary for comparison to x_norm above)
(fft.ihfft, fft.hfft),
]
for forw, back in func_pairs:
for n in [x.size, 2*x.size]:
for norm in [None, 'ortho']:
tmp = forw(x, n=n, norm=norm)
tmp = back(tmp, n=n, norm=norm)
assert_array_almost_equal(x_norm,
np.linalg.norm(tmp))
@pytest.mark.parametrize("dtype", [np.half, np.single, np.double,
np.longdouble])
def test_dtypes(self, dtype):
# make sure that all input precisions are accepted
x = random(30).astype(dtype)
assert_array_almost_equal(fft.ifft(fft.fft(x)), x)
assert_array_almost_equal(fft.irfft(fft.rfft(x)), x)
assert_array_almost_equal(fft.hfft(fft.ihfft(x), len(x)), x)
@pytest.mark.parametrize(
"dtype",
[np.float32, np.float64, np.longfloat,
np.complex64, np.complex128, np.longcomplex])
@pytest.mark.parametrize("order", ["F", 'non-contiguous'])
@pytest.mark.parametrize(
"fft",
[fft.fft, fft.fft2, fft.fftn,
fft.ifft, fft.ifft2, fft.ifftn])
def test_fft_with_order(dtype, order, fft):
# Check that FFT/IFFT produces identical results for C, Fortran and
# non contiguous arrays
rng = np.random.RandomState(42)
X = rng.rand(8, 7, 13).astype(dtype, copy=False)
if order == 'F':
Y = np.asfortranarray(X)
else:
# Make a non contiguous array
Y = X[::-1]
X = np.ascontiguousarray(X[::-1])
if fft.__name__.endswith('fft'):
for axis in range(3):
X_res = fft(X, axis=axis)
Y_res = fft(Y, axis=axis)
assert_array_almost_equal(X_res, Y_res)
elif fft.__name__.endswith(('fft2', 'fftn')):
axes = [(0, 1), (1, 2), (0, 2)]
if fft.__name__.endswith('fftn'):
axes.extend([(0,), (1,), (2,), None])
for ax in axes:
X_res = fft(X, axes=ax)
Y_res = fft(Y, axes=ax)
assert_array_almost_equal(X_res, Y_res)
else:
raise ValueError
class TestFFTThreadSafe(object):
threads = 16
input_shape = (800, 200)
def _test_mtsame(self, func, *args):
def worker(args, q):
q.put(func(*args))
q = queue.Queue()
expected = func(*args)
# Spin off a bunch of threads to call the same function simultaneously
t = [threading.Thread(target=worker, args=(args, q))
for i in range(self.threads)]
[x.start() for x in t]
[x.join() for x in t]
# Make sure all threads returned the correct value
for i in range(self.threads):
assert_array_equal(q.get(timeout=5), expected,
'Function returned wrong value in multithreaded context')
def test_fft(self):
a = np.ones(self.input_shape, dtype=np.complex128)
self._test_mtsame(fft.fft, a)
def test_ifft(self):
a = np.full(self.input_shape, 1+0j)
self._test_mtsame(fft.ifft, a)
def test_rfft(self):
a = np.ones(self.input_shape)
self._test_mtsame(fft.rfft, a)
def test_irfft(self):
a = np.full(self.input_shape, 1+0j)
self._test_mtsame(fft.irfft, a)
def test_hfft(self):
a = np.ones(self.input_shape, np.complex64)
self._test_mtsame(fft.hfft, a)
def test_ihfft(self):
a = np.ones(self.input_shape)
self._test_mtsame(fft.ihfft, a)
@pytest.mark.parametrize("func", [fft.fft, fft.ifft, fft.rfft, fft.irfft])
def test_multiprocess(func):
# Test that fft still works after fork (gh-10422)
with multiprocessing.Pool(2) as p:
res = p.map(func, [np.ones(100) for _ in range(4)])
expect = func(np.ones(100))
for x in res:
assert_allclose(x, expect)
class TestIRFFTN(object):
def test_not_last_axis_success(self):
ar, ai = np.random.random((2, 16, 8, 32))
a = ar + 1j*ai
axes = (-2,)
# Should not raise error
fft.irfftn(a, axes=axes)

View file

@ -0,0 +1,144 @@
import numpy as np
from numpy.testing import assert_allclose, assert_array_equal
import pytest
from scipy.fft import dct, idct, dctn, idctn, dst, idst, dstn, idstn
import scipy.fft as fft
from scipy import fftpack
# scipy.fft wraps the fftpack versions but with normalized inverse transforms.
# So, the forward transforms and definitions are already thoroughly tested in
# fftpack/test_real_transforms.py
@pytest.mark.parametrize("forward, backward", [(dct, idct), (dst, idst)])
@pytest.mark.parametrize("type", [1, 2, 3, 4])
@pytest.mark.parametrize("n", [2, 3, 4, 5, 10, 16])
@pytest.mark.parametrize("axis", [0, 1])
@pytest.mark.parametrize("norm", [None, 'ortho'])
def test_identity_1d(forward, backward, type, n, axis, norm):
# Test the identity f^-1(f(x)) == x
x = np.random.rand(n, n)
y = forward(x, type, axis=axis, norm=norm)
z = backward(y, type, axis=axis, norm=norm)
assert_allclose(z, x)
pad = [(0, 0)] * 2
pad[axis] = (0, 4)
y2 = np.pad(y, pad, mode='edge')
z2 = backward(y2, type, n, axis, norm)
assert_allclose(z2, x)
@pytest.mark.parametrize("forward, backward", [(dct, idct), (dst, idst)])
@pytest.mark.parametrize("type", [1, 2, 3, 4])
@pytest.mark.parametrize("dtype", [np.float16, np.float32, np.float64,
np.complex64, np.complex128])
@pytest.mark.parametrize("axis", [0, 1])
@pytest.mark.parametrize("norm", [None, 'ortho'])
@pytest.mark.parametrize("overwrite_x", [True, False])
def test_identity_1d_overwrite(forward, backward, type, dtype, axis, norm,
overwrite_x):
# Test the identity f^-1(f(x)) == x
x = np.random.rand(7, 8)
x_orig = x.copy()
y = forward(x, type, axis=axis, norm=norm, overwrite_x=overwrite_x)
y_orig = y.copy()
z = backward(y, type, axis=axis, norm=norm, overwrite_x=overwrite_x)
if not overwrite_x:
assert_allclose(z, x, rtol=1e-6, atol=1e-6)
assert_array_equal(x, x_orig)
assert_array_equal(y, y_orig)
else:
assert_allclose(z, x_orig, rtol=1e-6, atol=1e-6)
@pytest.mark.parametrize("forward, backward", [(dctn, idctn), (dstn, idstn)])
@pytest.mark.parametrize("type", [1, 2, 3, 4])
@pytest.mark.parametrize("shape, axes",
[
((4, 4), 0),
((4, 4), 1),
((4, 4), None),
((4, 4), (0, 1)),
((10, 12), None),
((10, 12), (0, 1)),
((4, 5, 6), None),
((4, 5, 6), 1),
((4, 5, 6), (0, 2)),
])
@pytest.mark.parametrize("norm", [None, 'ortho'])
def test_identity_nd(forward, backward, type, shape, axes, norm):
# Test the identity f^-1(f(x)) == x
x = np.random.random(shape)
if axes is not None:
shape = np.take(shape, axes)
y = forward(x, type, axes=axes, norm=norm)
z = backward(y, type, axes=axes, norm=norm)
assert_allclose(z, x)
if axes is None:
pad = [(0, 4)] * x.ndim
elif isinstance(axes, int):
pad = [(0, 0)] * x.ndim
pad[axes] = (0, 4)
else:
pad = [(0, 0)] * x.ndim
for a in axes:
pad[a] = (0, 4)
y2 = np.pad(y, pad, mode='edge')
z2 = backward(y2, type, shape, axes, norm)
assert_allclose(z2, x)
@pytest.mark.parametrize("forward, backward", [(dctn, idctn), (dstn, idstn)])
@pytest.mark.parametrize("type", [1, 2, 3, 4])
@pytest.mark.parametrize("shape, axes",
[
((4, 5), 0),
((4, 5), 1),
((4, 5), None),
])
@pytest.mark.parametrize("dtype", [np.float16, np.float32, np.float64,
np.complex64, np.complex128])
@pytest.mark.parametrize("norm", [None, 'ortho'])
@pytest.mark.parametrize("overwrite_x", [False, True])
def test_identity_nd_overwrite(forward, backward, type, shape, axes, dtype,
norm, overwrite_x):
# Test the identity f^-1(f(x)) == x
x = np.random.random(shape).astype(dtype)
x_orig = x.copy()
if axes is not None:
shape = np.take(shape, axes)
y = forward(x, type, axes=axes, norm=norm)
y_orig = y.copy()
z = backward(y, type, axes=axes, norm=norm)
if overwrite_x:
assert_allclose(z, x_orig, rtol=1e-6, atol=1e-6)
else:
assert_allclose(z, x, rtol=1e-6, atol=1e-6)
assert_array_equal(x, x_orig)
assert_array_equal(y, y_orig)
@pytest.mark.parametrize("func", ['dct', 'dst', 'dctn', 'dstn'])
@pytest.mark.parametrize("type", [1, 2, 3, 4])
@pytest.mark.parametrize("norm", [None, 'ortho'])
def test_fftpack_equivalience(func, type, norm):
x = np.random.rand(8, 16)
fft_res = getattr(fft, func)(x, type, norm=norm)
fftpack_res = getattr(fftpack, func)(x, type, norm=norm)
assert_allclose(fft_res, fftpack_res)