-
Notifications
You must be signed in to change notification settings - Fork 4
/
_arfima.py
145 lines (126 loc) · 4.9 KB
/
_arfima.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
import numpy as np
from scipy.fft import fft, ifft
from scipy.stats import levy_stable, norm
def __ma_model(
params: list[float],
n_points: int,
*,
noise_std: float = 1,
noise_alpha: float = 2,
) -> list[float]:
"""Generate discrete series using MA process.
Args:
params: list[float]
Coefficients used by the MA process:
x[t] = epsi[t] + params[1]*epsi[t-1] + params[2]*epsi[t-2] + ...
Order of the MA process is inferred from the length of this array.
n_points: int
Number of points to generate.
noise_std: float, optional
Scale of the generated noise (default: 1).
noise_alpha: float, optional
Parameter of the alpha-stable distribution (default: 2). Default
value corresponds to Gaussian distribution.
Returns:
Discrete series (array of length n_points) generated by
MA(len(params)) process
"""
ma_order = len(params)
if noise_alpha == 2:
noise = norm.rvs(scale=noise_std, size=(n_points + ma_order))
else:
noise = levy_stable.rvs(
noise_alpha, 0, scale=noise_std, size=(n_points + ma_order)
)
if ma_order == 0:
return noise
ma_coeffs = np.append([1], params)
ma_series = np.zeros(n_points)
for idx in range(ma_order, n_points + ma_order):
take_idx = np.arange(idx, idx - ma_order - 1, -1).astype(int)
ma_series[idx - ma_order] = np.dot(ma_coeffs, noise[take_idx])
return ma_series[ma_order:]
def __arma_model(params: list[float], noise: list[float]) -> list[float]:
"""Generate discrete series using ARMA process.
Args:
params: list[float]
Coefficients used by the AR process:
x[t] = params[1]*x[t-1] + params[2]*x[t-2] + ... + epsi[t]
Order of the AR process is inferred from the length of this array.
noise: list[float]
Values of the noise for each step. Length of the output array is
automatically inferred from the length of this array. Note that
noise needs not to be standard Gaussian noise (MA(0) process). It
may be also generated by a higher order MA process.
Returns:
Discrete series (array of the same length as noise array) generated by
the ARMA(len(params), ?) process.
"""
ar_order = len(params)
if ar_order == 0:
return noise
n_points = len(noise)
arma_series = np.zeros(n_points + ar_order)
for idx in np.arange(ar_order, len(arma_series)):
take_idx = np.arange(idx - 1, idx - ar_order - 1, -1).astype(int)
arma_series[idx] = np.dot(params, arma_series[take_idx]) + noise[idx - ar_order]
return arma_series[ar_order:]
def __frac_diff(x: list[float], d: float) -> list[float]:
"""Fast fractional difference algorithm (by Jensen & Nielsen (2014)).
Args:
x: list[float]
Array of values to be differentiated.
d: float
Order of the differentiation. Recommend to use -0.5 < d < 0.5, but
should work for almost any reasonable d.
Returns:
Fractionally differentiated series.
"""
def next_pow2(n):
# we assume that the input will always be n > 1,
# so this brief calculation should be fine
return (n - 1).bit_length()
n_points = len(x)
fft_len = 2 ** next_pow2(2 * n_points - 1)
prod_ids = np.arange(1, n_points)
frac_diff_coefs = np.append([1], np.cumprod((prod_ids - d - 1) / prod_ids))
dx = ifft(fft(x, fft_len) * fft(frac_diff_coefs, fft_len))
return np.real(dx[0:n_points])
def arfima(
ar_params: list[float],
d: float,
ma_params: list[float],
n_points: int,
*,
noise_std: float = 1,
noise_alpha: float = 2,
warmup: int = 0,
) -> list[float]:
"""Generate series from ARFIMA process.
Args:
ar_params: list[float]
Coefficients to be used by the AR process.
d: float
Differentiation order used by the ARFIMA process.
ma_params: list[float]
Coefficients to be used by the MA process.
n_points: int
Number of points to generate.
noise_std: float, optional
Scale of the generated noise (default: 1).
noise_alpha: float, optional
Parameter of the alpha-stable distribution (default: 2). Default
value corresponds to Gaussian distribution.
warmup: int, optional
Number of points to generate as a warmup for the model
(default: 0).
Returns:
Discrete series (array of length n_points) generated by the
ARFIMA(len(ar_params), d, len(ma_params)) process.
"""
ma_series = __ma_model(
ma_params, n_points + warmup, noise_std=noise_std, noise_alpha=noise_alpha
)
frac_ma = __frac_diff(ma_series, -d)
series = __arma_model(ar_params, frac_ma)
return series[-n_points:]