[rrd-developers] [patch] multiplicative holt-winters

Evan Miller emiller at imvu.com
Fri Jun 15 05:43:42 CEST 2007


There are two popular variants of the Holt-Winters forecasting method; RRDtool
supports the "additive" method, which means that seasonal variation is simply
added to the baseline. For our application, it would be more appropriate to use
the "multiplicative" Holt-Winters method, where seasonal variation is a
coefficient multiplied by the baseline.  Quick example to illustrate the
difference: if the average doubles season-over-season, the additive method
would predict the delta between min and max to be constant, whereas the
multiplicative method would predict the delta to double as well.

Attached is a patch against trunk to support the multiplicative method.  I've
done this with a new consolidation function, MHWPREDICT, which is essentially
interchangeable with HWPREDICT. There is a noticeable improvement in prediction
deviations for certain types of functions; the attachments show HWPREDICT and
MHWPREDICT predictions for a function with an x*sin(x) component.

Because HWPREDICT and MHWPREDICT differ only in their equations, I've factored
out their math into rrd_hw_math.c. The appropriate smoothing functions are
passed to the update functions in a container of function pointers, which are
called where appropriate. Thus the additive and multiplicative methods use the
same update functions, and the right equations are evaluated without having
flag checks everywhere. This approach, I think, makes the algorithms quite
clear, with minimal duplicate code.

I have moved update_hwpredict, update_seasonal, update_devpredict,
update_devseasonal, and update_failures into a separate file, rrd_hw_update.c,
with some slight refactoring related to rrd_hw_math.c. I ran some
regression tests against trunk to make sure I didn't break anything with
the existing HWPREDICT code.

MHWPREDICT uses the same deviation smoothing and failure detection algorithms
as HWPREDICT.

Some helpful references on the multiplicative Holt-Winters method:

http://www.it.iitb.ac.in/~praj/acads/seminar/04329008_ExponentialSmoothing.pdf
(a student's quick overview of additive vs. multiplicative HW)

http://ideas.repec.org/p/msh/ebswps/1999-1.html (paper on variations to the
multiplicative Holt-Winters, including variance calculations; FYI, my
implementation uses "Model 1")

My employer and the owner of this patch (IMVU, Inc.) is happy to license it
under the same terms as RRDtool, i.e. give it to the project.

Let me know what you think!

Thanks,

Evan Miller 
-------------- next part --------------
A non-text attachment was scrubbed...
Name: HWPREDICT.png
Type: image/png
Size: 32826 bytes
Desc: not available
Url : http://lists.oetiker.ch/pipermail/rrd-developers/attachments/20070614/d168d021/attachment-0002.png 
-------------- next part --------------
A non-text attachment was scrubbed...
Name: MHWPREDICT.png
Type: image/png
Size: 30803 bytes
Desc: not available
Url : http://lists.oetiker.ch/pipermail/rrd-developers/attachments/20070614/d168d021/attachment-0003.png 
-------------- next part --------------
Index: src/rrd_hw_math.c
===================================================================
--- src/rrd_hw_math.c	(revision 0)
+++ src/rrd_hw_math.c	(revision 0)
@@ -0,0 +1,139 @@
+/*****************************************************************************
+ * rrd_hw_math.c  Math functions for Holt-Winters computations
+ *****************************************************************************/
+
+#include "rrd_hw_math.h"
+#include "rrd_config.h"
+
+/*****************************************************************************
+ * RRDtool supports both the additive and multiplicative Holt-Winters methods. 
+ * The additive method makes predictions by adding seasonality to the baseline, 
+ * whereas the multiplicative method multiplies the seasonality coefficient by 
+ * the baseline to make a prediction. This file contains all the differences
+ * between the additive and multiplicative methods, as well as a few math 
+ * functions common to them both.
+ ****************************************************************************/
+
+/*****************************************************************************
+ * Functions for additive Holt-Winters
+ *****************************************************************************/
+
+rrd_value_t hw_additive_calculate_prediction(
+    rrd_value_t intercept,
+    rrd_value_t slope,
+    int null_count,
+    rrd_value_t seasonal_coef
+    )
+{
+    return intercept + slope * null_count + seasonal_coef;
+}
+
+rrd_value_t hw_additive_calculate_intercept(
+    rrd_value_t hw_alpha,
+    rrd_value_t observed,
+    rrd_value_t seasonal_coef,
+    unival *coefs)
+{
+    return hw_alpha * (observed - seasonal_coef) 
+            + (1 - hw_alpha) * (coefs[CDP_hw_intercept].u_val 
+            + (coefs[CDP_hw_slope].u_val) * (coefs[CDP_null_count].u_cnt));
+}
+
+rrd_value_t hw_additive_calculate_seasonality(
+    rrd_value_t hw_gamma,
+    rrd_value_t observed,
+    rrd_value_t intercept,
+    rrd_value_t seasonal_coef)
+{
+    return hw_gamma * (observed - intercept) 
+            + (1 - hw_gamma ) * seasonal_coef;
+}
+
+rrd_value_t hw_additive_init_seasonality(
+    rrd_value_t seasonal_coef,
+    rrd_value_t intercept)
+{
+    return seasonal_coef - intercept;
+}
+
+/*****************************************************************************
+ * Functions for multiplicative Holt-Winters
+ *****************************************************************************/
+
+rrd_value_t hw_multiplicative_calculate_prediction(
+    rrd_value_t intercept,
+    rrd_value_t slope,
+    int null_count,
+    rrd_value_t seasonal_coef)
+{
+    return (intercept + slope * null_count) * seasonal_coef;
+}
+
+rrd_value_t hw_multiplicative_calculate_intercept(
+    rrd_value_t hw_alpha,
+    rrd_value_t observed,
+    rrd_value_t seasonal_coef,
+    unival *coefs)
+{
+    if (seasonal_coef <= 0) {
+        return DNAN;
+    }
+
+    return hw_alpha * (observed / seasonal_coef) 
+            + (1 - hw_alpha) * (coefs[CDP_hw_intercept].u_val 
+            + (coefs[CDP_hw_slope].u_val) * (coefs[CDP_null_count].u_cnt));
+}
+
+rrd_value_t hw_multiplicative_calculate_seasonality(
+    rrd_value_t hw_gamma,
+    rrd_value_t observed,
+    rrd_value_t intercept,
+    rrd_value_t seasonal_coef)
+{
+    if (intercept <= 0) {
+        return DNAN;
+    }
+
+    return hw_gamma * (observed / intercept) 
+            + (1 - hw_gamma) * seasonal_coef;
+}
+
+rrd_value_t hw_multiplicative_init_seasonality(
+    rrd_value_t seasonal_coef,
+    rrd_value_t intercept)
+{
+    if (intercept <= 0) {
+        return DNAN;
+    }
+
+    return seasonal_coef / intercept;
+}
+
+/*****************************************************************************
+ * Math functions common to additive and multiplicative Holt-Winters
+ *****************************************************************************/
+
+rrd_value_t hw_calculate_slope(
+    rrd_value_t hw_beta,
+    unival *coefs)
+{
+    return hw_beta * (coefs[CDP_hw_intercept].u_val - coefs[CDP_hw_last_intercept].u_val) 
+            + (1 - hw_beta) * coefs[CDP_hw_slope].u_val;
+}
+
+rrd_value_t hw_calculate_seasonal_deviation(
+    rrd_value_t hw_gamma,
+    rrd_value_t prediction,
+    rrd_value_t observed,
+    rrd_value_t last)
+{
+    return hw_gamma * fabs(prediction - observed)
+            + (1 - hw_gamma) * last;
+}
+
+rrd_value_t hw_init_seasonal_deviation(
+    rrd_value_t prediction,
+    rrd_value_t observed)
+{
+    return fabs(prediction - observed);
+}
Index: src/rrd_info.c
===================================================================
--- src/rrd_info.c	(revision 1121)
+++ src/rrd_info.c	(working copy)
@@ -192,6 +192,7 @@
 
         switch (current_cf) {
         case CF_HWPREDICT:
+        case CF_MHWPREDICT:
             info.u_val = rrd.rra_def[i].par[RRA_hw_alpha].u_val;
             cd = info_push(cd, sprintf_alloc("rra[%d].alpha", i), RD_I_VAL,
                            info);
@@ -231,6 +232,7 @@
         for (ii = 0; ii < rrd.stat_head->ds_cnt; ii++) {
             switch (current_cf) {
             case CF_HWPREDICT:
+            case CF_MHWPREDICT:
                 info.u_val =
                     rrd.cdp_prep[i * rrd.stat_head->ds_cnt +
                                  ii].scratch[CDP_hw_intercept].u_val;
Index: src/rrd_hw_math.h
===================================================================
--- src/rrd_hw_math.h	(revision 0)
+++ src/rrd_hw_math.h	(revision 0)
@@ -0,0 +1,112 @@
+/*****************************************************************************
+ * rrd_hw_math.h  Math functions for Holt-Winters computations
+ *****************************************************************************/
+
+#include "rrd.h"
+#include "rrd_format.h"
+
+/*****************************************************************************
+ * Functions for additive Holt-Winters
+ *****************************************************************************/
+
+rrd_value_t hw_additive_calculate_prediction(
+    rrd_value_t intercept,
+    rrd_value_t slope,
+    int null_count,
+    rrd_value_t seasonal_coef);
+
+rrd_value_t hw_additive_calculate_intercept(
+    rrd_value_t alpha,
+    rrd_value_t scratch,
+    rrd_value_t seasonal_coef,
+    unival *coefs);
+
+rrd_value_t hw_additive_calculate_seasonality(
+    rrd_value_t gamma,
+    rrd_value_t scratch,
+    rrd_value_t intercept,
+    rrd_value_t seasonal_coef);
+
+rrd_value_t hw_additive_init_seasonality(
+    rrd_value_t seasonal_coef,
+    rrd_value_t intercept);
+
+/*****************************************************************************
+ * Functions for multiplicative Holt-Winters
+ *****************************************************************************/
+
+rrd_value_t hw_multiplicative_calculate_prediction(
+    rrd_value_t intercept,
+    rrd_value_t slope,
+    int null_count,
+    rrd_value_t seasonal_coef);
+
+rrd_value_t hw_multiplicative_calculate_intercept(
+    rrd_value_t alpha,
+    rrd_value_t scratch,
+    rrd_value_t seasonal_coef,
+    unival *coefs);
+
+rrd_value_t hw_multiplicative_calculate_seasonality(
+    rrd_value_t gamma,
+    rrd_value_t scratch,
+    rrd_value_t intercept,
+    rrd_value_t seasonal_coef);
+
+rrd_value_t hw_multiplicative_init_seasonality(
+    rrd_value_t seasonal_coef,
+    rrd_value_t intercept);
+
+/*****************************************************************************
+ * Math functions common to additive and multiplicative Holt-Winters
+ *****************************************************************************/
+
+rrd_value_t hw_calculate_slope(
+    rrd_value_t beta,
+    unival *coefs);
+
+rrd_value_t hw_calculate_seasonal_deviation(
+    rrd_value_t gamma,
+    rrd_value_t prediction,
+    rrd_value_t observed,
+    rrd_value_t last);
+
+rrd_value_t hw_init_seasonal_deviation(
+    rrd_value_t prediction,
+    rrd_value_t observed);
+
+
+/* Function container */
+
+typedef struct hw_functions_t {
+    rrd_value_t   (*predict)(rrd_value_t intercept, 
+                             rrd_value_t slope, 
+                             int null_count, 
+                             rrd_value_t seasonal_coef);
+
+    rrd_value_t   (*intercept)(rrd_value_t alpha,
+                               rrd_value_t observed,
+                               rrd_value_t seasonal_coef,
+                               unival *coefs);
+
+    rrd_value_t   (*slope)(rrd_value_t beta, 
+                           unival *coefs);
+
+    rrd_value_t   (*seasonality)(rrd_value_t gamma, 
+                                 rrd_value_t observed, 
+                                 rrd_value_t intercept,
+                                 rrd_value_t seasonal_coef);
+
+    rrd_value_t   (*init_seasonality)(rrd_value_t seasonal_coef,
+                                 rrd_value_t intercept);
+
+    rrd_value_t   (*seasonal_deviation)(rrd_value_t gamma, 
+                                 rrd_value_t prediction, 
+                                 rrd_value_t observed,
+                                 rrd_value_t last);
+
+    rrd_value_t   (*init_seasonal_deviation)(rrd_value_t prediction, 
+                                 rrd_value_t observed);
+
+    rrd_value_t   identity;
+} hw_functions_t;
Index: src/rrd_dump.c
===================================================================
--- src/rrd_dump.c	(revision 1121)
+++ src/rrd_dump.c	(working copy)
@@ -176,6 +176,7 @@
         fprintf(out_file, "\t\t<params>\n");
         switch (cf_conv(rrd.rra_def[i].cf_nam)) {
         case CF_HWPREDICT:
+        case CF_MHWPREDICT:
             fprintf(out_file, "\t\t<hw_alpha> %0.10e </hw_alpha>\n",
                     rrd.rra_def[i].par[RRA_hw_alpha].u_val);
             fprintf(out_file, "\t\t<hw_beta> %0.10e </hw_beta>\n",
@@ -255,6 +256,7 @@
             }
             switch (cf_conv(rrd.rra_def[i].cf_nam)) {
             case CF_HWPREDICT:
+            case CF_MHWPREDICT:
                 value =
                     rrd.cdp_prep[i * rrd.stat_head->ds_cnt +
                                  ii].scratch[CDP_hw_intercept].u_val;
Index: src/rrd_hw.c
===================================================================
--- src/rrd_hw.c	(revision 1121)
+++ src/rrd_hw.c	(working copy)
@@ -8,140 +8,18 @@
 
 #include "rrd_tool.h"
 #include "rrd_hw.h"
+#include "rrd_hw_math.h"
+#include "rrd_hw_update.h"
 
+#define hw_dep_idx(rrd, rra_idx) rrd->rra_def[rra_idx].par[RRA_dependent_rra_idx].u_cnt
+
 /* #define DEBUG */
 
 /* private functions */
-unsigned long MyMod(
+static unsigned long MyMod(
     signed long val,
     unsigned long mod);
-int       update_hwpredict(
-    rrd_t *rrd,
-    unsigned long cdp_idx,
-    unsigned long rra_idx,
-    unsigned long ds_idx,
-    unsigned short CDP_scratch_idx);
-int       update_seasonal(
-    rrd_t *rrd,
-    unsigned long cdp_idx,
-    unsigned long rra_idx,
-    unsigned long ds_idx,
-    unsigned short CDP_scratch_idx,
-    rrd_value_t *seasonal_coef);
-int       update_devpredict(
-    rrd_t *rrd,
-    unsigned long cdp_idx,
-    unsigned long rra_idx,
-    unsigned long ds_idx,
-    unsigned short CDP_scratch_idx);
-int       update_devseasonal(
-    rrd_t *rrd,
-    unsigned long cdp_idx,
-    unsigned long rra_idx,
-    unsigned long ds_idx,
-    unsigned short CDP_scratch_idx,
-    rrd_value_t *seasonal_dev);
-int       update_failures(
-    rrd_t *rrd,
-    unsigned long cdp_idx,
-    unsigned long rra_idx,
-    unsigned long ds_idx,
-    unsigned short CDP_scratch_idx);
 
-int update_hwpredict(
-    rrd_t *rrd,
-    unsigned long cdp_idx,
-    unsigned long rra_idx,
-    unsigned long ds_idx,
-    unsigned short CDP_scratch_idx)
-{
-    rrd_value_t prediction, seasonal_coef;
-    unsigned long dependent_rra_idx, seasonal_cdp_idx;
-    unival   *coefs = rrd->cdp_prep[cdp_idx].scratch;
-    rra_def_t *current_rra = &(rrd->rra_def[rra_idx]);
-
-    /* save coefficients from current prediction */
-    coefs[CDP_hw_last_intercept].u_val = coefs[CDP_hw_intercept].u_val;
-    coefs[CDP_hw_last_slope].u_val = coefs[CDP_hw_slope].u_val;
-    coefs[CDP_last_null_count].u_cnt = coefs[CDP_null_count].u_cnt;
-
-    /* retrieve the current seasonal coef */
-    dependent_rra_idx = current_rra->par[RRA_dependent_rra_idx].u_cnt;
-    seasonal_cdp_idx = dependent_rra_idx * (rrd->stat_head->ds_cnt) + ds_idx;
-    if (dependent_rra_idx < rra_idx)
-        seasonal_coef =
-            rrd->cdp_prep[seasonal_cdp_idx].scratch[CDP_hw_last_seasonal].
-            u_val;
-    else
-        seasonal_coef =
-            rrd->cdp_prep[seasonal_cdp_idx].scratch[CDP_hw_seasonal].u_val;
-
-    /* compute the prediction */
-    if (isnan(coefs[CDP_hw_intercept].u_val)
-        || isnan(coefs[CDP_hw_slope].u_val)
-        || isnan(seasonal_coef)) {
-        prediction = DNAN;
-
-        /* bootstrap initialization of slope and intercept */
-        if (isnan(coefs[CDP_hw_intercept].u_val) &&
-            !isnan(coefs[CDP_scratch_idx].u_val)) {
-#ifdef DEBUG
-            fprintf(stderr, "Initialization of slope/intercept\n");
-#endif
-            coefs[CDP_hw_intercept].u_val = coefs[CDP_scratch_idx].u_val;
-            coefs[CDP_hw_last_intercept].u_val = coefs[CDP_scratch_idx].u_val;
-            /* initialize the slope to 0 */
-            coefs[CDP_hw_slope].u_val = 0.0;
-            coefs[CDP_hw_last_slope].u_val = 0.0;
-            /* initialize null count to 1 */
-            coefs[CDP_null_count].u_cnt = 1;
-            coefs[CDP_last_null_count].u_cnt = 1;
-        }
-        /* if seasonal coefficient is NA, then don't update intercept, slope */
-    } else {
-        prediction = coefs[CDP_hw_intercept].u_val +
-            (coefs[CDP_hw_slope].u_val) * (coefs[CDP_null_count].u_cnt)
-            + seasonal_coef;
-#ifdef DEBUG
-        fprintf(stderr, "computed prediction: %f\n", prediction);
-#endif
-        if (isnan(coefs[CDP_scratch_idx].u_val)) {
-            /* NA value, no updates of intercept, slope;
-             * increment the null count */
-            (coefs[CDP_null_count].u_cnt)++;
-        } else {
-#ifdef DEBUG
-            fprintf(stderr, "Updating intercept, slope\n");
-#endif
-            /* update the intercept */
-            coefs[CDP_hw_intercept].u_val =
-                (current_rra->par[RRA_hw_alpha].u_val) *
-                (coefs[CDP_scratch_idx].u_val - seasonal_coef) + (1 -
-                                                                  current_rra->
-                                                                  par
-                                                                  [RRA_hw_alpha].
-                                                                  u_val) *
-                (coefs[CDP_hw_intercept].u_val +
-                 (coefs[CDP_hw_slope].u_val) * (coefs[CDP_null_count].u_cnt));
-            /* update the slope */
-            coefs[CDP_hw_slope].u_val =
-                (current_rra->par[RRA_hw_beta].u_val) *
-                (coefs[CDP_hw_intercept].u_val -
-                 coefs[CDP_hw_last_intercept].u_val) + (1 -
-                                                        current_rra->
-                                                        par[RRA_hw_beta].
-                                                        u_val) *
-                (coefs[CDP_hw_slope].u_val);
-            /* reset the null count */
-            coefs[CDP_null_count].u_cnt = 1;
-        }
-    }
-
-    /* store the prediction for writing */
-    coefs[CDP_scratch_idx].u_val = prediction;
-    return 0;
-}
-
 int lookup_seasonal(
     rrd_t *rrd,
     unsigned long rra_idx,
@@ -199,348 +77,6 @@
     return -1;
 }
 
-int update_seasonal(
-    rrd_t *rrd,
-    unsigned long cdp_idx,
-    unsigned long rra_idx,
-    unsigned long ds_idx,
-    unsigned short CDP_scratch_idx,
-    rrd_value_t *seasonal_coef)
-{
-/* TODO: extract common if subblocks in the wake of I/O optimization */
-    rrd_value_t intercept, seasonal;
-    rra_def_t *current_rra = &(rrd->rra_def[rra_idx]);
-    rra_def_t *hw_rra =
-        &(rrd->rra_def[current_rra->par[RRA_dependent_rra_idx].u_cnt]);
-    /* obtain cdp_prep index for HWPREDICT */
-    unsigned long hw_cdp_idx = (current_rra->par[RRA_dependent_rra_idx].u_cnt)
-        * (rrd->stat_head->ds_cnt) + ds_idx;
-    unival   *coefs = rrd->cdp_prep[hw_cdp_idx].scratch;
-
-    /* update seasonal coefficient in cdp prep areas */
-    seasonal = rrd->cdp_prep[cdp_idx].scratch[CDP_hw_seasonal].u_val;
-    rrd->cdp_prep[cdp_idx].scratch[CDP_hw_last_seasonal].u_val = seasonal;
-    rrd->cdp_prep[cdp_idx].scratch[CDP_hw_seasonal].u_val =
-        seasonal_coef[ds_idx];
-
-    /* update seasonal value for disk */
-    if (current_rra->par[RRA_dependent_rra_idx].u_cnt < rra_idx)
-        /* associated HWPREDICT has already been updated */
-        /* check for possible NA values */
-        if (isnan(rrd->cdp_prep[cdp_idx].scratch[CDP_scratch_idx].u_val)) {
-            /* no update, store the old value unchanged,
-             * doesn't matter if it is NA */
-            rrd->cdp_prep[cdp_idx].scratch[CDP_scratch_idx].u_val = seasonal;
-        } else if (isnan(coefs[CDP_hw_last_intercept].u_val)
-                   || isnan(coefs[CDP_hw_last_slope].u_val)) {
-            /* this should never happen, as HWPREDICT was already updated */
-            rrd->cdp_prep[cdp_idx].scratch[CDP_scratch_idx].u_val = DNAN;
-        } else if (isnan(seasonal)) {
-            /* initialization: intercept is not currently being updated */
-#ifdef DEBUG
-            fprintf(stderr, "Initialization of seasonal coef %lu\n",
-                    rrd->rra_ptr[rra_idx].cur_row);
-#endif
-            rrd->cdp_prep[cdp_idx].scratch[CDP_scratch_idx].u_val
-                -= coefs[CDP_hw_last_intercept].u_val;
-        } else {
-            intercept = coefs[CDP_hw_intercept].u_val;
-#ifdef DEBUG
-            fprintf(stderr,
-                    "Updating seasonal, params: gamma %f, new intercept %f, old seasonal %f\n",
-                    current_rra->par[RRA_seasonal_gamma].u_val,
-                    intercept, seasonal);
-#endif
-            rrd->cdp_prep[cdp_idx].scratch[CDP_scratch_idx].u_val =
-                (current_rra->par[RRA_seasonal_gamma].u_val) *
-                (rrd->cdp_prep[cdp_idx].scratch[CDP_scratch_idx].u_val -
-                 intercept) + (1 -
-                               current_rra->par[RRA_seasonal_gamma].u_val) *
-                seasonal;
-    } else {
-        /* SEASONAL array is updated first, which means the new intercept
-         * hasn't be computed; so we compute it here. */
-
-        /* check for possible NA values */
-        if (isnan(rrd->cdp_prep[cdp_idx].scratch[CDP_scratch_idx].u_val)) {
-            /* no update, simple store the old value unchanged,
-             * doesn't matter if it is NA */
-            rrd->cdp_prep[cdp_idx].scratch[CDP_scratch_idx].u_val = seasonal;
-        } else if (isnan(coefs[CDP_hw_intercept].u_val)
-                   || isnan(coefs[CDP_hw_slope].u_val)) {
-            /* Initialization of slope and intercept will occur.
-             * force seasonal coefficient to 0. */
-            rrd->cdp_prep[cdp_idx].scratch[CDP_scratch_idx].u_val = 0.0;
-        } else if (isnan(seasonal)) {
-            /* initialization: intercept will not be updated
-             * CDP_hw_intercept = CDP_hw_last_intercept; just need to 
-             * subtract this baseline value. */
-#ifdef DEBUG
-            fprintf(stderr, "Initialization of seasonal coef %lu\n",
-                    rrd->rra_ptr[rra_idx].cur_row);
-#endif
-            rrd->cdp_prep[cdp_idx].scratch[CDP_scratch_idx].u_val -=
-                coefs[CDP_hw_intercept].u_val;
-        } else {
-            /* Note that we must get CDP_scratch_idx from SEASONAL array, as CDP_scratch_idx
-             * for HWPREDICT array will be DNAN. */
-            intercept = (hw_rra->par[RRA_hw_alpha].u_val) *
-                (rrd->cdp_prep[cdp_idx].scratch[CDP_scratch_idx].u_val -
-                 seasonal)
-                + (1 -
-                   hw_rra->par[RRA_hw_alpha].u_val) *
-                (coefs[CDP_hw_intercept].u_val +
-                 (coefs[CDP_hw_slope].u_val) * (coefs[CDP_null_count].u_cnt));
-            rrd->cdp_prep[cdp_idx].scratch[CDP_scratch_idx].u_val =
-                (current_rra->par[RRA_seasonal_gamma].u_val) *
-                (rrd->cdp_prep[cdp_idx].scratch[CDP_scratch_idx].u_val -
-                 intercept) + (1 -
-                               current_rra->par[RRA_seasonal_gamma].u_val) *
-                seasonal;
-        }
-    }
-#ifdef DEBUG
-    fprintf(stderr, "seasonal coefficient set= %f\n",
-            rrd->cdp_prep[cdp_idx].scratch[CDP_scratch_idx].u_val);
-#endif
-    return 0;
-}
-
-int update_devpredict(
-    rrd_t *rrd,
-    unsigned long cdp_idx,
-    unsigned long rra_idx,
-    unsigned long ds_idx,
-    unsigned short CDP_scratch_idx)
-{
-    /* there really isn't any "update" here; the only reason this information
-     * is stored separately from DEVSEASONAL is to preserve deviation predictions
-     * for a longer duration than one seasonal cycle. */
-    unsigned long seasonal_cdp_idx =
-        (rrd->rra_def[rra_idx].par[RRA_dependent_rra_idx].u_cnt)
-        * (rrd->stat_head->ds_cnt) + ds_idx;
-
-    if (rrd->rra_def[rra_idx].par[RRA_dependent_rra_idx].u_cnt < rra_idx) {
-        /* associated DEVSEASONAL array already updated */
-        rrd->cdp_prep[cdp_idx].scratch[CDP_scratch_idx].u_val
-            =
-            rrd->cdp_prep[seasonal_cdp_idx].
-            scratch[CDP_last_seasonal_deviation].u_val;
-    } else {
-        /* associated DEVSEASONAL not yet updated */
-        rrd->cdp_prep[cdp_idx].scratch[CDP_scratch_idx].u_val
-            =
-            rrd->cdp_prep[seasonal_cdp_idx].scratch[CDP_seasonal_deviation].
-            u_val;
-    }
-    return 0;
-}
-
-int update_devseasonal(
-    rrd_t *rrd,
-    unsigned long cdp_idx,
-    unsigned long rra_idx,
-    unsigned long ds_idx,
-    unsigned short CDP_scratch_idx,
-    rrd_value_t *seasonal_dev)
-{
-    rrd_value_t prediction = 0, seasonal_coef = DNAN;
-    rra_def_t *current_rra = &(rrd->rra_def[rra_idx]);
-
-    /* obtain cdp_prep index for HWPREDICT */
-    unsigned long hw_rra_idx = current_rra->par[RRA_dependent_rra_idx].u_cnt;
-    unsigned long hw_cdp_idx = hw_rra_idx * (rrd->stat_head->ds_cnt) + ds_idx;
-    unsigned long seasonal_cdp_idx;
-    unival   *coefs = rrd->cdp_prep[hw_cdp_idx].scratch;
-
-    rrd->cdp_prep[cdp_idx].scratch[CDP_last_seasonal_deviation].u_val =
-        rrd->cdp_prep[cdp_idx].scratch[CDP_seasonal_deviation].u_val;
-    /* retrieve the next seasonal deviation value, could be NA */
-    rrd->cdp_prep[cdp_idx].scratch[CDP_seasonal_deviation].u_val =
-        seasonal_dev[ds_idx];
-
-    /* retrieve the current seasonal_coef (not to be confused with the
-     * current seasonal deviation). Could make this more readable by introducing
-     * some wrapper functions. */
-    seasonal_cdp_idx =
-        (rrd->rra_def[hw_rra_idx].par[RRA_dependent_rra_idx].u_cnt)
-        * (rrd->stat_head->ds_cnt) + ds_idx;
-    if (rrd->rra_def[hw_rra_idx].par[RRA_dependent_rra_idx].u_cnt < rra_idx)
-        /* SEASONAL array already updated */
-        seasonal_coef =
-            rrd->cdp_prep[seasonal_cdp_idx].scratch[CDP_hw_last_seasonal].
-            u_val;
-    else
-        /* SEASONAL array not yet updated */
-        seasonal_coef =
-            rrd->cdp_prep[seasonal_cdp_idx].scratch[CDP_hw_seasonal].u_val;
-
-    /* compute the abs value of the difference between the prediction and
-     * observed value */
-    if (hw_rra_idx < rra_idx) {
-        /* associated HWPREDICT has already been updated */
-        if (isnan(coefs[CDP_hw_last_intercept].u_val) ||
-            isnan(coefs[CDP_hw_last_slope].u_val) || isnan(seasonal_coef)) {
-            /* one of the prediction values is uinitialized */
-            rrd->cdp_prep[cdp_idx].scratch[CDP_scratch_idx].u_val = DNAN;
-            return 0;
-        } else {
-            prediction = coefs[CDP_hw_last_intercept].u_val +
-                (coefs[CDP_hw_last_slope].u_val) *
-                (coefs[CDP_last_null_count].u_cnt)
-                + seasonal_coef;
-        }
-    } else {
-        /* associated HWPREDICT has NOT been updated */
-        if (isnan(coefs[CDP_hw_intercept].u_val) ||
-            isnan(coefs[CDP_hw_slope].u_val) || isnan(seasonal_coef)) {
-            /* one of the prediction values is uinitialized */
-            rrd->cdp_prep[cdp_idx].scratch[CDP_scratch_idx].u_val = DNAN;
-            return 0;
-        } else {
-            prediction = coefs[CDP_hw_intercept].u_val +
-                (coefs[CDP_hw_slope].u_val) * (coefs[CDP_null_count].u_cnt)
-                + seasonal_coef;
-        }
-    }
-
-    if (isnan(rrd->cdp_prep[cdp_idx].scratch[CDP_scratch_idx].u_val)) {
-        /* no update, store existing value unchanged, doesn't
-         * matter if it is NA */
-        rrd->cdp_prep[cdp_idx].scratch[CDP_scratch_idx].u_val =
-            rrd->cdp_prep[cdp_idx].scratch[CDP_last_seasonal_deviation].u_val;
-    } else
-        if (isnan
-            (rrd->cdp_prep[cdp_idx].scratch[CDP_last_seasonal_deviation].
-             u_val)) {
-        /* initialization */
-#ifdef DEBUG
-        fprintf(stderr, "Initialization of seasonal deviation\n");
-#endif
-        rrd->cdp_prep[cdp_idx].scratch[CDP_scratch_idx].u_val =
-            fabs(prediction -
-                 rrd->cdp_prep[cdp_idx].scratch[CDP_scratch_idx].u_val);
-    } else {
-        /* exponential smoothing update */
-        rrd->cdp_prep[cdp_idx].scratch[CDP_scratch_idx].u_val =
-            (rrd->rra_def[rra_idx].par[RRA_seasonal_gamma].u_val) *
-            fabs(prediction -
-                 rrd->cdp_prep[cdp_idx].scratch[CDP_scratch_idx].u_val)
-            + (1 -
-               rrd->rra_def[rra_idx].par[RRA_seasonal_gamma].u_val) *
-            (rrd->cdp_prep[cdp_idx].scratch[CDP_last_seasonal_deviation].
-             u_val);
-    }
-    return 0;
-}
-
-/* Check for a failure based on a threshold # of violations within the specified
- * window. */
-int update_failures(
-    rrd_t *rrd,
-    unsigned long cdp_idx,
-    unsigned long rra_idx,
-    unsigned long ds_idx,
-    unsigned short CDP_scratch_idx)
-{
-    /* detection of a violation depends on 3 RRAs:
-     * HWPREDICT, SEASONAL, and DEVSEASONAL */
-    rra_def_t *current_rra = &(rrd->rra_def[rra_idx]);
-    unsigned long dev_rra_idx = current_rra->par[RRA_dependent_rra_idx].u_cnt;
-    rra_def_t *dev_rra = &(rrd->rra_def[dev_rra_idx]);
-    unsigned long hw_rra_idx = dev_rra->par[RRA_dependent_rra_idx].u_cnt;
-    rra_def_t *hw_rra = &(rrd->rra_def[hw_rra_idx]);
-    unsigned long seasonal_rra_idx = hw_rra->par[RRA_dependent_rra_idx].u_cnt;
-    unsigned long temp_cdp_idx;
-    rrd_value_t deviation = DNAN;
-    rrd_value_t seasonal_coef = DNAN;
-    rrd_value_t prediction = DNAN;
-    char      violation = 0;
-    unsigned short violation_cnt = 0, i;
-    char     *violations_array;
-
-    /* usual checks to determine the order of the RRAs */
-    temp_cdp_idx = dev_rra_idx * (rrd->stat_head->ds_cnt) + ds_idx;
-    if (rra_idx < seasonal_rra_idx) {
-        /* DEVSEASONAL not yet updated */
-        deviation =
-            rrd->cdp_prep[temp_cdp_idx].scratch[CDP_seasonal_deviation].u_val;
-    } else {
-        /* DEVSEASONAL already updated */
-        deviation =
-            rrd->cdp_prep[temp_cdp_idx].scratch[CDP_last_seasonal_deviation].
-            u_val;
-    }
-    if (!isnan(deviation)) {
-
-        temp_cdp_idx = seasonal_rra_idx * (rrd->stat_head->ds_cnt) + ds_idx;
-        if (rra_idx < seasonal_rra_idx) {
-            /* SEASONAL not yet updated */
-            seasonal_coef =
-                rrd->cdp_prep[temp_cdp_idx].scratch[CDP_hw_seasonal].u_val;
-        } else {
-            /* SEASONAL already updated */
-            seasonal_coef =
-                rrd->cdp_prep[temp_cdp_idx].scratch[CDP_hw_last_seasonal].
-                u_val;
-        }
-        /* in this code block, we know seasonal coef is not DNAN, because deviation is not
-         * null */
-
-        temp_cdp_idx = hw_rra_idx * (rrd->stat_head->ds_cnt) + ds_idx;
-        if (rra_idx < hw_rra_idx) {
-            /* HWPREDICT not yet updated */
-            prediction =
-                rrd->cdp_prep[temp_cdp_idx].scratch[CDP_hw_intercept].u_val +
-                (rrd->cdp_prep[temp_cdp_idx].scratch[CDP_hw_slope].u_val)
-                * (rrd->cdp_prep[temp_cdp_idx].scratch[CDP_null_count].u_cnt)
-                + seasonal_coef;
-        } else {
-            /* HWPREDICT already updated */
-            prediction =
-                rrd->cdp_prep[temp_cdp_idx].scratch[CDP_hw_last_intercept].
-                u_val +
-                (rrd->cdp_prep[temp_cdp_idx].scratch[CDP_hw_last_slope].u_val)
-                *
-                (rrd->cdp_prep[temp_cdp_idx].scratch[CDP_last_null_count].
-                 u_cnt)
-                + seasonal_coef;
-        }
-
-        /* determine if the observed value is a violation */
-        if (!isnan(rrd->cdp_prep[cdp_idx].scratch[CDP_scratch_idx].u_val)) {
-            if (rrd->cdp_prep[cdp_idx].scratch[CDP_scratch_idx].u_val >
-                prediction +
-                (current_rra->par[RRA_delta_pos].u_val) * deviation
-                || rrd->cdp_prep[cdp_idx].scratch[CDP_scratch_idx].u_val <
-                prediction -
-                (current_rra->par[RRA_delta_neg].u_val) * deviation)
-                violation = 1;
-        } else {
-            violation = 1;  /* count DNAN values as violations */
-        }
-
-    }
-
-    /* determine if a failure has occurred and update the failure array */
-    violation_cnt = violation;
-    violations_array = (char *) ((void *) rrd->cdp_prep[cdp_idx].scratch);
-    for (i = current_rra->par[RRA_window_len].u_cnt; i > 1; i--) {
-        /* shift */
-        violations_array[i - 1] = violations_array[i - 2];
-        violation_cnt += violations_array[i - 1];
-    }
-    violations_array[0] = violation;
-
-    if (violation_cnt < current_rra->par[RRA_failure_threshold].u_cnt)
-        /* not a failure */
-        rrd->cdp_prep[cdp_idx].scratch[CDP_scratch_idx].u_val = 0.0;
-    else
-        rrd->cdp_prep[cdp_idx].scratch[CDP_scratch_idx].u_val = 1.0;
-
-    return (rrd->cdp_prep[cdp_idx].scratch[CDP_scratch_idx].u_val);
-}
-
 /* For the specified CDP prep area and the FAILURES RRA,
  * erase all history of past violations.
  */
@@ -693,14 +229,29 @@
     free(working_average);
 
     if (cf_conv(rrd->rra_def[rra_idx].cf_nam) == CF_SEASONAL) {
+        rrd_value_t   (*init_seasonality)(rrd_value_t seasonal_coef,
+                                 rrd_value_t intercept);
+        switch(cf_conv(rrd->rra_def[hw_dep_idx(rrd, rra_idx)].cf_nam)) {
+            case CF_HWPREDICT:
+                    init_seasonality = hw_additive_init_seasonality;
+                    break;
+            case CF_MHWPREDICT:
+                    init_seasonality = hw_multiplicative_init_seasonality;
+                    break;
+            default:
+                    rrd_set_error("apply smoother: SEASONAL rra doesn't have "
+                                    "valid dependency: %s", rrd->rra_def[hw_dep_idx(rrd, rra_idx)].cf_nam);
+                    return -1;
+        }
+
         for (j = 0; j < row_length; ++j) {
             for (i = 0; i < row_count; ++i) {
-                rrd_values[i * row_length + j] -= baseline[j];
+                rrd_values[i * row_length + j] =
+                        init_seasonality(rrd_values[i * row_length + j], baseline[j]);
             }
             /* update the baseline coefficient,
              * first, compute the cdp_index. */
-            offset = (rrd->rra_def[rra_idx].par[RRA_dependent_rra_idx].u_cnt)
-                * row_length + j;
+            offset = hw_dep_idx(rrd, rra_idx) * row_length + j;
             (rrd->cdp_prep[offset]).scratch[CDP_hw_intercept].u_val +=
                 baseline[j];
         }
@@ -775,6 +326,7 @@
         cdp_idx = rra_idx * (rrd->stat_head->ds_cnt) + ds_idx;
         switch (cf_conv(rrd->rra_def[rra_idx].cf_nam)) {
         case CF_HWPREDICT:
+        case CF_MHWPREDICT:
             init_hwpredict_cdp(&(rrd->cdp_prep[cdp_idx]));
             break;
         case CF_SEASONAL:
@@ -850,31 +402,83 @@
     unsigned short CDP_scratch_idx,
     rrd_value_t *seasonal_coef)
 {
+    static hw_functions_t hw_multiplicative_functions = 
+    {
+        hw_multiplicative_calculate_prediction,
+        hw_multiplicative_calculate_intercept,
+        hw_calculate_slope,
+        hw_multiplicative_calculate_seasonality,
+        hw_multiplicative_init_seasonality,
+        hw_calculate_seasonal_deviation,
+        hw_init_seasonal_deviation,
+        1.0 // identity value
+    }; 
+
+    static hw_functions_t hw_additive_functions = {
+        hw_additive_calculate_prediction,
+        hw_additive_calculate_intercept,
+        hw_calculate_slope,
+        hw_additive_calculate_seasonality,
+        hw_additive_init_seasonality,
+        hw_calculate_seasonal_deviation,
+        hw_init_seasonal_deviation,
+        0.0 // identity value 
+    };
+
     rrd->cdp_prep[cdp_idx].scratch[CDP_scratch_idx].u_val = pdp_val;
     switch (current_cf) {
-    case CF_AVERAGE:
-    default:
-        return 0;
     case CF_HWPREDICT:
         return update_hwpredict(rrd, cdp_idx, rra_idx, ds_idx,
-                                CDP_scratch_idx);
+                                CDP_scratch_idx, &hw_additive_functions);
+    case CF_MHWPREDICT:
+        return update_hwpredict(rrd, cdp_idx, rra_idx, ds_idx,
+                                CDP_scratch_idx, &hw_multiplicative_functions);
     case CF_DEVPREDICT:
         return update_devpredict(rrd, cdp_idx, rra_idx, ds_idx,
                                  CDP_scratch_idx);
     case CF_SEASONAL:
-        return update_seasonal(rrd, cdp_idx, rra_idx, ds_idx, CDP_scratch_idx,
-                               seasonal_coef);
+        switch (cf_conv(rrd->rra_def[hw_dep_idx(rrd, rra_idx)].cf_nam)) {
+            case CF_HWPREDICT:
+                    return update_seasonal(rrd, cdp_idx, rra_idx, ds_idx, CDP_scratch_idx,
+                                    seasonal_coef, &hw_additive_functions);
+            case CF_MHWPREDICT:
+                    return update_seasonal(rrd, cdp_idx, rra_idx, ds_idx, CDP_scratch_idx,
+                                    seasonal_coef, &hw_multiplicative_functions);
+            default:
+                    return -1;
+        }
     case CF_DEVSEASONAL:
-        return update_devseasonal(rrd, cdp_idx, rra_idx, ds_idx,
-                                  CDP_scratch_idx, seasonal_coef);
+        switch (cf_conv(rrd->rra_def[hw_dep_idx(rrd, rra_idx)].cf_nam)) {
+            case CF_HWPREDICT:
+                    return update_devseasonal(rrd, cdp_idx, rra_idx, ds_idx,
+                                    CDP_scratch_idx, seasonal_coef, 
+                                    &hw_additive_functions);
+            case CF_MHWPREDICT:
+                    return update_devseasonal(rrd, cdp_idx, rra_idx, ds_idx,
+                                    CDP_scratch_idx, seasonal_coef, 
+                                    &hw_multiplicative_functions);
+            default:
+                    return -1;
+        }
     case CF_FAILURES:
-        return update_failures(rrd, cdp_idx, rra_idx, ds_idx,
-                               CDP_scratch_idx);
+        switch (cf_conv(rrd->rra_def[hw_dep_idx(rrd, hw_dep_idx(rrd, rra_idx))].cf_nam)) { 
+            case CF_HWPREDICT:
+                    return update_failures(rrd, cdp_idx, rra_idx, ds_idx,
+                                    CDP_scratch_idx, &hw_additive_functions);
+            case CF_MHWPREDICT:
+                    return update_failures(rrd, cdp_idx, rra_idx, ds_idx,
+                                    CDP_scratch_idx, &hw_multiplicative_functions);
+            default:
+                    return -1;
+        }
+    case CF_AVERAGE:
+    default:
+        return 0;
     }
     return -1;
 }
 
-unsigned long MyMod(
+static unsigned long MyMod(
     signed long val,
     unsigned long mod)
 {
Index: src/rrd_hw_update.c
===================================================================
--- src/rrd_hw_update.c	(revision 0)
+++ src/rrd_hw_update.c	(revision 0)
@@ -0,0 +1,451 @@
+/*****************************************************************************
+ * rrd_hw_update.c  Functions for updating a Holt-Winters RRA
+ ****************************************************************************/
+
+#include "rrd_format.h"
+#include "rrd_config.h"
+#include "rrd_hw_math.h"
+#include "rrd_hw_update.h"
+
+static void init_slope_intercept(
+    unival *coefs,
+    unsigned short CDP_scratch_idx)
+{
+#ifdef DEBUG
+    fprintf(stderr, "Initialization of slope/intercept\n");
+#endif
+    coefs[CDP_hw_intercept].u_val = coefs[CDP_scratch_idx].u_val;
+    coefs[CDP_hw_last_intercept].u_val = coefs[CDP_scratch_idx].u_val;
+    /* initialize the slope to 0 */
+    coefs[CDP_hw_slope].u_val = 0.0;
+    coefs[CDP_hw_last_slope].u_val = 0.0;
+    /* initialize null count to 1 */
+    coefs[CDP_null_count].u_cnt = 1;
+    coefs[CDP_last_null_count].u_cnt = 1;
+}
+
+static int hw_is_violation(
+    rrd_value_t observed,
+    rrd_value_t prediction,
+    rrd_value_t deviation,
+    rrd_value_t delta_pos,
+    rrd_value_t delta_neg)
+{
+    return (observed > prediction + delta_pos * deviation
+                || observed < prediction - delta_neg * deviation);
+}
+
+int update_hwpredict(
+    rrd_t *rrd,
+    unsigned long cdp_idx,
+    unsigned long rra_idx,
+    unsigned long ds_idx,
+    unsigned short CDP_scratch_idx,
+    hw_functions_t *functions)
+{
+    rrd_value_t prediction;
+    unsigned long dependent_rra_idx, seasonal_cdp_idx;
+    unival   *coefs = rrd->cdp_prep[cdp_idx].scratch;
+    rra_def_t *current_rra = &(rrd->rra_def[rra_idx]);
+
+    /* save coefficients from current prediction */
+    coefs[CDP_hw_last_intercept].u_val = coefs[CDP_hw_intercept].u_val;
+    coefs[CDP_hw_last_slope].u_val = coefs[CDP_hw_slope].u_val;
+    coefs[CDP_last_null_count].u_cnt = coefs[CDP_null_count].u_cnt;
+
+    /* retrieve the current seasonal coef */
+    dependent_rra_idx = current_rra->par[RRA_dependent_rra_idx].u_cnt;
+    seasonal_cdp_idx = dependent_rra_idx * (rrd->stat_head->ds_cnt) + ds_idx;
+
+    rrd_value_t seasonal_coef = (dependent_rra_idx < rra_idx)
+            ? rrd->cdp_prep[seasonal_cdp_idx].scratch[CDP_hw_last_seasonal].u_val
+            : rrd->cdp_prep[seasonal_cdp_idx].scratch[CDP_hw_seasonal].u_val;
+
+    /* compute the prediction */
+    if (isnan(coefs[CDP_hw_intercept].u_val)
+        || isnan(coefs[CDP_hw_slope].u_val)
+        || isnan(seasonal_coef)) {
+        prediction = DNAN;
+
+        /* bootstrap initialization of slope and intercept */
+        if (isnan(coefs[CDP_hw_intercept].u_val) &&
+            !isnan(coefs[CDP_scratch_idx].u_val)) {
+            init_slope_intercept(coefs, CDP_scratch_idx);
+        }
+        /* if seasonal coefficient is NA, then don't update intercept, slope */
+    } else {
+        prediction = functions->predict(
+                        coefs[CDP_hw_intercept].u_val,
+                        coefs[CDP_hw_slope].u_val,
+                        coefs[CDP_null_count].u_cnt,
+                        seasonal_coef);
+#ifdef DEBUG
+        fprintf(stderr, "computed prediction: %f (intercept %f, slope %f, season %f)\n", 
+                        prediction, coefs[CDP_hw_intercept].u_val,
+                        coefs[CDP_hw_slope].u_val,
+                        seasonal_coef);
+#endif
+        if (isnan(coefs[CDP_scratch_idx].u_val)) {
+            /* NA value, no updates of intercept, slope;
+             * increment the null count */
+            (coefs[CDP_null_count].u_cnt)++;
+        } else {
+            /* update the intercept */
+            coefs[CDP_hw_intercept].u_val = functions->intercept(
+                            current_rra->par[RRA_hw_alpha].u_val,
+                            coefs[CDP_scratch_idx].u_val, seasonal_coef,
+                            coefs);
+
+            /* update the slope */
+            coefs[CDP_hw_slope].u_val = functions->slope(
+                            current_rra->par[RRA_hw_beta].u_val, coefs);
+
+            /* reset the null count */
+            coefs[CDP_null_count].u_cnt = 1;
+#ifdef DEBUG
+            fprintf(stderr, "Updating intercept = %f, slope = %f\n",
+                            coefs[CDP_hw_intercept].u_val,
+                            coefs[CDP_hw_slope].u_val);
+#endif
+        }
+    }
+
+    /* store the prediction for writing */
+    coefs[CDP_scratch_idx].u_val = prediction;
+    return 0;
+}
+
+int update_seasonal(
+    rrd_t *rrd,
+    unsigned long cdp_idx,
+    unsigned long rra_idx,
+    unsigned long ds_idx,
+    unsigned short CDP_scratch_idx,
+    rrd_value_t *seasonal_coef,
+    hw_functions_t *functions)
+{
+/* TODO: extract common if subblocks in the wake of I/O optimization */
+    rrd_value_t intercept, seasonal;
+    rra_def_t *current_rra = &(rrd->rra_def[rra_idx]);
+    rra_def_t *hw_rra =
+        &(rrd->rra_def[current_rra->par[RRA_dependent_rra_idx].u_cnt]);
+
+    /* obtain cdp_prep index for HWPREDICT */
+    unsigned long hw_cdp_idx = (current_rra->par[RRA_dependent_rra_idx].u_cnt)
+        * (rrd->stat_head->ds_cnt) + ds_idx;
+    unival   *coefs = rrd->cdp_prep[hw_cdp_idx].scratch;
+
+    /* update seasonal coefficient in cdp prep areas */
+    seasonal = rrd->cdp_prep[cdp_idx].scratch[CDP_hw_seasonal].u_val;
+    rrd->cdp_prep[cdp_idx].scratch[CDP_hw_last_seasonal].u_val = seasonal;
+    rrd->cdp_prep[cdp_idx].scratch[CDP_hw_seasonal].u_val = seasonal_coef[ds_idx];
+
+    if (isnan(rrd->cdp_prep[cdp_idx].scratch[CDP_scratch_idx].u_val)) {
+        /* no update, store the old value unchanged,
+         * doesn't matter if it is NA */
+        rrd->cdp_prep[cdp_idx].scratch[CDP_scratch_idx].u_val = seasonal;
+        return 0;
+    }
+
+    /* update seasonal value for disk */
+    if (current_rra->par[RRA_dependent_rra_idx].u_cnt < rra_idx) {
+        /* associated HWPREDICT has already been updated */
+        /* check for possible NA values */
+        if (isnan(coefs[CDP_hw_last_intercept].u_val)
+                   || isnan(coefs[CDP_hw_last_slope].u_val)) {
+            /* this should never happen, as HWPREDICT was already updated */
+            rrd->cdp_prep[cdp_idx].scratch[CDP_scratch_idx].u_val = DNAN;
+        } else if (isnan(seasonal)) {
+            /* initialization: intercept is not currently being updated */
+#ifdef DEBUG
+            fprintf(stderr, "Initialization of seasonal coef %lu\n",
+                    rrd->rra_ptr[rra_idx].cur_row);
+#endif
+            rrd->cdp_prep[cdp_idx].scratch[CDP_scratch_idx].u_val =
+                    functions->init_seasonality(
+                            rrd->cdp_prep[cdp_idx].scratch[CDP_scratch_idx].u_val,
+                            coefs[CDP_hw_last_intercept].u_val);
+        } else {
+            intercept = coefs[CDP_hw_intercept].u_val;
+
+            rrd->cdp_prep[cdp_idx].scratch[CDP_scratch_idx].u_val =
+                    functions->seasonality(
+                                    current_rra->par[RRA_seasonal_gamma].u_val,
+                                    rrd->cdp_prep[cdp_idx].scratch[CDP_scratch_idx].u_val,
+                                    intercept, seasonal);
+#ifdef DEBUG
+            fprintf(stderr,
+                    "Updating seasonal = %f (params: gamma %f, new intercept %f, old seasonal %f)\n",
+                    rrd->cdp_prep[cdp_idx].scratch[CDP_scratch_idx].u_val,
+                    current_rra->par[RRA_seasonal_gamma].u_val,
+                    intercept, seasonal);
+#endif
+        }
+    } else {
+        /* SEASONAL array is updated first, which means the new intercept
+         * hasn't be computed; so we compute it here. */
+
+        /* check for possible NA values */
+        if (isnan(coefs[CDP_hw_intercept].u_val)
+                   || isnan(coefs[CDP_hw_slope].u_val)) {
+            /* Initialization of slope and intercept will occur.
+             * force seasonal coefficient to 0 or 1. */
+            rrd->cdp_prep[cdp_idx].scratch[CDP_scratch_idx].u_val = 
+                    functions->identity;
+        } else if (isnan(seasonal)) {
+            /* initialization: intercept will not be updated
+             * CDP_hw_intercept = CDP_hw_last_intercept; just need to 
+             * subtract/divide by this baseline value. */
+#ifdef DEBUG
+            fprintf(stderr, "Initialization of seasonal coef %lu\n",
+                    rrd->rra_ptr[rra_idx].cur_row);
+#endif
+            rrd->cdp_prep[cdp_idx].scratch[CDP_scratch_idx].u_val =
+                    functions->init_seasonality(
+                            rrd->cdp_prep[cdp_idx].scratch[CDP_scratch_idx].u_val,
+                            coefs[CDP_hw_intercept].u_val);
+        } else {
+            /* Note that we must get CDP_scratch_idx from SEASONAL array, as CDP_scratch_idx
+             * for HWPREDICT array will be DNAN. */
+            intercept = functions->intercept(
+                    hw_rra->par[RRA_hw_alpha].u_val,
+                    rrd->cdp_prep[cdp_idx].scratch[CDP_scratch_idx].u_val,
+                    seasonal, coefs);
+
+            rrd->cdp_prep[cdp_idx].scratch[CDP_scratch_idx].u_val =
+                    functions->seasonality(
+                                    current_rra->par[RRA_seasonal_gamma].u_val,
+                                    rrd->cdp_prep[cdp_idx].scratch[CDP_scratch_idx].u_val,
+                                    intercept, seasonal);
+        }
+    }
+#ifdef DEBUG
+    fprintf(stderr, "seasonal coefficient set= %f\n",
+            rrd->cdp_prep[cdp_idx].scratch[CDP_scratch_idx].u_val);
+#endif
+    return 0;
+}
+
+int update_devpredict(
+    rrd_t *rrd,
+    unsigned long cdp_idx,
+    unsigned long rra_idx,
+    unsigned long ds_idx,
+    unsigned short CDP_scratch_idx)
+{
+    /* there really isn't any "update" here; the only reason this information
+     * is stored separately from DEVSEASONAL is to preserve deviation predictions
+     * for a longer duration than one seasonal cycle. */
+    unsigned long seasonal_cdp_idx =
+        (rrd->rra_def[rra_idx].par[RRA_dependent_rra_idx].u_cnt)
+        * (rrd->stat_head->ds_cnt) + ds_idx;
+
+    if (rrd->rra_def[rra_idx].par[RRA_dependent_rra_idx].u_cnt < rra_idx) {
+        /* associated DEVSEASONAL array already updated */
+        rrd->cdp_prep[cdp_idx].scratch[CDP_scratch_idx].u_val
+            = rrd->cdp_prep[seasonal_cdp_idx].scratch[CDP_last_seasonal_deviation].u_val;
+    } else {
+        /* associated DEVSEASONAL not yet updated */
+        rrd->cdp_prep[cdp_idx].scratch[CDP_scratch_idx].u_val
+            = rrd->cdp_prep[seasonal_cdp_idx].scratch[CDP_seasonal_deviation].u_val;
+    }
+    return 0;
+}
+
+int update_devseasonal(
+    rrd_t *rrd,
+    unsigned long cdp_idx,
+    unsigned long rra_idx,
+    unsigned long ds_idx,
+    unsigned short CDP_scratch_idx,
+    rrd_value_t *seasonal_dev,
+    hw_functions_t *functions)
+{
+    rrd_value_t prediction = 0, seasonal_coef = DNAN;
+    rra_def_t *current_rra = &(rrd->rra_def[rra_idx]);
+
+    /* obtain cdp_prep index for HWPREDICT */
+    unsigned long hw_rra_idx = current_rra->par[RRA_dependent_rra_idx].u_cnt;
+    unsigned long hw_cdp_idx = hw_rra_idx * (rrd->stat_head->ds_cnt) + ds_idx;
+    unsigned long seasonal_cdp_idx;
+    unival   *coefs = rrd->cdp_prep[hw_cdp_idx].scratch;
+
+    rrd->cdp_prep[cdp_idx].scratch[CDP_last_seasonal_deviation].u_val =
+        rrd->cdp_prep[cdp_idx].scratch[CDP_seasonal_deviation].u_val;
+    /* retrieve the next seasonal deviation value, could be NA */
+    rrd->cdp_prep[cdp_idx].scratch[CDP_seasonal_deviation].u_val =
+        seasonal_dev[ds_idx];
+
+    /* retrieve the current seasonal_coef (not to be confused with the
+     * current seasonal deviation). Could make this more readable by introducing
+     * some wrapper functions. */
+    seasonal_cdp_idx =
+        (rrd->rra_def[hw_rra_idx].par[RRA_dependent_rra_idx].u_cnt)
+        * (rrd->stat_head->ds_cnt) + ds_idx;
+    if (rrd->rra_def[hw_rra_idx].par[RRA_dependent_rra_idx].u_cnt < rra_idx)
+        /* SEASONAL array already updated */
+        seasonal_coef =
+            rrd->cdp_prep[seasonal_cdp_idx].scratch[CDP_hw_last_seasonal].
+            u_val;
+    else
+        /* SEASONAL array not yet updated */
+        seasonal_coef =
+            rrd->cdp_prep[seasonal_cdp_idx].scratch[CDP_hw_seasonal].u_val;
+
+    /* compute the abs value of the difference between the prediction and
+     * observed value */
+    if (hw_rra_idx < rra_idx) {
+        /* associated HWPREDICT has already been updated */
+        if (isnan(coefs[CDP_hw_last_intercept].u_val) ||
+            isnan(coefs[CDP_hw_last_slope].u_val) || isnan(seasonal_coef)) {
+            /* one of the prediction values is uinitialized */
+            rrd->cdp_prep[cdp_idx].scratch[CDP_scratch_idx].u_val = DNAN;
+            return 0;
+        } else {
+            prediction = functions->predict(coefs[CDP_hw_last_intercept].u_val,
+                coefs[CDP_hw_last_slope].u_val,
+                coefs[CDP_last_null_count].u_cnt,
+                seasonal_coef);
+        }
+    } else {
+        /* associated HWPREDICT has NOT been updated */
+        if (isnan(coefs[CDP_hw_intercept].u_val) ||
+            isnan(coefs[CDP_hw_slope].u_val) || isnan(seasonal_coef)) {
+            /* one of the prediction values is uinitialized */
+            rrd->cdp_prep[cdp_idx].scratch[CDP_scratch_idx].u_val = DNAN;
+            return 0;
+        } else {
+            prediction = functions->predict(coefs[CDP_hw_intercept].u_val,
+                coefs[CDP_hw_slope].u_val,
+                coefs[CDP_null_count].u_cnt,
+                seasonal_coef);
+        }
+    }
+
+    if (isnan(rrd->cdp_prep[cdp_idx].scratch[CDP_scratch_idx].u_val)) {
+        /* no update, store existing value unchanged, doesn't
+         * matter if it is NA */
+        rrd->cdp_prep[cdp_idx].scratch[CDP_scratch_idx].u_val =
+            rrd->cdp_prep[cdp_idx].scratch[CDP_last_seasonal_deviation].u_val;
+    } else
+        if (isnan
+            (rrd->cdp_prep[cdp_idx].scratch[CDP_last_seasonal_deviation].
+             u_val)) {
+        /* initialization */
+#ifdef DEBUG
+        fprintf(stderr, "Initialization of seasonal deviation\n");
+#endif
+        rrd->cdp_prep[cdp_idx].scratch[CDP_scratch_idx].u_val =
+                functions->init_seasonal_deviation(prediction,
+                 rrd->cdp_prep[cdp_idx].scratch[CDP_scratch_idx].u_val);
+    } else {
+        /* exponential smoothing update */
+        rrd->cdp_prep[cdp_idx].scratch[CDP_scratch_idx].u_val =
+                functions->seasonal_deviation(
+                                rrd->rra_def[rra_idx].par[RRA_seasonal_gamma].u_val,
+                                prediction,
+                                rrd->cdp_prep[cdp_idx].scratch[CDP_scratch_idx].u_val,
+                                rrd->cdp_prep[cdp_idx].scratch[CDP_last_seasonal_deviation].u_val);
+    }
+    return 0;
+}
+
+/* Check for a failure based on a threshold # of violations within the specified
+ * window. */
+int update_failures(
+    rrd_t *rrd,
+    unsigned long cdp_idx,
+    unsigned long rra_idx,
+    unsigned long ds_idx,
+    unsigned short CDP_scratch_idx,
+    hw_functions_t *functions)
+{
+    /* detection of a violation depends on 3 RRAs:
+     * HWPREDICT, SEASONAL, and DEVSEASONAL */
+    rra_def_t *current_rra = &(rrd->rra_def[rra_idx]);
+    unsigned long dev_rra_idx = current_rra->par[RRA_dependent_rra_idx].u_cnt;
+    rra_def_t *dev_rra = &(rrd->rra_def[dev_rra_idx]);
+    unsigned long hw_rra_idx = dev_rra->par[RRA_dependent_rra_idx].u_cnt;
+    rra_def_t *hw_rra = &(rrd->rra_def[hw_rra_idx]);
+    unsigned long seasonal_rra_idx = hw_rra->par[RRA_dependent_rra_idx].u_cnt;
+    unsigned long temp_cdp_idx;
+    rrd_value_t deviation = DNAN;
+    rrd_value_t seasonal_coef = DNAN;
+    rrd_value_t prediction = DNAN;
+    char      violation = 0;
+    unsigned short violation_cnt = 0, i;
+    char     *violations_array;
+
+    /* usual checks to determine the order of the RRAs */
+    temp_cdp_idx = dev_rra_idx * (rrd->stat_head->ds_cnt) + ds_idx;
+    if (rra_idx < seasonal_rra_idx) {
+        /* DEVSEASONAL not yet updated */
+        deviation = rrd->cdp_prep[temp_cdp_idx].scratch[CDP_seasonal_deviation].u_val;
+    } else {
+        /* DEVSEASONAL already updated */
+        deviation = rrd->cdp_prep[temp_cdp_idx].scratch[CDP_last_seasonal_deviation].u_val;
+    }
+    if (!isnan(deviation)) {
+
+        temp_cdp_idx = seasonal_rra_idx * (rrd->stat_head->ds_cnt) + ds_idx;
+        if (rra_idx < seasonal_rra_idx) {
+            /* SEASONAL not yet updated */
+            seasonal_coef = rrd->cdp_prep[temp_cdp_idx].scratch[CDP_hw_seasonal].u_val;
+        } else {
+            /* SEASONAL already updated */
+            seasonal_coef = rrd->cdp_prep[temp_cdp_idx].scratch[CDP_hw_last_seasonal].u_val;
+        }
+        /* in this code block, we know seasonal coef is not DNAN, because deviation is not
+         * null */
+
+        temp_cdp_idx = hw_rra_idx * (rrd->stat_head->ds_cnt) + ds_idx;
+        if (rra_idx < hw_rra_idx) {
+            /* HWPREDICT not yet updated */
+            prediction = functions->predict(
+                            rrd->cdp_prep[temp_cdp_idx].scratch[CDP_hw_intercept].u_val,
+                            rrd->cdp_prep[temp_cdp_idx].scratch[CDP_hw_slope].u_val,
+                            rrd->cdp_prep[temp_cdp_idx].scratch[CDP_null_count].u_cnt,
+                            seasonal_coef);
+        } else {
+            /* HWPREDICT already updated */
+            prediction = functions->predict(
+                            rrd->cdp_prep[temp_cdp_idx].scratch[CDP_hw_last_intercept].u_val,
+                            rrd->cdp_prep[temp_cdp_idx].scratch[CDP_hw_last_slope].u_val,
+                            rrd->cdp_prep[temp_cdp_idx].scratch[CDP_last_null_count].u_cnt,
+                            seasonal_coef);
+        }
+
+        /* determine if the observed value is a violation */
+        if (!isnan(rrd->cdp_prep[cdp_idx].scratch[CDP_scratch_idx].u_val)) {
+            if (hw_is_violation(
+                rrd->cdp_prep[cdp_idx].scratch[CDP_scratch_idx].u_val,
+                prediction, deviation, 
+                current_rra->par[RRA_delta_pos].u_val,
+                current_rra->par[RRA_delta_neg].u_val))
+            {
+                violation = 1;
+            }
+        } else {
+            violation = 1;  /* count DNAN values as violations */
+        }
+
+    }
+
+    /* determine if a failure has occurred and update the failure array */
+    violation_cnt = violation;
+    violations_array = (char *) ((void *) rrd->cdp_prep[cdp_idx].scratch);
+    for (i = current_rra->par[RRA_window_len].u_cnt; i > 1; i--) {
+        /* shift */
+        violations_array[i - 1] = violations_array[i - 2];
+        violation_cnt += violations_array[i - 1];
+    }
+    violations_array[0] = violation;
+
+    if (violation_cnt < current_rra->par[RRA_failure_threshold].u_cnt)
+        /* not a failure */
+        rrd->cdp_prep[cdp_idx].scratch[CDP_scratch_idx].u_val = 0.0;
+    else
+        rrd->cdp_prep[cdp_idx].scratch[CDP_scratch_idx].u_val = 1.0;
+
+    return (rrd->cdp_prep[cdp_idx].scratch[CDP_scratch_idx].u_val);
+}
Index: src/rrd_update.c
===================================================================
--- src/rrd_update.c	(revision 1121)
+++ src/rrd_update.c	(working copy)
@@ -1198,6 +1198,7 @@
                                     u_val = seasonal_coef[ii];
                                 break;
                             case CF_HWPREDICT:
+                            case CF_MHWPREDICT:
                                 /* need to update the null_count and last_null_count.
                                  * even do this for non-DNAN pdp_temp because the
                                  * algorithm is not learning from batch updates. */
Index: src/rrd_hw_update.h
===================================================================
--- src/rrd_hw_update.h	(revision 0)
+++ src/rrd_hw_update.h	(revision 0)
@@ -0,0 +1,45 @@
+/*****************************************************************************
+ * rrd_hw_update.h  Functions for updating a Holt-Winters RRA
+ ****************************************************************************/
+
+int update_hwpredict(
+    rrd_t *rrd,
+    unsigned long cdp_idx,
+    unsigned long rra_idx,
+    unsigned long ds_idx,
+    unsigned short CDP_scratch_idx,
+    hw_functions_t *functions
+    );
+
+int update_seasonal(
+    rrd_t *rrd,
+    unsigned long cdp_idx,
+    unsigned long rra_idx,
+    unsigned long ds_idx,
+    unsigned short CDP_scratch_idx,
+    rrd_value_t *seasonal_coef,
+    hw_functions_t *functions);
+
+int update_devpredict(
+    rrd_t *rrd,
+    unsigned long cdp_idx,
+    unsigned long rra_idx,
+    unsigned long ds_idx,
+    unsigned short CDP_scratch_idx);
+
+int update_devseasonal(
+    rrd_t *rrd,
+    unsigned long cdp_idx,
+    unsigned long rra_idx,
+    unsigned long ds_idx,
+    unsigned short CDP_scratch_idx,
+    rrd_value_t *seasonal_dev,
+    hw_functions_t *functions);
+
+int update_failures(
+    rrd_t *rrd,
+    unsigned long cdp_idx,
+    unsigned long rra_idx,
+    unsigned long ds_idx,
+    unsigned short CDP_scratch_idx,
+    hw_functions_t *functions);
Index: src/rrd_format.c
===================================================================
--- src/rrd_format.c	(revision 1121)
+++ src/rrd_format.c	(working copy)
@@ -75,6 +75,7 @@
         converter(MAX, CF_MAXIMUM)
         converter(LAST, CF_LAST)
         converter(HWPREDICT, CF_HWPREDICT)
+        converter(MHWPREDICT, CF_MHWPREDICT)
         converter(DEVPREDICT, CF_DEVPREDICT)
         converter(SEASONAL, CF_SEASONAL)
         converter(DEVSEASONAL, CF_DEVSEASONAL)
Index: src/rrd_format.h
===================================================================
--- src/rrd_format.h	(revision 1121)
+++ src/rrd_format.h	(working copy)
@@ -165,6 +165,7 @@
     CF_MAXIMUM,
     CF_LAST,
     CF_HWPREDICT,
+    CF_MHWPREDICT,
     /* An array of predictions using the seasonal 
      * Holt-Winters algorithm. Requires an RRA of type
      * CF_SEASONAL for this data source. */
Index: src/rrd_graph.c
===================================================================
--- src/rrd_graph.c	(revision 1121)
+++ src/rrd_graph.c	(working copy)
@@ -717,6 +717,7 @@
                 else {
                     switch (cf) {
                     case CF_HWPREDICT:
+                    case CF_MHWPREDICT:
                     case CF_DEVSEASONAL:
                     case CF_DEVPREDICT:
                     case CF_SEASONAL:
@@ -742,6 +743,7 @@
             } else {
                 switch (cf) {
                 case CF_HWPREDICT:
+                case CF_MHWPREDICT:
                 case CF_DEVSEASONAL:
                 case CF_DEVPREDICT:
                 case CF_SEASONAL:
@@ -1438,6 +1440,7 @@
 
                     switch (im->gdes[i].cf) {
                     case CF_HWPREDICT:
+                    case CF_MHWPREDICT:
                     case CF_DEVPREDICT:
                     case CF_DEVSEASONAL:
                     case CF_SEASONAL:
Index: src/rrd_create.c
===================================================================
--- src/rrd_create.c	(revision 1121)
+++ src/rrd_create.c	(working copy)
@@ -247,14 +247,12 @@
                     switch (cf_conv
                             (rrd.rra_def[rrd.stat_head->rra_cnt].cf_nam)) {
                     case CF_HWPREDICT:
+                    case CF_MHWPREDICT:
                         /* initialize some parameters */
-                        rrd.rra_def[rrd.stat_head->rra_cnt].par[RRA_hw_alpha].
-                            u_val = 0.1;
-                        rrd.rra_def[rrd.stat_head->rra_cnt].par[RRA_hw_beta].
-                            u_val = 1.0 / 288;
-                        rrd.rra_def[rrd.stat_head->rra_cnt].
-                            par[RRA_dependent_rra_idx].u_cnt =
-                            rrd.stat_head->rra_cnt;
+                        rrd.rra_def[rrd.stat_head->rra_cnt].par[RRA_hw_alpha].u_val = 0.1;
+                        rrd.rra_def[rrd.stat_head->rra_cnt].par[RRA_hw_beta].u_val = 1.0 / 288;
+                        rrd.rra_def[rrd.stat_head->rra_cnt].par[RRA_dependent_rra_idx].u_cnt 
+                                = rrd.stat_head->rra_cnt;
                         break;
                     case CF_DEVSEASONAL:
                     case CF_SEASONAL:
@@ -293,6 +291,7 @@
                     switch (cf_conv
                             (rrd.rra_def[rrd.stat_head->rra_cnt].cf_nam)) {
                     case CF_HWPREDICT:
+                    case CF_MHWPREDICT:
                     case CF_DEVSEASONAL:
                     case CF_SEASONAL:
                     case CF_DEVPREDICT:
@@ -316,6 +315,7 @@
                     switch (cf_conv
                             (rrd.rra_def[rrd.stat_head->rra_cnt].cf_nam)) {
                     case CF_HWPREDICT:
+                    case CF_MHWPREDICT:
                         rrd.rra_def[rrd.stat_head->rra_cnt].par[RRA_hw_alpha].
                             u_val = atof(token);
                         if (atof(token) <= 0.0 || atof(token) >= 1.0)
@@ -361,6 +361,7 @@
                     switch (cf_conv
                             (rrd.rra_def[rrd.stat_head->rra_cnt].cf_nam)) {
                     case CF_HWPREDICT:
+                    case CF_MHWPREDICT:
                         rrd.rra_def[rrd.stat_head->rra_cnt].par[RRA_hw_beta].
                             u_val = atof(token);
                         if (atof(token) < 0.0 || atof(token) > 1.0)
@@ -415,6 +416,7 @@
                             atoi(token) - 1;
                         break;
                     case CF_HWPREDICT:
+                    case CF_MHWPREDICT:
                         /* length of the associated CF_SEASONAL and CF_DEVSEASONAL arrays. */
                         period = atoi(token);
                         if (period >
@@ -463,10 +465,11 @@
                     par[RRA_dependent_rra_idx].u_cnt, rrd.stat_head->rra_cnt);
 #endif
             /* should we create CF_SEASONAL, CF_DEVSEASONAL, and CF_DEVPREDICT? */
-            if (cf_conv(rrd.rra_def[rrd.stat_head->rra_cnt].cf_nam) ==
-                CF_HWPREDICT
-                && rrd.rra_def[rrd.stat_head->rra_cnt].
-                par[RRA_dependent_rra_idx].u_cnt == rrd.stat_head->rra_cnt) {
+            if ((cf_conv(rrd.rra_def[rrd.stat_head->rra_cnt].cf_nam) == CF_HWPREDICT
+                || cf_conv(rrd.rra_def[rrd.stat_head->rra_cnt].cf_nam) == CF_MHWPREDICT)
+                    && rrd.rra_def[rrd.stat_head->rra_cnt].
+                    par[RRA_dependent_rra_idx].u_cnt == rrd.stat_head->rra_cnt) 
+            {
 #ifdef DEBUG
                 fprintf(stderr, "Creating HW contingent RRAs\n");
 #endif
@@ -671,6 +674,7 @@
     for (i = 0; i < rrd->stat_head->rra_cnt; i++) {
         switch (cf_conv(rrd->rra_def[i].cf_nam)) {
         case CF_HWPREDICT:
+        case CF_MHWPREDICT:
             init_hwpredict_cdp(rrd->cdp_prep);
             break;
         case CF_SEASONAL:
Index: src/rrd_tune.c
===================================================================
--- src/rrd_tune.c	(revision 1121)
+++ src/rrd_tune.c	(working copy)
@@ -237,14 +237,20 @@
             break;
         case 'x':
             if (set_hwarg(&rrd, CF_HWPREDICT, RRA_hw_alpha, optarg)) {
-                rrd_free(&rrd);
-                return -1;
+                if (set_hwarg(&rrd, CF_MHWPREDICT, RRA_hw_alpha, optarg)) {
+                    rrd_free(&rrd);
+                    return -1;
+                }
+                rrd_clear_error();
             }
             break;
         case 'y':
             if (set_hwarg(&rrd, CF_HWPREDICT, RRA_hw_beta, optarg)) {
-                rrd_free(&rrd);
-                return -1;
+                if (set_hwarg(&rrd, CF_MHWPREDICT, RRA_hw_beta, optarg)) {
+                    rrd_free(&rrd);
+                    return -1;
+                }
+                rrd_clear_error();
             }
             break;
         case 'z':
Index: src/Makefile.am
===================================================================
--- src/Makefile.am	(revision 1121)
+++ src/Makefile.am	(working copy)
@@ -19,6 +19,8 @@
 	rrd_getopt1.c	\
 	parsetime.c	\
 	rrd_hw.c	\
+	rrd_hw_math.c	\
+	rrd_hw_update.c	\
 	rrd_diff.c	\
 	rrd_format.c	\
 	rrd_info.c	\
@@ -50,7 +52,8 @@
 noinst_HEADERS = \
 	unused.h \
 	rrd_getopt.h parsetime.h \
-	rrd_format.h rrd_tool.h rrd_xport.h rrd.h rrd_hw.h rrd_rpncalc.h \
+	rrd_format.h rrd_tool.h rrd_xport.h rrd.h rrd_rpncalc.h \
+	rrd_hw.h rrd_hw_math.h rrd_hw_update.h \
 	rrd_nan_inf.h fnv.h rrd_graph.h \
 	rrd_is_thread_safe.h
 
Index: src/rrd_restore.c
===================================================================
--- src/rrd_restore.c	(revision 1121)
+++ src/rrd_restore.c	(working copy)
@@ -366,6 +366,7 @@
             } else {
                 switch (cf_conv(rrd->rra_def[rra_index].cf_nam)) {
                 case CF_HWPREDICT:
+                case CF_MHWPREDICT:
                     read_tag(&ptr2, "hw_alpha", "%lf",
                              &(rrd->rra_def[rra_index].par[RRA_hw_alpha].
                                u_val));
@@ -456,6 +457,7 @@
                                         i].scratch[CDP_secondary_val].u_val));
                     switch (cf_conv(rrd->rra_def[rra_index].cf_nam)) {
                     case CF_HWPREDICT:
+                    case CF_MHWPREDICT:
                         read_tag(&ptr2, "intercept", "%lf",
                                  &(rrd->
                                    cdp_prep[rrd->stat_head->ds_cnt *
Index: doc/rrdcreate.pod
===================================================================
--- doc/rrdcreate.pod	(revision 1121)
+++ doc/rrdcreate.pod	(working copy)
@@ -206,6 +206,10 @@
 
 =item *
 
+B<RRA:>I<MHWPREDICT>B<:>I<rows>B<:>I<alpha>B<:>I<beta>B<:>I<seasonal period>[B<:>I<rra-num>]
+
+=item *
+
 B<RRA:>I<SEASONAL>B<:>I<seasonal period>B<:>I<gamma>B<:>I<rra-num>
 
 =item *
@@ -225,20 +229,33 @@
 These B<RRAs> differ from the true consolidation functions in several ways.
 First, each of the B<RRA>s is updated once for every primary data point.
 Second, these B<RRAs> are interdependent. To generate real-time confidence
-bounds, a matched set of HWPREDICT, SEASONAL, DEVSEASONAL, and
-DEVPREDICT must exist. Generating smoothed values of the primary data points
-requires both a HWPREDICT B<RRA> and SEASONAL B<RRA>. Aberrant behavior
-detection requires FAILURES, HWPREDICT, DEVSEASONAL, and SEASONAL.
+bounds, a matched set of SEASONAL, DEVSEASONAL, DEVPREDICT, and either
+HWPREDICT or MHWPREDICT must exist. Generating smoothed values of the primary
+data points requires a SEASONAL B<RRA> and either an HWPREDICT or MHWPREDICT 
+B<RRA>. Aberrant behavior detection requires FAILURES, DEVSEASONAL, SEASONAL,
+and either HWPREDICT or MHWPREDICT.
 
-The actual predicted, or smoothed, values are stored in the HWPREDICT
-B<RRA>. The predicted deviations are stored in DEVPREDICT (think a standard
-deviation which can be scaled to yield a confidence band). The FAILURES
-B<RRA> stores binary indicators. A 1 marks the indexed observation as
-failure; that is, the number of confidence bounds violations in the
-preceding window of observations met or exceeded a specified threshold. An
-example of using these B<RRAs> to graph confidence bounds and failures
-appears in L<rrdgraph>.
+The predicted, or smoothed, values are stored in the HWPREDICT or MHWPREDICT
+B<RRA>. HWPREDICT and MHWPREDICT are actually two variations on the
+Holt-Winters method. They are interchangeable. Both attempt to decompose data
+into three components: a baseline, a trend, and a seasonal coefficient.
+HWPREDICT adds its seasonal coefficient to the baseline to form a prediction, whereas
+MHWPREDICT multiplies its seasonal coefficient by the baseline to form a
+prediction. The difference is noticeable when the baseline changes
+significantly in the course of a season; HWPREDICT will predict the seasonality
+to stay constant as the baseline changes, but MHWPREDICT will predict the
+seasonality to grow or shrink in proportion to the baseline. The proper choice
+of method depends on the thing being modeled. For simplicity, the rest of this
+discussion will refer to HWPREDICT, but MHWPREDICT may be substituted in its
+place.
 
+The predicted deviations are stored in DEVPREDICT (think a standard deviation
+which can be scaled to yield a confidence band). The FAILURES B<RRA> stores 
+binary indicators. A 1 marks the indexed observation as failure; that is, the 
+number of confidence bounds violations in the preceding window of observations 
+met or exceeded a specified threshold. An example of using these B<RRAs> to graph 
+confidence bounds and failures appears in L<rrdgraph>.
+
 The SEASONAL and DEVSEASONAL B<RRAs> store the seasonal coefficients for the
 Holt-Winters forecasting algorithm and the seasonal deviations, respectively.
 There is one entry per observation time point in the seasonal cycle. For
Index: doc/rrdtune.pod
===================================================================
--- doc/rrdtune.pod	(revision 1121)
+++ doc/rrdtune.pod	(working copy)
@@ -121,14 +121,15 @@
 
 This option causes the aberrant behavior detection algorithm to reset
 for the specified data source; that is, forget all it is has learnt so far.
-Specifically, for the HWPREDICT B<RRA>, it sets the intercept and slope
-coefficients to unknown. For the SEASONAL B<RRA>, it sets all seasonal
+Specifically, for the HWPREDICT or MHWPREDICT B<RRA>, it sets the intercept and
+slope coefficients to unknown. For the SEASONAL B<RRA>, it sets all seasonal
 coefficients to unknown. For the DEVSEASONAL B<RRA>, it sets all seasonal
-deviation coefficients to unknown. For the FAILURES B<RRA>, it erases
-the violation history. Note that reset does not erase past predictions
-(the values of the HWPREDICT B<RRA>), predicted deviations (the values of the
-DEVPREDICT B<RRA>), or failure history (the values of the FAILURES B<RRA>).
-This option will function even if not all the listed B<RRAs> are present.
+deviation coefficients to unknown. For the FAILURES B<RRA>, it erases the
+violation history. Note that reset does not erase past predictions
+(the values of the HWPREDICT or MHWPREDICT B<RRA>), predicted deviations (the
+values of the DEVPREDICT B<RRA>), or failure history (the values of the 
+FAILURES B<RRA>).  This option will function even if not all the listed 
+B<RRAs> are present.
 
 Due to the implementation of this option, there is an indirect impact on
 other data sources in the RRD. A smoothing algorithm is applied to


More information about the rrd-developers mailing list