def test_fourier_trafo_range(exponent, odl_floating_dtype): # Check if the range is initialized correctly. Encompasses the init test dtype = odl_floating_dtype # Testing R2C for real dtype, else C2C # 1D shape = 10 space_discr = odl.uniform_discr(0, 1, shape, exponent=exponent, impl='numpy', dtype=dtype) dft = FourierTransform(space_discr, halfcomplex=True, shift=True) assert dft.range.field == odl.ComplexNumbers() halfcomplex = True if is_real_dtype(dtype) else False assert dft.range.grid == reciprocal_grid(dft.domain.grid, halfcomplex=halfcomplex, shift=True) assert dft.range.exponent == conj_exponent(exponent) # 3D shape = (3, 4, 5) space_discr = odl.uniform_discr([0] * 3, [1] * 3, shape, exponent=exponent, impl='numpy', dtype=dtype) dft = FourierTransform(space_discr, halfcomplex=True, shift=True) assert dft.range.field == odl.ComplexNumbers() halfcomplex = True if is_real_dtype(dtype) else False assert dft.range.grid == reciprocal_grid(dft.domain.grid, halfcomplex=halfcomplex, shift=True) assert dft.range.exponent == conj_exponent(exponent) # shift must be True in the last axis if halfcomplex: with pytest.raises(ValueError): FourierTransform(space_discr, shift=(True, True, False)) if exponent != 2.0: with pytest.raises(NotImplementedError): dft.adjoint with pytest.raises(TypeError): FourierTransform(dft.domain.partition)
def test_fourier_trafo_range(exponent, floating_dtype): # Check if the range is initialized correctly. Encompasses the init test # Testing R2C for real dtype, else C2C # 1D shape = 10 space_discr = odl.uniform_discr(0, 1, shape, exponent=exponent, impl="numpy", dtype=floating_dtype) dft = FourierTransform(space_discr, halfcomplex=True, shift=True) assert dft.range.field == odl.ComplexNumbers() halfcomplex = True if is_real_dtype(floating_dtype) else False assert dft.range.grid == reciprocal_grid(dft.domain.grid, halfcomplex=halfcomplex, shift=True) assert dft.range.exponent == conj_exponent(exponent) # 3D shape = (3, 4, 5) space_discr = odl.uniform_discr([0] * 3, [1] * 3, shape, exponent=exponent, impl="numpy", dtype=floating_dtype) dft = FourierTransform(space_discr, halfcomplex=True, shift=True) assert dft.range.field == odl.ComplexNumbers() halfcomplex = True if is_real_dtype(floating_dtype) else False assert dft.range.grid == reciprocal_grid(dft.domain.grid, halfcomplex=halfcomplex, shift=True) assert dft.range.exponent == conj_exponent(exponent) # shift must be True in the last axis if halfcomplex: with pytest.raises(ValueError): FourierTransform(space_discr, shift=(True, True, False)) if exponent != 2.0: with pytest.raises(NotImplementedError): dft.adjoint with pytest.raises(TypeError): FourierTransform(dft.domain.partition)
def reciprocal_space(space, axes=None, halfcomplex=False, shift=True, **kwargs): """Return the range of the Fourier transform on ``space``. Parameters ---------- space : `DiscreteLp` Real space whose reciprocal is calculated. It must be uniformly discretized. axes : sequence of ints, optional Dimensions along which the Fourier transform is taken. Default: all axes halfcomplex : bool, optional If ``True``, take only the negative frequency part along the last axis for. For ``False``, use the full frequency space. This option can only be used if ``space`` is a space of real-valued functions. shift : bool or sequence of bools, optional If ``True``, the reciprocal grid is shifted by half a stride in the negative direction. With a boolean sequence, this option is applied separately to each axis. If a sequence is provided, it must have the same length as ``axes`` if supplied. Note that this must be set to ``True`` in the halved axis in half-complex transforms. Default: ``True`` impl : string, optional Implementation back-end for the created space. Default: ``'numpy'`` exponent : float, optional Create a space with this exponent. By default, the conjugate exponent ``q = p / (p - 1)`` of the exponent of ``space`` is used, where ``q = inf`` for ``p = 1`` and vice versa. dtype : optional Complex data type of the created space. By default, the complex counterpart of ``space.dtype`` is used. Returns ------- rspace : `DiscreteLp` Reciprocal of the input ``space``. If ``halfcomplex=True``, the upper end of the domain (where the half space ends) is chosen to coincide with the grid node. """ if not isinstance(space, DiscreteLp): raise TypeError('`space` {!r} is not a `DiscreteLp` instance' ''.format(space)) if axes is None: axes = tuple(range(space.ndim)) axes = normalized_axes_tuple(axes, space.ndim) if not all(space.is_uniform_byaxis[axis] for axis in axes): raise ValueError('`space` is not uniformly discretized in the ' '`axes` of the transform') if halfcomplex and space.field != RealNumbers(): raise ValueError('`halfcomplex` option can only be used with real ' 'spaces') exponent = kwargs.pop('exponent', None) if exponent is None: exponent = conj_exponent(space.exponent) dtype = kwargs.pop('dtype', None) if dtype is None: dtype = complex_dtype(space.dtype) else: if not is_complex_floating_dtype(dtype): raise ValueError('{} is not a complex data type' ''.format(dtype_repr(dtype))) impl = kwargs.pop('impl', 'numpy') # Calculate range recip_grid = reciprocal_grid(space.grid, shift=shift, halfcomplex=halfcomplex, axes=axes) # Make a partition with nodes on the boundary in the last transform axis # if `halfcomplex == True`, otherwise a standard partition. if halfcomplex: max_pt = {axes[-1]: recip_grid.max_pt[axes[-1]]} part = uniform_partition_fromgrid(recip_grid, max_pt=max_pt) else: part = uniform_partition_fromgrid(recip_grid) # Use convention of adding a hat to represent fourier transform of variable axis_labels = list(space.axis_labels) for i in axes: # Avoid double math label = axis_labels[i].replace('$', '') axis_labels[i] = '$\^{{{}}}$'.format(label) recip_spc = uniform_discr_frompartition(part, exponent=exponent, dtype=dtype, impl=impl, axis_labels=axis_labels) return recip_spc
def reciprocal_space(space, axes=None, halfcomplex=False, shift=True, **kwargs): """Return the range of the Fourier transform on ``space``. Parameters ---------- space : `DiscreteLp` Real space whose reciprocal is calculated. It must be uniformly discretized. axes : sequence of ints, optional Dimensions along which the Fourier transform is taken. Default: all axes halfcomplex : bool, optional If ``True``, take only the negative frequency part along the last axis for. For ``False``, use the full frequency space. This option can only be used if ``space`` is a space of real-valued functions. shift : bool or sequence of bools, optional If ``True``, the reciprocal grid is shifted by half a stride in the negative direction. With a boolean sequence, this option is applied separately to each axis. If a sequence is provided, it must have the same length as ``axes`` if supplied. Note that this must be set to ``True`` in the halved axis in half-complex transforms. Default: ``True`` impl : string, optional Implementation back-end for the created space. Default: ``'numpy'`` exponent : float, optional Create a space with this exponent. By default, the conjugate exponent ``q = p / (p - 1)`` of the exponent of ``space`` is used, where ``q = inf`` for ``p = 1`` and vice versa. dtype : optional Complex data type of the created space. By default, the complex counterpart of ``space.dtype`` is used. Returns ------- rspace : `DiscreteLp` Reciprocal of the input ``space``. If ``halfcomplex=True``, the upper end of the domain (where the half space ends) is chosen to coincide with the grid node. """ if not isinstance(space, DiscreteLp): raise TypeError('`space` {!r} is not a `DiscreteLp` instance' ''.format(space)) if not space.is_uniform: raise ValueError('`space` is not uniformly discretized') if axes is None: axes = tuple(range(space.ndim)) axes = normalized_axes_tuple(axes, space.ndim) if halfcomplex and space.field != RealNumbers(): raise ValueError('`halfcomplex` option can only be used with real ' 'spaces') exponent = kwargs.pop('exponent', None) if exponent is None: exponent = conj_exponent(space.exponent) dtype = kwargs.pop('dtype', None) if dtype is None: dtype = complex_dtype(space.dtype) else: if not is_complex_floating_dtype(dtype): raise ValueError('{} is not a complex data type' ''.format(dtype_repr(dtype))) impl = kwargs.pop('impl', 'numpy') # Calculate range recip_grid = reciprocal_grid(space.grid, shift=shift, halfcomplex=halfcomplex, axes=axes) # Make a partition with nodes on the boundary in the last transform axis # if `halfcomplex == True`, otherwise a standard partition. if halfcomplex: max_pt = {axes[-1]: recip_grid.max_pt[axes[-1]]} part = uniform_partition_fromgrid(recip_grid, max_pt=max_pt) else: part = uniform_partition_fromgrid(recip_grid) # Use convention of adding a hat to represent fourier transform of variable axis_labels = list(space.axis_labels) for i in axes: # Avoid double math label = axis_labels[i].replace('$', '') axis_labels[i] = '$\^{{{}}}$'.format(label) recip_spc = uniform_discr_frompartition(part, exponent=exponent, dtype=dtype, impl=impl, axis_labels=axis_labels) return recip_spc