MorletWavelet.java
package com.morphiqlabs.wavelet.cwt;
import com.morphiqlabs.wavelet.api.ComplexContinuousWavelet;
import com.morphiqlabs.wavelet.api.Wavelet;
import com.morphiqlabs.wavelet.exception.InvalidArgumentException;
/**
* The Morlet wavelet (also known as Gabor wavelet) is a complex-valued
* continuous wavelet commonly used for time-frequency analysis.
*
* <p>The Morlet wavelet is defined as a complex exponential (carrier)
* multiplied by a Gaussian window (envelope). It provides excellent
* time-frequency localization.</p>
*
* <p>Properties:
* <ul>
* <li>Complex-valued (can extract phase information)</li>
* <li>No compact support (but effectively localized)</li>
* <li>Smooth</li>
* <li>Good frequency resolution</li>
* </ul>
*
* <p><strong>Canonical References:</strong></p>
* <ul>
* <li>Morlet, J., Arens, G., Fourgeau, E., {@literal &} Glard, D. (1982). "Wave propagation
* and sampling theory." Geophysics, 47(2), 203-221.</li>
* <li>Mallat, S. (2008). "A Wavelet Tour of Signal Processing: The Sparse Way"
* (3rd ed., pp. 88-91). Academic Press.</li>
* <li>Addison, P. S. (2002). "The Illustrated Wavelet Transform Handbook"
* (pp. 50-58). IOP Publishing.</li>
* </ul>
*/
public final class MorletWavelet implements ComplexContinuousWavelet {
private final double omega0; // Central frequency parameter (typically 5-6)
private final double sigma; // Bandwidth parameter
private final double normalizationFactor; // Pre-computed normalization constant
private final double correctionTerm; // Pre-computed correction term
/**
* Creates a Morlet wavelet with specified parameters.
*
* @param omega0 central frequency (typically 5-6)
* @param sigma bandwidth parameter (typically 1.0)
*/
public MorletWavelet(double omega0, double sigma) {
this.omega0 = omega0;
this.sigma = sigma;
this.normalizationFactor = 1.0 / Math.pow(Math.PI * sigma * sigma, 0.25);
this.correctionTerm = Math.exp(-0.5 * omega0 * omega0 * sigma * sigma);
}
/**
* Creates a standard Morlet wavelet with omega0=6, sigma=1.
*/
public MorletWavelet() {
this(6.0, 1.0);
}
@Override
public String name() {
return "morl";
}
@Override
public String description() {
return String.format("Morlet wavelet (ω₀=%.1f, σ=%.1f)", omega0, sigma);
}
@Override
public double psi(double t) {
// Real part of Morlet wavelet
double gaussianEnvelope = Math.exp(-0.5 * t * t / (sigma * sigma));
double carrier = Math.cos(omega0 * t);
return normalizationFactor * gaussianEnvelope * (carrier - correctionTerm);
}
/**
* Returns the imaginary part of the Morlet wavelet.
* This is useful for phase analysis.
*
* @param t the time parameter
* @return the imaginary part of psi(t)
*/
@Override
public double psiImaginary(double t) {
double gaussianEnvelope = Math.exp(-0.5 * t * t / (sigma * sigma));
double carrier = Math.sin(omega0 * t);
return normalizationFactor * gaussianEnvelope * carrier;
}
@Override
public double centerFrequency() {
return omega0 / (2 * Math.PI);
}
@Override
public double bandwidth() {
return sigma;
}
// isComplex() is already implemented by ComplexContinuousWavelet interface
@Override
public double[] discretize(int numCoeffs) {
if (numCoeffs % 2 != 0) {
throw new InvalidArgumentException("Number of coefficients must be even");
}
double[] coeffs = new double[numCoeffs];
double t0 = -4.0 * sigma; // Start at -4 standard deviations
double dt = 8.0 * sigma / (numCoeffs - 1);
for (int i = 0; i < numCoeffs; i++) {
double t = t0 + i * dt;
coeffs[i] = psi(t);
}
// Use the shared helper method for normalization
return Wavelet.normalizeToUnitL2Norm(coeffs);
}
}