---
title: "IMF decomposition: TS2IMF (EMD / EEMD / VMD)"
output: rmarkdown::html_vignette
vignette: >
  %\VignetteIndexEntry{IMF decomposition: TS2IMF (EMD / EEMD / VMD)}
  %\VignetteEngine{knitr::rmarkdown}
  %\VignetteEncoding{UTF-8}
---

```{r setup, include = FALSE}
knitr::opts_chunk$set(collapse = TRUE, comment = "#>", eval = FALSE)
```

```{r minimal-example, eval = TRUE}
# Minimal executable example
library(gmsp)
library(data.table)
t_vec <- seq(0, 5, by = 0.01)
dt_sig <- data.table(t = t_vec, s = sin(2 * pi * 2 * t_vec) + 0.3 * sin(2 * pi * 8 * t_vec))
imf_res <- TS2IMF(dt_sig, method = "vmd", K = 4, output = "IMF")
print(imf_res)
```

`TS2IMF()` decomposes a time series into **Intrinsic Mode Functions
(IMFs)** and a **residue**. A common goal is to separate oscillatory
components by scale / band and then reconstruct a filtered signal by
removing or keeping selected IMFs.

Canonical references: EMD (Huang et al., 1998), EEMD (Wu & Huang,
2009), VMD (Dragomiretskiy & Zosso, 2014).

## What is an IMF?

In the EMD framework, a component $c(t)$ is an IMF if it satisfies
the classic definition:

1. the number of zero crossings and extrema differ at most by 1, and
2. the local mean of the upper and lower envelopes is approximately
   zero.

This enables a meaningful instantaneous frequency (via the Hilbert
transform) for non-stationary signals.

## Input and grouping

* `.x`: one signal as a `data.table` with canonical columns `t` and `s`.

**Note:** internally `TS2IMF()` regularizes the time grid and rebuilds a
time vector `ts` from 0. The workflow requires `t` / `s` as the working
column names.

`TS2IMF()` is a worker and does not detect groups. For a canonical `TSL`,
group outside the helper:

```r
TSL[ID == "AT",
    TS2IMF(.SD, method = "vmd", K = 6, output = "TSL"),
    by = .(RecordID, OCID),
    .SDcols = c("t", "s")]
```

## Available engines

Parameter `method` selects one of three engines.

### VMD (`method = "vmd"`, default)

Calls `VMDecomp::vmd(...)`. Returns a matrix
$U \in \mathbb{R}^{N \times K}$ ($K$ modes). The residue is defined
as

$$r[n] = s[n] - \sum_{k=1}^{K} U_k[n].$$

### EMD (`method = "emd"`)

Calls `EMD::emd(...)` with `boundary` and `stop.rule` controlling
sifting. The output `AUX$imf` and `AUX$residue` are taken as-is.

### EEMD (`method = "eemd"`)

Calls `hht::EEMD(...)` followed by `hht::EEMDCompile(...)`. Runs an
ensemble with noise (`noise.amp`, `noise.type`, `trials`). Uses a
`tempdir()` as a working folder and then compiles the averaged IMFs.

## Output

If `output = NULL`, `TS2IMF()` returns a list with three elements.

| Element | Shape | Description |
|---|---|---|
| `TSW` | wide | `t`, `signal`, `IMF1..IMFk`, `residue` |
| `TSL` | long | melt of `TSW` with columns `t`, `IMF`, `s` |
| `IMF` | metrics | per-IMF center frequency / bandwidth / relative energy |

If `output` is `"TSW"`, `"TSL"` or `"IMF"`, only that component is
returned.

### Per-IMF metrics

For each IMF the code estimates the center frequency `Fo[Hz]`, the
bandwidth `BW[Hz]`, and the relative energy `E[k]/Eo [%]`. The
procedure is:

1. apply a Hann window $w[n]$,
2. compute FFT with zero-padding to a power of 2,
3. compute a power spectrum $P(f)$,
4. apply a −10 dB threshold relative to the maximum and compute
   `Fo` / `BW` as moments of $P(f)$ over "active" bins.

## Reconstruction filters

Inside `TS2IMF()` a local helper `.applyIMFFilter()` adds a
reconstructed signal `signal.R` when any of the following arguments
is set.

* **`imf.remove`** — if `character`, removes those columns
  (`IMF1`, `IMF2`, ...); if numeric, positive values remove IMFs
  counted from the first mode, negative values remove IMFs counted
  from the last mode, and both selections are unioned. For example,
  `c(1L, -1L, -2L)` removes `IMF1` plus the last two IMFs. Zero,
  non-finite, and out-of-range numeric values are ignored.

* **`remove.Fo = c(f1, f2)`** — removes IMFs whose frequency
  interval $[F_o - BW,\,F_o + BW]$ lies entirely within $[f_1, f_2]$.

* **`keep.Fo = c(f1, f2)`** — keeps only IMFs whose frequency
  interval $[F_o - BW,\,F_o + BW]$ lies entirely within $[f_1, f_2]$.

* **`keep.Residue`** — if `TRUE`, `signal.R` adds the residue on top
  of the kept IMFs.

## Composing with AT2TS / VT2TS / DT2TS

In practice the typical pattern is:

1. Start from a `TSL` (e.g. output of `AT2TS(..., output = "TSL")`).
2. Filter a specific `ID` + `OCID`
   (e.g. `ID == "DT" & OCID == "H1"`).
3. Run `TS2IMF()` on that slice, or inside a `data.table` grouped call.
4. Use `RES$TSL[IMF == "signal.R"]` as the reconstructed (filtered)
   signal.

```r
AT_H1 <- TSL[ID == "AT" & OCID == "H1", .(t, s)]
RES   <- TS2IMF(AT_H1, method = "vmd", K = 6,
                keep.Fo = c(2, 15), keep.Residue = TRUE)

# Reconstructed signal (only IMFs whose [Fo-BW, Fo+BW] fits in [2, 15] Hz):
RES$TSL[IMF == "signal.R"]
```

## References

* Huang, N. E., Shen, Z., Long, S. R., Wu, M. C., Shih, H. H., Zheng,
  Q., Yen, N.-C., Tung, C. C., & Liu, H. H. (1998). The empirical
  mode decomposition and the Hilbert spectrum for nonlinear and
  non-stationary time series analysis. *Proceedings of the Royal
  Society A*, 454(1971), 903–995.
* Wu, Z., & Huang, N. E. (2009). Ensemble Empirical Mode
  Decomposition: A Noise-Assisted Data Analysis Method. *Advances in
  Adaptive Data Analysis*, 1(1), 1–41.
* Dragomiretskiy, K., & Zosso, D. (2014). Variational Mode
  Decomposition. *IEEE Transactions on Signal Processing*, 62(3),
  531–544.
