=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 *
 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.
-
-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>.
+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 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.
 
 
 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
 
        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      \
 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
 
 
                     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;
                     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:
                     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)
                     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)
                             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 >
                     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
+            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
     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:
 
         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",
             }
             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;
 
         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)
 
     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. */
 
                 else {
                     switch (cf) {
                     case CF_HWPREDICT:
+                    case CF_MHWPREDICT:
                     case CF_DEVSEASONAL:
                     case CF_DEVPREDICT:
                     case CF_SEASONAL:
             } else {
                 switch (cf) {
                 case CF_HWPREDICT:
+                case CF_MHWPREDICT:
                 case CF_DEVSEASONAL:
                 case CF_DEVPREDICT:
                 case CF_SEASONAL:
 
                     switch (im->gdes[i].cf) {
                     case CF_HWPREDICT:
+                    case CF_MHWPREDICT:
                     case CF_DEVPREDICT:
                     case CF_DEVSEASONAL:
                     case CF_SEASONAL:
 
 
 #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,
     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.
  */
     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];
         }
         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:
     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)
 {
 
--- /dev/null
+/*****************************************************************************
+ * 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);
+}
 
--- /dev/null
+/*****************************************************************************
+ * rrd_hw_math.h  Math functions for Holt-Winters computations
+ *****************************************************************************/
+
+#include "rrd.h"
+#include "rrd_format.h"
+
+/* since /usr/include/bits/mathcalls.h:265 defines gamma already */
+#define gamma hw_gamma
+
+/*****************************************************************************
+ * 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;
+
+
+#undef gamma
 
--- /dev/null
+/*****************************************************************************
+ * 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);
+}
 
--- /dev/null
+/*****************************************************************************
+ * 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);
 
 
         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);
         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;
 
             } 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));
                                         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 *
 
             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':
 
                                     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. */