def ifftn(x, s=None, axes=None, norm=None, overwrite_x=False, *, plan=None): """Compute the N-dimensional inverse FFT. Args: x (cupy.ndarray): Array to be transformed. s (None or tuple of ints): Shape of the transformed axes of the output. If ``s`` is not given, the lengths of the input along the axes specified by ``axes`` are used. axes (tuple of ints): Axes over which to compute the FFT. norm (None or ``'ortho'``): Normalization mode. overwrite_x (bool): If True, the contents of ``x`` can be destroyed. plan (:class:`~cupy.cuda.cufft.PlanNd`) a cuFFT plan for transforming ``x`` over ``axis``, which can be obtained using:: plan = cupyx.scipy.fftpack.get_fft_plan(x, s, axes) Note that ``plan`` is defaulted to ``None``, meaning CuPy will use an auto-generated plan behind the scene. Returns: cupy.ndarray: The transformed array which shape is specified by ``s`` and type will convert to complex if that of the input is another. .. seealso:: :func:`scipy.fft.ifftn` """ s = _assequence(s) axes = _assequence(axes) func = _default_fft_func(x, s, axes) return func(x, s, axes, norm, cufft.CUFFT_INVERSE, overwrite_x=overwrite_x, plan=plan)
def ifftn(x, shape=None, axes=None, overwrite_x=False, plan=None): """Compute the N-dimensional inverse FFT. Args: x (cupy.ndarray): Array to be transformed. shape (None or tuple of ints): Shape of the transformed axes of the output. If ``shape`` is not given, the lengths of the input along the axes specified by ``axes`` are used. axes (tuple of ints): Axes over which to compute the FFT. overwrite_x (bool): If True, the contents of ``x`` can be destroyed. plan (cupy.cuda.cufft.PlanNd): a cuFFT plan for transforming ``x`` over ``axes``, which can be obtained using:: plan = cupyx.scipy.fftpack.get_fft_plan(x, axes) Note that `plan` is defaulted to None, meaning CuPy will either use an auto-generated plan behind the scene if cupy.fft.config. enable_nd_planning = True, or use no cuFFT plan if it is set to False. Returns: cupy.ndarray: The transformed array which shape is specified by ``shape`` and type will convert to complex if that of the input is another. .. seealso:: :func:`scipy.fftpack.ifftn` .. note:: The argument `plan` is currently experimental and the interface may be changed in the future version. """ func = _default_fft_func(x, shape, axes, plan) return func(x, shape, axes, None, cufft.CUFFT_INVERSE, overwrite_x=overwrite_x, plan=plan)
def rfftn(x, s=None, axes=None, norm=None, overwrite_x=False, *, plan=None): """Compute the N-dimensional FFT for real input. Args: a (cupy.ndarray): Array to be transform. s (None or tuple of ints): Shape to use from the input. If ``s`` is not given, the lengths of the input along the axes specified by ``axes`` are used. axes (tuple of ints): Axes over which to compute the FFT. norm (None or ``"ortho"``): Keyword to specify the normalization mode. overwrite_x (bool): If True, the contents of ``x`` can be destroyed. plan (None): This argument is currently not supported. Returns: cupy.ndarray: The transformed array which shape is specified by ``s`` and type will convert to complex if the input is other. The length of the last axis transformed will be ``s[-1]//2+1``. .. seealso:: :func:`scipy.fft.rfftn` """ # TODO(leofang): support R2C & C2R plans if plan is not None: raise NotImplementedError('rfftn plan is currently not yet supported') s = _assequence(s) axes = _assequence(axes) func = _default_fft_func(x, s, axes, value_type='R2C') return func(x, s, axes, norm, cufft.CUFFT_FORWARD, 'R2C', overwrite_x=overwrite_x)
def ifft2(x, shape=None, axes=(-2, -1), overwrite_x=False): """Compute the two-dimensional inverse FFT. Args: x (cupy.ndarray): Array to be transformed. shape (None or tuple of ints): Shape of the transformed axes of the output. If ``shape`` is not given, the lengths of the input along the axes specified by ``axes`` are used. axes (tuple of ints): Axes over which to compute the FFT. overwrite_x (bool): If True, the contents of ``x`` can be destroyed. Returns: cupy.ndarray: The transformed array which shape is specified by ``shape`` and type will convert to complex if that of the input is another. .. seealso:: :func:`scipy.fftpack.ifft2` """ func = _default_fft_func(x, shape, axes) return func(x, shape, axes, None, cufft.CUFFT_INVERSE, overwrite_x=overwrite_x)
def test_default_fft_func(self, enable_nd): # test cases where nd cuFFT plan is possible ca = cupy.ones((16, 16, 16)) for axes in [(0, 1), (1, 2), None, (0, 1, 2)]: fft_func = _default_fft_func(ca, axes=axes) if enable_nd: assert fft_func is _fftn else: assert fft_func is _fft # only a single axis is transformed -> 1d plan preferred for axes in [(0, ), (1, ), (2, )]: assert _default_fft_func(ca, axes=axes) is _fft # non-contiguous axes -> nd plan not possible assert _default_fft_func(ca, axes=(0, 2)) is _fft # >3 axes transformed -> nd plan not possible ca = cupy.ones((2, 4, 6, 8)) assert _default_fft_func(ca) is _fft # first or last axis not included -> nd plan not possible assert _default_fft_func(ca, axes=(1, )) is _fft # for rfftn ca = cupy.random.random((4, 2, 6)) for s, axes in zip([(3, 4), None, (8, 7, 5)], [(-2, -1), (0, 1), None]): fft_func = _default_fft_func(ca, s=s, axes=axes, value_type='R2C') if enable_nd: assert fft_func is _fftn else: assert fft_func is _fft # nd plan not possible if last axis is not 0 or ndim-1 assert _default_fft_func(ca, axes=(2, 1), value_type='R2C') is _fft # for irfftn ca = cupy.random.random((4, 2, 6)).astype(cupy.complex128) for s, axes in zip([(3, 4), None, (8, 7, 5)], [(-2, -1), (0, 1), None]): fft_func = _default_fft_func(ca, s=s, axes=axes, value_type='C2R') if enable_nd: assert fft_func is _fftn else: assert fft_func is _fft # nd plan not possible if last axis is not 0 or ndim-1 assert _default_fft_func(ca, axes=(2, 1), value_type='C2R') is _fft
def test_fftn_multiple_plan_error(self, dtype): import cupy import cupyx.scipy.fftpack as fftpack x = testing.shaped_random(self.shape, cupy, dtype) # hack: avoid testing the cases when getting a cuFFT plan is impossible if _default_fft_func(x, s=self.s, axes=self.axes) is not _fftn: return plan = fftpack.get_fft_plan(x, shape=self.s, axes=self.axes) with pytest.raises(RuntimeError) as ex, plan: fftpack.fftn(x, shape=self.s, axes=self.axes, plan=plan) assert 'Use the cuFFT plan either as' in str(ex.value)
def test_default_fft_func(self, enable_nd): # test cases where nd CUFFT plan is possible ca = cupy.ones((16, 16, 16)) for axes in [(0, 1), (1, 2), None, (0, 1, 2)]: fft_func = _default_fft_func(ca, axes=axes) if enable_nd: assert fft_func is _fftn else: assert fft_func is _fft # only a single axis is transformed -> 1d plan preferred for axes in [(0, ), (1, ), (2, )]: assert _default_fft_func(ca, axes=axes) is _fft # non-contiguous axes -> nd plan not possible assert _default_fft_func(ca, axes=(0, 2)) is _fft # >3 axes transformed -> nd plan not possible ca = cupy.ones((2, 4, 6, 8)) assert _default_fft_func(ca) is _fft # first or last axis not included -> nd plan not possible assert _default_fft_func(ca, axes=(1, )) is _fft
def test_ifftn_plan(self, xp, scp, dtype): x = testing.shaped_random(self.shape, xp, dtype) # hack: avoid testing the cases when getting a cuFFT plan is impossible if _default_fft_func(x, s=self.s, axes=self.axes) is not _fftn: return x if scp is cupyx.scipy: import cupy.fft.config as config config.enable_nd_planning = False # use explicit plan plan = scp.fftpack.get_fft_plan(x, shape=self.s, axes=self.axes) out = scp.fftpack.ifftn(x, shape=self.s, axes=self.axes, plan=plan) config.enable_nd_planning = True # default else: # scipy out = scp.fftpack.ifftn(x, shape=self.s, axes=self.axes) return out
def irfftn(x, s=None, axes=None, norm=None, overwrite_x=False, *, plan=None): """Compute the N-dimensional inverse FFT for real input. Args: a (cupy.ndarray): Array to be transform. s (None or tuple of ints): Shape of the output. If ``s`` is not given, they are determined from the lengths of the input along the axes specified by ``axes``. axes (tuple of ints): Axes over which to compute the FFT. norm (None or ``"ortho"``): Keyword to specify the normalization mode. overwrite_x (bool): If True, the contents of ``x`` can be destroyed. plan (:class:`cupy.cuda.cufft.PlanNd` or ``None``): a cuFFT plan for transforming ``x`` over ``axes``, which can be obtained using:: plan = cupyx.scipy.fftpack.get_fft_plan(x, s, axes, value_type='C2R') Note that ``plan`` is defaulted to ``None``, meaning CuPy will use an auto-generated plan behind the scene. Returns: cupy.ndarray: The transformed array which shape is specified by ``s`` and type will convert to complex if the input is other. If ``s`` is not given, the length of final transformed axis of output will be ``2*(m-1)`` where `m` is the length of the final transformed axis of the input. .. warning:: The input array may be modified in CUDA 10.1 and above, even when `overwrite_x is False`. .. seealso:: :func:`scipy.fft.irfftn` """ s = _assequence(s) axes = _assequence(axes) if (10020 >= cupy.cuda.runtime.runtimeGetVersion() >= 10010 and int(cupy.cuda.device.get_compute_capability()) < 70 and _size_last_transform_axis(x.shape, s, axes) == 2): warnings.warn('Output of irfftn might not be correct due to issue ' 'of cuFFT in CUDA 10.1/10.2 on Pascal or older GPUs.') func = _default_fft_func(x, s, axes, value_type='C2R') return func(x, s, axes, norm, cufft.CUFFT_INVERSE, 'C2R', overwrite_x=overwrite_x, plan=plan)
def test_ifftn_plan_manager(self, xp, scp, dtype): x = testing.shaped_random(self.shape, xp, dtype) # hack: avoid testing the cases when getting a cuFFT plan is impossible if _default_fft_func(x, s=self.s, axes=self.axes) is not _fftn: return x if scp is cupyx.scipy: from cupy.cuda.cufft import get_current_plan plan = scp.fftpack.get_fft_plan(x, shape=self.s, axes=self.axes) with plan: assert id(plan) == id(get_current_plan()) out = scp.fftpack.ifftn(x, shape=self.s, axes=self.axes) assert get_current_plan() is None else: # scipy out = scp.fftpack.ifftn(x, shape=self.s, axes=self.axes) return out
def test_fftn_orders(self, dtype, enable_nd): for order in ['C', 'F']: a = testing.shaped_random(self.shape, cupy, dtype) if order == 'F': a = cupy.asfortranarray(a) out = cupy.fft.fftn(a, s=self.s, axes=self.axes) fft_func = _default_fft_func(a, s=self.s, axes=self.axes) if fft_func is _fftn: # nd plans have output with contiguity matching the input self.assertEqual(out.flags.c_contiguous, a.flags.c_contiguous) self.assertEqual(out.flags.f_contiguous, a.flags.f_contiguous) else: # 1d planning case doesn't guarantee preserved contiguity pass
def test_ifftn_plan_manager(self, xp, dtype): x = testing.shaped_random(self.shape, xp, dtype) # hack: avoid testing the cases when getting a cuFFT plan is impossible if _default_fft_func(x, s=self.s, axes=self.axes) is not _fftn: return x x_orig = x.copy() if xp is cp: from cupy.cuda.cufft import get_current_plan plan = _fft_module(xp).get_fft_plan(x, shape=self.s, axes=self.axes) with plan: assert id(plan) == id(get_current_plan()) out = _fft_module(xp).ifftn(x, s=self.s, axes=self.axes) assert get_current_plan() is None else: out = _fft_module(xp).ifftn(x, s=self.s, axes=self.axes) testing.assert_array_equal(x, x_orig) return _correct_np_dtype(xp, dtype, out)
def test_ifftn_overwrite_plan(self, xp, dtype): x = testing.shaped_random(self.shape, xp, dtype) # hack: avoid testing the cases when getting a cuFFT plan is impossible if _default_fft_func(x, s=self.s, axes=self.axes) is not _fftn: return x if xp is cp: overwrite_kw = { 'plan': _fft_module(xp).get_fft_plan(x, shape=self.s, axes=self.axes), 'overwrite_x': True } else: overwrite_kw = {} out = _fft_module(xp).ifftn(x, s=self.s, axes=self.axes, norm=self.norm, **overwrite_kw) return _correct_np_dtype(xp, dtype, out)
def test_ifftn_plan(self, xp, dtype): x = testing.shaped_random(self.shape, xp, dtype) # hack: avoid testing the cases when getting a cuFFT plan is impossible if _default_fft_func(x, s=self.s, axes=self.axes) is not _fftn: return x x_orig = x.copy() if xp is cp: overwrite_kw = { 'plan': _fft_module(xp).get_fft_plan(x, shape=self.s, axes=self.axes) } else: overwrite_kw = {} out = _fft_module(xp).ifftn(x, s=self.s, axes=self.axes, norm=self.norm, **overwrite_kw) testing.assert_array_equal(x, x_orig) return _correct_np_dtype(xp, dtype, out)
def rfftn(x, s=None, axes=None, norm=None, overwrite_x=False, *, plan=None): """Compute the N-dimensional FFT for real input. Args: a (cupy.ndarray): Array to be transform. s (None or tuple of ints): Shape to use from the input. If ``s`` is not given, the lengths of the input along the axes specified by ``axes`` are used. axes (tuple of ints): Axes over which to compute the FFT. norm (None or ``"ortho"``): Keyword to specify the normalization mode. overwrite_x (bool): If True, the contents of ``x`` can be destroyed. plan (:class:`cupy.cuda.cufft.PlanNd` or ``None``): a cuFFT plan for transforming ``x`` over ``axes``, which can be obtained using:: plan = cupyx.scipy.fftpack.get_fft_plan(x, s, axes, value_type='R2C') Note that ``plan`` is defaulted to ``None``, meaning CuPy will use an auto-generated plan behind the scene. Returns: cupy.ndarray: The transformed array which shape is specified by ``s`` and type will convert to complex if the input is other. The length of the last axis transformed will be ``s[-1]//2+1``. .. seealso:: :func:`scipy.fft.rfftn` """ s = _assequence(s) axes = _assequence(axes) func = _default_fft_func(x, s, axes, value_type='R2C') return func(x, s, axes, norm, cufft.CUFFT_FORWARD, 'R2C', overwrite_x=overwrite_x, plan=plan)
def ifftn(x, s=None, axes=None, norm=None, overwrite_x=False): """Compute the N-dimensional inverse FFT. Args: x (cupy.ndarray): Array to be transformed. s (None or tuple of ints): Shape of the transformed axes of the output. If ``s`` is not given, the lengths of the input along the axes specified by ``axes`` are used. axes (tuple of ints): Axes over which to compute the FFT. norm (None or ``'ortho'``): Normalization mode. overwrite_x (bool): If True, the contents of ``x`` can be destroyed. Returns: cupy.ndarray: The transformed array which shape is specified by ``s`` and type will convert to complex if that of the input is another. .. seealso:: :func:`scipy.fft.ifftn` """ s = _assequence(s) axes = _assequence(axes) func = _default_fft_func(x, s, axes) return func(x, s, axes, norm, cufft.CUFFT_INVERSE, overwrite_x=overwrite_x)
def irfftn(x, s=None, axes=None, norm=None, overwrite_x=False, *, plan=None): """Compute the N-dimensional inverse FFT for real input. Args: a (cupy.ndarray): Array to be transform. s (None or tuple of ints): Shape of the output. If ``s`` is not given, they are determined from the lengths of the input along the axes specified by ``axes``. axes (tuple of ints): Axes over which to compute the FFT. norm (None or ``"ortho"``): Keyword to specify the normalization mode. overwrite_x (bool): If True, the contents of ``x`` can be destroyed. plan (None): This argument is currently not supported. Returns: cupy.ndarray: The transformed array which shape is specified by ``s`` and type will convert to complex if the input is other. If ``s`` is not given, the length of final transformed axis of output will be ``2*(m-1)`` where `m` is the length of the final transformed axis of the input. .. seealso:: :func:`scipy.fft.irfftn` """ # TODO(leofang): support R2C & C2R plans if plan is not None: raise NotImplementedError('irfftn plan is currently not yet supported') s = _assequence(s) axes = _assequence(axes) if (10020 >= cupy.cuda.runtime.runtimeGetVersion() >= 10010 and int(cupy.cuda.device.get_compute_capability()) < 70 and _size_last_transform_axis(x.shape, s, axes) == 2): warnings.warn('Output of irfftn might not be correct due to issue ' 'of cuFFT in CUDA 10.1/10.2 on Pascal or older GPUs.') func = _default_fft_func(x, s, axes, value_type='C2R') return func(x, s, axes, norm, cufft.CUFFT_INVERSE, 'C2R', overwrite_x=overwrite_x)