Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 15 additions & 2 deletions src/forecast/forecast.h
Original file line number Diff line number Diff line change
Expand Up @@ -2621,9 +2621,11 @@ class ForecastSolver : public Solver {
}
Forecast_SmapeAlfa = t;

// Initialize the smape weight array
// Initialize the smape weight array.
// Must fill the full array (matching ForecastSolver::initialize), otherwise
// entries above the old hardcoded bound stay stale when this setter runs.
weight[0] = 1.0;
for (int i = 0; i < 299; ++i)
for (int i = 0; i < MAXBUCKETS - 1; ++i)
weight[i + 1] = weight[i] * Forecast_SmapeAlfa;
}

Expand Down Expand Up @@ -3041,6 +3043,17 @@ class ForecastSolver : public Solver {
/* An array with weights for history buckets. */
static double weight[MAXBUCKETS];

/* Bounds-safe accessor for the smape weight array.
* The number of history buckets can exceed MAXBUCKETS (e.g. weekly or daily
* buckets over the default 10-year horizon). Because the weights decay
* exponentially, weight[>= MAXBUCKETS] is numerically ~0, so clamping the
* index is behavior-preserving and avoids an out-of-bounds read. */
static inline double smapeWeight(long idx) {
if (idx < 0) idx = 0;
if (idx >= MAXBUCKETS) idx = MAXBUCKETS - 1;
return weight[idx];
}

/* Number of warmup periods.
* These periods are used for the initialization of the algorithm
* and don't count towards measuring the forecast error.
Expand Down
50 changes: 25 additions & 25 deletions src/forecast/timeseries.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -352,8 +352,8 @@ ForecastSolver::Metrics ForecastSolver::MovingAverage::generateForecast(
if (i >= solver->getForecastSkip() && i < count &&
fabs(avg + actual) > ROUNDING_ERROR) {
error_smape +=
fabs(avg - actual) / fabs(avg + actual) * weight[count - i];
error_smape_weights += weight[count - i];
fabs(avg - actual) / fabs(avg + actual) * smapeWeight(count - i);
error_smape_weights += smapeWeight(count - i);
}
}

Expand Down Expand Up @@ -513,14 +513,14 @@ ForecastSolver::Metrics ForecastSolver::SingleExponential::generateForecast(
true);
}
}
sum_12 += df_dalfa_i * (history_i - f_i) * weight[count - i];
sum_11 += df_dalfa_i * df_dalfa_i * weight[count - i];
sum_12 += df_dalfa_i * (history_i - f_i) * smapeWeight(count - i);
sum_11 += df_dalfa_i * df_dalfa_i * smapeWeight(count - i);
if (i >= solver->getForecastSkip()) {
error += (f_i - history_i) * (f_i - history_i) * weight[count - i];
error += (f_i - history_i) * (f_i - history_i) * smapeWeight(count - i);
if (fabs(f_i + history_i) > ROUNDING_ERROR) {
error_smape +=
fabs(f_i - history_i) / (f_i + history_i) * weight[count - i];
error_smape_weights += weight[count - i];
fabs(f_i - history_i) / (f_i + history_i) * smapeWeight(count - i);
error_smape_weights += smapeWeight(count - i);
}
}
}
Expand Down Expand Up @@ -778,24 +778,24 @@ ForecastSolver::Metrics ForecastSolver::DoubleExponential::generateForecast(
(1 - gamma) * d_trend_d_gamma;
d_forecast_d_alfa = d_constant_d_alfa + d_trend_d_alfa;
d_forecast_d_gamma = d_constant_d_gamma + d_trend_d_gamma;
sum11 += weight[count - i] * d_forecast_d_alfa * d_forecast_d_alfa;
sum12 += weight[count - i] * d_forecast_d_alfa * d_forecast_d_gamma;
sum22 += weight[count - i] * d_forecast_d_gamma * d_forecast_d_gamma;
sum13 += weight[count - i] * d_forecast_d_alfa *
sum11 += smapeWeight(count - i) * d_forecast_d_alfa * d_forecast_d_alfa;
sum12 += smapeWeight(count - i) * d_forecast_d_alfa * d_forecast_d_gamma;
sum22 += smapeWeight(count - i) * d_forecast_d_gamma * d_forecast_d_gamma;
sum13 += smapeWeight(count - i) * d_forecast_d_alfa *
(history_i - constant_i - trend_i);
sum23 += weight[count - i] * d_forecast_d_gamma *
sum23 += smapeWeight(count - i) * d_forecast_d_gamma *
(history_i - constant_i - trend_i);
if (i >=
solver
->getForecastSkip()) // Don't measure during the warmup period
{
error += (constant_i + trend_i - history_i) *
(constant_i + trend_i - history_i) * weight[count - i];
(constant_i + trend_i - history_i) * smapeWeight(count - i);
if (fabs(constant_i + trend_i + history_i) > ROUNDING_ERROR) {
error_smape += fabs(constant_i + trend_i - history_i) /
fabs(constant_i + trend_i + history_i) *
weight[count - i];
error_smape_weights += weight[count - i];
smapeWeight(count - i);
error_smape_weights += smapeWeight(count - i);
}
}
}
Expand Down Expand Up @@ -1139,20 +1139,20 @@ ForecastSolver::Metrics
d_forecast_d_beta = (d_L_d_beta + d_T_d_beta) * S_i[cycleindex] +
(L_i + T_i) * d_S_d_beta[cycleindex];
forecast_i = (L_i + T_i) * S_i[cycleindex];
sum11 += weight[count - i] * d_forecast_d_alfa * d_forecast_d_alfa;
sum12 += weight[count - i] * d_forecast_d_alfa * d_forecast_d_beta;
sum22 += weight[count - i] * d_forecast_d_beta * d_forecast_d_beta;
sum13 += weight[count - i] * d_forecast_d_alfa * (actual - forecast_i);
sum23 += weight[count - i] * d_forecast_d_beta * (actual - forecast_i);
sum11 += smapeWeight(count - i) * d_forecast_d_alfa * d_forecast_d_alfa;
sum12 += smapeWeight(count - i) * d_forecast_d_alfa * d_forecast_d_beta;
sum22 += smapeWeight(count - i) * d_forecast_d_beta * d_forecast_d_beta;
sum13 += smapeWeight(count - i) * d_forecast_d_alfa * (actual - forecast_i);
sum23 += smapeWeight(count - i) * d_forecast_d_beta * (actual - forecast_i);
if (i >=
solver->getForecastSkip()) // Don't measure during the warmup period
{
double fcst = (L_i + T_i) * S_i[cycleindex];
error += (fcst - actual) * (fcst - actual) * weight[count - i];
error += (fcst - actual) * (fcst - actual) * smapeWeight(count - i);
if (fabs(fcst + actual) > ROUNDING_ERROR) {
error_smape +=
fabs(fcst - actual) / fabs(fcst + actual) * weight[count - i];
error_smape_weights += weight[count - i];
fabs(fcst - actual) / fabs(fcst + actual) * smapeWeight(count - i);
error_smape_weights += smapeWeight(count - i);
standarddeviation += (fcst - actual) * (fcst - actual);
}
}
Expand Down Expand Up @@ -1402,8 +1402,8 @@ ForecastSolver::Metrics ForecastSolver::Croston::generateForecast(
{
if (fabs(f_i + history_i) > ROUNDING_ERROR) {
error_smape += fabs(f_i - history_i) / fabs(f_i + history_i) *
weight[count - i];
error_smape_weights += weight[count - i];
smapeWeight(count - i);
error_smape_weights += smapeWeight(count - i);
}
}
}
Expand Down
Loading
Loading