413 lines
9.9 KiB
Python
413 lines
9.9 KiB
Python
import numpy as np
|
|
|
|
LOWER_BOUND = np.sqrt(np.finfo(float).eps)
|
|
|
|
|
|
class HoltWintersArgs:
|
|
def __init__(self, xi, p, bounds, y, m, n, transform=False):
|
|
self._xi = xi
|
|
self._p = p
|
|
self._bounds = bounds
|
|
self._y = y
|
|
self._lvl = np.empty(n)
|
|
self._b = np.empty(n)
|
|
self._s = np.empty(n + m - 1)
|
|
self._m = m
|
|
self._n = n
|
|
self._transform = transform
|
|
|
|
@property
|
|
def xi(self):
|
|
return self._xi
|
|
|
|
@xi.setter
|
|
def xi(self, value):
|
|
self._xi = value
|
|
|
|
@property
|
|
def p(self):
|
|
return self._p
|
|
|
|
@property
|
|
def bounds(self):
|
|
return self._bounds
|
|
|
|
@property
|
|
def y(self):
|
|
return self._y
|
|
|
|
@property
|
|
def lvl(self):
|
|
return self._lvl
|
|
|
|
@property
|
|
def b(self):
|
|
return self._b
|
|
|
|
@property
|
|
def s(self):
|
|
return self._s
|
|
|
|
@property
|
|
def m(self):
|
|
return self._m
|
|
|
|
@property
|
|
def n(self):
|
|
return self._n
|
|
|
|
@property
|
|
def transform(self):
|
|
return self._transform
|
|
|
|
@transform.setter
|
|
def transform(self, value):
|
|
self._transform = value
|
|
|
|
|
|
def to_restricted(p, sel, bounds):
|
|
"""
|
|
Transform parameters from the unrestricted [0,1] space
|
|
to satisfy both the bounds and the 2 constraints
|
|
beta <= alpha and gamma <= (1-alpha)
|
|
|
|
Parameters
|
|
----------
|
|
p : ndarray
|
|
The parameters to transform
|
|
sel : ndarray
|
|
Array indicating whether a parameter is being estimated. If not
|
|
estimated, not transformed.
|
|
bounds : ndarray
|
|
2-d array of bounds where bound for element i is in row i
|
|
and stored as [lb, ub]
|
|
|
|
Returns
|
|
-------
|
|
|
|
"""
|
|
a, b, g = p[:3]
|
|
|
|
if sel[0]:
|
|
lb = max(LOWER_BOUND, bounds[0, 0])
|
|
ub = min(1 - LOWER_BOUND, bounds[0, 1])
|
|
a = lb + a * (ub - lb)
|
|
if sel[1]:
|
|
lb = bounds[1, 0]
|
|
ub = min(a, bounds[1, 1])
|
|
b = lb + b * (ub - lb)
|
|
if sel[2]:
|
|
lb = bounds[2, 0]
|
|
ub = min(1.0 - a, bounds[2, 1])
|
|
g = lb + g * (ub - lb)
|
|
|
|
return a, b, g
|
|
|
|
|
|
def to_unrestricted(p, sel, bounds):
|
|
"""
|
|
Transform parameters to the unrestricted [0,1] space
|
|
|
|
Parameters
|
|
----------
|
|
p : ndarray
|
|
Parameters that strictly satisfy the constraints
|
|
|
|
Returns
|
|
-------
|
|
ndarray
|
|
Parameters all in (0,1)
|
|
"""
|
|
# eps < a < 1 - eps
|
|
# eps < b <= a
|
|
# eps < g <= 1 - a
|
|
|
|
a, b, g = p[:3]
|
|
|
|
if sel[0]:
|
|
lb = max(LOWER_BOUND, bounds[0, 0])
|
|
ub = min(1 - LOWER_BOUND, bounds[0, 1])
|
|
a = (a - lb) / (ub - lb)
|
|
if sel[1]:
|
|
lb = bounds[1, 0]
|
|
ub = min(p[0], bounds[1, 1])
|
|
b = (b - lb) / (ub - lb)
|
|
if sel[2]:
|
|
lb = bounds[2, 0]
|
|
ub = min(1.0 - p[0], bounds[2, 1])
|
|
g = (g - lb) / (ub - lb)
|
|
|
|
return a, b, g
|
|
|
|
|
|
def holt_init(x, hw_args: HoltWintersArgs):
|
|
"""
|
|
Initialization for the Holt Models
|
|
"""
|
|
# Map back to the full set of parameters
|
|
hw_args.p[hw_args.xi.astype(bool)] = x
|
|
|
|
# Ensure alpha and beta satisfy the requirements
|
|
if hw_args.transform:
|
|
alpha, beta, _ = to_restricted(hw_args.p, hw_args.xi, hw_args.bounds)
|
|
else:
|
|
alpha, beta = hw_args.p[:2]
|
|
# Level, trend and dampening
|
|
l0, b0, phi = hw_args.p[3:6]
|
|
# Save repeated calculations
|
|
alphac = 1 - alpha
|
|
betac = 1 - beta
|
|
# Setup alpha * y
|
|
y_alpha = alpha * hw_args.y
|
|
# In-place operations
|
|
hw_args.lvl[0] = l0
|
|
hw_args.b[0] = b0
|
|
|
|
return alpha, beta, phi, alphac, betac, y_alpha
|
|
|
|
|
|
def holt__(x, hw_args: HoltWintersArgs):
|
|
"""
|
|
Simple Exponential Smoothing
|
|
Minimization Function
|
|
(,)
|
|
"""
|
|
_, _, _, alphac, _, y_alpha = holt_init(x, hw_args)
|
|
n = hw_args.n
|
|
lvl = hw_args.lvl
|
|
for i in range(1, n):
|
|
lvl[i] = (y_alpha[i - 1]) + (alphac * (lvl[i - 1]))
|
|
return hw_args.y - lvl
|
|
|
|
|
|
def holt_mul_dam(x, hw_args: HoltWintersArgs):
|
|
"""
|
|
Multiplicative and Multiplicative Damped
|
|
Minimization Function
|
|
(M,) & (Md,)
|
|
"""
|
|
_, beta, phi, alphac, betac, y_alpha = holt_init(x, hw_args)
|
|
lvl = hw_args.lvl
|
|
b = hw_args.b
|
|
for i in range(1, hw_args.n):
|
|
lvl[i] = (y_alpha[i - 1]) + (alphac * (lvl[i - 1] * b[i - 1] ** phi))
|
|
b[i] = (beta * (lvl[i] / lvl[i - 1])) + (betac * b[i - 1] ** phi)
|
|
return hw_args.y - lvl * b**phi
|
|
|
|
|
|
def holt_add_dam(x, hw_args: HoltWintersArgs):
|
|
"""
|
|
Additive and Additive Damped
|
|
Minimization Function
|
|
(A,) & (Ad,)
|
|
"""
|
|
_, beta, phi, alphac, betac, y_alpha = holt_init(x, hw_args)
|
|
lvl = hw_args.lvl
|
|
b = hw_args.b
|
|
for i in range(1, hw_args.n):
|
|
lvl[i] = (y_alpha[i - 1]) + (alphac * (lvl[i - 1] + phi * b[i - 1]))
|
|
b[i] = (beta * (lvl[i] - lvl[i - 1])) + (betac * phi * b[i - 1])
|
|
return hw_args.y - (lvl + phi * b)
|
|
|
|
|
|
def holt_win_init(x, hw_args: HoltWintersArgs):
|
|
"""Initialization for the Holt Winters Seasonal Models"""
|
|
hw_args.p[hw_args.xi.astype(bool)] = x
|
|
if hw_args.transform:
|
|
alpha, beta, gamma = to_restricted(
|
|
hw_args.p, hw_args.xi, hw_args.bounds
|
|
)
|
|
else:
|
|
alpha, beta, gamma = hw_args.p[:3]
|
|
|
|
l0, b0, phi = hw_args.p[3:6]
|
|
s0 = hw_args.p[6:]
|
|
alphac = 1 - alpha
|
|
betac = 1 - beta
|
|
gammac = 1 - gamma
|
|
y_alpha = alpha * hw_args.y
|
|
y_gamma = gamma * hw_args.y
|
|
hw_args.lvl[:] = 0
|
|
hw_args.b[:] = 0
|
|
hw_args.s[:] = 0
|
|
hw_args.lvl[0] = l0
|
|
hw_args.b[0] = b0
|
|
hw_args.s[: hw_args.m] = s0
|
|
return alpha, beta, gamma, phi, alphac, betac, gammac, y_alpha, y_gamma
|
|
|
|
|
|
def holt_win__mul(x, hw_args: HoltWintersArgs):
|
|
"""
|
|
Multiplicative Seasonal
|
|
Minimization Function
|
|
(,M)
|
|
"""
|
|
(_, _, _, _, alphac, _, gammac, y_alpha, y_gamma) = holt_win_init(
|
|
x, hw_args
|
|
)
|
|
lvl = hw_args.lvl
|
|
s = hw_args.s
|
|
m = hw_args.m
|
|
for i in range(1, hw_args.n):
|
|
lvl[i] = (y_alpha[i - 1] / s[i - 1]) + (alphac * (lvl[i - 1]))
|
|
s[i + m - 1] = (y_gamma[i - 1] / (lvl[i - 1])) + (gammac * s[i - 1])
|
|
return hw_args.y - lvl * s[: -(m - 1)]
|
|
|
|
|
|
def holt_win__add(x, hw_args: HoltWintersArgs):
|
|
"""
|
|
Additive Seasonal
|
|
Minimization Function
|
|
(,A)
|
|
"""
|
|
(alpha, _, gamma, _, alphac, _, gammac, y_alpha, y_gamma) = holt_win_init(
|
|
x, hw_args
|
|
)
|
|
lvl = hw_args.lvl
|
|
s = hw_args.s
|
|
m = hw_args.m
|
|
for i in range(1, hw_args.n):
|
|
lvl[i] = (
|
|
(y_alpha[i - 1]) - (alpha * s[i - 1]) + (alphac * (lvl[i - 1]))
|
|
)
|
|
s[i + m - 1] = (
|
|
y_gamma[i - 1] - (gamma * (lvl[i - 1])) + (gammac * s[i - 1])
|
|
)
|
|
return hw_args.y - lvl - s[: -(m - 1)]
|
|
|
|
|
|
def holt_win_add_mul_dam(x, hw_args: HoltWintersArgs):
|
|
"""
|
|
Additive and Additive Damped with Multiplicative Seasonal
|
|
Minimization Function
|
|
(A,M) & (Ad,M)
|
|
"""
|
|
(
|
|
_,
|
|
beta,
|
|
_,
|
|
phi,
|
|
alphac,
|
|
betac,
|
|
gammac,
|
|
y_alpha,
|
|
y_gamma,
|
|
) = holt_win_init(x, hw_args)
|
|
lvl = hw_args.lvl
|
|
b = hw_args.b
|
|
s = hw_args.s
|
|
m = hw_args.m
|
|
for i in range(1, hw_args.n):
|
|
lvl[i] = (y_alpha[i - 1] / s[i - 1]) + (
|
|
alphac * (lvl[i - 1] + phi * b[i - 1])
|
|
)
|
|
b[i] = (beta * (lvl[i] - lvl[i - 1])) + (betac * phi * b[i - 1])
|
|
s[i + m - 1] = (y_gamma[i - 1] / (lvl[i - 1] + phi * b[i - 1])) + (
|
|
gammac * s[i - 1]
|
|
)
|
|
return hw_args.y - (lvl + phi * b) * s[: -(m - 1)]
|
|
|
|
|
|
def holt_win_mul_mul_dam(x, hw_args: HoltWintersArgs):
|
|
"""
|
|
Multiplicative and Multiplicative Damped with Multiplicative Seasonal
|
|
Minimization Function
|
|
(M,M) & (Md,M)
|
|
"""
|
|
(
|
|
_,
|
|
beta,
|
|
_,
|
|
phi,
|
|
alphac,
|
|
betac,
|
|
gammac,
|
|
y_alpha,
|
|
y_gamma,
|
|
) = holt_win_init(x, hw_args)
|
|
lvl = hw_args.lvl
|
|
s = hw_args.s
|
|
b = hw_args.b
|
|
m = hw_args.m
|
|
for i in range(1, hw_args.n):
|
|
lvl[i] = (y_alpha[i - 1] / s[i - 1]) + (
|
|
alphac * (lvl[i - 1] * b[i - 1] ** phi)
|
|
)
|
|
b[i] = (beta * (lvl[i] / lvl[i - 1])) + (betac * b[i - 1] ** phi)
|
|
s[i + m - 1] = (y_gamma[i - 1] / (lvl[i - 1] * b[i - 1] ** phi)) + (
|
|
gammac * s[i - 1]
|
|
)
|
|
return hw_args.y - (lvl * b**phi) * s[: -(m - 1)]
|
|
|
|
|
|
def holt_win_add_add_dam(x, hw_args: HoltWintersArgs):
|
|
"""
|
|
Additive and Additive Damped with Additive Seasonal
|
|
Minimization Function
|
|
(A,A) & (Ad,A)
|
|
"""
|
|
(
|
|
alpha,
|
|
beta,
|
|
gamma,
|
|
phi,
|
|
alphac,
|
|
betac,
|
|
gammac,
|
|
y_alpha,
|
|
y_gamma,
|
|
) = holt_win_init(x, hw_args)
|
|
lvl = hw_args.lvl
|
|
s = hw_args.s
|
|
b = hw_args.b
|
|
m = hw_args.m
|
|
for i in range(1, hw_args.n):
|
|
lvl[i] = (
|
|
(y_alpha[i - 1])
|
|
- (alpha * s[i - 1])
|
|
+ (alphac * (lvl[i - 1] + phi * b[i - 1]))
|
|
)
|
|
b[i] = (beta * (lvl[i] - lvl[i - 1])) + (betac * phi * b[i - 1])
|
|
s[i + m - 1] = (
|
|
y_gamma[i - 1]
|
|
- (gamma * (lvl[i - 1] + phi * b[i - 1]))
|
|
+ (gammac * s[i - 1])
|
|
)
|
|
return hw_args.y - ((lvl + phi * b) + s[: -(m - 1)])
|
|
|
|
|
|
def holt_win_mul_add_dam(x, hw_args: HoltWintersArgs):
|
|
"""
|
|
Multiplicative and Multiplicative Damped with Additive Seasonal
|
|
Minimization Function
|
|
(M,A) & (M,Ad)
|
|
"""
|
|
(
|
|
alpha,
|
|
beta,
|
|
gamma,
|
|
phi,
|
|
alphac,
|
|
betac,
|
|
gammac,
|
|
y_alpha,
|
|
y_gamma,
|
|
) = holt_win_init(x, hw_args)
|
|
lvl = hw_args.lvl
|
|
s = hw_args.s
|
|
b = hw_args.b
|
|
m = hw_args.m
|
|
for i in range(1, hw_args.n):
|
|
lvl[i] = (
|
|
(y_alpha[i - 1])
|
|
- (alpha * s[i - 1])
|
|
+ (alphac * (lvl[i - 1] * b[i - 1] ** phi))
|
|
)
|
|
b[i] = (beta * (lvl[i] / lvl[i - 1])) + (betac * b[i - 1] ** phi)
|
|
s[i + m - 1] = (
|
|
y_gamma[i - 1]
|
|
- (gamma * (lvl[i - 1] * b[i - 1] ** phi))
|
|
+ (gammac * s[i - 1])
|
|
)
|
|
return hw_args.y - ((lvl * phi * b) + s[: -(m - 1)])
|