ConstantPaddingStrategy.java
package com.morphiqlabs.wavelet.padding;
import com.morphiqlabs.wavelet.exception.InvalidArgumentException;
import java.util.Arrays;
/**
* Constant (edge) padding strategy that extends signals by repeating edge values.
*
* <p>This strategy extends the signal by replicating the first and last values.
* It provides better continuity than zero padding for signals with non-zero
* baseline or DC offset. Ideal for:</p>
* <ul>
* <li>Financial time series with non-zero baseline</li>
* <li>Signals with significant DC offset</li>
* <li>Preserving signal mean near boundaries</li>
* <li>Reducing edge discontinuities</li>
* </ul>
*
* <p>Example: {@code [1, 2, 3, 4]} padded to length 8 with RIGHT mode becomes
* {@code [1, 2, 3, 4, 4, 4, 4, 4]}</p>
*
* <p>Example: {@code [1, 2, 3, 4]} padded to length 8 with SYMMETRIC mode becomes
* {@code [1, 1, 1, 2, 3, 4, 4, 4]}</p>
*
* @param mode constant padding mode
*/
public record ConstantPaddingStrategy(PaddingMode mode) implements PaddingStrategy {
/**
* Padding mode determines where padding is applied.
*/
public enum PaddingMode {
/** Pad only on the right side */
RIGHT,
/** Pad equally on both sides (or favor right if odd) */
SYMMETRIC,
/** Pad only on the left side */
LEFT
}
/**
* Creates a constant padding strategy with default RIGHT mode.
*/
public ConstantPaddingStrategy() {
this(PaddingMode.RIGHT);
}
@Override
public double[] pad(double[] signal, int targetLength) {
if (signal == null) {
throw new InvalidArgumentException("Signal cannot be null");
}
if (signal.length == 0) {
throw new InvalidArgumentException("Signal cannot be empty");
}
if (targetLength < signal.length) {
throw new InvalidArgumentException(
"Target length " + targetLength + " must be >= signal length " + signal.length);
}
if (targetLength == signal.length) {
return signal.clone();
}
double[] padded = new double[targetLength];
int padLength = targetLength - signal.length;
switch (mode) {
case RIGHT -> {
// Copy original signal to the start
System.arraycopy(signal, 0, padded, 0, signal.length);
// Fill remaining with last value
Arrays.fill(padded, signal.length, targetLength, signal[signal.length - 1]);
}
case LEFT -> {
// Fill beginning with first value
Arrays.fill(padded, 0, padLength, signal[0]);
// Copy original signal after padding
System.arraycopy(signal, 0, padded, padLength, signal.length);
}
case SYMMETRIC -> {
// Calculate left and right padding
int leftPad = padLength / 2;
int rightPad = padLength - leftPad;
// Fill left padding with first value
Arrays.fill(padded, 0, leftPad, signal[0]);
// Copy original signal
System.arraycopy(signal, 0, padded, leftPad, signal.length);
// Fill right padding with last value
Arrays.fill(padded, leftPad + signal.length, targetLength, signal[signal.length - 1]);
}
}
return padded;
}
@Override
public double[] trim(double[] result, int originalLength) {
if (result.length == originalLength) {
return result;
}
if (originalLength > result.length) {
throw new InvalidArgumentException(
"Original length " + originalLength + " exceeds result length " + result.length);
}
double[] trimmed = new double[originalLength];
switch (mode) {
case RIGHT -> {
// Trim from the end
System.arraycopy(result, 0, trimmed, 0, originalLength);
}
case LEFT -> {
// Trim from the beginning
System.arraycopy(result, result.length - originalLength, trimmed, 0, originalLength);
}
case SYMMETRIC -> {
// Trim equally from both sides
int totalPadding = result.length - originalLength;
int leftPad = totalPadding / 2;
System.arraycopy(result, leftPad, trimmed, 0, originalLength);
}
}
return trimmed;
}
@Override
public String name() {
return "constant-" + mode.name().toLowerCase();
}
@Override
public String description() {
return String.format("Constant padding (%s mode) - extends signal by repeating edge values",
mode.name().toLowerCase());
}
}