/*
* DeviationTrigger.java - Copyright(c) 2014 Joe Pasqua
* Provided under the MIT License. See the LICENSE file for details.
* Created: Aug 10, 2014
*/
package org.noroomattheinn.visibletesla.trigger;
/**
* DeviationTrigger: Determines whether a sample deviates from a historical
* baseline.
*
* @author Joe Pasqua <joe at NoRoomAtTheInn dot org>
*/
public class DeviationTrigger {
private static final double NoAverageEstablished = Double.MIN_VALUE;
private double threshold;
private double runningAverage, baseline;
private long firstTime, lastTime, firstTriggerTime;
private long baselineInterval;
private long sampleCount;
/**
* An object that monitors whether a sample deviates from an established
* baseline value by more than a certain percentage.
* @param threshold Percentage change that will trigger a deviation.
* Expressed as a value from 0 to 1.
* @param baselineInterval The time (in millis) needed to establish a baseline
* average. Also the time required for a deviation
* to be sustained before being reported.
*/
public DeviationTrigger(double threshold, long baselineInterval) {
this.threshold = threshold;
this.baselineInterval = baselineInterval;
resetBaseline();
}
/**
* Take a sample and determine whether it is out of the range of the current
* baseline. If no baseline has yet been established, use this sample to help
* build it.
* @param sample The current sample of some variable
* @return true: if this sample deviates significantly from an
* established baseline
* false: The sample doesn't deviate significantly or the
* baseline is still being formed.
*/
public boolean evalPredicate(double sample) {
if (sample <= 0) {
resetBaseline();
return false;
}
if (almostEquals(runningAverage, NoAverageEstablished)) {
firstTime = lastTime = System.currentTimeMillis();
runningAverage = sample;
sampleCount = 1;
return false;
}
if (lastTime - firstTime <= baselineInterval) { // Keep accumulating
runningAverage = ((runningAverage * sampleCount) + sample) / (++sampleCount);
lastTime = System.currentTimeMillis();
} else { // Test whether we're in range
if (sample <= runningAverage * (1 - threshold)) { // The sample is too far from the average
if (firstTriggerTime == -1) {
firstTriggerTime = System.currentTimeMillis();
} else if (System.currentTimeMillis() - firstTriggerTime > baselineInterval) {
baseline = runningAverage;
resetBaseline(); // Start a new baseline
return true;
}
} else { // The sample is in range of the average
firstTriggerTime = -1;
}
}
return false;
}
/**
* Get the baseline that was established and lead to a trigger event. This
* should only be called after handleSample() returns true and before any
* new handleSample() calls are made.
* @return The baseline value
*/
public double getBaseline() { return baseline; }
private void resetBaseline() {
runningAverage = NoAverageEstablished;
firstTime = lastTime = firstTriggerTime = -1L;
sampleCount = 0;
}
private boolean almostEquals(double d1, double d2) {
return ( Math.abs(d1 - d2) < .0000001 );
}
}