AntisymmetricPaddingStrategy.java
package com.morphiqlabs.wavelet.padding;
import com.morphiqlabs.wavelet.exception.InvalidArgumentException;
/**
* Antisymmetric padding strategy that extends signals by reflection with sign change.
*
* <p>This strategy reflects the signal at boundaries with a sign change, preserving
* odd symmetry properties. It's particularly useful for signals that should maintain
* antisymmetric properties at boundaries. Ideal for:</p>
* <ul>
* <li>Signals with natural antisymmetry</li>
* <li>Preserving derivative continuity at boundaries</li>
* <li>Compatibility with antisymmetric wavelets</li>
* <li>Reducing Gibbs phenomena at boundaries</li>
* </ul>
*
* <p>Two symmetry types are supported:</p>
* <ul>
* <li><b>WHOLE_POINT</b>: Reflects around boundary point with sign change.
* Example: {@code [a,b,c]} → {@code [-c,-b,-a,a,b,c,-c,-b,-a]}</li>
* <li><b>HALF_POINT</b>: Reflects between boundary points with sign change.
* Example: {@code [a,b,c]} → {@code [-b,-a,a,b,c,-c,-b]}</li>
* </ul>
*
* @param type symmetry type for reflection behavior
*/
public record AntisymmetricPaddingStrategy(SymmetryType type) implements PaddingStrategy {
/**
* Type of antisymmetric reflection.
*/
public enum SymmetryType {
/** Reflect around boundary point (includes boundary in reflection) */
WHOLE_POINT,
/** Reflect between boundary points (excludes boundary from reflection) */
HALF_POINT
}
/**
* Creates an antisymmetric padding strategy with default HALF_POINT type.
*/
public AntisymmetricPaddingStrategy() {
this(SymmetryType.HALF_POINT);
}
@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 n = signal.length;
// Fill the padded array
for (int i = 0; i < targetLength; i++) {
padded[i] = getAntisymmetricValue(signal, i, n);
}
return padded;
}
/**
* Get the value at position i using antisymmetric extension.
*
* @param signal original signal
* @param i position in extended signal
* @param n length of original signal
* @return the antisymmetrically extended value
*/
private double getAntisymmetricValue(double[] signal, int i, int n) {
if (i >= 0 && i < n) {
// Within original signal
return signal[i];
}
if (type == SymmetryType.WHOLE_POINT) {
// Whole-point antisymmetry: reflect around boundary points
if (i < 0) {
// Left extension
int period = 2 * n;
int idx = (-i - 1) % period;
if (idx < n) {
return -signal[idx]; // Reflected with sign change
} else {
return signal[period - idx - 1]; // Back to original
}
} else {
// Right extension (i >= n)
int period = 2 * n;
int offset = i - n;
int idx = offset % period;
if (idx < n) {
return -signal[n - 1 - idx]; // Reflected with sign change
} else {
return signal[idx - n]; // Back to original
}
}
} else { // HALF_POINT
// Half-point antisymmetry: reflect between boundary points
if (i < 0) {
// Left extension
int period = 2 * n - 2; // Exclude boundaries from period
if (n == 1) {
return -signal[0]; // Special case for single element
}
int idx = -i - 1;
idx = idx % period;
if (idx < n - 1) {
return -signal[idx + 1]; // Reflected with sign change, skip first
} else {
return signal[period - idx]; // Back to original
}
} else {
// Right extension (i >= n)
int period = 2 * n - 2; // Exclude boundaries from period
if (n == 1) {
return -signal[0]; // Special case for single element
}
int offset = i - n;
int idx = offset % period;
if (idx < n - 1) {
return -signal[n - 2 - idx]; // Reflected with sign change, skip last
} else {
return signal[idx - n + 2]; // Back to original
}
}
}
}
@Override
public String name() {
return "antisymmetric-" + type.name().toLowerCase().replace('_', '-');
}
@Override
public String description() {
String typeDesc = type == SymmetryType.WHOLE_POINT
? "whole-point reflection"
: "half-point reflection";
return String.format("Antisymmetric padding (%s) - reflects signal with sign change", typeDesc);
}
}