//  [python]
def to_spectral(self):
        """Transform to spectral space.

        This method returns a :class:`SpectralArray` containing the
        Fourier transform of the data.  For details of the algorithm,
        see :ref:`Computing Three-Dimensional FFTs with Distributed
        Arrays`.
        """
        # Index array which picks out retained modes in a complex transform
        N = self.grid.sdims
        M = self.grid.pdims
        i0 = numpy.array([*range(0, N[0]//2+1), *range(-((N[0]-1)//2), 0)])
        t1 = numpy.fft.rfft(
                 self._data,
                 axis=-1
                 )
        t1 = numpy.ascontiguousarray(t1)
        t2 = numpy.zeros(
            t1.shape[:-3]
            + (self.grid._local_x_slice.stop
               - self.grid._local_x_slice.start,
               self.grid.pdims[1], 
               self.grid._local_kz_slice.stop
               - self.grid._local_kz_slice.start),
            dtype=complex
            )
        # Note:
        # 1. The length of count and disp correspond to the number of slabs we
        #    are transferring between (which is the MPI rank).
        # 2. The value of count is the size of the tensor (which is the same
        #    for all slabs.
        # 3. The value of disp is the offset of the portion of the array we are
        #    transferring.  Since the MPI datatype is a subarray, the offset is
        #    always zero, with the datatype itself containing the true offset.
        # 4. The offset between each of the count items to be transferred,
        #    which is *count*, is also already embedded in the subarray MPI
        #    datatype.
        count = numpy.prod(self.shape[:-3], dtype=int)
        counts = [count] * self.grid.comm_zy.size
        disp = [0] * self.grid.comm_zy.size

        self.grid.comm_zy.Alltoallw(
            [t1, counts, disp, self.grid._kz_pencils],
            [t2, counts, disp, self.grid._y_pencils]
            )
        t3 = numpy.fft.fft(
                 t2,
                 axis=-2
                 )
        if self.grid._aliasing_strategy == 'mpi4py':
            if M[1] > N[1] and N[1] % 2 == 0:
                t3[..., :, N[1]//2, :] = \
                  t3[..., :, N[1]//2, :] + t3[..., :, -(N[1]//2), :]
        elif self.grid._aliasing_strategy == 'truncate':
            if M[1] > N[1] and N[1] % 2 == 0:
                t3[..., :, N[1]//2, :] = 0
        t3 = numpy.ascontiguousarray(t3)
        t4 = numpy.zeros(
            t3.shape[:-3]
            + (self.grid.pdims[0],
               self.grid._local_ky_slice.stop
               - self.grid._local_ky_slice.start, 
               self.grid._local_kz_slice.stop 
               - self.grid._local_kz_slice.start),
            dtype=complex
            )
        count = numpy.prod(self.shape[:-3], dtype=int)
        counts = [count] * self.grid.comm_yx.size
        disp = [0] * self.grid.comm_yx.size

        self.grid.comm_yx.Alltoallw(
            [t3, counts, disp, self.grid._ky_pencils],
            [t4, counts, disp, self.grid._x_pencils]
            )

        t5 = numpy.fft.fft(
            t4,
            axis=-3,
            )
        if self.grid._aliasing_strategy == 'mpi4py':
            if M[0] > N[0] and N[0] % 2 == 0:
                t5[..., N[0]//2, :, :] = \
                  t5[..., N[0]//2, :, :] + t5[..., -(N[0]//2), :, :]
        if self.grid._aliasing_strategy == 'truncate':
            if M[0] > N[0] and N[0] % 2 == 0:
                t5[..., N[0]//2, :, :] = 0
        t5 = t5[..., i0, :, :]/numpy.prod(self.grid.pdims)
        return SpectralArray(self.grid, t5)