ContinuousMeyerWavelet.java
package com.morphiqlabs.wavelet.cwt;
import com.morphiqlabs.wavelet.api.ContinuousWavelet;
/**
* Continuous Meyer wavelet.
*
* <p>The Meyer wavelet is characterized by perfect smoothness in the frequency domain
* and optimal time-frequency localization. It's primarily defined in the frequency
* domain and requires numerical methods for time-domain evaluation.</p>
*
* <p>The Meyer wavelet has compact support in the frequency domain and provides
* excellent frequency resolution, making it suitable for spectral analysis and
* signal decomposition where frequency precision is critical.</p>
*/
public final class ContinuousMeyerWavelet implements ContinuousWavelet {
/**
* Creates a new ContinuousMeyerWavelet.
*/
public ContinuousMeyerWavelet() {
// Default constructor
}
private static final double TWO_PI = 2 * Math.PI;
private static final double TWO_THIRDS_PI = 2 * Math.PI / 3;
private static final double FOUR_THIRDS_PI = 4 * Math.PI / 3;
private static final double EIGHT_THIRDS_PI = 8 * Math.PI / 3;
@Override
public String name() {
return "meyr";
}
@Override
public double psi(double t) {
// Meyer wavelet is best computed via inverse FFT from frequency domain
// For efficiency, we use a pre-computed approximation
return computeMeyerTimeValue(t);
}
/**
* Meyer wavelet in frequency domain.
*
* @param omega frequency parameter
* @return complex value in frequency domain (real part only for now)
*/
public double psiHat(double omega) {
double absOmega = Math.abs(omega);
if (absOmega < TWO_THIRDS_PI) {
return 0.0;
} else if (absOmega < FOUR_THIRDS_PI) {
double arg = 3 * absOmega / (2 * Math.PI) - 1;
return Math.sin(Math.PI / 2 * nu(arg)) / Math.sqrt(TWO_PI);
} else if (absOmega < EIGHT_THIRDS_PI) {
double arg = 3 * absOmega / (4 * Math.PI) - 1;
return Math.cos(Math.PI / 2 * nu(arg)) / Math.sqrt(TWO_PI);
} else {
return 0.0;
}
}
@Override
public double centerFrequency() {
// Meyer wavelet center frequency
return 0.7; // Approximate value in normalized frequency
}
@Override
public double bandwidth() {
// Meyer wavelet has excellent frequency localization
return 1.5;
}
@Override
public boolean isComplex() {
return false;
}
@Override
public double[] discretize(int numCoeffs) {
double[] coeffs = new double[numCoeffs];
double tMax = 8.0; // Support region
double dt = 2 * tMax / (numCoeffs - 1);
for (int i = 0; i < numCoeffs; i++) {
double t = -tMax + i * dt;
coeffs[i] = computeMeyerTimeValue(t);
}
// Normalize
double sum = 0;
for (double c : coeffs) {
sum += c * c;
}
if (sum > 0) {
double norm = 1.0 / Math.sqrt(sum);
for (int i = 0; i < numCoeffs; i++) {
coeffs[i] *= norm;
}
}
return coeffs;
}
/**
* Meyer auxiliary function for smooth transitions.
*
* @param x input value
* @return smooth transition value between 0 and 1
*/
private double nu(double x) {
if (x <= 0) return 0;
if (x >= 1) return 1;
// Smooth polynomial transition: x^4(35 - 84x + 70x^2 - 20x^3)
double x2 = x * x;
double x3 = x2 * x;
double x4 = x3 * x;
return x4 * (35 - 84 * x + 70 * x2 - 20 * x3);
}
/**
* Computes Meyer wavelet value in time domain using approximation.
*
* @param t time parameter
* @return wavelet value at time t
*/
private double computeMeyerTimeValue(double t) {
// Approximation of Meyer wavelet in time domain
// Based on numerical inverse FFT of frequency domain definition
if (Math.abs(t) > 8.0) {
return 0.0; // Effectively zero outside support
}
// Use a combination of cosine and sinc functions for approximation
double envelope = Math.exp(-t * t / 50.0); // Decay envelope
if (Math.abs(t) < 1e-10) {
return 2.0 / 3.0 * envelope;
}
// Approximation based on the frequency domain characteristics
double omega0 = TWO_THIRDS_PI + FOUR_THIRDS_PI / 2; // Center frequency
double modulation = Math.cos(omega0 * t);
double sinc = Math.sin(Math.PI * t) / (Math.PI * t);
return 2.0 / 3.0 * sinc * modulation * envelope;
}
}