diff --git a/docs/api/fft.md b/docs/api/fft.md index f6b0567..00b7201 100644 --- a/docs/api/fft.md +++ b/docs/api/fft.md @@ -11,5 +11,6 @@ :toctree: ../_autosummary fft + ifft rfft ``` \ No newline at end of file diff --git a/tests/test_fft.py b/tests/test_fft.py index 371c644..267af28 100644 --- a/tests/test_fft.py +++ b/tests/test_fft.py @@ -9,3 +9,21 @@ def test_with_non_dimensional(self): da = xd.synthetics.wavelet_wavefronts() da["latitude"] = ("distance", np.arange(da.sizes["distance"])) xfft.rfft(da) + + +class TestIFFT: + def test_base(self): + expected = xd.synthetics.wavelet_wavefronts() + result = xfft.ifft( + xfft.fft(expected, dim={"time": "frequency"}), dim={"frequency": "time"} + ) + assert np.allclose(np.real(result).values, expected.values) + assert np.allclose(np.imag(result).values, 0) + for name in result.coords: + if name == "time": + ref = expected["time"].values + ref = (ref - ref[0]) / np.timedelta64(1, "s") + ref += result["time"][0].values + assert np.allclose(result["time"].values, ref) + else: + assert result[name].equals(expected[name]) diff --git a/xdas/fft.py b/xdas/fft.py index fdf5055..443b1f8 100644 --- a/xdas/fft.py +++ b/xdas/fft.py @@ -116,3 +116,60 @@ def rfft(da, n=None, dim={"last": "frequency"}, norm=None, parallel=None): } dims = tuple(newdim if dim == olddim else dim for dim in da.dims) return DataArray(data, coords, dims, da.name, da.attrs) + + +@atomized +def ifft(da, n=None, dim={"last": "time"}, norm=None, parallel=None): + """ + Compute the inverse discrete Fourier Transform along a given dimension. + + This function computes the inverse of the one-dimensional n-point discrete Fourier + transform computed by fft. In other words, ifft(fft(a)) == a to within numerical + accuracy. + + Parameters + ---------- + da: DataArray + The data array to process, should be complex. + n: int, optional + Length of transformed dimension of the output. 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 dimension specified + by `dim` is used. + dim: {str: str}, optional + A mapping indicating as a key the dimension along which to compute the IFFT, and + as value the new name of the dimension. Default to {"last": "spectrum"}. + norm: {“backward”, “ortho”, “forward”}, optional + Normalization mode (see `numpy.fft`). Default is "backward". Indicates which + direction of the forward/backward pair of transforms is scaled and with what + normalization factor. + + Returns + ------- + DataArray: + The transformed input with an updated dimension name and values. + + Notes + ----- + - To perform a multidimensional inverse fourrier transform, repeat this function on + the desired dimensions. + + """ + ((olddim, newdim),) = dim.items() + olddim = da.dims[da.get_axis_num(olddim)] + if n is None: + n = da.sizes[olddim] + axis = da.get_axis_num(olddim) + d = get_sampling_interval(da, olddim) + f = np.fft.ifftshift(np.fft.fftfreq(n, d)) + func = lambda x: np.fft.ifft(np.fft.ifftshift(x, axis), n, axis, norm) + across = int(axis == 0) + func = parallelize(across, across, parallel)(func) + data = func(da.values) + coords = { + newdim if name == olddim else name: f if name == olddim else da.coords[name] + for name in da.coords + if (da[name].dim != olddim or name == olddim) + } + dims = tuple(newdim if dim == olddim else dim for dim in da.dims) + return DataArray(data, coords, dims, da.name, da.attrs)