Esempio n. 1
0
def pyfftw_call(array_in, array_out, direction='forward', axes=None,
                halfcomplex=False, **kwargs):
    """Calculate the DFT with pyfftw.

    The discrete Fourier (forward) transform calcuates the sum::

        f_hat[k] = sum_j( f[j] * exp(-2*pi*1j * j*k/N) )

    where the summation is taken over all indices
    ``j = (j[0], ..., j[d-1])`` in the range ``0 <= j < N``
    (component-wise), with ``N`` being the shape of the input array.

    The output indices ``k`` lie in the same range, except
    for half-complex transforms, where the last axis ``i`` in ``axes``
    is shortened to ``0 <= k[i] < floor(N[i]/2) + 1``.

    In the backward transform, sign of the the exponential argument
    is flipped.

    Parameters
    ----------
    array_in : `numpy.ndarray`
        Array to be transformed
    array_out : `numpy.ndarray`
        Output array storing the transformed values, may be aliased
        with ``array_in``.
    direction : {'forward', 'backward'}, optional
        Direction of the transform
    axes : int or sequence of ints, optional
        Dimensions along which to take the transform. ``None`` means
        using all axes and is equivalent to ``np.arange(ndim)``.
    halfcomplex : bool, optional
        If ``True``, calculate only the negative frequency part along the
        last axis. If ``False``, calculate the full complex FFT.
        This option can only be used with real input data.

    Other Parameters
    ----------------
    fftw_plan : ``pyfftw.FFTW``, optional
        Use this plan instead of calculating a new one. If specified,
        the options ``planning_effort``, ``planning_timelimit`` and
        ``threads`` have no effect.
    planning_effort : str, optional
        Flag for the amount of effort put into finding an optimal
        FFTW plan. See the `FFTW doc on planner flags
        <http://www.fftw.org/fftw3_doc/Planner-Flags.html>`_.
        Available options: {'estimate', 'measure', 'patient', 'exhaustive'}
        Default: 'estimate'
    planning_timelimit : float or ``None``, optional
        Limit planning time to roughly this many seconds.
        Default: ``None`` (no limit)
    threads : int, optional
        Number of threads to use.
        Default: Number of CPUs if the number of data points is larger
        than 4096, else 1.
    normalise_idft : bool, optional
        If ``True``, the result of the backward transform is divided by
        ``1 / N``, where ``N`` is the total number of points in
        ``array_in[axes]``. This ensures that the IDFT is the true
        inverse of the forward DFT.
        Default: ``False``
    import_wisdom : filename or file handle, optional
        File to load FFTW wisdom from. If the file does not exist,
        it is ignored.
    export_wisdom : filename or file handle, optional
        File to append the accumulated FFTW wisdom to

    Returns
    -------
    fftw_plan : ``pyfftw.FFTW``
        The plan object created from the input arguments. It can be
        reused for transforms of the same size with the same data types.
        Note that reuse only gives a speedup if the initial plan
        used a planner flag other than ``'estimate'``.
        If ``fftw_plan`` was specified, the returned object is a
        reference to it.

    Notes
    -----
    * The planning and direction flags can also be specified as
      capitalized and prepended by ``'FFTW_'``, i.e. in the original
      FFTW form.
    * For a ``halfcomplex`` forward transform, the arrays must fulfill
      ``array_out.shape[axes[-1]] == array_in.shape[axes[-1]] // 2 + 1``,
      and vice versa for backward transforms.
    * All planning schemes except ``'estimate'`` require an internal copy
      of the input array but are often several times faster after the
      first call (measuring results are cached). Typically,
      'measure' is a good compromise. If you cannot afford the copy,
      use ``'estimate'``.
    * If a plan is provided via the ``fftw_plan`` parameter, no copy
      is needed internally.
    """
    import pickle

    if not array_in.flags.aligned:
        raise ValueError('input array not aligned')

    if not array_out.flags.aligned:
        raise ValueError('output array not aligned')

    if axes is None:
        axes = tuple(range(array_in.ndim))

    axes = normalized_axes_tuple(axes, array_in.ndim)

    direction = _flag_pyfftw_to_odl(direction)
    fftw_plan_in = kwargs.pop('fftw_plan', None)
    planning_effort = _flag_pyfftw_to_odl(
        kwargs.pop('planning_effort', 'estimate')
    )
    planning_timelimit = kwargs.pop('planning_timelimit', None)
    threads = kwargs.pop('threads', None)
    normalise_idft = kwargs.pop('normalise_idft', False)
    wimport = kwargs.pop('import_wisdom', '')
    wexport = kwargs.pop('export_wisdom', '')

    # Cast input to complex if necessary
    array_in_copied = False
    if is_real_dtype(array_in.dtype) and not halfcomplex:
        # Need to cast array_in to complex dtype
        array_in = array_in.astype(complex_dtype(array_in.dtype))
        array_in_copied = True

    # Do consistency checks on the arguments
    _pyfftw_check_args(array_in, array_out, axes, halfcomplex, direction)

    # Import wisdom if possible
    if wimport:
        try:
            with open(wimport, 'rb') as wfile:
                wisdom = pickle.load(wfile)
        except IOError:
            wisdom = []
        except TypeError:  # Got file handle
            wisdom = pickle.load(wimport)

        if wisdom:
            pyfftw.import_wisdom(wisdom)

    # Copy input array if it hasn't been done yet and the planner is likely
    # to destroy it. If we already have a plan, we don't have to worry.
    planner_destroys = _pyfftw_destroys_input(
        [planning_effort], direction, halfcomplex, array_in.ndim)
    must_copy_array_in = fftw_plan_in is None and planner_destroys

    if must_copy_array_in and not array_in_copied:
        plan_arr_in = np.empty_like(array_in)
        flags = [_flag_odl_to_pyfftw(planning_effort), 'FFTW_DESTROY_INPUT']
    else:
        plan_arr_in = array_in
        flags = [_flag_odl_to_pyfftw(planning_effort)]

    if fftw_plan_in is None:
        if threads is None:
            if plan_arr_in.size <= 4096:  # Trade-off wrt threading overhead
                threads = 1
            else:
                threads = cpu_count()

        fftw_plan = pyfftw.FFTW(
            plan_arr_in, array_out, direction=_flag_odl_to_pyfftw(direction),
            flags=flags, planning_timelimit=planning_timelimit,
            threads=threads, axes=axes)
    else:
        fftw_plan = fftw_plan_in

    fftw_plan(array_in, array_out, normalise_idft=normalise_idft)

    if wexport:
        try:
            with open(wexport, 'ab') as wfile:
                pickle.dump(pyfftw.export_wisdom(), wfile)
        except TypeError:  # Got file handle
            pickle.dump(pyfftw.export_wisdom(), wexport)

    return fftw_plan
Esempio n. 2
0
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
Esempio n. 3
0
def pyfftw_call(array_in, array_out, direction='forward', axes=None,
                halfcomplex=False, **kwargs):
    """Calculate the DFT with pyfftw.

    The discrete Fourier (forward) transform calcuates the sum::

        f_hat[k] = sum_j( f[j] * exp(-2*pi*1j * j*k/N) )

    where the summation is taken over all indices
    ``j = (j[0], ..., j[d-1])`` in the range ``0 <= j < N``
    (component-wise), with ``N`` being the shape of the input array.

    The output indices ``k`` lie in the same range, except
    for half-complex transforms, where the last axis ``i`` in ``axes``
    is shortened to ``0 <= k[i] < floor(N[i]/2) + 1``.

    In the backward transform, sign of the the exponential argument
    is flipped.

    Parameters
    ----------
    array_in : `numpy.ndarray`
        Array to be transformed
    array_out : `numpy.ndarray`
        Output array storing the transformed values, may be aliased
        with ``array_in``.
    direction : {'forward', 'backward'}
        Direction of the transform
    axes : int or sequence of ints, optional
        Dimensions along which to take the transform. ``None`` means
        using all axes and is equivalent to ``np.arange(ndim)``.
    halfcomplex : bool, optional
        If ``True``, calculate only the negative frequency part along the
        last axis. If ``False``, calculate the full complex FFT.
        This option can only be used with real input data.

    Other Parameters
    ----------------
    fftw_plan : ``pyfftw.FFTW``, optional
        Use this plan instead of calculating a new one. If specified,
        the options ``planning_effort``, ``planning_timelimit`` and
        ``threads`` have no effect.
    planning_effort : {'estimate', 'measure', 'patient', 'exhaustive'}
        Flag for the amount of effort put into finding an optimal
        FFTW plan. See the `FFTW doc on planner flags
        <http://www.fftw.org/fftw3_doc/Planner-Flags.html>`_.
        Default: 'estimate'
    planning_timelimit : float or ``None``, optional
        Limit planning time to roughly this many seconds.
        Default: ``None`` (no limit)
    threads : int, optional
        Number of threads to use.
        Default: Number of CPUs if the number of data points is larger
        than 4096, else 1.
    normalise_idft : bool, optional
        If ``True``, the result of the backward transform is divided by
        ``1 / N``, where ``N`` is the total number of points in
        ``array_in[axes]``. This ensures that the IDFT is the true
        inverse of the forward DFT.
        Default: ``False``
    import_wisdom : filename or file handle, optional
        File to load FFTW wisdom from. If the file does not exist,
        it is ignored.
    export_wisdom : filename or file handle, optional
        File to append the accumulated FFTW wisdom to

    Returns
    -------
    fftw_plan : ``pyfftw.FFTW``
        The plan object created from the input arguments. It can be
        reused for transforms of the same size with the same data types.
        Note that reuse only gives a speedup if the initial plan
        used a planner flag other than ``'estimate'``.
        If ``fftw_plan`` was specified, the returned object is a
        reference to it.

    Notes
    -----
    * The planning and direction flags can also be specified as
      capitalized and prepended by ``'FFTW_'``, i.e. in the original
      FFTW form.
    * For a ``halfcomplex`` forward transform, the arrays must fulfill
      ``array_out.shape[axes[-1]] == array_in.shape[axes[-1]] // 2 + 1``,
      and vice versa for backward transforms.
    * All planning schemes except ``'estimate'`` require an internal copy
      of the input array but are often several times faster after the
      first call (measuring results are cached). Typically,
      'measure' is a good compromise. If you cannot afford the copy,
      use ``'estimate'``.
    * If a plan is provided via the ``fftw_plan`` parameter, no copy
      is needed internally.
    """
    import pickle

    if not array_in.flags.aligned:
        raise ValueError('input array not aligned')

    if not array_out.flags.aligned:
        raise ValueError('output array not aligned')

    if axes is None:
        axes = tuple(range(array_in.ndim))

    axes = normalized_axes_tuple(axes, array_in.ndim)

    direction = _pyfftw_to_local(direction)
    fftw_plan_in = kwargs.pop('fftw_plan', None)
    planning_effort = _pyfftw_to_local(kwargs.pop('planning_effort',
                                                  'estimate'))
    planning_timelimit = kwargs.pop('planning_timelimit', None)
    threads = kwargs.pop('threads', None)
    normalise_idft = kwargs.pop('normalise_idft', False)
    wimport = kwargs.pop('import_wisdom', '')
    wexport = kwargs.pop('export_wisdom', '')

    # Cast input to complex if necessary
    array_in_copied = False
    if is_real_dtype(array_in.dtype) and not halfcomplex:
        # Need to cast array_in to complex dtype
        array_in = array_in.astype(complex_dtype(array_in.dtype))
        array_in_copied = True

    # Do consistency checks on the arguments
    _pyfftw_check_args(array_in, array_out, axes, halfcomplex, direction)

    # Import wisdom if possible
    if wimport:
        try:
            with open(wimport, 'rb') as wfile:
                wisdom = pickle.load(wfile)
        except IOError:
            wisdom = []
        except TypeError:  # Got file handle
            wisdom = pickle.load(wimport)

        if wisdom:
            pyfftw.import_wisdom(wisdom)

    # Copy input array if it hasn't been done yet and the planner is likely
    # to destroy it. If we already have a plan, we don't have to worry.
    planner_destroys = _pyfftw_destroys_input(
        [planning_effort], direction, halfcomplex, array_in.ndim)
    must_copy_array_in = fftw_plan_in is None and planner_destroys

    if must_copy_array_in and not array_in_copied:
        plan_arr_in = np.empty_like(array_in)
        flags = [_local_to_pyfftw(planning_effort), 'FFTW_DESTROY_INPUT']
    else:
        plan_arr_in = array_in
        flags = [_local_to_pyfftw(planning_effort)]

    if fftw_plan_in is None:
        if threads is None:
            if plan_arr_in.size <= 4096:  # Trade-off wrt threading overhead
                threads = 1
            else:
                threads = cpu_count()

        fftw_plan = pyfftw.FFTW(
            plan_arr_in, array_out, direction=_local_to_pyfftw(direction),
            flags=flags, planning_timelimit=planning_timelimit,
            threads=threads, axes=axes)
    else:
        fftw_plan = fftw_plan_in

    fftw_plan(array_in, array_out, normalise_idft=normalise_idft)

    if wexport:
        try:
            with open(wexport, 'ab') as wfile:
                pickle.dump(pyfftw.export_wisdom(), wfile)
        except TypeError:  # Got file handle
            pickle.dump(pyfftw.export_wisdom(), wexport)

    return fftw_plan
Esempio n. 4
0
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