FftHeuristics.java

package com.morphiqlabs.wavelet.util;

/**
 * Centralized FFT heuristics for MODWT and related operations.
 *
 * <p>Exposes configurable thresholds (via setters or system properties)
 * to decide when to use FFT-based convolution paths.</p>
 */
public final class FftHeuristics {

    // Defaults derived from Issue 009 heuristic
    private static final int DEFAULT_MIN_N_FOR_MODWT_FFT = 1024;
    private static final double DEFAULT_MIN_FILTER_TO_SIGNAL_RATIO = 1.0 / 8.0; // L > N/8

    // System property keys for optional overrides
    public static final String PROP_MODWT_MIN_N = "vectorwave.fft.modwt.minN";
    public static final String PROP_MODWT_MIN_FILTER_RATIO = "vectorwave.fft.modwt.minFilterToSignalRatio";

    private static volatile int minNForModwtFft = getIntProperty(PROP_MODWT_MIN_N, DEFAULT_MIN_N_FOR_MODWT_FFT);
    private static volatile double minFilterToSignalRatio = getDoubleProperty(PROP_MODWT_MIN_FILTER_RATIO, DEFAULT_MIN_FILTER_TO_SIGNAL_RATIO);

    private FftHeuristics() { throw new AssertionError("No instances"); }

    /**
     * Returns whether FFT-based circular convolution should be used for MODWT.
     *
     * @param signalLength signal length N
     * @param filterLength effective filter length L
     */
    public static boolean shouldUseModwtFFT(int signalLength, int filterLength) {
        if (signalLength <= 0 || filterLength <= 0) return false;
        if (signalLength < minNForModwtFft) return false;
        return filterLength > signalLength * minFilterToSignalRatio;
    }

    /**
     * Gets the minimum N threshold for MODWT FFT decision.
     */
    public static int getMinNForModwtFFT() {
        return minNForModwtFft;
    }

    /**
     * Sets the minimum N threshold for MODWT FFT decision (must be >= 1).
     */
    public static void setMinNForModwtFFT(int minN) {
        if (minN < 1) throw new IllegalArgumentException("minN must be >= 1");
        minNForModwtFft = minN;
    }

    /**
     * Gets the minimum filter-to-signal ratio for MODWT FFT decision.
     */
    public static double getMinFilterToSignalRatio() {
        return minFilterToSignalRatio;
    }

    /**
     * Sets the minimum filter-to-signal ratio for MODWT FFT decision (0 {@literal <} ratio {@literal <=} 1).
     */
    public static void setMinFilterToSignalRatio(double ratio) {
        if (!(ratio > 0.0 && ratio <= 1.0)) {
            throw new IllegalArgumentException("ratio must be in (0, 1]");
        }
        minFilterToSignalRatio = ratio;
    }

    /**
     * Resets thresholds to defaults (and re-reads system property overrides).
     */
    public static void resetToDefaults() {
        minNForModwtFft = getIntProperty(PROP_MODWT_MIN_N, DEFAULT_MIN_N_FOR_MODWT_FFT);
        minFilterToSignalRatio = getDoubleProperty(PROP_MODWT_MIN_FILTER_RATIO, DEFAULT_MIN_FILTER_TO_SIGNAL_RATIO);
    }

    private static int getIntProperty(String key, int def) {
        try {
            String v = System.getProperty(key);
            if (v == null) return def;
            int parsed = Integer.parseInt(v.trim());
            return parsed >= 1 ? parsed : def;
        } catch (Exception e) {
            return def;
        }
    }

    private static double getDoubleProperty(String key, double def) {
        try {
            String v = System.getProperty(key);
            if (v == null) return def;
            double parsed = Double.parseDouble(v.trim());
            return (parsed > 0.0 && parsed <= 1.0) ? parsed : def;
        } catch (Exception e) {
            return def;
        }
    }
}