asd
This commit is contained in:
114
venv/lib/python3.12/site-packages/scipy/fft/__init__.py
Normal file
114
venv/lib/python3.12/site-packages/scipy/fft/__init__.py
Normal file
@ -0,0 +1,114 @@
|
||||
"""
|
||||
==============================================
|
||||
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
|
||||
|
||||
Fast Hankel Transforms
|
||||
======================
|
||||
|
||||
.. autosummary::
|
||||
:toctree: generated/
|
||||
|
||||
fht - Fast Hankel transform
|
||||
ifht - Inverse of fht
|
||||
|
||||
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)
|
||||
fhtoffset - Compute an optimal offset for the Fast Hankel Transform
|
||||
next_fast_len - Find the optimal length to zero-pad an FFT for speed
|
||||
prev_fast_len - Find the maximum slice length that results in a fast FFT
|
||||
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 ._fftlog import fht, ifht, fhtoffset
|
||||
from ._helper import (
|
||||
next_fast_len, prev_fast_len, fftfreq,
|
||||
rfftfreq, fftshift, ifftshift)
|
||||
from ._backend import (set_backend, skip_backend, set_global_backend,
|
||||
register_backend)
|
||||
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', 'prev_fast_len',
|
||||
'dct', 'idct', 'dst', 'idst', 'dctn', 'idctn', 'dstn', 'idstn',
|
||||
'fht', 'ifht',
|
||||
'fhtoffset',
|
||||
'set_backend', 'skip_backend', 'set_global_backend', 'register_backend',
|
||||
'get_workers', 'set_workers']
|
||||
|
||||
|
||||
from scipy._lib._testutils import PytestTester
|
||||
test = PytestTester(__name__)
|
||||
del PytestTester
|
||||
196
venv/lib/python3.12/site-packages/scipy/fft/_backend.py
Normal file
196
venv/lib/python3.12/site-packages/scipy/fft/_backend.py
Normal file
@ -0,0 +1,196 @@
|
||||
import scipy._lib.uarray as ua
|
||||
from . import _basic_backend
|
||||
from . import _realtransforms_backend
|
||||
from . import _fftlog_backend
|
||||
|
||||
|
||||
class _ScipyBackend:
|
||||
"""The default backend for fft calculations
|
||||
|
||||
Notes
|
||||
-----
|
||||
We use the domain ``numpy.scipy`` rather than ``scipy`` because ``uarray``
|
||||
treats 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(_basic_backend, method.__name__, None)
|
||||
if fn is None:
|
||||
fn = getattr(_realtransforms_backend, method.__name__, None)
|
||||
if fn is None:
|
||||
fn = getattr(_fftlog_backend, 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 as e:
|
||||
raise ValueError(f'Unknown backend {backend}') from e
|
||||
|
||||
if backend.__ua_domain__ != 'numpy.scipy.fft':
|
||||
raise ValueError('Backend does not implement "numpy.scipy.fft"')
|
||||
|
||||
return backend
|
||||
|
||||
|
||||
def set_global_backend(backend, coerce=False, only=False, try_last=False):
|
||||
"""Sets the global fft backend
|
||||
|
||||
This utility method replaces the default backend for permanent use. It
|
||||
will be tried in the list of backends automatically, unless the
|
||||
``only`` flag is set on a backend. This will be the first tried
|
||||
backend outside the :obj:`set_backend` context manager.
|
||||
|
||||
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
|
||||
Whether to coerce input types when trying this backend.
|
||||
only : bool
|
||||
If ``True``, no more backends will be tried if this fails.
|
||||
Implied by ``coerce=True``.
|
||||
try_last : bool
|
||||
If ``True``, the global backend is tried after registered backends.
|
||||
|
||||
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 (default is "scipy").
|
||||
>>> fft([1]) # Calls the global backend
|
||||
array([1.+0.j])
|
||||
"""
|
||||
backend = _backend_from_arg(backend)
|
||||
ua.set_global_backend(backend, coerce=coerce, only=only, try_last=try_last)
|
||||
|
||||
|
||||
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
|
||||
# The registered backend is called because
|
||||
# the global backend returns `NotImplemented`
|
||||
>>> fft([1])
|
||||
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', try_last=True)
|
||||
1630
venv/lib/python3.12/site-packages/scipy/fft/_basic.py
Normal file
1630
venv/lib/python3.12/site-packages/scipy/fft/_basic.py
Normal file
File diff suppressed because it is too large
Load Diff
180
venv/lib/python3.12/site-packages/scipy/fft/_basic_backend.py
Normal file
180
venv/lib/python3.12/site-packages/scipy/fft/_basic_backend.py
Normal file
@ -0,0 +1,180 @@
|
||||
from scipy._lib._array_api import (
|
||||
array_namespace, is_numpy, xp_unsupported_param_msg, is_complex
|
||||
)
|
||||
from . import _pocketfft
|
||||
import numpy as np
|
||||
|
||||
|
||||
def _validate_fft_args(workers, plan, norm):
|
||||
if workers is not None:
|
||||
raise ValueError(xp_unsupported_param_msg("workers"))
|
||||
if plan is not None:
|
||||
raise ValueError(xp_unsupported_param_msg("plan"))
|
||||
if norm is None:
|
||||
norm = 'backward'
|
||||
return norm
|
||||
|
||||
|
||||
# pocketfft is used whenever SCIPY_ARRAY_API is not set,
|
||||
# or x is a NumPy array or array-like.
|
||||
# When SCIPY_ARRAY_API is set, we try to use xp.fft for CuPy arrays,
|
||||
# PyTorch arrays and other array API standard supporting objects.
|
||||
# If xp.fft does not exist, we attempt to convert to np and back to use pocketfft.
|
||||
|
||||
def _execute_1D(func_str, pocketfft_func, x, n, axis, norm, overwrite_x, workers, plan):
|
||||
xp = array_namespace(x)
|
||||
|
||||
if is_numpy(xp):
|
||||
x = np.asarray(x)
|
||||
return pocketfft_func(x, n=n, axis=axis, norm=norm,
|
||||
overwrite_x=overwrite_x, workers=workers, plan=plan)
|
||||
|
||||
norm = _validate_fft_args(workers, plan, norm)
|
||||
if hasattr(xp, 'fft'):
|
||||
xp_func = getattr(xp.fft, func_str)
|
||||
return xp_func(x, n=n, axis=axis, norm=norm)
|
||||
|
||||
x = np.asarray(x)
|
||||
y = pocketfft_func(x, n=n, axis=axis, norm=norm)
|
||||
return xp.asarray(y)
|
||||
|
||||
|
||||
def _execute_nD(func_str, pocketfft_func, x, s, axes, norm, overwrite_x, workers, plan):
|
||||
xp = array_namespace(x)
|
||||
|
||||
if is_numpy(xp):
|
||||
x = np.asarray(x)
|
||||
return pocketfft_func(x, s=s, axes=axes, norm=norm,
|
||||
overwrite_x=overwrite_x, workers=workers, plan=plan)
|
||||
|
||||
norm = _validate_fft_args(workers, plan, norm)
|
||||
if hasattr(xp, 'fft'):
|
||||
xp_func = getattr(xp.fft, func_str)
|
||||
return xp_func(x, s=s, axes=axes, norm=norm)
|
||||
|
||||
x = np.asarray(x)
|
||||
y = pocketfft_func(x, s=s, axes=axes, norm=norm)
|
||||
return xp.asarray(y)
|
||||
|
||||
|
||||
def fft(x, n=None, axis=-1, norm=None,
|
||||
overwrite_x=False, workers=None, *, plan=None):
|
||||
return _execute_1D('fft', _pocketfft.fft, x, n=n, axis=axis, norm=norm,
|
||||
overwrite_x=overwrite_x, workers=workers, plan=plan)
|
||||
|
||||
|
||||
def ifft(x, n=None, axis=-1, norm=None, overwrite_x=False, workers=None, *,
|
||||
plan=None):
|
||||
return _execute_1D('ifft', _pocketfft.ifft, x, n=n, axis=axis, norm=norm,
|
||||
overwrite_x=overwrite_x, workers=workers, plan=plan)
|
||||
|
||||
|
||||
def rfft(x, n=None, axis=-1, norm=None,
|
||||
overwrite_x=False, workers=None, *, plan=None):
|
||||
return _execute_1D('rfft', _pocketfft.rfft, x, n=n, axis=axis, norm=norm,
|
||||
overwrite_x=overwrite_x, workers=workers, plan=plan)
|
||||
|
||||
|
||||
def irfft(x, n=None, axis=-1, norm=None,
|
||||
overwrite_x=False, workers=None, *, plan=None):
|
||||
return _execute_1D('irfft', _pocketfft.irfft, x, n=n, axis=axis, norm=norm,
|
||||
overwrite_x=overwrite_x, workers=workers, plan=plan)
|
||||
|
||||
|
||||
def hfft(x, n=None, axis=-1, norm=None,
|
||||
overwrite_x=False, workers=None, *, plan=None):
|
||||
return _execute_1D('hfft', _pocketfft.hfft, x, n=n, axis=axis, norm=norm,
|
||||
overwrite_x=overwrite_x, workers=workers, plan=plan)
|
||||
|
||||
|
||||
def ihfft(x, n=None, axis=-1, norm=None,
|
||||
overwrite_x=False, workers=None, *, plan=None):
|
||||
return _execute_1D('ihfft', _pocketfft.ihfft, x, n=n, axis=axis, norm=norm,
|
||||
overwrite_x=overwrite_x, workers=workers, plan=plan)
|
||||
|
||||
|
||||
def fftn(x, s=None, axes=None, norm=None,
|
||||
overwrite_x=False, workers=None, *, plan=None):
|
||||
return _execute_nD('fftn', _pocketfft.fftn, x, s=s, axes=axes, norm=norm,
|
||||
overwrite_x=overwrite_x, workers=workers, plan=plan)
|
||||
|
||||
|
||||
|
||||
def ifftn(x, s=None, axes=None, norm=None,
|
||||
overwrite_x=False, workers=None, *, plan=None):
|
||||
return _execute_nD('ifftn', _pocketfft.ifftn, x, s=s, axes=axes, norm=norm,
|
||||
overwrite_x=overwrite_x, workers=workers, plan=plan)
|
||||
|
||||
|
||||
def fft2(x, s=None, axes=(-2, -1), norm=None,
|
||||
overwrite_x=False, workers=None, *, plan=None):
|
||||
return fftn(x, s, axes, norm, overwrite_x, workers, plan=plan)
|
||||
|
||||
|
||||
def ifft2(x, s=None, axes=(-2, -1), norm=None,
|
||||
overwrite_x=False, workers=None, *, plan=None):
|
||||
return ifftn(x, s, axes, norm, overwrite_x, workers, plan=plan)
|
||||
|
||||
|
||||
def rfftn(x, s=None, axes=None, norm=None,
|
||||
overwrite_x=False, workers=None, *, plan=None):
|
||||
return _execute_nD('rfftn', _pocketfft.rfftn, x, s=s, axes=axes, norm=norm,
|
||||
overwrite_x=overwrite_x, workers=workers, plan=plan)
|
||||
|
||||
|
||||
def rfft2(x, s=None, axes=(-2, -1), norm=None,
|
||||
overwrite_x=False, workers=None, *, plan=None):
|
||||
return rfftn(x, s, axes, norm, overwrite_x, workers, plan=plan)
|
||||
|
||||
|
||||
def irfftn(x, s=None, axes=None, norm=None,
|
||||
overwrite_x=False, workers=None, *, plan=None):
|
||||
return _execute_nD('irfftn', _pocketfft.irfftn, x, s=s, axes=axes, norm=norm,
|
||||
overwrite_x=overwrite_x, workers=workers, plan=plan)
|
||||
|
||||
|
||||
def irfft2(x, s=None, axes=(-2, -1), norm=None,
|
||||
overwrite_x=False, workers=None, *, plan=None):
|
||||
return irfftn(x, s, axes, norm, overwrite_x, workers, plan=plan)
|
||||
|
||||
|
||||
def _swap_direction(norm):
|
||||
if norm in (None, 'backward'):
|
||||
norm = 'forward'
|
||||
elif norm == 'forward':
|
||||
norm = 'backward'
|
||||
elif norm != 'ortho':
|
||||
raise ValueError('Invalid norm value %s; should be "backward", '
|
||||
'"ortho", or "forward".' % norm)
|
||||
return norm
|
||||
|
||||
|
||||
def hfftn(x, s=None, axes=None, norm=None,
|
||||
overwrite_x=False, workers=None, *, plan=None):
|
||||
xp = array_namespace(x)
|
||||
if is_numpy(xp):
|
||||
x = np.asarray(x)
|
||||
return _pocketfft.hfftn(x, s, axes, norm, overwrite_x, workers, plan=plan)
|
||||
if is_complex(x, xp):
|
||||
x = xp.conj(x)
|
||||
return irfftn(x, s, axes, _swap_direction(norm),
|
||||
overwrite_x, workers, plan=plan)
|
||||
|
||||
|
||||
def hfft2(x, s=None, axes=(-2, -1), norm=None,
|
||||
overwrite_x=False, workers=None, *, plan=None):
|
||||
return hfftn(x, s, axes, norm, overwrite_x, workers, plan=plan)
|
||||
|
||||
|
||||
def ihfftn(x, s=None, axes=None, norm=None,
|
||||
overwrite_x=False, workers=None, *, plan=None):
|
||||
xp = array_namespace(x)
|
||||
if is_numpy(xp):
|
||||
x = np.asarray(x)
|
||||
return _pocketfft.ihfftn(x, s, axes, norm, overwrite_x, workers, plan=plan)
|
||||
return xp.conj(rfftn(x, s, axes, _swap_direction(norm),
|
||||
overwrite_x, workers, plan=plan))
|
||||
|
||||
def ihfft2(x, s=None, axes=(-2, -1), norm=None,
|
||||
overwrite_x=False, workers=None, *, plan=None):
|
||||
return ihfftn(x, s, axes, norm, overwrite_x, workers, plan=plan)
|
||||
@ -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')
|
||||
223
venv/lib/python3.12/site-packages/scipy/fft/_fftlog.py
Normal file
223
venv/lib/python3.12/site-packages/scipy/fft/_fftlog.py
Normal file
@ -0,0 +1,223 @@
|
||||
"""Fast Hankel transforms using the FFTLog algorithm.
|
||||
|
||||
The implementation closely follows the Fortran code of Hamilton (2000).
|
||||
|
||||
added: 14/11/2020 Nicolas Tessore <n.tessore@ucl.ac.uk>
|
||||
"""
|
||||
|
||||
from ._basic import _dispatch
|
||||
from scipy._lib.uarray import Dispatchable
|
||||
from ._fftlog_backend import fhtoffset
|
||||
import numpy as np
|
||||
|
||||
__all__ = ['fht', 'ifht', 'fhtoffset']
|
||||
|
||||
|
||||
@_dispatch
|
||||
def fht(a, dln, mu, offset=0.0, bias=0.0):
|
||||
r'''Compute the fast Hankel transform.
|
||||
|
||||
Computes the discrete Hankel transform of a logarithmically spaced periodic
|
||||
sequence using the FFTLog algorithm [1]_, [2]_.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
a : array_like (..., n)
|
||||
Real periodic input array, uniformly logarithmically spaced. For
|
||||
multidimensional input, the transform is performed over the last axis.
|
||||
dln : float
|
||||
Uniform logarithmic spacing of the input array.
|
||||
mu : float
|
||||
Order of the Hankel transform, any positive or negative real number.
|
||||
offset : float, optional
|
||||
Offset of the uniform logarithmic spacing of the output array.
|
||||
bias : float, optional
|
||||
Exponent of power law bias, any positive or negative real number.
|
||||
|
||||
Returns
|
||||
-------
|
||||
A : array_like (..., n)
|
||||
The transformed output array, which is real, periodic, uniformly
|
||||
logarithmically spaced, and of the same shape as the input array.
|
||||
|
||||
See Also
|
||||
--------
|
||||
ifht : The inverse of `fht`.
|
||||
fhtoffset : Return an optimal offset for `fht`.
|
||||
|
||||
Notes
|
||||
-----
|
||||
This function computes a discrete version of the Hankel transform
|
||||
|
||||
.. math::
|
||||
|
||||
A(k) = \int_{0}^{\infty} \! a(r) \, J_\mu(kr) \, k \, dr \;,
|
||||
|
||||
where :math:`J_\mu` is the Bessel function of order :math:`\mu`. The index
|
||||
:math:`\mu` may be any real number, positive or negative. Note that the
|
||||
numerical Hankel transform uses an integrand of :math:`k \, dr`, while the
|
||||
mathematical Hankel transform is commonly defined using :math:`r \, dr`.
|
||||
|
||||
The input array `a` is a periodic sequence of length :math:`n`, uniformly
|
||||
logarithmically spaced with spacing `dln`,
|
||||
|
||||
.. math::
|
||||
|
||||
a_j = a(r_j) \;, \quad
|
||||
r_j = r_c \exp[(j-j_c) \, \mathtt{dln}]
|
||||
|
||||
centred about the point :math:`r_c`. Note that the central index
|
||||
:math:`j_c = (n-1)/2` is half-integral if :math:`n` is even, so that
|
||||
:math:`r_c` falls between two input elements. Similarly, the output
|
||||
array `A` is a periodic sequence of length :math:`n`, also uniformly
|
||||
logarithmically spaced with spacing `dln`
|
||||
|
||||
.. math::
|
||||
|
||||
A_j = A(k_j) \;, \quad
|
||||
k_j = k_c \exp[(j-j_c) \, \mathtt{dln}]
|
||||
|
||||
centred about the point :math:`k_c`.
|
||||
|
||||
The centre points :math:`r_c` and :math:`k_c` of the periodic intervals may
|
||||
be chosen arbitrarily, but it would be usual to choose the product
|
||||
:math:`k_c r_c = k_j r_{n-1-j} = k_{n-1-j} r_j` to be unity. This can be
|
||||
changed using the `offset` parameter, which controls the logarithmic offset
|
||||
:math:`\log(k_c) = \mathtt{offset} - \log(r_c)` of the output array.
|
||||
Choosing an optimal value for `offset` may reduce ringing of the discrete
|
||||
Hankel transform.
|
||||
|
||||
If the `bias` parameter is nonzero, this function computes a discrete
|
||||
version of the biased Hankel transform
|
||||
|
||||
.. math::
|
||||
|
||||
A(k) = \int_{0}^{\infty} \! a_q(r) \, (kr)^q \, J_\mu(kr) \, k \, dr
|
||||
|
||||
where :math:`q` is the value of `bias`, and a power law bias
|
||||
:math:`a_q(r) = a(r) \, (kr)^{-q}` is applied to the input sequence.
|
||||
Biasing the transform can help approximate the continuous transform of
|
||||
:math:`a(r)` if there is a value :math:`q` such that :math:`a_q(r)` is
|
||||
close to a periodic sequence, in which case the resulting :math:`A(k)` will
|
||||
be close to the continuous transform.
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] Talman J. D., 1978, J. Comp. Phys., 29, 35
|
||||
.. [2] Hamilton A. J. S., 2000, MNRAS, 312, 257 (astro-ph/9905191)
|
||||
|
||||
Examples
|
||||
--------
|
||||
|
||||
This example is the adapted version of ``fftlogtest.f`` which is provided
|
||||
in [2]_. It evaluates the integral
|
||||
|
||||
.. math::
|
||||
|
||||
\int^\infty_0 r^{\mu+1} \exp(-r^2/2) J_\mu(k, r) k dr
|
||||
= k^{\mu+1} \exp(-k^2/2) .
|
||||
|
||||
>>> import numpy as np
|
||||
>>> from scipy import fft
|
||||
>>> import matplotlib.pyplot as plt
|
||||
|
||||
Parameters for the transform.
|
||||
|
||||
>>> mu = 0.0 # Order mu of Bessel function
|
||||
>>> r = np.logspace(-7, 1, 128) # Input evaluation points
|
||||
>>> dln = np.log(r[1]/r[0]) # Step size
|
||||
>>> offset = fft.fhtoffset(dln, initial=-6*np.log(10), mu=mu)
|
||||
>>> k = np.exp(offset)/r[::-1] # Output evaluation points
|
||||
|
||||
Define the analytical function.
|
||||
|
||||
>>> def f(x, mu):
|
||||
... """Analytical function: x^(mu+1) exp(-x^2/2)."""
|
||||
... return x**(mu + 1)*np.exp(-x**2/2)
|
||||
|
||||
Evaluate the function at ``r`` and compute the corresponding values at
|
||||
``k`` using FFTLog.
|
||||
|
||||
>>> a_r = f(r, mu)
|
||||
>>> fht = fft.fht(a_r, dln, mu=mu, offset=offset)
|
||||
|
||||
For this example we can actually compute the analytical response (which in
|
||||
this case is the same as the input function) for comparison and compute the
|
||||
relative error.
|
||||
|
||||
>>> a_k = f(k, mu)
|
||||
>>> rel_err = abs((fht-a_k)/a_k)
|
||||
|
||||
Plot the result.
|
||||
|
||||
>>> figargs = {'sharex': True, 'sharey': True, 'constrained_layout': True}
|
||||
>>> fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(10, 4), **figargs)
|
||||
>>> ax1.set_title(r'$r^{\mu+1}\ \exp(-r^2/2)$')
|
||||
>>> ax1.loglog(r, a_r, 'k', lw=2)
|
||||
>>> ax1.set_xlabel('r')
|
||||
>>> ax2.set_title(r'$k^{\mu+1} \exp(-k^2/2)$')
|
||||
>>> ax2.loglog(k, a_k, 'k', lw=2, label='Analytical')
|
||||
>>> ax2.loglog(k, fht, 'C3--', lw=2, label='FFTLog')
|
||||
>>> ax2.set_xlabel('k')
|
||||
>>> ax2.legend(loc=3, framealpha=1)
|
||||
>>> ax2.set_ylim([1e-10, 1e1])
|
||||
>>> ax2b = ax2.twinx()
|
||||
>>> ax2b.loglog(k, rel_err, 'C0', label='Rel. Error (-)')
|
||||
>>> ax2b.set_ylabel('Rel. Error (-)', color='C0')
|
||||
>>> ax2b.tick_params(axis='y', labelcolor='C0')
|
||||
>>> ax2b.legend(loc=4, framealpha=1)
|
||||
>>> ax2b.set_ylim([1e-9, 1e-3])
|
||||
>>> plt.show()
|
||||
|
||||
'''
|
||||
return (Dispatchable(a, np.ndarray),)
|
||||
|
||||
|
||||
@_dispatch
|
||||
def ifht(A, dln, mu, offset=0.0, bias=0.0):
|
||||
r"""Compute the inverse fast Hankel transform.
|
||||
|
||||
Computes the discrete inverse Hankel transform of a logarithmically spaced
|
||||
periodic sequence. This is the inverse operation to `fht`.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
A : array_like (..., n)
|
||||
Real periodic input array, uniformly logarithmically spaced. For
|
||||
multidimensional input, the transform is performed over the last axis.
|
||||
dln : float
|
||||
Uniform logarithmic spacing of the input array.
|
||||
mu : float
|
||||
Order of the Hankel transform, any positive or negative real number.
|
||||
offset : float, optional
|
||||
Offset of the uniform logarithmic spacing of the output array.
|
||||
bias : float, optional
|
||||
Exponent of power law bias, any positive or negative real number.
|
||||
|
||||
Returns
|
||||
-------
|
||||
a : array_like (..., n)
|
||||
The transformed output array, which is real, periodic, uniformly
|
||||
logarithmically spaced, and of the same shape as the input array.
|
||||
|
||||
See Also
|
||||
--------
|
||||
fht : Definition of the fast Hankel transform.
|
||||
fhtoffset : Return an optimal offset for `ifht`.
|
||||
|
||||
Notes
|
||||
-----
|
||||
This function computes a discrete version of the Hankel transform
|
||||
|
||||
.. math::
|
||||
|
||||
a(r) = \int_{0}^{\infty} \! A(k) \, J_\mu(kr) \, r \, dk \;,
|
||||
|
||||
where :math:`J_\mu` is the Bessel function of order :math:`\mu`. The index
|
||||
:math:`\mu` may be any real number, positive or negative. Note that the
|
||||
numerical inverse Hankel transform uses an integrand of :math:`r \, dk`, while the
|
||||
mathematical inverse Hankel transform is commonly defined using :math:`k \, dk`.
|
||||
|
||||
See `fht` for further details.
|
||||
"""
|
||||
return (Dispatchable(A, np.ndarray),)
|
||||
199
venv/lib/python3.12/site-packages/scipy/fft/_fftlog_backend.py
Normal file
199
venv/lib/python3.12/site-packages/scipy/fft/_fftlog_backend.py
Normal file
@ -0,0 +1,199 @@
|
||||
import numpy as np
|
||||
from warnings import warn
|
||||
from ._basic import rfft, irfft
|
||||
from ..special import loggamma, poch
|
||||
|
||||
from scipy._lib._array_api import array_namespace, copy
|
||||
|
||||
__all__ = ['fht', 'ifht', 'fhtoffset']
|
||||
|
||||
# constants
|
||||
LN_2 = np.log(2)
|
||||
|
||||
|
||||
def fht(a, dln, mu, offset=0.0, bias=0.0):
|
||||
xp = array_namespace(a)
|
||||
a = xp.asarray(a)
|
||||
|
||||
# size of transform
|
||||
n = a.shape[-1]
|
||||
|
||||
# bias input array
|
||||
if bias != 0:
|
||||
# a_q(r) = a(r) (r/r_c)^{-q}
|
||||
j_c = (n-1)/2
|
||||
j = xp.arange(n, dtype=xp.float64)
|
||||
a = a * xp.exp(-bias*(j - j_c)*dln)
|
||||
|
||||
# compute FHT coefficients
|
||||
u = xp.asarray(fhtcoeff(n, dln, mu, offset=offset, bias=bias))
|
||||
|
||||
# transform
|
||||
A = _fhtq(a, u, xp=xp)
|
||||
|
||||
# bias output array
|
||||
if bias != 0:
|
||||
# A(k) = A_q(k) (k/k_c)^{-q} (k_c r_c)^{-q}
|
||||
A *= xp.exp(-bias*((j - j_c)*dln + offset))
|
||||
|
||||
return A
|
||||
|
||||
|
||||
def ifht(A, dln, mu, offset=0.0, bias=0.0):
|
||||
xp = array_namespace(A)
|
||||
A = xp.asarray(A)
|
||||
|
||||
# size of transform
|
||||
n = A.shape[-1]
|
||||
|
||||
# bias input array
|
||||
if bias != 0:
|
||||
# A_q(k) = A(k) (k/k_c)^{q} (k_c r_c)^{q}
|
||||
j_c = (n-1)/2
|
||||
j = xp.arange(n, dtype=xp.float64)
|
||||
A = A * xp.exp(bias*((j - j_c)*dln + offset))
|
||||
|
||||
# compute FHT coefficients
|
||||
u = xp.asarray(fhtcoeff(n, dln, mu, offset=offset, bias=bias, inverse=True))
|
||||
|
||||
# transform
|
||||
a = _fhtq(A, u, inverse=True, xp=xp)
|
||||
|
||||
# bias output array
|
||||
if bias != 0:
|
||||
# a(r) = a_q(r) (r/r_c)^{q}
|
||||
a /= xp.exp(-bias*(j - j_c)*dln)
|
||||
|
||||
return a
|
||||
|
||||
|
||||
def fhtcoeff(n, dln, mu, offset=0.0, bias=0.0, inverse=False):
|
||||
"""Compute the coefficient array for a fast Hankel transform."""
|
||||
lnkr, q = offset, bias
|
||||
|
||||
# Hankel transform coefficients
|
||||
# u_m = (kr)^{-i 2m pi/(n dlnr)} U_mu(q + i 2m pi/(n dlnr))
|
||||
# with U_mu(x) = 2^x Gamma((mu+1+x)/2)/Gamma((mu+1-x)/2)
|
||||
xp = (mu+1+q)/2
|
||||
xm = (mu+1-q)/2
|
||||
y = np.linspace(0, np.pi*(n//2)/(n*dln), n//2+1)
|
||||
u = np.empty(n//2+1, dtype=complex)
|
||||
v = np.empty(n//2+1, dtype=complex)
|
||||
u.imag[:] = y
|
||||
u.real[:] = xm
|
||||
loggamma(u, out=v)
|
||||
u.real[:] = xp
|
||||
loggamma(u, out=u)
|
||||
y *= 2*(LN_2 - lnkr)
|
||||
u.real -= v.real
|
||||
u.real += LN_2*q
|
||||
u.imag += v.imag
|
||||
u.imag += y
|
||||
np.exp(u, out=u)
|
||||
|
||||
# fix last coefficient to be real
|
||||
u.imag[-1] = 0
|
||||
|
||||
# deal with special cases
|
||||
if not np.isfinite(u[0]):
|
||||
# write u_0 = 2^q Gamma(xp)/Gamma(xm) = 2^q poch(xm, xp-xm)
|
||||
# poch() handles special cases for negative integers correctly
|
||||
u[0] = 2**q * poch(xm, xp-xm)
|
||||
# the coefficient may be inf or 0, meaning the transform or the
|
||||
# inverse transform, respectively, is singular
|
||||
|
||||
# check for singular transform or singular inverse transform
|
||||
if np.isinf(u[0]) and not inverse:
|
||||
warn('singular transform; consider changing the bias', stacklevel=3)
|
||||
# fix coefficient to obtain (potentially correct) transform anyway
|
||||
u = copy(u)
|
||||
u[0] = 0
|
||||
elif u[0] == 0 and inverse:
|
||||
warn('singular inverse transform; consider changing the bias', stacklevel=3)
|
||||
# fix coefficient to obtain (potentially correct) inverse anyway
|
||||
u = copy(u)
|
||||
u[0] = np.inf
|
||||
|
||||
return u
|
||||
|
||||
|
||||
def fhtoffset(dln, mu, initial=0.0, bias=0.0):
|
||||
"""Return optimal offset for a fast Hankel transform.
|
||||
|
||||
Returns an offset close to `initial` that fulfils the low-ringing
|
||||
condition of [1]_ for the fast Hankel transform `fht` with logarithmic
|
||||
spacing `dln`, order `mu` and bias `bias`.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
dln : float
|
||||
Uniform logarithmic spacing of the transform.
|
||||
mu : float
|
||||
Order of the Hankel transform, any positive or negative real number.
|
||||
initial : float, optional
|
||||
Initial value for the offset. Returns the closest value that fulfils
|
||||
the low-ringing condition.
|
||||
bias : float, optional
|
||||
Exponent of power law bias, any positive or negative real number.
|
||||
|
||||
Returns
|
||||
-------
|
||||
offset : float
|
||||
Optimal offset of the uniform logarithmic spacing of the transform that
|
||||
fulfils a low-ringing condition.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> from scipy.fft import fhtoffset
|
||||
>>> dln = 0.1
|
||||
>>> mu = 2.0
|
||||
>>> initial = 0.5
|
||||
>>> bias = 0.0
|
||||
>>> offset = fhtoffset(dln, mu, initial, bias)
|
||||
>>> offset
|
||||
0.5454581477676637
|
||||
|
||||
See Also
|
||||
--------
|
||||
fht : Definition of the fast Hankel transform.
|
||||
|
||||
References
|
||||
----------
|
||||
.. [1] Hamilton A. J. S., 2000, MNRAS, 312, 257 (astro-ph/9905191)
|
||||
|
||||
"""
|
||||
|
||||
lnkr, q = initial, bias
|
||||
|
||||
xp = (mu+1+q)/2
|
||||
xm = (mu+1-q)/2
|
||||
y = np.pi/(2*dln)
|
||||
zp = loggamma(xp + 1j*y)
|
||||
zm = loggamma(xm + 1j*y)
|
||||
arg = (LN_2 - lnkr)/dln + (zp.imag + zm.imag)/np.pi
|
||||
return lnkr + (arg - np.round(arg))*dln
|
||||
|
||||
|
||||
def _fhtq(a, u, inverse=False, *, xp=None):
|
||||
"""Compute the biased fast Hankel transform.
|
||||
|
||||
This is the basic FFTLog routine.
|
||||
"""
|
||||
if xp is None:
|
||||
xp = np
|
||||
|
||||
# size of transform
|
||||
n = a.shape[-1]
|
||||
|
||||
# biased fast Hankel transform via real FFT
|
||||
A = rfft(a, axis=-1)
|
||||
if not inverse:
|
||||
# forward transform
|
||||
A *= u
|
||||
else:
|
||||
# backward transform
|
||||
A /= xp.conj(u)
|
||||
A = irfft(A, n, axis=-1)
|
||||
A = xp.flip(A, axis=-1)
|
||||
|
||||
return A
|
||||
379
venv/lib/python3.12/site-packages/scipy/fft/_helper.py
Normal file
379
venv/lib/python3.12/site-packages/scipy/fft/_helper.py
Normal file
@ -0,0 +1,379 @@
|
||||
from functools import update_wrapper, lru_cache
|
||||
import inspect
|
||||
|
||||
from ._pocketfft import helper as _helper
|
||||
|
||||
import numpy as np
|
||||
from scipy._lib._array_api import array_namespace
|
||||
|
||||
|
||||
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 11.4 ms:
|
||||
|
||||
>>> from scipy import fft
|
||||
>>> import numpy as np
|
||||
>>> rng = np.random.default_rng()
|
||||
>>> min_len = 93059 # prime length is worst case for speed
|
||||
>>> a = rng.standard_normal(min_len)
|
||||
>>> b = fft.fft(a)
|
||||
|
||||
Zero-padding to the next regular length reduces computation time to
|
||||
1.6 ms, a speedup of 7.3 times:
|
||||
|
||||
>>> fft.next_fast_len(min_len, real=True)
|
||||
93312
|
||||
>>> b = fft.fft(a, 93312)
|
||||
|
||||
Rounding up to the next power of 2 is not optimal, taking 3.0 ms to
|
||||
compute; 1.9 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
|
||||
_sig = inspect.signature(next_fast_len)
|
||||
next_fast_len = update_wrapper(lru_cache(_helper.good_size), next_fast_len)
|
||||
next_fast_len.__wrapped__ = _helper.good_size
|
||||
next_fast_len.__signature__ = _sig
|
||||
|
||||
|
||||
def prev_fast_len(target, real=False):
|
||||
"""Find the previous fast size of input data to ``fft``.
|
||||
Useful for discarding a minimal number of samples before FFT.
|
||||
|
||||
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
|
||||
Maximum length to search until. 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 largest fast length less 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.
|
||||
|
||||
In the current implementation, prev_fast_len assumes radices of
|
||||
2,3,5,7,11 for complex FFT and 2,3,5 for real FFT.
|
||||
|
||||
Examples
|
||||
--------
|
||||
On a particular machine, an FFT of prime length takes 16.2 ms:
|
||||
|
||||
>>> from scipy import fft
|
||||
>>> import numpy as np
|
||||
>>> rng = np.random.default_rng()
|
||||
>>> max_len = 93059 # prime length is worst case for speed
|
||||
>>> a = rng.standard_normal(max_len)
|
||||
>>> b = fft.fft(a)
|
||||
|
||||
Performing FFT on the maximum fast length less than max_len
|
||||
reduces the computation time to 1.5 ms, a speedup of 10.5 times:
|
||||
|
||||
>>> fft.prev_fast_len(max_len, real=True)
|
||||
92160
|
||||
>>> c = fft.fft(a[:92160]) # discard last 899 samples
|
||||
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
# Directly wrap the c-function prev_good_size but take the docstring etc.,
|
||||
# from the prev_fast_len function above
|
||||
_sig_prev_fast_len = inspect.signature(prev_fast_len)
|
||||
prev_fast_len = update_wrapper(lru_cache()(_helper.prev_good_size), prev_fast_len)
|
||||
prev_fast_len.__wrapped__ = _helper.prev_good_size
|
||||
prev_fast_len.__signature__ = _sig_prev_fast_len
|
||||
|
||||
|
||||
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 ``numpy.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 : tuple
|
||||
The shape of the result as a tuple of integers.
|
||||
axes : list
|
||||
Axes along which the calculation is computed, as a list of integers.
|
||||
|
||||
"""
|
||||
x = np.asarray(x)
|
||||
return _helper._init_nd_shape_and_axes(x, shape, axes)
|
||||
|
||||
|
||||
def fftfreq(n, d=1.0, *, xp=None, device=None):
|
||||
"""Return the Discrete Fourier Transform sample frequencies.
|
||||
|
||||
The returned float array `f` contains the frequency bin centers in cycles
|
||||
per unit of the sample spacing (with zero at the start). For instance, if
|
||||
the sample spacing is in seconds, then the frequency unit is cycles/second.
|
||||
|
||||
Given a window length `n` and a sample spacing `d`::
|
||||
|
||||
f = [0, 1, ..., n/2-1, -n/2, ..., -1] / (d*n) if n is even
|
||||
f = [0, 1, ..., (n-1)/2, -(n-1)/2, ..., -1] / (d*n) if n is odd
|
||||
|
||||
Parameters
|
||||
----------
|
||||
n : int
|
||||
Window length.
|
||||
d : scalar, optional
|
||||
Sample spacing (inverse of the sampling rate). Defaults to 1.
|
||||
xp : array_namespace, optional
|
||||
The namespace for the return array. Default is None, where NumPy is used.
|
||||
device : device, optional
|
||||
The device for the return array.
|
||||
Only valid when `xp.fft.fftfreq` implements the device parameter.
|
||||
|
||||
Returns
|
||||
-------
|
||||
f : ndarray
|
||||
Array of length `n` containing the sample frequencies.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> import numpy as np
|
||||
>>> import scipy.fft
|
||||
>>> signal = np.array([-2, 8, 6, 4, 1, 0, 3, 5], dtype=float)
|
||||
>>> fourier = scipy.fft.fft(signal)
|
||||
>>> n = signal.size
|
||||
>>> timestep = 0.1
|
||||
>>> freq = scipy.fft.fftfreq(n, d=timestep)
|
||||
>>> freq
|
||||
array([ 0. , 1.25, 2.5 , ..., -3.75, -2.5 , -1.25])
|
||||
|
||||
"""
|
||||
xp = np if xp is None else xp
|
||||
# numpy does not yet support the `device` keyword
|
||||
# `xp.__name__ != 'numpy'` should be removed when numpy is compatible
|
||||
if hasattr(xp, 'fft') and xp.__name__ != 'numpy':
|
||||
return xp.fft.fftfreq(n, d=d, device=device)
|
||||
if device is not None:
|
||||
raise ValueError('device parameter is not supported for input array type')
|
||||
return np.fft.fftfreq(n, d=d)
|
||||
|
||||
|
||||
def rfftfreq(n, d=1.0, *, xp=None, device=None):
|
||||
"""Return the Discrete Fourier Transform sample frequencies
|
||||
(for usage with rfft, irfft).
|
||||
|
||||
The returned float array `f` contains the frequency bin centers in cycles
|
||||
per unit of the sample spacing (with zero at the start). For instance, if
|
||||
the sample spacing is in seconds, then the frequency unit is cycles/second.
|
||||
|
||||
Given a window length `n` and a sample spacing `d`::
|
||||
|
||||
f = [0, 1, ..., n/2-1, n/2] / (d*n) if n is even
|
||||
f = [0, 1, ..., (n-1)/2-1, (n-1)/2] / (d*n) if n is odd
|
||||
|
||||
Unlike `fftfreq` (but like `scipy.fftpack.rfftfreq`)
|
||||
the Nyquist frequency component is considered to be positive.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
n : int
|
||||
Window length.
|
||||
d : scalar, optional
|
||||
Sample spacing (inverse of the sampling rate). Defaults to 1.
|
||||
xp : array_namespace, optional
|
||||
The namespace for the return array. Default is None, where NumPy is used.
|
||||
device : device, optional
|
||||
The device for the return array.
|
||||
Only valid when `xp.fft.rfftfreq` implements the device parameter.
|
||||
|
||||
Returns
|
||||
-------
|
||||
f : ndarray
|
||||
Array of length ``n//2 + 1`` containing the sample frequencies.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> import numpy as np
|
||||
>>> import scipy.fft
|
||||
>>> signal = np.array([-2, 8, 6, 4, 1, 0, 3, 5, -3, 4], dtype=float)
|
||||
>>> fourier = scipy.fft.rfft(signal)
|
||||
>>> n = signal.size
|
||||
>>> sample_rate = 100
|
||||
>>> freq = scipy.fft.fftfreq(n, d=1./sample_rate)
|
||||
>>> freq
|
||||
array([ 0., 10., 20., ..., -30., -20., -10.])
|
||||
>>> freq = scipy.fft.rfftfreq(n, d=1./sample_rate)
|
||||
>>> freq
|
||||
array([ 0., 10., 20., 30., 40., 50.])
|
||||
|
||||
"""
|
||||
xp = np if xp is None else xp
|
||||
# numpy does not yet support the `device` keyword
|
||||
# `xp.__name__ != 'numpy'` should be removed when numpy is compatible
|
||||
if hasattr(xp, 'fft') and xp.__name__ != 'numpy':
|
||||
return xp.fft.rfftfreq(n, d=d, device=device)
|
||||
if device is not None:
|
||||
raise ValueError('device parameter is not supported for input array type')
|
||||
return np.fft.rfftfreq(n, d=d)
|
||||
|
||||
|
||||
def fftshift(x, axes=None):
|
||||
"""Shift the zero-frequency component to the center of the spectrum.
|
||||
|
||||
This function swaps half-spaces for all axes listed (defaults to all).
|
||||
Note that ``y[0]`` is the Nyquist component only if ``len(x)`` is even.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
x : array_like
|
||||
Input array.
|
||||
axes : int or shape tuple, optional
|
||||
Axes over which to shift. Default is None, which shifts all axes.
|
||||
|
||||
Returns
|
||||
-------
|
||||
y : ndarray
|
||||
The shifted array.
|
||||
|
||||
See Also
|
||||
--------
|
||||
ifftshift : The inverse of `fftshift`.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> import numpy as np
|
||||
>>> freqs = np.fft.fftfreq(10, 0.1)
|
||||
>>> freqs
|
||||
array([ 0., 1., 2., ..., -3., -2., -1.])
|
||||
>>> np.fft.fftshift(freqs)
|
||||
array([-5., -4., -3., -2., -1., 0., 1., 2., 3., 4.])
|
||||
|
||||
Shift the zero-frequency component only along the second axis:
|
||||
|
||||
>>> freqs = np.fft.fftfreq(9, d=1./9).reshape(3, 3)
|
||||
>>> freqs
|
||||
array([[ 0., 1., 2.],
|
||||
[ 3., 4., -4.],
|
||||
[-3., -2., -1.]])
|
||||
>>> np.fft.fftshift(freqs, axes=(1,))
|
||||
array([[ 2., 0., 1.],
|
||||
[-4., 3., 4.],
|
||||
[-1., -3., -2.]])
|
||||
|
||||
"""
|
||||
xp = array_namespace(x)
|
||||
if hasattr(xp, 'fft'):
|
||||
return xp.fft.fftshift(x, axes=axes)
|
||||
x = np.asarray(x)
|
||||
y = np.fft.fftshift(x, axes=axes)
|
||||
return xp.asarray(y)
|
||||
|
||||
|
||||
def ifftshift(x, axes=None):
|
||||
"""The inverse of `fftshift`. Although identical for even-length `x`, the
|
||||
functions differ by one sample for odd-length `x`.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
x : array_like
|
||||
Input array.
|
||||
axes : int or shape tuple, optional
|
||||
Axes over which to calculate. Defaults to None, which shifts all axes.
|
||||
|
||||
Returns
|
||||
-------
|
||||
y : ndarray
|
||||
The shifted array.
|
||||
|
||||
See Also
|
||||
--------
|
||||
fftshift : Shift zero-frequency component to the center of the spectrum.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> import numpy as np
|
||||
>>> freqs = np.fft.fftfreq(9, d=1./9).reshape(3, 3)
|
||||
>>> freqs
|
||||
array([[ 0., 1., 2.],
|
||||
[ 3., 4., -4.],
|
||||
[-3., -2., -1.]])
|
||||
>>> np.fft.ifftshift(np.fft.fftshift(freqs))
|
||||
array([[ 0., 1., 2.],
|
||||
[ 3., 4., -4.],
|
||||
[-3., -2., -1.]])
|
||||
|
||||
"""
|
||||
xp = array_namespace(x)
|
||||
if hasattr(xp, 'fft'):
|
||||
return xp.fft.ifftshift(x, axes=axes)
|
||||
x = np.asarray(x)
|
||||
y = np.fft.ifftshift(x, axes=axes)
|
||||
return xp.asarray(y)
|
||||
@ -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.
|
||||
@ -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
|
||||
251
venv/lib/python3.12/site-packages/scipy/fft/_pocketfft/basic.py
Normal file
251
venv/lib/python3.12/site-packages/scipy/fft/_pocketfft/basic.py
Normal file
@ -0,0 +1,251 @@
|
||||
"""
|
||||
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:
|
||||
message = f"invalid number of data points ({tmp.shape[axis]}) specified"
|
||||
raise ValueError(message)
|
||||
|
||||
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(f"invalid number of data points ({tmp.shape[axis]}) specified")
|
||||
|
||||
# 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(f"Invalid number of data points ({n}) specified")
|
||||
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 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")
|
||||
|
||||
shape = list(shape)
|
||||
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, _ = tuple(_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(f"invalid number of data points ({tmp.shape[axis]}) specified")
|
||||
|
||||
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'
|
||||
221
venv/lib/python3.12/site-packages/scipy/fft/_pocketfft/helper.py
Normal file
221
venv/lib/python3.12/site-packages/scipy/fft/_pocketfft/helper.py
Normal file
@ -0,0 +1,221 @@
|
||||
from numbers import Number
|
||||
import operator
|
||||
import os
|
||||
import threading
|
||||
import contextlib
|
||||
|
||||
import numpy as np
|
||||
|
||||
from scipy._lib._util import copy_if_needed
|
||||
|
||||
# good_size is exposed (and used) from this import
|
||||
from .pypocketfft import good_size, prev_good_size
|
||||
|
||||
|
||||
__all__ = ['good_size', 'prev_good_size', 'set_workers', 'get_workers']
|
||||
|
||||
_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(f"{name} must be a scalar or iterable of integers") 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(
|
||||
f"invalid number of data points ({shape}) specified")
|
||||
|
||||
return tuple(shape), list(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 = True if not x.flags['ALIGNED'] else copy_if_needed
|
||||
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(
|
||||
f"invalid number of data points ({n}) specified")
|
||||
|
||||
return _fix_shape(x, (n,), (axis,))
|
||||
|
||||
|
||||
_NORM_MAP = {None: 0, 'backward': 0, 'ortho': 1, 'forward': 2}
|
||||
|
||||
|
||||
def _normalization(norm, forward):
|
||||
"""Returns the pypocketfft normalization mode from the norm argument"""
|
||||
try:
|
||||
inorm = _NORM_MAP[norm]
|
||||
return inorm if forward else (2 - inorm)
|
||||
except KeyError:
|
||||
raise ValueError(
|
||||
f'Invalid norm value {norm!r}, should '
|
||||
'be "backward", "ortho" or "forward"') from None
|
||||
|
||||
|
||||
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(f"workers value out of range; got {workers}, must not be"
|
||||
f" less than {-_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
|
||||
--------
|
||||
>>> import numpy as np
|
||||
>>> from scipy import fft, signal
|
||||
>>> rng = np.random.default_rng()
|
||||
>>> x = rng.standard_normal((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)
|
||||
Binary file not shown.
@ -0,0 +1,109 @@
|
||||
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, orthogonalize=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(f"invalid number of data points ({tmp.shape[axis]}) specified")
|
||||
|
||||
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, orthogonalize)
|
||||
|
||||
|
||||
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, orthogonalize=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, orthogonalize)
|
||||
|
||||
|
||||
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'
|
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,494 @@
|
||||
from os.path import join, dirname
|
||||
from typing import Callable, Union
|
||||
|
||||
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.longdouble(1.)
|
||||
except TypeError:
|
||||
return False
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def reference_data():
|
||||
# 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.longdouble)
|
||||
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
|
||||
}
|
||||
|
||||
yield ref
|
||||
|
||||
if is_longdouble_binary_compatible():
|
||||
FFTWDATA_LONGDOUBLE.close()
|
||||
FFTWDATA_SINGLE.close()
|
||||
FFTWDATA_DOUBLE.close()
|
||||
MDATA.close()
|
||||
|
||||
|
||||
@pytest.fixture(params=range(FFTWDATA_COUNT))
|
||||
def fftwdata_size(request, reference_data):
|
||||
return reference_data['FFTWDATA_SIZES'][request.param]
|
||||
|
||||
@pytest.fixture(params=range(MDATA_COUNT))
|
||||
def mdata_x(request, reference_data):
|
||||
return reference_data['X'][request.param]
|
||||
|
||||
|
||||
@pytest.fixture(params=range(MDATA_COUNT))
|
||||
def mdata_xy(request, reference_data):
|
||||
y = reference_data['Y'][request.param]
|
||||
x = reference_data['X'][request.param]
|
||||
return x, y
|
||||
|
||||
|
||||
def fftw_dct_ref(type, size, dt, reference_data):
|
||||
x = np.linspace(0, size-1, size).astype(dt)
|
||||
dt = np.result_type(np.float32, dt)
|
||||
if dt == np.float64:
|
||||
data = reference_data['FFTWDATA_DOUBLE']
|
||||
elif dt == np.float32:
|
||||
data = reference_data['FFTWDATA_SINGLE']
|
||||
elif dt == np.longdouble:
|
||||
data = 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, reference_data):
|
||||
x = np.linspace(0, size-1, size).astype(dt)
|
||||
dt = np.result_type(np.float32, dt)
|
||||
if dt == np.float64:
|
||||
data = reference_data['FFTWDATA_DOUBLE']
|
||||
elif dt == np.float32:
|
||||
data = reference_data['FFTWDATA_SINGLE']
|
||||
elif dt == np.longdouble:
|
||||
data = 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.clongdouble])
|
||||
@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)
|
||||
|
||||
|
||||
DecMapType = dict[
|
||||
tuple[Callable[..., np.ndarray], Union[type[np.floating], type[int]], int],
|
||||
int,
|
||||
]
|
||||
|
||||
# map (transform, dtype, type) -> decimal
|
||||
dec_map: DecMapType = {
|
||||
# DCT
|
||||
(dct, np.float64, 1): 13,
|
||||
(dct, np.float32, 1): 6,
|
||||
|
||||
(dct, np.float64, 2): 14,
|
||||
(dct, np.float32, 2): 5,
|
||||
|
||||
(dct, np.float64, 3): 14,
|
||||
(dct, np.float32, 3): 5,
|
||||
|
||||
(dct, np.float64, 4): 13,
|
||||
(dct, np.float32, 4): 6,
|
||||
|
||||
# IDCT
|
||||
(idct, np.float64, 1): 14,
|
||||
(idct, np.float32, 1): 6,
|
||||
|
||||
(idct, np.float64, 2): 14,
|
||||
(idct, np.float32, 2): 5,
|
||||
|
||||
(idct, np.float64, 3): 14,
|
||||
(idct, np.float32, 3): 5,
|
||||
|
||||
(idct, np.float64, 4): 14,
|
||||
(idct, np.float32, 4): 6,
|
||||
|
||||
# DST
|
||||
(dst, np.float64, 1): 13,
|
||||
(dst, np.float32, 1): 6,
|
||||
|
||||
(dst, np.float64, 2): 14,
|
||||
(dst, np.float32, 2): 6,
|
||||
|
||||
(dst, np.float64, 3): 14,
|
||||
(dst, np.float32, 3): 7,
|
||||
|
||||
(dst, np.float64, 4): 13,
|
||||
(dst, np.float32, 4): 5,
|
||||
|
||||
# IDST
|
||||
(idst, np.float64, 1): 14,
|
||||
(idst, np.float32, 1): 6,
|
||||
|
||||
(idst, np.float64, 2): 14,
|
||||
(idst, np.float32, 2): 6,
|
||||
|
||||
(idst, np.float64, 3): 14,
|
||||
(idst, np.float32, 3): 6,
|
||||
|
||||
(idst, np.float64, 4): 14,
|
||||
(idst, np.float32, 4): 6,
|
||||
}
|
||||
|
||||
for k,v in dec_map.copy().items():
|
||||
if k[1] == np.float64:
|
||||
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.longdouble, np.float64, np.float32, int])
|
||||
@pytest.mark.parametrize('type', [1, 2, 3, 4])
|
||||
class TestDCT:
|
||||
def test_definition(self, rdt, type, fftwdata_size, reference_data):
|
||||
x, yr, dt = fftw_dct_ref(type, fftwdata_size, rdt, reference_data)
|
||||
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.longdouble, np.float64, 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.longdouble, np.float64, 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.longdouble, np.float64, 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.longdouble, np.float64, 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.longdouble, np.float64, np.float32, int])
|
||||
@pytest.mark.parametrize('type', [1, 2, 3, 4])
|
||||
def test_idct_definition(fftwdata_size, rdt, type, reference_data):
|
||||
xr, yr, dt = fftw_dct_ref(type, fftwdata_size, rdt, reference_data)
|
||||
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.longdouble, np.float64, np.float32, int])
|
||||
@pytest.mark.parametrize('type', [1, 2, 3, 4])
|
||||
def test_definition(fftwdata_size, rdt, type, reference_data):
|
||||
xr, yr, dt = fftw_dst_ref(type, fftwdata_size, rdt, reference_data)
|
||||
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.longdouble, np.float64, 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.longdouble, np.float64, 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.longdouble, np.float64, np.float32, int])
|
||||
@pytest.mark.parametrize('type', [1, 2, 3, 4])
|
||||
def test_idst_definition(fftwdata_size, rdt, type, reference_data):
|
||||
xr, yr, dt = fftw_dst_ref(type, fftwdata_size, rdt, reference_data)
|
||||
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.longdouble])
|
||||
@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 = "{}({}{!r}, {!r}, axis={!r}, overwrite_x={!r})".format(
|
||||
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:
|
||||
dec = 14
|
||||
dct_type = [1, 2, 3, 4]
|
||||
norms = [None, 'backward', 'ortho', 'forward']
|
||||
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', norms)
|
||||
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))
|
||||
693
venv/lib/python3.12/site-packages/scipy/fft/_realtransforms.py
Normal file
693
venv/lib/python3.12/site-packages/scipy/fft/_realtransforms.py
Normal file
@ -0,0 +1,693 @@
|
||||
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, *, orthogonalize=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
|
||||
``numpy.take(x.shape, axes, axis=0)``.
|
||||
If ``s[i] > x.shape[i]``, the ith dimension of the input is padded with zeros.
|
||||
If ``s[i] < x.shape[i]``, the ith dimension of the input 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 : {"backward", "ortho", "forward"}, optional
|
||||
Normalization mode (see Notes). Default is "backward".
|
||||
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.
|
||||
orthogonalize : bool, optional
|
||||
Whether to use the orthogonalized DCT variant (see Notes).
|
||||
Defaults to ``True`` when ``norm="ortho"`` and ``False`` otherwise.
|
||||
|
||||
.. versionadded:: 1.8.0
|
||||
|
||||
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
|
||||
--------
|
||||
>>> import numpy as np
|
||||
>>> from scipy.fft import dctn, idctn
|
||||
>>> rng = np.random.default_rng()
|
||||
>>> y = rng.standard_normal((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, orthogonalize=None):
|
||||
"""
|
||||
Return multidimensional Inverse 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 ``numpy.take(x.shape, axes, axis=0)``.
|
||||
If ``s[i] > x.shape[i]``, the ith dimension of the input is padded with zeros.
|
||||
If ``s[i] < x.shape[i]``, the ith dimension of the input 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 : {"backward", "ortho", "forward"}, optional
|
||||
Normalization mode (see Notes). Default is "backward".
|
||||
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.
|
||||
orthogonalize : bool, optional
|
||||
Whether to use the orthogonalized IDCT variant (see Notes).
|
||||
Defaults to ``True`` when ``norm="ortho"`` and ``False`` otherwise.
|
||||
|
||||
.. versionadded:: 1.8.0
|
||||
|
||||
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
|
||||
--------
|
||||
>>> import numpy as np
|
||||
>>> from scipy.fft import dctn, idctn
|
||||
>>> rng = np.random.default_rng()
|
||||
>>> y = rng.standard_normal((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, orthogonalize=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
|
||||
``numpy.take(x.shape, axes, axis=0)``.
|
||||
If ``s[i] > x.shape[i]``, the ith dimension of the input is padded with zeros.
|
||||
If ``s[i] < x.shape[i]``, the ith dimension of the input 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 : {"backward", "ortho", "forward"}, optional
|
||||
Normalization mode (see Notes). Default is "backward".
|
||||
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.
|
||||
orthogonalize : bool, optional
|
||||
Whether to use the orthogonalized DST variant (see Notes).
|
||||
Defaults to ``True`` when ``norm="ortho"`` and ``False`` otherwise.
|
||||
|
||||
.. versionadded:: 1.8.0
|
||||
|
||||
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
|
||||
--------
|
||||
>>> import numpy as np
|
||||
>>> from scipy.fft import dstn, idstn
|
||||
>>> rng = np.random.default_rng()
|
||||
>>> y = rng.standard_normal((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, orthogonalize=None):
|
||||
"""
|
||||
Return multidimensional Inverse 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
|
||||
``numpy.take(x.shape, axes, axis=0)``.
|
||||
If ``s[i] > x.shape[i]``, the ith dimension of the input is padded with zeros.
|
||||
If ``s[i] < x.shape[i]``, the ith dimension of the input 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 : {"backward", "ortho", "forward"}, optional
|
||||
Normalization mode (see Notes). Default is "backward".
|
||||
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.
|
||||
orthogonalize : bool, optional
|
||||
Whether to use the orthogonalized IDST variant (see Notes).
|
||||
Defaults to ``True`` when ``norm="ortho"`` and ``False`` otherwise.
|
||||
|
||||
.. versionadded:: 1.8.0
|
||||
|
||||
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
|
||||
--------
|
||||
>>> import numpy as np
|
||||
>>> from scipy.fft import dstn, idstn
|
||||
>>> rng = np.random.default_rng()
|
||||
>>> y = rng.standard_normal((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,
|
||||
orthogonalize=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 : {"backward", "ortho", "forward"}, optional
|
||||
Normalization mode (see Notes). Default is "backward".
|
||||
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.
|
||||
orthogonalize : bool, optional
|
||||
Whether to use the orthogonalized DCT variant (see Notes).
|
||||
Defaults to ``True`` when ``norm="ortho"`` and ``False`` otherwise.
|
||||
|
||||
.. versionadded:: 1.8.0
|
||||
|
||||
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)``.
|
||||
|
||||
.. warning:: For ``type in {1, 2, 3}``, ``norm="ortho"`` breaks the direct
|
||||
correspondence with the direct Fourier transform. To recover
|
||||
it you must specify ``orthogonalize=False``.
|
||||
|
||||
For ``norm="ortho"`` both the `dct` and `idct` are scaled by the same
|
||||
overall factor in both directions. By default, the transform is also
|
||||
orthogonalized which for types 1, 2 and 3 means the transform definition is
|
||||
modified to give orthogonality of the DCT matrix (see below).
|
||||
|
||||
For ``norm="backward"``, 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="forward"`` the ``1/N`` normalization is applied to the forward
|
||||
`dct` instead and the `idct` is unnormalized.
|
||||
|
||||
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="backward"``)
|
||||
|
||||
.. 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 ``orthogonalize=True``, ``x[0]`` and ``x[N-1]`` are multiplied by a
|
||||
scaling factor of :math:`\sqrt{2}`, and ``y[0]`` and ``y[N-1]`` are divided
|
||||
by :math:`\sqrt{2}`. When combined with ``norm="ortho"``, this makes the
|
||||
corresponding matrix of coefficients orthonormal (``O @ O.T = np.eye(N)``).
|
||||
|
||||
.. 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="backward"``)
|
||||
|
||||
.. math::
|
||||
|
||||
y_k = 2 \sum_{n=0}^{N-1} x_n \cos\left(\frac{\pi k(2n+1)}{2N} \right)
|
||||
|
||||
If ``orthogonalize=True``, ``y[0]`` is divided by :math:`\sqrt{2}` which,
|
||||
when combined with ``norm="ortho"``, 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="backward"``)
|
||||
|
||||
.. math::
|
||||
|
||||
y_k = x_0 + 2 \sum_{n=1}^{N-1} x_n \cos\left(\frac{\pi(2k+1)n}{2N}\right)
|
||||
|
||||
If ``orthogonalize=True``, ``x[0]`` terms are multiplied by
|
||||
:math:`\sqrt{2}` which, when combined with ``norm="ortho"``, makes the
|
||||
corresponding matrix of coefficients orthonormal (``O @ O.T = np.eye(N)``).
|
||||
|
||||
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="backward"``)
|
||||
|
||||
.. math::
|
||||
|
||||
y_k = 2 \sum_{n=0}^{N-1} x_n \cos\left(\frac{\pi(2k+1)(2n+1)}{4N} \right)
|
||||
|
||||
``orthogonalize`` has no effect here, as the DCT-IV matrix is already
|
||||
orthogonal up to a scale factor of ``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
|
||||
>>> import numpy as np
|
||||
>>> 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, orthogonalize=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 : {"backward", "ortho", "forward"}, optional
|
||||
Normalization mode (see Notes). Default is "backward".
|
||||
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.
|
||||
orthogonalize : bool, optional
|
||||
Whether to use the orthogonalized IDCT variant (see Notes).
|
||||
Defaults to ``True`` when ``norm="ortho"`` and ``False`` otherwise.
|
||||
|
||||
.. versionadded:: 1.8.0
|
||||
|
||||
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)``.
|
||||
|
||||
.. warning:: For ``type in {1, 2, 3}``, ``norm="ortho"`` breaks the direct
|
||||
correspondence with the inverse direct Fourier transform. To
|
||||
recover it you must specify ``orthogonalize=False``.
|
||||
|
||||
For ``norm="ortho"`` both the `dct` and `idct` are scaled by the same
|
||||
overall factor in both directions. By default, the transform is also
|
||||
orthogonalized which for types 1, 2 and 3 means the transform definition is
|
||||
modified to give orthogonality of the IDCT matrix (see `dct` for the full
|
||||
definitions).
|
||||
|
||||
'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
|
||||
>>> import numpy as np
|
||||
>>> 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,
|
||||
orthogonalize=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 : {"backward", "ortho", "forward"}, optional
|
||||
Normalization mode (see Notes). Default is "backward".
|
||||
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.
|
||||
orthogonalize : bool, optional
|
||||
Whether to use the orthogonalized DST variant (see Notes).
|
||||
Defaults to ``True`` when ``norm="ortho"`` and ``False`` otherwise.
|
||||
|
||||
.. versionadded:: 1.8.0
|
||||
|
||||
Returns
|
||||
-------
|
||||
dst : ndarray of reals
|
||||
The transformed input array.
|
||||
|
||||
See Also
|
||||
--------
|
||||
idst : Inverse DST
|
||||
|
||||
Notes
|
||||
-----
|
||||
.. warning:: For ``type in {2, 3}``, ``norm="ortho"`` breaks the direct
|
||||
correspondence with the direct Fourier transform. To recover
|
||||
it you must specify ``orthogonalize=False``.
|
||||
|
||||
For ``norm="ortho"`` both the `dst` and `idst` are scaled by the same
|
||||
overall factor in both directions. By default, the transform is also
|
||||
orthogonalized which for types 2 and 3 means the transform definition is
|
||||
modified to give orthogonality of the DST matrix (see below).
|
||||
|
||||
For ``norm="backward"``, there is no scaling on the `dst` and the `idst` is
|
||||
scaled by ``1/N`` where ``N`` is the "logical" size of the DST.
|
||||
|
||||
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="backward"``. DST-I assumes the input is odd around :math:`n=-1` and
|
||||
:math:`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 :math:`2(N+1)`.
|
||||
The orthonormalized DST-I is exactly its own inverse.
|
||||
|
||||
``orthogonalize`` has no effect here, as the DST-I matrix is already
|
||||
orthogonal up to a scale factor of ``2N``.
|
||||
|
||||
**Type II**
|
||||
|
||||
There are several definitions of the DST-II; we use the following for
|
||||
``norm="backward"``. DST-II assumes the input is odd around :math:`n=-1/2` and
|
||||
:math:`n=N-1/2`; the output is odd around :math:`k=-1` and even around :math:`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 ``orthogonalize=True``, ``y[-1]`` is divided :math:`\sqrt{2}` which, when
|
||||
combined with ``norm="ortho"``, makes the corresponding matrix of
|
||||
coefficients orthonormal (``O @ O.T = np.eye(N)``).
|
||||
|
||||
**Type III**
|
||||
|
||||
There are several definitions of the DST-III, we use the following (for
|
||||
``norm="backward"``). DST-III assumes the input is odd around :math:`n=-1` and
|
||||
even around :math:`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)
|
||||
|
||||
If ``orthogonalize=True``, ``x[-1]`` is multiplied by :math:`\sqrt{2}`
|
||||
which, when combined with ``norm="ortho"``, makes the corresponding matrix
|
||||
of coefficients orthonormal (``O @ O.T = np.eye(N)``).
|
||||
|
||||
The (unnormalized) DST-III is the inverse of the (unnormalized) DST-II, up
|
||||
to a factor :math:`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="backward"``). DST-IV assumes the input is odd around :math:`n=-0.5` and
|
||||
even around :math:`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)
|
||||
|
||||
``orthogonalize`` has no effect here, as the DST-IV matrix is already
|
||||
orthogonal up to a scale factor of ``2N``.
|
||||
|
||||
The (unnormalized) DST-IV is its own inverse, up to a factor :math:`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, orthogonalize=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 : {"backward", "ortho", "forward"}, optional
|
||||
Normalization mode (see Notes). Default is "backward".
|
||||
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.
|
||||
orthogonalize : bool, optional
|
||||
Whether to use the orthogonalized IDST variant (see Notes).
|
||||
Defaults to ``True`` when ``norm="ortho"`` and ``False`` otherwise.
|
||||
|
||||
.. versionadded:: 1.8.0
|
||||
|
||||
Returns
|
||||
-------
|
||||
idst : ndarray of real
|
||||
The transformed input array.
|
||||
|
||||
See Also
|
||||
--------
|
||||
dst : Forward DST
|
||||
|
||||
Notes
|
||||
-----
|
||||
.. warning:: For ``type in {2, 3}``, ``norm="ortho"`` breaks the direct
|
||||
correspondence with the inverse direct Fourier transform.
|
||||
|
||||
For ``norm="ortho"`` both the `dst` and `idst` are scaled by the same
|
||||
overall factor in both directions. By default, the transform is also
|
||||
orthogonalized which for types 2 and 3 means the transform definition is
|
||||
modified to give orthogonality of the DST matrix (see `dst` for the full
|
||||
definitions).
|
||||
|
||||
'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),)
|
||||
@ -0,0 +1,63 @@
|
||||
from scipy._lib._array_api import array_namespace
|
||||
import numpy as np
|
||||
from . import _pocketfft
|
||||
|
||||
__all__ = ['dct', 'idct', 'dst', 'idst', 'dctn', 'idctn', 'dstn', 'idstn']
|
||||
|
||||
|
||||
def _execute(pocketfft_func, x, type, s, axes, norm,
|
||||
overwrite_x, workers, orthogonalize):
|
||||
xp = array_namespace(x)
|
||||
x = np.asarray(x)
|
||||
y = pocketfft_func(x, type, s, axes, norm,
|
||||
overwrite_x=overwrite_x, workers=workers,
|
||||
orthogonalize=orthogonalize)
|
||||
return xp.asarray(y)
|
||||
|
||||
|
||||
def dctn(x, type=2, s=None, axes=None, norm=None,
|
||||
overwrite_x=False, workers=None, *, orthogonalize=None):
|
||||
return _execute(_pocketfft.dctn, x, type, s, axes, norm,
|
||||
overwrite_x, workers, orthogonalize)
|
||||
|
||||
|
||||
def idctn(x, type=2, s=None, axes=None, norm=None,
|
||||
overwrite_x=False, workers=None, *, orthogonalize=None):
|
||||
return _execute(_pocketfft.idctn, x, type, s, axes, norm,
|
||||
overwrite_x, workers, orthogonalize)
|
||||
|
||||
|
||||
def dstn(x, type=2, s=None, axes=None, norm=None,
|
||||
overwrite_x=False, workers=None, orthogonalize=None):
|
||||
return _execute(_pocketfft.dstn, x, type, s, axes, norm,
|
||||
overwrite_x, workers, orthogonalize)
|
||||
|
||||
|
||||
def idstn(x, type=2, s=None, axes=None, norm=None,
|
||||
overwrite_x=False, workers=None, *, orthogonalize=None):
|
||||
return _execute(_pocketfft.idstn, x, type, s, axes, norm,
|
||||
overwrite_x, workers, orthogonalize)
|
||||
|
||||
|
||||
def dct(x, type=2, n=None, axis=-1, norm=None,
|
||||
overwrite_x=False, workers=None, orthogonalize=None):
|
||||
return _execute(_pocketfft.dct, x, type, n, axis, norm,
|
||||
overwrite_x, workers, orthogonalize)
|
||||
|
||||
|
||||
def idct(x, type=2, n=None, axis=-1, norm=None,
|
||||
overwrite_x=False, workers=None, orthogonalize=None):
|
||||
return _execute(_pocketfft.idct, x, type, n, axis, norm,
|
||||
overwrite_x, workers, orthogonalize)
|
||||
|
||||
|
||||
def dst(x, type=2, n=None, axis=-1, norm=None,
|
||||
overwrite_x=False, workers=None, orthogonalize=None):
|
||||
return _execute(_pocketfft.dst, x, type, n, axis, norm,
|
||||
overwrite_x, workers, orthogonalize)
|
||||
|
||||
|
||||
def idst(x, type=2, n=None, axis=-1, norm=None,
|
||||
overwrite_x=False, workers=None, orthogonalize=None):
|
||||
return _execute(_pocketfft.idst, x, type, n, axis, norm,
|
||||
overwrite_x, workers, orthogonalize)
|
||||
@ -0,0 +1,92 @@
|
||||
import numpy as np
|
||||
import scipy.fft
|
||||
|
||||
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))
|
||||
|
||||
fht = _MockFunction(np.random.random(10))
|
||||
ifht = _MockFunction(np.random.random(10))
|
||||
|
||||
|
||||
__ua_domain__ = "numpy.scipy.fft"
|
||||
|
||||
|
||||
_implements = {
|
||||
scipy.fft.fft: fft,
|
||||
scipy.fft.fft2: fft2,
|
||||
scipy.fft.fftn: fftn,
|
||||
scipy.fft.ifft: ifft,
|
||||
scipy.fft.ifft2: ifft2,
|
||||
scipy.fft.ifftn: ifftn,
|
||||
scipy.fft.rfft: rfft,
|
||||
scipy.fft.rfft2: rfft2,
|
||||
scipy.fft.rfftn: rfftn,
|
||||
scipy.fft.irfft: irfft,
|
||||
scipy.fft.irfft2: irfft2,
|
||||
scipy.fft.irfftn: irfftn,
|
||||
scipy.fft.hfft: hfft,
|
||||
scipy.fft.hfft2: hfft2,
|
||||
scipy.fft.hfftn: hfftn,
|
||||
scipy.fft.ihfft: ihfft,
|
||||
scipy.fft.ihfft2: ihfft2,
|
||||
scipy.fft.ihfftn: ihfftn,
|
||||
scipy.fft.dct: dct,
|
||||
scipy.fft.idct: idct,
|
||||
scipy.fft.dctn: dctn,
|
||||
scipy.fft.idctn: idctn,
|
||||
scipy.fft.dst: dst,
|
||||
scipy.fft.idst: idst,
|
||||
scipy.fft.dstn: dstn,
|
||||
scipy.fft.idstn: idstn,
|
||||
scipy.fft.fht: fht,
|
||||
scipy.fft.ifht: ifht
|
||||
}
|
||||
|
||||
|
||||
def __ua_function__(method, args, kwargs):
|
||||
fn = _implements.get(method)
|
||||
return (fn(*args, **kwargs) if fn is not None
|
||||
else NotImplemented)
|
||||
@ -0,0 +1,98 @@
|
||||
from functools import partial
|
||||
|
||||
import numpy as np
|
||||
import scipy.fft
|
||||
from scipy.fft import _fftlog, _pocketfft, set_backend
|
||||
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',
|
||||
'fht', 'ifht')
|
||||
|
||||
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,
|
||||
# must provide required kwargs for fht, ifht
|
||||
partial(_fftlog.fht, dln=2, mu=0.5),
|
||||
partial(_fftlog.ifht, dln=2, mu=0.5))
|
||||
|
||||
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,
|
||||
# must provide required kwargs for fht, ifht
|
||||
partial(scipy.fft.fht, dln=2, mu=0.5),
|
||||
partial(scipy.fft.ifht, dln=2, mu=0.5))
|
||||
|
||||
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,
|
||||
mock_backend.fht, mock_backend.ifht)
|
||||
|
||||
|
||||
@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.astype(np.float64))
|
||||
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')
|
||||
491
venv/lib/python3.12/site-packages/scipy/fft/tests/test_basic.py
Normal file
491
venv/lib/python3.12/site-packages/scipy/fft/tests/test_basic.py
Normal file
@ -0,0 +1,491 @@
|
||||
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_allclose
|
||||
from pytest import raises as assert_raises
|
||||
import scipy.fft as fft
|
||||
from scipy.conftest import array_api_compatible
|
||||
from scipy._lib._array_api import (
|
||||
array_namespace, size, xp_assert_close, xp_assert_equal
|
||||
)
|
||||
|
||||
pytestmark = [array_api_compatible, pytest.mark.usefixtures("skip_xp_backends")]
|
||||
skip_xp_backends = pytest.mark.skip_xp_backends
|
||||
|
||||
|
||||
# Expected input dtypes. Note that `scipy.fft` is more flexible for numpy,
|
||||
# but for C2C transforms like `fft.fft`, the array API standard only mandates
|
||||
# that complex dtypes should work, float32/float64 aren't guaranteed to.
|
||||
def get_expected_input_dtype(func, xp):
|
||||
if func in [fft.fft, fft.fftn, fft.fft2,
|
||||
fft.ifft, fft.ifftn, fft.ifft2,
|
||||
fft.hfft, fft.hfftn, fft.hfft2,
|
||||
fft.irfft, fft.irfftn, fft.irfft2]:
|
||||
dtype = xp.complex128
|
||||
elif func in [fft.rfft, fft.rfftn, fft.rfft2,
|
||||
fft.ihfft, fft.ihfftn, fft.ihfft2]:
|
||||
dtype = xp.float64
|
||||
else:
|
||||
raise ValueError(f'Unknown FFT function: {func}')
|
||||
|
||||
return dtype
|
||||
|
||||
|
||||
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 TestFFT:
|
||||
|
||||
def test_identity(self, xp):
|
||||
maxlen = 512
|
||||
x = xp.asarray(random(maxlen) + 1j*random(maxlen))
|
||||
xr = xp.asarray(random(maxlen))
|
||||
# Check some powers of 2 and some primes
|
||||
for i in [1, 2, 16, 128, 512, 53, 149, 281, 397]:
|
||||
xp_assert_close(fft.ifft(fft.fft(x[0:i])), x[0:i])
|
||||
xp_assert_close(fft.irfft(fft.rfft(xr[0:i]), i), xr[0:i])
|
||||
|
||||
@skip_xp_backends(np_only=True, reasons=['significant overhead for some backends'])
|
||||
def test_identity_extensive(self, xp):
|
||||
maxlen = 512
|
||||
x = xp.asarray(random(maxlen) + 1j*random(maxlen))
|
||||
xr = xp.asarray(random(maxlen))
|
||||
for i in range(1, maxlen):
|
||||
xp_assert_close(fft.ifft(fft.fft(x[0:i])), x[0:i])
|
||||
xp_assert_close(fft.irfft(fft.rfft(xr[0:i]), i), xr[0:i])
|
||||
|
||||
def test_fft(self, xp):
|
||||
x = random(30) + 1j*random(30)
|
||||
expect = xp.asarray(fft1(x))
|
||||
x = xp.asarray(x)
|
||||
xp_assert_close(fft.fft(x), expect)
|
||||
xp_assert_close(fft.fft(x, norm="backward"), expect)
|
||||
xp_assert_close(fft.fft(x, norm="ortho"),
|
||||
expect / xp.sqrt(xp.asarray(30, dtype=xp.float64)),)
|
||||
xp_assert_close(fft.fft(x, norm="forward"), expect / 30)
|
||||
|
||||
@skip_xp_backends(np_only=True, reasons=['some backends allow `n=0`'])
|
||||
def test_fft_n(self, xp):
|
||||
x = xp.asarray([1, 2, 3], dtype=xp.complex128)
|
||||
assert_raises(ValueError, fft.fft, x, 0)
|
||||
|
||||
def test_ifft(self, xp):
|
||||
x = xp.asarray(random(30) + 1j*random(30))
|
||||
xp_assert_close(fft.ifft(fft.fft(x)), x)
|
||||
for norm in ["backward", "ortho", "forward"]:
|
||||
xp_assert_close(fft.ifft(fft.fft(x, norm=norm), norm=norm), x)
|
||||
|
||||
def test_fft2(self, xp):
|
||||
x = xp.asarray(random((30, 20)) + 1j*random((30, 20)))
|
||||
expect = fft.fft(fft.fft(x, axis=1), axis=0)
|
||||
xp_assert_close(fft.fft2(x), expect)
|
||||
xp_assert_close(fft.fft2(x, norm="backward"), expect)
|
||||
xp_assert_close(fft.fft2(x, norm="ortho"),
|
||||
expect / xp.sqrt(xp.asarray(30 * 20, dtype=xp.float64)))
|
||||
xp_assert_close(fft.fft2(x, norm="forward"), expect / (30 * 20))
|
||||
|
||||
def test_ifft2(self, xp):
|
||||
x = xp.asarray(random((30, 20)) + 1j*random((30, 20)))
|
||||
expect = fft.ifft(fft.ifft(x, axis=1), axis=0)
|
||||
xp_assert_close(fft.ifft2(x), expect)
|
||||
xp_assert_close(fft.ifft2(x, norm="backward"), expect)
|
||||
xp_assert_close(fft.ifft2(x, norm="ortho"),
|
||||
expect * xp.sqrt(xp.asarray(30 * 20, dtype=xp.float64)))
|
||||
xp_assert_close(fft.ifft2(x, norm="forward"), expect * (30 * 20))
|
||||
|
||||
def test_fftn(self, xp):
|
||||
x = xp.asarray(random((30, 20, 10)) + 1j*random((30, 20, 10)))
|
||||
expect = fft.fft(fft.fft(fft.fft(x, axis=2), axis=1), axis=0)
|
||||
xp_assert_close(fft.fftn(x), expect)
|
||||
xp_assert_close(fft.fftn(x, norm="backward"), expect)
|
||||
xp_assert_close(fft.fftn(x, norm="ortho"),
|
||||
expect / xp.sqrt(xp.asarray(30 * 20 * 10, dtype=xp.float64)))
|
||||
xp_assert_close(fft.fftn(x, norm="forward"), expect / (30 * 20 * 10))
|
||||
|
||||
def test_ifftn(self, xp):
|
||||
x = xp.asarray(random((30, 20, 10)) + 1j*random((30, 20, 10)))
|
||||
expect = fft.ifft(fft.ifft(fft.ifft(x, axis=2), axis=1), axis=0)
|
||||
xp_assert_close(fft.ifftn(x), expect, rtol=1e-7)
|
||||
xp_assert_close(fft.ifftn(x, norm="backward"), expect, rtol=1e-7)
|
||||
xp_assert_close(
|
||||
fft.ifftn(x, norm="ortho"),
|
||||
fft.ifftn(x) * xp.sqrt(xp.asarray(30 * 20 * 10, dtype=xp.float64))
|
||||
)
|
||||
xp_assert_close(fft.ifftn(x, norm="forward"),
|
||||
expect * (30 * 20 * 10),
|
||||
rtol=1e-7)
|
||||
|
||||
def test_rfft(self, xp):
|
||||
x = xp.asarray(random(29), dtype=xp.float64)
|
||||
for n in [size(x), 2*size(x)]:
|
||||
for norm in [None, "backward", "ortho", "forward"]:
|
||||
xp_assert_close(fft.rfft(x, n=n, norm=norm),
|
||||
fft.fft(xp.asarray(x, dtype=xp.complex128),
|
||||
n=n, norm=norm)[:(n//2 + 1)])
|
||||
xp_assert_close(
|
||||
fft.rfft(x, n=n, norm="ortho"),
|
||||
fft.rfft(x, n=n) / xp.sqrt(xp.asarray(n, dtype=xp.float64))
|
||||
)
|
||||
|
||||
def test_irfft(self, xp):
|
||||
x = xp.asarray(random(30))
|
||||
xp_assert_close(fft.irfft(fft.rfft(x)), x)
|
||||
for norm in ["backward", "ortho", "forward"]:
|
||||
xp_assert_close(fft.irfft(fft.rfft(x, norm=norm), norm=norm), x)
|
||||
|
||||
def test_rfft2(self, xp):
|
||||
x = xp.asarray(random((30, 20)), dtype=xp.float64)
|
||||
expect = fft.fft2(xp.asarray(x, dtype=xp.complex128))[:, :11]
|
||||
xp_assert_close(fft.rfft2(x), expect)
|
||||
xp_assert_close(fft.rfft2(x, norm="backward"), expect)
|
||||
xp_assert_close(fft.rfft2(x, norm="ortho"),
|
||||
expect / xp.sqrt(xp.asarray(30 * 20, dtype=xp.float64)))
|
||||
xp_assert_close(fft.rfft2(x, norm="forward"), expect / (30 * 20))
|
||||
|
||||
def test_irfft2(self, xp):
|
||||
x = xp.asarray(random((30, 20)))
|
||||
xp_assert_close(fft.irfft2(fft.rfft2(x)), x)
|
||||
for norm in ["backward", "ortho", "forward"]:
|
||||
xp_assert_close(fft.irfft2(fft.rfft2(x, norm=norm), norm=norm), x)
|
||||
|
||||
def test_rfftn(self, xp):
|
||||
x = xp.asarray(random((30, 20, 10)), dtype=xp.float64)
|
||||
expect = fft.fftn(xp.asarray(x, dtype=xp.complex128))[:, :, :6]
|
||||
xp_assert_close(fft.rfftn(x), expect)
|
||||
xp_assert_close(fft.rfftn(x, norm="backward"), expect)
|
||||
xp_assert_close(fft.rfftn(x, norm="ortho"),
|
||||
expect / xp.sqrt(xp.asarray(30 * 20 * 10, dtype=xp.float64)))
|
||||
xp_assert_close(fft.rfftn(x, norm="forward"), expect / (30 * 20 * 10))
|
||||
|
||||
def test_irfftn(self, xp):
|
||||
x = xp.asarray(random((30, 20, 10)))
|
||||
xp_assert_close(fft.irfftn(fft.rfftn(x)), x)
|
||||
for norm in ["backward", "ortho", "forward"]:
|
||||
xp_assert_close(fft.irfftn(fft.rfftn(x, norm=norm), norm=norm), x)
|
||||
|
||||
def test_hfft(self, xp):
|
||||
x = random(14) + 1j*random(14)
|
||||
x_herm = np.concatenate((random(1), x, random(1)))
|
||||
x = np.concatenate((x_herm, x[::-1].conj()))
|
||||
x = xp.asarray(x)
|
||||
x_herm = xp.asarray(x_herm)
|
||||
expect = xp.real(fft.fft(x))
|
||||
xp_assert_close(fft.hfft(x_herm), expect)
|
||||
xp_assert_close(fft.hfft(x_herm, norm="backward"), expect)
|
||||
xp_assert_close(fft.hfft(x_herm, norm="ortho"),
|
||||
expect / xp.sqrt(xp.asarray(30, dtype=xp.float64)))
|
||||
xp_assert_close(fft.hfft(x_herm, norm="forward"), expect / 30)
|
||||
|
||||
def test_ihfft(self, xp):
|
||||
x = random(14) + 1j*random(14)
|
||||
x_herm = np.concatenate((random(1), x, random(1)))
|
||||
x = np.concatenate((x_herm, x[::-1].conj()))
|
||||
x = xp.asarray(x)
|
||||
x_herm = xp.asarray(x_herm)
|
||||
xp_assert_close(fft.ihfft(fft.hfft(x_herm)), x_herm)
|
||||
for norm in ["backward", "ortho", "forward"]:
|
||||
xp_assert_close(fft.ihfft(fft.hfft(x_herm, norm=norm), norm=norm), x_herm)
|
||||
|
||||
def test_hfft2(self, xp):
|
||||
x = xp.asarray(random((30, 20)))
|
||||
xp_assert_close(fft.hfft2(fft.ihfft2(x)), x)
|
||||
for norm in ["backward", "ortho", "forward"]:
|
||||
xp_assert_close(fft.hfft2(fft.ihfft2(x, norm=norm), norm=norm), x)
|
||||
|
||||
def test_ihfft2(self, xp):
|
||||
x = xp.asarray(random((30, 20)), dtype=xp.float64)
|
||||
expect = fft.ifft2(xp.asarray(x, dtype=xp.complex128))[:, :11]
|
||||
xp_assert_close(fft.ihfft2(x), expect)
|
||||
xp_assert_close(fft.ihfft2(x, norm="backward"), expect)
|
||||
xp_assert_close(
|
||||
fft.ihfft2(x, norm="ortho"),
|
||||
expect * xp.sqrt(xp.asarray(30 * 20, dtype=xp.float64))
|
||||
)
|
||||
xp_assert_close(fft.ihfft2(x, norm="forward"), expect * (30 * 20))
|
||||
|
||||
def test_hfftn(self, xp):
|
||||
x = xp.asarray(random((30, 20, 10)))
|
||||
xp_assert_close(fft.hfftn(fft.ihfftn(x)), x)
|
||||
for norm in ["backward", "ortho", "forward"]:
|
||||
xp_assert_close(fft.hfftn(fft.ihfftn(x, norm=norm), norm=norm), x)
|
||||
|
||||
def test_ihfftn(self, xp):
|
||||
x = xp.asarray(random((30, 20, 10)), dtype=xp.float64)
|
||||
expect = fft.ifftn(xp.asarray(x, dtype=xp.complex128))[:, :, :6]
|
||||
xp_assert_close(expect, fft.ihfftn(x))
|
||||
xp_assert_close(expect, fft.ihfftn(x, norm="backward"))
|
||||
xp_assert_close(
|
||||
fft.ihfftn(x, norm="ortho"),
|
||||
expect * xp.sqrt(xp.asarray(30 * 20 * 10, dtype=xp.float64))
|
||||
)
|
||||
xp_assert_close(fft.ihfftn(x, norm="forward"), expect * (30 * 20 * 10))
|
||||
|
||||
def _check_axes(self, op, xp):
|
||||
dtype = get_expected_input_dtype(op, xp)
|
||||
x = xp.asarray(random((30, 20, 10)), dtype=dtype)
|
||||
axes = [(0, 1, 2), (0, 2, 1), (1, 0, 2), (1, 2, 0), (2, 0, 1), (2, 1, 0)]
|
||||
xp_test = array_namespace(x)
|
||||
for a in axes:
|
||||
op_tr = op(xp_test.permute_dims(x, axes=a))
|
||||
tr_op = xp_test.permute_dims(op(x, axes=a), axes=a)
|
||||
xp_assert_close(op_tr, tr_op)
|
||||
|
||||
@pytest.mark.parametrize("op", [fft.fftn, fft.ifftn, fft.rfftn, fft.irfftn])
|
||||
def test_axes_standard(self, op, xp):
|
||||
self._check_axes(op, xp)
|
||||
|
||||
@pytest.mark.parametrize("op", [fft.hfftn, fft.ihfftn])
|
||||
def test_axes_non_standard(self, op, xp):
|
||||
self._check_axes(op, xp)
|
||||
|
||||
@pytest.mark.parametrize("op", [fft.fftn, fft.ifftn,
|
||||
fft.rfftn, fft.irfftn])
|
||||
def test_axes_subset_with_shape_standard(self, op, xp):
|
||||
dtype = get_expected_input_dtype(op, xp)
|
||||
x = xp.asarray(random((16, 8, 4)), dtype=dtype)
|
||||
axes = [(0, 1, 2), (0, 2, 1), (1, 2, 0)]
|
||||
xp_test = array_namespace(x)
|
||||
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(xp_test.permute_dims(x, axes=a),
|
||||
s=shape[:2], axes=(0, 1))
|
||||
tr_op = xp_test.permute_dims(op(x, s=shape[:2], axes=a[:2]),
|
||||
axes=a)
|
||||
xp_assert_close(op_tr, tr_op)
|
||||
|
||||
@pytest.mark.parametrize("op", [fft.fft2, fft.ifft2,
|
||||
fft.rfft2, fft.irfft2,
|
||||
fft.hfft2, fft.ihfft2,
|
||||
fft.hfftn, fft.ihfftn])
|
||||
def test_axes_subset_with_shape_non_standard(self, op, xp):
|
||||
dtype = get_expected_input_dtype(op, xp)
|
||||
x = xp.asarray(random((16, 8, 4)), dtype=dtype)
|
||||
axes = [(0, 1, 2), (0, 2, 1), (1, 2, 0)]
|
||||
xp_test = array_namespace(x)
|
||||
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(xp_test.permute_dims(x, axes=a), s=shape[:2], axes=(0, 1))
|
||||
tr_op = xp_test.permute_dims(op(x, s=shape[:2], axes=a[:2]), axes=a)
|
||||
xp_assert_close(op_tr, tr_op)
|
||||
|
||||
def test_all_1d_norm_preserving(self, xp):
|
||||
# verify that round-trip transforms are norm-preserving
|
||||
x = xp.asarray(random(30), dtype=xp.float64)
|
||||
xp_test = array_namespace(x)
|
||||
x_norm = xp_test.linalg.vector_norm(x)
|
||||
n = size(x) * 2
|
||||
func_pairs = [(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),
|
||||
# functions that expect complex dtypes at the end
|
||||
(fft.fft, fft.ifft),
|
||||
]
|
||||
for forw, back in func_pairs:
|
||||
if forw == fft.fft:
|
||||
x = xp.asarray(x, dtype=xp.complex128)
|
||||
x_norm = xp_test.linalg.vector_norm(x)
|
||||
for n in [size(x), 2*size(x)]:
|
||||
for norm in ['backward', 'ortho', 'forward']:
|
||||
tmp = forw(x, n=n, norm=norm)
|
||||
tmp = back(tmp, n=n, norm=norm)
|
||||
xp_assert_close(xp_test.linalg.vector_norm(tmp), x_norm)
|
||||
|
||||
@skip_xp_backends(np_only=True)
|
||||
@pytest.mark.parametrize("dtype", [np.float16, np.longdouble])
|
||||
def test_dtypes_nonstandard(self, dtype):
|
||||
x = random(30).astype(dtype)
|
||||
out_dtypes = {np.float16: np.complex64, np.longdouble: np.clongdouble}
|
||||
x_complex = x.astype(out_dtypes[dtype])
|
||||
|
||||
res_fft = fft.ifft(fft.fft(x))
|
||||
res_rfft = fft.irfft(fft.rfft(x))
|
||||
res_hfft = fft.hfft(fft.ihfft(x), x.shape[0])
|
||||
# Check both numerical results and exact dtype matches
|
||||
assert_array_almost_equal(res_fft, x_complex)
|
||||
assert_array_almost_equal(res_rfft, x)
|
||||
assert_array_almost_equal(res_hfft, x)
|
||||
assert res_fft.dtype == x_complex.dtype
|
||||
assert res_rfft.dtype == np.result_type(np.float32, x.dtype)
|
||||
assert res_hfft.dtype == np.result_type(np.float32, x.dtype)
|
||||
|
||||
@pytest.mark.parametrize("dtype", ["float32", "float64"])
|
||||
def test_dtypes_real(self, dtype, xp):
|
||||
x = xp.asarray(random(30), dtype=getattr(xp, dtype))
|
||||
|
||||
res_rfft = fft.irfft(fft.rfft(x))
|
||||
res_hfft = fft.hfft(fft.ihfft(x), x.shape[0])
|
||||
# Check both numerical results and exact dtype matches
|
||||
xp_assert_close(res_rfft, x)
|
||||
xp_assert_close(res_hfft, x)
|
||||
|
||||
@pytest.mark.parametrize("dtype", ["complex64", "complex128"])
|
||||
def test_dtypes_complex(self, dtype, xp):
|
||||
x = xp.asarray(random(30), dtype=getattr(xp, dtype))
|
||||
|
||||
res_fft = fft.ifft(fft.fft(x))
|
||||
# Check both numerical results and exact dtype matches
|
||||
xp_assert_close(res_fft, x)
|
||||
|
||||
@skip_xp_backends(np_only=True,
|
||||
reasons=['array-likes only supported for NumPy backend'])
|
||||
@pytest.mark.parametrize("op", [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,])
|
||||
def test_array_like(self, xp, op):
|
||||
x = [[[1.0, 1.0], [1.0, 1.0]],
|
||||
[[1.0, 1.0], [1.0, 1.0]],
|
||||
[[1.0, 1.0], [1.0, 1.0]]]
|
||||
xp_assert_close(op(x), op(xp.asarray(x)))
|
||||
|
||||
|
||||
@skip_xp_backends(np_only=True)
|
||||
@pytest.mark.parametrize(
|
||||
"dtype",
|
||||
[np.float32, np.float64, np.longdouble,
|
||||
np.complex64, np.complex128, np.clongdouble])
|
||||
@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
|
||||
|
||||
|
||||
@skip_xp_backends(cpu_only=True)
|
||||
class TestFFTThreadSafe:
|
||||
threads = 16
|
||||
input_shape = (800, 200)
|
||||
|
||||
def _test_mtsame(self, func, *args, xp=None):
|
||||
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):
|
||||
xp_assert_equal(
|
||||
q.get(timeout=5), expected,
|
||||
err_msg='Function returned wrong value in multithreaded context'
|
||||
)
|
||||
|
||||
def test_fft(self, xp):
|
||||
a = xp.ones(self.input_shape, dtype=xp.complex128)
|
||||
self._test_mtsame(fft.fft, a, xp=xp)
|
||||
|
||||
def test_ifft(self, xp):
|
||||
a = xp.full(self.input_shape, 1+0j)
|
||||
self._test_mtsame(fft.ifft, a, xp=xp)
|
||||
|
||||
def test_rfft(self, xp):
|
||||
a = xp.ones(self.input_shape)
|
||||
self._test_mtsame(fft.rfft, a, xp=xp)
|
||||
|
||||
def test_irfft(self, xp):
|
||||
a = xp.full(self.input_shape, 1+0j)
|
||||
self._test_mtsame(fft.irfft, a, xp=xp)
|
||||
|
||||
def test_hfft(self, xp):
|
||||
a = xp.ones(self.input_shape, dtype=xp.complex64)
|
||||
self._test_mtsame(fft.hfft, a, xp=xp)
|
||||
|
||||
def test_ihfft(self, xp):
|
||||
a = xp.ones(self.input_shape)
|
||||
self._test_mtsame(fft.ihfft, a, xp=xp)
|
||||
|
||||
|
||||
@skip_xp_backends(np_only=True)
|
||||
@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:
|
||||
|
||||
def test_not_last_axis_success(self, xp):
|
||||
ar, ai = np.random.random((2, 16, 8, 32))
|
||||
a = ar + 1j*ai
|
||||
a = xp.asarray(a)
|
||||
|
||||
axes = (-2,)
|
||||
|
||||
# Should not raise error
|
||||
fft.irfftn(a, axes=axes)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("func", [fft.fft, fft.ifft, fft.rfft, fft.irfft,
|
||||
fft.fftn, fft.ifftn,
|
||||
fft.rfftn, fft.irfftn, fft.hfft, fft.ihfft])
|
||||
def test_non_standard_params(func, xp):
|
||||
if func in [fft.rfft, fft.rfftn, fft.ihfft]:
|
||||
dtype = xp.float64
|
||||
else:
|
||||
dtype = xp.complex128
|
||||
|
||||
if xp.__name__ != 'numpy':
|
||||
x = xp.asarray([1, 2, 3], dtype=dtype)
|
||||
# func(x) should not raise an exception
|
||||
func(x)
|
||||
assert_raises(ValueError, func, x, workers=2)
|
||||
# `plan` param is not tested since SciPy does not use it currently
|
||||
# but should be tested if it comes into use
|
||||
179
venv/lib/python3.12/site-packages/scipy/fft/tests/test_fftlog.py
Normal file
179
venv/lib/python3.12/site-packages/scipy/fft/tests/test_fftlog.py
Normal file
@ -0,0 +1,179 @@
|
||||
import warnings
|
||||
import numpy as np
|
||||
import pytest
|
||||
|
||||
from scipy.fft._fftlog import fht, ifht, fhtoffset
|
||||
from scipy.special import poch
|
||||
|
||||
from scipy.conftest import array_api_compatible
|
||||
from scipy._lib._array_api import xp_assert_close
|
||||
|
||||
pytestmark = [array_api_compatible, pytest.mark.usefixtures("skip_xp_backends"),]
|
||||
skip_xp_backends = pytest.mark.skip_xp_backends
|
||||
|
||||
|
||||
def test_fht_agrees_with_fftlog(xp):
|
||||
# check that fht numerically agrees with the output from Fortran FFTLog,
|
||||
# the results were generated with the provided `fftlogtest` program,
|
||||
# after fixing how the k array is generated (divide range by n-1, not n)
|
||||
|
||||
# test function, analytical Hankel transform is of the same form
|
||||
def f(r, mu):
|
||||
return r**(mu+1)*np.exp(-r**2/2)
|
||||
|
||||
r = np.logspace(-4, 4, 16)
|
||||
|
||||
dln = np.log(r[1]/r[0])
|
||||
mu = 0.3
|
||||
offset = 0.0
|
||||
bias = 0.0
|
||||
|
||||
a = xp.asarray(f(r, mu))
|
||||
|
||||
# test 1: compute as given
|
||||
ours = fht(a, dln, mu, offset=offset, bias=bias)
|
||||
theirs = [-0.1159922613593045E-02, +0.1625822618458832E-02,
|
||||
-0.1949518286432330E-02, +0.3789220182554077E-02,
|
||||
+0.5093959119952945E-03, +0.2785387803618774E-01,
|
||||
+0.9944952700848897E-01, +0.4599202164586588E+00,
|
||||
+0.3157462160881342E+00, -0.8201236844404755E-03,
|
||||
-0.7834031308271878E-03, +0.3931444945110708E-03,
|
||||
-0.2697710625194777E-03, +0.3568398050238820E-03,
|
||||
-0.5554454827797206E-03, +0.8286331026468585E-03]
|
||||
theirs = xp.asarray(theirs, dtype=xp.float64)
|
||||
xp_assert_close(ours, theirs)
|
||||
|
||||
# test 2: change to optimal offset
|
||||
offset = fhtoffset(dln, mu, bias=bias)
|
||||
ours = fht(a, dln, mu, offset=offset, bias=bias)
|
||||
theirs = [+0.4353768523152057E-04, -0.9197045663594285E-05,
|
||||
+0.3150140927838524E-03, +0.9149121960963704E-03,
|
||||
+0.5808089753959363E-02, +0.2548065256377240E-01,
|
||||
+0.1339477692089897E+00, +0.4821530509479356E+00,
|
||||
+0.2659899781579785E+00, -0.1116475278448113E-01,
|
||||
+0.1791441617592385E-02, -0.4181810476548056E-03,
|
||||
+0.1314963536765343E-03, -0.5422057743066297E-04,
|
||||
+0.3208681804170443E-04, -0.2696849476008234E-04]
|
||||
theirs = xp.asarray(theirs, dtype=xp.float64)
|
||||
xp_assert_close(ours, theirs)
|
||||
|
||||
# test 3: positive bias
|
||||
bias = 0.8
|
||||
offset = fhtoffset(dln, mu, bias=bias)
|
||||
ours = fht(a, dln, mu, offset=offset, bias=bias)
|
||||
theirs = [-7.3436673558316850E+00, +0.1710271207817100E+00,
|
||||
+0.1065374386206564E+00, -0.5121739602708132E-01,
|
||||
+0.2636649319269470E-01, +0.1697209218849693E-01,
|
||||
+0.1250215614723183E+00, +0.4739583261486729E+00,
|
||||
+0.2841149874912028E+00, -0.8312764741645729E-02,
|
||||
+0.1024233505508988E-02, -0.1644902767389120E-03,
|
||||
+0.3305775476926270E-04, -0.7786993194882709E-05,
|
||||
+0.1962258449520547E-05, -0.8977895734909250E-06]
|
||||
theirs = xp.asarray(theirs, dtype=xp.float64)
|
||||
xp_assert_close(ours, theirs)
|
||||
|
||||
# test 4: negative bias
|
||||
bias = -0.8
|
||||
offset = fhtoffset(dln, mu, bias=bias)
|
||||
ours = fht(a, dln, mu, offset=offset, bias=bias)
|
||||
theirs = [+0.8985777068568745E-05, +0.4074898209936099E-04,
|
||||
+0.2123969254700955E-03, +0.1009558244834628E-02,
|
||||
+0.5131386375222176E-02, +0.2461678673516286E-01,
|
||||
+0.1235812845384476E+00, +0.4719570096404403E+00,
|
||||
+0.2893487490631317E+00, -0.1686570611318716E-01,
|
||||
+0.2231398155172505E-01, -0.1480742256379873E-01,
|
||||
+0.1692387813500801E+00, +0.3097490354365797E+00,
|
||||
+2.7593607182401860E+00, 10.5251075070045800E+00]
|
||||
theirs = xp.asarray(theirs, dtype=xp.float64)
|
||||
xp_assert_close(ours, theirs)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('optimal', [True, False])
|
||||
@pytest.mark.parametrize('offset', [0.0, 1.0, -1.0])
|
||||
@pytest.mark.parametrize('bias', [0, 0.1, -0.1])
|
||||
@pytest.mark.parametrize('n', [64, 63])
|
||||
def test_fht_identity(n, bias, offset, optimal, xp):
|
||||
rng = np.random.RandomState(3491349965)
|
||||
|
||||
a = xp.asarray(rng.standard_normal(n))
|
||||
dln = rng.uniform(-1, 1)
|
||||
mu = rng.uniform(-2, 2)
|
||||
|
||||
if optimal:
|
||||
offset = fhtoffset(dln, mu, initial=offset, bias=bias)
|
||||
|
||||
A = fht(a, dln, mu, offset=offset, bias=bias)
|
||||
a_ = ifht(A, dln, mu, offset=offset, bias=bias)
|
||||
|
||||
xp_assert_close(a_, a, rtol=1.5e-7)
|
||||
|
||||
|
||||
def test_fht_special_cases(xp):
|
||||
rng = np.random.RandomState(3491349965)
|
||||
|
||||
a = xp.asarray(rng.standard_normal(64))
|
||||
dln = rng.uniform(-1, 1)
|
||||
|
||||
# let x = (mu+1+q)/2, y = (mu+1-q)/2, M = {0, -1, -2, ...}
|
||||
|
||||
# case 1: x in M, y in M => well-defined transform
|
||||
mu, bias = -4.0, 1.0
|
||||
with warnings.catch_warnings(record=True) as record:
|
||||
fht(a, dln, mu, bias=bias)
|
||||
assert not record, 'fht warned about a well-defined transform'
|
||||
|
||||
# case 2: x not in M, y in M => well-defined transform
|
||||
mu, bias = -2.5, 0.5
|
||||
with warnings.catch_warnings(record=True) as record:
|
||||
fht(a, dln, mu, bias=bias)
|
||||
assert not record, 'fht warned about a well-defined transform'
|
||||
|
||||
# case 3: x in M, y not in M => singular transform
|
||||
mu, bias = -3.5, 0.5
|
||||
with pytest.warns(Warning) as record:
|
||||
fht(a, dln, mu, bias=bias)
|
||||
assert record, 'fht did not warn about a singular transform'
|
||||
|
||||
# case 4: x not in M, y in M => singular inverse transform
|
||||
mu, bias = -2.5, 0.5
|
||||
with pytest.warns(Warning) as record:
|
||||
ifht(a, dln, mu, bias=bias)
|
||||
assert record, 'ifht did not warn about a singular transform'
|
||||
|
||||
|
||||
@pytest.mark.parametrize('n', [64, 63])
|
||||
def test_fht_exact(n, xp):
|
||||
rng = np.random.RandomState(3491349965)
|
||||
|
||||
# for a(r) a power law r^\gamma, the fast Hankel transform produces the
|
||||
# exact continuous Hankel transform if biased with q = \gamma
|
||||
|
||||
mu = rng.uniform(0, 3)
|
||||
|
||||
# convergence of HT: -1-mu < gamma < 1/2
|
||||
gamma = rng.uniform(-1-mu, 1/2)
|
||||
|
||||
r = np.logspace(-2, 2, n)
|
||||
a = xp.asarray(r**gamma)
|
||||
|
||||
dln = np.log(r[1]/r[0])
|
||||
|
||||
offset = fhtoffset(dln, mu, initial=0.0, bias=gamma)
|
||||
|
||||
A = fht(a, dln, mu, offset=offset, bias=gamma)
|
||||
|
||||
k = np.exp(offset)/r[::-1]
|
||||
|
||||
# analytical result
|
||||
At = xp.asarray((2/k)**gamma * poch((mu+1-gamma)/2, gamma))
|
||||
|
||||
xp_assert_close(A, At)
|
||||
|
||||
@skip_xp_backends(np_only=True,
|
||||
reasons=['array-likes only supported for NumPy backend'])
|
||||
@pytest.mark.parametrize("op", [fht, ifht])
|
||||
def test_array_like(xp, op):
|
||||
x = [[[1.0, 1.0], [1.0, 1.0]],
|
||||
[[1.0, 1.0], [1.0, 1.0]],
|
||||
[[1.0, 1.0], [1.0, 1.0]]]
|
||||
xp_assert_close(op(x, 1.0, 2.0), op(xp.asarray(x), 1.0, 2.0))
|
||||
570
venv/lib/python3.12/site-packages/scipy/fft/tests/test_helper.py
Normal file
570
venv/lib/python3.12/site-packages/scipy/fft/tests/test_helper.py
Normal file
@ -0,0 +1,570 @@
|
||||
"""Includes test functions for fftpack.helper module
|
||||
|
||||
Copied from fftpack.helper by Pearu Peterson, October 2005
|
||||
Modified for Array API, 2023
|
||||
|
||||
"""
|
||||
from scipy.fft._helper import next_fast_len, prev_fast_len, _init_nd_shape_and_axes
|
||||
from numpy.testing import assert_equal
|
||||
from pytest import raises as assert_raises
|
||||
import pytest
|
||||
import numpy as np
|
||||
import sys
|
||||
from scipy.conftest import array_api_compatible
|
||||
from scipy._lib._array_api import (
|
||||
xp_assert_close, get_xp_devices, device, array_namespace
|
||||
)
|
||||
from scipy import fft
|
||||
|
||||
pytestmark = [array_api_compatible, pytest.mark.usefixtures("skip_xp_backends")]
|
||||
skip_xp_backends = pytest.mark.skip_xp_backends
|
||||
|
||||
_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, \
|
||||
f'x={x_orig} is not {n}-smooth, remainder={x}'
|
||||
|
||||
|
||||
@skip_xp_backends(np_only=True)
|
||||
class TestNextFastLen:
|
||||
|
||||
def test_next_fast_len(self):
|
||||
np.random.seed(1234)
|
||||
|
||||
def nums():
|
||||
yield from range(1, 1000)
|
||||
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)
|
||||
|
||||
def test_keyword_args(self):
|
||||
assert next_fast_len(11, real=True) == 12
|
||||
assert next_fast_len(target=7, real=False) == 7
|
||||
|
||||
@skip_xp_backends(np_only=True)
|
||||
class TestPrevFastLen:
|
||||
|
||||
def test_prev_fast_len(self):
|
||||
np.random.seed(1234)
|
||||
|
||||
def nums():
|
||||
yield from range(1, 1000)
|
||||
yield 2**5 * 3**5 * 4**5 + 1
|
||||
|
||||
for n in nums():
|
||||
m = prev_fast_len(n)
|
||||
_assert_n_smooth(m, 11)
|
||||
assert m == prev_fast_len(n, False)
|
||||
|
||||
m = prev_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 = prev_fast_len(x)
|
||||
assert_equal(testN, prev_fast_len(int(x)))
|
||||
|
||||
testN = prev_fast_len(x, real=True)
|
||||
assert_equal(testN, prev_fast_len(int(x), real=True))
|
||||
|
||||
def testprev_fast_len_small(self):
|
||||
hams = {
|
||||
1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 6: 6, 7: 6, 8: 8, 14: 12, 15: 15,
|
||||
16: 16, 17: 16, 1021: 1000, 1536: 1536, 51200000: 51200000
|
||||
}
|
||||
for x, y in hams.items():
|
||||
assert_equal(prev_fast_len(x, True), y)
|
||||
|
||||
hams = {
|
||||
1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 6: 6, 7: 7, 8: 8, 9: 9, 10: 10,
|
||||
11: 11, 12: 12, 13: 12, 14: 14, 15: 15, 16: 16, 17: 16, 18: 18,
|
||||
19: 18, 20: 20, 21: 21, 22: 22, 120: 120, 121: 121, 122: 121,
|
||||
1021: 1008, 1536: 1536, 51200000: 51200000
|
||||
}
|
||||
for x, y in hams.items():
|
||||
assert_equal(prev_fast_len(x, False), y)
|
||||
|
||||
@pytest.mark.xfail(sys.maxsize < 2**32,
|
||||
reason="Hamming Numbers too large for 32-bit",
|
||||
raises=ValueError, strict=True)
|
||||
def testprev_fast_len_big(self):
|
||||
hams = {
|
||||
# 2**6 * 3**13 * 5**1
|
||||
510183360: 510183360,
|
||||
510183360 + 1: 510183360,
|
||||
510183360 - 1: 509607936, # 2**21 * 3**5
|
||||
# 2**6 * 5**6 * 7**1 * 73**1
|
||||
511000000: 510183360,
|
||||
511000000 + 1: 510183360,
|
||||
511000000 - 1: 510183360, # 2**6 * 3**13 * 5**1
|
||||
# 3**7 * 5**8
|
||||
854296875: 854296875,
|
||||
854296875 + 1: 854296875,
|
||||
854296875 - 1: 850305600, # 2**6 * 3**12 * 5**2
|
||||
# 2**22 * 3**1 * 5**6
|
||||
196608000000: 196608000000,
|
||||
196608000000 + 1: 196608000000,
|
||||
196608000000 - 1: 195910410240, # 2**13 * 3**14 * 5**1
|
||||
# 2**5 * 3**2 * 5**15
|
||||
8789062500000: 8789062500000,
|
||||
8789062500000 + 1: 8789062500000,
|
||||
8789062500000 - 1: 8748000000000, # 2**11 * 3**7 * 5**9
|
||||
# 2**24 * 3**9 * 5**4
|
||||
206391214080000: 206391214080000,
|
||||
206391214080000 + 1: 206391214080000,
|
||||
206391214080000 - 1: 206158430208000, # 2**39 * 3**1 * 5**3
|
||||
# 2**18 * 3**15 * 5**3
|
||||
470184984576000: 470184984576000,
|
||||
470184984576000 + 1: 470184984576000,
|
||||
470184984576000 - 1: 469654673817600, # 2**33 * 3**7 **5**2
|
||||
# 2**25 * 3**16 * 5**1
|
||||
7222041363087360: 7222041363087360,
|
||||
7222041363087360 + 1: 7222041363087360,
|
||||
7222041363087360 - 1: 7213895789838336, # 2**40 * 3**8
|
||||
# power of 5 5**23
|
||||
11920928955078125: 11920928955078125,
|
||||
11920928955078125 + 1: 11920928955078125,
|
||||
11920928955078125 - 1: 11901557422080000, # 2**14 * 3**19 * 5**4
|
||||
# power of 3 3**34
|
||||
16677181699666569: 16677181699666569,
|
||||
16677181699666569 + 1: 16677181699666569,
|
||||
16677181699666569 - 1: 16607531250000000, # 2**7 * 3**12 * 5**12
|
||||
# power of 2 2**54
|
||||
18014398509481984: 18014398509481984,
|
||||
18014398509481984 + 1: 18014398509481984,
|
||||
18014398509481984 - 1: 18000000000000000, # 2**16 * 3**2 * 5**15
|
||||
# 2**20 * 3**1 * 5**14
|
||||
19200000000000000: 19200000000000000,
|
||||
19200000000000000 + 1: 19200000000000000,
|
||||
19200000000000000 - 1: 19131876000000000, # 2**11 * 3**14 * 5**9
|
||||
# 2**58
|
||||
288230376151711744: 288230376151711744,
|
||||
288230376151711744 + 1: 288230376151711744,
|
||||
288230376151711744 - 1: 288000000000000000, # 2**20 * 3**2 * 5**15
|
||||
# 2**5 * 3**10 * 5**16
|
||||
288325195312500000: 288325195312500000,
|
||||
288325195312500000 + 1: 288325195312500000,
|
||||
288325195312500000 - 1: 288230376151711744, # 2**58
|
||||
}
|
||||
for x, y in hams.items():
|
||||
assert_equal(prev_fast_len(x, True), y)
|
||||
|
||||
def test_keyword_args(self):
|
||||
assert prev_fast_len(11, real=True) == 10
|
||||
assert prev_fast_len(target=7, real=False) == 7
|
||||
|
||||
|
||||
@skip_xp_backends(cpu_only=True)
|
||||
class Test_init_nd_shape_and_axes:
|
||||
|
||||
def test_py_0d_defaults(self, xp):
|
||||
x = xp.asarray(4)
|
||||
shape = None
|
||||
axes = None
|
||||
|
||||
shape_expected = ()
|
||||
axes_expected = []
|
||||
|
||||
shape_res, axes_res = _init_nd_shape_and_axes(x, shape, axes)
|
||||
|
||||
assert shape_res == shape_expected
|
||||
assert axes_res == axes_expected
|
||||
|
||||
def test_xp_0d_defaults(self, xp):
|
||||
x = xp.asarray(7.)
|
||||
shape = None
|
||||
axes = None
|
||||
|
||||
shape_expected = ()
|
||||
axes_expected = []
|
||||
|
||||
shape_res, axes_res = _init_nd_shape_and_axes(x, shape, axes)
|
||||
|
||||
assert shape_res == shape_expected
|
||||
assert axes_res == axes_expected
|
||||
|
||||
def test_py_1d_defaults(self, xp):
|
||||
x = xp.asarray([1, 2, 3])
|
||||
shape = None
|
||||
axes = None
|
||||
|
||||
shape_expected = (3,)
|
||||
axes_expected = [0]
|
||||
|
||||
shape_res, axes_res = _init_nd_shape_and_axes(x, shape, axes)
|
||||
|
||||
assert shape_res == shape_expected
|
||||
assert axes_res == axes_expected
|
||||
|
||||
def test_xp_1d_defaults(self, xp):
|
||||
x = xp.arange(0, 1, .1)
|
||||
shape = None
|
||||
axes = None
|
||||
|
||||
shape_expected = (10,)
|
||||
axes_expected = [0]
|
||||
|
||||
shape_res, axes_res = _init_nd_shape_and_axes(x, shape, axes)
|
||||
|
||||
assert shape_res == shape_expected
|
||||
assert axes_res == axes_expected
|
||||
|
||||
def test_py_2d_defaults(self, xp):
|
||||
x = xp.asarray([[1, 2, 3, 4],
|
||||
[5, 6, 7, 8]])
|
||||
shape = None
|
||||
axes = None
|
||||
|
||||
shape_expected = (2, 4)
|
||||
axes_expected = [0, 1]
|
||||
|
||||
shape_res, axes_res = _init_nd_shape_and_axes(x, shape, axes)
|
||||
|
||||
assert shape_res == shape_expected
|
||||
assert axes_res == axes_expected
|
||||
|
||||
def test_xp_2d_defaults(self, xp):
|
||||
x = xp.arange(0, 1, .1)
|
||||
x = xp.reshape(x, (5, 2))
|
||||
shape = None
|
||||
axes = None
|
||||
|
||||
shape_expected = (5, 2)
|
||||
axes_expected = [0, 1]
|
||||
|
||||
shape_res, axes_res = _init_nd_shape_and_axes(x, shape, axes)
|
||||
|
||||
assert shape_res == shape_expected
|
||||
assert axes_res == axes_expected
|
||||
|
||||
def test_xp_5d_defaults(self, xp):
|
||||
x = xp.zeros([6, 2, 5, 3, 4])
|
||||
shape = None
|
||||
axes = None
|
||||
|
||||
shape_expected = (6, 2, 5, 3, 4)
|
||||
axes_expected = [0, 1, 2, 3, 4]
|
||||
|
||||
shape_res, axes_res = _init_nd_shape_and_axes(x, shape, axes)
|
||||
|
||||
assert shape_res == shape_expected
|
||||
assert axes_res == axes_expected
|
||||
|
||||
def test_xp_5d_set_shape(self, xp):
|
||||
x = xp.zeros([6, 2, 5, 3, 4])
|
||||
shape = [10, -1, -1, 1, 4]
|
||||
axes = None
|
||||
|
||||
shape_expected = (10, 2, 5, 1, 4)
|
||||
axes_expected = [0, 1, 2, 3, 4]
|
||||
|
||||
shape_res, axes_res = _init_nd_shape_and_axes(x, shape, axes)
|
||||
|
||||
assert shape_res == shape_expected
|
||||
assert axes_res == axes_expected
|
||||
|
||||
def test_xp_5d_set_axes(self, xp):
|
||||
x = xp.zeros([6, 2, 5, 3, 4])
|
||||
shape = None
|
||||
axes = [4, 1, 2]
|
||||
|
||||
shape_expected = (4, 2, 5)
|
||||
axes_expected = [4, 1, 2]
|
||||
|
||||
shape_res, axes_res = _init_nd_shape_and_axes(x, shape, axes)
|
||||
|
||||
assert shape_res == shape_expected
|
||||
assert axes_res == axes_expected
|
||||
|
||||
def test_xp_5d_set_shape_axes(self, xp):
|
||||
x = xp.zeros([6, 2, 5, 3, 4])
|
||||
shape = [10, -1, 2]
|
||||
axes = [1, 0, 3]
|
||||
|
||||
shape_expected = (10, 6, 2)
|
||||
axes_expected = [1, 0, 3]
|
||||
|
||||
shape_res, axes_res = _init_nd_shape_and_axes(x, shape, axes)
|
||||
|
||||
assert shape_res == shape_expected
|
||||
assert axes_res == axes_expected
|
||||
|
||||
def test_shape_axes_subset(self, xp):
|
||||
x = xp.zeros((2, 3, 4, 5))
|
||||
shape, axes = _init_nd_shape_and_axes(x, shape=(5, 5, 5), axes=None)
|
||||
|
||||
assert shape == (5, 5, 5)
|
||||
assert axes == [1, 2, 3]
|
||||
|
||||
def test_errors(self, xp):
|
||||
x = xp.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(xp.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)
|
||||
|
||||
|
||||
class TestFFTShift:
|
||||
|
||||
def test_definition(self, xp):
|
||||
x = xp.asarray([0., 1, 2, 3, 4, -4, -3, -2, -1])
|
||||
y = xp.asarray([-4., -3, -2, -1, 0, 1, 2, 3, 4])
|
||||
xp_assert_close(fft.fftshift(x), y)
|
||||
xp_assert_close(fft.ifftshift(y), x)
|
||||
x = xp.asarray([0., 1, 2, 3, 4, -5, -4, -3, -2, -1])
|
||||
y = xp.asarray([-5., -4, -3, -2, -1, 0, 1, 2, 3, 4])
|
||||
xp_assert_close(fft.fftshift(x), y)
|
||||
xp_assert_close(fft.ifftshift(y), x)
|
||||
|
||||
def test_inverse(self, xp):
|
||||
for n in [1, 4, 9, 100, 211]:
|
||||
x = xp.asarray(np.random.random((n,)))
|
||||
xp_assert_close(fft.ifftshift(fft.fftshift(x)), x)
|
||||
|
||||
def test_axes_keyword(self, xp):
|
||||
freqs = xp.asarray([[0., 1, 2], [3, 4, -4], [-3, -2, -1]])
|
||||
shifted = xp.asarray([[-1., -3, -2], [2, 0, 1], [-4, 3, 4]])
|
||||
xp_assert_close(fft.fftshift(freqs, axes=(0, 1)), shifted)
|
||||
xp_assert_close(fft.fftshift(freqs, axes=0), fft.fftshift(freqs, axes=(0,)))
|
||||
xp_assert_close(fft.ifftshift(shifted, axes=(0, 1)), freqs)
|
||||
xp_assert_close(fft.ifftshift(shifted, axes=0),
|
||||
fft.ifftshift(shifted, axes=(0,)))
|
||||
xp_assert_close(fft.fftshift(freqs), shifted)
|
||||
xp_assert_close(fft.ifftshift(shifted), freqs)
|
||||
|
||||
def test_uneven_dims(self, xp):
|
||||
""" Test 2D input, which has uneven dimension sizes """
|
||||
freqs = xp.asarray([
|
||||
[0, 1],
|
||||
[2, 3],
|
||||
[4, 5]
|
||||
], dtype=xp.float64)
|
||||
|
||||
# shift in dimension 0
|
||||
shift_dim0 = xp.asarray([
|
||||
[4, 5],
|
||||
[0, 1],
|
||||
[2, 3]
|
||||
], dtype=xp.float64)
|
||||
xp_assert_close(fft.fftshift(freqs, axes=0), shift_dim0)
|
||||
xp_assert_close(fft.ifftshift(shift_dim0, axes=0), freqs)
|
||||
xp_assert_close(fft.fftshift(freqs, axes=(0,)), shift_dim0)
|
||||
xp_assert_close(fft.ifftshift(shift_dim0, axes=[0]), freqs)
|
||||
|
||||
# shift in dimension 1
|
||||
shift_dim1 = xp.asarray([
|
||||
[1, 0],
|
||||
[3, 2],
|
||||
[5, 4]
|
||||
], dtype=xp.float64)
|
||||
xp_assert_close(fft.fftshift(freqs, axes=1), shift_dim1)
|
||||
xp_assert_close(fft.ifftshift(shift_dim1, axes=1), freqs)
|
||||
|
||||
# shift in both dimensions
|
||||
shift_dim_both = xp.asarray([
|
||||
[5, 4],
|
||||
[1, 0],
|
||||
[3, 2]
|
||||
], dtype=xp.float64)
|
||||
xp_assert_close(fft.fftshift(freqs, axes=(0, 1)), shift_dim_both)
|
||||
xp_assert_close(fft.ifftshift(shift_dim_both, axes=(0, 1)), freqs)
|
||||
xp_assert_close(fft.fftshift(freqs, axes=[0, 1]), shift_dim_both)
|
||||
xp_assert_close(fft.ifftshift(shift_dim_both, axes=[0, 1]), freqs)
|
||||
|
||||
# axes=None (default) shift in all dimensions
|
||||
xp_assert_close(fft.fftshift(freqs, axes=None), shift_dim_both)
|
||||
xp_assert_close(fft.ifftshift(shift_dim_both, axes=None), freqs)
|
||||
xp_assert_close(fft.fftshift(freqs), shift_dim_both)
|
||||
xp_assert_close(fft.ifftshift(shift_dim_both), freqs)
|
||||
|
||||
|
||||
@skip_xp_backends("cupy", "jax.numpy",
|
||||
reasons=["CuPy has not implemented the `device` param",
|
||||
"JAX has not implemented the `device` param"])
|
||||
class TestFFTFreq:
|
||||
|
||||
def test_definition(self, xp):
|
||||
x = xp.asarray([0, 1, 2, 3, 4, -4, -3, -2, -1], dtype=xp.float64)
|
||||
x2 = xp.asarray([0, 1, 2, 3, 4, -5, -4, -3, -2, -1], dtype=xp.float64)
|
||||
|
||||
# default dtype varies across backends
|
||||
|
||||
y = 9 * fft.fftfreq(9, xp=xp)
|
||||
xp_assert_close(y, x, check_dtype=False, check_namespace=True)
|
||||
|
||||
y = 9 * xp.pi * fft.fftfreq(9, xp.pi, xp=xp)
|
||||
xp_assert_close(y, x, check_dtype=False)
|
||||
|
||||
y = 10 * fft.fftfreq(10, xp=xp)
|
||||
xp_assert_close(y, x2, check_dtype=False)
|
||||
|
||||
y = 10 * xp.pi * fft.fftfreq(10, xp.pi, xp=xp)
|
||||
xp_assert_close(y, x2, check_dtype=False)
|
||||
|
||||
def test_device(self, xp):
|
||||
xp_test = array_namespace(xp.empty(0))
|
||||
devices = get_xp_devices(xp)
|
||||
for d in devices:
|
||||
y = fft.fftfreq(9, xp=xp, device=d)
|
||||
x = xp_test.empty(0, device=d)
|
||||
assert device(y) == device(x)
|
||||
|
||||
|
||||
@skip_xp_backends("cupy", "jax.numpy",
|
||||
reasons=["CuPy has not implemented the `device` param",
|
||||
"JAX has not implemented the `device` param"])
|
||||
class TestRFFTFreq:
|
||||
|
||||
def test_definition(self, xp):
|
||||
x = xp.asarray([0, 1, 2, 3, 4], dtype=xp.float64)
|
||||
x2 = xp.asarray([0, 1, 2, 3, 4, 5], dtype=xp.float64)
|
||||
|
||||
# default dtype varies across backends
|
||||
|
||||
y = 9 * fft.rfftfreq(9, xp=xp)
|
||||
xp_assert_close(y, x, check_dtype=False, check_namespace=True)
|
||||
|
||||
y = 9 * xp.pi * fft.rfftfreq(9, xp.pi, xp=xp)
|
||||
xp_assert_close(y, x, check_dtype=False)
|
||||
|
||||
y = 10 * fft.rfftfreq(10, xp=xp)
|
||||
xp_assert_close(y, x2, check_dtype=False)
|
||||
|
||||
y = 10 * xp.pi * fft.rfftfreq(10, xp.pi, xp=xp)
|
||||
xp_assert_close(y, x2, check_dtype=False)
|
||||
|
||||
def test_device(self, xp):
|
||||
xp_test = array_namespace(xp.empty(0))
|
||||
devices = get_xp_devices(xp)
|
||||
for d in devices:
|
||||
y = fft.rfftfreq(9, xp=xp, device=d)
|
||||
x = xp_test.empty(0, device=d)
|
||||
assert device(y) == device(x)
|
||||
@ -0,0 +1,84 @@
|
||||
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)
|
||||
|
||||
|
||||
@pytest.mark.slow
|
||||
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
|
||||
@ -0,0 +1,249 @@
|
||||
import numpy as np
|
||||
from numpy.testing import assert_allclose, assert_array_equal
|
||||
import pytest
|
||||
import math
|
||||
|
||||
from scipy.fft import dct, idct, dctn, idctn, dst, idst, dstn, idstn
|
||||
import scipy.fft as fft
|
||||
from scipy import fftpack
|
||||
from scipy.conftest import array_api_compatible
|
||||
from scipy._lib._array_api import copy, xp_assert_close
|
||||
|
||||
pytestmark = [array_api_compatible, pytest.mark.usefixtures("skip_xp_backends")]
|
||||
skip_xp_backends = pytest.mark.skip_xp_backends
|
||||
|
||||
SQRT_2 = math.sqrt(2)
|
||||
|
||||
# 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
|
||||
|
||||
|
||||
@skip_xp_backends(cpu_only=True)
|
||||
@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, 'backward', 'ortho', 'forward'])
|
||||
@pytest.mark.parametrize("orthogonalize", [False, True])
|
||||
def test_identity_1d(forward, backward, type, n, axis, norm, orthogonalize, xp):
|
||||
# Test the identity f^-1(f(x)) == x
|
||||
x = xp.asarray(np.random.rand(n, n))
|
||||
|
||||
y = forward(x, type, axis=axis, norm=norm, orthogonalize=orthogonalize)
|
||||
z = backward(y, type, axis=axis, norm=norm, orthogonalize=orthogonalize)
|
||||
xp_assert_close(z, x)
|
||||
|
||||
pad = [(0, 0)] * 2
|
||||
pad[axis] = (0, 4)
|
||||
|
||||
y2 = xp.asarray(np.pad(np.asarray(y), pad, mode='edge'))
|
||||
z2 = backward(y2, type, n, axis, norm, orthogonalize=orthogonalize)
|
||||
xp_assert_close(z2, x)
|
||||
|
||||
|
||||
@skip_xp_backends(np_only=True,
|
||||
reasons=['`overwrite_x` only supported for NumPy backend.'])
|
||||
@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, 'backward', 'ortho', 'forward'])
|
||||
@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).astype(dtype)
|
||||
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)
|
||||
|
||||
|
||||
@skip_xp_backends(cpu_only=True)
|
||||
@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, 'backward', 'ortho', 'forward'])
|
||||
@pytest.mark.parametrize("orthogonalize", [False, True])
|
||||
def test_identity_nd(forward, backward, type, shape, axes, norm,
|
||||
orthogonalize, xp):
|
||||
# Test the identity f^-1(f(x)) == x
|
||||
|
||||
x = xp.asarray(np.random.random(shape))
|
||||
|
||||
if axes is not None:
|
||||
shape = np.take(shape, axes)
|
||||
|
||||
y = forward(x, type, axes=axes, norm=norm, orthogonalize=orthogonalize)
|
||||
z = backward(y, type, axes=axes, norm=norm, orthogonalize=orthogonalize)
|
||||
xp_assert_close(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)
|
||||
|
||||
# TODO write an array-agnostic pad
|
||||
y2 = xp.asarray(np.pad(np.asarray(y), pad, mode='edge'))
|
||||
z2 = backward(y2, type, shape, axes, norm, orthogonalize=orthogonalize)
|
||||
xp_assert_close(z2, x)
|
||||
|
||||
|
||||
@skip_xp_backends(np_only=True,
|
||||
reasons=['`overwrite_x` only supported for NumPy backend.'])
|
||||
@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, 'backward', 'ortho', 'forward'])
|
||||
@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)
|
||||
|
||||
|
||||
@skip_xp_backends(cpu_only=True)
|
||||
@pytest.mark.parametrize("func", ['dct', 'dst', 'dctn', 'dstn'])
|
||||
@pytest.mark.parametrize("type", [1, 2, 3, 4])
|
||||
@pytest.mark.parametrize("norm", [None, 'backward', 'ortho', 'forward'])
|
||||
def test_fftpack_equivalience(func, type, norm, xp):
|
||||
x = np.random.rand(8, 16)
|
||||
fftpack_res = xp.asarray(getattr(fftpack, func)(x, type, norm=norm))
|
||||
x = xp.asarray(x)
|
||||
fft_res = getattr(fft, func)(x, type, norm=norm)
|
||||
|
||||
xp_assert_close(fft_res, fftpack_res)
|
||||
|
||||
|
||||
@skip_xp_backends(cpu_only=True)
|
||||
@pytest.mark.parametrize("func", [dct, dst, dctn, dstn])
|
||||
@pytest.mark.parametrize("type", [1, 2, 3, 4])
|
||||
def test_orthogonalize_default(func, type, xp):
|
||||
# Test orthogonalize is the default when norm="ortho", but not otherwise
|
||||
x = xp.asarray(np.random.rand(100))
|
||||
|
||||
for norm, ortho in [
|
||||
("forward", False),
|
||||
("backward", False),
|
||||
("ortho", True),
|
||||
]:
|
||||
a = func(x, type=type, norm=norm, orthogonalize=ortho)
|
||||
b = func(x, type=type, norm=norm)
|
||||
xp_assert_close(a, b)
|
||||
|
||||
|
||||
@skip_xp_backends(cpu_only=True)
|
||||
@pytest.mark.parametrize("norm", ["backward", "ortho", "forward"])
|
||||
@pytest.mark.parametrize("func, type", [
|
||||
(dct, 4), (dst, 1), (dst, 4)])
|
||||
def test_orthogonalize_noop(func, type, norm, xp):
|
||||
# Transforms where orthogonalize is a no-op
|
||||
x = xp.asarray(np.random.rand(100))
|
||||
y1 = func(x, type=type, norm=norm, orthogonalize=True)
|
||||
y2 = func(x, type=type, norm=norm, orthogonalize=False)
|
||||
xp_assert_close(y1, y2)
|
||||
|
||||
|
||||
@skip_xp_backends('jax.numpy',
|
||||
reasons=['jax arrays do not support item assignment'],
|
||||
cpu_only=True)
|
||||
@pytest.mark.parametrize("norm", ["backward", "ortho", "forward"])
|
||||
def test_orthogonalize_dct1(norm, xp):
|
||||
x = xp.asarray(np.random.rand(100))
|
||||
|
||||
x2 = copy(x, xp=xp)
|
||||
x2[0] *= SQRT_2
|
||||
x2[-1] *= SQRT_2
|
||||
|
||||
y1 = dct(x, type=1, norm=norm, orthogonalize=True)
|
||||
y2 = dct(x2, type=1, norm=norm, orthogonalize=False)
|
||||
|
||||
y2[0] /= SQRT_2
|
||||
y2[-1] /= SQRT_2
|
||||
xp_assert_close(y1, y2)
|
||||
|
||||
|
||||
@skip_xp_backends('jax.numpy',
|
||||
reasons=['jax arrays do not support item assignment'],
|
||||
cpu_only=True)
|
||||
@pytest.mark.parametrize("norm", ["backward", "ortho", "forward"])
|
||||
@pytest.mark.parametrize("func", [dct, dst])
|
||||
def test_orthogonalize_dcst2(func, norm, xp):
|
||||
x = xp.asarray(np.random.rand(100))
|
||||
y1 = func(x, type=2, norm=norm, orthogonalize=True)
|
||||
y2 = func(x, type=2, norm=norm, orthogonalize=False)
|
||||
|
||||
y2[0 if func == dct else -1] /= SQRT_2
|
||||
xp_assert_close(y1, y2)
|
||||
|
||||
|
||||
@skip_xp_backends('jax.numpy',
|
||||
reasons=['jax arrays do not support item assignment'],
|
||||
cpu_only=True)
|
||||
@pytest.mark.parametrize("norm", ["backward", "ortho", "forward"])
|
||||
@pytest.mark.parametrize("func", [dct, dst])
|
||||
def test_orthogonalize_dcst3(func, norm, xp):
|
||||
x = xp.asarray(np.random.rand(100))
|
||||
x2 = copy(x, xp=xp)
|
||||
x2[0 if func == dct else -1] *= SQRT_2
|
||||
|
||||
y1 = func(x, type=3, norm=norm, orthogonalize=True)
|
||||
y2 = func(x2, type=3, norm=norm, orthogonalize=False)
|
||||
xp_assert_close(y1, y2)
|
||||
|
||||
@skip_xp_backends(np_only=True,
|
||||
reasons=['array-likes only supported for NumPy backend'])
|
||||
@pytest.mark.parametrize("func", [dct, idct, dctn, idctn, dst, idst, dstn, idstn])
|
||||
def test_array_like(xp, func):
|
||||
x = [[[1.0, 1.0], [1.0, 1.0]],
|
||||
[[1.0, 1.0], [1.0, 1.0]],
|
||||
[[1.0, 1.0], [1.0, 1.0]]]
|
||||
xp_assert_close(func(x), func(xp.asarray(x)))
|
||||
Reference in New Issue
Block a user