Repository: scipy/scipy

Issue:
scipy.fft: What functions should be included
Related to #10238 

As well as the functions in `fftpack`, `numpy.fft` also has transforms for hermitian signals with real frequency space representations (`hfft` and `ihfft`). A notable omission is n-dimensional variants; there are no `hfftn` and `ihfftn`. Should we include these in `scipy.fft`?

There are also the pseudo-differential operators from  `scipy.fftpack` which aren't conceptually related to FFTs, they just happen to use it as an implementation detail.  So I would be in favour of leaving them out, at least for now.

### Currently Included
- `fft(n)`
- `ifft(n)`
- `rfft(n)`
- `irfft(n)`
- `(i)fftshift`
- `(r)fftfreq`
- `next_fast_len`

### Needs decision
- `hfft(n?)`
- `ihfft(n?)`






Discussion:
Since the n-dimensional variants are missing, and because it's much easier to add something later than it is to remove something already in the namespace (from a deprecation / API perspective), I'd say let's leave them out for now / in the first implementation. Basically start with some minimal set of functions in `scipy.fft` that are uncontroversial, and go from there. So that would be something like:

- `fft`(n)
- `ifft`(n)
- `rfft`(n)
- `irfft`(n)

@peterbell10 feel free to list here others that should be in there. If you agree with this approach, feel free to start editing the top comment to list the functions that should exist in the first PR that actually adds the `scipy.fft` module.
> There are also the pseudo-differential operators from scipy.fftpack which aren't conceptually related to FFTs, they just happen to use it as an implementation detail. So I would be in favour of leaving them out, at least for now.

+1
> Since the n-dimensional variants are missing

They're missing where exactly?
They are missing from numpy:
https://docs.scipy.org/doc/numpy/reference/routines.fft.html#hermitian-ffts
Oh sorry, I misread. Looking at Eric's bullet list, I thought me meant `fftn` rather than `hfftn`. 

So yes, I agree with the suggestion - leaving the n-dim versions out now, can be added for completeness later on (it would make sense to have them).
Other ones to decide are the helper functions: `fftshift`, `ifftshift`, `fftfreq` and `rfftfreq` all make sense. Less sure about `next_fast_len`; unclear how it should behave if it's included.
A few things worth noting about `next_fast_len`:
 - PocketFFT adds a new prime factor of 11, but only for complex ffts. So, that means `next_fast_len` will be inaccurate for at least one of these.
 - `next_fast_len` is used in scipy itself in a few places
https://github.com/scipy/scipy/blob/90534919e139d2a81c24bf08341734ff41a3db12/scipy/signal/windows/windows.py#L1976
https://github.com/scipy/scipy/blob/e42b2d1480161d30a744dec3eab6702aa16b940c/scipy/signal/signaltools.py#L406-L407

Perhaps the right thing to do is to include it, but clearly document that the length it returns is implementation-dependent and may change without warning. So code using it should only use it to pad, transform, and then unpad back to the same length.

And after I've written the above I'm then thinking: that would really benefit from a utility function to do those 3 steps, or a keyword in all fft functions that enables using it under the hood.
I think the problem is that you usually want to perform some convolution-theorem type operation so it's more like
```python
N = next_fast_len(len(x))
x_f = fft(x, N)
y = ifft(x_f * x_f.conj())[:len(x)] # Or some other operation in fourier-space
```
Which can't be made into one step since we don't know what they want to do in the fourier domain.

However, I agree that something like
```python
x_f = fft(x, pad_fast_len=True)
```
does make a lot of sense. But it might be harder to generalize to n-dimensions.
> Perhaps the right thing to do is to include it, but clearly document that the length it returns is implementation-dependent and may change without warning.

Sounds good to me. Agreed it should be implementation-dependent and noted in the docs that this is the case. 

To deal with the real/complex issue, we can add and argument for the `dtype` that will be used for the `fft` operation. For `pocketfft` it can vary depending on whether this is complex or real. For other backends it probably won't.
>As well as the functions in `fftpack`, `numpy.fft` also has transforms for hermitian signals with real frequency space representations (`hfft` and `ihfft`).

As an update to this, @mreineck is adding a [`forwards` argument][1] to `r2c` and `c2r` that would support this directly in pocketfft.

>A notable omission is n-dimensional variants; there are no hfftn and ihfftn.

I would speculate that the reason NumPy has avoided this is that once the transform is over more than one axis, it's no longer strictly hermitian, because of the mirroring that appears in my `xfftn_real` function
https://github.com/scipy/scipy/blob/5153c454d938f7c237496a7025f6a8c9c59f3641/scipy/fft/_pocketfft/scipy_features.h#L211-L212

So, the concept of a real-in-frequency-space transform is valid. However, calling it `hfftn` may be misleading.

[1]: https://gitlab.mpcdf.mpg.de/mtr/pypocketfft/issues/2#note_41639

Resolving patch:
diff --git a/scipy/fft/__init__.py b/scipy/fft/__init__.py
--- a/scipy/fft/__init__.py
+++ b/scipy/fft/__init__.py
@@ -23,6 +23,12 @@
    irfft2 - Inverse of rfft2
    rfftn - n-dimensional FFT of real sequence
    irfftn - Inverse of rfftn
+   hfft - FFT of a Hermitian sequence (real spectrum)
+   ihfft - Inverse of hfft
+   hfft2 - Two dimensional FFT of a Hermitian sequence
+   ihfft2 - Inverse of hfft2
+   hfftn - n-dimensional FFT of a Hermitian sequence
+   ihfftn - Inverse of hfftn
 
 Discrete Sin and Cosine Transforms (DST and DCT)
 ================================================
@@ -55,8 +61,9 @@
 from __future__ import division, print_function, absolute_import
 
 from ._basic import (
-    fft, ifft, fft2,ifft2, fftn, ifftn,
-    rfft, irfft, rfft2, irfft2, rfftn, irfftn)
+    fft, ifft, fft2, ifft2, fftn, ifftn,
+    rfft, irfft, rfft2, irfft2, rfftn, irfftn,
+    hfft, ihfft, hfft2, ihfft2, hfftn, ihfftn)
 from ._realtransforms import dct, idct, dst, idst, dctn, idctn, dstn, idstn
 from ._helper import next_fast_len
 from numpy.fft import fftfreq, rfftfreq, fftshift, ifftshift
@@ -64,6 +71,7 @@
 __all__ = [
     'fft', 'ifft', 'fft2','ifft2', 'fftn', 'ifftn',
     'rfft', 'irfft', 'rfft2', 'irfft2', 'rfftn', 'irfftn',
+    'hfft', 'ihfft', 'hfft2', 'ihfft2', 'hfftn', 'ihfftn',
     'fftfreq', 'rfftfreq', 'fftshift', 'ifftshift',
     'next_fast_len',
     'dct', 'idct', 'dst', 'idst', 'dctn', 'idctn', 'dstn', 'idstn']
diff --git a/scipy/fft/_basic.py b/scipy/fft/_basic.py
--- a/scipy/fft/_basic.py
+++ b/scipy/fft/_basic.py
@@ -388,6 +388,129 @@ def irfft(x, n=None, axis=-1, norm=None, overwrite_x=False):
     return _pocketfft.irfft(x, n, axis, norm, overwrite_x)
 
 
+def hfft(x, n=None, axis=-1, norm=None, overwrite_x=False):
+    """
+    Compute the FFT of a signal that has Hermitian symmetry, i.e., a real
+    spectrum.
+
+    Parameters
+    ----------
+    x : array_like
+        The input array.
+    n : int, optional
+        Length of the transformed axis of the output. For `n` output
+        points, ``n//2 + 1`` input points are necessary.  If the input is
+        longer than this, it is cropped.  If it is shorter than this, it is
+        padded with zeros. If `n` is not given, it is taken to be ``2*(m-1)``
+        where ``m`` is the length of the input along the axis specified by
+        `axis`.
+    axis : int, optional
+        Axis over which to compute the FFT. If not given, the last
+        axis is used.
+    norm : {None, "ortho"}, optional
+        Normalization mode (see `fft`). Default is None.
+    overwrite_x : bool, optional
+        If True, the contents of `x` can be destroyed; the default is False.
+        See `fft` for more details.
+
+    Returns
+    -------
+    out : ndarray
+        The truncated or zero-padded input, transformed along the axis
+        indicated by `axis`, or the last one if `axis` is not specified.
+        The length of the transformed axis is `n`, or, if `n` is not given,
+        ``2*m - 2`` where ``m`` is the length of the transformed axis of
+        the input. To get an odd number of output points, `n` must be
+        specified, for instance as ``2*m - 1`` in the typical case,
+
+    Raises
+    ------
+    IndexError
+        If `axis` is larger than the last axis of `a`.
+
+    See also
+    --------
+    rfft : Compute the one-dimensional FFT for real input.
+    ihfft : The inverse of `hfft`.
+    hfftn : Compute the n-dimensional FFT of a Hermitian signal.
+
+    Notes
+    -----
+    `hfft`/`ihfft` are a pair analogous to `rfft`/`irfft`, but for the
+    opposite case: here the signal has Hermitian symmetry in the time
+    domain and is real in the frequency domain. So here it's `hfft` for
+    which you must supply the length of the result if it is to be odd.
+    * even: ``ihfft(hfft(a, 2*len(a) - 2) == a``, within roundoff error,
+    * odd: ``ihfft(hfft(a, 2*len(a) - 1) == a``, within roundoff error.
+
+    Examples
+    --------
+    >>> from scipy.fft import fft, hfft
+    >>> a = 2 * np.pi * np.arange(10) / 10
+    >>> signal = np.cos(a) + 3j * np.sin(3 * a)
+    >>> fft(signal).round(10)
+    array([ -0.+0.j,   5.+0.j,  -0.+0.j,  15.-0.j,   0.+0.j,   0.+0.j,
+            -0.+0.j, -15.-0.j,   0.+0.j,   5.+0.j])
+    >>> hfft(signal[:6]).round(10) # Input first half of signal
+    array([  0.,   5.,   0.,  15.,  -0.,   0.,   0., -15.,  -0.,   5.])
+    >>> hfft(signal, 10)  # Input entire signal and truncate
+    array([  0.,   5.,   0.,  15.,  -0.,   0.,   0., -15.,  -0.,   5.])
+    """
+    return _pocketfft.hfft(x, n, axis, norm, overwrite_x)
+
+
+def ihfft(x, n=None, axis=-1, norm=None, overwrite_x=False):
+    """
+    Compute the inverse FFT of a signal that has Hermitian symmetry.
+
+    Parameters
+    ----------
+    x : array_like
+        Input array.
+    n : int, optional
+        Length of the inverse FFT, the number of points along
+        transformation axis in the input to use.  If `n` is smaller than
+        the length of the input, the input is cropped.  If it is larger,
+        the input is padded with zeros. If `n` is not given, the length of
+        the input along the axis specified by `axis` is used.
+    axis : int, optional
+        Axis over which to compute the inverse FFT. If not given, the last
+        axis is used.
+    norm : {None, "ortho"}, optional
+        Normalization mode (see `fft`). Default is None.
+
+    Returns
+    -------
+    out : complex ndarray
+        The truncated or zero-padded input, transformed along the axis
+        indicated by `axis`, or the last one if `axis` is not specified.
+        The length of the transformed axis is ``n//2 + 1``.
+
+    See also
+    --------
+    hfft, irfft
+
+    Notes
+    -----
+    `hfft`/`ihfft` are a pair analogous to `rfft`/`irfft`, but for the
+    opposite case: here the signal has Hermitian symmetry in the time
+    domain and is real in the frequency domain. So here it's `hfft` for
+    which you must supply the length of the result if it is to be odd:
+    * even: ``ihfft(hfft(a, 2*len(a) - 2) == a``, within roundoff error,
+    * odd: ``ihfft(hfft(a, 2*len(a) - 1) == a``, within roundoff error.
+
+    Examples
+    --------
+    >>> from scipy.fft import ifft, ihfft
+    >>> spectrum = np.array([ 15, -4, 0, -1, 0, -4])
+    >>> ifft(spectrum)
+    array([1.+0.j,  2.+0.j,  3.+0.j,  4.+0.j,  3.+0.j,  2.+0.j]) # may vary
+    >>> ihfft(spectrum)
+    array([ 1.-0.j,  2.-0.j,  3.-0.j,  4.-0.j]) # may vary
+    """
+    return _pocketfft.ihfft(x, n, axis, norm, overwrite_x)
+
+
 def fftn(x, s=None, axes=None, norm=None, overwrite_x=False):
     """
     Compute the N-dimensional discrete Fourier Transform.
@@ -1012,3 +1135,264 @@ def irfft2(x, s=None, axes=(-2, -1), norm=None, overwrite_x=False):
     """
 
     return _pocketfft.irfft2(x, s, axes, norm, overwrite_x)
+
+
+def hfftn(x, s=None, axes=None, norm=None, overwrite_x=False):
+    """
+    Compute the N-dimensional FFT of Hermitian symmetric complex input, i.e. a
+    signal with a real spectrum.
+
+    This function computes the N-dimensional discrete Fourier Transform for a
+    Hermitian symmetric complex input over any number of axes in an
+    M-dimensional array by means of the Fast Fourier Transform (FFT). In other
+    words, ``ihfftn(hfftn(x, s)) == x`` to within numerical accuracy. (``s``
+    here is ``x.shape`` with ``s[-1] = x.shape[-1] * 2 - 1``, this is necessary
+    for the same reason ``x.shape`` would be necessary for `irfft`.)
+
+    Parameters
+    ----------
+    x : array_like
+        Input array.
+    s : sequence of ints, optional
+        Shape (length of each transformed axis) of the output
+        (``s[0]`` refers to axis 0, ``s[1]`` to axis 1, etc.). `s` is also the
+        number of input points used along this axis, except for the last axis,
+        where ``s[-1]//2+1`` points of the input are used.
+        Along any axis, if the shape indicated by `s` is smaller than that of
+        the input, the input is cropped.  If it is larger, the input is padded
+        with zeros. If `s` is not given, the shape of the input along the axes
+        specified by axes is used. Except for the last axis which is taken to be
+        ``2*(m-1)`` where ``m`` is the length of the input along that axis.
+    axes : sequence of ints, optional
+        Axes over which to compute the inverse FFT. If not given, the last
+        `len(s)` axes are used, or all axes if `s` is also not specified.
+        Repeated indices in `axes` means that the inverse transform over that
+        axis is performed multiple times.
+    norm : {None, "ortho"}, optional
+        Normalization mode (see `fft`). Default is None.
+    overwrite_x : bool, optional
+        If True, the contents of `x` can be destroyed; the default is False.
+        See :func:`fft` for more details.
+
+    Returns
+    -------
+    out : ndarray
+        The truncated or zero-padded input, transformed along the axes
+        indicated by `axes`, or by a combination of `s` or `x`,
+        as explained in the parameters section above.
+        The length of each transformed axis is as given by the corresponding
+        element of `s`, or the length of the input in every axis except for the
+        last one if `s` is not given.  In the final transformed axis the length
+        of the output when `s` is not given is ``2*(m-1)`` where ``m`` is the
+        length of the final transformed axis of the input.  To get an odd
+        number of output points in the final axis, `s` must be specified.
+
+    Raises
+    ------
+    ValueError
+        If `s` and `axes` have different length.
+    IndexError
+        If an element of `axes` is larger than than the number of axes of `x`.
+
+    See Also
+    --------
+    ihfftn : The inverse n-dimensional FFT with real spectrum. Inverse of `hfftn`.
+    fft : The one-dimensional FFT, with definitions and conventions used.
+    rfft : Forward FFT of real input
+
+    Notes
+    -----
+
+    For a 1 dimensional signal ``x`` to have a real spectrum, it must satisfy
+    the Hermitian property::
+
+        x[i] == np.conj(x[-i]) for all i
+
+    This generalizes into higher dimensions by reflecting over each axis in
+    turn::
+
+        x[i, j, k, ...] == np.conj(x[-i, -j, -k, ...]) for all i, j, k, ...
+
+    This should not be confused with a Hermitian matrix, for which the
+    transpose is it's own conjugate::
+
+        x[i, j] == np.conj(x[j, i]) for all i, j
+
+
+    The default value of `s` assumes an even output length in the final
+    transformation axis. When performing the final complex to real
+    transformation, the Hermitian symmetry requires that the last imaginary
+    component along that axis must be 0 and so it is ignored. To avoid losing
+    information, the correct length of the real input *must* be given.
+
+    Examples
+    --------
+    >>> import scipy.fft
+    >>> x = np.ones((3, 2, 2))
+    >>> scipy.fft.hfftn(x)
+    array([[[12.,  0.],
+            [ 0.,  0.]],
+           [[ 0.,  0.],
+            [ 0.,  0.]],
+           [[ 0.,  0.],
+            [ 0.,  0.]]])
+
+    """
+    return _pocketfft.hfftn(x, s, axes, norm, overwrite_x)
+
+
+def hfft2(x, s=None, axes=(-2, -1), norm=None, overwrite_x=False):
+    """
+    Compute the 2-dimensional FFT of a Hermitian complex array.
+
+    Parameters
+    ----------
+    x : array
+        Input array, taken to be Hermitian complex.
+    s : sequence of ints, optional
+        Shape of the real output.
+    axes : sequence of ints, optional
+        Axes over which to compute the FFT.
+    norm : {None, "ortho"}, optional
+        Normalization mode (see `fft`). Default is None.
+    overwrite_x : bool, optional
+        If True, the contents of `x` can be destroyed; the default is False.
+        See `fft` for more details.
+
+    Returns
+    -------
+    out : ndarray
+        The real result of the 2D Hermitian complex real FFT.
+
+    See Also
+    --------
+    hfftn : Compute the N-dimensional discrete Fourier Transform for Hermitian
+            complex input.
+
+    Notes
+    -----
+    This is really just `hfftn` with different default behavior.
+    For more details see `hfftn`.
+
+    """
+    return _pocketfft.hfft2(x, s, axes, norm, overwrite_x)
+
+
+def ihfftn(x, s=None, axes=None, norm=None, overwrite_x=False):
+    """
+    Compute the N-dimensional inverse discrete Fourier Transform for a real
+    spectum.
+
+    This function computes the N-dimensional inverse discrete Fourier Transform
+    over any number of axes in an M-dimensional real array by means of the Fast
+    Fourier Transform (FFT). By default, all axes are transformed, with the
+    real transform performed over the last axis, while the remaining transforms
+    are complex.
+
+    Parameters
+    ----------
+    x : array_like
+        Input array, taken to be real.
+    s : sequence of ints, optional
+        Shape (length along each transformed axis) to use from the input.
+        (``s[0]`` refers to axis 0, ``s[1]`` to axis 1, etc.).
+        Along any axis, if the given shape is smaller than that of the input,
+        the input is cropped.  If it is larger, the input is padded with zeros.
+        if `s` is not given, the shape of the input along the axes specified
+        by `axes` is used.
+    axes : sequence of ints, optional
+        Axes over which to compute the FFT.  If not given, the last ``len(s)``
+        axes are used, or all axes if `s` is also not specified.
+    norm : {None, "ortho"}, optional
+        Normalization mode (see `fft`). Default is None.
+    overwrite_x : bool, optional
+        If True, the contents of `x` can be destroyed; the default is False.
+        See :func:`fft` for more details.
+
+    Returns
+    -------
+    out : complex ndarray
+        The truncated or zero-padded input, transformed along the axes
+        indicated by `axes`, or by a combination of `s` and `x`,
+        as explained in the parameters section above.
+        The length of the last axis transformed will be ``s[-1]//2+1``,
+        while the remaining transformed axes will have lengths according to
+        `s`, or unchanged from the input.
+
+    Raises
+    ------
+    ValueError
+        If `s` and `axes` have different length.
+    IndexError
+        If an element of `axes` is larger than than the number of axes of `x`.
+
+    See Also
+    --------
+    hfftn : The forward n-dimensional FFT of Hermitian input.
+    hfft : The one-dimensional FFT of Hermitian input.
+    fft : The one-dimensional FFT, with definitions and conventions used.
+    fftn : The n-dimensional FFT.
+    hfft2 : The two-dimensional FFT of Hermitian input.
+
+    Notes
+    -----
+
+    The transform for real input is performed over the last transformation
+    axis, as by `ihfft`, then the transform over the remaining axes is
+    performed as by `ifftn`. The order of the output is the positive part of
+    the Hermitian output signal, in the same format as `rfft`.
+
+    Examples
+    --------
+    >>> import scipy.fft
+    >>> x = np.ones((2, 2, 2))
+    >>> scipy.fft.ihfftn(x)
+    array([[[1.+0.j,  0.+0.j], # may vary
+            [0.+0.j,  0.+0.j]],
+           [[0.+0.j,  0.+0.j],
+            [0.+0.j,  0.+0.j]]])
+    >>> scipy.fft.ihfftn(x, axes=(2, 0))
+    array([[[1.+0.j,  0.+0.j], # may vary
+            [1.+0.j,  0.+0.j]],
+           [[0.+0.j,  0.+0.j],
+            [0.+0.j,  0.+0.j]]])
+
+    """
+    return _pocketfft.ihfftn(x, s, axes, norm, overwrite_x)
+
+
+def ihfft2(x, s=None, axes=(-2, -1), norm=None, overwrite_x=False):
+    """
+    Compute the 2-dimensional inverse FFT of a real spectrum.
+
+    Parameters
+    ----------
+    x : array_like
+        The input array
+    s : sequence of ints, optional
+        Shape of the real input to the inverse FFT.
+    axes : sequence of ints, optional
+        The axes over which to compute the inverse fft.
+        Default is the last two axes.
+    norm : {None, "ortho"}, optional
+        Normalization mode (see `fft`). Default is None.
+    overwrite_x : bool, optional
+        If True, the contents of `x` can be destroyed; the default is False.
+        See :func:`fft` for more details.
+
+    Returns
+    -------
+    out : ndarray
+        The result of the inverse real 2-D FFT.
+
+    See Also
+    --------
+    ihfftn : Compute the inverse of the N-dimensional FFT of Hermitian input.
+
+    Notes
+    -----
+    This is really `ihfftn` with different defaults.
+    For more details see `ihfftn`.
+
+    """
+    return _pocketfft.ihfft2(x, s, axes, norm, overwrite_x)
diff --git a/scipy/fft/_pocketfft/basic.py b/scipy/fft/_pocketfft/basic.py
--- a/scipy/fft/_pocketfft/basic.py
+++ b/scipy/fft/_pocketfft/basic.py
@@ -112,12 +112,12 @@ def c2c(forward, x, n=None, axis=-1, norm=None, overwrite_x=False):
 ifft.__name__ = 'ifft'
 
 
-def rfft(x, n=None, axis=-1, norm=None, overwrite_x=False):
+def r2c(forward, x, n=None, axis=-1, norm=None, overwrite_x=False):
     """
     Discrete Fourier transform of a real sequence.
     """
     tmp = _asfarray(x)
-    norm = _normalization(norm, True)
+    norm = _normalization(norm, forward)
 
     if not np.isrealobj(tmp):
         raise TypeError("x must be a real sequence")
@@ -129,15 +129,21 @@ def rfft(x, n=None, axis=-1, norm=None, overwrite_x=False):
                          .format(tmp.shape[axis]))
 
     # Note: overwrite_x is not utilised
-    return pfft.r2c(tmp, (axis,), True, norm, None, _default_workers)
+    return pfft.r2c(tmp, (axis,), forward, norm, None, _default_workers)
+
+
+rfft = functools.partial(r2c, True)
+rfft.__name__ = 'rfft'
+ihfft = functools.partial(r2c, False)
+ihfft.__name__ = 'ihfft'
 
 
-def irfft(x, n=None, axis=-1, norm=None, overwrite_x=False):
+def c2r(forward, x, n=None, axis=-1, norm=None, overwrite_x=False):
     """
     Return inverse discrete Fourier transform of real sequence x.
     """
     tmp = _asfarray(x)
-    norm = _normalization(norm, False)
+    norm = _normalization(norm, forward)
 
     # TODO: Optimize for hermitian and real?
     if np.isrealobj(tmp):
@@ -153,7 +159,13 @@ def irfft(x, n=None, axis=-1, norm=None, overwrite_x=False):
         tmp, _ = _fix_shape_1d(tmp, (n//2) + 1, axis)
 
     # Note: overwrite_x is not utilised
-    return pfft.c2r(tmp, (axis,), n, False, norm, None, _default_workers)
+    return pfft.c2r(tmp, (axis,), n, forward, norm, None, _default_workers)
+
+
+hfft = functools.partial(c2r, True)
+hfft.__name__ = 'hfft'
+irfft = functools.partial(c2r, False)
+irfft.__name__ = 'irfft'
 
 
 def fft2(x, shape=None, axes=(-2,-1), norm=None, overwrite_x=False):
@@ -184,6 +196,20 @@ def irfft2(x, shape=None, axes=(-2,-1), norm=None, overwrite_x=False):
     return irfftn(x, shape, axes, norm, overwrite_x)
 
 
+def hfft2(x, shape=None, axes=(-2,-1), norm=None, overwrite_x=False):
+    """
+    2-D discrete Fourier transform of a Hermitian sequence
+    """
+    return hfftn(x, shape, axes, norm, overwrite_x)
+
+
+def ihfft2(x, shape=None, axes=(-2,-1), norm=None, overwrite_x=False):
+    """
+    2-D discrete inverse Fourier transform of a Hermitian sequence
+    """
+    return ihfftn(x, shape, axes, norm, overwrite_x)
+
+
 def c2cn(forward, x, shape=None, axes=None, norm=None, overwrite_x=False):
     """
     Return multidimensional discrete Fourier transform.
@@ -210,7 +236,7 @@ def c2cn(forward, x, shape=None, axes=None, norm=None, overwrite_x=False):
 ifftn = functools.partial(c2cn, False)
 ifftn.__name__ = 'ifftn'
 
-def rfftn(x, shape=None, axes=None, norm=None, overwrite_x=False):
+def r2cn(forward, x, shape=None, axes=None, norm=None, overwrite_x=False):
     """Return multi-dimensional discrete Fourier transform of real input"""
     tmp = _asfarray(x)
 
@@ -219,15 +245,22 @@ def rfftn(x, shape=None, axes=None, norm=None, overwrite_x=False):
 
     shape, axes = _init_nd_shape_and_axes(tmp, shape, axes)
     tmp, _ = _fix_shape(tmp, shape, axes)
-    norm = _normalization(norm, True)
+    norm = _normalization(norm, forward)
 
     if len(axes) == 0:
         raise ValueError("at least 1 axis must be transformed")
 
     # Note: overwrite_x is not utilised
-    return pfft.r2c(tmp, axes, True, norm, None, _default_workers)
+    return pfft.r2c(tmp, axes, forward, norm, None, _default_workers)
+
 
-def irfftn(x, shape=None, axes=None, norm=None, overwrite_x=False):
+rfftn = functools.partial(r2cn, True)
+rfftn.__name__ = 'rfftn'
+ihfftn = functools.partial(r2cn, False)
+ihfftn.__name__ = 'ihfftn'
+
+
+def c2rn(forward, x, shape=None, axes=None, norm=None, overwrite_x=False):
     """Multi-dimensional inverse discrete fourier transform with real output"""
     tmp = _asfarray(x)
 
@@ -244,7 +277,7 @@ def irfftn(x, shape=None, axes=None, norm=None, overwrite_x=False):
     if noshape:
         shape[-1] = (x.shape[axes[-1]] - 1) * 2
 
-    norm = _normalization(norm, False)
+    norm = _normalization(norm, forward)
 
     # Last axis utilises hermitian symmetry
     lastsize = shape[-1]
@@ -253,4 +286,10 @@ def irfftn(x, shape=None, axes=None, norm=None, overwrite_x=False):
     tmp, _ = _fix_shape(tmp, shape, axes)
 
     # Note: overwrite_x is not utilised
-    return pfft.c2r(tmp, axes, lastsize, False, norm, None, _default_workers)
+    return pfft.c2r(tmp, axes, lastsize, forward, norm, None, _default_workers)
+
+
+hfftn = functools.partial(c2rn, True)
+hfftn.__name__ = 'hfftn'
+irfftn = functools.partial(c2rn, False)
+irfftn.__name__ = 'irfftn'


Test patch:
diff --git a/scipy/fft/tests/test_numpy.py b/scipy/fft/tests/test_numpy.py
--- a/scipy/fft/tests/test_numpy.py
+++ b/scipy/fft/tests/test_numpy.py
@@ -118,7 +118,6 @@ def test_irfftn(self):
         assert_array_almost_equal(
             x, fft.irfftn(fft.rfftn(x, norm="ortho"), norm="ortho"))
 
-    @pytest.mark.skip(reason="hfft not currently implemented")
     def test_hfft(self):
         x = random(14) + 1j*random(14)
         x_herm = np.concatenate((random(1), x, random(1)))
@@ -127,7 +126,6 @@ def test_hfft(self):
         assert_array_almost_equal(fft.hfft(x_herm) / np.sqrt(30),
                                   fft.hfft(x_herm, norm="ortho"))
 
-    @pytest.mark.skip(reason="ihfft not currently implemented")
     def test_ihfft(self):
         x = random(14) + 1j*random(14)
         x_herm = np.concatenate((random(1), x, random(1)))
@@ -137,8 +135,33 @@ def test_ihfft(self):
             x_herm, fft.ihfft(fft.hfft(x_herm, norm="ortho"),
                                  norm="ortho"))
 
+    def test_hfft2(self):
+        x = random((30, 20))
+        assert_array_almost_equal(x, fft.hfft2(fft.ihfft2(x)))
+        assert_array_almost_equal(
+            x, fft.hfft2(fft.ihfft2(x, norm="ortho"), norm="ortho"))
+
+    def test_ihfft2(self):
+        x = random((30, 20))
+        assert_array_almost_equal(fft.ifft2(x)[:, :11], fft.ihfft2(x))
+        assert_array_almost_equal(fft.ihfft2(x) * np.sqrt(30 * 20),
+                                  fft.ihfft2(x, norm="ortho"))
+
+    def test_hfftn(self):
+        x = random((30, 20, 10))
+        assert_array_almost_equal(x, fft.hfftn(fft.ihfftn(x)))
+        assert_array_almost_equal(
+            x, fft.hfftn(fft.ihfftn(x, norm="ortho"), norm="ortho"))
+
+    def test_ihfftn(self):
+        x = random((30, 20, 10))
+        assert_array_almost_equal(fft.ifftn(x)[:, :, :6], fft.ihfftn(x))
+        assert_array_almost_equal(fft.ihfftn(x) * np.sqrt(30 * 20 * 10),
+                                  fft.ihfftn(x, norm="ortho"))
+
     @pytest.mark.parametrize("op", [fft.fftn, fft.ifftn,
-                                    fft.rfftn, fft.irfftn])
+                                    fft.rfftn, fft.irfftn,
+                                    fft.hfftn, fft.ihfftn])
     def test_axes(self, op):
         x = random((30, 20, 10))
         axes = [(0, 1, 2), (0, 2, 1), (1, 0, 2), (1, 2, 0), (2, 0, 1), (2, 1, 0)]
@@ -156,7 +179,7 @@ def test_all_1d_norm_preserving(self):
                       (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),
+                      (fft.ihfft, fft.hfft),
                       ]
         for forw, back in func_pairs:
             for n in [x.size, 2*x.size]:
@@ -173,6 +196,7 @@ def test_dtypes(self, dtype):
         x = random(30).astype(dtype)
         assert_array_almost_equal(fft.ifft(fft.fft(x)), x)
         assert_array_almost_equal(fft.irfft(fft.rfft(x)), x)
+        assert_array_almost_equal(fft.hfft(fft.ihfft(x), len(x)), x)
 
 
 @pytest.mark.parametrize(
@@ -251,6 +275,14 @@ def test_irfft(self):
         a = np.ones(self.input_shape) * 1+0j
         self._test_mtsame(fft.irfft, a)
 
+    def test_hfft(self):
+        a = np.ones(self.input_shape, np.complex64)
+        self._test_mtsame(fft.hfft, a)
+
+    def test_ihfft(self):
+        a = np.ones(self.input_shape)
+        self._test_mtsame(fft.ihfft, a)
+
 
 class TestIRFFTN(object):