ConfidenceInterval.java
package com.morphiqlabs.wavelet.performance;
import java.io.Serializable;
/**
* Confidence interval for performance predictions.
*
* <p>Represents the uncertainty in performance predictions as multipliers
* on the base prediction. For example, a 95% confidence interval of [0.9, 1.1]
* means the actual time is expected to be between 90% and 110% of the predicted time.</p>
*/
public class ConfidenceInterval implements Serializable {
private static final long serialVersionUID = 1L;
private double lowerMultiplier;
private double upperMultiplier;
// For adaptive updates
private double errorSum = 0;
private int errorCount = 0;
private static final double ALPHA = 0.1; // Exponential moving average factor
/**
* Creates a confidence interval with specified multipliers.
*
* @param lowerMultiplier Lower bound multiplier (e.g., 0.9 for 90%)
* @param upperMultiplier Upper bound multiplier (e.g., 1.1 for 110%)
*/
public ConfidenceInterval(double lowerMultiplier, double upperMultiplier) {
if (lowerMultiplier <= 0 || lowerMultiplier >= 1) {
throw new IllegalArgumentException("Lower multiplier must be between 0 and 1");
}
if (upperMultiplier <= 1 || upperMultiplier > 3) {
throw new IllegalArgumentException("Upper multiplier must be between 1 and 3");
}
if (lowerMultiplier >= upperMultiplier) {
throw new IllegalArgumentException("Lower multiplier must be less than upper multiplier");
}
this.lowerMultiplier = lowerMultiplier;
this.upperMultiplier = upperMultiplier;
}
/**
* Gets the lower bound for a predicted value.
*
* @param prediction The base prediction
* @return Lower bound of the confidence interval
*/
public double getLowerBound(double prediction) {
return prediction * lowerMultiplier;
}
/**
* Gets the upper bound for a predicted value.
*
* @param prediction The base prediction
* @return Upper bound of the confidence interval
*/
public double getUpperBound(double prediction) {
return prediction * upperMultiplier;
}
/**
* Updates the confidence interval based on prediction error.
*
* @param relativeError The relative error (|actual - predicted| / predicted)
*/
public void updateWithError(double relativeError) {
// Update error statistics
errorSum = ALPHA * relativeError + (1 - ALPHA) * errorSum;
errorCount++;
// Adjust interval width based on error magnitude
double avgError = errorSum;
// Target: 95% of predictions within interval
// If errors are large, widen the interval
double targetWidth = 2.0 * avgError + 0.1; // Minimum 10% width
// Calculate new multipliers
double center = 1.0;
double halfWidth = Math.min(targetWidth / 2.0, 1.0); // Cap at 100% error
lowerMultiplier = Math.max(0.1, center - halfWidth);
upperMultiplier = Math.min(3.0, center + halfWidth);
// Ensure multipliers are valid
if (lowerMultiplier >= upperMultiplier) {
lowerMultiplier = 0.5;
upperMultiplier = 2.0;
}
}
/**
* Gets the width of the confidence interval as a percentage.
*
* @return Width of the interval (e.g., 0.2 for 20% width)
*/
public double getWidth() {
return upperMultiplier - lowerMultiplier;
}
/**
* Gets the confidence level based on the interval width.
*
* @return Estimated confidence level (0-1)
*/
public double getConfidenceLevel() {
// Narrow intervals indicate higher confidence
double width = getWidth();
if (width <= 0.1) return 0.99; // Very high confidence
if (width <= 0.2) return 0.95; // High confidence
if (width <= 0.4) return 0.90; // Good confidence
if (width <= 0.6) return 0.80; // Moderate confidence
return 0.70; // Low confidence
}
/**
* Checks if an actual value falls within the confidence interval.
*
* @param predicted The predicted value
* @param actual The actual value
* @return true if actual is within the interval
*/
public boolean contains(double predicted, double actual) {
return actual >= getLowerBound(predicted) && actual <= getUpperBound(predicted);
}
/**
* Creates a copy of this confidence interval.
*
* @return New instance with same values
*/
public ConfidenceInterval copy() {
ConfidenceInterval copy = new ConfidenceInterval(lowerMultiplier, upperMultiplier);
copy.errorSum = this.errorSum;
copy.errorCount = this.errorCount;
return copy;
}
@Override
public String toString() {
return String.format("[%.1f%%, %.1f%%]",
(lowerMultiplier * 100 - 100),
(upperMultiplier * 100 - 100));
}
}