<SYSTEM_TASK:>
Solve the following problem using Python, implementing the functions described below, one line at a time
<END_TASK>
<USER_TASK:>
Description:
 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)

    # Need to do this for axes of length 1 that are not transformed
    non_axes = [i for i in range(space.ndim) if i not in axes]
    min_pt = {i: space.min_pt[i] for i in non_axes}
    max_pt = {i: space.max_pt[i] for i in non_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, min_pt, max_pt)

    # 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