Wavelet

A wavelet is a localized oscillation that starts at zero, rises, and returns to zero. Convolving wavelets with a signal extracts time–frequency structure that a global Fourier basis cannot localize in time.

The discrete wavelet transform (DWT) samples that idea on a dyadic grid. Like the FFT, it is a fast orthogonal transform on vectors whose length is a power of two (pad with zeros if needed), and it is invertible. SMILE’s implementation lives in smile.wavelet.

Wavelet Filters

Java exposes concrete filters: HaarWavelet, D4Wavelet, DaubechiesWavelet, SymletWavelet (least asymmetric), BestLocalizedWavelet, and CoifletWavelet. You can also build a custom filter with new Wavelet(coefficients).

Scala and Kotlin add a string factory wavelet(filter). Names use a class prefix plus length:

d — Daubechies
4, 6, 8, 10, 12, 14, 16, 18, 20 (d4 is special-cased via D4Wavelet with a different centering)
la — least asymmetric (symlets)
8, 10, 12, 14, 16, 18, 20
bl — best localized
14, 18, 20
c — Coiflet
6, 12, 18, 24, 30
haar
Haar wavelet (also known as D2)
import smile.wavelet.*

new HaarWavelet()
new D4Wavelet()
new DaubechiesWavelet(8)
new SymletWavelet(8)          // same family as filter "la8"
new BestLocalizedWavelet(14)
new CoifletWavelet(12)

HaarWavelet / D4Wavelet / DaubechiesWavelet / …
      

Discrete Wavelet Transform

transform replaces the input array with wavelet coefficients in place; inverse reconstructs the signal. Length must be a power of two.

import smile.wavelet.HaarWavelet
import java.util.Arrays

double[] x = {0.2, -0.4, -0.6, -0.5, -0.8, -0.4, -0.9, 0.0,
              -0.2, 0.1, -0.1, 0.1, 0.7, 0.9, 0.0, 0.3}
double[] original = x.clone()

var haar = new HaarWavelet()
haar.transform(x)
Arrays.toString(x)

haar.inverse(x)
// round-trip recovers the signal (up to ~1e-15)

[-0.4, -1.3, 0.2828, -0.7071, 0.45, -0.15, -0.05, 0.65,
  0.4243, -0.0707, -0.2828, -0.6364, -0.2121, -0.1414, -0.1414, -0.2121]
      

A constant signal has a single nonzero coefficient (the scaled global mean); all detail coefficients are zero:

import smile.wavelet.HaarWavelet
import java.util.Arrays

double[] c = {1, 1, 1, 1, 1, 1, 1, 1}
new HaarWavelet().transform(c)
Arrays.toString(c)

[2.8284271247461894, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
      

Scala/Kotlin also expose functional helpers that construct the filter for you: dwt(t, filter) and idwt(wt, filter).

Wavelet Shrinkage

Wavelet shrinkage denoises by thresholding coefficients:

  1. Apply the wavelet transform.
  2. Estimate a threshold.
  3. Hard thresholding zeros coefficients below the threshold; soft thresholding also shrinks the survivors toward zero.
  4. Invert the transform.

WaveletShrinkage uses the universal threshold T = σ √(2 log N), with noise scale σ from the scaled MAD of the finest-level detail coefficients. The scaling coefficient at index 0 (global mean) is never thresholded.

import java.awt.Color
import smile.math.MathEx
import smile.plot.swing.LinePlot
import smile.wavelet.*

MathEx.setSeed(19650218)
int n = 256
double[] noisy = new double[n]
for (int i = 0; i < n; i++) {
    noisy[i] = Math.sin(2 * Math.PI * i / 64.0) + 0.25 * MathEx.random()
}
double[] smooth = noisy.clone()
WaveletShrinkage.denoise(smooth, new D4Wavelet())

var figure = LinePlot.of(noisy).figure()
figure.add(LinePlot.of(smooth, Color.BLUE))
figure.show()

(opens a plot: noisy series + D4 hard-thresholded reconstruction in blue)
      
Wavelet shrinkage on S&P 500

Example: D4 shrinkage on an S&P 500 series (raw vs denoised).

Pass soft = true to WaveletShrinkage.denoise(t, wavelet, true) or wsdenoise(t, filter, soft = true) for soft thresholding.