All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.killbill.billing.server.healthchecks.HoltWintersComputer Maven / Gradle / Ivy

There is a newer version: 0.40.13
Show newest version
/*
 * Copyright 2010-2013 Ning, Inc.
 * Copyright 2014-2017 Groupon, Inc
 * Copyright 2014-2017 The Billing Project, LLC
 *
 * The Billing Project licenses this file to you under the Apache License, version 2.0
 * (the "License"); you may not use this file except in compliance with the
 * License.  You may obtain a copy of the License at:
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
 * License for the specific language governing permissions and limitations
 * under the License.
 */

package org.killbill.billing.server.healthchecks;

/**
 * Additive Holt-Winters prediction function
 */
public class HoltWintersComputer {

    private final FILTERING_TYPE filteringType;

    private double value;
    private double alpha;
    private double beta;
    private double gamma;
    private double baseline;
    private double slope;
    private double[] seasonalValues;
    private int seenValues = 0;
    private double lastForecast;

    /**
     * Simple exponential smoothing
     *
     * @param alpha alpha parameter (baseline)
     */
    public HoltWintersComputer(final double alpha) {
        this.alpha = alpha;
        this.filteringType = FILTERING_TYPE.SIMPLE;
    }

    /**
     * Double exponential smoothing
     *
     * @param alpha alpha parameter (baseline)
     * @param beta  beta parameter (slope)
     */
    public HoltWintersComputer(final double alpha, final double beta) {
        this.alpha = alpha;
        this.beta = beta;
        this.filteringType = FILTERING_TYPE.DOUBLE;
    }

    public HoltWintersComputer(final double alpha, final double beta, final double gamma, final int period) {
        this.alpha = alpha;
        this.beta = beta;
        this.gamma = gamma;
        this.seasonalValues = new double[period];
        this.filteringType = FILTERING_TYPE.TRIPLE;
    }

    public void addNextValue(final double value) {
        lastForecast = getForecast(1);
        this.value = value;
        updateParameters();
        seenValues++;
    }

    public double getDeviation() {
        return Math.abs(value - lastForecast);
    }

    /**
     * Yhat[t+h] = a[t] + h * b[t] + s[t + 1 + (h - 1) mod p]
     * slope is the current best estimate of the trend
     * baseline is the current smoothed value
     *
     * @param h how far to predict
     * @return prediction at +h in the future based on the raw data up to now
     */
    public double getForecast(final int h) {
        if (filteringType == FILTERING_TYPE.SIMPLE) {
            return baseline;
        } else if (filteringType == FILTERING_TYPE.DOUBLE) {
            return baseline + h * slope;
        } else if (filteringType == FILTERING_TYPE.TRIPLE) {
            return baseline + h * slope + seasonalValues[(seasonalValues.length - 1 + (h - 1) % seasonalValues.length) % seasonalValues.length];
        } else {
            throw new IllegalStateException("Computer not initialized properly");
        }
    }

    /**
     * Yhat[t+h] = a[t] + h * b[t] + s[t + 1 + (h - 1) mod p],
     * 

* where a[t], b[t] and s[t] are given by *

* a[t] = alpha (Y[t] - s[t-p]) + (1-alpha) (a[t-1] + b[t-1]) * b[t] = beta (a[t] - a[t-1]) + (1-beta) b[t-1] * s[t] = gamma (Y[t] - a[t]) + (1-gamma) s[t-p] */ private void updateParameters() { switch (filteringType) { case SIMPLE: updateParametersSimpleExponential(); break; case DOUBLE: updateParametersDoubleExponential(); break; case TRIPLE: updateParametersTripleExponential(); break; default: throw new IllegalStateException("Computer not initialized properly"); } } private void updateParametersTripleExponential() { if (seenValues == 0) { baseline = value; seasonalValues[seenValues] = value; } else if (seenValues == 1) { slope = value - baseline; baseline = value; seasonalValues[seenValues] = value; } // Initialize seasonal values else if (seenValues < seasonalValues.length) { seasonalValues[seenValues] = value; } else { final double oldBaseline = baseline; final double oldSlope = slope; final double oldSeasonal = seasonalValues[0]; // Move the seasonal window System.arraycopy(seasonalValues, 1, seasonalValues, 0, seasonalValues.length - 1); baseline = alpha * (value - oldSeasonal) + (1.0 - alpha) * (oldBaseline + oldSlope); slope = beta * (baseline - oldBaseline) + (1.0 - beta) * oldSlope; seasonalValues[seasonalValues.length - 1] = gamma * (value - baseline) + (1.0 - gamma) * oldSeasonal; } } private void updateParametersDoubleExponential() { // Initialize baseline using the second seen value (y2) // Initialize slope using the first two values (y2 - y1) if (seenValues == 0) { baseline = value; } else if (seenValues == 1) { slope = value - baseline; baseline = value; } else { final double oldBaseline = baseline; final double oldSlope = slope; baseline = alpha * value + (1.0 - alpha) * (oldBaseline + oldSlope); slope = beta * (baseline - oldBaseline) + (1.0 - beta) * oldSlope; } } private void updateParametersSimpleExponential() { // Initialize baseline using the first seen value (y1) if (seenValues == 0) { baseline = value; } else { final double oldBaseline = baseline; baseline = alpha * value + (1.0 - alpha) * oldBaseline; } } @Override public String toString() { return String.format("alpha=%f, beta=%f, gamma=%f", alpha, beta, gamma); } public double getAlpha() { return alpha; } public double getBeta() { return beta; } public double getGamma() { return gamma; } public double[] getSeasonal() { return seasonalValues; } enum FILTERING_TYPE { SIMPLE, DOUBLE, TRIPLE } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy