Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding idft #129

Merged
merged 35 commits into from
Jan 28, 2021
Merged
Changes from 4 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
256824f
Merge pull request #1 from xgcm/master
lanougue Nov 10, 2020
e7d8f2b
Merge pull request #2 from xgcm/master
lanougue Nov 17, 2020
7849305
Merge pull request #3 from xgcm/master
lanougue Dec 4, 2020
bb59a8a
updated idft
Dec 4, 2020
148fb26
update idft + fft + ifft
Dec 10, 2020
0ee8ad5
debug spacing_tol
Dec 11, 2020
0515c57
adding tests and warning
Dec 11, 2020
5bf333a
adding dft parseval tests + some typos
Dec 11, 2020
eeec439
moving test to parseval function
Dec 11, 2020
8b97038
correction idft with real
Dec 13, 2020
0502b21
simplification power spectrum
Dec 13, 2020
792cc2b
simplification spectrum and adding true_flags
Dec 14, 2020
6333630
simplification cross phase
Dec 14, 2020
d9f3d87
debug spectrum with False density
Dec 14, 2020
5ff68a7
debug cross-spectrum test
Dec 14, 2020
1e33e06
debug test cross_phase
Dec 14, 2020
f8a04f0
debug test cross_phase 2
Dec 14, 2020
b49f132
adding cross phase tests
Dec 14, 2020
febecb5
adding cross phase tests 2
Dec 14, 2020
b7684d1
adding test_real_dft_true_phase + scaling parameter/density deprecation
Dec 15, 2020
ac511fc
rm old code
Dec 15, 2020
886850e
typo + unknown sclaing
Dec 15, 2020
c4bc754
debug test_isotropize
Dec 15, 2020
c325c54
flag warning + adding test
Dec 15, 2020
7af46ce
update test density error
Dec 15, 2020
a8275f9
test_coordinates + idft default behaviour
Dec 15, 2020
644c731
restoring default dft true_flags
Dec 15, 2020
186a4aa
correct indentation
Dec 15, 2020
5ca6ae7
adding warning
Dec 15, 2020
5beb1aa
user warning
Dec 15, 2020
fbb9d06
density and scaling flag handling
Dec 15, 2020
9a3caaa
restoring phase warning in cross_spectrum
Dec 15, 2020
dcb40ee
function signature
Dec 15, 2020
41ec321
function signature2 + rm comments
Dec 16, 2020
5cb5cd2
dask debug in test
Dec 16, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
181 changes: 181 additions & 0 deletions xrft/xrft.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

__all__ = [
"dft",
"idft",
"power_spectrum",
"cross_spectrum",
"cross_phase",
Expand Down Expand Up @@ -333,6 +334,186 @@ def dft(
) # Do nothing if da was not transposed


def idft(
da,
roxyboy marked this conversation as resolved.
Show resolved Hide resolved
spacing_tol=1e-3,
dim=None,
real=None,
shift=True,
detrend=None,
true_phase=False,
window=False,
chunks_to_segments=False,
prefix="freq_",
lag=None,
):
"""
Perform inverse discrete Fourier transform of xarray data-array `da` along the
specified dimensions.

.. math::
daft = \mathbb{F}(da - \overline{da})

Parameters
----------
da : `xarray.DataArray`
The data to be transformed
spacing_tol: float, optional
Spacing tolerance. Fourier transform should not be applied to uneven grid but
this restriction can be relaxed with this setting. Use caution.
dim : str or sequence of str, optional
The dimensions along which to take the transformation. If `None`, all
dimensions will be transformed.
real : str, optional
Real Fourier transform will be taken along this dimension.
shift : bool, default
Whether to shift the fft output. Default is `True`, unless `real=True`,
in which case shift will be set to False always.
detrend : str, optional
If `constant`, the mean across the transform dimensions will be
subtracted before calculating the Fourier transform (FT).
If `linear`, the linear least-square fit will be subtracted before
the FT.
window : bool, optional
Whether to apply a Hann window to the data before the Fourier
transform is taken. A window will be applied to all the dimensions in
dim.
chunks_to_segments : bool, optional
Whether the data is chunked along the axis to take FFT.
prefix : str
The prefix for the new transformed dimensions.
lag : float or sequence of float, optional
If lag is None or zero, output coordinates are centered on zero.
If defined, lag must have same length as dim.
Output coordinates corresponding to transformed dimensions will be shifted by corresponding lag values.
Correct phase will be preserved if true_phase is set to True.

Returns
-------
daft : `xarray.DataArray`
roxyboy marked this conversation as resolved.
Show resolved Hide resolved
The output of the Inverse Fourier transformation, with appropriate dimensions.
"""
import warnings

if dim is None:
dim = list(da.dims)
else:
if isinstance(dim, str):
dim = [dim]

if real is not None:
if real not in da.dims:
raise ValueError(
"The dimension along which real FT is taken must be one of the existing dimensions."
)
else:
dim = [d for d in dim if d != real] + [
real
] # real dim has to be moved or added at the end !

if lag is not None:
if isinstance(lag, float) or isinstance(lag, int):
lag = [lag]
if len(dim) != len(lag):
raise ValueError("dim and lag must have the same length.")
if not true_phase:
msg = "Setting lag with true_phase=False do not guaranty accurate idft."
warnings.warn(msg, Warning)

for d, l in zip(dim, lag):
da = da * np.exp(1j * 2.0 * np.pi * da[d] * l)

if chunks_to_segments:
da = _stack_chunks(da, dim)

rawdims = da.dims # take care of segmented dimesions, if any

if real is not None:
da = da.transpose(
*[d for d in da.dims if d not in [real]] + [real]
) # dimension for real transformed is moved at the end

fft = _fft_module(da)

if real is None:
fft_fn = fft.ifftn
else:
shift = False
fft_fn = fft.irfftn

# the axes along which to take ffts
axis_num = [da.get_axis_num(d) for d in dim]

N = [da.shape[n] for n in axis_num]

# verify even spacing of input coordinates (It handle fftshifted grids)
delta_x = []
for d in dim:
diff = _diff_coord(da[d])
delta = np.abs(diff[0])
l = _lag_coord(da[d])
if not np.allclose(
diff, diff[0], rtol=spacing_tol
): # means that input is not on regular increasing grid
reordered_coord = da[d].copy()
reordered_coord = reordered_coord.sortby(d)
diff = _diff_coord(reordered_coord)
l = _lag_coord(reordered_coord)
if np.allclose(
diff, diff[0], rtol=spacing_tol
): # means that input is on fftshifted grid
da = da.sortby(d) # reordering the input
else:
raise ValueError(
"Can't take Fourier transform because "
"coodinate %s is not evenly spaced" % d
)
if np.abs(l) > spacing_tol:
raise ValueError(
"idft can not be computed because coordinate %s is not centered on zero frequency"
% d
)
delta_x.append(delta)

if detrend:
da = _apply_detrend(da, dim, axis_num, detrend)

if window:
da = _apply_window(da, dim)

f = fft.ifftshift(
da.data, axes=axis_num
) # Force to be on fftshift grid before Fourier Transform
f = fft_fn(f, axes=axis_num)

if not true_phase:
f = fft.ifftshift(f, axes=axis_num)

if shift:
f = fft.fftshift(f, axes=axis_num)

k = _freq(N, delta_x, real, shift)

newcoords, swap_dims = _new_dims_and_coords(da, dim, k, prefix)
daft = xr.DataArray(
f, dims=da.dims, coords=dict([c for c in da.coords.items() if c[0] not in dim])
)
daft = daft.swap_dims(swap_dims).assign_coords(newcoords)
daft = daft.drop([d for d in dim if d in daft.coords])

if lag is not None:
with xr.set_options(
keep_attrs=True
): # This line ensures keeping spacing attribute in output coordinates
for d, l in zip(dim, lag):
tfd = swap_dims[d]
daft = daft.assign_coords({tfd: daft[tfd] + l})

return daft.transpose(
*[swap_dims.get(d, d) for d in rawdims]
) # Do nothing if da was not transposed


def power_spectrum(
da,
spacing_tol=1e-3,
Expand Down