Skip to content

Commit 02b7de3

Browse files
committed
Fixed artifact in global scale separation
- Global scale separation relies on an interpolation of the larger decomposition levels to the smallest decomposition level. The previous versions misused the `fill_value` kwarg of the interpolation leading to an artifact in the clustered frequencies. This was fixed by adding a custom fill function. - Simultaneously, the tutorial with real data was re-worked to drop the dependence on the seaborn library while also updating to use the new interpolation logic.
1 parent e152f29 commit 02b7de3

File tree

2 files changed

+254
-187
lines changed

2 files changed

+254
-187
lines changed

pydmd/mrcosts.py

Lines changed: 59 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,61 @@ def fit(self, data, time, verbose=True):
357357
# Save the fitted costs object.
358358
self._costs_array.append(copy.copy(mrd))
359359

360+
@staticmethod
361+
def interp_fill(
362+
da_interp,
363+
da,
364+
):
365+
"""Extrapolate values to the full data window.
366+
367+
Since multi_res_interp uses a nearest neighbors interpolation without
368+
exraploation and the window_time_means are in the middle of the
369+
decomposition window, the first and last windows are incompletely
370+
filled. This function fills the NaN entries of these windows.
371+
372+
:param da_interp: Interpolated eigenvalues following multi_res_interp()
373+
:type da_interp: xarray.DataArray
374+
:param da: Uninterpolated eigenvalues
375+
:type da: xarray.DataArray
376+
:return: Interpolated eigenvalues with filled first and last windows.
377+
:rtype: xarray.DataArray
378+
"""
379+
window_delta = da.window_time_means.diff(
380+
dim="window_time_means"
381+
).values[0]
382+
383+
# Backwards fill the last non-NaN to the beginning of the first window
384+
first_window = da_interp.dropna(
385+
dim="window_time_means", how="all"
386+
).isel(window_time_means=0)
387+
388+
da_interp = xr.where(
389+
(
390+
da_interp.window_time_means
391+
> da.window_time_means[0] - window_delta / 2
392+
)
393+
& (da_interp.window_time_means < da.window_time_means[0]),
394+
first_window,
395+
da_interp,
396+
)
397+
398+
# Forward fill the last non-NaN to the end of the last window
399+
last_window = da_interp.dropna(dim="window_time_means", how="all").isel(
400+
window_time_means=-1
401+
)
402+
403+
da_interp = xr.where(
404+
(
405+
da_interp.window_time_means
406+
< da.window_time_means[-1] + window_delta / 2
407+
)
408+
& (da_interp.window_time_means > da.window_time_means[-1]),
409+
last_window,
410+
da_interp,
411+
)
412+
413+
return da_interp
414+
360415
def multi_res_interp(
361416
self,
362417
):
@@ -388,15 +443,17 @@ def multi_res_interp(
388443
da_real = da_real.interp(
389444
window_time_means=ds_list[0].window_time_means,
390445
method="nearest",
391-
kwargs={"fill_value": (da_real.min(), da_real.max())},
392446
)
393447

394448
da_imag = da_imag.interp(
395449
window_time_means=ds_list[0].window_time_means,
396450
method="nearest",
397-
kwargs={"fill_value": (da_imag.min(), da_imag.max())},
398451
)
399452

453+
# Complete the interpolation of the first and last window
454+
da_imag = self.interp_fill(da_imag, da)
455+
da_real = self.interp_fill(da_real, da)
456+
400457
da = da_real + 1j * da_imag
401458
da_to_concat.append(da)
402459

tutorials/tutorial20/costs-tutorial_real-data.ipynb

Lines changed: 195 additions & 185 deletions
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)