2024-10-02 22:15:59 +04:00

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)])